#!/bin/sh
# secret-doctor.sh — the paging-doctor cron wrapper for woodpecker-agent2 (SPEC-P4 §1).
#
# CANONICAL SOURCE: mgf-secrets/ops/secret-doctor.sh. Deploy target (Fable's ops step):
#   /opt/watcher/secret-doctor.sh   root:root 0750
#   root crontab:  */30 * * * * /opt/watcher/secret-doctor.sh >> /opt/watcher/secret-doctor.log 2>&1
# Survives a host rebuild the same way as the other estate crons: commit here, embed in
# agent2 cloud-init, add to mgf-cloud-provision ops/verify-deployed.sh TRACKED (AC-5).
#
# WHAT IT DOES (all logic lives in `mgf-secrets watch`, which is unit-tested):
#   * runs `mgf-secrets doctor` over {woodpecker (unmetered), forge (budgeted)}, reading
#     each activated repo's RAW COMMITTED .woodpecker.yml from the forge contents API
#     (--config-source forge-raw — Woodpecker's per-pipeline config is event-RENDERED and
#     false-cleans the consumed-check) so it needs NO repo checkout, only a shallow
#     SECRETS.toml pull. The config-fetch is Codeberg-budgeted (§7b: cache TTL + per-run
#     budget + 429->unverifiable) and bounded-parallel;
#   * on ANY finding OR unverifiable -> files/updates ONE deduped Codeberg issue on
#     magogi-admin/mgf-standard (the estate pager; VALUE-FREE summary, AG-18);
#   * on clean -> closes any standing issue;
#   * on every completed run -> heartbeats job=secret-doctor to OpenObserve, so the
#     estate dead-man's-switch (estate-watch-heartbeats.sh CHECKS) pages if this cron
#     ever stops. Register `secret-doctor:2100` in that CHECKS set (35-min window > the
#     30-min cadence).
#
# NO NEW SECRET is distributed (SPEC-P4 hard constraint): it reuses the watcher's
# existing Codeberg token + the estate heartbeat SA + the on-host Woodpecker mint.

set -eu

# --- config (override via the environment) --------------------------------------- #
FED_ROOT="${MGF_ROOT:-/opt/watcher/fed}"          # shallow SECRETS.toml + *_LIBRARIES.json
PAGER_TOKEN_FILE="${PAGER_TOKEN_FILE:-/opt/watcher/.codeberg-token}"
HEARTBEAT_URL_FILE="${HEARTBEAT_URL_FILE:-/etc/mgf/backup-heartbeat.url}"
HEARTBEAT_TOKEN_FILE="${HEARTBEAT_TOKEN_FILE:-/etc/mgf/backup-heartbeat.token}"
FORGE_CACHE_DIR="${FORGE_CACHE_DIR:-/opt/watcher/cache/secret-doctor-forge}"
FORGE_CACHE_TTL="${FORGE_CACHE_TTL:-21600}"       # 6h -> a warm run does 0 Codeberg reads
# Cap live Codeberg reads/run. A COLD run touches Codeberg twice per repo: ~50 committed
# .woodpecker.yml configs (forge-raw consumed-check) + ~50 deploy-key coverage reads.
# Warm runs (< TTL) = 0. 140 = ~100 needed + headroom; over-budget repos degrade to
# `unverifiable` (loud), never silently 0.
FORGE_BUDGET="${FORGE_BUDGET:-140}"
PAGE="${PAGE:-1}"                                 # PAGE=0 => fire-test without paging the founder

TS() { date -u +%Y-%m-%dT%H:%M:%SZ; }

# --- credentials (sourced on-host; never echoed) --------------------------------- #
# Forge surface (coverage sweep) + the pager both use the watcher's existing token.
if [ -r "$PAGER_TOKEN_FILE" ]; then
    CODEBERG_TOKEN="$(cat "$PAGER_TOKEN_FILE")"
    export CODEBERG_TOKEN
fi

# Woodpecker API token (unmetered). Prefer an on-host mint; fall back to a provided
# token file / env. agent2 has no Woodpecker DB, so the mint reaches the SERVER — wire
# the exact minter here (Fable's ops step); the reconciler's HS256 DB-hash mint is the
# reference (mgf-cloud-provision ops/agent-reconciler.py get_token()).
if [ -z "${WOODPECKER_TOKEN:-}" ] && [ -x /opt/watcher/mint-woodpecker-token.sh ]; then
    WOODPECKER_TOKEN="$(/opt/watcher/mint-woodpecker-token.sh)" && export WOODPECKER_TOKEN
fi
if [ -z "${WOODPECKER_TOKEN:-}" ]; then
    echo "$(TS) FATAL: no WOODPECKER_TOKEN (set it, or install /opt/watcher/mint-woodpecker-token.sh)" >&2
    exit 2
fi

# --- refresh the registry (shallow, public-to-us, no secret) --------------------- #
# The registry (SECRETS.toml) + *_LIBRARIES.json must be local; configs come from the
# API. Fable wires the sparse pull (git sparse-checkout of SECRETS.toml + the LIBRARIES
# jsons into $FED_ROOT/{mgf-standard,sooth-standard}); this wrapper only consumes them.

PAGE_FLAG=""
[ "$PAGE" = "0" ] && PAGE_FLAG="--no-page"

exec mgf-secrets --root "$FED_ROOT" watch \
    --surface woodpecker --surface forge \
    --config-source forge-raw \
    --pager-token-file "$PAGER_TOKEN_FILE" \
    --heartbeat-url-file "$HEARTBEAT_URL_FILE" \
    --heartbeat-token-file "$HEARTBEAT_TOKEN_FILE" \
    --forge-cache-dir "$FORGE_CACHE_DIR" \
    --forge-cache-ttl "$FORGE_CACHE_TTL" \
    --forge-budget "$FORGE_BUDGET" \
    $PAGE_FLAG
