这篇文章记录的是我在云服务器上使用 Mihomo 的一套实际流程。我的做法很简单从本地电脑上的 Clash 软件中取出已经在使用的config内容复制到云服务器上交给 Mihomo 运行随后补充一段tun配置再配合systemd、控制 API 和 shell 脚本完成日常使用。0. 我的使用场景我在云服务器上主要做这些事情访问外网资源拉取 GitHub 代码使用codex等 agent cli 工具安装 npm / pnpm 依赖给终端和开发工具提供代理环境在多个节点之间做切换和测速围绕这些需求我整理出了一套比较顺手的工作流。下面按顺序执行即可。1. 安装 Mihomosudoaptupdatesudoaptinstall-ycurlwgetgzippython3 ca-certificatesARCH$(dpkg --print-architecture)case$ARCHinamd64)KEYWORDmihomo-linux-amd64-compatible;;arm64)KEYWORDmihomo-linux-arm64;;*)echo不支持的架构:$ARCHexit1;;esacURL$(curl -fsSL https://api.github.com/repos/MetaCubeX/mihomo/releases/latest | python3 -c import json,sys keywordsys.argv[1] datajson.load(sys.stdin) for a in data[assets]: ua[browser_download_url] if keyword in u and u.endswith(.gz): print(u) break $KEYWORD)echo$URLwget-O/tmp/mihomo.gz$URLgunzip-f/tmp/mihomo.gzsudoinstall-m755/tmp/mihomo /usr/local/bin/mihomo /usr/local/bin/mihomo-v2. 放入 config 配置sudomkdir-p/etc/mihomosudonano/etc/mihomo/config.yaml把你本地 Clash 软件里的完整config.yaml内容粘进去。然后确认配置里有下面这几段没有就补进去external-controller:127.0.0.1:9090secret:initial-secretprofile:store-selected:truetun:enable:truestack:mixedauto-route:trueauto-redirect:trueauto-detect-interface:true保存退出。3. 配置 systemdsudotee/etc/systemd/system/mihomo.service/dev/nullEOF [Unit] DescriptionMihomo Afternetwork-online.target Wantsnetwork-online.target [Service] Typesimple Userroot ExecStart/usr/local/bin/mihomo -d /etc/mihomo -f /etc/mihomo/config.yaml Restartalways RestartSec3 AmbientCapabilitiesCAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CapabilityBoundingSetCAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW NoNewPrivilegesfalse [Install] WantedBymulti-user.target EOF4. 启动sudosystemctl daemon-reloadsudosystemctlenable--nowmihomo5. 测试sudosystemctl status mihomo --no-pagercurl-HAuthorization: Bearer initial-secret\http://127.0.0.1:9090/versioncurl-HAuthorization: Bearer initial-secret\http://127.0.0.1:9090/groupjournalctl-umihomo-n50--no-pager6. 写入切换节点脚本sudotee/usr/local/bin/mihomo-switch/dev/nullEOF #!/usr/bin/env bash set -euo pipefail CONTROLLER${CONTROLLER:-http://127.0.0.1:9090} SECRET${SECRET:-initial-secret} GROUP_NAME${GROUP_NAME:-⚓ 节点选择} TEST_URL${TEST_URL:-http://cp.cloudflare.com} TIMEOUT_MS${TIMEOUT_MS:-5000} PARALLEL${PARALLEL:-8} urlencode() { python3 -c import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe)) $1 } get_group_json() { local group_enc group_enc$(urlencode $GROUP_NAME) curl -fsS \ -H Authorization: Bearer $SECRET \ $CONTROLLER/proxies/$group_enc } json_get_now() { python3 -c import sys, json; print(json.load(sys.stdin).get(now, )) } json_get_all() { python3 -c import sys, json data json.load(sys.stdin) for item in data.get(all, []): print(item) } switch_node() { local target$1 local group_enc group_enc$(urlencode $GROUP_NAME) curl -fsS -X PUT \ $CONTROLLER/proxies/$group_enc \ -H Authorization: Bearer $SECRET \ -H Content-Type: application/json \ -d $(printf {name:%s} $target) /dev/null } should_skip() { local name$1 [[ $name DIRECT ]] return 0 [[ $name REJECT ]] return 0 [[ $name *官网* ]] return 0 [[ $name *自动选择* ]] return 0 [[ $name *直连* ]] return 0 return 1 } GROUP_JSON$(get_group_json) CURRENT$(printf %s $GROUP_JSON | json_get_now) mapfile -t RAW_OPTIONS (printf %s $GROUP_JSON | json_get_all) WORKDIR$(mktemp -d) trap rm -rf $WORKDIR EXIT NODES_FILE$WORKDIR/nodes.txt RESULT_FILE$WORKDIR/results.tsv for node in ${RAW_OPTIONS[]}; do if should_skip $node; then continue fi printf %s\n $node $NODES_FILE done export CONTROLLER SECRET TEST_URL TIMEOUT_MS RESULT_FILE cat $NODES_FILE | xargs -I{} -P $PARALLEL bash -c node$1 urlencode() { python3 -c import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe)) $1 } node_enc$(urlencode $node) url_enc$(urlencode $TEST_URL) if resp$(curl -fsS \ -H Authorization: Bearer $SECRET \ $CONTROLLER/proxies/$node_enc/delay?url$url_enctimeout$TIMEOUT_MS 2/dev/null); then delay$(printf %s $resp | python3 -c import sys, json try: data json.load(sys.stdin) d data.get(delay) print(d if isinstance(d, int) else FAIL) except Exception: print(FAIL) ) else delayFAIL fi if [[ $delay FAIL ]]; then sort_key999999 else sort_key$delay fi printf %s\t%s\n $sort_key $node $RESULT_FILE _ {} mapfile -t SORTED_LINES (sort -n $RESULT_FILE) declare -a OPTIONS() declare -a DELAYS() for line in ${SORTED_LINES[]}; do delay$(printf %s $line | cut -f1) node$(printf %s $line | cut -f2-) if [[ $delay 999999 ]]; then delayFAIL else delay${delay}ms fi OPTIONS($node) DELAYS($delay) done echo 当前节点: $CURRENT echo for i in ${!OPTIONS[]}; do idx$((i 1)) if [[ ${OPTIONS[$i]} $CURRENT ]]; then printf %2d) %-12s %s [当前]\n $idx ${DELAYS[$i]} ${OPTIONS[$i]} else printf %2d) %-12s %s\n $idx ${DELAYS[$i]} ${OPTIONS[$i]} fi done echo read -r -p 请输入编号q 退出: CHOICE if [[ $CHOICE q || $CHOICE Q ]]; then exit 0 fi if ! [[ $CHOICE ~ ^[0-9]$ ]]; then echo 输入不是有效编号 exit 1 fi if (( CHOICE 1 || CHOICE ${#OPTIONS[]} )); then echo 编号超出范围 exit 1 fi TARGET${OPTIONS[$((CHOICE - 1))]} if [[ $TARGET $CURRENT ]]; then echo 已是当前节点 exit 0 fi switch_node $TARGET AFTER_JSON$(get_group_json) AFTER$(printf %s $AFTER_JSON | json_get_now) echo 切换后节点: $AFTER if [[ $AFTER ! $TARGET ]]; then exit 2 fi EOFsudochmodx /usr/local/bin/mihomo-switch7. 更换节点mihomo-switch8. 常用命令sudosystemctl restart mihomosudosystemctl status mihomo --no-pagerjournalctl-umihomo-fcurl-HAuthorization: Bearer initial-secret\http://127.0.0.1:9090/group