fix(backup): preserve numeric ownership and NFS coherence across the DR chain

Every cross-host hop in the backup/recover chain remapped file owners
by NAME on the receiving host (rsync -a default; GNU tar extract as
root prefers name lookup), so a restored uid-1000-owned 0600 config
(n8n) came back owned by a foreign uid and the post-recovery redeploy
crash-looped on EACCES. Pass --numeric-ids to the capture, remote-pull,
device-sync and restore rsyncs and --numeric-owner to the drill's tar
extracts.

The drill additionally wiped and rewrote the export subtree directly on
the ganesha server's backing filesystem while the server kept running
and the nodes kept their client mounts: ganesha's mdcache and the
client dentry caches still described the pre-wipe tree, so update-pass
tasks were Rejected with readdirent ENOENT for the whole converge
window (moodle, espocrm multi-replica). Step 8b now restarts ganesha,
remounts every node and probes a real write through the manager's
mount, each bounded by timeout so a wedged DinD node fails loudly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 4.8 committed 2026-07-14 16:15:27 +02:00
1 parent fdc8fb6d4f
commit 69321d4135
4 files changed
+4 -3

No files matched your search

+1 -1
View File
@@ -49,7 +49,7 @@ def main():
print("Starting synchronization...")
try:
rsync_command = ["rsync", "-abP", "--delete", "--delete-excluded"]
rsync_command = ["rsync", "-abP", "--numeric-ids", "--delete", "--delete-excluded"]
if previous_version_path is not None:
rsync_command.append("--link-dest=" + previous_version_path)
rsync_command.extend([source_path, current_version_path])
+1 -1
View File
@@ -45,7 +45,7 @@ trap cleanup_failed_generation ERR
mkdir -p "${DEST_DIR}"
rsync_args=(-a --delete)
rsync_args=(-a --numeric-ids --delete)
if [[ -n "${EXCLUDE_REL}" ]]; then
rsync_args+=(--exclude "/${EXCLUDE_REL#/}")
fi
@@ -111,7 +111,7 @@ def pull_backups(hostname: str, backups_dir: str) -> None:
Path(local_backup_destination_path).mkdir(parents=True, exist_ok=True)
rsync_command = (
f"rsync -abP --delete --delete-excluded "
f"rsync -abP --numeric-ids --delete --delete-excluded "
f'--rsync-path="sudo rsync" '
f'--link-dest="{local_previous_version_dir}" '
f'"{remote_source_path}" "{local_backup_destination_path}"'
+1
View File
@@ -88,6 +88,7 @@ class DirectoryRecovery(ABC):
[
"rsync",
"-a",
"--numeric-ids",
"--delete",
*self.rsync_extra_args,
f"{self.source_dir}/",