21 Commits

Author SHA1 Message Date
eli 4502595776 docs(research): 归档 Caddy v2.11.4 上游 gRPC/h2 长流源码级调研报告(含引用与逐行证据) 2026-10-11 09:54:22 +08:00
eli 22e9078d79 docs(HANDOFF): 补 Caddy v2.11.4 源码级结论(keepalive_interval 非 h2 PING、别盲升级、2.11.4 有 #8185 挂起 bug) 2026-10-11 09:54:14 +08:00
eli 700e609e01 docs(HANDOFF): 实测证明 Caddy 不掐 gRPC——300s 是 traefik 的 504、404 是 proxy 用 IP 直连公司缺 SNI/Host
给 netbird-mgmt.caddy 临时加 log 后,把 Caddy 访问日志 / proxy 容器日志 / 容器 netns 连接表
按 UTC 对齐,结论:

1) Job 流经 Caddy 存活 300.074s,状态 504 且 error=None、带上游响应头与 Via: 2.0 Caddy
   ⇒ 504 是上游 traefik 发的,Caddy 只是透传(出超时会记 error),与直连同为一个 300s;
2) 0.2s 那类条目是 netbird 自身的短流(SignalExchange/Send 0.075-2.6s、ConnectStream 0s),
   全部 200,signal 客户端本就频繁重建;
3) 期间 1 次/秒的重试风暴(Unimplemented = HTTP 404)不经 Caddy:Caddy 该站点 5 分钟
   18 条日志无任何 404,容器连接表全部指向 183.238.250.166:10443、到本机 443 为 0;
4) 同一方法 SyncMappings 打公司 10443:用域名 200、直接用 IP 404(Go 默认 404 页)、
   用 IP 加 Host 头又是 200 ⇒ 根因是 proxy 以 IP 直连公司、缺 SNI/Host 导致 traefik 未匹配。

因此 dest+SNI 裸透传方案不必要(已记录未实施),并把 netbird 侧两项待办写清。
2026-10-11 09:52:18 +08:00
eli cb631c83f1 docs(HANDOFF): netbird 证书交付定型为「SFTP 直传最终目录 + SSH 只 reload」
把之前来回改过的临时方案(incoming 中转目录、install -o root -g caddy / -o caddy -g 1000、
SSH 里做 chown/chmod)全部作废,记录最终形态与实测结果:

交付契约:/etc/caddy/certs/netbird = ubuntu:caddy 2770(setgid ⇒ 新文件自动继承 caddy 组),
fullchain/cert/ca.pem 644、key.pem 640;Caddy 走 group caddy 读,netbird proxy 容器
(镜像固定 uid/gid 1000)走 compose 的 group_add:[996] 读同一个组;其他账号读不到私钥。
自动化里不再有任何 install/chown/chmod —— SFTP 直传即可,SSH 只 systemctl reload caddy。

并记录三个真实踩到的坑:
1) sftp_filename_cert/ca 留空 ≠ 不上传,会回落到插件默认模板 {{name}}/cert.pem → 多出子目录;
2) 站点块域名必须被所配证书覆盖,否则 Caddy 自行发起 ACME 申请(泛域名只能 DNS-01,
   必然失败)——本次证书 SAN 改成 *.nb-pub.savorcare.com 后旧块触发 3 分钟 23 次重试、
   负载 4+,处置后降为 0;
3) 双栈目标下 ACME 身份的 from= 只按 IPv4 算,automation host 必须写 IPv4 字面量。
另记:GET /certificates/automation/<uuid> 成功也返回 {"result":"failed"}(插件已知 bug)。
2026-10-11 09:28:42 +08:00
eli a9cd46906c fix(xray): REALITY dest 写全 127.0.0.1:8003(裸端口在 v25.7.26+ 会变 localhost)
源码核对(Xray-core v26.10.10 + xtls/reality 8cdf7bf):v25.7.26 起只写端口的 dest
会被补成 localhost,可能先解析到 ::1。本机 Caddy 恰好同时监听 127.0.0.1:8003 与
[::1]:8003,所以一直没暴露;一旦回落后端只监听 IPv4 就会静默失联(配置合法、服务照跑、
但回落流量到不了)。改成显式 127.0.0.1:8003,并在渲染函数头注释里写明原因防止被改回去。

顺带把这一轮核到的 REALITY 回落/分流语义写进 HANDOFF §10:REALITY 层 dest 是单一
无条件目标(serverNames 只是认证白名单,不是路由表);按域名分流只能在
① 终结 TLS 的后端(我们现在就是 Caddy)或 ② dest 指向本机 SNI 预读路由器;
VLESS fallbacks 仅对「外层握手成功但 VLESS 头非法」的流量生效,name 支持最长子串匹配
(无通配符)。另记三个版本坑:MLKEM768 要求导致老客户端静默降级、fallbacks 与
VLESS Encryption 互斥、REALITY 下 fallbacks.alpn 匹配的是目标站的 ALPN。
2026-10-10 22:11:43 +08:00
eli 1736a8a0d4 docs(HANDOFF): netbird 证书交付契约——同时满足 Caddy 与 netbird-proxy 的 UID/GID
netbird 的 reverse-proxy 镜像以 netbird:netbird(UID/GID 1000) 运行且 distroless;
本机 ubuntu=1001 / opc=1000 / caddy=996。原来的 root:caddy 640 key 容器读不了
(实测 sudo -u '#1000' test -r 失败),容器一起来就会加载证书失败。

改成双身份方案:install -o caddy -g 1000 -m 644(fullchain)/640(key)
  * Caddy 走 owner(caddy 996)  ✓
  * 容器走 group(1000)        ✓
  * 其他普通用户(含 ubuntu)读不到私钥 ✓
属主/属组写进 OPNsense 自动化命令,换证时重新落成该状态(已重跑自动化实测验证)。

同时记录容器侧挂载与 env(/etc/caddy/certs/netbird:/wildcard-certs:ro、
NB_PROXY_CERTIFICATE_DIRECTORY/FILE/KEY_FILE,不要设 NB_PROXY_WILDCARD_CERT_DIR)
与取舍:group 1000 = 本机 opc 组,opc 账号能读私钥;更紧的替代是容器加
group_add: ["996"] 后把属组改回 caddy。
2026-10-10 16:48:15 +08:00
eli 7550a65c61 feat(migrate): 额外站点块直接装进站点目录——迁移后不再需要人工合并
配合 setup_caddy_xray_hysteria.sh 的 /etc/caddy/sites 扩展点:迁移抽出的非主域站点块
现在直接安装为 CADDY_SITES_DIR/migrated-extra-sites.caddy(干跑落 DRY_RUN_DIR/sites/),
升级后由新 Caddyfile 末尾的 import 自动加载,install/upgrade 永不覆盖该目录;
参考件仍留一份在迁移输出目录。

刻意保留两道「只留参考文件、不自动安装」的保护(装进去会让 caddy validate 失败、
反而挡住升级):① 块里含 dns <provider> 等定制插件指令(官方 apt caddy 不含该插件);
② 装了 caddy 时 `caddy adapt` 预检不通过(只解析适配,不做 TLS 签发)。
另外:缺 bind 127.0.0.1 的块会告警(443 回落流量到不了它)、目标文件已存在时先备份
.bak-<时间戳>、结束语按是否真装分别提示。

测试 +6(共 194):dns 插件块不安装、干净块安装且原文保留、重复迁移先备份、缺 bind 告警。
文档:README 迁移细则 + AGENTS 脚本 4 描述 + HANDOFF §7.2/§7.4。
2026-10-10 11:05:37 +08:00
eli 13637a1c9e docs(HANDOFF): netbird 落地结果 + OPNsense ACME 自动化可复用配方
- §7.5:证书已签发(CN=netbird.savorcare.com,SAN=*.netbird.savorcare.com,
  到期 2027-01-08),两条自动化跑通;站点块 /etc/caddy/sites/netbird.caddy 上线,
  公网 SNI 实测出示该证书、HTTP 502(等 netbird proxy 容器)
- 记录两个坑:① 证书 name 字段必须是域名(填标签会导致 lego 下单 400,且挑战 TXT
  根本不写,很像 DNS 问题)——本次真正的根因;② 插件身份公钥的 from= 只按 IPv4 计算,
  双栈目标会被 IPv6 连入拒绝,automation host 要写 IPv4 字面量
- 记录内网 split-horizon:公司内解析 *.netbird.* → docker1(192.168.254.51),
  与 VPS 的 502 表现不同,别误判
- 新增 §9:OPNsense ACME API 可复用配方(端点/字段/异步签发/已知返回体 bug)
  与凭据脱敏教训(dns_namecheap_api 曾被漏打印)
2026-10-10 10:53:53 +08:00
eli 084bba7226 fix(apt): Caddy 官方源 402 的窄容错——upgrade 不再在第一步就中止
背景(上游 issue caddyserver/dist#142):cloudsmith 给 caddy 项目的免费包投递配额被
CI 滥用打满,2026-10-09 起 /public/caddy/stable/deb/debian 下所有下载一律 402
Payment Required;2023/2024 各发生过,2024 那次约 8 天。后果是 apt-get update 必失败,
而脚本把它放在 system_prepare 第一步 + set -e ⇒ ACTION=upgrade 在任何机器上都跑不完
(哪怕只是想重渲染配置)。这与本次站点扩展点无关,是既有的机队级故障。

容错刻意做得窄(apt_update_tolerant):
  * 报错【全部】来自 caddy/cloudsmith 源 且 本机已装 caddy → WARN 后继续
    (配置照常渲染 → caddy validate → 重载服务,只是本次不升级 caddy 二进制)
  * 报错牵涉任何其他仓库,或本机还没有 caddy → 依旧硬失败(不吞真问题)
install_caddy 里 apt-get install caddy 失败也做同样判断。

测试:从脚本抽出该函数,在隔离环境用假 apt-get / 假 caddy 跑三种分支(继续/硬失败/
硬失败)。oraclejp 实跑 ACTION=upgrade 退出码 0:caddy 停 2.11.4、Xray 停 26.3.27,
配置与凭据指纹不变,用户站点块在重渲染后原样保留并被服务。

文档:README「Caddy 官方 apt 源不可用时」小节 + AGENTS 脚本 2 描述 + HANDOFF §7.6
(含 netbird 落地进度与证书 400 卡点)。
2026-10-10 10:48:10 +08:00
eli addd631e79 docs: 额外站点扩展点(README 章节 + 环境变量表 + AGENTS/HANDOFF)
- README:新增「额外站点(升级安全的扩展点)」章节:用法、升级安全语义、
  四条硬性约束(bind / alpn / 通配符必须喂证书 / 换证后 reload)与写错会挡住升级的联动
- AGENTS.md:脚本 2 描述补扩展点与两条约束
- HANDOFF:§2 补统一约定;§7 新增迁移脚本接入扩展点的待办 + netbird 公网入口
  (oraclejp)的定稿:专用 *.netbird.savorcare.com 证书(密钥最小化,不用公司通配符)、
  照搬 docker1/2 的 OPNsense automation(SFTP 上传 + SSH 安装并 reload caddy)
2026-10-10 09:59:26 +08:00
eli 796dcd722b docs: gcorhy 未决项了结(用户确认域名本身已废弃);标注 migrate 额外站点合并逻辑由另一会话负责 2026-10-10 09:44:06 +08:00
eli cba988f55c docs: 新增 docs/HANDOFF.md —— 旧 Claude 会话中的部署事实落库
机队清单(2026-10-09 实测)、各机差异、cdn-bypass 两层架构与 mark255 根因、
ECH 联调结论、Northflank 要点、凭据位置、未决项(EUserv 不可达 / gcorhy)、运维命令。
全文脱敏:不含 uuid/私钥/token。
2026-10-09 23:47:54 +08:00
eli 15d91b2b7a fix: drop XHTTP extra/xPadding block — breaks every link-imported client (400)
End-to-end matrix with real xray v26.3.27 + caddy:
- server with extra padding + plain client: 400 (direct AND via caddy)
- server without extra + plain client: 204 (direct AND via caddy, TLS h2)

Padding placement/key names must match on both ends, but share links cannot
carry these params and mainstream clients can't configure them — so a
server-side-only extra block rejects every real client. Removed from the
template along with XPADDING_HEADER/XPADDING_KEY.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 23:55:38 +08:00
eli 1a040cb54e fix: WS route stripped /scilad prefix (handle_path) — xray 404'd every handshake
Root-caused with live probes: WS upgrade to /scilad returned 404 on both
direct and CDN paths. handle_path strips the prefix before proxying, but
xray wsSettings.path=/scilad expects it intact. Replaced with a site-level
@websockets matcher (path + Upgrade headers) + path-preserving reverse_proxy
in the main block; deleted the redundant handle_path /scilad* in the CDN
block (it also shadowed reverse_proxy @wspaths into dead code).

Verified with real caddy + mock upstream: WS handshake gets 101 and the
upstream receives /scilad unstripped.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 23:27:59 +08:00
eli 3c58aec356 fix: disable Caddy auto_https redirects shadowing hy2 ACME challenge route
Caddy injects runtime auto-redirect routes (host-matched, ahead of the
catch-all http:// site, not excluding ACME challenge paths) for domains
it manages certs for — invisible in static 'caddy adapt' output, so the
previous live test could not reproduce it. hy2's tokens got 308'd to 443
(LE: 'Invalid response from https://DOMAIN/...: 404' via masquerade).

Verified bidirectionally with real caddy v2.11.4: without the option the
challenge path gets 308 (exact production repro), with it 200 from hy2.
Live test now extracts the global block + http block + a tls-internal
managed site block to reproduce production conditions permanently.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 20:27:05 +08:00
eli b540cfc558 docs: add 2026-08 protocol landscape review to design doc
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 13:36:19 +08:00
eli 70a19d126c feat: default hysteria2 cert to ACME HTTP-01 via Caddy port-80 proxy
hy2 certmagic HTTP-01 listens only on 127.0.0.1:9180 (altPort); Caddy's
port-80 block reverse-proxies /.well-known/acme-challenge/ to it. Non-wildcard
DOMAIN cert, auto-renewed and hot-applied, no DNS API token needed, no new
public ports. TLS-ALPN is unusable (CA only connects to TCP 443 = Xray) and
reusing Caddy's cert files needs permission/copy hooks, so HTTP-01 is the
minimal stable default. HY2_CERT_MODE=dns keeps Cloudflare DNS-01 wildcard
as fallback (CF_API_TOKEN required only in dns mode).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 12:57:49 +08:00
eli 50c0de2d60 docs: usage docs for caddy+xray+hysteria script, archive original templates
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 00:37:04 +08:00
eli a6d42ae602 docs: condense implementation plan (keep tasks + key info)
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 00:15:49 +08:00
eli afe4aa3756 Add optional CDN_DOMAIN second site block to design
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-23 23:47:29 +08:00
eli bbf5b50a68 Add design doc for caddy+xray+hysteria2 setup script
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-23 23:44:25 +08:00