@@ -267,23 +267,308 @@ const mon = program.command("mon").description("monitoring services management")
267267mon
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 ( / ^ p o s t g r e s q l : \/ \/ [ ^ @ ] + @ ( [ ^ : / ] + ) / ) ;
377+ const autoInstanceName = match ? match [ 1 ] : "db-instance" ;
378+
379+ const connStr = opts . dbUrl ;
380+ const m = connStr . match ( / ^ p o s t g r e s q l : \/ \/ ( [ ^ : ] + ) : ( [ ^ @ ] + ) @ ( [ ^ : \/ ] + ) (?: : ( \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 - z A - Z 0 - 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 ( / ^ p o s t g r e s q l : \/ \/ ( [ ^ : ] + ) : ( [ ^ @ ] + ) @ ( [ ^ : \/ ] + ) (?: : ( \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 - z A - Z 0 - 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 ( / ^ g r a f a n a _ p a s s w o r d = ( [ ^ \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 ) => ! / ^ g r a f a n a _ p a s s w o r d = / . 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
289574mon
0 commit comments