fix(deploy): Docker 感知 MySQL 备份与 nx cron 安装
升级/定时备份通过 docker exec 1Panel MySQL 容器 dump,不再依赖宿主机 mysqldump;nx 菜单新增巡检与备份 crontab 安装。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+28
-85
@@ -1,102 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
# Nexus — MySQL Auto-Backup Script
|
||||
# Usage: Add to crontab for daily 3AM backup
|
||||
# 0 3 * * * /path/to/nexus/deploy/db_backup.sh >> /var/log/nexus_backup.log 2>&1
|
||||
# Nexus — MySQL 定时备份(Docker 1Panel / 宿主机)
|
||||
#
|
||||
# Reads DATABASE_URL from .env for connection parameters.
|
||||
# Retains backups for 30 days, auto-deletes older ones.
|
||||
|
||||
# 0 3 * * * NEXUS_ROOT=/opt/nexus /opt/nexus/deploy/db_backup.sh >> /var/log/nexus_backup.log 2>&1
|
||||
#
|
||||
# 亦可通过: sudo bash deploy/install_ops_cron.sh
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
ENV_FILE="$PROJECT_DIR/.env"
|
||||
BACKUP_DIR="$PROJECT_DIR/backups"
|
||||
RETENTION_DAYS=30
|
||||
NEXUS_ROOT="${NEXUS_ROOT:-$(dirname "$SCRIPT_DIR")}"
|
||||
BACKUP_DIR="${NEXUS_BACKUP_DIR:-/var/backups/nexus}"
|
||||
RETENTION_DAYS="${NEXUS_BACKUP_RETENTION_DAYS:-30}"
|
||||
DUMP_SH="${SCRIPT_DIR}/mysql_dump_to_file.sh"
|
||||
|
||||
# ── Load .env ──
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
echo "[$(date)] ERROR: .env not found at $ENV_FILE"
|
||||
if [[ ! -x "$DUMP_SH" ]]; then
|
||||
echo "[$(date)] ERROR: missing $DUMP_SH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Parse DATABASE_URL from .env
|
||||
# Format: mysql+asyncmy://user:password@host:port/dbname
|
||||
DB_URL=$(grep -E '^NEXUS_DATABASE_URL=' "$ENV_FILE" | head -1 | cut -d= -f2- | tr -d '"' | tr -d "'")
|
||||
if [ -z "$DB_URL" ]; then
|
||||
DB_URL=$(grep -E '^DATABASE_URL=' "$ENV_FILE" | head -1 | cut -d= -f2- | tr -d '"' | tr -d "'")
|
||||
fi
|
||||
|
||||
if [ -z "$DB_URL" ]; then
|
||||
echo "[$(date)] ERROR: DATABASE_URL not found in .env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Parse mysql URL: mysql+asyncmy://user:pass@host:port/dbname → user pass host port dbname
|
||||
# Strip scheme
|
||||
CONN="${DB_URL#*://}"
|
||||
# Extract user:pass
|
||||
USERPASS="${CONN%%@*}"
|
||||
DB_USER="${USERPASS%%:*}"
|
||||
DB_PASS="${USERPASS#*:}"
|
||||
# Extract host:port/dbname
|
||||
HOSTDB="${CONN#*@}"
|
||||
DB_HOST="${HOSTDB%%:*}"
|
||||
PORTDB="${HOSTDB#*:}"
|
||||
DB_PORT="${PORTDB%%/*}"
|
||||
DB_NAME="${PORTDB#*/}"
|
||||
# Remove query params
|
||||
DB_NAME="${DB_NAME%%\?*}"
|
||||
|
||||
if [ -z "$DB_NAME" ] || [ -z "$DB_HOST" ] || [ -z "$DB_USER" ]; then
|
||||
echo "[$(date)] ERROR: Failed to parse DATABASE_URL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Create backup directory ──
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
|
||||
RAW_FILE="${BACKUP_DIR}/nexus_daily_${TIMESTAMP}.sql"
|
||||
BACKUP_FILE="${RAW_FILE}.gz"
|
||||
|
||||
# ── Backup filename ──
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="$BACKUP_DIR/nexus_${DB_NAME}_${TIMESTAMP}.sql.gz"
|
||||
|
||||
# ── Run mysqldump ──
|
||||
echo "[$(date)] Starting backup: $DB_NAME @ $DB_HOST"
|
||||
|
||||
if [ -n "$DB_PASS" ]; then
|
||||
MYSQL_PWD="$DB_PASS" mysqldump \
|
||||
-h "$DB_HOST" \
|
||||
-P "${DB_PORT:-3306}" \
|
||||
-u "$DB_USER" \
|
||||
--single-transaction \
|
||||
--routines \
|
||||
--triggers \
|
||||
--set-gtid-purged=OFF \
|
||||
"$DB_NAME" | gzip > "$BACKUP_FILE"
|
||||
else
|
||||
mysqldump \
|
||||
-h "$DB_HOST" \
|
||||
-P "${DB_PORT:-3306}" \
|
||||
-u "$DB_USER" \
|
||||
--single-transaction \
|
||||
--routines \
|
||||
--triggers \
|
||||
--set-gtid-purged=OFF \
|
||||
"$DB_NAME" | gzip > "$BACKUP_FILE"
|
||||
fi
|
||||
|
||||
# ── Verify backup ──
|
||||
if [ -f "$BACKUP_FILE" ]; then
|
||||
SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
|
||||
echo "[$(date)] Backup completed: $BACKUP_FILE ($SIZE)"
|
||||
else
|
||||
echo "[$(date)] ERROR: Backup file not created!"
|
||||
echo "[$(date)] Starting backup → $BACKUP_FILE"
|
||||
if ! NEXUS_ROOT="$NEXUS_ROOT" bash "$DUMP_SH" --output "$RAW_FILE" --root "$NEXUS_ROOT"; then
|
||||
echo "[$(date)] ERROR: mysqldump failed"
|
||||
rm -f "$RAW_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Cleanup old backups (retain 30 days) ──
|
||||
DELETED=$(find "$BACKUP_DIR" -name "nexus_*.sql.gz" -mtime +${RETENTION_DAYS} -delete -print | wc -l)
|
||||
if [ "$DELETED" -gt 0 ]; then
|
||||
gzip -f "$RAW_FILE"
|
||||
if [[ ! -f "$BACKUP_FILE" ]]; then
|
||||
echo "[$(date)] ERROR: backup file not created"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SIZE="$(du -h "$BACKUP_FILE" | cut -f1)"
|
||||
echo "[$(date)] Backup completed: $BACKUP_FILE ($SIZE)"
|
||||
|
||||
DELETED="$(find "$BACKUP_DIR" -name 'nexus_*.sql.gz' -mtime +"${RETENTION_DAYS}" -delete -print 2>/dev/null | wc -l)"
|
||||
if [[ "$DELETED" -gt 0 ]]; then
|
||||
echo "[$(date)] Cleaned up $DELETED old backup(s) (>${RETENTION_DAYS} days)"
|
||||
fi
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env bash
|
||||
# Nexus — 安装/更新 host crontab:health_monitor + db_backup
|
||||
#
|
||||
# sudo bash deploy/install_ops_cron.sh
|
||||
# NEXUS_ROOT=/opt/nexus sudo bash deploy/install_ops_cron.sh
|
||||
set -euo pipefail
|
||||
|
||||
NEXUS_ROOT="${NEXUS_ROOT:-/opt/nexus}"
|
||||
DEPLOY_DIR="${NEXUS_ROOT}/deploy"
|
||||
HEALTH_MARKER="nexus-health-monitor"
|
||||
BACKUP_MARKER="nexus-mysql-backup"
|
||||
HEALTH_LOG="${NEXUS_HEALTH_LOG:-/var/log/nexus_health.log}"
|
||||
BACKUP_LOG="${NEXUS_BACKUP_LOG:-/var/log/nexus_backup.log}"
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
||||
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
error "请使用 root 运行: sudo bash $0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for f in "${DEPLOY_DIR}/health_monitor.sh" "${DEPLOY_DIR}/db_backup.sh"; do
|
||||
if [[ ! -f "$f" ]]; then
|
||||
error "缺少脚本: $f"
|
||||
exit 1
|
||||
fi
|
||||
chmod +x "$f" 2>/dev/null || true
|
||||
done
|
||||
|
||||
touch "$HEALTH_LOG" "$BACKUP_LOG" 2>/dev/null || true
|
||||
chmod 644 "$HEALTH_LOG" "$BACKUP_LOG" 2>/dev/null || true
|
||||
|
||||
HEALTH_LINE="* * * * * NEXUS_DEPLOY_DIR=${NEXUS_ROOT} ${DEPLOY_DIR}/health_monitor.sh >> ${HEALTH_LOG} 2>&1 # ${HEALTH_MARKER}"
|
||||
BACKUP_LINE="0 3 * * * NEXUS_ROOT=${NEXUS_ROOT} ${DEPLOY_DIR}/db_backup.sh >> ${BACKUP_LOG} 2>&1 # ${BACKUP_MARKER}"
|
||||
|
||||
existing="$(crontab -l 2>/dev/null || true)"
|
||||
filtered="$(printf '%s\n' "$existing" | grep -v "$HEALTH_MARKER" | grep -v "$BACKUP_MARKER" | grep -v 'deploy/health_monitor.sh' | grep -v 'deploy/db_backup.sh' || true)"
|
||||
{
|
||||
printf '%s\n' "$filtered" | sed '/^[[:space:]]*$/d'
|
||||
echo "$HEALTH_LINE"
|
||||
echo "$BACKUP_LINE"
|
||||
} | crontab -
|
||||
|
||||
info "已写入 crontab:"
|
||||
crontab -l | grep -E "$HEALTH_MARKER|$BACKUP_MARKER" || true
|
||||
info "健康巡检日志: $HEALTH_LOG"
|
||||
info "MySQL 备份日志: $BACKUP_LOG"
|
||||
@@ -0,0 +1,196 @@
|
||||
#!/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
|
||||
+6
-40
@@ -1051,55 +1051,21 @@ show_pending() {
|
||||
}
|
||||
|
||||
backup_mysql() {
|
||||
local root="$1" ts file db_url cname
|
||||
local root="$1" ts file dump_sh
|
||||
if [[ "$NO_BACKUP" == true ]]; then
|
||||
warn "已跳过备份 (--no-backup)"
|
||||
return 0
|
||||
fi
|
||||
if ! command -v mysqldump >/dev/null 2>&1; then
|
||||
warn "未安装 mysqldump,跳过 MySQL 备份"
|
||||
return 0
|
||||
fi
|
||||
cname="$(docker ps --format '{{.Names}}' | grep -E 'nexus-prod-nexus|nexus-nexus-1' | head -1 || true)"
|
||||
db_url=""
|
||||
if [[ -n "$cname" ]]; then
|
||||
db_url="$(docker exec "$cname" grep -E '^NEXUS_DATABASE_URL=' /app/.env 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"' | tr -d "'" || true)"
|
||||
fi
|
||||
if [[ -z "$db_url" ]]; then
|
||||
warn "未找到 NEXUS_DATABASE_URL(外置 MySQL 需先完成安装向导),跳过备份"
|
||||
dump_sh="${root}/deploy/mysql_dump_to_file.sh"
|
||||
if [[ ! -x "$dump_sh" ]]; then
|
||||
warn "缺少 $dump_sh,跳过 MySQL 备份"
|
||||
return 0
|
||||
fi
|
||||
mkdir -p "$NEXUS_BACKUP_DIR"
|
||||
ts="$(date +%Y%m%d-%H%M%S)"
|
||||
file="${NEXUS_BACKUP_DIR}/nexus-before-upgrade-${ts}.sql"
|
||||
step "备份外置 MySQL → $file"
|
||||
if ! python3 - "$db_url" "$file" <<'PY'
|
||||
import sys
|
||||
import urllib.parse
|
||||
from subprocess import run
|
||||
|
||||
url = sys.argv[1].replace("mysql+aiomysql://", "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}
|
||||
cmd = [
|
||||
"mysqldump", "-h", host, "-P", port, "-u", user,
|
||||
"--single-transaction", "--routines", "--triggers", "--set-gtid-purged=OFF", db,
|
||||
]
|
||||
r = 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")
|
||||
sys.exit(r.returncode)
|
||||
open(out, "w", encoding="utf-8").write(r.stdout)
|
||||
PY
|
||||
then
|
||||
step "备份 MySQL → $file"
|
||||
if ! NEXUS_ROOT="$root" bash "$dump_sh" --output "$file" --root "$root"; then
|
||||
if [[ "$REQUIRE_BACKUP" == true ]]; then
|
||||
error "mysqldump 失败,已中止(--require-backup)"
|
||||
rm -f "$file"
|
||||
|
||||
@@ -220,6 +220,10 @@ ops_backup_now() {
|
||||
backup_mysql "$NEXUS_ROOT"
|
||||
}
|
||||
|
||||
ops_install_cron() {
|
||||
bash "${NX_DIR}/install_ops_cron.sh"
|
||||
}
|
||||
|
||||
ops_update() {
|
||||
local args=()
|
||||
PRUNE_IMAGES=false
|
||||
@@ -265,6 +269,7 @@ menu_main() {
|
||||
[7] 启动 / 停止 Compose 栈
|
||||
[8] 重建镜像并启动(--build nexus)
|
||||
[9] 备份 MySQL
|
||||
[e] 安装巡检/备份 cron(health_monitor + db_backup)
|
||||
|
||||
── 其它 ──
|
||||
[a] 检查 Git 是否有更新
|
||||
@@ -338,6 +343,11 @@ EOF
|
||||
ops_backup_now
|
||||
pause_enter
|
||||
;;
|
||||
e|E)
|
||||
require_root
|
||||
ops_install_cron
|
||||
pause_enter
|
||||
;;
|
||||
a|A)
|
||||
require_root
|
||||
cmd_check
|
||||
@@ -399,6 +409,10 @@ nx_main() {
|
||||
require_root
|
||||
is_stack_installed && ops_status || check_ports_preflight "$NEXUS_ROOT"
|
||||
;;
|
||||
cron)
|
||||
require_root
|
||||
ops_install_cron
|
||||
;;
|
||||
-h|--help)
|
||||
cat <<EOF
|
||||
用法: nx [子命令]
|
||||
@@ -408,6 +422,7 @@ nx_main() {
|
||||
install-fresh 全新安装(/app/install.html)
|
||||
install-auto 非交互安装(默认 2c8g)
|
||||
health 健康检查
|
||||
cron 安装 health_monitor + db_backup crontab
|
||||
|
||||
god / ops 已合并进主菜单(兼容别名)
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# nx 上帝菜单巡检第四轮 — Docker MySQL 备份 + 运维 cron
|
||||
|
||||
| 项 | 内容 |
|
||||
|----|------|
|
||||
| 日期 | 2026-06-06 |
|
||||
| 动机 | 升级时 `backup_mysql` 因宿主机无 `mysqldump` 且 URL host 为 1Panel 容器名而静默跳过;`db_backup.sh` 仍读 Supervisor 时代 `.env` |
|
||||
|
||||
## 变更摘要
|
||||
|
||||
1. **mysql_dump_to_file.sh**(新):统一 mysqldump 策略 — `docker exec` MySQL 容器 → 宿主机客户端 → `docker run` on `1panel-network`。
|
||||
2. **backup_mysql**:改调共享脚本,不再要求宿主机预装 `mysqldump`。
|
||||
3. **db_backup.sh**:Docker 生产路径,从 Nexus 容器/volume 解析 `NEXUS_DATABASE_URL`,默认 `/var/backups/nexus`。
|
||||
4. **install_ops_cron.sh**(新):幂等写入 `health_monitor`(每分钟)与 `db_backup`(每日 03:00)crontab。
|
||||
5. **nx**:菜单 `[e]` / 子命令 `nx cron` 安装上述 cron。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `deploy/mysql_dump_to_file.sh`
|
||||
- `deploy/install_ops_cron.sh`
|
||||
- `deploy/db_backup.sh`
|
||||
- `deploy/nexus-1panel.sh`
|
||||
- `deploy/nx`
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
- 无需 DB 迁移;仅需 `git pull` 更新 host 脚本。
|
||||
- 可选:`sudo nx cron` 或菜单 `[e]` 安装 crontab。
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
bash -n deploy/mysql_dump_to_file.sh deploy/install_ops_cron.sh deploy/db_backup.sh
|
||||
sudo bash deploy/mysql_dump_to_file.sh --output /tmp/nexus-test.sql --root /opt/nexus
|
||||
ls -lh /var/backups/nexus/
|
||||
sudo nx cron && crontab -l | grep nexus
|
||||
```
|
||||
Reference in New Issue
Block a user