use std::path::PathBuf;
use bestool_canopy::{TargetOutcome, schema::BackupPurpose};
use bestool_kopia::{
RunAs, S3KopiaEnv, build_kopia_command_with_s3, find_kopia_binary,
};
use clap::{Parser, ValueEnum};
use miette::{Context as _, IntoDiagnostic as _, Result, bail, miette};
use super::backup::{base_url_of, build_client, connect_repo, load_registration, spawn_proxy};
use crate::actions::Context;
#[derive(Debug, Clone, Parser)]
pub struct KopiaArgs {
#[arg(long = "type", value_name = "TYPE")]
pub backup_type: String,
#[arg(long, value_enum, default_value_t = Purpose::Restore)]
pub purpose: Purpose,
#[arg(long, value_name = "DIR")]
pub config: Option<PathBuf>,
#[arg(
trailing_var_arg = true,
allow_hyphen_values = true,
value_name = "KOPIA_ARGS"
)]
pub args: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum Purpose {
Restore,
Backup,
}
impl From<Purpose> for BackupPurpose {
fn from(purpose: Purpose) -> Self {
match purpose {
Purpose::Restore => BackupPurpose::Restore,
Purpose::Backup => BackupPurpose::Backup,
}
}
}
pub async fn run(args: KopiaArgs, _ctx: Context) -> Result<()> {
if args.args.is_empty() {
bail!("no kopia command given; pass the kopia arguments after `--`");
}
let reg = load_registration(args.config.as_deref())
.await?
.ok_or_else(|| miette!("not registered with canopy; run `bestool canopy register` first"))?;
let device_key = reg
.device_key
.clone()
.ok_or_else(|| miette!("registration has no device key"))?;
let server_id = reg
.server_id
.clone()
.ok_or_else(|| miette!("registration has no server id"))?;
let client = build_client(base_url_of(®)?, &device_key).await?;
let target = match TargetOutcome::from_result(client.backup_target().await)? {
TargetOutcome::Ready(target) => target,
TargetOutcome::Dormant => {
bail!("device is not authorised for this backup repository")
}
};
let proxy = spawn_proxy(
client.clone(),
args.backup_type.clone(),
args.purpose.into(),
&target.region,
)
.await?;
let config_dir = tempfile::tempdir()
.into_diagnostic()
.wrap_err("creating transient kopia config dir")?;
let config_path = config_dir.path().join("repository.config");
let s3env = S3KopiaEnv {
password: &target.repo_password.0,
config_path: &config_path,
};
let kopia = find_kopia_binary(None).ok_or_else(|| miette!("could not find the kopia binary"))?;
connect_repo(
&kopia,
&s3env,
&target,
&proxy.endpoint(),
&server_id,
RunAs::CurrentUser,
)
.await?;
let mut cmd = build_kopia_command_with_s3(&kopia, &s3env, RunAs::CurrentUser)
.map_err(|e| miette!("{e}"))?;
cmd.args(&args.args);
let status = tokio::process::Command::from(cmd)
.status()
.await
.into_diagnostic()
.wrap_err("running kopia")?;
if !status.success() {
bail!("kopia exited with {status}");
}
Ok(())
}