mkrelease.sh (7530B)
1 #!/bin/sh 2 ## mkrelease.sh — package a per-arch boot2 release tarball. 3 ## 4 ## A release tarball is a self-contained bundle that lets anyone 5 ## reproduce the full boot0..boot6 chain off the bundled inputs and 6 ## byte-compare the outputs against a hash manifest. Layout: 7 ## 8 ## boot2-<arch>[-<rev>].tar.gz 9 ## boot2-<arch>[-<rev>]/ 10 ## README.md extract + run instructions 11 ## verify.sh drives boot0..boot6 + diffs OUTPUT_MANIFEST 12 ## INPUT_MANIFEST.txt sha256 of every input under src/ + boot/ 13 ## OUTPUT_MANIFEST.txt sha256 of expected per-stage artifacts 14 ## (driver-agnostic; the project's seed-accept 15 ## harness verifies podman vs seed equivalence) 16 ## src/ the sealed source tree (from 17 ## build/<arch>/src/, produced by prep-src.sh) 18 ## boot/ boot0..boot6 stage drivers + libs + 19 ## containers/Containerfile.* 20 ## 21 ## The output manifest is generated from the current build outputs in 22 ## build/<arch>/<driver>/boot{0..6}/. mkrelease.sh does NOT rebuild; 23 ## prereqs (`make all ARCH=<arch>`) must already have run. 24 ## 25 ## Usage: tools/mkrelease.sh <arch> 26 ## <arch> ∈ {aarch64, amd64, riscv64} 27 ## Env: 28 ## DRIVER podman (default) | seed — which build tree to hash for the 29 ## output manifest. The manifest is 30 ## claimed driver-agnostic regardless. 31 ## 32 ## The tarball name is `boot2-<arch>.tar.gz` — deliberately rev-free, so 33 ## the tarball's sha256 reflects content and nothing else. Provenance 34 ## (git rev, build timestamp) lives in a sidecar produced by the 35 ## validated mint path (tools/release.sh), not inside the tarball. 36 37 set -eu 38 39 ARCH=${1:-} 40 case "$ARCH" in 41 aarch64|amd64|riscv64) ;; 42 *) echo "usage: $0 <aarch64|amd64|riscv64>" >&2; exit 2 ;; 43 esac 44 45 DRIVER=${DRIVER:-podman} 46 case "$DRIVER" in 47 podman|seed) ;; 48 *) echo "[mkrelease] unknown DRIVER=$DRIVER (expected podman|seed)" >&2; exit 2 ;; 49 esac 50 51 ROOT=$(cd "$(dirname "$0")/.." && pwd) 52 cd "$ROOT" 53 54 case "$ARCH" in 55 aarch64) KERNEL_NAME=Image ;; 56 amd64) KERNEL_NAME=kernel.elf ;; 57 riscv64) KERNEL_NAME=kernel.elf ;; 58 esac 59 60 NAME=boot2-$ARCH 61 62 SRC_TREE=build/$ARCH/src 63 BUILD_TREE=build/$ARCH/$DRIVER 64 REL_DIR=build/$ARCH/release 65 STAGING=$REL_DIR/$NAME 66 TARBALL=$REL_DIR/$NAME.tar.gz 67 68 [ -d "$SRC_TREE" ] || { echo "[mkrelease] missing $SRC_TREE — run bootprep/prep-src.sh $ARCH" >&2; exit 1; } 69 [ -f "$BUILD_TREE/boot6/$KERNEL_NAME" ] || { echo "[mkrelease] missing $BUILD_TREE/boot6/$KERNEL_NAME — run 'make all ARCH=$ARCH DRIVER=$DRIVER'" >&2; exit 1; } 70 71 # Portable sha256. Use sha256sum if present; else shasum -a 256. 72 if command -v sha256sum >/dev/null 2>&1; then 73 sha256() { sha256sum "$1" | awk '{print $1}'; } 74 else 75 sha256() { shasum -a 256 "$1" | awk '{print $1}'; } 76 fi 77 78 echo "[mkrelease] staging -> $STAGING" 79 rm -rf "$STAGING" 80 mkdir -p "$STAGING" 81 82 # ── (1) sealed source tree ──────────────────────────────────────────── 83 cp -R "$SRC_TREE" "$STAGING/src" 84 85 # ── (2) boot drivers (boot/*.sh, lib-*.sh, containers/Containerfile.*) ─ 86 cp -R boot "$STAGING/boot" 87 88 # ── (3) README + verify.sh templates ────────────────────────────────── 89 cp tools/release/README.md "$STAGING/README.md" 90 cp tools/release/verify.sh "$STAGING/verify.sh" 91 chmod +x "$STAGING/verify.sh" 92 93 # Substitute @ARCH@ / @KERNEL_NAME@ into shipped docs/scripts. No git 94 # rev is embedded — content stays rev-free so sha256 reflects content. 95 for f in "$STAGING/README.md" "$STAGING/verify.sh"; do 96 sed -i.bak \ 97 -e "s/@ARCH@/$ARCH/g" \ 98 -e "s/@KERNEL_NAME@/$KERNEL_NAME/g" \ 99 "$f" 100 rm -f "$f.bak" 101 done 102 103 # ── (4) INPUT_MANIFEST.txt ──────────────────────────────────────────── 104 echo "[mkrelease] input manifest" 105 INMAN=$STAGING/INPUT_MANIFEST.txt 106 ( 107 cd "$STAGING" 108 find src boot -type f | LC_ALL=C sort | while read -r rel; do 109 h=$(sha256 "$rel") 110 printf '%s %s\n' "$h" "$rel" 111 done 112 ) > "$INMAN" 113 n_in=$(wc -l < "$INMAN" | tr -d ' ') 114 115 # ── (5) OUTPUT_MANIFEST.txt ─────────────────────────────────────────── 116 # Per-stage key artifacts (mirrors the stamp-anchored declarations in 117 # the top-level Makefile). Driver-agnostic. 118 gen_outputs() { 119 cat <<EOF 120 boot0/hex2 121 boot0/catm 122 boot0/M0 123 boot1/M1pp 124 boot1/hex2pp 125 boot2/catm 126 boot2/scheme1 127 boot3/tcc0 128 boot3/libc.P1pp 129 boot3/tcc.flat.P1pp 130 boot4/tcc1 131 boot4/tcc2 132 boot4/tcc3 133 boot4/hello 134 boot4/crt1.o 135 boot4/libc.a 136 boot4/libtcc1.a 137 boot5/libc.a 138 boot5/crt1.o 139 boot5/crti.o 140 boot5/crtn.o 141 boot5/hello 142 boot6/$KERNEL_NAME 143 EOF 144 } 145 146 echo "[mkrelease] output manifest (from $BUILD_TREE)" 147 OUTMAN=$STAGING/OUTPUT_MANIFEST.txt 148 : > "$OUTMAN" 149 rm -f "$OUTMAN.missing" 150 gen_outputs | while read -r rel; do 151 [ -n "$rel" ] || continue 152 f=$BUILD_TREE/$rel 153 if [ -e "$f" ]; then 154 h=$(sha256 "$f") 155 printf '%s %s\n' "$h" "$rel" >> "$OUTMAN" 156 else 157 printf '%s\n' "$rel" >> "$OUTMAN.missing" 158 fi 159 done 160 if [ -s "$OUTMAN.missing" ]; then 161 echo "[mkrelease] FAIL: missing expected outputs under $BUILD_TREE:" >&2 162 sed 's/^/ /' "$OUTMAN.missing" >&2 163 echo "[mkrelease] run 'make all ARCH=$ARCH DRIVER=$DRIVER' (and boot5) first" >&2 164 rm -f "$OUTMAN.missing" 165 exit 1 166 fi 167 n_out=$(wc -l < "$OUTMAN" | tr -d ' ') 168 169 # ── (6) tarball — deterministic ────────────────────────────────────── 170 # Identical inputs must yield byte-identical tarballs. Sources of drift: 171 # (a) gzip header embeds wall-clock mtime + original filename 172 # → use `gzip -n` (no name, no timestamp). 173 # (b) tar entries embed per-file mtime + uid/gid/uname/gname. 174 # → normalize on-disk mtimes with `touch -t`; override ownership 175 # in the tar headers via --uid/--gid/--uname/--gname. 176 # (c) tar entry order = filesystem readdir order. 177 # → sorted -T file list. 178 # Note: macOS bsdtar refuses `--mtime` together with `-T -`. We avoid 179 # that combo by normalizing mtimes on disk first. 180 # Also: a pipeline like `... | tar | gzip > out` silently produces an 181 # empty gzip if tar fails. Build the uncompressed tar to a tempfile so 182 # `set -e` catches tar's exit code, then gzip from disk. 183 echo "[mkrelease] tar -> $TARBALL" 184 find "$STAGING" -exec touch -t 200001010000.00 {} + 185 186 TAR_TMP=$PWD/$REL_DIR/$NAME.tar 187 TARBALL_ABS=$PWD/$TARBALL 188 rm -f "$TAR_TMP" "$TARBALL" 189 ( 190 cd "$REL_DIR" 191 find "$NAME" -print0 | LC_ALL=C sort -z | \ 192 tar --null -T - \ 193 --uid 0 --gid 0 --uname '' --gname '' \ 194 --format ustar \ 195 -cf "$TAR_TMP" 196 ) 197 gzip -n -9 < "$TAR_TMP" > "$TARBALL_ABS" 198 rm -f "$TAR_TMP" 199 200 # Tarball digest (echoed for release notes; not embedded in the tar). 201 TAR_SHA=$(sha256 "$TARBALL") 202 printf '%s %s\n' "$TAR_SHA" "$NAME.tar.gz" > "$REL_DIR/$NAME.tar.gz.sha256" 203 204 bytes=$(wc -c < "$TARBALL" | tr -d ' ') 205 echo "[mkrelease] OK" 206 echo "[mkrelease] tarball : $TARBALL ($bytes bytes)" 207 echo "[mkrelease] sha256 : $TAR_SHA" 208 echo "[mkrelease] inputs : $n_in files" 209 echo "[mkrelease] outputs : $n_out artifacts"