Update shared Gaia workflows and Sargo development tooling

Fix camera switching and Contacts/SMS activity routing across shared hosts.
Integrate Marketplace installation and native standalone recovery tools,
Bluetooth runtime packages and trusted certificates.

Update English/French READMEs and the shared changelog with tested scope
and remaining radio/audio/Bluetooth limitations. Preview17 remains the
existing APK; no new native image is released.

Validation: shared JavaScript and native Python tests; Android packaging
and payload parity from an isolated publication checkout.
This commit is contained in:
OverSu 2026-10-08 14:27:17 +02:00
commit 0041cd9660
101 changed files with 3883 additions and 335 deletions

View file

@ -0,0 +1,63 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MPL-2.0
"""Package boot, fresh userdata and boot metadata for a standalone installation.
Builds local artifacts only. A candidate is never marked flash-qualified here.
Private development credentials, if present in boot, prohibit redistribution.
"""
import argparse
import importlib.util
import json
import os
from pathlib import Path
import shutil
import subprocess
from standalone_volume import checked_volume
from tundra import ROOT,digest
def main():
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('--build',required=True,type=Path)
parser.add_argument('--volume',required=True,type=Path)
parser.add_argument('--dtbo',required=True,type=Path)
parser.add_argument('--output',required=True,type=Path)
args=parser.parse_args();os.umask(0o077)
volume=checked_volume(args.volume);build=json.loads((args.build/'build.json').read_text())
if build.get('storageMode')!='raw-userdata-ext4' or build.get('watchdogSeconds') is not None or build.get('localDebugger'):
raise ValueError('Expected a persistent standalone boot without debugger or timed reboot')
if build['standaloneVolume']!=volume or not volume.get('hardwareImages'):
raise ValueError('Boot must match a volume with bundled vendor/modem images')
boot=args.build/build['image']
if digest(boot)!=build['imageSha256']:raise ValueError('Boot image changed')
spec=importlib.util.spec_from_file_location('sources',ROOT/'tools/test-rescue-boot.py')
sources=importlib.util.module_from_spec(spec);spec.loader.exec_module(sources);sources.verify_sources(args.build,build)
lock=json.loads((ROOT/'manifests/sargo-hardware.json').read_text())
if args.dtbo.stat().st_size!=lock['dtbo']['bytes'] or digest(args.dtbo)!=lock['dtbo']['sha256']:
raise ValueError('Unqualified device-tree overlay')
out=args.output.resolve();out.mkdir(parents=True,exist_ok=False)
(out/'.incomplete').touch()
shutil.copyfile(boot,out/'boot.img');shutil.copyfile(args.dtbo,out/'dtbo.img')
subprocess.run(['avbtool','make_vbmeta_image','--flags','3','--padding_size','4096','--algorithm','NONE','--output',str(out/'vbmeta.img')],check=True)
subprocess.run(['img2simg',str(args.volume.parent/volume['image']),str(out/'userdata.img')],check=True)
# Roundtrip verifies the actual sparse payload, not only its header.
restored=out/'userdata-roundtrip.ext4'
subprocess.run(['simg2img',str(out/'userdata.img'),str(restored)],check=True)
if digest(restored)!=volume['imageSha256']:raise ValueError('Sparse image roundtrip mismatch')
restored.unlink()
files={name:{'file':file,'bytes':(out/file).stat().st_size,'sha256':digest(out/file)}
for name,file in [('boot_a','boot.img'),('userdata','userdata.img'),('dtbo_a','dtbo.img'),('vbmeta_a','vbmeta.img')]}
value={'schema':1,'device':'sargo','storage':'raw-userdata-ext4','partitions':files,
'userdataBytes':volume['bytes'],'userdataRawSha256':volume['imageSha256'],
'volumeUuid':volume['uuid'],'systemImageSha256':volume['systemImageSha256'],
'flashQualified':False,'containsPrivateMaintenanceKey':bool(build.get('containsPrivateTrialHostKey')),
'redistributionReady':False,'previousOsRequired':False,
'requirements':['Unlocked Pixel 3a (sargo)','Original device calibration partitions retained'],
'scope':'Experimental standalone installation candidate; userdata is replaced in full'}
(out/'package.json').write_text(json.dumps(value,indent=2)+'\n')
shutil.copyfile(ROOT/'tools/flash-standalone-sargo.py',out/'flash-standalone-sargo.py')
(out/'SHA256SUMS').write_text(''.join(digest(p)+' '+p.name+'\n' for p in sorted(out.iterdir()) if p.is_file() and p.name not in ('.incomplete','SHA256SUMS')))
(out/'.incomplete').unlink();print(out/'package.json')
if __name__=='__main__':main()