use clap::Parser;
use std::path::{Path, PathBuf};
use std::process::Command;
fn s3_cp(
local: &Path,
dest: &str,
endpoint_url: Option<&str>,
profile: Option<&str>,
) -> anyhow::Result<()> {
eprintln!("Uploading to {}...", dest);
let mut cmd = Command::new("aws");
cmd.args(["s3", "cp"]);
cmd.arg(local.to_str().unwrap());
cmd.arg(dest);
if let Some(endpoint) = endpoint_url {
cmd.args(["--endpoint-url", endpoint]);
}
if let Some(profile) = profile {
cmd.args(["--profile", profile]);
}
let status = cmd.status().map_err(|e| {
anyhow::anyhow!(
"failed to run `aws` CLI: {} — install from https://aws.amazon.com/cli/",
e
)
})?;
if !status.success() {
anyhow::bail!("aws s3 cp failed with exit code {}", status);
}
Ok(())
}
#[derive(Parser)]
#[command(about = "Build a checkpoint archive from a synced spaced data directory")]
struct Args {
#[arg(long)]
data_dir: PathBuf,
#[arg(long, default_value = ".")]
output_dir: PathBuf,
#[arg(long)]
upload: Option<String>,
#[arg(long, requires = "upload")]
endpoint_url: Option<String>,
#[arg(long, requires = "upload")]
profile: Option<String>,
#[arg(long)]
integrity_out: Option<PathBuf>,
}
fn main() -> anyhow::Result<()> {
let args = Args::parse();
let (block_hash, height) = spaces_checkpoint::read_tip(&args.data_dir)?;
eprintln!("Tip: height={} hash={}", height, block_hash);
let filename = format!("checkpoint-{}.tar.gz", height);
let output = args.output_dir.join(&filename);
eprintln!("Creating {}...", output.display());
for name in spaces_checkpoint::CHECKPOINT_FILES {
eprintln!(" {}", name);
}
let digest = spaces_checkpoint::build_checkpoint(&args.data_dir, &output)?;
eprintln!(
"\nArchive: {} ({} bytes)",
output.display(),
std::fs::metadata(&output)?.len()
);
eprintln!("SHA-256: {}", hex::encode(digest));
if let Some(path) = args.integrity_out.as_deref() {
spaces_checkpoint::write_integrity_to(path, height, &block_hash, &digest)?;
eprintln!("Updated {}", path.display());
}
if let Some(bucket) = &args.upload {
let bucket = bucket.trim_end_matches('/');
s3_cp(
&output,
&format!("{}/{}", bucket, filename),
args.endpoint_url.as_deref(),
args.profile.as_deref(),
)?;
let latest = spaces_checkpoint::Checkpoint {
height,
block_hash: block_hash.clone(),
digest: hex::encode(digest),
};
let latest_path = args.output_dir.join("latest.json");
std::fs::write(&latest_path, serde_json::to_string_pretty(&latest)?)?;
s3_cp(
&latest_path,
&format!("{}/latest.json", bucket),
args.endpoint_url.as_deref(),
args.profile.as_deref(),
)?;
eprintln!("Upload complete.");
}
Ok(())
}