197 lines
5.0 KiB
Bash
197 lines
5.0 KiB
Bash
#!/usr/bin/env bash
|
||
# Nexus — MySQL dump(Docker 1Panel / 宿主机 mysqldump 通用)
|
||
#
|
||
# bash deploy/mysql_dump_to_file.sh --output /path/dump.sql [--root /opt/nexus]
|
||
# bash deploy/mysql_dump_to_file.sh --output /path/dump.sql --db-url 'mysql+aiomysql://...'
|
||
#
|
||
# 策略(按序):
|
||
# 1. DB host 为运行中容器名 → docker exec 该容器内 mysqldump(连 127.0.0.1)
|
||
# 2. 宿主机有 mysqldump 且 host 可解析 → 本机 mysqldump
|
||
# 3. 1panel-network 存在 → docker run --rm mysql:8 客户端
|
||
set -euo pipefail
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||
NEXUS_ROOT="${NEXUS_ROOT:-/opt/nexus}"
|
||
OUTPUT=""
|
||
DB_URL=""
|
||
|
||
usage() {
|
||
cat <<'EOF'
|
||
用法: mysql_dump_to_file.sh --output FILE.sql [--root /opt/nexus] [--db-url URL]
|
||
EOF
|
||
}
|
||
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--output|-o) OUTPUT="$2"; shift 2 ;;
|
||
--root) NEXUS_ROOT="$2"; shift 2 ;;
|
||
--db-url) DB_URL="$2"; shift 2 ;;
|
||
-h|--help) usage; exit 0 ;;
|
||
*) echo "未知参数: $1" >&2; usage; exit 1 ;;
|
||
esac
|
||
done
|
||
|
||
if [[ -z "$OUTPUT" ]]; then
|
||
echo "缺少 --output" >&2
|
||
usage
|
||
exit 1
|
||
fi
|
||
|
||
resolve_db_url() {
|
||
if [[ -n "$DB_URL" ]]; then
|
||
echo "$DB_URL"
|
||
return 0
|
||
fi
|
||
local cname url
|
||
cname="$(docker ps --format '{{.Names}}' 2>/dev/null | grep -E 'nexus-prod-nexus|nexus-nexus-1' | head -1 || true)"
|
||
if [[ -n "$cname" ]]; then
|
||
url="$(docker exec "$cname" grep -E '^NEXUS_DATABASE_URL=' /app/.env 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"' | tr -d "'" || true)"
|
||
[[ -n "$url" ]] && echo "$url" && return 0
|
||
fi
|
||
for f in \
|
||
"${NEXUS_ROOT}/docker/.env.prod" \
|
||
"${NEXUS_ROOT}/.env" \
|
||
; do
|
||
[[ -f "$f" ]] || continue
|
||
url="$(grep -E '^NEXUS_DATABASE_URL=' "$f" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"' | tr -d "'" || true)"
|
||
[[ -n "$url" ]] && echo "$url" && return 0
|
||
done
|
||
return 1
|
||
}
|
||
|
||
DB_URL="$(resolve_db_url || true)"
|
||
if [[ -z "$DB_URL" ]]; then
|
||
echo "未找到 NEXUS_DATABASE_URL" >&2
|
||
exit 1
|
||
fi
|
||
|
||
python3 - "$DB_URL" "$OUTPUT" <<'PY'
|
||
import shutil
|
||
import subprocess
|
||
import sys
|
||
import urllib.parse
|
||
|
||
|
||
def docker_names() -> set[str]:
|
||
r = subprocess.run(
|
||
["docker", "ps", "--format", "{{.Names}}"],
|
||
capture_output=True,
|
||
text=True,
|
||
check=False,
|
||
)
|
||
return {ln.strip() for ln in (r.stdout or "").splitlines() if ln.strip()}
|
||
|
||
|
||
def network_exists(name: str) -> bool:
|
||
return (
|
||
subprocess.run(
|
||
["docker", "network", "inspect", name],
|
||
capture_output=True,
|
||
check=False,
|
||
).returncode
|
||
== 0
|
||
)
|
||
|
||
|
||
def write_dump(cmd: list[str], env: dict[str, str], out: str) -> int:
|
||
r = subprocess.run(cmd, env=env, capture_output=True, text=True)
|
||
if r.returncode != 0:
|
||
sys.stderr.write(r.stderr or r.stdout or "mysqldump failed\n")
|
||
return r.returncode
|
||
with open(out, "w", encoding="utf-8") as fh:
|
||
fh.write(r.stdout)
|
||
return 0
|
||
|
||
|
||
def dump_flags(db: str) -> list[str]:
|
||
return [
|
||
"--single-transaction",
|
||
"--routines",
|
||
"--triggers",
|
||
"--set-gtid-purged=OFF",
|
||
db,
|
||
]
|
||
|
||
|
||
url = sys.argv[1].replace("mysql+aiomysql://", "mysql://", 1)
|
||
url = url.replace("mysql+asyncmy://", "mysql://", 1)
|
||
out = sys.argv[2]
|
||
p = urllib.parse.urlparse(url)
|
||
user = urllib.parse.unquote(p.username or "")
|
||
password = urllib.parse.unquote(p.password or "")
|
||
host = p.hostname or "127.0.0.1"
|
||
if host == "host.docker.internal":
|
||
host = "127.0.0.1"
|
||
port = str(p.port or 3306)
|
||
db = (p.path or "/nexus").lstrip("/").split("?")[0]
|
||
env = {**__import__("os").environ, "MYSQL_PWD": password}
|
||
names = docker_names()
|
||
|
||
# 1) MySQL 跑在 Docker 容器内(1Panel 常见:URL host = 容器名)
|
||
if host in names:
|
||
cmd = [
|
||
"docker",
|
||
"exec",
|
||
"-e",
|
||
f"MYSQL_PWD={password}",
|
||
host,
|
||
"mysqldump",
|
||
"-h",
|
||
"127.0.0.1",
|
||
"-P",
|
||
port,
|
||
"-u",
|
||
user,
|
||
*dump_flags(db),
|
||
]
|
||
rc = write_dump(cmd, env, out)
|
||
if rc == 0:
|
||
sys.exit(0)
|
||
sys.stderr.write(f"docker exec {host} mysqldump 失败,尝试其它方式…\n")
|
||
|
||
# 2) 宿主机 mysqldump
|
||
if shutil.which("mysqldump"):
|
||
cmd = [
|
||
"mysqldump",
|
||
"-h",
|
||
host,
|
||
"-P",
|
||
port,
|
||
"-u",
|
||
user,
|
||
*dump_flags(db),
|
||
]
|
||
rc = write_dump(cmd, env, out)
|
||
if rc == 0:
|
||
sys.exit(0)
|
||
|
||
# 3) 临时 mysql 客户端容器(1panel-network)
|
||
for net in ("1panel-network", "bridge"):
|
||
if not network_exists(net):
|
||
continue
|
||
cmd = [
|
||
"docker",
|
||
"run",
|
||
"--rm",
|
||
"--network",
|
||
net,
|
||
"-e",
|
||
f"MYSQL_PWD={password}",
|
||
"mysql:8.0",
|
||
"mysqldump",
|
||
"-h",
|
||
host,
|
||
"-P",
|
||
port,
|
||
"-u",
|
||
user,
|
||
*dump_flags(db),
|
||
]
|
||
rc = write_dump(cmd, env, out)
|
||
if rc == 0:
|
||
sys.exit(0)
|
||
|
||
sys.stderr.write("所有 mysqldump 策略均失败(无宿主机客户端且 Docker 回退不可用)\n")
|
||
sys.exit(1)
|
||
PY
|