You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

105 lines
3.1 KiB

'use strict';
/**
* tls-proxy.js — HTTPS termination proxy for wg-easy-ip-tls
*
* Reads cert/key from env-provided paths, forwards HTTPS -> HTTP to Nitro.
* Supports hot-reload of certificates via server.setSecureContext().
* Handles WebSocket upgrades (used by wg-easy UI).
*
* Env vars (required):
* CERT_FILE - path to fullchain.pem
* KEY_FILE - path to privkey.pem
* NITRO_PORT - internal HTTP port (Nitro)
* PORT - external HTTPS port to listen on
*/
const https = require('https');
const http = require('http');
const net = require('net');
const fs = require('fs');
const CERT_FILE = process.env.CERT_FILE;
const KEY_FILE = process.env.KEY_FILE;
const NITRO_PORT = parseInt(process.env.NITRO_PORT, 10);
const PORT = parseInt(process.env.PORT, 10);
if (!CERT_FILE || !KEY_FILE || !NITRO_PORT || !PORT) {
console.error('[tls-proxy] Missing required env: CERT_FILE, KEY_FILE, NITRO_PORT, PORT');
process.exit(1);
}
function loadCreds() {
return {
cert: fs.readFileSync(CERT_FILE),
key: fs.readFileSync(KEY_FILE),
};
}
let creds;
try {
creds = loadCreds();
} catch (e) {
console.error('[tls-proxy] Failed to load certificates:', e.message);
process.exit(1);
}
const server = https.createServer(creds, (req, res) => {
const proxy = http.request(
{
hostname: '127.0.0.1',
port: NITRO_PORT,
path: req.url,
method: req.method,
headers: req.headers,
},
(proxyRes) => {
res.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(res);
}
);
proxy.on('error', (e) => {
console.error('[tls-proxy] upstream error:', e.message);
if (!res.headersSent) res.writeHead(502);
res.end('Bad Gateway');
});
req.pipe(proxy);
});
// WebSocket upgrade support (wg-easy uses WS for live updates)
server.on('upgrade', (req, socket, head) => {
const conn = net.connect(NITRO_PORT, '127.0.0.1', () => {
const headers = Object.entries(req.headers)
.map(([k, v]) => `${k}: ${v}`)
.join('\r\n');
conn.write(`GET ${req.url} HTTP/1.1\r\n${headers}\r\n\r\n`);
if (head && head.length) conn.write(head);
socket.pipe(conn);
conn.pipe(socket);
});
conn.on('error', (e) => {
console.error('[tls-proxy] ws upstream error:', e.message);
socket.destroy();
});
socket.on('error', (e) => {
console.error('[tls-proxy] ws client error:', e.message);
conn.destroy();
});
});
// Hot-reload certificates without restart when lego-renew.sh updates them
fs.watchFile(CERT_FILE, { interval: 60_000, persistent: true }, (curr, prev) => {
if (curr.mtime.getTime() === prev.mtime.getTime()) return;
try {
server.setSecureContext(loadCreds());
console.log('[tls-proxy] Certificate hot-reloaded at', curr.mtime.toISOString());
} catch (e) {
console.error('[tls-proxy] Failed to reload certificate:', e.message);
}
});
server.listen(PORT, '0.0.0.0', () => {
console.log(`[tls-proxy] HTTPS listening on 0.0.0.0:${PORT} -> http://127.0.0.1:${NITRO_PORT}`);
});
// Graceful shutdown
process.on('SIGTERM', () => { server.close(); process.exit(0); });
process.on('SIGINT', () => { server.close(); process.exit(0); });