a1a17e81a1
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
637 lines
23 KiB
Bash
637 lines
23 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
#########################################################################################
|
|
#### jenkins-backup.sh — Backup and restore Jenkins configuration and jobs ####
|
|
#### Supports JENKINS_HOME tar, job XML export, and credential backup ####
|
|
#### Requires: bash 4+, tar, curl ####
|
|
#### ####
|
|
#### Author: Phil Connor ####
|
|
#### Contact: contact@mylinux.work ####
|
|
#### License: MIT ####
|
|
#### Version 1.01 ####
|
|
#### ####
|
|
#### Usage: ####
|
|
#### export JENKINS_HOME="/var/lib/jenkins" ####
|
|
#### ./jenkins-backup.sh --backup ####
|
|
#### ####
|
|
#### See --help for all options. ####
|
|
#########################################################################################
|
|
|
|
set -euo pipefail
|
|
|
|
# ── Defaults ──────────────────────────────────────────────────────────
|
|
JENKINS_HOME="${JENKINS_HOME:-/var/lib/jenkins}"
|
|
BACKUP_DIR="${BACKUP_DIR:-/var/backups/jenkins}"
|
|
RETENTION_COUNT="${RETENTION_COUNT:-7}"
|
|
JENKINS_URL="${JENKINS_URL:-}"
|
|
JENKINS_USER="${JENKINS_USER:-}"
|
|
JENKINS_TOKEN="${JENKINS_TOKEN:-}"
|
|
BACKUP_TYPE="${BACKUP_TYPE:-full}"
|
|
EXCLUDE_PLUGINS="${EXCLUDE_PLUGINS:-false}"
|
|
CURL_TIMEOUT="${CURL_TIMEOUT:-30}"
|
|
CURL_INSECURE="${CURL_INSECURE:-false}"
|
|
VERBOSE="${VERBOSE:-false}"
|
|
COLOR="${COLOR:-auto}"
|
|
|
|
# ── State ─────────────────────────────────────────────────────────────
|
|
SCRIPT_NAME="$(basename "$0")"
|
|
readonly SCRIPT_NAME
|
|
RUN_MODE="backup"
|
|
RESTORE_DIR=""
|
|
DRY_RUN="false"
|
|
CONFIG_ONLY="false"
|
|
API_BACKUP="false"
|
|
TMPDIR_WORK=""
|
|
START_TIME=""
|
|
|
|
# ── Colors ────────────────────────────────────────────────────────────
|
|
setup_colors() {
|
|
if [[ "$COLOR" == "never" ]]; then
|
|
RED="" GREEN="" YELLOW="" BLUE="" BOLD="" RESET=""
|
|
return
|
|
fi
|
|
if [[ "$COLOR" == "always" ]] || [[ -t 1 ]]; then
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
BLUE='\033[0;34m'
|
|
BOLD='\033[1m'
|
|
RESET='\033[0m'
|
|
else
|
|
RED="" GREEN="" YELLOW="" BLUE="" BOLD="" RESET=""
|
|
fi
|
|
}
|
|
|
|
# ── Logging ───────────────────────────────────────────────────────────
|
|
log() { echo -e "${BLUE}[INFO]${RESET} $*"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${RESET} $*" >&2; }
|
|
err() { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
|
|
verbose() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${BLUE}[DEBUG]${RESET} $*"; fi; }
|
|
|
|
# ── Cleanup ───────────────────────────────────────────────────────────
|
|
cleanup() {
|
|
if [[ -n "$TMPDIR_WORK" && -d "$TMPDIR_WORK" ]]; then
|
|
verbose "Cleaning up temp directory: $TMPDIR_WORK"
|
|
rm -rf "$TMPDIR_WORK"
|
|
fi
|
|
}
|
|
|
|
trap cleanup EXIT
|
|
|
|
# ── Helpers ───────────────────────────────────────────────────────────
|
|
human_size() {
|
|
local bytes="$1"
|
|
if [[ "$bytes" -ge 1073741824 ]]; then
|
|
echo "$(( bytes / 1073741824 ))G"
|
|
elif [[ "$bytes" -ge 1048576 ]]; then
|
|
echo "$(( bytes / 1048576 ))M"
|
|
elif [[ "$bytes" -ge 1024 ]]; then
|
|
echo "$(( bytes / 1024 ))K"
|
|
else
|
|
echo "${bytes}B"
|
|
fi
|
|
}
|
|
|
|
elapsed_time() {
|
|
local end_time
|
|
end_time=$(date +%s)
|
|
local duration=$(( end_time - START_TIME ))
|
|
local mins=$(( duration / 60 ))
|
|
local secs=$(( duration % 60 ))
|
|
if [[ "$mins" -gt 0 ]]; then
|
|
echo "${mins}m ${secs}s"
|
|
else
|
|
echo "${secs}s"
|
|
fi
|
|
}
|
|
|
|
jenkins_api() {
|
|
local endpoint="$1"
|
|
local output="${2:-}"
|
|
local url="${JENKINS_URL%/}${endpoint}"
|
|
local curl_opts=(-s -S --max-time "$CURL_TIMEOUT" -f)
|
|
|
|
[[ "$CURL_INSECURE" == "true" ]] && curl_opts+=(-k)
|
|
|
|
if [[ -n "$JENKINS_USER" && -n "$JENKINS_TOKEN" ]]; then
|
|
curl_opts+=(-u "${JENKINS_USER}:${JENKINS_TOKEN}")
|
|
fi
|
|
|
|
if [[ -n "$output" ]]; then
|
|
curl "${curl_opts[@]}" "$url" -o "$output"
|
|
else
|
|
curl "${curl_opts[@]}" "$url"
|
|
fi
|
|
}
|
|
|
|
# ── Show Help ─────────────────────────────────────────────────────────
|
|
show_help() {
|
|
cat << EOF
|
|
Usage: $SCRIPT_NAME [OPTIONS]
|
|
|
|
Backup and restore Jenkins configuration, jobs, credentials, and plugins.
|
|
Supports full JENKINS_HOME archives, config-only snapshots, and remote
|
|
API exports with automatic retention.
|
|
|
|
OPTIONS:
|
|
--backup Run a backup (default)
|
|
--config-only Backup configuration files only (no workspaces/builds)
|
|
--api-backup Export job configs via Jenkins API (remote, no filesystem)
|
|
--restore DIR Restore from the specified backup directory or archive
|
|
--dry-run With --restore, show what would be restored without changes
|
|
--list List available backups
|
|
--verify DIR Verify backup integrity via checksums
|
|
--help, -h Show this help message
|
|
|
|
ENVIRONMENT VARIABLES:
|
|
JENKINS_HOME Jenkins home directory (default: /var/lib/jenkins)
|
|
BACKUP_DIR Root backup directory (default: /var/backups/jenkins)
|
|
RETENTION_COUNT Number of backups to retain (default: 7)
|
|
JENKINS_URL Jenkins base URL (required for --api-backup)
|
|
JENKINS_USER Jenkins username (required for --api-backup)
|
|
JENKINS_TOKEN Jenkins API token (required for --api-backup)
|
|
BACKUP_TYPE Backup type: full, config, api (default: full)
|
|
EXCLUDE_PLUGINS Skip plugin JPI files in config backup (default: false)
|
|
CURL_TIMEOUT HTTP timeout in seconds (default: 30)
|
|
CURL_INSECURE Allow self-signed certs (default: false)
|
|
VERBOSE Enable verbose output (default: false)
|
|
COLOR Color output: auto, always, never (default: auto)
|
|
|
|
EXAMPLES:
|
|
# Full JENKINS_HOME backup
|
|
JENKINS_HOME=/var/lib/jenkins ./jenkins-backup.sh --backup
|
|
|
|
# Config-only backup (XML configs + plugins, no build data)
|
|
./jenkins-backup.sh --config-only
|
|
|
|
# Remote API backup (no filesystem access needed)
|
|
JENKINS_URL=http://localhost:8080 JENKINS_USER=admin JENKINS_TOKEN=xxxx \\
|
|
./jenkins-backup.sh --api-backup
|
|
|
|
# Restore from a backup
|
|
./jenkins-backup.sh --restore /var/backups/jenkins/20260404-120000-full
|
|
|
|
# Dry-run restore
|
|
./jenkins-backup.sh --restore /var/backups/jenkins/20260404-120000-full --dry-run
|
|
|
|
# List available backups
|
|
./jenkins-backup.sh --list
|
|
|
|
# Verify a backup
|
|
./jenkins-backup.sh --verify /var/backups/jenkins/20260404-120000-full
|
|
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
# ── Parse Arguments ───────────────────────────────────────────────────
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--backup) RUN_MODE="backup"; shift ;;
|
|
--config-only) RUN_MODE="backup"; CONFIG_ONLY="true"; BACKUP_TYPE="config"; shift ;;
|
|
--api-backup) RUN_MODE="backup"; API_BACKUP="true"; BACKUP_TYPE="api"; shift ;;
|
|
--restore)
|
|
RUN_MODE="restore"
|
|
if [[ $# -lt 2 ]]; then
|
|
err "--restore requires a directory argument"
|
|
exit 1
|
|
fi
|
|
RESTORE_DIR="$2"; shift 2
|
|
;;
|
|
--dry-run) DRY_RUN="true"; shift ;;
|
|
--list) RUN_MODE="list"; shift ;;
|
|
--verify)
|
|
RUN_MODE="verify"
|
|
if [[ $# -lt 2 ]]; then
|
|
err "--verify requires a directory argument"
|
|
exit 1
|
|
fi
|
|
RESTORE_DIR="$2"; shift 2
|
|
;;
|
|
--help|-h) show_help ;;
|
|
*)
|
|
err "Unknown option: $1"
|
|
echo "Run '$SCRIPT_NAME --help' for usage." >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# ── Validation ────────────────────────────────────────────────────────
|
|
validate_backup() {
|
|
if [[ "$API_BACKUP" == "true" ]]; then
|
|
if [[ -z "$JENKINS_URL" ]]; then
|
|
err "JENKINS_URL is required for --api-backup"
|
|
exit 1
|
|
fi
|
|
if [[ -z "$JENKINS_USER" || -z "$JENKINS_TOKEN" ]]; then
|
|
err "JENKINS_USER and JENKINS_TOKEN are required for --api-backup"
|
|
exit 1
|
|
fi
|
|
if ! command -v curl &>/dev/null; then
|
|
err "curl is required but not installed"
|
|
exit 1
|
|
fi
|
|
else
|
|
if [[ ! -d "$JENKINS_HOME" ]]; then
|
|
err "JENKINS_HOME does not exist: $JENKINS_HOME"
|
|
exit 1
|
|
fi
|
|
for cmd in tar sha256sum; do
|
|
if ! command -v "$cmd" &>/dev/null; then
|
|
err "$cmd is required but not installed"
|
|
exit 1
|
|
fi
|
|
done
|
|
fi
|
|
|
|
mkdir -p "$BACKUP_DIR"
|
|
}
|
|
|
|
# ── Full Backup ───────────────────────────────────────────────────────
|
|
do_full_backup() {
|
|
local timestamp
|
|
timestamp=$(date +%Y%m%d-%H%M%S)
|
|
local dest="$BACKUP_DIR/${timestamp}-full"
|
|
local archive="$dest/jenkins-full-${timestamp}.tar.gz"
|
|
|
|
mkdir -p "$dest"
|
|
log "Starting full backup of $JENKINS_HOME"
|
|
verbose "Destination: $dest"
|
|
|
|
local tar_excludes=(
|
|
--exclude='workspace'
|
|
--exclude='*/builds/*/archive'
|
|
--exclude='*.log'
|
|
)
|
|
|
|
tar czf "$archive" \
|
|
"${tar_excludes[@]}" \
|
|
-C "$(dirname "$JENKINS_HOME")" \
|
|
"$(basename "$JENKINS_HOME")" 2>/dev/null || {
|
|
err "Failed to create tar archive"
|
|
rm -rf "$dest"
|
|
exit 1
|
|
}
|
|
|
|
sha256sum "$archive" > "$dest/checksums.sha256"
|
|
echo "full" > "$dest/.backup-type"
|
|
|
|
local size
|
|
size=$(stat -c%s "$archive" 2>/dev/null || stat -f%z "$archive" 2>/dev/null || echo 0)
|
|
log "Full backup complete: $(human_size "$size") in $(elapsed_time)"
|
|
log "Archive: $archive"
|
|
}
|
|
|
|
# ── Config-Only Backup ────────────────────────────────────────────────
|
|
do_config_backup() {
|
|
local timestamp
|
|
timestamp=$(date +%Y%m%d-%H%M%S)
|
|
local dest="$BACKUP_DIR/${timestamp}-config"
|
|
local archive="$dest/jenkins-config-${timestamp}.tar.gz"
|
|
|
|
mkdir -p "$dest"
|
|
log "Starting config-only backup of $JENKINS_HOME"
|
|
verbose "Destination: $dest"
|
|
|
|
TMPDIR_WORK=$(mktemp -d)
|
|
local staging="$TMPDIR_WORK/jenkins-config"
|
|
mkdir -p "$staging"
|
|
|
|
# Core config files
|
|
for f in config.xml credentials.xml hudson.model.UpdateCenter.xml jenkins.model.JenkinsLocationConfiguration.xml; do
|
|
if [[ -f "$JENKINS_HOME/$f" ]]; then
|
|
cp "$JENKINS_HOME/$f" "$staging/"
|
|
verbose " Copied $f"
|
|
fi
|
|
done
|
|
|
|
# Directories: users, secrets, nodes
|
|
for d in users secrets nodes; do
|
|
if [[ -d "$JENKINS_HOME/$d" ]]; then
|
|
cp -r "$JENKINS_HOME/$d" "$staging/"
|
|
verbose " Copied $d/"
|
|
fi
|
|
done
|
|
|
|
# Job configs (config.xml only)
|
|
if [[ -d "$JENKINS_HOME/jobs" ]]; then
|
|
find "$JENKINS_HOME/jobs" -name config.xml -type f | while IFS= read -r jobxml; do
|
|
local relpath
|
|
relpath="${jobxml#"$JENKINS_HOME"/}"
|
|
local target_dir
|
|
target_dir="$staging/$(dirname "$relpath")"
|
|
mkdir -p "$target_dir"
|
|
cp "$jobxml" "$target_dir/"
|
|
done
|
|
verbose " Copied job configs"
|
|
fi
|
|
|
|
# Plugin JPI files
|
|
if [[ "$EXCLUDE_PLUGINS" != "true" && -d "$JENKINS_HOME/plugins" ]]; then
|
|
mkdir -p "$staging/plugins"
|
|
find "$JENKINS_HOME/plugins" -maxdepth 1 -name '*.jpi' -exec cp {} "$staging/plugins/" \;
|
|
local plugin_count
|
|
plugin_count=$(find "$staging/plugins" -name '*.jpi' 2>/dev/null | wc -l)
|
|
verbose " Copied $plugin_count plugin files"
|
|
fi
|
|
|
|
tar czf "$archive" -C "$TMPDIR_WORK" "jenkins-config" 2>/dev/null || {
|
|
err "Failed to create config archive"
|
|
rm -rf "$dest"
|
|
exit 1
|
|
}
|
|
|
|
sha256sum "$archive" > "$dest/checksums.sha256"
|
|
echo "config" > "$dest/.backup-type"
|
|
|
|
local size
|
|
size=$(stat -c%s "$archive" 2>/dev/null || stat -f%z "$archive" 2>/dev/null || echo 0)
|
|
log "Config backup complete: $(human_size "$size") in $(elapsed_time)"
|
|
log "Archive: $archive"
|
|
}
|
|
|
|
# ── API Backup ────────────────────────────────────────────────────────
|
|
do_api_backup() {
|
|
local timestamp
|
|
timestamp=$(date +%Y%m%d-%H%M%S)
|
|
local dest="$BACKUP_DIR/${timestamp}-api"
|
|
|
|
mkdir -p "$dest/jobs" "$dest/plugins"
|
|
log "Starting API backup from $JENKINS_URL"
|
|
verbose "Destination: $dest"
|
|
|
|
# Get job list — parse JSON without jq
|
|
local job_list_json
|
|
job_list_json=$(jenkins_api "/api/json?tree=jobs[name]") || {
|
|
err "Failed to fetch job list from Jenkins API"
|
|
rm -rf "$dest"
|
|
exit 1
|
|
}
|
|
|
|
# Extract job names from JSON using grep/sed
|
|
local job_names
|
|
job_names=$(echo "$job_list_json" | { grep -o '"name" *: *"[^"]*"' || true; } | sed 's/.*: *"//;s/"$//')
|
|
|
|
local job_count=0
|
|
local job_fail=0
|
|
if [[ -n "$job_names" ]]; then
|
|
while IFS= read -r job_name; do
|
|
[[ -z "$job_name" ]] && continue
|
|
local encoded_name
|
|
encoded_name="${job_name// /%20}"
|
|
if jenkins_api "/job/${encoded_name}/config.xml" "$dest/jobs/${job_name}.xml" 2>/dev/null; then
|
|
verbose " Exported job: $job_name"
|
|
job_count=$((job_count + 1))
|
|
else
|
|
warn "Failed to export job: $job_name"
|
|
job_fail=$((job_fail + 1))
|
|
fi
|
|
done <<< "$job_names"
|
|
fi
|
|
log "Exported $job_count jobs ($job_fail failed)"
|
|
|
|
# Plugin list
|
|
local plugin_json
|
|
if plugin_json=$(jenkins_api "/pluginManager/api/json?depth=1&tree=plugins[shortName,version,enabled,active]"); then
|
|
echo "$plugin_json" > "$dest/plugins/plugin-list.json"
|
|
local plugin_count
|
|
plugin_count=$(echo "$plugin_json" | { grep -o '"shortName"' || true; } | wc -l)
|
|
log "Exported plugin list: $plugin_count plugins"
|
|
else
|
|
warn "Failed to fetch plugin list"
|
|
fi
|
|
|
|
# Create checksum file for all exported files
|
|
if command -v sha256sum &>/dev/null; then
|
|
find "$dest" -type f ! -name 'checksums.sha256' -exec sha256sum {} + > "$dest/checksums.sha256" 2>/dev/null || true
|
|
fi
|
|
echo "api" > "$dest/.backup-type"
|
|
|
|
local size
|
|
size=$(du -sb "$dest" 2>/dev/null | cut -f1)
|
|
size="${size:-0}"
|
|
log "API backup complete: $(human_size "$size") in $(elapsed_time)"
|
|
log "Backup directory: $dest"
|
|
}
|
|
|
|
# ── Backup Entry Point ───────────────────────────────────────────────
|
|
do_backup() {
|
|
START_TIME=$(date +%s)
|
|
validate_backup
|
|
|
|
if [[ "$API_BACKUP" == "true" ]]; then
|
|
do_api_backup
|
|
elif [[ "$CONFIG_ONLY" == "true" ]]; then
|
|
do_config_backup
|
|
else
|
|
do_full_backup
|
|
fi
|
|
|
|
prune_backups
|
|
}
|
|
|
|
# ── Prune ─────────────────────────────────────────────────────────────
|
|
prune_backups() {
|
|
log "Pruning old backups (retaining $RETENTION_COUNT)..."
|
|
|
|
local backup_count
|
|
backup_count=$(find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d | wc -l)
|
|
|
|
if [[ "$backup_count" -le "$RETENTION_COUNT" ]]; then
|
|
log " No pruning needed ($backup_count backups present)"
|
|
return 0
|
|
fi
|
|
|
|
local remove_count=$((backup_count - RETENTION_COUNT))
|
|
find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d | sort | head -n "$remove_count" | while IFS= read -r dir; do
|
|
log " Removing $(basename "$dir")"
|
|
rm -rf "$dir"
|
|
done
|
|
|
|
log " Pruned $remove_count old backups"
|
|
}
|
|
|
|
# ── Restore ───────────────────────────────────────────────────────────
|
|
do_restore() {
|
|
if [[ -z "$RESTORE_DIR" ]]; then
|
|
err "--restore requires a directory argument"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -d "$RESTORE_DIR" ]]; then
|
|
err "Restore directory does not exist: $RESTORE_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
# Find the tar archive in the backup directory
|
|
local archive
|
|
archive=$(find "$RESTORE_DIR" -maxdepth 1 -name '*.tar.gz' -type f | head -1)
|
|
|
|
if [[ -z "$archive" ]]; then
|
|
err "No tar archive found in: $RESTORE_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
log "Restore source: $archive"
|
|
log "Restore target: $JENKINS_HOME"
|
|
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
log "${BOLD}Dry run — listing archive contents:${RESET}"
|
|
tar tzf "$archive" | head -50
|
|
local total
|
|
total=$(tar tzf "$archive" | wc -l)
|
|
log " ($total files total)"
|
|
log "Dry run complete — no changes made"
|
|
return 0
|
|
fi
|
|
|
|
# Verify checksum if available
|
|
if [[ -f "$RESTORE_DIR/checksums.sha256" ]]; then
|
|
log "Verifying backup integrity..."
|
|
if (cd "$RESTORE_DIR" && sha256sum -c checksums.sha256 --quiet 2>/dev/null); then
|
|
log " ${GREEN}Checksums verified${RESET}"
|
|
else
|
|
warn "Checksum verification failed — proceed with caution"
|
|
fi
|
|
fi
|
|
|
|
if [[ ! -d "$JENKINS_HOME" ]]; then
|
|
mkdir -p "$JENKINS_HOME"
|
|
fi
|
|
|
|
log "Restoring archive..."
|
|
tar xzf "$archive" -C "$(dirname "$JENKINS_HOME")" || {
|
|
err "Failed to extract archive"
|
|
exit 1
|
|
}
|
|
|
|
warn "Jenkins must be restarted for changes to take effect"
|
|
warn " sudo systemctl restart jenkins"
|
|
log "Restore complete"
|
|
}
|
|
|
|
# ── List ──────────────────────────────────────────────────────────────
|
|
do_list() {
|
|
if [[ ! -d "$BACKUP_DIR" ]]; then
|
|
log "No backups found (directory does not exist: $BACKUP_DIR)"
|
|
return 0
|
|
fi
|
|
|
|
local count=0
|
|
local format=" %-28s %-8s %s\n"
|
|
|
|
printf "\n"
|
|
# shellcheck disable=SC2059
|
|
printf "$format" "BACKUP" "TYPE" "SIZE"
|
|
# shellcheck disable=SC2059
|
|
printf "$format" "----------------------------" "--------" "--------"
|
|
|
|
for dir in "$BACKUP_DIR"/*/; do
|
|
[[ -d "$dir" ]] || continue
|
|
count=$((count + 1))
|
|
|
|
local name
|
|
name=$(basename "$dir")
|
|
|
|
local btype="unknown"
|
|
if [[ -f "$dir/.backup-type" ]]; then
|
|
btype=$(cat "$dir/.backup-type")
|
|
fi
|
|
|
|
local size
|
|
size=$(du -sh "$dir" 2>/dev/null | cut -f1)
|
|
size="${size:-?}"
|
|
|
|
# shellcheck disable=SC2059
|
|
printf "$format" "$name" "$btype" "$size"
|
|
done
|
|
|
|
printf "\n"
|
|
if [[ "$count" -eq 0 ]]; then
|
|
log "No backups found in $BACKUP_DIR"
|
|
else
|
|
log "$count backup(s) in $BACKUP_DIR"
|
|
fi
|
|
}
|
|
|
|
# ── Verify ────────────────────────────────────────────────────────────
|
|
do_verify() {
|
|
if [[ -z "$RESTORE_DIR" ]]; then
|
|
err "--verify requires a directory argument"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -d "$RESTORE_DIR" ]]; then
|
|
err "Backup directory does not exist: $RESTORE_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
log "Verifying backup: $RESTORE_DIR"
|
|
|
|
# Check backup type
|
|
local btype="unknown"
|
|
if [[ -f "$RESTORE_DIR/.backup-type" ]]; then
|
|
btype=$(cat "$RESTORE_DIR/.backup-type")
|
|
fi
|
|
log " Backup type: $btype"
|
|
|
|
# Check for checksums
|
|
if [[ -f "$RESTORE_DIR/checksums.sha256" ]]; then
|
|
log " Verifying checksums..."
|
|
if (cd "$RESTORE_DIR" && sha256sum -c checksums.sha256 2>/dev/null); then
|
|
log " ${GREEN}All checksums passed${RESET}"
|
|
else
|
|
err "Checksum verification FAILED"
|
|
exit 1
|
|
fi
|
|
else
|
|
warn "No checksum file found — cannot verify integrity"
|
|
fi
|
|
|
|
# Archive contents summary
|
|
local archive
|
|
archive=$(find "$RESTORE_DIR" -maxdepth 1 -name '*.tar.gz' -type f | head -1)
|
|
if [[ -n "$archive" ]]; then
|
|
local size
|
|
size=$(stat -c%s "$archive" 2>/dev/null || stat -f%z "$archive" 2>/dev/null || echo 0)
|
|
log " Archive: $(basename "$archive") ($(human_size "$size"))"
|
|
local file_count
|
|
file_count=$(tar tzf "$archive" | wc -l)
|
|
log " Files in archive: $file_count"
|
|
fi
|
|
|
|
# API backup — list exported files
|
|
if [[ "$btype" == "api" ]]; then
|
|
local job_count
|
|
job_count=$(find "$RESTORE_DIR/jobs" -name '*.xml' 2>/dev/null | wc -l)
|
|
log " Exported jobs: $job_count"
|
|
if [[ -f "$RESTORE_DIR/plugins/plugin-list.json" ]]; then
|
|
local plugin_count
|
|
plugin_count=$({ grep -o '"shortName"' "$RESTORE_DIR/plugins/plugin-list.json" || true; } | wc -l)
|
|
log " Plugins recorded: $plugin_count"
|
|
fi
|
|
fi
|
|
|
|
log "Verification complete"
|
|
}
|
|
|
|
# ── Main ──────────────────────────────────────────────────────────────
|
|
main() {
|
|
setup_colors
|
|
|
|
parse_args "$@"
|
|
|
|
case "$RUN_MODE" in
|
|
backup) do_backup ;;
|
|
restore) do_restore ;;
|
|
list) do_list ;;
|
|
verify) do_verify ;;
|
|
*) err "Unknown mode: $RUN_MODE"; exit 1 ;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|