Files
Hermes-Skills/devops/clash-docker-workflow/SKILL.md
T
Hermes Skills Manager 6770bc9b9d Initial commit: Hermes Agent skills collection
- Trading skills (OKX, dividend, lottery, quantitative)
- Creative skills (ASCII art, diagrams, video)
- Development skills (GitHub, debugging, TDD)
- Research skills (arXiv, blog monitoring)
- Productivity skills (email, documents, notes)
- MCP integration skills
- Custom user skills
2026-07-05 02:31:15 -04:00

19 KiB

name, description, tags
name description tags
clash-docker-workflow Deploy and manage a high-performance proxy (Clash/Mihomo) — standalone binary (preferred) or Docker. Includes Web UI dashboards and Hermes Telegram proxy integration.
proxy
clash
mihomo
networking

Clash / Mihomo Proxy Workflow

Deploy and manage a high-performance proxy (Clash/Mihomo) for routing traffic through China's GFW.

Two deployment modes: standalone binary (preferred by user) or Docker.


Mode 1: Standalone Binary (preferred)

Install

# Download mihomo — use GitHub mirror in China (direct github.com times out)
cd ~/clash
curl -L "https://ghfast.top/https://github.com/MetaCubeX/mihomo/releases/download/v1.19.8/mihomo-linux-amd64-v1.19.8.gz" -o mihomo.gz
gunzip -f mihomo.gz && chmod +x mihomo
./mihomo -v  # verify

Configure

# 1. Create config dir
mkdir -p ~/clash/config

# 2. Download subscription — MUST set User-Agent or WAF blocks (403)
curl -L -H "User-Agent: clash-verge/v2.0.0" \
  "SUBSCRIPTION_URL" -o ~/clash/config/config.yaml

# 3. Inject required params if missing
#    Raw subscriptions often omit these:
cat >> ~/clash/config/config.yaml << 'EOF'
mixed-port: 7890
allow-lan: true
mode: rule
log-level: info
external-controller: 0.0.0.0:9090
EOF

Pre-download GeoIP data (critical for China servers)

mihomo downloads GeoIP/Geosite DBs from GitHub on first start. If GitHub is blocked, it hangs forever at "Can't find MMDB, start download". Pre-download via mirror:

cd ~/clash/config
curl -L "https://ghfast.top/https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.db" -o geoip.db
curl -L "https://ghfast.top/https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb" -o geoip.metadb
curl -L "https://ghfast.top/https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.db" -o geosite.db

Run

# Foreground (test, Ctrl+C to stop)
~/clash/mihomo -d ~/clash/config

# Background — use Hermes terminal background mode:
# terminal(background=true, command="cd ~/clash && exec ./mihomo -d config 2>&1")
# Then check: terminal(command="ss -tlnp | grep -E '7890|9090'")

⚠️ terminal(background=true) is NOT persistent. The mihomo process dies when:

  • The gateway session ends or restarts
  • The Hermes turn that started it finishes and gets garbage-collected
  • The system reboots

For production use, always use systemd (see references/systemd-setup.md). Only use terminal(background=true) for quick tests.

Verify

curl -x http://127.0.0.1:7890 https://api.telegram.org  # should return HTML/JSON (not timeout)
curl http://127.0.0.1:9090/version                        # {"meta":true,"version":"v1.19.8"}

Mode 2: Docker

docker-compose.yml

services:
  clash:
    image: orzly/mihomo:latest
    container_name: clash
    restart: always
    volumes:
      - ./config:/root/.config/mihomo
    ports:
      - "7890:7890"
      - "7891:7891"
      - "9090:9090"
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
  clash-ui:
    image: yaknet/yacd:latest
    container_name: clash-ui
    restart: always
    ports:
      - "3412:80"
    environment:
      - API_URL=http://clash:9090
    depends_on:
      - clash

Workflow

  1. mkdir -p ~/clash/config
  2. Place docker-compose.yml in ~/clash/
  3. Download subscription to ~/clash/config/config.yaml
  4. cd ~/clash && docker compose up -d
  5. UI at http://localhost:3412

Web UI (Dashboard)

Option A: Online dashboard (zero setup, LAN only)

Use a hosted dashboard pointing to your mihomo API:

⚠️ Mixed content blocker: Online dashboards are served over HTTPS. If your mihomo API is HTTP (typical for LAN), browsers block the fetch due to mixed-content policy. The dashboard loads but shows an empty/disconnected state. Fix: use Option B (local deployment).

Download yacd static files and serve locally over HTTP:

cd ~/clash
curl -L "https://ghfast.top/https://github.com/haishanh/yacd/releases/download/v0.3.8/yacd.tar.xz" -o yacd.tar.xz
mkdir -p yacd && tar xf yacd.tar.xz -C yacd --strip-components=1
# Serve on port 9091 (avoids conflict with mihomo API on 9090)
python3 -m http.server 9091 --bind 0.0.0.0 --directory ~/clash/yacd &

Access with API pre-filled: http://<server-ip>:9091/?host=http://<server-ip>:9090

For persistence, add the python HTTP server to the same systemd service as mihomo (see references/systemd-setup.md).

Option C: Docker container (see Mode 2 above)

YACD at http://localhost:3412.


Connecting Applications

Set proxy env vars in ~/.bashrc (Hermes terminal auto-sources it via auto_source_bashrc: true):

# Proxy for all HTTP/HTTPS traffic
export HTTP_PROXY=http://127.0.0.1:7890
export HTTPS_PROXY=http://127.0.0.1:7890
export http_proxy=http://127.0.0.1:7890
export https_proxy=http://127.0.0.1:7890
export ALL_PROXY=socks5://127.0.0.1:7890

# NO_PROXY: local/internal services that must NOT go through proxy
# Include: localhost, local model servers, WireGuard subnet, .local mDNS
export NO_PROXY=localhost,127.0.0.1,192.168.199.100,10.8.0.0/24,*.local

Important: Set BOTH uppercase AND lowercase variants (HTTP_PROXY + http_proxy). Some libraries only read one form.

Routing Specific Services via Mihomo Rules

When you need certain API services to always go through the proxy (even if MATCH rule changes), add explicit DOMAIN-SUFFIX rules to the top of the rules: section in config.yaml:

rules:
    # LongPort / LongBridge API
    - 'DOMAIN-SUFFIX,longportapp.com,BiXin Network'
    - 'DOMAIN-SUFFIX,longport.com,BiXin Network'
    - 'DOMAIN-SUFFIX,longbridge.com,BiXin Network'
    - 'DOMAIN-SUFFIX,longbridge.hk,BiXin Network'
    # OKX 交易所
    - 'DOMAIN-SUFFIX,okx.com,BiXin Network'
    - 'DOMAIN-SUFFIX,okx.me,BiXin Network'
    - 'DOMAIN-SUFFIX,okx.co,BiXin Network'
    - 'DOMAIN-SUFFIX,oklink.com,BiXin Network'
    - 'DOMAIN-SUFFIX,oklink.org,BiXin Network'
    # ... existing rules below ...

Place service-specific rules before the large DIRECT rule block for Chinese services — Mihomo evaluates rules top-down, first match wins.

Mihomo API Hot-Reload (no restart needed)

After editing config.yaml, reload without killing the process:

# Method 1: REST API reload
curl -s -X PUT http://127.0.0.1:9090/configs \
  -H 'Content-Type: application/json' \
  -d '{"path":"/home/openclaw/clash/config/config.yaml"}'

# Method 2: SIGHUP (simpler, same effect)
pkill -HUP -f mihomo

# Method 3: Full restart (only if hot-reload fails)
bash ~/clash/startup.sh restart mihomo

Hermes Proxy Configuration

Hermes supports Telegram proxy via config.yaml:

hermes config set telegram.proxy_url http://127.0.0.1:7890
hermes gateway restart

load_gateway_config() bridges telegram.proxy_url to TELEGRAM_PROXY env var at startup. Verified working.

For full detail see: skill_view(name="clash-docker-workflow", file_path="references/hermes-telegram-proxy.md")/.... Other mirrors: ghproxy.com, mirror.ghproxy.com`.

  • Subscription URL returns 403 (WAF): Many subscription providers block requests without a browser-like User-Agent. Add -H "User-Agent: clash-verge/v2.0.0" to curl. This was the fix for BiXin (sub-route.bxy.re).
  • mihomo hangs at "Can't find MMDB, start download": GeoIP/Geosite DBs need to be pre-downloaded when GitHub is blocked. See "Pre-download GeoIP data" section above. Without these files, mihomo starts but never binds ports.
  • mihomo.gz corrupted on failed download: If file mihomo.gz shows ASCII text instead of gzip, the download failed (GitHub returned 404/HTML). Re-download using mirror.
  • Missing Control Params: A raw subscription file often lacks external-controller or mode. Always ensure these are present in config.yaml for the API/UI to work.
  • Container Communication (Docker only): The UI container uses API_URL=http://clash:9090. This relies on the service name clash in the Docker network.
  • Subscription config format: mihomo (MetaCubeX) uses a slightly different config format than original Clash. Ensure your subscription provider supports mihomo/clash-meta format, or convert.
  • external-controller binding: Default 127.0.0.1:9090 only allows local access. Use 0.0.0.0:9090 if you need remote Web UI access from another machine (e.g., Mac → Linux server).
  • Background process output lost: When running mihomo with terminal(background=true), stdout/stderr go to pipes. Use a wrapper script with exec ./mihomo -d config 2>&1 or redirect to a log file to capture output.
  • Online dashboard shows empty/disconnected: Browser blocks HTTP API requests from HTTPS dashboard pages (mixed-content policy). Deploy yacd locally instead — see Web UI Option B.
  • {"hello":"mihomo"} when opening API URL: This is the mihomo API root response, NOT the Web UI. Users sometimes confuse this. The Web UI is a separate static app (yacd) that connects to the API.
  • Hot-reload preferred over restart: Editing config.yaml then calling PUT /configs or pkill -HUP reloads rules without dropping active connections. Only do a full restart if hot-reload fails or the binary itself needs updating.
  • NO_PROXY must include internal IPs: If you route all traffic through proxy, local services (Ollama on 192.168.x.x, WireGuard 10.8.0.x, localhost) will break. Always set NO_PROXY with internal subnets. Also include *.local for mDNS.
  • Lowercase proxy vars: Some Python libraries (requests, httpx, aiohttp) read http_proxy/https_proxy (lowercase) while others read HTTP_PROXY/HTTPS_PROXY (uppercase). Set both to be safe.
  • Mihomo rule order matters: Rules are evaluated top-down, first match wins. Place specific service rules (e.g., DOMAIN-SUFFIX,okx.com) BEFORE broad DIRECT rules (e.g., DOMAIN-SUFFIX,qq.com). If a broad DIRECT rule matches first, your specific proxy rule is never reached.
  • Proxy down = ALL platforms fail: When 127.0.0.1:7890 is unreachable, Telegram, QQBot, and WeChat all fail simultaneously. Gateway log pattern: Connect attempt N/8 failed: httpx.ConnectError: All connection attempts failed + Proxy detected; passing explicitly to HTTPXRequest. Quick diagnosis: read ~/.hermes/logs/gateway.log via read_file() (last 100 lines). For full diagnostic checklist: skill_view(name="clash-docker-workflow", file_path="references/proxy-diagnostics.md").
  • mihomo systemd service: The binary runs as a foreground process. For production, create a systemd service to survive reboots. Full recipe: skill_view(name="clash-docker-workflow", file_path="references/systemd-setup.md").
  • Security scanner blocks sudo writes to /etc/systemd/: Hermes's security layer intercepts sudo tee, sudo cp, and write_file targeting /etc/systemd/system/. Workaround: write .service files to /tmp/ via write_file(), then instruct the user to manually sudo cp and sudo systemctl. Do not retry blocked commands — they will keep failing.
  • startup.sh vs systemd conflict: If both startup.sh and systemd manage the same services, they can fight over PIDs. After enabling systemd, stop using startup.sh for start/stop — use sudo systemctl exclusively.
  • startup-service-manager skill: For generic service management (not just Clash), see the startup-service-manager skill — same ~/startup/ directory, manages mihomo + yacd + any other service.
  • Subscription update MUST NOT use proxy (circular dependency): The proxy depends on the subscription for node configs. If subscription updates go through the proxy, you get a dead-lock: proxy needs subscription → subscription update needs proxy. Always use direct connection (no -x flag) for subscription downloads. This may be slower or fail on some networks, but it avoids the circular dependency. If direct download also fails (e.g., the subscription provider is blocked by ISP), the only escape is fetching from an alternate network (mobile hotspot, VPN) and manually placing the config file.
  • Duplicate mihomo processes: If mihomo is started multiple times (e.g., both startup.sh start and a manual ./mihomo -d config), duplicate PIDs compete for the same ports. Check with ps aux | grep mihomo | grep -v grep and kill extras. The scripts/update-sub.sh uses flock to prevent concurrent updates, but the mihomo binary itself has no singleton guard.
  • hermes gateway restart fails when run from inside the gateway: Hermes blocks self-restart from within a gateway session (SIGTERM propagation). Instruct the user to run it from a separate shell. The agent cannot do this via terminal(). Workaround: systemctl --user restart hermes-gateway from a separate shell.
  • mihomo crash can permanently kill Telegram adapter (reconnect watcher bug): When mihomo goes down, the Telegram adapter hits 10 network retries → sets fatal error → gateway's _platform_reconnect_watcher is supposed to auto-reconnect with exponential backoff (30s→300s cap). BUT the watcher task can silently die (asyncio task exit with no log), leaving the adapter permanently offline even after mihomo recovers. Symptoms: (1) zero [Telegram] logs in journalctl after the crash, (2) no "Reconnecting telegram" or "Reconnect telegram error" entries, (3) DMs may still work (existing session) but group messages are lost. Diagnosis: journalctl --user -u hermes-gateway --since '1 hour ago' | grep -i 'telegram' | grep -v Traceback. If only crash/reconnect-failed logs exist with no subsequent "Reconnecting" attempts, the watcher is dead. Fix: restart gateway from external shell. This is a Hermes upstream bug — the watcher's while self._running loop exits silently under certain conditions. Until fixed upstream, consider a mihomo watchdog (see references/mihomo-watchdog.md) to prevent the initial crash.
  • Dual defense for proxy — set BOTH config bridge AND systemd env vars: telegram.proxy_url in config.yaml IS bridged to TELEGRAM_PROXY by load_gateway_config(). But if the bridge fails (manual start, config parse error), the gateway falls back to direct connection. Always also set HTTPS_PROXY in the systemd service file for belt-and-suspenders reliability. Full diagnostic checklist: skill_view(name="clash-docker-workflow", file_path="references/proxy-diagnostics.md").
  • channel_prompts are platform-agnostic in config.yaml: The same group ID prompt configured under telegram.channel_prompts, discord.channel_prompts, and slack.channel_prompts is redundant — only configure it under platforms actually in use. Check which platforms are connected in gateway_state.json before auditing.
  • Old gateway services linger: ~/.config/systemd/user/ may contain services from previous tools (e.g. openclaw-gateway.service). If the service is no longer needed, disable it: systemctl --user disable --now <name>. Otherwise it wastes resources on boot.
  • Proxy works but Telegram still times out: Two fixes available — (1) hermes config set telegram.proxy_url http://127.0.0.1:7890 then hermes gateway restart (simpler — load_gateway_config() bridges this to TELEGRAM_PROXY env var), OR (2) add Environment="HTTPS_PROXY=http://127.0.0.1:7890" to ~/.config/systemd/user/hermes-gateway.service, then systemctl --user daemon-reload && hermes gateway restart. Symptom: curl through proxy passes, but gateway logs show connection failures. Full diagnostic checklist: skill_view(name="clash-docker-workflow", file_path="references/proxy-diagnostics.md").

  • Mihomo watchdog to prevent cascading failures: mihomo crashes can silently kill the Telegram adapter's reconnect watcher. A lightweight watchdog script monitors mihomo health and restarts it before downstream services notice. Full recipe: skill_view(name="clash-docker-workflow", file_path="references/mihomo-watchdog.md").
  • ⚠️ CRITICAL: systemd oneshot cgroup kills mihomo on watchdog exit: If the watchdog runs as a systemd user service (Type=oneshot) and calls startup.sh restart mihomo, the mihomo process becomes a child of the watchdog's cgroup. When the watchdog service exits, systemd kills all processes in the cgroup — including the just-started mihomo. This creates a death loop: watchdog starts mihomo → watchdog exits → systemd kills mihomo → next watchdog cycle finds mihomo dead → repeat every 2 minutes. Symptoms: mihomo log shows Mihomo shutting down 3-6 seconds after each startup, watchdog.log shows repeated restarts. Fix: (1) mihomo MUST run as its own dedicated systemd service (not via startup.sh inside the watchdog), (2) watchdog calls systemctl --user restart mihomo, (3) watchdog must NOT check proxy connectivity — proxy node issues ≠ mihomo issues, unnecessary restarts break stable connections. See references/mihomo-watchdog.md.
  • Telegram adapter diagnostics: When the bot stops processing group messages, use the diagnostic checklist in references/telegram-adapter-diagnostics.md — covers adapter health, proxy checks, bot membership, config verification, forwarder DB inspection, and the reconnect watcher architecture.

Subscription URL Management\n\nHardcoding the subscription URL in multiple scripts creates a maintenance headache — change one place and the other drifts. Use a single-source-of-truth file:\n\nCreate ~/clash/sub-url.conf:\nbash\n# Clash 订阅地址 — 改这一行,所有脚本自动读取\nSUB_URL=\"https://sub-route.bxy.re/api/v1/client/subscribe?token=YOUR_TOKEN_HERE\"\n\n\nHave all scripts source it:\nbash\n# In startup.sh, update-sub.sh, or any other script:\nif [ -f \"$CLASH_DIR/sub-url.conf\" ]; then\n source \"$CLASH_DIR/sub-url.conf\"\nfi\n# Now $SUB_URL is available everywhere\n\n\nThe scripts/update-sub.sh in this skill can also accept the URL as a CLI argument (bash update-sub.sh \"URL\"), which overrides the default for one-off tests.\n\n---\n\n## Subscription Auto-Update (Cron)

Proxy nodes rotate periodically. Set up a recurring cron job to keep the config fresh:

  1. Direct-connection update script: scripts/update-sub.sh in this skill — downloads the subscription directly (no proxy), injects required params (external-controller, mixed-port, etc.), and hot-reloads mihomo via PUT /configs. No restart needed unless hot-reload fails.

    Why no proxy? The proxy itself depends on the subscription for node configs. If the update script routes through the proxy, you get a circular dependency: proxy needs subscription → update needs proxy → deadlock. Always download subscriptions via direct connection.

  2. Cron job: Run every 30 minutes. Only notify on failure:

    # Hermes cronjob approach:
    cronjob(action='create', schedule='30m', repeat=forever, name='Clash 订阅更新',
             prompt='bash ~/clash/update-sub.sh. Report only on failure.',
             enabled_toolsets=['terminal', 'file'])