postgresai 0.11.0-alpha.5 → 0.11.0-alpha.7
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/postgres-ai.js +268 -5
- package/package.json +1 -1
package/bin/postgres-ai.js
CHANGED
|
@@ -191,12 +191,166 @@ program
|
|
|
191
191
|
const code = await runCompose(["run", "--rm", "sources-generator"]);
|
|
192
192
|
if (code !== 0) process.exitCode = code;
|
|
193
193
|
});
|
|
194
|
-
program
|
|
194
|
+
program
|
|
195
|
+
.command("update")
|
|
196
|
+
.description("update project")
|
|
197
|
+
.action(async () => {
|
|
198
|
+
const { exec } = require("child_process");
|
|
199
|
+
const util = require("util");
|
|
200
|
+
const execPromise = util.promisify(exec);
|
|
201
|
+
const fs = require("fs");
|
|
202
|
+
const path = require("path");
|
|
203
|
+
|
|
204
|
+
console.log("Updating PostgresAI monitoring stack...\n");
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
// Check if we're in a git repo
|
|
208
|
+
const gitDir = path.resolve(process.cwd(), ".git");
|
|
209
|
+
if (!fs.existsSync(gitDir)) {
|
|
210
|
+
console.error("Not a git repository. Cannot update.");
|
|
211
|
+
process.exitCode = 1;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Fetch latest changes
|
|
216
|
+
console.log("Fetching latest changes...");
|
|
217
|
+
await execPromise("git fetch origin");
|
|
218
|
+
|
|
219
|
+
// Check current branch
|
|
220
|
+
const { stdout: branch } = await execPromise("git rev-parse --abbrev-ref HEAD");
|
|
221
|
+
const currentBranch = branch.trim();
|
|
222
|
+
console.log(`Current branch: ${currentBranch}`);
|
|
223
|
+
|
|
224
|
+
// Pull latest changes
|
|
225
|
+
console.log("Pulling latest changes...");
|
|
226
|
+
const { stdout: pullOut } = await execPromise("git pull origin " + currentBranch);
|
|
227
|
+
console.log(pullOut);
|
|
228
|
+
|
|
229
|
+
// Update Docker images
|
|
230
|
+
console.log("\nUpdating Docker images...");
|
|
231
|
+
const code = await runCompose(["pull"]);
|
|
232
|
+
|
|
233
|
+
if (code === 0) {
|
|
234
|
+
console.log("\n✓ Update completed successfully");
|
|
235
|
+
console.log("\nTo apply updates, restart services:");
|
|
236
|
+
console.log(" postgres-ai restart");
|
|
237
|
+
} else {
|
|
238
|
+
console.error("\n✗ Docker image update failed");
|
|
239
|
+
process.exitCode = 1;
|
|
240
|
+
}
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.error(`Update failed: ${error.message}`);
|
|
243
|
+
process.exitCode = 1;
|
|
244
|
+
}
|
|
245
|
+
});
|
|
195
246
|
program
|
|
196
247
|
.command("reset [service]")
|
|
197
248
|
.description("reset all or specific service")
|
|
198
|
-
.action(
|
|
199
|
-
|
|
249
|
+
.action(async (service) => {
|
|
250
|
+
const readline = require("readline");
|
|
251
|
+
const rl = readline.createInterface({
|
|
252
|
+
input: process.stdin,
|
|
253
|
+
output: process.stdout,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const question = (prompt) => new Promise((resolve) => rl.question(prompt, resolve));
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
if (service) {
|
|
260
|
+
// Reset specific service
|
|
261
|
+
console.log(`\nThis will stop '${service}', remove its volume, and restart it.`);
|
|
262
|
+
console.log("All data for this service will be lost!\n");
|
|
263
|
+
|
|
264
|
+
const answer = await question("Continue? (y/N): ");
|
|
265
|
+
if (answer.toLowerCase() !== "y") {
|
|
266
|
+
console.log("Cancelled");
|
|
267
|
+
rl.close();
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
console.log(`\nStopping ${service}...`);
|
|
272
|
+
await runCompose(["stop", service]);
|
|
273
|
+
|
|
274
|
+
console.log(`Removing volume for ${service}...`);
|
|
275
|
+
await runCompose(["rm", "-f", "-v", service]);
|
|
276
|
+
|
|
277
|
+
console.log(`Restarting ${service}...`);
|
|
278
|
+
const code = await runCompose(["up", "-d", service]);
|
|
279
|
+
|
|
280
|
+
if (code === 0) {
|
|
281
|
+
console.log(`\n✓ Service '${service}' has been reset`);
|
|
282
|
+
} else {
|
|
283
|
+
console.error(`\n✗ Failed to restart '${service}'`);
|
|
284
|
+
process.exitCode = 1;
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
// Reset all services
|
|
288
|
+
console.log("\nThis will stop all services and remove all data!");
|
|
289
|
+
console.log("Volumes, networks, and containers will be deleted.\n");
|
|
290
|
+
|
|
291
|
+
const answer = await question("Continue? (y/N): ");
|
|
292
|
+
if (answer.toLowerCase() !== "y") {
|
|
293
|
+
console.log("Cancelled");
|
|
294
|
+
rl.close();
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
console.log("\nStopping services and removing data...");
|
|
299
|
+
const downCode = await runCompose(["down", "-v"]);
|
|
300
|
+
|
|
301
|
+
if (downCode === 0) {
|
|
302
|
+
console.log("✓ Environment reset completed - all containers and data removed");
|
|
303
|
+
} else {
|
|
304
|
+
console.error("✗ Reset failed");
|
|
305
|
+
process.exitCode = 1;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
rl.close();
|
|
310
|
+
} catch (error) {
|
|
311
|
+
rl.close();
|
|
312
|
+
console.error(`Reset failed: ${error.message}`);
|
|
313
|
+
process.exitCode = 1;
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
program
|
|
317
|
+
.command("clean")
|
|
318
|
+
.description("cleanup artifacts")
|
|
319
|
+
.action(async () => {
|
|
320
|
+
const { exec } = require("child_process");
|
|
321
|
+
const util = require("util");
|
|
322
|
+
const execPromise = util.promisify(exec);
|
|
323
|
+
|
|
324
|
+
console.log("Cleaning up Docker resources...\n");
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
// Remove stopped containers
|
|
328
|
+
const { stdout: containers } = await execPromise("docker ps -aq --filter 'status=exited'");
|
|
329
|
+
if (containers.trim()) {
|
|
330
|
+
await execPromise(`docker rm ${containers.trim().split('\n').join(' ')}`);
|
|
331
|
+
console.log("✓ Removed stopped containers");
|
|
332
|
+
} else {
|
|
333
|
+
console.log("✓ No stopped containers to remove");
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Remove unused volumes
|
|
337
|
+
const { stdout: volumeOut } = await execPromise("docker volume prune -f");
|
|
338
|
+
console.log("✓ Removed unused volumes");
|
|
339
|
+
|
|
340
|
+
// Remove unused networks
|
|
341
|
+
const { stdout: networkOut } = await execPromise("docker network prune -f");
|
|
342
|
+
console.log("✓ Removed unused networks");
|
|
343
|
+
|
|
344
|
+
// Remove dangling images
|
|
345
|
+
const { stdout: imageOut } = await execPromise("docker image prune -f");
|
|
346
|
+
console.log("✓ Removed dangling images");
|
|
347
|
+
|
|
348
|
+
console.log("\nCleanup completed");
|
|
349
|
+
} catch (error) {
|
|
350
|
+
console.error(`Error during cleanup: ${error.message}`);
|
|
351
|
+
process.exitCode = 1;
|
|
352
|
+
}
|
|
353
|
+
});
|
|
200
354
|
program
|
|
201
355
|
.command("shell <service>")
|
|
202
356
|
.description("open service shell")
|
|
@@ -330,7 +484,69 @@ program
|
|
|
330
484
|
program
|
|
331
485
|
.command("test-instance <name>")
|
|
332
486
|
.description("test instance connectivity")
|
|
333
|
-
.action(
|
|
487
|
+
.action(async (name) => {
|
|
488
|
+
const fs = require("fs");
|
|
489
|
+
const path = require("path");
|
|
490
|
+
const { exec } = require("child_process");
|
|
491
|
+
const util = require("util");
|
|
492
|
+
const execPromise = util.promisify(exec);
|
|
493
|
+
|
|
494
|
+
const instancesPath = path.resolve(process.cwd(), "instances.yml");
|
|
495
|
+
if (!fs.existsSync(instancesPath)) {
|
|
496
|
+
console.error("instances.yml not found");
|
|
497
|
+
process.exitCode = 1;
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const content = fs.readFileSync(instancesPath, "utf8");
|
|
502
|
+
const lines = content.split(/\r?\n/);
|
|
503
|
+
let connStr = "";
|
|
504
|
+
let foundInstance = false;
|
|
505
|
+
|
|
506
|
+
for (let i = 0; i < lines.length; i++) {
|
|
507
|
+
const nameLine = lines[i].match(/^-[\t ]*name:[\t ]*(.+)$/);
|
|
508
|
+
if (nameLine && nameLine[1].trim() === name) {
|
|
509
|
+
foundInstance = true;
|
|
510
|
+
// Look for conn_str in next lines
|
|
511
|
+
for (let j = i + 1; j < lines.length && j < i + 15; j++) {
|
|
512
|
+
const connLine = lines[j].match(/^[\t ]*conn_str:[\t ]*(.+)$/);
|
|
513
|
+
if (connLine) {
|
|
514
|
+
connStr = connLine[1].trim();
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
// Stop at next instance
|
|
518
|
+
if (lines[j].match(/^-[\t ]*name:/)) break;
|
|
519
|
+
}
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (!foundInstance) {
|
|
525
|
+
console.error(`Instance '${name}' not found`);
|
|
526
|
+
process.exitCode = 1;
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (!connStr) {
|
|
531
|
+
console.error(`Connection string not found for instance '${name}'`);
|
|
532
|
+
process.exitCode = 1;
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
console.log(`Testing connection to '${name}'...`);
|
|
537
|
+
|
|
538
|
+
try {
|
|
539
|
+
const { stdout, stderr } = await execPromise(
|
|
540
|
+
`psql "${connStr}" -c "SELECT version();" --no-psqlrc`,
|
|
541
|
+
{ timeout: 10000, env: { ...process.env, PAGER: 'cat' } }
|
|
542
|
+
);
|
|
543
|
+
console.log(`✓ Connection successful`);
|
|
544
|
+
console.log(stdout.trim());
|
|
545
|
+
} catch (error) {
|
|
546
|
+
console.error(`✗ Connection failed: ${error.message}`);
|
|
547
|
+
process.exitCode = 1;
|
|
548
|
+
}
|
|
549
|
+
});
|
|
334
550
|
|
|
335
551
|
// API key and grafana
|
|
336
552
|
program
|
|
@@ -400,7 +616,54 @@ program
|
|
|
400
616
|
program
|
|
401
617
|
.command("generate-grafana-password")
|
|
402
618
|
.description("generate Grafana password")
|
|
403
|
-
.action(
|
|
619
|
+
.action(async () => {
|
|
620
|
+
const fs = require("fs");
|
|
621
|
+
const path = require("path");
|
|
622
|
+
const { exec } = require("child_process");
|
|
623
|
+
const util = require("util");
|
|
624
|
+
const execPromise = util.promisify(exec);
|
|
625
|
+
|
|
626
|
+
const cfgPath = path.resolve(process.cwd(), ".pgwatch-config");
|
|
627
|
+
|
|
628
|
+
try {
|
|
629
|
+
// Generate secure password using openssl
|
|
630
|
+
const { stdout: password } = await execPromise(
|
|
631
|
+
"openssl rand -base64 12 | tr -d '\n'"
|
|
632
|
+
);
|
|
633
|
+
const newPassword = password.trim();
|
|
634
|
+
|
|
635
|
+
if (!newPassword) {
|
|
636
|
+
console.error("Failed to generate password");
|
|
637
|
+
process.exitCode = 1;
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Read existing config
|
|
642
|
+
let config = "";
|
|
643
|
+
if (fs.existsSync(cfgPath)) {
|
|
644
|
+
config = fs.readFileSync(cfgPath, "utf8");
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Update or add grafana_password
|
|
648
|
+
const lines = config.split(/\r?\n/).filter((l) => !/^grafana_password=/.test(l));
|
|
649
|
+
lines.push(`grafana_password=${newPassword}`);
|
|
650
|
+
|
|
651
|
+
// Write back
|
|
652
|
+
fs.writeFileSync(cfgPath, lines.filter(Boolean).join("\n") + "\n", "utf8");
|
|
653
|
+
|
|
654
|
+
console.log("✓ New Grafana password generated and saved");
|
|
655
|
+
console.log("\nNew credentials:");
|
|
656
|
+
console.log(" URL: http://localhost:3000");
|
|
657
|
+
console.log(" Username: monitor");
|
|
658
|
+
console.log(` Password: ${newPassword}`);
|
|
659
|
+
console.log("\nRestart Grafana to apply:");
|
|
660
|
+
console.log(" postgres-ai restart grafana");
|
|
661
|
+
} catch (error) {
|
|
662
|
+
console.error(`Failed to generate password: ${error.message}`);
|
|
663
|
+
console.error("\nNote: This command requires 'openssl' to be installed");
|
|
664
|
+
process.exitCode = 1;
|
|
665
|
+
}
|
|
666
|
+
});
|
|
404
667
|
program
|
|
405
668
|
.command("show-grafana-credentials")
|
|
406
669
|
.description("show Grafana credentials")
|