Skip to main content

max / makenotwork

2.3 KB · 74 lines History Blame Raw
1 //! Fetch the prod backup that `migration_dry_run` runs against.
2 //!
3 //! Sources supported in v0:
4 //! - `file:///abs/path/to/dump.sql.gz` — local copy. Used for localhost dev.
5 //! - `rsync://host/module/path` — shells out to `rsync`. Used when MM
6 //! pulls from an astra/Hetzner replica.
7 //!
8 //! The fetch is command-driven: the operator triggers it via /backup/fetch, it
9 //! is not implicit in promote. That keeps the slowest, most failure-prone step
10 //! visible in the TUI rather than buried inside a deploy.
11
12 use crate::config::Config;
13 use crate::topology::Topology;
14 use anyhow::{Context, Result};
15 use chrono::Utc;
16 use sqlx::SqlitePool;
17 use std::path::Path;
18 use std::sync::Arc;
19 use tokio::process::Command;
20
21 #[derive(Debug, Clone)]
22 pub struct FetchedBackup {
23 pub source: String,
24 pub local_path: String,
25 pub byte_size: Option<i64>,
26 }
27
28 pub async fn fetch(
29 pool: &SqlitePool,
30 _cfg: &Arc<Config>,
31 topo: &Arc<Topology>,
32 ) -> Result<FetchedBackup> {
33 let source = topo.backup.source.clone();
34 let local_path = topo.backup.local_path.clone();
35
36 if let Some(parent) = Path::new(&local_path).parent() {
37 tokio::fs::create_dir_all(parent).await?;
38 }
39
40 if let Some(rest) = source.strip_prefix("file://") {
41 tokio::fs::copy(rest, &local_path)
42 .await
43 .with_context(|| format!("copy {rest} -> {local_path}"))?;
44 } else if source.starts_with("rsync://") {
45 let out = Command::new("rsync")
46 .args(["-az", "--inplace", &source, &local_path])
47 .output()
48 .await
49 .context("spawning rsync")?;
50 anyhow::ensure!(
51 out.status.success(),
52 "rsync failed: {}",
53 String::from_utf8_lossy(&out.stderr),
54 );
55 } else {
56 anyhow::bail!("unsupported backup source scheme: {source}");
57 }
58
59 let meta = tokio::fs::metadata(&local_path).await?;
60 let size = meta.len() as i64;
61
62 sqlx::query(
63 "INSERT INTO backups (fetched_at, source, local_path, byte_size) VALUES (?, ?, ?, ?)",
64 )
65 .bind(Utc::now().to_rfc3339())
66 .bind(&source)
67 .bind(&local_path)
68 .bind(size)
69 .execute(pool)
70 .await?;
71
72 Ok(FetchedBackup { source, local_path, byte_size: Some(size) })
73 }
74