fix(1panel): sync Redis URL to container name on 1panel-network
Update NEXUS_REDIS_URL when detecting 1Panel-redis-*; add Redis TCP precheck and connection errors matching MySQL. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -110,8 +110,8 @@ Docker 栈**仅含 Nexus 容器**。MySQL / Redis 在 **1Panel 应用商店**安
|
||||
`nx update` / `nexus-1panel.sh` 会:
|
||||
|
||||
1. 将 Nexus 加入 **`1panel-network`**(`docker/docker-compose.1panel.yml`)
|
||||
2. 自动探测 MySQL/Redis 容器名写入 `docker/.env.prod`
|
||||
3. 安装向导步骤 3 **预填容器名**
|
||||
2. 自动探测 MySQL/Redis 容器名写入 `docker/.env.prod`(含 `NEXUS_REDIS_URL=redis://1Panel-redis-xxxx:6379/0`)
|
||||
3. 安装向导步骤 3 **预填 MySQL/Redis 容器名**
|
||||
|
||||
### 安装步骤
|
||||
|
||||
@@ -120,7 +120,7 @@ Docker 栈**仅含 Nexus 容器**。MySQL / Redis 在 **1Panel 应用商店**安
|
||||
3. `nx update` 后打开安装向导步骤 3(主机应已预填 `1Panel-mysql-…`)
|
||||
4. 填写 MySQL 密码;Redis 若设了密码一并填写
|
||||
|
||||
若仍报 `Can't connect to MySQL server on 'host.docker.internal'`:说明 Nexus **未接入 1panel-network** 或未探测到容器名,在服务器执行:
|
||||
若仍报 `Can't connect to MySQL/Redis on 'host.docker.internal'`:说明 Nexus **未接入 1panel-network** 或未探测到容器名,在服务器执行:
|
||||
|
||||
```bash
|
||||
cd /opt/nexus && bash deploy/detect-1panel-services.sh
|
||||
|
||||
@@ -226,7 +226,8 @@ sync_1panel_service_hosts() {
|
||||
NEXUS_1PANEL_REDIS_HOST=*)
|
||||
redis_host="${line#NEXUS_1PANEL_REDIS_HOST=}"
|
||||
upsert_env_var "$env_file" NEXUS_1PANEL_REDIS_HOST "$redis_host"
|
||||
info "1Panel Redis 容器: $redis_host"
|
||||
upsert_env_var "$env_file" "NEXUS_REDIS_URL" "redis://${redis_host}:6379/0"
|
||||
info "1Panel Redis 容器: $redis_host(NEXUS_REDIS_URL 已同步)"
|
||||
;;
|
||||
esac
|
||||
done < <("$detect" 2>/dev/null || true)
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ docker compose build --no-cache nexus
|
||||
| 本地开发 `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`。
|
||||
生产 1Panel:`nx update` 自动写入 `NEXUS_REDIS_URL=redis://1Panel-redis-xxxx:6379/0`;MySQL 由安装向导写入 `NEXUS_DATABASE_URL`。
|
||||
|
||||
## 生产注意
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@ services:
|
||||
NEXUS_HOST: "0.0.0.0"
|
||||
NEXUS_PORT: "8600"
|
||||
NEXUS_DEPLOY_PATH: /app
|
||||
NEXUS_REDIS_URL: ${NEXUS_REDIS_URL:-redis://host.docker.internal:6379/0}
|
||||
# 1Panel: nx update 写入 redis://1Panel-redis-xxxx:6379/0;非 1Panel 回退 host.docker.internal
|
||||
NEXUS_REDIS_URL: ${NEXUS_REDIS_URL:-redis://${NEXUS_1PANEL_REDIS_HOST:-host.docker.internal}:6379/0}
|
||||
NEXUS_1PANEL_DB_HOST: ${NEXUS_1PANEL_DB_HOST:-}
|
||||
NEXUS_1PANEL_REDIS_HOST: ${NEXUS_1PANEL_REDIS_HOST:-}
|
||||
NEXUS_CORS_ORIGINS: ${NEXUS_CORS_ORIGINS:?set NEXUS_CORS_ORIGINS}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# 2026-06-05 — 1Panel Redis 容器名与 NEXUS_REDIS_URL 同步
|
||||
|
||||
## 摘要
|
||||
|
||||
与 MySQL 对齐:探测到 `1Panel-redis-*` 时同步更新 `NEXUS_REDIS_URL`;安装向导步骤 3 增加 Redis TCP 预检与 1panel-network 错误文案;Compose 默认 Redis URL 优先使用 `NEXUS_1PANEL_REDIS_HOST`。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `deploy/nexus-1panel.sh` — sync 写入 `NEXUS_REDIS_URL`
|
||||
- `server/api/install.py` — Redis 预检与 `_redis_connect_error_detail`
|
||||
- `docker/docker-compose.prod.yml` — Redis URL 默认表达式
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
bash deploy/detect-1panel-services.sh # 应输出 NEXUS_1PANEL_REDIS_HOST=...
|
||||
grep NEXUS_REDIS_URL docker/.env.prod # redis://1Panel-redis-xxxx:6379/0
|
||||
nx update --no-cache
|
||||
# 安装向导步骤 3 redis_host 预填容器名;步骤 4 Redis 连接通过
|
||||
```
|
||||
+53
-9
@@ -98,8 +98,18 @@ def _build_redis_url(req: InitDbRequest) -> str:
|
||||
return url
|
||||
|
||||
|
||||
def _onepanel_service_host(kind: str) -> str | None:
|
||||
"""1Panel App Store container name (NEXUS_1PANEL_DB_HOST / REDIS_HOST)."""
|
||||
val = os.environ.get(f"NEXUS_1PANEL_{kind}_HOST", "").strip()
|
||||
return val or None
|
||||
|
||||
|
||||
def _resolve_redis_probe() -> tuple[str, int, str | None]:
|
||||
"""Redis target for install wizard (Docker: host.docker.internal → 宿主机自建 Redis)."""
|
||||
"""Redis target for install wizard (1Panel: container name on 1panel-network)."""
|
||||
onepanel = _onepanel_service_host("REDIS")
|
||||
if onepanel:
|
||||
return onepanel, 6379, None
|
||||
|
||||
url = os.environ.get("NEXUS_REDIS_URL", "").strip()
|
||||
if url:
|
||||
parsed = urlparse(url)
|
||||
@@ -112,12 +122,6 @@ def _resolve_redis_probe() -> tuple[str, int, str | None]:
|
||||
return host, port, None
|
||||
|
||||
|
||||
def _onepanel_service_host(kind: str) -> str | None:
|
||||
"""1Panel App Store container name (NEXUS_1PANEL_DB_HOST / REDIS_HOST)."""
|
||||
val = os.environ.get(f"NEXUS_1PANEL_{kind}_HOST", "").strip()
|
||||
return val or None
|
||||
|
||||
|
||||
def _service_tcp_targets(port: int, onepanel_kind: str) -> list[tuple[str, int]]:
|
||||
"""Probe order: 1Panel container name → host.docker.internal → loopback."""
|
||||
targets: list[tuple[str, int]] = []
|
||||
@@ -164,7 +168,7 @@ def _probe_mysql_installed() -> tuple[bool, str]:
|
||||
return _probe_tcp_service(3306, "DB")
|
||||
|
||||
|
||||
def _mysql_tcp_reachable(host: str, port: int, timeout: float = 3.0) -> bool:
|
||||
def _service_tcp_reachable(host: str, port: int, timeout: float = 3.0) -> bool:
|
||||
import socket
|
||||
|
||||
try:
|
||||
@@ -195,6 +199,27 @@ def _mysql_connect_error_detail(host: str, port: int) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def _redis_connect_error_detail(host: str, port: int) -> str:
|
||||
if os.environ.get("NEXUS_DOCKER_WRITE_ENV") != "1":
|
||||
return ""
|
||||
onepanel_redis = _onepanel_service_host("REDIS")
|
||||
if onepanel_redis and host == onepanel_redis:
|
||||
return (
|
||||
f"无法连接 1Panel Redis 容器 {host}:{port}。"
|
||||
"请确认:① Redis 容器运行中 ② Nexus 已接入 1panel-network(服务器执行 nx update)"
|
||||
"③ 若 1Panel Redis 设置了密码,请在步骤 3 填写 redis_pass。"
|
||||
)
|
||||
if host in ("host.docker.internal", "172.17.0.1"):
|
||||
example = onepanel_redis or "1Panel-redis-xxxx"
|
||||
return (
|
||||
f"无法通过 {host} 连接 Redis。"
|
||||
"1Panel 应用商店 Redis 在 1panel-network 内,容器间应使用 Redis 容器名"
|
||||
f"(如 {example}),而非 host.docker.internal。"
|
||||
"在服务器执行 nx update 可自动接入并预填容器名。"
|
||||
)
|
||||
return ""
|
||||
|
||||
|
||||
def _parse_dotenv(path: Path) -> dict[str, str]:
|
||||
data: dict[str, str] = {}
|
||||
if not path.is_file():
|
||||
@@ -228,6 +253,12 @@ async def _test_mysql_url(db_url: str) -> tuple[bool, str]:
|
||||
def _test_redis_url(redis_url: str) -> tuple[bool, str]:
|
||||
if not redis_url:
|
||||
return False, "✗ 未配置 NEXUS_REDIS_URL"
|
||||
parsed = urlparse(redis_url)
|
||||
host = parsed.hostname or "127.0.0.1"
|
||||
port = parsed.port or 6379
|
||||
if not _service_tcp_reachable(host, port):
|
||||
detail = _redis_connect_error_detail(host, port)
|
||||
return False, detail or f"✗ Redis TCP 不通({host}:{port})"
|
||||
try:
|
||||
import redis as rlib
|
||||
|
||||
@@ -235,6 +266,11 @@ def _test_redis_url(redis_url: str) -> tuple[bool, str]:
|
||||
client.ping()
|
||||
return True, "✓ Redis 连接正常"
|
||||
except Exception as e:
|
||||
err = str(e)
|
||||
if "Connection refused" in err or "Error 111" in err:
|
||||
detail = _redis_connect_error_detail(host, port)
|
||||
if detail:
|
||||
return False, detail
|
||||
return False, f"✗ Redis 连接失败: {e}"
|
||||
|
||||
|
||||
@@ -645,14 +681,22 @@ async def init_db(req: InitDbRequest):
|
||||
redis_url = _build_redis_url(req)
|
||||
site_url = req.site_url.rstrip("/") if req.site_url else f"http://127.0.0.1:{req.api_port}"
|
||||
db_port = int(req.db_port)
|
||||
redis_port = int(req.redis_port)
|
||||
|
||||
if not _mysql_tcp_reachable(req.db_host, db_port):
|
||||
if not _service_tcp_reachable(req.db_host, db_port):
|
||||
detail = _mysql_connect_error_detail(req.db_host, db_port)
|
||||
raise HTTPException(
|
||||
400,
|
||||
detail or f"无法连接 MySQL 服务器 {req.db_host}:{db_port}(TCP 不通,请确认服务已启动)",
|
||||
)
|
||||
|
||||
if not _service_tcp_reachable(req.redis_host, redis_port):
|
||||
detail = _redis_connect_error_detail(req.redis_host, redis_port)
|
||||
raise HTTPException(
|
||||
400,
|
||||
detail or f"无法连接 Redis 服务器 {req.redis_host}:{redis_port}(TCP 不通,请确认服务已启动)",
|
||||
)
|
||||
|
||||
try:
|
||||
patch_aiomysql_do_ping()
|
||||
engine = create_async_engine(db_url, pool_pre_ping=True, pool_recycle=300)
|
||||
|
||||
Reference in New Issue
Block a user