Generate and deploy real WG/AWG configs to VPS over SSH
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+61
-16
@@ -11,6 +11,7 @@ from sqlalchemy.orm import selectinload
|
||||
from app.config import settings
|
||||
from app.models import Protocol, VpnClient, VpnServer
|
||||
from app.services.crypto import (
|
||||
generate_awg_params,
|
||||
generate_keypair,
|
||||
generate_preshared_key,
|
||||
next_client_ip,
|
||||
@@ -18,6 +19,7 @@ from app.services.crypto import (
|
||||
render_server_config,
|
||||
server_address,
|
||||
)
|
||||
from app.services.remote import deploy_config, install_vpn
|
||||
from app.services.ssh import probe_tcp, verify_ssh
|
||||
|
||||
|
||||
@@ -80,29 +82,36 @@ async def ensure_default_servers(session: AsyncSession) -> None:
|
||||
|
||||
def _defaults_for_protocol(protocol: str) -> dict:
|
||||
if protocol == Protocol.AWG2.value:
|
||||
awg = generate_awg_params()
|
||||
return {
|
||||
"interface_name": settings.awg_interface,
|
||||
"subnet": settings.awg_subnet,
|
||||
"public_port": settings.awg_public_port,
|
||||
"jc": 4,
|
||||
"jmin": 40,
|
||||
"jmax": 70,
|
||||
"s1": 0,
|
||||
"s2": 0,
|
||||
"h1": 1,
|
||||
"h2": 2,
|
||||
"h3": 3,
|
||||
"h4": 4,
|
||||
"mtu": 1280,
|
||||
"jc": awg.jc,
|
||||
"jmin": awg.jmin,
|
||||
"jmax": awg.jmax,
|
||||
"s1": awg.s1,
|
||||
"s2": awg.s2,
|
||||
"s3": awg.s3,
|
||||
"s4": awg.s4,
|
||||
"h1": awg.h1,
|
||||
"h2": awg.h2,
|
||||
"h3": awg.h3,
|
||||
"h4": awg.h4,
|
||||
}
|
||||
return {
|
||||
"interface_name": settings.wg_interface,
|
||||
"subnet": settings.wg_subnet,
|
||||
"public_port": settings.public_port,
|
||||
"mtu": 1420,
|
||||
"jc": None,
|
||||
"jmin": None,
|
||||
"jmax": None,
|
||||
"s1": None,
|
||||
"s2": None,
|
||||
"s3": None,
|
||||
"s4": None,
|
||||
"h1": None,
|
||||
"h2": None,
|
||||
"h3": None,
|
||||
@@ -185,10 +194,14 @@ async def create_server(
|
||||
jmax=defaults["jmax"],
|
||||
s1=defaults["s1"],
|
||||
s2=defaults["s2"],
|
||||
s3=defaults["s3"],
|
||||
s4=defaults["s4"],
|
||||
h1=defaults["h1"],
|
||||
h2=defaults["h2"],
|
||||
h3=defaults["h3"],
|
||||
h4=defaults["h4"],
|
||||
mtu=int(defaults["mtu"]),
|
||||
vpn_installed=False,
|
||||
is_enabled=True,
|
||||
)
|
||||
session.add(server)
|
||||
@@ -356,21 +369,27 @@ async def get_client_config(session: AsyncSession, client_id: int) -> str:
|
||||
if not client:
|
||||
raise ValueError("Client not found")
|
||||
server = client.server
|
||||
dns = server.dns
|
||||
if "," not in dns:
|
||||
dns = f"{dns}, 1.0.0.1"
|
||||
return render_client_config(
|
||||
protocol=server.protocol,
|
||||
client_private_key=client.private_key,
|
||||
client_address=client.address,
|
||||
dns=server.dns,
|
||||
dns=dns,
|
||||
server_public_key=server.server_public_key,
|
||||
preshared_key=client.preshared_key,
|
||||
endpoint_host=server.public_host,
|
||||
endpoint_port=server.public_port,
|
||||
allowed_ips=client.allowed_ips,
|
||||
mtu=server.mtu or (1280 if server.protocol == Protocol.AWG2.value else 1420),
|
||||
jc=server.jc,
|
||||
jmin=server.jmin,
|
||||
jmax=server.jmax,
|
||||
s1=server.s1,
|
||||
s2=server.s2,
|
||||
s3=server.s3,
|
||||
s4=server.s4,
|
||||
h1=server.h1,
|
||||
h2=server.h2,
|
||||
h3=server.h3,
|
||||
@@ -378,11 +397,7 @@ async def get_client_config(session: AsyncSession, client_id: int) -> str:
|
||||
)
|
||||
|
||||
|
||||
async def sync_server_config(session: AsyncSession, server_id: int) -> Path:
|
||||
result = await session.execute(
|
||||
select(VpnServer).options(selectinload(VpnServer.clients)).where(VpnServer.id == server_id)
|
||||
)
|
||||
server = result.scalar_one()
|
||||
def _build_server_config_text(server: VpnServer) -> str:
|
||||
peers = [
|
||||
{
|
||||
"name": c.name,
|
||||
@@ -393,7 +408,7 @@ async def sync_server_config(session: AsyncSession, server_id: int) -> Path:
|
||||
for c in server.clients
|
||||
if c.is_enabled
|
||||
]
|
||||
content = render_server_config(
|
||||
return render_server_config(
|
||||
protocol=server.protocol,
|
||||
interface_name=server.interface_name,
|
||||
private_key=server.server_private_key,
|
||||
@@ -405,14 +420,44 @@ async def sync_server_config(session: AsyncSession, server_id: int) -> Path:
|
||||
jmax=server.jmax,
|
||||
s1=server.s1,
|
||||
s2=server.s2,
|
||||
s3=server.s3,
|
||||
s4=server.s4,
|
||||
h1=server.h1,
|
||||
h2=server.h2,
|
||||
h3=server.h3,
|
||||
h4=server.h4,
|
||||
)
|
||||
|
||||
|
||||
async def sync_server_config(session: AsyncSession, server_id: int, push_remote: bool = True) -> Path:
|
||||
result = await session.execute(
|
||||
select(VpnServer).options(selectinload(VpnServer.clients)).where(VpnServer.id == server_id)
|
||||
)
|
||||
server = result.scalar_one()
|
||||
content = _build_server_config_text(server)
|
||||
|
||||
base = Path(settings.wg_config_dir if server.protocol == Protocol.WIREGUARD.value else settings.awg_config_dir)
|
||||
base.mkdir(parents=True, exist_ok=True)
|
||||
path = base / f"{server.interface_name}.conf"
|
||||
path.write_text(content, encoding="utf-8")
|
||||
|
||||
if push_remote and server.vpn_installed and server.ssh_host:
|
||||
await asyncio.to_thread(deploy_config, server, content)
|
||||
|
||||
return path
|
||||
|
||||
|
||||
async def install_vpn_on_server(session: AsyncSession, server_id: int) -> list[str]:
|
||||
result = await session.execute(
|
||||
select(VpnServer).options(selectinload(VpnServer.clients)).where(VpnServer.id == server_id)
|
||||
)
|
||||
server = result.scalar_one_or_none()
|
||||
if not server:
|
||||
raise ValueError("Server not found")
|
||||
content = _build_server_config_text(server)
|
||||
# Always save locally first
|
||||
await sync_server_config(session, server_id, push_remote=False)
|
||||
logs = await asyncio.to_thread(install_vpn, server, content)
|
||||
server.vpn_installed = True
|
||||
await session.commit()
|
||||
return logs
|
||||
|
||||
Reference in New Issue
Block a user