opentunnel-cli 1.0.29 → 1.0.31

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 CHANGED
@@ -18,6 +18,7 @@
18
18
  - [Server Mode](#server-mode-config)
19
19
  - [Hybrid Mode](#hybrid-mode-config)
20
20
  - [CLI Commands](#cli-commands)
21
+ - [Streaming Large Files / Video](#streaming-large-files--video)
21
22
  - [Documentation](#documentation)
22
23
  - [Architecture](#architecture)
23
24
  - [Troubleshooting](#troubleshooting-quick)
@@ -144,6 +145,13 @@ tunnels:
144
145
  protocol: http
145
146
  port: 4000
146
147
  subdomain: api
148
+
149
+ - name: media
150
+ protocol: http
151
+ port: 8080
152
+ subdomain: media
153
+ streaming: true # Enable streaming mode (10 min timeout)
154
+ # requestTimeout: 0 # Or no timeout (use with caution)
147
155
  ```
148
156
 
149
157
  ### Server Mode Config
@@ -219,6 +227,42 @@ tunnels:
219
227
  - `-t, --token <token>` - Auth token
220
228
  - `-n, --name <name>` - Custom subdomain
221
229
  - `--insecure` - Skip SSL verification
230
+ - `--streaming` - Enable streaming mode (10 min timeout for video/large files)
231
+ - `--timeout <ms>` - Custom request timeout in milliseconds (0 = no timeout)
232
+
233
+ ---
234
+
235
+ ## Streaming Large Files / Video
236
+
237
+ For tunneling video streams or large file downloads, use the streaming options:
238
+
239
+ ```bash
240
+ # Enable streaming mode (10 minute timeout)
241
+ opentunnel http 8080 -s example.com --streaming
242
+
243
+ # Custom timeout (5 minutes = 300000ms)
244
+ opentunnel http 8080 -s example.com --timeout 300000
245
+
246
+ # No timeout (use with caution - high memory usage)
247
+ opentunnel http 8080 -s example.com --timeout 0
248
+ ```
249
+
250
+ **In configuration file:**
251
+
252
+ ```yaml
253
+ tunnels:
254
+ - name: media-server
255
+ protocol: http
256
+ port: 8080
257
+ subdomain: media
258
+ streaming: true # 10 min timeout
259
+ # requestTimeout: 300000 # Or custom timeout in ms
260
+ ```
261
+
262
+ > **Note:** HTTP tunnels buffer the entire response in memory before forwarding. For very large files (>500MB), consider using TCP tunnels which stream byte-by-byte:
263
+ > ```bash
264
+ > opentunnel tcp 8080 -s example.com
265
+ > ```
222
266
 
223
267
  ---
224
268
 
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.29");
241
+ .version("1.0.31");
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
@@ -279,6 +279,8 @@ program
279
279
  .option("-h, --host <host>", "Local host to forward to", "localhost")
280
280
  .option("-t, --token <token>", "Authentication token (if server requires it)")
281
281
  .option("--insecure", "Skip SSL certificate verification (for self-signed certs)")
282
+ .option("--streaming", "Enable streaming mode (longer timeouts for video/large files)")
283
+ .option("--timeout <ms>", "Custom request timeout in milliseconds")
282
284
  .option("--local-server", "Start a local server before connecting")
283
285
  .option("--server-port <port>", "Port for the local server (default: 443)", "443")
284
286
  .action(async (port, options) => {
@@ -377,6 +379,8 @@ program
377
379
  localHost: options.host,
378
380
  localPort: parseInt(port),
379
381
  subdomain: options.subdomain,
382
+ streaming: options.streaming || false,
383
+ requestTimeout: options.timeout ? parseInt(options.timeout) : undefined,
380
384
  });
381
385
  return { client, tunnelId, publicUrl };
382
386
  };
@@ -579,6 +583,8 @@ program
579
583
  localHost: options.host,
580
584
  localPort: parseInt(port),
581
585
  subdomain: options.subdomain,
586
+ streaming: options.streaming || false,
587
+ requestTimeout: options.timeout ? parseInt(options.timeout) : undefined,
582
588
  });
583
589
  spinner.succeed("Tunnel established!");
584
590
  console.log("");
@@ -642,6 +648,8 @@ program
642
648
  .option("--server-port <port>", "Server port", "443")
643
649
  .option("--https", "Use HTTPS for local connection")
644
650
  .option("--insecure", "Skip SSL verification (for self-signed certs)")
651
+ .option("--streaming", "Enable streaming mode (longer timeouts for video/large files)")
652
+ .option("--timeout <ms>", "Custom request timeout in milliseconds")
645
653
  .option("--ngrok", "Use ngrok instead of OpenTunnel server")
646
654
  .option("--region <region>", "Ngrok region (us, eu, ap, au, sa, jp, in)", "us")
647
655
  .option("--cloudflare, --cf", "Use Cloudflare Tunnel instead of OpenTunnel server")
@@ -687,6 +695,8 @@ program
687
695
  serverUrl,
688
696
  token: options.token,
689
697
  insecure: options.insecure,
698
+ streaming: options.streaming,
699
+ requestTimeout: options.timeout ? parseInt(options.timeout) : undefined,
690
700
  });
691
701
  return;
692
702
  }
@@ -1809,7 +1819,15 @@ program
1809
1819
  const isClientMode = mode === "client";
1810
1820
  const isServerMode = mode === "server";
1811
1821
  const isHybridMode = mode === "hybrid";
1812
- if (!hasDomainConfig && !remote) {
1822
+ // Check if using external provider (cloudflare/ngrok) which doesn't need domain/remote
1823
+ const globalProvider = config.provider || "opentunnel";
1824
+ const isExternalProvider = globalProvider === "cloudflare" || globalProvider === "ngrok";
1825
+ // Also check if all tunnels use external providers
1826
+ const allTunnelsExternal = hasTunnels && tunnelsToStart.every(t => {
1827
+ const provider = t.provider || globalProvider;
1828
+ return provider === "cloudflare" || provider === "ngrok";
1829
+ });
1830
+ if (!hasDomainConfig && !remote && !isExternalProvider && !allTunnelsExternal) {
1813
1831
  console.log(chalk_1.default.red("Missing configuration."));
1814
1832
  console.log(chalk_1.default.gray("\nAdd to your config:"));
1815
1833
  console.log(chalk_1.default.cyan("\n # Run your own server:"));
@@ -1818,8 +1836,33 @@ program
1818
1836
  console.log(chalk_1.default.cyan("\n # Or connect to a remote server:"));
1819
1837
  console.log(chalk_1.default.white(" server:"));
1820
1838
  console.log(chalk_1.default.white(" remote: example.com"));
1839
+ console.log(chalk_1.default.cyan("\n # Or use an external provider:"));
1840
+ console.log(chalk_1.default.white(" provider: cloudflare # or ngrok"));
1821
1841
  process.exit(1);
1822
1842
  }
1843
+ // External provider mode: skip OpenTunnel server, use cloudflare/ngrok directly
1844
+ if ((isExternalProvider || allTunnelsExternal) && !hasDomainConfig && !remote) {
1845
+ if (!hasTunnels) {
1846
+ console.log(chalk_1.default.red("No tunnels configured."));
1847
+ console.log(chalk_1.default.gray("\nAdd tunnels to your config:"));
1848
+ console.log(chalk_1.default.white(" tunnels:"));
1849
+ console.log(chalk_1.default.white(" - name: web"));
1850
+ console.log(chalk_1.default.white(" protocol: http"));
1851
+ console.log(chalk_1.default.white(" port: 80"));
1852
+ process.exit(1);
1853
+ }
1854
+ console.log(chalk_1.default.cyan(`Starting ${tunnelsToStart.length} tunnel(s) via ${globalProvider}...\n`));
1855
+ try {
1856
+ await startTunnelsFromConfig(tunnelsToStart, "", undefined, true, config);
1857
+ console.log(chalk_1.default.gray("\nPress Ctrl+C to stop"));
1858
+ // Keep running
1859
+ await new Promise(() => { });
1860
+ }
1861
+ catch (error) {
1862
+ console.log(chalk_1.default.red(`Failed to start tunnels: ${error.message}`));
1863
+ process.exit(1);
1864
+ }
1865
+ }
1823
1866
  if (isClientMode) {
1824
1867
  // CLIENT MODE: Connect to remote server
1825
1868
  // Build URL using basePath (default: op) -> wss://op.example.com/_tunnel
@@ -2651,8 +2694,16 @@ async function startTunnelsFromConfig(tunnels, serverUrl, token, insecure, globa
2651
2694
  for (const tunnel of cloudflareTunnels) {
2652
2695
  const spinner = (0, ora_1.default)(`Creating Cloudflare tunnel: ${tunnel.name}...`).start();
2653
2696
  const cfHostname = tunnel.cfHostname || globalConfig?.cloudflare?.hostname;
2654
- // For Cloudflare: subdomain = tunnel name
2697
+ // For Cloudflare: subdomain = tunnel name (for named tunnels)
2655
2698
  const cfTunnelName = tunnel.subdomain || globalConfig?.cloudflare?.tunnelName;
2699
+ // Validate: named tunnels require cfHostname for public access
2700
+ if (cfTunnelName && !cfHostname) {
2701
+ spinner.fail(`${tunnel.name}: Named tunnel '${cfTunnelName}' requires cfHostname`);
2702
+ console.log(chalk_1.default.gray(` Add to your config:`));
2703
+ console.log(chalk_1.default.white(` cfHostname: ${cfTunnelName}.yourdomain.com`));
2704
+ console.log(chalk_1.default.gray(` Or remove 'subdomain' for a quick tunnel with random URL`));
2705
+ continue;
2706
+ }
2656
2707
  // Merge IP access config: tunnel-specific > global security > none
2657
2708
  const { IpFilter } = await Promise.resolve().then(() => __importStar(require("../shared/ip-filter")));
2658
2709
  const ipAccess = IpFilter.mergeConfigs(globalConfig?.security?.ipAccess, tunnel.ipAccess);
@@ -2664,6 +2715,13 @@ async function startTunnelsFromConfig(tunnels, serverUrl, token, insecure, globa
2664
2715
  });
2665
2716
  try {
2666
2717
  await client.connect();
2718
+ // Auto-route DNS if using named tunnel with hostname
2719
+ if (cfTunnelName && cfHostname) {
2720
+ const routeResult = await CloudflareTunnelClient_1.CloudflareTunnelClient.routeDns(cfTunnelName, cfHostname);
2721
+ if (!routeResult.success && !routeResult.error?.includes("already exists")) {
2722
+ spinner.warn(`${tunnel.name}: DNS routing failed: ${routeResult.error}`);
2723
+ }
2724
+ }
2667
2725
  const { tunnelId, publicUrl } = await client.createTunnel({
2668
2726
  protocol: tunnel.protocol,
2669
2727
  localHost: tunnel.host || "localhost",
@@ -2823,6 +2881,8 @@ async function createTunnel(options) {
2823
2881
  localPort: options.localPort,
2824
2882
  subdomain: options.subdomain,
2825
2883
  remotePort: options.remotePort,
2884
+ streaming: options.streaming,
2885
+ requestTimeout: options.requestTimeout,
2826
2886
  });
2827
2887
  spinner.succeed("Tunnel established!");
2828
2888
  printTunnelInfo({