release: BUG fixes batch 1-6, full bug scan docs, Ubuntu/Linux dev

- Backend: auth refresh reuse, schedule/retry/sync/agent/files fixes
- Frontend: push WS, files ETag browse, vite build assets
- Docs: audit/changelog, production deploy gate, bug scan registry B7-77
- Deploy: deploy-production.sh, prune assets, gate logs
This commit is contained in:
Nexus Deploy
2026-06-04 14:01:14 +08:00
parent 9ac6a2d27f
commit a2ae74d582
367 changed files with 77336 additions and 751 deletions
+62 -32
View File
@@ -36,14 +36,20 @@ CONFIG_JSON = CONFIG_DIR / "config.json"
STATE_FILE = ROOT_DIR / ".install_state.json"
def _is_installed() -> bool:
return ENV_FILE.exists() or INSTALL_LOCK.exists()
def _has_env() -> bool:
"""Python backend configured (.env written at install step 3)."""
return ENV_FILE.exists()
def _is_locked() -> bool:
return INSTALL_LOCK.exists()
def _is_installed() -> bool:
"""Install wizard fully complete — same as locked (backward-compatible for /status)."""
return _is_locked()
# ── Pydantic Models ──
class InitDbRequest(BaseModel):
@@ -176,7 +182,6 @@ def _write_config_json(req: InitDbRequest, api_key: str, site_url: str) -> None:
"db_port": req.db_port,
"db_name": req.db_name,
"db_user": req.db_user,
"db_pass": req.db_pass,
"app_name": "Nexus",
"app_version": "6.0.0",
"session_lifetime": 28800,
@@ -292,6 +297,19 @@ def _configure_guardian(install_dir: str, api_port: str) -> list[str]:
return results
def _finalize_install_lock() -> None:
"""Write install lock marker and remove wizard state file."""
write_utf8_lf(
INSTALL_LOCK,
f"Nexus installation locked at {datetime.now(timezone.utc).isoformat()}\n",
)
if STATE_FILE.exists():
try:
STATE_FILE.unlink()
except OSError as e:
logger.warning("Failed to remove install state file: %s", e)
# ── Endpoints ──
@router.get("/status")
@@ -300,20 +318,35 @@ async def install_status():
return {
"installed": _is_installed(),
"locked": _is_locked(),
"configured": _has_env(),
}
def _reject_if_installed():
"""Block install wizard after .env exists (except GET /status)."""
if _is_installed():
def _reject_if_locked():
"""Block install wizard mutations after step 5 lock."""
if _is_locked():
raise HTTPException(
status_code=403,
detail="系统已安装,安装向导已关闭。仅可查询 /api/install/status",
detail="安装向导已锁定。仅可查询 /api/install/status",
)
# Backward-compatible alias
_reject_post_install_wizard = _reject_if_installed
def _reject_if_env_exists():
"""Block repeat database init (step 3) once .env exists."""
if _has_env():
raise HTTPException(
status_code=403,
detail="系统已初始化,无法重复执行数据库安装。请继续创建管理员或前往登录。",
)
def _reject_if_installed():
"""Backward-compatible name — means wizard locked, not merely .env present."""
_reject_if_locked()
# Steps 25 (until lock): allow while .env exists
_reject_post_install_wizard = _reject_if_locked
@router.get("/env-check")
@@ -402,7 +435,8 @@ async def env_check():
@router.post("/init-db")
async def init_db(req: InitDbRequest):
"""Step 3: Connect to MySQL, create tables, write configs."""
_reject_if_installed()
_reject_if_locked()
_reject_if_env_exists()
db_url = _make_db_url(req)
redis_url = _build_redis_url(req)
@@ -538,9 +572,9 @@ async def init_db(req: InitDbRequest):
@router.post("/create-admin")
async def create_admin(req: CreateAdminRequest):
"""Step 4: Create admin account and set brand name."""
_reject_if_installed()
if _is_locked():
raise HTTPException(400, "系统已锁定")
_reject_if_locked()
if not _has_env():
raise HTTPException(400, "请先完成步骤3:数据库初始化")
if len(req.admin_password) < 6:
raise HTTPException(400, "密码长度至少6位")
@@ -561,9 +595,9 @@ async def create_admin(req: CreateAdminRequest):
# Insert admin
await conn.execute(
text(
"INSERT INTO admins (username, password_hash, email) "
"VALUES (:u, :h, :e) "
"ON DUPLICATE KEY UPDATE password_hash = :h2, email = :e2"
"INSERT INTO admins (username, password_hash, email, is_active) "
"VALUES (:u, :h, :e, 1) "
"ON DUPLICATE KEY UPDATE password_hash = :h2, email = :e2, is_active = 1"
),
{
"u": req.admin_username,
@@ -624,7 +658,9 @@ async def create_admin(req: CreateAdminRequest):
state["system_name"] = req.system_name
write_utf8_lf(STATE_FILE, json.dumps(state, ensure_ascii=False) + "\n")
return {"success": True}
# Atomically lock after admin creation (closes unauthenticated install API window)
_finalize_install_lock()
return {"success": True, "locked": True}
@router.post("/lock")
@@ -635,7 +671,10 @@ async def lock_install():
This prevents unauthenticated callers from locking the installer
before the legitimate admin has finished setup.
"""
_reject_if_installed()
if _is_locked():
return {"success": True, "already_locked": True}
if not _has_env():
raise HTTPException(400, "请先完成步骤3:数据库初始化")
# Verify at least one active admin account exists before allowing lock
try:
@@ -656,21 +695,12 @@ async def lock_install():
except Exception as e:
logger.warning("Failed to verify admin account before locking: %s", e)
# If DB is temporarily unreachable, conservatively reject lock
raise HTTPException(status_code=503, detail="数据库暂时不可用,无法验证管理员账户,请稍后重试。")
# Create lock marker file
write_utf8_lf(
INSTALL_LOCK,
f"Nexus installation locked at {datetime.now(timezone.utc).isoformat()}\n",
)
# Clean up state file
if STATE_FILE.exists():
try:
STATE_FILE.unlink()
except OSError as e:
logger.warning("Failed to remove install state file: %s", e)
raise HTTPException(
status_code=503,
detail="数据库暂时不可用,无法验证管理员账户,请稍后重试。",
) from e
_finalize_install_lock()
return {"success": True}