CPA Mihomo TUN 节点更换手册
CPA Mihomo TUN 节点更换手册
适用范围
本手册适用于当前服务器已经完成以下部署的情况:
- CPA Plus 位于
/opt/cpaplus-private; - Mihomo 配置位于
/opt/mihomo-private; - CPA 与 Mihomo 由
/opt/cpaplus-private/compose.yaml统一管理; - Mihomo 使用
network_mode: service:cpaplus; cpa-tun已开启,并只接管 CPA 使用的 UID 1000;- CPAMP 位于
/opt/cpamp-private。
本流程只更换 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
继续操作前应确认:
- CPA 和 Mihomo 都是
healthy; 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:
- 只是把一个节点换成另一个 Mihomo 支持的节点;
nodes.yaml中仍只有一个节点;- 节点不依赖另一个代理节点;
- 所有 CPA 流量仍然走
CPA-UPSTREAM; - 节点所需信息都能直接写在节点对象中。
Hysteria2、VMess、VLESS、Trojan、Shadowsocks、TUIC、HTTP 和 SOCKS5 之间的普通单节点切换,都属于这种情况。TUN、DNS、UID 路由和 Compose 不因节点 type 改变。
以下情况需要另外评估或修改配置:
- 在
nodes.yaml中放入多个节点,并希望固定默认节点或增加自动选择、故障转移; - 从本地文件切换为订阅 URL,需要把 provider 的
type改为http并设置更新策略; - 节点使用
dialer-proxy,需要确保它依赖的节点或代理组同时存在; - 节点引用证书、私钥或其他外部文件,需要把文件安全放入已挂载目录并核对容器内路径;
- 希望不同目标域名走不同节点,需要修改
proxy-groups和rules; - UDP 节点无法连接,需要核对服务器、云平台和上游是否允许 UDP,而不是盲目修改 TUN。
当前部署没有 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. 执行业务验证
使用平时的客户端分别发起一个最小请求:
- 调用需要验证的 Gemini 模型;
- 调用需要验证的 Claude 模型;
- 确认没有地区限制、鉴权、超时或
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。