diff options
Diffstat (limited to 'packages/shared')
| -rw-r--r-- | packages/shared/config.ts | 268 | ||||
| -rw-r--r-- | packages/shared/package.json | 2 |
2 files changed, 153 insertions, 117 deletions
diff --git a/packages/shared/config.ts b/packages/shared/config.ts index c435d012..5a6a3dad 100644 --- a/packages/shared/config.ts +++ b/packages/shared/config.ts @@ -97,6 +97,15 @@ const allEnv = z.object({ // Prometheus metrics configuration PROMETHEUS_AUTH_TOKEN: z.string().optional(), + // Email configuration + SMTP_HOST: z.string().optional(), + SMTP_PORT: z.coerce.number().optional().default(587), + SMTP_SECURE: stringBool("false"), + SMTP_USER: z.string().optional(), + SMTP_PASSWORD: z.string().optional(), + SMTP_FROM: z.string().optional(), + EMAIL_VERIFICATION_REQUIRED: stringBool("false"), + // Asset storage configuration ASSET_STORE_S3_ENDPOINT: z.string().optional(), ASSET_STORE_S3_REGION: z.string().optional(), @@ -106,130 +115,155 @@ const allEnv = z.object({ ASSET_STORE_S3_FORCE_PATH_STYLE: stringBool("false"), }); -const serverConfigSchema = allEnv.transform((val) => { - return { - apiUrl: val.API_URL, - publicUrl: val.NEXTAUTH_URL, - publicApiUrl: `${val.NEXTAUTH_URL}/api`, - signingSecret: () => { - if (!val.NEXTAUTH_SECRET) { - throw new Error("NEXTAUTH_SECRET is not set"); - } - return val.NEXTAUTH_SECRET; - }, - auth: { - disableSignups: val.DISABLE_SIGNUPS, - disablePasswordAuth: val.DISABLE_PASSWORD_AUTH, - oauth: { - allowDangerousEmailAccountLinking: - val.OAUTH_ALLOW_DANGEROUS_EMAIL_ACCOUNT_LINKING, - wellKnownUrl: val.OAUTH_WELLKNOWN_URL, - clientSecret: val.OAUTH_CLIENT_SECRET, - clientId: val.OAUTH_CLIENT_ID, - scope: val.OAUTH_SCOPE, - name: val.OAUTH_PROVIDER_NAME, - timeout: val.OAUTH_TIMEOUT, +const serverConfigSchema = allEnv + .transform((val) => { + return { + apiUrl: val.API_URL, + publicUrl: val.NEXTAUTH_URL, + publicApiUrl: `${val.NEXTAUTH_URL}/api`, + signingSecret: () => { + if (!val.NEXTAUTH_SECRET) { + throw new Error("NEXTAUTH_SECRET is not set"); + } + return val.NEXTAUTH_SECRET; }, - }, - inference: { - numWorkers: val.INFERENCE_NUM_WORKERS, - jobTimeoutSec: val.INFERENCE_JOB_TIMEOUT_SEC, - fetchTimeoutSec: val.INFERENCE_FETCH_TIMEOUT_SEC, - openAIApiKey: val.OPENAI_API_KEY, - openAIBaseUrl: val.OPENAI_BASE_URL, - ollamaBaseUrl: val.OLLAMA_BASE_URL, - ollamaKeepAlive: val.OLLAMA_KEEP_ALIVE, - textModel: val.INFERENCE_TEXT_MODEL, - imageModel: val.INFERENCE_IMAGE_MODEL, - inferredTagLang: val.INFERENCE_LANG, - contextLength: val.INFERENCE_CONTEXT_LENGTH, - outputSchema: - val.INFERENCE_SUPPORTS_STRUCTURED_OUTPUT !== undefined - ? val.INFERENCE_SUPPORTS_STRUCTURED_OUTPUT - ? ("structured" as const) - : ("plain" as const) - : val.INFERENCE_OUTPUT_SCHEMA, - enableAutoTagging: val.INFERENCE_ENABLE_AUTO_TAGGING, - enableAutoSummarization: val.INFERENCE_ENABLE_AUTO_SUMMARIZATION, - }, - embedding: { - textModel: val.EMBEDDING_TEXT_MODEL, - }, - crawler: { - numWorkers: val.CRAWLER_NUM_WORKERS, - headlessBrowser: val.CRAWLER_HEADLESS_BROWSER, - browserWebUrl: val.BROWSER_WEB_URL, - browserWebSocketUrl: val.BROWSER_WEBSOCKET_URL, - browserConnectOnDemand: val.BROWSER_CONNECT_ONDEMAND, - jobTimeoutSec: val.CRAWLER_JOB_TIMEOUT_SEC, - navigateTimeoutSec: val.CRAWLER_NAVIGATE_TIMEOUT_SEC, - downloadBannerImage: val.CRAWLER_DOWNLOAD_BANNER_IMAGE, - storeScreenshot: val.CRAWLER_STORE_SCREENSHOT, - fullPageScreenshot: val.CRAWLER_FULL_PAGE_SCREENSHOT, - fullPageArchive: val.CRAWLER_FULL_PAGE_ARCHIVE, - downloadVideo: val.CRAWLER_VIDEO_DOWNLOAD, - maxVideoDownloadSize: val.CRAWLER_VIDEO_DOWNLOAD_MAX_SIZE, - downloadVideoTimeout: val.CRAWLER_VIDEO_DOWNLOAD_TIMEOUT_SEC, - enableAdblocker: val.CRAWLER_ENABLE_ADBLOCKER, - ytDlpArguments: val.CRAWLER_YTDLP_ARGS, - screenshotTimeoutSec: val.CRAWLER_SCREENSHOT_TIMEOUT_SEC, - }, - ocr: { - langs: val.OCR_LANGS, - cacheDir: val.OCR_CACHE_DIR, - confidenceThreshold: val.OCR_CONFIDENCE_THRESHOLD, - }, - search: { - numWorkers: val.SEARCH_NUM_WORKERS, - meilisearch: val.MEILI_ADDR + auth: { + disableSignups: val.DISABLE_SIGNUPS, + disablePasswordAuth: val.DISABLE_PASSWORD_AUTH, + emailVerificationRequired: val.EMAIL_VERIFICATION_REQUIRED, + oauth: { + allowDangerousEmailAccountLinking: + val.OAUTH_ALLOW_DANGEROUS_EMAIL_ACCOUNT_LINKING, + wellKnownUrl: val.OAUTH_WELLKNOWN_URL, + clientSecret: val.OAUTH_CLIENT_SECRET, + clientId: val.OAUTH_CLIENT_ID, + scope: val.OAUTH_SCOPE, + name: val.OAUTH_PROVIDER_NAME, + timeout: val.OAUTH_TIMEOUT, + }, + }, + email: { + smtp: val.SMTP_HOST + ? { + host: val.SMTP_HOST, + port: val.SMTP_PORT, + secure: val.SMTP_SECURE, + user: val.SMTP_USER, + password: val.SMTP_PASSWORD, + from: val.SMTP_FROM, + } + : undefined, + }, + inference: { + numWorkers: val.INFERENCE_NUM_WORKERS, + jobTimeoutSec: val.INFERENCE_JOB_TIMEOUT_SEC, + fetchTimeoutSec: val.INFERENCE_FETCH_TIMEOUT_SEC, + openAIApiKey: val.OPENAI_API_KEY, + openAIBaseUrl: val.OPENAI_BASE_URL, + ollamaBaseUrl: val.OLLAMA_BASE_URL, + ollamaKeepAlive: val.OLLAMA_KEEP_ALIVE, + textModel: val.INFERENCE_TEXT_MODEL, + imageModel: val.INFERENCE_IMAGE_MODEL, + inferredTagLang: val.INFERENCE_LANG, + contextLength: val.INFERENCE_CONTEXT_LENGTH, + outputSchema: + val.INFERENCE_SUPPORTS_STRUCTURED_OUTPUT !== undefined + ? val.INFERENCE_SUPPORTS_STRUCTURED_OUTPUT + ? ("structured" as const) + : ("plain" as const) + : val.INFERENCE_OUTPUT_SCHEMA, + enableAutoTagging: val.INFERENCE_ENABLE_AUTO_TAGGING, + enableAutoSummarization: val.INFERENCE_ENABLE_AUTO_SUMMARIZATION, + }, + embedding: { + textModel: val.EMBEDDING_TEXT_MODEL, + }, + crawler: { + numWorkers: val.CRAWLER_NUM_WORKERS, + headlessBrowser: val.CRAWLER_HEADLESS_BROWSER, + browserWebUrl: val.BROWSER_WEB_URL, + browserWebSocketUrl: val.BROWSER_WEBSOCKET_URL, + browserConnectOnDemand: val.BROWSER_CONNECT_ONDEMAND, + jobTimeoutSec: val.CRAWLER_JOB_TIMEOUT_SEC, + navigateTimeoutSec: val.CRAWLER_NAVIGATE_TIMEOUT_SEC, + downloadBannerImage: val.CRAWLER_DOWNLOAD_BANNER_IMAGE, + storeScreenshot: val.CRAWLER_STORE_SCREENSHOT, + fullPageScreenshot: val.CRAWLER_FULL_PAGE_SCREENSHOT, + fullPageArchive: val.CRAWLER_FULL_PAGE_ARCHIVE, + downloadVideo: val.CRAWLER_VIDEO_DOWNLOAD, + maxVideoDownloadSize: val.CRAWLER_VIDEO_DOWNLOAD_MAX_SIZE, + downloadVideoTimeout: val.CRAWLER_VIDEO_DOWNLOAD_TIMEOUT_SEC, + enableAdblocker: val.CRAWLER_ENABLE_ADBLOCKER, + ytDlpArguments: val.CRAWLER_YTDLP_ARGS, + screenshotTimeoutSec: val.CRAWLER_SCREENSHOT_TIMEOUT_SEC, + }, + ocr: { + langs: val.OCR_LANGS, + cacheDir: val.OCR_CACHE_DIR, + confidenceThreshold: val.OCR_CONFIDENCE_THRESHOLD, + }, + search: { + numWorkers: val.SEARCH_NUM_WORKERS, + meilisearch: val.MEILI_ADDR + ? { + address: val.MEILI_ADDR, + key: val.MEILI_MASTER_KEY, + } + : undefined, + }, + logLevel: val.LOG_LEVEL, + demoMode: val.DEMO_MODE ? { - address: val.MEILI_ADDR, - key: val.MEILI_MASTER_KEY, + email: val.DEMO_MODE_EMAIL, + password: val.DEMO_MODE_PASSWORD, } : undefined, - }, - logLevel: val.LOG_LEVEL, - demoMode: val.DEMO_MODE - ? { - email: val.DEMO_MODE_EMAIL, - password: val.DEMO_MODE_PASSWORD, - } - : undefined, - dataDir: val.DATA_DIR, - assetsDir: val.ASSETS_DIR ?? path.join(val.DATA_DIR, "assets"), - maxAssetSizeMb: val.MAX_ASSET_SIZE_MB, - serverVersion: val.SERVER_VERSION, - disableNewReleaseCheck: val.DISABLE_NEW_RELEASE_CHECK, - usingLegacySeparateContainers: val.USING_LEGACY_SEPARATE_CONTAINERS, - webhook: { - timeoutSec: val.WEBHOOK_TIMEOUT_SEC, - retryTimes: val.WEBHOOK_RETRY_TIMES, - numWorkers: val.WEBHOOK_NUM_WORKERS, - }, - assetPreprocessing: { - numWorkers: val.ASSET_PREPROCESSING_NUM_WORKERS, - }, - ruleEngine: { - numWorkers: val.RULE_ENGINE_NUM_WORKERS, - }, - assetStore: { - type: val.ASSET_STORE_S3_ENDPOINT - ? ("s3" as const) - : ("filesystem" as const), - s3: { - endpoint: val.ASSET_STORE_S3_ENDPOINT, - region: val.ASSET_STORE_S3_REGION, - bucket: val.ASSET_STORE_S3_BUCKET, - accessKeyId: val.ASSET_STORE_S3_ACCESS_KEY_ID, - secretAccessKey: val.ASSET_STORE_S3_SECRET_ACCESS_KEY, - forcePathStyle: val.ASSET_STORE_S3_FORCE_PATH_STYLE, + dataDir: val.DATA_DIR, + assetsDir: val.ASSETS_DIR ?? path.join(val.DATA_DIR, "assets"), + maxAssetSizeMb: val.MAX_ASSET_SIZE_MB, + serverVersion: val.SERVER_VERSION, + disableNewReleaseCheck: val.DISABLE_NEW_RELEASE_CHECK, + usingLegacySeparateContainers: val.USING_LEGACY_SEPARATE_CONTAINERS, + webhook: { + timeoutSec: val.WEBHOOK_TIMEOUT_SEC, + retryTimes: val.WEBHOOK_RETRY_TIMES, + numWorkers: val.WEBHOOK_NUM_WORKERS, }, + assetPreprocessing: { + numWorkers: val.ASSET_PREPROCESSING_NUM_WORKERS, + }, + ruleEngine: { + numWorkers: val.RULE_ENGINE_NUM_WORKERS, + }, + assetStore: { + type: val.ASSET_STORE_S3_ENDPOINT + ? ("s3" as const) + : ("filesystem" as const), + s3: { + endpoint: val.ASSET_STORE_S3_ENDPOINT, + region: val.ASSET_STORE_S3_REGION, + bucket: val.ASSET_STORE_S3_BUCKET, + accessKeyId: val.ASSET_STORE_S3_ACCESS_KEY_ID, + secretAccessKey: val.ASSET_STORE_S3_SECRET_ACCESS_KEY, + forcePathStyle: val.ASSET_STORE_S3_FORCE_PATH_STYLE, + }, + }, + prometheus: { + metricsToken: val.PROMETHEUS_AUTH_TOKEN, + }, + }; + }) + .refine( + (val) => { + if (val.auth.emailVerificationRequired && !val.email.smtp) { + return false; + } + return true; }, - prometheus: { - metricsToken: val.PROMETHEUS_AUTH_TOKEN, + { + message: "To enable email verification, SMTP settings must be configured", }, - }; -}); + ); const serverConfig = serverConfigSchema.parse(process.env); // Always explicitly pick up stuff from server config to avoid accidentally leaking stuff diff --git a/packages/shared/package.json b/packages/shared/package.json index 6f22865f..0210e24f 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -11,6 +11,7 @@ "js-tiktoken": "^1.0.20", "liteque": "^0.3.2", "meilisearch": "^0.37.0", + "nodemailer": "^7.0.4", "ollama": "^0.5.14", "openai": "^4.86.1", "typescript-parsec": "^0.3.4", @@ -22,6 +23,7 @@ "@karakeep/prettier-config": "workspace:^0.1.0", "@karakeep/tsconfig": "workspace:^0.1.0", "@types/html-to-text": "^9.0.4", + "@types/nodemailer": "^6.4.17", "vitest": "^1.6.1" }, "scripts": { |
