feat(install): require MySQL/Redis credential check at wizard step 3

Add test-credentials API and UI gate before init-db writes .env.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Nexus Deploy
2026-06-06 06:47:22 +08:00
parent d0544c9bd2
commit da061d35db
4 changed files with 181 additions and 21 deletions
+47 -12
View File
@@ -233,6 +233,15 @@ def _mysql_connect_error_detail(host: str, port: int) -> str:
return ""
def _redis_auth_error_detail() -> str:
if os.environ.get("NEXUS_DOCKER_WRITE_ENV") == "1":
return (
"Redis 密码错误或用户被禁用(1Panel Redis 请在步骤 3 填写应用参数中的密码;"
"无密码则留空)。主机应为 1Panel-redis-xxxx,不是 127.0.0.1。"
)
return "Redis 密码错误或需要 AUTH;请核对 Redis 密码(无密码则留空)。"
def _redis_connect_error_detail(host: str, port: int) -> str:
if os.environ.get("NEXUS_DOCKER_WRITE_ENV") != "1":
return ""
@@ -310,6 +319,14 @@ def _test_redis_url(redis_url: str) -> tuple[bool, str]:
detail = _redis_connect_error_detail(host, port)
if detail:
return False, detail
lowered = err.lower()
if (
"invalid username-password" in lowered
or "wrongpass" in lowered
or "noauth" in lowered
or "authentication required" in lowered
):
return False, _redis_auth_error_detail()
return False, f"✗ Redis 连接失败: {e}"
@@ -690,6 +707,32 @@ async def env_check():
return payload
async def _credential_checks(req: InitDbRequest) -> tuple[list[dict], bool]:
"""Verify MySQL + Redis with credentials from install step 3 (TCP + AUTH)."""
db_url = _make_db_url(req)
redis_url = _build_redis_url(req)
mysql_ok, mysql_msg = await _test_mysql_url(db_url)
redis_ok, redis_msg = _test_redis_url(redis_url)
checks = [
{"name": "MySQL", "required": "可连接", "current": mysql_msg, "pass": mysql_ok},
{"name": "Redis", "required": "可连接", "current": redis_msg, "pass": redis_ok},
]
return checks, mysql_ok and redis_ok
@router.post("/test-credentials")
async def test_credentials(req: InitDbRequest):
"""Step 3: Test MySQL/Redis account password before init-db writes .env."""
_reject_if_locked()
if _has_env():
raise HTTPException(
400,
"系统已初始化,无法重复检测。请继续步骤 4 创建管理员,或修正 .env 后使用连接检测。",
)
checks, all_pass = await _credential_checks(req)
return {"checks": checks, "all_pass": all_pass}
@router.get("/connection-check")
async def connection_check():
"""Step 4: Verify MySQL + Redis using .env written in step 3."""
@@ -719,21 +762,13 @@ async def init_db(req: InitDbRequest):
db_url = _make_db_url(req)
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 _service_tcp_reachable(req.db_host, db_port):
detail = _mysql_connect_error_detail(req.db_host, db_port)
checks, cred_ok = await _credential_checks(req)
if not cred_ok:
failed = next((c for c in checks if not c["pass"]), checks[0])
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 不通,请确认服务已启动)",
failed.get("current") or "MySQL 或 Redis 连接未通过,请先检测账号密码",
)
try: