pgserve 2.0.1 → 2.0.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/package.json +1 -1
- package/src/daemon.js +1 -1
- package/src/fingerprint.js +30 -4
- package/src/postgres.js +34 -9
- package/tests/fingerprint.test.js +14 -0
package/package.json
CHANGED
package/src/daemon.js
CHANGED
|
@@ -251,7 +251,7 @@ export class PgserveDaemon extends EventEmitter {
|
|
|
251
251
|
|
|
252
252
|
this.pgManager = options.pgManager || new PostgresManager({
|
|
253
253
|
dataDir: this.baseDir,
|
|
254
|
-
port: options.pgPort
|
|
254
|
+
port: options.pgPort ?? 0,
|
|
255
255
|
logger: this.logger.child ? this.logger.child({ component: 'postgres' }) : this.logger,
|
|
256
256
|
useRam: this.useRam,
|
|
257
257
|
enablePgvector: options.enablePgvector || false,
|
package/src/fingerprint.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* 1. SO_PEERCRED (Linux) / getpeereid + LOCAL_PEERPID (macOS)
|
|
8
8
|
* → kernel-attested {pid, uid, gid}
|
|
9
|
-
* 2. /proc/$pid/cwd
|
|
9
|
+
* 2. peer cwd lookup → /proc/$pid/cwd on Linux, lsof on macOS
|
|
10
10
|
* 3. walk upward to the nearest package.json
|
|
11
11
|
* 4. if found: fingerprint = sha256(realpath \0 name \0 uid)[:12] mode='package'
|
|
12
12
|
* else: fingerprint = sha256(uid \0 cwd \0 cmdline[1])[:12] mode='script'
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
import crypto from 'crypto';
|
|
32
|
+
import { execFileSync } from 'child_process';
|
|
32
33
|
import fs from 'fs';
|
|
33
34
|
import path from 'path';
|
|
34
35
|
import { audit, AUDIT_EVENTS } from './audit.js';
|
|
@@ -175,17 +176,21 @@ function makeDarwinReader(symbols, ptr) {
|
|
|
175
176
|
}
|
|
176
177
|
|
|
177
178
|
// ---------------------------------------------------------------------------
|
|
178
|
-
//
|
|
179
|
+
// Peer process metadata reads
|
|
179
180
|
// ---------------------------------------------------------------------------
|
|
180
181
|
|
|
181
182
|
/**
|
|
182
|
-
* Resolve the cwd of a peer process
|
|
183
|
-
*
|
|
183
|
+
* Resolve the cwd of a peer process. Linux uses /proc/$pid/cwd; macOS has no
|
|
184
|
+
* /proc, so it shells out to the platform lsof binary and parses the cwd row.
|
|
185
|
+
* Returns null if the process disappeared, permissions deny the lookup, or the
|
|
186
|
+
* host does not expose a cwd for the peer.
|
|
184
187
|
*
|
|
185
188
|
* @param {number} pid
|
|
186
189
|
* @returns {string | null}
|
|
187
190
|
*/
|
|
188
191
|
export function readProcCwd(pid) {
|
|
192
|
+
if (!Number.isInteger(pid) || pid <= 0) return null;
|
|
193
|
+
if (process.platform === 'darwin') return readDarwinCwd(pid);
|
|
189
194
|
if (process.platform !== 'linux') return null;
|
|
190
195
|
try {
|
|
191
196
|
return fs.readlinkSync(`/proc/${pid}/cwd`);
|
|
@@ -194,6 +199,27 @@ export function readProcCwd(pid) {
|
|
|
194
199
|
}
|
|
195
200
|
}
|
|
196
201
|
|
|
202
|
+
function readDarwinCwd(pid) {
|
|
203
|
+
const lsof = process.env.PGSERVE_LSOF_BIN || '/usr/sbin/lsof';
|
|
204
|
+
try {
|
|
205
|
+
const output = execFileSync(lsof, ['-a', '-p', String(pid), '-d', 'cwd', '-Fn'], {
|
|
206
|
+
encoding: 'utf8',
|
|
207
|
+
timeout: 1000,
|
|
208
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
209
|
+
});
|
|
210
|
+
return parseDarwinLsofCwd(output);
|
|
211
|
+
} catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function parseDarwinLsofCwd(output) {
|
|
217
|
+
for (const line of String(output || '').split(/\r?\n/)) {
|
|
218
|
+
if (line.startsWith('n') && line.length > 1) return line.slice(1);
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
197
223
|
/**
|
|
198
224
|
* Read the peer's argv via /proc/$pid/cmdline (NUL-separated).
|
|
199
225
|
* argv[0] is the exe; argv[1] is typically the script.
|
package/src/postgres.js
CHANGED
|
@@ -406,10 +406,23 @@ export function pgvectorMetaMatches(meta, runtime) {
|
|
|
406
406
|
return true;
|
|
407
407
|
}
|
|
408
408
|
|
|
409
|
+
function findAvailableTcpPort() {
|
|
410
|
+
const server = Bun.listen({
|
|
411
|
+
hostname: '127.0.0.1',
|
|
412
|
+
port: 0,
|
|
413
|
+
socket: {
|
|
414
|
+
data() {},
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
const port = server.port;
|
|
418
|
+
server.stop(true);
|
|
419
|
+
return port;
|
|
420
|
+
}
|
|
421
|
+
|
|
409
422
|
export class PostgresManager {
|
|
410
423
|
constructor(options = {}) {
|
|
411
424
|
this.dataDir = options.dataDir || null; // null = memory mode (temp dir)
|
|
412
|
-
this.port = options.port
|
|
425
|
+
this.port = options.port ?? 5433; // Internal PG port (router listens on different port)
|
|
413
426
|
this.user = options.user || 'postgres';
|
|
414
427
|
this.password = options.password || 'postgres';
|
|
415
428
|
this.logger = options.logger;
|
|
@@ -465,6 +478,10 @@ export class PostgresManager {
|
|
|
465
478
|
await fs.promises.chmod(this.binaries.initdb, '755');
|
|
466
479
|
await fs.promises.chmod(this.binaries.postgres, '755');
|
|
467
480
|
|
|
481
|
+
if (this.port === 0) {
|
|
482
|
+
this.port = findAvailableTcpPort();
|
|
483
|
+
}
|
|
484
|
+
|
|
468
485
|
// Determine data directory
|
|
469
486
|
if (this.persistent) {
|
|
470
487
|
this.databaseDir = this.dataDir;
|
|
@@ -568,7 +585,8 @@ export class PostgresManager {
|
|
|
568
585
|
const initdbCmd = [
|
|
569
586
|
this.binaries.initdb,
|
|
570
587
|
`--pgdata=${this.databaseDir}`,
|
|
571
|
-
'--auth=
|
|
588
|
+
'--auth-local=trust',
|
|
589
|
+
'--auth-host=password',
|
|
572
590
|
`--username=${this.user}`,
|
|
573
591
|
`--pwfile=${passwordFile}`,
|
|
574
592
|
];
|
|
@@ -744,11 +762,13 @@ export class PostgresManager {
|
|
|
744
762
|
// Whichever succeeds first wins
|
|
745
763
|
|
|
746
764
|
const markReady = (method) => {
|
|
747
|
-
if (
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
}
|
|
765
|
+
if (started || processExited) return true;
|
|
766
|
+
const socketPath = this.getSocketPath();
|
|
767
|
+
if (socketPath && !fs.existsSync(socketPath)) return false;
|
|
768
|
+
started = true;
|
|
769
|
+
this.logger.info({ port: this.port, method }, 'PostgreSQL ready');
|
|
770
|
+
resolve();
|
|
771
|
+
return true;
|
|
752
772
|
};
|
|
753
773
|
|
|
754
774
|
// Read stderr - detect port binding in logs (locale-independent: just look for port number)
|
|
@@ -873,14 +893,19 @@ export class PostgresManager {
|
|
|
873
893
|
if (processExited) return;
|
|
874
894
|
|
|
875
895
|
try {
|
|
896
|
+
const socketPath = this.getSocketPath();
|
|
897
|
+
if (socketPath && fs.existsSync(socketPath)) {
|
|
898
|
+
markReady('unix-socket');
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
876
901
|
await tryConnect();
|
|
877
902
|
// On Windows, TCP port opens before PostgreSQL is fully ready for protocol handshakes
|
|
878
903
|
// Add delay to let PostgreSQL complete its startup sequence
|
|
879
904
|
if (isWindows) {
|
|
880
905
|
await Bun.sleep(2000); // 2 second delay for Windows
|
|
881
906
|
}
|
|
882
|
-
|
|
883
|
-
return;
|
|
907
|
+
if (processExited) return;
|
|
908
|
+
if (markReady('tcp')) return;
|
|
884
909
|
} catch {
|
|
885
910
|
await Bun.sleep(200);
|
|
886
911
|
}
|
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
initFingerprintFfi,
|
|
21
21
|
getPeerCred,
|
|
22
22
|
findNearestPackageJson,
|
|
23
|
+
parseDarwinLsofCwd,
|
|
24
|
+
readProcCwd,
|
|
23
25
|
readPackageName,
|
|
24
26
|
derivePackageFingerprint,
|
|
25
27
|
deriveScriptFingerprint,
|
|
@@ -80,6 +82,18 @@ test('getPeerCred reads kernel-attested pid/uid/gid via Unix socket pair', async
|
|
|
80
82
|
expect(cred.gid).toBe(expectedGid);
|
|
81
83
|
});
|
|
82
84
|
|
|
85
|
+
test('macOS lsof parser extracts cwd field output', () => {
|
|
86
|
+
const cwd = path.join(scratch, 'project');
|
|
87
|
+
const output = `p12345\nn${cwd}\n`;
|
|
88
|
+
expect(parseDarwinLsofCwd(output)).toBe(cwd);
|
|
89
|
+
expect(parseDarwinLsofCwd('p12345\n')).toBeNull();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('readProcCwd resolves the current process cwd on supported platforms', () => {
|
|
93
|
+
if (process.platform !== 'linux' && process.platform !== 'darwin') return;
|
|
94
|
+
expect(readProcCwd(process.pid)).toBe(process.cwd());
|
|
95
|
+
});
|
|
96
|
+
|
|
83
97
|
// ---------------------------------------------------------------------------
|
|
84
98
|
// Pure-function tests on derivation surface
|
|
85
99
|
// ---------------------------------------------------------------------------
|