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
|
[[ -n "$cname" ]] || return 0
|
||||||
restore_install_lock_in_container "$cname"
|
restore_install_lock_in_container "$cname"
|
||||||
if nexus_container_has_install_lock "$cname"; then
|
if nexus_container_has_install_lock "$cname"; then
|
||||||
|
archive_install_wizard_in_container "$cname"
|
||||||
upsert_env_var "$env_file" "NEXUS_INSTALL_WIZARD_PENDING" "0"
|
upsert_env_var "$env_file" "NEXUS_INSTALL_WIZARD_PENDING" "0"
|
||||||
info "安装已完成:NEXUS_INSTALL_WIZARD_PENDING=0"
|
info "安装已完成:NEXUS_INSTALL_WIZARD_PENDING=0"
|
||||||
fi
|
fi
|
||||||
@@ -897,10 +898,33 @@ compose_up() {
|
|||||||
verify_nexus_1panel_network "$root" || return 1
|
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() {
|
verify_install_wizard() {
|
||||||
local root="$1"
|
local root="$1"
|
||||||
local port="${2:-$(nexus_publish_port "$root")}"
|
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")
|
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
|
if [[ "$code" == "200" ]]; then
|
||||||
info " /app/install.html → HTTP 200"
|
info " /app/install.html → HTTP 200"
|
||||||
@@ -1167,9 +1191,15 @@ cmd_upgrade() {
|
|||||||
[[ "$health" == "ok" ]] && break
|
[[ "$health" == "ok" ]] && break
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
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")"
|
cname="$(nexus_container_name)"
|
||||||
if [[ "$wiz_code" != "200" && -x "$NEXUS_ROOT/deploy/sync-install-wizard-to-container.sh" ]]; then
|
if [[ -n "$cname" ]] && nexus_container_has_install_lock "$cname"; then
|
||||||
NEXUS_PUBLISH_PORT="$port" NEXUS_ROOT="$NEXUS_ROOT" bash "$NEXUS_ROOT/deploy/sync-install-wizard-to-container.sh"
|
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
|
fi
|
||||||
set_install_complete "$NEXUS_ROOT"
|
set_install_complete "$NEXUS_ROOT"
|
||||||
if [[ "$PRUNE_IMAGES" == true ]]; then
|
if [[ "$PRUNE_IMAGES" == true ]]; then
|
||||||
|
|||||||
@@ -41,8 +41,14 @@ main() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
resolve_container
|
resolve_container
|
||||||
info "同步安装向导静态文件..."
|
if docker exec "$CONTAINER" test -f /app/.install_locked 2>/dev/null \
|
||||||
sync_file "$app/install.html" /app/web/app/install.html
|
|| 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/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-init.js" /app/web/app/vendor/theme-init.js
|
||||||
sync_file "$app/vendor/theme.css" /app/web/app/vendor/theme.css
|
sync_file "$app/vendor/theme.css" /app/web/app/vendor/theme.css
|
||||||
|
|||||||
@@ -54,6 +54,23 @@ restore_persisted_install_lock() {
|
|||||||
fi
|
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() {
|
restore_persisted_env() {
|
||||||
if [ ! -f "${PERSIST_ENV}" ]; then
|
if [ ! -f "${PERSIST_ENV}" ]; then
|
||||||
return 0
|
return 0
|
||||||
@@ -136,6 +153,7 @@ if [ "${NEXUS_INSTALL_WIZARD_PENDING:-0}" != "1" ] && [ "${NEXUS_SKIP_REDIS_WAIT
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
restore_persisted_install_lock
|
restore_persisted_install_lock
|
||||||
|
archive_install_wizard_if_locked
|
||||||
restore_persisted_env
|
restore_persisted_env
|
||||||
write_env_from_environment
|
write_env_from_environment
|
||||||
export_app_env
|
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
|
ROOT_DIR = Path(__file__).resolve().parent.parent.parent
|
||||||
ENV_FILE = ROOT_DIR / ".env"
|
ENV_FILE = ROOT_DIR / ".env"
|
||||||
INSTALL_LOCK = ROOT_DIR / ".install_locked"
|
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_DIR = ROOT_DIR / "web" / "data"
|
||||||
CONFIG_JSON = CONFIG_DIR / "config.json"
|
CONFIG_JSON = CONFIG_DIR / "config.json"
|
||||||
ONEPANEL_HOSTS_JSON = CONFIG_DIR / "1panel-hosts.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")
|
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:
|
def _finalize_install_lock() -> None:
|
||||||
"""Write install lock marker and remove wizard state file."""
|
"""Write install lock marker and remove wizard state file."""
|
||||||
_sync_env_to_persist_volume()
|
_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",
|
f"Nexus installation locked at {datetime.now(timezone.utc).isoformat()}\n",
|
||||||
)
|
)
|
||||||
_sync_lock_to_persist_volume()
|
_sync_lock_to_persist_volume()
|
||||||
|
_archive_install_wizard_html()
|
||||||
if STATE_FILE.exists():
|
if STATE_FILE.exists():
|
||||||
try:
|
try:
|
||||||
STATE_FILE.unlink()
|
STATE_FILE.unlink()
|
||||||
|
|||||||
@@ -403,6 +403,22 @@ def test_script_aggregate_empty_server_ids_no_pending():
|
|||||||
assert _aggregate_execution_status([], {}) == "failed"
|
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):
|
def test_sync_lock_to_persist_volume(monkeypatch, tmp_path):
|
||||||
from server.api import install as install_api
|
from server.api import install as install_api
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user