This commit is contained in:
seb
2026-10-27 16:20:02 +01:00
parent 97cae698db
commit 80ecad6ddd
5 changed files with 40 additions and 75 deletions

View File

@@ -15,15 +15,6 @@ COPY --from=syslibs /usr/lib/x86_64-linux-gnu/libfontconfig.so* /usr/lib/x86_64-
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/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/ COPY --from=syslibs /usr/lib/x86_64-linux-gnu/libfribidi.so* /usr/lib/x86_64-linux-gnu/
# The standalone SEA binary contains Node.js runtime, src/, package.json, and all app logic
COPY dist/dashback ./dist/dashback
# Only native C++ addon packages required by OS dynamic linker (dlopen)
COPY node_modules/better-sqlite3 ./node_modules/better-sqlite3
COPY node_modules/canvas ./node_modules/canvas
COPY node_modules/@img ./node_modules/@img
COPY node_modules/sharp ./node_modules/sharp
# Set production defaults: port 4208 # Set production defaults: port 4208
ENV NODE_ENV=production \ ENV NODE_ENV=production \
HOST=0.0.0.0 \ HOST=0.0.0.0 \

View File

@@ -1,37 +0,0 @@
services:
dashback:
build:
context: .
dockerfile: Dockerfile
image: dashback:latest
container_name: dashback
read_only: true
user: "${UID:-1000}:${GID:-1000}"
tmpfs:
- /tmp:rw,noexec,nosuid,size=512m
- /run:rw,noexec,nosuid,size=16m
networks:
- dash_net
ports:
- "127.0.0.1:4208:4208"
environment:
- HOST=0.0.0.0
- PORT=4208
- HTTPS_PORT=0
- MSSQL_SERVER=sql_server
- HEALTH_MONITOR_ENABLED=false
- SQLITE_PATH=/app/data/dash.db
- STORAGE_DIR=/app/data/storage
- BACKUP_DIR=/app/data/backups
# If a .env file is present, its values will be loaded:
env_file:
- path: .env
required: false
volumes:
# Writable data directory
- ./data:/app/data:rw
restart: unless-stopped
networks:
dash_net:
external: true

View File

@@ -18,10 +18,7 @@ if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir, { recursive: true }); fs.mkdirSync(distDir, { recursive: true });
} }
console.log('Packaging application, siblings, and JavaScript dependencies into SEA payload...'); console.log('Packaging application, siblings, and dependencies into SEA payload...');
// Native C++ addon folders that will be loaded from the host filesystem via dlopen
const NATIVE_ADDON_PKGS = new Set(['better-sqlite3', 'canvas', 'sharp', '@img']);
function packDirectory(dir, prefix, result) { function packDirectory(dir, prefix, result) {
if (!fs.existsSync(dir)) return; if (!fs.existsSync(dir)) return;
@@ -30,11 +27,10 @@ function packDirectory(dir, prefix, result) {
const full = path.join(dir, entry.name); const full = path.join(dir, entry.name);
const rel = path.join(prefix, entry.name); const rel = path.join(prefix, entry.name);
if (entry.isDirectory()) { if (entry.isDirectory()) {
if (entry.name === 'test' || entry.name === 'tests' || entry.name === 'docs' || entry.name === '.github' || entry.name === '.git') continue; 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); packDirectory(full, rel, result);
} else if (entry.isFile()) { } else if (entry.isFile()) {
// Ignore compiled native binary extensions which are linked from the host 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') || entry.name.endsWith('.so') || entry.name.endsWith('.dylib') || entry.name.endsWith('.dll') || entry.name.endsWith('.a')) continue;
result[rel] = fs.readFileSync(full).toString('base64'); result[rel] = fs.readFileSync(full).toString('base64');
} }
} }
@@ -67,15 +63,14 @@ if (fs.existsSync(path.join(llmorbiterDir, 'src'))) {
} }
} }
// 3. Pack all non-native node_modules dependencies // 3. Pack all node_modules dependencies
function packNodeModules(nmPath) { function packNodeModules(nmPath) {
if (!fs.existsSync(nmPath)) return; if (!fs.existsSync(nmPath)) return;
for (const name of fs.readdirSync(nmPath)) { for (const name of fs.readdirSync(nmPath)) {
if (NATIVE_ADDON_PKGS.has(name) || name.startsWith('.') || name === 'libdashstore' || name === 'libllmorbiter') continue; if (name.startsWith('.') || name === 'libdashstore' || name === 'libllmorbiter') continue;
const pkgDir = path.join(nmPath, name); const pkgDir = path.join(nmPath, name);
if (name.startsWith('@')) { if (name.startsWith('@')) {
for (const sub of fs.readdirSync(pkgDir)) { for (const sub of fs.readdirSync(pkgDir)) {
if (NATIVE_ADDON_PKGS.has(sub) || NATIVE_ADDON_PKGS.has(`${name}/${sub}`)) continue;
packDirectory(path.join(pkgDir, sub), `node_modules/${name}/${sub}`, payload); packDirectory(path.join(pkgDir, sub), `node_modules/${name}/${sub}`, payload);
} }
} else { } else {

View File

@@ -2,8 +2,8 @@
* SEA Entry point for dashback. * SEA Entry point for dashback.
* Embedded inside the Node.js single executable binary. * Embedded inside the Node.js single executable binary.
* *
* Unpacks the bundled application, sibling packages, and JavaScript dependencies * Unpacks the bundled application, sibling packages, and all dependencies
* to a runtime directory, and links only the native C++ addons from the host. * (including native C++ addons) to a runtime directory.
*/ */
const sea = require('node:sea'); const sea = require('node:sea');
const fs = require('node:fs'); const fs = require('node:fs');
@@ -32,23 +32,6 @@ function getProjectRoot() {
fs.writeFileSync(markerFile, '1'); fs.writeFileSync(markerFile, '1');
} }
// Link host native addons into runtime node_modules
const hostNodeModules = path.resolve(process.cwd(), 'node_modules');
const runtimeNodeModules = path.join(runtimeDir, 'node_modules');
fs.mkdirSync(runtimeNodeModules, { recursive: true });
const NATIVE_ADDON_DIRS = ['better-sqlite3', 'canvas', 'sharp', '@img'];
for (const addon of NATIVE_ADDON_DIRS) {
const hostAddon = path.join(hostNodeModules, addon);
const targetAddon = path.join(runtimeNodeModules, addon);
if (fs.existsSync(hostAddon) && !fs.existsSync(targetAddon)) {
try {
fs.mkdirSync(path.dirname(targetAddon), { recursive: true });
fs.symlinkSync(hostAddon, targetAddon, 'junction');
} catch {}
}
}
// Link host .env if present so config/env.js can find it // Link host .env if present so config/env.js can find it
const hostEnv = path.resolve(process.cwd(), '.env'); const hostEnv = path.resolve(process.cwd(), '.env');
const runtimeEnv = path.join(runtimeDir, '.env'); const runtimeEnv = path.join(runtimeDir, '.env');

33
start-docker.sh Executable file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Ensure the writable data directory exists on the host
mkdir -p "${SCRIPT_DIR}/data"
ENV_ARGS=()
if [ -f "${SCRIPT_DIR}/.env" ]; then
ENV_ARGS+=(--env-file "${SCRIPT_DIR}/.env")
fi
docker run -d \
--name dashback \
--restart unless-stopped \
--read-only \
--user "$(id -u):$(id -g)" \
--tmpfs /tmp:rw,exec,nosuid,size=512m \
--tmpfs /run:rw,noexec,nosuid,size=16m \
-p 127.0.0.1:4608:4208 \
-e HOST=0.0.0.0 \
-e PORT=4208 \
-e HTTPS_PORT=0 \
-e HEALTH_MONITOR_ENABLED=false \
-e SQLITE_PATH=/app/data/dash.db \
-e STORAGE_DIR=/app/data/storage \
-e BACKUP_DIR=/app/data/backups \
"${ENV_ARGS[@]}" \
-v "${SCRIPT_DIR}/dist/dashback:/app/dist/dashback:ro" \
-v "${SCRIPT_DIR}/data:/app/data:rw" \
dashback:latest