genbox 1.0.57 → 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/rebuild.js +46 -0
- package/dist/commands/status.js +161 -1
- package/package.json +1 -1
package/dist/commands/rebuild.js
CHANGED
|
@@ -278,6 +278,26 @@ function buildRebuildPayload(resolved, config, publicKey, privateKey, configLoad
|
|
|
278
278
|
gitToken: envVars.GIT_TOKEN,
|
|
279
279
|
};
|
|
280
280
|
}
|
|
281
|
+
/**
|
|
282
|
+
* Validate git configuration and warn about missing credentials
|
|
283
|
+
*/
|
|
284
|
+
function validateGitCredentials(repos, gitToken, privateKey) {
|
|
285
|
+
const warnings = [];
|
|
286
|
+
const errors = [];
|
|
287
|
+
for (const repo of repos) {
|
|
288
|
+
const isHttps = repo.url.startsWith('https://');
|
|
289
|
+
const isSsh = repo.url.startsWith('git@') || repo.url.includes('ssh://');
|
|
290
|
+
const isPrivateRepo = repo.url.includes('github.com') && !repo.url.includes('/public/');
|
|
291
|
+
if (isHttps && !gitToken && isPrivateRepo) {
|
|
292
|
+
warnings.push(`Repository '${repo.name}' uses HTTPS URL but GIT_TOKEN is not set.`);
|
|
293
|
+
warnings.push(` Add GIT_TOKEN=<your-github-token> to .env.genbox for private repos.`);
|
|
294
|
+
}
|
|
295
|
+
if (isSsh && !privateKey) {
|
|
296
|
+
warnings.push(`Repository '${repo.name}' uses SSH URL but no SSH key was injected.`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return { warnings, errors };
|
|
300
|
+
}
|
|
281
301
|
/**
|
|
282
302
|
* Generate setup script
|
|
283
303
|
*/
|
|
@@ -709,6 +729,32 @@ exports.rebuildCommand = new commander_1.Command('rebuild')
|
|
|
709
729
|
}
|
|
710
730
|
}
|
|
711
731
|
}
|
|
732
|
+
// Validate git credentials before rebuilding
|
|
733
|
+
const envVarsForValidation = configLoader.loadEnvVars(process.cwd());
|
|
734
|
+
const gitValidation = validateGitCredentials(resolved.repos.map(r => ({ url: r.url, name: r.name })), envVarsForValidation.GIT_TOKEN, privateKeyContent);
|
|
735
|
+
if (gitValidation.warnings.length > 0) {
|
|
736
|
+
console.log('');
|
|
737
|
+
console.log(chalk_1.default.yellow('⚠ Git Authentication Warnings:'));
|
|
738
|
+
for (const warning of gitValidation.warnings) {
|
|
739
|
+
console.log(chalk_1.default.yellow(` ${warning}`));
|
|
740
|
+
}
|
|
741
|
+
console.log('');
|
|
742
|
+
if (!options.yes) {
|
|
743
|
+
const proceed = await prompts.confirm({
|
|
744
|
+
message: 'Git clone may fail without credentials. Continue anyway?',
|
|
745
|
+
default: false,
|
|
746
|
+
});
|
|
747
|
+
if (!proceed) {
|
|
748
|
+
console.log(chalk_1.default.dim('Cancelled. Add GIT_TOKEN to .env.genbox and try again.'));
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
console.log(chalk_1.default.red('Error: Missing git credentials with --yes flag'));
|
|
754
|
+
console.log(chalk_1.default.dim('Add GIT_TOKEN=<your-github-token> to .env.genbox'));
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
712
758
|
// Build payload
|
|
713
759
|
const payload = buildRebuildPayload(resolved, config, publicKey, privateKeyContent, configLoader);
|
|
714
760
|
// Add database info to payload if we have a snapshot
|
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
|