@stacksjs/rpx 0.1.1 → 0.3.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/README.md +3 -6
- package/dist/cli.js +432 -176
- package/dist/hosts.d.ts +5 -0
- package/dist/https.d.ts +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +451 -190
- package/dist/start.d.ts +1 -1
- package/dist/types.d.ts +2 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
// bin/cli.ts
|
|
20
|
-
import
|
|
20
|
+
import os10 from "os";
|
|
21
21
|
|
|
22
22
|
// node_modules/@stacksjs/cli/dist/index.js
|
|
23
23
|
import { formatWithOptions } from "util";
|
|
@@ -217845,7 +217845,7 @@ var export_mkdirSync = import_fs_extra.mkdirSync;
|
|
|
217845
217845
|
var export_fsWatch = import_fs_extra.watch;
|
|
217846
217846
|
var export_existsSync = import_fs_extra.pathExists;
|
|
217847
217847
|
// package.json
|
|
217848
|
-
var version = "0.
|
|
217848
|
+
var version = "0.3.0";
|
|
217849
217849
|
|
|
217850
217850
|
// src/config.ts
|
|
217851
217851
|
import os4 from "os";
|
|
@@ -217908,16 +217908,188 @@ var config6 = await loadConfig2({
|
|
|
217908
217908
|
validityDays: 180,
|
|
217909
217909
|
verbose: false
|
|
217910
217910
|
},
|
|
217911
|
+
etcHostsCleanup: false,
|
|
217911
217912
|
verbose: true
|
|
217912
217913
|
}
|
|
217913
217914
|
});
|
|
217914
217915
|
|
|
217915
217916
|
// src/start.ts
|
|
217916
|
-
import * as
|
|
217917
|
+
import * as fs7 from "fs";
|
|
217917
217918
|
import * as http from "http";
|
|
217918
217919
|
import * as https from "https";
|
|
217919
217920
|
import * as net from "net";
|
|
217920
|
-
import
|
|
217921
|
+
import os9 from "os";
|
|
217922
|
+
import path10 from "path";
|
|
217923
|
+
import process24 from "process";
|
|
217924
|
+
|
|
217925
|
+
// src/hosts.ts
|
|
217926
|
+
import { spawn } from "child_process";
|
|
217927
|
+
import fs4 from "fs";
|
|
217928
|
+
import os6 from "os";
|
|
217929
|
+
import path7 from "path";
|
|
217930
|
+
import process22 from "process";
|
|
217931
|
+
|
|
217932
|
+
// src/utils.ts
|
|
217933
|
+
function debugLog(category, message, verbose) {
|
|
217934
|
+
if (verbose || config6.verbose) {
|
|
217935
|
+
console.debug(`[rpx:${category}] ${message}`);
|
|
217936
|
+
}
|
|
217937
|
+
}
|
|
217938
|
+
|
|
217939
|
+
// src/hosts.ts
|
|
217940
|
+
var hostsFilePath = process22.platform === "win32" ? path7.join(process22.env.windir || "C:\\Windows", "System32", "drivers", "etc", "hosts") : "/etc/hosts";
|
|
217941
|
+
async function sudoWrite(operation, content) {
|
|
217942
|
+
return new Promise((resolve5, reject) => {
|
|
217943
|
+
if (process22.platform === "win32") {
|
|
217944
|
+
reject(new Error("Administrator privileges required on Windows"));
|
|
217945
|
+
return;
|
|
217946
|
+
}
|
|
217947
|
+
const tmpFile = path7.join(os6.tmpdir(), "hosts.tmp");
|
|
217948
|
+
try {
|
|
217949
|
+
if (operation === "append") {
|
|
217950
|
+
const currentContent = fs4.readFileSync(hostsFilePath, "utf8");
|
|
217951
|
+
fs4.writeFileSync(tmpFile, currentContent + content, "utf8");
|
|
217952
|
+
} else {
|
|
217953
|
+
fs4.writeFileSync(tmpFile, content, "utf8");
|
|
217954
|
+
}
|
|
217955
|
+
const sudo = spawn("sudo", ["cp", tmpFile, hostsFilePath]);
|
|
217956
|
+
sudo.on("close", (code) => {
|
|
217957
|
+
try {
|
|
217958
|
+
fs4.unlinkSync(tmpFile);
|
|
217959
|
+
if (code === 0)
|
|
217960
|
+
resolve5();
|
|
217961
|
+
else
|
|
217962
|
+
reject(new Error(`sudo process exited with code ${code}`));
|
|
217963
|
+
} catch (err3) {
|
|
217964
|
+
reject(err3);
|
|
217965
|
+
}
|
|
217966
|
+
});
|
|
217967
|
+
sudo.on("error", (err3) => {
|
|
217968
|
+
try {
|
|
217969
|
+
fs4.unlinkSync(tmpFile);
|
|
217970
|
+
} catch {
|
|
217971
|
+
}
|
|
217972
|
+
reject(err3);
|
|
217973
|
+
});
|
|
217974
|
+
} catch (err3) {
|
|
217975
|
+
reject(err3);
|
|
217976
|
+
}
|
|
217977
|
+
});
|
|
217978
|
+
}
|
|
217979
|
+
async function addHosts(hosts) {
|
|
217980
|
+
debugLog("hosts", `Adding hosts: ${hosts.join(", ")}`, config6.verbose);
|
|
217981
|
+
debugLog("hosts", `Using hosts file at: ${hostsFilePath}`, config6.verbose);
|
|
217982
|
+
try {
|
|
217983
|
+
const existingContent = await fs4.promises.readFile(hostsFilePath, "utf-8");
|
|
217984
|
+
const newEntries = hosts.filter((host) => {
|
|
217985
|
+
const ipv4Entry = `127.0.0.1 ${host}`;
|
|
217986
|
+
const ipv6Entry = `::1 ${host}`;
|
|
217987
|
+
return !existingContent.includes(ipv4Entry) && !existingContent.includes(ipv6Entry);
|
|
217988
|
+
});
|
|
217989
|
+
if (newEntries.length === 0) {
|
|
217990
|
+
debugLog("hosts", "All hosts already exist in hosts file", config6.verbose);
|
|
217991
|
+
log.info("All hosts are already in the hosts file");
|
|
217992
|
+
return;
|
|
217993
|
+
}
|
|
217994
|
+
const hostEntries = newEntries.map((host) => `\n# Added by rpx\n127.0.0.1 ${host}\n::1 ${host}`).join("\n");
|
|
217995
|
+
try {
|
|
217996
|
+
await fs4.promises.appendFile(hostsFilePath, hostEntries, { flag: "a" });
|
|
217997
|
+
log.success(`Added new hosts: ${newEntries.join(", ")}`);
|
|
217998
|
+
} catch (writeErr) {
|
|
217999
|
+
if (writeErr.code === "EACCES") {
|
|
218000
|
+
debugLog("hosts", "Permission denied, attempting with sudo", config6.verbose);
|
|
218001
|
+
try {
|
|
218002
|
+
await sudoWrite("append", hostEntries);
|
|
218003
|
+
log.success(`Added new hosts with sudo: ${newEntries.join(", ")}`);
|
|
218004
|
+
} catch (sudoErr) {
|
|
218005
|
+
log.error("Failed to modify hosts file automatically");
|
|
218006
|
+
log.warn("Please add these entries to your hosts file manually:");
|
|
218007
|
+
hostEntries.split("\n").forEach((entry) => log.warn(entry));
|
|
218008
|
+
if (process22.platform === "win32") {
|
|
218009
|
+
log.warn("\nOn Windows:");
|
|
218010
|
+
log.warn("1. Run notepad as administrator");
|
|
218011
|
+
log.warn("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");
|
|
218012
|
+
} else {
|
|
218013
|
+
log.warn("\nOn Unix systems:");
|
|
218014
|
+
log.warn(`sudo nano ${hostsFilePath}`);
|
|
218015
|
+
}
|
|
218016
|
+
throw new Error("Failed to modify hosts file: manual intervention required");
|
|
218017
|
+
}
|
|
218018
|
+
} else {
|
|
218019
|
+
throw writeErr;
|
|
218020
|
+
}
|
|
218021
|
+
}
|
|
218022
|
+
} catch (err3) {
|
|
218023
|
+
const error = err3;
|
|
218024
|
+
log.error(`Failed to manage hosts file: ${error.message}`);
|
|
218025
|
+
throw error;
|
|
218026
|
+
}
|
|
218027
|
+
}
|
|
218028
|
+
async function removeHosts(hosts) {
|
|
218029
|
+
debugLog("hosts", `Removing hosts: ${hosts.join(", ")}`, config6.verbose);
|
|
218030
|
+
try {
|
|
218031
|
+
const content = await fs4.promises.readFile(hostsFilePath, "utf-8");
|
|
218032
|
+
const lines = content.split("\n");
|
|
218033
|
+
const filteredLines = lines.filter((line, index) => {
|
|
218034
|
+
if (line.trim() === "# Added by rpx") {
|
|
218035
|
+
lines.splice(index + 1, 2);
|
|
218036
|
+
return false;
|
|
218037
|
+
}
|
|
218038
|
+
return true;
|
|
218039
|
+
});
|
|
218040
|
+
while (filteredLines[filteredLines.length - 1]?.trim() === "")
|
|
218041
|
+
filteredLines.pop();
|
|
218042
|
+
const newContent = `${filteredLines.join("\n")}\n`;
|
|
218043
|
+
try {
|
|
218044
|
+
await fs4.promises.writeFile(hostsFilePath, newContent);
|
|
218045
|
+
log.success("Hosts removed successfully");
|
|
218046
|
+
} catch (writeErr) {
|
|
218047
|
+
if (writeErr.code === "EACCES") {
|
|
218048
|
+
debugLog("hosts", "Permission denied, attempting with sudo", config6.verbose);
|
|
218049
|
+
try {
|
|
218050
|
+
await sudoWrite("write", newContent);
|
|
218051
|
+
log.success("Hosts removed successfully with sudo");
|
|
218052
|
+
} catch (sudoErr) {
|
|
218053
|
+
log.error("Failed to modify hosts file automatically");
|
|
218054
|
+
log.warn("Please remove these entries from your hosts file manually:");
|
|
218055
|
+
hosts.forEach((host) => {
|
|
218056
|
+
log.warn("# Added by rpx");
|
|
218057
|
+
log.warn(`127.0.0.1 ${host}`);
|
|
218058
|
+
log.warn(`::1 ${host}`);
|
|
218059
|
+
});
|
|
218060
|
+
if (process22.platform === "win32") {
|
|
218061
|
+
log.warn("\nOn Windows:");
|
|
218062
|
+
log.warn("1. Run notepad as administrator");
|
|
218063
|
+
log.warn("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");
|
|
218064
|
+
} else {
|
|
218065
|
+
log.warn("\nOn Unix systems:");
|
|
218066
|
+
log.warn(`sudo nano ${hostsFilePath}`);
|
|
218067
|
+
}
|
|
218068
|
+
throw new Error("Failed to modify hosts file: manual intervention required");
|
|
218069
|
+
}
|
|
218070
|
+
} else {
|
|
218071
|
+
throw writeErr;
|
|
218072
|
+
}
|
|
218073
|
+
}
|
|
218074
|
+
} catch (err3) {
|
|
218075
|
+
const error = err3;
|
|
218076
|
+
log.error(`Failed to remove hosts: ${error.message}`);
|
|
218077
|
+
throw error;
|
|
218078
|
+
}
|
|
218079
|
+
}
|
|
218080
|
+
async function checkHosts(hosts) {
|
|
218081
|
+
debugLog("hosts", `Checking hosts: ${hosts}`, config6.verbose);
|
|
218082
|
+
const content = await fs4.promises.readFile(hostsFilePath, "utf-8");
|
|
218083
|
+
return hosts.map((host) => {
|
|
218084
|
+
const ipv4Entry = `127.0.0.1 ${host}`;
|
|
218085
|
+
const ipv6Entry = `::1 ${host}`;
|
|
218086
|
+
return content.includes(ipv4Entry) || content.includes(ipv6Entry);
|
|
218087
|
+
});
|
|
218088
|
+
}
|
|
218089
|
+
|
|
218090
|
+
// src/https.ts
|
|
218091
|
+
import os8 from "os";
|
|
218092
|
+
import path9 from "path";
|
|
217921
218093
|
|
|
217922
218094
|
// node_modules/@stacksjs/tlsx/dist/index.js
|
|
217923
218095
|
import fs22 from "fs";
|
|
@@ -217956,7 +218128,7 @@ import process82 from "process";
|
|
|
217956
218128
|
import process102 from "process";
|
|
217957
218129
|
import process182 from "process";
|
|
217958
218130
|
import process112 from "process";
|
|
217959
|
-
import
|
|
218131
|
+
import os7 from "os";
|
|
217960
218132
|
import tty33 from "tty";
|
|
217961
218133
|
import process142 from "process";
|
|
217962
218134
|
import process132 from "process";
|
|
@@ -217965,10 +218137,10 @@ import process162 from "process";
|
|
|
217965
218137
|
import process172 from "process";
|
|
217966
218138
|
import process192 from "process";
|
|
217967
218139
|
import os23 from "os";
|
|
217968
|
-
import
|
|
218140
|
+
import path8 from "path";
|
|
217969
218141
|
import { resolve as resolve6 } from "path";
|
|
217970
|
-
import
|
|
217971
|
-
import
|
|
218142
|
+
import process23 from "process";
|
|
218143
|
+
import fs6 from "fs";
|
|
217972
218144
|
import path22 from "path";
|
|
217973
218145
|
var __create4 = Object.create;
|
|
217974
218146
|
var __getProtoOf4 = Object.getPrototypeOf;
|
|
@@ -251459,7 +251631,7 @@ function _supportsColor3(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
|
|
251459
251631
|
return min;
|
|
251460
251632
|
}
|
|
251461
251633
|
if (process112.platform === "win32") {
|
|
251462
|
-
const osRelease =
|
|
251634
|
+
const osRelease = os7.release().split(".");
|
|
251463
251635
|
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
251464
251636
|
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
251465
251637
|
}
|
|
@@ -254012,7 +254184,7 @@ function isObject33(item) {
|
|
|
254012
254184
|
return item && typeof item === "object" && !Array.isArray(item);
|
|
254013
254185
|
}
|
|
254014
254186
|
async function loadConfig3({ name, cwd, defaultConfig }) {
|
|
254015
|
-
const configPath2 = resolve6(cwd ||
|
|
254187
|
+
const configPath2 = resolve6(cwd || process23.cwd(), `${name}.config`);
|
|
254016
254188
|
try {
|
|
254017
254189
|
const importedConfig = await import(configPath2);
|
|
254018
254190
|
const loadedConfig = importedConfig.default || importedConfig;
|
|
@@ -254026,18 +254198,18 @@ var config42 = await loadConfig3({
|
|
|
254026
254198
|
defaultConfig: {
|
|
254027
254199
|
altNameIPs: ["127.0.0.1"],
|
|
254028
254200
|
altNameURIs: ["localhost"],
|
|
254029
|
-
organizationName: "
|
|
254201
|
+
organizationName: "Local Development",
|
|
254030
254202
|
countryName: "US",
|
|
254031
254203
|
stateName: "California",
|
|
254032
254204
|
localityName: "Playa Vista",
|
|
254033
254205
|
commonName: "stacks.localhost",
|
|
254034
254206
|
validityDays: 180,
|
|
254035
254207
|
hostCertCN: "stacks.localhost",
|
|
254036
|
-
domain: "localhost",
|
|
254208
|
+
domain: "stacks.localhost",
|
|
254037
254209
|
rootCAObject: { certificate: "", privateKey: "" },
|
|
254038
|
-
caCertPath:
|
|
254039
|
-
certPath:
|
|
254040
|
-
keyPath:
|
|
254210
|
+
caCertPath: path8.join(os23.homedir(), ".stacks", "ssl", `stacks.localhost.ca.crt`),
|
|
254211
|
+
certPath: path8.join(os23.homedir(), ".stacks", "ssl", `stacks.localhost.crt`),
|
|
254212
|
+
keyPath: path8.join(os23.homedir(), ".stacks", "ssl", `stacks.localhost.crt.key`),
|
|
254041
254213
|
verbose: false
|
|
254042
254214
|
}
|
|
254043
254215
|
});
|
|
@@ -254053,10 +254225,10 @@ function findFoldersWithFile(rootDir, fileName) {
|
|
|
254053
254225
|
const result2 = [];
|
|
254054
254226
|
function search(dir) {
|
|
254055
254227
|
try {
|
|
254056
|
-
const files2 =
|
|
254228
|
+
const files2 = fs6.readdirSync(dir);
|
|
254057
254229
|
for (const file of files2) {
|
|
254058
254230
|
const filePath = path22.join(dir, file);
|
|
254059
|
-
const stats =
|
|
254231
|
+
const stats = fs6.lstatSync(filePath);
|
|
254060
254232
|
if (stats.isDirectory()) {
|
|
254061
254233
|
search(filePath);
|
|
254062
254234
|
} else if (file === fileName) {
|
|
@@ -254070,29 +254242,29 @@ function findFoldersWithFile(rootDir, fileName) {
|
|
|
254070
254242
|
search(rootDir);
|
|
254071
254243
|
return result2;
|
|
254072
254244
|
}
|
|
254073
|
-
function
|
|
254245
|
+
function debugLog2(category, message, verbose) {
|
|
254074
254246
|
if (verbose || config42.verbose) {
|
|
254075
254247
|
console.debug(`[tlsx:${category}] ${message}`);
|
|
254076
254248
|
}
|
|
254077
254249
|
}
|
|
254078
254250
|
function randomSerialNumber(verbose) {
|
|
254079
|
-
|
|
254251
|
+
debugLog2("cert", "Generating random serial number", verbose);
|
|
254080
254252
|
const serialNumber = makeNumberPositive(import_node_forge2.default.util.bytesToHex(import_node_forge2.default.random.getBytesSync(20)));
|
|
254081
|
-
|
|
254253
|
+
debugLog2("cert", `Generated serial number: ${serialNumber}`, verbose);
|
|
254082
254254
|
return serialNumber;
|
|
254083
254255
|
}
|
|
254084
254256
|
function getCertNotBefore(verbose) {
|
|
254085
|
-
|
|
254257
|
+
debugLog2("cert", "Calculating certificate not-before date", verbose);
|
|
254086
254258
|
const twoDaysAgo = new Date(Date.now() - 172800000);
|
|
254087
254259
|
const year = twoDaysAgo.getFullYear();
|
|
254088
254260
|
const month = (twoDaysAgo.getMonth() + 1).toString().padStart(2, "0");
|
|
254089
254261
|
const day = twoDaysAgo.getDate().toString().padStart(2, "0");
|
|
254090
254262
|
const date = new Date(`${year}-${month}-${day}T23:59:59Z`);
|
|
254091
|
-
|
|
254263
|
+
debugLog2("cert", `Certificate not-before date: ${date.toISOString()}`, verbose);
|
|
254092
254264
|
return date;
|
|
254093
254265
|
}
|
|
254094
254266
|
function getCertNotAfter(notBefore, verbose) {
|
|
254095
|
-
|
|
254267
|
+
debugLog2("cert", "Calculating certificate not-after date", verbose);
|
|
254096
254268
|
const validityDays = config42.validityDays;
|
|
254097
254269
|
const daysInMillis = validityDays * 60 * 60 * 24 * 1000;
|
|
254098
254270
|
const notAfterDate = new Date(notBefore.getTime() + daysInMillis);
|
|
@@ -254100,27 +254272,27 @@ function getCertNotAfter(notBefore, verbose) {
|
|
|
254100
254272
|
const month = (notAfterDate.getMonth() + 1).toString().padStart(2, "0");
|
|
254101
254273
|
const day = notAfterDate.getDate().toString().padStart(2, "0");
|
|
254102
254274
|
const date = new Date(`${year}-${month}-${day}T23:59:59Z`);
|
|
254103
|
-
|
|
254275
|
+
debugLog2("cert", `Certificate not-after date: ${date.toISOString()} (${validityDays} days validity)`, verbose);
|
|
254104
254276
|
return date;
|
|
254105
254277
|
}
|
|
254106
254278
|
function getCANotAfter(notBefore, verbose) {
|
|
254107
|
-
|
|
254279
|
+
debugLog2("cert", "Calculating CA not-after date", verbose);
|
|
254108
254280
|
const year = notBefore.getFullYear() + 100;
|
|
254109
254281
|
const month = (notBefore.getMonth() + 1).toString().padStart(2, "0");
|
|
254110
254282
|
const day = notBefore.getDate().toString().padStart(2, "0");
|
|
254111
254283
|
const date = new Date(`${year}-${month}-${day}T23:59:59Z`);
|
|
254112
|
-
|
|
254284
|
+
debugLog2("cert", `CA not-after date: ${date.toISOString()} (100 years validity)`, verbose);
|
|
254113
254285
|
return date;
|
|
254114
254286
|
}
|
|
254115
254287
|
async function createRootCA(options23) {
|
|
254116
|
-
|
|
254117
|
-
|
|
254288
|
+
debugLog2("ca", "Creating new Root CA Certificate", options23?.verbose);
|
|
254289
|
+
debugLog2("ca", "Generating 2048-bit RSA key pair", options23?.verbose);
|
|
254118
254290
|
const { privateKey, publicKey } = import_node_forge2.pki.rsa.generateKeyPair(2048);
|
|
254119
254291
|
const mergedOptions = {
|
|
254120
254292
|
...config42,
|
|
254121
254293
|
...options23 || {}
|
|
254122
254294
|
};
|
|
254123
|
-
|
|
254295
|
+
debugLog2("ca", "Setting certificate attributes", options23?.verbose);
|
|
254124
254296
|
const attributes = [
|
|
254125
254297
|
{ shortName: "C", value: mergedOptions.countryName },
|
|
254126
254298
|
{ shortName: "ST", value: mergedOptions.stateName },
|
|
@@ -254128,7 +254300,7 @@ async function createRootCA(options23) {
|
|
|
254128
254300
|
{ shortName: "O", value: "Local Development CA" },
|
|
254129
254301
|
{ shortName: "CN", value: "Local Development Root CA" }
|
|
254130
254302
|
];
|
|
254131
|
-
|
|
254303
|
+
debugLog2("ca", "Setting certificate extensions", options23?.verbose);
|
|
254132
254304
|
const extensions = [
|
|
254133
254305
|
{
|
|
254134
254306
|
name: "basicConstraints",
|
|
@@ -254145,7 +254317,7 @@ async function createRootCA(options23) {
|
|
|
254145
254317
|
name: "subjectKeyIdentifier"
|
|
254146
254318
|
}
|
|
254147
254319
|
];
|
|
254148
|
-
|
|
254320
|
+
debugLog2("ca", "Creating CA certificate", options23?.verbose);
|
|
254149
254321
|
const caCert = import_node_forge2.pki.createCertificate();
|
|
254150
254322
|
caCert.publicKey = publicKey;
|
|
254151
254323
|
caCert.serialNumber = randomSerialNumber(options23?.verbose);
|
|
@@ -254154,11 +254326,11 @@ async function createRootCA(options23) {
|
|
|
254154
254326
|
caCert.setSubject(attributes);
|
|
254155
254327
|
caCert.setIssuer(attributes);
|
|
254156
254328
|
caCert.setExtensions(extensions);
|
|
254157
|
-
|
|
254329
|
+
debugLog2("ca", "Signing certificate with SHA-256", options23?.verbose);
|
|
254158
254330
|
caCert.sign(privateKey, import_node_forge2.default.md.sha256.create());
|
|
254159
254331
|
const pemCert = import_node_forge2.pki.certificateToPem(caCert);
|
|
254160
254332
|
const pemKey = import_node_forge2.pki.privateKeyToPem(privateKey);
|
|
254161
|
-
|
|
254333
|
+
debugLog2("ca", "Root CA Certificate created successfully", options23?.verbose);
|
|
254162
254334
|
return {
|
|
254163
254335
|
certificate: pemCert,
|
|
254164
254336
|
privateKey: pemKey,
|
|
@@ -254167,34 +254339,41 @@ async function createRootCA(options23) {
|
|
|
254167
254339
|
};
|
|
254168
254340
|
}
|
|
254169
254341
|
async function generateCert(options23) {
|
|
254170
|
-
|
|
254171
|
-
|
|
254342
|
+
debugLog2("cert", "Generating new host certificate", options23?.verbose);
|
|
254343
|
+
debugLog2("cert", `Options: ${JSON.stringify(options23)}`, options23?.verbose);
|
|
254172
254344
|
if (!options23?.hostCertCN?.trim()) {
|
|
254173
|
-
|
|
254345
|
+
debugLog2("cert", "Error: hostCertCN is required", options23?.verbose);
|
|
254174
254346
|
throw new Error('"hostCertCN" must be a String');
|
|
254175
254347
|
}
|
|
254176
254348
|
if (!options23.domain?.trim()) {
|
|
254177
|
-
|
|
254349
|
+
debugLog2("cert", "Error: domain is required", options23?.verbose);
|
|
254178
254350
|
throw new Error('"domain" must be a String');
|
|
254179
254351
|
}
|
|
254180
254352
|
if (!options23.rootCAObject || !options23.rootCAObject.certificate || !options23.rootCAObject.privateKey) {
|
|
254181
|
-
|
|
254353
|
+
debugLog2("cert", "Error: rootCAObject is invalid or missing", options23?.verbose);
|
|
254182
254354
|
throw new Error('"rootCAObject" must be an Object with the properties "certificate" & "privateKey"');
|
|
254183
254355
|
}
|
|
254184
|
-
|
|
254356
|
+
debugLog2("cert", "Converting Root CA PEM to forge objects", options23?.verbose);
|
|
254185
254357
|
const caCert = import_node_forge2.pki.certificateFromPem(options23.rootCAObject.certificate);
|
|
254186
254358
|
const caKey = import_node_forge2.pki.privateKeyFromPem(options23.rootCAObject.privateKey);
|
|
254187
|
-
|
|
254359
|
+
debugLog2("cert", "Generating 2048-bit RSA key pair for host certificate", options23?.verbose);
|
|
254188
254360
|
const hostKeys = import_node_forge2.pki.rsa.generateKeyPair(2048);
|
|
254189
|
-
|
|
254361
|
+
debugLog2("cert", "Setting certificate attributes", options23?.verbose);
|
|
254190
254362
|
const attributes = [
|
|
254191
|
-
{ shortName: "C", value: config42.countryName },
|
|
254192
|
-
{ shortName: "ST", value: config42.stateName },
|
|
254193
|
-
{ shortName: "L", value: config42.localityName },
|
|
254194
|
-
{ shortName: "O", value:
|
|
254195
|
-
{ shortName: "CN", value:
|
|
254363
|
+
{ shortName: "C", value: options23.countryName || config42.countryName },
|
|
254364
|
+
{ shortName: "ST", value: options23.stateName || config42.stateName },
|
|
254365
|
+
{ shortName: "L", value: options23.localityName || config42.localityName },
|
|
254366
|
+
{ shortName: "O", value: options23.organizationName || config42.organizationName },
|
|
254367
|
+
{ shortName: "CN", value: options23.commonName || config42.commonName }
|
|
254196
254368
|
];
|
|
254197
|
-
|
|
254369
|
+
const domain = options23.domain || config42.domain;
|
|
254370
|
+
const wildcardDomain = `*.${domain.includes(".") ? domain.split(".").slice(1).join(".") : domain}`;
|
|
254371
|
+
const altNames = [
|
|
254372
|
+
{ type: 2, value: wildcardDomain },
|
|
254373
|
+
{ type: 2, value: "localhost" },
|
|
254374
|
+
{ type: 2, value: domain }
|
|
254375
|
+
];
|
|
254376
|
+
debugLog2("cert", "Setting certificate extensions", options23?.verbose);
|
|
254198
254377
|
const extensions = [
|
|
254199
254378
|
{
|
|
254200
254379
|
name: "basicConstraints",
|
|
@@ -254214,30 +254393,25 @@ async function generateCert(options23) {
|
|
|
254214
254393
|
},
|
|
254215
254394
|
{
|
|
254216
254395
|
name: "subjectAltName",
|
|
254217
|
-
altNames
|
|
254218
|
-
{ type: 2, value: "*.localhost" },
|
|
254219
|
-
{ type: 2, value: "localhost" },
|
|
254220
|
-
{ type: 2, value: "stacks.localhost" },
|
|
254221
|
-
{ type: 2, value: options23.domain }
|
|
254222
|
-
]
|
|
254396
|
+
altNames
|
|
254223
254397
|
}
|
|
254224
254398
|
];
|
|
254225
|
-
|
|
254399
|
+
debugLog2("cert", "Creating new host certificate", options23?.verbose);
|
|
254226
254400
|
const newHostCert = import_node_forge2.pki.createCertificate();
|
|
254227
254401
|
newHostCert.publicKey = hostKeys.publicKey;
|
|
254228
|
-
|
|
254402
|
+
debugLog2("cert", "Setting certificate properties", options23?.verbose);
|
|
254229
254403
|
newHostCert.serialNumber = randomSerialNumber(options23?.verbose);
|
|
254230
254404
|
newHostCert.validity.notBefore = getCertNotBefore(options23?.verbose);
|
|
254231
254405
|
newHostCert.validity.notAfter = getCertNotAfter(newHostCert.validity.notBefore, options23?.verbose);
|
|
254232
254406
|
newHostCert.setSubject(attributes);
|
|
254233
254407
|
newHostCert.setIssuer(caCert.subject.attributes);
|
|
254234
254408
|
newHostCert.setExtensions(extensions);
|
|
254235
|
-
|
|
254409
|
+
debugLog2("cert", "Signing certificate with SHA-256", options23?.verbose);
|
|
254236
254410
|
newHostCert.sign(caKey, import_node_forge2.default.md.sha256.create());
|
|
254237
|
-
|
|
254411
|
+
debugLog2("cert", "Converting certificate to PEM format", options23?.verbose);
|
|
254238
254412
|
const pemHostCert = import_node_forge2.pki.certificateToPem(newHostCert);
|
|
254239
254413
|
const pemHostKey = import_node_forge2.pki.privateKeyToPem(hostKeys.privateKey);
|
|
254240
|
-
|
|
254414
|
+
debugLog2("cert", "Host certificate generated successfully", options23?.verbose);
|
|
254241
254415
|
return {
|
|
254242
254416
|
certificate: pemHostCert,
|
|
254243
254417
|
privateKey: pemHostKey,
|
|
@@ -254246,81 +254420,81 @@ async function generateCert(options23) {
|
|
|
254246
254420
|
};
|
|
254247
254421
|
}
|
|
254248
254422
|
async function addCertToSystemTrustStoreAndSaveCert(cert, caCert, options23) {
|
|
254249
|
-
|
|
254250
|
-
|
|
254423
|
+
debugLog2("trust", `Adding certificate to system trust store with options: ${JSON.stringify(options23)}`, options23?.verbose);
|
|
254424
|
+
debugLog2("trust", "Storing certificate and private key", options23?.verbose);
|
|
254251
254425
|
const certPath = storeCert(cert, options23);
|
|
254252
|
-
|
|
254426
|
+
debugLog2("trust", "Storing CA certificate", options23?.verbose);
|
|
254253
254427
|
const caCertPath = storeCACert(caCert, options23);
|
|
254254
254428
|
const platform23 = os42.platform();
|
|
254255
|
-
|
|
254429
|
+
debugLog2("trust", `Detected platform: ${platform23}`, options23?.verbose);
|
|
254256
254430
|
const args = "TC, C, C";
|
|
254257
254431
|
if (platform23 === "darwin") {
|
|
254258
|
-
|
|
254432
|
+
debugLog2("trust", "Adding certificate to macOS keychain", options23?.verbose);
|
|
254259
254433
|
await runCommand2(`sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ${caCertPath}`);
|
|
254260
254434
|
} else if (platform23 === "win32") {
|
|
254261
|
-
|
|
254435
|
+
debugLog2("trust", "Adding certificate to Windows certificate store", options23?.verbose);
|
|
254262
254436
|
await runCommand2(`certutil -f -v -addstore -enterprise Root ${caCertPath}`);
|
|
254263
254437
|
} else if (platform23 === "linux") {
|
|
254264
|
-
|
|
254438
|
+
debugLog2("trust", "Adding certificate to Linux certificate store", options23?.verbose);
|
|
254265
254439
|
const rootDirectory = os42.homedir();
|
|
254266
254440
|
const targetFileName = "cert9.db";
|
|
254267
|
-
|
|
254441
|
+
debugLog2("trust", `Searching for certificate databases in ${rootDirectory}`, options23?.verbose);
|
|
254268
254442
|
const foldersWithFile = findFoldersWithFile(rootDirectory, targetFileName);
|
|
254269
254443
|
for (const folder of foldersWithFile) {
|
|
254270
|
-
|
|
254444
|
+
debugLog2("trust", `Processing certificate database in ${folder}`, options23?.verbose);
|
|
254271
254445
|
try {
|
|
254272
|
-
|
|
254446
|
+
debugLog2("trust", `Attempting to delete existing cert for ${config42.commonName}`, options23?.verbose);
|
|
254273
254447
|
await runCommand2(`certutil -d sql:${folder} -D -n ${config42.commonName}`);
|
|
254274
254448
|
} catch (error) {
|
|
254275
|
-
|
|
254449
|
+
debugLog2("trust", `Warning: Error deleting existing cert: ${error}`, options23?.verbose);
|
|
254276
254450
|
console.warn(`Error deleting existing cert: ${error}`);
|
|
254277
254451
|
}
|
|
254278
|
-
|
|
254452
|
+
debugLog2("trust", `Adding new certificate to ${folder}`, options23?.verbose);
|
|
254279
254453
|
await runCommand2(`certutil -d sql:${folder} -A -t ${args} -n ${config42.commonName} -i ${caCertPath}`);
|
|
254280
254454
|
log3.info(`Cert added to ${folder}`);
|
|
254281
254455
|
}
|
|
254282
254456
|
} else {
|
|
254283
|
-
|
|
254457
|
+
debugLog2("trust", `Error: Unsupported platform ${platform23}`, options23?.verbose);
|
|
254284
254458
|
throw new Error(`Unsupported platform: ${platform23}`);
|
|
254285
254459
|
}
|
|
254286
|
-
|
|
254460
|
+
debugLog2("trust", "Certificate successfully added to system trust store", options23?.verbose);
|
|
254287
254461
|
return certPath;
|
|
254288
254462
|
}
|
|
254289
254463
|
function storeCert(cert, options23) {
|
|
254290
|
-
|
|
254464
|
+
debugLog2("storage", `Storing certificate and private key with options: ${JSON.stringify(options23)}`, options23?.verbose);
|
|
254291
254465
|
const certPath = options23?.certPath || config42.certPath;
|
|
254292
254466
|
const certKeyPath = options23?.keyPath || config42.keyPath;
|
|
254293
|
-
|
|
254294
|
-
|
|
254467
|
+
debugLog2("storage", `Certificate path: ${certPath}`, options23?.verbose);
|
|
254468
|
+
debugLog2("storage", `Private key path: ${certKeyPath}`, options23?.verbose);
|
|
254295
254469
|
const certDir = path32.dirname(certPath);
|
|
254296
254470
|
if (!fs22.existsSync(certDir)) {
|
|
254297
|
-
|
|
254471
|
+
debugLog2("storage", `Creating certificate directory: ${certDir}`, options23?.verbose);
|
|
254298
254472
|
fs22.mkdirSync(certDir, { recursive: true });
|
|
254299
254473
|
}
|
|
254300
|
-
|
|
254474
|
+
debugLog2("storage", "Writing certificate file", options23?.verbose);
|
|
254301
254475
|
fs22.writeFileSync(certPath, cert.certificate);
|
|
254302
254476
|
const certKeyDir = path32.dirname(certKeyPath);
|
|
254303
254477
|
if (!fs22.existsSync(certKeyDir)) {
|
|
254304
|
-
|
|
254478
|
+
debugLog2("storage", `Creating private key directory: ${certKeyDir}`, options23?.verbose);
|
|
254305
254479
|
fs22.mkdirSync(certKeyDir, { recursive: true });
|
|
254306
254480
|
}
|
|
254307
|
-
|
|
254481
|
+
debugLog2("storage", "Writing private key file", options23?.verbose);
|
|
254308
254482
|
fs22.writeFileSync(certKeyPath, cert.privateKey);
|
|
254309
|
-
|
|
254483
|
+
debugLog2("storage", "Certificate and private key stored successfully", options23?.verbose);
|
|
254310
254484
|
return certPath;
|
|
254311
254485
|
}
|
|
254312
254486
|
function storeCACert(caCert, options23) {
|
|
254313
|
-
|
|
254487
|
+
debugLog2("storage", "Storing CA certificate", options23?.verbose);
|
|
254314
254488
|
const caCertPath = options23?.caCertPath || config42.caCertPath;
|
|
254315
|
-
|
|
254489
|
+
debugLog2("storage", `CA certificate path: ${caCertPath}`, options23?.verbose);
|
|
254316
254490
|
const caCertDir = path32.dirname(caCertPath);
|
|
254317
254491
|
if (!fs22.existsSync(caCertDir)) {
|
|
254318
|
-
|
|
254492
|
+
debugLog2("storage", `Creating CA certificate directory: ${caCertDir}`, options23?.verbose);
|
|
254319
254493
|
fs22.mkdirSync(caCertDir, { recursive: true });
|
|
254320
254494
|
}
|
|
254321
|
-
|
|
254495
|
+
debugLog2("storage", "Writing CA certificate file", options23?.verbose);
|
|
254322
254496
|
fs22.writeFileSync(caCertPath, caCert);
|
|
254323
|
-
|
|
254497
|
+
debugLog2("storage", "CA certificate stored successfully", options23?.verbose);
|
|
254324
254498
|
return caCertPath;
|
|
254325
254499
|
}
|
|
254326
254500
|
var export_tls = import_node_forge2.tls;
|
|
@@ -254328,8 +254502,37 @@ var export_pki = import_node_forge2.pki;
|
|
|
254328
254502
|
var export_forge = import_node_forge2.default;
|
|
254329
254503
|
|
|
254330
254504
|
// src/https.ts
|
|
254505
|
+
function httpsConfig() {
|
|
254506
|
+
const domain = config6.to || "stacks.localhost";
|
|
254507
|
+
const defaultConfig = {
|
|
254508
|
+
domain,
|
|
254509
|
+
hostCertCN: domain,
|
|
254510
|
+
caCertPath: path9.join(os8.homedir(), ".stacks", "ssl", `${domain}.ca.crt`),
|
|
254511
|
+
certPath: path9.join(os8.homedir(), ".stacks", "ssl", `${domain}.crt`),
|
|
254512
|
+
keyPath: path9.join(os8.homedir(), ".stacks", "ssl", `${domain}.crt.key`),
|
|
254513
|
+
altNameIPs: ["127.0.0.1"],
|
|
254514
|
+
altNameURIs: ["localhost"],
|
|
254515
|
+
organizationName: "stacksjs.org",
|
|
254516
|
+
countryName: "US",
|
|
254517
|
+
stateName: "California",
|
|
254518
|
+
localityName: "Playa Vista",
|
|
254519
|
+
commonName: domain,
|
|
254520
|
+
validityDays: 180,
|
|
254521
|
+
verbose: false
|
|
254522
|
+
};
|
|
254523
|
+
if (config6.https === true)
|
|
254524
|
+
return defaultConfig;
|
|
254525
|
+
return {
|
|
254526
|
+
...defaultConfig,
|
|
254527
|
+
...config6.https
|
|
254528
|
+
};
|
|
254529
|
+
}
|
|
254331
254530
|
async function generateCertificate(domain) {
|
|
254332
|
-
|
|
254531
|
+
if (config6.https === true)
|
|
254532
|
+
config6.https = httpsConfig();
|
|
254533
|
+
else if (config6.https === false)
|
|
254534
|
+
return;
|
|
254535
|
+
domain = domain ?? config6.https.domain;
|
|
254333
254536
|
log.info(`Generating a self-signed SSL certificate for: ${domain}`);
|
|
254334
254537
|
const caCert = await createRootCA(config6.https);
|
|
254335
254538
|
const hostCert = await generateCert({
|
|
@@ -254352,70 +254555,89 @@ async function generateCertificate(domain) {
|
|
|
254352
254555
|
log.success("Certificate generated");
|
|
254353
254556
|
}
|
|
254354
254557
|
|
|
254355
|
-
// src/utils.ts
|
|
254356
|
-
function debugLog2(category, message, verbose) {
|
|
254357
|
-
if (verbose || config6.verbose) {
|
|
254358
|
-
console.debug(`[rpx:${category}] ${message}`);
|
|
254359
|
-
}
|
|
254360
|
-
}
|
|
254361
|
-
|
|
254362
254558
|
// src/start.ts
|
|
254363
254559
|
var activeServers = new Set;
|
|
254364
|
-
function cleanup() {
|
|
254365
|
-
|
|
254560
|
+
async function cleanup() {
|
|
254561
|
+
debugLog("cleanup", "Starting cleanup process", config6.verbose);
|
|
254366
254562
|
console.log(`\n`);
|
|
254367
254563
|
log.info("Shutting down proxy servers...");
|
|
254368
|
-
const
|
|
254564
|
+
const cleanupPromises = [];
|
|
254565
|
+
const serverClosePromises = Array.from(activeServers).map((server) => new Promise((resolve5) => {
|
|
254369
254566
|
server.close(() => {
|
|
254370
|
-
|
|
254567
|
+
debugLog("cleanup", "Server closed successfully", config6.verbose);
|
|
254371
254568
|
resolve5();
|
|
254372
254569
|
});
|
|
254373
254570
|
}));
|
|
254374
|
-
|
|
254375
|
-
|
|
254376
|
-
|
|
254377
|
-
|
|
254378
|
-
|
|
254379
|
-
|
|
254380
|
-
|
|
254381
|
-
|
|
254382
|
-
|
|
254383
|
-
}
|
|
254384
|
-
|
|
254385
|
-
|
|
254386
|
-
|
|
254387
|
-
|
|
254571
|
+
cleanupPromises.push(...serverClosePromises);
|
|
254572
|
+
if (config6.etcHostsCleanup) {
|
|
254573
|
+
debugLog("cleanup", "Cleaning up hosts file entries", config6.verbose);
|
|
254574
|
+
try {
|
|
254575
|
+
const toUrl = new URL(config6.to.startsWith("http") ? config6.to : `http://${config6.to}`);
|
|
254576
|
+
const hostname = toUrl.hostname;
|
|
254577
|
+
if (!hostname.includes("localhost") && !hostname.includes("127.0.0.1")) {
|
|
254578
|
+
log.info("Cleaning up hosts file entries...");
|
|
254579
|
+
cleanupPromises.push(removeHosts([hostname]).then(() => {
|
|
254580
|
+
debugLog("cleanup", `Removed hosts entry for ${hostname}`, config6.verbose);
|
|
254581
|
+
}).catch((err4) => {
|
|
254582
|
+
debugLog("cleanup", `Failed to remove hosts entry: ${err4}`, config6.verbose);
|
|
254583
|
+
log.warn(`Failed to clean up hosts file entry for ${hostname}:`, err4);
|
|
254584
|
+
}));
|
|
254585
|
+
}
|
|
254586
|
+
} catch (err4) {
|
|
254587
|
+
debugLog("cleanup", `Error parsing URL during hosts cleanup: ${err4}`, config6.verbose);
|
|
254588
|
+
log.warn("Failed to parse URL for hosts cleanup:", err4);
|
|
254589
|
+
}
|
|
254590
|
+
}
|
|
254591
|
+
try {
|
|
254592
|
+
await Promise.all(cleanupPromises);
|
|
254593
|
+
debugLog("cleanup", "All cleanup tasks completed successfully", config6.verbose);
|
|
254594
|
+
log.success("All cleanup tasks completed successfully");
|
|
254595
|
+
process24.exit(0);
|
|
254596
|
+
} catch (err4) {
|
|
254597
|
+
debugLog("cleanup", `Error during cleanup: ${err4}`, config6.verbose);
|
|
254598
|
+
log.error("Error during cleanup:", err4);
|
|
254599
|
+
process24.exit(1);
|
|
254600
|
+
}
|
|
254601
|
+
}
|
|
254602
|
+
process24.on("SIGINT", cleanup);
|
|
254603
|
+
process24.on("SIGTERM", cleanup);
|
|
254604
|
+
process24.on("uncaughtException", (err4) => {
|
|
254605
|
+
debugLog("process", `Uncaught exception: ${err4}`, config6.verbose);
|
|
254388
254606
|
log.error("Uncaught exception:", err4);
|
|
254389
254607
|
cleanup();
|
|
254390
254608
|
});
|
|
254391
254609
|
async function loadSSLConfig(options4) {
|
|
254392
|
-
|
|
254610
|
+
debugLog("ssl", "Loading SSL configuration", options4.verbose);
|
|
254611
|
+
if (options4.https === true)
|
|
254612
|
+
options4.https = httpsConfig();
|
|
254613
|
+
else if (options4.https === false)
|
|
254614
|
+
return null;
|
|
254393
254615
|
if (!options4.https?.keyPath && !options4.https?.certPath) {
|
|
254394
|
-
|
|
254616
|
+
debugLog("ssl", "No SSL configuration provided", options4.verbose);
|
|
254395
254617
|
return null;
|
|
254396
254618
|
}
|
|
254397
254619
|
if (options4.https?.keyPath && !options4.https?.certPath || !options4.https?.keyPath && options4.https?.certPath) {
|
|
254398
254620
|
const missing = !options4.https?.keyPath ? "keyPath" : "certPath";
|
|
254399
|
-
|
|
254621
|
+
debugLog("ssl", `Invalid SSL configuration - missing ${missing}`, options4.verbose);
|
|
254400
254622
|
throw new Error(`SSL Configuration requires both keyPath and certPath. Missing: ${missing}`);
|
|
254401
254623
|
}
|
|
254402
254624
|
try {
|
|
254403
254625
|
if (!options4.https?.keyPath || !options4.https?.certPath)
|
|
254404
254626
|
return null;
|
|
254405
254627
|
try {
|
|
254406
|
-
|
|
254407
|
-
const key = await
|
|
254408
|
-
const cert = await
|
|
254409
|
-
|
|
254628
|
+
debugLog("ssl", "Reading SSL certificate files", options4.verbose);
|
|
254629
|
+
const key = await fs7.promises.readFile(options4.https?.keyPath, "utf8");
|
|
254630
|
+
const cert = await fs7.promises.readFile(options4.https?.certPath, "utf8");
|
|
254631
|
+
debugLog("ssl", "SSL configuration loaded successfully", options4.verbose);
|
|
254410
254632
|
return { key, cert };
|
|
254411
254633
|
} catch (error) {
|
|
254412
254634
|
if (error.code === "ENOENT") {
|
|
254413
|
-
|
|
254414
|
-
await generateCertificate();
|
|
254415
|
-
|
|
254416
|
-
const key = await
|
|
254417
|
-
const cert = await
|
|
254418
|
-
|
|
254635
|
+
debugLog("ssl", "Certificates not found, generating new ones", options4.verbose);
|
|
254636
|
+
await generateCertificate(options4.to);
|
|
254637
|
+
debugLog("ssl", "Reading newly generated certificates", options4.verbose);
|
|
254638
|
+
const key = await fs7.promises.readFile(options4.https?.keyPath, "utf8");
|
|
254639
|
+
const cert = await fs7.promises.readFile(options4.https?.certPath, "utf8");
|
|
254640
|
+
debugLog("ssl", "New SSL certificates loaded successfully", options4.verbose);
|
|
254419
254641
|
return { key, cert };
|
|
254420
254642
|
}
|
|
254421
254643
|
throw error;
|
|
@@ -254423,22 +254645,22 @@ async function loadSSLConfig(options4) {
|
|
|
254423
254645
|
} catch (err4) {
|
|
254424
254646
|
const error = err4;
|
|
254425
254647
|
const detail = error.code === "ENOENT" ? `File not found: ${error.path}` : error.message;
|
|
254426
|
-
|
|
254648
|
+
debugLog("ssl", `SSL configuration error: ${error}`, options4.verbose);
|
|
254427
254649
|
throw new Error(`SSL Configuration Error: ${detail}`);
|
|
254428
254650
|
}
|
|
254429
254651
|
}
|
|
254430
254652
|
function isPortInUse(port, hostname, verbose) {
|
|
254431
|
-
|
|
254653
|
+
debugLog("port", `Checking if port ${port} is in use on ${hostname}`, verbose);
|
|
254432
254654
|
return new Promise((resolve5) => {
|
|
254433
254655
|
const server = net.createServer();
|
|
254434
254656
|
server.once("error", (err4) => {
|
|
254435
254657
|
if (err4.code === "EADDRINUSE") {
|
|
254436
|
-
|
|
254658
|
+
debugLog("port", `Port ${port} is in use`, verbose);
|
|
254437
254659
|
resolve5(true);
|
|
254438
254660
|
}
|
|
254439
254661
|
});
|
|
254440
254662
|
server.once("listening", () => {
|
|
254441
|
-
|
|
254663
|
+
debugLog("port", `Port ${port} is available`, verbose);
|
|
254442
254664
|
server.close();
|
|
254443
254665
|
resolve5(false);
|
|
254444
254666
|
});
|
|
@@ -254446,17 +254668,17 @@ function isPortInUse(port, hostname, verbose) {
|
|
|
254446
254668
|
});
|
|
254447
254669
|
}
|
|
254448
254670
|
async function findAvailablePort(startPort, hostname, verbose) {
|
|
254449
|
-
|
|
254671
|
+
debugLog("port", `Finding available port starting from ${startPort}`, verbose);
|
|
254450
254672
|
let port = startPort;
|
|
254451
254673
|
while (await isPortInUse(port, hostname, verbose)) {
|
|
254452
|
-
|
|
254674
|
+
debugLog("port", `Port ${port} is in use, trying ${port + 1}`, verbose);
|
|
254453
254675
|
port++;
|
|
254454
254676
|
}
|
|
254455
|
-
|
|
254677
|
+
debugLog("port", `Found available port: ${port}`, verbose);
|
|
254456
254678
|
return port;
|
|
254457
254679
|
}
|
|
254458
254680
|
async function testConnection(hostname, port, verbose) {
|
|
254459
|
-
|
|
254681
|
+
debugLog("connection", `Testing connection to ${hostname}:${port}`, verbose);
|
|
254460
254682
|
return new Promise((resolve5, reject) => {
|
|
254461
254683
|
const socket = net.connect({
|
|
254462
254684
|
host: hostname,
|
|
@@ -254464,54 +254686,89 @@ async function testConnection(hostname, port, verbose) {
|
|
|
254464
254686
|
timeout: 5000
|
|
254465
254687
|
});
|
|
254466
254688
|
socket.once("connect", () => {
|
|
254467
|
-
|
|
254689
|
+
debugLog("connection", `Successfully connected to ${hostname}:${port}`, verbose);
|
|
254468
254690
|
socket.end();
|
|
254469
254691
|
resolve5();
|
|
254470
254692
|
});
|
|
254471
254693
|
socket.once("timeout", () => {
|
|
254472
|
-
|
|
254694
|
+
debugLog("connection", `Connection to ${hostname}:${port} timed out`, verbose);
|
|
254473
254695
|
socket.destroy();
|
|
254474
254696
|
reject(new Error(`Connection to ${hostname}:${port} timed out`));
|
|
254475
254697
|
});
|
|
254476
254698
|
socket.once("error", (err4) => {
|
|
254477
|
-
|
|
254699
|
+
debugLog("connection", `Failed to connect to ${hostname}:${port}: ${err4}`, verbose);
|
|
254478
254700
|
socket.destroy();
|
|
254479
254701
|
reject(new Error(`Failed to connect to ${hostname}:${port}: ${err4.message}`));
|
|
254480
254702
|
});
|
|
254481
254703
|
});
|
|
254482
254704
|
}
|
|
254483
254705
|
async function startServer(options4) {
|
|
254484
|
-
|
|
254706
|
+
debugLog("server", `Starting server with options: ${JSON.stringify(options4)}`, options4?.verbose);
|
|
254485
254707
|
if (!options4)
|
|
254486
254708
|
options4 = config6;
|
|
254487
254709
|
if (!options4.from)
|
|
254488
254710
|
options4.from = config6.from;
|
|
254489
254711
|
if (!options4.to)
|
|
254490
254712
|
options4.to = config6.to;
|
|
254491
|
-
if (config6.https) {
|
|
254492
|
-
const domain = config6.https.altNameURIs?.[0] || new URL(options4.to).hostname;
|
|
254493
|
-
options4.keyPath = config6.https.keyPath || `/Users/${process23.env.USER}/.stacks/ssl/${domain}.crt.key`;
|
|
254494
|
-
options4.certPath = config6.https.certPath || `/Users/${process23.env.USER}/.stacks/ssl/${domain}.crt`;
|
|
254495
|
-
debugLog2("server", `HTTPS enabled, using cert paths: ${options4.keyPath}, ${options4.certPath}`, options4.verbose);
|
|
254496
|
-
}
|
|
254497
254713
|
const fromUrl = new URL(options4.from.startsWith("http") ? options4.from : `http://${options4.from}`);
|
|
254498
254714
|
const toUrl = new URL(options4.to.startsWith("http") ? options4.to : `http://${options4.to}`);
|
|
254499
254715
|
const fromPort = Number.parseInt(fromUrl.port) || (fromUrl.protocol.includes("https:") ? 443 : 80);
|
|
254500
|
-
|
|
254716
|
+
const hostsToCheck = [toUrl.hostname];
|
|
254717
|
+
if (!toUrl.hostname.includes("localhost") && !toUrl.hostname.includes("127.0.0.1")) {
|
|
254718
|
+
debugLog("hosts", `Checking if hosts file entry exists for: ${toUrl.hostname}`, options4.verbose);
|
|
254719
|
+
try {
|
|
254720
|
+
const hostsExist = await checkHosts(hostsToCheck);
|
|
254721
|
+
if (!hostsExist[0]) {
|
|
254722
|
+
log.info(`Adding ${toUrl.hostname} to hosts file...`);
|
|
254723
|
+
log.info("This may require sudo/administrator privileges");
|
|
254724
|
+
try {
|
|
254725
|
+
await addHosts(hostsToCheck);
|
|
254726
|
+
} catch (addError) {
|
|
254727
|
+
log.error("Failed to add hosts entry:", addError.message);
|
|
254728
|
+
log.warn("You can manually add this entry to your hosts file:");
|
|
254729
|
+
log.warn(`127.0.0.1 ${toUrl.hostname}`);
|
|
254730
|
+
log.warn(`::1 ${toUrl.hostname}`);
|
|
254731
|
+
if (process24.platform === "win32") {
|
|
254732
|
+
log.warn("On Windows:");
|
|
254733
|
+
log.warn("1. Run notepad as administrator");
|
|
254734
|
+
log.warn("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");
|
|
254735
|
+
} else {
|
|
254736
|
+
log.warn("On Unix systems:");
|
|
254737
|
+
log.warn("sudo nano /etc/hosts");
|
|
254738
|
+
}
|
|
254739
|
+
}
|
|
254740
|
+
} else {
|
|
254741
|
+
debugLog("hosts", `Host entry already exists for ${toUrl.hostname}`, options4.verbose);
|
|
254742
|
+
}
|
|
254743
|
+
} catch (checkError) {
|
|
254744
|
+
log.error("Failed to check hosts file:", checkError.message);
|
|
254745
|
+
}
|
|
254746
|
+
}
|
|
254747
|
+
if (config6.https) {
|
|
254748
|
+
if (config6.https === true)
|
|
254749
|
+
config6.https = httpsConfig();
|
|
254750
|
+
const domain = toUrl.hostname;
|
|
254751
|
+
if (typeof options4.https !== "boolean" && options4.https) {
|
|
254752
|
+
options4.https.keyPath = config6.https.keyPath || path10.join(os9.homedir(), ".stacks", "ssl", `${domain}.crt.key`);
|
|
254753
|
+
options4.https.certPath = config6.https.certPath || path10.join(os9.homedir(), ".stacks", "ssl", `${domain}.crt`);
|
|
254754
|
+
debugLog("server", `HTTPS enabled, using cert paths: ${options4.https.keyPath}, ${options4.https.certPath}`, options4.verbose);
|
|
254755
|
+
}
|
|
254756
|
+
}
|
|
254757
|
+
debugLog("server", `Parsed URLs - from: ${fromUrl}, to: ${toUrl}`, options4.verbose);
|
|
254501
254758
|
try {
|
|
254502
254759
|
await testConnection(fromUrl.hostname, fromPort, options4.verbose);
|
|
254503
254760
|
} catch (err4) {
|
|
254504
|
-
|
|
254761
|
+
debugLog("server", `Connection test failed: ${err4}`, options4.verbose);
|
|
254505
254762
|
log.error(err4.message);
|
|
254506
|
-
|
|
254763
|
+
process24.exit(1);
|
|
254507
254764
|
}
|
|
254508
254765
|
let sslConfig = null;
|
|
254509
254766
|
if (config6.https) {
|
|
254510
254767
|
try {
|
|
254511
254768
|
sslConfig = await loadSSLConfig(options4);
|
|
254512
254769
|
} catch (err4) {
|
|
254513
|
-
|
|
254514
|
-
await generateCertificate();
|
|
254770
|
+
debugLog("server", `SSL config failed, attempting to generate certificates: ${err4}`, options4.verbose);
|
|
254771
|
+
await generateCertificate(options4.to);
|
|
254515
254772
|
sslConfig = await loadSSLConfig(options4);
|
|
254516
254773
|
}
|
|
254517
254774
|
}
|
|
@@ -254528,9 +254785,9 @@ async function startServer(options4) {
|
|
|
254528
254785
|
});
|
|
254529
254786
|
}
|
|
254530
254787
|
async function createProxyServer(from, to, fromPort, listenPort, hostname, sourceUrl, ssl, verbose) {
|
|
254531
|
-
|
|
254788
|
+
debugLog("proxy", `Creating proxy server ${from} -> ${to}`, verbose);
|
|
254532
254789
|
const requestHandler = (req, res) => {
|
|
254533
|
-
|
|
254790
|
+
debugLog("request", `Incoming request: ${req.method} ${req.url}`, verbose);
|
|
254534
254791
|
const proxyOptions = {
|
|
254535
254792
|
hostname: sourceUrl.hostname,
|
|
254536
254793
|
port: fromPort,
|
|
@@ -254541,9 +254798,9 @@ async function createProxyServer(from, to, fromPort, listenPort, hostname, sourc
|
|
|
254541
254798
|
host: sourceUrl.host
|
|
254542
254799
|
}
|
|
254543
254800
|
};
|
|
254544
|
-
|
|
254801
|
+
debugLog("request", `Proxy request options: ${JSON.stringify(proxyOptions)}`, verbose);
|
|
254545
254802
|
const proxyReq = http.request(proxyOptions, (proxyRes) => {
|
|
254546
|
-
|
|
254803
|
+
debugLog("response", `Proxy response received with status ${proxyRes.statusCode}`, verbose);
|
|
254547
254804
|
const headers = {
|
|
254548
254805
|
...proxyRes.headers,
|
|
254549
254806
|
"Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
|
|
@@ -254553,7 +254810,7 @@ async function createProxyServer(from, to, fromPort, listenPort, hostname, sourc
|
|
|
254553
254810
|
proxyRes.pipe(res);
|
|
254554
254811
|
});
|
|
254555
254812
|
proxyReq.on("error", (err4) => {
|
|
254556
|
-
|
|
254813
|
+
debugLog("request", `Proxy request failed: ${err4}`, verbose);
|
|
254557
254814
|
log.error("Proxy request failed:", err4);
|
|
254558
254815
|
res.writeHead(502);
|
|
254559
254816
|
res.end(`Proxy Error: ${err4.message}`);
|
|
@@ -254580,11 +254837,11 @@ async function createProxyServer(from, to, fromPort, listenPort, hostname, sourc
|
|
|
254580
254837
|
allowHTTP1: true,
|
|
254581
254838
|
ALPNProtocols: ["h2", "http/1.1"]
|
|
254582
254839
|
} : undefined;
|
|
254583
|
-
|
|
254840
|
+
debugLog("server", `Creating server with SSL config: ${!!ssl}`, verbose);
|
|
254584
254841
|
const server = ssl && serverOptions ? https.createServer(serverOptions, requestHandler) : http.createServer(requestHandler);
|
|
254585
254842
|
if (ssl) {
|
|
254586
254843
|
server.on("secureConnection", (tlsSocket) => {
|
|
254587
|
-
|
|
254844
|
+
debugLog("tls", `TLS Connection established: ${JSON.stringify({
|
|
254588
254845
|
protocol: tlsSocket.getProtocol?.(),
|
|
254589
254846
|
cipher: tlsSocket.getCipher?.(),
|
|
254590
254847
|
authorized: tlsSocket.authorized,
|
|
@@ -254595,7 +254852,7 @@ async function createProxyServer(from, to, fromPort, listenPort, hostname, sourc
|
|
|
254595
254852
|
activeServers.add(server);
|
|
254596
254853
|
return new Promise((resolve5, reject) => {
|
|
254597
254854
|
server.listen(listenPort, hostname, () => {
|
|
254598
|
-
|
|
254855
|
+
debugLog("server", `Server listening on port ${listenPort}`, verbose);
|
|
254599
254856
|
console.log("");
|
|
254600
254857
|
console.log(` ${green(bold("reverse-proxy"))} ${green(`v${version}`)}`);
|
|
254601
254858
|
console.log("");
|
|
@@ -254612,13 +254869,13 @@ async function createProxyServer(from, to, fromPort, listenPort, hostname, sourc
|
|
|
254612
254869
|
resolve5();
|
|
254613
254870
|
});
|
|
254614
254871
|
server.on("error", (err4) => {
|
|
254615
|
-
|
|
254872
|
+
debugLog("server", `Server error: ${err4}`, verbose);
|
|
254616
254873
|
reject(err4);
|
|
254617
254874
|
});
|
|
254618
254875
|
});
|
|
254619
254876
|
}
|
|
254620
254877
|
async function setupReverseProxy(options4) {
|
|
254621
|
-
|
|
254878
|
+
debugLog("setup", `Setting up reverse proxy: ${JSON.stringify(options4)}`, options4.verbose);
|
|
254622
254879
|
const { from, to, fromPort, sourceUrl, ssl, verbose } = options4;
|
|
254623
254880
|
const httpPort = 80;
|
|
254624
254881
|
const httpsPort = 443;
|
|
@@ -254627,73 +254884,72 @@ async function setupReverseProxy(options4) {
|
|
|
254627
254884
|
if (ssl) {
|
|
254628
254885
|
const isHttpPortBusy = await isPortInUse(httpPort, hostname, verbose);
|
|
254629
254886
|
if (!isHttpPortBusy) {
|
|
254630
|
-
|
|
254887
|
+
debugLog("setup", "Starting HTTP redirect server", verbose);
|
|
254631
254888
|
startHttpRedirectServer(verbose);
|
|
254632
254889
|
} else {
|
|
254633
|
-
|
|
254890
|
+
debugLog("setup", "Port 80 is in use, skipping HTTP redirect", verbose);
|
|
254634
254891
|
log.warn("Port 80 is in use, HTTP to HTTPS redirect will not be available");
|
|
254635
254892
|
}
|
|
254636
254893
|
}
|
|
254637
254894
|
const targetPort = ssl ? httpsPort : httpPort;
|
|
254638
254895
|
const isTargetPortBusy = await isPortInUse(targetPort, hostname, verbose);
|
|
254639
254896
|
if (isTargetPortBusy) {
|
|
254640
|
-
|
|
254897
|
+
debugLog("setup", `Port ${targetPort} is busy, finding alternative`, verbose);
|
|
254641
254898
|
const availablePort = await findAvailablePort(ssl ? 8443 : 8080, hostname, verbose);
|
|
254642
254899
|
log.warn(`Port ${targetPort} is in use. Using port ${availablePort} instead.`);
|
|
254643
254900
|
log.info(`You can use 'sudo lsof -i :${targetPort}' (Unix) or 'netstat -ano | findstr :${targetPort}' (Windows) to check what's using the port.`);
|
|
254644
254901
|
await createProxyServer(from, to, fromPort, availablePort, hostname, sourceUrl, ssl, verbose);
|
|
254645
254902
|
} else {
|
|
254646
|
-
|
|
254903
|
+
debugLog("setup", `Using default port ${targetPort}`, verbose);
|
|
254647
254904
|
await createProxyServer(from, to, fromPort, targetPort, hostname, sourceUrl, ssl, verbose);
|
|
254648
254905
|
}
|
|
254649
254906
|
} catch (err4) {
|
|
254650
|
-
|
|
254907
|
+
debugLog("setup", `Setup failed: ${err4}`, verbose);
|
|
254651
254908
|
log.error(`Failed to setup reverse proxy: ${err4.message}`);
|
|
254652
254909
|
cleanup();
|
|
254653
254910
|
}
|
|
254654
254911
|
}
|
|
254655
254912
|
function startHttpRedirectServer(verbose) {
|
|
254656
|
-
|
|
254913
|
+
debugLog("redirect", "Starting HTTP redirect server", verbose);
|
|
254657
254914
|
const server = http.createServer((req, res) => {
|
|
254658
254915
|
const host = req.headers.host || "";
|
|
254659
|
-
|
|
254916
|
+
debugLog("redirect", `Redirecting request from ${host}${req.url} to HTTPS`, verbose);
|
|
254660
254917
|
res.writeHead(301, {
|
|
254661
254918
|
Location: `https://${host}${req.url}`
|
|
254662
254919
|
});
|
|
254663
254920
|
res.end();
|
|
254664
254921
|
}).listen(80);
|
|
254665
254922
|
activeServers.add(server);
|
|
254666
|
-
|
|
254923
|
+
debugLog("redirect", "HTTP redirect server started", verbose);
|
|
254667
254924
|
}
|
|
254668
254925
|
function startProxy(options4) {
|
|
254669
254926
|
const finalOptions = {
|
|
254670
254927
|
...config6,
|
|
254671
254928
|
...options4
|
|
254672
254929
|
};
|
|
254673
|
-
|
|
254930
|
+
debugLog("proxy", `Starting proxy with options: ${JSON.stringify({
|
|
254674
254931
|
from: finalOptions.from,
|
|
254675
254932
|
to: finalOptions.to,
|
|
254676
|
-
|
|
254677
|
-
certPath: finalOptions.https.certPath
|
|
254933
|
+
https: finalOptions.https
|
|
254678
254934
|
})}`, finalOptions.verbose);
|
|
254679
254935
|
startServer(finalOptions).catch((err4) => {
|
|
254680
|
-
|
|
254936
|
+
debugLog("proxy", `Failed to start proxy: ${err4}`, finalOptions.verbose);
|
|
254681
254937
|
log.error(`Failed to start proxy: ${err4.message}`);
|
|
254682
254938
|
cleanup();
|
|
254683
254939
|
});
|
|
254684
254940
|
}
|
|
254685
254941
|
function startProxies(options4) {
|
|
254686
254942
|
if (Array.isArray(options4)) {
|
|
254687
|
-
|
|
254943
|
+
debugLog("proxies", `Starting multiple proxies: ${options4.length}`, options4[0]?.verbose);
|
|
254688
254944
|
Promise.all(options4.map((option) => startServer(option))).catch((err4) => {
|
|
254689
|
-
|
|
254945
|
+
debugLog("proxies", `Failed to start proxies: ${err4}`, options4[0]?.verbose);
|
|
254690
254946
|
log.error("Failed to start proxies:", err4);
|
|
254691
254947
|
cleanup();
|
|
254692
254948
|
});
|
|
254693
254949
|
} else if (options4) {
|
|
254694
|
-
|
|
254950
|
+
debugLog("proxies", "Starting single proxy", options4.verbose);
|
|
254695
254951
|
startServer(options4).catch((err4) => {
|
|
254696
|
-
|
|
254952
|
+
debugLog("proxies", `Failed to start proxy: ${err4}`, options4.verbose);
|
|
254697
254953
|
log.error("Failed to start proxy:", err4);
|
|
254698
254954
|
cleanup();
|
|
254699
254955
|
});
|
|
@@ -254713,12 +254969,12 @@ cli.command("start", "Start the Reverse Proxy Server").option("--from <from>", "
|
|
|
254713
254969
|
});
|
|
254714
254970
|
cli.command("update:etc-hosts", "Update the /etc/hosts file with the proxy domains. Please note, this command requires sudo/admin permissions.").alias("update-etc-hosts").example("sudo reverse-proxy update:etc-hosts").example("sudo reverse-proxy update-etc-hosts").action(async () => {
|
|
254715
254971
|
log.info("Ensuring /etc/hosts file covers the proxy domain/s...");
|
|
254716
|
-
const
|
|
254972
|
+
const hostsFilePath2 = os10.platform() === "win32" ? "C:\\Windows\\System32\\drivers\\etc\\hosts" : "/etc/hosts";
|
|
254717
254973
|
if (config6 && typeof config6 === "object") {
|
|
254718
254974
|
const entriesToAdd = Object.entries(config6).map(([from, to]) => `127.0.0.1 ${to} # reverse-proxy mapping for ${from}`);
|
|
254719
254975
|
entriesToAdd.push("127.0.0.1 localhost # essential localhost mapping");
|
|
254720
254976
|
try {
|
|
254721
|
-
let currentHostsContent = export_readFileSync(
|
|
254977
|
+
let currentHostsContent = export_readFileSync(hostsFilePath2, "utf8");
|
|
254722
254978
|
let updated = false;
|
|
254723
254979
|
for (const entry of entriesToAdd) {
|
|
254724
254980
|
const [ip, host] = entry.split(" ", 2);
|
|
@@ -254731,7 +254987,7 @@ cli.command("update:etc-hosts", "Update the /etc/hosts file with the proxy domai
|
|
|
254731
254987
|
}
|
|
254732
254988
|
}
|
|
254733
254989
|
if (updated) {
|
|
254734
|
-
export_writeFileSync(
|
|
254990
|
+
export_writeFileSync(hostsFilePath2, currentHostsContent, "utf8");
|
|
254735
254991
|
log.success("Hosts file updated with latest proxy domains.");
|
|
254736
254992
|
} else {
|
|
254737
254993
|
log.info("No new entries were added to the hosts file.");
|