feat(#2862): push a snapshot to a peer hive's store over the mesh
Adds the caller the fd-passing machinery existed for: hivectl agent <name> subvol snapshot push --peer <hive> resolves the peer, connects to its snapshot store, writes the agent header, and hands the connected socket to hive-priv, which runs btrfs send straight into it. The split keeps the root helper ignorant. Everything that involves knowing where a peer is, what the wire protocol looks like, and which hive to trust happens in the unprivileged daemon; hive-priv only ever receives an already-open descriptor. Once btrfs send starts, neither process is in the data path, so a multi-gigabyte transfer costs no per-byte work and survives a hive-c0re restart. call_with_fd takes the descriptor by value and closes it as soon as the kernel has it. A socket stays open until every copy closes, so holding one back would leave the receiver waiting for an EOF that never comes: btrfs receive blocks and this side reports success for a transfer the peer never committed. Ownership makes that unrepresentable. The peer's store port is a new swarm.peers.<domain>.snapshotStorePort option rather than a constant matching the module default. A pushing hive cannot read the receiver's configuration, so assuming 51821 would push at a port nobody promised to listen on; absent, the push fails naming the option. swarm_peers parses the mesh address the host module has always rendered into HYPERHIVE_PEERS but nothing read.
This commit is contained in:
parent
51f352f0ca
commit
282bbc3709
10 changed files with 546 additions and 9 deletions
|
|
@ -151,10 +151,13 @@ in
|
|||
}
|
||||
// lib.optionalAttrs (config.services.hyperhive.swarm.peers != { }) {
|
||||
# Peer hives serialised as a JSON array of {domain, cert_fingerprint,
|
||||
# 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.
|
||||
# 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.
|
||||
HYPERHIVE_PEERS = builtins.toJSON (
|
||||
lib.mapAttrsToList (
|
||||
domain: p:
|
||||
|
|
@ -165,6 +168,9 @@ 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
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,6 +105,25 @@
|
|||
are silently excluded from `wg-hive`).
|
||||
'';
|
||||
};
|
||||
|
||||
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.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue