#!/usr/bin/env python3 # SPDX-License-Identifier: MPL-2.0 """Generate native trial configuration inside an already staged runtime tree.""" import argparse import runpy from pathlib import Path import shutil def prepare(root, network=None, hardware=None, diagnostics=False): if hardware is None: hardware = (root/'usr/sbin/ofonod').is_file() if network is None: network = (root/'usr/sbin/NetworkManager').is_file() if hardware and not network: raise ValueError('Hardware diagnostics require the isolated NV/network setup') if not (root/'opt/vulpes/trial/session.py').is_file():raise ValueError('Stage the common product first') def write(name,text,mode=0o644): p=root/name;p.parent.mkdir(parents=True,exist_ok=True);p.write_text(text);p.chmod(mode) fonts = Path(__file__).resolve().parents[2]/'gaia/shared/elements/vulpes-fonts' destination = root/'usr/share/fonts/opentype/vulpes' destination.mkdir(parents=True, exist_ok=True) for source in sorted(fonts.glob('*.otf')): shutil.copyfile(source, destination/source.name) (destination/source.name).chmod(0o644) for name in ('LICENSE-OFL', 'NOTICE', 'provenance.json'): shutil.copyfile(fonts/name, destination/name) write('etc/fonts/conf.d/00-vulpes-sans.conf', ''' sans-serifFira Sans FiraSansFira Sans ''') # fc-cache inherits HOME; running it as root leaves a root-owned user cache. write('etc/systemd/system/tundra-display.service.d/fonts.conf', '''[Service] ExecStartPre=/usr/bin/fc-cache -f /usr/share/fonts/opentype/vulpes ''') write('etc/os-release','NAME="Tundra"\nID=tundra\nPRETTY_NAME="Tundra Sargo development trial"\n') (root/'etc/tundra').mkdir(parents=True, exist_ok=True) shutil.copy2(Path(__file__).with_name('clock-state.py'), root/'etc/tundra/clock-state.py') write('etc/systemd/system/tundra-clock-restore.service', """[Unit] Description=Restore Tundra clock from persistent state and RTC elapsed time DefaultDependencies=no After=local-fs.target Before=time-set.target tundra-display.service tundra-radio.service [Service] Type=oneshot ExecStart=/usr/bin/python3 /etc/tundra/clock-state.py --restore RemainAfterExit=yes ExecStop=/usr/bin/python3 /etc/tundra/clock-state.py --save StateDirectory=tundra/clock """) write('etc/systemd/system/tundra-clock-save.service', """[Unit] Description=Checkpoint Tundra clock After=tundra-clock-restore.service [Service] Type=oneshot ExecStart=/usr/bin/python3 /etc/tundra/clock-state.py --save StateDirectory=tundra/clock """) write('etc/systemd/system/tundra-clock-save.timer', """[Unit] Description=Checkpoint Tundra clock once per minute [Timer] OnBootSec=60 OnUnitActiveSec=60 """) write('etc/systemd/system/tundra-cpuidle.service', '''[Unit] Description=Enable Qualcomm CPU low-power states for Tundra After=systemd-modules-load.service tundra-hal.service Before=tundra-display.service [Service] Type=oneshot ExecStartPre=/usr/bin/python3 /etc/tundra/wait-hal.py ExecStart=/usr/bin/python3 /etc/tundra/enable-cpuidle.py RemainAfterExit=yes ''') shutil.copy2(Path(__file__).with_name('enable-cpuidle.py'), root/'etc/tundra/enable-cpuidle.py') wants = root/'etc/systemd/system/tundra.target.wants' wants.mkdir(parents=True, exist_ok=True) for unit in ('tundra-clock-restore.service', 'tundra-clock-save.timer'): (wants/unit).symlink_to('../'+unit) write('etc/hostname','vulpes-sargo\n') write('etc/shells','/bin/sh\n/bin/bash\n/usr/bin/bash\n') write('etc/pam.d/tundra','auth required pam_permit.so\naccount required pam_permit.so\nsession required pam_systemd.so\n') write('etc/pam.d/other','auth required pam_deny.so\naccount required pam_deny.so\npassword required pam_deny.so\nsession required pam_deny.so\n') write('etc/pam.d/common-account','account required pam_unix.so\n') write('etc/pam.d/common-session-noninteractive','session required pam_unix.so\n') write('etc/systemd/logind.conf','[Login]\nHandlePowerKey=ignore\nIdleAction=suspend\nIdleActionSec=30s\n') write('etc/systemd/journald.conf','[Journal]\nStorage=volatile\nRuntimeMaxUse=32M\n') write('etc/passwd','root:x:0:0:root:/root:/bin/bash\ndroidian:x:32011:32011:Vulpes trial:/home/droidian:/bin/bash\nsystem:x:1000:1000:Android HAL:/:/usr/sbin/nologin\n') write('etc/group','root:x:0:\ndroidian:x:32011:\nsystem:x:1000:droidian\ngraphics:x:1003:droidian\nandroid_input:x:1004:droidian\nvideo:x:44:droidian\nrender:x:105:droidian\ninput:x:104:droidian\nnetdev:x:988:\n') write('etc/shadow','root:!:20000:0:99999:7:::\ndroidian:!:20000:0:99999:7:::\nsystem:!:20000:0:99999:7:::\n',0o600) write('etc/tundra/phoc.ini','[output:HWCOMPOSER-1]\nscale=3\n') write('etc/udev/rules.d/90-tundra-display.rules','KERNEL=="kgsl*", GROUP="graphics", MODE="0660"\nKERNEL=="ion", GROUP="graphics", MODE="0660"\nKERNEL=="event*", GROUP="input", MODE="0660"\nKERNEL=="*binder", MODE="0666"\n') # The Android camera subdevices are not generic V4L capture devices. The # desktop v4l_id probe opens them before their HAL and crashes this kernel. (root/'etc/udev/rules.d/60-persistent-v4l.rules').symlink_to('/dev/null') write('etc/systemd/system/tundra.target','''[Unit] Description=Tundra temporary hardware trial Requires=basic.target tundra-display.service After=basic.target tundra-display.service AllowIsolate=yes ''') # systemd does not consistently honor a --unit argument passed through # switch_root on the Android-derived initramfs. Make the trial target the # normal boot target as well, so the display starts without an SSH nudge. default_target = root/'etc/systemd/system/default.target' if default_target.exists() or default_target.is_symlink(): default_target.unlink() default_target.symlink_to('tundra.target') write('etc/systemd/system/tundra-hal.service','''[Unit] Description=Curated Android display HAL After=systemd-udev-trigger.service [Service] Type=simple ExecStartPre=/usr/bin/python3 /etc/tundra/setup-hal.py ExecStart=/usr/bin/python3 /etc/tundra/lxc-guard.py -F -n tundra-hal -f /etc/tundra/lxc.conf -o /run/tundra/lxc.log -l DEBUG KillMode=control-group TimeoutStopSec=15 ''') write('etc/systemd/system/tundra-display.service','''[Unit] Description=Gaia and Gecko on the Sargo display Requires=tundra-hal.service dbus.service systemd-logind.service Wants=tundra-cpuidle.service After=tundra-hal.service dbus.service systemd-logind.service tundra-cpuidle.service [Service] User=droidian PAMName=tundra WorkingDirectory=/home/droidian Environment=XDG_RUNTIME_DIR=/run/user/32011 Environment=XDG_SESSION_TYPE=wayland Environment=XDG_CURRENT_DESKTOP=Vulpes Environment=LANG=C.UTF-8 Environment=VULPES_TUNDRA=1 Environment=VULPES_DEVICE=sargo Environment=VULPES_TUNDRA_RADIO=1 Environment=WLR_BACKENDS=hwcomposer,libinput Environment=EGL_PLATFORM=hwcomposer Environment=WLR_HWC_SKIP_VERSION_CHECK=1 ExecStartPre=+/usr/bin/python3 /etc/tundra/wait-hal.py ExecStartPre=+chvt 7 ExecStart=/usr/bin/dbus-run-session -- /usr/bin/phoc --no-xwayland --socket=tundra-0 -C /etc/tundra/phoc.ini -E "/usr/bin/python3 /opt/vulpes/trial/session.py --own-compositor --hybris-egl" TTYPath=/dev/tty7 TTYReset=yes TTYVHangup=yes StandardInput=tty-fail StandardOutput=journal StandardError=inherit KillMode=control-group TimeoutStartSec=90 TimeoutStopSec=15 ''') if diagnostics: display = root/'etc/systemd/system/tundra-display.service' display.write_text(display.read_text().replace('--own-compositor --hybris-egl', '--own-compositor --hybris-egl --diagnostics')) write('etc/tundra/wait-hal.py','''import subprocess,time deadline=time.monotonic()+8 while time.monotonic()