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