Use root account for database
This commit is contained in:
+81
-73
@@ -18,7 +18,7 @@ umask 077
|
|||||||
CONTAINER="${DOCKER_CONTAINER_NAME:-mariadb}"
|
CONTAINER="${DOCKER_CONTAINER_NAME:-mariadb}"
|
||||||
|
|
||||||
PASSWORD_FILE="${PASSWORD_FILE:-/root/.mariadb-backup.pw}"
|
PASSWORD_FILE="${PASSWORD_FILE:-/root/.mariadb-backup.pw}"
|
||||||
DB_USER="${DB_USER:-backup}"
|
DB_USER="${DB_USER:-root}"
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
|
||||||
DUMP_DIR="${DUMP_DIR:-dump}"
|
DUMP_DIR="${DUMP_DIR:-dump}"
|
||||||
@@ -31,7 +31,10 @@ EXCLUDED_SCHEMAS="'information_schema','mysql','performance_schema','sys'"
|
|||||||
|
|
||||||
# =================================================================
|
# =================================================================
|
||||||
|
|
||||||
die() { echo "ERROR: $*" >&2; exit 1; }
|
die() {
|
||||||
|
echo "ERROR: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
warn() { echo "WARNING: $*" >&2; }
|
warn() { echo "WARNING: $*" >&2; }
|
||||||
|
|
||||||
cleanup() { rm -rf "$STAGING"; }
|
cleanup() { rm -rf "$STAGING"; }
|
||||||
@@ -41,39 +44,41 @@ trap cleanup EXIT
|
|||||||
|
|
||||||
command -v docker >/dev/null 2>&1 || die "docker not found in PATH"
|
command -v docker >/dev/null 2>&1 || die "docker not found in PATH"
|
||||||
|
|
||||||
[[ "$(docker inspect -f '{{.State.Running}}' "$CONTAINER" 2>/dev/null || echo false)" == "true" ]] \
|
[[ "$(docker inspect -f '{{.State.Running}}' "$CONTAINER" 2>/dev/null || echo false)" == "true" ]] ||
|
||||||
|| die "container '$CONTAINER' is not running"
|
die "container '$CONTAINER' is not running"
|
||||||
|
|
||||||
if [[ -n "${MYSQL_ROOT_PASSWORD:-}" ]]; then
|
if [[ -n "${MYSQL_ROOT_PASSWORD:-}" ]]; then
|
||||||
DB_PASS="$MYSQL_ROOT_PASSWORD"
|
DB_PASS="$MYSQL_ROOT_PASSWORD"
|
||||||
elif [[ -r "$PASSWORD_FILE" ]]; then
|
elif [[ -r "$PASSWORD_FILE" ]]; then
|
||||||
DB_PASS="$(< "$PASSWORD_FILE")"
|
DB_PASS="$(<"$PASSWORD_FILE")"
|
||||||
else
|
else
|
||||||
die "no password: set MYSQL_ROOT_PASSWORD or create $PASSWORD_FILE (chmod 600)"
|
die "no password: set MYSQL_ROOT_PASSWORD or create $PASSWORD_FILE (chmod 600)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Detect client binaries (MariaDB 11+ renamed them; MySQL keeps mysql/*).
|
# Detect client binaries (MariaDB 11+ renamed them; MySQL keeps mysql/*).
|
||||||
if docker exec "$CONTAINER" sh -c 'command -v mariadb-dump' >/dev/null 2>&1; then
|
if docker exec "$CONTAINER" sh -c 'command -v mariadb-dump' >/dev/null 2>&1; then
|
||||||
CLIENT=mariadb; DUMPER=mariadb-dump
|
CLIENT=mariadb
|
||||||
CLIENT_BIN="mariadb"
|
DUMPER=mariadb-dump
|
||||||
|
CLIENT_BIN="mariadb"
|
||||||
else
|
else
|
||||||
CLIENT=mysql; DUMPER=mysqldump
|
CLIENT=mysql
|
||||||
CLIENT_BIN="mysql"
|
DUMPER=mysqldump
|
||||||
|
CLIENT_BIN="mysql"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# MYSQL_PWD keeps the password out of the process list.
|
# MYSQL_PWD keeps the password out of the process list.
|
||||||
db_exec() { docker exec -e MYSQL_PWD="$DB_PASS" "$CONTAINER" "$@"; }
|
db_exec() { docker exec -e MYSQL_PWD="$DB_PASS" "$CONTAINER" "$@"; }
|
||||||
sql() { db_exec "$CLIENT_BIN" -u "$DB_USER" -N -B -e "$1"; }
|
sql() { db_exec "$CLIENT_BIN" -u "$DB_USER" -N -B -e "$1"; }
|
||||||
|
|
||||||
DUMP_OPTS=(
|
DUMP_OPTS=(
|
||||||
-u "$DB_USER"
|
-u "$DB_USER"
|
||||||
--single-transaction
|
--single-transaction
|
||||||
--quick
|
--quick
|
||||||
--routines --triggers --events
|
--routines --triggers --events
|
||||||
--hex-blob
|
--hex-blob
|
||||||
--max-allowed-packet=1G
|
--max-allowed-packet=1G
|
||||||
--default-character-set=utf8mb4
|
--default-character-set=utf8mb4
|
||||||
--skip-dump-date
|
--skip-dump-date
|
||||||
)
|
)
|
||||||
[[ "$DEDUP_FRIENDLY" == "1" ]] && DUMP_OPTS+=(--skip-extended-insert)
|
[[ "$DEDUP_FRIENDLY" == "1" ]] && DUMP_OPTS+=(--skip-extended-insert)
|
||||||
|
|
||||||
@@ -87,9 +92,9 @@ NON_TXN="$(sql "
|
|||||||
AND table_schema NOT IN ($EXCLUDED_SCHEMAS);" || true)"
|
AND table_schema NOT IN ($EXCLUDED_SCHEMAS);" || true)"
|
||||||
|
|
||||||
if [[ -n "$NON_TXN" ]]; then
|
if [[ -n "$NON_TXN" ]]; then
|
||||||
warn "non-transactional tables found - NOT covered by --single-transaction:"
|
warn "non-transactional tables found - NOT covered by --single-transaction:"
|
||||||
echo "$NON_TXN" | sed 's/^/ /' >&2
|
echo "$NON_TXN" | sed 's/^/ /' >&2
|
||||||
warn "convert them: ALTER TABLE <t> ENGINE=InnoDB;"
|
warn "convert them: ALTER TABLE <t> ENGINE=InnoDB;"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Database list ------------------------------------------------------
|
# --- Database list ------------------------------------------------------
|
||||||
@@ -99,15 +104,15 @@ mapfile -t DATABASES < <(sql "
|
|||||||
WHERE schema_name NOT IN ($EXCLUDED_SCHEMAS)
|
WHERE schema_name NOT IN ($EXCLUDED_SCHEMAS)
|
||||||
ORDER BY schema_name;" | tr -d '\r')
|
ORDER BY schema_name;" | tr -d '\r')
|
||||||
|
|
||||||
(( ${#DATABASES[@]} > 0 )) || die "no databases returned - check credentials"
|
((${#DATABASES[@]} > 0)) || die "no databases returned - check credentials"
|
||||||
echo "Found ${#DATABASES[@]} database(s) to dump"
|
echo "Found ${#DATABASES[@]} database(s) to dump"
|
||||||
|
|
||||||
# --- Free space sanity check --------------------------------------------
|
# --- Free space sanity check --------------------------------------------
|
||||||
|
|
||||||
if [[ -d "$DUMP_DIR" ]]; then
|
if [[ -d "$DUMP_DIR" ]]; then
|
||||||
need_kb=$(( $(du -sk "$DUMP_DIR" | cut -f1) * 3 / 2 ))
|
need_kb=$(($(du -sk "$DUMP_DIR" | cut -f1) * 3 / 2))
|
||||||
free_kb=$(df -Pk "$(dirname "$DUMP_DIR")" | awk 'NR==2{print $4}')
|
free_kb=$(df -Pk "$(dirname "$DUMP_DIR")" | awk 'NR==2{print $4}')
|
||||||
(( free_kb > need_kb )) || die "not enough free space (~${need_kb}K needed, ${free_kb}K free)"
|
((free_kb > need_kb)) || die "not enough free space (~${need_kb}K needed, ${free_kb}K free)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Detect user-dump method --------------------------------------------
|
# --- Detect user-dump method --------------------------------------------
|
||||||
@@ -116,25 +121,25 @@ fi
|
|||||||
# fallback : dump full mysql schema (larger, includes grant tables)
|
# fallback : dump full mysql schema (larger, includes grant tables)
|
||||||
|
|
||||||
dump_users_and_grants() {
|
dump_users_and_grants() {
|
||||||
local out="$1"
|
local out="$1"
|
||||||
if "$DUMPER" --help 2>&1 | grep -q -- ' --users '; then
|
if "$DUMPER" --help 2>&1 | grep -q -- ' --users '; then
|
||||||
# MySQL 8.0.31+: --users dumps CREATE USER + GRANT statements only
|
# MySQL 8.0.31+: --users dumps CREATE USER + GRANT statements only
|
||||||
# (when no --databases / --all-databases is given)
|
# (when no --databases / --all-databases is given)
|
||||||
log "[dump] using mysqldump --users (MySQL 8.0.31+ style)"
|
log "[dump] using mysqldump --users (MySQL 8.0.31+ style)"
|
||||||
db_exec "$DUMPER" -u "$DB_USER" --users \
|
db_exec "$DUMPER" -u "$DB_USER" --users \
|
||||||
> "$out" 2>/dev/null
|
>"$out" 2>/dev/null
|
||||||
elif docker exec "$CONTAINER" sh -c "command -v mariadb-dump" >/dev/null 2>&1 \
|
elif docker exec "$CONTAINER" sh -c "command -v mariadb-dump" >/dev/null 2>&1 &&
|
||||||
&& docker exec "$CONTAINER" mariadb-dump --help 2>&1 | grep -q -- ' --system '; then
|
docker exec "$CONTAINER" mariadb-dump --help 2>&1 | grep -q -- ' --system '; then
|
||||||
# MariaDB: --system=users
|
# MariaDB: --system=users
|
||||||
log "[dump] using mariadb-dump --system=users"
|
log "[dump] using mariadb-dump --system=users"
|
||||||
db_exec "$DUMPER" -u "$DB_USER" --system=users \
|
db_exec "$DUMPER" -u "$DB_USER" --system=users \
|
||||||
> "$out" 2>/dev/null
|
>"$out" 2>/dev/null
|
||||||
else
|
else
|
||||||
# Fallback: full mysql schema dump
|
# Fallback: full mysql schema dump
|
||||||
log "[dump] falling back to full mysql schema dump (older server)"
|
log "[dump] falling back to full mysql schema dump (older server)"
|
||||||
db_exec "$DUMPER" "${DUMP_OPTS[@]}" --databases mysql \
|
db_exec "$DUMPER" "${DUMP_OPTS[@]}" --databases mysql \
|
||||||
> "$out" 2>/dev/null
|
>"$out" 2>/dev/null
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Dump into staging, verify each file --------------------------------
|
# --- Dump into staging, verify each file --------------------------------
|
||||||
@@ -145,48 +150,51 @@ mkdir -p "$STAGING"
|
|||||||
failed=()
|
failed=()
|
||||||
|
|
||||||
verify_dump() {
|
verify_dump() {
|
||||||
local f="$1"
|
local f="$1"
|
||||||
[[ -s "$f" ]] || { warn "$(basename "$f"): empty"; return 1; }
|
[[ -s "$f" ]] || {
|
||||||
# mysqldump ends with "-- Dump completed on ..."; mysqlpump ends similarly.
|
warn "$(basename "$f"): empty"
|
||||||
# For the mysql-schema fallback, just check non-empty (it has no marker).
|
|
||||||
if grep -q '^-- Dump completed' "$f" 2>/dev/null; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
# Fallback files (full mysql dump) won't have the marker — accept non-empty.
|
|
||||||
if [[ "$(basename "$f")" == 00-mysql-schema.sql ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
warn "$(basename "$f"): no completion marker"
|
|
||||||
return 1
|
return 1
|
||||||
|
}
|
||||||
|
# mysqldump ends with "-- Dump completed on ..."; mysqlpump ends similarly.
|
||||||
|
# For the mysql-schema fallback, just check non-empty (it has no marker).
|
||||||
|
if grep -q '^-- Dump completed' "$f" 2>/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
# Fallback files (full mysql dump) won't have the marker — accept non-empty.
|
||||||
|
if [[ "$(basename "$f")" == 00-mysql-schema.sql ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
warn "$(basename "$f"): no completion marker"
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Users and grants first
|
# Users and grants first
|
||||||
USERS_FILE="$STAGING/00-users-and-grants.sql"
|
USERS_FILE="$STAGING/00-users-and-grants.sql"
|
||||||
echo "Dumping users and grants"
|
echo "Dumping users and grants"
|
||||||
if dump_users_and_grants "$USERS_FILE" && verify_dump "$USERS_FILE"; then
|
if dump_users_and_grants "$USERS_FILE" && verify_dump "$USERS_FILE"; then
|
||||||
echo " -> ok ($(du -h "$USERS_FILE" | cut -f1))"
|
echo " -> ok ($(du -h "$USERS_FILE" | cut -f1))"
|
||||||
else
|
else
|
||||||
warn "users-and-grants dump failed or empty"
|
warn "users-and-grants dump failed or empty"
|
||||||
failed+=("users-and-grants")
|
failed+=("users-and-grants")
|
||||||
rm -f "$USERS_FILE"
|
rm -f "$USERS_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for db in "${DATABASES[@]}"; do
|
for db in "${DATABASES[@]}"; do
|
||||||
out="$STAGING/${db}.sql"
|
out="$STAGING/${db}.sql"
|
||||||
printf 'Dumping %-32s ' "$db"
|
printf 'Dumping %-32s ' "$db"
|
||||||
if db_exec "$DUMPER" "${DUMP_OPTS[@]}" -- "$db" > "$out" && verify_dump "$out"; then
|
if db_exec "$DUMPER" "${DUMP_OPTS[@]}" -- "$db" >"$out" && verify_dump "$out"; then
|
||||||
printf 'ok (%s)\n' "$(du -h "$out" | cut -f1)"
|
printf 'ok (%s)\n' "$(du -h "$out" | cut -f1)"
|
||||||
else
|
else
|
||||||
printf 'FAILED\n'
|
printf 'FAILED\n'
|
||||||
failed+=("$db")
|
failed+=("$db")
|
||||||
rm -f "$out"
|
rm -f "$out"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# --- Abort before touching the good copy --------------------------------
|
# --- Abort before touching the good copy --------------------------------
|
||||||
|
|
||||||
if (( ${#failed[@]} > 0 )); then
|
if ((${#failed[@]} > 0)); then
|
||||||
die "${#failed[@]} dump(s) failed: ${failed[*]} - previous dumps left intact"
|
die "${#failed[@]} dump(s) failed: ${failed[*]} - previous dumps left intact"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Atomic swap --------------------------------------------------------
|
# --- Atomic swap --------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user