feat(hub): operator OOB peer + oob_peer_ip desired-state merge (H1 Part 1)

store.SetOperatorOOBPeer/GetOperatorOOBPeer (empty-host_id wg_peers row, explicit
/32, validated in-subnet/not-reserved/not-taken, last-write-wins rotation).
PUT/GET /admin/wg/operator-peer (global key). mergeWireguard adds oob_peer_ip when
an operator peer exists (absent = byte-identical). BumpAllHostGenerations forces
fleet re-fetch. Non-hollow tests both sides.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-05 22:08:06 +02:00
parent a3ee93e97e
commit 0ec7555126
6 changed files with 342 additions and 1 deletions
+5
View File
@@ -215,6 +215,11 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.handleAdminDeleteWGPeer(w, r)
case r.Method == http.MethodGet && path == "/admin/wg/peers":
h.handleAdminListWGPeers(w, r)
// H1: the fleet operator OOB peer (register/rotate at an explicit /32) + read-back.
case r.Method == http.MethodPut && path == "/admin/wg/operator-peer":
h.handleAdminSetOperatorPeer(w, r)
case r.Method == http.MethodGet && path == "/admin/wg/operator-peer":
h.handleAdminGetOperatorPeer(w, r)
case r.Method == http.MethodPost && path == "/event":
h.handleEvent(w, r)
case r.Method == http.MethodPost && path == "/mail":