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:
Vault Sovereign
2025-12-27 00:25:00 +00:00
commit eac77ef7b4
213 changed files with 11724 additions and 0 deletions

View 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 "$@"

View 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 "$@"

View 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 "$@"

View 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 "$@"

View 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 "$@"

View 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 "$@"

View 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 "$@"

View 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 "$@"

View 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 "$@"

View 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 "$@"

View 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 "$@"

View 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 "$@"