89 lines
4.7 KiB
Bash
Executable File
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.'
|