Files
ubuntu-post-install/services/backup.sh
T
Claude 7ae5a20e0e Rename backup services: backup (comprehensive) + gaming-backup (saves-only)
- backup: renamed from disaster-backup; full ~/docker/<service>/ snapshots,
  Minecraft flushed not stopped, everything else stop/snap/start for DB
  consistency; backup.conf / restore/ layout; post-install-backup timer
- gaming-backup: renamed from backup; frequent game-save snapshots (Minecraft
  world data, emulator saves, Steam, Wolf state) with no service downtime;
  defaults to hourly schedule; separate gaming-kopia repo + gaming-backup timer
- Deleted services/disaster-backup.sh
- README: updated backup row to reflect new names

https://claude.ai/code/session_019XgsQ13XKm4Zj3cNsDNwHj
2026-06-04 15:58:15 +00:00

574 lines
29 KiB
Bash

#!/bin/bash
# services/backup.sh — Full Docker-service backup via Kopia.
# Part of the modular post-install system (sourced by setup.sh).
#
# Backs up each entire ~/docker/<service>/ directory (compose file, config, data,
# databases — everything needed to restore from nothing). Per-service behaviour:
# Minecraft instances — flush world to disk (save-all), snapshot, no downtime
# All other services — stop, snapshot, restart (seconds of downtime each)
#
# Different services can be routed to different Kopia repos / drives.
# New services are auto-discovered on every run — no reconfiguration needed.
#
# Creates: ~/docker/backup/
# backup.conf settings + per-service destination map (chmod 600)
# backup.sh worker (run directly or via systemd timer)
# restore/<dest>/ restore_kopia_backup.sh + backup.conf per destination
register_service backup backup "Encrypted backup of all Docker services (full restore)"
install_backup() {
require_docker || return 1
local DIR="$DOCKER_DIR/backup"
local CONF_FILE="$DIR/backup.conf"
local WORKER="$DIR/backup.sh"
local RESTORE_DIR="$DIR/restore"
local RESTORE_SRC="${HERE:-}/extras/restore_kopia_backup.sh"
local SVC_NAME="post-install-backup"
echo ""
echo "╔═══════════════════════════════════════════════════════════════════╗"
echo "║ BACKUP STRATEGIES — choose the right tool for your data ║"
echo "╚═══════════════════════════════════════════════════════════════════╝"
echo ""
echo " This installer sets up Kopia for full service backup, but here is"
echo " a quick reference to all available options."
echo ""
echo " ┌─────────────────────────────────────────────────────────────────┐"
echo " │ KOPIA (installed here) — block-level dedup + zstd + encryption │"
echo " │ Use for: files that change constantly — Minecraft worlds, │"
echo " │ databases, configs, entire Docker service directories. │"
echo " │ Changed blocks stored once; unchanged blocks share space. │"
echo " │ Restores via: restore_kopia_backup.sh (interactive) │"
echo " └─────────────────────────────────────────────────────────────────┘"
echo ""
echo " ┌─────────────────────────────────────────────────────────────────┐"
echo " │ BORG (sudo apt install borgbackup) — chunk dedup + encryption │"
echo " │ Use for: same as Kopia. Choose Borg if you prefer Borgmatic │"
echo " │ (YAML config), Vorta (GUI), or multi-machine repos. │"
echo " │ borg init / borg create / borg list / borg extract │"
echo " └─────────────────────────────────────────────────────────────────┘"
echo ""
echo " ┌─────────────────────────────────────────────────────────────────┐"
echo " │ RSYNC plain rsync -av --delete /src/ /dest/ │"
echo " │ Use for: media, ROMs, files that rarely change and you just │"
echo " │ need a copy. Fast, transparent — no special restore tool. │"
echo " │ Not suitable for files that change often (one bad --delete │"
echo " │ run immediately destroys the only copy in the destination). │"
echo " └─────────────────────────────────────────────────────────────────┘"
echo ""
echo " ┌─────────────────────────────────────────────────────────────────┐"
echo " │ RSYNC --link-dest versioned snapshots, original folder layout │"
echo " │ Creates dated dirs (2024-01-15/, 2024-01-16/, …). │"
echo " │ Unchanged files are hard-linked — cost no extra disk space. │"
echo " │ Each dated dir is a complete, browsable snapshot. │"
echo " │ Use for: general files where you want versioning + readable │"
echo " │ snapshot dirs without a special restore tool. │"
echo " └─────────────────────────────────────────────────────────────────┘"
echo ""
echo " ┌─────────────────────────────────────────────────────────────────┐"
echo " │ RSNAPSHOT (sudo apt install rsnapshot) — automated rotation │"
echo " │ Wraps rsync with a retention scheme (daily.0, weekly.0, …). │"
echo " │ Hard-links unchanged files; dirs named by rsnapshot. │"
echo " │ Use for: automated versioning without scripting --link-dest. │"
echo " └─────────────────────────────────────────────────────────────────┘"
echo ""
echo " Quick reference:"
echo " Full service recovery, databases, configs → Kopia (this installer)"
echo " Frequently changing saves without downtime → gaming-backup service"
echo " Media / ROMs (rarely changes, just need a copy) → rsync plain"
echo " Versioned snapshots, keep original folder layout → rsync --link-dest"
echo " Versioned snapshots, want auto rotation scripted → rsnapshot"
echo ""
echo " Continuing with Kopia setup..."
echo ""
echo "╔═══════════════════════════════════════════════════════╗"
echo "║ Backup Setup · Kopia ║"
echo "║ Full ~/docker/<service>/ snapshots ║"
echo "╚═══════════════════════════════════════════════════════╝"
echo ""
echo " Backs up each entire service directory — compose file, config, data,"
echo " databases, everything needed to restore a service from scratch."
echo ""
echo " Minecraft: world flushed to disk (save-all), snapshot, NO downtime."
echo " Everything else: stopped briefly, snapshotted, restarted."
echo ""
echo " Different services can go to different drives / Kopia repos."
echo " New services are auto-detected on every run — no reconfiguration needed."
echo ""
if [ "$DRY_RUN" = true ]; then
echo "[DRY-RUN] Would discover services under $DOCKER_DIR"
echo "[DRY-RUN] Would create $DIR with conf, worker, and restore scripts"
echo "[DRY-RUN] Would create Kopia repo(s) at user-specified paths"
echo "[DRY-RUN] Would install systemd timer"
return 0
fi
# ── 1. Kopia ─────────────────────────────────────────────────────────────
if ! command -v kopia >/dev/null 2>&1; then
log_info "Installing Kopia..."
if command -v apt-get >/dev/null 2>&1; then
install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://kopia.io/signing-key \
| gpg --dearmor --yes -o /etc/apt/keyrings/kopia-keyring.gpg \
&& echo "deb [signed-by=/etc/apt/keyrings/kopia-keyring.gpg] http://packages.kopia.io/apt/ stable main" \
> /etc/apt/sources.list.d/kopia.list \
&& apt-get update -y && apt-get install -y kopia
fi
fi
command -v kopia >/dev/null 2>&1 \
|| { log_error "Kopia not installed. See https://kopia.io/docs/installation/"; return 1; }
local KOPIA_BIN; KOPIA_BIN="$(command -v kopia)"
log_success "Kopia: $("$KOPIA_BIN" --version 2>/dev/null | head -1)"
# ── 2. Discover installed services ───────────────────────────────────────
local -a ALL_SVCS=()
local d svc
for d in "$DOCKER_DIR"/*/; do
[ -f "${d}docker-compose.yml" ] || continue
svc="$(basename "$d")"
[[ "$svc" == "backup" || "$svc" == "gaming-backup" ]] && continue
ALL_SVCS+=("$svc")
done
if [ "${#ALL_SVCS[@]}" -eq 0 ]; then
log_warning "No services found under $DOCKER_DIR — auto-detected on each backup run."
else
log_info "Services found: ${ALL_SVCS[*]}"
fi
# ── 3. Destinations ───────────────────────────────────────────────────────
echo ""
echo "═══════════════════════════════════════════════════════"
echo " BACKUP DESTINATIONS"
echo "═══════════════════════════════════════════════════════"
echo ""
echo " Each destination is a Kopia repository directory."
echo " For best resilience: use a different drive or mount point from your data."
echo " Destination names must be letters, numbers, and underscores only."
echo ""
local DEFAULT_DEST="$ACTUAL_HOME/backups/kopia-backup"
local _repo=""
prompt_text " Default repository path [${DEFAULT_DEST}]:" "$DEFAULT_DEST" _repo
_repo="${_repo/#\~/$ACTUAL_HOME}"; _repo="${_repo%/}"
local -a DEST_NAMES_ARR=("default")
local -A DEST_REPOS=() DEST_PASSWORDS=() DEST_CONFIGS=()
DEST_REPOS["default"]="$_repo"
DEST_CONFIGS["default"]="/etc/kopia-backup/default.config"
local _extra=""
prompt_yn " Add more destinations (for services on different drives)? (y/N):" "n" _extra
if [[ "$_extra" =~ ^[Yy]$ ]]; then
echo ""
local _dn _dr
while true; do
prompt_text " Destination name (blank to finish):" "" _dn
[ -z "$_dn" ] && break
_dn="${_dn//[^a-zA-Z0-9_]/_}"
[ "$_dn" = "default" ] && { log_warning " 'default' is reserved — use another name."; continue; }
prompt_text " Path for '$_dn' repository:" "" _dr
[ -z "$_dr" ] && continue
_dr="${_dr/#\~/$ACTUAL_HOME}"; _dr="${_dr%/}"
DEST_REPOS["$_dn"]="$_dr"
DEST_CONFIGS["$_dn"]="/etc/kopia-backup/${_dn}.config"
DEST_NAMES_ARR+=("$_dn")
log_success " Destination '$_dn' → $_dr"
done
fi
# ── 4. Service → destination assignment ──────────────────────────────────
local -A SVC_DEST_MAP=()
if [ "${#ALL_SVCS[@]}" -gt 0 ] && [ "${#DEST_NAMES_ARR[@]}" -gt 1 ]; then
echo ""
echo "═══════════════════════════════════════════════════════"
echo " ASSIGN SERVICES TO DESTINATIONS"
echo "═══════════════════════════════════════════════════════"
echo ""
echo " Destinations:"
local dn
for dn in "${DEST_NAMES_ARR[@]}"; do
printf " %-16s %s\n" "$dn" "${DEST_REPOS[$dn]}"
done
echo ""
echo " Press Enter to accept the default for each service."
echo ""
local _d
for svc in "${ALL_SVCS[@]}"; do
prompt_text " $svc [default]:" "default" _d
if [ -n "$_d" ] && [ "$_d" != "default" ] && [ -n "${DEST_REPOS[$_d]:-}" ]; then
SVC_DEST_MAP["$svc"]="$_d"
fi
done
fi
# ── 5. Passwords ─────────────────────────────────────────────────────────
echo ""
log_info "Setting repository passwords (stored in backup.conf, chmod 600)..."
for dn in "${DEST_NAMES_ARR[@]}"; do
local pw=""
if [ "$UNATTENDED" = true ]; then
pw="$(generate_password 32)"
else
read -rsp " Password for '$dn' [Enter = auto-generate]: " pw; echo
fi
[ -z "$pw" ] && pw="$(generate_password 32)" && log_info " Auto-generated password for '$dn'."
DEST_PASSWORDS["$dn"]="$pw"
done
# ── 6. Schedule ───────────────────────────────────────────────────────────
echo ""
echo "═══════════════════════════════════════════════════════"
echo " SCHEDULE & RETENTION"
echo "═══════════════════════════════════════════════════════"
echo ""
echo " Minecraft runs uninterrupted; other services stop briefly (seconds each)."
echo " Schedule for off-peak hours."
echo ""
echo " 1) Daily at 02:00 (recommended)"
echo " 2) Every 12 hours"
echo " 3) Weekly (Sunday 02:00)"
echo " 4) Custom (systemd OnCalendar)"
echo ""
local _sch=""
prompt_text " How often? [1]:" "1" _sch
local ONCALENDAR SCHED_LABEL
case "${_sch:-1}" in
2) ONCALENDAR="*-*-* 02,14:00:00"; SCHED_LABEL="every 12 hours" ;;
3) ONCALENDAR="Sun *-*-* 02:00:00"; SCHED_LABEL="weekly Sunday 02:00" ;;
4) prompt_text " OnCalendar expression:" "*-*-* 02:00:00" ONCALENDAR; SCHED_LABEL="$ONCALENDAR" ;;
*) ONCALENDAR="*-*-* 02:00:00"; SCHED_LABEL="daily at 02:00" ;;
esac
local KEEP_LATEST=""
prompt_text " Snapshots to keep (latest)? [7]:" "7" KEEP_LATEST
KEEP_LATEST="${KEEP_LATEST:-7}"
# ── 7. Create dirs + init Kopia repos ────────────────────────────────────
mkdir -p "$DIR" "$RESTORE_DIR"
ensure_docker_dir_ownership "$DIR"
local repo pw cfg
for dn in "${DEST_NAMES_ARR[@]}"; do
repo="${DEST_REPOS[$dn]}"
pw="${DEST_PASSWORDS[$dn]}"
cfg="${DEST_CONFIGS[$dn]}"
mkdir -p "$repo" "$(dirname "$cfg")" /var/cache/kopia-backup
kp_d() { env KOPIA_PASSWORD="$pw" "$KOPIA_BIN" --config-file="$cfg" "$@"; }
if kp_d repository status >/dev/null 2>&1; then
log_success "Connected to existing repo '$dn'."
elif test -e "$repo/kopia.repository.f"; then
log_info "Connecting to existing repo '$dn' at $repo ..."
kp_d repository connect filesystem --path="$repo" \
--cache-directory=/var/cache/kopia-backup \
|| { log_error "Failed to connect to '$dn' repo."; return 1; }
else
log_info "Creating repo '$dn' at $repo ..."
kp_d repository create filesystem --path="$repo" \
--cache-directory=/var/cache/kopia-backup \
|| { log_error "Failed to create '$dn' repo."; return 1; }
fi
kp_d policy set --global --compression=zstd \
--keep-latest="$KEEP_LATEST" \
--keep-daily=7 --keep-weekly=4 --keep-monthly=3 \
--keep-annual=0 --keep-hourly=0 >/dev/null
log_success "Repo '$dn' ready at $repo"
done
unset -f kp_d
# ── 8. Write backup.conf ─────────────────────────────────────────────────
log_info "Writing $CONF_FILE ..."
{
echo "# ── backup.conf ────────────────────────────────────────────────────────────"
echo "# Generated $(date '+%F %T'). Safe to hand-edit."
echo "# Worker : sudo $WORKER"
echo "# Restore: sudo $RESTORE_DIR/<dest>/restore_kopia_backup.sh"
echo ""
echo "KOPIA=\"$KOPIA_BIN\""
echo ""
echo "# Space-separated list of destination names (defines iteration order)."
echo "DEST_NAMES=\"${DEST_NAMES_ARR[*]}\""
echo "DEST_DEFAULT=\"default\""
echo ""
for dn in "${DEST_NAMES_ARR[@]}"; do
echo "# ── destination: $dn"
echo "DEST_${dn}_REPO=\"${DEST_REPOS[$dn]}\""
echo "DEST_${dn}_CONFIG=\"${DEST_CONFIGS[$dn]}\""
printf "DEST_%s_PASSWORD='%s'\n" "$dn" "${DEST_PASSWORDS[$dn]}"
echo ""
done
echo "# ── Service → destination map ───────────────────────────────────────────────"
echo "# Format: SVC_<name>=<dest_name> (hyphens in service names become underscores)"
echo "# Omit a service (or comment it out) to use DEST_DEFAULT."
for svc in "${ALL_SVCS[@]}"; do
local svc_var="${svc//-/_}"
local dest_val="${SVC_DEST_MAP[$svc]:-}"
if [ -n "$dest_val" ]; then
echo "SVC_${svc_var}=\"${dest_val}\""
else
echo "# SVC_${svc_var}=\"default\""
fi
done
echo ""
echo "# ── Optional offsite mirror ─────────────────────────────────────────────────"
echo "# Mirror ALL repos offsite after each run (see kopia repository sync-to --help)."
echo "# Example SFTP: REMOTE_TYPE=sftp REMOTE_ARGS=\"--host H --username U --path /srv/...\""
echo "REMOTE_TYPE=\"none\""
echo "REMOTE_ARGS=\"\""
} > "$CONF_FILE"
chown root:root "$CONF_FILE" 2>/dev/null || true
chmod 600 "$CONF_FILE"
log_success "backup.conf written (chmod 600)"
# ── 9. Generate worker script ─────────────────────────────────────────────
log_info "Writing worker $WORKER ..."
cat > "$WORKER" << 'WORKEREOF'
#!/bin/bash
# Generated by the backup installer.
# Backs up full ~/docker/<service>/ directories via Kopia.
# Minecraft instances: flush to disk (save-all) then snapshot — no downtime.
# All other services: stop → snapshot → restart for consistency.
#
# sudo ./backup.sh run a full backup cycle
# sudo ./backup.sh snapshots list all snapshots (all repos)
# sudo ./backup.sh policy show retention policies
#
# Reads backup.conf from the same directory.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONF="${BACKUP_CONF:-$HERE/backup.conf}"
[ -f "$CONF" ] || { echo "Config not found: $CONF (re-run the backup service)"; exit 1; }
# shellcheck source=/dev/null
source "$CONF"
ACTUAL_USER="${SUDO_USER:-${USER:-$(id -un)}}"
ACTUAL_HOME="$(getent passwd "$ACTUAL_USER" 2>/dev/null | cut -d: -f6 || echo "/home/$ACTUAL_USER")"
DOCKER_DIR="$ACTUAL_HOME/docker"
log() { echo "[$(date '+%F %T')] $*"; }
kp_for() {
local dest="$1"; shift
local cfg_var="DEST_${dest}_CONFIG" pw_var="DEST_${dest}_PASSWORD"
local cfg="${!cfg_var:-}" pw="${!pw_var:-}"
[ -n "$cfg" ] || { log "Unknown destination: $dest"; return 1; }
env KOPIA_PASSWORD="$pw" "$KOPIA" --config-file="$cfg" "$@"
}
dest_for_svc() {
local var="SVC_${1//-/_}"
echo "${!var:-${DEST_DEFAULT:-default}}"
}
# Detect itzg Minecraft instances by their Dockerfile signature.
is_minecraft() { [ -f "${1}Dockerfile" ] && grep -qs itzg "${1}Dockerfile"; }
case "${1:-run}" in
snapshots)
for dest in ${DEST_NAMES:-default}; do
echo ""; echo "── dest: $dest ──"
kp_for "$dest" snapshot list 2>/dev/null || true
done
exit 0 ;;
policy)
for dest in ${DEST_NAMES:-default}; do
echo ""; echo "── dest: $dest ──"
kp_for "$dest" policy show --global 2>/dev/null || true
done
exit 0 ;;
esac
log "===== Backup starting ====="
rc=0
for svc_dir in "$DOCKER_DIR"/*/; do
[ -f "${svc_dir}docker-compose.yml" ] || continue
svc="$(basename "$svc_dir")"
[[ "$svc" == "backup" || "$svc" == "gaming-backup" ]] && continue
dest="$(dest_for_svc "$svc")"
_repo_var="DEST_${dest}_REPO"
[ -n "${!_repo_var:-}" ] || { log "SKIP $svc — dest '$dest' not configured in conf"; continue; }
if is_minecraft "$svc_dir"; then
# ── Minecraft: flush world to disk, snapshot without stopping ────────
if docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "$svc"; then
log "Flushing Minecraft world '$svc' (save-all, no downtime)..."
docker exec "$svc" mc-send-to-console save-all flush 2>/dev/null \
|| docker exec "$svc" rcon-cli save-all 2>/dev/null || true
sleep 5
fi
log "Snapshotting $svc (dest: $dest)..."
if kp_for "$dest" snapshot create --description="backup: $svc" "$svc_dir"; then
log "OK $svc (Minecraft, no downtime)"
else
log "WARNING: snapshot failed for $svc"; rc=1
fi
else
# ── All other services: stop → snapshot → restart ─────────────────
STOPPED=false
if docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "$svc"; then
log "Stopping $svc..."
docker compose -f "${svc_dir}docker-compose.yml" down 2>/dev/null \
|| docker stop "$svc" 2>/dev/null \
|| log "WARNING: could not stop $svc — snapshotting live (consistency not guaranteed)"
STOPPED=true
fi
log "Snapshotting $svc (dest: $dest)..."
if kp_for "$dest" snapshot create --description="backup: $svc" "$svc_dir"; then
log "OK $svc"
else
log "WARNING: snapshot failed for $svc"; rc=1
fi
if [ "$STOPPED" = true ]; then
log "Starting $svc..."
docker compose -f "${svc_dir}docker-compose.yml" up -d 2>/dev/null \
|| log "WARNING: could not restart $svc — run: docker compose -f ${svc_dir}docker-compose.yml up -d"
fi
fi
done
if [ "${REMOTE_TYPE:-none}" != "none" ] && [ -n "${REMOTE_TYPE:-}" ]; then
for dest in ${DEST_NAMES:-default}; do
log "Mirroring '$dest' offsite ($REMOTE_TYPE)..."
# shellcheck disable=SC2086
kp_for "$dest" repository sync-to "$REMOTE_TYPE" $REMOTE_ARGS \
|| { log "WARNING: mirror failed for '$dest'"; rc=1; }
done
fi
if [ "$rc" -eq 0 ]; then
log "===== Backup complete ====="
else
log "===== Backup finished WITH WARNINGS (see above) ====="
fi
exit "$rc"
WORKEREOF
chmod +x "$WORKER"
chown root:root "$WORKER" 2>/dev/null || true
log_success "backup.sh written"
# ── 10. Restore scripts (one per destination) ────────────────────────────
if [ -f "$RESTORE_SRC" ]; then
for dn in "${DEST_NAMES_ARR[@]}"; do
local dest_rdir="$RESTORE_DIR/$dn"
mkdir -p "$dest_rdir"
cp "$RESTORE_SRC" "$dest_rdir/restore_kopia_backup.sh"
chmod +x "$dest_rdir/restore_kopia_backup.sh"
{
echo "# backup.conf for backup destination '$dn'"
echo "# Read by restore_kopia_backup.sh in this directory."
echo "KOPIA=\"$KOPIA_BIN\""
echo "KOPIA_CONFIG=\"${DEST_CONFIGS[$dn]}\""
printf "KOPIA_PASSWORD='%s'\n" "${DEST_PASSWORDS[$dn]}"
} > "$dest_rdir/backup.conf"
chown root:root "$dest_rdir/backup.conf" 2>/dev/null || true
chmod 600 "$dest_rdir/backup.conf"
log_success "restore/$dn/ ready"
done
else
log_warning "extras/restore_kopia_backup.sh not found — restore scripts not installed"
log_warning "Copy it manually: cp extras/restore_kopia_backup.sh $RESTORE_DIR/<dest>/"
fi
# ── 11. Systemd timer ────────────────────────────────────────────────────
log_info "Installing systemd timer ($SCHED_LABEL)..."
local AUTORUN=""
if command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then
tee "/etc/systemd/system/${SVC_NAME}.service" >/dev/null << SVCEOF
[Unit]
Description=Post-install backup (full Docker service directories via Kopia)
After=docker.service network-online.target
Wants=docker.service
[Service]
Type=oneshot
ExecStart=/bin/bash $WORKER run
SVCEOF
tee "/etc/systemd/system/${SVC_NAME}.timer" >/dev/null << SVCEOF
[Unit]
Description=Schedule post-install backup ($SCHED_LABEL)
[Timer]
OnCalendar=$ONCALENDAR
Persistent=true
RandomizedDelaySec=300
[Install]
WantedBy=timers.target
SVCEOF
systemctl daemon-reload
systemctl enable --now "${SVC_NAME}.timer"
log_success "Timer enabled: $SCHED_LABEL"
AUTORUN="systemctl list-timers ${SVC_NAME}.timer"
else
log_warning "systemd not detected — installing cron fallback."
local CRON
case "${_sch:-1}" in
2) CRON="0 2,14 * * *" ;;
3) CRON="0 2 * * 0" ;;
*) CRON="0 2 * * *" ;;
esac
echo "$CRON root /bin/bash $WORKER run >> /var/log/${SVC_NAME}.log 2>&1" \
> "/etc/cron.d/${SVC_NAME}"
log_success "Cron job installed: $CRON"
AUTORUN="cat /etc/cron.d/${SVC_NAME}"
fi
# ── 12. Optional first run ────────────────────────────────────────────────
echo ""
local _now=""
prompt_yn " Run the first backup now? (y/N):" "n" _now
if [[ "$_now" =~ ^[Yy]$ ]]; then
/bin/bash "$WORKER" run || log_warning "First backup reported warnings — check output above."
fi
# ── Summary ───────────────────────────────────────────────────────────────
echo ""
echo "═══════════════════════════════════════════════════════"
echo " BACKUP CONFIGURED"
echo "═══════════════════════════════════════════════════════"
echo ""
echo " Config : $CONF_FILE"
echo " Worker : $WORKER"
echo " Schedule : $SCHED_LABEL"
echo ""
echo " Destinations:"
for dn in "${DEST_NAMES_ARR[@]}"; do
printf " %-16s %s\n" "$dn" "${DEST_REPOS[$dn]}"
done
echo ""
if [ "${#ALL_SVCS[@]}" -gt 0 ]; then
echo " Services backed up: ${ALL_SVCS[*]}"
else
echo " Services: none yet — auto-discovered on each run"
fi
echo ""
echo " Commands:"
echo " sudo $WORKER back up now"
echo " sudo $WORKER snapshots list all snapshots"
echo ""
echo " Restore (per destination):"
for dn in "${DEST_NAMES_ARR[@]}"; do
echo " sudo $RESTORE_DIR/$dn/restore_kopia_backup.sh"
echo " sudo $RESTORE_DIR/$dn/restore_kopia_backup.sh --list"
done
echo ""
[ -n "$AUTORUN" ] && echo " $AUTORUN" && echo ""
log_warning "Save your passwords (in backup.conf) somewhere safe —"
log_warning "without them the encrypted repos cannot be restored."
echo ""
log_success "Backup configured."
}