postgresai 0.17.0-dev.2 → 0.17.0-rc.2

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/README.md CHANGED
@@ -250,7 +250,7 @@ postgresai mon health [--wait <sec>] # Check monitoring services health
250
250
 
251
251
  When `--instance-id <uuid>` (or `PGAI_INSTANCE_ID`) is set, `local-install` forwards the id to the platform, which **adopts** the already-provisioned monitoring instance instead of self-registering a duplicate under an auto-created `postgres-ai-monitoring` project. The CLI then persists the adopted instance's real project to `.pgwatch-config`, so checkup reports upload alongside the rest of that instance's health data. Adoption is awaited (with one automatic retry); if it fails, the CLI warns and reports fall back to the default project until you re-run `local-install`. Without the flag, the legacy self-registration path is byte-for-byte unchanged.
252
252
 
253
- `local-install` writes `.env` in the monitoring directory. It preserves existing `REPLICATOR_PASSWORD` and `VM_AUTH_*` values or generates new random ones when missing; `VM_AUTH_USERNAME` defaults to `vmauth` when absent. The replication password is used by the demo PostgreSQL standby replication user, and the VM auth credentials are required before Docker Compose can provision Grafana datasources. If you run `docker compose` directly or maintain `.env` yourself, set both VM auth values before upgrading. For rotation, run `VM_AUTH_PASSWORD="$(openssl rand -base64 18)" ./scripts/rotate-vm-auth.sh` from the monitoring directory so `.env`, `sink-prometheus`, and `grafana` update together.
253
+ `local-install` writes `.env` in the monitoring directory. It preserves existing `REPLICATOR_PASSWORD`, `VM_AUTH_*` and the VictoriaMetrics admin-endpoint keys (`VM_DELETE_AUTH_KEY`, `VM_SNAPSHOT_AUTH_KEY`, `VM_FORCE_MERGE_AUTH_KEY`, `VM_PPROF_AUTH_KEY`, added in 0.17) or generates new random ones when missing. The admin keys live on the `sink-prometheus` command line, so `mon update` and `mon update-config` also recreate that container to apply them; `mon restart` (`docker compose restart`) does not; `VM_AUTH_USERNAME` defaults to `vmauth` when absent. The replication password is used by the demo PostgreSQL standby replication user, and the VM auth credentials are required before Docker Compose can provision Grafana datasources. If you run `docker compose` directly or maintain `.env` yourself, set both VM auth values before upgrading. For rotation, run `VM_AUTH_PASSWORD="$(openssl rand -base64 18)" ./scripts/rotate-vm-auth.sh` from the monitoring directory so `.env`, `sink-prometheus`, and `grafana` update together.
254
254
 
255
255
  #### Monitoring target databases (`mon targets` subgroup)
256
256
  ```bash
@@ -13524,7 +13524,7 @@ var {
13524
13524
  // package.json
13525
13525
  var package_default = {
13526
13526
  name: "postgresai",
13527
- version: "0.17.0-dev.2",
13527
+ version: "0.17.0-rc.2",
13528
13528
  description: "postgres_ai CLI",
13529
13529
  license: "Apache-2.0",
13530
13530
  private: false,
@@ -16884,7 +16884,7 @@ var Result = import_lib.default.Result;
16884
16884
  var TypeOverrides = import_lib.default.TypeOverrides;
16885
16885
  var defaults = import_lib.default.defaults;
16886
16886
  // package.json
16887
- var version = "0.17.0-dev.2";
16887
+ var version = "0.17.0-rc.2";
16888
16888
  var package_default2 = {
16889
16889
  name: "postgresai",
16890
16890
  version,
@@ -38701,47 +38701,101 @@ function stripMatchingQuotes(value) {
38701
38701
  }
38702
38702
  return trimmed;
38703
38703
  }
38704
+ var VM_ADMIN_AUTH_KEYS = [
38705
+ "VM_DELETE_AUTH_KEY",
38706
+ "VM_SNAPSHOT_AUTH_KEY",
38707
+ "VM_FORCE_MERGE_AUTH_KEY",
38708
+ "VM_PPROF_AUTH_KEY"
38709
+ ];
38704
38710
  var REQUIRED_ENV_KEYS = [
38705
38711
  { key: "REPLICATOR_PASSWORD", defaultValue: () => crypto2.randomBytes(32).toString("hex"), introducedIn: "0.13" },
38706
38712
  { key: "VM_AUTH_USERNAME", defaultValue: () => "vmauth", introducedIn: "0.15" },
38707
- { key: "VM_AUTH_PASSWORD", defaultValue: () => crypto2.randomBytes(18).toString("base64"), introducedIn: "0.15" }
38713
+ { key: "VM_AUTH_PASSWORD", defaultValue: () => crypto2.randomBytes(18).toString("base64"), introducedIn: "0.15" },
38714
+ { key: "VM_DELETE_AUTH_KEY", defaultValue: () => crypto2.randomBytes(32).toString("hex"), introducedIn: "0.17" },
38715
+ { key: "VM_SNAPSHOT_AUTH_KEY", defaultValue: () => crypto2.randomBytes(32).toString("hex"), introducedIn: "0.17" },
38716
+ { key: "VM_FORCE_MERGE_AUTH_KEY", defaultValue: () => crypto2.randomBytes(32).toString("hex"), introducedIn: "0.17" },
38717
+ { key: "VM_PPROF_AUTH_KEY", defaultValue: () => crypto2.randomBytes(32).toString("hex"), introducedIn: "0.17" }
38708
38718
  ];
38709
38719
  function ensureRequiredEnvVars(projectDir) {
38710
38720
  const envFile = path7.resolve(projectDir, ".env");
38711
38721
  const existing = fs9.existsSync(envFile) ? fs9.readFileSync(envFile, "utf8") : "";
38712
38722
  const added = [];
38713
38723
  const appendLines = [];
38724
+ let content = existing;
38714
38725
  for (const spec of REQUIRED_ENV_KEYS) {
38715
- const re = new RegExp(`^${spec.key}=`, "m");
38716
- if (!re.test(existing)) {
38726
+ const present = new RegExp(`^[ ]*(?:export[ ]+)?${spec.key}=`, "m");
38727
+ if (!present.test(content)) {
38717
38728
  appendLines.push(`${spec.key}=${spec.defaultValue()}`);
38718
38729
  added.push(spec.key);
38730
+ continue;
38731
+ }
38732
+ if (VM_ADMIN_AUTH_KEYS.includes(spec.key)) {
38733
+ const blank = new RegExp(`^([ ]*(?:export[ ]+)?${spec.key}=)[ ]*(?:""|'')?[ ]*\r?$`, "gm");
38734
+ if (blank.test(content)) {
38735
+ const value = spec.defaultValue();
38736
+ content = content.replace(blank, (_match, prefix) => `${prefix}${value}`);
38737
+ added.push(spec.key);
38738
+ }
38719
38739
  }
38720
38740
  }
38721
- if (appendLines.length === 0) {
38741
+ if (appendLines.length === 0 && content === existing) {
38722
38742
  return added;
38723
38743
  }
38724
- const needsTrailingNewline = existing.length > 0 && !existing.endsWith(`
38744
+ const needsTrailingNewline = content.length > 0 && !content.endsWith(`
38725
38745
  `);
38726
- const newContent = existing + (needsTrailingNewline ? `
38727
- ` : "") + appendLines.join(`
38746
+ const newContent = content + (needsTrailingNewline ? `
38747
+ ` : "") + (appendLines.length > 0 ? appendLines.join(`
38728
38748
  `) + `
38729
- `;
38730
- fs9.writeFileSync(envFile, newContent, { encoding: "utf8", mode: 384 });
38749
+ ` : "");
38750
+ writeEnvFile(envFile, newContent);
38731
38751
  return added;
38732
38752
  }
38753
+ function writeEnvFile(envFile, content) {
38754
+ let targetFile = envFile;
38755
+ try {
38756
+ if (fs9.existsSync(envFile))
38757
+ targetFile = fs9.realpathSync(envFile);
38758
+ } catch {}
38759
+ const tmpFile = `${targetFile}.tmp`;
38760
+ fs9.writeFileSync(tmpFile, content, { encoding: "utf8", mode: 384 });
38761
+ try {
38762
+ fs9.chmodSync(tmpFile, 384);
38763
+ if (fs9.existsSync(targetFile)) {
38764
+ const st = fs9.statSync(targetFile);
38765
+ fs9.chownSync(tmpFile, st.uid, st.gid);
38766
+ }
38767
+ } catch {}
38768
+ try {
38769
+ fs9.renameSync(tmpFile, targetFile);
38770
+ } catch (error) {
38771
+ try {
38772
+ fs9.unlinkSync(tmpFile);
38773
+ } catch {}
38774
+ throw error;
38775
+ }
38776
+ }
38733
38777
  var LOCAL_INSTALL_MANAGED_ENV_KEYS = [
38734
38778
  "PGAI_TAG",
38735
38779
  "PGAI_REGISTRY",
38736
38780
  "GF_SECURITY_ADMIN_PASSWORD",
38737
38781
  "REPLICATOR_PASSWORD",
38738
38782
  "VM_AUTH_USERNAME",
38739
- "VM_AUTH_PASSWORD"
38783
+ "VM_AUTH_PASSWORD",
38784
+ "VM_DELETE_AUTH_KEY",
38785
+ "VM_SNAPSHOT_AUTH_KEY",
38786
+ "VM_FORCE_MERGE_AUTH_KEY",
38787
+ "VM_PPROF_AUTH_KEY"
38740
38788
  ];
38741
38789
  function buildLocalInstallEnv(existingEnv, imageTag) {
38742
38790
  const readRaw = (key) => {
38743
- const m = existingEnv.match(new RegExp(`^${key}=(.+)$`, "m"));
38744
- return m ? m[1].trim() : null;
38791
+ const re = new RegExp(`^[ ]*(?:export[ ]+)?${key}=(.*)$`, "gm");
38792
+ let value = null;
38793
+ for (const m of existingEnv.matchAll(re)) {
38794
+ value = m[1].trim();
38795
+ }
38796
+ if (value === null)
38797
+ return null;
38798
+ return stripMatchingQuotes(value) === "" ? null : value;
38745
38799
  };
38746
38800
  const envLines = [`PGAI_TAG=${imageTag}`];
38747
38801
  const registry = readRaw("PGAI_REGISTRY");
@@ -38756,11 +38810,15 @@ function buildLocalInstallEnv(existingEnv, imageTag) {
38756
38810
  envLines.push(`VM_AUTH_USERNAME=${vmAuthUsername ? stripMatchingQuotes(vmAuthUsername) : "vmauth"}`);
38757
38811
  const vmAuthPassword = readRaw("VM_AUTH_PASSWORD");
38758
38812
  envLines.push(`VM_AUTH_PASSWORD=${vmAuthPassword ? stripMatchingQuotes(vmAuthPassword) : crypto2.randomBytes(18).toString("base64")}`);
38813
+ for (const key of VM_ADMIN_AUTH_KEYS) {
38814
+ const existing = readRaw(key);
38815
+ envLines.push(`${key}=${existing ? stripMatchingQuotes(existing) : crypto2.randomBytes(32).toString("hex")}`);
38816
+ }
38759
38817
  const preservedKeys = [];
38760
38818
  for (const line of existingEnv.split(/\r?\n/)) {
38761
38819
  if (!line.trim())
38762
38820
  continue;
38763
- const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)=/);
38821
+ const m = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=/);
38764
38822
  if (m) {
38765
38823
  if (LOCAL_INSTALL_MANAGED_ENV_KEYS.includes(m[1]))
38766
38824
  continue;
@@ -38772,9 +38830,9 @@ function buildLocalInstallEnv(existingEnv, imageTag) {
38772
38830
  `) + `
38773
38831
  `, preservedKeys };
38774
38832
  }
38775
- async function execFilePromise(file, args) {
38833
+ async function execFilePromise(file, args, options = {}) {
38776
38834
  return new Promise((resolve, reject) => {
38777
- childProcess.execFile(file, args, (error, stdout, stderr) => {
38835
+ childProcess.execFile(file, args, options, (error, stdout, stderr) => {
38778
38836
  if (error) {
38779
38837
  const err = error;
38780
38838
  err.code = typeof error.code === "number" ? error.code : 1;
@@ -40513,6 +40571,12 @@ function isDockerRunning() {
40513
40571
  return false;
40514
40572
  }
40515
40573
  }
40574
+ function shouldScaleOutNodeExporter(args) {
40575
+ const upIndex = args.indexOf("up");
40576
+ if (upIndex === -1)
40577
+ return false;
40578
+ return !args.slice(upIndex + 1).some((a) => !a.startsWith("-"));
40579
+ }
40516
40580
  function getComposeCmd() {
40517
40581
  const tryCmd = (cmd, args) => spawnSync2(cmd, args, { stdio: "ignore", timeout: 5000 }).status === 0;
40518
40582
  if (tryCmd("docker", ["compose", "version"]))
@@ -40725,7 +40789,7 @@ async function runCompose(args, grafanaPassword) {
40725
40789
  }
40726
40790
  }
40727
40791
  const finalArgs = [...args];
40728
- if (process.platform === "darwin" && args.includes("up")) {
40792
+ if (process.platform === "darwin" && shouldScaleOutNodeExporter(args)) {
40729
40793
  finalArgs.push("--scale", "self-node-exporter=0");
40730
40794
  }
40731
40795
  return new Promise((resolve) => {
@@ -40737,6 +40801,51 @@ async function runCompose(args, grafanaPassword) {
40737
40801
  child.on("close", (code) => resolve(code || 0));
40738
40802
  });
40739
40803
  }
40804
+ async function applySinkPrometheusConfig(projectDir) {
40805
+ const composeCmd = getComposeCmd();
40806
+ if (!composeCmd) {
40807
+ console.error(`
40808
+ \u2717 docker compose not found, so the sink-prometheus config was not applied.`);
40809
+ return false;
40810
+ }
40811
+ let running;
40812
+ try {
40813
+ const { stdout } = await execFilePromise(composeCmd[0], [
40814
+ ...composeCmd.slice(1),
40815
+ "-f",
40816
+ path7.resolve(projectDir, "docker-compose.yml"),
40817
+ "ps",
40818
+ "-q",
40819
+ "sink-prometheus"
40820
+ ], { cwd: projectDir });
40821
+ running = stdout.trim().length > 0;
40822
+ } catch (error) {
40823
+ console.error(`
40824
+ \u2717 Could not determine whether sink-prometheus is running.`);
40825
+ console.error(" The admin-endpoint keys may NOT be in effect. To apply them:");
40826
+ console.error(" docker compose up -d --no-deps sink-prometheus");
40827
+ if (process.env.DEBUG) {
40828
+ console.error(` ${error instanceof Error ? error.message : String(error)}`);
40829
+ }
40830
+ return false;
40831
+ }
40832
+ if (!running) {
40833
+ console.log(`
40834
+ (sink-prometheus is not running - the new config applies when you start the stack)`);
40835
+ return true;
40836
+ }
40837
+ console.log(`
40838
+ Applying sink-prometheus config (VictoriaMetrics admin keys, #359)...`);
40839
+ const code = await runCompose(["up", "-d", "--no-deps", "sink-prometheus"]);
40840
+ if (code !== 0) {
40841
+ console.error("\u2717 Could not apply the sink-prometheus config.");
40842
+ console.error(" The admin-endpoint keys are NOT in effect until you run:");
40843
+ console.error(" docker compose up -d --no-deps sink-prometheus");
40844
+ return false;
40845
+ }
40846
+ console.log("\u2713 sink-prometheus is running the current config");
40847
+ return true;
40848
+ }
40740
40849
  program2.command("help").description("show help").action(() => {
40741
40850
  program2.outputHelp();
40742
40851
  });
@@ -40775,7 +40884,7 @@ mon.command("local-install").description("install local monitoring stack (genera
40775
40884
  const previousTag = previousTagMatch ? previousTagMatch[1].trim() : null;
40776
40885
  const imageTag = opts.tag || package_default.version;
40777
40886
  const { content: envContent, preservedKeys } = buildLocalInstallEnv(existingEnv, imageTag);
40778
- fs9.writeFileSync(envFile, envContent, { encoding: "utf8", mode: 384 });
40887
+ writeEnvFile(envFile, envContent);
40779
40888
  if (preservedKeys.length > 0) {
40780
40889
  console.log(`Preserved existing .env settings: ${preservedKeys.join(", ")}
40781
40890
  `);
@@ -41071,9 +41180,9 @@ Searched: ${demoCandidates.join(", ")}
41071
41180
  const envLines = envContent.split(/\r?\n/).filter((l) => !/^VM_AUTH_USERNAME=/.test(l) && !/^VM_AUTH_PASSWORD=/.test(l)).filter((l, i, arr) => !(i === arr.length - 1 && l === ""));
41072
41181
  envLines.push(`VM_AUTH_USERNAME=${vmAuthUsername}`);
41073
41182
  envLines.push(`VM_AUTH_PASSWORD=${vmAuthPassword}`);
41074
- fs9.writeFileSync(envFile, envLines.join(`
41183
+ writeEnvFile(envFile, envLines.join(`
41075
41184
  `) + `
41076
- `, { encoding: "utf8", mode: 384 });
41185
+ `);
41077
41186
  }
41078
41187
  console.log(`\u2713 VictoriaMetrics auth configured
41079
41188
  `);
@@ -41374,8 +41483,13 @@ mon.command("update-config").description("apply monitoring services configuratio
41374
41483
  }
41375
41484
  await refreshBundledComposeIfStale(projectDir);
41376
41485
  const code = await runCompose(["run", "--rm", "sources-generator"]);
41377
- if (code !== 0)
41486
+ if (code !== 0) {
41378
41487
  process.exitCode = code;
41488
+ return;
41489
+ }
41490
+ if (!await applySinkPrometheusConfig(projectDir)) {
41491
+ process.exitCode = 1;
41492
+ }
41379
41493
  });
41380
41494
  mon.command("update").description("update monitoring stack (migrate .env, pull images)").action(async () => {
41381
41495
  console.log(`Updating PostgresAI monitoring stack...
@@ -41417,6 +41531,10 @@ mon.command("update").description("update monitoring stack (migrate .env, pull i
41417
41531
  Updating Docker images...`);
41418
41532
  const code = await runCompose(["pull"]);
41419
41533
  if (code === 0) {
41534
+ if (!await applySinkPrometheusConfig(projectDir)) {
41535
+ process.exitCode = 1;
41536
+ return;
41537
+ }
41420
41538
  console.log(`
41421
41539
  \u2713 Update completed successfully`);
41422
41540
  console.log(`
@@ -43402,5 +43520,6 @@ export {
43402
43520
  refreshBundledComposeIfStale,
43403
43521
  registerMonitoringInstance,
43404
43522
  resolveAdoptedProject,
43523
+ shouldScaleOutNodeExporter,
43405
43524
  updatePgwatchConfig
43406
43525
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresai",
3
- "version": "0.17.0-dev.2",
3
+ "version": "0.17.0-rc.2",
4
4
  "description": "postgres_ai CLI",
5
5
  "license": "Apache-2.0",
6
6
  "private": false,