#!/usr/bin/env bash set -Eeuo pipefail umask 027 # ============================================================================== # app-restore.sh # - Restore from a "run folder" (local dir or rclone remote folder) # - Applies archives per component (meta/db/wordpress/nextcloud/nextcloud-data/gitea...) # ============================================================================== LOG_DIR="/var/log/app-backup" mkdir -p "$LOG_DIR" ts="$(date '+%Y-%m-%d_%H-%M-%S')" LOG_FILE="${LOG_DIR}/app-restore_${ts}.log" exec > >(tee -a "$LOG_FILE" | systemd-cat -t app-restore -p info) 2>&1 CONFIG_FILE="/etc/app-backup/app-backup.conf" if [[ -r "$CONFIG_FILE" ]]; then # shellcheck disable=SC1090 source "$CONFIG_FILE" else echo "ERROR: Config not found/readable: $CONFIG_FILE" exit 2 fi : "${WORKDIR:=/var/backups/app-backup}" : "${RESTORE_ROOT:=${WORKDIR}/restore}" : "${ARCHIVE_PREFIX:=appbackup}" : "${RCLONE_BIN:=rclone}" : "${RCLONE_REMOTE_BASE:=}" # --- Remote base resolution & crypt enforcement --- # Prefer explicit RCLONE_REMOTE_BASE from config. # If only RCLONE_REMOTE is provided (e.g. "OneDriveCrypt:Sicherung"), derive the host folder. : "${RCLONE_REMOTE:=}" if [[ -z "${RCLONE_REMOTE_BASE}" ]]; then if [[ -n "${RCLONE_REMOTE}" ]]; then RCLONE_REMOTE_BASE="${RCLONE_REMOTE%/}/JRITServerBackups/$(hostname -s)" fi fi require_crypt_remote() { local base="$1" local remote_name="${base%%:*}" [[ -n "${remote_name}" && "${remote_name}" != "${base}" ]] || die "RCLONE_REMOTE_BASE must include an rclone remote prefix like 'OneDriveCrypt:...'. Got: ${base}" have "${RCLONE_BIN}" || die "rclone missing" # rclone prints full config; we only need the remote block. local cfg cfg="$("${RCLONE_BIN}" config show "${remote_name}" 2>/dev/null || true)" [[ -n "${cfg}" ]] || die "rclone remote '${remote_name}' not found. Check 'rclone listremotes' and your config." local typ typ="$(printf "%s\n" "${cfg}" | awk -F' = ' '$1=="type"{print $2; exit}')" [[ "${typ}" == "crypt" ]] || die "Remote '${remote_name}' is type='${typ:-?}', not 'crypt'. Refusing to restore from non-crypt remote." # Enforce filename/directory encryption so OneDrive web does not show cleartext names. local fe de fe="$(printf "%s\n" "${cfg}" | awk -F' = ' '$1=="filename_encryption"{print $2; exit}')" de="$(printf "%s\n" "${cfg}" | awk -F' = ' '$1=="directory_name_encryption"{print $2; exit}')" [[ "${fe}" == "standard" ]] || die "Remote '${remote_name}': filename_encryption='${fe:-?}' (expected 'standard'). Otherwise filenames can appear in cleartext." [[ "${de}" == "true" ]] || die "Remote '${remote_name}': directory_name_encryption='${de:-?}' (expected 'true'). Otherwise directory names can appear in cleartext." } : "${DRY_RUN:=false}" : "${RESTORE_DB:=true}" : "${RESTORE_FILES:=true}" : "${RESTORE_STRICT_DELETE:=false}" : "${ENABLE_NEXTCLOUD_MAINTENANCE:=true}" : "${NC_OCC_USER:=apache}" : "${NC_FILES_SCAN_AFTER_RESTORE:=false}" : "${ENABLE_GITEA_SERVICE_STOP:=true}" : "${GITEA_SERVICE_NAME:=gitea}" : "${ENABLE_HTTPD_STOP:=false}" : "${HTTPD_SERVICE_NAME:=httpd}" : "${ENABLE_PHPFPM_STOP:=false}" : "${PHPFPM_SERVICE_NAME:=php-fpm}" die() { echo "ERROR: $*"; exit 1; } have() { command -v "$1" >/dev/null 2>&1; } run_cmd() { [[ "${DRY_RUN}" == "true" ]] && echo "[DRY_RUN] $*" || "$@"; } NC_MAINTENANCE_ON=false nc_maintenance_off() { if [[ "${NC_MAINTENANCE_ON}" == "true" ]]; then echo "-- Nextcloud maintenance mode OFF (trap)..." run_cmd sudo -u "${NC_OCC_USER}" php "${NC_DIR}/occ" maintenance:mode --off || true NC_MAINTENANCE_ON=false fi } GITEA_WAS_STOPPED=false HTTPD_WAS_STOPPED=false PHPFPM_WAS_STOPPED=false gitea_start() { [[ "${GITEA_WAS_STOPPED}" == "true" ]] && { echo "-- Starting gitea (trap)"; run_cmd systemctl start "${GITEA_SERVICE_NAME}" || true; GITEA_WAS_STOPPED=false; }; } httpd_start() { [[ "${HTTPD_WAS_STOPPED}" == "true" ]] && { echo "-- Starting httpd (trap)"; run_cmd systemctl start "${HTTPD_SERVICE_NAME}" || true; HTTPD_WAS_STOPPED=false; }; } phpfpm_start(){ [[ "${PHPFPM_WAS_STOPPED}" == "true" ]] && { echo "-- Starting php-fpm (trap)"; run_cmd systemctl start "${PHPFPM_SERVICE_NAME}" || true; PHPFPM_WAS_STOPPED=false; }; } echo "== app-restore done: ${ts} ==" echo "-- Working dir: ${RUN_DIR}" echo "-- Log: ${LOG_FILE}"