Ruling 2: every tracked text mention of the login's pre-rename spelling becomes igneum-labs (29 files); the commit identity in the scripts becomes igneum-labs <337424239+igneum-labs@users.noreply.github.com> (the same noreply id); the three scripts that hand the stored gh keyring name to gh (register.sh, fetch-ci-artifacts.sh, ship-app.mjs) read it from ~/.config/igneum/gh-user (never in the repository), default igneum-labs; fresh-repo.sh reads the login to rewrite from the encoded list's row 10; the old spelling joins the founder-strings list, so no tracked file may spell it; CLAUDE.md's GitHub paragraph rewritten. The red watcher's self-test fixtures use a neutral author. Ruling 4: docs/ledger-public.md, one row per ledger item (id, the claim in one line, status, what was done in one line, the evidence link or the evidence in words), generated by tools/ledger/export-public.mjs from docs/fud-ledger.md: no round or status-update sections, no commit ids, no time of day, no lane, agent or branch names. 190 items. The gate runs its self-test (a fixture with a commit id, a time, a lane and a branch name comes out clean; --check fails on drift) and its --check on every merge; the founder check reads the output like every tracked file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
113 lines
8.7 KiB
Bash
Executable file
113 lines
8.7 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Pulls the Windows installer and payload from a green run of .github/workflows/windows.yml on master and copies
|
|
# them into the downloads folder (dl/<token>/), where the other packages live. Run on the Mac:
|
|
#
|
|
# packaging/windows/fetch-ci-artifacts.sh [--deploy] [--sign-manifest] [run-id]
|
|
#
|
|
# Without --deploy it prints the deploy command for the main session to run; with --deploy it deploys the folder with
|
|
# the Vercel CLI itself. A run id (gh run list) picks a specific run instead of the latest green one.
|
|
#
|
|
# The over-the-air update manifest (packaging/ota/publish-manifest.sh, Windows entry) is NOT touched unless
|
|
# --sign-manifest is given (review round 4, R4.5.2, ledger G13: signing used to be automatic from "the latest green
|
|
# run"). --sign-manifest needs an explicit run id, and before it signs anything it downloads that run's
|
|
# igneum-windows-inputs artifact (the payload-inputs.json the runner verified, its signature and the runner's record)
|
|
# and re-verifies on this Mac: the Ed25519 signature against ~/.config/igneum/ota-signing-key.pub, the pinned node
|
|
# commit against packaging/windows/node-source.pin AT THE RUN'S COMMIT, the run's branch (master) and event (push or
|
|
# workflow_dispatch), and that the runner's record names this run, this commit and this key. Any failure stops
|
|
# before the signature. OTA_NOTES= sets the changelog line (default: the version and the run id).
|
|
# Reads ~/.config/igneum/dl-token, ~/.config/igneum/dlsite-dir (IGNEUM_DLSITE overrides) and the gh login, which must
|
|
# be the stored login (~/.config/igneum/gh-user, default igneum-labs; gh auth switch --user <it>).
|
|
set -euo pipefail
|
|
REPO="igneum-network/igneum"
|
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
ROOT="$(cd "$HERE/../.." && pwd)"
|
|
DEPLOY=0
|
|
SIGN=0
|
|
RUN_ID=""
|
|
for a in "$@"; do
|
|
case "$a" in --deploy) DEPLOY=1 ;; --sign-manifest) SIGN=1 ;; --*) echo "unknown flag $a" >&2; exit 2 ;; *) RUN_ID="$a" ;; esac
|
|
done
|
|
if [ "${OTA_SKIP:-}" = 0 ]; then SIGN=1; fi # the old spelling; OTA_SKIP=1 (the default now) leaves the manifest alone
|
|
if [ "$SIGN" = 1 ] && [ -z "$RUN_ID" ]; then
|
|
echo "--sign-manifest needs the run id it signs (gh run list --repo $REPO --workflow windows.yml); the latest green run is never signed by default" >&2
|
|
exit 2
|
|
fi
|
|
TOKEN_FILE="$HOME/.config/igneum/dl-token"
|
|
DLSITE="${IGNEUM_DLSITE:-}"
|
|
[ -n "$DLSITE" ] || { [ -f "$HOME/.config/igneum/dlsite-dir" ] && DLSITE="$(tr -d '[:space:]' < "$HOME/.config/igneum/dlsite-dir")"; } || true
|
|
[ -f "$TOKEN_FILE" ] || { echo "no $TOKEN_FILE" >&2; exit 1; }
|
|
TOKEN="$(tr -d '[:space:]' < "$TOKEN_FILE")"
|
|
DEST="$DLSITE/dl/$TOKEN"
|
|
[ -n "$DLSITE" ] && [ -d "$DEST" ] || { echo "no downloads folder: set IGNEUM_DLSITE or ~/.config/igneum/dlsite-dir (must hold dl/<token>/)" >&2; exit 1; }
|
|
gh auth status 2>&1 | grep -q 'Active account: true' || { echo "gh is not logged in" >&2; exit 1; }
|
|
stored="$(cat "$HOME/.config/igneum/gh-user" 2>/dev/null | tr -d '[:space:]')"; stored="${stored:-igneum-labs}" # the keyring entry's name
|
|
gh auth status 2>&1 | grep -B1 'Active account: true' | grep -q -F "$stored" || { echo "gh active account is not the stored login $stored: run gh auth switch --user $stored" >&2; exit 1; }
|
|
|
|
if [ -z "$RUN_ID" ]; then
|
|
RUN_ID="$(gh run list --repo "$REPO" --workflow windows.yml --branch master --status success --limit 1 --json databaseId --jq '.[0].databaseId')"
|
|
[ -n "$RUN_ID" ] && [ "$RUN_ID" != "null" ] || { echo "no green windows.yml run on master yet" >&2; exit 1; }
|
|
fi
|
|
gh run view "$RUN_ID" --repo "$REPO" --json headSha,displayTitle,updatedAt,url,conclusion --jq '"run \(.url)\n\(.displayTitle)\n\(.headSha[0:12]) \(.updatedAt) \(.conclusion)"'
|
|
RUN_JSON="$(gh run view "$RUN_ID" --repo "$REPO" --json headSha,headBranch,event,conclusion,status)"
|
|
read -r HEAD_SHA HEAD_BRANCH RUN_EVENT RUN_CONCLUSION < <(printf '%s' "$RUN_JSON" | python3 -c 'import json,sys; r=json.load(sys.stdin); print(r["headSha"], r["headBranch"], r["event"], r["conclusion"])')
|
|
[ "$RUN_CONCLUSION" = success ] || { echo "run $RUN_ID concluded '$RUN_CONCLUSION', not success" >&2; exit 1; }
|
|
|
|
TMP="$(mktemp -d)"
|
|
gh run download "$RUN_ID" --repo "$REPO" --name igneum-windows-installer --name igneum-windows-payload --dir "$TMP"
|
|
SETUP="$(find "$TMP" -name 'Igneum-Miner-Setup-*.exe' | head -1)"
|
|
PAYLOAD="$(find "$TMP" -name 'igneum-windows-app.zip' | head -1)"
|
|
[ -n "$SETUP" ] && [ -n "$PAYLOAD" ] || { echo "the run has no installer or payload artifact" >&2; ls -R "$TMP"; exit 1; }
|
|
cp "$SETUP" "$DEST/"
|
|
cp "$PAYLOAD" "$DEST/igneum-windows-app.zip"
|
|
cat > "$DEST/igneum-windows-ci.json" <<JSON
|
|
{ "run": "https://github.com/$REPO/actions/runs/$RUN_ID", "installer": "$(basename "$SETUP")", "payload": "igneum-windows-app.zip", "fetched_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)" }
|
|
JSON
|
|
rm -rf "$TMP"
|
|
echo "copied into $DEST:"
|
|
ls -la "$DEST/$(basename "$SETUP")" "$DEST/igneum-windows-app.zip"
|
|
# the console's Builds tab (relay/): one build event per fetched CI run; never fatal. CONSOLE_SKIP=1 leaves it to the
|
|
# caller (tools/ship-app.mjs posts one item for the whole cut).
|
|
[ "${CONSOLE_SKIP:-0}" = 1 ] || node "$(dirname "$0")/../../tools/console.mjs" post --kind build --title "Windows CI $(basename "$SETUP") fetched (run $RUN_ID)" --body "https://github.com/$REPO/actions/runs/$RUN_ID" >/dev/null 2>&1 || true
|
|
# the over-the-air manifest (packaging/ota): only with --sign-manifest, only for the named run, and only after the
|
|
# run's verified inputs manifest re-verifies here (G13). The Mac entry of the same version is carried over.
|
|
if [ "$SIGN" = 1 ]; then
|
|
PUB="$HOME/.config/igneum/ota-signing-key.pub"
|
|
SIGNER="$ROOT/app/igneum-app/target/release/igneum-ota-sign"
|
|
[ -f "$PUB" ] || { echo "no $PUB" >&2; exit 1; }
|
|
[ -x "$SIGNER" ] || (cd "$ROOT/app/igneum-app" && nice -n 19 cargo build --release -j 4 --bin igneum-ota-sign --quiet)
|
|
[ "$HEAD_BRANCH" = master ] || { echo "refusing to sign: run $RUN_ID is on branch '$HEAD_BRANCH', not master" >&2; exit 1; }
|
|
case "$RUN_EVENT" in push|workflow_dispatch) ;; *) echo "refusing to sign: run $RUN_ID was triggered by '$RUN_EVENT'" >&2; exit 1 ;; esac
|
|
INP="$(mktemp -d)"
|
|
gh run download "$RUN_ID" --repo "$REPO" --name igneum-windows-inputs --dir "$INP" || { echo "refusing to sign: run $RUN_ID has no igneum-windows-inputs artifact (the run verified no inputs manifest)" >&2; exit 1; }
|
|
IJSON="$(find "$INP" -name payload-inputs.json | head -1)"; ISIG="$(find "$INP" -name payload-inputs.json.sig | head -1)"; IREC="$(find "$INP" -name inputs-verified.json | head -1)"
|
|
[ -n "$IJSON" ] && [ -n "$ISIG" ] && [ -n "$IREC" ] || { echo "refusing to sign: the inputs artifact is incomplete" >&2; ls -R "$INP" >&2; exit 1; }
|
|
# the pin as it stood in the commit the runner built, from this clone (fetched if the commit is not here yet)
|
|
git -C "$ROOT" cat-file -e "$HEAD_SHA^{commit}" 2>/dev/null || git -C "$ROOT" fetch --quiet origin "$HEAD_SHA" || true
|
|
PIN="$(git -C "$ROOT" show "$HEAD_SHA:packaging/windows/node-source.pin" 2>/dev/null | tr -d '[:space:]')"
|
|
[ ${#PIN} = 40 ] || { echo "refusing to sign: commit ${HEAD_SHA:0:12} carries no packaging/windows/node-source.pin" >&2; exit 1; }
|
|
"$SIGNER" verify-inputs "$PUB" "$IJSON" "$ISIG" --node-commit "$PIN" || { echo "refusing to sign: the run's inputs manifest does not verify against $PUB and the pin at ${HEAD_SHA:0:12}" >&2; exit 1; }
|
|
FP="$("$SIGNER" fingerprint "$PUB" | sed -n 2p)"
|
|
python3 - "$IREC" "$RUN_ID" "$HEAD_SHA" "$FP" <<'PYREC'
|
|
import json, sys
|
|
rec = json.load(open(sys.argv[1]))
|
|
want = {"run_id": sys.argv[2], "head_sha": sys.argv[3], "key_fingerprint": sys.argv[4]}
|
|
bad = [k for k, v in want.items() if str(rec.get(k, "")) != v]
|
|
if bad:
|
|
print("refusing to sign: the runner's record disagrees on " + ", ".join(f"{k} (record {rec.get(k)!r}, expected {want[k]!r})" for k in bad), file=sys.stderr)
|
|
sys.exit(1)
|
|
print(f"inputs verified by the runner and again here: node commit {rec.get('node_commit')}, zip {rec.get('zip_sha256')}, key {rec.get('key_fingerprint')}")
|
|
PYREC
|
|
rm -rf "$INP"
|
|
SETUP_VERSION="$(basename "$SETUP" | sed -n 's/^Igneum-Miner-Setup-\(.*\)\.exe$/\1/p')"
|
|
echo "signing the update manifest for Windows $SETUP_VERSION over run $RUN_ID (${HEAD_SHA:0:12})"
|
|
"$HERE/../ota/publish-manifest.sh" --version "$SETUP_VERSION" --win "$DEST/$(basename "$SETUP")" --notes "${OTA_NOTES:-Windows build $SETUP_VERSION from CI run $RUN_ID}" --no-deploy
|
|
else
|
|
echo "update manifest untouched (pass --sign-manifest <run-id> to sign it after the inputs check)"
|
|
fi
|
|
if [ "$DEPLOY" = 1 ]; then
|
|
(cd "$DLSITE" && npx vercel@latest --global-config "$HOME/.config/igneum/vercel" deploy --prod --yes 2>&1 | grep -v "$TOKEN" || true)
|
|
echo "live: https://dl.igneum.network/dl/<token>/$(basename "$SETUP")"
|
|
node "$(dirname "$0")/../../tools/console.mjs" sync-dl >/dev/null 2>&1 || true
|
|
else
|
|
echo "deploy: cd $DLSITE && npx vercel@latest --global-config ~/.config/igneum/vercel deploy --prod --yes"
|
|
fi
|