CI / Build & Test (push) Failing after 2m44s
- Add "Use TLS (secure connection)" checkbox in Add Virtual Host modal - Persist and restore useTls in state; show TLS badge in table - When enabled, HTTPS proxy connects to tunnel backend over TLS (SNI = hostname) for HTTP and WebSocket; supports services on port 443
146 lines
5.9 KiB
JavaScript
146 lines
5.9 KiB
JavaScript
/**
|
|
* Proxy startup and persisted tunnel restore.
|
|
* Starts the HTTPS and CONNECT proxies after loading persisted settings,
|
|
* then asynchronously restores all tunnels from state.json.
|
|
*/
|
|
|
|
const { log, debugLog } = require('./logger.js');
|
|
|
|
const PROXY_PORT = 8443;
|
|
const CONNECT_PROXY_PORT = 8442;
|
|
|
|
let proxiesReadyPromise = null;
|
|
let tunnelsRestoredPromise = null;
|
|
|
|
/**
|
|
* Re-start all tunnels that were persisted in state.json.
|
|
* Runs after the proxies are ready so tunnels have a working backend to connect through.
|
|
* Logs per-type success/failure counts but never throws — failures are non-fatal.
|
|
* @param {object} holesailManager - The holesail manager module.
|
|
* @param {object} restored - The persisted state object returned by `holesailManager.restorePersistedState()`.
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async function restorePersistedTunnels(holesailManager, restored) {
|
|
try {
|
|
debugLog('restorePersistedTunnels: starting');
|
|
const { servers: savedServers, virtualHosts: savedVhosts, serviceTunnels: savedServiceTunnels } = restored;
|
|
log('Restore: loaded', savedServers.length, 'servers,', savedVhosts.length, 'virtual hosts,', savedServiceTunnels.length, 'service tunnels');
|
|
let serverOk = 0, serverFail = 0;
|
|
for (const s of savedServers) {
|
|
const result = await holesailManager.startServer({
|
|
serverId: s.id,
|
|
port: s.port,
|
|
host: s.host || '127.0.0.1',
|
|
secure: s.secure !== false,
|
|
udp: s.udp === true,
|
|
label: s.label || ''
|
|
});
|
|
if (result.ok) serverOk++; else serverFail++;
|
|
}
|
|
let vhostOk = 0, vhostFail = 0;
|
|
for (const v of savedVhosts) {
|
|
const result = await holesailManager.setVirtualHost({ hostname: v.hostname, hsUrl: v.hsUrl, useTls: v.useTls === true });
|
|
if (result.ok) vhostOk++; else vhostFail++;
|
|
}
|
|
let svcOk = 0, svcFail = 0;
|
|
for (const t of savedServiceTunnels) {
|
|
const result = await holesailManager.startServiceTunnel({ tunnelId: t.id, label: t.label, hsUrl: t.hsUrl, localPort: t.localPort });
|
|
if (result.ok) svcOk++; else svcFail++;
|
|
}
|
|
if (savedServers.length || savedVhosts.length || savedServiceTunnels.length) {
|
|
log('Restored on startup: servers', serverOk + '/' + savedServers.length, 'virtual hosts', vhostOk + '/' + savedVhosts.length, 'service tunnels', svcOk + '/' + savedServiceTunnels.length);
|
|
if (serverFail || vhostFail || svcFail) log('Restore failures: servers', serverFail, 'virtual hosts', vhostFail, 'service tunnels', svcFail);
|
|
}
|
|
} catch (e) {
|
|
log('Restore persisted tunnels failed:', e.message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Kick off the async startup sequence: restore settings, start the CA, start both
|
|
* proxies, then begin restoring persisted tunnels in the background.
|
|
* Sets `proxiesReadyPromise` which resolves once the proxies are up (or have failed).
|
|
* Sets `tunnelsRestoredPromise` which resolves once all tunnels have been re-started.
|
|
* @param {object} holesailManager
|
|
* @param {object} certificateAuthority
|
|
* @param {object} httpsProxy
|
|
* @param {object} connectProxy
|
|
*/
|
|
function initStartup(holesailManager, certificateAuthority, httpsProxy, connectProxy) {
|
|
if (typeof setImmediate !== 'function') return;
|
|
|
|
proxiesReadyPromise = new Promise((resolve) => {
|
|
setImmediate(async () => {
|
|
try {
|
|
const restored = holesailManager.restorePersistedState();
|
|
const savedProxyPort = (restored.settings && restored.settings.proxyPort) || PROXY_PORT;
|
|
const savedConnectPort = (restored.settings && restored.settings.connectProxyPort) || CONNECT_PROXY_PORT;
|
|
|
|
try {
|
|
await certificateAuthority.caReady;
|
|
} catch (e) {
|
|
log('CA generation failed:', e.message, '— HTTPS proxy may not work correctly');
|
|
}
|
|
|
|
try {
|
|
await new Promise((res, rej) => {
|
|
httpsProxy.start(savedProxyPort, certificateAuthority, (err) => {
|
|
if (err) return rej(err);
|
|
res();
|
|
});
|
|
});
|
|
log('HTTPS proxy ready on port', savedProxyPort);
|
|
} catch (e) {
|
|
log('HTTPS proxy startup failed:', e.message);
|
|
}
|
|
|
|
try {
|
|
await new Promise((res, rej) => {
|
|
const timeout = setTimeout(() => {
|
|
rej(new Error('CONNECT proxy listen timed out after 5s'));
|
|
}, 5000);
|
|
connectProxy.start(savedConnectPort, savedProxyPort, (err) => {
|
|
clearTimeout(timeout);
|
|
if (err) return rej(err);
|
|
res();
|
|
});
|
|
});
|
|
log('CONNECT proxy ready on port', savedConnectPort);
|
|
} catch (e) {
|
|
log('CONNECT proxy startup failed:', e.message);
|
|
}
|
|
|
|
log('Proxies startup complete: HTTPS', savedProxyPort, 'CONNECT', connectProxy.getPort() ?? 'FAILED');
|
|
|
|
// Assign tunnelsRestoredPromise BEFORE resolving proxiesReadyPromise so
|
|
// that any getState call awaiting proxiesReadyPromise immediately sees the
|
|
// non-null promise rather than the one-microtask window where it is null.
|
|
tunnelsRestoredPromise = restorePersistedTunnels(holesailManager, restored).catch((e) => log('Restore tunnels failed:', e.message));
|
|
} catch (e) {
|
|
log('Unexpected startup error:', e.message);
|
|
} finally {
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/** @returns {Promise<void>|null} Resolves when both proxies have started (or failed). */
|
|
function getProxiesReadyPromise() { return proxiesReadyPromise; }
|
|
/** @returns {Promise<void>|null} Resolves when all persisted tunnels have been re-started. */
|
|
function getTunnelsRestoredPromise() { return tunnelsRestoredPromise; }
|
|
/**
|
|
* Override the tunnels-restored promise (used by message-router to assign it
|
|
* before proxiesReadyPromise resolves, avoiding a one-microtask null window).
|
|
* @param {Promise<void>} p
|
|
*/
|
|
function setTunnelsRestoredPromise(p) { tunnelsRestoredPromise = p; }
|
|
|
|
module.exports = {
|
|
initStartup,
|
|
restorePersistedTunnels,
|
|
getProxiesReadyPromise,
|
|
getTunnelsRestoredPromise,
|
|
setTunnelsRestoredPromise
|
|
};
|