Splits the previous /opt:/opt:rw bind into two mounts to narrow the writable scope of the container: - /opt:/opt:ro — read-only mount for legacy/existing project add-existing flow. resolveProjectPath still uses PROJECT_ROOT_WHITELIST (/opt by default) so existing projects under /opt/<name> (analytics, boolab, boocode itself) continue to resolve and serve their file-tree via the read-only tools. - /opt/projects:/opt/projects:rw — writable mount targeted at the create-new-project bootstrap path. Picked Option B from the spec (simpler than two scan roots): PROJECT_ROOT_WHITELIST stays /opt, new BOOTSTRAP_ROOT env var defaults to /opt/projects and is used by project_bootstrap.ts as the mkdir target. Bootstrap path-escape check now compares against BOOTSTRAP_ROOT. Prereq: host must `mkdir -p /opt/projects` before next container restart. Documented in CLAUDE.md and .env.example. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
34 lines
1.1 KiB
TypeScript
34 lines
1.1 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
const ConfigSchema = z.object({
|
|
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
|
PORT: z.coerce.number().int().positive().default(3000),
|
|
HOST: z.string().default('0.0.0.0'),
|
|
DATABASE_URL: z.string().url(),
|
|
LLAMA_SWAP_URL: z.string().url(),
|
|
PROJECT_ROOT_WHITELIST: z.string().default('/opt'),
|
|
BOOTSTRAP_ROOT: z.string().default('/opt/projects'),
|
|
DEFAULT_MODEL: z.string().default('qwen3.6-35b-a3b-mxfp4'),
|
|
LOG_LEVEL: z.string().default('info'),
|
|
GITEA_BASE_URL: z.string().url().default('https://git.indifferentketchup.com'),
|
|
GITEA_USER: z.string().default('indifferentketchup'),
|
|
GITEA_TOKEN: z.string().optional(),
|
|
GITEA_SSH_HOST: z.string().default('100.114.205.53:2222'),
|
|
});
|
|
|
|
export type Config = z.infer<typeof ConfigSchema>;
|
|
|
|
let cached: Config | null = null;
|
|
|
|
export function loadConfig(): Config {
|
|
if (cached) return cached;
|
|
const parsed = ConfigSchema.safeParse(process.env);
|
|
if (!parsed.success) {
|
|
console.error('Invalid environment configuration:');
|
|
console.error(parsed.error.flatten().fieldErrors);
|
|
process.exit(1);
|
|
}
|
|
cached = parsed.data;
|
|
return cached;
|
|
}
|