publish-manifest.sh: the live manifest must be byte-identical to the folder's and verify, with retries and named reasons; --verify-only; a failed deploy stops before the check
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
2b3ce1c7e3
commit
568d6ed9cb
2 changed files with 37 additions and 9 deletions
|
|
@ -19,6 +19,7 @@ shard run reported as exit 0, 7a7e873).
|
|||
| 4 Oct 2026 | the observer kept running old code after its fix was on master and HEAD had moved past it | `autosync.sh` restarted the observer only when its own fast-forward moved HEAD; a pull by hand (19:35 UTC, HEAD to 8a77b85) bypassed it | autosync compares the checked-out `tools/observer` tree id with a marker written at each restart and restarts on any difference; `autosync.sh check` says what it would do | `check` with no marker: "restart due", exit 3; with the marker equal to the tree: "not due", exit 0; the shared checkout at that moment: due |
|
||||
| 4 Oct 2026 | the Mac card read 0.0 MH/s for a minute while its blocks were still accepted (after the stale mark shipped) | STALE_S 120 s left no margin: one missed 60 s upload (a 120 s gap at 19:45:59 UTC) plus a 30 s STATUS age plus the 10 s cache | STALE_S 180 s (one missed upload is not stale; PC 2's real case was a 1,860 s gap) | test: 150 s is fresh, 240 s stale |
|
||||
| 4 Oct 2026 | a machine stopped on purpose (Sam's Mac, 14:47 UTC) read "silent 4h" on the console, the same as a crash or a lost network | the app logs `quit: stopping the miners, then the node` and `stopped` and uploads once more before it exits (so it does report), but the console never read those lines | 4d208c9: `parseAppTail` (now in `relay/lib/parse.mjs`) sets `stopped {at, reason quit\|update}` when a quit or OTA-install line has no status line after it; the card says "stopped (quit) N ago" with a grey stripe, `tools/console.mjs` says STOPPED | unit test: quit, update, running, and a quit followed by a later status line; live: five running machines show no false stop; a live stopped case is pending the next real quit |
|
||||
| 4 Oct 2026 | `publish-manifest.sh --deploy` called the live manifest "verified" after one signature check: any validly signed manifest, including the previous version still at the edge, passed; a failed deploy was hidden by `\|\| true` | one-shot check, signature only, no byte compare | `verify_live_manifest`: up to `--tries` checks 5 s apart, byte-identical to the folder's file, then the signature, each failure named; `--verify-only` runs just the check; a failed deploy stops first (the ship tool's own verify step already compared bytes and version, so a cut through `tools/ship-app.mjs` was covered; a hand publish was not) | finished: `--verify-only --tries 2` against the live 0.3.3 (try 1); failed: a local server with an altered copy ("differs", both stamps named) and a closed port ("is not reachable") |
|
||||
|
||||
## Open
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ PUB="$HOME/.config/igneum/ota-signing-key.pub"
|
|||
TOKEN_FILE="$HOME/.config/igneum/dl-token"
|
||||
SIGNER="$ROOT/app/igneum-app/target/release/igneum-ota-sign"
|
||||
|
||||
VERSION="" MAC="" WIN="" NOTES="" ACTIVATION="" DEADLINE=""; OVERRIDE="" MIN_SUPPORTED="" CHANNEL="devnet" BASE="" DEST="" DEPLOY=0
|
||||
VERSION="" MAC="" WIN="" NOTES="" ACTIVATION="" DEADLINE=""; OVERRIDE="" MIN_SUPPORTED="" CHANNEL="devnet" BASE="" DEST="" DEPLOY=0 VERIFY_ONLY=0 TRIES=12
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--version) VERSION="$2"; shift 2 ;;
|
||||
|
|
@ -44,11 +44,14 @@ while [ $# -gt 0 ]; do
|
|||
--dest) DEST="$2"; shift 2 ;;
|
||||
--deploy) DEPLOY=1; shift ;;
|
||||
--no-deploy) DEPLOY=0; shift ;;
|
||||
--verify-only) VERIFY_ONLY=1; shift ;; # no write, no deploy: check the live manifest against the one in the folder
|
||||
--tries) TRIES="$2"; shift 2 ;;
|
||||
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
[ -n "$VERSION" ] || { echo "--version is required" >&2; exit 2; }
|
||||
case "$VERSION" in [0-9]*.[0-9]*.[0-9]*) ;; *) echo "--version must be major.minor.patch" >&2; exit 2 ;; esac
|
||||
[ -n "$VERSION" ] || [ "$VERIFY_ONLY" = 1 ] || { echo "--version is required" >&2; exit 2; }
|
||||
[ -n "$VERSION" ] || VERSION="(the folder's)"
|
||||
[ "$VERIFY_ONLY" = 1 ] || case "$VERSION" in [0-9]*.[0-9]*.[0-9]*) ;; *) echo "--version must be major.minor.patch" >&2; exit 2 ;; esac
|
||||
[ -f "$KEY" ] || { echo "no $KEY: run $SIGNER keygen $KEY $PUB once (the public key then goes into src/manifest.rs)" >&2; exit 1; }
|
||||
[ -f "$PUB" ] || { echo "no $PUB" >&2; exit 1; }
|
||||
[ -f "$TOKEN_FILE" ] || { echo "no $TOKEN_FILE" >&2; exit 1; }
|
||||
|
|
@ -98,6 +101,33 @@ entry() { # <file> -> "url sha256 size kind"
|
|||
MAC_ENTRY=""; WIN_ENTRY=""
|
||||
[ -n "$MAC" ] && MAC_ENTRY="$(entry "$MAC")"
|
||||
[ -n "$WIN" ] && WIN_ENTRY="$(entry "$WIN")"
|
||||
# The live file must be THIS file: byte-identical to the local one and verifying, with retries because the edge
|
||||
# serves the previous deployment for some seconds (4 October 2026: the check used to accept any validly signed
|
||||
# manifest, so a stale 0.3.3 at the edge would have passed as the 0.3.4 verification). Each failure names its reason.
|
||||
# packaging/ota/publish-manifest.sh --verify-only [--tries N] [--base-url ...] runs only this check
|
||||
verify_live_manifest() {
|
||||
local tmp t=0 verdict=""
|
||||
tmp="$(mktemp -d)"
|
||||
while [ "$t" -lt "$TRIES" ]; do
|
||||
t=$((t + 1)); verdict=""
|
||||
if ! curl -fsSL -H 'Cache-Control: no-cache' -o "$tmp/m.json" "$BASE/igneum-app-latest.json"; then verdict="is not reachable"
|
||||
elif ! curl -fsSL -H 'Cache-Control: no-cache' -o "$tmp/m.sig" "$BASE/igneum-app-latest.json.sig"; then verdict="has no reachable signature"
|
||||
elif ! cmp -s "$tmp/m.json" "$DEST/igneum-app-latest.json"; then verdict="differs from the local one (live says $(python3 -c 'import json,sys; m=json.load(open(sys.argv[1])); print(m.get("version","?"), m.get("published_at","?"))' "$tmp/m.json" 2>/dev/null || echo unreadable), local $VERSION)"
|
||||
elif ! "$SIGNER" verify "$PUB" "$tmp/m.json" "$tmp/m.sig" >/dev/null 2>&1; then verdict="does not verify against $PUB"
|
||||
fi
|
||||
[ -z "$verdict" ] && break
|
||||
[ "$t" -lt "$TRIES" ] && sleep 5
|
||||
done
|
||||
rm -rf "$tmp"
|
||||
if [ -n "$verdict" ]; then echo "the live manifest $verdict after $t tr$([ "$t" = 1 ] && echo y || echo ies) ($(( (t - 1) * 5 )) s); check the deploy output, then: $0 --verify-only" >&2; return 1; fi
|
||||
echo "live manifest verified at ${BASE//$TOKEN/<token>}/igneum-app-latest.json (try $t of $TRIES): version $VERSION, byte-identical, signature OK"
|
||||
}
|
||||
|
||||
if [ "$VERIFY_ONLY" = 1 ]; then
|
||||
[ -f "$DEST/igneum-app-latest.json" ] || { echo "no manifest in $DEST" >&2; exit 1; }
|
||||
[ "$VERSION" != "(the folder's)" ] || VERSION="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("version","?"))' "$DEST/igneum-app-latest.json")"
|
||||
verify_live_manifest; exit $?
|
||||
fi
|
||||
OLD="$DEST/igneum-app-latest.json"
|
||||
if [ -f "$OLD" ]; then
|
||||
OLD_VERSION="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("version",""))' "$OLD" 2>/dev/null || true)"
|
||||
|
|
@ -148,12 +178,9 @@ echo "key fingerprint: $("$SIGNER" fingerprint "$PUB" | tail -1)"
|
|||
if [ "$DEPLOY" = 1 ]; then
|
||||
[ -n "$DLSITE" ] || { echo "--deploy needs the real downloads folder (no --dest)" >&2; exit 1; }
|
||||
echo "deploying $DLSITE"
|
||||
(cd "$DLSITE" && npx --yes vercel@latest --global-config "$HOME/.config/igneum/vercel" deploy --prod --yes 2>&1 | grep -v "$TOKEN" || true)
|
||||
TMP="$(mktemp -d)"
|
||||
curl -fsSL -o "$TMP/m.json" "$BASE/igneum-app-latest.json" && curl -fsSL -o "$TMP/m.sig" "$BASE/igneum-app-latest.json.sig" \
|
||||
&& "$SIGNER" verify "$PUB" "$TMP/m.json" "$TMP/m.sig" && echo "live manifest verified at $BASE/igneum-app-latest.json" \
|
||||
|| { echo "the live manifest is not reachable or does not verify yet; check the deploy output" >&2; rm -rf "$TMP"; exit 1; }
|
||||
rm -rf "$TMP"
|
||||
(cd "$DLSITE" && npx --yes vercel@latest --global-config "$HOME/.config/igneum/vercel" deploy --prod --yes 2>&1 | sed "s#$TOKEN#<token>#g"; exit "${PIPESTATUS[0]}") \
|
||||
|| { echo "the deploy failed (the Vercel CLI's exit status above); nothing verified" >&2; exit 1; }
|
||||
verify_live_manifest || exit 1
|
||||
# the console's Builds tab (relay/): one build event and a fresh downloads listing; never fatal
|
||||
node "$ROOT/tools/console.mjs" post --kind build --title "OTA manifest $VERSION ($CHANNEL) live" --body "$NOTES" >/dev/null 2>&1 || true
|
||||
node "$ROOT/tools/console.mjs" sync-dl >/dev/null 2>&1 || true
|
||||
|
|
|
|||
Loading…
Reference in a new issue