release.sh (6141B)
1 #!/bin/sh 2 ## release.sh — mint a validated boot2 release tarball. 3 ## 4 ## Releases are critical, so the path to minting one is paranoid: 5 ## 6 ## 1. `make clean` — wipe build/ entirely. 7 ## 2. `make package` — full prep-src → boot0..boot6 → mkrelease.sh chain. 8 ## Capture tarball A's sha256. 9 ## 3. `make clean` again — fresh state, no cached intermediates. 10 ## 4. `make package` again — full rebuild + repackage. 11 ## Capture tarball B's sha256. 12 ## 5. Assert A == B. Bit-for-bit reproducibility check; if this drifts, 13 ## something in the input set or build is nondeterministic and the 14 ## release is not safe to publish. 15 ## 6. Extract one tarball into a fresh dir under $HOME (so the macOS 16 ## podman VM can see it) and run the bundled verify.sh. verify.sh 17 ## re-runs boot0..boot6 off the bundled inputs and diffs each 18 ## artifact's sha256 against OUTPUT_MANIFEST.txt. End-to-end proof 19 ## that the shipped tarball reproduces the claimed outputs. 20 ## 7. Promote the validated tarball to dist/<name>.tar.gz with a 21 ## sha256 sidecar. dist/ is the only directory mkrelease.sh / 22 ## release.sh ever writes to that's intended for publication. 23 ## 24 ## On any failure the dist/ output is NOT created; the operator gets a 25 ## clear error pointing at which pass failed. The two intermediate 26 ## tarballs are kept under build/<arch>/release/ for forensic diffing. 27 ## 28 ## Usage: tools/release.sh <arch> 29 ## <arch> ∈ {aarch64, amd64, riscv64} 30 ## Env: 31 ## DRIVER podman (default) | seed — passed through to make. 32 ## 33 ## Tarball name is `boot2-<arch>.tar.gz` — no git rev embedded, so its 34 ## sha256 is a pure content hash. Provenance (rev, build date, build 35 ## host) is written to `dist/boot2-<arch>.tar.gz.provenance` next to 36 ## the tarball. 37 38 set -eu 39 40 ARCH=${1:-} 41 case "$ARCH" in 42 aarch64|amd64|riscv64) ;; 43 *) echo "usage: $0 <aarch64|amd64|riscv64>" >&2; exit 2 ;; 44 esac 45 46 DRIVER=${DRIVER:-podman} 47 case "$DRIVER" in 48 podman|seed) ;; 49 *) echo "[release] unknown DRIVER=$DRIVER" >&2; exit 2 ;; 50 esac 51 52 ROOT=$(cd "$(dirname "$0")/.." && pwd) 53 cd "$ROOT" 54 55 REV=$(git rev-parse --short HEAD 2>/dev/null || echo norev) 56 DIRTY= 57 if ! git diff --quiet HEAD 2>/dev/null || \ 58 ! git diff --quiet --cached HEAD 2>/dev/null; then 59 DIRTY=-dirty 60 fi 61 NAME=boot2-$ARCH 62 REL_DIR=build/$ARCH/release 63 DIST=dist 64 65 # Pass-tarball vault — must live OUTSIDE build/ so it survives the 66 # `make clean` between passes. mktemp under /tmp; cleaned on exit. 67 VAULT=$(mktemp -d -t boot2-release-XXXXXX) 68 trap 'rm -rf "$VAULT"' EXIT 69 70 # Portable sha256. 71 if command -v sha256sum >/dev/null 2>&1; then 72 sha256() { sha256sum "$1" | awk '{print $1}'; } 73 else 74 sha256() { shasum -a 256 "$1" | awk '{print $1}'; } 75 fi 76 77 log() { printf '[release] %s\n' "$*"; } 78 hr() { printf '[release] ===================== %s =====================\n' "$*"; } 79 80 t0=$(date +%s) 81 82 # Two passes from a clean state. Each pass: make clean + make package. 83 # Save each tarball aside under a pass-specific name so we can compare 84 # them even if the recipe is rerun by hand later. 85 do_pass() { 86 _label=$1; _outvar=$2 87 hr "pass $_label" 88 log "make clean" 89 make clean >/dev/null 90 log "make package ARCH=$ARCH DRIVER=$DRIVER" 91 make package ARCH="$ARCH" DRIVER="$DRIVER" 92 _src=$REL_DIR/$NAME.tar.gz 93 _dst=$VAULT/$NAME.pass-$_label.tar.gz 94 [ -f "$_src" ] || { log "FAIL: pass $_label produced no $_src"; exit 1; } 95 cp "$_src" "$_dst" 96 _h=$(sha256 "$_dst") 97 log "pass $_label sha256: $_h" 98 eval "$_outvar=\$_h" 99 } 100 101 do_pass A SHA_A 102 do_pass B SHA_B 103 104 hr "compare" 105 if [ "$SHA_A" != "$SHA_B" ]; then 106 log "FAIL: pass A and pass B produced DIFFERENT tarballs" 107 log " A: $SHA_A ($VAULT/$NAME.pass-A.tar.gz)" 108 log " B: $SHA_B ($VAULT/$NAME.pass-B.tar.gz)" 109 log "" 110 log "Copying tarballs to dist/.failed-passes/ for inspection (vault is auto-cleaned)." 111 mkdir -p "$DIST/.failed-passes" 112 cp "$VAULT/$NAME.pass-A.tar.gz" "$DIST/.failed-passes/" 113 cp "$VAULT/$NAME.pass-B.tar.gz" "$DIST/.failed-passes/" 114 log "To investigate, extract both and diff the trees:" 115 log " mkdir /tmp/A /tmp/B" 116 log " tar xzf $DIST/.failed-passes/$NAME.pass-A.tar.gz -C /tmp/A" 117 log " tar xzf $DIST/.failed-passes/$NAME.pass-B.tar.gz -C /tmp/B" 118 log " diff -r /tmp/A /tmp/B" 119 log "(check INPUT_MANIFEST.txt and OUTPUT_MANIFEST.txt for hash drift)" 120 exit 1 121 fi 122 log "OK: both passes produced identical tarballs" 123 log " sha256 = $SHA_A" 124 125 # End-to-end verify: extract the (still-installed) tarball, run its 126 # verify.sh, which re-runs boot0..boot6 inside a fresh tree and 127 # hash-diffs every artifact in OUTPUT_MANIFEST.txt. 128 hr "verify" 129 # macOS podman VM only mounts /Users/, so the verify dir must live 130 # under $HOME. ~/.cache is a stable, throwaway-friendly location. 131 VERIFY_BASE=$HOME/.cache/boot2-release-verify/$ARCH 132 log "extract -> $VERIFY_BASE/$NAME" 133 rm -rf "$VERIFY_BASE" 134 mkdir -p "$VERIFY_BASE" 135 tar xzf "$VAULT/$NAME.pass-A.tar.gz" -C "$VERIFY_BASE" 136 137 log "running ./verify.sh (DRIVER=$DRIVER) — this rebuilds the chain" 138 ( cd "$VERIFY_BASE/$NAME" && DRIVER=$DRIVER ./verify.sh ) 139 140 # Promote to dist/ — the only directory we treat as "publishable". 141 hr "mint" 142 mkdir -p "$DIST" 143 cp "$VAULT/$NAME.pass-A.tar.gz" "$DIST/$NAME.tar.gz" 144 printf '%s %s\n' "$SHA_A" "$NAME.tar.gz" > "$DIST/$NAME.tar.gz.sha256" 145 146 # Provenance sidecar — keeps git rev / build host / driver / timestamp 147 # outside the tarball so they don't perturb the content hash. 148 { 149 printf 'tarball: %s.tar.gz\n' "$NAME" 150 printf 'sha256: %s\n' "$SHA_A" 151 printf 'arch: %s\n' "$ARCH" 152 printf 'driver: %s\n' "$DRIVER" 153 printf 'git_rev: %s%s\n' "$REV" "$DIRTY" 154 printf 'built_at: %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" 155 printf 'built_on: %s %s\n' "$(uname -s)" "$(uname -m)" 156 } > "$DIST/$NAME.tar.gz.provenance" 157 158 # Clean up the verify dir (it's reproducible from the tarball). 159 rm -rf "$VERIFY_BASE" 160 161 elapsed=$(( $(date +%s) - t0 )) 162 bytes=$(wc -c < "$DIST/$NAME.tar.gz" | tr -d ' ') 163 log "MINTED in ${elapsed}s" 164 log " tarball : $DIST/$NAME.tar.gz ($bytes bytes)" 165 log " sha256 : $SHA_A"