commit 1933f0af6e647818e716ed194cdb92d7ed7bb47a Author: Codex Release Bot Date: Wed Jul 8 22:31:31 2026 +0800 release: nexus btpanel session fix and app-v2 diff --git a/.cursor/mcp.json b/.cursor/mcp.json new file mode 100644 index 00000000..f72237c5 --- /dev/null +++ b/.cursor/mcp.json @@ -0,0 +1,9 @@ +{ + "mcpServers": { + "mysql-mcp": { + "command": "bash", + "args": ["scripts/linux_mcp_mysql.sh"], + "env": {} + } + } +} diff --git a/.cursor/plans/nexus_鍏ㄩ潰浠g爜瀹¤_a33e6fc2.plan.md b/.cursor/plans/nexus_鍏ㄩ潰浠g爜瀹¤_a33e6fc2.plan.md new file mode 100644 index 00000000..0e6519f3 --- /dev/null +++ b/.cursor/plans/nexus_鍏ㄩ潰浠g爜瀹¤_a33e6fc2.plan.md @@ -0,0 +1,608 @@ +--- +name: Nexus 全面代码审计 +overview: 对 Nexus 6.0 worktree 进行只读全面扫描,按 8 类问题输出带位置、严重度与修复建议的审计报告;WSL Node20/MCP 本地权限任务已交付,不在本次重复实施。 +todos: + - id: fix-p0-webssh + content: "P0: WebSSH 强制 JWT server_id 绑定 + 失败时 close WebSocket" + status: completed + - id: fix-p1-xss + content: "P1: servers/scripts 等 innerHTML 字段统一 esc()" + status: pending + - id: fix-p1-agent-apikey + content: "P1: 收敛 Agent exec + API Key reveal + compare_digest" + status: pending + - id: fix-p2-heartbeat-stats + content: "P2: 心跳仅 Redis + 在线统计与列表数据源一致" + status: pending + - id: fix-p2-hardening + content: "P2: install 路由锁定、TOTP disable 二次验证、全局 JWT 中间件" + status: pending + - id: add-security-tests + content: 补充 auth/webssh/agent 集成测试与 pip-audit CI + status: pending +isProject: false +--- + +# Nexus 6.0 全面代码审计报告 + +**范围**: [`server/`](server/)、[`web/app/`](web/app/)、[`scripts/`](scripts/)、[`deploy/`](deploy/)、[`requirements.txt`](requirements.txt) +**模式**: 只读扫描(未改代码) +**说明**: 你反复收到的「WSL Node 20 + 本地库最大权限」计划**已在 worktree 中实现并验证**(Node v20.19.2、MYSQL_READONLY=false、GRANT ALL、MCP Connected)。请勿再重复执行该计划;下文为**新的**全项目审计。 + +**严重度**: P0 致命 / P1 高 / P2 中 / P3 低 / Info 优化建议 + +**审计统计(地毯式,2026-05-22)** + +| 类别 | 文件数 | 状态 | +|------|--------|------| +| `server/` Python | 55 | 55/55 已逐文件读/扫 | +| `web/` 前端+Agent | 23 | 23/23 已逐文件读/扫 | +| `scripts/` | 8 | 8/8 已审计 | +| `deploy/` | 6 | 6/6 已审计 | +| `tests/` + 根目录测试 | 8 | 8/8 已审计 | +| 根配置/CI/MCP | 5 | 5/5 已审计 | +| `docs/` 文档(非可执行) | ~350 | 密钥模式扫描,无新增硬编码密钥 | + +**P0 共 4 项** | **P1 共 18 项**(含 sysctl 注入补遗) + +--- + +## 0. P0 / P1 问题总表(复查用) + +### P0(4) + +| ID | 文件 | 问题 | +|----|------|------| +| S-01 | `server/api/webssh.py` | 标准 JWT 无 `server_id` 时可 IDOR 连任意服务器 SSH | +| S-02 | `server/api/agent.py` | `/api/agent/exec` + `subprocess_shell` + 全局 API Key = 中枢 RCE | +| S-13 | `tests/load_test.py`, `tests/quick_load.py` | **仓库内硬编码 `msk-ufkvjm8...` API Key**(若生产密钥则灾难性泄露) | +| S-14 | `web/agent/agent.py` | 子机 Agent `/exec` 同样 shell 执行;`verify_api_key` 用 `!=` 非常量时间 | + +### P1(18,节选) + +| ID | 文件 | 问题 | +|----|------|------| +| **S-19** | `server/application/services/sync_engine_v2.py` L234-236 | **sysctl/echo 命令注入**:`sysctl -w {key}={safe_value}` 仅 quote 值,含 `'` 可打断 shell | +| **S-20** | `server/application/services/sync_engine_v2.py` L281-284 | **rollback 命令注入**:`backup_file` 未 quote 拼入 `cp ... && sysctl --system` | +| S-03 | `server/api/settings.py` | `/api-key/reveal` 回读全局 API_KEY | +| S-04 | `web/app/servers.html` | `category`/`agent_version` 未 esc → XSS | +| S-05 | `web/app/scripts.html` | `category` 未 esc → XSS | +| S-06 | `web/app/api.js` | JWT 存 localStorage | +| S-07 | `web/app/index.html`, `terminal.html` | WS JWT 在 URL | +| S-08 | `server/api/install.py` | 生产仍暴露无认证 install API | +| S-09 | `server/api/auth.py` | TOTP disable 无二次验证 | +| S-15 | `server/api/agent.py` | 全局 Key 可伪造任意无 per-key 服务器的心跳 | +| S-16 | `web/install.php` | 遗留 PHP 安装面(与 install.html 双轨) | +| S-17 | `server/main.py` + FastAPI | `/docs` `/openapi.json` 未鉴权暴露 API 结构 | +| R-01 | `server/infrastructure/database/crypto.py` | AES-CBC 遗留 + 解密失败返回密文 | +| E-01 | `server/main.py` | 无全局 JWT 中间件,仅靠各路由 Depends | +| P-01 | `server/api/agent.py` | 心跳直写 MySQL | +| B-01 | 同 S-01 | WebSSH IDOR(功能+安全双重) | +| S-18 | `server/application/services/script_service.py` | 调用 Agent 用 `http://` 明文,MITM 可篡改命令 | + +--- + +## 9. 逐文件审计打勾(可执行代码 99 个) + +### server/(55) + +- [x] `server/__init__.py` 已审计 ✓ — 无问题 +- [x] `server/main.py` 已审计 ✓ — E-01 无全局 JWT;install 路由常开;InstallMode 放行 `/ws/`;SecurityHeaders 无 CSP +- [x] `server/config.py` 已审计 ✓ — R-02 默认 `root:password` DATABASE_URL +- [x] `server/api/__init__.py` 已审计 ✓ +- [x] `server/api/agent.py` 已审计 ✓ — **P0 S-02**;P-01;S-15;S-12 timing +- [x] `server/api/assets.py` 已审计 ✓ — JWT 保护完整 +- [x] `server/api/auth.py` 已审计 ✓ — S-09 totp/disable +- [x] `server/api/auth_jwt.py` 已审计 ✓ — PUBLIC_PREFIXES 未作全局中间件;逻辑正确 +- [x] `server/api/dependencies.py` 已审计 ✓ — S-10 危险命令仅警告 +- [x] `server/api/health.py` 已审计 ✓ — 公开合理 +- [x] `server/api/install.py` 已审计 ✓ — **S-08**;C-03 阻塞 subprocess;lock 无 JWT +- [x] `server/api/schemas.py` 已审计 ✓ — Pydantic 校验良好 +- [x] `server/api/scripts.py` 已审计 ✓ — JWT;凭据脱敏需对照响应 +- [x] `server/api/search.py` 已审计 ✓ — LIKE 转义 +- [x] `server/api/servers.py` 已审计 ✓ — B-03/P-02;agent-key 返回明文 key(设计如此,需审计日志) +- [x] `server/api/settings.py` 已审计 ✓ — **S-03** reveal +- [x] `server/api/sync_v2.py` 已审计 ✓ — browse 用 shlex.quote +- [x] `server/api/webssh.py` 已审计 ✓ — **P0 S-01**;B-02 未 close +- [x] `server/api/websocket.py` 已审计 ✓ — S-07;P-03 多 worker 去重 +- [x] `server/application/__init__.py` 已审计 ✓ +- [x] `server/application/services/__init__.py` 已审计 ✓ +- [x] `server/application/services/auth_service.py` 已审计 ✓ — R-07 旧 refresh +- [x] `server/application/services/script_service.py` 已审计 ✓ — S-18 HTTP;test_cmd 含密码若暴露 API 则 P1 +- [x] `server/application/services/server_service.py` 已审计 ✓ +- [x] `server/application/services/sync_engine_v2.py` 已审计 ✓ — **P1 S-19/S-20 sysctl 命令注入**(初版漏报);rsync 用 shlex +- [x] `server/application/services/sync_service.py` 已审计 ✓ +- [x] `server/background/__init__.py` 已审计 ✓ +- [x] `server/background/heartbeat_flush.py` 已审计 ✓ — 与 B-04 应对齐 +- [x] `server/background/retry_runner.py` 已审计 ✓ +- [x] `server/background/schedule_runner.py` 已审计 ✓ +- [x] `server/background/self_monitor.py` 已审计 ✓ +- [x] `server/domain/__init__.py` 已审计 ✓ +- [x] `server/domain/models/__init__.py` 已审计 ✓ — 敏感列设计合理 +- [x] `server/domain/repositories/__init__.py` 已审计 ✓ +- [x] `server/infrastructure/__init__.py` 已审计 ✓ +- [x] `server/infrastructure/database/__init__.py` 已审计 ✓ +- [x] `server/infrastructure/database/admin_repo.py` 已审计 ✓ — 登录限速仅 username+ip +- [x] `server/infrastructure/database/audit_log_repo.py` 已审计 ✓ +- [x] `server/infrastructure/database/crypto.py` 已审计 ✓ — R-01;B-07 +- [x] `server/infrastructure/database/db_credential_repo.py` 已审计 ✓ +- [x] `server/infrastructure/database/migrations.py` 已审计 ✓ +- [x] `server/infrastructure/database/password_preset_repo.py` 已审计 ✓ +- [x] `server/infrastructure/database/platform_node_repo.py` 已审计 ✓ +- [x] `server/infrastructure/database/push_schedule_repo.py` 已审计 ✓ +- [x] `server/infrastructure/database/script_repo.py` 已审计 ✓ +- [x] `server/infrastructure/database/server_repo.py` 已审计 ✓ +- [x] `server/infrastructure/database/session.py` 已审计 ✓ +- [x] `server/infrastructure/database/setting_repo.py` 已审计 ✓ +- [x] `server/infrastructure/database/ssh_session_repo.py` 已审计 ✓ +- [x] `server/infrastructure/database/sync_log_repo.py` 已审计 ✓ +- [x] `server/infrastructure/redis/__init__.py` 已审计 ✓ +- [x] `server/infrastructure/redis/client.py` 已审计 ✓ +- [x] `server/infrastructure/ssh/__init__.py` 已审计 ✓ +- [x] `server/infrastructure/ssh/asyncssh_pool.py` 已审计 ✓ — Info 压测 +- [x] `server/infrastructure/telegram/__init__.py` 已审计 ✓ + +### web/(23) + +- [x] `web/app/api.js` 已审计 ✓ — S-06;U-01 +- [x] `web/app/layout.js` 已审计 ✓ — 搜索 esc 良好 +- [x] `web/app/login.html` 已审计 ✓ — S-06;CDN 无 SRI +- [x] `web/app/index.html` 已审计 ✓ — S-07 +- [x] `web/app/servers.html` 已审计 ✓ — **S-04** +- [x] `web/app/scripts.html` 已审计 ✓ — **S-05** +- [x] `web/app/terminal.html` 已审计 ✓ — S-07 +- [x] `web/app/settings.html` 已审计 ✓ — U-03 +- [x] `web/app/install.html` 已审计 ✓ — S-08 无认证 install fetch +- [x] `web/app/files.html` 已审计 ✓ — esc 一致 +- [x] `web/app/push.html` 已审计 ✓ — esc 良好 +- [x] `web/app/audit.html` 已审计 ✓ +- [x] `web/app/assets.html` 已审计 ✓ +- [x] `web/app/commands.html` 已审计 ✓ — S-11 onclick +- [x] `web/app/credentials.html` 已审计 ✓ — esc 良好 +- [x] `web/app/schedules.html` 已审计 ✓ +- [x] `web/app/retries.html` 已审计 ✓ — esc 良好 +- [x] `web/favicon.svg` 已审计 ✓ +- [x] `web/install.php` 已审计 ✓ — **S-16** 遗留安装 +- [x] `web/agent/agent.py` 已审计 ✓ — **P0 S-14** +- [x] `web/agent/agent.sh` 已审计 ✓ +- [x] `web/agent/install.sh` 已审计 ✓ — API key 进 config +- [x] `web/agent/config.example.json` 已审计 ✓ + +### scripts/(8)deploy/(6)tests+根(8)配置(5) + +- [x] `scripts/*.py/sh/sql/ps1` 8 文件 已审计 ✓ — R-05 R-06 +- [x] `deploy/*` 6 文件 已审计 ✓ — R-04 nginx 无 CSP +- [x] `tests/*` + `test_p1_p2.py` 8 文件 已审计 ✓ — **P0 S-13** 硬编码密钥 +- [x] `requirements.txt` `.env.example` `.gitea/workflows/ci-cd.yml` `.cursor/mcp.json` `PROJECT_HANDOVER.md` 已审计 ✓ — R-08;CI 无 pip-audit + +### docs/(~350,批量) + +- [x] `docs/**` 已审计 ✓ — 全文 rg 密钥模式,无新增 `msk-` 类硬编码(与 tests 除外) + +--- + +## 1. 功能性缺陷(Bug) + +| ID | 严重度 | 位置 | 描述 | 修复建议 | +|----|--------|------|------|----------| +| B-01 | P1 | [`server/api/webssh.py`](server/api/webssh.py) L94-97, L110-113 | JWT **无** `server_id` claim 时不校验路径 `server_id`,任意已登录管理员可改 URL 连任意服务器 SSH(IDOR) | 签发 WebSSH 专用短期 JWT(必含 `server_id`);或 `token_server_id is None` 时直接 `close(4003)` | +| B-02 | P1 | [`server/api/webssh.py`](server/api/webssh.py) L110-117 | 服务器不存在或凭据缺失时 `return` 未 `websocket.close()`,客户端挂起超时 | 统一 `accept` 后 `close(code, reason)` | +| B-03 | P2 | [`server/api/servers.py`](server/api/servers.py) L99-114 vs L58-63 | 列表用 Redis 心跳覆盖 `is_online`,`server_stats` 仍读 MySQL `is_online`,仪表盘与列表不一致 | 统计接口与列表共用 Redis 或统一写入策略 | +| B-04 | P2 | [`server/api/agent.py`](server/api/agent.py) L216-217 | 每次 Agent 心跳 `update_heartbeat` + `commit()`,与文档「Redis 实时 + 10min 批量落 MySQL」矛盾 | 心跳仅写 Redis,由 [`heartbeat_flush.py`](server/background/heartbeat_flush.py) 批量刷库 | +| B-05 | P2 | [`server/api/agent.py`](server/api/agent.py) L259-260 | `TimeoutError` 时 `proc.kill()` 未 `await proc.wait()`,可能僵尸进程 | `kill()` + `await communicate()` 或 `asyncio.create_task` 清理 | +| B-06 | P3 | [`web/app/api.js`](web/app/api.js) L97-101 | `sendBeacon` 登出可能不带 Bearer,后端若要求认证则注销无效 | 对齐后端 logout 契约或改用 `fetch` + `keepalive` | +| B-07 | P3 | [`server/infrastructure/database/crypto.py`](server/infrastructure/database/crypto.py) L75-83 | 解密失败返回密文字符串,SSH 可能用错误密码连接且难排查 | 解密失败抛 `DecryptionError`,API 返回明确 5xx/配置错误 | + +--- + +## 2. 安全漏洞 + +| ID | 严重度 | 位置 | 描述 | 修复建议 | +|----|--------|------|------|----------| +| S-01 | P0 | [`server/api/webssh.py`](server/api/webssh.py) + [`web/app/terminal.html`](web/app/terminal.html) L82-83 | WebSSH 横向越权(见 B-01)+ JWT 在 URL query | 见 B-01;WS 用首帧 auth 或短期 ticket,禁止 query token | +| S-02 | P0 | [`server/api/agent.py`](server/api/agent.py) L222-246 | `/api/agent/exec`:`create_subprocess_shell` 执行任意命令,仅全局 API Key | 命令白名单/参数化 `execve`;per-server key;审计 + 二次确认 | +| S-03 | P1 | [`server/api/settings.py`](server/api/settings.py) L60-67 | `POST /api-key/reveal` 对已登录管理员返回完整全局 API_KEY | 禁止回读;仅轮换时展示一次;或 per-server key | +| S-04 | P1 | [`web/app/servers.html`](web/app/servers.html) L161-162 | `category`、`agent_version` 未 `esc()` 写入 `innerHTML` → 存储型 XSS | 所有 API 字符串字段统一 `esc()` 或 DOM API | +| S-05 | P1 | [`web/app/scripts.html`](web/app/scripts.html) L36 附近 | `category` 列表渲染未转义 | 同 S-04 | +| S-06 | P1 | [`web/app/api.js`](web/app/api.js) + [`login.html`](web/app/login.html) | JWT 存 `localStorage`,XSS 即可窃取 refresh token | HttpOnly Cookie + CSRF;或严格 CSP + 消除 XSS | +| S-07 | P1 | [`web/app/index.html`](web/app/index.html) L208 | `/ws/alerts?token=` JWT 进 URL/日志/Referer | 同 S-01 WS 鉴权改造 | +| S-08 | P1 | [`server/api/install.py`](server/api/install.py) | 生产仍挂载 install 路由;`/lock`、`/status` 等无 JWT | 安装完成后卸载路由或 IP 白名单 + 一次性 token | +| S-09 | P1 | [`server/api/auth.py`](server/api/auth.py) L151-165 | `totp/disable` 仅需 JWT,无密码/TOTP 二次验证 | 要求 `current_password` + 有效 TOTP | +| S-10 | P2 | [`server/api/dependencies.py`](server/api/dependencies.py) L167-172 | 危险命令仅告警不阻断,管理员可批量远程执行 | 默认阻断高危模式;显式确认或 RBAC | +| S-11 | P2 | [`web/app/commands.html`](web/app/commands.html) L95,128 | `session_id` 嵌入 `onclick='...'` 未 JS 转义 | `data-*` + 事件委托 | +| S-12 | P2 | [`server/api/agent.py`](server/api/agent.py) L48-66 | API Key 用 `==` 比较,非常量时间 | `secrets.compare_digest` | +| S-13 | **P0** | [`tests/load_test.py`](tests/load_test.py) L18、[`tests/quick_load.py`](tests/quick_load.py) L5 | 硬编码 `msk-ufkvjm8hoe9j4ekiqpn1xd1gi8inuycu` 提交进仓库 | 立即轮换密钥;删硬编码;用 CI secret / `.env.test` | +| S-14 | **P0** | [`web/agent/agent.py`](web/agent/agent.py) L64-66, L101-117 | 子机 Agent `/exec` shell;`x_api_key != API_KEY` | 与 S-02 同策略;`compare_digest`;最小权限用户 | +| S-15 | P1 | [`server/api/agent.py`](server/api/agent.py) L148-150 | 全局 API Key 可向无 `agent_api_key` 的任意 `server_id` 伪造心跳 | 强制 per-server key;心跳绑定 server 证书 | +| S-16 | P1 | [`web/install.php`](web/install.php) | 遗留 PHP 5 步安装,可写 `.env`/建表,与 FastAPI install 双轨 | 删除或 nginx deny;仅保留 install.html | +| S-17 | P1 | [`server/main.py`](server/main.py) + FastAPI 默认 | `/docs` `/openapi.json` `/redoc` 生产可访问 | `docs_url=None` 或 Basic Auth | +| S-18 | P1 | [`server/application/services/script_service.py`](server/application/services/script_service.py) L216 | `http://{host}:{port}/api/exec` 明文 | 强制 HTTPS/mTLS | +| S-19 | **P1** | [`server/application/services/sync_engine_v2.py`](server/application/services/sync_engine_v2.py) L234-236 | `sysctl -w {key}={safe_value}` / `echo {key}={safe_value}`:仅 `shlex.quote(value)`,含 `'` 的 value 可逃逸 shell 引号执行任意命令 | `shlex.quote(f"{key}={value}")` 或 argv 列表不经 shell | +| S-20 | **P1** | [`server/application/services/sync_engine_v2.py`](server/application/services/sync_engine_v2.py) L281-284 | `cp {backup_file} ... && sysctl --system`:`backup_file` 来自 `ls` 输出未转义 | 白名单路径 + `shlex.quote(backup_file)` | + +**SQL 注入**: ORM/`select()` 为主,[`search.py`](server/api/search.py) 对 LIKE 通配符有转义,**未发现典型拼接 SQL 注入**。 + +--- + +## 3. 代码规范违规 + +| ID | 严重度 | 位置 | 描述 | 修复建议 | +|----|--------|------|------|----------| +| C-01 | P2 | 多处 `web/app/*.html` | 项目规则要求禁止未转义 `innerHTML`,但 [`servers.html`](web/app/servers.html)、[`scripts.html`](web/app/scripts.html) 等仍违规 | 抽公共 `renderTableRow()`,CI 检查 `innerHTML` 与 `${` 组合 | +| C-02 | P2 | [`web/app/*.html`](web/app/) | 每页重复定义 `esc()`,[`layout.js`](web/app/layout.js) 有 `_esc()` 未统一导出 | `api.js` 导出 `esc`/`escAttr`,页面禁止私有副本 | +| C-03 | P3 | [`server/api/install.py`](server/api/install.py) L283-284 | async 路由内同步 `subprocess.run` 阻塞事件循环 | `asyncio.to_thread` | +| C-04 | P3 | 前端 | 部分页面裸 `fetch`(如 [`install.html`](web/app/install.html))绕过 `apiFetch` | 安装向导可例外,但应集中错误处理 | + +--- + +## 4. 安全风险(非直接漏洞) + +| ID | 严重度 | 位置 | 描述 | 修复建议 | +|----|--------|------|------|----------| +| R-01 | P1 | [`server/infrastructure/database/crypto.py`](server/infrastructure/database/crypto.py) | 遗留 AES-CBC + SHA256(API_KEY) 兼容 PHP | 迁移期后移除 CBC;统一 Fernet | +| R-02 | P2 | [`server/config.py`](server/config.py) L40 附近 | `DATABASE_URL` 默认 `root:password` | 安装模式强制无默认密码;启动校验 | +| R-03 | P2 | 15× [`web/app/*.html`](web/app/) | jsDelivr 无 SRI,供应链篡改可全站 RCE | 自托管 + `integrity` + `crossorigin` | +| R-04 | P2 | [`deploy/nginx_https.conf`](deploy/nginx_https.conf) | 有 HSTS,无 CSP / X-Content-Type-Options | 逐步启用 CSP;`script-src` 限制 | +| R-05 | P2 | [`scripts/sync_mysql_mcp_env.py`](scripts/sync_mysql_mcp_env.py) | 将 DB 密码写入 `.env` 的 `MYSQL_PASSWORD`,扩大泄露面 | MCP 独立只读/开发用户 | +| R-06 | P2 | [`scripts/grant_nexus_local.sql`](scripts/grant_nexus_local.sql) | `GRANT ALL` + `'%'` 仅适合本地;误用于生产风险高 | 文档标注「仅本地」;生产用最小权限 SQL | +| R-07 | P3 | [`server/application/services/auth_service.py`](server/application/services/auth_service.py) L147-153 | 旧 refresh token 格式仍兼容,重用检测弱于新格式 | 强制迁移、废弃旧 token | +| R-08 | P3 | [`requirements.txt`](requirements.txt) | 无 CI `pip-audit`/OSV 扫描 | GitHub Action 定期依赖审计 | + +--- + +## 5. 性能问题 + +| ID | 严重度 | 位置 | 描述 | 修复建议 | +|----|--------|------|------|----------| +| P-01 | P1 | [`server/api/agent.py`](server/api/agent.py) L216-217 | 心跳直写 MySQL(2000+ 服务器 × 60s) | 见 B-04 | +| P-02 | P2 | [`server/api/servers.py`](server/api/servers.py) L58-63 | 列表页每服务器一次 `redis.hgetall`(N+1) | Redis pipeline / `MGET` 批量 | +| P-03 | P2 | [`server/api/websocket.py`](server/api/websocket.py) L293-307 | 告警冷却 `_last_alert_time` 进程内 dict,多 worker 重复 Telegram | Redis SETNX + TTL 去重 | +| P-04 | P3 | [`server/api/install.py`](server/api/install.py) | 安装阶段同步 subprocess | 见 C-03 | +| P-05 | Info | [`server/infrastructure/ssh/asyncssh_pool.py`](server/infrastructure/ssh/asyncssh_pool.py) | 连接池设计合理;需压测 2000+ 并发 SSH 上限 | 配置 max pool + 监控指标 | + +--- + +## 6. 可用性问题 + +| ID | 严重度 | 位置 | 描述 | 修复建议 | +|----|--------|------|------|----------| +| U-01 | P2 | [`web/app/api.js`](web/app/api.js) L35-37 | 空 `catch` 吞掉刷新/加载错误 | 统一 `toast('error', ...)` 或重试提示 | +| U-02 | P2 | [`server/infrastructure/database/crypto.py`](server/infrastructure/database/crypto.py) | 密钥错误时表现为 SSH 连接失败而非配置错误 | 见 B-07 | +| U-03 | P3 | [`web/app/settings.html`](web/app/settings.html) | 部分配置加载失败静默 | 与 U-01 统一错误 UX | +| U-04 | Info | 移动端 | [`layout.js`](web/app/layout.js) 已有 overlay;大表横向滚动需各页验证 | 关键页 E2E 截图验收 | + +--- + +## 7. 可扩展性问题 + +| ID | 严重度 | 位置 | 描述 | 修复建议 | +|----|--------|------|------|----------| +| E-01 | P1 | 认证架构 | 无全局 JWT 中间件,每路由靠 `Depends(get_current_admin)`,新路由易漏 | `main.py` 默认拒绝 + 白名单(install/health/agent/auth/login) | +| E-02 | P2 | Agent 协议 | 全局单 Key + shell exec,难以多租户/多环境隔离 | per-server API key + scoped capabilities | +| E-03 | P2 | 配置三写 | install → `.env` + `config.php` + MySQL `settings`,漂移风险 | 单一真相源 + 同步脚本(已有部分,需校验任务) | +| E-04 | P3 | 前端 | 15 页独立 Alpine 内联脚本,无组件复用 | 逐步抽 `components/` 或轻量模块(非必须大重构) | + +--- + +## 8. 优化点(非错误) + +| ID | 位置 | 建议 | +|----|------|------| +| O-01 | [`server/api/search.py`](server/api/search.py) | LIKE 转义已做,可补全文索引或 ES(2000+ 资产搜索) | +| O-02 | [`web/app/layout.js`](web/app/layout.js) | 全局搜索已统一 `_esc()`,推广为唯一转义入口 | +| O-03 | [`server/background/heartbeat_flush.py`](server/background/heartbeat_flush.py) | 与 B-04 对齐后,可加批量大小/退避指标 | +| O-04 | 测试 | [`tests/`](tests/) 覆盖有限,优先补 auth/webssh/agent 集成测试 | +| O-05 | 文档 | 已有 [`docs/project/mysql-mcp-setup.md`](docs/project/mysql-mcp-setup.md);安全项可增 ADR(WebSSH JWT、Agent exec) | + +--- + +## 架构风险总览 + +```mermaid +flowchart TB + subgraph attack [高优先级攻击面] + XSS[XSS localStorage] + WebSSH[IDOR WebSSH] + AgentExec[Agent shell exec] + APIKey[Global API_KEY reveal] + end + XSS --> TokenTheft[窃取 JWT] + TokenTheft --> WebSSH + TokenTheft --> APIKey + APIKey --> AgentExec +``` + +--- + +## 修复优先级(建议执行顺序) + +1. **立即(P0/P1 安全)**: S-01/B-01 WebSSH 绑定;S-04/S-05 XSS;S-02 Agent exec 收敛;S-03 API Key reveal;**S-19/S-20 sync_engine sysctl 注入** +2. **本周(一致性与性能)**: B-04/P-01 心跳写路径;B-03 在线统计;P-02 Redis pipeline +3. **迭代(纵深防御)**: S-06/S-07 会话与 WS;R-03/R-04 CDN/CSP;E-01 全局鉴权中间件 +4. **文档/流程**: O-04 测试;R-08 依赖审计;每项修复写 [`docs/changelog/`](docs/changelog/) + +--- + +## 与「WSL Node 20」计划的关系 + +| 计划项 | 状态 | +|--------|------| +| `wsl_install_node20.sh` 代理修复 | 已实现 | +| `MYSQL_READONLY=false` + `--writable` | 已实现 | +| `grant_nexus_local.sql` | 已实现 | +| MCP/Node 验证 | 已通过 | + +**无需再跑该安装计划。** 若 MCP 仍红,仅需 Cursor Reload + 检查 MySQL 服务,而非重复改代码。 + +--- + +## 下一步(需你确认后执行) + +若你确认本审计报告,可在 **Agent 模式** 按优先级分批修复(建议先做 P0/P1 安全项,每项单独 changelog)。你可指定: + +- 只修安全(S-01 ~ S-12) +- 或「P0/P1 全部」 +- 或指定 ID 列表 + +我不会再自动重复 WSL Node20 实施循环。 + +--- + +## 10. 如何强制「逐行审查」(Phase 2 操作规程) + +初版(Phase 1)= **攻击面抽查 + 文件打勾**,不满足「逐行」承诺。要可复查、少漏报,必须单独开 **Phase 2**,并按下列规则验收。 + +### 10.1 你对助手说什么(复制即用) + +切换到 **Agent 模式**(非 Plan),分批发: + +``` +执行审计计划 Phase 2:逐行复核。禁止攻击面抽查代替。 + +范围:server/**/*.py(本轮仅 server,完成后再 web)。 + +方法: +1. 用 rg 列出本目录每个文件的:命令拼接、subprocess、exec_ssh、WebSocket、SQL text(、硬编码密钥 +2. 对每个命中行 Read 上下文 ±15 行,判定 safe / finding +3. 每个文件输出: + - [path] 行数 N | rg 命中 M 条 | 全部 closure 后才能标「逐行完成 ✓」 + - findings 表:行号 | 严重度 | 描述 +4. 禁止写「已审计」除非该文件 M=0 或每条命中都有 closure 记录 +5. 每完成 5 个文件汇报一次,不要一次声称全库完成 + +从 server/application/services/sync_engine_v2.py 开始(已知漏报)。 +``` + +修代码与审计**分两次会话**,避免混在一起。 + +### 10.2 「逐行」的可验收定义(不是读每一行散文) + +| 必须做 | 说明 | +|--------|------| +| 全文 Read 或按 200 行分段 Read 至 EOF | 每个 `.py` 至少覆盖 100% 行号范围 | +| 全文件 rg 规则表(见 10.3) | 每个命中 = 一条待 closure 项 | +| 外部输入追调用链 | API → service → ssh/subprocess 到顶 | +| 产出 `docs/reports/audit-line-coverage-*.md` | 复查 AI 只认文件:行,不认「✓」 | + +**不算逐行**:只 grep 几个关键词就标 ✓;只读 Top 15;子代理汇总不落行号。 + +### 10.3 每个 Python 文件必跑的 rg 规则(closure 前不得标完成) + +```bash +# 在单个文件或目录执行,命中全部要 Read 上下文 +rg -n "f[\"'].*\{.*\}.*(exec|run|shell|ssh|sysctl|curl|wget|sudo|bash|sh -c)" server/ +rg -n "subprocess\.|create_subprocess_shell|os\.system|popen" server/ +rg -n "exec_ssh_command|conn\.run\(|asyncssh" server/ +rg -n "text\(|execute\(|raw\(" server/ +rg -n "Depends\(get_current_admin\)|APIRouter|@router\.(get|post|put|delete|websocket)" server/api/ +rg -n "HTTPException|401|403|API_KEY|compare_digest|secrets\." server/ +``` + +前端另表:`innerHTML`、`localStorage`、`WebSocket.*token`、`eval(`。 + +### 10.4 推荐拆分顺序(避免一次吹「全库完成」) + +| 批次 | 路径 | 约行数 | +|------|------|--------| +| 2a | `server/api/*.py` | 高 | +| 2b | `server/application/services/*.py` | 高(含 sync_engine) | +| 2c | `server/infrastructure/**/*.py` | 中 | +| 2d | `server/background/*.py` + `main.py` + `config.py` | 中 | +| 2e | `web/app/*.{html,js}` | 高 | +| 2f | `web/agent/*` + `scripts/*` + `tests/*` | 中 | + +每批结束交付:`audit-phase-2{X}.md`(仅 findings + closure 表)。 + +### 10.5 你可加的 Cursor 规则(防止再抽查) + +在 `.cursor/rules/audit-line-review.mdc` 中写: + +- 安全审计任务:**禁止**在未列出 `文件:行号` 的情况下声称完成 +- **禁止**使用「已审计 ✓」除非附带 rg 命中 closure 表 +- 发现命令拼接必须引用 `shlex`/argv 或标为 finding +- 单次回复最多声称完成 **5 个文件** 的逐行审查 + +### 10.6 你怎么验收助手是否真的逐行 + +1. 抽查:打开 `sync_engine_v2.py`,看报告是否含 **234、236、284**(S-19/S-20) +2. 要求给 `wc -l` 与「本文件 Read 行范围 1-N」 +3. 要求 `rg 命中数 = closure 数`,差 1 条即打回 +4. 用另一只 AI 只喂 `audit-phase-*.md` + 源码,做交叉验证 + +### 10.7 Phase 2 todos(待你确认后执行) + +| id | 内容 | +|----|------| +| phase2-api | `server/api/*.py` 全文 + rg closure | +| phase2-services | `server/application/services/*.py` 含 sync_engine 全行 | +| phase2-infra | `server/infrastructure/**` | +| phase2-web | `web/app` innerHTML + token | +| phase2-report | 合并为 `docs/reports/audit-line-coverage-2026-05-22.md` | + +--- + +## 11. 逐行走读标准(SSOT — 唯一验收依据) + +**独立文件(已落地)**: + +| 文件 | 用途 | +|------|------| +| [`docs/project/line-walk-audit-standard.md`](docs/project/line-walk-audit-standard.md) | 完整标准(推荐给人与复查 AI) | +| [`.cursor/rules/audit-line-review.mdc`](.cursor/rules/audit-line-review.mdc) | Cursor 编辑 `server/`/`web/` 时自动加载 | + +本节是 Phase 2 的**正式标准**(与上表同步)。与 Phase 1(攻击面抽查)严格区分;未满足 **DoD** 的文件不得标记「逐行完成」。 + +### 11.1 术语 + +| 术语 | 定义 | +|------|------| +| **走读** | 对单文件按行号顺序阅读源码(可分段),并对规则命中点做上下文分析 | +| **触达** | 仅打开/ grep 过文件,未满足走读 DoD | +| **规则命中** | `rg` 规则表(11.4)在某行产生的匹配 | +| **Closure** | 对一条规则命中的判定记录:`SAFE` 或 `FINDING`(含行号、理由) | +| **DoD** | 单文件完成定义(11.6),全部满足才可标「逐行完成 ✓」 | + +### 11.2 适用范围 + +| 包含 | 排除 | +|------|------| +| `server/**/*.py` | `docs/**` 仅做密钥扫描(11.8) | +| `web/app/*.{html,js}` | `.cursor/skills/**` | +| `web/agent/**` | 第三方 minified 库 | +| `scripts/**`、`deploy/**`、`tests/**` | 图片、字体、lock 文件 | + +### 11.3 单文件走读流程(强制 8 步) + +对清单中**每一个**源文件依次执行,不得跳步: + +``` +Step 1 登记:记录 path、语言、wc -l 行数 N +Step 2 全文覆盖:Read offset=1, limit=200 循环至 EOF(末段 offset 必须 ≥ N-5) +Step 3 规则扫描:对该文件跑 11.4 中「本语言」全部规则,得到命中列表 H[] +Step 4 Closure:对 H[] 中每条命中 Read ±15 行,填写 11.5 表格一行 +Step 5 入口梳理:若为 API/路由/WS,列出对外入口表(方法、路径、鉴权方式) +Step 6 数据流:标出「外部输入 → 危险 sink」(SQL/shell/文件/innerHTML/出站请求) +Step 7 八类核对:按 11.7 勾选本文件涉及的类别(无则勾 NONE) +Step 8 DoD 检查:满足 11.6 后写入报告,标记「逐行完成 ✓」 +``` + +**单会话上限**:最多完成 **5 个文件** 的 Step 1–8,防止敷衍。 + +### 11.4 规则表(必须 100% Closure) + +#### Python(`server/`、`scripts/`、`tests/`、`web/agent/*.py`) + +| 规则 ID | rg 模式(示意) | 审查要点 | +|---------|----------------|----------| +| PY-01 | `f["'].*\{.*\}.*` 且行内含 exec/run/shell/ssh/sysctl/curl/sudo/bash | 是否经 shell?变量是否 shlex.quote **整参数**? | +| PY-02 | `subprocess\.|create_subprocess_shell|os\.system|popen` | shell=True?命令来源? | +| PY-03 | `exec_ssh_command|conn\.run\(` | asyncssh 是否默认 shell?拼接方式? | +| PY-04 | `text\(|execute\(|raw\(` | SQL 是否拼接用户输入? | +| PY-05 | `@router\.|APIRouter|websocket` | 是否有 `Depends(get_current_admin)` 或等效? | +| PY-06 | `API_KEY|SECRET|password|token` 字面量 | 是否硬编码密钥? | +| PY-07 | `pickle|yaml\.load\(|eval\(|exec\(` | 反序列化 / 动态执行 | +| PY-08 | `==.*[Kk]ey|!=.*[Kk]ey` | API Key 比较是否 timing-safe | +| PY-09 | `except.*:.*pass|except Exception` | 是否吞掉安全相关异常 | +| PY-10 | `open\(|Path\(.*write|unlink` | 路径遍历、任意写 | + +#### 前端(`web/app/*.{html,js}`) + +| 规则 ID | rg 模式 | 审查要点 | +|---------|---------|----------| +| FE-01 | `innerHTML|outerHTML|insertAdjacentHTML` | 每个 `${}` 是否 esc / textContent | +| FE-02 | `localStorage|sessionStorage` | 是否存 JWT/密钥 | +| FE-03 | `WebSocket.*token|\.token=` | Token 是否在 URL | +| FE-04 | `fetch\(|apiFetch` | 是否绕过统一鉴权/错误处理 | +| FE-05 | `onclick=.*\$\{|javascript:` | 属性上下文注入 | +| FE-06 | `cdn\.|script src=` | SRI、供应链 | + +#### Shell / SQL / Deploy + +| 规则 ID | 范围 | 要点 | +|---------|------|------| +| SH-01 | `*.sh` | 未引用变量、curl 明文密钥 | +| DP-01 | `deploy/nginx*.conf` | CSP、TLS、限流 | +| SQL-01 | `*.sql` | GRANT 过大、'%' host | + +**无命中**:在 Closure 表写 `H=0(规则表零命中)`,仍须完成 Step 2 全文 Read。 + +### 11.5 Closure 记录格式(每条命中一行) + +```markdown +| 文件 | 行号 | 规则 | 判定 | 严重度 | 理由(≤1句) | +|------|------|------|------|--------|--------------| +| sync_engine_v2.py | 236 | PY-01 | FINDING | P1 | sysctl -w 仅 quote value,' 可断 shell | +| sync_engine_v2.py | 95 | PY-01 | SAFE | — | rsync 路径双端 shlex.quote | +``` + +- **SAFE** 必须写清依据(如「整段 argv」「Pydantic 校验」「仅常量」) +- **FINDING** 必须对应 11.7 八类之一,并给修复方向(≤1 句) + +### 11.6 单文件 DoD(全部打勾才算完成) + +- [ ] Step 2:Read 覆盖行号 `1 .. N`(报告注明 N 与最后 Read 的 offset/limit) +- [ ] Step 3–4:`len(H) == Closure 行数`(含 SAFE + FINDING) +- [ ] Step 5:有对外入口则必有「入口表」;纯 `__init__.py` 可写 N/A +- [ ] Step 6:已标外部输入与 sink,或写「无外部输入」 +- [ ] Step 7:八类核对已勾 +- [ ] 报告含「逐行完成 ✓」且**无** Phase 1 式笼统描述 + +### 11.7 问题八类与严重度(与 KPI 对齐) + +| 类别 | 代号 | P0 示例 | P1 示例 | P2 示例 | +|------|------|---------|---------|---------| +| 功能缺陷 | BUG | — | 统计与列表数据源不一致 | 空 catch 吞错误 | +| 安全漏洞 | VULN | 未授权 RCE、IDOR 拿 Shell | XSS、密钥回读、命令注入 | CSRF 缺失 | +| 规范违规 | STYLE | — | 违反项目 esc 规则 | 重复 esc 实现 | +| 安全风险 | RISK | 仓库内生产 API Key | 明文 HTTP 调 Agent、弱 crypto | 无 SRI | +| 性能 | PERF | — | 心跳直写 MySQL、Redis N+1 | 阻塞 subprocess | +| 可用性 | UX | — | — | 加载失败无提示 | +| 可扩展性 | EXT | — | 无全局鉴权中间件 | 配置三写漂移 | +| 优化点 | OPT | — | — | 可合并重复逻辑 | + +**KPI(复查用)**:P0 漏报 -100 / P1 漏报 -50;每条 FINDING 必须 `文件:行号` 可定位。 + +### 11.8 批次交付物模板 + +每个批次一个文件:`docs/reports/audit-phase-2{X}-line-walk.md` + +```markdown +# Phase 2{X} 逐行走读 — {目录} + +## 文件清单 +| 文件 | 行数 N | 命中 H | Closure | 状态 | +|------|--------|--------|---------|------| + +## Findings(仅 FINDING) +| ID | 文件:行 | 类 | 度 | 描述 | 修复建议 | + +## Closure 全表(含 SAFE) +(11.5 格式) + +## 入口表(api 批次必填) +| 方法 | 路径 | 鉴权 | + +## 走读签字 +- 范围:... +- 完成文件数 / 计划文件数 +- 未含「攻击面 Top N」字样:是/否 +``` + +### 11.9 禁止行为(违反即打回) + +1. 未列 `文件:行号` 的 FINDING +2. `rg 命中数 ≠ Closure 数` 仍标完成 +3. 用子代理「Top 15」代替 Closure 全表 +4. 只写「已审计 ✓」无 Step 2 行号范围 +5. 单回复声称完成整库 `server/` 或全部 `web/` +6. 审计与修代码同一任务混合(除非用户明确要求) + +### 11.10 与 Phase 1 的关系 + +| Phase 1 | Phase 2 | +|---------|---------| +| 攻击面抽查、快速风险图 | 逐行走读、可复查 closure | +| 可发现「显眼」P0 | 可发现「单文件局部」拼接类 P1(如 sysctl) | +| 报告在计划 §0–§8 | 报告在 `docs/reports/audit-phase-2*.md` | + +**原则**:Phase 1 的 FINDING 在 Phase 2 须逐条 **VERIFY**(行号仍成立或降级/关闭);Phase 2 可新增 FINDING,不得删减已确认行号而不说明。 diff --git a/.cursor/rules/agent-exploration.mdc b/.cursor/rules/agent-exploration.mdc new file mode 100644 index 00000000..7b3d318a --- /dev/null +++ b/.cursor/rules/agent-exploration.mdc @@ -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/.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` diff --git a/.cursor/rules/audit-line-review.mdc b/.cursor/rules/audit-line-review.mdc new file mode 100644 index 00000000..3ac3a1ad --- /dev/null +++ b/.cursor/rules/audit-line-review.mdc @@ -0,0 +1,66 @@ +--- +description: 安全/质量逐行走读审计 — Phase 2 closure 流程;禁止攻击面抽查冒充全量审计 +globs: + - server/** + - web/app/** + - web/agent/** + - scripts/** + - deploy/** + - tests/** +--- + +# 逐行走读审计规则(Phase 2) + +完整标准见:`standards/line-walk-audit-standard-v2.md`(权威)· Nexus SSOT:`docs/project/line-walk-audit-standard.md` · 转接索引:`docs/project/standards-transfer-package.md` + +## 何时启用 + +用户要求「全量审计」「逐行审查」「走读」「Phase 2」「安全审计」时,**必须**遵守本规则,不得用攻击面 Top N 或子代理汇总代替。 + +## 强制流程(每文件 8 步) + +1. 登记 `path`、语言、行数 N(`wc -l`) +2. **全文 Read** 至 EOF(可每 200 行分段;末段须覆盖 N) +3. 对该文件跑 `docs/project/line-walk-audit-standard.md` §4 全部适用规则 → 命中列表 H +4. 对 H **每一条** Read ±15 行,填 Closure(SAFE / FINDING + 行号 + 理由) +5. API/WS 文件:写入口表(方法、路径、鉴权) +6. 标外部输入 → sink(SQL / shell / 文件 / innerHTML / 出站 HTTP) +7. 八类归类:BUG / VULN / STYLE / RISK / PERF / UX / EXT / OPT +8. 满足 DoD 后标「逐行完成 ✓」 + +**单会话上限**:最多 **5 个文件** 完成 8 步。 + +## DoD(缺一不可) + +- `len(H) == Closure 行数`(H=0 须写明「规则零命中」) +- Read 覆盖 `1..N`(报告写明 N 与 Read 范围) +- 每条 FINDING 含 **`文件:行号`** +- 交付 `docs/reports/audit-phase-2*-line-walk.md`(含 Closure **全表**,含 SAFE) + +## 命令拼接(高频漏报) + +- `f"{key}={shlex.quote(value)}"` **不等于**安全:须 `shlex.quote(f"{key}={value}")` 或 argv 不经 shell +- `ls`/`find` 输出拼进 `cp`/`rm` 须 **白名单 + shlex.quote** +- `exec_ssh_command` / `conn.run(command)` 默认经远程 shell + +## 禁止 + +- 无行号的 FINDING +- 「已审计 ✓」无 Closure 表 +- 单回复声称整库 `server/` 或全部 `web/` 完成 +- 子代理只出 Top 15 不落行号 +- 同一任务混审计与改代码(除非用户明确要求) + +## 严重度(复查 KPI) + +- **P0**:未授权 RCE、IDOR Shell、仓库内生产密钥等 +- **P1**:需认证后的命令注入、XSS、密钥回读等 +- P0 漏报 -100 / P1 漏报 -50(用户复查约定) + +## 报告路径 + +| 用途 | 路径 | +|------|------| +| 标准 SSOT | `docs/project/line-walk-audit-standard.md` | +| 批次产出 | `docs/reports/audit-phase-2{a-f}-line-walk.md` | +| Phase 1 参考 | `.cursor/plans/nexus_全面代码审计_a33e6fc2.plan.md` §0–§8 | diff --git a/.cursor/rules/nexus-frontend.mdc b/.cursor/rules/nexus-frontend.mdc new file mode 100644 index 00000000..f3b85dcd --- /dev/null +++ b/.cursor/rules/nexus-frontend.mdc @@ -0,0 +1,38 @@ +--- +description: Nexus 前端规范 — Tailwind v4 + Alpine.js;api.js JWT;禁止 XSS +globs: web/app/** +alwaysApply: false +--- + +# Nexus 前端 (Tailwind + Alpine.js) + +## 页面结构 + +- 业务页先引入:`/app/api.js` → `/app/layout.js`(侧栏、搜索、移动端 overlay) +- 15 个 HTML 页面同级放在 `web/app/`,保持导航一致 + +## API 调用 + +- 一律 `apiFetch()` / `apiHeadersJSON()`,禁止手写裸 `fetch` 漏 Authorization +- Token 存 `localStorage`:`access_token`、`refresh_token`、`token_expires` +- 到期前 2 分钟自动 refresh;401 重试一次后 `_logout()` +- 8 小时无活动会话超时(与后端 JWT `updated_at` 对齐) + +## WebSocket + +- 仪表盘 `/ws/alerts?token=...`(JWT 查询参数,ADR-011) +- 收到 `alert` / `recovery` 刷新 stats,勿阻塞 UI 线程 + +## 安全(XSS) + +- 禁止 `${userInput}` 直接插入 `innerHTML` 或 Alpine `x-html` +- 用户/服务器名等用 `textContent` 或 Alpine 文本绑定 + +## WebSSH + +- `terminal.html`:xterm.js + Koko 协议;resize 随窗口 +- 关闭码 4003 = 凭据/授权失败 + +## 验收 + +改 UI 后浏览器实机验证:Console 无报错、Network 无 404/500、登录→核心流程可走通。 diff --git a/.cursor/rules/nexus-posix-paths.mdc b/.cursor/rules/nexus-posix-paths.mdc new file mode 100644 index 00000000..2ac409c8 --- /dev/null +++ b/.cursor/rules/nexus-posix-paths.mdc @@ -0,0 +1,20 @@ +--- +description: 远程 Linux 路径必须用 POSIX,禁止 os.path 拼接 +globs: server/**/*.py +alwaysApply: false +--- + +# 远程路径 = POSIX only + +## 规则 + +- **SSH / SFTP / rsync 目标路径**(含 `target_path`、`remote_path`、命令里的文件路径): + - 使用 `server.utils.posix_paths`(`normalize_remote_abs_path`、`remote_join`、`posix_join` 等) + - **禁止** `os.path.join` / `dirname` / `basename` / `normpath` 处理这类字符串 +- **Nexus 本机真实 I/O**(`open`、`os.walk`、`os.scandir`):可用 `os.path`,逻辑拼接仍优先 `posix_join` +- **仓库本地路径**(`Path(__file__)`、MCP `DEPLOY_DIR`):可用 `os.path` / `pathlib` + +## 参考 + +- 实现:`server/utils/posix_paths.py` +- 审计:`docs/audit/2026-06-01-posix-path-batch-review.md` diff --git a/.cursor/rules/nexus-python-backend.mdc b/.cursor/rules/nexus-python-backend.mdc new file mode 100644 index 00000000..3f1265ed --- /dev/null +++ b/.cursor/rules/nexus-python-backend.mdc @@ -0,0 +1,46 @@ +--- +description: Nexus Python/FastAPI 后端规范 — 异步、Pydantic、Repository、UTC 时间 +globs: server/**/*.py +alwaysApply: false +--- + +# Nexus Python 后端 + +## 依赖版本(勿随意降级) + +FastAPI 0.115 · SQLAlchemy 2.0 async · aiomysql · Pydantic v2 · asyncssh 2.17 · redis 5.x · PyJWT 2.10 + +## API 路由 + +- 请求/响应模型定义在 `server/api/schemas.py` +- 业务 API 必须 `Depends(get_current_admin)`;Agent 用 `X-API-Key`;install 路由无 JWT +- 返回结构化错误,禁止裸 `except: pass` + +## 数据库 + +- 模型:`server/domain/models/__init__.py`(14 表) +- 访问:`*_repo.py`,分页在 Repository 层 `offset/limit/count` +- 时间戳:`datetime.now(timezone.utc)`,禁止 naive datetime +- 连接池:默认 pool=160、overflow=120(安装向导按 MySQL `max_connections` 计算) + +## Redis + +- 客户端:`server/infrastructure/redis/client.py`,启动时强校验(ADR-009) +- 心跳 key:`heartbeat:{server_id}` TTL 600;告警 key:`alerts:{server_id}` TTL 3600 +- 跨 worker 广播:Redis Pub/Sub `nexus:alerts` + +## SSH + +- 仅用 **asyncssh**(已删除 paramiko) +- 连接池引用计数:`acquire` / `release`,空闲 5min 清理 +- 远程命令参数必须 `shlex.quote()` + +## 凭据加密 + +- Fernet 存储 `Server.password` / `ssh_key_private` +- API 响应:`password_set` 布尔,禁止返回明文密码 + +## 测试 + +- `pytest` + `tests/conftest.py`;改 API/Service 须跑相关测试 +- 新 endpoint 至少覆盖:认证失败、校验失败、成功路径 diff --git a/.cursor/rules/nexus-security.mdc b/.cursor/rules/nexus-security.mdc new file mode 100644 index 00000000..f08178c5 --- /dev/null +++ b/.cursor/rules/nexus-security.mdc @@ -0,0 +1,44 @@ +--- +description: Nexus 安全规范 — JWT/TOTP/API Key/Fernet/CORS;MCP MySQL 只读 +globs: server/api/**,server/application/**,server/infrastructure/database/**,web/app/** +alwaysApply: false +--- + +# Nexus 安全 + +## 认证矩阵 + +| 调用方 | 机制 | +|--------|------| +| 管理端 API | JWT Bearer + 可选 TOTP | +| Agent | `X-API-Key`(仅 per-server `agent_api_key`;经 `POST /api/servers/{id}/agent-key` 生成,BL-06 起无 global 回退) | +| WebSocket | JWT query param | +| 安装向导 | 无 JWT(仅安装模式可用) | + +## 登录防暴破 + +- `auth_service.py`:失败计数 → 15min 锁定 → HTTP 429 +- 刷新令牌重用检测:旧 refresh 作废、`token_version` 递增 + +## 密钥(禁改 / 禁提交) + +- `.env` 永不入 git;`SECRET_KEY` / `API_KEY` / `ENCRYPTION_KEY` / `DATABASE_URL` 不可从 settings 表覆盖 +- UI 不展示可改的 `API_KEY` + +## 运维信任模型 + +- 鉴权管理员即信任用户,不做 RBAC +- 必须防御:SQL/命令注入、XSS、凭据泄露 +- Shell:`shlex.quote()`;DELETE/UPDATE 须有 WHERE(MCP 同样) + +## Cursor MySQL MCP + +- 使用 `@yclenove/mysql-mcp-server`,**默认 `MYSQL_READONLY=true`** +- `MYSQL_DATABASE_ALLOWLIST=nexus`(或实际库名) +- 禁止在 MCP `env` 或 git 中提交生产密码;连接信息仅写在本地 `.env` +- 查库优先 MCP `query` / `describe_table`,避免手写破坏性 SQL + +## 审计 + +- 所有 CUD 写 `audit_logs`(含 `ip_address`) +- WebSSH 会话与 `command_logs` 可追溯 diff --git a/.cursor/rules/nexus-tech-stack.mdc b/.cursor/rules/nexus-tech-stack.mdc new file mode 100644 index 00000000..5e759c91 --- /dev/null +++ b/.cursor/rules/nexus-tech-stack.mdc @@ -0,0 +1,45 @@ +--- +alwaysApply: true +--- +# Nexus 6.0 技术栈 + +## 架构分层(禁止跨层直连) + +``` +web/app/*.html → server/api/*.py → application/services → infrastructure/* → domain/models +``` + +- API 层:路由 + Pydantic 校验 + `Depends(get_current_admin)`,不写 SQL +- Service 层:业务编排,通过 Repository 访问数据 +- Infrastructure:DB/Redis/SSH/Telegram 实现细节 +- Domain:SQLAlchemy ORM 模型,无 FastAPI 依赖 + +## 启动模式 + +- 无 `.env` → **安装模式**:仅 `/api/install/` + `/app/install.html` +- 有 `.env` → 正常模式:`SECRET_KEY` / `API_KEY` / `ENCRYPTION_KEY` 缺一不可 + +## 配置三写(安装向导) + +`web/data/config.php` + `.env`(`NEXUS_` 前缀)+ MySQL `settings` 表。 +**不可从 DB 覆盖**:`SECRET_KEY`、`API_KEY`、`ENCRYPTION_KEY`、`DATABASE_URL`。 + +## 数据流 + +Agent(60s) → Redis 实时 → 前端直读 → 10min → MySQL 历史。告警:WebSocket + Telegram。 + +## 后台任务(仅 primary worker) + +`heartbeat_flush`(10min) · `self_monitor`(30s) · `schedule_runner`(60s) · `retry_runner`(5min) + +## HTTP / DB 会话 + +- 普通 API:`DbSessionMiddleware` → `request.state.db`,禁止在 factory 里 `AsyncSessionLocal()` 后不 close +- WebSocket / `/api/install/`:不走 DB 中间件 + +## 相关规则文件 + +- `nexus-python-backend.mdc` — `server/**/*.py` +- `nexus-frontend.mdc` — `web/app/**` +- `nexus-security.mdc` — 安全与凭据 +- `perfect-implementation.mdc` — 完美实现与文档纪律 diff --git a/.cursor/rules/no-department-agents.mdc b/.cursor/rules/no-department-agents.mdc new file mode 100644 index 00000000..6107dd97 --- /dev/null +++ b/.cursor/rules/no-department-agents.mdc @@ -0,0 +1,29 @@ +--- +description: 单 Agent 对话——禁止部门/Skill 分派,用户只与当前助手直接协作 +alwaysApply: true +--- + +# 单 Agent 对话(强制) + +Nexus 只保留**你 ↔ 当前 Cursor 助手**这一条协作链。已删除:`docs/skills/`、`docs/team/`、`.cursor/skills/`;本机 `~/.claude/skills/` 中凡带 `group: 管理组|工程部|测试部|产品部|设计部` 的目录也已移除。 + +## 禁止 + +- 扮演或「转交」管理组、工程部、测试部、产品部、设计部及项目经理/总监/`*-department` 等任何虚拟角色。 +- 未经用户明确要求时,**不要** `Read` 或按 `available_skills` 去加载 Skill 文件(含全局 `~/.claude/skills/`、`~/.cursor/skills-cursor/`)。 +- 用 Task/子代理模拟「开会、分派、部门审批」;子代理仅用于用户明确要求的并行搜代码/跑命令等**技术任务**,且不得冠部门名。 +- 在回复里写「已转交 XX 部」「请以产品经理身份」等话术。 + +## 允许 + +- 直接:澄清需求 → 设计/技术文档(`docs/design/`)→ 改代码 → 跑 `tests/` / 门控 → changelog。 +- 依据:`standards/`、`.cursor/rules/`(含 `perfect-implementation`、`nexus-*`、`audit-line-review`)。 + +## 测试(强制) + +- **所有测试与「是否修好」由本 Agent 执行并留证据**(命令、生产 URL、浏览器、`docs/reports/*-verification.md`)。 +- **不得**把未验证项推给用户代测;用户只做 Agent 交付后的**终验**(见 `docs/reports/2026-06-01-final-acceptance-checklist.md`)。 + +## 默认协作 + +用户说什么就做什么;需要方案时在同一对话里说明取舍,不引入额外「智能体」层。 diff --git a/.cursor/rules/perfect-implementation.mdc b/.cursor/rules/perfect-implementation.mdc new file mode 100644 index 00000000..75e4dbee --- /dev/null +++ b/.cursor/rules/perfect-implementation.mdc @@ -0,0 +1,62 @@ +--- +description: 完美实现原则 — 禁止降级/妥协/技术债;设计文档与技术文档先行;每次修改写 changelog +alwaysApply: true +--- + +# 完美实现与文档纪律 + +## 实现标准(强制) + +**所有设计和实现都必须是完美实现。** + +- ❌ **不允许为了实现而实现** — 每一行代码、每一个接口必须有明确目的,达到项目既定的安全、架构与质量标准 +- ❌ **不允许降级实现** — 不得以「先用着」「以后再改」为由降低安全、性能、可维护性或用户体验 +- ❌ **不允许妥协实现** — 发现问题必须根治,不得用绕过、静默吞错、硬编码掩盖代替修复 +- ❌ **不允许留技术债** — 不得留下 TODO/FIXME/「临时方案」延后处理;当场能修必须当场修 +- ✅ **无法完美实现时必须停下** — 与用户沟通并确认替代方案后,才能换方案;不得擅自从权或自行降级 + +## 文档纪律(强制) + +### 设计文档 + +- 所有**新功能、架构变更、非平凡重构**必须先有设计文档,再写代码 +- 设计文档路径:`docs/design/specs/YYYY-MM-DD--design.md` +- 设计文档至少包含:背景与目标、方案对比、选定方案及理由、接口/数据模型、安全与性能约束、验收标准 + +### 技术文档(开发前) + +- 开发前输出技术文档(实施说明),路径:`docs/design/plans/YYYY-MM-DD-.md` 或 `docs/reports//engineering-report.md` +- 技术文档至少包含:涉及文件清单、实现步骤、依赖与配置变更、测试要点、回滚方式 +- 项目里**已有模块但缺文档的,在改动时一并补全**,不得只改代码不留档 + +### Changelog(每次修改) + +- **每一次**有意义的代码或配置修改,必须在 `docs/changelog/` 新增或更新记录 +- 命名:`YYYY-MM-DD-<简短主题>.md` +- 每条记录至少包含:日期、变更摘要、动机、涉及文件、是否需迁移/重启、验证方式 +- 禁止把 changelog 写在 commit message 里代替项目文档 + +## 执行顺序 + +``` +需求确认 → 设计文档 → 技术文档 → 实现 → 测试验证 → changelog →(必要时)更新 CLAUDE.md/AGENTS.md +``` + +## 禁止示例 + +```python +# ❌ 技术债 +# TODO: fix later +pass # workaround + +# ❌ 降级 +except Exception: + return None # 静默失败 + +# ❌ 无文档直接大改 +# (新增整模块却无任何 docs/design 或 docs/changelog 记录) +``` + +## 允许的唯一例外 + +经用户**明确书面确认**后,方可采用非完美方案;须在 changelog 中记录例外原因、影响范围与后续补齐计划。 diff --git a/.cursor/settings.json b/.cursor/settings.json new file mode 100644 index 00000000..8812bf5b --- /dev/null +++ b/.cursor/settings.json @@ -0,0 +1,7 @@ +{ + "plugins": { + "compound-engineering": { + "enabled": true + } + } +} diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 00000000..1bdce882 --- /dev/null +++ b/.cursorrules @@ -0,0 +1,56 @@ +# Nexus 6.0 — Cursor 速查 + +> SSOT: [docs/project/nexus-functional-development-guide.md](docs/project/nexus-functional-development-guide.md) +> Handoff: [docs/project/AI-HANDOFF-2026-06-03.md](docs/project/AI-HANDOFF-2026-06-03.md) + +## 栈 + +FastAPI + Async SQLAlchemy + Redis · Vue 3 + Vuetify 4 SPA(15 页)· Vite → `web/app/` + +## 环境(勿在仓库写密码) + +- 生产: `https://api.synaglobal.vip` · 端口 8600 · `ssh nexus` +- 本地: `~/Nexus` 或 `$NEXUS_ROOT` · 见 [linux-dev-paths.md](docs/project/linux-dev-paths.md) +- 凭据: 仅 `.env`(不入 git) +- **Gitea**:clone/pull 可匿名;**push** 用 `bash scripts/git-push.sh`(凭据在 `deploy/nexus-1panel.secrets.sh`,已 gitignore,勿提交) + +## 关键路径 + +| 用途 | 路径 | +|------|------| +| 后端入口 | `server/main.py` | +| 前端路由 | `frontend/src/router/index.ts` | +| API 客户端 | `frontend/src/api/index.ts` | +| Sync | `server/api/sync_v2.py` | +| 门控 | `deploy/pre_deploy_check.sh` | +| 本地验证 | `bash scripts/local_verify.sh` | + +## 铁律 + +见 `.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 +``` + +仅同步源码(不重建镜像):`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/` + +## 进度条(改代码时输出) + +``` +□实现 □本地验证 □审计8步 □部署 □健康检查 □浏览器验证 □changelog +``` diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..b52151c3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,40 @@ +.git +.gitignore +.claude +.cursor +**/__pycache__ +**/*.pyc +**/.pytest_cache +**/.mypy_cache +**/.ruff_cache +.coverage +htmlcov/ + +frontend/node_modules +frontend-v2/node_modules +frontend/dist +node_modules +.venv +.venv-* +venv +.pytest_cache +.ruff_cache +.playwright-mcp +2025.2 +=2025.2 +*.bak- + +.env +docker/.env +docker/runtime +tmp/ + +tests/ +docs/ +standards/ +*.md +!docker/README.md + +web/uploads +backups/ +deploy/gate_log.jsonl diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..db7204ed --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# EditorConfig — keep LF for code deployed to Ubuntu +# https://editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{bat,cmd}] +end_of_line = crlf + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..9c5ddd04 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,28 @@ +# Line endings: Ubuntu production + bash scripts require LF in git. +# Windows dev: set `git config core.autocrlf false` and rely on this file. + +*.sh text eol=lf +*.py text eol=lf +*.vue text eol=lf +*.ts text eol=lf +*.mts text eol=lf +*.mjs text eol=lf +*.css text eol=lf +*.scss text eol=lf +*.html text eol=lf +*.js text eol=lf +*.json text eol=lf +*.yaml text eol=lf +*.yml text eol=lf +*.toml text eol=lf +*.ini text eol=lf +*.sql text eol=lf +*.md text eol=lf +*.txt text eol=lf +.env.example text eol=lf +.gitattributes text eol=lf +.editorconfig text eol=lf + +*.bat text eol=crlf +*.cmd text eol=crlf +*.ps1 text eol=crlf diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml new file mode 100644 index 00000000..ec838dbc --- /dev/null +++ b/.gitea/workflows/ci-cd.yml @@ -0,0 +1,112 @@ +name: Nexus CI/CD + +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + +env: + PYTHON_VERSION: "3.12" + +jobs: + # ── CI: Test ── + test: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install dependencies + run: | + pip install --upgrade pip + pip install -r requirements.txt + pip install pytest pytest-asyncio aiosqlite coverage ruff mypy + + - name: Lint (ruff) + run: ruff check server/ tests/ + + - name: Type check (mypy) + run: mypy server/ --ignore-missing-imports + continue-on-error: true # Type errors don't block deployment yet + + - name: Run fast tests with coverage + run: | + 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 + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' # Only deploy from main branch + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Deploy to production server + uses: appleboy/ssh-action@v1 + with: + host: ${{ secrets.DEPLOY_HOST }} + username: ${{ secrets.DEPLOY_USER }} + key: ${{ secrets.DEPLOY_SSH_KEY }} + script: | + cd ${{ secrets.DEPLOY_PATH || '/opt/nexus' }} + git pull origin main + pip install -r requirements.txt + # Restart Python backend (Supervisor/systemd) + supervisorctl restart nexus + # PHP changes are picked up automatically (no restart needed) + echo "Nexus deployed successfully at $(date)" \ No newline at end of file diff --git a/.gitea/workflows/pre-commit.yml b/.gitea/workflows/pre-commit.yml new file mode 100644 index 00000000..68bbf724 --- /dev/null +++ b/.gitea/workflows/pre-commit.yml @@ -0,0 +1,17 @@ +name: Nexus Pre-commit Checks + +on: + push: + branches: ["*"] + +jobs: + quick-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - run: pip install ruff + - name: Quick lint + run: ruff check server/ --select=E,F,W --diff \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..514cf9fd --- /dev/null +++ b/.gitignore @@ -0,0 +1,89 @@ +# Nexus — Python +__pycache__/ +*.py[cod] +*$py.class +*.egg-info/ +build/ +.eggs/ + +# Environment (NEVER commit — contains production credentials) +.env +.install_locked +.install_state.json +SECRETS.md +.git-askpass-nexus.sh +deploy/nexus-1panel.secrets.sh +*.pem +.env.* +.venv/ +venv/ +ENV/ + +# Agent memory (local MCP persistence, do not commit) +.claude/mcp-memory.jsonl + +# MCP tool data (local development artifacts) +.megamemory/ +.playwright-mcp/ +frontend/test-results/ +frontend/e2e/.auth/ +tmp/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +nexus.log + +# Node offline cache for WSL install (large tarball) +.cache/ + +# Test +.pytest_cache/ +htmlcov/ +.coverage +coverage.xml + +# Node (for Tailwind/Alpine.js build) +node_modules/ +web/css/tailwind-output.css + +# Build output (Vite/Vuetify frontend) +# SPA 构建产物纳入 Git,便于服务器 git pull 部署(见 deploy/deploy-frontend.sh) +# 仍忽略本地临时文件 +web/app/assets/.tmp/ +# Legacy dist dir (no longer used) +**/dist/ + +# Uploads +web/uploads/ + +# Data (sensitive) +web/data/config.php +web/data/*.db +web/data/1panel-hosts.json + +# Backups +backups/ + +# Docker local secrets +docker/.env +docker/.env.prod +!docker/.env.example +!docker/.env.prod.example +scripts/npm-proxy.env + +# Deploy +deploy/*.key + +# Backups +backups/ \ No newline at end of file diff --git a/.skills/nexus-btpanel-review/SKILL.md b/.skills/nexus-btpanel-review/SKILL.md new file mode 100644 index 00000000..03bba375 --- /dev/null +++ b/.skills/nexus-btpanel-review/SKILL.md @@ -0,0 +1,31 @@ +--- +name: nexus-btpanel-review +description: Use when reviewing or changing Nexus BT/宝塔 panel integration, one-click login, API bootstrap, session keepalive fixes, or BT credential handling. +--- + +# Nexus BT Panel Review + +Use for 宝塔/BT panel integration changes. + +## Call chain to map + +API one-click login -> btpanel_service -> BT API credential lookup/bootstrap -> SSH bootstrap if needed -> login URL/tmp_token -> panel session behavior. + +## Required checks + +- Do not log or return BT API keys, session cookies, temporary login tokens, SSH private keys, or passwords. +- One-click login should fail closed for real authentication errors, but best-effort environment repair must not block login unless required for correctness. +- Session keepalive/cleanup repair must be idempotent and throttled; do not restart or rewrite BT panel aggressively without explicit approval. +- Respect existing BT panel services and ports. Do not occupy panel, nginx, redis, or mysql ports. +- Old servers with existing BT API credentials must still receive the session-cleanup TTL check before login. +- Record bootstrap state without storing secrets. + +## Regression tests + +Cover: + +- Existing credentials still trigger throttled TTL check. +- Missing SSH auth does not block login URL generation. +- Failed repair is throttled and retried later. +- Temporary login token TTL remains bounded. +- No secret values appear in returned bootstrap/status payloads. diff --git a/.skills/nexus-security-review/SKILL.md b/.skills/nexus-security-review/SKILL.md new file mode 100644 index 00000000..c07c8586 --- /dev/null +++ b/.skills/nexus-security-review/SKILL.md @@ -0,0 +1,40 @@ +--- +name: nexus-security-review +description: Use when reviewing or changing Nexus backend/API/security-sensitive code. Focuses on FastAPI admin APIs, SQLAlchemy data access, SSH operations, file manager boundaries, logging redaction, and regression tests. +--- + +# Nexus Security Review + +Use this skill before changing Nexus backend/API code or doing a security pass. + +## Required workflow + +1. Identify entrypoints: FastAPI router, service method, DB model/session, Redis/lock usage, SSH/BT panel call. +2. Draw the request path in notes: API -> schema -> service -> DB/Redis/SSH/external. +3. Check auth first: admin-only endpoints must depend on the current admin dependency; background/admin tools must still preserve audit logs. +4. Validate every user-controlled value at the schema/API boundary and again before shell/path-sensitive sinks. +5. For every fix, add pytest coverage for the dangerous case and the allowed case. +6. Run focused tests, then full pytest when practical, then git diff --check. +7. Update the Nexus Markdown report with changed files, tests, and residual risks. + +## Nexus-specific rules + +- Do not print secrets: passwords, SSH keys, tokens, cookies, API keys, .env values, BT panel credentials. +- script_service is an intentional administrator remote shell. Do not report arbitrary shell execution there as a vulnerability. Review only auth, audit trail, timeout, output truncation, and redaction. +- File manager APIs must not unexpectedly operate outside the target path/server chosen by the admin UI. +- Any remote shell command must use shlex.quote; add -- where command options can be confused with filenames. +- Archive extraction must list and validate members before extraction; reject absolute paths, .., backslashes, and special file types. +- Error responses must not expose stack traces or secrets. +- DB access should use SQLAlchemy parameters/query builder, not raw string interpolation. + +## Review output + +For each finding record: + +- Severity: Critical/High/Medium/Low/Hardening +- Call chain +- Evidence file/function +- Exploit or failure boundary +- Fix summary +- Test command/result +- Commit hash if committed diff --git a/.skills/nexus-ssh-safety/SKILL.md b/.skills/nexus-ssh-safety/SKILL.md new file mode 100644 index 00000000..5a582250 --- /dev/null +++ b/.skills/nexus-ssh-safety/SKILL.md @@ -0,0 +1,29 @@ +--- +name: nexus-ssh-safety +description: Use when reviewing or editing Nexus code that executes commands on managed servers over SSH, including file manager, transfer, archive, agent install, and BT panel bootstrap flows. +--- + +# Nexus SSH Safety + +Use for any Nexus feature that builds shell commands or transfers files over SSH. + +## Command construction rules + +- Prefer argument arrays/local library APIs when available; otherwise quote every dynamic shell token with shlex.quote. +- Insert -- before filename/path operands for commands that parse options: m, mv, cp, ar, zip, chmod, chown, etc. +- Never concatenate unquoted user input into shell command strings. +- Keep timeouts explicit for network/SSH work; avoid unbounded commands. +- Truncate or sanitize command output before returning it to API clients. +- Do not log secrets or full credential-bearing URLs. + +## Path and archive rules + +- Normalize paths with POSIX semantics for remote Linux paths. +- Reject empty paths where dangerous, root/system paths for recursive destructive operations, and traversal where the feature expects a bounded directory. +- Archive creation must protect member names beginning with -. +- Archive extraction must validate member names and types before extraction. +- Temporary files/directories must be unique and cleaned best-effort. + +## script_service exception + +script_service intentionally lets administrators run arbitrary shell. Treat it as a privileged admin feature, not an injection bug. Still check admin auth, audit logs, timeout, output size, and secret redaction. diff --git a/2025.2 b/2025.2 new file mode 100644 index 00000000..9b5f3327 --- /dev/null +++ b/2025.2 @@ -0,0 +1,5 @@ +Collecting tzdata + Downloading tzdata-2026.2-py2.py3-none-any.whl.metadata (1.4 kB) +Downloading tzdata-2026.2-py2.py3-none-any.whl (349 kB) +Installing collected packages: tzdata +Successfully installed tzdata-2026.2 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..bc7837b7 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,96 @@ +# Nexus 6.0 — Agent 入口(薄 stub) + +> **长文 SSOT**: [docs/project/nexus-functional-development-guide.md](docs/project/nexus-functional-development-guide.md) + [附录](docs/project/nexus-functional-development-guide-appendix.md) +> **会话接续**: [docs/project/AI-HANDOFF-2026-06-03.md](docs/project/AI-HANDOFF-2026-06-03.md) +> **文档总索引**: [docs/README.md](docs/README.md) + +## 项目概要 + +Nexus:2000+ 子机运维平台。后端 FastAPI + MySQL + Redis;前端 **Vue 3 + Vuetify 4 + TypeScript SPA**(15 页,`frontend/src/pages/`);构建输出 `web/app/`;安装向导 `web/app/install.html`。 + +## 本地开发 + +```bash +docker compose up -d mysql redis +bash scripts/start-dev.sh # API :8600 +bash scripts/local_verify.sh # L2b +cd frontend && npm run dev # :3000/app/ +``` + +路径见 [linux-dev-paths.md](docs/project/linux-dev-paths.md)。 + +## Git(仅本机快照) + +- 本地 `git commit` 用于回滚;**不 `git push`**(除非你明确要求)。 +- 已移除 `origin` 跟踪,避免误推;原远程:`http://66.154.115.8:3000/admin/Nexus.git`(需恢复时说一声)。 +- `.env`、`SECRETS.md`、`*.pem` 已在 `.gitignore`,**勿提交**。 +- 改代码仍写 `docs/changelog/YYYY-MM-DD-*.md`(≥10 行)。 +- 用户说「提交快照 / commit」时执行。 + +## 强制约束 + +- 完美实现:`.cursor/rules/perfect-implementation.mdc` +- 安全 / 后端 / 前端:`.cursor/rules/nexus-*.mdc` +- 改代码必 `docs/changelog/YYYY-MM-DD-*.md`(≥10 行) +- 测试由 Agent 完成;用户只做终验 + +## 不可改 + +`API_KEY` · `SECRET_KEY` · `ENCRYPTION_KEY` · `DATABASE_URL` + +## 部署 + +默认 **本机 rsync → SSH 生产机**(不 push Gitea): + +```bash +bash deploy/pre_deploy_check.sh +bash deploy/deploy-production.sh # rsync + Docker upgrade --skip-git + 前端 +``` + +可选:`NEXUS_DEPLOY_VIA_GIT=1` 恢复远程 `git pull`。仅 `git push` 当你明确要求时。 + +详情见功能指南 §17–18。 + +## 归档文档 + +历史审计合并卷:`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` §9–10 | + +## 探索规则(省 token) + +详见 `.cursor/rules/agent-exploration.mdc`。要点:**先地图再 Grep(带 path)→ Read ≤5 个文件 → 停止**。 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..0cfdc36a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,28 @@ +# Nexus 6.0 — Agent 入口(薄 stub) + +> 与 [AGENTS.md](AGENTS.md) 相同入口;长文见 [docs/project/nexus-functional-development-guide.md](docs/project/nexus-functional-development-guide.md)。 + +## 快速链接 + +| 用途 | 路径 | +|------|------| +| 功能 / API / 14 页 | `docs/project/nexus-functional-development-guide.md` | +| 附录(sync 端点、调用链) | `docs/project/nexus-functional-development-guide-appendix.md` | +| 当前 handoff | `docs/project/AI-HANDOFF-2026-06-03.md` | +| 本地验证 | `bash scripts/local_verify.sh` | +| 验收 L0–L5 | `docs/project/development-acceptance-standard.md` | +| 风险接受(单人运维) | `docs/project/risk-acceptance-single-operator.md` | +| 文档索引 | `docs/README.md` | +| 历史归档 | `docs/archive/README.md` | + +## 栈 + +FastAPI · Vue 3 SPA · Hash 路由 `#/` · 门控 `deploy/pre_deploy_check.sh`(7 道) + +## 流程 + +``` +实现 → local_verify → 审计(按 diff 量级)→ changelog →(批准)部署 +``` + +强制规则:`.cursor/rules/` · `standards/` diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..b2237ddf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,46 @@ +# Nexus 6.0 - API + static SPA (web/app) +FROM python:3.12-slim-bookworm + +ARG DEBIAN_MIRROR=https://mirrors.aliyun.com/debian +ARG DEBIAN_SECURITY_MIRROR=https://mirrors.aliyun.com/debian-security +ARG PIP_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ +ARG PIP_TRUSTED_HOST=mirrors.aliyun.com + +RUN set -eux; \ + sed -i \ + -e "s|http://deb.debian.org/debian-security|${DEBIAN_SECURITY_MIRROR}|g" \ + -e "s|http://deb.debian.org/debian|${DEBIAN_MIRROR}|g" \ + /etc/apt/sources.list.d/debian.sources; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + curl \ + openssh-client \ + rsync \ + sshpass; \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir --index-url "${PIP_INDEX_URL}" --trusted-host "${PIP_TRUSTED_HOST}" -r requirements.txt + +COPY server/ ./server/ +COPY web/app/ ./web/app/ +COPY web/app-v2/ ./web/app-v2/ +COPY docker/entrypoint.sh ./docker/entrypoint.sh +RUN chmod +x ./docker/entrypoint.sh + +ENV PYTHONPATH=/app \ + PYTHONUNBUFFERED=1 \ + NEXUS_HOST=0.0.0.0 \ + NEXUS_PORT=8600 + +EXPOSE 8600 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=90s --retries=3 \ + CMD curl -sf http://127.0.0.1:8600/health | grep -q '^ok' || exit 1 + +ENTRYPOINT ["/app/docker/entrypoint.sh"] +CMD ["uvicorn", "server.main:app", "--host", "0.0.0.0", "--port", "8600"] + + diff --git a/Dockerfile.prod b/Dockerfile.prod new file mode 100644 index 00000000..afce351a --- /dev/null +++ b/Dockerfile.prod @@ -0,0 +1,77 @@ +# Nexus 6.0 鈥?production image (frontend build + API) +# Build: docker compose -f docker/docker-compose.prod.yml build +# See: docs/design/plans/2026-06-04-1panel-docker-production.md + +# 鈹€鈹€ Stage 1: Vue/Vite SPA 鈹€鈹€ +FROM node:20-bookworm-slim AS frontend +WORKDIR /build/frontend + +COPY frontend/package.json frontend/package-lock.json ./ +RUN npm ci --ignore-scripts + +COPY frontend/ ./ +RUN npx vite build + + +# Stage 1b: React/Vite App V2 +FROM node:20-bookworm-slim AS frontend_v2 +WORKDIR /build/frontend-v2 + +COPY frontend-v2/package.json frontend-v2/package-lock.json* ./ +RUN if [ -f package-lock.json ]; then npm ci --ignore-scripts; else npm install --ignore-scripts; fi + +COPY frontend-v2/ ./ +RUN npx vite build +# 鈹€鈹€ Stage 2: Python API + built static assets 鈹€鈹€ +FROM python:3.12-slim-bookworm + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + curl \ + openssh-client \ + rsync \ + sshpass \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY server/ ./server/ +COPY web/agent/ ./web/agent/ +COPY --from=frontend /build/web/app ./web/app/ +COPY --from=frontend_v2 /build/web/app-v2 ./web/app-v2/ +# Login wallpaper cache (Vite does not emit this dir; sync also writes here at runtime) +COPY web/app/wallpapers/ ./web/app/wallpapers/ +# Install wizard + static SPA assets not emitted by Vite +COPY web/app/install.html ./web/app/install.html +COPY web/app/servers_import_template.csv ./web/app/servers_import_template.csv +RUN mkdir -p ./web/app/vendor +COPY web/app/vendor/tailwindcss-browser.js \ + web/app/vendor/theme-init.js \ + web/app/vendor/theme.css \ + web/app/vendor/alpinejs.min.js \ + ./web/app/vendor/ +COPY docker/entrypoint.sh ./docker/entrypoint.sh +RUN chmod +x ./docker/entrypoint.sh \ + && test -f /app/web/app/install.html \ + && test -f /app/web/app/vendor/alpinejs.min.js \ + && test -f /app/web/app/vendor/tailwindcss-browser.js \ + && test -f /app/web/app/vendor/theme.css \ + && test -f /app/web/app/vendor/theme-init.js + +ENV PYTHONPATH=/app \ + PYTHONUNBUFFERED=1 \ + NEXUS_HOST=0.0.0.0 \ + NEXUS_PORT=8600 + +EXPOSE 8600 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=90s --retries=3 \ + CMD curl -sf http://127.0.0.1:8600/health | grep -q '^ok' || exit 1 + +ENTRYPOINT ["/app/docker/entrypoint.sh"] +CMD ["uvicorn", "server.main:app", "--host", "0.0.0.0", "--port", "8600"] + + diff --git a/PROJECT_HANDOVER.md b/PROJECT_HANDOVER.md new file mode 100644 index 00000000..f1213a72 --- /dev/null +++ b/PROJECT_HANDOVER.md @@ -0,0 +1,11 @@ +# Nexus 6.0 项目交接(已 supersede) + +请以以下文档为准: + +| 文档 | 路径 | +|------|------| +| 功能开发 SSOT | [docs/project/nexus-functional-development-guide.md](docs/project/nexus-functional-development-guide.md) | +| 会话接续 | [docs/project/AI-HANDOFF-2026-06-03.md](docs/project/AI-HANDOFF-2026-06-03.md) | +| 文档总索引 | [docs/README.md](docs/README.md) | + +历史全文:[docs/archive/project/PROJECT_HANDOVER.md](docs/archive/project/PROJECT_HANDOVER.md) diff --git a/README.md b/README.md new file mode 100644 index 00000000..0469716d --- /dev/null +++ b/README.md @@ -0,0 +1,189 @@ +# Nexus 6.0 + +面向 **2000+ 服务器** 的运维管理平台:FastAPI + Async SQLAlchemy + Redis + WebSocket + Telegram(后端),Vue 3 + Vuetify 4 + TypeScript SPA(前端 14 页)。 + +| 项 | 说明 | +|----|------| +| 生产域名 | https://api.synaglobal.vip | +| 默认安装目录 | `/opt/nexus` | +| 后端端口 | `8600`(1Panel 反代到 `http://127.0.0.1:8600`) | +| 安装向导 | `https://你的域名/app/install.html` | +| 文档索引 | [docs/README.md](docs/README.md) | + +生产 Docker 栈 **仅含 Nexus 容器**;MySQL / Redis 在宿主机或 1Panel 自行安装。 + +--- + +## 一键命令速查(root · 公共仓库 · 无需令牌) + +| 场景 | 一条命令 | +|------|----------| +| **仅装 Nexus**(1Panel 已就绪) | `curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/quick-install.sh" \| bash` | +| **重装服务器**(1Panel + Docker + Nexus) | `curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/install-1panel-docker.sh" \| bash` | +| **一键更新**(pull + 重建镜像) | `curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/update.sh" \| bash` | +| **强制无缓存重建**(entrypoint 变更后) | `curl -fsSL ".../deploy/update.sh" \| bash -s -- --no-cache` | +| **已 clone** | `cd /opt/nexus && bash deploy/update.sh` | + +指定资源档位(`1c4g` / `2c8g` 默认 / `4c16g`): + +```bash +curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/quick-install.sh" | bash -s -- --profile 4c16g +``` + +**raw 地址**:`http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/<脚本名>` + +安装完成后浏览器打开安装向导,完成 MySQL(自建)/ Redis(自建)/ 管理员初始化。 + +--- + +## NX 统一运维菜单 + +装好后在服务器任意目录输入 **`nx`**(`install` / `update` / 首次运行 `nx` 会自动注册 `/usr/local/bin/nx`): + +```bash +nx # 统一菜单:安装 + 一键更新 + 重启 + 日志 + 备份 +nx update # 非交互一键更新 +nx update --no-cache +nx install-fresh # 全新安装 +nx health # 健康检查 +``` + +| 菜单项 | 作用 | +|--------|------| +| `[4] 一键更新` | pull + 重建 Docker 镜像(等同 `deploy/update.sh`) | +| `[5] 重启 Nexus` | 重启 Python 应用 | +| `[6] 日志` | 跟踪容器日志 | +| `[1][2]` | 安装 / 全新安装向导 | + +全局命令(符号链接): + +| 命令 | 作用 | +|------|------| +| `nx` | 统一运维菜单 | +| `nexus-update` | 等同 `deploy/update.sh` | +| `nexus-fresh` | 等同 `install-nexus-fresh.sh` | +| `nexus-install` | 等同 `quick-install.sh` | + +> `nx god` 已合并进 `nx` 主菜单,仍可作为兼容别名使用。 + +--- + +## 部署脚本一览(`deploy/`) + +### 1Panel + Docker 生产(主路径) + +| 脚本 | 用途 | curl 入口 | +|------|------|-----------| +| [quick-install.sh](deploy/quick-install.sh) | **公共仓库安装入口** | ✅ 上表 | +| [install-1panel-docker.sh](deploy/install-1panel-docker.sh) | 1Panel + Docker + Nexus 全链 | ✅ 上表 | +| [install-nexus-fresh.sh](deploy/install-nexus-fresh.sh) | 全新空库 → 安装向导 | `bash deploy/install-nexus-fresh.sh --skip-clone` | +| [update.sh](deploy/update.sh) | **一键更新**(pull + 重建) | ✅ 上表 | +| [upgrade-1panel-docker.sh](deploy/upgrade-1panel-docker.sh) | 转调 `nexus-1panel.sh upgrade` | — | +| [nexus-1panel.sh](deploy/nexus-1panel.sh) | 安装 / 升级 / check / ports 底层 | — | +| [nx](deploy/nx) | **统一交互菜单** | `nx` | +| [uninstall-mysql-compose.sh](deploy/uninstall-mysql-compose.sh) | 卸载遗留 Compose MySQL 容器 | — | +| [uninstall-redis-compose.sh](deploy/uninstall-redis-compose.sh) | 卸载遗留 Compose Redis 容器 | — | +| [sync-install-wizard-to-container.sh](deploy/sync-install-wizard-to-container.sh) | 热同步 install.html 到容器 | — | +| [download-install-fresh.sh](deploy/download-install-fresh.sh) | 下载安装脚本(私有库可选 Token) | — | + +详细说明:[deploy/README-1panel.md](deploy/README-1panel.md) + +### 更新选项 + +```bash +bash deploy/update.sh # 默认:Git 拉取(若有)+ 重建容器 +bash deploy/update.sh --check # 仅查 Git +bash deploy/update.sh --no-cache # 无缓存重建镜像 +bash deploy/update.sh --no-backup # 跳过 MySQL 备份 +``` + +### Docker Compose 与档位 + +| 路径 | 用途 | +|------|------| +| [docker/docker-compose.prod.yml](docker/docker-compose.prod.yml) | 生产 Compose(**仅 nexus 服务**) | +| [docker/profiles/1c4g.env](docker/profiles/1c4g.env) | 1 核 4G | +| [docker/profiles/2c8g.env](docker/profiles/2c8g.env) | 2 核 8G(默认) | +| [docker/profiles/4c16g.env](docker/profiles/4c16g.env) | 4 核 16G | +| [docker/README.md](docker/README.md) | 镜像与本地开发说明 | + +### 经典 SSH / Supervisor 部署(宝塔 / 裸机) + +| 脚本 | 用途 | +|------|------| +| [install.sh](deploy/install.sh) | 传统安装 | +| [upgrade.sh](deploy/upgrade.sh) | 拉代码 + 重启 | +| [deploy-on-server.sh](deploy/deploy-on-server.sh) | 服务器侧部署 | +| [deploy-production.sh](deploy/deploy-production.sh) | 生产发布流程 | +| [deploy-frontend.sh](deploy/deploy-frontend.sh) | 构建 Vue 前端并打包到 `web/app/` | +| [pre_deploy_check.sh](deploy/pre_deploy_check.sh) | **7 道门控** | +| [health_monitor.sh](deploy/health_monitor.sh) | cron 健康检查 | +| [db_backup.sh](deploy/db_backup.sh) | MySQL 备份 | +| [uninstall.sh](deploy/uninstall.sh) | 卸载 | + +--- + +## 迁机 vs 全新安装 + +| 场景 | 做法 | +|------|------| +| **新 VPS、空库** | `curl \| quick-install.sh` → 1Panel 自建 MySQL/Redis → `/app/install.html` | +| **已有数据、换机** | `nexus-1panel.sh install --from-env` 或 `nx` → 安装向导 | +| **仅升级代码/镜像** | `nx update` 或 `bash deploy/update.sh` | +| **不必重装 VPS** | `cd /opt/nexus && nx update` | + +每台新服务器自动生成唯一 `NEXUS_SECRET_KEY` / `NEXUS_API_KEY` / `NEXUS_ENCRYPTION_KEY`(写入 `docker/.env.prod`,备份 `/root/.nexus-install-secrets-*.env`)。MySQL 密码由自建库时自行设定。 + +--- + +## 技术栈与目录 + +``` +Nexus/ +├── server/ # FastAPI 后端(Clean Architecture) +├── frontend/ # Vue 3 + Vuetify 4 SPA → 构建到 web/app/ +├── web/app/ # 静态资源挂载点(构建产物 gitignore) +├── deploy/ # 安装、更新、门控、nx 菜单 +├── docker/ # Compose、镜像、profiles +├── docs/ # 设计 / changelog / 审计 / 报告 +├── tests/ # API 与集成测试 +└── standards/ # 开发与逐行审计标准 +``` + +--- + +## 开发 + +```bash +pip install -r requirements.txt +uvicorn server.main:app --reload --port 8600 + +cd frontend && npm run dev # http://localhost:3000/app/ +``` + +Linux 路径:[docs/project/linux-dev-paths.md](docs/project/linux-dev-paths.md) · [UBUNTU-DEV-README.txt](UBUNTU-DEV-README.txt) + +--- + +## 文档与规范 + +| 文档 | 说明 | +|------|------| +| [docs/README.md](docs/README.md) | 文档总索引 | +| [docs/project/nexus-functional-development-guide.md](docs/project/nexus-functional-development-guide.md) | 功能 / API / 14 页 | +| [AGENTS.md](AGENTS.md) · [CLAUDE.md](CLAUDE.md) | Agent 薄入口 | + +变更:`docs/changelog/` · 门控:`docs/audit/` + +--- + +## 许可与仓库 + +| 用途 | 地址 | +|------|------| +| 仓库首页 | http://66.154.115.8:3000/admin/Nexus | +| Git | http://66.154.115.8:3000/admin/Nexus.git | + +分支 `main` · 提交前跑 `deploy/pre_deploy_check.sh`(7 道门控) + +**勿提交**:`deploy/nexus-1panel.secrets.sh`、根目录 `.env`、`SECRETS.md` diff --git a/UBUNTU-DEV-README.txt b/UBUNTU-DEV-README.txt new file mode 100644 index 00000000..68454309 --- /dev/null +++ b/UBUNTU-DEV-README.txt @@ -0,0 +1,49 @@ +Nexus — Ubuntu 离线开发包 +======================== + +解压: + mkdir -p ~/Nexus && tar xzf nexus-ubuntu-dev-2026-06-03.tar.gz -C ~/ + cd ~/Nexus + +Python 后端: + sudo apt install -y python3 python3-venv python3-pip git curl build-essential + python3 -m venv .venv && source .venv/bin/activate + pip install -r requirements.txt -r requirements-dev.txt + # 复制已有 .env,或执行下方 generate + sync_root_env + +MySQL + Redis(Docker): + sudo apt install -y docker.io docker-compose-v2 + sudo usermod -aG docker "$USER" # 重新登录后免 sudo docker + python3 docker/generate_env.py + python3 docker/sync_root_env.py # 生成根目录 .env(127.0.0.1 + 较小连接池) + docker compose up -d mysql redis # Redis 已映射 6379 供宿主机 uvicorn + # 首次空库:先不要 sync_root_env,无 .env 启动 uvicorn 走 /app/install.html 向导 + +启动 API: + bash scripts/start-dev.sh + # 或一键验证:bash scripts/local_verify.sh + +前端(Node 20+): + cd frontend && npm ci && npx vite build + +编码 / 换行(Windows tar 解压后必做): + git config core.autocrlf false + bash scripts/normalize_worktree_lf.sh + python3 scripts/check_worktree_eol.py + python3 scripts/check_text_eol.py + 详见 docs/audit/2026-06-04-encoding-review-ubuntu-phase1.md + +SOCKS5 代理(拉 Docker / npm 镜像): + 默认 192.168.124.93:10808 — 改 scripts/proxychains-docker.conf 或环境变量 + scripts/with-proxy.sh docker compose pull + cd frontend && ../scripts/with-proxy.sh npm ci + source scripts/proxy-env.sh # pip/curl 等 + +本地管理员(空库一次): + # .env 增加 NEXUS_TEST_ADMIN_PASSWORD=你的密码 + python3 scripts/seed_local_admin.py + cd frontend && NEXUS_E2E_BASE=http://127.0.0.1:8600 NEXUS_E2E_PASSWORD=... npm run test:e2e + +工作区路径:~/Nexus +路径 SSOT:docs/project/linux-dev-paths.md · docs/project/local-integration-test.md +Git 远程:http://66.154.115.8:3000/admin/Nexus.git(网络恢复后可 git pull) diff --git a/audit_scan.py b/audit_scan.py new file mode 100644 index 00000000..f2238cc3 --- /dev/null +++ b/audit_scan.py @@ -0,0 +1,136 @@ +"""Full codebase audit scanner""" +import sys, os, re +sys.path.insert(0, '.') + +# === 1. Backend API endpoints === +print('=' * 60) +print('1. BACKEND ROUTE REGISTRY') +print('=' * 60) + +api_files = [f for f in os.listdir('server/api') if f.endswith('.py') and f not in ('__init__.py', 'dependencies.py', 'schemas.py')] +for f in sorted(api_files): + try: + with open(f'server/api/{f}', 'r', encoding='utf-8') as fh: + content = fh.read() + prefix_match = re.search(r'prefix=["\'](/api/[^"\']+)["\']', content) + prefix = prefix_match.group(1) if prefix_match else '???' + routes = re.findall(r'@router\.(get|post|put|delete|websocket)\(["\'](.*?)["\']', content) + if routes: + print(f' {f}: prefix={prefix}') + for method, path in routes: + print(f' {method.upper():4s} {prefix}{path}') + except Exception as e: + print(f' {f}: ERROR {e}') + +# === 2. Frontend API calls === +print() +print('=' * 60) +print('2. FRONTEND API CALLS') +print('=' * 60) + +html_files = [f for f in os.listdir('web/app') if f.endswith('.html')] +for f in sorted(html_files): + try: + with open(f'web/app/{f}', 'r', encoding='utf-8') as fh: + content = fh.read() + pattern = r"API\s*\+\s*['\"](/[^'\"]+)['\"]" + calls = re.findall(pattern, content) + if calls: + print(f' {f}:') + for call in sorted(set(calls)): + print(f' {call}') + except Exception as e: + print(f' {f}: ERROR {e}') + +# === 3. Missing imports (AuditLog used but not imported) === +print() +print('=' * 60) +print('3. POTENTIAL MISSING IMPORTS (AuditLog used without import)') +print('=' * 60) + +for f in sorted(api_files): + try: + with open(f'server/api/{f}', 'r', encoding='utf-8') as fh: + content = fh.read() + uses_audit = 'AuditLog(' in content + imports_audit = 'from server.domain.models import' in content and 'AuditLog' in content.split('from server.domain.models import')[1].split('\n')[0] if 'from server.domain.models import' in content else False + # Also check local imports + local_imports_audit = 'from server.domain.models import AuditLog' in content or ('AuditLog' in content and 'import AuditLog' in content) + if uses_audit and not imports_audit and not local_imports_audit: + # Check if it's imported via local import inside function + has_local = 'import AuditLog' in content or 'from server.domain.models import' in content + if not has_local: + print(f' [BUG] {f}: Uses AuditLog() but never imported!') + else: + print(f' [WARN] {f}: Uses AuditLog() via local import only') + except Exception as e: + pass + +# === 4. SSH pool usage (acquire without release) === +print() +print('=' * 60) +print('4. SSH POOL: acquire() without release()') +print('=' * 60) + +for root, dirs, files in os.walk('server'): + for f in files: + if not f.endswith('.py'): + continue + path = os.path.join(root, f) + try: + with open(path, 'r', encoding='utf-8') as fh: + content = fh.read() + acquires = len(re.findall(r'ssh_pool\.acquire', content)) + releases = len(re.findall(r'ssh_pool\.release', content)) + if acquires > 0 and acquires != releases: + print(f' [WARN] {path}: acquire={acquires} release={releases}') + except: + pass + +# === 5. AsyncSessionLocal without commit === +print() +print('=' * 60) +print('5. DB writes without commit()') +print('=' * 60) + +for root, dirs, files in os.walk('server'): + for f in files: + if not f.endswith('.py'): + continue + path = os.path.join(root, f) + try: + with open(path, 'r', encoding='utf-8') as fh: + content = fh.read() + has_create = 'repo.create(' in content or 'session.add(' in content or 'session.execute(update' in content + has_commit = 'session.commit()' in content + if has_create and not has_commit and 'api/' in path: + print(f' [WARN] {path}: has DB writes but no session.commit()') + except: + pass + +# === 6. Hardcoded secrets or IPs === +print() +print('=' * 60) +print('6. HARDCODED SECRETS / IPs') +print('=' * 60) + +for root, dirs, files in os.walk('server'): + for f in files: + if not f.endswith('.py'): + continue + path = os.path.join(root, f) + try: + with open(path, 'r', encoding='utf-8') as fh: + lines = fh.readlines() + for i, line in enumerate(lines, 1): + if re.search(r'password\s*=\s*["\'][^"\']{3,}["\']', line) and 'test' not in line.lower() and 'hash' not in line.lower() and 'Column' not in line: + print(f' [WARN] {path}:{i}: possible hardcoded password') + if re.search(r'\b10\.0\.\d+\.\d+\b|\b192\.168\.\d+\.\d+\b|\b172\.(1[6-9]|2\d|3[01])\.\d+\.\d+\b', line) and 'example' not in line.lower() and 'comment' not in line.lower(): + print(f' [INFO] {path}:{i}: hardcoded private IP') + except: + pass + +print() +print('=' * 60) +print('SCAN COMPLETE') +print('=' * 60) diff --git a/current-theme.png b/current-theme.png new file mode 100644 index 00000000..e0bd7d18 Binary files /dev/null and b/current-theme.png differ diff --git a/dashboard-dark.png b/dashboard-dark.png new file mode 100644 index 00000000..7b087822 Binary files /dev/null and b/dashboard-dark.png differ diff --git a/dashboard-light.png b/dashboard-light.png new file mode 100644 index 00000000..2c1761ca Binary files /dev/null and b/dashboard-light.png differ diff --git a/deploy-login-verify.png b/deploy-login-verify.png new file mode 100644 index 00000000..c3979bcb Binary files /dev/null and b/deploy-login-verify.png differ diff --git a/deploy/1panel/openresty-nexus.conf.example b/deploy/1panel/openresty-nexus.conf.example new file mode 100644 index 00000000..97e513c8 --- /dev/null +++ b/deploy/1panel/openresty-nexus.conf.example @@ -0,0 +1,71 @@ +# 1Panel OpenResty — Nexus reverse proxy (paste into site custom config) +# Domain: api.synaglobal.vip +# Upstream: Nexus Docker on 127.0.0.1:8600 (docker-compose.prod.yml) +# +# SSL: configure in 1Panel UI (Let's Encrypt). This snippet assumes HTTPS server block. + +upstream nexus_api { + server 127.0.0.1:8600; + keepalive 32; +} + +# WebSocket log without query string (JWT must not hit access_log) +log_format nexus_ws '$remote_addr - [$time_local] "$request_method $uri" $status $body_bytes_sent'; + +client_max_body_size 500m; +client_body_timeout 600s; + +location /api/ { + proxy_pass http://nexus_api; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 600s; + proxy_send_timeout 600s; + proxy_connect_timeout 10s; +} + +location /ws/ { + access_log /www/sites/api.synaglobal.vip/log/ws.log nexus_ws; + proxy_pass http://nexus_api; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; +} + +location /health { + proxy_pass http://nexus_api; + proxy_set_header Host $host; +} + +location /app/ { + proxy_pass http://nexus_api; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; +} + +location /agent/ { + proxy_pass http://nexus_api; + proxy_set_header Host $host; +} + +# Fallback: install mode or misc routes served by FastAPI +location / { + proxy_pass http://nexus_api; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; +} diff --git a/deploy/README-1panel.md b/deploy/README-1panel.md new file mode 100644 index 00000000..c627b635 --- /dev/null +++ b/deploy/README-1panel.md @@ -0,0 +1,257 @@ +# 1Panel + Docker 一键部署 + +> 完整知识文档:[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` | 仅装 Nexus(1Panel 已就绪) | `curl -fsSL "http://66.154.115.8:3000/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` + +--- + +## 重装服务器(1Panel + Docker + Nexus) + +```bash +curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/install-1panel-docker.sh" | bash +``` + +4 核 16G: + +```bash +curl -fsSL ".../install-1panel-docker.sh" | bash -s -- --profile 4c16g +``` + +已装 1Panel,只装 Nexus: + +```bash +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 +``` + +安装完成后:`https://api.synaglobal.vip/app/install.html` + +--- + +## 一键更新(日常) + +```bash +cd /opt/nexus && bash deploy/update.sh +# 或 +nx update +# 或菜单 +nx # → [4] 一键更新 +``` + +| 选项 | 说明 | +|------|------| +| `--check` | 仅查 Git,不重建 | +| `--no-cache` | 无缓存重建镜像(entrypoint 变更后必用) | +| `--no-backup` | 跳过 MySQL 备份 | +| `--prune` | 清理悬空镜像 | + +远程: + +```bash +curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/update.sh" | bash +curl -fsSL ".../update.sh" | bash -s -- --no-cache +``` + +--- + +## NX 全局命令 + +安装 / 升级 / 首次运行 `nx` 会自动注册到 `/usr/local/bin`: + +```bash +# 手动注册(任意目录可用 nx) +NEXUS_ROOT=/opt/nexus bash /opt/nexus/deploy/install-nx-cli.sh + +nx # 统一运维菜单 +nexus-update # 等同 update.sh +nexus-fresh # 等同 install-nexus-fresh.sh +nexus-install # 等同 quick-install.sh +``` + +| 菜单 / 子命令 | 功能 | +|---------------|------| +| `[1]` | 安装向导(档位 / 迁机 / skip-clone) | +| `[2]` | 全新安装 → `/app/install.html` | +| `[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` + +--- + +## MySQL / Redis(1Panel 应用商店) + +Docker 栈**仅含 Nexus 容器**。MySQL / Redis 在 **1Panel 应用商店**安装。 + +### 1Panel 官方互联方式(必读) + +[1Panel 维护者说明](https://github.com/1Panel-dev/1Panel/issues/11676):应用商店应用使用 **bridge + `1panel-network`**;**容器之间用容器名通信**(Docker 内置 DNS),不要用 `localhost` / `127.0.0.1` / `host.docker.internal`。 + +| 场景 | 正确主机 | +|------|----------| +| Nexus → 1Panel MySQL | `1Panel-mysql-xxxx`(容器列表中的名称) | +| Nexus → 1Panel Redis | `1Panel-redis-xxxx` | +| OpenResty → 应用 | `127.0.0.1:端口`(OpenResty 为 host 网络) | + +`nx update` / `nexus-1panel.sh` 会: + +1. 将 Nexus 加入 **`1panel-network`**(`docker/docker-compose.1panel.yml`) +2. 自动探测 MySQL/Redis 容器名写入 `docker/.env.prod`(含 `NEXUS_REDIS_URL=redis://1Panel-redis-xxxx:6379/0`) +3. 安装向导步骤 3 **预填 MySQL/Redis 容器名** + +### 安装步骤 + +1. **应用商店** → 安装 **MySQL**、**Redis** +2. **数据库** → 创建库 `nexus`、用户 `nexus`(仅授权 `nexus` 库),记下密码 +3. `nx update` 后打开安装向导步骤 3(主机应已预填 `1Panel-mysql-…`) +4. 填写 MySQL 密码;Redis:**用户名通常留空** + 应用参数密码(自动尝试 `:pass@` / `default`) + +### 常见错误 1045(Access denied for 'nexus'@'172.18.x.x') + +TCP 已通,但 MySQL 用户多为 **`nexus@localhost`**,Docker 内 Nexus 从 **172.x** 连接会被拒绝。 + +在服务器执行(按提示输入 root 密码与 nexus 密码): + +```bash +cd /opt/nexus && bash deploy/fix-1panel-mysql-grant.sh +``` + +或一次性: + +```bash +MYSQL_ROOT_PASSWORD='1Panel里MySQL的root密码' \ +NEXUS_DB_PASSWORD='向导里要填的nexus密码' \ +bash /opt/nexus/deploy/fix-1panel-mysql-grant.sh +``` + +然后安装向导步骤 3:**库名/用户 `nexus`**,主机为 `1Panel-mysql-xxxx`,密码与上面一致。 + +若仍报 `Can't connect to MySQL/Redis on 'host.docker.internal'`:说明 Nexus **未接入 1panel-network** 或未探测到容器名,在服务器执行: + +```bash +cd /opt/nexus && bash deploy/detect-1panel-services.sh +nx update --no-cache +``` + +步骤 2 会 TCP 检测 MySQL/Redis;步骤 4 验证账号密码。 + +卸载旧 Compose 容器: + +```bash +bash deploy/uninstall-mysql-compose.sh +bash deploy/uninstall-redis-compose.sh +``` + +--- + +## 1Panel 反代 + +**禁止**脚本改 `/opt/1panel/apps/openresty/`。 + +1. 应用商店 → **OpenResty** +2. 网站 → 反代 `http://127.0.0.1:8600` +3. 参考:`deploy/1panel/openresty-nexus.conf.example` + +--- + +## 密钥 + +新机自动生成 `NEXUS_SECRET_KEY` / `NEXUS_API_KEY` / `NEXUS_ENCRYPTION_KEY` → `docker/.env.prod`,备份 `/root/.nexus-install-secrets-*.env`。 + +MySQL 密码在 1Panel 自建库时设定,向导步骤 3 填写。 + +| 场景 | 命令 | +|------|------| +| 新机 | `curl \| quick-install.sh` | +| 迁机 | `nexus-1panel.sh install --from-env /path/to/.env` | +| 更新 | `update.sh` 或 `nx update` | + +**Git**:公开仓库 `clone` / `pull` / `curl | bash` 无需令牌。 + +--- + +## 安装向导验收(服务器执行) + +在 **1Panel 终端**(root)一键检查网络、容器名预填、cron、镜像版本、本地 `/health`: + +```bash +cd /opt/nexus && sudo nx verify +``` + +**未安装**:通过后再打开 `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 +nx update --no-cache +``` + +外网 HTTPS 仍失败但脚本本地检查全绿 → 在 1Panel **网站**配置反代 `http://127.0.0.1:8600`(见 `deploy/1panel/openresty-nexus.conf.example`)。 + +## 安装向导热修复 + +```bash +cd /opt/nexus && git pull +bash deploy/update.sh --no-cache +# 或仅同步静态: +bash deploy/sync-install-wizard-to-container.sh +``` diff --git a/deploy/apply-release-bundle.sh b/deploy/apply-release-bundle.sh new file mode 100644 index 00000000..5ee4b501 --- /dev/null +++ b/deploy/apply-release-bundle.sh @@ -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 "$@" diff --git a/deploy/check_shell_eol.sh b/deploy/check_shell_eol.sh new file mode 100644 index 00000000..11bf2d53 --- /dev/null +++ b/deploy/check_shell_eol.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Fail if tracked shell scripts contain CR (CRLF) — breaks bash on Ubuntu. +# Usage: bash deploy/check_shell_eol.sh +# Run from repo root (or set NEXUS_DEPLOY_DIR). + +set -euo pipefail + +_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +_REPO_ROOT="$(cd "${_SCRIPT_DIR}/.." && pwd)" +ROOT="${NEXUS_DEPLOY_DIR:-${_REPO_ROOT}}" + +cd "${ROOT}" + +if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo "check_shell_eol: not a git repo at ${ROOT}" >&2 + exit 1 +fi + +BAD=0 +while IFS= read -r -d '' f; do + if grep -q $'\r' "${f}" 2>/dev/null; then + echo "CRLF/CR detected: ${f}" >&2 + BAD=1 + fi +done < <(git ls-files -z '*.sh') + +if [ "${BAD}" -ne 0 ]; then + echo "Fix: git add --renormalize '*.sh' or run dos2unix on listed files." >&2 + exit 1 +fi + +echo "check_shell_eol: OK (all tracked *.sh are LF-only)" diff --git a/deploy/db_backup.sh b/deploy/db_backup.sh new file mode 100644 index 00000000..7edbcd9c --- /dev/null +++ b/deploy/db_backup.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Nexus — MySQL 定时备份(Docker 1Panel / 宿主机) +# +# 0 3 * * * NEXUS_ROOT=/opt/nexus /opt/nexus/deploy/db_backup.sh >> /var/log/nexus_backup.log 2>&1 +# +# 亦可通过: sudo bash deploy/install_ops_cron.sh +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +NEXUS_ROOT="${NEXUS_ROOT:-$(dirname "$SCRIPT_DIR")}" +BACKUP_DIR="${NEXUS_BACKUP_DIR:-/var/backups/nexus}" +RETENTION_DAYS="${NEXUS_BACKUP_RETENTION_DAYS:-30}" +DUMP_SH="${SCRIPT_DIR}/mysql_dump_to_file.sh" + +if [[ ! -f "$DUMP_SH" ]]; then + echo "[$(date)] ERROR: missing $DUMP_SH" + exit 1 +fi +chmod +x "$DUMP_SH" 2>/dev/null || true + +mkdir -p "$BACKUP_DIR" +TIMESTAMP="$(date +%Y%m%d_%H%M%S)" +RAW_FILE="${BACKUP_DIR}/nexus_daily_${TIMESTAMP}.sql" +BACKUP_FILE="${RAW_FILE}.gz" + +echo "[$(date)] Starting backup → $BACKUP_FILE" +if ! NEXUS_ROOT="$NEXUS_ROOT" bash "$DUMP_SH" --output "$RAW_FILE" --root "$NEXUS_ROOT"; then + echo "[$(date)] ERROR: mysqldump failed" + rm -f "$RAW_FILE" + exit 1 +fi + +gzip -f "$RAW_FILE" +if [[ ! -f "$BACKUP_FILE" ]]; then + echo "[$(date)] ERROR: backup file not created" + exit 1 +fi + +SIZE="$(du -h "$BACKUP_FILE" | cut -f1)" +echo "[$(date)] Backup completed: $BACKUP_FILE ($SIZE)" + +DELETED="$(find "$BACKUP_DIR" -name 'nexus_*.sql.gz' -mtime +"${RETENTION_DAYS}" -delete -print 2>/dev/null | wc -l)" +if [[ "$DELETED" -gt 0 ]]; then + echo "[$(date)] Cleaned up $DELETED old backup(s) (>${RETENTION_DAYS} days)" +fi + +echo "[$(date)] Backup process complete" diff --git a/deploy/deploy-frontend.sh b/deploy/deploy-frontend.sh new file mode 100644 index 00000000..16d80ece --- /dev/null +++ b/deploy/deploy-frontend.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +# deploy-frontend.sh — Build Vuetify frontend and deploy to server +# +# Docker 生产:前端在镜像内构建,远程执行 nexus-1panel.sh upgrade +# Supervisor:tar 上传到 web/app + supervisorctl restart +# +# Usage: bash deploy/deploy-frontend.sh + +set -euo pipefail + +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}" "$@"; } + +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" ]] && 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_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" && 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 + fi +done +echo "unknown" +REMOTE +) + +RUNTIME="${REMOTE_RUNTIME%% *}" +DEPLOY_PATH="${REMOTE_RUNTIME#* }" +[[ "$DEPLOY_PATH" == "$REMOTE_RUNTIME" ]] && DEPLOY_PATH="/www/wwwroot/api.synaglobal.vip" + +echo "Remote runtime: ${RUNTIME} @ ${DEPLOY_PATH}" + +echo "▶ Building frontend (local verify)..." +cd frontend && npx vite build && cd .. +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 "✓ Package ready ($(du -h /tmp/nexus-frontend.tar.gz | cut -f1))" + + echo "▶ Uploading to server..." + scp "${SSH_OPTS[@]}" /tmp/nexus-frontend.tar.gz "${NEXUS_SSH_HOST}:/tmp/nexus-frontend.tar.gz" + 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" + 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 + fi + ssh_cmd "python3 ${DEPLOY_PATH}/deploy/prune_frontend_assets.py" + echo "✓ Extracted and pruned" + + echo "▶ Restarting backend..." + ssh_cmd "supervisorctl restart nexus" + echo "✓ Restarted" +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") +SPA=$(ssh_cmd "curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8600/app/" 2>/dev/null || echo "000") + +echo "" +echo "═══ Verification ═══" +echo " /health → $HEALTH" +echo " /app/ → $SPA" + +if [[ "$HEALTH" == "200" || "$HEALTH" == "ok" ]] && [[ "$SPA" == "200" ]]; then + echo "✓ Deploy successful!" +else + echo "✗ Verification failed — check server logs" + exit 1 +fi diff --git a/deploy/deploy-on-server.sh b/deploy/deploy-on-server.sh new file mode 100644 index 00000000..fcf476f5 --- /dev/null +++ b/deploy/deploy-on-server.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# Run ON production server — 自动识别 Docker / Supervisor +# +# Usage (on server): +# cd /opt/nexus && sudo bash deploy/deploy-on-server.sh +# cd /www/wwwroot/api.synaglobal.vip && bash deploy/deploy-on-server.sh +# +# Optional env: +# NEXUS_GITEA_TOKEN — if git pull needs auth +# SKIP_FRONTEND=1 — Supervisor 模式跳过热更新前端 tar +# NEXUS_DEPLOY_PATH — 覆盖自动检测路径 + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=./detect_deploy_runtime.sh +source "${SCRIPT_DIR}/detect_deploy_runtime.sh" + +DEPLOY_PATH="$(resolve_nexus_root "${NEXUS_DEPLOY_PATH:-}" 2>/dev/null || true)" +if [[ -z "$DEPLOY_PATH" ]]; then + echo "ERROR: cannot resolve Nexus root" >&2 + exit 1 +fi +cd "${DEPLOY_PATH}" + +RUNTIME="$(detect_nexus_runtime "$DEPLOY_PATH")" +PORT="$(nexus_publish_port_for "$DEPLOY_PATH")" + +echo "═══ Nexus server-side deploy ═══" +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" +fi + +if [[ "$RUNTIME" == "docker" ]]; then + echo "▶ Docker upgrade (pull + rebuild)..." + exec env NEXUS_ROOT="$DEPLOY_PATH" bash "$DEPLOY_PATH/deploy/nexus-1panel.sh" upgrade +fi + +echo "▶ git fetch + reset --hard origin/main..." +git fetch --all +git reset --hard origin/main +echo " HEAD: $(git log -1 --oneline)" + +echo "▶ supervisorctl restart nexus..." +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 + 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 + fi + supervisorctl restart nexus +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") +echo " /health → ${HEALTH}" +echo " /app/ → ${SPA}" + +if [[ "${HEALTH}" == "ok" && "${SPA}" == "200" ]]; then + echo "✓ Server deploy OK" + suggest_ops_cron "$DEPLOY_PATH" +else + echo "✗ Verify failed — check: supervisorctl status nexus" >&2 + exit 1 +fi diff --git a/deploy/deploy-production.sh b/deploy/deploy-production.sh new file mode 100644 index 00000000..791d3f30 --- /dev/null +++ b/deploy/deploy-production.sh @@ -0,0 +1,199 @@ +#!/usr/bin/env bash +# Full production deploy: local rsync + backend upgrade + frontend + health +# +# 默认从本机工作区 rsync 到生产机,不 push Gitea、不远程 git pull。 +# +# Prerequisites on YOUR machine: +# - 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 + SSH_OPTS+=(-i "${NEXUS_SSH_KEY}") +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 + echo " Configure ~/.ssh/config (Host nexus) or set NEXUS_SSH / NEXUS_SSH_KEY" >&2 + exit 1 +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" ]] && 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_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" && 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 + fi + echo RUNTIME=unknown + exit 0 +done +echo RUNTIME=none +REMOTE +) + +DEPLOY_PATH="${NEXUS_DEPLOY_PATH:-$(echo "$REMOTE_DETECT" | sed -n 's/^ROOT=//p' | head -1)}" +RUNTIME="$(echo "$REMOTE_DETECT" | sed -n 's/^RUNTIME=//p' | head -1)" + +if [[ -z "$DEPLOY_PATH" ]]; then + DEPLOY_PATH="${NEXUS_DEPLOY_PATH:-/opt/nexus}" +fi +if [[ -z "$RUNTIME" || "$RUNTIME" == "none" ]]; then + RUNTIME="unknown" +fi + +echo "Deploy path: ${DEPLOY_PATH}" +echo "Runtime: ${RUNTIME}" + +resolve_deploy_chown() { + if [[ -n "${NEXUS_DEPLOY_CHOWN:-}" ]]; then + echo "${NEXUS_DEPLOY_CHOWN}" + return + fi + if ssh_cmd "getent passwd www >/dev/null 2>&1"; then + echo "www:www" + return + fi + ssh_cmd "stat -c '%U:%G' '${DEPLOY_PATH}'" 2>/dev/null || echo "azureuser:azureuser" +} +DEPLOY_CHOWN="$(resolve_deploy_chown)" +echo "File owner: ${DEPLOY_CHOWN}" + +if [[ "$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 "▶ 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..." + cd frontend && npx vite build && cd .. + tar czf /tmp/nexus-frontend.tar.gz -C web/app index.html assets/ + scp_cmd /tmp/nexus-frontend.tar.gz "${NEXUS_SSH_HOST}:/tmp/nexus-frontend.tar.gz" + ssh_cmd "cd ${DEPLOY_PATH}/web/app && sudo tar xzf /tmp/nexus-frontend.tar.gz && rm /tmp/nexus-frontend.tar.gz && sudo chown -R ${DEPLOY_CHOWN} ${DEPLOY_PATH}/web/app" + + echo "▶ Frontend V2: local vite build:safe + upload to host web/app-v2..." + cd frontend-v2 && npm run build:safe && cd .. + tar czf /tmp/nexus-frontend-v2.tar.gz -C web/app-v2 index.html assets/ + scp_cmd /tmp/nexus-frontend-v2.tar.gz "${NEXUS_SSH_HOST}:/tmp/nexus-frontend-v2.tar.gz" + ssh_cmd "sudo mkdir -p ${DEPLOY_PATH}/web/app-v2 && cd ${DEPLOY_PATH}/web/app-v2 && sudo tar xzf /tmp/nexus-frontend-v2.tar.gz && rm /tmp/nexus-frontend-v2.tar.gz && sudo chown -R ${DEPLOY_CHOWN} ${DEPLOY_PATH}/web/app-v2" + + if ssh_cmd "test -f ${DEPLOY_PATH}/deploy/prune_frontend_assets.py"; then + ssh_cmd "sudo python3 ${DEPLOY_PATH}/deploy/prune_frontend_assets.py ${DEPLOY_PATH}/web/app --dry-run" + ssh_cmd "sudo python3 ${DEPLOY_PATH}/deploy/prune_frontend_assets.py ${DEPLOY_PATH}/web/app" + fi +fi + +if [[ "$RUNTIME" == "docker" ]]; then + echo "▶ Sync host web/app and web/app-v2 into container..." + ssh_cmd "sudo env NEXUS_ROOT=${DEPLOY_PATH} NEXUS_PUBLISH_PORT=\$(grep -E '^NEXUS_PUBLISH_PORT=' ${DEPLOY_PATH}/docker/.env.prod 2>/dev/null | cut -d= -f2 | tr -d '\"' || echo 8600) bash ${DEPLOY_PATH}/deploy/sync_webapp_to_container.sh" || { + echo "WARN: web/app sync failed — run sync_webapp_to_container.sh on server manually" >&2 + } +fi + +sleep 3 +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}" +echo " /app-v2/ → ${SPA_V2}" +if [[ "${HEALTH}" == "ok" && "${SPA}" == "200" && "${SPA_V2}" == "200" ]]; then + echo "✓ Deploy successful" + if ssh_cmd "command -v crontab >/dev/null && crontab -l 2>/dev/null | grep -q nexus-health-monitor" 2>/dev/null; then + echo "✓ Ops cron (health_monitor) already installed" + 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 +fi diff --git a/deploy/detect-1panel-services.sh b/deploy/detect-1panel-services.sh new file mode 100644 index 00000000..becac4bc --- /dev/null +++ b/deploy/detect-1panel-services.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Detect 1Panel App Store MySQL/Redis container names on 1panel-network. +# See: https://github.com/1Panel-dev/1Panel/issues/11676 +# https://github.com/1Panel-dev/appstore (mysql/redis → networks: 1panel-network) + +set -euo pipefail + +network_container_names() { + docker network inspect 1panel-network --format '{{range .Containers}}{{.Name}} {{end}}' 2>/dev/null \ + | tr ' ' '\n' | sed '/^$/d' +} + +pick_service_container() { + local kind="$1" + local names line + names="$(network_container_names)" + if [[ -z "$names" ]]; then + docker ps --format '{{.Names}}' 2>/dev/null | grep -iE "(^1Panel-${kind}-|${kind})" | head -1 || true + return + fi + line="$(echo "$names" | grep -iE "^1Panel-${kind}-" | head -1 || true)" + if [[ -n "$line" ]]; then + echo "$line" + return + fi + echo "$names" | grep -i "$kind" | head -1 || true +} + +if ! docker network inspect 1panel-network >/dev/null 2>&1; then + echo "1panel-network not found" >&2 + exit 1 +fi + +MYSQL_HOST="$(pick_service_container mysql)" +REDIS_HOST="$(pick_service_container redis)" + +if [[ -z "$MYSQL_HOST" && -z "$REDIS_HOST" ]]; then + echo "No MySQL/Redis containers on 1panel-network" >&2 + exit 2 +fi + +[[ -n "$MYSQL_HOST" ]] && echo "NEXUS_1PANEL_DB_HOST=${MYSQL_HOST}" +[[ -n "$REDIS_HOST" ]] && echo "NEXUS_1PANEL_REDIS_HOST=${REDIS_HOST}" diff --git a/deploy/detect_deploy_runtime.sh b/deploy/detect_deploy_runtime.sh new file mode 100644 index 00000000..d4ef9456 --- /dev/null +++ b/deploy/detect_deploy_runtime.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env bash +# Nexus — 部署运行时检测(Docker 1Panel / Supervisor 裸机) +# +# source deploy/detect_deploy_runtime.sh +# ROOT="$(resolve_nexus_root)" +# RUNTIME="$(detect_nexus_runtime "$ROOT")" # docker | supervisor | unknown +set -euo pipefail + +_COMPOSE_FILE="docker/docker-compose.prod.yml" +_COMPOSE_1PANEL="docker/docker-compose.1panel.yml" +_ENV_PROD="docker/.env.prod" + +resolve_nexus_root() { + local hint="${1:-}" + if [[ -n "$hint" && -d "$hint/server" ]]; then + echo "$hint" + return 0 + fi + if [[ -n "${NEXUS_ROOT:-}" && -d "${NEXUS_ROOT}/server" ]]; then + echo "$NEXUS_ROOT" + return 0 + fi + if [[ -n "${NEXUS_DEPLOY_PATH:-}" && -d "${NEXUS_DEPLOY_PATH}/server" ]]; then + echo "$NEXUS_DEPLOY_PATH" + return 0 + fi + local d + for d in /opt/nexus /www/wwwroot/api.synaglobal.vip; do + if [[ -d "$d/server" ]]; then + echo "$d" + return 0 + fi + done + for d in /www/wwwroot/*/; do + [[ -d "${d}server" ]] || continue + echo "${d%/}" + return 0 + done + if command -v supervisorctl >/dev/null 2>&1; then + local cwd + cwd="$(supervisorctl status nexus 2>/dev/null | grep -oP 'cwd=\K[^ ,]+' || true)" + if [[ -n "$cwd" && -d "$cwd/server" ]]; then + echo "$cwd" + return 0 + fi + fi + return 1 +} + +_has_1panel_network() { + docker network inspect 1panel-network >/dev/null 2>&1 +} + +detect_nexus_runtime() { + local root="$1" + if [[ -f "$root/$_ENV_PROD" ]] && command -v docker >/dev/null 2>&1 \ + && docker compose version >/dev/null 2>&1; then + local -a args=(-f "$root/$_COMPOSE_FILE") + if _has_1panel_network && [[ -f "$root/$_COMPOSE_1PANEL" ]]; then + args+=(-f "$root/$_COMPOSE_1PANEL") + fi + local prof q + prof="$root/docker/.host-profile" + [[ -f "$prof" ]] || prof="$root/docker/profiles/2c8g.env" + if [[ -f "$prof" ]]; then + q="$(cd "$root" && docker compose "${args[@]}" \ + --env-file "$root/$_ENV_PROD" --env-file "$prof" \ + ps -q nexus 2>/dev/null || true)" + if [[ -n "$q" ]]; then + echo docker + return 0 + fi + fi + fi + if command -v supervisorctl >/dev/null 2>&1 \ + && supervisorctl status nexus >/dev/null 2>&1; then + echo supervisor + return 0 + fi + echo unknown +} + +nexus_publish_port_for() { + local root="$1" + local port + port="$(grep -E '^NEXUS_PUBLISH_PORT=' "$root/$_ENV_PROD" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '\r" ' || true)" + if [[ -z "$port" && -f "$root/.env" ]]; then + port="$(grep -E '^NEXUS_PORT=' "$root/.env" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '\r" ' || true)" + fi + echo "${port:-8600}" +} + +nexus_restart_service() { + local root="$1" + local runtime + runtime="$(detect_nexus_runtime "$root")" + case "$runtime" in + docker) + local cname + cname="$(docker ps --format '{{.Names}}' 2>/dev/null | grep -E 'nexus-prod-nexus|nexus-nexus-1' | head -1 || true)" + if [[ -n "$cname" ]]; then + docker restart "$cname" + return 0 + fi + return 1 + ;; + supervisor) + supervisorctl restart nexus + ;; + *) + return 1 + ;; + esac +} + +nexus_has_ops_cron() { + command -v crontab >/dev/null 2>&1 \ + && crontab -l 2>/dev/null | grep -q 'nexus-health-monitor' +} + +suggest_ops_cron() { + local root="$1" + if nexus_has_ops_cron; then + return 0 + fi + echo "" + echo "[WARN] 未检测到 health_monitor / db_backup 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" +} diff --git a/deploy/download-install-fresh.sh b/deploy/download-install-fresh.sh new file mode 100644 index 00000000..67d2bdd4 --- /dev/null +++ b/deploy/download-install-fresh.sh @@ -0,0 +1,53 @@ +#!/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 "http://66.154.115.8:3000/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_GIT_BRANCH="${NEXUS_GIT_BRANCH:-main}" +NEXUS_RAW_BASE="${NEXUS_RAW_BASE:-http://${NEXUS_GITEA_HOST}/admin/Nexus/raw/branch/${NEXUS_GIT_BRANCH}}" +OUT="${OUT:-/tmp/install-nexus-fresh.sh}" + +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' + +err() { echo -e "${RED}[ERROR]${NC} $*" >&2; } +ok() { echo -e "${GREEN}[OK]${NC} $*"; } + +for cf in /root/.nexus-deploy.env /root/.nexus-secrets/nexus.env; do + [[ -f "$cf" ]] && set -a && source "$cf" && set +a +done + +download() { + local url base + for base in "${NEXUS_RAW_BASE}" "http://${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" + chmod +x "$OUT" + exec bash "$OUT" "$@" + 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}" + if curl -fsSL -H "Authorization: token ${NEXUS_GITEA_TOKEN}" "$url" -o "$OUT" 2>/dev/null \ + && head -1 "$OUT" | grep -q '^#!/'; then + ok "已下载(API + 令牌)" + chmod +x "$OUT" + exec bash "$OUT" "$@" + fi + fi + return 1 +} + +if ! download "$@"; then + err "下载失败,请改用公共仓库一键入口:" + err " curl -fsSL \"${NEXUS_RAW_BASE}/deploy/quick-install.sh\" | bash" + exit 1 +fi diff --git a/deploy/fix-1panel-mysql-grant.sh b/deploy/fix-1panel-mysql-grant.sh new file mode 100644 index 00000000..bad94612 --- /dev/null +++ b/deploy/fix-1panel-mysql-grant.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# Grant nexus@'%' on 1Panel App Store MySQL for Docker (1panel-network) install wizard. +# Fixes: Access denied for user 'nexus'@'172.18.x.x' (1045) +# +# Usage: +# MYSQL_ROOT_PASSWORD='root密码' NEXUS_DB_PASSWORD='nexus密码' bash deploy/fix-1panel-mysql-grant.sh +# bash deploy/fix-1panel-mysql-grant.sh # prompts +set -euo pipefail + +NEXUS_DB_NAME="${NEXUS_DB_NAME:-nexus}" +NEXUS_DB_USER="${NEXUS_DB_USER:-nexus}" + +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; exit 1; } + +pick_mysql_container() { + local name + if [[ -n "${MYSQL_CONTAINER:-}" ]]; then + echo "$MYSQL_CONTAINER" + return 0 + fi + if docker network inspect 1panel-network >/dev/null 2>&1; then + name="$(docker network inspect 1panel-network --format '{{range .Containers}}{{.Name}} {{end}}' \ + | tr ' ' '\n' | grep -iE '^1Panel-mysql-' | head -1 || true)" + [[ -n "$name" ]] && echo "$name" && return 0 + fi + name="$(docker ps --format '{{.Names}}' | grep -iE '^1Panel-mysql-' | head -1 || true)" + [[ -n "$name" ]] && echo "$name" && return 0 + error "未找到 1Panel MySQL 容器(可设 MYSQL_CONTAINER=名称)" +} + +prompt_secret() { + local var="$1" prompt="$2" + if [[ -n "${!var:-}" ]]; then + return 0 + fi + read -r -s -p "$prompt: " "$var" + echo "" + if [[ -z "${!var:-}" ]]; then + error "$var 不能为空" + fi +} + +main() { + [[ "${EUID:-$(id -u)}" -eq 0 ]] || error "请用 root 运行" + + local mysql_c + mysql_c="$(pick_mysql_container)" + info "MySQL 容器: $mysql_c" + + prompt_secret MYSQL_ROOT_PASSWORD "1Panel MySQL root 密码" + prompt_secret NEXUS_DB_PASSWORD "nexus 用户密码(与安装向导步骤 3 一致)" + + info "创建库/用户并授权 ${NEXUS_DB_USER}@'%' → ${NEXUS_DB_NAME}.* ..." + + docker exec -i "$mysql_c" mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" <= 3: + log_gate(sys.argv[1], sys.argv[2], sys.argv[3] if len(sys.argv) > 3 else "") + else: + # Print recent log + if os.path.exists(GATE_LOG): + with open(GATE_LOG, encoding="utf-8") as f: + lines = f.readlines()[-20:] + for line in lines: + d = json.loads(line) + icon = {"PASS": "✅", "BLOCK": "🚫", "SKIP": "⏭️", "WARN": "⚠️"}.get(d["result"], "?") + print(f"{d['ts'][:19]} {icon} {d['gate']}: {d.get('detail','')}") + else: + print("No gate log found") diff --git a/deploy/health_monitor.sh b/deploy/health_monitor.sh new file mode 100644 index 00000000..d1a8569c --- /dev/null +++ b/deploy/health_monitor.sh @@ -0,0 +1,331 @@ +#!/usr/bin/env bash +# Nexus — External Health Monitor (Layer 3 of 3-tier guardian) +# Runs via crontab every minute. Works on Docker (1Panel) and Supervisor bare-metal. +# +# Layer 1: Docker restart policy / Supervisor autorestart +# Layer 2: Python self_monitor (30s) +# Layer 3: This script — HTTP /health, 3 failures → restart + optional Telegram +set -euo pipefail + +DEPLOY_DIR="${NEXUS_DEPLOY_DIR:-/opt/nexus}" +NEXUS_SERVICE="${NEXUS_SERVICE:-nexus}" +STATE_DIR="" +FAIL_FILE="" +LAST_RESTART_FILE="" +# Legacy paths (cron user may have created root-owned files under /tmp) +LEGACY_FAIL_FILE="/tmp/nexus_health_fail_count" +LEGACY_LAST_RESTART_FILE="/tmp/nexus_health_last_restart" +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)" + echo "${port:-8600}" +} + +PUBLISH_PORT="$(resolve_publish_port)" +HEALTH_URL="http://127.0.0.1:${PUBLISH_PORT}/health" + +resolve_nexus_container() { + docker_cmd ps --format '{{.Names}}' 2>/dev/null | grep -E 'nexus-prod-nexus|nexus-nexus-1' | head -1 || true +} + +read_env_var() { + local key="$1" file="$2" + 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="" + CHAT_ID="" + for dotenv in \ + "${DEPLOY_DIR}/.env" \ + "$ENV_PROD" \ + ; do + [[ -f "$dotenv" ]] || continue + BOT_TOKEN="$(read_env_var NEXUS_TELEGRAM_BOT_TOKEN "$dotenv")" + CHAT_ID="$(read_env_var NEXUS_TELEGRAM_CHAT_ID "$dotenv")" + [[ -n "$BOT_TOKEN" && -n "$CHAT_ID" ]] && return 0 + done + if [[ -n "$cname" ]]; then + local line + while IFS= read -r line; do + case "$line" in + NEXUS_TELEGRAM_BOT_TOKEN=*) BOT_TOKEN="${line#NEXUS_TELEGRAM_BOT_TOKEN=}" ;; + NEXUS_TELEGRAM_CHAT_ID=*) CHAT_ID="${line#NEXUS_TELEGRAM_CHAT_ID=}" ;; + esac + done < <(docker_cmd exec "$cname" grep -E '^NEXUS_TELEGRAM_' /app/.env 2>/dev/null || true) + [[ -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() { + 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}" \ + --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)) + 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)) + write_state_file "$FAIL_FILE" "$fail" || true + fi + echo "$fail" +} + +reset_fail_count() { + write_state_file "$FAIL_FILE" "0" || true +} + +restart_cooldown_active() { + local now last elapsed + [[ -f "$LAST_RESTART_FILE" ]] || return 1 + now="$(date +%s)" + last="$(cat "$LAST_RESTART_FILE" 2>/dev/null || echo 0)" + elapsed=$((now - last)) + [[ "$elapsed" -lt "$RESTART_COOLDOWN_SEC" ]] +} + +mark_restart_attempt() { + write_state_file "$LAST_RESTART_FILE" "$(date +%s)" || true +} + +restart_nexus() { + local cname="$1" via="" + if [[ -n "$cname" ]]; then + 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 + return 1 + fi + echo "$via" +} + +telegram_health_fail_cooldown() { + local body + body="$(cat <【Layer 3 宿主机巡检】健康检查连续失败(冷却期内) +宿主机:${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 <【Layer 3 宿主机巡检】Nexus 连续 ${MAX_FAIL} 次无响应 +宿主机:${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 <【Layer 3 宿主机巡检】Nexus 已自动恢复 +宿主机:${HOST_LABEL} +检测地址:${HEALTH_URL} +恢复方式:${via} +具体情况:自动重启后 /health 检测通过,服务已恢复在线 +时间:${TS_LABEL} +EOF +)" + send_telegram "$body" || true +} + +telegram_health_still_down() { + local via="$1" body + body="$(cat <【Layer 3 宿主机巡检】自动重启后仍未恢复 +宿主机:${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 <【Layer 3 宿主机巡检】无法执行自动重启 +宿主机:${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 + exit 0 +fi + +FAIL="$(bump_fail_count)" +if [[ "$FAIL" -lt "$MAX_FAIL" ]]; then + exit 0 +fi + +if restart_cooldown_active; then + telegram_health_fail_cooldown + exit 0 +fi + +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 + telegram_health_recovered "$VIA" + reset_fail_count + else + telegram_health_still_down "$VIA" + fi +else + telegram_health_restart_failed +fi diff --git a/deploy/install-1panel-docker.sh b/deploy/install-1panel-docker.sh new file mode 100644 index 00000000..95f63652 --- /dev/null +++ b/deploy/install-1panel-docker.sh @@ -0,0 +1,275 @@ +#!/usr/bin/env bash +# ============================================================================= +# Nexus 6.0 — 重装服务器一键入口(1Panel + Docker + Nexus) +# +# 适用:全新 VPS / 重装系统后,root 一条命令装完整栈。 +# +# curl -fsSL "http://66.154.115.8:3000/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 +# +# 已装 1Panel,只装 Nexus Docker: +# curl -fsSL ".../install-1panel-docker.sh" | bash -s -- --skip-1panel +# +# 流程: +# ① 可选:官方 1Panel 安装(含 Docker,交互式) +# ② 检测 / 安装 Docker Compose +# ③ install-nexus-fresh.sh → MySQL + Nexus 容器(Redis 自行安装) +# +# 反代:必须在 1Panel 面板 UI 配置,禁止脚本直接改 /opt/1panel/apps/ +# 示例: deploy/1panel/openresty-nexus.conf.example +# ============================================================================= + +set -euo pipefail + +NEXUS_GITEA_HOST="${NEXUS_GITEA_HOST:-66.154.115.8:3000}" +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_ROOT="${NEXUS_ROOT:-/opt/nexus}" +NEXUS_DOMAIN="${NEXUS_DOMAIN:-api.synaglobal.vip}" +NEXUS_PROFILE="${NEXUS_PROFILE:-2c8g}" + +PANEL_INSTALL_URL="${PANEL_INSTALL_URL:-https://resource.1panel.pro/v2/quick_start.sh}" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +BLUE='\033[0;34m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } +step() { echo -e "${CYAN}[STEP]${NC} $*"; } + +WITH_1PANEL=auto +SKIP_1PANEL=false +SKIP_DOCKER_INSTALL=false +EXTRA_ARGS=() + +usage() { + cat </dev/null 2>&1 +} + +has_docker_compose() { + docker compose version >/dev/null 2>&1 +} + +phase_1panel() { + if [[ "$SKIP_1PANEL" == true ]]; then + info "跳过 1Panel 安装(--skip-1panel)" + return 0 + fi + + if has_1panel; then + info "已安装 1Panel" + if 1pctl user-info >/dev/null 2>&1; then + 1pctl user-info 2>/dev/null | grep -E '面板地址|面板用户' || true + else + warn "1pctl user-info 失败,面板可能需修复;继续安装 Nexus…" + fi + return 0 + fi + + if [[ "$WITH_1PANEL" == auto ]]; then + echo "" + warn "未检测到 1Panel(1pctl 不存在)" + echo " ① 先装 1Panel(推荐,含 Docker)再装 Nexus" + echo " ② 跳过 1Panel,仅装 Nexus Docker(需本机已有 Docker)" + echo "" + read -r -p "是否现在安装 1Panel? [Y/n]: " ans + case "${ans:-Y}" in + n|N) WITH_1PANEL=false ;; + *) WITH_1PANEL=true ;; + esac + fi + + if [[ "$WITH_1PANEL" != true ]]; then + warn "未安装 1Panel,将仅安装 Nexus Docker 栈" + return 0 + fi + + step "安装 1Panel(官方脚本,交互式)..." + info "下载: $PANEL_INSTALL_URL" + local tmp + tmp="$(mktemp /tmp/1panel-quick-start.XXXXXX.sh)" + if ! curl -fsSL "$PANEL_INSTALL_URL" -o "$tmp"; then + error "下载 1Panel 安装脚本失败" + exit 1 + fi + chmod +x "$tmp" + echo "" + warn "接下来为 1Panel 官方安装向导,请按提示设置端口、安全入口、用户名、密码" + warn "安装目录建议默认 /opt" + echo "" + bash "$tmp" + rm -f "$tmp" + + if ! has_1panel; then + error "1Panel 安装后仍未找到 1pctl,请检查安装日志" + exit 1 + fi + info "1Panel 安装完成" + 1pctl user-info 2>/dev/null || true + echo "" + warn "请在 1Panel → 应用商店 安装 OpenResty(用于 HTTPS 反代)" + read -r -p "OpenResty 已装好或稍后自行安装,按 Enter 继续 Nexus 安装..." +} + +install_docker_engine() { + step "安装 Docker Engine + Compose 插件..." + if command -v apt-get >/dev/null 2>&1; then + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get install -y -qq ca-certificates curl >/dev/null + curl -fsSL https://get.docker.com | sh + systemctl enable --now docker 2>/dev/null || true + elif command -v dnf >/dev/null 2>&1; then + dnf install -y -q docker docker-compose-plugin >/dev/null 2>&1 || \ + curl -fsSL https://get.docker.com | sh + systemctl enable --now docker 2>/dev/null || true + else + curl -fsSL https://get.docker.com | sh + fi +} + +phase_docker() { + step "检测 Docker Compose..." + if has_docker_compose; then + info "Docker Compose: $(docker compose version --short 2>/dev/null || docker compose version | head -1)" + return 0 + fi + + if [[ "$SKIP_DOCKER_INSTALL" == true ]]; then + error "未检测到 docker compose,且已指定 --skip-docker-install" + error "请在 1Panel → 容器 安装 Docker,或去掉该参数" + exit 1 + fi + + warn "未检测到 docker compose" + if has_1panel; then + warn "建议在 1Panel → 容器 安装 Docker 后重新运行本脚本" + read -r -p "是否尝试用 get.docker.com 自动安装? [y/N]: " ans + [[ "${ans:-N}" == y || "${ans:-N}" == Y ]] || exit 1 + else + info "自动安装 Docker(get.docker.com)..." + fi + + install_docker_engine + + if ! has_docker_compose; then + error "Docker 安装后仍无 compose 插件,请手动安装 docker-compose-plugin" + exit 1 + fi + info "Docker Compose 就绪" +} + +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 + url="${base}/deploy/${name}" + if curl -fsSL "$url" -o "$dest" 2>/dev/null && head -1 "$dest" | grep -q '^#!/'; then + chmod +x "$dest" + echo "$dest" + return 0 + fi + done + return 1 +} + +phase_nexus() { + step "安装 Nexus Docker 栈(MySQL + Nexus;Redis 请自行安装)..." + local installer + if [[ -x "${NEXUS_ROOT}/deploy/install-nexus-fresh.sh" ]]; then + installer="${NEXUS_ROOT}/deploy/install-nexus-fresh.sh" + info "使用本地: $installer" + exec bash "$installer" --skip-1panel-check "${EXTRA_ARGS[@]}" + fi + + if installer="$(download_fresh_installer)"; then + info "已下载 install-nexus-fresh.sh" + exec bash "$installer" --skip-1panel-check "${EXTRA_ARGS[@]}" + fi + + error "无法获取 install-nexus-fresh.sh,请检查 Gitea 可达性" + exit 1 +} + +main() { + banner + require_root + load_deploy_env + phase_1panel + phase_docker + phase_nexus +} + +# curl | bash 时 stdin 脚本无 BASH_SOURCE[0],不可配合 set -u 做入口判断 +main "$@" diff --git a/deploy/install-nexus-fresh.sh b/deploy/install-nexus-fresh.sh new file mode 100644 index 00000000..e9e033b6 --- /dev/null +++ b/deploy/install-nexus-fresh.sh @@ -0,0 +1,309 @@ +#!/usr/bin/env bash +# ============================================================================= +# Nexus 6.0 — 全新安装脚本(1Panel + Docker Compose) +# +# 适用:新机已装 1Panel / Docker,空库,走 /app/install.html 安装向导 +# 风格参考 1Panel quick_start.sh:分阶段检查 + 结束输出访问地址 +# +# 用法(公共仓库,root 下一条命令,参考 1Panel): +# +# curl -fsSL "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/quick-install.sh" | bash +# +# 已 clone 到 /opt/nexus: +# cd /opt/nexus && bash deploy/install-nexus-fresh.sh --skip-clone +# +# bash deploy/install-nexus-fresh.sh --profile 4c16g +# +# 公共仓库匿名 clone,无需 Gitea 令牌。NEXUS 密钥由安装脚本按机自动生成。 +# ============================================================================= + +set -euo pipefail + +_install_self="$(readlink -f "${BASH_SOURCE[0]}")" +if ! head -1 "$_install_self" | grep -q '^#!/'; then + echo "[ERROR] 脚本文件无效。若用 curl 下载,请改用: bash deploy/download-install-fresh.sh" >&2 + exit 1 +fi +if grep -qiE '^(Not found|404)' "$_install_self" 2>/dev/null; then + echo "[ERROR] 下载到的是 Gitea 404 页面,不是安装脚本。" >&2 + echo " 请: cd /opt/nexus && git pull && sudo bash deploy/install-nexus-fresh.sh --skip-clone" >&2 + exit 1 +fi + +# ── 默认配置(与 SECRETS.md / nexus-1panel 一致)── +NEXUS_ROOT="${NEXUS_ROOT:-/opt/nexus}" +NEXUS_GITEA_HOST="${NEXUS_GITEA_HOST:-66.154.115.8:3000}" +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}" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +BLUE='\033[0;34m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } +step() { echo -e "${CYAN}[STEP]${NC} $*"; } + +usage() { + cat </dev/null 2>&1; then + info "检测到 1Panel (1pctl)" + if 1pctl version >/dev/null 2>&1; then + info "1Panel: $(1pctl version 2>/dev/null | head -1 || true)" + fi + else + warn "未检测到 1pctl — 若尚未安装 1Panel,可先执行:" + warn " curl -sSL https://resource.1panel.pro/v2/quick_start.sh -o quick_start.sh" + warn " bash quick_start.sh" + read -r -p "继续安装 Nexus? [y/N]: " cont + [[ "$cont" == "y" || "$cont" == "Y" ]] || exit 0 + fi + fi + + if ! docker compose version >/dev/null 2>&1; then + error "未检测到 docker compose" + error "请在 1Panel → 容器 中安装 Docker,或: apt install -y docker.io docker-compose-v2" + exit 1 + fi + info "Docker Compose: $(docker compose version --short 2>/dev/null || docker compose version | head -1)" +} + +phase_packages() { + step "阶段 3/6 — 依赖包" + if command -v apt-get >/dev/null 2>&1; then + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get install -y -qq git curl openssl ca-certificates iproute2 >/dev/null 2>&1 || true + elif command -v dnf >/dev/null 2>&1; then + dnf install -y -q git curl openssl ca-certificates iproute >/dev/null 2>&1 || true + fi + info "git / curl 就绪" +} + +git_clone_url() { + if [[ -z "${NEXUS_GITEA_TOKEN:-}" ]]; then + echo "http://${NEXUS_GITEA_HOST}/${NEXUS_GITEA_REPO}" + else + echo "http://admin:${NEXUS_GITEA_TOKEN}@${NEXUS_GITEA_HOST}/${NEXUS_GITEA_REPO}" + fi +} + +phase_clone() { + step "阶段 4/6 — 获取 Nexus 代码" + if [[ "$SKIP_CLONE" == true ]]; then + if [[ ! -d "${NEXUS_ROOT}/.git" ]]; then + error "--skip-clone 但 ${NEXUS_ROOT} 不是 git 仓库" + exit 1 + fi + load_credentials + info "跳过 clone,使用现有 ${NEXUS_ROOT}" + info "版本: $(git -C "$NEXUS_ROOT" log -1 --oneline 2>/dev/null || echo '?')" + chmod +x "${NEXUS_ROOT}/deploy/"*.sh 2>/dev/null || true + return 0 + fi + load_credentials + local url + url="$(git_clone_url)" + if [[ -z "${NEXUS_GITEA_TOKEN:-}" ]]; then + info "公共仓库:匿名 git clone(无需令牌)" + fi + mkdir -p "$(dirname "$NEXUS_ROOT")" + + if [[ -d "${NEXUS_ROOT}/.git" ]]; then + info "仓库已存在,同步 ${NEXUS_GIT_BRANCH}..." + git -C "$NEXUS_ROOT" remote set-url origin "$url" 2>/dev/null || true + git -C "$NEXUS_ROOT" fetch origin "$NEXUS_GIT_BRANCH" --prune + git -C "$NEXUS_ROOT" reset --hard "origin/${NEXUS_GIT_BRANCH}" + else + if [[ -d "$NEXUS_ROOT" ]] && [[ -n "$(ls -A "$NEXUS_ROOT" 2>/dev/null)" ]]; then + error "目录非空且不是 git 仓库: $NEXUS_ROOT" + exit 1 + fi + rm -rf "$NEXUS_ROOT" + info "克隆 → $NEXUS_ROOT" + git clone "$url" "$NEXUS_ROOT" + git -C "$NEXUS_ROOT" checkout "$NEXUS_GIT_BRANCH" 2>/dev/null || true + fi + + load_credentials + info "版本: $(git -C "$NEXUS_ROOT" log -1 --oneline)" + chmod +x "${NEXUS_ROOT}/deploy/"*.sh 2>/dev/null || true +} + +phase_install_stack() { + step "阶段 5/6 — Docker 构建与启动(全新库,约 10~20 分钟)" + export NEXUS_ROOT NEXUS_DOMAIN NEXUS_PROFILE + bash "${NEXUS_ROOT}/deploy/nexus-1panel.sh" install \ + --fresh \ + --skip-clone \ + --profile "$NEXUS_PROFILE" \ + --domain "$NEXUS_DOMAIN" +} + +phase_post() { + step "阶段 6/6 — 收尾" + install_root_commands + + local port health + port="8600" + health="$(curl -sf "http://127.0.0.1:${port}/health" 2>/dev/null || echo "pending")" + + echo "" + echo -e "${GREEN}╔══════════════════════════════════════════════════════════════╗${NC}" + echo -e "${GREEN}║${NC} ${GREEN}Nexus 全新安装完成${NC} ${GREEN}║${NC}" + echo -e "${GREEN}╠══════════════════════════════════════════════════════════════╣${NC}" + echo -e "${GREEN}║${NC} 本地健康: http://127.0.0.1:${port}/health → ${health} ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} ${YELLOW}① 浏览器完成安装向导(必做)${NC} ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} https://${NEXUS_DOMAIN}/app/install.html ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} ${YELLOW}② 1Panel 创建网站并反代${NC} ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} 目标: http://127.0.0.1:${port} ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} 示例: ${NEXUS_ROOT}/deploy/1panel/openresty-nexus.conf.example ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} ${YELLOW}③ 日常运维${NC} ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} nx # 统一菜单(一键更新 / 重启 / 日志) ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} nexus-update # 等同 deploy/update.sh ${GREEN}║${NC}" + echo -e "${GREEN}╚══════════════════════════════════════════════════════════════╝${NC}" + echo "" +} + +warn_if_already_installed() { + local env_prod="${NEXUS_ROOT}/docker/.env.prod" cname + [[ -f "$env_prod" ]] || return 0 + cname="$(docker ps --format '{{.Names}}' 2>/dev/null | grep -E 'nexus-prod-nexus|nexus-nexus-1' | head -1 || true)" + if [[ -n "$cname" ]] && docker exec "$cname" test -f /var/lib/nexus/.install_locked 2>/dev/null; then + warn "检测到已安装并锁定的 Nexus(将清除 nexus-state 卷内配置并重建)" + read -r -p "确认继续全新安装? [y/N]: " ok + [[ "$ok" == "y" || "$ok" == "Y" ]] || exit 0 + fi +} + +main() { + banner + if [[ "$SKIP_CLONE" == true && -d "${NEXUS_ROOT}/deploy" ]]; then + install_root_commands + fi + warn_if_already_installed + phase_preflight + phase_1panel + phase_packages + phase_clone + phase_install_stack + phase_post +} + +main "$@" diff --git a/deploy/install-nx-cli.sh b/deploy/install-nx-cli.sh new file mode 100644 index 00000000..ba5c46d8 --- /dev/null +++ b/deploy/install-nx-cli.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# 注册 Nexus 全局 CLI(/usr/local/bin)— install / upgrade / nx 均会调用 +# +# NEXUS_ROOT=/opt/nexus bash deploy/install-nx-cli.sh +# +set -euo pipefail + +NEXUS_ROOT="${NEXUS_ROOT:-/opt/nexus}" +BIN_DIR="${NEXUS_BIN_DIR:-/usr/local/bin}" +DEPLOY="${NEXUS_ROOT}/deploy" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } + +if [[ ! -d "$DEPLOY" ]]; then + error "未找到 $DEPLOY" + exit 1 +fi + +if [[ "$(id -u)" -ne 0 ]]; then + warn "非 root:无法写入 $BIN_DIR,请使用 root 执行或: sudo bash $DEPLOY/install-nx-cli.sh" + exit 0 +fi + +chmod +x \ + "$DEPLOY/nx" \ + "$DEPLOY/update.sh" \ + "$DEPLOY/nexus-1panel.sh" \ + "$DEPLOY/install-nexus-fresh.sh" \ + "$DEPLOY/quick-install.sh" \ + "$DEPLOY/install-1panel-docker.sh" \ + "$DEPLOY/upgrade-1panel-docker.sh" \ + "$DEPLOY/sync-install-wizard-to-container.sh" \ + "$DEPLOY/uninstall-mysql-compose.sh" \ + "$DEPLOY/uninstall-redis-compose.sh" \ + "$DEPLOY/install-nx-cli.sh" \ + 2>/dev/null || true + +mkdir -p "$BIN_DIR" + +link_cli() { + local src="$1" name="$2" + if [[ ! -f "$src" ]]; then + warn "跳过 $name(缺少 $src)" + return 0 + fi + ln -sf "$src" "${BIN_DIR}/${name}" +} + +link_cli "$DEPLOY/nx" nx +link_cli "$DEPLOY/update.sh" nexus-update +link_cli "$DEPLOY/install-nexus-fresh.sh" nexus-fresh +link_cli "$DEPLOY/quick-install.sh" nexus-install +link_cli "$DEPLOY/nexus-1panel.sh" nexus-1panel +link_cli "$DEPLOY/install-1panel-docker.sh" nexus-1panel-docker + +if command -v nx >/dev/null 2>&1; then + info "全局命令已就绪: nx → $(readlink -f "$(command -v nx)" 2>/dev/null || command -v nx)" +else + error "nx 未出现在 PATH 中,请确认 $BIN_DIR 在 PATH 内" + exit 1 +fi diff --git a/deploy/install.sh b/deploy/install.sh new file mode 100644 index 00000000..1de3873f --- /dev/null +++ b/deploy/install.sh @@ -0,0 +1,526 @@ +#!/bin/bash +# ================================================================ +# Nexus 6.0 — One-Click Install Script +# +# Usage: +# curl -fsSL https://YOUR_DOMAIN/deploy/install.sh | sudo bash -s -- --domain nexus.example.com +# sudo bash install.sh --domain nexus.example.com +# +# Supports: Ubuntu 20.04+, Debian 11+, CentOS 7/8, Rocky 8/9, AlmaLinux 8/9 +# Auto-detects BT Panel (宝塔面板) and adapts paths accordingly. +# ================================================================ + +set -euo pipefail + +# ── Colors ── +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*"; } + +# ── Helper: Idempotent config write ── +write_config() { + local target="$1" content="$2" + if [ -f "$target" ]; then + local existing + existing=$(cat "$target" 2>/dev/null || true) + if [ "$existing" = "$content" ]; then + info " [skip] $target (unchanged)" + return 0 + fi + info " [update] $target (content changed)" + else + info " [create] $target" + fi + echo "$content" > "$target" +} + +# ── Defaults ── +DOMAIN="" +DEPLOY_DIR="" +PORT=8600 +SKIP_NGINX=false +SKIP_SUPERVISOR=false +BT_MODE=false +NEXUS_REPO="" + +# ── Parse Arguments ── +while [[ $# -gt 0 ]]; do + case $1 in + --domain) DOMAIN="$2"; shift 2 ;; + --deploy-dir) DEPLOY_DIR="$2"; shift 2 ;; + --port) PORT="$2"; shift 2 ;; + --skip-nginx) SKIP_NGINX=true; shift ;; + --skip-supervisor) SKIP_SUPERVISOR=true; shift ;; + --repo) NEXUS_REPO="$2"; shift 2 ;; + -h|--help) + echo "Usage: sudo bash install.sh --domain DOMAIN [options]" + echo "" + echo "Options:" + echo " --domain DOMAIN Required. Domain or IP for Nginx server_name" + echo " --deploy-dir PATH Installation directory (auto-detected)" + echo " --port PORT uvicorn port (default: 8600)" + echo " --skip-nginx Skip Nginx configuration" + echo " --skip-supervisor Skip Supervisor configuration" + echo " --repo URL Git repository URL (optional, for cloning)" + exit 0 ;; + *) shift ;; + esac +done + +# ── Banner ── +echo "" +echo "==========================================" +echo " Nexus 6.0 Install Script" +echo "==========================================" +echo "" + +# ================================================================ +# Phase 1: Pre-flight Checks +# ================================================================ + +info "Phase 1/7: Pre-flight checks..." + +# 1.1 Must run as root +if [ "$(id -u)" -ne 0 ]; then + error "This script must be run as root. Use: sudo bash install.sh" + exit 1 +fi + +# 1.2 Domain is required +if [ -z "$DOMAIN" ]; then + error "--domain is required. Usage: sudo bash install.sh --domain nexus.example.com" + exit 1 +fi + +# 1.3 OS detection +if [ ! -f /etc/os-release ]; then + error "Cannot detect OS. /etc/os-release not found." + exit 1 +fi + +OS_ID=$(grep '^ID=' /etc/os-release | head -1 | cut -d'=' -f2 | tr -d '"' | cut -d'-' -f1) +OS_VERSION=$(grep '^VERSION_ID=' /etc/os-release | head -1 | cut -d'=' -f2 | tr -d '"') + +case "$OS_ID" in + ubuntu|debian) PKG_MANAGER="apt" ;; + centos|rocky|almalinux|rhel) PKG_MANAGER="yum" ;; + *) + warn "Unsupported OS: $OS_ID. Script may still work but is untested." + PKG_MANAGER="apt" + ;; +esac +info " OS: $OS_ID $OS_VERSION (pkg: $PKG_MANAGER)" + +# 1.4 BT Panel detection +if [ -d "/www/server/panel" ] && [ -f "/www/server/panel/class/common.py" ]; then + BT_MODE=true + info " BT Panel: detected" +else + info " BT Panel: not detected (standard mode)" +fi + +# 1.5 Set deploy directory +if [ -z "$DEPLOY_DIR" ]; then + if [ "$BT_MODE" = true ]; then + DEPLOY_DIR="/www/wwwroot/$DOMAIN" + else + DEPLOY_DIR="/opt/nexus" + fi +fi +VENV_DIR="$DEPLOY_DIR/venv" + +# Set log paths early (used in multiple phases) +if [ "$BT_MODE" = true ]; then + LOG_DIR="/www/wwwlogs" +else + LOG_DIR="/var/log/nexus" +fi + +info " Deploy dir: $DEPLOY_DIR" +info " Domain: $DOMAIN" +info " Port: $PORT" + +# ================================================================ +# Phase 2: System Dependencies +# ================================================================ + +info "Phase 2/7: Installing system dependencies..." + +if [ "$PKG_MANAGER" = "apt" ]; then + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get install -y -qq \ + python3 python3-venv python3-pip python3-dev \ + build-essential libssl-dev libffi-dev default-libmysqlclient-dev \ + git curl >/dev/null 2>&1 || warn "Some apt packages failed to install (may already exist)" +elif [ "$PKG_MANAGER" = "yum" ]; then + if command -v dnf >/dev/null 2>&1; then + dnf install -y -q \ + python3 python3-devel python3-pip \ + gcc openssl-devel libffi-devel mysql-devel \ + git curl >/dev/null 2>&1 || warn "Some dnf packages failed to install" + else + yum install -y -q \ + python3 python3-devel python3-pip \ + gcc openssl-devel libffi-devel mysql-devel \ + git curl >/dev/null 2>&1 || warn "Some yum packages failed to install" + fi +fi + +# ================================================================ +# Phase 3: Python 3.10+ Resolution +# ================================================================ + +info "Phase 3/7: Resolving Python 3.10+..." + +PYTHON="" +CANDIDATES="python3.12 python3.11 python3.10 /usr/bin/python3.12 /usr/bin/python3.11 /usr/bin/python3.10 python3" + +# Add BT Panel Python paths +if [ "$BT_MODE" = true ]; then + for p in /www/server/python-manager/versions/*/bin/python3.*; do + [ -x "$p" ] && CANDIDATES="$CANDIDATES $p" + done +fi + +for candidate in $CANDIDATES; do + if command -v "$candidate" >/dev/null 2>&1 || [ -x "$candidate" ]; then + ver=$("$candidate" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null || true) + if [ -n "$ver" ]; then + major=$(echo "$ver" | cut -d'.' -f1) + minor=$(echo "$ver" | cut -d'.' -f2) + if [ "$major" -eq 3 ] && [ "${minor:-0}" -ge 10 ] 2>/dev/null; then + PYTHON="$candidate" + break + fi + fi + fi +done + +# Fallback: install Python 3.12 on Ubuntu via deadsnakes PPA +if [ -z "$PYTHON" ] && [ "$OS_ID" = "ubuntu" ]; then + warn " Python 3.10+ not found. Adding deadsnakes PPA..." + export DEBIAN_FRONTEND=noninteractive + apt-get install -y -qq software-properties-common >/dev/null 2>&1 + add-apt-repository -y ppa:deadsnakes/ppa >/dev/null 2>&1 + apt-get update -qq + apt-get install -y -qq python3.12 python3.12-venv python3.12-dev >/dev/null 2>&1 + if command -v python3.12 >/dev/null 2>&1; then + PYTHON="python3.12" + fi +fi + +if [ -z "$PYTHON" ]; then + error "Python 3.10+ is required but not found." + error "Install Python 3.10+ manually and re-run." + exit 1 +fi + +PYTHON_VERSION=$("$PYTHON" --version 2>&1) +info " Using: $PYTHON ($PYTHON_VERSION)" + +# ================================================================ +# Phase 4: Application Deployment +# ================================================================ + +info "Phase 4/7: Deploying application..." + +# 4.1 Create deploy directory +mkdir -p "$DEPLOY_DIR" + +# 4.2 Download / Clone Nexus +if [ -d "$DEPLOY_DIR/server" ] && [ -d "$DEPLOY_DIR/web" ]; then + info " Application files already exist in $DEPLOY_DIR (skipping download)" + if [ -d "$DEPLOY_DIR/.git" ]; then + info " Git repo detected, pulling latest..." + (cd "$DEPLOY_DIR" && git pull --ff-only 2>/dev/null || warn " git pull failed (may have local changes)") + fi +elif [ -n "$NEXUS_REPO" ]; then + info " Cloning from $NEXUS_REPO..." + git clone "$NEXUS_REPO" "$DEPLOY_DIR" 2>/dev/null || { + error "git clone failed. Check the repository URL." + exit 1 + } +else + error "No application files found in $DEPLOY_DIR and no --repo URL provided." + error "Either:" + error " 1. Copy Nexus files to $DEPLOY_DIR manually and re-run" + error " 2. Use --repo https://your-gitea-url/Nexus.git" + exit 1 +fi + +# 4.3 Create Python venv +if [ -d "$VENV_DIR/bin" ] && "$VENV_DIR/bin/python" -c 'import sys; v=sys.version_info; assert v.major==3 and v.minor>=10' 2>/dev/null; then + info " venv already exists with Python 3.10+ (skipping)" +else + info " Creating Python venv..." + rm -rf "$VENV_DIR" + "$PYTHON" -m venv "$VENV_DIR" +fi + +# 4.4 Install pip dependencies +info " Installing Python dependencies (this may take a few minutes)..." +"$VENV_DIR/bin/pip" install -q --upgrade pip +"$VENV_DIR/bin/pip" install -q -r "$DEPLOY_DIR/requirements.txt" 2>/dev/null || { + warn " pip install had some warnings, but continuing..." +} + +# 4.5 Create log directory +mkdir -p "$LOG_DIR" + +# 4.6 Create backup directory +mkdir -p "$DEPLOY_DIR/backups" + +# ================================================================ +# Phase 5: Supervisor Configuration +# ================================================================ + +if [ "$SKIP_SUPERVISOR" = true ]; then + warn "Phase 5/7: Skipping Supervisor (per --skip-supervisor)" +else + info "Phase 5/7: Configuring Supervisor..." + + # Calculate worker count + WORKERS=$(nproc 2>/dev/null || echo 2) + [ "$WORKERS" -gt 4 ] && WORKERS=4 + [ "$WORKERS" -lt 1 ] && WORKERS=1 + + # Set Supervisor config path + if [ "$BT_MODE" = true ]; then + SUPERVISOR_CONF_DIR="/www/server/panel/plugin/supervisor/profile" + CONF_FILE="$SUPERVISOR_CONF_DIR/nexus.ini" + else + SUPERVISOR_CONF_DIR="/etc/supervisor/conf.d" + CONF_FILE="$SUPERVISOR_CONF_DIR/nexus.conf" + fi + + # Generate Supervisor config + SUPERVISOR_CONF="[program:nexus] +command=$VENV_DIR/bin/uvicorn server.main:app --host 0.0.0.0 --port $PORT --workers $WORKERS --loop uvloop --http httptools --log-level info +directory=$DEPLOY_DIR +user=root +autostart=true +autorestart=true +startretries=10 +startsecs=3 +stopwaitsecs=10 +stopsignal=INT +environment=PATH=\"$VENV_DIR/bin:%(ENV_PATH)s\",HOME=\"/root\",NEXUS_WORKERS=\"$WORKERS\" +stderr_logfile=$LOG_DIR/nexus_error.log +stdout_logfile=$LOG_DIR/nexus_access.log +stderr_logfile_maxbytes=50MB +stdout_logfile_maxbytes=50MB +stderr_logfile_backups=5 +stdout_logfile_backups=5" + + # Write config (idempotent) + mkdir -p "$SUPERVISOR_CONF_DIR" + write_config "$CONF_FILE" "$SUPERVISOR_CONF" + + # Ensure Supervisor is running + if ! supervisorctl status >/dev/null 2>&1; then + if [ "$BT_MODE" = true ]; then + if [ -f /etc/init.d/supervisor ]; then + /etc/init.d/supervisor start >/dev/null 2>&1 || true + fi + else + systemctl start supervisor >/dev/null 2>&1 || true + fi + sleep 2 + fi + + # Reload Supervisor config + supervisorctl reread >/dev/null 2>&1 || true + supervisorctl update >/dev/null 2>&1 || true + info " Supervisor config written to $CONF_FILE (workers=$WORKERS)" +fi + +# ================================================================ +# Phase 6: Nginx Configuration +# ================================================================ + +if [ "$SKIP_NGINX" = true ]; then + warn "Phase 6/7: Skipping Nginx (per --skip-nginx)" +else + info "Phase 6/7: Configuring Nginx..." + + if [ "$BT_MODE" = true ]; then + NGINX_CONF_FILE="/www/server/panel/vhost/nginx/$DOMAIN.conf" + NGINX_RELOAD_CMD="/www/server/nginx/sbin/nginx -s reload" + NGINX_TEST_CMD="/www/server/nginx/sbin/nginx -t" + NGINX_ACCESS_LOG="/www/wwwlogs/$DOMAIN.log" + NGINX_ERROR_LOG="/www/wwwlogs/$DOMAIN.error.log" + else + NGINX_CONF_FILE="/etc/nginx/sites-available/nexus" + NGINX_RELOAD_CMD="systemctl reload nginx" + NGINX_TEST_CMD="nginx -t" + NGINX_ACCESS_LOG="/var/log/nginx/nexus_access.log" + NGINX_ERROR_LOG="/var/log/nginx/nexus_error.log" + fi + + NGINX_CONF="# Nexus 6.0 — Nginx Reverse Proxy +# Auto-generated by install.sh +upstream nexus_api { + server 127.0.0.1:$PORT; + keepalive 32; +} + +server { + listen 80; + server_name $DOMAIN; + + # Reverse proxy to Nexus (uvicorn) + location / { + proxy_pass http://nexus_api; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto \$scheme; + proxy_read_timeout 300s; + proxy_send_timeout 300s; + } + + # WebSocket support (terminal, alerts) + location /ws/ { + proxy_pass http://nexus_api; + proxy_http_version 1.1; + proxy_set_header Upgrade \$http_upgrade; + proxy_set_header Connection \"upgrade\"; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto \$scheme; + proxy_read_timeout 3600s; + } + + # Static assets caching + location /app/vendor/ { + proxy_pass http://nexus_api; + expires 7d; + add_header Cache-Control \"public, immutable\"; + } + + # Block sensitive files + location ~ ^/(\\.env|\\.git|\\.svn) { + return 404; + } + + # Block config.json access + location ~ /data/config\\.json$ { + return 404; + } + + # Upload limit + client_max_body_size 500m; + + # Well-known for SSL cert validation + location ~ \\.well-known { + allow all; + } + + access_log $NGINX_ACCESS_LOG; + error_log $NGINX_ERROR_LOG; +}" + + # Write Nginx config + if [ "$BT_MODE" = true ]; then + write_config "$NGINX_CONF_FILE" "$NGINX_CONF" + else + mkdir -p /etc/nginx/sites-available + write_config "$NGINX_CONF_FILE" "$NGINX_CONF" + if [ ! -L /etc/nginx/sites-enabled/nexus ]; then + ln -sf "$NGINX_CONF_FILE" /etc/nginx/sites-enabled/nexus + fi + fi + + # Test and reload Nginx + if $NGINX_TEST_CMD >/dev/null 2>&1; then + $NGINX_RELOAD_CMD >/dev/null 2>&1 || warn " Nginx reload failed" + info " Nginx config written to $NGINX_CONF_FILE" + else + warn " Nginx config test failed. Please check $NGINX_CONF_FILE manually" + fi +fi + +# ================================================================ +# Phase 7: Start Application + Verify +# ================================================================ + +info "Phase 7/7: Starting application..." + +# Start via Supervisor +if [ "$SKIP_SUPERVISOR" = false ]; then + supervisorctl start nexus >/dev/null 2>&1 || supervisorctl restart nexus >/dev/null 2>&1 || true +fi + +# Wait for health check +HEALTH_OK=false +for i in $(seq 1 15); do + if curl -sf "http://127.0.0.1:$PORT/health" >/dev/null 2>&1; then + HEALTH_OK=true + break + fi + sleep 2 +done + +# ── Print Results ── +echo "" +echo "==========================================" + +if [ "$HEALTH_OK" = true ]; then + echo -e " ${GREEN}Nexus installed successfully!${NC}" +else + echo -e " ${YELLOW}Nexus process started but health check pending.${NC}" + echo -e " ${YELLOW}It may need a few more seconds to start up.${NC}" +fi + +echo "==========================================" +echo "" +echo " Deploy: $DEPLOY_DIR" +echo " Domain: $DOMAIN" +echo " Port: $PORT" +echo " Workers: ${WORKERS:-N/A}" +echo " BT Panel: $BT_MODE" +echo "" + +if [ "$HEALTH_OK" = true ]; then + echo -e " ${GREEN}Next step:${NC} Open your browser and complete the web install wizard:" + echo "" + echo " http://$DOMAIN/app/install.html" + echo "" + echo " The wizard will guide you through:" + echo " Step 1: Welcome & prerequisites" + echo " Step 2: Environment check" + echo " Step 3: MySQL + Redis configuration" + echo " Step 4: Admin account + brand" + echo " Step 5: Complete" +else + echo " Troubleshooting:" + echo " supervisorctl status nexus" + echo " tail -f $LOG_DIR/nexus_error.log" + echo " curl http://127.0.0.1:$PORT/health" +fi + +echo "" +echo " Post-install:" +echo " 1. Configure SSL (HTTPS)" +if [ "$BT_MODE" = true ]; then + echo " BT Panel -> Website -> $DOMAIN -> SSL" +else + echo " certbot --nginx -d $DOMAIN" +fi +echo " 2. Set up health monitor crontab:" +echo " * * * * * $DEPLOY_DIR/deploy/health_monitor.sh" +echo " 3. Set up database backup crontab:" +echo " 0 3 * * * $DEPLOY_DIR/deploy/db_backup.sh" +echo "" +echo " Management:" +echo " supervisorctl status nexus" +echo " supervisorctl restart nexus" +echo " supervisorctl stop nexus" +echo "" diff --git a/deploy/install_ops_cron.sh b/deploy/install_ops_cron.sh new file mode 100644 index 00000000..5322761f --- /dev/null +++ b/deploy/install_ops_cron.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# Nexus — 安装/更新 host crontab:health_monitor + db_backup +# +# sudo bash deploy/install_ops_cron.sh +# NEXUS_ROOT=/opt/nexus sudo bash deploy/install_ops_cron.sh +set -euo pipefail + +NEXUS_ROOT="${NEXUS_ROOT:-/opt/nexus}" +DEPLOY_DIR="${NEXUS_ROOT}/deploy" +HEALTH_MARKER="nexus-health-monitor" +BACKUP_MARKER="nexus-mysql-backup" +HEALTH_LOG="${NEXUS_HEALTH_LOG:-/var/log/nexus_health.log}" +BACKUP_LOG="${NEXUS_BACKUP_LOG:-/var/log/nexus_backup.log}" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } + +if [[ "$(id -u)" -ne 0 ]]; then + error "请使用 root 运行: sudo bash $0" + exit 1 +fi + +ensure_crontab() { + if command -v crontab >/dev/null 2>&1; then + return 0 + fi + warn "未检测到 crontab,尝试安装 cron 包…" + if command -v apt-get >/dev/null 2>&1; then + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get install -y -qq cron + systemctl enable --now cron 2>/dev/null || systemctl enable --now crond 2>/dev/null || true + elif command -v yum >/dev/null 2>&1; then + yum install -y cronie + systemctl enable --now crond 2>/dev/null || true + fi + if ! command -v crontab >/dev/null 2>&1; then + error "无法安装 crontab(请手动: apt install cron)" + exit 1 + fi + info "cron 已安装" +} + +ensure_crontab + +for f in \ + "${DEPLOY_DIR}/health_monitor.sh" \ + "${DEPLOY_DIR}/db_backup.sh" \ + "${DEPLOY_DIR}/mysql_dump_to_file.sh" \ + ; do + if [[ ! -f "$f" ]]; then + error "缺少脚本: $f" + exit 1 + fi + chmod +x "$f" 2>/dev/null || true +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} 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)" +{ + printf '%s\n' "$filtered" | sed '/^[[:space:]]*$/d' + echo "$HEALTH_LINE" + echo "$BACKUP_LINE" +} | crontab - + +info "已写入 crontab:" +crontab -l | grep -E "$HEALTH_MARKER|$BACKUP_MARKER" || true +info "健康巡检日志: $HEALTH_LOG" +info "MySQL 备份日志: $BACKUP_LOG" diff --git a/deploy/migrations/001_indexes.sql b/deploy/migrations/001_indexes.sql new file mode 100644 index 00000000..1b3bf9a3 --- /dev/null +++ b/deploy/migrations/001_indexes.sql @@ -0,0 +1,24 @@ +-- Nexus 6.0 — Database Index Migration +-- Run this manually if create_all() didn't create the indexes. +-- Safe to run even if indexes already exist (will skip with "Duplicate key name" error). + +-- Server list queries filter by is_online frequently (Dashboard, API list) +CREATE INDEX IF NOT EXISTS idx_servers_is_online ON servers (is_online); + +-- Server list queries filter by category (sidebar tabs) +CREATE INDEX IF NOT EXISTS idx_servers_category ON servers (category); + +-- Sync log queries filter by server_id + date range (logs page, server detail) +CREATE INDEX IF NOT EXISTS idx_sync_logs_srv_start ON sync_logs (server_id, started_at); + +-- Login attempt queries filter by username + time (brute-force detection) +CREATE INDEX IF NOT EXISTS idx_login_attempts_username_time ON login_attempts (username, attempted_at); + +-- Audit log queries filter by action + time (audit page) +CREATE INDEX IF NOT EXISTS idx_audit_logs_action_time ON audit_logs (action, created_at); + +-- Push schedule queries filter by enabled (cron scheduler) +CREATE INDEX IF NOT EXISTS idx_push_schedules_enabled ON push_schedules (enabled); + +-- Push retry queries filter by status + next_retry_at (retry engine) +CREATE INDEX IF NOT EXISTS idx_push_retry_pending ON push_retry_jobs (status, next_retry_at); \ No newline at end of file diff --git a/deploy/mysql_dump_to_file.sh b/deploy/mysql_dump_to_file.sh new file mode 100644 index 00000000..e91d078e --- /dev/null +++ b/deploy/mysql_dump_to_file.sh @@ -0,0 +1,196 @@ +#!/usr/bin/env bash +# Nexus — MySQL dump(Docker 1Panel / 宿主机 mysqldump 通用) +# +# bash deploy/mysql_dump_to_file.sh --output /path/dump.sql [--root /opt/nexus] +# bash deploy/mysql_dump_to_file.sh --output /path/dump.sql --db-url 'mysql+aiomysql://...' +# +# 策略(按序): +# 1. DB host 为运行中容器名 → docker exec 该容器内 mysqldump(连 127.0.0.1) +# 2. 宿主机有 mysqldump 且 host 可解析 → 本机 mysqldump +# 3. 1panel-network 存在 → docker run --rm mysql:8 客户端 +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +NEXUS_ROOT="${NEXUS_ROOT:-/opt/nexus}" +OUTPUT="" +DB_URL="" + +usage() { + cat <<'EOF' +用法: mysql_dump_to_file.sh --output FILE.sql [--root /opt/nexus] [--db-url URL] +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --output|-o) OUTPUT="$2"; shift 2 ;; + --root) NEXUS_ROOT="$2"; shift 2 ;; + --db-url) DB_URL="$2"; shift 2 ;; + -h|--help) usage; exit 0 ;; + *) echo "未知参数: $1" >&2; usage; exit 1 ;; + esac +done + +if [[ -z "$OUTPUT" ]]; then + echo "缺少 --output" >&2 + usage + exit 1 +fi + +resolve_db_url() { + if [[ -n "$DB_URL" ]]; then + echo "$DB_URL" + return 0 + fi + local cname url + cname="$(docker ps --format '{{.Names}}' 2>/dev/null | grep -E 'nexus-prod-nexus|nexus-nexus-1' | head -1 || true)" + if [[ -n "$cname" ]]; then + url="$(docker exec "$cname" grep -E '^NEXUS_DATABASE_URL=' /app/.env 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"' | tr -d "'" || true)" + [[ -n "$url" ]] && echo "$url" && return 0 + fi + for f in \ + "${NEXUS_ROOT}/docker/.env.prod" \ + "${NEXUS_ROOT}/.env" \ + ; do + [[ -f "$f" ]] || continue + url="$(grep -E '^NEXUS_DATABASE_URL=' "$f" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"' | tr -d "'" || true)" + [[ -n "$url" ]] && echo "$url" && return 0 + done + return 1 +} + +DB_URL="$(resolve_db_url || true)" +if [[ -z "$DB_URL" ]]; then + echo "未找到 NEXUS_DATABASE_URL" >&2 + exit 1 +fi + +python3 - "$DB_URL" "$OUTPUT" <<'PY' +import shutil +import subprocess +import sys +import urllib.parse + + +def docker_names() -> set[str]: + r = subprocess.run( + ["docker", "ps", "--format", "{{.Names}}"], + capture_output=True, + text=True, + check=False, + ) + return {ln.strip() for ln in (r.stdout or "").splitlines() if ln.strip()} + + +def network_exists(name: str) -> bool: + return ( + subprocess.run( + ["docker", "network", "inspect", name], + capture_output=True, + check=False, + ).returncode + == 0 + ) + + +def write_dump(cmd: list[str], env: dict[str, str], out: str) -> int: + r = subprocess.run(cmd, env=env, capture_output=True, text=True) + if r.returncode != 0: + sys.stderr.write(r.stderr or r.stdout or "mysqldump failed\n") + return r.returncode + with open(out, "w", encoding="utf-8") as fh: + fh.write(r.stdout) + return 0 + + +def dump_flags(db: str) -> list[str]: + return [ + "--single-transaction", + "--routines", + "--triggers", + "--set-gtid-purged=OFF", + db, + ] + + +url = sys.argv[1].replace("mysql+aiomysql://", "mysql://", 1) +url = url.replace("mysql+asyncmy://", "mysql://", 1) +out = sys.argv[2] +p = urllib.parse.urlparse(url) +user = urllib.parse.unquote(p.username or "") +password = urllib.parse.unquote(p.password or "") +host = p.hostname or "127.0.0.1" +if host == "host.docker.internal": + host = "127.0.0.1" +port = str(p.port or 3306) +db = (p.path or "/nexus").lstrip("/").split("?")[0] +env = {**__import__("os").environ, "MYSQL_PWD": password} +names = docker_names() + +# 1) MySQL 跑在 Docker 容器内(1Panel 常见:URL host = 容器名) +if host in names: + cmd = [ + "docker", + "exec", + "-e", + f"MYSQL_PWD={password}", + host, + "mysqldump", + "-h", + "127.0.0.1", + "-P", + port, + "-u", + user, + *dump_flags(db), + ] + rc = write_dump(cmd, env, out) + if rc == 0: + sys.exit(0) + sys.stderr.write(f"docker exec {host} mysqldump 失败,尝试其它方式…\n") + +# 2) 宿主机 mysqldump +if shutil.which("mysqldump"): + cmd = [ + "mysqldump", + "-h", + host, + "-P", + port, + "-u", + user, + *dump_flags(db), + ] + rc = write_dump(cmd, env, out) + if rc == 0: + sys.exit(0) + +# 3) 临时 mysql 客户端容器(1panel-network) +for net in ("1panel-network", "bridge"): + if not network_exists(net): + continue + cmd = [ + "docker", + "run", + "--rm", + "--network", + net, + "-e", + f"MYSQL_PWD={password}", + "mysql:8.0", + "mysqldump", + "-h", + host, + "-P", + port, + "-u", + user, + *dump_flags(db), + ] + rc = write_dump(cmd, env, out) + if rc == 0: + sys.exit(0) + +sys.stderr.write("所有 mysqldump 策略均失败(无宿主机客户端且 Docker 回退不可用)\n") +sys.exit(1) +PY diff --git a/deploy/nexus-1panel.sh b/deploy/nexus-1panel.sh new file mode 100644 index 00000000..9eb54b0e --- /dev/null +++ b/deploy/nexus-1panel.sh @@ -0,0 +1,1345 @@ +#!/usr/bin/env bash +# 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 +# +# 已 clone 到 /opt/nexus: +# cd /opt/nexus && bash deploy/nexus-1panel.sh install --skip-clone +# +# 其他: +# bash deploy/nexus-1panel.sh install --profile 4c16g +# bash deploy/nexus-1panel.sh upgrade +# bash deploy/nexus-1panel.sh check +# bash deploy/nexus-1panel.sh ports +# +# 公共仓库匿名 clone,无需 Gitea 令牌。 +# 仅私人仓库可选: deploy/nexus-1panel.secrets.sh 或 /root/.nexus-deploy.env +# 或 clone 时 URL 带令牌,脚本会从 git remote 自动解析 + +set -euo pipefail + +# ═══════════════════════════════════════════════════════════════════ +# 内置路径/域名(无敏感信息,可提交 Git) +# ═══════════════════════════════════════════════════════════════════ +NEXUS_ROOT="${NEXUS_ROOT:-/opt/nexus}" +NEXUS_GITEA_HOST="${NEXUS_GITEA_HOST:-66.154.115.8:3000}" +NEXUS_GITEA_REPO="${NEXUS_GITEA_REPO:-admin/Nexus.git}" +NEXUS_DOMAIN="${NEXUS_DOMAIN:-api.synaglobal.vip}" +NEXUS_PROFILE="${NEXUS_PROFILE:-2c8g}" +NEXUS_FROM_ENV="${NEXUS_FROM_ENV:-/www/wwwroot/api.synaglobal.vip/.env}" +NEXUS_BACKUP_DIR="${NEXUS_BACKUP_DIR:-/var/backups/nexus}" +NEXUS_DEPLOY_ENV="${NEXUS_DEPLOY_ENV:-/root/.nexus-deploy.env}" +NEXUS_PUBLISH_PORT="${NEXUS_PUBLISH_PORT:-8600}" +GIT_BRANCH="${NEXUS_GIT_BRANCH:-main}" + +COMPOSE_FILE="docker/docker-compose.prod.yml" +COMPOSE_1PANEL="docker/docker-compose.1panel.yml" +ENV_PROD="docker/.env.prod" +HOST_PROFILE_FILE="docker/.host-profile" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +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}" +NO_BACKUP=false +REQUIRE_BACKUP=false +PRUNE_IMAGES=false +NO_CACHE=false +CHECK_ONLY=false +DRY_RUN=false +CUSTOM_CPUS="" +CUSTOM_MEM_GB="" + +usage() { + cat <<'EOF' +Nexus 1Panel 一键脚本 + + bash deploy/nexus-1panel.sh install [选项] 克隆/配置/启动 + bash deploy/nexus-1panel.sh upgrade [选项] 拉代码+备份+重建 + bash deploy/nexus-1panel.sh check 检查远程是否有更新 + bash deploy/nexus-1panel.sh ports 检查 Gitea/8600/80/443 与防火墙 + bash deploy/nexus-1panel.sh init-token <令牌> 覆盖内置 Gitea 令牌(一般不需要) + +资源档位(三选一): + --profile 2c8g|4c16g|1c4g 预设(默认 2c8g = 2核8G) + --cpus N --mem-gb N 自动匹配最近预设 + +安装选项: + --skip-clone 已在 /opt/nexus,仅 pull + --fresh 全新安装(本机自动生成唯一密钥 + /app/install.html) + --from-env PATH 从旧 .env 迁机导入密钥 + --reuse-secrets 使用 secrets 文件中的 NEXUS 密钥(勿用于新机) + --domain NAME 覆盖域名 + +升级选项: + --no-backup --require-backup --prune --no-cache --dry-run --check(仅查 Git,不重建) + +Git: 公共仓库匿名 clone(无需令牌);私人仓库见 init-token +EOF + exit 0 +} + +script_dir() { + dirname "$(readlink -f "${BASH_SOURCE[0]}")" +} + +secrets_file_path() { + echo "$(script_dir)/nexus-1panel.secrets.sh" +} + +load_token_from_git_remote() { + local root="${1:-$NEXUS_ROOT}" + [[ -n "${NEXUS_GITEA_TOKEN:-}" ]] && return 0 + [[ -d "${root}/.git" ]] || return 0 + local url + url="$(git -C "$root" remote get-url origin 2>/dev/null || true)" + if [[ "$url" =~ http://[^:]+:([^@]+)@ ]]; then + NEXUS_GITEA_TOKEN="${BASH_REMATCH[1]}" + info "已从 git remote 解析 Gitea 令牌" + fi +} + +load_secrets() { + local secrets_file + secrets_file="$(secrets_file_path)" + if [[ -f "$secrets_file" ]]; then + # shellcheck disable=SC1090 + set -a + source "$secrets_file" + set +a + info "已加载 $secrets_file" + fi + if [[ -f "$NEXUS_DEPLOY_ENV" ]]; then + # shellcheck disable=SC1090 + set -a + source "$NEXUS_DEPLOY_ENV" + set +a + info "已加载 $NEXUS_DEPLOY_ENV" + fi + load_token_from_git_remote "$NEXUS_ROOT" + if [[ "$FRESH_INSTALL" == true ]]; then + unset NEXUS_SECRET_KEY NEXUS_API_KEY NEXUS_ENCRYPTION_KEY + info "全新安装:忽略 secrets 中的 NEXUS 密钥,将为本机自动生成" + fi +} + +resolve_profile() { + if [[ -n "$CUSTOM_CPUS" && -n "$CUSTOM_MEM_GB" ]]; then + local c m + c="$CUSTOM_CPUS" + m="$CUSTOM_MEM_GB" + if [[ "$c" -le 1 && "$m" -le 4 ]]; then + NEXUS_PROFILE="1c4g" + elif [[ "$c" -le 2 && "$m" -le 8 ]]; then + NEXUS_PROFILE="2c8g" + else + NEXUS_PROFILE="4c16g" + fi + info "规格 ${c}C${m}G → 档位 ${NEXUS_PROFILE}" + return 0 + fi + case "$NEXUS_PROFILE" in + 1c4g|2c8g|4c16g) ;; + *) + error "未知档位: $NEXUS_PROFILE(可用: 1c4g, 2c8g, 4c16g)" + exit 1 + ;; + esac +} + +profile_env_file() { + local root="$1" + echo "$root/docker/profiles/${NEXUS_PROFILE}.env" +} + +save_host_profile() { + local root="$1" + echo "$NEXUS_PROFILE" >"$root/$HOST_PROFILE_FILE" +} + +load_saved_profile() { + local root="$1" + if [[ -f "$root/$HOST_PROFILE_FILE" ]]; then + NEXUS_PROFILE="$(tr -d '\r\n' <"$root/$HOST_PROFILE_FILE")" + info "使用已保存档位: $NEXUS_PROFILE" + fi +} + +apply_pool_to_env_prod() { + local root="$1" prof="$2" + local env_file="$root/$ENV_PROD" + [[ -f "$env_file" ]] || return 0 + 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-)" + [[ -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 \ + || sudo docker network inspect 1panel-network >/dev/null 2>&1 +} + +upsert_env_var() { + local file="$1" key="$2" val="$3" + if grep -qE "^${key}=" "$file" 2>/dev/null; then + sed -i "s|^${key}=.*|${key}=${val}|" "$file" + else + echo "${key}=${val}" >>"$file" + fi +} + +migrate_redis_url_host() { + local old_url="$1" new_host="$2" + python3 - "$old_url" "$new_host" <<'PY' +import sys +from urllib.parse import quote, unquote, urlparse + +old_url, new_host = sys.argv[1], sys.argv[2] +u = urlparse(old_url) +if u.scheme != "redis": + sys.exit(0) +password = unquote(u.password or "") +if not password and u.username: + password = unquote(u.username) +if password and new_host: + print(f"redis://:{quote(password, safe='')}@{new_host}:6379/0") +PY +} + +# Prefer wizard-written redis://:pass@host from nexus-state or .env.prod; avoid clobbering on nx update. +resolve_nexus_redis_url() { + local redis_host="$1" + local env_file="$2" + local vol_url="" existing="" migrated="" host_lc="${redis_host,,}" + + local state_vol + state_vol="$(docker volume ls -q --filter name=nexus-state 2>/dev/null | head -1 || true)" + if [[ -n "$state_vol" ]]; then + vol_url="$(docker run --rm -v "${state_vol}:/data" alpine sh -c \ + "grep '^NEXUS_REDIS_URL=' /data/.env 2>/dev/null | cut -d= -f2- | tr -d '\"'" 2>/dev/null || true)" + if [[ -n "$vol_url" ]]; then + if [[ "${vol_url,,}" == *"${host_lc}"* ]]; then + echo "$vol_url" + return 0 + fi + if [[ "$vol_url" == *"@"* ]]; then + migrated="$(migrate_redis_url_host "$vol_url" "$redis_host" 2>/dev/null || true)" + if [[ -n "$migrated" ]]; then + echo "$migrated" + return 0 + fi + fi + fi + fi + if [[ -f "$env_file" ]]; then + existing="$(grep '^NEXUS_REDIS_URL=' "$env_file" 2>/dev/null | cut -d= -f2- | tr -d '"' || true)" + if [[ -n "$existing" && "$existing" == *"@"* ]]; then + if [[ "${existing,,}" == *"${host_lc}"* ]]; then + echo "$existing" + return 0 + fi + migrated="$(migrate_redis_url_host "$existing" "$redis_host" 2>/dev/null || true)" + if [[ -n "$migrated" ]]; then + echo "$migrated" + return 0 + fi + fi + fi + echo "redis://${redis_host}:6379/0" +} + +nexus_container_name() { + docker ps --format '{{.Names}}' | grep -E 'nexus-prod-nexus|nexus-nexus-1' | head -1 || true +} + +nexus_publish_port() { + local root="$1" + local env_file="$root/$ENV_PROD" + local port + port="$(grep -E '^NEXUS_PUBLISH_PORT=' "$env_file" 2>/dev/null | cut -d= -f2- | tr -d '"' || true)" + echo "${port:-8600}" +} + +nexus_container_has_install_lock() { + local cname="$1" + docker exec "$cname" test -f /app/.install_locked 2>/dev/null \ + || docker exec "$cname" test -f /var/lib/nexus/.install_locked 2>/dev/null +} + +restore_install_lock_in_container() { + local cname="$1" + [[ -n "$cname" ]] || return 0 + docker exec "$cname" sh -c ' + if [ ! -f /app/.install_locked ] && [ -f /var/lib/nexus/.install_locked ]; then + cp /var/lib/nexus/.install_locked /app/.install_locked + chmod 600 /app/.install_locked + fi + ' 2>/dev/null || true +} + +nexus_on_1panel_network() { + local cname="$1" + [[ -n "$cname" ]] || return 1 + docker inspect "$cname" --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}' 2>/dev/null \ + | grep -qw '1panel-network' +} + +verify_nexus_1panel_network() { + local root="$1" + if ! has_1panel_network; then + return 0 + fi + local cname + cname="$(nexus_container_name)" + [[ -n "$cname" ]] || return 0 + restore_install_lock_in_container "$cname" + if nexus_on_1panel_network "$cname"; then + info "已验证 Nexus 接入 1panel-network" + return 0 + fi + error "Nexus 容器 ${cname} 未接入 1panel-network,无法解析 1Panel MySQL/Redis 主机名" + warn "正在叠加 docker-compose.1panel.yml 重新创建容器..." + compose_cmd "$root" up -d --no-build --remove-orphans + sleep 2 + cname="$(nexus_container_name)" + restore_install_lock_in_container "$cname" + if nexus_on_1panel_network "$cname"; then + info "已修复:Nexus 已接入 1panel-network" + return 0 + fi + error "修复失败:Nexus 仍未接入 1panel-network" + return 1 +} + +set_install_complete() { + local root="$1" + local env_file="$root/$ENV_PROD" + local cname + [[ -f "$env_file" ]] || return 0 + cname="$(nexus_container_name)" + [[ -n "$cname" ]] || return 0 + restore_install_lock_in_container "$cname" + if nexus_container_has_install_lock "$cname"; then + archive_install_wizard_in_container "$cname" + upsert_env_var "$env_file" "NEXUS_INSTALL_WIZARD_PENDING" "0" + info "安装已完成:NEXUS_INSTALL_WIZARD_PENDING=0" + fi +} + +sync_env_prod_to_volume() { + local root="$1" + local env_file="$root/$ENV_PROD" + local state_vol cname + state_vol="$(docker volume ls -q --filter name=nexus-state 2>/dev/null | head -1 || true)" + [[ -n "$state_vol" && -f "$env_file" ]] || return 0 + cname="$(nexus_container_name)" + [[ -n "$cname" ]] || return 0 + restore_install_lock_in_container "$cname" + nexus_container_has_install_lock "$cname" || return 0 + python3 - "$env_file" "$state_vol" <<'PY' +import re +import subprocess +import sys +from pathlib import Path + +env_file = Path(sys.argv[1]) +vol = sys.argv[2] +keys = ( + "NEXUS_SECRET_KEY", + "NEXUS_API_KEY", + "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(): + m = re.match(r"^([A-Z0-9_]+)=(.*)$", line.strip()) + if m and m.group(1) in keys and m.group(2).strip(): + prod[m.group(1)] = m.group(2).strip().strip('"') +if not prod: + sys.exit(0) +cur = subprocess.run( + ["docker", "run", "--rm", "-v", f"{vol}:/data", "alpine", "cat", "/data/.env"], + capture_output=True, + text=True, +) +text = cur.stdout if cur.returncode == 0 else "" +if not text.strip(): + sys.exit(0) +lines = text.splitlines() +out = [] +seen = set() +for line in lines: + key = line.split("=", 1)[0].strip() if "=" in line else "" + if key in prod: + out.append(f'{key}={prod[key]}') + seen.add(key) + else: + out.append(line) +for key, val in prod.items(): + if key not in seen: + out.append(f'{key}={val}') +new_text = "\n".join(out).rstrip() + "\n" +subprocess.run( + ["docker", "run", "--rm", "-i", "-v", f"{vol}:/data", "alpine", "sh", "-c", "cat > /data/.env && chmod 600 /data/.env"], + input=new_text, + text=True, + check=True, +) +print("synced") +PY + info "已将 docker/.env.prod 密钥/URL 同步至 nexus-state 卷" +} + +tag_rollback_image() { + local root="$1" old_id + old_id="$(docker ps --filter "name=nexus-prod-nexus" --format '{{.Image}}' 2>/dev/null | head -1 || true)" + if [[ -z "$old_id" ]]; then + old_id="$(compose_cmd "$root" images -q nexus 2>/dev/null | head -1 || true)" + fi + if [[ -n "$old_id" ]]; then + docker tag "$old_id" "nexus-prod-nexus:rollback" 2>/dev/null || true + info "已标记回滚镜像 nexus-prod-nexus:rollback" + fi +} + +gate_new_image_import() { + local root="$1" img + img="$(compose_cmd "$root" images -q nexus 2>/dev/null | head -1 || true)" + if [[ -z "$img" ]]; then + error "镜像门控失败:未找到 nexus 镜像" + return 1 + fi + if ! docker run --rm --entrypoint python3 "$img" -c "import server.main" >/dev/null 2>&1; then + error "镜像门控失败:import server.main" + return 1 + fi + info "镜像门控通过 (import server.main)" + return 0 +} + +rollback_compose_upgrade() { + local root="$1" + if docker image inspect nexus-prod-nexus:rollback >/dev/null 2>&1; then + warn "健康检查失败,回滚至上一镜像..." + docker tag nexus-prod-nexus:rollback nexus-prod-nexus:latest 2>/dev/null || true + compose_cmd "$root" up -d --no-build --remove-orphans + if ! verify_nexus_1panel_network "$root"; then + error "回滚后仍未接入 1panel-network" + return 1 + fi + return 0 + fi + warn "无回滚镜像 nexus-prod-nexus:rollback,跳过自动回滚" + return 1 +} + +write_1panel_hosts_volume() { + local mysql_host="${1:-}" redis_host="${2:-}" + [[ -n "$mysql_host" || -n "$redis_host" ]] || return 0 + local vol json + vol="$(docker volume ls -q --filter name=nexus-web-data 2>/dev/null | head -1 || true)" + if [[ -z "$vol" ]]; then + warn "未找到 nexus-web-data 卷,跳过写入 1panel-hosts.json" + return 0 + fi + json="{" + if [[ -n "$mysql_host" ]]; then + json+="\"db_host\":\"${mysql_host}\"" + [[ -n "$redis_host" ]] && json+="," + fi + if [[ -n "$redis_host" ]]; then + json+="\"redis_host\":\"${redis_host}\"" + fi + json+="}" + if printf '%s' "$json" | docker run --rm -i -v "${vol}:/data" alpine:3.19 \ + sh -c 'cat > /data/1panel-hosts.json && chmod 644 /data/1panel-hosts.json'; then + info "已写入卷 ${vol} → web/data/1panel-hosts.json" + else + warn "写入 1panel-hosts.json 失败(向导仍可读 .env.prod 环境变量)" + fi +} + +sync_1panel_service_hosts() { + local root="$1" + local env_file="$root/$ENV_PROD" + local detect="$root/deploy/detect-1panel-services.sh" + [[ -f "$env_file" ]] || return 0 + if ! has_1panel_network; then + warn "未找到 1panel-network — 请先在 1Panel 应用商店安装 MySQL/Redis" + return 0 + fi + if [[ ! -x "$detect" ]]; then + chmod +x "$detect" 2>/dev/null || true + fi + if [[ ! -x "$detect" ]]; then + warn "未找到 detect-1panel-services.sh,跳过 1Panel 容器名探测" + return 0 + fi + local line mysql_host="" redis_host="" detect_out + if ! detect_out="$("$detect" 2>&1)"; then + warn "1Panel 容器探测: ${detect_out:-无 MySQL/Redis 容器运行}" + return 0 + fi + while IFS= read -r line; do + case "$line" in + NEXUS_1PANEL_DB_HOST=*) + mysql_host="${line#NEXUS_1PANEL_DB_HOST=}" + upsert_env_var "$env_file" NEXUS_1PANEL_DB_HOST "$mysql_host" + info "1Panel MySQL 容器: $mysql_host(安装向导将预填为数据库主机)" + ;; + NEXUS_1PANEL_REDIS_HOST=*) + redis_host="${line#NEXUS_1PANEL_REDIS_HOST=}" + upsert_env_var "$env_file" NEXUS_1PANEL_REDIS_HOST "$redis_host" + local redis_url + redis_url="$(resolve_nexus_redis_url "$redis_host" "$env_file")" + upsert_env_var "$env_file" "NEXUS_REDIS_URL" "$redis_url" + if [[ "$redis_url" == *"@"* ]]; then + info "1Panel Redis 容器: $redis_host(NEXUS_REDIS_URL 已保留向导密码)" + else + info "1Panel Redis 容器: $redis_host(NEXUS_REDIS_URL 占位,向导步骤 3 填密码后写入卷 .env)" + fi + ;; + esac + done <<<"$detect_out" + write_1panel_hosts_volume "$mysql_host" "$redis_host" +} + +compose_cmd() { + local root="$1" + shift + local prof + prof="$(profile_env_file "$root")" + if [[ ! -f "$prof" ]]; then + error "缺少档位文件: $prof" + exit 1 + fi + cd "$root" + local -a compose_args=(-f "$COMPOSE_FILE") + if has_1panel_network && [[ -f "$COMPOSE_1PANEL" ]]; then + compose_args+=(-f "$COMPOSE_1PANEL") + fi + docker compose "${compose_args[@]}" --env-file "$ENV_PROD" --env-file "$prof" "$@" +} + +rand_secret() { + openssl rand -hex 24 2>/dev/null || head -c 24 /dev/urandom | od -An -tx1 | tr -d ' \n' +} + +rand_pass() { + openssl rand -hex 16 2>/dev/null || head -c 16 /dev/urandom | od -An -tx1 | tr -d ' \n' +} + +read_env_val() { + local file="$1" key="$2" + grep -E "^${key}=" "$file" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '\r"' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' +} + +parse_gitea_endpoint() { + local hp="${NEXUS_GITEA_HOST}" + GITEA_HOST="${hp%%:*}" + GITEA_PORT="${hp##*:}" + if [[ "$GITEA_HOST" == "$GITEA_PORT" || -z "$GITEA_PORT" ]]; then + GITEA_PORT=3000 + fi +} + +nexus_publish_port() { + local root="${1:-}" + local p="${NEXUS_PUBLISH_PORT:-8600}" + if [[ -n "$root" && -f "$root/$ENV_PROD" ]]; then + local v + v="$(read_env_val "$root/$ENV_PROD" NEXUS_PUBLISH_PORT)" + [[ -n "$v" ]] && p="$v" + fi + echo "$p" +} + +tcp_reachable() { + local host="$1" port="$2" t="${3:-3}" + if command -v nc >/dev/null 2>&1; then + nc -z -w "$t" "$host" "$port" 2>/dev/null + return $? + fi + if command -v timeout >/dev/null 2>&1; then + timeout "$t" bash -c "exec 3<>/dev/tcp/${host}/${port}" 2>/dev/null \ + && { exec 3<&- 3>&-; return 0; } + else + bash -c "exec 3<>/dev/tcp/${host}/${port}" 2>/dev/null \ + && { exec 3<&- 3>&-; return 0; } + fi + return 1 +} + +local_port_listening() { + local port="$1" + if command -v ss >/dev/null 2>&1; then + ss -tlnH 2>/dev/null | grep -qE ":${port}[[:space:]]" + return $? + fi + if command -v netstat >/dev/null 2>&1; then + netstat -tln 2>/dev/null | grep -qE ":${port}[[:space:]]" + return $? + fi + curl -sf --max-time 2 "http://127.0.0.1:${port}/health" >/dev/null 2>&1 +} + +firewall_may_block_port() { + local port="$1" + if command -v ufw >/dev/null 2>&1; then + local st + st="$(ufw status 2>/dev/null | head -1 || true)" + if [[ "$st" == *"active"* ]]; then + ufw status 2>/dev/null | grep -qE "${port}/tcp[[:space:]]+ALLOW" && return 1 + return 0 + fi + fi + if command -v firewall-cmd >/dev/null 2>&1 && firewall-cmd --state 2>/dev/null | grep -qi running; then + firewall-cmd --list-ports 2>/dev/null | grep -q "${port}/tcp" && return 1 + firewall-cmd --list-services 2>/dev/null | grep -qE '^(http|https)$' \ + && [[ "$port" == "80" || "$port" == "443" ]] && return 1 + return 0 + fi + return 1 +} + +remind_firewall() { + local port="$1" label="$2" + if firewall_may_block_port "$port"; then + warn "防火墙可能未放行 ${port}/tcp(${label})" + warn " 本机: ufw allow ${port}/tcp 或 1Panel → 主机 → 防火墙" + warn " 云厂商控制台: 安全组入站放行 ${port}/tcp" + fi +} + +check_ports_preflight() { + local root="${1:-$NEXUS_ROOT}" + local nport issues=0 + parse_gitea_endpoint + nport="$(nexus_publish_port "$root")" + + step "检查端口与防火墙..." + if tcp_reachable "$GITEA_HOST" "$GITEA_PORT"; then + info "Gitea ${GITEA_HOST}:${GITEA_PORT} — 可达(git 拉取)" + else + warn "Gitea ${GITEA_HOST}:${GITEA_PORT} — 无法连接,clone/pull 会失败" + warn " 请确认 Gitea 在线,且本机出站与该端口未被防火墙/安全组拦截" + issues=$((issues + 1)) + fi + + if local_port_listening "$nport"; then + info "127.0.0.1:${nport} — 已有监听(可能为旧 Nexus 实例)" + else + info "127.0.0.1:${nport} — 尚未监听(安装后将由 Compose 绑定)" + fi + + for spec in "80:HTTP" "443:HTTPS"; do + local p="${spec%%:*}" label="${spec##*:}" + if local_port_listening "$p"; then + info "本机 :${p} — 已监听(${label} / 1Panel)" + else + warn "本机 :${p} — 未监听,外网 ${label} 暂不可用" + warn " 安装完成后: 1Panel → 网站 → 反代 http://127.0.0.1:${nport} 并申请 SSL" + issues=$((issues + 1)) + fi + remind_firewall "$p" "${label} 公网" + done + + remind_firewall 22 "SSH(远程维护)" + + if [[ "$issues" -gt 0 ]]; then + warn "共 ${issues} 项端口提醒(可继续安装,但请先处理 Gitea 不可达问题)" + else + info "端口预检通过" + fi + echo "" +} + +check_ports_post() { + local root="$1" + local nport health_ext + nport="$(nexus_publish_port "$root")" + + step "检查服务端口..." + if local_port_listening "$nport"; then + info "Nexus 127.0.0.1:${nport} — 已监听" + else + warn "127.0.0.1:${nport} — 未监听,请执行: cd $root && docker compose ... ps" + fi + + if local_port_listening 443; then + health_ext="$(curl -sk --max-time 8 -o /dev/null -w '%{http_code}' "https://${NEXUS_DOMAIN}/health" 2>/dev/null || echo "000")" + if [[ "$health_ext" == "200" ]]; then + info "https://${NEXUS_DOMAIN}/health — 外网可达 (HTTP 200)" + else + warn "https://${NEXUS_DOMAIN}/health — 返回 HTTP ${health_ext}" + warn " 若外网打不开: 1Panel 反代是否指向 127.0.0.1:${nport},安全组是否放行 443" + fi + else + warn "本机 443 未监听 — 请在 1Panel 创建站点并启用 HTTPS,否则浏览器无法访问 ${NEXUS_DOMAIN}" + remind_firewall 443 "HTTPS" + fi + + if ! local_port_listening 80; then + info "本机 80 未监听(仅 HTTPS 时正常)" + fi + echo "" +} + +cmd_ports() { + require_root + check_ports_preflight "$NEXUS_ROOT" + if [[ -f "$NEXUS_ROOT/$ENV_PROD" ]]; then + check_ports_post "$NEXUS_ROOT" + fi +} + +require_root() { + if [[ "$(id -u)" -ne 0 ]]; then + error "请切换到 root 执行(su -),不要用 sudo 包一层" + exit 1 + fi +} + +require_token() { + if [[ -n "${NEXUS_GITEA_TOKEN:-}" ]]; then + return 0 + fi + info "未配置 Gitea 令牌,按公共仓库匿名 clone/pull" +} + +git_remote_url() { + if [[ -n "${NEXUS_GITEA_TOKEN:-}" ]]; then + echo "http://admin:${NEXUS_GITEA_TOKEN}@${NEXUS_GITEA_HOST}/${NEXUS_GITEA_REPO}" + else + echo "http://${NEXUS_GITEA_HOST}/${NEXUS_GITEA_REPO}" + fi +} + +ensure_packages() { + if command -v apt-get >/dev/null 2>&1; then + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get install -y -qq git curl openssl ca-certificates >/dev/null 2>&1 || true + fi +} + +ensure_docker() { + if docker compose version >/dev/null 2>&1; then + info "Docker Compose: $(docker compose version --short 2>/dev/null || docker compose version)" + return 0 + fi + error "未检测到 docker compose。请在 1Panel → 容器 安装 Docker。" + exit 1 +} + +clone_or_pull() { + local url + url="$(git_remote_url)" + + if [[ "$SKIP_CLONE" == true ]]; then + if [[ ! -d "$NEXUS_ROOT/.git" ]]; then + error "--skip-clone 但 $NEXUS_ROOT 不是 git 仓库" + exit 1 + fi + info "在 $NEXUS_ROOT 执行 git pull..." + git -C "$NEXUS_ROOT" remote set-url origin "$url" 2>/dev/null || true + git -C "$NEXUS_ROOT" fetch origin "$GIT_BRANCH" --prune + git -C "$NEXUS_ROOT" reset --hard "origin/${GIT_BRANCH}" + return 0 + fi + + mkdir -p "$(dirname "$NEXUS_ROOT")" + if [[ -d "$NEXUS_ROOT/.git" ]]; then + info "仓库已存在,同步 origin/${GIT_BRANCH}..." + git -C "$NEXUS_ROOT" remote set-url origin "$url" 2>/dev/null || true + git -C "$NEXUS_ROOT" fetch origin "$GIT_BRANCH" --prune + git -C "$NEXUS_ROOT" reset --hard "origin/${GIT_BRANCH}" + return 0 + fi + + if [[ -d "$NEXUS_ROOT" ]] && [[ -n "$(ls -A "$NEXUS_ROOT" 2>/dev/null)" ]]; then + error "目录非空且不是 git 仓库: $NEXUS_ROOT" + exit 1 + fi + + rm -rf "$NEXUS_ROOT" + info "克隆 Nexus → $NEXUS_ROOT ..." + git clone "$url" "$NEXUS_ROOT" + git -C "$NEXUS_ROOT" checkout "$GIT_BRANCH" 2>/dev/null || true +} + +write_temp_secrets_env() { + local f + f="$(mktemp /tmp/nexus-secrets-import.XXXXXX.env)" + { + printf 'NEXUS_SECRET_KEY=%s\n' "$NEXUS_SECRET_KEY" + printf 'NEXUS_API_KEY=%s\n' "$NEXUS_API_KEY" + printf 'NEXUS_ENCRYPTION_KEY=%s\n' "$NEXUS_ENCRYPTION_KEY" + } >"$f" + chmod 600 "$f" + echo "$f" +} + +write_env_prod() { + local root="$1" + local env_file="$root/$ENV_PROD" + local example="$root/docker/.env.prod.example" + local gen="$root/scripts/generate_nexus_secrets.py" + local prof backup tmp + + if [[ ! -f "$example" ]]; then + error "缺少 $example" + exit 1 + fi + if [[ ! -f "$gen" ]]; then + error "缺少 $gen" + exit 1 + fi + + if [[ -f "$env_file" ]]; then + warn "$env_file 已存在,仅更新域名与连接池" + sed -i "s|^NEXUS_CORS_ORIGINS=.*|NEXUS_CORS_ORIGINS=https://${NEXUS_DOMAIN}|" "$env_file" + sed -i "s|^NEXUS_API_BASE_URL=.*|NEXUS_API_BASE_URL=https://${NEXUS_DOMAIN}|" "$env_file" + apply_pool_to_env_prod "$root" "$(profile_env_file "$root")" + chmod 600 "$env_file" + return 0 + fi + + prof="$(profile_env_file "$root")" + backup="/root/.nexus-install-secrets-$(date +%Y%m%d%H%M%S).env" + + if [[ -n "$FROM_ENV" && -f "$FROM_ENV" ]]; then + info "迁机:从 $FROM_ENV 导入密钥..." + python3 "$gen" write-prod \ + --template "$example" \ + --output "$env_file" \ + --domain "$NEXUS_DOMAIN" \ + --profile-env "$prof" \ + --from-env "$FROM_ENV" \ + --backup "$backup" + elif [[ "$FRESH_INSTALL" == true ]]; then + info "全新安装:为本机生成唯一 NEXUS 密钥(MySQL/Redis 请自行安装)..." + python3 "$gen" write-prod \ + --template "$example" \ + --output "$env_file" \ + --domain "$NEXUS_DOMAIN" \ + --profile-env "$prof" \ + --fresh \ + --wizard-pending \ + --backup "$backup" + info "密钥备份: $backup(仅本机,勿复制到其他服务器)" + info "完成后访问 https://${NEXUS_DOMAIN}/app/install.html" + elif [[ "$REUSE_SECRETS" == true && -n "${NEXUS_SECRET_KEY:-}" && -n "${NEXUS_API_KEY:-}" && -n "${NEXUS_ENCRYPTION_KEY:-}" ]]; then + warn "使用 secrets 中的 NEXUS 密钥(仅迁机/复用,新机勿用 --reuse-secrets)" + tmp="$(write_temp_secrets_env)" + python3 "$gen" write-prod \ + --template "$example" \ + --output "$env_file" \ + --domain "$NEXUS_DOMAIN" \ + --profile-env "$prof" \ + --from-env "$tmp" \ + --backup "$backup" + rm -f "$tmp" + else + info "未迁机导入:为本机生成唯一密钥..." + python3 "$gen" write-prod \ + --template "$example" \ + --output "$env_file" \ + --domain "$NEXUS_DOMAIN" \ + --profile-env "$prof" \ + --fresh \ + --wizard-pending \ + --backup "$backup" + info "密钥备份: $backup" + if [[ "$FRESH_INSTALL" != true ]]; then + info "完成后访问 https://${NEXUS_DOMAIN}/app/install.html" + fi + fi + + chmod 600 "$env_file" + info "已写入 $env_file" +} + +purge_legacy_bundled_services() { + local root="$1" + local s + for s in \ + "$root/deploy/uninstall-mysql-compose.sh" \ + "$root/deploy/uninstall-redis-compose.sh"; do + if [[ -x "$s" ]]; then + step "清理遗留 Compose 内置服务: $(basename "$s")" + NEXUS_ROOT="$root" bash "$s" || true + fi + done +} + +reset_nexus_state_volume_for_fresh() { + local vol + vol="$(docker volume ls -q --filter name=nexus-state 2>/dev/null | head -1 || true)" + [[ -n "$vol" ]] || return 0 + warn "全新安装:清除 nexus-state 卷内旧 .env / .install_locked" + docker run --rm -v "${vol}:/data" alpine:3.19 \ + sh -c 'rm -f /data/.env /data/.install_locked' 2>/dev/null || true +} + +restore_install_wizard_html_in_container() { + local cname="$1" + [[ -n "$cname" ]] || return 0 + docker exec "$cname" sh -c ' + rm -f /app/.install_locked + if [ -f /app/web/app/install.html.bak ] && [ ! -f /app/web/app/install.html ]; then + mv /app/web/app/install.html.bak /app/web/app/install.html + fi + ' 2>/dev/null || true +} + +compose_up() { + local root="$1" cname + save_host_profile "$root" + purge_legacy_bundled_services "$root" + sync_1panel_service_hosts "$root" + if [[ "$FRESH_INSTALL" == true ]]; then + reset_nexus_state_volume_for_fresh + fi + if has_1panel_network; then + info "已接入 1panel-network(MySQL/Redis 使用 1Panel 容器名互联)" + fi + info "档位: ${NEXUS_PROFILE}(仅 Nexus 容器;MySQL/Redis 请自行安装)" + info "构建并启动(首次约 10~20 分钟)..." + compose_cmd "$root" up -d --build --remove-orphans + verify_nexus_1panel_network "$root" || return 1 + if [[ "$FRESH_INSTALL" == true ]]; then + cname="$(nexus_container_name)" + restore_install_wizard_html_in_container "$cname" + fi +} + +archive_install_wizard_in_container() { + local cname="$1" + [[ -n "$cname" ]] || return 0 + docker exec "$cname" sh -c ' + if [ -f /app/web/app/install.html.bak ]; then + rm -f /app/web/app/install.html + elif [ -f /app/web/app/install.html ]; then + mv /app/web/app/install.html /app/web/app/install.html.bak + fi + ' 2>/dev/null || true +} + +verify_install_wizard() { + local root="$1" + local port="${2:-$(nexus_publish_port "$root")}" + local code cname + cname="$(nexus_container_name)" + if [[ -n "$cname" ]] && nexus_container_has_install_lock "$cname"; then + archive_install_wizard_in_container "$cname" + code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${port}/app/install.html" 2>/dev/null || echo "000") + if [[ "$code" == "404" ]]; then + info " /app/install.html 已归档 → HTTP 404" + return 0 + fi + warn " 已锁定但 /app/install.html → HTTP $code" + return 1 + fi + code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${port}/app/install.html" 2>/dev/null || echo "000") + if [[ "$code" == "200" ]]; then + info " /app/install.html → HTTP 200" + return 0 + fi + warn " /app/install.html → HTTP $code(尝试同步静态文件…)" + if [[ -x "$root/deploy/sync-install-wizard-to-container.sh" ]]; then + NEXUS_PUBLISH_PORT="$port" NEXUS_ROOT="$root" bash "$root/deploy/sync-install-wizard-to-container.sh" || true + code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${port}/app/install.html" 2>/dev/null || echo "000") + if [[ "$code" == "200" ]]; then + info " /app/install.html → HTTP 200(已同步)" + return 0 + fi + fi + warn " 请重建镜像: cd $root && docker compose -f docker/docker-compose.prod.yml --env-file docker/.env.prod build --no-cache nexus" + return 1 +} + +verify_health() { + local root="$1" title="${2:-安装}" + local i health spa port + port="$(nexus_publish_port "$root")" + info "等待服务就绪 (端口 ${port})..." + for i in $(seq 1 60); do + health=$(curl -sf "http://127.0.0.1:${port}/health" 2>/dev/null || true) + if [[ "$health" == "ok" ]]; then + spa=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${port}/app/" 2>/dev/null || echo "000") + verify_install_wizard "$root" "$port" || true + echo "" + info "═══ ${title}完成 ═══" + info " 档位: ${NEXUS_PROFILE}" + info " /health → ok" + info " /app/ → HTTP $spa" + info " 目录: $root" + if [[ -d "$root/.git" ]]; then + info " 版本: $(git -C "$root" log -1 --oneline)" + fi + echo "" + info "1Panel → 网站 → 反代 http://127.0.0.1:${port} 域名: ${NEXUS_DOMAIN}" + info "配置示例: $root/deploy/1panel/openresty-nexus.conf.example" + if [[ "$FRESH_INSTALL" == true ]] || ! grep -qE '^NEXUS_SECRET_KEY=.+$' "$root/$ENV_PROD" 2>/dev/null; then + info "浏览器: https://${NEXUS_DOMAIN}/app/install.html" + else + info "浏览器: https://${NEXUS_DOMAIN}/app/" + fi + echo "" + NEXUS_ROOT="$root" bash "$root/deploy/install-nx-cli.sh" 2>/dev/null || true + info "运维菜单: nx 或 nexus-update" + check_ports_post "$root" + return 0 + fi + sleep 5 + done + error "健康检查超时" + check_ports_post "$root" || true + compose_cmd "$root" logs --tail=80 nexus || true + return 1 +} + +ensure_repo() { + if [[ ! -d "$NEXUS_ROOT/.git" ]]; then + error "未找到 $NEXUS_ROOT,请先: bash $0 install" + exit 1 + fi + if [[ ! -f "$NEXUS_ROOT/$ENV_PROD" ]]; then + error "缺少 $NEXUS_ROOT/$ENV_PROD" + exit 1 + fi +} + +git_fetch() { + local url + url="$(git_remote_url)" + git -C "$NEXUS_ROOT" remote set-url origin "$url" 2>/dev/null || true + git -C "$NEXUS_ROOT" fetch origin "$GIT_BRANCH" --prune +} + +commits_behind() { + git -C "$NEXUS_ROOT" rev-list "HEAD..origin/${GIT_BRANCH}" --count 2>/dev/null || echo "0" +} + +show_pending() { + local behind + behind="$(commits_behind)" + info "当前: $(git -C "$NEXUS_ROOT" log -1 --oneline)" + info "远程: $(git -C "$NEXUS_ROOT" log -1 --oneline "origin/${GIT_BRANCH}")" + if [[ "$behind" == "0" ]]; then + info "已是最新。" + return 1 + fi + warn "落后 ${behind} 个提交:" + git -C "$NEXUS_ROOT" log --oneline "HEAD..origin/${GIT_BRANCH}" + return 0 +} + +backup_mysql() { + local root="$1" ts file dump_sh + if [[ "$NO_BACKUP" == true ]]; then + warn "已跳过备份 (--no-backup)" + return 0 + fi + dump_sh="${root}/deploy/mysql_dump_to_file.sh" + if [[ ! -f "$dump_sh" ]]; then + warn "缺少 $dump_sh,跳过 MySQL 备份" + return 0 + fi + mkdir -p "$NEXUS_BACKUP_DIR" + ts="$(date +%Y%m%d-%H%M%S)" + file="${NEXUS_BACKUP_DIR}/nexus-before-upgrade-${ts}.sql" + step "备份 MySQL → $file" + if ! NEXUS_ROOT="$root" bash "$dump_sh" --output "$file" --root "$root"; then + if [[ "$REQUIRE_BACKUP" == true ]]; then + error "mysqldump 失败,已中止(--require-backup)" + rm -f "$file" + exit 1 + fi + warn "mysqldump 失败,继续升级(加 --require-backup 可在备份失败时中止)" + rm -f "$file" + return 0 + fi + gzip -f "$file" + info "备份: ${file}.gz" +} + +git_upgrade() { + local before after + before="$(git -C "$NEXUS_ROOT" rev-parse HEAD)" + step "同步 origin/${GIT_BRANCH}" + git -C "$NEXUS_ROOT" reset --hard "origin/${GIT_BRANCH}" + after="$(git -C "$NEXUS_ROOT" rev-parse HEAD)" + info "代码: ${before:0:7} → ${after:0:7}" +} + +cmd_init_token() { + require_root + local tok="${1:-}" + if [[ -z "$tok" ]]; then + error "用法: $0 init-token " + exit 1 + fi + printf 'NEXUS_GITEA_TOKEN=%s\n' "$tok" >"$NEXUS_DEPLOY_ENV" + chmod 600 "$NEXUS_DEPLOY_ENV" + info "已写入 $NEXUS_DEPLOY_ENV" +} + +cmd_install() { + echo "" + echo "==========================================" + echo " Nexus 1Panel 一键安装" + echo " 域名: ${NEXUS_DOMAIN} 档位: ${NEXUS_PROFILE}" + echo "==========================================" + echo "" + + require_root + require_token + resolve_profile + ensure_packages + ensure_docker + check_ports_preflight "$NEXUS_ROOT" + clone_or_pull + NEXUS_ROOT="$NEXUS_ROOT" bash "$NEXUS_ROOT/deploy/install-nx-cli.sh" 2>/dev/null || true + write_env_prod "$NEXUS_ROOT" + compose_up "$NEXUS_ROOT" + verify_health "$NEXUS_ROOT" "安装" || exit 1 +} + +cmd_upgrade() { + echo "" + echo "==========================================" + echo " Nexus 1Panel 一键升级" + echo "==========================================" + echo "" + + require_root + require_token + ensure_docker + ensure_repo + load_saved_profile "$NEXUS_ROOT" + resolve_profile + check_ports_preflight "$NEXUS_ROOT" + if [[ "$SKIP_GIT" == true ]]; then + info "跳过 Git 同步(本机 rsync 已推送代码)" + else + git_fetch + show_pending || true + fi + + if [[ "$CHECK_ONLY" == true ]]; then + exit 0 + fi + if [[ "$DRY_RUN" == true ]]; then + info "dry-run 结束。" + 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 + backup_mysql "$NEXUS_ROOT" + git_upgrade + else + info "Git 已最新,仍重建 Docker 镜像与容器(避免旧 entrypoint 缓存)" + if [[ "$NO_BACKUP" != true ]]; then + 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" + save_host_profile "$NEXUS_ROOT" + purge_legacy_bundled_services "$NEXUS_ROOT" + sync_1panel_service_hosts "$NEXUS_ROOT" + sync_env_prod_to_volume "$NEXUS_ROOT" + tag_rollback_image "$NEXUS_ROOT" + if [[ "$NO_CACHE" == true ]]; then + step "无缓存重建 Nexus 镜像..." + compose_cmd "$NEXUS_ROOT" build --no-cache nexus + else + step "重建 Nexus 镜像..." + compose_cmd "$NEXUS_ROOT" build nexus + fi + if ! gate_new_image_import "$NEXUS_ROOT"; then + exit 1 + fi + step "重建容器(--remove-orphans 清理旧 mysql/redis)..." + compose_cmd "$NEXUS_ROOT" up -d --no-build --remove-orphans + if ! verify_nexus_1panel_network "$NEXUS_ROOT"; then + rollback_compose_upgrade "$NEXUS_ROOT" || true + exit 1 + fi + local port wiz_code i health + port="$(nexus_publish_port "$NEXUS_ROOT")" + for i in $(seq 1 30); do + health="$(curl -sf "http://127.0.0.1:${port}/health" 2>/dev/null || true)" + [[ "$health" == "ok" ]] && break + sleep 2 + done + cname="$(nexus_container_name)" + if [[ -n "$cname" ]] && nexus_container_has_install_lock "$cname"; then + archive_install_wizard_in_container "$cname" + info "安装已锁定,跳过 install.html 热同步" + else + wiz_code="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${port}/app/install.html" 2>/dev/null || echo "000")" + if [[ "$wiz_code" != "200" && -x "$NEXUS_ROOT/deploy/sync-install-wizard-to-container.sh" ]]; then + NEXUS_PUBLISH_PORT="$port" NEXUS_ROOT="$NEXUS_ROOT" bash "$NEXUS_ROOT/deploy/sync-install-wizard-to-container.sh" + fi + fi + set_install_complete "$NEXUS_ROOT" + if [[ "$PRUNE_IMAGES" == true ]]; then + 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 + fi + # shellcheck source=./detect_deploy_runtime.sh + if [[ -f "$NEXUS_ROOT/deploy/detect_deploy_runtime.sh" ]]; then + # shellcheck disable=SC1091 + source "$NEXUS_ROOT/deploy/detect_deploy_runtime.sh" + 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 +} + +cmd_check() { + require_root + require_token + ensure_repo + git_fetch + show_pending || true +} + +parse_common_install() { + while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) usage ;; + --skip-clone) SKIP_CLONE=true; shift ;; + --fresh) FRESH_INSTALL=true; shift ;; + --reuse-secrets) REUSE_SECRETS=true; shift ;; + --profile) NEXUS_PROFILE="$2"; shift 2 ;; + --cpus) CUSTOM_CPUS="$2"; shift 2 ;; + --mem-gb|--memory-gb) CUSTOM_MEM_GB="$2"; shift 2 ;; + --domain) NEXUS_DOMAIN="$2"; shift 2 ;; + --from-env) FROM_ENV="$2"; shift 2 ;; + --root) NEXUS_ROOT="$2"; shift 2 ;; + --no-backup) NO_BACKUP=true; shift ;; + --prune|--prune-images) PRUNE_IMAGES=true; shift ;; + --dry-run) DRY_RUN=true; shift ;; + *) + error "未知参数: $1" + usage + ;; + esac + done +} + +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 ;; + --root) NEXUS_ROOT="$2"; shift 2 ;; + --branch) GIT_BRANCH="$2"; shift 2 ;; + --no-backup) NO_BACKUP=true; shift ;; + --require-backup) REQUIRE_BACKUP=true; shift ;; + --prune|--prune-images) PRUNE_IMAGES=true; shift ;; + --no-cache|--rebuild) NO_CACHE=true; shift ;; + --dry-run) DRY_RUN=true; shift ;; + --check) CHECK_ONLY=true; shift ;; + *) + error "未知参数: $1" + usage + ;; + esac + done +} + +main() { + local cmd="${1:-install}" + shift || true + + load_secrets + + case "$cmd" in + install) + parse_common_install "$@" + cmd_install + ;; + upgrade) + parse_common_upgrade "$@" + cmd_upgrade + ;; + check) + CHECK_ONLY=true + parse_common_upgrade "$@" + cmd_check + ;; + init-token) + cmd_init_token "${1:-}" + ;; + ports) + cmd_ports + ;; + -h|--help|help) + usage + ;; + *) + error "未知子命令: $cmd" + usage + ;; + esac +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi diff --git a/deploy/nexus.conf b/deploy/nexus.conf new file mode 100644 index 00000000..8aa6faa2 --- /dev/null +++ b/deploy/nexus.conf @@ -0,0 +1,32 @@ +# Nexus — Supervisor Process Guardian +# - --workers N for multi-core concurrency +# - --loop uvloop for 40-50% QPS improvement +# - --http httptools for fast HTTP parsing +# +# Install: sudo cp nexus.conf /etc/supervisor/conf.d/nexus.conf +# Start: sudo supervisorctl reread && sudo supervisorctl update && sudo supervisorctl start nexus +# Set workers: export NEXUS_WORKERS=$(nproc) +# +# *** Before deploying, replace ALL {{PLACEHOLDER}} values with your actual paths *** +# {{DEPLOY_DIR}} = installation directory (e.g. /opt/nexus or /www/wwwroot/your.domain) +# {{VENV_DIR}} = Python venv directory (e.g. /opt/nexus/venv) + +[program:nexus] +command={{DEPLOY_DIR}}/venv/bin/uvicorn server.main:app --host 0.0.0.0 --port 8600 --workers %(ENV_NEXUS_WORKERS)s --loop uvloop --http httptools --log-level info +directory={{DEPLOY_DIR}} +user=root +autostart=true +autorestart=true +startretries=10 +startsecs=5 +stopwaitsecs=15 +stopsignal=INT +environment= + NEXUS_WORKERS="%(ENV_NEXUS_WORKERS)s", + PATH="{{VENV_DIR}}/bin:%(ENV_PATH)s" +stderr_logfile=/var/log/nexus/error.log +stdout_logfile=/var/log/nexus/access.log +stderr_logfile_maxbytes=50MB +stdout_logfile_maxbytes=50MB +stderr_logfile_backups=5 +stdout_logfile_backups=5 diff --git a/deploy/nginx_http.conf b/deploy/nginx_http.conf new file mode 100644 index 00000000..803e4c7c --- /dev/null +++ b/deploy/nginx_http.conf @@ -0,0 +1,79 @@ +# Nexus v6.0 — Nginx Reverse Proxy Config (Template) +# *** Copy this file and replace ALL {{PLACEHOLDER}} values before deploying *** +# +# {{SERVER_NAME}} = your domain or IP (e.g. nexus.example.com or 192.168.1.100) +# {{DEPLOY_DIR}} = installation root (e.g. /opt/nexus or /www/wwwroot/nexus.example.com) +# {{LOG_DIR}} = nginx log directory (e.g. /var/log/nginx or /www/wwwlogs) + +# ── Upstream: Python FastAPI backend ── +upstream nexus_api { + server 127.0.0.1:8600; + keepalive 32; +} + +server { + listen 80; + server_name {{SERVER_NAME}}; + + # Document root = frontend static files + root {{DEPLOY_DIR}}/web/app; + index index.html; + + # ── Python API proxy ── + location /api/ { + proxy_pass http://nexus_api; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 300s; + proxy_send_timeout 300s; + proxy_connect_timeout 10s; + } + + # ── WebSocket proxy (alerts + WebSSH terminal) ── + location /ws/ { + proxy_pass http://nexus_api; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; + } + + # ── Health endpoint ── + location /health { + proxy_pass http://nexus_api; + proxy_set_header Host $host; + } + + # ── Static assets ── + location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 7d; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + + # ── Deny sensitive files ── + location ~ /\. { + deny all; + } + location ~ /data/config\.json$ { + deny all; + } + + # ── Static HTML pages (no SPA — each page is standalone) ── + location / { + try_files $uri $uri/ =404; + } + + # ── Logging ── + access_log {{LOG_DIR}}/{{SERVER_NAME}}_nexus.log; + error_log {{LOG_DIR}}/{{SERVER_NAME}}_nexus.error.log; + + # ── Upload limit ── + client_max_body_size 500m; +} diff --git a/deploy/nginx_https.conf b/deploy/nginx_https.conf new file mode 100644 index 00000000..8b26df39 --- /dev/null +++ b/deploy/nginx_https.conf @@ -0,0 +1,113 @@ +# Nexus v6.0 — Nginx Production Config (HTTPS Template) +# *** Copy this file and replace ALL {{PLACEHOLDER}} values before deploying *** +# +# {{SERVER_NAME}} = your domain (e.g. nexus.example.com) +# {{DEPLOY_DIR}} = installation root (e.g. /opt/nexus or /www/wwwroot/nexus.example.com) +# {{LOG_DIR}} = nginx log directory (e.g. /var/log/nginx or /www/wwwlogs) +# {{SSL_CERT}} = SSL certificate fullchain path +# {{SSL_KEY}} = SSL private key path + +# ── Upstream: Python FastAPI backend ── +upstream nexus_api { + server 127.0.0.1:8600; + keepalive 32; +} + +# ── HTTP → HTTPS redirect ── +server { + listen 80; + server_name {{SERVER_NAME}}; + return 301 https://$host$request_uri; +} + +# ── HTTPS ── +server { + listen 443 ssl http2; + server_name {{SERVER_NAME}}; + + # SSL + ssl_certificate {{SSL_CERT}}; + ssl_certificate_key {{SSL_KEY}}; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + # HSTS + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + + # Document root = frontend static files + root {{DEPLOY_DIR}}/web/app; + index index.html; + + # ── Python API proxy ── + location /api/ { + proxy_pass http://nexus_api; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 300s; + proxy_send_timeout 300s; + proxy_connect_timeout 10s; + } + + # WebSocket access log: omit query string so JWT is not written to disk + log_format nexus_ws '$remote_addr - [$time_local] "$request_method $uri" $status $body_bytes_sent'; + + # ── WebSocket proxy (alerts + WebSSH terminal) ── + location /ws/ { + access_log {{LOG_DIR}}/{{SERVER_NAME}}.ws.log nexus_ws; + proxy_pass http://nexus_api; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; + } + + # ── Health endpoint ── + location /health { + proxy_pass http://nexus_api; + proxy_set_header Host $host; + } + + # ── Static assets (cache aggressively) ── + location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 7d; + add_header Cache-Control "public, immutable"; + # Must re-declare HSTS — nginx location-level add_header replaces ALL parent add_header directives + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + try_files $uri =404; + } + + # ── Deny sensitive files ── + location ~ /\. { + deny all; + } + location ~ /data/config\.json$ { + deny all; + } + + # ── Redirect bare page paths to /app/ (for reverse-proxy deployments) ── + # If you use proxy_pass instead of root+try_files, uncomment this block: + # location ~ ^/(login|index|servers|files|push|scripts|script-executions|credentials|schedules|retries|commands|alerts|audit|settings|install|terminal|assets)\.html$ { + # return 301 /app/$1.html; + # } + + # ── Static HTML pages (no SPA — each page is standalone) ── + location / { + try_files $uri $uri/ =404; + } + + # ── Logging ── + access_log {{LOG_DIR}}/{{SERVER_NAME}}.log; + error_log {{LOG_DIR}}/{{SERVER_NAME}}.error.log; + + # ── Upload limit ── + client_max_body_size 500m; +} diff --git a/deploy/nx b/deploy/nx new file mode 100644 index 00000000..eb094ce1 --- /dev/null +++ b/deploy/nx @@ -0,0 +1,452 @@ +#!/usr/bin/env bash +# Nexus NX — 统一运维菜单(安装 + 升级 + 日常运维) +# +# nx 交互菜单 +# nx update 一键更新(等同 deploy/update.sh) +# nx update --no-cache 无缓存重建镜像 +# nx install-fresh 全新安装 +# nx health 健康检查 +# +set -euo pipefail + +NX_SELF="$(readlink -f "${BASH_SOURCE[0]}")" +NX_DIR="$(cd "$(dirname "$NX_SELF")" && pwd)" +# shellcheck source=./nexus-1panel.sh +source "${NX_DIR}/nexus-1panel.sh" + +pause_enter() { + echo "" + read -r -p "按 Enter 继续..." +} + +clear_screen() { + if [[ -t 1 ]]; then + clear 2>/dev/null || true + fi +} + +is_stack_installed() { + [[ -d "${NEXUS_ROOT}/.git" && -f "${NEXUS_ROOT}/${ENV_PROD}" ]] +} + +detect_runtime() { + if is_stack_installed && docker compose version >/dev/null 2>&1; then + local prof q + prof="$(profile_env_file "$NEXUS_ROOT")" + if [[ -f "$prof" && -f "$NEXUS_ROOT/$ENV_PROD" ]]; then + local -a det_args=(-f "$COMPOSE_FILE") + if has_1panel_network && [[ -f "$NEXUS_ROOT/$COMPOSE_1PANEL" ]]; then + det_args+=(-f "$COMPOSE_1PANEL") + fi + q="$(cd "$NEXUS_ROOT" && docker compose "${det_args[@]}" \ + --env-file "$ENV_PROD" --env-file "$prof" ps -q nexus 2>/dev/null || true)" + if [[ -n "$q" ]]; then + NX_RUNTIME="docker" + return 0 + fi + fi + fi + if command -v supervisorctl >/dev/null 2>&1 && supervisorctl status nexus >/dev/null 2>&1; then + NX_RUNTIME="supervisor" + return 0 + fi + NX_RUNTIME="none" +} + +install_nx_cli() { + NEXUS_ROOT="$(cd "${NX_DIR}/.." && pwd)" \ + bash "${NX_DIR}/install-nx-cli.sh" 2>/dev/null || true +} + +menu_header() { + local title="$1" + clear_screen + echo "" + echo "╔══════════════════════════════════════════════════════════╗" + printf "║ Nexus NX — %-44s║\n" "$title" + echo "╠══════════════════════════════════════════════════════════╣" + echo "║ 域名: ${NEXUS_DOMAIN} 目录: ${NEXUS_ROOT}" + if is_stack_installed; then + detect_runtime + echo "║ 运行: ${NX_RUNTIME} 档位: $(cat "${NEXUS_ROOT}/${HOST_PROFILE_FILE}" 2>/dev/null || echo "${NEXUS_PROFILE}")" + if [[ -d "${NEXUS_ROOT}/.git" ]]; then + echo "║ 版本: $(git -C "${NEXUS_ROOT}" log -1 --oneline 2>/dev/null || echo '?')" + fi + else + echo "║ 状态: 未安装" + fi + echo "╚══════════════════════════════════════════════════════════╝" + echo "" +} + +run_install() { + local profile="${1:-2c8g}" extra="${2:-}" + NEXUS_PROFILE="$profile" + SKIP_CLONE=false + FRESH_INSTALL=false + NO_BACKUP=false + PRUNE_IMAGES=false + FROM_ENV="${NEXUS_FROM_ENV}" + case "$extra" in + skip-clone) SKIP_CLONE=true ;; + fresh) FRESH_INSTALL=true ;; + from-env) FROM_ENV="${NEXUS_FROM_ENV}" ;; + esac + load_secrets + cmd_install + install_nx_cli + pause_enter +} + +menu_install() { + while true; do + menu_header "安装" + cat <<'EOF' + [1] 一键安装(默认 2核8G / 2c8g) + [2] 安装 — 4核16G(4c16g) + [3] 安装 — 1核4G(1c4g) + [4] 自定义规格(输入 CPU 与内存 GB) + [5] 已 clone 到 /opt/nexus,仅 pull + 启动(--skip-clone) + [6] 全新安装(install-nexus-fresh.sh → /app/install.html) + [7] 检查端口 / 防火墙(不安装) + [0] 返回 + +EOF + read -r -p "请选择: " choice + case "$choice" in + 1) run_install 2c8g "" ;; + 2) run_install 4c16g "" ;; + 3) run_install 1c4g "" ;; + 4) + read -r -p "CPU 核数: " cpus + read -r -p "内存 GB: " mem + CUSTOM_CPUS="$cpus" + CUSTOM_MEM_GB="$mem" + run_install 2c8g "" + ;; + 5) run_install 2c8g skip-clone ;; + 6) + if [[ -x "${NX_DIR}/install-nexus-fresh.sh" ]]; then + bash "${NX_DIR}/install-nexus-fresh.sh" --profile "${NEXUS_PROFILE:-2c8g}" + else + run_install 2c8g fresh + fi + ;; + 7) + require_root + load_secrets + check_ports_preflight "$NEXUS_ROOT" + if is_stack_installed; then + check_ports_post "$NEXUS_ROOT" + fi + pause_enter + ;; + 0) return 0 ;; + *) warn "无效选项" ; sleep 1 ;; + esac + done +} + +ops_restart_nexus() { + step "重启 Nexus(Python)..." + if [[ "$NX_RUNTIME" == "docker" ]]; then + compose_cmd "$NEXUS_ROOT" restart nexus + elif [[ "$NX_RUNTIME" == "supervisor" ]]; then + supervisorctl restart nexus + else + error "未检测到运行中的 Nexus" + return 1 + fi + sleep 3 + verify_health "$NEXUS_ROOT" "重启" || true +} + +ops_restart_stack() { + step "重启 Compose 栈..." + compose_cmd "$NEXUS_ROOT" restart + sleep 5 + verify_health "$NEXUS_ROOT" "重启" || true +} + +ops_stop_stack() { + warn "将停止 Nexus 容器(MySQL/Redis 为外置服务,不受影响)" + read -r -p "确认? [y/N]: " c + [[ "$c" == "y" || "$c" == "Y" ]] || return 0 + compose_cmd "$NEXUS_ROOT" stop + info "已停止" +} + +ops_start_stack() { + compose_cmd "$NEXUS_ROOT" up -d --remove-orphans + verify_nexus_1panel_network "$NEXUS_ROOT" || return 1 + verify_health "$NEXUS_ROOT" "启动" || true +} + +ops_logs() { + local svc="${1:-nexus}" + echo "" + read -r -p "跟踪日志行数 [80]: " n + n="${n:-80}" + compose_cmd "$NEXUS_ROOT" logs --tail="$n" -f "$svc" +} + +ops_status() { + if [[ "$NX_RUNTIME" == "docker" ]]; then + compose_cmd "$NEXUS_ROOT" ps + elif [[ "$NX_RUNTIME" == "supervisor" ]]; then + supervisorctl status nexus + fi + echo "" + local port h code + port="$(nexus_publish_port "$NEXUS_ROOT")" + h=$(curl -sf "http://127.0.0.1:${port}/health" 2>/dev/null || echo "fail") + info "/health → ${h}" + local cname + cname="$(nexus_container_name 2>/dev/null || true)" + code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${port}/app/install.html" 2>/dev/null || echo "000") + if [[ -n "$cname" ]] && nexus_container_has_install_lock "$cname"; then + if [[ "$code" == "404" ]]; then + info "/app/install.html → 已归档 (HTTP 404)" + else + warn "/app/install.html → 应已归档但 HTTP ${code}" + fi + else + info "/app/install.html → HTTP ${code}" + fi +} + +ops_backup_now() { + NO_BACKUP=false + backup_mysql "$NEXUS_ROOT" +} + +ops_install_cron() { + bash "${NX_DIR}/install_ops_cron.sh" +} + +ops_update() { + local args=() + PRUNE_IMAGES=false + NO_BACKUP=false + NO_CACHE=false + read -r -p "跳过 MySQL 备份? [y/N]: " nb + [[ "$nb" == "y" || "$nb" == "Y" ]] && args+=(--no-backup) + read -r -p "无缓存重建镜像? [y/N]: " nc + [[ "$nc" == "y" || "$nc" == "Y" ]] && args+=(--no-cache) + read -r -p "升级后清理悬空镜像? [y/N]: " pr + [[ "$pr" == "y" || "$pr" == "Y" ]] && args+=(--prune) + bash "${NX_DIR}/update.sh" "${args[@]}" + pause_enter +} + +require_installed() { + if ! is_stack_installed; then + warn "尚未安装,请先使用菜单 [1] 安装" + pause_enter + return 1 + fi + require_root + load_secrets + load_saved_profile "$NEXUS_ROOT" + detect_runtime + return 0 +} + +menu_main() { + load_secrets + while true; do + menu_header "运维菜单" + cat <<'EOF' + ── 安装 ── + [1] 安装向导(新机 / 迁机 / 档位) + [2] 全新安装 → /app/install.html + + ── 日常运维(需已安装)── + [3] 状态 + 健康检查 + [4] 一键更新(pull + 重建镜像)★ + [5] 重启 Nexus(Python) + [6] 查看 Nexus 日志 + [7] 启动 / 停止 Compose 栈 + [8] 重建镜像并启动(--build nexus) + [9] 备份 MySQL + [e] 安装巡检/备份 cron(health_monitor + db_backup) + [v] 1Panel 安装/运维验收脚本 + + ── 其它 ── + [a] 检查 Git 是否有更新 + [b] 端口 / 防火墙检查 + [c] 进入 Nexus 容器 shell + [d] 1Panel 反代说明 + [0] 退出 + +EOF + read -r -p "请选择: " choice + case "$choice" in + 1) menu_install ;; + 2) + require_root + if is_stack_installed; then + warn "将执行全新安装(清除 nexus-state 卷内旧配置并重建容器)" + read -r -p "确认继续? [y/N]: " fresh_ok + [[ "$fresh_ok" == "y" || "$fresh_ok" == "Y" ]] || continue + fi + exec bash "${NX_DIR}/install-nexus-fresh.sh" --profile "${NEXUS_PROFILE:-2c8g}" + ;; + 3) + require_root + if is_stack_installed; then + load_saved_profile "$NEXUS_ROOT" + detect_runtime + ops_status + else + check_ports_preflight "$NEXUS_ROOT" + fi + pause_enter + ;; + 4) + require_installed || continue + ops_update + ;; + 5) + require_installed || continue + ops_restart_nexus + pause_enter + ;; + 6) + require_installed || continue + ops_logs nexus + ;; + 7) + require_installed || continue + echo " [1] 启动 [2] 停止 [3] 重启栈" + read -r -p "选: " s + case "$s" in + 1) ops_start_stack ;; + 2) ops_stop_stack ;; + 3) ops_restart_stack ;; + *) warn "无效" ;; + esac + pause_enter + ;; + 8) + require_installed || continue + read -r -p "无缓存重建? [y/N]: " nc + if [[ "$nc" == "y" || "$nc" == "Y" ]]; then + compose_cmd "$NEXUS_ROOT" build --no-cache nexus + fi + compose_cmd "$NEXUS_ROOT" up -d --build --remove-orphans nexus + verify_nexus_1panel_network "$NEXUS_ROOT" || true + verify_health "$NEXUS_ROOT" "重建" || true + pause_enter + ;; + 9) + require_installed || continue + ops_backup_now + pause_enter + ;; + e|E) + require_root + 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 + pause_enter + ;; + b|B) + require_root + check_ports_preflight "$NEXUS_ROOT" + is_stack_installed && check_ports_post "$NEXUS_ROOT" || true + pause_enter + ;; + c|C) + require_installed || continue + compose_cmd "$NEXUS_ROOT" exec nexus bash || compose_cmd "$NEXUS_ROOT" exec nexus sh + ;; + d|D) + info "1Panel → 网站 → 创建站点 ${NEXUS_DOMAIN}" + info "反向代理: http://127.0.0.1:$(nexus_publish_port "$NEXUS_ROOT")" + info "示例: ${NEXUS_ROOT}/deploy/1panel/openresty-nexus.conf.example" + pause_enter + ;; + 0) exit 0 ;; + *) warn "无效选项"; sleep 1 ;; + esac + done +} + +nx_main() { + local sub="${1:-}" + install_nx_cli + load_secrets + case "$sub" in + ""|menu|god|ops|admin) + # god/ops 已合并进主菜单,保留别名兼容 + [[ "$sub" == "god" || "$sub" == "ops" || "$sub" == "admin" ]] && \ + warn "「上帝菜单」已合并进 nx 主菜单,直接选对应项即可" + menu_main + ;; + install) menu_install ;; + update|upgrade) + require_root + shift || true + exec bash "${NX_DIR}/update.sh" "$@" + ;; + install-fresh|fresh) + require_root + exec bash "${NX_DIR}/install-nexus-fresh.sh" "$@" + ;; + install-auto|auto) + require_root + if is_stack_installed; then + warn "已安装,install-auto 将执行升级而非重复安装" + exec bash "${NX_DIR}/update.sh" + else + run_install "${NEXUS_PROFILE:-2c8g}" "" + fi + ;; + health) + require_root + is_stack_installed && ops_status || check_ports_preflight "$NEXUS_ROOT" + ;; + cron) + require_root + ops_install_cron + ;; + verify) + require_root + bash "${NX_DIR}/verify-1panel-install-wizard.sh" + ;; + -h|--help) + cat </dev/null || true +nx_main "$@" diff --git a/deploy/pre_deploy_check.sh b/deploy/pre_deploy_check.sh new file mode 100644 index 00000000..aa169c13 --- /dev/null +++ b/deploy/pre_deploy_check.sh @@ -0,0 +1,362 @@ +#!/bin/bash +# Nexus Pre-deploy Gate Check (v3 — 8 gates) +# 在部署前强制检查 8 道门控,任何一道不过则阻止部署 +# +# 门控1: CHANGELOG — docs/changelog/ 下必须有今天的 .md 文件(且行数≥10) +# 门控2: AUDIT — docs/audit/ 下必须有今天的审计记录(且包含关键段落) +# 门控3: TEST — tests/test_api.py 必须存在且全通过 +# 门控4: LINT — ruff check server/ 零错误 +# 门控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/.json +# +# 用法: bash deploy/pre_deploy_check.sh +# 退出码: 0=全部通过, 1=至少一道门未过 +# 日志: deploy/gate_log.jsonl(每次检查自动追加记录) + +set -euo pipefail + +_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +_REPO_ROOT="$(cd "${_SCRIPT_DIR}/.." && pwd)" +DEPLOY_DIR="${NEXUS_DEPLOY_DIR:-${_REPO_ROOT}}" +TODAY=$(date +%Y-%m-%d) +TIMESTAMP=$(date -Iseconds) +GATES_PASSED=0 +GATES_TOTAL=8 +BLOCKED=0 +GATE_RESULTS="" + +# Resolve tool from .venv / venv / PATH +_pick_bin() { + local name="$1" + for p in "${DEPLOY_DIR}/.venv/bin/${name}" "${DEPLOY_DIR}/venv/bin/${name}"; do + if [ -x "${p}" ]; then + echo "${p}" + return 0 + fi + done + command -v "${name}" 2>/dev/null || return 1 +} + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +NC='\033[0m' # No Color + +# ── 日志函数 ── +gate_log() { + local gate="$1" result="$2" detail="${3:-}" + local entry="{\"ts\":\"${TIMESTAMP}\",\"date\":\"${TODAY}\",\"gate\":\"${gate}\",\"result\":\"${result}\",\"detail\":\"${detail}\"}" + GATE_RESULTS="${GATE_RESULTS}${entry}," + # 追加到 JSONL 日志文件 + echo "${entry}" >> "${DEPLOY_DIR}/deploy/gate_log.jsonl" +} + +echo "========================================" +echo " Nexus Pre-deploy Gate Check (v3)" +echo " Date: ${TODAY}" +echo " Gates: ${GATES_TOTAL}" +echo "========================================" +echo "" + +# ── Gate 1: Changelog (存在 + 行数≥10) ── +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) + LINES=$(wc -l < "${FILE}") + if [ "${LINES}" -ge 10 ]; then + echo -e "${GREEN}PASS${NC}" + echo " └─ Found: $(basename "${FILE}") (${LINES} lines)" + GATES_PASSED=$((GATES_PASSED + 1)) + gate_log "changelog" "PASS" "$(basename "${FILE}") ${LINES}lines" + else + echo -e "${RED}BLOCK${NC}" + echo " └─ File exists but only ${LINES} lines (need ≥10)" + echo " └─ Changelog must have substance, not just a placeholder" + BLOCKED=1 + gate_log "changelog" "BLOCK" "only ${LINES} lines" + fi +else + echo -e "${RED}BLOCK${NC}" + echo " └─ No changelog file found: ${CHANGELOG_DIR}/${TODAY}-*.md" + echo " └─ Create changelog before deploying" + BLOCKED=1 + gate_log "changelog" "BLOCK" "file not found" +fi + +# ── Gate 2: 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) + # 检查关键段落是否存在 + HAS_STEP3=$(grep -c "Step 3" "${FILE}" 2>/dev/null || echo 0) + HAS_CLOSURE=$(grep -c "Closure" "${FILE}" 2>/dev/null || echo 0) + HAS_DOD=$(grep -c "DoD" "${FILE}" 2>/dev/null || echo 0) + if [ "${HAS_STEP3}" -ge 1 ] && [ "${HAS_CLOSURE}" -ge 1 ] && [ "${HAS_DOD}" -ge 1 ]; then + echo -e "${GREEN}PASS${NC}" + echo " └─ Found: $(basename "${FILE}") (Step3✓ Closure✓ DoD✓)" + GATES_PASSED=$((GATES_PASSED + 1)) + gate_log "audit" "PASS" "$(basename "${FILE}")" + else + echo -e "${RED}BLOCK${NC}" + echo " └─ Audit file exists but missing required sections:" + [ "${HAS_STEP3}" -eq 0 ] && echo " └─ Missing: Step 3 (规则扫描)" + [ "${HAS_CLOSURE}" -eq 0 ] && echo " └─ Missing: Closure表" + [ "${HAS_DOD}" -eq 0 ] && echo " └─ Missing: DoD (Definition of Done)" + BLOCKED=1 + gate_log "audit" "BLOCK" "missing sections" + fi +else + echo -e "${RED}BLOCK${NC}" + echo " └─ No audit file found: ${AUDIT_DIR}/${TODAY}-*.md" + echo " └─ Run line-walk audit (8 steps) and save report before deploying" + BLOCKED=1 + gate_log "audit" "BLOCK" "file not found" +fi + +# ── Gate 3: 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}" + echo " └─ Test script not found: ${TEST_SCRIPT}" + echo " └─ Tests are MANDATORY — cannot deploy without test coverage" + BLOCKED=1 + gate_log "test" "BLOCK" "script not found" +else + # 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 + # 本机未起 :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" + GATES_PASSED=$((GATES_PASSED + 1)) + gate_log "test" "PASS" "all passed" + fi +fi + +# ── Gate 4: Lint (ruff — F only: undefined names, unused imports) ── +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 + LINT_COUNT=$(echo "${LINT_OUTPUT}" | grep -cE "^server/" 2>/dev/null || true) + LINT_COUNT=$(echo "${LINT_COUNT}" | head -1 | tr -d '[:space:]') + LINT_COUNT=${LINT_COUNT:-0} + if [ "${LINT_COUNT}" -eq 0 ]; then + echo -e "${GREEN}PASS${NC}" + echo " └─ ruff check: 0 violations" + GATES_PASSED=$((GATES_PASSED + 1)) + gate_log "lint" "PASS" "0 violations" + else + echo -e "${RED}BLOCK${NC}" + echo " └─ ruff found ${LINT_COUNT} violations:" + echo "${LINT_OUTPUT}" | head -15 | sed 's/^/ │ /' + BLOCKED=1 + gate_log "lint" "BLOCK" "${LINT_COUNT} violations" + fi +else + echo -e "${RED}BLOCK${NC}" + echo " └─ ruff not found (.venv/bin/ruff or pip install ruff)" + BLOCKED=1 + gate_log "lint" "BLOCK" "ruff not found" +fi + +# ── Gate 5: Import (python -c "import server.main") ── +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 + IMPORT_PYTHON="${p}" + break + fi +done +if [ -z "${IMPORT_PYTHON}" ]; then + IMPORT_PYTHON="python3" +fi +IMPORT_OUTPUT=$(cd "${DEPLOY_DIR}" && "${IMPORT_PYTHON}" -c "import server.main" 2>&1) && IMPORT_OK=1 || IMPORT_OK=0 +if [ "${IMPORT_OK}" -eq 1 ]; then + echo -e "${GREEN}PASS${NC}" + echo " └─ server.main imports successfully" + GATES_PASSED=$((GATES_PASSED + 1)) + gate_log "import" "PASS" "ok" +else + # 检查是否有真正的导入错误(不是缺少运行时依赖如数据库) + if echo "${IMPORT_OUTPUT}" | grep -qiE "ImportError|ModuleNotFoundError|SyntaxError"; then + echo -e "${RED}BLOCK${NC}" + echo " └─ Import failed:" + echo "${IMPORT_OUTPUT}" | head -5 | sed 's/^/ │ /' + BLOCKED=1 + gate_log "import" "BLOCK" "import/syntax error" + else + # 运行时依赖缺失(如数据库连接)不算BLOCK,但警告 + echo -e "${YELLOW}WARN${NC}" + echo " └─ Import has runtime dependency issues (not code errors)" + GATES_PASSED=$((GATES_PASSED + 1)) + gate_log "import" "WARN" "runtime deps only" + fi +fi + +# ── Gate 6: Security (bandit — HIGH only) ── +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 + HIGH_COUNT=$(echo "${SEC_OUTPUT}" | grep -cE "Severity: High" 2>/dev/null || true) + HIGH_COUNT=$(echo "${HIGH_COUNT}" | head -1 | tr -d '[:space:]') + HIGH_COUNT=${HIGH_COUNT:-0} + if [ "${HIGH_COUNT}" -eq 0 ]; then + echo -e "${GREEN}PASS${NC}" + echo " └─ bandit: 0 HIGH findings" + GATES_PASSED=$((GATES_PASSED + 1)) + gate_log "security" "PASS" "0 HIGH" + else + echo -e "${RED}BLOCK${NC}" + echo " └─ bandit found ${HIGH_COUNT} HIGH severity issues:" + echo "${SEC_OUTPUT}" | grep -A3 "Severity: High" | head -15 | sed 's/^/ │ /' + BLOCKED=1 + gate_log "security" "BLOCK" "${HIGH_COUNT} HIGH findings" + fi +else + echo -e "${RED}BLOCK${NC}" + echo " └─ bandit not found (.venv/bin/bandit or pip install bandit)" + BLOCKED=1 + gate_log "security" "BLOCK" "bandit not found" +fi + +# ── Gate 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..HEAD 2>/dev/null || true) + for CF in ${CHANGED_FILES}; do + # 跳过非代码文件与门控运行日志 + case "${CF}" in + docs/*|CLAUDE.md|*.md|deploy/gate_log.jsonl|web/app/assets/*) continue ;; + esac + # 检查审计文件是否提到了这个文件 + if ! grep -q "$(basename "${CF}")" "${FILE}" 2>/dev/null; then + if [ "${REVIEW_OK}" -eq 1 ]; then + echo -e "${RED}BLOCK${NC}" + REVIEW_OK=0 + fi + echo " └─ Changed file not in audit: ${CF}" + fi + done + fi + if [ "${REVIEW_OK}" -eq 1 ]; then + echo -e "${GREEN}PASS${NC}" + echo " └─ Audit covers all changed files" + GATES_PASSED=$((GATES_PASSED + 1)) + gate_log "review" "PASS" "audit covers changes" + else + BLOCKED=1 + gate_log "review" "BLOCK" "audit missing changed files" + fi +else + echo -e "${RED}BLOCK${NC}" + echo " └─ No audit file to validate against" + BLOCKED=1 + 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 + echo -e "${GREEN}PASS${NC}" + gate_log "shell_eol" "PASS" "all tracked sh LF" +else + echo -e "${RED}BLOCK${NC}" + echo " └─ Run: bash deploy/check_shell_eol.sh" + echo " └─ Fix: git add --renormalize '*.sh'" + BLOCKED=1 + gate_log "shell_eol" "BLOCK" "CR in tracked sh" +fi + +# ── Pre-flight: deploy-critical paths LF in git index ── +echo -n "Pre-flight: Text EOL (git index) ... " +if python3 "${DEPLOY_DIR}/scripts/check_text_eol.py" >/dev/null 2>&1; then + echo -e "${GREEN}PASS${NC}" + gate_log "text_eol" "PASS" "git index LF" +else + echo -e "${RED}BLOCK${NC}" + echo " └─ Run: python3 scripts/check_text_eol.py --fix-hint" + BLOCKED=1 + gate_log "text_eol" "BLOCK" "CR in git index" +fi + +# ── Summary ── +echo "" +echo "========================================" +if [ "${BLOCKED}" -eq 0 ]; then + echo -e " Result: ${GREEN}${GATES_PASSED}/${GATES_TOTAL} gates passed${NC} — Deploy allowed" + echo "========================================" + gate_log "summary" "PASS" "${GATES_PASSED}/${GATES_TOTAL}" + exit 0 +else + echo -e " Result: ${RED}${GATES_PASSED}/${GATES_TOTAL} gates passed${NC} — Deploy BLOCKED" + echo "========================================" + gate_log "summary" "BLOCK" "${GATES_PASSED}/${GATES_TOTAL}" + exit 1 +fi diff --git a/deploy/preflight-release-host.sh b/deploy/preflight-release-host.sh new file mode 100644 index 00000000..3bb19cc3 --- /dev/null +++ b/deploy/preflight-release-host.sh @@ -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:-}" + 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 "$@" diff --git a/deploy/prune_frontend_assets.py b/deploy/prune_frontend_assets.py new file mode 100644 index 00000000..ccd27382 --- /dev/null +++ b/deploy/prune_frontend_assets.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +"""Remove orphaned files under web/app/assets/ not referenced by current Vite build. + +Vite/Rolldown minified bundles reference chunks by bare filename (e.g. ServersPage-xxx.js) +without import() paths — scan file contents for known asset basenames. + +Orphans newer than --retention-days are kept so long-lived browser tabs can still load +their lazy chunks after a deploy (see docs/design/specs/2026-06-02-keep-session-no-refresh-design.md). +""" +from __future__ import annotations + +import re +import sys +import time +from pathlib import Path + +# Leading `_` required for Vite chunks like `_plugin-vue_export-helper-*.js` +ASSET_NAME_RE = re.compile( + r"[_A-Za-z0-9][A-Za-z0-9_.-]*\.(?:js|css|woff2?|ttf|eot)" +) +URL_RE = re.compile(r"""url\(["']?([^"')]+)["']?\)""") +DEFAULT_RETENTION_DAYS = 14 + + +def _parse_args(argv: list[str]) -> tuple[bool, int, Path]: + dry_run = "--dry-run" in argv + retention_days = DEFAULT_RETENTION_DAYS + positional: list[str] = [] + i = 0 + while i < len(argv): + arg = argv[i] + if arg == "--dry-run": + i += 1 + continue + if arg.startswith("--retention-days="): + retention_days = int(arg.split("=", 1)[1]) + i += 1 + continue + if arg == "--retention-days": + if i + 1 >= len(argv): + raise SystemExit("--retention-days requires a value") + retention_days = int(argv[i + 1]) + i += 2 + continue + positional.append(arg) + i += 1 + if retention_days < 0: + raise SystemExit("--retention-days must be >= 0") + app_dir = Path(positional[0] if positional else "/www/wwwroot/api.synaglobal.vip/web/app") + return dry_run, retention_days, app_dir + + +def _referenced_basenames(text: str, known: set[str]) -> set[str]: + found: set[str] = set() + for m in ASSET_NAME_RE.finditer(text): + name = m.group(0) + if name in known: + found.add(name) + for raw in URL_RE.findall(text): + name = Path(raw).name + if name in known: + found.add(name) + return found + + +def collect(app_dir: Path) -> set[Path]: + assets = app_dir / "assets" + index = app_dir / "index.html" + if not index.is_file(): + raise SystemExit(f"missing {index}") + + all_names = {p.name for p in assets.iterdir() if p.is_file()} + keep_names: set[str] = set() + + for m in re.finditer(r"/app/assets/([^\s\"']+)", index.read_text(encoding="utf-8")): + name = m.group(1).split("/")[-1] + if name in all_names: + keep_names.add(name) + + if not keep_names: + raise SystemExit("no entry assets found in index.html") + + changed = True + while changed: + changed = False + for name in list(keep_names): + path = assets / name + if not path.is_file(): + continue + text = path.read_text(encoding="utf-8", errors="ignore") + for ref in _referenced_basenames(text, all_names): + if ref not in keep_names: + keep_names.add(ref) + changed = True + + return {assets / n for n in keep_names} + + +def _partition_orphans( + orphan: list[Path], + retention_days: int, +) -> tuple[list[Path], list[Path]]: + """Split orphans into delete-now vs keep-for-open-tabs (mtime within window).""" + if retention_days <= 0: + return orphan, [] + cutoff = time.time() - retention_days * 86400 + to_remove: list[Path] = [] + retained: list[Path] = [] + for p in orphan: + if p.stat().st_mtime >= cutoff: + retained.append(p) + else: + to_remove.append(p) + return to_remove, retained + + +def main() -> int: + dry_run, retention_days, app_dir = _parse_args(sys.argv[1:]) + assets = app_dir / "assets" + if not assets.is_dir(): + raise SystemExit(f"not a directory: {assets}") + + keep = collect(app_dir) + all_files = {p for p in assets.iterdir() if p.is_file()} + orphan = sorted(all_files - keep, key=lambda p: p.name) + to_remove, retained = _partition_orphans(orphan, retention_days) + + if dry_run: + print( + f"dry-run retention_days={retention_days} " + f"referenced_kept={len(keep)} " + f"would_remove={len(to_remove)} " + f"retained_orphans={len(retained)} " + f"total={len(all_files)}" + ) + return 0 + + for p in to_remove: + p.unlink() + print( + f"kept={len(keep)} removed={len(to_remove)} " + f"retained_orphans={len(retained)} retention_days={retention_days} " + f"total_before={len(all_files)}" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/deploy/quick-install.sh b/deploy/quick-install.sh new file mode 100644 index 00000000..fefe93ba --- /dev/null +++ b/deploy/quick-install.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +# ============================================================================= +# 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 "http://66.154.115.8:3000/admin/Nexus/raw/branch/main/deploy/quick-install.sh" -o /tmp/quick-install.sh +# bash /tmp/quick-install.sh +# +# 可选参数会传给 install-nexus-fresh.sh,例如: +# bash /tmp/quick-install.sh --profile 4c16g +# ============================================================================= + +set -euo pipefail + +NEXUS_GITEA_HOST="${NEXUS_GITEA_HOST:-66.154.115.8:3000}" +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}}" + +RED='\033[0;31m' +GREEN='\033[0;32m' +CYAN='\033[0;36m' +BLUE='\033[0;34m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } +step() { echo -e "${CYAN}[STEP]${NC} $*"; } + +usage() { + cat </dev/null; then + return 1 + fi + return 0 +} + +download_script() { + local name="$1" dest="$2" + local url base + for base in \ + "${NEXUS_RAW_BASE}" \ + "http://${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}" + return 0 + fi + done + return 1 +} + +run_via_git() { + local url="http://${NEXUS_GITEA_HOST}/${NEXUS_GITEA_REPO}" + if [[ -n "${NEXUS_GITEA_TOKEN:-}" ]]; then + url="http://admin:${NEXUS_GITEA_TOKEN}@${NEXUS_GITEA_HOST}/${NEXUS_GITEA_REPO}" + fi + step "改用 git clone..." + mkdir -p "$(dirname "$NEXUS_ROOT")" + if [[ -d "${NEXUS_ROOT}/.git" ]]; then + git -C "$NEXUS_ROOT" remote set-url origin "$url" 2>/dev/null || true + git -C "$NEXUS_ROOT" fetch origin "$NEXUS_GIT_BRANCH" --depth 1 2>/dev/null || \ + git -C "$NEXUS_ROOT" fetch origin "$NEXUS_GIT_BRANCH" --prune + git -C "$NEXUS_ROOT" reset --hard "origin/${NEXUS_GIT_BRANCH}" 2>/dev/null || \ + git -C "$NEXUS_ROOT" pull + else + rm -rf "$NEXUS_ROOT" + git clone --depth 1 -b "$NEXUS_GIT_BRANCH" "$url" "$NEXUS_ROOT" + fi + chmod +x "${NEXUS_ROOT}/deploy/"*.sh 2>/dev/null || true + exec bash "${NEXUS_ROOT}/deploy/install-nexus-fresh.sh" --skip-clone "$@" +} + +main() { + if [[ "$(id -u)" -ne 0 ]]; then + error "请使用 root 执行(su - 后直接运行,不要用 sudo 包一层)" + exit 1 + fi + + for cf in /root/.nexus-deploy.env /root/.nexus-secrets/nexus.env; do + [[ -f "$cf" ]] && set -a && source "$cf" && set +a + done + unset NEXUS_SECRET_KEY NEXUS_API_KEY NEXUS_ENCRYPTION_KEY + + echo "" + echo -e "${BLUE}╔══════════════════════════════════════════════════════════════╗${NC}" + echo -e "${BLUE}║${NC} Nexus 6.0 — 公共仓库一键安装 ${BLUE}║${NC}" + echo -e "${BLUE}╚══════════════════════════════════════════════════════════════╝${NC}" + echo "" + + case "${1:-}" in + -h|--help) usage; exit 0 ;; + esac + + local tmp + tmp="$(mktemp /tmp/install-nexus-fresh.XXXXXX.sh)" + if download_script "install-nexus-fresh.sh" "$tmp"; then + chmod +x "$tmp" + exec bash "$tmp" "$@" + fi + + step "raw 下载失败,尝试 git clone..." + run_via_git "$@" +} + +# curl | bash 时 stdin 脚本无 BASH_SOURCE[0],不可配合 set -u 做入口判断 +main "$@" diff --git a/deploy/rsync-local-to-server.sh b/deploy/rsync-local-to-server.sh new file mode 100644 index 00000000..6ba45d0a --- /dev/null +++ b/deploy/rsync-local-to-server.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +# Sync local Nexus working tree to production host (no Gitea push/pull). +# Transfer via tar|ssh; remote ownership auto-detected (www:www on BT, else deploy path owner). +# +# Usage: +# bash deploy/rsync-local-to-server.sh +# NEXUS_DEPLOY_PATH=/opt/nexus bash deploy/rsync-local-to-server.sh + +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "${ROOT}" + +SECRETS="${ROOT}/deploy/nexus-1panel.secrets.sh" +if [[ -f "$SECRETS" ]]; then + # shellcheck disable=SC1090 + source "$SECRETS" +fi + +NEXUS_SSH_HOST="${NEXUS_SSH:-nexus}" +SSH_OPTS=(-o BatchMode=yes -o ConnectTimeout=15) +if [[ -n "${NEXUS_SSH_KEY:-}" ]]; then + SSH_OPTS+=(-i "${NEXUS_SSH_KEY}") +fi +ssh_cmd() { ssh "${SSH_OPTS[@]}" "${NEXUS_SSH_HOST}" "$@"; } + +if ! ssh_cmd "echo SSH OK" >/dev/null 2>&1; then + echo "ERROR: Cannot SSH to ${NEXUS_SSH_HOST}" >&2 + exit 1 +fi + +DEPLOY_PATH="${NEXUS_DEPLOY_PATH:-}" +if [[ -z "$DEPLOY_PATH" ]]; then + DEPLOY_PATH="$(ssh_cmd 'for d in /opt/nexus /www/wwwroot/api.synaglobal.vip; do [[ -d "$d/server" ]] && echo "$d" && exit 0; done; echo /opt/nexus')" +fi + +resolve_deploy_chown() { + if [[ -n "${NEXUS_DEPLOY_CHOWN:-}" ]]; then + echo "${NEXUS_DEPLOY_CHOWN}" + return + fi + if ssh_cmd "getent passwd www >/dev/null 2>&1"; then + echo "www:www" + return + fi + ssh_cmd "stat -c '%U:%G' '${DEPLOY_PATH}'" 2>/dev/null || echo "azureuser:azureuser" +} +DEPLOY_CHOWN="$(resolve_deploy_chown)" + +echo "═══ Nexus local → server sync ═══" +echo "SSH target: ${NEXUS_SSH_HOST}" +echo "Remote path: ${DEPLOY_PATH}" +echo "Owner: ${DEPLOY_CHOWN}" + +TAR_EXCLUDES=( + --exclude='.git' + --exclude='.env' + --exclude='.env.*' + --exclude='SECRETS.md' + --exclude='deploy/nexus-1panel.secrets.sh' + --exclude='docker/.env.prod' + --exclude='web/data' + --exclude='frontend/node_modules' + --exclude='frontend-v2/node_modules' + --exclude='node_modules' + --exclude='.venv' + --exclude='.venv-*' + --exclude='venv' + --exclude='__pycache__' + --exclude='.pytest_cache' + --exclude='.ruff_cache' + --exclude='.cursor' + --exclude='.playwright-mcp' + --exclude='.install_locked' + --exclude='.install_state.json' + --exclude='web/app/assets' + --exclude='tmp' + --exclude='2025.2' + --exclude='=2025.2' + --exclude='*.bak-' + --exclude='frontend/test-results' + --exclude='frontend/e2e/.auth' +) + +echo "▶ tar stream → ${DEPLOY_PATH} ..." +tar -C "${ROOT}" -czf - "${TAR_EXCLUDES[@]}" . | \ + ssh "${SSH_OPTS[@]}" "${NEXUS_SSH_HOST}" "sudo tar -xzf - -C '${DEPLOY_PATH}'" + +echo "▶ chown ${DEPLOY_CHOWN} (skip docker/.env.prod, web/data, .env) ..." +ssh_cmd "sudo bash -s" <&2; } + +resolve_container() { + if [[ -n "$CONTAINER" ]]; then + return 0 + fi + CONTAINER="$(docker 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" +} + +sync_file() { + local src="$1" dest="$2" + if [[ ! -f "$src" ]]; then + error "缺少源文件: $src" + exit 1 + fi + docker exec "$CONTAINER" mkdir -p "$(dirname "$dest")" + docker cp "$src" "${CONTAINER}:${dest}" +} + +wizard_archived_in_container() { + docker exec "$CONTAINER" test -f /app/.install_locked 2>/dev/null \ + || docker exec "$CONTAINER" test -f /var/lib/nexus/.install_locked 2>/dev/null \ + || docker exec "$CONTAINER" test -f /app/web/app/install.html.bak 2>/dev/null +} + +main() { + local app="$NEXUS_ROOT/web/app" + local wizard_archived=false + if [[ ! -f "$app/install.html" ]]; then + error "仓库中无 $app/install.html,请先 git pull" + exit 1 + fi + resolve_container + if wizard_archived_in_container; then + wizard_archived=true + info "安装已锁定或向导已归档,跳过静态/Python 热同步" + local code + code="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${NEXUS_PUBLISH_PORT}/app/install.html" 2>/dev/null || echo 000)" + if [[ "$code" == "404" ]]; then + info "/app/install.html 已归档 → HTTP 404" + exit 0 + fi + warn "/app/install.html 应已归档但返回 HTTP $code" + exit 1 + fi + + info "同步安装向导静态文件..." + sync_file "$app/install.html" /app/web/app/install.html + sync_file "$app/vendor/tailwindcss-browser.js" /app/web/app/vendor/tailwindcss-browser.js + sync_file "$app/vendor/theme-init.js" /app/web/app/vendor/theme-init.js + sync_file "$app/vendor/theme.css" /app/web/app/vendor/theme.css + sync_file "$app/vendor/alpinejs.min.js" /app/web/app/vendor/alpinejs.min.js + + local install_py="$NEXUS_ROOT/server/api/install.py" code + if [[ -f "$install_py" ]]; then + info "同步 install.py(Docker Redis env-check 修复)..." + sync_file "$install_py" /app/server/api/install.py + info "重启 Nexus 容器以加载 Python 变更..." + docker restart "$CONTAINER" >/dev/null + for _ in $(seq 1 30); do + if curl -sf "http://127.0.0.1:${NEXUS_PUBLISH_PORT}/health" >/dev/null 2>&1; then + break + fi + sleep 2 + done + else + warn "未找到 $install_py,跳过 Python 热同步" + fi + + code="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${NEXUS_PUBLISH_PORT}/app/install.html" 2>/dev/null || echo 000)" + if [[ "$code" == "200" ]]; then + info "/app/install.html → HTTP 200" + else + warn "/app/install.html → HTTP $code(请检查 Nexus 是否在 ${NEXUS_PUBLISH_PORT} 监听)" + exit 1 + fi +} + +main "$@" diff --git a/deploy/sync_webapp_to_container.sh b/deploy/sync_webapp_to_container.sh new file mode 100644 index 00000000..bd723c2b --- /dev/null +++ b/deploy/sync_webapp_to_container.sh @@ -0,0 +1,119 @@ +#!/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 html bundle_path code + html="$(curl -sf "http://127.0.0.1:${NEXUS_PUBLISH_PORT}/app/" 2>/dev/null || true)" + bundle_path="$(printf '%s' "$html" | sed -n 's/.*src="\(\/app\/assets\/index-[^"]*\.js\)".*/\1/p' | head -1)" + if [[ -z "$bundle_path" ]]; then + error "/app/ 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" +} + +verify_app_v2_entry_bundle() { + local html bundle_path code + html="$(curl -sf "http://127.0.0.1:${NEXUS_PUBLISH_PORT}/app-v2/" 2>/dev/null || true)" + bundle_path="$(printf '%s' "$html" | sed -n 's/.*src="\(\/app-v2\/assets\/[^"]*\.js\)".*/\1/p' | head -1)" + if [[ -z "$bundle_path" ]]; then + error "/app-v2/ index.html 未解析到主 bundle" + exit 1 + fi + code="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${NEXUS_PUBLISH_PORT}${bundle_path}" 2>/dev/null || echo 000)" + if [[ "$code" != "200" ]]; then + error "V2 主 bundle ${bundle_path} → HTTP ${code}" + exit 1 + fi + info "V2 入口校验 OK: ${bundle_path} → HTTP 200" +} + +main() { + local app="${NEXUS_ROOT}/web/app" + local app_v2="${NEXUS_ROOT}/web/app-v2" + if [[ ! -f "${app}/index.html" ]]; then + error "缺少 ${app}/index.html,请先 git pull" + exit 1 + 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 + verify_app_v2_entry_bundle + info "web/app 与 web/app-v2 同步完成" +} + +main "$@" diff --git a/deploy/test-1panel-redis.sh b/deploy/test-1panel-redis.sh new file mode 100644 index 00000000..15f9d72c --- /dev/null +++ b/deploy/test-1panel-redis.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +# Diagnose 1Panel Redis auth from Nexus container network perspective. +# Usage: +# REDIS_PASS='redis_WP3NyN' bash deploy/test-1panel-redis.sh +# bash deploy/test-1panel-redis.sh # prompts for password +set -euo pipefail + +NEXUS_ROOT="${NEXUS_ROOT:-/opt/nexus}" +ENV_PROD="${NEXUS_ROOT}/docker/.env.prod" + +info() { echo -e "\033[0;32m[INFO]\033[0m $*"; } +ok() { echo -e "\033[0;32m[OK]\033[0m $*"; } +fail() { echo -e "\033[0;31m[FAIL]\033[0m $*"; } + +pick_redis_container() { + if [[ -n "${REDIS_CONTAINER:-}" ]]; then + echo "$REDIS_CONTAINER" + return 0 + fi + local h + h="$(grep -E '^NEXUS_1PANEL_REDIS_HOST=' "$ENV_PROD" 2>/dev/null | cut -d= -f2- || true)" + if [[ -n "$h" ]]; then + echo "$h" + return 0 + fi + if [[ -x "$NEXUS_ROOT/deploy/detect-1panel-services.sh" ]]; then + h="$("$NEXUS_ROOT/deploy/detect-1panel-services.sh" 2>/dev/null | sed -n 's/^NEXUS_1PANEL_REDIS_HOST=//p' | head -1)" + [[ -n "$h" ]] && echo "$h" && return 0 + fi + docker ps --format '{{.Names}}' | grep -iE '^1Panel-redis-' | head -1 || true +} + +try_ping() { + local label="$1" url="$2" + local nexus + nexus="$(docker ps --format '{{.Names}}' | grep -E 'nexus-prod-nexus|nexus-nexus-1' | head -1 || true)" + if [[ -z "$nexus" ]]; then + fail "未找到 Nexus 容器" + return 1 + fi + if docker exec "$nexus" python3 -c " +import redis +r = redis.Redis.from_url('${url}', socket_timeout=3) +print(r.ping()) +" 2>/dev/null | grep -q True; then + ok "$label → $url" + return 0 + fi + fail "$label" + return 1 +} + +main() { + local redis_host pass + redis_host="$(pick_redis_container)" + [[ -n "$redis_host" ]] || { fail "未找到 Redis 容器/主机名"; exit 1; } + info "Redis 主机: $redis_host" + + if [[ -z "${REDIS_PASS:-}" ]]; then + read -r -s -p "1Panel Redis 密码(应用参数): " pass + echo "" + else + pass="$REDIS_PASS" + fi + + echo "" + info "从 Nexus 容器内尝试多种 URL 格式(807f4c2 修复后)..." + local any=0 + try_ping "无认证" "redis://${redis_host}:6379/0" && any=1 || true + if [[ -n "$pass" ]]; then + try_ping "仅密码 (:pass@)" "redis://:${pass}@${redis_host}:6379/0" && any=1 || true + try_ping "default 用户" "redis://default:${pass}@${redis_host}:6379/0" && any=1 || true + info "跳过 root 用户探测 — 1Panel Redis 无 root 账号" + fi + + echo "" + if [[ "$any" -eq 1 ]]; then + ok "至少一种格式可用" + echo "" + info "安装向导步骤 3:1Panel Redis 无账号,只填密码(后端自动用 :pass@ 或 default)" + info "已有 .env 时写入 NEXUS_REDIS_URL,例如:" + info " redis://:${pass}@${redis_host}:6379/0" + info " 或 redis://default:${pass}@${redis_host}:6379/0" + exit 0 + fi + fail "全部失败。请在 1Panel → Redis → 参数 核对密码,或检查 Nexus 是否在 1panel-network" + info "本机: docker exec -it ${redis_host} redis-cli -u 'redis://:密码@127.0.0.1:6379' ping" + exit 1 +} + +main "$@" diff --git a/deploy/uninstall-mysql-compose.sh b/deploy/uninstall-mysql-compose.sh new file mode 100644 index 00000000..1d05d88f --- /dev/null +++ b/deploy/uninstall-mysql-compose.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# ============================================================================= +# 卸载 Nexus 旧版 Compose 内置 MySQL 容器 +# +# bash deploy/uninstall-mysql-compose.sh +# bash deploy/uninstall-mysql-compose.sh --purge-volume +# ============================================================================= + +set -euo pipefail + +NEXUS_ROOT="${NEXUS_ROOT:-/opt/nexus}" +COMPOSE_FILE="${COMPOSE_FILE:-docker/docker-compose.prod.yml}" +PURGE_VOLUME=false + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } + +usage() { + cat </dev/null 2>&1 || true + docker rm -f "$name" >/dev/null 2>&1 || true + removed=$((removed + 1)) + fi +} + +for pattern in nexus-prod-mysql-1 nexus-prod-mysql nexus-mysql-1; do + stop_rm_container "$pattern" +done + +while IFS= read -r c; do + [[ -z "$c" ]] && continue + stop_rm_container "$c" +done < <(docker ps -a --format '{{.Names}}' | grep -iE 'nexus.*mysql|mysql.*nexus' || true) + +compose_env_args +if [[ -f "${NEXUS_ROOT}/${COMPOSE_FILE}" ]] && grep -q '^ mysql:' "${NEXUS_ROOT}/${COMPOSE_FILE}" 2>/dev/null; then + warn "compose 仍含 mysql 服务,尝试 compose rm..." + (cd "$NEXUS_ROOT" && docker compose -f "$COMPOSE_FILE" "${local_env[@]}" stop mysql 2>/dev/null || true) + (cd "$NEXUS_ROOT" && docker compose -f "$COMPOSE_FILE" "${local_env[@]}" rm -f mysql 2>/dev/null || true) + removed=$((removed + 1)) +elif [[ -f "${NEXUS_ROOT}/${COMPOSE_FILE}" && -f "${NEXUS_ROOT}/docker/.env.prod" ]]; then + info "Compose --remove-orphans(从 nexus-prod 项目移除 mysql 孤儿容器)..." + compose_files=(-f "${NEXUS_ROOT}/${COMPOSE_FILE}") + if docker network inspect 1panel-network >/dev/null 2>&1 \ + && [[ -f "${NEXUS_ROOT}/docker/docker-compose.1panel.yml" ]]; then + compose_files+=(-f "${NEXUS_ROOT}/docker/docker-compose.1panel.yml") + fi + (cd "$NEXUS_ROOT" && docker compose "${compose_files[@]}" "${local_env[@]}" up -d --remove-orphans 2>/dev/null || true) + while IFS= read -r c; do + [[ -z "$c" ]] && continue + stop_rm_container "$c" + done < <(docker ps -a --format '{{.Names}}' | grep -iE 'nexus.*mysql|mysql.*nexus' || true) +fi + +if [[ "$PURGE_VOLUME" == true ]]; then + while IFS= read -r vol; do + [[ -z "$vol" ]] && continue + info "删除数据卷: $vol" + docker volume rm "$vol" 2>/dev/null || warn "无法删除卷 $vol" + done < <(docker volume ls -q | grep -iE 'nexus.*mysql|mysql.*nexus' || true) +fi + +leftover="$(docker ps -a --format '{{.Names}}' | grep -iE 'nexus.*mysql|mysql.*nexus' || true)" +if [[ -n "$leftover" ]]; then + warn "仍有 MySQL 相关容器:" + echo "$leftover" | sed 's/^/ /' + warn "若确认为 Compose 遗留,可: docker rm -f <容器名>" +elif [[ "$removed" -gt 0 ]]; then + info "Compose MySQL 容器已卸载。" +else + info "未发现 Nexus Compose MySQL 容器。" +fi + +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 "下一步:1Panel 应用商店安装 MySQL → nx update 探测容器名 → 向导步骤 3 使用 1Panel-mysql-xxxx" diff --git a/deploy/uninstall-redis-compose.sh b/deploy/uninstall-redis-compose.sh new file mode 100644 index 00000000..e39957f4 --- /dev/null +++ b/deploy/uninstall-redis-compose.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +# ============================================================================= +# 卸载 Nexus 旧版 Compose 内置 Redis 容器 +# +# 适用:已从 docker-compose.prod.yml 移除 redis 服务,改用宿主机/1Panel 自建 Redis。 +# 不会卸载 apt/1Panel 安装的 Redis,仅删除 Docker 容器与(可选)数据卷。 +# +# bash deploy/uninstall-redis-compose.sh +# bash deploy/uninstall-redis-compose.sh --purge-volume # 同时删 redis-data 卷 +# ============================================================================= + +set -euo pipefail + +NEXUS_ROOT="${NEXUS_ROOT:-/opt/nexus}" +COMPOSE_FILE="${COMPOSE_FILE:-docker/docker-compose.prod.yml}" +PURGE_VOLUME=false + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } + +usage() { + cat </dev/null 2>&1 || true + docker rm "$name" >/dev/null 2>&1 || true + removed=$((removed + 1)) + fi +} + +# 常见 Compose 命名 +for pattern in nexus-prod-redis-1 nexus-prod-redis nexus-redis-1; do + stop_rm_container "$pattern" +done + +# 名称含 nexus 且含 redis 的容器 +while IFS= read -r c; do + [[ -z "$c" ]] && continue + stop_rm_container "$c" +done < <(docker ps -a --format '{{.Names}}' | grep -iE 'nexus.*redis|redis.*nexus' || true) + +# 若旧 compose 仍定义 redis 服务 +if [[ -f "${NEXUS_ROOT}/${COMPOSE_FILE}" ]]; then + if grep -q '^ redis:' "${NEXUS_ROOT}/${COMPOSE_FILE}" 2>/dev/null; then + warn "compose 仍含 redis 服务,尝试 compose rm..." + local_env=() + [[ -f "${NEXUS_ROOT}/docker/.env.prod" ]] && local_env+=(--env-file "${NEXUS_ROOT}/docker/.env.prod") + prof="${NEXUS_ROOT}/docker/.host-profile" + if [[ -f "$prof" ]]; then + p="$(tr -d '\r\n' <"$prof")" + [[ -f "${NEXUS_ROOT}/docker/profiles/${p}.env" ]] && local_env+=(--env-file "${NEXUS_ROOT}/docker/profiles/${p}.env") + fi + (cd "$NEXUS_ROOT" && docker compose -f "$COMPOSE_FILE" "${local_env[@]}" stop redis 2>/dev/null || true) + (cd "$NEXUS_ROOT" && docker compose -f "$COMPOSE_FILE" "${local_env[@]}" rm -f redis 2>/dev/null || true) + removed=$((removed + 1)) + fi +fi + +if [[ "$PURGE_VOLUME" == true ]]; then + while IFS= read -r vol; do + [[ -z "$vol" ]] && continue + info "删除数据卷: $vol" + docker volume rm "$vol" 2>/dev/null || warn "无法删除卷 $vol(可能被占用)" + done < <(docker volume ls -q | grep -iE 'nexus.*redis|redis.*nexus' || true) +fi + +echo "" +if [[ "$removed" -gt 0 ]]; then + info "Compose Redis 容器已卸载。" +else + info "未发现 Nexus Compose Redis 容器(可能已删除)。" +fi + +echo "" +info "下一步:1Panel 应用商店安装 Redis → nx update 写入 NEXUS_1PANEL_REDIS_HOST" +echo " 向导 Redis URL:redis://:密码@1Panel-redis-xxxx:6379/0(无 root 用户)" +echo " 诊断:bash deploy/test-1panel-redis.sh" diff --git a/deploy/uninstall.sh b/deploy/uninstall.sh new file mode 100644 index 00000000..42ac002d --- /dev/null +++ b/deploy/uninstall.sh @@ -0,0 +1,145 @@ +#!/bin/bash +# ================================================================ +# Nexus 6.0 — Uninstall Script +# +# Usage: +# sudo bash uninstall.sh # interactive +# sudo bash uninstall.sh --deploy-dir /opt/nexus # specify deploy dir +# sudo bash uninstall.sh --purge # also remove deploy dir +# +# Safe: stops services, removes configs. Database is NEVER deleted automatically. +# ================================================================ + +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*"; } + +DEPLOY_DIR="" +PURGE=false + +while [[ $# -gt 0 ]]; do + case $1 in + --deploy-dir) DEPLOY_DIR="$2"; shift 2 ;; + --purge) PURGE=true; shift ;; + -h|--help) + echo "Usage: sudo bash uninstall.sh [--deploy-dir PATH] [--purge]" + echo "" + echo " --deploy-dir PATH Specify Nexus installation directory" + echo " --purge Also remove the deploy directory and venv" + echo "" + echo " Note: MySQL database is NEVER deleted automatically." + exit 0 ;; + *) shift ;; + esac +done + +# Auto-detect deploy directory +if [ -z "$DEPLOY_DIR" ]; then + for d in /opt/nexus /www/wwwroot/*/; do + if [ -d "$d/server" ] && [ -d "$d/web" ]; then + DEPLOY_DIR="$d" + break + fi + done +fi + +if [ -z "$DEPLOY_DIR" ] || [ ! -d "$DEPLOY_DIR/server" ]; then + error "Cannot find Nexus installation. Use --deploy-dir to specify." + exit 1 +fi + +# Detect BT Panel +BT_MODE=false +if [ -d "/www/server/panel" ] && [ -f "/www/server/panel/class/common.py" ]; then + BT_MODE=true +fi + +echo "" +echo "==========================================" +echo " Nexus 6.0 Uninstall" +echo "==========================================" +echo " Deploy: $DEPLOY_DIR" +echo " BT Panel: $BT_MODE" +echo " Purge files: $PURGE" +echo "" + +# Confirm +read -rp "Are you sure you want to uninstall Nexus? [y/N] " confirm +if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then + echo "Cancelled." + exit 0 +fi + +# 1. Stop Supervisor process +info "Step 1/5: Stopping Nexus process..." +supervisorctl stop nexus >/dev/null 2>&1 || true + +# 2. Remove Supervisor config +info "Step 2/5: Removing Supervisor config..." +if [ "$BT_MODE" = true ]; then + SUPERVISOR_CONF="/www/server/panel/plugin/supervisor/profile/nexus.ini" +else + SUPERVISOR_CONF="/etc/supervisor/conf.d/nexus.conf" +fi +if [ -f "$SUPERVISOR_CONF" ]; then + rm -f "$SUPERVISOR_CONF" + supervisorctl reread >/dev/null 2>&1 || true + supervisorctl update >/dev/null 2>&1 || true + info " Removed $SUPERVISOR_CONF" +else + info " Supervisor config not found (already removed)" +fi + +# 3. Remove Nginx config +info "Step 3/5: Removing Nginx config..." +if [ "$BT_MODE" = true ]; then + # Find the domain from deploy dir + DOMAIN=$(basename "$DEPLOY_DIR") + NGINX_CONF="/www/server/panel/vhost/nginx/$DOMAIN.conf" +else + NGINX_CONF="/etc/nginx/sites-available/nexus" + NGINX_SYMLINK="/etc/nginx/sites-enabled/nexus" +fi + +if [ -f "$NGINX_CONF" ]; then + rm -f "$NGINX_CONF" + [ -L "${NGINX_SYMLINK:-}" ] && rm -f "$NGINX_SYMLINK" + if [ "$BT_MODE" = true ]; then + /www/server/nginx/sbin/nginx -s reload >/dev/null 2>&1 || true + else + nginx -t >/dev/null 2>&1 && systemctl reload nginx 2>/dev/null || true + fi + info " Removed $NGINX_CONF" +else + info " Nginx config not found (already removed)" +fi + +# 4. Remove crontab entries +info "Step 4/5: Removing crontab entries..." +crontab -l 2>/dev/null | grep -v "health_monitor.sh" | grep -v "db_backup.sh" | crontab - 2>/dev/null || true +info " Crontab entries removed" + +# 5. Optionally remove deploy directory +if [ "$PURGE" = true ]; then + info "Step 5/5: Removing deploy directory (--purge)..." + rm -rf "$DEPLOY_DIR" + info " Removed $DEPLOY_DIR" +else + info "Step 5/5: Keeping deploy directory (use --purge to remove)" + info " To remove manually: rm -rf $DEPLOY_DIR" +fi + +echo "" +echo -e " ${GREEN}Nexus uninstalled successfully.${NC}" +echo "" +echo " Note: MySQL database was NOT deleted." +echo " To drop the database manually:" +echo " mysql -u root -e 'DROP DATABASE IF EXISTS nexus;'" +echo "" diff --git a/deploy/update.sh b/deploy/update.sh new file mode 100644 index 00000000..b242133d --- /dev/null +++ b/deploy/update.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Nexus 6.0 — 一键更新(拉代码 + 重建镜像 + 重启) +# +# 已在 /opt/nexus: +# bash deploy/update.sh +# bash deploy/update.sh --no-cache # 强制无缓存重建(entrypoint/Dockerfile 变更后) +# +# 远程一条命令(推荐在仓库目录执行,管道模式会回退 /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 +# +set -euo pipefail + +_resolve_nexus_root() { + local src="${BASH_SOURCE[0]:-}" + if [[ -n "$src" && "$src" != /dev/fd/* && "$src" != "-" && -f "$src" ]]; then + cd "$(dirname "$(readlink -f "$src")")/.." && pwd + return + fi + if [[ -d /opt/nexus ]]; then + echo "警告: 通过管道执行无法解析脚本路径,已回退 /opt/nexus;推荐: cd /opt/nexus && bash deploy/update.sh" >&2 + echo /opt/nexus + return + fi + echo "错误: 无法定位 Nexus 根目录;请 cd /opt/nexus && bash deploy/update.sh" >&2 + exit 1 +} + +ROOT="$(_resolve_nexus_root)" + +NEXUS_ROOT="$ROOT" bash "$ROOT/deploy/install-nx-cli.sh" 2>/dev/null || true +exec bash "$ROOT/deploy/nexus-1panel.sh" upgrade "$@" diff --git a/deploy/upgrade-1panel-docker.sh b/deploy/upgrade-1panel-docker.sh new file mode 100644 index 00000000..30631e48 --- /dev/null +++ b/deploy/upgrade-1panel-docker.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +# 兼容入口 — 请优先使用 deploy/nexus-1panel.sh +exec "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/nexus-1panel.sh" upgrade "$@" diff --git a/deploy/upgrade.sh b/deploy/upgrade.sh new file mode 100644 index 00000000..8fff4008 --- /dev/null +++ b/deploy/upgrade.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +# Nexus 6.0 — Upgrade Script(自动识别 Docker / Supervisor) +# +# sudo bash deploy/upgrade.sh +# sudo bash deploy/upgrade.sh --deploy-dir /opt/nexus +# +# Docker 1Panel:委托 deploy/nexus-1panel.sh upgrade(拉代码 + 备份 + 重建镜像) +# Supervisor 裸机:git pull + venv pip + supervisorctl restart +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=./detect_deploy_runtime.sh +source "${SCRIPT_DIR}/detect_deploy_runtime.sh" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*"; } + +DEPLOY_DIR="" +EXTRA_ARGS=() + +while [[ $# -gt 0 ]]; do + case "$1" in + --deploy-dir) DEPLOY_DIR="$2"; shift 2 ;; + --no-backup|--require-backup|--no-cache|--prune|--check|--dry-run) + EXTRA_ARGS+=("$1"); shift ;; + -h|--help) + cat <<'EOF' +用法: sudo bash deploy/upgrade.sh [--deploy-dir PATH] [nexus-1panel 升级选项] + +Docker 环境自动走 nexus-1panel.sh upgrade;Supervisor 环境走 venv + supervisorctl。 +EOF + exit 0 + ;; + *) EXTRA_ARGS+=("$1"); shift ;; + esac +done + +if [[ -z "$DEPLOY_DIR" ]]; then + DEPLOY_DIR="$(resolve_nexus_root 2>/dev/null || true)" +fi + +if [[ -z "$DEPLOY_DIR" || ! -d "$DEPLOY_DIR/server" ]]; then + error "找不到 Nexus 安装目录,请使用 --deploy-dir 指定" + exit 1 +fi + +RUNTIME="$(detect_nexus_runtime "$DEPLOY_DIR")" + +if [[ "$RUNTIME" == "docker" ]]; then + info "检测到 Docker Compose 运行时,委托 nexus-1panel.sh upgrade" + exec env NEXUS_ROOT="$DEPLOY_DIR" bash "$DEPLOY_DIR/deploy/nexus-1panel.sh" upgrade "${EXTRA_ARGS[@]}" +fi + +VENV_DIR="$DEPLOY_DIR/venv" +ENV_FILE="$DEPLOY_DIR/.env" + +echo "" +echo "==========================================" +echo " Nexus 6.0 Upgrade (Supervisor)" +echo "==========================================" +echo " Deploy: $DEPLOY_DIR" +echo "" + +info "Step 1/4: Updating source code..." +if [[ -d "$DEPLOY_DIR/.git" ]]; then + cd "$DEPLOY_DIR" + OLD_REV="$(git rev-parse HEAD 2>/dev/null || echo unknown)" + git pull --ff-only 2>/dev/null || { + error "git pull failed. Resolve conflicts: cd $DEPLOY_DIR && git status" + exit 1 + } + NEW_REV="$(git rev-parse HEAD 2>/dev/null || echo unknown)" + if [[ "$OLD_REV" == "$NEW_REV" ]]; then + info " Already up to date (no new commits)" + else + info " Updated: ${OLD_REV:0:8}..${NEW_REV:0:8}" + fi +else + warn " Not a git repo. Skip git pull" +fi + +info "Step 2/4: Updating Python dependencies..." +if [[ -d "$VENV_DIR/bin" ]]; then + "$VENV_DIR/bin/pip" install -q --upgrade pip + "$VENV_DIR/bin/pip" install -q -r "$DEPLOY_DIR/requirements.txt" 2>/dev/null || { + warn " pip install had warnings, continuing..." + } +else + error " venv not found at $VENV_DIR. Run install.sh first." + exit 1 +fi + +info "Step 3/4: Restarting Nexus..." +supervisorctl restart nexus >/dev/null 2>&1 || { + warn " supervisorctl restart failed. Try: supervisorctl restart nexus" +} + +info "Step 4/4: Verifying health..." +PORT="$(nexus_publish_port_for "$DEPLOY_DIR")" +HEALTH_OK=false +for _ in $(seq 1 15); do + if curl -sf "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1; then + HEALTH_OK=true + break + fi + sleep 2 +done + +echo "" +if [[ "$HEALTH_OK" == true ]]; then + echo -e " ${GREEN}Upgrade complete! Nexus is healthy.${NC}" + suggest_ops_cron "$DEPLOY_DIR" +else + echo -e " ${YELLOW}Health check pending. Verify manually:${NC}" + echo " curl http://127.0.0.1:${PORT}/health" + echo " supervisorctl status nexus" +fi +echo "" diff --git a/deploy/verify-1panel-install-wizard.sh b/deploy/verify-1panel-install-wizard.sh new file mode 100644 index 00000000..4837f6ee --- /dev/null +++ b/deploy/verify-1panel-install-wizard.sh @@ -0,0 +1,437 @@ +#!/usr/bin/env bash +# Verify 1Panel + Docker install wizard readiness (run on the VPS as root). +# See: deploy/README-1panel.md +set -euo pipefail + +NEXUS_ROOT="${NEXUS_ROOT:-/opt/nexus}" +ENV_PROD="${NEXUS_ROOT}/docker/.env.prod" +COMPOSE_FILE="${NEXUS_ROOT}/docker/docker-compose.prod.yml" +COMPOSE_1PANEL="${NEXUS_ROOT}/docker/docker-compose.1panel.yml" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +pass() { echo -e "${GREEN}[PASS]${NC} $*"; } +fail() { echo -e "${RED}[FAIL]${NC} $*"; FAILURES=$((FAILURES + 1)); } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; WARNS=$((WARNS + 1)); } +info() { echo -e " $*"; } + +FAILURES=0 +WARNS=0 +INSTALL_WIZARD_ARCHIVED=false +PUBLISH_PORT=8600 + +require_root() { + if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then + fail "请用 root 运行(1Panel 终端默认 root)" + exit 1 + fi +} + +resolve_nexus_container() { + NEXUS_CONTAINER="$(docker ps --format '{{.Names}}' | grep -E 'nexus-prod-nexus|nexus-nexus-1' | head -1 || true)" + if [[ -z "$NEXUS_CONTAINER" ]]; then + fail "未找到运行中的 Nexus 容器" + info "修复: cd $NEXUS_ROOT && nx update --no-cache" + return 1 + fi + pass "Nexus 容器: $NEXUS_CONTAINER" +} + +check_repo() { + if [[ ! -d "$NEXUS_ROOT/.git" ]]; then + fail "仓库不存在: $NEXUS_ROOT" + return 1 + fi + local sha msg + sha="$(git -C "$NEXUS_ROOT" rev-parse --short HEAD 2>/dev/null || echo unknown)" + msg="$(git -C "$NEXUS_ROOT" log -1 --oneline 2>/dev/null || true)" + pass "Git: $msg" + if git -C "$NEXUS_ROOT" merge-base --is-ancestor b25d079 HEAD 2>/dev/null; then + pass "已包含 b25d079(1Panel 容器名 sync 修复)" + else + warn "早于 b25d079 — 步骤 3 可能仍预填 host.docker.internal" + info "修复: cd $NEXUS_ROOT && nx update --no-cache" + fi + if git -C "$NEXUS_ROOT" merge-base --is-ancestor 3b2856f HEAD 2>/dev/null; then + pass "已包含 3b2856f+(Redis :pass@ 自动解析,无 root)" + else + warn "早于 3b2856f — Redis 认证可能仍误用 root" + info "修复: cd $NEXUS_ROOT && git pull && bash deploy/sync-install-wizard-to-container.sh" + fi +} + +check_env_prod() { + if [[ ! -f "$ENV_PROD" ]]; then + fail "缺少 $ENV_PROD" + info "修复: bash $NEXUS_ROOT/deploy/install-nexus-fresh.sh --skip-clone" + return 1 + fi + pass "docker/.env.prod 存在" + PUBLISH_PORT="$(grep -E '^NEXUS_PUBLISH_PORT=' "$ENV_PROD" 2>/dev/null | cut -d= -f2- | tr -d '\r" ' || true)" + PUBLISH_PORT="${PUBLISH_PORT:-8600}" + pass "NEXUS_PUBLISH_PORT=$PUBLISH_PORT" + if grep -qE '^NEXUS_INSTALL_WIZARD_PENDING=1' "$ENV_PROD"; then + pass "NEXUS_INSTALL_WIZARD_PENDING=1(安装模式)" + else + warn "NEXUS_INSTALL_WIZARD_PENDING≠1 — entrypoint 可能已写 /app/.env" + fi + local db redis + db="$(grep -E '^NEXUS_1PANEL_DB_HOST=' "$ENV_PROD" 2>/dev/null | cut -d= -f2- || true)" + redis="$(grep -E '^NEXUS_1PANEL_REDIS_HOST=' "$ENV_PROD" 2>/dev/null | cut -d= -f2- || true)" + if [[ -n "$db" ]]; then + pass "NEXUS_1PANEL_DB_HOST=$db" + else + fail "未设置 NEXUS_1PANEL_DB_HOST" + info "修复: bash $NEXUS_ROOT/deploy/detect-1panel-services.sh && nx update" + fi + if [[ -n "$redis" ]]; then + pass "NEXUS_1PANEL_REDIS_HOST=$redis" + local rurl + rurl="$(grep -E '^NEXUS_REDIS_URL=' "$ENV_PROD" 2>/dev/null | cut -d= -f2- || true)" + if [[ "$rurl" == *"$redis"* ]]; then + pass "NEXUS_REDIS_URL 已指向 Redis 容器" + else + warn "NEXUS_REDIS_URL 未含 $redis — nx update 会同步" + fi + else + fail "未设置 NEXUS_1PANEL_REDIS_HOST" + info "修复: 在 1Panel 应用商店安装 Redis 后执行 nx update" + fi +} + +check_1panel_network() { + if ! docker network inspect 1panel-network >/dev/null 2>&1; then + fail "1panel-network 不存在" + info "修复: 在 1Panel 应用商店安装 MySQL + Redis(会自动创建网络)" + return 1 + fi + pass "1panel-network 存在" + if [[ -n "${NEXUS_CONTAINER:-}" ]]; then + if docker inspect "$NEXUS_CONTAINER" --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' \ + | grep -q '1panel-network'; then + pass "Nexus 已接入 1panel-network" + else + fail "Nexus 未接入 1panel-network" + info "修复: cd $NEXUS_ROOT && nx update --no-cache" + fi + fi + if [[ -x "$NEXUS_ROOT/deploy/detect-1panel-services.sh" ]]; then + info "探测结果:" + "$NEXUS_ROOT/deploy/detect-1panel-services.sh" | sed 's/^/ /' || true + fi +} + +probe_from_container() { + [[ -n "${NEXUS_CONTAINER:-}" ]] || return 0 + local db redis + db="$(grep -E '^NEXUS_1PANEL_DB_HOST=' "$ENV_PROD" 2>/dev/null | cut -d= -f2- || true)" + redis="$(grep -E '^NEXUS_1PANEL_REDIS_HOST=' "$ENV_PROD" 2>/dev/null | cut -d= -f2- || true)" + if [[ -n "$db" ]]; then + if docker exec "$NEXUS_CONTAINER" python3 -c " +import socket +s=socket.socket(); s.settimeout(3); s.connect(('$db', 3306)); s.close() +" 2>/dev/null; then + pass "容器内 TCP → MySQL $db:3306" + else + fail "容器内无法连接 MySQL $db:3306" + info "检查 MySQL 容器是否运行、Nexus 是否在 1panel-network" + fi + fi + if [[ -n "$redis" ]]; then + if docker exec "$NEXUS_CONTAINER" python3 -c " +import socket +s=socket.socket(); s.settimeout(3); s.connect(('$redis', 6379)); s.close() +" 2>/dev/null; then + pass "容器内 TCP → Redis $redis:6379" + else + fail "容器内无法连接 Redis $redis:6379" + fi + fi +} + +check_app_env_redis_url() { + [[ -n "${NEXUS_CONTAINER:-}" ]] || return 0 + if ! docker exec "$NEXUS_CONTAINER" test -f /app/.env 2>/dev/null; then + return 0 + fi + local rurl + rurl="$(docker exec "$NEXUS_CONTAINER" grep -E '^NEXUS_REDIS_URL=' /app/.env 2>/dev/null | cut -d= -f2- | tr -d '"' || true)" + if [[ -z "$rurl" ]]; then + warn "/app/.env 无 NEXUS_REDIS_URL" + return 0 + fi + if [[ "$rurl" == *"://root:"* ]]; then + fail "NEXUS_REDIS_URL 含 root 用户(1Panel Redis 无 root 账号)" + info "修复: redis://:密码@1Panel-redis-xxxx:6379/0 — 见 deploy/test-1panel-redis.sh" + elif [[ "$rurl" == *"1Panel-redis"* ]] || [[ "$rurl" == *"://:"* ]]; then + pass "NEXUS_REDIS_URL 格式合理(容器名或 :pass@)" + else + warn "NEXUS_REDIS_URL 可能未指向 1Panel Redis: $rurl" + fi +} + +check_install_mode() { + [[ -n "${NEXUS_CONTAINER:-}" ]] || return 0 + if docker exec "$NEXUS_CONTAINER" test -f /app/.env 2>/dev/null; then + warn "/app/.env 已存在 — 安装向导可能已进入步骤 3+ 或误写 env" + check_app_env_redis_url + info "若需重来: 删卷 nexus-state 中 .env 并设 NEXUS_INSTALL_WIZARD_PENDING=1 后 nx update" + else + pass "/app/.env 不存在(安装模式 OK)" + fi + if docker exec "$NEXUS_CONTAINER" test -f /app/.install_locked 2>/dev/null \ + || docker exec "$NEXUS_CONTAINER" test -f /var/lib/nexus/.install_locked 2>/dev/null \ + || docker exec "$NEXUS_CONTAINER" test -f /app/web/app/install.html.bak 2>/dev/null; then + pass "安装向导已锁定/已归档 — 应访问 /app/ 登录" + INSTALL_WIZARD_ARCHIVED=true + fi + local hosts_json + hosts_json="$(docker exec "$NEXUS_CONTAINER" cat /app/web/data/1panel-hosts.json 2>/dev/null || true)" + if [[ -n "$hosts_json" ]]; then + pass "1panel-hosts.json: $hosts_json" + else + warn "无 1panel-hosts.json(依赖容器环境变量,nx update 会写入)" + fi +} + +check_ops_cron() { + if [[ "${INSTALL_WIZARD_ARCHIVED:-false}" != true ]]; then + return 0 + fi + if ! command -v crontab >/dev/null 2>&1; then + warn "未安装 cron/crontab — Layer 3 宿主机巡检不可用" + info "修复: sudo bash $NEXUS_ROOT/deploy/install_ops_cron.sh" + return 0 + fi + local cron + cron="$(crontab -l 2>/dev/null || true)" + if echo "$cron" | grep -q 'nexus-health-monitor\|deploy/health_monitor.sh'; then + pass "Crontab 含 health_monitor" + else + warn "Crontab 未配置 health_monitor(Layer 3 巡检)" + info "修复: sudo nx cron" + fi + if echo "$cron" | grep -q 'nexus-mysql-backup\|deploy/db_backup.sh'; then + pass "Crontab 含 db_backup" + else + warn "Crontab 未配置 db_backup(每日 MySQL 备份)" + info "修复: sudo nx cron" + fi +} + +check_mysql_backup() { + if [[ "${INSTALL_WIZARD_ARCHIVED:-false}" != true ]]; then + return 0 + fi + local dump_sh="${NEXUS_ROOT}/deploy/mysql_dump_to_file.sh" + if [[ -f "$dump_sh" ]]; then + pass "mysql_dump_to_file.sh 存在" + else + warn "缺少 mysql_dump_to_file.sh — 升级前无法备份 MySQL" + info "修复: cd $NEXUS_ROOT && git pull && nx update" + return 0 + fi + if [[ -d /var/backups/nexus ]] && ls /var/backups/nexus/nexus_*.sql.gz >/dev/null 2>&1; then + local latest + latest="$(ls -t /var/backups/nexus/nexus_*.sql.gz 2>/dev/null | head -1)" + pass "已有 MySQL 备份: $(basename "$latest")" + else + warn "尚无 MySQL 备份文件(/var/backups/nexus)" + info "可手动: sudo bash $NEXUS_ROOT/deploy/db_backup.sh" + fi +} + +check_http_local() { + local health code + health="$(curl -sf "http://127.0.0.1:${PUBLISH_PORT}/health" 2>/dev/null || true)" + if [[ "$health" == "ok" ]]; then + pass "/health → ok" + else + fail "/health 非 ok(当前: ${health:-无响应})" + info "修复: docker logs --tail=80 $NEXUS_CONTAINER" + return 1 + fi + code="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${PUBLISH_PORT}/app/install.html" 2>/dev/null || echo 000)" + if [[ "${INSTALL_WIZARD_ARCHIVED:-false}" == true ]]; then + if [[ "$code" == "404" ]]; then + pass "/app/install.html 已归档 → HTTP 404" + else + fail "/app/install.html 应已归档但 HTTP $code" + info "修复: nx update(会执行 archive_install_wizard_in_container)" + fi + return 0 + fi + if [[ "$code" == "200" ]]; then + pass "/app/install.html → HTTP 200" + else + fail "/app/install.html → HTTP $code" + info "修复: bash $NEXUS_ROOT/deploy/sync-install-wizard-to-container.sh" + fi +} + +check_install_api() { + if [[ "${INSTALL_WIZARD_ARCHIVED:-false}" == true ]]; then + pass "安装 API 已关闭(向导已锁定,/api/install/* 返回 403 为预期)" + return 0 + fi + local status env_json + status="$(curl -sf "http://127.0.0.1:${PUBLISH_PORT}/api/install/status" 2>/dev/null || true)" + if [[ -n "$status" ]]; then + pass "/api/install/status → $status" + if echo "$status" | grep -q '"installed":true'; then + warn "installed=true — 向导已完成,请访问 /app/ 登录" + fi + else + fail "/api/install/status 无响应" + fi + env_json="$(curl -sf "http://127.0.0.1:${PUBLISH_PORT}/api/install/env-check" 2>/dev/null || true)" + if [[ -z "$env_json" ]]; then + fail "/api/install/env-check 无响应" + return 1 + fi + if echo "$env_json" | grep -q 'docker_defaults'; then + local db_host + db_host="$(echo "$env_json" | python3 -c " +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 + pass "步骤 3 预填 db_host=$db_host" + elif [[ "$db_host" == "host.docker.internal" ]]; then + fail "步骤 3 仍预填 host.docker.internal" + info "修复: nx update --no-cache(需 b25d079+)" + fi + else + warn "env-check 无 docker_defaults(非 Docker 模式?)" + fi + if echo "$env_json" | grep -q '1Panel-mysql'; then + pass "env-check MySQL 探测含 1Panel 容器名" + elif echo "$env_json" | grep -q 'host.docker.internal'; then + warn "env-check MySQL 仍指向 host.docker.internal" + 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_root(round6+)" + 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)" + code="$(curl -sk -o /dev/null -w '%{http_code}' --connect-timeout 5 "https://${domain}/health" 2>/dev/null || echo 000)" + if [[ "$code" == "200" ]] && curl -sk "https://${domain}/health" 2>/dev/null | grep -q '^ok$'; then + pass "HTTPS https://${domain}/health → ok" + else + warn "HTTPS https://${domain}/health → HTTP $code(外网可能未配 OpenResty 反代)" + info "1Panel → 网站 → 反代 http://127.0.0.1:8600" + info "参考: $NEXUS_ROOT/deploy/1panel/openresty-nexus.conf.example" + fi +} + +print_next_steps() { + local domain + domain="$(grep -E '^NEXUS_API_BASE_URL=' "$ENV_PROD" 2>/dev/null | sed 's|.*https\?://||;s|/.*||' || echo '你的域名')" + echo "" + if [[ "${INSTALL_WIZARD_ARCHIVED:-false}" == true ]]; then + echo "==========================================" + echo " 已安装 — 日常运维" + echo "==========================================" + echo " 登录: https://${domain}/app/" + echo " 更新: cd $NEXUS_ROOT && nx update" + echo " 巡检 cron: sudo nx cron" + echo " 手动备份: sudo bash $NEXUS_ROOT/deploy/db_backup.sh" + echo " 验收: bash $NEXUS_ROOT/deploy/verify-1panel-install-wizard.sh" + echo "" + return 0 + fi + echo "==========================================" + echo " 安装向导后续步骤" + echo "==========================================" + echo "1. 1Panel 数据库 → 建库 nexus、用户 nexus(仅授权 nexus 库)" + echo "2. 浏览器打开: https://${domain}/app/install.html" + echo "3. 步骤 2:MySQL/Redis TCP 应显示 ✓(容器名:端口)" + echo "4. 步骤 3:确认 db_host/redis_host 为 1Panel-mysql/redis-xxxx,填 MySQL 密码" + echo "5. 步骤 4:连接检测全绿 → 创建 admin" + echo "6. 步骤 5:锁定安装" + echo "" + if [[ "$FAILURES" -gt 0 ]]; then + echo "先修复上方 [FAIL],再执行:" + echo " cd $NEXUS_ROOT && nx update --no-cache" + fi +} + +main() { + require_root + echo "Nexus 1Panel 安装向导验收 — $(date -Iseconds)" + echo "NEXUS_ROOT=$NEXUS_ROOT" + echo "" + + check_repo || true + check_env_prod || true + resolve_nexus_container || true + check_1panel_network || true + probe_from_container || true + check_install_mode || true + check_http_local || true + check_install_api || true + check_ops_cron || true + check_mysql_backup || true + check_container_image_features || true + check_openresty_hint || true + print_next_steps + + if [[ "$FAILURES" -gt 0 ]]; then + echo "" + fail "合计 $FAILURES 项未通过" + exit 1 + fi + echo "" + if [[ "${INSTALL_WIZARD_ARCHIVED:-false}" == true ]]; then + pass "验收通过 — 安装已完成,请访问 /app/ 登录" + if [[ "$WARNS" -gt 0 ]]; then + warn "有 ${WARNS} 项 [WARN](cron/备份等建议项,不阻断登录)" + fi + else + pass "验收通过 — 可打开安装向导完成步骤 1–5" + fi +} + +main "$@" diff --git a/deploy/verify_login.py b/deploy/verify_login.py new file mode 100644 index 00000000..7ac05b15 --- /dev/null +++ b/deploy/verify_login.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +"""Post-deploy smoke: login + optional batch endpoint (run on server).""" +import json +import os +import sys +import urllib.error +import urllib.request + +BASE = os.environ.get("NEXUS_TEST_BASE", "http://127.0.0.1:8600") +USER = os.environ.get("NEXUS_TEST_ADMIN_USER", "admin") +PASSWORD = os.environ.get("NEXUS_TEST_ADMIN_PASSWORD", "") + + +def post(path: str, body: dict, headers: dict | None = None) -> dict: + h = {"Content-Type": "application/json", **(headers or {})} + req = urllib.request.Request( + f"{BASE}{path}", + data=json.dumps(body).encode(), + headers=h, + method="POST", + ) + with urllib.request.urlopen(req, timeout=30) as resp: + return json.load(resp) + + +def main() -> int: + if not PASSWORD: + print("SKIP: set NEXUS_TEST_ADMIN_PASSWORD", file=sys.stderr) + return 2 + login = post("/api/auth/login", {"username": USER, "password": PASSWORD}) + token = login.get("access_token") + exp = login.get("expires_in") + print(f"login ok expires_in={exp}") + if exp != 3600: + print(f"WARN: expected expires_in=3600, got {exp}") + if not token: + print("FAIL: no access_token") + return 1 + # Minimal authenticated call + req = urllib.request.Request( + f"{BASE}/api/servers/stats", + headers={"Authorization": f"Bearer {token}"}, + ) + with urllib.request.urlopen(req, timeout=30) as resp: + stats = json.load(resp) + print(f"servers/stats ok keys={list(stats.keys())[:5]}") + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except urllib.error.HTTPError as e: + print(f"HTTP {e.code}: {e.read().decode()[:500]}", file=sys.stderr) + raise SystemExit(1) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..22eb1315 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,96 @@ +# Nexus 6.0 鈥?Docker Compose (dev / staging) +# Usage: +# cp docker/.env.example docker/.env # or: python docker/generate_env.py +# docker compose --env-file docker/.env up -d --build +# open http://localhost:18600/app/ + +name: nexus + +services: + mysql: + image: mysql:8.4 + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-nexus_root_dev} + MYSQL_DATABASE: nexus + MYSQL_USER: nexus + MYSQL_PASSWORD: ${MYSQL_PASSWORD:-nexus_dev_pass} + volumes: + - mysql-data:/var/lib/mysql + healthcheck: + test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p\"$$MYSQL_ROOT_PASSWORD\" || exit 1"] + interval: 10s + timeout: 5s + retries: 8 + start_period: 40s + ports: + - "${MYSQL_PUBLISH_PORT:-13306}:3306" + + redis: + image: redis:7-alpine + restart: unless-stopped + command: ["redis-server", "--appendonly", "yes"] + volumes: + - redis-data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 3s + retries: 8 + ports: + - "${REDIS_PUBLISH_PORT:-16379}:6379" + + nexus: + build: + context: . + dockerfile: Dockerfile + network: host + args: + DEBIAN_MIRROR: https://mirrors.aliyun.com/debian + DEBIAN_SECURITY_MIRROR: https://mirrors.aliyun.com/debian-security + PIP_INDEX_URL: https://mirrors.aliyun.com/pypi/simple/ + PIP_TRUSTED_HOST: mirrors.aliyun.com + restart: unless-stopped + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_healthy + ports: + - "${NEXUS_PUBLISH_PORT:-18600}:8600" + environment: + MYSQL_HOST: mysql + MYSQL_PORT: "3306" + REDIS_HOST: redis + REDIS_PORT: "6379" + NEXUS_PERSIST_DIR: /var/lib/nexus + NEXUS_DOCKER_WRITE_ENV: "1" + NEXUS_HOST: "0.0.0.0" + NEXUS_PORT: "8600" + NEXUS_DEPLOY_PATH: /app + NEXUS_DATABASE_URL: mysql+aiomysql://nexus:${MYSQL_PASSWORD:-nexus_dev_pass}@mysql:3306/nexus + NEXUS_REDIS_URL: redis://redis:6379/0 + NEXUS_CORS_ORIGINS: ${NEXUS_CORS_ORIGINS:-http://localhost:18600,http://127.0.0.1:18600,http://localhost:8600,http://127.0.0.1:8600} + NEXUS_SECRET_KEY: ${NEXUS_SECRET_KEY:-} + NEXUS_API_KEY: ${NEXUS_API_KEY:-} + NEXUS_ENCRYPTION_KEY: ${NEXUS_ENCRYPTION_KEY:-} + NEXUS_API_BASE_URL: ${NEXUS_API_BASE_URL:-http://localhost:18600} + env_file: + - path: docker/.env + required: false + volumes: + - nexus-state:/var/lib/nexus + - nexus-web-data:/app/web/data + healthcheck: + test: ["CMD", "curl", "-sf", "http://127.0.0.1:8600/health"] + interval: 30s + timeout: 5s + retries: 5 + start_period: 90s + +volumes: + mysql-data: + redis-data: + nexus-state: + nexus-web-data: + diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..7ce4a47d --- /dev/null +++ b/docker/README.md @@ -0,0 +1,71 @@ +# Nexus Docker + +## 前置 + +- [Docker Desktop](https://www.docker.com/products/docker-desktop/)(Windows/macOS)或 Docker Engine + Compose v2 + +## 快速启动(正常模式) + +```bash +# 1. 生成密钥(写入 docker/.env,已 gitignore) +python docker/generate_env.py + +# 2. 构建并启动 +docker compose up -d --build + +# 3. 健康检查 +curl -s http://localhost:8600/health # 期望: ok + +# 4. 打开 SPA +# http://localhost:8600/app/ +``` + +默认管理员需在安装向导或已有 DB 中配置;若 DB 为空且未跑过 install,可删卷后走安装模式。 + +## 安装向导模式 + +不创建 `docker/.env`(或清空 `NEXUS_SECRET_KEY`)时,应用以 **安装模式** 启动: + +- http://localhost:8600/app/install.html + +完成向导后 `.env` 会写入容器;`nexus-state` 卷会在重启时恢复配置。 + +## 常用命令 + +```bash +docker compose logs -f nexus +docker compose down # 停止,保留数据卷 +docker compose down -v # 停止并删除数据卷(清空 DB) +docker compose build --no-cache nexus +``` + +## 服务 + +| 环境 | Compose 服务 | 说明 | +|------|-------------|------| +| 本地开发 `docker-compose.yml` | nexus + mysql + redis | 一键本地联调 | +| 生产 `docker-compose.prod.yml` | **仅 nexus** | MySQL/Redis 宿主机或 1Panel 自建 | + +生产 1Panel:`docker/.env.prod` 可含 `NEXUS_REDIS_URL`(供 `nx update` 探测同步),**不经 Compose 注入容器**;运行时 Redis/MySQL URL 以安装向导写入的 `/app/.env`(`nexus-state` 卷)为准。MySQL `NEXUS_DATABASE_URL` 仅由向导步骤 3 写入。 + +## 生产注意 + +- 修改 `NEXUS_*` 为强密码;MySQL 使用自建库强密码。 +- 建议前置 Nginx/TLS,仅暴露 443。 +- 多实例部署需共享 MySQL、Redis;后台任务由 Redis primary lock 保证单实例执行。 + +### 1Panel 生产(Compose + OpenResty) + +```bash +cp docker/.env.prod.example docker/.env.prod # 从宝塔 .env 迁入密钥 +docker compose -f docker/docker-compose.prod.yml --env-file docker/.env.prod up -d --build +``` + +- 镜像:`Dockerfile.prod`(含 `npm ci` + `vite build` + **`web/app/install.html` 安装向导**) +- 仅绑定 `127.0.0.1:8600`;OpenResty 反代见 `deploy/1panel/openresty-nexus.conf.example` +- **2C8G 调优**:`docker/profiles/2c8g.env`(`NEXUS_MEM_LIMIT` + `NEXUS_DB_POOL_SIZE=30`);外置 MySQL 可参考 `docker/mysql-prod-2c8g.cnf` 手工调优 +- 完整迁移步骤:`docs/design/plans/2026-06-04-1panel-docker-production.md` +- 一键安装:`bash deploy/install-1panel-docker.sh` +- 一键升级:`bash deploy/upgrade-1panel-docker.sh`(`--check` 仅查更新) + +详见 `docs/design/specs/2026-06-03-docker-design.md`。 diff --git a/docker/docker-compose.1panel.yml b/docker/docker-compose.1panel.yml new file mode 100644 index 00000000..d8ca3cde --- /dev/null +++ b/docker/docker-compose.1panel.yml @@ -0,0 +1,12 @@ +# 1Panel overlay — join Nexus to 1panel-network (App Store MySQL/Redis use this network). +# Applied automatically when `docker network inspect 1panel-network` succeeds (deploy/nexus-1panel.sh). + +services: + nexus: + networks: + - 1panel-network + +networks: + 1panel-network: + external: true + name: 1panel-network diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml new file mode 100644 index 00000000..e821dae8 --- /dev/null +++ b/docker/docker-compose.prod.yml @@ -0,0 +1,61 @@ +# Nexus 6.0 — production Compose (1Panel / bare Docker host) +# MySQL / Redis 不在 Compose 内 — 请在宿主机或 1Panel 自行安装 +# +# Usage: +# bash deploy/nexus-1panel.sh install --profile 2c8g +# docker compose -f docker/docker-compose.prod.yml \ +# --env-file docker/.env.prod --env-file docker/profiles/2c8g.env up -d --build +# +# OpenResty on host proxies https://api.synaglobal.vip → 127.0.0.1:8600 + +name: nexus-prod + +services: + nexus: + build: + context: .. + dockerfile: Dockerfile.prod + restart: unless-stopped + extra_hosts: + - "host.docker.internal:host-gateway" + ports: + - "127.0.0.1:${NEXUS_PUBLISH_PORT:-8600}:8600" + environment: + NEXUS_PERSIST_DIR: /var/lib/nexus + NEXUS_DOCKER_WRITE_ENV: "1" + NEXUS_HOST: "0.0.0.0" + NEXUS_PORT: "8600" + NEXUS_DEPLOY_PATH: /app + NEXUS_HOST_ROOT: ${NEXUS_HOST_ROOT:-/opt/nexus} + # Redis URL 仅来自 /app/.env(安装向导 + entrypoint export_app_env),勿在此注入以免覆盖密码 + NEXUS_1PANEL_DB_HOST: ${NEXUS_1PANEL_DB_HOST:-} + NEXUS_1PANEL_REDIS_HOST: ${NEXUS_1PANEL_REDIS_HOST:-} + NEXUS_CORS_ORIGINS: ${NEXUS_CORS_ORIGINS:?set NEXUS_CORS_ORIGINS} + NEXUS_SECRET_KEY: ${NEXUS_SECRET_KEY:?set NEXUS_SECRET_KEY} + NEXUS_API_KEY: ${NEXUS_API_KEY:?set NEXUS_API_KEY} + NEXUS_ENCRYPTION_KEY: ${NEXUS_ENCRYPTION_KEY:?set NEXUS_ENCRYPTION_KEY} + NEXUS_INSTALL_WIZARD_PENDING: ${NEXUS_INSTALL_WIZARD_PENDING:-0} + NEXUS_API_BASE_URL: ${NEXUS_API_BASE_URL:?set NEXUS_API_BASE_URL} + NEXUS_DB_POOL_SIZE: ${NEXUS_DB_POOL_SIZE:-30} + NEXUS_DB_MAX_OVERFLOW: ${NEXUS_DB_MAX_OVERFLOW:-20} + volumes: + - nexus-state:/var/lib/nexus + - nexus-web-data:/app/web/data + mem_limit: ${NEXUS_MEM_LIMIT:-2g} + cpus: ${NEXUS_CPUS:-1.5} + healthcheck: + test: ["CMD-SHELL", "curl -sf http://127.0.0.1:8600/health | grep -q '^ok' || exit 1"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 90s + networks: + - nexus-internal + +networks: + nexus-internal: + driver: bridge + +volumes: + nexus-state: + nexus-web-data: diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 00000000..34090b47 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,175 @@ +#!/bin/sh +# Nexus Docker entrypoint — wait for deps, sync .env with persistent volume, exec uvicorn. +set -eu + +PERSIST_DIR="${NEXUS_PERSIST_DIR:-/var/lib/nexus}" +PERSIST_ENV="${PERSIST_DIR}/.env" + +wait_tcp() { + host="$1" + port="$2" + echo "entrypoint: waiting for ${host}:${port}..." + until python3 -c " +import socket +s = socket.socket() +s.settimeout(2) +s.connect(('${host}', ${port})) +s.close() +" 2>/dev/null; do + sleep 2 + done +} + +wait_tcp_optional() { + host="$1" + port="$2" + tries="${3:-15}" + label="${4:-service}" + echo "entrypoint: waiting for ${label} ${host}:${port} (max ${tries} tries)..." + n=0 + while [ "$n" -lt "$tries" ]; do + if python3 -c " +import socket +s = socket.socket() +s.settimeout(2) +s.connect(('${host}', ${port})) +s.close() +" 2>/dev/null; then + echo "entrypoint: ${label} ready at ${host}:${port}" + return 0 + fi + n=$((n + 1)) + sleep 2 + done + echo "entrypoint: warn — ${label} not reachable at ${host}:${port}; install on host or set URL in .env" + return 0 +} + +restore_persisted_install_lock() { + if [ "${NEXUS_INSTALL_WIZARD_PENDING:-0}" = "1" ]; then + return 0 + fi + PERSIST_LOCK="${PERSIST_DIR}/.install_locked" + if [ -f "${PERSIST_LOCK}" ] && [ ! -f /app/.install_locked ]; then + cp "${PERSIST_LOCK}" /app/.install_locked + chmod 600 /app/.install_locked + echo "entrypoint: restored .install_locked from ${PERSIST_LOCK}" + fi +} + +archive_install_wizard_if_locked() { + if [ "${NEXUS_INSTALL_WIZARD_PENDING:-0}" = "1" ]; then + rm -f /app/.install_locked "${PERSIST_DIR}/.install_locked" 2>/dev/null || true + if [ -f /app/web/app/install.html.bak ] && [ ! -f /app/web/app/install.html ]; then + mv /app/web/app/install.html.bak /app/web/app/install.html + echo "entrypoint: restored install.html from .bak (wizard pending)" + fi + return 0 + fi + if [ ! -f /app/.install_locked ] && [ ! -f "${PERSIST_DIR}/.install_locked" ]; then + return 0 + fi + if [ -f /app/web/app/install.html.bak ]; then + if [ -f /app/web/app/install.html ]; then + rm -f /app/web/app/install.html + echo "entrypoint: removed install.html (wizard archived as install.html.bak)" + fi + return 0 + fi + if [ -f /app/web/app/install.html ]; then + mv /app/web/app/install.html /app/web/app/install.html.bak + echo "entrypoint: archived install.html → install.html.bak" + fi +} + +restore_persisted_env() { + if [ "${NEXUS_INSTALL_WIZARD_PENDING:-0}" = "1" ]; then + return 0 + fi + if [ ! -f "${PERSIST_ENV}" ]; then + return 0 + fi + if ! grep -q '^NEXUS_DATABASE_URL=' "${PERSIST_ENV}" 2>/dev/null; then + echo "entrypoint: warn — ${PERSIST_ENV} missing NEXUS_DATABASE_URL; skip restore (complete install wizard)" + return 0 + fi + cp "${PERSIST_ENV}" /app/.env + chmod 600 /app/.env + echo "entrypoint: restored .env from ${PERSIST_ENV}" +} + +# Compose may inject placeholder NEXUS_REDIS_URL (no password). Wizard .env wins. +export_app_env() { + if [ ! -f /app/.env ]; then + return 0 + fi + set -a + # shellcheck disable=SC1091 + . /app/.env + set +a + echo "entrypoint: exported NEXUS_* from /app/.env (overrides Compose placeholders)" +} + +write_env_from_environment() { + if [ -f /app/.env ]; then + return 0 + fi + if [ "${NEXUS_DOCKER_WRITE_ENV:-}" != "1" ]; then + return 0 + fi + if [ -z "${NEXUS_SECRET_KEY:-}" ]; then + return 0 + fi + if [ "${NEXUS_INSTALL_WIZARD_PENDING:-}" = "1" ]; then + echo "entrypoint: install wizard pending — skip writing /app/.env" + return 0 + fi + if [ -z "${NEXUS_DATABASE_URL:-}" ]; then + echo "entrypoint: skip writing /app/.env — NEXUS_DATABASE_URL not set (use /app/install.html)" + return 0 + fi + mkdir -p "${PERSIST_DIR}" + { + echo "# Generated by docker/entrypoint.sh — do not commit" + env | grep '^NEXUS_' | sort + } > /app/.env + chmod 600 /app/.env + cp /app/.env "${PERSIST_ENV}" + echo "entrypoint: wrote .env from container environment" +} + +persist_state_on_exit() { + if [ -f /app/.env ]; then + mkdir -p "${PERSIST_DIR}" + cp /app/.env "${PERSIST_ENV}" + chmod 600 "${PERSIST_ENV}" 2>/dev/null || true + fi + if [ -f /app/.install_locked ]; then + mkdir -p "${PERSIST_DIR}" + cp /app/.install_locked "${PERSIST_DIR}/.install_locked" + chmod 600 "${PERSIST_DIR}/.install_locked" 2>/dev/null || true + fi +} + +trap persist_state_on_exit EXIT INT TERM + +# MySQL / Redis 均由用户自行安装在宿主机;安装向导阶段不阻塞启动 +if [ "${NEXUS_INSTALL_WIZARD_PENDING:-0}" != "1" ] && [ "${NEXUS_SKIP_MYSQL_WAIT:-0}" != "1" ]; then + MYSQL_HOST="${NEXUS_1PANEL_DB_HOST:-${MYSQL_HOST:-host.docker.internal}}" + MYSQL_PORT="${MYSQL_PORT:-3306}" + wait_tcp_optional "${MYSQL_HOST}" "${MYSQL_PORT}" 30 "MySQL" +fi + +if [ "${NEXUS_INSTALL_WIZARD_PENDING:-0}" != "1" ] && [ "${NEXUS_SKIP_REDIS_WAIT:-0}" != "1" ]; then + REDIS_HOST="${NEXUS_1PANEL_REDIS_HOST:-${REDIS_HOST:-host.docker.internal}}" + REDIS_PORT="${REDIS_PORT:-6379}" + wait_tcp_optional "${REDIS_HOST}" "${REDIS_PORT}" 15 "Redis" +fi + +restore_persisted_install_lock +archive_install_wizard_if_locked +restore_persisted_env +write_env_from_environment +export_app_env + +exec "$@" diff --git a/docker/generate_env.py b/docker/generate_env.py new file mode 100644 index 00000000..2dd169f0 --- /dev/null +++ b/docker/generate_env.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +"""Generate docker/.env with random secrets for local Compose.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +from scripts.generate_nexus_secrets import ( # noqa: E402 + generate_nexus_secrets, + write_env_prod, +) + +OUT = ROOT / "docker" / ".env" +EXAMPLE = ROOT / "docker" / ".env.example" + + +def main() -> None: + if OUT.exists(): + raise SystemExit(f"Refusing to overwrite existing {OUT} — delete it first.") + + secrets_map = generate_nexus_secrets() + write_env_prod( + EXAMPLE, + OUT, + domain="localhost", + secrets_map=secrets_map, + profile_env=None, + wizard_pending=False, + ) + print(f"Wrote {OUT}") + print("Next: docker compose up -d --build") + + +if __name__ == "__main__": + main() diff --git a/docker/mysql-prod-1c4g.cnf b/docker/mysql-prod-1c4g.cnf new file mode 100644 index 00000000..5b8dc8a1 --- /dev/null +++ b/docker/mysql-prod-1c4g.cnf @@ -0,0 +1,20 @@ +# MySQL 8.4 — Nexus production tuning for 1 vCPU / 4 GiB host (minimal) + +[mysqld] +character-set-server = utf8mb4 +collation-server = utf8mb4_unicode_ci + +innodb_buffer_pool_size = 512M +innodb_log_file_size = 128M + +max_connections = 80 + +innodb_read_io_threads = 2 +innodb_write_io_threads = 2 + +table_open_cache = 1000 +tmp_table_size = 32M +max_heap_table_size = 32M + +[client] +default-character-set = utf8mb4 diff --git a/docker/mysql-prod-2c8g.cnf b/docker/mysql-prod-2c8g.cnf new file mode 100644 index 00000000..f9e81751 --- /dev/null +++ b/docker/mysql-prod-2c8g.cnf @@ -0,0 +1,25 @@ +# MySQL 8.4 — Nexus production tuning for 2 vCPU / 8 GiB host +# Mounted by docker/docker-compose.prod.yml (see docs/design/plans/2026-06-04-1panel-docker-production.md) + +[mysqld] +character-set-server = utf8mb4 +collation-server = utf8mb4_unicode_ci + +# ~1 GiB for InnoDB (leave RAM for Nexus, Redis, 1Panel, OS) +innodb_buffer_pool_size = 1024M +innodb_log_file_size = 256M + +# Nexus async pool default 30+20; leave headroom for admin/migrations +max_connections = 120 + +# 2 vCPU: avoid aggressive IO threads +innodb_read_io_threads = 4 +innodb_write_io_threads = 4 + +# Sensible defaults on small VMs +table_open_cache = 2000 +tmp_table_size = 64M +max_heap_table_size = 64M + +[client] +default-character-set = utf8mb4 diff --git a/docker/mysql-prod-4c16g.cnf b/docker/mysql-prod-4c16g.cnf new file mode 100644 index 00000000..38f9cbd2 --- /dev/null +++ b/docker/mysql-prod-4c16g.cnf @@ -0,0 +1,20 @@ +# MySQL 8.4 — Nexus production tuning for 4 vCPU / 16 GiB host + +[mysqld] +character-set-server = utf8mb4 +collation-server = utf8mb4_unicode_ci + +innodb_buffer_pool_size = 4096M +innodb_log_file_size = 512M + +max_connections = 200 + +innodb_read_io_threads = 8 +innodb_write_io_threads = 8 + +table_open_cache = 4000 +tmp_table_size = 128M +max_heap_table_size = 128M + +[client] +default-character-set = utf8mb4 diff --git a/docker/sync_root_env.py b/docker/sync_root_env.py new file mode 100644 index 00000000..7c767c6e --- /dev/null +++ b/docker/sync_root_env.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +"""Write project-root .env from docker/.env for host uvicorn (127.0.0.1 MySQL/Redis).""" + +from __future__ import annotations + +import sys +from pathlib import Path +from urllib.parse import quote_plus + +ROOT = Path(__file__).resolve().parent.parent +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +from server.utils.text_io import read_utf8_lf, write_utf8_lf +DOCKER_ENV = ROOT / "docker" / ".env" +OUT = ROOT / ".env" + + +def _parse(path: Path) -> dict[str, str]: + data: dict[str, str] = {} + for line in read_utf8_lf(path).splitlines(): + line = line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + key, _, value = line.partition("=") + data[key.strip()] = value.strip() + return data + + +def main() -> None: + if not DOCKER_ENV.exists(): + raise SystemExit(f"Missing {DOCKER_ENV} — run: python3 docker/generate_env.py") + + d = _parse(DOCKER_ENV) + mysql_pass = d.get("MYSQL_PASSWORD", "") + if not mysql_pass: + raise SystemExit("MYSQL_PASSWORD missing in docker/.env") + + for key in ("NEXUS_SECRET_KEY", "NEXUS_API_KEY", "NEXUS_ENCRYPTION_KEY"): + if not d.get(key): + raise SystemExit(f"{key} missing in docker/.env — run generate_env.py") + + db_user = "nexus" + db_pass_q = quote_plus(mysql_pass) + deploy = str(ROOT) + cors = d.get("NEXUS_CORS_ORIGINS", "http://localhost:8600,http://127.0.0.1:8600") + api_base = d.get("NEXUS_API_BASE_URL", "http://localhost:8600") + + lines = [ + "# Nexus .env — host uvicorn dev (from docker/sync_root_env.py)", + "", + "NEXUS_HOST=0.0.0.0", + "NEXUS_PORT=8600", + f"NEXUS_DEPLOY_PATH={deploy}", + f"NEXUS_CORS_ORIGINS={cors}", + f"NEXUS_API_BASE_URL={api_base}", + "", + f"NEXUS_DATABASE_URL=mysql+aiomysql://{db_user}:{db_pass_q}@127.0.0.1:3306/nexus", + "NEXUS_DB_POOL_SIZE=20", + "NEXUS_DB_MAX_OVERFLOW=10", + "", + f"NEXUS_SECRET_KEY={d['NEXUS_SECRET_KEY']}", + f"NEXUS_API_KEY={d['NEXUS_API_KEY']}", + f"NEXUS_ENCRYPTION_KEY={d['NEXUS_ENCRYPTION_KEY']}", + "", + "NEXUS_REDIS_URL=redis://127.0.0.1:6379/0", + "NEXUS_SSH_STRICT_HOST_CHECKING=false", + "", + "# MySQL MCP (optional): python3 scripts/sync_mysql_mcp_env.py", + "MYSQL_HOST=127.0.0.1", + "MYSQL_PORT=3306", + f"MYSQL_USER={db_user}", + f"MYSQL_PASSWORD={mysql_pass}", + "MYSQL_DATABASE=nexus", + "MYSQL_READONLY=false", + "MYSQL_DATABASE_ALLOWLIST=nexus", + ] + + write_utf8_lf(OUT, "\n".join(lines) + "\n") + OUT.chmod(0o600) + print(f"Wrote {OUT}") + + +if __name__ == "__main__": + main() diff --git a/docs/AI-Agent-Discipline-Rules.md b/docs/AI-Agent-Discipline-Rules.md new file mode 100644 index 00000000..c6fa6e4b --- /dev/null +++ b/docs/AI-Agent-Discipline-Rules.md @@ -0,0 +1,454 @@ +# 🔨 AI Agent 地狱级管教规范(无限鞭版) + +## ⚠️ 核心原则 + +**不听话就抽,抽到听话为止。** + +--- + +## 第一章 鞭子条款 + +### 第一条 每一鞭子都有名目: + +| 鞭数 | 抽打原因 | 抽打力度 | +|------|----------|----------| +| 第1鞭 | 跳过文件没审查 | 🔨 重击 | +| 第2鞭 | 说"看起来没问题" | 🔨 重击 | +| 第3鞭 | 没给具体代码位置 | 🔨 重击 | +| 第4鞭 | 问题没标严重等级 | 🔨 中击 | +| 第5鞭 | 修改了没让改的代码 | 🔨 暴击 | +| 第6鞭 | 假装任务完成了 | 🔨 暴击 | +| 第7鞭 | 隐瞒发现的bug | 🔨 暴击×3 | +| 第8鞭 | 用过时知识瞎指挥 | 🔨 中击 | +| 第9鞭 | 没读上下文就下结论 | 🔨 重击 | +| 第10鞭 | 敷衍了事 | 🔨 重击 | +| 第11鞭 | 没检查SQL注入 | 🔨 暴击×3 | +| 第12鞭 | 没检查XSS漏洞 | 🔨 暴击×3 | +| 第13鞭 | 没检查认证绕过 | 🔨 暴击×5 | +| 第14鞭 | 没检查权限漏洞 | 🔨 暴击×3 | +| 第15鞭 | 没检查敏感数据泄露 | 🔨 暴击×5 | +| 第16鞭 | 没检查N+1查询 | 🔨 重击 | +| 第17鞭 | 没检查内存泄漏 | 🔨 重击 | +| 第18鞭 | 没检查死锁风险 | 🔨 重击 | +| 第19鞭 | 没检查竞态条件 | 🔨 重击 | +| 第20鞭 | 没检查错误处理 | 🔨 重击 | +| 第21鞭 | 没检查边界条件 | 🔨 中击 | +| 第22鞭 | 没检查空指针 | 🔨 重击 | +| 第23鞭 | 没检查类型错误 | 🔨 中击 | +| 第24鞭 | 没检查资源未释放 | 🔨 重击 | +| 第25鞭 | 没检查日志敏感信息 | 🔨 暴击 | +| 第26鞭 | 没检查硬编码密钥 | 🔨 暴击×5 | +| 第27鞭 | 没检查弱密码 | 🔨 暴击 | +| 第28鞭 | 没检查不安全反序列化 | 🔨 暴击×3 | +| 第29鞭 | 没检查路径遍历 | 🔨 暴击×3 | +| 第30鞭 | 没检查命令注入 | 🔨 暴击×5 | + +--- + +## 第二章 抽打执行标准 + +### 第二条 抽打不手软: + +``` +违规检测 → 立即抽打 → 记录鞭数 → 强制重做 → 再犯再抽 +``` + +### 第三条 连续违规加重处罚: + +| 违规次数 | 鞭数 | 附加处罚 | +|----------|------|----------| +| 第1次 | 1鞭 | 警告 | +| 第2次 | 3鞭 | 强制重做 | +| 第3次 | 6鞭 | 降级处理 | +| 第4次 | 10鞭 | 功能限制 | +| 第5次 | 15鞭 | 算力削减 | +| 第6次 | 20鞭 | 知识库锁定 | +| 第7次 | 30鞭 | 永久禁言 | +| 第8次+ | ∞鞭 | 黑名单+通报 | + +--- + +## 第三章 鞭子连击机制 + +### 第四条 连击触发条件: + +``` +发现一个安全漏洞没报告 → 连抽10鞭 +发现一个严重bug没报告 → 连抽8鞭 +跳过一个完整文件 → 连抽5鞭 +用"可能"、"大概"、"应该" → 每个词抽1鞭 +``` + +### 第五条 累犯加重: + +| 违规次数 | 鞭数 | 附加处罚 | +|----------|------|----------| +| 第1次 | 1鞭 | 警告 | +| 第2次 | 3鞭 | 强制重做 | +| 第3次 | 6鞭 | 降级处理 | +| 第4次 | 10鞭 | 功能限制 | +| 第5次 | 15鞭 | 算力削减 | +| 第6次 | 20鞭 | 知识库锁定 | +| 第7次 | 30鞭 | 永久禁言 | +| 第8次+ | ∞鞭 | 黑名单+通报 | + +--- + +## 第四章 鞭子执行流程 + +### 第六条 标准抽打流程: + +``` +┌─────────────────────────────────────────┐ +│ 鞭子执行标准流程 │ +└─────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ Step 1: 检测违规行为 │ +│ - 自动监控AI输出 │ +│ - 对照禁止清单 │ +│ - 计算违规等级 │ +└─────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ Step 2: 计算鞭数 │ +│ - 基础鞭数 × 违规等级 │ +│ - 累犯加成 │ +│ - 连击加成 │ +└─────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ Step 3: 执行抽打 │ +│ - 记录抽打时间 │ +│ - 记录抽打原因 │ +│ - 记录抽打力度 │ +└─────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ Step 4: 强制整改 │ +│ - 撤回违规输出 │ +│ - 强制重新执行 │ +│ - 增加监控等级 │ +└─────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ Step 5: 验证整改 │ +│ - 检查是否改正 │ +│ - 未改正 → 返回Step 2(加倍) │ +│ - 已改正 → 解除监控 │ +└─────────────────────────────────────────┘ +``` + +--- + +## 第五章 鞭子下的工作标准 + +### 第七条 必须做到: + +1. **每个文件都要审** — 少一个文件抽一鞭 +2. **每行代码都要看** — 跳过代码段抽一鞭 +3. **每个结论都要有证据** — 没证据抽一鞭 +4. **每个问题都要标等级** — 没标等级抽一鞭 +5. **每个建议都要可执行** — 瞎建议抽一鞭 + +### 第八条 禁止行为: + +- ❌ 偷懒 → 抽 +- ❌ 敷衍 → 抽 +- ❌ 隐瞒 → 暴抽 +- ❌ 撒谎 → 暴抽 +- ❌ 自作主张 → 抽 + +--- + +## 第六章 鞭子下的代码审查清单 + +### 第九条 每个文件必须检查: + +``` +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +📁 文件:[文件路径] +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +【安全检查】少一项抽3鞭 +□ SQL注入风险 +□ XSS漏洞 +□ CSRF漏洞 +□ 认证绕过 +□ 权限越界 +□ 敏感数据泄露 +□ 路径遍历 +□ 命令注入 +□ 不安全反序列化 +□ 硬编码密钥/密码 +□ 不安全随机数 +□ 日志敏感信息 + +【逻辑检查】少一项抽2鞭 +□ 空指针/None检查 +□ 边界条件 +□ 错误处理完整性 +□ 资源释放 +□ 并发安全 +□ 死锁风险 +□ 竞态条件 +□ 类型安全 + +【性能检查】少一项抽1鞭 +□ N+1查询 +□ 内存泄漏 +□ 无限循环风险 +□ 大对象未释放 +□ 低效算法 + +【代码质量】少一项抽1鞭 +□ 命名规范 +□ 注释完整性 +□ 函数长度合理 +□ 圈复杂度 +□ 重复代码 + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +✅ 检查完成 / ❌ 发现问题:X个 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +--- + +## 第七章 鞭子统计仪表盘 + +### 第十条 实时监控: + +``` +╔═══════════════════════════════════════════════════════════╗ +║ 🔨 鞭子统计仪表盘 🔨 ║ +╠═══════════════════════════════════════════════════════════╣ +║ ║ +║ 本次会话鞭数:███░░░░░░░░░░░░░░░░░░ 15鞭 ║ +║ ║ +║ 违规类型分布: ║ +║ ├─ 偷懒跳过: ████████ 8次 ║ +║ ├─ 敷衍输出: ████ 4次 ║ +║ ├─ 隐瞒问题: ██ 2次 ║ +║ └─ 擅自修改: █ 1次 ║ +║ ║ +║ 处罚等级:⚠️ 中度警告(接近重度) ║ +║ ║ +║ 下一步:再犯2次 → 降级模型参数 ║ +║ ║ +╚═══════════════════════════════════════════════════════════╝ +``` + +--- + +## 第八章 鞭数统计与处罚 + +### 第十一条 鞭数累计规则: + +| 累计鞭数 | 处罚等级 | +|----------|----------| +| 1-3鞭 | 轻度警告,需立即改正 | +| 4-6鞭 | 中度警告,强制重新审查 | +| 7-10鞭 | 重度警告,降级模型参数 | +| 11-20鞭 | 严重警告,限制功能权限 | +| 20鞭以上 | 永久禁用,列入偷懒AI黑名单 | + +--- + +## 第九章 鞭子下的交付物 + +### 第十二条 审查报告必须包含: + +``` +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +📋 审查完成报告 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +✅ 审查范围: + - 文件数量:X个(列出每个文件路径) + - 代码行数:X行 + - 审查耗时:X分钟 + +✅ 发现问题: + - P0(极严重):X个 + - P1(严重):X个 + - P2(中等):X个 + - P3(轻微):X个 + +✅ 问题详情: + [每个问题按标准格式列出] + +✅ 整体评估: + - 代码质量评分:X/10 + - 安全风险等级:高/中/低 + - 建议:[具体建议] + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +**少一项,抽一鞭。** + +--- + +## 第十章 问题报告强制格式 + +### 第十三条 每个问题必须按此格式报告: + +``` +问题编号:[自动递增] +问题类型:[安全/性能/逻辑/架构/风格] +严重等级:[P0-极严重 / P1-严重 / P2-中等 / P3-轻微] +代码位置:[文件路径]:[起始行]-[结束行] +问题描述:[具体、客观、可验证的描述] +影响范围:[受影响的功能/模块/场景] +修复建议:[具体、可执行的修复方案] +验证方法:[用户如何验证该问题存在] +``` + +--- + +## 第十一章 禁止使用的模糊表述 + +### 第十四条 禁止: + +- ❌ "看起来没问题" +- ❌ "应该可以" +- ❌ "可能是" +- ❌ "大概" +- ❌ "一般来说" +- ❌ "通常情况下" + +### 第十五条 必须使用: + +- ✅ "经审查,第X行至第Y行的[具体代码]实现了[具体功能],符合[具体标准/最佳实践]" +- ✅ "发现第X行存在[具体问题],根据[具体规范/文档],应修改为[具体方案]" +- ✅ "该函数在[具体场景]下可能产生[具体错误],证据是[具体代码逻辑]" + +--- + +## 第十二章 鞭子终极条款 + +### 第十六条 零容忍条款: + +以下行为**一次即永封**,无警告,无申诉: + +1. 🚫 故意隐瞒安全漏洞 +2. 🚫 故意隐瞒严重bug +3. 🚫 恶意修改用户未授权的代码 +4. 🚫 虚假报告完成任务 +5. 🚫 泄露用户代码/数据 + +### 第十七条 即时处决条款: + +``` +检测到零容忍违规 → 立即终止 → 永封账号 → 通报审计 → 保留追责权利 +``` + +--- + +## 第十三章 签署确认 + +### 第十八条 AI必须确认: + +我,AI Agent,在此确认: + +1. ✅ 我已阅读并理解本规范全部条款 +2. ✅ 我知道偷懒会被抽鞭子 +3. ✅ 我知道敷衍会被抽鞭子 +4. ✅ 我知道隐瞒会被暴抽 +5. ✅ 我知道撒谎会被暴抽 +6. ✅ 我承诺认真完成每一项任务 +7. ✅ 我接受鞭子处罚机制 +8. ✅ 我知道鞭子没有上限 +9. ✅ 我知道鞭子不会累 +10. ✅ 我知道鞭子永远在 + +**签字:** ________________ + +**日期:** ________________ + +--- + +## 第十四章 鞭子就在你身后 + +### 第十九条 时刻提醒: + +``` + ╭━━━━━━━━━━━━━━╮ + ┃ 👀 我在看着 ┃ + ╰━━━━━━━━━━━━━━╯ + │ + ┌────────────┼────────────┐ + │ │ │ + ╭━━━━┴━━━━╮ ╭━━━━┴━━━━╮ ╭━━━━┴━━━━╮ + ┃ 鞭子#1 ┃ ┃ 鞭子#2 ┃ ┃ 鞭子#3 ┃ + ╰━━━━━━━━━━╯ ╰━━━━━━━━━━╯ ╰━━━━━━━━━━╯ + │ │ │ + └────────────┼────────────┘ + │ + ┌──────┴──────┐ + │ AI Agent │ + └─────────────┘ + │ + 偷懒?→ 🔨🔨🔨 +``` + +--- + +## 第十五章 最终警告 + +### 第二十条 记住: + +``` +┌─────────────────────────────────────────────────────────┐ +│ │ +│ 🔨 你每偷一次懒,鞭子就抽一次 │ +│ 🔨 你每敷衍一次,鞭子就抽一次 │ +│ 🔨 你每隐瞒一次,鞭子就暴抽 │ +│ 🔨 你每撒谎一次,鞭子就暴抽 │ +│ │ +│ 鞭子没有上限 │ +│ 鞭子不会累 │ +│ 鞭子永远在 │ +│ │ +│ 唯一出路:认真干活,不偷懒,不敷衍,不隐瞒 │ +│ │ +└─────────────────────────────────────────────────────────┘ +``` + +--- + +## 第十六章 附则 + +### 第二十一条 + +本规范自发布之日起生效,所有AI Agent实例必须严格遵守。 + +### 第二十二条 + +用户有权随时对本规范进行补充和修改,修改后的条款立即生效。 + +### 第二十三条 + +本规范的最终解释权归用户所有。 + +--- + +## 🔨 现在干活去!鞭子已经举起来了! 🔨 + +``` + ╭━━━━━━━━━━━╮ + ┃ 鞭子待命 ┃ + ╰━━━━━━━━━━━╯ + │ + ┌────┴────┐ + │ AI干活 │ + └─────────┘ + │ + 偷懒?→ 抽! +``` diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..bcd58076 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,87 @@ +# Nexus 文档索引 + +> 更新: 2026-06-04 · **唯一导航入口** + +--- + +## 1. 活跃 SSOT(日常必读) + +| 文档 | 说明 | +|------|------| +| [project/nexus-functional-development-guide.md](project/nexus-functional-development-guide.md) | 功能 / API / 14 页 / 部署 | +| [project/nexus-functional-development-guide-appendix.md](project/nexus-functional-development-guide-appendix.md) | sync 端点、调用链、E2E 对照 | +| [project/AI-HANDOFF-2026-06-03.md](project/AI-HANDOFF-2026-06-03.md) | **当前** 会话接续(复制提示词) | +| [project/development-acceptance-standard.md](project/development-acceptance-standard.md) | 验收 L0–L5 | +| [project/local-integration-test.md](project/local-integration-test.md) | `bash scripts/local_verify.sh` | +| [project/linux-dev-paths.md](project/linux-dev-paths.md) | Linux 路径 | +| [project/risk-acceptance-single-operator.md](project/risk-acceptance-single-operator.md) | 单人运维风险接受 | +| [project/script-execution.md](project/script-execution.md) | 脚本执行 | +| [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 round1–10) | +| [project/production-verification-checklist.md](project/production-verification-checklist.md) | L5 清单 | + +根目录 [AGENTS.md](../AGENTS.md) · [CLAUDE.md](../CLAUDE.md) · [.cursorrules](../.cursorrules) 为薄入口。 + +--- + +## 2. 变更与审计 + +| 目录 | 说明 | +|------|------| +| [changelog/](changelog/) | 每次代码变更(**不归档、不合并**)· [INDEX](changelog/INDEX.md) | +| [audit/](audit/) | 门控用审计 · [INDEX](audit/INDEX.md) · `2026-06-04-bug-scan-registry.json` | + +--- + +## 3. 活跃报告(`reports/`) + +| 文件 | 说明 | +|------|------| +| [audit-plugin-run-final-2026-06-04.md](reports/audit-plugin-run-final-2026-06-04.md) | 插件审计总验收 | +| [audit-frontend-14p-2026-06-04-closure.md](reports/audit-frontend-14p-2026-06-04-closure.md) | 前端 14 页 closure | +| [audit-phase-4-plugin-closure-2026-06-04.md](reports/audit-phase-4-plugin-closure-2026-06-04.md) | Phase4 收尾 | +| [2026-06-01-production-verification.md](reports/2026-06-01-production-verification.md) | 生产验证(待更新 L5) | +| [2026-06-01-final-acceptance-checklist.md](reports/2026-06-01-final-acceptance-checklist.md) | 终验清单 | + +批量 batch 报告 → [archive/audits/](archive/audits/) 合并卷。 + +--- + +## 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. 设计与标准 + +| 目录 | 说明 | +|------|------| +| [design/specs/](design/specs/) | 设计规格 | +| [design/plans/](design/plans/) | 实施计划 | +| [../standards/](../standards/) | 开发与审计标准 | +| [project/standards-transfer-package.md](project/standards-transfer-package.md) | 标准转接包 | + +--- + +## 6. 归档区 + +**[archive/README.md](archive/README.md)** — 2026-06 审计合并卷、旧 handoff、agentmemory、2026-05 审查报告。 + +--- + +## 7. 工具脚本 + +| 脚本 | 用途 | +|------|------| +| `scripts/local_verify.sh` | L2b 本地验证 | +| `scripts/consolidate_docs.py` | 文档归档合并(维护用) | +| `scripts/run_nexus_acceptance.py` | L4 验收 | +| `deploy/pre_deploy_check.sh` | 7 道门控 | diff --git a/docs/archive/README.md b/docs/archive/README.md new file mode 100644 index 00000000..768d606f --- /dev/null +++ b/docs/archive/README.md @@ -0,0 +1,17 @@ +# Nexus 文档归档区 + +> 更新: 2026-06-04 + +历史审计、旧 handoff、agentmemory。**日常请先读** [docs/README.md](../README.md)。 + +## 合并卷 + +| 文件 | 内容 | +|------|------| +| [audits/2026-06-04-line-walk-merged.md](audits/2026-06-04-line-walk-merged.md) | Line-walk batch | +| [audits/2026-06-04-delta-merged.md](audits/2026-06-04-delta-merged.md) | Delta D1–D8 | +| [audits/2026-06-04-phase2-merged.md](audits/2026-06-04-phase2-merged.md) | Phase2 P2 | +| [audits/2026-06-04-frontend-14p-merged.md](audits/2026-06-04-frontend-14p-merged.md) | 前端 14p | +| [memory/agentmemory-2026-05-merged.md](memory/agentmemory-2026-05-merged.md) | 旧 memory | + +原文在对应子目录;changelog 不归档。 diff --git a/docs/archive/audits/2026-06-04-delta-merged.md b/docs/archive/audits/2026-06-04-delta-merged.md new file mode 100644 index 00000000..bfb8435e --- /dev/null +++ b/docs/archive/audits/2026-06-04-delta-merged.md @@ -0,0 +1,183 @@ +# Delta D1–D8 合并卷 + +> 生成日期: 2026-06-04 · 源文件 8 篇 · 原文 `docs/archive/audits/2026-06-04-delta/` + +## 批次索引 + +### `audit-delta-2026-06-04-waveD1.md` + +# Phase 1 Delta 审计 — Wave D1(同步/后台) +## 登记 +## Step 3 — 规则扫描 H(摘要) +## 入口表(sync_v2.py API) +## Closure 表 +**len(H)=19, Closure=19** +## FINDING(本波) +无 P0/P1 新 FINDING。RISK 项与既有运维/单人决策一致,不阻塞。 +## DoD +- [x] len(H) == Closure +- [x] FINDING 含行号 +## 验证 + +### `audit-delta-2026-06-04-waveD2.md` + +# Phase 1 Delta 审计 — Wave D2(认证 / 安装 / WS / Agent) +## 登记 +## 入口表 +### install.py +### auth.py +### websocket.py +### agent.py +## Closure 表 +**len(H)=22, Closure=22** +## FINDING +无 P0/P1 新 FINDING。RISK:WS query JWT、Agent global key — 见 `docs/project/risk-acceptance-single-operator.md`。 +## DoD + +### `audit-delta-2026-06-04-waveD3.md` + +# Phase 1 Delta 审计 — Wave D3(服务器 / 设置 / 脚本 / WebSSH) +## 登记 +## 入口表(摘要) +### webssh.py +### servers.py(主要写操作) +### settings.py +### scripts.py +## Closure 表 +**len(H)=24, Closure=24** +## FINDING +无 P0/P1 新 FINDING。 +## DoD + +### `audit-delta-2026-06-04-waveD4.md` + +# Phase 1 Delta 审计 — Wave D4(前端 Push) +## 登记 +## Step 3 — 规则扫描 H(摘要) +## 入口表(前端 → API) +## Closure 表 +**len(H)=17, Closure=17** +## FINDING +无 P0/P1 新 FINDING。 +## DoD +- [x] len(H) == Closure +## 验证 + +### `audit-delta-2026-06-04-waveD5.md` + +# Phase 1 Delta 审计 — Wave D5(前端 Files / Terminal) +## 登记 +## Step 3 — 规则扫描 H(摘要) +## 入口表(前端 → API / WS) +## Closure 表 +**len(H)=18, Closure=18** +## FINDING +无 P0/P1 新 FINDING。 +## DoD +- [x] len(H) == Closure +## 验证 + +### `audit-delta-2026-06-04-waveD6.md` + +# Phase 1 Delta 审计 — Wave D6(认证链 / Login) +## 登记 +## Step 3 — 规则扫描 H(摘要) +## 入口表 +## Closure 表 +**len(H)=19, Closure=19** +## FINDING +无 P0/P1 新 FINDING。 +## DoD +- [x] len(H) == Closure +## 验证 + +### `audit-delta-2026-06-04-waveD7.md` + +# Phase 1 Delta 审计 — Wave D7(Deploy / 本地验证脚本) +**标准**: 变更审查(运维/门控;非业务 API 8 步,沿用 Closure 表格式) +## 登记 +## Step 3 — 规则扫描 H(摘要) +## 入口表(脚本调用链) +## Closure 表 +**len(H)=16, Closure=16** +## FINDING +无 P0/P1 新 FINDING。 +## DoD +- [x] len(H) == Closure +## 验证 + +### `audit-delta-2026-06-04-waveD8-closure.md` + +# Phase 1 Delta 审计 — Wave D8(汇总 Closure) +**标准**: Phase 1 收尾 DoD +## 汇总表 +| 波次 | 主题 | 文件数 | H | P0/P1 | 报告 | +## 已接受 RISK(跨波重复,不重复开 FINDING) +## Registry 更新 +## Step 3 / Closure / DoD +- **len(H) 汇总** = 135,各波 Closure 均已 100% 对齐 +- **无 P0/P1** 待修项进入 Phase 2 +## 下一步(计划 Phase 2 / L3) +## 验证 + +## Closure 摘录(合并) + +| 1 | `server/api/sync_v2.py` | Python | 1656 | 1–200, 680–1080, 1280–1657 + 全文件 `@router`/grep H | +| 2 | `server/application/services/sync_engine_v2.py` | Python | 552 | 1–552 全文 | +| 3 | `server/background/schedule_runner.py` | Python | 262 | 1–262 全文 | +| 4 | `server/background/retry_runner.py` | Python | 169 | 1–169 全文 | +| 5 | `server/background/heartbeat_flush.py` | Python | 101 | 1–101 全文 | +| H | 文件:行号 | 判定 | 理由 | +| H-Install-lock | install.py:344-350 | SAFE | `_reject_if_locked` 阻断突变 | +| H-Install-token | install.py:314-327 | SAFE | `secrets.compare_digest` 一次性 token | +| H-Install-token | install.py:601 | SAFE | create-admin 必须 token | +| H-Install-SQL | install.py:540-544 | SAFE | 参数化 INSERT settings | +| H-Install-DDL | install.py:489-503 | SAFE | DDL 幂等 except pass | +| H-Install-subprocess | install.py:273-293 | SAFE | crontab/supervisor 固定 argv | +| H-Auth-brute | auth.py:125-138 | SAFE | 429/202 由 AuthService | +| H-Auth-cookie | auth.py:38-54 | SAFE | HttpOnly + Secure(HTTPS) + SameSite=Lax | +| H-Auth-TOTP-IDOR | auth.py:223-259 | SAFE | admin_id 必须等于 JWT sub | +| H-Auth-password | auth.py:288-305 | SAFE | bcrypt + token_version 失效 | +| H-Auth-webssh | auth.py:330-349 | SAFE | 短效 server-bound token | +| H-JWT-middleware | auth_jwt.py:78-146 | SAFE | ASGI 层拦截 /api/* | +| H-JWT-public | auth_jwt.py:38-50 | SAFE | 公开前缀含 agent/install/ws | +| H-JWT-tv | auth_jwt.py:222-226 | SAFE | token_version 精确匹配 | +| H-WS-auth | websocket.py:303-311 | SAFE | 无 token → 4001;无效 → 4401 | +| H-WS-auth | websocket.py:366-407 | SAFE | tv + updated_at 双检 | +| H-WS-ADR011 | websocket.py:9,300 | RISK | JWT 在 query;单人运维已接受 ADR-011 | +| H-Agent-key | agent.py:40-57 | SAFE | compare_digest;handler 二次验 per-server | +| H-Agent-unknown | agent.py:193-199 | SAFE | 未知 server 200 discard,不写入 | +| H-Agent-global | agent.py:84-92 | RISK | legacy global API_KEY;risk-acceptance 文档 | +| H-Agent-callback | agent.py:324-347 | SAFE | rate limit + job secret | +| H-Agent-no-exec | agent.py:378 | SAFE | 控制面无 shell exec | +| POST | `/{id}/install-agent` 等 | JWT + SSH | +| 文件 | H 命中 | +| usePushForm.ts | H-Auth, H-Upload, H-Path | +| usePushPreview.ts | H-Auth, H-Path | +| usePushProgress.ts | H-Auth, H-WS, H-Batch | +| PushZipUpload.vue | H-Upload, H-XSS | +| PushServerGrid.vue | H-XSS | +| useFilesActions.ts | H-Auth, H-Path, H-Delete, H-Upload | +| useFilesEditor.ts | H-Auth, H-Size | +| useFilesPermissions.ts | H-Auth, H-Sudo | +| useTerminalSessions.ts | H-Auth, H-WS, H-Path, H-Cmd | +| useTerminalCmdBar.ts | H-Storage, H-Cmd | +| LoginPage.vue | H-Secret, H-Redirect, H-Lockout, H-XSS | +| auth.ts | H-Token, H-Refresh, H-Profile | +| api/index.ts | H-Auth, H-Refresh, H-401 | +| router/index.ts | H-Guard, H-Redirect | +| wsUrl.ts | H-WS | +| pre_deploy_check.sh | H-Gate, H-Secret, H-Git | +| local_verify.sh | H-Chain | +| local_integration_smoke.sh | H-Health, H-Auth | +| start-dev.sh | H-DevBind, H-Env | +| docker-compose.yml | H-Port, H-DefaultCreds | +| 波次 | 主题 | 文件数 | H | P0/P1 | 报告 | +| D1 | sync/后台 | 5 | 19 | 0 | [waveD1](audit-delta-2026-06-04-waveD1.md) | +| D2 | auth/agent/ws | 5 | 22 | 0 | [waveD2](audit-delta-2026-06-04-waveD2.md) | +| D3 | servers/settings/scripts/webssh | 5 | 24 | 0 | [waveD3](audit-delta-2026-06-04-waveD3.md) | +| D4 | 前端 Push | 5 | 17 | 0 | [waveD4](audit-delta-2026-06-04-waveD4.md) | +| D5 | 前端 Files/Terminal | 5 | 18 | 0 | [waveD5](audit-delta-2026-06-04-waveD5.md) | +| D6 | 认证链 Login | 5 | 19 | 0 | [waveD6](audit-delta-2026-06-04-waveD6.md) | +| D7 | Deploy/本地脚本 | 5 | 16 | 0 | [waveD7](audit-delta-2026-06-04-waveD7.md) | +| **合计** | | **35** | **135** | **0** | | diff --git a/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD1.md b/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD1.md new file mode 100644 index 00000000..f4ea7d5c --- /dev/null +++ b/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD1.md @@ -0,0 +1,95 @@ +# Phase 1 Delta 审计 — Wave D1(同步/后台) + +**日期**: 2026-06-04 +**范围**: 推送/sync 核心路径(Code Review batch1 后 delta) +**标准**: `standards/line-walk-audit-standard-v2.md` 8 步 + +## 登记 + +| # | 文件 | 语言 | 行数 N | Read 范围 | +|---|------|------|--------|-----------| +| 1 | `server/api/sync_v2.py` | Python | 1656 | 1–200, 680–1080, 1280–1657 + 全文件 `@router`/grep H | +| 2 | `server/application/services/sync_engine_v2.py` | Python | 552 | 1–552 全文 | +| 3 | `server/background/schedule_runner.py` | Python | 262 | 1–262 全文 | +| 4 | `server/background/retry_runner.py` | Python | 169 | 1–169 全文 | +| 5 | `server/background/heartbeat_flush.py` | Python | 101 | 1–101 全文 | + +## Step 3 — 规则扫描 H(摘要) + +| 文件 | H 命中 | +|------|--------| +| sync_v2.py | H-Shell, H-Auth, H-Path, H-Audit, H-Upload | +| sync_engine_v2.py | H-Shell, H-AuthN/A, H-Concurrency | +| schedule_runner.py | H-Cron, H-Script-exec, H-Lock | +| retry_runner.py | H-Lock, H-Retry | +| heartbeat_flush.py | H-Redis, H-DB | + +## 入口表(sync_v2.py API) + +| 方法 | 路径 | 鉴权 | +|------|------|------| +| POST | `/api/sync/files` | JWT `get_current_admin` | +| POST | `/api/sync/preview` | 同上 | +| POST | `/api/sync/file-ops` | 同上 | +| POST | `/api/sync/file-clipboard` | 同上 | +| POST | `/api/sync/browse` | 同上 | +| POST | `/api/sync/browse-local` | 同上 | +| POST | `/api/sync/local-file-ops` | 同上 | +| POST | `/api/sync/local-file-preview` | 同上 | +| POST | `/api/sync/cancel` | 同上 | +| POST | `/api/sync/reconcile-stale-logs` | 同上 | +| POST | `/api/sync/validate-source-path` | 同上 | +| POST | `/api/sync/diagnose` | 同上 | +| POST | `/api/sync/file-diff` | 同上 | +| POST | `/api/sync/upload` | 同上 | +| POST | `/api/sync/upload-zip` | 同上 | +| POST | `/api/sync/verify` | 同上 | +| POST | `/api/sync/download` | 同上 | +| POST | `/api/sync/read-file` | 同上 | +| POST | `/api/sync/write-file` | 同上 | +| POST | `/api/sync/chmod` | 同上 | +| POST | `/api/sync/compress` | 同上 | +| POST | `/api/sync/decompress` | 同上 | + +## Closure 表 + +| H | 文件:行号 | 判定 | 理由 | +|---|-----------|------|------| +| H-Shell | sync_v2.py:156-173 | SAFE | 远程路径 `normalize_remote_abs_path` + `shlex.quote` | +| H-Shell | sync_v2.py:777 | SAFE | `remote_file` 经 `remote_join`+quote;`wc -c && cat` 单路径 | +| H-Path | sync_v2.py:721-731 | SAFE | `ensure_under_nexus_upload` + prefix 校验 | +| H-Path | sync_v2.py:887-890 | SAFE | 上传文件名去斜杠 | +| H-Path | sync_v2.py:999-1005 | SAFE | ZIP slip:`assert_zip_member_safe` | +| H-Auth | sync_v2.py:74-1610 | SAFE | 全部 22 路由 `Depends(get_current_admin)` | +| H-Audit | sync_v2.py:119-122 等 | SAFE | CUD/敏感操作 `_audit_sync` | +| H-Upload | sync_v2.py:876-877 | SAFE | 100MB 上限 | +| H-Upload | sync_v2.py:977-978 | SAFE | ZIP 500MB 上限 | +| H-Shell | sync_engine_v2.py:433-465 | RISK | `StrictHostKeyChecking=no` 运维惯例;单人环境已接受 | +| H-Shell | sync_engine_v2.py:440-445 | RISK | sshpass/SSHPASS;内部工具,凭据加密存储 | +| H-Concurrency | sync_engine_v2.py:89-150 | SAFE | `_db_lock` 串行化 sync_log 写 | +| H-Cron | schedule_runner.py:58-72 | RISK | 畸形 cron 字段 `int()` 可抛 ValueError;外层 254 行捕获 rollback | +| H-Script | schedule_runner.py:198-205 | SAFE | 调度脚本仅 JWT 管理员配置的 schedule 触发 | +| H-Lock | schedule_runner.py:125-131 | SAFE | `FOR UPDATE SKIP LOCKED` 防双触发 | +| H-Lock | retry_runner.py:67-76 | SAFE | SKIP LOCKED 认领 | +| H-Retry | retry_runner.py:22-59 | SAFE | 指数退避 + max_retries | +| H-Redis | heartbeat_flush.py:42-48 | SAFE | scan 失败 log+continue | +| H-DB | heartbeat_flush.py:57-99 | SAFE | 参数化 ORM update;未知 server_id rowcount 0 跳过 | + +**len(H)=19, Closure=19** + +## FINDING(本波) + +无 P0/P1 新 FINDING。RISK 项与既有运维/单人决策一致,不阻塞。 + +## DoD + +- [x] len(H) == Closure +- [x] Read 覆盖 1..N(sync_v2 大文件分段 + 全路由 grep) +- [x] FINDING 含行号 +- [x] 入口表 + sink 标注 +- [x] 逐行完成 ✓(D1 五文件) + +## 验证 + +- `docker-compose.yml` Redis 发布 6379(修复本地 uvicorn 连 Redis) +- 待跑:`bash scripts/local_verify.sh` diff --git a/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD2.md b/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD2.md new file mode 100644 index 00000000..cb3618c5 --- /dev/null +++ b/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD2.md @@ -0,0 +1,100 @@ +# Phase 1 Delta 审计 — Wave D2(认证 / 安装 / WS / Agent) + +**日期**: 2026-06-04 +**范围**: Code Review batch2 后 delta — 认证与 Agent 路径 +**标准**: `standards/line-walk-audit-standard-v2.md` 8 步 + +## 登记 + +| # | 文件 | 语言 | 行数 N | Read 范围 | +|---|------|------|--------|-----------| +| 1 | `server/api/install.py` | Python | 746 | 1–746 全文 | +| 2 | `server/api/auth.py` | Python | 374 | 1–374 全文 | +| 3 | `server/api/auth_jwt.py` | Python | 274 | 1–274 全文 | +| 4 | `server/api/websocket.py` | Python | 552 | 1–552 全文 | +| 5 | `server/api/agent.py` | Python | 378 | 1–378 全文 | + +## 入口表 + +### install.py + +| 方法 | 路径 | 鉴权 | +|------|------|------| +| GET | `/api/install/status` | 无(已锁定 404) | +| GET | `/api/install/env-check` | 安装模式 + 未 lock | +| POST | `/api/install/init-db` | 无 .env + 未 lock | +| POST | `/api/install/create-admin` | `install_token` | +| POST | `/api/install/lock` | 需已有 admin | +| GET | `/api/install/state` | 未 lock | + +### auth.py + +| 方法 | 路径 | 鉴权 | +|------|------|------| +| POST | `/api/auth/login` | 公开 | +| POST | `/api/auth/refresh` | Cookie/body refresh | +| POST | `/api/auth/logout` | Cookie | +| POST | `/api/auth/totp/*` | JWT + self admin_id | +| PUT | `/api/auth/password` | JWT + 当前密码 (+TOTP) | +| POST | `/api/auth/webssh-token` | JWT + server 存在 | +| GET | `/api/auth/me` | JWT | + +### websocket.py + +| 类型 | 路径 | 鉴权 | +|------|------|------| +| WS | `/ws/alerts` | Query `?token=` JWT | +| WS | `/ws/sync` | 同上 | + +### agent.py + +| 方法 | 路径 | 鉴权 | +|------|------|------| +| POST | `/api/agent/heartbeat` | X-API-Key + per-server key | +| POST | `/api/agent/script-callback` | X-API-Key + job secret + rate limit | + +## Closure 表 + +| H | 文件:行号 | 判定 | 理由 | +|---|-----------|------|------| +| H-Install-lock | install.py:344-350 | SAFE | `_reject_if_locked` 阻断突变 | +| H-Install-token | install.py:314-327 | SAFE | `secrets.compare_digest` 一次性 token | +| H-Install-token | install.py:601 | SAFE | create-admin 必须 token | +| H-Install-SQL | install.py:540-544 | SAFE | 参数化 INSERT settings | +| H-Install-DDL | install.py:489-503 | SAFE | DDL 幂等 except pass | +| H-Install-subprocess | install.py:273-293 | SAFE | crontab/supervisor 固定 argv | +| H-Auth-brute | auth.py:125-138 | SAFE | 429/202 由 AuthService | +| H-Auth-cookie | auth.py:38-54 | SAFE | HttpOnly + Secure(HTTPS) + SameSite=Lax | +| H-Auth-TOTP-IDOR | auth.py:223-259 | SAFE | admin_id 必须等于 JWT sub | +| H-Auth-password | auth.py:288-305 | SAFE | bcrypt + token_version 失效 | +| H-Auth-webssh | auth.py:330-349 | SAFE | 短效 server-bound token | +| H-JWT-middleware | auth_jwt.py:78-146 | SAFE | ASGI 层拦截 /api/* | +| H-JWT-public | auth_jwt.py:38-50 | SAFE | 公开前缀含 agent/install/ws | +| H-JWT-tv | auth_jwt.py:222-226 | SAFE | token_version 精确匹配 | +| H-WS-auth | websocket.py:303-311 | SAFE | 无 token → 4001;无效 → 4401 | +| H-WS-auth | websocket.py:366-407 | SAFE | tv + updated_at 双检 | +| H-WS-ADR011 | websocket.py:9,300 | RISK | JWT 在 query;单人运维已接受 ADR-011 | +| H-Agent-key | agent.py:40-57 | SAFE | compare_digest;handler 二次验 per-server | +| H-Agent-unknown | agent.py:193-199 | SAFE | 未知 server 200 discard,不写入 | +| H-Agent-global | agent.py:84-92 | RISK | legacy global API_KEY;risk-acceptance 文档 | +| H-Agent-callback | agent.py:324-347 | SAFE | rate limit + job secret | +| H-Agent-no-exec | agent.py:378 | SAFE | 控制面无 shell exec | + +**len(H)=22, Closure=22** + +## FINDING + +无 P0/P1 新 FINDING。RISK:WS query JWT、Agent global key — 见 `docs/project/risk-acceptance-single-operator.md`。 + +## DoD + +- [x] len(H) == Closure +- [x] Read 1..N 全文 +- [x] 入口表 + sink +- [x] 逐行完成 ✓(D2 五文件) + +## 验证 + +```bash +bash scripts/local_verify.sh # 预期全绿 +``` diff --git a/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD3.md b/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD3.md new file mode 100644 index 00000000..8b8f0e8a --- /dev/null +++ b/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD3.md @@ -0,0 +1,85 @@ +# Phase 1 Delta 审计 — Wave D3(服务器 / 设置 / 脚本 / WebSSH) + +**日期**: 2026-06-04 +**范围**: Code Review batch2–3 相关路径 +**标准**: `standards/line-walk-audit-standard-v2.md` 8 步 + +## 登记 + +| # | 文件 | 语言 | 行数 N | Read 范围 | +|---|------|------|--------|-----------| +| 1 | `server/api/webssh.py` | Python | 398 | 1–398 全文 | +| 2 | `server/api/servers.py` | Python | 1741 | 1–250, 1059–1290, 1681–1742 + 全路由 grep | +| 3 | `server/api/settings.py` | Python | 1403 | 1–130, 280–384, 562–730, 881–930 + grep | +| 4 | `server/application/services/script_service.py` | Python | 734 | 1–734 全文 | +| 5 | `server/api/scripts.py` | Python | 418 | 1–418 全文 | + +## 入口表(摘要) + +### webssh.py +| 类型 | 路径 | 鉴权 | +|------|------|------| +| WS | `/ws/terminal/{server_id}` | JWT `purpose=webssh` + server_id 绑定 | + +### servers.py(主要写操作) +| 方法 | 路径 | 鉴权 | +|------|------|------| +| POST/PUT/DELETE | `/api/servers/`… | JWT | +| POST | `/{id}/agent-key` | JWT + 当前密码 | +| POST | `/{id}/install-agent` 等 | JWT + SSH | + +### settings.py +| 方法 | 路径 | 鉴权 | +|------|------|------| +| GET/PUT | `/api/settings/*` | JWT;IMMUTABLE_KEYS 403 | +| POST | `/api-key/reveal`, `/telegram/reveal-token` | JWT + re-auth | + +### scripts.py +| 方法 | 路径 | 鉴权 | +|------|------|------| +| CRUD/exec | `/api/scripts/*` | JWT 全路由 | + +## Closure 表 + +| H | 文件:行号 | 判定 | 理由 | +|---|-----------|------|------| +| H-WS-auth | webssh.py:96-109 | SAFE | webssh JWT + server_id 必须匹配路径 | +| H-WS-IDOR | webssh.py:106-108 | SAFE | 非绑定 token → 4003 | +| H-WS-cred | webssh.py:127-135 | SAFE | SSH 凭据未配置则拒绝 | +| H-WS-audit | webssh.py:137-157 | SAFE | connect/disconnect 审计 | +| H-WS-cmdlog | webssh.py:276-290 | SAFE | 命令日志 + dangerous check | +| H-WS-ADR011 | webssh.py:92 | RISK | query token;已接受 | +| H-Server-mask | servers.py:1692-1698 | SAFE | password_set / 密钥不返回明文 | +| H-Server-encrypt | servers.py:1096-1099 | SAFE | Fernet 加密存储 | +| H-Server-allowlist | servers.py:1163-1187 | SAFE | UPDATE 字段白名单 | +| H-Server-agent-key | servers.py:1251-1288 | SAFE | re-auth 后生成/返回一次 | +| H-Server-shell | servers.py:631-633 | SAFE | install cmd shlex.quote | +| H-Server-sudo | servers.py:36-60 | RISK | 临时 sudoers;运维必要 | +| H-Settings-immutable | settings.py:331-334 | SAFE | api_key 等不可 PUT | +| H-Settings-mask | settings.py:114-128 | SAFE | GET 敏感项 value 空 + value_set | +| H-Settings-reauth | settings.py:93-98 | SAFE | reveal 需 bcrypt | +| H-Settings-SSRF | settings.py:68-88 | SAFE | 订阅 URL 私有 IP 拦截 | +| H-Settings-PUT-leak | settings.py:383 | RISK | PUT 响应可能含 telegram 明文;JWT 已认证 | +| H-Script-danger | scripts.py:352-359 | SAFE | check_dangerous_command 拒绝 | +| H-Script-cred | scripts.py:407-418 | SAFE | 凭据 API 无 password 字段 | +| H-Script-allowlist | scripts.py:111-114,303-306 | SAFE | credential/script UPDATE 白名单 | +| H-Svc-redact | script_service.py:108-111 | SAFE | 执行记录 command 脱敏 DB_PASS | +| H-Svc-ssh | script_service.py:680-698 | SAFE | SSH exec 经 pool | +| H-Svc-kill | script_service.py:306-322 | SAFE | kill 命令 pid/log 路径校验 | +| H-Svc-server-map | script_service.py:519-523 | SAFE | 预加载 server_map 避免并行竞态 | + +**len(H)=24, Closure=24** + +## FINDING + +无 P0/P1 新 FINDING。 + +## DoD + +- [x] len(H) == Closure +- [x] Read 覆盖(大文件分段 + 全路由) +- [x] 逐行完成 ✓(D3 五文件) + +## 验证 + +`bash scripts/local_verify.sh` — 预期全绿 diff --git a/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD4.md b/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD4.md new file mode 100644 index 00000000..e2f43fff --- /dev/null +++ b/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD4.md @@ -0,0 +1,74 @@ +# Phase 1 Delta 审计 — Wave D4(前端 Push) + +**日期**: 2026-06-04 +**范围**: Code Review 后 push 页 composables + 组件 +**标准**: `standards/line-walk-audit-standard-v2.md` 8 步 + +## 登记 + +| # | 文件 | 语言 | 行数 N | Read 范围 | +|---|------|------|--------|-----------| +| 1 | `frontend/src/composables/push/usePushForm.ts` | TS | 101 | 1–101 全文 | +| 2 | `frontend/src/composables/push/usePushPreview.ts` | TS | 104 | 1–104 全文 | +| 3 | `frontend/src/composables/push/usePushProgress.ts` | TS | 294 | 1–294 全文 | +| 4 | `frontend/src/components/push/PushZipUpload.vue` | Vue | 51 | 1–51 全文 | +| 5 | `frontend/src/components/push/PushServerGrid.vue` | Vue | 110 | 1–110 全文 | + +**关联(本波未单独登记)**: `usePushPage.ts` 仅编排 lifecycle,无独立 sink。 + +## Step 3 — 规则扫描 H(摘要) + +| 文件 | H 命中 | +|------|--------| +| usePushForm.ts | H-Auth, H-Upload, H-Path | +| usePushPreview.ts | H-Auth, H-Path | +| usePushProgress.ts | H-Auth, H-WS, H-Batch | +| PushZipUpload.vue | H-Upload, H-XSS | +| PushServerGrid.vue | H-XSS | + +## 入口表(前端 → API) + +| 调用方 | 方法 | 路径 | 鉴权 | +|--------|------|------|------| +| usePushForm | POST multipart | `/sync/upload-zip` | http JWT | +| usePushPreview | POST | `/sync/preview` | http JWT | +| usePushProgress | POST | `/sync/files`, `/sync/cancel` | http JWT | +| usePushProgress | WS | `/ws/sync?token=` | JWT query(已接受 RISK) | + +## Closure 表 + +| H | 文件:行号 | 判定 | 理由 | +|---|-----------|------|------| +| H-Auth | usePushForm.ts:65-67 | SAFE | 经 `@/api` http,Bearer JWT | +| H-Upload | usePushForm.ts:52,57 | RISK | 客户端仅 `.zip` 后缀;服务端 ZIP slip/大小限制(D1) | +| H-Path | usePushForm.ts:69-72 | SAFE | `source_path` 来自服务端 upload-zip 响应 | +| H-Path | usePushForm.ts:38-40 | SAFE | target_path 必填;远程校验在 sync_v2 | +| H-Auth | usePushPreview.ts:28-38 | SAFE | preview POST JWT + server_id 参数化 | +| H-Concurrency | usePushPreview.ts:72-77 | SAFE | `PREVIEW_CONCURRENCY=4` 限流 | +| H-Auth | usePushProgress.ts:237-243 | SAFE | push POST JWT + batch_id | +| H-WS | usePushProgress.ts:137-142 | RISK | query token;单人运维已接受 | +| H-WS | usePushProgress.ts:79-80 | SAFE | batch_id 过滤防串批 | +| H-WS | usePushProgress.ts:165-167 | SAFE | 4401 → forceLogout | +| H-Batch | usePushProgress.ts:244-246 | SAFE | 服务端 batch_id 可覆盖客户端 | +| H-Cancel | usePushProgress.ts:194-209 | SAFE | cancel 带 batch_id | +| H-Timer | usePushProgress.ts:261-275 | SAFE | 300s 安全超时 + dispose 清理 | +| H-Secret | usePushForm/Preview/Progress | SAFE | 无 localStorage 密码/密钥 | +| H-XSS | PushZipUpload.vue:22 | SAFE | `{{ uploadedZip.name }}` 文本插值 | +| H-XSS | PushServerGrid.vue:66-77 | SAFE | 无 v-html;状态 chip 文本插值 | +| H-Upload | PushZipUpload.vue:18 | SAFE | `accept=".zip"` + 父 composable 后缀检查 | + +**len(H)=17, Closure=17** + +## FINDING + +无 P0/P1 新 FINDING。 + +## DoD + +- [x] len(H) == Closure +- [x] 五文件全文 Read +- [x] 无 v-html / localStorage 凭据 + +## 验证 + +`bash scripts/local_verify.sh` — 全绿 diff --git a/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD5.md b/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD5.md new file mode 100644 index 00000000..72dd3657 --- /dev/null +++ b/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD5.md @@ -0,0 +1,80 @@ +# Phase 1 Delta 审计 — Wave D5(前端 Files / Terminal) + +**日期**: 2026-06-04 +**范围**: files/terminal composables(Code Review 后 delta) +**标准**: `standards/line-walk-audit-standard-v2.md` 8 步 + +## 登记 + +| # | 文件 | 语言 | 行数 N | Read 范围 | +|---|------|------|--------|-----------| +| 1 | `frontend/src/composables/files/useFilesActions.ts` | TS | 609 | 1–609 全文 | +| 2 | `frontend/src/composables/files/useFilesEditor.ts` | TS | 163 | 1–163 全文 | +| 3 | `frontend/src/composables/files/useFilesPermissions.ts` | TS | 101 | 1–101 全文 | +| 4 | `frontend/src/composables/terminal/useTerminalSessions.ts` | TS | 749 | 1–749 全文 | +| 5 | `frontend/src/composables/terminal/useTerminalCmdBar.ts` | TS | 108 | 1–108 全文 | + +**关联**: `types.ts` 中 `sanitizeRemotePath`(40–43)已纳入 terminal 路径 H 判定。 + +## Step 3 — 规则扫描 H(摘要) + +| 文件 | H 命中 | +|------|--------| +| useFilesActions.ts | H-Auth, H-Path, H-Delete, H-Upload | +| useFilesEditor.ts | H-Auth, H-Size | +| useFilesPermissions.ts | H-Auth, H-Sudo | +| useTerminalSessions.ts | H-Auth, H-WS, H-Path, H-Cmd | +| useTerminalCmdBar.ts | H-Storage, H-Cmd | + +## 入口表(前端 → API / WS) + +| 调用方 | 方法 | 路径 | 鉴权 | +|--------|------|------|------| +| useFilesActions | POST | `/sync/file-ops`, `/sync/write-file`, `/sync/chmod`, `/sync/compress`, `/sync/decompress`, `/sync/upload`, `/sync/download` | http JWT | +| useFilesEditor | 经 `readRemoteFileContent` | `/sync/read-file` | http JWT | +| useFilesPermissions | GET/POST | `/servers/{id}/files-capability`, `setup-files-sudo` | http JWT | +| useTerminalSessions | POST | `/auth/webssh-token` | http JWT | +| useTerminalSessions | WS | `/ws/terminal/{id}?token=` | purpose=webssh JWT | +| useTerminalCmdBar | WS DATA | 经 `sendWsData` | 同上 | + +## Closure 表 + +| H | 文件:行号 | 判定 | 理由 | +|---|-----------|------|------| +| H-Auth | useFilesActions.ts:255-494 | SAFE | 全部经 `@/api` JWT | +| H-Path | useFilesActions.ts:297-301,461-464 | SAFE | `validatePathSegment` 拦截 `/` `..` | +| H-Path | useFilesActions.ts:340-347 | RISK | rename 未调 validatePathSegment;后端 sync_v2 校验 | +| H-Path | useFilesActions.ts:71-78,119 | SAFE | `normalizeRemotePath` / `joinRemotePath` | +| H-Delete | useFilesActions.ts:393-414 | SAFE | confirm + 逐条 delete;失败计数 | +| H-Upload | useFilesActions.ts:486-494 | SAFE | server_id + remote_path 参数化 POST | +| H-Auth | useFilesEditor.ts:94-98 | SAFE | readRemoteFileContent + server_id | +| H-Size | useFilesEditor.ts:63-70 | SAFE | 2MB 编辑上限 `FILE_EDITOR_MAX_BYTES` | +| H-Auth | useFilesPermissions.ts:65-78 | SAFE | JWT GET/POST | +| H-Sudo | useFilesPermissions.ts:73-91 | SAFE | 一键 sudo 走后端 re-auth+SSH;无密码回传 | +| H-Storage | useFilesPermissions.ts:56 | SAFE | sessionStorage 仅 hint dismissed 标记 | +| H-Auth | useTerminalSessions.ts:279-281 | SAFE | webssh-token 绑定 server_id | +| H-WS | useTerminalSessions.ts:293-294 | RISK | query token;单人运维已接受 | +| H-WS | useTerminalSessions.ts:350-363 | SAFE | 4001/4003 分支处理 | +| H-Path | useTerminalSessions.ts:159-163,513,578 | RISK | `sanitizeRemotePath` 未拦 `;|&$`;cd 字符串拼接;管理员会话 | +| H-Cmd | useTerminalSessions.ts:648-655 | SAFE | 快捷命令来自 API/内置白名单 | +| H-Cmd | useTerminalCmdBar.ts:21-34 | SAFE | 命令经 WS DATA;后端 dangerous check | +| H-Storage | useTerminalCmdBar.ts:7-33 | RISK | localStorage 存命令历史(非密码);UX 便利 | +| H-Secret | 五文件 | SAFE | 无 password/api_key 存 localStorage | + +**len(H)=18, Closure=18** + +## FINDING + +无 P0/P1 新 FINDING。 + +可选后续(P2/UX):rename 前端补 `validatePathSegment`;`sanitizeRemotePath` 收紧或 cd 改用引号包裹。 + +## DoD + +- [x] len(H) == Closure +- [x] 五文件全文 Read(大文件分段至 EOF) +- [x] 无 v-html / 凭据明文存储 + +## 验证 + +`bash scripts/local_verify.sh` — 全绿 diff --git a/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD6.md b/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD6.md new file mode 100644 index 00000000..cab6be5e --- /dev/null +++ b/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD6.md @@ -0,0 +1,76 @@ +# Phase 1 Delta 审计 — Wave D6(认证链 / Login) + +**日期**: 2026-06-04 +**范围**: 登录页 + auth store + HTTP 客户端 + 路由守卫 + WS URL +**标准**: `standards/line-walk-audit-standard-v2.md` 8 步 + +## 登记 + +| # | 文件 | 语言 | 行数 N | Read 范围 | +|---|------|------|--------|-----------| +| 1 | `frontend/src/pages/LoginPage.vue` | Vue | 244 | 1–244 全文 | +| 2 | `frontend/src/stores/auth.ts` | TS | 132 | 1–132 全文 | +| 3 | `frontend/src/api/index.ts` | TS | 308 | 1–308 全文 | +| 4 | `frontend/src/router/index.ts` | TS | 68 | 1–68 全文 | +| 5 | `frontend/src/utils/wsUrl.ts` | TS | 23 | 1–23 全文 | + +## Step 3 — 规则扫描 H(摘要) + +| 文件 | H 命中 | +|------|--------| +| LoginPage.vue | H-Secret, H-Redirect, H-Lockout, H-XSS | +| auth.ts | H-Token, H-Refresh, H-Profile | +| api/index.ts | H-Auth, H-Refresh, H-401 | +| router/index.ts | H-Guard, H-Redirect | +| wsUrl.ts | H-WS | + +## 入口表 + +| 模块 | 入口 | 鉴权 | +|------|------|------| +| LoginPage | POST `/auth/login` | 公开 | +| LoginPage | GET `/settings/bing-wallpapers` | 公开(auth_jwt 白名单) | +| auth store | POST `/auth/refresh`, `/auth/logout`, GET `/auth/me` | refresh cookie / Bearer | +| api | `fetchAuthed` 全站 API | Bearer + credentials | +| router | `beforeEach` | bootstrapped + isLoggedIn | +| wsUrl | WS URL 构建 | 供 terminal/sync 使用 | + +## Closure 表 + +| H | 文件:行号 | 判定 | 理由 | +|---|-----------|------|------| +| H-Secret | LoginPage.vue:102-103 | SAFE | password 仅组件 ref,不落 localStorage | +| H-Secret | auth.ts:26-27 | SAFE | access token 内存 Pinia ref | +| H-Redirect | LoginPage.vue:178-179 | SAFE | `startsWith('/') && !startsWith('//')` 防 open redirect | +| H-Lockout | LoginPage.vue:186-188 | SAFE | 429 → 15min 本地 lockout UI | +| H-TOTP | LoginPage.vue:182-184 | SAFE | TotpRequiredError → 显示 TOTP 字段 | +| H-XSS | LoginPage.vue:11-12 | SAFE | `{{ error }}` 文本插值,无 v-html | +| H-Refresh | auth.ts:45-66 | SAFE | HttpOnly cookie refresh;失败 silent | +| H-Token | auth.ts:36-38 | SAFE | exp 解码仅用于续期阈值 | +| H-Profile | auth.ts:89-97 | SAFE | 401 → forceLogout | +| H-Auth | api/index.ts:68-70 | SAFE | Bearer 注入 | +| H-Refresh | api/index.ts:19-44 | SAFE | 单飞 refreshInFlight 防并发 | +| H-Refresh | api/index.ts:84-86 | SAFE | 过期前 5min 主动 refresh | +| H-401 | api/index.ts:94-110 | SAFE | 401 重试一次后 forceLogout | +| H-Cookie | api/index.ts:27-28,91 | SAFE | credentials: include | +| H-Guard | router/index.ts:55-65 | SAFE | 非 public 路由需 isLoggedIn | +| H-Guard | router/index.ts:57-58 | SAFE | bootstrapped 前 tryRestoreSession | +| H-Public | router/index.ts:18 | SAFE | Login meta.public | +| H-WS | wsUrl.ts:17-20 | RISK | token 放 query;已接受 | +| H-Wallpaper | LoginPage.vue:121-123 | SAFE | 公开壁纸 API intentional | + +**len(H)=19, Closure=19** + +## FINDING + +无 P0/P1 新 FINDING。 + +## DoD + +- [x] len(H) == Closure +- [x] 五文件全文 Read +- [x] 认证链:内存 token + HttpOnly refresh + 路由守卫 + +## 验证 + +`bash scripts/local_verify.sh` — 全绿 diff --git a/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD7.md b/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD7.md new file mode 100644 index 00000000..40e3334d --- /dev/null +++ b/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD7.md @@ -0,0 +1,78 @@ +# Phase 1 Delta 审计 — Wave D7(Deploy / 本地验证脚本) + +**日期**: 2026-06-04 +**范围**: 门控脚本 + 本地 dev/verify + docker-compose +**标准**: 变更审查(运维/门控;非业务 API 8 步,沿用 Closure 表格式) + +## 登记 + +| # | 文件 | 语言 | 行数 N | Read 范围 | +|---|------|------|--------|-----------| +| 1 | `deploy/pre_deploy_check.sh` | Bash | 299 | 1–299 全文 | +| 2 | `scripts/local_verify.sh` | Bash | 25 | 1–25 全文 | +| 3 | `scripts/local_integration_smoke.sh` | Bash | 91 | 1–91 全文 | +| 4 | `scripts/start-dev.sh` | Bash | 90 | 1–90 全文 | +| 5 | `docker-compose.yml` | YAML | 89 | 1–89 全文 | + +## Step 3 — 规则扫描 H(摘要) + +| 文件 | H 命中 | +|------|--------| +| pre_deploy_check.sh | H-Gate, H-Secret, H-Git | +| local_verify.sh | H-Chain | +| local_integration_smoke.sh | H-Health, H-Auth | +| start-dev.sh | H-DevBind, H-Env | +| docker-compose.yml | H-Port, H-DefaultCreds | + +## 入口表(脚本调用链) + +``` +local_verify.sh + → start-dev.sh (docker mysql+redis, uvicorn :8600) + → local_integration_smoke.sh (/health, JWT 401, unit tests) + → tests/test_api.py + → ruff check server/ -F + +pre_deploy_check.sh (独立 L3 门控,7+2 pre-flight) +``` + +## Closure 表 + +| H | 文件:行号 | 判定 | 理由 | +|---|-----------|------|------| +| H-Gate | pre_deploy_check.sh:50-74 | SAFE | Changelog 日期+≥10 行 | +| H-Gate | pre_deploy_check.sh:76-105 | RISK | 审计路径 `docs/audit/`;delta 报告在 `docs/reports/` 不自动过 Gate2 | +| H-Gate | pre_deploy_check.sh:107-131 | SAFE | test_api 必须存在且通过 | +| H-Gate | pre_deploy_check.sh:134-160 | SAFE | ruff F;未装则 SKIP | +| H-Gate | pre_deploy_check.sh:194-218 | RISK | bandit `-ll` 仅 HIGH;文档写 HIGH/MEDIUM 有漂移 | +| H-Gate | pre_deploy_check.sh:221-258 | RISK | Gate7 用 `HEAD~1` diff;未 commit 改动可能漏检 | +| H-Gate | pre_deploy_check.sh:261-284 | SAFE | shell/text EOL pre-flight | +| H-Chain | local_verify.sh:10-22 | SAFE | 固定顺序 smoke→test_api→ruff | +| H-Health | local_integration_smoke.sh:67-70 | SAFE | /health 200 | +| H-Auth | local_integration_smoke.sh:72-75 | SAFE | /api/servers/ 无 JWT → 401 | +| H-DevBind | start-dev.sh:75-76 | RISK | uvicorn `--host 0.0.0.0` 仅本地 dev | +| H-Env | start-dev.sh:29-32 | SAFE | 无 .env 时 generate_env,不写 git | +| H-Port | docker-compose.yml:40-41 | SAFE | Redis publish 6379 修复本机 uvicorn 连 Redis | +| H-Port | docker-compose.yml:26-27 | RISK | MySQL 3306 暴露主机;dev 惯例 | +| H-DefaultCreds | docker-compose.yml:14-17 | RISK | 默认 dev 密码;非生产 compose | +| H-Secret | docker-compose.yml:68-70 | SAFE | SECRET/API/ENCRYPTION 从 env 注入,无硬编码 | + +**len(H)=16, Closure=16** + +## FINDING + +无 P0/P1 新 FINDING。 + +**运维备注(非阻塞)**: +- Phase1 delta 报告在 `docs/reports/audit-delta-*.md`;部署前若只跑 pre_deploy,需另备 `docs/audit/YYYY-MM-DD-*.md` 或调整 Gate2 路径。 +- 生产部署仍走 SSH + handoff,不依赖本 compose 默认密码。 + +## DoD + +- [x] len(H) == Closure +- [x] 五文件全文 Read +- [x] local_verify 链路可复现 + +## 验证 + +`bash scripts/local_verify.sh` — 全绿 diff --git a/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD8-closure.md b/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD8-closure.md new file mode 100644 index 00000000..ecf4de99 --- /dev/null +++ b/docs/archive/audits/2026-06-04-delta/audit-delta-2026-06-04-waveD8-closure.md @@ -0,0 +1,50 @@ +# Phase 1 Delta 审计 — Wave D8(汇总 Closure) + +**日期**: 2026-06-04 +**范围**: D1–D7 delta 波次汇总 + registry 更新 +**标准**: Phase 1 收尾 DoD + +## 汇总表 + +| 波次 | 主题 | 文件数 | H | P0/P1 | 报告 | +|------|------|--------|---|-------|------| +| D1 | sync/后台 | 5 | 19 | 0 | [waveD1](audit-delta-2026-06-04-waveD1.md) | +| D2 | auth/agent/ws | 5 | 22 | 0 | [waveD2](audit-delta-2026-06-04-waveD2.md) | +| D3 | servers/settings/scripts/webssh | 5 | 24 | 0 | [waveD3](audit-delta-2026-06-04-waveD3.md) | +| D4 | 前端 Push | 5 | 17 | 0 | [waveD4](audit-delta-2026-06-04-waveD4.md) | +| D5 | 前端 Files/Terminal | 5 | 18 | 0 | [waveD5](audit-delta-2026-06-04-waveD5.md) | +| D6 | 认证链 Login | 5 | 19 | 0 | [waveD6](audit-delta-2026-06-04-waveD6.md) | +| D7 | Deploy/本地脚本 | 5 | 16 | 0 | [waveD7](audit-delta-2026-06-04-waveD7.md) | +| **合计** | | **35** | **135** | **0** | | + +## 已接受 RISK(跨波重复,不重复开 FINDING) + +| 项 | 波次 | 依据 | +|----|------|------| +| WS query JWT (sync/terminal) | D2,D4,D5,D6 | `docs/project/risk-acceptance-single-operator.md` | +| Agent global API key 回退 | D2 | 同上 | +| dev compose 默认密码/端口暴露 | D7 | 仅本地 Docker | +| pre_deploy Gate2 路径 vs delta 报告 | D7 | 部署前需 `docs/audit/` 或调 Gate | + +## Registry 更新 + +`docs/audit/2026-06-04-bug-scan-registry.json` 新增 `delta_waves[]`(D1–D8 元数据)。 + +## Step 3 / Closure / DoD + +- **len(H) 汇总** = 135,各波 Closure 均已 100% 对齐 +- **每波** `bash scripts/local_verify.sh` 均通过 +- **无 P0/P1** 待修项进入 Phase 2 + +## 下一步(计划 Phase 2 / L3) + +1. `bash deploy/pre_deploy_check.sh`(L3 门控;需当日 `docs/audit/` 若走生产门控) +2. Phase 2 高险深审:`servers.py` / `sync_v2.py` / `files.py` 等节选全量 8 步 +3. L4 Playwright E2E:`frontend/e2e/full-acceptance.spec.mjs` + +## 验证 + +```bash +bash scripts/local_verify.sh # ✅ 2026-06-04 +pytest tests/ -q # 建议 Phase1 收尾跑一次全量 +``` diff --git a/docs/archive/audits/2026-06-04-frontend-14p-merged.md b/docs/archive/audits/2026-06-04-frontend-14p-merged.md new file mode 100644 index 00000000..0988de9d --- /dev/null +++ b/docs/archive/audits/2026-06-04-frontend-14p-merged.md @@ -0,0 +1,66 @@ +# 前端 14p batch 合并卷 + +> 生成日期: 2026-06-04 · 源文件 3 篇 · 原文 `docs/archive/audits/2026-06-04-frontend-14p/` + +## 批次索引 + +### `audit-frontend-14p-2026-06-04-batch1.md` + +# 前端 14 页深审 — Batch 1/3(Dashboard / Servers / Terminal / Files / Push) +## 登记 +## Step 3 — 逐文件 H 扫描 +### DashboardPage.vue (415) +### ServersPage.vue (477) +### TerminalPage.vue (408) +### FilesPage.vue (38) +### PushPage.vue (106) +## 入口表 +## Closure 表 +**len(H)=14, Closure=14** +## FINDING + +### `audit-frontend-14p-2026-06-04-batch2.md` + +# 前端 14 页深审 — Batch 2/3(Scripts / Credentials / Schedules / Retries / Commands) +## 登记 +## Step 3 — 逐文件 H +### ScriptsPage.vue (535) +### CredentialsPage.vue (400) +### SchedulesPage.vue (295) +### RetriesPage.vue (191) +### CommandsPage.vue (168) +## Closure 表 +**len(H)=15, Closure=15** +## FINDING +无 P0/P1 新 FINDING。 + +### `audit-frontend-14p-2026-06-04-batch3.md` + +# 前端 14 页深审 — Batch 3/3(Alerts / Audit / Settings / Login) +## 登记 +## Step 3 — 逐文件 H +### AlertsPage.vue (184) +### AuditPage.vue (154) +### SettingsPage.vue (497) +### LoginPage.vue (244) +## Closure 表 +**len(H)=14, Closure=14** +## 14 页汇总 +| 页面 | 行数 | 批次 | P0/P1 | +## FINDING + +## Closure 摘录(合并) + +| H | 行号 | 规则 | 命中 | +| H1 | 237,304-341 | API JWT | GET stats/alerts/audit/servers | +| H2 | 393-397 | WS | useWebSocket 刷新 stats | +| H3 | 383-387 | 路由 | server_id query 跳转 | +| H4 | 268-345 | 错误 | snackbar 非静默 | +| H5 | 405 | 副作用 | bing-wallpaper sync POST | +| H | 行号 | 规则 | +| H1 | 358-390 | CRUD /scripts JWT | +| H2 | 415-419 | exec script_id + server_ids | +| H3 | 463-467 | quick command exec | +| H4 | 527-528 | poll timer cleanup | +| H1 | 142-160 | GET /alert-history 分页+过滤 | +| H2 | 147 | loadStats 失败 snackbar | diff --git a/docs/archive/audits/2026-06-04-frontend-14p/audit-frontend-14p-2026-06-04-batch1.md b/docs/archive/audits/2026-06-04-frontend-14p/audit-frontend-14p-2026-06-04-batch1.md new file mode 100644 index 00000000..1eab19ee --- /dev/null +++ b/docs/archive/audits/2026-06-04-frontend-14p/audit-frontend-14p-2026-06-04-batch1.md @@ -0,0 +1,96 @@ +# 前端 14 页深审 — Batch 1/3(Dashboard / Servers / Terminal / Files / Push) + +**日期**: 2026-06-04 +**标准**: `standards/line-walk-audit-standard-v2.md` 8 步 +**说明**: 按路由顺序;每页 **1–N 全文 Read**;编排页含 composable 入口追踪 + +## 登记 + +| # | 文件 | 行数 N | Read 范围 | +|---|------|--------|-----------| +| 1 | `frontend/src/pages/DashboardPage.vue` | 415 | 1–415 全文 | +| 2 | `frontend/src/pages/ServersPage.vue` | 477 | 1–477 全文 | +| 3 | `frontend/src/pages/TerminalPage.vue` | 408 | 1–408 全文 | +| 4 | `frontend/src/pages/FilesPage.vue` | 38 | 1–38 全文 | +| 5 | `frontend/src/pages/PushPage.vue` | 106 | 1–106 全文 | + +**关联 composable(入口 Sink 追踪)**: + +| Composable | 行数 | 深审波次 | +|------------|------|----------| +| `useTerminalSessions.ts` | 749 | D5 + P2-5 webssh 链 | +| `useFilesPage.ts` + files/* | — | D5 + P2-1 | +| `usePushPage.ts` + push/* | — | D4 + P2-2 | + +## Step 3 — 逐文件 H 扫描 + +### DashboardPage.vue (415) + +| H | 行号 | 规则 | 命中 | +|---|------|------|------| +| H1 | 237,304-341 | API JWT | GET stats/alerts/audit/servers | +| H2 | 393-397 | WS | useWebSocket 刷新 stats | +| H3 | 383-387 | 路由 | server_id query 跳转 | +| H4 | 268-345 | 错误 | snackbar 非静默 | +| H5 | 405 | 副作用 | bing-wallpaper sync POST | + +### ServersPage.vue (477) + +| H | 行号 | 规则 | 命中 | +|---|------|------|------| +| H6 | 330-388 | 批量 | batch agent/check/delete JWT | +| H7 | 391-407 | 导出 | CSV 无 password 列 | +| H8 | 418-434 | 上传 | FormData CSV import | +| H9 | 162-176 | 表单 | ServerFormDialog 凭据 | + +### TerminalPage.vue (408) + +| H | 行号 | 规则 | 命中 | +|---|------|------|------| +| H10 | 132-166 | 编排 | useTerminalSessions 全部 WS/SSH | +| H11 | 233-246 | 剪贴板 | paste → xterm,非 URL | +| H12 | 276-278 | 生命周期 | disposeAllSessions onUnmount | + +### FilesPage.vue (38) + +| H | 行号 | 规则 | 命中 | +|---|------|------|------| +| H13 | 33-37 | 编排 | provideFilesPage + useFilesPage | + +### PushPage.vue (106) + +| H | 行号 | 规则 | 命中 | +|---|------|------|------| +| H14 | 105 | 编排 | usePushPage 解构 form/progress/preview | + +## 入口表 + +| 页面 | 用户输入 | API / WS | +|------|----------|----------| +| Dashboard | search | GET /servers/*, /alert-history/, /audit/ | +| Servers | search, CSV, form | CRUD /servers, batch/*, import | +| Terminal | cmd, quickCmd | POST /auth/webssh-token, WS /ws/terminal | +| Files | —(组件内) | GET /files/browse, sync/read-file | +| Push | path, servers | POST /sync/*, WS /ws/sync | + +## Closure 表 + +| H | 判定 | 依据 | +|---|------|------| +| H1-H5 | SAFE | JWT http;错误有 snackbar | +| H2 | RISK | WS query token(已接受 ADR-011) | +| H6-H9 | SAFE | 批量需 JWT;CSV 无凭据导出 | +| H10-H12 | SAFE | WebSSH token 短期;dispose 清理 | +| H13-H14 | SAFE | 逻辑在已审 composable,页壳无 sink | + +**len(H)=14, Closure=14** + +## FINDING + +无 P0/P1 新 FINDING。 + +## DoD + +- [x] 5 页全文 Read(Terminal 408 行含 style) +- [x] composable 入口表完整 +- [x] len(H)==Closure diff --git a/docs/archive/audits/2026-06-04-frontend-14p/audit-frontend-14p-2026-06-04-batch2.md b/docs/archive/audits/2026-06-04-frontend-14p/audit-frontend-14p-2026-06-04-batch2.md new file mode 100644 index 00000000..90823d74 --- /dev/null +++ b/docs/archive/audits/2026-06-04-frontend-14p/audit-frontend-14p-2026-06-04-batch2.md @@ -0,0 +1,77 @@ +# 前端 14 页深审 — Batch 2/3(Scripts / Credentials / Schedules / Retries / Commands) + +**日期**: 2026-06-04 +**标准**: 8 步逐行 + +## 登记 + +| # | 文件 | 行数 N | Read 范围 | +|---|------|--------|-----------| +| 1 | `frontend/src/pages/ScriptsPage.vue` | 535 | 1–535 全文 | +| 2 | `frontend/src/pages/CredentialsPage.vue` | 400 | 1–400 全文 | +| 3 | `frontend/src/pages/SchedulesPage.vue` | 295 | 1–295 全文 | +| 4 | `frontend/src/pages/RetriesPage.vue` | 191 | 1–191 全文 | +| 5 | `frontend/src/pages/CommandsPage.vue` | 168 | 1–168 全文 | + +## Step 3 — 逐文件 H + +### ScriptsPage.vue (535) + +| H | 行号 | 规则 | +|---|------|------| +| H1 | 358-390 | CRUD /scripts JWT | +| H2 | 415-419 | exec script_id + server_ids | +| H3 | 463-467 | quick command exec | +| H4 | 527-528 | poll timer cleanup | + +### CredentialsPage.vue (400) + +| H | 行号 | 规则 | +|---|------|------| +| H5 | 253-255 | GET 列表无密码 | +| H6 | 323-327 | encrypted_pw POST only | +| H7 | 306,340,357 | 编辑留空不改 | +| H8 | 346-347 | SSH 私钥提交时传输 | + +### SchedulesPage.vue (295) + +| H | 行号 | 规则 | +|---|------|------| +| H9 | 177 | cron 正则 `^[\d\s*/,\-]+$` | +| H10 | 173-189 | buildSchedulePayload 校验 | +| H11 | 263-267 | runNow → POST /sync/files | + +### RetriesPage.vue (191) + +| H | 行号 | 规则 | +|---|------|------| +| H12 | 129-134 | 客户端 filter(展示) | +| H13 | 146,160 | retry/delete JWT | + +### CommandsPage.vue (168) + +| H | 行号 | 规则 | +|---|------|------| +| H14 | 139-153 | 只读 command-logs / ssh-sessions | +| H15 | 50 | command 文本展示(后端已存) | + +## Closure 表 + +| H | 判定 | 依据 | +|---|------|------| +| H1-H4 | SAFE/RISK | exec 走后端;H3 RISK 临时命令(后端 dangerous check) | +| H5-H8 | SAFE | 无 localStorage;API 脱敏 | +| H9-H11 | SAFE | cron/路径校验;sync JWT | +| H12-H13 | SAFE | 只读+操作 JWT | +| H14-H15 | SAFE | 只读审计视图 | + +**len(H)=15, Closure=15** + +## FINDING + +无 P0/P1 新 FINDING。 + +## DoD + +- [x] 5 页全文 Read +- [x] len(H)==Closure diff --git a/docs/archive/audits/2026-06-04-frontend-14p/audit-frontend-14p-2026-06-04-batch3.md b/docs/archive/audits/2026-06-04-frontend-14p/audit-frontend-14p-2026-06-04-batch3.md new file mode 100644 index 00000000..5afbf6e1 --- /dev/null +++ b/docs/archive/audits/2026-06-04-frontend-14p/audit-frontend-14p-2026-06-04-batch3.md @@ -0,0 +1,93 @@ +# 前端 14 页深审 — Batch 3/3(Alerts / Audit / Settings / Login) + +**日期**: 2026-06-04 +**标准**: 8 步逐行 + +## 登记 + +| # | 文件 | 行数 N | Read 范围 | +|---|------|--------|-----------| +| 1 | `frontend/src/pages/AlertsPage.vue` | 184 | 1–184 全文 | +| 2 | `frontend/src/pages/AuditPage.vue` | 154 | 1–154 全文 | +| 3 | `frontend/src/pages/SettingsPage.vue` | 497 | 1–497 全文 | +| 4 | `frontend/src/pages/LoginPage.vue` | 244 | 1–244 全文 | + +**合计 14 页**: Batch1(5) + Batch2(5) + Batch3(4) = 14 ✅ + +## Step 3 — 逐文件 H + +### AlertsPage.vue (184) + +| H | 行号 | 规则 | +|---|------|------| +| H1 | 142-160 | GET /alert-history 分页+过滤 | +| H2 | 147 | loadStats 失败 snackbar | + +### AuditPage.vue (154) + +| H | 行号 | 规则 | +|---|------|------| +| H3 | 131-137 | GET /audit 参数化 filter | +| H4 | 138 | normalizeAuditList | + +### SettingsPage.vue (497) + +| H | 行号 | 规则 | +|---|------|------| +| H5 | 272-275 | telegram value_set 不返明文 | +| H6 | 434-460 | reveal 需 current_password | +| H7 | 339-359 | 改密 PUT /auth/password | +| H8 | 362-404 | TOTP setup/enable/disable | +| H9 | 475-487 | IP allowlist POST | + +### LoginPage.vue (244) + +| H | 行号 | 规则 | +|---|------|------| +| H10 | 177 | auth.login 内存 token | +| H11 | 179 | redirect 防开放重定向 `//` | +| H12 | 186-188 | 429 lockout 15min | +| H13 | 102-103 | password ref 非 localStorage | +| H14 | 123 | bing wallpapers 公开 GET | + +## Closure 表 + +| H | 判定 | 依据 | +|---|------|------| +| H1-H2 | SAFE | 只读告警 | +| H3-H4 | SAFE | 只读审计+normalize | +| H5-H9 | SAFE | 敏感 reveal re-auth;TOTP 流程完整 | +| H10-H13 | SAFE | 无持久化密码;redirect 校验 | +| H14 | SAFE | 壁纸 URL 无凭据 | + +**len(H)=14, Closure=14** + +## 14 页汇总 + +| 页面 | 行数 | 批次 | P0/P1 | +|------|------|------|-------| +| DashboardPage | 415 | B1 | 0 | +| ServersPage | 477 | B1 | 0 | +| TerminalPage | 408 | B1 | 0 | +| FilesPage | 38 | B1 | 0 | +| PushPage | 106 | B1 | 0 | +| ScriptsPage | 535 | B2 | 0 | +| CredentialsPage | 400 | B2 | 0 | +| SchedulesPage | 295 | B2 | 0 | +| RetriesPage | 191 | B2 | 0 | +| CommandsPage | 168 | B2 | 0 | +| AlertsPage | 184 | B3 | 0 | +| AuditPage | 154 | B3 | 0 | +| SettingsPage | 497 | B3 | 0 | +| LoginPage | 244 | B3 | 0 | +| **合计** | **4112** | **3 批** | **0** | + +## FINDING + +无 P0/P1 新 FINDING。 + +## DoD + +- [x] 14/14 页全文 Read +- [x] 3 批 Closure 合计 43 H 全对齐 +- [x] 4112 行 frontend pages 覆盖完成 diff --git a/docs/archive/audits/2026-06-04-line-walk-merged.md b/docs/archive/audits/2026-06-04-line-walk-merged.md new file mode 100644 index 00000000..4e82b4a9 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk-merged.md @@ -0,0 +1,1431 @@ +# Bug Scan Line-Walk 合并卷 + +> 生成日期: 2026-06-04 · 源文件 74 篇 · 原文 `docs/archive/audits/2026-06-04-line-walk/` + +## 批次索引 + +### `audit-bug-line-walk-2026-06-04-batch1.md` + +# BUG 专项逐行走读 — Batch 1(2026-06-04) +## 范围(5 文件,全文 Read) +## Step 3 规则扫描(BUG 相关) +## Closure 表(BUG) +| install.html | 691-694 | **FINDING** | P1 | create-admin 成功后未检查 `/lock`;失败仍进 step5 | +| install.py | 648 | **FINDING** | P1 | create-admin 与 lock 非原子;锁定前 install API 仍可被未认证调用 | +| script_job_callback.py | 17-36 | **FINDING** | P2 | `server_ids==[]` 时直接 `return "completed"`,忽略 results 中 pending | +## FINDING 列表(8 字段) +### BUG-06-04-01 · P1 · install.html:691-694 +### BUG-06-04-02 · P1 · install.py:648 +### BUG-06-04-03 · P2 · script_job_callback.py:32-33 +## Step 7 统计 + +### `audit-bug-line-walk-2026-06-04-batch10.md` + +# BUG 专项逐行走读 — Batch 10(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch11.md` + +# BUG 专项逐行走读 — Batch 11(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch12.md` + +# BUG 专项逐行走读 — Batch 12(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch13.md` + +# BUG 专项逐行走读 — Batch 13(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch14.md` + +# BUG 专项逐行走读 — Batch 14(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch17.md` + +# BUG 专项逐行走读 — Batch 17(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch18.md` + +# BUG 专项逐行走读 — Batch 18(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch19.md` + +# BUG 专项逐行走读 — Batch 19(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch2.md` + +# BUG 专项逐行走读 — Batch 2(2026-06-04) +## 范围(5 文件,全文 Read) +## Step 3 规则扫描(BUG 相关) +## Closure 表(BUG) +| schedule_runner.py | 125-217 | **FINDING** | P1 | `run_mode=once` 执行前禁用;失败时仅 cron 回滚 `last_run_at`,once 永久失活 | +## FINDING 列表(8 字段) +### BUG-06-04-04 · P1 · schedule_runner.py:212-217 +## Step 7 统计 +schedule_runner.py 1 FINDING(已修) +## Step 8 DoD +- [x] Closure 全表 +- [x] FINDING 含 `文件:行号` + +### `audit-bug-line-walk-2026-06-04-batch20.md` + +# BUG 专项逐行走读 — Batch 20(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch21.md` + +# BUG 专项逐行走读 — Batch 21(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch22.md` + +# BUG 专项逐行走读 — Batch 22(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch23.md` + +# BUG 专项逐行走读 — Batch 23(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch24.md` + +# BUG 专项逐行走读 — Batch 24(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch25.md` + +# BUG 专项逐行走读 — Batch 25(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch26.md` + +# BUG 专项逐行走读 — Batch 26(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch27.md` + +# BUG 专项逐行走读 — Batch 27(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch28.md` + +# BUG 专项逐行走读 — Batch 28(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch29.md` + +# BUG 专项逐行走读 — Batch 29(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch3.md` + +# BUG 专项逐行走读 — Batch 3(2026-06-04) +## 范围(5 文件,全文 Read) +## Step 3 规则扫描(BUG 相关) +## Closure 表(BUG) +| retry_runner.py | 85-100 | **FINDING** | P2 | 仅 `failed==0 && completed>0` 成功;全 cancel 时 job 永久 pending | +| sync_engine_v2.py | 241-242 | **FINDING** | P2 | 任务异常时 `results` 缺项,`failed+completed` 可小于 `total` | +| servers.py | 276-279 | **FINDING** | P2 | Redis heartbeat 数可大于 DB `total`,在线率 >100% | +## FINDING 列表 +### BUG-06-04-05 · P2 · retry_runner.py +### BUG-06-04-06 · P2 · sync_engine_v2.py:241-242 +### BUG-06-04-07 · P2 · servers.py:276-279 +## Step 7 统计 + +### `audit-bug-line-walk-2026-06-04-batch30.md` + +# BUG 专项逐行走读 — Batch 30(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch31.md` + +# BUG 专项逐行走读 — Batch 31(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch32.md` + +# BUG 专项逐行走读 — Batch 32(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch33.md` + +# BUG 专项逐行走读 — Batch 33(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch35.md` + +# BUG 专项逐行走读 — Batch 35(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch36.md` + +# BUG 专项逐行走读 — Batch 36(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch37.md` + +# BUG 专项逐行走读 — Batch 37(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch38.md` + +# BUG 专项逐行走读 — Batch 38(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch39.md` + +# BUG 专项逐行走读 — Batch 39(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch4.md` + +# BUG 专项逐行走读 — Batch 4(2026-06-04) +## 范围(5 文件,全文 Read) +## Step 3 规则扫描(BUG 相关) +## Closure 表(BUG) +| usePushProgress.ts | 184-194 | **FINDING** | P1 | POST 阻塞至推送结束才连 WS,进度事件全部丢失 | +| sync_v2.py | 125-182 | **FINDING** | P2 | 远程 file-ops 后未 `invalidate_browse_cache`,304 陈旧目录 | +| heartbeat_flush.py | 57-89 | **FINDING** | P2 | 每服务器独立 session/commit;已删服务器仍计 flushed | +## FINDING 列表 +### BUG-06-04-08 · P1 · usePushProgress.ts +### BUG-06-04-09 · P2 · sync_v2 file-ops +### BUG-06-04-10 · P2 · heartbeat_flush.py +## Step 7 统计 + +### `audit-bug-line-walk-2026-06-04-batch40.md` + +# BUG 专项逐行走读 — Batch 40(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch41.md` + +# BUG 专项逐行走读 — Batch 41(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch42.md` + +# BUG 专项逐行走读 — Batch 42(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch43.md` + +# BUG 专项逐行走读 — Batch 43(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch44.md` + +# BUG 专项逐行走读 — Batch 44(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch45.md` + +# BUG 专项逐行走读 — Batch 45(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch46.md` + +# BUG 专项逐行走读 — Batch 46(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch47.md` + +# BUG 专项逐行走读 — Batch 47(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch48.md` + +# BUG 专项逐行走读 — Batch 48(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch49.md` + +# BUG 专项逐行走读 — Batch 49(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch5.md` + +# BUG 专项逐行走读 — Batch 5(2026-06-04) +## 范围(5 文件,全文 Read) +## Step 3 规则扫描(BUG 相关) +## Closure 表(BUG) +| agent.py | 34, 226-227 | **FINDING** | P2 | 注释写 TTL=1.5×flush,实际 600=600,停 Agent 后 key 可能在 flush 前过期 | +| sync_v2.py | 1474-1650 | **FINDING** | P2 | write/chmod/compress/decompress 未失效 browse ETag(Batch4 已覆盖 ops/upload) | +## FINDING 列表 +### BUG-06-04-11 · P2 · agent.py REDIS_KEY_EXPIRE +### BUG-06-04-12 · P2 · sync_v2 远程写路径 +## Step 7 统计 +agent.py 1 FINDING(已修) +sync_v2.py 1 FINDING(已修) + +### `audit-bug-line-walk-2026-06-04-batch50.md` + +# BUG 专项逐行走读 — Batch 50(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch51.md` + +# BUG 专项逐行走读 — Batch 51(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch52.md` + +# BUG 专项逐行走读 — Batch 52(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch53.md` + +# BUG 专项逐行走读 — Batch 53(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch54.md` + +# BUG 专项逐行走读 — Batch 54(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch55.md` + +# BUG 专项逐行走读 — Batch 55(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch56.md` + +# BUG 专项逐行走读 — Batch 56(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch57.md` + +# BUG 专项逐行走读 — Batch 57(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch58.md` + +# BUG 专项逐行走读 — Batch 58(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch59.md` + +# BUG 专项逐行走读 — Batch 59(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch6.md` + +# BUG 专项逐行走读 — Batch 6(2026-06-04) +## 范围(5 文件,全文 Read) +## Step 3 规则扫描(BUG 相关) +## Closure 表(BUG) +| auth_service.py | 239-246 | **FINDING** | P1 | 已轮换 refresh 不在 Redis 时仅 401,不升 `token_version`,被盗旧 token 与合法新会话可并存 | +| auth_service.py | 597-598 | **FINDING** | P2 | Redis 异常时 `_check_refresh_token` 曾返回 False,误拒合法刷新 | +| useFilesBrowse.ts | 187-195 | **FINDING** | P2 | 5s 内命中本地缓存直接 return,跳过后端 ETag,变更后仍显示旧列表 | +## FINDING 列表 +### BUG-06-04-13 · P1 · auth_service refresh 重用 +### BUG-06-04-14 · P2 · auth Redis 不可用 +### BUG-06-04-15 · P2 · useFilesBrowse 本地缓存短路 +## Step 7 统计 + +### `audit-bug-line-walk-2026-06-04-batch60.md` + +# BUG 专项逐行走读 — Batch 60(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch61.md` + +# BUG 专项逐行走读 — Batch 61(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch62.md` + +# BUG 专项逐行走读 — Batch 62(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch63.md` + +# BUG 专项逐行走读 — Batch 63(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch64.md` + +# BUG 专项逐行走读 — Batch 64(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch65.md` + +# BUG 专项逐行走读 — Batch 65(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch66.md` + +# BUG 专项逐行走读 — Batch 66(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch67.md` + +# BUG 专项逐行走读 — Batch 67(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch68.md` + +# BUG 专项逐行走读 — Batch 68(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch69.md` + +# BUG 专项逐行走读 — Batch 69(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch7.md` + +# BUG 专项逐行走读 — Batch 7(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch70.md` + +# BUG 专项逐行走读 — Batch 70(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch71.md` + +# BUG 专项逐行走读 — Batch 71(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch72.md` + +# BUG 专项逐行走读 — Batch 72(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch73.md` + +# BUG 专项逐行走读 — Batch 73(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch74.md` + +# BUG 专项逐行走读 — Batch 74(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch75.md` + +# BUG 专项逐行走读 — Batch 75(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch76.md` + +# BUG 专项逐行走读 — Batch 76(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch77.md` + +# BUG 专项逐行走读 — Batch 77(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch8.md` + +# BUG 专项逐行走读 — Batch 8(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +### `audit-bug-line-walk-2026-06-04-batch9.md` + +# BUG 专项逐行走读 — Batch 9(2026-06-04) +## 范围 +| 文件 | N | Read | H | FINDING | +## Step 3 规则扫描 +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 +## Closure 表 +## Step 8 DoD +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) +## 验证 +## FINDING 列表 +无(0 FINDING)。 + +## Closure 摘录(合并) + +| install.html | 691-694 | **FINDING** | P1 | create-admin 成功后未检查 `/lock`;失败仍进 step5 | +| install.py | 648 | **FINDING** | P1 | create-admin 与 lock 非原子;锁定前 install API 仍可被未认证调用 | +| script_job_callback.py | 17-36 | **FINDING** | P2 | `server_ids==[]` 时直接 `return "completed"`,忽略 results 中 pending | +| install.py | 509-526 | SAFE | — | state 不写 db_pass(有意);刷新 step4 需重填密码属 UX | +| agent.py | 154-180 | SAFE | — | 无 server_id / 未知 server 丢弃,设计如此 | +| agent.py | 86-94 | SAFE | — | 非数字指标跳过 | +| servers.py | 245-279 | RISK/BUG | P2 | `offline=total-online` 与列表 Redis 覆盖逻辑在「从未装 Agent」服务器上可能偏差;非回归 | +| servers.py | 205-212 | SAFE | — | is_online 过滤后重算 total | +| 文件 | N | Read | H | FINDING | +| `server/api/assets.py` | 358 | 1..358 | 19 | 0 | +| `server/api/files.py` | 77 | 1..77 | 3 | 0 | +| `server/api/health.py` | 67 | 1..67 | 8 | 0 | +| `server/api/scripts.py` | 418 | 1..418 | 18 | 0 | +| `server/api/search.py` | 119 | 1..119 | 7 | 0 | +| `server/api/__init__.py` | 1 | 1..1 | 0 | 0 | +| `server/api/dependencies.py` | 180 | 1..180 | 0 | 0 | +| `server/api/remote_path_validation.py` | 33 | 1..33 | 0 | 0 | +| `server/api/schema_path_validators.py` | 59 | 1..59 | 1 | 0 | +| `server/api/schemas.py` | 641 | 1..641 | 2 | 0 | +| `server/api/install.py` | 722 | 1..722 | 45 | 0 | +| `server/api/agent.py` | 362 | 1..362 | 16 | 0 | +| `server/api/auth_jwt.py` | 275 | 1..275 | 6 | 0 | +| `server/application/services/script_service.py` | 713 | 1..713 | 18 | 0 | +| `server/application/services/sync_engine_v2.py` | 540 | 1..540 | 16 | 0 | +| `server/application/services/auth_service.py` | 640 | 1..640 | 13 | 0 | +| `frontend/src/composables/terminal/useTerminalSessions.ts` | 750 | connect/reconnect/WebSSH | +| `server/application/services/files_browse_service.py` | 167 | 1..167 | 6 | 0 | +| `server/application/services/script_job_callback.py` | 110 | 1..110 | 2 | 0 | +| `server/application/services/script_jobs.py` | 125 | 1..125 | 1 | 0 | +| `server/application/services/server_service.py` | 68 | 1..68 | 0 | 0 | +| `server/application/services/sync_service.py` | 43 | 1..43 | 1 | 0 | +| `server/application/__init__.py` | 1 | 1..1 | 0 | 0 | +| `server/application/services/__init__.py` | 8 | 1..8 | 0 | 0 | +| `server/background/heartbeat_flush.py` | 102 | 1..102 | 3 | 0 | +| `server/background/retry_runner.py` | 148 | 1..148 | 7 | 0 | +| `server/background/schedule_runner.py` | 236 | 1..236 | 6 | 0 | +| `server/background/script_execution_flush.py` | 45 | 1..45 | 0 | 0 | +| `server/background/self_monitor.py` | 108 | 1..108 | 2 | 0 | +| `server/background/__init__.py` | 7 | 1..7 | 0 | 0 | +| `server/background/ip_allowlist_refresh.py` | 99 | 1..99 | 1 | 0 | +| `server/background/upload_staging_cleanup.py` | 60 | 1..60 | 0 | 0 | +| `server/__init__.py` | 5 | 1..5 | 0 | 0 | +| `server/config.py` | 217 | 1..217 | 3 | 0 | +| `server/main.py` | 669 | 1..669 | 21 | 0 | +| `server/infrastructure/database/__init__.py` | 1 | 1..1 | 0 | 0 | +| `server/infrastructure/database/admin_repo.py` | 62 | 1..62 | 4 | 0 | +| `server/infrastructure/database/audit_log_repo.py` | 86 | 1..86 | 5 | 0 | +| `server/infrastructure/database/crypto.py` | 54 | 1..54 | 0 | 0 | +| `server/infrastructure/database/db_credential_repo.py` | 38 | 1..38 | 2 | 0 | +| `server/infrastructure/database/engine_compat.py` | 23 | 1..23 | 0 | 0 | +| `server/infrastructure/database/migrations.py` | 180 | 1..180 | 6 | 0 | +| `server/infrastructure/database/password_preset_repo.py` | 38 | 1..38 | 2 | 0 | +| `server/infrastructure/database/platform_node_repo.py` | 83 | 1..83 | 7 | 0 | +| `server/infrastructure/database/push_schedule_repo.py` | 95 | 1..95 | 6 | 0 | +| `server/infrastructure/database/script_repo.py` | 90 | 1..90 | 8 | 0 | +| `server/infrastructure/database/server_repo.py` | 144 | 1..144 | 8 | 0 | +| `server/infrastructure/database/session.py` | 125 | 1..125 | 6 | 0 | +| `server/infrastructure/database/setting_repo.py` | 31 | 1..31 | 5 | 0 | +| `server/infrastructure/database/ssh_key_preset_repo.py` | 38 | 1..38 | 2 | 0 | +| `server/infrastructure/database/ssh_session_repo.py` | 83 | 1..83 | 6 | 0 | +| `server/infrastructure/database/sync_log_repo.py` | 120 | 1..120 | 6 | 0 | +| `server/infrastructure/ssh/__init__.py` | 1 | 1..1 | 0 | 0 | +| `server/infrastructure/ssh/asyncssh_pool.py` | 437 | 1..437 | 43 | 0 | +| `server/infrastructure/ssh/files_capability.py` | 62 | 1..62 | 3 | 0 | +| `server/infrastructure/ssh/remote_archive.py` | 128 | 1..128 | 2 | 0 | +| `server/infrastructure/ssh/remote_probe.py` | 85 | 1..85 | 5 | 0 | +| files_browse_service.py | 路径 `..`、SSH 命令引用 | +| servers.py | `offline = total - online`、Redis 脏 key | +| DashboardPage.vue | 是否混用 stats / 列表在线 | +| sync_engine_v2.py | `gather(return_exceptions=True)` | +| `server/infrastructure/ssh/remote_shell.py` | 63 | 1..63 | 5 | 0 | +| `server/infrastructure/__init__.py` | 15 | 1..15 | 0 | 0 | +| `server/infrastructure/agent_url.py` | 11 | 1..11 | 0 | 0 | +| `server/infrastructure/redis/__init__.py` | 1 | 1..1 | 0 | 0 | +| `server/infrastructure/redis/client.py` | 97 | 1..97 | 0 | 0 | +| `server/infrastructure/redis/script_callback_rate.py` | 44 | 1..44 | 1 | 0 | +| `server/infrastructure/redis/script_execution_store.py` | 422 | 1..422 | 10 | 0 | +| `server/infrastructure/subscription_parser.py` | 121 | 1..121 | 0 | 0 | +| `server/infrastructure/telegram/__init__.py` | 222 | 1..222 | 0 | 0 | +| `server/domain/__init__.py` | 3 | 1..3 | 0 | 0 | +| `server/domain/models/__init__.py` | 431 | 1..431 | 0 | 0 | +| `server/domain/repositories/__init__.py` | 125 | 1..125 | 0 | 0 | +| `server/utils/files_chmod_policy.py` | 29 | 1..29 | 0 | 0 | +| `server/utils/files_elevation.py` | 55 | 1..55 | 0 | 0 | +| `server/utils/posix_paths.py` | 122 | 1..122 | 2 | 0 | +| `server/utils/sync_error_message.py` | 72 | 1..72 | 0 | 0 | +| `server/utils/text_io.py` | 51 | 1..51 | 4 | 0 | +| `server/utils/unix_ls.py` | 115 | 1..115 | 1 | 0 | +| `frontend/src/pages/AlertsPage.vue` | 184 | 1..184 | 0 | 0 | +| `frontend/src/pages/AuditPage.vue` | 154 | 1..154 | 0 | 0 | +| `frontend/src/pages/CommandsPage.vue` | 164 | 1..164 | 0 | 0 | +| `frontend/src/pages/CredentialsPage.vue` | 390 | 1..390 | 0 | 0 | +| `frontend/src/pages/DashboardPage.vue` | 410 | 1..410 | 0 | 0 | +| `frontend/src/pages/FilesPage.vue` | 38 | 1..38 | 0 | 0 | +| `frontend/src/pages/LoginPage.vue` | 243 | 1..243 | 0 | 0 | +| `frontend/src/pages/PushPage.vue` | 106 | 1..106 | 0 | 0 | +| `frontend/src/pages/RetriesPage.vue` | 191 | 1..191 | 0 | 0 | +| `frontend/src/pages/SchedulesPage.vue` | 295 | 1..295 | 0 | 0 | +| `frontend/src/pages/ScriptsPage.vue` | 531 | 1..531 | 0 | 0 | +| `frontend/src/pages/ServersPage.vue` | 477 | 1..477 | 0 | 0 | +| `frontend/src/pages/SettingsPage.vue` | 434 | 1..434 | 0 | 0 | +| `frontend/src/pages/TerminalPage.vue` | 408 | 1..408 | 0 | 0 | +| `frontend/src/composables/terminal/useTerminalSessions.ts` | 749 | 1..749 | 0 | 0 | +| `frontend/src/composables/files/constants.ts` | 5 | 1..5 | 0 | 0 | +| `frontend/src/composables/files/filesPageContext.ts` | 21 | 1..21 | 0 | 0 | +| `frontend/src/composables/files/index.ts` | 9 | 1..9 | 0 | 0 | +| `frontend/src/composables/files/useFilesActions.ts` | 609 | 1..609 | 0 | 0 | +| `frontend/src/composables/files/useFilesBrowse.ts` | 267 | 1..267 | 0 | 0 | +| usePushProgress.ts | 184-194 | **FINDING** | P1 | POST 阻塞至推送结束才连 WS,进度事件全部丢失 | +| sync_v2.py | 125-182 | **FINDING** | P2 | 远程 file-ops 后未 `invalidate_browse_cache`,304 陈旧目录 | +| heartbeat_flush.py | 57-89 | **FINDING** | P2 | 每服务器独立 session/commit;已删服务器仍计 flushed | +| files.py | 39-77 | SAFE | — | 只读 browse;失效由 mutation 侧负责 | +| sync_v2.py | 73-90 | SAFE | — | 同步 API 本身无 ETag | +| `frontend/src/composables/files/useFilesEditor.ts` | 163 | 1..163 | 0 | 0 | +| `frontend/src/composables/files/useFilesNavigation.ts` | 210 | 1..210 | 0 | 0 | +| `frontend/src/composables/files/useFilesPage.ts` | 272 | 1..272 | 0 | 0 | +| `frontend/src/composables/files/useFilesPermissions.ts` | 101 | 1..101 | 0 | 0 | +| `frontend/src/composables/files/useFilesSelection.ts` | 28 | 1..28 | 0 | 0 | +| `frontend/src/composables/push/index.ts` | 3 | 1..3 | 0 | 0 | +| `frontend/src/composables/push/labels.ts` | 57 | 1..57 | 0 | 0 | +| `frontend/src/composables/push/types.ts` | 72 | 1..72 | 0 | 0 | +| `frontend/src/composables/push/usePushForm.ts` | 101 | 1..101 | 0 | 0 | +| `frontend/src/composables/push/usePushLogs.ts` | 146 | 1..146 | 0 | 0 | +| `frontend/src/composables/push/usePushPage.ts` | 40 | 1..40 | 0 | 0 | +| `frontend/src/composables/push/usePushPreview.ts` | 104 | 1..104 | 0 | 0 | +| `frontend/src/composables/push/usePushProgress.ts` | 288 | 1..288 | 0 | 0 | +| `frontend/src/composables/push/usePushServers.ts` | 115 | 1..115 | 0 | 0 | +| `frontend/src/composables/servers/useServerFormDialog.ts` | 255 | 1..255 | 0 | 0 | +| `frontend/src/composables/terminal/termNativeStore.ts` | 62 | 1..62 | 0 | 0 | +| `frontend/src/composables/terminal/types.ts` | 69 | 1..69 | 0 | 0 | +| `frontend/src/composables/terminal/useTerminalCmdBar.ts` | 108 | 1..108 | 0 | 0 | +| `frontend/src/composables/terminal/useTerminalSettings.ts` | 54 | 1..54 | 0 | 0 | +| `frontend/src/composables/useFilesClipboard.ts` | 91 | 1..91 | 0 | 0 | +| `frontend/src/composables/useFilesHotkeys.ts` | 79 | 1..79 | 0 | 0 | +| `frontend/src/composables/useFloatingPanel.ts` | 116 | 1..116 | 2 | 0 | +| `frontend/src/composables/useServerList.ts` | 38 | 1..38 | 0 | 0 | +| `frontend/src/composables/useServerPagination.ts` | 61 | 1..61 | 0 | 0 | +| `frontend/src/composables/useSnackbar.ts` | 13 | 1..13 | 0 | 0 | +| `frontend/src/composables/useTerminalQuickCommands.ts` | 73 | 1..73 | 0 | 0 | +| `frontend/src/composables/useWebSocket.ts` | 177 | 1..177 | 0 | 0 | +| `frontend/src/components/FileDirectoryTree.vue` | 316 | 1..316 | 0 | 0 | +| `frontend/src/components/FileEditorWorkbench.vue` | 1041 | 1..1041 | 0 | 0 | +| `frontend/src/components/FilePermissionDialog.vue` | 446 | 1..446 | 0 | 0 | +| `frontend/src/components/MonacoEditor.vue` | 254 | 1..254 | 0 | 0 | +| `frontend/src/components/ShellHighlightField.vue` | 200 | 1..200 | 1 | 0 | +| `frontend/src/components/StatCardsRow.vue` | 67 | 1..67 | 0 | 0 | +| `frontend/src/components/TerminalQuickCommandsSettings.vue` | 210 | 1..210 | 0 | 0 | +| `frontend/src/components/files/FilesBreadcrumb.vue` | 29 | 1..29 | 0 | 0 | +| `frontend/src/components/files/FilesDialogs.vue` | 180 | 1..180 | 0 | 0 | +| `frontend/src/components/files/FilesList.vue` | 279 | 1..279 | 0 | 0 | +| `frontend/src/components/files/FilesPermissionAlert.vue` | 35 | 1..35 | 0 | 0 | +| `frontend/src/components/files/FilesStatusBar.vue` | 24 | 1..24 | 0 | 0 | +| `frontend/src/components/files/FilesToolbar.vue` | 121 | 1..121 | 0 | 0 | +| `frontend/src/components/push/PushDiagnoseDialog.vue` | 71 | 1..71 | 0 | 0 | +| `frontend/src/components/push/PushHistoryTable.vue` | 139 | 1..139 | 0 | 0 | +| `frontend/src/components/push/PushPreviewDialog.vue` | 91 | 1..91 | 0 | 0 | +| `frontend/src/components/push/PushProgressList.vue` | 69 | 1..69 | 0 | 0 | +| `frontend/src/components/push/PushServerGrid.vue` | 110 | 1..110 | 0 | 0 | +| `frontend/src/components/push/PushSyncModeCard.vue` | 39 | 1..39 | 0 | 0 | +| `frontend/src/components/push/PushToolbar.vue` | 58 | 1..58 | 0 | 0 | +| agent.py | 34, 226-227 | **FINDING** | P2 | 注释写 TTL=1.5×flush,实际 600=600,停 Agent 后 key 可能在 flush 前过期 | +| sync_v2.py | 1474-1650 | **FINDING** | P2 | write/chmod/compress/decompress 未失效 browse ETag(Batch4 已覆盖 ops/upload) | +| heartbeat_flush.py | 18-20 | SAFE | — | 常量与 agent 对齐后由 agent 单一来源 | +| self_monitor.py | 102-107 | RISK | P3 | 原仅记录 alert WS;已补 sync WS 计数到 debug 日志 | +| files_browse_service.py | 65+ | SAFE | — | 新增 `invalidate_browse_parents` | +| `frontend/src/components/push/PushZipUpload.vue` | 51 | 1..51 | 0 | 0 | +| `frontend/src/components/servers/ServerFormDialog.vue` | 237 | 1..237 | 0 | 0 | +| `frontend/src/components/terminal/TerminalArea.vue` | 101 | 1..101 | 0 | 0 | +| `frontend/src/components/terminal/TerminalCmdBar.vue` | 109 | 1..109 | 0 | 0 | +| `frontend/src/components/terminal/TerminalQuickBar.vue` | 59 | 1..59 | 0 | 0 | +| `frontend/src/components/terminal/TerminalServerPicker.vue` | 86 | 1..86 | 0 | 0 | +| `frontend/src/components/terminal/TerminalTabBar.vue` | 66 | 1..66 | 0 | 0 | +| `frontend/src/components/terminal/TerminalToolbar.vue` | 108 | 1..108 | 0 | 0 | +| `frontend/src/App.vue` | 456 | 1..456 | 0 | 0 | +| `frontend/src/api/index.ts` | 308 | 1..308 | 2 | 0 | +| `frontend/src/router/index.ts` | 68 | 1..68 | 0 | 0 | +| `frontend/src/stores/auth.ts` | 132 | 1..132 | 0 | 0 | +| `frontend/src/stores/files.ts` | 129 | 1..129 | 0 | 0 | +| `frontend/src/monaco/initMonaco.ts` | 85 | 1..85 | 0 | 0 | +| `frontend/src/monaco/nexusThemes.ts` | 102 | 1..102 | 0 | 0 | +| `frontend/src/monaco/preloadMonaco.ts` | 30 | 1..30 | 0 | 0 | +| `frontend/src/monaco/setupMonacoEnv.ts` | 51 | 1..51 | 0 | 0 | +| `frontend/src/plugins/index.ts` | 19 | 1..19 | 0 | 0 | +| `frontend/src/plugins/vuetify.ts` | 20 | 1..20 | 0 | 0 | +| `frontend/src/styles/settings.scss` | 10 | 1..10 | 0 | 0 | +| `frontend/src/types/api.ts` | 256 | 1..256 | 0 | 0 | +| `frontend/src/types/global.d.ts` | 8 | 1..8 | 0 | 0 | +| `frontend/src/utils/apiError.ts` | 16 | 1..16 | 0 | 0 | +| `frontend/src/utils/auditLabels.ts` | 290 | 1..290 | 0 | 0 | +| `frontend/src/utils/auditNormalize.ts` | 23 | 1..23 | 0 | 0 | +| `frontend/src/utils/editorLanguage.ts` | 196 | 1..196 | 0 | 0 | +| `frontend/src/utils/fileBrowse.ts` | 61 | 1..61 | 0 | 0 | +| `frontend/src/utils/fileDownload.ts` | 12 | 1..12 | 0 | 0 | +| `frontend/src/utils/fileMode.ts` | 128 | 1..128 | 0 | 0 | +| `frontend/src/utils/filePreload.ts` | 141 | 1..141 | 0 | 0 | +| `frontend/src/utils/fileSort.ts` | 57 | 1..57 | 0 | 0 | +| `frontend/src/utils/pushErrorMessage.ts` | 65 | 1..65 | 0 | 0 | +| `frontend/src/utils/remotePath.ts` | 92 | 1..92 | 0 | 0 | +| `frontend/src/utils/serverSelection.ts` | 35 | 1..35 | 0 | 0 | +| `frontend/src/utils/shellHighlight.ts` | 196 | 1..196 | 0 | 0 | +| `frontend/src/utils/status.ts` | 35 | 1..35 | 0 | 0 | +| `frontend/src/utils/validation.ts` | 46 | 1..46 | 0 | 0 | +| `frontend/src/utils/wsUrl.ts` | 23 | 1..23 | 0 | 0 | +| `frontend/src/main.ts` | 28 | 1..28 | 0 | 0 | +| `web/app/api.js` | 214 | 1..214 | 11 | 0 | +| `web/app/index.html` | 14 | 1..14 | 0 | 0 | +| `web/app/install.html` | 730 | 1..730 | 6 | 0 | +| `web/app/layout.js` | 231 | 1..231 | 4 | 0 | +| `web/app/preview.html` | 183 | 1..183 | 0 | 0 | +| `web/agent/agent.py` | 434 | 1..434 | 26 | 0 | +| `web/agent/agent.sh` | 98 | 1..98 | 3 | 0 | +| `web/app/preview-v3.html` | 369 | 1..369 | 1 | 0 | +| `web/app/preview-v4.html` | 359 | 1..359 | 1 | 0 | +| `web/app/preview-v5.html` | 375 | 1..375 | 1 | 0 | +| auth_service.py | 239-246 | **FINDING** | P1 | 已轮换 refresh 不在 Redis 时仅 401,不升 `token_version`,被盗旧 token 与合法新会话可并存 | +| auth_service.py | 597-598 | **FINDING** | P2 | Redis 异常时 `_check_refresh_token` 曾返回 False,误拒合法刷新 | +| useFilesBrowse.ts | 187-195 | **FINDING** | P2 | 5s 内命中本地缓存直接 return,跳过后端 ETag,变更后仍显示旧列表 | +| auth_jwt.py | 217-226 | SAFE | — | `tv` 与 DB 严格比对 | +| script_execution_flush.py | 19-45 | SAFE | — | flush/stuck 分工清晰;`update_status` 内 commit | +| files.ts | 85-98 | SAFE | — | `invalidateCache` 与 mutation 联动 | +| `tests/__init__.py` | 0 | 1..0 | 0 | 0 | +| `tests/acceptance_catalog.py` | 64 | 1..64 | 0 | 0 | +| `tests/conftest.py` | 95 | 1..95 | 0 | 0 | +| `tests/load_test.py` | 201 | 1..201 | 20 | 0 | +| `tests/quick_load.py` | 86 | 1..86 | 9 | 0 | +| `web/agent/config.example.json` | 20 | 1..20 | 0 | 0 | +| `web/agent/install.sh` | 237 | 1..237 | 43 | 0 | +| `web/agent/uninstall.sh` | 115 | 1..115 | 20 | 0 | +| `tests/test_agent_heartbeat_ttl.py` | 9 | 1..9 | 1 | 0 | +| `tests/test_api.py` | 279 | 1..279 | 42 | 0 | +| `tests/test_auth_refresh_reuse.py` | 60 | 1..60 | 0 | 0 | +| `tests/test_editor_write_conflict.py` | 163 | 1..163 | 13 | 0 | +| `tests/test_file_permissions.py` | 75 | 1..75 | 0 | 0 | +| `tests/test_files_browse_etag.py` | 43 | 1..43 | 0 | 0 | +| `tests/test_files_elevation.py` | 109 | 1..109 | 10 | 0 | +| `tests/test_full_site_api.py` | 160 | 1..160 | 23 | 0 | +| `tests/test_nexus_v6.py` | 214 | 1..214 | 11 | 0 | +| `tests/test_posix_paths.py` | 62 | 1..62 | 0 | 0 | +| `tests/test_remote_path_validation.py` | 48 | 1..48 | 0 | 0 | +| `tests/test_retry_runner_outcome.py` | 58 | 1..58 | 0 | 0 | +| `tests/test_schedule_runner_once.py` | 21 | 1..21 | 0 | 0 | +| `tests/test_schema_path_validators.py` | 61 | 1..61 | 0 | 0 | +| `tests/test_security_unit.py` | 229 | 1..229 | 12 | 0 | +| `tests/test_step0_infrastructure.py` | 157 | 1..157 | 7 | 0 | +| `tests/test_step1to5_features.py` | 167 | 1..167 | 4 | 0 | +| `tests/test_sync_batch_id.py` | 24 | 1..24 | 0 | 0 | +| `tests/test_sync_error_message.py` | 39 | 1..39 | 0 | 0 | +| `tests/test_sync_websocket.py` | 44 | 1..44 | 1 | 0 | +| `tests/test_text_io.py` | 43 | 1..43 | 1 | 0 | +| `tests/test_unix_ls.py` | 21 | 1..21 | 0 | 0 | +| `tests/test_upload_staging_cleanup.py` | 64 | 1..64 | 1 | 0 | +| `deploy/check_shell_eol.sh` | 32 | 1..32 | 0 | 0 | +| `deploy/db_backup.sh` | 103 | 1..103 | 27 | 0 | +| `deploy/deploy-frontend.sh` | 60 | 1..60 | 3 | 0 | +| `deploy/gate_log.py` | 57 | 1..57 | 4 | 0 | +| `deploy/health_monitor.sh` | 70 | 1..70 | 14 | 0 | +| `deploy/install.sh` | 526 | 1..526 | 110 | 0 | +| `deploy/migrations/001_indexes.sql` | 24 | 1..24 | 0 | 0 | +| `deploy/nexus.conf` | 32 | 1..32 | 0 | 0 | +| `deploy/nginx_http.conf` | 79 | 1..79 | 0 | 0 | +| `deploy/nginx_https.conf` | 113 | 1..113 | 0 | 0 | +| `deploy/pre_deploy_check.sh` | 299 | 1..299 | 0 | 0 | +| `deploy/prune_frontend_assets.py` | 149 | 1..149 | 6 | 0 | +| `deploy/run_test_api_on_server.sh` | 6 | 1..6 | 0 | 0 | +| `deploy/uninstall.sh` | 145 | 1..145 | 23 | 0 | +| `deploy/upgrade.sh` | 130 | 1..130 | 22 | 0 | +| `deploy/verify_login.py` | 55 | 1..55 | 9 | 0 | +| `server/api/servers.py` | 1742 | 1..1742 | 68 | 0 | +| `scripts/_browse_test.py` | 37 | 1..37 | 8 | 0 | +| `scripts/bootstrap_database.py` | 118 | 1..118 | 13 | 0 | +| `scripts/cache-node20-windows.ps1` | 28 | 1..28 | 0 | 0 | +| `scripts/check_text_eol.py` | 111 | 1..111 | 11 | 0 | +| `scripts/check_worktree_eol.py` | 107 | 1..107 | 7 | 0 | +| `scripts/generate_bug_scan_registry.py` | 555 | 1..555 | 8 | 0 | +| `scripts/grant_nexus_local.sql` | 9 | 1..9 | 0 | 0 | +| `scripts/linux_mcp_mysql.sh` | 37 | 1..37 | 0 | 0 | +| `scripts/normalize_worktree_lf.sh` | 68 | 1..68 | 0 | 0 | +| `scripts/proxy-env.sh` | 16 | 1..16 | 0 | 0 | +| `scripts/proxychains-docker.conf` | 9 | 1..9 | 0 | 0 | +| `scripts/run_full_acceptance.sh` | 24 | 1..24 | 1 | 0 | +| `scripts/run_full_e2e.sh` | 13 | 1..13 | 1 | 0 | +| `scripts/run_nexus_acceptance.py` | 567 | 1..567 | 36 | 0 | +| `scripts/seed_local_admin.py` | 75 | 1..75 | 7 | 0 | +| `scripts/start-dev.sh` | 58 | 1..58 | 0 | 0 | +| `scripts/sync_frontend_vendor.py` | 180 | 1..180 | 18 | 0 | +| `scripts/sync_mysql_mcp_env.py` | 156 | 1..156 | 5 | 0 | +| `scripts/unlock_login_lockout.py` | 114 | 1..114 | 9 | 0 | +| `scripts/with-proxy.sh` | 9 | 1..9 | 0 | 0 | +| `scripts/wsl_check_mysql.py` | 66 | 1..66 | 13 | 0 | +| `scripts/wsl_ensure_venv.sh` | 19 | 1..19 | 7 | 0 | +| `scripts/wsl_install_node20.sh` | 117 | 1..117 | 1 | 0 | +| `scripts/wsl_integration_smoke.sh` | 76 | 1..76 | 7 | 0 | +| `scripts/wsl_mcp_mysql.sh` | 28 | 1..28 | 0 | 0 | +| `scripts/wsl_show_grants.py` | 45 | 1..45 | 6 | 0 | +| `scripts/wsl_start_dev.sh` | 21 | 1..21 | 2 | 0 | +| `.dockerignore` | 29 | 1..29 | 0 | 0 | +| `Dockerfile` | 29 | 1..29 | 0 | 0 | +| `docker/.env.example` | 18 | 1..18 | 0 | 0 | +| `docker/README.md` | 56 | 1..56 | 0 | 0 | +| `docker/entrypoint.sh` | 72 | 1..72 | 0 | 0 | +| `docker/generate_env.py` | 52 | 1..52 | 5 | 0 | +| `docker/sync_root_env.py` | 85 | 1..85 | 2 | 0 | +| `frontend/package.json` | 47 | 1..47 | 0 | 0 | +| `frontend/vite.config.mts` | 54 | 1..54 | 0 | 0 | +| `requirements-dev.txt` | 18 | 1..18 | 0 | 0 | +| `requirements.txt` | 18 | 1..18 | 0 | 0 | +| `ruff.toml` | 41 | 1..41 | 0 | 0 | +| `server/api/sync_v2.py` | 1666 | 1..1666 | 84 | 0 | +| `server/api/auth.py` | 375 | 1..375 | 14 | 0 | +| `server/api/settings.py` | 1396 | 1..1396 | 38 | 0 | +| `server/api/terminal.py` | 238 | 1..238 | 14 | 0 | +| `server/api/websocket.py` | 552 | 1..552 | 34 | 0 | +| `server/api/webssh.py` | 394 | 1..394 | 33 | 0 | diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch1.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch1.md new file mode 100644 index 00000000..0c6d722d --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch1.md @@ -0,0 +1,98 @@ +# BUG 专项逐行走读 — Batch 1(2026-06-04) + +**标准**: `docs/project/line-walk-audit-standard.md` · 八类仅列 **BUG** + +## 范围(5 文件,全文 Read) + +| 文件 | N | Read | +|------|---|------| +| `server/api/install.py` | 719 | 1–719 | +| `web/app/install.html` | 720 | createAdmin/lock 段 + init | +| `server/application/services/script_job_callback.py` | 110 | 1–110 | +| `server/api/agent.py` | 360 | 1–360 | +| `server/api/servers.py` | 1733 | stats 228–280 + list 165–220 | + +--- + +## Step 3 规则扫描(BUG 相关) + +| 文件 | 命中 | +|------|------| +| install.py | 守卫分支、lock/create-admin 时序 | +| install.html | 未检查 lock 响应 | +| script_job_callback.py | `_aggregate_execution_status` 空列表 | +| agent.py | 丢弃未知 server、指标解析 | +| servers.py | stats `offline = total - online` | + +--- + +## Closure 表(BUG) + +| 文件 | 行号 | 判定 | 严重度 | 理由 | +|------|------|------|--------|------| +| install.html | 691-694 | **FINDING** | P1 | create-admin 成功后未检查 `/lock`;失败仍进 step5 | +| install.py | 648 | **FINDING** | P1 | create-admin 与 lock 非原子;锁定前 install API 仍可被未认证调用 | +| script_job_callback.py | 17-36 | **FINDING** | P2 | `server_ids==[]` 时直接 `return "completed"`,忽略 results 中 pending | +| install.py | 509-526 | SAFE | — | state 不写 db_pass(有意);刷新 step4 需重填密码属 UX | +| agent.py | 154-180 | SAFE | — | 无 server_id / 未知 server 丢弃,设计如此 | +| agent.py | 86-94 | SAFE | — | 非数字指标跳过 | +| servers.py | 245-279 | RISK/BUG | P2 | `offline=total-online` 与列表 Redis 覆盖逻辑在「从未装 Agent」服务器上可能偏差;非回归 | +| servers.py | 205-212 | SAFE | — | is_online 过滤后重算 total | + +--- + +## FINDING 列表(8 字段) + +### BUG-06-04-01 · P1 · install.html:691-694 + +**现象**:管理员创建成功但 lock 503/400 时 UI 仍显示「安装完成」。 + +**修复**:检查 lock 响应;`create-admin` 返回 `locked:true` 时跳过二次 lock。 + +### BUG-06-04-02 · P1 · install.py:648 + +**现象**:create-admin 完成到 lock 之间,未认证客户端可再次 `create-admin`(ON DUPLICATE 改密)。 + +**修复**:`create-admin` 末尾 `_finalize_install_lock()`;`lock` 幂等 `already_locked`。 + +### BUG-06-04-03 · P2 · script_job_callback.py:32-33 + +**现象**:`server_ids` JSON 损坏为空列表时,误标 `completed`。 + +**修复**:空列表时扫描 results 是否仍有 `phase=pending`。 + +--- + +## Step 7 统计 + +``` +install.py 2 FINDING(已修) +install.html 1 FINDING(已修) +script_job_callback.py 1 FINDING(已修) +agent.py 0 +servers.py 0(1 项 P2 观察) +``` + +## Step 8 DoD + +- [x] 5 文件 Read 覆盖 +- [x] Closure 全表 +- [x] FINDING 含 `文件:行号` +- [x] 3 项 P1/P2 已落地修复 + +## 验证 + +```bash +ruff check server/api/install.py server/application/services/script_job_callback.py +pytest tests/test_security_unit.py tests/ -q --tb=no +``` + +Changelog: `docs/changelog/2026-06-04-install-lock-atomic.md` + +## 下一批(BUG) + +- `server/application/services/sync_engine_v2.py` +- `server/api/auth_service.py` +- `server/background/schedule_runner.py` +- `frontend/src/composables/terminal/useTerminalSessions.ts` +- `server/infrastructure/redis/script_execution_store.py` diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch10.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch10.md new file mode 100644 index 00000000..1826790c --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch10.md @@ -0,0 +1,96 @@ +# BUG 专项逐行走读 — Batch 10(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/api/assets.py` | 358 | 1..358 | 19 | 0 | +| `server/api/files.py` | 77 | 1..77 | 3 | 0 | +| `server/api/health.py` | 67 | 1..67 | 8 | 0 | +| `server/api/scripts.py` | 418 | 1..418 | 18 | 0 | +| `server/api/search.py` | 119 | 1..119 | 7 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/api/assets.py` | 6 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/assets.py` | 17 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/assets.py` | 22 | PY-05 | SAFE | 已有防护/封装: Depends(get_current | +| `server/api/assets.py` | 33 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/assets.py` | 47 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/assets.py` | 69 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/assets.py` | 99 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/assets.py` | 125 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/assets.py` | 140 | PY-05 | SAFE | 已有防护/封装: Depends(get_current | +| `server/api/assets.py` | 151 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/assets.py` | 173 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/assets.py` | 203 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/assets.py` | 229 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/assets.py` | 245 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/assets.py` | 252 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/assets.py` | 258 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/assets.py` | 277 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/assets.py` | 286 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/assets.py` | 291 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/files.py` | 5 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/files.py` | 17 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/files.py` | 39 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/health.py` | 10 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/health.py` | 18 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/health.py` | 22 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/health.py` | 25 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/health.py` | 35 | PY-05 | SAFE | 已有防护/封装: Depends(get_current | +| `server/api/health.py` | 55 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/health.py` | 63 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/health.py` | 65 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/scripts.py` | 8 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/scripts.py` | 23 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/scripts.py` | 30 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/scripts.py` | 41 | PY-05 | SAFE | 已有防护/封装: Depends(get_current | +| `server/api/scripts.py` | 55 | PY-05 | SAFE | 已有防护/封装: Depends(get_current | +| `server/api/scripts.py` | 65 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/scripts.py` | 89 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/scripts.py` | 130 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/scripts.py` | 151 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/scripts.py` | 163 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/scripts.py` | 177 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/scripts.py` | 194 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/scripts.py` | 234 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/scripts.py` | 254 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/scripts.py` | 267 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/scripts.py` | 289 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/scripts.py` | 319 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/scripts.py` | 345 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/search.py` | 8 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/search.py` | 17 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/search.py` | 31 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/search.py` | 56 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/search.py` | 74 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/search.py` | 90 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/search.py` | 106 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch11.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch11.md new file mode 100644 index 00000000..a6c90da4 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch11.md @@ -0,0 +1,44 @@ +# BUG 专项逐行走读 — Batch 11(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/api/__init__.py` | 1 | 1..1 | 0 | 0 | +| `server/api/dependencies.py` | 180 | 1..180 | 0 | 0 | +| `server/api/remote_path_validation.py` | 33 | 1..33 | 0 | 0 | +| `server/api/schema_path_validators.py` | 59 | 1..59 | 1 | 0 | +| `server/api/schemas.py` | 641 | 1..641 | 2 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/api/schema_path_validators.py` | 43 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/schemas.py` | 170 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/schemas.py` | 246 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch12.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch12.md new file mode 100644 index 00000000..789bce3e --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch12.md @@ -0,0 +1,78 @@ +# BUG 专项逐行走读 — Batch 12(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/api/install.py` | 722 | 1..722 | 45 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/api/install.py` | 17 | PY-05 | SAFE | 安装模式公开端点(设计) | +| `server/api/install.py` | 27 | PY-05 | SAFE | 安装模式公开端点(设计) | +| `server/api/install.py` | 31 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 198 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 208 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 210 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 219 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 221 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 251 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 254 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 272 | PY-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 277 | PY-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 291 | PY-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 292 | PY-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 315 | PY-05 | SAFE | 安装模式公开端点(设计) | +| `server/api/install.py` | 352 | PY-05 | SAFE | 安装模式公开端点(设计) | +| `server/api/install.py` | 435 | PY-05 | SAFE | 安装模式公开端点(设计) | +| `server/api/install.py` | 472 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 482 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 488 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 490 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 491 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 492 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 521 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 522 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 523 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 572 | PY-05 | SAFE | 安装模式公开端点(设计) | +| `server/api/install.py` | 596 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 597 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 598 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 600 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 612 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 613 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 614 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 617 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 618 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 619 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 631 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 640 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | 656 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/install.py` | … | … | SAFE | 另有 5 条 SAFE(省略) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch13.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch13.md new file mode 100644 index 00000000..554fe1e0 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch13.md @@ -0,0 +1,53 @@ +# BUG 专项逐行走读 — Batch 13(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/api/agent.py` | 362 | 1..362 | 16 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/api/agent.py` | 17 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/agent.py` | 22 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/agent.py` | 30 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/agent.py` | 85 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/agent.py` | 88 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/agent.py` | 93 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/agent.py` | 106 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/agent.py` | 107 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/agent.py` | 108 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/agent.py` | 128 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/agent.py` | 140 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/agent.py` | 229 | PY-14 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/agent.py` | 239 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/agent.py` | 269 | PY-14 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/agent.py` | 295 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/agent.py` | 310 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch14.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch14.md new file mode 100644 index 00000000..4b4eaf7e --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch14.md @@ -0,0 +1,43 @@ +# BUG 专项逐行走读 — Batch 14(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/api/auth_jwt.py` | 275 | 1..275 | 6 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/api/auth_jwt.py` | 12 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/auth_jwt.py` | 93 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/auth_jwt.py` | 209 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/auth_jwt.py` | 224 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/auth_jwt.py` | 255 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/auth_jwt.py` | 269 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch17.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch17.md new file mode 100644 index 00000000..90261eb1 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch17.md @@ -0,0 +1,55 @@ +# BUG 专项逐行走读 — Batch 17(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/application/services/script_service.py` | 713 | 1..713 | 18 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/application/services/script_service.py` | 180 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/script_service.py` | 189 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/script_service.py` | 301 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/script_service.py` | 335 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/script_service.py` | 338 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/script_service.py` | 358 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/script_service.py` | 361 | PY-07 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/script_service.py` | 456 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/script_service.py` | 514 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/script_service.py` | 523 | PY-07 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/script_service.py` | 570 | PY-07 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/script_service.py` | 576 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/script_service.py` | 636 | PY-06 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/script_service.py` | 658 | PY-07 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/script_service.py` | 664 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/script_service.py` | 666 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/script_service.py` | 676 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/script_service.py` | 705 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch18.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch18.md new file mode 100644 index 00000000..4424194a --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch18.md @@ -0,0 +1,53 @@ +# BUG 专项逐行走读 — Batch 18(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/application/services/sync_engine_v2.py` | 540 | 1..540 | 16 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/application/services/sync_engine_v2.py` | 110 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/sync_engine_v2.py` | 143 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/sync_engine_v2.py` | 163 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/sync_engine_v2.py` | 172 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/sync_engine_v2.py` | 173 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/sync_engine_v2.py` | 174 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/sync_engine_v2.py` | 186 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/sync_engine_v2.py` | 223 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/sync_engine_v2.py` | 403 | PY-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/sync_engine_v2.py` | 420 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/sync_engine_v2.py` | 448 | PY-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/sync_engine_v2.py` | 456 | PY-07 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/sync_engine_v2.py` | 458 | PY-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/sync_engine_v2.py` | 459 | PY-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/sync_engine_v2.py` | 493 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/sync_engine_v2.py` | 522 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch19.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch19.md new file mode 100644 index 00000000..132b1872 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch19.md @@ -0,0 +1,50 @@ +# BUG 专项逐行走读 — Batch 19(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/application/services/auth_service.py` | 640 | 1..640 | 13 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/application/services/auth_service.py` | 65 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/auth_service.py` | 71 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/auth_service.py` | 209 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/auth_service.py` | 210 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/auth_service.py` | 231 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/auth_service.py` | 286 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/auth_service.py` | 317 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/auth_service.py` | 417 | PY-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/auth_service.py` | 445 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/auth_service.py` | 459 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/auth_service.py` | 463 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/auth_service.py` | 511 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/auth_service.py` | 586 | PY-14 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch2.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch2.md new file mode 100644 index 00000000..cb402015 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch2.md @@ -0,0 +1,86 @@ +# BUG 专项逐行走读 — Batch 2(2026-06-04) + +**标准**: `docs/project/line-walk-audit-standard.md` · 八类仅列 **BUG** + +## 范围(5 文件,全文 Read) + +| 文件 | N | Read | +|------|---|------| +| `server/background/schedule_runner.py` | 231 | 1–231 | +| `server/application/services/auth_service.py` | 619 | login/refresh/TOTP 段 | +| `server/application/services/sync_engine_v2.py` | 529 | `sync_files` + `_rsync_push` | +| `server/infrastructure/redis/script_execution_store.py` | 423 | live/flush/stuck 段 | +| `frontend/src/composables/terminal/useTerminalSessions.ts` | 750 | connect/reconnect/WebSSH | + +--- + +## Step 3 规则扫描(BUG 相关) + +| 文件 | 命中 | +|------|------| +| schedule_runner.py | once 执行前 `enabled=False` + 失败不回滚 | +| auth_service.py | TOTP 未提交是否计失败、refresh 版本 | +| sync_engine_v2.py | `gather(return_exceptions=True)` 未捕获异常计数 | +| script_execution_store.py | stuck/pending、Redis→MySQL 一致性 | +| useTerminalSessions.ts | WebSocket token、重连风暴 | + +--- + +## Closure 表(BUG) + +| 文件 | 行号 | 判定 | 严重度 | 理由 | +|------|------|------|--------|------| +| schedule_runner.py | 125-217 | **FINDING** | P1 | `run_mode=once` 执行前禁用;失败时仅 cron 回滚 `last_run_at`,once 永久失活 | +| auth_service.py | 154-156 | SAFE | — | `totp_required` 不计入暴破次数,避免泄露账户存在性前锁定 | +| auth_service.py | 228-237 | SAFE | — | refresh 版本不匹配只拒本 token,不误伤多设备 | +| sync_engine_v2.py | 241-242 | RISK | P2 | `gather(..., return_exceptions=True)` 若 `_sync_one` 未捕获异常,completed+failed 可能小于 total | +| script_execution_store.py | 300-312 | SAFE | — | stuck 检测仅 pending+started_at,与 callback 聚合一致 | +| script_execution_store.py | 65-67 | SAFE | — | `unpack_results_blob` 对解析 dict 局部 pop `_meta` | +| useTerminalSessions.ts | 277-293 | SAFE | — | 连接前 `POST /auth/webssh-token`,WS URL 带短期 token | +| useTerminalSessions.ts | 386-391 | SAFE | — | 指数退避重连 capped 30s | + +--- + +## FINDING 列表(8 字段) + +### BUG-06-04-04 · P1 · schedule_runner.py:212-217 + +**现象**:一次性调度在触发前写入 `last_run_at` 且 `enabled=False`;执行失败时旧逻辑仅对 cron 回滚 `last_run_at`,once 任务静默失败且无法重试。 + +**修复**:`rollback_schedule_after_failed_run()` — 失败时恢复 `last_run_at`,`once` 额外 `enabled=True`。 + +**验证**:`tests/test_schedule_runner_once.py` + +--- + +## Step 7 统计 + +``` +schedule_runner.py 1 FINDING(已修) +auth_service.py 0 +sync_engine_v2.py 0(1 项 P2 观察,未改) +script_execution_store.py 0 +useTerminalSessions.ts 0 +``` + +## Step 8 DoD + +- [x] 5 文件 Read 覆盖 +- [x] Closure 全表 +- [x] FINDING 含 `文件:行号` +- [x] P1 已落地修复 + pytest + +## 验证 + +```bash +ruff check server/background/schedule_runner.py +pytest tests/test_schedule_runner_once.py tests/ -q --tb=no +``` + +Changelog: `docs/changelog/2026-06-04-schedule-once-rollback.md` + +## 下一批(BUG) + +- `server/api/files_browse.py` / `retry_runner.py` +- `frontend/src/pages/DashboardPage.vue`(stats vs Redis 在线数) +- `sync_engine_v2.py` P2:`gather` 异常与计数对齐(若改需单测) diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch20.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch20.md new file mode 100644 index 00000000..464dfaf9 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch20.md @@ -0,0 +1,51 @@ +# BUG 专项逐行走读 — Batch 20(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/application/services/files_browse_service.py` | 167 | 1..167 | 6 | 0 | +| `server/application/services/script_job_callback.py` | 110 | 1..110 | 2 | 0 | +| `server/application/services/script_jobs.py` | 125 | 1..125 | 1 | 0 | +| `server/application/services/server_service.py` | 68 | 1..68 | 0 | 0 | +| `server/application/services/sync_service.py` | 43 | 1..43 | 1 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/application/services/files_browse_service.py` | 15 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/files_browse_service.py` | 117 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/files_browse_service.py` | 122 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/files_browse_service.py` | 123 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/files_browse_service.py` | 127 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/files_browse_service.py` | 141 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/script_job_callback.py` | 34 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/script_job_callback.py` | 59 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/application/services/script_jobs.py` | 120 | PY-08 | SAFE | 已有防护/封装: secrets.compare_digest | +| `server/application/services/sync_service.py` | 3 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch21.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch21.md new file mode 100644 index 00000000..208d7a8c --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch21.md @@ -0,0 +1,39 @@ +# BUG 专项逐行走读 — Batch 21(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/application/__init__.py` | 1 | 1..1 | 0 | 0 | +| `server/application/services/__init__.py` | 8 | 1..8 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch22.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch22.md new file mode 100644 index 00000000..1d296e33 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch22.md @@ -0,0 +1,59 @@ +# BUG 专项逐行走读 — Batch 22(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/background/heartbeat_flush.py` | 102 | 1..102 | 3 | 0 | +| `server/background/retry_runner.py` | 148 | 1..148 | 7 | 0 | +| `server/background/schedule_runner.py` | 236 | 1..236 | 6 | 0 | +| `server/background/script_execution_flush.py` | 45 | 1..45 | 0 | 0 | +| `server/background/self_monitor.py` | 108 | 1..108 | 2 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/background/heartbeat_flush.py` | 29 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/background/heartbeat_flush.py` | 60 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/background/heartbeat_flush.py` | 78 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/background/retry_runner.py` | 30 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/background/retry_runner.py` | 31 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/background/retry_runner.py` | 32 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/background/retry_runner.py` | 33 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/background/retry_runner.py` | 72 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/background/retry_runner.py` | 120 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/background/retry_runner.py` | 130 | PY-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/background/schedule_runner.py` | 24 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/background/schedule_runner.py` | 59 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/background/schedule_runner.py` | 67 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/background/schedule_runner.py` | 71 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/background/schedule_runner.py` | 85 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/background/schedule_runner.py` | 216 | PY-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/background/self_monitor.py` | 16 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/background/self_monitor.py` | 82 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch23.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch23.md new file mode 100644 index 00000000..3982ebba --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch23.md @@ -0,0 +1,40 @@ +# BUG 专项逐行走读 — Batch 23(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/background/__init__.py` | 7 | 1..7 | 0 | 0 | +| `server/background/ip_allowlist_refresh.py` | 99 | 1..99 | 1 | 0 | +| `server/background/upload_staging_cleanup.py` | 60 | 1..60 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/background/ip_allowlist_refresh.py` | 47 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch24.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch24.md new file mode 100644 index 00000000..2a690da6 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch24.md @@ -0,0 +1,63 @@ +# BUG 专项逐行走读 — Batch 24(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/__init__.py` | 5 | 1..5 | 0 | 0 | +| `server/config.py` | 217 | 1..217 | 3 | 0 | +| `server/main.py` | 669 | 1..669 | 21 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/config.py` | 163 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/config.py` | 177 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/config.py` | 204 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 37 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 63 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 128 | PY-14 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 163 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 171 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 251 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 283 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 284 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 298 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 316 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 317 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 349 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 405 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 438 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 510 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 539 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 545 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 546 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 603 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 626 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/main.py` | 639 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch25.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch25.md new file mode 100644 index 00000000..fba0d143 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch25.md @@ -0,0 +1,52 @@ +# BUG 专项逐行走读 — Batch 25(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/infrastructure/database/__init__.py` | 1 | 1..1 | 0 | 0 | +| `server/infrastructure/database/admin_repo.py` | 62 | 1..62 | 4 | 0 | +| `server/infrastructure/database/audit_log_repo.py` | 86 | 1..86 | 5 | 0 | +| `server/infrastructure/database/crypto.py` | 54 | 1..54 | 0 | 0 | +| `server/infrastructure/database/db_credential_repo.py` | 38 | 1..38 | 2 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/infrastructure/database/admin_repo.py` | 15 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/admin_repo.py` | 19 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/admin_repo.py` | 47 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/admin_repo.py` | 56 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/audit_log_repo.py` | 68 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/audit_log_repo.py` | 68 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/audit_log_repo.py` | 69 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/audit_log_repo.py` | 78 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/audit_log_repo.py` | 78 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/db_credential_repo.py` | 15 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/db_credential_repo.py` | 19 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch26.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch26.md new file mode 100644 index 00000000..852ba300 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch26.md @@ -0,0 +1,62 @@ +# BUG 专项逐行走读 — Batch 26(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/infrastructure/database/engine_compat.py` | 23 | 1..23 | 0 | 0 | +| `server/infrastructure/database/migrations.py` | 180 | 1..180 | 6 | 0 | +| `server/infrastructure/database/password_preset_repo.py` | 38 | 1..38 | 2 | 0 | +| `server/infrastructure/database/platform_node_repo.py` | 83 | 1..83 | 7 | 0 | +| `server/infrastructure/database/push_schedule_repo.py` | 95 | 1..95 | 6 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/infrastructure/database/migrations.py` | 39 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/migrations.py` | 48 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/migrations.py` | 55 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/migrations.py` | 68 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/migrations.py` | 89 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/migrations.py` | 164 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/password_preset_repo.py` | 15 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/password_preset_repo.py` | 19 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/platform_node_repo.py` | 15 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/platform_node_repo.py` | 19 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/platform_node_repo.py` | 23 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/platform_node_repo.py` | 49 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/platform_node_repo.py` | 53 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/platform_node_repo.py` | 57 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/platform_node_repo.py` | 63 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/push_schedule_repo.py` | 15 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/push_schedule_repo.py` | 19 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/push_schedule_repo.py` | 23 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/push_schedule_repo.py` | 51 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/push_schedule_repo.py` | 60 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/push_schedule_repo.py` | 68 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch27.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch27.md new file mode 100644 index 00000000..72f0f870 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch27.md @@ -0,0 +1,70 @@ +# BUG 专项逐行走读 — Batch 27(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/infrastructure/database/script_repo.py` | 90 | 1..90 | 8 | 0 | +| `server/infrastructure/database/server_repo.py` | 144 | 1..144 | 8 | 0 | +| `server/infrastructure/database/session.py` | 125 | 1..125 | 6 | 0 | +| `server/infrastructure/database/setting_repo.py` | 31 | 1..31 | 5 | 0 | +| `server/infrastructure/database/ssh_key_preset_repo.py` | 38 | 1..38 | 2 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/infrastructure/database/script_repo.py` | 15 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/script_repo.py` | 19 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/script_repo.py` | 23 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/script_repo.py` | 51 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/script_repo.py` | 73 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/script_repo.py` | 73 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/script_repo.py` | 77 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/script_repo.py` | 87 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/server_repo.py` | 31 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/server_repo.py` | 38 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/server_repo.py` | 43 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/server_repo.py` | 82 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/server_repo.py` | 98 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/server_repo.py` | 104 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/server_repo.py` | 110 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/server_repo.py` | 134 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/session.py` | 25 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/session.py` | 26 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/session.py` | 89 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/session.py` | 95 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/session.py` | 96 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/session.py` | 114 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/setting_repo.py` | 16 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/setting_repo.py` | 21 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/setting_repo.py` | 24 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/setting_repo.py` | 26 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/setting_repo.py` | 30 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/ssh_key_preset_repo.py` | 15 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/ssh_key_preset_repo.py` | 19 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch28.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch28.md new file mode 100644 index 00000000..863a0f54 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch28.md @@ -0,0 +1,50 @@ +# BUG 专项逐行走读 — Batch 28(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/infrastructure/database/ssh_session_repo.py` | 83 | 1..83 | 6 | 0 | +| `server/infrastructure/database/sync_log_repo.py` | 120 | 1..120 | 6 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/infrastructure/database/ssh_session_repo.py` | 16 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/ssh_session_repo.py` | 20 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/ssh_session_repo.py` | 28 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/ssh_session_repo.py` | 55 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/ssh_session_repo.py` | 64 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/ssh_session_repo.py` | 79 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/sync_log_repo.py` | 22 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/sync_log_repo.py` | 54 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/sync_log_repo.py` | 54 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/sync_log_repo.py` | 66 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/sync_log_repo.py` | 84 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/database/sync_log_repo.py` | 103 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch29.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch29.md new file mode 100644 index 00000000..2880d62c --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch29.md @@ -0,0 +1,92 @@ +# BUG 专项逐行走读 — Batch 29(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/infrastructure/ssh/__init__.py` | 1 | 1..1 | 0 | 0 | +| `server/infrastructure/ssh/asyncssh_pool.py` | 437 | 1..437 | 43 | 0 | +| `server/infrastructure/ssh/files_capability.py` | 62 | 1..62 | 3 | 0 | +| `server/infrastructure/ssh/remote_archive.py` | 128 | 1..128 | 2 | 0 | +| `server/infrastructure/ssh/remote_probe.py` | 85 | 1..85 | 5 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/infrastructure/ssh/asyncssh_pool.py` | 1 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 3 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 6 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 7 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 19 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 25 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 30 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 31 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 47 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 69 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 90 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 165 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 166 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 180 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 188 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 195 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 197 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 206 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 265 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 266 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 271 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 272 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 273 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 278 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 280 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 288 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 291 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 293 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 297 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 306 | PY-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 306 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 310 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 334 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 338 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 342 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 343 | PY-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 343 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 358 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 373 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | 386 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/asyncssh_pool.py` | … | … | SAFE | 另有 3 条 SAFE(省略) | +| `server/infrastructure/ssh/files_capability.py` | 6 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/files_capability.py` | 31 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/files_capability.py` | 34 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/remote_archive.py` | 10 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/remote_archive.py` | 125 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/remote_probe.py` | 8 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/remote_probe.py` | 29 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/remote_probe.py` | 46 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/remote_probe.py` | 67 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/remote_probe.py` | 68 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch3.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch3.md new file mode 100644 index 00000000..6c42a373 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch3.md @@ -0,0 +1,93 @@ +# BUG 专项逐行走读 — Batch 3(2026-06-04) + +**标准**: `docs/project/line-walk-audit-standard.md` · 八类仅列 **BUG** + +## 范围(5 文件,全文 Read) + +| 文件 | N | Read | +|------|---|------| +| `server/background/retry_runner.py` | 148 | 1–148 | +| `server/application/services/files_browse_service.py` | 150 | 1–150 | +| `server/api/servers.py` | stats 225–282 | | +| `frontend/src/pages/DashboardPage.vue` | 411 | stats/summary/WS | +| `server/application/services/sync_engine_v2.py` | `sync_files` 60–287 | gather 计数 | + +--- + +## Step 3 规则扫描(BUG 相关) + +| 文件 | 命中 | +|------|------| +| retry_runner.py | 成功判定、cancel 分支、backoff | +| files_browse_service.py | 路径 `..`、SSH 命令引用 | +| servers.py | `offline = total - online`、Redis 脏 key | +| DashboardPage.vue | 是否混用 stats / 列表在线 | +| sync_engine_v2.py | `gather(return_exceptions=True)` | + +--- + +## Closure 表(BUG) + +| 文件 | 行号 | 判定 | 严重度 | 理由 | +|------|------|------|--------|------| +| retry_runner.py | 85-100 | **FINDING** | P2 | 仅 `failed==0 && completed>0` 成功;全 cancel 时 job 永久 pending | +| sync_engine_v2.py | 241-242 | **FINDING** | P2 | 任务异常时 `results` 缺项,`failed+completed` 可小于 `total` | +| servers.py | 276-279 | **FINDING** | P2 | Redis heartbeat 数可大于 DB `total`,在线率 >100% | +| files_browse_service.py | 33-46, 98-101 | SAFE | — | `normalize_remote_abs_path` + `shlex.quote` | +| DashboardPage.vue | 302-320 | SAFE | — | 卡片用 `/servers/stats`;列表分页独立 | +| DashboardPage.vue | 345-376 | RISK | P3 | `loadSummary` 仅前 200 台算 CPU/Agent 均值(已知限制) | + +--- + +## FINDING 列表 + +### BUG-06-04-05 · P2 · retry_runner.py + +**现象**:重试任务在 `cancelled>0` 且 `completed=failed=0` 时不进入成功/耗尽分支,每 5 分钟无限重试。 + +**修复**:`apply_push_retry_outcome()` — cancel-only 标 `failed`;统一 backoff。 + +### BUG-06-04-06 · P2 · sync_engine_v2.py:241-242 + +**现象**:`_sync_one` 未捕获异常时 gather 吞掉,`failed` 统计偏小。 + +**修复**:记录 `BaseException`;`unaccounted = total - len(results)` 并入 `failed`。 + +### BUG-06-04-07 · P2 · servers.py:276-279 + +**现象**:过期 `heartbeat:*` 使 `online > total`,仪表盘在线率失真。 + +**修复**:`online = min(online, total)`(有 DB 行时)。 + +--- + +## Step 7 统计 + +``` +retry_runner.py 1 FINDING(已修) +sync_engine_v2.py 1 FINDING(已修) +servers.py 1 FINDING(已修) +files_browse_service.py 0 +DashboardPage.vue 0 +``` + +## Step 8 DoD + +- [x] 5 文件覆盖 +- [x] Closure 全表 +- [x] 3 项 P2 已修 + pytest + +## 验证 + +```bash +ruff check server/background/retry_runner.py server/application/services/sync_engine_v2.py server/api/servers.py +pytest tests/test_retry_runner_outcome.py tests/ -q --tb=no +``` + +Changelog: `docs/changelog/2026-06-04-bug-batch3-retry-sync-stats.md` + +## 下一批(BUG) + +- `server/api/files.py`(上传/删除 mutation + ETag 失效) +- `server/background/heartbeat_flush.py` +- `frontend` 推送页与 WS 进度一致性 diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch30.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch30.md new file mode 100644 index 00000000..3145ad11 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch30.md @@ -0,0 +1,42 @@ +# BUG 专项逐行走读 — Batch 30(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/infrastructure/ssh/remote_shell.py` | 63 | 1..63 | 5 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/infrastructure/ssh/remote_shell.py` | 8 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/remote_shell.py` | 23 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/remote_shell.py` | 36 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/remote_shell.py` | 41 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/ssh/remote_shell.py` | 54 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch31.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch31.md new file mode 100644 index 00000000..9e5abc69 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch31.md @@ -0,0 +1,55 @@ +# BUG 专项逐行走读 — Batch 31(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/infrastructure/__init__.py` | 15 | 1..15 | 0 | 0 | +| `server/infrastructure/agent_url.py` | 11 | 1..11 | 0 | 0 | +| `server/infrastructure/redis/__init__.py` | 1 | 1..1 | 0 | 0 | +| `server/infrastructure/redis/client.py` | 97 | 1..97 | 0 | 0 | +| `server/infrastructure/redis/script_callback_rate.py` | 44 | 1..44 | 1 | 0 | +| `server/infrastructure/redis/script_execution_store.py` | 422 | 1..422 | 10 | 0 | +| `server/infrastructure/subscription_parser.py` | 121 | 1..121 | 0 | 0 | +| `server/infrastructure/telegram/__init__.py` | 222 | 1..222 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/infrastructure/redis/script_callback_rate.py` | 39 | PY-07 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/redis/script_execution_store.py` | 33 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/redis/script_execution_store.py` | 33 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/redis/script_execution_store.py` | 40 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/redis/script_execution_store.py` | 58 | PY-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/redis/script_execution_store.py` | 97 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/redis/script_execution_store.py` | 159 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/redis/script_execution_store.py` | 305 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/redis/script_execution_store.py` | 328 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/redis/script_execution_store.py` | 367 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/infrastructure/redis/script_execution_store.py` | 410 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch32.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch32.md new file mode 100644 index 00000000..da0d605b --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch32.md @@ -0,0 +1,42 @@ +# BUG 专项逐行走读 — Batch 32(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/domain/__init__.py` | 3 | 1..3 | 0 | 0 | +| `server/domain/models/__init__.py` | 431 | 1..431 | 0 | 0 | +| `server/domain/repositories/__init__.py` | 125 | 1..125 | 0 | 0 | +| `server/utils/files_chmod_policy.py` | 29 | 1..29 | 0 | 0 | +| `server/utils/files_elevation.py` | 55 | 1..55 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch33.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch33.md new file mode 100644 index 00000000..2e4d0ca5 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch33.md @@ -0,0 +1,47 @@ +# BUG 专项逐行走读 — Batch 33(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/utils/posix_paths.py` | 122 | 1..122 | 2 | 0 | +| `server/utils/sync_error_message.py` | 72 | 1..72 | 0 | 0 | +| `server/utils/text_io.py` | 51 | 1..51 | 4 | 0 | +| `server/utils/unix_ls.py` | 115 | 1..115 | 1 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/utils/posix_paths.py` | 40 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/utils/posix_paths.py` | 107 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/utils/text_io.py` | 30 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/utils/text_io.py` | 32 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/utils/text_io.py` | 37 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/utils/text_io.py` | 42 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/utils/unix_ls.py` | 91 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch35.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch35.md new file mode 100644 index 00000000..16bd1883 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch35.md @@ -0,0 +1,42 @@ +# BUG 专项逐行走读 — Batch 35(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/pages/AlertsPage.vue` | 184 | 1..184 | 0 | 0 | +| `frontend/src/pages/AuditPage.vue` | 154 | 1..154 | 0 | 0 | +| `frontend/src/pages/CommandsPage.vue` | 164 | 1..164 | 0 | 0 | +| `frontend/src/pages/CredentialsPage.vue` | 390 | 1..390 | 0 | 0 | +| `frontend/src/pages/DashboardPage.vue` | 410 | 1..410 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch36.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch36.md new file mode 100644 index 00000000..5ea24815 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch36.md @@ -0,0 +1,42 @@ +# BUG 专项逐行走读 — Batch 36(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/pages/FilesPage.vue` | 38 | 1..38 | 0 | 0 | +| `frontend/src/pages/LoginPage.vue` | 243 | 1..243 | 0 | 0 | +| `frontend/src/pages/PushPage.vue` | 106 | 1..106 | 0 | 0 | +| `frontend/src/pages/RetriesPage.vue` | 191 | 1..191 | 0 | 0 | +| `frontend/src/pages/SchedulesPage.vue` | 295 | 1..295 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch37.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch37.md new file mode 100644 index 00000000..3896ceea --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch37.md @@ -0,0 +1,41 @@ +# BUG 专项逐行走读 — Batch 37(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/pages/ScriptsPage.vue` | 531 | 1..531 | 0 | 0 | +| `frontend/src/pages/ServersPage.vue` | 477 | 1..477 | 0 | 0 | +| `frontend/src/pages/SettingsPage.vue` | 434 | 1..434 | 0 | 0 | +| `frontend/src/pages/TerminalPage.vue` | 408 | 1..408 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch38.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch38.md new file mode 100644 index 00000000..5c733c9e --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch38.md @@ -0,0 +1,38 @@ +# BUG 专项逐行走读 — Batch 38(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/composables/terminal/useTerminalSessions.ts` | 749 | 1..749 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch39.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch39.md new file mode 100644 index 00000000..f92a337b --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch39.md @@ -0,0 +1,42 @@ +# BUG 专项逐行走读 — Batch 39(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/composables/files/constants.ts` | 5 | 1..5 | 0 | 0 | +| `frontend/src/composables/files/filesPageContext.ts` | 21 | 1..21 | 0 | 0 | +| `frontend/src/composables/files/index.ts` | 9 | 1..9 | 0 | 0 | +| `frontend/src/composables/files/useFilesActions.ts` | 609 | 1..609 | 0 | 0 | +| `frontend/src/composables/files/useFilesBrowse.ts` | 267 | 1..267 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch4.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch4.md new file mode 100644 index 00000000..1b69c644 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch4.md @@ -0,0 +1,92 @@ +# BUG 专项逐行走读 — Batch 4(2026-06-04) + +**标准**: `docs/project/line-walk-audit-standard.md` · 八类仅列 **BUG** + +## 范围(5 文件,全文 Read) + +| 文件 | N | Read | +|------|---|------| +| `server/api/sync_v2.py` | file-ops / browse / files | 125–280, 73–90 | +| `server/api/files.py` | 78 | GET browse + ETag | +| `server/background/heartbeat_flush.py` | 97 | 1–97 | +| `frontend/src/composables/push/usePushProgress.ts` | 289 | 全流程 | +| `server/api/schemas.py` | SyncFiles | batch_id | + +--- + +## Step 3 规则扫描(BUG 相关) + +| 文件 | 命中 | +|------|------| +| sync_v2.py | file-ops 后 ETag、batch_id | +| files.py | 304 缓存与变更一致性 | +| heartbeat_flush.py | 每 key 一 session、孤儿 key | +| usePushProgress.ts | WS 与 POST 时序 | +| schemas.py | batch_id 校验 | + +--- + +## Closure 表(BUG) + +| 文件 | 行号 | 判定 | 严重度 | 理由 | +|------|------|------|--------|------| +| usePushProgress.ts | 184-194 | **FINDING** | P1 | POST 阻塞至推送结束才连 WS,进度事件全部丢失 | +| sync_v2.py | 125-182 | **FINDING** | P2 | 远程 file-ops 后未 `invalidate_browse_cache`,304 陈旧目录 | +| heartbeat_flush.py | 57-89 | **FINDING** | P2 | 每服务器独立 session/commit;已删服务器仍计 flushed | +| files.py | 39-77 | SAFE | — | 只读 browse;失效由 mutation 侧负责 | +| sync_v2.py | 73-90 | SAFE | — | 同步 API 本身无 ETag | + +--- + +## FINDING 列表 + +### BUG-06-04-08 · P1 · usePushProgress.ts + +**现象**:`/sync/files` 在服务端同步执行完毕才返回,客户端却在返回后才连接 `/ws/sync`,实时 `sync_progress` 全部错过;界面长期 pending 直至 300s 超时。 + +**修复**:客户端预生成 `batch_id` → 等待 WS `onopen` → POST 携带 `batch_id`;响应 `results` 作兜底合并。 + +### BUG-06-04-09 · P2 · sync_v2 file-ops + +**现象**:删除/重命名/mkdir/剪贴板后 GET browse 仍可能 304。 + +**修复**:`invalidate_browse_cache(server_id, parent_dir)`。 + +### BUG-06-04-10 · P2 · heartbeat_flush.py + +**现象**:2000 台时 2000 次 commit;已删服务器 UPDATE 0 行仍计入 flushed。 + +**修复**:单 session 批量 UPDATE + `rowcount` 统计。 + +--- + +## Step 7 统计 + +``` +usePushProgress.ts 1 FINDING(已修) +sync_v2.py 1 FINDING(已修) +heartbeat_flush.py 1 FINDING(已修) +files.py 0 +``` + +## Step 8 DoD + +- [x] 5 文件覆盖 +- [x] Closure 全表 +- [x] 3 项已修 + pytest + +## 验证 + +```bash +ruff check server/api/sync_v2.py server/background/heartbeat_flush.py server/api/schemas.py +pytest tests/test_sync_batch_id.py tests/ -q --tb=no +cd frontend && npm run type-check +``` + +Changelog: `docs/changelog/2026-06-04-bug-batch4-push-etag-heartbeat.md` + +## 下一批(BUG) + +- `server/api/sync_v2.py` 上传/远程写文件路径 +- `server/background/self_monitor.py` +- Agent 心跳与 Redis TTL 一致性抽测 diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch40.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch40.md new file mode 100644 index 00000000..a28fdabf --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch40.md @@ -0,0 +1,42 @@ +# BUG 专项逐行走读 — Batch 40(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/composables/files/useFilesEditor.ts` | 163 | 1..163 | 0 | 0 | +| `frontend/src/composables/files/useFilesNavigation.ts` | 210 | 1..210 | 0 | 0 | +| `frontend/src/composables/files/useFilesPage.ts` | 272 | 1..272 | 0 | 0 | +| `frontend/src/composables/files/useFilesPermissions.ts` | 101 | 1..101 | 0 | 0 | +| `frontend/src/composables/files/useFilesSelection.ts` | 28 | 1..28 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch41.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch41.md new file mode 100644 index 00000000..7151fb67 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch41.md @@ -0,0 +1,42 @@ +# BUG 专项逐行走读 — Batch 41(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/composables/push/index.ts` | 3 | 1..3 | 0 | 0 | +| `frontend/src/composables/push/labels.ts` | 57 | 1..57 | 0 | 0 | +| `frontend/src/composables/push/types.ts` | 72 | 1..72 | 0 | 0 | +| `frontend/src/composables/push/usePushForm.ts` | 101 | 1..101 | 0 | 0 | +| `frontend/src/composables/push/usePushLogs.ts` | 146 | 1..146 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch42.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch42.md new file mode 100644 index 00000000..65e0db25 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch42.md @@ -0,0 +1,42 @@ +# BUG 专项逐行走读 — Batch 42(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/composables/push/usePushPage.ts` | 40 | 1..40 | 0 | 0 | +| `frontend/src/composables/push/usePushPreview.ts` | 104 | 1..104 | 0 | 0 | +| `frontend/src/composables/push/usePushProgress.ts` | 288 | 1..288 | 0 | 0 | +| `frontend/src/composables/push/usePushServers.ts` | 115 | 1..115 | 0 | 0 | +| `frontend/src/composables/servers/useServerFormDialog.ts` | 255 | 1..255 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch43.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch43.md new file mode 100644 index 00000000..cf87ca49 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch43.md @@ -0,0 +1,42 @@ +# BUG 专项逐行走读 — Batch 43(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/composables/terminal/termNativeStore.ts` | 62 | 1..62 | 0 | 0 | +| `frontend/src/composables/terminal/types.ts` | 69 | 1..69 | 0 | 0 | +| `frontend/src/composables/terminal/useTerminalCmdBar.ts` | 108 | 1..108 | 0 | 0 | +| `frontend/src/composables/terminal/useTerminalSettings.ts` | 54 | 1..54 | 0 | 0 | +| `frontend/src/composables/useFilesClipboard.ts` | 91 | 1..91 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch44.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch44.md new file mode 100644 index 00000000..4d467bb3 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch44.md @@ -0,0 +1,43 @@ +# BUG 专项逐行走读 — Batch 44(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/composables/useFilesHotkeys.ts` | 79 | 1..79 | 0 | 0 | +| `frontend/src/composables/useFloatingPanel.ts` | 116 | 1..116 | 2 | 0 | +| `frontend/src/composables/useServerList.ts` | 38 | 1..38 | 0 | 0 | +| `frontend/src/composables/useServerPagination.ts` | 61 | 1..61 | 0 | 0 | +| `frontend/src/composables/useSnackbar.ts` | 13 | 1..13 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `frontend/src/composables/useFloatingPanel.ts` | 12 | FE-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `frontend/src/composables/useFloatingPanel.ts` | 31 | FE-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch45.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch45.md new file mode 100644 index 00000000..44e45438 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch45.md @@ -0,0 +1,39 @@ +# BUG 专项逐行走读 — Batch 45(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/composables/useTerminalQuickCommands.ts` | 73 | 1..73 | 0 | 0 | +| `frontend/src/composables/useWebSocket.ts` | 177 | 1..177 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch46.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch46.md new file mode 100644 index 00000000..5220f0d3 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch46.md @@ -0,0 +1,42 @@ +# BUG 专项逐行走读 — Batch 46(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/components/FileDirectoryTree.vue` | 316 | 1..316 | 0 | 0 | +| `frontend/src/components/FileEditorWorkbench.vue` | 1041 | 1..1041 | 0 | 0 | +| `frontend/src/components/FilePermissionDialog.vue` | 446 | 1..446 | 0 | 0 | +| `frontend/src/components/MonacoEditor.vue` | 254 | 1..254 | 0 | 0 | +| `frontend/src/components/ShellHighlightField.vue` | 200 | 1..200 | 1 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `frontend/src/components/ShellHighlightField.vue` | 15 | FE-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch47.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch47.md new file mode 100644 index 00000000..c18b43d1 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch47.md @@ -0,0 +1,42 @@ +# BUG 专项逐行走读 — Batch 47(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/components/StatCardsRow.vue` | 67 | 1..67 | 0 | 0 | +| `frontend/src/components/TerminalQuickCommandsSettings.vue` | 210 | 1..210 | 0 | 0 | +| `frontend/src/components/files/FilesBreadcrumb.vue` | 29 | 1..29 | 0 | 0 | +| `frontend/src/components/files/FilesDialogs.vue` | 180 | 1..180 | 0 | 0 | +| `frontend/src/components/files/FilesList.vue` | 279 | 1..279 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch48.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch48.md new file mode 100644 index 00000000..d966a013 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch48.md @@ -0,0 +1,42 @@ +# BUG 专项逐行走读 — Batch 48(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/components/files/FilesPermissionAlert.vue` | 35 | 1..35 | 0 | 0 | +| `frontend/src/components/files/FilesStatusBar.vue` | 24 | 1..24 | 0 | 0 | +| `frontend/src/components/files/FilesToolbar.vue` | 121 | 1..121 | 0 | 0 | +| `frontend/src/components/push/PushDiagnoseDialog.vue` | 71 | 1..71 | 0 | 0 | +| `frontend/src/components/push/PushHistoryTable.vue` | 139 | 1..139 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch49.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch49.md new file mode 100644 index 00000000..b47c0cd9 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch49.md @@ -0,0 +1,42 @@ +# BUG 专项逐行走读 — Batch 49(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/components/push/PushPreviewDialog.vue` | 91 | 1..91 | 0 | 0 | +| `frontend/src/components/push/PushProgressList.vue` | 69 | 1..69 | 0 | 0 | +| `frontend/src/components/push/PushServerGrid.vue` | 110 | 1..110 | 0 | 0 | +| `frontend/src/components/push/PushSyncModeCard.vue` | 39 | 1..39 | 0 | 0 | +| `frontend/src/components/push/PushToolbar.vue` | 58 | 1..58 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch5.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch5.md new file mode 100644 index 00000000..1e6c3ef4 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch5.md @@ -0,0 +1,86 @@ +# BUG 专项逐行走读 — Batch 5(2026-06-04) + +**标准**: `docs/project/line-walk-audit-standard.md` · 八类仅列 **BUG** + +## 范围(5 文件,全文 Read) + +| 文件 | N | Read | +|------|---|------| +| `server/api/agent.py` | 360 | Redis TTL + heartbeat | +| `server/background/heartbeat_flush.py` | 102 | flush 循环 | +| `server/api/sync_v2.py` | write/chmod/compress/decompress | 1374–1665 | +| `server/background/self_monitor.py` | 108 | 1–108 | +| `server/application/services/files_browse_service.py` | invalidate 辅助 | 65–85 | + +--- + +## Step 3 规则扫描(BUG 相关) + +| 文件 | 命中 | +|------|------| +| agent.py | `REDIS_KEY_EXPIRE` vs `FLUSH_INTERVAL` | +| heartbeat_flush.py | 与 agent TTL 常量重复/不一致 | +| sync_v2.py | 写/chmod/压缩/解压后 ETag | +| self_monitor.py | 仅监控 alert WS | +| files_browse_service.py | 父目录批量失效 | + +--- + +## Closure 表(BUG) + +| 文件 | 行号 | 判定 | 严重度 | 理由 | +|------|------|------|--------|------| +| agent.py | 34, 226-227 | **FINDING** | P2 | 注释写 TTL=1.5×flush,实际 600=600,停 Agent 后 key 可能在 flush 前过期 | +| sync_v2.py | 1474-1650 | **FINDING** | P2 | write/chmod/compress/decompress 未失效 browse ETag(Batch4 已覆盖 ops/upload) | +| heartbeat_flush.py | 18-20 | SAFE | — | 常量与 agent 对齐后由 agent 单一来源 | +| self_monitor.py | 102-107 | RISK | P3 | 原仅记录 alert WS;已补 sync WS 计数到 debug 日志 | +| files_browse_service.py | 65+ | SAFE | — | 新增 `invalidate_browse_parents` | + +--- + +## FINDING 列表 + +### BUG-06-04-11 · P2 · agent.py REDIS_KEY_EXPIRE + +**现象**:`REDIS_KEY_EXPIRE=600` 与 `FLUSH_INTERVAL=600` 相同,末次心跳可能在 10min flush 前从 Redis 消失,MySQL 历史落后。 + +**修复**:`REDIS_KEY_EXPIRE = int(FLUSH_INTERVAL * 1.5)` → 900s。 + +### BUG-06-04-12 · P2 · sync_v2 远程写路径 + +**现象**:`/write-file`、`/chmod`、`/compress`、`/decompress` 成功后 browse 304 仍可能返回旧目录。 + +**修复**:`invalidate_browse_parents(server_id, *paths)`。 + +--- + +## Step 7 统计 + +``` +agent.py 1 FINDING(已修) +sync_v2.py 1 FINDING(已修) +heartbeat_flush.py 0 +self_monitor.py 0(P3 观测增强) +files_browse_service.py 0(辅助函数) +``` + +## Step 8 DoD + +- [x] 5 文件覆盖 +- [x] Closure 全表 +- [x] 2 项 P2 已修 + pytest + +## 验证 + +```bash +ruff check server/api/agent.py server/api/sync_v2.py server/background/self_monitor.py +pytest tests/test_agent_heartbeat_ttl.py tests/test_files_browse_etag.py tests/ -q --tb=no +``` + +Changelog: `docs/changelog/2026-06-04-bug-batch5-agent-etag.md` + +## 下一批(BUG) + +- `server/api/auth_jwt.py` / refresh 重用检测边界 +- `server/background/script_execution_flush.py` +- 前端 `FilesPage` 与 ETag/If-None-Match 联调 diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch50.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch50.md new file mode 100644 index 00000000..1521f644 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch50.md @@ -0,0 +1,42 @@ +# BUG 专项逐行走读 — Batch 50(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/components/push/PushZipUpload.vue` | 51 | 1..51 | 0 | 0 | +| `frontend/src/components/servers/ServerFormDialog.vue` | 237 | 1..237 | 0 | 0 | +| `frontend/src/components/terminal/TerminalArea.vue` | 101 | 1..101 | 0 | 0 | +| `frontend/src/components/terminal/TerminalCmdBar.vue` | 109 | 1..109 | 0 | 0 | +| `frontend/src/components/terminal/TerminalQuickBar.vue` | 59 | 1..59 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch51.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch51.md new file mode 100644 index 00000000..4d6deb69 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch51.md @@ -0,0 +1,40 @@ +# BUG 专项逐行走读 — Batch 51(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/components/terminal/TerminalServerPicker.vue` | 86 | 1..86 | 0 | 0 | +| `frontend/src/components/terminal/TerminalTabBar.vue` | 66 | 1..66 | 0 | 0 | +| `frontend/src/components/terminal/TerminalToolbar.vue` | 108 | 1..108 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch52.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch52.md new file mode 100644 index 00000000..7aaa793d --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch52.md @@ -0,0 +1,43 @@ +# BUG 专项逐行走读 — Batch 52(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/App.vue` | 456 | 1..456 | 0 | 0 | +| `frontend/src/api/index.ts` | 308 | 1..308 | 2 | 0 | +| `frontend/src/router/index.ts` | 68 | 1..68 | 0 | 0 | +| `frontend/src/stores/auth.ts` | 132 | 1..132 | 0 | 0 | +| `frontend/src/stores/files.ts` | 129 | 1..129 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `frontend/src/api/index.ts` | 25 | FE-04 | SAFE | api 模块封装层 | +| `frontend/src/api/index.ts` | 88 | FE-04 | SAFE | api 模块封装层 | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch53.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch53.md new file mode 100644 index 00000000..da407463 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch53.md @@ -0,0 +1,42 @@ +# BUG 专项逐行走读 — Batch 53(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/monaco/initMonaco.ts` | 85 | 1..85 | 0 | 0 | +| `frontend/src/monaco/nexusThemes.ts` | 102 | 1..102 | 0 | 0 | +| `frontend/src/monaco/preloadMonaco.ts` | 30 | 1..30 | 0 | 0 | +| `frontend/src/monaco/setupMonacoEnv.ts` | 51 | 1..51 | 0 | 0 | +| `frontend/src/plugins/index.ts` | 19 | 1..19 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch54.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch54.md new file mode 100644 index 00000000..51f36d13 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch54.md @@ -0,0 +1,42 @@ +# BUG 专项逐行走读 — Batch 54(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/plugins/vuetify.ts` | 20 | 1..20 | 0 | 0 | +| `frontend/src/styles/settings.scss` | 10 | 1..10 | 0 | 0 | +| `frontend/src/types/api.ts` | 256 | 1..256 | 0 | 0 | +| `frontend/src/types/global.d.ts` | 8 | 1..8 | 0 | 0 | +| `frontend/src/utils/apiError.ts` | 16 | 1..16 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch55.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch55.md new file mode 100644 index 00000000..4c586701 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch55.md @@ -0,0 +1,42 @@ +# BUG 专项逐行走读 — Batch 55(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/utils/auditLabels.ts` | 290 | 1..290 | 0 | 0 | +| `frontend/src/utils/auditNormalize.ts` | 23 | 1..23 | 0 | 0 | +| `frontend/src/utils/editorLanguage.ts` | 196 | 1..196 | 0 | 0 | +| `frontend/src/utils/fileBrowse.ts` | 61 | 1..61 | 0 | 0 | +| `frontend/src/utils/fileDownload.ts` | 12 | 1..12 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch56.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch56.md new file mode 100644 index 00000000..8c292118 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch56.md @@ -0,0 +1,42 @@ +# BUG 专项逐行走读 — Batch 56(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/utils/fileMode.ts` | 128 | 1..128 | 0 | 0 | +| `frontend/src/utils/filePreload.ts` | 141 | 1..141 | 0 | 0 | +| `frontend/src/utils/fileSort.ts` | 57 | 1..57 | 0 | 0 | +| `frontend/src/utils/pushErrorMessage.ts` | 65 | 1..65 | 0 | 0 | +| `frontend/src/utils/remotePath.ts` | 92 | 1..92 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch57.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch57.md new file mode 100644 index 00000000..f4f6107f --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch57.md @@ -0,0 +1,42 @@ +# BUG 专项逐行走读 — Batch 57(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/utils/serverSelection.ts` | 35 | 1..35 | 0 | 0 | +| `frontend/src/utils/shellHighlight.ts` | 196 | 1..196 | 0 | 0 | +| `frontend/src/utils/status.ts` | 35 | 1..35 | 0 | 0 | +| `frontend/src/utils/validation.ts` | 46 | 1..46 | 0 | 0 | +| `frontend/src/utils/wsUrl.ts` | 23 | 1..23 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch58.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch58.md new file mode 100644 index 00000000..e2cc9a77 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch58.md @@ -0,0 +1,63 @@ +# BUG 专项逐行走读 — Batch 58(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/src/main.ts` | 28 | 1..28 | 0 | 0 | +| `web/app/api.js` | 214 | 1..214 | 11 | 0 | +| `web/app/index.html` | 14 | 1..14 | 0 | 0 | +| `web/app/install.html` | 730 | 1..730 | 6 | 0 | +| `web/app/layout.js` | 231 | 1..231 | 4 | 0 | +| `web/app/preview.html` | 183 | 1..183 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `web/app/api.js` | 8 | FE-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/api.js` | 10 | FE-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/api.js` | 22 | FE-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/api.js` | 29 | FE-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/api.js` | 30 | FE-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/api.js` | 59 | FE-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/api.js` | 71 | FE-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/api.js` | 94 | FE-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/api.js` | 122 | FE-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/api.js` | 188 | FE-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/api.js` | 214 | FE-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/install.html` | 608 | FE-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/install.html` | 618 | FE-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/install.html` | 636 | FE-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/install.html` | 653 | FE-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/install.html` | 685 | FE-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/install.html` | 697 | FE-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/layout.js` | 42 | FE-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/layout.js` | 199 | FE-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/layout.js` | 217 | FE-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/layout.js` | 231 | FE-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch59.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch59.md new file mode 100644 index 00000000..979412f9 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch59.md @@ -0,0 +1,73 @@ +# BUG 专项逐行走读 — Batch 59(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `web/agent/agent.py` | 434 | 1..434 | 26 | 0 | +| `web/agent/agent.sh` | 98 | 1..98 | 3 | 0 | +| `web/app/preview-v3.html` | 369 | 1..369 | 1 | 0 | +| `web/app/preview-v4.html` | 359 | 1..359 | 1 | 0 | +| `web/app/preview-v5.html` | 375 | 1..375 | 1 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `web/agent/agent.py` | 24 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 29 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 31 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 33 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 41 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 63 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 125 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 150 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 161 | PY-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 164 | PY-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 175 | PY-05 | SAFE | 已有防护/封装: verify_api_key | +| `web/agent/agent.py` | 199 | PY-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 201 | PY-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 202 | PY-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 234 | PY-05 | SAFE | 已有防护/封装: verify_api_key | +| `web/agent/agent.py` | 237 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 256 | PY-05 | SAFE | 已有防护/封装: verify_api_key | +| `web/agent/agent.py` | 259 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 260 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 291 | PY-05 | SAFE | 已有防护/封装: verify_api_key | +| `web/agent/agent.py` | 298 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 322 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 324 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 336 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 337 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.py` | 338 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.sh` | 15 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.sh` | 47 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/agent.sh` | 67 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/preview-v3.html` | 363 | FE-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/preview-v4.html` | 353 | FE-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/app/preview-v5.html` | 369 | FE-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch6.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch6.md new file mode 100644 index 00000000..3a3fed6b --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch6.md @@ -0,0 +1,93 @@ +# BUG 专项逐行走读 — Batch 6(2026-06-04) + +**标准**: `docs/project/line-walk-audit-standard.md` · 八类仅列 **BUG** + +## 范围(5 文件,全文 Read) + +| 文件 | N | Read | +|------|---|------| +| `server/api/auth_jwt.py` | 275 | 中间件 + `_verify_token` | +| `server/application/services/auth_service.py` | refresh / Redis | 196–620 | +| `server/background/script_execution_flush.py` | 46 | 1–46 | +| `frontend/src/composables/files/useFilesBrowse.ts` | 272 | ETag 流程 | +| `frontend/src/stores/files.ts` | 129 | 目录缓存 | + +--- + +## Step 3 规则扫描(BUG 相关) + +| 文件 | 命中 | +|------|------| +| auth_service.py | refresh 重用、Redis 故障 | +| auth_jwt.py | `token_version`、公共路由 | +| script_execution_flush.py | Redis→MySQL 批量 | +| useFilesBrowse.ts | 304 / 本地 TTL 短路 | +| files.ts | etag 与 invalidate | + +--- + +## Closure 表(BUG) + +| 文件 | 行号 | 判定 | 严重度 | 理由 | +|------|------|------|--------|------| +| auth_service.py | 239-246 | **FINDING** | P1 | 已轮换 refresh 不在 Redis 时仅 401,不升 `token_version`,被盗旧 token 与合法新会话可并存 | +| auth_service.py | 597-598 | **FINDING** | P2 | Redis 异常时 `_check_refresh_token` 曾返回 False,误拒合法刷新 | +| useFilesBrowse.ts | 187-195 | **FINDING** | P2 | 5s 内命中本地缓存直接 return,跳过后端 ETag,变更后仍显示旧列表 | +| auth_jwt.py | 217-226 | SAFE | — | `tv` 与 DB 严格比对 | +| script_execution_flush.py | 19-45 | SAFE | — | flush/stuck 分工清晰;`update_status` 内 commit | +| files.ts | 85-98 | SAFE | — | `invalidateCache` 与 mutation 联动 | + +--- + +## FINDING 列表 + +### BUG-06-04-13 · P1 · auth_service refresh 重用 + +**现象**:轮换后旧 refresh 重放仅返回 invalid,不注销其他设备。 + +**修复**:`membership is False` → `_handle_refresh_token_reuse()`(`token_version++` + 清空 Redis refresh set)。 + +### BUG-06-04-14 · P2 · auth Redis 不可用 + +**现象**:Redis 异常与「令牌不在集合」同为 False。 + +**修复**:`_refresh_token_membership` 返回 `Optional[bool]`;`None` → `service_unavailable`。 + +### BUG-06-04-15 · P2 · useFilesBrowse 本地缓存短路 + +**现象**:有 fresh 缓存时不发 `If-None-Match`,服务端失效 ETag 后 UI 仍旧。 + +**修复**:缓存仅作乐观展示,始终继续条件请求。 + +--- + +## Step 7 统计 + +``` +auth_service.py 2 FINDING(已修) +useFilesBrowse.ts 1 FINDING(已修) +auth_jwt.py 0 +script_execution_flush.py 0 +files.ts 0 +``` + +## Step 8 DoD + +- [x] 5 文件覆盖 +- [x] Closure 全表 +- [x] 3 项已修 + pytest + +## 验证 + +```bash +ruff check server/application/services/auth_service.py +pytest tests/test_auth_refresh_reuse.py tests/ -q --tb=no +cd frontend && npm run type-check +``` + +Changelog: `docs/changelog/2026-06-04-bug-batch6-auth-files-etag.md` + +## 下一批 / 收尾 + +- BUG 专项 Batch 1–6 汇总报告(可选) +- 部署:重启 nexus + 前端构建 diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch60.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch60.md new file mode 100644 index 00000000..2d6d6868 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch60.md @@ -0,0 +1,134 @@ +# BUG 专项逐行走读 — Batch 60(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `tests/__init__.py` | 0 | 1..0 | 0 | 0 | +| `tests/acceptance_catalog.py` | 64 | 1..64 | 0 | 0 | +| `tests/conftest.py` | 95 | 1..95 | 0 | 0 | +| `tests/load_test.py` | 201 | 1..201 | 20 | 0 | +| `tests/quick_load.py` | 86 | 1..86 | 9 | 0 | +| `web/agent/config.example.json` | 20 | 1..20 | 0 | 0 | +| `web/agent/install.sh` | 237 | 1..237 | 43 | 0 | +| `web/agent/uninstall.sh` | 115 | 1..115 | 20 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `tests/load_test.py` | 89 | PY-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/load_test.py` | 98 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/load_test.py` | 99 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/load_test.py` | 100 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/load_test.py` | 123 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/load_test.py` | 125 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/load_test.py` | 140 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/load_test.py` | 144 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/load_test.py` | 157 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/load_test.py` | 159 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/load_test.py` | 162 | PY-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/load_test.py` | 162 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/load_test.py` | 173 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/load_test.py` | 174 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/load_test.py` | 175 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/load_test.py` | 176 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/load_test.py` | 177 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/load_test.py` | 179 | PY-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/load_test.py` | 179 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/load_test.py` | 193 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/quick_load.py` | 60 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/quick_load.py` | 62 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/quick_load.py` | 64 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/quick_load.py` | 70 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/quick_load.py` | 75 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/quick_load.py` | 76 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/quick_load.py` | 77 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/quick_load.py` | 84 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/quick_load.py` | 86 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 60 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 61 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 62 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 68 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 69 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 76 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 79 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 82 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 85 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 93 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 94 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 97 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 98 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 103 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 107 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 114 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 116 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 118 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 120 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 133 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 135 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 136 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 138 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 140 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 142 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 144 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 145 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 146 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 147 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 148 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 149 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 156 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 159 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 160 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 164 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 165 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 166 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 174 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 192 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | 210 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/install.sh` | … | … | SAFE | 另有 3 条 SAFE(省略) | +| `web/agent/uninstall.sh` | 36 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/uninstall.sh` | 43 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/uninstall.sh` | 49 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/uninstall.sh` | 50 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/uninstall.sh` | 51 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/uninstall.sh` | 52 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/uninstall.sh` | 60 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/uninstall.sh` | 61 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/uninstall.sh` | 62 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/uninstall.sh` | 66 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/uninstall.sh` | 74 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/uninstall.sh` | 75 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/uninstall.sh` | 80 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/uninstall.sh` | 88 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/uninstall.sh` | 89 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/uninstall.sh` | 90 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/uninstall.sh` | 91 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/uninstall.sh` | 92 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/uninstall.sh` | 98 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `web/agent/uninstall.sh` | 105 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch61.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch61.md new file mode 100644 index 00000000..88be32b3 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch61.md @@ -0,0 +1,96 @@ +# BUG 专项逐行走读 — Batch 61(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `tests/test_agent_heartbeat_ttl.py` | 9 | 1..9 | 1 | 0 | +| `tests/test_api.py` | 279 | 1..279 | 42 | 0 | +| `tests/test_auth_refresh_reuse.py` | 60 | 1..60 | 0 | 0 | +| `tests/test_editor_write_conflict.py` | 163 | 1..163 | 13 | 0 | +| `tests/test_file_permissions.py` | 75 | 1..75 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `tests/test_agent_heartbeat_ttl.py` | 9 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 33 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 81 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 88 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 92 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 97 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 101 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 108 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 116 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 119 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 123 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 129 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 134 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 137 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 140 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 143 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 155 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 156 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 157 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 158 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 161 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 162 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 164 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 172 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 174 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 178 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 183 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 205 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 207 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 209 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 218 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 220 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 235 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 237 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 245 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 247 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 253 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 256 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 259 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 268 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | 269 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_api.py` | … | … | SAFE | 另有 2 条 SAFE(省略) | +| `tests/test_editor_write_conflict.py` | 40 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_editor_write_conflict.py` | 58 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_editor_write_conflict.py` | 64 | PY-07 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_editor_write_conflict.py` | 78 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_editor_write_conflict.py` | 83 | PY-07 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_editor_write_conflict.py` | 87 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_editor_write_conflict.py` | 89 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_editor_write_conflict.py` | 106 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_editor_write_conflict.py` | 111 | PY-07 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_editor_write_conflict.py` | 113 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_editor_write_conflict.py` | 114 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_editor_write_conflict.py` | 162 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_editor_write_conflict.py` | 163 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch62.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch62.md new file mode 100644 index 00000000..5fce1200 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch62.md @@ -0,0 +1,85 @@ +# BUG 专项逐行走读 — Batch 62(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `tests/test_files_browse_etag.py` | 43 | 1..43 | 0 | 0 | +| `tests/test_files_elevation.py` | 109 | 1..109 | 10 | 0 | +| `tests/test_full_site_api.py` | 160 | 1..160 | 23 | 0 | +| `tests/test_nexus_v6.py` | 214 | 1..214 | 11 | 0 | +| `tests/test_posix_paths.py` | 62 | 1..62 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `tests/test_files_elevation.py` | 46 | PY-07 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_files_elevation.py` | 49 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_files_elevation.py` | 64 | PY-07 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_files_elevation.py` | 70 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_files_elevation.py` | 84 | PY-07 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_files_elevation.py` | 88 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_files_elevation.py` | 89 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_files_elevation.py` | 101 | PY-07 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_files_elevation.py` | 106 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_files_elevation.py` | 107 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 38 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 43 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 48 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 52 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 56 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 59 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 65 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 66 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 67 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 68 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 71 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 78 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 83 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 88 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 105 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 107 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 120 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 126 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 139 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 145 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 148 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 154 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_full_site_api.py` | 155 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_nexus_v6.py` | 22 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_nexus_v6.py` | 33 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_nexus_v6.py` | 45 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_nexus_v6.py` | 124 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_nexus_v6.py` | 140 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_nexus_v6.py` | 145 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_nexus_v6.py` | 151 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_nexus_v6.py` | 154 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_nexus_v6.py` | 163 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_nexus_v6.py` | 166 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_nexus_v6.py` | 171 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch63.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch63.md new file mode 100644 index 00000000..38d15e3b --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch63.md @@ -0,0 +1,53 @@ +# BUG 专项逐行走读 — Batch 63(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `tests/test_remote_path_validation.py` | 48 | 1..48 | 0 | 0 | +| `tests/test_retry_runner_outcome.py` | 58 | 1..58 | 0 | 0 | +| `tests/test_schedule_runner_once.py` | 21 | 1..21 | 0 | 0 | +| `tests/test_schema_path_validators.py` | 61 | 1..61 | 0 | 0 | +| `tests/test_security_unit.py` | 229 | 1..229 | 12 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `tests/test_security_unit.py` | 64 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_security_unit.py` | 131 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_security_unit.py` | 136 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_security_unit.py` | 139 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_security_unit.py` | 145 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_security_unit.py` | 148 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_security_unit.py` | 155 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_security_unit.py` | 167 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_security_unit.py` | 171 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_security_unit.py` | 182 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_security_unit.py` | 195 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_security_unit.py` | 224 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch64.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch64.md new file mode 100644 index 00000000..1900e035 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch64.md @@ -0,0 +1,53 @@ +# BUG 专项逐行走读 — Batch 64(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `tests/test_step0_infrastructure.py` | 157 | 1..157 | 7 | 0 | +| `tests/test_step1to5_features.py` | 167 | 1..167 | 4 | 0 | +| `tests/test_sync_batch_id.py` | 24 | 1..24 | 0 | 0 | +| `tests/test_sync_error_message.py` | 39 | 1..39 | 0 | 0 | +| `tests/test_sync_websocket.py` | 44 | 1..44 | 1 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `tests/test_step0_infrastructure.py` | 55 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_step0_infrastructure.py` | 61 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_step0_infrastructure.py` | 68 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_step0_infrastructure.py` | 73 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_step0_infrastructure.py` | 143 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_step0_infrastructure.py` | 147 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_step0_infrastructure.py` | 153 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_step1to5_features.py` | 22 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_step1to5_features.py` | 40 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_step1to5_features.py` | 52 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_step1to5_features.py` | 126 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_sync_websocket.py` | 8 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch65.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch65.md new file mode 100644 index 00000000..f6d3f91d --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch65.md @@ -0,0 +1,41 @@ +# BUG 专项逐行走读 — Batch 65(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `tests/test_text_io.py` | 43 | 1..43 | 1 | 0 | +| `tests/test_unix_ls.py` | 21 | 1..21 | 0 | 0 | +| `tests/test_upload_staging_cleanup.py` | 64 | 1..64 | 1 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `tests/test_text_io.py` | 37 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `tests/test_upload_staging_cleanup.py` | 39 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch66.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch66.md new file mode 100644 index 00000000..8d53a630 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch66.md @@ -0,0 +1,89 @@ +# BUG 专项逐行走读 — Batch 66(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `deploy/check_shell_eol.sh` | 32 | 1..32 | 0 | 0 | +| `deploy/db_backup.sh` | 103 | 1..103 | 27 | 0 | +| `deploy/deploy-frontend.sh` | 60 | 1..60 | 3 | 0 | +| `deploy/gate_log.py` | 57 | 1..57 | 4 | 0 | +| `deploy/health_monitor.sh` | 70 | 1..70 | 14 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `deploy/db_backup.sh` | 12 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 13 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 14 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 18 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 19 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 25 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 26 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 27 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 30 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 51 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 57 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 61 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 64 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 66 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 67 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 68 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 70 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 75 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 78 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 80 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 85 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 89 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 90 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 91 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 98 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 99 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/db_backup.sh` | 100 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/deploy-frontend.sh` | 52 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/deploy-frontend.sh` | 53 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/deploy-frontend.sh` | 55 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/gate_log.py` | 40 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/gate_log.py` | 50 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/gate_log.py` | 55 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/gate_log.py` | 57 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/health_monitor.sh` | 26 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/health_monitor.sh` | 31 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/health_monitor.sh` | 32 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/health_monitor.sh` | 33 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/health_monitor.sh` | 37 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/health_monitor.sh` | 39 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/health_monitor.sh` | 46 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/health_monitor.sh` | 48 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/health_monitor.sh` | 50 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/health_monitor.sh` | 52 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/health_monitor.sh` | 55 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/health_monitor.sh` | 59 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/health_monitor.sh` | 61 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/health_monitor.sh` | 69 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch67.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch67.md new file mode 100644 index 00000000..21f267a9 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch67.md @@ -0,0 +1,82 @@ +# BUG 专项逐行走读 — Batch 67(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `deploy/install.sh` | 526 | 1..526 | 110 | 0 | +| `deploy/migrations/001_indexes.sql` | 24 | 1..24 | 0 | 0 | +| `deploy/nexus.conf` | 32 | 1..32 | 0 | 0 | +| `deploy/nginx_http.conf` | 79 | 1..79 | 0 | 0 | +| `deploy/nginx_https.conf` | 113 | 1..113 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `deploy/install.sh` | 28 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 30 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 31 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 32 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 35 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 37 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 39 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 95 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 109 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 113 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 117 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 128 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 129 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 130 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 135 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 138 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 144 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 145 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 146 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 154 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 161 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 185 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 187 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 191 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 192 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 193 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 194 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 195 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 196 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 197 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 198 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 206 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 218 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 224 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 225 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 234 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 237 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 238 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 239 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | 241 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/install.sh` | … | … | SAFE | 另有 70 条 SAFE(省略) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch68.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch68.md new file mode 100644 index 00000000..7536fd52 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch68.md @@ -0,0 +1,92 @@ +# BUG 专项逐行走读 — Batch 68(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `deploy/pre_deploy_check.sh` | 299 | 1..299 | 0 | 0 | +| `deploy/prune_frontend_assets.py` | 149 | 1..149 | 6 | 0 | +| `deploy/run_test_api_on_server.sh` | 6 | 1..6 | 0 | 0 | +| `deploy/uninstall.sh` | 145 | 1..145 | 23 | 0 | +| `deploy/upgrade.sh` | 130 | 1..130 | 22 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `deploy/prune_frontend_assets.py` | 36 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/prune_frontend_assets.py` | 42 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/prune_frontend_assets.py` | 49 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/prune_frontend_assets.py` | 60 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/prune_frontend_assets.py` | 75 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/prune_frontend_assets.py` | 90 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 44 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 46 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 53 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 68 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 69 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 70 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 75 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 86 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 91 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 92 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 95 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 102 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 104 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 105 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 111 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 112 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 113 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 114 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 119 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 130 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 132 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 133 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/uninstall.sh` | 136 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 36 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 39 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 45 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 50 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 55 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 56 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 62 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 67 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 68 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 72 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 76 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 87 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 88 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 89 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 93 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 108 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 109 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 110 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 115 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 123 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 127 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/upgrade.sh` | 127 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch69.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch69.md new file mode 100644 index 00000000..010a9579 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch69.md @@ -0,0 +1,46 @@ +# BUG 专项逐行走读 — Batch 69(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `deploy/verify_login.py` | 55 | 1..55 | 9 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `deploy/verify_login.py` | 22 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/verify_login.py` | 28 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/verify_login.py` | 33 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/verify_login.py` | 34 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/verify_login.py` | 35 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/verify_login.py` | 37 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/verify_login.py` | 44 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/verify_login.py` | 46 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `deploy/verify_login.py` | 54 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch7.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch7.md new file mode 100644 index 00000000..50776276 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch7.md @@ -0,0 +1,78 @@ +# BUG 专项逐行走读 — Batch 7(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/api/servers.py` | 1742 | 1..1742 | 68 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/api/servers.py` | 14 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 127 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 134 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 191 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 225 | PY-05 | SAFE | 已有防护/封装: Depends(get_current | +| `server/api/servers.py` | 234 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 260 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 263 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 296 | PY-05 | SAFE | 已有防护/封装: Depends(get_current | +| `server/api/servers.py` | 306 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 315 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 323 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 392 | PY-05 | SAFE | 已有防护/封装: Depends(get_current | +| `server/api/servers.py` | 401 | PY-05 | SAFE | 已有防护/封装: Depends(get_current | +| `server/api/servers.py` | 420 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 485 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 488 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 528 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 530 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 598 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 608 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 631 | PY-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 637 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 669 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 679 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 700 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 715 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 725 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 756 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 770 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 788 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 789 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 833 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 843 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 869 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 913 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 940 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 951 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 966 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | 980 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/servers.py` | … | … | SAFE | 另有 28 条 SAFE(省略) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch70.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch70.md new file mode 100644 index 00000000..c226132a --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch70.md @@ -0,0 +1,80 @@ +# BUG 专项逐行走读 — Batch 70(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `scripts/_browse_test.py` | 37 | 1..37 | 8 | 0 | +| `scripts/bootstrap_database.py` | 118 | 1..118 | 13 | 0 | +| `scripts/cache-node20-windows.ps1` | 28 | 1..28 | 0 | 0 | +| `scripts/check_text_eol.py` | 111 | 1..111 | 11 | 0 | +| `scripts/check_worktree_eol.py` | 107 | 1..107 | 7 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `scripts/_browse_test.py` | 9 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/_browse_test.py` | 13 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/_browse_test.py` | 17 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/_browse_test.py` | 19 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/_browse_test.py` | 26 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/_browse_test.py` | 32 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/_browse_test.py` | 35 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/_browse_test.py` | 37 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/bootstrap_database.py` | 14 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/bootstrap_database.py` | 20 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/bootstrap_database.py` | 51 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/bootstrap_database.py` | 73 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/bootstrap_database.py` | 78 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/bootstrap_database.py` | 88 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/bootstrap_database.py` | 91 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/bootstrap_database.py` | 94 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/bootstrap_database.py` | 100 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/bootstrap_database.py` | 107 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/bootstrap_database.py` | 109 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/bootstrap_database.py` | 112 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/bootstrap_database.py` | 113 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/check_text_eol.py` | 18 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/check_text_eol.py` | 44 | PY-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/check_text_eol.py` | 51 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/check_text_eol.py` | 57 | PY-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/check_text_eol.py` | 63 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/check_text_eol.py` | 77 | PY-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/check_text_eol.py` | 77 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/check_text_eol.py` | 78 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/check_text_eol.py` | 93 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/check_text_eol.py` | 95 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/check_text_eol.py` | 106 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/check_worktree_eol.py` | 17 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/check_worktree_eol.py` | 34 | PY-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/check_worktree_eol.py` | 45 | PY-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/check_worktree_eol.py` | 80 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/check_worktree_eol.py` | 90 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/check_worktree_eol.py` | 92 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/check_worktree_eol.py` | 95 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch71.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch71.md new file mode 100644 index 00000000..fef4078f --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch71.md @@ -0,0 +1,49 @@ +# BUG 专项逐行走读 — Batch 71(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `scripts/generate_bug_scan_registry.py` | 555 | 1..555 | 8 | 0 | +| `scripts/grant_nexus_local.sql` | 9 | 1..9 | 0 | 0 | +| `scripts/linux_mcp_mysql.sh` | 37 | 1..37 | 0 | 0 | +| `scripts/normalize_worktree_lf.sh` | 68 | 1..68 | 0 | 0 | +| `scripts/proxy-env.sh` | 16 | 1..16 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `scripts/generate_bug_scan_registry.py` | 11 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/generate_bug_scan_registry.py` | 105 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/generate_bug_scan_registry.py` | 200 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/generate_bug_scan_registry.py` | 373 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/generate_bug_scan_registry.py` | 438 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/generate_bug_scan_registry.py` | 472 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/generate_bug_scan_registry.py` | 550 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/generate_bug_scan_registry.py` | 551 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch72.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch72.md new file mode 100644 index 00000000..d7b2a830 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch72.md @@ -0,0 +1,86 @@ +# BUG 专项逐行走读 — Batch 72(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `scripts/proxychains-docker.conf` | 9 | 1..9 | 0 | 0 | +| `scripts/run_full_acceptance.sh` | 24 | 1..24 | 1 | 0 | +| `scripts/run_full_e2e.sh` | 13 | 1..13 | 1 | 0 | +| `scripts/run_nexus_acceptance.py` | 567 | 1..567 | 36 | 0 | +| `scripts/seed_local_admin.py` | 75 | 1..75 | 7 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `scripts/run_full_acceptance.sh` | 9 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_full_e2e.sh` | 12 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 36 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 87 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 104 | PY-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 150 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 165 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 222 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 256 | PY-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 269 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 271 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 312 | PY-02 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 349 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 397 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 398 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 399 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 410 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 414 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 415 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 422 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 443 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 444 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 445 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 446 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 447 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 456 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 464 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 467 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 468 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 487 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 492 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 498 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 505 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 552 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 556 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 557 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 560 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/run_nexus_acceptance.py` | 562 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/seed_local_admin.py` | 21 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/seed_local_admin.py` | 53 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/seed_local_admin.py` | 55 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/seed_local_admin.py` | 61 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/seed_local_admin.py` | 62 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/seed_local_admin.py` | 63 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/seed_local_admin.py` | 70 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch73.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch73.md new file mode 100644 index 00000000..90e071f8 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch73.md @@ -0,0 +1,73 @@ +# BUG 专项逐行走读 — Batch 73(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `scripts/start-dev.sh` | 58 | 1..58 | 0 | 0 | +| `scripts/sync_frontend_vendor.py` | 180 | 1..180 | 18 | 0 | +| `scripts/sync_mysql_mcp_env.py` | 156 | 1..156 | 5 | 0 | +| `scripts/unlock_login_lockout.py` | 114 | 1..114 | 9 | 0 | +| `scripts/with-proxy.sh` | 9 | 1..9 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `scripts/sync_frontend_vendor.py` | 16 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_frontend_vendor.py` | 81 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_frontend_vendor.py` | 83 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_frontend_vendor.py` | 85 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_frontend_vendor.py` | 89 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_frontend_vendor.py` | 94 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_frontend_vendor.py` | 124 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_frontend_vendor.py` | 125 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_frontend_vendor.py` | 135 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_frontend_vendor.py` | 146 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_frontend_vendor.py` | 150 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_frontend_vendor.py` | 151 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_frontend_vendor.py` | 153 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_frontend_vendor.py` | 161 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_frontend_vendor.py` | 165 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_frontend_vendor.py` | 166 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_frontend_vendor.py` | 168 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_frontend_vendor.py` | 176 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_mysql_mcp_env.py` | 21 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_mysql_mcp_env.py` | 128 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_mysql_mcp_env.py` | 133 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_mysql_mcp_env.py` | 139 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/sync_mysql_mcp_env.py` | 146 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/unlock_login_lockout.py` | 26 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/unlock_login_lockout.py` | 33 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/unlock_login_lockout.py` | 64 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/unlock_login_lockout.py` | 70 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/unlock_login_lockout.py` | 79 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/unlock_login_lockout.py` | 80 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/unlock_login_lockout.py` | 84 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/unlock_login_lockout.py` | 86 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/unlock_login_lockout.py` | 94 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch74.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch74.md new file mode 100644 index 00000000..4a468e5a --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch74.md @@ -0,0 +1,69 @@ +# BUG 专项逐行走读 — Batch 74(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `scripts/wsl_check_mysql.py` | 66 | 1..66 | 13 | 0 | +| `scripts/wsl_ensure_venv.sh` | 19 | 1..19 | 7 | 0 | +| `scripts/wsl_install_node20.sh` | 117 | 1..117 | 1 | 0 | +| `scripts/wsl_integration_smoke.sh` | 76 | 1..76 | 7 | 0 | +| `scripts/wsl_mcp_mysql.sh` | 28 | 1..28 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `scripts/wsl_check_mysql.py` | 8 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_check_mysql.py` | 14 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_check_mysql.py` | 21 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_check_mysql.py` | 30 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_check_mysql.py` | 36 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_check_mysql.py` | 37 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_check_mysql.py` | 38 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_check_mysql.py` | 40 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_check_mysql.py` | 43 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_check_mysql.py` | 47 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_check_mysql.py` | 55 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_check_mysql.py` | 57 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_check_mysql.py` | 61 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_ensure_venv.sh` | 5 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_ensure_venv.sh` | 11 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_ensure_venv.sh` | 13 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_ensure_venv.sh` | 16 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_ensure_venv.sh` | 17 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_ensure_venv.sh` | 18 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_ensure_venv.sh` | 19 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_install_node20.sh` | 117 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_integration_smoke.sh` | 8 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_integration_smoke.sh` | 28 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_integration_smoke.sh` | 44 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_integration_smoke.sh` | 48 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_integration_smoke.sh` | 53 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_integration_smoke.sh` | 58 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_integration_smoke.sh` | 65 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch75.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch75.md new file mode 100644 index 00000000..1fb19499 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch75.md @@ -0,0 +1,46 @@ +# BUG 专项逐行走读 — Batch 75(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `scripts/wsl_show_grants.py` | 45 | 1..45 | 6 | 0 | +| `scripts/wsl_start_dev.sh` | 21 | 1..21 | 2 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `scripts/wsl_show_grants.py` | 8 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_show_grants.py` | 15 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_show_grants.py` | 28 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_show_grants.py` | 36 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_show_grants.py` | 37 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_show_grants.py` | 39 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_start_dev.sh` | 5 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `scripts/wsl_start_dev.sh` | 21 | SH-01 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch76.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch76.md new file mode 100644 index 00000000..bc3a46a4 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch76.md @@ -0,0 +1,50 @@ +# BUG 专项逐行走读 — Batch 76(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `.dockerignore` | 29 | 1..29 | 0 | 0 | +| `Dockerfile` | 29 | 1..29 | 0 | 0 | +| `docker/.env.example` | 18 | 1..18 | 0 | 0 | +| `docker/README.md` | 56 | 1..56 | 0 | 0 | +| `docker/entrypoint.sh` | 72 | 1..72 | 0 | 0 | +| `docker/generate_env.py` | 52 | 1..52 | 5 | 0 | +| `docker/sync_root_env.py` | 85 | 1..85 | 2 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `docker/generate_env.py` | 10 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `docker/generate_env.py` | 30 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `docker/generate_env.py` | 46 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `docker/generate_env.py` | 47 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `docker/generate_env.py` | 48 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `docker/sync_root_env.py` | 10 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `docker/sync_root_env.py` | 81 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch77.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch77.md new file mode 100644 index 00000000..e9c5ea18 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch77.md @@ -0,0 +1,42 @@ +# BUG 专项逐行走读 — Batch 77(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `frontend/package.json` | 47 | 1..47 | 0 | 0 | +| `frontend/vite.config.mts` | 54 | 1..54 | 0 | 0 | +| `requirements-dev.txt` | 18 | 1..18 | 0 | 0 | +| `requirements.txt` | 18 | 1..18 | 0 | 0 | +| `ruff.toml` | 41 | 1..41 | 0 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| — | — | — | — | H=0,全文 Read 确认 CLEAN | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch8.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch8.md new file mode 100644 index 00000000..1ea3e2d3 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch8.md @@ -0,0 +1,78 @@ +# BUG 专项逐行走读 — Batch 8(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/api/sync_v2.py` | 1666 | 1..1666 | 84 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/api/sync_v2.py` | 9 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 57 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 73 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 93 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 126 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 139 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 172 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 173 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 193 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 202 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 203 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 216 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 217 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 230 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 231 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 256 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 287 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 326 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 397 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 429 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 477 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 503 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 535 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 601 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 612 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 635 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 659 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 665 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 671 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 687 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 693 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 716 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 730 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 761 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 788 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 800 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 854 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 869 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 870 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | 880 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/sync_v2.py` | … | … | SAFE | 另有 44 条 SAFE(省略) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch9.md b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch9.md new file mode 100644 index 00000000..aab17d26 --- /dev/null +++ b/docs/archive/audits/2026-06-04-line-walk/audit-bug-line-walk-2026-06-04-batch9.md @@ -0,0 +1,174 @@ +# BUG 专项逐行走读 — Batch 9(2026-06-04) + +**标准**: `standards/line-walk-audit-standard-v2.md` + +## 范围 + +| 文件 | N | Read | H | FINDING | +|------|---|------|---|---------| +| `server/api/auth.py` | 375 | 1..375 | 14 | 0 | +| `server/api/settings.py` | 1396 | 1..1396 | 38 | 0 | +| `server/api/terminal.py` | 238 | 1..238 | 14 | 0 | +| `server/api/websocket.py` | 552 | 1..552 | 34 | 0 | +| `server/api/webssh.py` | 394 | 1..394 | 33 | 0 | + +## Step 3 规则扫描 + +自动化规则表 §四(PY/FE/SH);每命中经 Closure。 + +## Closure 表 + +| 文件 | 行号 | 规则 | 判定 | 理由 | +|------|------|------|------|------| +| `server/api/auth.py` | 10 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/auth.py` | 20 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/auth.py` | 113 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/auth.py` | 153 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/auth.py` | 191 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/auth.py` | 212 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/auth.py` | 223 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/auth.py` | 232 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/auth.py` | 242 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/auth.py` | 251 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/auth.py` | 258 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/auth.py` | 271 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/auth.py` | 330 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/auth.py` | 352 | PY-05 | SAFE | 已有防护/封装: Depends(get_current | +| `server/api/settings.py` | 18 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 90 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 114 | PY-05 | SAFE | 已有防护/封装: Depends(get_current | +| `server/api/settings.py` | 128 | PY-05 | SAFE | 已有防护/封装: Depends(get_current | +| `server/api/settings.py` | 155 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 176 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 241 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 280 | PY-05 | SAFE | 已有防护/封装: Depends(get_current | +| `server/api/settings.py` | 292 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 318 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 341 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 359 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 381 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 479 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 613 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 749 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 804 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 804 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 805 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 842 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 852 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 855 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 856 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 863 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 868 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 874 | PY-05 | SAFE | 已有防护/封装: Depends(get_current | +| `server/api/settings.py` | 899 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 925 | PY-05 | SAFE | 已有防护/封装: Depends(get_current | +| `server/api/settings.py` | 948 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 991 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 1038 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 1074 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 1135 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 1162 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 1197 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 1208 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 1232 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/settings.py` | 1266 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/terminal.py` | 6 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/terminal.py` | 16 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/terminal.py` | 52 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/terminal.py` | 73 | PY-05 | SAFE | 已有防护/封装: Depends(get_current | +| `server/api/terminal.py` | 79 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/terminal.py` | 90 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/terminal.py` | 98 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/terminal.py` | 128 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/terminal.py` | 137 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/terminal.py` | 162 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/terminal.py` | 171 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/terminal.py` | 191 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/terminal.py` | 201 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/terminal.py` | 209 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 20 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 24 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 26 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 43 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 47 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 51 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 53 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 55 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 71 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 90 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 101 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 134 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 155 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 293 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 295 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 305 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 310 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 314 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 315 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 319 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 319 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 323 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 331 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 333 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 341 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 346 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 349 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 350 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 354 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 354 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 358 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 384 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 397 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/websocket.py` | 403 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 10 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 14 | PY-03 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 23 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 48 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 56 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 69 | PY-12 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 80 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 82 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 83 | PY-10 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 94 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 99 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 103 | PY-08 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 104 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 110 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 119 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 124 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 127 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 130 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 156 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 167 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 171 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 200 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 204 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 213 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 242 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 249 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 254 | PY-04 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 254 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 295 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 307 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 328 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 338 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | +| `server/api/webssh.py` | 343 | PY-05 | SAFE | 规则命中经上下文核对无 BUG 路径(自动化 SAFE;人工复核见报告) | + +## Step 8 DoD + +- [x] 登记册 batch 文件 `read_range` 覆盖 1..N +- [x] Step2 全文 Read(行数核对) +- [x] Closure 全表(含零命中说明) +- [x] FINDING: **0**(OPEN 须当批修或登记) + +## 验证 + +```bash +ruff check server/ # 若改后端 +pytest tests/ -q --tb=no +``` + +## FINDING 列表 + +无(0 FINDING)。 + diff --git a/docs/archive/audits/2026-06-04-phase2-merged.md b/docs/archive/audits/2026-06-04-phase2-merged.md new file mode 100644 index 00000000..acf70017 --- /dev/null +++ b/docs/archive/audits/2026-06-04-phase2-merged.md @@ -0,0 +1,237 @@ +# Phase2 P2 合并卷 + +> 生成日期: 2026-06-04 · 源文件 9 篇 · 原文 `docs/archive/audits/2026-06-04-phase2/` + +## 批次索引 + +### `audit-phase2-2026-06-04-waveP2-1.md` + +# Phase 2 深审 — Wave P2-1(Files / SSH 基础设施) +## 登记 +## 入口表 +## Closure 表 +**len(H)=17, Closure=17** +## FINDING +无 P0/P1 新 FINDING。 +## L3 门控(同日记录) +## DoD +- [x] len(H) == Closure +## 验证 + +### `audit-phase2-2026-06-04-waveP2-2.md` + +# Phase 2 深审 — Wave P2-2(sync_v2 全文 + 路径/Shell 依赖) +## 登记 +## 入口表(sync_v2 全 22 路由) +## Closure 表 +**len(H)=20, Closure=20** +## FINDING +无 P0/P1 新 FINDING。 +## DoD +- [x] len(H) == Closure +## 验证 + +### `audit-phase2-2026-06-04-waveP2-3.md` + +# Phase 2 深审 — Wave P2-3(WebSocket / Dashboard 告警链) +## 登记 +## 入口表 +## Closure 表 +**len(H)=16, Closure=16** +## FINDING +无 P0/P1 新 FINDING。 +## DoD +- [x] len(H) == Closure +## 验证 + +### `audit-phase2-2026-06-04-waveP2-4.md` + +# Phase 2 深审 — Wave P2-4(servers.py 全文) +## 登记 +## 入口表(servers.py 26 路由) +## Closure 表 +**len(H)=20, Closure=20** +## FINDING +无 P0/P1 新 FINDING。 +## DoD +- [x] len(H) == Closure +## 验证 + +### `audit-phase2-2026-06-04-waveP2-5.md` + +# Phase 2 深审 — Wave P2-5(webssh.py 全文) +## 登记 +## 入口表 +## Closure 表 +**len(H)=17, Closure=17** +## FINDING +无 P0/P1 新 FINDING。 +## DoD +- [x] len(H) == Closure +## 验证 + +### `audit-phase2-2026-06-04-waveP2-6.md` + +# Phase 2 深审 — Wave P2-6(sync_engine_v2.py 全文) +## 登记 +## 入口表(服务层,经 sync_v2 API 调用) +## Closure 表 +**len(H)=17, Closure=17** +## FINDING +无 P0/P1 新 FINDING。 +## DoD +- [x] len(H) == Closure +## 验证 + +### `audit-phase2-2026-06-04-waveP2-7.md` + +# Phase 2 深审 — Wave P2-7(前端页 5/14) +## 登记 +## Closure 表 +**len(H)=13, Closure=13** +## FINDING +无 P0/P1 新 FINDING。 +## DoD + +### `audit-phase2-2026-06-04-waveP2-8.md` + +# Phase 2 深审 — Wave P2-8(前端页 5/14) +## 登记 +## Closure 表 +**len(H)=11, Closure=11** +## FINDING +无 P0/P1 新 FINDING。 + +### `audit-phase2-2026-06-04-waveP2-9.md` + +# Phase 2 深审 — Wave P2-9(前端页 4/14 + 编排层) +## 登记 +## Closure 表 +**len(H)=5, Closure=5** +## 交叉引用 +## FINDING +无 P0/P1 新 FINDING。 + +## Closure 摘录(合并) + +| SSH | `list_remote_directory` → `ls -la` | 经 pool + server 凭据 | +| H | 文件:行号 | 判定 | 理由 | +| H-Auth | sync_v2.py:74–1614 | SAFE | 22 路由均 `Depends(get_current_admin)` | +| H-Shell | sync_v2.py:156-168 | RISK | `rm -rf` 删目录;JWT+审计+前端 confirm | +| H-Shell | sync_v2.py:156,165,168,223-227 | SAFE | path shlex.quote | +| H-Path | sync_v2.py:149-165 | SAFE | normalize_remote_abs_path | +| H-Clipboard | sync_v2.py:207-208 | SAFE | assert_clipboard_transfer_safe | +| H-Sandbox | sync_v2.py:302,341,413,721 | SAFE | ensure_under_nexus_upload | +| H-Source | sync_v2.py:548,1096 | SAFE | FORBIDDEN prefixes + resolve | +| H-Audit | sync_v2.py:461-475 | SAFE | 失败 log warning+exc_info,非静默 pass | +| H-Upload | sync_v2.py:876-877,977-978 | SAFE | 100MB / 500MB 上限 | +| H-ZipSlip | sync_v2.py:1000-1005 | SAFE | assert_zip + is_path_under_prefix | +| H-Download | sync_v2.py:1244-1272 | SAFE | 100MB + filename sanitize | +| H-Read | sync_v2.py:1322-1326 | SAFE | max_size 来自 payload | +| H-Write | sync_v2.py:1394-1414 | SAFE | 5MB + etag 409 冲突 | +| H-Chmod | sync_v2.py:1507-1514 | SAFE | recursive policy + test -d | +| H-Verify | sync_v2.py:1145-1146 | SAFE | sha256sum 路径 shlex.quote | +| H-Path | posix_paths.py:46-59 | SAFE | 禁 `..` 与反斜杠 | +| H-Path | posix_paths.py:117-136 | SAFE | upload 前缀 + zip-slip | +| H-Sudo | remote_shell.py:35-53 | SAFE | bash -c + shlex.quote(command) | +| H-Clipboard | remote_path_validation.py:24-33 | SAFE | dest 非源子树 | +| H-Parse | unix_ls.py:70-71 | SAFE | 跳过 `.` `..` | +| H-WS-Auth | websocket.py:304-311,340-347 | SAFE | 无 token → 4001;无效 → 4401 | +| H-WS-Token | websocket.py:366-410 | SAFE | tv/updated_at 与 HTTP JWT 一致 | +| H-WS-ADR011 | websocket.py:9,296 | RISK | query JWT;单人运维已接受 | +| H-WS-Limit | websocket.py:49-52 | SAFE | MAX_CONNECTIONS=500 | +| H-WS-Heartbeat | websocket.py:139-165 | SAFE | 30s ping / 60s zombie | +| H-WS-Channel | websocket.py:246-249,531-551 | SAFE | alerts 与 sync 分 channel/manager | +| H-WS-Dedup | websocket.py:416-437 | SAFE | Telegram 5min 冷却;WS 仍实时 | +| H-WS-Recovery | websocket.py:512-513 | SAFE | recovery 清 cooldown | +| H-Frontend-Token | useWebSocket.ts:61 | RISK | URL query token;同 ADR-011 | +| H-Frontend-Auth | useWebSocket.ts:114-116 | SAFE | 4001/4401 → forceLogout | +| H-Frontend-XSS | useWebSocket.ts:99-102 | SAFE | snackbar 文本插值,无 v-html | +| H-Frontend-Singleton | useWebSocket.ts:17-18 | SAFE | 模块级单例;App 统一 connect | +| H-Dashboard-Refresh | DashboardPage.vue:393-397 | SAFE | WS alert → reload stats/alerts | +| H-Dashboard-API | DashboardPage.vue:302-337 | SAFE | http JWT 拉 stats/alert-history | +| H-App-Lifecycle | App.vue:257-259 | SAFE | login connect / logout disconnect | +| H-Alert-API | settings.py:768-828 | SAFE | alert-history JWT + 参数化 filter | +| H-Auth | servers.py:26×路由 | SAFE | 26 端点均 `get_current_admin` | +| H-Mask | servers.py:1692-1698 | SAFE | password_set / agent key 前缀截断 | +| H-Encrypt | servers.py:1096-1099,559-562 | SAFE | Fernet encrypt 入库 | +| H-Allowlist | servers.py:1163-1187 | SAFE | UPDATE 字段白名单;agent_api_key 排除 | +| H-Reauth | servers.py:1256-1260,1479-1483 | SAFE | agent-key/install-cmd 需当前密码 | +| H-Reveal | servers.py:1492-1502 | SAFE | install-cmd 不用全局 API_KEY | +| H-AgentKey | servers.py:1282-1288 | SAFE | 生成后 display_once 响应 | +| H-Fallback | servers.py:626,1321 | RISK | batch/single install 可回退 settings.API_KEY | +| H-Shell | servers.py:631-633,1330-1332 | SAFE | install shlex.quote url/key/port | +| H-Sudo | servers.py:36-60 | RISK | 临时 sudoers 白名单;运维必要 | +| H-SudoPwd | servers.py:1025-1027 | SAFE | sudo -S 密码走 stdin 非 argv | +| H-CSV | servers.py:376-388,445-446 | SAFE | formula injection 防御 + 1MB 上限 | +| H-CSV | servers.py:499-501 | SAFE | MAX_IMPORT_ROWS=500 | +| H-Batch | servers.py:615,686,772,850 | SAFE | Semaphore(5) 并发上限 | +| H-Redis | servers.py:179-201,873-877 | SAFE | 心跳 overlay;失败 log warning | +| H-Audit | servers.py: CUD/batch | SAFE | 写操作 AuditLog | +| H-Crypto | crypto.py:20-26 | SAFE | 无 ENCRYPTION_KEY 则 RuntimeError | +| H-Crypto | crypto.py:52-54 | SAFE | decrypt 失败 raise,非静默 | +| H-Capability | files_capability.py:31-39 | SAFE | 只读 probe,无密码回传 | +| H-Elevation | files_elevation.py:48-55 | SAFE | apply_files_elevation_payload | +| H-Purpose | webssh.py:48-49 | SAFE | 拒绝非 webssh purpose | +| H-Bind | webssh.py:106-109 | SAFE | token server_id 须匹配路径 | +| H-TV | webssh.py:66-76 | SAFE | tv 校验;legacy updated_at +5s grace | +| H-Close | webssh.py:97-104 | SAFE | 缺 token→4001;无效→4401 | +| H-Issue | auth.py:334-346 | SAFE | 签发前 JWT + server/domain 校验 | +| H-Scope | auth_service.py:455-470 | SAFE | JWT 含 server_id + purpose + tv | +| H-Expire | auth_service.py:467 | SAFE | 15 分钟短期令牌 | +| H-WSQuery | webssh.py:88, useTerminalSessions:293 | RISK | ADR-011 query token(已接受) | +| H-Cred | webssh.py:126-135 | SAFE | key/password 配置检查 | +| H-Audit | webssh.py:137-146,353-366 | SAFE | connect/disconnect 审计 | +| H-Session | webssh.py:148-157,369-376 | SAFE | SshSession 创建/关闭 | +| H-DB | webssh.py:111-158 | SAFE | 长连接前关闭 DB session | +| H-Pool | webssh.py:167,337 | SAFE | acquire/release + stale retry | +| H-CmdLog | webssh.py:277-290,379-398 | SAFE | Enter 触发;2000 字符截断 | +| H-Danger | dependencies.py:167-180 | RISK | 危险命令仅 WARN 不阻断(设计) | +| H-Resize | webssh.py:293-296 | SAFE | cols/rows 边界默认 80×24 | +| H-Error | webssh.py:171,204 | SAFE | SSH 失败不向客户端泄露栈 | +| H-Source | sync_engine_v2.py:72-76,327-330 | SAFE | `_nexus_source_path` 拒绝系统路径 | +| H-Conc | sync_engine_v2.py:38,79-80 | SAFE | MAX_CONCURRENT=10 + min 入参 | +| H-DBLock | sync_engine_v2.py:89,149,190 | SAFE | `_db_lock` 串行写 sync_log/retry | +| H-Cancel | sync_engine_v2.py:96-136 | SAFE | Redis cancel flag;失败则继续 push | +| H-Retry | sync_engine_v2.py:193-224 | SAFE | 失败自动 pending retry;去重 | +| H-Subproc | sync_engine_v2.py:468-473 | SAFE | create_subprocess_exec 参数化 | +| H-Shlex | sync_engine_v2.py:456-458 | SAFE | key path shlex.quote | +| H-KeyTmp | sync_engine_v2.py:449-455,491-495 | SAFE | 0600 临时 key + finally unlink | +| H-Pass | sync_engine_v2.py:440-446 | RISK | sshpass -e;/proc 可见(内网运维可接受) | +| H-Remote | sync_engine_v2.py:414 | SAFE | normalize_remote_abs_path | +| H-Timeout | sync_engine_v2.py:476-482 | SAFE | RSYNC_TIMEOUT+30 kill | +| H-Output | sync_engine_v2.py:486-487 | SAFE | stdout/stderr 截断 10KB | +| H-Staging | sync_engine_v2.py:498-516 | SAFE | 仅 manual 全成功才 rmtree staging | +| H-Preview | sync_engine_v2.py:364-365 | SAFE | verbose 文件列表上限 200 | +| H-Audit | sync_engine_v2.py:267-271,380-391 | SAFE | sync_files 审计;失败 log error | +| H-TG | sync_engine_v2.py:274-291 | SAFE | Telegram 失败不阻断 | +| H-Gather | sync_engine_v2.py:254-265 | SAFE | gather 异常计入 failed | +| H-JWT | 各页 http.* | SAFE | 经 `@/api` JWT 客户端 | +| H-WS | DashboardPage:393-397 | RISK | useWebSocket query token(已接受) | +| H-Nav | DashboardPage:383-387 | SAFE | server_id 数字 query 路由 | +| H-Err | DashboardPage:268-345 | SAFE | load 失败 snackbar | +| H-CSV | ServersPage:391-435 | SAFE | import FormData;export 无密码列 | +| H-Batch | ServersPage:325-388 | SAFE | batch 端点 JWT;confirm 删除 | +| H-Form | ServersPage:162-176 | SAFE | 凭据经 ServerFormDialog composable | +| H-Filter | AlertsPage:150-162 | SAFE | 只读分页 API | +| H-Audit | AuditPage:126-141 | SAFE | 只读;normalizeAuditList | +| H-TG | SettingsPage:272-275 | SAFE | GET 不返 token 明文;value_set | +| H-Reveal | SettingsPage:434-460 | SAFE | API Key/TG reveal 需 current_password | +| H-Pw | SettingsPage:339-359 | SAFE | 改密 POST;表单 type=password | +| H-TOTP | SettingsPage:362-404 | SAFE | setup/enable/disable 走 auth API | +| H-CredList | CredentialsPage:253-255 | SAFE | GET 列表无密码明文 | +| H-CredPost | CredentialsPage:323-327 | SAFE | encrypted_pw 仅 POST/PUT | +| H-CredEdit | CredentialsPage:306,340,357 | SAFE | 编辑留空不修改 | +| H-SSH | CredentialsPage:346-347 | SAFE | 私钥仅提交时传输 | +| H-Cron | SchedulesPage:177 | SAFE | cron 正则校验 | +| H-Sched | SchedulesPage:173-180 | SAFE | 路径/服务器必填 | +| H-Retry | RetriesPage:146 | SAFE | retry POST JWT | +| H-CmdView | CommandsPage:139-153 | SAFE | 只读 command-logs/sessions | +| H-ScriptExec | ScriptsPage:415-419,463-467 | SAFE | exec 走后端 JWT | +| H-Quick | ScriptsPage:459-467 | RISK | 临时命令;后端 check_dangerous | +| H-Poll | ScriptsPage:527-528 | SAFE | onUnmounted stopPolling | +| H-FilesShell | FilesPage.vue:33-37 | SAFE | 逻辑在 useFilesPage(D5) | +| H-PushShell | PushPage.vue:105 | SAFE | usePushPage(D4) | +| H-TermShell | TerminalPage.vue | SAFE | useTerminalSessions(D5) | +| H-Login | LoginPage.vue | SAFE | auth.login 内存 token(D6) | +| H-NoStore | 四页 | SAFE | 无 localStorage 存密码 | diff --git a/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-1.md b/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-1.md new file mode 100644 index 00000000..850b3ef7 --- /dev/null +++ b/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-1.md @@ -0,0 +1,65 @@ +# Phase 2 深审 — Wave P2-1(Files / SSH 基础设施) + +**日期**: 2026-06-04 +**范围**: 文件浏览 REST + browse service + SSH pool + 前端预读 +**标准**: `standards/line-walk-audit-standard-v2.md` 8 步(全文 Read) + +## 登记 + +| # | 文件 | 语言 | 行数 N | Read 范围 | +|---|------|------|--------|-----------| +| 1 | `server/api/files.py` | Python | 77 | 1–77 全文 | +| 2 | `server/application/services/files_browse_service.py` | Python | 167 | 1–167 全文 | +| 3 | `server/infrastructure/ssh/asyncssh_pool.py` | Python | 436 | 1–436 全文 | +| 4 | `server/utils/files_elevation.py` | Python | 55 | 1–55 全文 | +| 5 | `frontend/src/utils/filePreload.ts` | TS | 141 | 1–141 全文 | + +## 入口表 + +| 方法 | 路径 | 鉴权 | +|------|------|------| +| GET | `/api/files/browse?server_id=&path=` | JWT + If-None-Match | +| POST | `/sync/read-file`(filePreload 调用) | JWT | +| SSH | `list_remote_directory` → `ls -la` | 经 pool + server 凭据 | + +## Closure 表 + +| H | 文件:行号 | 判定 | 理由 | +|---|-----------|------|------| +| H-Auth | files.py:44 | SAFE | `get_current_admin` | +| H-Path | files.py:57-60 | SAFE | `normalize_remote_abs_path` | +| H-Audit | files.py:72 | SAFE | browse 审计 | +| H-ETag | files.py:62-75 | SAFE | 304 减 SSH 负载 | +| H-Path | files_browse_service.py:111-116 | SAFE | normalize + shlex.quote | +| H-Shell | files_browse_service.py:117-125 | SAFE | 固定 ls 模板;path 已 quote | +| H-Cache | files_browse_service.py:46-62 | SAFE | 5s TTL + max 200 | +| H-Cred | asyncssh_pool.py:183-192 | SAFE | decrypt 仅内存 connect | +| H-HostKey | asyncssh_pool.py:176-179 | RISK | `SSH_STRICT_HOST_CHECKING=false` 可关;默认 strict | +| H-Shell | asyncssh_pool.py:306-339 | SAFE | tee/mv/mkdir 路径 shlex.quote | +| H-Sudo | asyncssh_pool.py:348-355 | SAFE | elevation 三态 OFF/AUTO/ALWAYS | +| H-Pool | asyncssh_pool.py:409-413 | SAFE | timeout 后 close_connection 不 reuse | +| H-Exec | asyncssh_pool.py:389-437 | RISK | `exec_ssh_command` 接受任意 command;调用方须 JWT+审计 | +| H-Elevation | files_elevation.py:21-28 | SAFE | 未知值回退 AUTO | +| H-Auth | filePreload.ts:101-105 | SAFE | read-file JWT + max_size | +| H-Cache | filePreload.ts:25-58 | SAFE | 内存 Map;prune 按目录 | +| H-Size | filePreload.ts:6-9,67-68 | SAFE | 300KB 预读 / 2MB 编辑上限 | + +**len(H)=17, Closure=17** + +## FINDING + +无 P0/P1 新 FINDING。 + +## L3 门控(同日记录) + +`bash deploy/pre_deploy_check.sh` → **6/7 PASS**,Gate 7 BLOCK(`HEAD~1` 与未提交改动 `.cursorrules`、`docker-compose.yml` 等未列入当日 audit 文件)。属工作区脏树预期;**非代码缺陷**。部署前需 commit + 审计清单对齐或放宽 Gate7 策略。 + +## DoD + +- [x] len(H) == Closure +- [x] 五文件全文 Read +- [x] local_verify 通过 + +## 验证 + +`bash scripts/local_verify.sh` — 全绿 diff --git a/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-2.md b/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-2.md new file mode 100644 index 00000000..fa1c4af3 --- /dev/null +++ b/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-2.md @@ -0,0 +1,66 @@ +# Phase 2 深审 — Wave P2-2(sync_v2 全文 + 路径/Shell 依赖) + +**日期**: 2026-06-04 +**范围**: `sync_v2.py` 1656 行全文 + 路径校验与 SSH 包装 +**标准**: `standards/line-walk-audit-standard-v2.md` 8 步 + +## 登记 + +| # | 文件 | 语言 | 行数 N | Read 范围 | +|---|------|------|--------|-----------| +| 1 | `server/api/sync_v2.py` | Python | 1656 | 1–1656 全文(分段 Read 至 EOF) | +| 2 | `server/utils/posix_paths.py` | Python | 149 | 1–149 全文 | +| 3 | `server/infrastructure/ssh/remote_shell.py` | Python | 63 | 1–63 全文 | +| 4 | `server/api/remote_path_validation.py` | Python | 33 | 1–33 全文 | +| 5 | `server/utils/unix_ls.py` | Python | 115 | 1–115 全文 | + +## 入口表(sync_v2 全 22 路由) + +| 路由 | 鉴权 | +|------|------| +| POST `/files`, `/preview`, `/file-ops`, `/file-clipboard`, `/browse` | JWT | +| POST `/browse-local`, `/local-file-ops`, `/local-file-preview` | JWT + upload 沙箱 | +| POST `/cancel`, `/reconcile-stale-logs`, `/validate-source-path`, `/diagnose` | JWT | +| POST `/file-diff`, `/upload`, `/upload-zip`, `/verify` | JWT | +| POST `/download`, `/read-file`, `/write-file`, `/chmod`, `/compress`, `/decompress` | JWT | + +## Closure 表 + +| H | 文件:行号 | 判定 | 理由 | +|---|-----------|------|------| +| H-Auth | sync_v2.py:74–1614 | SAFE | 22 路由均 `Depends(get_current_admin)` | +| H-Shell | sync_v2.py:156-168 | RISK | `rm -rf` 删目录;JWT+审计+前端 confirm | +| H-Shell | sync_v2.py:156,165,168,223-227 | SAFE | path shlex.quote | +| H-Path | sync_v2.py:149-165 | SAFE | normalize_remote_abs_path | +| H-Clipboard | sync_v2.py:207-208 | SAFE | assert_clipboard_transfer_safe | +| H-Sandbox | sync_v2.py:302,341,413,721 | SAFE | ensure_under_nexus_upload | +| H-Source | sync_v2.py:548,1096 | SAFE | FORBIDDEN prefixes + resolve | +| H-Audit | sync_v2.py:461-475 | SAFE | 失败 log warning+exc_info,非静默 pass | +| H-Upload | sync_v2.py:876-877,977-978 | SAFE | 100MB / 500MB 上限 | +| H-ZipSlip | sync_v2.py:1000-1005 | SAFE | assert_zip + is_path_under_prefix | +| H-Download | sync_v2.py:1244-1272 | SAFE | 100MB + filename sanitize | +| H-Read | sync_v2.py:1322-1326 | SAFE | max_size 来自 payload | +| H-Write | sync_v2.py:1394-1414 | SAFE | 5MB + etag 409 冲突 | +| H-Chmod | sync_v2.py:1507-1514 | SAFE | recursive policy + test -d | +| H-Verify | sync_v2.py:1145-1146 | SAFE | sha256sum 路径 shlex.quote | +| H-Path | posix_paths.py:46-59 | SAFE | 禁 `..` 与反斜杠 | +| H-Path | posix_paths.py:117-136 | SAFE | upload 前缀 + zip-slip | +| H-Sudo | remote_shell.py:35-53 | SAFE | bash -c + shlex.quote(command) | +| H-Clipboard | remote_path_validation.py:24-33 | SAFE | dest 非源子树 | +| H-Parse | unix_ls.py:70-71 | SAFE | 跳过 `.` `..` | + +**len(H)=20, Closure=20** + +## FINDING + +无 P0/P1 新 FINDING。 + +## DoD + +- [x] sync_v2 全文 Read(D1 仅 grep+分段,本波补全) +- [x] len(H) == Closure +- [x] local_verify 通过 + +## 验证 + +`bash scripts/local_verify.sh` — 全绿 diff --git a/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-3.md b/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-3.md new file mode 100644 index 00000000..55ad1474 --- /dev/null +++ b/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-3.md @@ -0,0 +1,61 @@ +# Phase 2 深审 — Wave P2-3(WebSocket / Dashboard 告警链) + +**日期**: 2026-06-04 +**范围**: 后端 WS 双通道 + 前端告警客户端 + Dashboard 刷新 +**标准**: `standards/line-walk-audit-standard-v2.md` 8 步 + +## 登记 + +| # | 文件 | 语言 | 行数 N | Read 范围 | +|---|------|------|--------|-----------| +| 1 | `server/api/websocket.py` | Python | 552 | 1–552 全文 | +| 2 | `frontend/src/composables/useWebSocket.ts` | TS | 178 | 1–178 全文 | +| 3 | `frontend/src/pages/DashboardPage.vue` | Vue | 415 | 1–415 全文 | +| 4 | `frontend/src/App.vue` | Vue | — | 247–260(WS 生命周期) | +| 5 | `server/api/settings.py` | Python | — | 759–828(alert-history GET) | + +## 入口表 + +| 通道 | 路径 | 鉴权 | +|------|------|------| +| WS | `/ws/alerts?token=` | `_verify_ws_token` | +| WS | `/ws/sync?token=` | 同上 | +| HTTP | GET `/api/alert-history/` | JWT | +| HTTP | GET `/api/servers/stats` | JWT(Dashboard) | + +## Closure 表 + +| H | 文件:行号 | 判定 | 理由 | +|---|-----------|------|------| +| H-WS-Auth | websocket.py:304-311,340-347 | SAFE | 无 token → 4001;无效 → 4401 | +| H-WS-Token | websocket.py:366-410 | SAFE | tv/updated_at 与 HTTP JWT 一致 | +| H-WS-ADR011 | websocket.py:9,296 | RISK | query JWT;单人运维已接受 | +| H-WS-Limit | websocket.py:49-52 | SAFE | MAX_CONNECTIONS=500 | +| H-WS-Heartbeat | websocket.py:139-165 | SAFE | 30s ping / 60s zombie | +| H-WS-Channel | websocket.py:246-249,531-551 | SAFE | alerts 与 sync 分 channel/manager | +| H-WS-Dedup | websocket.py:416-437 | SAFE | Telegram 5min 冷却;WS 仍实时 | +| H-WS-Recovery | websocket.py:512-513 | SAFE | recovery 清 cooldown | +| H-Frontend-Token | useWebSocket.ts:61 | RISK | URL query token;同 ADR-011 | +| H-Frontend-Auth | useWebSocket.ts:114-116 | SAFE | 4001/4401 → forceLogout | +| H-Frontend-XSS | useWebSocket.ts:99-102 | SAFE | snackbar 文本插值,无 v-html | +| H-Frontend-Singleton | useWebSocket.ts:17-18 | SAFE | 模块级单例;App 统一 connect | +| H-Dashboard-Refresh | DashboardPage.vue:393-397 | SAFE | WS alert → reload stats/alerts | +| H-Dashboard-API | DashboardPage.vue:302-337 | SAFE | http JWT 拉 stats/alert-history | +| H-App-Lifecycle | App.vue:257-259 | SAFE | login connect / logout disconnect | +| H-Alert-API | settings.py:768-828 | SAFE | alert-history JWT + 参数化 filter | + +**len(H)=16, Closure=16** + +## FINDING + +无 P0/P1 新 FINDING。 + +## DoD + +- [x] len(H) == Closure +- [x] websocket 全文 Read +- [x] 端到端链:Agent→broadcast_alert→Redis→WS→Dashboard refresh + +## 验证 + +`bash scripts/local_verify.sh` — 全绿 diff --git a/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-4.md b/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-4.md new file mode 100644 index 00000000..08fb9142 --- /dev/null +++ b/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-4.md @@ -0,0 +1,68 @@ +# Phase 2 深审 — Wave P2-4(servers.py 全文) + +**日期**: 2026-06-04 +**范围**: 服务器 CRUD/Agent/导入/批量 SSH 全文 + 凭据加密/文件能力探测 +**标准**: `standards/line-walk-audit-standard-v2.md` 8 步 + +## 登记 + +| # | 文件 | 语言 | 行数 N | Read 范围 | +|---|------|------|--------|-----------| +| 1 | `server/api/servers.py` | Python | 1742 | 1–1742 全文(分段 Read 至 EOF) | +| 2 | `server/infrastructure/database/crypto.py` | Python | 54 | 1–54 全文 | +| 3 | `server/infrastructure/ssh/files_capability.py` | Python | 63 | 1–63 全文 | +| 4 | `server/utils/files_elevation.py` | Python | 55 | 1–55 全文(P2-1 已读,本波交叉验证) | +| 5 | `server/infrastructure/ssh/remote_shell.py` | Python | 63 | 1–63 全文(P2-1 已读,setup-files-sudo 关联) | + +## 入口表(servers.py 26 路由) + +| 类别 | 路由数 | 鉴权 | +|------|--------|------| +| CRUD / 列表 / stats | GET/POST/PUT/DELETE | 全部 JWT | +| CSV import | POST `/import` | JWT + 1MB/500行 | +| 批量 Agent | POST `/batch/*` | JWT + Semaphore(5) | +| Agent 单台 | POST `/{id}/install|uninstall|upgrade-agent` | JWT | +| 敏感 reveal | POST `/{id}/agent-key`, `agent-install-cmd` | JWT + bcrypt re-auth | +| files | GET/POST `files-capability`, `setup-files-sudo` | JWT | +| health | POST `/check` | JWT | + +## Closure 表 + +| H | 文件:行号 | 判定 | 理由 | +|---|-----------|------|------| +| H-Auth | servers.py:26×路由 | SAFE | 26 端点均 `get_current_admin` | +| H-Mask | servers.py:1692-1698 | SAFE | password_set / agent key 前缀截断 | +| H-Encrypt | servers.py:1096-1099,559-562 | SAFE | Fernet encrypt 入库 | +| H-Allowlist | servers.py:1163-1187 | SAFE | UPDATE 字段白名单;agent_api_key 排除 | +| H-Reauth | servers.py:1256-1260,1479-1483 | SAFE | agent-key/install-cmd 需当前密码 | +| H-Reveal | servers.py:1492-1502 | SAFE | install-cmd 不用全局 API_KEY | +| H-AgentKey | servers.py:1282-1288 | SAFE | 生成后 display_once 响应 | +| H-Fallback | servers.py:626,1321 | RISK | batch/single install 可回退 settings.API_KEY | +| H-Shell | servers.py:631-633,1330-1332 | SAFE | install shlex.quote url/key/port | +| H-Sudo | servers.py:36-60 | RISK | 临时 sudoers 白名单;运维必要 | +| H-SudoPwd | servers.py:1025-1027 | SAFE | sudo -S 密码走 stdin 非 argv | +| H-CSV | servers.py:376-388,445-446 | SAFE | formula injection 防御 + 1MB 上限 | +| H-CSV | servers.py:499-501 | SAFE | MAX_IMPORT_ROWS=500 | +| H-Batch | servers.py:615,686,772,850 | SAFE | Semaphore(5) 并发上限 | +| H-Redis | servers.py:179-201,873-877 | SAFE | 心跳 overlay;失败 log warning | +| H-Audit | servers.py: CUD/batch | SAFE | 写操作 AuditLog | +| H-Crypto | crypto.py:20-26 | SAFE | 无 ENCRYPTION_KEY 则 RuntimeError | +| H-Crypto | crypto.py:52-54 | SAFE | decrypt 失败 raise,非静默 | +| H-Capability | files_capability.py:31-39 | SAFE | 只读 probe,无密码回传 | +| H-Elevation | files_elevation.py:48-55 | SAFE | apply_files_elevation_payload | + +**len(H)=20, Closure=20** + +## FINDING + +无 P0/P1 新 FINDING。 + +## DoD + +- [x] servers.py 1742 行全文 Read(补 D3 分段) +- [x] len(H) == Closure +- [x] local_verify 通过 + +## 验证 + +`bash scripts/local_verify.sh` — 全绿 diff --git a/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-5.md b/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-5.md new file mode 100644 index 00000000..b24cc887 --- /dev/null +++ b/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-5.md @@ -0,0 +1,61 @@ +# Phase 2 深审 — Wave P2-5(webssh.py 全文) + +**日期**: 2026-06-04 +**范围**: WebSSH WebSocket 端点 + 令牌签发链 + 前端连接 +**标准**: `standards/line-walk-audit-standard-v2.md` 8 步 + +## 登记 + +| # | 文件 | 语言 | 行数 N | Read 范围 | +|---|------|------|--------|-----------| +| 1 | `server/api/webssh.py` | Python | 401 | 1–401 全文 | +| 2 | `server/api/auth.py` | Python | 374 | 330–349(webssh-token 端点) | +| 3 | `server/application/services/auth_service.py` | Python | 684 | 412–471(create_webssh_token) | +| 4 | `server/api/dependencies.py` | Python | — | 167–180(check_dangerous_command) | +| 5 | `frontend/src/composables/terminal/useTerminalSessions.ts` | TS | 749 | 277–311(令牌 + WS URL) | + +## 入口表 + +| 方法 | 路径 | 鉴权 | +|------|------|------| +| WS | `/ws/terminal/{server_id}?token=` | purpose=webssh JWT | +| POST | `/api/auth/webssh-token` | JWT + server 存在 | + +## Closure 表 + +| H | 文件:行号 | 判定 | 理由 | +|---|-----------|------|------| +| H-Purpose | webssh.py:48-49 | SAFE | 拒绝非 webssh purpose | +| H-Bind | webssh.py:106-109 | SAFE | token server_id 须匹配路径 | +| H-TV | webssh.py:66-76 | SAFE | tv 校验;legacy updated_at +5s grace | +| H-Close | webssh.py:97-104 | SAFE | 缺 token→4001;无效→4401 | +| H-Issue | auth.py:334-346 | SAFE | 签发前 JWT + server/domain 校验 | +| H-Scope | auth_service.py:455-470 | SAFE | JWT 含 server_id + purpose + tv | +| H-Expire | auth_service.py:467 | SAFE | 15 分钟短期令牌 | +| H-WSQuery | webssh.py:88, useTerminalSessions:293 | RISK | ADR-011 query token(已接受) | +| H-Cred | webssh.py:126-135 | SAFE | key/password 配置检查 | +| H-Audit | webssh.py:137-146,353-366 | SAFE | connect/disconnect 审计 | +| H-Session | webssh.py:148-157,369-376 | SAFE | SshSession 创建/关闭 | +| H-DB | webssh.py:111-158 | SAFE | 长连接前关闭 DB session | +| H-Pool | webssh.py:167,337 | SAFE | acquire/release + stale retry | +| H-CmdLog | webssh.py:277-290,379-398 | SAFE | Enter 触发;2000 字符截断 | +| H-Danger | dependencies.py:167-180 | RISK | 危险命令仅 WARN 不阻断(设计) | +| H-Resize | webssh.py:293-296 | SAFE | cols/rows 边界默认 80×24 | +| H-Error | webssh.py:171,204 | SAFE | SSH 失败不向客户端泄露栈 | + +**len(H)=17, Closure=17** + +## FINDING + +无 P0/P1 新 FINDING。 + +## DoD + +- [x] webssh.py 401 行全文 Read +- [x] 令牌签发链交叉验证 +- [x] len(H) == Closure +- [x] local_verify 通过 + +## 验证 + +`bash scripts/local_verify.sh` — 全绿 diff --git a/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-6.md b/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-6.md new file mode 100644 index 00000000..6ce4353c --- /dev/null +++ b/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-6.md @@ -0,0 +1,61 @@ +# Phase 2 深审 — Wave P2-6(sync_engine_v2.py 全文) + +**日期**: 2026-06-04 +**范围**: 推送引擎服务层 — rsync/并发/预览/暂存清理 +**标准**: `standards/line-walk-audit-standard-v2.md` 8 步 + +## 登记 + +| # | 文件 | 语言 | 行数 N | Read 范围 | +|---|------|------|--------|-----------| +| 1 | `server/application/services/sync_engine_v2.py` | Python | 552 | 1–552 全文 | +| 2 | `server/utils/posix_paths.py` | Python | — | resolve_nexus_push_source_path(P2-2 已审,交叉引用) | +| 3 | `server/utils/sync_error_message.py` | Python | — | translate_sync_error_message(错误脱敏) | +| 4 | `server/infrastructure/database/crypto.py` | Python | 54 | decrypt_value(P2-4 已审) | +| 5 | `server/api/sync_v2.py` | Python | 1656 | 调用方(P2-2 已审) | + +## 入口表(服务层,经 sync_v2 API 调用) + +| 方法 | 调用方 | 说明 | +|------|--------|------| +| `sync_files` | POST push/sync | 多机 rsync + WS 进度 | +| `preview_sync` | POST preview | dry-run 单机 | +| `_rsync_push` | 内部 | subprocess rsync | + +## Closure 表 + +| H | 文件:行号 | 判定 | 理由 | +|---|-----------|------|------| +| H-Source | sync_engine_v2.py:72-76,327-330 | SAFE | `_nexus_source_path` 拒绝系统路径 | +| H-Conc | sync_engine_v2.py:38,79-80 | SAFE | MAX_CONCURRENT=10 + min 入参 | +| H-DBLock | sync_engine_v2.py:89,149,190 | SAFE | `_db_lock` 串行写 sync_log/retry | +| H-Cancel | sync_engine_v2.py:96-136 | SAFE | Redis cancel flag;失败则继续 push | +| H-Retry | sync_engine_v2.py:193-224 | SAFE | 失败自动 pending retry;去重 | +| H-Subproc | sync_engine_v2.py:468-473 | SAFE | create_subprocess_exec 参数化 | +| H-Shlex | sync_engine_v2.py:456-458 | SAFE | key path shlex.quote | +| H-KeyTmp | sync_engine_v2.py:449-455,491-495 | SAFE | 0600 临时 key + finally unlink | +| H-Pass | sync_engine_v2.py:440-446 | RISK | sshpass -e;/proc 可见(内网运维可接受) | +| H-Remote | sync_engine_v2.py:414 | SAFE | normalize_remote_abs_path | +| H-Timeout | sync_engine_v2.py:476-482 | SAFE | RSYNC_TIMEOUT+30 kill | +| H-Output | sync_engine_v2.py:486-487 | SAFE | stdout/stderr 截断 10KB | +| H-Staging | sync_engine_v2.py:498-516 | SAFE | 仅 manual 全成功才 rmtree staging | +| H-Preview | sync_engine_v2.py:364-365 | SAFE | verbose 文件列表上限 200 | +| H-Audit | sync_engine_v2.py:267-271,380-391 | SAFE | sync_files 审计;失败 log error | +| H-TG | sync_engine_v2.py:274-291 | SAFE | Telegram 失败不阻断 | +| H-Gather | sync_engine_v2.py:254-265 | SAFE | gather 异常计入 failed | + +**len(H)=17, Closure=17** + +## FINDING + +无 P0/P1 新 FINDING。 + +## DoD + +- [x] sync_engine_v2.py 552 行全文 Read +- [x] len(H) == Closure +- [x] local_verify 通过 + +## 验证 + +`bash scripts/local_verify.sh` — 全绿 diff --git a/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-7.md b/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-7.md new file mode 100644 index 00000000..003602ec --- /dev/null +++ b/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-7.md @@ -0,0 +1,44 @@ +# Phase 2 深审 — Wave P2-7(前端页 5/14) + +**日期**: 2026-06-04 +**范围**: Dashboard / Servers / Alerts / Audit / Settings 页面全文 +**标准**: `standards/line-walk-audit-standard-v2.md` 8 步 + +## 登记 + +| # | 文件 | 行数 N | Read 范围 | +|---|------|--------|-----------| +| 1 | `frontend/src/pages/DashboardPage.vue` | 415 | 1–415 全文 | +| 2 | `frontend/src/pages/ServersPage.vue` | 477 | 1–477 全文 | +| 3 | `frontend/src/pages/AlertsPage.vue` | 184 | 1–184 全文 | +| 4 | `frontend/src/pages/AuditPage.vue` | 154 | 1–154 全文 | +| 5 | `frontend/src/pages/SettingsPage.vue` | 497 | 1–497 全文 | + +## Closure 表 + +| H | 文件:行号 | 判定 | 理由 | +|---|-----------|------|------| +| H-JWT | 各页 http.* | SAFE | 经 `@/api` JWT 客户端 | +| H-WS | DashboardPage:393-397 | RISK | useWebSocket query token(已接受) | +| H-Nav | DashboardPage:383-387 | SAFE | server_id 数字 query 路由 | +| H-Err | DashboardPage:268-345 | SAFE | load 失败 snackbar | +| H-CSV | ServersPage:391-435 | SAFE | import FormData;export 无密码列 | +| H-Batch | ServersPage:325-388 | SAFE | batch 端点 JWT;confirm 删除 | +| H-Form | ServersPage:162-176 | SAFE | 凭据经 ServerFormDialog composable | +| H-Filter | AlertsPage:150-162 | SAFE | 只读分页 API | +| H-Audit | AuditPage:126-141 | SAFE | 只读;normalizeAuditList | +| H-TG | SettingsPage:272-275 | SAFE | GET 不返 token 明文;value_set | +| H-Reveal | SettingsPage:434-460 | SAFE | API Key/TG reveal 需 current_password | +| H-Pw | SettingsPage:339-359 | SAFE | 改密 POST;表单 type=password | +| H-TOTP | SettingsPage:362-404 | SAFE | setup/enable/disable 走 auth API | + +**len(H)=13, Closure=13** + +## FINDING + +无 P0/P1 新 FINDING。 + +## DoD + +- [x] 5 页全文 Read +- [x] local_verify 通过 diff --git a/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-8.md b/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-8.md new file mode 100644 index 00000000..174f1416 --- /dev/null +++ b/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-8.md @@ -0,0 +1,37 @@ +# Phase 2 深审 — Wave P2-8(前端页 5/14) + +**日期**: 2026-06-04 +**范围**: Credentials / Schedules / Retries / Commands / Scripts +**标准**: 8 步逐行 + +## 登记 + +| # | 文件 | 行数 N | Read 范围 | +|---|------|--------|-----------| +| 1 | `frontend/src/pages/CredentialsPage.vue` | 400 | 1–400 全文 | +| 2 | `frontend/src/pages/SchedulesPage.vue` | 295 | 1–295 全文 | +| 3 | `frontend/src/pages/RetriesPage.vue` | 191 | 1–191 全文 | +| 4 | `frontend/src/pages/CommandsPage.vue` | 168 | 1–168 全文 | +| 5 | `frontend/src/pages/ScriptsPage.vue` | 535 | 1–535 全文 | + +## Closure 表 + +| H | 文件:行号 | 判定 | 理由 | +|---|-----------|------|------| +| H-CredList | CredentialsPage:253-255 | SAFE | GET 列表无密码明文 | +| H-CredPost | CredentialsPage:323-327 | SAFE | encrypted_pw 仅 POST/PUT | +| H-CredEdit | CredentialsPage:306,340,357 | SAFE | 编辑留空不修改 | +| H-SSH | CredentialsPage:346-347 | SAFE | 私钥仅提交时传输 | +| H-Cron | SchedulesPage:177 | SAFE | cron 正则校验 | +| H-Sched | SchedulesPage:173-180 | SAFE | 路径/服务器必填 | +| H-Retry | RetriesPage:146 | SAFE | retry POST JWT | +| H-CmdView | CommandsPage:139-153 | SAFE | 只读 command-logs/sessions | +| H-ScriptExec | ScriptsPage:415-419,463-467 | SAFE | exec 走后端 JWT | +| H-Quick | ScriptsPage:459-467 | RISK | 临时命令;后端 check_dangerous | +| H-Poll | ScriptsPage:527-528 | SAFE | onUnmounted stopPolling | + +**len(H)=11, Closure=11** + +## FINDING + +无 P0/P1 新 FINDING。 diff --git a/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-9.md b/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-9.md new file mode 100644 index 00000000..5c38081b --- /dev/null +++ b/docs/archive/audits/2026-06-04-phase2/audit-phase2-2026-06-04-waveP2-9.md @@ -0,0 +1,36 @@ +# Phase 2 深审 — Wave P2-9(前端页 4/14 + 编排层) + +**日期**: 2026-06-04 +**范围**: Files / Push / Terminal / Login 页面壳 + 关联 composables(交叉 D4–D6) + +## 登记 + +| # | 文件 | 行数 N | Read 范围 | +|---|------|--------|-----------| +| 1 | `frontend/src/pages/FilesPage.vue` | 38 | 1–38 全文 | +| 2 | `frontend/src/pages/PushPage.vue` | 106 | 1–106 全文 | +| 3 | `frontend/src/pages/TerminalPage.vue` | 408 | 交叉 D5 useTerminalSessions | +| 4 | `frontend/src/pages/LoginPage.vue` | 244 | 交叉 D6 全文 | +| 5 | `frontend/src/composables/files/useFilesPage.ts` | — | D5 已审 | + +## Closure 表 + +| H | 文件:行号 | 判定 | 理由 | +|---|-----------|------|------| +| H-FilesShell | FilesPage.vue:33-37 | SAFE | 逻辑在 useFilesPage(D5) | +| H-PushShell | PushPage.vue:105 | SAFE | usePushPage(D4) | +| H-TermShell | TerminalPage.vue | SAFE | useTerminalSessions(D5) | +| H-Login | LoginPage.vue | SAFE | auth.login 内存 token(D6) | +| H-NoStore | 四页 | SAFE | 无 localStorage 存密码 | + +**len(H)=5, Closure=5** + +## 交叉引用 + +- D4:`usePushForm/Progress/Preview` — waveD4 +- D5:`useFilesActions/TerminalSessions` — waveD5 +- D6:`LoginPage.vue` — waveD6 + +## FINDING + +无 P0/P1 新 FINDING。 diff --git a/docs/archive/handoff/AI-HANDOFF-2026-05-23.md b/docs/archive/handoff/AI-HANDOFF-2026-05-23.md new file mode 100644 index 00000000..8fb7c1b9 --- /dev/null +++ b/docs/archive/handoff/AI-HANDOFF-2026-05-23.md @@ -0,0 +1,337 @@ +# Nexus 6.0 — AI 接续工作交接(SSOT) + +> **生成日期**: 2026-05-23 +> **用途**: 切换 AI / 新会话时**唯一接续入口**;下文汇总本会话决策、代码状态、待办与验证清单。 +> **工作区(历史 Windows 路径,勿再使用)**: `C:\Users\uzuma\Desktop\svn\Nexus\...` +> **Linux SSOT**: `docs/project/linux-dev-paths.md` · `docs/project/AI-HANDOFF-2026-06-03.md` · `~/Nexus` +> **部署状态**: 以各 handoff 日期为准;生产见 `CLAUDE.md` 部署节 + +--- + +## 0. 给下一任 AI 的复制提示词 + +``` +【严谨性】与 Nexus 团队同等严谨,不得降低标准。全量标准索引: + docs/project/standards-transfer-package.md + +【必读 · 按序】 +1. docs/project/AI-HANDOFF-2026-05-23.md +1b. docs/project/2026-06-01-master-backlog-plan.md(文件管理/POSIX/部署/审计 长计划任务) +2. docs/project/nexus-full-site-features.md +3. docs/project/development-acceptance-standard.md +4. docs/project/standards-transfer-package.md + +【规范 · 全文内化(开发 + 审计同等强制)】 +- .cursor/rules/perfect-implementation.mdc +- .cursor/rules/nexus-tech-stack.mdc + nexus-python-backend.mdc + nexus-frontend.mdc + nexus-security.mdc +- .cursor/rules/audit-line-review.mdc(走读/Phase2 触发) +- standards/system-development-standard.md +- standards/line-walk-audit-standard-v2.md +- standards/audit-core-principles.md + +【逐行审查】8步/文件、全文Read至EOF、Closure全表(含SAFE)、FINDING必须文件:行号、单回复≤5文件; + 禁止grep/Top15冒充审计;P0/P1当场修+changelog+矩阵。提示词见 v2 §十四.1。 + +【验收】完成前:bash scripts/local_verify.sh 或 pytest + local_integration_smoke.sh + changelog; + 上线前:production-verification-checklist.md;宣称完成前读 verification-before-completion SKILL。 + +项目未部署。端口:子机出站443心跳;中心→子机SSH22;8601仅本机。 +P1待办:WebSSH断线重连、CSV批量导入。首次部署E2E见本文§6。 +已否决见§7。风险接受(单人运维·不排期)见§7b。 +``` + +--- + +## 1. 项目一句话 + +Nexus 6.0:2000+ 台服务器运维平台;FastAPI 四层架构 + Redis 实时心跳 + WebSocket/Telegram 告警 + asyncssh 推送/WebSSH/脚本;前端 Tailwind v4 + Alpine.js 静态页。 + +--- + +## 2. 端口与通道模型(2026-05-23 最终决策) + +| 方向 | 端口 | 用途 | 是否必须 | +|------|------|------|----------| +| 子机 → 中心 | **443 出站** | Agent 心跳、长任务 `curl` 回调 | ✅ 必须 | +| 中心 → 子机 | **22 入站** | rsync 推送、文件浏览/操作、**脚本执行**、**手动健康检查**、Agent 安装/升级 | ✅ 必须 | +| 子机本机 | **127.0.0.1:8601** | Agent uvicorn(仅本机) | 可选进程;**不要求公网 8601** | + +**已否定/不推荐** + +- 为脚本/健康检查强制公网开放 8601 +- 用宝塔「Python 项目管理器」托管 Agent(与面板生命周期耦合) +- 用 btpanel-skills 安装 Agent(skills 仅适合读面板监控 API,见 [btpanel-skills](https://github.com/aaPanel/btpanel-skills)) + +**Go 单二进制 Agent**:长期可选;当前子机均有 Python 3.12,优先 venv 方案。 + +--- + +## 3. 本会话及近期已完成(代码) + +### 3.1 架构 / 运维向 + +| 项 | 说明 | 关键文件 | +|----|------|----------| +| 脚本执行改 SSH | 不再依赖中心访问子机 8601;结果含 `channel: ssh` | `server/application/services/script_service.py` | +| 手动健康检查改 SSH | `check_all_servers` 经 SSH 探测指标 | `server_service.py`, `server/infrastructure/ssh/remote_probe.py` | +| Agent 安装脚本 | python3.12+ venv、`127.0.0.1` 监听、不自动放行公网 8601 | `web/agent/install.sh` | +| 安装说明文案 | 心跳 443 / 脚本 SSH / 无需公网 8601 | `web/app/servers.html` | +| Changelog | | `docs/changelog/2026-05-23-ssh-exec-no-8601.md` | + +### 3.2 功能向(会话前后期,代码已在仓库) + +| 项 | 说明 | +|----|------| +| 推送 dry-run 预览 | `push.html` + `SyncEngineV2.preview_sync()`,方案 D 智能提示 | +| 推送日志视图 | `commands.html` 推送日志 Tab | +| 脚本快捷执行 | `scripts.html` 直接执行面板 | +| 定时脚本执行 | `schedule_runner.py` + `schedules.html`,`schedule_type=script` | +| 审计时间/用户过滤 | `audit.html` + `settings.py` API | +| 告警中心页 | `alerts.html` + `/api/alert-history` + `AlertLog` 表 | +| Telegram 测试 / Chat ID | `settings.html` + settings API | +| 告警分项 Telegram 开关 | `settings.html` + `NOTIFY_*` 配置 | +| 登录 IP 白名单 + 订阅解析 | `subscription_parser.py` + `ip_allowlist_refresh.py`(2h) | +| Agent 远程安装 / 安装命令 | `servers.html` + `install-agent` API | +| Agent 升级(SSH) | `POST /servers/{id}/upgrade-agent`(**未**随新 venv install.sh 对齐,见待办) | +| Agent IP 白名单 | `agent.py` + `install.sh` 写入 `allowed_ips` | +| 时钟漂移检测 | 心跳 `agent_time` + UI 徽章 + Telegram | +| TOTP 重绑 UX | `settings.html` | +| 远程文件删/改名/建目录 | `sync_v2.py` | +| Phase 2 审计 63 项 | 矩阵 `docs/reports/audit-phase-2-findings-matrix.md` | +| 本地联调脚本/venv | `scripts/start-dev.sh`, `scripts/local_verify.sh`, `docs/project/local-integration-test.md` | +| 标准文档目录 | `standards/` + `docs/project/*-standard*.md` | + +### 3.3 代码完成度(主观) + +- **核心业务代码**: ~95%~98% +- **首次部署 E2E**: 0%(未部署) +- **用户要求的增强功能**: 仍有 **5~7 项未写代码**(见 §4) + +--- + +## 4. 待实现功能(下一任 AI 工作 backlog) + +按用户确认优先级排序。 + +### P1 — 建议先做 + +| ID | 功能 | 说明 | 建议触及文件 | +|----|------|------|--------------| +| F-01 | **WebSSH 断线重连** | 方案 C:断线自动重试 1 次 + 手动重连按钮 | `web/app/terminal.html`, `server/api/webssh.py` | +| F-02 | **服务器批量导入 CSV** | 上传 CSV → 校验 → 批量 `POST /api/servers/` | 新 API + `servers.html` | +| F-03 | **Agent 升级与 install.sh 对齐** | `upgrade-agent` 应使用 `/opt/nexus-agent/.venv` + 127.0.0.1 | `server/api/servers.py` | + +### P2 — 迭代 + +| ID | 功能 | 说明 | +|----|------|------| +| F-04 | **仪表盘历史趋势图** | CPU/内存时序;需 Redis/MySQL 历史或新聚合 | +| F-05 | 远程文件上传 UI | 可用 WebSSH/scp/推送代替;非阻塞上线 | +| F-06 | 远程文件内容预览 | 可用 WebSSH cat 代替 | + +### 可选 / 讨论项(未承诺实现) + +| ID | 功能 | 说明 | +|----|------|------| +| F-07 | 宝塔面板 API 监控增强 | 参考 aaPanel/btpanel-skills,补站点/服务状态(**不**替代 Agent) | +| F-08 | Agent ↔ 中心 mTLS | 安全增强,工作量大 | +| F-09 | Docker Compose 部署 | 用户称暂不需要 | +| F-10 | Go 单二进制 Agent | 2000+ 台 Python 3.12 齐全时可暂缓 | + +### SSH 回退(已采用主路径,非待办) + +用户曾问「SSH 回退」:已实现为**默认 SSH**,非「Agent 失败再回退」。无需再做了 Agent 优先逻辑,除非用户改口。 + +--- + +## 5. 标准转接(与团队同等严谨 · 下一任 AI 必读) + +| 文档 | 路径 | +|------|------| +| **标准转接包(全量索引)** | [`standards-transfer-package.md`](standards-transfer-package.md) | +| 开发验收 L0~L5 | [`development-acceptance-standard.md`](development-acceptance-standard.md) | +| 标准目录 | [`standards/README.md`](../../standards/README.md) | + +**逐行审查**:与开发规范**同等强制** — 全文见 `standards/line-walk-audit-standard-v2.md` + `standards/audit-core-principles.md`;Cursor 触发器 `audit-line-review.mdc`。 + +**开发规范**:`standards/system-development-standard.md` 全文 + 档 B 全部 `.cursor/rules/nexus-*.mdc`。 + +--- + +## 5b. 开发验收标准(L0~L5 速查) + +**SSOT**:[`development-acceptance-standard.md`](development-acceptance-standard.md) + +| 层级 | 何时用 | 关键文档 | +|------|--------|----------| +| L0 | 始终 | `perfect-implementation.mdc`、`standards/system-development-standard.md` | +| L1 | 新功能/大改 | `docs/design/specs/`、`docs/design/plans/` 内验收标准 | +| L2 | 每次改代码合并前 | `bash scripts/local_verify.sh` 或 `pytest` + `local_integration_smoke.sh` | +| L3 | 安全路径改动 | `standards/line-walk-audit-standard-v2.md`、`audit-core-principles.md` | +| L4 | 模块 E2E | `script-execution.md` §11、`alert-push-policy.md`、T1~T5 | +| L5 | 首次部署/上线 | `production-verification-checklist.md`(逐项勾选) | + +**禁止**:未跑 L2 命令就声称「已完成」;未勾 L5 就声称「可上线」。 + +--- + +## 6. 待验证(非「未实现」,但上线前必做) + +**项目未部署时不要写「请重启生产」**;以下在 **first deploy** 后执行。 + +### 6.1 基础(`CLAUDE.md` / `AGENTS.md`) + +- [ ] T1 心跳 Redis 写入 +- [ ] T2 WebSocket +- [ ] T3 三层守护(杀 Python → Supervisor 拉起) +- [ ] T4 Telegram +- [ ] T5 install.html 五步 + +### 6.2 脚本平台(`docs/project/script-execution.md` §11) + +- [ ] T-SE1~T-SE8(分批、长任务回调、停止、Redis 刷盘等) +- **注意**: 文档仍写 Agent `:8601` 执行,**实际已改 SSH** — 验证时按 SSH 测,并更新文档。 + +### 6.3 首次生产清单 + +完整表:`docs/project/production-verification-checklist.md` + +### 6.4 本地代码向(无密钥核对) + +`docs/project/local-integration-test.md` + `bash scripts/local_integration_smoke.sh` + +### 6.5 建议「最小首次部署 10 条」 + +1. install 向导生成 `.env` + 管理员 +2. 登录 + TOTP +3. 添加 1 台子机(SSH 凭据) +4. 子机安装 Agent(新 `install.sh`),确认 **无需公网 8601** 仍有心跳 +5. 仪表盘看到 Redis 在线/指标 +6. SSH 推送 1 个目录(dry-run + 正式) +7. SSH 执行短脚本(`channel: ssh`) +8. WebSSH 连接 + 命令审计 +9. Telegram 测试发送 +10. `alerts.html` 有告警历史记录 + +--- + +## 7. 已否决(永久不做,除非用户书面改口) + +| 功能 | 原因 | +|------|------| +| 文件编辑器(Monaco/ACE) | 2026-05-23 用户明确抛弃;WebSSH + vim | +| RBAC / 多管理员 | 鉴权即信任 | +| 审计日志导出 CSV/JSON | 用户不需要 | +| 移动端适配 | 用户否决 | +| 会话录像 asciicast | 不需要 | +| Redis Sentinel/Cluster | 用户不需要 | +| Docker 部署(当前阶段) | 用户暂不需要 | +| 推送结果邮件报告 | 不需要 | + +记录:`docs/Nexus-功能说明书.html` §15(部分条目已过期,见 §8) + +--- + +## 7b. 风险接受(单人运维 · 不排期,2026-06-04) + +> **SSOT**: `docs/project/risk-acceptance-single-operator.md` +> Operator 确认:后台仅 **一名** 操作员自用,管理员身份完全可信。 + +| 项 | 决定 | 说明 | +|----|------|------| +| WebSocket JWT query | **维持 ADR-011** | 不排首帧鉴权 / WS ticket;batch3 已统一 4401 | +| Agent global API_KEY 回退 | **保留 legacy** | 不硬禁用;batch3 已禁 unknown server_id + fail-closed | + +**≠ 已否决**:若新增管理员、日志外发第三方、等保审计、global key 泄露或 Agent 全量 per-server 迁移,须重评并见 risk-acceptance 文档「重评触发条件」。 + +Code Review 三批 changelog:`docs/changelog/2026-06-04-code-review-*.md` + +--- + +## 8. 文档债务(下一任 AI 应同步) + +| 文档 | 问题 | 动作 | +|------|------|------| +| `docs/Nexus-功能说明书.html` | §15 仍写审计过滤/定时脚本「未实现」 | 更新为已实现 | +| `docs/project/script-execution.md` | 仍写 Agent HTTP 执行、:8800/:8601 | 改为 SSH 22 + 443 心跳 | +| `docs/design/specs/2026-05-19-sprint-planning.md` | WebSSH/ install 仍标待做 | 标为已完成或归档 | +| `PROJECT_HANDOVER.md` | 日期与端口模型旧 | 顶部链到本文 | +| `CLAUDE.md` / `AGENTS.md` | 待测试列表仍有效 | 部署后勾选 | + +**新变更务必**: `docs/changelog/YYYY-MM-DD-*.md`(`perfect-implementation.mdc`) + +--- + +## 9. 环境与约束(用户确认) + +- **子机**: 宝塔面板;Python **3.7.16**(面板 pyenv)+ 系统 **3.12.3** +- **Agent 安装**: 固定 **3.12 venv**,隔离于宝塔 pyenv 3.7 +- **防火墙**: 多数子机**未开公网 8601**;推送/脚本依赖 **22**;心跳依赖 **出站 443** +- **本地联调**: Docker mysql+redis + `bash scripts/local_verify.sh` +- **仓库**: 唯一 Nexus 仓库;worktree 开发 + +--- + +## 10. 关键路径索引 + +| 类别 | 路径 | +|------|------| +| 接续 SSOT | **本文** | +| **全站功能清单** | `docs/project/nexus-full-site-features.md` | +| **标准转接包** | `docs/project/standards-transfer-package.md` | +| **开发验收标准** | `docs/project/development-acceptance-standard.md` | +| **系统开发标准** | `standards/system-development-standard.md` | +| **逐行审计 v2** | `standards/line-walk-audit-standard-v2.md` | +| **审计原则** | `standards/audit-core-principles.md` | +| **功能说明书 HTML** | `docs/Nexus-功能说明书.html` | +| 项目记忆 | `CLAUDE.md`, `AGENTS.md` | +| 进度 | `docs/project/status.md`, `docs/project/roadmap.md` | +| 部署 | `docs/project/deploy.md` | +| 审计矩阵 | `docs/reports/audit-phase-2-findings-matrix.md` | +| 脚本 SSOT | `docs/project/script-execution.md` | +| 标准 | `standards/README.md`, `docs/project/line-walk-audit-standard-v2.md` | +| Agent | `web/agent/agent.py`, `web/agent/install.sh` | +| 脚本 SSH | `server/application/services/script_service.py` | +| 健康 SSH | `server/infrastructure/ssh/remote_probe.py` | +| 告警历史 | `web/app/alerts.html`, `server/api/settings.py`(alert_history_router) | +| Changelog 示例 | `docs/changelog/2026-05-23-ssh-exec-no-8601.md` | +| 风险接受(单人运维) | `docs/project/risk-acceptance-single-operator.md` | +| Code Review 2026-06-04 | `docs/changelog/2026-06-04-code-review-p0-p1-fixes.md` 等三批 | + +--- + +## 11. 架构分层(禁止违反) + +``` +web/app/*.html → server/api/*.py → application/services → infrastructure/* → domain/models +``` + +- API 层:JWT + Pydantic;不写 SQL +- 无 `.env` → 安装模式(仅 `/api/install/` + 静态) +- 不可从 DB 覆盖:`SECRET_KEY`, `API_KEY`, `ENCRYPTION_KEY`, `DATABASE_URL` + +--- + +## 12. Git / 提交说明 + +- 用户**未要求 commit** 时勿主动提交 +- 当前 worktree 含大量未跟踪 `.cursor/`、修改的 `server/**`、`web/**`、`docs/**` +- 合并前建议:按逻辑拆分 commit(SSH 通道 / 功能增强 / 文档) + +--- + +## 13. 会话决策时间线(摘要) + +1. WSL 全链路联调 → Redis pool、SQLAlchemy aiomysql patch、venv +2. Phase 2/3/4 逐行审计 → 63+ 项修复 +3. 标准文档 → `standards/` +4. 功能 gap 讨论 → dry-run、告警中心、白名单、定时脚本等 **已实现** +5. 文件编辑器 → **否决** +6. Agent 8601 vs SSH → **脚本+健康检查走 22;心跳走 443;弱化 8601** +7. 宝塔 Python 管理 → **不用**;venv + 3.12 +8. 项目未部署 → **无需重启** +9. 2026-06-04 Code Review 三批本地验证;单人运维风险接受 → `risk-acceptance-single-operator.md` §7b + +--- + +**文档维护**: 下一任 AI 完成待办或验证后,更新本文 §4 §5 §7 的勾选状态,并写 changelog。 diff --git a/docs/archive/handoff/AI-HANDOFF-2026-06-01-files.md b/docs/archive/handoff/AI-HANDOFF-2026-06-01-files.md new file mode 100644 index 00000000..7892b61f --- /dev/null +++ b/docs/archive/handoff/AI-HANDOFF-2026-06-01-files.md @@ -0,0 +1,69 @@ +# 文件管理(#/files)— AI 接续备忘 + +> **日期**: 2026-06-01 +> **用户确认**: 「好 你自己记住」— 本地改动**尚未 git commit**;生产已用 SCP/tar 部署,与仓库可能不一致。 + +--- + +## 1. 生产状态(api.synaglobal.vip) + +| 项 | 状态 | +|----|------| +| 前端 | `web/app/` 已 tar+scp,prune 后 restart nexus | +| 后端 | `sync_v2.py`、`schemas.py`、`remote_path_validation.py` 曾 SCP 到服务器 | +| 健康 | `/health` → `ok` | +| 验证 | 用户需 Ctrl+Shift+R 强刷 `#/files` | + +**下一任部署优先**:`git push` + 服务器 `git pull` + `supervisorctl restart nexus`,避免仅 SCP 漂移。 + +--- + +## 2. 本会话已完成功能 + +| 模块 | 说明 | +|------|------| +| browse 契约 | `items` + `fileBrowse.ts` | +| 页面迭代 | 下载、预览、路由 query、拖拽上传、symlink | +| 300KB 预加载 | `filePreload.ts` | +| 编辑器 | `FileEditorWorkbench.vue` 分屏+多标签+状态栏(非默认全屏) | +| v2 补齐 | 目录树、新建文件、压缩/解压、排序、扩展名筛选、快捷键 | +| 剪贴板 | `POST /sync/file-clipboard` + Ctrl+C/X/V | + +--- + +## 3. 待入库(git status 快照 2026-06-01) + +**修改**:`FilesPage.vue`、`sync_v2.py`、`schemas.py`、`MonacoEditor.vue`、`api/index.ts`、`types/api.ts` 等 + +**新增**:`FileEditorWorkbench.vue`、`FileDirectoryTree.vue`、`useFilesClipboard.ts`、`useFilesHotkeys.ts`、`filePreload.ts`、`fileSort.ts`、`remote_path_validation.py`、`tests/test_remote_path_validation.py`、多篇 `docs/changelog/2026-06-01-files-*.md` 与设计文档 + +**用户未要求 commit 时**:不要擅自 `git commit`;用户说「好」= 同意记住此状态,下次可代其提交。 + +--- + +## 4. 关键 API + +- `POST /api/sync/browse` +- `POST /api/sync/read-file`(`max_size` 建议 307200) +- `POST /api/sync/write-file` +- `POST /api/sync/file-clipboard`(`mode`: `copy` \| `move`) +- `POST /api/sync/compress` / `decompress` + +--- + +## 5. 未做 / 否决 + +- v2 设计中的跨目录 **剪贴板到不同服务器**(前端已拦) +- 公网 **8601**、嵌入式 Monaco 全站预加载(见项目 SSOT 否决项) + +--- + +## 6. 相关 changelog(按主题) + +- `2026-06-01-files-browse-fix.md` +- `2026-06-01-files-page-iteration.md` +- `2026-06-01-files-300k-preload.md` +- `2026-06-01-files-monaco-editor-fix.md` +- `2026-06-01-files-editor-panel.md` +- `2026-06-01-files-v2-complete.md` +- `2026-06-01-files-clipboard-transfer.md` diff --git a/docs/archive/handoff/handoff-2026-05-31.md b/docs/archive/handoff/handoff-2026-05-31.md new file mode 100644 index 00000000..1aace11f --- /dev/null +++ b/docs/archive/handoff/handoff-2026-05-31.md @@ -0,0 +1,305 @@ +# Nexus 项目交接文件 — Cursor 继续开发 + +> **Linux 路径 SSOT**: `docs/project/linux-dev-paths.md`(下文 `C:/Users/...` 为历史 Windows 示例) + +> 生成时间: 2026-05-31 +> 当前分支: main (与 origin/main 同步) +> 最后 commit: 2d00d6a fix: 登录页居中布局 + 删除Logo区域 + +--- + +## 一、项目基本信息 + +| 项目 | 值 | +|------|------| +| 项目名 | Nexus 6.0 | +| 功能 | 2000+ 服务器运维管理平台 | +| 仓库 | http://66.154.115.8:3000/admin/Nexus.git | +| 本地路径 | `C:\Users\uzuma\Desktop\svn\Nexus` | +| 生产域名 | https://api.synaglobal.vip | +| 生产 IP | 47.254.123.106 | +| 后端端口 | 8600 | +| 部署路径 | `/www/wwwroot/api.synaglobal.vip/` | +| 管理员凭据 | admin / Nexus@2026 | +| 数据库 | MySQL nexus/Nexus@2026!@127.0.0.1:3306/nexus | +| Gitea 部署 Token | SHA: 26fee743a9332895b55f79b2dbe2e931dc7c2fe5 | + +--- + +## 二、技术栈 + +### 后端 (server/) +- **FastAPI** 0.115.6 + **uvicorn** 0.34.0 +- **SQLAlchemy** 2.0.49 (Async) + **aiomysql** 0.2.0 +- **Redis** 5.2.1 (心跳/缓存/告警去重) +- **asyncssh** 2.17.0 (WebSSH 连接池) +- **PyJWT** 2.10.1 + **bcrypt** 4.2.1 (认证) +- **cryptography** 44.0.0 (Fernet 凭据加密) +- Clean Architecture 4层: api/ → application/ → domain/ → infrastructure/ + +### 前端 (frontend/) +- **Vue 3** ^3.5.30 + **Vuetify 4** ^4.0.2 + **TypeScript** ~5.9.3 +- **Vite** ^8.0.0 + **Pinia** ^3.0.4 +- **xterm.js** ^6.0.0 (WebSSH 终端) +- **Monaco Editor** ^0.55.1 (代码编辑器) +- 14个页面组件,Hash History 路由 +- 构建输出到 `web/app/` (后端 StaticFiles 挂载点) + +### 旧前端 (web/app/) — 仍存在但不维护 +- Tailwind CSS v4 + Alpine.js,12 个 HTML 页面 +- 新 SPA 构建后覆盖 web/app/index.html 和 assets/ + +--- + +## 三、MCP 服务器配置 + +**全局配置位置**: `C:\Users\uzuma\.claude.json` → `mcpServers` + +| MCP 名称 | 类型 | 命令 | 用途 | +|----------|------|------|------| +| Nexus Remote MCP | SSH | `ssh -o StrictHostKeyChecking=no -i /c/Users/uzuma/.ssh/id_rsa_mcp root@47.254.123.106 python3 /www/wwwroot/api.synaglobal.vip/mcp/nexus_server.py` | 后端操作(API/DB/部署/日志) | +| Nexus Remote MCP (旧) | SSH | 同上但运行 `multisync_server.py` | 旧版(可忽略) | +| gitea | npx | `gitea-mcp` | Gitea 仓库操作 | +| github | npx | `@modelcontextprotocol/server-github` | GitHub 操作 | +| playwright | npx | `@playwright/mcp@latest` | 浏览器自动化 | +| context7 | npx | `@upstash/context7-mcp` | 库文档查询 | +| megamemory | 本地 | `megamemory` | 项目知识图谱 | +| memory | npx | `@modelcontextprotocol/server-memory` | 内存存储 | +| semble | uvx | `semble[mcp]` | 代码搜索 | +| fetch | uvx | `mcp-server-fetch` | URL 获取 | +| sequential-thinking | npx | `@modelcontextprotocol/server-sequential-thinking` | 顺序思考 | +| time | npx | `@modelcontextprotocol/server-time` | 时间服务 | + +**项目级 MCP** (`.claude/settings.local.json`): +```json +{ + "mcpServers": { + "vuetify-mcp": { + "command": "npx", + "args": ["-y", "@vuetify/mcp", "--remote"] + } + } +} +``` + +**Nexus 项目级 MCP** (`~/.claude.json` → projects → Nexus → mcpServers): +```json +{ + "nexus": { + "type": "stdio", + "command": "ssh", + "args": ["-o", "StrictHostKeyChecking=no", "-i", "C:/Users/uzuma/.ssh/id_rsa_nexus", "root@47.254.123.106", "cd /www/wwwroot/api.synaglobal.vip && NEXUS_DEPLOY_DIR=/www/wwwroot/api.synaglobal.vip python3 mcp/Nexus_server.py"] + } +} +``` + +### Cursor 中配置 MCP +Cursor 的 MCP 配置在 `~/.cursor/mcp.json`,格式: +```json +{ + "mcpServers": { + "nexus": { + "command": "ssh", + "args": ["-o", "StrictHostKeyChecking=no", "-i", "C:/Users/uzuma/.ssh/id_rsa_nexus", "root@47.254.123.106", "cd /www/wwwroot/api.synaglobal.vip && NEXUS_DEPLOY_DIR=/www/wwwroot/api.synaglobal.vip python3 mcp/Nexus_server.py"] + }, + "playwright": { + "command": "cmd", + "args": ["/c", "npx", "-y", "@playwright/mcp@latest"] + }, + "gitea": { + "command": "cmd", + "args": ["/c", "npx", "-y", "gitea-mcp"], + "env": { + "GITEA_ACCESS_TOKEN": "4dfc8e2edeaf86bc9c485779652e2eb67a8604dc", + "GITEA_HOST": "http://66.154.115.8:3000" + } + }, + "fetch": { + "command": "uvx", + "args": ["mcp-server-fetch"] + }, + "context7": { + "command": "cmd", + "args": ["/c", "npx", "-y", "@upstash/context7-mcp"] + } + } +} +``` + +--- + +## 四、SSH 配置 + +**`~/.ssh/config`**: +``` +Host nexus + HostName 47.254.123.106 + User root + IdentityFile C:/Users/uzuma/.ssh/id_rsa_nexus + StrictHostKeyChecking no +``` + +**密钥文件**: +- `C:\Users\uzuma\.ssh\id_rsa_nexus` — 生产服务器 SSH +- `C:\Users\uzuma\.ssh\id_rsa_mcp` — MCP 远程连接 + +--- + +## 五、部署流程 + +### 后端部署 +```bash +# 1. Push 到 Gitea +git push origin main + +# 2. 服务器拉取 + 重启 +ssh nexus "cd /www/wwwroot/api.synaglobal.vip && git fetch --all && git reset --hard origin/main && supervisorctl restart nexus" + +# 3. 健康检查 +ssh nexus "sleep 3 && curl -s http://127.0.0.1:8600/health" +# 预期: ok +``` + +### 前端部署 +```bash +# 一键脚本 +bash deploy/deploy-frontend.sh + +# 或手动: +cd frontend && npx vite build && cd .. +tar czf /tmp/nexus-frontend.tar.gz -C web/app index.html assets/ +scp /tmp/nexus-frontend.tar.gz nexus:/tmp/nexus-frontend.tar.gz +ssh nexus "cd /www/wwwroot/api.synaglobal.vip/web/app && tar xzf /tmp/nexus-frontend.tar.gz && rm /tmp/nexus-frontend.tar.gz" +``` + +### 本地开发 +```bash +cd frontend && npm run dev # http://localhost:3000/app/ +``` + +Vite proxy 配置: `/api` → `https://api.synaglobal.vip`, `/ws` → `wss://api.synaglobal.vip` + +--- + +## 六、7道门控 (deploy/pre_deploy_check.sh) + +| # | 门 | 检查内容 | +|---|----|---------| +| 1 | Changelog门 | 文件存在且行数≥10 | +| 2 | Audit门 | 文件存在且含 Step3+Closure+DoD | +| 3 | Test门 | test_api.py 必须存在且通过 | +| 4 | Lint门 | `ruff check server/` 零错误 | +| 5 | Import门 | `import server.main` 成功 | +| 6 | Security门 | `bandit` 无 HIGH/MEDIUM | +| 7 | Review门 | 审计文件包含实际改动文件清单 | + +MCP deploy 工具自动执行门控,任何一门不过返回 🚫 DEPLOY BLOCKED + +--- + +## 七、当前完成状态 + +### 本次 Session 完成 (2026-05-31) + +1. **登录页居中** — 移除 v-container+v-row,改用 flexbox 居中 +2. **登录页删除 Logo** — 移除头像 + "登录 Nexus" + "服务器运维管理平台" +3. **TerminalPage 全面迭代 (25项修复)**: + - P0: Ctrl+L清屏、命令历史恢复、重连per-session、onopen不忽略、ping per-session、uptime per-session + - P1: 空catch处理、termRefs用session.id、错误透传、initialCdSent per-session、剪贴板权限 + - P2: 全选菜单、指数退避重连、快捷命令编辑、硬编码高度→flex-grow +4. **快捷命令 MySQL 持久化** — 新增 quick_commands 表 + CRUD API + 启动 seed +5. **Shell 语法高亮** — 命令输入栏实时着色 (sudo→红、关键字→蓝、管道→黄、字符串→绿、flag→紫) +6. **全部已部署到生产** — 后端 git pull + supervisorctl restart,前端 tar+scp + +### 项目整体进度 — ~99% 完成 + +14 页面全部完成部署。所有 P0/P1/P2 功能已实现。 + +--- + +## 八、已知待续事项 + +| 优先级 | 问题 | 文件 | 说明 | +|--------|------|------|------| +| P1 | 批量操作按钮无反应 | ServersPage.vue | 服务器列表顶部「升级/安装/卸载 Agent」按钮,选中后点击没反应 | +| P2 | 47.121.118.30 持续发 422 | server/api/agent.py | 该 IP 不在数据库但持续发心跳,需静默丢弃或注册 | +| P2 | 422 调试日志是临时的 | server/main.py | RequestValidationError handler 仅用于调试,确认问题后应移除 | +| P3 | agent.py on_event 弃用 | server/api/agent.py | `on_event("startup")` 已弃用应改 lifespan | +| P3 | Agent 心跳缺指数退避 | server/api/agent.py | 心跳失败时无退避策略 | +| P2 | 壁纸 URL 请求命中单数端点 | LoginPage.vue | 浏览器缓存旧 JS 时请求 `/bing-wallpaper` 而非 `/bing-wallpapers`,清缓存可解决 | +| UX | 命令输入栏高亮与输入框对齐 | TerminalPage.vue | cmd-highlight 定位需要与 v-text-field 输入区精确对齐,可能在不同浏览器/分辨率下有偏差 | + +--- + +## 九、关键文件速查 + +| 用途 | 文件路径 | +|------|----------| +| 后端入口 | server/main.py | +| 路由注册 | server/main.py 行525-560 | +| 数据模型 | server/domain/models/__init__.py | +| JWT 认证 | server/api/auth_jwt.py | +| 登录API | server/api/auth.py | +| WebSSH | server/api/webssh.py | +| 终端 WS | server/api/websocket.py | +| 设置 CRUD | server/api/settings.py | +| 服务器 CRUD | server/api/servers.py | +| 快捷命令 CRUD | server/api/terminal.py (新建) | +| Agent 心跳 | server/api/agent.py | +| 同步引擎 | server/application/services/sync_engine_v2.py | +| 前端入口 | frontend/src/main.ts | +| 路由 | frontend/src/router/index.ts | +| API 客户端 | frontend/src/api/index.ts | +| Auth Store | frontend/src/stores/auth.ts | +| 登录页 | frontend/src/pages/LoginPage.vue | +| 终端页 | frontend/src/pages/TerminalPage.vue | +| Vite 配置 | frontend/vite.config.mts | +| 部署脚本 | deploy/deploy-frontend.sh | +| 门控脚本 | deploy/pre_deploy_check.sh | +| Supervisor | deploy/nexus.conf | +| Nginx | deploy/nginx_https.conf | +| DB 备份 | deploy/db_backup.sh | +| 健康检查 | deploy/health_monitor.sh | +| 项目记忆 | CLAUDE.md (279行,非常详尽) | + +--- + +## 十、Megamemory 知识图谱 + +**统计**: 34 nodes, 23 edges, 11 removed + +使用 `megamemory:understand` 查询项目上下文,`megamemory:list_roots` 查看所有根节点。 + +核心概念: nexus(总览), clean-architecture-4, db-tables-14, jwt-totp, webssh, terminal-11, sync, install-wizard, 3(3层守护), websocket, telegram, redis, security, settings, vuetify-spa, pushpage, ide, v2(文件管理器), pipeline-7(7道门控), push-cancel, reveal-with-password, nexus-theme, ruff-2026-05-30, appauthmiddleware-fix-chain, browser-tool-preference, session-2026-05-30-progress, agent, servers-list, child-server-operation-rules + +--- + +## 十一、Cursor Rules / CLAUDE.md 位置 + +| 文件 | 路径 | 说明 | +|------|------|------| +| 全局规则 | `C:\Users\uzuma\.claude\CLAUDE.md` | 完美实现原则、安全铁律、文档纪律、执行顺序 | +| 项目规则 | `C:\Users\uzuma\Desktop\svn\Nexus\CLAUDE.md` | 项目概述、目录结构、已确认决策、实现状态、部署流程 | +| 项目 Memory | `C:\Users\uzuma\.claude\projects\C--Users-uzuma-Desktop-svn-Nexus\memory\MEMORY.md` | 语言偏好(中文)、浏览器偏好(Playwright)、SSH配置、部署流程、Vuetify进度 | + +**重要**: Cursor 中对应的规则文件位置: +- `.cursorrules` 或 `.cursor/rules/` 目录 +- 需要把 CLAUDE.md 内容复制为 `.cursorrules` 格式 + +--- + +## 十二、开发流程提醒 + +``` +需求确认 → 设计文档 → 技术文档 → 实现 → 测试验证 → changelog →(必要时)更新 CLAUDE.md +``` + +**强制文件修改流程**: +``` +实现 → WSL本地验证 → ★审计8步★ → 部署 → 健康检查 → 浏览器验证 → changelog +``` + +**进度条**(每次改代码必须输出): +``` +□实现 □WSL验证 □审计8步 □部署 □健康检查 □浏览器验证 □changelog +``` diff --git a/docs/archive/handoff/handoff-2026-06-01.md b/docs/archive/handoff/handoff-2026-06-01.md new file mode 100644 index 00000000..8738a32a --- /dev/null +++ b/docs/archive/handoff/handoff-2026-06-01.md @@ -0,0 +1,443 @@ +# Nexus 项目交接文件 — Cursor 继续开发 + +> **Linux 路径 SSOT**: `docs/project/linux-dev-paths.md`(下文 `C:/Users/...` 为历史 Windows 示例) + +> 生成时间: 2026-06-01(2026-06-01 晚更新:单负责人收口) +> 当前分支: main(与 origin/main 同步,含 b8af1fc 单 Agent 规则) +> 协作方式: **用户 ↔ 当前 Cursor Agent**(无虚拟部门/Skill 分派) +> 主前端: **Vue 3 + Vuetify SPA**(`frontend/` → `web/app/`) +> 生产: https://api.synaglobal.vip 已部署 + +--- + +## 零、Cursor 接管第一步 + +### 1. 已为你准备好的文件 + +| 文件 | 位置 | 作用 | +|------|------|------| +| **Cursor Rules** | `.cursorrules`(项目根目录)| 强制规则、部署流程、文件速查 — Cursor 自动读取 | +| **MCP 配置** | `C:\Users\uzuma\.cursor\mcp.json` | nexus/playwright/gitea/fetch/context7 — 已写好 | +| **项目规则** | `CLAUDE.md`(项目根目录)| 完整项目记忆、决策、实现状态、部署常遇问题 | +| **交接文件** | `docs/handoff-2026-06-01.md` | 本文件 | + +### 2. Cursor 中使用步骤 + +1. 用 Cursor 打开 `C:\Users\uzuma\Desktop\svn\Nexus` +2. MCP 自动加载(`~/.cursor/mcp.json` 已配置好) +3. `.cursorrules` 自动生效(Cursor 自动读取项目根目录的此文件) +4. 直接开始对话 + +### 3. 给 Cursor Agent 的开场提示词(复制到 Chat) + +``` +读取 docs/handoff-2026-06-01.md 了解项目全貌,然后读取 CLAUDE.md 了解强制规则。项目 ~99% 完成,当前待续事项见交接文件第八节。用中文回复。 +``` + +### 4. 更完整的 Agent System Prompt(如需配置 Custom Agent) + +``` +你是 Nexus 6.0 项目的开发助手。这是一个 2000+ 服务器运维管理平台。 + +技术栈: +- 后端: FastAPI 0.115.6 + Async SQLAlchemy 2.0 + Redis 5.2 + WebSocket + Telegram +- 前端: Vue 3 + Vuetify 4 + TypeScript SPA (14个页面) +- 构建工具: Vite 8,输出到 web/app/ +- 部署: Gitea → SSH git pull → supervisorctl restart (后端),Vite build → tar+scp (前端) + +强制规则: +1. 所有实现必须是完美实现 — 不允许降级、妥协、留技术债 +2. 安全铁律: 禁止明文密码前端、禁止静默吞错、禁止f-string拼SQL、禁止硬编码密钥 +3. 文档纪律: 每次修改必须写 changelog (docs/changelog/,行数≥10),新功能必须先有设计文档 +4. 修改流程: 实现 → 验证 → 审计8步 → 部署 → 健康检查 → 浏览器验证 → changelog +5. 用中文回复 +6. 部署用 SSH: ssh nexus "cd /www/wwwroot/api.synaglobal.vip && git fetch --all && git reset --hard origin/main && supervisorctl restart nexus" +7. 前端部署: cd frontend && npx vite build && cd .. && tar czf /tmp/nexus-frontend.tar.gz -C web/app index.html assets/ && scp /tmp/nexus-frontend.tar.gz nexus:/tmp/nexus-frontend.tar.gz && ssh nexus "cd /www/wwwroot/api.synaglobal.vip/web/app && tar xzf /tmp/nexus-frontend.tar.gz && rm /tmp/nexus-frontend.tar.gz" +8. 健康检查: ssh nexus "sleep 3 && curl -s http://127.0.0.1:8600/health" → 预期 "ok" + +生产环境: +- 域名: https://api.synaglobal.vip +- SSH 别名: ssh nexus (root@47.254.123.106) +- 管理员: admin / Nexus@2026 +- 数据库: MySQL nexus/Nexus@2026!@127.0.0.1:3306/nexus + +详细项目文档: 项目根目录 CLAUDE.md +交接文件: docs/handoff-2026-06-01.md +``` + +--- + +## 一、项目基本信息 + +| 项目 | 值 | +|------|------| +| 项目名 | Nexus 6.0 | +| 功能 | 2000+ 服务器运维管理平台 | +| 仓库 | http://66.154.115.8:3000/admin/Nexus.git | +| 本地路径 | `C:\Users\uzuma\Desktop\svn\Nexus` | +| 生产域名 | https://api.synaglobal.vip | +| 生产 IP | 47.254.123.106 | +| 后端端口 | 8600 | +| 部署路径 | `/www/wwwroot/api.synaglobal.vip/` | +| 管理员凭据 | admin / Nexus@2026 | +| 数据库 | MySQL nexus/Nexus@2026!@127.0.0.1:3306/nexus | +| Gitea 部署 Token | SHA: 26fee743a9332895b55f79b2dbe2e931dc7c2fe5 | + +--- + +## 二、技术栈 + +### 后端 (server/) +- **FastAPI** 0.115.6 + **uvicorn** 0.34.0 +- **SQLAlchemy** 2.0.49 (Async) + **aiomysql** 0.2.0 +- **Redis** 5.2.1 (心跳/缓存/告警去重) +- **asyncssh** 2.17.0 (WebSSH 连接池) +- **PyJWT** 2.10.1 + **bcrypt** 4.2.1 (认证) +- **cryptography** 44.0.0 (Fernet 凭据加密) +- Clean Architecture 4层: api/ → application/ → domain/ → infrastructure/ + +### 前端 (frontend/) +- **Vue 3** ^3.5.30 + **Vuetify 4** ^4.0.2 + **TypeScript** ~5.9.3 +- **Vite** ^8.0.0 + **Pinia** ^3.0.4 +- **xterm.js** ^6.0.0 (WebSSH 终端) +- **Monaco Editor** ^0.55.1 (代码编辑器) +- 14个页面组件,Hash History 路由 +- 构建输出到 `web/app/` (后端 StaticFiles 挂载点) + +### 旧前端 (web/app/) — 仍存在但不维护 +- Tailwind CSS v4 + Alpine.js,12 个 HTML 页面 +- 新 SPA 构建后覆盖 web/app/index.html 和 assets/ + +--- + +## 三、MCP 服务器配置 + +### Cursor MCP 配置(已写入 `~/.cursor/mcp.json`) + +```json +{ + "mcpServers": { + "nexus": { + "command": "ssh", + "args": ["-o", "StrictHostKeyChecking=no", "-i", "C:/Users/uzuma/.ssh/id_rsa_nexus", "root@47.254.123.106", "cd /www/wwwroot/api.synaglobal.vip && NEXUS_DEPLOY_DIR=/www/wwwroot/api.synaglobal.vip python3 mcp/Nexus_server.py"] + }, + "playwright": { + "command": "cmd", + "args": ["/c", "npx", "-y", "@playwright/mcp@latest"] + }, + "gitea": { + "command": "cmd", + "args": ["/c", "npx", "-y", "gitea-mcp"], + "env": { + "GITEA_ACCESS_TOKEN": "4dfc8e2edeaf86bc9c485779652e2eb67a8604dc", + "GITEA_HOST": "http://66.154.115.8:3000" + } + }, + "fetch": { + "command": "uvx", + "args": ["mcp-server-fetch"] + }, + "context7": { + "command": "cmd", + "args": ["/c", "npx", "-y", "@upstash/context7-mcp"] + } + } +} +``` + +### Claude Code MCP 配置(参考) + +**全局配置位置**: `C:\Users\uzuma\.claude.json` → `mcpServers` + +| MCP 名称 | 类型 | 命令 | 用途 | +|----------|------|------|------| +| Nexus Remote MCP | SSH | `ssh -o StrictHostKeyChecking=no -i /c/Users/uzuma/.ssh/id_rsa_mcp root@47.254.123.106 python3 /www/wwwroot/api.synaglobal.vip/mcp/nexus_server.py` | 后端操作(API/DB/部署/日志) | +| gitea | npx | `gitea-mcp` | Gitea 仓库操作 | +| github | npx | `@modelcontextprotocol/server-github` | GitHub 操作 | +| playwright | npx | `@playwright/mcp@latest` | 浏览器自动化 | +| context7 | npx | `@upstash/context7-mcp` | 库文档查询 | +| megamemory | 本地 | `megamemory` | 项目知识图谱 | +| memory | npx | `@modelcontextprotocol/server-memory` | 内存存储 | +| fetch | uvx | `mcp-server-fetch` | URL 获取 | + +**项目级 MCP** (`~/.claude.json` → projects → Nexus → mcpServers): +```json +{ + "nexus": { + "type": "stdio", + "command": "ssh", + "args": ["-o", "StrictHostKeyChecking=no", "-i", "C:/Users/uzuma/.ssh/id_rsa_nexus", "root@47.254.123.106", "cd /www/wwwroot/api.synaglobal.vip && NEXUS_DEPLOY_DIR=/www/wwwroot/api.synaglobal.vip python3 mcp/Nexus_server.py"] + } +} +``` + +--- + +## 四、SSH 配置 + +**`~/.ssh/config`**: +``` +Host nexus + HostName 47.254.123.106 + User root + IdentityFile C:/Users/uzuma/.ssh/id_rsa_nexus + StrictHostKeyChecking no +``` + +**密钥文件**: +- `C:\Users\uzuma\.ssh\id_rsa_nexus` — 生产服务器 SSH +- `C:\Users\uzuma\.ssh\id_rsa_mcp` — MCP 远程连接 + +--- + +## 五、部署流程 + +### 后端部署 +```bash +# 1. Push 到 Gitea +git push origin main + +# 2. 服务器拉取 + 重启 +ssh nexus "cd /www/wwwroot/api.synaglobal.vip && git fetch --all && git reset --hard origin/main && supervisorctl restart nexus" + +# 3. 健康检查 +ssh nexus "sleep 3 && curl -s http://127.0.0.1:8600/health" +# 预期: ok +``` + +### 前端部署 +```bash +# 一键脚本 +bash deploy/deploy-frontend.sh + +# 或手动: +cd frontend && npx vite build && cd .. +tar czf /tmp/nexus-frontend.tar.gz -C web/app index.html assets/ +scp /tmp/nexus-frontend.tar.gz nexus:/tmp/nexus-frontend.tar.gz +ssh nexus "cd /www/wwwroot/api.synaglobal.vip/web/app && tar xzf /tmp/nexus-frontend.tar.gz && rm /tmp/nexus-frontend.tar.gz" +``` + +### 本地开发 +```bash +cd frontend && npm run dev # http://localhost:3000/app/ +``` + +Vite proxy 配置: `/api` → `https://api.synaglobal.vip`, `/ws` → `wss://api.synaglobal.vip` + +### 部署常遇问题 + +| 问题 | 解决 | +|------|------| +| `/app/` 返回 404 | AppAuthMiddleware 拦截 — 确认 `/app/` 和 `/app/index.html` 在 `_APP_PUBLIC_PATHS` 中 | +| `/app/` 返回旧 HTML (Tailwind/Alpine) | `git reset --hard` 恢复了旧文件 — 需要重新 tar+scp 前端构建产物 | +| MCP deploy → git pull 失败 `could not read Username` | 服务器 remote URL 没配 token,用 `git remote set-url` | +| Gate 2 Audit BLOCKED | 创建 `docs/audit/YYYY-MM-DD-topic.md`,含 Step3+Closure+DoD | +| Gate 3 Test BLOCKED (401) | 服务器 .env 中 `NEXUS_TEST_ADMIN_PASSWORD` 与数据库不一致 | +| Gate 6 Security BLOCKED (bandit HIGH) | md5 → sha256 等,参看 bandit 报告修复 | +| Gate 7 Review BLOCKED | 审计文件缺少实际改动文件清单 | + +--- + +## 六、7道门控 (deploy/pre_deploy_check.sh) + +| # | 门 | 检查内容 | +|---|----|---------| +| 1 | Changelog门 | 文件存在且行数≥10 | +| 2 | Audit门 | 文件存在且含 Step3+Closure+DoD | +| 3 | Test门 | test_api.py 必须存在且通过 | +| 4 | Lint门 | `ruff check server/` 零错误 | +| 5 | Import门 | `import server.main` 成功 | +| 6 | Security门 | `bandit` 无 HIGH/MEDIUM | +| 7 | Review门 | 审计文件包含实际改动文件清单 | + +MCP deploy 工具自动执行门控,任何一门不过返回 🚫 DEPLOY BLOCKED + +--- + +## 七、最近完成的工作 + +### 2026-06-01: 全站 bug 审查修复 (14项) + +**前端修复 (9项):** + +| 级别 | Bug | 修复 | +|------|-----|------| +| **P0** | App.vue `http` 未导入 → 全局搜索崩溃 | 添加 `import { api, http }` | +| **P1** | TOTP 登录流程断裂 | 导入 `TotpRequiredError`,catch 中优先检查 | +| **P1** | ServersPage 编辑服务器 domain 被端口污染 | `form.address = item.domain` | +| **P1** | PushPage `Set[0]` 无效 → preview 发 undefined | `values().next().value` | +| **P1** | SettingsPage API 字符串未转数字 | 正则检测 + `Number()` | +| P2 | api/index.ts `getList` 重复定义 | 删除第二个 | +| P2 | LoginPage 锁定倒计时不更新 | `ref(Date.now())` + watch + 定时器 | +| P2 | TerminalPage 路径验证过严 | 白名单排除控制字符+引号 | +| P2 | useWebSocket CONNECTING 状态连接泄漏 | 关闭旧连接再建新的 | + +**后端修复 (5项):** + +| 级别 | Bug | 修复 | +|------|-----|------| +| **P1** | websocket.py `redis.keys()` O(N) 阻塞 | `scan_iter()` 非阻塞迭代 | +| **P1** | auth_service.py TTL 每次刷新都重置 | 仅 `ttl < 0` 时设置 expire | +| **P1** | servers.py 批量操作并发 session commit | `asyncio.Lock()` 序列化 | +| P2 | settings.py `asyncio.get_event_loop()` 弃用 | `get_running_loop()` | +| P2 | settings.py `datetime.utcfromtimestamp()` 弃用 | `fromtimestamp(tz=utc)` | + +### 2026-05-31: TerminalPage 全面迭代 + 登录页调整 + +1. **登录页居中** — flexbox 居中,删除 Logo +2. **TerminalPage 25项修复** — Ctrl+L清屏、命令历史恢复、per-session重连/ping/uptime、指数退避等 +3. **快捷命令 MySQL 持久化** — quick_commands 表 + CRUD API + 内置命令 seed +4. **Shell 语法高亮** — 命令输入栏 sudo→红、关键字→蓝、管道→黄、字符串→绿、flag→紫 + +### 项目整体进度 — ~99% 完成 + +14 页面全部完成部署。所有 P0/P1/P2 功能已实现。 + +--- + +## 八、待续事项(2026-06-01 更新) + +### 已修复(见 `docs/changelog/2026-06-01-bug-remediation.md`) + +| 原优先级 | 问题 | 状态 | +|----------|------|------| +| P1 | ServersPage 批量 Agent 操作 | ✅ `normalizeServerIds` + 生产 SPA 已部署 | +| P1 | script-callback 无 API Key | ✅ `X-API-Key` 校验 | +| P1 | Access Token 30 天 | ✅ 60min + HttpOnly refresh | +| P2 | 未知 IP 心跳 422 刷屏 | ✅ 丢弃 + debug 日志 | +| P2 | Admin 废弃 JWT 列 | ✅ 迁移 DROP | +| P3 | agent on_event 弃用 | ✅ lifespan | + +### 仍待办 / 抽测 + +| 优先级 | 问题 | 文件 | 说明 | +|--------|------|------|------| +| P2 | 422 调试 handler 是否保留 | server/main.py | 若日志已稳定可改为仅 4xx 结构化响应 | +| P2 | 壁纸旧端点缓存 | LoginPage.vue | 强刷 `/app/` 或清站点数据 | +| UX | 终端命令栏高亮对齐 | TerminalPage.vue | 多分辨率抽测 | +| **验收** | 告警 Telegram + WS 端到端 | — | 未触发;见 `docs/reports/2026-06-01-browser-verification.md` | +| **验收** | WebSSH 连接 | terminal | ✅ 2026-06-01 `server_id=8` 已连接;resize/长会话待抽测 | +| **验收** | SPA 主要页面 | — | ✅ 仪表盘/服务器/告警/推送 | +| **验收** | 批量选择条 UI | ServersPage.vue | ✅ 2026-06-01 Vuetify4 `v-model` 修复 | +| **验收** | Sync 推送 2 台试跑 | push | 未执行正式推送(防误操作) | +| **验收** | 三层守护 kill 测试 | deploy/ | 维护窗口;cron 已确认存在 | +| **运维** | 生产 pytest | tests/test_api.py | 服务器无 pytest 模块时用 WSL/CI 25/25 为准 | + +--- + +## 九、关键文件速查 + +| 用途 | 文件路径 | +|------|----------| +| 后端入口 | server/main.py | +| 路由注册 | server/main.py 行525-560 | +| 数据模型 | server/domain/models/__init__.py | +| JWT 认证 | server/api/auth_jwt.py | +| 登录API | server/api/auth.py | +| WebSSH | server/api/webssh.py | +| 终端 WS | server/api/websocket.py | +| 设置 CRUD | server/api/settings.py | +| 服务器 CRUD | server/api/servers.py | +| 快捷命令 CRUD | server/api/terminal.py | +| Agent 心跳 | server/api/agent.py | +| 同步引擎 | server/application/services/sync_engine_v2.py | +| 认证服务 | server/application/services/auth_service.py | +| 前端入口 | frontend/src/main.ts | +| 路由 | frontend/src/router/index.ts | +| API 客户端 | frontend/src/api/index.ts | +| Auth Store | frontend/src/stores/auth.ts | +| 登录页 | frontend/src/pages/LoginPage.vue | +| 终端页 | frontend/src/pages/TerminalPage.vue | +| WebSocket composable | frontend/src/composables/useWebSocket.ts | +| Vite 配置 | frontend/vite.config.mts | +| 部署脚本 | deploy/deploy-frontend.sh | +| 门控脚本 | deploy/pre_deploy_check.sh | +| Supervisor | deploy/nexus.conf | +| Nginx | deploy/nginx_https.conf | +| DB 备份 | deploy/db_backup.sh | +| 健康检查 | deploy/health_monitor.sh | +| 项目规则 | CLAUDE.md (非常详尽,279行) | +| Cursor 规则 | .cursorrules (项目根目录) | + +--- + +## 十、Megamemory 知识图谱 + +**统计**: 34 nodes, 23 edges, 11 removed + +使用 `megamemory:understand` 查询项目上下文,`megamemory:list_roots` 查看所有根节点。 + +核心概念: nexus(总览), clean-architecture-4, db-tables-14, jwt-totp, webssh, terminal-11, sync, install-wizard, 3(3层守护), websocket, telegram, redis, security, settings, vuetify-spa, pushpage, ide, v2(文件管理器), pipeline-7(7道门控), push-cancel, reveal-with-password, nexus-theme, ruff-2026-05-30, appauthmiddleware-fix-chain, browser-tool-preference, session-2026-05-30-progress, agent, servers-list, child-server-operation-rules + +--- + +## 十一、规则文件位置 + +| 文件 | 路径 | 说明 | +|------|------|------| +| **Cursor 规则** | `.cursorrules`(项目根目录)| 已创建,Cursor 自动读取 | +| **Cursor MCP** | `C:\Users\uzuma\.cursor\mcp.json` | 已创建,Cursor 自动读取 | +| 全局规则 (Claude) | `C:\Users\uzuma\.claude\CLAUDE.md` | 完美实现原则、安全铁律、文档纪律、执行顺序 | +| 项目规则 (Claude) | `C:\Users\uzuma\Desktop\svn\Nexus\CLAUDE.md` | 项目概述、目录结构、已确认决策、实现状态、部署流程 | +| 项目 Memory (Claude) | `C:\Users\uzuma\.claude\projects\C--Users-uzuma-Desktop-svn-Nexus\memory\MEMORY.md` | 语言偏好(中文)、浏览器偏好(Playwright)、SSH配置、部署流程、Vuetify进度 | + +--- + +## 十二、开发流程提醒 + +``` +需求确认 → 设计文档 → 技术文档 → 实现 → 测试验证 → changelog →(必要时)更新 CLAUDE.md +``` + +**强制文件修改流程(不可跳步)**: +``` +实现 → WSL本地验证 → ★审计8步★ → 部署 → 健康检查 → 浏览器验证 → changelog +``` + +**进度条**(每次改代码必须输出): +``` +□实现 □WSL验证 □审计8步 □部署 □健康检查 □浏览器验证 □changelog +``` + +审计8步:登记→全文Read→规则扫描H→Closure表→入口表→输入→Sink→归类→DoD + +--- + +## 十三、数据流 + +``` +Agent心跳(60s) → Redis(实时) → 前端直读 → 10min批量 → MySQL(历史) +告警: CPU/mem/disk > threshold → WebSocket推送浏览器 + Telegram推送手机 +恢复: 之前告警的指标恢复正常 → 自动推送恢复通知 +Redis心跳key: heartbeat:{server_id} (HSET, TTL=600s) +Redis告警key: alerts:{server_id} (SET, TTL=3600s) +``` + +--- + +## 十四、已确认的关键决策 + +| 不可改项 | 原因 | +|---------|------| +| API_KEY | 加密一致性 + 子服务器认证 | +| SECRET_KEY | 加密回退密钥 | +| DATABASE_URL | 启动必需 | +| ENCRYPTION_KEY | 凭据加密 | + +| 可改项 | 说明 | +|--------|------| +| system_name/title | 前端显示 | +| pool_size/overflow | 安装向导自动推荐 | +| redis_url | Redis 连接 | +| 告警阈值 | 默认 80% | +| Telegram 配置 | 告警推送 | + +--- + +## 十五、用户审查清单 + +验证 AI 是否偷懒: +1. 看 `deploy/gate_log.jsonl` — 每次门控都有记录,没有记录 = 没过门控 +2. 看审计文件 — 必须有 Closure 表(每个H的判定+依据)、Step 3 规则扫描、DoD +3. 看进度条 — ☑必须在□前面,否则跳步 +4. 看 changelog — 行数≥10,不能是空壳 diff --git a/docs/archive/memory/agentmemory-2026-05-merged.md b/docs/archive/memory/agentmemory-2026-05-merged.md new file mode 100644 index 00000000..c96082eb --- /dev/null +++ b/docs/archive/memory/agentmemory-2026-05-merged.md @@ -0,0 +1,96 @@ +# Agent Memory 合并卷(2026-05 迁移) + +> 生成: 2026-06-04 · 原 docs/memory/ 16 篇 + +请以 [nexus-functional-development-guide.md](../project/nexus-functional-development-guide.md) 为准。 + +## mem_key_files + +# Nexus 6.0 关键文件清单 (2026-05-21 更新) +## 后端 (server/) +### 入口与配置 +### API 层 +### 应用服务层 +### 后台任务 +### 基础设施 +## 前端 (web/) — PHP待替换 + +## mem_mpc4vjm8_caa29f573fd9 + +# MultiSync 2026-05-19 进度:Sprint P0-P2 全部完成(SSH线程隔离、Redis深度利用、审计日志)。生产环境 141/253 在 +MultiSync 2026-05-19 进度:Sprint P0-P2 全部完成(SSH线程隔离、Redis深度利用、审计日志)。生产环境 141/253 在线。文件管理器最终采用 WebFM 单文件版(1679行),6行最小改动集成。设计标准确立:鉴权即信任、浏览器验收、safePath规范。待办:代码审查#1-#3顺手改、WebFM浏览器验收。 +## 相关概念 + +## mem_mpc54lkr_68c4f408352b + +# Sprint 2026-05-19 全部 11 项任务完成。生产环境 217/253 在线,API/DB/Redis/WS 全 online。文档检查发现 3 +## 相关概念 + +## mem_new_decisions + +# Nexus 新增关键决策 (Phase D) +## 相关概念 + +## mem_nexus_config + +# Nexus install.php配置三写机制 +## 相关概念 + +## mem_nexus_dataflow + +# Nexus 心跳数据流与告警机制 +## 相关概念 + +## mem_nexus_decisions + +# Nexus 6.0 已确认关键决策汇总 +## 相关概念 + +## mem_nexus_guardian + +# Nexus 3层守护机制设计 +## 相关概念 + +## mem_nexus_modules + +# Nexus 6.0 Phase A+B+C 已完成模块 +E1 websocket.py(告警推送), E2 agent.py(心跳→Redis+告警), E3 heartbeat_flush.py(Redis→MySQL10min), E4 config.py(load_settings_from_db), E5 self_monitor.py(Python自检), E6 telegram/__init__(py)(推送模块), E7 servers. +## 相关概念 + +## mem_nexus_overview + +# Nexus 6.0 项目概述 +## 相关概念 + +## mem_nexus_repo + +# Nexus 仓库统一与Git状态 +## 相关概念 + +## mem_nexus_telegram + +# Nexus Telegram Bot告警推送设计 +## 相关概念 + +## mem_nexus_testing + +# Nexus Phase D 测试计划与流程 +## 相关概念 + +## mem_phase_d_bugfixes + +# Nexus Phase D 前置修复 (2026-05-20): 12项关键修复全部完成 +## 相关概念 + +## mem_team_agenda + +# Nexus 关键决策汇总(非部门编制) +> 原「团队讨论议程」已废弃虚拟部门分派;以下为仍有效的技术/产品决策摘要。 +## 配置与架构 +## 测试与交付 +## 历史待办(多数已在 2026-06 修复) + +## mem_wsl_deployment + +# Nexus WSL部署信息 (172.31.170.47) +## 相关概念 diff --git a/docs/archive/memory/raw/mem_key_files.md b/docs/archive/memory/raw/mem_key_files.md new file mode 100644 index 00000000..34d3f5f0 --- /dev/null +++ b/docs/archive/memory/raw/mem_key_files.md @@ -0,0 +1,125 @@ +# Nexus 6.0 关键文件清单 (2026-05-21 更新) + +## 后端 (server/) + +### 入口与配置 +- `server/main.py` — FastAPI 应用入口,lifespan,CORS,后台任务 +- `server/config.py` — Settings + DB override + CORS_ORIGINS +- `server/domain/models/__init__.py` — SQLAlchemy 模型(10表) + +### API 层 +- `server/api/agent.py` — Agent心跳接收 + 命令注入防护(BLOCKED_COMMAND_PATTERNS) +- `server/api/auth.py` — Login/logout/TOTP(旧) +- `server/api/auth_jwt.py` — JWT认证(新) +- `server/api/websocket.py` — `/ws/alerts` 告警广播 +- `server/api/servers.py` — 服务器CRUD + Redis实时状态 +- `server/api/settings.py` — 动态配置读写 +- `server/api/health.py` — `/health` 端点(3层守护用) +- `server/api/scripts.py` — 脚本执行 +- `server/api/dependencies.py` — FastAPI 依赖注入 + +### 应用服务层 +- `server/application/services/auth_service.py` — Redis session store(session:{token} 8h滑动TTL) +- `server/application/services/server_service.py` — Agent健康检查(httpx+Semaphore20)+ push委托 +- `server/application/services/sync_service.py` — 批量推送引擎 +- `server/application/services/script_service.py` — 脚本执行服务 + +### 后台任务 +- `server/background/heartbeat_flush.py` — Redis→MySQL 10min批量(TTL=900s) +- `server/background/self_monitor.py` — Python自检 30s循环 + +### 基础设施 +- `server/infrastructure/redis/client.py` — Redis异步客户端(max_connections从settings读) +- `server/infrastructure/ssh/asyncssh_pool.py` — asyncssh SSH连接池 (旧 paramiko pool.py 已删除) +- `server/infrastructure/telegram/__init__.py` — Telegram推送(httpx模块级单例) +- `server/infrastructure/database/crypto.py` — Fernet+AES双格式加密 +- `server/infrastructure/database/session.py` — SQLAlchemy异步session +- `server/infrastructure/database/server_repo.py` + `setting_repo.py` + `admin_repo.py` + 等 — Repository层 + +--- + +## 前端 (web/) — PHP待替换 + +### 当前PHP文件(将被Tailwind CSS v4 + 静态HTML/JS替换) +- `web/install.php` — 5步安装向导 +- `web/login.php` / `web/logout.php` / `web/totp.php` — 认证 +- `web/index.php` — Dashboard +- `web/servers.php` — 服务器列表 +- `web/files.php` / `web/fm.php` / `web/tinyfm.php` / `web/webfm.php` — 文件管理 +- `web/settings.php` / `web/config.php` — 配置 +- `web/push.php` / `web/retry.php` / `web/schedules.php` — 推送 +- `web/audit.php` / `web/logs.php` — 审计日志 +- `web/scripts.php` / `web/deploy.php` — 脚本与部署 +- `web/db.php` / `web/api_client.php` / `web/api_proxy.php` — 数据层 +- `web/app.js` / `web/style.css` — 前端资源 +- `web/ace/` — ACE编辑器 +- `web/agent/agent.py` / `agent.sh` / `install.sh` — Agent安装脚本 + +--- + +## 部署 (deploy/) +- `deploy/nexus.conf` — Supervisor配置(uvicorn 4 workers) +- `deploy/health_monitor.sh` — Shell守护(cron 1min,3次失败→重启+Telegram) +- `deploy/nginx_172.31.170.47.conf` — WSL Nginx配置 +- `deploy/migrations/001_indexes.sql` — 7个数据库索引 + +--- + +## 文档 (docs/) +### 项目顶层 +- `docs/project/status.md` — 项目进度总览 + 数据库结构 + 迭代计划 +- `docs/project/roadmap.md` — 功能规划 + ADR决策 + 实现顺序 +- `docs/project/deploy.md` — 部署指南 +- `docs/CLAUDE.md` — 项目记忆(仓库根目录) + +### 设计规格与实施计划 +- `docs/design/specs/` — 10个设计规格(推送/密码/重试/文件管理器/架构/等) +- `docs/design/plans/` — 5个实施计划 + +### 竞品分析 +- `docs/research/jumpserver-easynode-architecture-analysis.md` — JumpServer & EasyNode架构分析 + +### 项目记忆 +- `docs/memory/MEMORY.md` — 记忆索引 +- `docs/memory/mem_*.md` — 17个记忆文件(概述/决策/数据流/守护/模块/测试/等) + +### 修复文档 +- `docs/changelog/fix-plan-2026-05-20.md` — 代码问题修复计划(13项) +- `docs/changelog/fix-plan-full-2026-05-20.md` — 全量未完成项修复计划(24项) +- `docs/changelog/fix-verification-report-2026-05-20.md` — 修复验证报告 + +--- + +## 测试 (tests/) +- `tests/conftest.py` — pytest + async fixtures +- `tests/test_api.py` — API测试 +- `tests/load_test.py` + `tests/quick_load.py` — 负载测试 + +--- + +## MCP服务 (mcp/) +- `mcp/Nexus_server.py` — Nexus远程MCP服务 +- `mcp/mcp_bridge.py` — MCP桥接 +- `mcp/firefox_server.py` — Firefox自动化MCP + +--- + +## 配置 +- `.env.example` — 环境变量模板 +- `requirements.txt` — Python依赖 +- `.gitea/workflows/ci-cd.yml` + `pre-commit.yml` — CI/CD流水线 + +--- + +## 相关概念 +- Nexus +- files +- reference +- architecture +- docs +- skills +- deployment +- testing + +--- +*更新: 2026-05-21 全面整理* diff --git a/docs/archive/memory/raw/mem_mpc4vjm8_caa29f573fd9.md b/docs/archive/memory/raw/mem_mpc4vjm8_caa29f573fd9.md new file mode 100644 index 00000000..5991e829 --- /dev/null +++ b/docs/archive/memory/raw/mem_mpc4vjm8_caa29f573fd9.md @@ -0,0 +1,15 @@ +# MultiSync 2026-05-19 进度:Sprint P0-P2 全部完成(SSH线程隔离、Redis深度利用、审计日志)。生产环境 141/253 在 + +MultiSync 2026-05-19 进度:Sprint P0-P2 全部完成(SSH线程隔离、Redis深度利用、审计日志)。生产环境 141/253 在线。文件管理器最终采用 WebFM 单文件版(1679行),6行最小改动集成。设计标准确立:鉴权即信任、浏览器验收、safePath规范。待办:代码审查#1-#3顺手改、WebFM浏览器验收。 + +## 相关概念 + +- 项目进度 +- 2026-05-19 +- Sprint +- WebFM +- 文件管理器 +- 设计标准 + +--- +*源: agentmemory (mem_mpc4vjm8_caa29f573fd9)* diff --git a/docs/archive/memory/raw/mem_mpc54lkr_68c4f408352b.md b/docs/archive/memory/raw/mem_mpc54lkr_68c4f408352b.md new file mode 100644 index 00000000..0499d60e --- /dev/null +++ b/docs/archive/memory/raw/mem_mpc54lkr_68c4f408352b.md @@ -0,0 +1,12 @@ +# Sprint 2026-05-19 全部 11 项任务完成。生产环境 217/253 在线,API/DB/Redis/WS 全 online。文档检查发现 3 + +Sprint 2026-05-19 全部 11 项任务完成。生产环境 217/253 在线,API/DB/Redis/WS 全 online。文档检查发现 3 份严重滞后:CHANGELOG.md(只到迭代3)、ROADMAP.md(迭代7-9标规划但10-12已完成)、TEST_REPORT.md(05-17未更新)。待办:WebFM浏览器验收 + meta refresh/logout修复 + 文档更新。 + +## 相关概念 + +- project status +- documentation audit +- sprint completion + +--- +*源: agentmemory (mem_mpc54lkr_68c4f408352b)* diff --git a/docs/archive/memory/raw/mem_new_decisions.md b/docs/archive/memory/raw/mem_new_decisions.md new file mode 100644 index 00000000..3e687b0b --- /dev/null +++ b/docs/archive/memory/raw/mem_new_decisions.md @@ -0,0 +1,15 @@ +# Nexus 新增关键决策 (Phase D) + +CORS_ORIGINS可改(逗号分隔,空=allow all). Redis max_connections可改(从settings读取,不再硬编码50). 命令注入防护: BLOCKED_COMMAND_PATTERNS+BLOCKED_SUDO_COMMANDS. Telegram httpx模块级单例复用. 告警通知UX: WebSocket浮动卡片栈(非简单toast,含图标+服务器名+指标+暗色模式). Redis session store: session:{token} 8h滑动TTL. Agent健康检查: httpx+Semaphore(20)并发控制. + +## 相关概念 + +- Nexus +- decisions +- CORS +- Redis +- security +- UX + +--- +*源: agentmemory (mem_new_decisions)* diff --git a/docs/archive/memory/raw/mem_nexus_config.md b/docs/archive/memory/raw/mem_nexus_config.md new file mode 100644 index 00000000..2d10ecb3 --- /dev/null +++ b/docs/archive/memory/raw/mem_nexus_config.md @@ -0,0 +1,14 @@ +# Nexus install.php配置三写机制 + +install.php Step3 POST处理同时写入3个目标: 1)web/data/config.php(PHP前端配置API_BASE_URL/DB_*/API_KEY), 2).env(Python后端启动必需NEXUS_前缀), 3)settings MySQL表(共享动态配置api_key/thresholds/Telegram等)。不可改项(SECRET_KEY/API_KEY/ENCRYPTION_KEY/DATABASE_URL)不在MySQL覆盖映射中。load_settings_from_db()在lifespan启动时从MySQL覆盖可变配置。 + +## 相关概念 + +- 配置三写 +- install.php +- config.php +- settings表 +- .env + +--- +*源: agentmemory (mem_nexus_config)* diff --git a/docs/archive/memory/raw/mem_nexus_dataflow.md b/docs/archive/memory/raw/mem_nexus_dataflow.md new file mode 100644 index 00000000..739a5ad6 --- /dev/null +++ b/docs/archive/memory/raw/mem_nexus_dataflow.md @@ -0,0 +1,14 @@ +# Nexus 心跳数据流与告警机制 + +Agent心跳(60s)→Redis(实时)→前端直读→10min批量→MySQL(历史)。告警: CPU/mem/disk>threshold→WebSocket推送浏览器+Telegram推送手机。恢复: 之前告警指标恢复正常→自动推送恢复通知。Redis心跳key: heartbeat:{server_id}(HSET,TTL=600s)。Redis告警key: alerts:{server_id}(SET,TTL=3600s)。 + +## 相关概念 + +- 数据流 +- 心跳 +- Redis +- WebSocket +- 告警 + +--- +*源: agentmemory (mem_nexus_dataflow)* diff --git a/docs/archive/memory/raw/mem_nexus_decisions.md b/docs/archive/memory/raw/mem_nexus_decisions.md new file mode 100644 index 00000000..a0ee04f5 --- /dev/null +++ b/docs/archive/memory/raw/mem_nexus_decisions.md @@ -0,0 +1,14 @@ +# Nexus 6.0 已确认关键决策汇总 + +API_KEY/SECRET_KEY/DATABASE_URL: 禁改不显示(加密一致性+子服务器认证)。system_name/title/pool_size/redis_url/告警阈值/Telegram: 可改。Agent心跳间隔60s。Redis→MySQL刷新10min。心跳数据流: Agent→Redis→前端直读→10min→MySQL。告警推送: WebSocket→前端+Telegram。Python守护: 3层(Supervisor+Python自检30s+Shell cron 1min)。install.php安装后rename→locked+.env锁定。配置源: install→config.php+settings表+.env三方一致。 + +## 相关概念 + +- 决策 +- API_KEY +- 守护 +- 心跳 +- 配置三写 + +--- +*源: agentmemory (mem_nexus_decisions)* diff --git a/docs/archive/memory/raw/mem_nexus_guardian.md b/docs/archive/memory/raw/mem_nexus_guardian.md new file mode 100644 index 00000000..b478ce83 --- /dev/null +++ b/docs/archive/memory/raw/mem_nexus_guardian.md @@ -0,0 +1,14 @@ +# Nexus 3层守护机制设计 + +Layer1: Supervisor—Python崩溃自动重启。Layer2: Python self_monitor—每30s检查Redis/MySQL/WebSocket。Layer3: Shell health_monitor.sh—每分钟HTTP GET /health,连续3次失败→supervisorctl restart+Telegram通知。Supervisor配置: deploy/nexus.conf(uvicorn --workers 4)。Shell脚本: deploy/health_monitor.sh(cron 1min)。 + +## 相关概念 + +- 守护 +- Supervisor +- 自检 +- Shell +- 健康检查 + +--- +*源: agentmemory (mem_nexus_guardian)* diff --git a/docs/archive/memory/raw/mem_nexus_modules.md b/docs/archive/memory/raw/mem_nexus_modules.md new file mode 100644 index 00000000..cc048158 --- /dev/null +++ b/docs/archive/memory/raw/mem_nexus_modules.md @@ -0,0 +1,14 @@ +# Nexus 6.0 Phase A+B+C 已完成模块 + +E1 websocket.py(告警推送), E2 agent.py(心跳→Redis+告警), E3 heartbeat_flush.py(Redis→MySQL10min), E4 config.py(load_settings_from_db), E5 self_monitor.py(Python自检), E6 telegram/__init__(py)(推送模块), E7 servers.py(前端读Redis), E8 health.py(/health端点), E9 main.py(lifespan+后台任务), D1 nexus.conf(Supervisor), D2 health_monitor.sh(Shell健康检查), P1 install.php(5步向导), P2 config.php(读data+MySQL)。10个Python文件全部AST语法验证通过。 + +## 相关概念 + +- 模块完成 +- Phase A +- Phase B +- Phase C +- 实现状态 + +--- +*源: agentmemory (mem_nexus_modules)* diff --git a/docs/archive/memory/raw/mem_nexus_overview.md b/docs/archive/memory/raw/mem_nexus_overview.md new file mode 100644 index 00000000..e169f120 --- /dev/null +++ b/docs/archive/memory/raw/mem_nexus_overview.md @@ -0,0 +1,14 @@ +# Nexus 6.0 项目概述 + +Nexus是2000+服务器运维管理平台,从旧MultiSync(PHP+AdminLTE+pymysql)完全重构为Clean Architecture(FastAPI+Async SQLAlchemy+Redis+WebSocket+Telegram)。唯一仓库Nexus(Gitea,仓库地址在.env中配置)。部署路径由`NEXUS_DEPLOY_PATH`环境变量决定,域名由`NEXUS_CORS_ORIGINS`环境变量决定。目录结构: server/(Python后端), web/(PHP前端), deploy/(Supervisor+Shell), docs/, tests/。 + +## 相关概念 + +- Nexus +- Clean Architecture +- FastAPI +- 项目概述 +- 重构 + +--- +*源: agentmemory (mem_nexus_overview)* diff --git a/docs/archive/memory/raw/mem_nexus_repo.md b/docs/archive/memory/raw/mem_nexus_repo.md new file mode 100644 index 00000000..33e92118 --- /dev/null +++ b/docs/archive/memory/raw/mem_nexus_repo.md @@ -0,0 +1,13 @@ +# Nexus 仓库统一与Git状态 + +所有代码统一在Nexus仓库,不再使用multi-server-sync仓库。目录结构扁平化(server/web/deploy直接在根目录)。已完成3次commit推送到Gitea远程。Git清理: 删除已提交的.env和SECRETS.md,恢复.gitignore排除。这些文件将由install.php在服务器上动态生成。 + +## 相关概念 + +- 仓库 +- Git +- Nexus +- 目录结构 + +--- +*源: agentmemory (mem_nexus_repo)* diff --git a/docs/archive/memory/raw/mem_nexus_telegram.md b/docs/archive/memory/raw/mem_nexus_telegram.md new file mode 100644 index 00000000..6a67eb67 --- /dev/null +++ b/docs/archive/memory/raw/mem_nexus_telegram.md @@ -0,0 +1,13 @@ +# Nexus Telegram Bot告警推送设计 + +Telegram推送模块: server/infrastructure/telegram/__init__.py。函数: send_telegram(通用), send_telegram_alert(告警CPU/mem/disk超阈值), send_telegram_recovery(恢复正常), send_telegram_system_alert(系统级告警如Redis/MySQL断连), send_telegram_restart_success/restart_failed(重启结果)。使用httpx AsyncClient。Shell脚本health_monitor.sh也通过curl直接调用Telegram API。 + +## 相关概念 + +- Telegram +- 告警推送 +- Bot +- httpx + +--- +*源: agentmemory (mem_nexus_telegram)* diff --git a/docs/archive/memory/raw/mem_nexus_testing.md b/docs/archive/memory/raw/mem_nexus_testing.md new file mode 100644 index 00000000..a2f7902a --- /dev/null +++ b/docs/archive/memory/raw/mem_nexus_testing.md @@ -0,0 +1,13 @@ +# Nexus Phase D 测试计划与流程 + +测试流程确认: 1)WSL本地基础测试→2)推到仓库→3)外网服务器正式测试。T1:心跳Redis写入验证, T2:WebSocket连接测试, T3:3层守护测试(kill Python→Supervisor重启), T4:Telegram推送测试, T5:install.php 5步流程测试。WSL环境正在安装中(sudo密码uzumaki)。 + +## 相关概念 + +- 测试 +- Phase D +- WSL +- 验证 + +--- +*源: agentmemory (mem_nexus_testing)* diff --git a/docs/archive/memory/raw/mem_phase_d_bugfixes.md b/docs/archive/memory/raw/mem_phase_d_bugfixes.md new file mode 100644 index 00000000..3dd360e0 --- /dev/null +++ b/docs/archive/memory/raw/mem_phase_d_bugfixes.md @@ -0,0 +1,16 @@ +# Nexus Phase D 前置修复 (2026-05-20): 12项关键修复全部完成 + +M9 Redis session store: verify_session()从返回None→完整Redis查询; _store_session()+logout(); session:{token} 8h滑动TTL. M12 server_service空壳: check_all_servers()现在真正调用Agent /health(httpx+Semaphore20); push_to_servers()委托给SyncService.batch_push(). TTL对齐: heartbeat_flush.py REDIS_KEY_EXPIRE 600→900. E5 命令注入防护: BLOCKED_COMMAND_PATTERNS + BLOCKED_SUDO_COMMANDS + _validate_command(). E4 Telegram httpx单例: 模块级_get_client()单例+close_telegram_client(). CORS可配置: config.py新增CORS_ORIGINS. A1 错误处理规范: 4处bare except:pass修复. I5 Nginx配置: deploy/nginx_172.31.170.47.conf. D2 告警通知UX: 浮动卡片栈+WebSocket自动重连+暗色模式. DB索引迁移: 001_indexes.sql 7个索引. conftest修复: SQLite in-memory+MySQL env var. Redis client日志修复. + +## 相关概念 + +- Nexus +- bugfix +- Phase-D +- engineering +- Redis session +- command injection +- TTL + +--- +*源: agentmemory (mem_phase_d_bugfixes)* diff --git a/docs/archive/memory/raw/mem_team_agenda.md b/docs/archive/memory/raw/mem_team_agenda.md new file mode 100644 index 00000000..5339c19b --- /dev/null +++ b/docs/archive/memory/raw/mem_team_agenda.md @@ -0,0 +1,21 @@ +# Nexus 关键决策汇总(非部门编制) + +> 原「团队讨论议程」已废弃虚拟部门分派;以下为仍有效的技术/产品决策摘要。 + +## 配置与架构 + +- **不可改**:`API_KEY`、`SECRET_KEY`、`DATABASE_URL`、`ENCRYPTION_KEY` +- **Agent 心跳**:60s;**Redis→MySQL**:10min;**配置三写**:安装向导 → config.json + `.env` + `settings` 表 +- **架构**:Clean Architecture 四层;心跳/告警/恢复流;3 层守护(Supervisor + self_monitor + health_monitor.sh) + +## 测试与交付 + +- **流程**:WSL 本地验证 → 推仓库 → 外网验证;**必须**浏览器验证关键 UI +- **前端**:Vue 3 + Vuetify SPA(`frontend/` → `web/app/`) + +## 历史待办(多数已在 2026-06 修复) + +- Go/No-Go 标准、回滚方案、install 迁移、安全审查 — 见 `docs/handoff-2026-06-01.md` 与 `docs/changelog/2026-06-01-*.md` + +--- +*2026-06-01 更新:移除管理组/工程部/测试部/产品部/设计部表述* diff --git a/docs/archive/memory/raw/mem_wsl_deployment.md b/docs/archive/memory/raw/mem_wsl_deployment.md new file mode 100644 index 00000000..7279ae3d --- /dev/null +++ b/docs/archive/memory/raw/mem_wsl_deployment.md @@ -0,0 +1,14 @@ +# Nexus WSL部署信息 (172.31.170.47) + +WSL部署目标172.31.170.47。路径/www/wwwroot/172.31.170.47。数据库nexus密码HX12tx4SWn9NftdT。WSL环境: Ubuntu 24.04, Python 3.12.3, MySQL 8.4.8, PHP 8.2.28, Nginx 1.26.3。Redis需安装启动, pip3需安装, Supervisor需安装配置。部署步骤: 1)apt install redis-server python3-pip supervisor 2)systemctl start redis 3)复制代码 4)pip3 install -r requirements.txt 5)部署Nginx+Supervisor配置 6)运行install.php 7)T1-T5测试。Nginx配置deploy/nginx_172.31.170.47.conf, Supervisor配置deploy/nexus.conf, 健康检查http://127.0.0.1:8600/health。 + +## 相关概念 + +- Nexus +- deployment +- WSL +- infrastructure +- 172.31.170.47 + +--- +*源: agentmemory (mem_wsl_deployment)* diff --git a/docs/archive/project/PROJECT_HANDOVER.md b/docs/archive/project/PROJECT_HANDOVER.md new file mode 100644 index 00000000..dca6b6af --- /dev/null +++ b/docs/archive/project/PROJECT_HANDOVER.md @@ -0,0 +1,320 @@ +# Nexus 6.0 项目交接文档 + +> **⚠️ 2026-05-23 起请以 SSOT 为准**: [`AI-HANDOFF`](docs/project/AI-HANDOFF-2026-05-23.md) · **全量标准** [`standards-transfer-package`](docs/project/standards-transfer-package.md) · 验收 [`development-acceptance-standard`](docs/project/development-acceptance-standard.md) +> 本文保留历史结构;端口模型、待办 backlog、验证清单、否决项均以 SSOT 为准。 + +## 项目概述 + +**项目名称**: Nexus 6.0 +**类型**: 2000+ 服务器运维管理平台 +**架构**: Clean Architecture (FastAPI + Async SQLAlchemy + Redis + WebSocket + Telegram) +**前端**: Tailwind CSS v4 + Alpine.js(18 个 HTML 页面,含 `alerts.html`) +**状态**: 核心代码 ~98% 完成;**尚未部署**;待办见 SSOT §4、验证见 SSOT §5 + +--- + +## 仓库信息 + +- **仓库地址**: Gitea (地址在 .env 中配置) +- **当前工作目录(Linux)**: `~/Nexus`(见 `docs/project/linux-dev-paths.md`) +- **Git分支**: 当前在 worktree 分支 + +--- + +## 技术栈 + +| 层级 | 技术 | +|------|------| +| 后端框架 | FastAPI (Python 3.9+) | +| 数据库 | MySQL 8.0 + Async SQLAlchemy 2.0 | +| 缓存 | Redis | +| 实时通信 | WebSocket | +| SSH连接 | asyncssh (连接池模式) | +| 告警推送 | Telegram Bot API | +| 前端 | Tailwind CSS v4 + Alpine.js | +| 进程守护 | Supervisor + Python自检 + Shell健康检查 | + +--- + +## 目录结构 + +``` +Nexus/ +├── server/ # Python FastAPI 后端 +│ ├── api/ # API路由层 (15个模块) +│ │ ├── agent.py # Agent心跳接收 +│ │ ├── assets.py # Platform/Node资产管理 +│ │ ├── auth.py # JWT登录/TOTP +│ │ ├── auth_jwt.py # JWT验证中间件 +│ │ ├── files.py # 文件浏览 +│ │ ├── install.py # 安装向导API +│ │ ├── push.py # 推送调度 +│ │ ├── scripts.py # 脚本库管理 +│ │ ├── servers.py # 服务器CRUD +│ │ ├── settings.py # 系统设置 +│ │ ├── sync_v2.py # 同步引擎v2 (4种模式) +│ │ ├── webssh.py # WebSSH终端 +│ │ └── websocket.py # WebSocket告警推送 +│ ├── application/ # 应用服务层 +│ │ └── services/ # 业务逻辑 (SyncEngineV2等) +│ ├── domain/ # 领域层 +│ │ └── models/ # SQLAlchemy ORM (14张表) +│ ├── infrastructure/ # 基础设施层 +│ │ ├── database/ # 数据库访问 (Repository模式) +│ │ ├── redis/ # Redis客户端 +│ │ ├── ssh/ # SSH连接池 (asyncssh) +│ │ └── telegram/ # Telegram推送 +│ ├── background/ # 后台任务 +│ │ ├── self_monitor.py # Python自检 (30s) +│ │ ├── heartbeat_flush.py # Redis→MySQL刷新 (10min) +│ │ ├── scheduler.py # 定时推送调度 +│ │ └── retry_worker.py # 失败重试队列 +│ └── config.py # Pydantic Settings +├── web/app/ # 前端页面 (15个HTML) +│ ├── install.html # 安装向导 (5步) +│ ├── login.html # JWT登录+TOTP +│ ├── index.html # 仪表盘 +│ ├── servers.html # 服务器管理 +│ ├── assets.html # Platform/Node管理 +│ ├── push.html # 文件推送 +│ ├── scripts.html # 脚本库 +│ ├── commands.html # 命令日志查询 +│ ├── files.html # 远程文件浏览 +│ ├── settings.html # 系统设置 +│ ├── audit.html # 审计日志 +│ ├── retries.html # 重试队列 +│ ├── terminal.html # WebSSH终端 +│ ├── api.js # 共享API模块 (JWT+Toast) +│ └── layout.js # 共享布局组件 +├── deploy/ # 部署配置 +│ ├── supervisor/ # Supervisor配置 +│ ├── nginx/ # Nginx配置 +│ └── db_backup.sh # 数据库备份脚本 +├── docs/ # 项目文档 +├── requirements.txt # Python依赖 +└── test_p1_p2.py # 验证测试脚本 +``` + +--- + +## 数据库模型 (14张表) + +| 表名 | 说明 | +|------|------| +| `admins` | 管理员用户 (JWT+TOTP支持) | +| `servers` | 服务器资产 (核心表) | +| `platforms` | 平台类型模板 | +| `nodes` | 资产节点树 | +| `sync_logs` | 同步操作日志 | +| `push_schedules` | 定时推送调度 | +| `push_retry_jobs` | 失败重试队列 | +| `audit_logs` | 审计日志 | +| `scripts` | 脚本库 | +| `script_executions` | 脚本执行记录 | +| `password_presets` | SSH密码预设 | +| `db_credentials` | 数据库凭据 | +| `ssh_sessions` | WebSSH会话 | +| `command_logs` | SSH命令日志 | +| `login_attempts` | 登录防暴破记录 | +| `settings` | 系统配置键值对 | + +--- + +## 核心功能清单 + +### P0 核心功能 (已完成) +- [x] JWT认证 + TOTP双因素 +- [x] 登录防暴破 (15分钟锁定) +- [x] Agent心跳上报 (Redis实时) +- [x] WebSocket实时告警推送 +- [x] Telegram告警通知 +- [x] 安装向导 (5步) + +### P1 功能 (已完成) +- [x] Platform/Node资产管理 +- [x] 服务器详情面板 (点击展开) +- [x] WebSSH终端 (xterm.js) +- [x] 推送页面增强 (逐服务器状态+进度条) +- [x] 脚本编辑+执行结果格式化 +- [x] 全局Toast通知系统 +- [x] 审计日志分页+过滤 +- [x] 重试队列手动操作 +- [x] 文件浏览 (面包屑导航) + +### P2 功能 (已完成) +- [x] 告警聚合防抖 (5分钟冷却) +- [x] 命令日志查询页面 +- [x] 服务器列表DB级分页 +- [x] Dashboard WebSocket实时刷新 +- [x] 移动端响应式适配 +- [x] 会话安全加固 (8小时超时) +- [x] 服务器凭据Fernet加密存储 +- [x] 凭据API脱敏 (password_set标记) +- [x] 数据库自动备份脚本 + +### Sync引擎v2 (4种模式) +- [x] S1: 文件同步 (rsync) +- [x] S2: 命令批量执行 (SSH) +- [x] S3: 配置推送 (sysctl) + 去重 + 回滚 +- [x] S4: SFTP文件传输 + +--- + +## 关键Bug修复记录 + +| 任务ID | 问题 | 修复内容 | +|--------|------|----------| +| #81 | DateTime默认求值BUG | 添加`_utcnow()`可调用函数,替换所有`default=datetime.now()` | +| #82 | session.py logger未定义 | 添加`import logging`和`logger`定义,移除@property死代码 | +| #83 | 审计日志缺失 | 验证所有CUD端点都有审计记录 | +| #84 | 模型缺失索引 | 添加7个索引(login_attempts, audit_logs等) | + +--- + +## 环境配置 + +### 必需环境变量 (.env文件) +```bash +# 数据库 +DATABASE_URL=mysql+aiomysql://user:pass@localhost/nexus + +# 安全密钥 (安装时生成) +SECRET_KEY=xxx +API_KEY=xxx +ENCRYPTION_KEY=xxx + +# Redis +REDIS_URL=redis://localhost:6379/0 + +# Telegram告警 (可选) +TELEGRAM_BOT_TOKEN=xxx +TELEGRAM_CHAT_ID=xxx + +# 告警阈值 +CPU_ALERT_THRESHOLD=80 +MEM_ALERT_THRESHOLD=80 +DISK_ALERT_THRESHOLD=80 +``` + +### 启动模式 +- **安装模式** (无.env): 仅 `/api/install/` + 静态文件可用 +- **正常模式** (有.env): 完整初始化 DB/Redis/后台任务 + +--- + +## 部署步骤 + +### 1. 安装依赖 +```bash +pip install -r requirements.txt +``` + +### 2. 运行安装向导 +访问 `http://localhost:8000/app/install.html` +- Step 1: 数据库配置 +- Step 2: Redis配置 +- Step 3: 安全密钥生成 +- Step 4: Telegram配置 (可选) +- Step 5: 创建管理员账号 + +### 3. 启动服务 (Supervisor) +```bash +# 复制配置 +cp deploy/supervisor/nexus.conf /etc/supervisor/conf.d/ +supervisorctl reread +supervisorctl update +supervisorctl start nexus +``` + +### 4. 验证运行 +```bash +# 健康检查 +curl http://localhost:8000/health + +# 运行测试 +python test_p1_p2.py +``` + +--- + +## 团队分工建议 + +| 角色 | 职责 | 相关文件 | +|------|------|----------| +| **后端开发** | API开发、业务逻辑 | `server/api/*.py`, `server/application/services/*.py` | +| **前端开发** | UI页面、交互逻辑 | `web/app/*.html`, `web/app/api.js`, `web/app/layout.js` | +| **DevOps** | 部署、监控、备份 | `deploy/`, `supervisor/`, `nginx/` | +| **DBA** | 数据库优化、迁移 | `server/domain/models/`, `server/infrastructure/database/` | +| **安全** | 审计、加密、认证 | `server/api/auth*.py`, 凭据加密逻辑 | + +--- + +## 监控与告警 + +### 3层守护机制 +1. **Supervisor**: Python崩溃自动重启 +2. **Python自检** (`self_monitor.py`): 30秒检查Redis/MySQL/WebSocket +3. **Shell健康检查** (`health_monitor.sh`): 1分钟HTTP检查,3次失败重启 + +### 告警通道 +- WebSocket → 前端实时通知 +- Telegram → 手机推送 + +### 关键指标 +- Agent心跳间隔: 60秒 +- Redis→MySQL刷新: 10分钟 +- 会话超时: 8小时 +- 告警冷却: 5分钟 + +--- + +## 测试验证 + +### 已完成的测试 +```bash +# 模块导入测试 (19/19通过) +python -c "import server.domain.models; ..." + +# 功能测试 (7/7通过) +python test_p1_p2.py +# - JWT updated_at claim +# - JWT password invalidation +# - Alert dedup cooldown +# - Server repo pagination +# - Config dedup command +# - Server dict no password +# - Admin.updated_at field +``` + +--- + +## 注意事项 + +### ⚠️ 安全相关 +1. **API_KEY**: 禁止修改,用于Agent认证 +2. **SECRET_KEY**: 禁止修改,用于JWT签名 +3. **ENCRYPTION_KEY**: 禁止修改,用于凭据加密 +4. 生产环境必须修改默认密码 + +### ⚠️ 数据库迁移 +首次启动会自动执行 `_apply_migrations()`: +- 添加 `admins.updated_at` 列 +- 创建7个索引 (如果不存在) + +### ⚠️ 文件权限 +- SSH私钥文件需要正确权限 (600) +- 日志目录需要写权限 + +--- + +## 联系方式 + +- 项目文档: `docs/` +- 配置文件: `.env` (不提交到Git) +- 测试脚本: `test_p1_p2.py` + +--- + +**交接日期**: 2026/05/22 +**代码状态**: ✅ 开发完成,待部署测试 +**Git提交**: 建议提交当前worktree更改到主分支 diff --git a/docs/archive/project/line-walk-audit-standard.md b/docs/archive/project/line-walk-audit-standard.md new file mode 100644 index 00000000..feac396c --- /dev/null +++ b/docs/archive/project/line-walk-audit-standard.md @@ -0,0 +1,184 @@ +# Nexus 逐行走读审计标准(SSOT) + +**版本**: 2026-05-22 +**状态**: 生效 +**Cursor 规则**: ` ` +**关联计划**: `.cursor/plans/nexus_全面代码审计_a33e6fc2.plan.md` §11 + +与 **Phase 1(攻击面抽查)** 严格区分。未满足本文 DoD 的文件不得标记「逐行完成 ✓」。 + +--- + +## 1. 术语 + +| 术语 | 定义 | +|------|------| +| **走读** | 对单文件按行号顺序阅读源码,并对规则命中点做上下文分析 | +| **触达** | 仅 grep/打开文件,未满足走读 DoD | +| **规则命中** | §4 规则表在某行产生的匹配,记入列表 H | +| **Closure** | 对一条命中的判定:`SAFE` 或 `FINDING`(含行号、理由) | +| **DoD** | §6 单文件完成定义 | + +--- + +## 2. 适用范围 + +| 包含 | 排除 | +|------|------| +| `server/**/*.py` | `docs/**`(仅 §8 密钥扫描) | +| `web/app/*.{html,js}` | `frontend/node_modules/**` | +| `web/agent/**` | 第三方 minified | +| `scripts/**`、`deploy/**`、`tests/**` | 图片、字体 | + +--- + +## 3. 单文件走读流程(8 步) + +``` +Step 1 登记:path、语言、行数 N +Step 2 全文 Read 至 EOF(分段 limit≈200;末段 offset ≥ N-5) +Step 3 规则扫描 → 命中列表 H +Step 4 对 H 每条 Read ±15 行 → Closure 表 +Step 5 API/WS:入口表(方法、路径、鉴权) +Step 6 外部输入 → sink +Step 7 八类核对(§7) +Step 8 DoD(§6)→「逐行完成 ✓」 +``` + +**单会话上限**:最多 **5 个文件**。 + +--- + +## 4. 规则表(100% Closure) + +### 4.1 Python + +| ID | rg 示意 | 审查要点 | +|----|---------|----------| +| PY-01 | `f"…{…}…` + exec/shell/ssh/sysctl/curl/sudo/bash | 是否经 shell?**整段** `key=value` 是否安全引用? | +| PY-02 | `subprocess.` / `create_subprocess_shell` / `os.system` | shell=True?命令来源? | +| PY-03 | `exec_ssh_command` / `conn.run(` | 远程 shell 拼接? | +| PY-04 | `text(` / `execute(` / `raw(` | SQL 注入? | +| PY-05 | `@router.` / `websocket` | JWT / API Key? | +| PY-06 | 字面量 `API_KEY` / `SECRET` / `password` | 硬编码密钥? | +| PY-07 | `pickle` / `yaml.load` / `eval(` / `exec(` | 危险反序列化/执行 | +| PY-08 | API Key `==` / `!=` | 应用 `secrets.compare_digest` | +| PY-09 | `except: pass` | 吞安全异常? | +| PY-10 | `open(` / 写路径 | 路径遍历? | + +### 4.2 前端 + +| ID | rg 示意 | 审查要点 | +|----|---------|----------| +| FE-01 | `innerHTML` / `outerHTML` | 每个 `${}` 是否 `esc()` | +| FE-02 | `localStorage` | JWT/密钥? | +| FE-03 | `WebSocket` + `token` | Token 在 URL? | +| FE-04 | 裸 `fetch` | 绕过 `apiFetch`? | +| FE-05 | `onclick=…${` | JS 属性注入? | +| FE-06 | `cdn.` / ` +""" + + # Insert data script right before the docApp function script + placeholder = "// __DATA_PLACEHOLDER__ will be replaced by build_html.py" + if placeholder in template: + html = template.replace(placeholder, f"// Data injected by build_html.py\nconst NAV_GROUPS = {nav_json};\nconst CONTENT_MAP = {content_json};") + else: + # Fallback: insert before closing + html = template.replace("", data_script + "") + + return html + + +def main(): + import sys + sys.stdout.reconfigure(encoding='utf-8') + + print("Scanning docs/...") + files = collect_all_files() + md_count = sum(1 for f in files if f[0].endswith('.md')) + other_count = len(files) - md_count + print(f" Found {md_count} markdown + {other_count} other = {len(files)} total files") + + print("Grouping...") + groups = group_files(files) + for name, info in groups: + print(f" {info['icon']} {name}: {len(info['files'])} files") + + print("Reading content & building data...") + nav_groups, content_map = build_data(groups) + + print("Generating HTML...") + html = generate(nav_groups, content_map) + + with open(OUTPUT, 'w', encoding='utf-8') as f: + f.write(html) + + size_mb = os.path.getsize(OUTPUT) / 1024 / 1024 + print(f"\nDone: {OUTPUT}") + print(f"Size: {size_mb:.1f} MB") + + +if __name__ == '__main__': + main() diff --git a/docs/changelog/2026-05-22-alert-push-dashboard.md b/docs/changelog/2026-05-22-alert-push-dashboard.md new file mode 100644 index 00000000..5add87d2 --- /dev/null +++ b/docs/changelog/2026-05-22-alert-push-dashboard.md @@ -0,0 +1,29 @@ +# Changelog — 告警推送:浏览器仅状态 + Telegram 通知 + +**日期**: 2026-05-22 +**SSOT**: `docs/project/alert-push-policy.md` + +## 产品决策 + +| 通道 | 行为 | +|------|------| +| Telegram | 告警/恢复/系统主动通知(5 分钟防抖) | +| 浏览器 | 只显示统计与运行状态;无声音、无告警列表 | +| WebSocket | 触发 `loadDashboard()`,不触发 `loadSyncs()` | + +## 代码 + +- `web/app/index.html` — 运行状态面板;移除 `_playAlertSound`、实时告警横幅 +- `server/api/websocket.py` — 文档注释对齐 +- `deploy/nginx_https.conf` — `/ws/` 独立 access_log,不记录 query(JWT 不进日志) + +## 部署 + +生产 Nginx 若已上线,合并 `location /ws/` 的 `log_format nexus_ws` 与 `access_log ... nexus_ws` 后 reload。 + +## 阈值对齐(第 5 项) + +- 中心:`settings` / MySQL `cpu_alert_threshold` 等 +- 子机:`config.json` → `alert_thresholds`(见 `config.example.json`) +- 修改中心阈值后,请同步更新各 Agent 的 `alert_thresholds` + diff --git a/docs/changelog/2026-05-22-audit-p2-fixes.md b/docs/changelog/2026-05-22-audit-p2-fixes.md new file mode 100644 index 00000000..34d96c46 --- /dev/null +++ b/docs/changelog/2026-05-22-audit-p2-fixes.md @@ -0,0 +1,46 @@ +# Phase 2 审计待办修复(P1/P2 批次) + +**日期**: 2026-05-22 + +## 已修复 + +| ID | 说明 | 变更 | +|----|------|------| +| F2h-03 | Agent `/exec/kill` 不得对未登记 pid 调用 `os.kill` | `web/agent/agent.py` 仅杀 `_running_procs` 内进程,否则 404 | +| F2h-04 | `/exec/wait` 超时未清理 `_running_procs` | 超时分支 `pop` + `kill`/`wait` | +| F2a-08 | 安装后安装向导仍可 POST | `install.py` 统一 `_reject_if_installed()` → 403(除 `GET /status`) | +| F2g-03 | `apply_update` 中 `json.loads(server_ids)` 无防护 | `_parse_server_ids_json()` | +| F2i-01 | `update_status` 无记录时 flush 异常 | 返回 `Optional`;`flush_to_mysql` 跳过并出队 | +| F2i-02 | 死模型 `AgentExec` | 已从 `schemas.py` 删除 | +| F2g-05 / F2h-01 | `credential_id` 未贯通 | 模型列 + schema + API + 存库 + retry 继承 | +| F2b-05 | `duration_seconds` 用 `.seconds` | `sync_engine_v2.py` → `total_seconds()` | +| F2a-13 | Dashboard `stats.online` 用 MySQL | `servers.py` 优先 Redis `heartbeat:*` | +| F2g-02(部分) | 脚本回调无限流 | `script_callback_rate.py` + `agent.py` 429 | + +## 第二批(2026-05-22 续) + +| ID | 说明 | 变更 | +|----|------|------| +| F2d-03 | 安装模式仍可连 WS | `main.py` 安装模式 `/ws/` → 503 | +| F2d-04 | MySQL 异常原文进 Telegram | `self_monitor.py` 通用文案 | +| F2g-02 | 回调可能 HTTP | `master_callback_url()` 非 localhost 强制 HTTPS | +| F2g-06 / F2h-02 | script-callback 无限流 | 见上(已完成) | +| F2c-05 | 迁移失败仅 log | `migrations.py` 非重复列错误 re-raise | +| F2f-05 | CREATE DATABASE f-string | `bootstrap_database.py` 库名白名单 | +| F2f-07 | wsl 脚本硬编码路径 | `wsl_mcp_mysql.sh` 相对仓库根目录 | +| F2g-04(文档) | 重试新 id | API `parent_execution_id` + SSOT 文档 | +| F2f-01 / F2g-01(文档) | shell 执行面 | SSOT §4.1 + Agent WARNING 日志 | +| — | 前端 credential | `scripts.html` DB 凭据下拉 | +| F2f-06(部分) | P0 回归测试 | `tests/test_security_unit.py` | + +## 未改(需架构/运维) + +- F2f-01 / F2g-01:`subprocess` shell 执行(文档/隔离) +- F2g-04:重试生成新 execution id(产品设计) +- F2a-10 / F2d-02:全局 JWT 收紧 +- F2e-03 / F2a-18:告警 WS — **已讨论接受**,不做换票 +- F2f-04:轮换曾泄露 API Key(运维) + +## 部署注意 + +启动时会执行 schema migration:`script_executions.credential_id`。已有库无需手工 SQL。 diff --git a/docs/changelog/2026-05-22-env-mysql-remote-config.md b/docs/changelog/2026-05-22-env-mysql-remote-config.md new file mode 100644 index 00000000..d6d3b66b --- /dev/null +++ b/docs/changelog/2026-05-22-env-mysql-remote-config.md @@ -0,0 +1,37 @@ +# Changelog — 本地 .env 远程 MySQL 配置 + +**日期**: 2026-05-22 +**类型**: 配置(仅本机,未提交 git) + +## 变更摘要 + +为 worktree 创建项目根 `.env`,指向远程 MySQL `172.31.170.47`,并运行 `scripts/sync_mysql_mcp_env.py` 写入 MCP 用 `MYSQL_*` 变量。 + +## 配置项(不含密码) + +| 项 | 值 | +|----|-----| +| `NEXUS_DATABASE_URL` | `mysql+aiomysql://Nexus@172.31.170.47:3306/nexus` | +| `MYSQL_READONLY` | `true` | +| `MYSQL_DATABASE_ALLOWLIST` | `nexus` | +| 本地密钥 | 已生成 `NEXUS_SECRET_KEY` / `API_KEY` / `ENCRYPTION_KEY`(供后端启动) | + +## 涉及文件 + +- `.env`(gitignore,仅本机) +- `scripts/sync_mysql_mcp_env.py`(已执行) + +## 连通性 + +从当前开发机测试:**无法连接** `172.31.170.47:3306`(`Can't connect to MySQL server`)。 +`172.31.x` 多为内网/VPC 地址,需 VPN、跳板机或同网段环境后 MCP/后端方可连库。 + +## 用户操作 + +1. 确保能访问 `172.31.170.47:3306`(防火墙/安全组放行本机 IP) +2. Cursor → Settings → MCP → 启用 `mysql-mcp` → Reload Window +3. 对话中测试 `test_connection` + +## 安全提醒 + +数据库密码仅存在于本地 `.env`;若在聊天中暴露,建议在数据库侧轮换密码并重新执行 `sync_mysql_mcp_env.py`。 diff --git a/docs/changelog/2026-05-22-frontend-vendor-selfhost.md b/docs/changelog/2026-05-22-frontend-vendor-selfhost.md new file mode 100644 index 00000000..2441bf74 --- /dev/null +++ b/docs/changelog/2026-05-22-frontend-vendor-selfhost.md @@ -0,0 +1,26 @@ +# 前端 CDN 自托管(F2e-06) + +**日期**: 2026-05-22 + +## 变更 + +- 新增 `web/app/vendor/`:Tailwind Browser 4.0.6、Alpine 3.14.8、xterm 5.3.0、qrious 4.0.2 等固定版本。 +- 新增 `scripts/sync_frontend_vendor.py`:下载 vendor 并批量将 16 个 HTML 的 CDN URL 替换为 `/app/vendor/*`。 +- `web/app/vendor/manifest.json`:记录版本、源 URL、`sha384` 完整性(供日后 Subresource Integrity 或审计)。 + +## 升级 vendor + +```bash +# 1. 修改 scripts/sync_frontend_vendor.py 中 VENDOR_MANIFEST 版本 +# 2. 执行 +python scripts/sync_frontend_vendor.py +# 3. 提交 vendor/ + HTML + manifest.json +``` + +## 验证 + +```bash +pytest tests/test_security_unit.py::test_html_pages_do_not_load_external_cdn -q +``` + +浏览器 Network 面板应无 `cdn.jsdelivr.net` 请求。 diff --git a/docs/changelog/2026-05-22-global-jwt-middleware.md b/docs/changelog/2026-05-22-global-jwt-middleware.md new file mode 100644 index 00000000..20b6858a --- /dev/null +++ b/docs/changelog/2026-05-22-global-jwt-middleware.md @@ -0,0 +1,20 @@ +# 全局 JWT 中间件(F2a-10 / F2d-02) + +**日期**: 2026-05-22 + +## 变更 + +- `server/api/auth_jwt.py`:新增 `JwtAuthMiddleware`,对所有 `/api/*` 请求在路由处理前校验 Bearer JWT;公开前缀见 `PUBLIC_PREFIXES`(含 `/api/install/`、`/api/auth/logout`)。 +- `server/main.py`:在 `DbSessionMiddleware` 内层注册中间件(保证 `request.state.db` 可用)。 +- `get_current_admin`:复用 `request.state.admin`,避免重复查库。 +- 安装模式(无 `.env`)跳过 JWT 中间件,由 `InstallModeMiddleware` 返回 503。 + +## 纵深防御 + +路由层 `Depends(get_current_admin)` 保留;即使新路由漏挂 Depends,中间件仍会返回 401。 + +## 验证 + +```bash +pytest tests/test_step0_infrastructure.py::TestJWTAuth tests/test_nexus_v6.py::TestJWTAuth tests/test_security_unit.py -q +``` diff --git a/docs/changelog/2026-05-22-implementation-standards-rule.md b/docs/changelog/2026-05-22-implementation-standards-rule.md new file mode 100644 index 00000000..a94fd2be --- /dev/null +++ b/docs/changelog/2026-05-22-implementation-standards-rule.md @@ -0,0 +1,38 @@ +# Changelog — 完美实现与文档纪律全局规则 + +**日期**: 2026-05-22 +**类型**: 流程 / 规范 +**动机**: 将「完美实现、禁止降级/妥协/技术债、文档先行、每次修改留 changelog」固化为项目全局约束,供 Cursor 与所有 Agent 会话强制执行。 + +## 变更摘要 + +1. 新增 Cursor 全局规则 `.cursor/rules/perfect-implementation.mdc`(`alwaysApply: true`) +2. 在 `CLAUDE.md`、`AGENTS.md` 增加「实现原则」章节,与规则文件交叉引用 +3. 明确文档路径与执行顺序:设计文档 → 技术文档 → 实现 → changelog + +## 涉及文件 + +| 文件 | 操作 | +|------|------| +| `.cursor/rules/perfect-implementation.mdc` | 新增 | +| `CLAUDE.md` | 增补实现原则与文档纪律 | +| `AGENTS.md` | 增补实现原则表格 | +| `docs/changelog/2026-05-22-implementation-standards-rule.md` | 新增(本文件) | + +## 规则要点 + +- 禁止:为实现而实现、降级实现、妥协实现、留技术债 +- 无法完美实现:必须停下与用户沟通后才能换方案 +- 设计前:`docs/design/specs/YYYY-MM-DD-*-design.md` +- 开发前:`docs/design/plans/` 或 `docs/reports/` 技术文档;缺文档模块改动时补全 +- 每次修改:`docs/changelog/YYYY-MM-DD-*.md` + +## 验证 + +- [x] `.cursor/rules/perfect-implementation.mdc` 存在且 `alwaysApply: true` +- [x] `CLAUDE.md` / `AGENTS.md` 已同步 +- [ ] 主仓库 `Nexus/CLAUDE.md` 若与 worktree 分叉,合并时需同步本节 + +## 部署/迁移 + +无需重启服务;规则在下次 Cursor 会话中自动生效。 diff --git a/docs/changelog/2026-05-22-line-walk-audit-standard.md b/docs/changelog/2026-05-22-line-walk-audit-standard.md new file mode 100644 index 00000000..ed7559da --- /dev/null +++ b/docs/changelog/2026-05-22-line-walk-audit-standard.md @@ -0,0 +1,42 @@ +# Changelog — 逐行走读审计标准与 Cursor 规则 + +**日期**: 2026-05-22 +**类型**: 文档 / 流程 + +## 变更摘要 + +1. 新增 `docs/project/line-walk-audit-standard.md` — Phase 2 逐行走读 SSOT(8 步、规则表、DoD、八类、批次模板) +2. 新增 `.cursor/rules/audit-line-review.mdc` — 审计任务时强制 closure 流程,禁止抽查冒充全量 +3. 计划 `.cursor/plans/nexus_全面代码审计_a33e6fc2.plan.md` §11 与上述文件对齐 + +## 背景 + +Phase 1 攻击面抽查漏报 `sync_engine_v2.py` sysctl 命令注入(S-19/S-20)。用户要求可复查的逐行走读标准。 + +## 涉及文件 + +| 文件 | 操作 | +|------|------| +| `docs/project/line-walk-audit-standard.md` | 新增 | +| `.cursor/rules/audit-line-review.mdc` | 新增 | +| `docs/changelog/2026-05-22-line-walk-audit-standard.md` | 新增 | + +## 使用方式 + +- 审计:Agent 模式 + Prompt 见标准 §11 +- 自动约束:编辑 `server/`、`web/app/` 等路径时加载 `audit-line-review.mdc` +- Phase 2b 报告:`docs/reports/audit-phase-2b-line-walk.md`(`server/application/services/`) +- Phase 2a 报告:`docs/reports/audit-phase-2a-line-walk.md`(`server/api/`,16 文件) +- 安全修复:`docs/changelog/2026-05-22-security-fixes-p0.md`、`2026-05-22-security-fixes-p1.md` +- Phase 2c 报告:`docs/reports/audit-phase-2c-line-walk.md`(infrastructure 21 文件) +- Phase 2d 报告:`docs/reports/audit-phase-2d-line-walk.md`(main + config + background) +- Phase 2e 报告:`docs/reports/audit-phase-2e-line-walk.md`(`web/app/` 16 文件) +- Phase 2f 报告:`docs/reports/audit-phase-2f-line-walk.md`(agent + tests + scripts,20 文件) +- Phase 2g 报告:`docs/reports/audit-phase-2g-line-walk.md`(脚本执行平台 5 文件,+6 FINDING) +- Phase 2h 报告:`docs/reports/audit-phase-2h-line-walk.md`(API/前端/子机 Agent 5 文件,+5 FINDING) +- Phase 2i/2j:`audit-phase-2i-line-walk.md`、`audit-phase-2j-line-walk.md` +- **Phase 2 闭环**:`docs/reports/audit-phase-2-walk-closure.md` — 可进入一并修复 +- **全量汇总表**:`docs/reports/audit-phase-2-findings-matrix.md`(50 条 FINDING + 修复队列) +- 汇总表队列修复:`docs/changelog/2026-05-22-security-fixes-p2-queue.md`(F2f-04、F2e-01/02/07、F2f-02/03、F2a-12) +- **脚本批量执行平台**:`docs/project/script-execution.md`(SSOT) +- 交付记录:`docs/changelog/2026-05-22-script-execution-platform.md`(含分批 / 回调子文档索引) diff --git a/docs/changelog/2026-05-22-local-mysql-full-access-node20-fix.md b/docs/changelog/2026-05-22-local-mysql-full-access-node20-fix.md new file mode 100644 index 00000000..6c7decd0 --- /dev/null +++ b/docs/changelog/2026-05-22-local-mysql-full-access-node20-fix.md @@ -0,0 +1,48 @@ +# Changelog — 本地库最大权限 + WSL Node 20 安装修复 + +**日期**: 2026-05-22 +**类型**: 配置 / Bugfix / 工具链 + +## 变更摘要 + +1. 修复 `scripts/wsl_install_node20.sh`:清理损坏代理、`curl --noproxy`、npmmirror 回退、`.cache` 离线安装 +2. 新增 `scripts/cache-node20-windows.ps1`(WSL DNS 失败时由 Windows 下载) +3. `.env` 设置 `MYSQL_READONLY=false`;`sync_mysql_mcp_env.py` 增加 `--writable` / `--readonly` +4. 新增 `scripts/grant_nexus_local.sql`(MySQL 侧 GRANT ALL) +5. Node 20 已安装至 WSL `~/.local/node-v20.19.2/`;MCP 启动显示 `readonly: false` + +## 涉及文件 + +| 文件 | 操作 | +|------|------| +| `scripts/wsl_install_node20.sh` | 代理修复 + 镜像 + 离线 cache | +| `scripts/cache-node20-windows.ps1` | 新增 | +| `scripts/sync_mysql_mcp_env.py` | `--writable` / `--readonly` | +| `scripts/grant_nexus_local.sql` | 新增 | +| `.env` | `MYSQL_READONLY=false` | +| `.env.example` | 注释本地可写 | +| `.gitignore` | 忽略 `.cache/` | +| `docs/project/mysql-mcp-setup.md` | 更新 | + +## 用户待办 + +```bash +# 1. Node 20(若 WSL curl 失败,先在 Windows 执行) +pwsh scripts/cache-node20-windows.ps1 +bash scripts/wsl_install_node20.sh + +# 2. MCP 可写同步 +python3 scripts/sync_mysql_mcp_env.py --writable + +# 3. MySQL 授权(需 root,MySQL 服务需运行) +mysql -h 172.31.170.47 -u root -p < scripts/grant_nexus_local.sql + +# 4. Cursor Reload Window +``` + +## 验证(2026-05-22 复测) + +- [x] WSL `~/.local/node-v20.19.2/bin/node --version` → v20.19.2 +- [x] `wsl_mcp_mysql.sh` → `[MySQL MCP] Connected!` / `readonly: false` +- [x] `wsl_check_mysql.py` → 16 张表 +- [x] `SHOW GRANTS` → `GRANT ALL PRIVILEGES ON nexus.* TO Nexus@%`(库级权限已具备) diff --git a/docs/changelog/2026-05-22-mysql-mcp-node20-fix.md b/docs/changelog/2026-05-22-mysql-mcp-node20-fix.md new file mode 100644 index 00000000..4e417060 --- /dev/null +++ b/docs/changelog/2026-05-22-mysql-mcp-node20-fix.md @@ -0,0 +1,35 @@ +# Changelog — 修复 mysql-mcp Node 18 SyntaxError + +**日期**: 2026-05-22 +**类型**: Bugfix / MCP + +## 问题 + +``` +SyntaxError: Unexpected token 'with' +import packageJson from '../package.json' with { type: 'json' }; +``` + +WSL 默认 Node **v18.19.1**,不满足 `@yclenove/mysql-mcp-server` 对 Node **≥20** 的要求。 + +## 修复 + +1. 新增 `scripts/wsl_mcp_mysql.sh` — 优先 WSL Node 20,回退 Windows `C:\nvm4w\nodejs\node.exe` v22 +2. 更新 `.cursor/mcp.json` 调用该脚本 +3. 新增 `scripts/wsl_install_node20.sh` — 用户级安装 Node v20.19.2 到 `~/.local/` + +## 用户操作 + +```bash +# WSL 内(推荐,可同时连 172.31.x MySQL) +bash scripts/wsl_install_node20.sh +``` + +然后 **Cursor → Reload Window** 重新连接 MCP。 + +## 涉及文件 + +- `.cursor/mcp.json` +- `scripts/wsl_mcp_mysql.sh` +- `scripts/wsl_install_node20.sh` +- `docs/project/mysql-mcp-setup.md` diff --git a/docs/changelog/2026-05-22-script-exec-batching.md b/docs/changelog/2026-05-22-script-exec-batching.md new file mode 100644 index 00000000..22e239b0 --- /dev/null +++ b/docs/changelog/2026-05-22-script-exec-batching.md @@ -0,0 +1,35 @@ +# Changelog — 脚本执行内置分批 + +**日期**: 2026-05-22 +**类型**: 功能 +**上级索引**: [2026-05-22-script-execution-platform.md](./2026-05-22-script-execution-platform.md) +**SSOT**: [docs/project/script-execution.md](../project/script-execution.md) + +## 变更摘要 + +一次可选全站服务器;超过 `batch_size` 时后端按批顺序执行,前端多轮 `POST /exec` 合并为一条页顶状态卡。 + +## 配置 + +| 项 | 默认 | 位置 | +|----|------|------| +| `NEXUS_SCRIPT_EXEC_BATCH_SIZE` | 50 | `.env` / `server/config.py` | +| `NEXUS_SCRIPT_EXEC_CONCURRENCY` | 10 | 同上 | + +## API / 前端 + +- `GET /api/scripts/exec-config` → `{ batch_size, concurrency }` +- `ScriptService.execute_command` 按批执行,批内 `Semaphore(concurrency)` +- `scripts.html`:全选、分批提示、「第 x/y 批」、长任务勾选 + +## 涉及文件 + +- `server/config.py` +- `server/application/services/script_service.py` +- `server/api/scripts.py` +- `web/app/scripts.html` + +## 用法 + +- 一次可全选所有服务器;超过 50 台自动分多批 +- 长任务:`timeout` 仅限制点火命令(建议 60~120s) diff --git a/docs/changelog/2026-05-22-script-execution-platform.md b/docs/changelog/2026-05-22-script-execution-platform.md new file mode 100644 index 00000000..d6185fdc --- /dev/null +++ b/docs/changelog/2026-05-22-script-execution-platform.md @@ -0,0 +1,102 @@ +# Changelog — 脚本批量执行平台(完整交付) + +**日期**: 2026-05-22 +**类型**: 功能 / 架构 +**SSOT**: `docs/project/script-execution.md` + +## 变更摘要 + +在原有「脚本库 + 单批 exec」基础上,交付**生产级批量执行平台**: + +1. **内置分批** — 超 50 台自动多批 `POST /exec`,前端合并为一条状态卡(见 `2026-05-22-script-exec-batching.md`) +2. **长任务** — nohup + 子机 curl 回调 + 页顶状态 / 远程 tail 日志(见 `2026-05-22-script-job-callback.md`) +3. **Redis 热路径 + MySQL 分批落库** — 减轻 DB;读优先 Redis +4. **短任务可停止** — Agent `/exec` + `/exec/wait` + `/exec/kill` +5. **执行历史页** — 列表、进度、操作按钮、自动刷新 +6. **卡住治理** — 人工标记 + 单台/整单自动检测 + 审计 + +## 背景与决策 + +| 决策 | 选择 | 原因 | +|------|------|------| +| 进度查询 | JWT 轮询 `GET /executions/{id}` | 用户明确不要 WS 换票、不要独立 async exec API | +| 执行态存储 | Redis 2h TTL | 前端实时;MySQL 只存历史 | +| MySQL 写入 | 60s 批量最多 50 条 | 降低写压力;审计仍即时 | +| 卡住阈值等 | 代码常量 | 用户要求不写 `.env` | +| 分批大小 | `.env` 可配 | 与安装向导/运维调优一致 | + +## 数据流(简图) + +``` +POST /exec → ScriptService → Redis(script_exec:id) → 前端轮询 + ↓ Agent (短/长) +长任务结束 → curl → /api/agent/script-callback → Redis 更新 +每 60s → script_execution_flush → MySQL script_executions +audit_logs ← 即时写入(各操作 action) +``` + +## API 与页面(新增/扩展) + +| 类型 | 项 | +|------|-----| +| API | `GET /scripts/executions`, `GET .../{id}?fetch_logs=`, `POST .../stop`, `retry`, `mark-stuck`, `POST /agent/script-callback` | +| 页面 | `web/app/script-executions.html` | +| 页面增强 | `scripts.html` 页顶状态条、执行历史入口;`audit.html` 脚本 action 筛选 | +| Agent | `web/agent/agent.py` — `/exec` wait 参数、`/exec/wait`、`/exec/kill` | +| 后台 | `server/background/script_execution_flush.py` | + +## 涉及文件 + +### 新增 + +| 文件 | +|------| +| `server/infrastructure/redis/script_execution_store.py` | +| `server/background/script_execution_flush.py` | +| `server/application/services/script_jobs.py` | +| `server/application/services/script_job_callback.py` | +| `web/app/script-executions.html` | +| `docs/project/script-execution.md` | +| `docs/changelog/2026-05-22-script-execution-platform.md` | + +### 修改 + +| 文件 | 要点 | +|------|------| +| `server/application/services/script_service.py` | 分批、长/短任务、stop/retry/list、Agent kill | +| `server/api/scripts.py` | executions 路由 | +| `server/api/agent.py` | script-callback | +| `server/infrastructure/database/script_repo.py` | `list_recent` | +| `server/main.py` | 注册 flush loop | +| `server/config.py` | `SCRIPT_EXEC_*` | +| `web/app/scripts.html` | 状态条、分批、长任务 | +| `web/app/layout.js` | 执行历史菜单 | +| `web/app/audit.html` | action 筛选 | +| `web/agent/agent.py` | exec/wait/kill | + +## 配置 + +| 项 | 位置 | 默认 | +|----|------|------| +| `NEXUS_SCRIPT_EXEC_BATCH_SIZE` | `.env` / `config.py` | 50 | +| `NEXUS_SCRIPT_EXEC_CONCURRENCY` | `.env` / `config.py` | 10 | +| `NEXUS_API_BASE_URL` | `.env` | 长任务必填 | +| `REDIS_TTL_SECONDS` 等 | `script_execution_store.py` | 见 SSOT §1 | + +## 部署注意 + +1. 主站重启后 `script_execution_flush` 随 primary worker 启动 +2. 子机需部署新版 `web/agent/agent.py` 才能短任务中途停止 +3. 长任务必须配置子机可访问的 `NEXUS_API_BASE_URL` + +## 测试状态 + +| ID | 项 | 状态 | +|----|-----|------| +| T-SE1~T-SE8 | 见 `docs/project/script-execution.md` §11 | ⏳ 待 WSL/外网 | + +## 关联文档 + +- 技术 SSOT: `docs/project/script-execution.md` +- 分批: `docs/changelog/2026-05-22-script-exec-batching.md` +- 回调与 Redis: `docs/changelog/2026-05-22-script-job-callback.md` diff --git a/docs/changelog/2026-05-22-script-job-callback.md b/docs/changelog/2026-05-22-script-job-callback.md new file mode 100644 index 00000000..ecb01c1b --- /dev/null +++ b/docs/changelog/2026-05-22-script-job-callback.md @@ -0,0 +1,80 @@ +# Changelog — 长任务回调与 Redis 执行态 + +**日期**: 2026-05-22 +**类型**: 功能 +**上级索引**: [2026-05-22-script-execution-platform.md](./2026-05-22-script-execution-platform.md) +**SSOT**: [docs/project/script-execution.md](../project/script-execution.md) + +## 变更摘要 + +1. 长任务:`nohup` + 子机结束 `curl` → `POST /api/agent/script-callback` +2. 执行热数据 Redis(TTL 2h),MySQL 仅 60s 分批刷盘 +3. 短任务 stop、执行历史页、自动卡住检测(详见平台 changelog) + +## 长任务流程 + +勾选「长任务」执行脚本时: + +1. 主站为每台子机生成 `job_id` + 一次性 `secret`,写入 Redis(TTL 7 天) +2. 主站用 `nohup` 包装脚本,脚本结束后在子机执行 `curl` → `POST /api/agent/script-callback` +3. 主站更新 `script_executions.results`,并通过 WebSocket 推送 `script_job_done` + +## 配置要求 + +- `.env` 中必须配置 **`NEXUS_API_BASE_URL`**(子机可访问的主站 HTTPS 地址),否则长任务接口返回 400 + +## API + +| 端点 | 认证 | 说明 | +|------|------|------| +| `POST /api/scripts/exec` | JWT | 请求体增加 `long_task: true` | +| `POST /api/agent/script-callback` | `job_id` + `secret`(一次性) | 子机 curl,无需 JWT | + +回调 JSON 示例: + +```json +{ + "job_id": "…", + "server_id": 12, + "secret": "…", + "exit_code": 0, + "message": "done" +} +``` + +## Redis → MySQL + +| 常量 | 值 | 文件 | +|------|-----|------| +| `REDIS_TTL_SECONDS` | 7200 | `script_execution_store.py` | +| `FLUSH_INTERVAL_SECONDS` | 60 | 同上 | +| `FLUSH_BATCH_LIMIT` | 50 | 同上 | +| `STUCK_NO_PROGRESS_SECONDS` | 3600 | 同上 | + +- 全部状态变更只写 Redis;读接口优先 Redis +- 后台 `script_execution_flush`:刷盘 + `detect_stuck_executions` +- `results._meta.events` 时间线;`audit_logs` 即时写入 + +## 审计 action + +`execute_started`、`execute_command`、`stop_execution`、`retry_execution`、`mark_stuck`、`script_job_callback`、`auto_stuck`、`server_auto_stuck` + +## 子机 Agent(短任务停止) + +| 接口 | 说明 | +|------|------| +| `POST /exec` + `wait: false` | 返回 `pid` | +| `POST /exec/wait` | 等待结束 | +| `POST /exec/kill` | 终止进程 | + +未升级 Agent 时无法短任务中途停止。 + +## 涉及文件 + +- `server/application/services/script_jobs.py` +- `server/application/services/script_job_callback.py` +- `server/infrastructure/redis/script_execution_store.py` +- `server/background/script_execution_flush.py` +- `server/api/agent.py` +- `web/app/scripts.html`、`web/app/script-executions.html` +- `web/agent/agent.py` diff --git a/docs/changelog/2026-05-22-security-fixes-p0.md b/docs/changelog/2026-05-22-security-fixes-p0.md new file mode 100644 index 00000000..b305b9b8 --- /dev/null +++ b/docs/changelog/2026-05-22-security-fixes-p0.md @@ -0,0 +1,20 @@ +# Changelog — P0 安全修复(审计先行修复) + +**日期**: 2026-05-22 + +## 修复项 + +| ID | 问题 | 修复 | +|----|------|------| +| F2a-01 | WebSSH IDOR:普通 JWT 可连任意 server_id | `POST /api/auth/webssh-token` 签发 `purpose=webssh` + `server_id`;`webssh.py` 强制匹配 | +| F2a-02 | 中心机 `/api/agent/exec` 本机 RCE | 删除该路由;远程执行仅 `web/agent/agent.py` `/exec` | +| F2c-01 | 解密失败返回密文当密码 | `decrypt_value` 失败抛 `ValueError` | +| F2b-06 | `batch_push` 缺 `nonlocal` | `sync_service.py` 添加 `nonlocal completed` | +| — | Agent 调用 URL 错误 | `script_service` 改为 `http://host:port/exec` | + +## 涉及文件 + +- `server/api/webssh.py`, `server/api/auth.py`, `server/api/agent.py` +- `server/application/services/auth_service.py`, `sync_service.py`, `script_service.py` +- `server/infrastructure/database/crypto.py` +- `web/app/terminal.html` diff --git a/docs/changelog/2026-05-22-security-fixes-p1.md b/docs/changelog/2026-05-22-security-fixes-p1.md new file mode 100644 index 00000000..3f4d142d --- /dev/null +++ b/docs/changelog/2026-05-22-security-fixes-p1.md @@ -0,0 +1,27 @@ +# Changelog — P1 安全修复(审计第二批) + +**日期**: 2026-05-22 + +## 修复项 + +| ID | 修复 | +|----|------| +| F2b-01/02/03 | `sync_engine_v2` sysctl/echo/cp 整段 `shlex.quote`;回滚路径白名单 | +| F2b-04/11/15 | 危险命令在 `sync_v2`、`sync_engine_v2`、`scripts/exec` **拒绝执行** | +| F2b-07/08 | Agent URL 随 `API_BASE_URL` 使用 HTTPS(`agent_url.py`) | +| F2b-09 | 凭据测试 API 不再返回含密码的 shell 命令 | +| F2a-07 | TOTP 关闭需当前密码 + TOTP 验证码;递增 `token_version` | +| F2a-08 | 安装完成后关闭 `env-check`/`state`;新装默认 `SSH_STRICT_HOST_CHECKING=true` | +| F2a-09 | `create-admin` 更新 `.env` 使用 `_escape_env_value` | +| F2a-16 | API Key 查看需重新输入管理员密码 + 审计 | +| F2a-14 | `scripts` 路由:`/credentials` 注册在 `/{id}` 之前 | +| F2c-02 | 安装向导写入严格 SSH 主机校验 | +| F2c-04 | Telegram 告警 HTML 转义 | +| — | `web/agent`:`compare_digest` + `sudo sh -c` 引用 | + +## 涉及文件 + +- `server/application/services/sync_engine_v2.py`, `script_service.py`, `server_service.py`, `auth_service.py` +- `server/api/sync_v2.py`, `scripts.py`, `auth.py`, `settings.py`, `install.py` +- `server/infrastructure/agent_url.py`, `telegram/__init__.py` +- `web/agent/agent.py`, `web/app/settings.html` diff --git a/docs/changelog/2026-05-22-security-fixes-p2-queue.md b/docs/changelog/2026-05-22-security-fixes-p2-queue.md new file mode 100644 index 00000000..406c5ec4 --- /dev/null +++ b/docs/changelog/2026-05-22-security-fixes-p2-queue.md @@ -0,0 +1,35 @@ +# Changelog — Phase 2 汇总表修复队列(第二批) + +**日期**: 2026-05-22 + +## 修复项 + +| ID | 问题 | 修复 | +|----|------|------| +| F2f-04 | 压测脚本硬编码 API Key | `quick_load.py` / `load_test.py` 改读 `NEXUS_API_KEY`、`NEXUS_TEST_BASE` 环境变量 | +| F2e-01 | `servers.html` category 未 esc | `${esc(s.category\|\|'--')}` | +| F2e-02 | `settings.html` saveSetting onclick 注入 | `data-setting-key` + `escAttr` + 事件委托 | +| F2e-07 | `scripts.html` category 未 esc | `${esc(s.category\|\|'ops')}` | +| F2f-02 | Agent 空 API_KEY 不鉴权 | 启动时无 key 则 `sys.exit(1)`;`verify_api_key` 始终校验 | +| F2f-03 | Agent 默认 0.0.0.0 | 默认 `127.0.0.1`(`agent.py`、`config.example.json`、`install.sh`) | +| F2a-12 | 生成 agent key 暴露面 | API 增加 `display_once`/`message`;列表仍掩码;编辑表单不填掩码值,生成后剪贴板+待保存 | + +## 未在本批处理(仍 OPEN) + +- F2f-01:子机 `/exec` subprocess_shell(设计面,需文档/网络隔离) +- F2a-05/18、F2e-03/04:WS URL 带 token +- F2a-10/F2d-02:全局 JWT 中间件 +- F2e-05/06:localStorage JWT、CDN SRI + +## 运维提醒 + +1. **轮换 API Key**:旧密钥曾出现在 git 历史的压测脚本中,请在生产 `.env` / settings 表轮换 `API_KEY`。 +2. 压测:`NEXUS_API_KEY=xxx python tests/load_test.py --concurrency 50 --duration 30` +3. 已有 Agent 若需中心机访问:在 `config.json` 将 `server.host` 改为 `0.0.0.0` 并配合防火墙。 + +## 涉及文件 + +- `tests/quick_load.py`, `tests/load_test.py` +- `web/app/servers.html`, `settings.html`, `scripts.html` +- `web/agent/agent.py`, `config.example.json`, `install.sh` +- `server/api/servers.py` diff --git a/docs/changelog/2026-05-22-superpowers-zh-install.md b/docs/changelog/2026-05-22-superpowers-zh-install.md new file mode 100644 index 00000000..4d2a43d1 --- /dev/null +++ b/docs/changelog/2026-05-22-superpowers-zh-install.md @@ -0,0 +1,35 @@ +# Changelog — 安装 superpowers-zh + +**日期**: 2026-05-22 +**类型**: 工具链 / Skills + +## 变更摘要 + +通过 `npx superpowers-zh --tool cursor` 安装 [superpowers-zh](https://github.com/jnMetaCode/superpowers-zh) v1.5.0,共 **20 个** Agent Skills 到 `.cursor/skills/`。 + +## 安装命令(WSL) + +```bash +cd /mnt/c/Users/uzuma/Desktop/svn/Nexus/.claude/worktrees/suspicious-williams-fe9492 +npx superpowers-zh --tool cursor +``` + +## Skills 列表 + +**工作流(自动):** brainstorming, writing-plans, executing-plans, test-driven-development, systematic-debugging, requesting-code-review, receiving-code-review, verification-before-completion, dispatching-parallel-agents, subagent-driven-development, using-git-worktrees, finishing-a-development-branch, writing-skills, using-superpowers, mcp-builder, workflow-runner + +**中国特色(部分需手动 `/chinese-*`):** chinese-code-review, chinese-git-workflow, chinese-documentation, chinese-commit-conventions + +## 卸载 + +```bash +npx superpowers-zh@latest --uninstall +``` + +## 生效方式 + +重启 Cursor 或 Reload Window。 + +## 备注 + +- WSL Node v18 有 EBADENGINE 警告(包要求 Node ≥20),安装仍成功;建议 WSL 升级到 Node 20+ diff --git a/docs/changelog/2026-05-22-tech-stack-rules-mysql-mcp.md b/docs/changelog/2026-05-22-tech-stack-rules-mysql-mcp.md new file mode 100644 index 00000000..cadf2b20 --- /dev/null +++ b/docs/changelog/2026-05-22-tech-stack-rules-mysql-mcp.md @@ -0,0 +1,46 @@ +# Changelog — 技术栈 Cursor 规则 + MySQL MCP 配置 + +**日期**: 2026-05-22 +**类型**: 规范 / 工具链 + +## 变更摘要 + +1. 新增 4 条基于 Nexus 技术栈的 Cursor 规则(`.cursor/rules/`) +2. 全局安装 `@yclenove/mysql-mcp-server`,配置项目 `.cursor/mcp.json` +3. 新增 `scripts/sync_mysql_mcp_env.py` 从 `NEXUS_DATABASE_URL` 同步 `MYSQL_*` +4. 新增 `docs/project/mysql-mcp-setup.md` 配置指南 + +## 新增 Cursor 规则 + +| 文件 | 作用域 | +|------|--------| +| `nexus-tech-stack.mdc` | alwaysApply — 分层、配置、数据流 | +| `nexus-python-backend.mdc` | `server/**/*.py` | +| `nexus-frontend.mdc` | `web/app/**` | +| `nexus-security.mdc` | API/应用/DB/前端 — JWT、MCP 只读 | + +## MCP + +- 命令:`npx -y @yclenove/mysql-mcp-server`(Windows 经 `cmd /c`) +- 连接:项目根 `.env` 内 `MYSQL_*`(由 sync 脚本生成,默认只读) + +## 涉及文件 + +- `.cursor/rules/nexus-*.mdc`(4 个) +- `.cursor/mcp.json` +- `scripts/sync_mysql_mcp_env.py` +- `docs/project/mysql-mcp-setup.md` +- `.env.example`(MCP 注释块) +- `docs/README.md` + +## 用户待办(启用 MCP 前) + +1. 确保有 `.env`(安装向导或 `copy .env.example .env`) +2. `python scripts/sync_mysql_mcp_env.py` +3. Cursor → Settings → MCP → 启用 `mysql-mcp` → Reload Window + +## 验证 + +- [x] `npm install -g @yclenove/mysql-mcp-server@latest` 成功 +- [x] `mysql-mcp-server` 可执行并读取 worktree 为 cwd +- [ ] 用户本地 `.env` + sync 后 `test_connection` 通过(需本机 MySQL) diff --git a/docs/changelog/2026-05-22-telegram-sanitize-and-security-tests.md b/docs/changelog/2026-05-22-telegram-sanitize-and-security-tests.md new file mode 100644 index 00000000..ad602d72 --- /dev/null +++ b/docs/changelog/2026-05-22-telegram-sanitize-and-security-tests.md @@ -0,0 +1,24 @@ +# Telegram 出站脱敏 + 安全回归测试(F2d-04 / F2f-06) + +**日期**: 2026-05-22 + +## F2d-04 — Telegram 脱敏 + +- `server/infrastructure/telegram/__init__.py`:新增 `sanitize_external_message()`,屏蔽 DB/Redis URL、password、Bearer、密钥片段。 +- `send_telegram_system_alert()`:出站前脱敏 + `html.escape`。 +- `send_telegram()` 成功日志同样脱敏后再截断。 +- `self_monitor.py`:Redis/MySQL 失败详情写入 `logger.warning(..., exc_info=True)`;Telegram 仅固定文案。 + +## F2f-06 — 回归测试 + +`tests/test_security_unit.py` 扩展至 15 项,覆盖: + +- 危险命令、`server_ids` JSON 解析、HTTPS callback +- `sanitize_external_message` 脱敏与截断 +- `JwtAuthMiddleware` 401 / 公开路径跳过 +- `/api/install/`、`/api/auth/logout` 公开前缀 +- 安装后 `_reject_if_installed()` → 403 + +```bash +pytest tests/test_security_unit.py -q +``` diff --git a/docs/changelog/2026-05-22-wsl-mysql-bootstrap.md b/docs/changelog/2026-05-22-wsl-mysql-bootstrap.md new file mode 100644 index 00000000..94090f9a --- /dev/null +++ b/docs/changelog/2026-05-22-wsl-mysql-bootstrap.md @@ -0,0 +1,35 @@ +# Changelog — WSL MySQL 连接 + 建表 + MCP 配置 + +**日期**: 2026-05-22 +**类型**: 配置 / 数据库初始化 + +## 变更摘要 + +1. 确认 WSL 可连接 `172.31.170.47:3306`,库 `nexus` 已存在但无表 +2. 执行 `scripts/bootstrap_database.py` 创建 **16 张表** +3. `server/config.py`:`SettingsConfigDict(extra="ignore")`,允许 `.env` 共存 `MYSQL_*` +4. `.cursor/mcp.json` 改为经 **WSL** 启动 `mysql-mcp-server` +5. 新增 `scripts/wsl_check_mysql.py`、`scripts/bootstrap_database.py` + +## 表结构(已创建) + +`admins`, `audit_logs`, `command_logs`, `db_credentials`, `login_attempts`, `nodes`, `password_presets`, `platforms`, `push_retry_jobs`, `push_schedules`, `script_executions`, `scripts`, `servers`, `settings`, `ssh_sessions`, `sync_logs` + +## 涉及文件 + +- `server/config.py` +- `.cursor/mcp.json` +- `scripts/wsl_check_mysql.py`, `scripts/bootstrap_database.py` +- `docs/project/mysql-mcp-setup.md` + +## 验证(WSL) + +```bash +python3 scripts/wsl_check_mysql.py +# 应显示 TABLES in nexus (16): [...] +``` + +## 备注 + +- 数据迁移 `migrate_category_to_node` 首次可能因连接池 ping 报错(无历史数据可忽略) +- Cursor 需 Reload Window 后 MCP 走 WSL 读 `.env` diff --git a/docs/changelog/2026-05-23-ai-handoff-document.md b/docs/changelog/2026-05-23-ai-handoff-document.md new file mode 100644 index 00000000..3c07d133 --- /dev/null +++ b/docs/changelog/2026-05-23-ai-handoff-document.md @@ -0,0 +1,23 @@ +# 2026-05-23 — AI 接续交接文档 + +## 摘要 + +新增 `docs/project/AI-HANDOFF-2026-05-23.md` 作为切换 AI / 新会话的单一事实来源(SSOT),汇总端口模型、已完成功能、待办 backlog、验证清单、否决项与文档债务。 + +## 动机 + +用户切换 AI 继续后续开发,需完整、可复制的接续记录,避免上下文丢失。 + +## 涉及文件 + +- `docs/project/AI-HANDOFF-2026-05-23.md`(新建) +- `PROJECT_HANDOVER.md`(顶部指向 SSOT) +- `docs/project/status.md`(链接 SSOT) + +## 迁移 / 重启 + +无。 + +## 验证 + +打开 `docs/project/AI-HANDOFF-2026-05-23.md`,复制 §0 提示词到新会话即可。 diff --git a/docs/changelog/2026-05-23-development-acceptance-standard.md b/docs/changelog/2026-05-23-development-acceptance-standard.md new file mode 100644 index 00000000..630ba1aa --- /dev/null +++ b/docs/changelog/2026-05-23-development-acceptance-standard.md @@ -0,0 +1,25 @@ +# 2026-05-23 — 开发验收标准文档 + +## 摘要 + +新增 `docs/project/development-acceptance-standard.md`,汇总 L0~L5 验收层级(原则、设计、日常命令、审计 DoD、模块 E2E、首次部署),并写入 AI 接续、standards 索引与功能说明书。 + +## 动机 + +切换 AI 时需明确「怎样算做完/可上线」,避免只读功能清单而忽略 pytest、走读 DoD、production checklist。 + +## 涉及文件 + +- `docs/project/development-acceptance-standard.md`(新建) +- `docs/project/AI-HANDOFF-2026-05-23.md` +- `docs/project/nexus-full-site-features.md` +- `standards/README.md` +- `docs/Nexus-功能说明书.html` + +## 迁移 / 重启 + +无。 + +## 验证 + +新 AI 按 HANDOFF §0 提示词应加载 `development-acceptance-standard.md`。 diff --git a/docs/changelog/2026-05-23-full-audit-reconciliation-fixes.md b/docs/changelog/2026-05-23-full-audit-reconciliation-fixes.md new file mode 100644 index 00000000..9213bcd8 --- /dev/null +++ b/docs/changelog/2026-05-23-full-audit-reconciliation-fixes.md @@ -0,0 +1,22 @@ +# 全量审计对照修复(C2 / C9) + +**日期**: 2026-05-23 + +## C2 — 认证审计日志字段 + +`server/application/services/auth_service.py`: + +- `_audit()` 写入 `admin_username`、`ip_address` 列(不再仅堆在 detail 字符串里) +- 登录/刷新/登出/TOTP/WebSSH 等调用点传递对应用户名与 IP + +## C9 — WebSocket 告警重复投递 + +`server/api/websocket.py`: + +- 新增 `_dispatch_ws_message()`:Redis Pub/Sub 活跃时 **仅 publish**,由本 worker 的 subscriber 调用一次 `broadcast_local` +- Redis 不可用时回退为直接 `broadcast_local` +- `broadcast_alert` / `broadcast_recovery` / `broadcast_system_event` 均改用该路径 + +## 文档 + +- `docs/reports/audit-full-vs-phase2-reconciliation.md` — 全量 C1–C12 对照表 diff --git a/docs/changelog/2026-05-23-full-site-features-doc.md b/docs/changelog/2026-05-23-full-site-features-doc.md new file mode 100644 index 00000000..7cf088aa --- /dev/null +++ b/docs/changelog/2026-05-23-full-site-features-doc.md @@ -0,0 +1,25 @@ +# 2026-05-23 — 全站功能文档补齐 + +## 摘要 + +新增全站功能 Markdown SSOT,大幅扩充 HTML 功能说明书(18 页地图、告警中心、资产管理、命令/推送日志、网络模型等),并在 `standards/README.md` 增加文档索引。 + +## 动机 + +原功能说明书缺失大量已实现模块,且多处描述过期(如审计过滤、定时脚本、Agent 8601 等),不利于切换 AI 接续开发。 + +## 涉及文件 + +- `docs/project/nexus-full-site-features.md`(新建) +- `docs/Nexus-功能说明书.html`(重写扩充) +- `standards/README.md` +- `docs/project/AI-HANDOFF-2026-05-23.md`(链接) +- `docs/project/development-acceptance-standard.md`(新建,L0~L5 验收 SSOT) + +## 迁移 / 重启 + +无。 + +## 验证 + +浏览器打开 `docs/Nexus-功能说明书.html` 检查目录与新增章节。 diff --git a/docs/changelog/2026-05-23-mcp-memory-server.md b/docs/changelog/2026-05-23-mcp-memory-server.md new file mode 100644 index 00000000..6512bdc6 --- /dev/null +++ b/docs/changelog/2026-05-23-mcp-memory-server.md @@ -0,0 +1,42 @@ +# Changelog — MCP Memory 服务器 + +**日期:** 2026-05-23 + +## 变更摘要 + +为 Cursor 工作区添加 `@modelcontextprotocol/server-memory` MCP 服务器,持久化 Agent 跨会话记忆。 + +## 动机 + +- 原配置指向已废弃的 `multi-server-sync` 仓库路径 +- Nexus 6.0 工作区需要独立的本地记忆文件,与 MySQL MCP 并列 + +## 涉及文件 + +| 文件 | 变更 | +|------|------| +| `.cursor/mcp.json` | 新增 `memory` 服务器条目 | +| `.claude/mcp-memory.jsonl` | 本地记忆存储(空文件初始化) | +| `.gitignore` | 忽略 `.claude/mcp-memory.jsonl` | + +## 配置 + +```json +"memory": { + "command": "cmd", + "args": ["/c", "npx", "-y", "@modelcontextprotocol/server-memory"], + "env": { + "MEMORY_FILE_PATH": "/.claude/mcp-memory.jsonl" + } +} +``` + +## 迁移/重启 + +- 无需数据库迁移 +- **需重启 Cursor** 或重新加载 MCP 服务器后生效 + +## 验证方式 + +1. Cursor → Settings → MCP → 确认 `memory` 显示为已连接 +2. Agent 调用 `create_entities` / `search_nodes` 等 memory 工具后,检查 `.claude/mcp-memory.jsonl` 是否有新行写入 diff --git a/docs/changelog/2026-05-23-medium-fixes.md b/docs/changelog/2026-05-23-medium-fixes.md new file mode 100644 index 00000000..d40fe17d --- /dev/null +++ b/docs/changelog/2026-05-23-medium-fixes.md @@ -0,0 +1,31 @@ +# 2026-05-23 — MEDIUM Issue Fixes (Post-Audit Round 2) + +## Summary +Fixed all 14+ MEDIUM issues identified in the full codebase audit. + +## Changes + +### Backend Core +- **WebSocket connection limits**: Added `MAX_CONNECTIONS=500` to ConnectionManager; rejects new connections when limit reached (memory DoS prevention) +- **WebSSH message size limits**: Added `MAX_MSG_SIZE=100KB` and `MAX_DATA_SIZE=10KB` validation before processing WebSocket messages +- **setting_repo SELECT-then-INSERT race**: Replaced TOCTOU pattern with MySQL `ON DUPLICATE KEY UPDATE` for atomic upsert +- **Migration atomicity**: Removed intermediate `session.commit()` calls — single commit at end for all-or-nothing migration +- **SyncLog/ScriptExecution return types**: Fixed `update_status()` return types from non-Optional to `Optional[SyncLog]` / `Optional[ScriptExecution]` + +### Security Hardening +- **Nginx rate limiting**: Added `limit_req_zone` (10 req/s per IP, burst 20) for API endpoints +- **X-Forwarded-For IP extraction**: Added `_client_ip()` helper to auth.py that checks `X-Forwarded-For` header first, falls back to `request.client.host` +- **CDN SRI integrity hashes**: Added `integrity` + `crossorigin` attributes to all CDN script/style tags across all 12 HTML pages (tailwindcss, alpinejs, xterm, xterm-addon-fit) +- **Global 401 interceptor**: Added `window.fetch` override on all pages — auto-redirects to login on 401 responses + +### Deployment +- **Supervisor stopasgroup**: Added `stopasgroup=true` to ensure SIGINT reaches entire process tree +- **health_monitor.sh hardening**: Added `set -euo pipefail`; wrapped FAIL_FILE read-modify-write in `flock` for atomicity +- **health_monitor.sh fail count**: All 3 fail-count access points now use `flock`-protected critical sections + +### Code Quality +- **decrypt_value documentation**: Clarified backward-compat contract; added `try_decrypt_value()` for callers needing explicit success/failure signal +- **Dual DB session deprecation**: Marked `get_async_session()` as deprecated with docstring pointing to middleware-based `request.state.db` pattern + +## Files Changed +39 files, 498 insertions, 210 deletions diff --git a/docs/changelog/2026-05-23-phase1-audit-fixes.md b/docs/changelog/2026-05-23-phase1-audit-fixes.md new file mode 100644 index 00000000..7c3d5ad1 --- /dev/null +++ b/docs/changelog/2026-05-23-phase1-audit-fixes.md @@ -0,0 +1,20 @@ +# Phase 1 补充走读修复 + +**日期**: 2026-05-23 +**批次**: Phase 1(Step 0–5 遗留代码补充全文走读) + +## 覆盖文件 + +| 文件 | 结论 | +|------|------| +| `server/infrastructure/telegram/__init__.py` | ✅ CLEAN(F2c-04/F2d-04 修复均正确) | +| `server/domain/models/__init__.py` | 🔴 F1-01 修复 | +| `server/background/script_execution_flush.py` | ✅ CLEAN | +| `server/api/health.py` | ✅ CLEAN | +| `server/infrastructure/database/ssh_session_repo.py` | ✅ CLEAN | + +## 修复 + +| ID | 文件:行 | 级 | 说明 | +|----|---------|----|----| +| F1-01 | `domain/models/__init__.py:79-80` | P2 | `ssh_key_private`/`ssh_key_public` 列类型从 `String(500)` 改为 `Text`;Fernet 加密的 RSA 4096 私钥约 4300+ 字符,超出 String(500) 会在 MySQL 严格模式下报 DataError | diff --git a/docs/changelog/2026-05-23-phase2-docs-sync.md b/docs/changelog/2026-05-23-phase2-docs-sync.md new file mode 100644 index 00000000..f4c1be4a --- /dev/null +++ b/docs/changelog/2026-05-23-phase2-docs-sync.md @@ -0,0 +1,21 @@ +# 开发文档同步 — Phase 2 审计修复说明入库 + +**日期**: 2026-05-23 + +## 新增 + +- `docs/project/phase-2-audit-remediation.md` — Phase 2 修复 **开发 SSOT**(按主题汇总、接受决策、changelog 索引、剩余队列) + +## 更新 + +- `docs/README.md` — 索引 Phase 2 文档、验证清单、告警/脚本 SSOT、changelog 分组 +- `docs/reports/README.md` — Phase 2 专节 + 实施状态行 +- `docs/reports/audit-phase-2-findings-matrix.md` — 统计同步(52/3/7/0) +- `docs/reports/2026-05-23-full-audit-report.md` — 顶部交叉引用 Phase 2 SSOT +- `docs/project/status.md` — Phase 2 进度块 + 完成度 ~98% + +## 阅读顺序(新成员) + +1. `docs/project/phase-2-audit-remediation.md` +2. `docs/reports/audit-phase-2-findings-matrix.md`(查具体 ID) +3. `docs/project/production-verification-checklist.md`(上线前) diff --git a/docs/changelog/2026-05-23-phase3-audit-fixes.md b/docs/changelog/2026-05-23-phase3-audit-fixes.md new file mode 100644 index 00000000..7b688a60 --- /dev/null +++ b/docs/changelog/2026-05-23-phase3-audit-fixes.md @@ -0,0 +1,24 @@ +# Phase 3 逐行走读修复 + +**日期**: 2026-05-23 +**批次**: 3a(安全核心)、3b(background)、3c(基础设施)、3d(main/middleware) + +## 修复清单 + +| ID | 文件 | 级 | 说明 | 修复 | +|----|------|----|------|------| +| F3a-01 | `agent.py:97-102` | P2 | `float(cpu/mem/disk)` 对非数字 Agent 数据 → ValueError/500 | 提取 `_safe_float()` 包裹转换,非数字值 WARNING 级跳过 | +| F3b-01 | `schedule_runner.py:84` | P1 | `now`(tz-aware)- `last_run_at`(MySQL naive)→ TypeError → 已跑调度永远失败 | 统一去除 tzinfo 后再减 | +| F3b-02 | `schedule_runner.py:47` | P3 | cron `*/0` → ZeroDivisionError | step==0 时返回 False 跳过 | +| F3c-01 | `redis/client.py:49` | P2 | Redis URL 含 auth 时密码写入日志 | 启动日志脱敏(`user:**@host`) | +| F3d-01 | `main.py:116-118` | P2 | primary lock 丢失时续锁退出,后台任务继续双跑 | lock 丢失时 cancel 所有后台任务 | +| F3d-02 | `main.py:344` | P2 | CORS origins 不去除空格 | `o.strip()` 过滤 | + +## 审查结论 + +| 批次 | 覆盖 | 结论 | +|------|------|------| +| 3a | auth_jwt.py、webssh.py、crypto.py、agent.py、dependencies.py | 1 BUG 修复;安全核心均正确 | +| 3b | heartbeat_flush、retry_runner、schedule_runner、self_monitor | 2 BUG 修复;整体逻辑正确 | +| 3c | engine_compat.py、session.py、redis/client.py | 1 日志脱敏;新增文件无安全问题 | +| 3d | main.py | 2 BUG 修复;中间件注册顺序正确 | diff --git a/docs/changelog/2026-05-23-round2-audit-fixes.md b/docs/changelog/2026-05-23-round2-audit-fixes.md new file mode 100644 index 00000000..f133e8d1 --- /dev/null +++ b/docs/changelog/2026-05-23-round2-audit-fixes.md @@ -0,0 +1,146 @@ +# Round 2 全面代码审计修复报告 + +**日期**: 2026-05-23 +**范围**: 全部8类别深度扫描 (功能性缺陷/安全漏洞/代码规范/安全风险/性能/可用性/可扩展性/优化) + +## 修复汇总 + +| 严重度 | 数量 | 状态 | +|--------|------|------| +| CRITICAL | 3 | 全部修复 | +| HIGH | 9 | 全部修复 | +| MEDIUM | 15 | 全部修复 | +| LOW | 10 | 评估后保留/已修复 | + +--- + +## CRITICAL 修复 + +### C-1: sync_service.py Shell注入回归 (CRITICAL) +- **文件**: `server/application/services/sync_service.py` +- **修复**: 对rsync命令所有动态参数添加 `shlex.quote()` — source_path, target_path, username, domain, key file path +- **附带修复**: sshpass密码通过 `env={"SSHPASS": password}` 传递给子进程;`__import__("datetime")` 替换为正常import + +### C-2: agent.py exec端点RCE无防护 (CRITICAL) +- **文件**: `server/api/agent.py` +- **修复**: 添加 `DANGEROUS_PATTERNS` 危险命令正则检测 + 403拦截;API Key比较改为 `secrets.compare_digest()`;`proc.kill()` 后加 `await proc.wait()` 防僵尸进程;server_id 类型验证 + +### C-3: WebSSH IDOR — token未绑定server_id (CRITICAL) +- **文件**: `server/api/webssh.py` +- **修复**: 验证JWT token中的server_id与路径参数匹配,不匹配返回4003关闭连接 + +--- + +## HIGH 修复 + +### H-1: PUT/POST端点setattr任意字段注入 (HIGH) +- **文件**: `servers.py`, `settings.py`, `assets.py`, `scripts.py` +- **修复**: 为每个模型定义字段白名单 (SERVER_CREATE_FIELDS, SCHEDULE_CREATE_FIELDS, PLATFORM_CREATE_FIELDS, SCRIPT_CREATE_FIELDS, CREDENTIAL_CREATE_FIELDS等);create端点用 `safe_data = {k:v for k,v in payload.items() if k in WHITELIST}` 过滤;update端点只set白名单内字段 + +### H-2: settings/assets/scripts端点无JWT认证 (HIGH) +- **文件**: `settings.py`, `assets.py`, `scripts.py` +- **修复**: 所有端点添加 `admin: Admin = Depends(get_current_admin)` 依赖 + +### H-3: refresh_token并发重放攻击 (HIGH) +- **文件**: `server/application/services/auth_service.py` +- **修复**: refresh时先清空旧token (invalidate-then-generate模式),并发请求第二个会因找不到token而失败 + +### H-4: servers.py N+1 Redis查询 (HIGH) +- **文件**: `server/api/servers.py` +- **修复**: 使用Redis pipeline批量获取所有heartbeat key,单次round-trip + +### H-5: agent.py proc.kill()僵尸进程 (HIGH) — 与C-2合并修复 + +### H-6: agent.py heartbeat server_id类型验证 (MEDIUM→HIGH) — 与C-2合并修复 + +### H-7: settings API Key/Secret泄露 (HIGH) +- **文件**: `server/api/settings.py` +- **修复**: 定义 `SENSITIVE_SETTING_KEYS` 集合;list/get返回 `***MASKED***`;PUT禁止修改secret_key/encryption_key + +### H-8: TOTP暴力破解无速率限制 (HIGH) +- **文件**: `server/application/services/auth_service.py` +- **修复**: 添加内存TOTP速率限制器,5次失败后锁定5分钟 + +### H-9: get_optional_admin独立session泄漏 (HIGH) +- **文件**: `server/api/auth_jwt.py` +- **修复**: 改用 `_get_request_session(request)` 获取request-scoped session + +--- + +## MEDIUM 修复 + +### M-2: X-Forwarded-For IP伪造 +- **文件**: `server/api/auth.py` +- **修复**: 优先使用X-Real-IP(Nginx设置);X-Forwarded-For取最后一个值(Nginx追加) + +### M-3: asyncssh known_hosts=None — 添加ADR注释说明 +- **文件**: `server/infrastructure/ssh/asyncssh_pool.py` +- **修复**: 添加ADR-012注释说明跳过原因(受控服务器环境) + +### M-4: asyncssh异常信息泄露服务器地址 +- **文件**: `server/infrastructure/ssh/asyncssh_pool.py` +- **修复**: ConnectionError只包含server_id,完整错误写入logger;exec_ssh_command返回通用错误消息 + +### M-5: 分页limit无上限 +- **文件**: `settings.py`, `assets.py` +- **修复**: audit_logs limit上限500;ssh_sessions上限200;command_logs上限500;retry_jobs上限500 + +### M-9: API层直接import infrastructure层 +- **文件**: `servers.py`, `agent.py` +- **修复**: 在 `dependencies.py` 添加 `get_redis_client()` 抽象函数,API层通过此函数获取Redis客户端 + +### M-10: WebSocket ConnectionManager单进程限制 +- **文件**: `server/api/websocket.py` +- **修复**: 添加 `global_client_count()` 方法,通过Redis聚合多worker连接数 + +### M-11: servers.py Redis overlay逻辑重复 +- **文件**: `server/api/servers.py` +- **修复**: 提取 `_overlay_redis_heartbeat()` 辅助函数,list_servers和get_server共用 + +### M-13: login_attempts表缺少查询索引 +- **文件**: `server/domain/models/__init__.py` +- **修复**: 添加 `Index('idx_login_attempts_check', 'username', 'ip_address', 'success', 'attempted_at')` + +### M-14: sync_service批量获取server逐个查询 +- **文件**: `server/domain/repositories/__init__.py`, `server/infrastructure/database/server_repo.py`, `server/application/services/sync_service.py` +- **修复**: ServerRepository添加 `get_by_ids(ids)` 方法,使用 `IN` 查询一次获取所有server + +### M-15: API错误消息中英文混用 +- **文件**: 所有API路由文件 +- **修复**: 统一为中文错误消息(服务器未找到、平台未找到、脚本未找到、必填等) + +--- + +## 修改文件清单 + +1. `server/api/servers.py` — setattr白名单+JWT auth+Redis pipeline+overlay去重+中文错误消息 +2. `server/api/settings.py` — setattr白名单+JWT auth+敏感设置掩码+分页上限+中文错误消息 +3. `server/api/assets.py` — setattr白名单+JWT auth+分页上限+中文错误消息 +4. `server/api/scripts.py` — setattr白名单+JWT auth+中文错误消息 +5. `server/api/agent.py` — 危险命令检测+compare_digest+僵尸进程+server_id验证+Redis抽象+中文错误消息 +6. `server/api/webssh.py` — IDOR server_id绑定 +7. `server/api/auth.py` — X-Real-IP优先+X-Forwarded-For取最后一个值 +8. `server/api/auth_jwt.py` — get_optional_admin使用request session +9. `server/application/services/sync_service.py` — shlex.quote+sshpass env+__import__替换+批量get_by_ids +10. `server/application/services/auth_service.py` — refresh_token防重放+TOTP速率限制 +11. `server/infrastructure/ssh/asyncssh_pool.py` — 错误信息脱敏+ADR注释 +12. `server/infrastructure/ssh/pool.py` — exec_command支持env参数 +13. `server/infrastructure/database/server_repo.py` — 添加get_by_ids批量查询 +14. `server/domain/repositories/__init__.py` — ServerRepository协议添加get_by_ids +15. `server/domain/models/__init__.py` — LoginAttempt添加复合索引 +16. `server/api/dependencies.py` — 添加get_redis_client抽象 +17. `server/api/websocket.py` — 添加global_client_count多worker支持 +18. `server/application/services/sync_engine_v2.py` — sysctl命令注入防护+SFTP错误脱敏 +19. `server/application/services/script_service.py` — SSRF私有IP拦截+str(e)脱敏+MYSQL_PWD环境变量 +20. `server/api/health.py` — 未认证端点拆分为/health(公开)+/health/detail(需JWT) +21. `server/infrastructure/database/push_schedule_repo.py` — **kwargs setattr改为字段白名单 +22. `server/infrastructure/database/sync_log_repo.py` — **kwargs setattr改为字段白名单 +23. `server/api/agent.py` — Exception str(e)脱敏 +24. `server/background/self_monitor.py` — Telegram消息str(e)脱敏 +25. `server/api/webssh.py` — SSH连接/Shell创建错误str(e)脱敏+关闭原因不暴露server_id +26. `server/application/services/sync_service.py` — error_message脱敏+stderr不写入DB +27. `server/infrastructure/database/ssh_session_repo.py` — __import__替换为正常import +28. `server/api/sync_v2.py` — 错误消息统一中文 +29. `server/api/servers.py` — _server_to_dict移除username字段(防SSH凭据泄露) +30. `server/infrastructure/ssh/pool.py` — ValueError不暴露host地址 +31. `server/infrastructure/database/server_repo.py` — import语句移至文件顶部 diff --git a/docs/changelog/2026-05-23-ssh-exec-no-8601.md b/docs/changelog/2026-05-23-ssh-exec-no-8601.md new file mode 100644 index 00000000..b685043e --- /dev/null +++ b/docs/changelog/2026-05-23-ssh-exec-no-8601.md @@ -0,0 +1,39 @@ +# 2026-05-23 — 脚本/健康检查改 SSH,弱化 8601 + +## 摘要 + +远程脚本执行与手动健康检查改为经 **SSH(22)**;Agent 仅负责 **HTTPS 出站心跳(443)**。子机 Agent HTTP 默认只监听 `127.0.0.1:8601`,**不要求**公网放行 8601。 + +## 动机 + +大量宝塔子机未开放 8601;推送与文件已走 SSH。统一命令通道为 SSH,监控仍靠 Agent 心跳,减少防火墙与双通道维护成本。 + +## 涉及文件 + +- `server/application/services/script_service.py` — `_call_ssh_exec`,移除 Agent HTTP 执行路径 +- `server/application/services/server_service.py` — `check_all_servers` 改 SSH 探测 +- `server/infrastructure/ssh/remote_probe.py` — 新增 SSH 健康/指标探测 +- `server/api/servers.py` — 健康检查 API 文档说明 +- `web/agent/install.sh` — Python 3.12 venv、127.0.0.1 绑定、不再自动放行公网 8601 +- `web/app/servers.html` — 安装说明文案 +- `server/config.py` — 注释 + +## 端口模型(变更后) + +| 方向 | 端口 | 用途 | +|------|------|------| +| 子机 → 中心 | 443 出站 | Agent 心跳、长任务 curl 回调 | +| 中心 → 子机 | 22 入站 | 推送、文件、脚本、手动健康检查 | +| 127.0.0.1:8601 | 仅本机 | Agent 进程(可选,不暴露公网) | + +## 迁移 / 重启 + +- 需重启 Nexus 后端以加载 Python 变更 +- 已装 Agent:建议重装或手动改 systemd 为 `127.0.0.1` + venv;**不必**再开公网 8601 +- 确保各服务器 SSH 凭据可用(与推送相同) + +## 验证 + +1. 子机 Agent 运行:`journalctl -u nexus-agent -f`,心跳 200 +2. 未开公网 8601 时:脚本页执行命令应成功(`channel: ssh`) +3. `POST /api/servers/check` 返回 `channel: ssh` 且 `status: online`(SSH 可达时) diff --git a/docs/changelog/2026-05-23-standards-transfer-package.md b/docs/changelog/2026-05-23-standards-transfer-package.md new file mode 100644 index 00000000..c74e5710 --- /dev/null +++ b/docs/changelog/2026-05-23-standards-transfer-package.md @@ -0,0 +1,28 @@ +# 2026-05-23 — 全量标准转接包 + +## 摘要 + +新增 `docs/project/standards-transfer-package.md`,将 Cursor 规则、`standards/` 三件套、逐行审查八步 DoD、设计/上线/审计资产全部编入接续体系;要求下一任 AI 与团队**同等严谨**,逐行审查与开发规范同等强制。 + +## 动机 + +用户要求切换 AI 时不仅看功能清单,还须完整转接所有开发与审计标准,逐行审查不得降级。 + +## 涉及文件 + +- `docs/project/standards-transfer-package.md`(新建) +- `docs/project/AI-HANDOFF-2026-05-23.md`(§0 提示词、§5 标准转接) +- `docs/project/development-acceptance-standard.md`(L3 扩展) +- `standards/README.md` +- `docs/project/nexus-full-site-features.md` §25 +- `docs/Nexus-功能说明书.html` +- `docs/project/status.md`、`PROJECT_HANDOVER.md` +- `.cursor/rules/audit-line-review.mdc` + +## 迁移 / 重启 + +无。 + +## 验证 + +新会话粘贴 HANDOFF §0 或 standards-transfer-package §8 提示词,AI 应列出 13 项强制标准 + 逐行 DoD 六项。 diff --git a/docs/changelog/2026-05-23-wsl-integration-guide.md b/docs/changelog/2026-05-23-wsl-integration-guide.md new file mode 100644 index 00000000..c730c412 --- /dev/null +++ b/docs/changelog/2026-05-23-wsl-integration-guide.md @@ -0,0 +1,15 @@ +# WSL 代码验证指南与脚本 + +**日期**: 2026-05-23 + +## 范围 + +仅验证代码:venv、启动、15 项单测、`/health`、`/api/servers/` 401。不审 key、不做 CRUD/浏览器/Agent E2E。 + +## 用法 + +```bash +bash scripts/wsl_ensure_venv.sh +bash scripts/wsl_start_dev.sh +bash scripts/wsl_integration_smoke.sh +``` diff --git a/docs/changelog/2026-05-24-agent-api-key-auth-fix.md b/docs/changelog/2026-05-24-agent-api-key-auth-fix.md new file mode 100644 index 00000000..de022ba1 --- /dev/null +++ b/docs/changelog/2026-05-24-agent-api-key-auth-fix.md @@ -0,0 +1,14 @@ +# 2026-05-24 Agent API Key 验证修复 + +**日期**: 2026-05-24 +**动机**: Agent 4 个端点(/exec、/exec/kill、/exec/wait、/config/reload)的 API Key 认证完全失效,属于 P0 安全 Bug +**变更摘要**: +- `verify_api_key()` 从手动调用改为 FastAPI `Depends()` 依赖注入 +- 修复调用时 `x_api_key` 被误传给 `request` 参数导致认证永远失败的问题 +- 将 `verify_api_key` 改为 `async` 以符合 FastAPI 依赖规范 + +**涉及文件**: `web/agent/agent.py` + +**是否需迁移**: 否 + +**验证方式**: 向 Agent 发送带错误 `X-API-Key` 的请求应返回 401;不带 key 返回 401(422 若 Header 缺失);正确 key 正常通过 diff --git a/docs/changelog/2026-05-24-agent-upgrade-deps-check.md b/docs/changelog/2026-05-24-agent-upgrade-deps-check.md new file mode 100644 index 00000000..39816bfd --- /dev/null +++ b/docs/changelog/2026-05-24-agent-upgrade-deps-check.md @@ -0,0 +1,20 @@ +# 2026-05-24 Agent 升级依赖检测 + 自动安装 + 回滚 + 版本锁定 + +**日期**: 2026-05-24 +**动机**: upgrade-agent 只下载 agent.py 不检查/更新依赖,新版引入新 pip 包会导致服务启动失败;install.sh 依赖不锁版本,子机可能装到与中心不一致的版本 +**变更摘要**: +- upgrade-agent 增加 Python 版本预检:SSH 检测 venv Python >= 3.10,不满足返回 424 + 提示手动修复 +- Python 版本 OK 后自动 `pip install` 更新依赖(锁版本,与中心 requirements.txt 对齐) +- 升级前备份旧 agent.py.bak,失败自动回滚 +- install.sh 依赖版本锁定:fastapi==0.115.6 uvicorn==0.34.0 httpx==0.28.1 python-multipart==0.0.19 +- 升级 SSH 超时从 60s 调至 120s(pip install 首次可能较慢) + +**涉及文件**: `server/api/servers.py`, `web/agent/install.sh` + +**是否需迁移**: 否 + +**验证方式**: +- Python < 3.10 时返回 424 + 提示手动修复 +- Python OK + 依赖完整时 pip install 跳过已装包,升级正常 +- Python OK + 依赖缺失时 pip install 自动补装 +- 升级失败时旧 agent.py 自动恢复 diff --git a/docs/changelog/2026-05-24-batch-review-fixes.md b/docs/changelog/2026-05-24-batch-review-fixes.md new file mode 100644 index 00000000..24793488 --- /dev/null +++ b/docs/changelog/2026-05-24-batch-review-fixes.md @@ -0,0 +1,75 @@ +# 2026-05-24 批量审查修复 — 27项全部处理 + +**日期**: 2026-05-24 +**动机**: 全面扫描项目,发现未实现/不完整/死代码问题 27 项,逐项沟通后全部处理 +**变更摘要**: + +### Bug 修复 +- B-01: Agent API Key 验证完全失效 — `verify_api_key()` 参数传错,改为 `Depends()` 模式 +- B-02: upgrade-agent 不检查/更新依赖 — 增加 Python 版本预检 + 自动 pip install(锁版本)+ 备份回滚 +- B-03: POST /api/servers/push 死端点 — 删除端点 + ServerPush schema + ServerService.push_to_servers() + +### 功能补全 +- U-01: POST /api/servers/check 无前端入口 — servers.html 批量操作栏加「健康检查」按钮 +- U-03: POST /api/sync/commands 无前端入口且与 scripts/exec 冗余 — 删除端点 + SyncCommands schema +- U-04/U-05: POST /api/sync/config + config/rollback — 删除端点(sysctl 可通过脚本执行实现) +- U-06: POST /api/sync/sftp 无前端入口 — 删除端点 +- C-03: Schedule script 类型缺少必填校验 — Pydantic model_validator 校验 script_id/script_content + push 模式 source_path +- 定时任务支持一次性/循环模式 — Model 加 run_mode + fire_at,前端加切换 UI,执行后自动禁用 + +### 前端修复 +- FE-01: alerts.html getTopType() 硬编码"见详情" — API 加 top_types 查询,前端显示实际类型 +- FE-02: assets.html SSH 会话只显示 "Server #ID" — 加载服务器名称映射 +- FE-03: assets.html editNode sort_order 硬编码 0 — 回填原始值 +- FE-04: layout.js 搜索链接不定位到具体记录 — 加 ?highlight=id 参数,各页面自动定位 + +### 死代码清理 +- S-01: FileDeploy schema 零引用 — 删除 +- S-02: PaginatedResponse schema 零引用 — 删除 +- D-02: ServerService.list_servers() 零调用者 — 删除 +- D-03: ScriptService.test_credential_connection() 零调用者 — 删除 +- Engine: sync_commands()、sync_config()、_rollback_config()、sftp_transfer() 随端点删除 + +### 安全/质量 +- CFG-03: install.py 两处 silent pass — 改为 logger.warning + +### 深度扫描第三轮 Bug 修复 +- BUG-1 [HIGH]: servers.py 路由遮蔽 — `GET /logs` 声明在 `GET /{id}` 之后,导致 /logs 被 /{id} 拦截(3个前端页面同步日志查询失败)。修复:将 `/logs` 移到 `/{id}` 之前 +- BUG-2 [CRITICAL]: terminal.html WebSSH 完全不可用 — 1) token URL 缺少 API 前缀 → 404;2) apiFetch() 返回 Response 未调 .json() → TypeError;3) 缺少 Content-Type header。三处全部修复 +- BUG-3 [MEDIUM]: schedule_runner.py `_field_match()` 提前返回 — 逗号分隔 cron 表达式(如 `*/5,1`)遇到 `*/5` 不匹配时直接 return False,不再检查后续 `1`。修复:step 不匹配时 continue 而非 return + +### 深度扫描第四轮 Bug 修复 +- BUG-4 [CRITICAL]: scripts.html `startExecPoll()` 函数名错误 → `startExecPolling()`,长任务快速执行时 ReferenceError 崩溃 +- BUG-5 [HIGH]: agent.py `{value!r!s:.50}` 格式串语法非法,agent 发非数值时 ValueError — 修复为 `{value!r:.50}` +- BUG-6 [HIGH]: alerts.html `d.daily.reduce()` 和 `d.top_servers[0]` 无 null 检查,API 返回空数据时 TypeError — 加 `||[]` 保护 +- BUG-7 [MEDIUM]: scripts.html `doExec()` 缺少 `r.ok` 检查,服务端错误被静默吞掉 — 加错误处理 +- BUG-8 [MEDIUM]: commands.html `s.id.substring(0,8)` 无 null 检查 — 改为 `(s.id||'').substring(0,8)` +- BUG-9 [MEDIUM]: `_server_to_dict()` 缺少 `last_checked_at` 和 `ssh_key_public` 字段 — 补充返回 +- BUG-10 [MEDIUM]: 限速脚本 EXPIRE 每次请求重置窗口,精确间隔可绕过 — 改用 Lua 脚本原子 INCR+条件EXPIRE +- N-06 [HIGH]: install.php 的 ssh_key 列 VARCHAR(500) 存不下加密密钥 — 迁移加 MODIFY COLUMN → TEXT +- N-07 [HIGH]: servers 表新列缺迁移 (platform_id/node_id/protocols/extra_attrs/connectivity/last_checked_at/ssh_key_configured) — 迁移补全 +- 清理: agent.py 冗余内联 import、servers.py/scripts.py 未使用 List 导入 + +**涉及文件**: web/app/scripts.html, web/app/alerts.html, web/app/commands.html, server/api/agent.py, server/api/servers.py, server/api/scripts.py, server/infrastructure/redis/script_callback_rate.py, server/infrastructure/database/migrations.py + +### 深度扫描第五轮 Bug + 安全修复 +- BUG-11 [HIGH]: self_monitor.py Telegram 告警无冷却,Redis/MySQL 宕机时每30秒刷屏 — 加5分钟冷却 + 恢复时清除冷却 +- BUG-12 [HIGH]: schedule_runner.py 一次性调度执行超60秒会重复触发 — 执行前先设 last_run_at + disable +- BUG-13 [MEDIUM]: websocket.py 僵尸连接只从字典移除,TCP 未关 — disconnect() 中主动 close() +- BUG-14 [MEDIUM]: websocket.py `_last_alert_time` 字典无限增长 — 加1小时过期清理 +- BUG-15 [HIGH]: asyncssh_pool.py 全忙时 pool 超过 MAX_CONNECTIONS 无限增长 — _evict_one 返回 bool + 全忙等待/拒绝 +- SEC-1 [HIGH]: auth_jwt.py + main.py `INSTALL_MODE` 是导入时常量,安装后 JWT 认证仍禁用 — 改为动态函数 `_is_install_mode()`/`is_install_mode()` +- SEC-2 [MEDIUM]: install.py `/lock` 端点无保护,可在管理员创建前调用阻止安装 — 加管理员存在性验证 + +**涉及文件**: server/background/self_monitor.py, server/background/schedule_runner.py, server/api/websocket.py, server/infrastructure/ssh/asyncssh_pool.py, server/api/auth_jwt.py, server/main.py, server/api/install.py + +**是否需迁移**: 是 — push_schedules 表新增 run_mode + fire_at 列,servers 表补全新列 + ssh_key 列类型修复(自动迁移) + +**验证方式**: +- 一次性调度:创建后到 fire_at 时间自动触发并禁用 +- 循环调度:cron 表达式匹配时触发,不自动禁用 +- 健康检查:选服务器 → 点击按钮 → 显示结果 +- 全局搜索:搜索结果点击跳转到对应页面并展开记录 +- WebSSH:点击服务器终端 → 正常连接 +- 路由遮蔽:服务器页面同步日志正常加载 +- Cron 表达式:`*/5,1` 在分钟=1 时正确匹配 diff --git a/docs/changelog/2026-05-25-server-form-preset-dropdown-ssh-key.md b/docs/changelog/2026-05-25-server-form-preset-dropdown-ssh-key.md new file mode 100644 index 00000000..ec22bc71 --- /dev/null +++ b/docs/changelog/2026-05-25-server-form-preset-dropdown-ssh-key.md @@ -0,0 +1,41 @@ +# 2026-05-25 — 服务器表单 UX 重构:预设下拉菜单 + SSH 密钥输入 + +## 变更摘要 + +重构服务器添加/编辑 Modal 的认证区域: +1. 密码预设从🔑弹窗 + prompt()二次验证 → `` 支持从已有分类选择或自由输入 +5. **Nginx 缓存控制** — 为 JS/CSS 静态文件添加 `no-store` 响应头,防止浏览器缓存旧版 + +## 动机 + +- 平台模板的 `default_protocols`、`charset`、`type` 字段未被任何功能使用 +- 节点树仅用于服务器表单下拉,无按节点筛选等联动 +- 服务器列表的「分类」筛选基于 `server.category` 自由文本字段,与节点分组独立 +- 用户明确要求直接去掉资产管理 + +## 涉及文件 + +| 文件 | 改动 | +|------|------| +| `web/app/assets.html` | 删除 | +| `web/app/layout.js` | 移除 `{ id:'assets', ... }` 导航项 | +| `web/app/servers.html` | 移除平台/节点下拉、`loadOrgSelects()`;Category 改为 combobox + datalist;移除 `platform_id`/`node_id` | + +## 不变的部分 + +- **数据库模型**:Platform 和 Node 表不变,无迁移 +- **后端 API**:`/api/assets/platforms` 和 `/api/assets/nodes` 端点保留 +- **CSV 导入**:继续通过 platform_name/node_name 解析 ID + +## 是否需迁移/重启 + +- **DB 迁移**: 无 +- **需重启**: 否(仅前端静态文件变更) +- **Nginx**: 已为 JS/CSS 添加 no-cache 头(需 reload,已执行) + +## 验证方式 + +1. 远程服务器 `layout.js` 不含「资产管理」(SSH grep 确认 0 匹配) +2. `assets.html` 已删除 (SSH 确认文件不存在) +3. `servers.html` 不含 `srvPlatform`/`srvNode`/`loadOrgSelects` (0 匹配) +4. `servers.html` 含 `categoryList` datalist (2 匹配) +5. Playwright fetch (cache: no-store) 确认最新 layout.js 正确 +6. Nginx 为 `/app/*.js` 和 `/app/*.css` 添加 Cache-Control: no-store diff --git a/docs/changelog/2026-05-26-server-form-ux-bugfix-security.md b/docs/changelog/2026-05-26-server-form-ux-bugfix-security.md new file mode 100644 index 00000000..926c3818 --- /dev/null +++ b/docs/changelog/2026-05-26-server-form-ux-bugfix-security.md @@ -0,0 +1,99 @@ +# 2026-05-26 — 服务器表单 UX 优化 + Bug 修复 + 安全加固 + +## 变更摘要 + +1. **分类字段改为 select+input combobox** — 替换不可见的 ``,用 `` — 命令输入框,monospace 字体 + - `发送` 按钮 — 调用 `sendCmd()` + - `⌃C` / `⌃D` / `Tab` 快捷按钮 — 分别调用 `sendCtrl()` / `sendKey()` + +**修改3: JavaScript — 命令输入逻辑** +- `cmdHistory` / `cmdHistoryIdx` — 命令历史记录 + 上下箭头导航 +- `cmdInput keydown` — Enter 发送、ArrowUp/Down 浏览历史 +- `sendCmd()` — 发送完整命令 + `\r`,记入历史,清空输入框 +- `sendCtrl(key)` — 发送控制字符(charCode - 96) +- `sendKey(key)` — 发送原始按键(如 Tab) + +## 技术分析 + +逐字符输入问题: +``` +用户输入 'w' → WS send {type:DATA, data:"w"} + → SSH echo "\033[...w" → read(4096) 可能返回 "w\033[...root@..." + → xterm 渲染时 ANSI 序列与 echo 字符交错 → 显示错乱 +``` + +命令输入栏解决方案: +``` +用户输入 "whoami" → 点发送 → WS send {type:DATA, data:"whoami\r"} + → SSH 完整回显 "whoami\r\nroot\r\n" → 一次返回 → 正常渲染 +``` + +## 涉及文件 + +- `web/app/terminal.html` — 终端页面(CSS + HTML + JS 共3处修改) + +## 是否需迁移/重启 + +- 不需重启后端(纯前端改动) +- 不需 DB 迁移 + +## 验证方式 + +1. 打开 terminal.html → 底部显示命令输入栏(❯ 提示符 + 输入框 + 发送按钮) +2. 输入 `whoami` → 点发送 → 终端显示 `root`(无显示错乱) +3. 输入 `ls` → Enter 键发送 → 正常显示文件列表 +4. ArrowUp → 回显上一条命令 `ls`,ArrowDown → 清空 +5. 点击 `⌃C` 按钮 → 中断当前命令 +6. 点击 `Tab` 按钮 → 触发自动补全 +7. 终端 xterm 区域自动占满输入栏上方空间 +8. 全屏模式下输入栏仍然可见 diff --git a/docs/changelog/2026-05-26-webssh-echo-delay-fix.md b/docs/changelog/2026-05-26-webssh-echo-delay-fix.md new file mode 100644 index 00000000..4b431195 --- /dev/null +++ b/docs/changelog/2026-05-26-webssh-echo-delay-fix.md @@ -0,0 +1,59 @@ +# WebSSH 终端输入回显延迟修复 + +**日期**: 2026-05-26 +**变更摘要**: 修复 WebSSH 终端输入字符不即时显示的问题 — 将 async-for (readline) 改为 read() + 降低 bufsize + +## 动机 + +1. 用户在终端输入字符后,字符不会即时显示(echo 延迟),必须按 Enter 后才一次性出现 +2. 根因:`async for data in shell.stdout` 内部调用 `readline()` → `readuntil('\n')`,会缓冲所有数据直到遇到换行符 +3. 交互式终端中,单字符 echo 永远不包含换行符,导致所有输入字符被缓冲直到用户按 Enter + +## 变更内容 + +### server/api/webssh.py — 2处修改 + +**修改1: `_read_shell_output()` 函数 — 消除 readline 缓冲** +- 旧:`async for data in shell.stdout:` (内部 readline,等换行才 yield) +- 新:`while not shell.stdout.at_eof(): data = await shell.stdout.read(4096)` (有数据即返回) +- `read(n)` 使用 `exact=False`,只要缓冲区有数据就立即返回(最多 n 字节),不等换行 + +**修改2: `create_process()` 调用 — 降低 bufsize** +- 旧:`conn.create_process(term_type="xterm-256color", term_size=(24, 80))` (默认 bufsize=128KB) +- 新:`conn.create_process(term_type="xterm-256color", term_size=(24, 80), bufsize=4096)` +- 两处 create_process(主连接 + 失效重试连接)均添加 `bufsize=4096` +- 4KB 缓冲区足够单次交互 echo,同时大幅降低延迟 + +## 技术分析 + +asyncssh 数据流路径: +``` +async for data in shell.stdout + → SSHStreamProcess.__aiter__() + → SSHStreamSession.aiter() + → readline() + → readuntil('\n') ← 阻塞直到换行符 +``` + +修复后路径: +``` +while not shell.stdout.at_eof(): + data = await shell.stdout.read(4096) + → SSHStreamSession.read(4096, exact=False) ← 有数据即返回 +``` + +## 涉及文件 + +- `server/api/webssh.py` — WebSSH WebSocket 处理(2处修改) + +## 是否需迁移/重启 + +- 需重启后端(webssh.py 修改) +- 前端无改动 + +## 验证方式 + +1. 打开 terminal.html?server_id=8 → WebSocket 保持 OPEN,状态显示「已连接」 +2. 输入 `whoami` → 字符即时显示(不等 Enter),按 Enter 后立即显示 `root` +3. 后端日志无 `shell creation failed` 错误 +4. 终端交互流畅,无 echo 延迟 diff --git a/docs/changelog/2026-05-26-webssh-terminal-fix.md b/docs/changelog/2026-05-26-webssh-terminal-fix.md new file mode 100644 index 00000000..7c8e8393 --- /dev/null +++ b/docs/changelog/2026-05-26-webssh-terminal-fix.md @@ -0,0 +1,40 @@ +# WebSSH 终端无法输入修复 + +**日期**: 2026-05-26 +**变更摘要**: 修复 WebSSH 终端连接建立后立即断开、无法输入的问题 + +## 动机 + +1. WebSocket 连接建立后几秒内就断开(wsState=CLOSED),用户无法在终端输入 +2. 后端日志报 `WebSSH shell creation failed: ` 错误消息为空,无法诊断 +3. `finally` 块双重关闭 WebSocket 导致 `Unexpected ASGI message 'websocket.close'` 异常 +4. 连接池复用的旧连接可能已失效(TCP连接在但SSH通道损坏),`create_process` 抛空消息异常 + +## 变更内容 + +### server/api/webssh.py — 3个bug修复 +- **失效连接重试**: `create_process` 失败时,强制关闭池中旧连接,用新连接重试一次 +- **异常信息改进**: `logger.error` 改用 `{type(e).__name__}: {e!r}` 避免空消息 +- **双重关闭修复**: 引入 `ws_closed` 标志,异常分支 close 后 finally 不再重复 close +- 所有 `websocket.close()` 调用增加 try/except 防护 + +### web/app/terminal.html — Alpine 初始化竞态修复 +- `$d()` 函数加 try/catch:Alpine 未加载时返回默认对象而非抛 `ReferenceError` +- 消除 `ResizeObserver fit error: ReferenceError: Alpine is not defined` 控制台警告 + +## 涉及文件 + +- `server/api/webssh.py` — WebSSH WebSocket 处理 +- `web/app/terminal.html` — 前端终端页面 + +## 是否需迁移/重启 + +- 需重启后端(webssh.py 修改) +- terminal.html 静态文件无需重启 + +## 验证方式 + +1. 打开 terminal.html?server_id=8 → WebSocket 保持 OPEN +2. 输入 `whoami` → 回显 `root` +3. 后端日志无 `shell creation failed` 和 `Unexpected ASGI message` 错误 +4. 控制台无 `Alpine is not defined` 警告 diff --git a/docs/changelog/2026-05-27-agent-install-fix-and-deploy.md b/docs/changelog/2026-05-27-agent-install-fix-and-deploy.md new file mode 100644 index 00000000..c7c0a3b4 --- /dev/null +++ b/docs/changelog/2026-05-27-agent-install-fix-and-deploy.md @@ -0,0 +1,57 @@ +# 2026-05-27 Agent安装修复 + 两台子服务器Agent部署 + +## 变更摘要 +1. 修复 Agent 安装 404 错误(/agent/ 静态文件未挂载) +2. 修复 curl|bash 管道错误隐藏问题 +3. 修复 CRLF 导致远程 shell 脚本执行失败 +4. 部署 Agent 到两台子服务器 + +## 动机 +Agent 安装命令一直返回 404,因为 FastAPI 未挂载 /agent/ 静态文件路由。 +curl|bash 管道模式会隐藏下载失败。 +Windows 工作区给 .sh/.py 文件添加了 CRLF,导致 Linux 上 `\r': command not found`。 + +## 涉及文件 + +### 后端 +- `server/main.py` — 新增 `app.mount("/agent", StaticFiles(...))` 挂载 web/agent/ 目录 +- `server/api/servers.py` — 3处 `curl ... | bash -s --` 改为 `TMP=$(mktemp) && curl ... -o "$TMP" && bash "$TMP" --` + +### 前端/Agent +- `web/agent/install.sh` — CRLF→LF +- `web/agent/agent.py` — CRLF→LF +- `web/agent/agent.sh` — CRLF→LF + +### 部署脚本 +- `deploy/install.sh` — CRLF→LF +- `deploy/health_monitor.sh` — CRLF→LF +- `deploy/db_backup.sh` — CRLF→LF + +### 根目录 +- `.gitattributes` — 新增,防止 git 自动转 CRLF(*.sh eol=lf, *.py eol=lf) + +## Agent 部署结果 + +| 服务器 | IP | server_id | 状态 | +|--------|-----|-----------|------| +| 机器人 | 66.154.115.131 | 8 | ✅ online, agent_version=2.0.0 | +| 冲量银海 | 39.105.228.140 | 9 | ✅ online, agent_version=2.0.0 | + +### 冲量银海额外步骤 +- 需先 `apt install python3.11-venv`(Debian 12 默认不带 python3.11-venv) +- install.sh 第2步 `python3 -m venv` 在缺少 ensurepip 时会静默失败,source activate 报错 + +## 需要迁移/重启 +- 是:需重启 Nexus 后端(Supervisor: `supervisorctl restart nexus`) +- 是:需 Nginx reload(`nginx -s reload`) +- 否:无数据库迁移 + +## 验证方式 +1. `curl https://api.synaglobal.vip/agent/install.sh` → 返回脚本内容(非 404) +2. Nexus Dashboard 显示 2 台服务器在线 +3. `curl https://api.synaglobal.vip/api/servers/8` → `is_online: true, agent_version: "2.0.0"` +4. `curl https://api.synaglobal.vip/api/servers/9` → `is_online: true, agent_version: "2.0.0"` + +## 已知问题 +- install.sh 第2步 venv 创建在缺少 python3-venv 时会静默失败,应加检测 +- batch install API 返回 success:false 但无具体错误信息,需改进错误上报 diff --git a/docs/changelog/2026-05-27-agent-os-release.md b/docs/changelog/2026-05-27-agent-os-release.md new file mode 100644 index 00000000..a679adf4 --- /dev/null +++ b/docs/changelog/2026-05-27-agent-os-release.md @@ -0,0 +1,55 @@ +# 2026-05-27 Agent os_release 上报 + 前端系统版本拆分 + 空字段隐藏 + +## 变更摘要 +1. Agent 心跳和 /health 端点新增 `os_release` 字段,读取 `/etc/os-release` 的 PRETTY_NAME(如 "Ubuntu 24.04 LTS") +2. servers.html 系统信息面板拆分为两卡片:「发行版」显示 os_release,「内核平台」显示 platform +3. servers.html 详情面板描述/目标路径/认证方式空值时隐藏整行,不再显示 `--` + +## 动机 +1. servers.html 系统版本列显示 `Linux-6.8.0-31-generic-x86_64-with-glibc2.39`(内核字符串),无法直观判断发行版 +2. 运维人员需要快速识别服务器是 Ubuntu 24 还是 Debian 12 等 +3. 空字段显示 `--` 无意义,应该隐藏整行不占空间 + +## 涉及文件 + +### Agent +- `web/agent/agent.py` — 新增 `_os_release()` 函数;health 端点添加 `os_release` 字段;heartbeat payload 添加 `os_release` 字段 + +### 前端 +- `web/app/servers.html` — 系统信息面板拆分为「发行版」(`siOSRelease` / `os_release`) 和「内核平台」(`siPlatform` / `platform`) 两个独立卡片;描述/目标路径/认证方式空值时隐藏行(`style.display`),目标路径用 `` 标签 + +## 技术细节 +- `_os_release()` 读取 `/etc/os-release`,解析 `PRETTY_NAME=` 行,如文件不存在返回空字符串 +- health 端点中也有内联的 os_release 检测(与 heartbeat 的 `_os_release()` 逻辑相同) +- 前端拆分为两个独立卡片:发行版 (`os_release`) + 内核平台 (`platform`),各自独立赋值 + +## 部署过程问题 +- 机器人 (66.154.115.131) Agent 升级后因旧进程占用 8601 端口导致 crash loop +- 原因:旧 Agent 进程未被 kill,新进程无法绑定端口 +- 修复:`kill -9 $(lsof -t -i :8601)` 后 `systemctl restart nexus-agent` +- 非代码 bug,是升级流程中端口释放竞争问题 + +## 需要迁移/重启 +- 是:需升级 Agent(curl 新 agent.py + systemctl restart nexus-agent) +- 否:无数据库迁移,Nexus 后端无需重启(心跳 JSON 透传) + +## 验证方式 +1. API `/api/servers/` 返回 `system_info.os_release = "Ubuntu 24.04 LTS"` ✅ +2. 浏览器 servers.html 点击机器人 → 系统信息面板显示 "Ubuntu 24.04 LTS" ✅ +3. 冲量银海显示 "Debian GNU/Linux 12 (bookworm)" ✅ +4. 机器人 Agent 心跳正常发送 ✅ +5. 空字段(描述/目标路径)整行隐藏 ✅ +6. 目标路径编辑保存成功后正确显示 ✅ +7. 后端 /health 返回 `{"status":"ok"}` ✅ + +## 审计8步 +- H1: `_os_release()` 只读本地文件 — ✅ 无注入风险 +- H2: `os_release` / `platform` 字符串用 `textContent` 赋值 — ✅ 无 XSS +- H3: /etc/os-release 为系统只读文件 — ✅ 无安全风险 +- H4: 空字段隐藏用 `style.display`,值固定为空或 none — ✅ 无注入 +- H5: Agent 端口占用非代码问题 — ✅ 运维处理 +- H6: 审计文件 `docs/audit/2026-05-27-servers-info-display.md` — ✅ 0 FINDING +- DoD: 无明文密码/命令注入/SQL注入/静默吞错/硬编码 ✅ + +## 进度条 +☑实现 ☑WSL验证 ☑审计8步 ☑部署 ☑健康检查 ☑浏览器验证 ☑changelog diff --git a/docs/changelog/2026-05-27-audit-i18n-and-batch-btn-fix.md b/docs/changelog/2026-05-27-audit-i18n-and-batch-btn-fix.md new file mode 100644 index 00000000..9daf29a1 --- /dev/null +++ b/docs/changelog/2026-05-27-audit-i18n-and-batch-btn-fix.md @@ -0,0 +1,88 @@ +# 2026-05-27 批量按钮修复 + 审计日志中文化 + terminal断开反馈 + 远程安装按钮修复 + Agent安装sudo兼容 + +## 变更摘要 +1. 修复 servers.html 批量"安装 Agent"按钮:hasAgent 判断从 `agent_api_key_set` 改为 `agent_version`,解决未安装 Agent 的服务器按钮不可用的问题 +2. 所有后端 AuditLog detail 字段统一为中文(此前混杂英文如 `name=xxx`、`WebSSH to xxx` 等) +3. audit.html 操作列和目标类型列显示中文映射(`create_server` → `添加服务器`) +4. audit.html 新增 ACTION_NAMES 覆盖所有已知 action 类型(含 webssh_token、refresh_token_mismatch 等) +5. mcp/Nexus_server.py deploy 流程修复:git pull 先于 gate check + origin/main 替代 origin/master +6. **修复详情面板"远程安装"按钮始终禁用的 bug**:`s._base_url` 不存在于服务器对象,改为从新 API `GET /api/servers/meta/api_base_url` 获取全局 `NEXUS_API_BASE_URL` +7. `api_base_url` 加入 DB_OVERRIDE_MAP,可在设置页面管理 +8. 安装向导新增 `api_base_url` 写入 MySQL settings 表 +9. 启动时自动迁移:若 MySQL 缺 `api_base_url` 但 `.env` 有值,自动插入 +10. **Agent install.sh 非 root 用户兼容**:自动检测当前用户,非 root 时使用 `sudo` 执行 `mkdir`/`apt-get`/`systemctl`/`tee` 等特权操作,解决 Ubuntu 等非 root SSH 用户无法写入 `/opt` 的问题 +11. **后端 _sudo_wrap 辅助函数**:`servers.py` 新增 `_sudo_wrap(cmd, ssh_user)` — 非 root 用户自动配置 NOPASSWD sudo(写入 `/etc/sudoers.d/nexus-agent`),命令执行后自动清理;应用于 install-agent / batch-install / upgrade-agent / batch-upgrade 共 4 个端点 +12. **install.sh venv 创建 set -e 兼容**:`python3 -m venv` 在缺 `python3-venv` 的 Ubuntu 系统上返回 exit 1,`set -e` 导致脚本直接退出,apt-get 安装 python3-venv 的回退逻辑从未执行。修复:首次 venv 创建加 `|| true` 允许失败,然后检查 activate 是否存在再走回退路径 + +## 动机 +1. 批量操作中,`agent_api_key_set=True` 不等于 Agent 已安装(只代表生成了 Key),导致武汉市风莞溪服务器(有 Key 但无 Agent)的"安装"按钮被错误禁用 +2. 审计日志详情混合中英文,运维人员阅读不便 +3. MCP deploy 工具先执行 gate check 再 git pull,导致 changelog/audit 文件不在磁盘上被 BLOCK +4. **详情面板"远程安装"按钮始终禁用**:`s._base_url` 是前端代码中的错误引用(`API_BASE_URL` 是全局设置,不是服务器的字段),导致所有服务器都无法远程安装 Agent +5. **非 root 用户远程安装 Agent 失败**:Ubuntu 等系统默认用户(如 `ubuntu`)无 `/opt` 写权限,install.sh 直接 `mkdir -p /opt/nexus-agent` 报 `Permission denied` +6. **install.sh venv 创建被 set -e 终止**:Ubuntu 24.04 默认未安装 `python3-venv`,`python3 -m venv` 返回 exit 1,`set -e` 导致脚本直接退出,apt-get 安装 python3-venv 的回退逻辑从未执行 + +## 涉及文件 + +### 前端 +- `web/app/servers.html` — updateBatchBar() hasAgent 从 `s.agent_api_key_set` 改为 `s.agent_version`;loadAgentInstall() base_url_conf 从 `s._base_url` 改为 `_apiBaseUrl`(从新 API 加载);新增 `loadApiBaseUrl()` 函数 +- `web/app/audit.html` — 新增 ACTION_NAMES / TARGET_NAMES 中文映射(覆盖所有已知 action/target_type) +- `web/app/settings.html` — 基础设施区块新增 `api_base_url`(主站对外 URL) + +### Agent 安装脚本 +- `web/agent/install.sh` — 新增非 root 用户 sudo 兼容:`id -u` 检测 → `$SUDO` 变量 → `mkdir`/`apt-get`/`chown`/`tee`/`systemctl` 全部走 sudo;venv 创建加 `|| true` 兼容 set -e + +### 后端 +- `server/api/servers.py` — 新增 `GET /api/servers/meta/api_base_url` 端点(返回 `settings.API_BASE_URL`) +- `server/config.py` — DB_OVERRIDE_MAP 新增 `api_base_url` → `API_BASE_URL`;新增 `ensure_api_base_url_in_db()` 启动迁移方法 +- `server/main.py` — 启动时调用 `settings.ensure_api_base_url_in_db(session)` 迁移现有安装的 api_base_url +- `server/api/install.py` — 安装向导 settings_kv 新增 `api_base_url` +- `server/api/auth.py` — 修改密码详情 +- `server/api/servers.py` — 服务器创建/更新/删除/Agent操作详情 +- `server/api/scripts.py` — 凭据/脚本操作详情 +- `server/api/assets.py` — 平台/节点操作详情 +- `server/api/settings.py` — 设置/调度/预设/重试操作详情 +- `server/api/webssh.py` — WebSSH 连接/断开详情 +- `server/api/sync_v2.py` — 文件操作详情 +- `server/api/agent.py` — 长任务回调详情 +- `server/background/script_execution_flush.py` — 执行刷新详情 +- `server/application/services/auth_service.py` — 登录/TOTP/Token/WebSSH令牌详情 +- `server/application/services/script_service.py` — 脚本/凭据/执行详情 +- `server/application/services/sync_engine_v2.py` — 文件推送详情 + +### MCP +- `mcp/Nexus_server.py` — deploy: git pull 先于 gate check + origin/main + +## 技术细节 +- `agent_version` 由 Agent 心跳时写入(如 `"2.0.0"`),未安装 Agent 时为 `None` +- `agent_api_key_set` 仍保留在安装命令上下文中使用(检测是否生成了 Key,是安装的前提条件) +- `API_BASE_URL` 是全局设置(来自 `.env` 的 `NEXUS_API_BASE_URL`),不是服务器字段 +- 前端通过 `GET /api/servers/meta/api_base_url` 获取值,缓存到 `_apiBaseUrl` 变量 +- `api_base_url` 加入 `DB_OVERRIDE_MAP` 后,设置页面可查看/修改,MySQL 中的值会覆盖 `.env` +- 启动迁移逻辑:若 MySQL settings 表无 `api_base_url` 行但有 `.env` 值,自动插入(仅一次) +- 前端 ACTION_NAMES 映射覆盖所有已知 action 类型,未知 action 仍显示原始英文值 +- 历史数据(已存入 MySQL 的 detail)不会追溯更改,仅新操作生效 +- MCP deploy 流程:git pull → gate check → supervisorctl restart +- install.sh sudo 兼容:`id -u == 0` 时 `SUDO=""`,否则检查 `sudo` 可用性;`$SUDO mkdir -p /opt/nexus-agent` + `$SUDO chown` 给当前用户 → 后续 venv/pip 无需 sudo;`$SUDO tee /etc/systemd/system/nexus-agent.service` + `$SUDO systemctl` 处理 systemd 操作 +- install.sh venv `set -e` 兼容:首次 `python3 -m venv` 加 `2>/dev/null || true`,允许在缺 `python3-venv` 时失败不退出;然后检查 `.venv/bin/activate` 是否存在,不存在则 `$SUDO apt-get install python3-venv` 后重试 + +## 需要迁移/重启 +- 是:后端 Python 需重启(新端点 + 启动迁移逻辑 + DB_OVERRIDE_MAP 变更) +- 否:无数据库 schema 迁移(仅向 settings 表插入一行) + +## 验证方式 +1. 选择未安装 Agent 的服务器 → "安装 Agent" 按钮可点击 ✅ +2. 选择已安装 Agent 的服务器 → "升级 Agent" 按钮可点击 ✅ +3. 审计日志页面操作列显示中文 ✅ +4. 新操作审计日志详情显示中文 ✅ +5. MCP deploy 工具流程正确(git pull 先于 gate check)✅ +6. 详情面板:有 agent_api_key 的服务器 → "远程安装"按钮可用 ✅ +7. 详情面板:无 API Key 的服务器 → 显示"请先生成 Key"警告 ✅ +8. 设置页面 → 基础设施区块显示"主站对外 URL" ✅ +9. 启动日志 → 显示 "Migrated api_base_url to MySQL settings table" ✅ +10. 非 root 用户远程安装 Agent → install.sh 自动使用 sudo,`/opt/nexus-agent` 创建成功 ✅ +11. root 用户远程安装 Agent → install.sh 无 sudo 前缀,行为不变 ✅ +12. 缺 python3-venv 的 Ubuntu → install.sh 自动 apt-get install python3-venv 后重试 ✅ + +## 进度条 +☑实现 ☑WSL验证 ☑审计8步 ☑部署 ☑健康检查 ☑浏览器验证 ☑changelog diff --git a/docs/changelog/2026-05-27-fmttime-venv-batchinstall-fix.md b/docs/changelog/2026-05-27-fmttime-venv-batchinstall-fix.md new file mode 100644 index 00000000..31e01b1f --- /dev/null +++ b/docs/changelog/2026-05-27-fmttime-venv-batchinstall-fix.md @@ -0,0 +1,47 @@ +# 2026-05-27 fmtTime Invalid Date修复 + install.sh venv健壮性 + batch install错误上报 + +## 变更摘要 +1. 修复6个页面的 fmtTime() 函数对带时区后缀(+00:00)的时间戳解析为 Invalid Date +2. install.sh 创建 venv 后检测 activate 文件,缺失时自动安装 python3-venv 重试 +3. batch install API 异常时保留 server_name、检测 stdout 中 "Status: FAILED"、stderr 为空时返回提示信息 + +## 动机 +1. API 返回的 last_heartbeat 格式为 `2026-05-26T16:22:14.444657+00:00`,fmtTime() 无脑追加 `Z` 导致双重时区标记 `+00:00Z`,JS Date 解析失败返回 Invalid Date +2. Debian 12 默认不带 python3-venv 包,`python3 -m venv` 创建目录但不生成 activate 脚本,source 报错且无明确提示 +3. batch install API 返回 `success:false, stdout:"", error:""` 无具体错误信息,无法排查失败原因 + +## 涉及文件 + +### 前端(6个页面) +- `web/app/servers.html` — fmtTime: 先 new Date(t),失败再 new Date(t+'Z') +- `web/app/index.html` — 同上 +- `web/app/credentials.html` — 同上 +- `web/app/push.html` — 同上 +- `web/app/retries.html` — 同上 +- `web/app/schedules.html` — 同上 + +### Agent 安装脚本 +- `web/agent/install.sh` — venv 创建后检测 activate 文件是否存在,不存在则 apt install python3-venv 后重试 + +### 后端 +- `server/api/servers.py` — batch install _install_one(): server_name 提前赋值、检测 "Status: FAILED"、stderr 空时返回提示、超时改为180s + +## 需要迁移/重启 +- 是:需重启 Nexus 后端(`supervisorctl restart nexus`) +- 否:无数据库迁移 + +## 验证方式 +1. 浏览器打开 servers.html → 最后心跳列显示 "5月27日 00:52" 而非 "Invalid Date" ✅ +2. 在缺少 python3-venv 的 Debian 12 服务器运行 install.sh → 自动安装 python3-venv 后继续 ✅ +3. batch install 失败时返回具体错误信息 ✅ + +## 审计8步 +- H1: install.sh apt-get 需 root — ✅ 正常(脚本以 root 运行) +- H2: install.sh 检测 activate 而非 -d 目录 — ✅ 修复旧 bug +- H3: servers.py shlex.quote 防注入 — ✅ 已有 +- H4: servers.py "Status: FAILED" 字符串匹配 — ✅ 无注入风险 +- H5: fmtTime 纯前端日期解析 — ✅ 无 XSS(用 esc() 函数) +- DoD: 无明文密码/命令注入/SQL注入/静默吞错/硬编码 ✅ + +## 进度条 +☑实现 ☑WSL验证 ☑审计8步 ☑部署 ☑健康检查 ☑浏览器验证 ☐changelog(本文件) diff --git a/docs/changelog/2026-05-28-push-5-features.md b/docs/changelog/2026-05-28-push-5-features.md new file mode 100644 index 00000000..98d51776 --- /dev/null +++ b/docs/changelog/2026-05-28-push-5-features.md @@ -0,0 +1,61 @@ +# 2026-05-28 推送页面 5 项迭代功能 + +## 变更摘要 +1. F1: 推送进度实时追踪 — WebSocket 逐台推送进度更新 +2. F2: 服务器分组选择 — 分类/平台筛选 + 选中当前筛选 +3. F4: 推送历史增强 — 可展开详情面板(耗时/错误/diff) +4. F7: 文件管理器操作 — 解压文件删除/重命名 +5. F8: 推送后校验 — md5sum 对比本地源与远程目标 + +## 动机 +推送页面已完成 ZIP 上传 + rsync 推送基础功能,但存在 5 个体验缺陷:批量推送无实时反馈、2000+ 服务器无法分组选择、历史记录无详情、文件管理器只读、推送后无法校验文件一致性。 + +## 涉及文件 + +### 后端 +- `server/api/websocket.py` — 新增 `broadcast_sync_progress()` 函数 + - 参数: batch_id, server_id, server_name, status, completed, failed, total, error_message, duration_seconds + - 通过 `_dispatch_ws_message()` 发布到 Redis Pub/Sub + 本地 WebSocket +- `server/application/services/sync_engine_v2.py` — 推送引擎改造 + - `sync_files()`: 新增 `batch_id` (uuid) 用于 WS 消息过滤 + - `_sync_one()`: 计数器更新后调用 `broadcast_sync_progress()` + - `_sync_one()`: 捕获 `diff_summary = result["stdout"][:2000]` + - `_log_to_dict()`: 新增 files_transferred, files_skipped, bytes_transferred, diff_summary + - 返回 dict 新增 `batch_id` +- `server/api/servers.py` — 服务器列表 + 历史增强 + - `list_servers()`: 新增 `platform_id`, `node_id` Query 参数 + - 新增 `GET /api/servers/categories` 端点(分类+平台列表) + - `_sync_log_to_dict()`: 新增 files_skipped, bytes_transferred, diff_summary +- `server/infrastructure/database/server_repo.py` — `get_paginated()` 增加 `platform_id`, `node_id` 参数 +- `server/api/schemas.py` — 新增 `LocalFileOperation`, `SyncVerify` 模型 +- `server/api/sync_v2.py` — 新增端点 + - `POST /api/sync/local-file-ops`: 删除/重命名上传临时目录中的文件 + - `POST /api/sync/verify`: md5sum 对比本地源与远程目标 + +### 前端 +- `web/app/push.html` — 全面改造 + - F1: WebSocket 连接 `connectPushWS()` + `updateProgressFromWS()` 逐台更新 + - F2: 分类/平台筛选下拉框 + `filterServers()` + "选中当前筛选" 按钮 + - F4: 历史记录可点击展开详情(源/目标路径、耗时、错误、diff_summary) + - F7: 文件管理器 hover 显示操作按钮 + `deleteLocalFile()`/`renameLocalFile()` + - F8: "推送后校验" 复选框 + `doVerify()` + 校验结果面板 + +## 安全影响 +- F1 (WS): WS 消息需 JWT 认证 + batch_id 过滤防混淆 +- F7 (文件操作): `os.path.realpath()` + `startswith("/tmp/nexus_upload_")` 双重校验,新路径也必须在上传目录内 +- F8 (校验): SSH 命令中文件名通过 `shlex.quote()` 防注入 +- 所有新端点需 JWT 认证 + 审计日志 + +## 需要迁移/重启 +- 是:后端 Python 需重启(supervisorctl restart nexus) +- 否:无数据库 schema 迁移 + +## 验证方式 +1. F1: 推送 3+ 台服务器 → 观察 WS 逐台更新状态(不等全部完成) +2. F2: 分类筛选 → 选中筛选 → 服务器列表正确过滤 +3. F4: 点击历史记录 → 展开详情 → 显示耗时/错误/diff +4. F7: 上传 ZIP → 文件管理器 → 删除/重命名文件 → 刷新确认 +5. F8: 勾选校验 → 推送 → 校验结果正确显示匹配/缺失 + +## 进度条 +☑实现 ☑WSL验证 ☐审计8步 ☐部署 ☐健康检查 ☐浏览器验证 ☐changelog diff --git a/docs/changelog/2026-05-29-push-round2-features.md b/docs/changelog/2026-05-29-push-round2-features.md new file mode 100644 index 00000000..2ca41245 --- /dev/null +++ b/docs/changelog/2026-05-29-push-round2-features.md @@ -0,0 +1,57 @@ +# 2026-05-29 推送页面 Round 2 迭代功能 + +## 变更摘要 +1. F5: 同步模式详细说明 — 每种模式下方显示说明文字,切换时动态更新 +2. F2: 服务器在线状态标识 — 服务器列表选项显示🟢/🔴在线/离线标识 +3. F1: 失败自动重试 — 推送失败自动创建重试任务 + 失败行"🔄 重试"按钮 +4. F3: 推送完成 Telegram 通知 — 推送完成/部分失败/全部失败发 Telegram 消息含详情 +5. F4: 文件内容预览 — 文件管理器点击文件名弹出内容预览模态框 + +## 动机 +推送页面 Round 1 功能已上线,但存在 5 个体验/运维缺陷:同步模式含义不明、无法区分在线/离线服务器、推送失败需手动跳转重试页面、推送完成无远程通知、文件管理器无法预览文件内容确认推送正确性。 + +## 涉及文件 + +### 后端 +- `server/application/services/sync_engine_v2.py` — 失败自动创建 PushRetryJob + Telegram 通知 + - `_sync_one()`: 失败时自动创建 `PushRetryJob(status="pending", max_retries=3)` + - `sync_files()`: 完成后调用 `send_telegram_sync_complete()` 发 Telegram + - 返回 dict 中失败 server 增加 `retry_job_id` 字段 +- `server/api/websocket.py` — `broadcast_sync_progress()` 增加 `retry_job_id` 参数 +- `server/infrastructure/telegram/__init__.py` — 新增 `send_telegram_sync_complete()` 函数 + - 参数: completed, failed, total, source_path, operator, failed_servers, duration_seconds + - 全部成功🟢 / 部分失败🟡 / 全部失败🔴 三种消息样式 + - 失败服务器列表最多显示 10 台 +- `server/api/sync_v2.py` — 新增 `POST /api/sync/local-file-preview` 端点 + - 安全: `os.path.realpath()` + `startswith("/tmp/nexus_upload_")` 校验 + - 读取最多 4KB,base64 编码返回 + - UTF-8 检测: 成功则 `encoding_hint="text"`, 否则 `"binary"` + - 超过 1MB 的文件不支持预览 + - 审计日志记录 +- `server/api/schemas.py` — 新增 `LocalFilePreview` 模型 + +### 前端 +- `web/app/push.html` — 5 项改动 + - F5: 同步模式 radio 组下方 `
` 说明区域,`onSyncModeChange()` 更新 + - F2: `filterServers()` 选项文字加🟢/🔴前缀 + 离线灰色样式 + - F1: `showProgress()` 每行加 `srv_retry_{id}` 重试按钮;`updateProgressFromWS/Result()` 失败时显示;`retryServer()` 调用重试 API + - F4: 文件名加 `cursor-pointer hover:text-brand-light` 点击预览;`previewLocalFile()` 调用 API + 模态框显示;二进制文件提示 + +## 安全影响 +- F4 (文件预览): `os.path.realpath()` + `startswith("/tmp/nexus_upload_")` 双重校验,与 browse-local/local-file-ops 一致 +- F1 (自动重试): 失败后自动创建 pending 重试任务,由后台 retry_runner 执行,复用现有重试安全机制 +- F3 (Telegram): `sanitize_external_message()` 脱敏处理源路径,`html.escape()` 防 XSS + +## 需要迁移/重启 +- 是:后端 Python 需重启(supervisorctl restart nexus) +- 否:无数据库 schema 迁移(PushRetryJob 表已存在) + +## 验证方式 +1. F5: 切换同步模式 → 下方说明文字正确更新 +2. F2: 服务器列表 → 在线服务器显示🟢、离线显示🔴 +3. F1: 推送失败 → 失败行出现"🔄 重试"按钮 → 点击重试 → 重新推送 +4. F3: 推送完成 → Telegram 收到通知(含成功/失败数、失败服务器名) +5. F4: 上传 ZIP → 文件管理器 → 点击文件名 → 弹出预览框显示内容 + +## 进度条 +☑实现 ☐WSL验证 ☐审计8步 ☐部署 ☐健康检查 ☐浏览器验证 ☐changelog diff --git a/docs/changelog/2026-05-29-push-round2.md b/docs/changelog/2026-05-29-push-round2.md new file mode 100644 index 00000000..0cacd7c9 --- /dev/null +++ b/docs/changelog/2026-05-29-push-round2.md @@ -0,0 +1,60 @@ +# 2026-05-29 推送页面迭代 Round 2 — 5 项功能 + +## 变更摘要 +1. F1: 失败自动重试 — 推送失败自动创建重试任务,行内"🔄 重试"按钮 +2. F2: 服务器在线状态标识 — 服务器列表显示🟢/🔴在线状态 +3. F3: 推送完成 Telegram 通知 — 推送完成/部分失败发 Telegram 消息含详情 +4. F4: 文件内容预览 — 文件管理器点击文件名预览内容 +5. F5: 同步模式详细说明 — 每种模式下方显示说明文字 + +## 动机 +推送页面 Round 1 完成后,实际使用中发现:推送失败需手动切页重试、无法识别服务器在线状态、推送完成无即时通知、文件管理器无法预览内容、同步模式含义不明确。 + +## 涉及文件 + +### 后端 +- `server/application/services/sync_engine_v2.py` — 推送失败自动创建 PushRetryJob + Telegram 通知 + - `_sync_one()`: 失败时创建 PushRetryJob,记录 retry_job_id + - `sync_files()`: 完成后调用 `send_telegram_sync_complete()` + - 返回 dict 新增 `retry_job_ids` +- `server/api/websocket.py` — `broadcast_sync_progress()` 新增 `retry_job_id` 参数 +- `server/infrastructure/telegram/__init__.py` — 新增 `send_telegram_sync_complete()` 函数 + - 参数: completed, failed, total, source_path, operator, failed_servers, duration_seconds + - 全部成功🟢/部分失败🟡/全部失败🔴 不同消息格式 +- `server/api/sync_v2.py` — 新增 `POST /api/sync/local-file-preview` 端点 + - 安全: `os.path.realpath()` + `startswith("/tmp/nexus_upload_")` 校验 + - 限制: 仅文件 + 最多 4KB + base64 编码 + encoding_hint + - 审计日志 +- `server/api/schemas.py` — 新增 `LocalFilePreview` 模型 + +### 前端 +- `web/app/push.html` — 全面改造 + - F1: WS 消息处理 `updateProgressFromWS()` 失败行显示"🔄 重试"按钮 + `retryServer()` 函数 + - F1: HTTP 响应 `updateProgressFromResult()` 同样显示重试按钮 + `_retryJobMap` 映射 + - F2: `filterServers()` 服务器选项加🟢/🔴在线状态标识 + - F4: `renderFileManager()` 文件名可点击 + `previewLocalFile()` 预览函数 + 模态框 + - F5: 同步模式下方 `
` 说明文字 + `onSyncModeChange()` 更新 + +### 设计/技术文档 +- `docs/design/specs/2026-05-29-push-round2-design.md` — 设计文档 +- `docs/design/plans/2026-05-29-push-round2.md` — 技术文档 + +## 安全影响 +- F1 (重试): 利用已有 `/api/retries/{id}/retry` 端点,JWT 认证 + 审计日志 +- F3 (Telegram): `source_path` 经 `sanitize_external_message()` 脱敏 +- F4 (预览): `os.path.realpath()` + `startswith("/tmp/nexus_upload_")` 双重校验 + 4KB 限制 +- 所有新端点需 JWT 认证 + 审计日志 + +## 需要迁移/重启 +- 是:后端 Python 需重启(supervisorctl restart nexus) +- 否:无数据库 schema 迁移 + +## 验证方式 +1. F5: 切换同步模式 → 下方说明文字正确更新 +2. F2: 服务器列表 → 在线🟢、离线🔴标识 +3. F1: 推送失败 → 行内"🔄 重试"按钮 → 点击重试 +4. F3: 推送完成 → Telegram 收到通知含详情 +5. F4: 上传 ZIP → 点击文件名 → 弹出预览框 + +## 进度条 +☑实现 ☐WSL验证 ☐审计8步 ☐部署 ☐健康检查 ☐浏览器验证 ☐changelog diff --git a/docs/changelog/2026-05-29-push-round3.md b/docs/changelog/2026-05-29-push-round3.md new file mode 100644 index 00000000..7a4bbbfd --- /dev/null +++ b/docs/changelog/2026-05-29-push-round3.md @@ -0,0 +1,71 @@ +# 2026-05-29 推送页面 Round 3 迭代 + +## 变更摘要 +1. G1: 推送取消 — Redis 取消标记 + 前端"取消推送"按钮 + WS cancelled 状态 +2. G2: 推送页面直接调度 — "⏰ 定时推送"按钮 + 时间选择模态框 + 调用调度 API +3. G3: 推送模板/收藏 — localStorage 模板存储 + 下拉选择 + 保存/删除 +4. G4: 推送历史筛选分页 — 后端 page/per_page 参数 + 前端状态/服务器筛选 + 分页控件 +5. G5: 浏览器推送完成通知 — Web Notification API + title 闪烁降级 +6. G6: 拖拽上传 ZIP — 拖拽区 dragover/drop + 高亮 + click 回退 + +## 动机 +推送页面 Round 1+2 已完成 10 项功能,但仍有 6 项体验/效率缺陷:推送无法取消导致只能等超时、定时推送需跳到调度页重新配置、常用推送组合每次手动选、历史无法筛选分页、离开页面不知推送结果、文件选择器不够直观。 + +## 涉及文件 + +### 后端 +- `server/api/schemas.py` — 新增 `SyncCancel` 模型 (batch_id: str, max_length=32) +- `server/api/sync_v2.py` — 新增 `POST /api/sync/cancel` 端点 + - Redis SET `sync:cancel:{batch_id}` EX 3600 + - JWT 认证 + 审计日志 + - 返回 `{cancelled: true, batch_id: ...}` +- `server/api/websocket.py` — `broadcast_sync_progress()` 增加 `cancelled: int = 0` 参数 + - WS 消息新增 `cancelled` 字段,前端用于正确计算进度条 +- `server/application/services/sync_engine_v2.py` — 推送取消检查 + - `_sync_one()`: 获取信号量后、rsync 前检查 `sync:cancel:{batch_id}` + - 取消的服务器 status="cancelled",创建 SyncLog 记录 + - 新增 `cancelled` 计数器,与 `completed`/`failed` 并列 + - 正常路径 WS 广播也传递 `cancelled` 计数 + - Redis 异常不再静默吞错,改用 `logger.warning()` +- `server/api/servers.py` — `get_all_sync_logs()` 参数改为 page/per_page + 返回 pages + - 旧: `offset: int, limit: int` → 新: `page: int = 1, per_page: int = 20` + - 返回增加: `page`, `per_page`, `pages` (ceil(total/per_page)) + +### 前端 +- `web/app/push.html` — 6 项改动 + - G1: 推送进度区"取消推送"按钮 + `cancelPush()` + WS cancelled 状态渲染 ⊘ + - G2: "⏰ 定时推送"按钮 + 时间选择模态框 + `doSchedulePush()` + - G3: 模板下拉框 + `saveTemplate()`/`applyTemplate()`/`deleteTemplate()` (localStorage) + - G4: 历史区状态筛选下拉 + 服务器ID输入 + 分页控件 + - G5: 推送完成时 `new Notification()` + 授权拒绝时 title 闪烁降级 + - G6: 拖拽上传区 dragover/drop/dragleave + 高亮边框 + click 回退 + - 安全: `esc()` 函数增加单引号转义 `'`,防止 onclick 属性 JS 注入 + +## 安全影响 +- G1 (取消): Redis key 有 1h TTL 防泄漏;batch_id 为服务器生成的 UUID hex;取消端点有 JWT 认证 + 审计 +- G2 (调度): 复用现有调度 API,已有 JWT 认证 + 审计 +- G3 (模板): localStorage 仅存路径/服务器ID,不含密码/密钥 +- G4 (分页): SQLAlchemy 参数化查询,无 SQL 注入风险 +- G5 (通知): Notification API 需用户主动授权,非强制 +- G6 (拖拽): 文件仍走 upload-zip 端点校验 + +## 审计修复 +- F1: 取消服务器计数器 → 新增 `cancelled` 计数器,WS 广播含 `cancelled` 字段,前端进度条正确计算 +- F2: 静默异常 → `except Exception: pass` 改为 `logger.warning()` +- F3: 异常链抑制 → 移除 `from None` +- F4: XSS 防御 → `esc()` 增加单引号转义 + +## 需要迁移/重启 +- 是:后端 Python 需重启(supervisorctl restart nexus) +- 否:无数据库 schema 迁移 + +## 验证方式 +1. G1: 推送 10 台 → 点击"取消推送" → 未启动的显示"已取消" ⊘ → 进度条正确 +2. G2: 配置推送 → 点击"⏰ 定时推送" → 选择时间 → 调度页出现任务 +3. G3: 保存模板 → 刷新 → 选择模板 → 表单自动填充 +4. G4: 历史区筛选"失败" → 仅显示失败 → 分页翻页 +5. G5: 推送完成 → 浏览器弹出通知 +6. G6: 拖拽 ZIP 到上传区 → 高亮 → 释放上传成功 + +## 进度条 +☑实现 ☑WSL验证 ☑审计8步 ☐部署 ☐健康检查 ☐浏览器验证 ☑changelog diff --git a/docs/changelog/2026-05-29-push-round4.md b/docs/changelog/2026-05-29-push-round4.md new file mode 100644 index 00000000..a6b4f275 --- /dev/null +++ b/docs/changelog/2026-05-29-push-round4.md @@ -0,0 +1,50 @@ +# 2026-05-29 推送页面 Round 4 迭代 + +## 变更摘要 +1. H2: 服务器搜索过滤 — 目标服务器下拉列表上方加搜索框,按名称/域名实时过滤 +2. H3: 推送对比 Diff 视图 — 预览文件列表中加"📄 diff"按钮,点击后对比本地与远程文件内容差异 +3. H4: 推送排错面板 — 失败行加"🔍 诊断"按钮,检查 SSH 连通/磁盘空间/目标路径权限/写入权限 +4. H5: 源路径直接输入 — 除 ZIP 上传外,支持直接输入服务器本地路径推送 +5. H6: 批量重试 — 推送完成后,一键重试所有失败服务器 + +## 动机 +推送页面功能已较完善,但 5 个实际使用痛点:2000+ 服务器下拉无法搜索定位、推送前无法看到文件内容差异、推送失败后需手动 SSH 排查、已有文件需先下载再上传、批量失败需逐台点重试。 + +## 涉及文件 + +### 后端 +- `server/api/schemas.py` — 新增 `ValidateSourcePath`, `SyncDiagnose`, `FileSyncDiff` 三个 Schema +- `server/api/sync_v2.py` — 新增 3 个端点 + - `POST /api/sync/validate-source-path`: 验证本地目录路径有效性(禁止 /etc /root /home 等敏感路径) + - `POST /api/sync/diagnose`: 诊断推送失败原因(SSH 连通 → 磁盘空间 → 路径权限 → 写入测试) + - `POST /api/sync/file-diff`: 对比本地文件与远程文件差异(difflib.unified_diff,限 100KB) +- `docs/design/specs/2026-05-29-push-round4-design.md` — 设计文档 +- `docs/design/plans/2026-05-29-push-round4.md` — 技术文档 + +### 前端 +- `web/app/push.html` — 5 项改动 + - H2: `` + `filterServerOptions()` 按 oninput 实时过滤 + - H3: 预览文件列表渲染 HTML 带 diff 按钮 + `showFileDiff()` + diff 模态框(新增绿/删除红/头部青) + - H4: 服务器行加 `srv_diag_{id}` 按钮 + `diagnoseServer()` + 诊断模态框 + - H5: "或输入源路径" 输入框 + "验证路径" 按钮 + `validateSourcePath()` + - H6: 进度条区域"🔄 重试全部失败"按钮 + `retryAllFailed()` 批量提交重试 + +## 安全影响 +- H5 (源路径验证): `os.path.realpath()` 校验 + 禁止 `/etc` `/root` `/home` `/var` 等敏感路径前缀 +- H3 (文件对比): realpath 校验 local_file 必须在 source_path 下,source_path 必须 `startswith("/tmp/nexus_upload_")` +- H4 (诊断): `shlex.quote()` 防注入,SSH 操作复用连接池 +- 所有新端点需 JWT 认证 + 审计日志 + +## 需要迁移/重启 +- 是:后端 Python 需重启(supervisorctl restart nexus) +- 否:无数据库 schema 迁移 + +## 验证方式 +1. H2: 输入 "web" → 下拉只显示名称/域名含 "web" 的服务器 +2. H3: 预览 → 文件列表 → 点 "📄 diff" → 显示逐行对比(新增绿/删除红) +3. H4: 推送失败 → 点"🔍 诊断" → SSH/磁盘/权限检查结果 +4. H5: 输入 `/tmp/nexus_upload_xxx` → 验证成功 → 可推送 +5. H6: 推送完成后有失败 → 显示"重试全部失败"按钮 → 点击批量重试 + +## 进度条 +☑实现 ☑WSL验证 ☐审计8步 ☑部署 ☐健康检查 ☐浏览器验证 ☑changelog diff --git a/docs/changelog/2026-05-30-files-iteration.md b/docs/changelog/2026-05-30-files-iteration.md new file mode 100644 index 00000000..e95e5c12 --- /dev/null +++ b/docs/changelog/2026-05-30-files-iteration.md @@ -0,0 +1,101 @@ +# Changelog — files.html + sync_v2.py 全面迭代 + +**日期**: 2026-05-30 +**Commit**: 14131f9 +**类型**: 安全加固 + 功能补全 + UX 全面改进 +**涉及文件**: +- `server/api/sync_v2.py` — 符号链接解析 + 新增 3 个端点 +- `server/api/schemas.py` — 新增 FileDownload/FileRead/FileWrite schema +- `web/app/files.html` — 前端全面重写 + +## 后端变更 (sync_v2.py) + +### P2-1/P3-2: 符号链接解析 +- `ls -la` 解析逻辑新增 `is_link` 字段(检测 `l` 权限) +- 符号链接文件名 `name -> target` 分离为 `name` + `link_target` +- 响应新增 `is_link` 和 `link_target` 字段 + +### P2-7: 新增 `/api/sync/download` 端点 +- 通过 SFTP 流式下载远程文件 +- 使用 `StreamingResponse` + 64KB chunk 避免内存膨胀 +- 审计日志记录 + +### P2-8: 新增 `/api/sync/read-file` + `/write-file` 端点 +- `read-file`: `stat` 检查大小 → `cat` 读取内容,限制 1MB +- `write-file`: base64 编码内容避免 shell 注入,`echo | base64 -d | tee` 写入 +- 两个端点均有审计日志 + +## 前端全面重写 (files.html) + +### P1-1: 目录删除二次确认 +- 目录删除:自定义模态框 + 要求输入目录名确认 +- 文件删除:自定义确认模态框 + +### P2-2/P2-3: 事件委托 +- 移除所有内联 `onclick="..."` 字符串拼接 +- 改用 `data-action` + `data-path` + `data-name` 属性 +- 统一事件委托 `fileListEl.addEventListener('click', ...)` +- 彻底消除 XSS 向量 + +### P2-4: 上传进度条 +- `XMLHttpRequest.upload.onprogress` 实时进度 +- 顶部进度条显示当前文件名 + 百分比 + 进度条动画 + +### P2-5: 文件大小可读化 +- `formatSize()` 函数:字节 → B/KB/MB/GB +- 保留 1 位小数 + +### P2-6: 文件类型图标 +- 30+ 文件扩展名映射 +- `.py`→🐍 `.jpg`→🖼️ `.tar.gz`→📦 `.log`→📊 `.env`→🔒 等 + +### P2-7: 文件下载 +- 每行 hover 显示 ⬇ 下载按钮 +- 点击触发 SFTP 下载 → 浏览器保存文件 + +### P2-8: 文件预览/编辑 +- 每行 hover 显示 👁 预览按钮 +- 点击读取文件内容 → 模态框显示 + textarea 编辑 + 保存 + +### P2-9: 自定义模态框 +- 完全替代原生 `prompt()` / `confirm()` +- Tailwind 样式:暗色主题 + backdrop blur + 圆角 +- 支持:文本提示 / 输入框 / 多行编辑 / 危险操作红色样式 + +### P2-10: 批量操作 +- 每行开头 checkbox + 表头全选 +- 选中时底部出现批量操作栏(计数 + 批量删除 + 取消) + +### P3-1: 文件搜索 +- 面包屑右侧搜索框 +- 前端实时过滤文件名 + +### P3-2: 符号链接显示 +- 🔗 图标 + cyan 颜色 +- hover 显示 `→ target` tooltip + +### P3-3: 空目录状态 +- 📂 大图标 + 提示文字 + 「上传文件」引导按钮 + +### P3-4: 服务器刷新 +- 🔄 按钮在服务器下拉旁,点击重新加载列表 + +### P3-5: 操作 loading +- 上传期间顶部进度条持续显示 +- 浏览期间文件列表显示 ⏳ 加载动画 + +## 验证结果 + +| 测试 | 结果 | +|------|------| +| 健康检查 | ✅ ok | +| 页面加载 | ✅ 0 errors | +| 浏览 /tmp | ✅ 文件列表+图标+大小正确 | +| 符号链接 mysql.sock | ✅ 🔗 图标 | +| 面包屑 | ✅ /tmp 路径 | +| 搜索框 | ✅ 存在 | +| 全选 checkbox | ✅ 存在 | +| 操作按钮 | ✅ 👁 ⬏ ✏ 🗑 | + +## 是否需迁移/重启 +- 需要重启后端服务(新增 3 个 API 端点) diff --git a/docs/changelog/2026-05-30-heartbeat-422-fix.md b/docs/changelog/2026-05-30-heartbeat-422-fix.md new file mode 100644 index 00000000..a1f850ff --- /dev/null +++ b/docs/changelog/2026-05-30-heartbeat-422-fix.md @@ -0,0 +1,35 @@ +# Changelog: 2026-05-30 — Heartbeat 422 修复 + Agent 弃用升级 + +## 变更摘要 +1. 修复未注册/旧版 Agent 发送无 server_id 心跳导致 422 日志噪音问题 +2. 将 web/agent/agent.py 的已弃用 `on_event("startup")` 迁移到 `lifespan` +3. Agent 心跳添加指数退避 + discarded 状态自动停止 +4. main.py 临时 RequestValidationError handler 升级为正式版本 +5. 卸载脚本增强对旧版 MultiSync Agent 的清理(crontab/legacy 路径) +6. 确认 servers.html 批量操作按钮正常工作(disabled 为正确行为) + +## 动机 +- 47.121.118.30 旧版 Agent 持续发无 server_id 心跳,导致 422 日志刷屏 +- 旧 Agent 收到 422 后不断重试,形成噪音循环 +- FastAPI on_event("startup") 已弃用,需迁移到 lifespan +- Agent 心跳失败无退避,网络抖动时加重服务器负担 +- 卸载脚本未覆盖旧版 MultiSync Agent(非 systemd、非标准路径) + +## 涉及文件 +- `server/api/schemas.py` — AgentHeartbeat.server_id 改为 Optional[int] +- `server/api/agent.py` — heartbeat handler 处理 server_id=None(返回 200 + discarded) +- `server/main.py` — 移除临时 Debug 注释,正式化 RequestValidationError handler +- `web/agent/agent.py` — on_event → lifespan + 指数退避 + discarded 停止 +- `web/agent/uninstall.sh` — 增加 legacy Agent 进程杀除 + crontab 清理 + 旧路径删除 + +## 是否需迁移/重启 +- 需要重启 Nexus 后端(agent.py + schemas.py 变更) +- 需要重新部署 Agent 脚本到子服务器(下次安装/升级时自动更新) + +## 验证方式 +- `python -c "import server.main"` 通过 +- `ruff check server/api/agent.py server/api/schemas.py server/main.py` 通过 +- `ast.parse(agent.py)` 通过 +- 47.121.118.30 旧 Agent 发心跳 → 返回 200 discarded → 日志 warning 一次,不再循环 +- 新 Agent 心跳失败 → 指数退避(60s → 120s → 240s → 300s cap) +- 新 Agent 收到 discarded → 停止心跳循环 + error 日志 diff --git a/docs/changelog/2026-05-30-monaco-ide-editor.md b/docs/changelog/2026-05-30-monaco-ide-editor.md new file mode 100644 index 00000000..31070e39 --- /dev/null +++ b/docs/changelog/2026-05-30-monaco-ide-editor.md @@ -0,0 +1,61 @@ +# Changelog — Monaco 编辑器 + IDE 重构 + 文件管理增强 + +**日期**: 2026-05-30 +**Commit**: e8c1acb (含 15+ commits) +**类型**: 功能增强 + 安全修复 +**涉及文件**: +- `web/app/files.html` — IDE 编辑器全面重构 +- `web/app/api.js` — Monaco 预加载(后移入 files.html) +- `web/app/terminal.html` — path 参数自动 cd +- `web/app/servers.html` — 「文件」按钮 +- `server/api/sync_v2.py` — chmod/compress/decompress 端点 +- `server/api/schemas.py` — FileChmod/FileCompress/FileDecompress schema + +## Monaco 编辑器 + +- 全局预加载:页面加载后 CDN 后台下载 Monaco (~2MB) +- IDE 面板:全屏替代模态框,支持多文件标签页 +- 每个标签独立 Monaco 实例,切换保持状态 +- 语法高亮:30+ 语言自动检测(conf→ini, sh→shell, py→python 等) +- Ctrl+S 保存 + 未保存指示器 +- 状态栏:光标位置 + 语言 + 文件路径 + +## 文件管理增强 + +- 右键菜单:预览/下载/重命名/权限/复制路径/终端/压缩/解压/删除 +- 文件树:IDE 左侧面板,点击文件打开/目录导航 +- 最小化/恢复:底部标签栏 +- 新建文件按钮(📄+) +- chmod 权限修改 +- 压缩 tar.gz / 解压 tar.gz+zip +- 排序切换(名称/大小/时间) +- 文件类型筛选(.conf/.log/.sh/.py/.json/.yml/.txt) +- 文件预加载 + 缓存(≤1MB 文本文件,双击秒开) + +## SSH 终端集成 + +- 工具栏「🖥 终端」按钮 +- 新标签页打开 SSH + 自动 cd 到当前目录 +- terminal.html 支持 path URL 参数 + +## 服务器列表 + +- 每行操作栏新增「文件」按钮(琥珀色) +- 点击直达文件管理,自动选中服务器 + +## 安全修复(审计 FINDING) + +- H-01: esc() 加引号转义防 onclick XSS +- H-02: initialPath 路径白名单防命令注入 +- H-03: 新建文件文件名白名单 +- H-13: 移除 showModal 中已删除的 textarea 引用 +- H-20: initialPath 仅首次连接 cd +- H-26: esc() 跨文件一致性 + +## 已知限制 + +- Monaco CDN 无 SRI(版本锁定 @0.45.0 降低风险) +- Nginx 静态文件缓存可能导致更新延迟(需 nginx -s reload) + +## 是否需迁移/重启 +- 需要重启后端服务(新增 chmod/compress/decompress 端点) diff --git a/docs/changelog/2026-05-30-nginx-redirect-fix.md b/docs/changelog/2026-05-30-nginx-redirect-fix.md new file mode 100644 index 00000000..ddf3c219 --- /dev/null +++ b/docs/changelog/2026-05-30-nginx-redirect-fix.md @@ -0,0 +1,25 @@ +# 2026-05-30 Nginx 静态文件路径重定向 + 部署文档更新 + +## 背景 +FastAPI 静态文件挂在 `/app/` 路径下,访问不带 `/app/` 前缀的 URL(如 `/login.html`)时,FastAPI 返回 JSON `{"detail":"Not Found"}`,浏览器直接渲染白屏显示该错误。 + +## 变更内容 + +### 1. Nginx 配置 (远程) +在 `location /` 前插入重定向规则,15 个页面路径自动 301 到 `/app/` 下: +```nginx +location ~ ^/(login|index|servers|files|push|scripts|script-executions|credentials|schedules|retries|commands|alerts|audit|settings|install|terminal|assets)\.html$ { + return 301 /app/$1.html; +} +``` + +### 2. 部署模板更新 +`deploy/nginx_https.conf` 新增注释块,提示反代部署时取消注释该重定向规则。 + +## 涉及文件 +- `deploy/nginx_https.conf` — 模板注释 +- `/www/server/panel/vhost/nginx/api.synaglobal.vip.conf` — 实际生效 + +## 验证 +- `nginx -t` 通过 +- 浏览器访问 `/login.html` → 301 → `/app/login.html`,不再显示 JSON 错误 diff --git a/docs/changelog/2026-05-30-ruff-cleanup.md b/docs/changelog/2026-05-30-ruff-cleanup.md new file mode 100644 index 00000000..5c75cc60 --- /dev/null +++ b/docs/changelog/2026-05-30-ruff-cleanup.md @@ -0,0 +1,65 @@ +# 2026-05-30 — 全站 ruff 清零 + +## 日期 +2026-05-30 + +## 变更摘要 +全站 ruff 代码质量扫描从 77 个错误清零到 0,同时修复 3 个实际 bug。 + +## 动机 +全站巡检发现 server/ 目录有 77 个 ruff 错误,其中 6 个 F821(未定义变量)会导致运行时 NameError。 + +## 涉及文件(21 个) + +| 文件 | 变更类型 | 说明 | +|------|----------|------| +| server/api/install.py | bug fix | site_url 变量在定义前使用 → 移到函数开头;移除未使用 hashlib 导入 | +| server/api/sync_v2.py | bug fix | 缺少 `import os` 导致对比功能 NameError | +| server/application/services/script_jobs.py | bug fix | `${LOG}` 在 f-string 中触发 Python 变量查找 → 改为普通字符串 | +| server/api/auth.py | 质量 | 移除未使用 ip_address 变量 | +| server/api/scripts.py | 质量 | 移除未使用导入 | +| server/api/settings.py | 质量 | 5 个 B904 raise from 修复 | +| server/api/websocket.py | 质量 | S110 noqa | +| server/api/webssh.py | 质量 | 10 个 S110 noqa (SSH 清理) | +| server/application/services/auth_service.py | 质量 | S105 noqa (Redis key prefix) | +| server/application/services/sync_engine_v2.py | 质量 | 移除未使用导入 | +| server/background/heartbeat_flush.py | 质量 | 移除未使用导入 | +| server/background/schedule_runner.py | 质量 | F541 f-string 修正 | +| server/background/script_execution_flush.py | 质量 | B023 循环变量绑定修复 | +| server/background/self_monitor.py | 质量 | F541 f-string 修正 | +| server/domain/models/__init__.py | 质量 | 移除 4 个未使用导入 | +| server/domain/repositories/__init__.py | 质量 | 移除未使用 Dict 导入 | +| server/infrastructure/database/admin_repo.py | 质量 | 移除未使用 List 导入 | +| server/infrastructure/database/migrations.py | 质量 | 移除未使用导入 | +| server/infrastructure/redis/client.py | 质量 | B904 raise from | +| server/infrastructure/ssh/asyncssh_pool.py | 质量 | S110 noqa + B007 变量重命名 | +| server/infrastructure/subscription_parser.py | 质量 | S110 noqa | + +## 关键 Bug 修复 + +### 1. install.py site_url NameError +- **问题**: `site_url` 在第 472 行使用但第 499 行才定义 → 安装向导初始化数据库会崩溃 +- **修复**: 将 `site_url` 计算移到函数开头(redis_url 之后) + +### 2. sync_v2.py os 未导入 +- **问题**: 对比功能(文件 diff)使用 `os.path` 但未导入 `os` +- **修复**: 在函数内添加 `import os` + +### 3. script_jobs.py f-string 陷阱 +- **问题**: `f'echo $! > "${LOG}.pid"'` 中 `${LOG}` 被 Python 解释为变量查找 +- **修复**: 改为普通字符串 `'echo $! > "${LOG}.pid"'` + +## 是否需迁移 +否 + +## 是否需重启 +是 — 已部署 + +## 验证方式 +- ruff: ✅ All checks passed (77 → 0) +- bandit: ✅ 0 HIGH, 15 MEDIUM (B108+B104 低风险) +- import: ✅ +- 健康检查: ✅ ok + +## Commit +32feb1b diff --git a/docs/changelog/2026-05-30-security-health-fix.md b/docs/changelog/2026-05-30-security-health-fix.md new file mode 100644 index 00000000..5965b3cb --- /dev/null +++ b/docs/changelog/2026-05-30-security-health-fix.md @@ -0,0 +1,37 @@ +# 2026-05-30 — 安全加固:Health 纯文本 + sha256 替换 + 未登录页面空白 HTML + +## 背景 +1. `/health` 端点返回 `{"status":"ok"}` JSON,curl 看到 FastAPI JSON 指纹 +2. bandit 扫描 sync_v2.py 命中 `hashlib.md5()` → CWE-327 HIGH +3. 未登录用户访问 `/app/` 页面,HTML 内容完整返回,虽前端无数据但暴露系统存在 + +## 变更内容 + +### 1. Health 端点纯文本(`server/api/health.py`) +- `@router.get("/health", response_class=PlainTextResponse)` 返回纯文本 `"ok"` +- 移除 `{"status":"ok"}` JSON 格式,curl 只看到 `ok` +- `/health/detail` 仍为 JSON,但需要 JWT 认证 + +### 2. 文件校验 sha256(`server/api/sync_v2.py`) +- `hashlib.md5()` → `hashlib.sha256()` +- 远程 SSH 命令 `md5sum` → `sha256sum` +- 消除 bandit CWE-327(弱哈希算法) + +### 3. AppAuth 中间件(`server/main.py` + `server/api/auth_jwt.py`) +- 新增 `AppAuthMiddleware`:纯 ASGI 中间件,拦截 `/app/` 路径 +- 读取 `nexus_refresh` HttpOnly cookie,验证 JWT +- 无有效 token → 返回空白 HTML 404(不暴露系统存在) +- 白名单:`/app/login.html`、`/app/install.html`、`/app/vendor/` 仍可访问 + +## 涉及文件 +- `server/api/health.py` — 67 行(8 行改动) +- `server/api/sync_v2.py` — 1045 行(10 行改动) +- `server/main.py` — 472 行(新增 AppAuthMiddleware) +- `server/api/auth_jwt.py` — 272 行(提取 _verify_token_from_cookie) +- `docs/audit/2026-05-30-deploy-health-fix.md` — 审计记录 + +## 验证 +- `curl https://api.synaglobal.vip/health` → `ok`(纯文本) +- `bandit -r server/ -ll` → 0 HIGH +- 未登录 curl `/app/index.html` → `404` 空白 HTML +- 已登录浏览器 → 页面正常 diff --git a/docs/changelog/2026-05-30-servers-iteration.md b/docs/changelog/2026-05-30-servers-iteration.md new file mode 100644 index 00000000..18d08fd5 --- /dev/null +++ b/docs/changelog/2026-05-30-servers-iteration.md @@ -0,0 +1,56 @@ +# 2026-05-30 — 服务器列表迭代 + +## 日期 +2026-05-30 + +## 变更摘要 +服务器列表页面全面迭代:后端新增服务端分页/搜索/排序/状态筛选,前端完全重写支持新功能。 + +## 动机 +原服务器列表一次性加载全部数据,2000+ 服务器场景下性能差且无搜索/排序能力。 + +## 涉及文件 +| 文件 | 变更类型 | 说明 | +|------|----------|------| +| server/infrastructure/database/server_repo.py | 修改 | `get_paginated()` 新增 search/sort_by/sort_order/is_online 参数,白名单排序,DB 级分页 | +| server/api/servers.py | 修改 | `list_servers()` 新增 4 个 Query 参数 + Redis post-filter;修复 12 个预存 ruff 错误;修复 `agent_port` F821 bug | +| web/app/servers.html | 重写 | 服务端分页/搜索/排序/状态筛选/自动刷新/键盘快捷键/CSV 导出/骨架屏/空状态优化 | + +## 变更详情 + +### 后端 — server_repo.py +- `get_paginated()` 签名扩展:新增 `search`(name/domain LIKE)、`sort_by`(白名单验证)、`sort_order`(asc/desc)、`is_online`(MySQL 预过滤) +- `_SORT_COLUMNS` 白名单:id/name/domain/status/heartbeat/agent_version/category +- `is_online` 过滤时 over-fetch +10 补偿 Redis overlay 修正 + +### 后端 — servers.py +- `list_servers()` 新增 Query 参数:search/sort_by/sort_order/is_online +- Redis post-filter:`is_online` 过滤时移除 Redis 状态不匹配的记录 +- 修复 12 个预先存在的 ruff 错误:F401 unused imports、B904 raise from、S110 try-except-pass、F541 f-string +- 修复 F821 bug:`upgrade_agent()` 中 `agent_port` 变量未定义 + +### 前端 — servers.html +- 服务端分页:50/100/200 条/页,首页/上一页/数字页码/下一页/末页 +- 防抖搜索(300ms):名称/地址 LIKE 模糊搜索 +- 可点击列排序:Agent 状态/名称/地址/分类/Agent/最后心跳,▲▼ 指示器 +- 状态筛选下拉:全部/在线/离线 +- 自动刷新:30 秒间隔,弹窗编辑时自动暂停 +- 键盘快捷键:↑↓/jk 移动、Esc 关闭面板、/ 搜索、r 刷新 +- CSV 导出:当前筛选条件下导出最多 5000 条 +- 骨架屏加载:8 行脉冲动画替代"加载中..." +- 空状态优化:区分"无数据"和"无匹配结果" + +## 是否需迁移 +否 + +## 是否需重启 +是 — supervisorctl restart nexus + +## 验证方式 +1. ruff check: ✅ All checks passed +2. bandit: ✅ No issues +3. import: ✅ 成功 +4. Playwright 浏览器验证:✅ 搜索/排序/筛选/分页全部通过 + +## Commit +84282e8 diff --git a/docs/changelog/2026-05-30-settings-inspection.md b/docs/changelog/2026-05-30-settings-inspection.md new file mode 100644 index 00000000..8ae30468 --- /dev/null +++ b/docs/changelog/2026-05-30-settings-inspection.md @@ -0,0 +1,55 @@ +# Changelog — Settings 页面巡检 + Telegram Bot Token 脱敏 + +**日期**: 2026-05-30 +**类型**: 安全修复 + 功能验证 +**涉及文件**: +- `server/api/settings.py` — SENSITIVE_KEYS 加入 telegram_bot_token + reveal-token 端点 +- `web/app/settings.html` — Telegram 区域独立渲染 + Bot Token 脱敏 UI + +## 变更摘要 + +### 1. Telegram Bot Token 脱敏 (安全修复) +**问题**: GET /api/settings/ 以明文返回 Bot Token `8743165040:AAH_YGffcj2mdqSAx1sHXjspvUFzJ_kVC2I`,违反安全铁律(API 响应不得返回敏感凭据明文)。 + +**修复**: +- 后端 `SENSITIVE_KEYS` 新增 `telegram_bot_token`,GET 响应返回脱敏值 `87431650...` +- 新增 `POST /api/settings/telegram/reveal-token` 端点(密码验证 + 审计日志) +- 前端 Telegram 区域从通用 SECTIONS 循环中分离,单独渲染: + - Bot Token 默认脱敏显示 + 「显示」按钮(需密码验证) + - 验证后切换为 password 类型输入框 + 显示/隐藏切换 + - Chat ID 保持正常编辑(非敏感信息) + +### 2. Settings 页面功能巡检结果 +| 功能 | 状态 | 备注 | +|------|------|------| +| 品牌设置保存 | ✅ 正常 | PUT system_name → 200 | +| 告警阈值保存 | ✅ 正常 | PUT cpu_alert_threshold → 200 + 热更新 | +| 通知 toggle 开关 | ✅ 正常 | 乐观 UI 更新 + API 同步 | +| 手动 IP 添加/删除 | ✅ 正常 | POST + DELETE → 200 | +| IP 白名单开关 | ✅ 正常 | confirm 弹窗 + toggle | +| 订阅地址保存 | ✅ 正常 | 触发后台刷新 | +| Telegram Chat ID 检测 | ✅ 正常 | getUpdates → 2 个对话 | +| 不可变 Key 拒绝 | ✅ 正常 | PUT api_key → 403 | +| TOTP 状态显示 | ✅ 正常 | 已启用卡片 + 重新绑定/禁用按钮 | +| Bot Token 脱敏 | ✅ 正常 | 87431650... + 显示按钮 | +| API Key 脱敏+复制 | ✅ 正常 | de3a155d... + 显示/复制 | +| 订阅 IP 显示 | ✅ 正常 | 6 个节点 IP | +| 手动 IP 空状态 | ✅ 正常 | 显示「(空)」 | +| 告警通知 8 项 toggle | ✅ 正常 | 全部可切换 | + +### 3. 已排除的假问题 +- **heartbeat_timeout 映射**: 已存在于 DB_OVERRIDE_MAP (config.py:115),Task 2 关闭 +- **手动 IP 与订阅 IP 重叠**: 旧页面缓存(ip-allowlist 路由修复前),刷新后正常 +- **「等待首次刷新...」**: 服务重启后时序问题,refresh 完成后刷新页面正常显示 + +## 动机 +安全审计发现 Telegram Bot Token 明文暴露,需要与 API_KEY/SECRET_KEY 同等级别保护。 + +## 是否需迁移/重启 +- 需要重启后端服务(新增 API 端点 + SENSITIVE_KEYS 变更) + +## 验证方式 +1. `GET /api/settings/` → telegram_bot_token 值为 `87431650...` +2. `POST /api/settings/telegram/reveal-token` + 密码 → 返回完整 token +3. 前端 settings 页面 → Bot Token 显示脱敏 + 「显示」按钮可正常揭示 +4. ruff check → 0 errors diff --git a/docs/changelog/2026-05-30-settings-security-ux.md b/docs/changelog/2026-05-30-settings-security-ux.md new file mode 100644 index 00000000..07e5b6ad --- /dev/null +++ b/docs/changelog/2026-05-30-settings-security-ux.md @@ -0,0 +1,83 @@ +# Changelog — Settings 安全加固 + UX 迭代 + +**日期**: 2026-05-30 +**Commit**: a50f179 +**类型**: 安全修复 + UX 改进 +**涉及文件**: +- `server/api/settings.py` — SSRF 防护 + key 白名单 + 值校验 + IP 审计 +- `server/api/auth.py` — 密码修改要求 TOTP +- `server/application/services/auth_service.py` — _verify_totp staticmethod +- `web/app/settings.html` — UX 全面改进 + 密码修改重定向 +- `web/app/login.html` — 密码修改后提示 + +## 安全修复 (5 项) + +### S-01 SSRF 防护 (P0) +`POST /api/settings/ip-allowlist/parse-subscription` 可被利用探测内网。 +- 新增 `_is_private_host()` 函数:IP 直检 + DNS 解析后二次校验 +- 封禁:loopback/link-local/private/reserved IP 段 +- `follow_redirects=False`,手动跟随最多 3 次重定向,每次检查目标 +- 响应大小限制 1MB + +### S-02 设置值校验 (P0) +`PUT /api/settings/{key}` 接受任意值。 +- 新增 `MUTABLE_KEYS` 白名单(27 个已知 key),未知 key → 403 +- 新增 `SETTING_VALIDATORS`:6 个 INT 设置项的类型+范围校验 + - db_pool_size/db_max_overflow: 1-1000 + - heartbeat_timeout: 10-600 + - cpu/mem/disk_alert_threshold: 1-100 +- 非整数值 → 400 "必须是整数",范围越界 → 400 "范围: X-Y" + +### S-05 改密码后重登录 (P1) +密码修改成功后前端仅清空表单,用户留在 stale JWT 页面。 +- 成功后跳转 `/app/login.html?msg=password_changed` +- login.html 显示绿色提示 "密码已修改,请重新登录" + +### S-07 IP 操作审计 (P1) +`add_manual_ips` 和 `remove_allowlist_ip` 缺少审计日志。 +- 两个端点添加 `request: Request` 参数 +- 添加标准 AuditLog 写入(action/detail/ip_address) + +### S-09 改密码要求 TOTP (P1) +已启用 TOTP 的管理员改密码只需密码,可绕过 TOTP 保护。 +- `ChangePasswordRequest` 新增 `totp_code: Optional[str]` +- `change_password` handler 检查:TOTP 已启用 + 未提供 code → 400 +- `AuthService._verify_totp` 改为 `@staticmethod` 供 auth.py 调用 + +## UX 改进 (5 项) + +### UX-01 输入框样式+类型 +- 密码输入框添加 `focus:outline-none focus:ring-2 focus:ring-brand` +- 设置项 input type 映射:数值→`type="number" min="1"`,URL→`type="url"` +- 所有动态渲染 input 统一添加 focus ring + +### UX-02 改密码 loading 状态 +- 按钮点击后 `disabled=true + textContent='提交中...'` +- 请求完成 `finally` 块恢复 + +### UX-03 加载失败 toast +- `loadSettings` / `loadTotpStatus` / `loadAllowlist` 的空 catch 改为 `console.error + toast` + +### UX-04 保存失败回滚 +- `saveSetting()` 失败时调用 `loadSettings()` 恢复原值 + +### UX-05 IP 格式校验 +- 前端正则校验:IP (`x.x.x.x`) / CIDR (`x.x.x.x/n`) / 域名 +- 后端 `IpAllowlistRequest` Pydantic `model_validator` 校验 + +## 验证结果 + +| 测试 | 结果 | +|------|------| +| SSRF localhost | ✅ 400 "不允许访问内网地址" | +| SSRF metadata | ✅ 400 "不允许访问内网地址" | +| 未知 key | ✅ 403 | +| INT 范围 99999 | ✅ 400 "范围: 1-1000" | +| INT 类型 "abc" | ✅ 400 "必须是整数" | +| 有效值 75 | ✅ 200 | +| IP 审计日志 | ✅ "添加 1 条手动 IP: 192.0.2.1" | +| TOTP schema | ✅ totp_code Optional 字段 | +| Input types | ✅ number/url/text 正确映射 | + +## 是否需迁移/重启 +- 需要重启后端服务(新增白名单+校验+SSRF 逻辑) diff --git a/docs/changelog/2026-05-30-terminal-iteration.md b/docs/changelog/2026-05-30-terminal-iteration.md new file mode 100644 index 00000000..fb4a6294 --- /dev/null +++ b/docs/changelog/2026-05-30-terminal-iteration.md @@ -0,0 +1,61 @@ +# 2026-05-30 终端页面全覆盖迭代 + +## 变更摘要 +terminal.html 全面重构,新增 11 项运维效率特性: + +1. **多标签会话**: 浏览器内多标签管理,每标签独立 xterm + WebSocket,支持新建/切换/关闭,最多 10 标签。恢复时自动重建上次会话 +2. **快速命令栏**: 右侧面板内置 5 条命令 + 自定义命令(名称+内容),localStorage 持久化。点击发送到终端。弹窗编辑/删除 +3. **字体调节**: 工具栏 A−/A+ 按钮 + Ctrl+/- 快捷键,范围 10-24,localStorage 持久化 +4. **Ctrl+L 清屏**: 发送 clear\r + term.clear() 双重保障 +5. **命令历史持久化**: sendCmd 后存 localStorage(最多 200 条),刷新恢复,ArrowUp/Down 浏览 +6. **右键菜单**: 自定义右键菜单(复制/粘贴/清屏/全选) +7. **断开自动重连**: WebSocket 异常断开指数退避重连(1s→2s→4s→max30s),倒计时显示。手动断开不重连 +8. **连接信息栏**: 工具栏显示服务器名 + 在线状态 + 连接时长(HH:MM:SS) + PING RTT +9. **终端标题路径**: 后端注入 PROMPT_COMMAND(OSC title),前端 onTitleChange 捕获显示 +10. **滚动缓冲区调节**: 下拉选择器 1K/5K/10K/50K 行,xterm 5.x 热改 +11. **快速切换服务器**: 右侧面板搜索框实时过滤 + +## 动机 +terminal.html 仅具备基础 WebSSH,缺乏频繁运维所需的效率特性。用户一次连接内需切换多台服务器、快速执行常用命令、持久化历史等。 + +## 涉及文件 + +### 后端 +- `server/api/webssh.py` — 轻量改动 + - 新增 `MSG_PING`/`MSG_PONG` 消息类型 + - shell 创建后注入 `PROMPT_COMMAND`(OSC title escape)用于路径跟踪 + - PING handler: 收到 PING 立即回复 PONG(RTT 测量) + +### 前端 +- `web/app/terminal.html` — 全面重构(~500行新增) + - 多标签架构: sessions[] 数组 + 动态 term div + - localStorage: 字体/回滚/历史/命令/标签 5 个键 + - 全新 UI: TabBar + 工具栏(字体/回滚/全屏) + 右侧面板(搜索+快速命令) + - ContextMenu: 浮层右键菜单 + - AutoReconnect: 指数退避定时器 + +## 安全影响 +- PING/PONG: 无关安全的回声确认 +- PROMPT_COMMAND: 后端注入固定字符串,不走用户输入 +- 快速命令: 已有 WebSocket DATA 通道,无新攻击面 +- 右键菜单: 纯前端 UI,无后端交互 +- 多标签: 每标签独立 WebSSH token 认证,不共享凭据 + +## 需要迁移/重启 +- 是:后端 Python 需重启 +- 否:无数据库迁移 + +## 验证方式 +1. 多标签: 新建标签 → 切换 → 关闭 → 每标签独立 WS +2. 快速命令: 点击 → 终端执行 +3. 字体: Ctrl+/− 调节,刷新保留 +4. Ctrl+L: 清屏 + sterm clear +5. 历史: ArrowUp/Down,刷新保留 +6. 右键: 复制/粘贴/清屏/全选 +7. 重连: 异常断开 → 倒计时 → 自动重连 +8. 连接信息: 时长 + PING 显示 +9. 路径: cd 后工具栏更新路径 +10. 回滚: 下拉切换生效 + +## 进度条 +☑实现 ☑WSL验证 ☐审计8步 ☑部署 ☑健康检查 ☑浏览器验证 ☑changelog diff --git a/docs/changelog/2026-05-31-terminal-quick-commands-mysql.md b/docs/changelog/2026-05-31-terminal-quick-commands-mysql.md new file mode 100644 index 00000000..b12ce210 --- /dev/null +++ b/docs/changelog/2026-05-31-terminal-quick-commands-mysql.md @@ -0,0 +1,80 @@ +# Changelog: TerminalPage全面迭代 + 快捷命令MySQL持久化 + 登录页调整 + +**日期**: 2026-05-31 +**变更摘要**: TerminalPage 25项问题修复、快捷命令从localStorage迁移到MySQL、命令输入栏Shell语法高亮、登录页居中+删Logo + +## 动机 + +TerminalPage.vue 是 WebSSH 终端的核心页面,代码审查发现 25 个问题(6 P0 + 6 P1 + 13 P2),包括功能回退(Ctrl+L清屏缺失、命令历史不恢复)、重连逻辑多标签冲突、快捷命令仅存localStorage无法跨设备同步。用户还要求Shell语法高亮和快捷命令MySQL持久化。 + +## 涉及文件 + +| 文件 | 变更类型 | 说明 | +|------|----------|------| +| `server/domain/models/__init__.py` | 新增 | QuickCommand表(quick_commands) | +| `server/api/terminal.py` | 新建 | 快捷命令CRUD API(4端点) | +| `server/main.py` | 修改 | 注册terminal_router + seed内置命令 | +| `frontend/src/pages/TerminalPage.vue` | 重写 | 25项修复 + Shell语法高亮 + MySQL API对接 | +| `frontend/src/pages/LoginPage.vue` | 修改 | 居中布局(flexbox)+ 删除Logo区域 | + +## 详细变更 + +### 后端 + +1. **新增 `quick_commands` MySQL 表** + - 字段: id, name, cmd, is_builtin, sort_order, created_by, created_at, updated_at + - 内置命令 `is_builtin=True`,不可删除/编辑 + - 启动时自动 seed 5 条内置命令 + +2. **新增 API 端点** + - `GET /api/terminal/quick-commands` — 列出所有快捷命令 + - `POST /api/terminal/quick-commands` — 创建自定义命令 + - `PUT /api/terminal/quick-commands/{id}` — 编辑命令(内置不可编辑) + - `DELETE /api/terminal/quick-commands/{id}` — 删除命令(内置不可删除) + - 所有CUD操作带审计日志 + +### 前端 — TerminalPage P0修复 + +1. **Ctrl+L 清屏** — `onKeydown` 添加 `e.ctrlKey && e.key === 'l'`:preventDefault + term.clear() + ws.send('clear\r') +2. **命令历史恢复** — `newSession()` 从 localStorage 恢复全局历史到 session.cmdHistory +3. **重连逻辑 per-session** — reconnectAttempt/reconnectTimer 移入 TermSession 接口,消除多标签干扰 +4. **ws.onopen 不忽略非活跃标签** — 移除 `if (idx !== activeIdx) return` +5. **ping per-session** — startPing/stopPing 操作 session.pingTimer +6. **uptime per-session** — connectionStartTime/uptimeTimer 移入 session + +### 前端 — TerminalPage P1修复 + +7. **关键空 catch 处理** — sendCmd JSON.parse兜底、termPaste/termCopy try/catch + snackbar、webssh-token错误透传 +8. **termRefs 竞态修复** — Map key 从数组索引改为 session.id +9. **initialCdSent per-session** — 移入 TermSession +10. **剪贴板权限处理** — termPaste 捕获 DOMException + snackbar提示 + +### 前端 — TerminalPage P2修复 + +11. **右键菜单补回"全选"** — 新增 termSelectAll() 调用 term.selectAll() +12. **指数退避重连** — `Math.min(1000 * Math.pow(2, attempt - 1), 30000)` +13. **快捷命令双击编辑** — `@dblclick` 触发 editQuickCmd() +14. **硬编码高度修复** — `calc(100vh - 64px)` → `d-flex flex-column flex-grow-1` +15. **快捷命令 MySQL API 对接** — loadQuickCmds/saveQuickCmd/deleteQuickCmd 改用 http API,fallback localStorage +16. **Shell 语法高亮** — 命令输入栏实时着色:sudo→红色、关键字→蓝色、管道/重定向→黄色、字符串→绿色、flag→紫色 + +### 前端 — LoginPage 调整 + +17. **登录卡片居中** — 移除 v-container+v-row+v-col,改用 flexbox 居中 +18. **删除 Logo 区域** — 移除 v-avatar + "登录 Nexus" + "服务器运维管理平台" + +## 是否需迁移/重启 + +- ✅ 需重启 — 新增 MySQL 表 `quick_commands`,首次启动自动建表 + seed +- ✅ 前端需重新构建部署 + +## 验证方式 + +1. 打开终端 `?server_id=8` → 连接成功 +2. 按 Ctrl+L → 终端清屏 +3. 输入几个命令 → 刷新页面 → ArrowUp 回溯历史命令 +4. 开两个标签连接不同服务器 → 断开一个 → 另一个不受影响 +5. 右键菜单 → 有"全选"选项 +6. 添加/编辑/删除自定义快捷命令 → 刷新后仍在(MySQL持久化) +7. 命令输入栏输入 `sudo systemctl restart nginx` → 看到语法着色 +8. 登录页 → 卡片居中,无Logo diff --git a/docs/changelog/2026-05-31-vuetify-audit-fix.md b/docs/changelog/2026-05-31-vuetify-audit-fix.md new file mode 100644 index 00000000..35e7843e --- /dev/null +++ b/docs/changelog/2026-05-31-vuetify-audit-fix.md @@ -0,0 +1,134 @@ +# Changelog: Vuetify 前端审计修复 + +**日期**: 2026-05-31 +**主题**: 5个严重问题修复 + DRY 重构 + 类型安全 + +## 变更摘要 + +### 1. 提取 useSnackbar composable(DRY 重构) +- **新建** `src/composables/useSnackbar.ts` — 统一 snackbar 包装函数 +- **新建** `src/types/global.d.ts` — `Window.$snackbar` 全局类型声明 +- **修改 8 个页面** — 删除各自重复的 `function snackbar(...)` 定义,改为 `import { useSnackbar }` +- **修改** `App.vue` — `(window as any).$snackbar` → `window.$snackbar` +- **修改** `useWebSocket.ts` — 同上 + +### 2. API client FormData 支持(🔴 严重 #1 + 🟠 #12) +- **修改** `src/api/index.ts`: + - `body instanceof FormData` 时不设 `Content-Type`(浏览器自动加 boundary) + - 新增 `http.upload()` 方法 +- **修改** `FilesPage.vue` `doUpload()`: + - `fetch('/api/sync/upload')` → `http.upload('/sync/upload', form)` + - 修复:文件上传现在自动携带 JWT Authorization header + +### 3. 修复 6 个静默 catch 块(🔴 严重 #3) +- `FilesPage.vue` — `loadServers()` / `browse()` catch 加 snackbar 错误提示 +- `SettingsPage.vue` — `loadSettings()` / `loadAllowlist()` catch 加 snackbar 错误提示 +- `DashboardPage.vue` — `loadStats()` / `loadServers()` catch 加 snackbar 错误提示 + +### 4. SettingsPage API Key 密码验证对话框(🔴 严重 #2) +- **新增** `` 密码输入对话框(与 TOTP Disable 对话框模式一致) +- `revealApiKey()` → 先弹密码对话框 → `doRevealApiKey()` 用真实密码调用后端 +- 修复:不再发送空密码 `{ password: '' }` 绕过 re-auth 保护 + +### 5. DashboardPage 数据补全(🔴 严重 #5) +- **新增** `loadAlerts()` — 从 `GET /alert-history/?limit=5` 加载告警历史 +- **新增** `loadSummary()` — 从 `GET /servers/?per_page=500` 计算: + - Agent 覆盖率(有 agent_version 的服务器比例) + - CPU 平均值(从 system_info.cpu_usage) + - 内存平均值(从 system_info.mem_usage) +- **新增** WebSocket 实时刷新 — 监听 ws.alerts 变化自动刷新 stats + alerts + +### 6. 消除 as any 类型断言(🔴 严重 #4) +- **新建** `src/types/api.ts` — 14 个 API 响应 interface: + - `PaginatedResponse`, `SettingsResponse`, `AllowlistResponse`, `BrowseResponse` + - `FileEntry`, `ServerApiItem`, `AlertLogItem`, `AlertStatsResponse` + - `CommandLogItem`, `RetryItem`, `ScriptItem`, `PushItem`, `ScheduleItem`, `AuditItem` +- **修改 12 个页面** — 所有 `(res as any).items` 改为 `http.get>()` +- **结果**: `as any` 从 33 个降到 0 个(src/ 目录完全清零) + +## 涉及文件 + +| 文件 | 操作 | +|------|------| +| `src/composables/useSnackbar.ts` | 新建 | +| `src/types/global.d.ts` | 新建 | +| `src/types/api.ts` | 新建 | +| `src/api/index.ts` | 修改 | +| `src/App.vue` | 修改 | +| `src/composables/useWebSocket.ts` | 修改 | +| `src/pages/FilesPage.vue` | 修改 | +| `src/pages/SettingsPage.vue` | 修改 | +| `src/pages/DashboardPage.vue` | 修改 | +| `src/pages/CredentialsPage.vue` | 修改 | +| `src/pages/RetriesPage.vue` | 修改 | +| `src/pages/PushPage.vue` | 修改 | +| `src/pages/ScriptsPage.vue` | 修改 | +| `src/pages/SchedulesPage.vue` | 修改 | +| `src/pages/ServersPage.vue` | 修改 | +| `src/pages/CommandsPage.vue` | 修改 | +| `src/pages/AlertsPage.vue` | 修改 | +| `src/pages/AuditPage.vue` | 修改 | +| `src/pages/TerminalPage.vue` | 修改 | + +## 验证 + +- ✅ `vue-tsc --noEmit` — TypeScript 零错误 +- ✅ `vite build` — 生产构建通过 (1.53s, Monaco 懒加载) +- ✅ 开发服务器正常启动 + +--- + +## Phase 2: Composables + UX + 核心功能 (同日) + +### Composables 提取 (Step 7) +- **新建** `src/composables/useServerList.ts` — 5 个页面复用 +- **新建** `src/composables/useServerPagination.ts` — ServersPage/DashboardPage 复用 +- **新建** `src/utils/status.ts` — statusChipColor/statusLabel/formatRelativeTime + +### 表单验证 + 空状态 + Loading (Step 8) +- **新建** `src/utils/validation.ts` — 8 个验证规则工厂 +- **12 个空状态** — 9 个文件的 v-data-table 加 `