Fix embedded Postgres native library bootstrap

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-05-21 20:29:52 +00:00
1 parent 9d6fbff9b4
commit 1edb8591e4
8 files changed
+142

No files matched your search

+2
View File
@@ -9,6 +9,7 @@ import {
createEmbeddedPostgresLogBuffer,
ensurePostgresDatabase,
formatEmbeddedPostgresError,
prepareEmbeddedPostgresNativeRuntime,
routines,
} from "@paperclipai/db";
import { eq, inArray } from "drizzle-orm";
@@ -116,6 +117,7 @@ async function ensureEmbeddedPostgres(dataDir: string, preferredPort: number): P
"Embedded PostgreSQL support requires dependency `embedded-postgres`. Reinstall dependencies and try again.",
);
}
await prepareEmbeddedPostgresNativeRuntime();
const postmasterPidFile = path.resolve(dataDir, "postmaster.pid");
const runningPid = readRunningPostmasterPid(postmasterPidFile);
+2
View File
@@ -45,6 +45,7 @@ import {
runDatabaseRestore,
createEmbeddedPostgresLogBuffer,
formatEmbeddedPostgresError,
prepareEmbeddedPostgresNativeRuntime,
} from "@paperclipai/db";
import type { Command } from "commander";
import { ensureAgentJwtSecret, loadPaperclipEnvFile, mergePaperclipEnvEntries, readPaperclipEnvEntries, resolvePaperclipEnvFile } from "../config/env.js";
@@ -1059,6 +1060,7 @@ async function ensureEmbeddedPostgres(dataDir: string, preferredPort: number): P
"Embedded PostgreSQL support requires dependency `embedded-postgres`. Reinstall dependencies and try again.",
);
}
await prepareEmbeddedPostgresNativeRuntime();
const postmasterPidFile = path.resolve(dataDir, "postmaster.pid");
const runningPid = readRunningPostmasterPid(postmasterPidFile);
@@ -0,0 +1,43 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { ensureLinuxSharedLibraryAliases } from "./embedded-postgres-native.js";
describe("embedded Postgres native runtime", () => {
const tempDirs: string[] = [];
afterEach(() => {
for (const tempDir of tempDirs.splice(0)) {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});
it.runIf(process.platform !== "win32")("creates soname aliases for bundled patch-level shared libraries", async () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-embedded-pg-libs-"));
tempDirs.push(tempDir);
fs.writeFileSync(path.join(tempDir, "libicuuc.so.60.2"), "");
fs.writeFileSync(path.join(tempDir, "libicui18n.so.60.2"), "");
fs.writeFileSync(path.join(tempDir, "README.md"), "");
const created = await ensureLinuxSharedLibraryAliases(tempDir);
expect(created.map((file) => path.basename(file)).sort()).toEqual([
"libicui18n.so.60",
"libicuuc.so.60",
]);
expect(fs.readlinkSync(path.join(tempDir, "libicuuc.so.60"))).toBe("libicuuc.so.60.2");
});
it.runIf(process.platform !== "win32")("is idempotent when aliases already exist", async () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-embedded-pg-libs-"));
tempDirs.push(tempDir);
fs.writeFileSync(path.join(tempDir, "libicuuc.so.60.2"), "");
await ensureLinuxSharedLibraryAliases(tempDir);
const second = await ensureLinuxSharedLibraryAliases(tempDir);
expect(second).toEqual([]);
expect(fs.readlinkSync(path.join(tempDir, "libicuuc.so.60"))).toBe("libicuuc.so.60.2");
});
});
@@ -0,0 +1,85 @@
import { promises as fs } from "node:fs";
import { createRequire } from "node:module";
import path from "node:path";
const require = createRequire(import.meta.url);
function resolveNativePackageName(): string | null {
if (process.platform !== "linux") return null;
switch (process.arch) {
case "arm64":
return "linux-arm64";
case "arm":
return "linux-arm";
case "ia32":
return "linux-ia32";
case "ppc64":
return "linux-ppc64";
case "x64":
return "linux-x64";
default:
return null;
}
}
async function pathExists(value: string): Promise<boolean> {
try {
await fs.stat(value);
return true;
} catch {
return false;
}
}
function resolveEmbeddedPostgresPackageRoot(): string | null {
try {
const entry = require.resolve("embedded-postgres");
return path.dirname(path.dirname(entry));
} catch {
return null;
}
}
function prependPathEnv(name: string, value: string): void {
const current = process.env[name] ?? "";
const parts = current.split(path.delimiter).filter(Boolean);
if (parts.includes(value)) return;
process.env[name] = [value, ...parts].join(path.delimiter);
}
export async function ensureLinuxSharedLibraryAliases(libDir: string): Promise<string[]> {
const entries = await fs.readdir(libDir, { withFileTypes: true });
const created: string[] = [];
for (const entry of entries) {
if (!entry.isFile()) continue;
const match = entry.name.match(/^(lib.+\.so\.\d+)\.\d+(?:\.\d+)?$/);
if (!match) continue;
const aliasName = match[1];
const aliasPath = path.join(libDir, aliasName);
try {
await fs.symlink(entry.name, aliasPath);
created.push(aliasPath);
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "EEXIST") continue;
throw error;
}
}
return created;
}
export async function prepareEmbeddedPostgresNativeRuntime(): Promise<void> {
const nativePackageName = resolveNativePackageName();
const packageRoot = resolveEmbeddedPostgresPackageRoot();
if (!nativePackageName || !packageRoot) return;
const nativeRoot = path.resolve(packageRoot, "..", "@embedded-postgres", nativePackageName);
const libDir = path.join(nativeRoot, "native", "lib");
if (!(await pathExists(libDir))) return;
prependPathEnv("LD_LIBRARY_PATH", libDir);
await ensureLinuxSharedLibraryAliases(libDir);
}
+4
View File
@@ -30,6 +30,10 @@ export {
createEmbeddedPostgresLogBuffer,
formatEmbeddedPostgresError,
} from "./embedded-postgres-error.js";
export {
ensureLinuxSharedLibraryAliases,
prepareEmbeddedPostgresNativeRuntime,
} from "./embedded-postgres-native.js";
export { issueRelations } from "./schema/issue_relations.js";
export { issueReferenceMentions } from "./schema/issue_reference_mentions.js";
export * from "./schema/index.js";
+2
View File
@@ -3,6 +3,7 @@ import { createServer } from "node:net";
import path from "node:path";
import { ensurePostgresDatabase, getPostgresDataDirectory } from "./client.js";
import { createEmbeddedPostgresLogBuffer, formatEmbeddedPostgresError } from "./embedded-postgres-error.js";
import { prepareEmbeddedPostgresNativeRuntime } from "./embedded-postgres-native.js";
import { resolveDatabaseTarget } from "./runtime-config.js";
type EmbeddedPostgresInstance = {
@@ -92,6 +93,7 @@ async function ensureEmbeddedPostgresConnection(
preferredPort: number,
): Promise<MigrationConnection> {
const EmbeddedPostgres = await loadEmbeddedPostgresCtor();
await prepareEmbeddedPostgresNativeRuntime();
const selectedPort = await findAvailablePort(preferredPort);
const postmasterPidFile = path.resolve(dataDir, "postmaster.pid");
const pgVersionFile = path.resolve(dataDir, "PG_VERSION");
@@ -3,6 +3,7 @@ import net from "node:net";
import os from "node:os";
import path from "node:path";
import { applyPendingMigrations, ensurePostgresDatabase } from "./client.js";
import { prepareEmbeddedPostgresNativeRuntime } from "./embedded-postgres-native.js";
type EmbeddedPostgresInstance = {
initialise(): Promise<void>;
@@ -48,6 +49,7 @@ function getReservedTestPorts(): Set<number> {
async function getEmbeddedPostgresCtor(): Promise<EmbeddedPostgresCtor> {
const mod = await import("embedded-postgres");
await prepareEmbeddedPostgresNativeRuntime();
return mod.default as EmbeddedPostgresCtor;
}
+2
View File
@@ -15,6 +15,7 @@ import {
inspectMigrations,
applyPendingMigrations,
createEmbeddedPostgresLogBuffer,
prepareEmbeddedPostgresNativeRuntime,
reconcilePendingMigrationHistory,
formatDatabaseBackupResult,
runDatabaseBackup,
@@ -316,6 +317,7 @@ export async function startServer(): Promise<StartedServer> {
"Embedded PostgreSQL mode requires dependency `embedded-postgres`. Reinstall dependencies (without omitting required packages), or set DATABASE_URL for external Postgres.",
);
}
await prepareEmbeddedPostgresNativeRuntime();
const dataDir = resolve(config.embeddedPostgresDataDir);
const configuredPort = config.embeddedPostgresPort;