@justin0713/opspilot 1.0.7 → 1.0.9
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/opspilot.js +120 -1
- package/package.json +1 -1
package/bin/opspilot.js
CHANGED
|
@@ -340,6 +340,121 @@ function cmdConfig() {
|
|
|
340
340
|
console.log(JSON.stringify(safe, null, 2));
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
+
function cmdUninstall(flags) {
|
|
344
|
+
const keepData = flags['keep-data'] || false;
|
|
345
|
+
const cfg = loadConfig();
|
|
346
|
+
const data = cfg.dataDir || 'opspilot-data';
|
|
347
|
+
|
|
348
|
+
console.log('');
|
|
349
|
+
log('Uninstalling OpsPilot Local...');
|
|
350
|
+
|
|
351
|
+
// 1. Stop + remove container
|
|
352
|
+
if (tryRun('docker --version')) {
|
|
353
|
+
const status = tryRun(`docker inspect -f "{{.State.Status}}" ${CONTAINER} 2>/dev/null`);
|
|
354
|
+
if (status) {
|
|
355
|
+
log('Stopping container...');
|
|
356
|
+
try { run(`docker stop ${CONTAINER}`, { silent: true }); } catch (_) {}
|
|
357
|
+
try { run(`docker rm -f ${CONTAINER}`, { silent: true }); } catch (_) {}
|
|
358
|
+
ok('Container removed.');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// 2. Remove data volume (unless --keep-data)
|
|
362
|
+
if (!keepData) {
|
|
363
|
+
const volExists = tryRun(`docker volume inspect ${data} 2>/dev/null`);
|
|
364
|
+
if (volExists) {
|
|
365
|
+
log(`Removing data volume '${data}'...`);
|
|
366
|
+
try { run(`docker volume rm ${data}`, { silent: true }); ok('Data volume removed.'); }
|
|
367
|
+
catch (_) { warn(`Could not remove volume '${data}' — remove manually: docker volume rm ${data}`); }
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
warn(`Data volume '${data}' kept (--keep-data). Remove manually later: docker volume rm ${data}`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// 3. Remove Docker image
|
|
374
|
+
const imgExists = tryRun(`docker image inspect ${IMAGE} 2>/dev/null`);
|
|
375
|
+
if (imgExists) {
|
|
376
|
+
log(`Removing image ${IMAGE}...`);
|
|
377
|
+
try { run(`docker rmi ${IMAGE}`, { silent: true }); ok('Image removed.'); } catch (_) {}
|
|
378
|
+
}
|
|
379
|
+
} else {
|
|
380
|
+
warn('Docker not available — skipping container/volume cleanup.');
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// 4. Remove local CLI config + credentials
|
|
384
|
+
const toDelete = [CONFIG_FILE, CREDENTIALS_FILE, SETUP_TOKEN_FILE];
|
|
385
|
+
toDelete.forEach(f => {
|
|
386
|
+
try { if (fs.existsSync(f)) { fs.unlinkSync(f); } } catch (_) {}
|
|
387
|
+
});
|
|
388
|
+
// Remove config dir if empty
|
|
389
|
+
try {
|
|
390
|
+
const remaining = fs.readdirSync(CONFIG_DIR);
|
|
391
|
+
if (remaining.length === 0) fs.rmdirSync(CONFIG_DIR);
|
|
392
|
+
} catch (_) {}
|
|
393
|
+
|
|
394
|
+
ok('OpsPilot Local uninstalled.');
|
|
395
|
+
console.log('');
|
|
396
|
+
console.log(`To reinstall: ${CYAN}opspilot start --token <license-token>${RESET}`);
|
|
397
|
+
console.log(`To remove CLI: ${CYAN}npm uninstall -g @justin0713/opspilot${RESET}`);
|
|
398
|
+
console.log('');
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function cmdResetAdmin() {
|
|
402
|
+
checkDocker();
|
|
403
|
+
|
|
404
|
+
const status = tryRun(`docker inspect -f "{{.State.Status}}" ${CONTAINER} 2>/dev/null`);
|
|
405
|
+
if (status !== 'running') {
|
|
406
|
+
die(
|
|
407
|
+
`Container '${CONTAINER}' is not running.\n\n` +
|
|
408
|
+
` Start it first: opspilot start --token <token>\n` +
|
|
409
|
+
` Then reset: opspilot reset-admin`
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const newPassword = generatePassword();
|
|
414
|
+
|
|
415
|
+
// Python runs INSIDE the container — reads password from stdin (never exposed in ps/docker inspect)
|
|
416
|
+
const script = [
|
|
417
|
+
'import sys, os',
|
|
418
|
+
'sys.path.insert(0, "/app")',
|
|
419
|
+
'from user_store import hash_password, load_users, save_users',
|
|
420
|
+
'data_dir = os.environ.get("OPSPILOT_DATA_DIR", "/app/data")',
|
|
421
|
+
'uf = data_dir + "/users.json"',
|
|
422
|
+
'pw = sys.stdin.readline().strip()',
|
|
423
|
+
'ud = load_users(uf)',
|
|
424
|
+
'ul = ud.get("users", [])',
|
|
425
|
+
'admin = next((u for u in ul if u.get("role") == "admin"), None)',
|
|
426
|
+
'assert admin, "ERROR: no admin user found"',
|
|
427
|
+
'salt, hashed = hash_password(pw)',
|
|
428
|
+
'admin["password_salt"] = salt',
|
|
429
|
+
'admin["password_hash"] = hashed',
|
|
430
|
+
'save_users(uf, ud)',
|
|
431
|
+
'print("OK:" + admin["username"])',
|
|
432
|
+
].join('\n');
|
|
433
|
+
|
|
434
|
+
log('Resetting admin password inside container...');
|
|
435
|
+
const result = spawnSync(
|
|
436
|
+
'docker', ['exec', '-i', CONTAINER, 'python3', '-c', script],
|
|
437
|
+
{ input: newPassword, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
if (result.status !== 0 || (result.stdout || '').startsWith('ERROR') || result.error) {
|
|
441
|
+
const detail = (result.stderr || result.stdout || String(result.error)).trim();
|
|
442
|
+
die(`Reset failed: ${detail}\n\nCheck logs: opspilot logs`);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Persist to credentials file (chmod 600) for `opspilot password`
|
|
446
|
+
writeCredentials(newPassword);
|
|
447
|
+
|
|
448
|
+
console.log('');
|
|
449
|
+
ok('Admin password reset successfully.');
|
|
450
|
+
console.log(`${BOLD} Username :${RESET} admin`);
|
|
451
|
+
console.log(`${BOLD} Password :${RESET} ${BOLD}${newPassword}${RESET}`);
|
|
452
|
+
console.log('');
|
|
453
|
+
console.log(`${YELLOW}Save this password — or retrieve it later with:${RESET}`);
|
|
454
|
+
console.log(` ${CYAN}opspilot password${RESET}`);
|
|
455
|
+
console.log('');
|
|
456
|
+
}
|
|
457
|
+
|
|
343
458
|
function cmdInstallGuide() {
|
|
344
459
|
const platform = os.platform();
|
|
345
460
|
console.log(`
|
|
@@ -399,6 +514,8 @@ ${BOLD}Commands:${RESET}
|
|
|
399
514
|
${CYAN}status${RESET} Show container status
|
|
400
515
|
${CYAN}config${RESET} Show saved local config
|
|
401
516
|
${CYAN}password${RESET} Show first-run admin password (--clear to delete after reading)
|
|
517
|
+
${CYAN}reset-admin${RESET} Reset admin password (official recovery if password is lost)
|
|
518
|
+
${CYAN}uninstall${RESET} Remove container, image, data volume and local config
|
|
402
519
|
${CYAN}install-guide${RESET} Show Docker installation instructions for your platform
|
|
403
520
|
|
|
404
521
|
${BOLD}Start options:${RESET}
|
|
@@ -440,7 +557,9 @@ switch (cmd) {
|
|
|
440
557
|
break;
|
|
441
558
|
case 'status': cmdStatus(); break;
|
|
442
559
|
case 'config': cmdConfig(); break;
|
|
443
|
-
case '
|
|
560
|
+
case 'reset-admin': cmdResetAdmin(); break;
|
|
561
|
+
case 'uninstall': cmdUninstall(parsed.flags); break;
|
|
562
|
+
case 'install-guide': cmdInstallGuide(); break;
|
|
444
563
|
case 'password':
|
|
445
564
|
if (parsed.flags.clear) {
|
|
446
565
|
try { fs.unlinkSync(CREDENTIALS_FILE); ok('Credentials file cleared.'); }
|
package/package.json
CHANGED