Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
164 lines
6.1 KiB
Python
164 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Prepare and run the independent Gaia desktop on a pinned Mozilla runtime."""
|
|
import argparse
|
|
import fcntl
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import socket
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
from project import validate_assets
|
|
|
|
|
|
def prepare(lock, test=False):
|
|
runtime = ROOT / lock["runtime"]
|
|
if not (runtime / "firefox").is_file():
|
|
raise RuntimeError(
|
|
"Moteur absent. Exécuter ./setup.sh pour installer le moteur verrouillé."
|
|
)
|
|
validate_assets()
|
|
for name in ["host", "services", "adapters"] + (["tests"] if test else []):
|
|
shutil.copytree(ROOT / name, runtime / "vulpes" / name, dirs_exist_ok=True)
|
|
(runtime / "defaults/pref").mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(ROOT / "host/bootstrap/vulpes.cfg", runtime / "vulpes.cfg")
|
|
shutil.copy2(
|
|
ROOT / "host/bootstrap/vulpes-autoconfig.js", runtime / "defaults/pref/vulpes-autoconfig.js"
|
|
)
|
|
return runtime
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--candidate", action="store_true")
|
|
parser.add_argument(
|
|
"--legacy-apps", action="store_true", help="Trois paquets archivés, profil séparé"
|
|
)
|
|
parser.add_argument(
|
|
"--test", action="store_true", help="Marionette 2857, profil de test et fenêtre auxiliaire"
|
|
)
|
|
parser.add_argument("--prepare-only", action="store_true")
|
|
args = parser.parse_args()
|
|
lock = json.loads(
|
|
(ROOT / ("candidate-engine.json" if args.candidate else "engine-lock.json")).read_text()
|
|
)
|
|
(ROOT / "logs").mkdir(exist_ok=True)
|
|
# One host owns the loopback origins/registry. Never attach silently to an
|
|
# unknown HTTP listener or to another application's profile.
|
|
with (ROOT / "logs/host.lock").open("w") as guard:
|
|
try:
|
|
fcntl.flock(guard, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
except BlockingIOError:
|
|
raise RuntimeError(
|
|
"Un hôte Gecko actuel est déjà ouvert. Fermer sa fenêtre avant de relancer."
|
|
)
|
|
if args.legacy_apps:
|
|
subprocess.run([sys.executable, str(ROOT / "tools/prepare-legacy-apps.py")], check=True)
|
|
runtime = prepare(lock, args.test)
|
|
if args.prepare_only:
|
|
print("Adaptateur synchronisé :", runtime)
|
|
return
|
|
probe = socket.socket()
|
|
probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
try:
|
|
probe.bind(("127.0.0.1", 8765))
|
|
except OSError:
|
|
raise RuntimeError("Port Gaia 8765 déjà occupé. Fermer le précédent hôte/serveur Gaia.")
|
|
finally:
|
|
probe.close()
|
|
profile = (
|
|
ROOT
|
|
/ "profiles"
|
|
/ (
|
|
("host-" if args.test else "desktop-")
|
|
+ ("legacy-" if args.legacy_apps else "")
|
|
+ lock["version"]
|
|
)
|
|
)
|
|
profile.mkdir(parents=True, exist_ok=True)
|
|
prefs = {
|
|
"browser.shell.checkDefaultBrowser": False,
|
|
"browser.dom.window.dump.enabled": True,
|
|
"browser.startup.homepage_override.mstone": "ignore",
|
|
"browser.aboutwelcome.enabled": False,
|
|
"font.default.x-western": "sans-serif",
|
|
"font.name.sans-serif.x-western": "Fira Sans",
|
|
"marionette.enabled": args.test,
|
|
"datareporting.policy.dataSubmissionEnabled": False,
|
|
}
|
|
if args.test:
|
|
prefs["marionette.port"] = 2857
|
|
(profile / "user.js").write_text(
|
|
"\n".join(f"user_pref({json.dumps(k)}, {json.dumps(v)});" for k, v in prefs.items())
|
|
+ "\n"
|
|
)
|
|
env = os.environ.copy()
|
|
for name in list(env):
|
|
if name.startswith("MOZ_DISABLE_") or name in (
|
|
"B2G_HOMESCREEN",
|
|
"MOZ_MARIONETTE",
|
|
"MOZ_HEADLESS",
|
|
):
|
|
env.pop(name)
|
|
env.update(VULPES_HOST_ROOT=str(runtime / "vulpes/host"), VULPES_WORKSPACE_ROOT=str(ROOT))
|
|
command = [
|
|
str(runtime / "firefox"),
|
|
"-no-remote",
|
|
"-purgecaches",
|
|
"-profile",
|
|
str(profile),
|
|
"-chrome",
|
|
"chrome://vulpes/content/shell.xhtml",
|
|
]
|
|
if args.test:
|
|
command += ["-marionette", "--remote-allow-system-access", "-new-window", "about:blank"]
|
|
proc = None
|
|
with (ROOT / "logs/gaia-server.log").open("w") as output:
|
|
server = subprocess.Popen(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools/serve-gaia.py"),
|
|
*(["--legacy-apps"] if args.legacy_apps else []),
|
|
],
|
|
stdout=output,
|
|
stderr=subprocess.STDOUT,
|
|
)
|
|
try:
|
|
for _ in range(900):
|
|
if server.poll() is not None:
|
|
raise RuntimeError("Échec du serveur Gaia : logs/gaia-server.log")
|
|
try:
|
|
with socket.create_connection(("127.0.0.1", 8765), timeout=0.2):
|
|
break
|
|
except OSError:
|
|
time.sleep(0.05)
|
|
else:
|
|
raise RuntimeError("Le serveur Gaia ne répond pas.")
|
|
print(f'Vulpes / Gecko {lock["version"]} — profil {profile}', flush=True)
|
|
proc = subprocess.Popen(command, env=env)
|
|
result = proc.wait()
|
|
if result:
|
|
raise RuntimeError(f"Gecko a quitté avec le code {result}")
|
|
finally:
|
|
if proc and proc.poll() is None:
|
|
proc.terminate()
|
|
try:
|
|
proc.wait(timeout=10)
|
|
except subprocess.TimeoutExpired:
|
|
proc.kill()
|
|
proc.wait()
|
|
server.terminate()
|
|
server.wait(timeout=10)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
main()
|
|
except KeyboardInterrupt:
|
|
pass
|
|
except (RuntimeError, OSError) as error:
|
|
sys.exit(str(error))
|