520 Commits

Author SHA1 Message Date
Nexus Agent 8092000880 fix(watch): psutil SSH 安装 apt 走 sudo
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
非 root 用户 apt-get 未加 sudo 导致 Permission denied;补充 run_root 与中文错误提示。
2026-06-12 04:09:10 +08:00
Nexus Agent e57d689fb2 docs(audit): 补 test_watch_probe_errors 到 psutil 安装审计 2026-06-12 04:04:59 +08:00
Nexus Agent 75efd506f5 feat(watch): 监测槽 SSH 一键安装 psutil
探针报 psutil missing 时显示安装按钮;POST install-psutil 经 SSH pip/apt 安装并审计。
2026-06-12 04:04:48 +08:00
Nexus Agent 32a1885f0d feat(watch): 监测槽开关、8h/24h、到期暂停与探针修复
支持随时暂停/恢复实时监测并保留槽位;TTL 到期自动暂停不占删槽;修复到期竞态导致空槽与唯一约束冲突;探针 parse_error 与中文错误;移除失败 snackbar。
2026-06-12 03:47:56 +08:00
Nexus Agent 7bb85aac41 feat(servers): 新服务器 Agent 安装/升级引导提示
修正仅有 API Key 误判为已装 Agent;列表与展开详情提示安装/升级并提供一键操作。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 03:16:12 +08:00
Nexus Agent f37047f746 docs(audit): 文件搜索与监测槽点击修复审计
补 Step3/Closure/DoD;选机对话框加载失败改 snackbar 提示。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 03:10:15 +08:00
Nexus Agent 6b8965aaf6 fix(watch): 监测槽空槽选机与卡片点击进历史
空槽弹出搜索选机对话框;已填槽整卡可点进监测历史;已在监测的 + 给出提示。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 03:07:00 +08:00
Nexus Agent 6140d68875 feat(files): 文件管理页服务器选择支持搜索
2000+ 子机场景下可搜索名称/域名/路径/ID,并加载全量服务器列表。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 03:02:28 +08:00
Nexus Agent b46922fe92 feat(watch): 4 槽实时监测 — 5s 探针、WS、历史页与告警联动
宝塔式自选子机监测:Pin CRUD、全指标 SSH/Redis 探针、探针落库、
/ws/watch 推送、监测历史与进程抽屉;Agent 可选附带 net/disk IO。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 02:56:37 +08:00
Nexus Agent f6e8b29640 fix(ui): 服务器/终端/推送搜索历史互不共享
终端独立 terminal_search 上下文(10条),与 servers_search、push_search 隔离。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 02:03:45 +08:00
Nexus Agent e0133b9098 feat(push,terminal): 推送搜索历史5条与终端中间搜索
推送页独立 combobox 历史(MySQL push_search,上限5);终端空态搜索回中间卡片,右侧栏仅列表。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 01:58:33 +08:00
Nexus Agent 724375c884 fix(push): 移除点击复制服务器名称
名称点击恢复为与卡片/行一致的勾选行为。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 01:51:39 +08:00
Nexus Agent 989b17902b feat(terminal): 右侧服务器列表与搜索历史
终端与会话并行:右侧常驻选服列表,combobox 共用服务器页最近 10 条搜索历史。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 01:47:46 +08:00
Nexus Agent 441aa0b9d2 docs: 部署门控审计(移除浏览器 + 推送 UI)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 01:44:17 +08:00
Nexus Agent 540712500f fix: 移除 iframe 浏览器并优化推送页服务器展示
删除内置浏览器前后端;推送页支持卡片/列表切换、点击名称复制与长名称展示。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 01:43:51 +08:00
Nexus Agent 17abd62261 feat(servers): 新服务器接入自动 sudo 配置与路径探测
创建/导入/IP 添加成功后后台 onboard 任务;前端注册进度条与 toast。
2026-06-12 00:19:31 +08:00
Nexus Agent 3a4b2ff3eb docs(reports): 未设路径 fleet 探测与推送验证报告 2026-06-11 23:50:11 +08:00
Nexus Agent 2877fd90a4 fix(scripts): batch_detect 不依赖 Redis 批任务结构 2026-06-11 23:48:28 +08:00
Nexus Agent e6abd4ae73 chore(deploy): 补 Gate7 审计与 shell LF 修复 2026-06-11 23:45:44 +08:00
Nexus Agent 29b053b3c0 fix(sync): 未设路径推送回退 /www/wwwroot,UI 判定不变
空或占位 /www/wwwroot 仍算「未设路径」;rsync 推送/预览/验证改用 resolve_push_target_path,并新增批量 detect-path 脚本。
2026-06-11 23:42:58 +08:00
Nexus Agent f11ad35708 chore(scripts): 推送提权验证脚本与温胜夜生产验证报告
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
2026-06-11 23:28:59 +08:00
Nexus Agent 84b388fae2 fix(sync): rsync 推送非 root 自动 sudo 提权
文件推送与文件管理对齐:非 root 默认 auto_sudo 时使用 sudo -n rsync;
新增批量 sudoers 补丁脚本以补全 rsync 白名单。
2026-06-11 23:20:45 +08:00
Nexus Agent 0a5111a959 docs(audit): 浏览器拖动手柄修复审计条目
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 20:20:02 +08:00
Nexus Agent 31846c4212 fix(browser): 修复浮动浏览器最小化条与面板无法拖动
迷你窗口使用独立最小尺寸与拖动手柄;展开态增加左侧拖拽条。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 20:19:49 +08:00
Nexus Agent 2e8daad802 fix(browser): 不可关闭、最小化可拖拽与重启
移除关闭入口;最小化为可拖动浮动条并持久化 minimized_rect;重启保留浏览历史。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 20:15:40 +08:00
Nexus Agent a94d340ec0 docs(audit): 补全全局浮动浏览器 Closure 与文件清单
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 20:11:21 +08:00
Nexus Agent 8356425814 feat: 全局浮动浏览器、记录持久化与角落快捷入口
浮动面板可最小化至右下角、左下角随时展开;标签与浏览记录写入 MySQL,切换菜单保持活动。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 20:10:57 +08:00
Nexus Agent 2a6f526bc9 feat: 内置 Web 浏览器页与服务器站点入口
侧栏浏览器页 iframe 预览 http(s) 站点;服务器列表「站点」由 domain 跳转,禁止嵌入时可新标签打开。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 18:30:44 +08:00
Nexus Agent 996403ad86 docs(audit): 文件上传 www 权限 fixup 审计记录
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 18:24:06 +08:00
Nexus Agent 4e117c04ea feat(files): 上传后自动 chown www:www 与文件 chmod
复用推送的 RSYNC_PUSH 配置,上传成功即 SSH 修正属主/权限;失败时仍保留文件并返回 warning。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 18:23:31 +08:00
Nexus Agent 79105dcec8 fix(files): 改权限后列表不更新及 chown 连带失败
ETag 纳入权限指纹避免 304 旧数据;chmod/chown 分步执行;仅变更属主/属组时才提交 chown。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 18:12:57 +08:00
Nexus Agent d3c0e57a08 fix(servers): 搜索改为手动触发,进页默认全量列表
不再自动恢复 last 搜索;下拉选历史、回车或清空才请求;输入过程不触发过滤。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 17:54:46 +08:00
Nexus Agent af912662de fix: combobox 清空 null 导致服务器列表加载失败
v-combobox 清空时 search 为 null 使 trim() 抛错;422 响应剥离不可序列化的 ctx 避免 500。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 17:52:49 +08:00
Nexus Agent c69e45a571 docs: 服务器搜索历史部署审计与门控记录
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 17:49:43 +08:00
Nexus Agent 130cc840c7 feat: 服务器搜索历史持久化到 MySQL
按管理员将 Servers 页搜索记录存入 admin_ui_preferences,换设备登录仍可恢复;首次自动迁移 localStorage。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 17:46:52 +08:00
Nexus Agent 704764bd7e fix: 订阅白名单多 worker 同步并统一上传上限 500MB
保存 IP 白名单时同步刷新并 Redis 广播 login_* 设置;推送/文件上传与 Nginx 模板对齐为 500MB,避免生产 50m/100m 导致大文件失败。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 17:39:26 +08:00
Nexus Agent 426bafd169 Audit: list package-lock.json in RDP removal review. 2026-06-10 23:23:28 +08:00
Nexus Agent dc4593f8b5 Remove browser RDP (guacd/Guacamole) feature entirely.
Product decision: drop 3389 remote desktop page, API, guacd sidecar, and related tests after unresolved target-side black screen issues.
2026-06-10 23:23:09 +08:00
Nexus Agent 341b130d9b fix(rdp): guacd 后台读取队列 + 隧道超时与尺寸
独立 guacd reader 避免 WS 竞争丢数据;ping 回显与 sync 已生产验证;前端 90s 超时与 OPEN 后发尺寸。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 21:54:50 +08:00
Nexus Agent af47b24450 fix(rdp): connect 前挂载画布并修复 RdpPage 布局
Guacamole display 需在 DOM 中才能完成 sync;canvasHost 独立挂载避免 Vue 重绘清除画布。
2026-06-10 21:45:54 +08:00
Nexus Agent aeca2fd7cb fix(rdp): guacd→WebSocket 按完整指令转发,修复黑屏
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
TCP 块转发会截断 img/blob 指令导致 guacamole-common-js 无法渲染;connect 不再经 URL 传密码。
2026-06-10 21:37:10 +08:00
Nexus Agent 960598f504 fix(rdp): 服务端 guacd 握手替代透明 TCP 桥接
guacamole-common-js 不会向 guacd 发送 select/connect;改为 Nexus 用 rdp_hosts 凭据完成握手后再桥接 WebSocket,消除 protocol violation。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 21:33:01 +08:00
Nexus Agent 6f998048b6 fix(rdp): WS JWT 改路径避免 Guacamole 查询串冲突
Guacamole connect() 追加 ?hostname= 会破坏 ?token= 导致 403。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 21:18:23 +08:00
Nexus Agent 9b9a857720 docs(audit): 补全 rdp_hosts 审计文件清单
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 20:59:09 +08:00
Nexus Agent f127f07ab2 feat(rdp): 3389 页独立管理 RDP 主机
rdp_hosts 表与专用 API,在 3389远程页添加/连接,不再依赖子机列表。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 20:58:46 +08:00
Nexus Agent dabfc128b3 feat: 浏览器 RDP、推送提示音与子机指标等多项功能
- 3389远程页 + guacd 侧车 WebSocket 桥(无 RDP 会话审计)
- 文件推送完成独立提示音,至少一台成功即播放
- 子机列表展开 CPU/内存/磁盘 sparkline 与指标采样落库
- 终端全量服务器列表、连接中状态;告警 Telegram 公网 IP
- SSH 密钥凭据 UI 简化;子机搜索未设路径筛选

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 01:37:32 +08:00
Nexus Agent dd870859b5 docs(changelog): 记录服务器名称列 chip 副标题回退
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 20:45:10 +08:00
Nexus Agent 700b3b4301 Revert "feat(servers): 名称列地址与资源 chip 副标题"
This reverts commit cc34b9c3cc.
2026-06-09 20:44:39 +08:00
Nexus Agent cc34b9c3cc feat(servers): 名称列地址与资源 chip 副标题
拆分 ServerListNameCell/AddressRow/MetricChips,列表展开前可见分类、CPU/内存/磁盘与心跳。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 20:41:34 +08:00
Nexus Agent 53b700f929 fix(dashboard): 移除舰队资源趋势 CSV 导出
产品决定不在趋势区提供 CSV 下载,保留全屏内明细表。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 18:01:45 +08:00
Nexus Agent fc4b46566e docs(audit): 子机离线告警 8 步审计与部署验证
补全 7209f53 门控审计报告与生产验证记录,更新离线统计审计 DoD。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 17:34:29 +08:00
Nexus Agent 7209f53af9 feat: 子机离线 Telegram 告警与仪表盘统计/趋势增强
离线边沿检测推送一次 Telegram(设置开关 notify_alert_offline);对齐仪表盘与服务器页统计卡、舰队趋势交互与告警叠加。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 17:16:48 +08:00
Nexus Agent 283041fe2f docs(audit): gate7 cover test_servers_dashboard 2026-06-09 09:50:51 +08:00
Nexus Agent 85b0c3a980 fix(servers): accurate offline count and status filter
Count online/offline per monitored server via Redis; filter list by UI status; expose offline_list for main table scope.
2026-06-09 09:50:37 +08:00
Nexus Agent cbbcdae66d feat(servers): interactive stat cards and unset_path count
Click offline/online to filter, unset-path to scroll to panel; add unset_path to /servers/stats; fix Docker deploy frontend sync.
2026-06-09 09:42:51 +08:00
Nexus Agent c5e54da8de docs(audit): list all files for agent diagnose UI gate 7 2026-06-09 09:27:04 +08:00
Nexus Agent a82d69c3e5 feat(servers): add Agent diagnose button and API endpoint
Expose central heartbeat + SSH probe diagnostics in the servers UI via POST /api/servers/{id}/agent-diagnose, sharing logic with the CLI script.
2026-06-09 09:26:42 +08:00
Nexus Agent fc837bfd43 feat(ops): add batch Agent remote diagnose script
SSH from central creds to inspect systemctl, redacted config, 401/heartbeat logs, and /health on sub-servers.
2026-06-09 09:19:40 +08:00
Nexus Agent 7a9dc3d3fc fix(credentials): default database credential host to 127.0.0.1
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 09:11:34 +08:00
Nexus Agent 1fefa4b6cb feat(credentials): validate RSA/OPENSSH PEM and auto-derive public key
Accepts keys like nz-admin.pem (BEGIN RSA PRIVATE KEY); rejects invalid PEM
before encrypting. Public key field optional on create/update.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 09:05:06 +08:00
Nexus Agent 6a6f3f7614 docs: audit cover web/app build output
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
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 09:00:29 +08:00
Nexus Agent fe8c11b706 fix(credentials): load SSH key presets via getList and validate save form
The ssh-key-presets API returns a plain array; fetchPagePerPage left the
list empty so new keys appeared not to save.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 09:00:00 +08:00
Nexus Agent b287707359 docs: record HTTP detail zh production deploy at 64c8bd7
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 08:56:56 +08:00
Nexus Agent 64c8bd74b8 docs: audit and deploy verification for API detail zh rollout
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 08:54:27 +08:00
Nexus Agent 1c0d7e9eb6 fix(api): localize service-layer ValueError messages to Chinese
So str(exc) passthrough on scripts, batch, sync, and schedule routes
returns Chinese without relying only on the HTTP detail translation layer.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 08:53:18 +08:00
Nexus Agent a842af2405 fix(auth): use Chinese fallbacks and map reason codes to user messages
Stop exposing raw reason codes like admin_not_found in TOTP setup errors;
auth routes and JWT guard now use Chinese literals with auth_failure_detail.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 08:44:51 +08:00
Nexus Agent 81004cf0a5 docs: note HTTP detail zh layer in handoff TOP 10
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 08:42:48 +08:00
Nexus Agent 3ba78cd10a feat(api): translate HTTPException detail to Chinese for admin routes
Add a global detail translator for common 4xx messages (Server not found,
auth headers, settings not found, etc.) while keeping /api/agent/* in English.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 08:42:43 +08:00
Nexus Agent 213aad97a4 docs: push category fix verification and refresh handoff to c366ed5
Record production evidence (101 机器人 vs old 5) and update AI handoff
with current deploy state and TOP 10 priorities.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 08:36:14 +08:00
Nexus Agent c366ed5adc fix(push): load full server list for accurate category selection
Push page previously fetched only 200 servers by ID order, so large categories
like 机器人 showed a handful of machines. Also decouple unset-path panel from
the main category chip filter and align category labels with the servers page.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 08:12:50 +08:00
Nexus Agent 664efb9ed5 feat(dashboard): add 24h fleet CPU/mem/disk trends with v-sparkline.
Sample fleet averages every heartbeat flush into fleet_metric_samples;
expose GET /api/servers/fleet-metrics and Dashboard sparkline card (B-02).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 07:24:57 +08:00
Nexus Agent adcd8010a5 docs: record production deploy at 863e49e after full rollout.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 07:14:32 +08:00
Nexus Agent 863e49e518 feat(ops): prod timezone probe, web/app container sync, and D-01 agent upgrade.
Unify upgrade-agent with install.sh via agent_deploy; auto-sync Git web/app after
Docker upgrade to prevent vite hash drift; extend prod_probe for Beijing TZ checks.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 07:12:38 +08:00
Nexus Agent 56139a42fe docs(audit): cover timezone follow-up files for gate 7 review.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 05:51:56 +08:00
Nexus Agent e3efbd4552 feat(timezone): operator schedules and filters use Beijing wall clock.
Cron matching, audit/alert date filters, and schedule UI align on Asia/Shanghai
while JWT/TOTP/heartbeat stay UTC; follow-ups add WS alert timestamps, fixed
install timezone copy, and 422 on invalid date filters.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 05:50:32 +08:00
Nexus Agent 4d82ed4031 build(frontend): rebuild web/app assets and prune stale hashes.
Align index.html with a complete Vite output set and remove 116 orphaned
chunks so git clone loads the SPA correctly.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 03:33:23 +08:00
Nexus Agent b62f1039db feat(ops): add production probe with dual-path health checks.
SSH origin checks cover auth contracts and once-schedule create/delete
when external HTTPS is blocked; includes install script for probe credentials.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 03:28:17 +08:00
Nexus Agent 9f78207aa2 docs: add agent diff guidance and close deploy verification.
Record .cursorrules rules to avoid full-repo diffs over web/app/assets,
and document production health checks for the alerts/schedule fix batch.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 02:52:32 +08:00
Nexus Agent 3864d120fb fix: alerts page size and schedule once fire_at parsing.
Wire alert history items-per-page to the API and parse fire_at ISO strings to datetime so single-shot push schedules save instead of 500.
2026-06-09 02:47:36 +08:00
Nexus Agent 0f6f91e61a fix(api): align alerts/commands contracts and ship full-site test suite.
Restore alert history filters and stats fields, paginate command/session logs for the SPA, show script labels on schedules, and land integration/E2E coverage with rebuilt web assets.
2026-06-09 02:13:13 +08:00
Nexus Agent 091fb97291 Push async submit, Chinese 422 field labels, hide server CSV export.
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
Return sync/files immediately with background runner; show loc_zh in validation errors; remove servers toolbar CSV export button.
2026-06-08 23:28:26 +08:00
Nexus Agent abeab9d3e4 Fix recovery Telegram dedup and unify Beijing time display.
Store alert metrics without per-value Redis members to stop duplicate recovery pushes; format all operator-facing timestamps in Asia/Shanghai across Web UI and Telegram.
2026-06-08 23:18:56 +08:00
Nexus Agent 972456a246 feat(agent): 401 停心跳、配置重载后重启与升级双文件下发
提取 heartbeat_policy;install/upgrade 同步 heartbeat_policy.py;config reload 恢复心跳协程。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 23:06:09 +08:00
Nexus Agent 69068e2e39 feat(ops): server_batch 回收、sync_logs 清理与 Agent 安装跳过
部署对齐三项后端能力:启动/周期收尾僵尸批量任务、30 天推送日志 purge、已安装且版本不低于主站时跳过批量安装 Agent。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 15:23:03 +08:00
Nexus Agent a401ea5a4d fix(servers): server_ids 批量上限 2000 并修复列表 422 中文
全选 101 台批量操作不再因 max_length=50 被拒;Pydantic 列表 too_long 错误显示完整中文。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 15:08:03 +08:00
Nexus Agent 632891e6e5 fix(settings): 表单校验与 422 中文错误信息
v-form 保存前 validate;validation_errors_zh 翻译 Pydantic msg;value 数字 coerce。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 14:52:59 +08:00
Nexus Agent ff143c286d fix(settings): 保存设置时将数值字段转为字符串避免 422
SettingUpdatePayload.value 要求 str,连接池与告警阈值为 number 输入。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 14:50:10 +08:00
Nexus Agent 85ecd1f54f fix(servers): 未设路径区表格绑定 sort-by 启用服务端排序
未设路径面板缺 v-model:sort-by,列头点击不触发 API;与主表共享 sortBy。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 14:36:21 +08:00
Nexus Agent 05c7aeb702 fix(servers): 未设路径批量检测对话框选中数
检测路径与批量改分类确认框按当前区块显示并提交正确 server_ids。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 14:09:50 +08:00
Nexus Agent d0526c06b7 fix(servers): 展开详情对比度与同步日志仅 1 条
展开区灰底可读;行内详情只拉最近一条同步记录,去掉「最近 N 条」文案。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 13:45:13 +08:00
Nexus Agent cb49fd9d6d docs(audit): gate7 补资源恢复开关联动与仪表盘去列表
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 13:23:04 +08:00
Nexus Agent fe8b24ca0b fix(notify): 资源恢复 Telegram 联动 CPU/内存/磁盘开关
关闭分项告警后不再推送对应恢复消息;仪表盘移除服务器列表并更新设置页说明。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 13:22:38 +08:00
Nexus Agent 6612c76dc6 docs(audit): gate7 补 dashboard 修复 index.html
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 13:13:43 +08:00
Nexus Agent 86bc737f4c fix(dashboard): 修复服务器列表不显示
清除 keep-alive 下 loading 卡死;改用 v-card-text 与表格内置 loading,并加 30s 自动刷新。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 13:13:34 +08:00
Nexus Agent 27da842a20 fix: Telegram 开关多 worker 同步与服务器行内详情展开
关闭 notify_* 后通过 Redis 广播同步各 worker 内存设置;修复 expanded id 类型不匹配导致点击名称无法展开详情。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 13:05:22 +08:00
Nexus Agent a5b1d34cec docs(audit): gate7 补 web/app/index.html 覆盖
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 12:55:20 +08:00
Nexus Agent 1e83bb34bd feat(servers): 未设路径区批量管理与主表齐平
独立勾选与全选筛选结果,共用 ServerBatchActionBar;列与批量操作对齐主列表。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 12:55:02 +08:00
Nexus Agent 8c86b50d09 feat(servers): 未设路径区块与行内详情;命令栏恢复单色
服务器页拆分未设路径列表、行内展开详情;API target_path_unset 可选过滤。
命令栏取消 Shell 语法彩色,ANSI 仅保留 xterm SSH 输出区。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 12:48:45 +08:00
Nexus Agent 8312a6cf81 fix(terminal): WebSSH xterm SSH 交互区 ANSI 彩色输出
PTY 环境变量注入 TERM=xterm-256color,DATA 帧 base64 保 ESC;前端解码写入 xterm;命令栏 Shell 高亮 CSS 与黑屏分离。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 12:35:27 +08:00
Nexus Agent 1d1fb6285b fix(terminal): 快捷命令栏「管理」改为「断开」
底部快捷栏提供断开会话,快捷命令配置仍走系统设置。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 12:14:47 +08:00
Nexus Agent 835ef2c8ea fix(terminal): WebSSH 固定黑底白字 ANSI 终端配色
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
不再跟随 Vuetify 浅色主题,xterm 始终使用经典黑底白字与 16 色输出。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 12:09:00 +08:00
Nexus Agent 09d4613e46 fix(scripts): script_executions.results 扩 MEDIUMTEXT 并截断落库
366 台批量执行 results JSON 超 TEXT 64KB 导致 flush 失败、状态卡在 running。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 11:57:20 +08:00
Nexus Agent 22d5d21c1f fix(schedules): 单次显示「当天」、收窄重复方式下拉
单次执行不再出现「重复方式」;「每天」改为「当天」;循环模式下拉宽度收窄。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 11:49:20 +08:00
Nexus Agent 08a0157c95 feat: 调度表单重构、登录门控、批量任务与页面缓存对齐
调度页执行周期可视化/单次执行/分类选机/推送源对齐;登录 IP 门控与无缝导航;
服务器批量后台任务、执行记录、凭据合并、各页激活刷新与错误提示修复。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 11:17:21 +08:00
Nexus Agent b8425cc059 fix(servers): 状态/心跳/Agent 列 overlay 后排序
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:38:28 +08:00
Nexus Agent b220a5a762 fix(terminal): 移除命令栏冗余按钮,断开遮罩仅覆盖 SSH 区
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:30:31 +08:00
Nexus Agent b011b60f9c feat(ui): 服务器/脚本/重试队列页面自动刷新
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:27:46 +08:00
Nexus Agent 442e005d27 docs(audit): 终端修复审计补 index.html
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:24:44 +08:00
Nexus Agent 908ae938b5 fix(terminal): 深链 server_id 下命令输入框可用
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:24:32 +08:00
Nexus Agent b12fb4770b docs(audit): Gate 7 vite assets 跳过门控审计
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:21:31 +08:00
Nexus Agent 6150a39889 fix(deploy): Gate 7 跳过 web/app/assets 哈希产物
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:21:20 +08:00
Nexus Agent 06af90d35e feat(scripts): 执行详情服务器名与顶栏居中提示
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:20:37 +08:00
Nexus Agent 9723e0f528 docs(audit): Gate 7 diff 修复门控审计
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:09:59 +08:00
Nexus Agent 118ec9f6b0 fix(deploy): Gate 7 仅对比 HEAD~1..HEAD 并跳过 gate_log
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:09:46 +08:00
Nexus Agent 3d76c4a447 docs(audit): 补齐脚本看板部署门控所需审计段落
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:08:28 +08:00
Nexus Agent 0b2d7ac50d feat(scripts): 异步执行、全局进度看板与完成提示音设置
脚本 exec 立即返回 running 并在后台跑批次;/ws/alerts 推送 script_progress/complete;
新增 ScriptRunsPage、Telegram 汇总通知与可配置的浏览器完成提示音。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 07:07:02 +08:00
Nexus Agent 3f995a74bb docs: 更新 AI handoff 至 df95a24
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 05:51:57 +08:00
Nexus Agent df95a2429f feat(frontend): 全选筛选结果与脚本失败分组复制
服务器列表支持跨页全选当前筛选/分类本组;脚本执行详情按错误类型分组(去动态 IP)并复制失败清单,不做 CSV 导出。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 05:51:11 +08:00
Nexus Agent 5f63fb0a3f fix(scripts): 执行进度按成功台数计算,失败项快速定位与面板清理
progress 改为 success/total(失败不计入分子);脚本库执行详情中文化、失败筛选与终态任务移出批量状态面板。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 05:11:39 +08:00
Nexus Agent c435413563 feat(servers): 分类筛选/批量改分类、分页中文与列排序,脚本库按分类选机
支持 300+ 台服务器按分类浏览与批量管理,修复分页「全部」与排序;门控 Gate3 强制本地 API。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 04:30:24 +08:00
Nexus Agent c8b0663508 feat: 批量IP添加、脚本库历史、推送权限记录与脚本SSH执行修复
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
批量添加替代CSV并支持去重;脚本库页内嵌执行历史;推送历史记录 rsync 权限策略;
脚本执行不再依赖 Agent 在线;服务器同步日志与相关单测补齐。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 03:15:40 +08:00
Nexus Agent fb6a4be797 feat(commands): 推送日志第三视图与 CSV 导入模板
按对齐计划 Phase1:Commands 页增加推送日志筛选视图;补全 servers_import_template.csv;扩写功能指南 §12.6/12.8/12.10 与 acceptance_catalog。
2026-06-08 02:45:17 +08:00
Nexus Agent dfb0ea2d8f fix(frontend): 脚本库与推送调度对齐设计文档
修复 ScriptsPage 执行契约(script_id/command、历史过滤、长任务/凭据)及 SchedulesPage 缺失能力(target_path、next_run、push/script、cron/once、sync_mode);后端补迁移与 schedule_runner 传参。
2026-06-08 02:39:18 +08:00
Nexus Agent f12a0b6a36 fix(sync): rsync 推送后设置远程属主 www 与权限 755
推送默认 --chown=www:www --chmod=D755,F755,避免文件落盘为 root。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 02:23:43 +08:00
Nexus Agent 5ed3c28491 fix(sync): 推送异常时落库 failed,修复订正时区与卡住阈值
rsync 抛错时 sync_log 不再永久 running;reconcile 兼容 naive UTC。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 02:14:33 +08:00
Nexus Agent 77cd9a5205 fix(docker): 生产镜像安装 rsync 修复文件推送失败
容器内缺少 rsync/openssh-client/sshpass,_rsync_push 触发 FileNotFoundError。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 02:10:06 +08:00
Nexus Agent 814262bd83 feat(push): 暂存文件支持批量删除
推送页暂存列表增加多选、全选与批量删除确认,复用 local-file-ops 逐条删除。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 02:05:33 +08:00
Nexus Agent 2b016f91af fix(push): 普通文件上传不限制后缀
upload-files 接受任意后缀含 .zip;拖拽/多选统一原样暂存,解压改走「ZIP 解压上传」按钮。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 01:19:20 +08:00
Nexus Agent 217c5d9935 feat(push): 源路径验证、普通文件上传与暂存文件管理
补齐 Round4 H5/H6:验证 Nexus 主机源路径、失败批量重试;新增 upload-files 与普通文件多选上传;上传后可浏览/预览/重命名/删除暂存目录内容。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 01:14:07 +08:00
Nexus Agent a225b655d9 feat(frontend): 恢复服务器批量检测路径并修复推送目标路径
补回 SPA 迁移丢失的 detect-path 确认框与结果弹窗,推送页留空走各机 target_path,文件页默认 50 条/页。
2026-06-08 00:56:35 +08:00
Nexus Agent bdb3b3bc34 docs(deploy): 生产对齐部署验证报告与 changelog 补全
记录 e5a3f25 部署、前端镜像重建与 Agent 1/1 升级结果。
2026-06-07 23:22:21 +08:00
Nexus Agent e5a3f2524e fix(auth): change_password 改密路径 audit_repo 定义顺序
部署前补齐门控审计与 Layer3 演练终验记录,消除 ruff F821。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 23:11:28 +08:00
Nexus Agent 9337f37da8 fix(files,deploy): 文件上传字段名与 Layer3 巡检脚本
后端同时接受 file/files 并支持多文件 SFTP 上传;修复 health_monitor.sh 引号语法使 Layer 3 cron 可运行。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 23:00:01 +08:00
Nexus Agent 7ea94a60ab fix(auth,servers): 修复 refresh 自动登出与服务器状态未知
API 返回 status 字段供前端显示在线状态;token_version 为 NULL 时 refresh cookie 含 :None 导致续期 401。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 22:53:29 +08:00
Nexus Agent 05006cb5df feat(auth): refresh token 先 Redis 后 MySQL 双写
登录 30 天会话 hash 持久化到 admin_refresh_tokens;启动从 MySQL 回填 Redis,
Redis 重启后会话仍可 refresh。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 21:56:56 +08:00
Nexus Agent 69087988b1 fix(auth): 反代后登录白名单读取真实客户端 IP
Docker/1Panel 反代时 request.client.host 为 172.18.0.1;可信内网跳读取 X-Real-IP/X-Forwarded-For。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 21:50:39 +08:00
Nexus Agent a14fbb3df3 fix(ssh): 快速添加跳过主机密钥校验并中文化错误
凭据轮询在 SSH_STRICT 默认 true 时因 HostKeyNotVerifiable 在认证前失败;
探测始终 known_hosts=None,默认 strict 改为 false,错误信息统一中文。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 21:31:38 +08:00
Nexus Agent c50f65e8ea docs: Layer 3 巡检 Telegram 用户终验确认
记录用户已收到引号修复与中文详细通知消息,闭环终验通过。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 21:09:14 +08:00
Nexus Agent ea2507dbc0 feat(telegram): 全部通知改为中文详细说明
统一告警/恢复/系统/Layer3/测试消息格式,含来源、影响与建议操作。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 21:01:47 +08:00
Nexus Agent f60a5ea5bc fix(deploy): health_monitor 剥离 .env 引号以修复 Telegram 404
容器 .env 带引号的 Token 导致 sendMessage URL 无效;增加 normalize_env_value
与失败日志。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 20:58:02 +08:00
Nexus Agent ca0de7d04d docs: Layer 3 模拟巡检告警验证报告
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
记录 flock 失败计数 bug 修复与 iptables 演练结果。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 20:56:21 +08:00
Nexus Agent 764703d401 fix(deploy): health_monitor flock 失败计数回传
flock 子 shell 递增后未回传 FAIL,导致 Layer 3 永不触发重启与 Telegram。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 20:53:14 +08:00
Nexus Agent b8785d1d51 docs: Layer 3 巡检 Telegram 配置后生产复查
记录用户配置 Telegram 后 env 同步、health_monitor 就绪与 cron 状态。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 20:49:13 +08:00
Nexus Agent 5b372fb8a2 docs: Layer 3 巡检部署生产验证报告
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 20:19:31 +08:00
Nexus Agent f9934bd4f1 feat(ops-patrol): Layer 3 巡检与 Telegram 配置打通
保存 Telegram 时同步至 .env,health_monitor 支持 MySQL 回退,
设置页展示巡检状态,部署时自动安装 cron。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 20:17:24 +08:00
Nexus Agent 66a4cba39b docs: 设置页 Telegram 修复生产验证报告
记录 540a7fc 部署结果与浏览器终验步骤。
2026-06-07 19:46:43 +08:00
Nexus Agent 540a7fce05 fix(settings): Telegram 检测/测试闭环与 8 项告警开关恢复
检测与测试前自动持久化 Token/Chat ID;getUpdates 清除 Webhook 并返回具体 API 错误;恢复 NOTIFY 分项开关与免密 reveal。
2026-06-07 19:45:07 +08:00
Nexus Agent 2e911dcca7 fix(frontend): 设置页恢复 IP 白名单订阅 URL 与解析预览
补回代理订阅配置 UI,修正 ip-allowlist 保存 payload 为 manual_ips + subscription_url。
2026-06-07 19:34:24 +08:00
Nexus Agent a5d5518054 fix(frontend): 设置页恢复 Telegram Chat ID 自动检测
Vue SPA 迁移遗漏 getUpdates 检测按钮;对接 GET /settings/telegram/chats,并修正 cron 脚本 bash 调用。
2026-06-07 19:31:13 +08:00
Nexus Agent e3fe161ae5 fix(deploy): 健康检查 PORT 缺省 8600
NEXUS_PUBLISH_PORT 未写入 .env.prod 时避免空端口误打 OpenResty 导致部署脚本误报失败。
2026-06-07 18:05:27 +08:00
Nexus Agent d0be2fdcf1 fix(deploy): SSH secrets 加载、Docker 运行时检测与外网探测脚本修复
使 deploy-production.sh 可读本机 secrets 并在非 docker 组用户下用 sudo 识别 1Panel 栈;修复 security_probe_external.sh 的 curl shift 误用与 WebSocket 自动 venv。
2026-06-07 17:34:27 +08:00
Nexus Agent 72d82d737b fix(security): BL-07 WS 403 澄清与 code-review 跟进加固
外网无 token WebSocket 的 HTTP 403 为应用层拒绝而非反代故障;边端 agent
在中心 401 时停止心跳重试;install 锁定路由级 404 且归档失败 fail-closed。
同步安全规范与 BL-06 一致,门控 7/7 PASS。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 12:12:49 +08:00
Nexus Agent eab35a35be fix(security): BL-06 强制 Agent per-server key 并合并心跳单次查库
移除 Agent 认证与安装链路的 global API_KEY 回退,收口 risk-acceptance 接受项 2;巡检补修心跳重复 get_server 并加 assert_called_once 回归测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 11:17:41 +08:00
Nexus Agent e5e2582743 fix(security): 外网攻击面加固 BL-01~05
收紧 PUBLIC 路径误匹配、强制 health/detail 与壁纸 sync 鉴权、默认关闭 OpenAPI;
安装锁定后 /api/install/* 统一 404;附探测脚本、测试与审计文档。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 01:45:10 +08:00
Nexus Agent 722917bfc9 fix(api): API 全量巡检 B1–B6 修复 server_ids 上限与 pending 审计
限制健康检查/推送/脚本执行的 server_ids 规模,补齐 pending 重试失败与删除审计,并附分批巡检报告。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 23:57:46 +08:00
Nexus Agent ac17f91106 docs(audit): 14 页 API 契约审计与凭据轮询 SSOT 收尾
记录凭据轮询/pending 契约对照与生产验证,更新功能指南与附录 J,并将 pending 相关类型集中到 api.ts。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 23:50:05 +08:00
Nexus Agent 9ee5c8a708 docs(project): nx 上帝菜单巡检 round1–10 归档总结
新增 nexus-god-menu-audit-summary.md 汇总巡检交付与日常速查,并链入文档索引。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 23:25:44 +08:00
Nexus Agent fb9dbcb11a docs(deploy): 同步 1Panel 运维 SSOT(round4–8 能力)
README 与知识文档补全 nx cron/备份/验收;卸载脚本提示改为 1Panel 容器名。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 23:23:27 +08:00
Nexus Agent 72e46cef32 fix(deploy): 升级后镜像验收与 verify 脚本修复
验收检查容器内 round6/7 特性;修复 host.docker.internal 引号;nx verify 与升级后自动验收。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 23:21:44 +08:00
Nexus Agent e5d482264d fix(install): 向导守护 UI 与升级后 cron 交互提示
Docker 完成页展示 Layer 3 cron 步骤与 host_deploy_root;TTY 下 nx update 可一键安装巡检/备份 crontab。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 23:20:23 +08:00
Nexus Agent c9e08799f9 fix(install): 守护配置对齐宿主机 cron 与验收检查
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
Docker 向导提示 nx cron/宿主机路径;裸机 cron 含 db_backup;验收脚本检查 cron 与 MySQL 备份能力。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 23:11:02 +08:00
Nexus Agent 89865ec690 fix(deploy): 部署脚本自动识别 Docker 与 Supervisor 运行时
upgrade/deploy-production 等分流到 nexus-1panel upgrade;升级完成后提示安装 nx cron。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 23:07:47 +08:00
Nexus Agent ba69bc3355 fix(deploy): git pull 后备份脚本不依赖可执行位
db_backup/backup_mysql 改为检测文件存在并用 bash 调用;install_ops_cron 一并 chmod 备份脚本。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 22:39:10 +08:00
Nexus Agent 49f159a8ce fix(deploy): install_ops_cron 自动安装 cron 包
生产 Azure 主机默认无 crontab,安装脚本现会 apt install cron 并启用服务。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 22:38:43 +08:00
Nexus Agent 4e1347ba45 fix(deploy): Docker 感知 MySQL 备份与 nx cron 安装
升级/定时备份通过 docker exec 1Panel MySQL 容器 dump,不再依赖宿主机 mysqldump;nx 菜单新增巡检与备份 crontab 安装。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 22:37:49 +08:00
Nexus Deploy 61b8d7d419 fix(deploy): health_monitor Docker 适配与巡检第三轮修补
Docker 生产用 docker restart;读 .env.prod 端口与容器 Telegram;已归档跳过全量热同步。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 22:25:27 +08:00
Nexus Deploy 954bdbd972 fix(deploy): 验收脚本已安装时输出正确通过文案
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 21:54:03 +08:00
Nexus Deploy 314bece418 fix(deploy): 验收脚本已安装时跳过 install API 检查
锁定后 /api/install/* 返回 403 属预期,不应判 FAIL。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 21:53:43 +08:00
Nexus Deploy 687e059a86 fix(deploy): 上帝菜单巡检第二轮 — 全新安装卷冲突与验收修正
FRESH 清除 nexus-state 旧锁;PENDING=1 时 entrypoint 不恢复旧 env;验收脚本已安装期望 404。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 21:53:03 +08:00
Nexus Deploy baacdb0252 fix(deploy): 已归档安装向导时 sync 脚本验收期望 404
避免 nx update 因 install.html 已移除而误报失败。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 21:31:06 +08:00
Nexus Deploy 58bdf820dc fix(install): 安装锁定后将 install.html 归档为 .bak
防止已安装环境继续提供安装向导静态页;entrypoint 与升级脚本避免容器重建后恢复。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 21:30:15 +08:00
Nexus Deploy dd66d99f2d fix(deploy): nx 上帝菜单巡检修补 1panel-network 与升级竞态
purge 阶段 compose 叠加 1panel overlay;菜单启动/重建与回滚后校验网络;install-auto 已安装改走 update。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 21:27:01 +08:00
Nexus Deploy 107b089b16 fix(1panel): 安装锁持久化与升级后 1panel-network 校验
防止 nx update 重建容器后丢失安装锁、未接入 1panel-network 导致 MySQL/Redis 不可达。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 20:52:13 +08:00
Nexus Deploy 1f5daebeed fix(deploy): nx update 在 set -u 下 local root 同行展开报错
拆分 nexus_publish_port/set_install_complete/sync_env_prod_to_volume 的 local 声明,
避免 env_file="$root/..." 在 root 赋值前展开导致 unbound variable。
2026-06-06 20:38:11 +08:00
Nexus Deploy 4ba45abf38 feat(servers): 凭据轮询登录与连接失败列表
- 密码/SSH密钥预设增加用户名;快速添加(IP)轮询全部预设尝试 SSH 登录
- 成功入 servers 列表;失败写入 pending_servers 并支持重试/删除
- 新增 credential_poller、try_ssh_login 与 add-by-ip/pending API
2026-06-06 20:08:48 +08:00
Nexus Deploy 5a56e5a8cb fix(1panel): 安装向导脱敏、配置一致性与 nx update 门控回滚
- /state 白名单脱敏;init-db db_port 修复;connection-check 后清空 redis_pass
- install.html sessionStorage token 与无 token 友好提示
- nx update: 镜像 import 门控、健康失败自动回滚、PENDING=0 回写、卷双写
- Redis 容器改名密码迁移;备份失败默认 WARN;update.sh 管道 ROOT 回退
2026-06-06 17:40:30 +08:00
Nexus Deploy 850038a3ec fix(install): 安装向导 Redis 密码不被 nx update/Compose 覆盖
Compose 不再注入 NEXUS_REDIS_URL;nx update 保留卷内带密码 URL;步骤 4 可从 install state 修复无密码 URL。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 11:34:26 +08:00
Nexus Deploy beb802802a chore: 添加 scripts/git-push.sh 与本地 Gitea secrets 约定
push 读取 deploy/nexus-1panel.secrets.sh(gitignore),clone/pull 仍可匿名。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 11:22:07 +08:00
Nexus Deploy 476d87c678 docs: Gitea 公共仓库无需凭据
更新 .cursorrules 与 linux-dev-paths,明确不配置/探测 GITEA token。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 11:13:07 +08:00
Nexus Deploy 49f88a2cd8 fix(install): Docker .env 持久化与 entrypoint 覆盖 Compose 占位符
向导 init-db 后立即同步 nexus-state 卷;entrypoint 跳过不完整 env 并在启动前 source /app/.env,避免缺 DATABASE_URL 与无密码 REDIS_URL 导致容器崩溃。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 11:08:22 +08:00
Nexus Deploy b11360015d fix(install): 1Panel Redis 无账号与步骤 4 URL 自愈
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
确立 Redis 仅密码认证(:pass@/default),隐藏误导性用户名字段;
connection-check 自动修正错误 NEXUS_REDIS_URL,并更新运维文档与验收脚本。
2026-06-06 07:51:52 +08:00
Nexus Deploy 3b2856f388 fix(install): auto-resolve 1Panel Redis auth (:pass@ / default)
Stop prefilling root; probe multiple Redis URL formats on credential
check and persist the URL that works for init-db.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 07:15:57 +08:00
Nexus Deploy 9d9fbc62f5 docs: add 1Panel operations knowledge SSOT and Redis test script
Consolidate install wizard, networking, URL formats, and troubleshooting
into docs/project/nexus-1panel-operations-knowledge.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 07:08:42 +08:00
Nexus Deploy 807f4c2448 fix(install): correct Redis URL auth for 1Panel root user
Password must use redis://:pass@ or redis://root:pass@ host; wizard adds
redis_user field defaulting to root on 1Panel Docker installs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 06:59:45 +08:00
Nexus Deploy da061d35db feat(install): require MySQL/Redis credential check at wizard step 3
Add test-credentials API and UI gate before init-db writes .env.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 06:47:22 +08:00
Nexus Deploy d0544c9bd2 fix(1panel): grant nexus@% for Docker MySQL 1045 on install
Add fix-1panel-mysql-grant.sh and clearer install wizard errors when
1Panel MySQL only allows nexus@localhost.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 06:19:19 +08:00
Nexus Deploy 3f5eacae60 feat(deploy): add 1Panel install wizard verify script
Server-side checklist for 1panel-network, container host sync, and
install API defaults before completing /app/install.html.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 06:05:15 +08:00
Nexus Deploy b25d0798f6 fix(1panel): sync MySQL/Redis container hosts on nx update
Upgrade was skipping 1panel-network detection; wizard now overrides stale host.docker.internal from volume/API defaults.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 05:47:43 +08:00
Nexus Deploy 6b1d23c7d7 fix(1panel): sync Redis URL to container name on 1panel-network
Update NEXUS_REDIS_URL when detecting 1Panel-redis-*; add Redis TCP precheck and connection errors matching MySQL.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 05:39:52 +08:00
Nexus Deploy 6951b8049f fix(1panel): join 1panel-network and connect MySQL/Redis by container name
Per 1Panel App Store architecture, bridge apps must use Docker DNS on 1panel-network instead of host.docker.internal.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 05:35:04 +08:00
Nexus Deploy da0424a268 fix(install): MySQL TCP probe and clearer host.docker.internal errors
Step 2 now detects whether host MySQL is listening; init-db fails fast with 1Panel setup guidance on error 2003.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 05:22:16 +08:00
Nexus Deploy 569263dddb fix(install): remove redundant Redis/MySQL host hints in wizard step 3
Docker defaults already prefill host.docker.internal; drop duplicate 1Panel install reminders from the form.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 05:08:55 +08:00
Nexus Deploy 646929ddff feat(deploy): register nx globally via install-nx-cli.sh
Centralize chmod and /usr/local/bin symlinks; install, upgrade, update, and every nx invocation refresh the global nx command.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 04:43:00 +08:00
Nexus Deploy 0a7c5ee617 fix(deploy): set executable bit on nx and refresh symlinks on update
Git tracked deploy/nx as 644 so /usr/local/bin/nx failed with Permission denied after pull; update.sh now chmods CLI scripts before upgrade.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 04:39:33 +08:00
Nexus Deploy 93400b7793 fix(install): split Redis install check (step 2) from connection test (step 4)
Step 2 only TCP-probes host Redis without blocking the wizard; step 4 runs /connection-check on MySQL and Redis from .env before creating the admin account.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 04:32:47 +08:00
Nexus Deploy a5aa5f81ad docs(deploy): merge nx god into unified menu and expand README
Consolidate install and ops into a single nx menu with one-click update via update.sh; document all curl install/update entry points in the root README.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 04:27:11 +08:00
Nexus Deploy 86c865e710 feat(deploy): add update.sh and always rebuild on upgrade
Upgrade no longer exits when Git is already up to date; it still rebuilds the Nexus image to pick up entrypoint changes. Add deploy/update.sh as the one-command entry point.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 04:16:15 +08:00
Nexus Deploy 1bd69077a2 fix(deploy): auto-remove legacy Compose MySQL containers on upgrade
Run uninstall-mysql-compose before install/upgrade and pass --remove-orphans so nexus-prod mysql orphans are stopped after the service is dropped from compose.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 03:32:00 +08:00
Nexus Deploy f37ebf8621 feat(docker): remove bundled MySQL from production Compose stack
Production installs now run only the Nexus container; MySQL and Redis are self-hosted on the machine or 1Panel, with install wizard defaults pointing at host.docker.internal.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 03:27:57 +08:00
Nexus Deploy e42dc77127 feat(deploy): add redis compose uninstall; require Redis in install wizard
Step 2 blocks until Redis connects; uninstall-redis-compose.sh removes legacy
nexus-prod-redis container without touching host/1Panel Redis.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 02:51:52 +08:00
Nexus Deploy 536d520744 fix(nx): resolve deploy dir when invoked via /usr/local/bin symlink
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
readlink -f fixes sourcing nexus-1panel.sh; register nexus-1panel global cmd.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 02:49:34 +08:00
Nexus Deploy 6276ea08b7 feat(docker): remove bundled Redis; use external host install
Compose stack is MySQL+Nexus only; wizard Redis check is non-blocking
and defaults to host.docker.internal for self-managed Redis.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 02:48:00 +08:00
Nexus Deploy c81fe3183f fix(deploy): sync install.py into container for Redis env-check hotfix
sync-install-wizard-to-container.sh now copies server/api/install.py and
restarts Nexus so Docker installs pick up redis:6379 without full rebuild.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 02:34:53 +08:00
Nexus Deploy 5e7c5beb43 fix(install): probe redis via docker service host in env-check
Install wizard no longer hardcodes 127.0.0.1:6379; Docker installs get
mysql/redis prefills and a passing Redis check inside the nexus container.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 02:31:11 +08:00
Nexus Deploy 9b7dba99ff fix(install): drop Gitea token warning for public repo installs
Public anonymous clone needs no secrets file; document token as optional
for private repos only.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 02:20:56 +08:00
Nexus Deploy 222703d733 fix(install): allow curl-pipe bash when set -u is enabled
BASH_SOURCE[0] is unset for stdin scripts; call main directly in
install-1panel-docker.sh and quick-install.sh.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 02:11:40 +08:00
Nexus Deploy d884ea00ca feat(install): add full-stack install-1panel-docker.sh for fresh servers
Replace the nx redirect stub with a real orchestrator that chains 1Panel
setup, Docker Compose checks, and install-nexus-fresh; document UI-only
OpenResty proxy rules in README-1panel.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 02:00:24 +08:00
Nexus Deploy 422dcae0ff fix(docker): bake install.html into prod image with build verify and sync script 2026-06-06 01:14:59 +08:00
Nexus Deploy 343def7e77 fix(docker): include install.html and wizard vendor assets in prod image 2026-06-06 00:36:03 +08:00
Nexus Deploy 83ce11f55c feat(install): per-host auto-generated secrets for Docker installs
Each fresh 1Panel/Docker install generates unique MYSQL and NEXUS keys
via scripts/generate_nexus_secrets.py; install wizard reuses compose env.
2026-06-06 00:25:57 +08:00
Nexus Deploy e613aecc8e docs: clarify Gitea web URL vs git clone URL in README 2026-06-06 00:19:04 +08:00
Nexus Deploy f71da47ccf docs: add root README for Gitea product page and install scripts 2026-06-06 00:14:34 +08:00
Nexus Deploy 1acac6439a feat(deploy): public repo quick-install via curl (1Panel style)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 00:11:43 +08:00
Nexus Deploy feda614c52 fix(deploy): register nx/nexus-fresh in PATH for root
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 23:58:37 +08:00
Nexus Deploy 6cef3d2942 fix(deploy): private Gitea raw 404 — use clone or token API download
Anonymous curl saved "Not found." causing "Not: command not found";
add download-install-fresh.sh, --skip-clone, and script self-check.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 23:52:48 +08:00
Nexus Deploy 00f44032ff feat(deploy): add 1Panel-style fresh install script for empty DB
install-nexus-fresh.sh with phased checks, install wizard URL, and nx god
menu hints; wired into nx install-fresh and install menu option 6.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 23:44:38 +08:00
Nexus Deploy 8ddcf2ad0d feat(deploy): add nx interactive installer and god ops menu
Single entry deploy/nx for install wizard, post-install restart/upgrade/logs,
and install-auto for non-interactive 2c8g setup on 1Panel Docker hosts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 23:41:04 +08:00
Nexus Deploy 65e4478b4b feat(deploy): load credentials from nexus-1panel.secrets.sh
Keep secrets out of tracked scripts; support git add -f secrets file,
git remote token parsing, and port/firewall checks for one-click install.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 23:37:37 +08:00
Nexus Deploy 2f830424a4 feat(deploy): unified 1Panel script with profiles and port checks
Merge install/upgrade into nexus-1panel.sh with 1c4g/2c8g/4c16g resource
profiles; preflight Gitea and firewall hints; post-install HTTPS health probe.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 22:59:47 +08:00
Nexus Deploy 7b33b745a0 feat(deploy): add 1Panel Docker one-click install script
Automates clone, docker/.env.prod, compose up, and health check for 2C8G prod stack.
2026-06-05 22:52:44 +08:00
Nexus Deploy bfda70bbe7 chore(docker): tune production stack for 2C8G hosts
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
Add mysql-prod-2c8g.cnf, container mem/cpu limits, Redis maxmemory,
and default Nexus pool 30/20 for 8GiB 1Panel deployments.
2026-06-05 22:44:06 +08:00
Nexus Deploy 2f67e9e2b9 docs: add 1Panel Docker production deploy stack
Add Dockerfile.prod, docker-compose.prod.yml, OpenResty example,
and migration plan for Baota to 1Panel + Compose deployment.
2026-06-05 22:40:04 +08:00
Nexus Deploy 811eb97fbf chore: Ubuntu local dev scripts, deploy gate, and audit changelogs
Rename WSL helpers to Linux-native scripts, expose Redis in docker-compose,
prefer .venv tools in pre_deploy_check, and record 2026-06-04 audit waves.
2026-06-04 23:02:21 +08:00
Nexus Deploy 0100ab2582 docs: consolidate archive, SSOT guide, and agent entry stubs
Move line-walk/delta/phase2/frontend audits and legacy memory into
docs/archive/, add functional development SSOT, thin AGENTS/CLAUDE stubs,
and regenerate changelog/audit indexes via consolidate_docs.py.
2026-06-04 23:01:03 +08:00
Nexus Deploy b866e944ab fix: Code Review P0/P1 batches 1-3 and single-operator risk acceptance
Apply sync/install/auth/schedule/retry/agent/settings fixes from full
code review; document accepted WS and Agent legacy risks for solo ops.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 15:57:49 +08:00
Nexus Deploy a2ae74d582 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
2026-06-04 14:01:14 +08:00
Your Name 9ac6a2d27f fix(docker): track docker/.env.example despite .env.* gitignore
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 00:59:00 +08:00
Your Name 3cec226b89 feat(docker): add Compose stack for MySQL, Redis and Nexus API
- Dockerfile with healthcheck and entrypoint for deps + .env persistence
- generate_env.py for local secrets; design docs and changelog

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 00:58:46 +08:00
Your Name 51948469c2 build(frontend): track Vite output in Git for Gitea deploy
- Stop ignoring web/app/index.html and web/app/assets/
- Bundle index-DBKIQT7H.js from current vite build (~101MB assets)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 00:52:44 +08:00
Your Name 4b5602a719 feat(files): fix editor freeze, EOL gates, server form and session UX
- FilesPage storeToRefs + remotePath coercion; Monaco preload; list action buttons
- text_io for UTF-8/LF; install/sync EOL; check_shell_eol + check_text_eol gates
- ServerFormDialog, hash login redirect, AppAuth keep-session; design docs and audits

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 00:42:55 +08:00
Your Name 9c9c763e9c fix(credentials): password preset is name+password only, no username field
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 16:08:50 +08:00
Your Name 0cc1cc14a4 fix(terminal): restore app-bar top inset so tab bar and xterm are not clipped
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 16:02:17 +08:00
Your Name f078d0e03b feat(push): localize sync/rsync error messages to Chinese in history and progress
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 15:18:01 +08:00
Your Name 524dabcf7f refactor(editor): P0 — per-tab model, EOL preservation, conflict detection (409)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 14:46:49 +08:00
Your Name 0ccd1ba934 fix(files): use proxyRefs for inject context unwrapping
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 08:25:25 +08:00
Your Name a6673157a5 fix(files): unwrap injected refs via reactive context for v-model
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 08:23:03 +08:00
Your Name 78798e3630 feat(push): refactor B0-B6, /ws/sync channel, staging cleanup
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 08:14:09 +08:00
Your Name 68fdcbae0a refactor(terminal): split TerminalPage into composables and components (T1-T6)
Extract session/WS/xterm logic with markRaw native store, per-session disconnect overlay, shared server picker, and cmd bar via ShellHighlightField expose.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 06:44:56 +08:00
Your Name 181560eca2 fix(ui): align servers stat cards with dashboard; improve files browse errors
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 06:08:02 +08:00
Your Name c6f1d73ff5 feat(files): P4 GET browse with ETag and P5 zero-flicker loading
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 05:59:55 +08:00
Your Name 6cb591212d refactor(files): P2 split UI into components with page context inject
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 05:14:13 +08:00
Your Name 7cbddf5d48 refactor(files): P1 split composables, Pinia store, directory cache
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 05:05:26 +08:00
Your Name cb738a8e9a fix: file manager double browse from v-select init and stale chunks
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 04:36:46 +08:00
Your Name 5393252036 fix: eliminate double browse requests in file manager
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 04:29:11 +08:00
Your Name 3bc2843642 fix: browse序号保护防止token刷新重试导致的双刷新 2026-06-02 03:48:01 +08:00
Your Name 1d6d26d612 fix: 加载中隐藏空状态提示,消除进目录时的视觉双刷新 2026-06-02 03:19:27 +08:00
Your Name c65e3ea2a7 fix: 目录双击导致双刷新(e.detail守卫),横幅仅在实际权限错误时显示
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 03:07:51 +08:00
Your Name 67dac0168f feat: 文件管理器复制粘贴守卫、2MB编辑限制、一键配置sudoers、编辑器外观重构
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 02:20:22 +08:00
Your Name 57d67c494b fix: 警告横幅关闭后进子目录不再重新出现,文案简化
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 01:51:13 +08:00
Your Name 928e3c5fdb fix: 登录白名单 IP 绕过防暴破锁定
白名单内 IP 跳过失败次数锁定;失败仅审计不计数;成功登录清除该用户历史失败记录。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 01:37:22 +08:00
Your Name 832e34fd98 fix: 修复 6 个 MEDIUM 级 Bug (锁定/重试/调度/分页/SSH池/token_version)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 01:29:06 +08:00
Your Name 19cfb7caaa fix: 系统全扫描修复 7 个 HIGH 级 Bug
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 01:22:00 +08:00
Your Name ea3046f635 fix: unix_ls parse_ls_la_line 兼容 long-iso 8字段格式
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 01:09:39 +08:00
Your Name 39a8cf16cf fix(ui): 调度与重试队列路由导航及前端资源一致性
侧栏可滚动与显式导航;懒加载失败提示;getList 兼容分页响应;生产 prune 清理 1065 个过期 chunk。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 22:36:08 +08:00
Your Name b62d4f6f9f fix(audit): 审计日志操作与目标类型全面中文化
补全 action/target 映射表与词段兜底;审计页筛选下拉使用中文标签。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 22:18:42 +08:00
Your Name 44c19f329a fix(terminal): Ctrl+D 快捷键文案改为退出
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 22:13:54 +08:00
Your Name 9e924562d2 fix(terminal): 右下角快捷键与右键菜单中文化并放大
快捷键区改为中文大按钮;工具栏字号/回滚/全屏中文化;右键菜单加大并在命令栏拦截英文系统菜单。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 22:00:49 +08:00
Your Name e3eb0e65a5 fix(ui): 仪表盘/审计/调度/重试修复与终端快捷命令改版
统一终端页与全局侧栏菜单;快捷命令仅在终端执行,增删改排序迁入系统设置;命令输入区加高与 Enter/Shift+Enter 行为;后端自定义命令优先与 reorder API。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 21:56:07 +08:00
Your Name fc056059f9 docs: production deploy changelog for files/POSIX release
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 20:45:14 +08:00
Your Name bd6467dff9 docs: master plan execution progress and changelog
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 19:58:50 +08:00
Your Name e704b01093 fix(deploy): restore pre_deploy_check.sh content
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 19:57:06 +08:00
Your Name 246070e599 fix(deploy): pre_deploy gate uses repo root and newest audit
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 19:56:36 +08:00
Your Name d93c518a14 feat(files): POSIX paths, elevation, recursive chmod, access hints and docs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 19:54:18 +08:00
Your Name 1cfac23c01 feat: terminal ANSI colors and enhanced shell syntax highlighting
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:49:27 +08:00
Your Name 799140f6c4 feat: terminal SPA layout, route sync, and WebSocket URL helper
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:41:59 +08:00
Your Name 8935081ac5 feat: full-site acceptance automation and health/detail fix
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:22:42 +08:00
Your Name ab431028c1 fix: 服务器页批量选择绑定 Vuetify4 modelValue
v-model:selected 无法同步勾选状态;生产已部署 ServersPage-CGp8DUVa.js 并验证「已选择 2 台服务器」。
2026-06-01 14:54:39 +08:00
Your Name cc2dad7f31 docs: 生产浏览器验收 — SPA/WebSSH/health_monitor
详见 docs/reports/2026-06-01-browser-verification.md;批量条与 Telegram/Push/kill 留维护窗口或人工确认。
2026-06-01 14:20:45 +08:00
Your Name 24b4439b2b docs: 单负责人执行计划 — handoff 对齐、生产验收、prune 加固
计划见 docs/design/plans/2026-06-01-single-owner-execution.md;生产 health/app/Redis/MySQL 已通过。
2026-06-01 14:09:17 +08:00
Your Name b8af1fc418 chore: 移除 superpowers 工作流 Skill,改为单 Agent 协作
停用 .cursor/skills 与虚拟部门分派,新增 no-department-agents 规则并更新文档索引与验收标准。
2026-06-01 14:04:50 +08:00
Your Name b77a314b36 chore: remove virtual department agent docs (team/skills)
Delete docs/skills/, docs/team/, and TEAM_ROLES.md. Update docs/README and mem_key_files. Standards and .cursor/skills workflows unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 13:52:55 +08:00
Your Name d519113734 fix: Gate3 health plain text, refresh empty body, prune underscore chunks
test_api: assert /health returns plain text ok instead of JSON parse.

auth: RefreshRequest.refresh_token optional so SPA POST {} is not 422.

prune: allow leading underscore in asset names to avoid deleting _plugin-vue_export-helper.

Add run_test_api_on_server.sh and deployment follow-up changelog.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 13:41:28 +08:00
Your Name 65c77155d8 fix: 修复前端 assets 清理脚本以支持 Rolldown 产物
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 12:31:55 +08:00
Your Name 2deb89b49c deploy: 前端部署后自动清理孤儿 assets + 冒烟脚本
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 12:29:57 +08:00
Your Name b76295aaec docs: 记录 2026-06-01 生产部署验证结果
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 12:23:44 +08:00
Your Name 8311821590 fix: 全量修复批量选择、JWT 60min、script-callback 认证与心跳数据流
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 12:17:47 +08:00
Your Name 73fdcafb81 docs: 完整交接文件 (15章节 + Cursor提示词 + 部署问题)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 11:58:11 +08:00
Your Name 91d0d4104e docs: 添加 .cursorrules 供 Cursor 接管
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 11:54:11 +08:00
Your Name 7fa4bd6dff docs: Cursor交接文件 2026-06-01
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 11:49:12 +08:00
Your Name a20cefcfbd docs: 审计+changelog 全站bug审查修复
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 11:40:23 +08:00
Your Name 6183047fd6 fix: 全站 bug 审查修复 (前端9项 + 后端5项)
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
前端修复:
- P0: App.vue http 未导入导致全局搜索崩溃
- P1: TOTP 登录流程断裂 (TotpRequiredError 非 ApiError 子类)
- P1: ServersPage 编辑服务器 domain 被端口字符串污染
- P1: PushPage Set[0] 无效 → values().next().value
- P1: SettingsPage API 返回字符串未转数字
- P2: api/index.ts getList 重复定义移除
- P2: LoginPage 锁定倒计时 now ref + watch 更新
- P2: TerminalPage 路径验证正则放松 (允许空格/中文)
- P2: useWebSocket CONNECTING/CLOSING 状态关闭旧连接

后端修复:
- P1: websocket.py redis.keys() → scan_iter() 避免阻塞
- P1: auth_service.py TTL 仅在 key 无 TTL 时设置
- P1: servers.py 批量操作加 asyncio.Lock 避免并发 session commit
- P2: settings.py asyncio.get_event_loop() → get_running_loop()
- P2: settings.py datetime.utcfromtimestamp() → datetime.fromtimestamp(tz=utc)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 11:24:55 +08:00
Your Name df2c188f5d docs: Cursor 交接文件
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 23:32:10 +08:00
Your Name 2d00d6a8eb fix: 登录页居中布局 + 删除Logo区域
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 23:30:12 +08:00
Your Name 080c1158d4 docs: 审计文档 + changelog (TerminalPage迭代)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 23:24:53 +08:00
Your Name facc1e8ae4 feat: TerminalPage全面迭代 + 快捷命令MySQL持久化
P0: Ctrl+L清屏、命令历史恢复、重连per-session、onopen不忽略、ping per-session
P1: 空catch处理、termRefs用session.id、错误透传、剪贴板权限、webssh-token错误
P2: 全选菜单、指数退避重连、快捷命令编辑、uptime per-session
新功能: quick_commands MySQL表 + CRUD API、Shell语法高亮输入栏
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 23:12:18 +08:00
Your Name 1ecd8e4542 fix: move wallpaper routes before /{key} catch-all to fix 404
Bing wallpaper endpoint (/bing-wallpapers) was returning 404 from HTTP
because FastAPI matched it against the /{key} catch-all route at position
2 before the specific route at position 13 ever got reached.

- Moved wallpaper routes (bing-wallpapers, bing-wallpaper) and helper code
  before the /{key} wildcard route
- Removed dead guard code in get_setting() that was never effective —
  FastAPI route matching happens before the handler body executes
- Removed duplicate wallpaper code block at end of file

Root cause: FastAPI matches routes in registration order. /{key} at
position 2 matched 'bing-wallpapers' as the key parameter, returning
'Setting not found' instead of routing to the wallpaper endpoint.
2026-05-31 22:00:33 +08:00
Your Name c6485168b6 fix: bing-wallpapers route was being caught by /{key} catch-all
FastAPI matches routes in order: /{key} registered before bing-wallpapers
because routes are sorted by path. /{key} matches 'bing-wallpapers' as a
key name, returning 404 'Setting not found' instead of hitting the actual
bing_wallpapers handler.

Fixed by adding a guard in get_setting() to skip these fixed path names,
and crucially by ensuring bing-wallpaper(s) routes are registered BEFORE
the /{key} catch-all in the router.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 20:59:26 +08:00
Your Name e9af02dd22 fix: add bing-wallpapers (plural) to PUBLIC_PREFIXES for JWT bypass 2026-05-31 20:53:49 +08:00
Your Name c9baecfdfa feat: Bing wallpaper slideshow — 12 images, hourly rotation, weekly cleanup
- Frontend: fullscreen wallpaper background with 1.5s crossfade transition,
  login card centered with glass effect, images rotate every hour
- Backend: new /api/settings/bing-wallpapers endpoint fetches 8 recent images
  from Bing HPImageArchive, caches to web/app/wallpapers/, returns all URLs
- Auto-cleanup: removes files older than 7 days, keeps max 12 newest
- Old /bing-wallpaper endpoint kept for backward compatibility

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 20:51:50 +08:00
Your Name 3d5870b404 fix: correct wallpaper dir to 3 levels up (server/api/settings.py -> Nexus/web/app/wallpapers)
Path breakdown: __file__ is server/api/settings.py
  .parent → server/api/
  .parent.parent → server/
  .parent.parent.parent → Nexus/ (repo root)
  Then append web/app/wallpapers

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 20:38:35 +08:00
Your Name afd32118b2 fix: correct wallpaper directory path in settings.py
- Path(__file__) is server/api/settings.py, so:
  .parent → api/  .parent → server/  .parent → Nexus/ (ROOT)
  .parent → (above ROOT — wrong!)
- Changed from .parent.parent.parent to .parent.parent.parent.parent
  to reach the project root, then into web/app/wallpapers/
- Moved _WALLPAPER_DIR.mkdir into the async function body

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 20:34:27 +08:00
Your Name d0308aaef6 fix: allow /app/wallpapers/ through AppAuthMiddleware
- Added /app/wallpapers/ to _APP_PUBLIC_PREFIXES so cached wallpaper images
  are served without requiring login auth
- Wallpaper files are now saved to web/app/wallpapers/ by bing_wallpaper() endpoint

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 20:30:46 +08:00
Your Name 04dda2c419 fix: download Bing wallpaper to server disk, auto-clean weekly
- Changed bing-wallpaper endpoint from URL proxy to image downloader
- Saves wallpaper to web/app/wallpapers/YYYY-MM-DD.jpg
- Auto-deletes wallpapers older than 7 days on each request
- Falls back to yesterday's cached file if download fails
- Added Path import for filesystem access

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 20:23:55 +08:00
Your Name e084d90c61 fix: add Bing wallpaper proxy endpoint, make it public (no JWT required)
- Added GET /api/settings/bing-wallpaper — proxies cn.bing.com HPImageArchive
  to avoid CORS issues, returns {url} for the daily wallpaper
- Added /api/settings/bing-wallpaper to PUBLIC_PREFIXES in auth_jwt.py
  so the login page can fetch it without authentication
- Login page now fetches wallpaper via backend proxy instead of direct CORS-blocked fetch

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 20:12:09 +08:00
Your Name 24aa8494e5 security: bind uvicorn to 127.0.0.1 instead of 0.0.0.0
The install wizard wrote '--host 0.0.0.0' which exposed the raw uvicorn
port 8600 on the public IP, bypassing Nginx HTTPS/TLS/WAF/middleware.
Changed to 127.0.0.1 — only local Nginx can reach the backend.

Also applied to running server via supervisor config fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:04:28 +08:00
Your Name 5c7775c10c chore: remove old Tailwind HTML pages from git
Vuetify SPA is now the sole frontend entry point at /app/.
Old pages (alerts/servers/scripts/...html) replaced by Vue router.
Keep install.html for setup wizard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 15:46:29 +08:00
Your Name 7a6479dbfa fix: remove stale login.html from public paths, clean up prefixes
Old Tailwind HTML pages removed from server — Vuetify SPA is now
the sole entry at /app/. Only install.html remains for setup wizard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 15:44:56 +08:00
Your Name 0fdae981cf fix: add ttf/eot/otf to static asset whitelist in AppAuthMiddleware
Vuetify/Monaco fonts return 404 without these extensions whitelisted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 15:42:43 +08:00
Your Name 3419ab8a09 chore: remove obsolete design department agent docs
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 15:37:57 +08:00
Your Name d512460dc9 fix: frontend type definitions match actual backend API responses
- AlertsPage: template fields alert_type/value/is_recovery (was type/message/recovered)
- CommandsPage: remove non-existent is_dangerous/duration/admin_name; use admin_username
- ScriptsPage: ExecHistoryItem matches /scripts/executions response shape
- types/api.ts: CommandLogItem + SshSessionItem + ScriptExecItem aligned to backend
- Remove unused PaginatedResponse imports from CommandsPage/ScriptsPage

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 15:37:41 +08:00
Your Name 0f02e047cc docs: update deployment instructions for Vuetify SPA
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
- Split backend/frontend deploy steps
- Add deploy-frontend.sh one-liner reference
- Document AppAuthMiddleware 404 and stale HTML troubleshooting
- Remove obsolete dist copy instructions

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 13:25:33 +08:00
Your Name 35583115ff fix: Vite outputs directly to web/app/ — no dist copy needed
- vite.config.mts: outDir changed from web/app/dist to web/app
- .gitignore: ignore web/app/index.html + web/app/assets/ (build artifacts)
- deploy/deploy-frontend.sh: automated build+deploy+verify script

Deployment is now: bash deploy/deploy-frontend.sh

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 13:24:22 +08:00
Your Name f745714530 fix: allow Vuetify SPA index.html through AppAuthMiddleware
The new Vuetify SPA uses hash routing — all pages served from single
index.html. The old middleware only whitelisted individual HTML page
paths (login.html etc.), blocking the SPA entry point with 404.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 13:13:13 +08:00
Your Name 0d056a45e9 feat: Vuetify 4 frontend — complete rebuild with audit fix
Vue 3 + Vuetify 4 + TypeScript + Vue Router 4 + Pinia frontend,
replacing the old Tailwind+Alpine.js static pages.

Infrastructure (8 new files):
- composables/useSnackbar.ts — centralized notifications
- composables/useServerList.ts — server dropdown data
- composables/useServerPagination.ts — paginated server table
- types/api.ts — 14 typed API response interfaces
- types/global.d.ts — Window augmentation
- utils/status.ts — server status display helpers
- utils/validation.ts — 8 form validation rules
- components/MonacoEditor.vue — fullscreen code editor

14 pages rebuilt:
- LoginPage, DashboardPage, ServersPage, TerminalPage
- FilesPage, PushPage, ScriptsPage, CredentialsPage
- SchedulesPage, RetriesPage, CommandsPage
- AlertsPage, AuditPage, SettingsPage

Critical fixes (from audit):
- Files upload JWT auth (was missing Authorization header)
- API Key reveal password verification dialog
- 6 silent catch blocks → snackbar error feedback
- 33 as any → 0 (typed interfaces + global.d.ts)
- Dashboard: alerts/summary/categories/audit data loading
- WebSocket reconnect timer cleanup + auth failure handling
- Terminal ResizeObserver leak fix
- PushPage timer cleanup on unmount
- FilesPage path double-slash fix (joinPath helper)
- Filter changes reset pagination to page 1

Features added:
- Servers: batch operations, CSV export/import, detail panel
- Push: sync modes, ZIP upload, WebSocket real-time progress, cancel
- Scripts: quick execute panel, execution status tracking
- Files: Monaco editor, context menu, batch delete, rename, chmod
- Terminal: right-click menu, server sidebar, tab persistence
- Settings: batch save, TOTP manual key, password TOTP verification
- Dashboard: category distribution, recent audit, WS refresh

Quality:
- vue-tsc --noEmit zero errors
- vite build passes (1613 modules)
- Formal 8-step security audit passed (0 FINDING)
- .gitignore: dist/ → **/dist/ to cover web/app/dist/

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 12:56:12 +08:00
Your Name 0ded4a28ec fix: 主题系统改用CSS变量 — dark:前缀在浏览器CDN不生效
根因: Tailwind v4 浏览器CDN(tailwindcss-browser.js)不处理外部CSS的
@custom-variant指令, dark:前缀规则完全不生成

方案: 改用CSS变量 + :root.dark {} 选择器
- theme.css: :root 定义亮色值, :root.dark 定义暗色值
- 15个HTML页面: 恢复 bg-[var(--bg-page)] 等CSS变量class
- 移除所有 dark: 前缀class(CSS变量自动适配)
- theme-init.js: 同步读localStorage设.dark class(FOUC防护)
- layout.js: toggleTheme 切换 .dark class + localStorage + MySQL

优势: CSS变量方案不依赖Tailwind dark:变体, 100%兼容浏览器CDN
2026-05-31 04:18:55 +08:00
Your Name 5ac4a5c7cf fix: theme.css 加 @import tailwindcss 使 dark: 变体生效
Tailwind v4 官方文档确认:
@import 'tailwindcss';
@custom-variant dark (&:where(.dark, .dark *));

浏览器CDN build需要 @import 来处理 @custom-variant 指令
2026-05-31 04:13:26 +08:00
Your Name 9c5ebdfdbd feat: 全站主题完全重做 — CSS变量→Tailwind dark: 前缀
设计依据: HyperUI/Flowbite/daisyUI/Meraki UI 深度调研
配色方案: HyperUI 全局色值映射 (gray-* 色系)

改动:
- theme.css: 重写为 @custom-variant dark 配置
- theme-init.js: FOUC防护 — 同步读localStorage设.dark class
- layout.js: 主题系统改用 .dark class + localStorage
- 15个HTML页面: 649处CSS变量class替换为dark:前缀
  bg-[var(--bg-page)] → bg-gray-50 dark:bg-gray-950
  bg-[var(--bg-card)] → bg-white dark:bg-gray-900
  bg-[var(--bg-surface)] → bg-gray-100 dark:bg-gray-800
  border-[var(--border)] → border-gray-200 dark:border-gray-700
  text-[var(--text-primary)] → text-gray-900 dark:text-white
  等9种映射
- design spec: docs/design/specs/2026-05-31-ui-redesign-design.md

修复: FOUC(切换主题时闪暗色)已解决
2026-05-31 04:11:17 +08:00
Your Name c457cb4058 feat: dark mode theme system with synchronous FOUC prevention
- Add vendor/theme-init.js for synchronous dark mode init in <head>
- Update all 13 frontend pages with dark: variant classes
- Dark mode as default, light mode via localStorage toggle
- Remove redundant theme.css rules in favor of Tailwind dark: classes
- Add settings-dark-theme.png reference screenshot

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 03:54:30 +08:00
Your Name 8b2263916c feat: design department agents integrate Tailwind ecosystem knowledge (9 sites)
Deep-learned 9 Tailwind CSS ecosystem sites and injected into all design
department agents via knowledge graph references:

- HyperUI (350+ components, Tailwind v4, Alpine.js)  best match
- Meraki UI (198 components, Application UI, RTL)
- Flowbite (56+ components, data-attr driven, Figma)
- daisyUI (65 semantic components, 35 themes)
- UIBak (200+ components, Alpine.js native)
- Headless UI (a11y benchmark, data-* patterns)
- Tailblocks (63 landing page blocks)
- Kutty (plugin architecture reference, unmaintained)
- WindyToolbox (resource directory)

Updated 10 files: design-department, ui-designer, ux-architect,
frontend-designer, color-curator, inspiration-analyzer, trend-researcher,
moodboard-creator, design-wizard, team roles.

Knowledge graph IDs for cross-session reference:
  tailwind-css-9 (overview)
  tailwind-css-9/hyperui-nexus-tailwind
  tailwind-css-9/meraki-ui-rtl-tailwind
  tailwind-css-9/flowbite-tailwind-bootstrap
  tailwind-css-9/daisyui-tailwind
  tailwind-css-9/uibak-alpinejs
  tailwind-css-9/headless-ui-tailwind-labs
  tailwind-css-9/tailblocks-landing-page
  tailwind-css-9/kutty-tailwind-plugin-alpinejs
  tailwind-css-9/windytoolbox-tailwind

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 03:53:40 +08:00
Your Name 293a0d64fe feat: 全站亮色/暗色主题切换
实现方案: CSS自定义属性 + Tailwind arbitrary values

新增文件:
- web/app/vendor/theme.css — CSS变量定义(亮色+暗色)

修改文件 (15个HTML + 2个JS + 2个Python):
- layout.js — 侧边栏🌙主题切换按钮 + loadTheme/toggleTheme
- settings.py — MUTABLE_KEYS加theme/editor_theme
- 15个HTML页面 — bg-slate-*替换为bg-[var(--bg-*)] + 引入theme.css

替换统计:
- index.html: 48处
- servers.html: 183处
- files.html: 94处
- push.html: 157处
- scripts.html: 83处
- credentials.html: 100处
- schedules.html: 59处
- retries.html: 40处
- commands.html: 57处
- alerts.html: 47处
- audit.html: 32处
- settings.html: 125处
- terminal.html: 89处
- install.html: 72处
- login.html: 14处
总计: 1199处替换

功能:
- 侧边栏底部🌙/☀️按钮切换亮色/暗色
- 主题偏好保存到MySQL(settings表)
- 页面加载时自动恢复主题
- Monaco编辑器主题独立控制(⚙️菜单)
2026-05-31 02:55:45 +08:00
Your Name 94fbdf7b2d refactor: 主题切换移到⚙️设置下拉菜单
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
- 移除标题栏独立主题按钮(🌙/☀️)
- 新增⚙️设置按钮 → 下拉菜单
- 菜单项: 「☀️ 亮色主题」/「🌙 暗色主题」动态文字
- 点击菜单外自动关闭
- 菜单样式: bg-slate-800, rounded, shadow, 最小宽度200px
2026-05-31 02:42:24 +08:00
Your Name e65c9993a2 feat: 编辑器亮色/暗色主题切换 + 保存到MySQL
- 标题栏🌙/☀️按钮切换 Monaco 主题
- 'vs'(亮色) / 'vs-dark'(暗色)
- 切换后立即应用到所有打开的标签页
- 通过 PUT /api/settings/editor_theme 保存到MySQL
- 页面加载时自动读取保存的主题偏好
- settings.py MUTABLE_KEYS 白名单加 editor_theme
2026-05-31 02:39:21 +08:00
Your Name ae47eb42de fix: 双击标题栏最大化 + Monaco用官方默认主题
1. 双击编辑器标题栏空白区域 → 切换最大化/还原
2. Monaco移除theme:'vs-dark'和fontFamily覆盖, 使用官方默认配置
3. 编辑器外观与 https://microsoft.github.io/monaco-editor 一致
2026-05-31 02:35:52 +08:00
Your Name c570801119 fix: 编辑器拖拽修复 — 扩大拖拽区域+退出最大化+边界限制
问题:
- 标签栏整体被排除导致可拖拽区域几乎为零
- 最大化/全屏后拖拽不工作
- 没有拖拽视觉提示

修复:
- 添加⠿拖拽把手图标(cursor-grab)
- 只排除button/input/select, 标签栏空白处可拖拽
- 拖拽时自动退出最大化/全屏模式
- 边界限制(不拖出屏幕)
- 拖拽结束触发Monaco重新layout
2026-05-31 02:31:56 +08:00
Your Name cba8b3d8f6 feat: 浮动编辑器 — 拖拽+缩放+全屏+文件树
功能:
- 拖拽: 按住标题栏拖动编辑器面板
- 缩放: 右下角拖拽调整大小
- 最大化(): 占满屏幕留边距
- 全屏(⤢): 完全覆盖页面
- 最小化(—): 收到底部标签栏
- 关闭(✕): 关闭全部标签
- 文件树(🌳): 左侧可折叠文件树, 点击文件打开/目录导航
- 文件树同步: 浏览新目录时自动更新

布局:
┌──────────────────────────────────┐
│ [🌳] [📄a.conf×][📄b.py×]  [][⤢][—][✕] │ ← 可拖拽
├────────┬─────────────────────────┤
│ 📂 ..  │                         │
│ 📁 dir │    Monaco Editor        │
│ 📄 a   │                         │
│ 📄 b   │                         │
│        │ Ln1,Col1|ini|UTF-8|file │
└────────┴─────────────────────────┘  ← 右下角可缩放
2026-05-31 02:26:25 +08:00
Your Name caef4a216b fix: Monaco配置完全对齐官方默认值
对齐项目:
- cursorBlinking: smooth→blink(官方默认)
- 移除: cursorSmoothCaretAnimation, cursorWidth(非官方默认)
- 移除: lineHeight, padding(非官方默认)
- 移除: renderLineHighlight, smoothScrolling(非官方默认)
- 移除: scrollbar自定义尺寸(使用官方默认)
- scrollBeyondLastLine: false→true(官方默认)
- renderWhitespace: selection→none(官方默认)
- 添加: fontFamily(Cascadia Code/Fira Code/JetBrains Mono)
- 移除: quickSuggestions, insertSpaces, foldingHighlight, minimap side/scale
- 保留vs-dark主题(匹配Nexus暗色UI)
2026-05-31 02:20:04 +08:00
Your Name 507b957b4e refactor: 编辑器改为浮动面板 + Monaco配置对齐官方
布局:
- 全屏覆盖 → 右下角浮动面板(70vw×65vh)
- 文件列表始终可见, 编辑器不遮挡
- 最大化/还原按钮()
- 最小化到底部标签栏
- 移除文件树(文件列表已在背景可见)

Monaco配置对齐官方demo:
- fontSize 13→14, lineHeight 20
- wordWrap on→off(水平滚动)
- 新增: folding, matchBrackets, cursorBlinking smooth
- 新增: cursorSmoothCaretAnimation, smoothScrolling
- 新增: padding, renderLineHighlight all, renderWhitespace selection
- 新增: suggestOnTriggerCharacters, quickSuggestions
- 新增: links, colorDecorators
- 新增: tabSize 4
2026-05-31 02:14:56 +08:00
Your Name c70424a224 docs: Monaco IDE编辑器 changelog + 审计报告 2026-05-31 02:08:31 +08:00
Your Name e8c1acba9f fix: 审计8个FINDING修复 (IDE编辑器+终端)
- H-01 [MEDIUM] esc() 加引号转义('"\') 防止onclick XSS
- H-02 [MEDIUM] initialPath 路径白名单校验(仅允许字母数字/.-_)
- H-03 [LOW] doNewFile 文件名白名单正则(替代简单的includes('/'))
- H-08 [LOW] Monaco CDN SRI — 已知限制, 版本锁定@0.45.0
- H-13 [MEDIUM] showModal 移除已删除的modalTextarea引用
- H-20 [LOW] initialPath仅首次连接cd, 不影响恢复的标签
- H-26 [MEDIUM] esc()跨文件一致性 — files.html对齐terminal.html
- H-30 [LOW] esc()注释更新
2026-05-31 02:06:42 +08:00
Your Name 34786737b4 fix: IDE编辑器语法错误 — 多余花括号 2026-05-31 01:56:15 +08:00
Your Name be633f3e58 feat: IDE编辑器重构 — 多标签+文件树+最小化+状态栏
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
- 全屏IDE面板替代模态框编辑器
- 多文件标签页: 独立Monaco实例, 切换保持状态
- 左侧文件树: 基于当前目录, 点击文件打开, 点击目录导航
- 最小化/恢复: 底部最小化栏显示所有打开的文件标签
- 状态栏: 光标位置+语言+编码+未保存指示器
- Ctrl+S保存 + 未保存关闭确认
- 标签栏: 点击切换, ×关闭, amber色未保存标记
- 文件树同步: 浏览新目录时自动更新文件树
- 模态框保留用于非编辑操作(重命名/删除/新建等)
2026-05-31 01:50:34 +08:00
Your Name 00a3309005 feat: 服务器列表加「文件」按钮直达文件管理
- servers.html: renderActions 加「文件」链接(琥珀色) → /app/files.html?server_id=X
- files.html: 支持 server_id URL参数, 自动选中服务器并浏览 /www/wwwroot
2026-05-31 01:33:52 +08:00
Your Name 3d15200cfa chore: 移除Monaco debug日志 2026-05-31 01:28:57 +08:00
Your Name d8aca5bf18 fix: Monaco预加载移入files.html(解决api.js缓存问题) 2026-05-31 01:26:59 +08:00
Your Name cb60af4483 debug: Monaco preload 加日志诊断 2026-05-31 01:22:45 +08:00
Your Name 5a5adafb39 feat: Monaco编辑器+右键菜单+排序筛选+chmod+压缩解压+新建文件
编辑器增强:
- E-2 Monaco集成: 70vh全屏编辑器, 暗色主题, bracket着色
- E-3 Ctrl+S保存快捷键 + 未保存指示器
- E-4 语言自动检测(30+扩展名映射)
- Monaco预加载: 登录后CDN后台加载, 打开编辑器秒开
- fallback: Monaco未就绪时退回textarea

文件管理增强:
- F-1 右键菜单: 预览/下载/重命名/权限/复制路径/终端/压缩/解压/删除
- F-2 新建文件按钮(📄+)
- F-3 chmod权限修改(右键→输入权限如755)
- F-4 排序切换(名称/大小/时间)
- F-5 文件类型筛选下拉(.conf/.log/.sh/.py等)
- F-6 压缩(tar.gz)
- F-7 解压(tar.gz/zip)
2026-05-31 01:10:22 +08:00
Your Name c0296b9d8e fix: 预加载文件大小 30KB → 1MB 2026-05-31 01:04:44 +08:00
Your Name ca4aceba3e fix: 面包屑点击事件委托 — 作用域不在fileListEl内
面包屑(#breadcrumbPath)和文件列表(#fileList)是兄弟节点, 不是父子关系
事件委托绑在fileListEl上无法捕获面包屑点击
修复: 给#breadcrumbPath加独立click事件委托
2026-05-31 00:57:58 +08:00
Your Name b5dfa8a870 feat: 文件管理器集成SSH终端
- files.html: 工具栏加「🖥 终端」按钮(绿色),点击在新标签页打开SSH终端
- files.html: openTerminal() 传递 server_id + 当前路径到 terminal.html
- terminal.html: 支持 path URL参数,连接成功后自动 cd 到指定目录
- 打开方式: /app/terminal.html?server_id=8&path=/www/wwwroot
- 右键菜单也会集成「打开终端」选项
2026-05-31 00:53:00 +08:00
Your Name 17cdc96bb0 feat: 文件预加载+缓存 — 双击秒开编辑器
- api.js: Monaco编辑器CDN预加载(登录后后台加载)
- files.html: 文件内容预加载(目录浏览后自动预读≤30KB文本文件)
- files.html: 文件缓存(_fileCache), 双击已缓存文件直接打开
- files.html: 切换服务器时清空缓存
- 预加载策略: 最多10个文件, 仅文本类型, ≤30KB
2026-05-31 00:46:04 +08:00
Your Name 6012af0b4d fix: 双击编辑器冲突 — 延迟区分单击/双击 2026-05-31 00:25:02 +08:00
Your Name 9bdf7e420f feat: 文件管理器4项改进
1. 切换服务器自动重置路径为 /www/wwwroot
2. 默认目录 /www/wwwroot(而非根目录 /)
3. 双击文件打开编辑器(doPreview textarea模态框)
4. 双击目录进入目录(browseDir)
2026-05-31 00:17:22 +08:00
Your Name 40d672fdbb fix: 面包屑双斜杠bug (/tmp显示为//tmp) 2026-05-31 00:11:47 +08:00
Your Name 557c8d28c1 docs: files迭代审计报告 2026-05-30 23:46:12 +08:00
Your Name 9f7be66356 fix: 审计5个FINDING修复 (files迭代)
- H-15 [MEDIUM] download端点加100MB大小限制
- H-02 [LOW] Content-Disposition filename sanitize(替换特殊字符)
- H-12 [LOW] FileDownload/FileRead/FileWrite path加max_length=4096
- H-27 [LOW] doPreview保存失败解析错误详情显示detail
- H-29 [INFO] esc()函数加注释说明用途
- H-01 [HIGH] 远程路径限制 — ACCEPTED_RISK(用户明确决策)
2026-05-30 23:44:35 +08:00
Your Name ac38442383 docs: files.html 全面迭代 changelog 2026-05-30 23:36:49 +08:00
Your Name 14131f98f0 feat: files.html 全面迭代 — 15项改进
安全+后端:
- P2-1 符号链接解析(l权限检测+name/target分离)
- P2-7 新增 /api/sync/download 端点(SFTP流式下载)
- P2-8 新增 /api/sync/read-file + /write-file 端点(base64编码避免shell注入)

前端全面重写:
- P1-1 目录删除二次确认(自定义模态框+输入目录名)
- P2-2/2-3 事件委托替代内联onclick(data-action+data-path)
- P2-4 上传进度条(XHR.upload.onprogress)
- P2-5 文件大小可读化(B/KB/MB/GB)
- P2-6 文件类型图标(30+扩展名映射)
- P2-7 下载按钮+逻辑
- P2-8 文件预览/编辑弹窗(读取+修改+保存)
- P2-9 自定义模态框替代prompt/confirm
- P2-10 批量选择+批量删除(checkbox+全选+底部操作栏)
- P3-1 文件名搜索框(前端实时过滤)
- P3-2 符号链接🔗图标+target tooltip
- P3-3 空目录状态(图标+上传引导按钮)
- P3-4 服务器刷新按钮
- P3-5 操作loading状态(进度条) 2>&1
2026-05-30 23:34:24 +08:00
Your Name 64503c0260 fix: IP校验改用endpoint级HTTPException替代Pydantic validator
model_validator/field_validator在自定义RequestValidationError handler下
无法正确序列化为JSON,导致500。改用endpoint级_validate_ip_list()辅助函数
抛HTTPException(400),避免Pydantic→FastAPI序列化链路问题。
2026-05-30 22:48:41 +08:00
Your Name d19f5af807 fix: IP格式校验用field_validator替代model_validator
model_validator(mode='after')抛出ValueError时FastAPI不能正确序列化为422
→ 导致500 TypeError: Object of type ValueError is not JSON serializable
改用field_validator(mode='before')由Pydantic正常捕获返回422
2026-05-30 22:44:26 +08:00
Your Name 463841a740 docs: settings安全加固审计报告 2026-05-30 22:35:40 +08:00
Your Name 36fde6e6ed fix: 审计7个FINDING修复
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
- F-1 [MEDIUM] IpAllowlistSaveRequest 加 IP/CIDR/域名格式校验
- F-2 [MEDIUM] _is_private_host 改为 async DNS 解析 (不再阻塞事件循环)
- F-3 [LOW] hot-reload int转换失败改 logger.warning 而非静默pass
- F-4 [LOW] 提取 _verify_reauth 辅助函数,消除4处重复re-auth代码
- F-5 [LOW] ChangePasswordRequest.totp_code 加 min_length=6 约束
- F-6 [LOW] revealTelegramToken 空catch改 console.error+toast
- F-7 [LOW] toggleApiKeyVisibility 空catch改 console.error+toast
2026-05-30 22:34:45 +08:00
Your Name 1a61fae590 docs: settings 安全加固+UX迭代 changelog 2026-05-30 22:02:45 +08:00
Your Name a50f17941d fix: settings 安全加固 + UX 迭代 — 10项修复
安全修复:
- S-01 SSRF: parse-subscription 加私有IP封禁+重定向限制+响应大小限制
- S-02 值校验: PUT /{key} 加 key 白名单 + INT 范围校验(1-1000/1-100/10-600)
- S-05 重登录: 改密码成功后跳转login.html+绿色提示
- S-07 审计: add_manual_ips/remove_allowlist_ip 补全审计日志
- S-09 TOTP: 已启用TOTP时改密码需输入验证码

UX改进:
- UX-01: 密码框focus ring + 设置项input type映射(number/url/text)
- UX-02: 改密码按钮loading状态(disabled+提交中...)
- UX-03: 3处空catch块→console.error+toast
- UX-04: 保存失败时reloadSettings恢复原值
- UX-05: IP格式校验(前端正则+后端Pydantic model_validator)
2026-05-30 21:58:13 +08:00
Your Name 362b28199f docs: settings 巡检 + Bot Token 脱敏 changelog 2026-05-30 21:18:34 +08:00
Your Name 5213def150 fix: Telegram Bot Token 脱敏 — GET 不返回明文,reveal 需密码验证
- 后端 SENSITIVE_KEYS 加入 telegram_bot_token,GET /settings/ 返回脱敏值
- 新增 POST /settings/telegram/reveal-token 端点(密码验证 + 审计日志)
- 前端 Telegram 区域独立渲染:Bot Token 默认脱敏,点"显示"需输入密码
- Chat ID 保持可编辑(非敏感信息)
- Bot Token 输入框默认 type=password,可切换明文/遮掩
2026-05-30 21:12:41 +08:00
Your Name 5166660f3a fix: GET /api/settings/ip-allowlist 被 /{key} 路由拦截 → 404
/{key} 路由在 ip-allowlist 之前定义,FastAPI 将 ip-allowlist
当作 setting key 查找,找不到返回 404。

修复:将 GET /ip-allowlist 路由移到 /{key} 之前(FastAPI 路由优先级)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 20:42:42 +08:00
Your Name 48ef06babb chore: gitignore 添加 MCP 工具目录 + 清理测试截图
- .megamemory/ (知识图谱)
- .playwright-mcp/ (浏览器状态/截图)
- tmp/ (临时文件)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 20:39:47 +08:00
Your Name ede7fbaa64 fix: settings PUT 后热更新内存 — 无需重启即生效
问题:修改 Telegram 等设置后需要重启服务器才能生效,
因为 load_settings_from_db() 只在启动时运行。

修复:PUT /api/settings/{key} 写入 DB 后,
同步更新 settings 内存对象(DB_OVERRIDE_MAP 映射 + INT_SETTINGS 类型转换)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 20:28:51 +08:00
Your Name 4c4d41b8f5 docs: add changelog and audit for ruff cleanup
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 20:10:28 +08:00
Your Name 32feb1b6db fix: 全站 ruff 清零 — 77 errors → 0
Fixes:
- F821: install.py site_url 未定义(NameError)、sync_v2.py os 未导入、script_jobs.py LOG f-string
- F401: 移除 22 个未使用导入(models/__init__.py、install.py、auth.py 等)
- B904: 20 个 except 块 raise 添加 from e/from None
- S110: 20 个有意的 try-except-pass 添加 noqa 注释(SSH清理/DDL幂等/WS断连)
- B007: 3 个未使用循环变量重命名(dirs→_dirs、server_id→_server_id)
- F541: 3 个无占位符 f-string 修正
- F841: auth.py 未使用 ip_address 变量移除
- S105: auth_service.py Redis key prefix 误报 noqa
- B023: script_execution_flush.py 循环变量绑定修复

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 20:07:45 +08:00
Your Name ebe276ea29 docs: add changelog and audit for servers iteration
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 19:50:07 +08:00
Your Name 84282e87ae feat: servers list iteration — server-side pagination/search/sort/status filter
Backend:
- server_repo.py: get_paginated() adds search/sort_by/sort_order/is_online params
  with whitelist-validated sorting and DB-level pagination
- servers.py: list_servers() adds search/sort_by/sort_order/is_online Query params
  with Redis post-filter for is_online accuracy
- servers.py: fix all 12 pre-existing ruff errors (unused imports, B904, S110, F541)
- servers.py: fix F821 bug — agent_port undefined in upgrade_agent()

Frontend:
- servers.html: complete rewrite with server-side pagination, debounced search,
  clickable column sorting, status filter dropdown, auto-refresh toggle,
  keyboard shortcuts (↑↓/jk/Esc//r), CSV export, skeleton loading,
  empty state with contextual hints, pagination controls (first/prev/nums/next/last)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 19:45:53 +08:00
Your Name de8d860244 fix: handle legacy agent heartbeat without server_id + migrate on_event to lifespan
- AgentHeartbeat.server_id changed to Optional[int] — missing server_id
  now returns 200 (discarded) instead of 422, stopping retry loops
- web/agent/agent.py: on_event("startup") → FastAPI lifespan pattern
- Added exponential backoff on consecutive heartbeat failures (cap 5min)
- Agent stops heartbeat loop on "discarded" response from central
- main.py: promoted temporary debug 422 handler to production-grade
- uninstall.sh: added legacy agent cleanup (pkill patterns, crontab,
  /opt/multisync-agent path)
- Confirmed servers.html batch buttons work correctly (disabled state
  is correct when no agents installed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 18:20:14 +08:00
Your Name 3f50a40d25 feat: Agent 安装/卸载/升级脚本全面迭代
install.sh:
- 新增 Step 0:停旧服务 + 杀端口占用(fuser/ss/netstat 兜底)
- 修正步骤编号 [1/5] → [0/7]
- pip 依赖版本提取为脚本顶部变量,方便统一维护

uninstall.sh:
- 加 pkill 杀残留 Agent 进程
- 加 fuser 杀端口占用
- 清理 /etc/sudoers.d/nexus-agent 残留
- root 用户跳过 sudo 前缀

_sudo_wrap:
- NOPASSWD 命令白名单替代 ALL=(ALL)(仅 systemctl/apt/yum/rm 等)
- 降低 SSH 超时后 sudoers 残留的风险

升级 API:
- restart 前加 fuser -k 杀端口占用(单台+批量)
- install_agent_remote 检查 stdout 中的 FAILED 标记

卸载后清理:
- 清空 agent_api_key(之前只清 agent_version)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 16:11:09 +08:00
Your Name dc48f71b25 feat: 创建 Agent 卸载脚本 uninstall.sh
之前卸载功能调用了不存在的 uninstall.sh,导致 404 下载失败。
脚本功能:停止 systemd 服务 → 禁用并删除 unit → 删除 /opt/nexus-agent → 删除日志
支持非 root 用户(自动检测 sudo 权限)。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 14:35:33 +08:00
Your Name 913fd16bb9 fix: Agent 升级命令添加 sudo 前缀(非 root 用户)
systemctl restart 需要 root 权限,非 root SSH 用户必须加 sudo。
之前 _sudo_wrap 只设置了免密 sudoers 但命令本身没带 sudo,
导致升级时 "Interactive authentication required" 失败。
同时修复单台升级和批量升级的 rollback 命令。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 14:28:49 +08:00
Your Name deffa8a8f4 fix: AppAuthMiddleware 验证 refresh token 格式而非 JWT 解码
refresh token 是 opaque 格式 (token:admin_id:token_version),不是 JWT。
之前用 pyjwt.decode() 验证必然失败,导致所有登录用户访问 /app/ 都返回 404。
改为验证格式合法性(3段、admin_id>0、token_version>=0)。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 05:10:39 +08:00
Your Name 64a8a18287 fix: refresh cookie path 改为 /,修复 AppAuthMiddleware 读不到 cookie
REFRESH_COOKIE_PATH 从 /api/auth 改为 /,这样浏览器访问 /app/ 页面时
也会发送 nexus_refresh cookie,AppAuthMiddleware 才能正确验证登录状态。

同时包含之前的 AppAuthMiddleware 定义顺序修复和 422 调试日志。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 05:08:21 +08:00
Your Name cc0ca47d61 debug: 422 handler 简化 — 只记录 errors 和 content_type,不读 body
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 03:09:30 +08:00
Your Name 449373f24f debug: 添加 422 验证错误日志 — 记录请求体和验证错误详情
诊断 Agent 心跳 422 问题,临时捕获请求体内容。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 03:05:30 +08:00
Your Name 730b665306 fix: AppAuthMiddleware 类定义移到 add_middleware 调用之前
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
Python 模块级代码顺序执行,类必须先定义再引用。
之前 add_middleware(AppAuthMiddleware) 在第404行但类定义在第495行,
导致 NameError 服务启动失败。同时修复 _is_install_mode → is_install_mode。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 03:01:08 +08:00
Your Name f5bea6f0d2 fix: AppAuthMiddleware add_middleware 重新加回 2026-05-30 02:46:11 +08:00
Your Name c808c28edf fix: 移除错误的 AppAuthMiddleware add_middleware(类定义在后面)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 02:45:19 +08:00
Your Name d53309e37e fix: AppAuthMiddleware 类定义移到 add_middleware 之前
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 02:43:29 +08:00
Your Name a8fe7fed0a fix: 添加 REFRESH_COOKIE_NAME import,修复 AppAuthMiddleware NameError
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 02:41:12 +08:00
Your Name f91cd847a9 feat: AppAuth 中间件 — 未登录时 /app/ 页面返回空白 HTML 404
通过 nexus_refresh cookie 验证身份,无有效 token 返回空白 HTML,
不暴露系统存在。白名单路径: login/install/vendor/static assets。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 02:39:48 +08:00
Your Name 993a234152 docs: 审计 + changelog — health纯文本/sha256/AppAuth
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 02:37:15 +08:00
Your Name e709c72f1f docs: CLAUDE.md 新增部署流程 + Health 纯文本修复
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 02:30:49 +08:00
Your Name f7c4b95dd6 fix: /health 返回纯文本 "ok" 替代 JSON,隐藏 FastAPI 后端指纹
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 02:30:01 +08:00
Your Name 75864fe04f fix: md5 → sha256 替换文件校验哈希算法,消除 bandit CWE-327
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 02:27:18 +08:00
Your Name ae22db4b3e fix: Nginx 重定向 + 自定义 404 — 隐藏 FastAPI 后端信息
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
nginx_https.conf 新增注释化重定向规则,将裸页面路径 301 到 /app/ 前缀;
main.py 添加 StarletteHTTPExceptionHandler,404 返回空白 HTML 而非 JSON,
避免泄露后端框架标识。

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-30 02:03:57 +08:00
Your Name 0b82a3fee7 feat: 推送取消 — 引擎 cancel flag 检查 + WS cancelled 计数器
sync_engine_v2: 每台服务器推送前检查 Redis sync:cancel:{batch_id},
若已取消则跳过并标记 cancelled 状态,WS 广播 cancelled 计数。
websocket: broadcast_sync_progress 新增 cancelled 参数。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 00:57:38 +08:00
Your Name d785c78d6d fix: 审计修复 — esc() 加单引号转义 + 移除死代码 editQuickCmd
B2: esc() 不转义 ' 可能在 onclick 中注入
B4: editQuickCmd 定义但无调用(✏按钮已移除)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 00:26:14 +08:00
Your Name 56906e8fe2 fix: 快速命令栏移至命令栏上方 — chip 样式替代右侧面板列表
自定义命令在前(brand高亮),内置命令在后(灰色)。hover 显示 × 可删除。
+ 号弹窗添加新命令。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 00:22:31 +08:00
Your Name da234c4e73 fix: 终端命令栏被覆盖 — termContainer隔离终端实例
termminal div 用 absolute inset:0 覆盖了底部 cmdBar。
修复: termContainer(flex-1 relative) 容器隔离,cmdBar 作为 flex sibling 在下方。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 00:19:16 +08:00
Your Name 7193b88156 feat: 终端页面 11 项全覆盖迭代 — 多标签/快速命令/字体/历史/重连/右键等
11 项改进:
- 多标签会话 (切换/关闭/新建,每标签独立 xterm+WS)
- 快速命令栏 (内置5条 + 自定义,localStorage持久化)
- 字体大小调节 (A−/A+按钮 + Ctrl+/-)
- Ctrl+L 清屏
- 命令历史持久化 (200条 FIFO localStorage)
- 右键菜单 (复制/粘贴/清屏/全选)
- 断开自动重连 (指数退避 + 倒计时)
- 连接信息栏 (时长 + PING RTT)
- 终端路径显示 (OSC title + onTitleChange)
- 滚动缓冲区调节 (1K/5K/10K/50K)
- 快速搜索服务器

后端: PING/PONG + PROMPT_COMMAND 注入

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 00:17:22 +08:00
Your Name ac66f5b4e7 fix: 卸载Agent后清除Redis心跳(之前只清MySQL导致在线状态残留)
卸载Agent后只更新MySQL is_online=False,但前端读Redis覆盖为True。
修复: uninstall_agent_remote + batch_uninstall_agent 都增加redis.delete(heartbeat:{id})

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 23:57:06 +08:00
Your Name 6dd0b487b7 fix: install.sh 和 upgrade_agent 增加 rsync 自动安装
推送功能依赖目标服务器安装 rsync,但 install.sh 仅安装 Python 包。
现在 install.sh 步骤2/6检测并自动安装 rsync(apt/yum/dnf)。
upgrade_agent / batch_upgrade_agent 流程中也增加 rsync 检测,
已有服务器点击"升级 Agent"即可自动补装 rsync。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 23:45:01 +08:00
Your Name fe211619aa fix: 审计修复 — esc() 增加双引号转义, 移除死代码 replace(/'/g,"\'")
B1: esc() 不转义 " 导致文件名含双引号时破坏 HTML onclick 属性
B2: esc().replace(/'/g,"\'") — esc已转义单引号为&#39;,后续replace为死代码

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 23:07:01 +08:00
Your Name 0cbda8c840 fix: 审计修复 — diagnose/file-diff 端点静默吞错改为 logger.debug/warning
B1: diagnose 磁盘和路径检查 except:pass 改为 logger.debug()
B2: file-diff SSH失败无日志改为 logger.warning()

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 22:11:06 +08:00
Your Name 5958c21048 feat: 推送页面 Round 4 — 服务器搜索 / Diff对比 / 排错诊断 / 源路径输入 / 批量重试
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
5 项新功能:
- H2: 服务器搜索过滤 — 目标服务器下拉加搜索框实时过滤
- H3: 推送对比 Diff 视图 — 预览文件列表加 diff 按钮对比本地与远程差异
- H4: 推送排错面板 — 失败行加诊断按钮检查SSH/磁盘/权限
- H5: 源路径直接输入 — 支持直接输入服务器本地路径推送
- H6: 批量重试 — 一键重试所有失败服务器

新增后端端点:
- POST /api/sync/validate-source-path (H5)
- POST /api/sync/diagnose (H4)
- POST /api/sync/file-diff (H3)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 21:29:41 +08:00
Your Name 02423cb803 docs: 推送页面 Round 2 迭代设计文档与 changelog
- 设计文档: 5 项迭代功能(同步说明、在线标识、失败重试、Telegram通知、文件预览)
- 技术方案: 详细实现步骤与文件清单
- Changelog: 功能变更记录

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 18:54:12 +08:00
Your Name 152852e2c3 fix: 会话时效30天 + 多设备同时登录
- access token 从 30 分钟延长到 30 天
- refresh token 从 7 天延长到 30 天
- refresh token 从 MySQL 单字段改为 Redis Set 存储,支持多设备同时登录
- 单设备退出不再踢掉其他设备(只删自己的 Redis hash)
- 改密码/禁用 TOTP 仍会让所有设备掉线(token_version 递增)
- 移除 reuse 误杀逻辑:版本不匹配只拒绝当前 token,不惩罚其他设备

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 18:52:14 +08:00
Your Name 3c7036babf fix: 补回 servers.html 丢失的检测路径+卸载Agent功能
从 worktree 分支同步完整的 servers.html,补回:
- 批量操作栏"检测路径"按钮(搜索 workerman.bat 自动设置 target_path)
- 批量操作栏"卸载 Agent"按钮
- batchDetectPath() 和 batchUninstallAgent() 函数

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 16:56:23 +08:00
Your Name 74de9d303e feat: 推送页面 Round 2 迭代 — 同步说明 + 在线标识 + 失败重试 + Telegram通知 + 文件预览
F5: 同步模式详细说明 — 每种模式下方显示说明文字,切换时动态更新
F2: 服务器在线状态标识 — 服务器列表显示🟢/🔴在线/离线标识
F1: 失败自动重试 — 推送失败自动创建重试任务 + 失败行"🔄 重试"按钮
F3: 推送完成 Telegram 通知 — 全成功🟢/部分失败🟡/全失败🔴 三种消息含详情
F4: 文件内容预览 — 文件管理器点击文件名弹出内容预览模态框

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:59:30 +08:00
Your Name ad2e7667d3 Merge branch 'claude/condescending-ishizaka-3cff3a' 2026-05-28 21:02:09 +08:00
Your Name 82c297776a feat: 推送页面 5 项迭代 — WS实时进度 + 分组选择 + 历史增强 + 文件操作 + 推送校验
F1: broadcast_sync_progress() + batch_id + 前端WS逐台更新
F2: get_paginated() platform_id/node_id + /categories端点 + 筛选下拉框
F4: _sync_log_to_dict() 补全字段 + diff_summary捕获 + 可展开详情面板
F7: /local-file-ops端点 + 文件管理器删除/重命名
F8: /verify端点 md5sum对比 + 推送后校验UI

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 21:01:27 +08:00
Your Name 77b332c037 fix: 远程安装按钮修复 + install.sh sudo/venv兼容 + api_base_url管理
- 修复详情面板"远程安装"按钮始终禁用:s._base_url 改为从新API获取全局API_BASE_URL
- 新增 GET /api/servers/meta/api_base_url 端点
- api_base_url 加入 DB_OVERRIDE_MAP + 启动迁移逻辑
- install.sh 非 root 用户自动 sudo 检测 + NOPASSWD 配置
- 后端 _sudo_wrap 辅助函数应用于 4 个 Agent 操作端点
- install.sh venv 创建加 || true 兼容 set -e(缺 python3-venv 不再直接退出)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 14:33:19 +08:00
Your Name 97be1fd214 完善审计日志中文化:auth_service/script_service/sync_engine 全部 detail 改中文 + 前端新增 webssh_token/refresh_token_mismatch 等映射 + MCP deploy 修复
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 02:57:51 +08:00
Your Name 366bdc18fa fix: deploy tool git pull before gate check + use origin/main
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 02:27:32 +08:00
Your Name 280357d7d7 Merge remote-tracking branch 'origin/claude/condescending-ishizaka-3cff3a' 2026-05-27 02:19:40 +08:00
Your Name fd9b7d85b5 fix: 批量安装按钮检测 + 审计日志中文化 + terminal断开反馈
1. updateBatchBar() hasAgent 判断从 agent_api_key_set 改为 agent_version
2. 所有后端 AuditLog detail 字段统一中文
3. audit.html 操作/目标类型中文映射
4. terminal.html 断开覆盖层 + 重新连接按钮
5. Agent os_release 上报 + 前端系统版本拆分

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 02:17:35 +08:00
Your Name 8b82d6bab0 Add changelog for fmtTime/venv/batchinstall fixes
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 00:59:48 +08:00
Your Name 869f64ac18 Fix install.sh venv robustness + batch install API error reporting
install.sh: detect incomplete venv (missing activate), auto-install
python3-venv and retry instead of silently failing.

servers.py batch install: preserve server_name on exception, detect
"Status: FAILED" in stdout, return error details when stderr is empty,
increase SSH timeout to 180s.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 00:53:00 +08:00
Your Name 69056491a7 Fix fmtTime Invalid Date for timezone-aware timestamps (+00:00)
fmtTime() was appending 'Z' to all timestamps, causing Invalid Date
when the timestamp already had a timezone suffix like +00:00.
Now tries new Date(t) first, falls back to new Date(t+'Z').

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 00:34:51 +08:00
Your Name 02f1fa4ef3 Add .gitattributes + changelog for Agent install fix and deploy
- .gitattributes: enforce LF line endings for .sh/.py to prevent CRLF issues on Linux
- Changelog: document Agent install 404 fix, curl|bash pipe fix, CRLF fix, and successful deployment to both sub-servers

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 00:27:05 +08:00
Your Name eadf7254a1 Merge remote-tracking branch 'origin/claude/condescending-ishizaka-3cff3a' 2026-05-26 22:59:14 +08:00
Your Name 1c70e597a3 Fix Agent install 404 + add command input bar + fix install pipe error hiding
1. Mount /agent/ static files for install.sh/agent.py (was 404)
2. Replace curl|bash pipe with temp-file download to catch curl errors
3. Add command input bar to terminal.html bottom
4. Fix terminal CSS layout for flex with command bar

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 22:57:32 +08:00
Your Name 867e2aa552 Merge branch 'claude/condescending-ishizaka-3cff3a'
# Conflicts:
#	server/infrastructure/database/setting_repo.py
2026-05-26 21:58:38 +08:00
Your Name a2d392dae7 fix: WebSSH terminal echo delay — replace readline() with read()
asyncssh's async-for on shell.stdout internally calls readline()
which buffers until a newline is found. For interactive terminal
use, single-character echo never contains a newline, so all
typed characters were buffered until Enter was pressed.

Fix: use shell.stdout.read(4096) which returns data as soon as
any is available, and set bufsize=4096 on create_process() to
reduce the internal read buffer from 128KB to 4KB for low-latency
interactive echo.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 21:54:40 +08:00
Your Name 8b10f9d64a fix: WebSSH terminal disconnect after connect + stale pool retry
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
- Fix shell creation failure on stale pooled SSH connections:
  force-close and retry with fresh connection
- Fix empty error message in logs: use {type(e).__name__}: {e!r}
- Fix double WebSocket.close() causing RuntimeError in finally block:
  track ws_closed flag to avoid closing twice
- Fix Alpine undefined error in terminal.html: $d() now returns
  default object when Alpine hasn't initialized yet
- All websocket.close() calls now wrapped in try/except

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 21:33:31 +08:00
Your Name b1aa235726 fix: Gate3 false positive + test_api.py production compatibility
- Gate3: Replace loose grep (matched "0/25 failed" summary) with precise
  [FAIL] marker counting + "N test(s) failed" pattern
- test_api.py: Add .env credential loading for gate check automation
- test_api.py: Fix REST status codes (POST→201, DELETE→204)
- test_api.py: Add 204 empty body handling, dynamic ID, pre-cleanup
- Add gate_log.jsonl tracking to all 7 gates
- Add knowledge graph restructure changelog

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 19:33:31 +08:00
Your Name 5afb18b745 feat: gate v2 — 3→7 gates with anti-bypass, lint, security, review cross-validation
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-26 10:02:14 +08:00
Your Name a1b0b2c514 feat: Terminal sidebar+panel, SSH key fix, auth cleanup, pre-deploy gates
- terminal.html: left sidebar (default open), right server panel with server list
- servers.py: fix SSH key double-encryption in update_server, auth method switch cleanup
- servers.html: auth switch clears opposite credentials (key↔password)
- deploy/pre_deploy_check.sh: 3-gate pre-deploy check (changelog/audit/test)
- mcp/Nexus_server.py: deploy() runs gate check before git pull+restart
- docs/audit/: audit record template + today's audit results
- CLAUDE.md: add gate enforcement rules and progress bar discipline

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-26 09:16:05 +08:00
Your Name f591033309 docs: add changelog for asset management removal
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-26 00:58:14 +08:00
Your Name 89fa517693 refactor: remove asset management page, simplify server form
- Delete assets.html (platform/node management had low utility)
- Remove "资产管理" sidebar entry from layout.js
- Server form: remove Platform/Node dropdowns, keep only Category
  combobox (with datalist for existing categories)
- Remove loadOrgSelects() and platform_id/node_id from save logic
- Backend API and DB tables preserved for data compatibility

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-26 00:46:39 +08:00
Your Name 973e23e038 fix: CSV template download 401 + category combobox
1. CSV template download link now uses apiFetch (with JWT) instead
   of direct <a> href which returned 401 Unauthorized
2. Server category field changed from plain input to combobox with
   datalist — users can select existing categories or type new ones

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-26 00:30:46 +08:00
Your Name ad7f4d8d62 docs: update changelog with route fix and browser verification results
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-26 00:27:25 +08:00
Your Name fb4e51ec5a fix: move static routes before /{id} to fix route conflict
/batch/install-agent, /batch/upgrade-agent, /import, and
/import/template were registered after /{id}, causing FastAPI
to match 'batch' and 'import' as the {id} parameter.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-26 00:25:25 +08:00
Your Name 8cd091add8 feat: CSV batch import, SFTP upload, batch Agent operations
Add three missing frontend management features:
1. Server CSV import (POST /api/servers/import) with injection prevention
2. SFTP file upload (POST /api/sync/upload) with path traversal protection
3. Batch Agent install/upgrade with Semaphore concurrency control

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-26 00:17:43 +08:00
Your Name 6be989c299 feat: add SSH key preset management to credentials page
Add third tab "SSH密钥预设" to credentials.html with full CRUD:
- List presets with public key preview and creation date
- Add/edit preset (name, private key, public key)
- Reveal private key via re-authentication modal
- Delete preset with confirmation
- Route "+ 新建" button to correct form based on active tab

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-25 23:54:11 +08:00
Your Name ccb78ed980 feat: SSH key preset system — dropdown + backend auto-resolve
- Add SshKeyPreset model + repo (encrypted_private_key, public_key)
- Add ssh_key_preset_id to Server model + schemas
- Add CRUD + reveal API routes (/api/ssh-key-presets/)
- servers.py: auto-resolve ssh_key_preset_id → decrypt → set ssh_key_private
- servers.html: key preset dropdown in key auth section, hides path/textarea on select
- Mirror password preset pattern: zero frontend credential exposure

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-25 22:49:57 +08:00
Your Name 95aa8a610f security: move refresh token to HttpOnly cookie
- Refresh token stored in HttpOnly + Secure + SameSite=Lax cookie
  instead of localStorage (XSS can no longer steal it)
- Login sets cookie, refresh reads from cookie, logout clears cookie
- Access token remains in localStorage (short-lived, 30min)
- Clear pendingPassword from memory after TOTP success/back
- Remove "admin" from username placeholder
- Cookie path scoped to /api/auth (minimal exposure)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-25 20:22:34 +08:00
Your Name 9de6ac9904 fix: auth default to password + remove SSH tab from assets
servers.html: Default auth method to "密码" (password) instead of "密钥"
(key) when adding a new server. Hide key path row by default.

assets.html: Remove SSH Sessions tab — it duplicated the server list
functionality. Assets page now focuses on classification metadata:
platform templates and node groups only.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-25 19:33:39 +08:00
Your Name cf0725b037 fix: credentials.html JS parse error + assets.html SSH session UX
credentials.html: Remove broken nested ternary `Alpine?Alpine.store?...`
that caused "Unexpected token ':'" which killed the entire script block,
making all functions (showAddCred, loadCreds, etc.) undefined.

assets.html: Replace SSH session history list with server list +
double-click to open terminal, matching user's requested UX.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-25 19:17:09 +08:00
Your Name 3943ff4f3a fix: remove updated_at secondary check that invalidated fresh JWTs
The updated_at secondary check in _verify_token() was a "defense in
depth" mechanism, but it caused a race condition: login updates admin
(last_login, onupdate→updated_at) AFTER creating the JWT, so the JWT's
"updated" claim is immediately stale. Any DB admin update (including
innocuous ones like last_login) would invalidate all existing tokens.

The token_version primary check already covers all security scenarios
(password change, TOTP disable, token reuse detection) and is the
correct mechanism for invalidating sessions. Removed the updated_at
check from both _verify_token() and get_optional_admin().

Also reordered login/refresh flows to create JWT AFTER admin update,
so the token captures the latest updated_at (belt and suspenders).

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-25 16:23:20 +08:00
Your Name deb3a94ee6 fix: token_version NULL handling — normalize None→0 in JWT comparison
Direct DB inserts (e.g. install wizard, manual MySQL) may leave
token_version as NULL. JWT payload had tv:null which failed the
strict != comparison against DB value 0. Normalize both sides
with `or 0` for defensive coding.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-25 15:37:10 +08:00
Your Name 99201f48fd fix: MissingGreenlet error — convert all middleware to pure ASGI
Root cause: BaseHTTPMiddleware.call_next() runs the endpoint in a
separate async task, which breaks SQLAlchemy's greenlet context.
After session.commit(), the connection is released to the pool.
When session.refresh() tries to acquire a new connection for a
SELECT, it fails with MissingGreenlet because the greenlet spawn
context is not available in the call_next() task.

Fix (two-part):
1. Convert all 4 middleware classes from BaseHTTPMiddleware to pure
   ASGI middleware — eliminates call_next() entirely so the entire
   request chain runs in the same async context.
2. Set expire_on_commit=False on the session factory — after commit,
   objects retain their in-memory values instead of being expired.
   This removes the need for session.refresh() entirely.

All session.refresh() calls removed from 11 repository files.
With expire_on_commit=False, post-commit attribute access no longer
triggers lazy loads that would also fail with MissingGreenlet.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-25 15:24:15 +08:00
Your Name c72631a293 merge: 安装脚本+PHP移除+宝塔适配 合并到main
合并 claude/condescending-ishizaka-3cff3a 分支:
- deploy/install.sh: 一键安装脚本(宝塔/标准模式自适应)
- deploy/upgrade.sh + uninstall.sh
- docs/install-guide.md: 完整中文安装教程
- PHP完全移除: install.php删除, config.php→config.json
- crypto.py: 移除PHP AES-CBC兼容, 纯Fernet
- install.py: 宝塔Supervisor路径自适应, Fernet密钥生成
- install.html: Step 5宝塔/标准模式条件渲染
- 44个冲突文件使用worktree分支版本(全部最新代码)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-25 08:25:29 +08:00
Your Name a1276f38ad feat: 一键安装脚本 + PHP完全移除 + config.php→config.json
新增文件:
- deploy/install.sh: 7步一键安装(自动检测宝塔/标准模式)
- deploy/upgrade.sh: git pull + pip install + restart + 健康检查
- deploy/uninstall.sh: 停服务+删配置, 可选--purge删部署目录
- docs/install-guide.md: 完整中文安装教程(宝塔+手动)

核心改动:
- install.py: 宝塔Supervisor路径自适应, init-db返回bt_panel标记,
  config.php→config.json, 移除PHP锁文件, 生成Fernet加密密钥
- install.html: Step 5根据btPanel显示不同Supervisor/Nginx/SSL指引
- crypto.py: 移除PHP AES-CBC兼容层, 纯Fernet加密
- 删除 web/install.php (1192行PHP, 功能已完全迁移到Python)

PHP清理:
- Nginx配置: config\.php→config\.json deny规则
- config.py/main.py/migrations.py: install.php注释→install wizard
- CLAUDE.md/AGENTS.md: config.php→config.json, 移除PHP引用
- firefox_server.py: login.php→login.html默认URL

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-25 08:23:37 +08:00
Your Name 400dcae781 fix: 代码验证发现的两处问题
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
- websocket.py: asyncio.get_event_loop() → get_running_loop()
  (Python 3.10+废弃,shutdown时可能RuntimeError)
- asyncssh_pool.py: 全忙等待逻辑在锁外操作有竞态条件
  简化为直接raise ConnectionError,不在锁外重试

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-24 16:31:13 +08:00
Your Name cdd0be328a fix: 六轮深度扫描 — 47项Bug修复、安全加固、死代码清理
Critical runtime bugs:
- terminal.html WebSSH完全不可用(URL前缀/JSON解析/Content-Type三处错误)
- servers.py路由遮蔽:/logs被/{id}拦截,3个前端页面同步日志查询失败
- scripts.html startExecPoll()→startExecPolling(),长任务快速执行崩溃
- agent.py {value!r!s:.50}格式串非法,agent发非数值时ValueError
- alerts.html d.daily.reduce()无null检查,API返回空数据时TypeError

Resource leak / stability:
- websocket.py僵尸连接未关闭TCP,文件描述符泄漏
- websocket.py _last_alert_time字典无限增长(加1小时过期清理)
- asyncssh_pool.py全忙时超过MAX_CONNECTIONS无限增长
- self_monitor.py Telegram告警无冷却,宕机时每30秒刷屏
- schedule_runner.py一次性调度执行超60秒会重复触发
- 限速脚本EXPIRE每次重置窗口可绕过(改用Lua原子脚本)

Security:
- JWT access token加token_version声明,改密码后旧token立失效(零宽限)
- INSTALL_MODE导入时常量→动态函数,安装后JWT认证不再残留禁用
- install.py /lock端点加管理员存在性验证,防止阻断安装
- ServerUpdate schema移除connectivity只读字段,防止伪造连接状态

Frontend fixes:
- doExec()缺r.ok检查、commands.html null检查
- _server_to_dict()补last_checked_at+ssh_key_public
- _field_match()逗号cron表达式修复
- alerts类型显示、SSH会话名称、搜索高亮定位
- 一次性/循环定时任务(run_mode+fire_at+自动禁用)

Dead code removed (400+ lines):
- SyncService batch_push/_push_single等5个方法(零调用者)
- 5个未使用schema(SyncCommands/SyncConfig/SyncSftp/FileDeploy/PaginatedResponse)
- 6个零调用service方法、3个无前端API端点
- 4个未使用import

Schema migrations:
- push_schedules: run_mode + fire_at列,cron_expr改NULL
- servers: 7个新列 + ssh_key_private/public VARCHAR(500)→TEXT

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-24 16:26:40 +08:00
Your Name fbf5eadcf2 fix(security): R2+R3 全面代码审计修复 — 3 CRITICAL / 9 HIGH / 15 MEDIUM / 10 LOW
CRITICAL:
- C-1: sync_service.py rsync命令shlex.quote防Shell注入回归
- C-2: agent.py exec端点危险命令正则拦截+compare_digest+僵尸进程修复
- C-3: WebSSH JWT token绑定server_id防IDOR

HIGH:
- H-1: 所有PUT/POST端点setattr改为字段白名单防任意字段注入
- H-2: settings/assets/scripts端点添加JWT认证
- H-3: refresh_token invalidate-then-generate防并发重放
- H-4: servers.py Redis pipeline解决N+1查询
- H-7: settings API Key/Secret掩码+禁止修改
- H-8: TOTP暴力破解速率限制(5次/5分钟)
- H-9: get_optional_admin改用request-scoped session

MEDIUM:
- M-2: X-Real-IP优先+X-Forwarded-For取末值防IP伪造
- M-4: asyncssh异常信息脱敏(只含server_id)
- M-5: 分页limit上限(500/200/500/500)
- M-9: API层Redis依赖抽象至dependencies.py
- M-10: WebSocket ConnectionManager多worker Redis聚合
- M-13: login_attempts复合索引
- M-14: ServerRepository.get_by_ids批量查询
- M-15: API错误消息统一中文

R3追加:
- sysctl命令注入防护+regex验证
- $DB_PASS明文存储→masked_command双命令方案
- MYSQL_PWD环境变量替代-p flag
- health端点拆分(公开/需认证)
- repo层**kwargs→字段白名单
- 全局str(e)信息泄露修复(API/Telegram/WebSocket)
- SSRF私有IP拦截
- Nginx HTTPS配置+DB备份脚本
- 前端CDN SRI哈希+XSS修复
- __import__替换为正常import

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 10:58:33 +08:00
Your Name 9bba58a529 feat: Telegram test+ChatID, Agent IP allowlist/upgrade, alert history page
Telegram:
- POST /api/settings/telegram/test: send test message (checks token+chat_id)
- GET /api/settings/telegram/chats: call getUpdates, return up to 5 chats
- settings.html: test button + detect chat IDs with click-to-fill

Agent IP allowlist (web/agent/agent.py):
- allowed_ips config: list of trusted IPs/CIDRs from config.json
- _ip_allowed(): checks exact IP, CIDR (ipaddress module), hostname
- verify_api_key(): now checks IP allowlist before API key
- /health: also checks IP allowlist
- install.sh: auto-extracts central server IP from --url, adds to allowed_ips

Agent auto-upgrade (server/api/servers.py):
- POST /api/servers/{id}/upgrade-agent: SSH → curl new agent.py → systemctl restart
- servers.html: 升级 Agent button in Agent tab (only when online)

Alert history (new page):
- domain/models: AlertLog table (server_id, type, value, is_recovery, created_at)
- migrations.py: CREATE TABLE IF NOT EXISTS alert_logs
- websocket.py: _save_alert_log() called from broadcast_alert/recovery
- settings.py: GET /api/alert-history/ (paginated, filters), GET /stats
- main.py: register alert_history_router
- alerts.html: new page with stats cards, top-servers bar chart, alert list
- layout.js: 🔔 告警中心 added to sidebar nav

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 18:27:12 +08:00
Your Name 488838c989 feat: separate subscription/manual IPs + master enable/disable toggle
Data model (separate storage):
- login_allowlist_enabled: 'true'/'false' master switch
- login_subscription_url: URL to fetch every 2h
- login_subscription_ips: last fetch result (replaced entirely each refresh)
- login_manual_ips: manually added (never overwritten by refresh)
- login_allowed_ips: combined (used by auth for legacy compat)

ip_allowlist_refresh.py:
- Saves to login_subscription_ips (replace) + rebuilds login_allowed_ips

auth_service.py:
- Login check: only runs when LOGIN_ALLOWLIST_ENABLED is 'true'
- Combines subscription_ips + manual_ips at auth time (no stale combined key needed)

settings.py:
- POST /ip-allowlist/toggle: quick enable/disable endpoint
- GET /ip-allowlist: returns subscription_ips, manual_ips separately
- POST /ip-allowlist: saves manual_ips + optional subscription_url + optional enabled

settings.html:
- Toggle switch: enable/disable with warning if list is empty
- Green notice when enabled (warns about self-lockout risk)
- Grey notice when disabled
- Subscription IPs section: read-only, auto-refresh badge
- Manual IPs section: deletable per-IP, clear all button

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 18:19:47 +08:00
Your Name da43960f38 feat: subscription URL stored in settings, auto-refresh allowlist every 2h
config.py:
- LOGIN_SUBSCRIPTION_URL: stored in DB, configurable from UI
- LOGIN_MANUAL_IPS: manually added IPs (preserved across auto-refresh)

background/ip_allowlist_refresh.py (new):
- ip_allowlist_refresh_loop(): primary-worker task, every 2h
- _do_refresh(): fetch subscription, parse IPs, merge with manual IPs,
  write combined to LOGIN_ALLOWED_IPS + DB setting; runs once at startup (5s delay)
- get_last_refresh_time(): returns last successful refresh timestamp

main.py:
- Register ip_allowlist_refresh_loop as background task

settings.py:
- GET /ip-allowlist: now returns subscription_url, manual_ips, last_refresh
- POST /ip-allowlist: saves manual_ips + subscription_url, triggers refresh
- POST /ip-allowlist/manual: append to manual list only, trigger rebuild
- DELETE /ip-allowlist/ip: remove single manual IP, trigger rebuild

settings.html:
- Subscription URL field with 保存 + 🔄 instant-refresh button
- Last refresh timestamp display
- Status badge shows '订阅自动刷新' when URL configured
- IP badges: blue=manual (deletable), grey=from subscription (auto)
- 预览 button to inspect subscription IPs without saving

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 18:15:29 +08:00
Your Name 5fcc1e22a6 feat: login IP allowlist with proxy subscription parser
server/infrastructure/subscription_parser.py (new):
- parse_subscription_content(): fetch and decode subscription, extract host/IP
- Supports SS, VMess, VLESS, Trojan, Hysteria2 formats
- Base64 decode with padding fix; IPv4/CIDR/hostname detection

server/config.py:
- LOGIN_ALLOWED_IPS setting (empty = allow all, comma-separated)

server/api/settings.py:
- POST /settings/ip-allowlist/parse-subscription: fetch URL, decode, return hosts
- POST /settings/ip-allowlist: save allowlist to settings table + reload
- GET /settings/ip-allowlist: return current list

server/application/services/auth_service.py:
- _ip_in_allowlist(): supports exact IP, CIDR (ipaddress module), hostname match
- login(): check allowlist before brute-force check; if IP blocked return
  reason='ip_blocked' with clear message; audit log 'login_ip_blocked'

web/app/settings.html:
- New '登录 IP 白名单' card with:
  - Subscription URL input + parse button (calls backend parser)
  - Parsed IPs with checkboxes (select/deselect before adding)
  - Manual IP/CIDR/hostname textarea input
  - Current allowlist with per-IP remove buttons
  - Clear all button (removes restriction)
  - Status badge: 启用 N条 / 未启用(不限制)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 18:11:23 +08:00
Your Name 37ceaef6d9 feat: auto-poll Agent online status after install with live badge update
- remoteInstallAgent(): after success, start _startAgentOnlinePoller() instead
  of one-shot setTimeout(loadServers, 2000)
- _startAgentOnlinePoller(serverId): poll GET /api/servers/{id} every 5s up to
  2 minutes (24 attempts); when is_online=true, update status badge live,
  disable remote-install button, toast 'Agent 已上线', reload server list
- _updateAgentStatusBadge(isOnline): in-place badge update without full reload
- Install log shows: '✓ 安装成功!等待首次心跳...' then ' Agent 心跳已收到'
- Hint text explains: install ~30-60s, first heartbeat another ~60s
- Manual install: 'click to wait for Agent online' button triggers same poller

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 17:47:22 +08:00
Your Name d414b945e1 feat: audit log time-range filter + script execution schedules
Audit log time-range filter:
- audit_log_repo.py: unified query() with action/admin_username/date_from/date_to
  filters + total count; _build_filters() helper for date parsing
- settings.py GET /audit/: add date_from, date_to, admin_username query params
- audit.html: date range inputs (YYYY-MM-DD), admin filter input, action filter
  expanded with optgroups (服务器/推送/脚本/认证/设置); clear filter button

Script execution schedules:
- domain/models: PushSchedule extended with schedule_type('push'|'script'),
  script_id FK, script_content Text, exec_timeout int, long_task bool;
  source_path made nullable
- migrations.py: 6 new ALTER TABLE statements (idempotent)
- schedule_runner.py: detect schedule_type; for 'script' type use ScriptService;
  for 'push' type keep original SyncEngineV2 sync_files behaviour
- schemas.py: ScheduleCreate/Update add all new fields
- schedules.html: full UI rewrite
  - type selector radio: 📁 文件推送 /  脚本执行
  - script fields: script library dropdown + textarea + timeout + long_task
  - schedule list shows type badge + detail preview
  - saveSched() validates required fields per type

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 17:29:16 +08:00
Your Name 4ca07fa252 feat: quick execute panel in scripts.html (no script save required)
Add collapsible '直接执行命令' card at top of scripts page:
- Textarea for ad-hoc command (no script_id, not saved to library)
- Multi-select server list (loaded on first expand, same batch size)
- Options: timeout, DB credential (* substitution), long_task nohup
- Ctrl+Enter shortcut to submit
- Uses same batch tracking + status panel + polling as saved scripts
- Clear button to reset command
- Batch hint shows server count and batch size
- 全选 button for quick server selection

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 17:23:25 +08:00
Your Name d2832567c7 feat: push logs view in commands.html + paginated sync log API
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
sync_log_repo.py:
- get_all_paginated(): server_id/status/sync_mode/trigger_type filters +
  server name JOIN + total count for pagination

server/api/servers.py GET /servers/logs:
- Add server_id/status/sync_mode/trigger_type/offset/limit query params
- Return {items, total, offset, limit} instead of plain list

web/app/commands.html (renamed title to '操作日志'):
- Third view: '推送日志' with paginated table
- Columns: 状态(dot badge) / 服务器 / 模式 / 触发方式 / 源路径 /
  目标路径 / 文件数 / 耗时 / 操作人 / 开始时间
- Filters: 服务器 + 状态(success/failed/running) + 模式 (incremental/full/...)
- Pagination controls with prev/next page (50 per page)
- Error message in row title attribute (hover for details)

web/app/push.html:
- Update loadHistory() to handle new {items, total} response format

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 17:20:53 +08:00
Your Name 14dc29f241 feat: Agent install tab with copy command + one-click remote install via SSH
server/api/servers.py:
- POST /{id}/install-agent: SSH-execute install.sh on managed server using
  stored credentials; requires API_BASE_URL + agent_api_key; 120s timeout
- GET /{id}/agent-install-cmd: return pre-filled curl command for copy-paste

web/app/servers.html:
- New 'Agent 安装' tab in server detail panel
  - Shows Agent online/offline status badge
  - Warns if NEXUS_API_BASE_URL or API Key is missing
  - Displays ready-to-copy curl install command with port auto-filled
  - ' 一键远程安装' button (SSH exec): disabled if already online
  - Scrollable install log output panel
  - Tab uses shared showDetailTab() + loadAgentInstall() / remoteInstallAgent()
- Explains: script auto-installs Python, downloads agent.py, writes config,
  sets up systemd, opens firewall port

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 17:16:14 +08:00
Your Name b9e8db7a3f feat: per-alert notification toggles with switch UI
config.py:
- Add NOTIFY_ALERT_CPU/MEM/DISK, NOTIFY_RECOVERY, NOTIFY_TIME_DRIFT,
  NOTIFY_SYSTEM_REDIS, NOTIFY_SYSTEM_MYSQL, NOTIFY_RESTART settings
  (string 'true'/'false', loaded from MySQL settings table at startup)

telegram/__init__.py:
- _notify_enabled(): check setting string ('false'/'0'/'no'/'off' = disabled)
- send_telegram_alert(): checks NOTIFY_ALERT_{CPU|MEM|DISK} per alert_type
- send_telegram_recovery(): checks NOTIFY_RECOVERY
- send_telegram_system_alert(): new notify_key param, checks matching setting
- send_telegram_restart_*(): check NOTIFY_RESTART

self_monitor.py:
- Pass notify_key='NOTIFY_SYSTEM_REDIS/MYSQL' to send_telegram_system_alert

server/api/agent.py:
- Pass notify_key='NOTIFY_TIME_DRIFT' to time-drift alert

settings.html:
- New '告警通知项目' section with 8 toggle switches (pill style)
- Grouped by category: 服务器资源 / 服务器 / 系统
- Optimistic toggle with revert on API failure
- renderNotifyToggles() reads _allSettings and toggleNotify() saves instantly

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 17:12:07 +08:00
Your Name 08d0baf762 feat: TOTP re-bind support + improved settings UI
settings.html TOTP section overhaul:
- Status cards: green (enabled) / amber (disabled) with context-aware buttons
- Enabled state: add '重新绑定' button to change Authenticator app/device
  without disabling first (same API, just new QR + verify flow)
- Disabled state: prominent 'Enable TOTP' CTA with warning about security
- Bind panel: shared between first-time setup and re-bind, auto-scrolls into view
- Auto-focus verify input after QR renders; Enter key submits
- QR generation: 180px, transparent background, fallback message if QRious fails
- Re-bind label distinguishes 'rebind success' from 'first enable' toast
- Disable flow: clearer confirm dialog mentioning security implication

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 17:07:40 +08:00
Your Name 9c063a788f feat: time drift detection between agent and central server
Agent (web/agent/agent.py):
- Heartbeat now includes system_info.agent_time (UTC ISO string)

Central (server/api/agent.py):
- On each heartbeat, compare agent_time vs central datetime.now(utc)
- Thresholds: warn >= 30s, crit >= 60s (affects TOTP/cron accuracy)
- Stores time_drift_seconds + drift_level in Redis heartbeat key
- Sends Telegram alert (5-min cooldown) when drift >= 60s critical

API (server/api/servers.py):
- List and detail endpoints expose time_drift_seconds + drift_level from Redis

Frontend (web/app/servers.html):
- driftBadge(): shows clock emoji badge next to server name
  - amber badge: >=30s (warn)
  - red badge: >=60s (crit)
  - tooltip shows exact drift and NTP check hint
  - no badge when drift < 30s or no data

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 17:04:34 +08:00
Your Name 5a42345bc0 docs: Nexus 6.0 功能说明书 HTML + roadmap stale fix
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 16:55:24 +08:00
Your Name 6c5e6a2daa feat: file operations (delete/rename/mkdir) + fix roadmap Step3 status
Backend:
- schemas.py: FileOperation model (server_id, operation, path, new_path)
- sync_v2.py: POST /api/sync/file-ops endpoint
  - delete: rm -rf with shlex.quote
  - rename: mv src -> dst with shlex.quote on both paths
  - mkdir: mkdir -p with shlex.quote
  - all operations audit-logged, 30s timeout

Frontend (files.html):
- Header: add 📁+ button (doMkdir via prompt)
- File rows: action buttons (rename + delete) appear on hover
  - rename: prompt for new name, inline mv
  - delete: confirm dialog with warning for dirs (rm -rf)
  - all ops refresh current directory after success
- fileOp() shared helper for POST /api/sync/file-ops

Docs:
- roadmap.md: Step 3 Web SSH updated from 70% to completed

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 16:46:00 +08:00
Your Name ac0eb79707 feat: rsync dry-run preview (方案D 智能提示型)
Backend:
- schemas.py: SyncPreview model (server_id, source/target_path, sync_mode, verbose)
- sync_engine_v2.py: preview_sync() + _parse_rsync_stats() helpers
  - runs rsync --dry-run --stats; verbose=True adds -v (capped 200 lines)
  - returns stats dict: files_transferred/created/deleted + byte counts
- sync_v2.py: POST /api/sync/preview endpoint with audit log

Frontend (push.html - 方案D):
- Sync mode radio change listener: onSyncModeChange()
- full/overwrite mode: orange warning card + prominent preview button
- incremental/checksum mode: subtle "预览首台变动 →" link
- Inline preview result section (no modal): stats cards + optional file list
- files_deleted shown in red card for destructive modes
- verbose toggle: "显示详细文件列表 ▼" triggers second request with verbose=true
- fmtBytes() helper for human-readable file sizes

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 16:41:47 +08:00
Your Name e37bc6cea9 docs: drop file editor (Monaco/ACE) - officially rejected
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 16:31:33 +08:00
Your Name 2c6f3abbd0 docs: add standards/ folder with all reusable dev & audit standards
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 16:23:50 +08:00
Your Name d5c498549b docs: system development standard v1.0 - full coding rules for Python/FastAPI/Frontend
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 16:22:36 +08:00
Your Name 07eab6020c docs: extract audit core principles as standalone reference card
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 16:19:49 +08:00
Your Name 938e700fb3 docs: line-walk audit standard v2.0 - full spec integrating mdc rules + Phase 1-4 lessons
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 16:17:27 +08:00
Your Name 424c4efd64 docs: general-purpose line-walk audit standard (reusable for any project/AI)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 16:09:28 +08:00
Your Name c08e2a2242 fix: Phase 1 audit - ssh_key columns String(500) -> Text for RSA key support
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 16:07:24 +08:00
Your Name 6108d69b24 fix: Phase 4 audit fixes
F4a-01 script_callback_rate: INCR+EXPIRE were non-atomic; a crash between
       the two could leave the rate-limit key without a TTL, permanently
       blocking future callbacks for that job/IP. Fix: use Redis pipeline
       so INCR+EXPIRE are sent in one roundtrip and EXPIRE always executes.

F4a-02 sync_service: add comment documenting that StrictHostKeyChecking=no
       is a legacy issue in the dead-code SyncService shim; main sync path
       (SyncEngineV2) honours SSH_STRICT_HOST_CHECKING from settings.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 16:00:28 +08:00
Your Name 680577fc97 docs: Phase 3 walk-closure report + index update
- docs/reports/audit-phase-3-walk-closure.md: closure declaration
  covering 3a-3i (12 new FINDINGs, all fixed, code backlog = 0)
- docs/reports/README.md: add Phase 3 closure entry

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 15:56:11 +08:00
Your Name 86bb32f87e fix: Phase 3h script_service + server_service
F3h-01 script_service._build_kill_command: shlex.quote(log_path) wraps the
       path in single-quotes; embedding that inside "..." produced a filename
       with literal single-quote chars that cat/kill could not find.
       Fix: use the shlex-quoted string directly without extra double-quotes.

F3h-02 script_service._substitute_db_vars: non-atomic string replacement
       allowed nested substitution when a credential value contained another
       $DB_* pattern. Fix: replace with null-byte sentinels first, then
       substitute actual values in a second pass.

server_service.push_to_servers: add comment documenting that audit_repo and
       retry_repo are None (internal compat shim — not used by routes).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 15:50:44 +08:00
Your Name c3c3bae370 fix: Phase 3g sync_engine_v2 bugs
F3g-01 sync_commands line 195: server_results is a list; unpacking it
       with ** raises TypeError, silently caught by gather(return_exceptions=True),
       so S2 command sync and S3 config sync returned empty results for all
       servers. Fixed by using a proper dict: {"server_name": ..., "results": ...}.

F3g-02 sync_config: config value containing newline chars was not stripped
       before shlex.quote; echo inside single-quotes would write multiple lines
       to the sysctl config file, injecting arbitrary keys. Strip \n\r\x00 from
       value before quoting (requires admin auth, defense-in-depth fix).

scripts.py, settings.py: CLEAN (no changes needed).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 15:46:52 +08:00
Your Name 33b7ed93bf fix: Phase 3e auth_service.py bugs
F3e-01 auth_service.py:180 - jwt_token_expires (MySQL tz-naive) vs
       datetime.now(timezone.utc) caused TypeError on token refresh;
       strip tzinfo before comparison when expires is tz-naive.

F3e-02 auth_service.py:271 - TOTP provisioning URI was not URL-encoding
       issuer or username; special chars (spaces etc.) broke QR scan.
       Use urllib.parse.quote() on both fields.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 15:41:50 +08:00
Your Name 61b891db73 fix: Phase 3 line-walk fixes (3a-3d)
F3a-01 agent.py: _safe_float() prevents ValueError from non-numeric metric values
F3b-01 schedule_runner.py: fix tz-aware minus naive DateTime TypeError
F3b-02 schedule_runner.py: cron field divided by 0 returns False instead of crash
F3c-01 redis/client.py: mask credentials in REDIS_URL log output
F3d-01 main.py: cancel background tasks when primary worker lock is lost
F3d-02 main.py: strip whitespace from CORS origins after split

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 15:37:15 +08:00
Your Name aa65f10c52 docs: Phase 2 audit records, project memory, WSL guide
Audit docs:
- docs/reports/audit-phase-2{a-j}-line-walk.md: per-file line walk findings
- docs/reports/audit-phase-2-findings-matrix.md: 63 findings (52 fixed, 3 partial, 7 accepted)
- docs/reports/2026-05-23-full-audit-report.md: full audit consolidation
- docs/reports/audit-full-vs-phase2-reconciliation.md: cross-reference
- docs/reports/audit-phase-2-walk-closure.md: closure sign-off

Project docs:
- docs/project/phase-2-audit-remediation.md: SSOT for all fixes
- docs/project/alert-push-policy.md: dashboard alert design decisions
- docs/project/production-verification-checklist.md: first-deploy checklist
- docs/project/script-execution.md: script platform design
- docs/project/wsl-integration-test.md: WSL code-only verification guide
- docs/project/line-walk-audit-standard.md: audit methodology
- docs/project/mysql-mcp-setup.md: MCP config guide

Changelogs: 18 entries (2026-05-22 to 2026-05-23)
Project memory: CLAUDE.md, AGENTS.md updated to reflect current state
Deploy: nginx_https.conf template updated

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 15:27:40 +08:00
Your Name 80d73d1b74 fix: WSL startup compatibility + dependency updates
- redis/client.py: use BlockingConnectionPool.from_url() (fixes 'url' kwarg error)
- database/engine_compat.py: patch aiomysql do_ping to satisfy pool_pre_ping
- database/session.py: apply engine_compat patch before create_async_engine
- requirements.txt: SQLAlchemy 2.0.49 (fixes aiomysql ping() reconnect signature)
- .env.example: document NEXUS_REDIS_URL format
- scripts/wsl_ensure_venv.sh: create project .venv (PEP 668 safe)
- scripts/wsl_start_dev.sh: use .venv python, validate Redis before start
- scripts/wsl_integration_smoke.sh: code-only verification mode
- scripts/wsl_check_mysql.py / bootstrap_database.py: MySQL setup helpers
- scripts/sync_frontend_vendor.py: vendor asset sync utility

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 15:27:18 +08:00
Your Name 752a24497c feat: Phase 2 security audit fixes + script execution platform
Security (P0/P1/P2):
- WebSSH: dedicated short-lived token, server_id binding, 4003 close code
- Remove /api/agent/exec from control plane (RCE surface eliminated)
- Global JWT middleware (JwtAuthMiddleware) with install-mode bypass
- decrypt_value: failure returns None, never leaks ciphertext
- Telegram: sanitize_external_message strips sensitive fields
- Agent auth: startup enforces non-empty API key, compare_digest
- Credentials: password_set bool flag, no plaintext in API responses
- audit_log: writes admin_username + ip_address on all CUD ops
- Install lock: all /api/install/* except GET /status return 403 post-install
- WebSocket dedup: publish once, subscribers deliver locally

Script execution platform:
- script_jobs.py / script_job_callback.py: async batching and callback
- script_execution_store.py / script_callback_rate.py: Redis-backed state
- script_execution_flush.py: background flusher to MySQL
- scripts.html / script-executions.html: full execution UI
- agent_url.py: centralised URL builder

Frontend:
- All 13 pages migrated from CDN to /app/vendor/ (no external deps)
- vendor/: alpinejs, tailwindcss-browser, xterm, xterm-addon-*, qrious
- Dashboard WebSocket real-time alerts; 8h JWT session timeout

Tests:
- test_security_unit.py: 15 unit tests (JWT, sanitize, vendor, install 403)
- test_api.py: env-configurable admin credentials

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 15:26:56 +08:00
Your Name 341d16fd6d P2迭代: 危险命令检测 + DB会话合并 + API密钥修复 + 安全审计 (6项)
P2-14: agent.py _verify_api_key() 重写文档并明确两步验证逻辑,
       全局密钥匹配直接通过,非匹配密钥放行至handler做per-server验证
P2-15: websocket.py 删除已废弃的 connected_clients=[] 别名,
       health.py 已使用 manager.client_count
P2-16: webssh.py 合并4个AsyncSessionLocal()为1个共享session(预接受操作),
       命令日志保留独立session(长连接WebSocket不能持有单session)
P2-18: 新增危险命令检测 (check_dangerous_command),识别 rm -rf /、
       fork bomb、dd写裸设备、mkfs等模式,scripts.py + webssh.py集成
P2-19: install.py 状态文件不再存储db_pass明文(步骤5已删除文件),
       审计确认所有logger调用无敏感数据泄露
P2-17: httpOnly cookie迁移标记为延期(需前后端+WebSocket全面重构,
       当前JWT+updated_at+CORS限制方案已足够安全)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 23:18:08 +08:00
Your Name d744d7df8e 安全与质量修复: P0-4/P0-5 + P1-6~P1-13 (8项)
P0-4: install.py _write_env() 值加引号转义,防止含#$\等字符的密码破坏.env格式
P0-5: install.py _build_redis_url() URL编码redis密码,防止@:等字符破坏URL解析
P1-6: auth_service.py _decode_access_token() 验证exp/sub声明存在,拒绝畸形JWT
P1-7: websocket.py + webssh.py WebSocket JWT验证增加updated_at检查,密码修改后令牌失效
P1-8: auth.py 无refresh_token的logout返回更明确的提示信息
P1-9: install.py create_admin增加_is_installed()检查,防止.env存在后重复创建
P1-10: servers.py server_stats() 改用SQL聚合查询,避免加载全部服务器对象
P1-11: heartbeat_flush.py 移除get_redis()永不返回None的死代码
P1-13: sync_engine_v2.py completed/failed计数器加asyncio.Lock防止并发竞态

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 23:04:40 +08:00
Your Name 4af9acf564 项目文档与验证脚本: AGENTS.md + 交接文档 + 审计/测试脚本
- AGENTS.md: 项目记忆(与CLAUDE.md同步)
- PROJECT_HANDOVER.md: 项目交接文档
- TEAM_ROLES.md: 团队分工与职责
- audit_scan.py: 全量代码审计扫描
- test_p1_p2.py: P1+P2功能快速验证
- verify.py: WSL模块/路由/安全验证

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 22:29:54 +08:00
Your Name 6354d507df 修复 sync_v2.py 缺少 AuditLog import (NameError)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 22:29:35 +08:00
Your Name 7e29397235 配置与文档更新: .gitignore + CLAUDE.md + API微调
- .gitignore: 添加.env/supervisor/日志等忽略规则
- CLAUDE.md: 更新项目记忆与完成状态
- assets.py: Platform/Node API端点
- settings.py: DB_OVERRIDE_MAP键名修复
- schemas.py: 模型schema补充

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 22:29:20 +08:00
Your Name 4969876ce8 新页面: 资产管理 + 命令日志 + 数据库备份脚本
- assets.html: Platform/Node资产管理页面
- commands.html: 命令/会话双视图 + 危险命令高亮
- deploy/db_backup.sh: mysqldump + 30天保留 + cron集成

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 22:29:13 +08:00
Your Name b676e320de 前端增强: XSS防护 + 共享布局 + 移动适配 + 设置页
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
- index.html: WebSocket告警文本esc()转义
- files.html: escAttr()函数 + 路径拼接转义
- layout.js: 移动端侧边栏overlay + 全局搜索 + 用户信息
- servers.html: 点击展开详情面板(系统信息/同步/SSH)
- settings.html: TOTP QR码设置 + 修改密码 + API Key复制
- api.js: JWT refresh token自动刷新 + Toast通知
- terminal.html: WebSSH xterm.js集成

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 22:29:06 +08:00
Your Name 938d26927f P1/P2: 后端安全与稳定性修复
- Agent per-server API Key认证 (agent.py)
- JWT updated_at校验与会话超时 (auth_jwt.py)
- 服务器凭据Fernet加密存储+密码脱敏 (servers.py)
- WebSSH服务器级授权检查 (webssh.py)
- Config同步去重+回滚机制 (sync_engine_v2.py)
- DB层分页offset/limit (server_repo.py)
- session.py logger修复 + @property死代码清理
- 心跳刷入rollback误用修复 (heartbeat_flush.py)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 22:28:57 +08:00
Your Name 8530f0e0d5 安全补强: 6项P0/P1漏洞修复
P0-1: PHP配置注入防护 — install.py添加_escape_php_string()转义单引号和反斜杠
P0-2: WebSocket JWT校验 — _verify_ws_token()要求exp+sub字段
P1-1: 删除SHA256密码fallback — auth_service.py和auth.py直接import bcrypt
P1-3: LIKE通配符转义 — search.py添加_escape_like()并对所有ilike()加escape参数
P1-2: 安全响应头中间件 — main.py添加SecurityHeadersMiddleware注入4个安全头
P0-3: Refresh Token重用检测 — Admin模型添加token_version字段,token格式改为token:admin_id:version

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 22:16:50 +08:00
Your Name cea5730804 P0: Add StaticFiles mount + root redirect to install wizard
Critical fix: FastAPI was not serving any static files, causing all
frontend pages (install.html, login.html, etc.) to return 404.

Changes:
- Add StaticFiles mount at /app/ for web/app/ directory (html=True)
- Redirect root path / to /app/install.html in install mode
- Remove redundant "/" path bypass in InstallModeMiddleware
- Production Nginx still serves static files directly (no conflict)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 12:24:32 +08:00
Your Name dfc37f6e90 修复设置页面键名与后端DB_OVERRIDE_MAP不匹配
settings.html引用pool_size/max_overflow/agent_heartbeat_interval
但config.py的DB_OVERRIDE_MAP期望db_pool_size/db_max_overflow/
heartbeat_timeout,导致这些设置无法从MySQL正确加载。
对齐前端键名与后端映射表。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 12:02:08 +08:00
Your Name 0bcfeaa6d6 修复心跳刷入rollback误用 + API参数规范化
- heartbeat_flush: 移除循环内session.rollback(),防止一个服务器
  刷入失败导致整个批次session状态损坏
- assets.py: 为Query参数添加Query()类型+描述+校验约束,
  统一FastAPI参数风格 (server_id/session_id/limit/parent_id)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 11:58:15 +08:00
Your Name cb5b4c42de 多Worker守护 + 告警服务器名 + 清理PHP残留
- main.py: Redis主Worker选举,防止多Worker重复执行后台任务
- agent.py: 告警/恢复广播使用真实服务器名(查MySQL)替代"server-{id}"
- sync_v2.py: 修复browse目录解析死代码,正确取parts[8]
- install.html: 移除过时install.php引用,更新为"删除.env重装"
- nginx配置: 移除PHP-FPM路由(install.html纯静态无需PHP)
- schedule_runner: 清理未使用的get_redis_sync导入

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 11:54:11 +08:00
Your Name b36e1d8010 服务器详情: 兼容多种Agent心跳字段名(cpu_percent/cpu_usage)
- normalize: cpu_percent || cpu_usage, memory_percent || mem_usage, disk_percent || disk_usage
- 防止Agent使用不同字段名时显示空白

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 11:23:19 +08:00
Your Name 8a5d4159a4 文件管理: 添加缺失的路径输入框 + Enter键触发浏览
- 添加dirPath输入框(之前只有JS引用没有DOM元素)
- Enter键支持快速跳转到指定路径
- 输入框预填默认值'/'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 11:13:23 +08:00
Your Name c03fe97d18 UX优化: 同步日志显示服务器名 + redis_url可编辑
- 同步日志API: JOIN Server表返回server_name字段
- 推送历史: 显示服务器名称替代操作人
- 仪表盘最近同步: 显示服务器名称替代server_id
- redis_url从SENSITIVE_KEYS移除(非密码,用户需可编辑)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 11:10:20 +08:00
Your Name e9feaa6cdc 前后端一致性审计 + 批量操作 + 调度同步模式
- 服务器列表: 添加批量选择(checkbox)+批量推送/删除操作栏
- 推送页面: 支持从服务器列表页预选服务器跳转(sessionStorage)
- 调度页面: 添加同步模式选择(增量/全量/校验和)
- 后端: PushSchedule模型+Schema+API+ScheduleRunner添加sync_mode字段
- 仪表盘: 移除重复的函数定义和初始化调用
- 数据库迁移: 启动时自动执行schema migration(添加sync_mode列)
- 安装向导: 添加schema migration步骤

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 11:01:17 +08:00
Your Name 83547c0b2e 服务器列表分类过滤 + 搜索增强
- 添加分类过滤下拉框(对接/api/servers/?category=参数)
- 自动从服务器数据提取分类选项填充下拉框
- 搜索框也匹配分类字段
- 添加刷新按钮
- 切换分类时自动重新加载服务器列表

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 10:11:15 +08:00
Your Name 2ec24371c5 /api/auth/me 返回 system_name 供前端品牌定制
- auth.py: /me端点现在查询settings表返回system_name字段
- layout.js的loadLayoutUser()使用此字段动态更新侧边栏品牌名
- 用户在设置页修改"系统名称"后,所有页面侧边栏自动显示

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 09:54:19 +08:00
Your Name 8ae4bc48cd 文件管理增强 + 更新项目完成度至99%
- files.html: 面包屑导航、目录排序(文件夹优先)、父目录(..)链接、
  文件所有者显示、toast反馈、owner列
- CLAUDE.md: 更新实现状态,新增Phase E UX增强条目(S1-S3安全,
  UX1-UX10前端体验),完成度从95%提升至99%

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 09:49:10 +08:00
Your Name 9d34655cc1 fix: Alpine.js x-data缺失 + 登录页429锁定处理
- index.html/servers.html: 添加缺失的 x-data="{sidebarOpen:true}"
  (Alpine.js需要初始化sidebarOpen变量)
- login.html: 正确处理429状态码(账户临时锁定),显示锁定提示

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 09:46:23 +08:00
Your Name 249a1aaed9 重试队列增强 + 审计分页
- 后端: 添加 POST /api/retries/{id}/retry (手动重试) + DELETE /api/retries/{id}
  + 审计日志分页(offset/limit/count) + PushRetryJobRepo新增get_all/get_by_id/delete
- 前端retries: 手动重试按钮、删除按钮、状态过滤、操作人显示、toast反馈
- 前端audit: 分页控件(上一页/下一页)、每页50条、新增retry_job/delete_retry过滤
- 修复index.html: 适配审计API新返回格式(data.items)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 09:41:33 +08:00
Your Name 120ae186ef 统一所有页面到layout.js共享布局组件
- 将11个业务页面(index/servers/files/scripts/credentials/settings/terminal等)
  的内联侧边栏全部迁移到layout.js的initLayout()共享组件
- 移除各页面重复的loadUser()函数,统一由layout.js处理
- 统一全局搜索功能、用户信息加载、品牌名称显示
- 消除约800行重复的侧边栏HTML代码

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 09:29:25 +08:00
Your Name cca410da33 P2: 推送页面增强 + 登录页安全强化
- push.html: 完整重写 — 逐服务器状态显示(成功/失败/等待)、进度条、
  同步模式选择卡片、并发/批次配置、全选/全不选、推送历史记录、toast反馈
- login.html: 密码可见性切换(eye图标)、失败次数提示(3次/5次警告)、
  登录失败时卡片shake动画、锁定警告提示区、迁移layout.js

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 09:23:38 +08:00
Your Name 26833f00d3 P1: Toast通知系统 + 脚本编辑/执行结果格式化
- api.js: 添加全局toast()通知函数,支持success/error/warning/info四种类型
- scripts.html: 完整重写 — 点击脚本名编辑、执行结果按服务器格式化显示(command/stdout/stderr/exit_code)
- servers.html/credentials.html/schedules.html/settings.html: 所有操作添加toast反馈

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 09:20:44 +08:00
Your Name d4ea03d735 P2: 全局搜索功能 — 后端API + 前端侧边栏搜索
- 新增 /api/search/ 端点: 跨服务器/脚本/凭据/调度搜索
- layout.js: 侧边栏底部搜索框, 300ms防抖, 下拉结果面板
- main.py: 注册搜索路由
2026-05-22 09:08:09 +08:00
Your Name f262876b25 P2: 共享侧边栏布局组件 + 4个页面迁移
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
- 新增 layout.js: initLayout() 自动生成侧边栏+高亮+用户信息
- retries/audit/schedules 已迁移到共享布局
- 减少约200行重复HTML代码
2026-05-22 09:05:49 +08:00
Your Name bfa50337e5 P1+P2: 前端功能增强
- servers.html: 服务器详情面板(系统信息/同步日志/SSH会话三标签页)
- index.html: 仪表盘增强(分类分布图/最近同步日志/自动刷新30s)
- credentials.html: 密码预设标签页(对接/api/presets/ CRUD)
2026-05-22 09:03:18 +08:00
Your Name 305f8d5689 P1: 全局JWT保护 + 审计日志补全 — scripts/assets/servers
- scripts.py: 所有9个端点添加JWT认证 + CUD操作审计日志
- assets.py: 所有GET端点添加JWT + 写操作审计日志补全
- servers.py: GET端点JWT保护(上轮未提交)
- WSL全量验证通过: ALL CHECKS PASSED
2026-05-22 08:55:34 +08:00
Your Name 785ffe2a7e fix: 设置页面敏感字段显示为只读锁定状态
脱敏值(包含***或...)显示为只读文本+🔒标记,防止用户编辑部分遮蔽值
2026-05-22 08:47:16 +08:00
Your Name 55b7cbe904 fix: P1安全加固 — 全局JWT保护 + 审计日志补全 + 敏感字段过滤
Settings API:
- 所有端点添加JWT认证(get_current_admin)
- 不可改项(secret_key/api_key/encryption_key/database_url)禁止PUT修改
- 敏感字段GET响应自动脱敏(前8位+...)
- Schedules/Presets/Retry CRUD添加JWT+审计日志

Servers API:
- POST/PUT/DELETE添加JWT认证
- 服务器增删改写审计日志

Assets API:
- Platform/Node写操作添加JWT认证
- Platform创建添加审计日志
2026-05-22 08:46:09 +08:00
Your Name b9139093f1 fix: 更新.env.example + CLAUDE.md反映D2迁移
- .env.example: install.php→install.html引用, 添加NEXUS_API_BASE_URL
- install.py: 移除_write_env中重复的site_url计算
- CLAUDE.md: 全面更新项目记忆, 反映6步完成+D1/D2/D3解决
2026-05-22 08:39:33 +08:00
Your Name f796ddf0a5 feat: D2 install.php→install.html迁移 + D3 WebSSH终端完善
D2: install.php → install.html + FastAPI API
- 新增 server/api/install.py: 6个安装向导API端点(无JWT)
- 新增 web/app/install.html: Alpine.js五步安装向导
- main.py: 条件启动模式(无.env=安装模式,仅/api/install/可用)
- InstallModeMiddleware: 安装模式下非安装路由返回503
- DbSessionMiddleware: 跳过安装API(自管理临时引擎连接)
- 三写一致性: .env + config.php + MySQL settings表同步

D3: WebSSH terminal.html 完善
- 全功能xterm.js终端 + Koko协议 + 自动resize
- Alpine.js + Tailwind CSS v4 统一风格
- 全屏/断开连接/连接状态指示器

文档更新:
- status.md: 第五步→, 完成度~95%
- roadmap.md: D2技术债务已解决
2026-05-22 08:37:01 +08:00
Your Name c9a99f4fb3 fix: 全项目文档对齐 + 代码清理
代码修复:
- web/agent/agent.py: datetime.utcnow() → datetime.now(timezone.utc)
- requirements.txt: 移除 paramiko==3.5.0 (已无活跃引用)
- 删除 server/infrastructure/ssh/pool.py (DEPRECATED, 无引用)

连接池三层对齐 (config.py/.env.example/session.py):
- DB_POOL_SIZE 100→160, DB_MAX_OVERFLOW 100→120
- 基于 MySQL max_connections=400, install.php 公式

文档修复 (21项):
- P0: 硬编码域名/IP替换为配置变量占位符
- P1: 10个过时设计文档加归档标注 (引用旧文件结构)
- P2: step-3-webssh报告 paramiko引用修正
- 6份审查报告连接池参数勘误 100/100→160/120
- ECC安全报告 EC5 datetime.utcnow 标记已修复
- docs/README.md 文档索引重写
- docs/memory/mem_nexus_overview.md 移除硬编码凭证
- docs/project/tech-stack-inventory.md paramiko标记已移除

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 08:19:56 +08:00
Your Name 8c8d2a74d5 docs: add Vercel-style documentation browser
Tailwind CSS + Alpine.js single-page HTML doc viewer with:
- 274 documents embedded (270 md + 4 other file types)
- Dark/light mode with localStorage persistence
- Sidebar navigation with grouped collapsible sections
- Full-text search (filename + content)
- Auto-generated TOC for each document
- Terminal-style code blocks with language labels
- Mobile responsive with slide-out sidebar
- rebuild: python docs/build_html.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 07:26:31 +08:00
Your Name 302fdcc1a1 test: update E2E test suite for v6 API + JWT auth
- Login first to acquire JWT token for all protected routes
- Use correct v6 API paths (/api/presets/, /api/schedules/, /api/scripts/)
- Add tests for: scripts CRUD, schedules toggle, settings, audit, sync browse
- Remove obsolete password-presets and retry-jobs paths

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 00:39:03 +08:00
Your Name 5590391779 feat: Nexus Agent rebrand + server_id + exec endpoint + trigger_type
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
Agent (agent.py):
- Rebrand MultiSync → Nexus throughout
- Add server_id to heartbeat payload (required by backend AgentHeartbeat schema)
- Add /exec endpoint for remote command execution via Nexus
- Update heartbeat fields to match backend: cpu_usage, mem_usage, disk_usage
- Use 60s default interval (matches CLAUDE.md spec)

Agent (agent.sh):
- Rebrand, add SERVER_ID env var, send in heartbeat payload
- Update service name to nexus-agent

Agent (install.sh):
- Rebrand, add --id parameter for server_id
- Write server_id to config.json
- Service name: nexus-agent
- Add config.example.json for reference

SyncEngineV2:
- Add trigger_type parameter to sync_files() (was hardcoded "manual")
- Schedule/retry runners can now pass "schedule"/"retry" trigger types

Nginx:
- Add /agent/ to static asset locations for agent file downloads

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 00:37:39 +08:00
Your Name 56993e4f98 fix: auth logout, Nginx configs, missing dependency, CORS
- auth: logout endpoint now accepts refresh_token (was admin_id which
  frontend can't know) — added logout_by_token() to AuthService
- api.js: sendBeacon uses Blob with application/json content-type
  (was sending text/plain which FastAPI couldn't parse)
- Nginx: fixed SPA fallback to 404 (not SPA), added install.php
  PHP-FPM handler, added production HTTPS config for api.synaglobal.vip
- requirements: added cryptography==44.0.0 (used by crypto.py but
  was missing from requirements.txt)
- models: removed unused Fernet import from domain models
- CORS: added http://api.synaglobal.vip origin

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 00:30:28 +08:00
Your Name cd3c35b5e6 fix: add per-field save buttons to settings page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 00:04:29 +08:00
Your Name f0c586a345 feat: complete frontend interactive features + unified sidebar
- credentials: add credential CRUD form (type, host, port, user, password, db)
- schedules: add enable/disable toggle, edit modal, server selection
- retries: add status badges, retry progress bar, error display, refresh
- audit: add action filter dropdown, IP column, better status badges
- settings: reorganize into sections (brand/alert/infra/telegram)
- servers: add/edit modal with full form fields
- scripts: add execute modal + delete button
- All pages: unified sidebar with all nav items + hover states
- install.php: fix post-AdminLTE removal link paths

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 00:02:46 +08:00
Your Name 00a6491e59 chore: remove AdminLTE PHP frontend (keep only install.php)
All functionality now covered by web/app/*.html (Alpine.js + Tailwind).
Deleted: 29 PHP pages, ace editor, chart.min.js, app.js, style.css.
Kept: install.php (installation wizard with DB table creation + .env write).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 23:42:21 +08:00
Your Name 7caa880ebd feat: unified api.js auth + Pydantic models + JWT security hardening
- All Alpine.js pages now use shared api.js for JWT auto-refresh (9 pages)
- Dashboard uses /servers/stats endpoint instead of fetching all servers
- Replaced all raw dict params with Pydantic models (agent, settings, presets)
- Added AgentHeartbeat, AgentExec, SettingUpdatePayload, DbCredentialCreate schemas
- JWT decode now requires exp+sub claims; better error logging
- ServerService.push_to_servers delegates to SyncService.batch_push
- SyncService handles None audit_repo/retry_repo gracefully
- Branding: web/app.js + web/style.css MultiSync→Nexus

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 23:38:01 +08:00
Your Name a584792603 feat: Pydantic request models + pagination + JWT auto-refresh
- server/api/schemas.py: New shared Pydantic models for all API routes
  (ServerCreate/Update/Push/Check, SyncFiles/Commands/Config/Sftp/Browse,
  ScriptCreate/Update/Execute, ScheduleCreate/Update, PlatformCreate/Update,
  NodeCreate/Update, PaginatedResponse)
- servers.py: Replace raw dict with Pydantic models, add pagination
  (page/per_page params, response now includes items/total/pages)
- sync_v2.py: Replace raw dict with SyncFiles/Commands/Config/Sftp/Browse
- scripts.py: Replace raw dict with ScriptCreate/Update/Execute
- assets.py: Replace raw dict with PlatformCreate/Update, NodeCreate/Update
- web/app/api.js: Shared JS auth module with JWT auto-refresh (2min
  before expiry), 401 retry with refresh token, doLogout with
  sendBeacon to backend, backward-compatible ah() alias
- web/app/servers.html: Use api.js, handle paginated response format

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 23:18:49 +08:00
Your Name 1a27327036 fix: PHP frontend JWT auth + API endpoints + missing tables + branding
- login.php: Fix JWT response format (success/access_token vs status=ok),
  store JWT tokens in session, support TOTP 202 flow
- api_client.php: Dual auth (JWT Bearer + X-API-Key fallback), add
  auto-refresh on 401, fix API endpoints (checkServerHealth → array,
  getServerLogs, getAuditLogs, getDashboardStats field mapping)
- api_proxy.php: Fix check_all to use /api/servers/check (not per-server),
  fix get_logs to support global endpoint, restart_multisync → restart_nexus
- install.php: Add 4 missing tables (platforms, nodes, ssh_sessions,
  command_logs), fix table creation order for FK dependencies, add JWT
  columns to admins, add new indexes, add platform_id/node_id/protocols/
  extra_attrs/connectivity to servers
- logout.php: Call /api/auth/logout to invalidate JWT refresh token
- index.php: Use JWT Bearer auth for API calls
- server_service.py: Implement real check_all_servers with concurrent
  httpx Agent health checks (was stub)
- servers.py: Add GET /api/servers/logs global sync logs endpoint
- Branding: MultiSync → Nexus across all PHP files (sidebar, titles,
  TOTP issuer, version strings)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 23:11:30 +08:00
Your Name 814e11588e feat: P1 background tasks + server stats endpoint
1. schedule_runner.py: cron-based schedule execution every 60s
   - Parses 5-field cron expressions (*, */N, ranges, lists)
   - Triggers SyncEngineV2.sync_files for due schedules
   - Prevents re-trigger within same minute
2. retry_runner.py: automatic retry of failed pushes every 5min
   - Exponential backoff (60s base, doubles per retry, max 64x)
   - Respects max_retries from PushRetryJob model
3. GET /api/servers/stats: dashboard aggregation endpoint
   - Returns total/online/offline counts + category breakdown
   - Reads real-time status from Redis, falls back to MySQL
   - Registered before /{id} to avoid path collision
4. main.py: register schedule_runner + retry_runner as background tasks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 22:58:17 +08:00
Your Name 73c1776e1e fix: P0 critical runtime issues
1. conftest.py: add missing `from server.main import app` import
2. dependencies.py: get_db() reuses middleware session instead of
   creating independent AsyncSessionLocal (fixes double-session bug)
3. auth_jwt.py: get_optional_admin uses request.state.db instead of
   creating leaked AsyncSessionLocal session
4. config.py: default DATABASE_URL uses mysql+aiomysql (not pymysql)
5. sync_engine_v2.py: sanitize config keys with regex, escape values
   to prevent command injection in sync_config
6. sync_v2.py: add POST /api/sync/browse endpoint for remote dir listing
7. files.html: browseDir calls real API instead of placeholder stub

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 22:54:35 +08:00
Your Name ead4b2580f fix: 3 code bugs + test fixes (46/46 passing)
Nexus CI/CD / test (push) Waiting to run
Nexus CI/CD / deploy (push) Blocked by required conditions
Nexus Pre-commit Checks / quick-check (push) Waiting to run
1. infrastructure/__init__.py: remove nonexistent get_ssh_pool import,
   use lazy __getattr__ to avoid triggering MySQL engine at import time
2. auth_service.py + auth_jwt.py: JWT sub field must be string
   (PyJWT 2.12+ enforces RFC 7519), decode with int() conversion
3. session.py: lazy engine init — engine created on first access
   instead of module import time, allows testing without MySQL
4. Tests: fix Column default assertions (SQLAlchemy defaults only
   apply on DB INSERT), fix Request.url mock for Starlette

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 22:30:34 +08:00
Your Name 3ba85986ed docs: update 194 skill files with cleaned group tags + metadata
All skill files group labels cleaned (no duplicates, no garbled text).
CMS developer renamed to frontend developer in AG group.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 22:12:57 +08:00
Your Name 3ecd3d75d4 docs: reports + research + team roster + tech-stack evaluation
Add implementation progress reports, review reports (CTO/quality/deploy),
session log, signoff table, team roster, collaboration charter,
tech-stack evaluation and inventory, research reports.
Remove obsolete multisync_server.py.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 22:12:29 +08:00
Your Name 539c33ef42 feat: Nexus 6.0 6-step implementation (Step 0-5)
Step 0: Infrastructure hardening — Redis BlockingConnectionPool,
WebSocket two-layer (memory+Redis Pub/Sub), JWT auth, DB session leak fix
Step 1: Data layer — 4 new tables (platforms/nodes/command_logs/ssh_sessions),
data migration (category→Node), repos + API routes
Step 2: Auth layer — JWT middleware on all routes, TOTP JWT integration
Step 3: Web SSH — asyncssh connection pool, /ws/terminal endpoint,
xterm.js frontend, Koko protocol
Step 4: Sync engine v2 — file/command/config/SFTP modes, parallel execution
Step 5: Frontend migration — 12 Tailwind CSS v4 + Alpine.js pages,
PHP-FPM removal from nginx config

21 Python backend + 12 HTML frontend + 2 deploy configs + 3 test files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 22:11:38 +08:00
Your Name e5b0c2d4ef ci: restore gitea workflows 2026-05-21 05:55:02 +08:00
Your Name 4cfed27bf7 docs: superpowers design specs + plans 2026-05-21 05:50:24 +08:00
Your Name 003ca96f07 tests: load_test + quick_load + test_api 2026-05-21 05:49:05 +08:00
Your Name 9e20898fd0 mcp: multisync_server + bridge + firefox 2026-05-21 05:48:19 +08:00
Your Name cad49e6de1 docs: memory knowledge base + settings 2026-05-21 05:47:42 +08:00
Your Name 06f1e9ee07 docs: 194 team skills 2026-05-21 05:43:51 +08:00
Your Name 55e14805de install.php: auto-detect site URL + simplified DB config + auto-configure process guardian
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 05:42:55 +08:00
Your Name ae94101eb8 docs: add CLAUDE.md project memory for agent continuity 2026-05-20 17:29:02 +08:00
Your Name f9045a8404 refactor: 仓库结构扁平化 + PHP前端合并到Nexus仓库
- 将 Nexus/Nexus/* 移到仓库根目录(消除双层嵌套)
- 删除旧的多层空壳目录 (server/, web/, agent/, deploy/, docs/)
- 将PHP前端 (web/) 合并进Nexus仓库 — 统一管理
- 部署时 git clone Nexus 仓库即可,不再需要两个仓库

目录结构:
  server/     ← Python FastAPI后端
  web/        ← PHP前端 (install.php, config.php, etc)
  deploy/     ← Supervisor + Shell健康检查
  docs/       ← 部署文档
  tests/      ← 测试
  .env        ← 不在git中 (install.php生成)
  .gitignore  ← 排除 .env, SECRETS.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 17:24:21 +08:00
Your Name 05600232b7 feat: Nexus 6.0 Phase A+B — 心跳系统 + 3层守护 + Telegram告警 + install.php重写
Phase A (Python Backend):
- E1: WebSocket alert/recovery broadcast (server/api/websocket.py)
- E2: Agent heartbeat → Redis write + alert detection (server/api/agent.py)
- E3: Redis→MySQL 10min batch flush (server/background/heartbeat_flush.py)
- E4: load_settings_from_db() + threshold fields (server/config.py)
- E5: Python self-monitor 30s loop (server/background/self_monitor.py)
- E6: Telegram Bot push module (server/infrastructure/telegram/)
- E7: Server listing reads Redis for live status (server/api/servers.py)
- E8: /health endpoint for Shell health_monitor.sh (server/api/health.py)
- E9: Lifespan startup with background tasks (server/main.py)

Phase C (DevOps):
- D1: Supervisor config (deploy/nexus.conf)
- D2: Shell health_monitor.sh (deploy/health_monitor.sh)

Also:
- .gitignore: restored .env + SECRETS.md exclusion (never commit)
- Agent heartbeat: Redis buffer → frontend direct read → 10min → MySQL
- Alert detection: CPU/mem/disk > threshold → WebSocket + Telegram push
- Recovery detection: previously alerted metrics normal → auto-push recovery

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 17:02:45 +08:00
Your Name 98cb33c3d6 Add .env + SECRETS.md for test deployment (private repo)
Temporary: includes real credentials for deployment testing.
Will be removed from .gitignore when switching to formal repo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 15:46:11 +08:00
Your Name 2cf7602573 Add SECRETS.md to .gitignore — prevent credential leak
SECRETS.md is a local-only file for storing database passwords,
API keys, and server credentials. It must never be committed to Git.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 15:37:47 +08:00
Your Name 669a62b17a Update DEPLOY.md: production domain + clean sensitive values
- Domain: api.synaglobal.vip (强制 HTTPS)
- Website directory: /www/wwwroot/api.synaglobal.vip
- Supervisor config using .env (no hardcoded passwords)
- MySQL create DB command uses placeholder password
- Nginx config with 宝塔 SSL path and PHP-FPM socket
- All sensitive values removed from doc — safe for Git

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 15:36:47 +08:00
Your Name 99e23d7e78 Phase 1 complete: Repository implementations + Service layer + API routes + Deploy docs
- All 11 Repository Protocol interfaces (Admin, Setting, AuditLog, PushSchedule,
  PushRetryJob, PasswordPreset + existing 5)
- All 11 async SQLAlchemy Repository implementations
- 4 Application Services (ServerService, ScriptService, AuthService, SyncService)
- FastAPI DI wiring in dependencies.py (repos → services)
- 6 API route modules (servers, auth, agent, scripts, settings/schedules/presets/audit/retries)
- Agent /api/exec endpoint for remote shell command execution
- Batch push engine with asyncio.Semaphore(10) concurrency control
- TOTP authentication + brute-force protection (5 failures / 15 min lockout)
- DB credential management with $DB_* variable substitution
- Ubuntu deployment guide (docs/DEPLOY.md) with dependency fix script

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 14:42:55 +08:00
Your Name 5ae8855894 Add CI/CD pipeline: Gitea Actions (test + lint + deploy)
CI (every push):
- pytest with coverage (>=50% gate)
- ruff lint check
- mypy type check (non-blocking)

CD (main branch only, after CI passes):
- SSH deploy to production server
- git pull + pip install + supervisorctl restart

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 14:25:31 +08:00
Your Name b2617b4e31 Nexus v6.0.0: Initialize project structure with Clean Architecture
- Project brand: Nexus (configurable via settings.SYSTEM_NAME)
- Clean Architecture 4-layer directory structure:
  server/domain/models/ — SQLAlchemy models
  server/domain/repositories/ — Abstract interfaces (Protocol)
  server/infrastructure/database/ — Async SQLAlchemy session + crypto
  server/infrastructure/redis/ — Decoupled Redis client
  server/infrastructure/ssh/ — Unified SSH connection pool
  server/application/services/ — Business logic (TODO)
  server/api/ — FastAPI routes (TODO)
- Config: Python 3.12+, PHP 8.2+, MySQL 8.4 LTS, Redis 7.0+
- Async SQLAlchemy (aiomysql) replacing sync pymysql
- Test framework: pytest + async fixtures + Playwright (conftest.py)
- MCP server address: 47.254.123.106

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 14:19:09 +08:00
822 changed files with 3855 additions and 56006 deletions
-44
View File
@@ -1,44 +0,0 @@
---
description: 减少盲目搜文件、控制 Read 次数与 token 消耗
alwaysApply: true
---
# Agent 探索约束(省 token
## 开始前
1. 读根目录 `AGENTS.md` 的「代码地图」与「问题 → 文件」表
2. 架构/API 全览只读 `docs/project/nexus-functional-development-guide.md`(或用户指定的 §),禁止为找结构全库 Grep
3. 用户已给出路径/文件名时,只读该路径,禁止扩大范围
## 搜索顺序
```
AGENTS.md / 功能指南 § → Grep(带 path)→ Read(≤3 个文件)→ 仍不够再扩一圈
```
- **禁止**无 `path` 的全仓库 `Grep` / `Glob`(除非用户明确要求全库)
- **Grep 优先于 Read**Glob 只用于定位文件名,不逐个 Read 全部匹配
- 同一任务 **Read 不超过 5 个文件**;找到答案后立即停止
- 不要重复 Read 已在本对话出现过的文件
## 分层定位(禁止跨层乱搜)
| 层 | 路径 |
|----|------|
| HTTP 路由 | `server/api/<module>.py` |
| 业务 | `server/application/services/` |
| 数据访问 | `server/infrastructure/` |
| 模型 | `server/domain/models/` |
| 前端页 | `frontend/src/pages/` |
| 前端路由 | `frontend/src/router/index.ts` |
| 前端 API 客户端 | `frontend/src/api/` |
真实前端源码在 `frontend/src/`,不是 `web/app/*.html`(那是构建产物)。
## 文档索引
- 功能 SSOT`docs/project/nexus-functional-development-guide.md`
- 文档导航:`docs/README.md`
- 本地路径/MCP`docs/project/linux-dev-paths.md`
- 运维(连机/exec):用户 Skill `Nexus平台` 或 `docs/project/nexus-1panel-operations-knowledge.md`
-7
View File
@@ -1,7 +0,0 @@
{
"plugins": {
"compound-engineering": {
"enabled": true
}
}
}
+1 -6
View File
@@ -38,16 +38,11 @@ FastAPI + Async SQLAlchemy + Redis · Vue 3 + Vuetify 4 SPA15 页)· Vite
## 部署
默认 **本机 rsync 直传**(不 push Gitea):
```bash
bash deploy/pre_deploy_check.sh && bash deploy/deploy-production.sh
```
仅同步源码(不重建镜像):`bash deploy/rsync-local-to-server.sh`
仍走 Gitea 拉取:`NEXUS_DEPLOY_VIA_GIT=1 bash deploy/deploy-production.sh`
前端: `cd frontend && npx vite build` → 部署脚本内 tar/scp 到 `web/app/`
前端: `cd frontend && npx vite build` → tar/scp(见 `deploy/deploy-frontend.sh`
## 进度条(改代码时输出)
+29 -40
View File
@@ -1,40 +1,29 @@
.git
.gitignore
.claude
.cursor
**/__pycache__
**/*.pyc
**/.pytest_cache
**/.mypy_cache
**/.ruff_cache
.coverage
htmlcov/
frontend/node_modules
frontend-v2/node_modules
frontend/dist
node_modules
.venv
.venv-*
venv
.pytest_cache
.ruff_cache
.playwright-mcp
2025.2
=2025.2
*.bak-
.env
docker/.env
docker/runtime
tmp/
tests/
docs/
standards/
*.md
!docker/README.md
web/uploads
backups/
deploy/gate_log.jsonl
.git
.gitignore
.claude
.cursor
**/__pycache__
**/*.pyc
**/.pytest_cache
**/.mypy_cache
**/.ruff_cache
.coverage
htmlcov/
frontend/node_modules
frontend/dist
node_modules
.env
docker/.env
docker/runtime
tests/
docs/
standards/
*.md
!docker/README.md
web/uploads
backups/
deploy/gate_log.jsonl
+63
View File
@@ -0,0 +1,63 @@
# Nexus Environment Configuration
# Copy this file to .env and modify values for your deployment.
# Or run the web installer at /app/install.html to auto-generate this file.
#
# Generate secret keys: openssl rand -hex 32
# ── Brand ──
NEXUS_SYSTEM_NAME=Nexus
NEXUS_SYSTEM_TITLE=Nexus — 服务器运维管理平台
# ── Server ──
NEXUS_HOST=0.0.0.0
NEXUS_PORT=8600
# ── Deployment (auto-set by installer) ──
NEXUS_DEPLOY_PATH=/opt/nexus
# Comma-separated frontend origins for CORS
NEXUS_CORS_ORIGINS=http://localhost:8600
# Base URL for Agent heartbeat reporting
NEXUS_API_BASE_URL=http://localhost:8600
# ── Database (MySQL, REQUIRED) ──
NEXUS_DATABASE_URL=mysql+aiomysql://root:password@127.0.0.1:3306/nexus
# Pool params: auto-calculated by installer from MySQL max_connections
# Formula: pool_size = max(20, maxConn*0.4), overflow = max(20, maxConn*0.3)
# Default below matches MySQL max_connections=400 (160+120=280 max)
# install wizard will overwrite these with values tuned to your MySQL instance
NEXUS_DB_POOL_SIZE=160
NEXUS_DB_MAX_OVERFLOW=120
# ── Security (REQUIRED — generate with: openssl rand -hex 32) ──
NEXUS_SECRET_KEY=change-me-use-openssl-rand-hex-32
NEXUS_API_KEY=change-me-use-openssl-rand-hex-32
NEXUS_ENCRYPTION_KEY=
# ── Redis (REQUIRED) ──
NEXUS_REDIS_URL=redis://127.0.0.1:6379/0
# ── SSH ──
NEXUS_SSH_STRICT_HOST_CHECKING=false
# ── Alert Thresholds (%) ──
NEXUS_CPU_ALERT_THRESHOLD=80
NEXUS_MEM_ALERT_THRESHOLD=80
NEXUS_DISK_ALERT_THRESHOLD=80
# ── Health Check ──
NEXUS_HEALTH_CHECK_INTERVAL=60
# ── Telegram Alerts (optional — configure in Settings UI) ──
NEXUS_TELEGRAM_BOT_TOKEN=
NEXUS_TELEGRAM_CHAT_ID=
# ── MySQL MCP (@yclenove/mysql-mcp-server — run: python scripts/sync_mysql_mcp_env.py) ──
# Cursor 从项目根 .env 读取;与 NEXUS_DATABASE_URL 保持一致;默认只读
# MYSQL_HOST=127.0.0.1
# MYSQL_PORT=3306
# MYSQL_USER=root
# MYSQL_PASSWORD=password
# MYSQL_DATABASE=nexus
# MYSQL_READONLY=true # production / MCP read-only
# MYSQL_READONLY=false # local dev: full DML/DDL via MCP
# MYSQL_DATABASE_ALLOWLIST=nexus
+89 -90
View File
@@ -1,90 +1,89 @@
# Nexus — Python
__pycache__/
*.py[cod]
*$py.class
*.egg-info/
build/
.eggs/
# Environment (NEVER commit — contains production credentials)
.env
.install_locked
.install_state.json
SECRETS.md
.git-askpass-nexus.sh
deploy/nexus-1panel.secrets.sh
*.pem
.env.*
.venv/
.venv-*/
venv/
ENV/
# Agent memory (local MCP persistence, do not commit)
.claude/mcp-memory.jsonl
# MCP tool data (local development artifacts)
.megamemory/
.playwright-mcp/
frontend/test-results/
frontend/e2e/.auth/
tmp/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Logs
*.log
nexus.log
# Node offline cache for WSL install (large tarball)
.cache/
# Test
.pytest_cache/
htmlcov/
.coverage
coverage.xml
# Node (for Tailwind/Alpine.js build)
node_modules/
web/css/tailwind-output.css
# Build output (Vite/Vuetify frontend)
# SPA 构建产物纳入 Git,便于服务器 git pull 部署(见 deploy/deploy-frontend.sh
# 仍忽略本地临时文件
web/app/assets/.tmp/
# Legacy dist dir (no longer used)
**/dist/
# Uploads
web/uploads/
# Data (sensitive)
web/data/config.php
web/data/*.db
web/data/1panel-hosts.json
# Backups
backups/
# Docker local secrets
docker/.env
docker/.env.prod
!docker/.env.example
!docker/.env.prod.example
scripts/npm-proxy.env
# Deploy
deploy/*.key
# Backups
backups/
# Nexus — Python
__pycache__/
*.py[cod]
*$py.class
*.egg-info/
build/
.eggs/
# Environment (NEVER commit — contains production credentials)
.env
.install_locked
.install_state.json
SECRETS.md
.git-askpass-nexus.sh
deploy/nexus-1panel.secrets.sh
*.pem
.env.*
.venv/
venv/
ENV/
# Agent memory (local MCP persistence, do not commit)
.claude/mcp-memory.jsonl
# MCP tool data (local development artifacts)
.megamemory/
.playwright-mcp/
frontend/test-results/
frontend/e2e/.auth/
tmp/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Logs
*.log
nexus.log
# Node offline cache for WSL install (large tarball)
.cache/
# Test
.pytest_cache/
htmlcov/
.coverage
coverage.xml
# Node (for Tailwind/Alpine.js build)
node_modules/
web/css/tailwind-output.css
# Build output (Vite/Vuetify frontend)
# SPA 构建产物纳入 Git,便于服务器 git pull 部署(见 deploy/deploy-frontend.sh
# 仍忽略本地临时文件
web/app/assets/.tmp/
# Legacy dist dir (no longer used)
**/dist/
# Uploads
web/uploads/
# Data (sensitive)
web/data/config.php
web/data/*.db
web/data/1panel-hosts.json
# Backups
backups/
# Docker local secrets
docker/.env
docker/.env.prod
!docker/.env.example
!docker/.env.prod.example
scripts/npm-proxy.env
# Deploy
deploy/*.key
# Backups
backups/
-31
View File
@@ -1,31 +0,0 @@
---
name: nexus-btpanel-review
description: Use when reviewing or changing Nexus BT/宝塔 panel integration, one-click login, API bootstrap, session keepalive fixes, or BT credential handling.
---
# Nexus BT Panel Review
Use for 宝塔/BT panel integration changes.
## Call chain to map
API one-click login -> btpanel_service -> BT API credential lookup/bootstrap -> SSH bootstrap if needed -> login URL/tmp_token -> panel session behavior.
## Required checks
- Do not log or return BT API keys, session cookies, temporary login tokens, SSH private keys, or passwords.
- One-click login should fail closed for real authentication errors, but best-effort environment repair must not block login unless required for correctness.
- Session keepalive/cleanup repair must be idempotent and throttled; do not restart or rewrite BT panel aggressively without explicit approval.
- Respect existing BT panel services and ports. Do not occupy panel, nginx, redis, or mysql ports.
- Old servers with existing BT API credentials must still receive the session-cleanup TTL check before login.
- Record bootstrap state without storing secrets.
## Regression tests
Cover:
- Existing credentials still trigger throttled TTL check.
- Missing SSH auth does not block login URL generation.
- Failed repair is throttled and retried later.
- Temporary login token TTL remains bounded.
- No secret values appear in returned bootstrap/status payloads.
-40
View File
@@ -1,40 +0,0 @@
---
name: nexus-security-review
description: Use when reviewing or changing Nexus backend/API/security-sensitive code. Focuses on FastAPI admin APIs, SQLAlchemy data access, SSH operations, file manager boundaries, logging redaction, and regression tests.
---
# Nexus Security Review
Use this skill before changing Nexus backend/API code or doing a security pass.
## Required workflow
1. Identify entrypoints: FastAPI router, service method, DB model/session, Redis/lock usage, SSH/BT panel call.
2. Draw the request path in notes: API -> schema -> service -> DB/Redis/SSH/external.
3. Check auth first: admin-only endpoints must depend on the current admin dependency; background/admin tools must still preserve audit logs.
4. Validate every user-controlled value at the schema/API boundary and again before shell/path-sensitive sinks.
5. For every fix, add pytest coverage for the dangerous case and the allowed case.
6. Run focused tests, then full pytest when practical, then git diff --check.
7. Update the Nexus Markdown report with changed files, tests, and residual risks.
## Nexus-specific rules
- Do not print secrets: passwords, SSH keys, tokens, cookies, API keys, .env values, BT panel credentials.
- script_service is an intentional administrator remote shell. Do not report arbitrary shell execution there as a vulnerability. Review only auth, audit trail, timeout, output truncation, and redaction.
- File manager APIs must not unexpectedly operate outside the target path/server chosen by the admin UI.
- Any remote shell command must use shlex.quote; add -- where command options can be confused with filenames.
- Archive extraction must list and validate members before extraction; reject absolute paths, .., backslashes, and special file types.
- Error responses must not expose stack traces or secrets.
- DB access should use SQLAlchemy parameters/query builder, not raw string interpolation.
## Review output
For each finding record:
- Severity: Critical/High/Medium/Low/Hardening
- Call chain
- Evidence file/function
- Exploit or failure boundary
- Fix summary
- Test command/result
- Commit hash if committed
-29
View File
@@ -1,29 +0,0 @@
---
name: nexus-ssh-safety
description: Use when reviewing or editing Nexus code that executes commands on managed servers over SSH, including file manager, transfer, archive, agent install, and BT panel bootstrap flows.
---
# Nexus SSH Safety
Use for any Nexus feature that builds shell commands or transfers files over SSH.
## Command construction rules
- Prefer argument arrays/local library APIs when available; otherwise quote every dynamic shell token with shlex.quote.
- Insert -- before filename/path operands for commands that parse options:
m, mv, cp, ar, zip, chmod, chown, etc.
- Never concatenate unquoted user input into shell command strings.
- Keep timeouts explicit for network/SSH work; avoid unbounded commands.
- Truncate or sanitize command output before returning it to API clients.
- Do not log secrets or full credential-bearing URLs.
## Path and archive rules
- Normalize paths with POSIX semantics for remote Linux paths.
- Reject empty paths where dangerous, root/system paths for recursive destructive operations, and traversal where the feature expects a bounded directory.
- Archive creation must protect member names beginning with -.
- Archive extraction must validate member names and types before extraction.
- Temporary files/directories must be unique and cleaned best-effort.
## script_service exception
-5
View File
@@ -1,5 +0,0 @@
Collecting tzdata
Downloading tzdata-2026.2-py2.py3-none-any.whl.metadata (1.4 kB)
Downloading tzdata-2026.2-py2.py3-none-any.whl (349 kB)
Installing collected packages: tzdata
Successfully installed tzdata-2026.2
+1 -53
View File
@@ -19,14 +19,6 @@ cd frontend && npm run dev # :3000/app/
路径见 [linux-dev-paths.md](docs/project/linux-dev-paths.md)。
## Git(仅本机快照)
- 本地 `git commit` 用于回滚;**不 `git push`**(除非你明确要求)。
- 已移除 `origin` 跟踪,避免误推;原远程:`http://66.154.115.8:3000/admin/Nexus.git`(需恢复时说一声)。
- `.env``SECRETS.md``*.pem` 已在 `.gitignore`**勿提交**。
- 改代码仍写 `docs/changelog/YYYY-MM-DD-*.md`(≥10 行)。
- 用户说「提交快照 / commit」时执行。
## 强制约束
- 完美实现:`.cursor/rules/perfect-implementation.mdc`
@@ -40,57 +32,13 @@ cd frontend && npm run dev # :3000/app/
## 部署
默认 **本机 rsync → SSH 生产机**(不 push Gitea):
```bash
bash deploy/pre_deploy_check.sh
bash deploy/deploy-production.sh # rsync + Docker upgrade --skip-git + 前端
bash deploy/deploy-production.sh # 需 SSH nexus + 用户批准
```
可选:`NEXUS_DEPLOY_VIA_GIT=1` 恢复远程 `git pull`。仅 `git push` 当你明确要求时。
详情见功能指南 §1718。
## 归档文档
历史审计合并卷:`docs/archive/README.md`
## 代码地图(Agent 先读这里,勿全库搜)
```
server/main.py # 路由挂载入口
server/config.py # 配置(.env + settings 表)
server/api/*.py # HTTP/WebSocket 路由(按模块拆分)
server/application/ # 业务 services
server/infrastructure/ # DB / Redis / SSH / Telegram
server/domain/models/ # SQLAlchemy ORM
frontend/src/pages/ # Vue 页面(15 页 SPA
frontend/src/router/ # Hash 路由 #/
frontend/src/api/ # 前端 API 客户端
frontend/e2e/ # Playwright 验收
tests/test_*.py # pytest
docs/project/ # 功能/部署 SSOT
deploy/ # 生产部署脚本
scripts/ # 本地验证、MCP、门控
.cursor/mcp.json # mysql-mcp
```
## 问题 → 文件(常见锚点)
| 问什么 | 先看 |
|--------|------|
| 某 API 怎么实现 | `server/main.py` 找 router → `server/api/<模块>.py` |
| 服务器列表/搜索/exec | `server/api/servers.py`, `server/api/scripts.py` |
| 认证/JWT | `server/api/auth.py`, `server/api/dependencies.py` |
| Agent 心跳/安装 | `server/api/agent.py` |
| WebSSH/终端 | `server/api/webssh.py`, `server/api/terminal.py` |
| 同步/文件/搜索 | `server/api/sync_v2.py`, `files.py`, `search.py` |
| 设置/审计/告警 | `server/api/settings.py` |
| 前端某页 UI | `frontend/src/router/index.ts``frontend/src/pages/` |
| MySQL MCP | `.cursor/mcp.json`, `scripts/linux_mcp_mysql.sh` |
| 本地开发路径 | `docs/project/linux-dev-paths.md` |
| 14 页/API 全览 | `docs/project/nexus-functional-development-guide.md` §910 |
## 探索规则(省 token
详见 `.cursor/rules/agent-exploration.mdc`。要点:**先地图再 Grep(带 path)→ Read ≤5 个文件 → 停止**。
+33 -46
View File
@@ -1,46 +1,33 @@
# Nexus 6.0 - API + static SPA (web/app)
FROM python:3.12-slim-bookworm
ARG DEBIAN_MIRROR=https://mirrors.aliyun.com/debian
ARG DEBIAN_SECURITY_MIRROR=https://mirrors.aliyun.com/debian-security
ARG PIP_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/
ARG PIP_TRUSTED_HOST=mirrors.aliyun.com
RUN set -eux; \
sed -i \
-e "s|http://deb.debian.org/debian-security|${DEBIAN_SECURITY_MIRROR}|g" \
-e "s|http://deb.debian.org/debian|${DEBIAN_MIRROR}|g" \
/etc/apt/sources.list.d/debian.sources; \
apt-get update; \
apt-get install -y --no-install-recommends \
curl \
openssh-client \
rsync \
sshpass; \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --index-url "${PIP_INDEX_URL}" --trusted-host "${PIP_TRUSTED_HOST}" -r requirements.txt
COPY server/ ./server/
COPY web/app/ ./web/app/
COPY web/app-v2/ ./web/app-v2/
COPY docker/entrypoint.sh ./docker/entrypoint.sh
RUN chmod +x ./docker/entrypoint.sh
ENV PYTHONPATH=/app \
PYTHONUNBUFFERED=1 \
NEXUS_HOST=0.0.0.0 \
NEXUS_PORT=8600
EXPOSE 8600
HEALTHCHECK --interval=30s --timeout=5s --start-period=90s --retries=3 \
CMD curl -sf http://127.0.0.1:8600/health | grep -q '^ok' || exit 1
ENTRYPOINT ["/app/docker/entrypoint.sh"]
CMD ["uvicorn", "server.main:app", "--host", "0.0.0.0", "--port", "8600"]
# Nexus 6.0 API + 静态 SPA (web/app)
FROM python:3.12-slim-bookworm
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
openssh-client \
rsync \
sshpass \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY server/ ./server/
COPY web/app/ ./web/app/
COPY docker/entrypoint.sh ./docker/entrypoint.sh
RUN chmod +x ./docker/entrypoint.sh
ENV PYTHONPATH=/app \
PYTHONUNBUFFERED=1 \
NEXUS_HOST=0.0.0.0 \
NEXUS_PORT=8600
EXPOSE 8600
HEALTHCHECK --interval=30s --timeout=5s --start-period=90s --retries=3 \
CMD curl -sf http://127.0.0.1:8600/health | grep -q '^ok' || exit 1
ENTRYPOINT ["/app/docker/entrypoint.sh"]
CMD ["uvicorn", "server.main:app", "--host", "0.0.0.0", "--port", "8600"]
+64 -77
View File
@@ -1,77 +1,64 @@
# Nexus 6.0 鈥?production image (frontend build + API)
# Build: docker compose -f docker/docker-compose.prod.yml build
# See: docs/design/plans/2026-06-04-1panel-docker-production.md
# 鈹€鈹€ Stage 1: Vue/Vite SPA 鈹€鈹€
FROM node:20-bookworm-slim AS frontend
WORKDIR /build/frontend
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci --ignore-scripts
COPY frontend/ ./
RUN npx vite build
# Stage 1b: React/Vite App V2
FROM node:20-bookworm-slim AS frontend_v2
WORKDIR /build/frontend-v2
COPY frontend-v2/package.json frontend-v2/package-lock.json* ./
RUN if [ -f package-lock.json ]; then npm ci --ignore-scripts; else npm install --ignore-scripts; fi
COPY frontend-v2/ ./
RUN npx vite build
# 鈹€鈹€ Stage 2: Python API + built static assets 鈹€鈹€
FROM python:3.12-slim-bookworm
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
openssh-client \
rsync \
sshpass \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY server/ ./server/
COPY web/agent/ ./web/agent/
COPY --from=frontend /build/web/app ./web/app/
COPY --from=frontend_v2 /build/web/app-v2 ./web/app-v2/
# Login wallpaper cache (Vite does not emit this dir; sync also writes here at runtime)
COPY web/app/wallpapers/ ./web/app/wallpapers/
# Install wizard + static SPA assets not emitted by Vite
COPY web/app/install.html ./web/app/install.html
COPY web/app/servers_import_template.csv ./web/app/servers_import_template.csv
RUN mkdir -p ./web/app/vendor
COPY web/app/vendor/tailwindcss-browser.js \
web/app/vendor/theme-init.js \
web/app/vendor/theme.css \
web/app/vendor/alpinejs.min.js \
./web/app/vendor/
COPY docker/entrypoint.sh ./docker/entrypoint.sh
RUN chmod +x ./docker/entrypoint.sh \
&& test -f /app/web/app/install.html \
&& test -f /app/web/app/vendor/alpinejs.min.js \
&& test -f /app/web/app/vendor/tailwindcss-browser.js \
&& test -f /app/web/app/vendor/theme.css \
&& test -f /app/web/app/vendor/theme-init.js
ENV PYTHONPATH=/app \
PYTHONUNBUFFERED=1 \
NEXUS_HOST=0.0.0.0 \
NEXUS_PORT=8600
EXPOSE 8600
HEALTHCHECK --interval=30s --timeout=5s --start-period=90s --retries=3 \
CMD curl -sf http://127.0.0.1:8600/health | grep -q '^ok' || exit 1
ENTRYPOINT ["/app/docker/entrypoint.sh"]
CMD ["uvicorn", "server.main:app", "--host", "0.0.0.0", "--port", "8600"]
# Nexus 6.0 production image (frontend build + API)
# Build: docker compose -f docker/docker-compose.prod.yml build
# See: docs/design/plans/2026-06-04-1panel-docker-production.md
# ── Stage 1: Vue/Vite SPA ──
FROM node:20-bookworm-slim AS frontend
WORKDIR /build/frontend
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci --ignore-scripts
COPY frontend/ ./
RUN npx vite build
# ── Stage 2: Python API + built static assets ──
FROM python:3.12-slim-bookworm
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
openssh-client \
rsync \
sshpass \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY server/ ./server/
COPY web/agent/ ./web/agent/
COPY --from=frontend /build/web/app ./web/app/
# Login wallpaper cache (Vite does not emit this dir; sync also writes here at runtime)
COPY web/app/wallpapers/ ./web/app/wallpapers/
# Install wizard + static SPA assets not emitted by Vite
COPY web/app/install.html ./web/app/install.html
COPY web/app/servers_import_template.csv ./web/app/servers_import_template.csv
RUN mkdir -p ./web/app/vendor
COPY web/app/vendor/tailwindcss-browser.js \
web/app/vendor/theme-init.js \
web/app/vendor/theme.css \
web/app/vendor/alpinejs.min.js \
./web/app/vendor/
COPY docker/entrypoint.sh ./docker/entrypoint.sh
RUN chmod +x ./docker/entrypoint.sh \
&& test -f /app/web/app/install.html \
&& test -f /app/web/app/vendor/alpinejs.min.js \
&& test -f /app/web/app/vendor/tailwindcss-browser.js \
&& test -f /app/web/app/vendor/theme.css \
&& test -f /app/web/app/vendor/theme-init.js
ENV PYTHONPATH=/app \
PYTHONUNBUFFERED=1 \
NEXUS_HOST=0.0.0.0 \
NEXUS_PORT=8600
EXPOSE 8600
HEALTHCHECK --interval=30s --timeout=5s --start-period=90s --retries=3 \
CMD curl -sf http://127.0.0.1:8600/health | grep -q '^ok' || exit 1
ENTRYPOINT ["/app/docker/entrypoint.sh"]
CMD ["uvicorn", "server.main:app", "--host", "0.0.0.0", "--port", "8600"]
+71 -71
View File
@@ -1,71 +1,71 @@
# 1Panel OpenResty — Nexus reverse proxy (paste into site custom config)
# Domain: api.synaglobal.vip
# Upstream: Nexus Docker on 127.0.0.1:8600 (docker-compose.prod.yml)
#
# SSL: configure in 1Panel UI (Let's Encrypt). This snippet assumes HTTPS server block.
upstream nexus_api {
server 127.0.0.1:8600;
keepalive 32;
}
# WebSocket log without query string (JWT must not hit access_log)
log_format nexus_ws '$remote_addr - [$time_local] "$request_method $uri" $status $body_bytes_sent';
client_max_body_size 500m;
client_body_timeout 600s;
location /api/ {
proxy_pass http://nexus_api;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
proxy_connect_timeout 10s;
}
location /ws/ {
access_log /www/sites/api.synaglobal.vip/log/ws.log nexus_ws;
proxy_pass http://nexus_api;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
location /health {
proxy_pass http://nexus_api;
proxy_set_header Host $host;
}
location /app/ {
proxy_pass http://nexus_api;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /agent/ {
proxy_pass http://nexus_api;
proxy_set_header Host $host;
}
# Fallback: install mode or misc routes served by FastAPI
location / {
proxy_pass http://nexus_api;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 1Panel OpenResty — Nexus reverse proxy (paste into site custom config)
# Domain: api.synaglobal.vip
# Upstream: Nexus Docker on 127.0.0.1:8600 (docker-compose.prod.yml)
#
# SSL: configure in 1Panel UI (Let's Encrypt). This snippet assumes HTTPS server block.
upstream nexus_api {
server 127.0.0.1:8600;
keepalive 32;
}
# WebSocket log without query string (JWT must not hit access_log)
log_format nexus_ws '$remote_addr - [$time_local] "$request_method $uri" $status $body_bytes_sent';
client_max_body_size 500m;
client_body_timeout 600s;
location /api/ {
proxy_pass http://nexus_api;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
proxy_connect_timeout 10s;
}
location /ws/ {
access_log /www/sites/api.synaglobal.vip/log/ws.log nexus_ws;
proxy_pass http://nexus_api;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
location /health {
proxy_pass http://nexus_api;
proxy_set_header Host $host;
}
location /app/ {
proxy_pass http://nexus_api;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /agent/ {
proxy_pass http://nexus_api;
proxy_set_header Host $host;
}
# Fallback: install mode or misc routes served by FastAPI
location / {
proxy_pass http://nexus_api;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
-272
View File
@@ -1,272 +0,0 @@
#!/usr/bin/env bash
# Apply a Nexus release tarball on the production host.
#
# Usage:
# sudo bash deploy/apply-release-bundle.sh \
# --tarball /tmp/nexus-release-20260708.tar.gz \
# --sha256 6474d58fdccde668b4c91d5e7454fa08e01e20660e3bcbb363eeadbb7c2346a1 \
# --deploy-path /opt/nexus
#
# Validate only (no backup, no rsync, no rebuild):
# bash deploy/apply-release-bundle.sh --validate-only \
# --tarball /tmp/nexus-release-20260708.tar.gz \
# --sha256 6474d58fdccde668b4c91d5e7454fa08e01e20660e3bcbb363eeadbb7c2346a1
#
# This script intentionally preserves runtime secrets and data:
# - .env
# - docker/.env.prod
# - web/data
set -euo pipefail
TARBALL=""
EXPECTED_SHA256=""
DEPLOY_PATH="${NEXUS_DEPLOY_PATH:-/opt/nexus}"
BACKUP_DIR="${NEXUS_BACKUP_DIR:-/opt/nexus-backups}"
SKIP_REBUILD=0
DRY_RUN=0
VALIDATE_ONLY=0
APPLY_TMP=""
info() { echo -e "\033[0;32m[INFO]\033[0m $*"; }
warn() { echo -e "\033[1;33m[WARN]\033[0m $*"; }
error() { echo -e "\033[0;31m[ERROR]\033[0m $*" >&2; }
usage() {
sed -n '1,26p' "$0"
}
while [[ $# -gt 0 ]]; do
case "$1" in
--tarball)
TARBALL="${2:-}"
shift 2
;;
--sha256)
EXPECTED_SHA256="${2:-}"
shift 2
;;
--deploy-path)
DEPLOY_PATH="${2:-}"
shift 2
;;
--backup-dir)
BACKUP_DIR="${2:-}"
shift 2
;;
--skip-rebuild)
SKIP_REBUILD=1
shift
;;
--dry-run)
DRY_RUN=1
shift
;;
--validate-only)
VALIDATE_ONLY=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
error "Unknown argument: $1"
usage
exit 2
;;
esac
done
require_cmd() {
command -v "$1" >/dev/null 2>&1 || {
error "Missing required command: $1"
exit 127
}
}
require_runtime_cmds() {
require_cmd tar
require_cmd sha256sum
if [[ "$VALIDATE_ONLY" == 1 ]]; then
return 0
fi
require_cmd rsync
require_cmd curl
}
run() {
echo "+ $*"
if [[ "$DRY_RUN" == 1 ]]; then
return 0
fi
"$@"
}
cleanup() {
if [[ -n "${APPLY_TMP:-}" && -d "$APPLY_TMP" ]]; then
rm -rf "$APPLY_TMP"
fi
}
detect_runtime() {
if [[ -f "${DEPLOY_PATH}/docker/.env.prod" ]] && command -v docker >/dev/null 2>&1; then
echo docker
return
fi
if command -v supervisorctl >/dev/null 2>&1 && supervisorctl status nexus >/dev/null 2>&1; then
echo supervisor
return
fi
echo unknown
}
verify_sha256() {
if [[ -z "$EXPECTED_SHA256" ]]; then
warn "No --sha256 provided; skipping checksum verification"
return 0
fi
local actual
actual="$(sha256sum "$TARBALL" | awk '{print $1}')"
if [[ "$actual" != "$EXPECTED_SHA256" ]]; then
error "SHA256 mismatch"
error "expected: $EXPECTED_SHA256"
error "actual: $actual"
exit 1
fi
info "SHA256 OK: $actual"
}
validate_extracted_tree() {
local src="$1"
for required in server deploy Dockerfile.prod frontend-v2 web/app-v2; do
if [[ ! -e "${src}/${required}" ]]; then
error "Release bundle missing required path: ${required}"
exit 1
fi
done
for forbidden in .env docker/.env.prod web/data .venv-py312-codex frontend-v2/node_modules 2025.2 =2025.2; do
if [[ -e "${src}/${forbidden}" ]]; then
error "Release bundle contains forbidden path: ${forbidden}"
exit 1
fi
done
info "Extracted release tree validation OK"
}
backup_current() {
if [[ ! -d "$DEPLOY_PATH" ]]; then
warn "Deploy path does not exist yet: $DEPLOY_PATH"
return 0
fi
local stamp backup
stamp="$(date +%Y%m%d-%H%M%S)"
backup="${BACKUP_DIR}/nexus-before-${stamp}.tar.gz"
run mkdir -p "$BACKUP_DIR"
info "Backup current deploy path -> $backup"
run tar czf "$backup" \
--exclude='./web/data' \
--exclude='./.venv*' \
--exclude='./venv' \
--exclude='./frontend/node_modules' \
--exclude='./frontend-v2/node_modules' \
-C "$(dirname "$DEPLOY_PATH")" "$(basename "$DEPLOY_PATH")"
}
sync_release() {
local src="$1"
run mkdir -p "$DEPLOY_PATH"
info "Sync release tree -> $DEPLOY_PATH"
run rsync -a --delete \
--exclude='.env' \
--exclude='docker/.env.prod' \
--exclude='web/data' \
"${src}/" "${DEPLOY_PATH}/"
}
rebuild_or_restart() {
if [[ "$SKIP_REBUILD" == 1 ]]; then
warn "--skip-rebuild set; not rebuilding/restarting Nexus"
return 0
fi
local runtime
runtime="$(detect_runtime)"
info "Runtime: $runtime"
case "$runtime" in
docker)
if [[ -x "${DEPLOY_PATH}/deploy/nexus-1panel.sh" || -f "${DEPLOY_PATH}/deploy/nexus-1panel.sh" ]]; then
run env NEXUS_ROOT="$DEPLOY_PATH" bash "${DEPLOY_PATH}/deploy/nexus-1panel.sh" upgrade --skip-git
else
error "Docker runtime detected but deploy/nexus-1panel.sh missing"
exit 1
fi
if [[ -f "${DEPLOY_PATH}/deploy/sync_webapp_to_container.sh" ]]; then
run env NEXUS_ROOT="$DEPLOY_PATH" bash "${DEPLOY_PATH}/deploy/sync_webapp_to_container.sh"
fi
;;
supervisor)
run supervisorctl restart nexus
;;
*)
error "Unknown runtime. Rebuild/restart manually."
exit 1
;;
esac
}
verify_local_endpoints() {
local port health app app_v2
port="$(grep -E '^NEXUS_PUBLISH_PORT=' "${DEPLOY_PATH}/docker/.env.prod" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '\r" ' || true)"
port="${port:-8600}"
health="$(curl -s "http://127.0.0.1:${port}/health" 2>/dev/null || true)"
app="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${port}/app/" 2>/dev/null || echo 000)"
app_v2="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${port}/app-v2/" 2>/dev/null || echo 000)"
echo " /health -> ${health}"
echo " /app/ -> ${app}"
echo " /app-v2/ -> ${app_v2}"
if [[ "$health" != "ok" || "$app" != "200" || "$app_v2" != "200" ]]; then
error "Local endpoint verification failed"
exit 1
fi
info "Local endpoint verification OK"
}
main() {
if [[ -z "$TARBALL" ]]; then
error "--tarball is required"
usage
exit 2
fi
[[ -f "$TARBALL" ]] || {
error "Tarball not found: $TARBALL"
exit 1
}
require_runtime_cmds
info "Tarball: $TARBALL"
info "Deploy path: $DEPLOY_PATH"
info "Backup dir: $BACKUP_DIR"
verify_sha256
local extracted
APPLY_TMP="$(mktemp -d /tmp/nexus-release-apply.XXXXXX)"
trap cleanup EXIT
run tar xzf "$TARBALL" -C "$APPLY_TMP"
extracted="${APPLY_TMP}/nexus-release"
validate_extracted_tree "$extracted"
if [[ "$VALIDATE_ONLY" == 1 ]]; then
info "Validate-only mode: tarball checksum and content validation passed"
return 0
fi
backup_current
sync_release "$extracted"
rebuild_or_restart
if [[ "$DRY_RUN" != 1 && "$SKIP_REBUILD" != 1 ]]; then
verify_local_endpoints
fi
info "Release applied successfully"
}
main "$@"
+26 -69
View File
@@ -1,19 +1,16 @@
#!/usr/bin/env bash
# Full production deploy: local rsync + backend upgrade + frontend + health
# Full production deploy: push (optional) + backend upgrade + frontend + health
#
# 默认从本机工作区 rsync 到生产机,不 push Gitea、不远程 git pull
# 自动识别远程 Docker/opt/nexus)或 Supervisor/www/wwwroot/...)运行时
#
# Prerequisites on YOUR machine:
# - rsync, SSH Host nexus OR export NEXUS_SSH="azureuser@20.24.218.235"
# - SSH: ~/.ssh/config Host nexus OR export NEXUS_SSH="azureuser@20.24.218.235"
# - Key: export NEXUS_SSH_KEY=~/.ssh/id_rsa_nexus
#
# Usage:
# cd "$NEXUS_ROOT"
# bash deploy/pre_deploy_check.sh
# bash deploy/deploy-production.sh
#
# 仍走 Gitea(仅当显式设置):
# NEXUS_DEPLOY_VIA_GIT=1 bash deploy/deploy-production.sh
set -euo pipefail
@@ -34,15 +31,8 @@ fi
ssh_cmd() { ssh "${SSH_OPTS[@]}" "${NEXUS_SSH_HOST}" "$@"; }
scp_cmd() { scp "${SSH_OPTS[@]}" "$@"; }
DEPLOY_VIA_GIT="${NEXUS_DEPLOY_VIA_GIT:-0}"
case "${DEPLOY_VIA_GIT}" in
1|true|yes|on) DEPLOY_VIA_GIT=1 ;;
*) DEPLOY_VIA_GIT=0 ;;
esac
echo "═══ Nexus production deploy ═══"
echo "SSH target: ${NEXUS_SSH_HOST}"
echo "Mode: $([[ "$DEPLOY_VIA_GIT" == 1 ]] && echo 'Gitea git pull' || echo 'local rsync')"
if ! ssh_cmd "echo SSH OK" >/dev/null 2>&1; then
echo "ERROR: Cannot SSH to ${NEXUS_SSH_HOST}" >&2
@@ -104,69 +94,38 @@ fi
echo "Deploy path: ${DEPLOY_PATH}"
echo "Runtime: ${RUNTIME}"
resolve_deploy_chown() {
if [[ -n "${NEXUS_DEPLOY_CHOWN:-}" ]]; then
echo "${NEXUS_DEPLOY_CHOWN}"
return
fi
if ssh_cmd "getent passwd www >/dev/null 2>&1"; then
echo "www:www"
return
fi
ssh_cmd "stat -c '%U:%G' '${DEPLOY_PATH}'" 2>/dev/null || echo "azureuser:azureuser"
}
DEPLOY_CHOWN="$(resolve_deploy_chown)"
echo "File owner: ${DEPLOY_CHOWN}"
if [[ "$RUNTIME" == "docker" ]]; then
echo "▶ Docker: git pull + 镜像重建(前端在镜像内 vite build..."
ssh_cmd "cd ${DEPLOY_PATH} && sudo git fetch --all && sudo git reset --hard origin/main && \
sudo env NEXUS_ROOT=${DEPLOY_PATH} bash ${DEPLOY_PATH}/deploy/nexus-1panel.sh upgrade"
elif [[ "$RUNTIME" == "supervisor" ]]; then
echo "▶ Supervisor: git pull + restart..."
ssh_cmd "cd ${DEPLOY_PATH} && git fetch --all && git reset --hard origin/main && supervisorctl restart nexus"
if [[ "$DEPLOY_VIA_GIT" == 1 ]]; then
echo "▶ Git mode: remote git pull..."
if [[ "$RUNTIME" == "docker" ]]; then
ssh_cmd "cd ${DEPLOY_PATH} && sudo git fetch --all && sudo git reset --hard origin/main && \
sudo env NEXUS_ROOT=${DEPLOY_PATH} bash ${DEPLOY_PATH}/deploy/nexus-1panel.sh upgrade"
elif [[ "$RUNTIME" == "supervisor" ]]; then
ssh_cmd "cd ${DEPLOY_PATH} && git fetch --all && git reset --hard origin/main && supervisorctl restart nexus"
else
echo "ERROR: 无法识别远程运行时" >&2
exit 1
echo "▶ Frontend: build + upload..."
cd frontend && npx vite build && cd ..
tar czf /tmp/nexus-frontend.tar.gz -C web/app index.html assets/
scp_cmd /tmp/nexus-frontend.tar.gz "${NEXUS_SSH_HOST}:/tmp/nexus-frontend.tar.gz"
ssh_cmd "cd ${DEPLOY_PATH}/web/app && tar xzf /tmp/nexus-frontend.tar.gz && rm /tmp/nexus-frontend.tar.gz"
if ssh_cmd "test -f ${DEPLOY_PATH}/deploy/prune_frontend_assets.py"; then
ssh_cmd "python3 ${DEPLOY_PATH}/deploy/prune_frontend_assets.py --dry-run"
ssh_cmd "python3 ${DEPLOY_PATH}/deploy/prune_frontend_assets.py"
fi
else
echo "▶ Local sync → ${DEPLOY_PATH}..."
NEXUS_DEPLOY_PATH="${DEPLOY_PATH}" bash "${ROOT}/deploy/rsync-local-to-server.sh"
if [[ "$RUNTIME" == "docker" ]]; then
echo "▶ Docker: 重建镜像(--skip-git..."
ssh_cmd "sudo env NEXUS_ROOT=${DEPLOY_PATH} bash ${DEPLOY_PATH}/deploy/nexus-1panel.sh upgrade --skip-git"
elif [[ "$RUNTIME" == "supervisor" ]]; then
echo "▶ Supervisor: restart..."
ssh_cmd "supervisorctl restart nexus"
else
echo "ERROR: 无法识别远程运行时(非 Docker 也非 Supervisor" >&2
echo " 请手动: ssh ${NEXUS_SSH_HOST} 'sudo nx update'" >&2
exit 1
fi
echo "ERROR: 无法识别远程运行时(非 Docker 也非 Supervisor" >&2
echo " 请手动: ssh ${NEXUS_SSH_HOST} 'sudo nx update'" >&2
exit 1
fi
if [[ "$RUNTIME" == "docker" || "$RUNTIME" == "supervisor" ]]; then
if [[ "$RUNTIME" == "docker" ]]; then
# Host web/app must match image build before sync_webapp_to_container (else stale assets overwrite container).
echo "▶ Frontend: local vite build + upload to host web/app..."
cd frontend && npx vite build && cd ..
tar czf /tmp/nexus-frontend.tar.gz -C web/app index.html assets/
scp_cmd /tmp/nexus-frontend.tar.gz "${NEXUS_SSH_HOST}:/tmp/nexus-frontend.tar.gz"
ssh_cmd "cd ${DEPLOY_PATH}/web/app && sudo tar xzf /tmp/nexus-frontend.tar.gz && rm /tmp/nexus-frontend.tar.gz && sudo chown -R ${DEPLOY_CHOWN} ${DEPLOY_PATH}/web/app"
ssh_cmd "cd ${DEPLOY_PATH}/web/app && sudo tar xzf /tmp/nexus-frontend.tar.gz && rm /tmp/nexus-frontend.tar.gz"
echo "▶ Frontend V2: local vite build:safe + upload to host web/app-v2..."
cd frontend-v2 && npm run build:safe && cd ..
tar czf /tmp/nexus-frontend-v2.tar.gz -C web/app-v2 index.html assets/
scp_cmd /tmp/nexus-frontend-v2.tar.gz "${NEXUS_SSH_HOST}:/tmp/nexus-frontend-v2.tar.gz"
ssh_cmd "sudo mkdir -p ${DEPLOY_PATH}/web/app-v2 && cd ${DEPLOY_PATH}/web/app-v2 && sudo tar xzf /tmp/nexus-frontend-v2.tar.gz && rm /tmp/nexus-frontend-v2.tar.gz && sudo chown -R ${DEPLOY_CHOWN} ${DEPLOY_PATH}/web/app-v2"
if ssh_cmd "test -f ${DEPLOY_PATH}/deploy/prune_frontend_assets.py"; then
ssh_cmd "sudo python3 ${DEPLOY_PATH}/deploy/prune_frontend_assets.py ${DEPLOY_PATH}/web/app --dry-run"
ssh_cmd "sudo python3 ${DEPLOY_PATH}/deploy/prune_frontend_assets.py ${DEPLOY_PATH}/web/app"
fi
fi
if [[ "$RUNTIME" == "docker" ]]; then
echo "▶ Sync host web/app and web/app-v2 into container..."
echo "▶ Sync host web/app into container..."
ssh_cmd "sudo env NEXUS_ROOT=${DEPLOY_PATH} NEXUS_PUBLISH_PORT=\$(grep -E '^NEXUS_PUBLISH_PORT=' ${DEPLOY_PATH}/docker/.env.prod 2>/dev/null | cut -d= -f2 | tr -d '\"' || echo 8600) bash ${DEPLOY_PATH}/deploy/sync_webapp_to_container.sh" || {
echo "WARN: web/app sync failed — run sync_webapp_to_container.sh on server manually" >&2
}
@@ -177,11 +136,9 @@ PORT="$(ssh_cmd "grep -E '^NEXUS_PUBLISH_PORT=' ${DEPLOY_PATH}/docker/.env.prod
PORT="${PORT:-8600}"
HEALTH=$(ssh_cmd "curl -s http://127.0.0.1:${PORT}/health" 2>/dev/null || true)
SPA=$(ssh_cmd "curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:${PORT}/app/" 2>/dev/null || echo "000")
SPA_V2=$(ssh_cmd "curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:${PORT}/app-v2/" 2>/dev/null || echo "000")
echo " /health → ${HEALTH}"
echo " /app/ → ${SPA}"
echo " /app-v2/ → ${SPA_V2}"
if [[ "${HEALTH}" == "ok" && "${SPA}" == "200" && "${SPA_V2}" == "200" ]]; then
if [[ "${HEALTH}" == "ok" && "${SPA}" == "200" ]]; then
echo "✓ Deploy successful"
if ssh_cmd "command -v crontab >/dev/null && crontab -l 2>/dev/null | grep -q nexus-health-monitor" 2>/dev/null; then
echo "✓ Ops cron (health_monitor) already installed"
File diff suppressed because it is too large Load Diff
+16 -145
View File
@@ -9,64 +9,12 @@ set -euo pipefail
DEPLOY_DIR="${NEXUS_DEPLOY_DIR:-/opt/nexus}"
NEXUS_SERVICE="${NEXUS_SERVICE:-nexus}"
STATE_DIR=""
FAIL_FILE=""
LAST_RESTART_FILE=""
# Legacy paths (cron user may have created root-owned files under /tmp)
LEGACY_FAIL_FILE="/tmp/nexus_health_fail_count"
LEGACY_LAST_RESTART_FILE="/tmp/nexus_health_last_restart"
FAIL_FILE="/tmp/nexus_health_fail_count"
LAST_RESTART_FILE="/tmp/nexus_health_last_restart"
MAX_FAIL=3
RESTART_COOLDOWN_SEC="${NEXUS_HEALTH_RESTART_COOLDOWN:-300}"
ENV_PROD="${DEPLOY_DIR}/docker/.env.prod"
ensure_state_paths() {
if [[ -n "$STATE_DIR" ]]; then
return 0
fi
local preferred="${DEPLOY_DIR}/var/layer3-health"
local fallback="${HOME:-/tmp}/.nexus/layer3-health"
if mkdir -p "$preferred" 2>/dev/null && [[ -w "$preferred" ]]; then
STATE_DIR="$preferred"
elif command -v sudo >/dev/null 2>&1 && sudo mkdir -p "$preferred" 2>/dev/null \
&& sudo chown -R "$(id -un):$(id -gn)" "$preferred" 2>/dev/null \
&& [[ -w "$preferred" ]]; then
STATE_DIR="$preferred"
else
mkdir -p "$fallback"
STATE_DIR="$fallback"
fi
FAIL_FILE="${STATE_DIR}/fail_count"
LAST_RESTART_FILE="${STATE_DIR}/last_restart"
}
docker_cmd() {
if docker "$@" 2>/dev/null; then
return 0
fi
if command -v sudo >/dev/null 2>&1; then
sudo docker "$@" 2>/dev/null
else
return 1
fi
}
init_layer3_state() {
ensure_state_paths
chmod 755 "$STATE_DIR" 2>/dev/null || true
if [[ ! -f "$FAIL_FILE" && -f "$LEGACY_FAIL_FILE" ]]; then
cp "$LEGACY_FAIL_FILE" "$FAIL_FILE" 2>/dev/null || true
fi
if [[ ! -f "$LAST_RESTART_FILE" && -f "$LEGACY_LAST_RESTART_FILE" ]]; then
cp "$LEGACY_LAST_RESTART_FILE" "$LAST_RESTART_FILE" 2>/dev/null || true
fi
}
write_state_file() {
local path="$1" value="$2"
init_layer3_state
printf '%s\n' "$value" >"$path" 2>/dev/null || return 1
}
resolve_publish_port() {
local port
port="$(grep -E '^NEXUS_PUBLISH_PORT=' "$ENV_PROD" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '\r" ' || true)"
@@ -77,7 +25,7 @@ PUBLISH_PORT="$(resolve_publish_port)"
HEALTH_URL="http://127.0.0.1:${PUBLISH_PORT}/health"
resolve_nexus_container() {
docker_cmd ps --format '{{.Names}}' 2>/dev/null | grep -E 'nexus-prod-nexus|nexus-nexus-1' | head -1 || true
docker ps --format '{{.Names}}' 2>/dev/null | grep -E 'nexus-prod-nexus|nexus-nexus-1' | head -1 || true
}
read_env_var() {
@@ -102,7 +50,7 @@ load_telegram_from_db() {
[[ -n "$cname" ]] || return 1
[[ -n "${BOT_TOKEN:-}" && -n "${CHAT_ID:-}" ]] && return 0
local out token chat
out="$(docker_cmd exec "$cname" python3 -c "
out="$(docker exec "$cname" python3 -c "
import asyncio
from server.config import settings as s
from server.infrastructure.database.session import AsyncSessionLocal
@@ -146,7 +94,7 @@ load_telegram_config() {
NEXUS_TELEGRAM_BOT_TOKEN=*) BOT_TOKEN="${line#NEXUS_TELEGRAM_BOT_TOKEN=}" ;;
NEXUS_TELEGRAM_CHAT_ID=*) CHAT_ID="${line#NEXUS_TELEGRAM_CHAT_ID=}" ;;
esac
done < <(docker_cmd exec "$cname" grep -E '^NEXUS_TELEGRAM_' /app/.env 2>/dev/null || true)
done < <(docker exec "$cname" grep -E '^NEXUS_TELEGRAM_' /app/.env 2>/dev/null || true)
[[ -n "$BOT_TOKEN" && -n "$CHAT_ID" ]] && return 0
load_telegram_from_db "$cname" || true
fi
@@ -155,7 +103,7 @@ load_telegram_config() {
}
send_telegram() {
local msg="${1}" resp http log="${NEXUS_HEALTH_LOG:-/var/log/nexus_health.log}"
local msg="${1//\\n/$'\n'}" resp http log="${NEXUS_HEALTH_LOG:-/var/log/nexus_health.log}"
BOT_TOKEN="$(normalize_env_value "${BOT_TOKEN:-}")"
CHAT_ID="$(normalize_env_value "${CHAT_ID:-}")"
[[ -n "$BOT_TOKEN" && -n "$CHAT_ID" ]] || return 1
@@ -173,25 +121,24 @@ send_telegram() {
bump_fail_count() {
local fail=0
init_layer3_state
if command -v flock >/dev/null 2>&1; then
(
flock -x 9
fail="$(cat "$FAIL_FILE" 2>/dev/null || echo 0)"
fail=$((fail + 1))
write_state_file "$FAIL_FILE" "$fail" || true
echo "$fail" > "$FAIL_FILE"
) 9>"${FAIL_FILE}.lock"
fail="$(cat "$FAIL_FILE" 2>/dev/null || echo 0)"
else
fail="$(cat "$FAIL_FILE" 2>/dev/null || echo 0)"
fail=$((fail + 1))
write_state_file "$FAIL_FILE" "$fail" || true
echo "$fail" > "$FAIL_FILE"
fi
echo "$fail"
}
reset_fail_count() {
write_state_file "$FAIL_FILE" "0" || true
echo "0" > "$FAIL_FILE"
}
restart_cooldown_active() {
@@ -204,13 +151,13 @@ restart_cooldown_active() {
}
mark_restart_attempt() {
write_state_file "$LAST_RESTART_FILE" "$(date +%s)" || true
date +%s > "$LAST_RESTART_FILE"
}
restart_nexus() {
local cname="$1" via=""
if [[ -n "$cname" ]]; then
docker_cmd restart "$cname" >/dev/null 2>&1 && via="Docker 容器 ${cname}"
docker restart "$cname" >/dev/null 2>&1 && via="Docker 容器 ${cname}"
elif command -v supervisorctl >/dev/null 2>&1 && supervisorctl status "$NEXUS_SERVICE" >/dev/null 2>&1; then
supervisorctl restart "$NEXUS_SERVICE" >/dev/null 2>&1 && via="Supervisor ${NEXUS_SERVICE}"
else
@@ -219,84 +166,8 @@ restart_nexus() {
echo "$via"
}
telegram_health_fail_cooldown() {
local body
body="$(cat <<EOF
🔴 <b>【Layer 3 宿主机巡检】健康检查连续失败(冷却期内)</b>
宿主机:${HOST_LABEL}
检测地址:${HEALTH_URL}
连续失败:已达 ${MAX_FAIL} 次
Docker 容器:${NEXUS_CONTAINER:-未识别}
具体情况:/health 仍不可达,但距上次自动重启不足 ${RESTART_COOLDOWN_SEC} 秒,本次跳过重启以免频繁抖动
建议操作:查看 docker logs ${NEXUS_CONTAINER:-nexus} 与 ${NEXUS_HEALTH_LOG:-/var/log/nexus_health.log}
时间:${TS_LABEL}
EOF
)"
send_telegram "$body" || true
}
telegram_health_fail_restart() {
local body
body="$(cat <<EOF
🔴 <b>【Layer 3 宿主机巡检】Nexus 连续 ${MAX_FAIL} 次无响应</b>
宿主机:${HOST_LABEL}
检测地址:${HEALTH_URL}
连续失败:${FAIL} 次(阈值 ${MAX_FAIL} 次)
Docker 容器:${NEXUS_CONTAINER:-未识别}
具体情况:宿主机 cron 无法访问 API 健康端点,平台可能已宕机或端口异常
正在执行:自动重启 Nexus 服务…
时间:${TS_LABEL}
EOF
)"
send_telegram "$body" || true
}
telegram_health_recovered() {
local via="$1" body
body="$(cat <<EOF
🟢 <b>【Layer 3 宿主机巡检】Nexus 已自动恢复</b>
宿主机:${HOST_LABEL}
检测地址:${HEALTH_URL}
恢复方式:${via}
具体情况:自动重启后 /health 检测通过,服务已恢复在线
时间:${TS_LABEL}
EOF
)"
send_telegram "$body" || true
}
telegram_health_still_down() {
local via="$1" body
body="$(cat <<EOF
🔴 <b>【Layer 3 宿主机巡检】自动重启后仍未恢复</b>
宿主机:${HOST_LABEL}
检测地址:${HEALTH_URL}
已尝试:${via}
具体情况:重启完成后 /health 仍失败,可能存在数据库/Redis 故障或配置错误
建议操作:立即 SSH 登录排查 docker logs、MySQL、Redis,需人工介入
时间:${TS_LABEL}
EOF
)"
send_telegram "$body" || true
}
telegram_health_restart_failed() {
local body
body="$(cat <<EOF
🔴 <b>【Layer 3 宿主机巡检】无法执行自动重启</b>
宿主机:${HOST_LABEL}
检测地址:${HEALTH_URL}
具体情况:未找到可重启的 Docker 容器或 Supervisor 服务(${NEXUS_SERVICE}
建议操作:确认 nexus 容器名称与 docker ps 输出,手动执行 docker restart 或 nx update
时间:${TS_LABEL}
EOF
)"
send_telegram "$body" || true
}
NEXUS_CONTAINER="$(resolve_nexus_container)"
load_telegram_config "$NEXUS_CONTAINER"
init_layer3_state
HOST_LABEL="$(hostname -f 2>/dev/null || hostname 2>/dev/null || echo unknown)"
TS_LABEL="$(date '+%Y-%m-%d %H:%M:%S %Z' 2>/dev/null || date)"
@@ -311,21 +182,21 @@ if [[ "$FAIL" -lt "$MAX_FAIL" ]]; then
fi
if restart_cooldown_active; then
telegram_health_fail_cooldown
send_telegram "🔴 <b>【Layer 3 宿主机巡检】健康检查连续失败(冷却期内)</b>\n宿主机:${HOST_LABEL}\n检测地址:${HEALTH_URL}\n连续失败:已达 ${MAX_FAIL} 次\nDocker 容器:${NEXUS_CONTAINER:-未识别}\n具体情况:/health 仍不可达,但距上次自动重启不足 ${RESTART_COOLDOWN_SEC} 秒,本次跳过重启以免频繁抖动\n建议操作:查看 docker logs ${NEXUS_CONTAINER:-nexus} 与 /var/log/nexus_health.log\n时间:${TS_LABEL}" || true
exit 0
fi
telegram_health_fail_restart
send_telegram "🔴 <b>【Layer 3 宿主机巡检】Nexus 连续 ${MAX_FAIL} 次无响应</b>\n宿主机:${HOST_LABEL}\n检测地址:${HEALTH_URL}\n连续失败:${FAIL} 次(阈值 ${MAX_FAIL} 次)\nDocker 容器:${NEXUS_CONTAINER:-未识别}\n具体情况:宿主机 cron 无法访问 API 健康端点,平台可能已宕机或端口异常\n正在执行:自动重启 Nexus 服务…\n时间:${TS_LABEL}" || true
mark_restart_attempt
if VIA="$(restart_nexus "$NEXUS_CONTAINER")"; then
sleep 5
if curl -sf --max-time 5 "$HEALTH_URL" > /dev/null 2>&1; then
telegram_health_recovered "$VIA"
send_telegram "🟢 <b>【Layer 3 宿主机巡检】Nexus 已自动恢复</b>\n宿主机:${HOST_LABEL}\n检测地址:${HEALTH_URL}\n恢复方式:${VIA}\n具体情况:自动重启后 /health 检测通过,服务已恢复在线\n时间:${TS_LABEL}" || true
reset_fail_count
else
telegram_health_still_down "$VIA"
send_telegram "🔴 <b>【Layer 3 宿主机巡检】自动重启后仍未恢复</b>\n宿主机:${HOST_LABEL}\n检测地址:${HEALTH_URL}\n已尝试:${VIA}\n具体情况:重启完成后 /health 仍失败,可能存在数据库/Redis 故障或配置错误\n建议操作:立即 SSH 登录排查 docker logs、MySQL、Redis,需人工介入\n时间:${TS_LABEL}" || true
fi
else
telegram_health_restart_failed
send_telegram "🔴 <b>【Layer 3 宿主机巡检】无法执行自动重启</b>\n宿主机:${HOST_LABEL}\n检测地址:${HEALTH_URL}\n具体情况:未找到可重启的 Docker 容器或 Supervisor 服务(${NEXUS_SERVICE})\n建议操作:确认 nexus 容器名称与 docker ps 输出,手动执行 docker restart 或 nx update\n时间:${TS_LABEL}" || true
fi
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
+21
View File
@@ -0,0 +1,21 @@
# 复制: cp deploy/nexus-1panel.secrets.sh.example deploy/nexus-1panel.secrets.sh
# 用于 git pushscripts/git-push.sh);clone/pull 仍可匿名
# NEXUS_GITEA_HOST='66.154.115.8:3000'
# NEXUS_GITEA_REPO='admin/Nexus.git'
# admin 账号密码或 Access Token
# NEXUS_GITEA_TOKEN='你的密码或令牌'
# 新机安装勿填下面三项(install-nexus-fresh / --fresh 会自动按机生成)
# NEXUS_SECRET_KEY=''
# NEXUS_API_KEY=''
# NEXUS_ENCRYPTION_KEY=''
NEXUS_DOMAIN='api.synaglobal.vip'
NEXUS_ROOT='/opt/nexus'
NEXUS_PROFILE='2c8g'
NEXUS_FROM_ENV='/www/wwwroot/api.synaglobal.vip/.env'
# 本机生产 SSHdeploy-production.sh 读取;私钥路径勿提交真实 .pem)
# NEXUS_SSH='azureuser@20.24.218.235'
# NEXUS_SSH_KEY='/path/to/nz-admin.pem'
Regular → Executable
+5 -26
View File
@@ -50,7 +50,6 @@ error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
step() { echo -e "${CYAN}[STEP]${NC} $*"; }
SKIP_CLONE=false
SKIP_GIT=false
FRESH_INSTALL=false
REUSE_SECRETS=false
FROM_ENV="${NEXUS_FROM_ENV}"
@@ -182,13 +181,9 @@ apply_pool_to_env_prod() {
local root="$1" prof="$2"
local env_file="$root/$ENV_PROD"
[[ -f "$env_file" ]] || return 0
if [[ ! -f "$prof" ]]; then
warn "未找到资源档位文件: $prof,保留 $ENV_PROD 中现有数据库连接池配置"
return 0
fi
local pool overflow
pool="$(grep -E '^NEXUS_DB_POOL_SIZE=' "$prof" | cut -d= -f2- || true)"
overflow="$(grep -E '^NEXUS_DB_MAX_OVERFLOW=' "$prof" | cut -d= -f2- || true)"
pool="$(grep -E '^NEXUS_DB_POOL_SIZE=' "$prof" | cut -d= -f2-)"
overflow="$(grep -E '^NEXUS_DB_MAX_OVERFLOW=' "$prof" | cut -d= -f2-)"
[[ -n "$pool" ]] && sed -i "s|^NEXUS_DB_POOL_SIZE=.*|NEXUS_DB_POOL_SIZE=${pool}|" "$env_file"
[[ -n "$overflow" ]] && sed -i "s|^NEXUS_DB_MAX_OVERFLOW=.*|NEXUS_DB_MAX_OVERFLOW=${overflow}|" "$env_file"
}
@@ -1023,14 +1018,10 @@ verify_health() {
}
ensure_repo() {
if [[ ! -d "$NEXUS_ROOT" ]]; then
if [[ ! -d "$NEXUS_ROOT/.git" ]]; then
error "未找到 $NEXUS_ROOT,请先: bash $0 install"
exit 1
fi
if [[ "$SKIP_GIT" != true && ! -d "$NEXUS_ROOT/.git" ]]; then
error "未找到 $NEXUS_ROOT/.git,请先: bash $0 install;如为发布包/rsync 部署请使用 --skip-git"
exit 1
fi
if [[ ! -f "$NEXUS_ROOT/$ENV_PROD" ]]; then
error "缺少 $NEXUS_ROOT/$ENV_PROD"
exit 1
@@ -1147,12 +1138,8 @@ cmd_upgrade() {
load_saved_profile "$NEXUS_ROOT"
resolve_profile
check_ports_preflight "$NEXUS_ROOT"
if [[ "$SKIP_GIT" == true ]]; then
info "跳过 Git 同步(本机 rsync 已推送代码)"
else
git_fetch
show_pending || true
fi
git_fetch
show_pending || true
if [[ "$CHECK_ONLY" == true ]]; then
exit 0
@@ -1162,12 +1149,6 @@ cmd_upgrade() {
exit 0
fi
if [[ "$SKIP_GIT" == true ]]; then
info "本机直传模式:跳过 git reset,仍备份并重建镜像"
if [[ "$NO_BACKUP" != true ]]; then
backup_mysql "$NEXUS_ROOT"
fi
else
local behind
behind="$(commits_behind)"
if [[ "$behind" != "0" ]]; then
@@ -1179,7 +1160,6 @@ cmd_upgrade() {
backup_mysql "$NEXUS_ROOT"
fi
fi
fi
apply_pool_to_env_prod "$NEXUS_ROOT" "$(profile_env_file "$NEXUS_ROOT")"
upsert_env_var "$NEXUS_ROOT/$ENV_PROD" "NEXUS_HOST_ROOT" "$NEXUS_ROOT"
@@ -1292,7 +1272,6 @@ parse_common_upgrade() {
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) usage ;;
--skip-git) SKIP_GIT=true; shift ;;
--profile) NEXUS_PROFILE="$2"; shift 2 ;;
--cpus) CUSTOM_CPUS="$2"; shift 2 ;;
--mem-gb|--memory-gb) CUSTOM_MEM_GB="$2"; shift 2 ;;
Regular → Executable
View File
+16 -64
View File
@@ -1,6 +1,6 @@
#!/bin/bash
# Nexus Pre-deploy Gate Check (v38 gates)
# 在部署前强制检查 8 道门控,任何一道不过则阻止部署
# Nexus Pre-deploy Gate Check (v27 gates)
# 在部署前强制检查 7 道门控,任何一道不过则阻止部署
#
# 门控1: CHANGELOG — docs/changelog/ 下必须有今天的 .md 文件(且行数≥10)
# 门控2: AUDIT — docs/audit/ 下必须有今天的审计记录(且包含关键段落)
@@ -9,7 +9,6 @@
# 门控5: IMPORT — python -c "import server.main" 成功
# 门控6: SECURITY — bandit -r server/ 无 HIGH/MEDIUM
# 门控7: REVIEW — 审计文件包含 Closure表+文件清单+DoD,且文件清单与 git diff 交叉验证
# 门控8: AI_REVIEW — cursor-agent 审查 HEAD~1..HEAD;缓存 docs/reviews/<head_sha>.json
#
# 用法: bash deploy/pre_deploy_check.sh
# 退出码: 0=全部通过, 1=至少一道门未过
@@ -23,7 +22,7 @@ DEPLOY_DIR="${NEXUS_DEPLOY_DIR:-${_REPO_ROOT}}"
TODAY=$(date +%Y-%m-%d)
TIMESTAMP=$(date -Iseconds)
GATES_PASSED=0
GATES_TOTAL=8
GATES_TOTAL=7
BLOCKED=0
GATE_RESULTS=""
@@ -54,14 +53,14 @@ gate_log() {
}
echo "========================================"
echo " Nexus Pre-deploy Gate Check (v3)"
echo " Nexus Pre-deploy Gate Check (v2)"
echo " Date: ${TODAY}"
echo " Gates: ${GATES_TOTAL}"
echo "========================================"
echo ""
# ── Gate 1: Changelog (存在 + 行数≥10) ──
echo -n "Gate 1/8: Changelog ... "
echo -n "Gate 1/7: Changelog ... "
CHANGELOG_DIR="${DEPLOY_DIR}/docs/changelog"
if ls "${CHANGELOG_DIR}/${TODAY}-"*.md 1>/dev/null 2>&1; then
FILE=$(ls -t "${CHANGELOG_DIR}/${TODAY}-"*.md 2>/dev/null | head -1)
@@ -87,7 +86,7 @@ else
fi
# ── Gate 2: Audit (存在 + 关键段落) ──
echo -n "Gate 2/8: Audit ... "
echo -n "Gate 2/7: Audit ... "
AUDIT_DIR="${DEPLOY_DIR}/docs/audit"
if ls "${AUDIT_DIR}/${TODAY}-"*.md 1>/dev/null 2>&1; then
FILE=$(ls -t "${AUDIT_DIR}/${TODAY}-"*.md 2>/dev/null | head -1)
@@ -118,7 +117,7 @@ else
fi
# ── Gate 3: Test (必须存在 + 必须通过) ──
echo -n "Gate 3/8: Test ... "
echo -n "Gate 3/7: Test ... "
TEST_SCRIPT="${DEPLOY_DIR}/tests/test_api.py"
if [ ! -f "${TEST_SCRIPT}" ]; then
echo -e "${RED}BLOCK${NC}"
@@ -131,39 +130,12 @@ else
TEST_OUTPUT=$(cd "${DEPLOY_DIR}" && env -u NEXUS_TEST_BASE NEXUS_TEST_BASE=http://127.0.0.1:8600 python3 "${TEST_SCRIPT}" 2>&1) || true
FAIL_COUNT=$(echo "${TEST_OUTPUT}" | grep -cE "^\s+\[FAIL\]" 2>/dev/null || true)
FAIL_COUNT=${FAIL_COUNT:-0}
GATE3_PASSED=0
if [ "${FAIL_COUNT}" -gt 0 ] || echo "${TEST_OUTPUT}" | grep -qE "[0-9]+ test(s) failed"; then
# 本机未起 :8600 时,若失败均为 Connection refused,回退 pytest 专项
NON_CONN_FAILS=$(echo "${TEST_OUTPUT}" | grep -E "^\s+\[FAIL\]" | grep -vcE "Connection refused|Errno 111" 2>/dev/null || true)
NON_CONN_FAILS=${NON_CONN_FAILS:-0}
if [ "${FAIL_COUNT}" -gt 0 ] && [ "${NON_CONN_FAILS}" -eq 0 ]; then
PYTEST_BIN=$(_pick_bin pytest || true)
FALLBACK_TESTS="tests/test_btpanel_ssh_bootstrap.py tests/test_terminal_quick_commands.py tests/test_gate_ai_review.py"
if [ -n "${PYTEST_BIN}" ]; then
echo -e "${YELLOW}fallback${NC}"
echo " └─ Local :8600 unreachable — running pytest gate batch"
FALLBACK_OUTPUT=$(cd "${DEPLOY_DIR}" && "${PYTEST_BIN}" ${FALLBACK_TESTS} -q 2>&1) || FALLBACK_RC=$?
FALLBACK_RC=${FALLBACK_RC:-0}
if [ "${FALLBACK_RC}" -eq 0 ]; then
echo -e " └─ ${GREEN}PASS${NC} (pytest fallback)"
GATES_PASSED=$((GATES_PASSED + 1))
gate_log "test" "PASS" "pytest fallback :8600 down"
GATE3_PASSED=1
else
echo -e " └─ ${RED}BLOCK${NC} pytest fallback failed:"
echo "${FALLBACK_OUTPUT}" | tail -15 | sed 's/^/ │ /'
BLOCKED=1
gate_log "test" "BLOCK" "pytest fallback failed"
fi
fi
fi
if [ "${GATE3_PASSED}" -eq 0 ]; then
echo -e "${RED}BLOCK${NC}"
echo " └─ Tests failed. Output:"
echo "${TEST_OUTPUT}" | head -20 | sed 's/^/ │ /'
BLOCKED=1
gate_log "test" "BLOCK" "tests failed"
fi
echo -e "${RED}BLOCK${NC}"
echo " └─ Tests failed. Output:"
echo "${TEST_OUTPUT}" | head -20 | sed 's/^/ │ /'
BLOCKED=1
gate_log "test" "BLOCK" "tests failed"
else
echo -e "${GREEN}PASS${NC}"
echo " └─ All tests passed"
@@ -173,7 +145,7 @@ else
fi
# ── Gate 4: Lint (ruff — F only: undefined names, unused imports) ──
echo -n "Gate 4/8: Lint ... "
echo -n "Gate 4/7: Lint ... "
RUFF_BIN=$(_pick_bin ruff || true)
if [ -n "${RUFF_BIN}" ]; then
LINT_OUTPUT=$(cd "${DEPLOY_DIR}" && "${RUFF_BIN}" check server/ --select F 2>&1) || true
@@ -200,7 +172,7 @@ else
fi
# ── Gate 5: Import (python -c "import server.main") ──
echo -n "Gate 5/8: Import ... "
echo -n "Gate 5/7: Import ... "
IMPORT_PYTHON=""
for p in "${DEPLOY_DIR}/.venv/bin/python3" "${DEPLOY_DIR}/venv/bin/python3"; do
if [ -x "${p}" ]; then
@@ -235,7 +207,7 @@ else
fi
# ── Gate 6: Security (bandit — HIGH only) ──
echo -n "Gate 6/8: Security ... "
echo -n "Gate 6/7: Security ... "
BANDIT_BIN=$(_pick_bin bandit || true)
if [ -n "${BANDIT_BIN}" ]; then
SEC_OUTPUT=$(cd "${DEPLOY_DIR}" && "${BANDIT_BIN}" -r server/ -f txt -ll 2>&1) || true
@@ -262,7 +234,7 @@ else
fi
# ── Gate 7: Review (审计文件交叉验证) ──
echo -n "Gate 7/8: Review ... "
echo -n "Gate 7/7: Review ... "
if ls "${AUDIT_DIR}/${TODAY}-"*.md 1>/dev/null 2>&1; then
FILE=$(ls -t "${AUDIT_DIR}/${TODAY}-"*.md 2>/dev/null | head -1)
# 检查审计文件中是否列出了实际改动的文件
@@ -301,26 +273,6 @@ else
gate_log "review" "BLOCK" "no audit file"
fi
# ── Gate 8: AI Review (cursor-agent + docs/reviews cache) ──
echo -n "Gate 8/8: AI Review ... "
AI_REVIEW_OUT=$(cd "${DEPLOY_DIR}" && python3 "${DEPLOY_DIR}/scripts/gate_ai_review.py" 2>&1) || AI_REVIEW_RC=$?
AI_REVIEW_RC=${AI_REVIEW_RC:-0}
if [ "${AI_REVIEW_RC}" -eq 0 ]; then
echo -e "${GREEN}PASS${NC}"
echo "${AI_REVIEW_OUT}" | sed 's/^/ └─ /' | head -5
GATES_PASSED=$((GATES_PASSED + 1))
if echo "${AI_REVIEW_OUT}" | grep -qi "skip"; then
gate_log "ai_review" "PASS" "skipped no code"
else
gate_log "ai_review" "PASS" "ok"
fi
else
echo -e "${RED}BLOCK${NC}"
echo "${AI_REVIEW_OUT}" | sed 's/^/ │ /' | head -12
BLOCKED=1
gate_log "ai_review" "BLOCK" "see gate_ai_review.py output"
fi
# ── Pre-flight: shell scripts LF ──
echo -n "Pre-flight: Shell EOL (LF) ... "
if bash "${DEPLOY_DIR}/deploy/check_shell_eol.sh" >/dev/null 2>&1; then
-197
View File
@@ -1,197 +0,0 @@
#!/usr/bin/env bash
# Nexus production host preflight before applying a release tarball.
#
# Usage:
# bash deploy/preflight-release-host.sh \
# --tarball /tmp/nexus-release-20260708.tar.gz \
# --sha256 a4eb8f35f0b98d40c1ad83117bfc652e7686ffc7d55f6bf90fcfd433a2ad6701 \
# --deploy-path /opt/nexus
set -euo pipefail
TARBALL=""
EXPECTED_SHA256=""
DEPLOY_PATH="${NEXUS_DEPLOY_PATH:-/opt/nexus}"
MIN_FREE_MB="${NEXUS_RELEASE_MIN_FREE_MB:-1024}"
info() { echo -e "\033[0;32m[INFO]\033[0m $*"; }
warn() { echo -e "\033[1;33m[WARN]\033[0m $*"; }
error() { echo -e "\033[0;31m[ERROR]\033[0m $*" >&2; }
usage() {
sed -n '1,12p' "$0"
}
while [[ $# -gt 0 ]]; do
case "$1" in
--tarball)
TARBALL="${2:-}"
shift 2
;;
--sha256)
EXPECTED_SHA256="${2:-}"
shift 2
;;
--deploy-path)
DEPLOY_PATH="${2:-}"
shift 2
;;
--min-free-mb)
MIN_FREE_MB="${2:-}"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
error "Unknown argument: $1"
usage
exit 2
;;
esac
done
check_cmd() {
local cmd="$1"
if command -v "$cmd" >/dev/null 2>&1; then
info "command OK: $cmd"
return 0
fi
error "missing command: $cmd"
return 1
}
docker_cmd() {
if docker "$@" 2>/dev/null; then return 0; fi
sudo docker "$@" 2>/dev/null
}
detect_runtime() {
if [[ -f "${DEPLOY_PATH}/docker/.env.prod" ]] && command -v docker >/dev/null 2>&1; then
echo docker
return
fi
if command -v supervisorctl >/dev/null 2>&1 && supervisorctl status nexus >/dev/null 2>&1; then
echo supervisor
return
fi
echo unknown
}
verify_tarball() {
if [[ -z "$TARBALL" ]]; then
warn "--tarball not provided; skipping tarball checksum/content checks"
return 0
fi
[[ -f "$TARBALL" ]] || {
error "tarball not found: $TARBALL"
return 1
}
info "tarball exists: $TARBALL"
if [[ -n "$EXPECTED_SHA256" ]]; then
local actual
actual="$(sha256sum "$TARBALL" | awk '{print $1}')"
if [[ "$actual" != "$EXPECTED_SHA256" ]]; then
error "SHA256 mismatch: expected=$EXPECTED_SHA256 actual=$actual"
return 1
fi
info "SHA256 OK: $actual"
else
warn "--sha256 not provided; checksum not verified"
fi
tar tzf "$TARBALL" nexus-release/server nexus-release/deploy nexus-release/web/app-v2 >/dev/null
info "tarball required paths OK"
}
check_deploy_path() {
if [[ -d "$DEPLOY_PATH" ]]; then
info "deploy path exists: $DEPLOY_PATH"
else
warn "deploy path does not exist yet: $DEPLOY_PATH"
fi
if [[ -f "${DEPLOY_PATH}/.env" ]]; then
info "runtime .env preserved path exists"
else
warn "${DEPLOY_PATH}/.env not found"
fi
if [[ -f "${DEPLOY_PATH}/docker/.env.prod" ]]; then
info "docker/.env.prod exists"
else
warn "${DEPLOY_PATH}/docker/.env.prod not found"
fi
if [[ -d "${DEPLOY_PATH}/web/data" ]]; then
info "web/data exists"
else
warn "${DEPLOY_PATH}/web/data not found"
fi
}
check_disk_space() {
local target free_mb
target="$DEPLOY_PATH"
[[ -e "$target" ]] || target="$(dirname "$DEPLOY_PATH")"
free_mb="$(df -Pm "$target" | awk 'NR==2 {print $4}')"
if [[ -z "$free_mb" ]]; then
warn "unable to determine free disk space for $target"
return 0
fi
if (( free_mb < MIN_FREE_MB )); then
error "low disk space on $target: ${free_mb}MB < ${MIN_FREE_MB}MB"
return 1
fi
info "disk free OK: ${free_mb}MB >= ${MIN_FREE_MB}MB"
}
check_runtime() {
local runtime
runtime="$(detect_runtime)"
info "runtime detected: $runtime"
case "$runtime" in
docker)
docker_cmd ps --format '{{.Names}}' | grep -E 'nexus-prod-nexus|nexus-nexus-1' >/dev/null \
&& info "Nexus docker container found" \
|| warn "Nexus docker container not found by known name pattern"
;;
supervisor)
supervisorctl status nexus || true
;;
*)
warn "runtime unknown; apply script may require manual restart"
;;
esac
}
check_local_endpoint() {
local port health app app_v2
port="$(grep -E '^NEXUS_PUBLISH_PORT=' "${DEPLOY_PATH}/docker/.env.prod" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '\r" ' || true)"
port="${port:-8600}"
health="$(curl -s "http://127.0.0.1:${port}/health" 2>/dev/null || true)"
app="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${port}/app/" 2>/dev/null || echo 000)"
app_v2="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${port}/app-v2/" 2>/dev/null || echo 000)"
echo " current /health -> ${health:-<empty>}"
echo " current /app/ -> ${app}"
echo " current /app-v2/ -> ${app_v2}"
}
main() {
local failures=0
for cmd in tar sha256sum df awk grep sed curl; do
check_cmd "$cmd" || failures=$((failures + 1))
done
check_cmd rsync || failures=$((failures + 1))
verify_tarball || failures=$((failures + 1))
check_deploy_path
check_disk_space || failures=$((failures + 1))
check_runtime
check_local_endpoint
if (( failures > 0 )); then
error "preflight failed: ${failures} blocking issue(s)"
return 1
fi
info "preflight OK"
}
main "$@"
Regular → Executable
View File
-112
View File
@@ -1,112 +0,0 @@
#!/usr/bin/env bash
# Sync local Nexus working tree to production host (no Gitea push/pull).
# Transfer via tar|ssh; remote ownership auto-detected (www:www on BT, else deploy path owner).
#
# Usage:
# bash deploy/rsync-local-to-server.sh
# NEXUS_DEPLOY_PATH=/opt/nexus bash deploy/rsync-local-to-server.sh
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "${ROOT}"
SECRETS="${ROOT}/deploy/nexus-1panel.secrets.sh"
if [[ -f "$SECRETS" ]]; then
# shellcheck disable=SC1090
source "$SECRETS"
fi
NEXUS_SSH_HOST="${NEXUS_SSH:-nexus}"
SSH_OPTS=(-o BatchMode=yes -o ConnectTimeout=15)
if [[ -n "${NEXUS_SSH_KEY:-}" ]]; then
SSH_OPTS+=(-i "${NEXUS_SSH_KEY}")
fi
ssh_cmd() { ssh "${SSH_OPTS[@]}" "${NEXUS_SSH_HOST}" "$@"; }
if ! ssh_cmd "echo SSH OK" >/dev/null 2>&1; then
echo "ERROR: Cannot SSH to ${NEXUS_SSH_HOST}" >&2
exit 1
fi
DEPLOY_PATH="${NEXUS_DEPLOY_PATH:-}"
if [[ -z "$DEPLOY_PATH" ]]; then
DEPLOY_PATH="$(ssh_cmd 'for d in /opt/nexus /www/wwwroot/api.synaglobal.vip; do [[ -d "$d/server" ]] && echo "$d" && exit 0; done; echo /opt/nexus')"
fi
resolve_deploy_chown() {
if [[ -n "${NEXUS_DEPLOY_CHOWN:-}" ]]; then
echo "${NEXUS_DEPLOY_CHOWN}"
return
fi
if ssh_cmd "getent passwd www >/dev/null 2>&1"; then
echo "www:www"
return
fi
ssh_cmd "stat -c '%U:%G' '${DEPLOY_PATH}'" 2>/dev/null || echo "azureuser:azureuser"
}
DEPLOY_CHOWN="$(resolve_deploy_chown)"
echo "═══ Nexus local → server sync ═══"
echo "SSH target: ${NEXUS_SSH_HOST}"
echo "Remote path: ${DEPLOY_PATH}"
echo "Owner: ${DEPLOY_CHOWN}"
TAR_EXCLUDES=(
--exclude='.git'
--exclude='.env'
--exclude='.env.*'
--exclude='SECRETS.md'
--exclude='deploy/nexus-1panel.secrets.sh'
--exclude='docker/.env.prod'
--exclude='web/data'
--exclude='frontend/node_modules'
--exclude='frontend-v2/node_modules'
--exclude='node_modules'
--exclude='.venv'
--exclude='.venv-*'
--exclude='venv'
--exclude='__pycache__'
--exclude='.pytest_cache'
--exclude='.ruff_cache'
--exclude='.cursor'
--exclude='.playwright-mcp'
--exclude='.install_locked'
--exclude='.install_state.json'
--exclude='web/app/assets'
--exclude='tmp'
--exclude='2025.2'
--exclude='=2025.2'
--exclude='*.bak-'
--exclude='frontend/test-results'
--exclude='frontend/e2e/.auth'
)
echo "▶ tar stream → ${DEPLOY_PATH} ..."
tar -C "${ROOT}" -czf - "${TAR_EXCLUDES[@]}" . | \
ssh "${SSH_OPTS[@]}" "${NEXUS_SSH_HOST}" "sudo tar -xzf - -C '${DEPLOY_PATH}'"
echo "▶ chown ${DEPLOY_CHOWN} (skip docker/.env.prod, web/data, .env) ..."
ssh_cmd "sudo bash -s" <<REMOTE
set -euo pipefail
root='${DEPLOY_PATH}'
own='${DEPLOY_CHOWN}'
chown_tree() {
local p="\$1"
[[ -e "\$p" ]] || return 0
sudo chown -R "\$own" "\$p"
}
for top in server deploy scripts tests standards docs frontend web; do
chown_tree "\$root/\$top"
done
if [[ -d "\$root/docker" ]]; then
while IFS= read -r -d '' p; do
sudo chown -R "\$own" "\$p"
done < <(find "\$root/docker" \( -path "\$root/docker/.env.prod" \) -prune -o -print0)
fi
for f in pyproject.toml requirements.txt requirements-dev.txt AGENTS.md CLAUDE.md; do
[[ -f "\$root/\$f" ]] && sudo chown "\$own" "\$root/\$f"
done
REMOTE
echo "✓ sync complete (owner ${DEPLOY_CHOWN})"
View File
+2 -34
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Sync Git-tracked web/app and web/app-v2 into the running Nexus Docker container.
# Sync Git-tracked web/app into the running Nexus Docker container.
#
# Root cause: Dockerfile.prod runs vite build inside the image; Docker layer cache
# can produce asset hashes that differ from committed web/app on the host.
@@ -51,25 +51,8 @@ verify_entry_bundle() {
info "入口校验 OK: ${bundle_path} → HTTP 200"
}
verify_app_v2_entry_bundle() {
local html bundle_path code
html="$(curl -sf "http://127.0.0.1:${NEXUS_PUBLISH_PORT}/app-v2/" 2>/dev/null || true)"
bundle_path="$(printf '%s' "$html" | sed -n 's/.*src="\(\/app-v2\/assets\/[^"]*\.js\)".*/\1/p' | head -1)"
if [[ -z "$bundle_path" ]]; then
error "/app-v2/ index.html 未解析到主 bundle"
exit 1
fi
code="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${NEXUS_PUBLISH_PORT}${bundle_path}" 2>/dev/null || echo 000)"
if [[ "$code" != "200" ]]; then
error "V2 主 bundle ${bundle_path} → HTTP ${code}"
exit 1
fi
info "V2 入口校验 OK: ${bundle_path} → HTTP 200"
}
main() {
local app="${NEXUS_ROOT}/web/app"
local app_v2="${NEXUS_ROOT}/web/app-v2"
if [[ ! -f "${app}/index.html" ]]; then
error "缺少 ${app}/index.html,请先 git pull"
exit 1
@@ -78,14 +61,6 @@ main() {
error "缺少 ${app}/assets"
exit 1
fi
if [[ ! -f "${app_v2}/index.html" ]]; then
error "缺少 ${app_v2}/index.html,请先构建 frontend-v2"
exit 1
fi
if [[ ! -d "${app_v2}/assets" ]]; then
error "缺少 ${app_v2}/assets"
exit 1
fi
resolve_container
@@ -99,12 +74,6 @@ main() {
docker_cmd cp "${app}/favicon.ico" "${CONTAINER}:/app/web/app/favicon.ico" 2>/dev/null || true
fi
info "同步 web/app-v2 → 容器 /app/web/app-v2 ..."
docker_cmd exec "$CONTAINER" rm -rf /app/web/app-v2/assets
docker_cmd exec "$CONTAINER" mkdir -p /app/web/app-v2
docker_cmd cp "${app_v2}/index.html" "${CONTAINER}:/app/web/app-v2/index.html"
docker_cmd cp "${app_v2}/assets" "${CONTAINER}:/app/web/app-v2/assets"
if [[ -x "${NEXUS_ROOT}/deploy/prune_frontend_assets.py" ]]; then
info "容器内 prune 未引用 assets(可选)..."
docker_cmd exec "$CONTAINER" python3 /app/deploy/prune_frontend_assets.py --dry-run 2>/dev/null || \
@@ -112,8 +81,7 @@ main() {
fi
verify_entry_bundle
verify_app_v2_entry_bundle
info "web/app 与 web/app-v2 同步完成"
info "web/app 同步完成"
}
main "$@"
View File
View File
Regular → Executable
View File
Regular → Executable
View File
+8 -15
View File
@@ -1,8 +1,8 @@
# Nexus 6.0 鈥?Docker Compose (dev / staging)
# Nexus 6.0 Docker Compose (dev / staging)
# Usage:
# cp docker/.env.example docker/.env # or: python docker/generate_env.py
# docker compose --env-file docker/.env up -d --build
# open http://localhost:18600/app/
# docker compose up -d --build
# open http://localhost:8600/app/
name: nexus
@@ -24,7 +24,7 @@ services:
retries: 8
start_period: 40s
ports:
- "${MYSQL_PUBLISH_PORT:-13306}:3306"
- "${MYSQL_PUBLISH_PORT:-3306}:3306"
redis:
image: redis:7-alpine
@@ -38,18 +38,12 @@ services:
timeout: 3s
retries: 8
ports:
- "${REDIS_PUBLISH_PORT:-16379}:6379"
- "${REDIS_PUBLISH_PORT:-6379}:6379"
nexus:
build:
context: .
dockerfile: Dockerfile
network: host
args:
DEBIAN_MIRROR: https://mirrors.aliyun.com/debian
DEBIAN_SECURITY_MIRROR: https://mirrors.aliyun.com/debian-security
PIP_INDEX_URL: https://mirrors.aliyun.com/pypi/simple/
PIP_TRUSTED_HOST: mirrors.aliyun.com
restart: unless-stopped
depends_on:
mysql:
@@ -57,7 +51,7 @@ services:
redis:
condition: service_healthy
ports:
- "${NEXUS_PUBLISH_PORT:-18600}:8600"
- "${NEXUS_PUBLISH_PORT:-8600}:8600"
environment:
MYSQL_HOST: mysql
MYSQL_PORT: "3306"
@@ -70,11 +64,11 @@ services:
NEXUS_DEPLOY_PATH: /app
NEXUS_DATABASE_URL: mysql+aiomysql://nexus:${MYSQL_PASSWORD:-nexus_dev_pass}@mysql:3306/nexus
NEXUS_REDIS_URL: redis://redis:6379/0
NEXUS_CORS_ORIGINS: ${NEXUS_CORS_ORIGINS:-http://localhost:18600,http://127.0.0.1:18600,http://localhost:8600,http://127.0.0.1:8600}
NEXUS_CORS_ORIGINS: ${NEXUS_CORS_ORIGINS:-http://localhost:8600,http://127.0.0.1:8600}
NEXUS_SECRET_KEY: ${NEXUS_SECRET_KEY:-}
NEXUS_API_KEY: ${NEXUS_API_KEY:-}
NEXUS_ENCRYPTION_KEY: ${NEXUS_ENCRYPTION_KEY:-}
NEXUS_API_BASE_URL: ${NEXUS_API_BASE_URL:-http://localhost:18600}
NEXUS_API_BASE_URL: ${NEXUS_API_BASE_URL:-http://localhost:8600}
env_file:
- path: docker/.env
required: false
@@ -93,4 +87,3 @@ volumes:
redis-data:
nexus-state:
nexus-web-data:
+18
View File
@@ -0,0 +1,18 @@
# Copy to docker/.env (or run: python docker/generate_env.py)
# Used by docker compose for nexus service — never commit docker/.env
MYSQL_ROOT_PASSWORD=change_me_root
MYSQL_PASSWORD=change_me_nexus
# Leave empty to use install wizard at http://localhost:8600/app/install.html
# Or run generate_env.py to fill these three:
NEXUS_SECRET_KEY=
NEXUS_API_KEY=
NEXUS_ENCRYPTION_KEY=
NEXUS_CORS_ORIGINS=http://localhost:8600,http://127.0.0.1:8600
NEXUS_API_BASE_URL=http://localhost:8600
# Optional host port overrides
# NEXUS_PUBLISH_PORT=8600
# MYSQL_PUBLISH_PORT=3306
+39
View File
@@ -0,0 +1,39 @@
# Production Compose env — copy to docker/.env.prod (never commit)
# Fresh install: bash deploy/install-nexus-fresh.sh (auto-generates NEXUS keys per host)
# MySQL / Redis 不在 Docker 栈 — 请在宿主机或 1Panel 自建,安装向导步骤 3 填写
# Auto-generated on fresh install (unique per server). Do NOT copy between hosts.
NEXUS_SECRET_KEY=
NEXUS_API_KEY=
NEXUS_ENCRYPTION_KEY=
# 1 = entrypoint must not write /app/.env until install wizard step 3
NEXUS_INSTALL_WIZARD_PENDING=0
# Public URL
NEXUS_CORS_ORIGINS=https://api.synaglobal.vip
NEXUS_API_BASE_URL=https://api.synaglobal.vip
# Nexus container resource profile (docker/profiles/*.env)
NEXUS_DB_POOL_SIZE=30
NEXUS_DB_MAX_OVERFLOW=20
# 1Panelnx update 自动探测并写入(安装向导步骤 3 预填)
# NEXUS_1PANEL_DB_HOST=1Panel-mysql-xxxx
# NEXUS_1PANEL_REDIS_HOST=1Panel-redis-xxxx
# 宿主机 Compose 插值/ nx update 同步用;安装向导完成后以卷内 /app/.env 为准(不经 Compose 注入)
# 1Panel 有密码时: redis://:你的密码@1Panel-redis-xxxx:6379/0
NEXUS_REDIS_URL=redis://host.docker.internal:6379/0
# 宿主机 git 仓库路径(容器内安装向导用于提示 nx cron / nx update
NEXUS_HOST_ROOT=/opt/nexus
# Optional — production probe / E2E only (host .env.prod; NOT for API runtime; never commit values)
# NEXUS_TEST_ADMIN_PASSWORD — must match admin login password
# NEXUS_PROBE_CLIENT_IP — optional; IP in login allowlist for origin probe (auto-read from DB if unset)
# NEXUS_TEST_ADMIN_PASSWORD=
# NEXUS_PROBE_CLIENT_IP=
# Optional
# NEXUS_PUBLISH_PORT=8600
+20 -20
View File
@@ -1,20 +1,20 @@
# MySQL 8.4 — Nexus production tuning for 1 vCPU / 4 GiB host (minimal)
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
innodb_buffer_pool_size = 512M
innodb_log_file_size = 128M
max_connections = 80
innodb_read_io_threads = 2
innodb_write_io_threads = 2
table_open_cache = 1000
tmp_table_size = 32M
max_heap_table_size = 32M
[client]
default-character-set = utf8mb4
# MySQL 8.4 — Nexus production tuning for 1 vCPU / 4 GiB host (minimal)
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
innodb_buffer_pool_size = 512M
innodb_log_file_size = 128M
max_connections = 80
innodb_read_io_threads = 2
innodb_write_io_threads = 2
table_open_cache = 1000
tmp_table_size = 32M
max_heap_table_size = 32M
[client]
default-character-set = utf8mb4
+25 -25
View File
@@ -1,25 +1,25 @@
# MySQL 8.4 — Nexus production tuning for 2 vCPU / 8 GiB host
# Mounted by docker/docker-compose.prod.yml (see docs/design/plans/2026-06-04-1panel-docker-production.md)
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
# ~1 GiB for InnoDB (leave RAM for Nexus, Redis, 1Panel, OS)
innodb_buffer_pool_size = 1024M
innodb_log_file_size = 256M
# Nexus async pool default 30+20; leave headroom for admin/migrations
max_connections = 120
# 2 vCPU: avoid aggressive IO threads
innodb_read_io_threads = 4
innodb_write_io_threads = 4
# Sensible defaults on small VMs
table_open_cache = 2000
tmp_table_size = 64M
max_heap_table_size = 64M
[client]
default-character-set = utf8mb4
# MySQL 8.4 — Nexus production tuning for 2 vCPU / 8 GiB host
# Mounted by docker/docker-compose.prod.yml (see docs/design/plans/2026-06-04-1panel-docker-production.md)
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
# ~1 GiB for InnoDB (leave RAM for Nexus, Redis, 1Panel, OS)
innodb_buffer_pool_size = 1024M
innodb_log_file_size = 256M
# Nexus async pool default 30+20; leave headroom for admin/migrations
max_connections = 120
# 2 vCPU: avoid aggressive IO threads
innodb_read_io_threads = 4
innodb_write_io_threads = 4
# Sensible defaults on small VMs
table_open_cache = 2000
tmp_table_size = 64M
max_heap_table_size = 64M
[client]
default-character-set = utf8mb4
+20 -20
View File
@@ -1,20 +1,20 @@
# MySQL 8.4 — Nexus production tuning for 4 vCPU / 16 GiB host
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
innodb_buffer_pool_size = 4096M
innodb_log_file_size = 512M
max_connections = 200
innodb_read_io_threads = 8
innodb_write_io_threads = 8
table_open_cache = 4000
tmp_table_size = 128M
max_heap_table_size = 128M
[client]
default-character-set = utf8mb4
# MySQL 8.4 — Nexus production tuning for 4 vCPU / 16 GiB host
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
innodb_buffer_pool_size = 4096M
innodb_log_file_size = 512M
max_connections = 200
innodb_read_io_threads = 8
innodb_write_io_threads = 8
table_open_cache = 4000
tmp_table_size = 128M
max_heap_table_size = 128M
[client]
default-character-set = utf8mb4
+5 -5
View File
@@ -1,5 +1,5 @@
# Host profile: 1 vCPU / 4 GiB
NEXUS_MEM_LIMIT=1g
NEXUS_CPUS=1
NEXUS_DB_POOL_SIZE=15
NEXUS_DB_MAX_OVERFLOW=10
# Host profile: 1 vCPU / 4 GiB
NEXUS_MEM_LIMIT=1g
NEXUS_CPUS=1
NEXUS_DB_POOL_SIZE=15
NEXUS_DB_MAX_OVERFLOW=10
+5 -5
View File
@@ -1,5 +1,5 @@
# Host profile: 2 vCPU / 8 GiB — default production
NEXUS_MEM_LIMIT=2g
NEXUS_CPUS=1.5
NEXUS_DB_POOL_SIZE=30
NEXUS_DB_MAX_OVERFLOW=20
# Host profile: 2 vCPU / 8 GiB — default production
NEXUS_MEM_LIMIT=2g
NEXUS_CPUS=1.5
NEXUS_DB_POOL_SIZE=30
NEXUS_DB_MAX_OVERFLOW=20
+5 -5
View File
@@ -1,5 +1,5 @@
# Host profile: 4 vCPU / 16 GiB
NEXUS_MEM_LIMIT=4g
NEXUS_CPUS=3
NEXUS_DB_POOL_SIZE=50
NEXUS_DB_MAX_OVERFLOW=30
# Host profile: 4 vCPU / 16 GiB
NEXUS_MEM_LIMIT=4g
NEXUS_CPUS=3
NEXUS_DB_POOL_SIZE=50
NEXUS_DB_MAX_OVERFLOW=30
+733
View File
@@ -0,0 +1,733 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Nexus 6.0 — 功能说明书</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#0f172a;color:#e2e8f0;line-height:1.6}
a{color:#60a5fa;text-decoration:none}
a:hover{text-decoration:underline}
/* Layout */
.wrap{max-width:960px;margin:0 auto;padding:32px 24px 80px}
/* Header */
.hero{text-align:center;padding:48px 0 40px;border-bottom:1px solid #1e293b;margin-bottom:40px}
.hero h1{font-size:2.2rem;font-weight:700;color:#f8fafc;letter-spacing:-0.02em}
.hero .sub{font-size:1rem;color:#64748b;margin-top:8px}
.badge{display:inline-block;margin:4px;padding:3px 10px;border-radius:999px;font-size:0.72rem;font-weight:600}
.badge-blue{background:#1e3a5f;color:#60a5fa;border:1px solid #2563eb55}
.badge-green{background:#14371f;color:#4ade80;border:1px solid #16a34a55}
.badge-amber{background:#3d2a00;color:#fbbf24;border:1px solid #d9770655}
.badge-red{background:#3b1212;color:#f87171;border:1px solid #dc262655}
/* TOC */
.toc{background:#1e293b;border:1px solid #334155;border-radius:12px;padding:20px 24px;margin-bottom:36px}
.toc h2{font-size:0.85rem;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.08em;margin-bottom:12px}
.toc ol{padding-left:18px;column-count:2;column-gap:24px}
.toc li{margin:5px 0;font-size:0.88rem}
/* Section */
.section{margin-bottom:48px}
.section-title{font-size:1.4rem;font-weight:700;color:#f1f5f9;padding-bottom:10px;border-bottom:2px solid #334155;margin-bottom:24px;display:flex;align-items:center;gap:10px}
.section-title .icon{font-size:1.2rem}
/* Card */
.card{background:#1e293b;border:1px solid #334155;border-radius:12px;padding:24px;margin-bottom:16px}
.card-title{font-size:1.05rem;font-weight:600;color:#f1f5f9;margin-bottom:6px;display:flex;align-items:center}
.card-title .num{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;background:#334155;font-size:0.7rem;font-weight:700;color:#94a3b8;margin-right:8px;shrink:0}
.card-desc{color:#94a3b8;font-size:0.88rem;margin-bottom:14px}
/* Info rows */
.info-grid{display:grid;grid-template-columns:1fr;gap:10px}
.info-block{border-radius:8px;padding:12px 14px}
.info-block.usage{background:#0f2d1f;border:1px solid #166534}
.info-block.warn{background:#2a1a00;border:1px solid #92400e}
.info-block.note{background:#1a1f2e;border:1px solid #4338ca}
.info-title{font-size:0.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;margin-bottom:6px}
.info-title.u{color:#4ade80}
.info-title.w{color:#fbbf24}
.info-title.n{color:#818cf8}
.info-block ul{padding-left:16px;font-size:0.86rem;color:#cbd5e1}
.info-block li{margin:4px 0}
.info-block p{font-size:0.86rem;color:#cbd5e1}
/* Sub table */
.feat-table{width:100%;border-collapse:collapse;font-size:0.86rem;margin-top:10px}
.feat-table th{text-align:left;color:#64748b;font-weight:600;font-size:0.75rem;text-transform:uppercase;padding:6px 10px;border-bottom:1px solid #334155}
.feat-table td{padding:7px 10px;border-bottom:1px solid #1e293b;color:#cbd5e1;vertical-align:top}
.feat-table tr:last-child td{border-bottom:none}
/* Footer */
footer{text-align:center;color:#334155;font-size:0.8rem;margin-top:60px;padding-top:24px;border-top:1px solid #1e293b}
code{background:#334155;color:#7dd3fc;padding:2px 6px;border-radius:4px;font-size:0.83em;font-family:monospace}
</style>
</head>
<body>
<div class="wrap">
<!-- HERO -->
<div class="hero">
<h1>⚡ Nexus 6.0</h1>
<p class="sub">服务器运维管理平台 — 全站功能说明书 · 2026-05-23</p>
<p class="sub" style="margin-top:10px;font-size:0.85rem">
Markdown 维护版:<code>docs/project/nexus-full-site-features.md</code> ·
AI 接续:<code>docs/project/AI-HANDOFF-2026-05-23.md</code>
</p>
<div style="margin-top:14px">
<span class="badge badge-blue">FastAPI</span>
<span class="badge badge-blue">SQLAlchemy 2.0</span>
<span class="badge badge-blue">Redis 7</span>
<span class="badge badge-blue">asyncssh</span>
<span class="badge badge-blue">Tailwind v4</span>
<span class="badge badge-green">2000+ 服务器</span>
<span class="badge badge-green">JWT + TOTP</span>
</div>
</div>
<!-- TOC -->
<div class="toc">
<h2>目录</h2>
<ol>
<li><a href="#sitemap">全站页面地图</a></li>
<li><a href="#network">网络与通道</a></li>
<li><a href="#auth">认证与访问控制</a></li>
<li><a href="#dashboard">仪表盘</a></li>
<li><a href="#alerts">告警中心</a></li>
<li><a href="#servers">服务器管理</a></li>
<li><a href="#assets">资产管理</a></li>
<li><a href="#webssh">Web SSH</a></li>
<li><a href="#commands">命令与推送日志</a></li>
<li><a href="#push">批量文件推送</a></li>
<li><a href="#scripts">脚本与执行历史</a></li>
<li><a href="#files">远程文件管理</a></li>
<li><a href="#schedules">定时调度</a></li>
<li><a href="#credentials">凭据管理</a></li>
<li><a href="#retries">重试队列</a></li>
<li><a href="#search">全局搜索</a></li>
<li><a href="#settings">系统设置</a></li>
<li><a href="#audit">审计日志</a></li>
<li><a href="#install">安装向导</a></li>
<li><a href="#guardian">3 层守护</a></li>
<li><a href="#agent">子机 Agent</a></li>
<li><a href="#background">后台任务</a></li>
<li><a href="#acceptance">开发验收标准</a></li>
<li><a href="#notimpl">未实现 / 已否决</a></li>
</ol>
</div>
<!-- 0. SITEMAP -->
<div class="section" id="sitemap">
<div class="section-title"><span class="icon">🗺</span> 全站页面地图(18 页)</div>
<div class="card">
<table class="feat-table">
<tr><th>页面</th><th>路径</th><th>功能</th></tr>
<tr><td>安装向导</td><td><code>/app/install.html</code></td><td>无 .env 时安装中心机</td></tr>
<tr><td>登录</td><td><code>/app/login.html</code></td><td>JWT + TOTP + 防暴破</td></tr>
<tr><td>仪表盘</td><td><code>/app/index.html</code></td><td>统计、WS 刷新统计</td></tr>
<tr><td>服务器</td><td><code>/app/servers.html</code></td><td>CRUD、Agent 安装/升级、漂移徽章</td></tr>
<tr><td>资产管理</td><td><code>/app/assets.html</code></td><td>Platform / Node</td></tr>
<tr><td>文件管理</td><td><code>/app/files.html</code></td><td>SSH 浏览与文件操作</td></tr>
<tr><td>推送</td><td><code>/app/push.html</code></td><td>rsync 批量 + dry-run</td></tr>
<tr><td>脚本库</td><td><code>/app/scripts.html</code></td><td>脚本 CRUD、直接执行</td></tr>
<tr><td>执行历史</td><td><code>/app/script-executions.html</code></td><td>停止/重试/日志</td></tr>
<tr><td>凭据</td><td><code>/app/credentials.html</code></td><td>DB 凭据 + 密码预设</td></tr>
<tr><td>定时调度</td><td><code>/app/schedules.html</code></td><td>Cron 推送或脚本</td></tr>
<tr><td>重试队列</td><td><code>/app/retries.html</code></td><td>推送失败重试</td></tr>
<tr><td>命令日志</td><td><code>/app/commands.html</code></td><td>WebSSH 命令 + 推送日志</td></tr>
<tr><td>告警中心</td><td><code>/app/alerts.html</code></td><td>告警历史与统计</td></tr>
<tr><td>审计</td><td><code>/app/audit.html</code></td><td>操作审计、多维过滤</td></tr>
<tr><td>设置</td><td><code>/app/settings.html</code></td><td>阈值、Telegram、白名单、TOTP</td></tr>
<tr><td>WebSSH</td><td><code>/app/terminal.html</code></td><td>xterm 终端</td></tr>
</table>
<p class="card-desc" style="margin-top:12px">共享:<code>layout.js</code>(侧栏/移动适配)、<code>api.js</code>JWT、toast、全局搜索)</p>
</div>
</div>
<!-- NETWORK -->
<div class="section" id="network">
<div class="section-title"><span class="icon">🔌</span> 网络与通道模型</div>
<div class="card">
<table class="feat-table">
<tr><th>方向</th><th>端口</th><th>用途</th><th>必须</th></tr>
<tr><td>子机 → 中心</td><td><strong>443 出站</strong></td><td>Agent 心跳、长任务 curl 回调</td><td></td></tr>
<tr><td>中心 → 子机</td><td><strong>22 入站</strong></td><td>推送、文件、脚本、健康检查、Agent 安装</td><td></td></tr>
<tr><td>子机本机</td><td><code>127.0.0.1:8601</code></td><td>Agent HTTP(可不暴露公网)</td><td>可选</td></tr>
</table>
<div class="info-block note" style="margin-top:12px"><div class="info-title n">说明</div><p>脚本执行与手动健康检查已改走 <strong>SSH</strong>,不依赖公网 8601。宝塔环境请用系统 Python 3.12 + <code>install.sh</code> venv,勿用面板 Python 项目管理器托管 Agent。</p></div>
</div>
</div>
<!-- 1. AUTH -->
<div class="section" id="auth">
<div class="section-title"><span class="icon">🔒</span> 1. 认证与访问控制</div>
<div class="card">
<div class="card-title"><span class="num">1</span> JWT 登录 + 自动刷新</div>
<p class="card-desc">管理员通过用户名+密码登录,获取 Access Token30 分钟)和 Refresh Token(7 天)。前端到期前 2 分钟自动换新 Token,无感续期。</p>
<div class="info-grid">
<div class="info-block usage"><div class="info-title u">使用说明</div><ul>
<li>访问 <code>/app/login.html</code>,输入管理员账号密码</li>
<li>Token 存于 <code>localStorage</code>,关闭浏览器后 7 天内免登录</li>
<li>8 小时无操作自动退出</li>
</ul></div>
<div class="info-block warn"><div class="info-title w">缺点 / 注意</div><ul>
<li>JWT 存于 localStorageXSS 漏洞可能导致 Token 泄露(已做 CSP 缓解)</li>
<li>无 RBAC:只有一个管理员角色,鉴权即信任</li>
<li>不支持多管理员角色区分</li>
</ul></div>
</div>
</div>
<div class="card">
<div class="card-title"><span class="num">2</span> TOTP 双因素认证</div>
<p class="card-desc">可选启用 Google Authenticator 兼容的 TOTP。登录时先验密码,再验 6 位动态码(30 秒窗口)。</p>
<div class="info-grid">
<div class="info-block usage"><div class="info-title u">使用说明</div><ul>
<li>进入「设置」→「安全设置」→ 启用 / <strong>重新绑定</strong> TOTP,扫描二维码</li>
<li>禁用 TOTP 需同时提供当前密码 + TOTP 码</li>
</ul></div>
<div class="info-block warn"><div class="info-title w">缺点 / 注意</div><ul>
<li>TOTP 秘钥丢失无法恢复,需提前备份</li>
<li>系统时间偏差 &gt;30s 会导致验证失败(接受 ±1 窗口共 90s)</li>
</ul></div>
</div>
</div>
<div class="card">
<div class="card-title"><span class="num">3</span> 登录防暴破</div>
<p class="card-desc">同一 IP + 用户名 15 分钟内失败 5 次,账号自动锁定并返回 HTTP 429。</p>
<div class="info-block note"><div class="info-title n">注意</div><p>锁定基于 IP + 用户名组合,更换 IP 可绕过。适合防自动化爆破,不防分布式攻击。</p></div>
</div>
<div class="card">
<div class="card-title"><span class="num">4</span> 登录 IP 白名单</div>
<p class="card-desc">可在设置中开启「仅允许白名单 IP 登录」。支持手动 IP/CIDR,以及从<strong>代理订阅 URL</strong>解析节点 IP(每 2 小时自动刷新,与手动列表合并)。</p>
<div class="info-block usage"><div class="info-title u">使用说明</div><ul>
<li>「设置」→「登录 IP 白名单」→ 总开关 + 订阅地址 + 手动 IP</li>
<li>非白名单 IP 登录被拒绝并记审计</li>
</ul></div>
</div>
</div>
<!-- 2. DASHBOARD -->
<div class="section" id="dashboard">
<div class="section-title"><span class="icon">📊</span> 2. 仪表盘与实时告警</div>
<div class="card">
<div class="card-title"><span class="num">1</span> 实时统计面板</div>
<p class="card-desc">首页展示服务器总数、在线数、离线数、告警中台数、最近推送记录。数据来源 Redis(实时)+ MySQL(历史),通过 WebSocket 触发刷新。</p>
<div class="info-grid">
<div class="info-block usage"><div class="info-title u">使用说明</div><ul>
<li>在线状态取自 Redis <code>heartbeat:{id}</code>,子机 60s 上报一次</li>
<li>告警计数 = Redis <code>alerts:*</code> key 数量</li>
<li>WS 断线会自动重连(3 次指数退避)</li>
</ul></div>
<div class="info-block warn"><div class="info-title w">缺点 / 注意</div><ul>
<li>在线状态最多滞后 60s(子机心跳间隔)</li>
<li>Redis 不可用时统计退化为 MySQL 数据,可能不准</li>
</ul></div>
</div>
</div>
<div class="card">
<div class="card-title"><span class="num">2</span> 告警通知(Telegram</div>
<p class="card-desc">CPU / 内存 / 磁盘超过阈值(默认 80%)时,通过 Telegram Bot 推送告警;恢复后发送恢复通知。同服务器同指标 5 分钟内不重复推送。</p>
<div class="info-grid">
<div class="info-block usage"><div class="info-title u">使用说明</div><ul>
<li>「设置」配置 <code>TELEGRAM_BOT_TOKEN</code> + <code>TELEGRAM_CHAT_ID</code></li>
<li>阈值可在设置页调整(CPU/内存/磁盘各独立)</li>
<li>浏览器仪表盘<strong>只显示计数,不弹窗、不响铃</strong>(设计决策)</li>
</ul></div>
<div class="info-block warn"><div class="info-title w">缺点 / 注意</div><ul>
<li>浏览器仪表盘不弹告警列表;历史见「告警中心」页</li>
<li>Telegram Bot 被封或网络不通时告警静默失败(有日志)</li>
<li>告警防抖 key 存于进程内存,重启后防抖状态重置</li>
</ul></div>
</div>
</div>
</div>
<!-- ALERTS -->
<div class="section" id="alerts">
<div class="section-title"><span class="icon">🔔</span> 告警中心</div>
<div class="card">
<div class="card-title"><span class="num">1</span> 告警历史(alerts.html</div>
<p class="card-desc">将 CPU/内存/磁盘告警与恢复事件持久化到 <code>alert_logs</code>,支持分页、按服务器/类型/日期筛选,以及近 7 日统计图表数据。</p>
<div class="info-block usage"><div class="info-title u">使用说明</div><ul>
<li>侧栏「告警中心」查看历史列表</li>
<li>API<code>GET /api/alert-history/</code><code>GET /api/alert-history/stats</code></li>
</ul></div>
</div>
<div class="card">
<div class="card-title"><span class="num">2</span> 分项 Telegram 开关</div>
<p class="card-desc">设置页可单独关闭某类告警的 Telegram 推送(告警/恢复/系统/时钟漂移等),便于后续扩展通道。</p>
</div>
</div>
<!-- 3. SERVERS -->
<div class="section" id="servers">
<div class="section-title"><span class="icon">🖥</span> 3. 服务器管理</div>
<div class="card">
<div class="card-title"><span class="num">1</span> 服务器 CRUD</div>
<p class="card-desc">增删改查服务器配置,支持 SSH 密钥 / 密码双认证,可按平台类型(Platform)和节点(Node 树)分组。</p>
<div class="info-grid">
<div class="info-block usage"><div class="info-title u">使用说明</div><ul>
<li>新增服务器:填写名称、域名/IP、SSH 端口、认证方式</li>
<li>认证方式选「密钥」时粘贴私钥内容,自动 Fernet 加密存储</li>
<li>支持 <code>platform_id</code> / <code>node_id</code> 分组归类</li>
<li>服务器列表分页显示(默认 50 条/页)</li>
</ul></div>
<div class="info-block warn"><div class="info-title w">缺点 / 注意</div><ul>
<li>批量导入(CSV)未实现,2000 台需逐一或通过 API 批量添加</li>
<li>删除服务器同时删除关联推送日志(CASCADE)</li>
</ul></div>
</div>
</div>
<div class="card">
<div class="card-title"><span class="num">2</span> 健康检查</div>
<p class="card-desc">手动触发 <code>POST /api/servers/check</code>:经 <strong>SSH</strong> 执行 Python 采集 CPU/内存/磁盘(<code>remote_probe</code>),<strong>不需要</strong>公网 8601。</p>
<div class="info-block note"><div class="info-title n">与在线状态区别</div><p>列表「在线」主要来自 Agent <strong>443 心跳</strong>;健康检查用于即时 SSH 探活与指标快照。</p></div>
</div>
<div class="card">
<div class="card-title"><span class="num">4</span> Agent 安装与升级</div>
<p class="card-desc">服务器详情「Agent 安装」:复制 <code>install.sh</code> 命令或一键 SSH 安装(带日志);安装后轮询在线。升级通过 SSH 拉取最新 <code>agent.py</code> 并 restart。</p>
<div class="info-block warn"><div class="info-title w">注意</div><ul>
<li>安装脚本:Python 3.12 venv、监听 <code>127.0.0.1:8601</code>,无需公网放行 8601</li>
<li>时钟漂移:心跳带 <code>agent_time</code>,列表显示 warn/crit 徽章</li>
</ul></div>
</div>
<div class="card">
<div class="card-title"><span class="num">3</span> 每服务器 API Key</div>
<p class="card-desc">为每台服务器单独生成 Agent API Key(格式 <code>nxs-xxx</code>),用于该服务器子机与中心通信的专属认证。</p>
<div class="info-grid">
<div class="info-block usage"><div class="info-title u">使用说明</div><ul>
<li>服务器详情 → 「生成 Agent Key」,完整 Key 仅本次显示,立即复制到子机 <code>config.json</code></li>
<li>列表页只显示前 8 位用于识别</li>
</ul></div>
<div class="info-block warn"><div class="info-title w">注意</div><ul>
<li>Key 生成后无法再次查看(一次性展示设计)</li>
<li>丢失需重新生成并更新子机配置</li>
</ul></div>
</div>
</div>
</div>
<!-- ASSETS -->
<div class="section" id="assets">
<div class="section-title"><span class="icon">🗂</span> 资产管理</div>
<div class="card">
<div class="card-title"><span class="num">1</span> Platform 与 Node</div>
<p class="card-desc"><code>assets.html</code> 管理资产类型模板(Platform)与树形节点(Node),服务器表单可关联分组。</p>
<p class="card-desc">API<code>/api/assets/platforms</code><code>/api/assets/nodes</code>(含 tree</p>
</div>
<div class="card">
<div class="card-title"><span class="num">2</span> SSH 会话与命令(只读查询)</div>
<p class="card-desc">同页或「命令日志」可查询 <code>ssh_sessions</code><code>command_logs</code>WebSSH 产生)。</p>
</div>
</div>
<!-- 4. WEB SSH -->
<div class="section" id="webssh">
<div class="section-title"><span class="icon">💻</span> 4. Web SSH 终端</div>
<div class="card">
<div class="card-title"><span class="num">1</span> 浏览器终端</div>
<p class="card-desc">基于 xterm.js + asyncssh,在浏览器内打开远程服务器 SSH 会话,支持全屏、自动 resize、彩色输出。</p>
<div class="info-grid">
<div class="info-block usage"><div class="info-title u">使用说明</div><ul>
<li>服务器列表点击「终端」→ 弹出 <code>/app/terminal.html?server_id=X</code></li>
<li>认证:先获取短效 webssh-token(15 分钟),绑定到该服务器,再建 WebSocket</li>
<li>支持窗口 resize 自动通知远程 shell</li>
</ul></div>
<div class="info-block warn"><div class="info-title w">缺点 / 注意</div><ul>
<li>Token 通过 URL 参数传递(?token=...),HTTPS 下安全;HTTP 下有风险</li>
<li>不支持文件上传 / 拖拽(需用文件管理页)</li>
<li>会话不录像(已明确否决)</li>
<li>网络中断 WebSocket 不会自动重连,需刷新页面</li>
</ul></div>
</div>
</div>
</div>
<!-- COMMANDS -->
<div class="section" id="commands">
<div class="section-title"><span class="icon">💻</span> 命令与推送日志</div>
<div class="card">
<div class="card-title"><span class="num">1</span> WebSSH 会话与命令</div>
<p class="card-desc"><code>commands.html</code>:会话列表、命令列表(危险命令高亮)、按服务器过滤。数据来自 WebSSH,直接 SSH 不记录。</p>
</div>
<div class="card">
<div class="card-title"><span class="num">2</span> 推送日志</div>
<p class="card-desc">同页「推送日志」Tab:rsync 批量推送历史,支持服务器/状态/同步模式/触发方式过滤与分页。</p>
</div>
</div>
<!-- 5. PUSH -->
<div class="section" id="push">
<div class="section-title"><span class="icon">📤</span> 5. 批量文件推送</div>
<div class="card">
<div class="card-title"><span class="num">1</span> rsync 批量推送</div>
<p class="card-desc">将源目录通过 rsync-over-SSH 推送到选定的服务器列表,支持 4 种同步模式,内置分批(50 台/批)并发(10 路)控制。</p>
<table class="feat-table">
<tr><th>同步模式</th><th>rsync 参数</th><th>适用场景</th></tr>
<tr><td>增量同步</td><td><code>-az</code></td><td>日常更新,只同步有变动的文件</td></tr>
<tr><td>全量同步</td><td><code>-az --delete</code></td><td>强制目标与源完全一致,会删除目标多余文件</td></tr>
<tr><td>校验和</td><td><code>-az --checksum</code></td><td>按文件内容而非时间戳判断差异</td></tr>
<tr><td>直接覆盖</td><td><code>-az --inplace</code></td><td>不用临时文件,直接覆盖(大文件节省空间)</td></tr>
</table>
<div class="info-grid" style="margin-top:12px">
<div class="info-block warn"><div class="info-title w">缺点 / 注意</div><ul>
<li><strong>全量同步(--delete)会不可逆删除</strong>目标目录多余文件,务必先「预览」</li>
<li>rsync 在中心机运行,中心机需能 SSH 到所有子机</li>
<li>失败的服务器自动加入重试队列(指数退避,最多 100 次)</li>
</ul></div>
</div>
</div>
<div class="card">
<div class="card-title"><span class="num">2</span> 推送前预览(dry-run</div>
<p class="card-desc">选中全量/覆盖模式时,页面显示橙色警告,引导先对首台服务器运行 <code>rsync --dry-run --stats</code>,查看待传输/删除文件数量后再决定是否推送。</p>
<div class="info-grid">
<div class="info-block usage"><div class="info-title u">使用说明</div><ul>
<li>选择「全量同步」→ 出现橙色预览按钮 → 点击 → 展示首台服务器变动摘要</li>
<li>可展开「详细文件列表」(最多 200 行)</li>
<li>预览不阻塞推送,看完数据自行决定是否点「开始推送」</li>
</ul></div>
<div class="info-block warn"><div class="info-title w">注意</div><ul>
<li>仅预览第一台选中的服务器,其余服务器可能略有差异</li>
<li>预览需 SSH 连通,与真实推送消耗相同时间(通常 &lt;5s</li>
</ul></div>
</div>
</div>
<div class="card">
<div class="card-title"><span class="num">3</span> 失败自动重试队列</div>
<p class="card-desc">推送失败的服务器自动进入 <code>push_retry_jobs</code> 队列,后台每 5 分钟按指数退避重试(60s → 120s → 240s...,最大 64 分钟)。</p>
<div class="info-block note"><div class="info-title n">注意</div><p>最多重试 100 次。可在「重试」页手动强制立即重试或删除任务。</p></div>
</div>
</div>
<!-- 6. SCRIPTS -->
<div class="section" id="scripts">
<div class="section-title"><span class="icon">📜</span> 脚本与执行历史</div>
<div class="card">
<div class="card-title"><span class="num">1</span> 脚本库管理</div>
<p class="card-desc">维护可复用 Shell 脚本库(按 ops/deploy/check/cleanup 分类),一键下发到选定的服务器。</p>
<div class="info-block usage"><div class="info-title u">使用说明</div><ul>
<li>「脚本」页 → 新建脚本 → 填写名称/类别/内容</li>
<li>点击脚本名 → 编辑;点击「执行」→ 选择服务器 → 运行</li>
<li>支持直接输入临时命令(无需保存到库)</li>
</ul></div>
</div>
<div class="card">
<div class="card-title"><span class="num">2</span> 批量执行(短任务 / 长任务)</div>
<p class="card-desc">短任务:同步等待结果(默认 30s 超时)。长任务:nohup 后台运行,完成后 curl 回调中心记录结果。</p>
<div class="info-grid">
<div class="info-block usage"><div class="info-title u">使用说明</div><ul>
<li>选择服务器 → 填写命令 → 「执行」</li>
<li>执行走 <strong>SSH 22</strong>(结果含 <code>channel: ssh</code>),无需子机公网 8601</li>
<li>长任务:nohup + 子机 curl 回调中心(需 HTTPS <code>NEXUS_API_BASE_URL</code></li>
<li>「执行历史」页:停止 / 重试 / 标记卡住 / 拉取 nexus-job 日志</li>
</ul></div>
<div class="info-block warn"><div class="info-title w">缺点 / 注意</div><ul>
<li><code>rm -rf /</code> 等危险命令被中心<strong>拦截(400</strong>,但仅匹配预设规则</li>
<li>长任务依赖子机能访问中心 <code>NEXUS_API_BASE_URL</code>(必须配置且使用 HTTPS</li>
<li>执行历史默认保留 2 小时 Redis 热数据,60s 刷入 MySQL</li>
<li>执行命令时密码等敏感 $DB_* 变量存储时已脱敏(替换为 ***)</li>
</ul></div>
</div>
</div>
<div class="card">
<div class="card-title"><span class="num">3</span> $DB_* 数据库凭据替换</div>
<p class="card-desc">脚本中使用 <code>$DB_USER</code> / <code>$DB_PASS</code> / <code>$DB_HOST</code> / <code>$DB_PORT</code> / <code>$DB_NAME</code> 占位符,执行时自动替换为选定的数据库凭据,密码不出现在脚本文本或日志中。</p>
<div class="info-block note"><div class="info-title n">注意</div><p>替换在中心机完成后发往子机,子机接收到的已是明文命令;传输通过 HTTPS,子机侧无加密保护。</p></div>
</div>
</div>
<!-- 7. FILES -->
<div class="section" id="files">
<div class="section-title"><span class="icon">📁</span> 7. 远程文件管理</div>
<div class="card">
<div class="card-title"><span class="num">1</span> 目录浏览</div>
<p class="card-desc">通过 SSH exec<code>ls -la</code>)浏览指定服务器的远程目录,支持面包屑导航和目录点击进入。</p>
<div class="info-grid">
<div class="info-block usage"><div class="info-title u">使用说明</div><ul>
<li>「文件管理」→ 选服务器 → 输入路径 → 「浏览」(或按 Enter)</li>
<li>点击目录名进入子目录,点击「..」返回上级</li>
<li>文件按 目录优先 / 字母顺序 排列</li>
</ul></div>
<div class="info-block warn"><div class="info-title w">缺点 / 注意</div><ul>
<li>无左侧树状面板(每次需手动导航)</li>
<li>显示的文件大小为 <code>ls</code> 输出的字节数,大文件不友好</li>
</ul></div>
</div>
</div>
<div class="card">
<div class="card-title"><span class="num">2</span> 文件操作(删除/重命名/新建目录)</div>
<p class="card-desc">鼠标悬停文件/目录行显示操作按钮,通过 SSH exec 在远程执行操作。</p>
<table class="feat-table">
<tr><th>操作</th><th>命令</th><th>触发方式</th></tr>
<tr><td>删除文件</td><td><code>rm -f path</code></td><td>悬停 → 🗑 → confirm 弹窗</td></tr>
<tr><td>删除目录</td><td><code>rm -rf path</code></td><td>悬停 → 🗑 → 额外警告弹窗</td></tr>
<tr><td>重命名</td><td><code>mv src dst</code></td><td>悬停 → ✏ → prompt 输入新名</td></tr>
<tr><td>新建目录</td><td><code>mkdir -p path</code></td><td>右上角 📁+ → prompt 输入名</td></tr>
</table>
<div class="info-block warn" style="margin-top:10px"><div class="info-title w">缺点 / 注意</div><ul>
<li>删除目录(rm -rf<strong>不可恢复</strong>,浏览器弹窗确认无法替代真正的安全保障</li>
<li>无文件编辑器(已明确否决,用 WebSSH vim/nano 代替)</li>
<li>无文件上传功能(可用 SFTP API 或 WebSSH scp</li>
<li>无文件内容预览</li>
</ul></div>
</div>
</div>
<!-- 8. SCHEDULES -->
<div class="section" id="schedules">
<div class="section-title"><span class="icon"></span> 8. 定时调度</div>
<div class="card">
<div class="card-title"><span class="num">1</span> Cron 调度(推送 + 脚本)</div>
<p class="card-desc">支持两种 <code>schedule_type</code><strong>文件推送</strong><strong>脚本执行</strong>。5 字段 Cron,后台每 60s 检查(仅 Primary Worker)。</p>
<div class="info-grid">
<div class="info-block usage"><div class="info-title u">使用说明</div><ul>
<li>「定时」→ 选类型 → 填 Cron、目标服务器</li>
<li>脚本型:选脚本库 ID 或内联内容,可设超时与长任务</li>
<li>示例:<code>0 2 * * *</code> = 每天凌晨 2 点</li>
</ul></div>
<div class="info-block warn"><div class="info-title w">注意</div><ul>
<li>Cron 精度受 60s 检查间隔限制</li>
<li>脚本调度同样走 SSH 执行通道</li>
</ul></div>
</div>
</div>
</div>
<!-- RETRIES -->
<div class="section" id="retries">
<div class="section-title"><span class="icon">🔄</span> 重试队列</div>
<div class="card">
<p class="card-desc"><code>retries.html</code> 管理推送失败任务:<code>retry_runner</code> 每 5 分钟指数退避;可手动立即重试或删除。</p>
</div>
</div>
<!-- SEARCH -->
<div class="section" id="search">
<div class="section-title"><span class="icon">🔍</span> 全局搜索</div>
<div class="card">
<p class="card-desc">侧栏搜索框调用 <code>GET /api/search/</code>,跨服务器、脚本、凭据、定时任务关键字匹配。</p>
</div>
</div>
<!-- 9. CREDENTIALS -->
<div class="section" id="credentials">
<div class="section-title"><span class="icon">🔑</span> 9. 凭据管理</div>
<div class="card">
<div class="card-title"><span class="num">1</span> 数据库凭据</div>
<p class="card-desc">存储 MySQL/PostgreSQL 连接信息,密码 Fernet 加密,API 响应不含密码(仅元数据)。供脚本执行时通过 $DB_* 变量引用。</p>
<div class="info-block warn"><div class="info-title w">注意</div><p>凭据只加密存储,子机接收到的命令已含明文连接信息(仅在中心→子机的 HTTPS 传输中受保护)。</p></div>
</div>
<div class="card">
<div class="card-title"><span class="num">2</span> 密码预设</div>
<p class="card-desc">存储 SSH 密码模板(Fernet 加密),可在推送配置中引用,避免重复输入常用密码。</p>
<div class="info-block note"><div class="info-title n">注意</div><p>密码预设不能用于脚本 $DB_PASS 替换,只用于推送密码模板。</p></div>
</div>
</div>
<!-- 10. SETTINGS -->
<div class="section" id="settings">
<div class="section-title"><span class="icon">⚙️</span> 10. 系统设置</div>
<div class="card">
<div class="card-title"><span class="num">1</span> 可配置项</div>
<table class="feat-table">
<tr><th>设置项</th><th>说明</th><th>是否可改</th></tr>
<tr><td>系统名称 / 标题</td><td>品牌展示名称</td><td>✅ 可改</td></tr>
<tr><td>告警阈值(CPU/内存/磁盘)</td><td>超过阈值触发 Telegram 告警,默认 80%</td><td>✅ 可改</td></tr>
<tr><td>Redis URL</td><td>Redis 连接地址</td><td>✅ 可改</td></tr>
<tr><td>数据库连接池大小</td><td>pool_size / max_overflow</td><td>✅ 可改</td></tr>
<tr><td>Telegram Bot Token / Chat ID</td><td>告警推送配置</td><td>✅ 可改</td></tr>
<tr><td>SECRET_KEY / API_KEY / ENCRYPTION_KEY</td><td>核心安全密钥</td><td>❌ 不可改(需改 .env 重启)</td></tr>
<tr><td>DATABASE_URL</td><td>数据库连接</td><td>❌ 不可改</td></tr>
</table>
</div>
<div class="card">
<div class="card-title"><span class="num">2</span> Telegram</div>
<p class="card-desc">配置 Bot Token / Chat ID<strong>测试发送</strong>;通过 getUpdates <strong>检测 Chat ID</strong>;各告警类型独立开关。</p>
</div>
<div class="card">
<div class="card-title"><span class="num">3</span> TOTP 与 API Key</div>
<p class="card-desc">TOTP 启用/禁用/<strong>重新绑定</strong>;全局 API Key 需密码二次验证后查看(记审计)。</p>
</div>
<div class="card">
<div class="card-title"><span class="num">4</span> 登录 IP 白名单</div>
<p class="card-desc">总开关、订阅 URL(2h 刷新)、手动 IP、解析预览(见「认证」章节)。</p>
</div>
</div>
<!-- 11. AUDIT -->
<div class="section" id="audit">
<div class="section-title"><span class="icon">📋</span> 11. 审计日志</div>
<div class="card">
<div class="card-title"><span class="num">1</span> 操作审计</div>
<p class="card-desc">所有 CUD 操作(创建/更新/删除服务器、推送、执行脚本、登录、改密码等)自动写入 <code>audit_logs</code>,记录操作人、IP、目标、详情。</p>
<div class="info-grid">
<div class="info-block usage"><div class="info-title u">使用说明</div><ul>
<li>「审计」页:日期起止、管理员用户名、操作类型(分组下拉)</li>
<li>每页 50 条分页</li>
</ul></div>
<div class="info-block warn"><div class="info-title w">缺点 / 注意</div><ul>
<li>无日志导出(已否决 CSV/JSON</li>
<li>审计日志不会自动清理,长期需归档</li>
</ul></div>
</div>
</div>
</div>
<!-- 12. INSTALL -->
<div class="section" id="install">
<div class="section-title"><span class="icon">🚀</span> 12. 安装向导</div>
<div class="card">
<div class="card-title"><span class="num">1</span> 5 步安装向导</div>
<p class="card-desc"><code>.env</code> 文件时系统进入安装模式,访问 <code>/app/install.html</code> 完成 5 步配置:欢迎 → 数据库 → Redis → 管理员账号 → 完成。自动生成 API Key / Secret Key / Encryption Key。</p>
<div class="info-grid">
<div class="info-block usage"><div class="info-title u">使用说明</div><ul>
<li>安装完成后 <code>.env</code> 自动生成,安装 API 除 GET /status 外全部锁定(返回 403</li>
<li>重装需手动删除 <code>.env</code> 并重启</li>
</ul></div>
<div class="info-block warn"><div class="info-title w">注意</div><ul>
<li>安装过程未验证数据库/Redis 连接成功才允许进入下一步(应在步骤中手动验证)</li>
<li>安装模式下除 install API 和静态文件外,其余 API 返回 503</li>
</ul></div>
</div>
</div>
</div>
<!-- 13. GUARDIAN -->
<div class="section" id="guardian">
<div class="section-title"><span class="icon">🛡</span> 13. 三层守护机制</div>
<div class="card">
<div class="card-title"><span class="num">1</span> 三层守护</div>
<table class="feat-table">
<tr><th>层级</th><th>机制</th><th>触发条件</th><th>动作</th></tr>
<tr><td>Layer 1</td><td>Supervisor</td><td>Python 进程崩溃</td><td>自动重启</td></tr>
<tr><td>Layer 2</td><td>Python self_monitor30s</td><td>Redis/MySQL 不可达</td><td>发 Telegram 告警/恢复</td></tr>
<tr><td>Layer 3</td><td>Shell cron1 分钟)</td><td>连续 3 次 /health 失败</td><td>supervisorctl restart + Telegram</td></tr>
</table>
<div class="info-block warn" style="margin-top:10px"><div class="info-title w">注意</div><p>Shell cron 依赖宝塔/系统 crontab 配置,部署时需手动添加定时任务。</p></div>
</div>
</div>
<!-- 14. AGENT -->
<div class="section" id="agent">
<div class="section-title"><span class="icon">🤖</span> 14. 子机 Agent</div>
<div class="card">
<div class="card-title"><span class="num">1</span> Agent 功能</div>
<p class="card-desc">部署在每台被管服务器上的轻量服务:<strong>心跳走 443 出站</strong>HTTP 仅本机 <code>127.0.0.1:8601</code>。中心侧脚本已改 <strong>SSH</strong>/exec 端点保留供兼容。</p>
<table class="feat-table">
<tr><th>能力</th><th>说明</th></tr>
<tr><td>心跳</td><td>POST 中心 <code>/api/agent/heartbeat</code>,写 Redis,智能加急</td></tr>
<tr><td>时钟漂移</td><td><code>agent_time</code> 与中心比对,warn/crit + Telegram</td></tr>
<tr><td>IP 白名单</td><td><code>allowed_ips</code> 限制访问本机 Agent HTTP</td></tr>
<tr><td>长任务回调</td><td>POST <code>/api/agent/script-callback</code></td></tr>
<tr><td>安装</td><td><code>web/agent/install.sh</code> — python3.12 venv + systemd</td></tr>
</table>
<div class="info-block warn" style="margin-top:12px"><div class="info-title w">注意</div><ul>
<li>升级 API 存在,建议与 venv 安装脚本对齐后使用</li>
<li>长任务回调需子机出站访问中心 HTTPS</li>
</ul></div>
</div>
</div>
<!-- BACKGROUND -->
<div class="section" id="background">
<div class="section-title"><span class="icon"></span> 后台任务(Primary Worker</div>
<div class="card">
<table class="feat-table">
<tr><th>任务</th><th>间隔</th><th>说明</th></tr>
<tr><td>heartbeat_flush</td><td>10min</td><td>Redis 心跳 → MySQL</td></tr>
<tr><td>self_monitor</td><td>30s</td><td>Redis/MySQL/WS 自检 + Telegram</td></tr>
<tr><td>schedule_runner</td><td>60s</td><td>Cron 调度</td></tr>
<tr><td>retry_runner</td><td>5min</td><td>推送重试</td></tr>
<tr><td>ip_allowlist_refresh</td><td>2h</td><td>订阅 IP 刷新</td></tr>
<tr><td>script_execution_flush</td><td>60s</td><td>执行记录 Redis→MySQL</td></tr>
</table>
</div>
</div>
<!-- ACCEPTANCE -->
<div class="section" id="acceptance">
<div class="section-title"><span class="icon"></span> 标准与验收(与团队同等严谨)</div>
<div class="card">
<p class="card-desc"><strong>全量标准转接</strong><code>docs/project/standards-transfer-package.md</code> — 含 Cursor 规则、<code>standards/</code> 三件套、逐行八步 DoD、设计/上线清单。下一任 AI <strong>不得降低</strong>执行力度。</p>
<table class="feat-table">
<tr><th>类别</th><th>权威路径</th></tr>
<tr><td>标准转接包</td><td><code>docs/project/standards-transfer-package.md</code></td></tr>
<tr><td>验收 L0L5</td><td><code>development-acceptance-standard.md</code></td></tr>
<tr><td>系统开发</td><td><code>standards/system-development-standard.md</code></td></tr>
<tr><td>逐行审计 v2</td><td><code>standards/line-walk-audit-standard-v2.md</code></td></tr>
<tr><td>审计原则</td><td><code>standards/audit-core-principles.md</code></td></tr>
<tr><td>Cursor</td><td><code>.cursor/rules/*.mdc</code>(含 audit-line-review</td></tr>
</table>
<p class="card-desc" style="margin-top:12px">逐行审查:8 步/文件、Closure 全表含 SAFE、FINDING 必须 <code>文件:行号</code>、单回复 ≤5 文件。AI 接续:HANDOFF + 全站功能 + 标准转接包。</p>
</div>
</div>
<!-- 15. NOT IMPL -->
<div class="section" id="notimpl">
<div class="section-title"><span class="icon">🚫</span> 15. 未实现 / 已否决功能</div>
<div class="card">
<table class="feat-table">
<tr><th>功能</th><th>状态</th><th>原因 / 替代方案</th></tr>
<tr><td>文件编辑器(Monaco/ACE</td><td><span class="badge badge-red">已否决</span></td><td>运维场景用 WebSSH + vim/nano;嵌入编辑器增加复杂度无实质收益</td></tr>
<tr><td>会话录像(asciicast</td><td><span class="badge badge-red">已否决</span></td><td>命令日志已满足审计需求</td></tr>
<tr><td>RBAC 权限体系</td><td><span class="badge badge-red">已否决</span></td><td>鉴权即信任,单管理员模型;增加 RBAC 代价远超收益</td></tr>
<tr><td>移动端适配</td><td><span class="badge badge-red">已否决</span></td><td>2000 台服务器运维不会用手机操作</td></tr>
<tr><td>批量服务器导入(CSV</td><td><span class="badge badge-amber">未实现</span></td><td>可通过 API 批量 POST /api/servers/ 实现</td></tr>
<tr><td>文件上传到远程服务器</td><td><span class="badge badge-amber">未实现</span></td><td>可用 WebSSH scp / 推送功能代替</td></tr>
<tr><td>文件内容预览</td><td><span class="badge badge-amber">未实现</span></td><td>可通过 WebSSH cat 查看</td></tr>
<tr><td>WebSSH 断线自动重连</td><td><span class="badge badge-amber">未实现</span></td><td>当前需刷新页面;计划自动重试+按钮</td></tr>
<tr><td>仪表盘历史趋势图</td><td><span class="badge badge-amber">未实现</span></td><td>仅实时 Redis 指标</td></tr>
<tr><td>宝塔 API 监控集成</td><td><span class="badge badge-amber">未实现</span></td><td>可选;参考 btpanel-skills</td></tr>
<tr><td>Agent mTLS</td><td><span class="badge badge-amber">讨论中</span></td><td>未实现</td></tr>
<tr><td>首次部署 E2ET1T5、T-SE</td><td><span class="badge badge-amber">待验证</span></td><td>见 production-verification-checklist.md</td></tr>
</table>
</div>
</div>
<!-- FOOTER -->
<footer>
<p>Nexus 6.0 全站功能说明书 · 2026-05-23(对齐代码库)</p>
<p style="margin-top:6px">
<a href="project/nexus-full-site-features.md">全站功能</a> ·
<a href="project/standards-transfer-package.md">标准转接</a> ·
<a href="project/development-acceptance-standard.md">验收标准</a> ·
<a href="project/AI-HANDOFF-2026-05-23.md">AI 接续</a>
</p>
<p style="margin-top:4px">Python 3.12 / FastAPI 0.115 / MySQL 8.4 / Redis 7 / asyncssh 2.17 / Tailwind v4</p>
</footer>
</div>
</body>
</html>
@@ -1,35 +0,0 @@
# 审计 — 文件管理「新建文件」修复
**Changelog**: `docs/changelog/2026-06-10-files-new-file-fix.md`
## 变更文件
| 文件 | 说明 |
|------|------|
| `frontend/src/composables/files/useFilesActions.ts` | `openNewFileDialog`、等待编辑器就绪 |
| `frontend/src/composables/files/useFilesEditor.ts` | 导出 `waitForEditorWorkbench` |
| `frontend/src/composables/files/useFilesPage.ts` | 透传 helper |
| `frontend/src/components/files/FilesToolbar.vue` | 未选服务器时提示 |
| `server/infrastructure/ssh/asyncssh_pool.py` | SFTP 写前确保父目录 |
## Step 3(规则扫描)
| 规则 | 结论 | 说明 |
|------|------|------|
| 鉴权 | PASS | 仍走 `/sync/write-file` + admin JWT |
| 路径 | PASS | `validatePathSegment` 不变 |
| 静默失败 | PASS | 编辑器未就绪有 warning snackbar |
| SSH | PASS | `mkdir -p` 仅父目录,不扩大写范围 |
## Closure
| 文件 | 结论 |
|------|------|
| `useFilesActions.ts` | SAFE — 无 v-html;错误经 snackbar |
| `asyncssh_pool.py` | SAFE — 复用已有 `_ensure_remote_parent_dir` |
## DoD
- [x] changelog + 本审计
- [x] frontend type-check
- [ ] 生产:文件管理新建文件 → 编辑器打开
@@ -1,20 +0,0 @@
# 审计 — 审计日志目标列显示服务器名称
**日期**2026-06-12
**Changelog**: `docs/changelog/2026-06-12-audit-log-server-target-name.md`
## 范围
| 文件 |
|------|
| `server/api/settings.py` |
| `frontend/src/pages/AuditPage.vue` |
| `frontend/src/utils/auditLabels.ts` |
| `frontend/src/utils/auditNormalize.ts` |
| `frontend/src/types/api.ts` |
| `tests/integration/test_alerts_audit.py` |
## Step 3 / Closure / DoD
- PASS — 只读增强,批量 IN 查 name
- DoD: integration test passed
@@ -1,142 +0,0 @@
# 审计 — 远程浏览器(Playwright Worker
**Changelog**: `docs/changelog/2026-06-12-remote-browser-worker-deploy.md`
**设计 SSOT**: `docs/design/specs/2026-06-11-server-browser-chromium-design.md`
**范围**: 2026-06-12 新增/恢复的浏览器 Worker 全链路(未含 `web/app` 构建产物)
## 范围(文件清单)
| 文件 | 职责 |
|------|------|
| `browser-worker/Dockerfile` | Worker 镜像 |
| `browser-worker/docker-compose.yml` | Worker 编排 |
| `browser-worker/docker-entrypoint.sh` | headed + Xvfb 入口 |
| `browser-worker/requirements.txt` | Worker 依赖 |
| `browser-worker/config.py` | Worker 环境配置 |
| `browser-worker/main.py` | Worker HTTP/WS API |
| `browser-worker/session_manager.py` | Playwright + screencast |
| `browser-worker/browser_stealth.py` | 反自动化 / 验证码辅助 |
| `browser-worker/browser_url_safe.py` | SSRFWorker 侧) |
| `server/main.py` | 注册 browser 路由 |
| `server/config.py` | `BROWSER_*` 与 TLS verify |
| `server/api/settings.py` | 设置项默认值(push_complete_sound |
| `server/api/browser_session.py` | 桥接 REST + `/ws/browser` |
| `server/api/browser.py` | UI 状态 `/api/browser/state` |
| `server/infrastructure/browser/__init__.py` | 包入口 |
| `server/infrastructure/browser/worker_client.py` | 连 Worker(可配置 TLS verify |
| `server/infrastructure/browser/session_registry.py` | 内存会话归属 |
| `server/utils/browser_url_safe.py` | SSRF(桥接层) |
| `server/utils/browser_ui_state.py` | 状态解析 |
| `frontend/src/App.vue` | 顶栏 + 固定 GlobalBrowserPanel |
| `frontend/src/router/index.ts` | `/browser` 路由 |
| `frontend/src/pages/ServersPage.vue` | 服务器页打开浏览器 |
| `frontend/src/pages/BrowserPage.vue` | 独立浏览器页(兼容) |
| `frontend/src/components/GlobalBrowserPanel.vue` | 固定顶栏下整窗 |
| `frontend/src/composables/useGlobalBrowser.ts` | 全局标签/状态 |
| `frontend/src/composables/useRemoteBrowserSession.ts` | canvas + WS |
| `tests/test_browser_url_safe.py` | SSRF 单测 |
| `tests/test_browser_ui_state.py` | UI 状态单测 |
| `tests/test_settings_sound_defaults.py` | 设置默认音单测 |
## API / WS 入口表
| 方法 | 路径 | 鉴权 |
|------|------|------|
| GET | `/api/browser/status` | JWT `get_current_admin` |
| POST | `/api/browser/sessions` | JWT |
| DELETE | `/api/browser/sessions/{id}` | JWT + 会话归属 |
| GET/PUT | `/api/browser/state` | JWT + `admin_ui_preferences` |
| WS | `/ws/browser/{session_id}?token=` | JWT + 会话归属 |
| GET | Worker `/health` | **无** |
| POST/DELETE | Worker `/v1/sessions*` | `X-Nexus-Worker-Key` |
| WS | Worker `/v1/sessions/{id}/stream` | Worker KeyHeader |
## Step 3 规则扫描
| ID | 规则 | 结论 |
|----|------|------|
| H1 | SSRF / 私网访问 | **RISK** — 见 F1、F2 |
| H2 | 鉴权 / IDOR | PASS — WS 校验 admin_id |
| H3 | 密钥不落库/不入 git | PASS — `.env` / 持久卷 |
| H4 | 无静默吞错 | **RISK** — 见 F4 |
| H5 | Worker 暴露面 | **RISK** — 见 F3 |
| H6 | 多 worker 一致性 | **RISK** — 见 F5 |
| H7 | UI 状态 URL 白名单 | PASS — `browser_ui_state` |
| H8 | 设计符合度(redirect | **GAP** — 见 F1 |
## Closure(全表)
| ID | 判定 | 严重度 | 位置 | 说明 |
|----|------|--------|------|------|
| H1 | FINDING | **P1** | `browser-worker/session_manager.py` NAVIGATE 后 | **F1** 页内点击/重定向未逐跳 SSRF 校验;设计文档要求 redirect 后重验 |
| H1 | FINDING | **P1** | Worker 部署 ufw | **F3** 装机时额外 `ufw allow 8443/tcp`(全网),削弱「仅主站 IP」隔离 |
| H2 | SAFE | — | `browser_session.py:125-127` | `session_id` 必须属于当前 `admin.id` |
| H2 | SAFE | — | `browser_session.py:95-104` | 桥接层 NAVIGATE 二次校验 |
| H3 | SAFE | — | `worker_client.py`, Worker `.env` | `WORKER_SECRET` 仅环境变量 |
| H4 | FINDING | P2 | `worker_client.py:67-68` | **F4** `delete_worker_session` 网络失败仅 warning,可能残留 Worker 会话 |
| H5 | FINDING | P2 | `browser-worker/main.py:45-51` | `/health` 无认证;若 8443 对公网可见会泄露会话数 |
| H6 | FINDING | P2 | `session_registry.py` | **F5** 内存注册表不跨 uvicorn worker;多进程下 WS 可能 4403 |
| H7 | SAFE | — | `browser_ui_state.py` | http(s) only,无凭据 URL |
| H8 | SAFE | — | screencast 热修 | `cdp.on("Page.screencastFrame")` 已替代错误 API |
| — | SAFE | — | `browser_session.py:61-64` | 创建前清理陈旧会话,避免 409 |
| — | SAFE | — | 前端 | 不再 iframecanvas 远程渲染 |
| — | SAFE | — | CUD 审计 | 首版未记 session 审计,与设计「与 RDP 一致」一致 |
### F1 — 重定向 / 页内导航 SSRF 缺口(P1)
`validate_navigation_url` 仅在显式 `NAVIGATE` 消息时调用。用户在远程 Chromium 内**点击链接**或 **302 跳转**到 `http://10.x` / metadata 时,Playwright 会照常请求,**不经过**校验。
**建议**`page.on("framenavigated")` / `response` 监听,每跳对 `page.url` 调用 `validate_navigation_url`;失败则 `page.close()``about:blank` 并 WS `ERROR`
### F3 — Worker 防火墙过宽(P1
部署日志显示除 `from 20.24.218.235` 外还有 `ufw allow 8443/tcp`Anywhere)。攻击者若猜到 `WORKER_SECRET`(或暴力)可直连 Worker。
**建议**:删除全网 8443 规则,仅保留主站源 IP;`/health` 改内网或加 Key。
### F4 — Worker 删除失败(P2
`delete_worker_session` 异常时静默;依赖 Worker 空闲超时清理。
### F5 — 多 uvicorn workerP2
`session_registry` 进程内字典;生产若 `--workers>1` 可能 POST 在 worker A、WS 落到 worker B。当前 Docker 单进程可接受;扩 worker 时需 Redis 注册表。
## 外部输入 → Sink
| 输入 | Sink | 缓解 |
|------|------|------|
| `NAVIGATE.url` | Playwright `goto` | 双端 `validate_navigation_url` |
| 页内链接点击 | Playwright 导航 | **未缓解(F1** |
| WS `MOUSE`/`KEY` | Chromium | 仅已认证管理员 |
| `browser/state` tabs URL | MySQL JSON | `_is_safe_url` 过滤 |
| Worker Key | Worker API | 常量时间比较(`!=` |
## DoD
| 项 | 状态 |
|----|------|
| `pytest tests/test_browser_url_safe.py tests/test_browser_ui_state.py` | 7 passed(生产机);本地沙箱 DNS 失败属环境限制 |
| `npm run type-check` | 已通过(实现日) |
| 生产 `GET /api/browser/status` | `enabled:true`, `worker_ok:true` |
| 生产 screencast 冒烟 | `SCREENCAST_OK` ~4KB JPEG |
| changelog | `docs/changelog/2026-06-12-remote-browser-worker-deploy.md` |
## 总结
| 级别 | 数量 | 处置建议 |
|------|------|----------|
| P0 | 0 | — |
| P1 | 2 | F1 redirect/页内 SSRFF3 收紧 ufw |
| P2 | 3 | F4/F5 可排期;/health 加固 |
**合并结论**:功能链路可用,鉴权与显式 NAVIGATE SSRF 达标;**未达设计文档「每跳 redirect 校验」**,且 Worker **8443 应对公网收口**。建议先修 F3(运维),再实现 F1(代码)后复审计。
## 验证命令
```bash
venv/bin/pytest tests/test_browser_url_safe.py tests/test_browser_ui_state.py -q
cd frontend && npm run type-check
# 生产
curl -H "Authorization: Bearer $TOKEN" https://api.synaglobal.vip/api/browser/status
```
@@ -1,15 +0,0 @@
# 审计 — 监测槽 hover 外框高亮
**日期**2026-06-12
**Changelog**: `docs/changelog/2026-06-12-watch-slot-hover-border.md`
## 范围
| 文件 |
|------|
| `frontend/src/components/watch/WatchSlotCard.vue` |
## Step 3 / Closure / DoD
- PASS — 纯 CSS,无逻辑/安全风险
- DoD: 浏览器悬停仅边框变色
@@ -1,35 +0,0 @@
# 审计 — 文件管理「新建文件」修复
**Changelog**: `docs/changelog/2026-06-13-files-new-file-fix.md`
## 变更文件
| 文件 | 说明 |
|------|------|
| `frontend/src/composables/files/useFilesActions.ts` | `openNewFileDialog`、等待编辑器就绪 |
| `frontend/src/composables/files/useFilesEditor.ts` | 导出 `waitForEditorWorkbench` |
| `frontend/src/composables/files/useFilesPage.ts` | 透传 helper |
| `frontend/src/components/files/FilesToolbar.vue` | 未选服务器时提示 |
| `server/infrastructure/ssh/asyncssh_pool.py` | SFTP 写前确保父目录 |
## Step 3(规则扫描)
| 规则 | 结论 | 说明 |
|------|------|------|
| 鉴权 | PASS | 仍走 `/sync/write-file` + admin JWT |
| 路径 | PASS | `validatePathSegment` 不变 |
| 静默失败 | PASS | 编辑器未就绪有 warning snackbar |
| SSH | PASS | `mkdir -p` 仅父目录 |
## Closure
| 文件 | 结论 |
|------|------|
| `useFilesActions.ts` | SAFE — snackbar 错误提示 |
| `asyncssh_pool.py` | SAFE — 复用 `_ensure_remote_parent_dir` |
## DoD
- [x] changelog + 本审计
- [x] frontend type-check
- [ ] 生产验证新建文件
@@ -1,50 +0,0 @@
# 审计 — 快捷命令回车修复 + 离线告警 Telegram 分流
**Changelog**:
- `docs/changelog/2026-06-13-terminal-quick-cmd-enter-fix.md`
- `docs/changelog/2026-06-13-offline-alert-telegram-split.md`
**设计/计划**:
- `docs/design/specs/2026-06-13-offline-alert-telegram-split-design.md`
- `docs/design/plans/2026-06-13-offline-alert-telegram-split.md`
## 审计范围
| 文件 | 变更 | 状态 |
|------|------|------|
| `frontend/src/composables/useTerminalQuickCommands.ts` | `formatQuickCmdForSend` | ☑ |
| `frontend/src/composables/terminal/useTerminalSessions.ts` | `execQuickCmd` | ☑ |
| `frontend/src/components/TerminalQuickCommandsSettings.vue` | 保存不再 append `\r` | ☑ |
| `frontend/src/pages/SettingsPage.vue` | 离线专用 Bot UI | ☑ |
| `frontend/src/types/api.ts` | 离线开关文案 | ☑ |
| `frontend/src/utils/auditLabels.ts` | reveal 审计标签 | ☑ |
| `server/infrastructure/telegram/__init__.py` | 离线路由 | ☑ |
| `server/api/settings.py` | offline API + `_telegram_fetch_chats` | ☑ |
| `server/config.py` | `TELEGRAM_OFFLINE_*` | ☑ |
| `tests/test_telegram_offline_channel.py` | 新 | ☑ |
| `tests/test_terminal_quick_commands.py` | 新 | ☑ |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | 离线 Token 敏感掩码 | SAFE — `SENSITIVE_KEYS` |
| H2 | reveal 审计 | SAFE — `reveal_offline_telegram_token` |
| H3 | 半配离线回退默认 Bot | SAFE — `resolve_offline_telegram_credentials` |
| H4 | 资源告警不走离线 Bot | SAFE — `send_telegram_alert` 未改 |
| H5 | 快捷命令 `\r``trim()` | SAFE — 发送时 `formatQuickCmdForSend` |
| H6 | Layer3 不同步离线 Bot | SAFE — 设计 intentional |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1H6 | SAFE | 代码 + pytest 10/10 + `test_ops_patrol_sync` 未破坏 |
## DoD
- [x] `pytest tests/test_telegram_offline_channel.py tests/test_terminal_quick_commands.py` passed
- [x] `bash scripts/local_verify.sh` 26/26
- [x] changelog / design / audit
- [x] 无 SECRET_KEY/API_KEY/ENCRYPTION_KEY/DATABASE_URL 变更
- [x] 前端 `vite build` 待部署脚本执行
@@ -1,28 +0,0 @@
# 审计 — SSH 监测探针引号修复
**Changelog**: `docs/changelog/2026-06-13-ssh-watch-probe-quote-fix.md`
## 审计范围
| 文件 | 变更 | 状态 |
|------|------|------|
| `server/infrastructure/ssh/remote_probe.py` | `strip(chr(34))` | ☑ |
| `tests/test_watch_metrics.py` | 探针脚本回归 | ☑ |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | 无新攻击面 | SAFE |
| H2 | 恢复监测可用性 | SAFE — 热修复 |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1H2 | SAFE | 本地 bash 执行探针返回 status ok |
## DoD
- [x] `test_ssh_watch_metrics_cmd_runs_locally` passed
- [x] changelog / audit
@@ -1,33 +0,0 @@
# 审计 — Telegram 群聊 Chat ID 检测
**Changelog**: `docs/changelog/2026-06-13-telegram-chat-id-group-detect.md`
## 审计范围
| 文件 | 变更 | 状态 |
|------|------|------|
| `server/api/settings.py` | `_extract_chats_from_telegram_updates`、群聊更新类型 | ☑ |
| `frontend/src/pages/SettingsPage.vue` | 群聊检测说明与类型标签 | ☑ |
| `tests/test_settings_telegram.py` | my_chat_member / callback 单测 | ☑ |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | Token 仍经既有 settings 鉴权 | SAFE — 未改 auth |
| H2 | getUpdates 仍先清 webhook | SAFE — `_telegram_clear_webhook_if_set` |
| H3 | 不泄露其他用户 chat | SAFE — 仅返回去重 chat 元数据 |
| H4 | 群聊隐私模式说明 | SAFE — UI 提示 `/start@Bot` |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1H4 | SAFE | `pytest tests/test_settings_telegram.py` 8/8 |
## DoD
- [x] `pytest tests/test_settings_telegram.py` passed
- [x] changelog / audit
- [x] 无密钥类 settings 变更
- [x] 前端 build 由部署脚本执行
@@ -1,69 +0,0 @@
# 审计 — 监测历史北京时间 + 下线远程浏览器
**Changelog**:
- `docs/changelog/2026-06-13-watch-history-beijing-time.md`
- `docs/changelog/2026-06-12-remove-remote-browser.md`
## 审计范围
| 文件 | 变更 | 状态 |
|------|------|------|
| `frontend/src/utils/datetime.ts` | `formatChartAxisBeijing` | ☑ |
| `frontend/src/components/watch/WatchProbeRecordsTable.vue` | 北京时间列 | ☑ |
| `frontend/src/components/watch/WatchSparkline.vue` | tooltip 北京时间 | ☑ |
| `frontend/src/components/watch/WatchTrendChart.vue` | 轴/tooltip 北京时间 | ☑ |
| `frontend/src/pages/WatchMetricsPage.vue` | 「北京时间」标签 | ☑ |
| `frontend/src/App.vue` | 移除 GlobalBrowserPanel | ☑ |
| `frontend/src/router/index.ts` | 移除 `/browser` 路由 | ☑ |
| `frontend/src/pages/ServersPage.vue` | 站点 `window.open` | ☑ |
| `frontend/src/pages/AuditPage.vue` | 移除未用 import | ☑ |
| `frontend/src/components/GlobalBrowserPanel.vue` | 删除 | ☑ |
| `frontend/src/composables/useGlobalBrowser.ts` | 删除 | ☑ |
| `frontend/src/composables/useRemoteBrowserSession.ts` | 删除 | ☑ |
| `frontend/src/pages/BrowserPage.vue` | 删除 | ☑ |
| `server/main.py` | 注销 browser 路由 | ☑ |
| `server/api/browser.py` | 删除 | ☑ |
| `server/api/browser_session.py` | 删除 | ☑ |
| `server/infrastructure/browser/` | 删除 | ☑ |
| `__init__.py` | browser 包删除 | ☑ |
| `server/utils/browser_ui_state.py` | 删除 | ☑ |
| `server/utils/browser_url_safe.py` | 删除 | ☑ |
| `browser-worker/` | 删除 | ☑ |
| `Dockerfile` | browser-worker 删除 | ☑ |
| `browser-worker/main.py` | 删除 | ☑ |
| `browser-worker/browser_url_safe.py` | 删除 | ☑ |
| `browser-worker/browser_stealth.py` | 删除 | ☑ |
| `browser-worker/config.py` | 删除 | ☑ |
| `browser-worker/docker-compose.yml` | 删除 | ☑ |
| `browser-worker/docker-entrypoint.sh` | 删除 | ☑ |
| `browser-worker/requirements.txt` | 删除 | ☑ |
| `browser-worker/session_manager.py` | 删除 | ☑ |
| `server/infrastructure/browser/__init__.py` | 删除 | ☑ |
| `server/infrastructure/browser/session_registry.py` | 删除 | ☑ |
| `server/infrastructure/browser/worker_client.py` | 删除 | ☑ |
| `tests/test_browser_ui_state.py` | 删除 | ☑ |
| `tests/test_browser_url_safe.py` | 删除 | ☑ |
| `tests/chain/test_agent_dashboard_flow.py` | Redis pipeline mock | ☑ |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | 北京时间仅前端展示,不改 API 时区 | SAFE |
| H2 | 浏览器下线后无孤立 `/api/browser` 注册 | SAFE |
| H3 | `browserUrl.ts` 保留供站点外链 | SAFE |
| H4 | 无新密钥/明文 | SAFE |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1H4 | SAFE | L2b 26/26chain 9/9vite build OK |
## DoD
- [x] `bash scripts/local_verify.sh`
- [x] `pytest tests/chain -q`
- [x] `cd frontend && npx vite build`
- [x] changelog + audit
- [ ] 生产 `/health` + `#/watch-metrics` 北京时间终验
@@ -1,39 +0,0 @@
# 审计 — 监测插槽硬件详情
**Changelog**: `docs/changelog/2026-06-13-watch-slot-hardware-specs.md`
## 审计范围
| 文件 | 变更 | 状态 |
|------|------|------|
| `server/utils/watch_metrics.py` | 硬件字段、fill_hardware_gaps | ☑ |
| `server/infrastructure/ssh/remote_probe.py` | SSH 探针采集详情 | ☑ |
| `server/background/watch_probe_runner.py` | 探针后补齐用量 | ☑ |
| `web/agent/agent.py` | `_hardware_specs_dict` 心跳 | ☑ |
| `frontend/src/components/watch/WatchSlotCard.vue` | 详情 UI | ☑ |
| `frontend/src/components/watch/WatchMetricBar.vue` | sub 副行 | ☑ |
| `frontend/src/utils/watchFormat.ts` | 格式化 | ☑ |
| `frontend/src/composables/useWatchPins.ts` | 类型 | ☑ |
| `tests/test_watch_metrics.py` | 单测 | ☑ |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | cpu_model 长度截断 | SAFE — `_short_text` 120 字符 |
| H2 | 无新公开端点 | SAFE — 复用 watch WS |
| H3 | SSH 脚本无 shell 注入 | SAFE — 固定 python -c |
| H4 | 旧 Agent 回退 | SAFE — `fill_hardware_gaps` 按 % 推算 |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1H4 | SAFE | `pytest tests/test_watch_metrics.py` 13/13 |
## DoD
- [x] pytest watch_metrics passed
- [x] changelog / audit
- [x] 前端 build 由部署脚本执行
- [x] 无密钥类配置变更
@@ -1,29 +0,0 @@
# 审计 — 监测槽暂停态居中开关
**Changelog**: `docs/changelog/2026-06-13-watch-slot-paused-center-switch.md`
## 审计范围
| 文件 | 变更 | 状态 |
|------|------|------|
| `WatchSlotCard.vue` | 暂停态居中开关、开启后才显示指标 | ☑ |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | 无新 API | SAFE — 仍 `emit('monitoring', …)` |
| H2 | 误触历史 | SAFE — 暂停态卡片主体不跳转,仅「历史」按钮 |
| H3 | 双开关冲突 | SAFE — 顶栏开关仅 `monitoringOn` 时显示;暂停仅中部开关开启 |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1H3 | SAFE | 纯前端 `WatchSlotCard.vue` 模板分支 |
## DoD
- [x] changelog
- [x] `cd frontend && npx vite build`
- [ ] 生产浏览器终验:暂停槽中部开关 → 开启后见 CPU/内存
-70
View File
@@ -1,70 +0,0 @@
# 审计 — 监测 UI 视觉迭代
**Changelog**: `docs/changelog/2026-06-13-watch-ui-polish.md`
**范围**:监测历史页 / 趋势图 / 探针表 / 实时槽 — 纯前端视觉,无 API 或后端变更
## 审计范围
| 文件 | 变更 | 状态 |
|------|------|------|
| `frontend/src/pages/WatchMetricsPage.vue` | 卡片页头、共用时间窗、骨架屏/空态 | ☑ |
| `frontend/src/components/watch/WatchTrendChart.vue` | 主题色 ECharts、暗色 tooltip/轴 | ☑ |
| `frontend/src/components/watch/WatchProbeRecordsTable.vue` | 表容器、色阶、分页 | ☑ |
| `frontend/src/components/watch/WatchSlotCard.vue` | I/O 网格、分隔线 | ☑ |
| `frontend/src/components/watch/WatchMetricBar.vue` | 进度条高度/过渡 | ☑ |
| `frontend/src/components/watch/WatchSparkline.vue` | 主题色折线、渐变面积 | ☑ |
| `frontend/src/components/watch/WatchSlotRow.vue` | 行首标题区 | ☑ |
| `frontend/src/utils/watchFormat.ts` | `metricPctClass` / `formatProbePct` | ☑ |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | XSS / HTML 注入 | SAFE — 趋势 tooltip 仅拼接固定 `labels` 与数值;探针错误列用 Vue 文本插值,非 `v-html` |
| H2 | 新 API / 鉴权面 | SAFE — 无后端改动;仍走既有 `/watch/metrics``/watch/probe-records` |
| H3 | 密钥 / 凭据 | SAFE — 无配置或环境变量变更 |
| H4 | 时区语义 | SAFE — 仅展示层;`formatDateTimeBeijing` / `formatChartAxisBeijing` 逻辑未改 |
| H5 | 分页与筛选 | SAFE — `statusFilter` 变更时 `page = 1``pageCount` 下限为 1 |
| H6 | 主题色一致性 | SAFE — `WatchTrendChart` 对齐 `FleetTrendEChart``useTheme` 模式 |
| H7 | Sparkline 渐变 | P3 — `areaStyle` 使用 `${color}33` 后缀透明度,依赖主题为 `#hex`;若主题为 `rgb()` 渐变可能失效(线条仍可见) |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1 | SAFE | `WatchTrendChart.vue` L94112seriesName 来自本地 `labels`;时间经 `formatDateTimeBeijing` |
| H2 | SAFE | diff 仅 `frontend/src/**` |
| H3 | SAFE | 无 `server/``.env` 变更 |
| H4 | SAFE | 未改 `datetime.ts` |
| H5 | SAFE | `WatchProbeRecordsTable.vue` L88、`pageCount` computed |
| H6 | SAFE | 与 `FleetTrendEChart.vue` 同模式 `themeColor()` |
| H7 | P3 接受 | 与 Vuetify 默认 hex 主题兼容;后续可改为 `color-mix` 或固定 alpha 对象 |
## 验证
| 项 | 结果 |
|----|------|
| `cd frontend && npx vite build` | ✅ 6.5s |
| `bash scripts/local_verify.sh` | 未重跑(纯前端 UI,无 API 变更) |
| 门控 Gate 7 文件名 | 上表已列全 8 个源码文件 basename |
## DoD
- [x] changelog `2026-06-13-watch-ui-polish.md`(≥10 行)
- [x] 本审计含 Step 3 / Closure / DoD
- [x] `vite build` 通过
- [x] 提交 `4bbc47f` + 生产部署(2026-06-13
- [x] 生产 `/health` ok、`/app/` 200
- [ ] 浏览器终验:`#/watch-metrics` 暗色/亮色、探针分页、四槽空态
## 部署说明
- **仅前端**`vite build``deploy/deploy-frontend.sh` 或全量 `deploy-production.sh`
- **无需** 重启后端或数据库迁移
## 待办(非阻塞)
| 优先级 | 项 |
|--------|-----|
| P3 | Sparkline `areaStyle``rgb()` 主题下的渐变兼容 |
| P3 | 探针表 `total=0` 时隐藏分页器(UX |
-35
View File
@@ -1,35 +0,0 @@
# 审计 — 2026-06-14 门控 Gate 8 AI Review
**Changelog**: `docs/changelog/2026-06-14-gate-ai-review.md`
## Step 3 规则扫描
| 文件 | 规则 | 结论 |
|------|------|------|
| `scripts/gate_ai_review.py` | 无静默吞错 | PASS — 失败 exit 1 + 明确 stderr |
| `scripts/gate_ai_review.py` | 无明文密钥 | PASS — 仅用 env CURSOR_API_KEY(标准) |
| `scripts/gate_ai_review.py` | subprocess 安全 | PASS — 固定 argv,无 shell 拼接用户输入 |
| `deploy/pre_deploy_check.sh` | 门控计数一致 | PASS — GATES_TOTAL=8 |
| `docs/reviews/` | 产物可追溯 | PASS — JSON 含 head_sha、verdict、findings |
## Closure
| 项 | 状态 | 说明 |
|----|------|------|
| gate_ai_review.py | SAFE | 只读 review + 写 docs/reviews |
| pre_deploy_check.sh Gate 8 | SAFE | 调用 python 脚本 |
| 应急跳过 | SAFE | NEXUS_SKIP_AI_REVIEW=1 显式 opt-out |
## 文件清单
- `scripts/gate_ai_review.py`
- `scripts/gate_ai_review.sh`
- `deploy/pre_deploy_check.sh`
- `docs/reviews/.gitkeep`
## DoD
- [x] Step 3 规则扫描
- [x] Closure 表
- [x] 文件清单与 git diff 对齐
- [x] changelog ≥10 行
@@ -1,37 +0,0 @@
# 审计 — Layer3 health_monitor P2 + 监测 UI P3
**Changelog**: `docs/changelog/2026-06-14-layer3-health-monitor-p2-p3-ui.md`
## 审计范围
| 文件 | 变更 | 状态 |
|------|------|------|
| `deploy/health_monitor.sh` | 状态目录、docker_cmd、heredoc Telegram | ☑ |
| `tests/test_ops_patrol_sync.py` | bash -n + 路径断言 | ☑ |
| `frontend/src/utils/echartsColor.ts` | 新建 | ☑ |
| `WatchSparkline.vue` | rgba 渐变 | ☑ |
| `WatchProbeRecordsTable.vue` | 空表隐藏分页 | ☑ |
| `WatchSlotCard.vue` | ⋮ 菜单、暂停态精简(同批部署) | ☑ |
| `WatchMetricRing.vue` | `dimmed` 占位(同批) | ☑ |
| `server/background/watch_probe_runner.py` | 删除未使用 import | ☑ |
## Step 3
| H | 规则 | 结论 |
|---|------|------|
| H1 | sudo docker | SAFE — 与 `nexus-1panel.sh` 同模式,失败则跳过重启 |
| H2 | 状态目录权限 | SAFE — 在 DEPLOY_DIR 下,cron 用户可写 |
| H3 | Telegram HTML | SAFE — heredoc 真实换行,`--data-urlencode` 不变 |
## Closure
| H | 判定 |
|---|------|
| H1H3 | SAFE |
## DoD
- [x] changelog
- [x] `bash -n` + pytest ops_patrol
- [x] vite build
- [ ] 生产 L3 日志 5min 无新错误
@@ -1,36 +0,0 @@
# 审计 — 监测服务器详情(宝塔对齐)
**Changelog**: `docs/changelog/2026-06-14-watch-baota-server-detail.md`
## 审计范围
| 文件 | 变更 | 状态 |
|------|------|------|
| `remote_probe.py` | 负载进程数、分核 CPU、内存 MB、双 Top5 | ☑ |
| `watch_metrics.py` | `sanitize_process_bundle`、live 字段 | ☑ |
| `watch_service.py` | 进程 API 返回 bundle | ☑ |
| `WatchProcessDrawer.vue` | 宝塔式详情抽屉 | ☑ |
| `watchFormat.ts` / `useWatchPins.ts` | 类型与格式化 | ☑ |
| `WatchSlotCard.vue` | 「详情」入口 | ☑ |
| `tests/test_watch_metrics.py` | 新字段单测 | ☑ |
## Step 3
| H | 规则 | 结论 |
|---|------|------|
| H1 | SSH 脚本注入 | SAFE — 固定 psutil 脚本 |
| H2 | API 契约 | SAFE — `processes` 兼容 list→bundle |
| H3 | 性能 | NOTE — 分核 CPU 每 5s 一次 SSH,与现有探针同频 |
## Closure
| H | 判定 |
|---|------|
| H1H3 | SAFE / 接受 |
## DoD
- [x] changelog
- [x] pytest watch_metrics
- [x] vite build
- [ ] 生产终验详情抽屉
@@ -1,36 +0,0 @@
# 审计 — 监测开启后立即探针
**Changelog**: `docs/changelog/2026-06-14-watch-eager-probe-on-resume.md`
## 审计范围
| 文件 | 变更 | 状态 |
|------|------|------|
| `watch_probe_runner.py` | 抽取 `_probe_server_with_pins``trigger_immediate_watch_probe` | ☑ |
| `watch_service.py` | 加槽/替换/恢复监测后 eager probe | ☑ |
| `useWatchPins.ts` | 恢复监测 3s/8s 兜底 refresh | ☑ |
| `WatchSlotCard.vue` | 暂停底栏「已暂停」文案 | ☑ |
| `test_watch_pins.py` | 恢复监测立即 metrics 用例 + noop mock | ☑ |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | 无静默吞错 | SAFE — `trigger_immediate_watch_probe` 异常 `logger.exception` |
| H2 | 探针重复/竞态 | SAFE — 与 5s 循环共用 `_probe_server_with_pins`,仅多一次即时采样 |
| H3 | 暂停清空 Redis | SAFE — 仍仅在 `monitoring_enabled=false``clear_watch_redis_snapshot` |
| H4 | WS 推送 | SAFE — 即时探针同样 `publish_watch_metrics` |
| H5 | 前端轮询 | SAFE — 仅 `isWatchMetricsPending` 时 refresh,不刷屏 |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1H5 | SAFE | 恢复监测空窗期根因已消除;测试 10/10local_verify 26/26 |
## DoD
- [x] changelog ≥10 行
- [x] `pytest tests/test_watch_pins.py`
- [x] `bash scripts/local_verify.sh`
- [ ] 生产:开启实时监测后数秒内见指标或探针错误态
@@ -1,29 +0,0 @@
# 审计 — 监测槽暂停态居中开关
**Changelog**: `docs/changelog/2026-06-14-watch-slot-paused-center-switch.md`
## 审计范围
| 文件 | 变更 | 状态 |
|------|------|------|
| `WatchSlotCard.vue` | 暂停态居中开关、开启后才显示指标 | ☑ |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | 无新 API | SAFE — 仍 `emit('monitoring', …)` |
| H2 | 误触历史 | SAFE — 暂停态卡片主体不跳转,仅「历史」按钮 |
| H3 | 双开关冲突 | SAFE — 顶栏开关仅 `monitoringOn` 时显示;暂停仅中部开关开启 |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1H3 | SAFE | 纯前端 `WatchSlotCard.vue` 模板分支 |
## DoD
- [x] changelog
- [x] `cd frontend && npx vite build`
- [ ] 生产浏览器终验:暂停槽中部开关 → 开启后见 CPU/内存
@@ -1,30 +0,0 @@
# 审计 — 监测槽暂停态 UI 与顶栏间距
**Changelog**: `docs/changelog/2026-06-14-watch-slot-paused-ui-polish.md`
## 审计范围
| 文件 | 变更 | 状态 |
|------|------|------|
| `WatchSlotCard.vue` | 顶栏 ⋮ 菜单(暂停/恢复/移除);底栏暂停;暂停态 UI | ☑ |
| `WatchMetricRing.vue` | `dimmed` 占位态 | ☑ |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | 无新 API | SAFE — 仍 `emit('monitoring'/'remove')` |
| H2 | 误触移除/暂停 | SAFE — 顶栏无开关;暂停/移除在菜单内;底栏「暂停」为文字链 |
| H3 | 无障碍 | SAFE — `aria-label` on 开关与移除 |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1H3 | SAFE | 纯前端 |
## DoD
- [x] changelog ≥10 行
- [x] `vite build`
- [ ] 生产终验:暂停态无大虚线框;开启后开关与 ✕ 不贴在一起
@@ -1,70 +0,0 @@
# 审计 — 监测探针 SSH 优先 + 移除 IO + 宝塔圆环 UI
**Changelog**:
- `docs/changelog/2026-06-14-watch-probe-ssh-first-no-io-skip.md`
- `docs/changelog/2026-06-14-watch-remove-io-metrics.md`
- `docs/changelog/2026-06-14-watch-metric-ring-baota-style.md`
**范围**:实时监测探针与槽卡片 UI,对齐宝塔 CPU/内存/磁盘/负载展示。
## 审计范围
| 文件 | 变更 | 状态 |
|------|------|------|
| `server/background/watch_probe_runner.py` | 5s 一律 SSH;去掉 IO 速率差分 | ☑ |
| `server/utils/watch_metrics.py` | merge/live 优先 SSH;去掉 bps | ☑ |
| `server/infrastructure/ssh/remote_probe.py` | 精简探针命令;load 补位 | ☑ |
| `tests/test_watch_metrics.py` | 适配无 IO / SSH 优先 | ☑ |
| `frontend/src/components/watch/WatchMetricRing.vue` | 新建圆环组件 | ☑ |
| `frontend/src/components/watch/WatchSlotCard.vue` | 三圆环 + 负载圆环 | ☑ |
| `frontend/src/components/watch/WatchProbeRecordsTable.vue` | 去掉 IO 列 | ☑ |
| `frontend/src/components/watch/WatchTrendChart.vue` | 去掉 IO 曲线 | ☑ |
| `frontend/src/components/watch/WatchSparkline.vue` | 去掉 IO 指标 | ☑ |
| `frontend/src/composables/useWatchPins.ts` | 类型去掉 bps | ☑ |
| `frontend/src/pages/WatchMetricsPage.vue` | 去掉 IO 展示 | ☑ |
| `frontend/src/utils/watchFormat.ts` | `metricRingColor` / `loadUsagePct` | ☑ |
| `frontend/src/components/watch/WatchMetricBar.vue` | 删除(横条弃用) | ☑ |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | SSH 凭据与命令注入 | SAFE — 仍走既有 `remote_probe` 封装;无用户输入拼入 shell |
| H2 | 探针频率与负载 | NOTE — 有 SSH 时每 5s 一次 SSH(与宝塔同源,用户明确要求) |
| H3 | 数据合并优先级 | SAFE — SSH 覆盖 Agent stale 心跳;SSH 全失败才回退 Agent |
| H4 | 前端 XSS | SAFE — 圆环仅数值与固定标签;tooltip 文本插值 |
| H5 | IO 字段移除 | SAFE — DB 列保留 NULLAPI 不再推送 bps |
| H6 | 密钥 / 凭据 | SAFE — 无 `.env` 或密钥变更 |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1 | SAFE | `remote_probe.py` 固定 psutil 脚本 |
| H2 | 接受 | 用户要求与宝塔一致;仅监测开启槽 |
| H3 | SAFE | `merge_redis_and_ssh` CPU/mem/disk/load 优先 SSH |
| H4 | SAFE | `WatchMetricRing.vue``v-html` |
| H5 | SAFE | `to_live_dict` 无 bps 键 |
| H6 | SAFE | diff 无 secrets |
## 验证
| 项 | 结果 |
|----|------|
| `pytest tests/test_watch_metrics.py tests/test_watch_pins.py -q` | 22 passed(本机无 psutil 的 SSH 本地探针 1 skip/fail 为环境) |
| `cd frontend && npx vite build` | ✅ |
| `bash deploy/pre_deploy_check.sh` | 部署前执行 |
## DoD
- [x] changelog 三份(≥10 行)
- [x] 本审计含 Step 3 / Closure / DoD / 文件清单
- [x] `vite build` 通过
- [ ] 提交并 push
- [ ] 生产 `/health` ok、`/app/` 200
- [ ] 浏览器终验:`#/watch-metrics` 圆环与宝塔数值接近
## 部署说明
- **后端 + 前端**:需 API 重启(Docker upgrade+ `vite build` 同步 `web/app`
- **无** 数据库迁移
@@ -1,33 +0,0 @@
# 审计 — 终端 UI 部署(2026-06-17
**Changelog**: `docs/changelog/2026-06-17-terminal-ui-deploy.md`
## 审计范围
| 模块 | 文件 |
|------|------|
| 终端页 | `frontend/src/pages/TerminalPage.vue` |
| 右键菜单 | `frontend/src/components/terminal/TerminalContextMenu.vue` |
| xterm 会话 | `frontend/src/composables/terminal/useTerminalSessions.ts` |
| 全局布局 | `frontend/src/App.vue` |
## Step 3
| H | 规则 | 结论 |
|---|------|------|
| H1 | 纯前端,无新 API | PASS |
| H2 | 剪贴板仍走浏览器 API | PASS |
| H3 | 无密钥/凭据变更 | PASS |
## Closure
| H | 判定 |
|---|------|
| H1H3 | PASS |
## DoD
- [x] `local_verify.sh` 26/26
- [x] `npx vite build`
- [x] changelog + audit
- [ ] 生产 `/app/` 200 + 浏览器终验
@@ -1,34 +0,0 @@
# 审计 — 2026-06-19 登录白名单 LOGIN_ALLOWED_IPS SSOT
**Changelog**: `docs/changelog/2026-06-19-login-allowlist-auth-ssot.md`
## Step 3 规则扫描
| 文件 | 规则 | 结论 |
|------|------|------|
| `server/utils/login_allowlist.py` | 多 worker 数据一致 | PASS — 优先 `LOGIN_ALLOWED_IPS` |
| `server/utils/login_allowlist.py` | 无静默吞错 | PASS — 空列表回退 sub+manual |
| `frontend/src/pages/SettingsPage.vue` | 刷新失败 UX | PASS — `refresh_ok=false``loadAllowlist` |
| `tests/test_login_access.py` | SSOT 回归 | PASS — 新增用例 |
## Closure
| 项 | 状态 | 说明 |
|----|------|------|
| login_allowlist.py | SAFE | 校验与 DB 广播合并列表一致 |
| SettingsPage.vue | SAFE | 失败不覆盖 chip |
| test_login_access.py | SAFE | 覆盖 SSOT 路径 |
## 文件清单
- `server/utils/login_allowlist.py`
- `frontend/src/pages/SettingsPage.vue`
- `tests/test_login_access.py`
- `docs/changelog/2026-06-19-login-allowlist-auth-ssot.md`
## DoD
- [x] Step 3 规则扫描
- [x] Closure 表
- [x] 文件清单与 git diff 对齐
- [x] changelog ≥10 行
@@ -1,48 +0,0 @@
# 审计 — 2026-06-21 宝塔一键登录自动 bootstrap
**Changelog**: `docs/changelog/2026-06-21-btpanel-login-auto-bootstrap.md`
## Step 3 规则扫描
| 文件 | 规则 | 结论 |
|------|------|------|
| `btpanel_service.py` `create_login_url` | 未配置先 bootstrap,失败显式 ValueError;审计含 bootstrapped | PASS |
| `useBtPanelLogin.ts` | 无静默吞错,成功提示区分 bootstrap | PASS |
| `BtPanelLoginPage.vue` | 移除未配置拦截,与 servers 页一致 | PASS |
| `bootstrap_server` | 复用既有锁与 immediate 源,无重复 SSH 逻辑 | PASS |
## Closure
| 项 | 状态 | 说明 |
|----|------|------|
| 未配置仅 SSH 登录 | FIXED | login-url 先 immediate bootstrap |
| 前端需手动获取 API | FIXED | 一键登录单请求完成 |
| 宝塔页拦截未配置 | FIXED | 与 servers 行为统一 |
## 文件清单
- `server/application/services/btpanel_service.py`
- `server/infrastructure/btpanel/client.py`
- `frontend/src/App.vue`
- `frontend/src/api/btpanel.ts`
- `frontend/src/types/global.d.ts`
- `frontend/src/composables/btpanel/useBtPanelLogin.ts`
- `frontend/src/composables/btpanel/useBtPanelBatchBootstrap.ts`
- `frontend/src/components/btpanel/BtPanelBatchBootstrapDialog.vue`
- `frontend/src/components/servers/ServerBatchActionBar.vue`
- `frontend/src/components/servers/ServerUnsetPathPanel.vue`
- `frontend/src/pages/ServersPage.vue`
- `frontend/src/pages/btpanel/BtPanelLoginPage.vue`
- `frontend/src/pages/btpanel/BtPanelMonitorPage.vue`
- `tests/test_btpanel_login_url.py`
- `tests/test_btpanel_client.py`
- `tests/test_btpanel_login_url_route.py`
- `server/api/btpanel.py`
- `docs/changelog/2026-06-21-btpanel-login-auto-bootstrap.md`
## DoD
- [x] `#/servers` 一键登录未配置时自动获取 API 后打开面板
- [x] bootstrap 失败返回可读错误
- [x] `pytest tests/test_btpanel_login_url.py` 通过
- [x] changelog 已写
@@ -1,26 +0,0 @@
# 审计 — 2026-06-21 侧栏隐藏宝塔菜单
**Changelog**: `docs/changelog/2026-06-21-btpanel-nav-hidden.md`
## Step 3 规则扫描
| 文件 | 规则 | 结论 |
|------|------|------|
| `App.vue` | 仅移除导航 UI,路由与 API 未删 | PASS |
## Closure
| 项 | 状态 | 说明 |
|----|------|------|
| 侧栏宝塔入口 | DONE | 整组菜单隐藏 |
| servers 一键登录 | OK | 未改动 |
## 文件清单
- `frontend/src/App.vue`
- `docs/changelog/2026-06-21-btpanel-nav-hidden.md`
## DoD
- [x] 侧栏无「宝塔面板」分组
- [x] changelog 已写
@@ -1,78 +0,0 @@
# 审计 — 2026-06-21 宝塔 SSH 自动获取 API 部署
**Changelog**: `docs/changelog/2026-06-20-btpanel-ssh-api-bootstrap.md`
**Commit**: `82426b19` feat(btpanel): 独立宝塔模块与 SSH 自动获取 API
## Step 3 规则扫描
| 文件 | 规则 | 结论 |
|------|------|------|
| `ssh_bootstrap.py` | 无静默吞错、远程 JSON 校验 | PASS |
| `bootstrap_state.py` | 仅已入库服务器进 loop | PASS |
| `btpanel_service.py` | CUD 审计 `bt_panel_ssh_bootstrap` | PASS |
| `BtPanelSettingsPage.vue` | 批量操作二次确认 | PASS |
| `bootstrap_lock.py` | 同机并发 SSH 互斥 | PASS |
## Closure
| 项 | 状态 | 说明 |
|----|------|------|
| 后台 loop 误扫全库 | FIXED | `is_eligible` 需显式 `bt_panel` 块 |
| `result.success` SSH | FIXED | 改用 `status == success` |
| Permission denied 误判 | FIXED | 区分 SSH 认证与 api.json 写权限 |
| 空凭据标 ready | FIXED | `bootstrap_empty_credentials` |
| get_config 每次 SSH | FIXED | pending/installing 或 refresh 才探测 |
## 文件清单
- `frontend/src/App.vue`
- `frontend/src/api/btpanel.ts`
- `frontend/src/components/btpanel/BtPanelPageShell.vue`
- `frontend/src/components/btpanel/BtPanelServerPicker.vue`
- `frontend/src/composables/btpanel/btPanelContext.ts`
- `frontend/src/composables/btpanel/useBtPanelPageLoad.ts`
- `frontend/src/composables/btpanel/useBtPanelServer.ts`
- `frontend/src/constants/cachedPages.ts`
- `frontend/src/pages/btpanel/BtPanelCrontabPage.vue`
- `frontend/src/pages/btpanel/BtPanelDatabasesPage.vue`
- `frontend/src/pages/btpanel/BtPanelDomainsPage.vue`
- `frontend/src/pages/btpanel/BtPanelLoginPage.vue`
- `frontend/src/pages/btpanel/BtPanelMonitorPage.vue`
- `frontend/src/pages/btpanel/BtPanelServicesPage.vue`
- `frontend/src/pages/btpanel/BtPanelSettingsPage.vue`
- `frontend/src/pages/btpanel/BtPanelSiteCreatePage.vue`
- `frontend/src/pages/btpanel/BtPanelSitesPage.vue`
- `frontend/src/pages/btpanel/BtPanelSslPage.vue`
- `frontend/src/router/index.ts`
- `server/api/btpanel.py`
- `server/api/btpanel_schemas.py`
- `server/api/servers.py`
- `server/application/services/btpanel_bootstrap_schedule.py`
- `server/application/services/btpanel_service.py`
- `server/application/services/server_batch_service.py`
- `server/background/bt_panel_bootstrap_loop.py`
- `server/config.py`
- `server/infrastructure/btpanel/__init__.py`
- `server/infrastructure/btpanel/bootstrap_lock.py`
- `server/infrastructure/btpanel/bootstrap_state.py`
- `server/infrastructure/btpanel/client.py`
- `server/infrastructure/btpanel/credentials.py`
- `server/infrastructure/btpanel/source_ip.py`
- `server/infrastructure/btpanel/ssh_bootstrap.py`
- `server/infrastructure/btpanel/ssh_login.py`
- `server/infrastructure/ssh/remote_shell.py`
- `server/main.py`
- `tests/test_btpanel_bootstrap_loop.py`
- `tests/test_btpanel_client.py`
- `tests/test_btpanel_get_config.py`
- `tests/test_btpanel_ssh_bootstrap.py`
- `tests/test_btpanel_ssh_login.py`
## DoD
- [x] Step 3 规则扫描
- [x] Closure 表
- [x] 文件清单与 `git diff HEAD~1..HEAD` 对齐
- [x] `local_verify` 通过
- [x] `tests/test_btpanel_*.py` 25 passed
- [x] 需重启 API 注册 `bt_panel_bootstrap_loop`
@@ -1,30 +0,0 @@
# 审计 — 2026-06-21 宝塔临时登录 24 小时
**Changelog**: `docs/changelog/2026-06-21-btpanel-temp-login-24h.md`
## Step 3 规则扫描
| 文件 | 规则 | 结论 |
|------|------|------|
| `constants.py` | 单点 TTL 常量 | PASS |
| `btpanel_service.py` | API 传 expire_timetoken 拼 URL | PASS |
| `ssh_login.py` | base64 远程脚本,无静默吞错 | PASS |
## Closure
| 项 | 状态 | 说明 |
|----|------|------|
| 默认 3h 临时登录 | FIXED | API + SSH 均 86400s |
## 文件清单
- `server/infrastructure/btpanel/constants.py`
- `server/application/services/btpanel_service.py`
- `server/infrastructure/btpanel/ssh_login.py`
- `tests/test_btpanel_temp_login_ttl.py`
- `docs/changelog/2026-06-21-btpanel-temp-login-24h.md`
## DoD
- [x] expire_time = now + 86400
- [x] pytest 通过
@@ -1,34 +0,0 @@
# 审计 — 2026-06-21 登录白名单 SSOT 生产部署
**Changelog**: `docs/changelog/2026-06-21-login-allowlist-deploy.md`
## Step 3 规则扫描
| 文件 | 规则 | 结论 |
|------|------|------|
| `server/utils/login_allowlist.py` | SSOT / 多 worker | PASS |
| `frontend/src/pages/SettingsPage.vue` | 刷新失败 UX | PASS |
| `tests/test_login_access.py` | 回归覆盖 | PASS |
## Closure
| 项 | 状态 | 说明 |
|----|------|------|
| login_allowlist.py | SAFE | 优先 LOGIN_ALLOWED_IPS |
| SettingsPage.vue | SAFE | refresh_ok=false 重载 |
| test_login_access.py | SAFE | SSOT 用例 |
## 文件清单
- `server/utils/login_allowlist.py`
- `frontend/src/pages/SettingsPage.vue`
- `tests/test_login_access.py`
- `server/infrastructure/btpanel/client.py`bandit nosec,宝塔 API md5 协议)
- `scripts/gate_ai_review.sh`CRLF 修复)
## DoD
- [x] Step 3 规则扫描
- [x] Closure 表
- [x] 文件清单与 git diff 对齐
- [x] changelog ≥10 行
@@ -1,46 +0,0 @@
# 审计 — 2026-06-21 跨服务器文件传输
**Changelog**: `docs/changelog/2026-06-21-server-file-transfer.md``docs/changelog/2026-06-21-server-file-transfer-audit-fixes.md`
## Step 3 规则扫描
| 文件 | 规则 | 结论 |
|------|------|------|
| `server_file_transfer_service.py` | 路径 `shlex.quote`、直传 500MB 上限、deliver 失败回滚 | PASS |
| `transfer-download` | token Redis TTLJWT 前缀白名单 | PASS(已知:前缀对所有 method 生效) |
| `useFilesActions.ts` | 模式推荐基于 `transferItems`relay/deliver 分路径缓存 | PASS |
| `test_server_file_transfer.py` | deliver 成功/回滚/超限、relay、store | PASS |
## Closure
| 项 | 状态 | 说明 |
|----|------|------|
| deliver 失败泄漏归档/token | FIXED | rollback cleanup |
| deliver 成功 token 残留 | FIXED | delete_package_meta |
| 右键传输模式误判 | FIXED | transferItems |
| 直传多项中途失败无回滚 | ACCEPT | v1 文档化 |
## 文件清单
- `server/application/services/server_file_transfer_service.py`
- `server/infrastructure/redis/transfer_package_store.py`
- `server/api/sync_v2.py`
- `server/api/schemas.py`
- `server/api/auth_jwt.py`
- `frontend/src/composables/files/useFilesActions.ts`
- `frontend/src/composables/files/useFilesPage.ts`
- `frontend/src/components/files/FilesDialogs.vue`
- `frontend/src/components/files/FilesList.vue`
- `frontend/src/components/files/FilesToolbar.vue`
- `frontend/src/utils/auditLabels.ts`
- `tests/test_server_file_transfer.py`
- `docs/design/specs/2026-06-21-server-file-transfer-design.md`
- `docs/design/plans/2026-06-21-server-file-transfer.md`
- `docs/changelog/2026-06-21-server-file-transfer.md`
- `docs/changelog/2026-06-21-server-file-transfer-audit-fixes.md`
## DoD
- [x] 直传 + 打包投递 API 与前端对话框
- [x] `pytest tests/test_server_file_transfer.py` 18 passed
- [x] changelog ≥10 行
@@ -1,41 +0,0 @@
# 审计 — 2026-06-21 跨服务器传输独立页 + 侧栏调整
**Changelog**: `docs/changelog/2026-06-21-server-transfer-page.md``docs/changelog/2026-06-21-nav-watch-metrics-under-system.md`
## Step 3 规则扫描
| 文件 | 规则 | 结论 |
|------|------|------|
| `useServerFileTransfer.ts` | 路径 normalize、relay 500MB 禁用、localStorage 分模式缓存 | PASS |
| `useFilesActions.ts` | 移除内嵌传输;跳转 query 预填 | PASS |
| `ServerTransferPage.vue` | 顶层解构 ref,模板类型正确 | PASS |
| `App.vue` | 仅菜单分组调整,路由不变 | PASS |
## Closure
| 项 | 状态 | 说明 |
|----|------|------|
| 文件页内嵌传输对话框 | REMOVED | 改独立页 |
| 监测历史菜单位置 | MOVED | 运维 → 系统 |
## 文件清单
- `frontend/src/pages/ServerTransferPage.vue`
- `frontend/src/composables/useServerFileTransfer.ts`
- `frontend/src/router/index.ts`
- `frontend/src/App.vue`
- `frontend/src/composables/files/useFilesActions.ts`
- `frontend/src/composables/files/useFilesPage.ts`
- `frontend/src/components/files/FilesDialogs.vue`
- `frontend/src/components/files/FilesToolbar.vue`
- `docs/project/nexus-functional-development-guide.md`
- `docs/changelog/2026-06-21-server-transfer-page.md`
- `docs/changelog/2026-06-21-nav-watch-metrics-under-system.md`
## DoD
- [x] 独立页 `#/server-transfer` + 侧栏入口
- [x] 文件页跳转预填 query
- [x] 监测历史归入系统分组
- [x] `npx vite build` 通过
- [x] changelog ≥10 行
@@ -1,32 +0,0 @@
# 审计 — 2026-06-21 跨服务器传输 UI 补强
**Changelog**: `docs/changelog/2026-06-21-server-transfer-dest-browser.md``docs/changelog/2026-06-21-server-transfer-source-site-link.md`
## Step 3 规则扫描
| 文件 | 规则 | 结论 |
|------|------|------|
| `useServerFileTransfer.ts` | B 机 browse 同步 destPath;站点 URL 计算 | PASS |
| `ServerTransferPage.vue` | 双端文件表;A 机站点外链 `rel=noopener` | PASS |
| `useServerList.ts` | ServerBrief 扩展 extra_attrs 供站点解析 | PASS |
## 文件清单
- `frontend/src/composables/useServerFileTransfer.ts`
- `frontend/src/composables/useServerList.ts`
- `frontend/src/pages/ServerTransferPage.vue`
- `docs/changelog/2026-06-21-server-transfer-dest-browser.md`
- `docs/changelog/2026-06-21-server-transfer-source-site-link.md`
## Closure
| 项 | 状态 |
|----|------|
| B 机目录浏览 | DONE |
| A 机站点链接 | DONE |
## DoD
- [x] B 机文件列表与路径同步
- [x] A 机下方可点击站点 URL
- [x] `vite build` 通过
@@ -1,30 +0,0 @@
# 审计 — 2026-06-22 宝塔临时登录 24 小时
**Changelog**: `docs/changelog/2026-06-22-btpanel-temp-login-24h.md`
## Step 3 规则扫描
| 文件 | 规则 | 结论 |
|------|------|------|
| `constants.py` | 单点 TTL 常量 | PASS |
| `btpanel_service.py` | API 传 expire_timetoken 拼 URL | PASS |
| `ssh_login.py` | base64 远程脚本,无静默吞错 | PASS |
## Closure
| 项 | 状态 | 说明 |
|----|------|------|
| 默认 3h 临时登录 | FIXED | API + SSH 均 86400s |
## 文件清单
- `server/infrastructure/btpanel/constants.py`
- `server/application/services/btpanel_service.py`
- `server/infrastructure/btpanel/ssh_login.py`
- `tests/test_btpanel_temp_login_ttl.py`
- `docs/changelog/2026-06-22-btpanel-temp-login-24h.md`
## DoD
- [x] expire_time = now + 86400
- [x] pytest 通过
@@ -1,46 +0,0 @@
# 审计 — 检测路径改为 nginx 网站目录
## 范围(文件清单)
| 文件 | 变更 |
|------|------|
| `server/utils/nginx_path_detect.py` | 新增:构建远程 nginx root 检测命令 |
| `server/application/server_batch_common.py` | `detect_and_save_target_path` 改用 nginx |
| `frontend/src/pages/ServersPage.vue` | 确认对话框文案 |
| `scripts/batch_detect_target_path.py` | 脚本说明 |
| `tests/test_nginx_path_detect.py` | 单元测试 |
| `tests/test_server_onboarding.py` | mock 错误文案 |
| `server/application/services/btpanel_service.py` | 同批部署:宝塔临时登录 TTL |
| `server/infrastructure/btpanel/constants.py` | 同上 |
| `server/infrastructure/btpanel/ssh_login.py` | 同上 |
| `tests/test_btpanel_temp_login_ttl.py` | 同上 |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | 远程命令 domain 须 `shlex.quote` | PASS |
| H2 | 解析路径走 `normalize_remote_abs_path` | PASS |
| H3 | 非 root SSH 仍 `sudo_wrap` | PASS |
| H4 | 未静默吞 SSH 错误 | PASS — 无 stdout 返回明确错误 |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1 | SAFE | `nginx_path_detect.py` `shlex.quote(host)` |
| H2 | SAFE | `server_batch_common.py` normalize 后写入 |
| H3 | SAFE | 沿用既有 `sudo_wrap` |
| H4 | SAFE | `未找到 nginx 网站目录` |
## DoD
- [x] pytest tests/test_nginx_path_detect.py
- [x] changelog 2026-06-23-detect-path-nginx-root.md
- [x] 功能指南 § 检测目标路径 已更新
## 验证
```bash
pytest tests/test_nginx_path_detect.py tests/test_server_onboarding.py -q
```
@@ -1,53 +0,0 @@
# 审计 — IP-only 服务器自动补全站点域名
## 范围(文件清单)
| 文件 | 变更 |
|------|------|
| `server/utils/site_host.py` | 新增:IP 判定与站点 host 解析 |
| `server/utils/nginx_domain_detect.py` | 新增:远程 nginx 域名探测命令 |
| `server/application/server_batch_common.py` | `detect_and_save_site_url` / `update_server_site_url` |
| `server/application/services/ip_domain_detect_schedule.py` | 新增:23:30 定时筛选与触发 |
| `server/background/ip_domain_detect_loop.py` | 新增:background loop |
| `server/application/services/server_batch_service.py` | batch op `detect-domain` |
| `server/config.py` | `IP_DOMAIN_DETECT_*` 配置 |
| `server/main.py` | 注册 loop |
| `tests/test_site_host.py` | 单元测试 |
| `tests/test_nginx_domain_detect.py` | 单元测试 |
| `tests/test_ip_domain_detect_schedule.py` | 单元测试 |
| `server/application/services/btpanel_service.py` | 同批未提交:宝塔临时登录 TTL |
| `server/infrastructure/btpanel/constants.py` | 同上 |
| `server/infrastructure/btpanel/ssh_login.py` | 同上 |
| `tests/test_btpanel_temp_login_ttl.py` | 同上 |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | 远程 shell 参数须 `shlex.quote` | PASS — `target_hint` 已 quote |
| H2 | 不改 SSH `domain` 字段 | PASS — 仅写 `extra_attrs.site_url` |
| H3 | 非 root SSH 仍 `sudo_wrap` | PASS — 沿用 detect-path 模式 |
| H4 | 未静默吞 SSH 错误 | PASS — 无 stdout 返回明确错误 |
| H5 | 每日 Redis 去重防重复 batch | PASS — `already_ran_today` |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1 | SAFE | `nginx_domain_detect.py` `shlex.quote(hint)` |
| H2 | SAFE | `update_server_site_url` 只 merge extra_attrs |
| H3 | SAFE | `detect_and_save_site_url` 调用 `sudo_wrap` |
| H4 | SAFE | `未找到 nginx 站点域名` |
| H5 | SAFE | `ip_domain_detect_schedule.py` Redis key |
## DoD
- [x] pytest tests/test_site_host.py tests/test_nginx_domain_detect.py tests/test_ip_domain_detect_schedule.py
- [x] changelog 2026-06-24-ip-domain-detect-schedule.md
- [x] 设计文档 specs + plans
## 验证
```bash
.venv/bin/pytest tests/test_site_host.py tests/test_nginx_domain_detect.py tests/test_ip_domain_detect_schedule.py -q
```
@@ -1,31 +0,0 @@
# 审计 — 2026-06-29 宝塔 bootstrap 跳过无意义 reload
**Changelog**: `docs/changelog/2026-06-29-btpanel-bootstrap-skip-reload.md`
## Step 3 规则扫描
| 文件 | 规则 | 结论 |
|------|------|------|
| `ssh_bootstrap.py` | 有变更才 reload;仍原子写 api.json | PASS |
| `test_btpanel_ssh_bootstrap.py` | 远程脚本含 `if actions:` 先于 reload | PASS |
## Closure
| 项 | 状态 | 说明 |
|----|------|------|
| 重复 bootstrap 踢面板会话 | FIXED | `actions` 空则跳过写盘与 reload |
| 首次对接须 reload | OK | 新 token / 开 API / 加白名单仍 reload |
## 文件清单
- `server/infrastructure/btpanel/ssh_bootstrap.py`
- `tests/test_btpanel_ssh_bootstrap.py`
- `docs/design/specs/2026-06-20-btpanel-module-design.md`
## DoD
- [x] Step 3 规则扫描
- [x] Closure 表
- [x] 文件清单与本次改动对齐
- [x] `pytest tests/test_btpanel_ssh_bootstrap.py` 16 passed
- [x] 生产 rsync + Docker upgrade
@@ -1,44 +0,0 @@
# 审计 — 每日离线巡检发报前 SSH 复检
## 范围(文件清单)
| 文件 | 变更 |
|------|------|
| `server/application/server_connectivity.py` | `health_check_server_ids`;扫描列表 `batch_server_display_name` |
| `server/application/services/offline_daily_report_schedule.py` | preflight 后二次 snapshot |
| `server/config.py` | `OFFLINE_DAILY_REPORT_HEALTH_CHECK` |
| `server/infrastructure/telegram/__init__.py` | `preflight` 参数与文案 |
| `server/background/server_offline_monitor.py` | 告警显示名备注优先 |
| `tests/test_offline_daily_report_schedule.py` | preflight 单测 |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | 复用既有 SSH 探测,不新造协议 | PASS — `ssh_health_probe` + `write_ssh_health_heartbeat` |
| H2 | 并发受控 | PASS — `Semaphore(10)` 与批量健康检查一致 |
| H3 | 可配置关闭 | PASS — `OFFLINE_DAILY_REPORT_HEALTH_CHECK` |
| H4 | 日报数据为复检后 snapshot | PASS — 二次 `collect_offline_snapshot` |
| H5 | 显示名与批量任务 SSOT 一致 | PASS — `batch_server_display_name` |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1 | SAFE | `server_connectivity.health_check_server_ids` |
| H2 | SAFE | `asyncio.Semaphore(max(1, concurrency))` |
| H3 | SAFE | `_health_check_enabled()` |
| H4 | SAFE | `offline_daily_report_schedule.run_scheduled_offline_daily_report` |
| H5 | SAFE | `scan_monitored_connectivity` / `server_offline_monitor` |
## DoD
- [x] pytest tests/test_offline_daily_report_schedule.py tests/test_batch_server_display_name.py
- [x] changelog 2026-06-30-offline-daily-preflight-health-check.md
- [x] 无 DB 迁移
## 验证
```bash
.venv/bin/pytest tests/test_offline_daily_report_schedule.py tests/test_batch_server_display_name.py -q
```
@@ -1,41 +0,0 @@
# 审计 — Agent 双模式四槽监测
**Changelog**: `docs/changelog/2026-07-01-agent-watch-dual-mode.md`
## Step 3 规则扫描
| 文件 | 规则 | 结论 |
|------|------|------|
| `agent.py` (子机) | watch 模式每 5s 全量;退出 watch 回 60s | PASS |
| `agent.py` (中心) | `watch_active` 仅查 monitoring pin 计数 | PASS |
| `watch_probe_runner` | 仅 `watch_mode`+新鲜心跳跳过 SSH | PASS |
| 进程列表 | 仍 SSH 偶发拉取 | PASS(已知限制) |
## Closure
| 项 | 状态 |
|----|------|
| 四槽长期 SSH 风控 | 有 Agent 时改走出站 5s |
| 双 Agent 并存 | 不需要,单进程双模式 |
| 旧 Agent 兼容 | SSH 兜底 |
## 文件清单
- `web/agent/agent.py`
- `web/agent/config.example.json`
- `server/api/agent.py`
- `server/background/watch_probe_runner.py`
- `server/utils/watch_metrics.py`
- `tests/test_agent_watch_mode.py`
## DoD
- [x] pytest 19 passedwatch + agent_watch_mode
- [x] 心跳响应含 watch_active
- [x] Agent 2.1.0 watch_mode 字段
## 验证
```bash
.venv/bin/pytest tests/test_agent_watch_mode.py tests/test_watch_metrics.py -q
```
@@ -1,53 +0,0 @@
# 审计 — 2026-07-01 补录门禁(宝塔 / Terminal / 快捷命令)
**Changelog**: `docs/changelog/2026-07-01-gate-catchup-terminal-btpanel.md`
## Step 3 规则扫描
| 文件 | 规则 | 结论 |
|------|------|------|
| `ssh_bootstrap.py` | 无变更不写盘、不 reload | PASS |
| `terminal.py` | 静态路由 `reorder` 先于 `{id}` | PASS |
| `useServerQuickAdd.ts` | 复用 Servers `add-by-ip` 契约 | PASS |
| `ServerQuickAddDialogs.vue` | 与 Servers 弹窗一致 | PASS |
| `TerminalPage.vue` | 成功后 `loadServers` + `newSession` | PASS |
| `TerminalQuickCommandsSettings.vue` | `@click.stop` 防列表吞事件 | PASS |
## Closure
| 项 | 状态 | 说明 |
|----|------|------|
| 重复 bootstrap 踢宝塔会话 | FIXED | `if actions:` 才 reload |
| reorder 被 `{id}` 抢路由 | FIXED | 路由顺序调整 + 单测 |
| Terminal 无快速添加 | FIXED | TabBar 按钮 + 共用对话框 |
| 6/30 部署跳过门禁 | 补录 | 本 changelog/audit + pre_deploy |
## 文件清单
- `server/infrastructure/btpanel/ssh_bootstrap.py`
- `server/api/terminal.py`
- `tests/test_btpanel_ssh_bootstrap.py`
- `tests/test_terminal_quick_commands.py`
- `frontend/src/composables/servers/useServerQuickAdd.ts`
- `frontend/src/components/servers/ServerQuickAddDialogs.vue`
- `frontend/src/utils/openServerTerminal.ts`
- `frontend/src/components/terminal/TerminalTabBar.vue`
- `frontend/src/pages/TerminalPage.vue`
- `frontend/src/pages/ServersPage.vue`
- `frontend/src/components/TerminalQuickCommandsSettings.vue`
- `deploy/pre_deploy_check.sh`
## DoD
- [x] Step 3 规则扫描
- [x] Closure 表
- [x] 文件清单与本次批次一致
- [x] `pytest tests/test_btpanel_ssh_bootstrap.py tests/test_terminal_quick_commands.py` 通过
- [x] `pre_deploy_check.sh` 全门通过(Gate 3 回退或本机 :8600
## 验证
```bash
.venv/bin/pytest tests/test_btpanel_ssh_bootstrap.py tests/test_terminal_quick_commands.py -q
bash deploy/pre_deploy_check.sh
```
@@ -1,39 +0,0 @@
# 审计 — Agent 2.1.1 CPU 采样
**Changelog**: `docs/changelog/2026-07-02-agent-cpu-sampling.md`
## Step 3 规则扫描
| 文件 | 规则 | 结论 |
|------|------|------|
| `cpu_metrics.py` | watch 首次 1s bootstrap,后续 interval=None 对齐 ~5s 心跳 | PASS |
| `agent.py` | 进入 watch 时 `reset_watch_cpu_bootstrap()` | PASS |
| `server_batch_common` | 升级命令含 cpu_metrics.py | PASS |
| 常态 60s 心跳 | 仍用 `sample_cpu_percent(watch_mode=False)` | PASS |
## Closure
| 项 | 状态 |
|----|------|
| CPU 曲线长期 0% | 监测模式改长窗口采样 |
| 双模心跳 | 不变,仍 5s/60s |
| 旧 Agent 2.1.0 | 可继续用,仅 CPU 显示偏 0 |
## 文件清单
- `web/agent/cpu_metrics.py`
- `web/agent/agent.py`
- `server/application/server_batch_common.py`
- `tests/test_agent_cpu_metrics.py`
## DoD
- [x] pytest cpu_metrics + watch_mode 通过
- [x] AGENT_VERSION 2.1.1
- [x] 升级脚本拉取 cpu_metrics.py
## 验证
```bash
.venv/bin/pytest tests/test_agent_cpu_metrics.py tests/test_agent_watch_mode.py -q
```
@@ -1,39 +0,0 @@
# 审计 — 统计卡「未设路径·离线」
**Changelog**: `docs/changelog/2026-07-02-stats-unset-path-offline-card.md`
## Step 3 规则扫描
| 文件 | 规则 | 结论 |
|------|------|------|
| `servers.py` | `unset_path_offline` 由 fleet/main 离线差值推导 | PASS |
| `useServerStatsCards.ts` | 6 卡顺序与字段映射 | PASS |
| `ServersPage.vue` | 点击展开未设路径 + 离线筛 | PASS |
| `DashboardPage.vue` | 跳转 query 一致 | PASS |
## Closure
| 项 | 状态 |
|----|------|
| 在线+离线≠总数 | 拆出未设路径·离线后可对齐 |
| 主列表离线筛选 | 不变,仍 8 台口径 |
## 文件清单
- `server/api/servers.py`
- `frontend/src/composables/useServerStatsCards.ts`
- `frontend/src/pages/ServersPage.vue`
- `frontend/src/pages/DashboardPage.vue`
- `tests/integration/test_servers_dashboard.py`
## DoD
- [x] API 返回 `unset_path_offline`
- [x] 前后端 6 卡展示
- [x] integration test 断言差值公式
## 验证
```bash
.venv/bin/pytest tests/integration/test_servers_dashboard.py -q
```
@@ -1,39 +0,0 @@
# 审计 — 2026-07-03 Terminal 宝塔登录按钮
**Changelog**: `docs/changelog/2026-07-03-terminal-btpanel-login-button.md`
## Step 3 规则扫描
| 文件 | 规则 | 结论 |
|------|------|------|
| `TerminalToolbar.vue` | 仅 `session` 存在时显示;loading 防重复点击 | PASS |
| `TerminalPage.vue` | 复用 `useBtPanelLogin``serverId` 取自 `activeSession` | PASS |
| `useBtPanelLogin.ts` | 未改;并发去重与 `window.open` 行为不变 | PASS |
## Closure
| 项 | 状态 | 说明 |
|----|------|------|
| 终端内无法一键进宝塔 | FIXED | 工具栏「宝塔登录」+ 移动端菜单 |
| 与列表登录行为不一致 | N/A | 同一 composable / API |
## 文件清单
- `frontend/src/components/terminal/TerminalToolbar.vue`
- `frontend/src/pages/TerminalPage.vue`
## DoD
- [x] Step 3 规则扫描
- [x] Closure 表
- [x] 文件清单与本次批次一致
- [x] `npm run build-only` 通过
- [x] `pre_deploy_check.sh` 全门通过
## 验证
```bash
cd frontend && npm run build-only
bash deploy/pre_deploy_check.sh
bash deploy/deploy-frontend.sh
```
@@ -0,0 +1,21 @@
# 2026-06-06 — Gitea 推送脚本与本地 secrets
## 摘要
新增 `scripts/git-push.sh`,从 `deploy/nexus-1panel.secrets.sh` 读取 admin 凭据推送;更新 `.cursorrules` / `linux-dev-paths.md`
## 动机
公共仓库可匿名 pull,push 需写权限;凭据仅存本机 gitignore 文件,不入仓库。
## 涉及文件
- `scripts/git-push.sh`
- `deploy/nexus-1panel.secrets.sh.example`
- `.cursorrules``docs/project/linux-dev-paths.md`
## 验证
```bash
bash scripts/git-push.sh main # 需已配置 deploy/nexus-1panel.secrets.sh
```
@@ -0,0 +1,34 @@
# 2026-06-06 — 安装脚本按机自动生成唯一密钥
## 摘要
Docker / 1Panel 全新安装路径统一通过 `scripts/generate_nexus_secrets.py` 生成每台服务器独立的 `MYSQL_*``NEXUS_SECRET_KEY` / `NEXUS_API_KEY` / `NEXUS_ENCRYPTION_KEY`,写入 `docker/.env.prod` 并备份到 `/root/.nexus-install-secrets-*.env`。安装向导 `init-db` 复用容器环境内预生成密钥,避免与 Compose 不一致。
## 动机
新机安装不应从 `nexus-1panel.secrets.sh` 复制生产密钥;此前 `--fresh` 留空密钥导致 Compose 校验失败或各机密钥相同。
## 涉及文件
- `scripts/generate_nexus_secrets.py`(新建)
- `deploy/nexus-1panel.sh``write_env_prod` / `--reuse-secrets` / `--fresh` 忽略 secrets 中 NEXUS 项
- `deploy/install-nexus-fresh.sh` / `deploy/quick-install.sh`
- `docker/generate_env.py` — 复用同一生成器
- `docker/entrypoint.sh``NEXUS_INSTALL_WIZARD_PENDING=1` 时不写 `/app/.env`
- `docker/docker-compose.prod.yml` — 传递 `NEXUS_INSTALL_WIZARD_PENDING`
- `server/api/install.py``_install_keys_from_environment()`
- `deploy/README-1panel.md` · `deploy/nexus-1panel.secrets.sh.example`
## 迁移 / 重启
已部署实例无需变更。新机安装后需完成 `/app/install.html` 向导。
## 验证
```bash
python3 scripts/generate_nexus_secrets.py write-prod \
--template docker/.env.prod.example --output /tmp/t.env.prod \
--domain test.example.com --profile-env docker/profiles/2c8g.env --fresh --wizard-pending
grep NEXUS_SECRET_KEY /tmp/t.env.prod
ruff check server/api/install.py scripts/generate_nexus_secrets.py
```
@@ -0,0 +1,29 @@
# Changelog — 生产 SSH 凭据纳入 secrets + deploy 自动加载
**日期**2026-06-07
## 摘要
本机生产 SSH 私钥路径写入 `deploy/nexus-1panel.secrets.sh`gitignore);`deploy-production.sh` 启动时自动 source,与 `git-push.sh` 同一凭据文件。
## 动机
本机 `Host nexus` 未指向生产 VPS;部署脚本需 `NEXUS_SSH` / `NEXUS_SSH_KEY`,避免每次手动 export。
## 涉及文件
- `deploy/deploy-production.sh`
- `deploy/nexus-1panel.secrets.sh.example`
- `docs/project/linux-dev-paths.md`
- `deploy/nexus-1panel.secrets.sh`(本地 only,不入 git
## 迁移 / 重启
无。
## 验证
```bash
source deploy/nexus-1panel.secrets.sh
ssh -i "$NEXUS_SSH_KEY" "$NEXUS_SSH" "echo SSH_OK"
```
@@ -1,25 +0,0 @@
# 2026-06-10 — 文件管理「新建文件」修复
## 摘要
修复文件页「新建文件」点击无反馈或创建后编辑器不弹出的问题。
## 动机
- 未选服务器时按钮被禁用,用户以为功能坏了
- 创建成功后未等待 `FileEditorWorkbench` 就绪即 `openFile`,编辑器静默不打开
- SFTP 写入前未确保父目录存在(部分路径下 write-file 失败)
## 涉及文件
- `frontend/src/composables/files/useFilesActions.ts``openNewFileDialog``waitForEditorWorkbench`
- `frontend/src/composables/files/useFilesEditor.ts` — 导出 `waitForEditorWorkbench`
- `frontend/src/components/files/FilesToolbar.vue` — 未选服务器时提示而非禁用
- `server/infrastructure/ssh/asyncssh_pool.py` — SFTP 写前 `mkdir -p` 父目录
## 验证
```bash
cd frontend && npm run type-check
# 文件管理 → 选服务器 → 新建文件 → 应创建并打开编辑器
```
@@ -1,32 +0,0 @@
# 2026-06-10 — RDP Guacamole Client.State 常量修复
## 摘要
修复浏览器 RDP 已收到 `sync` 但 UI 永远停在「连接中」:`guacamole-common-js` 1.5 运行时使用 `Client.State.CONNECTED`,而非错误的 `STATE_CONNECTED`undefined)。
## 动机
Playwright E2E 与 WS 抓包显示 sync 已到达,但 Pinia 状态未变为 `connected`;根因是 `onstatechange``undefined` 比较永远不成立。
## 涉及文件
- `frontend/src/composables/rdp/useRdpSession.ts` — 使用 `Client.State` / `Tunnel.State`connect 序号防竞态
- `frontend/src/pages/RdpPage.vue``mounted` 守卫避免重复 connect
- `frontend/src/types/guacamole.d.ts` — 类型与运行时对齐
- `frontend/e2e/pages/rdp-connect.spec.mjs` — JWT 注入登录、nav 侧栏、canvas `.first()`
- `frontend/e2e/helpers.mjs``loginWithAccessToken``/rdp` 导航
- `scripts/rdp_ws_probe.py``scripts/guacd_rdp_probe.py` — 生产 WS/guacd 探针
## 迁移 / 重启
- 仅前端热更新(`web/app` sync 到容器)
## 验证
```bash
cd frontend && npx vite build
# 生产 Playwright(需 NEXUS_E2E_ACCESS_TOKEN 由容器 mint JWT
NEXUS_E2E_ACCESS_TOKEN=... npx playwright test e2e/pages/rdp-connect.spec.mjs
```
生产 E2E:约 12s 内出现「已连接」+ 1024×768 画布。
@@ -1,22 +0,0 @@
# 2026-06-10 — RDP 黑屏:缩放 + 键鼠
## 摘要
修复「已连接但黑屏」:Guacamole 画布默认 1024×768 未缩放,大区域为深色背景;补 `display.scale()` 适配容器、键鼠转发与 `onsync` 后重算缩放。
## 动机
E2E 显示已连接且 canvas 存在,但用户看到全黑;WS 已有 img/sync 帧,属前端渲染/布局问题。
## 涉及文件
- `frontend/src/composables/rdp/useRdpSession.ts` — scale、Mouse/Keyboard、onsync
- `frontend/src/pages/RdpPage.vue` — canvas 容器 flex 居中
- `frontend/src/types/guacamole.d.ts`
- `frontend/e2e/pages/rdp-connect.spec.mjs` — 采样非黑像素
## 验证
`NEXUS_E2E_ACCESS_TOKEN=... npx playwright test e2e/pages/rdp-connect.spec.mjs`
生产入口 bundle`index-DTfFTLS8.js`;强刷后 3389 页应可见桌面且可键鼠操作。
@@ -46,7 +46,3 @@ cd frontend && npm run type-check
## 说明
iframe 在用户当前的 Chrome/Firefox 等引擎中渲染,非独立安装 Firefox;禁止嵌入的站点请用「新标签打开」。
## 待办(已记录需求)
- **网站导航 / 收藏**:用户可自建固定站点列表,见 [设计说明](../design/specs/2026-06-11-browser-nav-bookmarks-design.md) · [实施计划](../design/plans/2026-06-11-browser-nav-bookmarks.md)(后端 `nav_links` 已预留,前端 UI 待做)
@@ -1,18 +0,0 @@
# Changelog — 审计日志目标列显示服务器名称
**日期**2026-06-12
## 摘要
审计页「目标」列对 `target_type=server` 显示 **服务器名称**(如 `服务器 web-01`),不再仅显示 `#372`
## 变更
- API `/api/audit/` 列表项增加 `target_name`(批量查 `servers.name`
- 前端 `auditTargetText()` + `AuditPage` 目标列
## 验证
```bash
.venv/bin/pytest tests/integration/test_alerts_audit.py::test_audit_logs_server_target_name -q
```
@@ -1,48 +0,0 @@
# 2026-06-12 远程浏览器 — 验证码 / 反自动化优化
## 摘要
针对阿里云等站点验证码失败:Worker 改为 headed + Xvfb + 反自动化指纹;前端滑块拖动支持窗口级鼠标跟踪与平滑步进。
## 动机
用户反馈阿里云验证码过不去。根因非 Nexus URL 拦截,而是 headless Chromium 被风控识别,且拖滑块时鼠标移出 canvas 会丢失 move 事件。
## 涉及文件
| 区域 | 文件 |
|------|------|
| Worker | `browser_stealth.py`, `session_manager.py`, `config.py`, `Dockerfile`, `docker-entrypoint.sh`, `docker-compose.yml` |
| 前端 | `useRemoteBrowserSession.ts`, `GlobalBrowserPanel.vue` |
## 变更要点
- 去掉 `--enable-automation``disable-blink-features=AutomationControlled`init script 隐藏 `navigator.webdriver`
- 默认 `WORKER_HEADLESS=false`,容器内 Xvfb `:99`
- 中文 locale / Asia/Shanghai / 常见 Chrome UA
- Screencast JPEG quality 90
- MOUSE`down/up` 先 move 到位;`move` 支持 `steps`;新增 `click`
- 前端:mousedown 后 `window` 级 mousemove/mouseup,滑块拖出画面仍有效
## 部署
```bash
# Worker
cd /opt/nexus-browser-worker && docker compose up -d --build
# 前端
cd frontend && npx vite build
# sync_webapp_to_container.sh
```
Worker `.env` 可选:`WORKER_HEADLESS=false`(默认)、`WORKER_SCREENCAST_QUALITY=90`
## 验证
- 打开 `https://account.aliyun.com` 或控制台登录页,滑块可拖到底
- 仍失败时:Worker IP 为机房段,阿里云可能加强风控 — 可本机 Chrome 登录后仅浏览公网页,或换住宅 IP Worker
## 残余限制
- 无法保证 100% 通过所有风控(拼图、短信、设备指纹)
- Cookie 在 Worker Profile,与本机浏览器不共享
@@ -1,47 +0,0 @@
# 2026-06-12 远程浏览器 P0/P1 修复与生产同步
## 摘要
修复 Worker 僵尸 reservation 占槽;前端 `vite build` 同步生产;WS 连接失败时主动 DELETE 会话。
## 动机
Bug 巡查 #1#3:生产无浏览器 SPA(用户连不上);Worker `sessions:1``ready=false` 不进 idle 清理。
## 变更
| 区域 | 内容 |
|------|------|
| `browser-worker/config.py` | `WORKER_RESERVE_TIMEOUT_SEC=120` |
| `browser-worker/session_manager.py` | idle 清理未 attach 的 reservation |
| `browser-worker/main.py` | `attach_stream` RuntimeError 时 `destroy_session` |
| `frontend/.../useRemoteBrowserSession.ts` | WS/未登录失败时 `DELETE /browser/sessions/{id}` |
| 生产 `web/app` | `index-BimRgA6s.js` + BrowserPage 等已 `sync_webapp_to_container.sh` |
| Worker `66.154.115.131` | `docker compose up -d --build``/health``sessions:0` |
## 迁移 / 重启
- 无 DB 迁移
- Worker 已重建;主站仅同步 `web/app`(未重建 API 镜像)
## 验证
```bash
pytest tests/test_browser_url_safe.py tests/test_browser_ui_state.py -q
cd frontend && npm run type-check && npx vite build
curl https://api.synaglobal.vip/app/ # index-BimRgA6s.js
ssh nexus 'sudo docker exec nexus-prod-nexus-1 grep -rl ws/browser /app/web/app/assets/ | wc -l' # ≥1
curl http://66.154.115.131:8443/health # sessions:0
```
浏览器终验:登录 → 左下角地球 → `https://example.com` 有 canvas 画面。
## 未在本批修复
- F1 页内导航 SSRF`framenavigated`
- F3 Worker ufw 公网 8443
- P2 前进/后退 stack 与 Chromium 历史不同步
## 回滚
恢复上一版 `web/app` tarWorker `git checkout` + `docker compose up -d --build`(或保留 idle 修复)。
@@ -1,19 +0,0 @@
# 2026-06-12 浏览器固定顶栏(原悬浮窗)
## 摘要
原悬浮窗保留:可拖动、最小化条、缩放;**无地球启动钮**。登录后默认收起;有标签时显示**顶栏右下**最小化条。首次无保存坐标时面板默认在**账号右侧**(顶栏下右对齐)。
## 动机
用户只要改默认停靠位置,不要砍掉原悬浮窗能力。
## 涉及文件
- `frontend/src/App.vue` — 顶栏仅账号;`appChromeVars` 为浏览器预留 420px
- `frontend/src/components/GlobalBrowserPanel.vue` — 固定 `top: 64px` 全宽,无拖动/地球图标
- 删除误加的 `GlobalBrowserAppBar.vue``GlobalBrowserDock.vue``GlobalBrowserChromeHost.vue``useGlobalBrowserChrome.ts`
## 验证
`cd frontend && npm run type-check && npx vite build`;登录后 App Bar 下见完整浏览器窗(标签+地址栏+画面);账号在顶栏最右。
@@ -1,24 +0,0 @@
# 文件下载修复 + 文件/终端列表紧凑样式
**日期**: 2026-06-12
## 变更摘要
- **下载**:修复 SFTP 会话在流式响应开始前关闭导致下载失败;下载时显示 loading
- **文件列表**:表格 `compact` + 操作按钮与服务器页一致的 `text/x-small`
- **终端右侧列表**:行高与字号收紧,与服务器列表密度接近
## 涉及文件
- `server/api/sync_v2.py`
- `frontend/src/components/files/FilesList.vue`
- `frontend/src/composables/files/useFilesActions.ts`
- `frontend/src/components/terminal/TerminalServerPickerList.vue`
## 迁移 / 重启
- 需 API 重启(下载逻辑在后端)
## 验证
`pytest` 相关 sync 测试;文件页点击下载应保存文件;终端/文件列表行高与服务器页接近。
@@ -1,22 +0,0 @@
# 文件管理移除下载入口
**日期**: 2026-06-12
## 变更摘要
移除文件管理页行内「下载」按钮与右键菜单项;大文件提示改为仅建议使用终端。
## 动机
产品决策:文件管理不提供浏览器下载,避免大文件与 SFTP 流式链路问题。
## 涉及文件
- `frontend/src/components/files/FilesList.vue`
- `frontend/src/composables/files/useFilesActions.ts`
- `frontend/src/composables/files/useFilesPage.ts`
- `frontend/src/composables/files/useFilesEditor.ts`
## 验证
`npm run type-check`;文件页无下载按钮;右键菜单无下载项。
@@ -1,69 +0,0 @@
# 2026-06-12 远程浏览器(Playwright Worker)上线
## 摘要
**66.154.115.131** 部署 `browser-worker`Playwright Chromium),Nexus 主站桥接 WebSocket + canvas 浮动面板,替代已移除的 iframe 方案。
## 动机
运维需在面板内完整浏览公网站点(不受 X-Frame-Options 限制);Chromium 运行在独立 Worker,隔离 SSRF 与内存压力。
## 涉及文件
| 区域 | 文件 |
|------|------|
| Worker | `browser-worker/*` |
| 后端 | `server/api/browser.py`, `browser_session.py`, `server/infrastructure/browser/*`, `server/utils/browser_url_safe.py`, `server/utils/browser_ui_state.py`, `server/config.py`, `server/main.py` |
| 前端 | `GlobalBrowserPanel.vue`, `useGlobalBrowser.ts`, `useRemoteBrowserSession.ts`, `BrowserPage.vue`, `App.vue`, `router`, `ServersPage.vue` |
| 测试 | `tests/test_browser_url_safe.py`, `tests/test_browser_ui_state.py` |
## 部署拓扑
```
管理员 SPA → Nexus (20.24.218.235) → Worker (66.154.115.131:8443) → 公网
```
## 生产配置(勿提交 git
主站 `/var/lib/nexus/.env``docker/.env.prod` 均需:
```env
NEXUS_BROWSER_ENABLED=1
NEXUS_BROWSER_WORKER_URL=http://66.154.115.131:8443
NEXUS_BROWSER_WORKER_SECRET=<与 Worker .env WORKER_SECRET 相同>
NEXUS_BROWSER_MAX_SESSIONS=2
NEXUS_BROWSER_IDLE_TIMEOUT_SEC=1800
```
Worker`/opt/nexus-browser-worker/.env``WORKER_SECRET` 同上)
防火墙:Worker `8443` 已对 Nexus 主站 IP `20.24.218.235` 放行。
## 迁移 / 重启
- 无 DB 迁移(复用 `admin_ui_preferences` / `embedded_browser`
- Worker`docker compose up -d --build`
- 主站:重建/重启 `nexus-prod-nexus-1` + 同步 `web/app`
## 验证
```bash
venv/bin/pytest tests/test_browser_url_safe.py tests/test_browser_ui_state.py -q
cd frontend && npm run type-check && npx vite build
curl -H "Authorization: Bearer $TOKEN" https://api.synaglobal.vip/api/browser/status
# → enabled:true, worker_ok:true
```
浏览器:左下角地球按钮 / 侧栏「浏览器」;输入 `https://example.com` 应出现 canvas 画面。
## 回滚
`NEXUS_BROWSER_ENABLED=0` 重启主站;Worker `docker compose down` 不影响 Nexus 其他功能。
## 2026-06-12 热修 — 画面不显示 / 连不上
**根因**Worker `CDPSession` 误用 `wait_for_event`(不存在),screencast 立即崩溃;断线后会话残留导致 `409`
**修复**`browser-worker/session_manager.py` 改为 `cdp.on("Page.screencastFrame")` + 队列;`POST /api/browser/sessions` 创建前清理该管理员陈旧会话。
**验证**:主站容器内 WS 测试 `SCREENCAST_OK`example.com JPEG ~4KB)。
@@ -1,31 +0,0 @@
# 2026-06-12 移除远程内置浏览器方案
## 摘要
整体下线 Playwright Worker + 全局浮动 canvas 浏览器:删前端组件/路由、后端 `/api/browser/*``/ws/browser``browser-worker/` 目录及单测。
## 动机
黑屏与状态机复杂、验证码与 SSRF 未闭环;运维场景下「站点」新标签打开即可。经验沉淀见 `docs/design/specs/2026-06-12-remote-browser-retired-lessons.md`
## 涉及文件(删/改)
- 删:`frontend/src/components/GlobalBrowserPanel.vue``useGlobalBrowser.ts``useRemoteBrowserSession.ts``BrowserPage.vue`
- 删:`server/api/browser.py``browser_session.py``server/infrastructure/browser/*``server/utils/browser_*`
- 删:`browser-worker/``tests/test_browser_*`
- 改:`server/main.py`(已不注册路由)、`server/config.py`(移除 `BROWSER_*`
- 保留:`frontend/src/utils/browserUrl.ts``ServersPage`「站点」→ `window.open`
## 迁移/重启
- 生产可保留 `NEXUS_BROWSER_*``.env``Settings.extra=ignore` 忽略);建议删除 Worker 容器与 8443 规则。
-**重启 Nexus API****前端 build 同步**
## 验证
```bash
venv/bin/pytest tests/test_settings_sound_defaults.py -q
cd frontend && npm run type-check && npx vite build
curl -sf https://api.synaglobal.vip/health
# 404 预期:GET /api/browser/status(需 JWT
```
@@ -1,33 +0,0 @@
# 2026-06-12 修复 push_complete_sound GET 404
## 摘要
`GET /api/settings/push_complete_sound` 在 MySQL 尚无该行时返回 404;现对已有默认值的设置项返回默认 JSON。
## 动机
登录后 `App.vue` 调用 `syncPushSoundFromServer` 触发 404`script_exec_complete_sound` 同理。
## 涉及文件
- `server/api/settings.py``SETTING_DEFAULTS` + `get_setting` 回退
- `tests/test_settings_sound_defaults.py`
## 默认
| key | 默认 |
|-----|------|
| `push_complete_sound` | `beep_double` |
| `script_exec_complete_sound` | `beep_double` |
| `theme` | `dark` |
## 验证
```bash
venv/bin/pytest tests/test_settings_sound_defaults.py -q
# 生产(JWTGET /api/settings/push_complete_sound → 200 {"value":"beep_double",...}
```
## 迁移
无;首次 PUT 仍会 upsert 入库。

Some files were not shown because too many files have changed in this diff Show More