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
350 lines
8.8 KiB
Plaintext
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]"
|
|
}
|