@celilo/cli 0.3.12 → 0.3.14
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/package.json
CHANGED
|
@@ -221,11 +221,22 @@ export async function handleModuleRemove(
|
|
|
221
221
|
log.success('Infrastructure destroyed');
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
// Remove DNS record (if dns_internal is available)
|
|
224
|
+
// Remove DNS record (if dns_internal is available). Wrapped in a
|
|
225
|
+
// FuelGauge with a gauge logger so the dns_internal capability calls
|
|
226
|
+
// inside (`deleteRecord`, etc.) nest as sub-events rather than
|
|
227
|
+
// leaking to scrollback as unindented top-level lines via
|
|
228
|
+
// cli/prompts.log.instantEvent (which pops every pending step).
|
|
229
|
+
const dnsGauge = new FuelGauge(`Removing ${moduleId} from DNS`, {
|
|
230
|
+
skipAnimation: !process.stdout.isTTY,
|
|
231
|
+
});
|
|
232
|
+
dnsGauge.start();
|
|
225
233
|
try {
|
|
234
|
+
const dnsLogger = createGaugeLogger(dnsGauge, moduleId, 'auto_deregister_dns');
|
|
226
235
|
const { autoDeregisterDns } = await import('../../services/dns-auto-register');
|
|
227
|
-
await autoDeregisterDns(moduleId, db,
|
|
236
|
+
await autoDeregisterDns(moduleId, db, dnsLogger);
|
|
237
|
+
dnsGauge.stop(true);
|
|
228
238
|
} catch {
|
|
239
|
+
dnsGauge.stop(false);
|
|
229
240
|
// Non-fatal -- continue with removal
|
|
230
241
|
}
|
|
231
242
|
|
|
@@ -210,6 +210,25 @@ async function initInteractive(cliOverrides: Record<string, string> = {}): Promi
|
|
|
210
210
|
if (!has('ssh.public_key')) {
|
|
211
211
|
const detectedKeys = autoDetectSSHKeys();
|
|
212
212
|
const existingSshKey = defaults['ssh.public_key'];
|
|
213
|
+
|
|
214
|
+
// Fresh box, no key in ~/.ssh and no previously-saved key — point the
|
|
215
|
+
// user at ssh-keygen rather than making them paste at a blank prompt.
|
|
216
|
+
// They can also skip this branch by passing ssh.public_key=... on the CLI.
|
|
217
|
+
if (detectedKeys.length === 0 && !existingSshKey) {
|
|
218
|
+
p.cancel('No SSH key found in ~/.ssh/');
|
|
219
|
+
console.log();
|
|
220
|
+
console.log('Celilo uses an SSH key to reach managed machines');
|
|
221
|
+
console.log('(Proxmox hosts, VPS, Raspberry Pi, etc.). Generate one:');
|
|
222
|
+
console.log();
|
|
223
|
+
console.log(' ssh-keygen -t ed25519 -C "<your-email>"');
|
|
224
|
+
console.log();
|
|
225
|
+
console.log('Then re-run: celilo system init');
|
|
226
|
+
console.log();
|
|
227
|
+
console.log('Or, to paste a key from elsewhere without generating one:');
|
|
228
|
+
console.log(' celilo system init ssh.public_key="ssh-ed25519 AAAA..."');
|
|
229
|
+
return { success: false, error: 'No SSH key available — run ssh-keygen first' };
|
|
230
|
+
}
|
|
231
|
+
|
|
213
232
|
let keySelected = false;
|
|
214
233
|
|
|
215
234
|
if (detectedKeys.length === 1) {
|
|
@@ -258,7 +277,9 @@ async function initInteractive(cliOverrides: Record<string, string> = {}): Promi
|
|
|
258
277
|
? 'SSH public key (press Enter to keep existing)'
|
|
259
278
|
: 'SSH public key',
|
|
260
279
|
defaultValue: String(existingSshKey || ''),
|
|
261
|
-
|
|
280
|
+
// String(undefined) === 'undefined' (truthy), so guard explicitly —
|
|
281
|
+
// otherwise the prompt shows "undefined" as the placeholder text.
|
|
282
|
+
placeholder: existingSshKey ? String(existingSshKey) : 'ssh-ed25519 AAAA...',
|
|
262
283
|
validate: (value) => {
|
|
263
284
|
if (existingSshKey && !value) {
|
|
264
285
|
return;
|
package/src/config/paths.ts
CHANGED
|
@@ -4,38 +4,22 @@ import { join } from 'node:path';
|
|
|
4
4
|
/**
|
|
5
5
|
* Get the module storage path based on environment and platform
|
|
6
6
|
*
|
|
7
|
-
* Priority:
|
|
8
|
-
* 1. CELILO_DATA_DIR environment variable (explicit override)
|
|
9
|
-
* 2. ENVIRONMENT=dev uses ./celilo-data/modules/ (for testing)
|
|
10
|
-
* 3. Platform defaults:
|
|
11
|
-
* - macOS: ~/Library/Application Support/celilo/modules/
|
|
12
|
-
* - Linux: /var/lib/celilo/modules/
|
|
13
|
-
*
|
|
14
7
|
* @returns Absolute path to module storage directory
|
|
15
8
|
*/
|
|
16
9
|
export function getModuleStoragePath(): string {
|
|
17
|
-
|
|
18
|
-
if (process.env.CELILO_DATA_DIR) {
|
|
19
|
-
return join(process.env.CELILO_DATA_DIR, 'modules');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Development mode
|
|
23
|
-
if (process.env.ENVIRONMENT === 'dev') {
|
|
24
|
-
return join(process.cwd(), 'celilo-data', 'modules');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Platform defaults
|
|
28
|
-
if (platform() === 'darwin') {
|
|
29
|
-
return join(homedir(), 'Library', 'Application Support', 'celilo', 'modules');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Linux/other
|
|
33
|
-
return '/var/lib/celilo/modules';
|
|
10
|
+
return join(getDataDir(), 'modules');
|
|
34
11
|
}
|
|
35
12
|
|
|
36
13
|
/**
|
|
37
14
|
* Get the base data directory for Celilo
|
|
38
15
|
*
|
|
16
|
+
* Priority:
|
|
17
|
+
* 1. CELILO_DATA_DIR environment variable (explicit override)
|
|
18
|
+
* 2. ENVIRONMENT=dev uses ./celilo-data/ (for testing)
|
|
19
|
+
* 3. Platform defaults — always user-scoped so the CLI works without root:
|
|
20
|
+
* - macOS: ~/Library/Application Support/celilo/
|
|
21
|
+
* - Linux: $XDG_DATA_HOME/celilo/ (defaults to ~/.local/share/celilo/)
|
|
22
|
+
*
|
|
39
23
|
* @returns Absolute path to base data directory
|
|
40
24
|
*/
|
|
41
25
|
export function getDataDir(): string {
|
|
@@ -49,13 +33,15 @@ export function getDataDir(): string {
|
|
|
49
33
|
return join(process.cwd(), 'celilo-data');
|
|
50
34
|
}
|
|
51
35
|
|
|
52
|
-
//
|
|
36
|
+
// macOS
|
|
53
37
|
if (platform() === 'darwin') {
|
|
54
38
|
return join(homedir(), 'Library', 'Application Support', 'celilo');
|
|
55
39
|
}
|
|
56
40
|
|
|
57
|
-
// Linux/other
|
|
58
|
-
|
|
41
|
+
// Linux/other — XDG Base Directory spec, user-scoped (no sudo needed).
|
|
42
|
+
// System-wide installs can opt in via CELILO_DATA_DIR=/var/lib/celilo.
|
|
43
|
+
const xdgDataHome = process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share');
|
|
44
|
+
return join(xdgDataHome, 'celilo');
|
|
59
45
|
}
|
|
60
46
|
|
|
61
47
|
/**
|
package/src/db/client.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Database } from 'bun:sqlite';
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
2
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { drizzle } from 'drizzle-orm/bun-sqlite';
|
|
@@ -166,6 +166,13 @@ export function createDbClient(config?: Partial<DatabaseConfig>) {
|
|
|
166
166
|
const dbPath = config?.path ?? getDbPath();
|
|
167
167
|
const readonly = config?.readonly ?? false;
|
|
168
168
|
|
|
169
|
+
// bun:sqlite's `create: true` makes the file but not the parent directory.
|
|
170
|
+
// First-run after a fresh install hits this — without recursive mkdir we
|
|
171
|
+
// get SQLITE_CANTOPEN before init can write its config.
|
|
172
|
+
if (!readonly) {
|
|
173
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
174
|
+
}
|
|
175
|
+
|
|
169
176
|
const sqlite = new Database(dbPath, {
|
|
170
177
|
readonly,
|
|
171
178
|
create: true,
|
package/src/hooks/logger.ts
CHANGED
|
@@ -72,7 +72,12 @@ export function createGaugeLogger(
|
|
|
72
72
|
// pushStep nests under the FuelGauge step that wraps the hook,
|
|
73
73
|
// so inner log calls (logger.info within the capability impl)
|
|
74
74
|
// are sub-events of this nested step.
|
|
75
|
-
|
|
75
|
+
//
|
|
76
|
+
// No `→`/`✓` glyph in the message — the display's spinner
|
|
77
|
+
// (in-progress) and green ✔ (done) already convey the state.
|
|
78
|
+
// Sticking a literal `✓` into the doneMsg produced a double-
|
|
79
|
+
// checkmark line on completion (`✔ ✓ name`).
|
|
80
|
+
display.pushStep(name, name);
|
|
76
81
|
} else {
|
|
77
82
|
// Without a display, fall back to a plain info-style marker so
|
|
78
83
|
// the line still appears in raw log output.
|
|
@@ -1234,12 +1234,23 @@ async function deployModuleImpl(
|
|
|
1234
1234
|
}
|
|
1235
1235
|
}
|
|
1236
1236
|
|
|
1237
|
-
// Auto-register module hostname in internal DNS (if available)
|
|
1237
|
+
// Auto-register module hostname in internal DNS (if available).
|
|
1238
|
+
// Wrapped in a FuelGauge with a gauge logger so the capability calls
|
|
1239
|
+
// inside (`dns_internal.registerRecord`, etc.) nest as sub-events
|
|
1240
|
+
// under a single step rather than leaking to scrollback as
|
|
1241
|
+
// unindented top-level lines via cli/prompts.log.instantEvent.
|
|
1238
1242
|
let dnsRegistered = true;
|
|
1243
|
+
const dnsGauge = new FuelGauge(`Registering ${moduleId} in DNS`, {
|
|
1244
|
+
skipAnimation: !process.stdout.isTTY,
|
|
1245
|
+
});
|
|
1246
|
+
dnsGauge.start();
|
|
1239
1247
|
try {
|
|
1248
|
+
const dnsLogger = createGaugeLogger(dnsGauge, moduleId, 'auto_register_dns');
|
|
1240
1249
|
const { autoRegisterDns } = await import('./dns-auto-register');
|
|
1241
|
-
await autoRegisterDns(moduleId, db,
|
|
1250
|
+
await autoRegisterDns(moduleId, db, dnsLogger);
|
|
1251
|
+
dnsGauge.stop(true);
|
|
1242
1252
|
} catch (error) {
|
|
1253
|
+
dnsGauge.stop(false);
|
|
1243
1254
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1244
1255
|
log.warn(`DNS auto-registration failed: ${msg}`);
|
|
1245
1256
|
dnsRegistered = false;
|