systemprompt_cli/commands/cloud/backup/
mod.rs1mod client;
13
14use std::path::PathBuf;
15
16use anyhow::{Result, anyhow, bail};
17use systemprompt_identifiers::ProfileName;
18use systemprompt_loader::bundle::{ExtractOptions, TarLayout, extract_bytes};
19use systemprompt_logging::CliService;
20
21use client::BackupClient;
22
23use super::deploy::{resolve_deploy_target, resolve_profile};
24use crate::cli_settings::CliConfig;
25use crate::interactive::Prompter;
26
27const BACKUP_DIRS: &[&str] = &[
28 "agents", "skills", "content", "mcp", "ai", "config", "profiles",
29];
30
31const MAX_BACKUP_BYTES: u64 = 2 * 1024 * 1024 * 1024;
32
33pub(super) struct BackupArgs {
34 pub profile_name: Option<ProfileName>,
35 pub output: Option<PathBuf>,
36 pub list: bool,
37}
38
39pub(super) async fn execute(
40 args: BackupArgs,
41 prompter: &dyn Prompter,
42 config: &CliConfig,
43) -> Result<()> {
44 CliService::section("systemprompt.io Cloud Backup");
45
46 let (profile, _profile_path) = resolve_profile(prompter, args.profile_name.as_ref(), config)?;
47 if profile.target != systemprompt_manifest::ProfileType::Cloud {
48 bail!("Cannot back up a local profile. Select a cloud profile with --profile <name>.");
49 }
50
51 let target = resolve_deploy_target(&profile)?;
52 let hostname = target.hostname.ok_or_else(|| {
53 anyhow!(
54 "Tenant {} has no hostname. Run 'systemprompt cloud auth login' to refresh tenants.",
55 target.tenant_id
56 )
57 })?;
58
59 let spinner = CliService::spinner("Authenticating with tenant deployment...");
60 let client = BackupClient::connect(&hostname, target.creds.api_token.as_str()).await?;
61 spinner.finish_and_clear();
62
63 if args.list {
64 let manifest = client.fetch_manifest().await?;
65 CliService::info(&format!("{} files on {}", manifest.files.len(), hostname));
66 for file in &manifest.files {
67 CliService::info(&format!(" {} ({} bytes)", file.path, file.size));
68 }
69 return Ok(());
70 }
71
72 let output = args.output.unwrap_or_else(|| {
73 PathBuf::from(format!(
74 "systemprompt-backup-{}",
75 chrono::Utc::now().format("%Y%m%d-%H%M%S")
76 ))
77 });
78 std::fs::create_dir_all(&output)?;
79
80 let spinner = CliService::spinner("Downloading services tree...");
81 let bundle = client.download_bundle().await?;
82 spinner.finish_and_clear();
83
84 let count = extract_bytes(
85 &bundle,
86 &output,
87 &ExtractOptions {
88 allowed_dirs: BACKUP_DIRS,
89 max_bytes: MAX_BACKUP_BYTES,
90 layout: TarLayout::Root,
91 },
92 )?
93 .len();
94 CliService::success(&format!(
95 "Backed up {} files from {} to {}",
96 count,
97 hostname,
98 output.display()
99 ));
100
101 Ok(())
102}