Skip to content

Commit 24b81c5

Browse files
Nik SamokhvalovNik Samokhvalov
authored andcommitted
feat(cli): auto-generate monitoring password in init
- Default to generated password (no interactive prompt) - Still allow --password / PGAI_MON_PASSWORD overrides - Print generated password once for safe copy
1 parent 6145ead commit 24b81c5

File tree

3 files changed

+31
-31
lines changed

3 files changed

+31
-31
lines changed

cli/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ npx postgresai init -h host -p 5432 -U admin -d dbname
5959
Password input options (in priority order):
6060
- `--password <password>`
6161
- `PGAI_MON_PASSWORD` environment variable
62-
- interactive prompt (TTY only)
62+
- if not provided: a strong password is generated automatically and printed once
6363

6464
Optional permissions (RDS/self-managed extras from the root `README.md`) are enabled by default. To skip them:
6565

cli/bin/postgres-ai.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -159,20 +159,6 @@ program
159159
return;
160160
}
161161

162-
let monPassword: string;
163-
try {
164-
monPassword = await resolveMonitoringPassword({
165-
passwordFlag: opts.password,
166-
passwordEnv: process.env.PGAI_MON_PASSWORD,
167-
monitoringUser: opts.monitoringUser,
168-
});
169-
} catch (e) {
170-
const msg = e instanceof Error ? e.message : String(e);
171-
console.error(`✗ ${msg}`);
172-
process.exitCode = 1;
173-
return;
174-
}
175-
176162
const includeOptionalPermissions = !opts.skipOptionalPermissions;
177163

178164
console.log(`Connecting to: ${adminConn.display}`);
@@ -197,6 +183,25 @@ program
197183
throw new Error("Failed to resolve current database name");
198184
}
199185

186+
let monPassword: string;
187+
try {
188+
const resolved = await resolveMonitoringPassword({
189+
passwordFlag: opts.password,
190+
passwordEnv: process.env.PGAI_MON_PASSWORD,
191+
monitoringUser: opts.monitoringUser,
192+
});
193+
monPassword = resolved.password;
194+
if (resolved.generated) {
195+
console.log(`Generated password for monitoring user ${opts.monitoringUser}: ${monPassword}`);
196+
console.log("Store it securely (or rerun with --password / PGAI_MON_PASSWORD to set your own).");
197+
}
198+
} catch (e) {
199+
const msg = e instanceof Error ? e.message : String(e);
200+
console.error(`✗ ${msg}`);
201+
process.exitCode = 1;
202+
return;
203+
}
204+
200205
const plan = await buildInitPlan({
201206
database,
202207
monitoringUser: opts.monitoringUser,

cli/lib/init.ts

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as readline from "readline";
2+
import { randomBytes } from "crypto";
23
import { URL } from "url";
34
import type { Client as PgClient } from "pg";
45

@@ -268,31 +269,25 @@ export async function promptHidden(prompt: string): Promise<string> {
268269
});
269270
}
270271

272+
function generateMonitoringPassword(): string {
273+
// URL-safe and easy to copy/paste; length ~32 chars.
274+
return randomBytes(24).toString("base64url");
275+
}
276+
271277
export async function resolveMonitoringPassword(opts: {
272278
passwordFlag?: string;
273279
passwordEnv?: string;
274280
prompt?: (prompt: string) => Promise<string>;
275281
monitoringUser: string;
276-
}): Promise<string> {
282+
}): Promise<{ password: string; generated: boolean }> {
277283
const fromFlag = (opts.passwordFlag || "").trim();
278-
if (fromFlag) return fromFlag;
284+
if (fromFlag) return { password: fromFlag, generated: false };
279285

280286
const fromEnv = (opts.passwordEnv || "").trim();
281-
if (fromEnv) return fromEnv;
282-
283-
if (!process.stdin.isTTY) {
284-
throw new Error(
285-
"Monitoring user password is required in non-interactive mode (use --password or PGAI_MON_PASSWORD)"
286-
);
287-
}
287+
if (fromEnv) return { password: fromEnv, generated: false };
288288

289-
const prompter = opts.prompt || promptHidden;
290-
while (true) {
291-
const pw = (await prompter(`Enter password for monitoring user ${opts.monitoringUser}: `)).trim();
292-
if (pw) return pw;
293-
// eslint-disable-next-line no-console
294-
console.error("Password cannot be empty");
295-
}
289+
// Default: auto-generate (safer than prompting; works in non-interactive mode).
290+
return { password: generateMonitoringPassword(), generated: true };
296291
}
297292

298293
export async function buildInitPlan(params: {

0 commit comments

Comments
 (0)