refactor: move backup/restore worker scripts to extras/ as source files
Replace embedded heredocs in the three backup service installers with cp from versioned source files in extras/: extras/backup_kopia.sh — Kopia worker (was inline in services/backup.sh) extras/backup_borg.sh — Borg worker (was inline in services/borg-backup.sh) extras/backup_gaming.sh — gaming saves worker (was inline in services/gaming-backup.sh) extras/restore_kopia.sh — unified Kopia restore (multi-dest + single-dest) extras/restore_borg.sh — unified Borg restore with destination picker Each installer now does `cp extras/<script>.sh $DIR/<script>.sh` instead of writing the script inline. Workers and restore scripts are now readable in the repo rather than buried in heredocs. Restore scripts are installed flat into the service directory root: ~/docker/backup/backup_kopia.sh ~/docker/backup/restore_kopia.sh ~/docker/borg-backup/backup_borg.sh ~/docker/borg-backup/restore_borg.sh ~/docker/gaming-backup/backup_gaming.sh ~/docker/gaming-backup/restore_kopia.sh The new restore scripts handle destination selection internally, so a single script replaces the old per-destination restore/<dest>/ layout. Also fixes `local` used outside a function in restore_kopia.sh and restore_borg.sh (destination picker loop), and removes the now-superseded extras/restore_kopia_backup.sh and extras/restore_borg_backup.sh. https://claude.ai/code/session_019XgsQ13XKm4Zj3cNsDNwHj
This commit is contained in:
+22
-160
@@ -11,9 +11,9 @@
|
||||
# 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
|
||||
# backup.conf settings + per-service destination map (chmod 600)
|
||||
# backup_kopia.sh worker (run directly or via systemd timer)
|
||||
# restore_kopia.sh interactive restore helper
|
||||
|
||||
register_service backup backup "Encrypted backup of all Docker services (full restore)"
|
||||
|
||||
@@ -22,9 +22,8 @@ install_backup() {
|
||||
|
||||
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 WORKER="$DIR/backup_kopia.sh"
|
||||
local RESTORE="$DIR/restore_kopia.sh"
|
||||
local SVC_NAME="post-install-backup"
|
||||
|
||||
echo ""
|
||||
@@ -248,7 +247,7 @@ install_backup() {
|
||||
KEEP_LATEST="${KEEP_LATEST:-7}"
|
||||
|
||||
# ── 7. Create dirs + init Kopia repos ────────────────────────────────────
|
||||
mkdir -p "$DIR" "$RESTORE_DIR"
|
||||
mkdir -p "$DIR"
|
||||
ensure_docker_dir_ownership "$DIR"
|
||||
|
||||
local repo pw cfg
|
||||
@@ -288,7 +287,7 @@ install_backup() {
|
||||
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 "# Restore: sudo $RESTORE"
|
||||
echo ""
|
||||
echo "KOPIA=\"$KOPIA_BIN\""
|
||||
echo ""
|
||||
@@ -326,158 +325,23 @@ install_backup() {
|
||||
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
|
||||
# ── 9. Install worker script ──────────────────────────────────────────────
|
||||
log_info "Installing worker $WORKER ..."
|
||||
cp "${HERE:-}/extras/backup_kopia.sh" "$WORKER"
|
||||
chmod +x "$WORKER"
|
||||
chown root:root "$WORKER" 2>/dev/null || true
|
||||
log_success "backup.sh written"
|
||||
log_success "backup_kopia.sh installed"
|
||||
|
||||
# ── 10. Restore scripts (one per destination) ────────────────────────────
|
||||
# ── 10. Install restore script ────────────────────────────────────────────
|
||||
local RESTORE_SRC="${HERE:-}/extras/restore_kopia.sh"
|
||||
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
|
||||
cp "$RESTORE_SRC" "$RESTORE"
|
||||
chmod +x "$RESTORE"
|
||||
chown root:root "$RESTORE" 2>/dev/null || true
|
||||
log_success "restore_kopia.sh installed"
|
||||
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>/"
|
||||
log_warning "extras/restore_kopia.sh not found — restore script not installed"
|
||||
log_warning "Copy it manually: cp extras/restore_kopia.sh $RESTORE"
|
||||
fi
|
||||
|
||||
# ── 11. Systemd timer ────────────────────────────────────────────────────
|
||||
@@ -559,11 +423,9 @@ SVCEOF
|
||||
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 " Restore:"
|
||||
echo " sudo $RESTORE"
|
||||
echo " sudo $RESTORE --list"
|
||||
echo ""
|
||||
[ -n "$AUTORUN" ] && echo " $AUTORUN" && echo ""
|
||||
log_warning "Save your passwords (in backup.conf) somewhere safe —"
|
||||
|
||||
Reference in New Issue
Block a user