Files
dsh_ai1net_server/归档/交接单-20260924-归档/交接单_覆盖网络落地执行_20260916.md
T
admin 7bd1151c67 chore(工作区): 归档交接单 27 件 + 新增 CODEBUDDY §9 工作区卫生
交接单按归属约定(正文落文档库、工作区只放指针)归档至
归档/交接单-20260924-归档/,含逐件判定 README:
- 16 件已被文档库正式版取代(T09–T21 + 覆盖网络-24/25/26)
- 4 件主题已被覆盖网络线入口汇总
- 7 件历史接续包/规划件

CODEBUDDY §9:收口清本棒 tmp、tmp 保留期 7 天、禁「待清理」中间态、
工作区入库只放文档与文件、不保留脚本副本、>60 KB 单文件须逐个判。
2026-09-24 07:58:57 +08:00

23 KiB
Raw Blame History

交接单 · 覆盖网络落地执行(v2 · 2026-09-16 15:2x 复核后重写)

给执行会话:按本单开工,不读规划会话的上下文。 v2 改了什么:新增 §0 复核结论 + 6 条勘误(其中 A/B 为实测,改变了 S3 的做法);更正部署面(v1 只写 106,漏了 47);步骤改为 P1 对齐 47 → P2=S2 → P3=S3(改写) → P4=S4。 方案来源 = 工作区根 会合中继拆分_取证与改造方案_20260916.md —— 必读 §4 + §8 + §9。 用户的验收要求(原话口径):改造后 47 + 106 + 本机都能跑起来;现有功能与多用户机制正常。 分层规则见代码仓 docs/architecture.md(新模块必须守;npm run check:layering 会在 verify 里拦你)。


0. 本轮复核结论(2026-09-16 15:2x · 全部只读实测)

0.1 已核实基线(部署前先照这个对一遍)

项 实测值
本机 HEAD 640813e,工作区干净;lib/net/{reachability,rendezvous}.js 已 build(09-16 14:34)
47(Manager) dshs / dshs-worker / dshs-pg 全 active;Manager 跑 /opt/dshs/lib/cli.js(EnvironmentFile=/etc/dshs.env + drop-in dshs.service.d/cluster.conf)
47 代码版本 🔴 落后于 S0 —— /opt/dshs/lib/ 无 net/;config.js 指纹 91fc3756 ≠ 本机 1c3601f7(lib mtime 09-16 00:39)
106(Worker) dshs-worker active;/healthz ⇒ {"ok":true,"hostId":"w-106","instances":0,"tunnel":{"ready":true,"ports":[19000]}}
106 代码版本 ✅ S1 三件已上:config.js 1c3601f7、worker/tunnel.js 5f2649b4 == 本机;lib/net/ 无(无需,那是 Manager 侧文件)
dsh_hosts w-106 → http://127.0.0.1:19000(cap 2048)|w-47 → http://127.0.0.1:19100(cap 1024);共 7 列,无 via
用户 / 实例 admin@w-47、dbg2mx897@w-106、guest@w-106、pocuimwkrr@w-106;⚠️ 四条实例全 stopped ⇒ 验收要主动 launch
47 sshd gatewayports no|allowtcpforwarding yes|permitopen any|OpenSSH 8.0p1(106 = 9.3p2);lo = 127.0.0.1/8
47 端口面 0.0.0.0:32022 sshd(会合)|127.0.0.1:19000 sshd(106 隧道落点)|127.0.0.1:19100 node(w-47 agent)|127.0.0.1:15432 postgres

0.2 勘误(6 条 · A/B 为实测,🔴 照原方案字面做会白做或出事)

A 🔴 DSHS_RELAY_LOCAL_NAMESPACE(回环别名)实测无效 ⇒ S3 换做法。 实测:自 106 发起 ssh -R 127.0.0.2:19999:127.0.0.1:19000 -o ExitOnForwardFailure=yes ⇒ 47 上落点实际是 127.0.0.1:19999(另含 [::1]:19999),127.0.0.2 被 sshd 静默改写,ssh 侧零报错(ExitOnForwardFailure 未触发、日志为空;测试后已清理)。 根因:47 sshd -T ⇒ gatewayports no(默认)⇒ sshd 强制把远端转发绑到回环,客户端指定的绑定地址被丢弃。 ⇒ S3 改为「实例端口区间隔离」(§3 P3):同样打掉根因,且不改 sshd、不改协议、不需要 portMap。 将来若确需回环别名,只能开 GatewayPorts clientspecified —— 且只能开在新建的 relay sshd 上,⛔ 绝不动主 sshd 32022(那等于让持隧道密钥者可绑 0.0.0.0,命中 R5)。

B 🔴 端口撞号是真 bug**,不是"管道美化" —— 这才是 S3 的真实必要性。** findFreePort()(src/supervisor/spawn.ts:53,唯一调用点 src/supervisor/orchestrator.ts:595)在 worker 自己那台机器上随机取端口 ⇒ 两台 worker 取到同号是常态;而 tunnel.forward() 的返回值在两处都被忽略(src/worker/agent.ts:303、:205)⇒ 撞号时 -R 失败但静默;Manager 仍按 127.0.0.1:<port> 拨 ⇒ 打到另一个用户的实例。w-47 的本地实例与 w-106 的隧道落点共享 47 的 127.0.0.1 端口空间 ⇒ 只用两台机器就能触发。

C 🔴 部署面:v1 只写了 106,漏了 47。 S0 的 src/web/server.ts、src/supervisor/remote-spawner.ts、src/net/* 跑在 Manager(47);S2 的 hostsProvider 也在那儿。 ⇒ v1 说的"S1 已上线"只覆盖 Worker 侧;47 至今是 S0 之前的版本。执行时必须双侧对齐(§3 P1)。

D ⚠️ verify-cluster-cross.mjs 不能照抄跑,且有生产副作用**。** ① 默认 MANAGER=http://127.0.0.1:13080 = 演练端口(现役是 3080);AGENT_TOKEN 默认 cross-machine-token(现役 dshs-worker-7f3a91c05e);还需 ADMIN_PW(真实管理员口令)。 ② 它会 幂等注册 w-106 并把容量改成 4096、新建 crossuser<ts> 用户并审批、在 106 上拉真实例,可选注册 w-106b 并迁移实例 ⇒ 跑完必须清理(§5.2)。 ③ 脚本头部注释"控制面 PG 也在 106"已过时 —— PG 现在在 47(127.0.0.1:15432)。

E ⚠️ S4 有方案缺口:中继一旦不在 Manager 主机上,"Manager 拨落点"就断。 隧道落点在接收方那台机器的 127.0.0.1。中继换到别的机器 ⇒ Manager 拨不到 ⇒ 需要额外一跳(Manager 侧向中继建出向隧道 ssh -L,或中继侧把落点暴露到非回环地址 + nft 收窄)。 ⇒ 本轮 S4 只做同机 relay(47,独立单元 / 端口 / 密钥 / 账号);异地 relay 留待定这一跳。 ⛔ 不要用方案 §4 S4 的"会合指向第二个中继实例"当通过判据 —— 那要先解决本缺口;本轮改判据(见 §3 P4)。

F ⚠️ S2 减负:不要加 address 列。 endpoint 本身就是"要拨的地址",再加 address = 同义双真相(回填后必然逐字相等)。⇒ 只加 via(NOT NULL DEFAULT 'manager-ssh'),回填用一条显式 UPDATE。

方案 §8 的 ①②③ 仍然有效,其中 ① 本轮再次确认:proxy.ts:109 是发给上游 dsh 的 Host 头(dsh 的 /api 信任栅栏要 loopback),⛔ 绝不能改;真正的连接目标在 proxy.ts:136-137(host/port 取自 endpointFor)。


1. 只读前置(必做,全部只读)

  1. 抢锁:bash dsh-server-docs/scripts/handoff-guard.sh --claim-exec "<会话名>"
  2. 读方案 §4 + §8 + §9;读 docs/architecture.md §5(现存 5 条违规基线,别新增)
  3. 照 §0.1 表逐项复核基线(本机 HEAD|47 的 /etc/dshs.env + dshs.service.d/cluster.conf|106 的 /etc/dshs-worker.env|dsh_hosts 现值|47 端口面)
  4. 远程入口:ssh [email protected](22)|ssh test106(必须 -i ~/.ssh/id_ed25519_test106)

2. 范围

做:P1 → P2 → P3(P4 视 P1–P3 结果决定是否本轮) 不做:⛔ 不换传输协议(WireGuard / TURN 属远期)|⛔ 不动 proxy.ts:109|⛔ 不动权威状态(归属 / 租约 / 骨干资格仍只在 Manager 写)|⛔ 不做 DSHS_RELAY_LOCAL_NAMESPACE(勘误 A)|⛔ 不加 portMap(不再改"不同号")


3. 步骤(每步独立可验、独立可回滚)

P1 · 对齐 47 到 HEAD 640813e(把 S0+S1 真正补上 Manager 侧)

为什么先做这一步:47 现在落后一代,直接上 S2 就是"跳版部署",出问题无从二分。这一步本身零行为变化,风险最低。

  1. 本机门禁(Node 22,24 会因 better-sqlite3 ABI 全红): npm run build → npm test(须 44 项 / 0 失败)→ npm run verify → npm run check:layering(现存违规 5 条,不得新增)
  2. 备份 47(回滚点必须存在): ssh [email protected] 'tar czf /opt/dsh/backups/lib-pre-S0-$(date +%Y%m%d-%H%M).tgz -C /opt/dshs lib'
  3. 只传这几件(= git show --name-only 640813e 的 src/ 清单 → lib/ 产物;同名 .d.ts 一起传;lib/net/ 需先 mkdir -p):
    lib/config.js              lib/net/reachability.js     lib/net/rendezvous.js
    lib/supervisor/remote-spawner.js   lib/web/server.js
    lib/worker/agent.js        lib/worker/tunnel.js
    
    ⛔ 别整包覆盖 lib/ —— 会把别人直接改在服务器上的内容回退。
  4. 逐文件 md5 对账(47 上算完与本机比,必须逐条相等;.d.ts 也核)
  5. systemctl restart dshs(会短暂中断门户;本项目为开发环境服务器 ⇒ 直接做,动手前一句话说明在动什么)
  • 验收:① 47 三单元 active ② 门户 curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3080/ = 200 ③ 106 /healthz tunnel.ready=true ④ admin(w-47)与 guest(w-106)各自的实例页都能打开 + 工作区文件能读能写 ⑤ ls /opt/dshs/lib/net/ 有两个文件且 md5 == 本机
  • 回滚:systemctl stop dshs → 用步骤 2 的 tgz 还原 lib → systemctl start dshs

P2 · S2:dsh_hosts 增 via 列(打掉 C3)

  • 顺序(不能颠倒):先加列(带默认值)→ 再改代码 → 最后回填 —— 任一步中断都不崩
  • 加列:src/db/schema.ts 的 MIGRATIONS 追加 v8,双方言都要写:
    • sqlite:ALTER TABLE dsh_hosts ADD COLUMN via TEXT NOT NULL DEFAULT 'manager-ssh';
    • pg:同款 SQL(语法一致)
  • 改代码(6 处):
    文件 改什么
    src/db/pg.ts:56、src/db/repo.ts:598 HOST_COLS 末尾加 , via
    src/db/pg.ts:605-646、src/db/repo.ts:603-625 upsert 带上 via
    src/db/types.ts DshHost / UpsertDshHostInput 加 via?: string(缺省语义 = manager-ssh)
    src/web/routes/admin.ts:181-203 注册口接受 via,缺省 manager-ssh
    src/web/server.ts:256-266 hostsProvider:读 via → RendezvousRegistry.get(via) → resolve(id) → 塞 ClusterHost.reachability;via 未知/未设 ⇒ 回退 parseReachability(id, endpoint)
    src/net/rendezvous.ts LocalRendezvous / ManagerSshRendezvous 的 addressOf 由调用方从 db.listDshHosts() 建 map 传入(resolve 是 async,hostsProvider 本来就是 async ⇒ 无需改签名)
  • 回填(一条显式 SQL,幂等): UPDATE dsh_hosts SET via='local' WHERE id='w-47'; —— 判据:Manager 与 worker 同机 ⇒ 直连 ⇒ local;w-106 保持默认 manager-ssh(47 上 sshd 的隧道落点)。⛔ 别一刀切(方案 §8 勘误 ②)。
  • 部署:只改 47(S2 全是 Manager 侧代码,106 本轮无需改):传 lib/{config,net/*,db/*,web/*} 对账 → systemctl restart dshs
  • 验收: ① 单测扩展 test/reachability.test.mjs ⇒ 断言「迁移前 agentUrl」== 「迁移后 resolve() 结果」逐条相等,两条真实行写死(w-47→local、w-106→manager-ssh) ② 真机 /api/admin/hosts 返回带 via,且不含 agentToken ③ P1 的四条业务验收重跑一遍(多用户不受影响)
  • 回滚:列有默认值 ⇒ 旧代码读 endpoint 完全不受影响,列可留着不删

P3 · S3(改写):实例端口区间隔离

  • 目标:消除勘误 B 那条真 bug —— 跨 worker 同号 ⇒ 47 的 127.0.0.1 端口空间撞号 ⇒ 静默不转发 ⇒ 拨到别人的实例
  • 改哪些文件:
    文件 改什么
    src/supervisor/spawn.ts:53 findFreePort() → 增可选取址(区间内挑端口;区间为空 ⇒ 保持旧 listen(0) 行为)
    src/supervisor/orchestrator.ts:595 取端口改走区间(读 config)
    src/config.ts 新增 DSHS_INSTANCE_PORT_BASE(默认 0 = 旧行为)/ DSHS_INSTANCE_PORT_SPAN(默认 1000)
  • ⚠️ src/worker/agent.ts:352 不用改(instanceHost 仍 127.0.0.1)—— 这就是本方案相对原设计的简化点;也因此不需要 portMap
  • env 分配(必须互不重叠):w-47 → base 42000|w-106 → base 43000|span 各 1000
  • 部署:47 —— lib/{config,supervisor/*,worker/agent.js} + drop-in 加两行 env → systemctl restart dshs dshs-worker;106 —— lib/{config,supervisor/*} + /etc/dshs-worker.env 加两行 env → systemctl restart dshs-worker
  • 验收(三条端到端,缺一不可): ① 跨机实例页能打开 ② 跨机工作区文件能读能写 ③ 同一时刻 47 与 106 各起一个实例 ⇒ ss -lntp 上 47 的 127.0.0.1:w-47 实例端口 ∈ 42000+、w-106 实例端口 ∈ 43000+,且无重叠
  • 回滚:删两个 env ⇒ 回 listen(0) 随机(零代码回滚)
  • 不做:DSHS_RELAY_LOCAL_NAMESPACE(勘误 A)|portMap(不再改"不同号")

P4 · S4(本轮收尾,可延后):中继独立成单元

  • 做什么:47 上新建 dshs-relay.service = 独立 sshd 实例(独立端口如 32023 / 独立 host key / 独立 authorized_keys / 独立系统账号 dshsrelay;gatewayports no 保持不动)
  • worker 侧只改一行 env:DSHS_RENDEZVOUS_URL=ssh://[email protected]:32023
  • via 补一个实现:新增 RelayRendezvous(id 如 relay:47-32023),hostsProvider 经注册表选到它;w-106 的 via 由 manager-ssh 改指它
  • 验收(三条,已按勘误 E 改判据): ① 单中继下全链路通(实例面 + 文件面) ② 停掉中继 ⇒ 同机形态(w-47)实例仍可用、跨机代理失败但不崩 ③ ⛔ 不判"第二个中继实例";改判:把会合地址换回 32022 后,106 能重新注册并被正确解析(本轮能验的"会合可换"就到这里)
  • 回滚:DSHS_RENDEZVOUS_URL 指回 32022(或删掉它,走 DSHS_TUNNEL_TARGET 兜底)+ systemctl disable --now dshs-relay

4. 本机(客户端类型节点,不是 worker)

要验的是可行性评估缺口 1(全案唯一无证据的技术点):

  1. 本机起实例(soft 模式 —— Windows 无 bwrap/systemd)⇒ 实例页 200
  2. 实例内跑一次 bash 工具 ⇒ 验 dsh 在 Windows 的沙箱后端行为
  3. 本机 ↔ 47 / 106 的真实网络画像(NAT 类型 / 打洞可行性 / RTT + jitter)⇒ 直接填"最该先测三项"里的两项

⚠️ ⛔ 不得把本机接进现有生产链路(要独立形态、独立开关);本机是唯一开发机 ⇒ 用最小配额。


5. 全局验收(缺一 = 没做完)

5.1 通过项

  1. 多用户机制:admin(锚 w-47)、guest(锚 w-106)各自实例页 + 工作区文件都正常(用户硬要求)—— ⚠️ 当前四条实例全 stopped,要主动 launch
  2. verify-cluster-cross.mjs 全绿 —— 必须按勘误 D 的参数跑: MANAGER=http://127.0.0.1:3080 AGENT_TOKEN=dshs-worker-7f3a91c05e ADMIN_PW=<真实管理员口令> node scripts/verify-cluster-cross.mjs(AGENT2 留空 ⇒ 跳过第二台 worker 段)
  3. npm test + npm run verify 全绿,且 npm run check:layering 无新增违规
  4. 47:dshs / dshs-worker / dshs-pg active;106:dshs-worker active
  5. 每步的回滚点写清并实测过(至少回滚命令能跑)

5.2 跑完必须清理(勘误 D 的副作用)

  • 删掉 crossuser* 用户;把 dsh_hosts 里 w-106 的 capacity_mb 改回 2048
  • 若跑过 AGENT2 段:删掉 w-106b 登记,并把被迁移的实例改回 w-106
  • 自建的测试实例:stop 掉,别留在生产

6. 回报格式

判定 → 改了什么(文件 + 部署到哪台哪个路径 + md5 对账结果)→ 验收实测输出 → 未做 / 风险 → 锁状态 (截图或命令原文均可;⛔ 不许写"应该没问题")


7. 开工前已知的坑(省你踩一遍)

坑 事实
106 连不上 必须 -i ~/.ssh/id_ed25519_test106;其它 key 一律 Permission denied
47 的 cluster 配置 在 drop-in dshs.service.d/cluster.conf,不在 /etc/dshs.env
47 的 /opt/dshs 是 git 仓库 ⚠️ 但不是部署来源 —— 部署 = 本机 build 后 scp lib/ 文件;47 上 git log 只有初始提交 + 一堆 M ⇒ ⛔ 别在 47 上 git pull。git -C /opt/dshs 会报 dubious ownership,要加 -c safe.directory=/opt/dshs
47 的 PG 查询 只能 TCP:PGPASSWORD=dshs_cluster_2026 psql -h 127.0.0.1 -p 15432 -U dshs -d dshs;sudo -u postgres psql 不通(unix socket 未启用)
C4 其实不成立 106/47 的 worker env 都没有 DSHS_TUNNEL_STATIC_PORTS ⇒ 隧道不含控制面 PG ⇒ 回滚面比方案预估小
本机 git refs/remotes/** 写不进去 ⇒ git fetch 静默失败 ⇒ 分叉判定必须 git ls-remote 取裸 sha
本机 bash PATH 被 shim 重置 ⇒ 先 export PATH=<PortableGit>/usr/bin:<PortableGit>/bin:/c/Windows/System32:/c/Windows:$PATH;⛔ 进程替换 diff <(a) <(b) 不可用
本机跑测试 必须 Node 22(Node 24 会因 better-sqlite3 ABI 全红)

8. 执行记录(2026-09-16 15:5x–16:2x · P1/P2/P3 已完成并验收)

执行会话:覆盖网络-落地执行。P4 未做 —— 理由见 §8.4(命中 R5,需授权)。

8.1 ✅ P1 · 47 对齐到 640813e(Manager 侧补上 S0+S1)

  • 前置取证:全量产物指纹对账 ⇒ 47 与本地 HEAD 的差异恰好只有 S0+S1 那 7 个文件,无其他漂移。 ⚠️ 踩坑记录:本机 md5sum 输出 hash *path(二进制模式)、远端是 hash path(文本模式)⇒ 直接 diff 会得到"59 个文件全不同"的假象。判据必须先归一化路径字段。
  • 备份:/opt/dsh/backups/lib-pre-S0-20260916-1555.tgz
  • 部署:7 个 .js + 同名 .d.ts/.js.map(共 21 件)⇒ 47 /opt/dshs/lib/,属主/权限 197108:197121 644(与现存一致)
  • 验收实测:7 件 md5 逐条相等|dshs/dshs-worker/dshs-pg active|门户 200|w-47 与 w-106 agent 均可达(106 经隧道,tunnel.ready=true)|日志 0 错误
  • 业务验收(多用户,R4 临时会话): · admin(w-47)实例 running + 实例页 200 · guest(w-106)launch 成功 + 跨机实例页 200 + /api/desktop/tree 通 + mkdir 落盘已核实写在 106 的盘上
  • 痕迹清理:临时会话 DELETE 2⇒余 0|/tmp 临时件已删|guest 实例还原为 stopped

8.2 ✅ P2 · dsh_hosts 增 via(打掉 C3)

  • 改 7 处:db/schema.ts(v8 迁移,双方言同写)|db/types.ts(DshHost.via + UpsertDshHostInput.via? + toDshHost)|db/pg.ts / db/repo.ts(HOST_COLS + upsert)|web/routes/admin.ts(注册口接受 via)|web/server.ts(hostsProvider:读 via → RendezvousRegistry → resolve() → ClusterHost.reachability)|test/reachability.test.mjs(+2 条判据)
  • 两处按勘误 F 落地:不加 address 列(endpoint 本身就是要拨的地址,加列=同义双真相);via 省略时不覆盖已有值(COALESCE(?, dsh_hosts.via))—— 否则一次不带 via 的 join 会把回填好的 local 冲回默认
  • 回填(显式、幂等):UPDATE dsh_hosts SET via='local' WHERE id='w-47'; ⇒ 结果 w-47=local / w-106=manager-ssh ✅
  • 门禁:build rc=0|单测 46 项 / 0 失败|check:layering 无新增违规|迁移后 schema_migrations = 1–8,dsh_hosts 列含 via(默认 manager-ssh)
  • 验收实测:GET /api/admin/hosts ⇒ "via":"manager-ssh" / "via":"local",且不含 agentToken(只有 hasToken)|两条路径实例页均 200(w-47 同机 / w-106 跨机)|日志 0 错误
  • 补做的一处:v1 交接单只要求"API 返回带 via",实测发现 GET 列表原本不下发 via ⇒ 已补(否则运维看不见 = 加列白加)

8.3 ✅ P3(改写版)· 实例端口区间隔离

  • 按勘误 A 执行:⛔ 未做 DSHS_RELAY_LOCAL_NAMESPACE(实测被 sshd 静默改写)|⛔ 未加 portMap
  • 区间选定(避开 OS 临时端口段 32768-60999):w-47 = 20000, span 1000|w-106 = 21000, span 1000
  • 改 4 处:config.ts(instancePortBase/Span + toPortNumber 防 NaN)|supervisor/spawn.ts(findFreePortInRange + findInstancePort)|supervisor/orchestrator.ts(取端口改走区间)|新增 test/instance-port.test.mjs(5 条,已挂进 npm test/verify)
  • 部署:两台(47 /opt/dshs、106 /opt/dshs-cluster)各 3 件,md5 逐条相等;env 加在各自 /etc/dshs-worker.env;备份 lib-pre-S3-*.tgz + dshs-worker.env.bak-*
  • 门禁:build rc=0|单测 51 项 / 0 失败|check:layering 无新增违规
  • 验收实测(缺一不可的三条全过): ① 两台各起一个实例 ⇒ w-47 落 20000、w-106 落 21000 ② 47 上 127.0.0.1:20000(node 直连) 与 127.0.0.1:21000(sshd 隧道落点) 互不重叠 ⇒ 原先的撞号根因消除 ③ 实例页 admin(同机) 200 + guest(跨机) 200,跨机文件面通
  • 状态还原:guest 回 stopped(与开工前一致);admin 实例保持运行(与开工前一致)|临时会话已删|40 端口重启后旧端口(42185/37523/35765)全部释放

8.4 ⏳ P4 未做 —— 命中 R5,需要一句授权

做什么:47 上新建独立 dshs-relay.service(独立 sshd、独立端口 32023、独立 host key、独立 authorized_keys、非 root 账号 dshsrelay),106 的 DSHS_RENDEZVOUS_URL 指过去。

为什么停在这里:它要在公网面新开一个 SSH 监听口(32023) —— 属 **R5「权限只准收窄」**的反面(新暴露面),必须先知会。其余三项(P1–P3)都是"收窄/修复"性质,故直接做了。

P4 的收益与代价(供判断):

  • 优点:与宝塔面板共用的主 sshd 32022 解耦(面板改配置不再波及覆盖网络)|隧道密钥从 root 的 authorized_keys 挪到独立文件与非 root 账号|是"中继可多实例/可换机"的前置
  • 缺点:新增一个公网 SSH 入口(尽管只允许密钥 + restrict,port-forwarding)|受勘误 E 限制,异地中继仍差"一跳"未设计 ⇒ 本轮只能验同机形态

回滚:DSHS_RENDEZVOUS_URL 指回 ssh://[email protected]:32022 + systemctl disable --now dshs-relay。

8.5 本轮顺带确认的两件事(未改,仅记录)

  1. 控制面已在 PG 上:DSHS_DEPLOY_MODE=cluster + DSHS_DB_URL=postgres://…:15432/dshs;dshs 进程未打开任何 dshs.db(该文件 mtime 停在 09-15 17:51)= 纯回滚副本。⇒ 不存在"需要改造为 PG"这件事。
  2. 隧道密钥已受约束:47 的 authorized_keys 里 dshs-tunnel-106to47 那行带 restrict,port-forwarding ⇒ 该密钥只能做端口转发、拿不到 shell(另两把是部署键与个人键,无约束属预期)。 ⚠️ dsh_instances.status 不反映实时状态:实测 admin 的实例在跑,DB 里却是 stopped。运行态以 worker agent 的 /status/:userId 为准(Worker 按设计不写控制面数据)—— 排查时别拿这张表的 status 当事实。