@mucan54/porterman 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-SWPUBQIV.js → chunk-NBKAOCKA.js} +86 -48
- package/dist/chunk-NBKAOCKA.js.map +1 -0
- package/dist/cli.js +5 -10
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-SWPUBQIV.js.map +0 -1
|
@@ -265,7 +265,7 @@ async function obtainCert(hostname, staging) {
|
|
|
265
265
|
});
|
|
266
266
|
const cert = await client.auto({
|
|
267
267
|
csr,
|
|
268
|
-
email: "porterman@
|
|
268
|
+
email: "porterman@sslip.io",
|
|
269
269
|
termsOfServiceAgreed: true,
|
|
270
270
|
challengeCreateFn: async (_authz, _challenge, keyAuthorization) => {
|
|
271
271
|
const token = _challenge.token;
|
|
@@ -377,7 +377,7 @@ var ERROR_502_HTML = (port) => `<!DOCTYPE html>
|
|
|
377
377
|
</body>
|
|
378
378
|
</html>`;
|
|
379
379
|
function createProxyEngine(options) {
|
|
380
|
-
const { timeout, routes, nameMap } = options;
|
|
380
|
+
const { timeout, routes = [], nameMap, dynamic = false } = options;
|
|
381
381
|
const routeMap = /* @__PURE__ */ new Map();
|
|
382
382
|
for (const route of routes) {
|
|
383
383
|
routeMap.set(route.hostname, route.targetPort);
|
|
@@ -406,18 +406,19 @@ function createProxyEngine(options) {
|
|
|
406
406
|
if (routeMap.has(hostname)) {
|
|
407
407
|
return routeMap.get(hostname);
|
|
408
408
|
}
|
|
409
|
-
const port = parsePortFromHost(host);
|
|
410
|
-
if (port !== null) {
|
|
411
|
-
for (const route of routes) {
|
|
412
|
-
if (route.targetPort === port) return port;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
409
|
if (nameMap) {
|
|
416
410
|
const prefix = hostname.split("-")[0];
|
|
417
411
|
if (nameMap.has(prefix)) {
|
|
418
412
|
return nameMap.get(prefix);
|
|
419
413
|
}
|
|
420
414
|
}
|
|
415
|
+
const port = parsePortFromHost(host);
|
|
416
|
+
if (port !== null) {
|
|
417
|
+
if (dynamic) return port;
|
|
418
|
+
for (const route of routes) {
|
|
419
|
+
if (route.targetPort === port) return port;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
421
422
|
return null;
|
|
422
423
|
}
|
|
423
424
|
function handleRequest(req, res) {
|
|
@@ -453,7 +454,7 @@ function createProxyEngine(options) {
|
|
|
453
454
|
function close() {
|
|
454
455
|
proxy.close();
|
|
455
456
|
}
|
|
456
|
-
return { handleRequest, handleUpgrade, close };
|
|
457
|
+
return { handleRequest, handleUpgrade, close, resolveTargetPort };
|
|
457
458
|
}
|
|
458
459
|
|
|
459
460
|
// src/server.ts
|
|
@@ -462,7 +463,7 @@ import { createServer as createHttpsServer } from "https";
|
|
|
462
463
|
import { createSecureContext } from "tls";
|
|
463
464
|
async function startServer(options) {
|
|
464
465
|
const {
|
|
465
|
-
ports,
|
|
466
|
+
ports = [],
|
|
466
467
|
name,
|
|
467
468
|
noSsl = false,
|
|
468
469
|
verbose = false,
|
|
@@ -483,6 +484,7 @@ async function startServer(options) {
|
|
|
483
484
|
if (name && ports.length > 1) {
|
|
484
485
|
throw new Error("--name can only be used with a single port");
|
|
485
486
|
}
|
|
487
|
+
const isDynamic = ports.length === 0;
|
|
486
488
|
logger.info("Detecting public IP...");
|
|
487
489
|
const publicIp = host ?? await detectPublicIp();
|
|
488
490
|
const dashedIp = await getDashedIp(host);
|
|
@@ -517,7 +519,12 @@ async function startServer(options) {
|
|
|
517
519
|
- Stop the process using port ${httpPort}`
|
|
518
520
|
);
|
|
519
521
|
}
|
|
520
|
-
const proxyEngine = createProxyEngine({
|
|
522
|
+
const proxyEngine = createProxyEngine({
|
|
523
|
+
timeout,
|
|
524
|
+
routes,
|
|
525
|
+
nameMap,
|
|
526
|
+
dynamic: isDynamic
|
|
527
|
+
});
|
|
521
528
|
let authCredentials = null;
|
|
522
529
|
if (auth) {
|
|
523
530
|
const [user, pass] = auth.split(":");
|
|
@@ -586,40 +593,58 @@ async function startServer(options) {
|
|
|
586
593
|
}
|
|
587
594
|
let httpsServer = null;
|
|
588
595
|
const certCache = /* @__PURE__ */ new Map();
|
|
596
|
+
const certPending = /* @__PURE__ */ new Map();
|
|
597
|
+
async function getOrProvisionCert(hostname) {
|
|
598
|
+
if (certCache.has(hostname)) {
|
|
599
|
+
return certCache.get(hostname);
|
|
600
|
+
}
|
|
601
|
+
if (certPending.has(hostname)) {
|
|
602
|
+
return certPending.get(hostname);
|
|
603
|
+
}
|
|
604
|
+
const promise = getCertificate(hostname, { staging }).then((cert) => {
|
|
605
|
+
certCache.set(hostname, cert);
|
|
606
|
+
certPending.delete(hostname);
|
|
607
|
+
return cert;
|
|
608
|
+
}).catch((err) => {
|
|
609
|
+
certPending.delete(hostname);
|
|
610
|
+
throw err;
|
|
611
|
+
});
|
|
612
|
+
certPending.set(hostname, promise);
|
|
613
|
+
return promise;
|
|
614
|
+
}
|
|
589
615
|
if (!noSsl) {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
616
|
+
if (routes.length > 0) {
|
|
617
|
+
logger.info("Obtaining SSL certificates...");
|
|
618
|
+
for (const route of routes) {
|
|
619
|
+
const cert = await getOrProvisionCert(route.hostname);
|
|
620
|
+
if (cert.selfSigned) {
|
|
621
|
+
logger.warn(
|
|
622
|
+
`Using self-signed certificate for ${route.hostname} (browsers will show a warning)`
|
|
623
|
+
);
|
|
624
|
+
}
|
|
598
625
|
}
|
|
599
626
|
}
|
|
627
|
+
let defaultCert;
|
|
628
|
+
if (certCache.size > 0) {
|
|
629
|
+
defaultCert = certCache.values().next().value;
|
|
630
|
+
} else {
|
|
631
|
+
const defaultHostname = `porterman-${dashedIp}.sslip.io`;
|
|
632
|
+
defaultCert = await getCertificate(defaultHostname, { staging });
|
|
633
|
+
certCache.set(defaultHostname, defaultCert);
|
|
634
|
+
}
|
|
600
635
|
const sniCallback = (servername, callback) => {
|
|
601
|
-
const
|
|
602
|
-
if (
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
cert: cert.cert
|
|
606
|
-
});
|
|
607
|
-
callback(null, ctx);
|
|
608
|
-
} else {
|
|
609
|
-
for (const [hostname, c] of certCache) {
|
|
610
|
-
if (servername.endsWith(hostname.slice(hostname.indexOf(".")))) {
|
|
611
|
-
const ctx = createSecureContext({
|
|
612
|
-
key: c.key,
|
|
613
|
-
cert: c.cert
|
|
614
|
-
});
|
|
615
|
-
callback(null, ctx);
|
|
616
|
-
return;
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
callback(new Error(`No certificate for ${servername}`));
|
|
636
|
+
const cached = certCache.get(servername);
|
|
637
|
+
if (cached) {
|
|
638
|
+
callback(null, createSecureContext({ key: cached.key, cert: cached.cert }));
|
|
639
|
+
return;
|
|
620
640
|
}
|
|
641
|
+
getOrProvisionCert(servername).then((cert) => {
|
|
642
|
+
callback(null, createSecureContext({ key: cert.key, cert: cert.cert }));
|
|
643
|
+
}).catch((err) => {
|
|
644
|
+
logger.verbose(`SNI cert provision failed for ${servername}: ${err.message}`);
|
|
645
|
+
callback(null, createSecureContext({ key: defaultCert.key, cert: defaultCert.cert }));
|
|
646
|
+
});
|
|
621
647
|
};
|
|
622
|
-
const defaultCert = certCache.values().next().value;
|
|
623
648
|
httpsServer = createHttpsServer(
|
|
624
649
|
{
|
|
625
650
|
key: defaultCert.key,
|
|
@@ -636,16 +661,16 @@ async function startServer(options) {
|
|
|
636
661
|
proxyEngine.handleUpgrade(req, socket, head);
|
|
637
662
|
});
|
|
638
663
|
await new Promise((resolve, reject) => {
|
|
639
|
-
httpsServer.listen(httpsPort, () => resolve());
|
|
664
|
+
httpsServer.listen(httpsPort, "0.0.0.0", () => resolve());
|
|
640
665
|
httpsServer.once("error", reject);
|
|
641
666
|
});
|
|
642
|
-
logger.verbose(`HTTPS server listening on
|
|
667
|
+
logger.verbose(`HTTPS server listening on 0.0.0.0:${httpsPort}`);
|
|
643
668
|
}
|
|
644
669
|
await new Promise((resolve, reject) => {
|
|
645
|
-
httpServer.listen(httpPort, () => resolve());
|
|
670
|
+
httpServer.listen(httpPort, "0.0.0.0", () => resolve());
|
|
646
671
|
httpServer.once("error", reject);
|
|
647
672
|
});
|
|
648
|
-
logger.verbose(`HTTP server listening on
|
|
673
|
+
logger.verbose(`HTTP server listening on 0.0.0.0:${httpPort}`);
|
|
649
674
|
await writePidFile(process.pid);
|
|
650
675
|
const urls = /* @__PURE__ */ new Map();
|
|
651
676
|
for (const route of routes) {
|
|
@@ -656,9 +681,22 @@ async function startServer(options) {
|
|
|
656
681
|
logger.blank();
|
|
657
682
|
logger.success("Ready!");
|
|
658
683
|
logger.blank();
|
|
659
|
-
|
|
660
|
-
const
|
|
661
|
-
|
|
684
|
+
if (isDynamic) {
|
|
685
|
+
const protocol = noSsl ? "http" : "https";
|
|
686
|
+
const portSuffix = !noSsl && httpsPort !== 443 ? `:${httpsPort}` : noSsl && httpPort !== 80 ? `:${httpPort}` : "";
|
|
687
|
+
console.log(` Any port is now accessible via:`);
|
|
688
|
+
console.log(` ${protocol}://{port}-${dashedIp}.sslip.io${portSuffix}`);
|
|
689
|
+
logger.blank();
|
|
690
|
+
console.log(` Examples:`);
|
|
691
|
+
for (const examplePort of [3e3, 5173, 8080]) {
|
|
692
|
+
const exUrl = `${protocol}://${examplePort}-${dashedIp}.sslip.io${portSuffix}`;
|
|
693
|
+
logger.link(`http://localhost:${examplePort}`, exUrl);
|
|
694
|
+
}
|
|
695
|
+
} else {
|
|
696
|
+
for (const route of routes) {
|
|
697
|
+
const url = urls.get(route.targetPort);
|
|
698
|
+
logger.link(`http://localhost:${route.targetPort}`, url);
|
|
699
|
+
}
|
|
662
700
|
}
|
|
663
701
|
logger.blank();
|
|
664
702
|
console.log(" Press Ctrl+C to stop");
|
|
@@ -683,7 +721,7 @@ async function startServer(options) {
|
|
|
683
721
|
}
|
|
684
722
|
logger.success("Stopped");
|
|
685
723
|
}
|
|
686
|
-
return { close, urls };
|
|
724
|
+
return { close, urls, dashedIp, publicIp };
|
|
687
725
|
}
|
|
688
726
|
|
|
689
727
|
export {
|
|
@@ -705,4 +743,4 @@ export {
|
|
|
705
743
|
createProxyEngine,
|
|
706
744
|
startServer
|
|
707
745
|
};
|
|
708
|
-
//# sourceMappingURL=chunk-
|
|
746
|
+
//# sourceMappingURL=chunk-NBKAOCKA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/logger.ts","../src/utils.ts","../src/ip.ts","../src/config.ts","../src/certs.ts","../src/proxy.ts","../src/server.ts"],"sourcesContent":["const RESET = \"\\x1b[0m\";\nconst BOLD = \"\\x1b[1m\";\nconst DIM = \"\\x1b[2m\";\nconst RED = \"\\x1b[31m\";\nconst GREEN = \"\\x1b[32m\";\nconst YELLOW = \"\\x1b[33m\";\nconst BLUE = \"\\x1b[34m\";\nconst CYAN = \"\\x1b[36m\";\nconst WHITE = \"\\x1b[37m\";\n\nlet verboseEnabled = false;\n\nexport function setVerbose(enabled: boolean): void {\n verboseEnabled = enabled;\n}\n\nexport function isVerbose(): boolean {\n return verboseEnabled;\n}\n\nexport const logger = {\n info(message: string): void {\n console.log(`${CYAN}ℹ${RESET} ${message}`);\n },\n\n success(message: string): void {\n console.log(`${GREEN}✅${RESET} ${message}`);\n },\n\n warn(message: string): void {\n console.log(`${YELLOW}⚠${RESET} ${message}`);\n },\n\n error(message: string): void {\n console.error(`${RED}✖${RESET} ${message}`);\n },\n\n rocket(message: string): void {\n console.log(`🚀 ${message}`);\n },\n\n link(label: string, url: string): void {\n console.log(` ${GREEN}${url}${RESET} → ${DIM}${label}${RESET}`);\n },\n\n verbose(message: string): void {\n if (verboseEnabled) {\n console.log(`${DIM}[verbose] ${message}${RESET}`);\n }\n },\n\n request(method: string, host: string, path: string, status: number): void {\n if (!verboseEnabled) return;\n const color = status < 400 ? GREEN : status < 500 ? YELLOW : RED;\n const time = new Date().toISOString().slice(11, 19);\n console.log(\n `${DIM}${time}${RESET} ${BOLD}${method}${RESET} ${host}${path} ${color}${status}${RESET}`\n );\n },\n\n banner(version: string): void {\n console.log(`\\n${BOLD}🚪 Porterman v${version}${RESET}`);\n },\n\n blank(): void {\n console.log();\n },\n};\n","import { createServer, type Server } from \"node:net\";\n\n/**\n * Convert a dotted IP address to dashed format.\n * 85.100.50.25 → 85-100-50-25\n */\nexport function ipToDashed(ip: string): string {\n return ip.replace(/\\./g, \"-\").replace(/:/g, \"-\");\n}\n\n/**\n * Generate a sslip.io hostname for a given port and IP.\n */\nexport function makeHostname(\n port: number | string,\n dashedIp: string\n): string {\n return `${port}-${dashedIp}.sslip.io`;\n}\n\n/**\n * Parse the port number from a sslip.io hostname.\n * \"3000-85-100-50-25.sslip.io\" → 3000\n */\nexport function parsePortFromHost(host: string): number | null {\n // Remove port suffix if present (e.g., \":443\")\n const hostname = host.split(\":\")[0];\n // Extract the prefix before the first dash followed by IP-like pattern\n const match = hostname.match(/^(\\w+)-\\d+-/);\n if (!match) return null;\n const prefix = match[1];\n const port = parseInt(prefix, 10);\n return isNaN(port) ? null : port;\n}\n\n/**\n * Parse custom name from hostname.\n * \"myapp-85-100-50-25.sslip.io\" → \"myapp\"\n */\nexport function parsePrefixFromHost(host: string): string | null {\n const hostname = host.split(\":\")[0];\n const match = hostname.match(/^(\\w+)-\\d+-/);\n return match ? match[1] : null;\n}\n\n/**\n * Check if an IP is a private/reserved address.\n */\nexport function isPrivateIp(ip: string): boolean {\n const parts = ip.split(\".\").map(Number);\n if (parts.length !== 4 || parts.some((p) => isNaN(p))) return true;\n\n // 10.0.0.0/8\n if (parts[0] === 10) return true;\n // 172.16.0.0/12\n if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true;\n // 192.168.0.0/16\n if (parts[0] === 192 && parts[1] === 168) return true;\n // 127.0.0.0/8\n if (parts[0] === 127) return true;\n // 0.0.0.0\n if (parts.every((p) => p === 0)) return true;\n // 169.254.0.0/16 (link-local)\n if (parts[0] === 169 && parts[1] === 254) return true;\n\n return false;\n}\n\n/**\n * Check if a port is available.\n */\nexport function isPortAvailable(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const server = createServer();\n server.once(\"error\", () => resolve(false));\n server.once(\"listening\", () => {\n server.close(() => resolve(true));\n });\n server.listen(port, \"0.0.0.0\");\n });\n}\n\n/**\n * Validate that a port number is valid.\n */\nexport function isValidPort(port: number): boolean {\n return Number.isInteger(port) && port >= 1 && port <= 65535;\n}\n\n/**\n * Sleep for a given number of milliseconds.\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { logger } from \"./logger.js\";\nimport { isPrivateIp, ipToDashed } from \"./utils.js\";\n\nconst IP_SERVICES = [\n \"https://api.ipify.org\",\n \"https://ifconfig.me/ip\",\n \"https://icanhazip.com\",\n];\n\nlet cachedIp: string | null = null;\n\n/**\n * Fetch public IP from a single service with timeout.\n */\nasync function fetchIpFrom(url: string): Promise<string | null> {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 5000);\n const res = await fetch(url, {\n signal: controller.signal,\n headers: { Accept: \"text/plain\" },\n });\n clearTimeout(timeout);\n if (!res.ok) return null;\n const text = await res.text();\n return text.trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Detect the machine's public IP address using multiple services with fallback.\n * Results are cached for the session lifetime.\n */\nexport async function detectPublicIp(): Promise<string> {\n if (cachedIp) return cachedIp;\n\n for (const service of IP_SERVICES) {\n logger.verbose(`Trying IP detection via ${service}`);\n const ip = await fetchIpFrom(service);\n if (ip && /^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(ip)) {\n if (isPrivateIp(ip)) {\n throw new Error(\n `Detected private IP address (${ip}). Porterman requires a public IP.\\n` +\n \"If you're behind NAT, you need a machine with a direct public IP (VPS, cloud instance, etc.).\\n\" +\n \"You can also specify your IP manually with --host <ip>.\"\n );\n }\n cachedIp = ip;\n logger.verbose(`Public IP detected: ${ip}`);\n return ip;\n }\n }\n\n throw new Error(\n \"Could not detect public IP address. All detection services failed.\\n\" +\n \"Please specify your IP manually with --host <ip>.\"\n );\n}\n\n/**\n * Get the dashed format of the public IP (e.g., 85-100-50-25).\n */\nexport async function getDashedIp(overrideIp?: string): Promise<string> {\n const ip = overrideIp ?? (await detectPublicIp());\n if (overrideIp && isPrivateIp(overrideIp)) {\n logger.warn(\n `Specified IP ${overrideIp} appears to be a private address. sslip.io may not work correctly.`\n );\n }\n return ipToDashed(ip);\n}\n\n/**\n * Clear the cached IP (useful for testing).\n */\nexport function clearIpCache(): void {\n cachedIp = null;\n}\n","import { mkdir, readFile, writeFile, chmod } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nconst PORTERMAN_DIR = join(homedir(), \".porterman\");\nconst CONFIG_FILE = join(PORTERMAN_DIR, \"config.json\");\nconst CERTS_DIR = join(PORTERMAN_DIR, \"certs\");\nconst ACCOUNT_KEY_FILE = join(PORTERMAN_DIR, \"account.pem\");\nconst PID_FILE = join(PORTERMAN_DIR, \"porterman.pid\");\n\nexport interface PortermanConfig {\n defaultTimeout?: number;\n defaultHttpPort?: number;\n defaultHttpsPort?: number;\n}\n\nexport const paths = {\n base: PORTERMAN_DIR,\n config: CONFIG_FILE,\n certs: CERTS_DIR,\n accountKey: ACCOUNT_KEY_FILE,\n pidFile: PID_FILE,\n\n certDir(hostname: string): string {\n return join(CERTS_DIR, hostname);\n },\n\n certFile(hostname: string): string {\n return join(CERTS_DIR, hostname, \"cert.pem\");\n },\n\n keyFile(hostname: string): string {\n return join(CERTS_DIR, hostname, \"privkey.pem\");\n },\n\n chainFile(hostname: string): string {\n return join(CERTS_DIR, hostname, \"chain.pem\");\n },\n\n metaFile(hostname: string): string {\n return join(CERTS_DIR, hostname, \"meta.json\");\n },\n};\n\nexport async function ensureDirs(): Promise<void> {\n await mkdir(PORTERMAN_DIR, { recursive: true });\n await mkdir(CERTS_DIR, { recursive: true });\n}\n\nexport async function loadConfig(): Promise<PortermanConfig> {\n try {\n const data = await readFile(CONFIG_FILE, \"utf-8\");\n return JSON.parse(data);\n } catch {\n return {};\n }\n}\n\nexport async function saveConfig(config: PortermanConfig): Promise<void> {\n await ensureDirs();\n await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2));\n}\n\nexport async function writeSecureFile(\n filePath: string,\n content: string\n): Promise<void> {\n await writeFile(filePath, content, { mode: 0o600 });\n}\n\nexport async function writePidFile(pid: number): Promise<void> {\n await ensureDirs();\n await writeFile(PID_FILE, String(pid));\n}\n\nexport async function readPidFile(): Promise<number | null> {\n try {\n const data = await readFile(PID_FILE, \"utf-8\");\n const pid = parseInt(data.trim(), 10);\n return isNaN(pid) ? null : pid;\n } catch {\n return null;\n }\n}\n\nexport function pidFileExists(): boolean {\n return existsSync(PID_FILE);\n}\n","import * as acme from \"acme-client\";\nimport { readFile, mkdir, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { createServer, type Server } from \"node:http\";\nimport { paths, ensureDirs, writeSecureFile } from \"./config.js\";\nimport { logger } from \"./logger.js\";\n\ninterface CertMeta {\n issuedAt: string;\n expiresAt: string;\n domains: string[];\n}\n\ninterface CertFiles {\n key: string;\n cert: string;\n chain: string;\n}\n\n// In-memory store for ACME HTTP-01 challenge tokens\nconst challengeTokens = new Map<string, string>();\n\n/**\n * Get or create the ACME account private key.\n */\nasync function getAccountKey(): Promise<Buffer> {\n await ensureDirs();\n if (existsSync(paths.accountKey)) {\n return readFile(paths.accountKey);\n }\n const key = await acme.crypto.createPrivateKey();\n await writeSecureFile(paths.accountKey, key.toString());\n return key;\n}\n\n/**\n * Check if an existing certificate is still valid (>30 days remaining).\n */\nasync function isCertValid(hostname: string): Promise<boolean> {\n const metaPath = paths.metaFile(hostname);\n if (!existsSync(metaPath)) return false;\n\n try {\n const data = await readFile(metaPath, \"utf-8\");\n const meta: CertMeta = JSON.parse(data);\n const expires = new Date(meta.expiresAt);\n const daysRemaining =\n (expires.getTime() - Date.now()) / (1000 * 60 * 60 * 24);\n if (daysRemaining > 30) {\n logger.verbose(\n `Certificate for ${hostname} valid for ${Math.floor(daysRemaining)} more days`\n );\n return true;\n }\n logger.verbose(\n `Certificate for ${hostname} expires in ${Math.floor(daysRemaining)} days, needs renewal`\n );\n return false;\n } catch {\n return false;\n }\n}\n\n/**\n * Load existing certificate files from disk.\n */\nasync function loadCertFromDisk(hostname: string): Promise<CertFiles> {\n const [key, cert, chain] = await Promise.all([\n readFile(paths.keyFile(hostname), \"utf-8\"),\n readFile(paths.certFile(hostname), \"utf-8\"),\n readFile(paths.chainFile(hostname), \"utf-8\"),\n ]);\n return { key, cert, chain };\n}\n\n/**\n * Create the HTTP-01 challenge handler for the ACME server.\n * This is used as middleware to respond to /.well-known/acme-challenge/ requests.\n */\nexport function handleAcmeChallenge(\n url: string\n): string | null {\n const prefix = \"/.well-known/acme-challenge/\";\n if (!url.startsWith(prefix)) return null;\n const token = url.slice(prefix.length);\n return challengeTokens.get(token) ?? null;\n}\n\n/**\n * Obtain a certificate for a hostname via Let's Encrypt ACME.\n */\nasync function obtainCert(\n hostname: string,\n staging: boolean\n): Promise<CertFiles> {\n const accountKey = await getAccountKey();\n\n const directoryUrl = staging\n ? acme.directory.letsencrypt.staging\n : acme.directory.letsencrypt.production;\n\n const client = new acme.Client({\n directoryUrl,\n accountKey,\n });\n\n // Create a CSR\n const [certKey, csr] = await acme.crypto.createCsr({\n commonName: hostname,\n });\n\n // Order the certificate\n const cert = await client.auto({\n csr,\n email: \"porterman@sslip.io\",\n termsOfServiceAgreed: true,\n challengeCreateFn: async (_authz, _challenge, keyAuthorization) => {\n const token = _challenge.token;\n logger.verbose(`Setting ACME challenge token: ${token}`);\n challengeTokens.set(token, keyAuthorization);\n },\n challengeRemoveFn: async (_authz, _challenge) => {\n const token = _challenge.token;\n challengeTokens.delete(token);\n },\n challengePriority: [\"http-01\"],\n });\n\n // Save certificate files\n const certDir = paths.certDir(hostname);\n await mkdir(certDir, { recursive: true });\n\n const keyStr = certKey.toString();\n const certStr = cert.toString();\n\n await Promise.all([\n writeSecureFile(paths.keyFile(hostname), keyStr),\n writeFile(paths.certFile(hostname), certStr),\n writeFile(paths.chainFile(hostname), certStr),\n ]);\n\n // Save metadata\n const now = new Date();\n const meta: CertMeta = {\n issuedAt: now.toISOString(),\n // Let's Encrypt certs are valid for 90 days\n expiresAt: new Date(now.getTime() + 90 * 24 * 60 * 60 * 1000).toISOString(),\n domains: [hostname],\n };\n await writeFile(paths.metaFile(hostname), JSON.stringify(meta, null, 2));\n\n return { key: keyStr, cert: certStr, chain: certStr };\n}\n\n/**\n * Generate a self-signed certificate as a fallback.\n */\nasync function generateSelfSigned(hostname: string): Promise<CertFiles> {\n const { exec } = await import(\"node:child_process\");\n const { promisify } = await import(\"node:util\");\n const execAsync = promisify(exec);\n\n const certDir = paths.certDir(hostname);\n await mkdir(certDir, { recursive: true });\n\n const keyPath = paths.keyFile(hostname);\n const certPath = paths.certFile(hostname);\n\n await execAsync(\n `openssl req -x509 -newkey rsa:2048 -keyout \"${keyPath}\" -out \"${certPath}\" ` +\n `-days 365 -nodes -subj \"/CN=${hostname}\" 2>/dev/null`\n );\n\n await execAsync(`chmod 600 \"${keyPath}\"`);\n\n const [key, cert] = await Promise.all([\n readFile(keyPath, \"utf-8\"),\n readFile(certPath, \"utf-8\"),\n ]);\n\n // Write chain (same as cert for self-signed)\n await writeFile(paths.chainFile(hostname), cert);\n\n const now = new Date();\n const meta: CertMeta = {\n issuedAt: now.toISOString(),\n expiresAt: new Date(\n now.getTime() + 365 * 24 * 60 * 60 * 1000\n ).toISOString(),\n domains: [hostname],\n };\n await writeFile(paths.metaFile(hostname), JSON.stringify(meta, null, 2));\n\n return { key, cert, chain: cert };\n}\n\nexport interface CertResult {\n key: string;\n cert: string;\n chain: string;\n selfSigned: boolean;\n}\n\n/**\n * Get a valid certificate for a hostname.\n * - First checks for a cached valid cert\n * - Then tries ACME/Let's Encrypt\n * - Falls back to self-signed if ACME fails\n */\nexport async function getCertificate(\n hostname: string,\n options: { staging?: boolean; forceRenew?: boolean } = {}\n): Promise<CertResult> {\n await ensureDirs();\n\n // Check for existing valid cert\n if (!options.forceRenew && (await isCertValid(hostname))) {\n logger.verbose(`Using cached certificate for ${hostname}`);\n const files = await loadCertFromDisk(hostname);\n return { ...files, selfSigned: false };\n }\n\n // Try ACME\n try {\n logger.info(`Obtaining SSL certificate for ${hostname}...`);\n const files = await obtainCert(hostname, options.staging ?? false);\n logger.success(`Certificate obtained for ${hostname}`);\n return { ...files, selfSigned: false };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.warn(`Let's Encrypt failed: ${message}`);\n logger.warn(\"Falling back to self-signed certificate\");\n logger.warn(\n \"Browsers will show a security warning. Consider using a custom domain.\"\n );\n\n const files = await generateSelfSigned(hostname);\n return { ...files, selfSigned: true };\n }\n}\n\n/**\n * Remove all cached certificates.\n */\nexport async function cleanCerts(): Promise<void> {\n const { rm } = await import(\"node:fs/promises\");\n if (existsSync(paths.certs)) {\n await rm(paths.certs, { recursive: true, force: true });\n await mkdir(paths.certs, { recursive: true });\n logger.success(\"All cached certificates removed\");\n }\n}\n","import httpProxy from \"http-proxy\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { logger } from \"./logger.js\";\nimport { parsePortFromHost } from \"./utils.js\";\n\nexport interface ProxyRoute {\n hostname: string;\n targetPort: number;\n name?: string;\n}\n\nexport interface ProxyOptions {\n timeout: number;\n routes?: ProxyRoute[];\n nameMap?: Map<string, number>; // name → target port\n dynamic?: boolean; // allow any port parsed from hostname\n}\n\nconst ERROR_502_HTML = (port: number) => `<!DOCTYPE html>\n<html>\n<head><title>502 Bad Gateway</title>\n<style>\n body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; max-width: 600px; margin: 80px auto; padding: 0 20px; color: #333; }\n h1 { color: #e74c3c; }\n code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; }\n</style>\n</head>\n<body>\n <h1>502 Bad Gateway</h1>\n <p>Nothing is running on <code>localhost:${port}</code></p>\n <p>Make sure your application is started and listening on port <strong>${port}</strong>.</p>\n <hr>\n <p><small>Porterman</small></p>\n</body>\n</html>`;\n\nexport function createProxyEngine(options: ProxyOptions) {\n const { timeout, routes = [], nameMap, dynamic = false } = options;\n\n // Build port lookup: hostname → target port\n const routeMap = new Map<string, number>();\n for (const route of routes) {\n routeMap.set(route.hostname, route.targetPort);\n }\n\n const proxy = httpProxy.createProxyServer({\n xfwd: true, // sets X-Forwarded-* headers\n ws: true,\n proxyTimeout: timeout * 1000,\n timeout: timeout * 1000,\n });\n\n proxy.on(\"error\", (err, req, res) => {\n const host = req.headers.host ?? \"unknown\";\n const targetPort = resolveTargetPort(host);\n logger.verbose(`Proxy error for ${host}: ${err.message}`);\n\n if (res && \"writeHead\" in res) {\n const serverRes = res as ServerResponse;\n if (!serverRes.headersSent) {\n serverRes.writeHead(502, { \"Content-Type\": \"text/html\" });\n serverRes.end(ERROR_502_HTML(targetPort ?? 0));\n }\n }\n });\n\n function resolveTargetPort(host: string): number | null {\n // Normalize: remove port suffix\n const hostname = host.split(\":\")[0];\n\n // Direct hostname match (pre-registered routes)\n if (routeMap.has(hostname)) {\n return routeMap.get(hostname)!;\n }\n\n // Check name map\n if (nameMap) {\n const prefix = hostname.split(\"-\")[0];\n if (nameMap.has(prefix)) {\n return nameMap.get(prefix)!;\n }\n }\n\n // Try parsing port from hostname dynamically\n const port = parsePortFromHost(host);\n if (port !== null) {\n // In dynamic mode, accept any valid port\n if (dynamic) return port;\n // In static mode, only accept ports that are in our routes\n for (const route of routes) {\n if (route.targetPort === port) return port;\n }\n }\n\n return null;\n }\n\n function handleRequest(req: IncomingMessage, res: ServerResponse): boolean {\n const host = req.headers.host;\n if (!host) {\n res.writeHead(400, { \"Content-Type\": \"text/plain\" });\n res.end(\"Bad Request: No Host header\");\n return false;\n }\n\n const targetPort = resolveTargetPort(host);\n if (targetPort === null) {\n res.writeHead(404, { \"Content-Type\": \"text/plain\" });\n res.end(`Not Found: No route configured for ${host}`);\n return false;\n }\n\n logger.verbose(`Proxying ${req.method} ${host}${req.url} → localhost:${targetPort}`);\n\n proxy.web(req, res, {\n target: `http://127.0.0.1:${targetPort}`,\n });\n\n return true;\n }\n\n function handleUpgrade(\n req: IncomingMessage,\n socket: import(\"node:stream\").Duplex,\n head: Buffer\n ): boolean {\n const host = req.headers.host;\n if (!host) return false;\n\n const targetPort = resolveTargetPort(host);\n if (targetPort === null) return false;\n\n logger.verbose(`WebSocket upgrade ${host} → localhost:${targetPort}`);\n\n proxy.ws(req, socket, head, {\n target: `http://127.0.0.1:${targetPort}`,\n });\n\n return true;\n }\n\n function close(): void {\n proxy.close();\n }\n\n return { handleRequest, handleUpgrade, close, resolveTargetPort };\n}\n","import { createServer as createHttpServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createServer as createHttpsServer } from \"node:https\";\nimport { createSecureContext, type SecureContext } from \"node:tls\";\nimport { getDashedIp, detectPublicIp } from \"./ip.js\";\nimport { getCertificate, handleAcmeChallenge, type CertResult } from \"./certs.js\";\nimport { createProxyEngine, type ProxyRoute } from \"./proxy.js\";\nimport { makeHostname, isPortAvailable, isValidPort, parsePortFromHost } from \"./utils.js\";\nimport { writePidFile, paths } from \"./config.js\";\nimport { logger, setVerbose } from \"./logger.js\";\n\nexport interface ServerOptions {\n ports?: number[];\n name?: string;\n noSsl?: boolean;\n verbose?: boolean;\n timeout?: number;\n host?: string;\n staging?: boolean;\n httpPort?: number;\n httpsPort?: number;\n auth?: string;\n ipAllow?: string[];\n}\n\nexport interface PortermanServer {\n close(): Promise<void>;\n urls: Map<number, string>;\n dashedIp: string;\n publicIp: string;\n}\n\nexport async function startServer(options: ServerOptions): Promise<PortermanServer> {\n const {\n ports = [],\n name,\n noSsl = false,\n verbose = false,\n timeout = 30,\n host,\n staging = false,\n httpPort = 80,\n httpsPort = 443,\n auth,\n ipAllow,\n } = options;\n\n setVerbose(verbose);\n\n // Validate explicit ports if provided\n for (const port of ports) {\n if (!isValidPort(port)) {\n throw new Error(`Invalid port number: ${port}`);\n }\n }\n\n if (name && ports.length > 1) {\n throw new Error(\"--name can only be used with a single port\");\n }\n\n // Dynamic mode: no explicit ports means proxy ANY port from hostname\n const isDynamic = ports.length === 0;\n\n // Detect public IP\n logger.info(\"Detecting public IP...\");\n const publicIp = host ?? (await detectPublicIp());\n const dashedIp = await getDashedIp(host);\n logger.info(`Public IP: ${publicIp}`);\n\n // Generate hostnames for explicit ports\n const routes: ProxyRoute[] = ports.map((port) => {\n const prefix = name && ports.length === 1 ? name : String(port);\n return {\n hostname: makeHostname(prefix, dashedIp),\n targetPort: port,\n name: name && ports.length === 1 ? name : undefined,\n };\n });\n\n // Build name map if using custom names\n const nameMap = new Map<string, number>();\n if (name && ports.length === 1) {\n nameMap.set(name, ports[0]);\n }\n\n // Check port availability\n if (!noSsl) {\n if (!(await isPortAvailable(httpsPort))) {\n throw new Error(\n `Port ${httpsPort} is already in use. Try:\\n` +\n ` - Run with sudo if port < 1024\\n` +\n ` - Use --https-port <port> to specify a different port\\n` +\n ` - Stop the process using port ${httpsPort}`\n );\n }\n }\n\n if (!(await isPortAvailable(httpPort))) {\n throw new Error(\n `Port ${httpPort} is already in use. Try:\\n` +\n ` - Run with sudo if port < 1024\\n` +\n ` - Use --http-port <port> to specify a different port\\n` +\n ` - Stop the process using port ${httpPort}`\n );\n }\n\n // Create proxy engine — dynamic mode if no explicit ports\n const proxyEngine = createProxyEngine({\n timeout,\n routes,\n nameMap,\n dynamic: isDynamic,\n });\n\n // Parse basic auth credentials if provided\n let authCredentials: { user: string; pass: string } | null = null;\n if (auth) {\n const [user, pass] = auth.split(\":\");\n if (!user || !pass) {\n throw new Error(\"--auth must be in format user:pass\");\n }\n authCredentials = { user, pass };\n }\n\n // Parse allowed IPs\n const allowedIps = ipAllow ? new Set(ipAllow) : null;\n\n // Middleware: auth check\n function checkAuth(req: IncomingMessage, res: ServerResponse): boolean {\n if (!authCredentials) return true;\n\n const authHeader = req.headers.authorization;\n if (!authHeader || !authHeader.startsWith(\"Basic \")) {\n res.writeHead(401, {\n \"WWW-Authenticate\": 'Basic realm=\"Porterman\"',\n \"Content-Type\": \"text/plain\",\n });\n res.end(\"Authentication required\");\n return false;\n }\n\n const decoded = Buffer.from(authHeader.slice(6), \"base64\").toString();\n const [user, pass] = decoded.split(\":\");\n if (user !== authCredentials.user || pass !== authCredentials.pass) {\n res.writeHead(403, { \"Content-Type\": \"text/plain\" });\n res.end(\"Forbidden\");\n return false;\n }\n\n return true;\n }\n\n // Middleware: IP allow check\n function checkIpAllow(req: IncomingMessage, res: ServerResponse): boolean {\n if (!allowedIps) return true;\n\n const clientIp =\n (req.headers[\"x-forwarded-for\"] as string)?.split(\",\")[0]?.trim() ??\n req.socket.remoteAddress ??\n \"\";\n\n // Normalize IPv6-mapped IPv4\n const normalizedIp = clientIp.replace(/^::ffff:/, \"\");\n\n if (!allowedIps.has(normalizedIp)) {\n res.writeHead(403, { \"Content-Type\": \"text/plain\" });\n res.end(\"Forbidden: IP not allowed\");\n return false;\n }\n\n return true;\n }\n\n // HTTP request handler\n function httpRequestHandler(req: IncomingMessage, res: ServerResponse): void {\n // Handle ACME challenges\n if (req.url) {\n const challengeResponse = handleAcmeChallenge(req.url);\n if (challengeResponse) {\n res.writeHead(200, { \"Content-Type\": \"text/plain\" });\n res.end(challengeResponse);\n return;\n }\n }\n\n if (noSsl) {\n // In no-ssl mode, HTTP server handles proxying\n if (!checkIpAllow(req, res)) return;\n if (!checkAuth(req, res)) return;\n proxyEngine.handleRequest(req, res);\n return;\n }\n\n // Redirect HTTP to HTTPS\n const host = req.headers.host ?? \"\";\n const httpsUrl = `https://${host.split(\":\")[0]}${httpsPort !== 443 ? `:${httpsPort}` : \"\"}${req.url ?? \"/\"}`;\n res.writeHead(301, { Location: httpsUrl });\n res.end();\n }\n\n // Start HTTP server\n const httpServer = createHttpServer(httpRequestHandler);\n\n // Handle WebSocket upgrades on HTTP server (no-ssl mode)\n if (noSsl) {\n httpServer.on(\"upgrade\", (req, socket, head) => {\n proxyEngine.handleUpgrade(req, socket, head);\n });\n }\n\n let httpsServer: ReturnType<typeof createHttpsServer> | null = null;\n const certCache = new Map<string, CertResult>();\n\n // Track in-flight cert requests to avoid duplicate concurrent requests\n const certPending = new Map<string, Promise<CertResult>>();\n\n /**\n * Get or provision a certificate for a hostname.\n * Caches results and deduplicates concurrent requests for the same hostname.\n */\n async function getOrProvisionCert(hostname: string): Promise<CertResult> {\n // Check in-memory cache\n if (certCache.has(hostname)) {\n return certCache.get(hostname)!;\n }\n\n // Check if there's already a pending request\n if (certPending.has(hostname)) {\n return certPending.get(hostname)!;\n }\n\n // Start provisioning\n const promise = getCertificate(hostname, { staging }).then((cert) => {\n certCache.set(hostname, cert);\n certPending.delete(hostname);\n return cert;\n }).catch((err) => {\n certPending.delete(hostname);\n throw err;\n });\n\n certPending.set(hostname, promise);\n return promise;\n }\n\n if (!noSsl) {\n // Pre-obtain certificates for explicitly listed hostnames\n if (routes.length > 0) {\n logger.info(\"Obtaining SSL certificates...\");\n for (const route of routes) {\n const cert = await getOrProvisionCert(route.hostname);\n if (cert.selfSigned) {\n logger.warn(\n `Using self-signed certificate for ${route.hostname} (browsers will show a warning)`\n );\n }\n }\n }\n\n // Generate a wildcard self-signed cert as default fallback for SNI\n // This ensures the HTTPS server can start even with no pre-registered routes\n let defaultCert: CertResult;\n if (certCache.size > 0) {\n defaultCert = certCache.values().next().value!;\n } else {\n // Dynamic mode with no pre-registered ports — create a default cert\n const defaultHostname = `porterman-${dashedIp}.sslip.io`;\n defaultCert = await getCertificate(defaultHostname, { staging });\n certCache.set(defaultHostname, defaultCert);\n }\n\n // SNI callback: dynamically serve correct cert per hostname, provision on-demand\n const sniCallback = (\n servername: string,\n callback: (err: Error | null, ctx?: SecureContext) => void\n ): void => {\n // Check cache first (fast path)\n const cached = certCache.get(servername);\n if (cached) {\n callback(null, createSecureContext({ key: cached.key, cert: cached.cert }));\n return;\n }\n\n // On-demand cert provisioning for dynamic mode\n getOrProvisionCert(servername)\n .then((cert) => {\n callback(null, createSecureContext({ key: cert.key, cert: cert.cert }));\n })\n .catch((err) => {\n logger.verbose(`SNI cert provision failed for ${servername}: ${err.message}`);\n // Fall back to default cert so the connection doesn't just hang\n callback(null, createSecureContext({ key: defaultCert.key, cert: defaultCert.cert }));\n });\n };\n\n httpsServer = createHttpsServer(\n {\n key: defaultCert.key,\n cert: defaultCert.cert,\n SNICallback: sniCallback,\n },\n (req, res) => {\n if (!checkIpAllow(req, res)) return;\n if (!checkAuth(req, res)) return;\n proxyEngine.handleRequest(req, res);\n }\n );\n\n // Handle WebSocket upgrades on HTTPS\n httpsServer.on(\"upgrade\", (req, socket, head) => {\n proxyEngine.handleUpgrade(req, socket, head);\n });\n\n // Start HTTPS server — bind to 0.0.0.0 explicitly\n await new Promise<void>((resolve, reject) => {\n httpsServer!.listen(httpsPort, \"0.0.0.0\", () => resolve());\n httpsServer!.once(\"error\", reject);\n });\n\n logger.verbose(`HTTPS server listening on 0.0.0.0:${httpsPort}`);\n }\n\n // Start HTTP server — bind to 0.0.0.0 explicitly\n await new Promise<void>((resolve, reject) => {\n httpServer.listen(httpPort, \"0.0.0.0\", () => resolve());\n httpServer.once(\"error\", reject);\n });\n\n logger.verbose(`HTTP server listening on 0.0.0.0:${httpPort}`);\n\n // Write PID file\n await writePidFile(process.pid);\n\n // Build URL map for explicit ports\n const urls = new Map<number, string>();\n for (const route of routes) {\n const protocol = noSsl ? \"http\" : \"https\";\n const portSuffix =\n (!noSsl && httpsPort !== 443)\n ? `:${httpsPort}`\n : (noSsl && httpPort !== 80)\n ? `:${httpPort}`\n : \"\";\n urls.set(route.targetPort, `${protocol}://${route.hostname}${portSuffix}`);\n }\n\n // Print ready message\n logger.blank();\n logger.success(\"Ready!\");\n logger.blank();\n\n if (isDynamic) {\n const protocol = noSsl ? \"http\" : \"https\";\n const portSuffix =\n (!noSsl && httpsPort !== 443)\n ? `:${httpsPort}`\n : (noSsl && httpPort !== 80)\n ? `:${httpPort}`\n : \"\";\n console.log(` Any port is now accessible via:`);\n console.log(` ${protocol}://{port}-${dashedIp}.sslip.io${portSuffix}`);\n logger.blank();\n console.log(` Examples:`);\n for (const examplePort of [3000, 5173, 8080]) {\n const exUrl = `${protocol}://${examplePort}-${dashedIp}.sslip.io${portSuffix}`;\n logger.link(`http://localhost:${examplePort}`, exUrl);\n }\n } else {\n for (const route of routes) {\n const url = urls.get(route.targetPort)!;\n logger.link(`http://localhost:${route.targetPort}`, url);\n }\n }\n\n logger.blank();\n console.log(\" Press Ctrl+C to stop\");\n logger.blank();\n\n // Graceful shutdown\n async function close(): Promise<void> {\n logger.info(\"Shutting down...\");\n proxyEngine.close();\n\n const closePromises: Promise<void>[] = [];\n\n closePromises.push(\n new Promise<void>((resolve) => httpServer.close(() => resolve()))\n );\n\n if (httpsServer) {\n closePromises.push(\n new Promise<void>((resolve) => httpsServer!.close(() => resolve()))\n );\n }\n\n await Promise.all(closePromises);\n\n // Clean up PID file\n try {\n const { unlink } = await import(\"node:fs/promises\");\n await unlink(paths.pidFile);\n } catch {}\n\n logger.success(\"Stopped\");\n }\n\n return { close, urls, dashedIp, publicIp };\n}\n"],"mappings":"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","names":["readFile","mkdir","writeFile","existsSync","existsSync","readFile","mkdir","writeFile","host"]}
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
pidFileExists,
|
|
5
5
|
readPidFile,
|
|
6
6
|
startServer
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-NBKAOCKA.js";
|
|
8
8
|
|
|
9
9
|
// src/cli.ts
|
|
10
10
|
import cac from "cac";
|
|
@@ -29,15 +29,7 @@ var cli = cac("porterman");
|
|
|
29
29
|
cli.command("expose [...ports]", "Expose one or more local ports over HTTPS").option("-n, --name <name>", "Custom subdomain prefix (single port only)").option("--no-ssl", "HTTP only mode (skip SSL)").option("-v, --verbose", "Log all requests").option("--timeout <seconds>", "Proxy timeout in seconds", { default: 30 }).option("--host <ip>", "Override auto-detected public IP").option("--staging", "Use Let's Encrypt staging environment").option("--http-port <port>", "Custom HTTP port", { default: 80 }).option("--https-port <port>", "Custom HTTPS port", { default: 443 }).option("--auth <user:pass>", "Enable basic auth on exposed ports").option("--ip-allow <ips>", "Comma-separated list of allowed IPs").action(async (portsRaw, options) => {
|
|
30
30
|
logger.banner(version);
|
|
31
31
|
logger.blank();
|
|
32
|
-
|
|
33
|
-
logger.error("Please specify at least one port to expose.");
|
|
34
|
-
logger.blank();
|
|
35
|
-
console.log(" Usage: porterman expose <port> [port2] [port3]");
|
|
36
|
-
console.log(" Example: porterman expose 3000 8080");
|
|
37
|
-
logger.blank();
|
|
38
|
-
process.exit(1);
|
|
39
|
-
}
|
|
40
|
-
const ports = portsRaw.map((p) => {
|
|
32
|
+
const ports = (portsRaw ?? []).map((p) => {
|
|
41
33
|
const num = parseInt(p, 10);
|
|
42
34
|
if (isNaN(num) || num < 1 || num > 65535) {
|
|
43
35
|
logger.error(`Invalid port: ${p}`);
|
|
@@ -45,6 +37,9 @@ cli.command("expose [...ports]", "Expose one or more local ports over HTTPS").op
|
|
|
45
37
|
}
|
|
46
38
|
return num;
|
|
47
39
|
});
|
|
40
|
+
if (ports.length === 0) {
|
|
41
|
+
logger.info("Dynamic mode: all ports will be proxied automatically");
|
|
42
|
+
}
|
|
48
43
|
const ipAllow = options.ipAllow ? options.ipAllow.split(",").map((ip) => ip.trim()) : void 0;
|
|
49
44
|
let server = null;
|
|
50
45
|
try {
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import cac from \"cac\";\nimport { readFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, join } from \"node:path\";\nimport { startServer } from \"./server.js\";\nimport { cleanCerts, getCertificate } from \"./certs.js\";\nimport { readPidFile, pidFileExists } from \"./config.js\";\nimport { logger } from \"./logger.js\";\n\n// Read version from package.json\nlet version = \"1.0.0\";\ntry {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // Try multiple paths since we might be in dist/ or src/\n for (const rel of [\"../package.json\", \"../../package.json\"]) {\n try {\n const pkg = JSON.parse(readFileSync(join(__dirname, rel), \"utf-8\"));\n version = pkg.version;\n break;\n } catch {}\n }\n} catch {}\n\nconst cli = cac(\"porterman\");\n\n// expose command\ncli\n .command(\"expose [...ports]\", \"Expose one or more local ports over HTTPS\")\n .option(\"-n, --name <name>\", \"Custom subdomain prefix (single port only)\")\n .option(\"--no-ssl\", \"HTTP only mode (skip SSL)\")\n .option(\"-v, --verbose\", \"Log all requests\")\n .option(\"--timeout <seconds>\", \"Proxy timeout in seconds\", { default: 30 })\n .option(\"--host <ip>\", \"Override auto-detected public IP\")\n .option(\"--staging\", \"Use Let's Encrypt staging environment\")\n .option(\"--http-port <port>\", \"Custom HTTP port\", { default: 80 })\n .option(\"--https-port <port>\", \"Custom HTTPS port\", { default: 443 })\n .option(\"--auth <user:pass>\", \"Enable basic auth on exposed ports\")\n .option(\"--ip-allow <ips>\", \"Comma-separated list of allowed IPs\")\n .action(async (portsRaw: string[], options) => {\n logger.banner(version);\n logger.blank();\n\n
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import cac from \"cac\";\nimport { readFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, join } from \"node:path\";\nimport { startServer } from \"./server.js\";\nimport { cleanCerts, getCertificate } from \"./certs.js\";\nimport { readPidFile, pidFileExists } from \"./config.js\";\nimport { logger } from \"./logger.js\";\n\n// Read version from package.json\nlet version = \"1.0.0\";\ntry {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // Try multiple paths since we might be in dist/ or src/\n for (const rel of [\"../package.json\", \"../../package.json\"]) {\n try {\n const pkg = JSON.parse(readFileSync(join(__dirname, rel), \"utf-8\"));\n version = pkg.version;\n break;\n } catch {}\n }\n} catch {}\n\nconst cli = cac(\"porterman\");\n\n// expose command\ncli\n .command(\"expose [...ports]\", \"Expose one or more local ports over HTTPS\")\n .option(\"-n, --name <name>\", \"Custom subdomain prefix (single port only)\")\n .option(\"--no-ssl\", \"HTTP only mode (skip SSL)\")\n .option(\"-v, --verbose\", \"Log all requests\")\n .option(\"--timeout <seconds>\", \"Proxy timeout in seconds\", { default: 30 })\n .option(\"--host <ip>\", \"Override auto-detected public IP\")\n .option(\"--staging\", \"Use Let's Encrypt staging environment\")\n .option(\"--http-port <port>\", \"Custom HTTP port\", { default: 80 })\n .option(\"--https-port <port>\", \"Custom HTTPS port\", { default: 443 })\n .option(\"--auth <user:pass>\", \"Enable basic auth on exposed ports\")\n .option(\"--ip-allow <ips>\", \"Comma-separated list of allowed IPs\")\n .action(async (portsRaw: string[], options) => {\n logger.banner(version);\n logger.blank();\n\n // No ports = dynamic mode (proxy any port from hostname)\n const ports = (portsRaw ?? []).map((p) => {\n const num = parseInt(p, 10);\n if (isNaN(num) || num < 1 || num > 65535) {\n logger.error(`Invalid port: ${p}`);\n process.exit(1);\n }\n return num;\n });\n\n if (ports.length === 0) {\n logger.info(\"Dynamic mode: all ports will be proxied automatically\");\n }\n\n const ipAllow = options.ipAllow\n ? (options.ipAllow as string).split(\",\").map((ip: string) => ip.trim())\n : undefined;\n\n let server: Awaited<ReturnType<typeof startServer>> | null = null;\n\n try {\n server = await startServer({\n ports,\n name: options.name,\n noSsl: options.ssl === false,\n verbose: options.verbose,\n timeout: Number(options.timeout),\n host: options.host,\n staging: options.staging,\n httpPort: Number(options.httpPort),\n httpsPort: Number(options.httpsPort),\n auth: options.auth,\n ipAllow,\n });\n } catch (err) {\n logger.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n\n // Handle graceful shutdown\n const shutdown = async () => {\n if (server) {\n await server.close();\n }\n process.exit(0);\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n });\n\n// status command\ncli.command(\"status\", \"Show running Porterman instance info\").action(async () => {\n if (!pidFileExists()) {\n logger.info(\"No Porterman instance is running.\");\n return;\n }\n\n const pid = await readPidFile();\n if (pid === null) {\n logger.info(\"No Porterman instance is running.\");\n return;\n }\n\n // Check if process is actually running\n try {\n process.kill(pid, 0);\n logger.success(`Porterman is running (PID: ${pid})`);\n } catch {\n logger.info(\"No Porterman instance is running (stale PID file).\");\n }\n});\n\n// stop command\ncli.command(\"stop\", \"Stop running Porterman instance\").action(async () => {\n if (!pidFileExists()) {\n logger.info(\"No Porterman instance is running.\");\n return;\n }\n\n const pid = await readPidFile();\n if (pid === null) {\n logger.info(\"No Porterman instance is running.\");\n return;\n }\n\n try {\n process.kill(pid, \"SIGTERM\");\n logger.success(`Porterman stopped (PID: ${pid})`);\n } catch {\n logger.info(\"No Porterman instance is running (stale PID file).\");\n }\n});\n\n// certs command\ncli\n .command(\"certs\", \"Manage SSL certificates\")\n .option(\"--renew\", \"Force certificate renewal\")\n .option(\"--clean\", \"Remove all cached certificates\")\n .action(async (options) => {\n if (options.clean) {\n await cleanCerts();\n return;\n }\n\n if (options.renew) {\n logger.info(\"Certificate renewal is done automatically when using 'expose'.\");\n logger.info(\"Use 'porterman expose <port> --staging' to test with Let's Encrypt staging.\");\n return;\n }\n\n logger.info(\"Use --renew to force renewal or --clean to remove all cached certs.\");\n });\n\n// Default command (show help)\ncli.command(\"\", \"Show help\").action(() => {\n cli.outputHelp();\n});\n\ncli.help();\ncli.version(version);\n\ncli.parse();\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as node_stream from 'node:stream';
|
|
|
2
2
|
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
3
3
|
|
|
4
4
|
interface ServerOptions {
|
|
5
|
-
ports
|
|
5
|
+
ports?: number[];
|
|
6
6
|
name?: string;
|
|
7
7
|
noSsl?: boolean;
|
|
8
8
|
verbose?: boolean;
|
|
@@ -17,6 +17,8 @@ interface ServerOptions {
|
|
|
17
17
|
interface PortermanServer {
|
|
18
18
|
close(): Promise<void>;
|
|
19
19
|
urls: Map<number, string>;
|
|
20
|
+
dashedIp: string;
|
|
21
|
+
publicIp: string;
|
|
20
22
|
}
|
|
21
23
|
declare function startServer(options: ServerOptions): Promise<PortermanServer>;
|
|
22
24
|
|
|
@@ -58,13 +60,15 @@ interface ProxyRoute {
|
|
|
58
60
|
}
|
|
59
61
|
interface ProxyOptions {
|
|
60
62
|
timeout: number;
|
|
61
|
-
routes
|
|
63
|
+
routes?: ProxyRoute[];
|
|
62
64
|
nameMap?: Map<string, number>;
|
|
65
|
+
dynamic?: boolean;
|
|
63
66
|
}
|
|
64
67
|
declare function createProxyEngine(options: ProxyOptions): {
|
|
65
68
|
handleRequest: (req: IncomingMessage, res: ServerResponse) => boolean;
|
|
66
69
|
handleUpgrade: (req: IncomingMessage, socket: node_stream.Duplex, head: Buffer) => boolean;
|
|
67
70
|
close: () => void;
|
|
71
|
+
resolveTargetPort: (host: string) => number | null;
|
|
68
72
|
};
|
|
69
73
|
|
|
70
74
|
declare function setVerbose(enabled: boolean): void;
|
|
@@ -126,4 +130,4 @@ declare function isPortAvailable(port: number): Promise<boolean>;
|
|
|
126
130
|
*/
|
|
127
131
|
declare function isValidPort(port: number): boolean;
|
|
128
132
|
|
|
129
|
-
export { type CertResult, type PortermanConfig, type PortermanServer, type ProxyRoute, type ServerOptions, cleanCerts, createProxyEngine, detectPublicIp, getCertificate, getDashedIp, ipToDashed, isPortAvailable, isPrivateIp, isValidPort, logger, makeHostname, parsePortFromHost, paths, setVerbose, startServer };
|
|
133
|
+
export { type CertResult, type PortermanConfig, type PortermanServer, type ProxyOptions, type ProxyRoute, type ServerOptions, cleanCerts, createProxyEngine, detectPublicIp, getCertificate, getDashedIp, ipToDashed, isPortAvailable, isPrivateIp, isValidPort, logger, makeHostname, parsePortFromHost, paths, setVerbose, startServer };
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/logger.ts","../src/utils.ts","../src/ip.ts","../src/config.ts","../src/certs.ts","../src/proxy.ts","../src/server.ts"],"sourcesContent":["const RESET = \"\\x1b[0m\";\nconst BOLD = \"\\x1b[1m\";\nconst DIM = \"\\x1b[2m\";\nconst RED = \"\\x1b[31m\";\nconst GREEN = \"\\x1b[32m\";\nconst YELLOW = \"\\x1b[33m\";\nconst BLUE = \"\\x1b[34m\";\nconst CYAN = \"\\x1b[36m\";\nconst WHITE = \"\\x1b[37m\";\n\nlet verboseEnabled = false;\n\nexport function setVerbose(enabled: boolean): void {\n verboseEnabled = enabled;\n}\n\nexport function isVerbose(): boolean {\n return verboseEnabled;\n}\n\nexport const logger = {\n info(message: string): void {\n console.log(`${CYAN}ℹ${RESET} ${message}`);\n },\n\n success(message: string): void {\n console.log(`${GREEN}✅${RESET} ${message}`);\n },\n\n warn(message: string): void {\n console.log(`${YELLOW}⚠${RESET} ${message}`);\n },\n\n error(message: string): void {\n console.error(`${RED}✖${RESET} ${message}`);\n },\n\n rocket(message: string): void {\n console.log(`🚀 ${message}`);\n },\n\n link(label: string, url: string): void {\n console.log(` ${GREEN}${url}${RESET} → ${DIM}${label}${RESET}`);\n },\n\n verbose(message: string): void {\n if (verboseEnabled) {\n console.log(`${DIM}[verbose] ${message}${RESET}`);\n }\n },\n\n request(method: string, host: string, path: string, status: number): void {\n if (!verboseEnabled) return;\n const color = status < 400 ? GREEN : status < 500 ? YELLOW : RED;\n const time = new Date().toISOString().slice(11, 19);\n console.log(\n `${DIM}${time}${RESET} ${BOLD}${method}${RESET} ${host}${path} ${color}${status}${RESET}`\n );\n },\n\n banner(version: string): void {\n console.log(`\\n${BOLD}🚪 Porterman v${version}${RESET}`);\n },\n\n blank(): void {\n console.log();\n },\n};\n","import { createServer, type Server } from \"node:net\";\n\n/**\n * Convert a dotted IP address to dashed format.\n * 85.100.50.25 → 85-100-50-25\n */\nexport function ipToDashed(ip: string): string {\n return ip.replace(/\\./g, \"-\").replace(/:/g, \"-\");\n}\n\n/**\n * Generate a sslip.io hostname for a given port and IP.\n */\nexport function makeHostname(\n port: number | string,\n dashedIp: string\n): string {\n return `${port}-${dashedIp}.sslip.io`;\n}\n\n/**\n * Parse the port number from a sslip.io hostname.\n * \"3000-85-100-50-25.sslip.io\" → 3000\n */\nexport function parsePortFromHost(host: string): number | null {\n // Remove port suffix if present (e.g., \":443\")\n const hostname = host.split(\":\")[0];\n // Extract the prefix before the first dash followed by IP-like pattern\n const match = hostname.match(/^(\\w+)-\\d+-/);\n if (!match) return null;\n const prefix = match[1];\n const port = parseInt(prefix, 10);\n return isNaN(port) ? null : port;\n}\n\n/**\n * Parse custom name from hostname.\n * \"myapp-85-100-50-25.sslip.io\" → \"myapp\"\n */\nexport function parsePrefixFromHost(host: string): string | null {\n const hostname = host.split(\":\")[0];\n const match = hostname.match(/^(\\w+)-\\d+-/);\n return match ? match[1] : null;\n}\n\n/**\n * Check if an IP is a private/reserved address.\n */\nexport function isPrivateIp(ip: string): boolean {\n const parts = ip.split(\".\").map(Number);\n if (parts.length !== 4 || parts.some((p) => isNaN(p))) return true;\n\n // 10.0.0.0/8\n if (parts[0] === 10) return true;\n // 172.16.0.0/12\n if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true;\n // 192.168.0.0/16\n if (parts[0] === 192 && parts[1] === 168) return true;\n // 127.0.0.0/8\n if (parts[0] === 127) return true;\n // 0.0.0.0\n if (parts.every((p) => p === 0)) return true;\n // 169.254.0.0/16 (link-local)\n if (parts[0] === 169 && parts[1] === 254) return true;\n\n return false;\n}\n\n/**\n * Check if a port is available.\n */\nexport function isPortAvailable(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const server = createServer();\n server.once(\"error\", () => resolve(false));\n server.once(\"listening\", () => {\n server.close(() => resolve(true));\n });\n server.listen(port, \"0.0.0.0\");\n });\n}\n\n/**\n * Validate that a port number is valid.\n */\nexport function isValidPort(port: number): boolean {\n return Number.isInteger(port) && port >= 1 && port <= 65535;\n}\n\n/**\n * Sleep for a given number of milliseconds.\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { logger } from \"./logger.js\";\nimport { isPrivateIp, ipToDashed } from \"./utils.js\";\n\nconst IP_SERVICES = [\n \"https://api.ipify.org\",\n \"https://ifconfig.me/ip\",\n \"https://icanhazip.com\",\n];\n\nlet cachedIp: string | null = null;\n\n/**\n * Fetch public IP from a single service with timeout.\n */\nasync function fetchIpFrom(url: string): Promise<string | null> {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 5000);\n const res = await fetch(url, {\n signal: controller.signal,\n headers: { Accept: \"text/plain\" },\n });\n clearTimeout(timeout);\n if (!res.ok) return null;\n const text = await res.text();\n return text.trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Detect the machine's public IP address using multiple services with fallback.\n * Results are cached for the session lifetime.\n */\nexport async function detectPublicIp(): Promise<string> {\n if (cachedIp) return cachedIp;\n\n for (const service of IP_SERVICES) {\n logger.verbose(`Trying IP detection via ${service}`);\n const ip = await fetchIpFrom(service);\n if (ip && /^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(ip)) {\n if (isPrivateIp(ip)) {\n throw new Error(\n `Detected private IP address (${ip}). Porterman requires a public IP.\\n` +\n \"If you're behind NAT, you need a machine with a direct public IP (VPS, cloud instance, etc.).\\n\" +\n \"You can also specify your IP manually with --host <ip>.\"\n );\n }\n cachedIp = ip;\n logger.verbose(`Public IP detected: ${ip}`);\n return ip;\n }\n }\n\n throw new Error(\n \"Could not detect public IP address. All detection services failed.\\n\" +\n \"Please specify your IP manually with --host <ip>.\"\n );\n}\n\n/**\n * Get the dashed format of the public IP (e.g., 85-100-50-25).\n */\nexport async function getDashedIp(overrideIp?: string): Promise<string> {\n const ip = overrideIp ?? (await detectPublicIp());\n if (overrideIp && isPrivateIp(overrideIp)) {\n logger.warn(\n `Specified IP ${overrideIp} appears to be a private address. sslip.io may not work correctly.`\n );\n }\n return ipToDashed(ip);\n}\n\n/**\n * Clear the cached IP (useful for testing).\n */\nexport function clearIpCache(): void {\n cachedIp = null;\n}\n","import { mkdir, readFile, writeFile, chmod } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nconst PORTERMAN_DIR = join(homedir(), \".porterman\");\nconst CONFIG_FILE = join(PORTERMAN_DIR, \"config.json\");\nconst CERTS_DIR = join(PORTERMAN_DIR, \"certs\");\nconst ACCOUNT_KEY_FILE = join(PORTERMAN_DIR, \"account.pem\");\nconst PID_FILE = join(PORTERMAN_DIR, \"porterman.pid\");\n\nexport interface PortermanConfig {\n defaultTimeout?: number;\n defaultHttpPort?: number;\n defaultHttpsPort?: number;\n}\n\nexport const paths = {\n base: PORTERMAN_DIR,\n config: CONFIG_FILE,\n certs: CERTS_DIR,\n accountKey: ACCOUNT_KEY_FILE,\n pidFile: PID_FILE,\n\n certDir(hostname: string): string {\n return join(CERTS_DIR, hostname);\n },\n\n certFile(hostname: string): string {\n return join(CERTS_DIR, hostname, \"cert.pem\");\n },\n\n keyFile(hostname: string): string {\n return join(CERTS_DIR, hostname, \"privkey.pem\");\n },\n\n chainFile(hostname: string): string {\n return join(CERTS_DIR, hostname, \"chain.pem\");\n },\n\n metaFile(hostname: string): string {\n return join(CERTS_DIR, hostname, \"meta.json\");\n },\n};\n\nexport async function ensureDirs(): Promise<void> {\n await mkdir(PORTERMAN_DIR, { recursive: true });\n await mkdir(CERTS_DIR, { recursive: true });\n}\n\nexport async function loadConfig(): Promise<PortermanConfig> {\n try {\n const data = await readFile(CONFIG_FILE, \"utf-8\");\n return JSON.parse(data);\n } catch {\n return {};\n }\n}\n\nexport async function saveConfig(config: PortermanConfig): Promise<void> {\n await ensureDirs();\n await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2));\n}\n\nexport async function writeSecureFile(\n filePath: string,\n content: string\n): Promise<void> {\n await writeFile(filePath, content, { mode: 0o600 });\n}\n\nexport async function writePidFile(pid: number): Promise<void> {\n await ensureDirs();\n await writeFile(PID_FILE, String(pid));\n}\n\nexport async function readPidFile(): Promise<number | null> {\n try {\n const data = await readFile(PID_FILE, \"utf-8\");\n const pid = parseInt(data.trim(), 10);\n return isNaN(pid) ? null : pid;\n } catch {\n return null;\n }\n}\n\nexport function pidFileExists(): boolean {\n return existsSync(PID_FILE);\n}\n","import * as acme from \"acme-client\";\nimport { readFile, mkdir, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { createServer, type Server } from \"node:http\";\nimport { paths, ensureDirs, writeSecureFile } from \"./config.js\";\nimport { logger } from \"./logger.js\";\n\ninterface CertMeta {\n issuedAt: string;\n expiresAt: string;\n domains: string[];\n}\n\ninterface CertFiles {\n key: string;\n cert: string;\n chain: string;\n}\n\n// In-memory store for ACME HTTP-01 challenge tokens\nconst challengeTokens = new Map<string, string>();\n\n/**\n * Get or create the ACME account private key.\n */\nasync function getAccountKey(): Promise<Buffer> {\n await ensureDirs();\n if (existsSync(paths.accountKey)) {\n return readFile(paths.accountKey);\n }\n const key = await acme.crypto.createPrivateKey();\n await writeSecureFile(paths.accountKey, key.toString());\n return key;\n}\n\n/**\n * Check if an existing certificate is still valid (>30 days remaining).\n */\nasync function isCertValid(hostname: string): Promise<boolean> {\n const metaPath = paths.metaFile(hostname);\n if (!existsSync(metaPath)) return false;\n\n try {\n const data = await readFile(metaPath, \"utf-8\");\n const meta: CertMeta = JSON.parse(data);\n const expires = new Date(meta.expiresAt);\n const daysRemaining =\n (expires.getTime() - Date.now()) / (1000 * 60 * 60 * 24);\n if (daysRemaining > 30) {\n logger.verbose(\n `Certificate for ${hostname} valid for ${Math.floor(daysRemaining)} more days`\n );\n return true;\n }\n logger.verbose(\n `Certificate for ${hostname} expires in ${Math.floor(daysRemaining)} days, needs renewal`\n );\n return false;\n } catch {\n return false;\n }\n}\n\n/**\n * Load existing certificate files from disk.\n */\nasync function loadCertFromDisk(hostname: string): Promise<CertFiles> {\n const [key, cert, chain] = await Promise.all([\n readFile(paths.keyFile(hostname), \"utf-8\"),\n readFile(paths.certFile(hostname), \"utf-8\"),\n readFile(paths.chainFile(hostname), \"utf-8\"),\n ]);\n return { key, cert, chain };\n}\n\n/**\n * Create the HTTP-01 challenge handler for the ACME server.\n * This is used as middleware to respond to /.well-known/acme-challenge/ requests.\n */\nexport function handleAcmeChallenge(\n url: string\n): string | null {\n const prefix = \"/.well-known/acme-challenge/\";\n if (!url.startsWith(prefix)) return null;\n const token = url.slice(prefix.length);\n return challengeTokens.get(token) ?? null;\n}\n\n/**\n * Obtain a certificate for a hostname via Let's Encrypt ACME.\n */\nasync function obtainCert(\n hostname: string,\n staging: boolean\n): Promise<CertFiles> {\n const accountKey = await getAccountKey();\n\n const directoryUrl = staging\n ? acme.directory.letsencrypt.staging\n : acme.directory.letsencrypt.production;\n\n const client = new acme.Client({\n directoryUrl,\n accountKey,\n });\n\n // Create a CSR\n const [certKey, csr] = await acme.crypto.createCsr({\n commonName: hostname,\n });\n\n // Order the certificate\n const cert = await client.auto({\n csr,\n email: \"porterman@localhost\",\n termsOfServiceAgreed: true,\n challengeCreateFn: async (_authz, _challenge, keyAuthorization) => {\n const token = _challenge.token;\n logger.verbose(`Setting ACME challenge token: ${token}`);\n challengeTokens.set(token, keyAuthorization);\n },\n challengeRemoveFn: async (_authz, _challenge) => {\n const token = _challenge.token;\n challengeTokens.delete(token);\n },\n challengePriority: [\"http-01\"],\n });\n\n // Save certificate files\n const certDir = paths.certDir(hostname);\n await mkdir(certDir, { recursive: true });\n\n const keyStr = certKey.toString();\n const certStr = cert.toString();\n\n await Promise.all([\n writeSecureFile(paths.keyFile(hostname), keyStr),\n writeFile(paths.certFile(hostname), certStr),\n writeFile(paths.chainFile(hostname), certStr),\n ]);\n\n // Save metadata\n const now = new Date();\n const meta: CertMeta = {\n issuedAt: now.toISOString(),\n // Let's Encrypt certs are valid for 90 days\n expiresAt: new Date(now.getTime() + 90 * 24 * 60 * 60 * 1000).toISOString(),\n domains: [hostname],\n };\n await writeFile(paths.metaFile(hostname), JSON.stringify(meta, null, 2));\n\n return { key: keyStr, cert: certStr, chain: certStr };\n}\n\n/**\n * Generate a self-signed certificate as a fallback.\n */\nasync function generateSelfSigned(hostname: string): Promise<CertFiles> {\n const { exec } = await import(\"node:child_process\");\n const { promisify } = await import(\"node:util\");\n const execAsync = promisify(exec);\n\n const certDir = paths.certDir(hostname);\n await mkdir(certDir, { recursive: true });\n\n const keyPath = paths.keyFile(hostname);\n const certPath = paths.certFile(hostname);\n\n await execAsync(\n `openssl req -x509 -newkey rsa:2048 -keyout \"${keyPath}\" -out \"${certPath}\" ` +\n `-days 365 -nodes -subj \"/CN=${hostname}\" 2>/dev/null`\n );\n\n await execAsync(`chmod 600 \"${keyPath}\"`);\n\n const [key, cert] = await Promise.all([\n readFile(keyPath, \"utf-8\"),\n readFile(certPath, \"utf-8\"),\n ]);\n\n // Write chain (same as cert for self-signed)\n await writeFile(paths.chainFile(hostname), cert);\n\n const now = new Date();\n const meta: CertMeta = {\n issuedAt: now.toISOString(),\n expiresAt: new Date(\n now.getTime() + 365 * 24 * 60 * 60 * 1000\n ).toISOString(),\n domains: [hostname],\n };\n await writeFile(paths.metaFile(hostname), JSON.stringify(meta, null, 2));\n\n return { key, cert, chain: cert };\n}\n\nexport interface CertResult {\n key: string;\n cert: string;\n chain: string;\n selfSigned: boolean;\n}\n\n/**\n * Get a valid certificate for a hostname.\n * - First checks for a cached valid cert\n * - Then tries ACME/Let's Encrypt\n * - Falls back to self-signed if ACME fails\n */\nexport async function getCertificate(\n hostname: string,\n options: { staging?: boolean; forceRenew?: boolean } = {}\n): Promise<CertResult> {\n await ensureDirs();\n\n // Check for existing valid cert\n if (!options.forceRenew && (await isCertValid(hostname))) {\n logger.verbose(`Using cached certificate for ${hostname}`);\n const files = await loadCertFromDisk(hostname);\n return { ...files, selfSigned: false };\n }\n\n // Try ACME\n try {\n logger.info(`Obtaining SSL certificate for ${hostname}...`);\n const files = await obtainCert(hostname, options.staging ?? false);\n logger.success(`Certificate obtained for ${hostname}`);\n return { ...files, selfSigned: false };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.warn(`Let's Encrypt failed: ${message}`);\n logger.warn(\"Falling back to self-signed certificate\");\n logger.warn(\n \"Browsers will show a security warning. Consider using a custom domain.\"\n );\n\n const files = await generateSelfSigned(hostname);\n return { ...files, selfSigned: true };\n }\n}\n\n/**\n * Remove all cached certificates.\n */\nexport async function cleanCerts(): Promise<void> {\n const { rm } = await import(\"node:fs/promises\");\n if (existsSync(paths.certs)) {\n await rm(paths.certs, { recursive: true, force: true });\n await mkdir(paths.certs, { recursive: true });\n logger.success(\"All cached certificates removed\");\n }\n}\n","import httpProxy from \"http-proxy\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { logger } from \"./logger.js\";\nimport { parsePortFromHost } from \"./utils.js\";\n\nexport interface ProxyRoute {\n hostname: string;\n targetPort: number;\n name?: string;\n}\n\ninterface ProxyOptions {\n timeout: number;\n routes: ProxyRoute[];\n nameMap?: Map<string, number>; // name → target port\n}\n\nconst ERROR_502_HTML = (port: number) => `<!DOCTYPE html>\n<html>\n<head><title>502 Bad Gateway</title>\n<style>\n body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; max-width: 600px; margin: 80px auto; padding: 0 20px; color: #333; }\n h1 { color: #e74c3c; }\n code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; }\n</style>\n</head>\n<body>\n <h1>502 Bad Gateway</h1>\n <p>Nothing is running on <code>localhost:${port}</code></p>\n <p>Make sure your application is started and listening on port <strong>${port}</strong>.</p>\n <hr>\n <p><small>Porterman</small></p>\n</body>\n</html>`;\n\nexport function createProxyEngine(options: ProxyOptions) {\n const { timeout, routes, nameMap } = options;\n\n // Build port lookup: hostname → target port\n const routeMap = new Map<string, number>();\n for (const route of routes) {\n routeMap.set(route.hostname, route.targetPort);\n }\n\n const proxy = httpProxy.createProxyServer({\n xfwd: true, // sets X-Forwarded-* headers\n ws: true,\n proxyTimeout: timeout * 1000,\n timeout: timeout * 1000,\n });\n\n proxy.on(\"error\", (err, req, res) => {\n const host = req.headers.host ?? \"unknown\";\n const targetPort = resolveTargetPort(host);\n logger.verbose(`Proxy error for ${host}: ${err.message}`);\n\n if (res && \"writeHead\" in res) {\n const serverRes = res as ServerResponse;\n if (!serverRes.headersSent) {\n serverRes.writeHead(502, { \"Content-Type\": \"text/html\" });\n serverRes.end(ERROR_502_HTML(targetPort ?? 0));\n }\n }\n });\n\n function resolveTargetPort(host: string): number | null {\n // Normalize: remove port suffix\n const hostname = host.split(\":\")[0];\n\n // Direct hostname match\n if (routeMap.has(hostname)) {\n return routeMap.get(hostname)!;\n }\n\n // Try parsing port from hostname\n const port = parsePortFromHost(host);\n if (port !== null) {\n // Verify this port is in our routes\n for (const route of routes) {\n if (route.targetPort === port) return port;\n }\n }\n\n // Check name map\n if (nameMap) {\n const prefix = hostname.split(\"-\")[0];\n if (nameMap.has(prefix)) {\n return nameMap.get(prefix)!;\n }\n }\n\n return null;\n }\n\n function handleRequest(req: IncomingMessage, res: ServerResponse): boolean {\n const host = req.headers.host;\n if (!host) {\n res.writeHead(400, { \"Content-Type\": \"text/plain\" });\n res.end(\"Bad Request: No Host header\");\n return false;\n }\n\n const targetPort = resolveTargetPort(host);\n if (targetPort === null) {\n res.writeHead(404, { \"Content-Type\": \"text/plain\" });\n res.end(`Not Found: No route configured for ${host}`);\n return false;\n }\n\n logger.verbose(`Proxying ${req.method} ${host}${req.url} → localhost:${targetPort}`);\n\n proxy.web(req, res, {\n target: `http://127.0.0.1:${targetPort}`,\n });\n\n return true;\n }\n\n function handleUpgrade(\n req: IncomingMessage,\n socket: import(\"node:stream\").Duplex,\n head: Buffer\n ): boolean {\n const host = req.headers.host;\n if (!host) return false;\n\n const targetPort = resolveTargetPort(host);\n if (targetPort === null) return false;\n\n logger.verbose(`WebSocket upgrade ${host} → localhost:${targetPort}`);\n\n proxy.ws(req, socket, head, {\n target: `http://127.0.0.1:${targetPort}`,\n });\n\n return true;\n }\n\n function close(): void {\n proxy.close();\n }\n\n return { handleRequest, handleUpgrade, close };\n}\n","import { createServer as createHttpServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createServer as createHttpsServer } from \"node:https\";\nimport { createSecureContext, type SecureContext } from \"node:tls\";\nimport { getDashedIp, detectPublicIp } from \"./ip.js\";\nimport { getCertificate, handleAcmeChallenge, type CertResult } from \"./certs.js\";\nimport { createProxyEngine, type ProxyRoute } from \"./proxy.js\";\nimport { makeHostname, isPortAvailable, isValidPort } from \"./utils.js\";\nimport { writePidFile, paths } from \"./config.js\";\nimport { logger, setVerbose } from \"./logger.js\";\nimport { readFileSync } from \"node:fs\";\n\nexport interface ServerOptions {\n ports: number[];\n name?: string;\n noSsl?: boolean;\n verbose?: boolean;\n timeout?: number;\n host?: string;\n staging?: boolean;\n httpPort?: number;\n httpsPort?: number;\n auth?: string;\n ipAllow?: string[];\n}\n\nexport interface PortermanServer {\n close(): Promise<void>;\n urls: Map<number, string>;\n}\n\nexport async function startServer(options: ServerOptions): Promise<PortermanServer> {\n const {\n ports,\n name,\n noSsl = false,\n verbose = false,\n timeout = 30,\n host,\n staging = false,\n httpPort = 80,\n httpsPort = 443,\n auth,\n ipAllow,\n } = options;\n\n setVerbose(verbose);\n\n // Validate ports\n for (const port of ports) {\n if (!isValidPort(port)) {\n throw new Error(`Invalid port number: ${port}`);\n }\n }\n\n if (name && ports.length > 1) {\n throw new Error(\"--name can only be used with a single port\");\n }\n\n // Detect public IP\n logger.info(\"Detecting public IP...\");\n const publicIp = host ?? (await detectPublicIp());\n const dashedIp = await getDashedIp(host);\n logger.info(`Public IP: ${publicIp}`);\n\n // Generate hostnames\n const routes: ProxyRoute[] = ports.map((port) => {\n const prefix = name && ports.length === 1 ? name : String(port);\n return {\n hostname: makeHostname(prefix, dashedIp),\n targetPort: port,\n name: name && ports.length === 1 ? name : undefined,\n };\n });\n\n // Build name map if using custom names\n const nameMap = new Map<string, number>();\n if (name && ports.length === 1) {\n nameMap.set(name, ports[0]);\n }\n\n // Check port availability\n if (!noSsl) {\n if (!(await isPortAvailable(httpsPort))) {\n throw new Error(\n `Port ${httpsPort} is already in use. Try:\\n` +\n ` - Run with sudo if port < 1024\\n` +\n ` - Use --https-port <port> to specify a different port\\n` +\n ` - Stop the process using port ${httpsPort}`\n );\n }\n }\n\n if (!(await isPortAvailable(httpPort))) {\n throw new Error(\n `Port ${httpPort} is already in use. Try:\\n` +\n ` - Run with sudo if port < 1024\\n` +\n ` - Use --http-port <port> to specify a different port\\n` +\n ` - Stop the process using port ${httpPort}`\n );\n }\n\n // Create proxy engine\n const proxyEngine = createProxyEngine({ timeout, routes, nameMap });\n\n // Parse basic auth credentials if provided\n let authCredentials: { user: string; pass: string } | null = null;\n if (auth) {\n const [user, pass] = auth.split(\":\");\n if (!user || !pass) {\n throw new Error(\"--auth must be in format user:pass\");\n }\n authCredentials = { user, pass };\n }\n\n // Parse allowed IPs\n const allowedIps = ipAllow ? new Set(ipAllow) : null;\n\n // Middleware: auth check\n function checkAuth(req: IncomingMessage, res: ServerResponse): boolean {\n if (!authCredentials) return true;\n\n const authHeader = req.headers.authorization;\n if (!authHeader || !authHeader.startsWith(\"Basic \")) {\n res.writeHead(401, {\n \"WWW-Authenticate\": 'Basic realm=\"Porterman\"',\n \"Content-Type\": \"text/plain\",\n });\n res.end(\"Authentication required\");\n return false;\n }\n\n const decoded = Buffer.from(authHeader.slice(6), \"base64\").toString();\n const [user, pass] = decoded.split(\":\");\n if (user !== authCredentials.user || pass !== authCredentials.pass) {\n res.writeHead(403, { \"Content-Type\": \"text/plain\" });\n res.end(\"Forbidden\");\n return false;\n }\n\n return true;\n }\n\n // Middleware: IP allow check\n function checkIpAllow(req: IncomingMessage, res: ServerResponse): boolean {\n if (!allowedIps) return true;\n\n const clientIp =\n (req.headers[\"x-forwarded-for\"] as string)?.split(\",\")[0]?.trim() ??\n req.socket.remoteAddress ??\n \"\";\n\n // Normalize IPv6-mapped IPv4\n const normalizedIp = clientIp.replace(/^::ffff:/, \"\");\n\n if (!allowedIps.has(normalizedIp)) {\n res.writeHead(403, { \"Content-Type\": \"text/plain\" });\n res.end(\"Forbidden: IP not allowed\");\n return false;\n }\n\n return true;\n }\n\n // HTTP request handler\n function httpRequestHandler(req: IncomingMessage, res: ServerResponse): void {\n // Handle ACME challenges\n if (req.url) {\n const challengeResponse = handleAcmeChallenge(req.url);\n if (challengeResponse) {\n res.writeHead(200, { \"Content-Type\": \"text/plain\" });\n res.end(challengeResponse);\n return;\n }\n }\n\n if (noSsl) {\n // In no-ssl mode, HTTP server handles proxying\n if (!checkIpAllow(req, res)) return;\n if (!checkAuth(req, res)) return;\n proxyEngine.handleRequest(req, res);\n return;\n }\n\n // Redirect HTTP to HTTPS\n const host = req.headers.host ?? \"\";\n const httpsUrl = `https://${host.split(\":\")[0]}${httpsPort !== 443 ? `:${httpsPort}` : \"\"}${req.url ?? \"/\"}`;\n res.writeHead(301, { Location: httpsUrl });\n res.end();\n }\n\n // Start HTTP server\n const httpServer = createHttpServer(httpRequestHandler);\n\n // Handle WebSocket upgrades on HTTP server (no-ssl mode)\n if (noSsl) {\n httpServer.on(\"upgrade\", (req, socket, head) => {\n proxyEngine.handleUpgrade(req, socket, head);\n });\n }\n\n let httpsServer: ReturnType<typeof createHttpsServer> | null = null;\n const certCache = new Map<string, CertResult>();\n\n if (!noSsl) {\n // Obtain certificates for all hostnames\n logger.info(\"Obtaining SSL certificates...\");\n\n for (const route of routes) {\n const cert = await getCertificate(route.hostname, { staging });\n certCache.set(route.hostname, cert);\n if (cert.selfSigned) {\n logger.warn(\n `Using self-signed certificate for ${route.hostname} (browsers will show a warning)`\n );\n }\n }\n\n // SNI callback for multi-cert support\n const sniCallback = (\n servername: string,\n callback: (err: Error | null, ctx?: SecureContext) => void\n ): void => {\n const cert = certCache.get(servername);\n if (cert) {\n const ctx = createSecureContext({\n key: cert.key,\n cert: cert.cert,\n });\n callback(null, ctx);\n } else {\n // Try to find a matching cert\n for (const [hostname, c] of certCache) {\n if (servername.endsWith(hostname.slice(hostname.indexOf(\".\")))) {\n const ctx = createSecureContext({\n key: c.key,\n cert: c.cert,\n });\n callback(null, ctx);\n return;\n }\n }\n callback(new Error(`No certificate for ${servername}`));\n }\n };\n\n // Use the first cert as default\n const defaultCert = certCache.values().next().value!;\n\n httpsServer = createHttpsServer(\n {\n key: defaultCert.key,\n cert: defaultCert.cert,\n SNICallback: sniCallback,\n },\n (req, res) => {\n if (!checkIpAllow(req, res)) return;\n if (!checkAuth(req, res)) return;\n proxyEngine.handleRequest(req, res);\n }\n );\n\n // Handle WebSocket upgrades on HTTPS\n httpsServer.on(\"upgrade\", (req, socket, head) => {\n proxyEngine.handleUpgrade(req, socket, head);\n });\n\n // Start HTTPS server\n await new Promise<void>((resolve, reject) => {\n httpsServer!.listen(httpsPort, () => resolve());\n httpsServer!.once(\"error\", reject);\n });\n\n logger.verbose(`HTTPS server listening on port ${httpsPort}`);\n }\n\n // Start HTTP server\n await new Promise<void>((resolve, reject) => {\n httpServer.listen(httpPort, () => resolve());\n httpServer.once(\"error\", reject);\n });\n\n logger.verbose(`HTTP server listening on port ${httpPort}`);\n\n // Write PID file\n await writePidFile(process.pid);\n\n // Build URL map\n const urls = new Map<number, string>();\n for (const route of routes) {\n const protocol = noSsl ? \"http\" : \"https\";\n const portSuffix =\n (!noSsl && httpsPort !== 443)\n ? `:${httpsPort}`\n : (noSsl && httpPort !== 80)\n ? `:${httpPort}`\n : \"\";\n urls.set(route.targetPort, `${protocol}://${route.hostname}${portSuffix}`);\n }\n\n // Print ready message\n logger.blank();\n logger.success(\"Ready!\");\n logger.blank();\n for (const route of routes) {\n const url = urls.get(route.targetPort)!;\n logger.link(`http://localhost:${route.targetPort}`, url);\n }\n logger.blank();\n console.log(\" Press Ctrl+C to stop\");\n logger.blank();\n\n // Graceful shutdown\n async function close(): Promise<void> {\n logger.info(\"Shutting down...\");\n proxyEngine.close();\n\n const closePromises: Promise<void>[] = [];\n\n closePromises.push(\n new Promise<void>((resolve) => httpServer.close(() => resolve()))\n );\n\n if (httpsServer) {\n closePromises.push(\n new Promise<void>((resolve) => httpsServer!.close(() => resolve()))\n );\n }\n\n await Promise.all(closePromises);\n\n // Clean up PID file\n try {\n const { unlink } = await import(\"node:fs/promises\");\n await unlink(paths.pidFile);\n } catch {}\n\n logger.success(\"Stopped\");\n }\n\n return { close, urls };\n}\n"],"mappings":"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","names":["readFile","mkdir","writeFile","existsSync","existsSync","readFile","mkdir","writeFile","host"]}
|