fix(release): publish assets before exposing manifest
This commit is contained in:
@@ -1,21 +1,22 @@
|
||||
#!/bin/bash
|
||||
# Validate releases/manifest.json:
|
||||
# Validate the live or a pending release manifest:
|
||||
# - version matches core/archipelago/Cargo.toml
|
||||
# - changelog contains curated release notes, not raw git log output
|
||||
# - every component's download_url exists on disk and matches sha256/size
|
||||
#
|
||||
# Run on every push from CI, and also locally before publishing a release:
|
||||
# scripts/check-release-manifest.sh
|
||||
# scripts/check-release-manifest.sh [path/to/manifest.json]
|
||||
#
|
||||
# Exits non-zero on any mismatch so the release process fails loud.
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
MANIFEST="$REPO_ROOT/releases/manifest.json"
|
||||
MANIFEST="${1:-$REPO_ROOT/releases/manifest.json}"
|
||||
[[ "$MANIFEST" = /* ]] || MANIFEST="$REPO_ROOT/$MANIFEST"
|
||||
|
||||
if [ ! -f "$MANIFEST" ]; then
|
||||
echo "❌ releases/manifest.json missing"
|
||||
echo "❌ manifest missing: $MANIFEST"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -25,6 +26,18 @@ ok() { echo "✅ $*"; }
|
||||
MANIFEST_VERSION=$(python3 -c "import json; print(json.load(open('$MANIFEST'))['version'])")
|
||||
CARGO_VERSION=$(grep '^version' "$REPO_ROOT/core/archipelago/Cargo.toml" | head -1 | sed -E 's/.*"([^"]+)".*/\1/')
|
||||
|
||||
# A prepared release deliberately leaves the live manifest on the previous
|
||||
# version. Ordinary pushes are therefore harmless: only the publisher promotes
|
||||
# the pending manifest after its assets have been uploaded and downloaded back.
|
||||
if [ "$MANIFEST_VERSION" != "$CARGO_VERSION" ] && [ "$MANIFEST" = "$REPO_ROOT/releases/manifest.json" ]; then
|
||||
PENDING="$REPO_ROOT/releases/pending/v${CARGO_VERSION}/manifest.json"
|
||||
if [ -f "$PENDING" ]; then
|
||||
ok "live manifest remains v${MANIFEST_VERSION} while v${CARGO_VERSION} is pending"
|
||||
MANIFEST="$PENDING"
|
||||
MANIFEST_VERSION="$CARGO_VERSION"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$MANIFEST_VERSION" != "$CARGO_VERSION" ]; then
|
||||
fail "manifest version ($MANIFEST_VERSION) ≠ Cargo.toml ($CARGO_VERSION)"
|
||||
fi
|
||||
@@ -105,4 +118,4 @@ for i in $(seq 0 $((COMPONENT_COUNT - 1))); do
|
||||
done
|
||||
|
||||
echo
|
||||
ok "releases/manifest.json passes all checks — safe to publish v${MANIFEST_VERSION}"
|
||||
ok "$MANIFEST passes all checks — safe to publish v${MANIFEST_VERSION}"
|
||||
|
||||
@@ -298,7 +298,7 @@ echo ""
|
||||
cat "$OUTPUT_FILE"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Review the manifest above"
|
||||
echo " 2. Upload artifacts to Gitea release v$VERSION"
|
||||
echo " 3. Commit manifest.json to releases/manifest.json on main"
|
||||
echo " 4. Tag the release: git tag v$VERSION && git push --tags"
|
||||
echo " 1. Review and sign the manifest above"
|
||||
echo " 2. Keep it under releases/pending/v$VERSION/ — do NOT replace the live manifest"
|
||||
echo " 3. Run scripts/publish-release-assets.sh $VERSION gitea-vps2"
|
||||
echo " (it uploads + verifies assets before atomically promoting the manifest)"
|
||||
|
||||
+39
-49
@@ -2,7 +2,8 @@
|
||||
# create-release.sh — Full release automation for Archipelago
|
||||
#
|
||||
# Bumps version in Cargo.toml and package.json, generates changelog from git log,
|
||||
# creates release manifest, and creates git tag.
|
||||
# creates a pending release manifest, and creates git tag. The live manifest is
|
||||
# promoted only by publish-release-assets.sh after the assets are verified.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/create-release.sh 1.0.0 # Release v1.0.0
|
||||
@@ -30,9 +31,9 @@ for arg in "$@"; do
|
||||
echo " 2. Bump version in Cargo.toml and package.json"
|
||||
echo " 3. Build backend"
|
||||
echo " 4. Build frontend"
|
||||
echo " 5. Generate changelog from git log"
|
||||
echo " 6. Create release manifest"
|
||||
echo " 7. Commit version bump"
|
||||
echo " 5. Validate the curated changelog"
|
||||
echo " 6. Create pending release manifest"
|
||||
echo " 7. Commit release preparation"
|
||||
echo " 8. Create git tag v{VERSION}"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
@@ -121,14 +122,13 @@ if $DRY_RUN; then
|
||||
echo " 2. Update neode-ui/package.json version to $VERSION"
|
||||
echo " 3. Build backend (cargo build --release -p archipelago)"
|
||||
echo " 4. Build frontend (npm run build)"
|
||||
echo " 5. Generate changelog from git log since v${CURRENT_CARGO_VERSION}"
|
||||
echo " 6. Create release manifest"
|
||||
echo " 7. Commit: 'chore: release v${VERSION}'"
|
||||
echo " 5. Validate the curated changelog"
|
||||
echo " 6. Create pending release manifest (the live manifest stays unchanged)"
|
||||
echo " 7. Commit: 'chore: prepare release v${VERSION}'"
|
||||
echo " 8. Tag: v${VERSION}"
|
||||
echo ""
|
||||
echo "After this script, you would:"
|
||||
echo " - Push: git push && git push --tags"
|
||||
echo " - Build ISOs on server: ssh archipelago@192.0.2.10"
|
||||
echo "After this script, publish only with:"
|
||||
echo " scripts/publish-release-assets.sh ${VERSION} gitea-vps2"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -214,9 +214,13 @@ if [ ! -f "$CHANGELOG_FILE" ] || ! grep -q "^## v${VERSION} (" "$CHANGELOG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[6/8] Creating release manifest..."
|
||||
mkdir -p "$PROJECT_ROOT/releases"
|
||||
"$SCRIPT_DIR/create-release-manifest.sh" --version "$VERSION" --date "$RELEASE_DATE" --output "$PROJECT_ROOT/releases/manifest.json" 2>&1 | grep -v "^$"
|
||||
echo "[6/8] Creating pending release manifest..."
|
||||
# Never write the fleet-visible path here. A normal `git push main` must not be
|
||||
# capable of advertising assets which have not been uploaded yet.
|
||||
PENDING_DIR="$PROJECT_ROOT/releases/pending/v${VERSION}"
|
||||
PENDING_MANIFEST="$PENDING_DIR/manifest.json"
|
||||
mkdir -p "$PENDING_DIR"
|
||||
"$SCRIPT_DIR/create-release-manifest.sh" --version "$VERSION" --date "$RELEASE_DATE" --output "$PENDING_MANIFEST" 2>&1 | grep -v "^$"
|
||||
|
||||
# §A supply-chain: the OTA manifest must carry the release-root signature.
|
||||
# Nodes refuse to AUTO-apply unsigned manifests, and publish-release-assets.sh
|
||||
@@ -239,60 +243,45 @@ if [ -n "${RELEASE_MASTER_MNEMONIC:-}" ] || [ -t 0 ]; then
|
||||
echo " Enter by itself will NOT submit; pasting twice concatenates"
|
||||
echo " the phrases and fails on word count."
|
||||
echo "════════════════════════════════════════════════════════════════"
|
||||
"$SIGNER" ceremony sign "$PROJECT_ROOT/releases/manifest.json"
|
||||
"$SIGNER" ceremony verify "$PROJECT_ROOT/releases/manifest.json"
|
||||
"$SIGNER" ceremony sign "$PENDING_MANIFEST"
|
||||
"$SIGNER" ceremony verify "$PENDING_MANIFEST"
|
||||
else
|
||||
echo "⚠ WARNING: no TTY and RELEASE_MASTER_MNEMONIC unset — manifest left UNSIGNED."
|
||||
echo " This run will ABORT before committing (step 7 refuses an unsigned"
|
||||
echo " manifest), because nodes read releases/manifest.json from branch main"
|
||||
echo " and would refuse to auto-apply it."
|
||||
echo " Sign it, then re-run: bash scripts/sign-manifest.sh"
|
||||
echo "⚠ WARNING: no TTY and RELEASE_MASTER_MNEMONIC unset — pending manifest left UNSIGNED."
|
||||
echo " This run will ABORT before committing (step 7 refuses an unsigned manifest)."
|
||||
echo " Sign it, then re-run: bash scripts/sign-manifest.sh $PENDING_MANIFEST"
|
||||
fi
|
||||
cp "$PROJECT_ROOT/releases/manifest.json" "$PROJECT_ROOT/release-manifest.json"
|
||||
|
||||
echo "[6c/8] Staging release artifacts for validation..."
|
||||
VERSION_DIR="$PROJECT_ROOT/releases/v${VERSION}"
|
||||
FRONTEND_ARCHIVE="/tmp/archipelago-frontend-${VERSION}.tar.gz"
|
||||
mkdir -p "$VERSION_DIR"
|
||||
install -m 0755 "$PROJECT_ROOT/core/target/release/archipelago" "$VERSION_DIR/archipelago"
|
||||
install -m 0644 "$FRONTEND_ARCHIVE" "$VERSION_DIR/archipelago-frontend-${VERSION}.tar.gz"
|
||||
"$SCRIPT_DIR/check-release-manifest.sh"
|
||||
"$SCRIPT_DIR/check-release-manifest.sh" "$PENDING_MANIFEST"
|
||||
|
||||
# §A supply-chain gate, mirroring publish-release-assets.sh — but EARLIER,
|
||||
# because publishing is not the first way an unsigned manifest reaches the
|
||||
# fleet. Nodes fetch releases/manifest.json straight from branch `main`
|
||||
# (see the verification URLs printed below), so the COMMIT is what exposes
|
||||
# it, not the publish. publish-release-assets.sh refusing to ship is a
|
||||
# backstop that arrives one step too late: by then the unsigned manifest is
|
||||
# already on main and the fleet is already refusing to auto-apply.
|
||||
#
|
||||
# This is why every cycle needed a manual catch. The signing block above is
|
||||
# conditional — no TTY and no RELEASE_MASTER_MNEMONIC means it prints a
|
||||
# warning and falls through — and the commit then happened anyway. A release
|
||||
# commit carrying a manifest no node will accept has no valid use, so refuse
|
||||
# to create one rather than leave a tag that has to be re-cut.
|
||||
# §A supply-chain gate, mirroring publish-release-assets.sh. The pending path
|
||||
# prevents an ordinary main push from exposing the release, but an unsigned
|
||||
# manifest is still unpublishable and must never be tagged as ready.
|
||||
# Release root ROTATED 2026-08-05. v1.7.122-alpha was the last release signed
|
||||
# with the old root (z6Mkkid…q7ur) — it is the release that installed this
|
||||
# pin on every node. From v1.7.123 onward the new root signs, and nodes
|
||||
# running .122+ reject anything signed with the old key.
|
||||
EXPECTED_DID="did:key:z6Mkfu5LT8d4DjETtrkATvHh9Dvcbnr7zBCUwfau8Sw7DLWT"
|
||||
if ! grep -q '"signature":' "$PROJECT_ROOT/releases/manifest.json" \
|
||||
|| ! grep -q "\"signed_by\": \"$EXPECTED_DID\"" "$PROJECT_ROOT/releases/manifest.json"; then
|
||||
if ! grep -q '"signature":' "$PENDING_MANIFEST" \
|
||||
|| ! grep -q "\"signed_by\": \"$EXPECTED_DID\"" "$PENDING_MANIFEST"; then
|
||||
echo "" >&2
|
||||
echo "Error: releases/manifest.json is NOT signed by the release root." >&2
|
||||
echo " Refusing to commit — nodes read this file from branch main and will" >&2
|
||||
echo " refuse to auto-apply it, so the release would be dead on arrival." >&2
|
||||
echo "Error: the pending manifest is NOT signed by the release root." >&2
|
||||
echo " Refusing to commit an unpublishable release." >&2
|
||||
echo "" >&2
|
||||
echo " Sign it, then re-run this script:" >&2
|
||||
echo " bash scripts/sign-manifest.sh" >&2
|
||||
echo " bash scripts/sign-manifest.sh $PENDING_MANIFEST" >&2
|
||||
echo "" >&2
|
||||
echo " (Signing needs a TTY for the mnemonic prompt, or RELEASE_MASTER_MNEMONIC set.)" >&2
|
||||
exit 1
|
||||
fi
|
||||
"$SIGNER" ceremony verify "$PROJECT_ROOT/releases/manifest.json" \
|
||||
"$SIGNER" ceremony verify "$PENDING_MANIFEST" \
|
||||
|| { echo "Error: manifest signature failed cryptographic verification — refusing to commit" >&2; exit 1; }
|
||||
|
||||
echo "[7/8] Committing version bump..."
|
||||
echo "[7/8] Committing release preparation..."
|
||||
git -C "$PROJECT_ROOT" add \
|
||||
core/archipelago/Cargo.toml \
|
||||
core/Cargo.lock \
|
||||
@@ -300,15 +289,16 @@ git -C "$PROJECT_ROOT" add \
|
||||
neode-ui/package-lock.json \
|
||||
neode-ui/public/catalog.json \
|
||||
CHANGELOG.md \
|
||||
releases/manifest.json \
|
||||
release-manifest.json \
|
||||
2>/dev/null || true
|
||||
# releases/** is ignored because binaries live in Gitea attachments; force-add
|
||||
# only this small signed pending manifest.
|
||||
git -C "$PROJECT_ROOT" add -f "releases/pending/v${VERSION}/manifest.json"
|
||||
# Cargo.lock (rewritten by the release build after the version bump) and
|
||||
# neode-ui/public/catalog.json (regenerated by the frontend build) belong in
|
||||
# THIS commit: leaving them dirty failed build-iso-release.sh's clean-tree
|
||||
# preflight on three consecutive releases (.127-.129, 2026-08-09/10).
|
||||
|
||||
git -C "$PROJECT_ROOT" commit -m "chore: release v${VERSION}"
|
||||
git -C "$PROJECT_ROOT" commit -m "chore: prepare release v${VERSION}"
|
||||
|
||||
echo "[8/8] Creating git tag..."
|
||||
git -C "$PROJECT_ROOT" tag -a "v${VERSION}" -m "Release v${VERSION}"
|
||||
@@ -319,8 +309,8 @@ echo ""
|
||||
echo "Artifacts:"
|
||||
echo " - Version bumped in Cargo.toml and package.json"
|
||||
echo " - Changelog updated in CHANGELOG.md"
|
||||
echo " - Release manifest: releases/manifest.json"
|
||||
echo " - Release manifest copy: release-manifest.json"
|
||||
echo " - Pending manifest: releases/pending/v${VERSION}/manifest.json"
|
||||
echo " - Live manifest: unchanged until assets pass publication verification"
|
||||
echo " - Staged artifacts: releases/v${VERSION}/"
|
||||
echo " - Git tag: v${VERSION}"
|
||||
echo ""
|
||||
|
||||
@@ -16,14 +16,26 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
VERSION_DIR="$PROJECT_ROOT/releases/v${VERSION}"
|
||||
BACKEND="$VERSION_DIR/archipelago"
|
||||
FRONTEND="$VERSION_DIR/archipelago-frontend-${VERSION}.tar.gz"
|
||||
PENDING_MANIFEST="$PROJECT_ROOT/releases/pending/v${VERSION}/manifest.json"
|
||||
LIVE_MANIFEST="$PROJECT_ROOT/releases/manifest.json"
|
||||
if [ -f "$PENDING_MANIFEST" ]; then
|
||||
MANIFEST="$PENDING_MANIFEST"
|
||||
PROMOTE_MANIFEST=1
|
||||
else
|
||||
# Backward compatibility for releases prepared before pending manifests.
|
||||
MANIFEST="$LIVE_MANIFEST"
|
||||
PROMOTE_MANIFEST=0
|
||||
fi
|
||||
|
||||
fail() { echo "Error: $*" >&2; exit 1; }
|
||||
|
||||
[ -f "$PROJECT_ROOT/releases/manifest.json" ] || fail "releases/manifest.json missing"
|
||||
[ -f "$MANIFEST" ] || fail "release manifest missing: $MANIFEST"
|
||||
MANIFEST_VERSION=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["version"])' "$MANIFEST")
|
||||
[ "$MANIFEST_VERSION" = "$VERSION" ] || fail "requested v$VERSION but $MANIFEST describes v$MANIFEST_VERSION"
|
||||
[ -f "$BACKEND" ] || fail "backend artifact missing: $BACKEND"
|
||||
[ -f "$FRONTEND" ] || fail "frontend artifact missing: $FRONTEND"
|
||||
|
||||
"$SCRIPT_DIR/check-release-manifest.sh"
|
||||
"$SCRIPT_DIR/check-release-manifest.sh" "$MANIFEST"
|
||||
|
||||
# §A supply-chain gate: never publish an unsigned OTA manifest. Fleet nodes
|
||||
# with the pinned release-root anchor refuse to auto-apply unsigned manifests,
|
||||
@@ -32,11 +44,11 @@ fail() { echo "Error: $*" >&2; exit 1; }
|
||||
# Release root ROTATED 2026-08-05; see create-release.sh. New root from
|
||||
# v1.7.123 onward.
|
||||
EXPECTED_DID="did:key:z6Mkfu5LT8d4DjETtrkATvHh9Dvcbnr7zBCUwfau8Sw7DLWT"
|
||||
grep -q '"signature":' "$PROJECT_ROOT/releases/manifest.json" \
|
||||
&& grep -q "\"signed_by\": \"$EXPECTED_DID\"" "$PROJECT_ROOT/releases/manifest.json" \
|
||||
|| fail "releases/manifest.json is not signed by the release root — run: bash scripts/sign-manifest.sh"
|
||||
grep -q '"signature":' "$MANIFEST" \
|
||||
&& grep -q "\"signed_by\": \"$EXPECTED_DID\"" "$MANIFEST" \
|
||||
|| fail "$MANIFEST is not signed by the release root — run: bash scripts/sign-manifest.sh $MANIFEST"
|
||||
if [ -x "$PROJECT_ROOT/core/target/release/archipelago" ]; then
|
||||
"$PROJECT_ROOT/core/target/release/archipelago" ceremony verify "$PROJECT_ROOT/releases/manifest.json" \
|
||||
"$PROJECT_ROOT/core/target/release/archipelago" ceremony verify "$MANIFEST" \
|
||||
|| fail "manifest signature failed cryptographic verification"
|
||||
fi
|
||||
|
||||
@@ -130,12 +142,36 @@ echo "Verifying public download URLs (full GET + size + sha256)..."
|
||||
# hand during recovery. It fails hard on the first bad asset — the previous
|
||||
# inline `while read` ran in a pipe subshell, where a `fail` (exit) killed only
|
||||
# the subshell and let this script march on to "published and verified".
|
||||
"$PROJECT_ROOT/scripts/check-release-assets.sh" "$PROJECT_ROOT/releases/manifest.json" \
|
||||
"$PROJECT_ROOT/scripts/check-release-assets.sh" "$MANIFEST" \
|
||||
|| fail "asset verification failed — NOT pushing main. The manifest stays off the branch nodes read, so no node sees a version it cannot fetch. Repair the assets and re-run."
|
||||
|
||||
# Assets are proven fetchable — only now does the manifest become live.
|
||||
echo "Assets verified. Pushing main to $REMOTE (this makes v${VERSION} live)..."
|
||||
git -C "$PROJECT_ROOT" push "$REMOTE" main
|
||||
# Assets are proven fetchable — only now may the manifest become live. First
|
||||
# incorporate concurrent work, then promote in a dedicated commit. Until the
|
||||
# final push succeeds the remote still serves the previous manifest.
|
||||
echo "Assets verified. Synchronizing main before manifest promotion..."
|
||||
git -C "$PROJECT_ROOT" fetch "$REMOTE" main
|
||||
git -C "$PROJECT_ROOT" merge --no-edit "$REMOTE/main"
|
||||
|
||||
if [ "$PROMOTE_MANIFEST" = "1" ]; then
|
||||
cp "$MANIFEST" "$LIVE_MANIFEST"
|
||||
cp "$MANIFEST" "$PROJECT_ROOT/release-manifest.json"
|
||||
git -C "$PROJECT_ROOT" add releases/manifest.json release-manifest.json
|
||||
git -C "$PROJECT_ROOT" rm -f -- "releases/pending/v${VERSION}/manifest.json"
|
||||
git -C "$PROJECT_ROOT" commit -m "chore: publish release v${VERSION}"
|
||||
fi
|
||||
|
||||
echo "Publishing verified manifest to main (this makes v${VERSION} live)..."
|
||||
# A concurrent push can race the fetch above. Merge and retry without ever
|
||||
# force-pushing; the remote remains on its old, working manifest meanwhile.
|
||||
for attempt in 1 2 3; do
|
||||
if git -C "$PROJECT_ROOT" push "$REMOTE" HEAD:main; then
|
||||
break
|
||||
fi
|
||||
[ "$attempt" -lt 3 ] || fail "main advanced repeatedly; assets are safe but manifest was not promoted"
|
||||
echo "main advanced during publication; merging and retrying..."
|
||||
git -C "$PROJECT_ROOT" fetch "$REMOTE" main
|
||||
git -C "$PROJECT_ROOT" merge --no-edit "$REMOTE/main"
|
||||
done
|
||||
|
||||
echo "Release v${VERSION} published and verified on $REMOTE."
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# One-step OTA-manifest signer (counterpart to sign-catalog.sh).
|
||||
#
|
||||
# Run: bash scripts/sign-manifest.sh
|
||||
# Run: bash scripts/sign-manifest.sh [path/to/manifest.json]
|
||||
# Then: paste your 24-word release master mnemonic, press Enter, then Ctrl-D.
|
||||
#
|
||||
# Signs releases/manifest.json in place and cryptographically verifies the
|
||||
# Signs the requested manifest (live by default) and cryptographically verifies the
|
||||
# result against the pinned release-root anchor. The mnemonic is read from the
|
||||
# terminal only (never stored, never in shell history, never passed to Claude).
|
||||
#
|
||||
@@ -18,7 +18,9 @@
|
||||
set -euo pipefail
|
||||
|
||||
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
MANIFEST="$REPO/releases/manifest.json"
|
||||
MANIFEST="${1:-$REPO/releases/manifest.json}"
|
||||
[[ "$MANIFEST" = /* ]] || MANIFEST="$REPO/$MANIFEST"
|
||||
[ -f "$MANIFEST" ] || { echo "Manifest not found: $MANIFEST" >&2; exit 1; }
|
||||
|
||||
# Use ONLY a prebuilt signer — never compile here (compiling caused hangs in
|
||||
# the earlier catalog ceremony). Prefer the repo's release build.
|
||||
@@ -41,9 +43,10 @@ echo "════════════════════════
|
||||
|
||||
echo
|
||||
if "$BIN" ceremony verify "$MANIFEST"; then
|
||||
echo "✅ SUCCESS — manifest signed by the pinned release root."
|
||||
echo " Commit + push releases/manifest.json (and release-manifest.json if present)."
|
||||
cp "$MANIFEST" "$REPO/release-manifest.json" 2>/dev/null || true
|
||||
echo "✅ SUCCESS — manifest signed by the pinned release root: $MANIFEST"
|
||||
if [ "$MANIFEST" = "$REPO/releases/manifest.json" ]; then
|
||||
cp "$MANIFEST" "$REPO/release-manifest.json"
|
||||
fi
|
||||
else
|
||||
echo "❌ Signature did NOT verify against the pinned release-root anchor."
|
||||
echo " Do NOT commit. Check the mnemonic and re-run."
|
||||
|
||||
Reference in New Issue
Block a user