chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)

- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次)
- .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…),
  目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪
- .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/)
- .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*)
- .gitignore 补:备份件(*.bak-*)
- 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
This commit is contained in:
admin committed 2026-10-10 23:13:22 +08:00
1 parent 30b46dbd0c
commit c1b5e4d966
735 files changed
+153192 -2415

No files matched your search

@@ -0,0 +1,24 @@
set -u
TS=$(date +%Y%m%d-%H%M%S)
echo "=== [1] 备份 ==="
cp -a /etc/dshs-worker.env "/etc/dshs-worker.env.bak-dom-$TS"
cp -a /etc/systemd/system/dshs-relay-client.service "/etc/systemd/system/dshs-relay-client.service.bak-dom-$TS"
ls -la "/etc/dshs-worker.env.bak-dom-$TS" "/etc/systemd/system/dshs-relay-client.service.bak-dom-$TS"
echo "=== [2] worker env:rendezvous 切新域 + 追加种子(加性)==="
sed -i 's|^DSHS_RENDEZVOUS_URL=wss://alotbuy\.com/dshs-relay$|DSHS_RENDEZVOUS_URL=wss://ai1net.com/dshs-relay|' /etc/dshs-worker.env
if ! grep -q '^DSHS_OVERLAY_BOOTSTRAP_SEEDS=' /etc/dshs-worker.env; then
printf 'DSHS_OVERLAY_BOOTSTRAP_SEEDS=https://ai1net.com/dshs-relay,https://alotbuy.com/dshs-relay,https://relay-direct.ai1net.com/dshs-relay,https://relay-direct.alotbuy.com/dshs-relay,https://106.54.21.172/dshs-relay\n' >> /etc/dshs-worker.env
fi
grep -nE "RENDEZVOUS|BOOTSTRAP_SEEDS" /etc/dshs-worker.env
echo "=== [3] relay-client 单元:--url 切新域 ==="
sed -i 's|--url wss://alotbuy\.com/dshs-relay|--url wss://ai1net.com/dshs-relay|' /etc/systemd/system/dshs-relay-client.service
grep -n "ExecStart" /etc/systemd/system/dshs-relay-client.service
echo "=== [4] 残留 alotbuy 检查(应无 RENDEZVOUS/--url 命中)==="
grep -rn "alotbuy" /etc/dshs-worker.env /etc/systemd/system/dshs-relay-client.service | grep -v "BOOTSTRAP_SEEDS" || echo "(除种子兜底外无残留)"
echo "=== [5] daemon-reload + restart dshs-worker ==="
systemctl daemon-reload
systemctl restart dshs-worker
sleep 8
systemctl is-active dshs-worker dshs-relay
echo "=== [6] 启动日志 ==="
journalctl -u dshs-worker --since @$(date -d '90 sec ago' +%s) --no-pager 2>/dev/null | grep -iE "overlay-candidates|relay-client|registered|落点" | tail -8
@@ -0,0 +1,23 @@
set -u
TS=$(date +%Y%m%d-%H%M%S)
echo "=== [1] 备份 ==="
cp -a /etc/systemd/system/dshs.service.d/overlay-443fb.conf "/etc/systemd/system/dshs.service.d/overlay-443fb.conf.bak-dom-$TS"
cp -a /etc/dshs.env "/etc/dshs.env.bak-seeds-$TS"
ls -la "/etc/systemd/system/dshs.service.d/overlay-443fb.conf.bak-dom-$TS" "/etc/dshs.env.bak-seeds-$TS"
echo "=== [2] 安装新 drop-in ==="
install -m 644 /tmp/overlay-443fb.conf.new /etc/systemd/system/dshs.service.d/overlay-443fb.conf
grep -n "BOOTSTRAP_SEEDS\|ADDR_OVERRIDES" /etc/systemd/system/dshs.service.d/overlay-443fb.conf
echo "=== [3] 从 /etc/dshs.env 删除重复键 ==="
grep -v "^DSHS_OVERLAY_BOOTSTRAP_SEEDS=" /etc/dshs.env > /tmp/dshs.env.new
install -m 600 /tmp/dshs.env.new /etc/dshs.env
echo "残留计数(应为 0):"; grep -c "DSHS_OVERLAY_BOOTSTRAP_SEEDS" /etc/dshs.env || true
echo "=== [4] daemon-reload + restart dshs ==="
systemctl daemon-reload
systemctl restart dshs
sleep 6
systemctl is-active dshs
PID=$(systemctl show dshs -p MainPID --value)
echo "=== [5] 生效环境(进程实际)==="
tr '\0' '\n' < /proc/$PID/environ | grep -E "DSHS_OVERLAY_BOOTSTRAP_SEEDS|DSHS_OVERLAY_ADDR_OVERRIDES|DSHS_BASE_DOMAIN|DSHS_COOKIE_DOMAIN"
echo "=== [6] 启动日志(候选链 / 地址覆盖)==="
journalctl -u dshs --since @$(date -d '90 sec ago' +%s) --no-pager 2>/dev/null | grep -iE "overlay-candidates|addr-override|relay-client|registered" | tail -12
@@ -0,0 +1,11 @@
set -u
echo "=== [A] www 响应体 ==="
curl -s --max-time 15 -H "Host: www.ai1net.com" http://127.0.0.1:3080/portal.html; echo
echo "=== [B] 主机 → 路由 判定(直连 3080,不绕 nginx)==="
for h in ai1net.com www.ai1net.com guest.ai1net.com guest.alotbuy.com dsh.ai1net.com dsh.alotbuy.com; do
printf "%-26s " "$h"
curl -s -o /tmp/_b -w "code=%{http_code} size=%{size_download} " --max-time 15 -H "Host: $h" http://127.0.0.1:3080/
head -c 60 /tmp/_b | tr -d '\n'; echo
done
echo "=== [C] 权威库实例主机名 ==="
sudo -u postgres psql -p 15432 -d dshs -Atc "select table_name from information_schema.tables where table_schema='public' order by 1" 2>&1 | head -40
@@ -0,0 +1,10 @@
set -u
echo "=== /dshs-relay 握手:新域 vs 旧域(应同源同响应)==="
for u in https://ai1net.com/dshs-relay https://alotbuy.com/dshs-relay https://relay-direct.ai1net.com/dshs-relay; do
printf "%-46s " "$u"
curl -s -o /tmp/_r -w "code=%{http_code} size=%{size_download} " --max-time 12 "$u"
head -c 80 /tmp/_r | tr -d '\n'; echo
done
echo
echo "=== mksess.cjs 40-90 行(输出格式)==="
sed -n '40,90p' /opt/dshs/mksess.cjs
@@ -0,0 +1,25 @@
set -u
TOKEN=$(/usr/local/bin/node /opt/dshs/mksess.cjs guest 2>&1 | tail -1)
if [ ${#TOKEN} -ne 64 ]; then echo "mksess 失败: $TOKEN"; exit 1; fi
echo "临时会话已建(token 长度 ${#TOKEN})"
echo
echo "=== [1] 平台直连 3080(Host=guest.ai1net.com,带 cookie)==="
curl -s -o /tmp/_a -w "code=%{http_code} size=%{size_download} loc=%{redirect_url}\n" --max-time 20 \
-H "Host: guest.ai1net.com" -H "Accept: text/html" -H "Cookie: sid=$TOKEN" http://127.0.0.1:3080/
head -c 120 /tmp/_a | tr -d '\n'; echo
echo
echo "=== [2] 完整链路 nginx443(--resolve 直指本机,Host=guest.ai1net.com)==="
curl -sk -o /tmp/_b -w "code=%{http_code} size=%{size_download} loc=%{redirect_url}\n" --max-time 20 \
--resolve guest.ai1net.com:443:127.0.0.1 -H "Accept: text/html" -H "Cookie: sid=$TOKEN" \
https://guest.ai1net.com/
head -c 120 /tmp/_b | tr -d '\n'; echo
echo
echo "=== [3] 旧域对照(Host=guest.alotbuy.com,同 cookie)==="
curl -sk -o /tmp/_c -w "code=%{http_code} size=%{size_download} loc=%{redirect_url}\n" --max-time 20 \
--resolve guest.alotbuy.com:443:127.0.0.1 -H "Accept: text/html" -H "Cookie: sid=$TOKEN" \
https://guest.alotbuy.com/
head -c 120 /tmp/_c | tr -d '\n'; echo
echo
echo "=== [4] 清理临时会话 ==="
sudo -u postgres psql -p 15432 -d dshs -Atc "DELETE FROM sessions WHERE user_agent='poc-curl2'" 2>&1 | grep -v 'could not change'
sudo -u postgres psql -p 15432 -d dshs -Atc "SELECT count(*) FROM sessions WHERE user_agent='poc-curl2'" 2>&1 | grep -v 'could not change'
@@ -0,0 +1,19 @@
set -u
echo "=== [A] 47 中继 /status 摘要 ==="
curl -s --max-time 8 http://127.0.0.1:20080/status | /usr/local/bin/node -e '
let d="";process.stdin.on("data",c=>d+=c).on("end",()=>{try{const j=JSON.parse(d);
console.log("keys:",Object.keys(j).join(","));
if(j.peers)console.log("peers:",JSON.stringify(j.peers).slice(0,600));
if(j.endpoints)console.log("endpoints:",JSON.stringify(j.endpoints).slice(0,600));
if(j.counters)console.log("counters:",JSON.stringify(j.counters).slice(0,300));
}catch(e){console.log("RAW:",d.slice(0,400))}})'
echo
echo "=== [B] 目录缓存 ==="
ls -la /var/lib/dshs/overlay/ 2>/dev/null
if [ -f /var/lib/dshs/overlay/directory.json ]; then
/usr/local/bin/node -e 'const j=require("/var/lib/dshs/overlay/directory.json");const s=JSON.stringify(j);console.log("size:",s.length);console.log(s.slice(0,900))'
fi
echo
echo "=== [C] dshs 日志中的落点/目录相关 ==="
systemctl show dshs -p ActiveEnterTimestamp --value
journalctl -u dshs --since @$(date -d '20 min ago' +%s) --no-pager 2>/dev/null | grep -iE "落点|directory|拒|relay-client|dial" | tail -25
@@ -0,0 +1,11 @@
set -u
echo "=== DEFAULT_OVERLAY_BOOTSTRAP_SEEDS / DEFAULT_OVERLAY_SEED(config.js)==="
grep -n "DEFAULT_OVERLAY_BOOTSTRAP_SEEDS\|DEFAULT_OVERLAY_SEED\|DEFAULT_BASE_DOMAIN" /opt/dshs/lib/config.js | head -20
echo "--- 上下文 ---"
grep -n "DEFAULT_OVERLAY_BOOTSTRAP_SEEDS" -A 6 -B 3 /opt/dshs/lib/config.js | head -40
echo
echo "=== 中继如何生成目录文档(relays 字段)==="
grep -rn "relays" /opt/dshs/lib/net/relay/*.js | grep -v "\.map" | head -15
echo
echo "=== reachability.js 55-80 ==="
sed -n '52,80p' /opt/dshs/lib/net/reachability.js
@@ -0,0 +1,12 @@
set -u
echo "=== [A] dshs-relay 单元 ==="
systemctl cat dshs-relay 2>/dev/null
echo "=== [B] 中继进程实际环境里的 OVERLAY 变量 ==="
PID=$(systemctl show dshs-relay -p MainPID --value)
echo "MainPID=$PID"
tr '\0' '\n' < /proc/$PID/environ 2>/dev/null | grep -iE "OVERLAY|RELAY|SEED|DOMAIN" | sed 's/\(TOKEN\|SECRET\|KEY\)=[^ ]*/\1=***/g'
echo "=== [C] dshs(manager) 进程实际环境里的 OVERLAY 变量 ==="
PID2=$(systemctl show dshs -p MainPID --value)
tr '\0' '\n' < /proc/$PID2/environ 2>/dev/null | grep -iE "OVERLAY|RELAY|SEED|DOMAIN" | sed 's/\(TOKEN\|SECRET\|KEY\)=[^ ]*/\1=***/g'
echo "=== [D] 目录文档发布方(bootstrap 处理)==="
grep -rn "dshs-overlay/bootstrap\|BOOTSTRAP_PATH\|issueDirectory\|publishDirectory" --include=*.js /opt/dshs/lib 2>/dev/null | head -12
@@ -0,0 +1,6 @@
set -u
echo "=== web/routes/overlay.js ==="
cat /opt/dshs/lib/web/routes/overlay.js
echo
echo "=== 谁产生 relays 清单(directory.js 560-660)==="
sed -n '560,660p' /opt/dshs/lib/net/relay/directory.js
@@ -0,0 +1,19 @@
set -u
echo "=== [A] 「解析不出落点」最早出现时间(全量)==="
journalctl -u dshs --no-pager -o short-iso 2>/dev/null | grep -c "解析不出落点" || echo 0
echo "--- 最早 3 条 ---"
journalctl -u dshs --no-pager -o short-iso 2>/dev/null | grep "解析不出落点" | head -3 | cut -c1-120
echo "--- 最晚 1 条 ---"
journalctl -u dshs --no-pager -o short-iso 2>/dev/null | grep "解析不出落点" | tail -1 | cut -c1-120
echo
echo "=== [B] dshs 服务启动边界(判断是否跨重启)==="
journalctl -u dshs --no-pager -o short-iso 2>/dev/null | grep -E "Started|Stopped|Stopping" | tail -8
echo
echo "=== [C] 106 relay-client 单元为何 dead(是否被 disable)==="
systemctl is-enabled dshs-relay-client 2>/dev/null
systemctl show dshs-relay-client -p ExecMainStartTimestamp -p ExecMainExitTimestamp --value
echo
echo "=== [D] remote-spawner 落点解析来源 ==="
sed -n '100,150p' /opt/dshs/lib/supervisor/remote-spawner.js
echo "--- 200,230 ---"
sed -n '200,230p' /opt/dshs/lib/supervisor/remote-spawner.js
@@ -0,0 +1,11 @@
set -u
echo "=== [A] 「解析不出落点」计数与首末时间 ==="
journalctl -u dshs --no-pager -o short-iso 2>/dev/null | grep "解析不出落点" | wc -l
journalctl -u dshs --no-pager -o short-iso 2>/dev/null | grep "解析不出落点" | head -2 | cut -c1-60
journalctl -u dshs --no-pager -o short-iso 2>/dev/null | grep "解析不出落点" | tail -1 | cut -c1-60
echo "=== [B] dshs 启停边界 ==="
journalctl -u dshs --no-pager -o short-iso 2>/dev/null | grep -E "Started|Stopped|Stopping" | tail -6
echo "=== [C] 106 relay-client ==="
ssh -o BatchMode=yes -o ConnectTimeout=8 -o StrictHostKeyChecking=no 106.54.21.172 'systemctl is-enabled dshs-relay-client; systemctl show dshs-relay-client -p ActiveState -p ExecMainExitTimestamp --value' 2>&1 | tail -4
echo "=== [D] ensureHosts:hosts 目录来源 ==="
grep -n "ensureHosts" -A 45 /opt/dshs/lib/supervisor/remote-spawner.js | head -60
@@ -0,0 +1,6 @@
set -u
echo "=== hostsProvider / reachability 组装点 ==="
grep -rn "hostsProvider\|reachability" --include=*.js /opt/dshs/lib/supervisor /opt/dshs/lib/net 2>/dev/null | grep -v "\.map" | head -25
echo
echo "=== 谁读 DSHS_RELAY_STATUS_URL ==="
grep -rn "DSHS_RELAY_STATUS_URL\|RELAY_STATUS_URL" --include=*.js /opt/dshs/lib 2>/dev/null | grep -v "\.map" | head -10
@@ -0,0 +1,3 @@
set -u
echo "=== net/rendezvous.js 1-160 ==="
sed -n '1,160p' /opt/dshs/lib/net/rendezvous.js
@@ -0,0 +1,14 @@
set -u
cd /opt/dshs/lib 2>/dev/null || cd /
echo "=== [A] 错误串来源 ==="
grep -rn "unknown_user" --include=*.js /opt/dshs/lib 2>/dev/null | head -8
echo "---"
grep -rn "\"unauthorized\"\|'unauthorized'" --include=*.js /opt/dshs/lib 2>/dev/null | head -8
echo "=== [B] dsh_hosts 结构 ==="
sudo -u postgres psql -p 15432 -d dshs -Atc "select column_name||':'||data_type from information_schema.columns where table_name='dsh_hosts' order by ordinal_position" 2>&1 | grep -v 'could not change'
echo "=== [C] dsh_hosts 内容 ==="
sudo -u postgres psql -p 15432 -d dshs -Atc "select * from dsh_hosts order by 1" 2>&1 | grep -v 'could not change' | head -30
echo "=== [D] domains 结构 ==="
sudo -u postgres psql -p 15432 -d dshs -Atc "select column_name||':'||data_type from information_schema.columns where table_name='domains' order by ordinal_position" 2>&1 | grep -v 'could not change'
echo "=== [E] domains 内容 ==="
sudo -u postgres psql -p 15432 -d dshs -Atc "select * from domains" 2>&1 | grep -v 'could not change' | head -30
@@ -0,0 +1,15 @@
set -u
echo "=== 备份里的 SEEDS / 重复键检查 ==="
grep -c "DSHS_OVERLAY_BOOTSTRAP_SEEDS" /etc/dshs.env.bak-dom-20260919-061626
grep -n "DSHS_OVERLAY_BOOTSTRAP_SEEDS\|DSHS_OVERLAY_ADDR_OVERRIDES\|DSHS_BASE_DOMAIN\|DSHS_COOKIE" /etc/dshs.env.bak-dom-20260919-061626
echo
echo "=== 当前 /etc/dshs.env 里的重复键检查 ==="
grep -c "DSHS_OVERLAY_BOOTSTRAP_SEEDS" /etc/dshs.env
grep -n "DSHS_OVERLAY_BOOTSTRAP_SEEDS" /etc/dshs.env
echo
echo "=== drop-in 里是否还有种子/addr-override ==="
grep -rn "SEEDS\|ADDR_OVERRIDES" /etc/systemd/system/dshs.service.d/ 2>/dev/null
ls /etc/systemd/system/dshs.service.d/
echo
echo "=== 备份与当前文件差异(仅键名与种子行)==="
diff <(sed 's/=.*/=<v>/' /etc/dshs.env.bak-dom-20260919-061626) <(sed 's/=.*/=<v>/' /etc/dshs.env)
@@ -0,0 +1,12 @@
set -u
echo "=== relayStatusUrl 的使用点 ==="
grep -rn "relayStatusUrl" --include=*.js /opt/dshs/lib 2>/dev/null | grep -v "\.map" | head
echo
echo "=== hostsProvider: 的构造点 ==="
grep -rn "hostsProvider:" --include=*.js /opt/dshs/lib 2>/dev/null | grep -v "\.map" | head
echo
echo "=== 覆盖网络 host 目录构建(relay 视图 → reachability)==="
grep -rn "reachability:" --include=*.js /opt/dshs/lib 2>/dev/null | grep -v "\.map" | head -12
echo
echo "=== net/relay/rendezvous.js 关键段 ==="
grep -n "class \|reachability\|statusUrl\|endpoints\|online" /opt/dshs/lib/net/relay/rendezvous.js | head -40
@@ -0,0 +1,7 @@
set -u
echo "=== server.js 320-395 ==="
sed -n '320,395p' /opt/dshs/lib/web/server.js
echo "=== server.js 720-790 ==="
sed -n '720,790p' /opt/dshs/lib/web/server.js
echo "=== net/relay/rendezvous.js 20-70 ==="
sed -n '20,70p' /opt/dshs/lib/net/relay/rendezvous.js
@@ -0,0 +1,6 @@
set -u
echo "=== proxy.js 180-235 ==="
sed -n '180,235p' /opt/dshs/lib/supervisor/proxy.js
echo
echo "=== proxy.js 500,570 ==="
sed -n '500,570p' /opt/dshs/lib/supervisor/proxy.js
@@ -0,0 +1,7 @@
set -u
echo "=== parseSubdomain 实现 ==="
grep -rn "function parseSubdomain" -A 22 /opt/dshs/lib/supervisor/proxy.js
echo
echo "=== 是否存在 legacy/extra 域名配置 ==="
grep -rn "baseDomain\|base_domain\|legacyDomain\|extraDomains\|aliasDomain" --include=*.js /opt/dshs/lib/config* 2>/dev/null | head -20
ls /opt/dshs/lib | head -40
@@ -0,0 +1,11 @@
set -u
echo "=== 全部 443 监听块:来源文件 + server_name ==="
nginx -T 2>/dev/null | awk '
/^# configuration file / { f=$4 }
/listen[^;]*443/ { inblk=1; file=f; sn="" }
inblk && /server_name/ { gsub(/^[ \t]+/,""); sn=sn" "$0 }
inblk && /^}/ { if (sn!="") print file" |"sn; inblk=0 }
'
echo
echo "=== alotbuy.com.conf(现网,前 60 行)==="
sed -n '1,60p' /www/server/panel/vhost/nginx/alotbuy.com.conf
@@ -0,0 +1,14 @@
set -u
echo "=== [A] 平台是否把 baseDomain/实例子域下发前端 ==="
grep -rn "subdomainForUser\|baseDomain" --include=*.js /opt/dshs/lib/web 2>/dev/null | head -20
echo
echo "=== [B] users 表 label 列表 ==="
sudo -u postgres psql -p 15432 -d dshs -Atc "select username, role, disabled from users order by 1" 2>&1 | grep -v 'could not change'
echo
echo "=== [C] 门户页里是否出现 ai1net ==="
curl -s --max-time 10 http://127.0.0.1:3080/portal.html | grep -o "ai1net[^\"]*" | head -5
echo "--- alotbuy 出现次数 ---"
curl -s --max-time 10 http://127.0.0.1:3080/portal.html | grep -o "alotbuy" | wc -l
echo
echo "=== [D] work.alotbuy.com 是否被本机 nginx 承载 ==="
ls -la /www/server/panel/vhost/nginx/ | head -30
@@ -0,0 +1,10 @@
set -u
echo "=== [A] mksess 工具 ==="
find /opt/dshs -maxdepth 3 -name 'mksess*' 2>/dev/null | head
ls /opt/dshs/tools 2>/dev/null | head -20
echo "=== [B] 47 上的 env 文件 ==="
for f in /etc/dshs.env /etc/dshs-worker.env; do echo "--- $f ---"; if [ -f "$f" ]; then grep -vE '^\s*#|^\s*$' "$f" | sed 's/\(TOKEN\|SECRET\|KEY\)=.*/\1=***/'; else echo "(不存在)"; fi; done
echo "=== [C] 47 单元清单 ==="
systemctl list-units --type=service --all 'dshs*' --no-pager --plain 2>/dev/null | head -12
echo "=== [D] 106 侧 ==="
ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=no test106 'for f in /etc/dshs.env /etc/dshs-cluster.env /etc/dshs-worker.env; do echo "--- $f ---"; if [ -f "$f" ]; then grep -vE "^\s*#|^\s*$" "$f" | sed "s/\(TOKEN\|SECRET\|KEY\)=.*/\1=***/"; else echo "(不存在)"; fi; done; echo "--- units ---"; systemctl list-units --type=service --all "dshs*" --no-pager --plain 2>/dev/null | head -12' 2>&1 | tail -30
@@ -0,0 +1,5 @@
set -u
echo "=== 谁读 DSHS_OVERLAY_BOOTSTRAP_SEEDS ==="
grep -rn "DSHS_OVERLAY_BOOTSTRAP_SEEDS" /opt/dshs/lib 2>/dev/null | head -10
echo "=== mksess.cjs 用法(前 40 行)==="
sed -n '1,40p' /opt/dshs/mksess.cjs
@@ -0,0 +1,10 @@
set -u
echo "=== 47:所有 dshs 相关配置里出现 alotbuy 的位置 ==="
grep -rn "alotbuy" /etc/dshs*.env /etc/systemd/system/dshs*.service /etc/systemd/system/dshs*.service.d/*.conf 2>/dev/null | sed 's/\(TOKEN\|SECRET\|KEY\)=[^ ]*/\1=***/g'
echo "(以上为全部命中)"
echo
echo "=== 47:目录候选链默认值 ==="
sed -n '60,85p' /opt/dshs/lib/net/relay/directory.js
echo
echo "=== 47:config.js 218-232 ==="
sed -n '218,232p' /opt/dshs/lib/config.js
@@ -0,0 +1,7 @@
set -u
ls -la /opt/dshs/scripts/overlay-probe.cjs 2>/dev/null
echo "=== 用法(头部注释)==="
sed -n '1,40p' /opt/dshs/scripts/overlay-probe.cjs | grep -nE "用法|usage|node |--" | head -12
echo "=== 参数表位置 ==="
ls -la /opt/dshs/*参数表* /opt/dshs/docs/*参数表* 2>/dev/null | head -5
find / -maxdepth 4 -name "参数表_覆盖网络_*.md" 2>/dev/null | head -5
@@ -0,0 +1,5 @@
set -u
echo "=== overlay-443fb.conf 全文 ==="
cat /etc/systemd/system/dshs.service.d/overlay-443fb.conf
echo "=== overlay-dir.conf ==="
cat /etc/systemd/system/dshs.service.d/overlay-dir.conf 2>/dev/null | sed 's/\(SECRET\|KEY\)=.*/\1=***/'
@@ -0,0 +1,9 @@
set -u
echo "=== ADDR_OVERRIDES 解析格式 ==="
grep -rn "ADDR_OVERRIDES" --include=*.js /opt/dshs/lib 2>/dev/null | grep -v "\.map" | head -5
grep -rn "addrOverride" -A 12 /opt/dshs/lib/net/relay/directory.js 2>/dev/null | head -30
echo
echo "=== relay-direct.ai1net.com / .alotbuy.com 解析 ==="
for h in relay-direct.ai1net.com relay-direct.alotbuy.com; do printf "%-30s " "$h"; getent hosts "$h" | head -1; done
echo "=== 是否经 CF(响应头)==="
curl -skI --max-time 10 https://relay-direct.ai1net.com/dshs-relay 2>/dev/null | grep -iE "^HTTP|^server|^cf-ray" | head -5
@@ -0,0 +1,2 @@
set -u
sed -n '138,178p' /opt/dshs/lib/net/relay/addr-override.js
@@ -0,0 +1,15 @@
set -u
echo "=== 候选链(重启后)==="
journalctl -u dshs --since @$(date -d '3 min ago' +%s) --no-pager 2>/dev/null | grep "overlay-candidates" | tail -3
echo
echo "=== 47 中继 endpoints(w-106 是否 online)==="
curl -s --max-time 8 http://127.0.0.1:20080/status | /usr/local/bin/node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>{const j=JSON.parse(d);console.log(JSON.stringify(j.endpoints));console.log("sessions:",JSON.stringify(j.sessions??j.networks));})'
echo
TOKEN=$(/usr/local/bin/node /opt/dshs/mksess.cjs guest 2>&1 | tail -1)
echo "临时会话长度 ${#TOKEN}"
echo "=== 验收:guest.ai1net.com(带会话)==="
curl -sk -o /tmp/_v -w "code=%{http_code} size=%{size_download} loc=%{redirect_url}\n" --max-time 25 \
--resolve guest.ai1net.com:443:127.0.0.1 -H "Accept: text/html" -H "Cookie: sid=$TOKEN" https://guest.ai1net.com/
head -c 200 /tmp/_v | tr -d '\n'; echo
echo "=== 清理 ==="
sudo -u postgres psql -p 15432 -d dshs -Atc "DELETE FROM sessions WHERE user_agent='poc-curl2'" 2>&1 | grep -v 'could not change'
@@ -0,0 +1,8 @@
set -u
echo "=== 用户归属(main 实例)==="
sudo -u postgres psql -p 15432 -d dshs -Atc "select u.username, i.host_id, i.status from users u left join dsh_instances i on i.user_id=u.id and i.scope='main' order by 1" 2>&1 | grep -v 'could not change'
echo "=== dsh_instances 全量 ==="
sudo -u postgres psql -p 15432 -d dshs -Atc "select user_id, scope, host_id, status from dsh_instances order by 1,2" 2>&1 | grep -v 'could not change' | head -20
echo
echo "=== 106 中继当前实况 ==="
ssh -o BatchMode=yes -o ConnectTimeout=8 -o StrictHostKeyChecking=no -i /root/.ssh/id_rsa 106.54.21.172 'curl -s --max-time 6 http://127.0.0.1:20080/status' 2>/dev/null | /usr/local/bin/node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>{try{const j=JSON.parse(d);console.log("dialers:",JSON.stringify(j.dialers));console.log("sessions:",JSON.stringify(j.sessions));console.log("endpoints:",JSON.stringify(j.endpoints));}catch(e){console.log("RAW:",d.slice(0,200))}})'
@@ -0,0 +1,5 @@
set -u
echo "=== dsh_instances 列 ==="
sudo -u postgres psql -p 15432 -d dshs -Atc "select column_name from information_schema.columns where table_name='dsh_instances' order by ordinal_position" 2>&1 | grep -v 'could not change' | tr '\n' ' '; echo
echo "=== 归属 ==="
sudo -u postgres psql -p 15432 -d dshs -Atc "select u.username, i.host_id, i.status from users u left join dsh_instances i on i.user_id=u.id order by 1" 2>&1 | grep -v 'could not change'
@@ -0,0 +1,17 @@
set -u
sleep 5
echo "=== 47 中继:w-106 端点在线态 ==="
curl -s --max-time 8 http://127.0.0.1:20080/status | /usr/local/bin/node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>{const j=JSON.parse(d);console.log("endpoints:",JSON.stringify(j.endpoints));console.log("online:",JSON.stringify(j.online??null));})'
echo
TOKEN=$(/usr/local/bin/node /opt/dshs/mksess.cjs guest 2>&1 | tail -1)
echo "=== 验收:guest.ai1net.com(带会话)==="
curl -sk -o /tmp/_v -w "code=%{http_code} size=%{size_download} loc=%{redirect_url}\n" --max-time 25 \
--resolve guest.ai1net.com:443:127.0.0.1 -H "Accept: text/html" -H "Cookie: sid=$TOKEN" https://guest.ai1net.com/
head -c 260 /tmp/_v | tr -d '\n'; echo
sudo -u postgres psql -p 15432 -d dshs -Atc "DELETE FROM sessions WHERE user_agent='poc-curl2'" 2>&1 | grep -v 'could not change'
echo "=== 管理员用户对照(w-47 归属)==="
TOKEN2=$(/usr/local/bin/node /opt/dshs/mksess.cjs admin 2>&1 | tail -1)
curl -sk -o /tmp/_w -w "code=%{http_code} size=%{size_download} loc=%{redirect_url}\n" --max-time 25 \
--resolve admin.ai1net.com:443:127.0.0.1 -H "Accept: text/html" -H "Cookie: sid=$TOKEN2" https://admin.ai1net.com/
head -c 160 /tmp/_w | tr -d '\n'; echo
sudo -u postgres psql -p 15432 -d dshs -Atc "DELETE FROM sessions WHERE user_agent='poc-curl2'" 2>&1 | grep -v 'could not change'
@@ -0,0 +1,19 @@
set +e
echo "=== 清理临时探针与标记文件 ==="
rm -f /www/server/panel/vhost/nginx/zz-probe-scheme.conf
rm -f /www/server/nginx/html/probe-ai1net.txt /www/server/nginx/html/.well-known/acme-challenge/probe-token
rm -f /www/wwwroot/default/probe-ai1net.txt /www/wwwroot/47.77.182.89/probe-ai1net.txt /www/wwwroot/dsh.alotbuy.com/probe-ai1net.txt
rmdir /www/server/nginx/html/.well-known/acme-challenge /www/server/nginx/html/.well-known 2>/dev/null
nginx -t 2>&1 | tail -1
nginx -s reload 2>&1 | tail -1
sleep 1
echo "=== 复验:Host=ai1net.com 已回到默认 404(探针已撤) ==="
curl -s -o /dev/null -w "code=%{http_code}\n" -H "Host: ai1net.com" http://127.0.0.1/
echo "=== 复验:门户 alotbuy.com 仍 200 ==="
curl -s -o /dev/null -w "alotbuy 门户 => %{http_code}\n" -H "Host: alotbuy.com" http://127.0.0.1/
echo "=== 建暂存目录 ==="
mkdir -p /www/server/panel/vhost/nginx/_pending-ai1net
ls -ld /www/server/panel/vhost/nginx/_pending-ai1net
echo "=== 确认临时文件已清 ==="
ls /www/server/panel/vhost/nginx/ | grep -E "zz-probe|ai1net" || echo "(vhost 目录无 ai1net/zz-probe 残留 ✅)"
ls /www/server/nginx/html/ | head -6
@@ -0,0 +1,110 @@
# 域名迁移 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 步骤(**证书就绪后**按序执行,每步带验收)
```bash
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 会回来,而探针此时**仍是全绿**(序㊾ 让它按并集看 ⇒ 观测面绿、控制面红)。⇒ 正解 = 把「两台中继并集」这条口径**从探针推广到控制面**(同一族问题),属**独立立项**,⛔ 本轮未动。
@@ -0,0 +1,155 @@
# ai1net.com —— DSH 平台站点(2026-09-19 建立:域名 alotbuy.com → ai1net.com 迁移)
# 本文件与 alotbuy.com.conf **逐字同构**,只改三处:域名、证书路径、ACME 挑战落点。
# 1) 走 Cloudflare 代理(橙云)⇒ 必须 Full (strict):CF→源站证书须覆盖该主机名,否则 526
# 2) 80 端口「代理」而非 301:CF 若为 Flexible,源站 301 会造成 CF 侧循环
# 3) set_real_ip_from + CF-Connecting-IP 还原真实客户端 IP
# 4) proxy_buffering off(流式)+ gzip_proxied any(930KB bundle 压缩)
#
# ⚠️ 状态:**待启用**。启用前置 = 证书 /etc/letsencrypt/live/ai1net.com/{fullchain,privkey}.pem
# 存在(SAN 含 ai1net.com + *.ai1net.com)。证书未就绪前启用本文件 ⇒ nginx 起不来 ⇒ 门户全挂。
#
# 🔴 前置(2026-09-19 复验结论,⛔ 别照旧说"必须切 Full(strict)"):
# https 访客:CF→源站**已是 HTTPS 且在校验证书**(`https://ai1net.com` 现报 526 = 该校验的产物
# ⇒ zone 本已是 **Full (strict)**;Flexible 下 https 访客会 200 而不是 526)⇒ **无需再改模式**。
# http 访客:CF→源站走 **HTTP:80**(实测标记文件可经 http 取回)⇒ 与 alotbuy 的真实差别只在
# 「**Always Use HTTPS**」未开。⚠️ 未开时经 http 访问本块 = 明文回源
# ⇒ 建议在 CF 开启 Always Use HTTPS(边缘 301 掉),开启后源站 80 永不被用到(与 alotbuy 一致)。
# ---- HTTP:代理(不用 301,兼容 CF Flexible;Full (Strict) 下 80 不会被用到)----
server {
# CF 回源 IP → 还原真实客户端 IP(仅本 server 生效)
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;
real_ip_header CF-Connecting-IP;
listen 80;
server_name ai1net.com www.ai1net.com *.ai1net.com;
# ── ACME 挑战(http-01 续期用;`^~` 优先于下面的 `location /`)──
# 2026-09-19:本域名走 DNS-01 签发,但保留 webroot 落点 ⇒ 两条路都能续期,且回滚不掉链。
location ^~ /.well-known/acme-challenge/ {
root /www/server/nginx/html;
default_type text/plain;
access_log off;
}
location = /desktop.html { return 301 /portal.html; }
location = /plugins.html { return 301 /portal.html#/plugins; }
location = /skills.html { return 301 /portal.html#/skills; }
location / {
proxy_pass http://127.0.0.1:3080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
proxy_buffering off;
proxy_cache off;
gzip_proxied any;
}
access_log /www/wwwlogs/ai1net.com.log;
error_log /www/wwwlogs/ai1net.com.error.log;
}
# ---- HTTPS:门户 + 用户实例子域(<user>.ai1net.com)----
server {
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;
real_ip_header CF-Connecting-IP;
listen 443 ssl;
http2 on;
server_name ai1net.com www.ai1net.com *.ai1net.com;
ssl_certificate /etc/letsencrypt/live/ai1net.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ai1net.com/privkey.pem;
location ^~ /.well-known/acme-challenge/ {
root /www/server/nginx/html;
default_type text/plain;
access_log off;
}
location = /desktop.html { return 301 /portal.html; }
location = /plugins.html { return 301 /portal.html#/plugins; }
location = /skills.html { return 301 /portal.html#/skills; }
# ── DSH 覆盖网络中继(自研 relay)—— 与门户块 alotbuy.com.conf 逐字一致 ──
location /dshs-relay {
proxy_pass http://127.0.0.1:20080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
}
location / {
proxy_pass http://127.0.0.1:3080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
proxy_buffering off;
proxy_cache off;
proxy_send_timeout 3600s;
gzip_proxied any;
}
# 内容哈希命名的静态资源 → 长缓存
location ~* ^/assets/ {
proxy_pass http://127.0.0.1:3080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_buffering off;
gzip_proxied any;
expires 30d;
}
access_log /www/wwwlogs/ai1net.com.log;
error_log /www/wwwlogs/ai1net.com.error.log;
}
@@ -0,0 +1,43 @@
# 覆盖网络 · 序④(443/TCP 兜底)· 2026-09-17 建立 / 序⑥(2026-09-17)追加**第二中继**
# / 2026-09-19 域名迁移(alotbuy.com → ai1net.com)改主入口
#
# 1) 入口列表的唯一载体 = DSHS_OVERLAY_BOOTSTRAP_SEEDS(逗号多值;**主入口必须在首位**)。
# ⛔ 不用 `--url` / `DSHS_RELAY_URL` 表达入口 —— 那两个变量算"env 显式",会**压制整条引导链**
# (见 net/relay/directory.ts#resolveOverlayRelay ① / main.ts argv 说明),等于把兜底关掉。
# 改这里一次 ⇒ 签名目录里的 relays[] / bootstrap[] 跟着变 ⇒ 已入网节点下次刷新自动拿到(不重装)。
#
# 序⑥ 追加项 = **106 的第二台中继**(L3 跨机真容灾,接力棒 `交接单_最小形态真机批次_20260917` S8)。
# ⚠️ 顺序语义 = 「主入口首位」(参数表 §5.6 已把这条语义写死并明确"不引入显式优先级键")
# ⇒ 第二中继**放在末位**,⛔ 不得插到首位。
#
# 2) 地址覆盖(L1「去 CF」)= DSHS_OVERLAY_ADDR_OVERRIDES:把**逐字列出**的域名解析钉到指定 IP。
# 为什么必须有它:兜底子域与主域名同属泛解析(都被 CF 代理)⇒ 不做覆盖的话
# "多一条入口"并不等于"CF 不可用时还能连",解析层仍会把客户端送回 CF。
# ⛔ 未配 ⇒ 完全不生效(连补丁都不打);白名单之外一律走原始解析(定向,不是全局劫持)。
# ⚠️ 106 那条入口用的是**裸 IP 主机名**,不需要覆盖项(它本来就不过 CF)。
#
# 🔴 2026-09-19 域名迁移踩坑(**必须留档**):切 `DSHS_BASE_DOMAIN` 到 ai1net.com 时,曾把本条
# `DSHS_OVERLAY_BOOTSTRAP_SEEDS` **重复**追加进 `/etc/dshs.env`。而 `/etc/dshs.env`
# (EnvironmentFile)在本单元里**压过** drop-in 的 `Environment=` ⇒ 生效列表被**窄化**成
# 「ai1net + alotbuy」两条、且两条都落在 47 同一台中继上 ⇒ 丢掉 `106.54.21.172` 这条
# 第二中继 ⇒ Manager 不再对 106 建拨号会话 ⇒ 归属在 `w-106` 的用户实例子域直接
# 500 `host "w-106" 声明 via=relay 但解析不出落点:拒绝回落到 endpoint`。
# ⇒ 已从 `/etc/dshs.env` **删除**该重复键(回到"唯一载体"纪律),并把主入口改到新域。
# ⚠️ 教训:本列表**只在本文件里改**;`/etc/dshs.env` 里出现同名字段 = 会静默压掉这里。
#
# 现行列表(5 条 · 顺序语义 = 主入口首位 / 第二中继末位):
# ① https://ai1net.com/dshs-relay 主入口(**新服务域名**,2026-09-19 起)
# ② https://alotbuy.com/dshs-relay 旧域(过渡期保留;旧域仍在线,是一条独立入口)
# ③ https://relay-direct.ai1net.com/dshs-relay 443 兜底(新域对位,解析钉 IP 去 CF)
# ④ https://relay-direct.alotbuy.com/dshs-relay 443 兜底(旧域)
# ⑤ https://106.54.21.172/dshs-relay 第二中继(L3 跨机真容灾 · 裸 IP,不过 CF)
#
# 回滚(单步):删掉本文件 → `systemctl daemon-reload` → `systemctl restart dshs`
# ⚠️ 回滚时主入口项必须保留(本文件整体删除即回到"只有内置常量种子"的今天形态)。
# 只回滚域名迁移那一步:把 ①②③ 三条按旧域顺序改回
# `https://alotbuy.com/dshs-relay,https://relay-direct.alotbuy.com/dshs-relay,https://106.54.21.172/dshs-relay`
# 并同步把 DSHS_BASE_DOMAIN / DSHS_COOKIE_DOMAIN 改回 alotbuy.com。
# ⛔ 无论如何都不要只"删 ⑤" —— 见上方 2026-09-19 踩坑。
[Service]
Environment="DSHS_OVERLAY_BOOTSTRAP_SEEDS=https://ai1net.com/dshs-relay,https://alotbuy.com/dshs-relay,https://relay-direct.ai1net.com/dshs-relay,https://relay-direct.alotbuy.com/dshs-relay,https://106.54.21.172/dshs-relay"
Environment="DSHS_OVERLAY_ADDR_OVERRIDES=relay-direct.ai1net.com=47.77.182.89,relay-direct.alotbuy.com=47.77.182.89"
@@ -0,0 +1,52 @@
# relay-direct.ai1net.com —— 覆盖网络 **443/TCP 兜底入口**(域名迁移版 · 2026-09-19 建立)
#
# 与 live 的 relay-direct.conf(alotbuy 版)**逐字同构**,只改三处:
# 1) server_name relay-direct.alotbuy.com → relay-direct.ai1net.com
# 2) 证书 复用 ai1net.com 那张(`*.ai1net.com` 已含本名 ⇒ ⛔ 不新增证书)
# 3) 引导目录端点 Host 改写 alotbuy.com → ai1net.com(必须与 DSHS_BASE_DOMAIN 同步!
# 平台先按 Host 做租户路由、再进路由表;用兜底子域直接回源会命中 404 unknown_user)
#
# ⛔ 零新增:不新增监听口(仍 443/TCP)、不新增证书、不新增域名/解析记录(`*.ai1net.com` 泛解析天然覆盖)。
# ✅ 暴露面只收窄:本块只承载下面两个端点,其余路径一律 404(不复制门户任何路径)。
#
# ⚠️ 状态:**待启用**。前置 = 证书 /etc/letsencrypt/live/ai1net.com/ 就绪。
# 维护:`nginx -t` 通过后再 `nginx -s reload`;回滚 = 删掉本文件后同两步。
#
# 🔴 cutover 连带项(同一批做,否则留半破状态):
# · relay-direct.conf(alotbuy 版)的 Host 改写也须由 alotbuy.com → ai1net.com
# · dsh.alotbuy.com.conf(旧域名 301 块)的 map 目标须由 <label>.alotbuy.com → <label>.ai1net.com
server {
listen 443 ssl;
http2 on; # ⚠️ 与门户一致;curl 验收必须带 --http1.1(否则假 404)
server_name relay-direct.ai1net.com;
ssl_certificate /etc/letsencrypt/live/ai1net.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ai1net.com/privkey.pem;
# ── 中继入口(正文与门户块 /dshs-relay 逐字一致)──
location /dshs-relay {
proxy_pass http://127.0.0.1:20080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
}
# ── 引导目录端点(引导地址 = 中继入口同源;缺了它兜底入口拿不到签名目录)──
location = /dshs-overlay/bootstrap {
proxy_pass http://127.0.0.1:3080;
proxy_set_header Host ai1net.com;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_buffering off;
}
# ── 兜底:未知路径 404(可诊断;⛔ 不 return 444)──
location / { return 404; }
access_log /www/wwwlogs/relay-direct.ai1net.log;
error_log /www/wwwlogs/relay-direct.ai1net.error.log;
}
@@ -0,0 +1,24 @@
set +e
echo "=== [P1] 放置探针文件 ==="
for d in /www/wwwroot/default /www/wwwroot/47.77.182.89 /www/wwwroot/dsh.alotbuy.com; do
if mkdir -p "$d" 2>/dev/null && echo "MARKER-AI1NET-OK" > "$d/probe-ai1net.txt" 2>/dev/null; then
echo "OK $d/probe-ai1net.txt ($(stat -c%s "$d/probe-ai1net.txt" 2>/dev/null) B)"
else
echo "FAIL $d"
fi
done
echo "=== [P2] 各 80 vhost 的 server_name / root ==="
for f in 0.default.conf 47.77.182.89.conf; do
echo "--- $f ---"
grep -nE "server_name|^\s*root |listen " "/www/server/panel/vhost/nginx/$f" 2>/dev/null | head -8
done
echo "=== [P3] 本机按 Host=ai1net.com 请求 80 ==="
curl -s -o /dev/null -w "Host=ai1net.com http://127.0.0.1/ => %{http_code}\n" -H "Host: ai1net.com" http://127.0.0.1/ 2>&1 | tail -1
curl -s -H "Host: ai1net.com" http://127.0.0.1/probe-ai1net.txt 2>&1 | head -2
echo "=== [P4] 443 SNI 证书 ==="
for s in ai1net.com alotbuy.com; do
echo "--- SNI=$s ---"
echo | timeout 8 openssl s_client -connect 127.0.0.1:443 -servername "$s" 2>/dev/null | openssl x509 -noout -subject -issuer -dates 2>/dev/null | head -4
done
echo "=== [P5] 公网 IP ==="
curl -s --max-time 8 https://api.ipify.org 2>/dev/null; echo
@@ -0,0 +1,17 @@
set +e
R=/www/server/nginx/html
echo "=== [Q1] 放置探针到默认 80 根 ==="
mkdir -p "$R/.well-known/acme-challenge" 2>/dev/null
echo "MARKER-AI1NET-OK" > "$R/probe-ai1net.txt"
echo "ACME-TOKEN-OK" > "$R/.well-known/acme-challenge/probe-token"
ls -l "$R/probe-ai1net.txt" "$R/.well-known/acme-challenge/probe-token" 2>/dev/null | awk '{print $5, $9}'
echo "=== [Q2] 0.default.conf 全文 ==="
cat /www/server/panel/vhost/nginx/0.default.conf 2>/dev/null | head -40
echo "=== [Q3] well-known 目录 ==="
ls -la /www/server/panel/vhost/nginx/well-known/ 2>/dev/null | head -10
echo "=== [Q4] 本机自测(Host=ai1net.com) ==="
curl -s -H "Host: ai1net.com" http://127.0.0.1/probe-ai1net.txt 2>&1 | head -2
curl -s -o /dev/null -w "acme probe => %{http_code}\n" -H "Host: ai1net.com" http://127.0.0.1/.well-known/acme-challenge/probe-token 2>&1 | tail -1
echo "=== [Q5] 本机自测(Host=alotbuy.com 对照) ==="
curl -s -o /dev/null -w "alotbuy 门户 => %{http_code}\n" -H "Host: alotbuy.com" http://127.0.0.1/ 2>&1 | tail -1
curl -s -H "Host: alotbuy.com" http://127.0.0.1/probe-ai1net.txt 2>&1 | head -2
@@ -0,0 +1,18 @@
set +e
F=/www/server/panel/vhost/nginx/zz-probe-scheme.conf
cat > "$F" <<'CONF'
# 临时探针(2026-09-19,用完即删):判定 CF→源站用的是 http 还是 https
server {
listen 80;
server_name ai1net.com;
location / { return 200 "SCHEME=$scheme PROTO=$server_protocol XFP=$http_x_forwarded_proto CFIP=$http_cf_connecting_ip HOSTV=$host\n"; }
}
CONF
echo "--- nginx -t ---"
nginx -t 2>&1 | tail -1
nginx -s reload 2>&1 | tail -1
sleep 1
echo "--- 本机直连 80 自测 ---"
curl -s -H "Host: ai1net.com" http://127.0.0.1/ 2>&1 | head -2
echo "--- BT catchall 443 的 root ---"
grep -nE "server_name|root |ssl_certificate" /www/server/panel/vhost/nginx/0.catchall-443.conf 2>/dev/null | head -6
@@ -0,0 +1,22 @@
set +e
echo "=== [1] BT nginx vhost 文件 ==="
ls /www/server/panel/vhost/nginx/ 2>/dev/null | head -30
echo "=== [2] server_name 汇总 ==="
grep -rh "^[[:space:]]*server_name" /www/server/panel/vhost/nginx/ 2>/dev/null | sed 's/^[[:space:]]*//' | sort -u | head -30
echo "=== [3] 证书目录 ==="
ls /www/server/panel/vhost/cert/ 2>/dev/null | head -20
ls /etc/letsencrypt/live/ 2>/dev/null | head -10
echo "=== [4] wwwroot ==="
ls /www/wwwroot/ 2>/dev/null | head -20
echo "=== [5] dshs 单元定义(去敏感) ==="
systemctl cat dshs 2>/dev/null | grep -v -iE "secret|token|password|key=" | head -40
echo "=== [6] /etc/dshs.env 域名相关行 ==="
grep -iE "domain|host|url|origin|base|portal" /etc/dshs.env 2>/dev/null | head -25
echo "=== [7] /opt/dshs 内 alotbuy 命中文件 ==="
grep -rl "alotbuy" /opt/dshs --include="*.json" --include="*.js" --include="*.cjs" --include="*.mjs" --include="*.env" --include="*.conf" --include="*.ts" 2>/dev/null | head -20
echo "=== [8] /opt/dshs 内 ai1net 命中文件 ==="
grep -rl "ai1net" /opt/dshs 2>/dev/null | head -10
echo "=== [9] /etc/nginx 附加目录 ==="
ls -la /etc/nginx/conf.d/ 2>/dev/null | head -12
echo "=== [10] 监听 443 的进程 ==="
ss -lntp 2>/dev/null | grep -E ":443|:80 " | head -8
@@ -0,0 +1,30 @@
set +e
C=/www/server/panel/vhost/cert
echo "=== [A] alotbuy.com.conf ==="
sed -n '1,80p' /www/server/panel/vhost/nginx/alotbuy.com.conf 2>/dev/null
echo "=== [B] 证书主体/签发者 ==="
for d in alotbuy.com dsh.alotbuy.com; do
f="$C/$d/fullchain.pem"
if [ -f "$f" ]; then
echo "--- $d ---"
openssl x509 -in "$f" -noout -subject -issuer -dates 2>/dev/null
openssl x509 -in "$f" -noout -ext subjectAltName 2>/dev/null | tr ',' '\n' | head -8
else
echo "--- $d : 无 fullchain.pem ---"
fi
done
echo "=== [C] /opt/dshs 内 alotbuy 命中 ==="
grep -rl "alotbuy" /opt/dshs 2>/dev/null | head -20
echo "=== [D] /opt/dshs 内 ai1net 命中 ==="
grep -rl "ai1net" /opt/dshs 2>/dev/null | head -10
echo "=== [E] /etc/dshs.env 域名/URL 相关 ==="
grep -inE "domain|host|url|origin|base|portal|cookie" /etc/dshs.env 2>/dev/null | head -25
echo "=== [F] BT 是否存有 DNS/Cloudflare API 凭据(只报存在性) ==="
for p in /www/server/panel/data/dns_accounts.json /www/server/panel/config/cloudflare.json /root/.acme.sh/account.conf; do
if [ -f "$p" ]; then echo "存在: $p($(wc -c < "$p") B, mtime $(date -r "$p" '+%F %T' 2>/dev/null))"; else echo "不存在: $p"; fi
done
ls -d /root/.acme.sh 2>/dev/null && ls /root/.acme.sh 2>/dev/null | head -12
echo "=== [G] portal 站点根 ==="
ls /www/wwwroot/dsh.alotbuy.com/ 2>/dev/null | head -15
echo "=== [H] 443/80 监听 ==="
ss -lntp 2>/dev/null | grep -E ":443 |:80 " | head -6
@@ -0,0 +1,29 @@
set +e
C=/www/server/panel/vhost/cert
echo "=== [B2] 证书目录实际文件 ==="
ls -la "$C/alotbuy.com/" 2>/dev/null | head -12
echo "--- 证书信息(取第一个 pem) ---"
for f in "$C/alotbuy.com"/*.pem; do [ -f "$f" ] && { echo "FILE: $f"; openssl x509 -in "$f" -noout -subject -issuer -dates 2>/dev/null; openssl x509 -in "$f" -noout -ext subjectAltName 2>/dev/null | tail -3; break; }; done
echo "=== [A2] alotbuy.com.conf 的 443 段 ==="
awk '/listen 443/,0' /www/server/panel/vhost/nginx/alotbuy.com.conf 2>/dev/null | grep -vE "set_real_ip_from|^$" | head -45
echo "=== [C2] 活动 lib/web 里 alotbuy 具体行 ==="
grep -rn "alotbuy" /opt/dshs/lib /opt/dshs/web 2>/dev/null | head -12
grep -n "alotbuy" /opt/dshs/package.json 2>/dev/null | head -4
echo "=== [D] ai1net 命中 ==="
grep -rl "ai1net" /opt/dshs 2>/dev/null | head -8
echo "=== [E] /etc/dshs.env 全部键名(只报键,不报值) ==="
grep -oE "^[A-Za-z_][A-Za-z0-9_]*" /etc/dshs.env 2>/dev/null | sort -u | tr '\n' ' ' | head -c 1200
echo
grep -inE "domain|origin|portal|cookie|url" /etc/dshs.env 2>/dev/null | sed 's/=.*/=<值已隐藏>/' | head -12
echo "=== [F] BT DNS 凭据存在性 ==="
for p in /www/server/panel/data/dns_accounts.json /root/.acme.sh/account.conf /www/server/panel/config/panel.json; do
[ -f "$p" ] && echo "存在: $p ($(wc -c < "$p") B)" || echo "不存在: $p"
done
echo "=== [G] relay-direct.conf server_name ==="
grep -nE "server_name|listen|proxy_pass" /www/server/panel/vhost/nginx/relay-direct.conf 2>/dev/null | head -12
echo "=== [H] dsh.alotbuy.com.conf 是否仍在生效 ==="
grep -nE "server_name|listen|proxy_pass|root " /www/server/panel/vhost/nginx/dsh.alotbuy.com.conf 2>/dev/null | head -14
echo "=== [I] nginx -T 里 alotbuy 出现次数 ==="
nginx -T 2>/dev/null | grep -c "alotbuy"
echo "=== [J] 80/443 监听 ==="
ss -lntp 2>/dev/null | grep -E ":443 |:80 " | head -6
@@ -0,0 +1,28 @@
set +e
echo "=== [1] /etc/dshs.env 域名三项(值本身就写) ==="
grep -nE "^DSHS_BASE_DOMAIN|^DSHS_COOKIE_DOMAIN|^DSHS_SECURE_COOKIES|^DSHS_PORT|^DSHS_ISOLATION_MODE" /etc/dshs.env 2>/dev/null
echo "=== [2] lib/config.js 种子与 env 覆盖 ==="
sed -n '40,80p' /opt/dshs/lib/config.js 2>/dev/null
echo "--- 种子相关 env 键 ---"
grep -nE "RELAY_BOOTSTRAP|BOOTSTRAP|SEED" /opt/dshs/lib/config.js 2>/dev/null | head -8
echo "=== [3] BT 站点列表(panel 数据) ==="
ls /www/server/panel/data/db/ 2>/dev/null | head -5
python3 - <<'PY' 2>/dev/null || true
import sqlite3
try:
c=sqlite3.connect('/www/server/panel/data/default.db')
rows=list(c.execute("select name,domain,path,status from sites"))
for r in rows[:20]: print(r)
except Exception as e:
print('sqlite 读取失败:', e)
PY
echo "=== [4] BT 内 cloudflare/dns 相关文件 ==="
ls /www/server/panel/data/ 2>/dev/null | grep -iE "cloud|cf_|dns|acme" | head -8
echo "=== [5] 是否已存在 ai1net 任何配置 ==="
grep -rl "ai1net" /www/server/panel/vhost/ /etc/nginx/ /etc/dshs.env 2>/dev/null | head -8
echo "=== [6] nginx -t 现状(改前基线) ==="
nginx -t 2>&1 | tail -2
echo "=== [7] web/wake.html 里的 alotbuy 行 ==="
grep -n "alotbuy" /opt/dshs/web/wake.html 2>/dev/null | head -6
echo "=== [8] 实例 URL 派生处(BASE_DOMAIN 用法) ==="
grep -rn "DSHS_BASE_DOMAIN\|baseDomain" /opt/dshs/lib/*.js /opt/dshs/lib/web/*.js 2>/dev/null | head -12
@@ -0,0 +1,20 @@
set +e
echo "=== [R1] LE renewal 目录 ==="
ls -1 /etc/letsencrypt/renewal/ 2>/dev/null | head -10
echo "=== [R2] alotbuy.com 续期配置(去注释) ==="
grep -vE "^\s*#|^\s*$" /etc/letsencrypt/renewal/alotbuy.com.conf 2>/dev/null | head -25
echo "=== [R3] 证书 SAN ==="
openssl x509 -in /etc/letsencrypt/live/alotbuy.com/fullchain.pem -noout -subject -dates 2>/dev/null
openssl x509 -in /etc/letsencrypt/live/alotbuy.com/fullchain.pem -noout -ext subjectAltName 2>/dev/null | tr ',' '\n' | head -12
echo "=== [R4] certbot 与 DNS 插件 ==="
which certbot 2>/dev/null; certbot --version 2>&1 | head -2
certbot plugins 2>/dev/null | grep -iE "cloudflare|dns|webroot|nginx" | head -8
echo "=== [R5] 是否存在凭据文件(只报存在性,不打印内容) ==="
for p in /root/.secrets/cloudflare.ini /etc/letsencrypt/dns-cloudflare.ini /root/cloudflare.ini /etc/letsencrypt/cloudflare.ini; do
[ -f "$p" ] && echo "存在: $p ($(stat -c%s "$p") B, mode $(stat -c%a "$p"))" || echo "无: $p"
done
ls -la /root/.secrets/ 2>/dev/null | head -6
echo "=== [R6] dsh.alotbuy.com.conf 的证书路径 ==="
grep -nE "ssl_certificate|server_name|listen|proxy_pass" /www/server/panel/vhost/nginx/dsh.alotbuy.com.conf 2>/dev/null | head -10
echo "=== [R7] relay-direct.alotbuy.com 证书 ==="
grep -nE "ssl_certificate|server_name|listen" /www/server/panel/vhost/nginx/relay-direct.conf 2>/dev/null | head -8
@@ -0,0 +1,49 @@
set +e
echo "=== [S1] /etc/cloudflare.ini 存在性与键名(值不打印) ==="
ls -l /etc/cloudflare.ini 2>/dev/null
grep -oE '^[a-zA-Z_]+' /etc/cloudflare.ini 2>/dev/null | sort -u | tr '\n' ' '; echo
echo "=== [S2] 该令牌能看到的 zone(只报名字与 id 前 8 位) ==="
python3 - <<'PY' 2>&1 | head -20
import configparser, json, urllib.request
cp = configparser.ConfigParser()
try:
cp.read('/etc/cloudflare.ini')
sec = cp['dns_cloudflare'] if cp.has_section('dns_cloudflare') else cp[cp.sections()[0]]
d = dict(sec)
except Exception as e:
print('读取失败:', e); raise SystemExit
tok = d.get('dns_cloudflare_api_token')
email = d.get('dns_cloudflare_email')
key = d.get('dns_cloudflare_api_key')
print('凭据形态: token' if tok else ('key+email' if key else '未知'))
def call(path):
url = 'https://api.cloudflare.com/client/v4' + path
req = urllib.request.Request(url)
if tok is not None:
req.add_header('Authorization', 'Bearer ' + tok)
else:
req.add_header('X-Auth-Email', email or '')
req.add_header('X-Auth-Key', key or '')
try:
with urllib.request.urlopen(req, timeout=20) as r:
return json.loads(r.read().decode())
except Exception as e:
return {'error': str(e)}
r = call('/zones?per_page=50')
if 'result' in r:
zs = r['result']
print('可管理 zone 数 =', len(zs))
for z in zs[:20]:
print(' -', z['name'], z['id'][:8], z.get('status'))
names = {z['name'] for z in zs}
print('含 ai1net.com ?', 'ai1net.com' in names)
print('含 alotbuy.com ?', 'alotbuy.com' in names)
else:
print('查询结果:', json.dumps(r)[:200])
PY
echo "=== [S3] dsh.alotbuy.com 证书 SAN ==="
openssl x509 -in /etc/letsencrypt/live/dsh.alotbuy.com/fullchain.pem -noout -ext subjectAltName 2>/dev/null | tr ',' '\n' | head -8
echo "=== [S4] certbot 版本 & 是否可非交互 ==="
certbot --version 2>&1 | head -1
echo "=== [S5] nginx -t 基线 ==="
nginx -t 2>&1 | tail -1
@@ -0,0 +1,15 @@
set +e
echo "=== 既有 ACME 账号(复用,不新建) ==="
ls -1 /etc/letsencrypt/accounts/acme-v02.api.letsencrypt.org/directory/ 2>/dev/null | head -3
echo "=== S1 签发 ai1net.com + *.ai1net.com(DNS-01) ==="
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 \
--non-interactive --agree-tos 2>&1 | tail -18
echo "=== 证书落地核对 ==="
certbot certificates 2>/dev/null | grep -A5 "Certificate Name: ai1net.com" | head -8
openssl x509 -in /etc/letsencrypt/live/ai1net.com/fullchain.pem -noout -subject -issuer -dates 2>/dev/null
openssl x509 -in /etc/letsencrypt/live/ai1net.com/fullchain.pem -noout -ext subjectAltName 2>/dev/null | tr ',' '\n' | head -6
@@ -0,0 +1,28 @@
set +e
V=/www/server/panel/vhost/nginx
S=$V/_pending-ai1net
echo "=== S2-1 启用前 nginx -t 基线 ==="
nginx -t 2>&1 | tail -1
echo "=== S2-2 拷入两份 vhost ==="
cp -a "$S/ai1net.com.conf" "$V/ai1net.com.conf"
cp -a "$S/relay-direct.ai1net.com.conf" "$V/relay-direct.ai1net.com.conf"
ls -l "$V/ai1net.com.conf" "$V/relay-direct.ai1net.com.conf" | awk '{print $5, $9}'
echo "=== S2-3 nginx -t(失败即自动回滚) ==="
if nginx -t 2>&1 | tail -1 | grep -q successful; then
echo "✅ 语法通过 → reload"
nginx -s reload 2>&1 | tail -1
else
echo "❌ 语法失败 → 立即回滚并保持原状"
nginx -t 2>&1 | tail -3
rm -f "$V/ai1net.com.conf" "$V/relay-direct.ai1net.com.conf"
nginx -t 2>&1 | tail -1
exit 9
fi
sleep 1
echo "=== S2-4 源站侧验收(绕过 CF,直连 47) ==="
curl -sk --http1.1 -o /dev/null -w "origin https://ai1net.com/portal.html => %{http_code}\n" -H "Host: ai1net.com" https://127.0.0.1/portal.html
curl -sk --http1.1 -o /dev/null -w "origin https://dsh.ai1net.com/ => %{http_code}\n" -H "Host: dsh.ai1net.com" https://127.0.0.1/
echo "--- 源站侧按 SNI 看证书是否已是 ai1net(不再回 alotbuy 那张)---"
echo | timeout 8 openssl s_client -connect 127.0.0.1:443 -servername ai1net.com 2>/dev/null | openssl x509 -noout -subject 2>/dev/null
echo "=== S2-5 门户旧域未受影响 ==="
curl -s -o /dev/null -w "alotbuy.com => %{http_code}\n" -H "Host: alotbuy.com" http://127.0.0.1/
@@ -0,0 +1,28 @@
set +e
TS=$(date +%Y%m%d-%H%M%S)
echo "=== S3-1 切换前 env 现值 ==="
grep -E "^DSHS_BASE_DOMAIN|^DSHS_COOKIE_DOMAIN" /etc/dshs.env
echo "SEEDS 行数 = $(grep -c '^DSHS_OVERLAY_BOOTSTRAP_SEEDS=' /etc/dshs.env)"
echo "=== S3-2 备份 env ==="
cp -a /etc/dshs.env "/etc/dshs.env.bak-dom-$TS" && ls -l "/etc/dshs.env.bak-dom-$TS" | awk '{print $5, $9}'
echo "=== S3-3 改两项 + 追加中继种子(加性) ==="
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
grep -nE "^DSHS_BASE_DOMAIN|^DSHS_COOKIE_DOMAIN|^DSHS_OVERLAY_BOOTSTRAP_SEEDS" /etc/dshs.env
echo "=== S3-4 取一个真实用户名(只打印名字,不打印连接串) ==="
URL=$(sed -n 's/.*DSHS_DB_URL="\([^"]*\)".*/\1/p' /etc/systemd/system/dshs.service.d/cluster.conf | head -1)
if [ -n "$URL" ]; then
psql "$URL" -t -A -c "select name from users order by name limit 5" 2>&1 | head -6
else
echo "(未从 drop-in 取到 DB URL)"
fi
echo "=== S3-5 重启 dshs 并看活性 ==="
systemctl restart dshs
sleep 4
echo "is-active = $(systemctl is-active dshs)"
journalctl -u dshs --since "@$(( $(date +%s) - 60 ))" --no-pager 2>/dev/null | tail -6
echo "=== S3-6 源站侧回归(两域都应正常) ==="
curl -sk --http1.1 -o /dev/null -w "origin ai1net 门户 => %{http_code}\n" -H "Host: ai1net.com" https://127.0.0.1/portal.html
curl -sk --http1.1 -o /dev/null -w "origin alotbuy 门户 => %{http_code}\n" -H "Host: alotbuy.com" https://127.0.0.1/portal.html
@@ -0,0 +1,33 @@
set +e
V=/www/server/panel/vhost/nginx
TS=$(date +%Y%m%d-%H%M%S)
DB='postgres://dshs:[email protected]:15432/dshs'
echo "=== S4-0 取实例用户名(只打印名字) ==="
psql "$DB" -t -A -c "select name from users order by name limit 6" 2>&1 | head -8
echo "--- 表清单(若上面报错则看这里) ---"
psql "$DB" -t -A -c "select tablename from pg_tables where schemaname='public' order by tablename" 2>&1 | head -14
echo "=== S4-1 备份连带项两份 ==="
cp -a "$V/relay-direct.conf" "$V/relay-direct.conf.bak-dom-$TS"
cp -a "$V/dsh.alotbuy.com.conf" "$V/dsh.alotbuy.com.conf.bak-dom-$TS"
ls -l "$V"/relay-direct.conf.bak-dom-$TS "$V"/dsh.alotbuy.com.conf.bak-dom-$TS | awk '{print $5, $9}'
echo "=== S4-2 relay-direct.conf:Host 改写 → ai1net.com ==="
sed -i 's/^\([[:space:]]*proxy_set_header Host[[:space:]]*\)alotbuy\.com;/\1ai1net.com;/' "$V/relay-direct.conf"
grep -n "proxy_set_header Host" "$V/relay-direct.conf"
echo "=== S4-3 dsh.alotbuy.com.conf:map 目标 → 新域(⛔ 不动 server_name/正则) ==="
sed -i 's/^\([[:space:]]*default[[:space:]]*\)alotbuy\.com;/\1ai1net.com;/' "$V/dsh.alotbuy.com.conf"
sed -i 's/\$label\.alotbuy\.com;/$label.ai1net.com;/' "$V/dsh.alotbuy.com.conf"
sed -n '7,10p' "$V/dsh.alotbuy.com.conf"
echo "--- 确认 server_name 未被改(应仍是 alotbuy) ---"
grep -n "server_name" "$V/dsh.alotbuy.com.conf"
echo "=== S4-4 nginx -t + reload ==="
nginx -t 2>&1 | tail -1 && nginx -s reload 2>&1 | tail -1
sleep 1
echo "=== S4-5 兜底入口验收(源站侧) ==="
curl -sk --http1.1 -o /dev/null -w "relay-direct.ai1net /dshs-overlay/bootstrap => %{http_code}\n" -H "Host: relay-direct.ai1net.com" https://127.0.0.1/dshs-overlay/bootstrap
curl -sk --http1.1 -o /dev/null -w "relay-direct.ai1net /unknown => %{http_code}(应 404)\n" -H "Host: relay-direct.ai1net.com" https://127.0.0.1/unknown
curl -sk --http1.1 -o /dev/null -w "relay-direct.alotbuy /dshs-overlay/bootstrap => %{http_code}\n" -H "Host: relay-direct.alotbuy.com" https://127.0.0.1/dshs-overlay/bootstrap
curl -sk --http1.1 -o /dev/null -w "ai1net 门户 => %{http_code}\n" -H "Host: ai1net.com" https://127.0.0.1/portal.html
@@ -0,0 +1,271 @@
@@@@@ /www/server/panel/vhost/nginx/alotbuy.com.conf
# alotbuy.com —— DSH 平台站点(2026-09-10 建立,档案 21 场景延伸)
# 走 Cloudflare 代理(橙云),故:
# 1) 用 set_real_ip_from + CF-Connecting-IP 还原真实客户端 IP(否则日志/风控里全是 CF 的 IP)
# 2) 80 端口「代理」而非 301:CF 若为 Flexible,源站 301 会造成 CF 侧循环;改成代理两种模式都能用
# —— 但**必须**把 CF 的 SSL/TLS 模式设为 Full (Strict),否则 CF→源站是明文(凭据裸奔)
# 3) 继承 dsh 站点的关键优化:proxy_buffering off(流式)、gzip_proxied any(930KB bundle 压缩)
# ---- HTTP:代理(不用 301,兼容 CF Flexible;Full (Strict) 下 80 不会被用到)----
server {
# CF 回源 IP → 还原真实客户端 IP(仅本 server 生效)
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;
real_ip_header CF-Connecting-IP;
listen 80;
server_name alotbuy.com www.alotbuy.com *.alotbuy.com;
# 2026-09-13:3 个跳转桩页已删除(档案 80 第 7 项 / 档案 81 §四)—— 保留 301 防旧书签 404
location = /desktop.html { return 301 /portal.html; }
location = /plugins.html { return 301 /portal.html#/plugins; }
location = /skills.html { return 301 /portal.html#/skills; }
location / {
proxy_pass http://127.0.0.1:3080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
proxy_buffering off;
proxy_cache off;
gzip_proxied any;
}
access_log /www/wwwlogs/alotbuy.com.log;
error_log /www/wwwlogs/alotbuy.com.error.log;
}
# ---- HTTPS:门户 + 用户实例子域 ----
server {
# CF 回源 IP → 还原真实客户端 IP(仅本 server 生效)
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;
real_ip_header CF-Connecting-IP;
listen 443 ssl;
http2 on;
server_name alotbuy.com www.alotbuy.com *.alotbuy.com;
ssl_certificate /etc/letsencrypt/live/alotbuy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/alotbuy.com/privkey.pem;
# 2026-09-13:3 个跳转桩页已删除(档案 80 第 7 项 / 档案 81 §四)—— 保留 301 防旧书签 404
location = /desktop.html { return 301 /portal.html; }
location = /plugins.html { return 301 /portal.html#/plugins; }
location = /skills.html { return 301 /portal.html#/skills; }
# ── DSH 覆盖网络中继(自研 relay)────────────────────────────────
# 只在既有 443 server 块里加一个 location:不新增监听口、不新增证书、不动门户其它路径。
# `/status` 不在此前缀下 ⇒ 不会被代理出去(relay 端也只认 RELAY_PATH 前缀)。
location /dshs-relay {
proxy_pass http://127.0.0.1:20080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
}
location / {
proxy_pass http://127.0.0.1:3080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
# 流式(dsh 回复/思考逐块下发)+ 反代压缩 + 解除缓冲
proxy_buffering off;
proxy_cache off;
proxy_send_timeout 3600s;
gzip_proxied any;
}
# 内容哈希命名的静态资源 → 长缓存
location ~* ^/assets/ {
proxy_pass http://127.0.0.1:3080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_buffering off;
gzip_proxied any;
expires 30d;
}
access_log /www/wwwlogs/alotbuy.com.log;
error_log /www/wwwlogs/alotbuy.com.error.log;
}
@@@@@ /www/server/panel/vhost/nginx/dsh.alotbuy.com.conf
# dsh.alotbuy.com —— 旧域名(2026-09-10 平台迁移到 alotbuy.com 后仅做 301 跳转)
# 用 map + 通配 server_name(比正则 server_name 更稳),保留用户名映射:
# admin.dsh.alotbuy.com → admin.alotbuy.com
# dsh.alotbuy.com → alotbuy.com
# map 必须位于 http 上下文 —— 面板 vhost 文件正是被 include 在 http{} 内,故写在本文件顶层。
map $host $alotbuy_new_host {
default alotbuy.com;
~^(?<label>[^.]+)\.dsh\.alotbuy\.com$ $label.alotbuy.com;
}
server {
listen 80;
server_name dsh.alotbuy.com *.dsh.alotbuy.com;
return 301 https://$alotbuy_new_host$request_uri;
}
server {
listen 443 ssl;
server_name dsh.alotbuy.com *.dsh.alotbuy.com;
ssl_certificate /etc/letsencrypt/live/dsh.alotbuy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dsh.alotbuy.com/privkey.pem;
return 301 https://$alotbuy_new_host$request_uri;
}
@@@@@ /www/server/panel/vhost/nginx/relay-direct.conf
# relay-direct.alotbuy.com —— 覆盖网络 **443/TCP 兜底入口**(序④ · L2「去门户站点 conf」)
#
# 为什么要有这个块(而不是往门户块里再加一条 location):
# 门户块 `/www/server/panel/vhost/nginx/alotbuy.com.conf` 由宝塔面板管理 —— 面板重写配置、
# 或人工改坏它,都会**同时**打掉门户与 relay 入口(两者共用一个失败域)。
# 独立 `server_name` ⇒ 本入口与门户块**互不影响**(nginx 精确名优先于 `*.alotbuy.com` 通配)。
#
# ⛔ 零新增:不新增监听口(仍 443/TCP)、不新增证书(复用 `*.alotbuy.com` 那张)、
# 不新增域名/解析记录(`*.alotbuy.com` 泛解析天然覆盖本名)、不新增花费、不新增依赖。
# ⛔ 不碰门户 443 块、不碰 relay 进程(relay 仍是 `127.0.0.1:20080` 的纯 `ws://`,TLS 由 nginx 终结)。
# ✅ 暴露面**只收窄**:本块只承载下面两个端点,其余路径一律 404(不把门户任何路径复制过来)。
#
# 维护:`nginx -t` 通过后再 `nginx -s reload`;回滚 = 删掉本文件后同两步。
# 变更记录:2026-09-17 建立(交接单_443兜底_20260917.md §5 S1)。
server {
listen 443 ssl;
http2 on; # ⚠️ 与门户一致;curl 验收必须带 --http1.1(否则假 404)
server_name relay-direct.alotbuy.com;
ssl_certificate /etc/letsencrypt/live/alotbuy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/alotbuy.com/privkey.pem;
# ── 中继入口(正文与门户块 `/dshs-relay` 逐字一致,只改 server_name / 证书两处变量)──
location /dshs-relay {
proxy_pass http://127.0.0.1:20080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
}
# ── 引导目录端点(约定「引导地址 = 中继入口同源」;缺了它兜底入口拿不到签名目录)──
# 只放行这一个路径(`=` 精确匹配)—— 目录端点只有这一个(`directory.ts` 的 DIRECTORY_PATH)。
#
# ⚠️ `Host` 必须改写成既有目录 origin:平台(3080)**先按 Host 做租户路由、再进路由表**,
# 直接用兜底子域回源会命中 `404 {"error":"unknown_user"}`(实测 2026-09-17 09:00)。
# 这是「同源」在入口层的等价翻译 —— 发的就是门户今天在发的同一个请求,**不扩大任何权限**
# (本 location 只放行这一个公开只读路由;本块其余路径一律 404)。
location = /dshs-overlay/bootstrap {
proxy_pass http://127.0.0.1:3080;
proxy_set_header Host alotbuy.com;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_buffering off;
}
# ── 兜底:本块**只**承载上面两个端点。未知路径 404(可诊断;⛔ 不 return 444,
# 444 是「这个域名不存在」的语义,会让"路径写错"看起来像"域名没配")──
location / { return 404; }
# 说明:本块**故意不复制**门户块的 `set_real_ip_from` 那一组 —— 本块只服务两个机器端点,
# 不依赖客户端 IP(无 ACL / 无限速 / 无风控);日志里出现 CF 回源 IP 不影响可诊断性。
access_log /www/wwwlogs/relay-direct.log;
error_log /www/wwwlogs/relay-direct.error.log;
}
@@@@@ 查找 dsh 用户名映射文件
2:# 用 map + 通配 server_name(比正则 server_name 更稳),保留用户名映射:
5:# map 必须位于 http 上下文 —— 面板 vhost 文件正是被 include 在 http{} 内,故写在本文件顶层。
7:map $host $alotbuy_new_host {
@@@@@ worker/env 文件清单
-rw------- 1 root root 684 Sep 13 15:46 /etc/dshs.env
-rw------- 1 root root 529 Sep 17 08:17 /etc/dshs-worker.env
--- systemctl cat dshs-worker ---
# /etc/systemd/system/dshs-worker.service
[Unit]
Description=DSHS cluster worker agent (this host = 47, local users' instances)
After=network-online.target
[Service]
Type=simple
EnvironmentFile=/etc/dshs-worker.env
ExecStart=/usr/local/bin/node /opt/dshs/lib/cli.js worker --port 19100 --host 127.0.0.1 --host-id w-47 --instance-host 127.0.0.1 --log-level info
Restart=on-failure
RestartSec=3
KillMode=mixed
[Install]
WantedBy=multi-user.target
--- systemctl cat dshs-relay ---
# /etc/systemd/system/dshs-relay.service
[Unit]
Description=dshs relay (loopback-only WebSocket relay for the DSH overlay network)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/dsh-relay
ExecStart=/usr/local/bin/node /opt/dsh-relay/lib/net/relay/main.js --port 20080 --keys-file /etc/dshs/relay-keys.json --base 19000 --span 3000 --max-hosts 0
Restart=always
RestartSec=1
TimeoutStopSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=dshs-relay
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/dshs-relay.service.d/40-content-probe.conf
# 覆盖网络 序㉔(2026-09-17):内容面**活性自证** —— relay 侧块级内容寻址的判据落点。
#
# 背景:`OBS-17` 第三个判据要求 `local + peer` 命中 ≥ `CONTENT_TIER_HITS_MIN`(=1)。
@@@@@ 两机 SEEDS/RELAY env 命中