mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
baudolo 7.1.x writes volume generations with `rsync -aHAX`, so the producing host keeps `user.*` extended attributes (OpenCloud keeps its storage metadata in `user.oc.*`). Every later copy dropped them again, because none of the other rsync calls passed `-X` (nor `-A`/`-H`): - NFS export snapshots (svc-bkp-nfs-2-local `script.sh`), - the backup host's pull (svc-bkp-remote-2-local `pull_specific_host.py`), - the device sync (svc-bkp-local-2-device `script.py`), - every recover into a live target (`utils/recovery/base.py`, used by the volume, NFS and device recover scripts), - the `recover full` and `recover remote` tree pulls. A disaster recovery through the documented chain therefore restored OpenCloud without `user.oc.*`, silently: the copies succeeded and the dirval stamp does not cover attributes. All of these now run `rsync -aHAX` (`-aHAXP` for the device sync). A target filesystem without xattr support makes rsync exit non-zero as soon as a source file carries attributes, so the hop fails loudly instead of dropping them; there is no per-device opt-out. The single-image pull in `recover remote` keeps `-a`. The provider's ssh-wrapper needs no change: its glob accepts any server flags and its character guard allows them. Two tests write a `user.oc.id` attribute and assert it survives the NFS snapshot and the recover; with the old `-a` both fail with `OSError: [Errno 61] No data available`. Closes #1021 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
66 lines
2.3 KiB
Bash
66 lines
2.3 KiB
Bash
#!/bin/bash
|
|
# Stores a hard-linked differential snapshot of the NFS export in the
|
|
# local backup directory.
|
|
#
|
|
# Arguments:
|
|
# $1 SOURCE_DIR NFS export base to back up
|
|
# $2 BACKUPS_DIR local backup root
|
|
# $3 REPO_NAME repository directory name inside the machine hash dir
|
|
# $4 EXCLUDE_REL optional path relative to SOURCE_DIR to exclude
|
|
# (the shared backup root inside the export; without it
|
|
# every generation snapshots all previous backups)
|
|
#
|
|
# Environment:
|
|
# BKP_NFS_2_LOCAL_GENERATION optional generation name override
|
|
# (defaults to the current timestamp)
|
|
set -euo pipefail
|
|
|
|
SOURCE_DIR="${1:?usage: script.sh SOURCE_DIR BACKUPS_DIR REPO_NAME [EXCLUDE_REL]}"
|
|
BACKUPS_DIR="${2:?usage: script.sh SOURCE_DIR BACKUPS_DIR REPO_NAME [EXCLUDE_REL]}"
|
|
REPO_NAME="${3:?usage: script.sh SOURCE_DIR BACKUPS_DIR REPO_NAME [EXCLUDE_REL]}"
|
|
EXCLUDE_REL="${4:-}"
|
|
|
|
if [[ ! -d "${SOURCE_DIR}" ]]; then
|
|
echo "ERROR: ${SOURCE_DIR} missing; this host is expected to serve the NFS export" >&2
|
|
exit 1
|
|
fi
|
|
|
|
MACHINE_HASH="$(sha256sum /etc/machine-id | cut -c1-64)"
|
|
REPO_DIR="${BACKUPS_DIR%/}/${MACHINE_HASH}/${REPO_NAME}"
|
|
GENERATION="${BKP_NFS_2_LOCAL_GENERATION:-$(date +%Y%m%d%H%M%S)}"
|
|
DEST_DIR="${REPO_DIR}/${GENERATION}/files"
|
|
|
|
PREVIOUS_FILES=""
|
|
if [[ -d "${REPO_DIR}" ]]; then
|
|
PREVIOUS_GENERATION="$(find "${REPO_DIR}" -mindepth 1 -maxdepth 1 -type d | sort | tail -n1)"
|
|
if [[ -n "${PREVIOUS_GENERATION}" && -d "${PREVIOUS_GENERATION}/files" ]]; then
|
|
PREVIOUS_FILES="${PREVIOUS_GENERATION}/files/"
|
|
fi
|
|
fi
|
|
|
|
cleanup_failed_generation() {
|
|
rm -rf "${REPO_DIR:?}/${GENERATION:?}"
|
|
}
|
|
trap cleanup_failed_generation ERR
|
|
|
|
mkdir -p "${DEST_DIR}"
|
|
|
|
rsync_args=(-aHAX --numeric-ids --delete)
|
|
if [[ -n "${EXCLUDE_REL}" ]]; then
|
|
rsync_args+=(--exclude "/${EXCLUDE_REL#/}")
|
|
fi
|
|
if [[ -n "${PREVIOUS_FILES}" ]]; then
|
|
rsync_args+=(--link-dest "${PREVIOUS_FILES}")
|
|
fi
|
|
|
|
rc=0
|
|
rsync "${rsync_args[@]}" "${SOURCE_DIR%/}/" "${DEST_DIR}/" || rc=$?
|
|
if [[ "${rc}" -eq 24 ]]; then
|
|
echo "WARN: rsync exit 24 (files vanished from the live export mid-transfer), snapshot kept"
|
|
elif [[ "${rc}" -ne 0 ]]; then
|
|
cleanup_failed_generation
|
|
echo "ERROR: rsync failed with exit ${rc}, incomplete generation removed" >&2
|
|
exit "${rc}"
|
|
fi
|
|
echo "OK: differential backup of ${SOURCE_DIR} stored in ${DEST_DIR}"
|