fix(felhomsshd): don't empty the belt/authkeys on an unfetched block (operator lockout)

Mirrors wgtunnel fetched=false-is-never-a-teardown: until the desired-state is
delivered, @operator_ips + felhom-op authorized_keys are left untouched (a nil
block on agent restart would otherwise empty @operator_ips and lock the operator
out until the next fetch). Belt.Sync split into SyncPort (always) + SyncOperator
(fetched only).

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:51:30 +02:00
parent b99d02b7a9
commit 2bf9354c0e
3 changed files with 43 additions and 25 deletions
+5 -4
View File
@@ -88,7 +88,8 @@ func TestBelt_SyncMutatesThenIdempotent(t *testing.T) {
r.stdout["nft list operator_ips"] = "set operator_ips {\n\ttype ipv4_addr\n}"
r.stdout["nft list ssh_port"] = "set ssh_port {\n\ttype inet_service\n}"
b := NewBelt(r, nil)
b.Sync(context.Background(), 8822, "10.77.0.250")
b.SyncPort(context.Background(), 8822)
b.SyncOperator(context.Background(), "10.77.0.250")
// mutations happened: flush + add for BOTH sets
if r.count("nft", "flush") < 2 || r.count("nft", "add") < 2 {
@@ -103,7 +104,8 @@ func TestBelt_SyncMutatesThenIdempotent(t *testing.T) {
r2.stdout["nft list operator_ips"] = "elements = { 10.77.0.250 }"
r2.stdout["nft list ssh_port"] = "elements = { 8822 }"
b2 := NewBelt(r2, nil)
b2.Sync(context.Background(), 8822, "10.77.0.250")
b2.SyncPort(context.Background(), 8822)
b2.SyncOperator(context.Background(), "10.77.0.250")
if r2.count("nft", "flush") != 0 || r2.count("nft", "add") != 0 {
t.Fatalf("idempotent sync must do ZERO mutations; flushes=%d adds=%d", r2.count("nft", "flush"), r2.count("nft", "add"))
}
@@ -114,8 +116,7 @@ func TestBelt_OperatorEmptyEmptiesSet(t *testing.T) {
r.stdout["nft list operator_ips"] = "elements = { 10.77.0.250 }" // currently has an operator IP
r.stdout["nft list ssh_port"] = "elements = { 8822 }"
b := NewBelt(r, nil)
b.Sync(context.Background(), 8822, "") // OOB off → operator set must be emptied
// operator_ips: current {250} != desired {} → flush, no add. ssh_port: {} vs {8822}...
b.SyncOperator(context.Background(), "") // OOB explicitly off → operator set must be emptied
if r.count("nft", "flush") < 1 {
t.Fatal("emptying the operator set must flush it")
}