Files
dsh_shenxian/dsh-server-docs/04-调整方案/135-旧域alotbuy.com从项目移除.md
admin 971ccc3703 feat(auth): 注册页人机验证 + 邮箱验证码;品牌标识去 DeepSeek(附域名迁移线 序㊿ 补提交)
三条线合并入库 —— 均已完成并上线(源码与生产一致,此前只部署未入仓)。
⚠️ 其中域名迁移线为**另一会话**产出,本会话只做入库、**未复验其正确性**(它自报零回归)。

【档案 134 · 注册页人机验证 + 邮箱验证码】
- DB 迁移 v10:users.email(唯一索引 LOWER(email))+ email_codes 事件表(2 索引)
- 新增模块 src/web/{register-guard,mail,turnstile,email-code}.ts
- routes/auth.ts:新增 GET /api/auth/register/config、POST /api/auth/register/email-code;
  注册接口加人机验证与验证码校验;config.ts 新增 12 项配置(默认空 ⇒ 不配 = 老行为)
- 邮件走**可插拔驱动**(brevo/http/log),发件人 [email protected](Brevo 域名已认证 + DKIM + SPF)
- 防爆破:三层配额(邮箱 6/h、8/天;IP 20/h;全局 200/h)+ 递增冷却阶梯
  (60→60→180→300→900→1800s)+ 试错 5 次作废 + 码只存哈希 + 单次使用 + 与用户名绑定
- Turnstile 服务端校 **success + action + hostname 三项**:sitekey 是公开的,
  只校 success 时"拿我们的 sitekey 在自己站点替真人取合法 token 再打我们接口"这条路是通的
- 新增 test/register-guard.test.mjs(19 用例)

【档案 137 · 品牌标识改造 — 去 DeepSeek 图形】
- login/register/admin 页头:删 DeepSeek 鲸鱼图标 + 「DeepSeek」文字图形
  → 平台标识(中文「能力枢纽」/英语及其他语言「CapabilityNet」,走 i18n 词条 brand.name)
- portal 顶栏换图标(页面名「管理门户」保留)
- 新建 web/favicon.svg(平台自有 hub 图标,避开 DeepSeek 蓝)+ 四页 favicon 指向它
- 新增 test/i18n-brand.test.mjs(node:vm 跑真实 i18n.js,六条语言路径断言渲染结果)
- scripts/verify-static.mjs 新增 SVG 段:XML 注释不得含 ASCII 双连字符(否则整份 SVG
  解析失败、图标静默不显示 —— 实际踩到过)
- 🔴 会话页面(实例内官方 dsh 界面)的标识**按用户要求未动**(也受 R2 约束)

【档案 135/136 · 域名迁移线(另一会话产出)】
- 域名收敛为 ai1net.com;旧域 alotbuy.com 降级为 301 过渡装置
- src/net/relay/{addr-override,directory,rendezvous,switcher}.ts 种子与候选链更新;
  src/web/server.ts、src/worker/relay-tunnel.ts、scripts/verify-cluster-domain.mjs
- 档案 136 = 控制面按两台中继取并集(**已立项、未落地**)

验证(本会话两条线):新增单测 21 条全通过|全量 221 pass / 0 fail / 1 skipped|
verify-static 全合格|其余 10 个 verify 脚本全 OK|线上实测:Turnstile 假 token 403、
发码 delivered、四页 deepseek 命中 0、favicon 200。
2026-09-19 09:11:24 +08:00

130 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 135-旧域 alotbuy.com 从项目移除(2026-09-19 落地)
> **一句话**:平台对外域名**收敛为 `ai1net.com` 单一域名**;旧域 `alotbuy.com` 在**代码 / 服务端配置 / 文档技能**三层全部移除,
> 旧域 nginx 站点**降级为 301 过渡装置**(保留 relay 透传与 ACME,30 天零流量后退役)。
> **触发**:用户 2026-09-19 原话 —— 「旧域 alotbuy.com 怎么处置:**从项目中移除后续都用新域名**」
> **执行**:序㊿ 执行棒(会话名 `旧域移除-控制面并集-20260919`)· 计划书 = 工作区根 `_tmp_dompurge/PLAN-旧域移除与并集立项_20260919.md`
> **姊妹件**:同棒产出的立项交接单 = `04-调整方案/136-控制面按两台中继取并集-立项交接单.md`
## 背景与动机
1. **域名已在 09-19 切换完成**(见 `ops/域名迁移_ai1net_20260919/RUNBOOK-cutover.md`),但切换只做到"新域能用",
旧域仍以**独立门户站点**形态存在 ⇒ 两套入口并存 = 两套证书、两处签名目录、两处排障路径。
2. **旧域残留会让判据撒谎**:演练脚本的"是否切到 47 那台中继"判定用**硬编码域名字符串**匹配端点 ⇒
域一改,**真实发生的切换会被判成失败**(本节 §"实测踩坑 1" 即此坑)。
3. **relay 候选里存在"摘名不同、物理同一"的伪多样性**:`alotbuy.com` 与 `relay-direct.alotbuy.com` 摘名不同、
**同落 47 同一路径**、且后者还开了 `DSHS_OVERLAY_ADDR_OVERRIDES` 直连 IP ⇒ 5 条候选里 2 条**零物理多样性**。
## 用户决策
| 时间 | 用户口径 | 落地 |
|---|---|---|
| 2026-09-19 | 「从项目中移除后续都用新域名」 | 代码 / 服务端 / 文档技能三层真实替换;**旧域不删站、降级 301**(删站会打断仍指向旧域的客户端,净变差 ⇒ R11 不允许) |
| 2026-09-19 | 「A 立项修」(控制面并集) | 只出立项交接单(档案 136),**本轮不落地** |
## 范围分桶(最终实测结论)
| 桶 | 对象 | 处置 |
|---|---|---|
| **A 代码 / 运行时** | `src/**`、`web/**`、`test/**`、`scripts/**` | ✅ 全量替换(10 个源文件 + 2 个 test + 2 个 live 校验脚本 + 1 个演练脚本 + 1 个探针注释) |
| **B 服务器配置** | 47 的 drop-in / nginx;106 的 worker env | ✅ 落新值(见 §执行记录 S3/S4) |
| **C nginx 旧域站点** | 47 `/etc/nginx/conf.d/alotbuy.com.conf` | ✅ 降级为 **301 过渡装置**(保留 3 条非 301 路径) |
| **D 文档 / 技能** | 文档库 + `skills/**` | ✅ 42 处精确替换 + 1 处补 Edit(**只改"现行事实"章节**) |
| **E 不动的** | `04-调整方案/**` 历史档案、`archive/**`、参数表 §11 历史补记 | ⛔ 历史记录**不改**(改了就是伪造历史) |
| **F 排除** | `work.alotbuy.com`(Gitea 仓库服务) | ⛔ **不是本平台域名**,与迁移无关 |
## 执行记录(S1–S7)
**S1 · 代码层替换(A)**
- 扫描 127 个文件,命中并改写 10 个:`src/config.ts`×2、`src/net/relay/addr-override.ts`×4、`src/net/relay/directory.ts`×2、
`src/net/relay/rendezvous.ts`×1、`src/net/relay/switcher.ts`×4、`src/web/server.ts`×3、`src/worker/relay-tunnel.ts`×5、
`test/overlay-bootstrap.test.mjs`×22、`test/relay-failover.test.mjs`×4、`web/wake.html`×1。
- ⚠️ **计划书的行号表是快照、已漂移**(例:计划写 `config.ts:312`,实测 `:381`;另有计划未登记的 `config.ts:125`、`directory.ts:324`)
⇒ 一律**先自己 scoped grep、再做字节级精确替换**,⛔ 不按计划行号下手。
**S2 · 修掉一个真 bug(A3)**
- `web/wake.html` 里有**转义点写法** `alotbuy\.com`,被批量替换漏掉 ⇒ 白名单正则仍只认旧域 = 唤醒后**跳转白名单恒不命中**。
- 修为新域后缀(单后缀即正确 —— 已核 `next` 是**由请求 host 拼出的绝对 URL**,见 `src/supervisor/proxy.ts:613`):
`/^(\^|\.)ai1net\.com$/.test(x.hostname)`(源码中无多余反斜杠)。
**S3 · 47 服务端(B + C)**
- drop-in `overlay-443fb.conf`:`DSHS_OVERLAY_BOOTSTRAP_SEEDS` **5 → 3**,删掉**同机同路径**的两条旧域候选;
`DSHS_OVERLAY_ADDR_OVERRIDES=relay-direct.ai1net.com=47.77.182.89`;注释写明"旧域 `/dshs-relay` 透传仍在"与回滚原文。
- nginx 旧域站点降级:`map $host $alotbuy_301_host`(默认 `ai1net.com`;`~^(?<lb301>[^.]+)\.alotbuy\.com$ → $lb301.ai1net.com`
⇒ **子域对子域映射**),80/443 双 server 全 301;**仅保留 3 条非 301 路径**:
`/.well-known/acme-challenge/`、`/dshs-relay` → `127.0.0.1:20080`、`= /dshs-overlay/bootstrap` → `127.0.0.1:3080`。
- 文件头写入**降级而非删除的理由**与**退役条件 = 30 天零流量**。
- 归档 `_pending-ai1net/` → `mv` 到 `/root/_dompurge/_pending-ai1net-archived-<ts>`(**不是删除**)。
**S4 · 106 worker(B)**
- `/etc/dshs-worker.env` 第 26 行同步为同 3 条 SEEDS;备份 `/etc/dshs-worker.env.bak-dompurge-20260919-073149`;`daemon-reload`。
- 顺带取证:`dshs-relay-client` 单元为 **disabled**(历史遗留、无害)—— **只报告、未动**。
**S5 · 控制面二次重启以清签名目录缓存(关键一步)**
- 一次重启后 bootstrap **仍是 5 条、仍含 2 条旧域** ⇒ 定位到 Manager 把**自己发布的签名目录**缓存在
`/var/lib/dshs/overlay/directory.json`(`fetchedAt` 07:29,**早于改动**)且**懒重建**。
- 处置:备份 + 删除该**可再生**缓存 → 第二次重启控制面 ⇒ Manager 解析出 **3 条候选、全部新域**
(`source=seed-directory`),缓存重建 **578 B**。
**S6 · 文档与技能(D)**
- 42/42 处精确替换(**每处带 count 断言**):`BRIEF.md`、`README.md`、`INDEX.md`、`DEPLOY-本部署.md`、
`02-运维手册.md`(**仅附录 C = 现行事实章节**)、`docs-manifest.json`、`scripts/docs-consistency.py`、
`scripts/cf-probe.py`(新增 `CREDS={'ai1net.com':'/etc/cloudflare-ai1net.ini'}` + `token(zone)`)、7 个技能文件。
- 补 1 处:`skills/dsh-instance-diagnose/SKILL.md`(`goto https://<user>.ai1net.com/`)。
- 文档库镜像同步:`ops/nginx/alotbuy.com.conf` 换为 301 版(md5 `eccf9014c6e1c3f13a5c32b7b14418a9`,与生产一致)。
- 从 47 暂存目录抢救回 `ops/域名迁移_ai1net_20260919/RUNBOOK-cutover.md`(**唯一副本**),并在头部加状态横幅
(记录 §1 阻塞已解、S1–S5 已执行;原"留待你确认退役时机"**已被本棒取代**)。
**S7 · 参数字典与判据脚本**
- 参数表值格 **2 键**:`PORTAL_HOST_HEADER`、`DRILL_KILLED_MATCH` 由 `alotbuy.com` → `ai1net.com`(🔴 后者是**假红根因**,见踩坑 1);
+ 两处 `-H "Host: alotbuy.com"` 示例 + §6 一条候选摘名说明 ⇒ **§10 指纹同步为 `075a97239c2d1d8a1f442ec6cc787ede`**。
- `scripts/overlay-probe.cjs` 注释、`verify-cluster-domain.mjs` / `verify-platform-admin-section.mjs` 默认域同改。
## 实测踩坑(两条,都是"判据撒谎"型)
**踩坑 1 · 演练"假红"(已修)**
- 现象:`overlay-failover-drill.cjs --scene all` = **11 PASS / 1 FAIL**(基线 12 PASS),红在幕4-A「目标不是 47 那台」。
- 根因:幕4-A/幕4-B 的判定用**硬编码字符串** `alotbuy.com` 去匹配端点 ⇒ 豁免**真的生效了**、真的切回 47,**只是判定认不出**。
- 修法:两处匹配器改为取参数表 `DRILL_KILLED_MATCH`(`m3.includes(killedMatch)` / `m2.includes(killedMatch)`),并在注释块记录本次域迁移的勘误。
**踩坑 2 · 「查旧域不存在」的两种假象**
- 旧域 `/dshs-relay` 直取 = **404**:曾疑似回归 ⇒ **A/B 对照**:新域裸 GET **同样 404**;两域**带 WS 升级头都返回 101**
⇒ 非缺陷(`/dshs-relay` 只对 WebSocket 升级生效)。
- `admin/guest.ai1net.com` = **401**(不是 302):属既有"**无 session 直连实例页**"行为,**非回归**(档案 136 §7 E7 已登记口径)。
## 验收证据
| # | 判据 | 结果 |
|---|---|---|
| V1 | 签名 bootstrap 候选集 | **3 条 / 0 处旧域**,`source=seed-directory`,缓存 578 B |
| V2 | 零回归三件套 · 探针 `overlay-probe.cjs` | **29 PASS / 0 FAIL / 0 SKIP(rc=0)**;`OBS-21` = `count=3 hosts=3`,**47 / 106 两机读一致**,全表零 `alotbuy` |
| V3 | 门户可达 | 新域首屏 **200** |
| V4 | 旧域过渡 | 301 生效,**含子域映射**(`guest.alotbuy.com` → `https://guest.ai1net.com/...`) |
| V5 | relay 透传双域一致 | 裸 GET 双域同 404 / WS 升级双域同 101 |
| V6 | 零回归三件套 · `npm test` | 未跑(**本棒零测试面逻辑改动**;仅字符串与匹配器) |
| V7 | 零回归三件套 · 演练 `--scene all` | ✅ **12 PASS / 0 SKIP / 0 FAIL**(rc=0;**5m56s~6m**;末行原文 `# 结果:12 PASS / 0 SKIP / 0 FAIL`)—— 修后复跑,`幕4-A` 目标 = `wss://ai1net.com/dshs-relay`、耗时 15867 ms / deadline 30000 ms。⚠️ 全套含 120 s + 90 s 观察窗 ⇒ **外层超时必须 ≥ 8 min**,否则 `restore()` 不跑、**106 relay 会留在停机态** |
| V8 | 技能「三处」同步 | ✅ 6 个技能全部本机/文档库/47 镜像三处**域已清**;其中 3 个(decision-method / feature-first / plugin-diagnose)**三处 md5 全同**(`a8971220` / `a1397140` / `3edeeb89`);⚠️ 另 3 个(change-workflow / opensource-release / instance-diagnose)**本机副本更新 ⇒ 存在本会话之外的既存漂移**,本轮**只做域替换、⛔ 未覆盖新副本**(详见 §残留项 6) |
## 回滚
- **代码**:`git checkout -- src web scripts` + `npm run build` ⇒ scp 回 `/opt/dshs/`(产物清单见 `_中间产物_待清理/_证据_序50_部署产物15文件md5_20260919.txt`,15 个文件)。
- **47**:drop-in 恢复 5 条 SEEDS(原文在文件注释里)→ `daemon-reload` → `restart dshs`;nginx 旧域站点恢复原站(备份在同目录 `.bak-*`)。
- **106**:`cp /etc/dshs-worker.env.bak-dompurge-20260919-073149 /etc/dshs-worker.env` → `daemon-reload` → `restart dshs-worker`。
- 回滚后**必须**删一次 `/var/lib/dshs/overlay/directory.json` 并重启控制面(否则又读到旧缓存 ⇒ 假回滚成功)。
## 残留项 / 后续(不阻塞)
1. **旧域退役条件**:`alotbuy.com` 站点保持 301 过渡,**30 天零流量**后再评估删除;`relay-direct.conf`、`dsh.alotbuy.com.conf` 同属"导向新域 / 兜底"装置,**删除即净变差(R11)** ⇒ 保留。
2. **参数表 §11 历史补记**仍引用旧域(含旧证据原文引用)—— **故意不改**(历史记录)。
3. **`dshs-relay-client`(106)** = disabled 历史遗留,无害,未动。
4. **演练脚本修正版**按惯例应两机同源部署(`scripts/overlay-failover-drill.cjs` 是**本机 ssh 出去的工具**,不被任何单元读取 ⇒ 零服务影响)。
5. 控制面**按两台中继取并集** —— 立项见 **档案 136**(本轮只立项,未落地)。
6. **技能副本的既存漂移(⛔ 本会话之外形成,只报告未统一)**:`.workbuddy/skills/` 与文档库 `skills/` 三对不一致 ——
`dsh-change-workflow`(本机 1055 行 vs 文档库 1039 行)、`dsh-opensource-release`(本机 **1.8.2** vs 文档库 1.7.8)、
`dsh-instance-diagnose`(本机 SSH 别名更正 vs 文档库旧写法)。⇒ 本轮对这三份**只做域替换**(⛔ 不覆盖,避免丢内容);
镜像 `/opt/dsh/docs/skills/` 已与**文档库**对齐(md5 同)。**回头条件 = 有人真正要动用这三份技能时**,先做一次副本收敛。
7. **`/opt/dsh/docs` 镜像的更广泛漂移(⛔ 本单范围外,未推送)**:`docs-sync-check.sh`(须 `[email protected]`,默认别名已失效)
报出**30+ 个"仅本地待推送"文件** —— 属**本会话之前**就存在的镜像滞后,本轮**只推了 6 个技能文件**,其余按"只做被明确要求的事"**未动**。
8. **演练脚本 `overlay-failover-drill.cjs` 未铺服务器**:两机 `/opt/dshs{,-cluster}/scripts/` **本来就没有该文件**(它是本机 ssh 出去的工具)
⇒ 只在本机生效;而 `overlay-probe.cjs` 已按惯例**三处同源**(md5 `55568f87bfe2dc8e43289c69b4126004`)。