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>
This commit is contained in:
114
backup-sovereign/scripts/00_preflight.sh
Executable file
114
backup-sovereign/scripts/00_preflight.sh
Executable file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# === METADATA ===
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# === CONFIGURATION ===
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
: "${BACKUP_SOURCES:=}"
|
||||
: "${BACKUP_EXCLUDES:=.git,node_modules,target,dist,outputs}"
|
||||
|
||||
# === FUNCTIONS ===
|
||||
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
||||
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
|
||||
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
|
||||
die() { log_error "$@"; exit 1; }
|
||||
|
||||
check_tool() {
|
||||
local tool="$1"
|
||||
if command -v "$tool" &>/dev/null; then
|
||||
log_info "Found: $tool ($(command -v "$tool"))"
|
||||
return 0
|
||||
else
|
||||
log_warn "Missing: $tool"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_b3sum() {
|
||||
if command -v b3sum &>/dev/null; then
|
||||
log_info "Found: b3sum ($(command -v b3sum))"
|
||||
return 0
|
||||
elif command -v blake3 &>/dev/null; then
|
||||
log_info "Found: blake3 ($(command -v blake3))"
|
||||
return 0
|
||||
else
|
||||
log_warn "Missing: b3sum or blake3"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
log_info "Starting $SCRIPT_NAME..."
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
local missing=0
|
||||
|
||||
log_info "=== Required Tools ==="
|
||||
check_tool tar || ((missing++))
|
||||
check_tool gzip || ((missing++))
|
||||
check_tool age || ((missing++))
|
||||
check_b3sum || ((missing++))
|
||||
check_tool stat || ((missing++))
|
||||
check_tool find || ((missing++))
|
||||
|
||||
log_info "=== Backup Sources ==="
|
||||
if [[ -z "$BACKUP_SOURCES" ]]; then
|
||||
log_warn "BACKUP_SOURCES not set (required for backup)"
|
||||
else
|
||||
IFS=',' read -r -a sources <<< "$BACKUP_SOURCES"
|
||||
for src in "${sources[@]}"; do
|
||||
# Expand ~ if present
|
||||
src="${src/#\~/$HOME}"
|
||||
if [[ -e "$src" ]]; then
|
||||
log_info "Source exists: $src"
|
||||
else
|
||||
log_warn "Source missing: $src"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
log_info "=== Encryption Files ==="
|
||||
if [[ -n "${AGE_RECIPIENT_FILE:-}" ]]; then
|
||||
if [[ -f "$AGE_RECIPIENT_FILE" ]]; then
|
||||
log_info "AGE_RECIPIENT_FILE exists: $AGE_RECIPIENT_FILE"
|
||||
else
|
||||
log_warn "AGE_RECIPIENT_FILE missing: $AGE_RECIPIENT_FILE"
|
||||
fi
|
||||
else
|
||||
log_warn "AGE_RECIPIENT_FILE not set (required for encryption)"
|
||||
fi
|
||||
|
||||
if [[ -n "${AGE_IDENTITY_FILE:-}" ]]; then
|
||||
if [[ -f "$AGE_IDENTITY_FILE" ]]; then
|
||||
log_info "AGE_IDENTITY_FILE exists: $AGE_IDENTITY_FILE"
|
||||
else
|
||||
log_warn "AGE_IDENTITY_FILE missing: $AGE_IDENTITY_FILE"
|
||||
fi
|
||||
else
|
||||
log_warn "AGE_IDENTITY_FILE not set (required for restore drill)"
|
||||
fi
|
||||
|
||||
log_info "=== Disk Space ==="
|
||||
local avail
|
||||
avail=$(df -P "$OUTPUT_DIR" | awk 'NR==2 {print $4}')
|
||||
log_info "Available space in $OUTPUT_DIR: $((avail / 1024)) MB"
|
||||
|
||||
log_info "=== Parameters ==="
|
||||
log_info "NODE_NAME=${NODE_NAME:-node-a}"
|
||||
log_info "BACKUP_LABEL=${BACKUP_LABEL:-manual}"
|
||||
log_info "BACKUP_EXCLUDES=$BACKUP_EXCLUDES"
|
||||
log_info "DRY_RUN=${DRY_RUN:-1} (apply scripts require DRY_RUN=0)"
|
||||
|
||||
if [[ $missing -gt 0 ]]; then
|
||||
die "Missing $missing required tools. Install them before proceeding."
|
||||
fi
|
||||
|
||||
log_info "Preflight OK."
|
||||
log_info "Completed $SCRIPT_NAME"
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
89
backup-sovereign/scripts/10_backup_plan.sh
Executable file
89
backup-sovereign/scripts/10_backup_plan.sh
Executable file
@@ -0,0 +1,89 @@
|
||||
#!/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}"
|
||||
|
||||
# === FUNCTIONS ===
|
||||
log_plan() { echo "[PLAN] $(date -Iseconds) $*"; }
|
||||
die() { echo "[ERROR] $(date -Iseconds) $*" >&2; exit 1; }
|
||||
|
||||
estimate_size() {
|
||||
local total=0
|
||||
IFS=',' read -r -a sources <<< "$BACKUP_SOURCES"
|
||||
IFS=',' read -r -a excludes <<< "$BACKUP_EXCLUDES"
|
||||
|
||||
for src in "${sources[@]}"; do
|
||||
src="${src/#\~/$HOME}"
|
||||
if [[ -e "$src" ]]; then
|
||||
# Build find exclude args
|
||||
local find_excludes=()
|
||||
for ex in "${excludes[@]}"; do
|
||||
find_excludes+=(-name "$ex" -prune -o)
|
||||
done
|
||||
|
||||
local size
|
||||
size=$(find "$src" "${find_excludes[@]}" -type f -print0 2>/dev/null | \
|
||||
xargs -0 stat -c%s 2>/dev/null | \
|
||||
awk '{sum+=$1} END {print sum+0}')
|
||||
total=$((total + size))
|
||||
fi
|
||||
done
|
||||
echo "$total"
|
||||
}
|
||||
|
||||
main() {
|
||||
[[ -n "$BACKUP_SOURCES" ]] || die "BACKUP_SOURCES is required (comma-separated paths)."
|
||||
|
||||
local ts run_id run_dir
|
||||
ts="$(date -Iseconds | tr ':' '-')"
|
||||
run_id="${NODE_NAME}_${BACKUP_LABEL}_${ts}"
|
||||
run_dir="$OUTPUT_DIR/runs/$run_id"
|
||||
|
||||
log_plan "=== Backup Plan ==="
|
||||
log_plan "Run ID: $run_id"
|
||||
log_plan "Run directory: $run_dir"
|
||||
log_plan "Archive: $run_dir/archive.tar.gz"
|
||||
log_plan "Manifest: $run_dir/manifest.json"
|
||||
echo ""
|
||||
|
||||
log_plan "=== Sources ==="
|
||||
IFS=',' read -r -a sources <<< "$BACKUP_SOURCES"
|
||||
for src in "${sources[@]}"; do
|
||||
src="${src/#\~/$HOME}"
|
||||
if [[ -e "$src" ]]; then
|
||||
log_plan " [OK] $src"
|
||||
else
|
||||
log_plan " [MISSING] $src"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
log_plan "=== Excludes ==="
|
||||
IFS=',' read -r -a excludes <<< "$BACKUP_EXCLUDES"
|
||||
for ex in "${excludes[@]}"; do
|
||||
log_plan " - $ex"
|
||||
done
|
||||
echo ""
|
||||
|
||||
log_plan "=== Size Estimate ==="
|
||||
local est_bytes est_mb
|
||||
est_bytes=$(estimate_size)
|
||||
est_mb=$((est_bytes / 1024 / 1024))
|
||||
log_plan "Estimated uncompressed: ${est_mb} MB ($est_bytes bytes)"
|
||||
log_plan "Compressed size will be smaller (typically 30-70% of original)"
|
||||
echo ""
|
||||
|
||||
log_plan "Next: ./scripts/11_backup_apply.sh (requires DRY_RUN=0)"
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
136
backup-sovereign/scripts/11_backup_apply.sh
Executable file
136
backup-sovereign/scripts/11_backup_apply.sh
Executable file
@@ -0,0 +1,136 @@
|
||||
#!/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 "$@"
|
||||
63
backup-sovereign/scripts/20_encrypt_plan.sh
Executable file
63
backup-sovereign/scripts/20_encrypt_plan.sh
Executable file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# === METADATA ===
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# === CONFIGURATION ===
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
: "${AGE_RECIPIENT_FILE:=}"
|
||||
|
||||
# === FUNCTIONS ===
|
||||
log_plan() { echo "[PLAN] $(date -Iseconds) $*"; }
|
||||
die() { echo "[ERROR] $(date -Iseconds) $*" >&2; exit 1; }
|
||||
|
||||
main() {
|
||||
local last_run_file="$OUTPUT_DIR/last_run_dir.txt"
|
||||
|
||||
[[ -f "$last_run_file" ]] || die "No last run pointer. Run 11_backup_apply.sh first."
|
||||
|
||||
local run_dir
|
||||
run_dir="$(cat "$last_run_file")"
|
||||
|
||||
[[ -d "$run_dir" ]] || die "Run directory missing: $run_dir"
|
||||
[[ -f "$run_dir/archive.tar.gz" ]] || die "Archive missing: $run_dir/archive.tar.gz"
|
||||
|
||||
log_plan "=== Encryption Plan ==="
|
||||
log_plan "Method: age"
|
||||
log_plan "Run directory: $run_dir"
|
||||
log_plan "Input: $run_dir/archive.tar.gz"
|
||||
log_plan "Output: $run_dir/archive.tar.gz.age"
|
||||
echo ""
|
||||
|
||||
log_plan "=== Recipient File ==="
|
||||
if [[ -n "$AGE_RECIPIENT_FILE" ]]; then
|
||||
if [[ -f "$AGE_RECIPIENT_FILE" ]]; then
|
||||
log_plan "File: $AGE_RECIPIENT_FILE"
|
||||
log_plan "Recipients:"
|
||||
while IFS= read -r line; do
|
||||
[[ "$line" =~ ^# ]] && continue
|
||||
[[ -z "$line" ]] && continue
|
||||
# Show truncated public key
|
||||
log_plan " - ${line:0:20}..."
|
||||
done < "$AGE_RECIPIENT_FILE"
|
||||
else
|
||||
log_plan "[MISSING] $AGE_RECIPIENT_FILE"
|
||||
fi
|
||||
else
|
||||
log_plan "[NOT SET] AGE_RECIPIENT_FILE required for encryption"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
log_plan "=== Archive Info ==="
|
||||
local size
|
||||
size=$(stat -c%s "$run_dir/archive.tar.gz")
|
||||
log_plan "Archive size: $size bytes ($((size / 1024 / 1024)) MB)"
|
||||
echo ""
|
||||
|
||||
log_plan "Next: ./scripts/21_encrypt_apply.sh (requires DRY_RUN=0)"
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
66
backup-sovereign/scripts/21_encrypt_apply.sh
Executable file
66
backup-sovereign/scripts/21_encrypt_apply.sh
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# === METADATA ===
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# === CONFIGURATION ===
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
: "${AGE_RECIPIENT_FILE:=}"
|
||||
: "${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; }
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
main() {
|
||||
require_confirm
|
||||
|
||||
local last_run_file="$OUTPUT_DIR/last_run_dir.txt"
|
||||
[[ -f "$last_run_file" ]] || die "No last run pointer. Run 11_backup_apply.sh first."
|
||||
|
||||
local run_dir
|
||||
run_dir="$(cat "$last_run_file")"
|
||||
|
||||
local archive="$run_dir/archive.tar.gz"
|
||||
[[ -f "$archive" ]] || die "Missing archive: $archive"
|
||||
|
||||
[[ -n "$AGE_RECIPIENT_FILE" ]] || die "AGE_RECIPIENT_FILE is required for encryption."
|
||||
[[ -f "$AGE_RECIPIENT_FILE" ]] || die "AGE_RECIPIENT_FILE not found: $AGE_RECIPIENT_FILE"
|
||||
|
||||
local encrypted="$run_dir/archive.tar.gz.age"
|
||||
|
||||
log_info "Encrypting with age..."
|
||||
log_info "Input: $archive"
|
||||
log_info "Output: $encrypted"
|
||||
log_info "Recipients: $AGE_RECIPIENT_FILE"
|
||||
|
||||
age -R "$AGE_RECIPIENT_FILE" -o "$encrypted" "$archive"
|
||||
|
||||
local enc_size
|
||||
enc_size=$(stat -c%s "$encrypted")
|
||||
log_info "Encrypted size: $enc_size bytes"
|
||||
|
||||
log_info "Encryption complete."
|
||||
log_info "Next: ./scripts/30_generate_proof.sh"
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
96
backup-sovereign/scripts/30_generate_proof.sh
Executable file
96
backup-sovereign/scripts/30_generate_proof.sh
Executable file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# === METADATA ===
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# === CONFIGURATION ===
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
: "${NODE_NAME:=node-a}"
|
||||
|
||||
# === 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
|
||||
}
|
||||
|
||||
b3_string() {
|
||||
if command -v b3sum &>/dev/null; then
|
||||
echo -n "$1" | b3sum | awk '{print $1}'
|
||||
else
|
||||
echo -n "$1" | blake3
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
local last_run_file="$OUTPUT_DIR/last_run_dir.txt"
|
||||
[[ -f "$last_run_file" ]] || die "No last run pointer. Run 11_backup_apply.sh first."
|
||||
|
||||
local run_dir
|
||||
run_dir="$(cat "$last_run_file")"
|
||||
|
||||
local manifest="$run_dir/manifest.json"
|
||||
[[ -f "$manifest" ]] || die "Missing manifest: $manifest"
|
||||
|
||||
# Find encrypted archive
|
||||
local encrypted=""
|
||||
if [[ -f "$run_dir/archive.tar.gz.age" ]]; then
|
||||
encrypted="$run_dir/archive.tar.gz.age"
|
||||
else
|
||||
die "Missing encrypted archive. Run 21_encrypt_apply.sh first."
|
||||
fi
|
||||
|
||||
log_info "Generating proof receipts..."
|
||||
log_info "Run directory: $run_dir"
|
||||
|
||||
# Compute BLAKE3 hashes
|
||||
local manifest_b3 encrypted_b3
|
||||
manifest_b3=$(b3_file "$manifest")
|
||||
encrypted_b3=$(b3_file "$encrypted")
|
||||
|
||||
log_info "Manifest BLAKE3: $manifest_b3"
|
||||
log_info "Encrypted BLAKE3: $encrypted_b3"
|
||||
|
||||
# Compute ROOT = BLAKE3(manifest_b3 || encrypted_b3)
|
||||
# Using stable text concatenation
|
||||
local concat root_b3
|
||||
concat="${manifest_b3}${encrypted_b3}"
|
||||
root_b3=$(b3_string "$concat")
|
||||
|
||||
log_info "ROOT BLAKE3: $root_b3"
|
||||
|
||||
# Write ROOT.txt
|
||||
echo "$root_b3" > "$run_dir/ROOT.txt"
|
||||
log_info "Wrote: $run_dir/ROOT.txt"
|
||||
|
||||
# Write PROOF.json
|
||||
cat > "$run_dir/PROOF.json" <<EOF
|
||||
{
|
||||
"version": 1,
|
||||
"node": "$NODE_NAME",
|
||||
"created_at": "$(date -Iseconds)",
|
||||
"artifacts": {
|
||||
"manifest_blake3": "$manifest_b3",
|
||||
"encrypted_archive_blake3": "$encrypted_b3",
|
||||
"root_blake3": "$root_b3"
|
||||
},
|
||||
"computation": "ROOT = BLAKE3(manifest_blake3 || encrypted_archive_blake3)",
|
||||
"notes": "ROOT can be anchored via merkle-forest/rfc3161-anchor skills."
|
||||
}
|
||||
EOF
|
||||
log_info "Wrote: $run_dir/PROOF.json"
|
||||
|
||||
log_info "Proof generation complete."
|
||||
log_info "Next: ./scripts/40_verify_backup.sh"
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
74
backup-sovereign/scripts/40_verify_backup.sh
Executable file
74
backup-sovereign/scripts/40_verify_backup.sh
Executable file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# === METADATA ===
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# === CONFIGURATION ===
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
|
||||
# === FUNCTIONS ===
|
||||
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
||||
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
|
||||
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
|
||||
die() { log_error "$@"; exit 1; }
|
||||
|
||||
check_file() {
|
||||
local path="$1"
|
||||
local name="$2"
|
||||
if [[ -f "$path" ]]; then
|
||||
local size
|
||||
size=$(stat -c%s "$path")
|
||||
log_info "[OK] $name ($size bytes)"
|
||||
return 0
|
||||
else
|
||||
log_warn "[MISSING] $name"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
local last_run_file="$OUTPUT_DIR/last_run_dir.txt"
|
||||
[[ -f "$last_run_file" ]] || die "No last run pointer. Run 11_backup_apply.sh first."
|
||||
|
||||
local run_dir
|
||||
run_dir="$(cat "$last_run_file")"
|
||||
|
||||
log_info "Verifying backup artifacts..."
|
||||
log_info "Run directory: $run_dir"
|
||||
echo ""
|
||||
|
||||
local missing=0
|
||||
|
||||
log_info "=== Core Artifacts ==="
|
||||
check_file "$run_dir/archive.tar.gz" "archive.tar.gz" || ((missing++))
|
||||
check_file "$run_dir/archive.tar.gz.age" "archive.tar.gz.age" || ((missing++))
|
||||
check_file "$run_dir/manifest.json" "manifest.json" || ((missing++))
|
||||
echo ""
|
||||
|
||||
log_info "=== Proof Artifacts ==="
|
||||
check_file "$run_dir/ROOT.txt" "ROOT.txt" || ((missing++))
|
||||
check_file "$run_dir/PROOF.json" "PROOF.json" || ((missing++))
|
||||
echo ""
|
||||
|
||||
log_info "=== Metadata ==="
|
||||
check_file "$run_dir/excludes.txt" "excludes.txt" || true
|
||||
echo ""
|
||||
|
||||
if [[ $missing -gt 0 ]]; then
|
||||
die "Verification failed: $missing missing artifacts."
|
||||
fi
|
||||
|
||||
log_info "=== ROOT Value ==="
|
||||
local root
|
||||
root=$(cat "$run_dir/ROOT.txt")
|
||||
log_info "ROOT: $root"
|
||||
echo ""
|
||||
|
||||
log_info "Verification complete. All artifacts present."
|
||||
log_info "Next: ./scripts/50_restore_drill.sh (MANDATORY)"
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
99
backup-sovereign/scripts/50_restore_drill.sh
Executable file
99
backup-sovereign/scripts/50_restore_drill.sh
Executable file
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# === METADATA ===
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# === CONFIGURATION ===
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
: "${AGE_IDENTITY_FILE:=}"
|
||||
: "${DRY_RUN:=1}"
|
||||
: "${REQUIRE_CONFIRM:=1}"
|
||||
: "${CONFIRM_PHRASE:=I UNDERSTAND THIS WILL CREATE AND ENCRYPT BACKUPS}"
|
||||
|
||||
# === FUNCTIONS ===
|
||||
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
||||
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
|
||||
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
|
||||
die() { log_error "$@"; exit 1; }
|
||||
|
||||
require_confirm() {
|
||||
[[ "$DRY_RUN" == "0" ]] || die "DRY_RUN=$DRY_RUN (set DRY_RUN=0 to apply)."
|
||||
|
||||
if [[ "$REQUIRE_CONFIRM" == "1" ]]; then
|
||||
echo ""
|
||||
echo "RESTORE DRILL - CONFIRMATION REQUIRED"
|
||||
echo "This will decrypt and extract the backup to verify recoverability."
|
||||
echo "Type the phrase exactly to continue:"
|
||||
echo " $CONFIRM_PHRASE"
|
||||
read -r input
|
||||
[[ "$input" == "$CONFIRM_PHRASE" ]] || die "Confirmation phrase mismatch; aborting."
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
require_confirm
|
||||
|
||||
local last_run_file="$OUTPUT_DIR/last_run_dir.txt"
|
||||
[[ -f "$last_run_file" ]] || die "No last run pointer. Run 11_backup_apply.sh first."
|
||||
|
||||
local run_dir
|
||||
run_dir="$(cat "$last_run_file")"
|
||||
|
||||
# Find encrypted archive
|
||||
local encrypted=""
|
||||
if [[ -f "$run_dir/archive.tar.gz.age" ]]; then
|
||||
encrypted="$run_dir/archive.tar.gz.age"
|
||||
else
|
||||
die "Missing encrypted archive. Run 21_encrypt_apply.sh first."
|
||||
fi
|
||||
|
||||
[[ -n "$AGE_IDENTITY_FILE" ]] || die "AGE_IDENTITY_FILE is required for restore drill."
|
||||
[[ -f "$AGE_IDENTITY_FILE" ]] || die "AGE_IDENTITY_FILE not found: $AGE_IDENTITY_FILE"
|
||||
|
||||
# Create temp directory for restore
|
||||
local restore_dir
|
||||
restore_dir=$(mktemp -d)
|
||||
log_info "Restore drill temp directory: $restore_dir"
|
||||
|
||||
local decrypted="$restore_dir/archive.tar.gz"
|
||||
|
||||
# Decrypt
|
||||
log_info "Decrypting archive..."
|
||||
age -d -i "$AGE_IDENTITY_FILE" -o "$decrypted" "$encrypted"
|
||||
log_info "Decryption successful."
|
||||
|
||||
# Extract
|
||||
log_info "Extracting archive..."
|
||||
mkdir -p "$restore_dir/extract"
|
||||
tar -xzf "$decrypted" -C "$restore_dir/extract"
|
||||
|
||||
# Count files
|
||||
local file_count
|
||||
file_count=$(find "$restore_dir/extract" -type f | wc -l | tr -d ' ')
|
||||
|
||||
if [[ "$file_count" -eq 0 ]]; then
|
||||
die "Restore drill FAILED: No files extracted."
|
||||
fi
|
||||
|
||||
log_info "Extracted $file_count files."
|
||||
|
||||
# Save restore pointer
|
||||
echo "$restore_dir" > "$run_dir/last_restore_dir.txt"
|
||||
log_info "Saved restore pointer: $run_dir/last_restore_dir.txt"
|
||||
|
||||
echo ""
|
||||
log_info "=========================================="
|
||||
log_info " RESTORE DRILL: PASSED"
|
||||
log_info " Files restored: $file_count"
|
||||
log_info " Location: $restore_dir/extract"
|
||||
log_info "=========================================="
|
||||
echo ""
|
||||
|
||||
log_info "Restore drill complete."
|
||||
log_info "Next: ./scripts/90_verify.sh then ./scripts/99_report.sh"
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
139
backup-sovereign/scripts/90_verify.sh
Executable file
139
backup-sovereign/scripts/90_verify.sh
Executable file
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# === METADATA ===
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
CHECKS_DIR="$SKILL_ROOT/checks"
|
||||
|
||||
# === CONFIGURATION ===
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
: "${NODE_NAME:=node-a}"
|
||||
|
||||
# === FUNCTIONS ===
|
||||
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
||||
die() { echo "[ERROR] $(date -Iseconds) $*" >&2; exit 1; }
|
||||
|
||||
run_check() {
|
||||
local script="$1"
|
||||
if [[ -x "$CHECKS_DIR/$script" ]]; then
|
||||
if "$CHECKS_DIR/$script" &>/dev/null; then
|
||||
echo "true"
|
||||
else
|
||||
echo "false"
|
||||
fi
|
||||
else
|
||||
echo "skip"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
local last_run_file="$OUTPUT_DIR/last_run_dir.txt"
|
||||
[[ -f "$last_run_file" ]] || die "No last run pointer. Run 11_backup_apply.sh first."
|
||||
|
||||
local run_dir
|
||||
run_dir="$(cat "$last_run_file")"
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
local status="$OUTPUT_DIR/status_matrix.json"
|
||||
|
||||
# Check artifacts
|
||||
local has_archive has_encrypted has_manifest has_proof has_root has_restore
|
||||
|
||||
[[ -f "$run_dir/archive.tar.gz" ]] && has_archive="true" || has_archive="false"
|
||||
[[ -f "$run_dir/archive.tar.gz.age" ]] && has_encrypted="true" || has_encrypted="false"
|
||||
[[ -f "$run_dir/manifest.json" ]] && has_manifest="true" || has_manifest="false"
|
||||
[[ -f "$run_dir/PROOF.json" ]] && has_proof="true" || has_proof="false"
|
||||
[[ -f "$run_dir/ROOT.txt" ]] && has_root="true" || has_root="false"
|
||||
[[ -f "$run_dir/last_restore_dir.txt" ]] && has_restore="true" || has_restore="false"
|
||||
|
||||
# Run check scripts
|
||||
local tools_ok space_ok restore_ok
|
||||
tools_ok=$(run_check "check_tools.sh")
|
||||
space_ok=$(run_check "check_space.sh")
|
||||
restore_ok=$(run_check "check_restore.sh")
|
||||
|
||||
# Determine blockers and warnings
|
||||
local blockers="" warnings="" next_steps=""
|
||||
|
||||
if [[ "$has_restore" == "false" ]]; then
|
||||
blockers="${blockers}\"Restore drill not completed\","
|
||||
fi
|
||||
if [[ "$has_encrypted" == "false" ]]; then
|
||||
blockers="${blockers}\"Archive not encrypted\","
|
||||
fi
|
||||
if [[ "$has_manifest" == "false" ]]; then
|
||||
warnings="${warnings}\"Manifest missing\","
|
||||
fi
|
||||
if [[ "$has_proof" == "false" ]]; then
|
||||
warnings="${warnings}\"Proof receipts missing\","
|
||||
fi
|
||||
|
||||
# Determine next steps
|
||||
if [[ "$has_restore" == "true" && "$has_encrypted" == "true" ]]; then
|
||||
next_steps="${next_steps}\"Store encrypted bundle off-node\","
|
||||
next_steps="${next_steps}\"Anchor ROOT.txt with rfc3161-anchor\","
|
||||
next_steps="${next_steps}\"Proceed to disaster-recovery skill\","
|
||||
else
|
||||
if [[ "$has_encrypted" == "false" ]]; then
|
||||
next_steps="${next_steps}\"Run 21_encrypt_apply.sh\","
|
||||
fi
|
||||
if [[ "$has_restore" == "false" ]]; then
|
||||
next_steps="${next_steps}\"Run 50_restore_drill.sh (MANDATORY)\","
|
||||
fi
|
||||
fi
|
||||
|
||||
# Remove trailing commas
|
||||
blockers="[${blockers%,}]"
|
||||
warnings="[${warnings%,}]"
|
||||
next_steps="[${next_steps%,}]"
|
||||
|
||||
# Get ROOT value if exists
|
||||
local root_value="null"
|
||||
if [[ -f "$run_dir/ROOT.txt" ]]; then
|
||||
root_value="\"$(cat "$run_dir/ROOT.txt")\""
|
||||
fi
|
||||
|
||||
cat > "$status" <<EOF
|
||||
{
|
||||
"skill": "backup-sovereign",
|
||||
"node": "$NODE_NAME",
|
||||
"timestamp": "$(date -Iseconds)",
|
||||
"run_dir": "$run_dir",
|
||||
"root": $root_value,
|
||||
"checks": {
|
||||
"archive": $has_archive,
|
||||
"encrypted": $has_encrypted,
|
||||
"manifest": $has_manifest,
|
||||
"proof": $has_proof,
|
||||
"root": $has_root,
|
||||
"restore_drill": $has_restore,
|
||||
"tools": $tools_ok,
|
||||
"space": $space_ok
|
||||
},
|
||||
"blockers": $blockers,
|
||||
"warnings": $warnings,
|
||||
"next_steps": $next_steps
|
||||
}
|
||||
EOF
|
||||
|
||||
log_info "Wrote status matrix: $status"
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo " VERIFICATION SUMMARY"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
echo " Archive: $has_archive"
|
||||
echo " Encrypted: $has_encrypted"
|
||||
echo " Manifest: $has_manifest"
|
||||
echo " Proof: $has_proof"
|
||||
echo " ROOT: $has_root"
|
||||
echo " Restore Drill: $has_restore"
|
||||
echo ""
|
||||
|
||||
# Return success only if restore drill passed
|
||||
[[ "$has_restore" == "true" ]]
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
157
backup-sovereign/scripts/99_report.sh
Executable file
157
backup-sovereign/scripts/99_report.sh
Executable file
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# === METADATA ===
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# === CONFIGURATION ===
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
: "${NODE_NAME:=node-a}"
|
||||
|
||||
# === FUNCTIONS ===
|
||||
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
||||
|
||||
get_file_size() {
|
||||
local path="$1"
|
||||
if [[ -f "$path" ]]; then
|
||||
stat -c%s "$path"
|
||||
else
|
||||
echo "0"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
local report="$OUTPUT_DIR/audit_report.md"
|
||||
local status="$OUTPUT_DIR/status_matrix.json"
|
||||
local last_run_file="$OUTPUT_DIR/last_run_dir.txt"
|
||||
|
||||
local run_dir="(unknown)"
|
||||
[[ -f "$last_run_file" ]] && run_dir="$(cat "$last_run_file")"
|
||||
|
||||
local root_value="(not generated)"
|
||||
[[ -f "$run_dir/ROOT.txt" ]] && root_value="$(cat "$run_dir/ROOT.txt")"
|
||||
|
||||
local archive_size enc_size
|
||||
archive_size=$(get_file_size "$run_dir/archive.tar.gz")
|
||||
enc_size=$(get_file_size "$run_dir/archive.tar.gz.age")
|
||||
|
||||
local restore_status="NOT COMPLETED"
|
||||
[[ -f "$run_dir/last_restore_dir.txt" ]] && restore_status="PASSED"
|
||||
|
||||
cat > "$report" <<EOF
|
||||
# Backup Sovereign Audit Report
|
||||
|
||||
**Generated:** $(date -Iseconds)
|
||||
**Node:** $NODE_NAME
|
||||
**Run Directory:** $run_dir
|
||||
**Skill Version:** 1.0.0
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This report documents the backup operations performed on **$NODE_NAME**
|
||||
for sovereign EU infrastructure.
|
||||
|
||||
---
|
||||
|
||||
## Backup Artifacts
|
||||
|
||||
| Artifact | Path | Size |
|
||||
|----------|------|------|
|
||||
| Archive | archive.tar.gz | $archive_size bytes |
|
||||
| Encrypted | archive.tar.gz.age | $enc_size bytes |
|
||||
| Manifest | manifest.json | $(get_file_size "$run_dir/manifest.json") bytes |
|
||||
| Proof | PROOF.json | $(get_file_size "$run_dir/PROOF.json") bytes |
|
||||
| ROOT | ROOT.txt | $(get_file_size "$run_dir/ROOT.txt") bytes |
|
||||
|
||||
---
|
||||
|
||||
## Proof Receipt
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| ROOT (BLAKE3) | \`$root_value\` |
|
||||
|
||||
This ROOT value can be anchored via:
|
||||
- merkle-forest skill (aggregate with other proofs)
|
||||
- rfc3161-anchor skill (RFC 3161 timestamp)
|
||||
- eth-anchor / btc-anchor skills (blockchain anchoring)
|
||||
|
||||
---
|
||||
|
||||
## Restore Drill
|
||||
|
||||
| Status | Result |
|
||||
|--------|--------|
|
||||
| Restore Drill | **$restore_status** |
|
||||
|
||||
$(if [[ -f "$run_dir/last_restore_dir.txt" ]]; then
|
||||
echo "Restore location: \`$(cat "$run_dir/last_restore_dir.txt")\`"
|
||||
else
|
||||
echo "**WARNING:** Backup has not been verified via restore drill."
|
||||
echo "Run \`./scripts/50_restore_drill.sh\` to validate recoverability."
|
||||
fi)
|
||||
|
||||
---
|
||||
|
||||
## Verification Results
|
||||
|
||||
$(if [[ -f "$status" ]]; then
|
||||
echo '```json'
|
||||
cat "$status"
|
||||
echo '```'
|
||||
else
|
||||
echo "Status matrix not found. Run 90_verify.sh first."
|
||||
fi)
|
||||
|
||||
---
|
||||
|
||||
## EU Compliance Declaration
|
||||
|
||||
| Aspect | Value |
|
||||
|--------|-------|
|
||||
| Data Residency | EU (Ireland - Dublin) |
|
||||
| GDPR Applicable | Yes (depends on backup content) |
|
||||
| Jurisdiction | Irish Law |
|
||||
| Encryption at Rest | Yes (age) |
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Store encrypted bundle off-node (secondary disk / object store)
|
||||
2. Test restore on a different machine (recommended)
|
||||
3. Anchor ROOT.txt with rfc3161-anchor skill
|
||||
4. Proceed to **disaster-recovery** skill
|
||||
|
||||
---
|
||||
|
||||
## Artifact Locations
|
||||
|
||||
| Artifact | Path |
|
||||
|----------|------|
|
||||
| Archive | $run_dir/archive.tar.gz |
|
||||
| Encrypted Archive | $run_dir/archive.tar.gz.age |
|
||||
| Manifest | $run_dir/manifest.json |
|
||||
| ROOT | $run_dir/ROOT.txt |
|
||||
| Proof | $run_dir/PROOF.json |
|
||||
| Status Matrix | $OUTPUT_DIR/status_matrix.json |
|
||||
| This Report | $OUTPUT_DIR/audit_report.md |
|
||||
|
||||
---
|
||||
|
||||
*Report generated by backup-sovereign skill v1.0.0*
|
||||
*$(date -Iseconds)*
|
||||
EOF
|
||||
|
||||
log_info "Audit report written to $report"
|
||||
echo ""
|
||||
cat "$report"
|
||||
log_info "Completed $SCRIPT_NAME"
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
59
backup-sovereign/scripts/rollback/purge_outputs.sh
Executable file
59
backup-sovereign/scripts/rollback/purge_outputs.sh
Executable file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# === METADATA ===
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
|
||||
# === CONFIGURATION ===
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
|
||||
# === FUNCTIONS ===
|
||||
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
||||
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
|
||||
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
|
||||
die() { log_error "$@"; exit 1; }
|
||||
|
||||
main() {
|
||||
if [[ ! -d "$OUTPUT_DIR" ]]; then
|
||||
log_info "Output directory does not exist. Nothing to purge."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
local run_count=0
|
||||
if [[ -d "$OUTPUT_DIR/runs" ]]; then
|
||||
run_count=$(find "$OUTPUT_DIR/runs" -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ')
|
||||
fi
|
||||
|
||||
log_warn "This will PERMANENTLY DELETE all backup outputs:"
|
||||
log_warn " Directory: $OUTPUT_DIR"
|
||||
log_warn " Backup runs: $run_count"
|
||||
log_warn ""
|
||||
log_warn "This action cannot be undone!"
|
||||
echo ""
|
||||
echo "Type 'PURGE ALL' to confirm:"
|
||||
read -r confirm
|
||||
[[ "$confirm" == "PURGE ALL" ]] || die "Aborted."
|
||||
|
||||
# Clean up any restore drill temp directories
|
||||
if [[ -d "$OUTPUT_DIR/runs" ]]; then
|
||||
for run_dir in "$OUTPUT_DIR/runs"/*; do
|
||||
if [[ -f "$run_dir/last_restore_dir.txt" ]]; then
|
||||
local restore_dir
|
||||
restore_dir="$(cat "$run_dir/last_restore_dir.txt")"
|
||||
if [[ -d "$restore_dir" ]]; then
|
||||
log_info "Removing restore temp: $restore_dir"
|
||||
rm -rf "$restore_dir"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
log_info "Purging outputs directory..."
|
||||
rm -rf "$OUTPUT_DIR"/*
|
||||
|
||||
log_info "Purge complete."
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
61
backup-sovereign/scripts/rollback/undo_last_backup.sh
Executable file
61
backup-sovereign/scripts/rollback/undo_last_backup.sh
Executable file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# === METADATA ===
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
|
||||
# === CONFIGURATION ===
|
||||
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
||||
|
||||
# === FUNCTIONS ===
|
||||
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
||||
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
|
||||
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
|
||||
die() { log_error "$@"; exit 1; }
|
||||
|
||||
main() {
|
||||
local last_run_file="$OUTPUT_DIR/last_run_dir.txt"
|
||||
|
||||
if [[ ! -f "$last_run_file" ]]; then
|
||||
log_warn "No last run pointer found. Nothing to undo."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
local run_dir
|
||||
run_dir="$(cat "$last_run_file")"
|
||||
|
||||
if [[ ! -d "$run_dir" ]]; then
|
||||
log_warn "Run directory does not exist: $run_dir"
|
||||
rm -f "$last_run_file"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log_warn "This will remove the last backup run:"
|
||||
log_warn " $run_dir"
|
||||
echo ""
|
||||
echo "Type 'DELETE' to confirm:"
|
||||
read -r confirm
|
||||
[[ "$confirm" == "DELETE" ]] || die "Aborted."
|
||||
|
||||
# Clean up restore drill temp directory if it exists
|
||||
if [[ -f "$run_dir/last_restore_dir.txt" ]]; then
|
||||
local restore_dir
|
||||
restore_dir="$(cat "$run_dir/last_restore_dir.txt")"
|
||||
if [[ -d "$restore_dir" ]]; then
|
||||
log_info "Removing restore drill temp: $restore_dir"
|
||||
rm -rf "$restore_dir"
|
||||
fi
|
||||
fi
|
||||
|
||||
log_info "Removing run directory: $run_dir"
|
||||
rm -rf "$run_dir"
|
||||
|
||||
log_info "Removing last run pointer"
|
||||
rm -f "$last_run_file"
|
||||
|
||||
log_info "Undo complete."
|
||||
}
|
||||
|
||||
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|
||||
Reference in New Issue
Block a user