VulpesOS/tools/run-host.py
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

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))