feat(docker): remove bundled MySQL from production Compose stack

Production installs now run only the Nexus container; MySQL and Redis are self-hosted on the machine or 1Panel, with install wizard defaults pointing at host.docker.internal.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Nexus Deploy
2026-06-06 03:27:57 +08:00
parent e42dc77127
commit f37ebf8621
17 changed files with 245 additions and 108 deletions
+1 -1
View File
@@ -40,7 +40,7 @@ bash /tmp/quick-install.sh --profile 2c8g
安装完成后在浏览器打开安装向导,完成 `.env` / MySQL / 管理员初始化。
**每台新服务器的密钥自动不同**:安装脚本生成 `MYSQL_*` `NEXUS_SECRET_KEY` / `NEXUS_API_KEY` / `NEXUS_ENCRYPTION_KEY` 写入 `docker/.env.prod`,备份在 `/root/.nexus-install-secrets-*.env`。勿把生产密钥放进 `nexus-1panel.secrets.sh`(仅保留 `NEXUS_GITEA_TOKEN`)。
**每台新服务器的 NEXUS 密钥自动不同**:安装脚本生成 `NEXUS_SECRET_KEY` / `NEXUS_API_KEY` / `NEXUS_ENCRYPTION_KEY` 写入 `docker/.env.prod`,备份在 `/root/.nexus-install-secrets-*.env`MySQL/Redis 请自行安装;勿把生产密钥放进 `nexus-1panel.secrets.sh`(仅保留 `NEXUS_GITEA_TOKEN`)。
---
+14 -6
View File
@@ -28,17 +28,24 @@ curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/quick-in
安装完成后浏览器打开 `https://api.synaglobal.vip/app/install.html` 完成向导。
## Redis(自行安装,脚本不附带)
## MySQL / Redis(自行安装,脚本不附带)
Docker 栈仅含 **MySQL + Nexus**Redis 请在宿主机或 1Panel 应用商店单独安装,监听 `127.0.0.1:6379`(或改 `docker/.env.prod``NEXUS_REDIS_URL`
Docker 栈**仅含 Nexus 容器**MySQL Redis 请在宿主机或 1Panel 应用商店单独安装。
安装向导步骤 2:**必须先连通 Redis** 才能进入步骤 3。步骤 3 Redis 主机填 **`host.docker.internal`**,端口 `6379`
| 服务 | 宿主机监听 | 安装向导步骤 3 填写 |
|------|-----------|---------------------|
| MySQL | `127.0.0.1:3306` | 主机 `host.docker.internal`,端口 `3306` |
| Redis | `127.0.0.1:6379` | 主机 `host.docker.internal`,端口 `6379` |
卸载旧 Compose Redis 容器:
安装向导步骤 2:**必须先连通 Redis** 才能进入步骤 3。步骤 3 请填写**自建库**的用户名与密码(脚本不再生成 `MYSQL_PASSWORD`)。
卸载旧 Compose 内置容器:
```bash
bash /opt/nexus/deploy/uninstall-mysql-compose.sh
bash /opt/nexus/deploy/uninstall-mysql-compose.sh --purge-volume
bash /opt/nexus/deploy/uninstall-redis-compose.sh
bash /opt/nexus/deploy/uninstall-redis-compose.sh --purge-volume # 连数据卷一并删除
bash /opt/nexus/deploy/uninstall-redis-compose.sh --purge-volume
```
## 1Panel 反代(必须在面板 UI 操作)
@@ -83,9 +90,10 @@ nx god # 上帝菜单(重启 Python / 升级 / 日志)
全新安装(`install-nexus-fresh.sh` / `--fresh`)会调用 `scripts/generate_nexus_secrets.py` 自动生成:
- `MYSQL_ROOT_PASSWORD` / `MYSQL_PASSWORD`
- `NEXUS_SECRET_KEY` / `NEXUS_API_KEY` / `NEXUS_ENCRYPTION_KEY`(与安装向导算法一致)
MySQL 密码由你在自建库时设定,安装向导步骤 3 填写。
写入 `docker/.env.prod`,备份到 `/root/.nexus-install-secrets-*.env`chmod 600)。
| 场景 | 命令 |
+2 -1
View File
@@ -124,7 +124,8 @@ install_root_commands() {
"${NEXUS_ROOT}/deploy/install-nexus-fresh.sh" \
"${NEXUS_ROOT}/deploy/nexus-1panel.sh" \
"${NEXUS_ROOT}/deploy/sync-install-wizard-to-container.sh" \
"${NEXUS_ROOT}/deploy/uninstall-redis-compose.sh" 2>/dev/null || true
"${NEXUS_ROOT}/deploy/uninstall-redis-compose.sh" \
"${NEXUS_ROOT}/deploy/uninstall-mysql-compose.sh" 2>/dev/null || true
ln -sf "${NEXUS_ROOT}/deploy/nx" /usr/local/bin/nx
ln -sf "${NEXUS_ROOT}/deploy/nexus-1panel.sh" /usr/local/bin/nexus-1panel
ln -sf "${NEXUS_ROOT}/deploy/install-nexus-fresh.sh" /usr/local/bin/nexus-fresh
+42 -12
View File
@@ -449,8 +449,6 @@ write_temp_secrets_env() {
local f
f="$(mktemp /tmp/nexus-secrets-import.XXXXXX.env)"
{
printf 'MYSQL_ROOT_PASSWORD=%s\n' "${MYSQL_ROOT_PASSWORD:-$(rand_pass)}"
printf 'MYSQL_PASSWORD=%s\n' "${MYSQL_PASSWORD:-$(rand_pass)}"
printf 'NEXUS_SECRET_KEY=%s\n' "$NEXUS_SECRET_KEY"
printf 'NEXUS_API_KEY=%s\n' "$NEXUS_API_KEY"
printf 'NEXUS_ENCRYPTION_KEY=%s\n' "$NEXUS_ENCRYPTION_KEY"
@@ -497,7 +495,7 @@ write_env_prod() {
--from-env "$FROM_ENV" \
--backup "$backup"
elif [[ "$FRESH_INSTALL" == true ]]; then
info "全新安装:为本机生成唯一 MYSQL / NEXUS 密钥..."
info "全新安装:为本机生成唯一 NEXUS 密钥MySQL/Redis 请自行安装)..."
python3 "$gen" write-prod \
--template "$example" \
--output "$env_file" \
@@ -542,7 +540,7 @@ write_env_prod() {
compose_up() {
local root="$1"
save_host_profile "$root"
info "档位: ${NEXUS_PROFILE}MySQL/Nexus 内存与连接池已套用;Redis 请自行安装)"
info "档位: ${NEXUS_PROFILE}仅 Nexus 容器;MySQL/Redis 请自行安装)"
info "构建并启动(首次约 1020 分钟)..."
compose_cmd "$root" up -d --build
}
@@ -645,23 +643,55 @@ show_pending() {
}
backup_mysql() {
local root="$1" ts file mysql_root
local root="$1" ts file db_url cname
if [[ "$NO_BACKUP" == true ]]; then
warn "已跳过备份 (--no-backup)"
return 0
fi
mysql_root="$(read_env_val "$root/$ENV_PROD" MYSQL_ROOT_PASSWORD)"
if [[ -z "$mysql_root" ]]; then
warn "无法读取 MYSQL_ROOT_PASSWORD,跳过备份"
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 需先完成安装向导),跳过备份"
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 ! compose_cmd "$root" exec -T mysql \
mysqldump -uroot -p"${mysql_root}" --single-transaction --routines --triggers nexus \
>"$file" 2>/dev/null; then
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
error "mysqldump 失败,已中止"
rm -f "$file"
exit 1
+4 -8
View File
@@ -177,7 +177,7 @@ god_restart_stack() {
}
god_stop_stack() {
warn "将停止 MySQL / Redis / Nexus 全部容器"
warn "将停止 Nexus 容器(MySQL/Redis 为外置服务,不受影响)"
read -r -p "确认? [y/N]: " c
[[ "$c" == "y" || "$c" == "Y" ]] || return 0
compose_cmd "$NEXUS_ROOT" stop
@@ -243,7 +243,7 @@ menu_god() {
cat <<EOF
── 服务 ──
[1] 重启 NexusPython 应用)
[2] 重启全部容器(mysql + redis + nexus
[2] 重启 Compose 栈(仅 Nexus 容器
[3] 启动 Compose 栈
[4] 停止 Compose 栈
[5] 重建 Nexus 镜像并启动(up -d --build nexus
@@ -251,7 +251,7 @@ menu_god() {
── 观测 ──
[6] 状态 + 健康检查
[7] 查看 Nexus 日志(跟踪)
[8] 查看 MySQL / Redis 日志
[8] 查看 Nexus 日志(备用入口)
[9] 端口 / 防火墙检查
── 发布 ──
@@ -278,11 +278,7 @@ EOF
;;
6) god_status; pause_enter ;;
7) god_logs nexus ;;
8)
echo "1) mysql 2) redis"
read -r -p "选: " s
[[ "$s" == "1" ]] && god_logs mysql || god_logs redis
;;
8) god_logs nexus ;;
9)
check_ports_preflight "$NEXUS_ROOT"
check_ports_post "$NEXUS_ROOT"
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env bash
# =============================================================================
# 卸载 Nexus 旧版 Compose 内置 MySQL 容器
#
# bash deploy/uninstall-mysql-compose.sh
# bash deploy/uninstall-mysql-compose.sh --purge-volume
# =============================================================================
set -euo pipefail
NEXUS_ROOT="${NEXUS_ROOT:-/opt/nexus}"
COMPOSE_FILE="${COMPOSE_FILE:-docker/docker-compose.prod.yml}"
PURGE_VOLUME=false
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; }
usage() {
cat <<EOF
卸载 Nexus Compose 遗留 MySQL 容器
bash deploy/uninstall-mysql-compose.sh [--purge-volume]
说明:
- 不卸载宿主机/1Panel 自建的 MySQL
- 卸载后请自建库 nexus + 用户,安装向导步骤 3 填写连接信息
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) usage; exit 0 ;;
--purge-volume) PURGE_VOLUME=true; shift ;;
*) error "未知参数: $1"; usage; exit 1 ;;
esac
done
[[ "$(id -u)" -eq 0 ]] || { error "请使用 root 执行"; exit 1; }
removed=0
stop_rm_container() {
local name="$1"
if docker ps -a --format '{{.Names}}' | grep -qx "$name"; then
info "停止并删除容器: $name"
docker stop "$name" >/dev/null 2>&1 || true
docker rm "$name" >/dev/null 2>&1 || true
removed=$((removed + 1))
fi
}
for pattern in nexus-prod-mysql-1 nexus-prod-mysql nexus-mysql-1; do
stop_rm_container "$pattern"
done
while IFS= read -r c; do
[[ -z "$c" ]] && continue
stop_rm_container "$c"
done < <(docker ps -a --format '{{.Names}}' | grep -iE 'nexus.*mysql|mysql.*nexus' || true)
if [[ -f "${NEXUS_ROOT}/${COMPOSE_FILE}" ]] && grep -q '^ mysql:' "${NEXUS_ROOT}/${COMPOSE_FILE}" 2>/dev/null; then
warn "compose 仍含 mysql 服务,尝试 compose rm..."
local_env=()
[[ -f "${NEXUS_ROOT}/docker/.env.prod" ]] && local_env+=(--env-file "${NEXUS_ROOT}/docker/.env.prod")
prof="${NEXUS_ROOT}/docker/.host-profile"
if [[ -f "$prof" ]]; then
p="$(tr -d '\r\n' <"$prof")"
[[ -f "${NEXUS_ROOT}/docker/profiles/${p}.env" ]] && local_env+=(--env-file "${NEXUS_ROOT}/docker/profiles/${p}.env")
fi
(cd "$NEXUS_ROOT" && docker compose -f "$COMPOSE_FILE" "${local_env[@]}" stop mysql 2>/dev/null || true)
(cd "$NEXUS_ROOT" && docker compose -f "$COMPOSE_FILE" "${local_env[@]}" rm -f mysql 2>/dev/null || true)
removed=$((removed + 1))
fi
if [[ "$PURGE_VOLUME" == true ]]; then
while IFS= read -r vol; do
[[ -z "$vol" ]] && continue
info "删除数据卷: $vol"
docker volume rm "$vol" 2>/dev/null || warn "无法删除卷 $vol"
done < <(docker volume ls -q | grep -iE 'nexus.*mysql|mysql.*nexus' || true)
fi
if [[ "$removed" -gt 0 ]]; then
info "Compose MySQL 容器已卸载。"
else
info "未发现 Nexus Compose MySQL 容器。"
fi
echo ""
info "下一步:自建 MySQL 数据库与用户,安装向导步骤 3 使用 host.docker.internal:3306"
+4 -8
View File
@@ -1,9 +1,6 @@
# Production Compose env — copy to docker/.env.prod (never commit)
# Fresh install: bash deploy/install-nexus-fresh.sh (auto-generates unique secrets per host)
# Migration: bash deploy/nexus-1panel.sh install --from-env /path/to/old/.env
MYSQL_ROOT_PASSWORD=change_me_root_strong
MYSQL_PASSWORD=change_me_nexus_strong
# Fresh install: bash deploy/install-nexus-fresh.sh (auto-generates NEXUS keys per host)
# MySQL / Redis 不在 Docker 栈 — 请在宿主机或 1Panel 自建,安装向导步骤 3 填写
# Auto-generated on fresh install (unique per server). Do NOT copy between hosts.
NEXUS_SECRET_KEY=
@@ -17,12 +14,11 @@ NEXUS_INSTALL_WIZARD_PENDING=0
NEXUS_CORS_ORIGINS=https://api.synaglobal.vip
NEXUS_API_BASE_URL=https://api.synaglobal.vip
# 2 vCPU / 8 GiB host (matches docker/mysql-prod-2c8g.cnf + compose limits)
# Nexus container resource profile (docker/profiles/*.env)
NEXUS_DB_POOL_SIZE=30
NEXUS_DB_MAX_OVERFLOW=20
# Redis 不在 Docker 栈内 — 请在宿主机或 1Panel 应用商店自行安装
# Nexus 容器通过 host.docker.internal 访问宿主机 Redis(见 docker-compose.prod.yml
# 外置 Redis(宿主机/1Panel),Nexus 容器经 host.docker.internal 访问
NEXUS_REDIS_URL=redis://host.docker.internal:6379/0
# Optional
+8 -7
View File
@@ -41,15 +41,16 @@ docker compose build --no-cache nexus
## 服务
| 服务 | 端口 | 说明 |
|------|------|------|
| nexus | 8600 | FastAPI + 静态 `/app/` |
| mysql | 3306 | 数据库 `nexus`Compose 内) |
| redis | — | **不在 Compose 内**;宿主机/1Panel 自建,默认 `host.docker.internal:6379` |
| 环境 | Compose 服务 | 说明 |
|------|-------------|------|
| 本地开发 `docker-compose.yml` | nexus + mysql + redis | 一键本地联调 |
| 生产 `docker-compose.prod.yml` | **仅 nexus** | MySQL/Redis 宿主机或 1Panel 自建 |
生产默认 `NEXUS_REDIS_URL=redis://host.docker.internal:6379/0`MySQL 由安装向导写入 `NEXUS_DATABASE_URL`
## 生产注意
- 修改 `MYSQL_*``NEXUS_*` 为强密码,勿使用 `generate_env.py` 默认值上生产
- 修改 `NEXUS_*` 为强密码;MySQL 使用自建库强密码
- 建议前置 Nginx/TLS,仅暴露 443。
- 多实例部署需共享 MySQL、Redis;后台任务由 Redis primary lock 保证单实例执行。
@@ -62,7 +63,7 @@ docker compose -f docker/docker-compose.prod.yml --env-file docker/.env.prod up
- 镜像:`Dockerfile.prod`(含 `npm ci` + `vite build` + **`web/app/install.html` 安装向导**
- 仅绑定 `127.0.0.1:8600`OpenResty 反代见 `deploy/1panel/openresty-nexus.conf.example`
- **2C8G 调优**`docker/mysql-prod-2c8g.cnf`buffer pool 1G、`max_connections=120`+ 容器 `mem_limit` + `NEXUS_DB_POOL_SIZE=30`
- **2C8G 调优**`docker/profiles/2c8g.env``NEXUS_MEM_LIMIT` + `NEXUS_DB_POOL_SIZE=30`);外置 MySQL 可参考 `docker/mysql-prod-2c8g.cnf` 手工调优
- 完整迁移步骤:`docs/design/plans/2026-06-04-1panel-docker-production.md`
- 一键安装:`bash deploy/install-1panel-docker.sh`
- 一键升级:`bash deploy/upgrade-1panel-docker.sh``--check` 仅查更新)
+1 -32
View File
@@ -1,9 +1,8 @@
# Nexus 6.0 — production Compose (1Panel / bare Docker host)
# Resource profile: docker/profiles/{1c4g,2c8g,4c16g}.env — see deploy/nexus-1panel.sh
# MySQL / Redis 不在 Compose 内 — 请在宿主机或 1Panel 自行安装
#
# Usage:
# bash deploy/nexus-1panel.sh install --profile 2c8g
# # or manually:
# docker compose -f docker/docker-compose.prod.yml \
# --env-file docker/.env.prod --env-file docker/profiles/2c8g.env up -d --build
#
@@ -12,50 +11,21 @@
name: nexus-prod
services:
mysql:
image: mysql:8.4
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?set MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: nexus
MYSQL_USER: nexus
MYSQL_PASSWORD: ${MYSQL_PASSWORD:?set MYSQL_PASSWORD}
volumes:
- mysql-data:/var/lib/mysql
- ./${MYSQL_CNF_FILE:-mysql-prod-2c8g.cnf}:/etc/mysql/conf.d/99-nexus-prod.cnf:ro
mem_limit: ${MYSQL_MEM_LIMIT:-2g}
cpus: ${MYSQL_CPUS:-1.5}
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p\"$$MYSQL_ROOT_PASSWORD\" || exit 1"]
interval: 10s
timeout: 5s
retries: 8
start_period: 40s
networks:
- nexus-internal
nexus:
build:
context: ..
dockerfile: Dockerfile.prod
restart: unless-stopped
depends_on:
mysql:
condition: service_healthy
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- "127.0.0.1:${NEXUS_PUBLISH_PORT:-8600}:8600"
environment:
MYSQL_HOST: mysql
MYSQL_PORT: "3306"
NEXUS_PERSIST_DIR: /var/lib/nexus
NEXUS_DOCKER_WRITE_ENV: "1"
NEXUS_HOST: "0.0.0.0"
NEXUS_PORT: "8600"
NEXUS_DEPLOY_PATH: /app
NEXUS_DATABASE_URL: mysql+aiomysql://nexus:${MYSQL_PASSWORD}@mysql:3306/nexus
# Redis 不在 Compose 内 — 请在宿主机/1Panel 自建,默认连宿主机 6379
NEXUS_REDIS_URL: ${NEXUS_REDIS_URL:-redis://host.docker.internal:6379/0}
NEXUS_CORS_ORIGINS: ${NEXUS_CORS_ORIGINS:?set NEXUS_CORS_ORIGINS}
NEXUS_SECRET_KEY: ${NEXUS_SECRET_KEY:?set NEXUS_SECRET_KEY}
@@ -84,6 +54,5 @@ networks:
driver: bridge
volumes:
mysql-data:
nexus-state:
nexus-web-data:
+6 -5
View File
@@ -86,12 +86,13 @@ persist_env_on_exit() {
trap persist_env_on_exit EXIT INT TERM
MYSQL_HOST="${MYSQL_HOST:-mysql}"
MYSQL_PORT="${MYSQL_PORT:-3306}"
# MySQL / Redis 均由用户自行安装在宿主机;安装向导阶段不阻塞启动
if [ "${NEXUS_INSTALL_WIZARD_PENDING:-0}" != "1" ] && [ "${NEXUS_SKIP_MYSQL_WAIT:-0}" != "1" ]; then
MYSQL_HOST="${MYSQL_HOST:-host.docker.internal}"
MYSQL_PORT="${MYSQL_PORT:-3306}"
wait_tcp_optional "${MYSQL_HOST}" "${MYSQL_PORT}" 30
fi
wait_tcp "${MYSQL_HOST}" "${MYSQL_PORT}"
# Redis 由用户自行安装在宿主机/1Panel;安装向导阶段不阻塞启动
if [ "${NEXUS_INSTALL_WIZARD_PENDING:-0}" != "1" ] && [ "${NEXUS_SKIP_REDIS_WAIT:-0}" != "1" ]; then
REDIS_HOST="${REDIS_HOST:-host.docker.internal}"
REDIS_PORT="${REDIS_PORT:-6379}"
+1 -4
View File
@@ -1,7 +1,4 @@
# Host profile: 1 vCPU / 4 GiB (test / small VPS)
MYSQL_CNF_FILE=mysql-prod-1c4g.cnf
MYSQL_MEM_LIMIT=1g
MYSQL_CPUS=1
# Host profile: 1 vCPU / 4 GiB
NEXUS_MEM_LIMIT=1g
NEXUS_CPUS=1
NEXUS_DB_POOL_SIZE=15
-3
View File
@@ -1,7 +1,4 @@
# Host profile: 2 vCPU / 8 GiB — default production
MYSQL_CNF_FILE=mysql-prod-2c8g.cnf
MYSQL_MEM_LIMIT=2g
MYSQL_CPUS=1.5
NEXUS_MEM_LIMIT=2g
NEXUS_CPUS=1.5
NEXUS_DB_POOL_SIZE=30
+2 -5
View File
@@ -1,8 +1,5 @@
# Host profile: 4 vCPU / 16 GiB
MYSQL_CNF_FILE=mysql-prod-4c16g.cnf
MYSQL_MEM_LIMIT=6g
MYSQL_CPUS=3
NEXUS_MEM_LIMIT=4g
NEXUS_CPUS=3
NEXUS_DB_POOL_SIZE=60
NEXUS_DB_MAX_OVERFLOW=40
NEXUS_DB_POOL_SIZE=50
NEXUS_DB_MAX_OVERFLOW=30
@@ -0,0 +1,42 @@
# 2026-06-04 — 生产 Compose 移除外置 MySQL
## 摘要
生产 Docker 栈从「MySQL + Nexus」改为**仅 Nexus 容器**MySQL 与 Redis 均由用户在宿主机或 1Panel 自行安装,安装向导步骤 3 填写连接信息。
## 动机
- 用户希望数据库自行创建与管理,不随安装脚本附带 MySQL 容器与自动密码。
- 与已移除外置 Redis Compose 服务的设计一致,降低 bind mount / 卷冲突风险。
## 涉及文件
| 文件 | 变更 |
|------|------|
| `docker/docker-compose.prod.yml` | 移除 `mysql` 服务与数据卷 |
| `docker/.env.prod.example` | 移除 `MYSQL_*` |
| `docker/profiles/*.env` | 移除 `MYSQL_CNF_FILE` 等 |
| `docker/entrypoint.sh` | MySQL 等待改为 `host.docker.internal`(可选) |
| `scripts/generate_nexus_secrets.py` | 新机仅生成 `NEXUS_*` |
| `deploy/nexus-1panel.sh` | 备份改读外置 `NEXUS_DATABASE_URL` |
| `deploy/uninstall-mysql-compose.sh` | 卸载遗留 Compose MySQL 容器 |
| `deploy/README-1panel.md` / `docker/README.md` | 文档更新 |
| `web/app/install.html` | MySQL 自建说明 |
| `server/api/install.py` | Docker 默认 `db_host=host.docker.internal` |
## 迁移 / 重启
```bash
cd /opt/nexus && git pull
bash deploy/uninstall-mysql-compose.sh # 可选:删旧 Compose MySQL
bash deploy/nexus-1panel.sh upgrade
bash deploy/sync-install-wizard-to-container.sh
```
自建 MySQL 后完成 `/app/install.html` 步骤 3(主机 `host.docker.internal`)。
## 验证
- `docker compose -f docker/docker-compose.prod.yml config` 仅含 `nexus` 服务
- `python3 scripts/generate_nexus_secrets.py write-prod --fresh` 输出无 `MYSQL_*`
- 安装向导步骤 3 可连接外置 MySQL 并完成 `init-db`
+18 -13
View File
@@ -1,7 +1,10 @@
#!/usr/bin/env python3
"""Generate per-host Nexus secrets (install / Docker).
Each fresh install must get unique MYSQL_* and NEXUS_* keys. Matches install wizard:
Fresh install generates unique NEXUS_* keys only (MySQL is self-hosted).
Migration (--from-env) may still import legacy MYSQL_* from old .env.prod.
Matches install wizard:
SECRET_KEY = token_hex(32)
API_KEY = token_hex(16)
ENCRYPTION_KEY = urlsafe_b64encode(token_bytes(32))
@@ -25,14 +28,16 @@ if str(ROOT) not in sys.path:
from server.utils.text_io import read_utf8_lf, write_utf8_lf # noqa: E402
def generate_nexus_secrets() -> dict[str, str]:
return {
"MYSQL_ROOT_PASSWORD": secrets.token_urlsafe(24),
"MYSQL_PASSWORD": secrets.token_urlsafe(24),
def generate_nexus_secrets(*, include_mysql: bool = False) -> dict[str, str]:
out: dict[str, str] = {
"NEXUS_SECRET_KEY": secrets.token_hex(32),
"NEXUS_API_KEY": secrets.token_hex(16),
"NEXUS_ENCRYPTION_KEY": base64.urlsafe_b64encode(secrets.token_bytes(32)).decode(),
}
if include_mysql:
out["MYSQL_ROOT_PASSWORD"] = secrets.token_urlsafe(24)
out["MYSQL_PASSWORD"] = secrets.token_urlsafe(24)
return out
def parse_env_file(path: Path) -> dict[str, str]:
@@ -51,13 +56,13 @@ def parse_env_file(path: Path) -> dict[str, str]:
def import_secrets_from_env(path: Path) -> dict[str, str]:
"""Import NEXUS + MySQL passwords from an existing .env (migration)."""
raw = parse_env_file(path)
out = generate_nexus_secrets()
out = generate_nexus_secrets(include_mysql=False)
for key in (
"MYSQL_ROOT_PASSWORD",
"MYSQL_PASSWORD",
"NEXUS_SECRET_KEY",
"NEXUS_API_KEY",
"NEXUS_ENCRYPTION_KEY",
"MYSQL_ROOT_PASSWORD",
"MYSQL_PASSWORD",
):
val = raw.get(key, "").strip()
if val:
@@ -155,13 +160,13 @@ def write_secrets_backup(path: Path, secrets_map: dict[str, str], *, domain: str
"",
]
for key in (
"MYSQL_ROOT_PASSWORD",
"MYSQL_PASSWORD",
"NEXUS_SECRET_KEY",
"NEXUS_API_KEY",
"NEXUS_ENCRYPTION_KEY",
"MYSQL_ROOT_PASSWORD",
"MYSQL_PASSWORD",
):
if key in secrets_map:
if secrets_map.get(key):
lines.append(f"{key}={secrets_map[key]}")
write_utf8_lf(path, "\n".join(lines) + "\n")
path.chmod(0o600)
@@ -212,10 +217,10 @@ def main() -> None:
secrets_map = import_secrets_from_env(args.from_env)
wizard = False
elif args.fresh:
secrets_map = generate_nexus_secrets()
secrets_map = generate_nexus_secrets(include_mysql=False)
wizard = bool(args.wizard_pending)
else:
secrets_map = generate_nexus_secrets()
secrets_map = generate_nexus_secrets(include_mysql=False)
wizard = bool(args.wizard_pending)
write_env_prod(
+3 -3
View File
@@ -114,11 +114,11 @@ def _resolve_redis_probe() -> tuple[str, int, str | None]:
def _docker_wizard_defaults() -> dict[str, str] | None:
"""Prefill install step 3 when running inside docker-compose.prod.yml."""
if os.environ.get("NEXUS_DOCKER_WRITE_ENV") != "1" and not os.environ.get("REDIS_HOST"):
if os.environ.get("NEXUS_DOCKER_WRITE_ENV") != "1":
return None
return {
"db_host": os.environ.get("MYSQL_HOST", "mysql"),
"db_port": os.environ.get("MYSQL_PORT", "3306"),
"db_host": "host.docker.internal",
"db_port": "3306",
"db_name": "nexus",
"db_user": "nexus",
"redis_host": "host.docker.internal",
+1
View File
@@ -155,6 +155,7 @@
<!-- MySQL -->
<div class="border-t-2 border-slate-100 pt-4 mt-4">
<h3 class="font-bold text-slate-700 mb-3">🗄 MySQL 数据库</h3>
<p class="text-xs text-slate-500 mb-3">请先在宿主机或 1Panel 安装 MySQL 并创建库与用户。Docker 部署填主机 <code class="bg-slate-100 px-1 rounded">host.docker.internal</code>,端口 3306。</p>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block font-semibold text-sm text-slate-700 mb-1">主机</label>