Files
archy/scripts/build-iso-release.sh
T

89 lines
4.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# Build an ISO from the signed pending OTA byte domain, before feed promotion.
# Invoke under flock /tmp/opencode/release-heavy.lock for the entire operation.
set -euo pipefail
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO"
UNBUNDLED=1 RC_OVERRIDE='' TARGET_PLAN='' INPUT_MANIFEST=''
while [ "$#" -gt 0 ]; do
case "$1" in
--input-manifest) INPUT_MANIFEST="${2:?--input-manifest requires reviewed JSON}"; shift 2 ;;
--bundled) UNBUNDLED=0; shift ;;
--rc) RC_OVERRIDE="${2:?--rc requires a number}"; shift 2 ;;
--os-plan-sha256) TARGET_PLAN="${2:?--os-plan-sha256 requires a digest}"; shift 2 ;;
*) echo "Unsupported flag: $1 (release gates cannot be skipped)" >&2; exit 2 ;;
esac
done
[[ "$RC_OVERRIDE" = '' || "$RC_OVERRIDE" =~ ^[0-9]+$ ]] || exit 2
[[ "$TARGET_PLAN" = '' || "$TARGET_PLAN" =~ ^[0-9a-f]{64}$ ]] || exit 2
[ -n "$INPUT_MANIFEST" ] || { echo "--input-manifest is required" >&2; exit 2; }
INPUT_MANIFEST=$(realpath "$INPUT_MANIFEST")
VERSION=$(python3 -c 'import tomllib; print(tomllib.load(open("core/archipelago/Cargo.toml", "rb"))["package"]["version"])')
SOURCE_COMMIT=$(python3 scripts/release_payload.py source --version "$VERSION")
MANIFEST="$REPO/releases/pending/v$VERSION/manifest.json"
if [ -n "$TARGET_PLAN" ]; then
MANIFEST="$REPO/releases/pending/v$VERSION/targets/$TARGET_PLAN/manifest.json"
test "$TARGET_PLAN" = "$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["os_update"]["plan_sha256"])' "$MANIFEST")"
fi
ARTIFACTS="$REPO/releases/v$VERSION"
BACKEND="$ARTIFACTS/archipelago"
FRONTEND="$ARTIFACTS/archipelago-frontend-$VERSION.tar.gz"
bash scripts/check-release-manifest.sh "$MANIFEST"
test "$SOURCE_COMMIT" = "$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["source_commit"])' "$MANIFEST")"
"$BACKEND" ceremony verify "$MANIFEST"
python3 scripts/release_payload.py companion
# The separately reviewed image manifest must select the signed OTA payload too.
python3 - "$INPUT_MANIFEST" "$BACKEND" "$FRONTEND" <<'PYBIND'
import hashlib,json,sys
from pathlib import Path
manifest=json.loads(Path(sys.argv[1]).read_text())
for name,path in [('archipelago',sys.argv[2]),('web-ui.tar',sys.argv[3])]:
with open(path,'rb') as stream: expected=hashlib.file_digest(stream,'sha256').hexdigest()
if manifest['artifacts'][name]['sha256'] != expected: raise SystemExit('image manifest differs from signed OTA: '+name)
PYBIND
bash tests/release/run.sh
python3 scripts/check-app-catalog-drift.py --release --strict
command -v qemu-system-x86_64 >/dev/null
# The ISO receives the exact flat archive checked above, not a mutable dist that
# merely contains the version string. Keep it for post-build byte comparison.
WORK=$(mktemp -d -t archipelago-iso-release.XXXXXX)
trap 'rm -rf "$WORK"' EXIT
mkdir "$WORK/web-ui"
tar -xzf "$FRONTEND" -C "$WORK/web-ui" --no-same-owner
touch "$WORK/build-started"
env_args=(UNBUNDLED="$UNBUNDLED" BUILD_FROM_SOURCE=0 DEV_SERVER=localhost
ARCHIPELAGO_BIN="$BACKEND" ARCHIPELAGO_WEB_UI="$WORK/web-ui")
[ -z "$RC_OVERRIDE" ] || env_args+=(RC="$RC_OVERRIDE")
sudo -E env "${env_args[@]}" nice -n 5 bash image-recipe/build-debian-iso.sh --source-ref "$SOURCE_COMMIT" --input-manifest "$INPUT_MANIFEST"
test "$SOURCE_COMMIT" = "$(python3 scripts/release_payload.py source --version "$VERSION")"
ISO=$(python3 - "$VERSION" "$WORK/build-started" <<'PY'
from pathlib import Path
import sys
start = Path(sys.argv[2]).stat().st_mtime_ns
paths = [p for p in Path('image-recipe/results').glob(f'archipelago-installer-{sys.argv[1]}*-x86_64_RC*.iso') if p.stat().st_mtime_ns > start]
if len(paths) != 1:
raise SystemExit('expected exactly one newly built ISO; refusing stale/ambiguous result')
print(paths[0].resolve())
PY
)
bash scripts/iso-smoke-test.sh "$ISO" "$VERSION"
# Inspect actual ISO bytes without mounting or modifying a node.
mkdir "$WORK/extracted"
xorriso -osirrox on -indev "$ISO" -extract /archipelago/web-ui "$WORK/extracted/web-ui"
diff -qr "$WORK/web-ui" "$WORK/extracted/web-ui"
mkdir "$WORK/qemu"
read -r SSH_PORT HTTP_PORT < <(python3 - <<'PY'
import socket
with socket.socket() as a, socket.socket() as b:
a.bind(('127.0.0.1', 0)); b.bind(('127.0.0.1', 0))
print(a.getsockname()[1], b.getsockname()[1])
PY
)
TMPDIR="$WORK/qemu" QEMU_SSH_PORT="$SSH_PORT" QEMU_HTTP_PORT="$HTTP_PORT" \
bash image-recipe/_archived/test-iso-qemu.sh "$ISO" 180
(cd "$(dirname "$ISO")" && sha256sum "$(basename "$ISO")" > "$ISO.sha256")
echo "ISO artifact gates passed: $ISO (source $SOURCE_COMMIT)"
echo "Operator signing command (input now exists): bash scripts/sign-iso-checksums.sh '$ISO'"
echo 'Installed/running kernel, physical install, canary and operator acceptance remain separate gates.'