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 +123 -0
- package/package.json +1 -1
- package/src/cli-install.cjs +33 -9
- package/tests/cli-install.test.js +22 -1
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
package/src/cli-install.cjs
CHANGED
|
@@ -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
|
|
51
|
-
* failures
|
|
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`
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)', () => {
|