Install
$ agentstack add skill-visol-456-mihomo-skill-public-mihomo-skill-public ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo 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.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
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 →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-controllersecret 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-controllersecret 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: httpentry 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 busyon 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.
- Author: Visol-456
- Source: Visol-456/mihomo-skill-public
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.