#!/usr/bin/env nu # VAPORA Restic Backup Script # Incremental, deduplicated backups with integrated encryption # Follows NUSHELL_GUIDELINES.md strictly (0.109.0+) # Get timestamp def get-timestamp []: nothing -> string { date now | format date "%Y%m%d-%H%M%S" } # Initialize Restic repository def init-restic-repo [ repo_path: string password: string ]: nothing -> record { print $"Initializing Restic repository at [$repo_path]..." # Check if already initialized let check_result = do { ^bash -c $"RESTIC_PASSWORD=($password) restic -r ($repo_path) list snapshots" } | complete if ($check_result.exit_code == 0) { { success: true repo: $repo_path action: "verified" error: null } } else { # Initialize new repository let init_result = do { ^bash -c $"RESTIC_PASSWORD=($password) restic -r ($repo_path) init" } | complete if ($init_result.exit_code == 0) { { success: true repo: $repo_path action: "initialized" error: null } } else { { success: false repo: $repo_path action: "init-failed" error: ($init_result.stderr | str trim) } } } } # Backup directory to Restic def backup-to-restic [ backup_dir: string repo_path: string password: string tag: string backup_type: string ]: nothing -> record { print $"Backing up [$backup_type] via Restic..." let result = do { ^bash -c ( $"RESTIC_PASSWORD=($password) restic -r ($repo_path) " + $"backup ($backup_dir) --tag ($tag) --tag ($backup_type)" ) } | complete if ($result.exit_code == 0) { { success: true tag: $tag backup_type: $backup_type output: ($result.stdout | str trim) error: null } } else { { success: false tag: $tag backup_type: $backup_type error: ($result.stderr | str trim) } } } # Get repository statistics def get-repo-stats [ repo_path: string password: string ]: nothing -> record { print "Getting repository statistics..." let result = do { ^bash -c $"RESTIC_PASSWORD=($password) restic -r ($repo_path) stats --mode raw" } | complete if ($result.exit_code == 0) { { success: true stats: ($result.stdout | str trim) error: null } } else { { success: false stats: null error: ($result.stderr | str trim) } } } # List recent snapshots def list-snapshots [ repo_path: string password: string limit: int ]: nothing -> record { print $"Listing recent snapshots (limit: [$limit])..." let result = do { ^bash -c ( $"RESTIC_PASSWORD=($password) restic -r ($repo_path) " + $"list snapshots --max ($limit)" ) } | complete if ($result.exit_code == 0) { { success: true count: ($result.stdout | lines | length) snapshots: ($result.stdout | str trim) error: null } } else { { success: false count: 0 snapshots: null error: ($result.stderr | str trim) } } } # Verify backup integrity def verify-repository [ repo_path: string password: string ]: nothing -> record { print "Verifying backup integrity..." let result = do { ^bash -c ( $"RESTIC_PASSWORD=($password) restic -r ($repo_path) " + "check --read-data-subset=10%" ) } | complete if ($result.exit_code == 0) { { success: true message: "Integrity check passed" error: null } } else { { success: false message: null error: ($result.stderr | str trim) } } } # Cleanup old snapshots def cleanup-old-snapshots [ repo_path: string password: string keep_daily: int keep_weekly: int keep_monthly: int ]: nothing -> record { print $"Cleaning up old snapshots (daily: [$keep_daily], weekly: [$keep_weekly], monthly: [$keep_monthly])..." let result = do { ^bash -c ( $"RESTIC_PASSWORD=($password) restic -r ($repo_path) forget " + $"--keep-daily ($keep_daily) --keep-weekly ($keep_weekly) " + $"--keep-monthly ($keep_monthly) --prune" ) } | complete if ($result.exit_code == 0) { { success: true message: ($result.stdout | str trim) error: null } } else { { success: false message: null error: ($result.stderr | str trim) } } } # Collect backup results using reduce def collect-results [ items: list ]: nothing -> list { $items | reduce --fold [] {|item, acc| $acc | append $item } } # Main Restic backup def main [ --repo: string = "" --password: string = "" --database-dir: string = "/tmp/vapora-db-backup" --k8s-dir: string = "/tmp/vapora-k8s-backup" --iac-dir: string = "provisioning" --backup-db --backup-k8s --backup-iac --verify --cleanup --keep-daily: int = 7 --keep-weekly: int = 4 --keep-monthly: int = 12 ]: nothing { print "=== VAPORA Restic Backup ===" print "" # Validate inputs if ($repo == "") { print "ERROR: --repo required (s3://bucket/path or /local/path)" exit 1 } if ($password == "") { print "ERROR: --password required" exit 1 } # Initialize repository let init_result = (init-restic-repo $repo $password) if (not $init_result.success) { print $"ERROR: Repository initialization failed: [$init_result.error]" exit 1 } print $"✓ Repository [$init_result.action]" let backup_tag = (get-timestamp) # Backup database if requested let db_backup = if $backup_db { let result = (backup-to-restic $database_dir $repo $password $backup_tag "database") if (not $result.success) { print $"WARNING: Database backup failed: [$result.error]" } else { print "✓ Database backed up" } $result } else { { success: false backup_type: "database" } } # Backup Kubernetes if requested let k8s_backup = if $backup_k8s { let result = (backup-to-restic $k8s_dir $repo $password $backup_tag "kubernetes") if (not $result.success) { print $"WARNING: Kubernetes backup failed: [$result.error]" } else { print "✓ Kubernetes configs backed up" } $result } else { { success: false backup_type: "kubernetes" } } # Backup IaC if requested let iac_backup = if $backup_iac { let result = (backup-to-restic $iac_dir $repo $password $backup_tag "iac") if (not $result.success) { print $"WARNING: IaC backup failed: [$result.error]" } else { print "✓ IaC backed up" } $result } else { { success: false backup_type: "iac" } } # Collect results let backups = (collect-results [ $db_backup $k8s_backup $iac_backup ]) # Verify repository if $verify { let verify_result = (verify-repository $repo $password) if (not $verify_result.success) { print $"WARNING: Integrity check failed: [$verify_result.error]" } else { print "✓ Backup integrity verified" } } # Cleanup old snapshots if $cleanup { let cleanup_result = (cleanup-old-snapshots $repo $password $keep_daily $keep_weekly $keep_monthly) if (not $cleanup_result.success) { print $"WARNING: Cleanup failed: [$cleanup_result.error]" } else { print "✓ Old snapshots cleaned up" } } # Show repository stats let stats_result = (get-repo-stats $repo $password) if ($stats_result.success) { print "" print "Repository Statistics:" print $stats_result.stats } # List recent snapshots let snapshots_result = (list-snapshots $repo $password 5) if ($snapshots_result.success) { print "" print $"Recent snapshots ([$snapshots_result.count] shown):" print $snapshots_result.snapshots } # Summary print "" print "=== Backup Complete ===" print $"Repository: [$repo]" print $"Timestamp: [$backup_tag]" let successful = ($backups | where {|b| $b.success} | length) print $"Successful backups: [$successful]" }