diff --git a/packaging/ota/publish-manifest.sh b/packaging/ota/publish-manifest.sh index e0c8e54ff..250257819 100755 --- a/packaging/ota/publish-manifest.sh +++ b/packaging/ota/publish-manifest.sh @@ -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/}/igneum-app-latest.json (try $t of $TRIES): version $VERSION, byte-identical, signature OK" + echo "live manifest verified at ${BASE//$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