hub: S2 store — host-bound WG peers (register/re-key/adopt), one-per-host index, BumpHostDesired
allocateWGPeerTx extracted from addWGPeerOnce (behavior-neutral; S1 tests unmodified+green). RegisterWGPeerForHost: idempotent / re-key-in-place-keep-ip / adopt-unbound / ErrWGPubkeyBoundElsewhere. Partial unique index enforces one bound peer per host. BumpHostDesired touches ONLY the generation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -391,6 +391,8 @@ func (s *Store) migrate() error {
|
||||
created_at DATETIME NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at DATETIME NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
-- S2: one BOUND peer per host (partial index — S1's unbound admin/test rows unaffected).
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_wg_peers_host ON wg_peers(host_id) WHERE host_id != '';
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1459,6 +1461,27 @@ func (s *Store) SetHostDesired(hostID string, desiredJSON []byte) (int64, error)
|
||||
return gen, nil
|
||||
}
|
||||
|
||||
// BumpHostDesired advances a host's desired_generation WITHOUT touching desired_json (S2). Used
|
||||
// when hub-OWNED served state changes (the merge-at-read wireguard block) — the stored operator
|
||||
// blob is not the thing that moved, so SetHostDesired (which replaces it) must not be used.
|
||||
// Returns the new generation; sql.ErrNoRows for an unknown host.
|
||||
func (s *Store) BumpHostDesired(hostID string) (int64, error) {
|
||||
res, err := s.db.Exec(`
|
||||
UPDATE hosts SET desired_generation = desired_generation + 1, updated_at = datetime('now')
|
||||
WHERE host_id = ?`, hostID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n, _ := res.RowsAffected(); n == 0 {
|
||||
return 0, sql.ErrNoRows // unknown host
|
||||
}
|
||||
var gen int64
|
||||
if err := s.db.QueryRow(`SELECT desired_generation FROM hosts WHERE host_id = ?`, hostID).Scan(&gen); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return gen, nil
|
||||
}
|
||||
|
||||
// SignedJob is one OPAQUE operator-signed destructive-op blob queued for a host (slice 10A). The
|
||||
// hub stores + serves the bytes; it never forges, opens, or executes them (10B owns verify+run).
|
||||
type SignedJob struct {
|
||||
|
||||
Reference in New Issue
Block a user