hub v0.59.0: Direction-2a agent-plane immediate-sync poke sender + ep0 felhom-poke surface

- internal/poke: pinned-host-key SSH poke sender (wgsync sibling) + fire-and-forget Notifier (PokeHost/PokeAllHosts). Poke refuses non-WG targets pre-dial; contentless via ep0 forced command to the box WG /32:51822.
- wiring: Server.SetPoke; applyPBSDR pokes the host after each descriptor gen-bump; handleSetArtifacts (MinAgent floor) pokes all hosts. main.go env POKE_SSH_KEY_FILE (reuses peersync endpoint/hostkey).
- scripts/felhom-poke.sh (non-root forced command) + offsite-endpoint.md §11; manifests/hub.yaml Secret/agent-poke + POKE_SSH_KEY_FILE (image tag bump follows the build).
This commit is contained in:
2026-07-16 22:48:15 +02:00
parent bdb65a80e8
commit eb227486d0
11 changed files with 567 additions and 0 deletions
+6
View File
@@ -1000,6 +1000,12 @@ func (s *Server) handleSetArtifacts(w http.ResponseWriter, r *http.Request) {
return
}
s.logger.Printf("[INFO] Artifact manifest set: agent=%s golden=%s min_agent=%q", agentVer, goldenVer, minAgent)
// Agent-plane immediate-sync (Direction-2a, v0.59.0): a MinAgent-floor / vouched-agent change is
// a fleet-wide agent-plane intent shift. Fire-and-forget nudge every box so it re-reports at
// once (the self-update train's signed op / floor re-evaluation lands in seconds, not ≤15 min).
// The manifest write itself does not bump per-host desired generation; the poke only accelerates
// the next report where the floor is applied. Never blocks the save.
s.poke.PokeAllHosts()
http.Redirect(w, r, "/configuration?flash=artifacts_set", http.StatusSeeOther)
}
+10
View File
@@ -25,6 +25,7 @@ import (
"strings"
"time"
"gitea.dooplex.hu/admin/felhom-hub/internal/poke"
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
"gitea.dooplex.hu/admin/felhom-hub/internal/tenantsync"
)
@@ -39,6 +40,11 @@ type tenancyProvisioner interface {
// tier enabled returns an error (not configured on this hub); the form section still renders.
func (s *Server) SetTenantSync(p tenancyProvisioner) { s.tenantsync = p }
// SetPoke wires the agent-plane immediate-sync sender (v0.59.0, Direction-2a). Without it, an
// agent-plane desired-state save just bumps the generation and the box picks it up on its next
// ≤15-min report — the poke only shortens that to seconds. Optional.
func (s *Server) SetPoke(p *poke.Notifier) { s.poke = p }
// pbsDRDescriptor is the NON-SECRET pbs_dr block in a host's desired_json. It NEVER carries the
// token secret (that is host_pbs_secrets custody, consume-once).
type pbsDRDescriptor struct {
@@ -136,6 +142,7 @@ func (s *Server) applyPBSDR(ctx context.Context, r *http.Request, cfg *store.Cus
return fmt.Errorf("pbsdr: desired-state write: %w", err)
}
s.logger.Printf("[INFO] pbsdr disabled for %s (host %s, gen %d; ep0 tenancy kept)", cfg.CustomerID, host.HostID, gen)
s.poke.PokeHost(host.HostID) // agent-plane immediate-sync (Direction-2a): land the change in seconds
return nil
}
@@ -160,6 +167,7 @@ func (s *Server) applyPBSDR(ctx context.Context, r *http.Request, cfg *store.Cus
return fmt.Errorf("pbsdr: desired-state write: %w", err)
}
s.logger.Printf("[INFO] pbsdr descriptor updated for %s (host %s, gen %d; tenancy unchanged)", cfg.CustomerID, host.HostID, gen)
s.poke.PokeHost(host.HostID) // agent-plane immediate-sync (Direction-2a): land the change in seconds
return nil
}
@@ -171,6 +179,8 @@ func (s *Server) applyPBSDR(ctx context.Context, r *http.Request, cfg *store.Cus
}
if blocked != "" {
s.logger.Printf("[INFO] pbsdr: DR tier ON for %s — waiting: %s", cfg.CustomerID, blocked)
} else {
s.poke.PokeHost(host.HostID) // freshly provisioned + generation bumped → nudge the box now
}
return nil
}
+8
View File
@@ -19,6 +19,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/intent"
"gitea.dooplex.hu/admin/felhom-hub/internal/poke"
"gitea.dooplex.hu/admin/felhom-hub/internal/offsite"
"gitea.dooplex.hu/admin/felhom-hub/internal/semver"
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
@@ -73,6 +74,13 @@ type Server struct {
// no immediacy (bumps are no-ops; the 15-min cycle still reconciles).
intentHub *intent.Hub
// poke (v0.59.0, Direction-2a agent-plane immediate-sync) fires a fire-and-forget UDP nudge to
// a host's box (via the ep0 forced command) when an AGENT-plane desired-state change is saved
// (a pbsdr descriptor, the MinAgent floor) so the box ticks in seconds instead of ≤15 min. nil
// = no immediacy (a no-op; the report cycle still reconciles). Its sibling intentHub handles
// the CONTROLLER plane (customer/app config) via the long-poll wait channel.
poke *poke.Notifier
sessions map[string]*hubSession
sessionsMu sync.RWMutex
}