fix(install): Docker .env 持久化与 entrypoint 覆盖 Compose 占位符

向导 init-db 后立即同步 nexus-state 卷;entrypoint 跳过不完整 env 并在启动前 source /app/.env,避免缺 DATABASE_URL 与无密码 REDIS_URL 导致容器崩溃。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Nexus Deploy
2026-06-06 11:08:22 +08:00
parent b11360015d
commit 49f88a2cd8
48 changed files with 534 additions and 20 deletions
+81 -1
View File
@@ -443,6 +443,7 @@ def _patch_env_redis_url(new_url: str) -> None:
out.append(new_line)
write_utf8_lf(ENV_FILE, "\n".join(out) + "\n")
logger.info("Patched NEXUS_REDIS_URL in %s", ENV_FILE)
_sync_env_to_persist_volume()
def _resolve_redis_url(req: InitDbRequest) -> tuple[bool, str, str | None]:
@@ -553,6 +554,28 @@ def _write_env(req: InitDbRequest, secret_key: str, api_key: str, encryption_key
write_utf8_lf(ENV_FILE, "\n".join(lines) + "\n")
logger.info(f".env written to {ENV_FILE}")
_sync_env_to_persist_volume()
def _persist_env_path() -> Path:
persist_dir = os.environ.get("NEXUS_PERSIST_DIR", "/var/lib/nexus").strip() or "/var/lib/nexus"
return Path(persist_dir) / ".env"
def _sync_env_to_persist_volume() -> None:
"""Copy /app/.env to nexus-state volume immediately (Docker install wizard)."""
if not _is_docker_install_mode() or not ENV_FILE.is_file():
return
dest = _persist_env_path()
try:
dest.parent.mkdir(parents=True, exist_ok=True)
import shutil
shutil.copy2(ENV_FILE, dest)
dest.chmod(0o600)
logger.info("Synced .env to persist volume %s", dest)
except OSError as e:
logger.warning("Failed to sync .env to persist volume: %s", e)
def _write_config_json(req: InitDbRequest, api_key: str, site_url: str) -> None:
@@ -586,8 +609,62 @@ def _detect_bt_panel() -> bool:
return Path("/www/server/panel/class/common.py").exists()
def _configure_docker_guardian(install_dir: str, api_port: str) -> list[str]:
"""1Panel + Docker: Compose restart/healthcheck + in-app self_monitor (no host Supervisor)."""
results: list[str] = []
if Path("/.dockerenv").is_file():
results.append("✓ Layer 1: Docker 容器运行中(Compose restart: unless-stopped")
else:
results.append("⚠ Layer 1: 未检测到 /.dockerenv — 请确认使用 docker-compose.prod.yml 部署")
results.append("✓ Layer 2: Python self_monitor 随正常模式启动(每 30s 自检 Redis/MySQL/WebSocket")
try:
import urllib.request
with urllib.request.urlopen(
f"http://127.0.0.1:{api_port}/health", timeout=3.0
) as resp:
body = resp.read().decode().strip()
if body == "ok":
results.append(f"✓ 容器内 /health 正常(127.0.0.1:{api_port}")
else:
results.append(f"⚠ /health 响应异常: {body[:80]}")
except Exception as e:
results.append(f"⚠ 容器内 /health 暂不可达: {e}")
db_host = _onepanel_service_host("DB")
redis_host = _onepanel_service_host("REDIS")
if db_host and redis_host:
results.append(f"✓ 1panel-network 容器名: MySQL={db_host}, Redis={redis_host}")
elif db_host or redis_host:
results.append(
f"⚠ 1Panel 主机名不完整(DB={db_host or '未设置'}, Redis={redis_host or '未设置'}"
)
else:
results.append(" 未配置 NEXUS_1PANEL_*_HOST — 宿主机执行 nx update 可自动探测")
site_url = os.environ.get("NEXUS_API_BASE_URL", "").strip()
publish_port = os.environ.get("NEXUS_PUBLISH_PORT", api_port).strip() or api_port
if site_url:
results.append(
f" Layer 3(宿主机): 1Panel OpenResty 反代 {site_url} → 127.0.0.1:{publish_port}"
)
else:
results.append(
f"ℹ Layer 3(宿主机): 配置 OpenResty 反代 → 127.0.0.1:{publish_port}(见 deploy/1panel/openresty-nexus.conf.example"
)
results.append(f" 日常升级: 宿主机 cd {install_dir} && nx update")
return results
def _configure_guardian(install_dir: str, api_port: str) -> list[str]:
"""Best-effort: configure Supervisor, crontab, health_monitor.sh"""
"""Best-effort process guardian: Docker/1Panel vs bare-metal Supervisor stack."""
if _is_docker_install_mode():
return _configure_docker_guardian(install_dir, api_port)
results: list[str] = []
bt_panel = _detect_bt_panel()
@@ -687,6 +764,7 @@ def _configure_guardian(install_dir: str, api_port: str) -> list[str]:
def _finalize_install_lock() -> None:
"""Write install lock marker and remove wizard state file."""
_sync_env_to_persist_volume()
write_utf8_lf(
INSTALL_LOCK,
f"Nexus installation locked at {datetime.now(timezone.utc).isoformat()}\n",
@@ -1047,6 +1125,7 @@ async def init_db(req: InitDbRequest):
"site_url": site_url,
"api_port": req.api_port,
"guardian_results": guardian_results,
"docker_mode": _is_docker_install_mode(),
"step": 4,
}
write_utf8_lf(STATE_FILE, json.dumps(state, ensure_ascii=False) + "\n")
@@ -1059,6 +1138,7 @@ async def init_db(req: InitDbRequest):
"tables_created": 14,
"guardian_results": guardian_results,
"bt_panel": _detect_bt_panel(),
"docker_mode": _is_docker_install_mode(),
}