mirror of
https://github.com/kevinveenbirkenbach/docker-volume-backup.git
synced 2026-08-02 13:02:41 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a93a61ca9 | |||
| 988d92534c |
15
CHANGELOG.md
15
CHANGELOG.md
@@ -1,5 +1,20 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [3.4.0] - 2026-08-02
|
||||||
|
|
||||||
|
- Backup: *-a* implies *-D*, so a generation was written with
|
||||||
|
*--devices --specials* and rsync recreated every unix socket and fifo found in
|
||||||
|
a volume. Where the backup root is an nfs-ganesha export, ganesha accepts the
|
||||||
|
socket on write but cannot serve it back, and the remote pull's sender then
|
||||||
|
fails with *readdir* / *readlink_stat* "Invalid argument (22)" and exits 23 —
|
||||||
|
deterministically, for every retry. *--no-D* keeps them out of the generation.
|
||||||
|
- Backup: nothing restorable is lost. Sockets and fifos are recreated by the
|
||||||
|
daemons that own them, and the postfix queue itself — *incoming*, *active*,
|
||||||
|
*deferred*, *hold*, *maildrop* — is unaffected, so accepted-but-undelivered
|
||||||
|
mail stays in the backup. Device nodes go too; the only volume that could hold
|
||||||
|
them is a nested docker data root, which does not belong in a backup anyway.
|
||||||
|
- Tests: the flag is asserted on the rsync invocation.
|
||||||
|
|
||||||
## [3.3.0] - 2026-08-02
|
## [3.3.0] - 2026-08-02
|
||||||
|
|
||||||
- Backup: *--volumes-no-backup-required* excludes a volume by name.
|
- Backup: *--volumes-no-backup-required* excludes a volume by name.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "backup-docker-to-local"
|
name = "backup-docker-to-local"
|
||||||
version = "3.3.0"
|
version = "3.4.0"
|
||||||
description = "Backup Docker volumes to local with rsync and optional DB dumps."
|
description = "Backup Docker volumes to local with rsync and optional DB dumps."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
|
|||||||
@@ -49,7 +49,10 @@ def backup_volume(
|
|||||||
link_dest = f"--link-dest='{last}'" if last else ""
|
link_dest = f"--link-dest='{last}'" if last else ""
|
||||||
verify = "--checksum " if authoritative else ""
|
verify = "--checksum " if authoritative else ""
|
||||||
|
|
||||||
cmd = f"rsync -abP --delete --delete-excluded {verify}{link_dest} {source} {dest}"
|
cmd = (
|
||||||
|
f"rsync -abP --no-D --delete --delete-excluded "
|
||||||
|
f"{verify}{link_dest} {source} {dest}"
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
execute_shell_command(cmd)
|
execute_shell_command(cmd)
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ class TestBackupVolume(unittest.TestCase):
|
|||||||
def test_it_always_deletes_what_the_source_no_longer_has(self) -> None:
|
def test_it_always_deletes_what_the_source_no_longer_has(self) -> None:
|
||||||
self.assertIn("--delete", self.copy())
|
self.assertIn("--delete", self.copy())
|
||||||
|
|
||||||
|
def test_it_carries_no_kernel_objects_into_a_generation(self) -> None:
|
||||||
|
self.assertIn("--no-D", self.copy())
|
||||||
|
|
||||||
def test_it_creates_the_destination(self) -> None:
|
def test_it_creates_the_destination(self) -> None:
|
||||||
with tempfile.TemporaryDirectory() as tmp:
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
dest = Path(tmp) / "gen" / "demo"
|
dest = Path(tmp) / "gen" / "demo"
|
||||||
|
|||||||
Reference in New Issue
Block a user