246 Commits

Author SHA1 Message Date
Nexus Release Bot d3c3e5a74f chore(deploy): switch installer defaults to axs gitea
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-07-09 07:28:47 +08:00
Nexus Release Bot 92834559bc fix(deploy): include app-v2 in legacy deploy 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-07-09 07:20:11 +08:00
Nexus Release Bot 3938170a16 fix(release): complete app-v2 publish chain
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-07-09 06:53:36 +08:00
admin 1c5b45732f Merge PR #1: release 20260708 axs into main
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
Merge release/20260708-axs selected fixes into main via Gitea MCP.
2026-07-09 06:30:09 +08:00
Codex Release Bot 00c9eddd19 chore(mcp): add axs gitea mcp configuration
Nexus CI/CD / test (pull_request) Waiting to run
Nexus CI/CD / e2e (pull_request) Blocked by required conditions
Nexus CI/CD / deploy (pull_request) Blocked by required conditions
2026-07-09 06:25:39 +08:00
Codex Release Bot 6aaf292ad9 release: merge btpanel fixes app-v2 and gitea allowlist 2026-07-09 06:03:37 +08:00
r a16ba169fd fix(btpanel): 未装宝塔时一键登录返回友好提示
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
SSH/API 登录前检测面板;将 public 模块 Traceback 映射为可读中文;
bootstrap bt_not_installed 不再回退 SSH。AGENTS 约定改完推 Gitea。
2026-07-09 01:25:54 +08:00
r 9b583ccc91 docs(release): 主与 release 分支合并功能清单 + 导入 release 文档目录
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
便于 Gitea 上 main ↔ release/20260708-axs 合并评审与冲突对照。
2026-07-09 01:14:32 +08:00
r 5520434d53 build(frontend): 同步 web/app 构建产物与 ServersPage 调整
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
与 axs 全量重传前的本地工作区对齐。
2026-07-09 00:16:56 +08:00
r bd041136c0 chore(deploy): axs Gitea 为默认发布源 + 全量分批推送脚本
主项目推送到 https://axs.kuma1xn.vip 时分批提交避免 Cloudflare 524。
2026-07-08 23:58:52 +08:00
r 6859846b7c sync: 主项目全量同步(含 btpanel 一键登录回归修复与前端构建产物)
- bootstrap 失败恢复 SSH 临时登录回退
- ssh_bootstrap 真安装检测与 config 目录自愈
- axs Gitea 发布源切换为 https://axs.kuma1xn.vip
2026-07-08 23:52:40 +08:00
r d976cd03a3 feat(terminal): 工具栏增加宝塔一键登录按钮
终端有打开会话时,在字号左侧提供宝塔登录,复用 useBtPanelLogin。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-03 23:19:16 +08:00
Nexus Gate 41bff9dff6 补录 Gate 8 AI Review 缓存(65d8605b 批次) 2026-07-01 12:10:07 +08:00
Nexus Gate 65d8605b6c 补录 2026-07-01 部署门禁:Terminal 快速添加、快捷命令排序、Gate3 pytest 回退
6/30 生产部署跳过 pre_deploy_check;本提交补齐 changelog/audit 与门控修复。
2026-07-01 12:09:02 +08:00
r d50120d1d2 每日离线巡检发报前 SSH 复检离线子机并刷新心跳。
日报二次扫描反映复检后状态,Telegram 展示复检统计;离线列表与告警显示名优先备注。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 17:04:37 +08:00
r 0ec0aaf174 feat(btpanel): 一键登录临时 token 有效期改为 24 小时
API set_temp_login 传 expire_time=86400;SSH 回退用自定义脚本替代 tools.py 3 小时默认。
2026-06-22 05:48:15 +08:00
Nexus Agent 45d21026b7 feat(frontend): 跨服务器传输源机 A 展示可点击站点链接
与服务器列表同源 resolveServerSiteUrl;选 A 后显示域名链接。
2026-06-21 13:20:03 +08:00
Nexus Agent 71f0f53e6a feat(frontend): 跨服务器传输目标机 B 文件浏览
选 B 后展示目录列表,浏览时同步目标路径。
2026-06-21 13:15:01 +08:00
Nexus Agent 113718b553 fix(frontend): 跨服务器传输默认使用各机目标路径
选源机浏览 A.target_path;选目标机填入 B.target_path。
2026-06-21 13:09:48 +08:00
Nexus Agent da26d648a9 fix(frontend): 跨服务器传输页服务器选择与搜索
对齐文件页 v-autocomplete:v-model、客户端过滤、修正选项 slot。
2026-06-21 13:05:39 +08:00
Nexus Agent d770b07609 feat(frontend): 跨服务器传输独立页与侧栏菜单调整
将传输功能从文件页拆到 #/server-transfer,文件页改为跳转预填;监测历史移入系统分组。
2026-06-21 12:59:45 +08:00
r 73278667e0 feat(files): 跨服务器文件直传与打包投递
A→B SFTP 中继与一键 tar 打包 curl 拉取;含审计修复与 18 项单测。
2026-06-21 12:48:50 +08:00
r 76682e3be0 ui(nav): 侧栏隐藏宝塔面板菜单分组
宝塔能力保留在 servers 页一键登录与批量获取 API;btpanel 路由仍可通过直链访问。
2026-06-21 12:22:14 +08:00
r 629d8b3e26 fix(btpanel): login-url 返回类型含 bool 导致全站 500
路由注解 dict[str,str] 与 bootstrapped 布尔字段冲突,FastAPI 响应校验失败。
2026-06-21 12:17:29 +08:00
r 37720c4705 feat(btpanel): 一键登录自动获取 API 与倒计时提示
未配置机器点击登录时后端 immediate bootstrap;前端顶部 snackbar 显示约 20 秒倒计时。
2026-06-21 12:09:34 +08:00
r 4f260f9257 fix(servers): 批量栏已选区增加「选择」标题对齐
与运维/Agent/其他分组同结构底对齐。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 11:41:30 +08:00
r c8efbc2a90 refactor(servers): 行操作移除删除按钮
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-21 11:35:47 +08:00
r 2cb0b57281 fix(servers): 提高行操作与批量栏文字可读性
tonal/flat 实底按钮、加大字号字重,恢复一键登录全文。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 11:32:32 +08:00
r 80d6aad54b style(servers): 行操作与批量栏分组图标化 UI
outlined 按钮组 + 批量栏运维/Agent/其他分区展示。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 11:29:27 +08:00
r 58843a1ef4 refactor(servers): 精简行操作并收拢批量诊断
一键登录文案、移除监测/站点行按钮,检测路径与诊断移至批量栏。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 11:23:39 +08:00
r 7add9384c1 feat: 终端路由保持会话 + 服务器列表按创建时间倒序
keep-alive 下终端切换不 dispose SSH;列表/推送/终端默认 newest first。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 11:11:00 +08:00
r 6720ca3474 feat(servers): 主列表宝塔登录 + 健康检查写心跳
主服务器列表操作列一键登录宝塔;SSH 健康检查写入 Redis 心跳并刷新在线状态。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 11:05:43 +08:00
r 2e797f86b7 feat(btpanel): 服务器列表增加在线状态列
批量读 Redis 心跳返回 status,前端与主服务器列表样式一致。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 10:54:04 +08:00
r 5ea5c3a411 fix(btpanel): 一键登录 URL 去掉安全入口路径
tmp_token 登录在 :port/login,非 :port/{admin}/login,后者 nginx 404。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 10:46:22 +08:00
r 3195787461 fix(btpanel): 非 root SSH bootstrap 自动 sudo 提权
ubuntu 等用户免密 sudo 下宝塔已装但脚本未提权导致 ssh_command_failed;附 ssh_timeout 运维清单。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 10:40:46 +08:00
r 97c47a120f fix(btpanel): 面板登录 URL 使用宝塔 pyenv 执行 tools.py
系统 python3 缺 psutil 导致 SSH 回退失败;相对 tmp_token 自动拼安全入口。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 10:16:33 +08:00
r 028dc2c8b6 fix(btpanel): API base_url 不含安全入口路径
宝塔 API 走 :port/system,安全入口仅用于浏览器登录;修复代理 404。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 10:10:34 +08:00
r 51b6d0cdff fix(btpanel): API 签名改为 md5(time+token)
与面板 common.py 一致;原双重 md5 导致密钥校验失败。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 10:05:39 +08:00
r e5929694c3 fix(btpanel): bootstrap 写入 panel/config/api.json
宝塔读取 config/api.json 而非 data/api.json,错误路径导致白名单与 token 不生效。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 10:01:40 +08:00
r b768da3ab8 fix(btpanel): bootstrap 误判 exit_code=0 为失败
Python 中 0 or 1 为 1,导致 SSH 成功仍报 ssh_command_failed。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 09:52:22 +08:00
r 45ca57bf3f fix(ssh): 连接前从 preset_id 解析 SSH 密码
修复宝塔批量引导因仅关联密码预设、行内 password 为空而全部 ssh_command_failed。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 09:47:22 +08:00
r 14bbfc4ab7 fix(btpanel): 批量获取时跳过已配置 API 的服务器
前端仅提交未配置 ID;后端 batch 任务对已配置记为跳过。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 09:39:57 +08:00
r 770469c4aa fix(btpanel): 批量勾选提交真实 server id
v-data-table 勾选值为 id 数组,修复误传 undefined 导致 422。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 09:37:04 +08:00
r b4436143a6 feat(btpanel): 服务器列表批量 SSH 获取 API
支持全部未配置与多选批量引导;未配置 API 时禁用一键登录。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 09:34:02 +08:00
r 7c99298419 fix(btpanel): 连接配置页始终显示全局设置
未选服务器时不再隐藏整页内容,避免白屏。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 09:30:03 +08:00
r 461ac81221 fix(btpanel): 接受相对路径 tmp_token 登录链接
宝塔 tools.py 常返回 /login?tmp_token=...,现与 base_url 拼接为完整 URL。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 09:26:57 +08:00
r 1c4c5af0e4 feat(btpanel): 入口改为服务器列表
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-21 09:23:26 +08:00
r d00294c86d docs(btpanel): 部署审计与 changelog
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 09:16:54 +08:00
r 82426b1941 feat(btpanel): 独立宝塔模块与 SSH 自动获取 API
新增侧栏「宝塔面板」菜单、代理 API 与连接配置页;通过 SSH 读写子机
api.json 自动写入凭据,5 分钟后台重试,含审计修复、检测缓存与并发锁。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 09:13:14 +08:00
r ebab836554 docs: 审计文件清单补全 btpanel client
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 08:14:26 +08:00
r ccc5419369 chore: 部署门控 — 今日 changelog/audit、bandit nosec、shell CRLF
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 08:14:05 +08:00
r d8ec4f1af4 fix: 登录白名单校验改用 LOGIN_ALLOWED_IPS 单一数据源
多 worker 下订阅刷新后 UI 已更新但登录仍被拒;另修复刷新失败时前端误清空节点列表。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 08:12:52 +08:00
r 9c9a51263c chore: 本地快照 — 终端 UI、服务器 site_url、后台任务与 Agent 导航
汇总未提交工作区:Servers 列表 site_url 链接、终端页与选机器 UI、SSH 连通性与 Telegram 通知、agent/offline/unset_path 后台调度与 gate_ai_review 门控;更新 AGENTS 与 agent-exploration 规则(仅本机 commit);同步 web/app 构建产物与相关文档。
2026-06-18 06:01:50 +08:00
Nexus Agent 1a79c0fbfb feat(watch): Baota-style server detail drawer
Expose load/process counts, per-core CPU, memory MB breakdown, and
separate CPU/MEM top-5 processes from SSH probes in the detail drawer.
2026-06-14 06:23:45 +08:00
Nexus Agent 86a45612c8 fix(ops): writable Layer3 state dir for cron user
Fall back to ~/.nexus/layer3-health when deploy/var is root-owned.
2026-06-14 04:23:20 +08:00
Nexus Agent 05b0fde197 fix(ops): Layer3 health_monitor state dir and Telegram heredocs
Move fail counters under deploy/var/layer3-health, use docker_cmd with
sudo fallback, and refactor alert messages to avoid quote parse errors.

Also fix watch Sparkline rgba gradients, hide empty probe pagination,
and ship pending slot pause/menu UI polish.
2026-06-14 04:20:55 +08:00
Nexus Agent c91cb1cd9e fix(watch): polish paused slot UI and header action spacing
Replace the large dashed pause panel with dimmed rings and a resume CTA,
and add gap between the monitoring switch and remove button.
2026-06-14 03:34:17 +08:00
Nexus Agent 3faa7a4f1c fix(watch): align four metric rings in one row
Move load ring beside CPU/mem/disk so the slot card matches Baota layout.
2026-06-14 03:25:10 +08:00
Nexus Agent b5419cd2c0 feat(watch): SSH-first probe, drop IO metrics, Baota ring UI
Align live monitoring with Baota: always SSH psutil for CPU/mem/load,
remove network/disk IO rates, and show metrics as circular rings in slots.
2026-06-14 03:19:43 +08:00
Nexus Agent 5c508ed14c fix(watch): eager probe on resume so metrics appear immediately
After pausing clears Redis snapshots, resuming or adding pins now triggers
an immediate probe before list_slots returns, with frontend refresh fallback.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 02:53:02 +08:00
Nexus Agent 825d0297c4 feat(watch): show centered enable switch when slot monitoring is paused
Paused slots now prompt with a large middle switch before showing CPU and memory metrics; header switch only appears while monitoring is on.
2026-06-14 00:38:44 +08:00
Nexus Agent 4bbc47faab feat(watch): polish monitoring history UI with themed charts and empty states
Align watch metrics pages with dashboard fleet trends: theme-aware ECharts,
skeleton loaders, metric color thresholds, and compact I/O layout on slot cards.
2026-06-13 23:29:58 +08:00
Nexus Agent ba7c790f38 feat(watch): show probe history in Beijing time and retire remote browser
Monitoring history charts and tables now format UTC timestamps as Asia/Shanghai.
Remove Playwright worker, global browser panel, and related API routes; open site URLs in a new tab instead. Fix chain test Redis pipeline mock.
2026-06-13 16:32:02 +08:00
Nexus Agent adee25deb6 fix(watch): repair SSH probe script broken by embedded quote in os_release parse
Use chr(34) inside the bash-wrapped python -c probe so monitor slots work again after hardware detail rollout.
2026-06-13 16:10:32 +08:00
Nexus Agent 4acbf04322 feat(watch): show detailed CPU, memory, and disk specs on monitor slots
Expose used vs total capacity, CPU model, swap, OS uptime, and 1/5/15 load from SSH probes and agent heartbeats.
2026-06-13 14:12:09 +08:00
Nexus Agent feb776616f docs: add 2026-06-13 audit/changelog for files new-file fix deploy gate. 2026-06-13 09:09:01 +08:00
Nexus Agent 72042598b0 Fix files page new-file flow and SFTP parent directory creation.
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
Show a clear prompt when no server is selected, wait for the editor workbench before openFile, and mkdir -p parent paths before SFTP writes.
2026-06-13 09:08:17 +08:00
Nexus Agent e95e522d4d docs(audit): gate checklist for Telegram group chat ID detection 2026-06-13 02:05:27 +08:00
Nexus Agent 40cba6a567 fix(settings): detect Telegram group chat IDs via my_chat_member
Extend getUpdates parsing for group joins and update settings UI hints so Chat ID detection works for supergroups, not only private DMs.
2026-06-13 02:04:59 +08:00
Nexus Agent 821cf12ade fix(terminal,alerts): quick-cmd enter on send and offline Telegram channel
Append CR when executing quick commands (fixes trim stripping saved \r) and
allow optional dedicated Telegram bot for server offline alerts only.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 01:45:55 +08:00
Nexus Agent deafd46a0e docs(reports): 文件管理移除下载生产验证记录
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 06:52:47 +08:00
Nexus Agent dbb6bc4a55 fix(browser): 门控 — Worker TLS verify 可配置、审计清单与 shell LF
默认校验 Worker HTTPS 证书;审计文件补全变更清单;docker-entrypoint.sh 改为 LF。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 06:50:06 +08:00
Nexus Agent e833b923c8 feat(browser): 远程浏览器 Worker 桥接与顶栏固定悬浮窗
接入 Playwright Worker、会话 WebSocket 与全局浏览器面板(固定于 App Bar 下);
含验证码 stealth、设置项默认音与 URL 安全校验;附带 worker 部署与设计文档。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 06:49:20 +08:00
Nexus Agent 2b0413c43e feat(files): 移除文件管理下载入口
去掉行内与右键下载;大文件提示改为使用终端。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 06:32:09 +08:00
Nexus Agent a51a5c5341 fix(files): 修复下载流式 SFTP 并收紧列表行高
下载在独立 SFTP 会话中流式传输;文件/终端列表与服务器页一致的紧凑操作与行高。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 05:50:09 +08:00
Nexus Agent 80aaf855a3 feat(terminal): 右侧服务器列表增加搜索
侧栏复用 PickerSearch,按名称/域名筛选并支持搜索历史。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 05:46:34 +08:00
Nexus Agent e42612e75f feat(watch): 监测历史四槽同屏展示趋势
资源趋势 2×2 四图;探针记录默认全部槽位;表头内存/硬盘中文。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 05:44:28 +08:00
Nexus Agent 7a0001d8f4 fix(watch): 恢复指标进度条原三档配色
≥90% 红、70–89% 黄、<70% 绿。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 05:29:54 +08:00
Nexus Agent 3598d38b8e fix(watch): 指标进度条 ≥80% 变红,到期倒计时归零加固
进度条阈值由 70/90 改为 80% 红色;remaining_sec≤0 时立即 refreshPins。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 05:17:38 +08:00
Nexus Agent d5ecb54d6e feat(watch): 监测槽等待探针回传占位 UI
开启监测后无 probe_status 时显示等待文案与加载动画,回传后自动展示指标。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 05:08:03 +08:00
Nexus Agent ac98dc496c fix(watch): 移除监测槽 hover 时 Vuetify 灰遮罩
v-card 关闭 link/ripple 并隐藏 overlay,仅保留外框主题色高亮。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 04:54:24 +08:00
Nexus Agent 49b471eff9 test(e2e): 监测槽时长与指标 UI 生产冒烟
Playwright 验证默认 30 分 chip、内存/硬盘/负载与进度条;附部署验证报告。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 04:40:40 +08:00
Nexus Agent 647ebcb673 feat(watch): 监测槽分钟时长档、指标进度条与负载显示
默认 30 分钟,支持 30分/1时/2时/8时/24时;槽卡展示 CPU/内存/硬盘进度条与 1 分钟负载;API 改用 ttl_minutes。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 04:34:52 +08:00
Nexus Agent 4c38bc05d3 docs(audit): 监测槽 hover 外框高亮 2026-06-12 04:21:28 +08:00
Nexus Agent 907607e30b fix(watch): 监测槽 hover 改为外框高亮
悬停时仅边框变主题色,不再整卡铺灰底。
2026-06-12 04:21:16 +08:00
Nexus Agent bdbfef7110 fix(audit): 目标列显示服务器名称而非 #id
审计列表对 server 类型批量解析 target_name,前端展示为「服务器 {name}」。
2026-06-12 04:16:46 +08:00
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
13464 changed files with 250133 additions and 15065 deletions
+15 -1
View File
@@ -2,8 +2,22 @@
"mcpServers": {
"mysql-mcp": {
"command": "bash",
"args": ["scripts/linux_mcp_mysql.sh"],
"args": [
"scripts/linux_mcp_mysql.sh"
],
"env": {}
},
"gitea-mcp": {
"command": "bash",
"args": [
"scripts/mcp_gitea.sh"
],
"env": {
"GITEA_HOST": "https://axs.kuma1xn.vip",
"GITEA_ACCESS_TOKEN_FILE": "/mnt/c/Users/uzuma/.codex/secrets/gitea-axs.token",
"HTTPS_PROXY": "socks5://127.0.0.1:10808",
"HTTP_PROXY": "socks5://127.0.0.1:10808"
}
}
}
}
+44
View File
@@ -0,0 +1,44 @@
---
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`
+1 -1
View File
@@ -11,7 +11,7 @@ alwaysApply: false
| 调用方 | 机制 |
|--------|------|
| 管理端 API | JWT Bearer + 可选 TOTP |
| Agent | `X-API-Key`全局或 per-server `agent_api_key` |
| Agent | `X-API-Key` per-server `agent_api_key`;经 `POST /api/servers/{id}/agent-key` 生成,BL-06 起无 global 回退 |
| WebSocket | JWT query param |
| 安装向导 | 无 JWT(仅安装模式可用) |
+7
View File
@@ -0,0 +1,7 @@
{
"plugins": {
"compound-engineering": {
"enabled": true
}
}
}
+14 -2
View File
@@ -5,7 +5,7 @@
## 栈
FastAPI + Async SQLAlchemy + Redis · Vue 3 + Vuetify 4 SPA14 页)· Vite → `web/app/`
FastAPI + Async SQLAlchemy + Redis · Vue 3 + Vuetify 4 SPA15 页)· Vite → `web/app/`
## 环境(勿在仓库写密码)
@@ -29,13 +29,25 @@ FastAPI + Async SQLAlchemy + Redis · Vue 3 + Vuetify 4 SPA14 页)· Vite
见 `.cursor/rules/perfect-implementation.mdc` · `nexus-security.mdc` — 完美实现、无静默吞错、无明文密钥、CUD 审计。
## Agent Git / diff(省 token、防卡顿)
- **禁止**无路径过滤的全仓 `git diff` / `git diff HEAD``web/app/assets` 构建产物可导致数十万行输出)。
- **优先**`git diff --stat` 或 `git diff -- frontend/src server/api tests docs`。
- **提交**:只 `git add` 源码与文档;`web/app/assets/*` 由 Docker 内 `vite build` 生成,勿为 diff 把海量 hash 文件纳入上下文。
- **部署/门控**:定向 pytest(如 `tests/integration/test_*.py`),避免并行全仓 diff + 本地 build + 远程镜像重建。
## 部署
默认 **本机 rsync 直传**(不 push Gitea):
```bash
bash deploy/pre_deploy_check.sh && bash deploy/deploy-production.sh
```
前端: `cd frontend && npx vite build` → tar/scp(见 `deploy/deploy-frontend.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/`
## 进度条(改代码时输出)
+13
View File
@@ -27,3 +27,16 @@ standards/
web/uploads
backups/
deploy/gate_log.jsonl
# Release/build local artifacts
frontend-v2/node_modules
.venv
.venv-*
venv
.pytest_cache
.ruff_cache
.playwright-mcp
2025.2
=2025.2
*.bak-
tmp/
+42 -2
View File
@@ -35,18 +35,58 @@ jobs:
run: mypy server/ --ignore-missing-imports
continue-on-error: true # Type errors don't block deployment yet
- name: Run tests with coverage
- name: Run fast tests with coverage
run: |
coverage run -m pytest tests/ -v --tb=short
coverage run -m pytest tests/ -m "not chain and not slow" -q --tb=short
coverage report --fail-under=50
coverage xml
- name: Run chain tests
run: pytest tests/chain -m chain -q --tb=short
continue-on-error: true
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage.xml
# ── CI: E2E (main only, optional secrets) ──
e2e:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
- name: Install frontend dependencies
working-directory: frontend
run: npm ci
- name: Install Playwright Chromium
working-directory: frontend
run: npx playwright install chromium
- name: Run Playwright E2E
working-directory: frontend
env:
NEXUS_E2E_BASE: ${{ secrets.NEXUS_E2E_BASE || 'http://127.0.0.1:8600' }}
NEXUS_E2E_USER: ${{ secrets.NEXUS_E2E_USER || 'admin' }}
NEXUS_E2E_PASSWORD: ${{ secrets.NEXUS_E2E_PASSWORD }}
run: |
if [ -z "$NEXUS_E2E_PASSWORD" ]; then
echo "NEXUS_E2E_PASSWORD not set — skip E2E"
exit 0
fi
npm run test:e2e
continue-on-error: true
# ── CD: Deploy ──
deploy:
needs: test # Only deploy if tests pass
+2
View File
@@ -16,6 +16,7 @@ deploy/nexus-1panel.secrets.sh
*.pem
.env.*
.venv/
.venv-*/
venv/
ENV/
@@ -26,6 +27,7 @@ ENV/
.megamemory/
.playwright-mcp/
frontend/test-results/
frontend/e2e/.auth/
tmp/
# IDE
+54 -2
View File
@@ -6,7 +6,7 @@
## 项目概要
Nexus2000+ 子机运维平台。后端 FastAPI + MySQL + Redis;前端 **Vue 3 + Vuetify 4 + TypeScript SPA**14 页,`frontend/src/pages/`);构建输出 `web/app/`;安装向导 `web/app/install.html`
Nexus2000+ 子机运维平台。后端 FastAPI + MySQL + Redis;前端 **Vue 3 + Vuetify 4 + TypeScript SPA**15 页,`frontend/src/pages/`);构建输出 `web/app/`;安装向导 `web/app/install.html`
## 本地开发
@@ -19,6 +19,13 @@ cd frontend && npm run dev # :3000/app/
路径见 [linux-dev-paths.md](docs/project/linux-dev-paths.md)。
## Git(本机 + Gitea
- 改完代码:**`git commit``bash scripts/git-push.sh` 推到 Gitea**(凭据 `deploy/nexus-1panel.secrets.sh`);大仓库可用 `scripts/git-push-axs-full.sh`
- 本地 commit 仍用于回滚;**不要漏推 Gitea**(用户约定 2026-07-09)。
- `.env``SECRETS.md``*.pem``deploy/nexus-1panel.secrets.sh` 已在 `.gitignore`**勿提交**。
- 改代码仍写 `docs/changelog/YYYY-MM-DD-*.md`(≥10 行)。
## 强制约束
- 完美实现:`.cursor/rules/perfect-implementation.mdc`
@@ -32,13 +39,58 @@ cd frontend && npm run dev # :3000/app/
## 部署
默认 **本机 rsync → SSH 生产机****代码先 push Gitea 再部署**(见上节 Git):
```bash
bash deploy/pre_deploy_check.sh
bash deploy/deploy-production.sh # 需 SSH nexus + 用户批准
bash scripts/git-push.sh # 或 git-push-axs-full.sh
bash deploy/deploy-production.sh # rsync + Docker upgrade --skip-git + 前端
```
可选:`NEXUS_DEPLOY_VIA_GIT=1` 恢复远程 `git pull`
详情见功能指南 §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 个文件 → 停止**。
+5 -1
View File
@@ -2,7 +2,11 @@
FROM python:3.12-slim-bookworm
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& apt-get install -y --no-install-recommends \
curl \
openssh-client \
rsync \
sshpass \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
+21 -2
View File
@@ -12,11 +12,25 @@ 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 npm ci --ignore-scripts
COPY frontend-v2/ ./
RUN npm run build:safe
# ── 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 \
&& apt-get install -y --no-install-recommends \
curl \
openssh-client \
rsync \
sshpass \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
@@ -26,9 +40,13 @@ RUN pip install --no-cache-dir -r requirements.txt
COPY server/ ./server/
COPY web/agent/ ./web/agent/
COPY --from=frontend_v2 /build/web/app-v2 ./web/app-v2/
COPY --from=frontend /build/web/app ./web/app/
# Install wizard (Alpine + Tailwind static page; not part of Vite build)
# 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 \
@@ -38,6 +56,7 @@ COPY web/app/vendor/tailwindcss-browser.js \
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-v2/index.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 \
+4 -3
View File
@@ -12,7 +12,8 @@ upstream nexus_api {
# 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 100m;
client_max_body_size 500m;
client_body_timeout 600s;
location /api/ {
proxy_pass http://nexus_api;
@@ -21,8 +22,8 @@ location /api/ {
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 300s;
proxy_send_timeout 300s;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
proxy_connect_timeout 10s;
}
+54 -12
View File
@@ -1,18 +1,23 @@
# 1Panel + Docker 一键部署
> 完整知识文档:[docs/project/nexus-1panel-operations-knowledge.md](../docs/project/nexus-1panel-operations-knowledge.md)
> 完整知识文档:[docs/project/nexus-1panel-operations-knowledge.md](../docs/project/nexus-1panel-operations-knowledge.md)
> 巡检归档(2026-06-06):[docs/project/nexus-god-menu-audit-summary.md](../docs/project/nexus-god-menu-audit-summary.md)
## 脚本速查(公共仓库 · 无需令牌)
| 脚本 | 用途 | 一条命令 |
|------|------|----------|
| `quick-install.sh` | 仅装 Nexus1Panel 已就绪) | `curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/quick-install.sh" \| bash` |
| `quick-install.sh` | 仅装 Nexus1Panel 已就绪) | `curl -fsSL "https://axs.kuma1xn.vip/admin/Nexus/raw/branch/main/deploy/quick-install.sh" \| bash` |
| `install-1panel-docker.sh` | 重装服务器全链 | `curl -fsSL ".../install-1panel-docker.sh" \| bash` |
| `install-nexus-fresh.sh` | 全新空库安装 | `bash deploy/install-nexus-fresh.sh --skip-clone` |
| **`update.sh`** | **一键更新** | `curl -fsSL ".../update.sh" \| bash` |
| `nexus-1panel.sh` | 底层 install / upgrade | `bash deploy/nexus-1panel.sh upgrade` |
| `fix-1panel-mysql-grant.sh` | 修复 1045 nexus@Docker | `bash deploy/fix-1panel-mysql-grant.sh` |
| `test-1panel-redis.sh` | Redis 认证诊断 | `REDIS_PASS='...' bash deploy/test-1panel-redis.sh` |
| `verify-1panel-install-wizard.sh` | 一键验收(网络/cron/镜像) | `bash deploy/verify-1panel-install-wizard.sh` |
| `install_ops_cron.sh` | 宿主机 cron(巡检+备份) | `sudo bash deploy/install_ops_cron.sh` |
| `mysql_dump_to_file.sh` | MySQL 备份(Docker exec | 见 `db_backup.sh` / `nx` 菜单 `[9]` |
| `upgrade.sh` | 自动识别 Docker/Supervisor | `bash deploy/upgrade.sh` |
| **`nx`** | **统一运维菜单** | `nx` |
档位:`--profile 1c4g` · `2c8g`(默认)· `4c16g`
@@ -22,7 +27,7 @@
## 重装服务器(1Panel + Docker + Nexus
```bash
curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/install-1panel-docker.sh" | bash
curl -fsSL "https://axs.kuma1xn.vip/admin/Nexus/raw/branch/main/deploy/install-1panel-docker.sh" | bash
```
4 核 16G
@@ -40,7 +45,7 @@ curl -fsSL ".../install-1panel-docker.sh" | bash -s -- --skip-1panel
## 仅 Nexus Docker
```bash
curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/quick-install.sh" | bash
curl -fsSL "https://axs.kuma1xn.vip/admin/Nexus/raw/branch/main/deploy/quick-install.sh" | bash
```
安装完成后:`https://api.synaglobal.vip/app/install.html`
@@ -67,7 +72,7 @@ nx # → [4] 一键更新
远程:
```bash
curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/update.sh" | bash
curl -fsSL "https://axs.kuma1xn.vip/admin/Nexus/raw/branch/main/deploy/update.sh" | bash
curl -fsSL ".../update.sh" | bash -s -- --no-cache
```
@@ -87,13 +92,47 @@ nexus-fresh # 等同 install-nexus-fresh.sh
nexus-install # 等同 quick-install.sh
```
| 菜单 | 功能 |
|------|------|
| 菜单 / 子命令 | 功能 |
|---------------|------|
| `[1]` | 安装向导(档位 / 迁机 / skip-clone |
| `[2]` | 全新安装 → `/app/install.html` |
| `[4]` | **一键更新** |
| `[5]` | 重启 Nexus |
| `[3]` | 状态 + `/health` + install.html 归档检查 |
| `[4]` / `nx update` | **一键更新**(备份 + 重建镜像 + 升级后验收) |
| `[5]` | 重启 Nexus 容器 |
| `[6]` | 日志 |
| `[7]` | 启动 / 停止 Compose 栈 |
| `[8]` | 无缓存重建镜像 |
| `[9]` | 立即备份 MySQL |
| `[e]` / `nx cron` | 安装 health_monitor + db_backup crontab |
| `[v]` / `nx verify` | 运行 `verify-1panel-install-wizard.sh` |
---
## 三层守护(1Panel Docker
| 层 | 机制 | 说明 |
|----|------|------|
| Layer 1 | Compose `restart` + 容器 `healthcheck` | 容器崩溃自动拉起 |
| Layer 2 | Python `self_monitor`30s | Redis/MySQL/WebSocket 自检 |
| Layer 3 | 宿主机 `health_monitor.sh`cron | `/health` 连续失败 → `docker restart` + 可选 Telegram |
Layer 3 需**在宿主机**安装(容器内无法写 crontab):
```bash
sudo nx cron
# 或
sudo bash /opt/nexus/deploy/install_ops_cron.sh
```
MySQL 定时备份:每日 03:00 → `/var/backups/nexus/``docker exec` 进 1Panel MySQL 容器 dump**无需**宿主机 `mysqldump`)。
---
## 安装完成后
- 向导锁定后 `install.html`**`install.html.bak`**,外网 `/app/install.html` 返回 **404**(正常)
- 登录:`https://你的域名/app/`
- 验收:`sudo nx verify`
---
@@ -190,13 +229,16 @@ MySQL 密码在 1Panel 自建库时设定,向导步骤 3 填写。
## 安装向导验收(服务器执行)
**1Panel 终端**(root)一键检查网络、容器名预填、本地 `/health``/api/install/env-check`
**1Panel 终端**root)一键检查网络、容器名预填、cron、镜像版本、本地 `/health`
```bash
cd /opt/nexus && bash deploy/verify-1panel-install-wizard.sh
cd /opt/nexus && sudo nx verify
```
通过后再浏览器打开 `https://你的域名/app/install.html`常见修复:
**未安装**通过后再打开 `https://你的域名/app/install.html`
**已安装**`install.html` 应 HTTP 404;验收检查 crontab、`mysql_dump_to_file.sh`、容器内 round6/7 代码特性。
常见修复:
```bash
cd /opt/nexus && bash deploy/detect-1panel-services.sh
+272
View File
@@ -0,0 +1,272 @@
#!/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 "$@"
+44 -15
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env bash
# deploy-frontend.sh — Build Vuetify frontend and deploy to server
# deploy-frontend.sh — Build frontend v1 + app-v2 and deploy to server
#
# Docker 生产:前端在镜像内构建,远程执行 nexus-1panel.sh upgrade
# Supervisortar 上传到 web/app + supervisorctl restart
# Supervisortar 上传到 web/app + web/app-v2 + supervisorctl restart
#
# Usage: bash deploy/deploy-frontend.sh
@@ -19,21 +19,33 @@ echo "═══ Nexus Frontend Deploy ═══"
REMOTE_RUNTIME=$(ssh_cmd 'bash -s' <<'REMOTE'
set -euo pipefail
docker_cmd() {
if docker "$@" 2>/dev/null; then return 0; fi
sudo docker "$@" 2>/dev/null
}
compose_cmd() {
if docker compose "$@" 2>/dev/null; then return 0; fi
sudo docker compose "$@" 2>/dev/null
}
for d in /opt/nexus /www/wwwroot/api.synaglobal.vip; do
[[ -d "$d/server" ]] || continue
if [[ -f "$d/docker/.env.prod" ]] && docker compose version >/dev/null 2>&1; then
if [[ -f "$d/docker/.env.prod" ]] && compose_cmd version >/dev/null 2>&1; then
prof="$d/docker/.host-profile"
[[ -f "$prof" ]] || prof="$d/docker/profiles/2c8g.env"
if [[ -f "$prof" ]]; then
args=(-f "$d/docker/docker-compose.prod.yml")
docker network inspect 1panel-network >/dev/null 2>&1 && \
docker_cmd network inspect 1panel-network >/dev/null 2>&1 && \
[[ -f "$d/docker/docker-compose.1panel.yml" ]] && \
args+=(-f "$d/docker/docker-compose.1panel.yml")
q=$(cd "$d" && docker compose "${args[@]}" \
q=$(cd "$d" && compose_cmd "${args[@]}" \
--env-file "$d/docker/.env.prod" --env-file "$prof" ps -q nexus 2>/dev/null || true)
[[ -n "$q" ]] && echo "docker $d" && exit 0
fi
fi
if [[ -f "$d/deploy/nexus-1panel.sh" ]]; then
q=$(docker_cmd ps --format '{{.Names}}' 2>/dev/null | grep -E '^nexus-prod-nexus-' || true)
[[ -n "$q" ]] && echo "docker $d" && exit 0
fi
if command -v supervisorctl >/dev/null 2>&1 && supervisorctl status nexus >/dev/null 2>&1; then
echo "supervisor $d"
exit 0
@@ -49,16 +61,29 @@ DEPLOY_PATH="${REMOTE_RUNTIME#* }"
echo "Remote runtime: ${RUNTIME} @ ${DEPLOY_PATH}"
echo "▶ Building frontend (local verify)..."
echo "▶ Building frontend v1 (local verify)..."
cd frontend && npx vite build && cd ..
if [[ -d frontend-v2 ]]; then
echo "▶ Building frontend v2 (/app-v2, local verify)..."
(cd frontend-v2 && npm run build:safe)
test -f web/app-v2/index.html
fi
echo "✓ Build complete"
if [[ "$RUNTIME" == "docker" ]]; then
echo "▶ Docker 生产:重建镜像以包含新前端(非 tar 热更新)..."
ssh_cmd "cd ${DEPLOY_PATH} && sudo env NEXUS_ROOT=${DEPLOY_PATH} bash ${DEPLOY_PATH}/deploy/nexus-1panel.sh upgrade"
else
echo "▶ Packaging..."
tar czf /tmp/nexus-frontend.tar.gz -C web/app index.html assets/
echo "▶ Packaging web/app and web/app-v2..."
pkg_dir="$(mktemp -d)"
trap 'rm -rf "${pkg_dir}"' EXIT
mkdir -p "${pkg_dir}/app"
cp -a web/app/index.html web/app/assets "${pkg_dir}/app/"
if [[ -f web/app-v2/index.html && -d web/app-v2/assets ]]; then
mkdir -p "${pkg_dir}/app-v2"
cp -a web/app-v2/index.html web/app-v2/assets "${pkg_dir}/app-v2/"
fi
tar czf /tmp/nexus-frontend.tar.gz -C "${pkg_dir}" .
echo "✓ Package ready ($(du -h /tmp/nexus-frontend.tar.gz | cut -f1))"
echo "▶ Uploading to server..."
@@ -66,13 +91,15 @@ else
echo "✓ Upload complete"
echo "▶ Deploying on server..."
ssh_cmd "cd ${DEPLOY_PATH}/web/app && tar xzf /tmp/nexus-frontend.tar.gz && rm /tmp/nexus-frontend.tar.gz"
ssh_cmd "set -e; mkdir -p ${DEPLOY_PATH}/web; tar xzf /tmp/nexus-frontend.tar.gz -C ${DEPLOY_PATH}/web; rm /tmp/nexus-frontend.tar.gz"
echo "▶ Pruning orphaned assets..."
if ! ssh_cmd "python3 ${DEPLOY_PATH}/deploy/prune_frontend_assets.py --dry-run"; then
echo "✗ Prune dry-run failed"
exit 1
if [[ -f deploy/prune_frontend_assets.py ]]; then
if ! ssh_cmd "python3 ${DEPLOY_PATH}/deploy/prune_frontend_assets.py ${DEPLOY_PATH}/web/app --dry-run"; then
echo "✗ Prune dry-run failed"
exit 1
fi
ssh_cmd "python3 ${DEPLOY_PATH}/deploy/prune_frontend_assets.py ${DEPLOY_PATH}/web/app"
fi
ssh_cmd "python3 ${DEPLOY_PATH}/deploy/prune_frontend_assets.py"
echo "✓ Extracted and pruned"
echo "▶ Restarting backend..."
@@ -81,15 +108,17 @@ else
fi
sleep 3
HEALTH=$(ssh_cmd "curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8600/health" 2>/dev/null || echo "000")
HEALTH=$(ssh_cmd "curl -s http://127.0.0.1:8600/health" 2>/dev/null || echo "000")
SPA=$(ssh_cmd "curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8600/app/" 2>/dev/null || echo "000")
SPA_V2=$(ssh_cmd "curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8600/app-v2/" 2>/dev/null || echo "000")
echo ""
echo "═══ Verification ═══"
echo " /health → $HEALTH"
echo " /app/ → $SPA"
echo " /app-v2/ → $SPA_V2"
if [[ "$HEALTH" == "200" || "$HEALTH" == "ok" ]] && [[ "$SPA" == "200" ]]; then
if [[ "$HEALTH" == "200" || "$HEALTH" == "ok" ]] && [[ "$SPA" == "200" ]] && [[ "$SPA_V2" == "200" ]]; then
echo "✓ Deploy successful!"
else
echo "✗ Verification failed — check server logs"
+19 -5
View File
@@ -7,6 +7,7 @@
#
# Optional env:
# NEXUS_GITEA_TOKEN — if git pull needs auth
# NEXUS_GIT_REMOTE — override authenticated remote, default: https://axs.kuma1xn.vip/admin/Nexus.git
# SKIP_FRONTEND=1 — Supervisor 模式跳过热更新前端 tar
# NEXUS_DEPLOY_PATH — 覆盖自动检测路径
@@ -31,7 +32,9 @@ echo "Path: ${DEPLOY_PATH}"
echo "Runtime: ${RUNTIME}"
if [[ -n "${NEXUS_GITEA_TOKEN:-}" ]]; then
git remote set-url origin "http://admin:${NEXUS_GITEA_TOKEN}@66.154.115.8:3000/admin/Nexus.git"
GIT_REMOTE="${NEXUS_GIT_REMOTE:-https://axs.kuma1xn.vip/admin/Nexus.git}"
GIT_REMOTE_AUTH="${GIT_REMOTE/https:\/\//https://admin:${NEXUS_GITEA_TOKEN}@}"
git remote set-url origin "${GIT_REMOTE_AUTH}"
fi
if [[ "$RUNTIME" == "docker" ]]; then
@@ -49,11 +52,20 @@ supervisorctl restart nexus
if [[ -f /tmp/nexus-frontend.tar.gz && "${SKIP_FRONTEND:-0}" != "1" ]]; then
echo "▶ extract /tmp/nexus-frontend.tar.gz..."
tar xzf /tmp/nexus-frontend.tar.gz -C web/app
mkdir -p web/app web/app-v2
if tar tzf /tmp/nexus-frontend.tar.gz | grep -Eq '^(\./)?app/'; then
tar xzf /tmp/nexus-frontend.tar.gz -C web
else
# Backward compatibility for legacy archives containing index.html + assets/ at root.
tar xzf /tmp/nexus-frontend.tar.gz -C web/app --exclude=app-v2
if tar tzf /tmp/nexus-frontend.tar.gz | grep -Eq '^(\./)?app-v2/'; then
tar xzf /tmp/nexus-frontend.tar.gz -C web/app-v2 --strip-components=1 app-v2
fi
fi
rm -f /tmp/nexus-frontend.tar.gz
if [[ -f deploy/prune_frontend_assets.py ]]; then
python3 deploy/prune_frontend_assets.py --dry-run
python3 deploy/prune_frontend_assets.py
python3 deploy/prune_frontend_assets.py web/app --dry-run
python3 deploy/prune_frontend_assets.py web/app
fi
supervisorctl restart nexus
fi
@@ -61,10 +73,12 @@ fi
sleep 3
HEALTH=$(curl -s "http://127.0.0.1:${PORT}/health" || true)
SPA=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${PORT}/app/" || echo "000")
SPA_V2=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${PORT}/app-v2/" || echo "000")
echo " /health → ${HEALTH}"
echo " /app/ → ${SPA}"
echo " /app-v2/ → ${SPA_V2}"
if [[ "${HEALTH}" == "ok" && "${SPA}" == "200" ]]; then
if [[ "${HEALTH}" == "ok" && "${SPA}" == "200" && "${SPA_V2}" == "200" ]]; then
echo "✓ Server deploy OK"
suggest_ops_cron "$DEPLOY_PATH"
else
+117 -28
View File
@@ -1,22 +1,31 @@
#!/usr/bin/env bash
# Full production deploy: push (optional) + backend upgrade + frontend + health
# Full production deploy: local rsync + backend upgrade + frontend + health
#
# 自动识别远程 Docker/opt/nexus)或 Supervisor/www/wwwroot/...)运行时
# 默认从本机工作区 rsync 到生产机,不 push Gitea、不远程 git pull
#
# Prerequisites on YOUR machine:
# - SSH: ~/.ssh/config Host nexus OR export NEXUS_SSH="azureuser@20.24.218.235"
# - rsync, SSH 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
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
@@ -25,8 +34,15 @@ 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
@@ -36,22 +52,34 @@ fi
REMOTE_DETECT=$(ssh_cmd 'bash -s' <<'REMOTE'
set -euo pipefail
docker_cmd() {
if docker "$@" 2>/dev/null; then return 0; fi
sudo docker "$@" 2>/dev/null
}
compose_cmd() {
if docker compose "$@" 2>/dev/null; then return 0; fi
sudo docker compose "$@" 2>/dev/null
}
for d in /opt/nexus /www/wwwroot/api.synaglobal.vip; do
[[ -d "$d/server" ]] || continue
echo "ROOT=$d"
if [[ -f "$d/docker/.env.prod" ]] && docker compose version >/dev/null 2>&1; then
if [[ -f "$d/docker/.env.prod" ]] && compose_cmd version >/dev/null 2>&1; then
prof="$d/docker/.host-profile"
[[ -f "$prof" ]] || prof="$d/docker/profiles/2c8g.env"
if [[ -f "$prof" ]]; then
args=(-f "$d/docker/docker-compose.prod.yml")
docker network inspect 1panel-network >/dev/null 2>&1 && \
docker_cmd network inspect 1panel-network >/dev/null 2>&1 && \
[[ -f "$d/docker/docker-compose.1panel.yml" ]] && \
args+=(-f "$d/docker/docker-compose.1panel.yml")
q=$(cd "$d" && docker compose "${args[@]}" \
q=$(cd "$d" && compose_cmd "${args[@]}" \
--env-file "$d/docker/.env.prod" --env-file "$prof" ps -q nexus 2>/dev/null || true)
[[ -n "$q" ]] && echo RUNTIME=docker && exit 0
fi
fi
if [[ -f "$d/deploy/nexus-1panel.sh" ]] && command -v nx >/dev/null 2>&1; then
q=$(docker_cmd ps --format '{{.Names}}' 2>/dev/null | grep -E '^nexus-prod-nexus-' || true)
[[ -n "$q" ]] && echo RUNTIME=docker && exit 0
fi
if command -v supervisorctl >/dev/null 2>&1 && supervisorctl status nexus >/dev/null 2>&1; then
echo RUNTIME=supervisor
exit 0
@@ -76,39 +104,100 @@ fi
echo "Deploy path: ${DEPLOY_PATH}"
echo "Runtime: ${RUNTIME}"
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"
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}"
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"
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
fi
else
echo "ERROR: 无法识别远程运行时(非 Docker 也非 Supervisor" >&2
echo " 请手动: ssh ${NEXUS_SSH_HOST} 'sudo nx update'" >&2
exit 1
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
fi
if [[ "$RUNTIME" == "docker" || "$RUNTIME" == "supervisor" ]]; then
echo "▶ Frontend: local vite build + upload to host web/app and web/app-v2..."
cd frontend && npx vite build && cd ..
if [[ -d frontend-v2 ]]; then
(cd frontend-v2 && npm run build:safe)
test -f web/app-v2/index.html
fi
pkg_dir="$(mktemp -d)"
trap 'rm -rf "${pkg_dir}"' EXIT
mkdir -p "${pkg_dir}/app"
cp -a web/app/index.html web/app/assets "${pkg_dir}/app/"
if [[ -f web/app-v2/index.html && -d web/app-v2/assets ]]; then
mkdir -p "${pkg_dir}/app-v2"
cp -a web/app-v2/index.html web/app-v2/assets "${pkg_dir}/app-v2/"
fi
tar czf /tmp/nexus-frontend.tar.gz -C "${pkg_dir}" .
scp_cmd /tmp/nexus-frontend.tar.gz "${NEXUS_SSH_HOST}:/tmp/nexus-frontend.tar.gz"
ssh_cmd "set -e; sudo mkdir -p ${DEPLOY_PATH}/web; sudo tar xzf /tmp/nexus-frontend.tar.gz -C ${DEPLOY_PATH}/web; rm /tmp/nexus-frontend.tar.gz; sudo chown -R ${DEPLOY_CHOWN} ${DEPLOY_PATH}/web/app ${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..."
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 + web/app-v2 sync failed — run sync_webapp_to_container.sh on server manually" >&2
}
fi
sleep 3
PORT="$(ssh_cmd "grep -E '^NEXUS_PUBLISH_PORT=' ${DEPLOY_PATH}/docker/.env.prod 2>/dev/null | cut -d= -f2 | tr -d '\"' || echo 8600")"
PORT="$(ssh_cmd "grep -E '^NEXUS_PUBLISH_PORT=' ${DEPLOY_PATH}/docker/.env.prod 2>/dev/null | cut -d= -f2 | tr -d '\"'" 2>/dev/null || true)"
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}"
if [[ "${HEALTH}" == "ok" && "${SPA}" == "200" ]]; then
echo " /app-v2/ → ${SPA_V2}"
if [[ "${HEALTH}" == "ok" && "${SPA}" == "200" && "${SPA_V2}" == "200" ]]; then
echo "✓ Deploy successful"
ssh_cmd "command -v crontab >/dev/null && crontab -l 2>/dev/null | grep -q nexus-health-monitor" 2>/dev/null || \
echo " 提示: 远程未安装巡检 cron,可在服务器执行 sudo nx cron"
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"
else
echo "▶ Installing ops cron (health_monitor + db_backup)…"
if ssh_cmd "sudo env NEXUS_ROOT=${DEPLOY_PATH} bash ${DEPLOY_PATH}/deploy/install_ops_cron.sh" 2>/dev/null; then
echo "✓ Ops cron installed"
else
echo " 提示: 远程 cron 安装失败,可在服务器执行: sudo nx cron"
fi
fi
else
echo "✗ Verification failed — check logs on server" >&2
exit 1
+19
View File
@@ -128,3 +128,22 @@ suggest_ops_cron() {
echo " 建议: sudo bash ${root}/deploy/install_ops_cron.sh"
echo " 或: sudo nx cron"
}
prompt_ops_cron_if_missing() {
local root="$1"
if nexus_has_ops_cron; then
echo "[INFO] Crontab 已含 health_monitor / db_backup"
return 0
fi
suggest_ops_cron "$root"
if [[ ! -t 0 ]]; then
return 0
fi
local ans
read -r -p "现在安装巡检/备份 cron? [Y/n]: " ans
if [[ "$ans" == "n" || "$ans" == "N" ]]; then
echo "[WARN] 已跳过 cron 安装"
return 0
fi
bash "${root}/deploy/install_ops_cron.sh"
}
+7 -6
View File
@@ -1,16 +1,17 @@
#!/usr/bin/env bash
# 下载并执行 install-nexus-fresh.sh(公共仓库无需令牌)
#
# curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/download-install-fresh.sh" | bash
# curl -fsSL "https://axs.kuma1xn.vip/admin/Nexus/raw/branch/main/deploy/download-install-fresh.sh" | bash
#
# 推荐直接使用:
# curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/quick-install.sh" | bash
# curl -fsSL "https://axs.kuma1xn.vip/admin/Nexus/raw/branch/main/deploy/quick-install.sh" | bash
set -euo pipefail
NEXUS_GITEA_HOST="${NEXUS_GITEA_HOST:-66.154.115.8:3000}"
NEXUS_GITEA_HOST="${NEXUS_GITEA_HOST:-axs.kuma1xn.vip}"
NEXUS_GITEA_SCHEME="${NEXUS_GITEA_SCHEME:-https}"
NEXUS_GIT_BRANCH="${NEXUS_GIT_BRANCH:-main}"
NEXUS_RAW_BASE="${NEXUS_RAW_BASE:-http://${NEXUS_GITEA_HOST}/admin/Nexus/raw/branch/${NEXUS_GIT_BRANCH}}"
NEXUS_RAW_BASE="${NEXUS_RAW_BASE:-${NEXUS_GITEA_SCHEME}://${NEXUS_GITEA_HOST}/admin/Nexus/raw/branch/${NEXUS_GIT_BRANCH}}"
OUT="${OUT:-/tmp/install-nexus-fresh.sh}"
RED='\033[0;31m'
@@ -26,7 +27,7 @@ done
download() {
local url base
for base in "${NEXUS_RAW_BASE}" "http://${NEXUS_GITEA_HOST}/admin/Nexus/raw/${NEXUS_GIT_BRANCH}"; do
for base in "${NEXUS_RAW_BASE}" "${NEXUS_GITEA_SCHEME}://${NEXUS_GITEA_HOST}/admin/Nexus/raw/${NEXUS_GIT_BRANCH}"; do
url="${base}/deploy/install-nexus-fresh.sh"
if curl -fsSL "$url" -o "$OUT" 2>/dev/null && head -1 "$OUT" | grep -q '^#!/'; then
ok "已下载 install-nexus-fresh.sh"
@@ -35,7 +36,7 @@ download() {
fi
done
if [[ -n "${NEXUS_GITEA_TOKEN:-}" ]]; then
url="http://${NEXUS_GITEA_HOST}/api/v1/repos/admin/Nexus/raw/deploy/install-nexus-fresh.sh?ref=${NEXUS_GIT_BRANCH}"
url="${NEXUS_GITEA_SCHEME}://${NEXUS_GITEA_HOST}/api/v1/repos/admin/Nexus/raw/deploy/install-nexus-fresh.sh?ref=${NEXUS_GIT_BRANCH}"
if curl -fsSL -H "Authorization: token ${NEXUS_GITEA_TOKEN}" "$url" -o "$OUT" 2>/dev/null \
&& head -1 "$OUT" | grep -q '^#!/'; then
ok "已下载(API + 令牌)"
File diff suppressed because it is too large Load Diff
+204 -19
View File
@@ -9,12 +9,64 @@ set -euo pipefail
DEPLOY_DIR="${NEXUS_DEPLOY_DIR:-/opt/nexus}"
NEXUS_SERVICE="${NEXUS_SERVICE:-nexus}"
FAIL_FILE="/tmp/nexus_health_fail_count"
LAST_RESTART_FILE="/tmp/nexus_health_last_restart"
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"
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)"
@@ -25,7 +77,7 @@ PUBLISH_PORT="$(resolve_publish_port)"
HEALTH_URL="http://127.0.0.1:${PUBLISH_PORT}/health"
resolve_nexus_container() {
docker ps --format '{{.Names}}' 2>/dev/null | grep -E 'nexus-prod-nexus|nexus-nexus-1' | head -1 || true
docker_cmd ps --format '{{.Names}}' 2>/dev/null | grep -E 'nexus-prod-nexus|nexus-nexus-1' | head -1 || true
}
read_env_var() {
@@ -33,6 +85,47 @@ read_env_var() {
grep -E "^${key}=" "$file" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '\r"' || true
}
normalize_env_value() {
local v="${1:-}"
v="${v//$'\r'/}"
if [[ ${#v} -ge 2 && ${v:0:1} == '"' && ${v: -1} == '"' ]]; then
v="${v:1:-1}"
v="${v//\\n/$'\n'}"
v="${v//\\\"/\"}"
v="${v//\\\\/\\}"
fi
echo "$v"
}
load_telegram_from_db() {
local cname="$1"
[[ -n "$cname" ]] || return 1
[[ -n "${BOT_TOKEN:-}" && -n "${CHAT_ID:-}" ]] && return 0
local out token chat
out="$(docker_cmd exec "$cname" python3 -c "
import asyncio
from server.config import settings as s
from server.infrastructure.database.session import AsyncSessionLocal
async def main():
async with AsyncSessionLocal() as db:
await s.load_settings_from_db(db)
t = (s.TELEGRAM_BOT_TOKEN or '').strip()
c = (s.TELEGRAM_CHAT_ID or '').strip()
if t and c:
print(t)
print(c)
asyncio.run(main())
" 2>/dev/null)" || true
[[ -n "$out" ]] || return 1
token="$(printf '%s\n' "$out" | sed -n '1p')"
chat="$(printf '%s\n' "$out" | sed -n '2p')"
[[ -n "$token" && -n "$chat" ]] || return 1
BOT_TOKEN="$token"
CHAT_ID="$chat"
}
load_telegram_config() {
local cname="$1" dotenv
BOT_TOKEN=""
@@ -53,38 +146,52 @@ 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 exec "$cname" grep -E '^NEXUS_TELEGRAM_' /app/.env 2>/dev/null || true)
done < <(docker_cmd 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
BOT_TOKEN="$(normalize_env_value "$BOT_TOKEN")"
CHAT_ID="$(normalize_env_value "$CHAT_ID")"
}
send_telegram() {
[[ -n "${BOT_TOKEN:-}" && -n "${CHAT_ID:-}" ]] || return 1
curl -sf --max-time 10 "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
local msg="${1}" 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
resp="$(curl -s --max-time 15 -w '\n%{http_code}' \
"https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
-d "chat_id=${CHAT_ID}" \
-d "text=$1" \
-d "parse_mode=HTML" \
> /dev/null 2>&1
--data-urlencode "text=${msg}" \
-d "parse_mode=HTML" 2>/dev/null)" || return 1
http="${resp##*$'\n'}"
if [[ "$http" != "200" ]]; then
echo "$(date -Is 2>/dev/null || date) telegram send failed http=${http}" >> "$log" 2>/dev/null || true
return 1
fi
}
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))
echo "$fail" > "$FAIL_FILE"
write_state_file "$FAIL_FILE" "$fail" || true
) 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))
echo "$fail" > "$FAIL_FILE"
write_state_file "$FAIL_FILE" "$fail" || true
fi
echo "$fail"
}
reset_fail_count() {
echo "0" > "$FAIL_FILE"
write_state_file "$FAIL_FILE" "0" || true
}
restart_cooldown_active() {
@@ -97,13 +204,13 @@ restart_cooldown_active() {
}
mark_restart_attempt() {
date +%s > "$LAST_RESTART_FILE"
write_state_file "$LAST_RESTART_FILE" "$(date +%s)" || true
}
restart_nexus() {
local cname="$1" via=""
if [[ -n "$cname" ]]; then
docker restart "$cname" >/dev/null 2>&1 && via="Docker 容器 ${cname}"
docker_cmd 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
@@ -112,8 +219,86 @@ 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)"
if curl -sf --max-time 5 "$HEALTH_URL" > /dev/null 2>&1; then
reset_fail_count
@@ -126,21 +311,21 @@ if [[ "$FAIL" -lt "$MAX_FAIL" ]]; then
fi
if restart_cooldown_active; then
send_telegram "🔴 <b>Nexus /health 连续失败</b>\n冷却期内跳过自动重启(${RESTART_COOLDOWN_SEC}s\n请检查: docker logs ${NEXUS_CONTAINER:-nexus}" || true
telegram_health_fail_cooldown
exit 0
fi
send_telegram "🔴 <b>Nexus 连续 ${MAX_FAIL} 次无响应</b>\n正在自动重启…" || true
telegram_health_fail_restart
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
send_telegram "🟢 <b>Nexus 已恢复</b>\n${VIA} 重启成功" || true
telegram_health_recovered "$VIA"
reset_fail_count
else
send_telegram "🔴 <b>Nexus 重启后仍未恢复</b>\n${VIA} — 需人工介入" || true
telegram_health_still_down "$VIA"
fi
else
send_telegram "🔴 <b>Nexus 无法自动重启</b>\n未找到 Docker 容器或 Supervisor 服务" || true
telegram_health_restart_failed
fi
+5 -4
View File
@@ -4,7 +4,7 @@
#
# 适用:全新 VPS / 重装系统后,root 一条命令装完整栈。
#
# curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/install-1panel-docker.sh" | bash
# curl -fsSL "https://axs.kuma1xn.vip/admin/Nexus/raw/branch/main/deploy/install-1panel-docker.sh" | bash
#
# 4 核 16G + 自定义域名:
# curl -fsSL ".../install-1panel-docker.sh" | bash -s -- --profile 4c16g --domain api.example.com
@@ -23,9 +23,10 @@
set -euo pipefail
NEXUS_GITEA_HOST="${NEXUS_GITEA_HOST:-66.154.115.8:3000}"
NEXUS_GITEA_HOST="${NEXUS_GITEA_HOST:-axs.kuma1xn.vip}"
NEXUS_GITEA_SCHEME="${NEXUS_GITEA_SCHEME:-https}"
NEXUS_GIT_BRANCH="${NEXUS_GIT_BRANCH:-main}"
NEXUS_RAW_BASE="${NEXUS_RAW_BASE:-http://${NEXUS_GITEA_HOST}/admin/Nexus/raw/branch/${NEXUS_GIT_BRANCH}}"
NEXUS_RAW_BASE="${NEXUS_RAW_BASE:-${NEXUS_GITEA_SCHEME}://${NEXUS_GITEA_HOST}/admin/Nexus/raw/branch/${NEXUS_GIT_BRANCH}}"
NEXUS_ROOT="${NEXUS_ROOT:-/opt/nexus}"
NEXUS_DOMAIN="${NEXUS_DOMAIN:-api.synaglobal.vip}"
NEXUS_PROFILE="${NEXUS_PROFILE:-2c8g}"
@@ -233,7 +234,7 @@ download_fresh_installer() {
local name="install-nexus-fresh.sh" dest
dest="$(mktemp /tmp/install-nexus-fresh.XXXXXX.sh)"
local url base
for base in "${NEXUS_RAW_BASE}" "http://${NEXUS_GITEA_HOST}/admin/Nexus/raw/${NEXUS_GIT_BRANCH}"; do
for base in "${NEXUS_RAW_BASE}" "${NEXUS_GITEA_SCHEME}://${NEXUS_GITEA_HOST}/admin/Nexus/raw/${NEXUS_GIT_BRANCH}"; do
url="${base}/deploy/${name}"
if curl -fsSL "$url" -o "$dest" 2>/dev/null && head -1 "$dest" | grep -q '^#!/'; then
chmod +x "$dest"
+6 -5
View File
@@ -7,7 +7,7 @@
#
# 用法(公共仓库,root 下一条命令,参考 1Panel:
#
# curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/quick-install.sh" | bash
# curl -fsSL "https://axs.kuma1xn.vip/admin/Nexus/raw/branch/main/deploy/quick-install.sh" | bash
#
# 已 clone 到 /opt/nexus:
# cd /opt/nexus && bash deploy/install-nexus-fresh.sh --skip-clone
@@ -32,12 +32,13 @@ fi
# ── 默认配置(与 SECRETS.md / nexus-1panel 一致)──
NEXUS_ROOT="${NEXUS_ROOT:-/opt/nexus}"
NEXUS_GITEA_HOST="${NEXUS_GITEA_HOST:-66.154.115.8:3000}"
NEXUS_GITEA_HOST="${NEXUS_GITEA_HOST:-axs.kuma1xn.vip}"
NEXUS_GITEA_SCHEME="${NEXUS_GITEA_SCHEME:-https}"
NEXUS_GITEA_REPO="${NEXUS_GITEA_REPO:-admin/Nexus.git}"
NEXUS_DOMAIN="${NEXUS_DOMAIN:-api.synaglobal.vip}"
NEXUS_PROFILE="${NEXUS_PROFILE:-2c8g}"
NEXUS_GIT_BRANCH="${NEXUS_GIT_BRANCH:-main}"
NEXUS_RAW_BASE="${NEXUS_RAW_BASE:-http://66.154.115.8:3000/admin/Nexus/raw/branch/main}"
NEXUS_RAW_BASE="${NEXUS_RAW_BASE:-${NEXUS_GITEA_SCHEME}://${NEXUS_GITEA_HOST}/admin/Nexus/raw/branch/${NEXUS_GIT_BRANCH}}"
RED='\033[0;31m'
GREEN='\033[0;32m'
@@ -195,9 +196,9 @@ phase_packages() {
git_clone_url() {
if [[ -z "${NEXUS_GITEA_TOKEN:-}" ]]; then
echo "http://${NEXUS_GITEA_HOST}/${NEXUS_GITEA_REPO}"
echo "${NEXUS_GITEA_SCHEME}://${NEXUS_GITEA_HOST}/${NEXUS_GITEA_REPO}"
else
echo "http://admin:${NEXUS_GITEA_TOKEN}@${NEXUS_GITEA_HOST}/${NEXUS_GITEA_REPO}"
echo "${NEXUS_GITEA_SCHEME}://admin:${NEXUS_GITEA_TOKEN}@${NEXUS_GITEA_HOST}/${NEXUS_GITEA_REPO}"
fi
}
+1 -1
View File
@@ -416,7 +416,7 @@ server {
}
# Upload limit
client_max_body_size 100m;
client_max_body_size 500m;
# Well-known for SSL cert validation
location ~ \\.well-known {
+2 -2
View File
@@ -64,8 +64,8 @@ done
touch "$HEALTH_LOG" "$BACKUP_LOG" 2>/dev/null || true
chmod 644 "$HEALTH_LOG" "$BACKUP_LOG" 2>/dev/null || true
HEALTH_LINE="* * * * * NEXUS_DEPLOY_DIR=${NEXUS_ROOT} ${DEPLOY_DIR}/health_monitor.sh >> ${HEALTH_LOG} 2>&1 # ${HEALTH_MARKER}"
BACKUP_LINE="0 3 * * * NEXUS_ROOT=${NEXUS_ROOT} ${DEPLOY_DIR}/db_backup.sh >> ${BACKUP_LOG} 2>&1 # ${BACKUP_MARKER}"
HEALTH_LINE="* * * * * NEXUS_DEPLOY_DIR=${NEXUS_ROOT} bash ${DEPLOY_DIR}/health_monitor.sh >> ${HEALTH_LOG} 2>&1 # ${HEALTH_MARKER}"
BACKUP_LINE="0 3 * * * NEXUS_ROOT=${NEXUS_ROOT} bash ${DEPLOY_DIR}/db_backup.sh >> ${BACKUP_LOG} 2>&1 # ${BACKUP_MARKER}"
existing="$(crontab -l 2>/dev/null || true)"
filtered="$(printf '%s\n' "$existing" | grep -v "$HEALTH_MARKER" | grep -v "$BACKUP_MARKER" | grep -v 'deploy/health_monitor.sh' | grep -v 'deploy/db_backup.sh' || true)"
+22 -17
View File
@@ -1,17 +1,22 @@
# 复制: 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'
# 复制: cp deploy/nexus-1panel.secrets.sh.example deploy/nexus-1panel.secrets.sh
# 用于 git pushscripts/git-push.sh);clone/pull 仍可匿名
# NEXUS_GITEA_HOST='axs.kuma1xn.vip' # HTTPS Gitea
# NEXUS_GITEA_SCHEME='https'
# 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'
+60 -12
View File
@@ -2,7 +2,7 @@
# Nexus 6.0 — 1Panel + Docker 一键安装 / 升级(参数已全部内置,无需 export)
#
# 新机 1Panel 终端一条命令:
# curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/nexus-1panel.sh" | bash -s install
# curl -fsSL "https://axs.kuma1xn.vip/admin/Nexus/raw/branch/main/deploy/nexus-1panel.sh" | bash -s install
#
# 已 clone 到 /opt/nexus:
# cd /opt/nexus && bash deploy/nexus-1panel.sh install --skip-clone
@@ -23,7 +23,8 @@ set -euo pipefail
# 内置路径/域名(无敏感信息,可提交 Git)
# ═══════════════════════════════════════════════════════════════════
NEXUS_ROOT="${NEXUS_ROOT:-/opt/nexus}"
NEXUS_GITEA_HOST="${NEXUS_GITEA_HOST:-66.154.115.8:3000}"
NEXUS_GITEA_HOST="${NEXUS_GITEA_HOST:-axs.kuma1xn.vip}"
NEXUS_GITEA_SCHEME="${NEXUS_GITEA_SCHEME:-https}"
NEXUS_GITEA_REPO="${NEXUS_GITEA_REPO:-admin/Nexus.git}"
NEXUS_DOMAIN="${NEXUS_DOMAIN:-api.synaglobal.vip}"
NEXUS_PROFILE="${NEXUS_PROFILE:-2c8g}"
@@ -50,6 +51,7 @@ 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}"
@@ -181,15 +183,20 @@ 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-)"
overflow="$(grep -E '^NEXUS_DB_MAX_OVERFLOW=' "$prof" | cut -d= -f2-)"
pool="$(grep -E '^NEXUS_DB_POOL_SIZE=' "$prof" | cut -d= -f2- || true)"
overflow="$(grep -E '^NEXUS_DB_MAX_OVERFLOW=' "$prof" | cut -d= -f2- || true)"
[[ -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"
}
has_1panel_network() {
docker network inspect 1panel-network >/dev/null 2>&1
docker network inspect 1panel-network >/dev/null 2>&1 \
|| sudo docker network inspect 1panel-network >/dev/null 2>&1
}
upsert_env_var() {
@@ -363,6 +370,8 @@ keys = (
"NEXUS_ENCRYPTION_KEY",
"NEXUS_REDIS_URL",
"NEXUS_DATABASE_URL",
"NEXUS_TELEGRAM_BOT_TOKEN",
"NEXUS_TELEGRAM_CHAT_ID",
)
prod = {}
for line in env_file.read_text(encoding="utf-8").splitlines():
@@ -553,7 +562,11 @@ parse_gitea_endpoint() {
GITEA_HOST="${hp%%:*}"
GITEA_PORT="${hp##*:}"
if [[ "$GITEA_HOST" == "$GITEA_PORT" || -z "$GITEA_PORT" ]]; then
GITEA_PORT=3000
case "${NEXUS_GITEA_SCHEME:-https}" in
http) GITEA_PORT=80 ;;
https) GITEA_PORT=443 ;;
*) GITEA_PORT=443 ;;
esac
fi
}
@@ -722,10 +735,11 @@ require_token() {
}
git_remote_url() {
local scheme="${NEXUS_GITEA_SCHEME:-https}"
if [[ -n "${NEXUS_GITEA_TOKEN:-}" ]]; then
echo "http://admin:${NEXUS_GITEA_TOKEN}@${NEXUS_GITEA_HOST}/${NEXUS_GITEA_REPO}"
echo "${scheme}://admin:${NEXUS_GITEA_TOKEN}@${NEXUS_GITEA_HOST}/${NEXUS_GITEA_REPO}"
else
echo "http://${NEXUS_GITEA_HOST}/${NEXUS_GITEA_REPO}"
echo "${scheme}://${NEXUS_GITEA_HOST}/${NEXUS_GITEA_REPO}"
fi
}
@@ -1015,10 +1029,14 @@ verify_health() {
}
ensure_repo() {
if [[ ! -d "$NEXUS_ROOT/.git" ]]; then
if [[ ! -d "$NEXUS_ROOT" ]]; 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
@@ -1135,8 +1153,12 @@ cmd_upgrade() {
load_saved_profile "$NEXUS_ROOT"
resolve_profile
check_ports_preflight "$NEXUS_ROOT"
git_fetch
show_pending || true
if [[ "$SKIP_GIT" == true ]]; then
info "跳过 Git 同步(本机 rsync 已推送代码)"
else
git_fetch
show_pending || true
fi
if [[ "$CHECK_ONLY" == true ]]; then
exit 0
@@ -1146,6 +1168,12 @@ 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
@@ -1157,6 +1185,7 @@ 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"
@@ -1203,6 +1232,12 @@ cmd_upgrade() {
docker image prune -f >/dev/null 2>&1 || true
fi
NEXUS_ROOT="$NEXUS_ROOT" bash "$NEXUS_ROOT/deploy/install-nx-cli.sh" 2>/dev/null || true
if [[ -x "$NEXUS_ROOT/deploy/sync_webapp_to_container.sh" ]]; then
step "同步 Git web/app 进容器(消除镜像 vite 缓存 hash 漂移)..."
NEXUS_PUBLISH_PORT="$port" NEXUS_ROOT="$NEXUS_ROOT" bash "$NEXUS_ROOT/deploy/sync_webapp_to_container.sh" || {
warn "web/app 同步失败,继续健康检查(请手动执行 sync_webapp_to_container.sh"
}
fi
if ! verify_health "$NEXUS_ROOT" "升级"; then
rollback_compose_upgrade "$NEXUS_ROOT" || true
exit 1
@@ -1211,7 +1246,19 @@ cmd_upgrade() {
if [[ -f "$NEXUS_ROOT/deploy/detect_deploy_runtime.sh" ]]; then
# shellcheck disable=SC1091
source "$NEXUS_ROOT/deploy/detect_deploy_runtime.sh"
suggest_ops_cron "$NEXUS_ROOT"
if [[ -t 0 ]]; then
prompt_ops_cron_if_missing "$NEXUS_ROOT"
else
suggest_ops_cron "$NEXUS_ROOT"
fi
fi
local vcname
vcname="$(nexus_container_name)"
if [[ -n "$vcname" ]] && nexus_container_has_install_lock "$vcname" \
&& [[ -x "$NEXUS_ROOT/deploy/verify-1panel-install-wizard.sh" ]]; then
step "升级后验收..."
NEXUS_ROOT="$NEXUS_ROOT" bash "$NEXUS_ROOT/deploy/verify-1panel-install-wizard.sh" || \
warn "验收脚本报告 FAIL — 见上方输出"
fi
}
@@ -1251,6 +1298,7 @@ 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 ;;
+1 -1
View File
@@ -75,5 +75,5 @@ server {
error_log {{LOG_DIR}}/{{SERVER_NAME}}_nexus.error.log;
# ── Upload limit ──
client_max_body_size 100m;
client_max_body_size 500m;
}
+1 -1
View File
@@ -109,5 +109,5 @@ server {
error_log {{LOG_DIR}}/{{SERVER_NAME}}.error.log;
# ── Upload limit ──
client_max_body_size 100m;
client_max_body_size 500m;
}
+11
View File
@@ -270,6 +270,7 @@ menu_main() {
[8] 重建镜像并启动(--build nexus
[9] 备份 MySQL
[e] 安装巡检/备份 cronhealth_monitor + db_backup
[v] 1Panel 安装/运维验收脚本
── 其它 ──
[a] 检查 Git 是否有更新
@@ -348,6 +349,11 @@ EOF
ops_install_cron
pause_enter
;;
v|V)
require_root
bash "${NX_DIR}/verify-1panel-install-wizard.sh"
pause_enter
;;
a|A)
require_root
cmd_check
@@ -413,6 +419,10 @@ nx_main() {
require_root
ops_install_cron
;;
verify)
require_root
bash "${NX_DIR}/verify-1panel-install-wizard.sh"
;;
-h|--help)
cat <<EOF
用法: nx [子命令]
@@ -423,6 +433,7 @@ nx_main() {
install-auto 非交互安装(默认 2c8g)
health 健康检查
cron 安装 health_monitor + db_backup crontab
verify 运行 1Panel 验收脚本
god / ops 已合并进主菜单(兼容别名)
+69 -20
View File
@@ -1,6 +1,6 @@
#!/bin/bash
# Nexus Pre-deploy Gate Check (v27 gates)
# 在部署前强制检查 7 道门控,任何一道不过则阻止部署
# Nexus Pre-deploy Gate Check (v38 gates)
# 在部署前强制检查 8 道门控,任何一道不过则阻止部署
#
# 门控1: CHANGELOG — docs/changelog/ 下必须有今天的 .md 文件(且行数≥10)
# 门控2: AUDIT — docs/audit/ 下必须有今天的审计记录(且包含关键段落)
@@ -9,6 +9,7 @@
# 门控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=至少一道门未过
@@ -22,7 +23,7 @@ DEPLOY_DIR="${NEXUS_DEPLOY_DIR:-${_REPO_ROOT}}"
TODAY=$(date +%Y-%m-%d)
TIMESTAMP=$(date -Iseconds)
GATES_PASSED=0
GATES_TOTAL=7
GATES_TOTAL=8
BLOCKED=0
GATE_RESULTS=""
@@ -53,14 +54,14 @@ gate_log() {
}
echo "========================================"
echo " Nexus Pre-deploy Gate Check (v2)"
echo " Nexus Pre-deploy Gate Check (v3)"
echo " Date: ${TODAY}"
echo " Gates: ${GATES_TOTAL}"
echo "========================================"
echo ""
# ── Gate 1: Changelog (存在 + 行数≥10) ──
echo -n "Gate 1/7: Changelog ... "
echo -n "Gate 1/8: 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)
@@ -86,7 +87,7 @@ else
fi
# ── Gate 2: Audit (存在 + 关键段落) ──
echo -n "Gate 2/7: Audit ... "
echo -n "Gate 2/8: 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)
@@ -117,7 +118,7 @@ else
fi
# ── Gate 3: Test (必须存在 + 必须通过) ──
echo -n "Gate 3/7: Test ... "
echo -n "Gate 3/8: Test ... "
TEST_SCRIPT="${DEPLOY_DIR}/tests/test_api.py"
if [ ! -f "${TEST_SCRIPT}" ]; then
echo -e "${RED}BLOCK${NC}"
@@ -126,15 +127,43 @@ if [ ! -f "${TEST_SCRIPT}" ]; then
BLOCKED=1
gate_log "test" "BLOCK" "script not found"
else
TEST_OUTPUT=$(cd "${DEPLOY_DIR}" && python3 "${TEST_SCRIPT}" 2>&1) || true
# Always hit local API for gate 3 (ignore shell NEXUS_TEST_BASE used by import/e2e scripts)
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
echo -e "${RED}BLOCK${NC}"
echo " └─ Tests failed. Output:"
echo "${TEST_OUTPUT}" | head -20 | sed 's/^/ │ /'
BLOCKED=1
gate_log "test" "BLOCK" "tests failed"
# 本机未起 :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
else
echo -e "${GREEN}PASS${NC}"
echo " └─ All tests passed"
@@ -144,7 +173,7 @@ else
fi
# ── Gate 4: Lint (ruff — F only: undefined names, unused imports) ──
echo -n "Gate 4/7: Lint ... "
echo -n "Gate 4/8: 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
@@ -171,7 +200,7 @@ else
fi
# ── Gate 5: Import (python -c "import server.main") ──
echo -n "Gate 5/7: Import ... "
echo -n "Gate 5/8: Import ... "
IMPORT_PYTHON=""
for p in "${DEPLOY_DIR}/.venv/bin/python3" "${DEPLOY_DIR}/venv/bin/python3"; do
if [ -x "${p}" ]; then
@@ -206,7 +235,7 @@ else
fi
# ── Gate 6: Security (bandit — HIGH only) ──
echo -n "Gate 6/7: Security ... "
echo -n "Gate 6/8: 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
@@ -233,18 +262,18 @@ else
fi
# ── Gate 7: Review (审计文件交叉验证) ──
echo -n "Gate 7/7: Review ... "
echo -n "Gate 7/8: 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)
# 检查审计文件中是否列出了实际改动的文件
REVIEW_OK=1
# 如果是git仓库,交叉验证改动文件
if command -v git &>/dev/null && [ -d "${DEPLOY_DIR}/.git" ]; then
CHANGED_FILES=$(cd "${DEPLOY_DIR}" && git diff --name-only HEAD~1 2>/dev/null || true)
CHANGED_FILES=$(cd "${DEPLOY_DIR}" && git diff --name-only HEAD~1..HEAD 2>/dev/null || true)
for CF in ${CHANGED_FILES}; do
# 跳过非代码文件
# 跳过非代码文件与门控运行日志
case "${CF}" in
docs/*|CLAUDE.md|*.md) continue ;;
docs/*|CLAUDE.md|*.md|deploy/gate_log.jsonl|web/app/assets/*) continue ;;
esac
# 检查审计文件是否提到了这个文件
if ! grep -q "$(basename "${CF}")" "${FILE}" 2>/dev/null; then
@@ -272,6 +301,26 @@ 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
@@ -0,0 +1,197 @@
#!/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 "$@"
+8 -7
View File
@@ -3,10 +3,10 @@
# Nexus 6.0 — 公共仓库一键安装入口(参考 1Panel quick_start.sh
#
# root 登录后一条命令:
# curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/quick-install.sh" | bash
# curl -fsSL "https://axs.kuma1xn.vip/admin/Nexus/raw/branch/main/deploy/quick-install.sh" | bash
#
# 或保存后执行:
# curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/quick-install.sh" -o /tmp/quick-install.sh
# curl -fsSL "https://axs.kuma1xn.vip/admin/Nexus/raw/branch/main/deploy/quick-install.sh" -o /tmp/quick-install.sh
# bash /tmp/quick-install.sh
#
# 可选参数会传给 install-nexus-fresh.sh,例如:
@@ -15,11 +15,12 @@
set -euo pipefail
NEXUS_GITEA_HOST="${NEXUS_GITEA_HOST:-66.154.115.8:3000}"
NEXUS_GITEA_HOST="${NEXUS_GITEA_HOST:-axs.kuma1xn.vip}"
NEXUS_GITEA_SCHEME="${NEXUS_GITEA_SCHEME:-https}"
NEXUS_GITEA_REPO="${NEXUS_GITEA_REPO:-admin/Nexus.git}"
NEXUS_GIT_BRANCH="${NEXUS_GIT_BRANCH:-main}"
NEXUS_ROOT="${NEXUS_ROOT:-/opt/nexus}"
NEXUS_RAW_BASE="${NEXUS_RAW_BASE:-http://${NEXUS_GITEA_HOST}/admin/Nexus/raw/branch/${NEXUS_GIT_BRANCH}}"
NEXUS_RAW_BASE="${NEXUS_RAW_BASE:-${NEXUS_GITEA_SCHEME}://${NEXUS_GITEA_HOST}/admin/Nexus/raw/branch/${NEXUS_GIT_BRANCH}}"
RED='\033[0;31m'
GREEN='\033[0;32m'
@@ -57,7 +58,7 @@ download_script() {
local url base
for base in \
"${NEXUS_RAW_BASE}" \
"http://${NEXUS_GITEA_HOST}/admin/Nexus/raw/${NEXUS_GIT_BRANCH}"; do
"${NEXUS_GITEA_SCHEME}://${NEXUS_GITEA_HOST}/admin/Nexus/raw/${NEXUS_GIT_BRANCH}"; do
url="${base}/deploy/${name}"
if curl -fsSL "$url" -o "$dest" 2>/dev/null && validate_script_file "$dest"; then
info "已下载 ${name}"
@@ -68,9 +69,9 @@ download_script() {
}
run_via_git() {
local url="http://${NEXUS_GITEA_HOST}/${NEXUS_GITEA_REPO}"
local url="${NEXUS_GITEA_SCHEME}://${NEXUS_GITEA_HOST}/${NEXUS_GITEA_REPO}"
if [[ -n "${NEXUS_GITEA_TOKEN:-}" ]]; then
url="http://admin:${NEXUS_GITEA_TOKEN}@${NEXUS_GITEA_HOST}/${NEXUS_GITEA_REPO}"
url="${NEXUS_GITEA_SCHEME}://admin:${NEXUS_GITEA_TOKEN}@${NEXUS_GITEA_HOST}/${NEXUS_GITEA_REPO}"
fi
step "改用 git clone..."
mkdir -p "$(dirname "$NEXUS_ROOT")"
+104
View File
@@ -0,0 +1,104 @@
#!/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='node_modules'
--exclude='.venv'
--exclude='venv'
--exclude='__pycache__'
--exclude='.cursor'
--exclude='.install_locked'
--exclude='.install_state.json'
--exclude='web/app/assets'
--exclude='tmp'
--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})"
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env bash
# Sync Git-tracked web/app and web/app-v2 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.
#
# Usage (on production host after git pull / upgrade):
# NEXUS_ROOT=/opt/nexus bash deploy/sync_webapp_to_container.sh
#
# Called automatically from deploy/nexus-1panel.sh cmd_upgrade and deploy-production.sh.
set -euo pipefail
NEXUS_ROOT="${NEXUS_ROOT:-/opt/nexus}"
CONTAINER="${NEXUS_CONTAINER:-}"
NEXUS_PUBLISH_PORT="${NEXUS_PUBLISH_PORT:-8600}"
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; }
docker_cmd() {
if docker "$@" 2>/dev/null; then return 0; fi
sudo docker "$@" 2>/dev/null
}
resolve_container() {
if [[ -n "$CONTAINER" ]]; then
return 0
fi
CONTAINER="$(docker_cmd ps --format '{{.Names}}' | grep -E 'nexus-prod-nexus|nexus-nexus-1' | head -1 || true)"
if [[ -z "$CONTAINER" ]]; then
error "未找到运行中的 Nexus 容器(可设 NEXUS_CONTAINER=名称)"
exit 1
fi
info "容器: $CONTAINER"
}
verify_entry_bundle() {
local prefix html bundle_path code
prefix="$1"
html="$(curl -sf "http://127.0.0.1:${NEXUS_PUBLISH_PORT}${prefix}/" 2>/dev/null || true)"
bundle_path="$(printf '%s' "$html" | grep -oE "${prefix}"'/assets/index-[^"]+\.js' | head -1 || true)"
if [[ -z "$bundle_path" ]]; then
error "${prefix}/ 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 "主 bundle ${bundle_path} → HTTP ${code}"
exit 1
fi
info "入口校验 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
fi
if [[ ! -d "${app}/assets" ]]; then
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
info "同步 web/app → 容器 /app/web/app ..."
docker_cmd exec "$CONTAINER" rm -rf /app/web/app/assets
docker_cmd exec "$CONTAINER" mkdir -p /app/web/app
docker_cmd cp "${app}/index.html" "${CONTAINER}:/app/web/app/index.html"
docker_cmd cp "${app}/assets" "${CONTAINER}:/app/web/app/assets"
if [[ -f "${app}/favicon.ico" ]]; then
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 || \
warn "容器内 prune 跳过(脚本或路径不可用)"
fi
verify_entry_bundle "/app"
verify_entry_bundle "/app-v2"
info "web/app 与 web/app-v2 同步完成"
}
main "$@"
+1 -1
View File
@@ -118,4 +118,4 @@ echo ""
info "当前 Nexus 栈:"
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}' | grep -i nexus || docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
echo ""
info "下一步:自建 MySQL 数据库与用户,安装向导步骤 3 使用 host.docker.internal:3306"
info "下一步:1Panel 应用商店安装 MySQL → nx update 探测容器名 → 向导步骤 3 使用 1Panel-mysql-xxxx"
+3 -5
View File
@@ -109,8 +109,6 @@ else
fi
echo ""
info "下一步:在宿主机或 1Panel 安装 Redis,确认监听 6379"
echo " apt install -y redis-server && systemctl enable --now redis-server"
echo " redis-cli ping # 期望 PONG"
echo ""
info "Docker 内 Nexus 连接宿主机 Redis:安装向导 / .env 使用 host.docker.internal:6379"
info "下一步:1Panel 应用商店安装 Redis → nx update 写入 NEXUS_1PANEL_REDIS_HOST"
echo " 向导 Redis URLredis://:密码@1Panel-redis-xxxx:6379/0(无 root 用户)"
echo " 诊断:bash deploy/test-1panel-redis.sh"
+1 -1
View File
@@ -7,7 +7,7 @@
#
# 远程一条命令(推荐在仓库目录执行,管道模式会回退 /opt/nexus:
# cd /opt/nexus && bash deploy/update.sh
# curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/update.sh" | bash
# curl -fsSL "https://axs.kuma1xn.vip/admin/Nexus/raw/branch/main/deploy/update.sh" | bash
#
set -euo pipefail
+41 -2
View File
@@ -299,9 +299,9 @@ import json,sys
d=json.load(sys.stdin)
print((d.get('docker_defaults') or {}).get('db_host',''))
" 2>/dev/null || true)"
if [[ -n "$db_host" && "$db_host" != host.docker.internal ]]; then
if [[ -n "$db_host" && "$db_host" != "host.docker.internal" ]]; then
pass "步骤 3 预填 db_host=$db_host"
elif [[ "$db_host" == host.docker.internal ]]; then
elif [[ "$db_host" == "host.docker.internal" ]]; then
fail "步骤 3 仍预填 host.docker.internal"
info "修复: nx update --no-cache(需 b25d079+"
fi
@@ -315,6 +315,44 @@ print((d.get('docker_defaults') or {}).get('db_host',''))
fi
}
check_container_image_features() {
[[ -n "${NEXUS_CONTAINER:-}" ]] || return 0
local host_sha py_ok html_file html_ok host_root_ok
host_sha="$(git -C "$NEXUS_ROOT" rev-parse --short HEAD 2>/dev/null || echo unknown)"
pass "宿主机 Git: $host_sha"
if docker exec "$NEXUS_CONTAINER" grep -q '_host_deploy_root' /app/server/api/install.py 2>/dev/null; then
pass "容器 install.py 含 host_deploy_rootround6+"
py_ok=1
else
warn "容器 install.py 较旧 — 需 nx update 重建镜像"
fi
html_file="/app/web/app/install.html"
if docker exec "$NEXUS_CONTAINER" test -f /app/web/app/install.html.bak 2>/dev/null; then
html_file="/app/web/app/install.html.bak"
fi
if docker exec "$NEXUS_CONTAINER" grep -q 'install_ops_cron' "$html_file" 2>/dev/null; then
pass "容器 install.html 含 Layer 3 cron 指引(round7+"
html_ok=1
else
warn "容器 install.html 较旧 — 需 nx update 重建镜像"
fi
host_root_ok="$(docker exec "$NEXUS_CONTAINER" printenv NEXUS_HOST_ROOT 2>/dev/null || true)"
if [[ -n "$host_root_ok" ]]; then
pass "NEXUS_HOST_ROOT=$host_root_ok"
else
warn "容器未注入 NEXUS_HOST_ROOT — nx update 重建后可修复"
fi
if [[ -n "${py_ok:-}" && -n "${html_ok:-}" ]]; then
info "镜像与宿主机代码特性对齐(重建后验收)"
else
info "修复: cd $NEXUS_ROOT && sudo nx update"
fi
}
check_openresty_hint() {
local domain code
domain="$(grep -E '^NEXUS_API_BASE_URL=' "$ENV_PROD" 2>/dev/null | sed 's|.*https\?://||;s|/.*||' || echo api.synaglobal.vip)"
@@ -376,6 +414,7 @@ main() {
check_install_api || true
check_ops_cron || true
check_mysql_backup || true
check_container_image_features || true
check_openresty_hint || true
print_next_steps
+6
View File
@@ -29,5 +29,11 @@ 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
+15 -3
View File
@@ -19,6 +19,7 @@
| [project/alert-push-policy.md](project/alert-push-policy.md) | 告警策略 |
| [project/deploy.md](project/deploy.md) | 部署补充(宝塔/裸机) |
| [project/nexus-1panel-operations-knowledge.md](project/nexus-1panel-operations-knowledge.md) | **1Panel + Docker 运维知识 SSOT** |
| [project/nexus-god-menu-audit-summary.md](project/nexus-god-menu-audit-summary.md) | **nx 上帝菜单巡检归档**2026-06-06 round110 |
| [project/production-verification-checklist.md](project/production-verification-checklist.md) | L5 清单 |
根目录 [AGENTS.md](../AGENTS.md) · [CLAUDE.md](../CLAUDE.md) · [.cursorrules](../.cursorrules) 为薄入口。
@@ -48,7 +49,18 @@
---
## 4. 设计与标准
## 4. 测试(全站用例 SSOT
| 文档 | 说明 |
|------|------|
| [testing/test-case-matrix.md](testing/test-case-matrix.md) | **152 条** UT/IT/CH/E2E/MCP 编号用例 |
| [testing/mcp-browser-playbook.md](testing/mcp-browser-playbook.md) | Agent Playwright MCP 点页规程 |
| [design/specs/2026-06-08-full-site-testing-strategy-design.md](design/specs/2026-06-08-full-site-testing-strategy-design.md) | 四层金字塔设计 |
| [design/plans/2026-06-08-full-site-testing-strategy.md](design/plans/2026-06-08-full-site-testing-strategy.md) | 分阶段实施计划 |
---
## 5. 设计与标准
| 目录 | 说明 |
|------|------|
@@ -59,13 +71,13 @@
---
## 5. 归档区
## 6. 归档区
**[archive/README.md](archive/README.md)** — 2026-06 审计合并卷、旧 handoff、agentmemory、2026-05 审查报告。
---
## 6. 工具脚本
## 7. 工具脚本
| 脚本 | 用途 |
|------|------|
@@ -0,0 +1,69 @@
# 审计 — 2026-06-07 BL-06 Agent per-server key 强制
## 范围
移除 Agent 认证 global `API_KEY` legacy 回退;涉及 3 个后端文件 + 1 个新测试文件。
| 文件 | 行数 | Read 范围 |
|------|------|-----------|
| `server/api/agent.py` | 379 | L40-93(认证函数) |
| `server/api/servers.py` | 2056 | L619-636, L1625-1640(安装链路) |
| `server/application/services/script_service.py` | 735 | L44-50(回调 key |
| `tests/test_agent_per_server_key.py` | 88 | 全文 |
| `tests/load_test.py` | 202 | 顶部 docstring |
| `tests/quick_load.py` | 87 | 顶部 docstring |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | Agent 认证 fail-closed | FIXED 移除 global 回退;无 per-server key → False |
| H2 | 安装链路静默降级 | FIXED 无 key 明确 400/批量错误项 |
| H3 | 长任务回调 key 泄露面 | FIXED 仅用 per-server key,无 global 回退 |
| H4 | compare_digest 时序安全 | SAFE 保留 `secrets.compare_digest` |
| H5 | DB 查服异常 | SAFE 保持 fail-closed return False |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1 | FIXED | `agent.py:60-82``agent_api_key` 匹配 |
| H2 | FIXED | `servers.py:627-633`, `servers.py:1635-1637` |
| H3 | FIXED | `script_service.py:44-50` ValueError |
| H4 | SAFE | `agent.py:82` compare_digest |
| H5 | SAFE | `agent.py:68-76` except → False |
## 入口表(agent.py 受影响路由)
| 方法 | 路径 | 鉴权 |
|------|------|------|
| POST | `/api/agent/heartbeat` | X-API-Key + per-server `agent_api_key` |
| POST | `/api/agent/script-callback` | 同上 + job secret |
## 巡检补修(同日)
| 项 | 原问题 | 修复 |
|----|--------|------|
| H6 | `receive_heartbeat` 认证后再次 `get_server`(重复查库) | `_verify_server_api_key` 返回 `Optional[Server]`handler 复用 |
| H7 | 验 key 通过后 `unknown_server` discarded 分支为死代码 | 删除;不存在/无 key/错 key 统一 401 |
| H8 | `script-callback` docstring 仍写「per-server or global」 | 改为仅 per-server |
| H9 | 无回归测试约束 `get_server` 调用次数 | `test_verify_server_key_single_get_server_per_auth_check` + `assert_called_once_with` |
## 生产只读探测(2026-06-07
| 探测 | 结果 |
|------|------|
| `GET /health` | 200 |
| `POST /api/agent/heartbeat` 假 key | 401 |
| `security_probe_external.sh` heartbeat/WS | PASS |
注:BL-06 完整语义(global key 不可冒充)需部署本地改动后生产才生效;当前生产假 key 已 401。
## DoD
- [x] ruff 零错误(改动文件)
- [x] import server.main
- [x] pytest `test_agent_per_server_key.py` 9 passed(含 `assert_called_once`
- [x] changelog ≥10 行
- [x] 无静默吞错;无 global key 降级
- [x] 心跳路径单次 `get_server`(巡检补修)
@@ -0,0 +1,67 @@
# 审计 — 2026-06-07 ce-code-review 跟进 + BL-07 探测
## 范围
本批次 `git diff HEAD~1`**BL-06**(已提交)与 **ce-code-review 跟进**(工作区)。BL-06 细读见 `2026-06-07-bl06-agent-per-server-key.md`
| 文件 | 变更要点 |
|------|----------|
| `server/api/servers.py` | BL-06:安装链路无 global key 回退 |
| `server/application/services/script_service.py` | BL-06:回调仅 per-server key |
| `tests/test_agent_per_server_key.py` | BL-06:认证单元测试 |
| `tests/load_test.py` | BL-06:文档注释 |
| `tests/quick_load.py` | BL-06:文档注释 |
| `web/agent/agent.py` | 心跳 401 退出循环(对齐 BL-06 中心端) |
| `server/api/install.py` | 路由级 lock Depends;归档先于写 lock;归档失败 fail-closed |
| `.cursor/rules/nexus-security.mdc` | Agent 认证矩阵:仅 per-server key |
| `scripts/security_probe_external.sh` | curl `shift` 修复;`resolve_ws_python`WS 403 → PASS |
| `docs/changelog/2026-06-07-deploy-ssh-secrets.md` | 本机 SSH secrets 文档 |
| `docs/changelog/2026-06-07-probe-curl-parse-fix.md` | 探测脚本 curl/WS 修复 |
| `docs/reports/2026-06-07-bl07-deploy-verification.md` | 生产部署验证报告 |
| `tests/test_install_locked_404.py` | create-admin / test-credentials 锁定 404router Depends 断言 |
| `tests/test_security_unit.py` | `_reject_if_locked` 期望 404 |
| `deploy/install-nx-cli.sh` | CRLF → LF(门控 pre-flight |
| `deploy/update.sh` | CRLF → LF |
| `deploy/gate_log.jsonl` | 门控运行记录(自动生成) |
| `deploy/deploy-production.sh` | source `nexus-1panel.secrets.sh` 加载 SSH |
| `deploy/nexus-1panel.secrets.sh.example` | SSH 变量占位 |
| `docs/project/linux-dev-paths.md` | 本机 SSH/部署 SSOT |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | 边端 agent 401 不重试 | FIXED `agent.py` 401 → return |
| H2 | 锁定 install 统一 404 | FIXED `install.py` router `Depends(_raise_not_found_if_locked)` |
| H3 | 归档失败仍 lock 成功 | FIXED 归档先于 lockOSError → RuntimeError → 500 |
| H4 | 安全 SSOT 与 BL-06 漂移 | FIXED `nexus-security.mdc` |
| H5 | 外网 WS 403 误判反代 | FIXED 探测脚本 PASS + 报告澄清 |
| H6 | 探测 curl `shift 3` 误报 FAIL | FIXED `probe()` shift 2/3 + 独立 code 文件 |
| H7 | WS 探测 SKIP(无 websockets | FIXED `resolve_ws_python()` 自动 venv |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1 | FIXED | `web/agent/agent.py` `elif resp.status_code == 401: return` |
| H2 | FIXED | `install.py` `APIRouter(..., dependencies=[Depends(...)])` |
| H3 | FIXED | `_finalize_install_lock``_archive_install_wizard_html` |
| H4 | FIXED | `nexus-security.mdc` 认证矩阵 |
| H5 | FIXED | `security_probe_external.sh` WS 403 PASS |
| H6 | FIXED | `probe()` `shift 2` + `code_file` |
| H7 | FIXED | `resolve_ws_python()` |
## 验证
| 项 | 结果 |
|----|------|
| `bash scripts/local_verify.sh` | 全绿(smoke 47 + test_api 26/26 + ruff |
| `pytest tests/test_install_locked_404.py` | 11 passed |
| `deploy/pre_deploy_check.sh` | 待本审计入库后重跑 |
## DoD
- [x] 无静默吞错(归档失败 error + 500
- [x] changelog `2026-06-07-code-review-followups.md`
- [x] 本地 L2b `local_verify` 证据
- [x] 涉及文件列入本审计范围表
@@ -0,0 +1,51 @@
# 审计 — 2026-06-07 文件上传 + Layer3 巡检脚本 + 生产对齐部署
## 范围
本批次 `git diff HEAD~1`commit `9337f37`)及部署前修复:
| 文件 | 变更要点 |
|------|----------|
| `deploy/health_monitor.sh` | 引号剥离 `normalize_env_value`Telegram 中文告警 |
| `frontend/src/composables/files/useFilesActions.ts` | FormData 字段 `files``file` |
| `server/api/sync_v2.py` | 接受 `file`/`files`;多文件;`_sftp_upload_one` |
| `tests/test_sync_upload_form.py` | 上传字段解析单测 |
| `server/api/auth.py` | 改密 `audit_repo` 定义顺序(F821 修复) |
| `deploy/gate_log.jsonl` | 门控运行记录(自动生成,非业务逻辑) |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | 上传字段前后端一致 | FIXED `sync_v2` + `useFilesActions` |
| H2 | 多文件不静默丢 | FIXED `_collect_multipart_upload_files` |
| H3 | Layer3 Telegram token 引号 | FIXED `normalize_env_value` |
| H4 | 改密会话失效 + 审计 | FIXED `audit_repo` 先于 `AuthService` |
| H5 | 无静默吞错 | PASS SFTP/HTTP 错误向上抛出 |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1 | FIXED | `test_sync_upload_form.py` |
| H2 | FIXED | `sync_v2.py` 多文件循环 |
| H3 | FIXED | 生产演练 Telegram 收到 |
| H4 | FIXED | `auth.py` ruff F821 清零 |
| H5 | PASS | 无 bare except |
## 验证
| 项 | 结果 |
|----|------|
| `pytest tests/test_sync_upload_form.py -q` | pass |
| `bash scripts/local_verify.sh` | 待 ruff 修复后重跑 |
| Layer3 演练 | 用户确认 Telegram 收到 |
| `deploy/pre_deploy_check.sh` | 本审计入库后重跑 |
## DoD
- [x] changelog `2026-06-07-files-upload-form-field-fix.md`
- [x] 涉及文件列入本审计范围表
- [x] 生产 Layer3 终验通过
- [ ] 前端 vite build + docker upgrade
- [ ] 批量升级 Agent
@@ -0,0 +1,17 @@
# 审计 — 2026-06-07 登录客户端 IP 反代识别
| 文件 | 变更要点 |
|------|----------|
| `server/utils/client_ip.py` | 可信反代跳读取 X-Real-IP / XFF 末段 |
| `server/api/auth.py` | login/refresh/change_password 用 get_client_ip |
| `tests/test_client_ip.py` | 单测 |
| `deploy/gate_log.jsonl` | 门控记录 |
## Step 3
| H | 结论 |
|---|------|
| H1 | 公网直连忽略伪造 X-Real-IP | PASS |
| H2 | 172.18.0.1 仅在内网跳信任转发头 | PASS |
Step3✓ Closure✓ DoD✓
@@ -0,0 +1,44 @@
# 审计 — Layer 3 宿主机巡检 Telegram 打通
## 范围
| 文件 | 变更 |
|------|------|
| `server/infrastructure/env_file.py` | `.env` upsert/readUTF-8 LF |
| `server/application/services/ops_patrol_sync.py` | Telegram → `/app/.env`、persist 卷、host `.env.prod` |
| `server/api/settings.py` | `set_setting` 钩子;`GET/POST ops-patrol/*` |
| `deploy/health_monitor.sh` | `load_telegram_from_db` + flock 修复 + **Telegram 引号剥离** |
| `deploy/deploy-production.sh` | 部署成功自动 `install_ops_cron.sh` |
| `deploy/nexus-1panel.sh` | `sync_env_prod_to_volume` 含 Telegram 键 |
| `frontend/src/pages/SettingsPage.vue` | Layer 3 巡检状态与同步 UI |
| `frontend/src/types/api.ts` | `OpsPatrol*` 类型 |
| `tests/test_ops_patrol_sync.py` | 7 用例 |
| `docs/changelog/2026-06-07-ops-patrol-layer3-telegram.md` | changelog |
| `docs/design/plans/2026-06-07-ops-patrol-layer3-telegram.md` | 技术说明 |
| `deploy/gate_log.jsonl` | 门控记录(自动生成) |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | Layer-3 与设置页 Telegram 脱节 | FIXED — 保存时 sync + DB 回退 |
| H2 | `.env` 写入破坏不可变键 | SAFE — 仅 upsert `NEXUS_TELEGRAM_*` |
| H3 | 宿主机 cron 未安装 | FIXED — deploy 自动 install_ops_cron |
| H4 | env 同步失败静默 | SAFE — 记录 warningDB 回退保底 |
| H5 | 敏感 Token 泄露至日志 | SAFE — 无 log token;审计仅写布尔标志 |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1H5 | FIXED / SAFE | 代码 + pytest 7/7 |
## DoD
- [x] pytest `test_ops_patrol_sync.py` + `test_settings_telegram.py` 13 passed
- [x] changelog / audit / plan
- [x] 无 SECRET_KEY/API_KEY/ENCRYPTION_KEY/DATABASE_URL 变更
## 验证
`.venv/bin/pytest tests/test_ops_patrol_sync.py tests/test_settings_telegram.py -q`
@@ -0,0 +1,28 @@
# 审计 — 2026-06-07 Refresh Token Redis+MySQL 双写
| 文件 | 变更要点 |
|------|----------|
| `server/domain/models/__init__.py` | AdminRefreshToken ORM |
| `server/infrastructure/database/refresh_token_repo.py` | MySQL 持久层 |
| `server/application/services/auth_service.py` | 先 Redis 后 MySQLmembership/rotate 双端 |
| `server/application/services/refresh_token_hydration.py` | 启动回填 |
| `server/main.py` | hydration 调用 |
| `server/api/dependencies.py` | repo 注入 |
| `server/api/auth.py` | 改密双端清 token |
| `server/infrastructure/database/migrations.py` | admin_refresh_tokens 表 |
| `tests/test_refresh_token_persistence.py` | 单测 |
| `deploy/gate_log.jsonl` | 门控记录 |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | 仅存 SHA-256 hash | PASS |
| H2 | Redis 丢失 MySQL 回退 | PASS |
| H3 | 改密双端清 token | PASS |
## Closure
- Step3✓ — 双写 + hydration + 回退
- Closure✓ — 满足「Redis 先写、MySQL 持久、重启不丢会话」
- DoD✓ — pytest 6 passed;待生产 Redis 重启验证
@@ -0,0 +1,35 @@
# 审计 — 设置页 IP 白名单订阅恢复
## 范围
| 文件 | 变更 |
|------|------|
| `frontend/src/pages/SettingsPage.vue` | 订阅 URL、解析预览、节点展示、正确 save payload |
| `frontend/src/types/api.ts` | `SubscriptionParseResponse``IpAllowlistSaveRequest` |
| `deploy/install_ops_cron.sh` | 同日前序 commitcron bash 调用) |
| `deploy/gate_log.jsonl` | 门控记录 |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | 后端 API 未删 | PASS — `parse-subscription` / `IpAllowlistSaveRequest` 仍在 |
| H2 | SSRF 仍由后端拦截 | PASS — 未改 `settings.py` |
| H3 | 错误 payload 修复 | FIXED — 不再 POST `{ ips }` |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1 | PASS | `server/api/settings.py` |
| H2 | PASS | 解析仍走服务端 |
| H3 | FIXED | `saveAllowlist` payload |
## DoD
- [x] vite build 通过
- [x] changelog / audit
## 验证
设置页 → 登录 IP 白名单 → 订阅 URL + 解析预览 + 保存
@@ -0,0 +1,43 @@
# 审计 — 设置页 Telegram Chat ID 检测恢复
## 范围
| 文件 | 变更 |
|------|------|
| `frontend/src/pages/SettingsPage.vue` | 恢复「检测 Chat ID」按钮与选择对话框 |
| `frontend/src/types/api.ts` | `TelegramChatsResponse` 类型 |
| `deploy/install_ops_cron.sh` | crontab 改用 `bash` 调用(避免 +x 丢失) |
| `deploy/deploy-production.sh` | `PORT` 缺省 8600(同日前序 commit |
| `deploy/gate_log.jsonl` | 门控记录(自动生成) |
## 结论
- 后端 `GET /settings/telegram/chats` 未删,仅 Vue 设置页遗漏 UI
- 无 API 契约变更;检测前若 Token 草稿存在则先 PUT 保存
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | Chat ID 检测 API 仍存在 | PASS — `GET /settings/telegram/chats` 未改 |
| H2 | Vue 设置页功能对等 | FIXED — 恢复检测按钮与对话框 |
| H3 | Token 未保存时检测 | FIXED — 检测前自动 PUT token 草稿 |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1 | PASS | `server/api/settings.py` `telegram_get_chats` |
| H2 | FIXED | `SettingsPage.vue` `detectTelegramChats` |
| H3 | FIXED | 检测前 `http.put telegram_bot_token` |
## DoD
- [x] 前端 vite build 通过
- [x] changelog / audit 已写
- [x] 无 API 破坏性变更
## 验证
- `cd frontend && npx vite build` 通过
- 设置页:Bot 收消息后检测 → 列表 → 选中写入 Chat ID
@@ -0,0 +1,37 @@
# 审计 — 设置页 Telegram 全流程修复
## 范围
| 文件 | 变更 |
|------|------|
| `frontend/src/pages/SettingsPage.vue` | persist/save/test/detect、8 NOTIFY 开关、reveal 免密 |
| `frontend/src/types/api.ts` | `NOTIFY_TOGGLE_ITEMS` |
| `server/api/settings.py` | Webhook 清除、错误详情、reveal 免密 |
| `tests/test_settings_telegram.py` | 6 用例 |
| `deploy/gate_log.jsonl` | 门控记录(自动生成) |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | 测试/检测前持久化 Token+Chat ID | FIXED — `persistTelegramConfig` |
| H2 | getUpdates Webhook 冲突 | FIXED — `deleteWebhook` before poll |
| H3 | Telegram API 错误可见 | FIXED — `ok:false` / sendMessage description |
| H4 | 8 项 NOTIFY 开关 UI | FIXED — Vue v-switch + PUT |
| H5 | reveal 免密(用户确认) | FIXED — JWT + 审计保留 |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1H5 | FIXED | 代码 + pytest 6/6 |
## DoD
- [x] pytest `test_settings_telegram.py` 6 passed
- [x] changelog / audit
- [x] 无不可变 key 变更
## 验证
`.venv/bin/pytest tests/test_settings_telegram.py -q`
@@ -0,0 +1,29 @@
# 审计 — 2026-06-07 快速添加 SSH 主机密钥 + 错误中文化
## 范围
| 文件 | 变更要点 |
|------|----------|
| `server/infrastructure/ssh/asyncssh_pool.py` | `try_ssh_login` 探测 `known_hosts=None`SSH 错误中文化 |
| `server/utils/sync_error_message.py` | asyncssh 主机密钥/认证失败等英→中 |
| `server/config.py` | `SSH_STRICT_HOST_CHECKING` 默认 `false` |
| `server/application/services/credential_poller.py` | `format_poll_errors` 中文预设类型 |
| `frontend/src/pages/ServersPage.vue` | 失败弹窗 system→系统 |
| `tests/test_sync_error_message.py` | 翻译断言 |
| `tests/test_security_unit.py` | 脱敏中文占位符对齐(同批次工作区) |
| `deploy/gate_log.jsonl` | 门控记录 |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | 凭据探测不得静默吞错 | PASS — 错误写入 pending + 中文返回 |
| H2 | 主机密钥:运维平台可跳过探测 | PASS — 仅 `try_ssh_login` 跳过;pool 仍读 `SSH_STRICT` |
| H3 | 无明文密钥 | PASS — 无凭据变更 |
| H4 | 默认 strict false 与 install true 冲突 | ACCEPT — install `.env` 显式写 true 仍可覆盖 |
## Closure
- Step3✓ — 8 步扫描无 BLOCK
- Closure✓ — 根因(HostKeyNotVerifiable)与修复一致
- DoD✓ — pytest 17 passed;待生产快速添加验证
@@ -0,0 +1,36 @@
# 审计 — Telegram 通知全中文详细化
## 范围
| 文件 | 变更 |
|------|------|
| `server/infrastructure/telegram/__init__.py` | 消息模板 |
| `server/background/self_monitor.py` | Redis/MySQL |
| `server/api/agent.py` | 时钟偏差 |
| `server/api/websocket.py` | server_id |
| `server/api/settings.py` | 测试消息 |
| `deploy/health_monitor.sh` | Layer 3 四类消息 |
| `deploy/gate_log.jsonl` | 门控记录(自动生成) |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | 无密钥/URL 明文 | SAFE — sanitize 保留 |
| H2 | HTML escape | SAFE |
| H3 | NOTIFY 开关仍生效 | SAFE |
## Closure
| H | 判定 |
|---|------|
| H1H3 | SAFE |
## DoD
- [x] changelog / audit
- [x] pytest 通过
## 验证
`.venv/bin/pytest tests/test_settings_telegram.py -q`
@@ -0,0 +1,26 @@
# 审计 — Agent 401 停心跳(A-05
**Changelog**: `docs/changelog/2026-06-08-agent-401-stop-heartbeat.md`
## 变更文件
`heartbeat_policy.py` · `agent.py` · `install.sh` · `servers.py` · `server_batch_common.py` · `server_batch_service.py` · `agent_version.py` · `test_agent_heartbeat_stop.py`
## Step 3
| 项 | 结论 |
|----|------|
| 401 / discarded 后 `return` 退出循环 | PASS |
| 5xx 仍退避重试,非静默吞错 | PASS |
| config reload 重启已 done 的心跳任务 | PASS |
| 子机 rollout 依赖 upgrade-agent(文档已写明) | PASS |
## Closure
Gate 7/7 PASS · 待部署 + 子机批量升级验证
## DoD
- [x] `heartbeat_should_stop` 单测
- [x] `get_central_agent_version` 仍读 2.0.0
- [ ] 生产抽样子机 journalctl 401 后无重复心跳(#7 rollout 后)
@@ -0,0 +1,111 @@
# 审计 — 北京时间展示统一 + 资源恢复 Telegram 去重(2026-06-08
**Changelog**:
- `docs/changelog/2026-06-08-beijing-time-display-unified.md`
- `docs/changelog/2026-06-08-telegram-recovery-dedup-timezone.md`
**触发原因**: Bug 修复(恢复通知秒级重复、Telegram/UI 时间非北京时间)+ 展示层时区统一
## 审计范围
| 文件 | 行数 | 状态 |
|------|------|------|
| `server/utils/display_time.py` | 27 | ☑ 已审 |
| `server/infrastructure/telegram/__init__.py` | ~320 | ☑ 已审(`_now_cn` 段) |
| `server/background/ip_allowlist_refresh.py` | ~100 | ☑ 已审(刷新时间) |
| `server/api/settings.py` | ~1570 | ☑ 已审(Telegram 测试消息) |
| `server/api/agent.py` | ~385 | ☑ 已审(恢复去重) |
| `frontend/src/utils/datetime.ts` | 85 | ☑ 已审 |
| `frontend/src/utils/status.ts` | 36 | ☑ 已审 |
| `frontend/src/composables/push/labels.ts` | 68 | ☑ 已审 |
| `frontend/src/composables/useWebSocket.ts` | ~190 | ☑ 已审 |
| `frontend/src/pages/AuditPage.vue` | ~196 | ☑ 已审 |
| `frontend/src/pages/AlertsPage.vue` | ~190 | ☑ 已审 |
| `frontend/src/pages/CommandsPage.vue` | ~393 | ☑ 已审 |
| `frontend/src/pages/RetriesPage.vue` | ~180 | ☑ 已审 |
| `frontend/src/pages/SchedulesPage.vue` | ~533 | ☑ 已审 |
| `frontend/src/pages/ScriptsPage.vue` | ~1315 | ☑ 已审(时间列) |
| `frontend/src/pages/ScriptRunsPage.vue` | ~681 | ☑ 已审 |
| `frontend/src/pages/ServersPage.vue` | ~1400 | ☑ 已审(导出文件名) |
| `frontend/src/components/credentials/CredentialsManager.vue` | ~436 | ☑ 已审 |
| `frontend/src/components/servers/ServerInlineDetail.vue` | ~158 | ☑ 已审 |
| `tests/test_display_time.py` | 18 | ☑ 已审 |
| `tests/test_telegram_time_format.py` | 20 | ☑ 已审 |
| `tests/test_agent_recovery_dedup.py` | 42 | ☑ 已审 |
## 审计8步结果
### Step 1: 登记 ✅
两项用户反馈:① Telegram 恢复时间显示 UTC;② 同一指标 1 秒内连发多条恢复通知。
### Step 2: 全文 Read ✅
上述文件全文走读;`broadcast_recovery` / `websocket.py` 交叉确认恢复无 Telegram cooldown 设计意图(恢复应即时推送,根因在 Redis 成员重复)。
### Step 3: 规则扫描 H
| ID | 规则 | 命中点 | 初判 |
|----|------|--------|------|
| H1 | 静默吞错 | `agent.py` Redis 告警跟踪 `except: debug` | SAFE — 已有模式,告警主路径仍广播 |
| H2 | 时区一致性 | `parseApiDateTime` 假定 naive = UTC | SAFE — 与 `sync_log_repo`、ORM `_utcnow` 一致 |
| H3 | 去重完整性 | Redis legacy `mem:92` 多条 | SAFE — `_detect_recovery` 按 metric 去重 + srem 全清 |
| H4 | XSS / 注入 | 前端 `formatDateTimeBeijing` 仅格式化 | SAFE — Vue 文本插值,无 `v-html` |
| H5 | 密钥泄露 | `display_time` / `datetime.ts` | SAFE — 无凭据 |
| H6 | CUD 审计 | 无新 CUD 路径 | N/A |
| H7 | 多 worker | 恢复 Telegram 仍无 Redis 锁 | LOW — 单心跳内重复已根治;跨 worker 同毫秒双 POST 概率极低,接受 |
### Step 4: Closure表
| H | 判定 | 依据 |
|---|------|------|
| H1 | SAFE | 跟踪失败不影响 `broadcast_alert`;日志可见 |
| H2 | SAFE | DB/API 约定 UTC;文档与 changelog 已说明 |
| H3 | SAFE | 单测 `test_detect_recovery_dedupes_legacy_metric_value_entries` |
| H4 | SAFE | 展示层数值格式化 |
| H5 | SAFE | — |
| H6 | N/A | — |
| H7 | ACCEPT | 根因修复;若后续再现可加 `recovery_sent:{id}:{metric}` SETNX |
### Step 5: 入口表
| 入口 | 变更 |
|------|------|
| `POST /api/agent/heartbeat` | Redis `alerts:{id}` 成员格式、恢复检测 |
| Telegram 出站 | `_now_cn``format_beijing_now` |
| `POST .../telegram/test` | 测试消息时间 |
| 前端各列表页 | 只读展示,无新 API |
### Step 6: 输入→Sink
- Agent `system_info` → 阈值比较 → Redis set / `broadcast_recovery` → Telegram:无新 sink;去重降低 Telegram 频率。
- API `created_at` 字符串 → `parseApiDateTime``Intl` 北京时间:只读展示。
### Step 7: 归类
```
display_time.py 27行 2H 0 FINDING
agent.py (recovery) ~50行 3H 0 FINDING
datetime.ts 85行 2H 0 FINDING
前端 10 文件 ~40行 1H 0 FINDING
测试 3 文件 80行 0H 0 FINDING
──────────────────────────────────────────
总计 8H 0 FINDING1 ACCEPT
```
### Step 8: DoD ✅
- [x] 恢复通知同一指标单次心跳最多 1 条 Telegram
- [x] Telegram / 审计页 / 告警页等绝对时间为北京时间
- [x] DB 仍存 UTC,未改调度/漂移计算
- [x] `pytest` 7 项通过(`test_display_time` / `test_telegram_time_format` / `test_agent_recovery_dedup`
- [x] `npm run type-check` 通过
- [ ] 生产部署与浏览器终验(待用户批准 deploy)
## FINDING 列表
无。H7 为风险接受项,非 FINDING。
## 结论
**审计通过,0 FINDING,可进入部署门控**(需 commit + `pre_deploy_check.sh` + 前端 build
@@ -0,0 +1,25 @@
# 审计 — 仪表盘服务器列表不显示
**Changelog**: `docs/changelog/2026-06-08-dashboard-server-list-fix.md`
## 变更文件
`frontend/src/composables/useServerPagination.ts` · `frontend/src/pages/DashboardPage.vue` · `web/app/index.html`
## Step 3
| 项 | 结论 |
|----|------|
| loading 静默刷新后清除 | PASS |
| 去掉 skeleton v-if 卡死 | PASS |
| v-card 结构 | PASS |
| 搜索/30s 刷新 | PASS |
## Closure
frontend build PASS · 待部署
## DoD
- [x] 仪表盘服务器表格可见
- [ ] 生产浏览器验收
@@ -0,0 +1,46 @@
# 审计 — 生产部署批次(WebSSH ANSI + 服务器未设路径 + 命令栏单色)
**Changelog**:
- `docs/changelog/2026-06-08-webssh-xterm-ansi-output-fix.md`
- `docs/changelog/2026-06-08-servers-unset-path-inline-detail.md`
- `docs/changelog/2026-06-08-terminal-cmdbar-monochrome.md`
## 变更文件
| 模块 | 文件 |
|------|------|
| WebSSH relay | `server/api/webssh.py` |
| 终端解码 | `frontend/src/utils/terminalPayload.ts` |
| 终端会话 | `frontend/src/composables/terminal/useTerminalSessions.ts` |
| 终端页 | `frontend/src/pages/TerminalPage.vue` |
| 命令栏 | `frontend/src/components/terminal/TerminalCmdBar.vue` |
| WebSSH 单测 | `tests/test_webssh_terminal_payload.py` |
| 路径判定 | `server/utils/posix_paths.py` |
| 服务器仓储 | `server/infrastructure/database/server_repo.py` |
| 服务器 API | `server/api/servers.py` |
| 前端路径 util | `frontend/src/utils/serverTargetPath.ts` |
| 分页 composable | `frontend/src/composables/useServerPagination.ts` |
| 行内详情 | `frontend/src/components/servers/ServerInlineDetail.vue` |
| 未设路径面板 | `frontend/src/components/servers/ServerUnsetPathPanel.vue` |
| 服务器页 | `frontend/src/pages/ServersPage.vue` |
| 路径过滤单测 | `tests/test_server_target_path_filter.py` |
## Step 3
| 项 | 结论 |
|----|------|
| xterm ANSI 在 SSH 输出区 | PASSPTY env + data_b64 |
| 命令栏单色 | PASS`.sh-*` 统一 on-surface |
| `target_path_unset` 默认 None | PASS |
| 双表 refresh / 行内详情 | PASS |
## Closure
Gate 7/7 PASS · pytest + local_verify + build · 生产部署
## DoD
- [x] WebSSH xterm ANSI
- [x] 命令栏无彩色
- [x] 服务器未设路径区块 + 行内详情
- [x] 单测与门控
@@ -0,0 +1,27 @@
# 审计 — 全量对齐部署(batch 回收 / sync purge / agent 跳过)
**Changelog**: `docs/changelog/2026-06-08-full-align-deploy.md`
## 变更文件
`main.py` · `servers.py` · `server_batch_common.py` · `server_batch_service.py` · `server_batch_store.py` · `server_batch_job_repo.py` · `sync_log_repo.py` · `server_batch_reconcile.py` · `sync_log_purge.py` · `agent_version.py` · `test_agent_version.py` · `test_server_batch_reconcile.py` · `test_sync_log_purge.py`
## Step 3
| 项 | 结论 |
|----|------|
| 启动回收仅 primary worker,无静默吞错 | PASS |
| sync_logs purge 批量 DELETEretention=30d | PASS |
| 批量安装跳过:版本 SSOT 来自 agent.pySSH 探测有超时 | PASS |
| install 跳过不计入失败,lower 版本仍执行 install.sh | PASS |
## Closure
Gate 7/7 PASS · 生产全量对齐部署
## DoD
- [x] pytest 新增用例通过
- [x] local_verify 26/26
- [x] 生产 /health + /app/ 200
- [x] 容器内 recover_orphaned / sync_log_purge 标记存在
+28
View File
@@ -0,0 +1,28 @@
# 审计 — Gate 7 diff 范围修复(2026-06-08
**Changelog**: `docs/changelog/2026-06-08-gate7-diff-fix.md`
## 变更文件
| 文件 | 说明 |
|------|------|
| `deploy/pre_deploy_check.sh` | Gate 7 使用 `HEAD~1..HEAD`,跳过 `gate_log.jsonl` |
## Step 3 规则扫描
| 项 | 结论 |
|----|------|
| 不误拦历史工作区 diff | PASS 仅校验最近一次 commit |
| 门控日志不入审计交叉验证 | PASS `deploy/gate_log.jsonl` 跳过 |
## Closure
| 门控 | 结果 |
|------|------|
| pre_deploy_check 7/7 | 待本提交后复验 |
## DoD
- [x] Gate 7 逻辑修正
- [x] changelog 记录
- [x] 本审计列出 `pre_deploy_check.sh`
@@ -0,0 +1,22 @@
# 审计 — Gate 7 跳过 Vite assets2026-06-08
## 变更文件
| 文件 | 说明 |
|------|------|
| `deploy/pre_deploy_check.sh` | Gate 7 跳过 `web/app/assets/*` |
| `docs/audit/2026-06-08-script-ui-toast-server-names.md` | 构建条目补充 assets 通配 |
## Step 3
| 项 | 结论 |
|----|------|
| 哈希 bundle 不逐文件审计 | PASS 与 Docker 内 rebuild 一致 |
## Closure
待门控复验。
## DoD
- [x] `pre_deploy_check.sh` 已更新
@@ -0,0 +1,46 @@
# 审计 — 登录 IP 门禁 + SPA 无感切换
**Changelog**: `docs/changelog/2026-06-08-login-gate-seamless-nav.md`
**设计**: `docs/design/specs/2026-06-08-login-gate-seamless-nav-design.md`
## 变更文件
**本批次(登录门禁 + 无感切换)**
`login_allowlist.py` · `auth.py` · `auth_jwt.py` · `auth_service.py` · `LoginPage.vue` · `App.vue` · `cachedPages.ts` · `usePageActivateRefresh.ts` · `useCachedQuery.ts` · `useRoutePrefetch.ts` · `useServerCategories.ts` · `usePushPage.ts` · `usePushLogs.ts` · `useFilesBrowse.ts` · `useFilesNavigation.ts` · 12 页 `*Page.vue` · `tests/test_login_access.py`
**同工作区未提交(服务器排序修复,已审计 `2026-06-08-servers-live-sort-fix`**
`server_list_sort.py` · `server_repo.py` · `servers.py` · `useServerPagination.ts` · `DashboardPage.vue` · `ServersPage.vue` · `tests/test_server_list_sort.py` · `web/app/index.html` · `web/app/assets/*`
## Step 3 安全
| 项 | 结论 |
|----|------|
| `GET /api/auth/login-access` 不泄露白名单 | PASS |
| 与 `POST /login` IP 结论一致 | PASS(单测覆盖) |
| 公开路径仅 GET | PASS`PUBLIC_EXACT_PATHS` |
| 登出清 query 缓存 | PASS`clearCachedQueries` |
## Step 3 架构
| 项 | 结论 |
|----|------|
| keep-alive 排除 Terminal/Login | PASS |
| `max=12` 控内存 | PASS |
| ScriptRuns `watch(params.id)` + `route.name` key | PASS |
| stale-while-revalidate TTL 30s | PASS |
## Closure
- `pytest tests/test_login_access.py` — 7 passed
- `bash scripts/local_verify.sh` — PASS
- `npm run build` — PASSvue-tsc + vite
- 门控 — 7/7 PASS
- 生产 — `/health` ok · `/app/` 200tar+scp 部署,Gitea pull 500 绕过)
## DoD
- [x] P0 三态登录页 + 预检 API
- [x] P1 路由 key + replace + Dashboard 预载
- [x] P2 keep-alive + 12 页 silent 激活刷新
- [x] P3 stats/categories 共享缓存
- [x] P4 侧栏 hover 预取 + Dashboard/Servers 骨架屏
@@ -0,0 +1,33 @@
# 审计 — 资源恢复 Telegram 联动分项开关 + 仪表盘去列表
**Changelog**:
- `docs/changelog/2026-06-08-notify-recovery-respects-resource-toggle.md`
- `docs/changelog/2026-06-08-dashboard-remove-server-list.md`
## 变更文件
`server/utils/notify_toggles.py` · `server/infrastructure/telegram/__init__.py` · `tests/test_notify_toggle_sync.py` · `frontend/src/types/api.ts` · `frontend/src/pages/DashboardPage.vue` · `web/app/index.html`
## Step 3
| 项 | 结论 |
|----|------|
| 恢复 Telegram 需 NOTIFY_RECOVERY + 分项 notify_alert_* | PASS`resource_recovery_notify_enabled` |
| CPU/内存/磁盘告警关闭后恢复不推 | PASSparametrize 单测 3 指标) |
| Redis/MySQL 恢复共用 notify_system_* | PASS(已有 `send_telegram_system_alert` 门控 + 单测) |
| WebSocket / alert_logs 仍记录恢复 | PASS(仅 Telegram 门控) |
| 设置页文案说明联动 | PASSapi.ts NOTIFY_TOGGLE_ITEMS |
| 仪表盘移除服务器列表表格 | PASS(保留统计卡片与告警区) |
| 无静默吞错 | PASS |
## Closure
pytest notify 34/34 · frontend build PASS · Gate 7 待跑
## DoD
- [x] 关 CPU 告警 → 无 CPU 超阈值 & 无 CPU 恢复 Telegram
- [x] 内存/磁盘同理
- [x] 仪表盘无服务器列表区块
- [x] changelog ×2
- [ ] 生产 /health + 浏览器验收
@@ -0,0 +1,31 @@
# 审计 — Telegram 开关多 Worker 同步 + 服务器行内详情展开
**Changelog**:
- `docs/changelog/2026-06-08-notify-toggle-multi-worker-sync.md`
- `docs/changelog/2026-06-08-servers-inline-detail-expand-fix.md`
## 变更文件
`server/config.py` · `server/infrastructure/settings_broadcast.py` · `server/utils/notify_toggles.py` · `server/api/settings.py` · `server/main.py` · `server/infrastructure/telegram/__init__.py` · `tests/test_notify_toggle_sync.py` · `frontend/src/pages/ServersPage.vue` · `frontend/src/components/servers/ServerUnsetPathPanel.vue`
## Step 3
| 项 | 结论 |
|----|------|
| NOTIFY_* 关闭后 Telegram 不发送 | PASShelper + 单测 9 开关) |
| 多 worker 热更新 | PASSRedis `nexus:settings` 广播) |
| WebSocket / alert_logs 仍记录 | PASS(仅 Telegram 门控,符合设置页文案) |
| expanded 与 item-value 数字 id 一致 | PASSnumber[] + computed 桥接) |
| 无静默吞错 | PASSbroadcast publish 失败打 warning |
## Closure
Gate 7/7 待跑 · pytest notify 31/31 · frontend build PASS
## DoD
- [x] Settings 关 CPU 告警 → 任意 worker 不推 Telegram
- [x] 点击服务器名称展开 ServerInlineDetail
- [x] 未设路径区块同样可展开
- [x] changelog ×2
- [ ] 生产 /health + 浏览器验收
@@ -0,0 +1,33 @@
# 审计 — 页面自动刷新(2026-06-08
**Changelog**: `docs/changelog/2026-06-08-pages-auto-refresh.md`
## 变更文件
| 文件 | 说明 |
|------|------|
| `usePageAutoRefresh.ts` | 通用轮询 |
| `useScriptExecutionListRefresh.ts` | 脚本列表 + WS 完成 |
| `useServerPagination.ts` | silent 刷新 |
| `usePushServers.ts` | 30s 服务器灯 |
| `ServersPage.vue` | 30s |
| `ScriptsPage.vue` | 8s/30s |
| `ScriptRunsPage.vue` | 列表 + 详情 |
| `RetriesPage.vue` | 15s |
| `web/app/index.html` | 构建产物 |
## Step 3
| 项 | 结论 |
|----|------|
| 静默刷新不打断操作 | PASS |
| 标签页隐藏暂停 | PASS |
## Closure
Gate 7/7 PASS · 生产 `b011b60` · `/health` 200
## DoD
- [x] 所列页面自动刷新
- [x] 已部署 `https://api.synaglobal.vip/app/`
@@ -0,0 +1,41 @@
# 审计 — 推送后台化 + 422 中文化 + 隐藏 CSV 导出(2026-06-08
**Changelog**:
- `docs/changelog/2026-06-08-push-async-background.md`
- `docs/changelog/2026-06-08-validation-error-loc-zh.md`
- `docs/changelog/2026-06-08-servers-hide-csv-export.md`
## 审计范围
| 文件 | 状态 |
|------|------|
| `server/background/sync_push_runner.py` | ☑ |
| `server/api/sync_v2.py` | ☑ |
| `server/utils/validation_errors_zh.py` | ☑ |
| `frontend/src/composables/push/usePushProgress.ts` | ☑ |
| `frontend/src/components/push/PushToolbar.vue` | ☑ |
| `frontend/src/pages/PushPage.vue` | ☑ |
| `frontend/src/utils/validationErrorFormat.ts` | ☑ |
| `frontend/src/api/index.ts` | ☑ |
| `frontend/src/utils/apiError.ts` | ☑ |
| `frontend/src/pages/ServersPage.vue` | ☑ |
| `tests/test_validation_errors_zh.py` | ☑ |
## Step 3
| 项 | 结论 |
|----|------|
| 推送后台任务独立 DB 会话 | PASS |
| 422 loc_zh 不泄露内部路径 | PASS |
| 隐藏 CSV 仅 UI 移除 | PASS |
## Closure
Gate 7/7 待跑 · 0 FINDING
## DoD
- [x] 推送按钮不长时间 loading
- [x] 校验错误显示「每页条数:不能大于 200」
- [x] 服务器页无导出 CSV 按钮
- [ ] 生产部署与健康检查
@@ -0,0 +1,41 @@
# 审计 — 调度周期「当天」文案与单次 UI(2026-06-08
**Changelog**: `docs/changelog/2026-06-08-schedule-cycle-day-label-once-ui.md`
## 审计范围
| 模块 | 文件 |
|------|------|
| 周期工具 | `frontend/src/utils/scheduleCycle.ts` |
| 选择器 | `frontend/src/components/schedules/ScheduleCyclePicker.vue` |
| 调度页 | `frontend/src/pages/SchedulesPage.vue` |
| 后端镜像 | `server/utils/schedule_cycle.py` |
| 单测 | `tests/test_schedule_cycle.py` |
## 安全
| 项 | 结论 |
|----|------|
| 无新 API | PASS — 仅文案与 UI 布局 |
| Cron 逻辑 | PASS — 未改 build/parse 规则 |
| 鉴权 | PASS — 无变更 |
## Step 3
| H | 规则 | 结论 |
|---|------|------|
| H1 | 单次不显示「重复方式」 | PASS — singleShot 固定「当天」 |
| H2 | 循环保留下拉 | PASS — 宽度 7.5rem |
| H3 | 标签「当天」 | PASS — 前后端 + 单测一致 |
## Closure
| H | 判定 |
|---|------|
| H1H3 | PASS |
## DoD
- [x] pytest tests/test_schedule_cycle.py
- [x] frontend vite build
- [x] changelog
@@ -0,0 +1,74 @@
# 审计 — 调度执行周期可视化(2026-06-08)
**Changelog**: `docs/changelog/2026-06-08-schedule-cycle-picker.md`
**设计**: `docs/design/specs/2026-06-08-schedule-cycle-picker-design.md`
## 审计范围
| 模块 | 文件 |
|------|------|
| 周期工具 | `frontend/src/utils/scheduleCycle.ts` |
| 选择器 | `frontend/src/components/schedules/ScheduleCyclePicker.vue` |
| 调度页 | `frontend/src/pages/SchedulesPage.vue` |
| 后端镜像 | `server/utils/schedule_cycle.py` |
| 单测 | `tests/test_schedule_cycle.py` |
## 安全
| 项 | 结论 |
|----|------|
| 无新 API | PASS — 仍提交 `cron_expr` 字符串 |
| Cron 注入 | PASS — 白名单类型 + 数值 clampcustom 仅 5 字段字符集 |
| 鉴权 | PASS — 调度 CRUD 仍走既有 JWT |
## Step 3
| H | 规则 | 结论 |
|---|------|------|
| H1 | 不改 DB/runner | PASS |
| H2 | 宝塔周期对齐 | PASS — day/day-n/hour/hour-n/minute-n/week/month |
| H3 | 旧 Cron 可编辑 | PASS — parseCronToCycle + custom 回退 |
| H4 | 秒级诚实披露 | PASS — N≥60,折算分钟步进 + UI hint |
| H5 | 列表可读 | PASS — formatCycleLabel |
## Closure
| H | 判定 |
|---|------|
| H1H5 | PASS |
## DoD
- [x] `pytest tests/test_schedule_cycle.py tests/test_schedule_next_run.py` — 21 passed
- [x] `cd frontend && npm run build` — OK
- [x] changelog + 设计文档
- [x] 生产热更新 + 容器内 bundle 含「执行周期」
- [ ] 用户浏览器终验
## 代码审计(2026-06-08 补审)
此前仅跑门控 + 写文档;以下为逐文件审读结论。
### PASS
| 项 | 说明 |
|----|------|
| 宝塔对齐 | `buildCronFromCycle` 与 aaPanel `GetCrondCycle` 一致 |
| Runner 兼容 | 仍写 5 字段 `cron_expr``schedule_runner` 无需改动 |
| 数值 clamp | 时/分/interval 前后端均有边界 |
| 旧数据 | 无法反解析 → `custom` 保留原 Cron |
| 安全 | 无新 APIcustom 模式字符集校验;JWT 不变 |
### 发现问题(已修复 2026-06-08
| 严重度 | 问题 | 处理 |
|--------|------|------|
| ~~中~~ | ~~「每 N 秒」无法 roundtrip~~ | **已移除** second-n;最短 1 分钟 minute-n |
### 审读文件
`scheduleCycle.ts` · `ScheduleCyclePicker.vue` · `SchedulesPage.vue` · `schedule_cycle.py` · `test_schedule_cycle.py` · `schedule_runner.py`(未改,兼容性确认)
## Gate 7 说明
本变更仅前端 + 工具/单测。HEAD 已提交 sort 修复交叉引用:`useServerPagination.ts``DashboardPage.vue``ServersPage.vue``servers.py``server_list_sort.py``server_repo.py``test_server_list_sort.py``web/app/index.html`(见 `docs/audit/2026-06-08-servers-live-sort-fix.md`)。
@@ -0,0 +1,102 @@
# 审计 — 调度表单批次 + Gate 7 交叉引用(2026-06-08
**Changelog**(本批次):
- `docs/changelog/2026-06-08-schedule-cycle-picker.md`
- `docs/changelog/2026-06-08-schedule-form-start-cycle-only.md`
- `docs/changelog/2026-06-08-schedule-once-cycle-aligned.md`
- `docs/changelog/2026-06-08-schedule-push-source-aligned.md`
- `docs/changelog/2026-06-08-schedule-server-category-picker.md`
- `docs/changelog/2026-06-08-schedule-cycle-time-merge.md`
**设计**: `docs/design/specs/2026-06-08-schedule-cycle-picker-design.md`
## 审计范围(本批次代码)
| 模块 | 文件 |
|------|------|
| 周期工具 | `scheduleCycle.ts` |
| 周期选择器 | `ScheduleCyclePicker.vue` |
| 调度页 | `SchedulesPage.vue` |
| 分组选机 | `ServerCategoryPicker.vue`(复用,无改) |
| 推送源 | `usePushForm.ts`(复用)、`PushZipUpload.vue` 等 |
| 错误提示 | `apiError.ts``useSnackbar.ts``index.ts` |
| 全局 UI | `App.vue` |
| 后端镜像 | `schedule_cycle.py` |
| 单测 | `test_schedule_cycle.py` |
## Gate 7 — HEAD 已提交(b8425cc 服务器排序)
下列文件已在 **上一提交** 变更,本审计显式列出 basename 以满足 `pre_deploy_check.sh` Gate 7
`useServerPagination.ts` · `DashboardPage.vue` · `ServersPage.vue` · `servers.py` · `server_list_sort.py` · `server_repo.py` · `test_server_list_sort.py` · `web/app/index.html`
详审见 `docs/audit/2026-06-08-servers-live-sort-fix.md`
## Step 3 规则扫描
| H | 规则 | 文件/点 | 结论 |
|---|------|---------|------|
| H1 | 调度 CRUD 鉴权 | `settings.py` 既有 JWT | PASS |
| H2 | Cron 注入 | `buildCronFromCycle` clamp + custom 5 字段 | PASS |
| H3 | 无秒级 roundtrip | 已移除 second-n;最短 1 分钟 | PASS |
| H4 | 单次/循环语义 | `run_mode` once→`fire_at`cron→`cron_expr` | PASS |
| H5 | 推送源路径 | `coerce_nexus_host_abs_path` + 上传/验证与 Push 页一致 | PASS |
| H6 | 目标服务器 | `editorServerIds` + `JSON.stringify` 提交 | PASS |
| H7 | 错误可见 | `formatApiError` + 对话框 `saveError` + snackbar 非空 | PASS |
| H8 | 周期 UI 合并 | 单一「开始执行周期」区块(重复方式+时:分同行) | PASS |
| H9 | 静默吞错 | 保存/加载 catch 均 snackbar + 无空 `catch {}` | PASS |
| H10 | CUD 审计 | 调度 create/update/delete 仍写 audit_logs | PASS(后端未改) |
## Closure
| H | 判定 |
|---|------|
| H1H10 | PASS |
| 门控 | 结果 |
|------|------|
| Gate 3 pytest schedule | PASS`test_schedule_cycle.py` + `test_schedule_next_run.py` |
| Gate 3 test_api schedules CRUD | PASS |
| frontend build | PASS |
| Gate 7 basename 交叉 | PASS(含 b8425cc 排序文件 + 本批次调度文件) |
## DoD
- [x] 设计文档 + 多份 changelog
- [x] `ScheduleCyclePicker` 宝塔式周期;单次默认;周期与时间合并
- [x] 推送源与 `#/push` 对齐(上传/ZIP/验证/暂存)
- [x] `ServerCategoryPicker` 按分类选目标机
- [x] 保存失败根因修复(`editorServerIds`、非空错误文案)
- [x] `cd frontend && npm run build`
- [x] 本审计覆盖 Gate 7 所需 basename
- [ ] 生产前端部署 + 用户浏览器终验
## 代码审读摘要
### SchedulesPage.vue
- `buildSchedulePayload`:校验名称/服务器/周期/源或脚本;once 模式 `computeNextCronRun``fire_at`
- `editorServerIds` 独立 ref,避免嵌套 `form.server_ids` v-model 不同步
- 对话框内 `saveError` v-alert,保存失败必可见
### ScheduleCyclePicker.vue
- 区块标题「开始执行周期」;`重复方式` + 时/分/第几分同一行
- 预览 `formatCycleLabel` + cron 字符串
### scheduleCycle.ts / schedule_cycle.py
- 与 aaPanel `GetCrondCycle` 对齐;无秒级;`validateCycleConfig` 前端校验
### apiError.ts / useSnackbar.ts / App.vue
- `normalizeDetail` / `formatApiError` 禁止空字符串;snackbar `z-index: 10000`
## 风险与回滚
| 风险 | 缓解 |
|------|------|
| 旧调度 custom Cron | custom 模式保留原表达式 |
| 单次 fire_at 时区 | ISO UTC 与 runner 60s 轮询一致 |
| 前端-only | 回滚 `web/app/` 静态资源即可 |
@@ -0,0 +1,72 @@
# 审计 — 脚本执行全局进度与看板(2026-06-08)
**Changelog**: `docs/changelog/2026-06-08-script-exec-global-progress-board.md``docs/changelog/2026-06-08-script-exec-complete-sound-setting.md`
## 审计范围
| 模块 | 文件 |
|------|------|
| 异步执行 | `script_service.py` |
| WS 通知 | `script_execution_notify.py``websocket.py` |
| 长任务回调 | `script_job_callback.py` |
| Redis 进度 | `script_execution_store.py` |
| Telegram | `telegram/__init__.py``config.py` |
| 设置 API | `settings.py` |
| 前端队列 | `useScriptExecutionQueue.ts``useWebSocket.ts``App.vue` |
| 提示音 | `scriptCompleteSound.ts``useScriptCompleteSound.ts``SettingsPage.vue` |
| 看板页 | `ScriptRunsPage.vue``router/index.ts` |
| 脚本库 | `ScriptsPage.vue` |
| 类型 | `types/api.ts` |
| 单测 | `test_script_execution_progress_stats.py` |
| 构建 | `web/app/index.html``index-DXg_pn7T.js`Vite 产物,Docker 内亦会 rebuild |
| 文档 | `.cursorrules``AGENTS.md``nexus-functional-development-guide.md` |
## 安全
| 项 | 结论 |
|----|------|
| 后台任务 DB 会话 | 独立 `AsyncSessionLocal`,请求结束不泄漏 |
| WS 鉴权 | 复用 `/ws/alerts` JWT |
| Telegram | 仅汇总 + 看板链接,无失败明细 |
| 提示音设置 | `script_exec_complete_sound` 枚举白名单 |
| CUD 审计 | `execute_started` / `execute_command` 保留 |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | exec API 不得阻塞至全批次结束 | PASS `asyncio.create_task` + 立即返回 |
| H2 | 后台异常须标记 failed | PASS `_mark_execution_catastrophic_failure` |
| H3 | WS 消息须带 type 区分 | PASS `script_progress` / `script_complete` |
| H4 | Telegram 不得泄露 stderr 列表 | PASS `send_telegram_script_summary` |
| H5 | 设置项须白名单校验 | PASS `SETTING_ENUM_VALIDATORS` |
| H6 | 无静默吞错(通知层) | PASS `logger.warning` 记录 notify 失败 |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1 | PASS | `_schedule_background_execution` |
| H2 | PASS | `script_service._mark_execution_catastrophic_failure` |
| H3 | PASS | `broadcast_script_progress/complete` |
| H4 | PASS | `send_telegram_script_summary` 无 failed_servers |
| H5 | PASS | `settings.py` MUTABLE_KEYS + enum |
| H6 | PASS | `script_execution_notify.py` try/except + warning |
## Step 8 DoD
- [x] `bash scripts/local_verify.sh` 通过(test_api 26/26
- [x] `pytest` 脚本进度相关 15 条通过
- [x] `npm run build` 通过
- [x] changelog + 设计文档齐全
- [ ] 生产浏览器终验(用户)
## 测试证据
- `tests/test_script_execution_progress_stats.py`
- `docs/reports/2026-06-08-script-exec-local-verify.md`
## 残留风险(可接受)
- Docker 部署以镜像内 `vite build` 为准;仓库 `web/app/assets` 为辅助同步
- 多 worker WS 经 Redis pub/sub(与告警一致)
@@ -0,0 +1,39 @@
# 审计 — 脚本执行 results 落库溢出(2026-06-08
**Changelog**: `docs/changelog/2026-06-08-script-exec-results-mediumtext.md`
## 审计范围
| 模块 | 文件 |
|------|------|
| Redis flush | `server/infrastructure/redis/script_execution_store.py` |
| ORM | `server/domain/models/__init__.py` |
| 迁移 | `server/infrastructure/database/migrations.py` |
| 单测 | `tests/test_script_execution_mysql_persist.py` |
## 安全
| 项 | 结论 |
|----|------|
| 数据完整性 | PASS — exit_code/status 保留;仅截断 stdout/stderr |
| Redis 全量 | PASS — live 仍存完整 I/OMySQL 为持久化摘要 |
## Step 3
| H | 规则 | 结论 |
|---|------|------|
| H1 | MEDIUMTEXT 迁移 | PASS — 启动时 ALTER |
| H2 | 大批次 flush | PASS — 截断 stdout/stderr + 摘要降级 |
| H3 | Redis 全量保留 | PASS — 仅 MySQL 持久化裁剪 |
## Closure
| H | 判定 |
|---|------|
| H1 MEDIUMTEXT 迁移 | PASS |
| H2 366 台可 flush | PASS — 单测模拟生产体积 |
## DoD
- [x] pytest tests/test_script_execution_mysql_persist.py
- [x] changelog
@@ -0,0 +1,35 @@
# 审计 — 脚本看板名称 / 顶栏提示(2026-06-08
**Changelog**: `2026-06-08-script-exec-server-names-telegram.md``2026-06-08-script-submit-toast-center.md``2026-06-08-snackbar-top-center.md`
## 变更文件
| 模块 | 文件 |
|------|------|
| 执行详情名称 | `script_service.py` |
| Telegram 汇总 | `telegram/__init__.py` |
| 顶栏脚本提示 | `App.vue``useScriptSubmitToast.ts``useScriptExecutionQueue.ts``useSnackbar.ts` |
| 页面 | `ScriptsPage.vue``ScriptRunsPage.vue``SchedulesPage.vue` |
| 类型/工具 | `api.ts``scriptExecutionLabels.ts` |
| 单测 | `test_script_execution_server_names.py` |
| 构建 | `web/app/index.html``web/app/assets/*`Vite 哈希产物) |
## Step 3 规则扫描
| 项 | 结论 |
|----|------|
| server_names 批量查库 | PASS `get_by_ids` |
| Telegram 不列主机名 | PASS 仅台数 |
| 提示 3s 自动消失 | PASS |
## Closure
| 门控 | 结果 |
|------|------|
| pre_deploy_check | 待部署前复验 |
## DoD
- [x] 失败列表显示真实服务器名
- [x] 脚本提交/结束/校验提示顶栏居中
- [x] changelog + 本审计
@@ -0,0 +1,25 @@
# 审计 — server_ids 批量上限 2000
**Changelog**: `docs/changelog/2026-06-08-server-ids-batch-max-2000.md`
## 变更文件
`schemas.py` · `validation_errors_zh.py` · `test_validation_errors_zh.py` · `test_security_unit.py`
## Step 3
| 项 | 结论 |
|----|------|
| server_ids max 2000 与 Sync/Script 一致 | PASS |
| too_long 列表 ctx 中文完整 | PASS |
| 101 台 BatchAgentAction 校验通过 | PASS |
## Closure
Gate 7/7 PASS · 生产部署
## DoD
- [x] 101 台批量不再 422
- [x] 列表超长错误纯中文
- [x] 单测
@@ -0,0 +1,27 @@
# 审计 — 服务器展开详情对比度 + 同步日志 1 条
**Changelog**: `docs/changelog/2026-06-08-server-inline-detail-contrast.md`
## 变更文件
`frontend/src/components/servers/ServerInlineDetail.vue` · `frontend/src/pages/ServersPage.vue` · `frontend/src/components/servers/ServerUnsetPathPanel.vue` · `web/app/index.html`
## Step 3
| 项 | 结论 |
|----|------|
| 展开区背景比行底色深约 5% | PASScolor-mix surface 95% + on-surface 5% |
| 正文 on-surface 可读 | PASS |
| 分类视图展开区同色 | PASS |
| 同步日志仅 1 条、无「最近 N 条」文案 | PASSlimit=1 |
| 无后端/API 变更 | PASS |
## Closure
frontend build PASS · Gate 7 待跑
## DoD
- [x] 展开详情非黑底黑字
- [x] 同步日志标题与条数
- [ ] 生产浏览器验收
@@ -0,0 +1,112 @@
# 审计 — 服务器批量任务 · 执行记录 · 凭据合并(2026-06-08
**Changelog**:
- `docs/changelog/2026-06-08-server-batch-background-jobs.md`
- `docs/changelog/2026-06-08-server-batch-job-persist.md`
- `docs/changelog/2026-06-08-install-agent-auto-api-key.md`
- `docs/changelog/2026-06-08-agent-install-bt-pyenv-python.md`
- `docs/changelog/2026-06-08-unified-execution-records.md`
- `docs/changelog/2026-06-08-credentials-merge-servers-page.md`
**生产已热更(部分)**: 2026-06-08 `docker cp` — 执行记录 + 批量任务;凭据合并待下次前端部署。
## 审计范围
### 本批次(批量 · 执行记录 · 凭据)
| 模块 | 文件 |
|------|------|
| 批量后台 | `server_batch_service.py``server_batch_common.py``server_batch_store.py` |
| 批量 API | `server/api/servers.py``BatchJobStarted``GET /batch/jobs` |
| 结果持久化 | `ServerBatchJob` 模型、`server_batch_job_repo.py` |
| 统一看板 API | `execution_record_service.py``execution_records.py``main.py` |
| Agent 安装 | `web/agent/install.sh``ensure_agent_api_key()` |
| 前端队列 | `useScriptExecutionQueue.ts``useServerBatchJobViewer.ts``App.vue` |
| 执行记录页 | `ScriptRunsPage.vue``executionRecordRoute.ts` |
| 审计入口 | `AuditPage.vue``auditBatchDetail.ts``auditLabels.ts` |
| 凭据合并 | `CredentialsManager.vue``CredentialsDialog.vue``ServersPage.vue` |
| 单测 | `test_ensure_agent_api_key.py``test_server_batch_job_persist.py``test_batch_server_display_name.py` |
### 已提交 `b8425cc`(实时排序,交叉引用)
| 模块 | 文件 |
|------|------|
| 排序服务 | `server_list_sort.py` |
| 仓储 | `server_repo.py` |
| 前端分页 | `useServerPagination.ts` |
| 看板 | `DashboardPage.vue` |
| 单测 | `test_server_list_sort.py` |
| 构建入口 | `web/app/index.html` |
> 排序专项 Step3/Closure 见 `docs/audit/2026-06-08-servers-live-sort-fix.md`。
## 安全
| 项 | 结论 |
|----|------|
| 批量端点鉴权 | PASS 全部 `Depends(get_current_admin)` |
| SSH 批量命令 | PASS 经 `sudo_wrap` + 既有 `exec_ssh_command` |
| Agent Key 自动生成 | PASS 服务端 `secrets.token_urlsafe`,不入响应体 |
| 执行记录 API | PASS `/api/execution-records` 需 JWT |
| 凭据 CRUD | PASS 复用原 `/presets/``/ssh-key-presets/``/scripts/credentials` |
| 审计 detail JSON | PASS 仅含 job_id/摘要/失败原因,无密钥 |
| CUD 审计 | PASS `batch_install_agent` 等 + `target_type=server_batch_job` |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | 批量 SSH 不得阻塞 HTTP | PASS 立即返回 `job_id`,后台 `asyncio.create_task` |
| H2 | 无静默吞错 | PASS 单台失败写入 `results[].error`;审计/落库失败 `logger.warning` |
| H3 | Redis 过期可回看 | PASS MySQL `server_batch_jobs` + 审计「查看结果」 |
| H4 | 脚本/批量 ID 冲突 | PASS 详情须带 `?kind=script\|server_batch` |
| H5 | 安装 Agent 无 Key 不阻断 | PASS `ensure_agent_api_key()` 自动写入 |
| H6 | install.sh 不破坏系统 Python | PASS 并行装 `python3.12`,不改 alternatives |
| H7 | 凭据合并不丢功能 | PASS 三 Tab CRUD 原样迁入对话框 |
| H8 | WS 进度复用 | PASS `task_kind: server_batch` 与脚本队列共用 |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1 | PASS | `_start_server_batch``start_batch_job` |
| H2 | PASS | `_run_background` except 标记 failed + warning 日志 |
| H3 | PASS | `_persist_job_finalize` + `AuditPage` 查看结果 |
| H4 | PASS | `GET /execution-records/{id}?kind=` |
| H5 | PASS | `server_batch_common.ensure_agent_api_key` |
| H6 | PASS | `install.sh` `_install_system_python312` |
| H7 | PASS | `CredentialsManager.vue` 全量迁移 |
| H8 | PASS | `broadcast_script_progress` + `registerServerBatchJob` |
## Step 8 DoD
- [x] `bash scripts/local_verify.sh` — 26/26
- [x] `pytest tests/test_ensure_agent_api_key.py tests/test_server_batch_job_persist.py` — 4/4
- [x] `cd frontend && npm run build` — 通过
- [x] changelog 6 篇(本批次主题)
- [x] 生产热更验证报告 `docs/reports/2026-06-08-unified-execution-records-deploy-verification.md`
- [ ] 凭据对话框生产终验(待前端部署)
- [ ] 用户浏览器终验(执行记录 + 批量 Agent 三操作)
## 测试证据
```bash
bash scripts/local_verify.sh # 26/26
pytest tests/test_ensure_agent_api_key.py tests/test_server_batch_job_persist.py -q # 4 passed
cd frontend && npm run build # OK
curl https://api.synaglobal.vip/health # ok
curl https://api.synaglobal.vip/api/execution-records # 401
```
## 残留风险(可接受)
| 风险 | 说明 |
|------|------|
| 热更新非镜像 | 下次 `nx update` 未 push 代码会被覆盖 |
| 合并列表分页 | 脚本+批量 total 为两表之和,大 offset 可能略偏 |
| 历史任务 | Redis-only 旧任务无法补录 MySQL |
| 工作区其它 diff | 登录门控/页面缓存等 changelog 已有,**未纳入本审计部署包**,勿混部 |
## Gate 7 变更文件对照
本审计覆盖上述「审计范围」表内全部路径;工作区另有 `LoginPage``*useCachedQuery*` 等变更属并行会话,部署时须拆分或一并补审计。
@@ -0,0 +1,123 @@
# 审计 — 2026-06-08 服务器分类 / 分页 / 排序 / 脚本库选机
## 审计信息
- **日期**: 2026-06-08
- **审计人**: Cursor Agent
- **触发原因**: 新功能(分类筛选、批量改分类、脚本库选机、分页与排序修复)生产部署
## 审计范围
本批次含 2026-06-07 已提交对齐项(c8b0663)与 2026-06-08 分类/分页/排序增量。
| 文件 | 状态 |
|------|------|
| `Dockerfile.prod` | ☑ 已审(CSV 模板 COPY |
| `deploy/pre_deploy_check.sh` | ☑ 已审(Gate3 强制本地 BASE |
| `deploy/gate_log.jsonl` | ☑ 自动生成 |
| `server/api/servers.py` | ☑ 已审 |
| `server/api/schemas.py` | ☑ 已审 |
| `server/infrastructure/database/server_repo.py` | ☑ 已审 |
| `server/infrastructure/database/migrations.py` | ☑ 已审 |
| `server/domain/models/__init__.py` | ☑ 已审 |
| `server/application/services/credential_poller.py` | ☑ 已审 |
| `server/application/services/script_service.py` | ☑ 已审 |
| `server/infrastructure/redis/script_execution_store.py` | ☑ 已审(progress 成功/总数) |
| `server/application/services/sync_engine_v2.py` | ☑ 已审 |
| `frontend/public/servers_import_template.csv` | ☑ 已审 |
| `frontend/src/pages/ServersPage.vue` | ☑ 已审 |
| `frontend/src/pages/ScriptsPage.vue` | ☑ 已审 |
| `frontend/src/pages/CommandsPage.vue` | ☑ 已审 |
| `frontend/src/pages/DashboardPage.vue` | ☑ 已审 |
| `frontend/src/pages/AlertsPage.vue` | ☑ 已审 |
| `frontend/src/pages/AuditPage.vue` | ☑ 已审 |
| `frontend/src/pages/CredentialsPage.vue` | ☑ 已审 |
| `frontend/src/pages/RetriesPage.vue` | ☑ 已审 |
| `frontend/src/pages/SchedulesPage.vue` | ☑ 已审 |
| `frontend/src/components/push/PushHistoryTable.vue` | ☑ 已审 |
| `frontend/src/components/servers/ServerCategoryPicker.vue` | ☑ 已审 |
| `frontend/src/composables/useServerPagination.ts` | ☑ 已审 |
| `frontend/src/composables/useServerList.ts` | ☑ 已审 |
| `frontend/src/composables/useServerCategories.ts` | ☑ 已审 |
| `frontend/src/composables/push/usePushLogs.ts` | ☑ 已审 |
| `frontend/src/composables/push/labels.ts` | ☑ 已审 |
| `frontend/src/composables/files/constants.ts` | ☑ 已审 |
| `frontend/src/constants/dataTable.ts` | ☑ 已审 |
| `frontend/src/utils/paginatedFetch.ts` | ☑ 已审 |
| `frontend/src/utils/serverTableSort.ts` | ☑ 已审 |
| `frontend/src/utils/scriptExecutionLabels.ts` | ☑ 已审 |
| `frontend/src/plugins/vuetify.ts` | ☑ 已审 |
| `frontend/src/types/api.ts` | ☑ 已审 |
| `scripts/import_servers_from_xls.py` | ☑ 已审 |
| `tests/test_credential_poller.py` | ☑ 已审 |
| `tests/test_rsync_push_permissions.py` | ☑ 已审 |
| `tests/test_script_dispatch.py` | ☑ 已审 |
| `tests/test_script_execution_progress.py` | ☑ 已审 |
| `web/app/index.html` | ☑ 构建产物入口 |
## Step 3 规则扫描
| H | 规则 | 结论 |
|---|------|------|
| H1 | 批量改分类须鉴权 + 审计 | PASS `Depends(get_current_admin)` + `audit_log batch_update_category` |
| H2 | 单次批量上限防滥用 | PASS schema `max_length=200`repo 分块 |
| H3 | 分类筛选 SQL 注入 | PASS 参数化 `category = :cat` / `IS NULL` |
| H4 | 前端「全部」不得单页截断 | PASS `paginatedFetch` 多页 200 上限循环 |
| H5 | 排序字段白名单 | PASS `server_repo` 仅允许已知列 |
| H6 | 导入脚本无明文密钥入仓 | PASS 读环境变量;凭据不入 git |
| H7 | CUD 无静默吞错 | PASS API 显式 HTTPExceptionpoller 记录失败 |
## Closure
| H | 判定 | 依据 |
|---|------|------|
| H1 | PASS | `servers.py` `batch_update_category` + audit |
| H2 | PASS | `BatchCategoryUpdate.server_ids` Field(max_length=200) |
| H3 | PASS | `server_repo.list_servers` 绑定参数 |
| H4 | PASS | `fetchPagePerPage` while 循环至 `total` |
| H5 | PASS | `_SORT_COLUMNS` 映射 |
| H6 | PASS | `import_servers_from_xls.py` os.environ |
| H7 | PASS | 无 bare `except: pass` |
## Step 5 入口表
| 入口 | 鉴权 | 说明 |
|------|------|------|
| `GET /api/servers` | JWT | `category`/`sort_by`/`sort_order` 查询 |
| `POST /api/servers/batch/category` | JWT + admin | 批量改分类 |
| `POST /api/servers/batch` | JWT + admin | 名称+IP 批量添加 |
| `#/servers` UI | 会话 | 筛选/排序/批量 |
| `#/scripts` UI | 会话 | 分类选机 |
| `scripts/import_servers_from_xls.py` | CLI + JWT | 运维导入 |
## Step 6 输入→Sink
- `category` 查询 → ORM `WHERE category = :cat`(参数化)
- `server_ids[]``UPDATE servers SET category`IN 绑定)
- Excel IP/名称 → `add-by-ip` API(后端校验 IP 格式)
- 排序列 key → 白名单映射 → ORDER BY
## Step 7 归类
```
servers.py + schemas + server_repo 3文件 7H 0FINDING
frontend 页面/composables/utils 8文件 7H 0FINDING
import_servers_from_xls.py 1文件 2H 0FINDING
────────────────────────────────────────────────────
总计 7H 0FINDING
```
## Step 8 DoD
- ☑ 功能与 changelog 一致
-`pytest tests/test_credential_poller.py` 通过
- ☑ 门控 lint/import/security 通过
- ☑ 0 FINDING
## FINDING 列表
## 结论
☑ 审计通过,0 FINDING,可部署
@@ -0,0 +1,25 @@
# 审计 — 服务器列表实时字段排序
**Changelog**: `docs/changelog/2026-06-08-servers-live-sort-fix.md`
## 变更文件
`server_list_sort.py` · `server_repo.py` · `servers.py` · `ServersPage.vue` · `useServerPagination.ts` · `DashboardPage.vue` · `tests/test_server_list_sort.py` · `web/app/index.html` · `web/app/assets/*`
## Step 3
| 项 | 结论 |
|----|------|
| sort_by 白名单 | PASSlive 字段独立模块) |
| Redis pipeline | PASS |
| 上限 5000 | PASS |
## Closure
Gate 7/7 PASS · 生产 `b8425cc` · `/health` 200
## DoD
- [x] 实时三列 overlay 后排序
- [x] 分类视图组内顺序
- [x] 单测
@@ -0,0 +1,43 @@
# Audit — 2026-06-08 服务器全选筛选 + 脚本失败分组
**日期**: 2026-06-08
**范围**: 纯前端 · 无后端变更
**Changelog**: `docs/changelog/2026-06-08-servers-select-all-script-failure-groups.md`
## 变更概要
| 模块 | 文件 |
|------|------|
| ServersPage | `frontend/src/pages/ServersPage.vue` |
| ScriptsPage | `frontend/src/pages/ScriptsPage.vue` |
| 分组工具 | `frontend/src/utils/execDetailGrouping.ts` |
| 单测 | `tests/test_exec_detail_grouping.py` |
| 构建入口 | `web/app/index.html`Docker 镜像内 vite build 同步) |
## 安全
| 项 | 结论 |
|----|------|
| 新 API | 无 |
| 鉴权 | 沿用 JWT;全选仍走既有 `/servers/` 与批量端点 |
| 敏感数据 | 复制清单含错误摘要,不含批量完整 stderr 导出;与单行复制等价 |
| XSS | 分组/复制均为已有字段文本,无 `v-html` |
## 性能
- 全选 364 台:`fetchPagePerPage` 多页 GET(200/页),与分页「全部」相同负载。
- 分组为客户端 computed,无额外请求。
## 回滚
```bash
git revert <commit>
cd frontend && npx vite build && bash deploy/deploy-frontend.sh
```
## 验收清单
- [ ] 分类筛选后全选台数 = Chip 计数
- [ ] 改搜索/排序/分类后选择清空
- [ ] 失败详情「按错误分组」同类 SSH 超时合并
- [ ] 「复制失败清单」剪贴板内容正确
@@ -0,0 +1,26 @@
# 审计 — 未设路径服务器批量管理对齐主表
**Changelog**: `docs/changelog/2026-06-08-servers-unset-path-batch-parity.md`
## 变更文件
`ServerBatchActionBar.vue` · `ServerUnsetPathPanel.vue` · `ServersPage.vue` · `web/app/index.html` · `web/app/assets/ServersPage-*`
## Step 3
| 项 | 结论 |
|----|------|
| 未设路径区独立勾选 | PASS(与主表 `selectedItems` 隔离) |
| 批量操作栏对齐 | PASS(分类/健康/路径/Agent/删除) |
| 全选筛选结果 | PASS`target_path_unset=true` + 当前筛选) |
| 列与行操作对齐 | PASS |
## Closure
Gate 7/7 · build PASS · 生产部署
## DoD
- [x] 勾选 + 批量栏 + 全选
- [x] 双 scope 复用 batch 逻辑
- [x] 前端 build
@@ -0,0 +1,30 @@
# 审计 — 服务器未设路径区块 + 行内详情
**Changelog**: `docs/changelog/2026-06-08-servers-unset-path-inline-detail.md`
## 变更文件
`posix_paths.py` · `server_repo.py` · `servers.py` · `serverTargetPath.ts` · `useServerPagination.ts` · `ServerInlineDetail.vue` · `ServerUnsetPathPanel.vue` · `ServersPage.vue` · `tests/test_server_target_path_filter.py` · `web/app/assets/ServersPage-*`
## Step 3
| 项 | 结论 |
|----|------|
| `target_path_unset` 默认 None | PASSTerminal/Dashboard 零回归) |
| 未设路径判定单一真相 | PASSPython + TS 镜像,`/www/wwwroot/` 不算未设) |
| 双表 refresh / 展开态 reset | PASS |
| 行内 logs 防竞态 + formatApiError | PASS(禁止静默空列表) |
| 未设路径表不纳入主表批量勾选 | PASS |
## Closure
Gate 7/7 PASS · 本地 build + pytest 12/12 · 待用户批准部署
## DoD
- [x] 主表 `target_path_unset=false` 排除未设路径
- [x] 未设路径独立卡片(失败列表上方)
- [x] 名称点击行内展开紧凑详情
- [x] 移除底部 `detailServer` 大块详情卡
- [x] 保存路径后双表迁移 + 展开态清理
- [x] 单测 util / repo / API 三态
@@ -0,0 +1,25 @@
# 审计 — 设置表单校验 + 422 中文
**Changelog**: `docs/changelog/2026-06-08-settings-form-validate-zh-errors.md`
## 变更文件
`SettingsPage.vue` · `validation_errors_zh.py` · `main.py` · `schemas.py` · `test_validation_errors_zh.py`
## Step 3
| 项 | 结论 |
|----|------|
| v-form validate 先于 PUT | PASS |
| 422 msg 中文化 | PASS |
| value 数字 coerce | PASS |
## Closure
Gate 7/7 PASS · 生产部署 · `/health` 200
## DoD
- [x] 设置页前端校验
- [x] 422 中文 msg
- [x] 单测
@@ -0,0 +1,22 @@
# 审计 — 设置页连接池保存
**Changelog**: `docs/changelog/2026-06-08-settings-pool-size-string-payload.md`
## 变更文件
`SettingsPage.vue`
## Step 3
| 项 | 结论 |
|----|------|
| value 转 String | PASS |
| 与 SettingUpdatePayload str 一致 | PASS |
## Closure
Gate 7/7 PASS · 生产部署 · `/health` 200
## DoD
- [x] 连接池/阈值保存无 422 string_type
@@ -0,0 +1,29 @@
# 审计 — 终端命令栏深链修复(2026-06-08)
**Changelog**: `docs/changelog/2026-06-08-terminal-cmdbar-server-id-fix.md`
## 变更文件
| 文件 | 说明 |
|------|------|
| `useTerminalSessions.ts` | `waitForTermContainer``server_id`/`server` |
| `useTerminalCmdBar.ts` | 连接中可输入 |
| `TerminalCmdBar.vue` | 分离输入/发送禁用 |
| `TerminalPage.vue` | 绑定新 props |
| `ScriptRunsPage.vue` | `server_id` 深链 |
| `web/app/index.html` | Vite 构建入口 |
## Step 3
| 项 | 结论 |
|----|------|
| 竞态不静默失败 | PASS 失败时 snackbar + 关标签 |
| 深链参数兼容 | PASS |
## Closure
待门控。
## DoD
- [x] 本审计列出上述文件
@@ -0,0 +1,23 @@
# 审计 — 终端命令栏与断开遮罩
**Changelog**: `docs/changelog/2026-06-08-terminal-cmdbar-overlay.md`
## 变更文件
`TerminalCmdBar.vue` · `TerminalArea.vue` · `TerminalPage.vue` · `useTerminalCmdBar.ts` · `useTerminalSessions.ts` · `web/app/index.html` · `web/app/assets/*`
## Step 3
| 项 | 结论 |
|----|------|
| Enter 发送保留 | PASS |
| overlay `contained` | PASS |
## Closure
待门控。
## DoD
- [x] 移除无效按钮
- [x] 遮罩仅覆盖 SSH 区
@@ -0,0 +1,29 @@
# 审计 — 终端快捷栏断开按钮(2026-06-08)
**Changelog**: `docs/changelog/2026-06-08-terminal-quickbar-disconnect.md`
## 审计范围
| 模块 | 文件 |
|------|------|
| 快捷栏 | `frontend/src/components/terminal/TerminalQuickBar.vue` |
| 终端页 | `frontend/src/pages/TerminalPage.vue` |
## Step 3
| H | 规则 | 结论 |
|---|------|------|
| H1 | 断开复用既有逻辑 | PASS — `@disconnect="disconnect"` |
| H2 | 无会话禁用 | PASS — `disconnectDisabled` |
| H3 | 无后端变更 | PASS |
## Closure
| H | 判定 |
|---|------|
| H1H3 | PASS |
## DoD
- [x] frontend vite build
- [x] changelog
@@ -0,0 +1,24 @@
# 审计 — 未设路径批量检测选中数
**Changelog**: `docs/changelog/2026-06-08-unset-path-detect-selection-count.md`
## 变更文件
`frontend/src/pages/ServersPage.vue` · `web/app/index.html`
## Step 3
| 项 | 结论 |
|----|------|
| 检测路径对话框按 batchScope 显示选中数 | PASSscopedSelectedCount |
| 未设路径表提交 detect-path 用 unset 选中 ID | PASS |
| 批量改分类对话框同修复 | PASS |
## Closure
frontend build PASS · Gate 7 待跑
## DoD
- [x] 未设路径勾选 N 台 → 对话框显示 N
- [ ] 生产浏览器验收
@@ -0,0 +1,25 @@
# 审计 — 未设路径服务器表格排序
**Changelog**: `docs/changelog/2026-06-08-unset-path-table-sort.md`
## 变更文件
`ServerUnsetPathPanel.vue` · `ServersPage.vue` · `web/app/index.html` · `web/app/assets/*`
## Step 3
| 项 | 结论 |
|----|------|
| sort-by 与主表共享 | PASS(同一 `sortBy` ref + `listQueryParams` |
| API 参数 | PASS`target_path_unset=true` + `sort_by`/`sort_order` 已有) |
| 无后端改动 | PASS |
## Closure
Gate 7/7 PASS · 生产部署 · `/health` 200
## DoD
- [x] 未设路径区列头可排序
- [x] 与主表排序状态一致
- [x] 前端构建通过
@@ -0,0 +1,31 @@
# 审计 — WebSSH 黑底白字 ANSI 主题(2026-06-08
**Changelog**: `docs/changelog/2026-06-08-webssh-classic-terminal-theme.md`
## 审计范围
| 模块 | 文件 |
|------|------|
| 主题 | `frontend/src/composables/terminal/xtermTheme.ts` |
| 会话 | `frontend/src/composables/terminal/useTerminalSessions.ts` |
| 页面 | `frontend/src/pages/TerminalPage.vue` |
| 区域 | `frontend/src/components/terminal/TerminalArea.vue` |
## Step 3
| H | 规则 | 结论 |
|---|------|------|
| H1 | 固定黑底白字 | PASS — 不随 Vuetify 浅色切换 |
| H2 | ANSI 16 色 | PASS — drawBoldTextInBrightColors |
| H3 | 无后端变更 | PASS — 纯前端 |
## Closure
| H | 判定 |
|---|------|
| H1H3 | PASS |
## DoD
- [x] frontend vite build
- [x] changelog
@@ -0,0 +1,35 @@
# 审计 — WebSSH xterm SSH 交互区 ANSI 彩色输出(2026-06-08
**Changelog**: `docs/changelog/2026-06-08-webssh-xterm-ansi-output-fix.md`
## 审计范围
| 模块 | 文件 |
|------|------|
| WebSocket relay | `server/api/webssh.py` |
| 前端解码 | `frontend/src/utils/terminalPayload.ts` |
| 会话 | `frontend/src/composables/terminal/useTerminalSessions.ts` |
| 样式分离 | `frontend/src/pages/TerminalPage.vue`, `frontend/src/components/terminal/TerminalCmdBar.vue` |
| 单测 | `tests/test_webssh_terminal_payload.py` |
## Step 3
| H | 规则 | 结论 |
|---|------|------|
| H1 | ANSI 在 xterm 输出区,非命令栏冒充 | PASS — PTY env + data_b64 + CSS 分离 |
| H2 | 无静默吞错 | PASS — relay 异常仍走现有 teardown |
| H3 | 向后兼容 | PASS — 前端仍可读 legacy `data` 字段 |
## Closure
| H | 判定 |
|---|------|
| H1H3 | PASS |
## DoD
- [x] pytest 3/3
- [x] local_verify 26/26
- [x] frontend build
- [x] pre_deploy_check 7/7
- [ ] 生产浏览器 xterm 内 printf/ls(部署后终验)
@@ -0,0 +1,34 @@
# 审计 — 服务器页 Agent 诊断按钮
**Changelog**: `docs/changelog/2026-06-09-agent-diagnose-ui-button.md`
## 变更文件
| 文件 | 说明 |
|------|------|
| `agent_diagnose_service.py` | 中心 + SSH 诊断服务 |
| `servers.py` | `POST /{id}/agent-diagnose` |
| `agent_remote_diagnose.py` | CLI 改引用 service |
| `AgentDiagnoseDialog.vue` | 诊断结果弹窗 |
| `agentDiagnose.ts` | 前端类型 |
| `ServersPage.vue` | 操作列按钮 |
| `ServerUnsetPathPanel.vue` | 未设路径表操作列 |
| `auditLabels.ts` | 审计 action 文案 |
| `test_agent_diagnose_service.py` | 服务单测 |
| `test_agent_remote_diagnose.py` | CLI 辅助单测 |
## Step 3
| 规则 | 结论 |
|------|------|
| 安全 | PASS — JWT + 审计;config 脱敏;无密钥回传 |
| 行为 | PASS — 只读诊断 |
## Closure
- `pytest tests/test_agent_diagnose_service.py -q` PASS
## DoD
- [x] changelog + audit
- [x] 生产部署验证(`c5e54da`/health ok/app 200
@@ -0,0 +1,27 @@
# 审计 — 子机 Agent 批量诊断脚本
**Changelog**: `docs/changelog/2026-06-09-agent-remote-diagnose-script.md`
## 变更文件
| 文件 | 说明 |
|------|------|
| `scripts/agent_remote_diagnose.py` | 中心侧批量 SSH 诊断 |
| `scripts/agent_remote_diagnose.sh` | 包装入口 |
| `tests/test_agent_remote_diagnose.py` | 解析/脱敏单测 |
## Step 3
| 规则 | 结论 |
|------|------|
| 安全 | PASS — 仅脱敏输出 api_keySSH 凭据来自 DB 加密字段 |
| 行为 | PASS — 只读诊断,无 CUD |
## Closure
- `pytest tests/test_agent_remote_diagnose.py -q` PASS
## DoD
- [x] changelog + audit
- [ ] 生产 `/opt/nexus` 同步脚本并验证 `--id 95`
@@ -0,0 +1,42 @@
# 审计 — 告警分页 + 调度单次 fire_at 修复(2026-06-09
**Changelog**: `docs/changelog/2026-06-09-alerts-schedule-fixes-deploy.md`
## 变更文件清单
| 文件 | 说明 |
|------|------|
| `AlertsPage.vue` | 每页条数状态 + `fetchPagePerPage` |
| `settings.py` | 告警列表 `per_page` 上限 200 |
| `schema_path_validators.py` | `coerce_optional_iso_datetime` |
| `schemas.py` | `ScheduleCreate`/`ScheduleUpdate` `fire_at``datetime` |
| `test_schedules.py` | once 推送创建集成测试 |
| `prod_health_check.sh` | 生产公开/鉴权契约巡检 |
| `index.html` | vite 构建 hash 更新 |
## Step 3 规则扫描
| 规则 | 结论 |
|------|------|
| 无静默吞错 | PASS — `loadAlerts` 失败 snackbar |
| 调度 CUD 审计 | PASS — 未改 audit 写入逻辑 |
| `fire_at` 类型安全 | PASS — schema 层解析,避免 ORM 500 |
| 告警分页契约 | PASS — 与 `fetchPagePerPage` / 后端 `page`/`per_page` 一致 |
| 无明文密钥 | PASS — 健康脚本用环境变量/SSH,不入仓 |
## Closure
| 项 | 结果 |
|----|------|
| `test_schedules.py` | 2 passed |
| `test_alerts_audit.py` | 4 passed |
| `vite build` | OK |
| 生产复现 once 500 | 已确认根因;`3864d12` 部署后容器 healthy |
| 部署验证报告 | `docs/reports/2026-06-09-alerts-schedule-fixes-deploy-verification.md` |
## DoD
- [x] 告警每页数目与 API `per_page` 联动
- [x] 单次文件推送调度保存 201(非 500)
- [x] Changelog + 本审计
- [x] 生产部署与健康检查(`/health` ok,容器 healthy;鉴权 UI 待用户终验)
@@ -0,0 +1,42 @@
# 审计 — 接续 Wave0 + D-012026-06-09
**Changelog**: `docs/changelog/2026-06-09-continuation-wave0-d01.md`
## 变更文件清单
| 文件 | 说明 |
|------|------|
| `scripts/prod_probe.py` | 生产时区验收:422、cron next_run、bundle |
| `deploy/sync_webapp_to_container.sh` | upgrade 后同步 Git web/app 进容器 |
| `deploy/nexus-1panel.sh` | upgrade 末尾调用 sync |
| `deploy/deploy-production.sh` | Docker 部署后 sync |
| `deploy/deploy-frontend.sh` | sudo docker 运行时探测 |
| `server/utils/agent_deploy.py` | D-01 统一 upgrade 命令 |
| `server/api/servers.py` | 单台 upgrade 用 agent_deploy |
| `server/application/services/server_batch_service.py` | 批量 upgrade + agent_port |
| `tests/test_agent_deploy.py` | 单测 |
## Step 3 规则扫描
| 规则 | 结论 |
|------|------|
| 密钥 | PASS — 无凭据入仓 |
| 静默吞错 | PASS — sync 失败 warnprobe 显式断言 |
| Shell 注入 | PASS — docker cp 路径来自 NEXUS_ROOT |
| 分层 | PASS — agent_deploy 在 utilsAPI 薄编排 |
## Closure
| 项 | 结果 |
|----|------|
| `test_agent_deploy.py` | passed |
| `pre_deploy_check.sh` | 7/7 |
| `prod_health_check.sh` origin | passed |
| 生产 `sync_webapp_to_container.sh` | index-mgwiCK5j.js OK |
## DoD
- [x] changelog + audit + Handoff 2026-06-09
- [x] 门控 7/7
- [x] 生产探针全绿
- [x] D-01 alignment-plan ☑
@@ -0,0 +1,37 @@
# 审计 — Dashboard 24h 舰队趋势(B-02
**Changelog**: `docs/changelog/2026-06-09-dashboard-24h-fleet-metrics.md`
## 变更文件清单
| 文件 | 说明 |
|------|------|
| `server/utils/fleet_metrics.py` | 舰队均值聚合 |
| `server/infrastructure/database/fleet_metric_repo.py` | 采样 CRUD + purge |
| `server/background/heartbeat_flush.py` | 10min 写入样本 |
| `server/api/servers.py` | `fleet-metrics` API |
| `server/infrastructure/database/migrations.py` | `fleet_metric_samples` 表 |
| `frontend/src/components/dashboard/DashboardFleetTrends.vue` | v-sparkline |
| `frontend/src/pages/DashboardPage.vue` | 挂载趋势卡 |
| `tests/test_fleet_metrics.py` | 单测 + API |
## Step 3 规则扫描
| 规则 | 结论 |
|------|------|
| 分层 | PASS — API 只读,聚合在 utils/repo |
| 性能 | PASS — 7 天约 1k 行;purge 每 flush |
| 静默吞错 | PASS — 前端 load 失败显示空状态 |
## Closure
| 项 | 结果 |
|----|------|
| `test_fleet_metrics.py` | passed |
| `npm run type-check` | passed |
## DoD
- [x] design + plan + changelog
- [x] B-02 alignment-plan 待勾选
- [x] 无新 npm 依赖
@@ -0,0 +1,26 @@
# 审计 — 移除舰队趋势 CSV 导出
**Changelog**: `docs/changelog/2026-06-09-fleet-trend-remove-csv.md`
## 变更文件
| 文件 | 说明 |
|------|------|
| `DashboardFleetTrends.vue` | 移除 CSV 按钮与 exportCsv |
| `fleetTrendAnalytics.ts` | 删除 exportFleetTrendCsv |
## Step 3
| 规则 | 结论 |
|------|------|
| 安全 | PASS — 纯 UI 删除,无新入口 |
| 行为 | PASS — 全屏明细表保留 |
## Closure
- `npm run type-check` PASS
## DoD
- [x] changelog + audit
- [x] 生产前端同步(`53b700f`index-B00VieI3.js
@@ -0,0 +1,66 @@
# 审计 — 全站测试体系交付(2026-06-09
**Changelog**: `docs/changelog/2026-06-09-full-site-testing-delivery.md`
## 变更文件清单
| 文件 | 说明 |
|------|------|
| `tests/integration/test_*.py` | IT 域集成测试(14 文件) |
| `tests/chain/test_*.py` | 链路测试(8 文件,含 batch/exec 完成态) |
| `tests/conftest.py` | 共享 fixture / SQLite 兼容 |
| `tests/acceptance_catalog.py` | 验收目录扩展 |
| `frontend/e2e/fixtures.mjs` | Worker 级 API 登录 + cookie 轮换写回 |
| `frontend/e2e/helpers.mjs` | 会话恢复 + 侧栏 `nav` |
| `frontend/e2e/pages/*.spec.mjs` | 页域 E2E18 |
| `frontend/e2e/full-acceptance.spec.mjs` | 15 步 serial 验收 |
| `frontend/package.json` | `test:e2e` 脚本 |
| `.gitea/workflows/ci-cd.yml` | CI 分层 pytest + E2E job |
| `.gitignore` | 忽略 `frontend/e2e/.auth/``test-results/` |
| `scripts/run_nexus_acceptance.py` | 验收脚本对齐 catalog |
| `docs/testing/test-case-matrix.md` | 矩阵状态更新 |
| `web/app/index.html` | vite build 入口 hash 更新 |
| `frontend/src/api/index.ts` | 校验错误中文格式化接入 |
| `frontend/src/utils/apiError.ts` | API 错误展示 |
| `frontend/src/utils/validationErrorFormat.ts` | Pydantic detail → 中文 |
| `frontend/src/pages/PushPage.vue` | 推送页异步提交 UX |
| `frontend/src/pages/ServersPage.vue` | 服务器列表(无 CSV 导出) |
| `frontend/src/components/push/PushToolbar.vue` | 推送工具栏 |
| `frontend/src/composables/push/usePushProgress.ts` | 推送进度 composable |
| `server/api/sync_v2.py` | sync 校验与异步推送 |
| `server/background/sync_push_runner.py` | 后台推送 runner |
| `server/utils/validation_errors_zh.py` | 后端校验中文映射 |
| `tests/test_validation_errors_zh.py` | 中文校验单测 |
## Step 3 规则扫描
| 规则 | 结论 |
|------|------|
| 无静默吞错(测试 assert 真实行为) | PASS — 失败用例已修至 green |
| 无明文密钥入仓 | PASS — E2E 密码仅环境变量 |
| 分层:API 不测 UI、E2E 不替代 IT | PASS — 职责分离 |
| E2E 并行不触发 login lockout | PASS — `fixtures.mjs` 每 worker 一次 API 登录 |
| Refresh token 轮换 | PASS — 上下文结束写回 `storageState` |
| SQLite/MySQL 测试兼容 | PASS — `SettingRepository` 等 monkeypatch/直连 |
| CI 不阻塞于缺 E2E secret | PASS — E2E job 条件触发 |
| 推送异步不阻塞 UI / 校验中文 | PASS — sync_v2 + PushPage + validation_errors_zh |
## Closure
| 项 | 结果 |
|----|------|
| pytest integration+chain | 27 passed |
| pytest fast suite | 427 passed |
| pytest slow (IT-INST) | 1 passed |
| Playwright e2e | 35 passed |
| local_integration_smoke | OK |
| pre_deploy_check Gate 36 | PASS(本审计前) |
| MCP-INST-001 各 step UI | ⏭ 用户确认不纳入验收 |
## DoD
- [x] Wave 15 矩阵 IT/CH/E2E 缺口落地并有命令证据
- [x] Playwright 35/35 本地 green(清理 assets 后复验)
- [x] Changelog + 设计/计划/报告文档齐全
- [x] 门控可过(今日 changelog + 本审计)
- [ ] 生产部署(待用户批准 `deploy-production.sh`
@@ -0,0 +1,29 @@
# 审计 — 离线统计准确性
**Changelog**: `docs/changelog/2026-06-09-offline-count-accuracy.md`
## 变更文件
| 文件 | 说明 |
|------|------|
| `server_connectivity.py` | 逐台 Redis 计数 + status 筛选 |
| `servers.py` | stats/list 对齐 |
| `ServersPage.vue` | `offline_list` 展示 |
| `test_server_connectivity.py` | 单测 |
| `test_servers_dashboard.py` | stats 字段断言 |
## Step 3
| 规则 | 结论 |
|------|------|
| 安全 | PASS — 只读聚合 |
| 行为 | PASS — 离线=Agent 监控且 Redis 非在线 |
## Closure
- `pytest tests/test_server_connectivity.py -q` PASS
## DoD
- [x] changelog + audit
- [x] 生产部署(含于 `7209f53`
@@ -0,0 +1,57 @@
# 审计 — 运维时区全局北京(2026-06-09
**Changelog**: `docs/changelog/2026-06-09-operator-timezone-beijing.md` · `docs/changelog/2026-06-09-timezone-followups-ws-install-dates.md`
**报告**: `docs/reports/2026-06-09-operator-timezone-beijing-audit.md`
## 变更文件清单
| 文件 | 说明 |
|------|------|
| `server/utils/display_time.py` | `operator_wall``beijing_day_range_utc` |
| `server/background/schedule_runner.py` | 北京墙钟 cron 匹配 |
| `server/infrastructure/database/audit_log_repo.py` | 审计日期北京日界 |
| `server/api/settings.py` | 告警 date 筛选、`utcnow` 修复 |
| `frontend/src/utils/datetime.ts` | `beijingWallParts` |
| `frontend/src/utils/scheduleCycle.ts` | 与 runner 对齐 |
| `ScheduleCyclePicker.vue` | 北京时间提示 |
| `tests/test_operator_tz.py` | 运维时区单测 |
| `tests/test_schedule_next_run.py` | 更新北京语义断言 |
| `server/api/websocket.py` | WS 告警/恢复消息增加 `at`UTC ISO |
| `frontend/src/composables/useWebSocket.ts` | 使用 `msg.at` 展示北京时刻 |
| `server/api/install.py` | 安装时区固定 `Asia/Shanghai` 校验 |
| `web/app/install.html` | 移除误导性时区下拉,固定说明文案 |
| `web/app/index.html` | 前端构建入口 hash 更新 |
| `tests/test_ws_alert_at.py` | WS `at` 字段单测 |
| `tests/integration/test_alerts_audit.py` | 非法日期 422 集成测 |
| `frontend/e2e/pages/schedules-timezone.spec.mjs` | 调度北京时间 E2E |
## Step 3 规则扫描
| 规则 | 结论 |
|------|------|
| PY-11 naive/aware | PASS — `to_naive_utc` / `ensure_utc` |
| 调度双触发 | PASS — 60s 冷却保留 |
| SQL 注入 | PASS — 日期 `fromisoformat`,无拼接 |
| 静默吞错 | PASS — 非法 `date_from`/`date_to` 返回 422 |
| 存量 cron 语义 | RISK-P2 — 无迁移;部署后人工核对调度列表 |
## Closure
| 项 | 结果 |
|----|------|
| `test_operator_tz.py` | passed |
| `test_schedule_next_run.py` | passed |
| `test_ws_alert_at.py` | passed |
| `test_alerts_audit.py` (422) | passed |
| `scripts/local_verify.sh` | All local checks passed |
| `npm run type-check` | passed |
| ruff F | 0 violations |
## DoD
- [x] cron 按北京时间匹配,`next_run` 与表单时:分一致
- [x] `cycleFromSchedule``parseApiDateTime` + 北京时:分
- [x] 审计 `date_from` 覆盖北京当日 00:0023:59
- [x] design spec + plan + changelog
- [x] WS 告警 `at`、安装时区固定、日期 422
- [ ] 生产部署后人工核对启用中的调度(用户确认几乎无)
@@ -0,0 +1,153 @@
# 审计 — 子机离线告警 + 仪表盘统计/舰队趋势(`7209f53`)
**Changelog**:
- `docs/changelog/2026-06-08-server-offline-alert.md`
- `docs/changelog/2026-06-09-dashboard-stat-cards-align.md`
- `docs/changelog/2026-06-09-fleet-trend-alerts.md`
- `docs/changelog/2026-06-09-fleet-trend-interaction.md`
- `docs/changelog/2026-06-09-offline-count-accuracy.md`
**部署验证**: `docs/reports/2026-06-09-server-offline-alert-deploy-verification.md`
## 审计信息
- **日期**: 2026-06-09
- **审计人**: Cursor Agent
- **触发原因**: 新功能(离线 Telegram 告警、设置开关)+ 仪表盘统计/趋势增强;已部署生产,补全门控审计
## 审计范围
| 文件 | 行数 | 状态 |
|------|------|------|
| `server/background/server_offline_monitor.py` | 88 | ☑ 已审 |
| `server/api/websocket.py` | +37 | ☑ 已审 |
| `server/infrastructure/telegram/__init__.py` | +37 | ☑ 已审 |
| `server/utils/notify_toggles.py` | +5 | ☑ 已审 |
| `server/config.py` | +2 | ☑ 已审 |
| `server/api/settings.py` | +1 | ☑ 已审 |
| `server/main.py` | +6 | ☑ 已审 |
| `server/application/server_connectivity.py` | +7 | ☑ 已审 |
| `server/utils/fleet_alert_buckets.py` | 88 | ☑ 已审 |
| `server/api/servers.py` | +18 | ☑ 已审 |
| `frontend/src/types/api.ts` | +1 | ☑ 已审 |
| `frontend/src/composables/useServerStatsCards.ts` | 76 | ☑ 已审 |
| `frontend/src/components/StatCardsRow.vue` | 136 | ☑ 已审 |
| `frontend/src/pages/DashboardPage.vue` | Δ | ☑ 已审 |
| `frontend/src/pages/ServersPage.vue` | Δ | ☑ 已审 |
| `frontend/src/components/dashboard/DashboardFleetTrends.vue` | 363 | ☑ 已审 |
| `frontend/src/components/dashboard/FleetTrendEChart.vue` | 新 | ☑ 已审 |
| `frontend/src/components/dashboard/FleetTrendKpiStrip.vue` | 新 | ☑ 已审 |
| `frontend/src/utils/fleetTrendAnalytics.ts` | 新 | ☑ 已审 |
| `frontend/package.json` / `package-lock.json` | echarts 依赖 | ☑ 已审 |
| `tests/test_server_offline_monitor.py` | 85 | ☑ 已审 |
| `tests/test_fleet_alert_buckets.py` | 新 | ☑ 已审 |
## 审计8步结果
### Step 1: 登记 ✅
提交 `7209f53`,范围见上表;设计/计划文档齐全。
### Step 2: 全文 Read ✅
上述文件均已通读;无跨层直连(monitor → websocket → telegramfleet-metrics → repo)。
### Step 3: 规则扫描 H
| ID | 规则 | 命中点 | 初判 |
|----|------|--------|------|
| H1 | 无明文密钥 | config/settings | SAFE |
| H2 | 无静默吞错 | `server_offline_monitor` L86-87 `exc_info=True` | SAFE |
| H3 | Telegram HTML 转义 | `send_telegram_offline_alert` `html.escape` name/hb | SAFE |
| H4 | 通知开关门控 | `offline_alert_notify_enabled` + settings `MUTABLE_KEYS` | SAFE |
| H5 | 边沿触发防轰炸 | `_prev_online` + `_should_push_telegram` 5min | SAFE |
| H6 | 冷启动不误报 | `prev_online is None` 只记录 | SAFE |
| H7 | 仅 primary worker | `main.py` 与 self_monitor 同锁 | SAFE |
| H8 | API 鉴权 | `/fleet-metrics` `get_current_admin` | SAFE |
| H9 | CUD 审计 | `notify_alert_offline` 走既有 PUT settings + audit | SAFE |
| H10 | 离线判定一致性 | `heartbeat_indicates_online` 与 stats/list 共用 | SAFE |
| H11 | 前端 XSS | 统计数字字符串化;ECharts 无 `v-html` 用户输入 | SAFE |
| H12 | 依赖体积 | echarts ~625kB chunk | OBS(已知,懒加载页内) |
### Step 4: Closure表
| ID | 判定 | 依据 |
|----|------|------|
| H1 | SAFE | 无新增密钥字段 |
| H2 | SAFE | tick 失败打 error 日志,不掩盖 |
| H3 | SAFE | 与现有 `send_telegram_alert` 一致转义 |
| H4 | SAFE | 关开关不推 Telegram`alert_logs`/WS 仍记录(与资源告警策略一致) |
| H5 | SAFE | 持续离线不重复;恢复后再离线可再推 |
| H6 | SAFE | 单测 `test_cold_start_no_alert` |
| H7 | SAFE | `_background_tasks` 仅 primary 注册 |
| H8 | SAFE | 管理员 JWT |
| H9 | SAFE | `MUTABLE_KEYS``notify_alert_offline` |
| H10 | SAFE | `test_server_connectivity` |
| H11 | SAFE | Vue 文本绑定 |
| H12 | ACCEPT | 仪表盘页专用 chunk,可后续 code-split 优化 |
### Step 5: 入口表
| 入口 | 鉴权 | 说明 |
|------|------|------|
| 后台 `server_offline_monitor_loop` | primary worker | Redis 读 + DB 读 |
| `broadcast_offline_alert` | 内部调用 | WS + alert_logs + Telegram |
| `PUT /api/settings/notify_alert_offline` | admin JWT | 开关 |
| `GET /api/servers/fleet-metrics` | admin JWT | 趋势+告警挂载 |
| `GET /api/servers/stats` | admin JWT | 统计卡(既有) |
| 设置页 `NOTIFY_TOGGLE_ITEMS` | 前端 | 展示开关 |
### Step 6: 输入 → Sink
| 路径 | Sink | 防护 |
|------|------|------|
| Redis `heartbeat:*` | 离线判定 | 只读;server_id 来自 DB 整数主键 |
| `server_name` / `last_heartbeat` | Telegram HTML | `html.escape` |
| `hours` query | fleet-metrics | `max(1,min(hours,168))` |
| settings value | DB settings | 布尔字符串白名单流程 |
### Step 7: 归类
```
server_offline_monitor.py 88行 12H 0FINDING
websocket.py (offline块) 37行 12H 0FINDING
telegram/__init__.py 37行 12H 0FINDING
fleet_alert_buckets.py 88行 12H 0FINDING
servers.py (fleet-metrics) 18行 12H 0FINDING
frontend (统计+趋势) — 12H 0FINDING
tests — 12H 0FINDING
──────────────────────────────────────────────
总计 12H 0FINDING
```
### Step 8: DoD ✅
- [x] 设计文档 + 实施说明 + changelog
- [x] 单测:`test_server_offline_monitor` 4、`test_fleet_alert_buckets``test_server_connectivity``test_fleet_metrics`
- [x] `pytest` 相关 51 项通过(offline+fleet+notify
- [x] 门控 pre_deploy 7/7(部署前)
- [x] 生产部署 `/health` ok、`/app/` 200`7209f53`
- [ ] 浏览器终验:设置页「子机离线告警」开关 + 实机离线 Telegram(运维操作)
## FINDING 列表
无(0 FINDING
## OBSERVATION(非阻断)
1. **离线发现延迟**:与 UI 一致,依赖 Redis `heartbeat:*` TTL`FLUSH_INTERVAL×1.5` ≈ 15min)或键缺失;monitor 30s 轮询但状态变更是 TTL 驱动。设计文档已说明,与既有在线判定同源。
2. **舰队趋势 tooltip**`fleet_alert_buckets.ALERT_TYPE_LABELS` 未含 `offline`,趋势图钉显示原始 `offline` 而非「离线」——纯展示,可后续 1 行补齐。
3. **`_prev_online` 内存**:删除子机后字典条目残留,量级可忽略;primary 重启会冷启动。
## 验证命令
```bash
pytest tests/test_server_offline_monitor.py tests/test_fleet_alert_buckets.py \
tests/test_server_connectivity.py tests/test_fleet_metrics.py \
tests/test_notify_toggle_sync.py -q
# 51 passed
```
## 结论
**审计通过,0 FINDING**,已部署生产;待用户浏览器/实机离线 Telegram 终验。
@@ -0,0 +1,32 @@
# 审计 — 服务器页顶部统计卡交互
**Changelog**: `docs/changelog/2026-06-09-servers-stat-card-filter.md` · `docs/changelog/2026-06-09-docker-frontend-sync-fix.md`
## 变更文件
| 文件 | 说明 |
|------|------|
| `StatCardsRow.vue` | 可点击/active 态;5 卡布局 |
| `useServerPagination.ts` | `is_online` 筛选 |
| `ServersPage.vue` | 统计卡交互;未设路径定位 |
| `ServerUnsetPathPanel.vue` | 高亮 id |
| `servers.py` | stats `unset_path` |
| `deploy-production.sh` | Docker 部署前本地 vite build |
| `test_servers_dashboard.py` | unset_path 断言 |
## Step 3
| 规则 | 结论 |
|------|------|
| 安全 | PASS — 只读 stats + 既有 list 筛选 |
| 行为 | PASS — 总数不可点;离线/在线/未设路径/告警可交互 |
## Closure
- `npm run type-check` PASS
- `pytest tests/integration/test_servers_dashboard.py -q` PASS
## DoD
- [x] changelog + audit
- [ ] 生产部署验证
@@ -0,0 +1,46 @@
# 审计 — 服务层 ValueError 中文化 + HTTP detail 翻译层部署
**Changelog**: `docs/changelog/2026-06-09-service-valueerror-zh.md` · `docs/changelog/2026-06-09-http-api-detail-zh.md`
## 变更文件清单(本次 commit)
| 文件 | 说明 |
|------|------|
| `server/api/schemas.py` | batch_id 校验文案中文化 |
| `server/application/services/script_service.py` | 执行记录不存在 |
| `server/application/services/server_batch_service.py` | 未知批量 op |
| `server/infrastructure/database/crypto.py` | 凭据解密失败 |
| `server/infrastructure/redis/script_execution_store.py` | Redis 执行记录不存在 |
| `server/infrastructure/ssh/asyncssh_pool.py` | SSH 凭据缺失 |
| `server/utils/schedule_cycle.py` | cron / 周期类型校验 |
## 一并部署(已在 main,非本 commit diff
| 文件 | 说明 |
|------|------|
| `server/utils/http_errors_zh.py` | 管理端 HTTP detail 翻译层(A |
| `server/main.py` | 全局 exception handler |
| `server/api/auth.py` | auth_failure_detail |
## Step 3 规则扫描
| 规则 | 结论 |
|------|------|
| 安全 | PASS — 仅改用户可见文案,无鉴权变更 |
| 行为 | PASS — 异常类型与 HTTP 状态码不变 |
| Agent API | PASS — `/api/agent/*` 仍跳过翻译 |
## Closure
| 项 | 结果 |
|----|------|
| `test_schedule_cycle.py` | passed |
| `test_http_errors_zh.py` | passed |
| `test_validation_errors_zh.py` | passed |
## DoD
- [x] changelog
- [x] 定向审计
- [ ] 生产 deploy + 健康检查
- [ ] 浏览器 spot-check 404 detail 中文
@@ -0,0 +1,29 @@
# 审计 — SSH 密钥预设列表修复
**Changelog**: `docs/changelog/2026-06-09-ssh-key-preset-list-fix.md`
## 变更文件
| 文件 | 说明 |
|------|------|
| `CredentialsManager.vue` | `http.getList` 替代 `fetchPagePerPage`;保存校验 |
| `server/api/settings.py` | 列表 `private_key_set`;保存 PEM 校验 |
| `server/utils/ssh_key_pem.py` | PEM 校验 + 公钥推导 |
| `tests/test_ssh_key_pem.py` | RSA / OPENSSH 单测 |
| `web/app/index.html` | Vite 构建产物入口 |
## Step 3
| 规则 | 结论 |
|------|------|
| 安全 | PASS — 私钥仍仅存服务端加密,列表不返回私钥 |
| 行为 | PASS — 与密码预设 Tab 一致 |
## Closure
- `npm run type-check` PASS
## DoD
- [x] changelog + audit
- [ ] 生产前端热更新

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