diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b701b1c..95581b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,6 +109,12 @@ jobs: working-directory: server env: SELF_HOSTED: 'true' + # Boot WITH the collector on. This block is config-gated and only the + # statistics-collecting deployment sets the flag, so it had never executed in CI, + # on alpha, or in any test - and a load-time crash inside it took production down + # while every check was green. Code only one deployment runs is exactly the code + # CI has to execute. + TELEMETRY_COLLECTOR: '1' run: | node server.js > "$RUNNER_TEMP/server.log" 2>&1 & echo $! > "$RUNNER_TEMP/server.pid" @@ -131,6 +137,21 @@ jobs: test "$REPORTED" = "$EXPECTED" echo "OK: status ok, version $REPORTED matches VERSION" + # Booting is not enough on its own - the collector could be mounted and broken. Prove + # the routes it adds actually answer, so a fault inside that block fails here rather + # than on the single deployment that turns it on. + - name: Assert the collector routes answer when enabled + run: | + STATS="$(curl -sf http://localhost:3001/api/public/stats)" + echo "stats: $STATS" + test "$(echo "$STATS" | jq -r 'has("screens") and has("installs")')" = "true" + REPORT="$(curl -s -o /dev/null -w '%{http_code}' -X POST \ + -H 'Content-Type: application/json' -d '{"bad":1}' \ + http://localhost:3001/api/telemetry/report)" + echo "malformed report -> HTTP $REPORT" + test "$REPORT" = "400" + echo "OK: collector mounted and answering" + - name: Stop server if: always() run: kill "$(cat "$RUNNER_TEMP/server.pid")" 2>/dev/null || true diff --git a/server/server.js b/server/server.js index 2bdb847..07249a0 100644 --- a/server/server.js +++ b/server/server.js @@ -983,7 +983,13 @@ app.use('/api/status', require('./routes/status')); * TELEMETRY_COLLECTOR=1, so a normal self-hosted install exposes neither. */ if (process.env.TELEMETRY_COLLECTOR === '1') { - app.use('/api', require('./routes/telemetry-collector')(db)); + /* `require('./db/database').db`, not the module-scope `db` — that binding is declared far + below this line, so naming it here throws "Cannot access 'db' before initialization" at + load and the process never starts. The inline handler this replaced only touched `db` + inside a request callback, which runs long after the binding exists; passing it to a + factory made the reference eager. Every neighbouring call site in this region resolves + the same lazy way. */ + app.use('/api', require('./routes/telemetry-collector')(require('./db/database').db)); console.log('[telemetry] collector enabled at POST /api/telemetry/report (+ GET /api/public/stats)'); }