vps-health-api/agent/run-push.sh
le king fu e181a9691c fix(review): close the three gaps found in stack review
Follow-up to the /pr-review pass on PRs #18-#22. Three findings, none of
which changed behaviour, all of which weakened a guarantee the stack was
supposed to provide.

1. auth.test.js carried "any new route must be added here" but /hosts was
   never added when #13 introduced it, so no test asserted GET /hosts -> 401
   on a missing header. The drift class the file exists to catch slipped on
   its first outing. ROUTES now covers /hosts, and a dedicated block pins
   both directions of the token separation: a read token cannot write, an
   ingest token cannot read.

2. ingestOversized() folded any transport error into 413, so the four
   oversized-body tests would have stayed green if the server had stopped
   writing the status and merely killed the socket - on the one path where
   "a status, not a dead socket" is the whole client contract. Transport
   errors are now surfaced instead of swallowed.

3. HOST_AGENT_TIMEOUT_MS was read by push-metrics.js but never exported by
   run-push.sh, so setting it in the documented env file did nothing. Now
   exported and documented.

Also drops a claim from agent/README.md that the review proved false: the
ingest/read token split buys no containment on the ThinkPad, which already
stores HEALTH_TOKEN in cleartext for defenseur-auto. Losing that laptop
compromises both, so they rotate together.
2026-08-16 14:24:43 -04:00

70 lines
2.4 KiB
Bash
Executable file

#!/bin/sh
#
# Cron wrapper for push-metrics.js: load the secrets, check the install, hand
# over to node. Every five minutes, forever, unattended.
#
# NEVER add `set -x`. This script is meant to be piped into `logger`, and the
# shell would echo the `. "$ENV_FILE"` expansion — the ingest token — straight
# into /var/log/syslog and journald, where it would stay. Same reason nothing
# below ever echoes a variable that holds a secret: the checks print the NAME of
# what is missing, never its value.
#
# Suggested crontab line (see README.md):
# */5 * * * * $HOME/.local/share/maximus-host-agent/agent/run-push.sh 2>&1 | logger -t host-agent
set -eu
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
AGENT="$SCRIPT_DIR/push-metrics.js"
# Overridable for testing only; cron uses the default.
ENV_FILE="${HOST_AGENT_ENV_FILE:-$HOME/.config/maximus-host-agent.env}"
if [ ! -f "$ENV_FILE" ]; then
echo "host-agent: env file not found: $ENV_FILE" >&2
exit 1
fi
# A warning, not a refusal: a wrong mode bit is worth shouting about, but it is
# not worth silencing the heartbeat of the whole workstation over.
FILE_MODE=$(stat -c "%a" "$ENV_FILE" 2>/dev/null || echo "")
if [ -n "$FILE_MODE" ] && [ "$FILE_MODE" != "600" ]; then
echo "host-agent: warning: $ENV_FILE is mode $FILE_MODE, expected 600" >&2
fi
# shellcheck source=/dev/null
. "$ENV_FILE"
if [ -z "${HOSTS_API_URL:-}" ]; then
echo "host-agent: HOSTS_API_URL is not set in $ENV_FILE" >&2
exit 1
fi
if [ -z "${HOSTS_INGEST_TOKEN:-}" ]; then
echo "host-agent: HOSTS_INGEST_TOKEN is not set in $ENV_FILE" >&2
exit 1
fi
if [ -z "${HOST_ID:-}" ]; then
echo "host-agent: HOST_ID is not set in $ENV_FILE" >&2
exit 1
fi
if [ ! -f "$AGENT" ]; then
echo "host-agent: agent not found: $AGENT" >&2
exit 1
fi
# cron's PATH is famously short (/usr/bin:/bin), and a node installed through
# nvm or /usr/local lives outside it. Set NODE_BIN in the env file when
# `command -v node` comes up empty under cron but works in a login shell.
NODE_BIN="${NODE_BIN:-node}"
if ! command -v "$NODE_BIN" >/dev/null 2>&1; then
echo "host-agent: node not found (NODE_BIN=$NODE_BIN); set NODE_BIN in $ENV_FILE" >&2
exit 1
fi
# HOST_AGENT_TIMEOUT_MS is optional and read by push-metrics.js. It must be
# exported too, otherwise setting it in the env file silently does nothing and
# the agent always runs on its 10s default.
export HOSTS_API_URL HOSTS_INGEST_TOKEN HOST_ID HOST_AGENT_TIMEOUT_MS
exec "$NODE_BIN" "$AGENT" "$@"