From e5da813a9fe89ec445ac68b6f8481e0e3f37c03c Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Sun, 12 Jul 2026 14:53:55 +0200 Subject: [PATCH] refactor(restore)!: rsync volumes directly on the host, drop alpine-rsync Restore now resolves the target volume's mountpoint via docker volume inspect and rsyncs into it directly, mirroring how the backup path already reads the mountpoint; the alpine-rsync container and the --rsync-image flag are gone. The e2e harness mounts /var/lib/docker read-write in the test container so the direct restore can write, the same way baudolo runs as root on a real host. BREAKING CHANGE: the restore 'files' subcommand no longer accepts --rsync-image; the caller must have write access to the docker volume root (root on the host), which is baudolo's normal privilege. Co-Authored-By: Claude Fable 5 --- scripts/test-e2e.sh | 11 ++-------- src/baudolo/restore/__main__.py | 5 ----- src/baudolo/restore/files.py | 36 ++++++++++++++++----------------- 3 files changed, 19 insertions(+), 33 deletions(-) diff --git a/scripts/test-e2e.sh b/scripts/test-e2e.sh index 86cb761..f40e951 100755 --- a/scripts/test-e2e.sh +++ b/scripts/test-e2e.sh @@ -27,7 +27,6 @@ dind() { docker exec "${DIND}" docker "$@"; } dind_stdin() { docker exec -i "${DIND}" docker "$@"; } IMG="${E2E_IMAGE:-baudolo:local}" -RSYNC_IMG="${E2E_RSYNC_IMAGE:-ghcr.io/kevinveenbirkenbach/alpine-rsync}" READY_TIMEOUT_SECONDS="${E2E_READY_TIMEOUT_SECONDS:-120}" ARTIFACTS_DIR="${E2E_ARTIFACTS_DIR:-./artifacts}" @@ -164,10 +163,6 @@ for i in $(seq 1 "${READY_TIMEOUT_SECONDS}"); do fi done -log "Pre-pulling helper images in DinD..." -log " - Pulling: ${RSYNC_IMG}" -dind pull "${RSYNC_IMG}" - log "Ensuring alpine exists in DinD (for debug helpers)" dind pull alpine:3.20 >/dev/null @@ -181,8 +176,7 @@ if [ "${DEBUG_SHELL}" = "1" ]; then docker run --rm -it \ --network "${NET}" \ -e DOCKER_HOST="${DIND_HOST_IN_NET}" \ - -e E2E_RSYNC_IMAGE="${RSYNC_IMG}" \ - -v "${DIND_VOL}:/var/lib/docker:ro" \ + -v "${DIND_VOL}:/var/lib/docker" \ -v "${E2E_TMP_VOL}:/tmp" \ "${IMG}" \ bash -lc ' @@ -200,8 +194,7 @@ else docker run --rm \ --network "${NET}" \ -e DOCKER_HOST="${DIND_HOST_IN_NET}" \ - -e E2E_RSYNC_IMAGE="${RSYNC_IMG}" \ - -v "${DIND_VOL}:/var/lib/docker:ro" \ + -v "${DIND_VOL}:/var/lib/docker" \ -v "${E2E_TMP_VOL}:/tmp" \ "${IMG}" \ bash -lc ' diff --git a/src/baudolo/restore/__main__.py b/src/baudolo/restore/__main__.py index 3ad7629..c68f324 100644 --- a/src/baudolo/restore/__main__.py +++ b/src/baudolo/restore/__main__.py @@ -38,10 +38,6 @@ def main(argv: list[str] | None = None) -> int: # ------------------------------------------------------------------ p_files = sub.add_parser("files", help="Restore files into a docker volume") _add_common_backup_args(p_files) - p_files.add_argument( - "--rsync-image", - default="ghcr.io/kevinveenbirkenbach/alpine-rsync", - ) p_files.add_argument( "--source-volume", default=None, @@ -95,7 +91,6 @@ def main(argv: list[str] | None = None) -> int: return restore_volume_files( args.volume_name, bp_files.files_dir(), - rsync_image=args.rsync_image, ) if args.cmd == "postgres": diff --git a/src/baudolo/restore/files.py b/src/baudolo/restore/files.py index 6450572..e10eed3 100644 --- a/src/baudolo/restore/files.py +++ b/src/baudolo/restore/files.py @@ -3,12 +3,10 @@ from __future__ import annotations import os import sys -from .run import run, docker_volume_exists +from .run import docker_volume_exists, run -def restore_volume_files( - volume_name: str, backup_files_dir: str, *, rsync_image: str -) -> int: +def restore_volume_files(volume_name: str, backup_files_dir: str) -> int: if not os.path.isdir(backup_files_dir): print(f"ERROR: backup files dir not found: {backup_files_dir}", file=sys.stderr) return 2 @@ -19,21 +17,21 @@ def restore_volume_files( else: print(f"Volume {volume_name} already exists.") - # Keep behavior close to the old script: rsync -avv --delete - run( - [ - "docker", - "run", - "--rm", - "-v", - f"{volume_name}:/recover/", - "-v", - f"{backup_files_dir}:/backup/", - rsync_image, - "sh", - "-lc", - "rsync -avv --delete /backup/ /recover/", - ] + cp = run( + ["docker", "volume", "inspect", "--format", "{{ .Mountpoint }}", volume_name], + capture=True, ) + raw = cp.stdout or b"" + mountpoint = (raw.decode() if isinstance(raw, bytes) else raw).strip() + if not mountpoint: + print( + f"ERROR: could not resolve mountpoint for volume {volume_name}", + file=sys.stderr, + ) + return 2 + + src = os.path.join(backup_files_dir, "") + dest = os.path.join(mountpoint, "") + run(["rsync", "-avv", "--delete", src, dest]) print("File restore complete.") return 0