Metrics collector (wiregui/collector.py): - Standalone process spawned by web app when WG_METRICS_ENABLED=true - Polls wg show dump every WG_METRICS_POLL_INTERVAL seconds (default 5) - Updates device stats in PostgreSQL - Pushes Prometheus-format metrics to VictoriaMetrics (if configured) - Graceful shutdown on SIGTERM Integration test stack (compose.yml): - Unified compose file for dev, test, and integration modes - VictoriaMetrics single-node TSDB for metrics storage - 3 mock WireGuard client containers generating ping traffic - Automated setup script seeds server keypair, admin user, client devices - make test-stack-up: one command to start everything - make test-stack-verify: validates metrics flowing end-to-end Infrastructure: - Makefile with targets for dev, test, integration, and production - Integration tests verify VictoriaMetrics has data for all 3 clients - Fix Dockerfile to include img/ directory - Separate TESTS.md for test tracking, clean TODO.md for features only
147 lines
4.5 KiB
Python
147 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Seed the test stack: generate server + client keypairs, write client WG configs,
|
|
and insert devices into the database.
|
|
|
|
Usage: uv run python docker/mock-clients/setup.py
|
|
|
|
Requires: Postgres running and migrations applied (alembic upgrade head).
|
|
"""
|
|
|
|
import asyncio
|
|
import sys
|
|
from pathlib import Path
|
|
from uuid import uuid4
|
|
|
|
from sqlmodel import select
|
|
|
|
from wiregui.auth.passwords import hash_password
|
|
from wiregui.db import async_session, engine
|
|
from wiregui.models.configuration import Configuration
|
|
from wiregui.models.device import Device
|
|
from wiregui.models.user import User
|
|
from wiregui.utils.crypto import generate_keypair, generate_preshared_key
|
|
|
|
NUM_CLIENTS = 3
|
|
SUBNET = "10.3.2"
|
|
SERVER_ENDPOINT = "wiregui:51820"
|
|
CONFIG_DIR = Path(__file__).parent / "configs"
|
|
|
|
# Test admin user
|
|
ADMIN_EMAIL = "admin@test.local"
|
|
ADMIN_PASSWORD = "admin123"
|
|
|
|
# Client definitions
|
|
CLIENTS = [
|
|
{"name": f"test-client-{i}", "ip": f"{SUBNET}.{100 + i}"}
|
|
for i in range(1, NUM_CLIENTS + 1)
|
|
]
|
|
|
|
|
|
async def seed():
|
|
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
async with async_session() as session:
|
|
# --- Server keypair ---
|
|
config = (await session.execute(select(Configuration).limit(1))).scalar_one_or_none()
|
|
if config is None:
|
|
config = Configuration()
|
|
session.add(config)
|
|
await session.flush()
|
|
|
|
if not config.server_private_key or not config.server_public_key:
|
|
server_priv, server_pub = generate_keypair()
|
|
config.server_private_key = server_priv
|
|
config.server_public_key = server_pub
|
|
session.add(config)
|
|
print(f" Server keypair generated: {server_pub[:20]}...")
|
|
else:
|
|
server_pub = config.server_public_key
|
|
print(f" Server keypair already exists: {server_pub[:20]}...")
|
|
|
|
# --- Admin user ---
|
|
admin = (await session.execute(
|
|
select(User).where(User.email == ADMIN_EMAIL)
|
|
)).scalar_one_or_none()
|
|
if admin is None:
|
|
admin = User(
|
|
email=ADMIN_EMAIL,
|
|
password_hash=hash_password(ADMIN_PASSWORD),
|
|
role="admin",
|
|
)
|
|
session.add(admin)
|
|
await session.flush()
|
|
print(f" Admin user created: {ADMIN_EMAIL}")
|
|
else:
|
|
print(f" Admin user already exists: {ADMIN_EMAIL}")
|
|
|
|
# --- Client devices (delete + recreate for clean state) ---
|
|
client_names = [c["name"] for c in CLIENTS]
|
|
client_ips = [c["ip"] for c in CLIENTS]
|
|
stale = (await session.execute(
|
|
select(Device).where(
|
|
Device.name.in_(client_names) | Device.ipv4.in_(client_ips)
|
|
)
|
|
)).scalars().all()
|
|
for d in stale:
|
|
await session.delete(d)
|
|
if stale:
|
|
await session.flush()
|
|
print(f" Cleaned up {len(stale)} stale device(s)")
|
|
|
|
for client in CLIENTS:
|
|
client_priv, client_pub = generate_keypair()
|
|
psk = generate_preshared_key()
|
|
|
|
device = Device(
|
|
name=client["name"],
|
|
public_key=client_pub,
|
|
preshared_key=psk,
|
|
ipv4=client["ip"],
|
|
user_id=admin.id,
|
|
)
|
|
session.add(device)
|
|
|
|
client["privkey"] = client_priv
|
|
client["pubkey"] = client_pub
|
|
client["psk"] = psk
|
|
print(f" Device '{client['name']}' created ({client['ip']})")
|
|
|
|
await session.commit()
|
|
|
|
# --- Write client WG configs ---
|
|
for i, client in enumerate(CLIENTS):
|
|
conf = f"""[Interface]
|
|
PrivateKey = {client["privkey"]}
|
|
|
|
[Peer]
|
|
PublicKey = {server_pub}
|
|
PresharedKey = {client["psk"]}
|
|
Endpoint = {SERVER_ENDPOINT}
|
|
AllowedIPs = {SUBNET}.0/24
|
|
PersistentKeepalive = 5
|
|
"""
|
|
conf_path = CONFIG_DIR / f"client{i + 1}.conf"
|
|
conf_path.write_text(conf)
|
|
print(f" Config written: {conf_path}")
|
|
|
|
# --- Write env vars for compose ---
|
|
env_lines = []
|
|
for i, client in enumerate(CLIENTS):
|
|
other_ips = " ".join(c["ip"] for c in CLIENTS if c["ip"] != client["ip"])
|
|
env_lines.append(f"CLIENT{i + 1}_IP={client['ip']}")
|
|
env_lines.append(f"CLIENT{i + 1}_PEERS={other_ips}")
|
|
env_path = CONFIG_DIR / "clients.env"
|
|
env_path.write_text("\n".join(env_lines) + "\n")
|
|
|
|
await engine.dispose()
|
|
|
|
|
|
def main():
|
|
print("[*] Seeding test stack...")
|
|
asyncio.run(seed())
|
|
print("\n[*] Done. Start the stack with:")
|
|
print(" make test-stack-up")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|