Release Vulpes OS 2.7-preview.24

This commit is contained in:
OverSu 2026-11-04 12:52:20 +01:00
commit 97ef99f827
135 changed files with 7717 additions and 2524 deletions

View file

@ -18,6 +18,7 @@ from cryptography.hazmat.primitives import serialization
from ramdisk_reference import read_newc, select_helpers
from storage_layout import mount_table
from shared_ui import compress_archive
from kernel_cmdline import vulpes_cmdline
from tundra import ROOT, cpio, checked_source, digest, save, linker
@ -33,7 +34,9 @@ def main():
parser.add_argument('--layout',type=Path,required=True)
parser.add_argument('--device-workspace', type=Path,
help='Private per-phone workspace; never use the development inventory')
parser.add_argument('--native',action='store_true',help='Switch to the staged native root with a ten-minute watchdog')
parser.add_argument('--native',action='store_true',help='Switch to the staged native root with a timed watchdog')
parser.add_argument('--watchdog-seconds', type=int, default=600,
help='RAM trial auto-reboot deadline (60–3600 seconds; default: 600)')
parser.add_argument('--system-image',type=Path,help='system.json for a self-contained image staged at /tundra/images/<sha256>.ext4')
parser.add_argument('--differential-ui', action='store_true', help='Bundle only UI files differing from the verified immutable system image')
parser.add_argument('--hold-hal',action='store_true',help='Boot basic.target first so HAL startup can be traced over USB')
@ -45,6 +48,8 @@ def main():
parser.add_argument('--state', type=Path, help='Prepared Tundra data-image manifest; enables persistent writes in that file')
parser.add_argument('--no-deadline', action='store_true', help='Keep the persistent development boot running; requires --state')
args=parser.parse_args()
if not 60 <= args.watchdog_seconds <= 3600:
parser.error('--watchdog-seconds must be between 60 and 3600')
if args.hold_hal and not args.native:parser.error('--hold-hal requires --native')
if args.system_image and not args.native:parser.error('--system-image requires --native')
if args.differential_ui and not args.system_image:parser.error('--differential-ui requires --system-image')
@ -79,6 +84,9 @@ def main():
else:
plan,_=load_boot().checked_plan()
storage_table = mount_table(layout)
if state and storage_table is None and not (
layout.get('schema') == 2 and layout.get('storage') == 'raw-userdata-ext4'):
raise ValueError('Persistent trials require inventoried ext4 userdata')
out=args.output.resolve();out.mkdir(parents=True,exist_ok=False)
source=Path(plan['backupDirectory'])/'boot_a.img'
packager=checked_source('aosp-mkbootimg')
@ -86,6 +94,8 @@ def main():
result=subprocess.check_output(['python3',str(packager/'unpack_bootimg.py'),'--boot_img',str(source),'--out',str(unpack),'--format=mkbootimg'],text=True)
options=shlex.split(result);fields=dict(zip(options[::2],options[1::2]))
if fields['--header_version']!='0':raise ValueError('Expected header v0')
source_cmdline=fields['--cmdline']
fields['--cmdline'],removed_kernel_parameters=vulpes_cmdline(source_cmdline)
with tempfile.TemporaryDirectory() as temp:
helpers=select_helpers(read_newc((unpack/'ramdisk').read_bytes()),Path(temp),
extra=('sbin/blkid','sbin/e2fsck') if state else ())
@ -113,15 +123,23 @@ def main():
(out/'known_hosts').write_text('[10.15.19.82]:2222 '+hostpublic+'\n[127.0.0.1]:2222 '+hostpublic+'\n')
init=(ROOT/'boot/rescue-init.sh').read_bytes().replace(b'# No LVM activation',
('EXPECTED_STORAGE_HASH='+shlex.quote(layout['firstMiBSha256'])+'\n# No LVM activation').encode())
init=init.replace(b"'0 104448000 linear /dev/mmcblk0p72 329728'",
shlex.quote(storage_table).encode())
sources=[Path(__file__),ROOT/'boot/rescue-init.sh',ROOT/'tools/ramdisk_reference.py',
if storage_table is None:
uuid_hex=layout['filesystemUuid'].replace('-','')
init=init.replace(b'# No LVM activation',
('RAW_USERDATA_EXT4=1\nEXPECTED_STORAGE_UUID_HEX='+uuid_hex+
'\n# No LVM activation').encode())
else:
init=init.replace(b"'0 104448000 linear /dev/mmcblk0p72 329728'",
shlex.quote(storage_table).encode())
sources=[Path(__file__),ROOT/'boot/rescue-init.sh',ROOT/'tools/kernel_cmdline.py',ROOT/'tools/ramdisk_reference.py',
ROOT/'tools/storage_layout.py']
if args.device_workspace:
sources += [ROOT/'tools/sargo_workspace.py', ROOT/'tools/boot_backup.py']
if state:
# Keep the existing layout intact; only /tundra/data/<UUID>.ext4 is used
# for Tundra state. Opening its containing filesystem RW is necessary.
if storage_table is None:
init=init.replace(b'RAW_USERDATA_EXT4=1',b'RAW_USERDATA_EXT4=1\nTUNDRA_USERDATA_RW=1')
init=init.replace(b'--readonly --table', b'--table')
init=init.replace(b'-o ro,noload /dev/mapper/tundra-installed-ro', b'-o rw /dev/mapper/tundra-installed-ro')
init=init.replace(b'# Temporary RAM-only boot. Installed data is never mounted writable.', b'# Private persistent boot. Only the dedicated Tundra data file receives system writes.')
@ -130,7 +148,7 @@ def main():
shared_ui = {}
if args.native:
binary=out/'watchdog'
subprocess.run(['clang','--target=aarch64-linux-gnu','--ld-path='+str(linker()),'-Os','-ffreestanding','-fno-builtin','-fno-stack-protector','-fno-pie','-fno-unwind-tables','-fno-asynchronous-unwind-tables','-Wall','-Wextra','-Werror','-nostdlib','-static','-Wl,--build-id=none','-Wl,-e,_start',str(ROOT/'boot/watchdog.c'),'-o',str(binary)],check=True)
subprocess.run(['clang','--target=aarch64-linux-gnu','--ld-path='+str(linker()),'-Os','-ffreestanding','-fno-builtin','-fno-stack-protector','-fno-pie','-fno-unwind-tables','-fno-asynchronous-unwind-tables','-Wall','-Wextra','-Werror','-nostdlib','-static','-Wl,--build-id=none','-Wl,-e,_start','-DTUNDRA_WATCHDOG_SECONDS='+str(args.watchdog_seconds),str(ROOT/'boot/watchdog.c'),'-o',str(binary)],check=True)
init=init.replace(b"(sleep 480; echo 'Tundra trial deadline reached'; reboot -f) &",b'/sbin/tundra-watchdog &')
if args.no_deadline:
init=init.replace(b'/sbin/tundra-watchdog &', b'# Persistent development boot: no timed reboot.')
@ -174,6 +192,8 @@ def main():
archive.add(config_dir, arcname=directory, filter=owned)
files['native-config.tar']=(0o600,compress_archive(buffer.getvalue()))
sources += list(sorted((ROOT/'runtime').glob('*.py')))
if args.runtime_config:
sources.append(ROOT/'tools/inspect-hardware.py')
files['sbin/tundra-watchdog']=(0o755,binary.read_bytes())
native_init=(ROOT/'boot/native-root.sh').read_bytes().replace(b'# BUILD_TIME_FLOOR', ('BUILD_EPOCH='+str(built_at)).encode())
if args.runtime_config:
@ -181,7 +201,8 @@ def main():
if system:
sha = system['imageSha256']
native_init = native_init.replace(b'# A kernel fallback',
('SYSTEM_IMAGE=/installed/tundra/images/'+sha+'.ext4\nSYSTEM_IMAGE_SHA='+sha+'\n# A kernel fallback').encode())
('SYSTEM_IMAGE=/installed/tundra/images/'+sha+'.ext4\nSYSTEM_IMAGE_SHA='+sha+
'\nSYSTEM_IMAGE_BYTES='+str(system['bytes'])+'\n# A kernel fallback').encode())
# Bundle UI changes with the boot trial rather than retransferring
# the entire immutable system image for each CSS/JS adjustment.
from shared_ui import build_payload, differential_payload
@ -240,9 +261,10 @@ def main():
shutil.copy2(path,target)
save(out/'build.json',{'scope':'Private persistent Sargo development boot; unqualified' if state else ('Private temporary native-root trial; unqualified' if args.native else 'Private temporary SSH boot; installed root read-only; no graphical session'),
'builtAtUnix':built_at,'runtimeConfigOverlay':args.runtime_config,'networkTrial':args.network,'hardwareTrial':args.hardware,'interactiveDiagnostics':args.diagnostics,'image':image.name,'imageSha256':digest(image),'sourceBootSha256':digest(source),
'removedKernelParameters':removed_kernel_parameters,
'serialSha256':plan['serialSha256'],'kernelSha256':kernel_hash,
'deviceWorkspaceSha256':plan.get('workspaceSha256'),
'ramdiskSha256':digest(ramdisk),'roundtripPassed':True,'flashReady':False,'localDebugger':args.local_debugger,'watchdogSeconds':None if args.no_deadline else (600 if args.native else 480),'persistentState':state,'storageWrites':bool(state),'nativeRootRequested':args.native,'halHeldForDiagnostics':args.hold_hal,
'ramdiskSha256':digest(ramdisk),'roundtripPassed':True,'flashReady':False,'localDebugger':args.local_debugger,'watchdogSeconds':None if args.no_deadline else (args.watchdog_seconds if args.native else 480),'persistentState':state,'storageWrites':bool(state),'nativeRootRequested':args.native,'halHeldForDiagnostics':args.hold_hal,
'layout':layout,'layoutSha256':digest(args.layout),'systemImage':system,'sharedUiHashes':shared_ui,'sourceSnapshot':True,'sources':{str(p.relative_to(ROOT)):digest(p) for p in sources},
'helpers':{n:__import__('hashlib').sha256(b).hexdigest() for n,b in helpers.items()},
'sharedUiFilesAlreadyInSystem':omitted_ui if args.differential_ui else 0,