mirror of
https://github.com/kevinveenbirkenbach/docker-volume-backup.git
synced 2026-11-05 18:57:42 +00:00
MariaDB dumps and restores read the password from MYSQL_PWD, and docker exec forwards env variables by name only, so no process listing shows a password. baudolo-restore gains --db-password-stdin; --db-password still works but warns. 🎯 Fix: 96 % 💥 Break: 3 % 💸 Cost: – ⏱️ Time: – 🛡️ Security: ~100 % 🏆 Score: 93 🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 % Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
107 lines
2.9 KiB
Python
107 lines
2.9 KiB
Python
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from baudolo.restore.run import docker_exec, docker_exec_sh
|
|
|
|
from .version import guard
|
|
|
|
_NO_CLIENT = "ERROR: neither 'mariadb' nor 'mysql' found in container."
|
|
|
|
|
|
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()
|
|
except Exception:
|
|
print(_NO_CLIENT, file=sys.stderr)
|
|
raise
|
|
if not out:
|
|
print(_NO_CLIENT, file=sys.stderr)
|
|
raise RuntimeError("empty client detection output")
|
|
return out
|
|
|
|
|
|
def restore_mariadb_sql(
|
|
*,
|
|
container: str,
|
|
db_name: str,
|
|
user: str,
|
|
password: str,
|
|
sql_path: str,
|
|
empty: bool,
|
|
check_version: bool = True,
|
|
) -> None:
|
|
client = _pick_client(container)
|
|
|
|
if not Path(sql_path).is_file():
|
|
raise FileNotFoundError(sql_path)
|
|
|
|
docker_env = {"MYSQL_PWD": password}
|
|
|
|
if check_version:
|
|
guard(
|
|
sql_path=sql_path,
|
|
engine="mariadb",
|
|
container=container,
|
|
user=user,
|
|
password=password,
|
|
client=client,
|
|
)
|
|
|
|
if empty:
|
|
# Do not hardcode 'mysql': MariaDB 11 images may not ship that binary.
|
|
result = docker_exec(
|
|
container,
|
|
[
|
|
client,
|
|
"-u",
|
|
user,
|
|
"-N",
|
|
"-e",
|
|
f"SELECT table_name FROM information_schema.tables WHERE table_schema = '{db_name}';", # noqa: S608 - validate_database() constrains the name to ^[a-zA-Z0-9_][a-zA-Z0-9_-]*$
|
|
],
|
|
capture=True,
|
|
docker_env=docker_env,
|
|
)
|
|
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,
|
|
"-e",
|
|
drop_sql,
|
|
],
|
|
docker_env=docker_env,
|
|
)
|
|
|
|
with Path(sql_path).open("rb") as f:
|
|
docker_exec(
|
|
container, [client, "-u", user, db_name], stdin=f, docker_env=docker_env
|
|
)
|
|
|
|
print(f"MariaDB/MySQL restore complete for db '{db_name}'.")
|