import { z } from "zod"; import { AUTH_BASE_URL_MODES, DEPLOYMENT_EXPOSURES, DEPLOYMENT_MODES, SECRET_PROVIDERS, STORAGE_PROVIDERS, } from "./constants.js"; export const configMetaSchema = z.object({ version: z.literal(1), updatedAt: z.string(), source: z.enum(["onboard", "configure", "doctor"]), }); export const llmConfigSchema = z.object({ provider: z.enum(["claude", "openai"]), apiKey: z.string().optional(), }); export const databaseBackupConfigSchema = z.object({ enabled: z.boolean().default(true), intervalMinutes: z.number().int().min(1).max(7 * 24 * 60).default(60), retentionDays: z.number().int().min(1).max(3650).default(30), dir: z.string().default("~/.paperclip/instances/default/data/backups"), }); export const databaseConfigSchema = z.object({ mode: z.enum(["embedded-postgres", "postgres"]).default("embedded-postgres"), connectionString: z.string().optional(), embeddedPostgresDataDir: z.string().default("~/.paperclip/instances/default/db"), embeddedPostgresPort: z.number().int().min(1).max(65535).default(54329), backup: databaseBackupConfigSchema.default({ enabled: true, intervalMinutes: 60, retentionDays: 30, dir: "~/.paperclip/instances/default/data/backups", }), }); export const loggingConfigSchema = z.object({ mode: z.enum(["file", "cloud"]), logDir: z.string().default("~/.paperclip/instances/default/logs"), }); export const serverConfigSchema = z.object({ deploymentMode: z.enum(DEPLOYMENT_MODES).default("local_trusted"), exposure: z.enum(DEPLOYMENT_EXPOSURES).default("private"), host: z.string().default("127.0.0.1"), port: z.number().int().min(1).max(65535).default(3100), allowedHostnames: z.array(z.string().min(1)).default([]), serveUi: z.boolean().default(true), }); export const authConfigSchema = z.object({ baseUrlMode: z.enum(AUTH_BASE_URL_MODES).default("auto"), publicBaseUrl: z.string().url().optional(), disableSignUp: z.boolean().default(false), }); export const storageLocalDiskConfigSchema = z.object({ baseDir: z.string().default("~/.paperclip/instances/default/data/storage"), }); export const storageS3ConfigSchema = z.object({ bucket: z.string().min(1).default("paperclip"), region: z.string().min(1).default("us-east-1"), endpoint: z.string().optional(), prefix: z.string().default(""), forcePathStyle: z.boolean().default(false), }); export const storageConfigSchema = z.object({ provider: z.enum(STORAGE_PROVIDERS).default("local_disk"), localDisk: storageLocalDiskConfigSchema.default({ baseDir: "~/.paperclip/instances/default/data/storage", }), s3: storageS3ConfigSchema.default({ bucket: "paperclip", region: "us-east-1", prefix: "", forcePathStyle: false, }), }); export const secretsLocalEncryptedConfigSchema = z.object({ keyFilePath: z.string().default("~/.paperclip/instances/default/secrets/master.key"), }); export const secretsConfigSchema = z.object({ provider: z.enum(SECRET_PROVIDERS).default("local_encrypted"), strictMode: z.boolean().default(false), localEncrypted: secretsLocalEncryptedConfigSchema.default({ keyFilePath: "~/.paperclip/instances/default/secrets/master.key", }), }); export const paperclipConfigSchema = z .object({ $meta: configMetaSchema, llm: llmConfigSchema.optional(), database: databaseConfigSchema, logging: loggingConfigSchema, server: serverConfigSchema, auth: authConfigSchema.default({ baseUrlMode: "auto", disableSignUp: false, }), storage: storageConfigSchema.default({ provider: "local_disk", localDisk: { baseDir: "~/.paperclip/instances/default/data/storage", }, s3: { bucket: "paperclip", region: "us-east-1", prefix: "", forcePathStyle: false, }, }), secrets: secretsConfigSchema.default({ provider: "local_encrypted", strictMode: false, localEncrypted: { keyFilePath: "~/.paperclip/instances/default/secrets/master.key", }, }), }) .superRefine((value, ctx) => { if (value.server.deploymentMode === "local_trusted") { if (value.server.exposure !== "private") { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "server.exposure must be private when deploymentMode is local_trusted", path: ["server", "exposure"], }); } return; } if (value.auth.baseUrlMode === "explicit" && !value.auth.publicBaseUrl) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "auth.publicBaseUrl is required when auth.baseUrlMode is explicit", path: ["auth", "publicBaseUrl"], }); } if (value.server.exposure === "public" && value.auth.baseUrlMode !== "explicit") { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "auth.baseUrlMode must be explicit when deploymentMode=authenticated and exposure=public", path: ["auth", "baseUrlMode"], }); } if (value.server.exposure === "public" && !value.auth.publicBaseUrl) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "auth.publicBaseUrl is required when deploymentMode=authenticated and exposure=public", path: ["auth", "publicBaseUrl"], }); } }); export type PaperclipConfig = z.infer; export type LlmConfig = z.infer; export type DatabaseConfig = z.infer; export type LoggingConfig = z.infer; export type ServerConfig = z.infer; export type StorageConfig = z.infer; export type StorageLocalDiskConfig = z.infer; export type StorageS3Config = z.infer; export type SecretsConfig = z.infer; export type SecretsLocalEncryptedConfig = z.infer; export type AuthConfig = z.infer; export type ConfigMeta = z.infer; export type DatabaseBackupConfig = z.infer;