u
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM debian:12-slim AS syslibs
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libatomic1 libuuid1 libfontconfig1 libxml2 libfribidi0 \
|
||||
libatomic1 libuuid1 libfontconfig1 libfribidi0 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
FROM gcr.io/distroless/cc-debian12
|
||||
@@ -12,7 +12,6 @@ WORKDIR /app
|
||||
COPY --from=syslibs /usr/lib/x86_64-linux-gnu/libatomic.so* /usr/lib/x86_64-linux-gnu/
|
||||
COPY --from=syslibs /usr/lib/x86_64-linux-gnu/libuuid.so* /usr/lib/x86_64-linux-gnu/
|
||||
COPY --from=syslibs /usr/lib/x86_64-linux-gnu/libfontconfig.so* /usr/lib/x86_64-linux-gnu/
|
||||
COPY --from=syslibs /usr/lib/x86_64-linux-gnu/libxml2.so* /usr/lib/x86_64-linux-gnu/
|
||||
COPY --from=syslibs /usr/lib/x86_64-linux-gnu/libfribidi.so* /usr/lib/x86_64-linux-gnu/
|
||||
|
||||
# Set production defaults: port 4208
|
||||
|
||||
@@ -18,7 +18,55 @@ if (!fs.existsSync(distDir)) {
|
||||
fs.mkdirSync(distDir, { recursive: true });
|
||||
}
|
||||
|
||||
console.log('Packaging application, siblings, and dependencies into SEA payload...');
|
||||
console.log('Packaging application, siblings, and JavaScript dependencies into SEA payload (excluding native binaries)...');
|
||||
|
||||
const nativeNodes = {};
|
||||
|
||||
function findNearestPackageJson(startDir) {
|
||||
let dir = startDir;
|
||||
while (dir && dir !== path.dirname(dir)) {
|
||||
const pkgFile = path.join(dir, 'package.json');
|
||||
if (fs.existsSync(pkgFile)) {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(pkgFile, 'utf8'));
|
||||
} catch {}
|
||||
}
|
||||
dir = path.dirname(dir);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getVersionedNodeFilename(fullPath) {
|
||||
const base = path.basename(fullPath, '.node');
|
||||
const pkg = findNearestPackageJson(path.dirname(fullPath));
|
||||
if (!pkg || !pkg.name || !pkg.version) {
|
||||
return `${base}.node`;
|
||||
}
|
||||
const shortName = pkg.name.replace(/^@[^/]+\//, '');
|
||||
if (base.includes(pkg.version)) {
|
||||
return `${base}.node`;
|
||||
}
|
||||
if (base === shortName) {
|
||||
return `${shortName}-${process.platform}-${process.arch}-${pkg.version}.node`;
|
||||
}
|
||||
return `${shortName}-${base}-${pkg.version}.node`;
|
||||
}
|
||||
|
||||
function isNativeBinary(name) {
|
||||
return (
|
||||
name.endsWith('.node') ||
|
||||
name.endsWith('.so') ||
|
||||
name.includes('.so.') ||
|
||||
name.endsWith('.dylib') ||
|
||||
name.endsWith('.dll') ||
|
||||
name.endsWith('.a') ||
|
||||
name.endsWith('.o') ||
|
||||
name.endsWith('.cc') ||
|
||||
name.endsWith('.cpp') ||
|
||||
name.endsWith('.c') ||
|
||||
name.endsWith('.h')
|
||||
);
|
||||
}
|
||||
|
||||
function packDirectory(dir, prefix, result) {
|
||||
if (!fs.existsSync(dir)) return;
|
||||
@@ -30,7 +78,11 @@ function packDirectory(dir, prefix, result) {
|
||||
if (entry.name === 'test' || entry.name === 'tests' || entry.name === 'docs' || entry.name === '.github' || entry.name === '.git' || entry.name === 'obj.target') continue;
|
||||
packDirectory(full, rel, result);
|
||||
} else if (entry.isFile()) {
|
||||
if (entry.name.endsWith('.a') || entry.name.endsWith('.o') || entry.name.endsWith('.cc') || entry.name.endsWith('.cpp') || entry.name.endsWith('.c') || entry.name.endsWith('.h')) continue;
|
||||
if (entry.name.endsWith('.node')) {
|
||||
nativeNodes[rel] = getVersionedNodeFilename(full);
|
||||
continue;
|
||||
}
|
||||
if (isNativeBinary(entry.name)) continue;
|
||||
result[rel] = fs.readFileSync(full).toString('base64');
|
||||
}
|
||||
}
|
||||
@@ -82,6 +134,8 @@ function packNodeModules(nmPath) {
|
||||
packNodeModules(path.join(rootDir, 'node_modules'));
|
||||
packNodeModules(path.join(dbschemaDir, 'node_modules'));
|
||||
|
||||
payload['.native-nodes.json'] = Buffer.from(JSON.stringify(nativeNodes)).toString('base64');
|
||||
|
||||
const compressedPayload = zlib.deflateSync(Buffer.from(JSON.stringify(payload)));
|
||||
fs.writeFileSync(payloadPath, compressedPayload);
|
||||
|
||||
|
||||
@@ -316,13 +316,9 @@ export async function runSelfTest() {
|
||||
apiLog.info(`Config: host=${env.host} port=${env.port} dbProvider=${env.dbProvider}`);
|
||||
|
||||
// 2. Test database & store initialization
|
||||
try {
|
||||
await ensureConnection();
|
||||
const schemaVer = await getSchemaVersion();
|
||||
apiLog.info(`Database & store: OK (provider: ${env.dbProvider}, schema v${schemaVer})`);
|
||||
} catch (err) {
|
||||
apiLog.warn(`Database connection warning: ${err.message}`);
|
||||
}
|
||||
await ensureConnection();
|
||||
const schemaVer = await getSchemaVersion();
|
||||
apiLog.info(`Database & store: OK (provider: ${env.dbProvider}, schema v${schemaVer})`);
|
||||
|
||||
// 3. Build API app and register all route modules
|
||||
const app = await buildApiApp();
|
||||
|
||||
@@ -15,23 +15,65 @@ const { createRequire } = require('node:module');
|
||||
function getProjectRoot() {
|
||||
// If embedded application payload is present in SEA
|
||||
if (sea.isSea && sea.isSea() && sea.getAssetKeys && sea.getAssetKeys().includes('app-payload.bin')) {
|
||||
const distDir = path.dirname(process.execPath);
|
||||
const runtimeDir = process.env.DASHBACK_RUNTIME_DIR || path.join(os.tmpdir(), 'dashback-runtime');
|
||||
const markerFile = path.join(runtimeDir, '.unpacked');
|
||||
|
||||
const nativeMapFile = path.join(runtimeDir, '.native-nodes.json');
|
||||
|
||||
if (!fs.existsSync(markerFile)) {
|
||||
const compressed = Buffer.from(sea.getRawAsset('app-payload.bin'));
|
||||
const json = zlib.inflateSync(compressed).toString('utf8');
|
||||
const files = JSON.parse(json);
|
||||
const nativeNodes = files['.native-nodes.json']
|
||||
? JSON.parse(Buffer.from(files['.native-nodes.json'], 'base64').toString('utf8'))
|
||||
: {};
|
||||
|
||||
for (const [relPath, b64] of Object.entries(files)) {
|
||||
if (relPath === '.native-nodes.json') continue;
|
||||
const targetFile = path.join(runtimeDir, relPath);
|
||||
fs.mkdirSync(path.dirname(targetFile), { recursive: true });
|
||||
fs.writeFileSync(targetFile, Buffer.from(b64, 'base64'));
|
||||
}
|
||||
|
||||
for (const [relPath, distFileName] of Object.entries(nativeNodes)) {
|
||||
const targetLink = path.join(runtimeDir, relPath);
|
||||
const distTarget = path.join(distDir, distFileName);
|
||||
fs.mkdirSync(path.dirname(targetLink), { recursive: true });
|
||||
try {
|
||||
if (fs.lstatSync(targetLink)) fs.unlinkSync(targetLink);
|
||||
} catch {}
|
||||
try {
|
||||
fs.symlinkSync(distTarget, targetLink, 'file');
|
||||
} catch {}
|
||||
}
|
||||
|
||||
fs.writeFileSync(nativeMapFile, JSON.stringify(nativeNodes));
|
||||
fs.writeFileSync(markerFile, '1');
|
||||
}
|
||||
|
||||
const nativeNodes = fs.existsSync(nativeMapFile)
|
||||
? JSON.parse(fs.readFileSync(nativeMapFile, 'utf8'))
|
||||
: {};
|
||||
|
||||
// Redirect native .node loading to distDir and preload libvips from distDir if present
|
||||
const origDlopen = process.dlopen;
|
||||
process.dlopen = function (mod, filename, flags) {
|
||||
const relFromRuntime = path.relative(runtimeDir, filename);
|
||||
const mappedName = nativeNodes[relFromRuntime] || path.basename(filename);
|
||||
const distCandidate = path.join(distDir, mappedName);
|
||||
const target = fs.existsSync(distCandidate) ? distCandidate : filename;
|
||||
if (mappedName.startsWith('sharp-')) {
|
||||
const vipsPath = path.join(distDir, 'libvips-cpp.so.8.18.7');
|
||||
if (fs.existsSync(vipsPath)) {
|
||||
try {
|
||||
origDlopen.call(process, { exports: {} }, vipsPath, os.constants.dlopen.RTLD_NOW | os.constants.dlopen.RTLD_GLOBAL);
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
return flags !== undefined ? origDlopen.call(process, mod, target, flags) : origDlopen.call(process, mod, target);
|
||||
};
|
||||
|
||||
// Link host .env if present so config/env.js can find it
|
||||
const hostEnv = path.resolve(process.cwd(), '.env');
|
||||
const runtimeEnv = path.join(runtimeDir, '.env');
|
||||
|
||||
Reference in New Issue
Block a user