mirror of
https://github.com/kevinveenbirkenbach/docker-volume-backup.git
synced 2026-10-25 15:35:45 +00:00
Compare commits
4
Commits
1a50eea187
...
v7.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
389b418f42 | ||
|
|
948b8347f4 | ||
|
|
f824fabea1 | ||
|
|
07d819cb41 |
No files matched your search
@@ -1,5 +1,32 @@
|
||||
# Changelog
|
||||
|
||||
## [7.1.1] - 2026-10-17
|
||||
|
||||
Fixed:
|
||||
|
||||
- Tests: e2e MariaDB probe ran root passwordless and timed out at random
|
||||
- Tests: probe now logs in with the root password, skips the temp server
|
||||
- Tests: anonymous-preemption bootstrap now uses the root password too
|
||||
|
||||
Upgrade notes:
|
||||
|
||||
- 7.1.0 never reached stable: its CI failed, no image was published
|
||||
- 7.1.1 is the first published -aHAX build; 7.1.0 upgrade notes apply
|
||||
|
||||
## [7.1.0] - 2026-10-17
|
||||
|
||||
Fixed:
|
||||
|
||||
- Backup: rsync -a stored no xattrs, ACLs or hardlinks; now -aHAX
|
||||
- Restore: restore_volume_files had the same gap; now -aHAX too
|
||||
- An OpenCloud volume lost its storage metadata through a DR drill
|
||||
|
||||
Upgrade notes:
|
||||
|
||||
- First backup re-copies every attribute-carrying volume once in full
|
||||
- A destination without xattr support now fails the backup run outright
|
||||
- exFAT, xattr-less NFS and object-storage FUSE mounts must move first
|
||||
|
||||
## [7.0.2] - 2026-09-23
|
||||
|
||||
* Backup: failed live pre-copy no longer aborts when a stopped copy follows
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "backup-docker-to-local"
|
||||
version = "7.0.2"
|
||||
version = "7.1.1"
|
||||
description = "Backup Docker volumes to local with rsync and optional DB dumps."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
|
||||
@@ -75,7 +75,7 @@ def backup_volume(
|
||||
pathlib.Path(dest).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
last = get_last_backup_dir(versions_dir, volume_name, dest)
|
||||
cmd = ["rsync", "-aP", "--no-D", "--delete", "--delete-excluded"]
|
||||
cmd = ["rsync", "-aHAX", "-P", "--no-D", "--delete", "--delete-excluded"]
|
||||
if authoritative:
|
||||
cmd.append("--checksum")
|
||||
if last:
|
||||
|
||||
@@ -59,6 +59,6 @@ def restore_volume_files(volume_name: str, backup_files_dir: str) -> int:
|
||||
# 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])
|
||||
run(["rsync", "-aHAX", "-vv", "--delete", src, dest])
|
||||
print("File restore complete.")
|
||||
return 0
|
||||
@@ -96,12 +96,13 @@ def wait_for_mariadb(
|
||||
container: str, *, root_password: str, timeout_s: int = 90
|
||||
) -> None:
|
||||
"""
|
||||
Liveness probe for MariaDB.
|
||||
Readiness probe for MariaDB, as root over the socket.
|
||||
|
||||
IMPORTANT (MariaDB 11):
|
||||
Root TCP auth is often restricted (unix_socket auth), so a TCP ping like
|
||||
`mariadb-admin -uroot -p... -h localhost ping` can fail even though the server is up.
|
||||
We therefore check readiness via a socket-based query.
|
||||
The entrypoint drops root@127.0.0.1, so root is socket-only, and it
|
||||
installs root@localhost without unix_socket auth: a probe without the
|
||||
password only passes in the init window before the password is set.
|
||||
`@@skip_networking` must be 0 so the entrypoint's temporary server,
|
||||
which shuts down right after init, does not count as ready.
|
||||
"""
|
||||
deadline = time.time() + timeout_s
|
||||
while time.time() < deadline:
|
||||
@@ -112,12 +113,15 @@ def wait_for_mariadb(
|
||||
container,
|
||||
"sh",
|
||||
"-lc",
|
||||
'mariadb -uroot --protocol=socket -e "SELECT 1;"',
|
||||
(
|
||||
f"mariadb -uroot -p{root_password} --protocol=socket "
|
||||
'-N -e "SELECT @@skip_networking;"'
|
||||
),
|
||||
],
|
||||
capture=True,
|
||||
check=False,
|
||||
)
|
||||
if p.returncode == 0:
|
||||
if p.returncode == 0 and p.stdout.strip() == "0":
|
||||
return
|
||||
time.sleep(1)
|
||||
raise TimeoutError(
|
||||
|
||||
@@ -100,7 +100,10 @@ class TestE2EMariaDBAnonymousPreemption(unittest.TestCase):
|
||||
cls.db_container,
|
||||
"sh",
|
||||
"-lc",
|
||||
f'mariadb -uroot --protocol=socket -e "{bootstrap_sql}"',
|
||||
(
|
||||
f"mariadb -uroot -p{cls.root_password} --protocol=socket "
|
||||
f'-e "{bootstrap_sql}"'
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@@ -46,9 +46,12 @@ class TestBackupVolume(unittest.TestCase):
|
||||
def test_it_carries_no_kernel_objects_into_a_generation(self) -> None:
|
||||
self.assertIn("--no-D", self.copy())
|
||||
|
||||
def test_it_stores_the_metadata_a_restore_has_to_give_back(self) -> None:
|
||||
self.assertIn("-aHAX", self.copy())
|
||||
|
||||
def test_it_keeps_no_twin_of_what_the_second_pass_replaces(self) -> None:
|
||||
command = self.copy(authoritative=True)
|
||||
self.assertEqual(command[:2], ["rsync", "-aP"])
|
||||
self.assertEqual(command[:3], ["rsync", "-aHAX", "-P"])
|
||||
self.assertNotIn("--backup", command)
|
||||
|
||||
def test_it_creates_the_destination(self) -> None:
|
||||
|
||||
Reference in new issue
Block a user