CPA Mihomo TUN 节点更换手册

· 网安 · #服务器, #自用, #命令

CPA Mihomo TUN 节点更换手册

适用范围

本手册适用于当前服务器已经完成以下部署的情况:

本流程只更换 Mihomo 节点并验证现有 TUN,不重新迁移 Compose,也不修改服务器全局网络、SSH、Docker 防火墙或 Aurora 服务。

节点地址、密码和 UUID 属于私密信息。不要将完整节点配置粘贴到日志、聊天或公开仓库。

1. 登录并确认当前状态

从 Mac 登录服务器:

ssh blog

查看 CPA 和 Mihomo:

cd /opt/cpaplus-private

docker compose ps

docker inspect \
  --format '{{.State.Health.Status}}' \
  cpaplus-private-mihomo-1

docker exec cpaplus-private-mihomo-1 \
  ip link show dev cpa-tun

继续操作前应确认:

如果预检失败,停止更换节点,先修复当前服务。

2. 备份当前节点和 Mihomo 配置

cd /opt/mihomo-private
umask 077

backup_dir="/opt/mihomo-private/backups/$(date +%F-%H%M%S)-before-node-switch"

mkdir -p "$backup_dir"
chmod 700 "$backup_dir"

cp -p config/nodes.yaml "$backup_dir/nodes.yaml"
cp -p config/config.yaml "$backup_dir/config.yaml"

printf '备份目录:%s\n' "$backup_dir"

保持当前 SSH 会话。后续回退命令会继续使用 $backup_dir。

3. 创建候选节点文件

install -m 600 /dev/null \
  /opt/mihomo-private/config/nodes.candidate.yaml

vim /opt/mihomo-private/config/nodes.candidate.yaml

nodes.yaml 的根节点始终是 proxies: 数组。数组中的每一项是一种 Mihomo 出站节点,type 决定后续字段。

Hysteria2

proxies:
  - name: 你的节点名称
    type: hysteria2
    server: 你的节点域名
    port: 你的端口
    password: 你的密码
    sni: 你的SNI
    up: 3000
    down: 3000
    skip-cert-verify: true

Hysteria2 使用 QUIC/UDP。up、down 不写单位时默认是 Mbps;应填写服务商给出的值,不要把示例带宽当成测速结果。只有服务端确实使用无法验证的证书时才保留 skip-cert-verify: true。

VMess + WebSocket

proxies:
  - name: 你的VMess节点
    type: vmess
    server: 你的节点域名
    port: 443
    uuid: 你的UUID
    alterId: 0
    cipher: auto
    udp: true
    tls: true
    servername: 证书域名
    skip-cert-verify: false
    network: ws
    ws-opts:
      path: /你的WebSocket路径
      headers:
        Host: 你的WebSocket-Host

如果服务商配置没有 TLS,应删除 tls、servername 和 skip-cert-verify,而不是自行猜测。旧订阅有时同时给出 ws-path、ws-headers 与 ws-opts;优先原样使用服务商提供的 Mihomo/Clash Meta 配置。

VLESS + Reality

proxies:
  - name: 你的VLESS-Reality节点
    type: vless
    server: 你的节点域名
    port: 443
    uuid: 你的UUID
    udp: true
    network: tcp
    tls: true
    servername: Reality伪装域名
    client-fingerprint: chrome
    flow: xtls-rprx-vision
    encryption: ""
    reality-opts:
      public-key: 服务端Reality公钥
      short-id: 服务端Short-ID

flow、servername、public-key 和 short-id 必须与服务端一致。如果服务商给的是 VLESS + WS、gRPC 或 XHTTP,应使用对应的 network 和传输参数,不能套用 Reality 模板。

Trojan

proxies:
  - name: 你的Trojan节点
    type: trojan
    server: 你的节点域名
    port: 443
    password: 你的密码
    udp: true
    sni: 证书域名
    skip-cert-verify: false
    network: tcp

Trojan 使用 TLS。若服务商提供的是 Trojan + WS 或 gRPC,需要把 network 改成对应类型,并完整保留其传输参数。

Shadowsocks

proxies:
  - name: 你的Shadowsocks节点
    type: ss
    server: 你的节点域名或IP
    port: 你的端口
    cipher: 服务商提供的加密方法
    password: 你的密码
    udp: true

cipher 必须与服务端一致,例如 aes-128-gcm、aes-256-gcm 或 chacha20-ietf-poly1305。如果服务商还提供插件,必须同时保留 plugin 和 plugin-opts。

TUIC v5

proxies:
  - name: 你的TUIC节点
    type: tuic
    server: 你的节点域名
    port: 443
    uuid: 你的UUID
    password: 你的密码
    sni: 证书域名
    alpn:
      - h3
    udp-relay-mode: native
    congestion-controller: bbr
    skip-cert-verify: false

TUIC v5 使用 uuid 和 password。TUIC v4 使用 token,不能同时照搬 v5 的认证字段。TUIC 与 Hysteria2 都依赖 UDP 出站能力。

HTTP 或 HTTPS 上游代理

proxies:
  - name: 你的HTTP代理
    type: http
    server: 代理服务器域名
    port: 代理端口
    username: 你的用户名
    password: 你的密码
    tls: true
    sni: 证书域名
    skip-cert-verify: false

普通 HTTP 代理删除 tls、sni 和 skip-cert-verify。这里的 type: http 是 Mihomo 的上游节点类型,与 CPA 渠道中的 base-url 没有关系。

SOCKS5 上游代理

proxies:
  - name: 你的SOCKS5代理
    type: socks5
    server: 代理服务器域名
    port: 代理端口
    username: 你的用户名
    password: 你的密码
    udp: true

如果代理没有用户名和密码,删除这两个字段。

能否直接复制 Clash Verge 节点

如果节点来自兼容 Mihomo/Clash Meta 的配置,最可靠的做法是完整复制该节点对象,并在外层加上:

proxies:
  - name: 原节点名称
    type: 原节点类型
    # 以下字段保持原样

不要把 vmess://、vless://、hysteria2:// 等分享链接直接写进 nodes.yaml。应先在 Clash Verge 中查看解析后的 YAML,或者使用可信的本地转换方式。

请使用服务商提供的真实字段替换所有占位内容。不同传输层的字段不能只根据 type 推导;同为 VMess 或 VLESS,也可能分别使用 TCP、WS、gRPC、Reality 或 XHTTP。

更换 type 后是否需要修改主 config.yaml

当前主配置使用:

proxy-providers:
  cpa-node:
    type: file
    path: ./config/nodes.yaml

proxy-groups:
  - name: CPA-UPSTREAM
    type: select
    use:
      - cpa-node

rules:
  - MATCH,CPA-UPSTREAM

在以下情况下,不需要修改 /opt/mihomo-private/config/config.yaml:

Hysteria2、VMess、VLESS、Trojan、Shadowsocks、TUIC、HTTP 和 SOCKS5 之间的普通单节点切换,都属于这种情况。TUN、DNS、UID 路由和 Compose 不因节点 type 改变。

以下情况需要另外评估或修改配置:

当前部署没有 Mihomo 控制面板,并且关闭了选择状态持久化。为了让切换结果确定,日常维护继续保持 nodes.yaml 只有一个节点。

在 Vim 中保存:

Esc
:wq
Enter

4. 检查候选节点结构

以下检查不会打印密码或节点地址:

python3 - <<'PY'
import yaml
from pathlib import Path

path = Path("/opt/mihomo-private/config/nodes.candidate.yaml")
data = yaml.safe_load(path.read_text())

assert isinstance(data, dict), "根节点必须是对象"
assert isinstance(data.get("proxies"), list), "缺少 proxies 列表"
assert len(data["proxies"]) == 1, "当前配置要求只保留一个节点"

node = data["proxies"][0]
node_type = node.get("type")
required = ["name", "type", "server", "port"]

required_by_type = {
    "hysteria2": ["password", "sni"],
    "vmess": ["uuid", "alterId", "cipher"],
    "vless": ["uuid"],
    "trojan": ["password", "sni"],
    "ss": ["cipher", "password"],
}
required += required_by_type.get(node_type, [])

if node_type == "tuic":
    tuic_v4 = bool(node.get("token"))
    tuic_v5 = bool(node.get("uuid") and node.get("password"))
    assert tuic_v4 != tuic_v5, "TUIC 必须二选一:v4 token 或 v5 uuid+password"

def missing_value(key):
    return key not in node or node[key] is None or node[key] == ""

missing = [key for key in required if missing_value(key)]
assert not missing, f"缺少必要字段:{missing}"

assert isinstance(node["port"], int), "port 必须是数字"
assert 1 <= node["port"] <= 65535, "port 超出范围"

print({
    "validation": "passed",
    "node_count": len(data["proxies"]),
    "type": node_type,
})
PY

只有看到 validation: passed 才继续。

5. 生成临时 Mihomo 配置

正式 config.yaml 读取 nodes.yaml。为了在替换正式节点前完成镜像级校验,创建一份指向候选节点的临时配置:

python3 - <<'PY'
import os
import yaml
from pathlib import Path

source = Path("/opt/mihomo-private/config/config.yaml")
target = Path("/opt/mihomo-private/config/config.candidate.yaml")

config = yaml.safe_load(source.read_text())
provider = config["proxy-providers"]["cpa-node"]
provider["path"] = "./config/nodes.candidate.yaml"

target.write_text(
    yaml.safe_dump(
        config,
        sort_keys=False,
        allow_unicode=True,
    )
)

os.chmod(target, 0o600)
print("候选 Mihomo 配置已生成")
PY

这一步不会改动正式 config.yaml。

6. 使用实际 Mihomo 镜像校验候选配置

docker compose \
  -f /opt/cpaplus-private/compose.yaml \
  run --rm --no-deps --pull never -T \
  mihomo \
  -t \
  -d /data \
  -f /data/config/config.candidate.yaml

-t 表示只校验配置后退出。命令必须成功返回,才能替换正式节点。

如果校验失败,停止流程并删除候选文件:

rm -f \
  /opt/mihomo-private/config/nodes.candidate.yaml \
  /opt/mihomo-private/config/config.candidate.yaml

此时正式节点和运行中的服务均未改变。

7. 替换正式节点

配置校验成功后执行:

cd /opt/mihomo-private

mv config/nodes.candidate.yaml config/nodes.yaml
chmod 600 config/nodes.yaml

rm -f config/config.candidate.yaml

检查权限:

stat -c '%a %U:%G %n' config/nodes.yaml

预期权限为 600,属主通常为 blog:blog。

8. 重启 Mihomo

docker compose \
  -f /opt/cpaplus-private/compose.yaml \
  restart mihomo

等待健康状态:

health=""

for attempt in $(seq 1 20); do
  health="$(
    docker inspect \
      --format '{{.State.Health.Status}}' \
      cpaplus-private-mihomo-1
  )"

  printf 'Mihomo: %s\n' "$health"

  if [ "$health" = "healthy" ]; then
    break
  fi

  sleep 2
done

test "$health" = "healthy"

最后一条 test 没有输出并返回终端提示符,表示检查通过。如果它返回非零状态,执行本文的回退步骤。

9. 验证 TUN 和内部连接

确认 TUN 接口仍然存在:

docker exec cpaplus-private-mihomo-1 \
  ip link show dev cpa-tun

确认 CPA 健康:

curl --noproxy '*' \
  -fsS \
  --max-time 5 \
  http://127.0.0.1:8317/healthz

确认 CPAMP 仍能访问 CPA:

docker exec cpamp-private-cpamp-1 \
  wget -qO- -T 5 \
  http://cpaplus:8317/healthz

上述检查验证容器和内部网络,不证明目标模型已经可用。

10. 验证 CPA 的真实 TUN 出口

以下命令以 CPA 使用的 UID 1000 发起公开 HTTPS 请求,并主动清空显式 HTTP、HTTPS 和 SOCKS 代理环境变量:

docker exec \
  --user 1000:1000 \
  cpaplus-private-mihomo-1 \
  env \
  HTTP_PROXY= \
  HTTPS_PROXY= \
  ALL_PROXY= \
  http_proxy= \
  https_proxy= \
  all_proxy= \
  wget \
  -T 25 \
  -qO- \
  https://www.cloudflare.com/cdn-cgi/trace

关注以下字段:

ip=...
loc=...
tls=TLSv1.3

loc 是实际公开出口位置。不能根据节点名称推断出口国家。

这一步只证明公开 HTTPS 出站链路,不证明 Gemini、Claude 或其他业务接口一定接受该节点。

11. 执行业务验证

使用平时的客户端分别发起一个最小请求:

  1. 调用需要验证的 Gemini 模型;
  2. 调用需要验证的 Claude 模型;
  3. 确认没有地区限制、鉴权、超时或 auth_unavailable 错误。

不要仅凭容器 healthy、TUN 接口存在或公开 HTTPS 成功宣布切换完成。目标模型的实际生成成功才是最终验证。

12. 失败时回退

如果当前 SSH 会话仍然保存 $backup_dir:

cd /opt/mihomo-private

cp -p "$backup_dir/nodes.yaml" config/nodes.yaml
cp -p "$backup_dir/config.yaml" config/config.yaml

chmod 600 config/nodes.yaml config/config.yaml

docker compose \
  -f /opt/cpaplus-private/compose.yaml \
  restart mihomo

回退后重新检查:

docker inspect \
  --format '{{.State.Health.Status}}' \
  cpaplus-private-mihomo-1

curl --noproxy '*' \
  -fsS \
  --max-time 5 \
  http://127.0.0.1:8317/healthz

如果已经重新登录,先列出备份目录:

ls -dt \
  /opt/mihomo-private/backups/*-before-node-switch

确认正确的目录后重新定义变量:

backup_dir="/opt/mihomo-private/backups/实际备份目录"

然后执行上面的恢复命令。不要未经核对就恢复最旧或最新的任意备份。

13. 关于首次启用 TUN

当前服务器已经是 CPA 与 Mihomo 共享网络命名空间的 TUN 架构,不要再次运行:

scripts/mihomo-tun-migrate.py prepare

该脚本只用于从旧的独立 HTTP 代理部署首次迁移到 TUN sidecar。它会检测当前结构并拒绝重复迁移。

日常换节点只执行本手册第 1 至第 12 节。不要重新创建 CPA 容器、修改宿主机默认路由或开启服务器全局 TUN。

14. 官方配置参考

← 全部文章