Skip to main content

systemprompt_cli/commands/cloud/backup/
mod.rs

1//! `cloud backup` command: download the tenant's runtime `services/` tree.
2//!
3//! Deploys are stateless container rebuilds — runtime files created inside
4//! the live container (uploads, AI-generated images) do not survive them.
5//! This command downloads that tree as a one-shot backup into a standalone
6//! directory. It never writes into the project's own `services/` directory
7//! and is deliberately not part of the deploy flow.
8//!
9//! Copyright (c) systemprompt.io — Business Source License 1.1.
10//! See <https://systemprompt.io> for licensing details.
11
12mod client;
13mod extract;
14
15use std::path::PathBuf;
16
17use anyhow::{Result, anyhow, bail};
18use systemprompt_logging::CliService;
19
20use client::BackupClient;
21use extract::extract_tarball;
22
23use super::deploy::{resolve_deploy_target, resolve_profile};
24use crate::cli_settings::CliConfig;
25use crate::interactive::Prompter;
26
27pub(super) struct BackupArgs {
28    pub profile_name: Option<String>,
29    pub output: Option<PathBuf>,
30    pub list: bool,
31}
32
33pub(super) async fn execute(
34    args: BackupArgs,
35    prompter: &dyn Prompter,
36    config: &CliConfig,
37) -> Result<()> {
38    CliService::section("systemprompt.io Cloud Backup");
39
40    let (profile, _profile_path) = resolve_profile(prompter, args.profile_name.as_deref(), config)?;
41    if profile.target != systemprompt_models::ProfileType::Cloud {
42        bail!("Cannot back up a local profile. Select a cloud profile with --profile <name>.");
43    }
44
45    let target = resolve_deploy_target(&profile)?;
46    let hostname = target.hostname.ok_or_else(|| {
47        anyhow!(
48            "Tenant {} has no hostname. Run 'systemprompt cloud login' to refresh tenants.",
49            target.tenant_id
50        )
51    })?;
52
53    let spinner = CliService::spinner("Authenticating with tenant deployment...");
54    let client = BackupClient::connect(&hostname, target.creds.api_token.as_str()).await?;
55    spinner.finish_and_clear();
56
57    if args.list {
58        let manifest = client.fetch_manifest().await?;
59        CliService::info(&format!("{} files on {}", manifest.files.len(), hostname));
60        for file in &manifest.files {
61            CliService::info(&format!("  {} ({} bytes)", file.path, file.size));
62        }
63        return Ok(());
64    }
65
66    let output = args.output.unwrap_or_else(|| {
67        PathBuf::from(format!(
68            "systemprompt-backup-{}",
69            chrono::Utc::now().format("%Y%m%d-%H%M%S")
70        ))
71    });
72    std::fs::create_dir_all(&output)?;
73
74    let spinner = CliService::spinner("Downloading services tree...");
75    let bundle = client.download_bundle().await?;
76    spinner.finish_and_clear();
77
78    let count = extract_tarball(&bundle, &output)?;
79    CliService::success(&format!(
80        "Backed up {} files from {} to {}",
81        count,
82        hostname,
83        output.display()
84    ));
85
86    Ok(())
87}