#!/usr/bin/env python3 # SPDX-License-Identifier: MPL-2.0 import json import os import re from pathlib import Path import shutil import subprocess import time os.umask(0o022) for name in ('gaia-audit.json', 'gaia-native.png', 'gecko.log', 'gaia-server.log', 'memory-samples.jsonl', 'keypad-request.json', 'keypad-response.json', 'manual-request.json', 'manual-response.json'): Path('/work',name).unlink(missing_ok=True) ROOT = Path('/opt/vulpes') PROFILE = Path('/work/gaia-profile') PROFILE.mkdir(exist_ok=True) os.chown(PROFILE,2000,2000) prefs = { 'browser.shell.checkDefaultBrowser': False, 'browser.aboutwelcome.enabled': False, 'browser.startup.homepage_override.mstone': 'ignore', 'browser.dom.window.dump.enabled': True, 'datareporting.policy.dataSubmissionEnabled': False, 'app.update.auto': False, 'fission.autostart': False, 'dom.ipc.processCount': 1, 'dom.ipc.processPrelaunch.enabled': False, 'gfx.webrender.software': True, 'layers.acceleration.disabled': True, 'layout.css.devPixelsPerPx': '1.0', 'browser.cache.disk.enable': False, 'browser.cache.memory.capacity': 8192, } (PROFILE/'user.js').write_text('\n'.join('user_pref(%s,%s);'%(json.dumps(k),json.dumps(v)) for k,v in prefs.items())) os.chown(PROFILE/'user.js',2000,2000) os.chmod(PROFILE/'user.js',0o600) saved = PROFILE/'vulpes-settings-v1.json' if not saved.exists(): saved.write_text(json.dumps({'lockscreen.enabled':False,'vulpes.gaia-restoration.v1':True,'language.current':'fr'})) os.chown(saved,2000,2000) shutil.copy2(ROOT/'host/bootstrap/vulpes-autoconfig.js','/opt/firefox/defaults/pref/vulpes-autoconfig.js') config=(ROOT/'host/bootstrap/vulpes.cfg').read_text() config+="\nChromeUtils.importESModule('resource://vulpes/tests/BeatlesAudit.sys.mjs').install();\n" Path('/opt/firefox/vulpes.cfg').write_text(config) os.chmod('/opt/firefox/vulpes.cfg',0o644) for directory in ('logs','profiles'): (ROOT/directory).mkdir(exist_ok=True) os.chown(ROOT/directory,2000,2000) for path in (ROOT/'assets').iterdir(): if path.is_file():os.chown(path,2000,2000) def unprivileged(): Path('/proc/self/oom_score_adj').write_text('800') # Android's paranoid-network kernel requires inet even for private loopback. os.setgroups([3003]) os.setgid(2000) os.setuid(2000) env=os.environ.copy() env.update(HOME='/work',TMPDIR='/tmp',DISPLAY=':0',MOZ_ENABLE_WAYLAND='0', VULPES_HOST_ROOT='/opt/firefox/vulpes/host',VULPES_WORKSPACE_ROOT='/opt/vulpes', VULPES_FULLSCREEN='1',VULPES_KEYPAD='1',VULPES_NATIVE_TOUCH='1', VULPES_DEVICE='beatles',VULPES_TUNDRA_STATUS='1', MOZ_CRASHREPORTER_DISABLE='1',FONTCONFIG_PATH='/etc/fonts') duration=1780 if os.environ.get("VULPES_TRIAL_MANUAL")=="1" else 160 processes=[] handles=[] try: def start(command,log,drop=False): path=Path('/work/'+log) output=path.open('w');os.chmod(path,0o644);handles.append(output) proc=subprocess.Popen(command,env=env,stdout=output,stderr=subprocess.STDOUT, preexec_fn=unprivileged if drop else None) processes.append(proc) return proc xorg=start(['/usr/lib/xorg/Xorg',':0','vt1','-config','/etc/X11/xorg.conf', '-logfile','/work/Xorg.log','-nolisten','tcp','-ac','-noreset','-novtswitch','-sharevts','-nocursor'],'xorg-console.log') for _ in range(100): if xorg.poll() is not None:raise RuntimeError('Xorg failed') if Path('/tmp/.X11-unix/X0').exists():break time.sleep(.1) keypad=start(['/work/keypad'],'keypad.log') for _ in range(120): if keypad.poll() is not None:raise RuntimeError('Physical keypad bridge failed; inspect keypad.log') if 'Nokia keypad bridge ready' in Path('/work/keypad.log').read_text():break time.sleep(.1) else:raise RuntimeError('Physical keypad bridge startup timed out') cover=start(['/usr/bin/python3.11','/work/cover-display.py'],'cover-display.log') for _ in range(50): if cover.poll() is not None:raise RuntimeError('External display failed; inspect cover-display.log') if Path('/work/cover-ready').exists():break time.sleep(.1) else:raise RuntimeError('External display startup timed out') server=start(['/usr/bin/python3.11',str(ROOT/'tools/serve-gaia.py')],'gaia-server.log',True) import socket for _ in range(100): if server.poll() is not None:raise RuntimeError('Gaia server failed') try: with socket.create_connection(('127.0.0.1',8765),timeout=.2):break except OSError:time.sleep(.1) gecko=start(['/opt/firefox/firefox','-no-remote','-purgecaches','-profile',str(PROFILE), '-chrome','chrome://vulpes/content/shell.xhtml'],'gecko.log',True) begin=time.monotonic() last_key_sequence=0 last_memory_sample=0 with Path('/work/memory-samples.jsonl').open('w') as memory: while gecko.poll() is None and time.monotonic()-begin=2: memory.write(json.dumps(sample)+'\n');memory.flush();last_memory_sample=time.monotonic() time.sleep(.1) print('Gecko exit:',gecko.poll(),flush=True) finally: for proc in reversed(processes): if proc.poll() is None:proc.terminate() for proc in reversed(processes): try:proc.wait(timeout=3) except subprocess.TimeoutExpired:proc.kill();proc.wait() for handle in handles:handle.close()