#!/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()