feat(hub): Direction-2 immediate-sync wait channel (v0.58.0)
GET /api/v1/wait long-poll: the box holds an authed hanging GET; the hub completes it the instant any operator intent bumps that customer's in-memory generation, then the box fires its ordinary report and the ACK delivers everything through the unchanged machinery. 240s hold with a 25s heartbeat newline defeats the nginx 60s proxy_read_timeout with no ingress annotation; WriteTimeout lifted per-connection via ResponseController. - internal/intent: per-customer generation counter + waiter registry (Bump/Wait/Close), coalescing to latest, race-closer, in-memory by design. Red-proofs: counter-vs-queue + race-closer (run-fail-reverted). - api/wait.go: the endpoint (per-customer only; global key 400; A cannot see B). - web bumps after every intent write (fire-after-commit): config CRUD, claim resend, offsite re-issue/freeze, password regen, block/unblock, floors (global bumps all config-managed), controller log-tail + log-bundle. - main.go: one intent hub shared by web+api; Close() before server.Shutdown. Pairs with controller v0.140.0 (the long-poll client). Grounding: documentation/audits/SPIKE-immediate-sync-transport-2026-07-16.md.
This commit is contained in:
@@ -18,6 +18,7 @@ import (
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/claim"
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/gitea"
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/hetznerapi"
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/intent"
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/mailrelay"
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/offsite"
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/pbsdrheal"
|
||||
@@ -308,6 +309,15 @@ func main() {
|
||||
// unconfigured or the customer has no offsite tier.
|
||||
apiHandler.SetOffsiteReissuer(webServer.ReissueOffsiteForCustomer)
|
||||
|
||||
// Direction-2 immediate-sync (v0.58.0): one in-memory operator-intent notifier, shared by the
|
||||
// web handlers (which Bump it after every intent write) and the API handler (which long-polls it
|
||||
// at GET /api/v1/wait). In-memory BY DESIGN — a restart resets generations to zero; the box
|
||||
// compares with != , so a restart costs exactly one harmless full-state report. Closed before
|
||||
// server.Shutdown (below) so held waits complete instantly instead of eating the grace window.
|
||||
intentHub := intent.New()
|
||||
apiHandler.SetIntentHub(intentHub)
|
||||
webServer.SetIntentHub(intentHub)
|
||||
|
||||
// Build HTTP mux
|
||||
mux := http.NewServeMux()
|
||||
|
||||
@@ -515,6 +525,9 @@ func main() {
|
||||
logger.Printf("[INFO] Received signal %v, shutting down...", sig)
|
||||
cancel()
|
||||
|
||||
// Complete every held long-poll before Shutdown so they don't consume the grace window.
|
||||
intentHub.Close()
|
||||
|
||||
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer shutdownCancel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user