navi/backend/deploy/caddy/navi-admin.caddy.notes.md
malice d644741c75 Add navi-admin service (extraction #7) (#7)
* Add navi-admin service (extraction #7)

Net-new fleet admin aggregator on :8427 — no port from recon (recon has no
/api/admin route; Phase A §3). Three @require_auth routes:
  GET /api/admin/fleet            fan-out to all 6 navi-* /api/admin/<svc>/info
                                  + recon /api/health, merged; never 5xx
                                  (failures land in errors[])
  GET /api/admin/recon/info       recon /api/health wrapped in the info shape
  GET /api/admin/navi-admin/info  self-describe

Fan-out forwards the caller's X-Authentik-Username so the @require_auth
upstreams accept it; per-service admin endpoints stay localhost-only (this is
the single edge-exposed admin surface). Service discovery: hardcoded list in
fleet.py (Option B). No secrets, no DB.

Deploy artifacts (NOT applied here): navi-admin.env.example, systemd unit,
nginx ^~ /api/admin snippet, and deploy/caddy notes for the @authed_api edit
(first Caddy change since #2).

12 hermetic tests (fleet happy-path, per-service timeout/500 → errors[],
auth-header forwarding, recon-down degraded-not-5xx, self-info no-secrets,
auth-required). Full monorepo suite green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* PR #7 review fixes

1. Symmetric degraded-entry handling in fleet.build_fleet — every
   probed service now appears in `services` with a uniform degraded
   dict on failure (matches recon's existing pattern), AND in errors[].
   Operators see "everything I tried + which broke" consistently.
2. Catch ValueError specifically in _get_json — non-JSON 200 responses
   now surface as `error: 'invalid JSON'` instead of opaque 'ValueError'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* PR #7 review fixes (round 2)

1. Unified degraded shape: wrap_recon_health calls _degraded_entry on
   failure — no more runtime.status vs runtime.recon_status asymmetry.
   Every probed service has the same shape on failure
   (runtime.status == 'unreachable'). recon-specific runtime fields
   (recon_status/recon_uptime/pipeline) remain only on the success path.
2. DRY'd git short-SHA helper into shared/git_sha.py — was duplicated in
   7 service app.py files + fleet.recon_git_sha. One implementation,
   one place to fix when behavior changes. Adds shared/tests (testpaths
   now includes "shared").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: zvx-echo6 <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 21:21:00 -06:00

2.1 KiB

navi-admin — Caddy edit notes (deploy task, NOT done in the PR)

navi-admin's /api/admin/* is auth-gated (every admin-info is @require_auth). Today the navi.echo6.co Caddy block on CT 101 (192.168.1.241 → pct 101, /etc/caddy/Caddyfile) routes /api/admin/* through @public_api (path /api/*) with no auth → it would serve the fleet/admin endpoints unauthenticated.

So the deploy task must add /api/admin/* to the @authed_api matcher. This is the first Caddy edit since extraction #2 (Phase A §6/§8).

The edit

Inside the navi.echo6.co { ... } block, the @authed_api matcher:

Before

@authed_api {
    path /api/contacts /api/contacts/* /api/auth/whoami /api/traffic /api/traffic/*
}

After (append /api/admin/*)

@authed_api {
    path /api/contacts /api/contacts/* /api/auth/whoami /api/traffic /api/traffic/* /api/admin/*
}

No other change. @authed_api already runs forward_auth https://auth.echo6.co then reverse_proxy 100.64.0.24:8440; adding the path makes /api/admin/* take that authed path instead of falling through to @public_api. Because Caddy evaluates the handle blocks in source order and @authed_api is defined before @public_api, the new path wins for /api/admin/* while /api/* still catches everything else publicly.

Apply (on CT 101)

ssh root@192.168.1.241 "pct exec 101 -- caddy validate --config /etc/caddy/Caddyfile"
ssh root@192.168.1.241 "pct exec 101 -- systemctl reload caddy"   # acme/admin off → reload is fine here

Why this is the only Caddy entry needed

The per-service /api/admin/<svc>/info endpoints stay localhost-only — they are never edge-routed (nginx has no per-service admin block; navi-admin reaches them over 127.0.0.1). Only navi-admin's single /api/admin front door is edge-exposed, so only this one path needs adding to @authed_api.

nginx side (VM 1130)

Pair this with the ^~ /api/admin → 127.0.0.1:8427 block in deploy/nginx/navi-admin.conf.snippet, added before the location /api/ catch-all in /etc/nginx/sites-available/navi.echo6.co.