92 lines
4.1 KiB
Bash
Executable File
92 lines
4.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Produce (and optionally publish) a **pre-unwrapped** raw arm64 Linux kernel `Image` for the Nucleic
|
|
# macOS-guest **Linux VM base build** (docs/LINUX_VM.md, docs/LINUX_VM_SEMANTIC_AGENT.md).
|
|
#
|
|
# scripts/publish-linux-vm-kernel.sh [CODENAME] [VERSION]
|
|
# # e.g. scripts/publish-linux-vm-kernel.sh resolute 26.04
|
|
#
|
|
# Why this exists: modern Ubuntu arm64 `vmlinuz` ships as an **EFI-zboot** self-decompressing PE with a
|
|
# **zstd** payload. `VZLinuxBootLoader` can't boot that — it needs a raw arm64 `Image`. The app's
|
|
# artifact pipeline (`MacVMEngine.prepareBootableKernel`) unwraps zboot host-side, but that needs a
|
|
# `zstd` binary on the Mac (macOS has no system zstd). Publishing a pre-unwrapped Image and pointing
|
|
# `MacVMSettings.linuxKernelURL` at it removes that host dependency — the app then just downloads a raw
|
|
# Image. This script does the unwrap in Linux CI (where zstd is a package) exactly like the app would.
|
|
#
|
|
# Output: $OUT (default ./vmlinux-arm64) — an uncompressed arm64 Image, magic "ARM\x64" at byte 0x38.
|
|
#
|
|
# Publishing: pass PUBLISH=oras to push it to GHCR as an OCI artifact (mirrors
|
|
# .github/workflows/kernel-image.yml). NOTE: the MacVM download path is a plain HTTPS GET, so to consume
|
|
# it via `linuxKernelURL` you need a plain-HTTPS location (a GitHub release asset or the R2 updates
|
|
# bucket), not a GHCR package. The GHCR push here is for parity/backup; wire the plain URL you host it at.
|
|
#
|
|
# Needs: curl, zstd (or gzip), python3 — all present on an ubuntu-latest runner.
|
|
|
|
set -euo pipefail
|
|
|
|
CODENAME="${1:-resolute}"
|
|
VERSION="${2:-26.04}"
|
|
OUT="${OUT:-vmlinux-arm64}"
|
|
BASE="https://cloud-images.ubuntu.com/releases/${CODENAME}/release/unpacked"
|
|
VMLINUZ="ubuntu-${VERSION}-server-cloudimg-arm64-vmlinuz-generic"
|
|
|
|
TMP="$(mktemp -d)"
|
|
trap 'rm -rf "$TMP"' EXIT
|
|
|
|
echo "▸ Downloading ${VMLINUZ} …"
|
|
curl -SsL -o "$TMP/vmlinuz" "${BASE}/${VMLINUZ}"
|
|
|
|
echo "▸ Unwrapping (EFI-zboot / gzip / raw) → ${OUT} …"
|
|
python3 - "$TMP/vmlinuz" "$TMP/payload" "$TMP/comp" <<'PY'
|
|
import sys, struct
|
|
src, payload_out, comp_out = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
d = open(src, 'rb').read()
|
|
# Raw arm64 Image? (magic "ARM\x64" at 0x38)
|
|
if len(d) > 0x3C and d[0x38:0x3C] == b'ARMd':
|
|
open(payload_out, 'wb').write(d); open(comp_out, 'w').write('raw'); sys.exit(0)
|
|
# Plain gzip vmlinuz (older releases).
|
|
if d[:2] == b'\x1f\x8b':
|
|
open(payload_out, 'wb').write(d); open(comp_out, 'w').write('gzip'); sys.exit(0)
|
|
# EFI-zboot: locate the "zimg" header, read payload size + compression, slice by compressor magic.
|
|
z = d.find(b'zimg')
|
|
if z < 0:
|
|
sys.stderr.write('not a recognized kernel (no raw Image, gzip, or zboot header)\n'); sys.exit(2)
|
|
size = struct.unpack_from('<I', d, z + 8)[0]
|
|
comp = d[z+20:z+52].split(b'\x00')[0].decode('ascii', 'replace')
|
|
magics = {'gzip': b'\x1f\x8b\x08', 'zstd': b'\x28\xb5\x2f\xfd', 'lz4': b'\x04\x22\x4d\x18'}
|
|
if comp not in magics:
|
|
sys.stderr.write('unsupported zboot compression: %r\n' % comp); sys.exit(3)
|
|
start = d.find(magics[comp], z)
|
|
end = start + size if size else len(d)
|
|
open(payload_out, 'wb').write(d[start:end])
|
|
open(comp_out, 'w').write(comp)
|
|
PY
|
|
|
|
COMP="$(cat "$TMP/comp")"
|
|
case "$COMP" in
|
|
raw) cp "$TMP/payload" "$OUT" ;;
|
|
gzip) gzip -dc "$TMP/payload" > "$OUT" || true ;; # trailing garbage after the stream is harmless
|
|
zstd) zstd -dc "$TMP/payload" > "$OUT" || true ;;
|
|
*) echo "unexpected compression '$COMP'" >&2; exit 3 ;;
|
|
esac
|
|
|
|
# Sanity: the result must be a raw arm64 Image (magic "ARM\x64" at 0x38).
|
|
python3 - "$OUT" <<'PY'
|
|
import sys
|
|
d = open(sys.argv[1], 'rb').read(64)
|
|
if d[0x38:0x3C] != b'ARMd':
|
|
sys.stderr.write('FAIL: output is not a raw arm64 Image (bad magic at 0x38)\n'); sys.exit(1)
|
|
print("✓ raw arm64 Image (%d bytes)" % len(open(sys.argv[1],'rb').read()))
|
|
PY
|
|
|
|
echo "▸ Wrote ${OUT} (compression was: ${COMP})"
|
|
|
|
if [ "${PUBLISH:-}" = "oras" ]; then
|
|
: "${IMAGE:?set IMAGE=ghcr.io/<owner>/nucleic-linux-vm-kernel}"
|
|
: "${TAG:?set TAG=<version>-<abi> e.g. 26.04-generic}"
|
|
echo "▸ Pushing ${IMAGE}:${TAG} via oras …"
|
|
oras push "${IMAGE}:${TAG}" \
|
|
--artifact-type application/vnd.nucleic.kernel \
|
|
"${OUT}:application/octet-stream"
|
|
fi
|