feat(docker): remove bundled Redis; use external host install

Compose stack is MySQL+Nexus only; wizard Redis check is non-blocking
and defaults to host.docker.internal for self-managed Redis.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Nexus Deploy
2026-06-06 02:48:00 +08:00
parent c81fe3183f
commit 6276ea08b7
15 changed files with 156 additions and 55 deletions
+6
View File
@@ -28,6 +28,12 @@ curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/quick-in
安装完成后浏览器打开 `https://api.synaglobal.vip/app/install.html` 完成向导。 安装完成后浏览器打开 `https://api.synaglobal.vip/app/install.html` 完成向导。
## Redis(自行安装,脚本不附带)
Docker 栈仅含 **MySQL + Nexus**。Redis 请在宿主机或 1Panel 应用商店单独安装,监听 `127.0.0.1:6379`(或改 `docker/.env.prod``NEXUS_REDIS_URL`)。
安装向导步骤 3:Docker 部署时 Redis 主机填 **`host.docker.internal`**,端口 `6379`
## 1Panel 反代(必须在面板 UI 操作) ## 1Panel 反代(必须在面板 UI 操作)
**禁止** Agent/脚本直接 `mkdir``tee``/opt/1panel/apps/openresty/` — 会破坏 bind mount。 **禁止** Agent/脚本直接 `mkdir``tee``/opt/1panel/apps/openresty/` — 会破坏 bind mount。
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# Run ON production server (宝塔终端 / ssh root@47.254.123.106)
# Pulls latest main from Gitea and restarts Nexus. Frontend tar must be uploaded separately
# if you did not build on this machine.
#
# Usage (on server):
# cd /www/wwwroot/api.synaglobal.vip
# bash deploy/deploy-on-server.sh
#
# Optional env:
# NEXUS_GITEA_TOKEN — if git pull needs auth
# SKIP_FRONTEND=1 — backend only
set -euo pipefail
DEPLOY_PATH="${NEXUS_DEPLOY_PATH:-/www/wwwroot/api.synaglobal.vip}"
cd "${DEPLOY_PATH}"
echo "═══ Nexus server-side deploy ═══"
echo "Path: ${DEPLOY_PATH}"
if [ -n "${NEXUS_GITEA_TOKEN:-}" ]; then
git remote set-url origin "http://admin:${NEXUS_GITEA_TOKEN}@66.154.115.8:3000/admin/Nexus.git"
fi
echo "▶ git fetch + reset --hard origin/main..."
git fetch --all
git reset --hard origin/main
echo " HEAD: $(git log -1 --oneline)"
echo "▶ supervisorctl restart nexus..."
supervisorctl restart nexus
if [ -f /tmp/nexus-frontend.tar.gz ] && [ "${SKIP_FRONTEND:-0}" != "1" ]; then
echo "▶ extract /tmp/nexus-frontend.tar.gz..."
tar xzf /tmp/nexus-frontend.tar.gz -C web/app
rm -f /tmp/nexus-frontend.tar.gz
if [ -f deploy/prune_frontend_assets.py ]; then
python3 deploy/prune_frontend_assets.py --dry-run
python3 deploy/prune_frontend_assets.py
fi
supervisorctl restart nexus
fi
sleep 3
HEALTH=$(curl -s http://127.0.0.1:8600/health || true)
SPA=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8600/app/ || echo "000")
echo " /health → ${HEALTH}"
echo " /app/ → ${SPA}"
if [ "${HEALTH}" = "ok" ] && [ "${SPA}" = "200" ]; then
echo "✓ Server deploy OK"
else
echo "✗ Verify failed — check: supervisorctl status nexus" >&2
exit 1
fi
+10
View File
@@ -156,3 +156,13 @@
{"ts":"2026-06-04T19:58:01+08:00","date":"2026-06-04","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"} {"ts":"2026-06-04T19:58:01+08:00","date":"2026-06-04","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-04T19:58:01+08:00","date":"2026-06-04","gate":"text_eol","result":"PASS","detail":"git index LF"} {"ts":"2026-06-04T19:58:01+08:00","date":"2026-06-04","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-04T19:58:01+08:00","date":"2026-06-04","gate":"summary","result":"BLOCK","detail":"5/7"} {"ts":"2026-06-04T19:58:01+08:00","date":"2026-06-04","gate":"summary","result":"BLOCK","detail":"5/7"}
{"ts":"2026-06-04T23:47:38+08:00","date":"2026-06-04","gate":"changelog","result":"PASS","detail":"2026-06-04-audit-phase2-p2-6-sync-engine-full.md 32lines"}
{"ts":"2026-06-04T23:47:38+08:00","date":"2026-06-04","gate":"audit","result":"PASS","detail":"2026-06-04-docs-consolidation.md"}
{"ts":"2026-06-04T23:47:38+08:00","date":"2026-06-04","gate":"test","result":"BLOCK","detail":"tests failed"}
{"ts":"2026-06-04T23:47:38+08:00","date":"2026-06-04","gate":"lint","result":"PASS","detail":"0 violations"}
{"ts":"2026-06-04T23:47:38+08:00","date":"2026-06-04","gate":"import","result":"PASS","detail":"ok"}
{"ts":"2026-06-04T23:47:38+08:00","date":"2026-06-04","gate":"security","result":"PASS","detail":"0 HIGH"}
{"ts":"2026-06-04T23:47:38+08:00","date":"2026-06-04","gate":"review","result":"BLOCK","detail":"audit missing changed files"}
{"ts":"2026-06-04T23:47:38+08:00","date":"2026-06-04","gate":"shell_eol","result":"PASS","detail":"all tracked sh LF"}
{"ts":"2026-06-04T23:47:38+08:00","date":"2026-06-04","gate":"text_eol","result":"PASS","detail":"git index LF"}
{"ts":"2026-06-04T23:47:38+08:00","date":"2026-06-04","gate":"summary","result":"BLOCK","detail":"5/7"}
+2 -2
View File
@@ -15,7 +15,7 @@
# 流程: # 流程:
# ① 可选:官方 1Panel 安装(含 Docker,交互式) # ① 可选:官方 1Panel 安装(含 Docker,交互式)
# ② 检测 / 安装 Docker Compose # ② 检测 / 安装 Docker Compose
# ③ install-nexus-fresh.sh → MySQL + Redis + Nexus 容器 # ③ install-nexus-fresh.sh → MySQL + Nexus 容器Redis 自行安装)
# #
# 反代:必须在 1Panel 面板 UI 配置,禁止脚本直接改 /opt/1panel/apps/ # 反代:必须在 1Panel 面板 UI 配置,禁止脚本直接改 /opt/1panel/apps/
# 示例: deploy/1panel/openresty-nexus.conf.example # 示例: deploy/1panel/openresty-nexus.conf.example
@@ -245,7 +245,7 @@ download_fresh_installer() {
} }
phase_nexus() { phase_nexus() {
step "安装 Nexus Docker 栈(MySQL + Redis + Nexus..." step "安装 Nexus Docker 栈(MySQL + NexusRedis 请自行安装..."
local installer local installer
if [[ -x "${NEXUS_ROOT}/deploy/install-nexus-fresh.sh" ]]; then if [[ -x "${NEXUS_ROOT}/deploy/install-nexus-fresh.sh" ]]; then
installer="${NEXUS_ROOT}/deploy/install-nexus-fresh.sh" installer="${NEXUS_ROOT}/deploy/install-nexus-fresh.sh"
+1 -1
View File
@@ -542,7 +542,7 @@ write_env_prod() {
compose_up() { compose_up() {
local root="$1" local root="$1"
save_host_profile "$root" save_host_profile "$root"
info "档位: ${NEXUS_PROFILE}MySQL/Redis/Nexus 内存与连接池已套用)" info "档位: ${NEXUS_PROFILE}MySQL/Nexus 内存与连接池已套用Redis 请自行安装"
info "构建并启动(首次约 1020 分钟)..." info "构建并启动(首次约 1020 分钟)..."
compose_cmd "$root" up -d --build compose_cmd "$root" up -d --build
} }
+4
View File
@@ -21,5 +21,9 @@ NEXUS_API_BASE_URL=https://api.synaglobal.vip
NEXUS_DB_POOL_SIZE=30 NEXUS_DB_POOL_SIZE=30
NEXUS_DB_MAX_OVERFLOW=20 NEXUS_DB_MAX_OVERFLOW=20
# Redis 不在 Docker 栈内 — 请在宿主机或 1Panel 应用商店自行安装
# Nexus 容器通过 host.docker.internal 访问宿主机 Redis(见 docker-compose.prod.yml
NEXUS_REDIS_URL=redis://host.docker.internal:6379/0
# Optional # Optional
# NEXUS_PUBLISH_PORT=8600 # NEXUS_PUBLISH_PORT=8600
+2 -2
View File
@@ -44,8 +44,8 @@ docker compose build --no-cache nexus
| 服务 | 端口 | 说明 | | 服务 | 端口 | 说明 |
|------|------|------| |------|------|------|
| nexus | 8600 | FastAPI + 静态 `/app/` | | nexus | 8600 | FastAPI + 静态 `/app/` |
| mysql | 3306 | 数据库 `nexus` | | mysql | 3306 | 数据库 `nexus`Compose 内) |
| redis | 6379 | 仅容器内网(未映射到主机) | | redis | — | **不在 Compose 内**;宿主机/1Panel 自建,默认 `host.docker.internal:6379` |
## 生产注意 ## 生产注意
+4 -29
View File
@@ -34,29 +34,6 @@ services:
networks: networks:
- nexus-internal - nexus-internal
redis:
image: redis:7-alpine
restart: unless-stopped
command:
- redis-server
- --appendonly
- "yes"
- --maxmemory
- ${REDIS_MAXMEMORY:-256mb}
- --maxmemory-policy
- allkeys-lru
volumes:
- redis-data:/data
mem_limit: ${REDIS_MEM_LIMIT:-384m}
cpus: ${REDIS_CPUS:-0.5}
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 8
networks:
- nexus-internal
nexus: nexus:
build: build:
context: .. context: ..
@@ -65,22 +42,21 @@ services:
depends_on: depends_on:
mysql: mysql:
condition: service_healthy condition: service_healthy
redis: extra_hosts:
condition: service_healthy - "host.docker.internal:host-gateway"
ports: ports:
- "127.0.0.1:${NEXUS_PUBLISH_PORT:-8600}:8600" - "127.0.0.1:${NEXUS_PUBLISH_PORT:-8600}:8600"
environment: environment:
MYSQL_HOST: mysql MYSQL_HOST: mysql
MYSQL_PORT: "3306" MYSQL_PORT: "3306"
REDIS_HOST: redis
REDIS_PORT: "6379"
NEXUS_PERSIST_DIR: /var/lib/nexus NEXUS_PERSIST_DIR: /var/lib/nexus
NEXUS_DOCKER_WRITE_ENV: "1" NEXUS_DOCKER_WRITE_ENV: "1"
NEXUS_HOST: "0.0.0.0" NEXUS_HOST: "0.0.0.0"
NEXUS_PORT: "8600" NEXUS_PORT: "8600"
NEXUS_DEPLOY_PATH: /app NEXUS_DEPLOY_PATH: /app
NEXUS_DATABASE_URL: mysql+aiomysql://nexus:${MYSQL_PASSWORD}@mysql:3306/nexus NEXUS_DATABASE_URL: mysql+aiomysql://nexus:${MYSQL_PASSWORD}@mysql:3306/nexus
NEXUS_REDIS_URL: redis://redis:6379/0 # 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_CORS_ORIGINS: ${NEXUS_CORS_ORIGINS:?set NEXUS_CORS_ORIGINS}
NEXUS_SECRET_KEY: ${NEXUS_SECRET_KEY:?set NEXUS_SECRET_KEY} NEXUS_SECRET_KEY: ${NEXUS_SECRET_KEY:?set NEXUS_SECRET_KEY}
NEXUS_API_KEY: ${NEXUS_API_KEY:?set NEXUS_API_KEY} NEXUS_API_KEY: ${NEXUS_API_KEY:?set NEXUS_API_KEY}
@@ -109,6 +85,5 @@ networks:
volumes: volumes:
mysql-data: mysql-data:
redis-data:
nexus-state: nexus-state:
nexus-web-data: nexus-web-data:
+31 -3
View File
@@ -20,6 +20,30 @@ s.close()
done done
} }
wait_tcp_optional() {
host="$1"
port="$2"
tries="${3:-15}"
echo "entrypoint: waiting for external Redis ${host}:${port} (max ${tries} tries)..."
n=0
while [ "$n" -lt "$tries" ]; do
if python3 -c "
import socket
s = socket.socket()
s.settimeout(2)
s.connect(('${host}', ${port}))
s.close()
" 2>/dev/null; then
echo "entrypoint: Redis ready at ${host}:${port}"
return 0
fi
n=$((n + 1))
sleep 2
done
echo "entrypoint: warn — Redis not reachable at ${host}:${port}; install on host or set NEXUS_REDIS_URL"
return 0
}
restore_persisted_env() { restore_persisted_env() {
if [ -f "${PERSIST_ENV}" ]; then if [ -f "${PERSIST_ENV}" ]; then
cp "${PERSIST_ENV}" /app/.env cp "${PERSIST_ENV}" /app/.env
@@ -64,11 +88,15 @@ trap persist_env_on_exit EXIT INT TERM
MYSQL_HOST="${MYSQL_HOST:-mysql}" MYSQL_HOST="${MYSQL_HOST:-mysql}"
MYSQL_PORT="${MYSQL_PORT:-3306}" MYSQL_PORT="${MYSQL_PORT:-3306}"
REDIS_HOST="${REDIS_HOST:-redis}"
REDIS_PORT="${REDIS_PORT:-6379}"
wait_tcp "${MYSQL_HOST}" "${MYSQL_PORT}" wait_tcp "${MYSQL_HOST}" "${MYSQL_PORT}"
wait_tcp "${REDIS_HOST}" "${REDIS_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}"
wait_tcp_optional "${REDIS_HOST}" "${REDIS_PORT}" 15
fi
restore_persisted_env restore_persisted_env
write_env_from_environment write_env_from_environment
-3
View File
@@ -2,9 +2,6 @@
MYSQL_CNF_FILE=mysql-prod-1c4g.cnf MYSQL_CNF_FILE=mysql-prod-1c4g.cnf
MYSQL_MEM_LIMIT=1g MYSQL_MEM_LIMIT=1g
MYSQL_CPUS=1 MYSQL_CPUS=1
REDIS_MAXMEMORY=128mb
REDIS_MEM_LIMIT=256m
REDIS_CPUS=0.25
NEXUS_MEM_LIMIT=1g NEXUS_MEM_LIMIT=1g
NEXUS_CPUS=1 NEXUS_CPUS=1
NEXUS_DB_POOL_SIZE=15 NEXUS_DB_POOL_SIZE=15
-3
View File
@@ -2,9 +2,6 @@
MYSQL_CNF_FILE=mysql-prod-2c8g.cnf MYSQL_CNF_FILE=mysql-prod-2c8g.cnf
MYSQL_MEM_LIMIT=2g MYSQL_MEM_LIMIT=2g
MYSQL_CPUS=1.5 MYSQL_CPUS=1.5
REDIS_MAXMEMORY=256mb
REDIS_MEM_LIMIT=384m
REDIS_CPUS=0.5
NEXUS_MEM_LIMIT=2g NEXUS_MEM_LIMIT=2g
NEXUS_CPUS=1.5 NEXUS_CPUS=1.5
NEXUS_DB_POOL_SIZE=30 NEXUS_DB_POOL_SIZE=30
-3
View File
@@ -2,9 +2,6 @@
MYSQL_CNF_FILE=mysql-prod-4c16g.cnf MYSQL_CNF_FILE=mysql-prod-4c16g.cnf
MYSQL_MEM_LIMIT=6g MYSQL_MEM_LIMIT=6g
MYSQL_CPUS=3 MYSQL_CPUS=3
REDIS_MAXMEMORY=512mb
REDIS_MEM_LIMIT=768m
REDIS_CPUS=1
NEXUS_MEM_LIMIT=4g NEXUS_MEM_LIMIT=4g
NEXUS_CPUS=3 NEXUS_CPUS=3
NEXUS_DB_POOL_SIZE=60 NEXUS_DB_POOL_SIZE=60
@@ -0,0 +1,31 @@
# 2026-06-05 — Docker 栈移除内置 Redis
## 摘要
按用户要求,一键安装脚本 / `docker-compose.prod.yml` **不再附带 Redis 容器**
Redis 由用户在宿主机或 1Panel 应用商店自行安装;Nexus 默认连 `host.docker.internal:6379`
## 变更
- `docker/docker-compose.prod.yml` — 删除 `redis` 服务与 `redis-data`
- `docker/entrypoint.sh` — 安装向导阶段不等待 Redis;否则有限次等待外置 Redis
- `server/api/install.py` — Redis 环境检测改为**不阻塞**(`pass: true`
- `deploy/README-1panel.md` — 补充 Redis 自建说明
- `docker/.env.prod.example` — 增加 `NEXUS_REDIS_URL`
## 迁移(已装旧栈)
```bash
cd /opt/nexus && git pull
docker stop nexus-prod-redis-1 2>/dev/null; docker rm nexus-prod-redis-1 2>/dev/null
# 在宿主机/1Panel 安装 Redis 并监听 6379
docker compose -f docker/docker-compose.prod.yml \
--env-file docker/.env.prod \
--env-file docker/profiles/$(cat docker/.host-profile).env \
up -d --build
```
## 安装向导
- DockerRedis 主机 `host.docker.internal`,端口 `6379`
- 步骤 2 Redis 未连接也可继续
+8 -9
View File
@@ -116,14 +116,13 @@ def _docker_wizard_defaults() -> dict[str, str] | None:
"""Prefill install step 3 when running inside docker-compose.prod.yml.""" """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" and not os.environ.get("REDIS_HOST"):
return None return None
rh, rp, _ = _resolve_redis_probe()
return { return {
"db_host": os.environ.get("MYSQL_HOST", "mysql"), "db_host": os.environ.get("MYSQL_HOST", "mysql"),
"db_port": os.environ.get("MYSQL_PORT", "3306"), "db_port": os.environ.get("MYSQL_PORT", "3306"),
"db_name": "nexus", "db_name": "nexus",
"db_user": "nexus", "db_user": "nexus",
"redis_host": rh, "redis_host": "host.docker.internal",
"redis_port": str(rp), "redis_port": "6379",
"redis_db": "0", "redis_db": "0",
} }
@@ -443,9 +442,9 @@ async def env_check():
"pass": False, "pass": False,
}) })
# Redis — Docker Compose: redis:6379 (not published on host 127.0.0.1) # Redis — 用户自行安装(1Panel/宿主机),不阻塞安装向导
redis_ok = False redis_ok = False
redis_msg = "安装" redis_msg = "检测"
rh, rp, rpass = _resolve_redis_probe() rh, rp, rpass = _resolve_redis_probe()
redis_label = f"{rh}:{rp}" redis_label = f"{rh}:{rp}"
try: try:
@@ -461,14 +460,14 @@ async def env_check():
redis_ok = True redis_ok = True
redis_msg = f"✓ 已连接 {redis_label}" redis_msg = f"✓ 已连接 {redis_label}"
except Exception as e: except Exception as e:
redis_msg = f"连接失败: {e}" redis_msg = f"连接 {redis_label}(可继续,步骤3配置或稍后安装 Redis)— {e}"
except ImportError: except ImportError:
redis_msg = "redis 库未安装" redis_msg = "redis 库未安装(可继续)"
checks.append({ checks.append({
"name": "Redis", "name": "Redis",
"required": "已连接", "required": "自行安装(步骤3配置)",
"current": redis_msg, "current": redis_msg,
"pass": redis_ok, "pass": True,
}) })
# MySQL database — user configures in step 3 # MySQL database — user configures in step 3
+1
View File
@@ -182,6 +182,7 @@
<!-- Redis --> <!-- Redis -->
<div class="border-t-2 border-slate-100 pt-4 mt-4"> <div class="border-t-2 border-slate-100 pt-4 mt-4">
<h3 class="font-bold text-slate-700 mb-3">⚡ Redis 配置</h3> <h3 class="font-bold text-slate-700 mb-3">⚡ Redis 配置</h3>
<p class="text-xs text-slate-500 mb-3">请先在宿主机或 1Panel 安装 Redis。Docker 部署填主机 <code class="bg-slate-100 px-1 rounded">host.docker.internal</code>,端口 6379。</p>
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
<div> <div>
<label class="block font-semibold text-sm text-slate-700 mb-1">Redis 主机</label> <label class="block font-semibold text-sm text-slate-700 mb-1">Redis 主机</label>