77 lines
2.9 KiB
Bash
Executable File
77 lines
2.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
usage() {
|
|
cat >&2 <<'EOF'
|
|
Usage: scheduled-maintenance.sh <backup|restore-latest>
|
|
|
|
Requires FRAMEFLOW_BACKUP_MOUNTPOINT and FRAMEFLOW_BACKUP_DESTINATION.
|
|
The destination must be a writable directory inside the active mount point.
|
|
EOF
|
|
exit "${1:-2}"
|
|
}
|
|
|
|
fail() {
|
|
echo "Scheduled maintenance failed: $1" >&2
|
|
exit 1
|
|
}
|
|
|
|
[[ $# -eq 1 ]] || usage
|
|
case "$1" in
|
|
backup|restore-latest) mode="$1" ;;
|
|
--help|-h) usage 0 ;;
|
|
*) usage ;;
|
|
esac
|
|
|
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
mount_point="${FRAMEFLOW_BACKUP_MOUNTPOINT:-}"
|
|
backup_destination="${FRAMEFLOW_BACKUP_DESTINATION:-}"
|
|
|
|
[[ -n "${mount_point}" ]] || fail "FRAMEFLOW_BACKUP_MOUNTPOINT is required"
|
|
[[ -n "${backup_destination}" ]] || fail "FRAMEFLOW_BACKUP_DESTINATION is required"
|
|
|
|
for path in "${mount_point}" "${backup_destination}"; do
|
|
[[ "${path}" == /* ]] || fail "backup paths must be absolute: ${path}"
|
|
[[ "${path}" != "/" ]] || fail "the filesystem root cannot be used for backups"
|
|
[[ ! "${path}" =~ [[:space:]] ]] || fail "backup paths must not contain whitespace: ${path}"
|
|
[[ -d "${path}" ]] || fail "backup directory does not exist: ${path}"
|
|
[[ ! -L "${path}" ]] || fail "backup paths must not be symbolic links: ${path}"
|
|
done
|
|
|
|
mount_point="$(cd "${mount_point}" && pwd -P)"
|
|
backup_destination="$(cd "${backup_destination}" && pwd -P)"
|
|
if [[ "${backup_destination}" != "${mount_point}" && "${backup_destination}" != "${mount_point}/"* ]]; then
|
|
fail "backup destination must be inside FRAMEFLOW_BACKUP_MOUNTPOINT"
|
|
fi
|
|
[[ -w "${backup_destination}" ]] || fail "backup destination is not writable: ${backup_destination}"
|
|
|
|
command -v findmnt >/dev/null 2>&1 || fail "findmnt is required to verify the backup mount"
|
|
mounted_target="$(findmnt -rn -M "${mount_point}" -o TARGET 2>/dev/null)" \
|
|
|| fail "backup mount is not active: ${mount_point}"
|
|
[[ "${mounted_target}" == "${mount_point}" ]] \
|
|
|| fail "backup mount resolved to an unexpected target: ${mounted_target}"
|
|
|
|
case "${mode}" in
|
|
backup)
|
|
exec "${script_dir}/backup.sh" "${backup_destination}"
|
|
;;
|
|
restore-latest)
|
|
latest_name=""
|
|
latest_path=""
|
|
while IFS= read -r -d '' candidate; do
|
|
[[ ! -L "${candidate}" ]] || continue
|
|
candidate_name="${candidate##*/}"
|
|
[[ "${candidate_name}" =~ ^frameflow-[0-9]{8}T[0-9]{6}Z$ ]] || continue
|
|
if [[ -z "${latest_name}" || "${candidate_name}" > "${latest_name}" ]]; then
|
|
latest_name="${candidate_name}"
|
|
latest_path="${candidate}"
|
|
fi
|
|
done < <(find "${backup_destination}" -mindepth 1 -maxdepth 1 -type d -name 'frameflow-*' -print0)
|
|
|
|
[[ -n "${latest_path}" ]] || fail "no timestamped FrameFlow backup was found"
|
|
echo "Selected latest backup for restore rehearsal: ${latest_path}"
|
|
"${script_dir}/verify-backup.sh" "${latest_path}"
|
|
exec "${script_dir}/rehearse-restore.sh" "${latest_path}"
|
|
;;
|
|
esac
|