eli
1d5af2248b
fix(firewall): forward 链改回"作用域白名单 + 默认丢弃"
...
之前把 forward 链清空成 policy accept 是错的——那不是标准,是相对基线的
**安全退化**。基线(init-iptables.sh)本来就是:
-A FORWARD -o podman+ -j ACCEPT
-A FORWARD -j REJECT
配合 net.ipv4.ip_forward=1,清空 forward 等于让主机变成转发器。
现在按基线语义实现:
chain forward {
type filter hook forward priority filter; policy drop;
ct state established,related accept
iifname/oifname "wt0" accept # NetBird 覆盖网
iifname/oifname "zt*" accept # ZeroTier 覆盖网
iifname/oifname "podman*" accept # Podman
iifname/oifname "docker0" accept
iifname/oifname "br-*" accept
}
两处必须显式列出接口的原因(都实测过):
1. **容器发布端口的入站在 forward 里没有任何现成规则可匹配**。实测
NETAVARK_FORWARD 只有 `saddr 10.89.0.0/24 accept`(出站)和
`daddr 10.89.0.0/24 established accept`(回程),**没有发布端口的入站
accept**。基线是靠 `-o podman+ ACCEPT` 放行的,这里同理用 oifname 匹配。
2. **NetBird/ZeroTier 会自己往 `ip filter FORWARD` 写规则**。实测 oraclejp
的 `ip filter FORWARD` 里有 netbird 加的 `iifname "wt0" accept`。本表的
`policy drop` 是终局裁决,不同步放行就会把 VPN 转发打死。
验证(oraclezyus-arm):SSH/80/443/容器 9000 全通、容器 DNS 与出站正常、
容器到宿主机 443 正常。测试 77 → 79 项全绿。
2026-11-11 12:24:44 +08:00
eli
38c2f1fd14
fix(firewall): 加 wt0 进模板、删掉无用的 NTP 规则
...
两项都是在 oraclezyus-arm 上单机验证得出的:
1. **加 `iifname "wt0" accept`**。NetBird 官方要求主机防火墙放行 wt0
("NetBird automatically adds rules to allow traffic on the wt0 interface")。
oraclejp/oraclezyus-arm 上原本是手工 `nft insert` 的,而本脚本每次
`delete table` + 重建,会把它洗掉——必须进模板。
2. **删 `udp sport 123`**。systemd-timesyncd 是纯客户端,实测轮询间隔 34 分钟、
conntrack UDP 超时 120 秒,每次轮询都是新建条目,回包走 established 即可。
实测去掉后 `NTPSynchronized` 仍为 yes。旧表里那条规则的计数器也是 0 命中。
顺带堵掉"源端口 123 可穿透所有 UDP 端口"这个口子。
测试 77 项全绿。
2026-11-11 00:11:25 +08:00
eli
a8b22b594c
fix(firewall): 按旧 iptables 实现精确复刻容器放行,并补回遗漏的 NTP
...
用户质疑"iifname podman* accept 到底合不合理、为何旧实现只放 53"——质疑成立,
我之前把整个网桥全放行(还有 docker0/br-*),是修自己弄坏的 DNS 时顺手放开的,
没有论证过。
实测查清了容器到底需要什么:
· 容器 DNS 走 aardvark-dns(网桥网关 :53) → 必须放
· oraclejp 上有容器正在连宿主机 10.89.1.1:443 → 也必须放
· 其余端口无一使用 → 不放
改为按端口放行(podman*/docker0/br-* 各 53 + 80/443),不再整网卡 accept。
容器要访问宿主机其它服务,显式用 --extra-tcp/udp 加。
同时补回旧实现有、新实现漏掉的 `udp sport 123`(NTP)。
关于"高端口 9993-65535":实测全队没有服务监听在该区间的公网地址上——
· xray 的 *:58xxx / *:54xxx 是 UNCONN 通配**出站**源端口,不是监听
· zerotier 的 23517/28019 等只绑内网地址
· 只有 tcp/9993(zerotier)和 udp/51820(WireGuard)需要入站,已由 extra 参数覆盖
所以旧的宽范围是在裸奔,不保留。
(注:我先前汇报过 tcp 58409/38034/43149 是监听,那是我 awk 把本地+对端地址
拼接产生的假端口,已更正。)
验证:5 台容器 DNS 各 3/3 成功、出站 301、9000 入站 200、zerotier 9993 通。
测试 77 项全绿。
2026-11-10 18:53:05 +08:00
eli
4889ac36f2
docs: 删除「rules.v6 由另一个 agent 管」这条过期约束
...
它写在 2026-08 OCI IPv6 迁移期(当时另一个会话在写 v4/v6 对称规则),
现在已经不成立——本项目只有一个维护者。留着它会把"要不要收敛两套防火墙"
这个正当决策挡死。
替换成当前的真实状态与收敛方向,并顺带记录今天实测到的冲突:
6 台(dmit + 5 台 Oracle)同时跑着 iptables-persistent 的 ip filter / ip6 filter
和本仓库的 inet dsh_firewall,两者都挂 input hook、必须都放行才通,
所以新表只能收紧不能放开(实测:新表放行的 tcp/111 仍被旧表 REJECT)。
同步改掉 init-firewall.sh 头部和 README 里同样的措辞。
2026-11-10 15:46:47 +08:00
eli
57b21d5b99
refactor(firewall): forward 链彻底不做过滤(回到 netavark 本来就能工作的状态)
...
之前连续两次修错方向,根因是没搞清 nftables 的多表裁决语义:
· netavark 自己会在 `ip filter` 的 NETAVARK_FORWARD 链里放行容器流量
(实测:`ip saddr 10.89.0.0/24 accept`,Docker 同理有 DOCKER-FORWARD);
· 但 nftables 的 `accept` **不是终局裁决**,包会继续流到下一张表的基础链;
· 本表在 forward 上写 reject → 把运行时已经放行的流量打死。
第一次修:加 `ct status dnat accept`(只解决外部访问发布端口,解决不了容器出站,
因为 SNAT 在 postrouting,ct status 不是 dnat)。
第二次修:再加一堆 `iifname podman*/docker0/br-*` accept。
两次都是"补规则",越补越脆弱,还踩到 nft 1.0.2 不支持通配接口名集合的坑。
正确做法是**这一条都别加**:宿主机防火墙的职责是保护本机服务(input),
转发流量本来就该由容器运行时和云安全组负责,本表不掺和。
这是社区标准结论(Red Hat KB 5885821、netavark#977:把 FORWARD 设 DROP 就会
让 Podman/Docker 连不上)。
改动:`chain forward` 只留 `type filter hook forward priority filter; policy accept;`,
删除之前的 dnat/网桥规则。黄金基线与回归测试同步改为"forward 链内不许出现 reject/drop"。
验证:容器内 DNS 解析正常、出站 301、5 台 9000/api/health 全 200、forward 链 0 条拒绝规则。
测试 77 项全绿。
2026-11-10 13:06:25 +08:00
eli
bff47139d7
fix(firewall): 补容器 DNS/出站放行、修 --local --apply 的静默失败
...
三处,都是 iptables→nftables 迁移时丢的语义或本地分支与远端分支不一致:
1. forward 链只放行了 `ct status dnat`(外部连容器发布端口),漏了容器**主动出站**。
SNAT 发生在 postrouting(forward 之后),这类包的 ct status 不是 dnat,于是容器
连外网全被 reject。实测容器内 curl 1.1.1.1 直接 Couldn't connect。
补 `iifname podman*/docker0/br-* accept`。
2. input 链没放行容器网桥,容器内的 DNS(aardvark-dns 监听在网桥网关 :53)和
host-gateway 访问全部被拦。实测容器内域名解析全失败。
同样补网桥放行,另加 `ct state invalid drop`(乱序/孤儿包不该回 RST 给反射用)。
3. `--local --apply` 分支没有 set -e,且 `rm -f; exit $?` 把退出码冲成 0 ——
nft 加载失败也会照样把坏规则持久化并返回成功,下次开机整份加载失败等于裸奔。
改成内部 set -euo pipefail、失败即退、`rc=$?` 保留退出码、落盘用 install -m 644。
另外踩到一个 nft 1.0.2 的坑:`iifname { "podman*", ... }` 会报
"Byteorder mismatch: expected big endian, got host endian",必须拆成多条独立规则。
正是新加的 nft -c 预检在 gcoregon 上拦住了它,否则会把坏规则推上去。
验证:容器内 DNS 解析成功、出站 301、5 台 9000/api/health 全部 200。
测试 77 项全绿。
2026-11-10 11:48:48 +08:00
eli
328a41fda3
fix(firewall): forward 链放行 DNAT,否则容器发布端口全被挡成 refused
...
从 iptables 版迁移到 nftables 版时丢了"容器转发先放行"的语义:
· init-iptables.sh 把 reject 追加在 FORWARD 链**尾部**,Docker/Podman 会把自己的链
插在最前面,容器流量天然先被接受;
· nftables 版写成 `ct state established,related accept` + `reject`,而 Podman 的
端口发布靠 NAT 的 DNAT 改写目标地址,改写后照样要过 forward 链 → 撞 reject。
症状很坑:宿主机自己 curl 127.0.0.1 正常(走 lo),外部一律 **connection refused**。
不是超时——本表用 reject 而不是 drop,所以看着像"端口根本没开",很容易往错方向查。
暴露方式:昨天把规则推到 5 台 Oracle,把这些机器上 Podman 发布的 9000(Arcane agent
的 API)打掉了,表现是 Arcane 面板里这批 VPS 全部离线。
修法:在 forward 链的 reject 之前加 `ct status dnat accept`——只放行被本机 NAT 改写过的
包,其余转发仍然拒绝。黄金基线 dsh-firewall.nft 同步更新。
补了两条回归测试:forward 链必须有该规则,且必须排在 reject 之前。
测试 75 → 77 项。
2026-11-10 11:24:37 +08:00
eli
3b19271b6a
feat(rotate): hy2 的 CF 令牌轮换脚本(含一次真实事故的教训)
...
只换 hysteria 那一路:/etc/hysteria/config.yaml 与 setup.env 里的令牌,重启
hysteria-server。明确不碰 Caddy 的 Caddyfile、docker acme-companion 的 CF_Token、
路由器的 ddns、以及历史备份——它们各自可能用别的令牌,用途拆分前不动。
第一版在这里翻过车,10 台 hysteria 全挂约 4 分钟:
远端脚本走 stdin 喂给 `bash -s`,脚本里又用 `read` 从 stdin 读令牌,两者抢同一个
stdin,read 读到了脚本自己的下一行,把源码片段写进了 YAML,解析失败服务起不来。
现在改成:远端脚本先 scp 落地成文件,stdin 只留给令牌。
另外补了两道保险:
· 重启前先 yaml.safe_load 校验,坏了不重启
· 备份之后任何一步失败(校验/重启/active/内容核对)都自动回滚回备份再重启
令牌经 stdin 传输,不出现在任何一端的 argv 里。改完是幂等的:再跑一次只会说"已是新令牌"。
2026-11-10 10:42:45 +08:00
eli
6982948324
docs(readme): 补 init-firewall.sh 章节(之前脚本在、README 里完全没提)
...
README 的索引和选型表里都没有它,等于新人只知道有四个脚本。补上:
- profile 表 + 端口开关清单
- 部署到新机器的最小步骤,以及脚本内置的 SSH 自检说明
- **多机差异化怎么判断**:只看 `ss` 里 0.0.0.0 / * / [::] 监听什么,
绑 127.0.0.1 的一律不管。把当前所有对外端口全放进白名单 = 功能零变更,
这是上生产最稳的推进方式(先做到行为不变只加纵深,再考虑收窄)
- 两个坑:会自选端口的服务(netbird)重启后可能换端口被挡;
`ss -uln` 里的随机高端口多半是出站 socket,别往白名单里加
以及不变式重申:只 nft -f 自己的文件,永不跑整份 /etc/nftables.conf,
不碰 rules.v6,独立表不动别人的表。
2026-11-08 20:40:25 +08:00
eli
dea51c546a
chore(cdn-v6): 文件头补上 Akamai 来源仓库地址(测试断言要求写明来源)
2026-11-08 13:13:39 +08:00
eli
ca5d8ee84a
refactor: CDN 清单生成器移交 gc-toolkit/cdn-list
...
这份清单存在的唯一原因是 GCP 把 CDN 出站单独计价(Carrier Peering,$0.08/GiB 从
第一字节起算、无 200 GiB 免费额度),而消费它的 VPC 拒绝规则 cdn-egress-deny 本身
也是 GCP 资源。所以生成器归 gc-toolkit 更合理。
- 删掉本仓库的 tools/refresh-cdn-lists.sh(功能已合并进 gc-toolkit,避免两份分叉)
- 脚本头部与 docs/cdn-list-audit.md 改成指路 gc-toolkit
- 清单内容本次无变化:v4 271 条 / v6 193 条,与 gc-toolkit 生成的 canonical 文件一致
迁移后 gc-toolkit 那边多了两件事:生成 canonical 的 cdn-cidrs-v4/v6.txt,
以及 --vpsetup 参数(一次运行同时同步本仓库的内嵌清单与 v6 文件)。
2026-11-08 13:12:01 +08:00
eli
8fd7d9db1b
feat(tools): 新增 refresh-cdn-lists.sh,把清单刷新变成一条命令
...
之前刷清单是我临时写内联 python 干的,没脚本、不可复现。现在补上:
- 四个来源:CF ips-v4/v6、Fastly public-ip-list、Akamai 的
akamai_ip_cidr_blocks.lst(v4 已合并版)+ akamai_ipv6_list.lst(v6 未合并,自己 collapse)
- **交叉校验**:把 Akamai 原始 9462 行自己 collapse 一遍,与仓库给的合并版逐条比对,
不一致就拒绝落盘(说明上游出了问题)。实测两者都是 235 条、集合完全相同。
- **确定性**:排序固定,连跑两次字节级一致(已验证 v4 脚本与 v6 文件 diff 都为空)。
- **幂等**:无差异时直接报"已是最新"退出,不碰文件。
- 默认只报告差异(`<` 当前独有 / `>` 上游独有);`--apply` 才落盘;`--cache DIR` 供离线/测试。
首次运行就抓到上游漂移:Fastly 从 19 条涨到 **21** 条,新增 90.98.0.0/17 与
90.98.128.0/18(相邻不重叠,净增 2 条 / 49,152 个地址)——这两段之前一直没被绕行。
已落盘,v4 269 → 271。
修掉一个自己引入的 bug:v6 文件的注释拼接逻辑写重了,第一次 --apply 把 193 条写成了
385 条(每条重复两次)。现在 v6 改成整文件重新生成(头和正文同源),并验证无重复。
线上已同步:两台主机 + 两个 GCP 项目的 cdn-egress-deny 规则都是 271 条,
两侧集合逐条比对完全一致。
2026-11-08 11:31:52 +08:00
eli
37db75413d
fix(cdn-list): 换用上游仓库自带的合并版,并补上一直缺失的 Akamai IPv6
...
两件事,都是之前搞错的。
一、v4 清单的源头找错了处理方式
实际数据源是 SecOps-Institute/Akamai-ASN-and-IPs-List(按 40 个 ASN 从 RADb 拉 BGP
宣告,每小时更新)。内嵌的那份是拿它的 akamai_ip_list.lst 手工合并、且是一两年前的
快照。复核发现:
- 该仓库本来就提供合并好的 akamai_ip_cidr_blocks.lst(235 条,与原始 9462 行
collapse 后完全等价),根本不用自己合;
- 旧快照已漂移:265 段 / 209,106 地址 Akamai 已不再宣告(白绕),
49 段 / 13,504 地址是新宣告的(没绕 → 一直在按 $0.08/GiB 计费)。
换成 235 条后,v4 总数 402 → 269。
二、Akamai 的 IPv6 一直是空的
之前写"没有官方来源所以故意不收"——前提就错了:同一个仓库有 akamai_ipv6_list.lst
(3849 行,未合并,需要自己 collapse 成 183 条)。也就是说在此之前所有走 v6 的
Akamai 边缘流量都没被绕行,一直在按 CDN 计费。现已补上:v6 从 10 条 → 193 条
(CF 7 + Fastly 3 + Akamai 183)。
配套改动:
- 脚本头部写清【怎么更新这份清单】+ CDN_LIST_AUDIT_DATE,超过 180 天打告警;
- 测试断言从"单一区间"改成三层:总数 269±10%、CF 15 条逐条核对、Fastly 抽样;
v6 侧补 Akamai 主段与条数下限,防止再次被删空;
- docs/cdn-list-audit.md 重写,记录依据(GCP 按目的地网络自动判定、Akamai 无官方
公开清单的穷举证据)、20 万地址漂移、以及 5 万条集合的实测无差异。
线上已同步:两台主机渲染结果 md5 一致(c8f16ac8…);两个 GCP 项目的 egress 拒绝规则
都改成同一份 269 段(原来是 gcoregon 旧 368 段、gcoregon-2 中间版 402 段,两边还不一致)。
真实 Akamai 边缘地址抽验命中:www.akamai.com / www.microsoft.com 的 AAAA 都落在
2600:1400::/24 内。
2026-11-08 11:17:55 +08:00
eli
bb27fb24dc
feat(cdn-list): v4 清单 188 → 402 条,覆盖面上补到 GCP 那份的规模
...
现网 188 条(CF 15 + Fastly 19 + Akamai/Linode 154)是 2026-09 审计的产物;
GCP 项目里的 egress 拒绝规则用的是更完整的一份(Akamai 368 条)。这次把主机侧
对齐到同一份,两边从此一致——不一致会导致"漏绕(按 CDN 计费)"或"误丢包"。
实测结论(写进 docs/cdn-list-audit.md):
- nft 是"一个集合一次查找",netlink 字节码里只有一条 lookup,不是几百条 cmp;
- 集合 188 条 vs 40 条端到端无差异,扩到 50,188 条仍然测不出差异;
- 所以"精简到几十条"没有任何性能收益,只会把无辜地址丢给上游 Oracle。
顺带证伪了两个之前的口头推测:
- GCP 单条防火墙规则没有 256 个 destinationRanges 上限(实测 3000 个也能建);
- 精确挖掉 Linode 反而使条数从 368 涨到 381(减法切碎片),故不挖。
测试断言从单一区间 [180,200] 换成三层保护:总数 402±10%、Cloudflare 15 条逐条核对、
Fastly 抽样核对——CF/Fastly 是官方权威值,不该漂移;Akamai 是 BGP 派生的浮动值。
两台机器已重跑并验证:CDN 出口走 Oracle,非 CDN 仍走 GCP 自己。
2026-11-08 10:38:45 +08:00
eli
5420ea95e3
feat(firewall): 主机防火墙收进本仓库,全机队共用
...
init-firewall.sh 之前落在 oci-toolkit 里,但它是"给任何一台代理节点上防火墙",
跟 OCI 没关系 —— GCP、阿里云、搬瓦工、Oracle 都要用同一份。挪到 vpsetup,
和 setup_*/migrate_* 放一起,按机器角色选 profile:
proxy(默认) / hy2 / web / minimal
端口全走开关(--ssh-port/--hy2-port/--hy2-hop/--no-hy2-hop/--no-http3/
--extra-tcp/--extra-udp/--enable-frps/--enable-xmpp)。默认 profile 生成的
规则与现网 dsh-firewall.nft 逐条一致,老机器重跑是纯 no-op。
测试 tests/run_firewall_tests.sh 75 项,钉死:默认与现网一致、四个 profile 的
端口集合、任何组合都保留链尾 REJECT 兜底、非法输入 exit 2 且不产出规则。
OCI 那边同时删掉,避免两份分叉。
2026-11-08 00:48:17 +08:00
eli
b1ab48afd3
docs(cdn-v6): 把 Akamai 段的空缺从 TODO 改成有依据的结论
...
上一版留的是"待从官方文档/WHOIS 核对后补入"。调研后发现这个待办本身不成立:
Akamai 根本没有面向公众的单文件网段清单,官方只给客户 Site Shield / Origin IP ACL
(按客户定制),第三方仓库 SecOps-Institute/Akamai-ASN-and-IPs-List 里还混着
Akamai Connected Cloud / Linode(AS63949)的客户虚拟机段,拿来当 CDN 名单会误绕一大片
普通 Linode 站点。
按仓库既有的"CDN 清单只收官方公布的网段"约束,结论是【不收】,并把理由写进文件。
对应测试从"必须含 TODO"改成"必须含留空理由 + 必须含不采用第三方的说明",防止以后
有人顺手补段却不留依据。
2026-11-07 21:05:58 +08:00
eli
b4a3640536
docs(guard): 记录 2026-11-07 用 GCP Monitoring 做的实测校准
...
拿 compute.googleapis.com/instance/network/sent_bytes_count 跟本站 tx_bytes 增量法在同一
时间窗口对账:4,096,497,518 vs 4,137,694,034 bytes,差不到 1%。结论是计量手段本身够准,
偏差主要来自 set-used 的初始估计,不是方法问题。
顺手把"怎么查真实用量"写进头注释(gcloud 没有 time-series 子命令,得打 Monitoring v3
REST API 并自己把 delta 点求和),下次校准不用重新摸索。同时标注该指标比计费口径宽,
是上界。
gcoregon 的 set-used 已按实测从 12 GiB 校正到 9 GiB。
2026-11-07 21:04:16 +08:00
eli
15690fd9a6
fix(cdn-bypass): 改为独立实例,修掉 confdir 合并污染主配置
...
原方案给主 xray 加 -confdir,被 xray 的合并语义坑了两处(真机 -dump 实证):
1) 片段 outbounds 被前插成 outbounds[0],cdn-bypass-out 变成所有未命中流量的默认出站
2) Config.Override 用 c.RouterConfig = o.RouterConfig,片段的 routing 整体替换主配置的
routing —— 5 条规则与 IPIfNonMatch 全丢(geoip:cn/私网/bittorrent/tg-in 全部失效)
单进程无解:带 routing 就毁主配置,去掉 routing 则 bypass 入站落到 direct 并打环。
改为独立 xray 实例(同一个 /usr/local/bin/xray,不重复安装):
- xray-cdn-bypass.service:User/Group 继承主 unit,CAP_NET_ADMIN 供 tproxy+SO_MARK,
PartOf 传播 stop/restart,WantedBy 同时挂主服务(PartOf 不传播 start,
否则看门狗次月恢复后 bypass 仍 dead,而 nft 还在打标 → CDN 流量黑洞)
- 多上游 balancer:oracle-1..N 随机选健康节点(random + observatory;
fallbackTag 必须非空,否则 strategy 不会注入 observatory)
- 双栈:table ip / table ip6 各一套打标+tproxy,v6 tproxy 目标写死 [::1]
- 卸载顺序:先撤 nft/策略路由再停实例,避免仍在打标+端口已关
已修的真机问题:mktemp 无 .json 后缀导致 xray 拒绝加载配置;
route unit 是 oneshot+RemainAfterExit,只 enable 不会重跑 ExecStart,必须 restart;
运行时 systemctl enable --now nftables 会触发 flush ruleset 清空主机防火墙,改为只 enable。
新增 cdn-cidrs-v6.txt(只收官方段,Akamai v6 待补)与 6 条回归守卫(已做变异测试)。
2026-11-07 20:53:47 +08:00
eli
3d35d0381c
feat(guard): 新增月度出站流量看门狗
...
为 GCP Standard Tier 每月 200 GiB 免费出站额度设计的守护脚本:
- traffic-guard.sh:统计非 lo、UP 网卡的 tx_bytes,达阈值后停代理服务(绝不关机),
次月自动恢复且只恢复它自己停过的服务;status / set-used / resume 子命令
- setup_traffic_guard.sh:安装为 oneshot service + 5min timer,配置写 /etc/traffic-guard.env
- 手动执行时也读 /etc/traffic-guard.env(sed 精确取 KEY="value",不 source)
默认阈值 185 GiB(留 15 GiB 余量),账期按 America/Los_Angeles 自然月。
2026-11-07 20:53:39 +08:00
eli
0cc1d093b9
docs: 替换 AGENTS/README 真实域名/IP,改用代号与占位符;更新测试断言与夹具
2026-11-04 19:07:29 +08:00
eli
c3d06b2273
docs: cdn-bypass networkd drop-in 三处文档同步;HANDOFF §13 记录 10-16 事故与 10-30 修复实测;README 287→188 段订正;顺带落库 §12(10-24 ZeroTier 恢复,此前漏提交)
2026-10-30 20:38:45 +08:00
eli
a7f63052a2
fix(cdn-bypass): networkd drop-in 防 fwmark 策略路由被清——2026-10-16 gcoregon 静默失效根治
...
- 安装时写 /etc/systemd/networkd.conf.d/90-cdn-bypass.conf(ManageForeignRoutingPolicyRules=no),
卸载时移除;落盘即对下一次 networkd 重启生效,装时不动 networkd。
- 根因:unattended-upgrade 升级 systemd 触发 networkd 重启,默认 ManageForeignRoutingPolicyRules=yes
会把 route unit 加的 ip rule fwmark 1 当外源规则清掉,oneshot unit 仍 active (exited) 无人察觉。
- 已在 gcoregon 实测:写 drop-in 后重启 networkd,规则存活、CDN 出口恢复为 alius。
- 测试断言同步;顺带消除本脚本链接解析处的 SC2015。
2026-10-30 20:38:45 +08:00
eli
f0b4c1a984
style: vlessenc 解析处 A&&B||C 改 if(SC2015)——本地 shellcheck 0.8.0 默认 severity 闸门恢复全绿
2026-10-30 20:38:45 +08:00
eli
4a88b3a4ac
AGENTS: point workspace to /home/eli/cloud/vpsetup
2026-10-25 10:20:36 +08:00
eli
817238206e
Never start nftables at runtime: enable --now flushes the host firewall
...
On a fresh Ubuntu 24.04, systemctl enable --now nftables starts the unit,
which runs the whole /etc/nftables.conf including flush ruleset, wiping
every iptables-persistent INPUT/FORWARD rule until the next reboot.
Enable only (boot ordering nftables@sysinit -> netfilter-persistent@
multi-user keeps the flush harmless); the hysteria table is still loaded
individually via nft -f below.
2026-10-25 10:20:36 +08:00
eli
21afa5bfae
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
3f6e1cff21
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
4d7415e803
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
cd26ae505f
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
00e05c0c71
feat(caddy): 额外站点目录扩展点——升级安全的 import,手加站点不再被 upgrade 覆盖
...
主 Caddyfile 由脚本整体重写,手工加的站点块必丢(迁移脚本也只能提示人工合并)。
新增 CADDY_SITES_DIR(默认 /etc/caddy/sites):脚本只在末尾渲染
`import $CADDY_SITES_DIR/*.caddy`,对该目录只读——只建目录 + 目录全空时补一个
纯注释的 00-example.caddy,永不覆盖/删除用户文件(uninstall 也不动)。
用 glob 而非字面路径是刻意的:Caddy 对空匹配的 glob 只警告不报错(v2.0→v2.11
一直如此),字面路径不存在才是致命错误,所以目录空着不会挡住升级。
体检(仅告警不阻断):站点块没写 bind 127.0.0.1 时提示——本 Caddy 只在
127.0.0.1:8003 收 Xray REALITY 回落流量(PROXY protocol + tls 包装),缺 bind 的块
会落到另一个未包装的 :8003 监听器上,443 流量永远到不了它(静默不可达)。
测试:import 行渲染、示例纯注释、用户文件不被覆盖、目录非空不补示例、缺 bind 告警、
CADDY_SITES_DIR 覆盖 + 落 setup.env + 拒绝相对路径。
2026-10-10 09:59:19 +08:00
eli
8c8cb05e0a
docs: 项目指引迁移 CLAUDE.md → AGENTS.md(DSH 原生),补访问地图与 standing constraints
...
- AGENTS.md 为唯一真源:新增「主控状态与访问」(2026-10-09 实测机队表)、
「Standing constraints」(rules.v6 归属、凭据纪律、改动闸门、nft 不变式、CDN 清单只收官方段)
- 修正 Commands:syntax/lint 补上 setup_cdn_bypass_tproxy.sh
- 修正 Conventions:origin 是私有 git 服务器,不再写「GitHub web UI」
- CLAUDE.md 保留为指向 AGENTS.md 的短指针(Claude Code 兼容)
2026-10-09 23:47:54 +08:00
eli
9722fa1825
docs: cdn-bypass 注释/CLAUDE.md 对齐新架构(客户端流量同样经 nft 筛选;direct 出站带 255 的典型症状;188 官方 CIDR;drop-in 20- 前缀)
...
Co-Authored-By: Claude Code <noreply@anthropic.com >
2026-09-28 13:10:11 +08:00
eli
06d26b252b
fix: cdn-bypass drop-in 改名 20- 前缀——官方 xray 安装脚本自带 10-donot_touch_single_conf.conf 会在同前缀 ASCII 序后加载盖掉我们的 -confdir;卸载同时清 10-/20- 两种残留
...
Co-Authored-By: Claude Code <noreply@anthropic.com >
2026-09-28 13:04:50 +08:00
eli
582f12efaa
fix: systemd drop-in ExecStart 从新版 {path=...;argv[]=...} 形式正确抽出 argv[]
...
新版 systemd systemctl show -p ExecStart --value 输出复合形式,
先前直接把整串写进 drop-in ExecStart= 会被 systemd 拒绝
('argv[]=' 不是合法可执行名),xray 用旧 ExecStart 起来没挂 confdir,
dokodemo 永远不起。
drop-in 现在从 argv[]= 提取纯命令行再附加 -confdir。
Co-Authored-By: Claude Code <noreply@anthropic.com >
2026-09-28 12:51:23 +08:00
eli
e23f5a7c1a
fix: migrate 脚本 xray 解析器补上 # 剥注释(gcoregon 配置含 # 注释无法解析)
...
剥注释状态机只处理 // 和 /* */,但 gcoregon 历史配置混有 # 风格注释
(如 #"sniffing": { ... #}),json.loads 直接报错迁移失败。
加一行状态机分支:遇 # 在非字符串态也跳到行尾。
Co-Authored-By: Claude Code <noreply@anthropic.com >
2026-09-28 11:55:21 +08:00
eli
960ca8c3f2
refactor: 端口过滤下沉到 nft 层(xray 不再做端口筛选),架构对齐 nft 全筛原则
...
- nft mangle 打标链加 `th dport { 53, 80, 443 }`:非 web/DNS 端口的 CDN 流量
根本不进 tproxy,直接直连。省 upstream 流量的同时 QuIC(udp/443) 自动覆盖。
- xray routing 删 port 过滤:交给 nft 预筛,不再有重复筛分层。
- 测试断言同步反向:xray 不应再写 "port": "53,80,443"。
设计原则:get/post 只在一个地方 —— nft 决定哪些包进 tproxy;xray 只决定
「进来的包走哪个 upstream」。两套筛选条件不重叠就不会出现「nft 抓了 xray
不认」或反过来。
Co-Authored-By: Claude Code <noreply@anthropic.com >
2026-09-28 11:50:01 +08:00
eli
2c13ffc03d
fix: cdn-bypass 清单只保留三家公司官方段(401→188,剔除 213 条来源不明的 LE 回源 IP)
...
2026-09 期间治理 LE 证书续期时,把 LE 验证回源节点的 GCP/Linode IP 误当 CDN 段
加进清单;经 Whois+CF/Fastly/Akamai 官方清单逐条审计后:
- CF 15 条(cloudflare.com/ips-v4)
- Fastly 19 条(api.fastly.com/public-ip-list)
- Akamai/Linode 154 条(AS20940/AS16625/AS63949/AS36183)
共 188 条;剔掉 NTT/Level3/CenturyLink/Verizon/GTT/IBM Cloud/Zayo/AAPT/
CAT Telecom/Softbank/HE/Microsoft/Cologix/PCCW/TPG/IDNIC/Reliance/Telstra/
Telefonica/e-Trade/Fortinet 等 213 条非 CDN 段。
测试断言放宽到 [180,200],防止再次膨胀。
Co-Authored-By: Claude Code <noreply@anthropic.com >
2026-09-28 11:34:52 +08:00
eli
2c7c3d5566
test: cdn-bypass 脚本 DRY_RUN 断言(routing 端口过滤 / mark255 / 双 nft 表 / CIDR≥400)
...
Co-Authored-By: Claude Code <noreply@anthropic.com >
2026-09-28 08:01:56 +08:00
eli
09a5f6566a
fix: cdn-bypass 与 gcoregon 实战清单合并(287→401) + routing 端口过滤
...
- CDN CIDR 快照 stale:2026-09 扩到 400 条时没回灌脚本,补入 114 条缺失段
(103.x/122.252/128.241/139.177/157.238/165.254 等 Akamai),只多 1 条 /32
- routing 片段 catch-all → network tcp,udp + port 53,80,443:
nft 已按 CDN 网段预筛,xray 只补 dokodemo 入站的端口层过滤,
非 web/DNS 端口直接直连以省上游流量(上游常为计费/限流节点)
- 头注释更新:说明两层的分工、客户端入站不在本脚本范围
Co-Authored-By: Claude Code <noreply@anthropic.com >
2026-09-28 08:00:56 +08:00
eli
5916d1e1e1
fix: northflank 模板容器端口 443->8443(对齐实测:public 端口是 8443,443 是 private 上已移除的死 REALITY)
...
Co-Authored-By: Claude Code <noreply@anthropic.com >
2026-09-26 16:47:47 +08:00
eli
0bd50a3157
docs: northflank/ 部署模板(XHTTP 最优配置 + hy2 + healthcheck 指南)
...
- xray-config.template.json:边缘终结 TLS 形态(security none / listen 0.0.0.0),
XHTTP mode auto + extra xPadding(与链接 extra= 一致),routing/dns 同 VPS 模板
- hysteria-config.template.yaml:DNS-01 或自签两方案(边缘独占 80/443,HTTP-01 不可用),
公网可达需 UDP L4 LB
- README:协议可行性结论(REALITY/hy2 需付费 L4 LB,XHTTP 唯一免费路径)、
客户端链接模板、healthcheck 须写在 template 服务节点 spec(service 级会被覆盖)、
TCP 探针优于 HTTP(未认证 GET 返回 404)
- 修正 render_xray_config 上方过时注释:extra 两端一致即可用(f0ea7bc 起链接内嵌),
原注释仍停留在 48e6bd4 的结论
Co-Authored-By: Claude Code <noreply@anthropic.com >
2026-09-26 15:48:28 +08:00
eli
1c7880609a
feat: 迁移脚本 migrate_existing_stack.sh + WS_PATH 参数化 + ACTION=links
...
- migrate_existing_stack.sh:手工部署的 caddy/xray/hysteria2 栈一键迁入脚本管理
- 只读解析三份旧配置(python3 注释感知花括号扫描/注释剥离+json/awk YAML)
- 交互确认 DOMAIN/EMAIL/WS_PATH,凭据脱敏清单确认后落盘
- 写 setup.env(%q 格式 mode 600,保留旧 uuid/私钥/shortId/密码,同协议旧客户端不断线)
- 备份旧配置到 migration-backup-<ts>/;非主域站点块 verbatim 抽取提示人工合并
- nft 只体检不修改:脏表生成参考配置,绝不插入 include
- REALITY 公钥用 xray x25519 -i 现推;HY2_CERT_MODE 按旧 acme 配置推断
- setup_caddy_xray_hysteria.sh:
- WS_PATH 参数化(默认 /scilad,迁移可保留 /vlessws 等旧路径,进 RENDER_VARS)
- 新增 ACTION=links:只读 setup.env 重打全部分享链接,不碰系统
- 环境变量优先于 setup.env:sudo CDN_DOMAIN=x ACTION=upgrade 带参升级真正生效
- 测试 167 条断言全绿(fixtures 已脱敏);README/CLAUDE.md 同步
- 已在 oraclejp/oraclejp-2 两机实测:迁移+升级+CDN 端到端验证通过
Co-Authored-By: Claude Code <noreply@anthropic.com >
2026-09-26 15:31:41 +08:00
eli
4740f67ddc
chore: gitignore 加入 .env(Northflank API token 等本地机密,不入库)
...
Co-Authored-By: Claude Code <noreply@anthropic.com >
2026-09-26 15:31:15 +08:00
eli
4fe71e323d
docs: README 重构——索引、选型总览、mermaid 架构图、新增 cdn-bypass 章节
...
CLAUDE.md 同步为三脚本并补 nft 运行时 invariant。
Co-Authored-By: Claude Code <noreply@anthropic.com >
2026-09-26 12:25:30 +08:00
eli
826014e3b8
feat: 新增 setup_cdn_bypass_tproxy.sh —— CDN 绕行透明代理
...
面向 GCP 等封锁/计费 CDN(Cloudflare/Fastly/Akamai)出口的 VPS:本机到
287 段 CDN 网段的流量经 nft 打标 → ip rule → lo → xray dokodemo tproxy →
上游不限流节点。粘贴 vless:// 链接即用;xray 配置经 confdir 注入不碰原
config.json;ip rule/route 由 oneshot service 持久化;出站 mark 255 防回环。
Co-Authored-By: Claude Code <noreply@anthropic.com >
2026-09-26 12:25:30 +08:00
eli
49b389c9cb
fix: nftables 运行时只加载自己的表,不再执行整份 nftables.conf
...
Oracle/Debian 云镜像启用 iptables-persistent 时,conf 首行 flush ruleset 会
无差别清空主机防火墙(含 SSH 放行)与 Docker NAT 表,而 persistent 只在开机
补回——运行时执行等于裸奔到重启。改为只 nft -f hysteria.nft(先删后加保证
幂等);开机顺序由 systemd 硬依赖链保证(nftables@sysinit → persistent@
multi-user),flush 无害。卸载路径同步改为只删自己的表,不再 reload nftables。
Co-Authored-By: Claude Code <noreply@anthropic.com >
2026-09-26 12:25:30 +08:00
eli
e786b38624
fix: ECH 默认 DNS 8.8.8.8 -> 9.9.9.9(Quad9,实测不被屏蔽;8.8.8.8 偶发断)
...
Co-Authored-By: Claude <noreply@anthropic.com >
2026-08-25 15:25:23 +08:00
eli
b43d56edb2
fix: ECH 默认查询改用 udp://8.8.8.8(明文 UDP53,墙内实测最稳)
...
8.8.8.8:443 DoH 墙内不稳(疑似被 passwall 本机代理劫进代理通道);
源码只有 udp:///https:///h2c:// 三个分支,tcp:// 不存在。
full 策略下 UDP 投毒只会断连不会静默降级,风险可接受。
Co-Authored-By: Claude <noreply@anthropic.com >
2026-08-25 13:13:23 +08:00
eli
f97bb8e133
fix: ECH default DoH -> 8.8.8.8 IP 字面量(实测连通;免 dns.google 域名二次解析)
...
Co-Authored-By: Claude <noreply@anthropic.com >
2026-08-25 12:30:54 +08:00
eli
3b45fda3c1
fix: ECH default DoH endpoint cloudflare-dns.com -> dns.google
...
Xray ECH DoH 是 RFC8484 wireformat(POST application/dns-message 走 h2),
/resolve 是 JSON 调试接口不认。cloudflare-dns.com/1.1.1.1 墙内实测被封,
dns.google:443 可达。备选 alidns / IP 字面量形式见 README 与 ECH_CONFIG。
Co-Authored-By: Claude <noreply@anthropic.com >
2026-08-25 12:24:43 +08:00