18 KiB
18 KiB
Backup Module Integration Guide
This guide covers integrating the backup module with Provctl and the Provisioning system.
Table of Contents
- Standalone Usage
- Provctl Integration
- Provisioning System Integration
- KCL Configuration Examples
- Automation Workflows
Standalone Usage
As a CLI Tool
Create a backup script that uses the backup library:
// 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<dyn std::error::Error>> {
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<dyn BackupBackend>, Box<dyn std::error::Error>> {
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
use backup::config::BackupConfig;
use backup::backends::ResticBackend;
use backup::backend::BackupBackend;
pub async fn create_daily_backup(config_path: &str) -> Result<(), Box<dyn std::error::Error>> {
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:
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<String>,
},
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<dyn std::error::Error>> {
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:
mod commands;
use commands::backup;
pub enum Command {
Backup(backup::BackupArgs),
// ... other commands
}
pub async fn handle_command(cmd: Command) -> Result<(), Box<dyn std::error::Error>> {
match cmd {
Command::Backup(args) => backup::handle_backup(args).await,
// ... other commands
}
}
Usage
# 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:
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:
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
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
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
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
use backup::config::BackupConfig;
use backup::backends::ResticBackend;
use backup::backend::BackupBackend;
#[tokio::main]
async fn backup_database() -> Result<(), Box<dyn std::error::Error>> {
// 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
use backup::config::BackupConfig;
use backup::backends::ResticBackend;
use backup::backend::BackupBackend;
#[tokio::main]
async fn verified_backup() -> Result<(), Box<dyn std::error::Error>> {
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
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<dyn std::error::Error>> {
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:
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<dyn std::error::Error>> {
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:
use backup::config::BackupConfig;
use backup::backends::ResticBackend;
use backup::backend::BackupBackend;
#[tokio::main]
async fn monitored_backup() -> Result<(), Box<dyn std::error::Error>> {
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<BackupStats, Box<dyn std::error::Error>> {
// ... backup implementation
Ok(Default::default())
}
Next Steps
- Install required backend tools (restic, borg, etc.)
- Create backup configuration files
- Test configurations with dry runs
- Set up scheduling with init system
- Configure monitoring and alerting
- Document recovery procedures
- Test restore procedures regularly