fix: 限制边缘节点仅使用 IPv4 并记录维护约定
This commit is contained in:
@@ -5,14 +5,13 @@ whatever `ACME_DNS_PLUGIN` you set — acme.sh supports Cloudflare, DNSPod, Aliy
|
|||||||
GoDaddy, and dozens more. That part is provider-agnostic.
|
GoDaddy, and dozens more. That part is provider-agnostic.
|
||||||
|
|
||||||
The only thing that is *not* automated for non-Cloudflare providers is creating the host
|
The only thing that is *not* automated for non-Cloudflare providers is creating the host
|
||||||
**A/AAAA records**. That's just two names. Create them in your DNS provider's console/API:
|
**A records**. The current fleet is IPv4-only: keep `SERVER_IPV6` empty and do not create
|
||||||
|
AAAA records. Create these two names in your DNS provider's console/API:
|
||||||
|
|
||||||
| Name | Type | Value | Proxy/CDN |
|
| Name | Type | Value | Proxy/CDN |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `<DOMAIN_ROOT>` (e.g. `nodeA.example.com`) | A | `SERVER_IP` | **off** (DNS-only / grey cloud) |
|
| `<DOMAIN_ROOT>` (e.g. `nodeA.example.com`) | A | `SERVER_IP` | **off** (DNS-only / grey cloud) |
|
||||||
| `*.<DOMAIN_ROOT>` | A | `SERVER_IP` | **off** |
|
| `*.<DOMAIN_ROOT>` | A | `SERVER_IP` | **off** |
|
||||||
| `<DOMAIN_ROOT>` | AAAA | `SERVER_IPV6` | off (skip if no IPv6) |
|
|
||||||
| `*.<DOMAIN_ROOT>` | AAAA | `SERVER_IPV6` | off |
|
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- Keep them **unproxied** — the node terminates its own TLS and needs the real client IP path; a proxy/CDN in front would break the WS cover and the panel anti-scan logic.
|
- Keep them **unproxied** — the node terminates its own TLS and needs the real client IP path; a proxy/CDN in front would break the WS cover and the panel anti-scan logic.
|
||||||
@@ -21,10 +20,14 @@ Notes:
|
|||||||
|
|
||||||
## Cloudflare shortcut
|
## Cloudflare shortcut
|
||||||
|
|
||||||
If your zone is on Cloudflare, skip the manual step — `02-cf-dns.py` upserts all four
|
If your zone is on Cloudflare, skip the manual step — `02-cf-dns.py` upserts the two A
|
||||||
records for you via the API (`CF_API_TOKEN` with `Zone:DNS:Edit`). Run it from wherever the
|
records when `SERVER_IPV6` is empty (`CF_API_TOKEN` with `Zone:DNS:Edit`). Run it from wherever the
|
||||||
token lives; it never touches the VPS.
|
token lives; it never touches the VPS.
|
||||||
|
|
||||||
|
Leaving `SERVER_IPV6` empty does **not** delete existing AAAA records. For an existing
|
||||||
|
node, back up DNS records, remove the AAAA records under that node's domain, then check
|
||||||
|
the API and public DNS. See [IPv4-only fleet policy](../../../../docs/EDGE-IPV4-ONLY.md).
|
||||||
|
|
||||||
## Picking the acme.sh plugin + creds
|
## Picking the acme.sh plugin + creds
|
||||||
|
|
||||||
Set `ACME_DNS_PLUGIN` and the matching credential vars in `config.env` (acme.sh reads them
|
Set `ACME_DNS_PLUGIN` and the matching credential vars in `config.env` (acme.sh reads them
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ Every item here cost real debugging time on a live deployment. They are ordered
|
|||||||
|
|
||||||
## Cloudflare WARP
|
## Cloudflare WARP
|
||||||
|
|
||||||
|
- **Disabling host IPv6 does not disable IPv6 inside WARP's proxy tunnel.** Keep the IPv4-only guards in Xray and sing-box: force IPv4 domain resolution and reject `::/0` before WARP routing. `UseIPv4` can fall back to system resolution in Xray; use `ForceIPv4`. Do not pass unresolved target domains to WARP. See [fleet policy](../../../../docs/EDGE-IPV4-ONLY.md).
|
||||||
- **`gpg` must be installed first.** On a minimal Debian, `gpg --dearmor` for the repo keyring fails with `gpg: command not found`, and the later `apt update` then errors `Failed to parse keyring … sqv returned an error code`. `apt-get install -y gnupg` before adding the repo.
|
- **`gpg` must be installed first.** On a minimal Debian, `gpg --dearmor` for the repo keyring fails with `gpg: command not found`, and the later `apt update` then errors `Failed to parse keyring … sqv returned an error code`. `apt-get install -y gnupg` before adding the repo.
|
||||||
- Use the **`bookworm`** repo line even on Debian 13 (trixie) — the bookworm package works.
|
- Use the **`bookworm`** repo line even on Debian 13 (trixie) — the bookworm package works.
|
||||||
- **WARP can lie about being connected.** The control plane shows `Connected` while the SOCKS proxy stops egressing. The watchdog (`warp-healthcheck.*`) checks `warp=on` *through the proxy* and restarts `warp-svc` when it isn't.
|
- **WARP can lie about being connected.** The control plane shows `Connected` while the SOCKS proxy stops egressing. The watchdog (`warp-healthcheck.*`) checks `warp=on` *through the proxy* and restarts `warp-svc` when it isn't.
|
||||||
|
|||||||
@@ -107,22 +107,26 @@ TEMPLATE = {
|
|||||||
"inbounds": [{"listen": "127.0.0.1", "port": 62789, "protocol": "dokodemo-door",
|
"inbounds": [{"listen": "127.0.0.1", "port": 62789, "protocol": "dokodemo-door",
|
||||||
"settings": {"address": "127.0.0.1"}, "tag": "api"}],
|
"settings": {"address": "127.0.0.1"}, "tag": "api"}],
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
{"protocol": "freedom", "settings": {}},
|
{"protocol": "freedom", "settings": {"domainStrategy": "ForceIPv4"},
|
||||||
|
"streamSettings": {"sockopt": {"domainStrategy": "ForceIPv4"}}},
|
||||||
{"tag": "warp-outbound", "protocol": "freedom",
|
{"tag": "warp-outbound", "protocol": "freedom",
|
||||||
"settings": {"domainStrategy": "UseIPv6v4"}, "proxySettings": {"tag": "warp-socks"}},
|
"settings": {"domainStrategy": "ForceIPv4"},
|
||||||
|
"streamSettings": {"sockopt": {"domainStrategy": "ForceIPv4"}},
|
||||||
|
"proxySettings": {"tag": "warp-socks"}},
|
||||||
{"tag": "warp-socks", "protocol": "socks",
|
{"tag": "warp-socks", "protocol": "socks",
|
||||||
"settings": {"servers": [{"address": "127.0.0.1", "port": WARP_SOCKS_PORT}]}},
|
"settings": {"servers": [{"address": "127.0.0.1", "port": WARP_SOCKS_PORT}]}},
|
||||||
{"protocol": "blackhole", "settings": {}, "tag": "blocked"},
|
{"protocol": "blackhole", "settings": {}, "tag": "blocked"},
|
||||||
],
|
],
|
||||||
"policy": {"system": {"statsInboundDownlink": True, "statsInboundUplink": True}},
|
"policy": {"system": {"statsInboundDownlink": True, "statsInboundUplink": True}},
|
||||||
"routing": {"rules": [
|
"routing": {"rules": [
|
||||||
|
{"ip": ["::/0"], "outboundTag": "blocked", "type": "field"},
|
||||||
{"inboundTag": ["api"], "outboundTag": "api", "type": "field"},
|
{"inboundTag": ["api"], "outboundTag": "api", "type": "field"},
|
||||||
{"inboundTag": [], "outboundTag": "warp-outbound", "type": "field"}, # tags filled in below
|
{"inboundTag": [], "outboundTag": "warp-outbound", "type": "field"}, # tags filled in below
|
||||||
{"ip": ["geoip:private"], "outboundTag": "blocked", "type": "field"},
|
{"ip": ["geoip:private"], "outboundTag": "blocked", "type": "field"},
|
||||||
{"protocol": ["bittorrent"], "outboundTag": "blocked", "type": "field"},
|
{"protocol": ["bittorrent"], "outboundTag": "blocked", "type": "field"},
|
||||||
]},
|
]},
|
||||||
"stats": {},
|
"stats": {},
|
||||||
"dns": {"servers": ["1.1.1.1"], "queryStrategy": "UseIP"},
|
"dns": {"servers": ["1.1.1.1"], "queryStrategy": "UseIPv4"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -258,6 +262,16 @@ def main():
|
|||||||
# WARP routing rule: read-modify-write (fix #4) -- append, never clobber.
|
# WARP routing rule: read-modify-write (fix #4) -- append, never clobber.
|
||||||
row = cur.execute("SELECT value FROM settings WHERE key='xrayTemplateConfig'").fetchone()
|
row = cur.execute("SELECT value FROM settings WHERE key='xrayTemplateConfig'").fetchone()
|
||||||
tmpl = json.loads(row[0]) if row else TEMPLATE
|
tmpl = json.loads(row[0]) if row else TEMPLATE
|
||||||
|
# Enforce the fleet's IPv4-only policy when extending an existing template too.
|
||||||
|
for outbound in tmpl["outbounds"]:
|
||||||
|
if outbound.get("protocol") == "freedom":
|
||||||
|
outbound.setdefault("settings", {})["domainStrategy"] = "ForceIPv4"
|
||||||
|
outbound.setdefault("streamSettings", {}).setdefault("sockopt", {})["domainStrategy"] = "ForceIPv4"
|
||||||
|
tmpl.setdefault("dns", {})["queryStrategy"] = "UseIPv4"
|
||||||
|
blocked_tag = next(o["tag"] for o in tmpl["outbounds"] if o.get("protocol") == "blackhole")
|
||||||
|
ipv6_rule = {"ip": ["::/0"], "outboundTag": blocked_tag, "type": "field"}
|
||||||
|
if ipv6_rule not in tmpl["routing"]["rules"]:
|
||||||
|
tmpl["routing"]["rules"].insert(0, ipv6_rule)
|
||||||
for rule in tmpl["routing"]["rules"]:
|
for rule in tmpl["routing"]["rules"]:
|
||||||
if rule.get("outboundTag") == "warp-outbound":
|
if rule.get("outboundTag") == "warp-outbound":
|
||||||
for t in warp_tags:
|
for t in warp_tags:
|
||||||
@@ -269,7 +283,8 @@ def main():
|
|||||||
cur.execute("INSERT INTO settings (key, value) VALUES ('xrayTemplateConfig', ?)", (json.dumps(tmpl, ensure_ascii=False),))
|
cur.execute("INSERT INTO settings (key, value) VALUES ('xrayTemplateConfig', ?)", (json.dumps(tmpl, ensure_ascii=False),))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
print(" xrayTemplateConfig written, warp rule tags:", tmpl["routing"]["rules"][1]["inboundTag"])
|
print(" xrayTemplateConfig written, warp rule tags:",
|
||||||
|
next(r["inboundTag"] for r in tmpl["routing"]["rules"] if r.get("outboundTag") == "warp-outbound"))
|
||||||
|
|
||||||
subprocess.run(["systemctl", "restart", "x-ui"], check=True)
|
subprocess.run(["systemctl", "restart", "x-ui"], check=True)
|
||||||
time.sleep(6)
|
time.sleep(6)
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ for tag, pvar in (("hy2-direct", "PORT_HY2_DIRECT"), ("hy2-warp", "PORT_HY2_WARP
|
|||||||
p = port(pvar)
|
p = port(pvar)
|
||||||
if not p:
|
if not p:
|
||||||
continue
|
continue
|
||||||
ib = {"type": "hysteria2", "tag": tag, "listen": "::", "listen_port": p,
|
ib = {"type": "hysteria2", "tag": tag, "listen": "0.0.0.0", "listen_port": p,
|
||||||
"up_mbps": up, "down_mbps": down,
|
"up_mbps": up, "down_mbps": down,
|
||||||
"users": [{"name": "node-main", "password": os.environ["NODE_PASSWORD"]}],
|
"users": [{"name": "node-main", "password": os.environ["NODE_PASSWORD"]}],
|
||||||
"tls": tls()}
|
"tls": tls()}
|
||||||
@@ -67,7 +67,7 @@ for tag, pvar in (("tuic-direct", "PORT_TUIC_DIRECT"), ("tuic-warp", "PORT_TUIC_
|
|||||||
if not p:
|
if not p:
|
||||||
continue
|
continue
|
||||||
inbounds.append({
|
inbounds.append({
|
||||||
"type": "tuic", "tag": tag, "listen": "::", "listen_port": p,
|
"type": "tuic", "tag": tag, "listen": "0.0.0.0", "listen_port": p,
|
||||||
"users": [{"name": "node-main", "uuid": os.environ["XUI_UUID"],
|
"users": [{"name": "node-main", "uuid": os.environ["XUI_UUID"],
|
||||||
"password": os.environ["NODE_PASSWORD"]}],
|
"password": os.environ["NODE_PASSWORD"]}],
|
||||||
"congestion_control": tuic_cc, "auth_timeout": "3s",
|
"congestion_control": tuic_cc, "auth_timeout": "3s",
|
||||||
@@ -79,7 +79,10 @@ if not inbounds:
|
|||||||
warp_tags = [i["tag"] for i in inbounds if i["tag"].endswith("-warp")]
|
warp_tags = [i["tag"] for i in inbounds if i["tag"].endswith("-warp")]
|
||||||
direct_tags = [i["tag"] for i in inbounds if i["tag"].endswith("-direct")]
|
direct_tags = [i["tag"] for i in inbounds if i["tag"].endswith("-direct")]
|
||||||
|
|
||||||
rules = []
|
rules = [
|
||||||
|
{"action": "resolve", "strategy": "ipv4_only", "server": "ipv4-dns"},
|
||||||
|
{"ip_cidr": ["::/0"], "action": "reject"},
|
||||||
|
]
|
||||||
if warp_tags:
|
if warp_tags:
|
||||||
rules.append({"inbound": warp_tags, "outbound": "warp"})
|
rules.append({"inbound": warp_tags, "outbound": "warp"})
|
||||||
if direct_tags:
|
if direct_tags:
|
||||||
@@ -88,6 +91,8 @@ rules.append({"protocol": "bittorrent", "outbound": "block"})
|
|||||||
|
|
||||||
cfg = {
|
cfg = {
|
||||||
"log": {"level": "warn", "timestamp": True},
|
"log": {"level": "warn", "timestamp": True},
|
||||||
|
"dns": {"servers": [{"type": "udp", "tag": "ipv4-dns", "server": "1.1.1.1",
|
||||||
|
"server_port": 53}], "strategy": "ipv4_only", "final": "ipv4-dns"},
|
||||||
"inbounds": inbounds,
|
"inbounds": inbounds,
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
{"type": "direct", "tag": "direct"},
|
{"type": "direct", "tag": "direct"},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
# --- target VPS ---
|
# --- target VPS ---
|
||||||
SERVER_IP="203.0.113.10" # public IPv4
|
SERVER_IP="203.0.113.10" # public IPv4
|
||||||
SERVER_IPV6="2001:db8::10" # public IPv6 (optional; leave empty to skip AAAA)
|
SERVER_IPV6="" # fleet is IPv4-only; do not create AAAA
|
||||||
SSH_USER="root" # root, or a sudo user (scripts escalate via sudo)
|
SSH_USER="root" # root, or a sudo user (scripts escalate via sudo)
|
||||||
SSH_KEY="$HOME/.ssh/your-vps-key.pem" # private key for the VPS
|
SSH_KEY="$HOME/.ssh/your-vps-key.pem" # private key for the VPS
|
||||||
TZ="UTC" # server timezone (e.g. UTC, Asia/Shanghai, America/Los_Angeles)
|
TZ="UTC" # server timezone (e.g. UTC, Asia/Shanghai, America/Los_Angeles)
|
||||||
@@ -16,7 +16,7 @@ DOMAIN_ROOT="nodeA.example.com" # this node owns *.nodeA.exampl
|
|||||||
# Cloudflare is the default & has a turnkey record-creation script (02-cf-dns.py).
|
# Cloudflare is the default & has a turnkey record-creation script (02-cf-dns.py).
|
||||||
# Any acme.sh-supported DNS provider works for the wildcard cert — set ACME_DNS_PLUGIN and
|
# Any acme.sh-supported DNS provider works for the wildcard cert — set ACME_DNS_PLUGIN and
|
||||||
# the matching credential vars below. For A/AAAA records on a non-CF provider, create them
|
# the matching credential vars below. For A/AAAA records on a non-CF provider, create them
|
||||||
# yourself (see references/dns-records.md — it's two names, A + AAAA, DNS-only).
|
# yourself (see references/dns-records.md — two names with A records, DNS-only).
|
||||||
DNS_PROVIDER="cloudflare" # informational: cloudflare | dnspod | aliyun | route53 | other
|
DNS_PROVIDER="cloudflare" # informational: cloudflare | dnspod | aliyun | route53 | other
|
||||||
ACME_DNS_PLUGIN="dns_cf" # acme.sh plugin: dns_cf | dns_dp | dns_ali | dns_aws | dns_gd | ...
|
ACME_DNS_PLUGIN="dns_cf" # acme.sh plugin: dns_cf | dns_dp | dns_ali | dns_aws | dns_gd | ...
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
- 通用部署脚本、参考资料与占位交付模板唯一源码在 `.claude/skills/edge-node-multi-protocol/`,Codex 从 `.agents/skills/edge-node-multi-protocol` 相对链接加载。部署/重建/维护 VPS 节点使用此技能;发布 Proxy Station 应用用 `deploy`,生产订阅数据查询与修改用 `update-db`。
|
- 通用部署脚本、参考资料与占位交付模板唯一源码在 `.claude/skills/edge-node-multi-protocol/`,Codex 从 `.agents/skills/edge-node-multi-protocol` 相对链接加载。部署/重建/维护 VPS 节点使用此技能;发布 Proxy Station 应用用 `deploy`,生产订阅数据查询与修改用 `update-db`。
|
||||||
- 真实配置、面板入口、分享链接、二维码、交付文档和各站点脚本工作副本在 `edge-nodes/private/<id>-edge-node-docs/`,全部禁止入 Git 或公开静态托管,也不发送到在线二维码服务。新增配置/产物用 `umask 077`,私有目录用受控备份转交,新克隆不会携带这些文件。
|
- 真实配置、面板入口、分享链接、二维码、交付文档和各站点脚本工作副本在 `edge-nodes/private/<id>-edge-node-docs/`,全部禁止入 Git 或公开静态托管,也不发送到在线二维码服务。新增配置/产物用 `umask 077`,私有目录用受控备份转交,新克隆不会携带这些文件。
|
||||||
- 当前舰队为 sg(AWS 新加坡)、la(DMIT 洛杉矶,同时承载 Proxy Station)、rn(RackNerd 洛杉矶,当前工作副本为 `rn2-edge-node-docs/`)、jp(VMISS 东京,裸 nginx)。`rn-edge-node-docs/` 与 `private/archive/` 是历史资料,不作为当前配置来源;原兄弟目录仅保留为迁移前备份,后续在本仓库维护。
|
- 当前舰队为 sg(AWS 新加坡)、la(DMIT 洛杉矶,同时承载 Proxy Station)、rn(RackNerd 洛杉矶,当前工作副本为 `rn2-edge-node-docs/`)、jp(VMISS 东京,裸 nginx)。`rn-edge-node-docs/` 与 `private/archive/` 是历史资料,不作为当前配置来源;原兄弟目录仅保留为迁移前备份,后续在本仓库维护。
|
||||||
|
- 舰队自 2026-10-03 统一使用 IPv4:系统接口禁用 IPv6,Xray/sing-box 仅解析 IPv4 并拒绝 IPv6 目标(含 WARP),节点域名不保留 AAAA。重建或扩展线路须保留这些约束,详见 `docs/EDGE-IPV4-ONLY.md`;仅禁用网卡或删除 AAAA 不能阻止 WARP 隧道内的 IPv6 转发。
|
||||||
- TCP 基础线路由 nginx+3x-ui/Xray 承载 VMess/Trojan/SS × direct/WARP,QUIC(HY2/TUIC)由 sing-box 独立承载。3x-ui 客户端不能只写投影表,必须保留 `settings.clients`,否则升级迁移会丢失认证。改远端前读取节点交付记录与技能参考,备份受影响配置和数据库,并保留已有证书续期动作。
|
- TCP 基础线路由 nginx+3x-ui/Xray 承载 VMess/Trojan/SS × direct/WARP,QUIC(HY2/TUIC)由 sing-box 独立承载。3x-ui 客户端不能只写投影表,必须保留 `settings.clients`,否则升级迁移会丢失认证。改远端前读取节点交付记录与技能参考,备份受影响配置和数据库,并保留已有证书续期动作。
|
||||||
- 迁移工程、修改脚本或复制文件不会授权远端部署;节点与应用的部署各自按任务执行。应用运行时仍通过管理 API/Web 导入节点,不自动读取私有目录。TUIC/SOCKS5 交付线路不属于本系统当前支持的导入类型。
|
- 迁移工程、修改脚本或复制文件不会授权远端部署;节点与应用的部署各自按任务执行。应用运行时仍通过管理 API/Web 导入节点,不自动读取私有目录。TUIC/SOCKS5 交付线路不属于本系统当前支持的导入类型。
|
||||||
|
|
||||||
|
|||||||
28
docs/EDGE-IPV4-ONLY.md
Normal file
28
docs/EDGE-IPV4-ONLY.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# 边缘舰队 IPv4-only
|
||||||
|
|
||||||
|
2026-10-03 按用户授权对 SG、LA、RN、JP 四台现役节点统一禁用 IPv6。目标是经任何代理线路访问目标时,直连与 WARP 均只使用 IPv4;IPv6 地址及仅有 AAAA 的目标直接失败,不回退到 IPv6。
|
||||||
|
|
||||||
|
## 三层约束
|
||||||
|
|
||||||
|
1. 系统:`/etc/sysctl.d/99-z-proxy-station-ipv4-only.conf` 将 `net.ipv6.conf.all.disable_ipv6`、`default.disable_ipv6`、`lo.disable_ipv6` 设为 `1`。`proxy-station-ipv4-only.service` 已启用,在 `network-online.target` 之后、Xray/sing-box/WARP 之前重新应用。检查每个接口,不只检查 `all`;`ip -6 addr` 与 `ip -6 route` 应无地址/路由。
|
||||||
|
2. 代理:Xray 的每个 freedom 出站使用 `settings.domainStrategy=ForceIPv4` 与 `streamSettings.sockopt.domainStrategy=ForceIPv4`,内置 DNS 使用 `queryStrategy=UseIPv4`;`::/0` 拒绝规则在 WARP 分流之前。持久配置写在 3x-ui 数据库的 `xrayTemplateConfig`,不能只改生成的 `bin/config.json`。sing-box 在路由开始先 `resolve`(`ipv4_only`),再拒绝 `::/0`,之后才进入原有 direct/WARP 规则;DNS 经 IPv4 UDP 服务器,QUIC 入站监听 `0.0.0.0`。
|
||||||
|
3. DNS:节点根域、通配符及其他节点子域不保留 AAAA,A 记录为 DNS-only。私有 `config.env` 的 `SERVER_IPV6` 清空,防止重新创建 AAAA;清空变量不会删除已存在的记录。
|
||||||
|
|
||||||
|
WARP 在用户态代理隧道中仍有双栈能力,因此主机没有公网 IPv6 也不能替代代理层限制。Xray 的 `UseIPv4` 允许解析失败后回退;当前策略使用 `ForceIPv4`。依据:[Linux 内核 sysctl](https://www.kernel.org/doc/html/latest/networking/ip-sysctl.html)、[Xray sockopt](https://xtls.github.io/config/transports/sockopt.html)、[sing-box resolve](https://sing-box.sagernet.org/configuration/route/rule_action/#resolve)。
|
||||||
|
|
||||||
|
## 维护与回滚
|
||||||
|
|
||||||
|
通用 `08-xui-bootstrap.py` 与 `13-singbox-config.py` 已保留代理层防护;前者扩展已有模板时也应用 IPv4-only,后者需要 sing-box 1.12+。新建/重建服务器还须配置上述系统 sysctl 与开机服务,不要只依赖生成器。当前私有脚本工作副本已同步相关修改。
|
||||||
|
|
||||||
|
本次每台机器先通过 SQLite backup API 在线备份 3x-ui 数据库,并打包受影响配置、系统网络设置、systemd 单元、nginx、acme.sh 与 WARP 状态。备份在远端 `/root/ipv4-only-backup-<UTC时间>/`,具体路径、Cloudflare 删除前的记录及验收结果在 Git 忽略的 `edge-nodes/private/ipv4-only-20261003/`。不要将这些内容公开托管或入 Git。证书续期动作未修改。
|
||||||
|
|
||||||
|
回滚应读取本机的私有维护记录和该服务器快照,仅恢复受影响的 3x-ui 模板、sing-box 配置及 sysctl/开机单元;不要用整个目录快照覆盖后来新增的配置。恢复 IPv6 或 AAAA 须有用户授权。
|
||||||
|
|
||||||
|
## 本次验证与边界
|
||||||
|
|
||||||
|
- 四台服务器全部接口的 `disable_ipv6=1`,无 IPv6 地址;持久文件与开机服务均已配置。未进行整机重启验收。
|
||||||
|
- 服务器侧真实协议客户端验证 33 条线路(SG 8、LA 6、RN 11、JP 8):IPv4 出口正常,direct 的 `warp=off`、WARP 的 `warp=on`;IPv6 字面地址与仅 IPv6 域名均失败。
|
||||||
|
- 从本机经公网验证线路:Trojan/Hysteria2/TUIC/SOCKS5 使用 sing-box,VMess/SS over WS 使用 mihomo。临时客户端只监听本机回环,不修改活动代理配置。具体结果见私有记录。
|
||||||
|
- Cloudflare 删除 SG、LA 的 4 条 AAAA;RN、JP 原本无 AAAA。实际 HTTPS Surge 订阅通过官方 CLI 校验。
|
||||||
|
|
||||||
|
代理入站使用的 QUIC UDP 已做真实连接验证;未逐条进行被代理 UDP 负载、手机真机或本地直连 IPv6 泄漏验收。这项策略限制服务器代理转发,不改变客户端绕过代理的本地网络行为。当前不需要通过 VPS 厂商面板才能达到代理仅 IPv4 的目标。
|
||||||
@@ -34,6 +34,8 @@ edge-nodes/
|
|||||||
|
|
||||||
站点信息是迁移时的工作记录;维护前仍需读取私有交付文档并核对实际机器。`rn-edge-node-docs/` 的旧链接不作为当前 RN 节点导入来源。
|
站点信息是迁移时的工作记录;维护前仍需读取私有交付文档并核对实际机器。`rn-edge-node-docs/` 的旧链接不作为当前 RN 节点导入来源。
|
||||||
|
|
||||||
|
2026-10-03 四台现役节点已统一使用 IPv4:系统接口禁用 IPv6,代理层拒绝 IPv6 目标(含 WARP),Cloudflare 不保留节点 AAAA。后续重建与扩展遵守 [IPv4-only 维护约定](../docs/EDGE-IPV4-ONLY.md);具体备份与验收在各站点的私有 `04-IPv4-only.md`。
|
||||||
|
|
||||||
## 日常工作
|
## 日常工作
|
||||||
|
|
||||||
在仓库根目录启动 Codex/Claude Code。节点部署或维护使用 `edge-node-multi-protocol` 技能,应用发布使用 `deploy`,线上订阅数据使用 `update-db`。技能只维护一份源码,Codex 通过 `.agents/skills/` 相对链接发现。
|
在仓库根目录启动 Codex/Claude Code。节点部署或维护使用 `edge-node-multi-protocol` 技能,应用发布使用 `deploy`,线上订阅数据使用 `update-db`。技能只维护一份源码,Codex 通过 `.agents/skills/` 相对链接发现。
|
||||||
|
|||||||
Reference in New Issue
Block a user