Files
Nexus/docs/release/20260708/Nexus-宝塔长期掉线修复与测试部署记录-20260707.md
T
r 9b583ccc91
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / e2e (push) Blocked by required conditions
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
docs(release): 主与 release 分支合并功能清单 + 导入 release 文档目录
便于 Gitea 上 main ↔ release/20260708-axs 合并评审与冲突对照。
2026-07-09 01:14:32 +08:00

13 KiB
Raw Blame History

Nexus 宝塔长期掉线修复与测试部署记录(2026-07-07)

1. 当前结论

本轮重点处理的是:宝塔面板“一键登录成功后,多个面板同时打开,约 1-2 小时后刷新陆续掉线”的问题。

当前状态:

  • Nexus 测试部署机:192.168.124.219
  • Nexus 项目目录:/opt/nexus-dev-current
  • Git 分支:audit/security-review
  • 最新提交:99edd30 chore: default compose ports for nexus test deploy
  • 宝塔长期会话修复提交:a538358 fix: keep bt panel sessions alive after one-click login
  • Docker 服务已恢复:
    • Nexus0.0.0.0:18600 -> 8600
    • MySQL0.0.0.0:13306 -> 3306
    • Redis0.0.0.0:16379 -> 6379
  • Health checkhttp://127.0.0.1:18600/health 返回 ok
  • 容器内确认补丁已生效:
    • _SESSION_CLEANUP_TTL_CHECK_SECONDS = 86400
    • _SESSION_CLEANUP_TTL_FAILED_RETRY_SECONDS = 3600

2. 用户现象与根因判断

用户反馈:

不是登录失败,是登录宝塔以后几个小时后自动退出;一键登录同时打开 10 个宝塔,过 2 个小时陆续掉;刷新就掉线。

这个现象不像是 Nexus 生成登录 URL 失败,也不像 tmp_token 本身立即失效。更符合下面链路:

  1. Nexus 一键登录宝塔时生成临时登录 token。
  2. 用户成功进入宝塔面板。
  3. 之后是否持续在线,取决于宝塔面板内部 session 过期/清理逻辑。
  4. 如果宝塔面板仍然按老逻辑清理 session,例如 task.py 中固定 if f_time > 3600:,那么即使 Nexus 生成的 tmp_token TTL 是 86400 秒,登录后的 session 仍可能在约 1 小时左右被宝塔清理。
  5. 用户刷新页面时,宝塔发现 session 已被清理,于是表现为掉线。

因此,本轮修复重点不是“能不能生成一键登录 URL”,而是:

  • 确保已配置过宝塔 API 的服务器,也会在一键登录前做一次 session cleanup TTL 检查/修复。
  • 避免老服务器因为已经配置过 API 而跳过 bootstrap,导致宝塔 session 清理阈值仍停留在 3600 秒。

3. 本次代码改动摘要

3.1 server/application/services/btpanel_service.py

新增/调整逻辑:

  • 新增检查周期:
    • _SESSION_CLEANUP_TTL_CHECK_SECONDS = 24 * 3600
    • _SESSION_CLEANUP_TTL_FAILED_RETRY_SECONDS = 3600
  • create_login_url() / _create_login_url_fresh() 的一键登录链路中,针对已经配置过宝塔 API 的服务器,也会 best-effort 触发 session cleanup TTL 检查。
  • 检查成功后 24 小时内不重复检查。
  • 检查失败后 1 小时内不反复重试,避免频繁 SSH。
  • 无 SSH 授权、缺中心 IP、SSH 失败时,不阻断用户登录,只记录状态。

3.2 server/infrastructure/btpanel/ssh_bootstrap.py

增强宝塔 bootstrap 输出:

  • session_cleanup_ttl_ok
  • session_cleanup_ttl_patched
  • warnings 中记录 patch 失败或异常信息。

宝塔侧修复目标是将固定 3600 秒清理逻辑改成读取面板 session timeout

session_cleanup_timeout = int(public.get_session_timeout() or 86400)
if f_time > session_cleanup_timeout:
    ...

3.3 server/infrastructure/btpanel/bootstrap_state.py

公开并记录 session cleanup 状态字段:

  • bootstrap_last_warnings
  • session_cleanup_ttl_ok
  • session_cleanup_ttl_checked_at
  • session_cleanup_ttl_last_error
  • session_cleanup_ttl_patched

这些字段方便后续在后端/前端展示每台宝塔服务器是否已经做过长期 session 修复。

3.4 tests/test_btpanel_login_url.py

新增测试:

  • 已配置宝塔 API 的服务器,一键登录前会执行一次 session cleanup TTL 检查。
  • 第二次一键登录不会在 24 小时窗口内重复执行。
  • 登录 URL 仍然是 fresh 生成,不缓存一次性 URL。

3.5 docker-compose.yml

新增防复发修复:

  • 默认 MySQL 发布端口从 3306 改为 13306
  • 默认 Redis 发布端口从 6379 改为 16379
  • 默认 Nexus 发布端口从 8600 改为 18600
  • 默认 CORS/API base 改到 18600
  • Usage 注释改成推荐:
docker compose --env-file docker/.env up -d --build

修复原因:Docker Compose 的 env_file 只会传给容器环境,不一定参与 compose 变量插值;如果忘记 --env-file docker/.env,端口会落回默认值。之前默认值是 3306/6379/8600,会和测试机上的宝塔 Redis 等服务冲突。


4. 宝塔一键登录调用链

flowchart TD
    UI["前端:一键登录按钮"] --> API["POST /api/btpanel/servers/{server_id}/login-url"]
    API --> SVC["BtPanelService.create_login_url"]
    SVC --> LOCK["Redis login_url_lock(server_id)"]
    LOCK --> FRESH["_create_login_url_fresh"]
    FRESH --> CHECK["_ensure_session_cleanup_ttl_for_login"]
    CHECK --> NEEDSSH{"需要/允许 SSH 检查?"}
    NEEDSSH -- "否" --> APIURL["_try_login_url_via_api"]
    NEEDSSH -- "是" --> SSHBOOT["ssh_bootstrap_panel"]
    SSHBOOT --> PATCH["ensure_session_cleanup_ttl patch 宝塔 task.py"]
    PATCH --> STATE["apply_bootstrap_success 写入 extra_attrs.bt_panel"]
    STATE --> APIURL
    APIURL --> BTAPI["宝塔 API: /config?action=set_temp_login"]
    BTAPI --> TOKEN["生成 tmp_token, TTL 86400"]
    TOKEN --> RESOLVE["resolve_temp_login_url"]
    RESOLVE --> URL["返回登录 URL"]
    APIURL -- "API 不可用" --> SSHLOGIN["ssh_temp_login_url fallback"]
    SSHLOGIN --> URL

关键点:

  • Redis lock 防止同一台服务器并发点击互相覆盖 tmp_token。
  • tmp_token TTL 是 86400 秒。
  • 本次补上的是“登录后 session cleanup TTL”的检查,解决成功进入宝塔后过一段时间刷新掉线的问题。

5. 已验证测试

在测试机 /opt/nexus-dev-current 上通过:

.venv/bin/python -m pytest tests/test_btpanel_login_url.py tests/test_btpanel_temp_login_ttl.py tests/test_btpanel_ssh_bootstrap.py -q

结果:

25 passed

完整后端测试:

.venv/bin/python -m pytest -q

结果:

728 passed, 1 skipped

运行容器验证:

Nexus health: ok
SESSION_CLEANUP_TTL_CHECK_SECONDS: 86400
FAILED_RETRY_SECONDS: 3600

6. 测试机 Docker 状态

当前容器状态:

nexus-nexus-1   Up healthy   0.0.0.0:18600->8600/tcp
nexus-mysql-1   Up healthy   0.0.0.0:13306->3306/tcp
nexus-redis-1   Up healthy   0.0.0.0:16379->6379/tcp

宿主机宝塔 Redis

127.0.0.1:6379

已避免 Nexus Redis 占用宿主机 6379


7. Docker 镜像构建代理问题

本轮执行 docker compose --env-file docker/.env up -d --build 时,Docker build 阶段失败过一次:

proxyconnect tcp: dial tcp 127.0.0.1:10808: connect: connection refused

原因:测试机上的 Docker daemon 使用了 127.0.0.1:10808 代理;但这里的 127.0.0.1 指的是 Linux 测试机自己,不是 Windows 主机。如果 Linux 测试机本地没有运行代理服务,Docker 就无法通过这个地址拉镜像。

本轮恢复方式:

  • 使用测试机已有的本地镜像 nexus-nexus:latest
  • 执行 docker compose --env-file docker/.env up -d --no-build 恢复服务。
  • 容器内确认代码已经包含本次宝塔长期会话修复。

后续建议二选一:

  1. 在 Linux 测试机本地启动可用代理,并确保 Docker daemon 能访问。
  2. 不使用 127.0.0.1:10808,改成测试机能访问的局域网代理地址,或者直接使用可用 Docker registry mirror。

8. 回滚方案

如需回滚本次宝塔长期会话修复:

cd /opt/nexus-dev-current
git revert a538358

如需只回滚 compose 默认端口防复发修复:

cd /opt/nexus-dev-current
git revert 99edd30

恢复部署推荐命令:

cd /opt/nexus-dev-current
docker compose --env-file docker/.env up -d --no-build

如果镜像代理问题已解决,再使用:

cd /opt/nexus-dev-current
docker compose --env-file docker/.env up -d --build

9. 仍需关注的边界问题

  1. _ensure_session_cleanup_ttl_for_login() 复用了 ssh_bootstrap_panel(),会同时检查/修复宝塔 API token、IP 白名单和 session cleanup TTL。

    • 优点:能顺便修复 API 白名单漂移。
    • 风险:首次检查会增加一次 SSH 耗时。
  2. _server_has_ssh_auth() 对 preset 的判断偏粗,只看配置里是否存在 preset id。

    • 后续安全巡检要确认 preset 缺失、失效、被删时是否降级得足够安全。
  3. apply_bootstrap_success() 现在也可能把 session cleanup 维护检查计入 bootstrap_attempts

    • 如果后续要精细统计,可以拆出单独的 maintenance check counter。
  4. Docker build 代理配置需要单独治理。

    • 当前运行服务没问题,但未来如果要重新 build,需要先修 Docker daemon 代理或镜像源。

10. 后续安全巡检计划

阶段 1:宝塔登录链路复审

  • 一键登录 tmp_token TTL 检查
  • Redis per-server lock 检查
  • 已配置 API 老服务器跳过 bootstrap 的边界修复
  • session cleanup TTL 检查/修复
  • 单测与完整后端测试
  • Docker 测试机运行验证

阶段 2SSH / 命令执行风险巡检

重点文件:

  • server/infrastructure/btpanel/ssh_bootstrap.py
  • server/infrastructure/btpanel/ssh_login.py
  • server/application/services/server_file_transfer_service.py
  • server/infrastructure/ssh/*

检查项:

  • 是否存在 shell 注入风险。
  • 是否所有远程命令都有 timeout。
  • 是否所有用户输入进入命令前都做 quote/escape。
  • 是否有敏感信息进入日志。
  • 是否有 sudo / root 权限边界问题。

阶段 3API 鉴权与权限边界巡检

重点:

  • server/api/*.py
  • 依赖注入中的 Depends / 当前用户获取逻辑
  • API key / session / CSRF / CORS

检查项:

  • 是否存在未鉴权管理接口。
  • 是否存在越权访问其他服务器资源。
  • 是否存在批量操作缺少权限校验。
  • CORS 是否过宽。

阶段 4:数据库与敏感字段巡检

重点:

  • server/models/*
  • server/repositories/*
  • server/application/services/*

检查项:

  • 密码、token、key 是否加密存储。
  • 查询是否存在 SQL 注入风险。
  • extra_attrs 是否可能被污染或写入不可控结构。
  • 数据迁移是否兼容老数据。

阶段 5:Redis / 锁 / 并发巡检

重点:

  • 登录 URL 锁
  • bootstrap 锁
  • 批量 bootstrap 并发
  • 缓存失效策略

检查项:

  • 锁 TTL 是否足够。
  • 异常时锁是否释放。
  • 并发点击是否导致 token 覆盖。
  • 多 worker/多容器下是否一致。

11. 当前建议

短期:

  1. 用当前部署继续测试“一键登录同时打开 10 个宝塔,2 小时后刷新是否还掉线”。
  2. 优先测试之前最容易掉线的服务器,例如用户提到的 42.193.182.190
  3. 如果仍掉线,下一步要查看该目标宝塔机器的 session cleanup patch 状态字段,而不是只看 Nexus 登录 URL 是否生成成功。

中期:

  1. 修 Docker daemon 代理,保证以后可稳定 --build
  2. 把 session cleanup 状态展示到 Nexus 后台,方便看到每台宝塔是否已修复。
  3. 继续做 SSH/API/DB/Redis 安全巡检。

12. SSH/文件管理安全巡检阶段 1 补充(2026-07-07

用户已确认:script_service 是设计内“管理员远程执行命令”能力,天然允许任意 shell,不作为漏洞修复项。后续仅检查其鉴权、审计、敏感信息日志边界。

本轮继续修复非脚本服务的命令参数边界:

  • 112e617 fix: stop archive option injection via filenames
    • remote_archive.py 的 tar/zip 压缩命令增加 -- 参数终止符。
  • bcec78f fix: harden file manager shell operations
    • 文件管理 rm/mv/mkdir/cp 命令增加 -- 参数终止符。
    • 递归 chmod/chown 禁止关键系统目录子树,例如 /etc/nginx/usr/*/var/log/*
    • 仍允许常见 Web 根目录,例如 /www/wwwroot/site/var/www/html

验证:

pytest tests/test_file_permissions.py tests/test_schema_path_validators.py tests/test_remote_archive_commands.py -q
22 passed in 0.54s

pytest -q
733 passed, 1 skipped in 11.77s

运行容器已热更新并重启,http://127.0.0.1:18600/health 返回 ok


13. SSH/文件管理安全巡检阶段 2:安全解压(2026-07-07

新增提交:

845d7e2 fix: validate remote archives before extraction

修复内容:

  • /api/sync/decompress 不再直接执行 tar xzf / unzip -o
  • 新增 run_remote_decompress():先列归档成员,再校验,再解压。
  • 拒绝归档内部:
    • 绝对路径,例如 /etc/passwd
    • 路径穿越,例如 ../evil.txt
    • Windows 反斜杠路径,例如 dir\evil.txt
    • 符号链接、硬链接、设备文件、FIFO、socket
  • tar 解压增加:
tar --no-same-owner --no-same-permissions -xzf <archive> -C <dest>

验证:

pytest tests/test_remote_archive_commands.py tests/test_schema_path_validators.py tests/test_file_permissions.py -q
31 passed in 0.55s

pytest -q
742 passed, 1 skipped in 11.83s

运行容器已热更新并重启,http://127.0.0.1:18600/health 返回 ok