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:
+81
-1
@@ -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(),
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user