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:
Marcelo Alcantara committed 2026-09-24 16:05:38 +10:00
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 -2
View File
@@ -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"
+3 -5
View File
@@ -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.