|
| 1 | +#!/usr/bin/env python3 |
| 2 | +"""Fetch the pinned kernel packages from a Kernel-repo release and verify every checksum. |
| 3 | +
|
| 4 | +The OS image consumes kernel output by immutable release tag and sha256 (mseries/kernel-pin.json), |
| 5 | +never by branch. `--check-profiles` only compares the pin with the Fairydust mkosi profile versions. |
| 6 | +""" |
| 7 | +import argparse |
| 8 | +import hashlib |
| 9 | +import json |
| 10 | +import re |
| 11 | +import sys |
| 12 | +import urllib.request |
| 13 | +from pathlib import Path |
| 14 | + |
| 15 | +ROOT = Path(__file__).resolve().parent.parent |
| 16 | +PIN = ROOT / 'mseries/kernel-pin.json' |
| 17 | +PROFILE = ROOT / 'mkosi.images/base/mkosi.profiles/fairydust/mkosi.conf' |
| 18 | +SHA = re.compile(r'[0-9a-f]{64}') |
| 19 | +BASE = 'https://github.com/{repo}/releases/download/{tag}/{asset}' |
| 20 | + |
| 21 | + |
| 22 | +def load_pin(path=PIN): |
| 23 | + pin = json.loads(Path(path).read_text()) |
| 24 | + tag = pin.get('release_tag', '') |
| 25 | + if pin.get('schema') != 1 or not tag.startswith('kernel-') or tag in ('latest', 'main', 'dev'): |
| 26 | + raise SystemExit('pin needs schema 1 and an immutable kernel-* release_tag') |
| 27 | + items = pin['packages'] + [pin['boot']['m1n1_bootbin']] |
| 28 | + for item in items: |
| 29 | + if not SHA.fullmatch(item.get('sha256', '')) or not item.get('asset') or not item.get('file'): |
| 30 | + raise SystemExit(f'pin entry is missing sha256, asset or file: {item}') |
| 31 | + return pin, items |
| 32 | + |
| 33 | + |
| 34 | +def sha256(data): |
| 35 | + return hashlib.sha256(data).hexdigest() |
| 36 | + |
| 37 | + |
| 38 | +def profile_versions(path=PROFILE): |
| 39 | + out = {} |
| 40 | + for line in Path(path).read_text().splitlines(): |
| 41 | + m = re.fullmatch(r'\s+([A-Za-z0-9.+-]+)=(\S+)', line) |
| 42 | + if m: |
| 43 | + out[m[1]] = m[2] |
| 44 | + return out |
| 45 | + |
| 46 | + |
| 47 | +def check_profiles(pin, path=PROFILE): |
| 48 | + have = profile_versions(path) |
| 49 | + bad = [f"{p['name']}: pin {p['version']} profile {have.get(p['name'])}" |
| 50 | + for p in pin['packages'] if have.get(p['name']) != p['version']] |
| 51 | + if bad: |
| 52 | + raise SystemExit('fairydust profile does not match kernel-pin.json:\n ' + '\n '.join(bad)) |
| 53 | + |
| 54 | + |
| 55 | +def fetch(pin, items, out, base): |
| 56 | + out.mkdir(parents=True, exist_ok=True) |
| 57 | + for item in items: |
| 58 | + url = base.format(repo=pin['kernel_repo'], tag=pin['release_tag'], asset=item['asset']) |
| 59 | + with urllib.request.urlopen(url) as r: |
| 60 | + data = r.read() |
| 61 | + if sha256(data) != item['sha256']: |
| 62 | + raise SystemExit(f"checksum mismatch for {item['file']}: got {sha256(data)}, pinned {item['sha256']}") |
| 63 | + (out / item['file']).write_bytes(data) |
| 64 | + print(f"ok {item['file']}") |
| 65 | + debs = [p for p in pin['packages']] |
| 66 | + upstream = [p for p in debs if not p['name'].startswith('linux-image-')] |
| 67 | + boot = pin['boot']['m1n1_bootbin'] |
| 68 | + line = lambda i: f"{i['sha256']} {i['file']}\n" |
| 69 | + (out / 'SHA256SUMS').write_text(''.join(line(p) for p in debs)) |
| 70 | + (out / 'UPSTREAM-SHA256SUMS').write_text(''.join(line(p) for p in upstream)) |
| 71 | + (out / 'FAIRYDUST-BOOT-SHA256SUMS').write_text(line(boot)) |
| 72 | + |
| 73 | + |
| 74 | +def main(): |
| 75 | + ap = argparse.ArgumentParser() |
| 76 | + ap.add_argument('--pin', default=str(PIN)) |
| 77 | + ap.add_argument('--out', type=Path) |
| 78 | + ap.add_argument('--base-url', default=BASE, help='template with {repo} {tag} {asset}; file:// works for tests') |
| 79 | + ap.add_argument('--check-profiles', action='store_true') |
| 80 | + a = ap.parse_args() |
| 81 | + pin, items = load_pin(a.pin) |
| 82 | + if a.check_profiles: |
| 83 | + check_profiles(pin) |
| 84 | + print('pin matches fairydust profile versions') |
| 85 | + if a.out: |
| 86 | + fetch(pin, items, a.out, a.base_url) |
| 87 | + return 0 |
| 88 | + |
| 89 | + |
| 90 | +if __name__ == '__main__': |
| 91 | + sys.exit(main()) |
0 commit comments