VulpesOS/tundra/runtime/runtime-packages.py
OverSu 1311bed5e9 Release Preview 20 shared desktop, Android and Sargo updates
Restore Gaia settings and media workflows, extend native network adapters, and align the APK payload. Generate French/English changelogs and website entries from the simplified JSON source.

Validation: shared JS and Tundra unit tests; desktop workflows and persistence; signed APK payload parity; Sargo RAM and installed startup with boot readback. Android runtime qualification is recorded separately.
2026-10-13 00:19:45 +02:00

107 lines
7.4 KiB
Python

#!/usr/bin/env python3
# SPDX-License-Identifier: MPL-2.0
"""Stage pinned package payloads missing from the original ARM64 reference."""
import hashlib
import io
import json
from pathlib import Path
import shutil
import subprocess
import tarfile
import tempfile
import urllib.request
PACKAGES = (
('pulseaudio-module-bluetooth_14.2-2+droidian4+git20250504201345.dc4fdb8.next.production_arm64.deb',
'https://production.repo.droidian.org/pool/main/p/pulseaudio',
'f0a6fa15835f1e22a5708dc46356bd30bb492d57655f769aaf4df78b6838f4f3'),
('libsbc1_2.1-1+b2_arm64.deb', 's/sbc',
'62dd7210ff6500e3ee4976d865826d603127787f588c4c4a8f93556104c95634'),
('libdw1t64_0.196-1_arm64.deb', 'e/elfutils', 'a82634454de2b28d73374f693e4ab6c1c2634fa53b0139312c5f439c294dad2b'),
('libelf1t64_0.196-1_arm64.deb', 'e/elfutils', 'ead8a1244824d982ce51fb82be4904312d5dc44ddc157e589def58c257738476'),
('bluez_5.87-4_arm64.deb', 'b/bluez', 'fe733034dbdfa6146fc94112ad8ca0fce71d8e01d22a59ddb49ef2df3777d6b5'),
('ca-certificates_20260816_all.deb', 'c/ca-certificates', '1cb8b73c74c47677b329fad4095fb9370227d4b8f1969372411478a4586c46cf'),
('mobile-broadband-provider-info_20251101-1_all.deb', 'm/mobile-broadband-provider-info', 'c34e6bad952a320078fa91faf29a6c754d57963e7280998a01ccb058e60b87cc'),
('audiosystem-passthrough_1.3.0-1+droidian1+git20240519112654.f49813c.next.production_arm64.deb',
'https://production.repo.droidian.org/pool/main/a/audiosystem-passthrough',
'bb4dc41ee66b3b00e8fbd3e0bb8559ab0b98675836219efef7b02958c47b815d'),
)
# Existing libelf 0.194-4 files in the inventoried runtime; reject other replacements.
REPLACES = {'usr/lib/aarch64-linux-gnu/libelf-0.194.so': 'ade23632ea253cb4ecfe95d5436bfeb3d40ae2003c5fe10bf1ac1c5b30bdf2e2', 'usr/share/doc/libelf1t64/changelog.Debian.gz': 'ffb0ea69fdbf6f32053059985048676931fd7d5c4458f8ca4bc54750cf4b1b31', 'usr/share/doc/libelf1t64/changelog.gz': '2e8e0af5c39ab744ac6934fe07419b5fd5504a50cd2f42110dd04315c548a708', 'usr/share/doc/libelf1t64/copyright': '2409860c86675ab33f8ad22480608faf849cbc2559d6478f708c23c508a1dbf2', 'usr/share/locale/de/LC_MESSAGES/elfutils.mo': 'f72328e130e63f231acb8e1a56ce5f4f3e91d0fc290db95d7ba4e4b2448d4d93', 'usr/share/locale/en@boldquot/LC_MESSAGES/elfutils.mo': '90158a24e3f85aaacc64085ef2644ba19075cb847c754341c9f245aaa69e94c6', 'usr/share/locale/en@quot/LC_MESSAGES/elfutils.mo': '33c6cda39c2145463dfdb2eb849922dbf6ccf51a9e4bfa8fd757744712d9abfa', 'usr/share/locale/es/LC_MESSAGES/elfutils.mo': '1ebff7e5583759b3f08eb5caaf18dd1a6ed6eb5ec71a02502ed8e21b134ad54f', 'usr/share/locale/ja/LC_MESSAGES/elfutils.mo': '764ed859f3aebd861da0b73c69da4d72d0f252952bb3be1c9c66360258a1661c', 'usr/share/locale/pl/LC_MESSAGES/elfutils.mo': '8abb000f43024994a9ec3032b52eb10858e004b81bc61c7ceea96f7467d13fdd', 'usr/share/locale/uk/LC_MESSAGES/elfutils.mo': 'ce0d747f478ee91a1cdef66bfd1e230a3ff1e787358f4c6e1ffb6513df713117'}
def stage(root, cache=None):
cache = cache or Path(__file__).resolve().parents[1]/'downloads/native-packages-20261008'
cache.mkdir(parents=True, exist_ok=True)
records = []
for name, directory, sha in PACKAGES:
base = directory if directory.startswith('https://') else 'https://deb.debian.org/debian/pool/main/'+directory
url = base+'/'+name
package = cache/name
if not package.exists():
with urllib.request.urlopen(url, timeout=60) as response:
data = response.read()
if hashlib.sha256(data).hexdigest() != sha:
raise ValueError('Package checksum mismatch: '+name)
package.write_bytes(data)
if hashlib.sha256(package.read_bytes()).hexdigest() != sha:
raise ValueError('Package checksum mismatch: '+name)
members = subprocess.check_output(['ar', 't', str(package)], text=True).splitlines()
member = next(m for m in members if m.startswith('data.tar'))
payload = subprocess.check_output(['ar', 'p', str(package), member])
with tempfile.TemporaryDirectory() as folder:
source = Path(folder)
with tarfile.open(fileobj=io.BytesIO(payload)) as archive:
archive.extractall(source, filter='data')
if name.startswith('ca-certificates'):
certificates = sorted((source/'usr/share/ca-certificates/mozilla').glob('*.crt'))
if len(certificates) < 50:
raise ValueError('Incomplete CA bundle')
target = root/'etc/ssl/certs/ca-certificates.crt'
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(b'\n'.join(p.read_bytes() for p in certificates)+b'\n')
target.chmod(0o644)
cert_link = root/'usr/lib/ssl/cert.pem'
cert_link.parent.mkdir(parents=True, exist_ok=True)
if not cert_link.exists() and not cert_link.is_symlink():
cert_link.symlink_to('../../../etc/ssl/certs/ca-certificates.crt')
selected = [source/'usr/share/doc/ca-certificates/copyright']
else:
selected = [p for p in (source/'usr').rglob('*') if p.is_file() or p.is_symlink()]
# The packet-capture utility is not used by the runtime. Keep it in
# the pinned package cache rather than enlarging the boot ramdisk.
excluded = ['usr/bin/btmon'] if name.startswith('bluez_') else []
selected = [p for p in selected if str(p.relative_to(source)) not in excluded]
for path in selected:
target = root/path.relative_to(source)
target.parent.mkdir(parents=True, exist_ok=True)
if path.is_symlink():
if target.exists() or target.is_symlink():
if not target.is_symlink():
raise ValueError('Conflicting package link: '+str(target))
# The reference runtime ships the compatibility link
# for libelf 0.194. The pinned 0.196 package keeps the
# same ABI name but points it at the newer SONAME.
# Replace that one known compatibility link while
# retaining the strict conflict checks for everything
# else.
if target.readlink() != path.readlink():
if str(target.relative_to(root)) != 'usr/lib/aarch64-linux-gnu/libelf.so.1':
raise ValueError('Conflicting package link: '+str(target))
target.unlink()
target.symlink_to(path.readlink())
else:
target.symlink_to(path.readlink())
else:
if target.exists() and target.read_bytes() != path.read_bytes():
expected = REPLACES.get(str(path.relative_to(source)))
if not expected or hashlib.sha256(target.read_bytes()).hexdigest() != expected:
raise ValueError('Conflicting package file: '+str(target))
shutil.copyfile(path, target)
target.chmod(path.stat().st_mode & 0o777)
records.append({'file': name, 'url': url, 'sha256': sha, 'excluded': excluded})
record = root/'usr/share/vulpes/runtime-packages.json'
record.parent.mkdir(parents=True, exist_ok=True)
record.write_text(json.dumps(records, indent=2)+'\n')
return records