@justin0713/opspilot 1.0.4 → 1.0.6
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 +87 -20
- package/package.json +1 -1
package/bin/opspilot.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
const { execSync, spawnSync } = require('child_process');
|
|
19
|
+
const crypto = require('crypto');
|
|
19
20
|
const fs = require('fs');
|
|
20
21
|
const path = require('path');
|
|
21
22
|
const os = require('os');
|
|
@@ -27,7 +28,9 @@ const PKG_VERSION = require('../package.json').version;
|
|
|
27
28
|
const IMAGE = 'opspilot/local:latest';
|
|
28
29
|
const CONTAINER = 'opspilot';
|
|
29
30
|
const CONFIG_DIR = path.join(os.homedir(), '.opspilot');
|
|
30
|
-
const CONFIG_FILE
|
|
31
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
32
|
+
const CREDENTIALS_FILE = path.join(CONFIG_DIR, '.credentials'); // chmod 600
|
|
33
|
+
const SETUP_TOKEN_FILE = path.join(CONFIG_DIR, '.setup_token'); // temp, deleted after start
|
|
31
34
|
|
|
32
35
|
const CYAN = '\x1b[36m';
|
|
33
36
|
const GREEN = '\x1b[32m';
|
|
@@ -98,6 +101,17 @@ function parseArgs(argv) {
|
|
|
98
101
|
|
|
99
102
|
// ── Commands ─────────────────────────────────────────────────────────────────
|
|
100
103
|
|
|
104
|
+
function generatePassword() {
|
|
105
|
+
// 18 random bytes → 24-char base64url (no shell-special chars)
|
|
106
|
+
return crypto.randomBytes(18).toString('base64url');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function writeCredentials(password) {
|
|
110
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
111
|
+
fs.writeFileSync(CREDENTIALS_FILE, `username: admin\npassword: ${password}\n`);
|
|
112
|
+
try { fs.chmodSync(CREDENTIALS_FILE, 0o600); } catch (_) {} // best-effort on Windows
|
|
113
|
+
}
|
|
114
|
+
|
|
101
115
|
function cmdStart(flags) {
|
|
102
116
|
checkDocker();
|
|
103
117
|
|
|
@@ -115,7 +129,7 @@ function cmdStart(flags) {
|
|
|
115
129
|
);
|
|
116
130
|
}
|
|
117
131
|
|
|
118
|
-
// Persist for next run
|
|
132
|
+
// Persist token for next run (never persists password)
|
|
119
133
|
saveConfig({ token, cloudUrl: url || 'https://teams.codetop.net', port, dataDir: data });
|
|
120
134
|
|
|
121
135
|
// Stop existing container if running
|
|
@@ -132,8 +146,27 @@ function cmdStart(flags) {
|
|
|
132
146
|
log(`Pulling ${IMAGE} ...`);
|
|
133
147
|
run(`docker pull ${IMAGE}`);
|
|
134
148
|
|
|
149
|
+
// ── Secure first-run password delivery ────────────────────────────────────
|
|
150
|
+
// Detect first run: credentials file doesn't exist yet
|
|
151
|
+
const isFirstRun = !fs.existsSync(CREDENTIALS_FILE);
|
|
152
|
+
let setupMount = '';
|
|
153
|
+
|
|
154
|
+
if (isFirstRun) {
|
|
155
|
+
const password = generatePassword();
|
|
156
|
+
|
|
157
|
+
// 1. Write to host temp file (chmod 600) — never passed as CLI arg or env var
|
|
158
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
159
|
+
fs.writeFileSync(SETUP_TOKEN_FILE, password);
|
|
160
|
+
try { fs.chmodSync(SETUP_TOKEN_FILE, 0o600); } catch (_) {}
|
|
161
|
+
|
|
162
|
+
// 2. Save to credentials file (chmod 600) for `opspilot password` command
|
|
163
|
+
writeCredentials(password);
|
|
164
|
+
|
|
165
|
+
// 3. Mount into container read-only — entrypoint reads & deletes it
|
|
166
|
+
setupMount = `-v "${SETUP_TOKEN_FILE}:/app/.setup_token:ro" `;
|
|
167
|
+
}
|
|
168
|
+
|
|
135
169
|
// Volume arg: named volume or host path
|
|
136
|
-
const isAbsolute = path.isAbsolute(data);
|
|
137
170
|
const volArg = `${data}:/app/data`;
|
|
138
171
|
|
|
139
172
|
log(`Starting OpsPilot Local on port ${port} ...`);
|
|
@@ -144,19 +177,44 @@ function cmdStart(flags) {
|
|
|
144
177
|
`-e OPSPILOT_CLOUD_URL=${url || 'https://teams.codetop.net'} ` +
|
|
145
178
|
`-e OPSPILOT_CLOUD_TOKEN=${token} ` +
|
|
146
179
|
`-e OPSPILOT_PORT=5000 ` +
|
|
180
|
+
setupMount +
|
|
147
181
|
`-v ${volArg} ` +
|
|
148
182
|
`--restart unless-stopped ` +
|
|
149
183
|
`${IMAGE}`
|
|
150
184
|
);
|
|
151
185
|
|
|
186
|
+
// 4. Delete the temp setup token from host immediately after container starts
|
|
187
|
+
try { fs.unlinkSync(SETUP_TOKEN_FILE); } catch (_) {}
|
|
188
|
+
|
|
152
189
|
console.log('');
|
|
153
190
|
ok(`OpsPilot Local is running!`);
|
|
154
|
-
console.log(`${BOLD} URL
|
|
155
|
-
console.log(`${BOLD}
|
|
191
|
+
console.log(`${BOLD} URL :${RESET} http://localhost:${port}`);
|
|
192
|
+
console.log(`${BOLD} Username :${RESET} admin`);
|
|
193
|
+
if (isFirstRun) {
|
|
194
|
+
console.log(`${BOLD} Password :${RESET} run ${CYAN}opspilot password${RESET} to reveal`);
|
|
195
|
+
}
|
|
196
|
+
console.log(`${BOLD} Data :${RESET} ${path.isAbsolute(data) ? data : `docker volume '${data}'`}`);
|
|
156
197
|
console.log('');
|
|
157
|
-
console.log(` ${CYAN}opspilot
|
|
158
|
-
console.log(` ${CYAN}opspilot
|
|
159
|
-
console.log(` ${CYAN}opspilot
|
|
198
|
+
console.log(` ${CYAN}opspilot password${RESET} — show first-run admin password`);
|
|
199
|
+
console.log(` ${CYAN}opspilot logs -f${RESET} — tail live logs`);
|
|
200
|
+
console.log(` ${CYAN}opspilot stop${RESET} — stop the server`);
|
|
201
|
+
console.log(` ${CYAN}opspilot update${RESET} — upgrade to latest`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function cmdPassword() {
|
|
205
|
+
if (!fs.existsSync(CREDENTIALS_FILE)) {
|
|
206
|
+
warn('No credentials file found. Already cleared, or this is not a first-run install.');
|
|
207
|
+
warn(`File would be at: ${CREDENTIALS_FILE}`);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const content = fs.readFileSync(CREDENTIALS_FILE, 'utf8').trim();
|
|
211
|
+
console.log('');
|
|
212
|
+
console.log(`${BOLD}First-run admin credentials${RESET} (${CREDENTIALS_FILE}):`);
|
|
213
|
+
console.log('');
|
|
214
|
+
content.split('\n').forEach(line => console.log(` ${line}`));
|
|
215
|
+
console.log('');
|
|
216
|
+
console.log(`${YELLOW}Change your password after first login, then run:${RESET}`);
|
|
217
|
+
console.log(` ${CYAN}opspilot password --clear${RESET}`);
|
|
160
218
|
}
|
|
161
219
|
|
|
162
220
|
function cmdStop() {
|
|
@@ -226,18 +284,19 @@ ${BOLD}Usage:${RESET}
|
|
|
226
284
|
opspilot <command> [options]
|
|
227
285
|
|
|
228
286
|
${BOLD}Commands:${RESET}
|
|
229
|
-
${CYAN}start${RESET}
|
|
230
|
-
${CYAN}stop${RESET}
|
|
231
|
-
${CYAN}logs${RESET}
|
|
232
|
-
${CYAN}update${RESET}
|
|
233
|
-
${CYAN}status${RESET}
|
|
234
|
-
${CYAN}config${RESET}
|
|
287
|
+
${CYAN}start${RESET} Pull image and start the container
|
|
288
|
+
${CYAN}stop${RESET} Stop and remove the container
|
|
289
|
+
${CYAN}logs${RESET} Show container logs (-f to follow)
|
|
290
|
+
${CYAN}update${RESET} Pull latest image and restart
|
|
291
|
+
${CYAN}status${RESET} Show container status
|
|
292
|
+
${CYAN}config${RESET} Show saved local config
|
|
293
|
+
${CYAN}password${RESET} Show first-run admin password (--clear to delete after reading)
|
|
235
294
|
|
|
236
295
|
${BOLD}Start options:${RESET}
|
|
237
|
-
--token
|
|
238
|
-
--cloud-url
|
|
239
|
-
--port
|
|
240
|
-
--data-dir
|
|
296
|
+
--token <token> License token from OpsPilot Cloud ${YELLOW}(required)${RESET}
|
|
297
|
+
--cloud-url <url> Cloud server URL [default: https://teams.codetop.net]
|
|
298
|
+
--port <port> Local port [default: 5000]
|
|
299
|
+
--data-dir <path> Host data path or Docker volume name [default: opspilot-data]
|
|
241
300
|
|
|
242
301
|
${BOLD}Examples:${RESET}
|
|
243
302
|
npx @justin0713/opspilot start --token eyJ...
|
|
@@ -270,8 +329,16 @@ switch (cmd) {
|
|
|
270
329
|
cmdStart(Object.assign({}, cfg, parsed.flags));
|
|
271
330
|
})();
|
|
272
331
|
break;
|
|
273
|
-
case 'status':
|
|
274
|
-
case 'config':
|
|
332
|
+
case 'status': cmdStatus(); break;
|
|
333
|
+
case 'config': cmdConfig(); break;
|
|
334
|
+
case 'password':
|
|
335
|
+
if (parsed.flags.clear) {
|
|
336
|
+
try { fs.unlinkSync(CREDENTIALS_FILE); ok('Credentials file cleared.'); }
|
|
337
|
+
catch (_) { warn('No credentials file to clear.'); }
|
|
338
|
+
} else {
|
|
339
|
+
cmdPassword();
|
|
340
|
+
}
|
|
341
|
+
break;
|
|
275
342
|
case undefined:
|
|
276
343
|
case 'help':
|
|
277
344
|
case '--help':
|
package/package.json
CHANGED