Demo images / Build & push demo images (push) Failing after 2m16s
Keeps the dev and test tooling an outside contributor would want, and takes our node addresses out of it. Scripts that silently defaulted to one of our nodes now require an explicit host and exit 2 without one: smoke-test.sh, trust-archipelago-cert.sh, dev-container-test.sh (which also derives its RPC and health URLs from the SSH target instead of a second hardcoded copy), and image-recipe/dev-branding.sh. A default that points at a machine the user does not own is worse than no default: it fails confusingly, or reaches a stranger's device. Usage examples, mock data and test fixtures move to the RFC 5737 documentation range (192.0.2.0/24). CGNAT test values stay inside 100.64.0.0/10 so the range-check semantics they exercise still hold, and 192.168.1.0/.1/.254 are left alone — those are gateway logic and UI placeholders, not our addresses. Playwright and the perf spec defaulted their baseURL to one of our nodes; they now default to localhost:8100, the local dev server. Removed neode-ui APP_URLS entirely. It is dead code — exported, never imported — and it pinned fedimint's *prod* launch URL to 192.168.1.228:8175. Had anything consumed it, every user's node would have tried to reach an address that on their LAN is either nothing or someone else's machine. Deleting beats sanitizing dead config. Verified: frontend 868/868 vitest across 108 files; archipelago-container 75/75; mesh tests 9/9; audit-secrets 5/5. Zero node addresses and zero node names remain in tracked files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
297 lines
12 KiB
Bash
Executable File
297 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# 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.
|
|
#
|
|
# Usage:
|
|
# ./scripts/create-release.sh 1.0.0 # Release v1.0.0
|
|
# ./scripts/create-release.sh 1.0.0 --dry-run # Preview without changes
|
|
#
|
|
# Releases are tarball-only. ISO builds are archived under
|
|
# image-recipe/_archived/. Nodes OTA-update from releases/manifest.json.
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
DRY_RUN=false
|
|
VERSION=""
|
|
|
|
# Parse args
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--dry-run) DRY_RUN=true ;;
|
|
--help|-h)
|
|
echo "Usage: $0 VERSION [--dry-run]"
|
|
echo ""
|
|
echo "Steps performed:"
|
|
echo " 1. Validate version format (SemVer)"
|
|
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 " 8. Create git tag v{VERSION}"
|
|
echo ""
|
|
echo "Options:"
|
|
echo " --dry-run Show what would be done without making changes"
|
|
exit 0
|
|
;;
|
|
*)
|
|
if [ -z "$VERSION" ]; then
|
|
VERSION="$arg"
|
|
else
|
|
echo "Error: Unknown argument: $arg"
|
|
exit 1
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ -z "$VERSION" ]; then
|
|
echo "Error: VERSION argument required"
|
|
echo "Usage: $0 VERSION [--dry-run]"
|
|
exit 1
|
|
fi
|
|
|
|
# Validate SemVer format
|
|
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$'; then
|
|
echo "Error: Version '$VERSION' is not valid SemVer (expected: X.Y.Z or X.Y.Z-suffix)"
|
|
exit 1
|
|
fi
|
|
|
|
# Check we're on main branch
|
|
BRANCH=$(git -C "$PROJECT_ROOT" branch --show-current)
|
|
if [ "$BRANCH" != "main" ]; then
|
|
echo "Error: Must be on 'main' branch (currently on '$BRANCH')"
|
|
exit 1
|
|
fi
|
|
|
|
# Check for uncommitted changes
|
|
if ! git -C "$PROJECT_ROOT" diff --quiet HEAD; then
|
|
echo "Error: Uncommitted changes detected. Commit or stash first."
|
|
exit 1
|
|
fi
|
|
|
|
# ── Pre-flight test gate ──────────────────────────────────────────────
|
|
# A release must not ship if the static/frontend/backend checks fail. This
|
|
# runs the release gate harness (cargo fmt/check, catalog drift, vitest, and
|
|
# the focused cargo suites — incl. the receive/port-drift/secret regressions).
|
|
# Skipped on --dry-run, or set SKIP_RELEASE_TESTS=1 to bypass in an emergency.
|
|
# The lifecycle bats harness (tests/lifecycle/run-gate.sh) still runs separately
|
|
# against live nodes — see tests/lifecycle/TESTING.md.
|
|
if ! $DRY_RUN; then
|
|
if [ "${SKIP_RELEASE_TESTS:-0}" = "1" ]; then
|
|
echo "WARNING: SKIP_RELEASE_TESTS=1 — bypassing the pre-flight test gate"
|
|
elif [ -x "$PROJECT_ROOT/tests/release/run.sh" ]; then
|
|
echo "[0/7] Running release gate (tests/release/run.sh)..."
|
|
if ! "$PROJECT_ROOT/tests/release/run.sh"; then
|
|
echo "Error: release gate failed — aborting release. Fix the failing"
|
|
echo " stage, or re-run with SKIP_RELEASE_TESTS=1 to override."
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "WARNING: tests/release/run.sh not found/executable — skipping test gate"
|
|
fi
|
|
fi
|
|
|
|
# Check tag doesn't already exist
|
|
if git -C "$PROJECT_ROOT" tag -l "v$VERSION" | grep -q "v$VERSION"; then
|
|
echo "Error: Tag v$VERSION already exists"
|
|
exit 1
|
|
fi
|
|
|
|
# Get current version
|
|
CURRENT_CARGO_VERSION=$(grep '^version' "$PROJECT_ROOT/core/archipelago/Cargo.toml" | head -1 | sed 's/.*"\(.*\)".*/\1/')
|
|
CURRENT_NPM_VERSION=$(node -p "require('$PROJECT_ROOT/neode-ui/package.json').version")
|
|
|
|
echo "=== Archipelago Release v${VERSION} ==="
|
|
echo " Current Cargo version: ${CURRENT_CARGO_VERSION}"
|
|
echo " Current npm version: ${CURRENT_NPM_VERSION}"
|
|
echo " Target version: ${VERSION}"
|
|
echo " Dry run: ${DRY_RUN}"
|
|
echo ""
|
|
|
|
if $DRY_RUN; then
|
|
echo "[DRY RUN] Would perform the following:"
|
|
echo " 0. Run pre-flight test gate (tests/release/run.sh) — aborts on failure"
|
|
echo " 1. Update core/archipelago/Cargo.toml version to $VERSION"
|
|
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 " 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"
|
|
exit 0
|
|
fi
|
|
|
|
echo "[1/7] Bumping version in Cargo.toml..."
|
|
# Update archipelago Cargo.toml
|
|
sed -i.bak "s/^version = \".*\"/version = \"$VERSION\"/" "$PROJECT_ROOT/core/archipelago/Cargo.toml"
|
|
rm -f "$PROJECT_ROOT/core/archipelago/Cargo.toml.bak"
|
|
|
|
# Also update workspace Cargo.lock if it exists
|
|
if [ -f "$PROJECT_ROOT/core/Cargo.lock" ]; then
|
|
# Cargo will update the lock file on next build; touch the toml to trigger
|
|
true
|
|
fi
|
|
|
|
echo "[2/7] Bumping version in package.json..."
|
|
cd "$PROJECT_ROOT/neode-ui"
|
|
npm version "$VERSION" --no-git-tag-version --allow-same-version 2>/dev/null || true
|
|
cd "$PROJECT_ROOT"
|
|
|
|
echo "[3/8] Building backend..."
|
|
cd "$PROJECT_ROOT/core"
|
|
cargo build --release -p archipelago
|
|
cd "$PROJECT_ROOT"
|
|
|
|
echo "[4/8] Building frontend..."
|
|
cd "$PROJECT_ROOT/neode-ui"
|
|
npm run build 2>&1 | tail -3
|
|
cd "$PROJECT_ROOT"
|
|
|
|
# npm run build can silently no-op (vue-tsc EACCES burned us before) — a stale
|
|
# dist would ship with a perfectly valid sha256. Require the freshly built
|
|
# bundle to embed the version we just bumped to before it gets packaged.
|
|
if ! grep -rqo "${VERSION}" "$PROJECT_ROOT"/web/dist/neode-ui/assets/*.js; then
|
|
echo "Error: web/dist/neode-ui does not contain v${VERSION} — the frontend" >&2
|
|
echo " build no-opped or its output is stale. Aborting release." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "[4b/8] Building packaged radio tools (archy-reticulum-daemon, archy-rnodeconf)..."
|
|
# These ride the frontend tarball's runtime payload (radio-tools/) and are
|
|
# promoted to /usr/local/bin by bootstrap.rs — the ONLY path that updates them
|
|
# on OTA-only nodes. Stale-dist releases re-broke fleet mesh once (v1.7.117),
|
|
# so always rebuild here; the manifest script hard-fails if they're missing.
|
|
(cd "$PROJECT_ROOT/reticulum-daemon" && ./build.sh) || {
|
|
echo "Error: reticulum-daemon/build.sh failed — radio tools are release-critical" >&2
|
|
exit 1
|
|
}
|
|
|
|
echo "[5/8] Validating curated changelog..."
|
|
|
|
CHANGELOG_FILE="$PROJECT_ROOT/CHANGELOG.md"
|
|
RELEASE_DATE=$(date +%Y-%m-%d)
|
|
if [ ! -f "$CHANGELOG_FILE" ] || ! grep -q "^## v${VERSION} (" "$CHANGELOG_FILE"; then
|
|
echo "Error: CHANGELOG.md must already contain curated notes for v${VERSION}."
|
|
echo "Add a section like:"
|
|
echo ""
|
|
echo "## v${VERSION} (${RELEASE_DATE})"
|
|
echo ""
|
|
echo "- User/operator-facing change ..."
|
|
echo "- Another concrete change ..."
|
|
echo "- Validation or operational note ..."
|
|
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 "^$"
|
|
|
|
# §A supply-chain: the OTA manifest must carry the release-root signature.
|
|
# Nodes refuse to AUTO-apply unsigned manifests, and publish-release-assets.sh
|
|
# hard-refuses to ship one. The mnemonic is read interactively (or from
|
|
# RELEASE_MASTER_MNEMONIC) — it must never land in files or shell history.
|
|
SIGNER="$PROJECT_ROOT/core/target/release/archipelago"
|
|
if [ ! -x "$SIGNER" ]; then
|
|
echo "Error: release binary not found at $SIGNER — cannot sign manifest" >&2
|
|
exit 1
|
|
fi
|
|
if [ -n "${RELEASE_MASTER_MNEMONIC:-}" ] || [ -t 0 ]; then
|
|
echo "[6b/8] Signing release manifest (paste the release master mnemonic when prompted)..."
|
|
"$SIGNER" ceremony sign "$PROJECT_ROOT/releases/manifest.json"
|
|
"$SIGNER" ceremony verify "$PROJECT_ROOT/releases/manifest.json"
|
|
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"
|
|
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"
|
|
|
|
# §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.
|
|
# 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
|
|
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 "" >&2
|
|
echo " Sign it, then re-run this script:" >&2
|
|
echo " bash scripts/sign-manifest.sh" >&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" \
|
|
|| { echo "Error: manifest signature failed cryptographic verification — refusing to commit" >&2; exit 1; }
|
|
|
|
echo "[7/8] Committing version bump..."
|
|
git -C "$PROJECT_ROOT" add \
|
|
core/archipelago/Cargo.toml \
|
|
neode-ui/package.json \
|
|
neode-ui/package-lock.json \
|
|
CHANGELOG.md \
|
|
releases/manifest.json \
|
|
release-manifest.json \
|
|
2>/dev/null || true
|
|
|
|
git -C "$PROJECT_ROOT" commit -m "chore: release v${VERSION}"
|
|
|
|
echo "[8/8] Creating git tag..."
|
|
git -C "$PROJECT_ROOT" tag -a "v${VERSION}" -m "Release v${VERSION}"
|
|
|
|
echo ""
|
|
echo "=== Release v${VERSION} Ready ==="
|
|
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 " - Staged artifacts: releases/v${VERSION}/"
|
|
echo " - Git tag: v${VERSION}"
|
|
echo ""
|
|
echo "Next steps:"
|
|
echo " 1. Review: git log --oneline -5"
|
|
echo " 2. Publish commits, tag, artifacts, and verify download URLs:"
|
|
echo " scripts/publish-release-assets.sh ${VERSION} gitea-vps2"
|
|
echo " 3. Verify manifest is live on both mirrors:"
|
|
echo " curl -fsS http://localhost:3000/lfg2025/archy/raw/branch/main/releases/manifest.json"
|
|
echo " curl -fsS http://146.59.87.168:3000/lfg2025/archy/raw/branch/main/releases/manifest.json"
|