postgresai 0.14.0-beta.13 → 0.14.0-beta.15
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 +324 -12
- package/dist/bin/postgres-ai.js +1009 -46
- 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/checkup-dictionary.ts +114 -0
- package/lib/checkup.ts +130 -14
- package/lib/init.ts +109 -8
- package/package.json +9 -7
- package/scripts/embed-checkup-dictionary.ts +106 -0
- 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/checkup.test.ts +17 -18
- package/test/init.test.ts +245 -11
- package/lib/metrics-embedded.ts +0 -79
- /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-beta.
|
|
13067
|
+
version: "0.14.0-beta.15",
|
|
13068
13068
|
description: "postgres_ai CLI",
|
|
13069
13069
|
license: "Apache-2.0",
|
|
13070
13070
|
private: false,
|
|
@@ -13090,15 +13090,17 @@ var package_default = {
|
|
|
13090
13090
|
},
|
|
13091
13091
|
scripts: {
|
|
13092
13092
|
"embed-metrics": "bun run scripts/embed-metrics.ts",
|
|
13093
|
-
|
|
13093
|
+
"embed-checkup-dictionary": "bun run scripts/embed-checkup-dictionary.ts",
|
|
13094
|
+
"embed-all": "bun run embed-metrics && bun run embed-checkup-dictionary",
|
|
13095
|
+
build: `bun run embed-all && bun build ./bin/postgres-ai.ts --outdir ./dist/bin --target node && node -e "const fs=require('fs');const f='./dist/bin/postgres-ai.js';fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace('#!/usr/bin/env bun','#!/usr/bin/env node'))" && cp -r ./sql ./dist/sql`,
|
|
13094
13096
|
prepublishOnly: "npm run build",
|
|
13095
13097
|
start: "bun ./bin/postgres-ai.ts --help",
|
|
13096
13098
|
"start:node": "node ./dist/bin/postgres-ai.js --help",
|
|
13097
|
-
dev: "bun run embed-
|
|
13098
|
-
test: "bun run embed-
|
|
13099
|
-
"test:fast": "bun run embed-
|
|
13100
|
-
"test:coverage": "bun run embed-
|
|
13101
|
-
typecheck: "bun run embed-
|
|
13099
|
+
dev: "bun run embed-all && bun --watch ./bin/postgres-ai.ts",
|
|
13100
|
+
test: "bun run embed-all && bun test",
|
|
13101
|
+
"test:fast": "bun run embed-all && bun test --coverage=false",
|
|
13102
|
+
"test:coverage": "bun run embed-all && bun test --coverage && echo 'Coverage report: cli/coverage/lcov-report/index.html'",
|
|
13103
|
+
typecheck: "bun run embed-all && bunx tsc --noEmit"
|
|
13102
13104
|
},
|
|
13103
13105
|
dependencies: {
|
|
13104
13106
|
"@modelcontextprotocol/sdk": "^1.20.2",
|
|
@@ -15887,7 +15889,7 @@ var Result = import_lib.default.Result;
|
|
|
15887
15889
|
var TypeOverrides = import_lib.default.TypeOverrides;
|
|
15888
15890
|
var defaults = import_lib.default.defaults;
|
|
15889
15891
|
// package.json
|
|
15890
|
-
var version = "0.14.0-beta.
|
|
15892
|
+
var version = "0.14.0-beta.15";
|
|
15891
15893
|
var package_default2 = {
|
|
15892
15894
|
name: "postgresai",
|
|
15893
15895
|
version,
|
|
@@ -15916,15 +15918,17 @@ var package_default2 = {
|
|
|
15916
15918
|
},
|
|
15917
15919
|
scripts: {
|
|
15918
15920
|
"embed-metrics": "bun run scripts/embed-metrics.ts",
|
|
15919
|
-
|
|
15921
|
+
"embed-checkup-dictionary": "bun run scripts/embed-checkup-dictionary.ts",
|
|
15922
|
+
"embed-all": "bun run embed-metrics && bun run embed-checkup-dictionary",
|
|
15923
|
+
build: `bun run embed-all && bun build ./bin/postgres-ai.ts --outdir ./dist/bin --target node && node -e "const fs=require('fs');const f='./dist/bin/postgres-ai.js';fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace('#!/usr/bin/env bun','#!/usr/bin/env node'))" && cp -r ./sql ./dist/sql`,
|
|
15920
15924
|
prepublishOnly: "npm run build",
|
|
15921
15925
|
start: "bun ./bin/postgres-ai.ts --help",
|
|
15922
15926
|
"start:node": "node ./dist/bin/postgres-ai.js --help",
|
|
15923
|
-
dev: "bun run embed-
|
|
15924
|
-
test: "bun run embed-
|
|
15925
|
-
"test:fast": "bun run embed-
|
|
15926
|
-
"test:coverage": "bun run embed-
|
|
15927
|
-
typecheck: "bun run embed-
|
|
15927
|
+
dev: "bun run embed-all && bun --watch ./bin/postgres-ai.ts",
|
|
15928
|
+
test: "bun run embed-all && bun test",
|
|
15929
|
+
"test:fast": "bun run embed-all && bun test --coverage=false",
|
|
15930
|
+
"test:coverage": "bun run embed-all && bun test --coverage && echo 'Coverage report: cli/coverage/lcov-report/index.html'",
|
|
15931
|
+
typecheck: "bun run embed-all && bunx tsc --noEmit"
|
|
15928
15932
|
},
|
|
15929
15933
|
dependencies: {
|
|
15930
15934
|
"@modelcontextprotocol/sdk": "^1.20.2",
|
|
@@ -24961,7 +24965,11 @@ end $$;`;
|
|
|
24961
24965
|
const roleSql = applyTemplate(loadSqlTemplate("01.role.sql"), { ...vars, ROLE_STMT: roleStmt });
|
|
24962
24966
|
steps.push({ name: "01.role", sql: roleSql });
|
|
24963
24967
|
}
|
|
24964
|
-
|
|
24968
|
+
steps.push({
|
|
24969
|
+
name: "02.extensions",
|
|
24970
|
+
sql: loadSqlTemplate("02.extensions.sql")
|
|
24971
|
+
});
|
|
24972
|
+
let permissionsSql = applyTemplate(loadSqlTemplate("03.permissions.sql"), vars);
|
|
24965
24973
|
if (SKIP_ALTER_USER_PROVIDERS.includes(provider)) {
|
|
24966
24974
|
permissionsSql = permissionsSql.split(`
|
|
24967
24975
|
`).filter((line) => {
|
|
@@ -24973,21 +24981,21 @@ end $$;`;
|
|
|
24973
24981
|
`);
|
|
24974
24982
|
}
|
|
24975
24983
|
steps.push({
|
|
24976
|
-
name: "
|
|
24984
|
+
name: "03.permissions",
|
|
24977
24985
|
sql: permissionsSql
|
|
24978
24986
|
});
|
|
24979
24987
|
steps.push({
|
|
24980
|
-
name: "
|
|
24981
|
-
sql: applyTemplate(loadSqlTemplate("
|
|
24988
|
+
name: "06.helpers",
|
|
24989
|
+
sql: applyTemplate(loadSqlTemplate("06.helpers.sql"), vars)
|
|
24982
24990
|
});
|
|
24983
24991
|
if (params.includeOptionalPermissions) {
|
|
24984
24992
|
steps.push({
|
|
24985
|
-
name: "
|
|
24986
|
-
sql: applyTemplate(loadSqlTemplate("
|
|
24993
|
+
name: "04.optional_rds",
|
|
24994
|
+
sql: applyTemplate(loadSqlTemplate("04.optional_rds.sql"), vars),
|
|
24987
24995
|
optional: true
|
|
24988
24996
|
}, {
|
|
24989
|
-
name: "
|
|
24990
|
-
sql: applyTemplate(loadSqlTemplate("
|
|
24997
|
+
name: "05.optional_self_managed",
|
|
24998
|
+
sql: applyTemplate(loadSqlTemplate("05.optional_self_managed.sql"), vars),
|
|
24991
24999
|
optional: true
|
|
24992
25000
|
});
|
|
24993
25001
|
}
|
|
@@ -25055,6 +25063,62 @@ async function applyInitPlan(params) {
|
|
|
25055
25063
|
}
|
|
25056
25064
|
return { applied, skippedOptional };
|
|
25057
25065
|
}
|
|
25066
|
+
async function buildUninitPlan(params) {
|
|
25067
|
+
const monitoringUser = params.monitoringUser || DEFAULT_MONITORING_USER;
|
|
25068
|
+
const database = params.database;
|
|
25069
|
+
const provider = params.provider ?? "self-managed";
|
|
25070
|
+
const dropRole = params.dropRole ?? true;
|
|
25071
|
+
const qRole = quoteIdent(monitoringUser);
|
|
25072
|
+
const qDb = quoteIdent(database);
|
|
25073
|
+
const qRoleLiteral = quoteLiteral(monitoringUser);
|
|
25074
|
+
const steps = [];
|
|
25075
|
+
const vars = {
|
|
25076
|
+
ROLE_IDENT: qRole,
|
|
25077
|
+
DB_IDENT: qDb,
|
|
25078
|
+
ROLE_LITERAL: qRoleLiteral
|
|
25079
|
+
};
|
|
25080
|
+
steps.push({
|
|
25081
|
+
name: "01.drop_helpers",
|
|
25082
|
+
sql: applyTemplate(loadSqlTemplate("uninit/01.helpers.sql"), vars)
|
|
25083
|
+
});
|
|
25084
|
+
steps.push({
|
|
25085
|
+
name: "02.revoke_permissions",
|
|
25086
|
+
sql: applyTemplate(loadSqlTemplate("uninit/02.permissions.sql"), vars)
|
|
25087
|
+
});
|
|
25088
|
+
if (dropRole && !SKIP_ROLE_CREATION_PROVIDERS.includes(provider)) {
|
|
25089
|
+
steps.push({
|
|
25090
|
+
name: "03.drop_role",
|
|
25091
|
+
sql: applyTemplate(loadSqlTemplate("uninit/03.role.sql"), vars)
|
|
25092
|
+
});
|
|
25093
|
+
}
|
|
25094
|
+
return { monitoringUser, database, steps, dropRole };
|
|
25095
|
+
}
|
|
25096
|
+
async function applyUninitPlan(params) {
|
|
25097
|
+
const applied = [];
|
|
25098
|
+
const errors3 = [];
|
|
25099
|
+
const executeStep = async (step) => {
|
|
25100
|
+
await params.client.query("begin;");
|
|
25101
|
+
try {
|
|
25102
|
+
await params.client.query(step.sql, step.params);
|
|
25103
|
+
await params.client.query("commit;");
|
|
25104
|
+
} catch (e) {
|
|
25105
|
+
try {
|
|
25106
|
+
await params.client.query("rollback;");
|
|
25107
|
+
} catch {}
|
|
25108
|
+
throw e;
|
|
25109
|
+
}
|
|
25110
|
+
};
|
|
25111
|
+
for (const step of params.plan.steps) {
|
|
25112
|
+
try {
|
|
25113
|
+
await executeStep(step);
|
|
25114
|
+
applied.push(step.name);
|
|
25115
|
+
} catch (e) {
|
|
25116
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
25117
|
+
errors3.push(`${step.name}: ${msg}`);
|
|
25118
|
+
}
|
|
25119
|
+
}
|
|
25120
|
+
return { applied, errors: errors3 };
|
|
25121
|
+
}
|
|
25058
25122
|
async function verifyInitSetup(params) {
|
|
25059
25123
|
await params.client.query("begin isolation level repeatable read;");
|
|
25060
25124
|
try {
|
|
@@ -26482,6 +26546,572 @@ function transformMetricRow(row) {
|
|
|
26482
26546
|
return result;
|
|
26483
26547
|
}
|
|
26484
26548
|
|
|
26549
|
+
// lib/checkup-dictionary-embedded.ts
|
|
26550
|
+
var CHECKUP_DICTIONARY_DATA = [
|
|
26551
|
+
{
|
|
26552
|
+
code: "A001",
|
|
26553
|
+
title: "System information",
|
|
26554
|
+
description: "OS, kernel, hardware details",
|
|
26555
|
+
category: "system",
|
|
26556
|
+
sort_order: null,
|
|
26557
|
+
is_system_report: false
|
|
26558
|
+
},
|
|
26559
|
+
{
|
|
26560
|
+
code: "A002",
|
|
26561
|
+
title: "Postgres major version",
|
|
26562
|
+
description: "Major version and end-of-life status",
|
|
26563
|
+
category: "system",
|
|
26564
|
+
sort_order: null,
|
|
26565
|
+
is_system_report: false
|
|
26566
|
+
},
|
|
26567
|
+
{
|
|
26568
|
+
code: "A003",
|
|
26569
|
+
title: "Postgres settings",
|
|
26570
|
+
description: "Full list of current settings",
|
|
26571
|
+
category: "system",
|
|
26572
|
+
sort_order: null,
|
|
26573
|
+
is_system_report: false
|
|
26574
|
+
},
|
|
26575
|
+
{
|
|
26576
|
+
code: "A004",
|
|
26577
|
+
title: "Cluster information",
|
|
26578
|
+
description: "Data directory, cluster name, system identifier",
|
|
26579
|
+
category: "system",
|
|
26580
|
+
sort_order: null,
|
|
26581
|
+
is_system_report: false
|
|
26582
|
+
},
|
|
26583
|
+
{
|
|
26584
|
+
code: "A005",
|
|
26585
|
+
title: "Extensions",
|
|
26586
|
+
description: "Installed and available extensions",
|
|
26587
|
+
category: "system",
|
|
26588
|
+
sort_order: null,
|
|
26589
|
+
is_system_report: false
|
|
26590
|
+
},
|
|
26591
|
+
{
|
|
26592
|
+
code: "A006",
|
|
26593
|
+
title: "Postgres setting deviations",
|
|
26594
|
+
description: "Settings that differ from defaults",
|
|
26595
|
+
category: "system",
|
|
26596
|
+
sort_order: null,
|
|
26597
|
+
is_system_report: false
|
|
26598
|
+
},
|
|
26599
|
+
{
|
|
26600
|
+
code: "A007",
|
|
26601
|
+
title: "Altered settings",
|
|
26602
|
+
description: "Settings changed via ALTER SYSTEM or per-user/database",
|
|
26603
|
+
category: "system",
|
|
26604
|
+
sort_order: null,
|
|
26605
|
+
is_system_report: false
|
|
26606
|
+
},
|
|
26607
|
+
{
|
|
26608
|
+
code: "A008",
|
|
26609
|
+
title: "Disk usage and file system type",
|
|
26610
|
+
description: "Disk space, mount points, filesystem types",
|
|
26611
|
+
category: "system",
|
|
26612
|
+
sort_order: null,
|
|
26613
|
+
is_system_report: false
|
|
26614
|
+
},
|
|
26615
|
+
{
|
|
26616
|
+
code: "A009",
|
|
26617
|
+
title: "Tablespaces",
|
|
26618
|
+
description: "Tablespace locations and usage",
|
|
26619
|
+
category: "system",
|
|
26620
|
+
sort_order: null,
|
|
26621
|
+
is_system_report: false
|
|
26622
|
+
},
|
|
26623
|
+
{
|
|
26624
|
+
code: "A010",
|
|
26625
|
+
title: "Corruption control",
|
|
26626
|
+
description: "Checksums, wal_log_hints, backup safety",
|
|
26627
|
+
category: "system",
|
|
26628
|
+
sort_order: null,
|
|
26629
|
+
is_system_report: false
|
|
26630
|
+
},
|
|
26631
|
+
{
|
|
26632
|
+
code: "A011",
|
|
26633
|
+
title: "Connection management and pooling",
|
|
26634
|
+
description: "Connection limits and pooler detection",
|
|
26635
|
+
category: "system",
|
|
26636
|
+
sort_order: null,
|
|
26637
|
+
is_system_report: false
|
|
26638
|
+
},
|
|
26639
|
+
{
|
|
26640
|
+
code: "A012",
|
|
26641
|
+
title: "Anti-crash checks",
|
|
26642
|
+
description: "fsync, full_page_writes, synchronous_commit",
|
|
26643
|
+
category: "system",
|
|
26644
|
+
sort_order: null,
|
|
26645
|
+
is_system_report: false
|
|
26646
|
+
},
|
|
26647
|
+
{
|
|
26648
|
+
code: "A013",
|
|
26649
|
+
title: "Postgres minor version",
|
|
26650
|
+
description: "Minor version and available updates",
|
|
26651
|
+
category: "system",
|
|
26652
|
+
sort_order: null,
|
|
26653
|
+
is_system_report: false
|
|
26654
|
+
},
|
|
26655
|
+
{
|
|
26656
|
+
code: "B001",
|
|
26657
|
+
title: "SLO/SLA, RPO, RTO",
|
|
26658
|
+
description: "Recovery objectives and current capabilities",
|
|
26659
|
+
category: "backup",
|
|
26660
|
+
sort_order: null,
|
|
26661
|
+
is_system_report: false
|
|
26662
|
+
},
|
|
26663
|
+
{
|
|
26664
|
+
code: "B002",
|
|
26665
|
+
title: "File system and mount flags",
|
|
26666
|
+
description: "Mount options affecting durability",
|
|
26667
|
+
category: "backup",
|
|
26668
|
+
sort_order: null,
|
|
26669
|
+
is_system_report: false
|
|
26670
|
+
},
|
|
26671
|
+
{
|
|
26672
|
+
code: "B003",
|
|
26673
|
+
title: "Full and incremental backups",
|
|
26674
|
+
description: "Backup tooling and schedule",
|
|
26675
|
+
category: "backup",
|
|
26676
|
+
sort_order: null,
|
|
26677
|
+
is_system_report: false
|
|
26678
|
+
},
|
|
26679
|
+
{
|
|
26680
|
+
code: "B004",
|
|
26681
|
+
title: "WAL archiving",
|
|
26682
|
+
description: "Archive configuration and status",
|
|
26683
|
+
category: "backup",
|
|
26684
|
+
sort_order: null,
|
|
26685
|
+
is_system_report: false
|
|
26686
|
+
},
|
|
26687
|
+
{
|
|
26688
|
+
code: "B005",
|
|
26689
|
+
title: "Backup restore testing and monitoring",
|
|
26690
|
+
description: "Restore testing practices and alerting",
|
|
26691
|
+
category: "backup",
|
|
26692
|
+
sort_order: null,
|
|
26693
|
+
is_system_report: false
|
|
26694
|
+
},
|
|
26695
|
+
{
|
|
26696
|
+
code: "C001",
|
|
26697
|
+
title: "SLO/SLA",
|
|
26698
|
+
description: "Availability objectives",
|
|
26699
|
+
category: "replication",
|
|
26700
|
+
sort_order: null,
|
|
26701
|
+
is_system_report: false
|
|
26702
|
+
},
|
|
26703
|
+
{
|
|
26704
|
+
code: "C002",
|
|
26705
|
+
title: "Replication mode and method",
|
|
26706
|
+
description: "Sync/async, streaming/WAL shipping",
|
|
26707
|
+
category: "replication",
|
|
26708
|
+
sort_order: null,
|
|
26709
|
+
is_system_report: false
|
|
26710
|
+
},
|
|
26711
|
+
{
|
|
26712
|
+
code: "C003",
|
|
26713
|
+
title: "Single points of failure",
|
|
26714
|
+
description: "SPOF analysis",
|
|
26715
|
+
category: "replication",
|
|
26716
|
+
sort_order: null,
|
|
26717
|
+
is_system_report: false
|
|
26718
|
+
},
|
|
26719
|
+
{
|
|
26720
|
+
code: "C004",
|
|
26721
|
+
title: "Failover",
|
|
26722
|
+
description: "Failover mechanism and automation",
|
|
26723
|
+
category: "replication",
|
|
26724
|
+
sort_order: null,
|
|
26725
|
+
is_system_report: false
|
|
26726
|
+
},
|
|
26727
|
+
{
|
|
26728
|
+
code: "C005",
|
|
26729
|
+
title: "Switchover",
|
|
26730
|
+
description: "Planned switchover procedures",
|
|
26731
|
+
category: "replication",
|
|
26732
|
+
sort_order: null,
|
|
26733
|
+
is_system_report: false
|
|
26734
|
+
},
|
|
26735
|
+
{
|
|
26736
|
+
code: "C006",
|
|
26737
|
+
title: "Delayed replica",
|
|
26738
|
+
description: "Delayed standby configuration",
|
|
26739
|
+
category: "replication",
|
|
26740
|
+
sort_order: null,
|
|
26741
|
+
is_system_report: false
|
|
26742
|
+
},
|
|
26743
|
+
{
|
|
26744
|
+
code: "C007",
|
|
26745
|
+
title: "Replication slots and lag",
|
|
26746
|
+
description: "Slot status, lag, standby feedback",
|
|
26747
|
+
category: "replication",
|
|
26748
|
+
sort_order: null,
|
|
26749
|
+
is_system_report: false
|
|
26750
|
+
},
|
|
26751
|
+
{
|
|
26752
|
+
code: "D001",
|
|
26753
|
+
title: "Logging settings",
|
|
26754
|
+
description: "Log destination, verbosity, rotation",
|
|
26755
|
+
category: "monitoring",
|
|
26756
|
+
sort_order: null,
|
|
26757
|
+
is_system_report: false
|
|
26758
|
+
},
|
|
26759
|
+
{
|
|
26760
|
+
code: "D002",
|
|
26761
|
+
title: "Useful Linux tools",
|
|
26762
|
+
description: "Recommended OS-level tooling",
|
|
26763
|
+
category: "monitoring",
|
|
26764
|
+
sort_order: null,
|
|
26765
|
+
is_system_report: false
|
|
26766
|
+
},
|
|
26767
|
+
{
|
|
26768
|
+
code: "D003",
|
|
26769
|
+
title: "Monitoring metrics",
|
|
26770
|
+
description: "Key metrics to track",
|
|
26771
|
+
category: "monitoring",
|
|
26772
|
+
sort_order: null,
|
|
26773
|
+
is_system_report: false
|
|
26774
|
+
},
|
|
26775
|
+
{
|
|
26776
|
+
code: "D004",
|
|
26777
|
+
title: "pg_stat_statements and pg_stat_kcache",
|
|
26778
|
+
description: "Query stats extension settings",
|
|
26779
|
+
category: "monitoring",
|
|
26780
|
+
sort_order: null,
|
|
26781
|
+
is_system_report: false
|
|
26782
|
+
},
|
|
26783
|
+
{
|
|
26784
|
+
code: "D005",
|
|
26785
|
+
title: "track_io_timing and auto_explain",
|
|
26786
|
+
description: "I/O timing and plan logging",
|
|
26787
|
+
category: "monitoring",
|
|
26788
|
+
sort_order: null,
|
|
26789
|
+
is_system_report: false
|
|
26790
|
+
},
|
|
26791
|
+
{
|
|
26792
|
+
code: "D006",
|
|
26793
|
+
title: "Recommended DBA toolsets",
|
|
26794
|
+
description: "Useful third-party tools",
|
|
26795
|
+
category: "monitoring",
|
|
26796
|
+
sort_order: null,
|
|
26797
|
+
is_system_report: false
|
|
26798
|
+
},
|
|
26799
|
+
{
|
|
26800
|
+
code: "D007",
|
|
26801
|
+
title: "Postgres troubleshooting tools",
|
|
26802
|
+
description: "Built-in and ecosystem diagnostic tools",
|
|
26803
|
+
category: "monitoring",
|
|
26804
|
+
sort_order: null,
|
|
26805
|
+
is_system_report: false
|
|
26806
|
+
},
|
|
26807
|
+
{
|
|
26808
|
+
code: "E001",
|
|
26809
|
+
title: "WAL and checkpoint settings",
|
|
26810
|
+
description: "WAL size, checkpoint timing, I/O limits",
|
|
26811
|
+
category: "wal",
|
|
26812
|
+
sort_order: null,
|
|
26813
|
+
is_system_report: false
|
|
26814
|
+
},
|
|
26815
|
+
{
|
|
26816
|
+
code: "E002",
|
|
26817
|
+
title: "Checkpoint and bgwriter activity",
|
|
26818
|
+
description: "Checkpoint frequency, bgwriter stats",
|
|
26819
|
+
category: "wal",
|
|
26820
|
+
sort_order: null,
|
|
26821
|
+
is_system_report: false
|
|
26822
|
+
},
|
|
26823
|
+
{
|
|
26824
|
+
code: "F001",
|
|
26825
|
+
title: "Autovacuum: current settings",
|
|
26826
|
+
description: "Autovacuum configuration",
|
|
26827
|
+
category: "vacuum",
|
|
26828
|
+
sort_order: null,
|
|
26829
|
+
is_system_report: false
|
|
26830
|
+
},
|
|
26831
|
+
{
|
|
26832
|
+
code: "F002",
|
|
26833
|
+
title: "Autovacuum: transaction ID wraparound",
|
|
26834
|
+
description: "XID age and wraparound risk",
|
|
26835
|
+
category: "vacuum",
|
|
26836
|
+
sort_order: null,
|
|
26837
|
+
is_system_report: false
|
|
26838
|
+
},
|
|
26839
|
+
{
|
|
26840
|
+
code: "F003",
|
|
26841
|
+
title: "Autovacuum: dead tuples",
|
|
26842
|
+
description: "Dead tuple counts and cleanup status",
|
|
26843
|
+
category: "vacuum",
|
|
26844
|
+
sort_order: null,
|
|
26845
|
+
is_system_report: false
|
|
26846
|
+
},
|
|
26847
|
+
{
|
|
26848
|
+
code: "F004",
|
|
26849
|
+
title: "Autovacuum: heap bloat estimate",
|
|
26850
|
+
description: "Estimated table bloat",
|
|
26851
|
+
category: "vacuum",
|
|
26852
|
+
sort_order: null,
|
|
26853
|
+
is_system_report: false
|
|
26854
|
+
},
|
|
26855
|
+
{
|
|
26856
|
+
code: "F005",
|
|
26857
|
+
title: "Autovacuum: index bloat estimate",
|
|
26858
|
+
description: "Estimated index bloat",
|
|
26859
|
+
category: "vacuum",
|
|
26860
|
+
sort_order: null,
|
|
26861
|
+
is_system_report: false
|
|
26862
|
+
},
|
|
26863
|
+
{
|
|
26864
|
+
code: "F006",
|
|
26865
|
+
title: "Precise heap bloat analysis",
|
|
26866
|
+
description: "Detailed table bloat measurement",
|
|
26867
|
+
category: "vacuum",
|
|
26868
|
+
sort_order: null,
|
|
26869
|
+
is_system_report: false
|
|
26870
|
+
},
|
|
26871
|
+
{
|
|
26872
|
+
code: "F007",
|
|
26873
|
+
title: "Precise index bloat analysis",
|
|
26874
|
+
description: "Detailed index bloat measurement",
|
|
26875
|
+
category: "vacuum",
|
|
26876
|
+
sort_order: null,
|
|
26877
|
+
is_system_report: false
|
|
26878
|
+
},
|
|
26879
|
+
{
|
|
26880
|
+
code: "F008",
|
|
26881
|
+
title: "Autovacuum: resource usage",
|
|
26882
|
+
description: "Autovacuum I/O and CPU impact",
|
|
26883
|
+
category: "vacuum",
|
|
26884
|
+
sort_order: null,
|
|
26885
|
+
is_system_report: false
|
|
26886
|
+
},
|
|
26887
|
+
{
|
|
26888
|
+
code: "G001",
|
|
26889
|
+
title: "Memory-related settings",
|
|
26890
|
+
description: "shared_buffers, work_mem, maintenance_work_mem",
|
|
26891
|
+
category: "tuning",
|
|
26892
|
+
sort_order: null,
|
|
26893
|
+
is_system_report: false
|
|
26894
|
+
},
|
|
26895
|
+
{
|
|
26896
|
+
code: "G002",
|
|
26897
|
+
title: "Connections and current activity",
|
|
26898
|
+
description: "Connection usage and active sessions",
|
|
26899
|
+
category: "tuning",
|
|
26900
|
+
sort_order: null,
|
|
26901
|
+
is_system_report: false
|
|
26902
|
+
},
|
|
26903
|
+
{
|
|
26904
|
+
code: "G003",
|
|
26905
|
+
title: "Timeouts, locks, deadlocks",
|
|
26906
|
+
description: "Lock settings and deadlock stats",
|
|
26907
|
+
category: "tuning",
|
|
26908
|
+
sort_order: null,
|
|
26909
|
+
is_system_report: false
|
|
26910
|
+
},
|
|
26911
|
+
{
|
|
26912
|
+
code: "G004",
|
|
26913
|
+
title: "Query planner settings",
|
|
26914
|
+
description: "Planner cost and behavior settings",
|
|
26915
|
+
category: "tuning",
|
|
26916
|
+
sort_order: null,
|
|
26917
|
+
is_system_report: false
|
|
26918
|
+
},
|
|
26919
|
+
{
|
|
26920
|
+
code: "G005",
|
|
26921
|
+
title: "I/O settings",
|
|
26922
|
+
description: "effective_io_concurrency, random_page_cost",
|
|
26923
|
+
category: "tuning",
|
|
26924
|
+
sort_order: null,
|
|
26925
|
+
is_system_report: false
|
|
26926
|
+
},
|
|
26927
|
+
{
|
|
26928
|
+
code: "G006",
|
|
26929
|
+
title: "Statistics target settings",
|
|
26930
|
+
description: "default_statistics_target and per-column targets",
|
|
26931
|
+
category: "tuning",
|
|
26932
|
+
sort_order: null,
|
|
26933
|
+
is_system_report: false
|
|
26934
|
+
},
|
|
26935
|
+
{
|
|
26936
|
+
code: "H001",
|
|
26937
|
+
title: "Invalid indexes",
|
|
26938
|
+
description: "Indexes that failed to build",
|
|
26939
|
+
category: "indexes",
|
|
26940
|
+
sort_order: null,
|
|
26941
|
+
is_system_report: false
|
|
26942
|
+
},
|
|
26943
|
+
{
|
|
26944
|
+
code: "H002",
|
|
26945
|
+
title: "Unused indexes",
|
|
26946
|
+
description: "Indexes with no scans",
|
|
26947
|
+
category: "indexes",
|
|
26948
|
+
sort_order: null,
|
|
26949
|
+
is_system_report: false
|
|
26950
|
+
},
|
|
26951
|
+
{
|
|
26952
|
+
code: "H003",
|
|
26953
|
+
title: "Non-indexed foreign keys",
|
|
26954
|
+
description: "FKs missing supporting indexes",
|
|
26955
|
+
category: "indexes",
|
|
26956
|
+
sort_order: null,
|
|
26957
|
+
is_system_report: false
|
|
26958
|
+
},
|
|
26959
|
+
{
|
|
26960
|
+
code: "H004",
|
|
26961
|
+
title: "Redundant indexes",
|
|
26962
|
+
description: "Indexes covered by other indexes",
|
|
26963
|
+
category: "indexes",
|
|
26964
|
+
sort_order: null,
|
|
26965
|
+
is_system_report: false
|
|
26966
|
+
},
|
|
26967
|
+
{
|
|
26968
|
+
code: "J001",
|
|
26969
|
+
title: "Capacity planning",
|
|
26970
|
+
description: "Growth trends and resource projections",
|
|
26971
|
+
category: "capacity",
|
|
26972
|
+
sort_order: null,
|
|
26973
|
+
is_system_report: false
|
|
26974
|
+
},
|
|
26975
|
+
{
|
|
26976
|
+
code: "K001",
|
|
26977
|
+
title: "Globally aggregated query metrics",
|
|
26978
|
+
description: "Overall query stats summary",
|
|
26979
|
+
category: "queries",
|
|
26980
|
+
sort_order: null,
|
|
26981
|
+
is_system_report: false
|
|
26982
|
+
},
|
|
26983
|
+
{
|
|
26984
|
+
code: "K002",
|
|
26985
|
+
title: "Workload type",
|
|
26986
|
+
description: "Read/write/mixed workload classification",
|
|
26987
|
+
category: "queries",
|
|
26988
|
+
sort_order: null,
|
|
26989
|
+
is_system_report: false
|
|
26990
|
+
},
|
|
26991
|
+
{
|
|
26992
|
+
code: "K003",
|
|
26993
|
+
title: "Top queries by total time",
|
|
26994
|
+
description: "Highest total execution + planning time",
|
|
26995
|
+
category: "queries",
|
|
26996
|
+
sort_order: null,
|
|
26997
|
+
is_system_report: false
|
|
26998
|
+
},
|
|
26999
|
+
{
|
|
27000
|
+
code: "K004",
|
|
27001
|
+
title: "Top queries by temp bytes written",
|
|
27002
|
+
description: "Queries spilling to disk",
|
|
27003
|
+
category: "queries",
|
|
27004
|
+
sort_order: null,
|
|
27005
|
+
is_system_report: false
|
|
27006
|
+
},
|
|
27007
|
+
{
|
|
27008
|
+
code: "K005",
|
|
27009
|
+
title: "Top queries by WAL generation",
|
|
27010
|
+
description: "Queries generating most WAL",
|
|
27011
|
+
category: "queries",
|
|
27012
|
+
sort_order: null,
|
|
27013
|
+
is_system_report: false
|
|
27014
|
+
},
|
|
27015
|
+
{
|
|
27016
|
+
code: "K006",
|
|
27017
|
+
title: "Top queries by shared blocks read",
|
|
27018
|
+
description: "Queries with most disk reads",
|
|
27019
|
+
category: "queries",
|
|
27020
|
+
sort_order: null,
|
|
27021
|
+
is_system_report: false
|
|
27022
|
+
},
|
|
27023
|
+
{
|
|
27024
|
+
code: "K007",
|
|
27025
|
+
title: "Top queries by shared blocks hit",
|
|
27026
|
+
description: "Queries with most buffer hits",
|
|
27027
|
+
category: "queries",
|
|
27028
|
+
sort_order: null,
|
|
27029
|
+
is_system_report: false
|
|
27030
|
+
},
|
|
27031
|
+
{
|
|
27032
|
+
code: "K008",
|
|
27033
|
+
title: "Top queries by shared blocks accessed",
|
|
27034
|
+
description: "Queries touching most data",
|
|
27035
|
+
category: "queries",
|
|
27036
|
+
sort_order: null,
|
|
27037
|
+
is_system_report: false
|
|
27038
|
+
},
|
|
27039
|
+
{
|
|
27040
|
+
code: "L001",
|
|
27041
|
+
title: "Table sizes",
|
|
27042
|
+
description: "Table and toast sizes",
|
|
27043
|
+
category: "schema",
|
|
27044
|
+
sort_order: null,
|
|
27045
|
+
is_system_report: false
|
|
27046
|
+
},
|
|
27047
|
+
{
|
|
27048
|
+
code: "L002",
|
|
27049
|
+
title: "Data types being used",
|
|
27050
|
+
description: "Column type distribution",
|
|
27051
|
+
category: "schema",
|
|
27052
|
+
sort_order: null,
|
|
27053
|
+
is_system_report: false
|
|
27054
|
+
},
|
|
27055
|
+
{
|
|
27056
|
+
code: "L003",
|
|
27057
|
+
title: "Integer overflow risks in primary keys",
|
|
27058
|
+
description: "PKs approaching integer limits",
|
|
27059
|
+
category: "schema",
|
|
27060
|
+
sort_order: null,
|
|
27061
|
+
is_system_report: false
|
|
27062
|
+
},
|
|
27063
|
+
{
|
|
27064
|
+
code: "L004",
|
|
27065
|
+
title: "Tables without primary key",
|
|
27066
|
+
description: "Tables missing PK or unique constraint",
|
|
27067
|
+
category: "schema",
|
|
27068
|
+
sort_order: null,
|
|
27069
|
+
is_system_report: false
|
|
27070
|
+
},
|
|
27071
|
+
{
|
|
27072
|
+
code: "M001",
|
|
27073
|
+
title: "Top queries by mean execution time",
|
|
27074
|
+
description: "Slowest queries on average",
|
|
27075
|
+
category: "queries",
|
|
27076
|
+
sort_order: null,
|
|
27077
|
+
is_system_report: false
|
|
27078
|
+
},
|
|
27079
|
+
{
|
|
27080
|
+
code: "M002",
|
|
27081
|
+
title: "Top queries by rows processed",
|
|
27082
|
+
description: "Queries touching most rows",
|
|
27083
|
+
category: "queries",
|
|
27084
|
+
sort_order: null,
|
|
27085
|
+
is_system_report: false
|
|
27086
|
+
},
|
|
27087
|
+
{
|
|
27088
|
+
code: "M003",
|
|
27089
|
+
title: "Top queries by I/O time",
|
|
27090
|
+
description: "Queries with highest I/O wait",
|
|
27091
|
+
category: "queries",
|
|
27092
|
+
sort_order: null,
|
|
27093
|
+
is_system_report: false
|
|
27094
|
+
},
|
|
27095
|
+
{
|
|
27096
|
+
code: "N001",
|
|
27097
|
+
title: "Wait events by type and query",
|
|
27098
|
+
description: "Wait event analysis",
|
|
27099
|
+
category: "waits",
|
|
27100
|
+
sort_order: null,
|
|
27101
|
+
is_system_report: false
|
|
27102
|
+
}
|
|
27103
|
+
];
|
|
27104
|
+
|
|
27105
|
+
// lib/checkup-dictionary.ts
|
|
27106
|
+
var dictionaryByCode = new Map(CHECKUP_DICTIONARY_DATA.map((entry) => [entry.code.toUpperCase(), entry]));
|
|
27107
|
+
function buildCheckInfoMap() {
|
|
27108
|
+
const result = {};
|
|
27109
|
+
for (const entry of CHECKUP_DICTIONARY_DATA) {
|
|
27110
|
+
result[entry.code] = entry.title;
|
|
27111
|
+
}
|
|
27112
|
+
return result;
|
|
27113
|
+
}
|
|
27114
|
+
|
|
26485
27115
|
// lib/checkup.ts
|
|
26486
27116
|
var __dirname = "/builds/postgres-ai/postgres_ai/cli/lib";
|
|
26487
27117
|
var SECONDS_PER_DAY = 86400;
|
|
@@ -27125,6 +27755,23 @@ async function generateD004(client, nodeName) {
|
|
|
27125
27755
|
};
|
|
27126
27756
|
return report;
|
|
27127
27757
|
}
|
|
27758
|
+
async function generateD001(client, nodeName) {
|
|
27759
|
+
const report = createBaseReport("D001", "Logging settings", nodeName);
|
|
27760
|
+
const postgresVersion = await getPostgresVersion(client);
|
|
27761
|
+
const pgMajorVersion = parseInt(postgresVersion.server_major_ver, 10) || 16;
|
|
27762
|
+
const allSettings = await getSettings(client, pgMajorVersion);
|
|
27763
|
+
const loggingSettings = {};
|
|
27764
|
+
for (const [name, setting] of Object.entries(allSettings)) {
|
|
27765
|
+
if (name.startsWith("log_") || name.startsWith("logging_")) {
|
|
27766
|
+
loggingSettings[name] = setting;
|
|
27767
|
+
}
|
|
27768
|
+
}
|
|
27769
|
+
report.results[nodeName] = {
|
|
27770
|
+
data: loggingSettings,
|
|
27771
|
+
postgres_version: postgresVersion
|
|
27772
|
+
};
|
|
27773
|
+
return report;
|
|
27774
|
+
}
|
|
27128
27775
|
async function generateF001(client, nodeName) {
|
|
27129
27776
|
const report = createBaseReport("F001", "Autovacuum: current settings", nodeName);
|
|
27130
27777
|
const postgresVersion = await getPostgresVersion(client);
|
|
@@ -27225,32 +27872,88 @@ async function generateG001(client, nodeName) {
|
|
|
27225
27872
|
};
|
|
27226
27873
|
return report;
|
|
27227
27874
|
}
|
|
27875
|
+
async function generateG003(client, nodeName) {
|
|
27876
|
+
const report = createBaseReport("G003", "Timeouts, locks, deadlocks", nodeName);
|
|
27877
|
+
const postgresVersion = await getPostgresVersion(client);
|
|
27878
|
+
const pgMajorVersion = parseInt(postgresVersion.server_major_ver, 10) || 16;
|
|
27879
|
+
const allSettings = await getSettings(client, pgMajorVersion);
|
|
27880
|
+
const lockTimeoutSettingNames = [
|
|
27881
|
+
"lock_timeout",
|
|
27882
|
+
"statement_timeout",
|
|
27883
|
+
"idle_in_transaction_session_timeout",
|
|
27884
|
+
"idle_session_timeout",
|
|
27885
|
+
"deadlock_timeout",
|
|
27886
|
+
"max_locks_per_transaction",
|
|
27887
|
+
"max_pred_locks_per_transaction",
|
|
27888
|
+
"max_pred_locks_per_relation",
|
|
27889
|
+
"max_pred_locks_per_page",
|
|
27890
|
+
"log_lock_waits",
|
|
27891
|
+
"transaction_timeout"
|
|
27892
|
+
];
|
|
27893
|
+
const lockSettings = {};
|
|
27894
|
+
for (const name of lockTimeoutSettingNames) {
|
|
27895
|
+
if (allSettings[name]) {
|
|
27896
|
+
lockSettings[name] = allSettings[name];
|
|
27897
|
+
}
|
|
27898
|
+
}
|
|
27899
|
+
let deadlockStats = null;
|
|
27900
|
+
let deadlockError = null;
|
|
27901
|
+
try {
|
|
27902
|
+
const statsResult = await client.query(`
|
|
27903
|
+
select
|
|
27904
|
+
coalesce(sum(deadlocks), 0)::bigint as deadlocks,
|
|
27905
|
+
coalesce(sum(conflicts), 0)::bigint as conflicts,
|
|
27906
|
+
min(stats_reset)::text as stats_reset
|
|
27907
|
+
from pg_stat_database
|
|
27908
|
+
where datname = current_database()
|
|
27909
|
+
`);
|
|
27910
|
+
if (statsResult.rows.length > 0) {
|
|
27911
|
+
const row = statsResult.rows[0];
|
|
27912
|
+
deadlockStats = {
|
|
27913
|
+
deadlocks: parseInt(row.deadlocks, 10),
|
|
27914
|
+
conflicts: parseInt(row.conflicts, 10),
|
|
27915
|
+
stats_reset: row.stats_reset || null
|
|
27916
|
+
};
|
|
27917
|
+
}
|
|
27918
|
+
} catch (err) {
|
|
27919
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
27920
|
+
console.log(`[G003] Error querying deadlock stats: ${errorMsg}`);
|
|
27921
|
+
deadlockError = errorMsg;
|
|
27922
|
+
}
|
|
27923
|
+
report.results[nodeName] = {
|
|
27924
|
+
data: {
|
|
27925
|
+
settings: lockSettings,
|
|
27926
|
+
deadlock_stats: deadlockStats,
|
|
27927
|
+
...deadlockError && { deadlock_stats_error: deadlockError }
|
|
27928
|
+
},
|
|
27929
|
+
postgres_version: postgresVersion
|
|
27930
|
+
};
|
|
27931
|
+
return report;
|
|
27932
|
+
}
|
|
27228
27933
|
var REPORT_GENERATORS = {
|
|
27229
27934
|
A002: generateA002,
|
|
27230
27935
|
A003: generateA003,
|
|
27231
27936
|
A004: generateA004,
|
|
27232
27937
|
A007: generateA007,
|
|
27233
27938
|
A013: generateA013,
|
|
27939
|
+
D001: generateD001,
|
|
27234
27940
|
D004: generateD004,
|
|
27235
27941
|
F001: generateF001,
|
|
27236
27942
|
G001: generateG001,
|
|
27943
|
+
G003: generateG003,
|
|
27237
27944
|
H001: generateH001,
|
|
27238
27945
|
H002: generateH002,
|
|
27239
27946
|
H004: generateH004
|
|
27240
27947
|
};
|
|
27241
|
-
var CHECK_INFO = {
|
|
27242
|
-
|
|
27243
|
-
|
|
27244
|
-
|
|
27245
|
-
|
|
27246
|
-
|
|
27247
|
-
|
|
27248
|
-
|
|
27249
|
-
|
|
27250
|
-
H001: "Invalid indexes",
|
|
27251
|
-
H002: "Unused indexes",
|
|
27252
|
-
H004: "Redundant indexes"
|
|
27253
|
-
};
|
|
27948
|
+
var CHECK_INFO = (() => {
|
|
27949
|
+
const fullMap = buildCheckInfoMap();
|
|
27950
|
+
const expressCheckIds = Object.keys(REPORT_GENERATORS);
|
|
27951
|
+
const filtered = {};
|
|
27952
|
+
for (const checkId of expressCheckIds) {
|
|
27953
|
+
filtered[checkId] = fullMap[checkId] || checkId;
|
|
27954
|
+
}
|
|
27955
|
+
return filtered;
|
|
27956
|
+
})();
|
|
27254
27957
|
async function generateAllReports(client, nodeName = "node-01", onProgress) {
|
|
27255
27958
|
const reports = {};
|
|
27256
27959
|
const entries = Object.entries(REPORT_GENERATORS);
|
|
@@ -27269,6 +27972,12 @@ async function generateAllReports(client, nodeName = "node-01", onProgress) {
|
|
|
27269
27972
|
return reports;
|
|
27270
27973
|
}
|
|
27271
27974
|
|
|
27975
|
+
// lib/checkup-dictionary.ts
|
|
27976
|
+
var dictionaryByCode2 = new Map(CHECKUP_DICTIONARY_DATA.map((entry) => [entry.code.toUpperCase(), entry]));
|
|
27977
|
+
function getCheckupEntry(code) {
|
|
27978
|
+
return dictionaryByCode2.get(code.toUpperCase()) ?? null;
|
|
27979
|
+
}
|
|
27980
|
+
|
|
27272
27981
|
// lib/checkup-api.ts
|
|
27273
27982
|
import * as https from "https";
|
|
27274
27983
|
import { URL as URL3 } from "url";
|
|
@@ -28229,12 +28938,16 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
28229
28938
|
if (errAny.code === "42501") {
|
|
28230
28939
|
if (failedStep === "01.role") {
|
|
28231
28940
|
console.error(" Context: role creation/update requires CREATEROLE or superuser");
|
|
28232
|
-
} else if (failedStep === "
|
|
28941
|
+
} else if (failedStep === "03.permissions") {
|
|
28233
28942
|
console.error(" Context: grants/view/search_path require sufficient GRANT/DDL privileges");
|
|
28234
28943
|
}
|
|
28235
28944
|
console.error(" Fix: ensure your Supabase access token has sufficient permissions");
|
|
28236
28945
|
console.error(" Tip: run with --print-sql to review the exact SQL plan");
|
|
28237
28946
|
}
|
|
28947
|
+
if (errAny.code === "42P06" || message.includes("already exists") && failedStep === "03.permissions") {
|
|
28948
|
+
console.error(" Hint: postgres_ai schema or objects already exist from a previous setup.");
|
|
28949
|
+
console.error(" Fix: run 'postgresai unprepare-db <connection>' first to clean up, then retry prepare-db.");
|
|
28950
|
+
}
|
|
28238
28951
|
}
|
|
28239
28952
|
if (errAny && typeof errAny === "object" && typeof errAny.httpStatus === "number") {
|
|
28240
28953
|
if (errAny.httpStatus === 401) {
|
|
@@ -28498,13 +29211,17 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
28498
29211
|
if (errAny.code === "42501") {
|
|
28499
29212
|
if (failedStep === "01.role") {
|
|
28500
29213
|
console.error(" Context: role creation/update requires CREATEROLE or superuser");
|
|
28501
|
-
} else if (failedStep === "
|
|
29214
|
+
} else if (failedStep === "03.permissions") {
|
|
28502
29215
|
console.error(" Context: grants/view/search_path require sufficient GRANT/DDL privileges");
|
|
28503
29216
|
}
|
|
28504
29217
|
console.error(" Fix: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges)");
|
|
28505
29218
|
console.error(" Fix: on managed Postgres, use the provider's admin/master user");
|
|
28506
29219
|
console.error(" Tip: run with --print-sql to review the exact SQL plan");
|
|
28507
29220
|
}
|
|
29221
|
+
if (errAny.code === "42P06" || message.includes("already exists") && failedStep === "03.permissions") {
|
|
29222
|
+
console.error(" Hint: postgres_ai schema or objects already exist from a previous setup.");
|
|
29223
|
+
console.error(" Fix: run 'postgresai unprepare-db <connection>' first to clean up, then retry prepare-db.");
|
|
29224
|
+
}
|
|
28508
29225
|
if (errAny.code === "ECONNREFUSED") {
|
|
28509
29226
|
console.error(" Hint: check host/port and ensure Postgres is reachable from this machine");
|
|
28510
29227
|
}
|
|
@@ -28525,18 +29242,258 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
28525
29242
|
}
|
|
28526
29243
|
}
|
|
28527
29244
|
});
|
|
28528
|
-
program2.command("
|
|
29245
|
+
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", [
|
|
29246
|
+
"",
|
|
29247
|
+
"Examples:",
|
|
29248
|
+
" postgresai unprepare-db postgresql://admin@host:5432/dbname",
|
|
29249
|
+
' postgresai unprepare-db "dbname=dbname host=host user=admin"',
|
|
29250
|
+
" postgresai unprepare-db -h host -p 5432 -U admin -d dbname",
|
|
29251
|
+
"",
|
|
29252
|
+
"Admin password:",
|
|
29253
|
+
" --admin-password <password> or PGPASSWORD=... (libpq standard)",
|
|
29254
|
+
"",
|
|
29255
|
+
"Keep role but remove objects/permissions:",
|
|
29256
|
+
" postgresai unprepare-db <conn> --keep-role",
|
|
29257
|
+
"",
|
|
29258
|
+
"Inspect SQL without applying changes:",
|
|
29259
|
+
" postgresai unprepare-db <conn> --print-sql",
|
|
29260
|
+
"",
|
|
29261
|
+
"Offline SQL plan (no DB connection):",
|
|
29262
|
+
" postgresai unprepare-db --print-sql",
|
|
29263
|
+
"",
|
|
29264
|
+
"Skip confirmation prompt:",
|
|
29265
|
+
" postgresai unprepare-db <conn> --force"
|
|
29266
|
+
].join(`
|
|
29267
|
+
`)).action(async (conn, opts, cmd) => {
|
|
29268
|
+
const jsonOutput = opts.json;
|
|
29269
|
+
const outputJson = (data) => {
|
|
29270
|
+
console.log(JSON.stringify(data, null, 2));
|
|
29271
|
+
};
|
|
29272
|
+
const outputError = (error2) => {
|
|
29273
|
+
if (jsonOutput) {
|
|
29274
|
+
outputJson({
|
|
29275
|
+
success: false,
|
|
29276
|
+
error: error2
|
|
29277
|
+
});
|
|
29278
|
+
} else {
|
|
29279
|
+
console.error(`Error: unprepare-db: ${error2.message}`);
|
|
29280
|
+
if (error2.step)
|
|
29281
|
+
console.error(` Step: ${error2.step}`);
|
|
29282
|
+
if (error2.code)
|
|
29283
|
+
console.error(` Code: ${error2.code}`);
|
|
29284
|
+
if (error2.detail)
|
|
29285
|
+
console.error(` Detail: ${error2.detail}`);
|
|
29286
|
+
if (error2.hint)
|
|
29287
|
+
console.error(` Hint: ${error2.hint}`);
|
|
29288
|
+
}
|
|
29289
|
+
process.exitCode = 1;
|
|
29290
|
+
};
|
|
29291
|
+
const shouldPrintSql = !!opts.printSql;
|
|
29292
|
+
const dropRole = !opts.keepRole;
|
|
29293
|
+
const providerWarning = validateProvider(opts.provider);
|
|
29294
|
+
if (providerWarning) {
|
|
29295
|
+
console.warn(`\u26A0 ${providerWarning}`);
|
|
29296
|
+
}
|
|
29297
|
+
if (!conn && !opts.dbUrl && !opts.host && !opts.port && !opts.username && !opts.adminPassword) {
|
|
29298
|
+
if (shouldPrintSql) {
|
|
29299
|
+
const database = (opts.dbname ?? process.env.PGDATABASE ?? "postgres").trim();
|
|
29300
|
+
const plan = await buildUninitPlan({
|
|
29301
|
+
database,
|
|
29302
|
+
monitoringUser: opts.monitoringUser,
|
|
29303
|
+
dropRole,
|
|
29304
|
+
provider: opts.provider
|
|
29305
|
+
});
|
|
29306
|
+
console.log(`
|
|
29307
|
+
--- SQL plan (offline; not connected) ---`);
|
|
29308
|
+
console.log(`-- database: ${database}`);
|
|
29309
|
+
console.log(`-- monitoring user: ${opts.monitoringUser}`);
|
|
29310
|
+
console.log(`-- provider: ${opts.provider ?? "self-managed"}`);
|
|
29311
|
+
console.log(`-- drop role: ${dropRole}`);
|
|
29312
|
+
for (const step of plan.steps) {
|
|
29313
|
+
console.log(`
|
|
29314
|
+
-- ${step.name}`);
|
|
29315
|
+
console.log(step.sql);
|
|
29316
|
+
}
|
|
29317
|
+
console.log(`
|
|
29318
|
+
--- end SQL plan ---
|
|
29319
|
+
`);
|
|
29320
|
+
return;
|
|
29321
|
+
}
|
|
29322
|
+
}
|
|
29323
|
+
let adminConn;
|
|
29324
|
+
try {
|
|
29325
|
+
adminConn = resolveAdminConnection({
|
|
29326
|
+
conn,
|
|
29327
|
+
dbUrlFlag: opts.dbUrl,
|
|
29328
|
+
host: opts.host ?? process.env.PGHOST,
|
|
29329
|
+
port: opts.port ?? process.env.PGPORT,
|
|
29330
|
+
username: opts.username ?? process.env.PGUSER,
|
|
29331
|
+
dbname: opts.dbname ?? process.env.PGDATABASE,
|
|
29332
|
+
adminPassword: opts.adminPassword,
|
|
29333
|
+
envPassword: process.env.PGPASSWORD
|
|
29334
|
+
});
|
|
29335
|
+
} catch (e) {
|
|
29336
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
29337
|
+
if (jsonOutput) {
|
|
29338
|
+
outputError({ message: msg });
|
|
29339
|
+
} else {
|
|
29340
|
+
console.error(`Error: unprepare-db: ${msg}`);
|
|
29341
|
+
if (typeof msg === "string" && msg.startsWith("Connection is required.")) {
|
|
29342
|
+
console.error("");
|
|
29343
|
+
cmd.outputHelp({ error: true });
|
|
29344
|
+
}
|
|
29345
|
+
process.exitCode = 1;
|
|
29346
|
+
}
|
|
29347
|
+
return;
|
|
29348
|
+
}
|
|
29349
|
+
if (!jsonOutput) {
|
|
29350
|
+
console.log(`Connecting to: ${adminConn.display}`);
|
|
29351
|
+
console.log(`Monitoring user: ${opts.monitoringUser}`);
|
|
29352
|
+
console.log(`Drop role: ${dropRole}`);
|
|
29353
|
+
}
|
|
29354
|
+
if (!opts.force && !jsonOutput && !shouldPrintSql) {
|
|
29355
|
+
const answer = await new Promise((resolve6) => {
|
|
29356
|
+
const readline = getReadline();
|
|
29357
|
+
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()));
|
|
29358
|
+
});
|
|
29359
|
+
if (answer !== "y" && answer !== "yes") {
|
|
29360
|
+
console.log("Aborted.");
|
|
29361
|
+
return;
|
|
29362
|
+
}
|
|
29363
|
+
}
|
|
29364
|
+
let client;
|
|
29365
|
+
try {
|
|
29366
|
+
const connResult = await connectWithSslFallback(Client, adminConn);
|
|
29367
|
+
client = connResult.client;
|
|
29368
|
+
const dbRes = await client.query("select current_database() as db");
|
|
29369
|
+
const database = dbRes.rows?.[0]?.db;
|
|
29370
|
+
if (typeof database !== "string" || !database) {
|
|
29371
|
+
throw new Error("Failed to resolve current database name");
|
|
29372
|
+
}
|
|
29373
|
+
const plan = await buildUninitPlan({
|
|
29374
|
+
database,
|
|
29375
|
+
monitoringUser: opts.monitoringUser,
|
|
29376
|
+
dropRole,
|
|
29377
|
+
provider: opts.provider
|
|
29378
|
+
});
|
|
29379
|
+
if (shouldPrintSql) {
|
|
29380
|
+
console.log(`
|
|
29381
|
+
--- SQL plan ---`);
|
|
29382
|
+
for (const step of plan.steps) {
|
|
29383
|
+
console.log(`
|
|
29384
|
+
-- ${step.name}`);
|
|
29385
|
+
console.log(step.sql);
|
|
29386
|
+
}
|
|
29387
|
+
console.log(`
|
|
29388
|
+
--- end SQL plan ---
|
|
29389
|
+
`);
|
|
29390
|
+
return;
|
|
29391
|
+
}
|
|
29392
|
+
const { applied, errors: errors3 } = await applyUninitPlan({ client, plan });
|
|
29393
|
+
if (jsonOutput) {
|
|
29394
|
+
outputJson({
|
|
29395
|
+
success: errors3.length === 0,
|
|
29396
|
+
action: "unprepare",
|
|
29397
|
+
database,
|
|
29398
|
+
monitoringUser: opts.monitoringUser,
|
|
29399
|
+
dropRole,
|
|
29400
|
+
applied,
|
|
29401
|
+
errors: errors3
|
|
29402
|
+
});
|
|
29403
|
+
if (errors3.length > 0) {
|
|
29404
|
+
process.exitCode = 1;
|
|
29405
|
+
}
|
|
29406
|
+
} else {
|
|
29407
|
+
if (errors3.length === 0) {
|
|
29408
|
+
console.log("\u2713 unprepare-db completed");
|
|
29409
|
+
console.log(`Applied ${applied.length} steps`);
|
|
29410
|
+
} else {
|
|
29411
|
+
console.log("\u26A0 unprepare-db completed with errors");
|
|
29412
|
+
console.log(`Applied ${applied.length} steps`);
|
|
29413
|
+
console.log("Errors:");
|
|
29414
|
+
for (const err of errors3) {
|
|
29415
|
+
console.log(` - ${err}`);
|
|
29416
|
+
}
|
|
29417
|
+
process.exitCode = 1;
|
|
29418
|
+
}
|
|
29419
|
+
}
|
|
29420
|
+
} catch (error2) {
|
|
29421
|
+
const errAny = error2;
|
|
29422
|
+
let message = "";
|
|
29423
|
+
if (error2 instanceof Error && error2.message) {
|
|
29424
|
+
message = error2.message;
|
|
29425
|
+
} else if (errAny && typeof errAny === "object" && typeof errAny.message === "string" && errAny.message) {
|
|
29426
|
+
message = errAny.message;
|
|
29427
|
+
} else {
|
|
29428
|
+
message = String(error2);
|
|
29429
|
+
}
|
|
29430
|
+
if (!message || message === "[object Object]") {
|
|
29431
|
+
message = "Unknown error";
|
|
29432
|
+
}
|
|
29433
|
+
const errorObj = { message };
|
|
29434
|
+
if (errAny && typeof errAny === "object") {
|
|
29435
|
+
if (typeof errAny.code === "string" && errAny.code)
|
|
29436
|
+
errorObj.code = errAny.code;
|
|
29437
|
+
if (typeof errAny.detail === "string" && errAny.detail)
|
|
29438
|
+
errorObj.detail = errAny.detail;
|
|
29439
|
+
if (typeof errAny.hint === "string" && errAny.hint)
|
|
29440
|
+
errorObj.hint = errAny.hint;
|
|
29441
|
+
}
|
|
29442
|
+
if (jsonOutput) {
|
|
29443
|
+
outputJson({
|
|
29444
|
+
success: false,
|
|
29445
|
+
error: errorObj
|
|
29446
|
+
});
|
|
29447
|
+
process.exitCode = 1;
|
|
29448
|
+
} else {
|
|
29449
|
+
console.error(`Error: unprepare-db: ${message}`);
|
|
29450
|
+
if (errAny && typeof errAny === "object") {
|
|
29451
|
+
if (typeof errAny.code === "string" && errAny.code) {
|
|
29452
|
+
console.error(` Code: ${errAny.code}`);
|
|
29453
|
+
}
|
|
29454
|
+
if (typeof errAny.detail === "string" && errAny.detail) {
|
|
29455
|
+
console.error(` Detail: ${errAny.detail}`);
|
|
29456
|
+
}
|
|
29457
|
+
if (typeof errAny.hint === "string" && errAny.hint) {
|
|
29458
|
+
console.error(` Hint: ${errAny.hint}`);
|
|
29459
|
+
}
|
|
29460
|
+
}
|
|
29461
|
+
if (errAny && typeof errAny === "object" && typeof errAny.code === "string") {
|
|
29462
|
+
if (errAny.code === "42501") {
|
|
29463
|
+
console.error(" Context: dropping roles/objects requires sufficient privileges");
|
|
29464
|
+
console.error(" Fix: connect as a superuser (or a role with appropriate DROP privileges)");
|
|
29465
|
+
}
|
|
29466
|
+
if (errAny.code === "ECONNREFUSED") {
|
|
29467
|
+
console.error(" Hint: check host/port and ensure Postgres is reachable from this machine");
|
|
29468
|
+
}
|
|
29469
|
+
if (errAny.code === "ENOTFOUND") {
|
|
29470
|
+
console.error(" Hint: DNS resolution failed; double-check the host name");
|
|
29471
|
+
}
|
|
29472
|
+
if (errAny.code === "ETIMEDOUT") {
|
|
29473
|
+
console.error(" Hint: connection timed out; check network/firewall rules");
|
|
29474
|
+
}
|
|
29475
|
+
}
|
|
29476
|
+
process.exitCode = 1;
|
|
29477
|
+
}
|
|
29478
|
+
} finally {
|
|
29479
|
+
if (client) {
|
|
29480
|
+
try {
|
|
29481
|
+
await client.end();
|
|
29482
|
+
} catch {}
|
|
29483
|
+
}
|
|
29484
|
+
closeReadline();
|
|
29485
|
+
}
|
|
29486
|
+
});
|
|
29487
|
+
program2.command("checkup [conn]").description("generate health check reports directly from PostgreSQL (express mode)").option("--check-id <id>", `specific check to run (see list below), 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", [
|
|
28529
29488
|
"",
|
|
28530
29489
|
"Available checks:",
|
|
28531
29490
|
...Object.entries(CHECK_INFO).map(([id, title]) => ` ${id}: ${title}`),
|
|
28532
29491
|
"",
|
|
28533
29492
|
"Examples:",
|
|
28534
29493
|
" postgresai checkup postgresql://user:pass@host:5432/db",
|
|
28535
|
-
" postgresai checkup postgresql://user:pass@host:5432/db --check-id
|
|
29494
|
+
" postgresai checkup postgresql://user:pass@host:5432/db --check-id D001",
|
|
28536
29495
|
" postgresai checkup postgresql://user:pass@host:5432/db --output ./reports",
|
|
28537
29496
|
" postgresai checkup postgresql://user:pass@host:5432/db --project my_project",
|
|
28538
|
-
" postgresai set-default-project my_project",
|
|
28539
|
-
" postgresai checkup postgresql://user:pass@host:5432/db",
|
|
28540
29497
|
" postgresai checkup postgresql://user:pass@host:5432/db --no-upload --json"
|
|
28541
29498
|
].join(`
|
|
28542
29499
|
`)).action(async (conn, opts, cmd) => {
|
|
@@ -28584,8 +29541,14 @@ program2.command("checkup [conn]").description("generate health check reports di
|
|
|
28584
29541
|
const generator = REPORT_GENERATORS[checkId];
|
|
28585
29542
|
if (!generator) {
|
|
28586
29543
|
spinner.stop();
|
|
28587
|
-
|
|
28588
|
-
|
|
29544
|
+
const dictEntry = getCheckupEntry(checkId);
|
|
29545
|
+
if (dictEntry) {
|
|
29546
|
+
console.error(`Check ${checkId} (${dictEntry.title}) is not yet available in express mode.`);
|
|
29547
|
+
console.error(`Express-mode checks: ${Object.keys(CHECK_INFO).join(", ")}`);
|
|
29548
|
+
} else {
|
|
29549
|
+
console.error(`Unknown check ID: ${opts.checkId}`);
|
|
29550
|
+
console.error(`See 'postgresai checkup --help' for available checks.`);
|
|
29551
|
+
}
|
|
28589
29552
|
process.exitCode = 1;
|
|
28590
29553
|
return;
|
|
28591
29554
|
}
|
|
@@ -28651,14 +29614,14 @@ async function resolveOrInitPaths() {
|
|
|
28651
29614
|
}
|
|
28652
29615
|
function isDockerRunning() {
|
|
28653
29616
|
try {
|
|
28654
|
-
const result = spawnSync2("docker", ["info"], { stdio: "pipe" });
|
|
29617
|
+
const result = spawnSync2("docker", ["info"], { stdio: "pipe", timeout: 5000 });
|
|
28655
29618
|
return result.status === 0;
|
|
28656
29619
|
} catch {
|
|
28657
29620
|
return false;
|
|
28658
29621
|
}
|
|
28659
29622
|
}
|
|
28660
29623
|
function getComposeCmd() {
|
|
28661
|
-
const tryCmd = (cmd, args) => spawnSync2(cmd, args, { stdio: "ignore" }).status === 0;
|
|
29624
|
+
const tryCmd = (cmd, args) => spawnSync2(cmd, args, { stdio: "ignore", timeout: 5000 }).status === 0;
|
|
28662
29625
|
if (tryCmd("docker-compose", ["version"]))
|
|
28663
29626
|
return ["docker-compose"];
|
|
28664
29627
|
if (tryCmd("docker", ["compose", "version"]))
|
|
@@ -28667,7 +29630,7 @@ function getComposeCmd() {
|
|
|
28667
29630
|
}
|
|
28668
29631
|
function checkRunningContainers() {
|
|
28669
29632
|
try {
|
|
28670
|
-
const result = spawnSync2("docker", ["ps", "--filter", "name=grafana-with-datasources", "--filter", "name=pgwatch", "--format", "{{.Names}}"], { stdio: "pipe", encoding: "utf8" });
|
|
29633
|
+
const result = spawnSync2("docker", ["ps", "--filter", "name=grafana-with-datasources", "--filter", "name=pgwatch", "--format", "{{.Names}}"], { stdio: "pipe", encoding: "utf8", timeout: 5000 });
|
|
28671
29634
|
if (result.status === 0 && result.stdout) {
|
|
28672
29635
|
const containers = result.stdout.trim().split(`
|
|
28673
29636
|
`).filter(Boolean);
|