Persist settings in CA, expose /admin/vpn and /internal/vpn, and route Telegram (and optional web/nlp) traffic through mihomo SOCKS when enabled. Co-authored-by: Cursor <cursoragent@cursor.com>
138 lines
4.5 KiB
Python
138 lines
4.5 KiB
Python
"""VPN settings persistence helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from contracts.queues import known_source_types
|
|
from contracts.vpn import VpnSettings
|
|
from sqlalchemy.orm import Session
|
|
|
|
from ..models import VpnSettingsRow
|
|
from ..schemas import VpnSettingsAdminRead, VpnSettingsInternalRead, VpnSettingsUpdate
|
|
|
|
SINGLETON_ID = 1
|
|
|
|
|
|
def _hint_secret(value: str | None) -> str | None:
|
|
if not value:
|
|
return None
|
|
if len(value) <= 4:
|
|
return "****"
|
|
return f"…{value[-4:]}"
|
|
|
|
|
|
def ensure_vpn_settings(db: Session) -> VpnSettingsRow:
|
|
row = db.query(VpnSettingsRow).filter(VpnSettingsRow.id == SINGLETON_ID).first()
|
|
if row is not None:
|
|
return row
|
|
row = VpnSettingsRow(
|
|
id=SINGLETON_ID,
|
|
enabled=False,
|
|
mode="subscription",
|
|
subscription_interval_seconds=3600,
|
|
proxied_source_types=[],
|
|
)
|
|
db.add(row)
|
|
db.commit()
|
|
db.refresh(row)
|
|
return row
|
|
|
|
|
|
def row_to_contract(row: VpnSettingsRow) -> VpnSettings:
|
|
return VpnSettings.model_validate(
|
|
{
|
|
"enabled": row.enabled,
|
|
"mode": row.mode,
|
|
"subscription_url": row.subscription_url,
|
|
"subscription_interval_seconds": row.subscription_interval_seconds,
|
|
"host": row.host,
|
|
"port": row.port,
|
|
"username": row.username,
|
|
"password": row.password,
|
|
"proxied_source_types": row.proxied_source_types or [],
|
|
}
|
|
)
|
|
|
|
|
|
def to_admin_read(row: VpnSettingsRow) -> VpnSettingsAdminRead:
|
|
return VpnSettingsAdminRead(
|
|
enabled=row.enabled,
|
|
mode=row.mode, # type: ignore[arg-type]
|
|
subscription_url_set=bool(row.subscription_url),
|
|
subscription_url_hint=_hint_secret(row.subscription_url),
|
|
subscription_interval_seconds=row.subscription_interval_seconds,
|
|
host=row.host,
|
|
port=row.port,
|
|
username=row.username,
|
|
password_set=bool(row.password),
|
|
proxied_source_types=list(row.proxied_source_types or []),
|
|
available_source_types=known_source_types(),
|
|
)
|
|
|
|
|
|
def to_internal_read(row: VpnSettingsRow) -> VpnSettingsInternalRead:
|
|
return VpnSettingsInternalRead(
|
|
enabled=row.enabled,
|
|
mode=row.mode, # type: ignore[arg-type]
|
|
subscription_url=row.subscription_url,
|
|
subscription_interval_seconds=row.subscription_interval_seconds,
|
|
host=row.host,
|
|
port=row.port,
|
|
username=row.username,
|
|
password=row.password,
|
|
proxied_source_types=list(row.proxied_source_types or []),
|
|
)
|
|
|
|
|
|
def apply_vpn_update(db: Session, payload: VpnSettingsUpdate) -> VpnSettingsRow:
|
|
row = ensure_vpn_settings(db)
|
|
data = {
|
|
"enabled": row.enabled if payload.enabled is None else payload.enabled,
|
|
"mode": row.mode if payload.mode is None else payload.mode,
|
|
"subscription_url": row.subscription_url,
|
|
"subscription_interval_seconds": (
|
|
row.subscription_interval_seconds
|
|
if payload.subscription_interval_seconds is None
|
|
else payload.subscription_interval_seconds
|
|
),
|
|
"host": row.host if payload.host is None else (payload.host.strip() or None),
|
|
"port": row.port if payload.port is None else payload.port,
|
|
"username": (
|
|
row.username
|
|
if payload.username is None
|
|
else (payload.username.strip() or None)
|
|
),
|
|
"password": row.password,
|
|
"proxied_source_types": (
|
|
list(row.proxied_source_types or [])
|
|
if payload.proxied_source_types is None
|
|
else payload.proxied_source_types
|
|
),
|
|
}
|
|
|
|
if payload.clear_subscription_url:
|
|
data["subscription_url"] = None
|
|
elif payload.subscription_url is not None:
|
|
stripped = payload.subscription_url.strip()
|
|
data["subscription_url"] = stripped or None
|
|
|
|
if payload.clear_password:
|
|
data["password"] = None
|
|
elif payload.password is not None:
|
|
stripped = payload.password.strip()
|
|
data["password"] = stripped or None
|
|
|
|
validated = VpnSettings.model_validate(data)
|
|
|
|
row.enabled = validated.enabled
|
|
row.mode = validated.mode
|
|
row.subscription_url = validated.subscription_url
|
|
row.subscription_interval_seconds = validated.subscription_interval_seconds
|
|
row.host = validated.host
|
|
row.port = validated.port
|
|
row.username = validated.username
|
|
row.password = validated.password
|
|
row.proxied_source_types = validated.proxied_source_types
|
|
db.commit()
|
|
db.refresh(row)
|
|
return row
|