#!/usr/bin/env nu # VAPORA Configuration Backup Script # Backs up Kubernetes ConfigMaps, Secrets, and deployment configs # Follows NUSHELL_GUIDELINES.md - 17 rules # Get current timestamp def get-timestamp []: string { date now | format date "%Y%m%d-%H%M%S" } # Get Kubernetes namespace from environment or use default def get-namespace []: string { if ("VAPORA_NAMESPACE" in $env) { $env.VAPORA_NAMESPACE } else { "vapora" } } # Backup ConfigMaps def backup-configmaps [ output_dir: string namespace: string ]: record { print $"Backing up ConfigMaps from namespace [$namespace]..." let output_file = $"($output_dir)/configmaps-$(get-timestamp).yaml" let result = do { ^kubectl get configmaps \ -n $namespace \ -o yaml \ > $output_file \ 2>&1 } | complete if ($result.exit_code == 0) { { success: true, file: $output_file, count: ( do { ^kubectl get configmaps -n $namespace --no-headers 2>/dev/null } | complete | if ($in.exit_code == 0) { ($in.stdout | lines | length) } else { 0 } ), error: null } } else { { success: false, file: $output_file, count: 0, error: ($result.stderr | str trim) } } } # Backup Secrets def backup-secrets [ output_dir: string namespace: string ]: record { print $"Backing up Secrets from namespace [$namespace]..." let output_file = $"($output_dir)/secrets-$(get-timestamp).yaml" let result = do { ^kubectl get secrets \ -n $namespace \ -o yaml \ > $output_file \ 2>&1 } | complete if ($result.exit_code == 0) { { success: true, file: $output_file, count: ( do { ^kubectl get secrets -n $namespace --no-headers 2>/dev/null } | complete | if ($in.exit_code == 0) { ($in.stdout | lines | length) } else { 0 } ), error: null } } else { { success: false, file: $output_file, count: 0, error: ($result.stderr | str trim) } } } # Backup Deployment manifests def backup-deployments [ output_dir: string namespace: string ]: record { print $"Backing up Deployments from namespace [$namespace]..." let output_file = $"($output_dir)/deployments-$(get-timestamp).yaml" let result = do { ^kubectl get deployments,statefulsets,daemonsets \ -n $namespace \ -o yaml \ > $output_file \ 2>&1 } | complete if ($result.exit_code == 0) { { success: true, file: $output_file, resource_types: ["deployments", "statefulsets", "daemonsets"], error: null } } else { { success: false, file: $output_file, resource_types: [], error: ($result.stderr | str trim) } } } # Backup Services and Ingress def backup-networking [ output_dir: string namespace: string ]: record { print $"Backing up Services and Ingress from namespace [$namespace]..." let output_file = $"($output_dir)/networking-$(get-timestamp).yaml" let result = do { ^kubectl get services,ingresses \ -n $namespace \ -o yaml \ > $output_file \ 2>&1 } | complete if ($result.exit_code == 0) { { success: true, file: $output_file, resource_types: ["services", "ingresses"], error: null } } else { { success: false, file: $output_file, resource_types: [], error: ($result.stderr | str trim) } } } # Compress all backup files def compress-backups [output_dir: string]: record { print $"Compressing backup files..." let archive_name = $"configs-$(get-timestamp).tar.gz" let result = do { ^tar -czf $archive_name -C $output_dir . 2>&1 } | complete if ($result.exit_code == 0) { { success: true, archive: $archive_name, size: ( do { ^ls -lh $archive_name 2>/dev/null } | complete | if ($in.exit_code == 0) { ($in.stdout | str trim) } else { "unknown" } ), error: null } } else { { success: false, archive: $archive_name, size: null, error: ($result.stderr | str trim) } } } # Upload to S3 def upload-to-s3 [ file_path: string s3_bucket: string s3_prefix: string ]: record { print $"Uploading to S3 [$s3_bucket]..." let s3_key = $"($s3_prefix)/configs-$(get-timestamp).tar.gz" let result = do { ^aws s3 cp $file_path \ $"s3://($s3_bucket)/($s3_key)" \ --sse AES256 \ --metadata "backup-type=config,timestamp=$(get-timestamp)" } | complete if ($result.exit_code == 0) { { success: true, s3_location: $"s3://($s3_bucket)/($s3_key)", error: null } } else { { success: false, s3_location: $"s3://($s3_bucket)/($s3_key)", error: ($result.stderr | str trim) } } } # Main backup function def main [ --namespace: string = "" --s3-bucket: string = "" --s3-prefix: string = "backups/config" --work-dir: string = "/tmp/vapora-config-backups" --keep-local: bool = false ]: void { print "=== VAPORA Configuration Backup ===" print "" # Get namespace let ns = if ($namespace == "") { get-namespace } else { $namespace } # Validate environment if ($s3_bucket == "") { print "ERROR: --s3-bucket is required" exit 1 } # Create working directory let work_path = $"($work_dir)/$(get-timestamp)" let result_create = do { ^mkdir -p $work_path 2>&1 } | complete if ($result_create.exit_code != 0) { print "ERROR: Failed to create working directory" exit 1 } # Backup all configuration types let configmaps_result = (backup-configmaps $work_path $ns) if (not $configmaps_result.success) { print $"WARNING: ConfigMap backup failed: ($configmaps_result.error)" } else { print $"✓ Backed up ($configmaps_result.count) ConfigMaps" } let secrets_result = (backup-secrets $work_path $ns) if (not $secrets_result.success) { print $"WARNING: Secret backup failed: ($secrets_result.error)" } else { print $"✓ Backed up ($secrets_result.count) Secrets" } let deployments_result = (backup-deployments $work_path $ns) if (not $deployments_result.success) { print $"WARNING: Deployment backup failed: ($deployments_result.error)" } else { print $"✓ Backed up deployments" } let networking_result = (backup-networking $work_path $ns) if (not $networking_result.success) { print $"WARNING: Networking backup failed: ($networking_result.error)" } else { print $"✓ Backed up networking resources" } # Compress backups let compress_result = (compress-backups $work_path) if (not $compress_result.success) { print $"ERROR: Compression failed: ($compress_result.error)" exit 1 } print "✓ Backups compressed successfully" # Upload to S3 let upload_result = (upload-to-s3 $compress_result.archive $s3_bucket $s3_prefix) if (not $upload_result.success) { print $"ERROR: S3 upload failed: ($upload_result.error)" exit 1 } print "✓ Configuration backup uploaded to S3" # Cleanup unless requested to keep if (not $keep_local) { let cleanup = do { ^rm -rf $work_dir 2>&1 } | complete if ($cleanup.exit_code == 0) { print "✓ Temporary files cleaned up" } } else { print $"Local backup kept at: ($work_dir)" } # Summary print "" print "=== Backup Complete ===" print $"Location: ($upload_result.s3_location)" print $"Namespace: ($ns)" print $"Timestamp: $(get-timestamp)" }