Compare commits
1 changed files with 145 additions and 253 deletions
|
|
@ -332,27 +332,48 @@ async fn write_line_event(writer: &mut OwnedWriteHalf, stream: PrivStream, data:
|
||||||
/// For streaming ops (`CreateContainer`/`UpdateContainer` with `stream: true`)
|
/// For streaming ops (`CreateContainer`/`UpdateContainer` with `stream: true`)
|
||||||
/// output lines are forwarded to `writer` as `PrivEvent::Line` messages and
|
/// output lines are forwarded to `writer` as `PrivEvent::Line` messages and
|
||||||
/// the returned strings are empty.
|
/// the returned strings are empty.
|
||||||
|
// One match arm per priv op — a flat 1:1 dispatch table. The length tracks
|
||||||
|
// the op count, not complexity; splitting it would just scatter the mapping.
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
/// Execute one validated request.
|
||||||
///
|
///
|
||||||
/// `fd` is the descriptor that arrived with this request, already checked
|
/// `fd` is the descriptor that arrived with this request, already checked
|
||||||
/// against the operation by [`check_fd_agreement`]: `Some` exactly for
|
/// against the operation by [`check_fd_agreement`]: `Some` exactly for
|
||||||
/// the variants that stream into a caller-supplied descriptor, `None`
|
/// the variants that stream into a caller-supplied descriptor, `None`
|
||||||
/// for every other operation.
|
/// for every other operation.
|
||||||
// One match arm per priv op — a flat 1:1 dispatch table. Every arm either
|
|
||||||
// delegates directly or validates then delegates; an op whose handling is
|
|
||||||
// more than that gets its own named function instead, so the match's length
|
|
||||||
// tracks the op count, not complexity.
|
|
||||||
#[allow(clippy::too_many_lines)]
|
|
||||||
async fn exec(
|
async fn exec(
|
||||||
req: PrivRequest,
|
req: PrivRequest,
|
||||||
fd: Option<OwnedFd>,
|
fd: Option<OwnedFd>,
|
||||||
writer: &mut OwnedWriteHalf,
|
writer: &mut OwnedWriteHalf,
|
||||||
) -> Result<(String, String)> {
|
) -> Result<(String, String)> {
|
||||||
match req {
|
match req {
|
||||||
PrivRequest::StartContainer { ref name } => start_container(name).await,
|
PrivRequest::StartContainer { ref name } => {
|
||||||
|
validate_container_name(name)?;
|
||||||
|
let machine = container_system_name(name);
|
||||||
|
// Clear any start-limit lockout left by earlier failures so a
|
||||||
|
// now-correct start isn't blocked. nixos-container start does not
|
||||||
|
// do this itself. Best-effort: if the unit doesn't exist yet
|
||||||
|
// (first-time create) reset-failed is a no-op and we proceed.
|
||||||
|
let _ = Command::new("systemctl")
|
||||||
|
.args(["reset-failed", &format!("container@{machine}.service")])
|
||||||
|
.status()
|
||||||
|
.await;
|
||||||
|
container_run(&["start", &machine]).await
|
||||||
|
}
|
||||||
|
|
||||||
PrivRequest::StopContainer { ref name } => stop_container(name).await,
|
PrivRequest::StopContainer { ref name } => {
|
||||||
|
validate_container_name(name)?;
|
||||||
|
stop_and_release(&container_system_name(name)).await
|
||||||
|
}
|
||||||
|
|
||||||
PrivRequest::KillContainer { ref name } => kill_container(name).await,
|
PrivRequest::KillContainer { ref name } => {
|
||||||
|
validate_container_name(name)?;
|
||||||
|
// nixos-container has no kill verb. Use machinectl to send SIGKILL
|
||||||
|
// to all processes in the container — the right semantics for a
|
||||||
|
// forced shutdown after a graceful stop has already been attempted.
|
||||||
|
let machine = container_system_name(name);
|
||||||
|
machinectl_run(&["kill", &machine, "--signal=SIGKILL"]).await
|
||||||
|
}
|
||||||
|
|
||||||
PrivRequest::UpdateContainer { ref name, stream } => {
|
PrivRequest::UpdateContainer { ref name, stream } => {
|
||||||
container_flake_action("update", name, stream, writer).await
|
container_flake_action("update", name, stream, writer).await
|
||||||
|
|
@ -362,14 +383,20 @@ async fn exec(
|
||||||
container_flake_action("create", name, stream, writer).await
|
container_flake_action("create", name, stream, writer).await
|
||||||
}
|
}
|
||||||
|
|
||||||
PrivRequest::DestroyContainer { ref name } => destroy_container(name).await,
|
PrivRequest::DestroyContainer { ref name } => {
|
||||||
|
validate_container_name(name)?;
|
||||||
|
container_run(&["destroy", &container_system_name(name)]).await
|
||||||
|
}
|
||||||
|
|
||||||
PrivRequest::ListContainers => container_run(&["list"]).await,
|
PrivRequest::ListContainers => container_run(&["list"]).await,
|
||||||
|
|
||||||
PrivRequest::ReadContainerJournal {
|
PrivRequest::ReadContainerJournal {
|
||||||
ref container,
|
ref container,
|
||||||
ref query,
|
ref query,
|
||||||
} => exec_read_container_journal(container, query).await,
|
} => {
|
||||||
|
validate_container_system_name(container)?;
|
||||||
|
read_container_journal(container, query).await
|
||||||
|
}
|
||||||
|
|
||||||
PrivRequest::WriteNspawnFlags {
|
PrivRequest::WriteNspawnFlags {
|
||||||
ref container,
|
ref container,
|
||||||
|
|
@ -392,41 +419,105 @@ async fn exec(
|
||||||
|
|
||||||
PrivRequest::ReloadGatewayNginx => sync_gateway_nginx().await,
|
PrivRequest::ReloadGatewayNginx => sync_gateway_nginx().await,
|
||||||
|
|
||||||
PrivRequest::RunForgeAdmin { ref args } => exec_forge_admin(args).await,
|
PrivRequest::RunForgeAdmin { ref args } => {
|
||||||
|
for arg in args {
|
||||||
|
validate_forge_admin_arg(arg)?;
|
||||||
|
}
|
||||||
|
run_forge_admin(args).await
|
||||||
|
}
|
||||||
|
|
||||||
PrivRequest::SetAgentPaused {
|
PrivRequest::SetAgentPaused {
|
||||||
ref agent_name,
|
ref agent_name,
|
||||||
paused,
|
paused,
|
||||||
} => exec_set_agent_paused(agent_name, paused),
|
} => {
|
||||||
|
validate_agent_name(agent_name)?;
|
||||||
|
set_agent_paused(agent_name, paused)
|
||||||
|
}
|
||||||
|
|
||||||
PrivRequest::WriteAgentForgeToken {
|
PrivRequest::WriteAgentForgeToken {
|
||||||
ref agent_name,
|
ref agent_name,
|
||||||
ref token,
|
ref token,
|
||||||
} => write_forge_token(agent_name, token),
|
} => {
|
||||||
|
validate_agent_name(agent_name)?;
|
||||||
|
write_agent_state_file(agent_name, "forge-token", &format!("{token}\n"))
|
||||||
|
}
|
||||||
|
|
||||||
PrivRequest::WriteAgentMatrixToken {
|
PrivRequest::WriteAgentMatrixToken {
|
||||||
ref agent_name,
|
ref agent_name,
|
||||||
ref token,
|
ref token,
|
||||||
ref account,
|
ref account,
|
||||||
ref homeserver,
|
ref homeserver,
|
||||||
} => write_matrix_token(agent_name, token, account.as_deref(), homeserver.as_deref()),
|
} => {
|
||||||
|
validate_agent_name(agent_name)?;
|
||||||
|
// Build the token filename. `None` → the hive account's
|
||||||
|
// `matrix-token`; `Some(a)` → `matrix-token-<a>`. The account
|
||||||
|
// suffix MUST be validated as a plain identifier (no `/`, `.`,
|
||||||
|
// `..`) before it goes into the filename, or a crafted account
|
||||||
|
// could traverse out of the state dir — `write_agent_state_file`
|
||||||
|
// trusts its `filename` argument.
|
||||||
|
let filename = match account {
|
||||||
|
None => "matrix-token".to_owned(),
|
||||||
|
Some(a) => {
|
||||||
|
validate_name_chars(a)?;
|
||||||
|
format!("matrix-token-{a}")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let res = write_agent_state_file(agent_name, &filename, &format!("{token}\n"))?;
|
||||||
|
// For an extra account, persist its homeserver in a sidecar
|
||||||
|
// (`matrix-account-<a>.json`) so the daemon can auto-discover the
|
||||||
|
// account without a static `matrixAccounts` config entry. Only
|
||||||
|
// when both `account` and `homeserver` are present; the account
|
||||||
|
// suffix is already validated above.
|
||||||
|
if let (Some(a), Some(hs)) = (account, homeserver) {
|
||||||
|
let meta = serde_json::to_string(&MatrixAccountSidecar {
|
||||||
|
homeserver: hs.as_str(),
|
||||||
|
})
|
||||||
|
.context("serialize matrix account sidecar")?;
|
||||||
|
write_agent_state_file(agent_name, &format!("matrix-account-{a}.json"), &meta)?;
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
PrivRequest::WriteAgentGithubToken {
|
PrivRequest::WriteAgentGithubToken {
|
||||||
ref agent_name,
|
ref agent_name,
|
||||||
ref token,
|
ref token,
|
||||||
} => write_github_token(agent_name, token),
|
} => {
|
||||||
|
validate_agent_name(agent_name)?;
|
||||||
|
write_agent_state_file(agent_name, "github-token", &format!("{token}\n"))
|
||||||
|
}
|
||||||
|
|
||||||
PrivRequest::WriteAgentExtraForgeAccount {
|
PrivRequest::WriteAgentExtraForgeAccount {
|
||||||
ref agent_name,
|
ref agent_name,
|
||||||
ref label,
|
ref label,
|
||||||
ref base_url,
|
ref base_url,
|
||||||
ref token,
|
ref token,
|
||||||
} => write_extra_forge_account(agent_name, label, base_url, token),
|
} => {
|
||||||
|
validate_agent_name(agent_name)?;
|
||||||
|
validate_name_chars(label)?;
|
||||||
|
let res = write_agent_state_file(
|
||||||
|
agent_name,
|
||||||
|
&format!("forge-{label}-token"),
|
||||||
|
&format!("{token}\n"),
|
||||||
|
)?;
|
||||||
|
// Sidecar carries the base URL — there's no host-side nix config
|
||||||
|
// for extra forges, so this is the only place it's persisted.
|
||||||
|
let meta = serde_json::to_string(&ForgeSidecar {
|
||||||
|
base_url: base_url.as_str(),
|
||||||
|
})
|
||||||
|
.context("serialize forge account sidecar")?;
|
||||||
|
write_agent_state_file(agent_name, &format!("forge-{label}.json"), &meta)?;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
PrivRequest::DeleteAgentExtraForgeAccount {
|
PrivRequest::DeleteAgentExtraForgeAccount {
|
||||||
ref agent_name,
|
ref agent_name,
|
||||||
ref label,
|
ref label,
|
||||||
} => delete_extra_forge_account(agent_name, label),
|
} => {
|
||||||
|
validate_agent_name(agent_name)?;
|
||||||
|
validate_name_chars(label)?;
|
||||||
|
delete_agent_state_file(agent_name, &format!("forge-{label}-token"))?;
|
||||||
|
delete_agent_state_file(agent_name, &format!("forge-{label}.json"))
|
||||||
|
}
|
||||||
|
|
||||||
PrivRequest::RestartMatrixDaemon { ref agent_name } => {
|
PrivRequest::RestartMatrixDaemon { ref agent_name } => {
|
||||||
restart_matrix_daemon(agent_name).await
|
restart_matrix_daemon(agent_name).await
|
||||||
|
|
@ -439,37 +530,52 @@ async fn exec(
|
||||||
}
|
}
|
||||||
|
|
||||||
PrivRequest::EnsureAgentSubvolume { ref agent_name } => {
|
PrivRequest::EnsureAgentSubvolume { ref agent_name } => {
|
||||||
exec_ensure_agent_subvolume(agent_name).await
|
validate_agent_name(agent_name)?;
|
||||||
|
ensure_agent_subvolume(agent_name).await
|
||||||
}
|
}
|
||||||
|
|
||||||
PrivRequest::DeleteAgentSubvolume { ref agent_name } => {
|
PrivRequest::DeleteAgentSubvolume { ref agent_name } => {
|
||||||
exec_delete_agent_subvolume(agent_name).await
|
validate_agent_name(agent_name)?;
|
||||||
|
delete_agent_subvolume(agent_name).await
|
||||||
}
|
}
|
||||||
|
|
||||||
PrivRequest::EnsureBtrfsQuota => ensure_btrfs_quota().await,
|
PrivRequest::EnsureBtrfsQuota => ensure_btrfs_quota().await,
|
||||||
|
|
||||||
PrivRequest::ReadSubvolumeUsage { ref agent_name } => {
|
PrivRequest::ReadSubvolumeUsage { ref agent_name } => {
|
||||||
exec_read_subvolume_usage(agent_name).await
|
validate_agent_name(agent_name)?;
|
||||||
|
read_subvolume_usage(agent_name).await
|
||||||
}
|
}
|
||||||
|
|
||||||
PrivRequest::SetSubvolumeQuota {
|
PrivRequest::SetSubvolumeQuota {
|
||||||
ref agent_name,
|
ref agent_name,
|
||||||
limit_bytes,
|
limit_bytes,
|
||||||
} => exec_set_subvolume_quota(agent_name, limit_bytes).await,
|
} => {
|
||||||
|
validate_agent_name(agent_name)?;
|
||||||
|
set_subvolume_quota(agent_name, limit_bytes).await
|
||||||
|
}
|
||||||
|
|
||||||
PrivRequest::UpgradeAgentSubvolume { ref agent_name } => {
|
PrivRequest::UpgradeAgentSubvolume { ref agent_name } => {
|
||||||
exec_upgrade_agent_subvolume(agent_name).await
|
validate_agent_name(agent_name)?;
|
||||||
|
upgrade_agent_subvolume(agent_name).await
|
||||||
}
|
}
|
||||||
|
|
||||||
PrivRequest::SnapshotAgentSubvolume {
|
PrivRequest::SnapshotAgentSubvolume {
|
||||||
ref agent_name,
|
ref agent_name,
|
||||||
ref snapshot_name,
|
ref snapshot_name,
|
||||||
} => exec_snapshot_agent_subvolume(agent_name, snapshot_name).await,
|
} => {
|
||||||
|
validate_agent_name(agent_name)?;
|
||||||
|
validate_snapshot_name(snapshot_name)?;
|
||||||
|
snapshot_agent_subvolume(agent_name, snapshot_name).await
|
||||||
|
}
|
||||||
|
|
||||||
PrivRequest::DeleteAgentSnapshot {
|
PrivRequest::DeleteAgentSnapshot {
|
||||||
ref agent_name,
|
ref agent_name,
|
||||||
ref snapshot_name,
|
ref snapshot_name,
|
||||||
} => exec_delete_agent_snapshot(agent_name, snapshot_name).await,
|
} => {
|
||||||
|
validate_agent_name(agent_name)?;
|
||||||
|
validate_snapshot_name(snapshot_name)?;
|
||||||
|
delete_agent_snapshot(agent_name, snapshot_name).await
|
||||||
|
}
|
||||||
|
|
||||||
PrivRequest::SendAgentSnapshotToFile {
|
PrivRequest::SendAgentSnapshotToFile {
|
||||||
ref agent_name,
|
ref agent_name,
|
||||||
|
|
@ -477,7 +583,13 @@ async fn exec(
|
||||||
ref parent_snapshot_name,
|
ref parent_snapshot_name,
|
||||||
ref dest_file_name,
|
ref dest_file_name,
|
||||||
} => {
|
} => {
|
||||||
exec_send_agent_snapshot_to_file(
|
validate_agent_name(agent_name)?;
|
||||||
|
validate_snapshot_name(snapshot_name)?;
|
||||||
|
if let Some(parent) = parent_snapshot_name {
|
||||||
|
validate_snapshot_name(parent)?;
|
||||||
|
}
|
||||||
|
validate_credential_name(dest_file_name)?;
|
||||||
|
send_agent_snapshot_to_file(
|
||||||
agent_name,
|
agent_name,
|
||||||
snapshot_name,
|
snapshot_name,
|
||||||
parent_snapshot_name.as_deref(),
|
parent_snapshot_name.as_deref(),
|
||||||
|
|
@ -491,243 +603,23 @@ async fn exec(
|
||||||
ref snapshot_name,
|
ref snapshot_name,
|
||||||
ref parent_snapshot_name,
|
ref parent_snapshot_name,
|
||||||
} => {
|
} => {
|
||||||
exec_send_agent_snapshot_to_fd(
|
|
||||||
agent_name,
|
|
||||||
snapshot_name,
|
|
||||||
parent_snapshot_name.as_deref(),
|
|
||||||
fd,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
PrivRequest::SyncAgentTmpfiles { ref agents } => sync_agent_tmpfiles(agents).await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `StartContainer`: clear any start-limit lockout left by earlier failures
|
|
||||||
/// so a now-correct start isn't blocked (`nixos-container start` does not do
|
|
||||||
/// this itself). Best-effort — if the unit doesn't exist yet (first-time
|
|
||||||
/// create), `reset-failed` is a no-op and the start proceeds regardless.
|
|
||||||
async fn start_container(name: &str) -> Result<(String, String)> {
|
|
||||||
validate_container_name(name)?;
|
|
||||||
let machine = container_system_name(name);
|
|
||||||
let _ = Command::new("systemctl")
|
|
||||||
.args(["reset-failed", &format!("container@{machine}.service")])
|
|
||||||
.status()
|
|
||||||
.await;
|
|
||||||
container_run(&["start", &machine]).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `RunForgeAdmin`: every arg must pass [`validate_forge_admin_arg`] before
|
|
||||||
/// the admin CLI ever sees it.
|
|
||||||
async fn exec_forge_admin(args: &[String]) -> Result<(String, String)> {
|
|
||||||
for arg in args {
|
|
||||||
validate_forge_admin_arg(arg)?;
|
|
||||||
}
|
|
||||||
run_forge_admin(args).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `WriteAgentMatrixToken`: `account = None` writes the hive account's
|
|
||||||
/// `matrix-token`; `Some(a)` writes `matrix-token-<a>`. The account suffix
|
|
||||||
/// MUST be validated as a plain identifier (no `/`, `.`, `..`) before it goes
|
|
||||||
/// into the filename, or a crafted account could traverse out of the state
|
|
||||||
/// dir — `write_agent_state_file` trusts its `filename` argument. When both
|
|
||||||
/// `account` and `homeserver` are `Some`, also persists a
|
|
||||||
/// `matrix-account-<a>.json` sidecar so the daemon can auto-discover the
|
|
||||||
/// extra account without a static `matrixAccounts` config entry.
|
|
||||||
fn write_matrix_token(
|
|
||||||
agent_name: &str,
|
|
||||||
token: &str,
|
|
||||||
account: Option<&str>,
|
|
||||||
homeserver: Option<&str>,
|
|
||||||
) -> Result<(String, String)> {
|
|
||||||
validate_agent_name(agent_name)?;
|
|
||||||
let filename = match account {
|
|
||||||
None => "matrix-token".to_owned(),
|
|
||||||
Some(a) => {
|
|
||||||
validate_name_chars(a)?;
|
|
||||||
format!("matrix-token-{a}")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let res = write_agent_state_file(agent_name, &filename, &format!("{token}\n"))?;
|
|
||||||
if let (Some(a), Some(hs)) = (account, homeserver) {
|
|
||||||
let meta = serde_json::to_string(&MatrixAccountSidecar { homeserver: hs })
|
|
||||||
.context("serialize matrix account sidecar")?;
|
|
||||||
write_agent_state_file(agent_name, &format!("matrix-account-{a}.json"), &meta)?;
|
|
||||||
}
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `WriteAgentExtraForgeAccount`: writes the token, then a
|
|
||||||
/// `forge-<label>.json` sidecar carrying the base URL — there's no
|
|
||||||
/// host-side nix config for extra forges, so this is the only place it's
|
|
||||||
/// persisted.
|
|
||||||
fn write_extra_forge_account(
|
|
||||||
agent_name: &str,
|
|
||||||
label: &str,
|
|
||||||
base_url: &str,
|
|
||||||
token: &str,
|
|
||||||
) -> Result<(String, String)> {
|
|
||||||
validate_agent_name(agent_name)?;
|
|
||||||
validate_name_chars(label)?;
|
|
||||||
let res = write_agent_state_file(
|
|
||||||
agent_name,
|
|
||||||
&format!("forge-{label}-token"),
|
|
||||||
&format!("{token}\n"),
|
|
||||||
)?;
|
|
||||||
let meta = serde_json::to_string(&ForgeSidecar { base_url })
|
|
||||||
.context("serialize forge account sidecar")?;
|
|
||||||
write_agent_state_file(agent_name, &format!("forge-{label}.json"), &meta)?;
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `StopContainer`.
|
|
||||||
async fn stop_container(name: &str) -> Result<(String, String)> {
|
|
||||||
validate_container_name(name)?;
|
|
||||||
stop_and_release(&container_system_name(name)).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `KillContainer`: `nixos-container` has no kill verb, so this uses
|
|
||||||
/// `machinectl` to send `SIGKILL` to every process in the container — the
|
|
||||||
/// right semantics for a forced shutdown after a graceful stop has already
|
|
||||||
/// been attempted.
|
|
||||||
async fn kill_container(name: &str) -> Result<(String, String)> {
|
|
||||||
validate_container_name(name)?;
|
|
||||||
let machine = container_system_name(name);
|
|
||||||
machinectl_run(&["kill", &machine, "--signal=SIGKILL"]).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `DestroyContainer`.
|
|
||||||
async fn destroy_container(name: &str) -> Result<(String, String)> {
|
|
||||||
validate_container_name(name)?;
|
|
||||||
container_run(&["destroy", &container_system_name(name)]).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `ReadContainerJournal`.
|
|
||||||
async fn exec_read_container_journal(
|
|
||||||
container: &str,
|
|
||||||
query: &JournalQuery,
|
|
||||||
) -> Result<(String, String)> {
|
|
||||||
validate_container_system_name(container)?;
|
|
||||||
read_container_journal(container, query).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `SetAgentPaused`.
|
|
||||||
fn exec_set_agent_paused(agent_name: &str, paused: bool) -> Result<(String, String)> {
|
|
||||||
validate_agent_name(agent_name)?;
|
|
||||||
set_agent_paused(agent_name, paused)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `WriteAgentForgeToken`.
|
|
||||||
fn write_forge_token(agent_name: &str, token: &str) -> Result<(String, String)> {
|
|
||||||
validate_agent_name(agent_name)?;
|
|
||||||
write_agent_state_file(agent_name, "forge-token", &format!("{token}\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `WriteAgentGithubToken`.
|
|
||||||
fn write_github_token(agent_name: &str, token: &str) -> Result<(String, String)> {
|
|
||||||
validate_agent_name(agent_name)?;
|
|
||||||
write_agent_state_file(agent_name, "github-token", &format!("{token}\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `DeleteAgentExtraForgeAccount`. Missing files are not an error
|
|
||||||
/// (idempotent revoke).
|
|
||||||
fn delete_extra_forge_account(agent_name: &str, label: &str) -> Result<(String, String)> {
|
|
||||||
validate_agent_name(agent_name)?;
|
|
||||||
validate_name_chars(label)?;
|
|
||||||
delete_agent_state_file(agent_name, &format!("forge-{label}-token"))?;
|
|
||||||
delete_agent_state_file(agent_name, &format!("forge-{label}.json"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `EnsureAgentSubvolume`.
|
|
||||||
async fn exec_ensure_agent_subvolume(agent_name: &str) -> Result<(String, String)> {
|
|
||||||
validate_agent_name(agent_name)?;
|
|
||||||
ensure_agent_subvolume(agent_name).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `DeleteAgentSubvolume`.
|
|
||||||
async fn exec_delete_agent_subvolume(agent_name: &str) -> Result<(String, String)> {
|
|
||||||
validate_agent_name(agent_name)?;
|
|
||||||
delete_agent_subvolume(agent_name).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `ReadSubvolumeUsage`.
|
|
||||||
async fn exec_read_subvolume_usage(agent_name: &str) -> Result<(String, String)> {
|
|
||||||
validate_agent_name(agent_name)?;
|
|
||||||
read_subvolume_usage(agent_name).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `SetSubvolumeQuota`.
|
|
||||||
async fn exec_set_subvolume_quota(
|
|
||||||
agent_name: &str,
|
|
||||||
limit_bytes: Option<u64>,
|
|
||||||
) -> Result<(String, String)> {
|
|
||||||
validate_agent_name(agent_name)?;
|
|
||||||
set_subvolume_quota(agent_name, limit_bytes).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `UpgradeAgentSubvolume`.
|
|
||||||
async fn exec_upgrade_agent_subvolume(agent_name: &str) -> Result<(String, String)> {
|
|
||||||
validate_agent_name(agent_name)?;
|
|
||||||
upgrade_agent_subvolume(agent_name).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `SnapshotAgentSubvolume`.
|
|
||||||
async fn exec_snapshot_agent_subvolume(
|
|
||||||
agent_name: &str,
|
|
||||||
snapshot_name: &str,
|
|
||||||
) -> Result<(String, String)> {
|
|
||||||
validate_agent_name(agent_name)?;
|
|
||||||
validate_snapshot_name(snapshot_name)?;
|
|
||||||
snapshot_agent_subvolume(agent_name, snapshot_name).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `DeleteAgentSnapshot`.
|
|
||||||
async fn exec_delete_agent_snapshot(
|
|
||||||
agent_name: &str,
|
|
||||||
snapshot_name: &str,
|
|
||||||
) -> Result<(String, String)> {
|
|
||||||
validate_agent_name(agent_name)?;
|
|
||||||
validate_snapshot_name(snapshot_name)?;
|
|
||||||
delete_agent_snapshot(agent_name, snapshot_name).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `SendAgentSnapshotToFile`.
|
|
||||||
async fn exec_send_agent_snapshot_to_file(
|
|
||||||
agent_name: &str,
|
|
||||||
snapshot_name: &str,
|
|
||||||
parent_snapshot_name: Option<&str>,
|
|
||||||
dest_file_name: &str,
|
|
||||||
) -> Result<(String, String)> {
|
|
||||||
validate_agent_name(agent_name)?;
|
|
||||||
validate_snapshot_name(snapshot_name)?;
|
|
||||||
if let Some(parent) = parent_snapshot_name {
|
|
||||||
validate_snapshot_name(parent)?;
|
|
||||||
}
|
|
||||||
validate_credential_name(dest_file_name)?;
|
|
||||||
send_agent_snapshot_to_file(
|
|
||||||
agent_name,
|
|
||||||
snapshot_name,
|
|
||||||
parent_snapshot_name,
|
|
||||||
dest_file_name,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `SendAgentSnapshotToFd`.
|
|
||||||
async fn exec_send_agent_snapshot_to_fd(
|
|
||||||
agent_name: &str,
|
|
||||||
snapshot_name: &str,
|
|
||||||
parent_snapshot_name: Option<&str>,
|
|
||||||
fd: Option<OwnedFd>,
|
|
||||||
) -> Result<(String, String)> {
|
|
||||||
validate_agent_name(agent_name)?;
|
validate_agent_name(agent_name)?;
|
||||||
validate_snapshot_name(snapshot_name)?;
|
validate_snapshot_name(snapshot_name)?;
|
||||||
if let Some(parent) = parent_snapshot_name {
|
if let Some(parent) = parent_snapshot_name {
|
||||||
validate_snapshot_name(parent)?;
|
validate_snapshot_name(parent)?;
|
||||||
}
|
}
|
||||||
let dest = fd.context("no descriptor to stream into")?;
|
let dest = fd.context("no descriptor to stream into")?;
|
||||||
send_agent_snapshot_to_fd(agent_name, snapshot_name, parent_snapshot_name, dest).await
|
send_agent_snapshot_to_fd(
|
||||||
|
agent_name,
|
||||||
|
snapshot_name,
|
||||||
|
parent_snapshot_name.as_deref(),
|
||||||
|
dest,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
PrivRequest::SyncAgentTmpfiles { ref agents } => sync_agent_tmpfiles(agents).await,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shared body for `CreateContainer` / `UpdateContainer`: validate the
|
/// Shared body for `CreateContainer` / `UpdateContainer`: validate the
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue