#!/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