feat(vpn): VPN-Satelliten — WireGuard-Clients als Satelliten ohne Installation beim Kunden
Einstellungen → VPN → WireGuard → Clients: WireGuard-Datei des Kunden hochladen/einfuegen (editierbar), je Verbindung Schalter (an = auch nach Neustart, aus = bleibt aus), bearbeiten, Download, ℹ-Verlauf, loeschen; Log-Aufbewahrung im Reiter Einstellungen. Server-Reiter mit Anleitung fuer UDP 443 (NPM-Stream bzw. ohne Nginx), Server selbst folgt. Jede Verbindung ist ein eigener Container (aria-vpn-<id>, Image aus satellite/Dockerfile.vpn) mit eigenem Netz-Namensraum: WireGuard + normaler satellite.py (kind=vpn). Firewall im Container: Tunnel nur ausgehend, keine Verbindung Kundennetz → ARIA; ins ARIA-Netz nur RVS + Endpunkt. Routen ohne wg-quick, RVS-Weg nie durch den Tunnel (auch bei 0.0.0.0/0). Scan ueber den Tunnel aktiv per TCP (kein mDNS/ARP), FritzBox an Port 49000 erkannt. Satelliten-Ansicht: eigene Gruppe VPN-Satelliten. Lokal Ende-zu-Ende getestet (Kunde mit WG-Server + LAN, Fake-RVS): Tunnel, Verlauf, Scan, Sperren in beide Richtungen, Volltunnel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+59
-1
@@ -17,6 +17,7 @@ const { WebSocket, WebSocketServer } = require("ws");
|
||||
const crypto = require("crypto");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const vpn = require("./vpn");
|
||||
|
||||
// ── Konfiguration ───────────────────────────────────────
|
||||
const HTTP_PORT = parseInt(process.env.DIAG_PORT || "3001", 10);
|
||||
@@ -1090,7 +1091,7 @@ function satelliteList() {
|
||||
return Array.from(satellites.values()).map(s => ({
|
||||
id: s.id, location: alias[s.id] || s.location,
|
||||
agentName: s.location, alias: alias[s.id] || "",
|
||||
caps: s.caps, control: s.control, net: s.net || null,
|
||||
caps: s.caps, control: s.control, net: s.net || null, kind: s.kind || "lan",
|
||||
online: (now - (s.last_seen || 0)) < 300000,
|
||||
}));
|
||||
}
|
||||
@@ -1098,6 +1099,58 @@ function satelliteList() {
|
||||
// requestId → Browser-Socket, der Klartext-Zugangsdaten angefordert hat
|
||||
const credsRevealWaiters = new Map();
|
||||
|
||||
function vpnRvs() {
|
||||
return { host: RVS_HOST, port: RVS_PORT, tls: RVS_TLS, tlsFallback: RVS_TLS_FALLBACK,
|
||||
token: RVS_TOKEN, sni: process.env.RVS_SNI || "" };
|
||||
}
|
||||
|
||||
async function broadcastVpn() {
|
||||
try { broadcast({ type: "vpn_list", ...(await vpn.list()) }); }
|
||||
catch (e) { log("warn", "vpn", "Liste: " + e.message); }
|
||||
}
|
||||
|
||||
async function handleVpn(ws, msg) {
|
||||
const reply = (body) => { try { ws.send(JSON.stringify({ type: "vpn_result", requestId: msg.requestId || "", action: msg.action, ...body })); } catch { /* weg */ } };
|
||||
try {
|
||||
switch (msg.action) {
|
||||
case "vpn_list":
|
||||
ws.send(JSON.stringify({ type: "vpn_list", ...(await vpn.list()) }));
|
||||
return;
|
||||
case "vpn_check":
|
||||
reply({ ok: true, check: vpn.checkConf(msg.conf) });
|
||||
return;
|
||||
case "vpn_get":
|
||||
reply({ ok: true, connection: vpn.getConf(String(msg.id || "")) });
|
||||
return;
|
||||
case "vpn_info":
|
||||
reply({ ok: true, info: await vpn.info(String(msg.id || "")) });
|
||||
return;
|
||||
case "vpn_save":
|
||||
reply({ ok: true, ...(await vpn.save(msg, vpnRvs())) });
|
||||
log("info", "vpn", `Verbindung gespeichert: ${String(msg.name || "").slice(0, 60)}`);
|
||||
break;
|
||||
case "vpn_toggle":
|
||||
reply({ ok: true, ...(await vpn.toggle(String(msg.id || ""), !!msg.enabled, vpnRvs())) });
|
||||
log("info", "vpn", `Verbindung ${msg.id} ${msg.enabled ? "an" : "aus"}`);
|
||||
break;
|
||||
case "vpn_delete":
|
||||
reply({ ok: true, ...(await vpn.remove(String(msg.id || ""))) });
|
||||
log("info", "vpn", `Verbindung ${msg.id} geloescht`);
|
||||
break;
|
||||
case "vpn_settings":
|
||||
reply({ ok: true, settings: vpn.setSettings(msg.settings || {}) });
|
||||
break;
|
||||
default:
|
||||
reply({ ok: false, error: "unbekannte Aktion" });
|
||||
return;
|
||||
}
|
||||
broadcastVpn();
|
||||
} catch (e) {
|
||||
reply({ ok: false, error: e.message });
|
||||
broadcastVpn();
|
||||
}
|
||||
}
|
||||
|
||||
function broadcastSatellites() {
|
||||
broadcast({ type: "sat_update", satellites: satelliteList() });
|
||||
}
|
||||
@@ -1644,6 +1697,7 @@ function connectRVS(forcePlain) {
|
||||
satellites.set(p.id, {
|
||||
id: p.id, location: p.location || p.id,
|
||||
caps: p.caps || [], control: !!p.control, net: p.net || null,
|
||||
kind: p.kind || "lan",
|
||||
last_seen: Date.now(),
|
||||
});
|
||||
broadcastSatellites();
|
||||
@@ -3695,6 +3749,10 @@ wss.on("connection", (ws) => {
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
} else if (typeof msg.action === "string" && msg.action.startsWith("vpn_")) {
|
||||
// VPN-Satelliten (Einstellungen → VPN). Antworten nur an DIESEN Browser —
|
||||
// vpn_get enthaelt den privaten Schluessel. Aenderungen → Liste an alle.
|
||||
handleVpn(ws, msg);
|
||||
} else if (msg.action === "llm_runtime") {
|
||||
// llama-swap/llama.cpp einer Box: Version pruefen, aktualisieren, zuruecksetzen.
|
||||
const op = String(msg.op || "status");
|
||||
|
||||
Reference in New Issue
Block a user