#!/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//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 /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}' # the live SIXTEEN-field object (publish 2 of 0.3.15/0.3.16, 6 October 2026 22:43Z, digest eada4bda): a fresh install starts # its node on the network's current object and peers at once; the manifest's consensus.override replaces it on the first read NODE_OVERRIDE_PARAMS='{"difficulty_v2_activation_daa":33000,"exec_restart_hash":"bb45cf0dd2d7cc97ebfa5a2701527c09a8ede5d32de74efead9caa293b15688a","exec_restart_number":27276,"exec_restart_state_root":"0xed27bb2d6128daf50493ed5539a73bb93f9d7c63ad7f70a7cbb46ba81c9e164e","exec_restart_trust_daa":200000,"fees_v1_activation_daa":210000,"finality_v3_activation_daa":135200,"program_class_v3_activation_daa":154800,"program_class_v4_activation_daa":831600,"program_class_v4_signal_window_daa":86400,"proving_v0_activation_daa":84100,"proving_v1_activation_daa":154800,"proving_v1_aggregator_share_bps":1000,"proving_v1_fresh_rule_daa":198000,"proving_v1_segment_blocks":8,"proving_v1_unproven_daa":600}' # igneum_secret_file -> the file to read: the variable when set, else .next when it # exists, else ; 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 -> 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 -> 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 -> 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/}" 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" <&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