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.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,217 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MPL-2.0
"""Check or temporarily boot the qualified Sargo USB diagnostic. Never flash."""
import argparse
import hashlib
import importlib.util
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import time
from boot_backup import verify
from tundra import ROOT, digest, now, save
from usb_protocol import exchange, device_layout
def checked_plan():
path = ROOT/'logs/device/boot-backup-qualification.json'
validation = json.loads((ROOT/'VALIDATION.json').read_text())
if validation.get('bootBackup', {}).get('reportSha256') != digest(path):
raise ValueError('Backup qualification report changed')
plan = json.loads(path.read_text())
backup = Path(plan['backupDirectory'])
verify(backup)
if digest(backup/'manifest.json') != plan['backupManifestSha256']:
raise ValueError('Backup manifest changed; requalify')
image = ROOT/plan['diagnosticImage']
if digest(image) != plan['diagnosticImageSha256'] or digest(image.parent/'build.json') != plan['diagnosticBuildSha256']:
raise ValueError('Diagnostic image/build changed; requalify')
build = json.loads((image.parent/'build.json').read_text())
if build['sourceSha256'] != digest(ROOT/'boot/usb-init.c') or build['sharedInitSha256'] != digest(ROOT/'boot/init.c'):
raise ValueError('Diagnostic sources changed; rebuild and requalify')
if not build['roundtripPassed'] or build.get('rawBootSlot') != 'a' or build['gaiaIncluded']:
raise ValueError('Expected the roundtrip-checked USB-only slot A diagnostic')
return plan, image
def fastboot(serial, *args):
# Whitelist the complete command shape: no flash, erase, slot or unlock path.
if not (len(args) == 2 and args[0] in ('getvar', 'boot')):
raise ValueError('Only getvar and temporary boot are implemented')
return subprocess.run(['fastboot','-s',serial,*args], capture_output=True,
text=True, check=True, timeout=45)
def getvar(serial, name):
result = fastboot(serial, 'getvar', name)
for line in (result.stdout+'\n'+result.stderr).splitlines():
match = re.fullmatch(r'(?:\(bootloader\)\s*)?'+re.escape(name)+r':\s*(.+)',line.strip())
if match:
return match[1].strip()
raise RuntimeError('Fastboot did not return '+name)
def check_device(serial, expected_hash):
if hashlib.sha256(serial.encode()).hexdigest() != expected_hash:
raise ValueError('Fastboot device differs from the inventoried phone')
values = {name:getvar(serial,name) for name in ('product','unlocked','current-slot','partition-size:boot_a')}
if values['product'] != 'sargo' or values['unlocked'] != 'yes' or values['current-slot'] != 'a':
raise ValueError('Expected unlocked Sargo on slot A; device left untouched')
if int(values['partition-size:boot_a'],16) != 67108864:
raise ValueError('Boot partition size differs from inventory')
return values
def reboot_from_droidian(plan):
if not sys.stdin.isatty():
raise RuntimeError('Run in your terminal to enter the phone sudo password')
spec = importlib.util.spec_from_file_location('collector', ROOT/'tools/collect-device-ssh.py')
collector = importlib.util.module_from_spec(spec)
spec.loader.exec_module(collector)
ssh = collector.ssh_base(ROOT/'logs/device/ssh-control', '10.15.19.82', 'droidian')
serial = subprocess.check_output(ssh+[shlex.join(['getprop','ro.boot.serialno'])],text=True,timeout=10).strip()
if hashlib.sha256(serial.encode()).hexdigest() != plan['serialSha256']:
raise ValueError('SSH identity changed; refusing reboot')
print('Rebooting the inventoried phone into Fastboot. Enter its sudo password if requested.',flush=True)
result = subprocess.run(ssh[:-1]+['-t',ssh[-1],
shlex.join(['sudo','/usr/bin/systemctl','reboot','--reboot-argument=bootloader'])])
# The SSH transport can disappear as shutdown completes. Fastboot must still identify the phone.
if result.returncode not in (0,255):
raise RuntimeError('Phone reboot request failed')
def find_fastboot(expected_hash, timeout):
deadline = time.monotonic()+timeout
while True:
output = subprocess.check_output(['fastboot','devices'],text=True,timeout=5)
for line in output.splitlines():
fields = line.split()
if len(fields) >= 2 and fields[1] == 'fastboot' and hashlib.sha256(fields[0].encode()).hexdigest() == expected_hash:
return fields[0]
if time.monotonic() >= deadline:
raise RuntimeError('Inventoried phone not present in Fastboot; no boot command sent')
time.sleep(1)
def find_diagnostic():
matches = []
for p in Path('/sys/bus/usb/devices').iterdir():
try:
if [(p/key).read_text().strip() for key in ('idVendor','idProduct','serial')] == ['1d6b','0104','TUNDRA-DIAG']:
matches.append(Path('/dev/bus/usb')/f'{int((p/"busnum").read_text()):03d}'/f'{int((p/"devnum").read_text()):03d}')
except (OSError,ValueError):
continue
if len(matches) > 1:
raise RuntimeError('Multiple USB diagnostics present; select a device manually')
return matches[0] if matches else None
def main():
parser = argparse.ArgumentParser(description=__doc__)
mode = parser.add_mutually_exclusive_group()
mode.add_argument('--boot',action='store_true',help='Download and boot the diagnostic in RAM')
mode.add_argument('--collect',action='store_true',help='Resume USB checks on the already running diagnostic; no reboot')
parser.add_argument('--reboot-from-droidian',action='store_true',help='Request sudo reboot through the existing SSH master')
args = parser.parse_args()
if args.reboot_from_droidian and not args.boot:
parser.error('--reboot-from-droidian requires --boot')
os.umask(0o077)
plan,image = checked_plan()
print('Backup hashes, image and source provenance: OK',flush=True)
print('This diagnostic has no graphical interface.',flush=True)
if not args.boot and not args.collect:
print('Local checks only; no device operation performed.')
return
if args.reboot_from_droidian:
reboot_from_droidian(plan)
previous = None
values = None
if args.collect:
for candidate in sorted((ROOT/'logs/device').glob('temporary-boot-*.json'),reverse=True):
old = json.loads(candidate.read_text())
if (old.get('operation') == 'fastboot boot' and old.get('fastbootOutput')
and old.get('imageSha256') == plan['diagnosticImageSha256']
and old.get('serialSha256') == plan['serialSha256']):
previous = {'report':str(candidate.relative_to(ROOT)), 'sha256':digest(candidate)}
break
if not previous:
raise RuntimeError('No previous boot report matches this image and phone')
if not find_diagnostic():
raise RuntimeError('Diagnostic USB is no longer present; no reboot requested')
else:
serial = find_fastboot(plan['serialSha256'],60 if args.reboot_from_droidian else 0)
values = check_device(serial,plan['serialSha256'])
if find_diagnostic():
raise RuntimeError('A diagnostic is already connected; use --collect to resume its checks')
report = {'recordedAt':now(),'serialSha256':plan['serialSha256'],
'imageSha256':digest(image),'bootloader':values,'operation':'usb collect' if args.collect else 'fastboot boot',
'previousBoot':previous,
'flashCommandsSent':False,'passed':False,'gaiaIncluded':False}
path = ROOT/'logs/device'/('temporary-boot-'+now().replace(':','-')+'.json')
save(path,report)
try:
if not args.collect:
print('Booting diagnostic in RAM; waiting up to 90 seconds for its USB interface.',flush=True)
result = fastboot(serial,'boot',str(image))
report['fastbootOutput'] = result.stdout+result.stderr
deadline = time.monotonic()+90
device = None
while time.monotonic() < deadline:
device = find_diagnostic()
if device:
break
time.sleep(1)
if not device:
raise RuntimeError('Diagnostic USB not detected; hardware boot remains unvalidated')
report['usbDevice'] = str(device)
report['usbLayout'] = device_layout(device)
layout = report['usbLayout']
print(f"USB interface {layout['interface']}: OUT 0x{layout['out']:02x}, IN 0x{layout['in']:02x}",flush=True)
try:
report['usb'] = exchange(device,('status','version','mounts','invalid'))
except PermissionError:
if not sys.stdin.isatty():
raise RuntimeError('USB access denied; rerun this script with --collect in your terminal (PC sudo required)')
print('Linux USB access needs the PC sudo password. Only the diagnostic reader runs as root.',flush=True)
report['usb'] = json.loads(subprocess.check_output(['sudo',sys.executable,
str(ROOT/'tools/read-usb.py'),'--device',str(device),'--stdout','--check-invalid'],text=True))
if report['usb']['status'] != 'TUNDRA_USB_STATUS protocol=1 readOnly=true\nEND\n':
raise RuntimeError('Unexpected diagnostic status')
if '4.9-124-google-sargo' not in report['usb']['version'] or report['usb']['invalid'] != 'ERROR unsupported command\nEND\n':
raise RuntimeError('Unexpected kernel or command handling')
if any(line.startswith('/dev/') for line in report['usb']['mounts'].splitlines()):
raise RuntimeError('Unexpected block device mount')
report['passed'] = True
print('ARM64 diagnostic and read-only USB protocol validated on Sargo.',flush=True)
except Exception as error:
report['error'] = str(error)
raise
finally:
save(path,report)
if report['passed']:
validation = json.loads((ROOT/'VALIDATION.json').read_text())
validation['phase'] = 'pixel-diagnostic-boot-validated'
validation['sargoHardwareDiagnostic'] = {
'report':str(path.relative_to(ROOT)), 'reportSha256':digest(path),
'imageSha256':report['imageSha256'], 'passed':True,
'scope':'Temporary ARM64 init and USB on Sargo; no graphical system',
'gaiaIncluded':False, 'flashReady':False}
validation['rawSlotADiagnostic']['hardwareTested'] = True
save(ROOT/'VALIDATION.json',validation)
print('Report:',path,flush=True)
if report['passed']:
print('Return to Fastboot using the phone buttons, then select Start to boot Droidian.',flush=True)
else:
print('You can retry --collect without rebooting while the diagnostic remains connected.',flush=True)
if __name__ == '__main__':
try:
main()
except (OSError,ValueError,RuntimeError,subprocess.SubprocessError) as error:
print('Tundra boot test:',error,file=sys.stderr)
sys.exit(1)