Vapora/scripts/backup/database-backup.nu

285 lines
6.8 KiB
Plaintext
Raw Permalink Normal View History

2026-01-12 03:36:55 +00:00
#!/usr/bin/env nu
# VAPORA Database Backup Script - SurrealDB to S3 + Restic
# Follows NUSHELL_GUIDELINES.md strictly (0.109.0+)
# Get ISO 8601 timestamp
def get-timestamp []: nothing -> string {
date now | format date "%Y%m%d-%H%M%S"
}
# Export SurrealDB
def export-database [
surreal_url: string
surreal_user: string
surreal_pass: string
output_file: string
]: nothing -> record {
print $"Exporting database from [$surreal_url]..."
let result = do {
^surreal export \
--conn $surreal_url \
--user $surreal_user \
--pass $surreal_pass \
--output $output_file
} | complete
if ($result.exit_code == 0) {
{
success: true
file: $output_file
timestamp: (get-timestamp)
error: null
}
} else {
{
success: false
file: $output_file
timestamp: (get-timestamp)
error: ($result.stderr | str trim)
}
}
}
# Compress backup
def compress-backup [input_file: string]: nothing -> record {
print $"Compressing [$input_file]..."
let compressed = $"($input_file).gz"
let result = do {
^gzip --force $input_file
} | complete
if ($result.exit_code == 0) {
{
success: true
original: $input_file
compressed: $compressed
error: null
}
} else {
{
success: false
original: $input_file
compressed: $compressed
error: ($result.stderr | str trim)
}
}
}
# Encrypt with AES-256
def encrypt-backup [
input_file: string
key_file: string
]: nothing -> record {
print $"Encrypting [$input_file]..."
let encrypted = $"($input_file).enc"
let result = do {
^openssl enc -aes-256-cbc \
-in $input_file \
-out $encrypted \
-pass file:$key_file
} | complete
if ($result.exit_code == 0) {
{
success: true
encrypted_file: $encrypted
error: null
}
} else {
{
success: false
encrypted_file: $encrypted
error: ($result.stderr | str trim)
}
}
}
# Upload to S3
def upload-to-s3 [
file_path: string
s3_bucket: string
s3_prefix: string
]: nothing -> record {
print $"Uploading to S3 [$s3_bucket]..."
let s3_key = $"($s3_prefix)/database-$(get-timestamp).sql.gz.enc"
let result = do {
^aws s3 cp $file_path \
$"s3://($s3_bucket)/($s3_key)" \
--sse AES256 \
--metadata $"backup-type=database,timestamp=$(get-timestamp)"
} | complete
if ($result.exit_code == 0) {
{
success: true
s3_location: $"s3://($s3_bucket)/($s3_key)"
timestamp: (get-timestamp)
error: null
}
} else {
{
success: false
s3_location: $"s3://($s3_bucket)/($s3_key)"
error: ($result.stderr | str trim)
}
}
}
# Verify S3 backup exists
def verify-backup [s3_location: string]: nothing -> record {
print $"Verifying backup [$s3_location]..."
let result = do {
^aws s3 ls $s3_location --human-readable
} | complete
if ($result.exit_code == 0) {
{
success: true
location: $s3_location
size_info: ($result.stdout | str trim)
error: null
}
} else {
{
success: false
location: $s3_location
error: ($result.stderr | str trim)
}
}
}
# Cleanup temporary files
def cleanup-temp-files [temp_dir: string]: nothing -> record {
print $"Cleaning up [$temp_dir]..."
let result = do {
^rm -rf $temp_dir
} | complete
if ($result.exit_code == 0) {
{
success: true
removed: $temp_dir
error: null
}
} else {
{
success: false
removed: $temp_dir
error: ($result.stderr | str trim)
}
}
}
# Main backup procedure
def main [
--surreal-url: string = "ws://localhost:8000"
--surreal-user: string = "root"
--surreal-pass: string = ""
--s3-bucket: string = ""
--s3-prefix: string = "backups/database"
--encryption-key: string = ""
--work-dir: string = "/tmp/vapora-backups"
]: nothing {
print "=== VAPORA Database Backup (S3) ==="
print ""
if ($s3_bucket == "") {
print "ERROR: --s3-bucket is required"
exit 1
}
if ($surreal_pass == "") {
print "ERROR: --surreal-pass is required"
exit 1
}
if ($encryption_key == "") {
print "ERROR: --encryption-key is required"
exit 1
}
# Create work directory
let work_path = $"($work_dir)/$(get-timestamp)"
let create_result = do {
^mkdir -p $work_path
} | complete
if (not ($create_result.exit_code == 0)) {
print "ERROR: Failed to create work directory"
exit 1
}
# Export database
let backup_file = $"($work_path)/vapora-db.sql"
let export_result = (export-database $surreal_url $surreal_user $surreal_pass $backup_file)
if (not $export_result.success) {
print $"ERROR: Database export failed: ($export_result.error)"
cleanup-temp-files $work_path
exit 1
}
print "✓ Database exported successfully"
# Compress
let compress_result = (compress-backup $backup_file)
if (not $compress_result.success) {
print $"ERROR: Compression failed: ($compress_result.error)"
cleanup-temp-files $work_path
exit 1
}
print "✓ Backup compressed"
# Encrypt
let encrypt_result = (encrypt-backup $compress_result.compressed $encryption_key)
if (not $encrypt_result.success) {
print $"ERROR: Encryption failed: ($encrypt_result.error)"
cleanup-temp-files $work_path
exit 1
}
print "✓ Backup encrypted"
# Upload to S3
let upload_result = (upload-to-s3 $encrypt_result.encrypted_file $s3_bucket $s3_prefix)
if (not $upload_result.success) {
print $"ERROR: S3 upload failed: ($upload_result.error)"
cleanup-temp-files $work_path
exit 1
}
print "✓ Backup uploaded to S3"
# Verify
let verify_result = (verify-backup $upload_result.s3_location)
if (not $verify_result.success) {
print $"ERROR: Backup verification failed: ($verify_result.error)"
cleanup-temp-files $work_path
exit 1
}
print "✓ Backup verified"
# Cleanup
cleanup-temp-files $work_path
# Summary
print ""
print "=== Backup Complete ==="
print $"Location: [$upload_result.s3_location]"
print $"Size: [$verify_result.size_info]"
print $"Timestamp: [$(get-timestamp)]"
}