729 lines
18 KiB
Markdown
729 lines
18 KiB
Markdown
|
|
# 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<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
|
||
|
|
|
||
|
|
```rust
|
||
|
|
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`:
|
||
|
|
|
||
|
|
```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<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:
|
||
|
|
|
||
|
|
```rust
|
||
|
|
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
|
||
|
|
|
||
|
|
```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<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
|
||
|
|
|
||
|
|
```rust
|
||
|
|
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
|
||
|
|
|
||
|
|
```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<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`:
|
||
|
|
|
||
|
|
```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<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:
|
||
|
|
|
||
|
|
```rust
|
||
|
|
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
|
||
|
|
|
||
|
|
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
|