feat(#1763): local-file half of btrfs send/receive migration transport
SendAgentSnapshotToFile priv op: btrfs send [-p <parent>] <snapshot> to a file under MIGRATE_STAGING_ROOT. Standalone-useful as a point-in-time snapshot export/backup today; the cross-hive ssh-piped leg (auth/trust design posted on #1763, awaiting mara/damocles steer) is a later, separate piece this doesn't block on. - hive-sh4re: PrivRequest::SendAgentSnapshotToFile + MIGRATE_STAGING_ROOT - hive-priv: validates names, refuses to overwrite an existing export, cleans up a partial file on btrfs send failure - hive-c0re: priv_client::send_agent_snapshot_to_file - hivectl: `hivectl subvol snapshot send <agent> <label> [--parent <label>] --dest <file>`
This commit is contained in:
parent
f5c8a6a8db
commit
90af0edab0
4 changed files with 201 additions and 2 deletions
|
|
@ -650,6 +650,26 @@ enum SnapshotCmd {
|
|||
/// Snapshot label passed to `subvol snapshot create --label`.
|
||||
label: String,
|
||||
},
|
||||
/// Export a snapshot to a local file via `btrfs send` (the local-file
|
||||
/// half of inter-hive migration transport; the cross-hive `ssh ...
|
||||
/// btrfs receive` leg isn't wired up yet). Also useful standalone as a
|
||||
/// point-in-time backup: a full send with no `--parent` produces a
|
||||
/// self-contained archive of the snapshot.
|
||||
Send {
|
||||
/// Agent name the snapshot belongs to.
|
||||
name: String,
|
||||
/// Snapshot label passed to `subvol snapshot create --label`.
|
||||
label: String,
|
||||
/// Optional parent snapshot label for an incremental send
|
||||
/// (`btrfs send -p`) — must be an existing, older snapshot of the
|
||||
/// same agent. Omit for a full send.
|
||||
#[arg(long)]
|
||||
parent: Option<String>,
|
||||
/// Destination filename (not a path) under the migrate-staging
|
||||
/// dir. Refused if it already exists.
|
||||
#[arg(long)]
|
||||
dest: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
|
|
@ -726,6 +746,12 @@ async fn main() -> Result<()> {
|
|||
SubvolCmd::Snapshot { cmd } => match cmd {
|
||||
SnapshotCmd::Create { name, label } => subvol_snapshot_create(&name, label).await,
|
||||
SnapshotCmd::Delete { name, label } => subvol_snapshot_delete(&name, &label).await,
|
||||
SnapshotCmd::Send {
|
||||
name,
|
||||
label,
|
||||
parent,
|
||||
dest,
|
||||
} => subvol_snapshot_send(&name, &label, parent.as_deref(), &dest).await,
|
||||
},
|
||||
},
|
||||
Cmd::Choom {
|
||||
|
|
@ -1777,6 +1803,24 @@ async fn subvol_snapshot_delete(name: &str, label: &str) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// `subvol snapshot send <agent> <label> [--parent <label>] --dest <file>` —
|
||||
/// export a snapshot to a local file via `btrfs send`. `dest` must be a
|
||||
/// bare filename (hive-priv rejects anything else via the credential-name
|
||||
/// charset check, which excludes `/`), written under the migrate-staging
|
||||
/// dir. Prints the resulting file's host path.
|
||||
async fn subvol_snapshot_send(
|
||||
name: &str,
|
||||
label: &str,
|
||||
parent: Option<&str>,
|
||||
dest: &str,
|
||||
) -> Result<()> {
|
||||
let path = hive_c0re::priv_client::send_agent_snapshot_to_file(name, label, parent, dest)
|
||||
.await
|
||||
.with_context(|| format!("send {name} snapshot (label {label:?}) to file {dest:?}"))?;
|
||||
println!("{path}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Render a hive-wide stop/start response: one `<verb>: <name>` line per
|
||||
/// touched container, then surface any aggregated per-target failure as a
|
||||
/// non-zero exit. `verb` is the past-tense word printed per item
|
||||
|
|
|
|||
|
|
@ -474,6 +474,32 @@ pub async fn delete_agent_snapshot(agent_name: &str, snapshot_name: &str) -> Res
|
|||
.await?)
|
||||
}
|
||||
|
||||
/// Stream a read-only agent snapshot to a local file via `btrfs send`
|
||||
/// (optionally incremental against `parent_snapshot_name`). Returns the
|
||||
/// full path of the written file under `MIGRATE_STAGING_ROOT`. Via
|
||||
/// hive-priv as root. See [`PrivRequest::SendAgentSnapshotToFile`].
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the hive-priv call fails, the snapshot (or parent)
|
||||
/// doesn't exist, or the destination file already exists.
|
||||
pub async fn send_agent_snapshot_to_file(
|
||||
agent_name: &str,
|
||||
snapshot_name: &str,
|
||||
parent_snapshot_name: Option<&str>,
|
||||
dest_file_name: &str,
|
||||
) -> Result<String> {
|
||||
let (stdout, _) = check(
|
||||
call(&PrivRequest::SendAgentSnapshotToFile {
|
||||
agent_name: agent_name.to_owned(),
|
||||
snapshot_name: snapshot_name.to_owned(),
|
||||
parent_snapshot_name: parent_snapshot_name.map(str::to_owned),
|
||||
dest_file_name: dest_file_name.to_owned(),
|
||||
})
|
||||
.await?,
|
||||
)?;
|
||||
Ok(stdout)
|
||||
}
|
||||
|
||||
/// Write `/etc/tmpfiles.d/hyperhive-agents.conf` for `agents` (logical names,
|
||||
/// e.g. `"atlas"`) and immediately apply it with `systemd-tmpfiles --create`.
|
||||
/// See [`PrivRequest::SyncAgentTmpfiles`] for the full semantics.
|
||||
|
|
|
|||
Loading…
Reference in a new issue