From 77b3b65bfb0664004353eb95de0c3656275a4889 Mon Sep 17 00:00:00 2001 From: Johannes Rest Date: Sun, 8 Feb 2026 21:49:45 +0100 Subject: [PATCH] Updating app-backup.sh and app-restore.sh to handle nextcloud data correctly. --- usr-local-sbin/app-backup.sh | 53 ++++- usr-local-sbin/app-restore.sh | 403 ++++++++++++++++++---------------- 2 files changed, 266 insertions(+), 190 deletions(-) diff --git a/usr-local-sbin/app-backup.sh b/usr-local-sbin/app-backup.sh index 4e2c53c..51a57eb 100755 --- a/usr-local-sbin/app-backup.sh +++ b/usr-local-sbin/app-backup.sh @@ -53,6 +53,10 @@ fi : "${MAIL_SUBJECT_PREFIX:=[app-backup]}" : "${MAIL_INCLUDE_LOG_TAIL_LINES:=200}" +# OPTIONAL: allow wp excludes via config, e.g. WP_EXCLUDES=("nextcloud/" "foo/") +# If unset, we compute a safe default for your setup. +: "${WP_EXCLUDES_MODE:=auto}" # auto|manual + # ---------- State for report ---------- START_EPOCH="$(date +%s)" STATUS="SUCCESS" @@ -95,10 +99,7 @@ cleanup_old_local_archives() { DELETED_LOCAL_LIST_FILE="$list_file" : > "$list_file" - # collect files older than retention - # shellcheck disable=SC2016 while IFS= read -r -d '' f; do - # best-effort size local sz sz="$(stat -c '%s' "$f" 2>/dev/null || echo 0)" DELETED_LOCAL_BYTES=$((DELETED_LOCAL_BYTES + sz)) @@ -108,7 +109,6 @@ cleanup_old_local_archives() { if [[ "$DELETED_LOCAL_COUNT" -gt 0 ]]; then echo "-- Local retention: deleting ${DELETED_LOCAL_COUNT} archive(s) older than ${LOCAL_RETENTION_DAYS} day(s) from ${ARCHIVE_DIR}" - # delete using the collected list to ensure count/bytes match what we report while IFS= read -r f; do rm -f -- "$f" || true done < "$list_file" @@ -356,14 +356,55 @@ echo "-- Collecting files via rsync..." rsync_dir() { local src="$1" local dst="$2" + shift 2 || true + [[ -d "$src" ]] || die "Source directory missing: $src" mkdir -p "$dst" - rsync -aHAX --numeric-ids --delete --info=stats2 "$src"/ "$dst"/ + + # Remaining args are exclude patterns like "nextcloud/" + local excludes=() + while [[ $# -gt 0 ]]; do + excludes+=("--exclude=$1") + shift + done + + rsync -aHAX --numeric-ids --delete --info=stats2 \ + "${excludes[@]}" \ + "$src"/ "$dst"/ +} + +compute_wp_excludes() { + # Returns exclude patterns via stdout, one per line + if [[ "${WP_EXCLUDES_MODE}" == "manual" ]]; then + if declare -p WP_EXCLUDES >/dev/null 2>&1; then + for e in "${WP_EXCLUDES[@]}"; do + echo "$e" + done + fi + return 0 + fi + + # auto mode: + # If Nextcloud is enabled and sits inside WP_DIR (your setup), exclude "nextcloud/" from WP sync + if [[ "${ENABLE_NEXTCLOUD}" == "true" ]]; then + local wp="${WP_DIR%/}" + local nc="${NC_DIR%/}" + if [[ "$nc" == "$wp/nextcloud" ]]; then + echo "nextcloud/" + fi + fi } if [[ "${ENABLE_WORDPRESS}" == "true" ]]; then echo "-- WordPress files: ${WP_DIR}" - rsync_dir "${WP_DIR}" "$STAGING_DIR/files/wordpress" + + mapfile -t _wp_excludes < <(compute_wp_excludes || true) + if [[ "${#_wp_excludes[@]}" -gt 0 ]]; then + echo "-- WordPress excludes: ${_wp_excludes[*]}" + rsync_dir "${WP_DIR}" "$STAGING_DIR/files/wordpress" "${_wp_excludes[@]}" + else + rsync_dir "${WP_DIR}" "$STAGING_DIR/files/wordpress" + fi fi if [[ "${ENABLE_NEXTCLOUD}" == "true" ]]; then diff --git a/usr-local-sbin/app-restore.sh b/usr-local-sbin/app-restore.sh index 57d3b68..94662cd 100755 --- a/usr-local-sbin/app-restore.sh +++ b/usr-local-sbin/app-restore.sh @@ -2,14 +2,14 @@ set -Eeuo pipefail umask 027 -# ---------------- Logging ---------------- +# ---------- Logging ---------- 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 ---------------- +# ---------- Config ---------- CONFIG_FILE="/etc/app-backup/app-backup.conf" if [[ -r "$CONFIG_FILE" ]]; then # shellcheck disable=SC1090 @@ -19,225 +19,260 @@ else exit 2 fi +# ---------- Defaults ---------- : "${WORKDIR:=/var/backups/app-backup}" -: "${STAGING_ROOT:=${WORKDIR}/restore-staging}" +: "${RESTORE_ROOT:=${WORKDIR}/restore}" : "${RCLONE_REMOTE:=onedrive:Sicherung}" : "${RCLONE_BIN:=rclone}" + +: "${DRY_RUN:=false}" # true = show what would happen +: "${RESTORE_DB:=true}" # true/false +: "${RESTORE_FILES:=true}" # true/false +: "${RESTORE_STRICT_DELETE:=false}" # true = rsync --delete on restore + +: "${ENABLE_NEXTCLOUD_MAINTENANCE:=true}" : "${NC_OCC_USER:=apache}" +: "${NC_FILES_SCAN_AFTER_RESTORE:=false}" -ARCHIVE_PATH="" -REMOTE_NAME="" -DRY_RUN=false -FORCE=false +die() { echo "ERROR: $*"; exit 1; } +have() { command -v "$1" >/dev/null 2>&1; } -DO_WORDPRESS=true -DO_NEXTCLOUD=true -DO_NEXTCLOUD_DATA=true -DO_MAIL=true -DO_DB=true - -usage() { - cat < Local archive path. - --remote-file Download that file name from rclone remote (${RCLONE_REMOTE}) into ${WORKDIR}/restore-downloads/. - --dry-run No writes (rsync --dry-run; DB import skipped; services not restarted). - --force Skip some safety checks. - --only Comma-separated: wordpress,nextcloud,nextcloud-data,mail,db - --skip Comma-separated: wordpress,nextcloud,nextcloud-data,mail,db - -h|--help Help. -EOF -} - -set_all_false() { DO_WORDPRESS=false; DO_NEXTCLOUD=false; DO_NEXTCLOUD_DATA=false; DO_MAIL=false; DO_DB=false; } -parse_list_flags() { - local mode="$1" list="$2" - IFS=',' read -r -a items <<< "$list" - for it in "${items[@]}"; do - it="$(echo "$it" | tr '[:upper:]' '[:lower:]' | xargs)" - case "$it" in - wordpress) [[ "$mode" == "only" ]] && DO_WORDPRESS=true || DO_WORDPRESS=false ;; - nextcloud) [[ "$mode" == "only" ]] && DO_NEXTCLOUD=true || DO_NEXTCLOUD=false ;; - nextcloud-data) [[ "$mode" == "only" ]] && DO_NEXTCLOUD_DATA=true || DO_NEXTCLOUD_DATA=false ;; - mail) [[ "$mode" == "only" ]] && DO_MAIL=true || DO_MAIL=false ;; - db) [[ "$mode" == "only" ]] && DO_DB=true || DO_DB=false ;; - "" ) : ;; - *) echo "WARN: unknown component: $it" ;; - esac - done -} - -while [[ $# -gt 0 ]]; do - case "$1" in - --archive) ARCHIVE_PATH="${2:-}"; shift 2 ;; - --remote-file) REMOTE_NAME="${2:-}"; shift 2 ;; - --dry-run) DRY_RUN=true; shift ;; - --force) FORCE=true; shift ;; - --only) set_all_false; parse_list_flags "only" "${2:-}"; shift 2 ;; - --skip) parse_list_flags "skip" "${2:-}"; shift 2 ;; - -h|--help) usage; exit 0 ;; - *) echo "ERROR: unknown arg: $1"; usage; exit 2 ;; - esac -done - -die(){ echo "ERROR: $*"; exit 1; } -have(){ command -v "$1" >/dev/null 2>&1; } -service_is_active(){ systemctl is-active --quiet "$1"; } - -STOPPED_SERVICES=() -stop_service_if_active() { - local s="$1" - if service_is_active "$s"; then - echo "-- Stopping service: $s" - [[ "$DRY_RUN" == "true" ]] || systemctl stop "$s" - STOPPED_SERVICES+=("$s") - fi -} - -start_stopped_services() { - if [[ "$DRY_RUN" == "true" ]]; then - echo "-- DRY RUN: not starting services" - return 0 - fi - for s in "${STOPPED_SERVICES[@]}"; do - echo "-- Starting service: $s" - systemctl start "$s" || echo "WARN: failed to start $s" - done -} - -integrity_check() { - local f="$1" - if [[ "$f" == *.zst ]]; then - have zstd || die "zstd missing but archive is .zst" - zstd -t "$f" - elif [[ "$f" == *.gz ]]; then - have gzip || die "gzip missing but archive is .gz" - gzip -t "$f" +run_cmd() { + if [[ "${DRY_RUN}" == "true" ]]; then + echo "[DRY_RUN] $*" else - die "Unsupported archive extension: $f" + "$@" fi } -extract_archive() { - local f="$1" dest="$2" - mkdir -p "$dest" - if [[ "$f" == *.zst ]]; then - tar --use-compress-program=unzstd -xf "$f" -C "$dest" - else - tar -xzf "$f" -C "$dest" - fi -} - -rsync_restore_dir() { - local src="$1" dst="$2" - [[ -d "$src" ]] || die "Staging source missing: $src" - mkdir -p "$dst" - local args=(-aHAX --numeric-ids --delete) - [[ "$DRY_RUN" == "true" ]] && args+=(--dry-run) - echo "-- Restoring: $src => $dst" - rsync "${args[@]}" "$src"/ "$dst"/ -} - -mysql_import() { - local cnf="$1" db="$2" sql="$3" - [[ -r "$cnf" ]] || die "DB cnf not readable: $cnf" - [[ -r "$sql" ]] || die "SQL dump not readable: $sql" - have mysql || die "mysql client missing" - if [[ "$DRY_RUN" == "true" ]]; then - echo "-- DRY RUN: skipping DB import for $db" - return 0 - fi - mysql --defaults-extra-file="$cnf" -e "CREATE DATABASE IF NOT EXISTS \`${db}\`;" - mysql --defaults-extra-file="$cnf" "$db" < "$sql" -} - +# Nextcloud maintenance-mode safety trap NC_MAINTENANCE_ON=false -nc_maintenance_on() { - if [[ "$DRY_RUN" == "true" ]]; then - echo "-- DRY RUN: not toggling Nextcloud maintenance mode" - return 0 - fi - if [[ -f "${NC_DIR}/occ" ]]; then - sudo -u "${NC_OCC_USER}" php "${NC_DIR}/occ" maintenance:mode --on - NC_MAINTENANCE_ON=true - fi -} nc_maintenance_off() { if [[ "${NC_MAINTENANCE_ON}" == "true" ]]; then - sudo -u "${NC_OCC_USER}" php "${NC_DIR}/occ" maintenance:mode --off || true + 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 } -echo "== app-restore start: ${ts} ==" -echo "-- Log: ${LOG_FILE}" +on_exit() { + local exit_code=$? + nc_maintenance_off + exit "${exit_code}" +} +trap on_exit EXIT + +# ---------- Preconditions ---------- [[ $EUID -eq 0 ]] || die "Must run as root." -LOCKFILE="/run/app-restore.lock" +for t in tar rsync flock df find stat; do + have "$t" || die "Missing required tool: $t" +done + +mkdir -p "$WORKDIR" "$RESTORE_ROOT" "$LOG_DIR" + +# ---------- Locking ---------- +LOCKFILE="/run/app-backup.lock" exec 9>"$LOCKFILE" -flock -n 9 || die "Another restore is already running (lock: $LOCKFILE)" +if ! flock -n 9; then + die "Another backup/restore already running (lock: $LOCKFILE)" +fi -DOWNLOAD_DIR="${WORKDIR}/restore-downloads" -mkdir -p "$DOWNLOAD_DIR" +# ---------- Input ---------- +# Usage: +# app-restore.sh /path/to/appbackup_YYYY-mm-dd_HH-MM-SS.tar.zst +# or +# app-restore.sh /path/to/appbackup_YYYY-mm-dd_HH-MM-SS.tar.gz +# or +# app-restore.sh --remote appbackup_YYYY-mm-dd_HH-MM-SS.tar.zst +# (copies from RCLONE_REMOTE to RESTORE_ROOT first) +ARCHIVE_PATH="" +REMOTE_NAME="" +if [[ "${1:-}" == "--remote" ]]; then + REMOTE_NAME="${2:-}" + [[ -n "$REMOTE_NAME" ]] || die "Usage: $0 --remote " + have "$RCLONE_BIN" || die "rclone missing but --remote used" +else + ARCHIVE_PATH="${1:-}" + [[ -n "$ARCHIVE_PATH" ]] || die "Usage: $0 OR $0 --remote " +fi + +echo "== app-restore start: ${ts} ==" +echo "-- Config: ${CONFIG_FILE}" +echo "-- Log: ${LOG_FILE}" +echo "-- DRY_RUN: ${DRY_RUN}" +echo "-- RESTORE_FILES: ${RESTORE_FILES}" +echo "-- RESTORE_DB: ${RESTORE_DB}" +echo "-- STRICT_DELETE: ${RESTORE_STRICT_DELETE}" + +# ---------- Fetch from remote if requested ---------- if [[ -n "$REMOTE_NAME" ]]; then - have "$RCLONE_BIN" || die "rclone required for --remote-file" - ARCHIVE_PATH="${DOWNLOAD_DIR}/${REMOTE_NAME}" - if [[ "$DRY_RUN" == "true" ]]; then - echo "-- DRY RUN: would download ${RCLONE_REMOTE}/${REMOTE_NAME} to ${ARCHIVE_PATH}" + ARCHIVE_PATH="${RESTORE_ROOT}/${REMOTE_NAME}" + echo "-- Fetching from remote: ${RCLONE_REMOTE}/${REMOTE_NAME} -> ${ARCHIVE_PATH}" + run_cmd "$RCLONE_BIN" copy "${RCLONE_REMOTE}/${REMOTE_NAME}" "${RESTORE_ROOT}" --checksum --log-level INFO +fi + +[[ -f "$ARCHIVE_PATH" ]] || die "Archive not found: $ARCHIVE_PATH" + +# ---------- Detect compression ---------- +ARCHIVE_BASENAME="$(basename "$ARCHIVE_PATH")" +IS_ZSTD=false +IS_GZIP=false + +case "$ARCHIVE_BASENAME" in + *.tar.zst) IS_ZSTD=true ;; + *.tar.gz) IS_GZIP=true ;; + *) + # fallback: try file(1) + if have file; then + ftype="$(file -b "$ARCHIVE_PATH" || true)" + if echo "$ftype" | grep -qi zstd; then + IS_ZSTD=true + elif echo "$ftype" | grep -qi gzip; then + IS_GZIP=true + else + die "Cannot detect archive compression for: $ARCHIVE_PATH" + fi + else + die "Unknown archive extension and file(1) not available: $ARCHIVE_PATH" + fi + ;; +esac + +if [[ "$IS_ZSTD" == "true" ]]; then + have zstd || die "zstd archive but zstd missing" +elif [[ "$IS_GZIP" == "true" ]]; then + have gzip || die "gzip archive but gzip missing" +fi + +# ---------- Extract ---------- +RUN_DIR="${RESTORE_ROOT}/run_${ts}" +STAGING_DIR="${RUN_DIR}/staging" +mkdir -p "$STAGING_DIR" + +echo "-- Extracting archive to: ${STAGING_DIR}" +if [[ "$IS_ZSTD" == "true" ]]; then + run_cmd tar --zstd -xf "$ARCHIVE_PATH" -C "$STAGING_DIR" +elif [[ "$IS_GZIP" == "true" ]]; then + run_cmd tar -xzf "$ARCHIVE_PATH" -C "$STAGING_DIR" +fi + +[[ -d "$STAGING_DIR/files" ]] || die "Invalid archive content: missing files/ in extracted staging" + +# ---------- Maintenance mode (Nextcloud) ---------- +if [[ "${ENABLE_NEXTCLOUD}" == "true" && "${ENABLE_NEXTCLOUD_MAINTENANCE}" == "true" ]]; then + if [[ -d "${NC_DIR}" && -f "${NC_DIR}/occ" ]]; then + echo "-- Nextcloud maintenance mode ON..." + run_cmd sudo -u "${NC_OCC_USER}" php "${NC_DIR}/occ" maintenance:mode --on + NC_MAINTENANCE_ON=true else - "$RCLONE_BIN" copy "${RCLONE_REMOTE}/${REMOTE_NAME}" "$DOWNLOAD_DIR" --log-level INFO + echo "WARN: Nextcloud directory/occ not found at NC_DIR=${NC_DIR} - cannot toggle maintenance mode" fi fi -[[ -n "$ARCHIVE_PATH" ]] || { usage; die "Provide --archive or --remote-file"; } -[[ -f "$ARCHIVE_PATH" ]] || die "Archive not found: $ARCHIVE_PATH" +# ---------- Restore files ---------- +rsync_restore_dir() { + local src="$1" + local dst="$2" -echo "-- Selected archive: $ARCHIVE_PATH" -integrity_check "$ARCHIVE_PATH" + [[ -d "$src" ]] || die "Restore source missing: $src" + mkdir -p "$dst" -STAGING_DIR="${STAGING_ROOT}/run_${ts}" -trap 'nc_maintenance_off; start_stopped_services; rm -rf "${STAGING_DIR:?}"' EXIT -extract_archive "$ARCHIVE_PATH" "$STAGING_DIR" + local delete_flag=() + if [[ "${RESTORE_STRICT_DELETE}" == "true" ]]; then + delete_flag=(--delete) + fi -[[ -d "$STAGING_DIR/files" ]] || die "Invalid archive: missing 'files' dir" + run_cmd rsync -aHAX --numeric-ids --info=stats2 \ + "${delete_flag[@]}" \ + "$src"/ "$dst"/ +} -# stop services to prevent writes -if systemctl list-unit-files | grep -q '^httpd\.service'; then stop_service_if_active httpd; fi -if [[ "$DO_MAIL" == "true" ]]; then - if systemctl list-unit-files | grep -q '^postfix\.service'; then stop_service_if_active postfix; fi - if systemctl list-unit-files | grep -q '^dovecot\.service'; then stop_service_if_active dovecot; fi +if [[ "${RESTORE_FILES}" == "true" ]]; then + echo "-- Restoring files..." + + if [[ "${ENABLE_WORDPRESS}" == "true" ]]; then + echo "-- Restore WordPress (webroot) to: ${WP_DIR}" + # Backup excluded nextcloud/ automatically, so this should not overwrite Nextcloud. + rsync_restore_dir "$STAGING_DIR/files/wordpress" "${WP_DIR}" + fi + + if [[ "${ENABLE_NEXTCLOUD}" == "true" ]]; then + echo "-- Restore Nextcloud code to: ${NC_DIR}" + rsync_restore_dir "$STAGING_DIR/files/nextcloud" "${NC_DIR}" + + if [[ "${ENABLE_NEXTCLOUD_DATA}" == "true" ]]; then + echo "-- Restore Nextcloud data to: ${NC_DATA_DIR}" + rsync_restore_dir "$STAGING_DIR/files/nextcloud-data" "${NC_DATA_DIR}" + fi + fi + + if [[ "${ENABLE_MAIL}" == "true" ]]; then + echo "-- Restore mail files..." + [[ -d "$STAGING_DIR/files/mail" && -n "${MAIL_DIR:-}" ]] && rsync_restore_dir "$STAGING_DIR/files/mail" "${MAIL_DIR}" || true + [[ -d "$STAGING_DIR/files/postfix" && -n "${POSTFIX_DIR:-}" ]] && rsync_restore_dir "$STAGING_DIR/files/postfix" "${POSTFIX_DIR}" || true + [[ -d "$STAGING_DIR/files/dovecot" && -n "${DOVECOT_DIR:-}" ]] && rsync_restore_dir "$STAGING_DIR/files/dovecot" "${DOVECOT_DIR}" || true + fi +else + echo "-- RESTORE_FILES=false (skipping file restore)" fi -# nextcloud maintenance for restore -if [[ "$DO_NEXTCLOUD" == "true" || "$DO_NEXTCLOUD_DATA" == "true" || "$DO_DB" == "true" ]]; then - nc_maintenance_on +# ---------- Restore databases ---------- +mysql_restore_sql() { + local cnf="$1" + local db="$2" + local sql_file="$3" + [[ -r "$cnf" ]] || die "DB CNF not readable: $cnf" + [[ -r "$sql_file" ]] || die "SQL file not readable: $sql_file" + + echo "-- Import DB: ${db} from ${sql_file}" + run_cmd mysql --defaults-extra-file="$cnf" "$db" < "$sql_file" +} + +if [[ "${RESTORE_DB}" == "true" ]]; then + echo "-- Restoring databases..." + + if [[ -n "${WP_DB_NAME:-}" ]]; then + wp_sql="$(ls -1 "$STAGING_DIR/db"/wordpress_*.sql 2>/dev/null | tail -n 1 || true)" + if [[ -n "$wp_sql" ]]; then + mysql_restore_sql "${WP_DB_CNF}" "${WP_DB_NAME}" "$wp_sql" + else + echo "WARN: No WordPress SQL dump found in archive." + fi + fi + + if [[ -n "${NC_DB_NAME:-}" ]]; then + nc_sql="$(ls -1 "$STAGING_DIR/db"/nextcloud_*.sql 2>/dev/null | tail -n 1 || true)" + if [[ -n "$nc_sql" ]]; then + mysql_restore_sql "${NC_DB_CNF}" "${NC_DB_NAME}" "$nc_sql" + else + echo "WARN: No Nextcloud SQL dump found in archive." + fi + fi +else + echo "-- RESTORE_DB=false (skipping DB restore)" fi -# restore files -[[ "$DO_WORDPRESS" == "true" && -d "$STAGING_DIR/files/wordpress" ]] && rsync_restore_dir "$STAGING_DIR/files/wordpress" "$WP_DIR" || true -[[ "$DO_NEXTCLOUD" == "true" && -d "$STAGING_DIR/files/nextcloud" ]] && rsync_restore_dir "$STAGING_DIR/files/nextcloud" "$NC_DIR" || true -[[ "$DO_NEXTCLOUD_DATA" == "true" && -d "$STAGING_DIR/files/nextcloud-data" ]] && rsync_restore_dir "$STAGING_DIR/files/nextcloud-data" "$NC_DATA_DIR" || true +# ---------- Post-restore Nextcloud steps ---------- +if [[ "${ENABLE_NEXTCLOUD}" == "true" && -d "${NC_DIR}" && -f "${NC_DIR}/occ" ]]; then + echo "-- Nextcloud post-restore: maintenance:repair" + run_cmd sudo -u "${NC_OCC_USER}" php "${NC_DIR}/occ" maintenance:repair || true -if [[ "$DO_MAIL" == "true" ]]; then - [[ -d "$STAGING_DIR/files/mail" && -n "${MAIL_DIR:-}" ]] && rsync_restore_dir "$STAGING_DIR/files/mail" "$MAIL_DIR" || true - [[ -d "$STAGING_DIR/files/postfix" && -n "${POSTFIX_DIR:-}" ]] && rsync_restore_dir "$STAGING_DIR/files/postfix" "$POSTFIX_DIR" || true - [[ -d "$STAGING_DIR/files/dovecot" && -n "${DOVECOT_DIR:-}" ]] && rsync_restore_dir "$STAGING_DIR/files/dovecot" "$DOVECOT_DIR" || true + echo "-- Nextcloud post-restore: files:scan (optional, can be slow)" + if [[ "${NC_FILES_SCAN_AFTER_RESTORE}" == "true" ]]; then + run_cmd sudo -u "${NC_OCC_USER}" php "${NC_DIR}/occ" files:scan --all || true + else + echo "-- Skipping files:scan (set NC_FILES_SCAN_AFTER_RESTORE=true to enable)" + fi + + if [[ "${ENABLE_NEXTCLOUD_MAINTENANCE}" == "true" ]]; then + echo "-- Nextcloud maintenance mode OFF..." + run_cmd sudo -u "${NC_OCC_USER}" php "${NC_DIR}/occ" maintenance:mode --off + NC_MAINTENANCE_ON=false + fi fi -# restore DBs -if [[ "$DO_DB" == "true" && "${ENABLE_DB_DUMPS:-false}" == "true" ]]; then - WP_SQL="$(ls -1 "$STAGING_DIR/db"/wordpress_*.sql 2>/dev/null | tail -n 1 || true)" - NC_SQL="$(ls -1 "$STAGING_DIR/db"/nextcloud_*.sql 2>/dev/null | tail -n 1 || true)" - [[ "$DO_WORDPRESS" == "true" && -n "${WP_DB_NAME:-}" && -n "$WP_SQL" ]] && mysql_import "$WP_DB_CNF" "$WP_DB_NAME" "$WP_SQL" || true - [[ ( "$DO_NEXTCLOUD" == "true" || "$DO_NEXTCLOUD_DATA" == "true" ) && -n "${NC_DB_NAME:-}" && -n "$NC_SQL" ]] && mysql_import "$NC_DB_CNF" "$NC_DB_NAME" "$NC_SQL" || true -fi - -nc_maintenance_off -start_stopped_services - echo "== app-restore done: ${ts} ==" +echo "-- Extracted staging kept at: ${STAGING_DIR}" echo "-- Log: ${LOG_FILE}"