mirror of
https://github.com/kevinveenbirkenbach/docker-volume-backup.git
synced 2026-07-17 06:05:13 +00:00
Restore fixes, both hit by the infinito svc-bkp e2e drill: - mariadb --empty dropped tables one docker exec at a time with SET FOREIGN_KEY_CHECKS=0 issued in a separate client session, so the session-scoped toggle never applied and any FK-referenced parent table (mailu.users) died with ERROR 1451. Issue the toggle and all DROPs in one session. - postgres --empty now drops only current_user-owned objects (extension members like pg_trgm's set_limit are superuser-owned) with IF EXISTS absorbing CASCADE fallout, and the replay skips superuser-only dump lines (COMMENT ON EXTENSION, ALTER DEFAULT PRIVILEGES) that abort an app-user psql run under ON_ERROR_STOP. Backup fixes: - pg_dump now runs with --no-owner --no-privileges so future dumps are replayable by the owning app user in the first place. - Swarm task containers are never stopped or started manually: the orchestrator replaces a stopped task and a later docker start fails on the detached overlay network. filter_stoppable skips them visibly and the whitelist stop check ignores them. Validated end to end against a live infinito compose stack: the full svc-bkp-volume-2-local drill (verify, restore cycle, sql replay for mailu, keycloak and one more db) passes with these patches applied. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
93 lines
2.6 KiB
Python
93 lines
2.6 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
|
|
from ..run import docker_exec, docker_exec_sh
|
|
|
|
|
|
def _pick_client(container: str) -> str:
|
|
"""
|
|
Prefer 'mariadb', fallback to 'mysql'.
|
|
Some MariaDB images no longer ship a 'mysql' binary, so we must not assume it exists.
|
|
"""
|
|
script = r"""
|
|
set -eu
|
|
if command -v mariadb >/dev/null 2>&1; then echo mariadb; exit 0; fi
|
|
if command -v mysql >/dev/null 2>&1; then echo mysql; exit 0; fi
|
|
exit 42
|
|
"""
|
|
try:
|
|
out = docker_exec_sh(container, script, capture=True).stdout.decode().strip()
|
|
if not out:
|
|
raise RuntimeError("empty client detection output")
|
|
return out
|
|
except Exception as e:
|
|
print(
|
|
"ERROR: neither 'mariadb' nor 'mysql' found in container.", file=sys.stderr
|
|
)
|
|
raise e
|
|
|
|
|
|
def restore_mariadb_sql(
|
|
*,
|
|
container: str,
|
|
db_name: str,
|
|
user: str,
|
|
password: str,
|
|
sql_path: str,
|
|
empty: bool,
|
|
) -> None:
|
|
client = _pick_client(container)
|
|
|
|
if not os.path.isfile(sql_path):
|
|
raise FileNotFoundError(sql_path)
|
|
|
|
if empty:
|
|
# IMPORTANT:
|
|
# Do NOT hardcode 'mysql' here. Use the detected client.
|
|
# MariaDB 11 images may not contain the mysql binary at all.
|
|
result = docker_exec(
|
|
container,
|
|
[
|
|
client,
|
|
"-u",
|
|
user,
|
|
f"--password={password}",
|
|
"-N",
|
|
"-e",
|
|
f"SELECT table_name FROM information_schema.tables WHERE table_schema = '{db_name}';",
|
|
],
|
|
capture=True,
|
|
)
|
|
tables = result.stdout.decode().split()
|
|
|
|
if tables:
|
|
# SET FOREIGN_KEY_CHECKS is session-scoped, so it must share one
|
|
# client session with the DROPs or FK constraints still fire.
|
|
drop_sql = (
|
|
"SET FOREIGN_KEY_CHECKS=0; "
|
|
+ " ".join(
|
|
f"DROP TABLE IF EXISTS `{db_name}`.`{tbl}`;" for tbl in tables
|
|
)
|
|
+ " SET FOREIGN_KEY_CHECKS=1;"
|
|
)
|
|
docker_exec(
|
|
container,
|
|
[
|
|
client,
|
|
"-u",
|
|
user,
|
|
f"--password={password}",
|
|
"-e",
|
|
drop_sql,
|
|
],
|
|
)
|
|
|
|
with open(sql_path, "rb") as f:
|
|
docker_exec(
|
|
container, [client, "-u", user, f"--password={password}", db_name], stdin=f
|
|
)
|
|
|
|
print(f"MariaDB/MySQL restore complete for db '{db_name}'.")
|