Files
computer-playbook/cli/administration/recover/schema.yml
kevinveenbirkenbachandClaude Opus 5.5 21eba1e88f fix(backup): carry hard links, ACLs and xattrs on every backup and recover hop
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>
2026-10-20 18:20:46 +02:00

62 lines
2.4 KiB
YAML

---
# Recovery schema for `infinito administration recover`.
# One uniform command wraps each svc-bkp-* role's files/python/recover.py.
command: "recover <type> <source> <target> [--preview] [--no-safety-backup]"
target: "host to restore onto (positional label)"
order: [device, nfs, volume, secrets]
types:
device:
role: svc-bkp-local-2-device
restores: "encrypted LUKS device/image -> local backup root"
source: "<device>[:subpath][:YYYYMMDDHHMMSS][:/restore-dir]"
default_target: /var/lib/infinito/backup
remote_source: "[user@]host:/dev/... -> ssh recover on host then pull; [user@]host:/image -> pull then local"
passphrase: "interactive on a TTY, read from stdin when piped"
nfs:
role: svc-bkp-nfs-2-local
restores: "snapshot -> live NFS export subtree"
source: "<files-dir>[:/export-subtree] | [user@]host:/path"
default_target: /srv/nfs/infinito-state
remote_source: true
volume:
role: svc-bkp-volume-2-local
restores: "snapshot -> docker volume (name derived from .../<volume>/files)"
source: "<.../<volume>/files> | [user@]host:/path"
default_target: "<docker volume mountpoint>"
remote_source: true
secrets:
role: svc-bkp-secrets-2-local
restores: "snapshot -> host secret paths"
source: "<generation>/files"
default_target: [/var/lib/infinito/secrets, /etc/<domain>/ca, /etc/letsencrypt, /etc/certbot]
remote_source: false
full:
restores: "every backup-stored type present under the source, in order"
source: "local backup-root | [user@]host | LUKS device/image"
chain: >-
device (only if the source is a device) -> nfs -> volume -> secrets.
A host source is rsync-pulled first, a device is recovered to its
restore-root first, then the newest generation of each repo under the
root is recovered.
source_segments:
"/absolute/path": "restore destination, overrides the derived default target"
"relative/path": "device on-device snapshot subpath"
"NNNNNNNNNNNNNN": "device generation (14 digits YYYYMMDDHHMMSS, default: newest)"
"[user@]host:/path": "remote source, rsync-pulled (nfs / volume / full only)"
flags:
"--preview": "print the commands without running them"
"--no-safety-backup": >-
skip the pre-recover safety backup of the current target
(only when it holds nothing worth saving: fresh host / empty / disposable)
mechanism: "rsync -aHAX --delete, preceded by a pre-recover safety backup by default"