# Backup Module Integration Guide This guide covers integrating the backup module with Provctl and the Provisioning system. ## Table of Contents 1. [Standalone Usage](#standalone-usage) 2. [Provctl Integration](#provctl-integration) 3. [Provisioning System Integration](#provisioning-system-integration) 4. [KCL Configuration Examples](#kcl-configuration-examples) 5. [Automation Workflows](#automation-workflows) ## Standalone Usage ### As a CLI Tool Create a backup script that uses the backup library: ```rust // bin/backup-tool/main.rs use backup::config::BackupConfig; use backup::backends::{ResticBackend, BorgBackend, TarBackend, CpioBackend, RsyncBackend}; use backup::backend::BackupBackend; use backup::backend_type::BackendType; use std::path::Path; use clap::{Parser, Subcommand}; #[derive(Parser)] struct Args { #[command(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { Backup { #[arg(short, long)] config: String, }, Restore { #[arg(short, long)] config: String, #[arg(short, long)] snapshot: String, #[arg(short, long)] target: String, }, List { #[arg(short, long)] config: String, }, } #[tokio::main] async fn main() -> Result<(), Box> { let args = Args::parse(); match args.command { Commands::Backup { config } => { let config = BackupConfig::from_toml(Path::new(&config))?; config.validate()?; let backend = create_backend(&config.backend)?; for target in config.targets { println!("Backing up: {}", target.name); let report = backend.backup(&target).await?; println!("Complete: {} files", report.files_processed); } } Commands::List { config } => { let config = BackupConfig::from_toml(Path::new(&config))?; let backend = create_backend(&config.backend)?; let snapshots = backend.list_snapshots().await?; for snapshot in snapshots { println!("{}: {} bytes", snapshot.id, snapshot.size); } } Commands::Restore { config, snapshot, target } => { let config = BackupConfig::from_toml(Path::new(&config))?; let backend = create_backend(&config.backend)?; backend.restore(&snapshot, Path::new(&target), &Default::default()).await?; println!("Restore complete"); } } Ok(()) } fn create_backend(backend_type: &BackendType) -> Result, Box> { match backend_type { BackendType::Restic => Ok(Box::new(ResticBackend::new(None))), BackendType::Borg => Ok(Box::new(BorgBackend::new(None))), BackendType::Tar => Ok(Box::new(TarBackend::new(None))), BackendType::Cpio => Ok(Box::new(CpioBackend::new(None))), BackendType::Rsync => Ok(Box::new(RsyncBackend::new(None))), } } ``` ### As a Library in Your App ```rust use backup::config::BackupConfig; use backup::backends::ResticBackend; use backup::backend::BackupBackend; pub async fn create_daily_backup(config_path: &str) -> Result<(), Box> { let config = BackupConfig::from_toml(std::path::Path::new(config_path))?; config.validate()?; let backend = ResticBackend::new(None); // Initialize repository if needed if !backend.is_available().await? { eprintln!("Restic not available"); return Ok(()); } backend.init_repository(&config.repository).await?; // Execute backups for target in &config.targets { backend.backup(target).await?; } // Apply retention policy backend.prune(&config.retention).await?; Ok(()) } ``` ## Provctl Integration ### Adding Backup Commands to Provctl Add to `crates/provctl/src/commands/backup.rs`: ```rust use backup::config::BackupConfig; use backup::backends::ResticBackend; use backup::backend::BackupBackend; use clap::Args; #[derive(Args)] pub struct BackupArgs { #[command(subcommand)] pub command: BackupCommand, } #[derive(clap::Subcommand)] pub enum BackupCommand { Init { #[arg(short, long)] config: String, #[arg(short, long)] backend: Option, }, Run { #[arg(short, long)] config: String, }, Status { #[arg(short, long)] config: String, }, Check { #[arg(short, long)] config: String, }, } pub async fn handle_backup(args: BackupArgs) -> Result<(), Box> { match args.command { BackupCommand::Init { config, backend } => { let config = BackupConfig::from_toml(std::path::Path::new(&config))?; config.validate()?; let backend = ResticBackend::new(None); backend.init_repository(&config.repository).await?; println!("Repository initialized"); } BackupCommand::Run { config } => { let config = BackupConfig::from_toml(std::path::Path::new(&config))?; let backend = ResticBackend::new(None); for target in config.targets { let report = backend.backup(&target).await?; println!("✓ {} backed up", target.name); } } BackupCommand::Status { config } => { let config = BackupConfig::from_toml(std::path::Path::new(&config))?; let backend = ResticBackend::new(None); let stats = backend.stats().await?; println!("Repository size: {} bytes", stats.total_size); println!("Snapshots: {}", stats.snapshot_count); } BackupCommand::Check { config } => { let config = BackupConfig::from_toml(std::path::Path::new(&config))?; let backend = ResticBackend::new(None); let check = backend.check().await?; if check.valid { println!("✓ Repository is valid"); } else { println!("✗ Repository has issues:"); for issue in check.issues { println!(" - {}", issue); } } } } Ok(()) } ``` Add to Provctl's main command handler: ```rust mod commands; use commands::backup; pub enum Command { Backup(backup::BackupArgs), // ... other commands } pub async fn handle_command(cmd: Command) -> Result<(), Box> { match cmd { Command::Backup(args) => backup::handle_backup(args).await, // ... other commands } } ``` ### Usage ```bash # Initialize backup repository provctl backup init --config backup.toml # Run a backup provctl backup run --config backup.toml # Check backup status provctl backup status --config backup.toml # Verify repository integrity provctl backup check --config backup.toml ``` ## Provisioning System Integration ### Using Backup in Provisioning Configs Create `provisioning/backup-deployment.kcl`: ```kcl import backup # Backup configuration for production deployment backup_config = backup.BackupConfig { version = "1.0" name = "prod-database" description = "Production database daily backups" backend = "restic" repository = { type = "s3" path = "s3://company-backups/prod-db" env_vars = { AWS_ACCESS_KEY_ID = env("AWS_ACCESS_KEY_ID") AWS_SECRET_ACCESS_KEY = env("AWS_SECRET_ACCESS_KEY") } password_file = "/etc/backup/db.pwd" } targets = [ { name = "postgresql" paths = ["/var/lib/postgresql"] excludes = ["pg_wal/*", "*.tmp"] tags = ["production", "daily"] pre_hooks = [ "systemctl stop postgresql", "pg_dumpall -U postgres > /tmp/pg_backup.sql" ] post_hooks = [ "rm /tmp/pg_backup.sql", "systemctl start postgresql" ] } ] retention = { keep_daily = 7 keep_weekly = 4 keep_monthly = 12 keep_yearly = 5 } schedule = { enabled = true cron = "0 2 * * *" init_system = "systemd" on_failure = "notify" } notifications = { enabled = true on_success = false on_failure = true command = "mail -s 'Backup alert' ops@company.com" } hooks = { on_error = [ "echo 'Backup failed at $(date)' | mail -s 'Critical: Backup failure' ops@company.com" ] } } # Output the configuration backup_config ``` ### Integration with Init Services Create `provisioning/backup-service.kcl`: ```kcl import init_system # Generate systemd service for backups backup_service = init_system.Service { name = "prod-backup" description = "Production backup service" type = "oneshot" exec_start = "/usr/local/bin/backup-tool --config /etc/backup/prod.toml" # Run as backup user user = "backup" group = "backup" # Restart policy restart = "on-failure" restart_sec = 300 } # Generate the timer backup_timer = init_system.Timer { name = "prod-backup" description = "Production backup timer" # Run at 2 AM daily on_calendar = "*-*-* 02:00:00" unit = "prod-backup.service" persistent = true } ``` ## KCL Configuration Examples ### Simple Local Backup ```kcl import backup config = backup.BackupConfig { version = "1.0" name = "local-backup" backend = "tar" repository = { type = "local" path = "/mnt/backups/local" } targets = [ { name = "home" paths = ["/home/user/documents", "/home/user/projects"] excludes = ["node_modules/*", ".git/*"] } ] retention = { keep_daily = 7 } schedule = { enabled = true cron = "0 3 * * *" init_system = "cron" } } ``` ### Cloud S3 Backup with Encryption ```kcl import backup config = backup.BackupConfig { version = "1.0" name = "cloud-backup" backend = "restic" repository = { type = "s3" path = "s3://my-bucket/backups" password_file = "/etc/backup/restic.pwd" env_vars = { AWS_ACCESS_KEY_ID = env("AWS_KEY") AWS_SECRET_ACCESS_KEY = env("AWS_SECRET") } } targets = [ { name = "app-data" paths = ["/var/lib/myapp", "/etc/myapp"] excludes = ["*.tmp", "*.log"] tags = ["production"] } ] retention = { keep_daily = 7 keep_weekly = 4 keep_monthly = 12 } schedule = { enabled = true cron = "0 */6 * * *" # Every 6 hours init_system = "systemd" on_failure = "notify" } } ``` ### Multi-Target SFTP Backup ```kcl import backup config = backup.BackupConfig { version = "1.0" name = "sftp-backup" backend = "rsync" repository = { type = "sftp" path = "sftp://backup.example.com/backups" env_vars = { RSYNC_RSH = "ssh -i /etc/backup/id_rsa" } } targets = [ { name = "web-content" paths = ["/var/www/html", "/var/www/uploads"] excludes = ["*.temp", "cache/*"] tags = ["web"] } { name = "configs" paths = ["/etc", "/opt/app"] excludes = ["/etc/ssl/private"] tags = ["system"] } ] retention = { keep_daily = 7 keep_weekly = 4 } schedule = { enabled = true cron = "30 1 * * *" init_system = "systemd" } } ``` ## Automation Workflows ### Database Backup Workflow ```rust use backup::config::BackupConfig; use backup::backends::ResticBackend; use backup::backend::BackupBackend; #[tokio::main] async fn backup_database() -> Result<(), Box> { // Pre-backup: dump database println!("Dumping database..."); std::process::Command::new("pg_dump") .arg("mydb") .arg("-f") .arg("/tmp/db_backup.sql") .output()?; // Run backup let config = BackupConfig::from_toml("backup.toml")?; let backend = ResticBackend::new(None); for target in config.targets { println!("Backing up: {}", target.name); let report = backend.backup(&target).await?; if !report.success { eprintln!("Backup failed!"); // Trigger error handlers for cmd in config.hooks.on_error { std::process::Command::new("sh") .arg("-c") .arg(&cmd) .output()?; } return Err("Backup failed".into()); } } // Post-backup: cleanup and prune println!("Cleaning up..."); std::fs::remove_file("/tmp/db_backup.sql")?; println!("Pruning old snapshots..."); backend.prune(&config.retention).await?; // Verify let check = backend.check().await?; if !check.valid { eprintln!("Repository check failed!"); } println!("✓ Backup complete"); Ok(()) } ``` ### Incremental Backup with Verification ```rust use backup::config::BackupConfig; use backup::backends::ResticBackend; use backup::backend::BackupBackend; #[tokio::main] async fn verified_backup() -> Result<(), Box> { let config = BackupConfig::from_toml("backup.toml")?; let backend = ResticBackend::new(None)?; // Step 1: Check repository before backup println!("Step 1: Checking repository..."); let pre_check = backend.check().await?; if !pre_check.valid { eprintln!("Repository has issues before backup!"); return Err("Pre-backup check failed".into()); } // Step 2: Run backups println!("Step 2: Running backups..."); for target in config.targets { let report = backend.backup(&target).await?; println!(" {} - {} files processed", target.name, report.files_processed); } // Step 3: Check repository after backup println!("Step 3: Post-backup verification..."); let post_check = backend.check().await?; if !post_check.valid { eprintln!("Repository has issues after backup!"); return Err("Post-backup check failed".into()); } // Step 4: Get and display stats println!("Step 4: Repository statistics"); let stats = backend.stats().await?; println!(" Total size: {} MB", stats.total_size / (1024 * 1024)); println!(" Snapshots: {}", stats.snapshot_count); println!("✓ Backup verified and complete"); Ok(()) } ``` ### Selective Restore ```rust use backup::config::BackupConfig; use backup::backends::ResticBackend; use backup::backend::BackupBackend; use backup::types::RestoreOptions; use std::path::Path; #[tokio::main] async fn selective_restore() -> Result<(), Box> { let config = BackupConfig::from_toml("backup.toml")?; let backend = ResticBackend::new(None)?; // List available snapshots println!("Available snapshots:"); let snapshots = backend.list_snapshots().await?; for (idx, snapshot) in snapshots.iter().enumerate() { println!(" [{}] {} - {} bytes", idx, snapshot.id, snapshot.size); } // Restore specific snapshot let restore_opts = RestoreOptions { overwrite: false, verify: true, restore_permissions: true, }; println!("\nRestoring latest snapshot..."); if let Some(latest) = snapshots.first() { backend.restore(&latest.id, Path::new("/restore"), &restore_opts).await?; println!("✓ Restore complete"); } Ok(()) } ``` ## Testing Integration Create integration tests in `crates/backup/tests/integration.rs`: ```rust use backup::config::BackupConfig; use backup::backends::TarBackend; use backup::backend::BackupBackend; use tempfile::TempDir; #[tokio::test] async fn test_tar_backup_and_restore() -> Result<(), Box> { let temp_repo = TempDir::new()?; let temp_restore = TempDir::new()?; let backend = TarBackend::new(None); // Create test configuration let config = create_test_config(temp_repo.path().to_str().unwrap())?; // Initialize backend.init_repository(&config.repository).await?; // Backup let target = &config.targets[0]; let report = backend.backup(target).await?; assert!(report.success); // List snapshots let snapshots = backend.list_snapshots().await?; assert!(!snapshots.is_empty()); // Restore backend.restore(&snapshots[0].id, temp_restore.path(), &Default::default()).await?; Ok(()) } ``` ## Monitoring and Alerting Integrate with monitoring systems: ```rust use backup::config::BackupConfig; use backup::backends::ResticBackend; use backup::backend::BackupBackend; #[tokio::main] async fn monitored_backup() -> Result<(), Box> { let config = BackupConfig::from_toml("backup.toml")?; let backend = ResticBackend::new(None)?; let start = std::time::Instant::now(); match perform_backups(&backend, &config).await { Ok(stats) => { let duration = start.elapsed(); println!("gauge backup_duration_seconds {}", duration.as_secs()); println!("gauge backup_files_processed {}", stats.files_processed); println!("gauge backup_size_bytes {}", stats.size_processed); println!("gauge backup_success 1"); } Err(e) => { println!("gauge backup_success 0"); eprintln!("Backup failed: {}", e); } } Ok(()) } async fn perform_backups( backend: &(impl BackupBackend + ?Sized), config: &BackupConfig, ) -> Result> { // ... backup implementation Ok(Default::default()) } ``` ## Next Steps 1. Install required backend tools (restic, borg, etc.) 2. Create backup configuration files 3. Test configurations with dry runs 4. Set up scheduling with init system 5. Configure monitoring and alerting 6. Document recovery procedures 7. Test restore procedures regularly