fix(install): 安装锁定后将 install.html 归档为 .bak
防止已安装环境继续提供安装向导静态页;entrypoint 与升级脚本避免容器重建后恢复。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+34
-4
@@ -333,6 +333,7 @@ set_install_complete() {
|
||||
[[ -n "$cname" ]] || return 0
|
||||
restore_install_lock_in_container "$cname"
|
||||
if nexus_container_has_install_lock "$cname"; then
|
||||
archive_install_wizard_in_container "$cname"
|
||||
upsert_env_var "$env_file" "NEXUS_INSTALL_WIZARD_PENDING" "0"
|
||||
info "安装已完成:NEXUS_INSTALL_WIZARD_PENDING=0"
|
||||
fi
|
||||
@@ -897,10 +898,33 @@ compose_up() {
|
||||
verify_nexus_1panel_network "$root" || return 1
|
||||
}
|
||||
|
||||
archive_install_wizard_in_container() {
|
||||
local cname="$1"
|
||||
[[ -n "$cname" ]] || return 0
|
||||
docker exec "$cname" sh -c '
|
||||
if [ -f /app/web/app/install.html.bak ]; then
|
||||
rm -f /app/web/app/install.html
|
||||
elif [ -f /app/web/app/install.html ]; then
|
||||
mv /app/web/app/install.html /app/web/app/install.html.bak
|
||||
fi
|
||||
' 2>/dev/null || true
|
||||
}
|
||||
|
||||
verify_install_wizard() {
|
||||
local root="$1"
|
||||
local port="${2:-$(nexus_publish_port "$root")}"
|
||||
local code
|
||||
local code cname
|
||||
cname="$(nexus_container_name)"
|
||||
if [[ -n "$cname" ]] && nexus_container_has_install_lock "$cname"; then
|
||||
archive_install_wizard_in_container "$cname"
|
||||
code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${port}/app/install.html" 2>/dev/null || echo "000")
|
||||
if [[ "$code" == "404" ]]; then
|
||||
info " /app/install.html 已归档 → HTTP 404"
|
||||
return 0
|
||||
fi
|
||||
warn " 已锁定但 /app/install.html → HTTP $code"
|
||||
return 1
|
||||
fi
|
||||
code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${port}/app/install.html" 2>/dev/null || echo "000")
|
||||
if [[ "$code" == "200" ]]; then
|
||||
info " /app/install.html → HTTP 200"
|
||||
@@ -1167,9 +1191,15 @@ cmd_upgrade() {
|
||||
[[ "$health" == "ok" ]] && break
|
||||
sleep 2
|
||||
done
|
||||
wiz_code="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${port}/app/install.html" 2>/dev/null || echo "000")"
|
||||
if [[ "$wiz_code" != "200" && -x "$NEXUS_ROOT/deploy/sync-install-wizard-to-container.sh" ]]; then
|
||||
NEXUS_PUBLISH_PORT="$port" NEXUS_ROOT="$NEXUS_ROOT" bash "$NEXUS_ROOT/deploy/sync-install-wizard-to-container.sh"
|
||||
cname="$(nexus_container_name)"
|
||||
if [[ -n "$cname" ]] && nexus_container_has_install_lock "$cname"; then
|
||||
archive_install_wizard_in_container "$cname"
|
||||
info "安装已锁定,跳过 install.html 热同步"
|
||||
else
|
||||
wiz_code="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${port}/app/install.html" 2>/dev/null || echo "000")"
|
||||
if [[ "$wiz_code" != "200" && -x "$NEXUS_ROOT/deploy/sync-install-wizard-to-container.sh" ]]; then
|
||||
NEXUS_PUBLISH_PORT="$port" NEXUS_ROOT="$NEXUS_ROOT" bash "$NEXUS_ROOT/deploy/sync-install-wizard-to-container.sh"
|
||||
fi
|
||||
fi
|
||||
set_install_complete "$NEXUS_ROOT"
|
||||
if [[ "$PRUNE_IMAGES" == true ]]; then
|
||||
|
||||
@@ -41,8 +41,14 @@ main() {
|
||||
exit 1
|
||||
fi
|
||||
resolve_container
|
||||
info "同步安装向导静态文件..."
|
||||
sync_file "$app/install.html" /app/web/app/install.html
|
||||
if docker exec "$CONTAINER" test -f /app/.install_locked 2>/dev/null \
|
||||
|| docker exec "$CONTAINER" test -f /var/lib/nexus/.install_locked 2>/dev/null \
|
||||
|| docker exec "$CONTAINER" test -f /app/web/app/install.html.bak 2>/dev/null; then
|
||||
info "安装已锁定或向导已归档,跳过 install.html 同步"
|
||||
else
|
||||
info "同步安装向导静态文件..."
|
||||
sync_file "$app/install.html" /app/web/app/install.html
|
||||
fi
|
||||
sync_file "$app/vendor/tailwindcss-browser.js" /app/web/app/vendor/tailwindcss-browser.js
|
||||
sync_file "$app/vendor/theme-init.js" /app/web/app/vendor/theme-init.js
|
||||
sync_file "$app/vendor/theme.css" /app/web/app/vendor/theme.css
|
||||
|
||||
@@ -54,6 +54,23 @@ restore_persisted_install_lock() {
|
||||
fi
|
||||
}
|
||||
|
||||
archive_install_wizard_if_locked() {
|
||||
if [ ! -f /app/.install_locked ] && [ ! -f "${PERSIST_DIR}/.install_locked" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ -f /app/web/app/install.html.bak ]; then
|
||||
if [ -f /app/web/app/install.html ]; then
|
||||
rm -f /app/web/app/install.html
|
||||
echo "entrypoint: removed install.html (wizard archived as install.html.bak)"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
if [ -f /app/web/app/install.html ]; then
|
||||
mv /app/web/app/install.html /app/web/app/install.html.bak
|
||||
echo "entrypoint: archived install.html → install.html.bak"
|
||||
fi
|
||||
}
|
||||
|
||||
restore_persisted_env() {
|
||||
if [ ! -f "${PERSIST_ENV}" ]; then
|
||||
return 0
|
||||
@@ -136,6 +153,7 @@ if [ "${NEXUS_INSTALL_WIZARD_PENDING:-0}" != "1" ] && [ "${NEXUS_SKIP_REDIS_WAIT
|
||||
fi
|
||||
|
||||
restore_persisted_install_lock
|
||||
archive_install_wizard_if_locked
|
||||
restore_persisted_env
|
||||
write_env_from_environment
|
||||
export_app_env
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# 安装完成后归档 install.html
|
||||
|
||||
| 项 | 内容 |
|
||||
|----|------|
|
||||
| 日期 | 2026-06-06 |
|
||||
| 动机 | 已安装环境仍可访问 `/app/install.html` 并展示「系统已安装」,应物理移除向导页 |
|
||||
|
||||
## 变更摘要
|
||||
|
||||
- `install.py`:`_finalize_install_lock()` 将 `web/app/install.html` 重命名为 `install.html.bak`。
|
||||
- `entrypoint.sh`:检测到安装锁时启动阶段归档(防止镜像重建恢复向导页)。
|
||||
- `sync-install-wizard-to-container.sh`:已锁定/已归档时不再覆盖 `install.html`。
|
||||
- `nexus-1panel.sh`:升级与健康检查在已安装时期望 `install.html` → HTTP 404。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `server/api/install.py`
|
||||
- `docker/entrypoint.sh`
|
||||
- `deploy/sync-install-wizard-to-container.sh`
|
||||
- `deploy/nexus-1panel.sh`
|
||||
- `tests/test_security_unit.py`
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
- 需重建 Nexus 镜像(entrypoint)并重启容器。
|
||||
- 已安装实例:`nx update` 后 `set_install_complete` / entrypoint 自动归档;或手动 `mv install.html install.html.bak`。
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
pytest tests/test_security_unit.py -k archive_install -q
|
||||
curl -s -o /dev/null -w "%{http_code}\n" https://api.synaglobal.vip/app/install.html # 404
|
||||
docker exec nexus-prod-nexus-1 test -f /app/web/app/install.html.bak
|
||||
```
|
||||
@@ -31,6 +31,8 @@ router = APIRouter(prefix="/api/install", tags=["install"])
|
||||
ROOT_DIR = Path(__file__).resolve().parent.parent.parent
|
||||
ENV_FILE = ROOT_DIR / ".env"
|
||||
INSTALL_LOCK = ROOT_DIR / ".install_locked"
|
||||
INSTALL_HTML = ROOT_DIR / "web" / "app" / "install.html"
|
||||
INSTALL_HTML_BAK = ROOT_DIR / "web" / "app" / "install.html.bak"
|
||||
CONFIG_DIR = ROOT_DIR / "web" / "data"
|
||||
CONFIG_JSON = CONFIG_DIR / "config.json"
|
||||
ONEPANEL_HOSTS_JSON = CONFIG_DIR / "1panel-hosts.json"
|
||||
@@ -854,6 +856,24 @@ def _clear_state_redis_pass() -> None:
|
||||
write_utf8_lf(STATE_FILE, json.dumps(state, ensure_ascii=False) + "\n")
|
||||
|
||||
|
||||
def _archive_install_wizard_html() -> None:
|
||||
"""Rename install.html → install.html.bak so the wizard is not served after lock."""
|
||||
if INSTALL_HTML_BAK.is_file():
|
||||
if INSTALL_HTML.is_file():
|
||||
try:
|
||||
INSTALL_HTML.unlink()
|
||||
except OSError as e:
|
||||
logger.warning("Failed to remove install.html after archive: %s", e)
|
||||
return
|
||||
if not INSTALL_HTML.is_file():
|
||||
return
|
||||
try:
|
||||
INSTALL_HTML.rename(INSTALL_HTML_BAK)
|
||||
logger.info("Archived install wizard: %s → %s", INSTALL_HTML, INSTALL_HTML_BAK)
|
||||
except OSError as e:
|
||||
logger.warning("Failed to archive install.html: %s", e)
|
||||
|
||||
|
||||
def _finalize_install_lock() -> None:
|
||||
"""Write install lock marker and remove wizard state file."""
|
||||
_sync_env_to_persist_volume()
|
||||
@@ -862,6 +882,7 @@ def _finalize_install_lock() -> None:
|
||||
f"Nexus installation locked at {datetime.now(timezone.utc).isoformat()}\n",
|
||||
)
|
||||
_sync_lock_to_persist_volume()
|
||||
_archive_install_wizard_html()
|
||||
if STATE_FILE.exists():
|
||||
try:
|
||||
STATE_FILE.unlink()
|
||||
|
||||
@@ -403,6 +403,22 @@ def test_script_aggregate_empty_server_ids_no_pending():
|
||||
assert _aggregate_execution_status([], {}) == "failed"
|
||||
|
||||
|
||||
def test_archive_install_wizard_html(monkeypatch, tmp_path):
|
||||
from server.api import install as install_api
|
||||
|
||||
app_dir = tmp_path / "web" / "app"
|
||||
app_dir.mkdir(parents=True)
|
||||
html = app_dir / "install.html"
|
||||
html.write_text("<html>wizard</html>", encoding="utf-8")
|
||||
monkeypatch.setattr(install_api, "INSTALL_HTML", html)
|
||||
monkeypatch.setattr(install_api, "INSTALL_HTML_BAK", app_dir / "install.html.bak")
|
||||
|
||||
install_api._archive_install_wizard_html()
|
||||
|
||||
assert not html.is_file()
|
||||
assert (app_dir / "install.html.bak").read_text(encoding="utf-8") == "<html>wizard</html>"
|
||||
|
||||
|
||||
def test_sync_lock_to_persist_volume(monkeypatch, tmp_path):
|
||||
from server.api import install as install_api
|
||||
|
||||
|
||||
Reference in New Issue
Block a user