Relay (X23, X27): the intake key is its own tier (upload and file drops only, RELAY_INTAKE_COMPAT=0 closes it);
a run task needs an Ed25519 signature by the Mac run key over {to, nonce, body sha256, flags} (RELAY_RUN_PUB,
401 without) and an HMAC tag with the target's machine secret that the agent verifies before anything runs;
results and registration are bound to the machine the secret proves (403 on a forged from).
X24: every client and Mac tool sends x-relay-token as a header to /api/relay?fn=; the path token stays for the
phone page only. X25: the agent arms the logon task only for a restart a task asked for and disarms on start
and exit. X26: 30-day retention with blob deletion, feed capped at 100, the dl base as RELAY_DL_BASE held by the
agent, never in a body. X28: GET inbox never acks (POST inbox does), RELAY-REBOOT on its own line and only with a
reboot flag, 120/min and 10 failed auths/min per IP, no username or folder on register, WSL sudo scoped to
apt-get and dpkg with SETENV, no password on a command line. X29: the intake key reaches curl through -K in
upload.sh and both upload-log.bat; tools/ci/curl-header-check.sh fails the class. G14: TZ=UTC in ship-app.mjs
and publish-jobs.sh; tools/ci/commit-tz-check.sh fails the class; history-rewrite.md names the .old-2026-10-05
files as the values in the history. The handler moved to relay/lib/handler.mjs with injected sql and blobs
(relay/lib/blob.mjs holds @vercel/blob) so relay/test/handler.test.mjs drives it without a database:
47 tests across 6 suites, all green.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
32 lines
2 KiB
Bash
Executable file
32 lines
2 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Upload a log (its last 256 KB) to the Igneum log intake, the same endpoint and key as
|
|
# proto-cuda/windows-miner/upload-log.bat (the key only authorises log uploads and ships inside the packages).
|
|
# Read back on the Mac with `node tools/logs.mjs`.
|
|
# ./upload.sh <logfile> <label> [run_id]
|
|
set -euo pipefail
|
|
IGNEUM_LOG_URL="${IGNEUM_LOG_URL:-https://igneum-six.vercel.app/api/log}"
|
|
# the key: IGNEUM_LOG_KEY, else the file named by IGNEUM_INTAKE_KEY_FILE, else ~/.config/igneum/log-intake-key.next when
|
|
# staged, else ~/.config/igneum/log-intake-key (rotation phase 2, 5 October 2026: no key literal in the repository)
|
|
if [ -z "${IGNEUM_LOG_KEY:-}" ]; then
|
|
kf="${IGNEUM_INTAKE_KEY_FILE:-}"
|
|
[ -n "$kf" ] || { [ -f "$HOME/.config/igneum/log-intake-key.next" ] && kf="$HOME/.config/igneum/log-intake-key.next" || kf="$HOME/.config/igneum/log-intake-key"; }
|
|
[ -f "$kf" ] && IGNEUM_LOG_KEY="$(tr -d '[:space:]' < "$kf")" || IGNEUM_LOG_KEY=""
|
|
fi
|
|
[ -n "$IGNEUM_LOG_KEY" ] || { echo "upload: no intake key (set IGNEUM_LOG_KEY or IGNEUM_INTAKE_KEY_FILE)"; exit 3; }
|
|
[ $# -ge 2 ] || { echo "usage: upload.sh <logfile> <label> [run_id]"; exit 1; }
|
|
file="$1"; label="$2"; run_id="${3:-${IGNEUM_RUN_ID:-$label-$(date -u +%Y%m%d-%H%M)}}"
|
|
[ -f "$file" ] || { echo "upload: file not found: $file"; exit 2; }
|
|
body=$(mktemp)
|
|
python3 - "$file" "$label" "$run_id" "$body" <<'EOF'
|
|
import json, socket, sys
|
|
path, label, run_id, out = sys.argv[1:5]
|
|
data = open(path, 'rb').read()[-262144:]
|
|
json.dump({"label": label, "machine": socket.gethostname(), "run_id": run_id, "lines": data.decode('utf-8', 'replace')}, open(out, 'w'))
|
|
print(f"upload: run_id {run_id}, {len(data)} bytes")
|
|
EOF
|
|
# the key reaches curl through a config file (-K), never on its command line, where every local user can read it (X29)
|
|
cfg=$(mktemp); chmod 600 "$cfg"
|
|
printf 'header = "x-igneum-key: %s"\n' "$IGNEUM_LOG_KEY" > "$cfg"
|
|
curl -sS --max-time 60 -K "$cfg" -X POST "$IGNEUM_LOG_URL" -H "Content-Type: application/json" --data-binary "@$body"
|
|
echo
|
|
rm -f "$body" "$cfg"
|