13 KiB
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 服务已恢复:
- Nexus:
0.0.0.0:18600 -> 8600 - MySQL:
0.0.0.0:13306 -> 3306 - Redis:
0.0.0.0:16379 -> 6379
- Nexus:
- Health check:
http://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 本身立即失效。更符合下面链路:
- Nexus 一键登录宝塔时生成临时登录 token。
- 用户成功进入宝塔面板。
- 之后是否持续在线,取决于宝塔面板内部 session 过期/清理逻辑。
- 如果宝塔面板仍然按老逻辑清理 session,例如
task.py中固定if f_time > 3600:,那么即使 Nexus 生成的 tmp_token TTL 是 86400 秒,登录后的 session 仍可能在约 1 小时左右被宝塔清理。 - 用户刷新页面时,宝塔发现 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_oksession_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_warningssession_cleanup_ttl_oksession_cleanup_ttl_checked_atsession_cleanup_ttl_last_errorsession_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恢复服务。 - 容器内确认代码已经包含本次宝塔长期会话修复。
后续建议二选一:
- 在 Linux 测试机本地启动可用代理,并确保 Docker daemon 能访问。
- 不使用
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. 仍需关注的边界问题
-
_ensure_session_cleanup_ttl_for_login()复用了ssh_bootstrap_panel(),会同时检查/修复宝塔 API token、IP 白名单和 session cleanup TTL。- 优点:能顺便修复 API 白名单漂移。
- 风险:首次检查会增加一次 SSH 耗时。
-
_server_has_ssh_auth()对 preset 的判断偏粗,只看配置里是否存在 preset id。- 后续安全巡检要确认 preset 缺失、失效、被删时是否降级得足够安全。
-
apply_bootstrap_success()现在也可能把 session cleanup 维护检查计入bootstrap_attempts。- 如果后续要精细统计,可以拆出单独的 maintenance check counter。
-
Docker build 代理配置需要单独治理。
- 当前运行服务没问题,但未来如果要重新 build,需要先修 Docker daemon 代理或镜像源。
10. 后续安全巡检计划
阶段 1:宝塔登录链路复审
- 一键登录 tmp_token TTL 检查
- Redis per-server lock 检查
- 已配置 API 老服务器跳过 bootstrap 的边界修复
- session cleanup TTL 检查/修复
- 单测与完整后端测试
- Docker 测试机运行验证
阶段 2:SSH / 命令执行风险巡检
重点文件:
server/infrastructure/btpanel/ssh_bootstrap.pyserver/infrastructure/btpanel/ssh_login.pyserver/application/services/server_file_transfer_service.pyserver/infrastructure/ssh/*
检查项:
- 是否存在 shell 注入风险。
- 是否所有远程命令都有 timeout。
- 是否所有用户输入进入命令前都做 quote/escape。
- 是否有敏感信息进入日志。
- 是否有 sudo / root 权限边界问题。
阶段 3:API 鉴权与权限边界巡检
重点:
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. 当前建议
短期:
- 用当前部署继续测试“一键登录同时打开 10 个宝塔,2 小时后刷新是否还掉线”。
- 优先测试之前最容易掉线的服务器,例如用户提到的
42.193.182.190。 - 如果仍掉线,下一步要查看该目标宝塔机器的 session cleanup patch 状态字段,而不是只看 Nexus 登录 URL 是否生成成功。
中期:
- 修 Docker daemon 代理,保证以后可稳定
--build。 - 把 session cleanup 状态展示到 Nexus 后台,方便看到每台宝塔是否已修复。
- 继续做 SSH/API/DB/Redis 安全巡检。
12. SSH/文件管理安全巡检阶段 1 补充(2026-07-07)
用户已确认:script_service 是设计内“管理员远程执行命令”能力,天然允许任意 shell,不作为漏洞修复项。后续仅检查其鉴权、审计、敏感信息日志边界。
本轮继续修复非脚本服务的命令参数边界:
112e617 fix: stop archive option injection via filenamesremote_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。