publish-manifest.sh: --product app|wallet (the wallet's own manifest, igneum-wallet-latest.json, the same shape and key; the arm the 0.1.5 release plan named and no script carried; --public then publishes the wallet alone, the live verify reads the wallet's file). Used for Igneum Wallet 0.1.6 on 8 October 2026.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-08 11:28:51 +00:00
parent 2de98ee98b
commit bb98bf3f7f

View file

@ -14,6 +14,10 @@
# [--public] also publish into dl/public/ (no token: the site's download
# links, packaging/ota/publish-public.sh --app, plus --wallet
# when the wallet manifest is in the folder); the same deploy
# [--product app|wallet] which manifest: igneum-app-latest.json (default) or
# igneum-wallet-latest.json (the Igneum Wallet, 0.1.6: the same
# shape, its own file, signed with the same key; --public then
# publishes the wallet alone, --deploy verifies the wallet's file)
# [--tuning tuning.json | --no-tuning] the fleet's per-card kernel tuning (tools/tuning.mjs writes it;
# docs/design/miner-tuning.md); carried over from the current
# manifest when not given, as is consensus.override
@ -37,6 +41,7 @@ KEY="$HOME/.config/igneum/ota-signing-key"
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"
PRODUCT="app"
VERSION="" MAC="" WIN="" NOTES="" ACTIVATION="" DEADLINE="" MIN_SUPPORTED="" CHANNEL="devnet" BASE="" DEST="" DEPLOY=0 VERIFY_ONLY=0 TRIES=12
OVERRIDE="" TUNING_FILE="" NO_TUNING=0 PUBLIC=0
@ -51,6 +56,7 @@ while [ $# -gt 0 ]; do
--override) OVERRIDE="$2"; shift 2 ;; # consensus.override: the exact JSON object every app writes to its override.json (all height switches, not just the new one)
--min-supported) MIN_SUPPORTED="$2"; shift 2 ;;
--channel) CHANNEL="$2"; shift 2 ;;
--product) PRODUCT="$2"; shift 2 ;;
--tuning) TUNING_FILE="$2"; shift 2 ;;
--no-tuning) NO_TUNING=1; shift ;;
--base-url) BASE="$2"; shift 2 ;;
@ -63,6 +69,8 @@ while [ $# -gt 0 ]; do
*) echo "unknown argument: $1" >&2; exit 2 ;;
esac
done
case "$PRODUCT" in app|wallet) ;; *) echo "--product must be app or wallet" >&2; exit 2 ;; esac
MF="igneum-$PRODUCT-latest.json"
[ -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
@ -124,9 +132,9 @@ verify_live_manifest() {
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)"
if ! curl -fsSL -H 'Cache-Control: no-cache' -o "$tmp/m.json" "$BASE/$MF"; then verdict="is not reachable"
elif ! curl -fsSL -H 'Cache-Control: no-cache' -o "$tmp/m.sig" "$BASE/$MF.sig"; then verdict="has no reachable signature"
elif ! cmp -s "$tmp/m.json" "$DEST/$MF"; 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
@ -134,15 +142,15 @@ verify_live_manifest() {
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"
echo "live manifest verified at ${BASE//$TOKEN/<token>}/$MF (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")"
[ -f "$DEST/$MF" ] || { 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/$MF")"
verify_live_manifest; exit $?
fi
OLD="$DEST/igneum-app-latest.json"
OLD="$DEST/$MF"
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)"
if [ "$OLD_VERSION" = "$VERSION" ]; then
@ -172,7 +180,7 @@ elif [ "$NO_TUNING" = 0 ] && [ -f "$OLD" ]; then
fi
# canonical JSON: sorted keys, no whitespace; the signature is over these exact bytes
NEW="$DEST/igneum-app-latest.json.new"
NEW="$DEST/$MF.new"
python3 - "$NEW" "$VERSION" "$CHANNEL" "$NOTES" "$MIN_SUPPORTED" "$ACTIVATION" "$DEADLINE" "$MAC_ENTRY" "$WIN_ENTRY" "${OVERRIDE:-}" "${TUNING:-}" <<'PY'
import json, sys, datetime
out, version, channel, notes, min_supported, activation, deadline, mac, win, override, tuning = sys.argv[1:12]
@ -197,17 +205,17 @@ open(out, "w").write(json.dumps(m, sort_keys=True, separators=(",", ":"), ensure
PY
"$SIGNER" sign "$KEY" "$NEW" > "$NEW.sig"
"$SIGNER" verify "$PUB" "$NEW" "$NEW.sig"
mv "$NEW" "$DEST/igneum-app-latest.json"
mv "$NEW.sig" "$DEST/igneum-app-latest.json.sig"
echo "manifest: $DEST/igneum-app-latest.json"
cat "$DEST/igneum-app-latest.json"; echo
echo "signature: $(cat "$DEST/igneum-app-latest.json.sig")"
mv "$NEW" "$DEST/$MF"
mv "$NEW.sig" "$DEST/$MF.sig"
echo "manifest: $DEST/$MF"
cat "$DEST/$MF"; echo
echo "signature: $(cat "$DEST/$MF.sig")"
echo "key fingerprint: $("$SIGNER" fingerprint "$PUB" | tail -1)"
# --public: the same version into dl/public/ with public URLs (its own signed manifest), the aliases rewritten
if [ "$PUBLIC" = 1 ]; then
[ -n "$DLSITE" ] || { echo "--public needs the real downloads folder (no --dest)" >&2; exit 1; }
pub_args=(--app); [ -f "$DEST/igneum-wallet-latest.json" ] && pub_args+=(--wallet)
if [ "$PRODUCT" = wallet ]; then pub_args=(--wallet); else pub_args=(--app); [ -f "$DEST/igneum-wallet-latest.json" ] && pub_args+=(--wallet); fi
"$HERE/publish-public.sh" "${pub_args[@]}" || { echo "publish-public.sh failed; dl/public/ not updated" >&2; exit 1; }
fi
@ -219,12 +227,12 @@ if [ "$DEPLOY" = 1 ]; then
verify_live_manifest || exit 1
if [ "$PUBLIC" = 1 ]; then "$HERE/publish-public.sh" --verify --no-prune || exit 1; fi
# 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" post --kind build --title "OTA manifest $PRODUCT $VERSION ($CHANNEL) live" --body "$NOTES" >/dev/null 2>&1 || true
node "$ROOT/tools/console.mjs" sync-dl >/dev/null 2>&1 || true
else
if [ -n "$DLSITE" ]; then
echo "not deployed: cd $DLSITE && npx --yes vercel@latest --global-config ~/.config/igneum/vercel deploy --prod --yes"
echo "then the apps see it at $BASE/igneum-app-latest.json (checked hourly, and from Settings > Check now)"
echo "then the apps see it at $BASE/$MF (checked hourly, and from Settings > Check now)"
else
echo "written to $DEST for $BASE (test manifest; not the downloads folder)"
fi