Files
dsh_ai1net_server/归档/域名迁移_ai1net_20260919/pending/RUNBOOK-cutover.md
T
admin ce8e6ceed9 chore(工作区): 纳入版本控制基线(回收 411 MB 过程产物)
回收 411 MB(470 M → 58.8 M),全部经回收站,可恢复:
- 待清理/(146.2 M,含 relay 分片 128 M 与 42 项过程目录)
- tmp/(32.4 M,按接续棒命名的过程临时区)
- .workbuddy/tmp/(39.5 M)
- 4 份 workbuddy.db 冗余副本(101 M,09-23 事故的坏副本 / 抢救产物)
- tmp/im16/gw/centrifugo 二进制(63.9 M,可重下)+ 缓存残留

入库范围:常驻规则(CODEBUDDY.md / README.md / state.py)、在途接续入口与
接续包、docs/、交付物/、交接单/、归档/、scripts/、.codebuddy/、
.workbuddy/memory/;共 398 件,其中 >60 KB 的 26 件全为文档。

排除(.gitignore):tmp/、待清理/、运行态日志与缓存、*.db 与 DB 备份整目录、
打包二进制(*.tar.gz / *.tgz)、记忆修复前备份。
2026-09-24 07:51:03 +08:00

11 KiB
Raw Blame History

域名迁移 runbook —— alotbuy.com → ai1net.com(47 · 2026-09-19 建立)

本文件与同目录两个 *.conf 一起放在 47 的 /www/server/panel/vhost/nginx/_pending-ai1net/(待启用,nginx 只 include 该目录的 *.conf 一层,子目录天然不生效)。 目标:把线上部署的 DSH 服务域名由 alotbuy.com 切到 ai1net.com(门户 + 实例子域 + 中继兜底入口 + 平台 env)。

0. 现状(2026-09-19 取证)

项 现状
门户 vhost /www/server/panel/vhost/nginx/alotbuy.com.conf:alotbuy.com www.alotbuy.com *.alotbuy.com → 3080,/dshs-relay → 20080,证书 live/alotbuy.com(SAN alotbuy.com + *.alotbuy.com)
实例子域 <user>.alotbuy.com(一级标签,由门户 vhost 的 *.alotbuy.com 承载,无需第二张证书)
旧名 301 dsh.alotbuy.com.conf:dsh.alotbuy.com / *.dsh.alotbuy.com → 301 到 alotbuy.com / <label>.alotbuy.com,证书 live/dsh.alotbuy.com
中继兜底 relay-direct.conf:relay-direct.alotbuy.com(443),两端点 /dshs-relay + = /dshs-overlay/bootstrap(Host 改写成 alotbuy.com)
平台 env /etc/dshs.env:DSHS_BASE_DOMAIN=alotbuy.com、DSHS_COOKIE_DOMAIN=.alotbuy.com、DSHS_PORT=3080
中继引导种子 lib/config.js 内置 ['https://alotbuy.com/dshs-relay'];env 覆盖键 = DSHS_OVERLAY_BOOTSTRAP_SEEDS(splitList,可多值)
证书签发 certbot 1.22.0 + dns-cloudflare 插件;凭据 /etc/cloudflare.ini(令牌仅覆盖 alotbuy.com 一个 zone)

ai1net.com 侧已就绪的部分:DNS 已在 Cloudflare(ai1net.com / www / * / <x>.dsh 均解析到 CF)、CF→源站(47.77.182.89)链路已验证可达(用 80 端口探针文件经 CF 取回原文)、LE http-01 路径可用。

1. 🔴 唯一阻塞项(需用户侧提供)

*.ai1net.com 通配证书必须走 DNS-01(LE 对通配符只认 DNS-01),而 47 上现存的 CF 令牌只覆盖 alotbuy.com(实测 /zones 只返回 1 个 zone)⇒ 我无法为 ai1net.com 写 _acme-challenge TXT。

两条可选路径(任选其一):

  • A. 给一份 ai1net.com 域的 CF API 令牌(权限最小化:Zone → DNS → Edit,Zone Resources = 仅 ai1net.com) → 落到 /etc/cloudflare-ai1net.ini(dns_cloudflare_api_token = <令牌>,mode 600) → 我执行 S1:certbot certonly --dns-cloudflare --dns-cloudflare-credentials /etc/cloudflare-ai1net.ini --dns-cloudflare-propagation-seconds 60 -d ai1net.com -d '*.ai1net.com' --cert-name ai1net.com
  • B. 在 CF 面板生成 Origin CA 证书(主机名填 ai1net.com, *.ai1net.com),把 cert + key 文本给我 → 落到 /etc/ssl/ai1net/{fullchain.pem,privkey.pem},ai1net.com.conf 里两处证书路径改指它 → 无需给我任何 API 权限;代价是证书由 CF 侧管理、不参与 certbot 自动续期。

2. cutover 步骤(证书就绪后按序执行,每步带验收)

V=/www/server/panel/vhost/nginx
# S1 证书(见 §1;A 路径用 certbot,B 路径跳过)
certbot certificates | grep -A3 ai1net.com

# S2 启用两份 vhost(⛔ 证书不存在时**不要**做这步 ⇒ nginx 起不来 = 门户全挂)
cp -a $V/_pending-ai1net/ai1net.com.conf             $V/ai1net.com.conf
cp -a $V/_pending-ai1net/relay-direct.ai1net.com.conf $V/relay-direct.ai1net.com.conf
nginx -t && nginx -s reload
# 验收:curl -s --http1.1 -o /dev/null -w '%{http_code}\n' https://ai1net.com/portal.html   ⇒ 200

# S3 平台 env 切换(备份 → 改两项 → 追加种子(加性,保留 alotbuy 作第二种子)→ 重启)
cp -a /etc/dshs.env /etc/dshs.env.bak-dom-$(date +%Y%m%d-%H%M%S)
sed -i 's/^DSHS_BASE_DOMAIN=.*/DSHS_BASE_DOMAIN=ai1net.com/'     /etc/dshs.env
sed -i 's/^DSHS_COOKIE_DOMAIN=.*/DSHS_COOKIE_DOMAIN=.ai1net.com/' /etc/dshs.env
grep -q '^DSHS_OVERLAY_BOOTSTRAP_SEEDS=' /etc/dshs.env || \
  echo 'DSHS_OVERLAY_BOOTSTRAP_SEEDS=https://ai1net.com/dshs-relay,https://alotbuy.com/dshs-relay' >> /etc/dshs.env
systemctl restart dshs
# 验收:实例地址变成 <user>.ai1net.com 且能登录;中继仍在线(探针 29 PASS / 0 FAIL / 0 SKIP)

# S4 旧名连带项(**必须同批**,否则留半破状态)
#   S4a relay-direct.conf(alotbuy 版)的 Host 改写也必须指向新基底域:
#       proxy_set_header Host              alotbuy.com;   →  ai1net.com
#   S4b dsh.alotbuy.com.conf(旧名 301)的 map 目标改指新域(⚠️ 只改两行,别碰 server_name/正则):
#       default                               alotbuy.com;              →  ai1net.com;
#       ~^(?<label>[^.]+)\.dsh\.alotbuy\.com$ $label.alotbuy.com;       →  $label.ai1net.com;
nginx -t && nginx -s reload

# S5 worker 侧种子(可选,加性):/etc/dshs-worker.env 加同名多值 SEEDS,再 restart dshs-worker
#     ⚠️ 改前 `--scene all` 演练含观察窗口,预算 ≥8 min;⛔ 别套短超时

3. 回滚(任一步失败都能退回)

  1. 删两份新 vhost ⇒ nginx -t && nginx -s reload(门户立即回到 alotbuy 版)。
  2. cp -a /etc/dshs.env.bak-dom-<ts> /etc/dshs.env ⇒ systemctl restart dshs(回到 alotbuy.com / .alotbuy.com)。
  3. S4 的两处 sed 反向改回(alotbuy 版 vhost 与旧名 301 都可原样恢复)。
  4. 证书可留(不影响 alotbuy);certbot delete --cert-name ai1net.com 可彻底清掉。

4. 已知影响面(提前说明,非阻塞)

  • 切换瞬间所有人需要在新域重登一次:cookie 域由 .alotbuy.com 变 .ai1net.com ⇒ 旧 cookie 不再随请求发送(无法双域共存,一个 cookie 只能挂一个 Domain)。
  • 实例地址由 <user>.alotbuy.com 变为 <user>.ai1net.com:旧地址在 alotbuy 版 vhost 仍可用(两套 vhost 并存期间);若要彻底退役旧域,再补一条 alotbuy.com → ai1net.com 的 301(本轮不做,留待你确认退役时机)。
  • work.alotbuy.com(Gitea · 154.40.35.3)与本次无关,不动。
  • 中继引导种子改成「新域为主、旧域为备」⇒ 两域任一存活都不影响 worker 入网。

5. ✅ 执行结果(2026-09-19 06:1x–06:4x · 已全部落地)

步 状态 关键读数
S1 证书 ✅ certbot certonly --dns-cloudflare(凭据 /etc/cloudflare-ai1net.ini,mode 600,/zones 自检 = 可管 zone 数 1 = ai1net.com);结果 Successfully received certificate.|CN = ai1net.com,issuer Let's Encrypt YR1,notAfter = Dec 17 21:16:42 2026,SAN = ai1net.com + *.ai1net.com|复用既有 ACME 账号 63fe5a37…|certbot 已建自动续期任务
S2 vhost ✅ ai1net.com.conf(6811 B)+ relay-direct.ai1net.com.conf(2737 B)拷入 /www/server/panel/vhost/nginx/(nginx -t 失败即自动 rm -f 回滚);源站验收:https://ai1net.com/portal.html=200、https://dsh.ai1net.com/=200、SNI = CN=ai1net.com,alotbuy 仍 200
S3 平台 env ✅ 备份 /etc/dshs.env.bak-dom-20260919-061626(684 B);DSHS_BASE_DOMAIN=ai1net.com、DSHS_COOKIE_DOMAIN=.ai1net.com;restart dshs ⇒ active;日志 [relay-client] registered host=manager network=ops、presence SNAP 2 条
S4 连带项 ✅ 备份 relay-direct.conf.bak-dom-20260919-061658(3635 B)、dsh.alotbuy.com.conf.bak-dom-20260919-061658(982 B);relay-direct.conf 第 44 行 Host → ai1net.com;dsh.alotbuy.com.conf map 两目标 → ai1net.com / $label.ai1net.com;nginx -t + reload;验收:relay-direct.ai1net /dshs-overlay/bootstrap=200、/unknown=404、relay-direct.alotbuy /dshs-overlay/bootstrap=200、ai1net 门户=200
S5 worker ✅ 必须做(见 §5.2);106 DSHS_RENDEZVOUS_URL + relay-client --url 由旧域切新域,两机补同一份 5 条 SEEDS

5.1 端到端验收(经真实链路)

  • 门户:https://ai1net.com/portal.html = 200 / 50726 B / title=管理门户(与 alotbuy.com 逐字节同源同大小)。
  • 实例子域(mksess.cjs 临时会话,用完即删):admin.ai1net.com ⇒ 302 → ?token=…(实例已就绪);guest.ai1net.com ⇒ 302 → ai1net.com/wake.html?next=…(实例未跑 ⇒ 走过渡页拉起);无 cookie ⇒ 302 → ai1net.com/login.html。
  • 中继:/dshs-relay 在 ai1net.com / alotbuy.com / relay-direct.ai1net.com 三处响应逐字一致(dshs relay: WebSocket upgrade only)。
  • 覆盖网络探针:29 PASS / 0 FAIL / 0 SKIP(rc=0);OBS-21 两路径 count=5 hosts=5。

5.2 🔴 执行中发现并修掉的两个真问题(均已在生产验证)

(甲)种子列表「双载体」冲突 —— 我引入的回归。 S3 原脚本把 DSHS_OVERLAY_BOOTSTRAP_SEEDS 重复追加进 /etc/dshs.env;而该文件(EnvironmentFile)在本单元里压过 drop-in 的 Environment= ⇒ 生效列表被窄化成 2 条(ai1net + alotbuy,且两条同落 47 一台中继),丢掉 relay-direct.* 与 106.54.21.172 ⇒ Manager 不再对 106 建拨号会话。 ⇒ 修法:删除 /etc/dshs.env 里的重复键(回到 drop-in 是"唯一载体"的既定纪律),并把 drop-in 的列表升级成 5 条、主入口改到新域;同时给地址覆盖加 relay-direct.ai1net.com=47.77.182.89。备份 = overlay-443fb.conf.bak-dom-20260919-063350(2124 B) + /etc/dshs.env.bak-seeds-20260919-063350(772 B)。

(乙)w-106 用户实例子域 500 解析不出落点 —— 既有缺口,被本轮验收照出来。 现象 = 归属在 w-106 的用户(guest / dbg2mx897 / pocuimwkrr)打开实例子域 = 500 host "w-106" 声明 via=relay 但解析不出落点:拒绝回落到 endpoint。 机理 = 平台对 via=relay host 的可达性只读单一 DSHS_RELAY_STATUS_URL(http://127.0.0.1:20080/status = 47 中继),而 w-106 的实时在线态在 106 中继上(106 /status 明写 online: ["w-106(session=… ports=19000/21001 …)"]),47 中继那份是 online:false 的陈旧条目 ⇒ RelayRendezvous.resolve() 返回 undefined ⇒ agentBaseUrlOf() 按设计拒绝回落(R4/P0-3:relay 语义下回落会打到本机同号端口)⇒ 500。 ⇒ 修法(本轮的 S5 正好覆盖):给 106 的 worker 一份以新域为主入口的 SEEDS ⇒ worker 把主入口落回 47 中继注册 ⇒ 47 /status 该端点由 online:false 转 online:true ⇒ 解析恢复。验证:guest.ai1net.com 由 500 → 302(wake.html)。 ⚠️ 该 500 在"恢复 5 条候选"之后仍然复现 ⇒ 不是由(甲)单独造成的;真正修好它的是本步。 🔴 遗留(未修 · 已上报):只要 worker 因抖动漂到 106 自家中继,47 中继即再度视其为离线 ⇒ 同一 500 会回来,而探针此时仍是全绿(序㊾ 让它按并集看 ⇒ 观测面绿、控制面红)。⇒ 正解 = 把「两台中继并集」这条口径从探针推广到控制面(同一族问题),属独立立项,⛔ 本轮未动。