refactor(#2862): one snapshot store per swarm, not one per peer
The push side modelled a store per peer hive: a --peer argument, a swarm.peers.<domain>.snapshotStorePort option, and a swarm_peers module whose entire job was answering "which peer". A swarm has exactly one store, so none of that had anything to select between. The receiver already proved it. It keys destination directories by agent, not by sending hive, precisely so an agent that migrates keeps one unbroken incremental chain -- which only makes sense if every hive pushes to the same place. Per-hive stores would split the chain in two, the case that keying exists to prevent. So the destination moves to services.hyperhive.swarm.snapshotStore, rendered into HYPERHIVE_SNAPSHOT_STORE, and swarm_peers is deleted rather than adapted. address has no default because it is a deployment fact this host cannot derive; port defaults because it is a convention both ends read from the same option docs. An unset or empty address fails naming the option instead of connecting somewhere arbitrary, and a test asserts the message suggests no value.
This commit is contained in:
parent
258c0998ab
commit
ba71e45486
9 changed files with 171 additions and 279 deletions
|
|
@ -149,15 +149,24 @@ in
|
|||
# or no gatewayHost is set (no browser-reachable matrix vhost).
|
||||
HIVE_MATRIX_PUBLIC_URL = "https://${config.services.hyperhive.matrix.gatewayHost}/";
|
||||
}
|
||||
// lib.optionalAttrs (config.services.hyperhive.swarm.snapshotStore.address != null) {
|
||||
# `host:port` of the swarm's single snapshot store, for pushing agent
|
||||
# snapshots (hive-c0re::snapshot_push). One per swarm, not one per
|
||||
# peer — the receiver keys destinations by agent so a migrating agent
|
||||
# keeps one incremental chain. Absent when no store is configured, and
|
||||
# a push then fails naming the option rather than guessing.
|
||||
HYPERHIVE_SNAPSHOT_STORE =
|
||||
let
|
||||
s = config.services.hyperhive.swarm.snapshotStore;
|
||||
in
|
||||
"${s.address}:${toString s.port}";
|
||||
}
|
||||
// lib.optionalAttrs (config.services.hyperhive.swarm.peers != { }) {
|
||||
# Peer hives serialised as a JSON array of {domain, cert_fingerprint,
|
||||
# wireguard_address?, snapshot_store_port?} objects. Consumed by
|
||||
# hive-agent::identity::peers(), the dashboard's peer_hives
|
||||
# StateSnapshot field (P33RS tab), and snapshot pushes
|
||||
# (hive-c0re::swarm_peers). Domain is the attrset key;
|
||||
# cert_fingerprint is null for CA-trusted peers; wireguard_address is
|
||||
# omitted when not part of the mesh; snapshot_store_port is omitted
|
||||
# when the peer runs no snapshot store.
|
||||
# wireguard_address?} objects. Consumed by hive-agent::identity::peers()
|
||||
# + the dashboard's peer_hives StateSnapshot field (P33RS tab). Domain
|
||||
# is the attrset key; cert_fingerprint is null for CA-trusted peers;
|
||||
# wireguard_address is omitted when not part of the mesh.
|
||||
HYPERHIVE_PEERS = builtins.toJSON (
|
||||
lib.mapAttrsToList (
|
||||
domain: p:
|
||||
|
|
@ -168,9 +177,6 @@ in
|
|||
// lib.optionalAttrs (p.wireguardAddress != null) {
|
||||
wireguard_address = p.wireguardAddress;
|
||||
}
|
||||
// lib.optionalAttrs (p.snapshotStorePort != null) {
|
||||
snapshot_store_port = p.snapshotStorePort;
|
||||
}
|
||||
) config.services.hyperhive.swarm.peers
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,24 +106,6 @@
|
|||
'';
|
||||
};
|
||||
|
||||
snapshotStorePort = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.port;
|
||||
default = null;
|
||||
example = 51821;
|
||||
description = ''
|
||||
TCP port this peer's snapshot store listens on, when it
|
||||
runs one (`services.hyperhive.snapshotStore`). Injected
|
||||
into `HYPERHIVE_PEERS` so a pushing hive can reach the
|
||||
receiver at `wireguardAddress:snapshotStorePort`.
|
||||
|
||||
Null means this peer hosts no snapshot store, and pushing
|
||||
to it fails with that message rather than guessing a port.
|
||||
The port lives here — on the peer, alongside the mesh
|
||||
address it pairs with — because it describes *that host's*
|
||||
deployment, and a pushing hive cannot read the receiver's
|
||||
own configuration.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
|
|
@ -144,4 +126,41 @@
|
|||
'';
|
||||
};
|
||||
|
||||
options.services.hyperhive.swarm.snapshotStore = {
|
||||
address = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "10.100.0.1";
|
||||
description = ''
|
||||
Mesh address of the swarm's snapshot store — the single
|
||||
`btrfs receive` endpoint every hive in this swarm pushes agent
|
||||
snapshots to. Bare IP, no prefix.
|
||||
|
||||
There is exactly **one** store per swarm, not one per peer: the
|
||||
receiver keys destinations by *agent*, so an agent that migrates
|
||||
between hives keeps a single unbroken incremental chain. Per-hive
|
||||
stores would split that chain in two, which is the case the store
|
||||
exists to serve.
|
||||
|
||||
Null means this swarm has no store configured, and pushing fails
|
||||
saying so rather than guessing an address. Set it on every hive
|
||||
that pushes; the receiving host separately sets
|
||||
`services.hyperhive.snapshotStore.enable`.
|
||||
'';
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 51821;
|
||||
description = ''
|
||||
TCP port the swarm's snapshot store listens on. Must match the
|
||||
receiving host's `services.hyperhive.snapshotStore.port`.
|
||||
|
||||
Defaulted (unlike `address`) because it is a shared convention
|
||||
both sides read from the same option docs — whereas an address
|
||||
is deployment-specific and cannot be guessed.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue