#!/usr/bin/env bash
# WSL 2 Ubuntu: 独立 Mihomo TUN 安装器,不依赖 Windows Clash。
# 用法: sudo bash mihomo-wsl-subscription.sh [install|start|stop|restart|status|logs|check]
# 默认使用 https://ghproxy.imciel.com/;可选 GH_MIRROR=direct 使用 GitHub 官方
set -Eeuo pipefail
umask 077
info() { printf '\033[1;34m[信息]\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[提醒]\033[0m %s\n' "$*" >&2; }
die() { printf '\033[1;31m[错误]\033[0m %s\n' "$*" >&2; exit 1; }
need_root() { [[ "$(id -u)" -eq 0 ]] || die '请用 sudo bash mihomo-wsl-subscription.sh 运行'; }
COMMAND="${1:-install}"
case "$COMMAND" in
start|stop|restart|status|logs|check)
case "$COMMAND" in
start|restart|stop)
need_root
# systemd 的 ExecStartPre / ExecStopPost 自动切换 DNS。
systemctl "$COMMAND" mihomo
;;
status) systemctl status mihomo --no-pager || true ;;
logs) journalctl -u mihomo -n 80 --no-pager ;;
check)
unset http_proxy https_proxy all_proxy HTTP_PROXY HTTPS_PROXY ALL_PROXY no_proxy NO_PROXY || true
systemctl is-active --quiet mihomo || die 'Mihomo 服务未运行'
ip link show mihomo-tun >/dev/null 2>&1 || die 'TUN 网卡不存在'
printf 'HTTP 代理出口:'
curl --noproxy '' -4 -x http://127.0.0.1:7890 --connect-timeout 8 --max-time 25 -fsS https://api.ipify.org || die 'HTTP 代理联网失败'
printf '\nTUN 透明代理出口:'
curl --noproxy '*' -4 --connect-timeout 8 --max-time 25 -fsS https://api.ipify.org || die 'TUN 联网失败'
printf '\n'
;;
esac
exit 0
;;
install)
need_root
# 不使用 Windows Clash 或此前留在 Shell 中的 HTTP/SOCKS 环境代理。
unset http_proxy https_proxy all_proxy HTTP_PROXY HTTPS_PROXY ALL_PROXY no_proxy NO_PROXY || true
;;
*) die '用法: sudo bash mihomo-wsl-subscription.sh [install|start|stop|restart|status|logs|check]' ;;
esac
# 仅安装到 Ubuntu;适用于 WSL 2 Ubuntu 或原生 Ubuntu。
[[ -r /etc/os-release ]] || die '无法识别系统版本'
. /etc/os-release
[[ "${ID:-}" == ubuntu ]] || die "这是 Ubuntu 专用版,当前系统:${PRETTY_NAME:-unknown}"
IS_WSL=0
if grep -qiE 'microsoft|wsl' /proc/sys/kernel/osrelease 2>/dev/null; then
IS_WSL=1
fi
# 修改 wsl.conf 的现有分组,避免重复 [network]、[boot]。
ini_set() {
python3 - "$1" "$2" "$3" <<'PY_INI'
from pathlib import Path
import re, sys
section, key, value = sys.argv[1:]
p = Path('/etc/wsl.conf')
s = p.read_text() if p.exists() else ''
m = re.search(r'(?im)^[ \t]*\[' + re.escape(section) + r'\][ \t]*$', s)
if m:
end_match = re.search(r'(?m)^[ \t]*\[', s[m.end():])
end = m.end() + end_match.start() if end_match else len(s)
body = s[m.end():end]
pat = re.compile(r'(?im)^[ \t]*' + re.escape(key) + r'[ \t]*=.*$')
if pat.search(body):
body = pat.sub(key + '=' + value, body)
else:
body = body.rstrip('\n') + '\n' + key + '=' + value + '\n'
s = s[:m.end()] + body + s[end:]
else:
s = s.rstrip() + f'\n\n[{section}]\n{key}={value}\n'
p.write_text(s)
PY_INI
}
if [[ ! -d /run/systemd/system ]]; then
if [[ "$IS_WSL" == 1 ]]; then
if [[ -f /etc/wsl.conf && ! -f /etc/wsl.conf.before-mihomo ]]; then
cp -a /etc/wsl.conf /etc/wsl.conf.before-mihomo
fi
command -v python3 >/dev/null || die "请先安装 python3:sudo apt-get install -y python3"
ini_set boot systemd true
die $'已为 WSL Ubuntu 写入 [boot] systemd=true。请在 Windows PowerShell 执行:wsl --shutdown,然后重新进入 Ubuntu,再运行本脚本。'
fi
die '请先在 Ubuntu 启用 systemd,当前环境未检测到 systemd。'
fi
info "检测系统:${PRETTY_NAME},WSL=${IS_WSL};安装依赖..."
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y \
ca-certificates curl gzip python3 python3-yaml iproute2 iptables nftables kmod
if [[ ! -c /dev/net/tun ]]; then
modprobe tun 2>/dev/null || true
fi
[[ -c /dev/net/tun ]] || die '找不到 /dev/net/tun,请检查 WSL2 内核是否启用 TUN 设备'
case "$(dpkg --print-architecture)" in
amd64) ARCH_TAG='linux-amd64-compatible' ;;
arm64) ARCH_TAG='linux-arm64' ;;
*) die "暂不支持该 CPU 架构:$(dpkg --print-architecture)" ;;
esac
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
# GitHub 访问优化:官方 API 用于获取可靠的资产哈希;如果无法连接 API,
# 回退到已从官方 GitHub Release 核对过的固定版本与 SHA256。
# Release 二进制优先通过可选国内镜像下载,且总是验证 SHA256。
PINNED_VERSION='v1.19.32'
case "$ARCH_TAG" in
linux-amd64-compatible)
PINNED_SHA256='ba3ce607747a07f948fc35780e108a4a7c7f552a38b9bd4d115f313ebcb89c20'
;;
linux-arm64)
PINNED_SHA256='9dd862e28b46ff7d775f169cceebc28deccaa0a9e804237d421cd2571e0caba0'
;;
esac
info '获取 Mihomo 官方最新稳定版 Release 信息(失败则使用预校验版本)...'
if curl --fail --silent --show-error --location --retry 1 \
--connect-timeout 6 --max-time 18 \
-H 'Accept: application/vnd.github+json' \
'https://api.github.com/repos/MetaCubeX/mihomo/releases/latest' \
-o "$TMP_DIR/release.json"; then
if ! python3 - "$TMP_DIR/release.json" "$ARCH_TAG" > "$TMP_DIR/asset.txt" <<'PY_RELEASE'
import json, sys
with open(sys.argv[1], encoding='utf-8') as f:
data = json.load(f)
tag = sys.argv[2]
assets = data.get('assets', [])
matches = [a for a in assets if a.get('name', '').startswith('mihomo-' + tag + '-')
and a.get('name', '').endswith('.gz')]
if len(matches) != 1:
sys.exit('找不到唯一的 ' + tag + ' .gz 官方安装包')
asset = matches[0]
print(asset['browser_download_url'])
print(asset.get('digest') or '')
print(asset['name'])
PY_RELEASE
then
warn '官方 API 返回的数据无法解析,使用预校验版本'
: > "$TMP_DIR/asset.txt"
fi
else
warn 'GitHub API 暂时无法连接,使用预校验版本(无需连接 GitHub API)'
: > "$TMP_DIR/asset.txt"
fi
mapfile -t ASSET < "$TMP_DIR/asset.txt"
ASSET_URL="${ASSET[0]:-}"
ASSET_DIGEST="${ASSET[1]:-}"
ASSET_NAME="${ASSET[2]:-}"
if [[ ! "$ASSET_DIGEST" =~ ^sha256:[a-fA-F0-9]{64}$ ]]; then
if [[ -n "$ASSET_URL" ]]; then
warn '官方 API 未提供有效 SHA256,回退到带固定 SHA256 的预校验版本'
fi
ASSET_NAME="mihomo-${ARCH_TAG}-${PINNED_VERSION}.gz"
ASSET_URL="https://github.com/MetaCubeX/mihomo/releases/download/${PINNED_VERSION}/${ASSET_NAME}"
ASSET_DIGEST="sha256:${PINNED_SHA256}"
fi
[[ "$ASSET_URL" == https://github.com/MetaCubeX/mihomo/releases/download/* ]] || die '资产链接不是官方 Mihomo Release,已拒绝下载'
EXPECTED_SHA256="${ASSET_DIGEST#sha256:}"
# 默认通过 ghproxy.imciel.com 下载;如果镜像完全不可用,再尝试 GitHub 官方。
# 如需自定义代理:sudo GH_MIRROR=https://镜像域名 bash mihomo-wsl-subscription.sh
# 如需绕过镜像:sudo GH_MIRROR=direct bash mihomo-wsl-subscription.sh
GH_MIRROR="${GH_MIRROR:-https://ghproxy.imciel.com}"
MIRRORS=()
if [[ "$GH_MIRROR" != 'direct' ]]; then
[[ "$GH_MIRROR" == https://* ]] || die 'GH_MIRROR 必须以 https:// 开头,或设为 direct'
MIRRORS+=("${GH_MIRROR%/}")
fi
MIRRORS+=('') # Github 官方兜底
# 使用持久缓存保存未完成的文件,脚本退出后依然能够断点续传。
# 将预期 SHA256 写入缓存名,避免不同版本/内容的文件混淆。
CACHE_DIR='/var/cache/mihomo-installer'
install -d -m 0700 "$CACHE_DIR"
PART_FILE="${CACHE_DIR}/${ASSET_NAME}.${EXPECTED_SHA256}.part"
info "准备下载 ${ASSET_NAME}(ghproxy.imciel.com 优先、可断点续传、SHA256 校验)..."
DOWNLOAD_OK=0
if [[ -s "$PART_FILE" ]] && printf '%s %s\n' "$EXPECTED_SHA256" "$PART_FILE" | sha256sum --check --status; then
info '安装包已完整缓存且 SHA256 校验通过,无需重复下载'
DOWNLOAD_OK=1
else
for MIRROR in "${MIRRORS[@]}"; do
if [[ -n "$MIRROR" ]]; then
URL="${MIRROR}/${ASSET_URL}"
LABEL="$MIRROR"
else
URL="$ASSET_URL"
LABEL='GitHub 官方'
fi
info "尝试下载:${LABEL}"
# 网络失败时保留 .part:下一次尝试或再次运行脚本可继续下载。
# 不设 90 秒总超时;仅在 120 秒持续低于 1 KiB/s 时重试。
for ATTEMPT in 1 2 3 4; do
info "下载尝试 ${ATTEMPT}/4;已缓存 $(stat -c %s "$PART_FILE" 2>/dev/null || echo 0) 字节"
if curl --fail --location --show-error --continue-at - \
--connect-timeout 15 --max-time 0 \
--speed-time 120 --speed-limit 1024 \
--proto '=https' --proto-redir '=https' \
"$URL" -o "$PART_FILE" 2> >(tee "$TMP_DIR/curl-error.log" >&2); then
if printf '%s %s\n' "$EXPECTED_SHA256" "$PART_FILE" | sha256sum --check --status; then
DOWNLOAD_OK=1
info "下载完成,SHA256 校验通过:${LABEL}"
break
fi
warn "${LABEL} 返回的文件 SHA256 不匹配,删除损坏的缓存"
rm -f "$PART_FILE"
else
cat "$TMP_DIR/curl-error.log" >&2
# 部分国内镜像不支持 HTTP Range,无法续传时从头重新下载。
if grep -Eiq 'range|resume|requested range|HTTP server does not seem to support' "$TMP_DIR/curl-error.log"; then
warn '镜像不支持断点续传,清除部分文件,下次从头下载'
rm -f "$PART_FILE"
else
warn "${LABEL} 下载中断,保留已下载内容"
fi
fi
if [[ "$ATTEMPT" -lt 4 ]]; then sleep 3; fi
done
if [[ "$DOWNLOAD_OK" -eq 1 ]]; then break; fi
warn "${LABEL} 下载未完成,尝试下一下载源"
done
fi
[[ "$DOWNLOAD_OK" -eq 1 ]] || die '镜像及官方源下载失败;已保存部分文件,可再次运行本脚本继续下载'
# 仅使用通过 SHA256 验证的下载文件。
cp "$PART_FILE" "$TMP_DIR/mihomo.gz"
gzip -t "$TMP_DIR/mihomo.gz"
gzip -dc "$TMP_DIR/mihomo.gz" > "$TMP_DIR/mihomo"
chmod +x "$TMP_DIR/mihomo"
install -m 0755 "$TMP_DIR/mihomo" /usr/local/bin/mihomo
mihomo -v
# 仅接受一个 Clash/Mihomo HTTPS 订阅,拒绝拼接链接和单节点链接。
SUB_URL="${SUB_URL:-}"
if [[ -z "$SUB_URL" ]]; then
[[ -t 0 ]] || die '非交互运行请设置 SUB_URL'
read -r -s -p '请输入 Clash/Mihomo 订阅地址:' SUB_URL
printf '\n'
fi
printf '%s' "$SUB_URL" > "$TMP_DIR/sub-url"
unset SUB_URL
python3 - "$TMP_DIR/sub-url" "$TMP_DIR/curl.conf" <<'PY_URL'
import sys, json
from pathlib import Path
from urllib.parse import urlsplit
raw=Path(sys.argv[1]).read_text().strip()
try:
u=urlsplit(raw)
if u.scheme!='https' or not u.hostname or u.username or u.password or u.fragment or any(c.isspace() for c in raw) or raw.count('://')!=1:
raise ValueError()
except ValueError:
sys.exit('订阅地址无效:请输入单个完整 HTTPS 链接,不要拼接多个链接')
Path(sys.argv[1]).write_text(raw)
Path(sys.argv[2]).write_text('url = '+json.dumps(raw)+'\n')
PY_URL
info '下载并检测订阅内容...'
if ! curl --config "$TMP_DIR/curl.conf" --fail --silent --location \
--connect-timeout 10 --max-time 60 --max-filesize 10485760 \
--proto '=https' --proto-redir '=https' \
-A 'clash.meta' -D "$TMP_DIR/sub-headers" \
-o "$TMP_DIR/subscription.yaml" 2> "$TMP_DIR/sub-error"; then
die '订阅下载失败:请核对地址、有效期和网络。现有配置未修改'
fi
python3 - "$TMP_DIR" <<'PY_SUB'
import sys, json, re
from pathlib import Path
from datetime import datetime, timezone
import yaml
p=Path(sys.argv[1])
try:
raw=yaml.safe_load((p/'subscription.yaml').read_text(encoding='utf-8-sig'))
if not isinstance(raw,dict) or not isinstance(raw.get('proxies'),list) or not raw['proxies']:
sys.exit('订阅未提供有效 proxies 列表,请使用 Clash/Mihomo 格式订阅(不支持 Base64 节点列表)')
nodes=raw['proxies']; names=set(); counts={}
for n in nodes:
if not isinstance(n,dict) or not all(n.get(k) for k in ('name','type','server','port')):
sys.exit('订阅存在缺失 name/type/server/port 的节点')
if not isinstance(n['name'],str) or n['name'] in names:
sys.exit('订阅节点名称重复或无效')
if not isinstance(n['port'],int) or isinstance(n['port'],bool) or not 1<=n['port']<=65535:
sys.exit('订阅存在无效节点端口')
if n.get('dialer-proxy'):
sys.exit('订阅含链式代理引用,无法独立提取节点;请使用普通节点订阅')
names.add(n['name']); typ=str(n['type']); counts[typ]=counts.get(typ,0)+1
print('有效节点:'+str(len(nodes))+' 个')
print('协议统计:'+', '.join(k+': '+str(v) for k,v in sorted(counts.items())))
# 不打印节点名称、服务器或订阅 URL。
headers=(p/'sub-headers').read_text(errors='replace')
vals=re.findall(r'(?im)^subscription-userinfo:\s*([^\r\n]+)',headers)
if vals:
data=dict((k,int(v)) for k,v in re.findall(r'(upload|download|total|expire)\s*=\s*(\d+)',vals[-1]))
if 'total' in data and 'upload' in data and 'download' in data:
used=data['upload']+data['download']; total=data['total']
print(f'订阅流量:已用 {used/1024**3:.2f} GiB,总量 {total/1024**3:.2f} GiB')
if total>0 and used>=total:
sys.exit('订阅流量已用尽,请更新订阅后重试')
if data.get('expire',0)>0:
expiry=data['expire']
print('订阅到期时间(UTC):'+datetime.fromtimestamp(expiry,timezone.utc).strftime('%Y-%m-%d %H:%M:%S'))
if expiry<=datetime.now(timezone.utc).timestamp():
sys.exit('订阅已过期')
else:
print('订阅未提供流量/到期响应头,仅检测节点内容')
# 只保留节点,忽略订阅内置 DNS、规则、规则集和远程资源。
(p/'subscription.yaml').write_text(json.dumps({'proxies':nodes},ensure_ascii=False,indent=2)+'\n')
cfg={
'mixed-port':7890,'socks-port':7891,'allow-lan':False,
'bind-address':'127.0.0.1','mode':'rule','log-level':'info','ipv6':False,
'proxy-providers':{'subscription':{'type':'http','url':(p/'sub-url').read_text(),
'path':'./providers/subscription.yaml','interval':86400,
'header':{'User-Agent':['clash.meta']},
'health-check':{'enable':True,'url':'https://www.gstatic.com/generate_204','interval':300}}},
'proxy-groups':[{'name':'WSL_AUTO','type':'url-test','use':['subscription'],
'url':'https://www.gstatic.com/generate_204','interval':300}],
'rules':['IP-CIDR,127.0.0.0/8,DIRECT,no-resolve','IP-CIDR,10.0.0.0/8,DIRECT,no-resolve',
'IP-CIDR,172.16.0.0/12,DIRECT,no-resolve','IP-CIDR,192.168.0.0/16,DIRECT,no-resolve',
'MATCH,WSL_AUTO'],
'tun':{'enable':True,'device':'mihomo-tun','stack':'mixed','auto-route':True,
'auto-redirect':True,'auto-detect-interface':True,'strict-route':True,
'dns-hijack':['any:53','tcp://any:53']},
'dns':{'enable':True,'listen':'127.0.0.1:53','ipv6':False,
'enhanced-mode':'fake-ip','fake-ip-range':'198.18.0.1/16',
'fake-ip-filter':['*.lan','*.local','localhost'],
'default-nameserver':['223.5.5.5','1.1.1.1'],
'proxy-server-nameserver':['223.5.5.5','1.1.1.1'],
'nameserver':['https://1.1.1.1/dns-query#WSL_AUTO','https://8.8.8.8/dns-query#WSL_AUTO']}}
(p/'config.yaml').write_text(json.dumps(cfg,ensure_ascii=False,indent=2)+'\n')
except SystemExit:
raise
except Exception:
sys.exit('订阅解析失败:请检查是否为有效的 Clash/Mihomo YAML 配置')
PY_SUB
info '检查订阅节点及候选配置...'
install -d -m 0700 "$TMP_DIR/test-config/providers"
install -m 0600 "$TMP_DIR/config.yaml" "$TMP_DIR/test-config/config.yaml"
install -m 0600 "$TMP_DIR/subscription.yaml" "$TMP_DIR/test-config/providers/subscription.yaml"
if ! mihomo -t -d "$TMP_DIR/test-config" > "$TMP_DIR/config-test.log" 2>&1; then
die 'Mihomo 配置检查失败:订阅节点参数错误或版本不支持。现有配置未修改'
fi
install -d -m 0700 /etc/mihomo/providers
BACKUP=''
if [[ -f /etc/mihomo/config.yaml ]]; then
BACKUP="/etc/mihomo/config.yaml.bak.$(date +%Y%m%d-%H%M%S)"
cp -p /etc/mihomo/config.yaml "$BACKUP"
fi
# 使用独立缓存文件,避免失败时覆盖原订阅缓存。
PROVIDER_FILE="providers/subscription.$(date +%s).yaml"
python3 - "$TMP_DIR/config.yaml" "$PROVIDER_FILE" <<'PY_CACHE'
import json,sys
from pathlib import Path
p=Path(sys.argv[1]); cfg=json.loads(p.read_text())
cfg['proxy-providers']['subscription']['path']='./'+sys.argv[2]
p.write_text(json.dumps(cfg,ensure_ascii=False,indent=2)+'\n')
PY_CACHE
install -m 0600 "$TMP_DIR/subscription.yaml" "/etc/mihomo/$PROVIDER_FILE"
install -m 0600 "$TMP_DIR/config.yaml" /etc/mihomo/config.yaml
info '创建 systemd 服务...'
# Ubuntu 的 resolv.conf 可能是符号链接:恢复时保留链接类型。
cat > /usr/local/sbin/mihomo-wsl-dns <<'DNSHELPER'
#!/usr/bin/env bash
set -e
case "${1:-}" in
up)
rm -f /etc/resolv.conf
printf 'nameserver 127.0.0.1\noptions timeout:2 attempts:2\n' > /etc/resolv.conf
;;
down)
if [[ -e /etc/mihomo/resolv.conf.before-mihomo || -L /etc/mihomo/resolv.conf.before-mihomo ]]; then
rm -f /etc/resolv.conf
cp -a /etc/mihomo/resolv.conf.before-mihomo /etc/resolv.conf
fi
;;
*) exit 2 ;;
esac
DNSHELPER
chmod 0755 /usr/local/sbin/mihomo-wsl-dns
cat > /etc/systemd/system/mihomo.service <<'UNIT'
[Unit]
Description=Mihomo TUN for WSL 2 Ubuntu
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
ExecStartPre=/usr/local/sbin/mihomo-wsl-dns up
ExecStart=/usr/local/bin/mihomo -d /etc/mihomo
ExecStopPost=/usr/local/sbin/mihomo-wsl-dns down
Restart=on-failure
RestartSec=5
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
UNIT
# WSL 环境:禁用自动生成 resolv.conf,避免自动 DNS 隧道绕过本地 DNS。
# 原生 Ubuntu 不修改 /etc/wsl.conf。
info '备份 Ubuntu 的 DNS 配置...'
if [[ ! -e /etc/mihomo/resolv.conf.before-mihomo && ! -L /etc/mihomo/resolv.conf.before-mihomo ]]; then
if [[ -e /etc/resolv.conf || -L /etc/resolv.conf ]]; then
cp -a /etc/resolv.conf /etc/mihomo/resolv.conf.before-mihomo
fi
fi
if [[ "$IS_WSL" == 1 ]]; then
if [[ -f /etc/wsl.conf && ! -f /etc/wsl.conf.before-mihomo ]]; then
cp -a /etc/wsl.conf /etc/wsl.conf.before-mihomo
fi
ini_set network generateResolvConf false
fi
systemctl daemon-reload
systemctl enable mihomo
if ! systemctl restart mihomo; then
[[ -z "$BACKUP" ]] || cp -p "$BACKUP" /etc/mihomo/config.yaml
systemctl stop mihomo || true
[[ -z "$BACKUP" ]] || systemctl start mihomo || true
die '服务启动失败,已尝试恢复原配置。使用 sudo journalctl -u mihomo -n 80 查看日志'
fi
sleep 2
if ! systemctl is-active --quiet mihomo; then
systemctl stop mihomo || true
if [[ -n "$BACKUP" ]]; then
cp -p "$BACKUP" /etc/mihomo/config.yaml
systemctl start mihomo || true
fi
die '服务启动后退出,已尝试恢复原配置和 DNS;请查看日志'
fi
info '服务已启动,验证实际联网...'
CHECK_OK=1
ip link show mihomo-tun >/dev/null 2>&1 || CHECK_OK=0
HTTP_IP="$(curl --noproxy '' -4 -x http://127.0.0.1:7890 --connect-timeout 8 --max-time 25 -fsS https://api.ipify.org || true)"
TUN_IP="$(curl --noproxy '*' -4 --connect-timeout 8 --max-time 25 -fsS https://api.ipify.org || true)"
if [[ -n "$HTTP_IP" && -n "$TUN_IP" && "$CHECK_OK" == 1 ]]; then
info "HTTP 出口:$HTTP_IP;TUN 出口:$TUN_IP,请核对是否属于订阅节点"
else
warn '服务运行,但联网验证未通过,不能认定部署成功'
warn '请运行 check,并检查订阅节点可用性和服务日志'
CHECK_OK=0
fi
info '安装及配置写入完成。'
printf '\n'
printf '查看状态:sudo bash %s status\n' "$0"
printf '查看日志:sudo bash %s logs\n' "$0"
printf '检查出口:bash %s check\n' "$0"
printf '停止并恢复旧 DNS:sudo bash %s stop\n' "$0"
printf '启动:sudo bash %s start\n' "$0"
if [[ "$IS_WSL" == 1 ]]; then
warn 'Windows PowerShell 执行 wsl --shutdown,再 wsl -d Ubuntu 以应用 WSL DNS 配置。'
warn '推荐 WSL 使用 NAT 模式,禁用 Windows 的 autoProxy,并清除旧的 HTTP_PROXY 变量。'
fi
warn '这不是防泄漏 Kill Switch:启动前或 TUN 故障时可能直连;节点域名的引导 DNS 也可能直连。'
warn '如果 .bashrc 内曾设 Windows Clash 的 http_proxy/https_proxy,请手动删除它们。'
[[ "${CHECK_OK:-1}" == 1 ]] || exit 1