| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879 |
- // M13a W5: admin HTTP routes for archiverd. One route is
- // registered when BA_AUTHD_JWT_SECRET is set:
- //
- // POST /v1/admin/archiver/run — trigger an immediate archiver
- // pass (instead of waiting for the next ticker fire). Destructive:
- // takes the Postgres advisory lock and runs a full drain. Any
- // authenticated user can fire it; the existing runLock
- // guarantees no two pass can overlap.
- //
- // When BA_AUTHD_JWT_SECRET is unset, no admin routes are
- // registered. The /health and /metrics endpoints are owned by
- // the shared metricsHandler and continue to work as before.
- package main
- import (
- "encoding/json"
- "log/slog"
- "net/http"
- "git3.techno-world.net/lrosales/broad-announce/internal/authd"
- )
- // wireAdminRoutes wires the admin routes onto mux. The triggerCh
- // is a buffered channel of size 1; a non-blocking send coalesces
- // concurrent trigger requests (a run that's already in progress
- // will complete, and any queued trigger is dropped — the next
- // ticker will fire normally).
- //
- // When the JWT gate is disabled (env not set), the function logs
- // a warning and returns without registering anything — backward
- // compat for non-M13 deploys.
- func wireAdminRoutes(mux *http.ServeMux, triggerCh chan<- struct{}, logger *slog.Logger) {
- if !authd.EnvEnabled() {
- logger.Warn("admin routes are DISABLED (set BA_AUTHD_JWT_SECRET to enable)")
- return
- }
- ad, err := authd.NewFromEnv()
- if err != nil {
- logger.Error("BA_AUTHD_JWT_SECRET set but authd init failed; admin routes DISABLED", "err", err)
- return
- }
- logger.Info("admin routes enabled with JWT gate")
- // Any authenticated user can fire an archiver run; it's not
- // state-destroying (the archiver is idempotent thanks to the
- // Postgres advisory lock + the SELECT/DELETE contract).
- mux.Handle("POST /v1/admin/archiver/run",
- ad.RequireAuth(http.HandlerFunc(handleRunNow(triggerCh, logger))))
- }
- // handleRunNow signals the runLoop to fire a pass. The send is
- // non-blocking: if a trigger is already pending (a previous
- // request fired and the loop hasn't picked it up yet), we report
- // it back to the caller instead of queueing duplicates.
- func handleRunNow(triggerCh chan<- struct{}, logger *slog.Logger) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- actor := ""
- if c := authd.ClaimsFromContext(r.Context()); c != nil {
- actor = c.UserID
- }
- select {
- case triggerCh <- struct{}{}:
- logger.Info("admin archiver run triggered", "actor", actor)
- w.Header().Set("Content-Type", "application/json")
- _ = json.NewEncoder(w).Encode(map[string]any{
- "triggered": true,
- })
- default:
- // Trigger already pending or run in progress.
- // Coalesce — return 202 Accepted with a hint.
- logger.Info("admin archiver run coalesced (already pending)", "actor", actor)
- w.Header().Set("Content-Type", "application/json")
- w.WriteHeader(http.StatusAccepted)
- _ = json.NewEncoder(w).Encode(map[string]any{
- "triggered": false,
- "reason": "already_pending",
- })
- }
- }
- }
|