Added support for encyption and moved from one big backup file to smaller ones for each application.
This commit is contained in:
@@ -28,7 +28,41 @@ fi
|
||||
: "${ARCHIVE_PREFIX:=appbackup}"
|
||||
|
||||
: "${RCLONE_BIN:=rclone}"
|
||||
: "${RCLONE_REMOTE_BASE:=OneDrive:Sicherung/JRITServerBackups/$(hostname -s)}"
|
||||
: "${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}"
|
||||
@@ -68,182 +102,6 @@ gitea_start() { [[ "${GITEA_WAS_STOPPED}" == "true" ]] && { echo "-- Starting gi
|
||||
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; }; }
|
||||
|
||||
on_exit() { local ec=$?; nc_maintenance_off; gitea_start; httpd_start; phpfpm_start; exit "${ec}"; }
|
||||
trap on_exit EXIT
|
||||
|
||||
[[ $EUID -eq 0 ]] || die "Must run as root."
|
||||
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"
|
||||
|
||||
LOCKFILE="/run/app-backup.lock"
|
||||
exec 9>"$LOCKFILE"
|
||||
flock -n 9 || die "Another backup/restore already running (lock: $LOCKFILE)"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage:
|
||||
$0 --remote-run <run_folder_name> # e.g. ${ARCHIVE_PREFIX}_2026-02-11_02-31-28
|
||||
$0 --local-run <path_to_run_dir> # directory containing archives
|
||||
Options:
|
||||
--dry-run
|
||||
--no-db
|
||||
--no-files
|
||||
EOF
|
||||
}
|
||||
|
||||
REMOTE_RUN=""
|
||||
LOCAL_RUN=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--remote-run) REMOTE_RUN="${2:-}"; shift 2;;
|
||||
--local-run) LOCAL_RUN="${2:-}"; shift 2;;
|
||||
--dry-run) DRY_RUN=true; shift;;
|
||||
--no-db) RESTORE_DB=false; shift;;
|
||||
--no-files) RESTORE_FILES=false; shift;;
|
||||
-h|--help) usage; exit 0;;
|
||||
*) die "Unknown arg: $1";;
|
||||
esac
|
||||
done
|
||||
[[ -z "${REMOTE_RUN}" && -z "${LOCAL_RUN}" ]] && { usage; exit 2; }
|
||||
|
||||
RUN_DIR="${RESTORE_ROOT}/run_${ts}"
|
||||
DOWNLOAD_DIR="${RUN_DIR}/downloads"
|
||||
EXTRACT_DIR="${RUN_DIR}/extract"
|
||||
mkdir -p "$DOWNLOAD_DIR" "$EXTRACT_DIR"
|
||||
|
||||
if [[ -n "${REMOTE_RUN}" ]]; then
|
||||
have "$RCLONE_BIN" || die "rclone missing but --remote-run used"
|
||||
remote_path="${RCLONE_REMOTE_BASE}/${REMOTE_RUN}"
|
||||
echo "-- Fetching archives from remote: ${remote_path} -> ${DOWNLOAD_DIR}"
|
||||
run_cmd "$RCLONE_BIN" copy "${remote_path}" "${DOWNLOAD_DIR}" --checksum --log-level INFO
|
||||
SRC_DIR="${DOWNLOAD_DIR}"
|
||||
else
|
||||
[[ -d "${LOCAL_RUN}" ]] || die "Local run dir not found: ${LOCAL_RUN}"
|
||||
SRC_DIR="${LOCAL_RUN}"
|
||||
fi
|
||||
|
||||
echo "== app-restore start: ${ts} =="
|
||||
echo "-- Source dir: ${SRC_DIR}"
|
||||
echo "-- DRY_RUN: ${DRY_RUN}"
|
||||
|
||||
detect_tar_flags() { case "$1" in *.tar.zst) echo "--zstd" ;; *.tar.gz) echo "-z" ;; *) die "Unsupported archive: $1" ;; esac; }
|
||||
extract_archive() {
|
||||
local f="$1" flags; flags="$(detect_tar_flags "$f")"
|
||||
echo "-- Extract: $(basename "$f") -> ${EXTRACT_DIR}"
|
||||
[[ "${DRY_RUN}" == "true" ]] && echo "[DRY_RUN] tar ${flags} -xf $f -C ${EXTRACT_DIR}" || tar ${flags} -xf "$f" -C "$EXTRACT_DIR"
|
||||
}
|
||||
pick_one() { ls -1 "${SRC_DIR}"/$1 2>/dev/null | sort | tail -n 1 || true; }
|
||||
|
||||
# stop services (optional)
|
||||
if [[ "${ENABLE_HTTPD_STOP}" == "true" ]] && systemctl is-active --quiet "${HTTPD_SERVICE_NAME}"; then
|
||||
echo "-- Stopping httpd for restore: ${HTTPD_SERVICE_NAME}"
|
||||
run_cmd systemctl stop "${HTTPD_SERVICE_NAME}"; HTTPD_WAS_STOPPED=true
|
||||
fi
|
||||
if [[ "${ENABLE_PHPFPM_STOP}" == "true" ]] && systemctl is-active --quiet "${PHPFPM_SERVICE_NAME}"; then
|
||||
echo "-- Stopping php-fpm for restore: ${PHPFPM_SERVICE_NAME}"
|
||||
run_cmd systemctl stop "${PHPFPM_SERVICE_NAME}"; PHPFPM_WAS_STOPPED=true
|
||||
fi
|
||||
if [[ "${ENABLE_GITEA:-false}" == "true" && "${ENABLE_GITEA_SERVICE_STOP}" == "true" ]] && systemctl is-active --quiet "${GITEA_SERVICE_NAME}"; then
|
||||
echo "-- Stopping gitea for restore: ${GITEA_SERVICE_NAME}"
|
||||
run_cmd systemctl stop "${GITEA_SERVICE_NAME}"; GITEA_WAS_STOPPED=true
|
||||
fi
|
||||
|
||||
# nextcloud maintenance
|
||||
if [[ "${ENABLE_NEXTCLOUD:-false}" == "true" && "${ENABLE_NEXTCLOUD_MAINTENANCE}" == "true" ]] && [[ -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
|
||||
fi
|
||||
|
||||
# extract archives
|
||||
meta_arc="$(pick_one "${ARCHIVE_PREFIX}_*_meta.tar.*")"; [[ -n "$meta_arc" ]] && extract_archive "$meta_arc" || true
|
||||
db_arc="$(pick_one "${ARCHIVE_PREFIX}_*_db.tar.*")"
|
||||
wp_arc="$(pick_one "${ARCHIVE_PREFIX}_*_wordpress.tar.*")"
|
||||
nc_arc="$(pick_one "${ARCHIVE_PREFIX}_*_nextcloud.tar.*")"
|
||||
ncd_arc="$(pick_one "${ARCHIVE_PREFIX}_*_nextcloud-data.tar.*")"
|
||||
g_arc="$(pick_one "${ARCHIVE_PREFIX}_*_gitea.tar.*")"
|
||||
g_etc_arc="$(pick_one "${ARCHIVE_PREFIX}_*_gitea-etc.tar.*")"
|
||||
|
||||
[[ -n "$db_arc" ]] && extract_archive "$db_arc" || true
|
||||
[[ -n "$wp_arc" && "${RESTORE_FILES}" == "true" ]] && extract_archive "$wp_arc" || true
|
||||
[[ -n "$nc_arc" && "${RESTORE_FILES}" == "true" ]] && extract_archive "$nc_arc" || true
|
||||
[[ -n "$ncd_arc" && "${RESTORE_FILES}" == "true" ]] && extract_archive "$ncd_arc" || true
|
||||
[[ -n "$g_arc" && "${RESTORE_FILES}" == "true" ]] && extract_archive "$g_arc" || true
|
||||
[[ -n "$g_etc_arc" && "${RESTORE_FILES}" == "true" ]] && extract_archive "$g_etc_arc" || true
|
||||
|
||||
rsync_restore_dir() {
|
||||
local src="$1" dst="$2"
|
||||
[[ -d "$src" ]] || die "Restore source missing: $src"
|
||||
mkdir -p "$dst"
|
||||
local del=(); [[ "${RESTORE_STRICT_DELETE}" == "true" ]] && del=(--delete)
|
||||
run_cmd rsync -aHAX --numeric-ids --info=stats2 "${del[@]}" "$src"/ "$dst"/
|
||||
}
|
||||
|
||||
if [[ "${RESTORE_FILES}" == "true" ]]; then
|
||||
echo "-- Restoring files..."
|
||||
if [[ -d "${EXTRACT_DIR}/files/wordpress" && "${ENABLE_WORDPRESS:-false}" == "true" ]]; then
|
||||
echo "-- WordPress -> ${WP_DIR}"
|
||||
rsync_restore_dir "${EXTRACT_DIR}/files/wordpress" "${WP_DIR}"
|
||||
fi
|
||||
|
||||
if [[ "${ENABLE_NEXTCLOUD:-false}" == "true" ]]; then
|
||||
[[ -d "${EXTRACT_DIR}/files/nextcloud" ]] && { echo "-- Nextcloud code -> ${NC_DIR}"; rsync_restore_dir "${EXTRACT_DIR}/files/nextcloud" "${NC_DIR}"; }
|
||||
: "${NC_DATA_DIR:=${NC_DIR%/}/data}"
|
||||
[[ -d "${EXTRACT_DIR}/files/nextcloud-data" && "${ENABLE_NEXTCLOUD_DATA:-true}" == "true" ]] && { echo "-- Nextcloud data -> ${NC_DATA_DIR}"; rsync_restore_dir "${EXTRACT_DIR}/files/nextcloud-data" "${NC_DATA_DIR}"; }
|
||||
fi
|
||||
|
||||
if [[ "${ENABLE_GITEA:-false}" == "true" ]]; then
|
||||
: "${GITEA_DATA_DIR:=/var/lib/gitea/data}"
|
||||
[[ -d "${EXTRACT_DIR}/files/gitea-data" ]] && { echo "-- Gitea data -> ${GITEA_DATA_DIR}"; rsync_restore_dir "${EXTRACT_DIR}/files/gitea-data" "${GITEA_DATA_DIR}"; }
|
||||
: "${GITEA_ETC_DIR:=/etc/gitea}"
|
||||
[[ -d "${EXTRACT_DIR}/files/gitea-etc" && -n "${GITEA_ETC_DIR:-}" ]] && { echo "-- Gitea etc -> ${GITEA_ETC_DIR}"; rsync_restore_dir "${EXTRACT_DIR}/files/gitea-etc" "${GITEA_ETC_DIR}"; }
|
||||
fi
|
||||
else
|
||||
echo "-- RESTORE_FILES=false (skipping)"
|
||||
fi
|
||||
|
||||
mysql_restore_sql() {
|
||||
local cnf="$1" db="$2" sql="$3"
|
||||
[[ -r "$cnf" ]] || die "DB CNF not readable: $cnf"
|
||||
[[ -r "$sql" ]] || die "SQL not readable: $sql"
|
||||
have mysql || die "mysql client missing"
|
||||
echo "-- Import MySQL/MariaDB DB: ${db} from $(basename "$sql")"
|
||||
run_cmd mysql --defaults-extra-file="$cnf" "$db" < "$sql"
|
||||
}
|
||||
|
||||
if [[ "${RESTORE_DB}" == "true" && -d "${EXTRACT_DIR}/db" ]]; then
|
||||
echo "-- Restoring databases..."
|
||||
wp_sql="$(ls -1 "${EXTRACT_DIR}/db"/wordpress_*.sql 2>/dev/null | sort | tail -n 1 || true)"
|
||||
nc_sql="$(ls -1 "${EXTRACT_DIR}/db"/nextcloud_*.sql 2>/dev/null | sort | tail -n 1 || true)"
|
||||
g_sql="$(ls -1 "${EXTRACT_DIR}/db"/gitea_*.sql 2>/dev/null | sort | tail -n 1 || true)"
|
||||
|
||||
[[ -n "${WP_DB_NAME:-}" && -n "$wp_sql" ]] && mysql_restore_sql "${WP_DB_CNF}" "${WP_DB_NAME}" "$wp_sql" || echo "WARN: WP DB dump missing"
|
||||
[[ -n "${NC_DB_NAME:-}" && -n "$nc_sql" ]] && mysql_restore_sql "${NC_DB_CNF}" "${NC_DB_NAME}" "$nc_sql" || echo "WARN: NC DB dump missing"
|
||||
[[ "${ENABLE_GITEA:-false}" == "true" && -n "${GITEA_DB_NAME:-}" && -n "$g_sql" ]] && mysql_restore_sql "${GITEA_DB_CNF}" "${GITEA_DB_NAME}" "$g_sql" || true
|
||||
else
|
||||
echo "-- RESTORE_DB=false or no db dump present (skipping)"
|
||||
fi
|
||||
|
||||
if [[ "${ENABLE_NEXTCLOUD:-false}" == "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 [[ "${NC_FILES_SCAN_AFTER_RESTORE}" == "true" ]]; then
|
||||
echo "-- Nextcloud post-restore: files:scan --all"
|
||||
run_cmd sudo -u "${NC_OCC_USER}" php "${NC_DIR}/occ" files:scan --all || true
|
||||
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
|
||||
|
||||
gitea_start
|
||||
phpfpm_start
|
||||
httpd_start
|
||||
|
||||
echo "== app-restore done: ${ts} =="
|
||||
echo "-- Working dir: ${RUN_DIR}"
|
||||
echo "-- Log: ${LOG_FILE}"
|
||||
|
||||
Reference in New Issue
Block a user