91 lines
11 KiB
Markdown
91 lines
11 KiB
Markdown
# 平台速查(硬编码事实,勿猜)
|
||||
|
|
|
|||
|
|
> **归属**:技能 `dsh-change-workflow` 的详情档(**按需读**,不是每次都要读)。
|
|||
|
|
> **本档覆盖**:平台速查(硬编码事实,勿猜)(原行 L14–L92)。
|
|||
|
|
> **主文件 / 判据与流程主干** = `../SKILL.md`(§1 六阶段流程 · §2 红线 R1–R8 原文速查 · §3 本机 Git Bash 环境坑 · §4 并行调度结论)。
|
|||
|
|
> **来源**:2026-09-22「技能重组线」把 `../SKILL.md` 的 L14–L92 段**逐行原样**下沉到本文件,未改一字。
|
|||
|
|
> **跨档引用**:正文里的「§N / 见 §8 坑 N / 见下表」等编号,用 `../SKILL.md` 末节「详情索引」的原章节列定位。
|
|||
|
|
> **维护**:本文件与 `../SKILL.md` 的指针行成对;改内容时同时核对主文件的指针描述是否仍准确。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
## 平台速查(硬编码事实,勿猜)
|
|||
|
|
|
|||
|
|
> ⚠️ **正确域名 = `ai1net.com`**(2026-09-19 由 `alotbuy.com` 迁入);`alotbuy.com` 是**旧域名、已降为 nginx 301 过渡装置**
|
|||
|
|
> ——对它抓页面只会拿到 301 HTML,**别误判为"改动没生效"**(2026-09-11 踩过)。
|
|||
|
|
> 平台页由门户直出(`127.0.0.1:3080`),生效副本只有 `/opt/dshs/web/` 一份。
|
|||
|
|
> 存量会话提示(档案 45)落在 `web/login.html`:告知「shell 频繁要求审批 → **新开一个会话**即可」。
|
|||
|
|
>
|
|||
|
|
> ⚠️ **`*.ai1net.com`(含每个用户子域)都由同一个 vhost 先 `proxy_pass 127.0.0.1:3080`(门户)**,
|
|||
|
|
> 再由门户的 `src/supervisor/proxy.ts` 转发到实例 → **想改"子域访问行为"要改 `proxy.ts`,
|
|||
|
|
> nginx 的 `error_page` / `location` 到不了实例层**(档案 49 实测)。
|
|||
|
|
> 实例空闲回收后首次访问的链路(档案 49):**导航**(GET+`Accept: text/html`)→ 立刻 302 到
|
|||
|
|
> `web/wake.html`(门户同源,动画 + 自动调 `/api/dsh/enter` 跳回);**XHR/API** → **等实例就绪后
|
|||
|
|
> 继续转发**(不是回错误/动画 —— 让请求最终成功,dsh 自身 loading 即反馈)。
|
|||
|
|
>
|
|||
|
|
> ⚠️ **实例的浏览器凭证 cookie 名带随机后缀**:`dsh-auth-<随机后缀>=v1.<base64>.<sig>`(HttpOnly/Secure)。
|
|||
|
|
> **实例重建后这个后缀会变**(不是"过期",是"名字都不对")→ 已打开的页面手里那份对新实例**永远无效**。
|
|||
|
|
> 档案 51 的修法在 `proxy.ts`:**非导航请求遇实例侧 401 → 取当前实例 token → `GET /?token=` 换新 cookie
|
|||
|
|
> → 覆盖 cookie 头透明重放同一请求 → 响应追加 `Set-Cookie` 回写浏览器**(此后含 SSE 自动重连直接成功)。
|
|||
|
|
> 重放闸门是 `canReplay = mayHaveBody ? bodyBuf !== undefined : true` —— **GET/HEAD 无请求体,一样要能重放**。
|
|||
|
|
>
|
|||
|
|
> ⚠️ **"空闲回收"实际从未生效(2026-09-11 核实,别再拿它解释现象)**:
|
|||
|
|
> ① **TTL** `instanceIdleTtlSeconds` 默认 = **7 天**(`src/config.ts:167` `60*60*24*7`),线上 env 未设 → 等于不会触发;
|
|||
|
|
> ② **LRU 上限** `maxIdleInstances` 默认 = **4**,只有**第 5 个用户**拉起实例时才淘汰最久未活跃者(现有 2 用户永远达不到);
|
|||
|
|
> ③ `reapOnce()` 只看 `lastActive`,而 `proxy.ts::resolveSubdomainAccess()` **每个子域请求都 `touch()`**,
|
|||
|
|
> dsh 前端还有常驻 SSE `/plugins/events` → **页面开着就永远"活跃"**,TTL 规则不可能命中。
|
|||
|
|
> **历史实证**:全历史 `grep -c "idle-reap"` = **1**(`09-09 12:24 stop … idle>20s`,是档案 08 拿 TTL=20s 做验证那次)。
|
|||
|
|
> ### ⛔ **别把"回收没生效"外推成"实例不会中断"**(2026-09-11 我犯过这个错,被用户当面纠正)
|
|||
|
|
>
|
|||
|
|
> 「页面等一会就模型断开、必须刷新」的真凶是**中断**,与"空闲回收"是两码事。实测统计:
|
|||
|
|
>
|
|||
|
|
> | 中断来源 | 实证 | 机制 |
|
|||
|
|
> |---|---|---|
|
|||
|
|
> | **① 门户服务重启**(主因) | 2026-09-11 一天重启 **48 次**(全是部署/探活) | 重启后 supervisor **内存 `mains` 表清空** → 旧实例变**孤儿**(进程还在但不被跟踪)→ 再访问判 `not_running` → **重新拉起新实例(新端口/token/cookie 名)** → 旧页面带旧 cookie → **401**。**这正是档案 51 的触发源** |
|
|||
|
|
> | **② 实例崩溃后自动重启** | 近 4 天 `crash-restart` **69 次**(admin 34 / guest 16 / 测试号 19) | admin 根因 = `duplicate loader entry id: permission`(`exitCode 1`)——**启用与官方 `permission` 撞 id 的候选插件**所致;guest = 信号终止(无 exitCode),紧跟服务重启 |
|
|||
|
|
> | ③ 空闲回收 | 全历史 `[idle-reap]` **仅 1 次**(09-09 测试值 20s) | **从来没生效**,不构成中断来源 |
|
|||
|
|
>
|
|||
|
|
> **必查手法**:`journalctl -u dshs --since "-1d" | grep -c "Started DSH server login orchestrator"`(重启次数)
|
|||
|
|
> + `grep -c crash-restart` + `grep -c idle-reap` —— 三个数一比就知道该往哪查。
|
|||
|
|
>
|
|||
|
|
> **治本方向(待办)**:① 部署**攒批重启**(别每改一处重启一次);② **supervisor 启动时收养已存在 scope**
|
|||
|
|
> (本地模式目前**无**收养逻辑,`reconcile.ts` 只覆盖 k8s)→ 重启不再产生"新实例 + 401";③ 业务插件探活失败**自动摘 bundle**。
|
|||
|
|
> ⚠️ **配置风险**:`maxIdleInstances=4` × `MemoryMax=1024M`(硬限上界) = 4096M **远超** 宿主总量 1870MB(**上限非预留**,但真跑满必然换页)→ **撑不住,建议调 2**。
|
|||
|
|
>
|
|||
|
|
> ⚠️ **dsh web 客户端的会话事件流走 SSE(`EventSource`),不是 WebSocket**(源码
|
|||
|
|
> `dsh-api-session-controller/lib/types/client/sessions/session.js`)。**凡"页面内自愈"类注入脚本,
|
|||
|
|
> 只 hook `fetch`/`XMLHttpRequest` 是够不到主链路的** —— EventSource 遇 401 只静默 `onerror` 重连,
|
|||
|
|
> 界面什么都不显示(档案 50 因此对真实用户无效,档案 51 才在传输层根治)。
|
|||
|
|
> 判断前端用什么传输**别猜**:`grep -rl "new WebSocket\|EventSource" <dsh 包>`。
|
|||
|
|
>
|
|||
|
|
> ⛔ **注入脚本(`src/supervisor/proxy.ts` 的 `SESSION_*_JS`)两条铁律 —— 2026-09-13 事故换来的**:
|
|||
|
|
> ① **它是 TS 模板字面量,里面的 `\n` / `${` / 反引号会在模板求值时先被处理一次。**
|
|||
|
|
> 写 `.join('\n')`(单反斜杠)⇒ 求值后变**裸换行** ⇒ 注入的 JS 直接 **SyntaxError** ⇒
|
|||
|
|
> **整段脚本静默不执行**(浮层 / 自愈 / 助手面板全废,页面只剩 dsh 自己的「连接异常」)。
|
|||
|
|
> → 用 `String.fromCharCode(10)`;**校验必须"先模板求值、再 `node --check`"**:
|
|||
|
|
> `node 07-scripts/verify-inject.cjs lib/supervisor/proxy.js`(**已接入 `npm test`,勿绕过**)。
|
|||
|
|
> ⚠️ `new Function(原文)` 与「grep 页面 HTML 有没有标记」**都是假绿** —— 前者跳过求值,后者验不出"跑不跑得起来"。
|
|||
|
|
> ② **触发面必须覆盖"页面开着不动"**:用户盯着页面时无任何事件;现已有 **可见时 25 s 心跳**
|
|||
|
|
> + `EventSource` / `WebSocket` 断流包装,且两者都**连续两次失败才恢复**(恢复=原地 replace,会丢未保存输入)。
|
|||
|
|
>
|
|||
|
|
> 🔴 **浏览器自动化:唯一允许 `browser-harness`**(⚠️ **2026-09-25 用户明令**:「**改为 browser-harness,只允许使用这个**」
|
|||
|
|
> ⇒ 此前「两件(含 `agent-browser`)」的口径**作废**):
|
|||
|
|
> ① **`browser-harness`**(CDP)—— **唯一允许的一条**:全局 console script `browser-harness`(`~/.local/bin`);
|
|||
|
|
> ⛔ **动手前必须先 `list_tabs()`**;**默认起独立 headless 实例(`BU_CDP_URL` 指向 9223),⛔ 绝不附着用户日常 Chrome**
|
|||
|
|
> —— 旧事故:在其浏览器里登录测试号 → **覆盖了用户的 `.ai1net.com` 的 `sid`**。
|
|||
|
|
> 📂 正确调用(含 2026-09-25 实测路径更正)⇒ `08-浏览器验证栈详解.md`。
|
|||
|
|
> ⛔ **`agent-browser` 已禁用**(2026-09-13 曾定为「本项目默认」,2026-09-25 用户明令收窄 ⇒ 新增此禁);
|
|||
|
|
> ⛔ **禁止 Playwright / `playwright-core`**(含「独立无头」用法)(用户 2026-09-13 明令:「**playwright 禁止使用,加到规则中**」)。
|
|||
|
|
> 静态页与 API 取数优先 `curl` / WebFetch,**别动浏览器**。
|
|||
|
|
|
|||
|
|
| 项 | 值 |
|
|||
|
|
|----|-----|
|
|||
|
|
| 服务器 | 47.77.182.89(Alibaba Cloud Linux al8);SSH:**`ssh -i ~/.ssh/id_ed25519 -p 22 [email protected]`**(🔴 2026-09-22 实测更正:`~/.ssh/config` 里的别名 `bt-server` **写死 `Port 32022`,而 sshd 只监听 22 ⇒ 用它必 `Connection refused`**;私钥真名 `~/.ssh/id_ed25519`,⛔ 技能旧文里的 `id_ed25519_dsh` 不存在) |
|
|||
|
|
| 门户 | dshs(systemd `dshs`,127.0.0.1:3080);源码 `/opt/dshs`(git master) |
|
|||
|
|
| 数据 | `/var/lib/dshs/users/<id>/{home,ws}`;DB `/var/lib/dshs/dshs.db`(root 600) |
|
|||
|
|
| 域名 | **ai1net.com**(门户,CF 代理 + 通配证书,源站 443);用户子域 **`<用户名>.ai1net.com`** 经 portal proxy 转发实例;旧 `alotbuy.com` 301 → `ai1net.com`(档案 22 / 2026-09-19 迁入) |
|
|||
|
|
| 实例 | 门户 spawn `node /usr/local/bin/dsh --profile web --host 127.0.0.1 --port <动态>`;ISOLATION account(setpriv,uid∈[100000,199999];admin=114801) |
|
|||
|
|
| 文档 | **本机 git 工作树 = 唯一源**:🔴 **`D:/github/dsh_shenxian/dsh-server-docs`**(仓库 `dsh_shenxian_doc`)→ 同步到服务器 `/opt/dsh/docs`(**无 .git 的部署镜像**;`08-skills/**` 与根 `README.md`/`INDEX.md` 均 **600 root:root**)。⚠️ **2026-09-22 实测更正**:旧文写的 `E://ProgramData//AI技能//aliyun-dsh-server//dsh-server-docs` **该目录不存在**(本工作区里只有 `docs/`,那是会话规则副本);⚠️ **根 `README.md`/`INDEX.md` 也在服务器上有一份** ⇒ 登记跟改后要连它们一起推。⛔ **本行 2026-09-13 更正**:原文写「服务器唯一源 / 本地不保留副本 / `dsh-server-docs/` 已废弃」——那是**更早的状态,现已作废** |
|
|||
|
|
| 文档同步 | 改完跑**四件套**:`docs-audit.py` → `docs-index-stats.py --write`(INDEX 状态摘要**机器生成**)→ `docs-manifest.py` → `docs-sync-check.sh` → `docs-consistency.py`;**只 scp 自己本次改的文件**、推前复跑对账。⛔ `docs-status/` 机制**已不存在**(原文作废) |
|
|||
|
|
| 硬规则 | 动**文档 / 代码 / 服务器**之前先抢**全局执行锁**(`bash dsh-server-docs/07-scripts/handoff-guard.sh --claim-exec "<会话名>"`);要动**生产**再占服务器侧 `07-scripts/op-lock.sh`;**同一时刻只放一个执行会话**。规则实体在项目根 `CODEBUDDY.md`(§3 红线 R1–R9 / §6 并发纪律)—— **本技能不复述、也不得与之冲突**。⚠️ 释放 op-lock 必须带 `ME=<原占用者>`,否则按 R9 被拒 |
|
|||
|
|
| 档案号 | **复跑取号,勿写死**(`ls 04-调整方案/ | sort -n | tail -1`;并行改动会打穿)(01-52 已落;**50/51/52** = 会话过期自愈注入(已被51取代) / 实例侧401透明重放 / 新用户实例无法启动;⚠️ 无 48;**37/38 各有两条**(并行通道撞号)→ **归档前先 `mkdir <目录>/.lock-<n>` 原子占号**,光"先 ls 再写"不够) |
|
|||
|
|
|