vpn-config/server.js
Claude 13f0edd1ca
feat(vpn-config): initial push
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 22:42:13 +01:00

137 lines
4.7 KiB
JavaScript

#!/usr/bin/env node
/**
* Lethean VPN Config API
*
* Simple HTTP API that serves WireGuard peer configs for beta testers.
* In production this sits behind Blesta auth. For testnet it's open.
*
* Endpoints:
* GET / — info page
* GET /api/peer/:name — get WireGuard config for a peer
* GET /api/peers — list available peers
* GET /api/status — gateway status
*/
const http = require('http');
const { execFileSync } = require('child_process');
const PORT = process.env.PORT || 8100;
const WG_CONTAINER = process.env.WG_CONTAINER || 'lthn-wireguard';
const DAEMON_URL = process.env.DAEMON_URL || 'http://127.0.0.1:46941';
function jsonResponse(res, data, status = 200) {
res.writeHead(status, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
});
res.end(JSON.stringify(data, null, 2));
}
function dockerExec(args) {
try {
return execFileSync('docker', ['exec', WG_CONTAINER, ...args], {
encoding: 'utf8',
timeout: 10000,
}).trim();
} catch {
return null;
}
}
function listPeerConfigs() {
const list = dockerExec(['ls', '/config/']);
if (!list) return [];
return list.split('\n')
.filter(d => d.startsWith('peer_'))
.map(d => d.replace('peer_', ''));
}
function getPeerConfig(peerName) {
const safeName = peerName.replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 20);
if (!safeName) return null;
return dockerExec(['cat', `/config/peer_${safeName}/peer_${safeName}.conf`]);
}
function getPeers() {
const dump = dockerExec(['wg', 'show', 'wg0', 'dump']);
if (!dump) return [];
return dump.split('\n').slice(1).map(line => {
const fields = line.split('\t');
if (fields.length < 7) return null;
return {
pubkey: fields[0]?.slice(0, 16) + '...',
endpoint: fields[2] || 'none',
active: parseInt(fields[4]) > 0 && (Date.now() / 1000 - parseInt(fields[4])) < 300,
rxBytes: parseInt(fields[5]) || 0,
txBytes: parseInt(fields[6]) || 0,
};
}).filter(Boolean);
}
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, `http://localhost:${PORT}`);
if (url.pathname === '/' && req.method === 'GET') {
const peers = listPeerConfigs();
res.writeHead(200, { 'Content-Type': 'text/html' });
return res.end(`<!DOCTYPE html><html><head><title>Lethean VPN</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>body{font-family:monospace;background:#0a0a0a;color:#e0e0e0;max-width:600px;margin:40px auto;padding:20px}
h1{color:#00d4aa}a{color:#00d4aa}pre{background:#111;padding:15px;border-radius:6px;overflow-x:auto}
.peer{background:#111;padding:15px;margin:10px 0;border-radius:8px;border-left:3px solid #00d4aa}
.peer a{font-size:18px}</style></head>
<body><h1>Lethean Testnet VPN</h1>
<p>Download a WireGuard config and connect to the Lethean network.</p>
<h2>Available Configs</h2>
${peers.map(p => `<div class="peer"><a href="/api/peer/${p}">Download: ${p}.conf</a></div>`).join('\n')}
<h2>How to connect</h2>
<pre># Download config
curl -o lethean-vpn.conf http://localhost:${PORT}/api/peer/${peers[0] || 'testpeer1'}
# Connect (Linux)
sudo wg-quick up ./lethean-vpn.conf
# Or import into WireGuard app (Windows/Mac/iOS/Android)</pre>
<p><a href="/api/status">Gateway Status</a> · <a href="/api/peers">Active Peers</a></p>
</body></html>`);
}
// GET /api/peer/:name — download config
const peerMatch = url.pathname.match(/^\/api\/peer\/([a-zA-Z0-9_-]+)$/);
if (peerMatch && req.method === 'GET') {
const config = getPeerConfig(peerMatch[1]);
if (!config) return jsonResponse(res, { error: 'Peer not found' }, 404);
res.writeHead(200, {
'Content-Type': 'text/plain',
'Content-Disposition': `attachment; filename="${peerMatch[1]}.conf"`,
});
return res.end(config);
}
if (url.pathname === '/api/peers' && req.method === 'GET') {
return jsonResponse(res, { available: listPeerConfigs(), connected: getPeers() });
}
if (url.pathname === '/api/status' && req.method === 'GET') {
const peers = getPeers();
let chainHeight = null;
try {
const resp = await fetch(`${DAEMON_URL}/json_rpc`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', id: '0', method: 'getinfo' })
});
chainHeight = (await resp.json()).result?.height;
} catch {}
return jsonResponse(res, {
chain: { height: chainHeight },
vpn: { total: peers.length, active: peers.filter(p => p.active).length, configs: listPeerConfigs().length },
});
}
jsonResponse(res, { error: 'Not found' }, 404);
});
server.listen(PORT, () => {
console.log(`Lethean VPN Config API on :${PORT}`);
});