Files
wireguard-admin/app/service.py
T
lofyerandfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> c4dc22074e Peer enhancements: notes, traffic quotas with auto-disable, batch create, usage tracking; relay subnets for bridging into other tunnels
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-07-04 07:34:57 +08:00

162 lines
4.2 KiB
Python

from datetime import datetime, timezone
from sqlalchemy import select
from sqlalchemy.orm import Session
from . import wireguard
from .crypto import encrypt
from .models import Peer, TrafficSample
def list_peers(db: Session) -> list[Peer]:
return list(db.scalars(select(Peer).order_by(Peer.id)))
def get_peer(db: Session, peer_id: int) -> Peer | None:
return db.get(Peer, peer_id)
def create_peer(
db: Session,
name: str,
expires_at: datetime | None = None,
note: str = "",
quota_bytes: int = 0,
) -> Peer:
private, public = wireguard.generate_keypair()
psk = wireguard.genpsk()
address = wireguard.next_free_address([p.address for p in list_peers(db)])
peer = Peer(
name=name,
public_key=public,
private_key_enc=encrypt(private),
preshared_key_enc=encrypt(psk),
address=address,
expires_at=expires_at,
note=note,
quota_bytes=quota_bytes,
)
db.add(peer)
db.commit()
apply_config(db)
return peer
def create_peers_batch(
db: Session,
base_name: str,
count: int,
expires_at: datetime | None = None,
note: str = "",
quota_bytes: int = 0,
) -> list[Peer]:
existing = {p.name for p in list_peers(db)}
peers = []
index = 1
for _ in range(count):
while f"{base_name}-{index}" in existing:
index += 1
name = f"{base_name}-{index}"
existing.add(name)
peers.append(create_peer(db, name, expires_at, note, quota_bytes))
return peers
def update_peer(db: Session, peer: Peer, note: str, quota_bytes: int) -> Peer:
peer.note = note
peer.quota_bytes = quota_bytes
db.commit()
if not peer.over_quota:
apply_config(db)
return peer
def reset_peer_usage(db: Session, peer: Peer) -> Peer:
peer.cum_rx = 0
peer.cum_tx = 0
db.commit()
apply_config(db)
return peer
def rotate_peer_keys(db: Session, peer: Peer) -> Peer:
private, public = wireguard.generate_keypair()
peer.last_rx = 0
peer.last_tx = 0
peer.public_key = public
peer.private_key_enc = encrypt(private)
peer.preshared_key_enc = encrypt(wireguard.genpsk())
db.commit()
apply_config(db)
return peer
def toggle_peer(db: Session, peer: Peer) -> Peer:
peer.enabled = not peer.enabled
db.commit()
apply_config(db)
return peer
def delete_peer(db: Session, peer: Peer) -> None:
db.delete(peer)
db.commit()
apply_config(db)
def disable_expired_peers(db: Session) -> bool:
now = datetime.now(timezone.utc)
changed = False
for peer in list_peers(db):
if not peer.enabled:
continue
expires = peer.expires_at
if expires is not None and expires.tzinfo is None:
expires = expires.replace(tzinfo=timezone.utc)
if (expires is not None and expires <= now) or peer.over_quota:
peer.enabled = False
changed = True
if changed:
db.commit()
apply_config(db)
return changed
def accumulate_usage(db: Session) -> None:
status = wireguard.get_status()
changed = False
for peer in list_peers(db):
peer_status = status.peers.get(peer.public_key)
if peer_status is None:
continue
rx, tx = peer_status.rx_bytes, peer_status.tx_bytes
# wg counters reset on interface restart or peer re-add
delta_rx = rx - peer.last_rx if rx >= peer.last_rx else rx
delta_tx = tx - peer.last_tx if tx >= peer.last_tx else tx
if delta_rx or delta_tx or rx != peer.last_rx or tx != peer.last_tx:
peer.cum_rx += delta_rx
peer.cum_tx += delta_tx
peer.last_rx = rx
peer.last_tx = tx
changed = True
if changed:
db.commit()
def apply_config(db: Session) -> None:
private, _ = wireguard.ensure_server_keys()
wireguard.sync_peers(private, list_peers(db))
def sample_traffic(db: Session) -> None:
status = wireguard.get_status()
for peer_status in status.peers.values():
db.add(
TrafficSample(
peer_public_key=peer_status.public_key,
rx_bytes=peer_status.rx_bytes,
tx_bytes=peer_status.tx_bytes,
)
)
db.commit()