From 88bf1b23a3a0f0c14ee68c0a33f657b71035f6bc Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 16 Mar 2026 21:03:05 -0500 Subject: [PATCH] Harden embedded postgres adoption on startup --- packages/db/src/client.ts | 15 +++++++++++++++ packages/db/src/index.ts | 1 + packages/db/src/migration-runtime.ts | 27 +++++---------------------- server/src/index.ts | 8 ++++++++ 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/packages/db/src/client.ts b/packages/db/src/client.ts index c5cbf547..b8cadbe3 100644 --- a/packages/db/src/client.ts +++ b/packages/db/src/client.ts @@ -50,6 +50,21 @@ export function createDb(url: string) { return drizzlePg(sql, { schema }); } +export async function getPostgresDataDirectory(url: string): Promise { + const sql = createUtilitySql(url); + try { + const rows = await sql<{ data_directory: string | null }[]>` + SELECT current_setting('data_directory', true) AS data_directory + `; + const actual = rows[0]?.data_directory; + return typeof actual === "string" && actual.length > 0 ? actual : null; + } catch { + return null; + } finally { + await sql.end(); + } +} + async function listMigrationFiles(): Promise { const entries = await readdir(MIGRATIONS_FOLDER, { withFileTypes: true }); return entries diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index f280cee1..5c32ab13 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -1,5 +1,6 @@ export { createDb, + getPostgresDataDirectory, ensurePostgresDatabase, inspectMigrations, applyPendingMigrations, diff --git a/packages/db/src/migration-runtime.ts b/packages/db/src/migration-runtime.ts index abbafaea..3b5921b1 100644 --- a/packages/db/src/migration-runtime.ts +++ b/packages/db/src/migration-runtime.ts @@ -1,8 +1,7 @@ import { existsSync, readFileSync, rmSync } from "node:fs"; import { createServer } from "node:net"; import path from "node:path"; -import postgres from "postgres"; -import { ensurePostgresDatabase } from "./client.js"; +import { ensurePostgresDatabase, getPostgresDataDirectory } from "./client.js"; import { resolveDatabaseTarget } from "./runtime-config.js"; type EmbeddedPostgresInstance = { @@ -99,25 +98,6 @@ async function loadEmbeddedPostgresCtor(): Promise { } } -async function matchesEmbeddedDataDir( - adminConnectionString: string, - expectedDataDir: string, -): Promise { - const sql = postgres(adminConnectionString, { max: 1, onnotice: () => {} }); - try { - const rows = await sql<{ data_directory: string | null }[]>` - SELECT current_setting('data_directory', true) AS data_directory - `; - const actual = rows[0]?.data_directory; - if (typeof actual !== "string" || actual.length === 0) return false; - return path.resolve(actual) === path.resolve(expectedDataDir); - } catch { - return false; - } finally { - await sql.end(); - } -} - async function ensureEmbeddedPostgresConnection( dataDir: string, preferredPort: number, @@ -132,7 +112,10 @@ async function ensureEmbeddedPostgresConnection( if (!runningPid && existsSync(pgVersionFile)) { try { - const matchesDataDir = await matchesEmbeddedDataDir(preferredAdminConnectionString, dataDir); + const actualDataDir = await getPostgresDataDirectory(preferredAdminConnectionString); + const matchesDataDir = + typeof actualDataDir === "string" && + path.resolve(actualDataDir) === path.resolve(dataDir); if (!matchesDataDir) { throw new Error("reachable postgres does not use the expected embedded data directory"); } diff --git a/server/src/index.ts b/server/src/index.ts index 7751a450..744ee10b 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -10,6 +10,7 @@ import { and, eq } from "drizzle-orm"; import { createDb, ensurePostgresDatabase, + getPostgresDataDirectory, inspectMigrations, applyPendingMigrations, reconcilePendingMigrationHistory, @@ -322,6 +323,13 @@ export async function startServer(): Promise { } else { const configuredAdminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${configuredPort}/postgres`; try { + const actualDataDir = await getPostgresDataDirectory(configuredAdminConnectionString); + if ( + typeof actualDataDir !== "string" || + resolve(actualDataDir) !== resolve(dataDir) + ) { + throw new Error("reachable postgres does not use the expected embedded data directory"); + } await ensurePostgresDatabase(configuredAdminConnectionString, "paperclip"); logger.warn( `Embedded PostgreSQL appears to already be reachable without a pid file; reusing existing server on configured port ${configuredPort}`,