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:
atlas 2026-07-31 21:40:34 +02:00 committed by mara
commit 282bbc3709
10 changed files with 546 additions and 9 deletions

View file

@ -44,6 +44,11 @@ pub(crate) async fn dispatch_subvol(socket: &Path, name: &str, cmd: SubvolCmd) -
parent,
dest,
} => subvol_snapshot_send(socket, name, &label, parent.as_deref(), &dest).await,
SnapshotCmd::Push {
label,
parent,
peer,
} => subvol_snapshot_push(socket, name, &label, parent.as_deref(), &peer).await,
},
}
}
@ -217,3 +222,34 @@ async fn subvol_snapshot_send(
)
.await
}
/// `subvol snapshot push <agent> <label> [--parent <label>] --peer <hive>`
/// — stream a snapshot to a peer hive's snapshot store over the mesh.
///
/// The network sibling of `send`. Nothing is staged on this host, so
/// there is no path to print: success is silent apart from the
/// confirmation below.
async fn subvol_snapshot_push(
socket: &Path,
name: &str,
label: &str,
parent: Option<&str>,
peer: &str,
) -> Result<()> {
daemon_request(
socket,
hive_host_sock::HostRequest::PushSnapshot {
name: crate::util::parse_ident(name)?,
label: label.to_owned(),
parent: parent.map(str::to_owned),
peer: peer.to_owned(),
},
"snapshot push",
)
.await?;
match parent {
Some(p) => println!("pushed {name} snapshot {label:?} (incremental from {p:?}) to {peer}"),
None => println!("pushed {name} snapshot {label:?} (full) to {peer}"),
}
Ok(())
}