Register 32-bit ARM binfmt rules and keep QEMU changes package-local
qemu-binfmt-conf.sh groups arm with aarch64 and skipped it; Apple Silicon has no AArch32 execution, so --ignore-family is needed for ARM32 programs. The bin/sync-upstream extension, its tests and the Tests workflow edit are reverted; QEMU updates become reviewed pins.
This commit is contained in:
1 parent
aa64ec9a4f
commit
90fee672ce
7 files changed
+10
-261
No files matched your search
@@ -1,120 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Track Debian 13 ARM64 QEMU revisions, verifying an immutable snapshot first."""
|
||||
import functools
|
||||
import hashlib
|
||||
import json
|
||||
import lzma
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
FEEDS = [
|
||||
'https://deb.debian.org/debian/dists/trixie/main/binary-arm64/Packages.xz',
|
||||
'https://deb.debian.org/debian/dists/trixie-updates/main/binary-arm64/Packages.xz',
|
||||
'https://security.debian.org/debian-security/dists/trixie-security/main/binary-arm64/Packages.xz',
|
||||
]
|
||||
|
||||
def fetch(url):
|
||||
with urllib.request.urlopen(url, timeout=120) as response:
|
||||
if not response.url.startswith('https://'):
|
||||
raise ValueError('Insecure redirect')
|
||||
return response.read()
|
||||
|
||||
def parts(version):
|
||||
match = re.fullmatch(r'(?:(\d+):)?([0-9][A-Za-z0-9.+~]*?)-([A-Za-z0-9.+~]+)', version)
|
||||
if not match:
|
||||
raise ValueError('Unsupported Debian version: ' + version)
|
||||
return int(match[1] or '0'), match[2], match[3]
|
||||
|
||||
def segment_cmp(a, b):
|
||||
# Debian policy: tilde precedes everything, then end/digits, letters,
|
||||
# then other characters; digit runs are compared numerically.
|
||||
def order(c):
|
||||
if c == '~': return -1
|
||||
if not c or c.isdigit(): return 0
|
||||
return ord(c) if c.isalpha() else ord(c) + 256
|
||||
while a or b:
|
||||
while (a and not a[0].isdigit()) or (b and not b[0].isdigit()):
|
||||
x, y = order(a[:1]), order(b[:1])
|
||||
if x != y: return (x > y) - (x < y)
|
||||
a, b = a[1:], b[1:]
|
||||
x = re.match(r'\d*', a)[0]
|
||||
y = re.match(r'\d*', b)[0]
|
||||
nx, ny = int(x or '0'), int(y or '0')
|
||||
if nx != ny: return (nx > ny) - (nx < ny)
|
||||
a, b = a[len(x):], b[len(y):]
|
||||
return 0
|
||||
|
||||
def compare(a, b):
|
||||
ea, ua, ra = parts(a)
|
||||
eb, ub, rb = parts(b)
|
||||
return (ea > eb) - (ea < eb) or segment_cmp(ua, ub) or segment_cmp(ra, rb)
|
||||
|
||||
def package_version(version):
|
||||
epoch, upstream, revision = parts(version)
|
||||
# Stable releases only. Fail visibly on prerelease/repack conventions that
|
||||
# need a reviewed Arch ordering rather than silently misordering them.
|
||||
if '~' in version:
|
||||
raise ValueError('Debian prerelease needs a reviewed Arch version mapping')
|
||||
return f'{epoch}.{upstream}.{revision}'
|
||||
|
||||
def records(data):
|
||||
found = []
|
||||
for stanza in re.split(r'\n\s*\n', lzma.decompress(data).decode()):
|
||||
fields = dict(re.findall(r'^([A-Za-z0-9-]+): (.*)$', stanza, re.M))
|
||||
if fields.get('Package') != 'qemu-user': continue
|
||||
if fields.get('Architecture') != 'arm64': raise ValueError('Wrong architecture')
|
||||
parts(fields['Version'])
|
||||
if not re.fullmatch(r'[0-9a-f]{64}', fields.get('SHA256', '')):
|
||||
raise ValueError('Missing/malformed SHA256')
|
||||
if not re.fullmatch(r'[1-9][0-9]*', fields.get('Size', '')):
|
||||
raise ValueError('Missing/malformed package size')
|
||||
found.append(fields)
|
||||
return found
|
||||
|
||||
def discover(current, current_pkgver, current_hash, get=fetch):
|
||||
candidates = [row for url in FEEDS for row in records(get(url))]
|
||||
if not candidates: raise ValueError('No ARM64 qemu-user package in Debian feeds')
|
||||
selected = max(candidates, key=functools.cmp_to_key(lambda a,b: compare(a['Version'], b['Version'])))
|
||||
version, checksum = selected['Version'], selected['SHA256']
|
||||
if any(row['SHA256'] != checksum for row in candidates if row['Version'] == version):
|
||||
raise ValueError('Debian feeds disagree on the selected checksum')
|
||||
ordering = compare(version, current)
|
||||
if ordering < 0: raise ValueError('Debian feeds are older than the pinned recipe')
|
||||
if ordering == 0:
|
||||
if checksum != current_hash: raise ValueError('Checksum changed for the pinned Debian revision')
|
||||
return {}
|
||||
pkgver = package_version(version)
|
||||
if int(subprocess.check_output(['vercmp', pkgver, current_pkgver], text=True)) <= 0:
|
||||
raise ValueError('New Debian revision does not advance Arch version; review mapping')
|
||||
api = 'https://snapshot.debian.org/mr/binary/qemu-user/' + urllib.parse.quote(version, safe='') + '/binfiles'
|
||||
document = json.loads(get(api))
|
||||
if document.get('binary') != 'qemu-user' or document.get('binary_version') != version:
|
||||
raise ValueError('Snapshot revision differs')
|
||||
hashes = {row['hash'] for row in document['result'] if row['architecture'] == 'arm64'}
|
||||
if len(hashes) != 1 or not re.fullmatch(r'[0-9a-f]{40}', next(iter(hashes), '')):
|
||||
raise ValueError('Missing/ambiguous ARM64 snapshot')
|
||||
snapshot = hashes.pop()
|
||||
archive = get('https://snapshot.debian.org/file/' + snapshot)
|
||||
if len(archive) != int(selected['Size']) or hashlib.sha256(archive).hexdigest() != checksum or hashlib.sha1(archive).hexdigest() != snapshot:
|
||||
raise ValueError('Snapshot bytes differ from Debian package metadata')
|
||||
return {'pkgver': pkgver, 'variables': {'_debver': version, '_snapshot': snapshot},
|
||||
'sha256sums': {'aarch64': [checksum]}}
|
||||
|
||||
def main():
|
||||
recipe = Path('PKGBUILD').read_text()
|
||||
def scalar(name):
|
||||
match = re.search(r'^' + name + r'=[\"\']?([^\"\'\n]+)', recipe, re.M)
|
||||
if not match: raise ValueError('Missing recipe scalar: ' + name)
|
||||
return match[1]
|
||||
checksum = re.search(r"^sha256sums_aarch64=\('([a-f0-9]{64})'\)", recipe, re.M)
|
||||
if not checksum: raise ValueError('Missing current ARM checksum')
|
||||
print(json.dumps(discover(scalar('_debver'), scalar('pkgver'), checksum[1])))
|
||||
|
||||
if __name__ == '__main__':
|
||||
try: main()
|
||||
except Exception as error:
|
||||
sys.exit('QEMU Debian update failed: ' + str(error))
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
exec python3 .omarchy/upstream.py
|
||||
@@ -6,7 +6,8 @@
|
||||
pkgbase=qemu-user-static
|
||||
pkgname=('qemu-user-static' 'qemu-user-static-binfmt')
|
||||
# One-time epoch moves from upstream-only to full Debian revision ordering.
|
||||
# pkgver records Debian epoch.upstream.revision; updates also verify vercmp.
|
||||
# pkgver records Debian epoch.upstream.revision. Updates are reviewed pins:
|
||||
# bump _debver, pkgver, _snapshot and the checksum together.
|
||||
epoch=1
|
||||
pkgver=1.10.0.13+ds.0+deb13u1
|
||||
_debver=1:10.0.13+ds-0+deb13u1
|
||||
@@ -55,13 +56,16 @@ package_qemu-user-static-binfmt() {
|
||||
install -dm755 "$pkgdir/usr/lib/binfmt.d"
|
||||
# HOST_ARCH keeps the native architecture out of the rule set regardless
|
||||
# of the build host, so the package is identical under native and QEMU builds.
|
||||
# --ignore-family keeps 32-bit ARM: the script groups it with aarch64, but
|
||||
# Apple Silicon cannot execute AArch32 natively.
|
||||
HOST_ARCH=aarch64 sh "$srcdir/qemu-binfmt-conf.sh" \
|
||||
--systemd ALL \
|
||||
--exportdir "$pkgdir/usr/lib/binfmt.d/" \
|
||||
--qemu-path /usr/bin \
|
||||
--qemu-suffix -static \
|
||||
--persistent yes \
|
||||
--preserve-argv0 yes
|
||||
--preserve-argv0 yes \
|
||||
--ignore-family yes
|
||||
local conf
|
||||
for conf in "$pkgdir"/usr/lib/binfmt.d/*.conf; do
|
||||
mv "$conf" "${conf%.conf}-static.conf"
|
||||
|
||||
@@ -4,10 +4,8 @@ This aarch64-only split recipe provides qemu-user-static and qemu-user-static-bi
|
||||
|
||||
## Updates
|
||||
|
||||
The package-local upstream hook checks Debian 13 (trixie), trixie-updates, and trixie-security ARM64 indexes. It compares full Debian versions, including epochs, security revisions and binary rebuilds, and resolves the selected binary through snapshot.debian.org’s API. The immutable content-addressed archive must match the index’s size and SHA256 as well as the snapshot SHA1 before an update is returned.
|
||||
Updates are reviewed pins, not automatic. To move to a new Debian 13 revision, update `_debver`, `pkgver` (Debian epoch.upstream.revision), `_snapshot` (the snapshot.debian.org SHA1 of the ARM64 `qemu-user` archive) and `sha256sums_aarch64` together, and confirm both Debian ordering and `vercmp` advance. A one-time Arch epoch of 1 moves away from the previous upstream-only version.
|
||||
|
||||
A one-time Arch epoch of 1 moves away from the previous upstream-only version. pkgver encodes Debian epoch.upstream.revision. The hook requires both Debian ordering and pacman vercmp to advance; unfamiliar prerelease conventions or ordering discrepancies fail for manual review. Debian distribution upgrades are explicit recipe changes, not automatic jumps to testing/unstable.
|
||||
## binfmt rules
|
||||
|
||||
The existing six-hour upstream update PR workflow discovers the hook. Its pkgver, _debver, _snapshot and ARM checksum are applied atomically through the normal sync interface. Repeated checks are idempotent. Feed failures, malformed metadata, conflicting checksums and unavailable/corrupt snapshots fail visibly before recipe mutation. No update installs packages or registers binfmt rules.
|
||||
|
||||
Offline fixtures cover version/epoch/security/binNMU updates, security-feed selection, unchanged versions, metadata and snapshot failures, atomic application, and hostile/missing scalar rejection. Existing GUI-independent VM qualification and packaging evidence are recorded in the PR; new binaries still receive build and runtime checks before publication.
|
||||
The rules are generated with `--ignore-family yes`, so 32-bit ARM binaries are registered even though the script groups them with aarch64; Apple Silicon has no AArch32 execution. Native aarch64 stays excluded. Rules use the persistent and preserve-argv0 flags and never the credential flag.
|
||||
Reference in new issue
Block a user