AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Mihomo Proxy Deployment

skill-visol-456-mihomo-skill-public-mihomo-skill-public · by Visol-456

>-

No reviews yet
0 installs
22 views
0.0% view→install

Install

$ agentstack add skill-visol-456-mihomo-skill-public-mihomo-skill-public

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access Used
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-visol-456-mihomo-skill-public-mihomo-skill-public)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
1mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Mihomo Proxy Deployment? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Mihomo Proxy Deployment

Deploy mihomo (clash-meta) as a standalone systemd service on headless Linux, typically after migrating from a GUI frontend (clash-verge-rev).

When to use

  • User wants to drop the desktop and run the proxy as a background service
  • TUN-mode transparent proxy (旁路由 / gateway) on headless Ubuntu/Debian
  • Migrating from clash-verge / CFW to standalone systemd-managed mihomo
  • Deploying a secondary mihomo on another server that mirrors the primary's rules — HTTP/S proxy mode for containers, no TUN, no fake-ip DNS

Fresh Install from Scratch

Use this path when there is no existing mihomo/Clash Verge installation to migrate. It covers download, first config, systemd startup, config changes, and restart.

0. Ask for subscription and secret before deploying

Before downloading or writing config, ask the user for:

  • The subscription URL, for example

https:///api/v1/client/subscribe?token=.

  • The external-controller secret they want to use for YACD/API access.

Do not invent a subscription URL or reuse a token from another deployment. If the user has not provided either value, ask again before proceeding.

1. Download and install the latest stable mihomo

Ubuntu/Debian example:

curl -sL https://api.github.com/repos/MetaCubeX/mihomo/releases/latest -o /tmp/mihomo-release.json
MIHOMO_VERSION=$(python3 -c "import json; print(json.load(open('/tmp/mihomo-release.json'))['tag_name'])")
echo "$MIHOMO_VERSION"

# Select the architecture and instruction-set-compatible asset before downloading.
case "$(uname -m)" in
  x86_64)
    MIHOMO_BASE="linux-amd64"
    if ! grep -qE 'avx2|bmi' /proc/cpuinfo; then
      echo "CPU lacks AVX2/BMI; selecting linux-amd64-compatible"
      MIHOMO_BASE="linux-amd64-compatible"
    fi
    ;;
  aarch64)
    MIHOMO_BASE="linux-arm64"
    ;;
  *)
    echo "Unsupported architecture: $(uname -m)" >&2
    exit 1
    ;;
esac
echo "Selected asset base: ${MIHOMO_BASE}"

cd /tmp
MIHOMO_ASSET="mihomo-${MIHOMO_BASE}-${MIHOMO_VERSION}.gz"
curl -L -o "${MIHOMO_ASSET}" \
  "https://github.com/MetaCubeX/mihomo/releases/download/${MIHOMO_VERSION}/${MIHOMO_ASSET}"
gunzip -k "${MIHOMO_ASSET}"
file "mihomo-${MIHOMO_BASE}-${MIHOMO_VERSION}"

sudo install -m 0755 "/tmp/mihomo-${MIHOMO_BASE}-${MIHOMO_VERSION}" /usr/bin/mihomo
/usr/bin/mihomo -v

The check above handles the common cases: x86_64 selects linux-amd64 when the CPU has AVX2/BMI, linux-amd64-compatible when it does not, and aarch64 selects linux-arm64. Verify the downloaded binary with /usr/bin/mihomo -v before replacing anything.

2. Create the first config

sudo mkdir -p /etc/mihomo
sudo chmod 700 /etc/mihomo

Minimal standalone config with a subscription provider:

sudo tee /etc/mihomo/config.yaml >/dev/null "

dns:
  enable: true
  listen: 0.0.0.0:1053
  ipv6: true
  default-nameserver:
    - 114.114.114.114
    - 223.5.5.5
  enhanced-mode: fake-ip
  fake-ip-range: 198.18.0.1/16
  nameserver:
    - https://223.5.5.5/dns-query
    - https://doh.pub/dns-query

proxy-providers:
  MyProvider:
    type: http
    url: "https:///api/v1/client/subscribe?token="
    interval: 1440
    health-check:
      enable: true
      url: http://www.gstatic.com/generate_204
      interval: 300

proxy-groups:
  - name: 节点选择
    type: select
    proxies:
      - 自动选择
      - DIRECT
    use:
      - MyProvider
  - name: 自动选择
    type: url-test
    url: http://www.gstatic.com/generate_204
    interval: 300

rules:
  - MATCH,节点选择
EOF

sudo chmod 600 /etc/mihomo/config.yaml
mihomo -t -d /etc/mihomo

Replace ` with the user-provided token. For a richer example with TUN, GEOIP/GEOSITE rules, and more DNS options, start from references/standalone-config.yaml or references/rules-template.yaml`, then replace the subscription URL and secret.

3. Run it as a systemd service

sudo tee /etc/systemd/system/mihomo.service >/dev/null "

5. Install YACD management panel

Deploy YACD so the user can inspect nodes, switch proxy groups, and reload through a browser:

sudo docker run -d --restart unless-stopped --name yacd -p 9099:80 haishanh/yacd:latest

Then tell the user to open:

http://:9099

In YACD settings use:

  • API URL: http://:9097
  • Secret: the external-controller secret collected in step 0

If Docker is not installed, install Docker first or serve the YACD static files with nginx/caddy. The API URL and secret settings are the same.

6. Modify the config later

Always edit /etc/mihomo/config.yaml as the source of truth:

sudo cp /etc/mihomo/config.yaml /etc/mihomo/config.yaml.bak
sudo nano /etc/mihomo/config.yaml
sudo chmod 600 /etc/mihomo/config.yaml
mihomo -t -d /etc/mihomo

For most changes, reload without restarting:

sudo systemctl reload mihomo
# Equivalent to: sudo kill -HUP $(pgrep -x mihomo)

Or reload through the API:

SECRET=$(sudo grep 'secret:' /etc/mihomo/config.yaml | sed 's/secret: "*//' | sed 's/"$//')
curl -s -X PUT "http://127.0.0.1:9097/configs?force=true" \
  -H "Authorization: Bearer $SECRET" \
  -H "Content-Type: application/json" \
  -d '{"path":"","payload":""}'

7. Restart mihomo

Use a full restart when the change touches TUN, sniffing, listeners, ports, or static settings that reload cannot apply:

sudo systemctl restart mihomo

For a manually managed nohup instance:

sudo pkill -x mihomo || true
sudo nohup /usr/bin/mihomo -d /etc/mihomo > ~/nohup.out 2>&1 &

After restart, verify with systemctl status mihomo, ss -tlnp, and python3 scripts/check_mihomo.py "".

Migrate from Clash Verge

1. Locate the existing config

The clash-verge data lives at:

~/.local/share/io.github.clash-verge-rev.clash-verge-rev/

Key files:

| File | What it holds | |------|---------------| | config.yaml | Ports, TUN, allow-lan, external-controller, mode | | verge.yaml | Verge-specific settings (TUN toggle, core type) | | profiles.yaml | Subscription list, selected nodes, merge/rules overrides | | profiles/.yaml | Downloaded subscription (proxies + groups + rules) | | profiles/.yaml | Custom prepend/append rules |

Check clash_core: in verge.yaml — if it's not mihomo, the binary name differs.

2. Extract the subscription URL

From profiles.yaml:

current: 
items:
- uid: 
  type: remote
  url: 
  selected:
  - name: 节点选择
    now: 

3. Build the standalone config

Create /etc/mihomo/config.yaml with:

  • Core settings: ports, allow-lan: true, mode: rule, external-controller
  • TUN block: enable: true, stack: gvisor, auto-route: true
  • DNS: fake-ip mode, copied from subscription
  • proxy-providers: one type: http entry per subscription URL
  • proxy-groups: select + url-test groups, reference provider via use:
  • rules: custom rules + GEOSITE/GEOIP + MATCH fallback

4. Create systemd service

[Unit]
Description=Mihomo (Clash Meta) - Transparent Proxy
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/bin/mihomo -d /etc/mihomo
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
User=root

[Install]
WantedBy=multi-user.target

5. Deploy

sudo mkdir -p /etc/mihomo
sudo cp config.yaml /etc/mihomo/
sudo cp mihomo.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now mihomo

6. Verify

systemctl status mihomo
ss -tlnp | grep -E "mihomo|789|9097"
journalctl -u mihomo -f

7. CORS Configuration for Management Panels (YACD / Metacubexd / Zashboard)

When accessing a mihomo management panel from a different machine on the LAN, the browser blocks API requests due to CORS. The panel page loads fine but shows "fail to connect" when trying to reach the mihomo API.

Root cause: external-controller-cors.allow-origins only includes local origins (tauri://localhost, http://tauri.localhost, https://yacd.metacubex.one) by default — not your panel's URL.

Fix — add the panel URL to the allow list:

# 1. Check current CORS config
grep -A5 "external-controller-cors" /etc/mihomo/config.yaml

# 2. Add the panel URL (replace with your actual panel URL)
sudo sed -i '/^  - https:\/\/yacd.metacubex.one/a\  - http://:9099' /etc/mihomo/config.yaml

# 3. Reload mihomo (HUP signal, no restart needed)
sudo kill -HUP $(pgrep -x mihomo)

Panel deployment options:

# YACD (port 9099)
sudo docker run -d --restart unless-stopped --name yacd -p 9099:80 haishanh/yacd:latest

# Metacubexd (port 9098)
sudo docker run -d --restart unless-stopped --name metacubexd -p 9098:80 ghcr.io/metacubex/metacubexd:latest

In the panel's settings page, after accessing it at http://::

  • API URL: http://:9097 (e.g. http://:9097)
  • Secret: from /etc/mihomo/config.yaml

Don't set allow-origins: ["*"] — defeats CORS security against malicious websites on the LAN.

Runtime API Operations

Once mihomo is running, query and control proxies through the REST API on external-controller (default 127.0.0.1:9097).

Authentication

The API requires Authorization: Bearer header. Find the secret in the active config:

| Config type | Path | How to read | |-------------|------|-------------| | Standalone (-d /etc/mihomo) | /etc/mihomo/config.yaml | sudo grep 'secret:' /etc/mihomo/config.yaml \| sed 's/secret: "*//' \| sed 's/"$//' | | Clash Verge | ~/.local/share/io.github.clash-verge-rev.clash-verge-rev/config.yaml | grep 'secret:' ~/.local/share/io.github.clash-verge-rev.clash-verge-rev/config.yaml |

YAML quoting: secret: "" — the quotes are YAML syntax, NOT part of the value. Strip them when constructing the Bearer token.

Test latency to a specific target URL from each proxy node

Use the /proxies//delay endpoint with a custom URL (not just the health check gstatic URL) to find the fastest node for a particular service:

# Test a single node
curl -s --max-time 5 -H "Authorization: Bearer " \
  "http://127.0.0.1:9097/proxies//delay?url=https://opencode.ai&timeout=5000"
# → {"delay":329}

To test multiple nodes in sequence (parallel calls can't be done in one foreground terminal call; iterate instead):

for p in "-GPT" "" ""; do
  encoded=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$p'))")
  result=$(curl -s --max-time 5 -H "Authorization: Bearer " \
    "http://127.0.0.1:9097/proxies/$encoded/delay?url=https://opencode.ai&timeout=5000")
  echo "$p => $result"
done

The delay result is in milliseconds. Compare across nodes to route a given service to its fastest proxy.

Query proxy state

Get all proxy groups and their current selection:

# Safer: write to temp file, then parse separately (see Pitfalls below)
curl -s -H "Authorization: Bearer " http://127.0.0.1:9097/proxies -o /tmp/proxies.json
python3 -c "
import json
d = json.load(open('/tmp/proxies.json'))
for k, v in d['proxies'].items():
    t = v.get('type')
    if t in ('Selector', 'URLTest'):
        print(f'  {k:30s} [{t:10s}] now={v.get(\"now\",\"?\")}')
"

### Check subscription remaining data from live API

**Do NOT rely on Clash Verge's `profiles.yaml` `extra` field** (upload/download/total) for current subscription status — this data is cached at profile-update time and goes stale. The standalone config (`-d /etc/mihomo`) with `proxy-providers` updates independently.

The correct source is the **live proxy node names** from the `/proxies` API. Subscription providers encode remaining traffic into node names like:

剩余流量:406.31 GB 套餐到期:长期有效


To read these from the API:

```bash
curl -s -H "Authorization: Bearer " http://127.0.0.1:9097/proxies -o /tmp/proxies.json
python3 -c "
import json
d = json.load(open('/tmp/proxies.json'))
for name in d.get('proxies', {}):
    if '流量' in name or '剩余' in name or '套餐' in name or 'traffic' in name.lower():
        print(f'  {name}')
"

When users report data consumption ("一天跑了20G"), check the API node names first to determine the actual plan total and remaining. The subscription's billing panel (管理面板) and per-proxy history[] are the authoritative and opaque sources — mihomo local counters alone cannot explain airport billing. Cross-reference with /proc/net/dev for the Mihomo NIC for cumulative host-level traffic, and with /connections API by traffic volume to identify bandwidth-heavy processes (see Traffic Consumption Forensics below).


Key fields in the response:

| Field | Meaning |
|-------|---------|
| `proxies..type` | Group type: `Selector`, `URLTest`, `Vmess`, `Direct`, etc. |
| `proxies..now` | Currently selected node (for Selector) or auto-picked node (URLTest) |
| `proxies..all` | Array of available node names in this group |
| `proxies..type` = `URLTest` | Auto-picks lowest-latency node from its pool |

### URLTest 低延迟 ≠ 高带宽(大文件下载要手动切节点)

URLTest/自动选择 按**延迟**挑节点,不按带宽。一个 77ms 的节点可能带宽烂到
50KB/s(实测: 延迟77ms,走它下载 PyPI 大包 40 秒只下了 4KB;
手动切到  后立刻 16MB/s)。

**症状**:`pip install` / 大文件下载通过代理龟速,但节点延迟测出来很低。

**诊断流程**:
1. `curl -x http://127.0.0.1:7897 -so /dev/null -w '%{speed_download}' --max-time 12 `
   直接测代理链路真实带宽(小页面测速不准,测 wheel/zip 之类的大文件)
2. 对比直连速度:慢 = 节点带宽问题;代理快 = 规则问题(域名被直连)

**修复**:手动切换组内节点再测:
```bash
curl -s -X PUT -H "Authorization: Bearer " \
  -d '{"name":""}' http://127.0.0.1:9097/proxies/节点选择

装完大件后可切回 自动选择 恢复自动。

通用教训:给容器/服务器拉大文件(pip、模型、镜像)前,先花 10 秒测 代理带宽;下载完记得把组切回自动选择。

Switch a proxy group node

PUT to /proxies/ with the desired node:

curl -s -X PUT \
  -H "Authorization: Bearer " \
  -H "Content-Type: application/json" \
  -d '{"name":""}' \
  http://127.0.0.1:9097/proxies/节点选择

Reload entire configuration (without sudo)

Use PUT /configs?force=true with {"path":"","payload":""} to reload the current on-disk config without needing sudo access to /etc/mihomo/config.yaml:

import urllib.request, json

secret = ""  # read from config at runtime

req = urllib.request.Request(
    "http://127.0.0.1:9097/configs?force=true",
    data=json.dumps({"path": "", "payload": ""}).encode(),
    headers={
        "Authorization": f"Bearer {secret}",
        "Content-Type": "application/json"
    },
    method="PUT"
)
resp = urllib.request.urlopen(req, timeout=5)
# 204 No Content = success

Limitations:

  • This reloads the config mihomo is already using. It does not write

/etc/mihomo/config.yaml; edit the file first for changes that must survive a restart.

  • If you want to apply a YAML string without writing it to disk, put the YAML

text in payload instead of an empty string. Remember that in-memory changes are lost on restart.

  • If proxy-providers are enabled, providers download fresh nodes during

reload.

  • TUN device may log device or resource busy on reload if the old TUN

device is still active. Verify with journalctl -u mihomo afterward.

Common proxy groups layout

From a typical subscription-based config:

  • 节点选择 — main Selector; can point to "自动选择" (URLTest auto) or a specific node
  • 自动选择 — URLTest group; auto-picks best node by latency
  • GLOBAL — fallback group (often set to DIRECT)
  • Service-specific groups (ChatGPT, Gemini etc.) — often point back to 节点选择

Intermittent failure pattern ("comes back after a while")

If blocked sites fail for a few minutes then recover on their own, the root cause is almost always proxy nodes — not routing or DNS:

  • URL-test interval too long: interval: 600 = checks every 10 minutes.

If the current node dies between checks, all traffic fails until the next check picks a different (working) node.

  • Health check not fast enough: proxy-provider's health-check.interval

defaults to 3

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.