@stacksjs/rpx 0.2.0 → 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 +1 -0
- package/dist/cli.js +423 -216
- package/dist/hosts.d.ts +5 -0
- package/dist/https.d.ts +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +441 -230
- package/dist/start.d.ts +1 -1
- package/dist/types.d.ts +1 -0
- 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,22 +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
|
|
217922
|
-
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
|
+
}
|
|
217923
218089
|
|
|
217924
218090
|
// src/https.ts
|
|
217925
|
-
import
|
|
217926
|
-
import
|
|
218091
|
+
import os8 from "os";
|
|
218092
|
+
import path9 from "path";
|
|
217927
218093
|
|
|
217928
218094
|
// node_modules/@stacksjs/tlsx/dist/index.js
|
|
217929
218095
|
import fs22 from "fs";
|
|
@@ -217962,7 +218128,7 @@ import process82 from "process";
|
|
|
217962
218128
|
import process102 from "process";
|
|
217963
218129
|
import process182 from "process";
|
|
217964
218130
|
import process112 from "process";
|
|
217965
|
-
import
|
|
218131
|
+
import os7 from "os";
|
|
217966
218132
|
import tty33 from "tty";
|
|
217967
218133
|
import process142 from "process";
|
|
217968
218134
|
import process132 from "process";
|
|
@@ -217971,10 +218137,10 @@ import process162 from "process";
|
|
|
217971
218137
|
import process172 from "process";
|
|
217972
218138
|
import process192 from "process";
|
|
217973
218139
|
import os23 from "os";
|
|
217974
|
-
import
|
|
218140
|
+
import path8 from "path";
|
|
217975
218141
|
import { resolve as resolve6 } from "path";
|
|
217976
|
-
import
|
|
217977
|
-
import
|
|
218142
|
+
import process23 from "process";
|
|
218143
|
+
import fs6 from "fs";
|
|
217978
218144
|
import path22 from "path";
|
|
217979
218145
|
var __create4 = Object.create;
|
|
217980
218146
|
var __getProtoOf4 = Object.getPrototypeOf;
|
|
@@ -251465,7 +251631,7 @@ function _supportsColor3(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
|
|
251465
251631
|
return min;
|
|
251466
251632
|
}
|
|
251467
251633
|
if (process112.platform === "win32") {
|
|
251468
|
-
const osRelease =
|
|
251634
|
+
const osRelease = os7.release().split(".");
|
|
251469
251635
|
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
251470
251636
|
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
251471
251637
|
}
|
|
@@ -254018,7 +254184,7 @@ function isObject33(item) {
|
|
|
254018
254184
|
return item && typeof item === "object" && !Array.isArray(item);
|
|
254019
254185
|
}
|
|
254020
254186
|
async function loadConfig3({ name, cwd, defaultConfig }) {
|
|
254021
|
-
const configPath2 = resolve6(cwd ||
|
|
254187
|
+
const configPath2 = resolve6(cwd || process23.cwd(), `${name}.config`);
|
|
254022
254188
|
try {
|
|
254023
254189
|
const importedConfig = await import(configPath2);
|
|
254024
254190
|
const loadedConfig = importedConfig.default || importedConfig;
|
|
@@ -254032,18 +254198,18 @@ var config42 = await loadConfig3({
|
|
|
254032
254198
|
defaultConfig: {
|
|
254033
254199
|
altNameIPs: ["127.0.0.1"],
|
|
254034
254200
|
altNameURIs: ["localhost"],
|
|
254035
|
-
organizationName: "
|
|
254201
|
+
organizationName: "Local Development",
|
|
254036
254202
|
countryName: "US",
|
|
254037
254203
|
stateName: "California",
|
|
254038
254204
|
localityName: "Playa Vista",
|
|
254039
254205
|
commonName: "stacks.localhost",
|
|
254040
254206
|
validityDays: 180,
|
|
254041
254207
|
hostCertCN: "stacks.localhost",
|
|
254042
|
-
domain: "localhost",
|
|
254208
|
+
domain: "stacks.localhost",
|
|
254043
254209
|
rootCAObject: { certificate: "", privateKey: "" },
|
|
254044
|
-
caCertPath:
|
|
254045
|
-
certPath:
|
|
254046
|
-
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`),
|
|
254047
254213
|
verbose: false
|
|
254048
254214
|
}
|
|
254049
254215
|
});
|
|
@@ -254059,10 +254225,10 @@ function findFoldersWithFile(rootDir, fileName) {
|
|
|
254059
254225
|
const result2 = [];
|
|
254060
254226
|
function search(dir) {
|
|
254061
254227
|
try {
|
|
254062
|
-
const files2 =
|
|
254228
|
+
const files2 = fs6.readdirSync(dir);
|
|
254063
254229
|
for (const file of files2) {
|
|
254064
254230
|
const filePath = path22.join(dir, file);
|
|
254065
|
-
const stats =
|
|
254231
|
+
const stats = fs6.lstatSync(filePath);
|
|
254066
254232
|
if (stats.isDirectory()) {
|
|
254067
254233
|
search(filePath);
|
|
254068
254234
|
} else if (file === fileName) {
|
|
@@ -254076,29 +254242,29 @@ function findFoldersWithFile(rootDir, fileName) {
|
|
|
254076
254242
|
search(rootDir);
|
|
254077
254243
|
return result2;
|
|
254078
254244
|
}
|
|
254079
|
-
function
|
|
254245
|
+
function debugLog2(category, message, verbose) {
|
|
254080
254246
|
if (verbose || config42.verbose) {
|
|
254081
254247
|
console.debug(`[tlsx:${category}] ${message}`);
|
|
254082
254248
|
}
|
|
254083
254249
|
}
|
|
254084
254250
|
function randomSerialNumber(verbose) {
|
|
254085
|
-
|
|
254251
|
+
debugLog2("cert", "Generating random serial number", verbose);
|
|
254086
254252
|
const serialNumber = makeNumberPositive(import_node_forge2.default.util.bytesToHex(import_node_forge2.default.random.getBytesSync(20)));
|
|
254087
|
-
|
|
254253
|
+
debugLog2("cert", `Generated serial number: ${serialNumber}`, verbose);
|
|
254088
254254
|
return serialNumber;
|
|
254089
254255
|
}
|
|
254090
254256
|
function getCertNotBefore(verbose) {
|
|
254091
|
-
|
|
254257
|
+
debugLog2("cert", "Calculating certificate not-before date", verbose);
|
|
254092
254258
|
const twoDaysAgo = new Date(Date.now() - 172800000);
|
|
254093
254259
|
const year = twoDaysAgo.getFullYear();
|
|
254094
254260
|
const month = (twoDaysAgo.getMonth() + 1).toString().padStart(2, "0");
|
|
254095
254261
|
const day = twoDaysAgo.getDate().toString().padStart(2, "0");
|
|
254096
254262
|
const date = new Date(`${year}-${month}-${day}T23:59:59Z`);
|
|
254097
|
-
|
|
254263
|
+
debugLog2("cert", `Certificate not-before date: ${date.toISOString()}`, verbose);
|
|
254098
254264
|
return date;
|
|
254099
254265
|
}
|
|
254100
254266
|
function getCertNotAfter(notBefore, verbose) {
|
|
254101
|
-
|
|
254267
|
+
debugLog2("cert", "Calculating certificate not-after date", verbose);
|
|
254102
254268
|
const validityDays = config42.validityDays;
|
|
254103
254269
|
const daysInMillis = validityDays * 60 * 60 * 24 * 1000;
|
|
254104
254270
|
const notAfterDate = new Date(notBefore.getTime() + daysInMillis);
|
|
@@ -254106,27 +254272,27 @@ function getCertNotAfter(notBefore, verbose) {
|
|
|
254106
254272
|
const month = (notAfterDate.getMonth() + 1).toString().padStart(2, "0");
|
|
254107
254273
|
const day = notAfterDate.getDate().toString().padStart(2, "0");
|
|
254108
254274
|
const date = new Date(`${year}-${month}-${day}T23:59:59Z`);
|
|
254109
|
-
|
|
254275
|
+
debugLog2("cert", `Certificate not-after date: ${date.toISOString()} (${validityDays} days validity)`, verbose);
|
|
254110
254276
|
return date;
|
|
254111
254277
|
}
|
|
254112
254278
|
function getCANotAfter(notBefore, verbose) {
|
|
254113
|
-
|
|
254279
|
+
debugLog2("cert", "Calculating CA not-after date", verbose);
|
|
254114
254280
|
const year = notBefore.getFullYear() + 100;
|
|
254115
254281
|
const month = (notBefore.getMonth() + 1).toString().padStart(2, "0");
|
|
254116
254282
|
const day = notBefore.getDate().toString().padStart(2, "0");
|
|
254117
254283
|
const date = new Date(`${year}-${month}-${day}T23:59:59Z`);
|
|
254118
|
-
|
|
254284
|
+
debugLog2("cert", `CA not-after date: ${date.toISOString()} (100 years validity)`, verbose);
|
|
254119
254285
|
return date;
|
|
254120
254286
|
}
|
|
254121
254287
|
async function createRootCA(options23) {
|
|
254122
|
-
|
|
254123
|
-
|
|
254288
|
+
debugLog2("ca", "Creating new Root CA Certificate", options23?.verbose);
|
|
254289
|
+
debugLog2("ca", "Generating 2048-bit RSA key pair", options23?.verbose);
|
|
254124
254290
|
const { privateKey, publicKey } = import_node_forge2.pki.rsa.generateKeyPair(2048);
|
|
254125
254291
|
const mergedOptions = {
|
|
254126
254292
|
...config42,
|
|
254127
254293
|
...options23 || {}
|
|
254128
254294
|
};
|
|
254129
|
-
|
|
254295
|
+
debugLog2("ca", "Setting certificate attributes", options23?.verbose);
|
|
254130
254296
|
const attributes = [
|
|
254131
254297
|
{ shortName: "C", value: mergedOptions.countryName },
|
|
254132
254298
|
{ shortName: "ST", value: mergedOptions.stateName },
|
|
@@ -254134,7 +254300,7 @@ async function createRootCA(options23) {
|
|
|
254134
254300
|
{ shortName: "O", value: "Local Development CA" },
|
|
254135
254301
|
{ shortName: "CN", value: "Local Development Root CA" }
|
|
254136
254302
|
];
|
|
254137
|
-
|
|
254303
|
+
debugLog2("ca", "Setting certificate extensions", options23?.verbose);
|
|
254138
254304
|
const extensions = [
|
|
254139
254305
|
{
|
|
254140
254306
|
name: "basicConstraints",
|
|
@@ -254151,7 +254317,7 @@ async function createRootCA(options23) {
|
|
|
254151
254317
|
name: "subjectKeyIdentifier"
|
|
254152
254318
|
}
|
|
254153
254319
|
];
|
|
254154
|
-
|
|
254320
|
+
debugLog2("ca", "Creating CA certificate", options23?.verbose);
|
|
254155
254321
|
const caCert = import_node_forge2.pki.createCertificate();
|
|
254156
254322
|
caCert.publicKey = publicKey;
|
|
254157
254323
|
caCert.serialNumber = randomSerialNumber(options23?.verbose);
|
|
@@ -254160,11 +254326,11 @@ async function createRootCA(options23) {
|
|
|
254160
254326
|
caCert.setSubject(attributes);
|
|
254161
254327
|
caCert.setIssuer(attributes);
|
|
254162
254328
|
caCert.setExtensions(extensions);
|
|
254163
|
-
|
|
254329
|
+
debugLog2("ca", "Signing certificate with SHA-256", options23?.verbose);
|
|
254164
254330
|
caCert.sign(privateKey, import_node_forge2.default.md.sha256.create());
|
|
254165
254331
|
const pemCert = import_node_forge2.pki.certificateToPem(caCert);
|
|
254166
254332
|
const pemKey = import_node_forge2.pki.privateKeyToPem(privateKey);
|
|
254167
|
-
|
|
254333
|
+
debugLog2("ca", "Root CA Certificate created successfully", options23?.verbose);
|
|
254168
254334
|
return {
|
|
254169
254335
|
certificate: pemCert,
|
|
254170
254336
|
privateKey: pemKey,
|
|
@@ -254173,34 +254339,41 @@ async function createRootCA(options23) {
|
|
|
254173
254339
|
};
|
|
254174
254340
|
}
|
|
254175
254341
|
async function generateCert(options23) {
|
|
254176
|
-
|
|
254177
|
-
|
|
254342
|
+
debugLog2("cert", "Generating new host certificate", options23?.verbose);
|
|
254343
|
+
debugLog2("cert", `Options: ${JSON.stringify(options23)}`, options23?.verbose);
|
|
254178
254344
|
if (!options23?.hostCertCN?.trim()) {
|
|
254179
|
-
|
|
254345
|
+
debugLog2("cert", "Error: hostCertCN is required", options23?.verbose);
|
|
254180
254346
|
throw new Error('"hostCertCN" must be a String');
|
|
254181
254347
|
}
|
|
254182
254348
|
if (!options23.domain?.trim()) {
|
|
254183
|
-
|
|
254349
|
+
debugLog2("cert", "Error: domain is required", options23?.verbose);
|
|
254184
254350
|
throw new Error('"domain" must be a String');
|
|
254185
254351
|
}
|
|
254186
254352
|
if (!options23.rootCAObject || !options23.rootCAObject.certificate || !options23.rootCAObject.privateKey) {
|
|
254187
|
-
|
|
254353
|
+
debugLog2("cert", "Error: rootCAObject is invalid or missing", options23?.verbose);
|
|
254188
254354
|
throw new Error('"rootCAObject" must be an Object with the properties "certificate" & "privateKey"');
|
|
254189
254355
|
}
|
|
254190
|
-
|
|
254356
|
+
debugLog2("cert", "Converting Root CA PEM to forge objects", options23?.verbose);
|
|
254191
254357
|
const caCert = import_node_forge2.pki.certificateFromPem(options23.rootCAObject.certificate);
|
|
254192
254358
|
const caKey = import_node_forge2.pki.privateKeyFromPem(options23.rootCAObject.privateKey);
|
|
254193
|
-
|
|
254359
|
+
debugLog2("cert", "Generating 2048-bit RSA key pair for host certificate", options23?.verbose);
|
|
254194
254360
|
const hostKeys = import_node_forge2.pki.rsa.generateKeyPair(2048);
|
|
254195
|
-
|
|
254361
|
+
debugLog2("cert", "Setting certificate attributes", options23?.verbose);
|
|
254196
254362
|
const attributes = [
|
|
254197
|
-
{ shortName: "C", value: config42.countryName },
|
|
254198
|
-
{ shortName: "ST", value: config42.stateName },
|
|
254199
|
-
{ shortName: "L", value: config42.localityName },
|
|
254200
|
-
{ shortName: "O", value:
|
|
254201
|
-
{ 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 }
|
|
254202
254368
|
];
|
|
254203
|
-
|
|
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);
|
|
254204
254377
|
const extensions = [
|
|
254205
254378
|
{
|
|
254206
254379
|
name: "basicConstraints",
|
|
@@ -254220,30 +254393,25 @@ async function generateCert(options23) {
|
|
|
254220
254393
|
},
|
|
254221
254394
|
{
|
|
254222
254395
|
name: "subjectAltName",
|
|
254223
|
-
altNames
|
|
254224
|
-
{ type: 2, value: "*.localhost" },
|
|
254225
|
-
{ type: 2, value: "localhost" },
|
|
254226
|
-
{ type: 2, value: "stacks.localhost" },
|
|
254227
|
-
{ type: 2, value: options23.domain }
|
|
254228
|
-
]
|
|
254396
|
+
altNames
|
|
254229
254397
|
}
|
|
254230
254398
|
];
|
|
254231
|
-
|
|
254399
|
+
debugLog2("cert", "Creating new host certificate", options23?.verbose);
|
|
254232
254400
|
const newHostCert = import_node_forge2.pki.createCertificate();
|
|
254233
254401
|
newHostCert.publicKey = hostKeys.publicKey;
|
|
254234
|
-
|
|
254402
|
+
debugLog2("cert", "Setting certificate properties", options23?.verbose);
|
|
254235
254403
|
newHostCert.serialNumber = randomSerialNumber(options23?.verbose);
|
|
254236
254404
|
newHostCert.validity.notBefore = getCertNotBefore(options23?.verbose);
|
|
254237
254405
|
newHostCert.validity.notAfter = getCertNotAfter(newHostCert.validity.notBefore, options23?.verbose);
|
|
254238
254406
|
newHostCert.setSubject(attributes);
|
|
254239
254407
|
newHostCert.setIssuer(caCert.subject.attributes);
|
|
254240
254408
|
newHostCert.setExtensions(extensions);
|
|
254241
|
-
|
|
254409
|
+
debugLog2("cert", "Signing certificate with SHA-256", options23?.verbose);
|
|
254242
254410
|
newHostCert.sign(caKey, import_node_forge2.default.md.sha256.create());
|
|
254243
|
-
|
|
254411
|
+
debugLog2("cert", "Converting certificate to PEM format", options23?.verbose);
|
|
254244
254412
|
const pemHostCert = import_node_forge2.pki.certificateToPem(newHostCert);
|
|
254245
254413
|
const pemHostKey = import_node_forge2.pki.privateKeyToPem(hostKeys.privateKey);
|
|
254246
|
-
|
|
254414
|
+
debugLog2("cert", "Host certificate generated successfully", options23?.verbose);
|
|
254247
254415
|
return {
|
|
254248
254416
|
certificate: pemHostCert,
|
|
254249
254417
|
privateKey: pemHostKey,
|
|
@@ -254252,81 +254420,81 @@ async function generateCert(options23) {
|
|
|
254252
254420
|
};
|
|
254253
254421
|
}
|
|
254254
254422
|
async function addCertToSystemTrustStoreAndSaveCert(cert, caCert, options23) {
|
|
254255
|
-
|
|
254256
|
-
|
|
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);
|
|
254257
254425
|
const certPath = storeCert(cert, options23);
|
|
254258
|
-
|
|
254426
|
+
debugLog2("trust", "Storing CA certificate", options23?.verbose);
|
|
254259
254427
|
const caCertPath = storeCACert(caCert, options23);
|
|
254260
254428
|
const platform23 = os42.platform();
|
|
254261
|
-
|
|
254429
|
+
debugLog2("trust", `Detected platform: ${platform23}`, options23?.verbose);
|
|
254262
254430
|
const args = "TC, C, C";
|
|
254263
254431
|
if (platform23 === "darwin") {
|
|
254264
|
-
|
|
254432
|
+
debugLog2("trust", "Adding certificate to macOS keychain", options23?.verbose);
|
|
254265
254433
|
await runCommand2(`sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ${caCertPath}`);
|
|
254266
254434
|
} else if (platform23 === "win32") {
|
|
254267
|
-
|
|
254435
|
+
debugLog2("trust", "Adding certificate to Windows certificate store", options23?.verbose);
|
|
254268
254436
|
await runCommand2(`certutil -f -v -addstore -enterprise Root ${caCertPath}`);
|
|
254269
254437
|
} else if (platform23 === "linux") {
|
|
254270
|
-
|
|
254438
|
+
debugLog2("trust", "Adding certificate to Linux certificate store", options23?.verbose);
|
|
254271
254439
|
const rootDirectory = os42.homedir();
|
|
254272
254440
|
const targetFileName = "cert9.db";
|
|
254273
|
-
|
|
254441
|
+
debugLog2("trust", `Searching for certificate databases in ${rootDirectory}`, options23?.verbose);
|
|
254274
254442
|
const foldersWithFile = findFoldersWithFile(rootDirectory, targetFileName);
|
|
254275
254443
|
for (const folder of foldersWithFile) {
|
|
254276
|
-
|
|
254444
|
+
debugLog2("trust", `Processing certificate database in ${folder}`, options23?.verbose);
|
|
254277
254445
|
try {
|
|
254278
|
-
|
|
254446
|
+
debugLog2("trust", `Attempting to delete existing cert for ${config42.commonName}`, options23?.verbose);
|
|
254279
254447
|
await runCommand2(`certutil -d sql:${folder} -D -n ${config42.commonName}`);
|
|
254280
254448
|
} catch (error) {
|
|
254281
|
-
|
|
254449
|
+
debugLog2("trust", `Warning: Error deleting existing cert: ${error}`, options23?.verbose);
|
|
254282
254450
|
console.warn(`Error deleting existing cert: ${error}`);
|
|
254283
254451
|
}
|
|
254284
|
-
|
|
254452
|
+
debugLog2("trust", `Adding new certificate to ${folder}`, options23?.verbose);
|
|
254285
254453
|
await runCommand2(`certutil -d sql:${folder} -A -t ${args} -n ${config42.commonName} -i ${caCertPath}`);
|
|
254286
254454
|
log3.info(`Cert added to ${folder}`);
|
|
254287
254455
|
}
|
|
254288
254456
|
} else {
|
|
254289
|
-
|
|
254457
|
+
debugLog2("trust", `Error: Unsupported platform ${platform23}`, options23?.verbose);
|
|
254290
254458
|
throw new Error(`Unsupported platform: ${platform23}`);
|
|
254291
254459
|
}
|
|
254292
|
-
|
|
254460
|
+
debugLog2("trust", "Certificate successfully added to system trust store", options23?.verbose);
|
|
254293
254461
|
return certPath;
|
|
254294
254462
|
}
|
|
254295
254463
|
function storeCert(cert, options23) {
|
|
254296
|
-
|
|
254464
|
+
debugLog2("storage", `Storing certificate and private key with options: ${JSON.stringify(options23)}`, options23?.verbose);
|
|
254297
254465
|
const certPath = options23?.certPath || config42.certPath;
|
|
254298
254466
|
const certKeyPath = options23?.keyPath || config42.keyPath;
|
|
254299
|
-
|
|
254300
|
-
|
|
254467
|
+
debugLog2("storage", `Certificate path: ${certPath}`, options23?.verbose);
|
|
254468
|
+
debugLog2("storage", `Private key path: ${certKeyPath}`, options23?.verbose);
|
|
254301
254469
|
const certDir = path32.dirname(certPath);
|
|
254302
254470
|
if (!fs22.existsSync(certDir)) {
|
|
254303
|
-
|
|
254471
|
+
debugLog2("storage", `Creating certificate directory: ${certDir}`, options23?.verbose);
|
|
254304
254472
|
fs22.mkdirSync(certDir, { recursive: true });
|
|
254305
254473
|
}
|
|
254306
|
-
|
|
254474
|
+
debugLog2("storage", "Writing certificate file", options23?.verbose);
|
|
254307
254475
|
fs22.writeFileSync(certPath, cert.certificate);
|
|
254308
254476
|
const certKeyDir = path32.dirname(certKeyPath);
|
|
254309
254477
|
if (!fs22.existsSync(certKeyDir)) {
|
|
254310
|
-
|
|
254478
|
+
debugLog2("storage", `Creating private key directory: ${certKeyDir}`, options23?.verbose);
|
|
254311
254479
|
fs22.mkdirSync(certKeyDir, { recursive: true });
|
|
254312
254480
|
}
|
|
254313
|
-
|
|
254481
|
+
debugLog2("storage", "Writing private key file", options23?.verbose);
|
|
254314
254482
|
fs22.writeFileSync(certKeyPath, cert.privateKey);
|
|
254315
|
-
|
|
254483
|
+
debugLog2("storage", "Certificate and private key stored successfully", options23?.verbose);
|
|
254316
254484
|
return certPath;
|
|
254317
254485
|
}
|
|
254318
254486
|
function storeCACert(caCert, options23) {
|
|
254319
|
-
|
|
254487
|
+
debugLog2("storage", "Storing CA certificate", options23?.verbose);
|
|
254320
254488
|
const caCertPath = options23?.caCertPath || config42.caCertPath;
|
|
254321
|
-
|
|
254489
|
+
debugLog2("storage", `CA certificate path: ${caCertPath}`, options23?.verbose);
|
|
254322
254490
|
const caCertDir = path32.dirname(caCertPath);
|
|
254323
254491
|
if (!fs22.existsSync(caCertDir)) {
|
|
254324
|
-
|
|
254492
|
+
debugLog2("storage", `Creating CA certificate directory: ${caCertDir}`, options23?.verbose);
|
|
254325
254493
|
fs22.mkdirSync(caCertDir, { recursive: true });
|
|
254326
254494
|
}
|
|
254327
|
-
|
|
254495
|
+
debugLog2("storage", "Writing CA certificate file", options23?.verbose);
|
|
254328
254496
|
fs22.writeFileSync(caCertPath, caCert);
|
|
254329
|
-
|
|
254497
|
+
debugLog2("storage", "CA certificate stored successfully", options23?.verbose);
|
|
254330
254498
|
return caCertPath;
|
|
254331
254499
|
}
|
|
254332
254500
|
var export_tls = import_node_forge2.tls;
|
|
@@ -254334,28 +254502,37 @@ var export_pki = import_node_forge2.pki;
|
|
|
254334
254502
|
var export_forge = import_node_forge2.default;
|
|
254335
254503
|
|
|
254336
254504
|
// src/https.ts
|
|
254337
|
-
|
|
254338
|
-
domain
|
|
254339
|
-
|
|
254340
|
-
|
|
254341
|
-
|
|
254342
|
-
|
|
254343
|
-
|
|
254344
|
-
|
|
254345
|
-
|
|
254346
|
-
|
|
254347
|
-
|
|
254348
|
-
|
|
254349
|
-
|
|
254350
|
-
|
|
254351
|
-
|
|
254352
|
-
|
|
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
|
+
}
|
|
254353
254530
|
async function generateCertificate(domain) {
|
|
254354
254531
|
if (config6.https === true)
|
|
254355
|
-
config6.https =
|
|
254532
|
+
config6.https = httpsConfig();
|
|
254356
254533
|
else if (config6.https === false)
|
|
254357
254534
|
return;
|
|
254358
|
-
domain = domain ?? config6.https.
|
|
254535
|
+
domain = domain ?? config6.https.domain;
|
|
254359
254536
|
log.info(`Generating a self-signed SSL certificate for: ${domain}`);
|
|
254360
254537
|
const caCert = await createRootCA(config6.https);
|
|
254361
254538
|
const hostCert = await generateCert({
|
|
@@ -254378,90 +254555,89 @@ async function generateCertificate(domain) {
|
|
|
254378
254555
|
log.success("Certificate generated");
|
|
254379
254556
|
}
|
|
254380
254557
|
|
|
254381
|
-
// src/utils.ts
|
|
254382
|
-
function debugLog2(category, message, verbose) {
|
|
254383
|
-
if (verbose || config6.verbose) {
|
|
254384
|
-
console.debug(`[rpx:${category}] ${message}`);
|
|
254385
|
-
}
|
|
254386
|
-
}
|
|
254387
|
-
|
|
254388
254558
|
// src/start.ts
|
|
254389
254559
|
var activeServers = new Set;
|
|
254390
|
-
function cleanup() {
|
|
254391
|
-
|
|
254560
|
+
async function cleanup() {
|
|
254561
|
+
debugLog("cleanup", "Starting cleanup process", config6.verbose);
|
|
254392
254562
|
console.log(`\n`);
|
|
254393
254563
|
log.info("Shutting down proxy servers...");
|
|
254394
|
-
const
|
|
254564
|
+
const cleanupPromises = [];
|
|
254565
|
+
const serverClosePromises = Array.from(activeServers).map((server) => new Promise((resolve5) => {
|
|
254395
254566
|
server.close(() => {
|
|
254396
|
-
|
|
254567
|
+
debugLog("cleanup", "Server closed successfully", config6.verbose);
|
|
254397
254568
|
resolve5();
|
|
254398
254569
|
});
|
|
254399
254570
|
}));
|
|
254400
|
-
|
|
254401
|
-
|
|
254402
|
-
|
|
254403
|
-
|
|
254404
|
-
|
|
254405
|
-
|
|
254406
|
-
|
|
254407
|
-
|
|
254408
|
-
|
|
254409
|
-
}
|
|
254410
|
-
|
|
254411
|
-
|
|
254412
|
-
|
|
254413
|
-
|
|
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);
|
|
254414
254606
|
log.error("Uncaught exception:", err4);
|
|
254415
254607
|
cleanup();
|
|
254416
254608
|
});
|
|
254417
254609
|
async function loadSSLConfig(options4) {
|
|
254418
|
-
|
|
254419
|
-
if (options4.https === true)
|
|
254420
|
-
options4.https =
|
|
254421
|
-
|
|
254422
|
-
hostCertCN: "stacks.localhost",
|
|
254423
|
-
caCertPath: path9.join(os8.homedir(), ".stacks", "ssl", `stacks.localhost.ca.crt`),
|
|
254424
|
-
certPath: path9.join(os8.homedir(), ".stacks", "ssl", `stacks.localhost.crt`),
|
|
254425
|
-
keyPath: path9.join(os8.homedir(), ".stacks", "ssl", `stacks.localhost.crt.key`),
|
|
254426
|
-
altNameIPs: ["127.0.0.1"],
|
|
254427
|
-
altNameURIs: ["localhost"],
|
|
254428
|
-
organizationName: "stacksjs.org",
|
|
254429
|
-
countryName: "US",
|
|
254430
|
-
stateName: "California",
|
|
254431
|
-
localityName: "Playa Vista",
|
|
254432
|
-
commonName: "stacks.localhost",
|
|
254433
|
-
validityDays: 180,
|
|
254434
|
-
verbose: false
|
|
254435
|
-
};
|
|
254436
|
-
} else if (options4.https === false) {
|
|
254610
|
+
debugLog("ssl", "Loading SSL configuration", options4.verbose);
|
|
254611
|
+
if (options4.https === true)
|
|
254612
|
+
options4.https = httpsConfig();
|
|
254613
|
+
else if (options4.https === false)
|
|
254437
254614
|
return null;
|
|
254438
|
-
}
|
|
254439
254615
|
if (!options4.https?.keyPath && !options4.https?.certPath) {
|
|
254440
|
-
|
|
254616
|
+
debugLog("ssl", "No SSL configuration provided", options4.verbose);
|
|
254441
254617
|
return null;
|
|
254442
254618
|
}
|
|
254443
254619
|
if (options4.https?.keyPath && !options4.https?.certPath || !options4.https?.keyPath && options4.https?.certPath) {
|
|
254444
254620
|
const missing = !options4.https?.keyPath ? "keyPath" : "certPath";
|
|
254445
|
-
|
|
254621
|
+
debugLog("ssl", `Invalid SSL configuration - missing ${missing}`, options4.verbose);
|
|
254446
254622
|
throw new Error(`SSL Configuration requires both keyPath and certPath. Missing: ${missing}`);
|
|
254447
254623
|
}
|
|
254448
254624
|
try {
|
|
254449
254625
|
if (!options4.https?.keyPath || !options4.https?.certPath)
|
|
254450
254626
|
return null;
|
|
254451
254627
|
try {
|
|
254452
|
-
|
|
254453
|
-
const key = await
|
|
254454
|
-
const cert = await
|
|
254455
|
-
|
|
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);
|
|
254456
254632
|
return { key, cert };
|
|
254457
254633
|
} catch (error) {
|
|
254458
254634
|
if (error.code === "ENOENT") {
|
|
254459
|
-
|
|
254460
|
-
await generateCertificate();
|
|
254461
|
-
|
|
254462
|
-
const key = await
|
|
254463
|
-
const cert = await
|
|
254464
|
-
|
|
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);
|
|
254465
254641
|
return { key, cert };
|
|
254466
254642
|
}
|
|
254467
254643
|
throw error;
|
|
@@ -254469,22 +254645,22 @@ async function loadSSLConfig(options4) {
|
|
|
254469
254645
|
} catch (err4) {
|
|
254470
254646
|
const error = err4;
|
|
254471
254647
|
const detail = error.code === "ENOENT" ? `File not found: ${error.path}` : error.message;
|
|
254472
|
-
|
|
254648
|
+
debugLog("ssl", `SSL configuration error: ${error}`, options4.verbose);
|
|
254473
254649
|
throw new Error(`SSL Configuration Error: ${detail}`);
|
|
254474
254650
|
}
|
|
254475
254651
|
}
|
|
254476
254652
|
function isPortInUse(port, hostname, verbose) {
|
|
254477
|
-
|
|
254653
|
+
debugLog("port", `Checking if port ${port} is in use on ${hostname}`, verbose);
|
|
254478
254654
|
return new Promise((resolve5) => {
|
|
254479
254655
|
const server = net.createServer();
|
|
254480
254656
|
server.once("error", (err4) => {
|
|
254481
254657
|
if (err4.code === "EADDRINUSE") {
|
|
254482
|
-
|
|
254658
|
+
debugLog("port", `Port ${port} is in use`, verbose);
|
|
254483
254659
|
resolve5(true);
|
|
254484
254660
|
}
|
|
254485
254661
|
});
|
|
254486
254662
|
server.once("listening", () => {
|
|
254487
|
-
|
|
254663
|
+
debugLog("port", `Port ${port} is available`, verbose);
|
|
254488
254664
|
server.close();
|
|
254489
254665
|
resolve5(false);
|
|
254490
254666
|
});
|
|
@@ -254492,17 +254668,17 @@ function isPortInUse(port, hostname, verbose) {
|
|
|
254492
254668
|
});
|
|
254493
254669
|
}
|
|
254494
254670
|
async function findAvailablePort(startPort, hostname, verbose) {
|
|
254495
|
-
|
|
254671
|
+
debugLog("port", `Finding available port starting from ${startPort}`, verbose);
|
|
254496
254672
|
let port = startPort;
|
|
254497
254673
|
while (await isPortInUse(port, hostname, verbose)) {
|
|
254498
|
-
|
|
254674
|
+
debugLog("port", `Port ${port} is in use, trying ${port + 1}`, verbose);
|
|
254499
254675
|
port++;
|
|
254500
254676
|
}
|
|
254501
|
-
|
|
254677
|
+
debugLog("port", `Found available port: ${port}`, verbose);
|
|
254502
254678
|
return port;
|
|
254503
254679
|
}
|
|
254504
254680
|
async function testConnection(hostname, port, verbose) {
|
|
254505
|
-
|
|
254681
|
+
debugLog("connection", `Testing connection to ${hostname}:${port}`, verbose);
|
|
254506
254682
|
return new Promise((resolve5, reject) => {
|
|
254507
254683
|
const socket = net.connect({
|
|
254508
254684
|
host: hostname,
|
|
@@ -254510,58 +254686,89 @@ async function testConnection(hostname, port, verbose) {
|
|
|
254510
254686
|
timeout: 5000
|
|
254511
254687
|
});
|
|
254512
254688
|
socket.once("connect", () => {
|
|
254513
|
-
|
|
254689
|
+
debugLog("connection", `Successfully connected to ${hostname}:${port}`, verbose);
|
|
254514
254690
|
socket.end();
|
|
254515
254691
|
resolve5();
|
|
254516
254692
|
});
|
|
254517
254693
|
socket.once("timeout", () => {
|
|
254518
|
-
|
|
254694
|
+
debugLog("connection", `Connection to ${hostname}:${port} timed out`, verbose);
|
|
254519
254695
|
socket.destroy();
|
|
254520
254696
|
reject(new Error(`Connection to ${hostname}:${port} timed out`));
|
|
254521
254697
|
});
|
|
254522
254698
|
socket.once("error", (err4) => {
|
|
254523
|
-
|
|
254699
|
+
debugLog("connection", `Failed to connect to ${hostname}:${port}: ${err4}`, verbose);
|
|
254524
254700
|
socket.destroy();
|
|
254525
254701
|
reject(new Error(`Failed to connect to ${hostname}:${port}: ${err4.message}`));
|
|
254526
254702
|
});
|
|
254527
254703
|
});
|
|
254528
254704
|
}
|
|
254529
254705
|
async function startServer(options4) {
|
|
254530
|
-
|
|
254706
|
+
debugLog("server", `Starting server with options: ${JSON.stringify(options4)}`, options4?.verbose);
|
|
254531
254707
|
if (!options4)
|
|
254532
254708
|
options4 = config6;
|
|
254533
254709
|
if (!options4.from)
|
|
254534
254710
|
options4.from = config6.from;
|
|
254535
254711
|
if (!options4.to)
|
|
254536
254712
|
options4.to = config6.to;
|
|
254713
|
+
const fromUrl = new URL(options4.from.startsWith("http") ? options4.from : `http://${options4.from}`);
|
|
254714
|
+
const toUrl = new URL(options4.to.startsWith("http") ? options4.to : `http://${options4.to}`);
|
|
254715
|
+
const fromPort = Number.parseInt(fromUrl.port) || (fromUrl.protocol.includes("https:") ? 443 : 80);
|
|
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
|
+
}
|
|
254537
254747
|
if (config6.https) {
|
|
254538
254748
|
if (config6.https === true)
|
|
254539
|
-
config6.https =
|
|
254540
|
-
const domain =
|
|
254749
|
+
config6.https = httpsConfig();
|
|
254750
|
+
const domain = toUrl.hostname;
|
|
254541
254751
|
if (typeof options4.https !== "boolean" && options4.https) {
|
|
254542
|
-
options4.https.keyPath = config6.https.keyPath ||
|
|
254543
|
-
options4.https.certPath = config6.https.certPath ||
|
|
254544
|
-
|
|
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);
|
|
254545
254755
|
}
|
|
254546
254756
|
}
|
|
254547
|
-
|
|
254548
|
-
const toUrl = new URL(options4.to.startsWith("http") ? options4.to : `http://${options4.to}`);
|
|
254549
|
-
const fromPort = Number.parseInt(fromUrl.port) || (fromUrl.protocol.includes("https:") ? 443 : 80);
|
|
254550
|
-
debugLog2("server", `Parsed URLs - from: ${fromUrl}, to: ${toUrl}`, options4.verbose);
|
|
254757
|
+
debugLog("server", `Parsed URLs - from: ${fromUrl}, to: ${toUrl}`, options4.verbose);
|
|
254551
254758
|
try {
|
|
254552
254759
|
await testConnection(fromUrl.hostname, fromPort, options4.verbose);
|
|
254553
254760
|
} catch (err4) {
|
|
254554
|
-
|
|
254761
|
+
debugLog("server", `Connection test failed: ${err4}`, options4.verbose);
|
|
254555
254762
|
log.error(err4.message);
|
|
254556
|
-
|
|
254763
|
+
process24.exit(1);
|
|
254557
254764
|
}
|
|
254558
254765
|
let sslConfig = null;
|
|
254559
254766
|
if (config6.https) {
|
|
254560
254767
|
try {
|
|
254561
254768
|
sslConfig = await loadSSLConfig(options4);
|
|
254562
254769
|
} catch (err4) {
|
|
254563
|
-
|
|
254564
|
-
await generateCertificate();
|
|
254770
|
+
debugLog("server", `SSL config failed, attempting to generate certificates: ${err4}`, options4.verbose);
|
|
254771
|
+
await generateCertificate(options4.to);
|
|
254565
254772
|
sslConfig = await loadSSLConfig(options4);
|
|
254566
254773
|
}
|
|
254567
254774
|
}
|
|
@@ -254578,9 +254785,9 @@ async function startServer(options4) {
|
|
|
254578
254785
|
});
|
|
254579
254786
|
}
|
|
254580
254787
|
async function createProxyServer(from, to, fromPort, listenPort, hostname, sourceUrl, ssl, verbose) {
|
|
254581
|
-
|
|
254788
|
+
debugLog("proxy", `Creating proxy server ${from} -> ${to}`, verbose);
|
|
254582
254789
|
const requestHandler = (req, res) => {
|
|
254583
|
-
|
|
254790
|
+
debugLog("request", `Incoming request: ${req.method} ${req.url}`, verbose);
|
|
254584
254791
|
const proxyOptions = {
|
|
254585
254792
|
hostname: sourceUrl.hostname,
|
|
254586
254793
|
port: fromPort,
|
|
@@ -254591,9 +254798,9 @@ async function createProxyServer(from, to, fromPort, listenPort, hostname, sourc
|
|
|
254591
254798
|
host: sourceUrl.host
|
|
254592
254799
|
}
|
|
254593
254800
|
};
|
|
254594
|
-
|
|
254801
|
+
debugLog("request", `Proxy request options: ${JSON.stringify(proxyOptions)}`, verbose);
|
|
254595
254802
|
const proxyReq = http.request(proxyOptions, (proxyRes) => {
|
|
254596
|
-
|
|
254803
|
+
debugLog("response", `Proxy response received with status ${proxyRes.statusCode}`, verbose);
|
|
254597
254804
|
const headers = {
|
|
254598
254805
|
...proxyRes.headers,
|
|
254599
254806
|
"Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
|
|
@@ -254603,7 +254810,7 @@ async function createProxyServer(from, to, fromPort, listenPort, hostname, sourc
|
|
|
254603
254810
|
proxyRes.pipe(res);
|
|
254604
254811
|
});
|
|
254605
254812
|
proxyReq.on("error", (err4) => {
|
|
254606
|
-
|
|
254813
|
+
debugLog("request", `Proxy request failed: ${err4}`, verbose);
|
|
254607
254814
|
log.error("Proxy request failed:", err4);
|
|
254608
254815
|
res.writeHead(502);
|
|
254609
254816
|
res.end(`Proxy Error: ${err4.message}`);
|
|
@@ -254630,11 +254837,11 @@ async function createProxyServer(from, to, fromPort, listenPort, hostname, sourc
|
|
|
254630
254837
|
allowHTTP1: true,
|
|
254631
254838
|
ALPNProtocols: ["h2", "http/1.1"]
|
|
254632
254839
|
} : undefined;
|
|
254633
|
-
|
|
254840
|
+
debugLog("server", `Creating server with SSL config: ${!!ssl}`, verbose);
|
|
254634
254841
|
const server = ssl && serverOptions ? https.createServer(serverOptions, requestHandler) : http.createServer(requestHandler);
|
|
254635
254842
|
if (ssl) {
|
|
254636
254843
|
server.on("secureConnection", (tlsSocket) => {
|
|
254637
|
-
|
|
254844
|
+
debugLog("tls", `TLS Connection established: ${JSON.stringify({
|
|
254638
254845
|
protocol: tlsSocket.getProtocol?.(),
|
|
254639
254846
|
cipher: tlsSocket.getCipher?.(),
|
|
254640
254847
|
authorized: tlsSocket.authorized,
|
|
@@ -254645,7 +254852,7 @@ async function createProxyServer(from, to, fromPort, listenPort, hostname, sourc
|
|
|
254645
254852
|
activeServers.add(server);
|
|
254646
254853
|
return new Promise((resolve5, reject) => {
|
|
254647
254854
|
server.listen(listenPort, hostname, () => {
|
|
254648
|
-
|
|
254855
|
+
debugLog("server", `Server listening on port ${listenPort}`, verbose);
|
|
254649
254856
|
console.log("");
|
|
254650
254857
|
console.log(` ${green(bold("reverse-proxy"))} ${green(`v${version}`)}`);
|
|
254651
254858
|
console.log("");
|
|
@@ -254662,13 +254869,13 @@ async function createProxyServer(from, to, fromPort, listenPort, hostname, sourc
|
|
|
254662
254869
|
resolve5();
|
|
254663
254870
|
});
|
|
254664
254871
|
server.on("error", (err4) => {
|
|
254665
|
-
|
|
254872
|
+
debugLog("server", `Server error: ${err4}`, verbose);
|
|
254666
254873
|
reject(err4);
|
|
254667
254874
|
});
|
|
254668
254875
|
});
|
|
254669
254876
|
}
|
|
254670
254877
|
async function setupReverseProxy(options4) {
|
|
254671
|
-
|
|
254878
|
+
debugLog("setup", `Setting up reverse proxy: ${JSON.stringify(options4)}`, options4.verbose);
|
|
254672
254879
|
const { from, to, fromPort, sourceUrl, ssl, verbose } = options4;
|
|
254673
254880
|
const httpPort = 80;
|
|
254674
254881
|
const httpsPort = 443;
|
|
@@ -254677,72 +254884,72 @@ async function setupReverseProxy(options4) {
|
|
|
254677
254884
|
if (ssl) {
|
|
254678
254885
|
const isHttpPortBusy = await isPortInUse(httpPort, hostname, verbose);
|
|
254679
254886
|
if (!isHttpPortBusy) {
|
|
254680
|
-
|
|
254887
|
+
debugLog("setup", "Starting HTTP redirect server", verbose);
|
|
254681
254888
|
startHttpRedirectServer(verbose);
|
|
254682
254889
|
} else {
|
|
254683
|
-
|
|
254890
|
+
debugLog("setup", "Port 80 is in use, skipping HTTP redirect", verbose);
|
|
254684
254891
|
log.warn("Port 80 is in use, HTTP to HTTPS redirect will not be available");
|
|
254685
254892
|
}
|
|
254686
254893
|
}
|
|
254687
254894
|
const targetPort = ssl ? httpsPort : httpPort;
|
|
254688
254895
|
const isTargetPortBusy = await isPortInUse(targetPort, hostname, verbose);
|
|
254689
254896
|
if (isTargetPortBusy) {
|
|
254690
|
-
|
|
254897
|
+
debugLog("setup", `Port ${targetPort} is busy, finding alternative`, verbose);
|
|
254691
254898
|
const availablePort = await findAvailablePort(ssl ? 8443 : 8080, hostname, verbose);
|
|
254692
254899
|
log.warn(`Port ${targetPort} is in use. Using port ${availablePort} instead.`);
|
|
254693
254900
|
log.info(`You can use 'sudo lsof -i :${targetPort}' (Unix) or 'netstat -ano | findstr :${targetPort}' (Windows) to check what's using the port.`);
|
|
254694
254901
|
await createProxyServer(from, to, fromPort, availablePort, hostname, sourceUrl, ssl, verbose);
|
|
254695
254902
|
} else {
|
|
254696
|
-
|
|
254903
|
+
debugLog("setup", `Using default port ${targetPort}`, verbose);
|
|
254697
254904
|
await createProxyServer(from, to, fromPort, targetPort, hostname, sourceUrl, ssl, verbose);
|
|
254698
254905
|
}
|
|
254699
254906
|
} catch (err4) {
|
|
254700
|
-
|
|
254907
|
+
debugLog("setup", `Setup failed: ${err4}`, verbose);
|
|
254701
254908
|
log.error(`Failed to setup reverse proxy: ${err4.message}`);
|
|
254702
254909
|
cleanup();
|
|
254703
254910
|
}
|
|
254704
254911
|
}
|
|
254705
254912
|
function startHttpRedirectServer(verbose) {
|
|
254706
|
-
|
|
254913
|
+
debugLog("redirect", "Starting HTTP redirect server", verbose);
|
|
254707
254914
|
const server = http.createServer((req, res) => {
|
|
254708
254915
|
const host = req.headers.host || "";
|
|
254709
|
-
|
|
254916
|
+
debugLog("redirect", `Redirecting request from ${host}${req.url} to HTTPS`, verbose);
|
|
254710
254917
|
res.writeHead(301, {
|
|
254711
254918
|
Location: `https://${host}${req.url}`
|
|
254712
254919
|
});
|
|
254713
254920
|
res.end();
|
|
254714
254921
|
}).listen(80);
|
|
254715
254922
|
activeServers.add(server);
|
|
254716
|
-
|
|
254923
|
+
debugLog("redirect", "HTTP redirect server started", verbose);
|
|
254717
254924
|
}
|
|
254718
254925
|
function startProxy(options4) {
|
|
254719
254926
|
const finalOptions = {
|
|
254720
254927
|
...config6,
|
|
254721
254928
|
...options4
|
|
254722
254929
|
};
|
|
254723
|
-
|
|
254930
|
+
debugLog("proxy", `Starting proxy with options: ${JSON.stringify({
|
|
254724
254931
|
from: finalOptions.from,
|
|
254725
254932
|
to: finalOptions.to,
|
|
254726
254933
|
https: finalOptions.https
|
|
254727
254934
|
})}`, finalOptions.verbose);
|
|
254728
254935
|
startServer(finalOptions).catch((err4) => {
|
|
254729
|
-
|
|
254936
|
+
debugLog("proxy", `Failed to start proxy: ${err4}`, finalOptions.verbose);
|
|
254730
254937
|
log.error(`Failed to start proxy: ${err4.message}`);
|
|
254731
254938
|
cleanup();
|
|
254732
254939
|
});
|
|
254733
254940
|
}
|
|
254734
254941
|
function startProxies(options4) {
|
|
254735
254942
|
if (Array.isArray(options4)) {
|
|
254736
|
-
|
|
254943
|
+
debugLog("proxies", `Starting multiple proxies: ${options4.length}`, options4[0]?.verbose);
|
|
254737
254944
|
Promise.all(options4.map((option) => startServer(option))).catch((err4) => {
|
|
254738
|
-
|
|
254945
|
+
debugLog("proxies", `Failed to start proxies: ${err4}`, options4[0]?.verbose);
|
|
254739
254946
|
log.error("Failed to start proxies:", err4);
|
|
254740
254947
|
cleanup();
|
|
254741
254948
|
});
|
|
254742
254949
|
} else if (options4) {
|
|
254743
|
-
|
|
254950
|
+
debugLog("proxies", "Starting single proxy", options4.verbose);
|
|
254744
254951
|
startServer(options4).catch((err4) => {
|
|
254745
|
-
|
|
254952
|
+
debugLog("proxies", `Failed to start proxy: ${err4}`, options4.verbose);
|
|
254746
254953
|
log.error("Failed to start proxy:", err4);
|
|
254747
254954
|
cleanup();
|
|
254748
254955
|
});
|
|
@@ -254762,12 +254969,12 @@ cli.command("start", "Start the Reverse Proxy Server").option("--from <from>", "
|
|
|
254762
254969
|
});
|
|
254763
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 () => {
|
|
254764
254971
|
log.info("Ensuring /etc/hosts file covers the proxy domain/s...");
|
|
254765
|
-
const
|
|
254972
|
+
const hostsFilePath2 = os10.platform() === "win32" ? "C:\\Windows\\System32\\drivers\\etc\\hosts" : "/etc/hosts";
|
|
254766
254973
|
if (config6 && typeof config6 === "object") {
|
|
254767
254974
|
const entriesToAdd = Object.entries(config6).map(([from, to]) => `127.0.0.1 ${to} # reverse-proxy mapping for ${from}`);
|
|
254768
254975
|
entriesToAdd.push("127.0.0.1 localhost # essential localhost mapping");
|
|
254769
254976
|
try {
|
|
254770
|
-
let currentHostsContent = export_readFileSync(
|
|
254977
|
+
let currentHostsContent = export_readFileSync(hostsFilePath2, "utf8");
|
|
254771
254978
|
let updated = false;
|
|
254772
254979
|
for (const entry of entriesToAdd) {
|
|
254773
254980
|
const [ip, host] = entry.split(" ", 2);
|
|
@@ -254780,7 +254987,7 @@ cli.command("update:etc-hosts", "Update the /etc/hosts file with the proxy domai
|
|
|
254780
254987
|
}
|
|
254781
254988
|
}
|
|
254782
254989
|
if (updated) {
|
|
254783
|
-
export_writeFileSync(
|
|
254990
|
+
export_writeFileSync(hostsFilePath2, currentHostsContent, "utf8");
|
|
254784
254991
|
log.success("Hosts file updated with latest proxy domains.");
|
|
254785
254992
|
} else {
|
|
254786
254993
|
log.info("No new entries were added to the hosts file.");
|