1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
+ ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
51 lines
3.3 KiB
Markdown
51 lines
3.3 KiB
Markdown
# 24 · 实例侧 401(launch token 过期)浏览器导航自动恢复
|
||
|
||
- 日期:2026-09-11
|
||
- 触发:用户刷新实例子域页面(`admin.alotbuy.com`)看到 **`dsh web authentication required; reopen the URL printed by dsh web.`**,**既不自动跳转也不自动重启**
|
||
- 结论一句话:**代理只处理了"门户侧 401"(会话失效),没处理"实例侧 401"(launch token 过期)** → 浏览器停在 dsh 的死端页。已修复:实例侧 401 + 浏览器导航 → 用当前实例的**最新 token 302 回同一地址**(拿不到则回门户)。
|
||
- 状态:**已实施并上线**(服务已重启,`lib/` 已验证)
|
||
|
||
---
|
||
|
||
## 一、现象与成因
|
||
|
||
| 环节 | 事实 |
|
||
|---|---|
|
||
| 触发场景 | 实例**重启后 token 轮换**(本次由我 00:14 为验证 picker 重启实例引起;此前 23:32 域名迁移 drain 亦同) |
|
||
| 浏览器侧 | 旧标签页 URL 形如 `https://admin.alotbuy.com/?token=<旧token>`,token 已失效 |
|
||
| dsh 侧 | 返回 `401` + 文案 `dsh web authentication required; reopen the URL printed by dsh web.`(HTML) |
|
||
| 平台侧 | `proxy.ts` 的 401 处理**只覆盖门户自身判定**(`resolveSubdomainAccess` 的 `unauthorized` → 302 `/login.html`);**上游(实例)返回的 401 被原样透传** → 用户看到死端页 |
|
||
| 为何没重启 | "自动重启"逻辑只在 `not_running`(404)分支触发;本例实例**正在运行**,只是 token 过期 |
|
||
|
||
## 二、修复(`src/supervisor/proxy.ts`)
|
||
|
||
1. `proxyHttp` 新增参数 `freshAuthUrl?: () => Promise<string | undefined>`;
|
||
2. 上游响应为 **401 且是浏览器导航**(`GET` + `accept: text/html`)时:
|
||
- `upRes.resume()` 释放上游连接;
|
||
- 调用 `freshAuthUrl()` 取恢复地址并 **302**;
|
||
3. 子域 hook 传入的 `freshAuthUrl`:`app.supervisor.status(userId).main?.launchToken` → 有 token 则 `https://<同一 Host>/?token=<新token>`(**原地恢复,无需重新登录**),否则回门户根;
|
||
4. 路径式路由 `/u/:slug/dsh/` 同样接入(复用 `prefix`);
|
||
5. `SubdomainAccess` 成功分支补充 `userId`(取 token 需要)。
|
||
|
||
## 三、验证
|
||
|
||
| 检查 | 结果 |
|
||
|---|---|
|
||
| `bash scripts/ci.sh`(typecheck + build + 单测) | ✅ 38 pass / 0 fail |
|
||
| 运行产物 | ✅ `lib/supervisor/proxy.js` 含 `freshAuthUrl` ×3;`lib/supervisor/orchestrator.js` 含 `symlink` ×3(档案 23 的沙箱修复同时生效) |
|
||
| 服务重启(drain) | ✅ `active` / 残留实例 0 / 门户 200 |
|
||
| 端到端 | ⏳ 待用户实测:实例重启后刷新旧标签页应自动恢复(302 带新 token) |
|
||
|
||
## 四、回滚
|
||
|
||
- 代码:`/opt/dsh/backups/proxy.ts.bak-<TS>`;或 `git revert d7ab5b4` → `npm run build` → `systemctl restart dshs`。
|
||
|
||
## 五、顺带说明(本次一并上线)
|
||
|
||
- **档案 23 的沙箱修复同时生效**:实例 bwrap 参数补齐 `/bin`、`/sbin`、`/lib` 符号链接 → 实例内 **bash 工具恢复可用**(此前被 dsh 沙箱后端整体拒绝)。
|
||
- 本次重启后所有实例已清空,用户下次进入会以新参数启动。
|
||
|
||
## 六、红线遵守
|
||
|
||
- 只改编排器自身代码(`src/supervisor/proxy.ts`),未触碰官方 dsh 主程序与缓存;修复走既有的"302 透明恢复"模式(与档案 13 的 not_running 自动重启同源)。
|