pinggy 0.4.6 → 0.4.8
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 +92 -4
- package/dist/{chunk-STEISST3.js → chunk-YFTL44B3.js} +25 -5
- package/dist/index.cjs +735 -113
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -2
- package/dist/{main-XKFFUSKJ.js → main-4WTJG54V.js} +667 -100
- package/dist/workers/file_serve_worker.cjs +14 -2
- package/dist/workers/file_serve_worker.js +14 -2
- package/package.json +2 -2
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
RemoteManagementUnauthorizedError,
|
|
4
4
|
TunnelManager,
|
|
5
5
|
TunnelOperations,
|
|
6
|
+
buildRemoteManagementWsUrl,
|
|
6
7
|
closeRemoteManagement,
|
|
7
8
|
getRandomId,
|
|
8
9
|
getRemoteManagementState,
|
|
@@ -10,8 +11,9 @@ import {
|
|
|
10
11
|
initiateRemoteManagement,
|
|
11
12
|
isValidPort,
|
|
12
13
|
parseRemoteManagement,
|
|
13
|
-
printer_default
|
|
14
|
-
|
|
14
|
+
printer_default,
|
|
15
|
+
startRemoteManagement
|
|
16
|
+
} from "./chunk-YFTL44B3.js";
|
|
15
17
|
import {
|
|
16
18
|
configureLogger,
|
|
17
19
|
enablePackageLogging,
|
|
@@ -44,10 +46,14 @@ var cliOptions = {
|
|
|
44
46
|
v: { type: "boolean", description: "Print logs to stdout for Cli. Overrides PINGGY_LOG_STDOUT environment variable" },
|
|
45
47
|
vv: { type: "boolean", description: "Enable detailed logging for the Node.js SDK and Libpinggy, including both info and debug level logs." },
|
|
46
48
|
vvv: { type: "boolean", description: "Enable all logs from Cli, SDK and internal components." },
|
|
47
|
-
autoreconnect: { type: "
|
|
48
|
-
// Save and load config
|
|
49
|
+
"no-autoreconnect": { type: "boolean", short: "a", description: "Disable auto reconnection on failure (enabled by default)." },
|
|
50
|
+
// Save and load config (legacy file-based)
|
|
49
51
|
saveconf: { type: "string", description: "Create the configuration file based on the options provided here" },
|
|
50
52
|
conf: { type: "string", description: "Use the configuration file as base. Other options will be used to override this file" },
|
|
53
|
+
// Used by `pinggy config save` and `buildAndStartTunnel` save flow
|
|
54
|
+
save: { type: "boolean", short: "s", description: "Save the tunnel config (use with config save or -l)", hidden: true },
|
|
55
|
+
name: { type: "string", description: "Name for the tunnel config", hidden: true },
|
|
56
|
+
auto: { type: "boolean", description: "Mark tunnel config for auto-start", hidden: true },
|
|
51
57
|
// File server
|
|
52
58
|
serve: { type: "string", description: "Start a webserver to serve files from the specified path. Eg --serve /path/to/files" },
|
|
53
59
|
// Remote Control
|
|
@@ -95,11 +101,92 @@ function printHelpMessage() {
|
|
|
95
101
|
console.log(" pinggy -R0:localhost:3000 # Basic HTTP tunnel");
|
|
96
102
|
console.log(" pinggy --type tcp -R0:localhost:22 # TCP tunnel for SSH");
|
|
97
103
|
console.log(" pinggy -R0:localhost:8080 -L4300:localhost:4300 # HTTP tunnel with debugger");
|
|
98
|
-
console.log(" pinggy tcp@ap.example.com -R0:localhost:22 # TCP tunnel to region
|
|
104
|
+
console.log(" pinggy tcp@ap.example.com -R0:localhost:22 # TCP tunnel to region");
|
|
105
|
+
console.log("\nConfig Management:");
|
|
106
|
+
console.log(" pinggy config list # List saved configs");
|
|
107
|
+
console.log(" pinggy config show my-tunnel # Show config details");
|
|
108
|
+
console.log(" pinggy config save my-tunnel -l 3000 token@pro.pinggy.io # Save config");
|
|
109
|
+
console.log(" pinggy config save my-tunnel --auto -l 3000 # Save with auto-start");
|
|
110
|
+
console.log(" pinggy config update my-tunnel -l 4000 # Update saved config");
|
|
111
|
+
console.log(" pinggy config delete my-tunnel # Delete saved config");
|
|
112
|
+
console.log(" pinggy config auto my-tunnel # Enable auto-start");
|
|
113
|
+
console.log(" pinggy config noauto my-tunnel # Disable auto-start");
|
|
114
|
+
console.log("\nStart Saved Tunnels:");
|
|
115
|
+
console.log(" pinggy start my-tunnel # Start saved tunnel");
|
|
116
|
+
console.log(" pinggy start my-tunnel -l 4000 # Start with runtime overrides");
|
|
117
|
+
console.log(" pinggy start tunnela tunnelb # Start multiple tunnels");
|
|
118
|
+
console.log(" pinggy start --all # Start all auto-start tunnels\n");
|
|
99
119
|
}
|
|
100
120
|
|
|
121
|
+
// src/utils/parseArgs.ts
|
|
122
|
+
import { parseArgs } from "util";
|
|
123
|
+
import * as os from "os";
|
|
124
|
+
function isAttachedReverseOrLocalFlag(arg) {
|
|
125
|
+
return /^-[RL].+/.test(arg);
|
|
126
|
+
}
|
|
127
|
+
function shouldMergeReverseOrLocalFragment(current, next) {
|
|
128
|
+
if (next.startsWith("-")) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
if (next.startsWith(".")) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
const body = current.slice(2);
|
|
135
|
+
if (body.endsWith(":")) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
if (body.includes("//") && !body.includes(":")) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
function preprocessWindowsArgs(args) {
|
|
144
|
+
if (os.platform() !== "win32") {
|
|
145
|
+
return args;
|
|
146
|
+
}
|
|
147
|
+
;
|
|
148
|
+
const out = [];
|
|
149
|
+
let i = 0;
|
|
150
|
+
while (i < args.length) {
|
|
151
|
+
const arg = args[i];
|
|
152
|
+
if (isAttachedReverseOrLocalFlag(arg)) {
|
|
153
|
+
let merged = arg;
|
|
154
|
+
while (i + 1 < args.length && shouldMergeReverseOrLocalFragment(merged, args[i + 1])) {
|
|
155
|
+
merged += args[i + 1];
|
|
156
|
+
i++;
|
|
157
|
+
}
|
|
158
|
+
out.push(merged);
|
|
159
|
+
i++;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
out.push(arg);
|
|
163
|
+
i++;
|
|
164
|
+
}
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
function parseCliArgs(options, overrideArgs) {
|
|
168
|
+
const rawArgs = overrideArgs ?? process.argv.slice(2);
|
|
169
|
+
const processedArgs = preprocessWindowsArgs(rawArgs);
|
|
170
|
+
const parsed = parseArgs({
|
|
171
|
+
args: processedArgs,
|
|
172
|
+
options,
|
|
173
|
+
allowPositionals: true
|
|
174
|
+
});
|
|
175
|
+
const hasAnyArgs = parsed.positionals.length > 0 || Object.values(parsed.values).some((v) => v !== void 0 && v !== false);
|
|
176
|
+
return {
|
|
177
|
+
...parsed,
|
|
178
|
+
hasAnyArgs
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/main.ts
|
|
183
|
+
import { fileURLToPath } from "url";
|
|
184
|
+
import { argv } from "process";
|
|
185
|
+
import { realpathSync } from "fs";
|
|
186
|
+
|
|
101
187
|
// src/cli/defaults.ts
|
|
102
188
|
var defaultOptions = {
|
|
189
|
+
version: "1.0",
|
|
103
190
|
token: void 0,
|
|
104
191
|
// No default token
|
|
105
192
|
serverAddress: "a.pinggy.io",
|
|
@@ -606,7 +693,7 @@ function parseToken(finalConfig, explicitToken) {
|
|
|
606
693
|
finalConfig.token = explicitToken;
|
|
607
694
|
}
|
|
608
695
|
}
|
|
609
|
-
function
|
|
696
|
+
function parseArgs2(finalConfig, remainingPositionals) {
|
|
610
697
|
let localserverTls = "";
|
|
611
698
|
localserverTls = parseExtendedOptions(remainingPositionals, finalConfig, localserverTls);
|
|
612
699
|
if (localserverTls.length > 0 && finalConfig.forwarding) {
|
|
@@ -617,10 +704,10 @@ function parseArgs(finalConfig, remainingPositionals) {
|
|
|
617
704
|
}
|
|
618
705
|
function storeJson(config, saveconf) {
|
|
619
706
|
if (saveconf) {
|
|
620
|
-
const
|
|
707
|
+
const path4 = saveconf;
|
|
621
708
|
try {
|
|
622
|
-
fs.writeFileSync(
|
|
623
|
-
logger.info(`Configuration saved to ${
|
|
709
|
+
fs.writeFileSync(path4, JSON.stringify(config, null, 2), { encoding: "utf-8", flag: "w" });
|
|
710
|
+
logger.info(`Configuration saved to ${path4}`);
|
|
624
711
|
} catch (err) {
|
|
625
712
|
const msg = err instanceof Error ? err.message : String(err);
|
|
626
713
|
logger.error("Error loading configuration:", msg);
|
|
@@ -657,20 +744,12 @@ function parseServe(finalConfig, values) {
|
|
|
657
744
|
return null;
|
|
658
745
|
}
|
|
659
746
|
function parseAutoReconnect(finalConfig, values) {
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
const trimmed = autoReconnectValue.trim().toLowerCase();
|
|
663
|
-
if (trimmed === "true" || trimmed === "") {
|
|
664
|
-
finalConfig.autoReconnect = true;
|
|
665
|
-
} else if (trimmed === "false") {
|
|
666
|
-
finalConfig.autoReconnect = false;
|
|
667
|
-
} else {
|
|
668
|
-
return new Error(`Invalid autoreconnect value: ${autoReconnectValue}. Use true or false.`);
|
|
669
|
-
}
|
|
747
|
+
if (values["no-autoreconnect"]) {
|
|
748
|
+
finalConfig.autoReconnect = false;
|
|
670
749
|
}
|
|
671
750
|
return null;
|
|
672
751
|
}
|
|
673
|
-
async function buildFinalConfig(values, positionals) {
|
|
752
|
+
async function buildFinalConfig(values, positionals, baseConfig) {
|
|
674
753
|
let token;
|
|
675
754
|
let server;
|
|
676
755
|
let type;
|
|
@@ -678,7 +757,7 @@ async function buildFinalConfig(values, positionals) {
|
|
|
678
757
|
let qrCode = false;
|
|
679
758
|
let finalConfig = new Object();
|
|
680
759
|
let saveconf = isSaveConfOption(values);
|
|
681
|
-
const configFromFile = loadJsonConfig(values);
|
|
760
|
+
const configFromFile = baseConfig || loadJsonConfig(values);
|
|
682
761
|
const userParse = parseUsers(positionals, values.token);
|
|
683
762
|
token = userParse.token;
|
|
684
763
|
server = userParse.server;
|
|
@@ -730,72 +809,11 @@ async function buildFinalConfig(values, positionals) {
|
|
|
730
809
|
if (forceFlag || values.force) {
|
|
731
810
|
finalConfig.force = true;
|
|
732
811
|
}
|
|
733
|
-
|
|
812
|
+
parseArgs2(finalConfig, remainingPositionals);
|
|
734
813
|
storeJson(finalConfig, saveconf);
|
|
735
814
|
return finalConfig;
|
|
736
815
|
}
|
|
737
816
|
|
|
738
|
-
// src/utils/parseArgs.ts
|
|
739
|
-
import { parseArgs as parseArgs2 } from "util";
|
|
740
|
-
import * as os from "os";
|
|
741
|
-
function isAttachedReverseOrLocalFlag(arg) {
|
|
742
|
-
return /^-[RL].+/.test(arg);
|
|
743
|
-
}
|
|
744
|
-
function shouldMergeReverseOrLocalFragment(current, next) {
|
|
745
|
-
if (next.startsWith("-")) {
|
|
746
|
-
return false;
|
|
747
|
-
}
|
|
748
|
-
if (next.startsWith(".")) {
|
|
749
|
-
return true;
|
|
750
|
-
}
|
|
751
|
-
const body = current.slice(2);
|
|
752
|
-
if (body.endsWith(":")) {
|
|
753
|
-
return true;
|
|
754
|
-
}
|
|
755
|
-
if (body.includes("//") && !body.includes(":")) {
|
|
756
|
-
return true;
|
|
757
|
-
}
|
|
758
|
-
return false;
|
|
759
|
-
}
|
|
760
|
-
function preprocessWindowsArgs(args) {
|
|
761
|
-
if (os.platform() !== "win32") {
|
|
762
|
-
return args;
|
|
763
|
-
}
|
|
764
|
-
;
|
|
765
|
-
const out = [];
|
|
766
|
-
let i = 0;
|
|
767
|
-
while (i < args.length) {
|
|
768
|
-
const arg = args[i];
|
|
769
|
-
if (isAttachedReverseOrLocalFlag(arg)) {
|
|
770
|
-
let merged = arg;
|
|
771
|
-
while (i + 1 < args.length && shouldMergeReverseOrLocalFragment(merged, args[i + 1])) {
|
|
772
|
-
merged += args[i + 1];
|
|
773
|
-
i++;
|
|
774
|
-
}
|
|
775
|
-
out.push(merged);
|
|
776
|
-
i++;
|
|
777
|
-
continue;
|
|
778
|
-
}
|
|
779
|
-
out.push(arg);
|
|
780
|
-
i++;
|
|
781
|
-
}
|
|
782
|
-
return out;
|
|
783
|
-
}
|
|
784
|
-
function parseCliArgs(options) {
|
|
785
|
-
const rawArgs = process.argv.slice(2);
|
|
786
|
-
const processedArgs = preprocessWindowsArgs(rawArgs);
|
|
787
|
-
const parsed = parseArgs2({
|
|
788
|
-
args: processedArgs,
|
|
789
|
-
options,
|
|
790
|
-
allowPositionals: true
|
|
791
|
-
});
|
|
792
|
-
const hasAnyArgs = parsed.positionals.length > 0 || Object.values(parsed.values).some((v) => v !== void 0 && v !== false);
|
|
793
|
-
return {
|
|
794
|
-
...parsed,
|
|
795
|
-
hasAnyArgs
|
|
796
|
-
};
|
|
797
|
-
}
|
|
798
|
-
|
|
799
817
|
// src/utils/getFreePort.ts
|
|
800
818
|
import net from "net";
|
|
801
819
|
function getFreePort(webDebugger) {
|
|
@@ -2299,22 +2317,579 @@ async function startCli(finalConfig, manager) {
|
|
|
2299
2317
|
}
|
|
2300
2318
|
}
|
|
2301
2319
|
|
|
2320
|
+
// src/cli/configStore.ts
|
|
2321
|
+
import fs3 from "fs";
|
|
2322
|
+
import path3 from "path";
|
|
2323
|
+
|
|
2324
|
+
// src/utils/configDir.ts
|
|
2325
|
+
import os2 from "os";
|
|
2326
|
+
import path2 from "path";
|
|
2327
|
+
import fs2 from "fs";
|
|
2328
|
+
function getPinggyConfigDir() {
|
|
2329
|
+
const platform2 = os2.platform();
|
|
2330
|
+
let baseDir;
|
|
2331
|
+
if (platform2 === "win32") {
|
|
2332
|
+
baseDir = process.env.APPDATA || path2.join(os2.homedir(), "AppData", "Roaming");
|
|
2333
|
+
} else {
|
|
2334
|
+
baseDir = process.env.XDG_CONFIG_HOME || path2.join(os2.homedir(), ".config");
|
|
2335
|
+
}
|
|
2336
|
+
return path2.join(baseDir, "pinggy");
|
|
2337
|
+
}
|
|
2338
|
+
function getTunnelConfigDir() {
|
|
2339
|
+
return path2.join(getPinggyConfigDir(), "tunnels");
|
|
2340
|
+
}
|
|
2341
|
+
function ensureTunnelConfigDir() {
|
|
2342
|
+
const dir = getTunnelConfigDir();
|
|
2343
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
2344
|
+
return dir;
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
// src/cli/configStore.ts
|
|
2348
|
+
import pico2 from "picocolors";
|
|
2349
|
+
function buildFilename(name, configId) {
|
|
2350
|
+
return `${name}_${configId}.json`;
|
|
2351
|
+
}
|
|
2352
|
+
function sanitizeName(name) {
|
|
2353
|
+
return name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2354
|
+
}
|
|
2355
|
+
function validateName(name) {
|
|
2356
|
+
if (!name || name.trim().length === 0) {
|
|
2357
|
+
return new Error("Tunnel name cannot be empty.");
|
|
2358
|
+
}
|
|
2359
|
+
if (name.length > 128) {
|
|
2360
|
+
return new Error("Tunnel name cannot exceed 128 characters.");
|
|
2361
|
+
}
|
|
2362
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
2363
|
+
return new Error("Tunnel name can only contain alphanumeric characters, hyphens, and underscores.");
|
|
2364
|
+
}
|
|
2365
|
+
return null;
|
|
2366
|
+
}
|
|
2367
|
+
function readConfigFile(filePath) {
|
|
2368
|
+
try {
|
|
2369
|
+
const data = fs3.readFileSync(filePath, { encoding: "utf-8" });
|
|
2370
|
+
return JSON.parse(data);
|
|
2371
|
+
} catch (err) {
|
|
2372
|
+
logger.warn(`Failed to read config file ${filePath}:`, err);
|
|
2373
|
+
return null;
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
function writeConfigFile(filePath, config) {
|
|
2377
|
+
fs3.writeFileSync(filePath, JSON.stringify(config, null, 2), { encoding: "utf-8" });
|
|
2378
|
+
}
|
|
2379
|
+
function listSavedConfigs() {
|
|
2380
|
+
const dir = getTunnelConfigDir();
|
|
2381
|
+
if (!fs3.existsSync(dir)) {
|
|
2382
|
+
return [];
|
|
2383
|
+
}
|
|
2384
|
+
const files = fs3.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
2385
|
+
const configs = [];
|
|
2386
|
+
for (const file of files) {
|
|
2387
|
+
const config = readConfigFile(path3.join(dir, file));
|
|
2388
|
+
if (config && config.name && config.configId) {
|
|
2389
|
+
configs.push(config);
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
return configs;
|
|
2393
|
+
}
|
|
2394
|
+
function findConfigFile(nameOrId) {
|
|
2395
|
+
const dir = getTunnelConfigDir();
|
|
2396
|
+
if (!fs3.existsSync(dir)) return null;
|
|
2397
|
+
const files = fs3.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
2398
|
+
const sanitized = sanitizeName(nameOrId);
|
|
2399
|
+
const nameMatch = files.find((f) => f.startsWith(sanitized + "_"));
|
|
2400
|
+
if (nameMatch) {
|
|
2401
|
+
const filePath = path3.join(dir, nameMatch);
|
|
2402
|
+
const config = readConfigFile(filePath);
|
|
2403
|
+
if (config && config.name === nameOrId) return { filePath, config };
|
|
2404
|
+
}
|
|
2405
|
+
const idCandidates = files.filter((f) => {
|
|
2406
|
+
const withoutExt = f.replace(/\.json$/, "");
|
|
2407
|
+
const lastUnderscore = withoutExt.indexOf("_");
|
|
2408
|
+
if (lastUnderscore === -1) return false;
|
|
2409
|
+
const idPart = withoutExt.slice(lastUnderscore + 1);
|
|
2410
|
+
return idPart.startsWith(nameOrId);
|
|
2411
|
+
});
|
|
2412
|
+
if (idCandidates.length === 1) {
|
|
2413
|
+
const filePath = path3.join(dir, idCandidates[0]);
|
|
2414
|
+
const config = readConfigFile(filePath);
|
|
2415
|
+
if (config) return { filePath, config };
|
|
2416
|
+
}
|
|
2417
|
+
return null;
|
|
2418
|
+
}
|
|
2419
|
+
function findConfigByName(name) {
|
|
2420
|
+
const resolved = findConfigFile(name);
|
|
2421
|
+
return resolved?.config.name === name ? resolved.config : null;
|
|
2422
|
+
}
|
|
2423
|
+
function findConfig(nameOrId) {
|
|
2424
|
+
return findConfigFile(nameOrId)?.config ?? null;
|
|
2425
|
+
}
|
|
2426
|
+
function saveConfig(name, configId, tunnelConfig, autoStart = false) {
|
|
2427
|
+
const nameErr = validateName(name);
|
|
2428
|
+
if (nameErr) {
|
|
2429
|
+
throw nameErr;
|
|
2430
|
+
}
|
|
2431
|
+
const existing = findConfigByName(name);
|
|
2432
|
+
if (existing) {
|
|
2433
|
+
throw new Error(
|
|
2434
|
+
`A tunnel config with the name "${name}" already exists (configId: ${existing.configId}). Please use a different name.`
|
|
2435
|
+
);
|
|
2436
|
+
}
|
|
2437
|
+
const dir = ensureTunnelConfigDir();
|
|
2438
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2439
|
+
const saved = {
|
|
2440
|
+
name,
|
|
2441
|
+
configId,
|
|
2442
|
+
autoStart,
|
|
2443
|
+
createdAt: now,
|
|
2444
|
+
updatedAt: now,
|
|
2445
|
+
tunnelConfig
|
|
2446
|
+
};
|
|
2447
|
+
const filename = buildFilename(sanitizeName(name), configId);
|
|
2448
|
+
const filePath = path3.join(dir, filename);
|
|
2449
|
+
fs3.writeFileSync(filePath, JSON.stringify(saved, null, 2), { encoding: "utf-8" });
|
|
2450
|
+
logger.info(`Config "${name}" saved to ${filePath}`);
|
|
2451
|
+
return saved;
|
|
2452
|
+
}
|
|
2453
|
+
function deleteConfig(nameOrId) {
|
|
2454
|
+
const resolved = findConfigFile(nameOrId);
|
|
2455
|
+
if (!resolved) return null;
|
|
2456
|
+
fs3.unlinkSync(resolved.filePath);
|
|
2457
|
+
logger.info(`Config "${resolved.config.name}" deleted.`);
|
|
2458
|
+
return resolved.config.name;
|
|
2459
|
+
}
|
|
2460
|
+
function updateConfigAutoStart(nameOrId, autoStart) {
|
|
2461
|
+
const resolved = findConfigFile(nameOrId);
|
|
2462
|
+
if (!resolved) return null;
|
|
2463
|
+
resolved.config.autoStart = autoStart;
|
|
2464
|
+
resolved.config.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2465
|
+
writeConfigFile(resolved.filePath, resolved.config);
|
|
2466
|
+
logger.info(`Config "${resolved.config.name}" auto-start set to ${autoStart}`);
|
|
2467
|
+
return resolved.config;
|
|
2468
|
+
}
|
|
2469
|
+
function updateTunnelConfig(nameOrId, tunnelConfig) {
|
|
2470
|
+
const resolved = findConfigFile(nameOrId);
|
|
2471
|
+
if (!resolved) return null;
|
|
2472
|
+
resolved.config.tunnelConfig = tunnelConfig;
|
|
2473
|
+
resolved.config.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2474
|
+
writeConfigFile(resolved.filePath, resolved.config);
|
|
2475
|
+
logger.info(`Config "${resolved.config.name}" tunnel configuration updated`);
|
|
2476
|
+
return resolved.config;
|
|
2477
|
+
}
|
|
2478
|
+
function getAutoStartConfigs() {
|
|
2479
|
+
return listSavedConfigs().filter((c) => c.autoStart);
|
|
2480
|
+
}
|
|
2481
|
+
function printConfigList() {
|
|
2482
|
+
const configs = listSavedConfigs();
|
|
2483
|
+
if (configs.length === 0) {
|
|
2484
|
+
console.log(pico2.yellow("No saved tunnel configs found."));
|
|
2485
|
+
console.log(pico2.gray(`Config directory: ${getTunnelConfigDir()}`));
|
|
2486
|
+
return;
|
|
2487
|
+
}
|
|
2488
|
+
const nameW = 20;
|
|
2489
|
+
const idW = 12;
|
|
2490
|
+
const typeW = 8;
|
|
2491
|
+
const fwdW = 25;
|
|
2492
|
+
const serverW = 22;
|
|
2493
|
+
const autoW = 10;
|
|
2494
|
+
const header = pico2.bold("Name".padEnd(nameW)) + pico2.bold("Config ID".padEnd(idW)) + pico2.bold("Type".padEnd(typeW)) + pico2.bold("Forwarding".padEnd(fwdW)) + pico2.bold("Server".padEnd(serverW)) + pico2.bold("Auto-start".padEnd(autoW));
|
|
2495
|
+
console.log("\n" + header);
|
|
2496
|
+
console.log(pico2.gray("\u2500".repeat(nameW + idW + typeW + fwdW + serverW + autoW)));
|
|
2497
|
+
for (const c of configs) {
|
|
2498
|
+
const tc = c.tunnelConfig;
|
|
2499
|
+
const forwarding = Array.isArray(tc.forwarding) ? tc.forwarding[0]?.address : String(tc.forwarding || "");
|
|
2500
|
+
const type = (Array.isArray(tc.forwarding) ? tc.forwarding[0]?.type : void 0) || "http";
|
|
2501
|
+
const server = tc.serverAddress || "a.pinggy.io";
|
|
2502
|
+
const line = pico2.cyanBright(c.name.padEnd(nameW)) + pico2.gray(c.configId.slice(0, 8).padEnd(idW)) + type.padEnd(typeW) + forwarding.slice(0, fwdW - 2).padEnd(fwdW) + server.slice(0, serverW - 2).padEnd(serverW) + (c.autoStart ? pico2.green("yes") : pico2.gray("no")).padEnd(autoW);
|
|
2503
|
+
console.log(line);
|
|
2504
|
+
}
|
|
2505
|
+
console.log();
|
|
2506
|
+
}
|
|
2507
|
+
function printConfigDetail(config) {
|
|
2508
|
+
console.log(pico2.bold(`
|
|
2509
|
+
Tunnel Config: ${pico2.cyanBright(config.name)}`));
|
|
2510
|
+
console.log(pico2.gray("\u2500".repeat(40)));
|
|
2511
|
+
console.log(` Config ID: ${config.configId}`);
|
|
2512
|
+
console.log(` Auto-start: ${config.autoStart ? pico2.green("yes") : pico2.gray("no")}`);
|
|
2513
|
+
console.log(` Created: ${config.createdAt}`);
|
|
2514
|
+
console.log(` Updated: ${config.updatedAt}`);
|
|
2515
|
+
console.log(pico2.gray("\u2500".repeat(40)));
|
|
2516
|
+
console.log(` Server: ${config.tunnelConfig.serverAddress || "a.pinggy.io"}`);
|
|
2517
|
+
console.log(` Token: ${config.tunnelConfig.token ? "***" + config.tunnelConfig.token.slice(-4) : "(none)"}`);
|
|
2518
|
+
const fwd = config.tunnelConfig.forwarding;
|
|
2519
|
+
if (Array.isArray(fwd)) {
|
|
2520
|
+
const defaultFwds = [];
|
|
2521
|
+
const customFwds = [];
|
|
2522
|
+
for (const f of fwd) {
|
|
2523
|
+
if (typeof f === "string") {
|
|
2524
|
+
defaultFwds.push(f);
|
|
2525
|
+
} else if (f.listenAddress) {
|
|
2526
|
+
customFwds.push(f);
|
|
2527
|
+
} else {
|
|
2528
|
+
defaultFwds.push(f);
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
for (const f of defaultFwds) {
|
|
2532
|
+
const addr = typeof f === "string" ? f : `${f.address} (${f.type || "http"})`;
|
|
2533
|
+
console.log(` Forwarding: ${addr}`);
|
|
2534
|
+
if (config.tunnelConfig.webDebugger) {
|
|
2535
|
+
console.log(` Debugger: ${config.tunnelConfig.webDebugger}`);
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
if (customFwds.length > 0) {
|
|
2539
|
+
console.log(pico2.gray("\u2500".repeat(40)));
|
|
2540
|
+
console.log(pico2.bold(" Domain Mappings:"));
|
|
2541
|
+
for (const f of customFwds) {
|
|
2542
|
+
if (typeof f === "string") continue;
|
|
2543
|
+
const domain = f.listenAddress;
|
|
2544
|
+
const target = f.address;
|
|
2545
|
+
const type = f.type || "http";
|
|
2546
|
+
console.log(` ${pico2.cyanBright(domain)} \u2192 ${target} (${type})`);
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
} else if (fwd) {
|
|
2550
|
+
console.log(` Forwarding: ${fwd}`);
|
|
2551
|
+
}
|
|
2552
|
+
console.log();
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
// src/cli/buildAndStartTunnel.ts
|
|
2556
|
+
async function buildAndStartTunnel(values, positionals, manager) {
|
|
2557
|
+
await initRemoteManagement(values);
|
|
2558
|
+
logger.debug("Building final config from CLI values and positionals", { values, positionals });
|
|
2559
|
+
const finalConfig = await buildFinalConfig(values, positionals);
|
|
2560
|
+
logger.debug("Final configuration built", finalConfig);
|
|
2561
|
+
if (values.save) {
|
|
2562
|
+
const name = values.name;
|
|
2563
|
+
if (!name) {
|
|
2564
|
+
printer_default.error("--save requires --name to specify a name for the tunnel config.");
|
|
2565
|
+
process.exit(1);
|
|
2566
|
+
}
|
|
2567
|
+
const nameErr = validateName(name);
|
|
2568
|
+
if (nameErr) {
|
|
2569
|
+
printer_default.error(nameErr.message);
|
|
2570
|
+
process.exit(1);
|
|
2571
|
+
}
|
|
2572
|
+
const autoStart = !!values.auto;
|
|
2573
|
+
saveConfig(name, finalConfig.configId, finalConfig, autoStart);
|
|
2574
|
+
printer_default.success(`Config "${name}" saved.`);
|
|
2575
|
+
}
|
|
2576
|
+
await startCli(finalConfig, manager);
|
|
2577
|
+
}
|
|
2578
|
+
async function initRemoteManagement(values) {
|
|
2579
|
+
const parseResult = await parseRemoteManagement(values);
|
|
2580
|
+
if (parseResult?.ok === false) {
|
|
2581
|
+
logger.error("Failed to initiate remote management:", parseResult.error);
|
|
2582
|
+
printer_default.fatal(parseResult.error);
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
// src/cli/subcommands.ts
|
|
2587
|
+
import pico3 from "picocolors";
|
|
2588
|
+
var SUBCOMMANDS = /* @__PURE__ */ new Set(["config", "start"]);
|
|
2589
|
+
function isSubcommand(rawArgs) {
|
|
2590
|
+
return rawArgs.length > 0 && SUBCOMMANDS.has(rawArgs[0]);
|
|
2591
|
+
}
|
|
2592
|
+
async function handleSubcommand(rawArgs, manager) {
|
|
2593
|
+
const sub = rawArgs[0];
|
|
2594
|
+
const rest = rawArgs.slice(1);
|
|
2595
|
+
switch (sub) {
|
|
2596
|
+
case "config":
|
|
2597
|
+
await handleConfig(rest);
|
|
2598
|
+
return;
|
|
2599
|
+
case "start":
|
|
2600
|
+
await handleStart(rest, manager);
|
|
2601
|
+
return;
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
async function handleConfig(args) {
|
|
2605
|
+
if (args.length === 0) {
|
|
2606
|
+
printConfigHelp();
|
|
2607
|
+
return;
|
|
2608
|
+
}
|
|
2609
|
+
const verb = args[0];
|
|
2610
|
+
const rest = args.slice(1);
|
|
2611
|
+
switch (verb) {
|
|
2612
|
+
case "list":
|
|
2613
|
+
case "ls":
|
|
2614
|
+
printConfigList();
|
|
2615
|
+
return;
|
|
2616
|
+
case "show": {
|
|
2617
|
+
const names = requireNames(rest, "config show");
|
|
2618
|
+
for (const name of names) {
|
|
2619
|
+
const saved2 = resolveConfig(name);
|
|
2620
|
+
if (saved2) printConfigDetail(saved2);
|
|
2621
|
+
}
|
|
2622
|
+
return;
|
|
2623
|
+
}
|
|
2624
|
+
case "save": {
|
|
2625
|
+
const name = requireName(rest, "config save");
|
|
2626
|
+
await handleConfigSave(name, rest.slice(1));
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2629
|
+
case "delete": {
|
|
2630
|
+
const names = requireNames(rest, "config delete");
|
|
2631
|
+
for (const name of names) {
|
|
2632
|
+
const deletedName = deleteConfig(name);
|
|
2633
|
+
if (deletedName) {
|
|
2634
|
+
printer_default.success(`Config "${deletedName}" deleted.`);
|
|
2635
|
+
} else {
|
|
2636
|
+
printer_default.error(`No config found matching "${name}". Use: pinggy config list`);
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
return;
|
|
2640
|
+
}
|
|
2641
|
+
case "update": {
|
|
2642
|
+
const name = requireName(rest, "config update");
|
|
2643
|
+
await handleConfigUpdate(name, rest.slice(1));
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2646
|
+
case "auto": {
|
|
2647
|
+
const names = requireNames(rest, "config auto");
|
|
2648
|
+
for (const name of names) {
|
|
2649
|
+
const updated = updateConfigAutoStart(name, true);
|
|
2650
|
+
if (updated) {
|
|
2651
|
+
printer_default.success(`Config "${updated.name}" auto-start set to on.`);
|
|
2652
|
+
} else {
|
|
2653
|
+
printer_default.error(`No config found matching "${name}". Use: pinggy config list`);
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
return;
|
|
2657
|
+
}
|
|
2658
|
+
case "noauto": {
|
|
2659
|
+
const names = requireNames(rest, "config noauto");
|
|
2660
|
+
for (const name of names) {
|
|
2661
|
+
const updated = updateConfigAutoStart(name, false);
|
|
2662
|
+
if (updated) {
|
|
2663
|
+
printer_default.success(`Config "${updated.name}" auto-start set to off.`);
|
|
2664
|
+
} else {
|
|
2665
|
+
printer_default.error(`No config found matching "${name}". Use: pinggy config list`);
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
return;
|
|
2669
|
+
}
|
|
2670
|
+
default:
|
|
2671
|
+
const saved = resolveConfig(verb);
|
|
2672
|
+
if (saved) printConfigDetail(saved);
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
async function handleConfigSave(name, remainingArgs) {
|
|
2677
|
+
const nameErr = validateName(name);
|
|
2678
|
+
if (nameErr) {
|
|
2679
|
+
printer_default.error(nameErr.message);
|
|
2680
|
+
process.exit(1);
|
|
2681
|
+
}
|
|
2682
|
+
const { values, positionals } = parseCliArgs(cliOptions, remainingArgs);
|
|
2683
|
+
const autoStart = !!values.auto;
|
|
2684
|
+
logger.debug("Building config for save", { name, values, positionals });
|
|
2685
|
+
const finalConfig = await buildFinalConfig(values, positionals);
|
|
2686
|
+
saveConfig(name, finalConfig.configId, finalConfig, autoStart);
|
|
2687
|
+
printer_default.success(`Config "${name}" saved.`);
|
|
2688
|
+
}
|
|
2689
|
+
async function handleConfigUpdate(nameOrId, remainingArgs) {
|
|
2690
|
+
const saved = resolveConfig(nameOrId);
|
|
2691
|
+
if (!saved) return;
|
|
2692
|
+
const { values, positionals } = parseCliArgs(cliOptions, remainingArgs);
|
|
2693
|
+
logger.debug("Building updated config", { nameOrId, values, positionals });
|
|
2694
|
+
const updatedConfig = await buildFinalConfig(values, positionals, saved.tunnelConfig);
|
|
2695
|
+
const result = updateTunnelConfig(nameOrId, updatedConfig);
|
|
2696
|
+
if (result) {
|
|
2697
|
+
printer_default.success(`Config "${result.name}" updated.`);
|
|
2698
|
+
printConfigDetail(result);
|
|
2699
|
+
} else {
|
|
2700
|
+
printer_default.error(`Failed to update config "${nameOrId}".`);
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
async function handleStart(args, manager) {
|
|
2704
|
+
const startAll = args.includes("--all");
|
|
2705
|
+
const argsWithoutAll = args.filter((a) => a !== "--all");
|
|
2706
|
+
const names = [];
|
|
2707
|
+
let i = 0;
|
|
2708
|
+
while (i < argsWithoutAll.length && !argsWithoutAll[i].startsWith("-")) {
|
|
2709
|
+
names.push(argsWithoutAll[i]);
|
|
2710
|
+
i++;
|
|
2711
|
+
}
|
|
2712
|
+
const flagArgs = argsWithoutAll.slice(i);
|
|
2713
|
+
const { values, positionals } = parseCliArgs(cliOptions, flagArgs);
|
|
2714
|
+
configureLogger(values);
|
|
2715
|
+
if (startAll) {
|
|
2716
|
+
await initRemoteManagementBackground(values);
|
|
2717
|
+
await startAutoStartTunnels(manager);
|
|
2718
|
+
return;
|
|
2719
|
+
}
|
|
2720
|
+
if (names.length === 0) {
|
|
2721
|
+
printStartHelp();
|
|
2722
|
+
return;
|
|
2723
|
+
}
|
|
2724
|
+
const resolved = [];
|
|
2725
|
+
for (const name of names) {
|
|
2726
|
+
const saved = resolveConfig(name);
|
|
2727
|
+
if (!saved) return;
|
|
2728
|
+
resolved.push(saved);
|
|
2729
|
+
}
|
|
2730
|
+
if (resolved.length > 1 && flagArgs.length > 0) {
|
|
2731
|
+
printer_default.error("Runtime overrides (-l, --type, etc.) can only be used when starting a single tunnel.");
|
|
2732
|
+
printer_default.print(" Start one tunnel: pinggy start my-tunnel -l 4000");
|
|
2733
|
+
printer_default.print(" Or update first: pinggy config update my-tunnel -l 4000");
|
|
2734
|
+
return;
|
|
2735
|
+
}
|
|
2736
|
+
await initRemoteManagementBackground(values);
|
|
2737
|
+
if (resolved.length === 1) {
|
|
2738
|
+
const saved = resolved[0];
|
|
2739
|
+
logger.debug("Building config with overrides", { name: saved.name });
|
|
2740
|
+
const finalConfig = await buildFinalConfig(values, positionals, saved.tunnelConfig);
|
|
2741
|
+
finalConfig.configId = saved.configId;
|
|
2742
|
+
await startCli(finalConfig, manager);
|
|
2743
|
+
} else {
|
|
2744
|
+
await startNamedTunnels(resolved, manager);
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
async function startAutoStartTunnels(manager) {
|
|
2748
|
+
const configs = getAutoStartConfigs();
|
|
2749
|
+
if (configs.length === 0) {
|
|
2750
|
+
printer_default.warn("No configs marked for auto-start. Use: pinggy config auto <name>");
|
|
2751
|
+
return;
|
|
2752
|
+
}
|
|
2753
|
+
printer_default.print(pico3.cyanBright(`Starting ${configs.length} auto-start tunnel(s)...`));
|
|
2754
|
+
for (const saved of configs) {
|
|
2755
|
+
await startSavedTunnel(saved, manager);
|
|
2756
|
+
}
|
|
2757
|
+
printer_default.print(pico3.gray("\nAll auto-start tunnels launched. Press Ctrl+C to stop.\n"));
|
|
2758
|
+
await new Promise(() => {
|
|
2759
|
+
});
|
|
2760
|
+
}
|
|
2761
|
+
async function startNamedTunnels(configs, manager) {
|
|
2762
|
+
printer_default.print(pico3.cyanBright(`Starting ${configs.length} tunnel(s)...`));
|
|
2763
|
+
for (const saved of configs) {
|
|
2764
|
+
await startSavedTunnel(saved, manager);
|
|
2765
|
+
}
|
|
2766
|
+
printer_default.print(pico3.gray("\nAll tunnels launched. Press Ctrl+C to stop.\n"));
|
|
2767
|
+
await new Promise(() => {
|
|
2768
|
+
});
|
|
2769
|
+
}
|
|
2770
|
+
async function startSavedTunnel(saved, manager) {
|
|
2771
|
+
const config = {
|
|
2772
|
+
...saved.tunnelConfig,
|
|
2773
|
+
configId: saved.configId,
|
|
2774
|
+
name: saved.name,
|
|
2775
|
+
optional: {
|
|
2776
|
+
...saved.tunnelConfig.optional,
|
|
2777
|
+
noTui: true
|
|
2778
|
+
}
|
|
2779
|
+
};
|
|
2780
|
+
try {
|
|
2781
|
+
const tunnel = await manager.createTunnel(config);
|
|
2782
|
+
await manager.startTunnel(tunnel.tunnelid);
|
|
2783
|
+
const urls = await manager.getTunnelUrls(tunnel.tunnelid);
|
|
2784
|
+
printer_default.success(`"${saved.name}" started`);
|
|
2785
|
+
(urls ?? []).forEach(
|
|
2786
|
+
(url) => printer_default.print(" " + pico3.magentaBright(url))
|
|
2787
|
+
);
|
|
2788
|
+
manager.registerWorkerErrorListner(tunnel.tunnelid, (_id, error) => {
|
|
2789
|
+
printer_default.error(`[${saved.name}] Fatal: ${error.message}`);
|
|
2790
|
+
});
|
|
2791
|
+
manager.registerDisconnectListener(tunnel.tunnelid, async (_id, error, messages) => {
|
|
2792
|
+
if (error) printer_default.warn(`[${saved.name}] Disconnected: ${error}`);
|
|
2793
|
+
messages?.forEach((m) => printer_default.warn(`[${saved.name}] ${m}`));
|
|
2794
|
+
});
|
|
2795
|
+
manager.registerReconnectingListener(tunnel.tunnelid, (_id, retryCnt) => {
|
|
2796
|
+
printer_default.print(pico3.gray(`[${saved.name}] Reconnecting (attempt #${retryCnt})...`));
|
|
2797
|
+
});
|
|
2798
|
+
manager.registerReconnectionCompletedListener(tunnel.tunnelid, async (_id, urls2) => {
|
|
2799
|
+
printer_default.success(`[${saved.name}] Reconnected`);
|
|
2800
|
+
(urls2 ?? []).forEach(
|
|
2801
|
+
(url) => printer_default.print(" " + pico3.magentaBright(url))
|
|
2802
|
+
);
|
|
2803
|
+
});
|
|
2804
|
+
manager.registerReconnectionFailedListener(tunnel.tunnelid, (_id, retryCnt) => {
|
|
2805
|
+
printer_default.error(`[${saved.name}] Reconnection failed after ${retryCnt} attempts`);
|
|
2806
|
+
});
|
|
2807
|
+
} catch (err) {
|
|
2808
|
+
printer_default.error(`[${saved.name}] Failed to start: ${err.message || err}`);
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
function resolveConfig(nameOrId) {
|
|
2812
|
+
const saved = findConfig(nameOrId);
|
|
2813
|
+
if (!saved) {
|
|
2814
|
+
printer_default.error(`No config found matching "${nameOrId}". Use: pinggy config list`);
|
|
2815
|
+
return null;
|
|
2816
|
+
}
|
|
2817
|
+
return saved;
|
|
2818
|
+
}
|
|
2819
|
+
function requireName(args, command) {
|
|
2820
|
+
if (args.length === 0 || args[0].startsWith("-")) {
|
|
2821
|
+
printer_default.error(`Tunnel name is required. Usage: pinggy ${command} <name>`);
|
|
2822
|
+
process.exit(1);
|
|
2823
|
+
}
|
|
2824
|
+
return args[0];
|
|
2825
|
+
}
|
|
2826
|
+
function requireNames(args, command) {
|
|
2827
|
+
const names = [];
|
|
2828
|
+
for (const arg of args) {
|
|
2829
|
+
if (arg.startsWith("-")) break;
|
|
2830
|
+
names.push(arg);
|
|
2831
|
+
}
|
|
2832
|
+
if (names.length === 0) {
|
|
2833
|
+
printer_default.error(`At least one tunnel name is required. Usage: pinggy ${command} <name> [name2 ...]`);
|
|
2834
|
+
process.exit(1);
|
|
2835
|
+
}
|
|
2836
|
+
return names;
|
|
2837
|
+
}
|
|
2838
|
+
async function initRemoteManagementBackground(values) {
|
|
2839
|
+
const rmToken = values["remote-management"];
|
|
2840
|
+
if (typeof rmToken === "string" && rmToken.trim().length > 0) {
|
|
2841
|
+
const manageHost = values["manage"];
|
|
2842
|
+
try {
|
|
2843
|
+
await startRemoteManagement({
|
|
2844
|
+
apiKey: rmToken,
|
|
2845
|
+
serverUrl: buildRemoteManagementWsUrl(manageHost)
|
|
2846
|
+
});
|
|
2847
|
+
} catch (e) {
|
|
2848
|
+
logger.error("Failed to initiate remote management:", e);
|
|
2849
|
+
printer_default.fatal(e);
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
function printConfigHelp() {
|
|
2854
|
+
console.log("\nUsage: pinggy config <command> [name] [options]\n");
|
|
2855
|
+
console.log("Commands:");
|
|
2856
|
+
console.log(" list List all saved configs");
|
|
2857
|
+
console.log(" show <name> Show config details");
|
|
2858
|
+
console.log(" save <name> [tunnel flags] Save a tunnel config");
|
|
2859
|
+
console.log(" update <name> [tunnel flags] Update a saved config");
|
|
2860
|
+
console.log(" delete <name> Delete a saved config");
|
|
2861
|
+
console.log(" auto <name> Enable auto-start");
|
|
2862
|
+
console.log(" noauto <name> Disable auto-start\n");
|
|
2863
|
+
}
|
|
2864
|
+
function printStartHelp() {
|
|
2865
|
+
console.log("\nUsage: pinggy start <name> [options]\n");
|
|
2866
|
+
console.log("Examples:");
|
|
2867
|
+
console.log(" pinggy start my-tunnel Start a saved tunnel");
|
|
2868
|
+
console.log(" pinggy start my-tunnel -l 4000 Start with override");
|
|
2869
|
+
console.log(" pinggy start tunnela tunnelb Start multiple tunnels");
|
|
2870
|
+
console.log(" pinggy start --all Start all auto-start tunnels\n");
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2302
2873
|
// src/main.ts
|
|
2303
|
-
import { fileURLToPath } from "url";
|
|
2304
|
-
import { argv } from "process";
|
|
2305
|
-
import { realpathSync } from "fs";
|
|
2306
2874
|
async function main() {
|
|
2307
2875
|
try {
|
|
2308
|
-
const
|
|
2309
|
-
configureLogger(values);
|
|
2876
|
+
const rawArgs = process.argv.slice(2);
|
|
2310
2877
|
const manager = TunnelManager.getInstance();
|
|
2311
|
-
|
|
2312
|
-
logger.info(
|
|
2878
|
+
const gracefulShutdown = (signal) => {
|
|
2879
|
+
logger.info(`${signal} received: stopping tunnels and exiting`);
|
|
2313
2880
|
console.log("\nStopping all tunnels...");
|
|
2314
2881
|
manager.stopAllTunnels();
|
|
2315
2882
|
console.log("Tunnels stopped. Exiting.");
|
|
2316
2883
|
process.exit(0);
|
|
2317
|
-
}
|
|
2884
|
+
};
|
|
2885
|
+
process.on("SIGINT", () => gracefulShutdown("SIGINT"));
|
|
2886
|
+
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
|
|
2887
|
+
if (isSubcommand(rawArgs)) {
|
|
2888
|
+
await handleSubcommand(rawArgs, manager);
|
|
2889
|
+
return;
|
|
2890
|
+
}
|
|
2891
|
+
const { values, positionals, hasAnyArgs } = parseCliArgs(cliOptions);
|
|
2892
|
+
configureLogger(values);
|
|
2318
2893
|
if (!hasAnyArgs || values.help) {
|
|
2319
2894
|
printHelpMessage();
|
|
2320
2895
|
return;
|
|
@@ -2323,15 +2898,7 @@ async function main() {
|
|
|
2323
2898
|
printer_default.print(`Pinggy CLI version: ${getVersion()}`);
|
|
2324
2899
|
return;
|
|
2325
2900
|
}
|
|
2326
|
-
|
|
2327
|
-
if (parseResult?.ok === false) {
|
|
2328
|
-
logger.error("Failed to initiate remote management:", parseResult.error);
|
|
2329
|
-
printer_default.fatal(parseResult.error);
|
|
2330
|
-
}
|
|
2331
|
-
logger.debug("Building final config from CLI values and positionals", { values, positionals });
|
|
2332
|
-
const finalConfig = await buildFinalConfig(values, positionals);
|
|
2333
|
-
logger.debug("Final configuration built", finalConfig);
|
|
2334
|
-
await startCli(finalConfig, manager);
|
|
2901
|
+
await buildAndStartTunnel(values, positionals, manager);
|
|
2335
2902
|
} catch (error) {
|
|
2336
2903
|
logger.error("Unhandled error in CLI:", error);
|
|
2337
2904
|
printer_default.fatal(error);
|