OpenWRT Cluster¶
The OpenWRT cluster provides the VPN infrastructure for cbintel. It consists of 18 OpenWRT devices: 1 master router, 1 gateway, and 16 workers.
Hardware Overview¶
| Role | Count | IP Range | Purpose |
|---|---|---|---|
| Master | 1 | 17.0.0.1 | HAProxy, LuCI RPC |
| Gateway | 1 | 17.0.0.2 | Network gateway |
| Workers | 16 | 17.0.0.10-25 | VPN tunnels, proxies |
Network Architecture¶
graph TB
subgraph "Host Server"
CLUSTER_API[Cluster API :9002]
LUCI_CLIENT[LuCI RPC Client]
WG[WireGuard Tunnels<br/>wg01-wg16]
end
subgraph "Master Router 17.0.0.1"
HAPROXY[HAProxy<br/>Ports 8890-8999]
LUCI[LuCI JSON-RPC]
end
subgraph "Workers 17.0.0.10-25"
W1[Worker 1<br/>TinyProxy :3128<br/>OpenVPN tun0]
W2[Worker 2<br/>TinyProxy :3128<br/>OpenVPN tun0]
W16[Worker 16<br/>TinyProxy :3128<br/>OpenVPN tun0]
end
subgraph "VPN Providers"
PROTON[ProtonVPN<br/>12,909 profiles<br/>127 countries]
end
CLUSTER_API --> LUCI_CLIENT --> LUCI
WG --> W1 & W2 & W16
HAPROXY --> W1 & W2 & W16
W1 & W2 & W16 --> PROTON
Worker Components¶
Each worker runs:
| Component | Port | Purpose |
|---|---|---|
| TinyProxy | 3128 | HTTP proxy server |
| OpenVPN | tun0 | VPN tunnel interface |
| Dropbear | 22 | SSH access |
| LuCI | 80 | Web admin interface |
LuCI RPC Interface¶
The LuCI JSON-RPC interface enables programmatic device control.
Authentication¶
from cbintel.cluster.clients.luci_rpc import LuCIClient
# Connect to a worker
client = LuCIClient("17.0.0.10", username="root", password="...")
await client.authenticate()
Available Methods¶
| Method | Purpose |
|---|---|
system.info |
Get system status |
luci.admin.uci.get_all |
Read UCI configuration |
luci.admin.uci.set |
Write UCI configuration |
luci.admin.uci.commit |
Apply UCI changes |
sys.exec |
Execute shell command |
Example: Get System Info¶
# Get worker system information
info = await client.call("system.info")
print(f"Uptime: {info['uptime']}s")
print(f"Load: {info['load']}")
print(f"Memory: {info['memory']['free']} / {info['memory']['total']}")
Example: Control VPN¶
# Stop VPN service
await client.call("sys.exec", "service openvpn stop")
# Upload new profile
await client.upload("/etc/openvpn/client.ovpn", profile_content)
# Start VPN service
await client.call("sys.exec", "service openvpn start")
# Check tunnel status
result = await client.call("sys.exec", "ifconfig tun0")
HAProxy Configuration¶
The master router runs HAProxy to load-balance across workers.
Port Allocation¶
Configuration Example¶
frontend california_frontend
bind *:8890
default_backend california_backend
backend california_backend
balance roundrobin
server worker1 17.0.0.10:3128 check
server worker2 17.0.0.11:3128 check
server worker3 17.0.0.12:3128 check
Dynamic Configuration¶
Banks are configured dynamically via the Cluster API:
from cbintel.cluster.services.bank_service import BankService
service = BankService()
# Create bank - updates HAProxy config
bank = await service.create_bank(
name="California",
workers=[1, 2, 3],
filter="us:ca"
)
print(f"Endpoint: {bank.endpoint}") # http://17.0.0.1:8890
WireGuard Tunnels¶
Each worker has a dedicated WireGuard tunnel from the host.
Tunnel Configuration¶
| Tunnel | Worker | Tunnel IP |
|---|---|---|
| wg01 | 17.0.0.10 | 10.200.1.2 |
| wg02 | 17.0.0.11 | 10.200.2.2 |
| ... | ... | ... |
| wg16 | 17.0.0.25 | 10.200.16.2 |
Host Configuration¶
# /etc/wireguard/wg01.conf
[Interface]
Address = 10.200.1.1/24
PrivateKey = <host_private_key>
[Peer]
PublicKey = <worker1_public_key>
AllowedIPs = 10.200.1.2/32
Endpoint = 17.0.0.10:51820
Device Registry¶
The Device Registry tracks all 18 devices with cached metadata.
Registry Model¶
@dataclass
class DeviceRegistryEntry:
id: int # 1-18
hostname: str # lazarus-worker-01
lan_ip: str # 17.0.0.10
wg_interface: str # wg01
wg_tunnel_ip: str # 10.200.1.2
wg_public_key: str # WireGuard public key
vpn_profile: str # Current VPN profile
vpn_country: str # VPN country
vpn_region: str # VPN region
vpn_provider: str # ProtonVPN
external_ip: str # Current exit IP
status: str # online/offline
last_seen: datetime # Last status check
uptime: int # Uptime in seconds
load_avg: float # System load
memory_free_mb: float # Free memory
ping_latency_ms: float # Network latency
download_speed_mbps: float # Download speed
upload_speed_mbps: float # Upload speed
tags: list[str] # Custom tags
notes: str # Custom notes
Registry Operations¶
from cbintel.cluster.services.device_service import DeviceService
service = DeviceService()
# Ping a device
result = await service.ping_device(1)
print(f"Latency: {result.avg_ms}ms")
# Get external IP with geolocation
ip_info = await service.get_external_ip(1)
print(f"Exit: {ip_info.ip} ({ip_info.country}, {ip_info.city})")
# Run speedtest
speed = await service.run_speedtest(1)
print(f"Down: {speed.download_mbps} Mbps")
# Execute command
result = await service.execute_command(1, "uptime")
print(result.stdout)
# Get system info
info = await service.get_system_info(1)
print(f"Load: {info.load_avg}")
VPN Profile Management¶
Profile Location¶
/path/to/profiles/intl-ovpn/
├── us-ca-001.protonvpn.udp.ovpn
├── us-ca-002.protonvpn.udp.ovpn
├── us-ny-001.protonvpn.udp.ovpn
├── de-fra-001.protonvpn.udp.ovpn
└── ... (~12,909 profiles)
Profile Selection¶
Profiles are selected based on geographic filters:
| Filter | Description | Example Profiles |
|---|---|---|
us |
Any US server | us--.ovpn |
us:ca |
California | us-ca-*.ovpn |
us:ca:tor |
California + Tor | us-ca-*-tor.ovpn |
de |
Germany | de-*.ovpn |
uk |
United Kingdom | uk-*.ovpn |
Profile Assignment¶
from cbintel.cluster.services.worker_service import WorkerService
service = WorkerService()
# Start VPN with specific profile
await service.start_vpn(
worker_number=1,
profile="us-ca-324.protonvpn.udp.ovpn"
)
# Stop VPN
await service.stop_vpn(1)
# Restart with new profile
await service.restart_vpn(1, "us-ny-123.protonvpn.udp.ovpn")
Monitoring¶
Health Checks¶
from cbintel.cluster.services.device_service import DeviceService
service = DeviceService()
# Ping all devices
results = await service.ping_all_devices()
for device_id, result in results.items():
if result.success:
print(f"Device {device_id}: {result.avg_ms}ms")
else:
print(f"Device {device_id}: UNREACHABLE")
# Refresh device status
await service.refresh_device(1)
# Refresh all devices
await service.refresh_all_devices()
Status Endpoints¶
| Endpoint | Description |
|---|---|
GET /api/v1/devices/ |
List all devices |
GET /api/v1/devices/{id}/status |
Device system info |
POST /api/v1/devices/{id}/ping |
Ping device |
POST /api/v1/devices/ping-all |
Ping all devices |
Troubleshooting¶
Device Unreachable¶
- Check WireGuard tunnel status:
wg show wg01 - Ping worker directly:
ping 17.0.0.10 - Check SSH access:
ssh root@17.0.0.10 - Verify VPN status on device:
ifconfig tun0
VPN Not Connecting¶
- Check profile validity
- Verify credentials in profile
- Check DNS resolution on worker
- Review OpenVPN logs:
/var/log/openvpn.log
HAProxy Issues¶
- Check HAProxy status:
systemctl status haproxy - View config:
/etc/haproxy/haproxy.cfg - Check backend health:
/api/v1/cluster/health