Vapora/scripts/backup/restic-backup.nu
Jesús Pérez a395bd972f
Some checks failed
Rust CI / Security Audit (push) Has been cancelled
Rust CI / Check + Test + Lint (nightly) (push) Has been cancelled
Rust CI / Check + Test + Lint (stable) (push) Has been cancelled
mdBook Build & Deploy / Build mdBook (push) Has been cancelled
Nickel Type Check / Nickel Type Checking (push) Has been cancelled
mdBook Build & Deploy / Documentation Quality Check (push) Has been cancelled
mdBook Build & Deploy / Deploy to GitHub Pages (push) Has been cancelled
mdBook Build & Deploy / Notification (push) Has been cancelled
chore: add cd/ci ops
2026-01-12 03:36:55 +00:00

350 lines
8.8 KiB
Plaintext

#!/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]"
}