VPN Cluster Management API
FastAPI service for managing geographic VPN clustering across OpenWRT workers.
Overview
The VPN Cluster API provides programmatic control over a network of 16 OpenWRT workers, each capable of running VPN tunnels. The API allows creating "banks" of workers that route traffic through specific geographic regions (countries or US states).
Architecture
flowchart TB
subgraph Host["Host Server"]
API[FastAPI Service<br/>Port 9002]
end
subgraph Master["Master Router (17.0.0.1)"]
HAPROXY[HAProxy<br/>Ports 8890-8999]
LUCI[LuCI RPC]
end
subgraph Workers["OpenWRT Workers (16x)"]
W1[Worker 1<br/>17.0.0.10]
W2[Worker 2<br/>17.0.0.11]
WDOTS[...]
W16[Worker 16<br/>17.0.0.25]
end
subgraph VPN["ProtonVPN Profiles"]
P1[~12,909 profiles]
P2[127 countries]
P3[US state routing]
end
API -->|RPC| LUCI
API -->|Configure| HAPROXY
HAPROXY -->|Load Balance| W1 & W2 & W16
W1 & W2 & W16 -->|OpenVPN| VPN
Location
- Source:
/home/bisenbek/projects/tinymachines/llarp/api/
- Symlink:
extern/llarp -> /home/bisenbek/projects/tinymachines/llarp/
Quick Start
# Start the API (from llarp directory)
cd /home/bisenbek/projects/tinymachines/llarp
source ~/.pyenv/versions/nominates/bin/activate
python -m uvicorn api.main:app --host 0.0.0.0 --port 9002
# Check health
curl http://localhost:9002/health
# List workers
curl http://localhost:9002/api/v1/workers/
# Create a bank
curl -X POST http://localhost:9002/api/v1/banks/ \
-H "Content-Type: application/json" \
-d '{"name": "California", "workers": [1, 2, 3], "filter": "us:ca"}'
API Endpoints
Health
| Method |
Endpoint |
Description |
| GET |
/health |
API health check |
| GET |
/ |
Root info |
Banks (Geographic VPN Pools)
| Method |
Endpoint |
Description |
| GET |
/api/v1/banks/ |
List all banks |
| POST |
/api/v1/banks/ |
Create a new bank |
| GET |
/api/v1/banks/{tag} |
Get bank details |
| PUT |
/api/v1/banks/{tag} |
Update bank |
| DELETE |
/api/v1/banks/{tag} |
Delete bank |
| POST |
/api/v1/banks/{tag}/idle |
Pause bank |
| POST |
/api/v1/banks/{tag}/resume |
Resume bank |
Workers
| Method |
Endpoint |
Description |
| GET |
/api/v1/workers/ |
List all workers |
| GET |
/api/v1/workers/{n} |
Get worker details |
| GET |
/api/v1/workers/{n}/exit-ip |
Get worker's current exit IP |
| POST |
/api/v1/workers/{n}/vpn/start |
Start VPN on worker |
| POST |
/api/v1/workers/{n}/vpn/stop |
Stop VPN on worker |
| POST |
/api/v1/workers/{n}/vpn/restart |
Restart VPN on worker |
Cluster
| Method |
Endpoint |
Description |
| GET |
/api/v1/cluster/ |
Cluster status |
| GET |
/api/v1/cluster/health |
Cluster health |
| POST |
/api/v1/cluster/idle |
Pause all banks |
| POST |
/api/v1/cluster/resume |
Resume all banks |
| POST |
/api/v1/cluster/reset |
Reset cluster |
Devices (Device Registry & Management)
| Method |
Endpoint |
Description |
| GET |
/api/v1/devices/ |
List all devices in registry |
| GET |
/api/v1/devices/{id} |
Get device from registry |
| PUT |
/api/v1/devices/{id} |
Update device metadata |
| POST |
/api/v1/devices/{id}/refresh |
Refresh device status from live device |
| POST |
/api/v1/devices/refresh |
Refresh all devices |
| POST |
/api/v1/devices/{id}/ping |
Ping device and get latency |
| GET |
/api/v1/devices/{id}/status |
Get device system info |
| GET |
/api/v1/devices/{id}/external-ip |
Get device external IP with geolocation |
| POST |
/api/v1/devices/{id}/speedtest |
Run bandwidth test on device |
| POST |
/api/v1/devices/{id}/execute |
Execute command on device |
| POST |
/api/v1/devices/{id}/reboot |
Reboot device |
| POST |
/api/v1/devices/{id}/sync-time |
Sync device time via NTP |
| POST |
/api/v1/devices/ping-all |
Ping all devices in parallel |
Browser Automation (Planned)
| Method |
Endpoint |
Description |
| POST |
/api/v1/browser/screenshot |
Take screenshot via proxy |
| POST |
/api/v1/browser/scrape |
Scrape page via proxy |
| POST |
/api/v1/browser/pdf |
Generate PDF via proxy |
Tor (Planned)
| Method |
Endpoint |
Description |
| GET |
/api/v1/tor/workers |
List tor-enabled workers |
| POST |
/api/v1/tor/workers/{n}/tor/start |
Start tor on worker |
Creating Banks
Banks are pools of workers assigned to a geographic region. When you create a bank:
- Workers are assigned from the available pool
- VPN profiles matching the filter are selected
- HAProxy is configured to load-balance across workers
- A unique port (8890-8999) is allocated
Bank Create Schema
{
"name": "California Test", // Display name
"workers": [1, 2, 3], // Worker numbers (1-16)
"filter": "us:ca" // Geographic filter
}
| Filter |
Description |
us |
United States (any state) |
us:ca |
California |
us:ny |
New York |
de |
Germany |
uk |
United Kingdom |
us:tx:tor |
Texas with Tor exit |
Bank Response
{
"name": "California Test",
"tag": "californiatest",
"workers": [1, 2, 3],
"worker_assignments": [
{
"worker_number": 1,
"worker_ip": "17.0.0.10",
"vpn_profile": "us-ca-324.protonvpn.udp.ovpn",
"vpn_status": "up",
"exit_ip": "146.70.195.107"
}
],
"filter": {"country": "us", "state": "ca", "type": null},
"port": 8894,
"status": "active",
"endpoint": "http://17.0.0.1:8894",
"health": {
"workers_total": 3,
"workers_up": 3,
"workers_down": 0,
"success_rate": 1.0
}
}
Device Registry
The Device Registry provides comprehensive tracking of all 16 OpenWRT devices with cached metadata, WireGuard tunnel info, VPN status, and performance metrics.
DeviceRegistry Model
{
"id": 1,
"hostname": "lazarus-worker-01",
"lan_ip": "17.0.0.10",
"wg_interface": "wg01",
"wg_tunnel_ip": "10.200.1.2",
"wg_public_key": "...",
"vpn_profile": "es-mad-001.protonvpn.udp.ovpn",
"vpn_country": "Spain",
"vpn_region": "Madrid",
"vpn_provider": "ProtonVPN",
"external_ip": "185.159.157.42",
"status": "online",
"last_seen": "2026-01-03T12:00:00Z",
"uptime": 86400,
"load_avg": 0.15,
"memory_free_mb": 128.5,
"memory_total_mb": 256.0,
"ping_latency_ms": 2.5,
"download_speed_mbps": 95.2,
"upload_speed_mbps": 48.7,
"tags": ["production", "europe"],
"notes": "Primary EU exit node"
}
Device Utility Functions
# Ping a device
curl -X POST http://localhost:9002/api/v1/devices/1/ping?count=5
# Get device system info
curl http://localhost:9002/api/v1/devices/1/status
# Get external IP with geolocation
curl http://localhost:9002/api/v1/devices/1/external-ip
# Run speedtest on device
curl -X POST http://localhost:9002/api/v1/devices/1/speedtest
# Execute command on device
curl -X POST "http://localhost:9002/api/v1/devices/1/execute?command=uptime"
# Refresh device status from live device
curl -X POST http://localhost:9002/api/v1/devices/1/refresh
# Refresh all devices
curl -X POST http://localhost:9002/api/v1/devices/refresh
# Ping all devices
curl -X POST http://localhost:9002/api/v1/devices/ping-all
Device Registry Storage
The device registry is stored at /var/lib/vpn-banks/device-registry.json and provides persistent storage for device metadata, WireGuard configuration, and cached status information.
Worker Status
Each worker provides detailed status:
{
"ip": "17.0.0.10",
"worker_number": 1,
"status": "online",
"vpn_status": "up",
"vpn_profile": "us-ca-324.protonvpn.udp.ovpn",
"exit_ip": "146.70.195.107",
"exit_country": "US",
"exit_region": "California",
"exit_city": "Los Angeles",
"proxy_status": "running",
"proxy_port": 3128
}
Configuration
Environment Variables (.env)
OPENWRT_USERNAME=root
OPENWRT_PASSWORD=<password>
MASTER_IP=17.0.0.1
API_PORT=9000
PROFILES_BASE=/home/bisenbek/projects/tinymachines/llarp/resources/profiles/intl-ovpn
VPN Profiles
Located at: /home/bisenbek/projects/tinymachines/llarp/resources/profiles/intl-ovpn/
- ~12,909 ProtonVPN profiles
- 127 countries
- US profiles include state codes (us-ca, us-ny, etc.)
Integration with cbintel
Current Integration Points
- WireGuard tunnels (
wg01-wg15) already route through workers
- VPN API provides bank management for geographic routing
- HAProxy load-balances across worker proxies
Proposed cbintel Refactoring
src/cbintel/
└── cluster/
├── __init__.py
├── api.py # VPN Cluster API client
├── bank.py # Bank management
├── worker.py # Worker operations
└── proxy.py # Proxy configuration
Testing
# Health check
curl http://localhost:9002/health
# List workers with status
curl http://localhost:9002/api/v1/workers/ | python3 -m json.tool
# Create test bank
curl -X POST http://localhost:9002/api/v1/banks/ \
-H "Content-Type: application/json" \
-d '{"name": "Test", "workers": [1], "filter": "us:ca"}'
# Get worker exit IP
curl http://localhost:9002/api/v1/workers/1/exit-ip
# Clean up
curl -X DELETE http://localhost:9002/api/v1/banks/test
Files
| File |
Purpose |
src/cbintel/cluster/main.py |
FastAPI application |
src/cbintel/cluster/config.py |
Settings (pydantic) |
src/cbintel/cluster/routers/banks.py |
Bank endpoints |
src/cbintel/cluster/routers/workers.py |
Worker endpoints |
src/cbintel/cluster/routers/cluster.py |
Cluster endpoints |
src/cbintel/cluster/routers/devices.py |
Device registry endpoints |
src/cbintel/cluster/services/bank_service.py |
Bank business logic |
src/cbintel/cluster/services/worker_service.py |
Worker operations |
src/cbintel/cluster/services/device_service.py |
Device registry & utilities |
src/cbintel/cluster/services/state_manager.py |
Persistent state management |
src/cbintel/cluster/clients/luci_rpc.py |
OpenWRT RPC client |
src/cbintel/cluster/models/device.py |
Device & DeviceRegistry models |
src/cbintel/cluster/models/worker.py |
Worker models |
src/cbintel/cluster/models/bank.py |
Bank models |
References