postgresai 0.14.0-dev.75 → 0.14.0-dev.76
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.ts +312 -6
- package/dist/bin/postgres-ai.js +325 -15
- package/dist/sql/02.extensions.sql +8 -0
- package/dist/sql/{02.permissions.sql → 03.permissions.sql} +1 -0
- package/dist/sql/sql/02.extensions.sql +8 -0
- package/dist/sql/sql/{02.permissions.sql → 03.permissions.sql} +1 -0
- package/dist/sql/sql/uninit/01.helpers.sql +5 -0
- package/dist/sql/sql/uninit/02.permissions.sql +30 -0
- package/dist/sql/sql/uninit/03.role.sql +27 -0
- package/dist/sql/uninit/01.helpers.sql +5 -0
- package/dist/sql/uninit/02.permissions.sql +30 -0
- package/dist/sql/uninit/03.role.sql +27 -0
- package/lib/init.ts +109 -8
- package/lib/metrics-embedded.ts +1 -1
- package/package.json +1 -1
- package/sql/02.extensions.sql +8 -0
- package/sql/{02.permissions.sql → 03.permissions.sql} +1 -0
- package/sql/uninit/01.helpers.sql +5 -0
- package/sql/uninit/02.permissions.sql +30 -0
- package/sql/uninit/03.role.sql +27 -0
- package/test/init.test.ts +245 -11
- /package/dist/sql/{03.optional_rds.sql → 04.optional_rds.sql} +0 -0
- /package/dist/sql/{04.optional_self_managed.sql → 05.optional_self_managed.sql} +0 -0
- /package/dist/sql/{05.helpers.sql → 06.helpers.sql} +0 -0
- /package/dist/sql/sql/{03.optional_rds.sql → 04.optional_rds.sql} +0 -0
- /package/dist/sql/sql/{04.optional_self_managed.sql → 05.optional_self_managed.sql} +0 -0
- /package/dist/sql/sql/{05.helpers.sql → 06.helpers.sql} +0 -0
- /package/sql/{03.optional_rds.sql → 04.optional_rds.sql} +0 -0
- /package/sql/{04.optional_self_managed.sql → 05.optional_self_managed.sql} +0 -0
- /package/sql/{05.helpers.sql → 06.helpers.sql} +0 -0
package/dist/bin/postgres-ai.js
CHANGED
|
@@ -13064,7 +13064,7 @@ var {
|
|
|
13064
13064
|
// package.json
|
|
13065
13065
|
var package_default = {
|
|
13066
13066
|
name: "postgresai",
|
|
13067
|
-
version: "0.14.0-dev.
|
|
13067
|
+
version: "0.14.0-dev.76",
|
|
13068
13068
|
description: "postgres_ai CLI",
|
|
13069
13069
|
license: "Apache-2.0",
|
|
13070
13070
|
private: false,
|
|
@@ -15887,7 +15887,7 @@ var Result = import_lib.default.Result;
|
|
|
15887
15887
|
var TypeOverrides = import_lib.default.TypeOverrides;
|
|
15888
15888
|
var defaults = import_lib.default.defaults;
|
|
15889
15889
|
// package.json
|
|
15890
|
-
var version = "0.14.0-dev.
|
|
15890
|
+
var version = "0.14.0-dev.76";
|
|
15891
15891
|
var package_default2 = {
|
|
15892
15892
|
name: "postgresai",
|
|
15893
15893
|
version,
|
|
@@ -24961,7 +24961,11 @@ end $$;`;
|
|
|
24961
24961
|
const roleSql = applyTemplate(loadSqlTemplate("01.role.sql"), { ...vars, ROLE_STMT: roleStmt });
|
|
24962
24962
|
steps.push({ name: "01.role", sql: roleSql });
|
|
24963
24963
|
}
|
|
24964
|
-
|
|
24964
|
+
steps.push({
|
|
24965
|
+
name: "02.extensions",
|
|
24966
|
+
sql: loadSqlTemplate("02.extensions.sql")
|
|
24967
|
+
});
|
|
24968
|
+
let permissionsSql = applyTemplate(loadSqlTemplate("03.permissions.sql"), vars);
|
|
24965
24969
|
if (SKIP_ALTER_USER_PROVIDERS.includes(provider)) {
|
|
24966
24970
|
permissionsSql = permissionsSql.split(`
|
|
24967
24971
|
`).filter((line) => {
|
|
@@ -24973,21 +24977,21 @@ end $$;`;
|
|
|
24973
24977
|
`);
|
|
24974
24978
|
}
|
|
24975
24979
|
steps.push({
|
|
24976
|
-
name: "
|
|
24980
|
+
name: "03.permissions",
|
|
24977
24981
|
sql: permissionsSql
|
|
24978
24982
|
});
|
|
24979
24983
|
steps.push({
|
|
24980
|
-
name: "
|
|
24981
|
-
sql: applyTemplate(loadSqlTemplate("
|
|
24984
|
+
name: "06.helpers",
|
|
24985
|
+
sql: applyTemplate(loadSqlTemplate("06.helpers.sql"), vars)
|
|
24982
24986
|
});
|
|
24983
24987
|
if (params.includeOptionalPermissions) {
|
|
24984
24988
|
steps.push({
|
|
24985
|
-
name: "
|
|
24986
|
-
sql: applyTemplate(loadSqlTemplate("
|
|
24989
|
+
name: "04.optional_rds",
|
|
24990
|
+
sql: applyTemplate(loadSqlTemplate("04.optional_rds.sql"), vars),
|
|
24987
24991
|
optional: true
|
|
24988
24992
|
}, {
|
|
24989
|
-
name: "
|
|
24990
|
-
sql: applyTemplate(loadSqlTemplate("
|
|
24993
|
+
name: "05.optional_self_managed",
|
|
24994
|
+
sql: applyTemplate(loadSqlTemplate("05.optional_self_managed.sql"), vars),
|
|
24991
24995
|
optional: true
|
|
24992
24996
|
});
|
|
24993
24997
|
}
|
|
@@ -25055,6 +25059,62 @@ async function applyInitPlan(params) {
|
|
|
25055
25059
|
}
|
|
25056
25060
|
return { applied, skippedOptional };
|
|
25057
25061
|
}
|
|
25062
|
+
async function buildUninitPlan(params) {
|
|
25063
|
+
const monitoringUser = params.monitoringUser || DEFAULT_MONITORING_USER;
|
|
25064
|
+
const database = params.database;
|
|
25065
|
+
const provider = params.provider ?? "self-managed";
|
|
25066
|
+
const dropRole = params.dropRole ?? true;
|
|
25067
|
+
const qRole = quoteIdent(monitoringUser);
|
|
25068
|
+
const qDb = quoteIdent(database);
|
|
25069
|
+
const qRoleLiteral = quoteLiteral(monitoringUser);
|
|
25070
|
+
const steps = [];
|
|
25071
|
+
const vars = {
|
|
25072
|
+
ROLE_IDENT: qRole,
|
|
25073
|
+
DB_IDENT: qDb,
|
|
25074
|
+
ROLE_LITERAL: qRoleLiteral
|
|
25075
|
+
};
|
|
25076
|
+
steps.push({
|
|
25077
|
+
name: "01.drop_helpers",
|
|
25078
|
+
sql: applyTemplate(loadSqlTemplate("uninit/01.helpers.sql"), vars)
|
|
25079
|
+
});
|
|
25080
|
+
steps.push({
|
|
25081
|
+
name: "02.revoke_permissions",
|
|
25082
|
+
sql: applyTemplate(loadSqlTemplate("uninit/02.permissions.sql"), vars)
|
|
25083
|
+
});
|
|
25084
|
+
if (dropRole && !SKIP_ROLE_CREATION_PROVIDERS.includes(provider)) {
|
|
25085
|
+
steps.push({
|
|
25086
|
+
name: "03.drop_role",
|
|
25087
|
+
sql: applyTemplate(loadSqlTemplate("uninit/03.role.sql"), vars)
|
|
25088
|
+
});
|
|
25089
|
+
}
|
|
25090
|
+
return { monitoringUser, database, steps, dropRole };
|
|
25091
|
+
}
|
|
25092
|
+
async function applyUninitPlan(params) {
|
|
25093
|
+
const applied = [];
|
|
25094
|
+
const errors3 = [];
|
|
25095
|
+
const executeStep = async (step) => {
|
|
25096
|
+
await params.client.query("begin;");
|
|
25097
|
+
try {
|
|
25098
|
+
await params.client.query(step.sql, step.params);
|
|
25099
|
+
await params.client.query("commit;");
|
|
25100
|
+
} catch (e) {
|
|
25101
|
+
try {
|
|
25102
|
+
await params.client.query("rollback;");
|
|
25103
|
+
} catch {}
|
|
25104
|
+
throw e;
|
|
25105
|
+
}
|
|
25106
|
+
};
|
|
25107
|
+
for (const step of params.plan.steps) {
|
|
25108
|
+
try {
|
|
25109
|
+
await executeStep(step);
|
|
25110
|
+
applied.push(step.name);
|
|
25111
|
+
} catch (e) {
|
|
25112
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
25113
|
+
errors3.push(`${step.name}: ${msg}`);
|
|
25114
|
+
}
|
|
25115
|
+
}
|
|
25116
|
+
return { applied, errors: errors3 };
|
|
25117
|
+
}
|
|
25058
25118
|
async function verifyInitSetup(params) {
|
|
25059
25119
|
await params.client.query("begin isolation level repeatable read;");
|
|
25060
25120
|
try {
|
|
@@ -28229,12 +28289,16 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
28229
28289
|
if (errAny.code === "42501") {
|
|
28230
28290
|
if (failedStep === "01.role") {
|
|
28231
28291
|
console.error(" Context: role creation/update requires CREATEROLE or superuser");
|
|
28232
|
-
} else if (failedStep === "
|
|
28292
|
+
} else if (failedStep === "03.permissions") {
|
|
28233
28293
|
console.error(" Context: grants/view/search_path require sufficient GRANT/DDL privileges");
|
|
28234
28294
|
}
|
|
28235
28295
|
console.error(" Fix: ensure your Supabase access token has sufficient permissions");
|
|
28236
28296
|
console.error(" Tip: run with --print-sql to review the exact SQL plan");
|
|
28237
28297
|
}
|
|
28298
|
+
if (errAny.code === "42P06" || message.includes("already exists") && failedStep === "03.permissions") {
|
|
28299
|
+
console.error(" Hint: postgres_ai schema or objects already exist from a previous setup.");
|
|
28300
|
+
console.error(" Fix: run 'postgresai unprepare-db <connection>' first to clean up, then retry prepare-db.");
|
|
28301
|
+
}
|
|
28238
28302
|
}
|
|
28239
28303
|
if (errAny && typeof errAny === "object" && typeof errAny.httpStatus === "number") {
|
|
28240
28304
|
if (errAny.httpStatus === 401) {
|
|
@@ -28498,13 +28562,258 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
28498
28562
|
if (errAny.code === "42501") {
|
|
28499
28563
|
if (failedStep === "01.role") {
|
|
28500
28564
|
console.error(" Context: role creation/update requires CREATEROLE or superuser");
|
|
28501
|
-
} else if (failedStep === "
|
|
28565
|
+
} else if (failedStep === "03.permissions") {
|
|
28502
28566
|
console.error(" Context: grants/view/search_path require sufficient GRANT/DDL privileges");
|
|
28503
28567
|
}
|
|
28504
28568
|
console.error(" Fix: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges)");
|
|
28505
28569
|
console.error(" Fix: on managed Postgres, use the provider's admin/master user");
|
|
28506
28570
|
console.error(" Tip: run with --print-sql to review the exact SQL plan");
|
|
28507
28571
|
}
|
|
28572
|
+
if (errAny.code === "42P06" || message.includes("already exists") && failedStep === "03.permissions") {
|
|
28573
|
+
console.error(" Hint: postgres_ai schema or objects already exist from a previous setup.");
|
|
28574
|
+
console.error(" Fix: run 'postgresai unprepare-db <connection>' first to clean up, then retry prepare-db.");
|
|
28575
|
+
}
|
|
28576
|
+
if (errAny.code === "ECONNREFUSED") {
|
|
28577
|
+
console.error(" Hint: check host/port and ensure Postgres is reachable from this machine");
|
|
28578
|
+
}
|
|
28579
|
+
if (errAny.code === "ENOTFOUND") {
|
|
28580
|
+
console.error(" Hint: DNS resolution failed; double-check the host name");
|
|
28581
|
+
}
|
|
28582
|
+
if (errAny.code === "ETIMEDOUT") {
|
|
28583
|
+
console.error(" Hint: connection timed out; check network/firewall rules");
|
|
28584
|
+
}
|
|
28585
|
+
}
|
|
28586
|
+
process.exitCode = 1;
|
|
28587
|
+
}
|
|
28588
|
+
} finally {
|
|
28589
|
+
if (client) {
|
|
28590
|
+
try {
|
|
28591
|
+
await client.end();
|
|
28592
|
+
} catch {}
|
|
28593
|
+
}
|
|
28594
|
+
}
|
|
28595
|
+
});
|
|
28596
|
+
program2.command("unprepare-db [conn]").description("remove monitoring setup: drop monitoring user, views, schema, and revoke permissions").option("--db-url <url>", "PostgreSQL connection URL (admin) (deprecated; pass it as positional arg)").option("-h, --host <host>", "PostgreSQL host (psql-like)").option("-p, --port <port>", "PostgreSQL port (psql-like)").option("-U, --username <username>", "PostgreSQL user (psql-like)").option("-d, --dbname <dbname>", "PostgreSQL database name (psql-like)").option("--admin-password <password>", "Admin connection password (otherwise uses PGPASSWORD if set)").option("--monitoring-user <name>", "Monitoring role name to remove", DEFAULT_MONITORING_USER).option("--keep-role", "Keep the monitoring role (only revoke permissions and drop objects)", false).option("--provider <provider>", "Database provider (e.g., supabase). Affects which steps are executed.").option("--print-sql", "Print SQL plan and exit (no changes applied)", false).option("--force", "Skip confirmation prompt", false).option("--json", "Output result as JSON (machine-readable)", false).addHelpText("after", [
|
|
28597
|
+
"",
|
|
28598
|
+
"Examples:",
|
|
28599
|
+
" postgresai unprepare-db postgresql://admin@host:5432/dbname",
|
|
28600
|
+
' postgresai unprepare-db "dbname=dbname host=host user=admin"',
|
|
28601
|
+
" postgresai unprepare-db -h host -p 5432 -U admin -d dbname",
|
|
28602
|
+
"",
|
|
28603
|
+
"Admin password:",
|
|
28604
|
+
" --admin-password <password> or PGPASSWORD=... (libpq standard)",
|
|
28605
|
+
"",
|
|
28606
|
+
"Keep role but remove objects/permissions:",
|
|
28607
|
+
" postgresai unprepare-db <conn> --keep-role",
|
|
28608
|
+
"",
|
|
28609
|
+
"Inspect SQL without applying changes:",
|
|
28610
|
+
" postgresai unprepare-db <conn> --print-sql",
|
|
28611
|
+
"",
|
|
28612
|
+
"Offline SQL plan (no DB connection):",
|
|
28613
|
+
" postgresai unprepare-db --print-sql",
|
|
28614
|
+
"",
|
|
28615
|
+
"Skip confirmation prompt:",
|
|
28616
|
+
" postgresai unprepare-db <conn> --force"
|
|
28617
|
+
].join(`
|
|
28618
|
+
`)).action(async (conn, opts, cmd) => {
|
|
28619
|
+
const jsonOutput = opts.json;
|
|
28620
|
+
const outputJson = (data) => {
|
|
28621
|
+
console.log(JSON.stringify(data, null, 2));
|
|
28622
|
+
};
|
|
28623
|
+
const outputError = (error2) => {
|
|
28624
|
+
if (jsonOutput) {
|
|
28625
|
+
outputJson({
|
|
28626
|
+
success: false,
|
|
28627
|
+
error: error2
|
|
28628
|
+
});
|
|
28629
|
+
} else {
|
|
28630
|
+
console.error(`Error: unprepare-db: ${error2.message}`);
|
|
28631
|
+
if (error2.step)
|
|
28632
|
+
console.error(` Step: ${error2.step}`);
|
|
28633
|
+
if (error2.code)
|
|
28634
|
+
console.error(` Code: ${error2.code}`);
|
|
28635
|
+
if (error2.detail)
|
|
28636
|
+
console.error(` Detail: ${error2.detail}`);
|
|
28637
|
+
if (error2.hint)
|
|
28638
|
+
console.error(` Hint: ${error2.hint}`);
|
|
28639
|
+
}
|
|
28640
|
+
process.exitCode = 1;
|
|
28641
|
+
};
|
|
28642
|
+
const shouldPrintSql = !!opts.printSql;
|
|
28643
|
+
const dropRole = !opts.keepRole;
|
|
28644
|
+
const providerWarning = validateProvider(opts.provider);
|
|
28645
|
+
if (providerWarning) {
|
|
28646
|
+
console.warn(`\u26A0 ${providerWarning}`);
|
|
28647
|
+
}
|
|
28648
|
+
if (!conn && !opts.dbUrl && !opts.host && !opts.port && !opts.username && !opts.adminPassword) {
|
|
28649
|
+
if (shouldPrintSql) {
|
|
28650
|
+
const database = (opts.dbname ?? process.env.PGDATABASE ?? "postgres").trim();
|
|
28651
|
+
const plan = await buildUninitPlan({
|
|
28652
|
+
database,
|
|
28653
|
+
monitoringUser: opts.monitoringUser,
|
|
28654
|
+
dropRole,
|
|
28655
|
+
provider: opts.provider
|
|
28656
|
+
});
|
|
28657
|
+
console.log(`
|
|
28658
|
+
--- SQL plan (offline; not connected) ---`);
|
|
28659
|
+
console.log(`-- database: ${database}`);
|
|
28660
|
+
console.log(`-- monitoring user: ${opts.monitoringUser}`);
|
|
28661
|
+
console.log(`-- provider: ${opts.provider ?? "self-managed"}`);
|
|
28662
|
+
console.log(`-- drop role: ${dropRole}`);
|
|
28663
|
+
for (const step of plan.steps) {
|
|
28664
|
+
console.log(`
|
|
28665
|
+
-- ${step.name}`);
|
|
28666
|
+
console.log(step.sql);
|
|
28667
|
+
}
|
|
28668
|
+
console.log(`
|
|
28669
|
+
--- end SQL plan ---
|
|
28670
|
+
`);
|
|
28671
|
+
return;
|
|
28672
|
+
}
|
|
28673
|
+
}
|
|
28674
|
+
let adminConn;
|
|
28675
|
+
try {
|
|
28676
|
+
adminConn = resolveAdminConnection({
|
|
28677
|
+
conn,
|
|
28678
|
+
dbUrlFlag: opts.dbUrl,
|
|
28679
|
+
host: opts.host ?? process.env.PGHOST,
|
|
28680
|
+
port: opts.port ?? process.env.PGPORT,
|
|
28681
|
+
username: opts.username ?? process.env.PGUSER,
|
|
28682
|
+
dbname: opts.dbname ?? process.env.PGDATABASE,
|
|
28683
|
+
adminPassword: opts.adminPassword,
|
|
28684
|
+
envPassword: process.env.PGPASSWORD
|
|
28685
|
+
});
|
|
28686
|
+
} catch (e) {
|
|
28687
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
28688
|
+
if (jsonOutput) {
|
|
28689
|
+
outputError({ message: msg });
|
|
28690
|
+
} else {
|
|
28691
|
+
console.error(`Error: unprepare-db: ${msg}`);
|
|
28692
|
+
if (typeof msg === "string" && msg.startsWith("Connection is required.")) {
|
|
28693
|
+
console.error("");
|
|
28694
|
+
cmd.outputHelp({ error: true });
|
|
28695
|
+
}
|
|
28696
|
+
process.exitCode = 1;
|
|
28697
|
+
}
|
|
28698
|
+
return;
|
|
28699
|
+
}
|
|
28700
|
+
if (!jsonOutput) {
|
|
28701
|
+
console.log(`Connecting to: ${adminConn.display}`);
|
|
28702
|
+
console.log(`Monitoring user: ${opts.monitoringUser}`);
|
|
28703
|
+
console.log(`Drop role: ${dropRole}`);
|
|
28704
|
+
}
|
|
28705
|
+
if (!opts.force && !jsonOutput && !shouldPrintSql) {
|
|
28706
|
+
const answer = await new Promise((resolve6) => {
|
|
28707
|
+
const readline = getReadline();
|
|
28708
|
+
readline.question(`This will remove the monitoring setup for user "${opts.monitoringUser}"${dropRole ? " and drop the role" : ""}. Continue? [y/N] `, (ans) => resolve6(ans.trim().toLowerCase()));
|
|
28709
|
+
});
|
|
28710
|
+
if (answer !== "y" && answer !== "yes") {
|
|
28711
|
+
console.log("Aborted.");
|
|
28712
|
+
return;
|
|
28713
|
+
}
|
|
28714
|
+
}
|
|
28715
|
+
let client;
|
|
28716
|
+
try {
|
|
28717
|
+
const connResult = await connectWithSslFallback(Client, adminConn);
|
|
28718
|
+
client = connResult.client;
|
|
28719
|
+
const dbRes = await client.query("select current_database() as db");
|
|
28720
|
+
const database = dbRes.rows?.[0]?.db;
|
|
28721
|
+
if (typeof database !== "string" || !database) {
|
|
28722
|
+
throw new Error("Failed to resolve current database name");
|
|
28723
|
+
}
|
|
28724
|
+
const plan = await buildUninitPlan({
|
|
28725
|
+
database,
|
|
28726
|
+
monitoringUser: opts.monitoringUser,
|
|
28727
|
+
dropRole,
|
|
28728
|
+
provider: opts.provider
|
|
28729
|
+
});
|
|
28730
|
+
if (shouldPrintSql) {
|
|
28731
|
+
console.log(`
|
|
28732
|
+
--- SQL plan ---`);
|
|
28733
|
+
for (const step of plan.steps) {
|
|
28734
|
+
console.log(`
|
|
28735
|
+
-- ${step.name}`);
|
|
28736
|
+
console.log(step.sql);
|
|
28737
|
+
}
|
|
28738
|
+
console.log(`
|
|
28739
|
+
--- end SQL plan ---
|
|
28740
|
+
`);
|
|
28741
|
+
return;
|
|
28742
|
+
}
|
|
28743
|
+
const { applied, errors: errors3 } = await applyUninitPlan({ client, plan });
|
|
28744
|
+
if (jsonOutput) {
|
|
28745
|
+
outputJson({
|
|
28746
|
+
success: errors3.length === 0,
|
|
28747
|
+
action: "unprepare",
|
|
28748
|
+
database,
|
|
28749
|
+
monitoringUser: opts.monitoringUser,
|
|
28750
|
+
dropRole,
|
|
28751
|
+
applied,
|
|
28752
|
+
errors: errors3
|
|
28753
|
+
});
|
|
28754
|
+
if (errors3.length > 0) {
|
|
28755
|
+
process.exitCode = 1;
|
|
28756
|
+
}
|
|
28757
|
+
} else {
|
|
28758
|
+
if (errors3.length === 0) {
|
|
28759
|
+
console.log("\u2713 unprepare-db completed");
|
|
28760
|
+
console.log(`Applied ${applied.length} steps`);
|
|
28761
|
+
} else {
|
|
28762
|
+
console.log("\u26A0 unprepare-db completed with errors");
|
|
28763
|
+
console.log(`Applied ${applied.length} steps`);
|
|
28764
|
+
console.log("Errors:");
|
|
28765
|
+
for (const err of errors3) {
|
|
28766
|
+
console.log(` - ${err}`);
|
|
28767
|
+
}
|
|
28768
|
+
process.exitCode = 1;
|
|
28769
|
+
}
|
|
28770
|
+
}
|
|
28771
|
+
} catch (error2) {
|
|
28772
|
+
const errAny = error2;
|
|
28773
|
+
let message = "";
|
|
28774
|
+
if (error2 instanceof Error && error2.message) {
|
|
28775
|
+
message = error2.message;
|
|
28776
|
+
} else if (errAny && typeof errAny === "object" && typeof errAny.message === "string" && errAny.message) {
|
|
28777
|
+
message = errAny.message;
|
|
28778
|
+
} else {
|
|
28779
|
+
message = String(error2);
|
|
28780
|
+
}
|
|
28781
|
+
if (!message || message === "[object Object]") {
|
|
28782
|
+
message = "Unknown error";
|
|
28783
|
+
}
|
|
28784
|
+
const errorObj = { message };
|
|
28785
|
+
if (errAny && typeof errAny === "object") {
|
|
28786
|
+
if (typeof errAny.code === "string" && errAny.code)
|
|
28787
|
+
errorObj.code = errAny.code;
|
|
28788
|
+
if (typeof errAny.detail === "string" && errAny.detail)
|
|
28789
|
+
errorObj.detail = errAny.detail;
|
|
28790
|
+
if (typeof errAny.hint === "string" && errAny.hint)
|
|
28791
|
+
errorObj.hint = errAny.hint;
|
|
28792
|
+
}
|
|
28793
|
+
if (jsonOutput) {
|
|
28794
|
+
outputJson({
|
|
28795
|
+
success: false,
|
|
28796
|
+
error: errorObj
|
|
28797
|
+
});
|
|
28798
|
+
process.exitCode = 1;
|
|
28799
|
+
} else {
|
|
28800
|
+
console.error(`Error: unprepare-db: ${message}`);
|
|
28801
|
+
if (errAny && typeof errAny === "object") {
|
|
28802
|
+
if (typeof errAny.code === "string" && errAny.code) {
|
|
28803
|
+
console.error(` Code: ${errAny.code}`);
|
|
28804
|
+
}
|
|
28805
|
+
if (typeof errAny.detail === "string" && errAny.detail) {
|
|
28806
|
+
console.error(` Detail: ${errAny.detail}`);
|
|
28807
|
+
}
|
|
28808
|
+
if (typeof errAny.hint === "string" && errAny.hint) {
|
|
28809
|
+
console.error(` Hint: ${errAny.hint}`);
|
|
28810
|
+
}
|
|
28811
|
+
}
|
|
28812
|
+
if (errAny && typeof errAny === "object" && typeof errAny.code === "string") {
|
|
28813
|
+
if (errAny.code === "42501") {
|
|
28814
|
+
console.error(" Context: dropping roles/objects requires sufficient privileges");
|
|
28815
|
+
console.error(" Fix: connect as a superuser (or a role with appropriate DROP privileges)");
|
|
28816
|
+
}
|
|
28508
28817
|
if (errAny.code === "ECONNREFUSED") {
|
|
28509
28818
|
console.error(" Hint: check host/port and ensure Postgres is reachable from this machine");
|
|
28510
28819
|
}
|
|
@@ -28523,6 +28832,7 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
28523
28832
|
await client.end();
|
|
28524
28833
|
} catch {}
|
|
28525
28834
|
}
|
|
28835
|
+
closeReadline();
|
|
28526
28836
|
}
|
|
28527
28837
|
});
|
|
28528
28838
|
program2.command("checkup [conn]").description("generate health check reports directly from PostgreSQL (express mode)").option("--check-id <id>", `specific check to run: ${Object.keys(CHECK_INFO).join(", ")}, or ALL`, "ALL").option("--node-name <name>", "node name for reports", "node-01").option("--output <path>", "output directory for JSON files").option("--[no-]upload", "upload JSON results to PostgresAI (default: enabled; requires API key)", undefined).option("--project <project>", "project name or ID for remote upload (used with --upload; defaults to config defaultProject; auto-generated on first run)").option("--json", "output JSON to stdout (implies --no-upload)").addHelpText("after", [
|
|
@@ -28651,14 +28961,14 @@ async function resolveOrInitPaths() {
|
|
|
28651
28961
|
}
|
|
28652
28962
|
function isDockerRunning() {
|
|
28653
28963
|
try {
|
|
28654
|
-
const result = spawnSync2("docker", ["info"], { stdio: "pipe" });
|
|
28964
|
+
const result = spawnSync2("docker", ["info"], { stdio: "pipe", timeout: 5000 });
|
|
28655
28965
|
return result.status === 0;
|
|
28656
28966
|
} catch {
|
|
28657
28967
|
return false;
|
|
28658
28968
|
}
|
|
28659
28969
|
}
|
|
28660
28970
|
function getComposeCmd() {
|
|
28661
|
-
const tryCmd = (cmd, args) => spawnSync2(cmd, args, { stdio: "ignore" }).status === 0;
|
|
28971
|
+
const tryCmd = (cmd, args) => spawnSync2(cmd, args, { stdio: "ignore", timeout: 5000 }).status === 0;
|
|
28662
28972
|
if (tryCmd("docker-compose", ["version"]))
|
|
28663
28973
|
return ["docker-compose"];
|
|
28664
28974
|
if (tryCmd("docker", ["compose", "version"]))
|
|
@@ -28667,7 +28977,7 @@ function getComposeCmd() {
|
|
|
28667
28977
|
}
|
|
28668
28978
|
function checkRunningContainers() {
|
|
28669
28979
|
try {
|
|
28670
|
-
const result = spawnSync2("docker", ["ps", "--filter", "name=grafana-with-datasources", "--filter", "name=pgwatch", "--format", "{{.Names}}"], { stdio: "pipe", encoding: "utf8" });
|
|
28980
|
+
const result = spawnSync2("docker", ["ps", "--filter", "name=grafana-with-datasources", "--filter", "name=pgwatch", "--format", "{{.Names}}"], { stdio: "pipe", encoding: "utf8", timeout: 5000 });
|
|
28671
28981
|
if (result.status === 0 && result.stdout) {
|
|
28672
28982
|
const containers = result.stdout.trim().split(`
|
|
28673
28983
|
`).filter(Boolean);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
-- Extensions required for postgres_ai monitoring
|
|
2
|
+
|
|
3
|
+
-- Enable pg_stat_statements for query performance monitoring
|
|
4
|
+
-- Note: Uses IF NOT EXISTS because extension may already be installed.
|
|
5
|
+
-- We do NOT drop this extension in unprepare-db since it may have been pre-existing.
|
|
6
|
+
create extension if not exists pg_stat_statements;
|
|
7
|
+
|
|
8
|
+
|
|
@@ -8,6 +8,7 @@ grant pg_monitor to {{ROLE_IDENT}};
|
|
|
8
8
|
grant select on pg_catalog.pg_index to {{ROLE_IDENT}};
|
|
9
9
|
|
|
10
10
|
-- Create postgres_ai schema for our objects
|
|
11
|
+
-- Using IF NOT EXISTS for idempotency - prepare-db can be run multiple times
|
|
11
12
|
create schema if not exists postgres_ai;
|
|
12
13
|
grant usage on schema postgres_ai to {{ROLE_IDENT}};
|
|
13
14
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
-- Extensions required for postgres_ai monitoring
|
|
2
|
+
|
|
3
|
+
-- Enable pg_stat_statements for query performance monitoring
|
|
4
|
+
-- Note: Uses IF NOT EXISTS because extension may already be installed.
|
|
5
|
+
-- We do NOT drop this extension in unprepare-db since it may have been pre-existing.
|
|
6
|
+
create extension if not exists pg_stat_statements;
|
|
7
|
+
|
|
8
|
+
|
|
@@ -8,6 +8,7 @@ grant pg_monitor to {{ROLE_IDENT}};
|
|
|
8
8
|
grant select on pg_catalog.pg_index to {{ROLE_IDENT}};
|
|
9
9
|
|
|
10
10
|
-- Create postgres_ai schema for our objects
|
|
11
|
+
-- Using IF NOT EXISTS for idempotency - prepare-db can be run multiple times
|
|
11
12
|
create schema if not exists postgres_ai;
|
|
12
13
|
grant usage on schema postgres_ai to {{ROLE_IDENT}};
|
|
13
14
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
-- Revoke permissions and drop objects created by prepare-db (template-filled by cli/lib/init.ts)
|
|
2
|
+
|
|
3
|
+
-- Drop the postgres_ai.pg_statistic view
|
|
4
|
+
drop view if exists postgres_ai.pg_statistic;
|
|
5
|
+
|
|
6
|
+
-- Drop the postgres_ai schema (CASCADE to handle any remaining objects)
|
|
7
|
+
drop schema if exists postgres_ai cascade;
|
|
8
|
+
|
|
9
|
+
-- Revoke permissions from the monitoring role
|
|
10
|
+
-- Use a DO block to handle the case where the role doesn't exist
|
|
11
|
+
do $$ begin
|
|
12
|
+
revoke pg_monitor from {{ROLE_IDENT}};
|
|
13
|
+
exception when undefined_object then
|
|
14
|
+
null; -- Role doesn't exist, nothing to revoke
|
|
15
|
+
end $$;
|
|
16
|
+
|
|
17
|
+
do $$ begin
|
|
18
|
+
revoke select on pg_catalog.pg_index from {{ROLE_IDENT}};
|
|
19
|
+
exception when undefined_object then
|
|
20
|
+
null; -- Role doesn't exist
|
|
21
|
+
end $$;
|
|
22
|
+
|
|
23
|
+
do $$ begin
|
|
24
|
+
revoke connect on database {{DB_IDENT}} from {{ROLE_IDENT}};
|
|
25
|
+
exception when undefined_object then
|
|
26
|
+
null; -- Role doesn't exist
|
|
27
|
+
end $$;
|
|
28
|
+
|
|
29
|
+
-- Note: USAGE on public is typically granted by default; we don't revoke it
|
|
30
|
+
-- to avoid breaking other applications that may rely on it.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
-- Drop the monitoring role created by prepare-db (template-filled by cli/lib/init.ts)
|
|
2
|
+
-- This must run after revoking all permissions from the role.
|
|
3
|
+
|
|
4
|
+
-- Use a DO block to handle the case where the role doesn't exist
|
|
5
|
+
do $$ begin
|
|
6
|
+
-- Reassign owned objects to current user before dropping
|
|
7
|
+
-- This handles any objects that might have been created by the role
|
|
8
|
+
begin
|
|
9
|
+
execute format('reassign owned by %I to current_user', {{ROLE_LITERAL}});
|
|
10
|
+
exception when undefined_object then
|
|
11
|
+
null; -- Role doesn't exist, nothing to reassign
|
|
12
|
+
end;
|
|
13
|
+
|
|
14
|
+
-- Drop owned objects (in case reassign didn't work for some objects)
|
|
15
|
+
begin
|
|
16
|
+
execute format('drop owned by %I', {{ROLE_LITERAL}});
|
|
17
|
+
exception when undefined_object then
|
|
18
|
+
null; -- Role doesn't exist
|
|
19
|
+
end;
|
|
20
|
+
|
|
21
|
+
-- Drop the role
|
|
22
|
+
begin
|
|
23
|
+
execute format('drop role %I', {{ROLE_LITERAL}});
|
|
24
|
+
exception when undefined_object then
|
|
25
|
+
null; -- Role doesn't exist, that's fine
|
|
26
|
+
end;
|
|
27
|
+
end $$;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
-- Revoke permissions and drop objects created by prepare-db (template-filled by cli/lib/init.ts)
|
|
2
|
+
|
|
3
|
+
-- Drop the postgres_ai.pg_statistic view
|
|
4
|
+
drop view if exists postgres_ai.pg_statistic;
|
|
5
|
+
|
|
6
|
+
-- Drop the postgres_ai schema (CASCADE to handle any remaining objects)
|
|
7
|
+
drop schema if exists postgres_ai cascade;
|
|
8
|
+
|
|
9
|
+
-- Revoke permissions from the monitoring role
|
|
10
|
+
-- Use a DO block to handle the case where the role doesn't exist
|
|
11
|
+
do $$ begin
|
|
12
|
+
revoke pg_monitor from {{ROLE_IDENT}};
|
|
13
|
+
exception when undefined_object then
|
|
14
|
+
null; -- Role doesn't exist, nothing to revoke
|
|
15
|
+
end $$;
|
|
16
|
+
|
|
17
|
+
do $$ begin
|
|
18
|
+
revoke select on pg_catalog.pg_index from {{ROLE_IDENT}};
|
|
19
|
+
exception when undefined_object then
|
|
20
|
+
null; -- Role doesn't exist
|
|
21
|
+
end $$;
|
|
22
|
+
|
|
23
|
+
do $$ begin
|
|
24
|
+
revoke connect on database {{DB_IDENT}} from {{ROLE_IDENT}};
|
|
25
|
+
exception when undefined_object then
|
|
26
|
+
null; -- Role doesn't exist
|
|
27
|
+
end $$;
|
|
28
|
+
|
|
29
|
+
-- Note: USAGE on public is typically granted by default; we don't revoke it
|
|
30
|
+
-- to avoid breaking other applications that may rely on it.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
-- Drop the monitoring role created by prepare-db (template-filled by cli/lib/init.ts)
|
|
2
|
+
-- This must run after revoking all permissions from the role.
|
|
3
|
+
|
|
4
|
+
-- Use a DO block to handle the case where the role doesn't exist
|
|
5
|
+
do $$ begin
|
|
6
|
+
-- Reassign owned objects to current user before dropping
|
|
7
|
+
-- This handles any objects that might have been created by the role
|
|
8
|
+
begin
|
|
9
|
+
execute format('reassign owned by %I to current_user', {{ROLE_LITERAL}});
|
|
10
|
+
exception when undefined_object then
|
|
11
|
+
null; -- Role doesn't exist, nothing to reassign
|
|
12
|
+
end;
|
|
13
|
+
|
|
14
|
+
-- Drop owned objects (in case reassign didn't work for some objects)
|
|
15
|
+
begin
|
|
16
|
+
execute format('drop owned by %I', {{ROLE_LITERAL}});
|
|
17
|
+
exception when undefined_object then
|
|
18
|
+
null; -- Role doesn't exist
|
|
19
|
+
end;
|
|
20
|
+
|
|
21
|
+
-- Drop the role
|
|
22
|
+
begin
|
|
23
|
+
execute format('drop role %I', {{ROLE_LITERAL}});
|
|
24
|
+
exception when undefined_object then
|
|
25
|
+
null; -- Role doesn't exist, that's fine
|
|
26
|
+
end;
|
|
27
|
+
end $$;
|