🌐 AI搜索 & 代理 主页
Skip to content

Commit c7ff9b5

Browse files
added interactive database setup to quickstart
Support --api-key, --db-url, and -y flags for automated setup. Clears instances.yml in production mode for clean configuration.
1 parent 137990e commit c7ff9b5

File tree

2 files changed

+317
-6
lines changed

2 files changed

+317
-6
lines changed

cli/README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,19 @@ Start monitoring with demo database:
5555
postgres-ai mon quickstart --demo
5656
```
5757

58+
Start monitoring with your own database:
59+
```bash
60+
postgres-ai mon quickstart --db-url postgresql://user:pass@host:5432/db
61+
```
62+
63+
Complete automated setup with API key and database:
64+
```bash
65+
postgres-ai mon quickstart --api-key your_key --db-url postgresql://user:pass@host:5432/db -y
66+
```
67+
5868
This will:
69+
- Configure API key for automated report uploads (if provided)
70+
- Add PostgreSQL instance to monitor (if provided)
5971
- Generate secure Grafana password
6072
- Start all monitoring services
6173
- Open Grafana at http://localhost:3000
@@ -66,14 +78,28 @@ This will:
6678

6779
#### Service lifecycle
6880
```bash
69-
postgres-ai mon quickstart [--demo] # Complete setup (generate config, start services)
81+
# Complete setup with various options
82+
postgres-ai mon quickstart # Interactive setup for production
83+
postgres-ai mon quickstart --demo # Demo mode with sample database
84+
postgres-ai mon quickstart --api-key <key> # Setup with API key
85+
postgres-ai mon quickstart --db-url <url> # Setup with database URL
86+
postgres-ai mon quickstart --api-key <key> --db-url <url> # Complete automated setup
87+
postgres-ai mon quickstart -y # Auto-accept all defaults
88+
89+
# Service management
7090
postgres-ai mon start # Start monitoring services
7191
postgres-ai mon stop # Stop monitoring services
7292
postgres-ai mon restart [service] # Restart all or specific monitoring service
7393
postgres-ai mon status # Show monitoring services status
7494
postgres-ai mon health [--wait <sec>] # Check monitoring services health
7595
```
7696

97+
##### Quickstart options
98+
- `--demo` - Demo mode with sample database (testing only, cannot use with --api-key)
99+
- `--api-key <key>` - Postgres AI API key for automated report uploads
100+
- `--db-url <url>` - PostgreSQL connection URL to monitor (format: `postgresql://user:pass@host:port/db`)
101+
- `-y, --yes` - Accept all defaults and skip interactive prompts
102+
77103
#### Monitoring target databases (`mon targets` subgroup)
78104
```bash
79105
postgres-ai mon targets list # List databases to monitor

cli/bin/postgres-ai.ts

Lines changed: 290 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -267,23 +267,308 @@ const mon = program.command("mon").description("monitoring services management")
267267
mon
268268
.command("quickstart")
269269
.description("complete setup (generate config, start monitoring services)")
270-
.option("--demo", "demo mode", false)
271-
.action(async () => {
270+
.option("--demo", "demo mode with sample database", false)
271+
.option("--api-key <key>", "Postgres AI API key for automated report uploads")
272+
.option("--db-url <url>", "PostgreSQL connection URL to monitor")
273+
.option("-y, --yes", "accept all defaults and skip interactive prompts", false)
274+
.action(async (opts: { demo: boolean; apiKey?: string; dbUrl?: string; yes: boolean }) => {
275+
console.log("\n=================================");
276+
console.log(" PostgresAI Monitoring Quickstart");
277+
console.log("=================================\n");
278+
console.log("This will install, configure, and start the monitoring system\n");
279+
280+
// Validate conflicting options
281+
if (opts.demo && opts.dbUrl) {
282+
console.log("⚠ Both --demo and --db-url provided. Demo mode includes its own database.");
283+
console.log("⚠ The --db-url will be ignored in demo mode.\n");
284+
opts.dbUrl = undefined;
285+
}
286+
287+
if (opts.demo && opts.apiKey) {
288+
console.error("✗ Cannot use --api-key with --demo mode");
289+
console.error("✗ Demo mode is for testing only and does not support API key integration");
290+
console.error("\nUse demo mode without API key: postgres-ai mon quickstart --demo");
291+
console.error("Or use production mode with API key: postgres-ai mon quickstart --api-key=your_key");
292+
process.exitCode = 1;
293+
return;
294+
}
295+
272296
// Check if containers are already running
273297
const { running, containers } = checkRunningContainers();
274298
if (running) {
275-
console.log(`Monitoring services are already running: ${containers.join(", ")}`);
276-
console.log("Use 'postgres-ai mon restart' to restart them");
299+
console.log(`Monitoring services are already running: ${containers.join(", ")}`);
300+
console.log("Use 'postgres-ai mon restart' to restart them\n");
277301
return;
278302
}
279303

304+
// Step 1: API key configuration (only in production mode)
305+
if (!opts.demo) {
306+
console.log("Step 1: Postgres AI API Configuration (Optional)");
307+
console.log("An API key enables automatic upload of PostgreSQL reports to Postgres AI\n");
308+
309+
if (opts.apiKey) {
310+
console.log("Using API key provided via --api-key parameter");
311+
config.writeConfig({ apiKey: opts.apiKey });
312+
console.log("✓ API key saved\n");
313+
} else if (opts.yes) {
314+
// Auto-yes mode without API key - skip API key setup
315+
console.log("Auto-yes mode: no API key provided, skipping API key setup");
316+
console.log("⚠ Reports will be generated locally only");
317+
console.log("You can add an API key later with: postgres-ai add-key <api_key>\n");
318+
} else {
319+
const rl = readline.createInterface({
320+
input: process.stdin,
321+
output: process.stdout
322+
});
323+
324+
const question = (prompt: string): Promise<string> =>
325+
new Promise((resolve) => rl.question(prompt, resolve));
326+
327+
try {
328+
const answer = await question("Do you have a Postgres AI API key? (Y/n): ");
329+
const proceedWithApiKey = !answer || answer.toLowerCase() === "y";
330+
331+
if (proceedWithApiKey) {
332+
while (true) {
333+
const inputApiKey = await question("Enter your Postgres AI API key: ");
334+
const trimmedKey = inputApiKey.trim();
335+
336+
if (trimmedKey) {
337+
config.writeConfig({ apiKey: trimmedKey });
338+
console.log("✓ API key saved\n");
339+
break;
340+
}
341+
342+
console.log("⚠ API key cannot be empty");
343+
const retry = await question("Try again or skip API key setup, retry? (Y/n): ");
344+
if (retry.toLowerCase() === "n") {
345+
console.log("⚠ Skipping API key setup - reports will be generated locally only");
346+
console.log("You can add an API key later with: postgres-ai add-key <api_key>\n");
347+
break;
348+
}
349+
}
350+
} else {
351+
console.log("⚠ Skipping API key setup - reports will be generated locally only");
352+
console.log("You can add an API key later with: postgres-ai add-key <api_key>\n");
353+
}
354+
} finally {
355+
rl.close();
356+
}
357+
}
358+
} else {
359+
console.log("Step 1: Demo mode - API key configuration skipped");
360+
console.log("Demo mode is for testing only and does not support API key integration\n");
361+
}
362+
363+
// Step 2: Add PostgreSQL instance (if not demo mode)
364+
if (!opts.demo) {
365+
console.log("Step 2: Add PostgreSQL Instance to Monitor\n");
366+
367+
// Clear instances.yml in production mode (start fresh)
368+
const instancesPath = path.resolve(process.cwd(), "instances.yml");
369+
const emptyInstancesContent = "# PostgreSQL instances to monitor\n# Add your instances using: postgres-ai mon targets add\n\n";
370+
fs.writeFileSync(instancesPath, emptyInstancesContent, "utf8");
371+
372+
if (opts.dbUrl) {
373+
console.log("Using database URL provided via --db-url parameter");
374+
console.log(`Adding PostgreSQL instance from: ${opts.dbUrl}\n`);
375+
376+
const match = opts.dbUrl.match(/^postgresql:\/\/[^@]+@([^:/]+)/);
377+
const autoInstanceName = match ? match[1] : "db-instance";
378+
379+
const connStr = opts.dbUrl;
380+
const m = connStr.match(/^postgresql:\/\/([^:]+):([^@]+)@([^:\/]+)(?::(\d+))?\/(.+)$/);
381+
382+
if (!m) {
383+
console.error("✗ Invalid connection string format");
384+
process.exitCode = 1;
385+
return;
386+
}
387+
388+
const host = m[3];
389+
const db = m[5];
390+
const instanceName = `${host}-${db}`.replace(/[^a-zA-Z0-9-]/g, "-");
391+
392+
const body = `- name: ${instanceName}\n conn_str: ${connStr}\n preset_metrics: full\n custom_metrics:\n is_enabled: true\n group: default\n custom_tags:\n env: production\n cluster: default\n node_name: ${instanceName}\n sink_type: ~sink_type~\n`;
393+
fs.appendFileSync(instancesPath, body, "utf8");
394+
console.log(`✓ Monitoring target '${instanceName}' added\n`);
395+
396+
// Test connection
397+
console.log("Testing connection to the added instance...");
398+
try {
399+
const { Client } = require("pg");
400+
const client = new Client({ connectionString: connStr });
401+
await client.connect();
402+
const result = await client.query("select version();");
403+
console.log("✓ Connection successful");
404+
console.log(`${result.rows[0].version}\n`);
405+
await client.end();
406+
} catch (error) {
407+
const message = error instanceof Error ? error.message : String(error);
408+
console.error(`✗ Connection failed: ${message}\n`);
409+
}
410+
} else if (opts.yes) {
411+
// Auto-yes mode without database URL - skip database setup
412+
console.log("Auto-yes mode: no database URL provided, skipping database setup");
413+
console.log("⚠ No PostgreSQL instance added");
414+
console.log("You can add one later with: postgres-ai mon targets add\n");
415+
} else {
416+
const rl = readline.createInterface({
417+
input: process.stdin,
418+
output: process.stdout
419+
});
420+
421+
const question = (prompt: string): Promise<string> =>
422+
new Promise((resolve) => rl.question(prompt, resolve));
423+
424+
try {
425+
console.log("You need to add at least one PostgreSQL instance to monitor");
426+
const answer = await question("Do you want to add a PostgreSQL instance now? (Y/n): ");
427+
const proceedWithInstance = !answer || answer.toLowerCase() === "y";
428+
429+
if (proceedWithInstance) {
430+
console.log("\nYou can provide either:");
431+
console.log(" 1. A full connection string: postgresql://user:pass@host:port/database");
432+
console.log(" 2. Press Enter to skip for now\n");
433+
434+
const connStr = await question("Enter connection string (or press Enter to skip): ");
435+
436+
if (connStr.trim()) {
437+
const m = connStr.match(/^postgresql:\/\/([^:]+):([^@]+)@([^:\/]+)(?::(\d+))?\/(.+)$/);
438+
if (!m) {
439+
console.error("✗ Invalid connection string format");
440+
console.log("⚠ Continuing without adding instance\n");
441+
} else {
442+
const host = m[3];
443+
const db = m[5];
444+
const instanceName = `${host}-${db}`.replace(/[^a-zA-Z0-9-]/g, "-");
445+
446+
const body = `- name: ${instanceName}\n conn_str: ${connStr}\n preset_metrics: full\n custom_metrics:\n is_enabled: true\n group: default\n custom_tags:\n env: production\n cluster: default\n node_name: ${instanceName}\n sink_type: ~sink_type~\n`;
447+
fs.appendFileSync(instancesPath, body, "utf8");
448+
console.log(`✓ Monitoring target '${instanceName}' added\n`);
449+
450+
// Test connection
451+
console.log("Testing connection to the added instance...");
452+
try {
453+
const { Client } = require("pg");
454+
const client = new Client({ connectionString: connStr });
455+
await client.connect();
456+
const result = await client.query("select version();");
457+
console.log("✓ Connection successful");
458+
console.log(`${result.rows[0].version}\n`);
459+
await client.end();
460+
} catch (error) {
461+
const message = error instanceof Error ? error.message : String(error);
462+
console.error(`✗ Connection failed: ${message}\n`);
463+
}
464+
}
465+
} else {
466+
console.log("⚠ No PostgreSQL instance added - you can add one later with: postgres-ai mon targets add\n");
467+
}
468+
} else {
469+
console.log("⚠ No PostgreSQL instance added - you can add one later with: postgres-ai mon targets add\n");
470+
}
471+
} finally {
472+
rl.close();
473+
}
474+
}
475+
} else {
476+
console.log("Step 2: Demo mode enabled - using included demo PostgreSQL database\n");
477+
}
478+
479+
// Step 3: Update configuration
480+
console.log(opts.demo ? "Step 3: Updating configuration..." : "Step 3: Updating configuration...");
280481
const code1 = await runCompose(["run", "--rm", "sources-generator"]);
281482
if (code1 !== 0) {
282483
process.exitCode = code1;
283484
return;
284485
}
486+
console.log("✓ Configuration updated\n");
487+
488+
// Step 4: Ensure Grafana password is configured
489+
console.log(opts.demo ? "Step 4: Configuring Grafana security..." : "Step 4: Configuring Grafana security...");
490+
const cfgPath = path.resolve(process.cwd(), ".pgwatch-config");
491+
let grafanaPassword = "";
492+
493+
try {
494+
if (fs.existsSync(cfgPath)) {
495+
const stats = fs.statSync(cfgPath);
496+
if (!stats.isDirectory()) {
497+
const content = fs.readFileSync(cfgPath, "utf8");
498+
const match = content.match(/^grafana_password=([^\r\n]+)/m);
499+
if (match) {
500+
grafanaPassword = match[1].trim();
501+
}
502+
}
503+
}
504+
505+
if (!grafanaPassword) {
506+
console.log("Generating secure Grafana password...");
507+
const { stdout: password } = await execPromise("openssl rand -base64 12 | tr -d '\n'");
508+
grafanaPassword = password.trim();
509+
510+
let configContent = "";
511+
if (fs.existsSync(cfgPath)) {
512+
const stats = fs.statSync(cfgPath);
513+
if (!stats.isDirectory()) {
514+
configContent = fs.readFileSync(cfgPath, "utf8");
515+
}
516+
}
517+
518+
const lines = configContent.split(/\r?\n/).filter((l) => !/^grafana_password=/.test(l));
519+
lines.push(`grafana_password=${grafanaPassword}`);
520+
fs.writeFileSync(cfgPath, lines.filter(Boolean).join("\n") + "\n", "utf8");
521+
}
522+
523+
console.log("✓ Grafana password configured\n");
524+
} catch (error) {
525+
console.log("⚠ Could not generate Grafana password automatically");
526+
console.log("Using default password: demo\n");
527+
grafanaPassword = "demo";
528+
}
529+
530+
// Step 5: Start services
531+
console.log(opts.demo ? "Step 5: Starting monitoring services..." : "Step 5: Starting monitoring services...");
285532
const code2 = await runCompose(["up", "-d"]);
286-
if (code2 !== 0) process.exitCode = code2;
533+
if (code2 !== 0) {
534+
process.exitCode = code2;
535+
return;
536+
}
537+
console.log("✓ Services started\n");
538+
539+
// Final summary
540+
console.log("=================================");
541+
console.log(" 🎉 Quickstart setup completed!");
542+
console.log("=================================\n");
543+
544+
console.log("What's running:");
545+
if (opts.demo) {
546+
console.log(" ✅ Demo PostgreSQL database (monitoring target)");
547+
}
548+
console.log(" ✅ PostgreSQL monitoring infrastructure");
549+
console.log(" ✅ Grafana dashboards (with secure password)");
550+
console.log(" ✅ Prometheus metrics storage");
551+
console.log(" ✅ Flask API backend");
552+
console.log(" ✅ Automated report generation (every 24h)");
553+
console.log(" ✅ Host stats monitoring (CPU, memory, disk, I/O)\n");
554+
555+
if (!opts.demo) {
556+
console.log("Next steps:");
557+
console.log(" • Add more PostgreSQL instances: postgres-ai mon targets add");
558+
console.log(" • View configured instances: postgres-ai mon targets list");
559+
console.log(" • Check service health: postgres-ai mon health\n");
560+
} else {
561+
console.log("Demo mode next steps:");
562+
console.log(" • Explore Grafana dashboards at http://localhost:3000");
563+
console.log(" • Connect to demo database: postgresql://postgres:postgres@localhost:55432/target_database");
564+
console.log(" • Generate some load on the demo database to see metrics\n");
565+
}
566+
567+
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
568+
console.log("🚀 MAIN ACCESS POINT - Start here:");
569+
console.log(" Grafana Dashboard: http://localhost:3000");
570+
console.log(` Login: monitor / ${grafanaPassword}`);
571+
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
287572
});
288573

289574
mon

0 commit comments

Comments
 (0)