termbeam 1.14.4 → 1.14.5
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 +2 -1
- package/package.json +1 -1
- package/src/cli/index.js +18 -16
- package/src/cli/interactive.js +14 -15
- package/src/cli/service.js +12 -13
- package/src/server/index.js +4 -29
- package/src/tunnel/index.js +10 -10
package/README.md
CHANGED
|
@@ -109,7 +109,8 @@ flowchart LR
|
|
|
109
109
|
| `--port <port>` | Server port | `3456` |
|
|
110
110
|
| `--host <addr>` | Bind address | `127.0.0.1` |
|
|
111
111
|
| `--lan` | Bind to all interfaces (LAN access) | Off |
|
|
112
|
-
| `--public` |
|
|
112
|
+
| `--public` | Public tunnel access (default) | On |
|
|
113
|
+
| `--private` | Require Microsoft login for tunnel access | Off |
|
|
113
114
|
| `-i, --interactive` | Interactive setup wizard | Off |
|
|
114
115
|
| `--log-level <level>` | Log verbosity (error/warn/info/debug) | `info` |
|
|
115
116
|
|
package/package.json
CHANGED
package/src/cli/index.js
CHANGED
|
@@ -25,10 +25,10 @@ Options:
|
|
|
25
25
|
--password <pw> Set access password (or TERMBEAM_PASSWORD env var)
|
|
26
26
|
--generate-password Auto-generate a secure password (default: auto)
|
|
27
27
|
--no-password Disable password authentication
|
|
28
|
-
--tunnel Create a devtunnel URL (default: on,
|
|
28
|
+
--tunnel Create a devtunnel URL (default: on, public access)
|
|
29
29
|
--no-tunnel Disable tunnel (LAN-only mode)
|
|
30
30
|
--persisted-tunnel Create a reusable devtunnel URL (stable across restarts)
|
|
31
|
-
--
|
|
31
|
+
--private Require Microsoft login to access the tunnel (owner-only)
|
|
32
32
|
--port <port> Set port (default: 3456, or PORT env var)
|
|
33
33
|
--host <addr> Bind address (default: 127.0.0.1)
|
|
34
34
|
--lan Bind to 0.0.0.0 (allow LAN access, default: localhost only)
|
|
@@ -40,9 +40,9 @@ Options:
|
|
|
40
40
|
|
|
41
41
|
Defaults:
|
|
42
42
|
By default, TermBeam enables tunnel + auto-generated password for secure
|
|
43
|
-
mobile access (clipboard, HTTPS). Tunnels are
|
|
44
|
-
|
|
45
|
-
--no-tunnel for LAN-only mode.
|
|
43
|
+
mobile access (clipboard, HTTPS). Tunnels are public (anonymous) — TermBeam's
|
|
44
|
+
own password auth protects your terminal. Use --private for owner-only access
|
|
45
|
+
via Microsoft login, or --no-tunnel for LAN-only mode.
|
|
46
46
|
|
|
47
47
|
Examples:
|
|
48
48
|
termbeam Start with tunnel + auto password
|
|
@@ -249,7 +249,7 @@ function parseArgs() {
|
|
|
249
249
|
let useTunnel = true;
|
|
250
250
|
let noTunnel = false;
|
|
251
251
|
let persistedTunnel = false;
|
|
252
|
-
let
|
|
252
|
+
let privateTunnel = false;
|
|
253
253
|
let interactive = false;
|
|
254
254
|
let force = false;
|
|
255
255
|
let explicitPassword = !!password;
|
|
@@ -268,8 +268,10 @@ function parseArgs() {
|
|
|
268
268
|
} else if (args[i] === '--persisted-tunnel') {
|
|
269
269
|
useTunnel = true;
|
|
270
270
|
persistedTunnel = true;
|
|
271
|
+
} else if (args[i] === '--private') {
|
|
272
|
+
privateTunnel = true;
|
|
271
273
|
} else if (args[i] === '--public') {
|
|
272
|
-
|
|
274
|
+
// no-op: public is now the default; kept for backwards compatibility
|
|
273
275
|
} else if (args[i].startsWith('--password=')) {
|
|
274
276
|
password = args[i].split('=')[1];
|
|
275
277
|
if (!password) {
|
|
@@ -332,24 +334,24 @@ function parseArgs() {
|
|
|
332
334
|
// --no-tunnel disables the default tunnel
|
|
333
335
|
if (noTunnel) useTunnel = false;
|
|
334
336
|
|
|
335
|
-
// --
|
|
336
|
-
if (
|
|
337
|
+
// --private requires a tunnel
|
|
338
|
+
if (privateTunnel && !useTunnel) {
|
|
337
339
|
const rd = '\x1b[31m';
|
|
338
340
|
const rs = '\x1b[0m';
|
|
339
|
-
log.error('--
|
|
341
|
+
log.error('--private requires a tunnel');
|
|
340
342
|
console.error(
|
|
341
|
-
`${rd}Error: --
|
|
343
|
+
`${rd}Error: --private requires a tunnel. Remove --no-tunnel or remove --private.${rs}`,
|
|
342
344
|
);
|
|
343
345
|
process.exit(1);
|
|
344
346
|
}
|
|
345
347
|
|
|
346
|
-
//
|
|
347
|
-
if (
|
|
348
|
+
// Public tunnels (default) require password authentication
|
|
349
|
+
if (useTunnel && !privateTunnel && !password) {
|
|
348
350
|
const rd = '\x1b[31m';
|
|
349
351
|
const rs = '\x1b[0m';
|
|
350
352
|
log.error('Public tunnels require password authentication');
|
|
351
353
|
console.error(
|
|
352
|
-
`${rd}Error: Public tunnels require password authentication. Remove --no-password or
|
|
354
|
+
`${rd}Error: Public tunnels require password authentication. Remove --no-password or add --private.${rs}`,
|
|
353
355
|
);
|
|
354
356
|
process.exit(1);
|
|
355
357
|
}
|
|
@@ -366,7 +368,7 @@ function parseArgs() {
|
|
|
366
368
|
password,
|
|
367
369
|
useTunnel,
|
|
368
370
|
persistedTunnel,
|
|
369
|
-
|
|
371
|
+
privateTunnel,
|
|
370
372
|
shell,
|
|
371
373
|
shellArgs,
|
|
372
374
|
cwd,
|
|
@@ -378,7 +380,7 @@ function parseArgs() {
|
|
|
378
380
|
};
|
|
379
381
|
|
|
380
382
|
log.debug(
|
|
381
|
-
`Config: port=${port}, host=${host}, shell=${shell}, tunnel=${useTunnel}, persisted=${persistedTunnel},
|
|
383
|
+
`Config: port=${port}, host=${host}, shell=${shell}, tunnel=${useTunnel}, persisted=${persistedTunnel}, private=${privateTunnel}`,
|
|
382
384
|
);
|
|
383
385
|
|
|
384
386
|
return config;
|
package/src/cli/interactive.js
CHANGED
|
@@ -58,7 +58,7 @@ async function runInteractiveSetup(baseConfig) {
|
|
|
58
58
|
password: baseConfig.password,
|
|
59
59
|
useTunnel: baseConfig.useTunnel,
|
|
60
60
|
persistedTunnel: baseConfig.persistedTunnel,
|
|
61
|
-
|
|
61
|
+
privateTunnel: baseConfig.privateTunnel,
|
|
62
62
|
shell: baseConfig.shell,
|
|
63
63
|
shellArgs: baseConfig.shellArgs,
|
|
64
64
|
cwd: baseConfig.cwd,
|
|
@@ -153,19 +153,18 @@ async function runInteractiveSetup(baseConfig) {
|
|
|
153
153
|
showProgress(2);
|
|
154
154
|
const publicChoice = await choose(rl, 'Tunnel access:', [
|
|
155
155
|
{
|
|
156
|
-
label: '
|
|
157
|
-
hint: '
|
|
156
|
+
label: 'Public',
|
|
157
|
+
hint: 'Anonymous access — TermBeam password protects your terminal (recommended)',
|
|
158
158
|
},
|
|
159
159
|
{
|
|
160
|
-
label: '
|
|
161
|
-
hint: '
|
|
162
|
-
danger: true,
|
|
160
|
+
label: 'Private (owner-only)',
|
|
161
|
+
hint: 'Requires Microsoft login to reach the tunnel — adds extra auth layer',
|
|
163
162
|
},
|
|
164
163
|
]);
|
|
165
|
-
config.
|
|
164
|
+
config.privateTunnel = publicChoice.index === 1;
|
|
166
165
|
|
|
167
166
|
// Auto-generate password if public tunnel with no password
|
|
168
|
-
if (config.
|
|
167
|
+
if (!config.privateTunnel && !config.password) {
|
|
169
168
|
console.log(yellow(' ⚠ Public tunnels require password authentication.'));
|
|
170
169
|
config.password = crypto.randomBytes(16).toString('base64url');
|
|
171
170
|
process.stdout.write(dim(` Auto-generated password: ${config.password}`) + '\n');
|
|
@@ -178,22 +177,22 @@ async function runInteractiveSetup(baseConfig) {
|
|
|
178
177
|
config.host = '0.0.0.0';
|
|
179
178
|
config.useTunnel = false;
|
|
180
179
|
config.persistedTunnel = false;
|
|
181
|
-
config.
|
|
180
|
+
config.privateTunnel = false;
|
|
182
181
|
} else {
|
|
183
182
|
// Localhost only
|
|
184
183
|
config.host = '127.0.0.1';
|
|
185
184
|
config.useTunnel = false;
|
|
186
185
|
config.persistedTunnel = false;
|
|
187
|
-
config.
|
|
186
|
+
config.privateTunnel = false;
|
|
188
187
|
}
|
|
189
188
|
|
|
190
189
|
const accessLabel = !config.useTunnel
|
|
191
190
|
? config.host === '0.0.0.0'
|
|
192
191
|
? 'LAN (0.0.0.0)'
|
|
193
192
|
: 'Localhost only'
|
|
194
|
-
: config.
|
|
195
|
-
? 'DevTunnel (
|
|
196
|
-
: 'DevTunnel (
|
|
193
|
+
: config.privateTunnel
|
|
194
|
+
? 'DevTunnel (private)'
|
|
195
|
+
: 'DevTunnel (public)';
|
|
197
196
|
log.debug(`Access mode selected: ${accessLabel}`);
|
|
198
197
|
decisions.push({ label: 'Access', value: accessLabel });
|
|
199
198
|
|
|
@@ -226,7 +225,7 @@ async function runInteractiveSetup(baseConfig) {
|
|
|
226
225
|
console.log(` Tunnel: ${config.useTunnel ? cyan('enabled') : yellow('disabled')}`);
|
|
227
226
|
if (config.useTunnel) {
|
|
228
227
|
console.log(` Persisted: ${config.persistedTunnel ? cyan('yes') : dim('no')}`);
|
|
229
|
-
console.log(` Public: ${config.
|
|
228
|
+
console.log(` Public: ${config.privateTunnel ? dim('no (private)') : cyan('yes')}`);
|
|
230
229
|
}
|
|
231
230
|
console.log(` Shell: ${cyan(config.shell || 'default')}`);
|
|
232
231
|
console.log(` Directory: ${cyan(config.cwd)}`);
|
|
@@ -247,7 +246,7 @@ async function runInteractiveSetup(baseConfig) {
|
|
|
247
246
|
if (config.host === '0.0.0.0') cmdParts.push('--lan');
|
|
248
247
|
} else {
|
|
249
248
|
if (config.persistedTunnel) cmdParts.push('--persisted-tunnel');
|
|
250
|
-
if (config.
|
|
249
|
+
if (config.privateTunnel) cmdParts.push('--private');
|
|
251
250
|
}
|
|
252
251
|
if (config.logLevel !== 'info') cmdParts.push('--log-level', config.logLevel);
|
|
253
252
|
const cliCommand = cmdParts.join(' ');
|
package/src/cli/service.js
CHANGED
|
@@ -80,8 +80,8 @@ function buildArgs(config) {
|
|
|
80
80
|
if (config.persistedTunnel) {
|
|
81
81
|
args.push('--persisted-tunnel');
|
|
82
82
|
}
|
|
83
|
-
if (config.
|
|
84
|
-
args.push('--
|
|
83
|
+
if (config.privateTunnel) {
|
|
84
|
+
args.push('--private');
|
|
85
85
|
}
|
|
86
86
|
if (config.logLevel && config.logLevel !== 'info') {
|
|
87
87
|
args.push('--log-level', config.logLevel);
|
|
@@ -291,17 +291,16 @@ async function actionInstall() {
|
|
|
291
291
|
showProgress(3);
|
|
292
292
|
const publicChoice = await choose(rl, 'Tunnel access:', [
|
|
293
293
|
{
|
|
294
|
-
label: '
|
|
295
|
-
hint: '
|
|
294
|
+
label: 'Public (anyone with the link)',
|
|
295
|
+
hint: 'Anonymous access — TermBeam password protects your terminal (recommended)',
|
|
296
296
|
},
|
|
297
297
|
{
|
|
298
|
-
label: '
|
|
299
|
-
hint: '
|
|
300
|
-
danger: true,
|
|
298
|
+
label: 'Private (requires Microsoft login)',
|
|
299
|
+
hint: 'Only you can access the tunnel — secured via your Microsoft account',
|
|
301
300
|
},
|
|
302
301
|
]);
|
|
303
|
-
config.
|
|
304
|
-
if (config.
|
|
302
|
+
config.privateTunnel = publicChoice.index === 1;
|
|
303
|
+
if (!config.privateTunnel && config.password === false) {
|
|
305
304
|
console.log(yellow(' ⚠ Public tunnels require password authentication.'));
|
|
306
305
|
config.password = crypto.randomBytes(16).toString('base64url');
|
|
307
306
|
process.stdout.write(dim(` Auto-generated password: ${config.password}`) + '\n');
|
|
@@ -319,9 +318,9 @@ async function actionInstall() {
|
|
|
319
318
|
? config.lan
|
|
320
319
|
? 'LAN (0.0.0.0)'
|
|
321
320
|
: 'Localhost only'
|
|
322
|
-
: config.
|
|
323
|
-
? 'DevTunnel (
|
|
324
|
-
: 'DevTunnel (
|
|
321
|
+
: config.privateTunnel
|
|
322
|
+
? 'DevTunnel (private)'
|
|
323
|
+
: 'DevTunnel (public)';
|
|
325
324
|
decisions.push({ label: 'Access', value: accessLabel });
|
|
326
325
|
|
|
327
326
|
// Working directory
|
|
@@ -368,7 +367,7 @@ async function actionInstall() {
|
|
|
368
367
|
console.log(` Tunnel: ${config.noTunnel ? yellow('disabled') : cyan('enabled')}`);
|
|
369
368
|
if (!config.noTunnel) {
|
|
370
369
|
console.log(` Persisted: ${config.persistedTunnel ? cyan('yes') : dim('no')}`);
|
|
371
|
-
console.log(` Public: ${config.
|
|
370
|
+
console.log(` Public: ${config.privateTunnel ? yellow('no (private)') : dim('yes')}`);
|
|
372
371
|
}
|
|
373
372
|
console.log(` Directory: ${cyan(config.cwd)}`);
|
|
374
373
|
console.log(` Shell: ${cyan(config.shell || 'default')}`);
|
package/src/server/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const os = require('os');
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const readline = require('readline');
|
|
5
4
|
const express = require('express');
|
|
6
5
|
const cookieParser = require('cookie-parser');
|
|
7
6
|
const http = require('http');
|
|
@@ -18,7 +17,6 @@ const { createPreviewProxy } = require('./preview');
|
|
|
18
17
|
const { writeConnectionConfig, removeConnectionConfig } = require('../cli/resume');
|
|
19
18
|
const { checkForUpdate, detectInstallMethod } = require('../utils/update-check');
|
|
20
19
|
|
|
21
|
-
// --- Helpers ---
|
|
22
20
|
function getLocalIP() {
|
|
23
21
|
const interfaces = os.networkInterfaces();
|
|
24
22
|
for (const name of Object.keys(interfaces)) {
|
|
@@ -29,16 +27,6 @@ function getLocalIP() {
|
|
|
29
27
|
return '127.0.0.1';
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
function confirmPublicTunnel() {
|
|
33
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
34
|
-
return new Promise((resolve) => {
|
|
35
|
-
rl.question(' Do you want to continue with public access? (y/N): ', (answer) => {
|
|
36
|
-
rl.close();
|
|
37
|
-
resolve(answer.trim().toLowerCase() === 'y');
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
30
|
/**
|
|
43
31
|
* Create a TermBeam server instance without starting it.
|
|
44
32
|
* @param {object} [overrides] - Optional overrides
|
|
@@ -140,26 +128,13 @@ function createTermBeamServer(overrides = {}) {
|
|
|
140
128
|
}
|
|
141
129
|
}
|
|
142
130
|
|
|
143
|
-
// Warn
|
|
144
|
-
if (config.useTunnel && config.
|
|
145
|
-
const rd = '\x1b[31m';
|
|
131
|
+
// Warn about private tunnel access when --private flag is used
|
|
132
|
+
if (config.useTunnel && config.privateTunnel) {
|
|
146
133
|
const yl = '\x1b[33m';
|
|
147
134
|
const rs = '\x1b[0m';
|
|
148
|
-
const bd = '\x1b[1m';
|
|
149
|
-
console.log('');
|
|
150
|
-
console.log(` ${rd}${bd}⚠️ DANGER: Public tunnel access requested${rs}`);
|
|
151
135
|
console.log('');
|
|
152
|
-
console.log(` ${yl}
|
|
153
|
-
console.log(` ${yl}No Microsoft login will be required to reach the tunnel.${rs}`);
|
|
154
|
-
console.log(` ${yl}Only the TermBeam password will protect your terminal.${rs}`);
|
|
136
|
+
console.log(` ${yl}ℹ️ Private tunnel: Microsoft login required to access the tunnel.${rs}`);
|
|
155
137
|
console.log('');
|
|
156
|
-
const confirmed = await confirmPublicTunnel();
|
|
157
|
-
if (!confirmed) {
|
|
158
|
-
console.log('');
|
|
159
|
-
console.log(' Aborted. Restart without --public for private access.');
|
|
160
|
-
console.log('');
|
|
161
|
-
process.exit(1);
|
|
162
|
-
}
|
|
163
138
|
}
|
|
164
139
|
|
|
165
140
|
return new Promise((resolve) => {
|
|
@@ -229,7 +204,7 @@ function createTermBeamServer(overrides = {}) {
|
|
|
229
204
|
if (config.useTunnel) {
|
|
230
205
|
const tunnel = await startTunnel(actualPort, {
|
|
231
206
|
persisted: config.persistedTunnel,
|
|
232
|
-
|
|
207
|
+
private: config.privateTunnel,
|
|
233
208
|
});
|
|
234
209
|
if (tunnel) {
|
|
235
210
|
publicUrl = tunnel.url;
|
package/src/tunnel/index.js
CHANGED
|
@@ -234,8 +234,16 @@ async function startTunnel(port, options = {}) {
|
|
|
234
234
|
{ stdio: 'pipe' },
|
|
235
235
|
);
|
|
236
236
|
} catch {}
|
|
237
|
-
// Set tunnel access: public (anonymous) or private (owner-only via Microsoft login)
|
|
238
|
-
if (options.
|
|
237
|
+
// Set tunnel access: public (anonymous, default) or private (owner-only via Microsoft login)
|
|
238
|
+
if (options.private) {
|
|
239
|
+
// Remove any existing anonymous access to ensure the tunnel is private
|
|
240
|
+
try {
|
|
241
|
+
execFileSync(devtunnelCmd, ['access', 'reset', tunnelId], {
|
|
242
|
+
stdio: 'pipe',
|
|
243
|
+
});
|
|
244
|
+
} catch {}
|
|
245
|
+
log.info('Tunnel access: private (owner-only via Microsoft login)');
|
|
246
|
+
} else {
|
|
239
247
|
try {
|
|
240
248
|
execFileSync(
|
|
241
249
|
devtunnelCmd,
|
|
@@ -244,14 +252,6 @@ async function startTunnel(port, options = {}) {
|
|
|
244
252
|
);
|
|
245
253
|
} catch {}
|
|
246
254
|
log.info('Tunnel access: public (anonymous)');
|
|
247
|
-
} else {
|
|
248
|
-
// Remove any existing anonymous access to ensure the tunnel is private
|
|
249
|
-
try {
|
|
250
|
-
execFileSync(devtunnelCmd, ['access', 'reset', tunnelId], {
|
|
251
|
-
stdio: 'pipe',
|
|
252
|
-
});
|
|
253
|
-
} catch {}
|
|
254
|
-
log.info('Tunnel access: private (owner-only via Microsoft login)');
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
const hostProc = spawn(devtunnelCmd, ['host', tunnelId], {
|