feat: add backup restore workflow

This commit is contained in:
Eric Wendland 2026-07-05 22:35:18 +02:00
commit 619e8eee62
7 changed files with 498 additions and 6 deletions

View file

@ -368,6 +368,10 @@ pub enum Command {
#[command(subcommand)]
command: WaitCommand,
},
Backup {
#[command(subcommand)]
command: BackupCommand,
},
Node {
#[command(subcommand)]
command: NodeCommand,
@ -511,6 +515,19 @@ pub enum WaitCommand {
},
}
#[derive(Debug, Subcommand)]
pub enum BackupCommand {
Create {
#[arg(long, value_name = "DIR")]
out: PathBuf,
},
Restore {
backup_dir: PathBuf,
#[arg(long, value_name = "DIR")]
target_home: PathBuf,
},
}
#[derive(Debug, Subcommand)]
pub enum NodeCommand {
Id,
@ -1320,6 +1337,10 @@ async fn run_inner(cli: Cli) -> Result<()> {
std::process::exit(1);
}
}
Command::Backup { command } => {
run_backup_command(&paths, command, cli.json || cli.jsonl)
.context("run backup command")?;
}
Command::Ssh {
command:
SshCommand::Proxy {
@ -2219,6 +2240,7 @@ fn request_for_command(command: Command) -> Result<ControlRequest> {
Command::Guide { .. }
| Command::Init { .. }
| Command::Daemon { .. }
| Command::Backup { .. }
| Command::Wait { .. } => {
bail!("command is handled directly")
}
@ -2234,6 +2256,66 @@ struct WaitReport {
reason: String,
}
fn run_backup_command(paths: &GethPaths, command: BackupCommand, json: bool) -> Result<()> {
match command {
BackupCommand::Create { out } => {
let report = geth_node::backup::create_backup(paths, &out)?;
if json {
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"type": "backup-created",
"backup_dir": report.backup_dir,
"files_copied": report.files_copied,
"bytes_copied": report.bytes_copied,
"manifest": report.manifest,
}))?
);
} else {
println!("backup: {}", report.backup_dir.display());
println!("files_copied: {}", report.files_copied);
println!("bytes_copied: {}", report.bytes_copied);
println!(
"manifest: {}",
report.backup_dir.join("manifest.json").display()
);
println!(
"note: private geth identity keys and private SSH admin keys are not copied"
);
}
}
BackupCommand::Restore {
backup_dir,
target_home,
} => {
let report = geth_node::backup::restore_backup(&backup_dir, &target_home)?;
if json {
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"type": "backup-restored",
"backup_dir": report.backup_dir,
"target_home": report.target_home,
"files_restored": report.files_restored,
"bytes_restored": report.bytes_restored,
"manifest": report.manifest,
}))?
);
} else {
println!("restored: {}", report.target_home.display());
println!("backup: {}", report.backup_dir.display());
println!("files_restored: {}", report.files_restored);
println!("bytes_restored: {}", report.bytes_restored);
println!(
"note: validate with GETH_HOME={} geth status after starting a daemon for the restored home",
report.target_home.display()
);
}
}
}
Ok(())
}
async fn run_wait_command(paths: &GethPaths, command: WaitCommand) -> Result<WaitReport> {
match command {
WaitCommand::Daemon {