Build verified checkpoint containers for TrueNAS
This commit is contained in:
1 parent
c5508b727a
commit
64e8299aeb
27 files changed
+536
-16
No files matched your search
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# A failed migration must stop startup. Both commands use the same local D1 ID
|
||||
# and storage root. Never reset or replace this directory during updates.
|
||||
state_path=${DUNE_STATE_PATH:-/data}
|
||||
# Validate before migration; this is an administrator setting, not a request header.
|
||||
node --input-type=module -e '
|
||||
const value = process.env.DUNE_PUBLIC_ORIGIN;
|
||||
if (value) {
|
||||
const u = new URL(value);
|
||||
if (!["http:", "https:"].includes(u.protocol) || u.username || u.password ||
|
||||
u.search || u.hash || u.pathname !== "/") throw new Error("Invalid DUNE_PUBLIC_ORIGIN");
|
||||
}
|
||||
'
|
||||
node node_modules/wrangler/bin/wrangler.js d1 migrations apply DB \
|
||||
--local --config tools/wrangler.local.json --persist-to "$state_path"
|
||||
exec node deploy/serve.mjs
|
||||
@@ -0,0 +1,71 @@
|
||||
import { readFile, readdir } from 'node:fs/promises';
|
||||
import { resolve } from 'node:path';
|
||||
import { Miniflare } from 'miniflare';
|
||||
|
||||
const server = resolve('dist/server');
|
||||
const config = JSON.parse(await readFile(resolve(server, 'wrangler.json'), 'utf8'));
|
||||
const migrations = JSON.parse(await readFile('tools/wrangler.local.json', 'utf8'));
|
||||
const migrationIds = new Map(migrations.d1_databases.map((db) => [db.binding, db.database_id]));
|
||||
for (const db of config.d1_databases) {
|
||||
if (migrationIds.get(db.binding) !== db.database_id) {
|
||||
throw new Error(`Migration/runtime database identity mismatch for ${db.binding}`);
|
||||
}
|
||||
}
|
||||
const state = resolve(process.env.DUNE_STATE_PATH || '/data');
|
||||
const port = Number(process.env.DUNE_PORT || 3000);
|
||||
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error('Invalid DUNE_PORT');
|
||||
async function modulesIn(directory) {
|
||||
const modules = [];
|
||||
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
||||
const path = resolve(directory, entry.name);
|
||||
if (entry.isDirectory()) modules.push(...await modulesIn(path));
|
||||
else if (/\.m?js$/.test(entry.name)) modules.push({ type: 'ESModule', path });
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
const entrypoint = resolve(server, config.main);
|
||||
// Vinext contains computed imports. Register the complete emitted module graph
|
||||
// explicitly instead of asking Miniflare's static import scanner to infer it.
|
||||
const modules = [
|
||||
{ type: 'ESModule', path: entrypoint },
|
||||
...(await modulesIn(server)).filter((module) => module.path !== entrypoint),
|
||||
];
|
||||
|
||||
// Serve the immutable build directly. Wrangler's development proxy can fail a
|
||||
// subsequent POST after Vinext cancels a rejected request body with assets on.
|
||||
const runtime = new Miniflare({
|
||||
name: config.name,
|
||||
host: '0.0.0.0',
|
||||
port,
|
||||
cf: false,
|
||||
modules,
|
||||
modulesRoot: server,
|
||||
compatibilityDate: config.compatibility_date,
|
||||
compatibilityFlags: config.compatibility_flags,
|
||||
bindings: { ...config.vars, DUNE_PUBLIC_ORIGIN: process.env.DUNE_PUBLIC_ORIGIN || '' },
|
||||
d1Databases: Object.fromEntries(config.d1_databases.map((db) => [db.binding, db.database_id])),
|
||||
// Match Wrangler's --persist-to layout, including its local migration ledger.
|
||||
d1Persist: resolve(state, 'v3/d1'),
|
||||
cachePersist: resolve(state, 'v3/cache'),
|
||||
assets: {
|
||||
directory: resolve(server, config.assets.directory),
|
||||
binding: 'ASSETS',
|
||||
routerConfig: { has_user_worker: true },
|
||||
},
|
||||
});
|
||||
|
||||
let stopping = false;
|
||||
async function stop() {
|
||||
if (stopping) return;
|
||||
stopping = true;
|
||||
await runtime.dispose();
|
||||
}
|
||||
process.on('SIGTERM', () => { void stop(); });
|
||||
process.on('SIGINT', () => { void stop(); });
|
||||
try {
|
||||
await runtime.ready;
|
||||
console.log(`Dune listening on HTTP port ${port}`);
|
||||
} catch (error) {
|
||||
await stop();
|
||||
throw error;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
services:
|
||||
dune:
|
||||
image: ghcr.io/quintine/dune:checkpoint
|
||||
init: true
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
DUNE_PUBLIC_ORIGIN: https://dune.procrastination.games
|
||||
ports:
|
||||
- "33046:3000"
|
||||
volumes:
|
||||
- dune-data:/data
|
||||
stop_grace_period: 60s
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
|
||||
volumes:
|
||||
dune-data:
|
||||
Reference in new issue
Block a user