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
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: network-failover
|
||||
description: Automatic network failover and proxy/VPN switching. Detect connectivity loss → activate backup route (WireGuard VPN, proxy, etc.) → deactivate when primary recovers. Use when user says "自动切换代理", "网络受限", "auto switch VPN", "network failover", "proxy auto switch".
|
||||
origin: user
|
||||
---
|
||||
|
||||
# Network Failover & Auto-Switch
|
||||
|
||||
Automatically detect network restrictions and switch to a backup route (WireGuard VPN, SOCKS/HTTP proxy, etc.), then switch back when the primary network recovers.
|
||||
|
||||
## When to Use
|
||||
|
||||
- User has a WireGuard VPN and wants it activated only when needed
|
||||
- User wants automatic proxy switching when direct connectivity fails
|
||||
- Environments where the network is intermittently restricted (GFW, corporate firewalls, etc.)
|
||||
- Reducing VPN overhead by only using it when necessary
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Monitor (systemd timer / cron) │
|
||||
│ Every 30s: ping/curl to multiple targets │
|
||||
│ │
|
||||
│ ┌─ Success? ──────────────────────────────┐│
|
||||
│ │ VPN up + N consecutive recoveries ││
|
||||
│ │ → disconnect VPN ││
|
||||
│ └─────────────────────────────────────────┘│
|
||||
│ │
|
||||
│ ┌─ Failure? ──────────────────────────────┐│
|
||||
│ │ VPN down + M consecutive failures ││
|
||||
│ │ → activate VPN ││
|
||||
│ └─────────────────────────────────────────┘│
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
1. **Hysteresis (anti-flap)**: Require N consecutive failures before activating, M consecutive successes before deactivating. Never switch on a single check.
|
||||
2. **Multi-target probing**: Test multiple IPs (8.8.8.8, 1.1.1.1, 208.67.222.222) with both curl and ping. DNS can break before ICMP, so probe at multiple layers.
|
||||
3. **Logging**: Always log switch events to a file for post-mortem debugging.
|
||||
4. **State files**: Use `/tmp/` files for counters (ephemeral, reset on reboot = safe default).
|
||||
|
||||
## WireGuard Auto-Switch
|
||||
|
||||
See `references/wireguard-auto-switch.md` for the full implementation script and systemd timer setup.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **⚠️ TEST BEFORE EXECUTE — network changes can kill connectivity instantly.** WireGuard with `AllowedIPs = 0.0.0.0/0` intercepts ALL traffic the moment `wg-quick up` runs. If the VPN endpoint is unreachable or misconfigured, the machine loses all network access — including SSH. **Always**: (1) check current state first (`ip route`, `wg show`, `ping`), (2) test VPN endpoint reachability before activating, (3) verify connectivity after each change, (4) have a fallback plan (e.g., a second terminal with a pre-existing SSH session, or physical console access). Never blindly execute `wg-quick up/down` or route changes.
|
||||
- **Single-ping switching causes flapping** — always use hysteresis (3 failures to activate, 2 recoveries to deactivate).
|
||||
- **DNS failure ≠ network failure** — if DNS breaks first, `curl http://8.8.8.8` still works (IP literal). Test both DNS-dependent and DNS-independent targets.
|
||||
- **Full tunnel kills DNS** — when WireGuard `AllowedIPs = 0.0.0.0/0`, ALL traffic (including DNS) goes through VPN. If VPN has DNS issues, the system is blind. Consider setting `DNS = 8.8.8.8` in the WireGuard config.
|
||||
- **sudo without password** — the script needs `wg-quick up/down` which requires root. Either add NOPASSWD sudoers entry or run the systemd service as root.
|
||||
- **Log rotation** — the switch log grows unbounded. Add logrotate or truncate periodically.
|
||||
- **PersistentKeepalive = 25** — mandatory on mobile/NAT clients, otherwise idle tunnels get dropped by NAT and the VPN appears "connected" but is actually dead.
|
||||
|
||||
## Related Skills
|
||||
|
||||
- `homelab-wireguard-vpn` — WireGuard server/client setup fundamentals
|
||||
- `homelab-wireguard-vpn` > references/ — DDNS, key generation, split vs full tunnel
|
||||
@@ -0,0 +1,120 @@
|
||||
# WireGuard Auto-Switch — Full Implementation
|
||||
|
||||
Complete script + systemd timer for automatic WireGuard failover.
|
||||
|
||||
## Auto-Switch Script
|
||||
|
||||
Path: `~/.hermes/scripts/wg-auto-switch.sh`
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
WG_IF="wg0"
|
||||
LOG="/var/log/wg-auto-switch.log"
|
||||
TARGETS=("8.8.8.8" "1.1.1.1" "208.67.222.222")
|
||||
FAIL_THRESHOLD=3
|
||||
RECOVER_THRESHOLD=2
|
||||
FAIL_FILE="/tmp/wg-auto-fail-count"
|
||||
RECOVER_FILE="/tmp/wg-auto-recover-count"
|
||||
|
||||
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG"; }
|
||||
|
||||
check_network() {
|
||||
for target in "${TARGETS[@]}"; do
|
||||
if curl -s --max-time 3 --head "http://$target" > /dev/null 2>&1; then return 0; fi
|
||||
if ping -c 1 -W 2 "$target" > /dev/null 2>&1; then return 0; fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
wg_is_up() { ip link show "$WG_IF" > /dev/null 2>&1; }
|
||||
get_count() { [ -f "$1" ] && cat "$1" || echo 0; }
|
||||
|
||||
if check_network; then
|
||||
if wg_is_up; then
|
||||
count=$(($(get_count "$RECOVER_FILE") + 1))
|
||||
echo "$count" > "$RECOVER_FILE"
|
||||
[ "$count" -ge "$RECOVER_THRESHOLD" ] && {
|
||||
log "✅ Network recovered, disconnecting WireGuard"
|
||||
sudo wg-quick down "$WG_IF" 2>>"$LOG"
|
||||
echo 0 > "$FAIL_FILE"; echo 0 > "$RECOVER_FILE"
|
||||
}
|
||||
else
|
||||
echo 0 > "$FAIL_FILE"; echo 0 > "$RECOVER_FILE"
|
||||
fi
|
||||
else
|
||||
if ! wg_is_up; then
|
||||
count=$(($(get_count "$FAIL_FILE") + 1))
|
||||
echo "$count" > "$FAIL_FILE"
|
||||
[ "$count" -ge "$FAIL_THRESHOLD" ] && {
|
||||
log "❌ Network restricted (${count} failures), starting WireGuard"
|
||||
sudo wg-quick up "$WG_IF" 2>>"$LOG"
|
||||
echo 0 > "$FAIL_FILE"; echo 0 > "$RECOVER_FILE"
|
||||
}
|
||||
else
|
||||
log "⚠️ WireGuard connected but still no network"
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
## Systemd Timer Setup
|
||||
|
||||
```bash
|
||||
# Make script executable
|
||||
chmod +x ~/.hermes/scripts/wg-auto-switch.sh
|
||||
|
||||
# Create service unit
|
||||
sudo tee /etc/systemd/system/wg-auto-switch.service << 'EOF'
|
||||
[Unit]
|
||||
Description=WireGuard Auto Switch - Network Check
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/home/openclaw/.hermes/scripts/wg-auto-switch.sh
|
||||
User=openclaw
|
||||
EOF
|
||||
|
||||
# Create timer unit
|
||||
sudo tee /etc/systemd/system/wg-auto-switch.timer << 'EOF'
|
||||
[Unit]
|
||||
Description=Run WireGuard auto switch every 30 seconds
|
||||
|
||||
[Timer]
|
||||
OnBootSec=30
|
||||
OnUnitActiveSec=30
|
||||
AccuracySec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
EOF
|
||||
|
||||
# Enable and start
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now wg-auto-switch.timer
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
```bash
|
||||
sudo systemctl status wg-auto-switch.timer
|
||||
tail -f /var/log/wg-auto-switch.log
|
||||
sudo wg show
|
||||
```
|
||||
|
||||
## Tuning
|
||||
|
||||
| Parameter | Default | Effect |
|
||||
|-----------|---------|--------|
|
||||
| `FAIL_THRESHOLD` | 3 | Consecutive failures before VPN activates (higher = slower to react, less flapping) |
|
||||
| `RECOVER_THRESHOLD` | 2 | Consecutive successes before VPN deactivates |
|
||||
| Timer interval | 30s | How often to check (in `/etc/systemd/system/wg-auto-switch.timer`) |
|
||||
| `curl --max-time` | 3s | Per-target timeout |
|
||||
| `ping -W` | 2s | Ping timeout |
|
||||
|
||||
For stricter environments (GFW deep packet inspection), add DNS-over-HTTPS check:
|
||||
```bash
|
||||
# Add to check_network() as a third test type
|
||||
if curl -s --max-time 5 "https://dns.google/resolve?name=google.com&type=A" | grep -q '"Answer"'; then
|
||||
return 0
|
||||
fi
|
||||
```
|
||||
@@ -0,0 +1,11 @@
|
||||
[Interface]
|
||||
Address = 10.7.0.3/24
|
||||
DNS = 8.8.8.8, 8.8.4.4
|
||||
PrivateKey = <CLIENT_PRIVATE_KEY>
|
||||
|
||||
[Peer]
|
||||
PublicKey = <SERVER_PUBLIC_KEY>
|
||||
PresharedKey = <PRESHARED_KEY>
|
||||
AllowedIPs = 0.0.0.0/0, ::/0
|
||||
Endpoint = <SERVER_IP>:51820
|
||||
PersistentKeepalive = 25
|
||||
Reference in New Issue
Block a user