genbox 1.0.58 → 1.0.59
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/commands/status.js +161 -1
- package/package.json +1 -1
package/dist/commands/status.js
CHANGED
|
@@ -145,10 +145,55 @@ function displayTimingBreakdown(timing) {
|
|
|
145
145
|
console.log('');
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
|
+
// Watch mode: tail cloud-init logs in realtime
|
|
149
|
+
function tailCloudInitLogs(ip, keyPath) {
|
|
150
|
+
const sshOpts = [
|
|
151
|
+
'-i', keyPath,
|
|
152
|
+
'-o', 'IdentitiesOnly=yes',
|
|
153
|
+
'-o', 'StrictHostKeyChecking=no',
|
|
154
|
+
'-o', 'UserKnownHostsFile=/dev/null',
|
|
155
|
+
'-o', 'LogLevel=ERROR',
|
|
156
|
+
'-o', 'ConnectTimeout=10',
|
|
157
|
+
`dev@${ip}`,
|
|
158
|
+
'sudo tail -f /var/log/cloud-init-output.log 2>/dev/null'
|
|
159
|
+
];
|
|
160
|
+
const child = (0, child_process_1.spawn)('ssh', sshOpts, {
|
|
161
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
162
|
+
});
|
|
163
|
+
child.stdout?.on('data', (data) => {
|
|
164
|
+
const lines = data.toString().split('\n');
|
|
165
|
+
for (const line of lines) {
|
|
166
|
+
if (line.trim()) {
|
|
167
|
+
// Color-code certain log lines
|
|
168
|
+
if (line.startsWith('===') || line.includes('===')) {
|
|
169
|
+
console.log(chalk_1.default.cyan(line));
|
|
170
|
+
}
|
|
171
|
+
else if (line.toLowerCase().includes('error') || line.toLowerCase().includes('failed')) {
|
|
172
|
+
console.log(chalk_1.default.red(line));
|
|
173
|
+
}
|
|
174
|
+
else if (line.toLowerCase().includes('done') || line.toLowerCase().includes('complete')) {
|
|
175
|
+
console.log(chalk_1.default.green(line));
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
console.log(line);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
child.stderr?.on('data', (data) => {
|
|
184
|
+
// Ignore SSH stderr (connection warnings, etc.)
|
|
185
|
+
});
|
|
186
|
+
return child;
|
|
187
|
+
}
|
|
188
|
+
// Sleep helper for async code
|
|
189
|
+
function sleep(ms) {
|
|
190
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
191
|
+
}
|
|
148
192
|
exports.statusCommand = new commander_1.Command('status')
|
|
149
193
|
.description('Check cloud-init progress and service status of a Genbox')
|
|
150
194
|
.argument('[name]', 'Name of the Genbox (optional - will prompt if not provided)')
|
|
151
195
|
.option('-a, --all', 'Select from all genboxes (not just current project)')
|
|
196
|
+
.option('-w, --watch', 'Watch mode: tail logs in realtime during setup, refresh status periodically when running')
|
|
152
197
|
.action(async (name, options) => {
|
|
153
198
|
try {
|
|
154
199
|
// 1. Select Genbox (interactive if no name provided)
|
|
@@ -256,7 +301,118 @@ exports.statusCommand = new commander_1.Command('status')
|
|
|
256
301
|
}
|
|
257
302
|
// If DB says PROVISIONING, check if we can SSH to get progress
|
|
258
303
|
if (dbStatus === 'provisioning') {
|
|
259
|
-
//
|
|
304
|
+
// In watch mode, handle SSH connection retries and log tailing
|
|
305
|
+
if (options.watch) {
|
|
306
|
+
// Set up Ctrl+C handler
|
|
307
|
+
let stopWatching = false;
|
|
308
|
+
const cleanup = () => {
|
|
309
|
+
stopWatching = true;
|
|
310
|
+
console.log('\n' + chalk_1.default.dim('Watch mode stopped.'));
|
|
311
|
+
process.exit(0);
|
|
312
|
+
};
|
|
313
|
+
process.on('SIGINT', cleanup);
|
|
314
|
+
process.on('SIGTERM', cleanup);
|
|
315
|
+
// Wait for SSH to become available
|
|
316
|
+
let sshAvailable = false;
|
|
317
|
+
while (!sshAvailable && !stopWatching) {
|
|
318
|
+
const status = sshExec(target.ipAddress, keyPath, 'cloud-init status 2>&1');
|
|
319
|
+
if (!status) {
|
|
320
|
+
console.log(chalk_1.default.yellow('[INFO] Waiting for server to boot... (Ctrl+C to exit)'));
|
|
321
|
+
await sleep(5000);
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
const sshErrors = [
|
|
325
|
+
'Operation timed out',
|
|
326
|
+
'Connection refused',
|
|
327
|
+
'Connection timed out',
|
|
328
|
+
'No route to host',
|
|
329
|
+
'Network is unreachable',
|
|
330
|
+
'ssh: connect to host',
|
|
331
|
+
];
|
|
332
|
+
if (sshErrors.some(err => status.includes(err))) {
|
|
333
|
+
console.log(chalk_1.default.yellow('[INFO] Server is still initializing - SSH is not yet available... (Ctrl+C to exit)'));
|
|
334
|
+
await sleep(5000);
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
sshAvailable = true;
|
|
338
|
+
// Check if cloud-init is still running
|
|
339
|
+
if (status.includes('running')) {
|
|
340
|
+
// Get timing breakdown
|
|
341
|
+
const timing = getTimingBreakdown(target.ipAddress, keyPath);
|
|
342
|
+
if (timing.total !== null) {
|
|
343
|
+
console.log(chalk_1.default.yellow(`[INFO] Setup in progress... (elapsed: ${formatDuration(timing.total)})`));
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
console.log(chalk_1.default.yellow('[INFO] Setup in progress...'));
|
|
347
|
+
}
|
|
348
|
+
console.log('');
|
|
349
|
+
// Show timing breakdown so far
|
|
350
|
+
if (timing.sshReady !== null) {
|
|
351
|
+
console.log(chalk_1.default.blue('[INFO] === Timing So Far ==='));
|
|
352
|
+
console.log(` SSH Ready: ${formatDuration(timing.sshReady)}`);
|
|
353
|
+
if (timing.total !== null) {
|
|
354
|
+
console.log(` Elapsed: ${formatDuration(timing.total)}`);
|
|
355
|
+
}
|
|
356
|
+
console.log('');
|
|
357
|
+
}
|
|
358
|
+
console.log(chalk_1.default.blue('[INFO] Tailing setup logs in realtime (Ctrl+C to exit):'));
|
|
359
|
+
console.log(chalk_1.default.dim('─'.repeat(60)));
|
|
360
|
+
const tailProcess = tailCloudInitLogs(target.ipAddress, keyPath);
|
|
361
|
+
// Update cleanup to kill tail process
|
|
362
|
+
process.removeListener('SIGINT', cleanup);
|
|
363
|
+
process.removeListener('SIGTERM', cleanup);
|
|
364
|
+
const cleanupWithTail = () => {
|
|
365
|
+
stopWatching = true;
|
|
366
|
+
tailProcess.kill();
|
|
367
|
+
console.log('\n' + chalk_1.default.dim('Watch mode stopped.'));
|
|
368
|
+
process.exit(0);
|
|
369
|
+
};
|
|
370
|
+
process.on('SIGINT', cleanupWithTail);
|
|
371
|
+
process.on('SIGTERM', cleanupWithTail);
|
|
372
|
+
// Wait for tail process to end
|
|
373
|
+
await new Promise((resolve) => {
|
|
374
|
+
tailProcess.on('close', () => resolve());
|
|
375
|
+
tailProcess.on('error', () => resolve());
|
|
376
|
+
});
|
|
377
|
+
// Clean up handlers
|
|
378
|
+
process.removeListener('SIGINT', cleanupWithTail);
|
|
379
|
+
process.removeListener('SIGTERM', cleanupWithTail);
|
|
380
|
+
if (!stopWatching) {
|
|
381
|
+
console.log('');
|
|
382
|
+
console.log(chalk_1.default.blue('[INFO] Log stream ended. Checking final status...'));
|
|
383
|
+
console.log('');
|
|
384
|
+
await sleep(2000);
|
|
385
|
+
// Check final status
|
|
386
|
+
const finalStatus = sshExec(target.ipAddress, keyPath, 'cloud-init status 2>&1');
|
|
387
|
+
if (finalStatus.includes('done')) {
|
|
388
|
+
console.log(chalk_1.default.green('[SUCCESS] Setup completed!'));
|
|
389
|
+
const finalTiming = getTimingBreakdown(target.ipAddress, keyPath);
|
|
390
|
+
displayTimingBreakdown(finalTiming);
|
|
391
|
+
}
|
|
392
|
+
else if (finalStatus.includes('error')) {
|
|
393
|
+
console.log(chalk_1.default.red('[ERROR] Setup encountered an error!'));
|
|
394
|
+
console.log(chalk_1.default.dim(' Run `gb connect` to investigate.'));
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
console.log(chalk_1.default.yellow(`[INFO] Final status: ${finalStatus}`));
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
else if (status.includes('done')) {
|
|
402
|
+
console.log(chalk_1.default.green('[SUCCESS] Setup completed!'));
|
|
403
|
+
console.log(chalk_1.default.dim(' (Waiting for status update...)'));
|
|
404
|
+
}
|
|
405
|
+
else if (status.includes('error')) {
|
|
406
|
+
console.log(chalk_1.default.red('[ERROR] Setup encountered an error!'));
|
|
407
|
+
console.log(chalk_1.default.dim(' Run `gb connect` to investigate.'));
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
console.log(chalk_1.default.yellow(`[INFO] Status: ${status}`));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
// Non-watch mode: single status check
|
|
260
416
|
let status = sshExec(target.ipAddress, keyPath, 'cloud-init status 2>&1');
|
|
261
417
|
if (!status) {
|
|
262
418
|
console.log(chalk_1.default.yellow('[WARN] Unable to connect to server. It may still be booting.'));
|
|
@@ -275,6 +431,8 @@ exports.statusCommand = new commander_1.Command('status')
|
|
|
275
431
|
console.log(chalk_1.default.yellow('[INFO] Server is still initializing - SSH is not yet available.'));
|
|
276
432
|
console.log(chalk_1.default.dim(' This is normal for newly created servers.'));
|
|
277
433
|
console.log(chalk_1.default.dim(' Please wait 1-2 minutes and try again.'));
|
|
434
|
+
console.log('');
|
|
435
|
+
console.log(chalk_1.default.dim('Tip: Use `gb status -w` to watch progress in realtime'));
|
|
278
436
|
return;
|
|
279
437
|
}
|
|
280
438
|
// Cloud-init still running
|
|
@@ -303,6 +461,8 @@ exports.statusCommand = new commander_1.Command('status')
|
|
|
303
461
|
if (logOutput) {
|
|
304
462
|
console.log(logOutput);
|
|
305
463
|
}
|
|
464
|
+
console.log('');
|
|
465
|
+
console.log(chalk_1.default.dim('Tip: Use `gb status -w` to watch progress in realtime'));
|
|
306
466
|
return;
|
|
307
467
|
}
|
|
308
468
|
// Cloud-init done but callback might not have been received yet
|