diff --git a/README.md b/README.md index 62ad6bf0..5d5c1b66 100644 --- a/README.md +++ b/README.md @@ -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`)。 --- diff --git a/deploy/README-1panel.md b/deploy/README-1panel.md index bdf8731d..1b252333 100644 --- a/deploy/README-1panel.md +++ b/deploy/README-1panel.md @@ -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)。 | 场景 | 命令 | diff --git a/deploy/install-nexus-fresh.sh b/deploy/install-nexus-fresh.sh index 4150114e..a71d594f 100644 --- a/deploy/install-nexus-fresh.sh +++ b/deploy/install-nexus-fresh.sh @@ -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 diff --git a/deploy/nexus-1panel.sh b/deploy/nexus-1panel.sh index c2ece4da..3723ea9b 100644 --- a/deploy/nexus-1panel.sh +++ b/deploy/nexus-1panel.sh @@ -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 "构建并启动(首次约 10~20 分钟)..." 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 diff --git a/deploy/nx b/deploy/nx index 40baeb17..cdf489e2 100644 --- a/deploy/nx +++ b/deploy/nx @@ -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 <&2; } + +usage() { + cat </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" diff --git a/docker/.env.prod.example b/docker/.env.prod.example index 57d186c6..e111ef6f 100644 --- a/docker/.env.prod.example +++ b/docker/.env.prod.example @@ -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 diff --git a/docker/README.md b/docker/README.md index d4b00cf7..d8328f84 100644 --- a/docker/README.md +++ b/docker/README.md @@ -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` 仅查更新) diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index 4acecdbf..fd206d70 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -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: diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index b50867c1..60e24357 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -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}" diff --git a/docker/profiles/1c4g.env b/docker/profiles/1c4g.env index 29cb6e13..ee392e0e 100644 --- a/docker/profiles/1c4g.env +++ b/docker/profiles/1c4g.env @@ -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 diff --git a/docker/profiles/2c8g.env b/docker/profiles/2c8g.env index 366587b7..b24e4842 100644 --- a/docker/profiles/2c8g.env +++ b/docker/profiles/2c8g.env @@ -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 diff --git a/docker/profiles/4c16g.env b/docker/profiles/4c16g.env index 1706c44f..6feb0cf7 100644 --- a/docker/profiles/4c16g.env +++ b/docker/profiles/4c16g.env @@ -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 diff --git a/docs/changelog/2026-06-04-external-mysql-compose.md b/docs/changelog/2026-06-04-external-mysql-compose.md new file mode 100644 index 00000000..ba4e754b --- /dev/null +++ b/docs/changelog/2026-06-04-external-mysql-compose.md @@ -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` diff --git a/scripts/generate_nexus_secrets.py b/scripts/generate_nexus_secrets.py index ea6a931e..64ab4fe1 100644 --- a/scripts/generate_nexus_secrets.py +++ b/scripts/generate_nexus_secrets.py @@ -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( diff --git a/server/api/install.py b/server/api/install.py index c788ebb0..41739c75 100644 --- a/server/api/install.py +++ b/server/api/install.py @@ -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", diff --git a/web/app/install.html b/web/app/install.html index 37254b20..59557964 100644 --- a/web/app/install.html +++ b/web/app/install.html @@ -155,6 +155,7 @@

🗄 MySQL 数据库

+

请先在宿主机或 1Panel 安装 MySQL 并创建库与用户。Docker 部署填主机 host.docker.internal,端口 3306。