release.sh (6463B)
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. Clean-build and package prep-src → boot0..boot6 once. 7 ## 2. Extract the archive into a fresh directory under $HOME. 8 ## 3. Clean-regenerate the checkout's canonical src/ tree and compare 9 ## its full boot/ + src/ hash manifest with the bundled 10 ## INPUT_MANIFEST.txt. 11 ## 4. Repackage the extracted sealed payload and assert that its tarball 12 ## is byte-identical to the original. 13 ## 5. Run the bundled verify.sh. It rebuilds boot0..boot6 from only the 14 ## sealed inputs and compares every artifact with OUTPUT_MANIFEST.txt, 15 ## proving both build determinism and archive completeness. 16 ## 6. Promote the validated tarball to dist/<name>.tar.gz with a 17 ## sha256 sidecar. dist/ is the only directory mkrelease.sh / 18 ## release.sh ever writes to that's intended for publication. 19 ## 20 ## On any failure no new dist/ output is promoted. 21 ## 22 ## Usage: tools/release.sh <arch> 23 ## <arch> ∈ {aarch64, amd64, riscv64} 24 ## Env: 25 ## DRIVER podman (default) | seed — passed through to make. 26 ## 27 ## Tarball name is `boot2-<arch>.tar.gz` — no git rev embedded, so its 28 ## sha256 is a pure content hash. Provenance (rev, build date, build 29 ## host) is written to `dist/boot2-<arch>.tar.gz.provenance` next to 30 ## the tarball. 31 32 set -eu 33 34 ARCH=${1:-} 35 case "$ARCH" in 36 aarch64|amd64|riscv64) ;; 37 *) echo "usage: $0 <aarch64|amd64|riscv64>" >&2; exit 2 ;; 38 esac 39 40 DRIVER=${DRIVER:-podman} 41 case "$DRIVER" in 42 podman|seed) ;; 43 *) echo "[release] unknown DRIVER=$DRIVER" >&2; exit 2 ;; 44 esac 45 46 ROOT=$(cd "$(dirname "$0")/.." && pwd) 47 cd "$ROOT" 48 49 REV=$(git rev-parse --short HEAD 2>/dev/null || echo norev) 50 DIRTY= 51 if ! git diff --quiet HEAD 2>/dev/null || \ 52 ! git diff --quiet --cached HEAD 2>/dev/null; then 53 DIRTY=-dirty 54 fi 55 NAME=boot2-$ARCH 56 REL_DIR=build/$ARCH/release 57 DIST=dist 58 59 # Validation vault — must live outside build/ so it survives make clean. 60 VAULT=$(mktemp -d -t boot2-release-XXXXXX) 61 trap 'rm -rf "$VAULT"' EXIT 62 63 # Portable sha256. 64 if command -v sha256sum >/dev/null 2>&1; then 65 sha256() { sha256sum "$1" | awk '{print $1}'; } 66 else 67 sha256() { shasum -a 256 "$1" | awk '{print $1}'; } 68 fi 69 70 log() { printf '[release] %s\n' "$*"; } 71 hr() { printf '[release] ===================== %s =====================\n' "$*"; } 72 73 t0=$(date +%s) 74 75 hr "build" 76 log "make clean" 77 make clean >/dev/null 78 log "make package ARCH=$ARCH DRIVER=$DRIVER" 79 make package ARCH="$ARCH" DRIVER="$DRIVER" 80 ARCHIVE=$VAULT/$NAME.original.tar.gz 81 [ -f "$REL_DIR/$NAME.tar.gz" ] || { log "FAIL: package produced no archive"; exit 1; } 82 cp "$REL_DIR/$NAME.tar.gz" "$ARCHIVE" 83 SHA_A=$(sha256 "$ARCHIVE") 84 log "archive sha256: $SHA_A" 85 86 # Extract before regenerating checkout inputs. The verify directory must 87 # live under $HOME because the macOS podman VM only mounts /Users/. 88 hr "extract" 89 # macOS podman VM only mounts /Users/, so the verify dir must live 90 # under $HOME. ~/.cache is a stable, throwaway-friendly location. 91 VERIFY_BASE=$HOME/.cache/boot2-release-verify/$ARCH 92 log "extract -> $VERIFY_BASE/$NAME" 93 rm -rf "$VERIFY_BASE" 94 mkdir -p "$VERIFY_BASE" 95 tar xzf "$ARCHIVE" -C "$VERIFY_BASE" 96 97 SEALED=$VERIFY_BASE/$NAME 98 INPUT_MANIFEST=$SEALED/INPUT_MANIFEST.txt 99 OUTPUT_MANIFEST=$SEALED/OUTPUT_MANIFEST.txt 100 [ -f "$INPUT_MANIFEST" ] || { log "FAIL: archive has no INPUT_MANIFEST.txt"; exit 1; } 101 [ -f "$OUTPUT_MANIFEST" ] || { log "FAIL: archive has no OUTPUT_MANIFEST.txt"; exit 1; } 102 103 # Independently regenerate canonical inputs from the checkout. This is 104 # cheap compared with boot3 and proves prep-src produces the sealed tree. 105 hr "inputs" 106 log "make clean" 107 make clean >/dev/null 108 log "make src ARCH=$ARCH DRIVER=$DRIVER" 109 make src ARCH="$ARCH" DRIVER="$DRIVER" 110 REGENERATED_INPUTS=$VAULT/$NAME.inputs.regenerated.txt 111 tools/release/input-manifest.sh "build/$ARCH/src" boot "$REGENERATED_INPUTS" 112 if ! cmp -s "$INPUT_MANIFEST" "$REGENERATED_INPUTS"; then 113 log "FAIL: regenerated canonical inputs differ from INPUT_MANIFEST.txt" 114 diff -u "$INPUT_MANIFEST" "$REGENERATED_INPUTS" || true 115 exit 1 116 fi 117 log "OK: regenerated canonical inputs match ($(wc -l < "$INPUT_MANIFEST" | tr -d ' ') files)" 118 119 # Repackage the extracted payload with the same deterministic archiver. 120 # This proves the published tar bytes are stable without rebuilding boot3. 121 hr "repackage" 122 REPACKED=$VAULT/$NAME.repacked.tar.gz 123 tools/release/tar-payload.sh "$SEALED" "$REPACKED" 124 SHA_REPACKED=$(sha256 "$REPACKED") 125 if [ "$SHA_A" != "$SHA_REPACKED" ]; then 126 log "FAIL: repackaged payload differs from original archive" 127 log " original : $SHA_A" 128 log " repacked : $SHA_REPACKED" 129 exit 1 130 fi 131 log "OK: repackaged payload is byte-identical" 132 log " sha256 = $SHA_A" 133 134 # End-to-end build from the archive. Always run podman first: a seed 135 # verification needs the podman-built boot6 kernel as its runtime. 136 hr "verify" 137 138 log "running ./verify.sh (DRIVER=podman) — this rebuilds the chain" 139 ( cd "$SEALED" && DRIVER=podman ./verify.sh ) 140 if [ "$DRIVER" = seed ]; then 141 log "running ./verify.sh (DRIVER=seed) — this closes the kernel loop" 142 ( cd "$SEALED" && DRIVER=seed ./verify.sh ) 143 fi 144 145 OUTPUT_MANIFEST_SHA=$(sha256 "$OUTPUT_MANIFEST") 146 147 # Promote to dist/ — the only directory we treat as "publishable". 148 hr "mint" 149 mkdir -p "$DIST" 150 cp "$ARCHIVE" "$DIST/$NAME.tar.gz" 151 printf '%s %s\n' "$SHA_A" "$NAME.tar.gz" > "$DIST/$NAME.tar.gz.sha256" 152 cp "$OUTPUT_MANIFEST" "$DIST/$NAME.outputs.sha256" 153 154 # Provenance sidecar — keeps git rev / build host / driver / timestamp 155 # outside the tarball so they don't perturb the content hash. 156 { 157 printf 'tarball: %s.tar.gz\n' "$NAME" 158 printf 'sha256: %s\n' "$SHA_A" 159 printf 'outputs: %s.outputs.sha256\n' "$NAME" 160 printf 'outputs_sha256: %s\n' "$OUTPUT_MANIFEST_SHA" 161 printf 'arch: %s\n' "$ARCH" 162 printf 'driver: %s\n' "$DRIVER" 163 printf 'git_rev: %s%s\n' "$REV" "$DIRTY" 164 printf 'built_at: %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" 165 printf 'built_on: %s %s\n' "$(uname -s)" "$(uname -m)" 166 } > "$DIST/$NAME.tar.gz.provenance" 167 168 # Clean up the verify dir (it's reproducible from the tarball). 169 rm -rf "$VERIFY_BASE" 170 171 elapsed=$(( $(date +%s) - t0 )) 172 bytes=$(wc -c < "$DIST/$NAME.tar.gz" | tr -d ' ') 173 log "MINTED in ${elapsed}s" 174 log " tarball : $DIST/$NAME.tar.gz ($bytes bytes)" 175 log " sha256 : $SHA_A" 176 log " outputs : $DIST/$NAME.outputs.sha256"