fix(install): probe redis via docker service host in env-check
Install wizard no longer hardcodes 127.0.0.1:6379; Docker installs get mysql/redis prefills and a passing Redis check inside the nexus container. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
# 2026-06-05 — 安装向导 Docker Redis 检测修复
|
||||||
|
|
||||||
|
## 摘要
|
||||||
|
|
||||||
|
修复 Docker 部署下安装向导步骤 2 误报 `127.0.0.1:6379 Connection refused`。
|
||||||
|
Compose 栈中 Redis 仅在容器内网 `redis:6379`,未映射宿主机端口。
|
||||||
|
|
||||||
|
## 变更
|
||||||
|
|
||||||
|
- `server/api/install.py`:`env-check` 读取 `NEXUS_REDIS_URL` / `REDIS_HOST`
|
||||||
|
- 返回 `docker_defaults`(db_host=mysql, redis_host=redis 等)供步骤 3 预填
|
||||||
|
- `web/app/install.html`:环境检测后自动应用 `docker_defaults`
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
- Docker 容器内 `env-check` Redis 应对 `redis:6379` 显示 ✓
|
||||||
|
- 步骤 3 数据库主机应为 `mysql`,Redis 为 `redis`
|
||||||
|
|
||||||
|
## 已部署容器热修
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/nexus && git pull
|
||||||
|
bash deploy/sync-install-wizard-to-container.sh
|
||||||
|
docker compose -f docker/docker-compose.prod.yml --env-file docker/.env.prod restart nexus
|
||||||
|
```
|
||||||
|
|
||||||
|
或 `nexus-1panel.sh upgrade` 重建镜像。
|
||||||
+46
-5
@@ -12,7 +12,7 @@ import logging
|
|||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from urllib.parse import quote_plus
|
from urllib.parse import quote_plus, urlparse
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
@@ -98,6 +98,36 @@ def _build_redis_url(req: InitDbRequest) -> str:
|
|||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_redis_probe() -> tuple[str, int, str | None]:
|
||||||
|
"""Redis target for install wizard health check (Docker uses redis:6379, not host loopback)."""
|
||||||
|
url = os.environ.get("NEXUS_REDIS_URL", "").strip()
|
||||||
|
if url:
|
||||||
|
parsed = urlparse(url)
|
||||||
|
host = parsed.hostname or "127.0.0.1"
|
||||||
|
port = parsed.port or 6379
|
||||||
|
return host, port, parsed.password
|
||||||
|
|
||||||
|
host = os.environ.get("REDIS_HOST", "127.0.0.1")
|
||||||
|
port = int(os.environ.get("REDIS_PORT", "6379"))
|
||||||
|
return host, port, 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"):
|
||||||
|
return None
|
||||||
|
rh, rp, _ = _resolve_redis_probe()
|
||||||
|
return {
|
||||||
|
"db_host": os.environ.get("MYSQL_HOST", "mysql"),
|
||||||
|
"db_port": os.environ.get("MYSQL_PORT", "3306"),
|
||||||
|
"db_name": "nexus",
|
||||||
|
"db_user": "nexus",
|
||||||
|
"redis_host": rh,
|
||||||
|
"redis_port": str(rp),
|
||||||
|
"redis_db": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _install_keys_from_environment() -> tuple[str, str, str] | None:
|
def _install_keys_from_environment() -> tuple[str, str, str] | None:
|
||||||
"""Reuse keys pre-generated by Docker install (docker/.env.prod → compose env)."""
|
"""Reuse keys pre-generated by Docker install (docker/.env.prod → compose env)."""
|
||||||
import os
|
import os
|
||||||
@@ -413,16 +443,23 @@ async def env_check():
|
|||||||
"pass": False,
|
"pass": False,
|
||||||
})
|
})
|
||||||
|
|
||||||
# Redis
|
# Redis — Docker Compose: redis:6379 (not published on host 127.0.0.1)
|
||||||
redis_ok = False
|
redis_ok = False
|
||||||
redis_msg = "未安装"
|
redis_msg = "未安装"
|
||||||
|
rh, rp, rpass = _resolve_redis_probe()
|
||||||
|
redis_label = f"{rh}:{rp}"
|
||||||
try:
|
try:
|
||||||
import redis as rlib
|
import redis as rlib
|
||||||
try:
|
try:
|
||||||
r = rlib.Redis(host="127.0.0.1", port=6379, socket_timeout=0.5)
|
r = rlib.Redis(
|
||||||
|
host=rh,
|
||||||
|
port=rp,
|
||||||
|
password=rpass or None,
|
||||||
|
socket_timeout=0.5,
|
||||||
|
)
|
||||||
r.ping()
|
r.ping()
|
||||||
redis_ok = True
|
redis_ok = True
|
||||||
redis_msg = "✓ 已连接 127.0.0.1:6379"
|
redis_msg = f"✓ 已连接 {redis_label}"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
redis_msg = f"连接失败: {e}"
|
redis_msg = f"连接失败: {e}"
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -460,7 +497,11 @@ async def env_check():
|
|||||||
})
|
})
|
||||||
|
|
||||||
all_pass = all(c["pass"] for c in checks)
|
all_pass = all(c["pass"] for c in checks)
|
||||||
return {"checks": checks, "all_pass": all_pass}
|
payload: dict = {"checks": checks, "all_pass": all_pass}
|
||||||
|
defaults = _docker_wizard_defaults()
|
||||||
|
if defaults:
|
||||||
|
payload["docker_defaults"] = defaults
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
@router.post("/init-db")
|
@router.post("/init-db")
|
||||||
|
|||||||
@@ -639,6 +639,9 @@ function installWizard() {
|
|||||||
const d = await r.json();
|
const d = await r.json();
|
||||||
this.envChecks = d.checks;
|
this.envChecks = d.checks;
|
||||||
this.envAllPass = d.all_pass;
|
this.envAllPass = d.all_pass;
|
||||||
|
if (d.docker_defaults) {
|
||||||
|
Object.assign(this.form, d.docker_defaults);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
this.error = '环境检测请求失败: ' + e.message;
|
this.error = '环境检测请求失败: ' + e.message;
|
||||||
|
|||||||
Reference in New Issue
Block a user