genbox 1.0.33 → 1.0.35
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/init.js +31 -14
- package/dist/commands/status.js +107 -39
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -1021,7 +1021,20 @@ function createProfilesFromScan(scan, definedEnvironments = []) {
|
|
|
1021
1021
|
const profiles = {};
|
|
1022
1022
|
const frontendApps = scan.apps.filter(a => a.type === 'frontend');
|
|
1023
1023
|
const backendApps = scan.apps.filter(a => a.type === 'backend' || a.type === 'api');
|
|
1024
|
-
|
|
1024
|
+
// Docker services are also runnable (api, web services from docker-compose)
|
|
1025
|
+
const dockerServices = scan.compose?.applications || [];
|
|
1026
|
+
const dockerServiceNames = dockerServices.map(s => s.name);
|
|
1027
|
+
// Combine backend apps + Docker services (avoid duplicates)
|
|
1028
|
+
const allBackendNames = new Set([
|
|
1029
|
+
...backendApps.map(a => a.name),
|
|
1030
|
+
...dockerServiceNames.filter(name => {
|
|
1031
|
+
// Include Docker services that aren't already in apps OR aren't frontend
|
|
1032
|
+
const existingApp = scan.apps.find(a => a.name === name);
|
|
1033
|
+
return !existingApp || existingApp.type !== 'frontend';
|
|
1034
|
+
}),
|
|
1035
|
+
]);
|
|
1036
|
+
const backendAppNames = Array.from(allBackendNames);
|
|
1037
|
+
const hasBackend = backendAppNames.length > 0;
|
|
1025
1038
|
// Determine which environment to use for remote connections
|
|
1026
1039
|
// Priority: staging > production > first defined
|
|
1027
1040
|
const remoteEnv = definedEnvironments.includes('staging')
|
|
@@ -1045,32 +1058,32 @@ function createProfilesFromScan(scan, definedEnvironments = []) {
|
|
|
1045
1058
|
// No remote environment - create local-only profiles
|
|
1046
1059
|
for (const frontend of frontendApps.slice(0, 3)) {
|
|
1047
1060
|
profiles[`${frontend.name}-local`] = {
|
|
1048
|
-
description: `${frontend.name} with local
|
|
1061
|
+
description: `${frontend.name} with local backend`,
|
|
1049
1062
|
size: 'medium',
|
|
1050
|
-
apps: [frontend.name, ...
|
|
1063
|
+
apps: [frontend.name, ...backendAppNames],
|
|
1051
1064
|
};
|
|
1052
1065
|
}
|
|
1053
1066
|
}
|
|
1054
1067
|
// Full local development (with DB copy from available environment)
|
|
1055
|
-
if (
|
|
1068
|
+
if (hasBackend && frontendApps.length > 0) {
|
|
1056
1069
|
const primaryFrontend = frontendApps[0];
|
|
1057
1070
|
const dbSource = remoteEnv || 'staging'; // Use defined env or default
|
|
1058
1071
|
profiles[`${primaryFrontend.name}-full`] = {
|
|
1059
|
-
description: `${primaryFrontend.name} + local
|
|
1072
|
+
description: `${primaryFrontend.name} + local backend + DB copy`,
|
|
1060
1073
|
size: 'large',
|
|
1061
|
-
apps: [primaryFrontend.name,
|
|
1074
|
+
apps: [primaryFrontend.name, ...backendAppNames],
|
|
1062
1075
|
database: {
|
|
1063
1076
|
mode: remoteEnv ? 'copy' : 'local', // Only copy if we have a source
|
|
1064
1077
|
...(remoteEnv && { source: dbSource }),
|
|
1065
1078
|
},
|
|
1066
1079
|
};
|
|
1067
1080
|
}
|
|
1068
|
-
//
|
|
1069
|
-
|
|
1070
|
-
profiles[
|
|
1071
|
-
description:
|
|
1081
|
+
// Backend/service development only (for each backend app and Docker service)
|
|
1082
|
+
for (const backendName of backendAppNames) {
|
|
1083
|
+
profiles[`${backendName}-dev`] = {
|
|
1084
|
+
description: `${backendName} with local infrastructure`,
|
|
1072
1085
|
size: 'medium',
|
|
1073
|
-
apps: [
|
|
1086
|
+
apps: [backendName],
|
|
1074
1087
|
database: {
|
|
1075
1088
|
mode: 'local',
|
|
1076
1089
|
},
|
|
@@ -1085,13 +1098,17 @@ function createProfilesFromScan(scan, definedEnvironments = []) {
|
|
|
1085
1098
|
default_connection: remoteEnv,
|
|
1086
1099
|
};
|
|
1087
1100
|
}
|
|
1088
|
-
// Full stack
|
|
1089
|
-
|
|
1101
|
+
// Full stack - combine apps + Docker services (deduplicated)
|
|
1102
|
+
const allRunnableNames = new Set([
|
|
1103
|
+
...scan.apps.filter(a => a.type !== 'library').map(a => a.name),
|
|
1104
|
+
...dockerServiceNames,
|
|
1105
|
+
]);
|
|
1106
|
+
if (allRunnableNames.size > 1) {
|
|
1090
1107
|
const dbSource = remoteEnv || 'staging';
|
|
1091
1108
|
profiles['full-stack'] = {
|
|
1092
1109
|
description: 'Everything local' + (remoteEnv ? ' with DB copy' : ''),
|
|
1093
1110
|
size: 'xl',
|
|
1094
|
-
apps:
|
|
1111
|
+
apps: Array.from(allRunnableNames),
|
|
1095
1112
|
database: {
|
|
1096
1113
|
mode: remoteEnv ? 'copy' : 'local',
|
|
1097
1114
|
...(remoteEnv && { source: dbSource }),
|
package/dist/commands/status.js
CHANGED
|
@@ -187,56 +187,124 @@ exports.statusCommand = new commander_1.Command('status')
|
|
|
187
187
|
console.error(chalk_1.default.red(error.message));
|
|
188
188
|
return;
|
|
189
189
|
}
|
|
190
|
-
console.log(chalk_1.default.blue(`[INFO] Checking
|
|
190
|
+
console.log(chalk_1.default.blue(`[INFO] Checking status of ${selectedName}...`));
|
|
191
191
|
console.log('');
|
|
192
|
-
// 3. Check
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
'
|
|
201
|
-
|
|
202
|
-
'
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
192
|
+
// 3. Check status from DB first (more reliable than SSH)
|
|
193
|
+
const dbStatus = target.status; // 'provisioning' | 'running' | 'error' etc.
|
|
194
|
+
const setupDuration = target.setupDuration;
|
|
195
|
+
const setupCompletedAt = target.setupCompletedAt;
|
|
196
|
+
// If DB says RUNNING and we have setupCompletedAt, setup is complete
|
|
197
|
+
if (dbStatus === 'running' && setupCompletedAt) {
|
|
198
|
+
const durationStr = setupDuration ? formatDuration(setupDuration) : 'unknown';
|
|
199
|
+
console.log(chalk_1.default.green(`[SUCCESS] Setup completed! (took ${durationStr})`));
|
|
200
|
+
console.log('');
|
|
201
|
+
// Show Docker containers status
|
|
202
|
+
const dockerStatus = sshExec(target.ipAddress, keyPath, 'docker ps --format "{{.Names}}\\t{{.Status}}" 2>/dev/null', 10);
|
|
203
|
+
if (dockerStatus && dockerStatus.trim()) {
|
|
204
|
+
console.log(chalk_1.default.blue('[INFO] === Docker Services ==='));
|
|
205
|
+
console.log('NAMES\tSTATUS');
|
|
206
|
+
console.log(dockerStatus);
|
|
207
|
+
console.log('');
|
|
208
|
+
}
|
|
209
|
+
// Show PM2 processes
|
|
210
|
+
const pm2Status = sshExec(target.ipAddress, keyPath, 'source ~/.nvm/nvm.sh 2>/dev/null; pm2 list 2>/dev/null || echo ""', 10);
|
|
211
|
+
if (pm2Status && pm2Status.trim() && !pm2Status.includes('No process')) {
|
|
212
|
+
console.log(chalk_1.default.blue('[INFO] === PM2 Services ==='));
|
|
213
|
+
console.log(pm2Status);
|
|
214
|
+
console.log('');
|
|
215
|
+
}
|
|
216
|
+
// Show URLs if available
|
|
217
|
+
if (target.urls && Object.keys(target.urls).length > 0) {
|
|
218
|
+
console.log(chalk_1.default.blue('[INFO] === Service URLs ==='));
|
|
219
|
+
for (const [service, url] of Object.entries(target.urls)) {
|
|
220
|
+
console.log(` ${service}: ${chalk_1.default.cyan(url)}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
211
223
|
return;
|
|
212
224
|
}
|
|
213
|
-
if
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
225
|
+
// If DB says PROVISIONING, check if we can SSH to get progress
|
|
226
|
+
if (dbStatus === 'provisioning') {
|
|
227
|
+
// Try to SSH and check cloud-init progress
|
|
228
|
+
let status = sshExec(target.ipAddress, keyPath, 'cloud-init status 2>&1');
|
|
229
|
+
if (!status) {
|
|
230
|
+
console.log(chalk_1.default.yellow('[WARN] Unable to connect to server. It may still be booting.'));
|
|
231
|
+
return;
|
|
218
232
|
}
|
|
219
|
-
|
|
220
|
-
|
|
233
|
+
// Check for SSH connection errors (timeout, refused, etc.)
|
|
234
|
+
const sshErrors = [
|
|
235
|
+
'Operation timed out',
|
|
236
|
+
'Connection refused',
|
|
237
|
+
'Connection timed out',
|
|
238
|
+
'No route to host',
|
|
239
|
+
'Network is unreachable',
|
|
240
|
+
'ssh: connect to host',
|
|
241
|
+
];
|
|
242
|
+
if (sshErrors.some(err => status.includes(err))) {
|
|
243
|
+
console.log(chalk_1.default.yellow('[INFO] Server is still initializing - SSH is not yet available.'));
|
|
244
|
+
console.log(chalk_1.default.dim(' This is normal for newly created servers.'));
|
|
245
|
+
console.log(chalk_1.default.dim(' Please wait 1-2 minutes and try again.'));
|
|
246
|
+
return;
|
|
221
247
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
console.log(` SSH Ready: ${formatDuration(timing.sshReady)}`);
|
|
248
|
+
// Cloud-init still running
|
|
249
|
+
if (status.includes('running')) {
|
|
250
|
+
// Get timing breakdown
|
|
251
|
+
const timing = getTimingBreakdown(target.ipAddress, keyPath);
|
|
227
252
|
if (timing.total !== null) {
|
|
228
|
-
console.log(`
|
|
253
|
+
console.log(chalk_1.default.yellow(`[INFO] Setup in progress... (elapsed: ${formatDuration(timing.total)})`));
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
console.log(chalk_1.default.yellow('[INFO] Setup in progress...'));
|
|
229
257
|
}
|
|
230
258
|
console.log('');
|
|
259
|
+
// Show timing breakdown so far
|
|
260
|
+
if (timing.sshReady !== null) {
|
|
261
|
+
console.log(chalk_1.default.blue('[INFO] === Timing So Far ==='));
|
|
262
|
+
console.log(` SSH Ready: ${formatDuration(timing.sshReady)}`);
|
|
263
|
+
if (timing.total !== null) {
|
|
264
|
+
console.log(` Elapsed: ${formatDuration(timing.total)}`);
|
|
265
|
+
}
|
|
266
|
+
console.log('');
|
|
267
|
+
}
|
|
268
|
+
console.log(chalk_1.default.blue('[INFO] Latest setup progress:'));
|
|
269
|
+
// Tail cloud-init output log for progress
|
|
270
|
+
const logOutput = sshExec(target.ipAddress, keyPath, "sudo tail -30 /var/log/cloud-init-output.log 2>/dev/null | grep -E '^===|^#|Progress:|DONE|error|Error|failed|Failed' || sudo tail -20 /var/log/cloud-init-output.log", 15);
|
|
271
|
+
if (logOutput) {
|
|
272
|
+
console.log(logOutput);
|
|
273
|
+
}
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
// Cloud-init done but callback might not have been received yet
|
|
277
|
+
if (status.includes('done')) {
|
|
278
|
+
console.log(chalk_1.default.green('[SUCCESS] Setup completed!'));
|
|
279
|
+
console.log(chalk_1.default.dim(' (Waiting for status update...)'));
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
// Cloud-init not found (already cleaned up) - show generic status
|
|
283
|
+
if (status.includes('not found') || status.includes('command not found')) {
|
|
284
|
+
console.log(chalk_1.default.yellow('[INFO] Setup is finalizing...'));
|
|
285
|
+
return;
|
|
231
286
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
287
|
+
// Other status
|
|
288
|
+
console.log(chalk_1.default.yellow(`[INFO] Status: ${status}`));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
// If DB says ERROR
|
|
292
|
+
if (dbStatus === 'error') {
|
|
293
|
+
console.log(chalk_1.default.red('[ERROR] Setup failed!'));
|
|
294
|
+
console.log(chalk_1.default.dim(' Run `genbox connect` to investigate.'));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
// Fallback: try legacy cloud-init check for backwards compatibility
|
|
298
|
+
let status = sshExec(target.ipAddress, keyPath, 'cloud-init status 2>&1');
|
|
299
|
+
if (!status || status.includes('not found')) {
|
|
300
|
+
// No cloud-init, check our status file
|
|
301
|
+
const genboxStatus = sshExec(target.ipAddress, keyPath, 'cat ~/.genbox-status 2>/dev/null');
|
|
302
|
+
if (genboxStatus && genboxStatus.includes('Setup Complete')) {
|
|
303
|
+
console.log(chalk_1.default.green('[SUCCESS] Setup completed!'));
|
|
304
|
+
return;
|
|
237
305
|
}
|
|
238
306
|
}
|
|
239
|
-
|
|
307
|
+
if (status && status.includes('done')) {
|
|
240
308
|
// Get timing breakdown
|
|
241
309
|
const timing = getTimingBreakdown(target.ipAddress, keyPath);
|
|
242
310
|
console.log(chalk_1.default.green(`[SUCCESS] Cloud-init completed!${timing.total !== null ? ` (${formatDuration(timing.total)})` : ''}`));
|