feat: dockerfile (node runtime), startup migration, README, runtime env config

This commit is contained in:
2026-06-10 08:16:45 +00:00
parent 56186a1fea
commit 7331fe75e8
12 changed files with 286 additions and 31 deletions

View File

@@ -1,8 +1,7 @@
// Idempotent migration runner. Run via `bun run migrate` / on container start.
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
// CLI migration runner (local / CI). Run via `bun run migrate`.
// Production migrations run automatically via server/plugins/migrate.ts on startup.
import postgres from "postgres";
import { INIT_SQL } from "../utils/schema";
const url = process.env.DATABASE_URL;
if (!url) {
@@ -10,14 +9,10 @@ if (!url) {
process.exit(1);
}
const here = dirname(fileURLToPath(import.meta.url));
const sql = postgres(url, { max: 1 });
try {
const ddl = readFileSync(join(here, "migrations", "0001_init.sql"), "utf8");
// Trusted local DDL file, not user input.
await sql.unsafe(ddl);
console.log("migration 0001_init applied");
await sql.unsafe(INIT_SQL);
console.log("schema applied");
} finally {
await sql.end();
}

View File

@@ -3,7 +3,7 @@
export default defineEventHandler((event) => {
if (!getRequestURL(event).pathname.startsWith("/api/")) return;
const origin = useRuntimeConfig().webOrigin;
const origin = process.env.WEB_ORIGIN || "";
if (origin) {
setResponseHeader(event, "Access-Control-Allow-Origin", origin);
setResponseHeader(event, "Vary", "Origin");

17
server/plugins/migrate.ts Normal file
View File

@@ -0,0 +1,17 @@
import { INIT_SQL } from "../utils/schema";
// Apply the schema idempotently on server startup. An advisory lock prevents concurrent
// instances from racing on CREATE TABLE. Idempotent CREATE ... IF NOT EXISTS means this is
// safe to run on every boot.
export default defineNitroPlugin(async () => {
try {
const sql = getSql();
await sql.begin(async (tx) => {
await tx`select pg_advisory_xact_lock(871042)`;
await tx.unsafe(INIT_SQL);
});
console.log("[migrate] schema ensured");
} catch (e) {
console.error("[migrate] failed:", (e as Error).message);
}
});

View File

@@ -45,14 +45,13 @@ function splitCsv(v: unknown): string[] {
let _cached: ReturnType<typeof makeVerifier> | null = null;
/** Process-wide verifier built from runtime config (Nitro server context). */
/** Process-wide verifier built from environment (read at runtime, not baked at build). */
export function getVerifier() {
if (!_cached) {
const c = useRuntimeConfig();
_cached = makeVerifier({
issuer: c.zitadelIssuer,
audiences: splitCsv(c.zitadelAudience),
allowedSubs: splitCsv(c.allowedUserIds),
issuer: process.env.ZITADEL_ISSUER || "https://auth.kuns.dev",
audiences: splitCsv(process.env.ZITADEL_AUDIENCE),
allowedSubs: splitCsv(process.env.ALLOWED_USER_IDS),
});
}
return _cached;

View File

@@ -1,6 +1,6 @@
-- ClaudeDo Online Inbox initial schema.
-- Mirrors the desktop's Idle task backlog. Idempotent.
// Canonical schema for the Online Inbox. Single source of truth, applied idempotently by
// the Nitro startup plugin (server/plugins/migrate.ts) and the CLI (server/db/migrate.ts).
export const INIT_SQL = `
create table if not exists lists (
id text primary key, -- GUID supplied by the desktop, reused verbatim
name text not null,
@@ -20,3 +20,4 @@ create table if not exists tasks (
create index if not exists idx_tasks_list_id on tasks(list_id);
create index if not exists idx_tasks_unconsumed on tasks(consumed) where consumed = false;
`;