@rmbk/compeek 0.2.3 → 0.2.4
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/bin/compeek.mjs +101 -10
- package/package.json +1 -1
package/bin/compeek.mjs
CHANGED
|
@@ -137,6 +137,57 @@ function waitForHealth(host, port, timeout) {
|
|
|
137
137
|
});
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
function waitForTunnelUrls(host, port, timeout) {
|
|
141
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
142
|
+
let frame = 0;
|
|
143
|
+
let spinner;
|
|
144
|
+
|
|
145
|
+
if (isColorSupported) {
|
|
146
|
+
spinner = setInterval(() => {
|
|
147
|
+
process.stdout.write(`\r ${c.cyan}${frames[frame]}${c.reset} Waiting for tunnel URLs...`);
|
|
148
|
+
frame = (frame + 1) % frames.length;
|
|
149
|
+
}, 80);
|
|
150
|
+
} else {
|
|
151
|
+
console.log(' Waiting for tunnel URLs...');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return new Promise((resolve) => {
|
|
155
|
+
const start = Date.now();
|
|
156
|
+
const check = () => {
|
|
157
|
+
const req = http.get(`http://${host}:${port}/api/info`, { timeout: 2000 }, (res) => {
|
|
158
|
+
let body = '';
|
|
159
|
+
res.on('data', d => body += d);
|
|
160
|
+
res.on('end', () => {
|
|
161
|
+
try {
|
|
162
|
+
const data = JSON.parse(body);
|
|
163
|
+
if (data.tunnel?.apiUrl && data.tunnel?.vncUrl) {
|
|
164
|
+
if (spinner) {
|
|
165
|
+
clearInterval(spinner);
|
|
166
|
+
process.stdout.write(`\r ${c.green}✓${c.reset} Tunnels ready \n`);
|
|
167
|
+
}
|
|
168
|
+
return resolve(data.tunnel);
|
|
169
|
+
}
|
|
170
|
+
} catch { /* retry */ }
|
|
171
|
+
retry();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
req.on('error', retry);
|
|
175
|
+
req.on('timeout', () => { req.destroy(); retry(); });
|
|
176
|
+
};
|
|
177
|
+
const retry = () => {
|
|
178
|
+
if (Date.now() - start > timeout) {
|
|
179
|
+
if (spinner) {
|
|
180
|
+
clearInterval(spinner);
|
|
181
|
+
process.stdout.write(`\r ${c.yellow}!${c.reset} Tunnel timed out (local-only mode)\n`);
|
|
182
|
+
}
|
|
183
|
+
return resolve(null);
|
|
184
|
+
}
|
|
185
|
+
setTimeout(check, HEALTH_INTERVAL);
|
|
186
|
+
};
|
|
187
|
+
check();
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
140
191
|
function buildConnectionString(name, apiHost, apiPort, vncHost, vncPort, vncPassword) {
|
|
141
192
|
const config = { name, type: 'compeek', apiHost, apiPort, vncHost, vncPort };
|
|
142
193
|
if (vncPassword) config.vncPassword = vncPassword;
|
|
@@ -168,9 +219,15 @@ async function cmdStart(args) {
|
|
|
168
219
|
const apiPort = parseInt(flags['api-port']) || defaultApi;
|
|
169
220
|
const vncPort = parseInt(flags['vnc-port']) || defaultVnc;
|
|
170
221
|
const mode = flags.mode || 'full';
|
|
171
|
-
const vncPassword = flags.password || crypto.randomBytes(
|
|
222
|
+
const vncPassword = flags.password || crypto.randomBytes(24).toString('base64url').slice(0, 24);
|
|
172
223
|
const sessionName = name.replace(CONTAINER_PREFIX, '').replace(/^(\d+)$/, 'Desktop $1');
|
|
173
224
|
|
|
225
|
+
// Tunnel provider: cloudflare by default, --no-tunnel to disable, --tunnel <provider> to override
|
|
226
|
+
const tunnelProvider = flags['no-tunnel'] ? 'none'
|
|
227
|
+
: typeof flags.tunnel === 'string' ? flags.tunnel
|
|
228
|
+
: flags.tunnel === true ? 'cloudflare'
|
|
229
|
+
: 'cloudflare';
|
|
230
|
+
|
|
174
231
|
console.log('');
|
|
175
232
|
console.log(` ${c.bold}${c.cyan}compeek${c.reset}`);
|
|
176
233
|
console.log('');
|
|
@@ -191,7 +248,7 @@ async function cmdStart(args) {
|
|
|
191
248
|
`vnc:${c.white}${vncPort}${c.reset}`,
|
|
192
249
|
];
|
|
193
250
|
if (flags.persist) info.push(`${c.green}persist${c.reset}`);
|
|
194
|
-
if (
|
|
251
|
+
if (tunnelProvider !== 'none') info.push(`${c.yellow}${tunnelProvider}${c.reset}`);
|
|
195
252
|
|
|
196
253
|
console.log(` ${c.cyan}▸${c.reset} Starting ${c.bold}${name}${c.reset} ${c.dim}${info.join(' · ')}${c.reset}`);
|
|
197
254
|
|
|
@@ -208,7 +265,7 @@ async function cmdStart(args) {
|
|
|
208
265
|
`-e DESKTOP_MODE=${mode}`,
|
|
209
266
|
`-e COMPEEK_SESSION_NAME="${sessionName}"`,
|
|
210
267
|
`-e VNC_PASSWORD="${vncPassword}"`,
|
|
211
|
-
|
|
268
|
+
`-e TUNNEL_PROVIDER=${tunnelProvider}`,
|
|
212
269
|
flags.persist ? `-v ${name}-data:/home/compeek/data` : '',
|
|
213
270
|
`--security-opt seccomp=unconfined`,
|
|
214
271
|
IMAGE,
|
|
@@ -221,21 +278,47 @@ async function cmdStart(args) {
|
|
|
221
278
|
process.exit(1);
|
|
222
279
|
}
|
|
223
280
|
|
|
224
|
-
|
|
225
|
-
|
|
281
|
+
// Wait for tunnel URLs if tunneling is enabled
|
|
282
|
+
let tunnel = null;
|
|
283
|
+
if (tunnelProvider !== 'none' && mode !== 'headless') {
|
|
284
|
+
tunnel = await waitForTunnelUrls('localhost', apiPort, 30_000);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Build connection strings
|
|
288
|
+
const localConnStr = buildConnectionString(sessionName, 'localhost', apiPort, 'localhost', vncPort, vncPassword);
|
|
289
|
+
let tunnelConnStr = null;
|
|
290
|
+
let dashboardLink;
|
|
291
|
+
|
|
292
|
+
if (tunnel) {
|
|
293
|
+
const apiUrl = new URL(tunnel.apiUrl);
|
|
294
|
+
const vncUrl = new URL(tunnel.vncUrl);
|
|
295
|
+
tunnelConnStr = buildConnectionString(
|
|
296
|
+
sessionName,
|
|
297
|
+
apiUrl.hostname, 443,
|
|
298
|
+
vncUrl.hostname, 443,
|
|
299
|
+
vncPassword,
|
|
300
|
+
);
|
|
301
|
+
dashboardLink = `${DASHBOARD_URL}/#config=${tunnelConnStr}`;
|
|
302
|
+
} else {
|
|
303
|
+
dashboardLink = `${DASHBOARD_URL}/#config=${localConnStr}`;
|
|
304
|
+
}
|
|
226
305
|
|
|
227
306
|
console.log('');
|
|
228
307
|
console.log(` ${c.dim}──── Links ─────────────────────────────────────${c.reset}`);
|
|
229
308
|
console.log('');
|
|
230
309
|
console.log(` ${c.bold}Dashboard${c.reset} ${c.cyan}${dashboardLink}${c.reset}`);
|
|
231
|
-
|
|
310
|
+
if (tunnel) {
|
|
311
|
+
console.log(` ${c.dim}API${c.reset} ${tunnel.apiUrl}`);
|
|
312
|
+
console.log(` ${c.dim}noVNC${c.reset} ${tunnel.vncUrl}`);
|
|
313
|
+
}
|
|
314
|
+
console.log(` ${c.dim}Local API${c.reset} http://localhost:${apiPort}`);
|
|
232
315
|
if (mode !== 'headless') {
|
|
233
|
-
console.log(` ${c.dim}
|
|
316
|
+
console.log(` ${c.dim}Local VNC${c.reset} http://localhost:${vncPort}`);
|
|
234
317
|
console.log(` ${c.dim}Password${c.reset} ${vncPassword}`);
|
|
235
318
|
}
|
|
236
319
|
console.log('');
|
|
237
320
|
console.log(` ${c.dim}──── Connection string ──────────────────────────${c.reset}`);
|
|
238
|
-
console.log(` ${c.dim}${
|
|
321
|
+
console.log(` ${c.dim}${tunnelConnStr || localConnStr}${c.reset}`);
|
|
239
322
|
console.log('');
|
|
240
323
|
|
|
241
324
|
if (flags.open) {
|
|
@@ -348,8 +431,15 @@ function parseFlags(args) {
|
|
|
348
431
|
const arg = args[i];
|
|
349
432
|
if (arg.startsWith('--')) {
|
|
350
433
|
const key = arg.slice(2);
|
|
351
|
-
if (key === 'open' || key === 'no-pull' || key === 'persist' || key === 'tunnel') {
|
|
434
|
+
if (key === 'open' || key === 'no-pull' || key === 'persist' || key === 'no-tunnel') {
|
|
352
435
|
flags[key] = true;
|
|
436
|
+
} else if (key === 'tunnel') {
|
|
437
|
+
// --tunnel (default provider) or --tunnel cloudflare / --tunnel localtunnel
|
|
438
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
|
|
439
|
+
flags[key] = args[++i];
|
|
440
|
+
} else {
|
|
441
|
+
flags[key] = true;
|
|
442
|
+
}
|
|
353
443
|
} else if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
|
|
354
444
|
flags[key] = args[++i];
|
|
355
445
|
}
|
|
@@ -398,7 +488,8 @@ switch (command) {
|
|
|
398
488
|
--mode ${c.dim}<m>${c.reset} ${c.dim}......${c.reset} full ${c.dim}|${c.reset} browser ${c.dim}|${c.reset} minimal ${c.dim}|${c.reset} headless
|
|
399
489
|
--persist ${c.dim}.......${c.reset} Mount volume for persistent data
|
|
400
490
|
--password ${c.dim}<pw>${c.reset} ${c.dim}.${c.reset} Custom VNC password ${c.dim}(auto-generated if omitted)${c.reset}
|
|
401
|
-
--tunnel ${c.dim}
|
|
491
|
+
--no-tunnel ${c.dim}.....${c.reset} Disable tunneling ${c.dim}(local-only mode)${c.reset}
|
|
492
|
+
--tunnel ${c.dim}<p>${c.reset} ${c.dim}.....${c.reset} cloudflare ${c.dim}(default)${c.reset} ${c.dim}|${c.reset} localtunnel
|
|
402
493
|
--no-pull ${c.dim}.......${c.reset} Skip pulling latest Docker image
|
|
403
494
|
--name ${c.dim}<n>${c.reset} ${c.dim}......${c.reset} Custom container name
|
|
404
495
|
--api-port ${c.dim}<p>${c.reset} ${c.dim}.${c.reset} Host port for tool API
|
package/package.json
CHANGED