Files
backup-docker-to-local/src/baudolo/restore/files.py
T
Kevin Veen-BirkenbachandClaude Opus 5 efcfe88e7f style!: adopt the core lint bar and migrate to it
The package carried no ruff configuration at all, so it ran on the defaults (E4+E7+E9+F) while infinito-nexus-core, its only consumer, holds itself to a far wider selection. Measured against that selection this tree had 224 findings. It now has none.

The selector list is core's verbatim so both repositories answer to one bar. target-version stays py39 rather than core's py311, because requires-python still declares >=3.9 and pyupgrade would otherwise propose syntax the declared minimum cannot run. Every ignore carries its reason: S603/S607 in particular, since running docker and dump binaries from PATH in list form is this tool's whole job and is already injection-safe.

Two conversions are judgement rather than mechanics. os.path.join(dir, '') was the rsync idiom for a trailing separator, which Path drops, so it becomes an explicit os.sep. os.path.abspath stays where Path.resolve() would follow symlinks and let a symlinked volume test as inside the snapshot subject.

BREAKING CHANGE: VersionMismatch is renamed VersionMismatchError.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 01:51:18 +02:00

65 lines
2.3 KiB
Python

"""Restore a volume's file tree by writing into its mountpoint.
That shortcut only holds for a plain local volume, where the mountpoint *is*
the storage. A volume with driver options - NFS, a bind device, tmpfs - keeps
the same ``/var/lib/docker/volumes/<name>/_data`` path, but docker mounts the
real backing store over it on demand and unmounts it again when the last
consumer stops. Writing there while nothing has it mounted lands in the empty
directory underneath, is hidden by the next mount, and rsync reports success.
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
from .run import docker_volume_exists, run, stdout_of
INSPECT_FORMAT = (
"{{ .Mountpoint }}|{{ .Driver }}|{{ if .Options }}opts{{ else }}plain{{ end }}"
)
def restore_volume_files(volume_name: str, backup_files_dir: str) -> int:
if not Path(backup_files_dir).is_dir():
print(f"ERROR: backup files dir not found: {backup_files_dir}", file=sys.stderr)
return 2
if not docker_volume_exists(volume_name):
print(f"Volume {volume_name} does not exist. Creating...")
run(["docker", "volume", "create", volume_name])
else:
print(f"Volume {volume_name} already exists.")
cp = run(
["docker", "volume", "inspect", "--format", INSPECT_FORMAT, volume_name],
capture=True,
)
fields = stdout_of(cp).split("|")
mountpoint = fields[0] if fields else ""
if not mountpoint:
print(
f"ERROR: could not resolve mountpoint for volume {volume_name}",
file=sys.stderr,
)
return 2
driver, options = ([*fields, "local", "plain"])[1:3]
if (driver != "local" or options == "opts") and not os.path.ismount(mountpoint):
print(
f"ERROR: volume {volume_name} has a backing store of its own "
f"(driver {driver}) but nothing has it mounted; writing to "
f"{mountpoint} now would land under the mount and be lost. "
"Start a container that mounts the volume, then restore again.",
file=sys.stderr,
)
return 2
# rsync reads "dir/" as its contents and "dir" as the directory itself.
src = f"{Path(backup_files_dir)}{os.sep}"
dest = f"{Path(mountpoint)}{os.sep}"
run(["rsync", "-avv", "--delete", src, dest])
print("File restore complete.")
return 0