igneum/packaging/mac/packaged-config.sh
2026-10-06 11:21:33 +00:00

132 lines
10 KiB
Bash

#!/bin/bash
# The configuration the packagers write next to the engine (igneum-app.json): the update manifest URL, the log intake,
# the live page. Sourced by packaging/mac/build-dmg.sh and packaging/windows/make-payload.sh (on the Mac and on the
# GitHub runner alike). Run on its own (`packaging/mac/packaged-config.sh --test`) it checks itself.
#
# No secret lives in this file (rotation phase 2, 5 October 2026: the intake key used to be a literal here and is in
# eight commits of the history; docs/plans/rotation-phase-2.md and docs/plans/history-rewrite.md). Both values come
# from files named by two environment variables, each defaulting to the NEXT value when one is staged:
#
# IGNEUM_INTAKE_KEY_FILE the log intake key (authorises log uploads only; it ships inside every package).
# Default: ~/.config/igneum/log-intake-key.next when that file exists, else
# ~/.config/igneum/log-intake-key.
# IGNEUM_DL_TOKEN_FILE the downloads path token: the manifest is https://dl.igneum.network/dl/<token>/igneum-app-latest.json.
# Default: ~/.config/igneum/dl-token.next when that file exists, else ~/.config/igneum/dl-token.
#
# So the 0.3.6 build carries the rotated key and checks the manifest in the NEW folder with no flag at all, while a
# build that must target the old folder says so: IGNEUM_DL_TOKEN_FILE=~/.config/igneum/dl-token. When the rotation is
# over, `mv log-intake-key.next log-intake-key` and `mv dl-token.next dl-token` make the defaults the plain files
# again. A missing or empty token file disables the update check (the note says so); a missing or empty key file
# disables log uploads. Values are never printed, only the file names and the values' lengths.
LOG_URL="${IGNEUM_INTAKE_URL:-https://igneum-six.vercel.app/api/log}"
LIVE_PAGE="https://igneum.network/live"
DOWNLOAD_PAGE="https://igneum.network/#mine"
DL_HOST="https://dl.igneum.network"
# Consensus parameters pinned into the package for the bundled node: igneum-app.json "node_override_params"; the engine
# writes them to <app data>/override-params.json and starts igneumd with --override-params-file. Empty = no override
# file, the node runs the network's defaults. Difficulty v2 (4 Oct 2026): the version that bundles igneumd v2 must
# carry the devnet's activation height here, the same N as every other devnet node, before it is cut (Mac and CI alike:
# make-payload.sh sources this file). Rule and order: docs/plans/difficulty-v2-rollout-devnet.md.
# Example: NODE_OVERRIDE_PARAMS='{"difficulty_v2_activation_daa": 123456}'
NODE_OVERRIDE_PARAMS='{"difficulty_v2_activation_daa":33000,"proving_v0_activation_daa":84100,"fees_v1_activation_daa":210000,"finality_v3_activation_daa":135200,"program_class_v3_activation_daa":154800,"proving_v1_activation_daa":154800,"proving_v1_segment_blocks":8,"proving_v1_unproven_daa":600,"proving_v1_aggregator_share_bps":1000,"proving_v1_fresh_rule_daa":198000}'
# igneum_secret_file <env var name> <base name> -> the file to read: the variable when set, else <base>.next when it
# exists, else <base>; IGNEUM_CONFIG_DIR (tests) replaces ~/.config/igneum
igneum_secret_file() {
local var="$1" base="$2" dir="${IGNEUM_CONFIG_DIR:-$HOME/.config/igneum}" set_value=""
set_value="${!var:-}"
if [ -n "$set_value" ]; then printf '%s' "$set_value"
elif [ -f "$dir/$base.next" ]; then printf '%s' "$dir/$base.next"
else printf '%s' "$dir/$base"
fi
}
# igneum_read_trimmed <file> -> the file's content without whitespace, or nothing when the file is missing or blank
igneum_read_trimmed() {
[ -f "$1" ] && tr -d '[:space:]' < "$1" || true
}
# igneum_manifest_url <token> -> the manifest URL for that downloads folder; nothing for an empty token
igneum_manifest_url() {
[ -n "$1" ] && printf '%s/dl/%s/igneum-app-latest.json' "$DL_HOST" "$1" || true
}
# igneum_fingerprint <value> -> the first 8 hex of sha256 over the value, for logs and the app's header (never the value)
igneum_fingerprint() {
printf '%s' "$1" | { shasum -a 256 2>/dev/null || sha256sum; } | cut -c1-8
}
# writes the JSON to $1; prints which files were used (names), the lengths and the fingerprints, never the values
write_packaged_config() {
local out="$1" token="" manifest="" key="" key_file="" token_file=""
key_file="$(igneum_secret_file IGNEUM_INTAKE_KEY_FILE log-intake-key)"
token_file="$(igneum_secret_file IGNEUM_DL_TOKEN_FILE dl-token)"
key="$(igneum_read_trimmed "$key_file")"
token="$(igneum_read_trimmed "$token_file")"
manifest="$(igneum_manifest_url "$token")"
if [ -n "$key" ]; then echo "intake key: $key_file (${#key} chars, fingerprint $(igneum_fingerprint "$key"))"
else echo "note: no key in $key_file; log uploads are disabled in this build"; fi
if [ -n "$manifest" ]; then echo "manifest: $token_file (${#token} chars, fingerprint $(igneum_fingerprint "$token")) -> ${manifest//$token/<token>}"
else echo "note: no token in $token_file; the update check is disabled in this build"; fi
local override_line=""
[ -n "$NODE_OVERRIDE_PARAMS" ] && override_line=" \"node_override_params\": $NODE_OVERRIDE_PARAMS,"
cat > "$out" <<JSON
{
$override_line
"update_manifest": "$manifest",
"log_intake_url": "$LOG_URL",
"log_intake_key": "$key",
"live_page": "$LIVE_PAGE",
"download_page": "$DOWNLOAD_PAGE"
}
JSON
}
# ---- self-test: packaging/mac/packaged-config.sh --test (temporary files only; nothing under ~/.config is read) -----
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
set -euo pipefail
[ "${1:-}" = "--test" ] || { echo "usage: $0 --test (otherwise source this file)" >&2; exit 2; }
T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT
PY="$(command -v python3 || command -v python)" # the GitHub Windows runner's Git Bash may only have python
fails=0
check() { if [ "$2" = "$3" ]; then echo " ok $1"; else echo " FAIL $1: got '$2', want '$3'"; fails=$((fails + 1)); fi; }
export IGNEUM_CONFIG_DIR="$T/cfg"; mkdir -p "$T/cfg"
unset IGNEUM_INTAKE_KEY_FILE IGNEUM_DL_TOKEN_FILE
# 1. nothing staged: the plain files
check "default key file is the plain one" "$(igneum_secret_file IGNEUM_INTAKE_KEY_FILE log-intake-key)" "$T/cfg/log-intake-key"
check "default token file is the plain one" "$(igneum_secret_file IGNEUM_DL_TOKEN_FILE dl-token)" "$T/cfg/dl-token"
# 2. a .next file wins
printf 'nextkeyvalue0123456789abcdef\n' > "$T/cfg/log-intake-key.next"
printf 'plainkeyvalue0123456789abcdef\n' > "$T/cfg/log-intake-key"
printf 'tok2new\n' > "$T/cfg/dl-token.next"
printf 'tok1old\n' > "$T/cfg/dl-token"
check ".next key file wins when present" "$(igneum_secret_file IGNEUM_INTAKE_KEY_FILE log-intake-key)" "$T/cfg/log-intake-key.next"
check ".next token file wins when present" "$(igneum_secret_file IGNEUM_DL_TOKEN_FILE dl-token)" "$T/cfg/dl-token.next"
# 3. the variable beats both
check "the variable names the file" "$(IGNEUM_DL_TOKEN_FILE="$T/cfg/dl-token" igneum_secret_file IGNEUM_DL_TOKEN_FILE dl-token)" "$T/cfg/dl-token"
# 4. reading trims; a missing file reads as nothing
check "trimmed read" "$(igneum_read_trimmed "$T/cfg/dl-token.next")" "tok2new"
check "missing file reads empty" "$(igneum_read_trimmed "$T/cfg/none")" ""
check "manifest url" "$(igneum_manifest_url tok2new)" "$DL_HOST/dl/tok2new/igneum-app-latest.json"
check "empty token gives no manifest" "$(igneum_manifest_url '')" ""
check "fingerprint is 8 hex" "$(igneum_fingerprint abc | grep -cE '^[0-9a-f]{8}$')" "1"
check "fingerprint of abc" "$(igneum_fingerprint abc)" "ba7816bf"
# 5. the written JSON: defaults pick the .next pair; the values land; nothing printed carries them
out="$(NODE_OVERRIDE_PARAMS='' write_packaged_config "$T/a.json")"
check "output names the .next key file" "$(printf '%s' "$out" | grep -c 'log-intake-key.next')" "1"
check "output never carries the key" "$(printf '%s' "$out" | grep -c 'nextkeyvalue')" "0"
check "output never carries the token" "$(printf '%s' "$out" | grep -c 'tok2new')" "0"
check "json carries the .next key" "$("$PY" -c 'import json,sys; print(json.load(open(sys.argv[1]))["log_intake_key"])' "$T/a.json")" "nextkeyvalue0123456789abcdef"
check "json manifest points at the .next folder" "$("$PY" -c 'import json,sys; print(json.load(open(sys.argv[1]))["update_manifest"])' "$T/a.json")" "$DL_HOST/dl/tok2new/igneum-app-latest.json"
check "json has no override line" "$("$PY" -c 'import json,sys; print("node_override_params" in json.load(open(sys.argv[1])))' "$T/a.json")" "False"
# 6. the variables aim a build at the old folder and the old key
out="$(IGNEUM_INTAKE_KEY_FILE="$T/cfg/log-intake-key" IGNEUM_DL_TOKEN_FILE="$T/cfg/dl-token" write_packaged_config "$T/b.json")"
check "old-folder build: manifest" "$("$PY" -c 'import json,sys; print(json.load(open(sys.argv[1]))["update_manifest"])' "$T/b.json")" "$DL_HOST/dl/tok1old/igneum-app-latest.json"
check "old-folder build: key" "$("$PY" -c 'import json,sys; print(json.load(open(sys.argv[1]))["log_intake_key"])' "$T/b.json")" "plainkeyvalue0123456789abcdef"
# 7. missing files: notes, empty fields, valid JSON
out="$(IGNEUM_INTAKE_KEY_FILE="$T/cfg/nokey" IGNEUM_DL_TOKEN_FILE="$T/cfg/notoken" write_packaged_config "$T/c.json")"
check "missing key noted" "$(printf '%s' "$out" | grep -c 'log uploads are disabled')" "1"
check "missing token noted" "$(printf '%s' "$out" | grep -c 'update check is disabled')" "1"
check "missing files give empty fields" "$("$PY" -c 'import json,sys; j=json.load(open(sys.argv[1])); print(j["update_manifest"]+"|"+j["log_intake_key"])' "$T/c.json")" "|"
# 8. the override line
NODE_OVERRIDE_PARAMS='{"difficulty_v2_activation_daa": 123456}' write_packaged_config "$T/d.json" >/dev/null
check "override params land" "$("$PY" -c 'import json,sys; print(json.load(open(sys.argv[1]))["node_override_params"]["difficulty_v2_activation_daa"])' "$T/d.json")" "123456"
if [ "$fails" = 0 ]; then echo "packaged-config: all checks passed"; else echo "packaged-config: $fails check(s) failed"; exit 1; fi
fi