Files
vm-skills/backup-sovereign/scripts/11_backup_apply.sh
Vault Sovereign eac77ef7b4 Initial commit: VaultMesh Skills collection
Collection of operational skills for VaultMesh infrastructure including:
- backup-sovereign: Backup and recovery operations
- btc-anchor: Bitcoin anchoring
- cloudflare-tunnel-manager: Cloudflare tunnel management
- container-registry: Container registry operations
- disaster-recovery: Disaster recovery procedures
- dns-sovereign: DNS management
- eth-anchor: Ethereum anchoring
- gitea-bootstrap: Gitea setup and configuration
- hetzner-bootstrap: Hetzner server provisioning
- merkle-forest: Merkle tree operations
- node-hardening: Node security hardening
- operator-bootstrap: Operator initialization
- proof-verifier: Cryptographic proof verification
- rfc3161-anchor: RFC3161 timestamping
- secrets-vault: Secrets management

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-27 00:25:00 +00:00

137 lines
3.6 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# === METADATA ===
SCRIPT_NAME="$(basename "$0")"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
# === CONFIGURATION ===
: "${NODE_NAME:=node-a}"
: "${BACKUP_LABEL:=manual}"
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
: "${BACKUP_SOURCES:=}"
: "${BACKUP_EXCLUDES:=.git,node_modules,target,dist,outputs}"
: "${DRY_RUN:=1}"
: "${REQUIRE_CONFIRM:=1}"
: "${CONFIRM_PHRASE:=I UNDERSTAND THIS WILL CREATE AND ENCRYPT BACKUPS}"
# === FUNCTIONS ===
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
die() { log_error "$@"; exit 1; }
b3_file() {
if command -v b3sum &>/dev/null; then
b3sum "$1" | awk '{print $1}'
else
blake3 "$1"
fi
}
require_confirm() {
[[ "$DRY_RUN" == "0" ]] || die "DRY_RUN=$DRY_RUN (set DRY_RUN=0 to apply)."
if [[ "$REQUIRE_CONFIRM" == "1" ]]; then
echo ""
echo "CONFIRMATION REQUIRED"
echo "Type the phrase exactly to continue:"
echo " $CONFIRM_PHRASE"
read -r input
[[ "$input" == "$CONFIRM_PHRASE" ]] || die "Confirmation phrase mismatch; aborting."
fi
}
json_array() {
# Convert comma-separated string to JSON array
local input="$1"
local first=true
echo -n "["
IFS=',' read -r -a items <<< "$input"
for item in "${items[@]}"; do
if [[ "$first" == "true" ]]; then
first=false
else
echo -n ","
fi
echo -n "\"$item\""
done
echo -n "]"
}
main() {
[[ -n "$BACKUP_SOURCES" ]] || die "BACKUP_SOURCES is required (comma-separated paths)."
require_confirm
local ts run_id run_dir archive excludes_file manifest
ts="$(date -Iseconds | tr ':' '-')"
run_id="${NODE_NAME}_${BACKUP_LABEL}_${ts}"
run_dir="$OUTPUT_DIR/runs/$run_id"
archive="$run_dir/archive.tar.gz"
excludes_file="$run_dir/excludes.txt"
manifest="$run_dir/manifest.json"
mkdir -p "$run_dir"
# Write excludes file
log_info "Writing excludes: $excludes_file"
: > "$excludes_file"
IFS=',' read -r -a excludes <<< "$BACKUP_EXCLUDES"
for ex in "${excludes[@]}"; do
echo "$ex" >> "$excludes_file"
done
# Build tar exclude args
local tar_excludes=()
while IFS= read -r pat; do
[[ -n "$pat" ]] && tar_excludes+=("--exclude=$pat")
done < "$excludes_file"
# Expand sources
local expanded_sources=()
IFS=',' read -r -a sources <<< "$BACKUP_SOURCES"
for src in "${sources[@]}"; do
expanded_sources+=("${src/#\~/$HOME}")
done
# Create archive
log_info "Creating archive: $archive"
tar -czf "$archive" "${tar_excludes[@]}" "${expanded_sources[@]}"
local archive_size archive_b3
archive_size=$(stat -c%s "$archive")
archive_b3=$(b3_file "$archive")
log_info "Archive size: $archive_size bytes"
log_info "Archive BLAKE3: $archive_b3"
# Create manifest (pure bash JSON)
log_info "Writing manifest: $manifest"
cat > "$manifest" <<EOF
{
"version": 1,
"node": "$NODE_NAME",
"label": "$BACKUP_LABEL",
"run_id": "$run_id",
"created_at": "$(date -Iseconds)",
"sources": $(json_array "$BACKUP_SOURCES"),
"excludes": $(json_array "$BACKUP_EXCLUDES"),
"archive": {
"path": "archive.tar.gz",
"bytes": $archive_size,
"blake3": "$archive_b3"
}
}
EOF
# Save last run pointer
echo "$run_dir" > "$OUTPUT_DIR/last_run_dir.txt"
log_info "Saved last run pointer: $OUTPUT_DIR/last_run_dir.txt"
log_info "Backup complete."
log_info "Next: ./scripts/20_encrypt_plan.sh then 21_encrypt_apply.sh"
}
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"