opentunnel-cli 1.0.31 → 1.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +192 -23
- package/dist/cli/index.js.map +1 -1
- package/dist/client/CloudflareTunnelClient.js +1 -1
- package/dist/client/CloudflareTunnelClient.js.map +1 -1
- package/dist/client/TunnelClient.d.ts +2 -0
- package/dist/client/TunnelClient.d.ts.map +1 -1
- package/dist/client/TunnelClient.js +110 -29
- package/dist/client/TunnelClient.js.map +1 -1
- package/dist/server/TunnelServer.d.ts +3 -0
- package/dist/server/TunnelServer.d.ts.map +1 -1
- package/dist/server/TunnelServer.js +93 -29
- package/dist/server/TunnelServer.js.map +1 -1
- package/dist/shared/types.d.ts +20 -2
- package/dist/shared/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -238,7 +238,7 @@ program
|
|
|
238
238
|
.name("opentunnel")
|
|
239
239
|
.alias("ot")
|
|
240
240
|
.description("Expose local ports to the internet via custom domains, ngrok, or Cloudflare Tunnel")
|
|
241
|
-
.version("1.0.
|
|
241
|
+
.version("1.0.33");
|
|
242
242
|
// Helper function to build WebSocket URL from domain
|
|
243
243
|
// User only provides base domain (e.g., fjrg2007.com), system handles the rest
|
|
244
244
|
// Note: --insecure flag only affects certificate verification, not the protocol
|
|
@@ -651,9 +651,9 @@ program
|
|
|
651
651
|
.option("--streaming", "Enable streaming mode (longer timeouts for video/large files)")
|
|
652
652
|
.option("--timeout <ms>", "Custom request timeout in milliseconds")
|
|
653
653
|
.option("--ngrok", "Use ngrok instead of OpenTunnel server")
|
|
654
|
-
.option("--region <region>", "
|
|
654
|
+
.option("--region <region>", "Region (ngrok: us, eu, ap, au, sa, jp, in)", "us")
|
|
655
655
|
.option("--cloudflare, --cf", "Use Cloudflare Tunnel instead of OpenTunnel server")
|
|
656
|
-
.option("--
|
|
656
|
+
.option("--hostname <hostname>", "Custom hostname for tunnel (cloudflare named tunnels)")
|
|
657
657
|
.option("--provider <provider>", "Tunnel provider (opentunnel, ngrok, cloudflare)")
|
|
658
658
|
.action(async (port, options) => {
|
|
659
659
|
// Determine provider
|
|
@@ -666,8 +666,8 @@ program
|
|
|
666
666
|
protocol: options.https ? "https" : "http",
|
|
667
667
|
localHost: options.host,
|
|
668
668
|
localPort: parseInt(port),
|
|
669
|
-
hostname: options.
|
|
670
|
-
tunnelName: options.subdomain, // -n works as tunnel name
|
|
669
|
+
hostname: options.hostname,
|
|
670
|
+
tunnelName: options.subdomain, // -n works as tunnel name
|
|
671
671
|
noTlsVerify: options.insecure,
|
|
672
672
|
});
|
|
673
673
|
return;
|
|
@@ -721,9 +721,9 @@ program
|
|
|
721
721
|
.option("-h, --host <host>", "Local host", "localhost")
|
|
722
722
|
.option("--insecure", "Skip SSL verification (for self-signed certs)")
|
|
723
723
|
.option("--ngrok", "Use ngrok instead of OpenTunnel server")
|
|
724
|
-
.option("--region <region>", "
|
|
724
|
+
.option("--region <region>", "Region (ngrok: us, eu, ap, au, sa, jp, in)", "us")
|
|
725
725
|
.option("--cloudflare, --cf", "Use Cloudflare Tunnel (TCP requires named tunnel)")
|
|
726
|
-
.option("--
|
|
726
|
+
.option("--hostname <hostname>", "Custom hostname for tunnel")
|
|
727
727
|
.option("--provider <provider>", "Tunnel provider (opentunnel, ngrok, cloudflare)")
|
|
728
728
|
.action(async (port, options) => {
|
|
729
729
|
// Determine provider
|
|
@@ -741,7 +741,7 @@ program
|
|
|
741
741
|
protocol: "tcp",
|
|
742
742
|
localHost: options.host,
|
|
743
743
|
localPort: parseInt(port),
|
|
744
|
-
hostname: options.
|
|
744
|
+
hostname: options.hostname,
|
|
745
745
|
tunnelName: options.subdomain,
|
|
746
746
|
noTlsVerify: options.insecure,
|
|
747
747
|
});
|
|
@@ -2661,8 +2661,8 @@ async function startTunnelsFromConfig(tunnels, serverUrl, token, insecure, globa
|
|
|
2661
2661
|
const ngrokTunnels = tunnelsByProvider.get("ngrok") || [];
|
|
2662
2662
|
for (const tunnel of ngrokTunnels) {
|
|
2663
2663
|
const spinner = (0, ora_1.default)(`Creating ngrok tunnel: ${tunnel.name}...`).start();
|
|
2664
|
-
const ngrokToken = tunnel.
|
|
2665
|
-
const ngrokRegion = tunnel.
|
|
2664
|
+
const ngrokToken = tunnel.token || globalConfig?.token;
|
|
2665
|
+
const ngrokRegion = tunnel.region || globalConfig?.region || "us";
|
|
2666
2666
|
// Merge IP access config: tunnel-specific > global security > none
|
|
2667
2667
|
const { IpFilter } = await Promise.resolve().then(() => __importStar(require("../shared/ip-filter")));
|
|
2668
2668
|
const ipAccess = IpFilter.mergeConfigs(globalConfig?.security?.ipAccess, tunnel.ipAccess);
|
|
@@ -2693,31 +2693,31 @@ async function startTunnelsFromConfig(tunnels, serverUrl, token, insecure, globa
|
|
|
2693
2693
|
const cloudflareTunnels = tunnelsByProvider.get("cloudflare") || [];
|
|
2694
2694
|
for (const tunnel of cloudflareTunnels) {
|
|
2695
2695
|
const spinner = (0, ora_1.default)(`Creating Cloudflare tunnel: ${tunnel.name}...`).start();
|
|
2696
|
-
const
|
|
2697
|
-
//
|
|
2698
|
-
const
|
|
2699
|
-
// Validate: named tunnels require
|
|
2700
|
-
if (
|
|
2701
|
-
spinner.fail(`${tunnel.name}: Named tunnel '${
|
|
2696
|
+
const tunnelHostname = tunnel.hostname || globalConfig?.hostname;
|
|
2697
|
+
// tunnelName takes priority, subdomain as fallback for compatibility
|
|
2698
|
+
const namedTunnel = tunnel.tunnelName || tunnel.subdomain || globalConfig?.tunnelName;
|
|
2699
|
+
// Validate: named tunnels require hostname for public access
|
|
2700
|
+
if (namedTunnel && !tunnelHostname) {
|
|
2701
|
+
spinner.fail(`${tunnel.name}: Named tunnel '${namedTunnel}' requires hostname`);
|
|
2702
2702
|
console.log(chalk_1.default.gray(` Add to your config:`));
|
|
2703
|
-
console.log(chalk_1.default.white(`
|
|
2704
|
-
console.log(chalk_1.default.gray(` Or remove '
|
|
2703
|
+
console.log(chalk_1.default.white(` hostname: ${namedTunnel}.yourdomain.com`));
|
|
2704
|
+
console.log(chalk_1.default.gray(` Or remove 'tunnelName' for a quick tunnel with random URL`));
|
|
2705
2705
|
continue;
|
|
2706
2706
|
}
|
|
2707
2707
|
// Merge IP access config: tunnel-specific > global security > none
|
|
2708
2708
|
const { IpFilter } = await Promise.resolve().then(() => __importStar(require("../shared/ip-filter")));
|
|
2709
2709
|
const ipAccess = IpFilter.mergeConfigs(globalConfig?.security?.ipAccess, tunnel.ipAccess);
|
|
2710
2710
|
const client = new CloudflareTunnelClient_1.CloudflareTunnelClient({
|
|
2711
|
-
hostname:
|
|
2712
|
-
tunnelName:
|
|
2711
|
+
hostname: tunnelHostname,
|
|
2712
|
+
tunnelName: namedTunnel,
|
|
2713
2713
|
protocol: tunnel.protocol === "https" ? "https" : "http",
|
|
2714
2714
|
ipAccess,
|
|
2715
2715
|
});
|
|
2716
2716
|
try {
|
|
2717
2717
|
await client.connect();
|
|
2718
2718
|
// Auto-route DNS if using named tunnel with hostname
|
|
2719
|
-
if (
|
|
2720
|
-
const routeResult = await CloudflareTunnelClient_1.CloudflareTunnelClient.routeDns(
|
|
2719
|
+
if (namedTunnel && tunnelHostname) {
|
|
2720
|
+
const routeResult = await CloudflareTunnelClient_1.CloudflareTunnelClient.routeDns(namedTunnel, tunnelHostname);
|
|
2721
2721
|
if (!routeResult.success && !routeResult.error?.includes("already exists")) {
|
|
2722
2722
|
spinner.warn(`${tunnel.name}: DNS routing failed: ${routeResult.error}`);
|
|
2723
2723
|
}
|
|
@@ -2729,7 +2729,7 @@ async function startTunnelsFromConfig(tunnels, serverUrl, token, insecure, globa
|
|
|
2729
2729
|
});
|
|
2730
2730
|
activeTunnels.push({ name: tunnel.name, tunnelId, publicUrl, provider: "cloudflare", client });
|
|
2731
2731
|
clients.push({ provider: "cloudflare", client });
|
|
2732
|
-
const tunnelMode =
|
|
2732
|
+
const tunnelMode = namedTunnel ? ` [${namedTunnel}]` : "";
|
|
2733
2733
|
spinner.succeed(`${tunnel.name}: ${publicUrl}${tunnelMode} (cloudflare)`);
|
|
2734
2734
|
}
|
|
2735
2735
|
catch (err) {
|
|
@@ -3426,5 +3426,174 @@ configCommand
|
|
|
3426
3426
|
console.log(chalk_1.default.gray(" " + "─".repeat(40)));
|
|
3427
3427
|
console.log(chalk_1.default.gray(`\n Config file: ${credentials_1.CredentialsManager.getCredentialsFile()}\n`));
|
|
3428
3428
|
});
|
|
3429
|
+
// =============================================================================
|
|
3430
|
+
// Diagnose command - Generate troubleshooting report
|
|
3431
|
+
// =============================================================================
|
|
3432
|
+
program
|
|
3433
|
+
.command("diagnose")
|
|
3434
|
+
.description("Generate a diagnostic report for troubleshooting")
|
|
3435
|
+
.option("-o, --output <file>", "Output file name", "opentunnel-diagnostic.txt")
|
|
3436
|
+
.action(async (options) => {
|
|
3437
|
+
const os = await Promise.resolve().then(() => __importStar(require("os")));
|
|
3438
|
+
const { execSync } = await Promise.resolve().then(() => __importStar(require("child_process")));
|
|
3439
|
+
const lines = [];
|
|
3440
|
+
const addSection = (title) => {
|
|
3441
|
+
lines.push("");
|
|
3442
|
+
lines.push("=".repeat(60));
|
|
3443
|
+
lines.push(title);
|
|
3444
|
+
lines.push("=".repeat(60));
|
|
3445
|
+
};
|
|
3446
|
+
const addLine = (label, value) => {
|
|
3447
|
+
lines.push(`${label.padEnd(25)}: ${value}`);
|
|
3448
|
+
};
|
|
3449
|
+
const runCommand = (cmd) => {
|
|
3450
|
+
try {
|
|
3451
|
+
return execSync(cmd, { encoding: "utf-8", timeout: 5000 }).trim();
|
|
3452
|
+
}
|
|
3453
|
+
catch {
|
|
3454
|
+
return "(not available)";
|
|
3455
|
+
}
|
|
3456
|
+
};
|
|
3457
|
+
lines.push("OpenTunnel Diagnostic Report");
|
|
3458
|
+
lines.push(`Generated: ${new Date().toISOString()}`);
|
|
3459
|
+
// System Information
|
|
3460
|
+
addSection("SYSTEM INFORMATION");
|
|
3461
|
+
addLine("OpenTunnel Version", program.version() || "unknown");
|
|
3462
|
+
addLine("Node.js Version", process.version);
|
|
3463
|
+
addLine("Platform", `${os.platform()} (${os.arch()})`);
|
|
3464
|
+
addLine("OS Release", os.release());
|
|
3465
|
+
addLine("OS Type", os.type());
|
|
3466
|
+
addLine("Hostname", os.hostname());
|
|
3467
|
+
addLine("Working Directory", process.cwd());
|
|
3468
|
+
addLine("Home Directory", os.homedir());
|
|
3469
|
+
addLine("Total Memory", `${Math.round(os.totalmem() / 1024 / 1024 / 1024)} GB`);
|
|
3470
|
+
addLine("Free Memory", `${Math.round(os.freemem() / 1024 / 1024 / 1024)} GB`);
|
|
3471
|
+
// Provider Status
|
|
3472
|
+
addSection("PROVIDER STATUS");
|
|
3473
|
+
// Check cloudflared
|
|
3474
|
+
const cloudflaredVersion = runCommand("cloudflared --version");
|
|
3475
|
+
addLine("cloudflared", cloudflaredVersion.includes("cloudflared") ? cloudflaredVersion.split("\n")[0] : "(not installed)");
|
|
3476
|
+
// Check ngrok
|
|
3477
|
+
const ngrokVersion = runCommand("ngrok version");
|
|
3478
|
+
addLine("ngrok", ngrokVersion.includes("ngrok") ? ngrokVersion : "(not installed)");
|
|
3479
|
+
// Configuration
|
|
3480
|
+
addSection("CONFIGURATION");
|
|
3481
|
+
const configPath = path.join(process.cwd(), CONFIG_FILE);
|
|
3482
|
+
if (fs.existsSync(configPath)) {
|
|
3483
|
+
addLine("Config File", configPath);
|
|
3484
|
+
try {
|
|
3485
|
+
const config = loadConfig(configPath);
|
|
3486
|
+
if (config) {
|
|
3487
|
+
addLine("Provider", config.provider || "opentunnel");
|
|
3488
|
+
addLine("Mode", config.mode || "auto");
|
|
3489
|
+
addLine("Tunnel Name", config.tunnelName || "(not set)");
|
|
3490
|
+
addLine("Hostname", config.hostname || "(not set)");
|
|
3491
|
+
addLine("Region", config.region || "(not set)");
|
|
3492
|
+
addLine("Tunnels Count", config.tunnels?.length?.toString() || "0");
|
|
3493
|
+
if (config.tunnels) {
|
|
3494
|
+
lines.push("");
|
|
3495
|
+
lines.push("Configured Tunnels:");
|
|
3496
|
+
for (const t of config.tunnels) {
|
|
3497
|
+
lines.push(` - ${t.name}: ${t.protocol}://localhost:${t.port} (provider: ${t.provider || config.provider || "opentunnel"})`);
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
catch (err) {
|
|
3503
|
+
addLine("Config Parse Error", err.message);
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
else {
|
|
3507
|
+
addLine("Config File", "(not found)");
|
|
3508
|
+
}
|
|
3509
|
+
// Credentials
|
|
3510
|
+
addSection("CREDENTIALS STATUS");
|
|
3511
|
+
const credsFile = credentials_1.CredentialsManager.getCredentialsFile();
|
|
3512
|
+
addLine("Credentials File", fs.existsSync(credsFile) ? "exists" : "not found");
|
|
3513
|
+
try {
|
|
3514
|
+
const credsMgr = new credentials_1.CredentialsManager();
|
|
3515
|
+
const creds = credsMgr.getAll();
|
|
3516
|
+
addLine("ngrok Token", creds.ngrok?.token ? "configured" : "not set");
|
|
3517
|
+
addLine("Cloudflare Auth", creds.cloudflare ? "configured" : "not set");
|
|
3518
|
+
}
|
|
3519
|
+
catch {
|
|
3520
|
+
addLine("Credentials", "(error reading)");
|
|
3521
|
+
}
|
|
3522
|
+
// Running Instances
|
|
3523
|
+
addSection("RUNNING INSTANCES");
|
|
3524
|
+
const runDir = path.join(os.homedir(), ".opentunnel", "run");
|
|
3525
|
+
if (fs.existsSync(runDir)) {
|
|
3526
|
+
const pidFiles = fs.readdirSync(runDir).filter(f => f.endsWith(".pid"));
|
|
3527
|
+
if (pidFiles.length > 0) {
|
|
3528
|
+
for (const pidFile of pidFiles) {
|
|
3529
|
+
const name = pidFile.replace(".pid", "");
|
|
3530
|
+
const pidPath = path.join(runDir, pidFile);
|
|
3531
|
+
try {
|
|
3532
|
+
const pid = fs.readFileSync(pidPath, "utf-8").trim();
|
|
3533
|
+
addLine(`Instance: ${name}`, `PID ${pid}`);
|
|
3534
|
+
}
|
|
3535
|
+
catch {
|
|
3536
|
+
addLine(`Instance: ${name}`, "(error reading PID)");
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
else {
|
|
3541
|
+
lines.push("No running instances found");
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
else {
|
|
3545
|
+
lines.push("No running instances found");
|
|
3546
|
+
}
|
|
3547
|
+
// Recent Logs
|
|
3548
|
+
addSection("RECENT LOGS (last 50 lines)");
|
|
3549
|
+
const logsDir = path.join(os.homedir(), ".opentunnel", "logs");
|
|
3550
|
+
if (fs.existsSync(logsDir)) {
|
|
3551
|
+
const logFiles = fs.readdirSync(logsDir)
|
|
3552
|
+
.filter(f => f.endsWith(".log"))
|
|
3553
|
+
.map(f => ({ name: f, mtime: fs.statSync(path.join(logsDir, f)).mtime }))
|
|
3554
|
+
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
3555
|
+
if (logFiles.length > 0) {
|
|
3556
|
+
const latestLog = logFiles[0];
|
|
3557
|
+
lines.push(`Log file: ${latestLog.name}`);
|
|
3558
|
+
lines.push("-".repeat(40));
|
|
3559
|
+
try {
|
|
3560
|
+
const logContent = fs.readFileSync(path.join(logsDir, latestLog.name), "utf-8");
|
|
3561
|
+
const logLines = logContent.split("\n").slice(-50);
|
|
3562
|
+
lines.push(...logLines);
|
|
3563
|
+
}
|
|
3564
|
+
catch {
|
|
3565
|
+
lines.push("(error reading log file)");
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
else {
|
|
3569
|
+
lines.push("No log files found");
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
else {
|
|
3573
|
+
lines.push("No logs directory found");
|
|
3574
|
+
}
|
|
3575
|
+
// Environment Variables (relevant ones only)
|
|
3576
|
+
addSection("ENVIRONMENT");
|
|
3577
|
+
const relevantEnvVars = [
|
|
3578
|
+
"OPENTUNNEL_TOKEN",
|
|
3579
|
+
"NGROK_AUTHTOKEN",
|
|
3580
|
+
"CLOUDFLARE_TUNNEL_TOKEN",
|
|
3581
|
+
"TUNNEL_TOKEN",
|
|
3582
|
+
"HTTP_PROXY",
|
|
3583
|
+
"HTTPS_PROXY",
|
|
3584
|
+
"NO_PROXY"
|
|
3585
|
+
];
|
|
3586
|
+
for (const envVar of relevantEnvVars) {
|
|
3587
|
+
const value = process.env[envVar];
|
|
3588
|
+
addLine(envVar, value ? "(set)" : "(not set)");
|
|
3589
|
+
}
|
|
3590
|
+
// Write report
|
|
3591
|
+
const reportContent = lines.join("\n");
|
|
3592
|
+
const outputFile = options.output;
|
|
3593
|
+
fs.writeFileSync(outputFile, reportContent, "utf-8");
|
|
3594
|
+
console.log(chalk_1.default.green(`\n✓ Diagnostic report generated: ${outputFile}`));
|
|
3595
|
+
console.log(chalk_1.default.gray(`\nInclude this file when reporting issues at:`));
|
|
3596
|
+
console.log(chalk_1.default.cyan(" https://github.com/anthropics/opentunnel/issues\n"));
|
|
3597
|
+
});
|
|
3429
3598
|
program.parse();
|
|
3430
3599
|
//# sourceMappingURL=index.js.map
|