pgserve 2.1.0 → 2.1.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/install.sh ADDED
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================================
3
+ # pgserve — Canonical PostgreSQL backbone installer
4
+ #
5
+ # Bootstraps a single shared pgserve instance under pm2 supervision. Used as
6
+ # a prerequisite by `omni/install.sh` and `genie/install.sh` so every
7
+ # automagik service on a host points at the same Postgres.
8
+ #
9
+ # Usage:
10
+ # curl -fsSL https://raw.githubusercontent.com/namastexlabs/pgserve/main/install.sh | bash
11
+ #
12
+ # With pinned version:
13
+ # PGSERVE_VERSION=^2.1.1 curl -fsSL .../install.sh | bash
14
+ #
15
+ # Local checkout:
16
+ # bash install.sh
17
+ #
18
+ # Idempotent — re-running is a no-op success when pgserve is already
19
+ # registered under pm2 with a healthy entry.
20
+ # ============================================================================
21
+ set -euo pipefail
22
+
23
+ PGSERVE_VERSION="${PGSERVE_VERSION:-^2.1.0}"
24
+
25
+ # Colors (no-op when stdout isn't a tty)
26
+ if [[ -t 1 ]]; then
27
+ RED='\033[0;31m'
28
+ GREEN='\033[0;32m'
29
+ YELLOW='\033[1;33m'
30
+ BLUE='\033[0;34m'
31
+ CYAN='\033[0;36m'
32
+ BOLD='\033[1m'
33
+ NC='\033[0m'
34
+ else
35
+ RED='' GREEN='' YELLOW='' BLUE='' CYAN='' BOLD='' NC=''
36
+ fi
37
+
38
+ info() { printf "${BLUE}ℹ${NC} %s\n" "$*"; }
39
+ ok() { printf "${GREEN}✓${NC} %s\n" "$*"; }
40
+ warn() { printf "${YELLOW}⚠${NC} %s\n" "$*"; }
41
+ fail() { printf "${RED}✗${NC} %s\n" "$*" >&2; exit 1; }
42
+ step() { printf "\n${BOLD}${CYAN}▸ %s${NC}\n" "$*"; }
43
+
44
+ has_cmd() { command -v "$1" >/dev/null 2>&1; }
45
+
46
+ # ============================================================================
47
+ # Prerequisites: bun + pm2
48
+ # ============================================================================
49
+
50
+ ensure_bun() {
51
+ if has_cmd bun; then
52
+ ok "bun $(bun --version 2>/dev/null || echo '?')"
53
+ return 0
54
+ fi
55
+ info "Installing bun (https://bun.sh)..."
56
+ curl -fsSL https://bun.sh/install | bash >/dev/null 2>&1 || fail "bun install failed — see https://bun.sh"
57
+ # Make bun available to the rest of this script without requiring a re-login.
58
+ export PATH="$HOME/.bun/bin:$PATH"
59
+ has_cmd bun || fail "bun installed but not on PATH — restart your shell and re-run."
60
+ ok "bun $(bun --version)"
61
+ }
62
+
63
+ ensure_pm2() {
64
+ if has_cmd pm2; then
65
+ ok "pm2 $(pm2 --version 2>/dev/null || echo '?')"
66
+ return 0
67
+ fi
68
+ info "Installing pm2 (process supervisor)..."
69
+ bun add -g pm2 >/dev/null 2>&1 || fail "pm2 install failed — try: bun add -g pm2"
70
+ has_cmd pm2 || fail "pm2 installed but not on PATH — restart your shell and re-run."
71
+ ok "pm2 installed"
72
+ }
73
+
74
+ # ============================================================================
75
+ # pgserve binary + pm2 registration
76
+ # ============================================================================
77
+
78
+ ensure_pgserve_binary() {
79
+ # Probe via `pgserve port` (real subcommand). `pgserve --version` doesn't
80
+ # exist in 2.1.x — using it would false-negative and trigger a redundant
81
+ # reinstall every time install.sh runs.
82
+ if has_cmd pgserve && pgserve port >/dev/null 2>&1; then
83
+ ok "pgserve binary present (port $(pgserve port 2>/dev/null))"
84
+ return 0
85
+ fi
86
+ info "Installing pgserve@${PGSERVE_VERSION} globally..."
87
+ bun add -g "pgserve@${PGSERVE_VERSION}" >/dev/null 2>&1 \
88
+ || fail "pgserve install failed — try: bun add -g pgserve@${PGSERVE_VERSION}"
89
+ has_cmd pgserve || fail "pgserve installed but not on PATH — restart your shell and re-run."
90
+ ok "pgserve $(pgserve port 2>/dev/null || echo '?')"
91
+ }
92
+
93
+ register_pgserve_pm2() {
94
+ info "Registering pgserve under pm2 (idempotent)..."
95
+ # `pgserve install` prints its own success/already-installed line and exits
96
+ # 0 in both cases. We pipe stderr through so any pm2 errors surface to the
97
+ # operator (the pm2-6.x --min-uptime breakage we hit on 2026-04-30 was
98
+ # invisible because stderr was being captured).
99
+ pgserve install || fail "pgserve install failed — see ~/.pgserve/logs/pgserve-error.log"
100
+ }
101
+
102
+ # ============================================================================
103
+ # Main
104
+ # ============================================================================
105
+
106
+ main() {
107
+ step "Installing canonical pgserve"
108
+ ensure_bun
109
+ ensure_pm2
110
+ ensure_pgserve_binary
111
+ register_pgserve_pm2
112
+
113
+ echo ""
114
+ ok "Canonical pgserve ready"
115
+ info "URL: $(pgserve url 2>/dev/null || echo '<run: pgserve url>')"
116
+ info "Port: $(pgserve port 2>/dev/null || echo '?')"
117
+ info "Logs: ~/.pgserve/logs/"
118
+ echo ""
119
+ info "Other automagik services on this host (omni, genie, ...) will share this pgserve."
120
+ echo ""
121
+ }
122
+
123
+ main "$@"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgserve",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "Embedded PostgreSQL server with true concurrent connections - zero config, auto-provision databases",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -47,8 +47,13 @@ const DEFAULT_PORT = 8432;
47
47
  * Revised defaults:
48
48
  * - 4G memory ceiling — covers realistic load while still bounded so
49
49
  * a runaway query can't eat the host.
50
- * - 50 max restarts BUT only counted when min_uptime < 10s ("rapid"
51
- * failures). Healthy long-uptime crashes don't count against the cap.
50
+ * - 50 max restarts. Earlier drafts paired this with `--min-uptime` to
51
+ * only count rapid failures, but pm2 6.0 dropped `--min-uptime` from
52
+ * the CLI surface (it survives only inside ecosystem files now). We
53
+ * keep the budget generous enough that occasional long-uptime crashes
54
+ * don't burn through it; if you observe restart-budget exhaustion
55
+ * from non-rapid crashes, raise `maxRestarts` rather than reintroducing
56
+ * `--min-uptime` (which would break install on pm2 6.x).
52
57
  * - Exponential backoff on repeated failures (100ms → 60s) so we don't
53
58
  * hammer on persistent issues.
54
59
  * - 60s graceful shutdown window — Postgres needs time to flush WAL.
@@ -62,7 +67,6 @@ const DEFAULT_PORT = 8432;
62
67
  */
63
68
  const HARDENED_DEFAULTS = {
64
69
  maxRestarts: 50,
65
- minUptimeMs: 10_000,
66
70
  restartDelayMs: 4000,
67
71
  expBackoffRestartDelayMs: 100,
68
72
  // pm2 caps `--exp-backoff-restart-delay` ramp at the current backoff
@@ -149,11 +153,14 @@ function buildPm2StartArgs({ scriptPath, port, dataDir }) {
149
153
  'none',
150
154
  '--max-restarts',
151
155
  String(HARDENED_DEFAULTS.maxRestarts),
152
- // `--min-uptime` makes `--max-restarts` count only RAPID failures
153
- // (process crashed within N ms of starting). Healthy long-uptime
154
- // crashes don't burn the budget.
155
- '--min-uptime',
156
- String(HARDENED_DEFAULTS.minUptimeMs),
156
+ // NOTE: pm2 ≥ 6.0 dropped `--min-uptime` from the CLI surface passing
157
+ // it produces `error: unknown option --min-uptime` and aborts the
158
+ // install. The flag still works inside an ecosystem file, but per the
159
+ // canonical-pm2-supervision wish we keep `pgserve install` as a pure
160
+ // CLI flow (no extra files for operators to manage). The trade-off is
161
+ // that `--max-restarts` now counts every restart (rapid or not) rather
162
+ // than only sub-`min_uptime` ones; the budget of 50 above is sized
163
+ // accordingly.
157
164
  '--restart-delay',
158
165
  String(HARDENED_DEFAULTS.restartDelayMs),
159
166
  // Exponential backoff between successive failures: starts at 100ms,
@@ -172,7 +179,24 @@ function buildPm2StartArgs({ scriptPath, port, dataDir }) {
172
179
  '--error',
173
180
  logs.error,
174
181
  '--',
175
- 'daemon',
182
+ // Foreground multi-tenant mode (`pgserve [options]`), NOT daemon mode.
183
+ //
184
+ // Daemon mode binds a unix control socket and requires libpq peers to
185
+ // authenticate via a fingerprint+token handshake (`pgserve daemon
186
+ // issue-token`). Downstream services that connect with a plain
187
+ // `postgres://` URL (omni, genie, anything that doesn't speak the
188
+ // fingerprint protocol) cannot reach a daemon-mode listener. We also
189
+ // observed live: `pgserve install` was passing `--port` to the daemon
190
+ // parser, which only accepts `--data | --ram | --log | --no-provision
191
+ // | --listen | --pgvector` — every install attempt crashed with
192
+ // `Unknown daemon option: --port` and pm2 burned its restart budget.
193
+ //
194
+ // Foreground mode (the default `pgserve [options]` invocation in
195
+ // postgres-server.js) accepts `--port`, auto-provisions databases on
196
+ // first connect, runs the cluster on multi-core hosts, and binds TCP
197
+ // on `127.0.0.1:<port>` with no auth dance — exactly what canonical
198
+ // pgserve consumers expect. Pass the same flags pgserve already
199
+ // documents in its own `pgserve --help` output.
176
200
  '--port',
177
201
  String(port),
178
202
  '--data',
@@ -124,7 +124,12 @@ describe('pgserve install', () => {
124
124
  expect(startCall).toContain('pgserve');
125
125
  expect(startCall).toContain('--max-restarts');
126
126
  expect(startCall).toContain('50');
127
- expect(startCall).toContain('--min-uptime');
127
+ // pm2 ≥ 6.0 dropped `--min-uptime` from the CLI surface — it now lives
128
+ // only inside ecosystem files. Passing it on the command line aborts
129
+ // `pm2 start` with `error: unknown option --min-uptime`. Lock that out:
130
+ // `pgserve install` must NOT pass `--min-uptime` so it stays compatible
131
+ // across pm2 5.x → 6.x. See cli-install.cjs:HARDENED_DEFAULTS.
132
+ expect(startCall).not.toContain('--min-uptime');
128
133
  expect(startCall).toContain('--exp-backoff-restart-delay');
129
134
  expect(startCall).toContain('--max-memory-restart');
130
135
  expect(startCall).toContain('4G');
@@ -132,6 +137,22 @@ describe('pgserve install', () => {
132
137
  expect(startCall).toContain('60000');
133
138
  expect(startCall).toContain('--interpreter');
134
139
  expect(startCall).toContain('none');
140
+
141
+ // pgserve install must launch the foreground multi-tenant server, NOT
142
+ // the daemon. Daemon mode rejects `--port` (it only accepts --data,
143
+ // --ram, --log, --no-provision, --listen, --pgvector) and its TCP
144
+ // listeners require fingerprint+token auth which downstream services
145
+ // (omni, genie) don't speak. Foreground mode binds plain TCP on
146
+ // 127.0.0.1:<port> with auto-provisioning. Lock that out:
147
+ expect(startCall).not.toContain('daemon');
148
+ // The script-arg handover (after `--`) must include `--port` so the
149
+ // foreground parser binds the right TCP port.
150
+ const dashDashIdx = startCall.indexOf('--');
151
+ const scriptArgs = startCall.slice(dashDashIdx + 1);
152
+ expect(scriptArgs).toContain('--port');
153
+ expect(scriptArgs).toContain('--data');
154
+ expect(scriptArgs).toContain('--log');
155
+ expect(scriptArgs).not.toContain('daemon');
135
156
  });
136
157
 
137
158
  test('second install is idempotent (no second pm2 start)', () => {