Updating app-backup.sh and app-restore.sh to handle nextcloud data correctly.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 <<EOF
|
||||
Usage:
|
||||
app-restore.sh --archive /path/to/appbackup_....tar.(zst|gz)
|
||||
app-restore.sh --remote-file appbackup_....tar.(zst|gz)
|
||||
Options:
|
||||
--archive <path> Local archive path.
|
||||
--remote-file <name> 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 <list> Comma-separated: wordpress,nextcloud,nextcloud-data,mail,db
|
||||
--skip <list> 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 <archive_filename>"
|
||||
have "$RCLONE_BIN" || die "rclone missing but --remote used"
|
||||
else
|
||||
ARCHIVE_PATH="${1:-}"
|
||||
[[ -n "$ARCHIVE_PATH" ]] || die "Usage: $0 <archive_file.tar.zst|tar.gz> OR $0 --remote <archive_filename>"
|
||||
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}"
|
||||
|
||||
Reference in New Issue
Block a user