diff --git a/server/db/redis.ts b/server/db/redis.ts index 80f1e690..7ffd6419 100644 --- a/server/db/redis.ts +++ b/server/db/redis.ts @@ -14,7 +14,7 @@ class RedisManager { > = new Map(); private constructor() { - this.isEnabled = config.getRawConfig().redis?.enabled || false; + this.isEnabled = config.getRawConfig().flags?.enable_redis || false; if (this.isEnabled) { this.initializeClients(); } diff --git a/server/lib/readConfigFile.ts b/server/lib/readConfigFile.ts index aca2e262..0f99fd4e 100644 --- a/server/lib/readConfigFile.ts +++ b/server/lib/readConfigFile.ts @@ -12,244 +12,255 @@ const getEnvOrYaml = (envVar: string) => (valFromYaml: any) => { return process.env[envVar] ?? valFromYaml; }; -export const configSchema = z.object({ - app: z.object({ - dashboard_url: z - .string() - .url() - .optional() - .pipe(z.string().url()) - .transform((url) => url.toLowerCase()), - log_level: z - .enum(["debug", "info", "warn", "error"]) - .optional() - .default("info"), - save_logs: z.boolean().optional().default(false), - log_failed_attempts: z.boolean().optional().default(false) - }), - domains: z - .record( - z.string(), - z.object({ - base_domain: z - .string() - .nonempty("base_domain must not be empty") - .transform((url) => url.toLowerCase()), - cert_resolver: z.string().optional().default("letsencrypt"), - prefer_wildcard_cert: z.boolean().optional().default(false) - }) - ) - .refine( - (domains) => { - const keys = Object.keys(domains); - - if (keys.length === 0) { - return false; - } - - return true; - }, - { - message: "At least one domain must be defined" - } - ), - server: z.object({ - integration_port: portSchema - .optional() - .default(3003) - .transform(stoi) - .pipe(portSchema.optional()), - external_port: portSchema - .optional() - .default(3000) - .transform(stoi) - .pipe(portSchema), - internal_port: portSchema - .optional() - .default(3001) - .transform(stoi) - .pipe(portSchema), - next_port: portSchema - .optional() - .default(3002) - .transform(stoi) - .pipe(portSchema), - internal_hostname: z - .string() - .optional() - .default("pangolin") - .transform((url) => url.toLowerCase()), - session_cookie_name: z.string().optional().default("p_session_token"), - resource_access_token_param: z.string().optional().default("p_token"), - resource_access_token_headers: z - .object({ - id: z.string().optional().default("P-Access-Token-Id"), - token: z.string().optional().default("P-Access-Token") - }) - .optional() - .default({}), - resource_session_request_param: z - .string() - .optional() - .default("resource_session_request_param"), - dashboard_session_length_hours: z - .number() - .positive() - .gt(0) - .optional() - .default(720), - resource_session_length_hours: z - .number() - .positive() - .gt(0) - .optional() - .default(720), - cors: z - .object({ - origins: z.array(z.string()).optional(), - methods: z.array(z.string()).optional(), - allowed_headers: z.array(z.string()).optional(), - credentials: z.boolean().optional() - }) - .optional(), - trust_proxy: z.boolean().optional().default(true), - secret: z - .string() - .optional() - .transform(getEnvOrYaml("SERVER_SECRET")) - .pipe(z.string().min(8)) - }), - postgres: z - .object({ - connection_string: z.string(), - replicas: z - .array( - z.object({ - connection_string: z.string() - }) - ) +export const configSchema = z + .object({ + app: z.object({ + dashboard_url: z + .string() + .url() .optional() - }) - .optional(), - redis: z - .object({ - enabled: z.boolean(), - host: z.string().optional(), - port: portSchema.optional(), - password: z.string().optional(), - db: z.number().int().nonnegative().optional().default(0), - tls: z - .object({ - rejectUnauthorized: z.boolean().optional().default(true) + .pipe(z.string().url()) + .transform((url) => url.toLowerCase()), + log_level: z + .enum(["debug", "info", "warn", "error"]) + .optional() + .default("info"), + save_logs: z.boolean().optional().default(false), + log_failed_attempts: z.boolean().optional().default(false) + }), + domains: z + .record( + z.string(), + z.object({ + base_domain: z + .string() + .nonempty("base_domain must not be empty") + .transform((url) => url.toLowerCase()), + cert_resolver: z.string().optional().default("letsencrypt"), + prefer_wildcard_cert: z.boolean().optional().default(false) }) - .optional() - }) - .refine( - (redis) => { - if (!redis.enabled) { + ) + .refine( + (domains) => { + const keys = Object.keys(domains); + + if (keys.length === 0) { + return false; + } + return true; + }, + { + message: "At least one domain must be defined" } - return redis.host !== undefined && redis.port !== undefined; - }, - { - message: - "If Redis is enabled, connection details must be provided" - } - ) - .optional(), - traefik: z - .object({ - http_entrypoint: z.string().optional().default("web"), - https_entrypoint: z.string().optional().default("websecure"), - additional_middlewares: z.array(z.string()).optional() - }) - .optional() - .default({}), - gerbil: z - .object({ - start_port: portSchema + ), + server: z.object({ + integration_port: portSchema .optional() - .default(51820) + .default(3003) + .transform(stoi) + .pipe(portSchema.optional()), + external_port: portSchema + .optional() + .default(3000) .transform(stoi) .pipe(portSchema), - base_endpoint: z + internal_port: portSchema + .optional() + .default(3001) + .transform(stoi) + .pipe(portSchema), + next_port: portSchema + .optional() + .default(3002) + .transform(stoi) + .pipe(portSchema), + internal_hostname: z .string() .optional() - .pipe(z.string()) + .default("pangolin") .transform((url) => url.toLowerCase()), - use_subdomain: z.boolean().optional().default(false), - subnet_group: z.string().optional().default("100.89.137.0/20"), - block_size: z.number().positive().gt(0).optional().default(24), - site_block_size: z.number().positive().gt(0).optional().default(30) - }) - .optional() - .default({}), - rate_limits: z - .object({ - global: z + session_cookie_name: z + .string() + .optional() + .default("p_session_token"), + resource_access_token_param: z + .string() + .optional() + .default("p_token"), + resource_access_token_headers: z .object({ - window_minutes: z - .number() - .positive() - .gt(0) - .optional() - .default(1), - max_requests: z - .number() - .positive() - .gt(0) - .optional() - .default(500) + id: z.string().optional().default("P-Access-Token-Id"), + token: z.string().optional().default("P-Access-Token") }) .optional() .default({}), - auth: z - .object({ - window_minutes: z.number().positive().gt(0), - max_requests: z.number().positive().gt(0) - }) - .optional() - }) - .optional() - .default({}), - email: z - .object({ - smtp_host: z.string().optional(), - smtp_port: portSchema.optional(), - smtp_user: z.string().optional(), - smtp_pass: z.string().optional(), - smtp_secure: z.boolean().optional(), - smtp_tls_reject_unauthorized: z.boolean().optional(), - no_reply: z.string().email().optional() - }) - .optional(), - users: z.object({ - server_admin: z.object({ - email: z + resource_session_request_param: z .string() - .email() .optional() - .transform(getEnvOrYaml("USERS_SERVERADMIN_EMAIL")) - .pipe(z.string().email()) - .transform((v) => v.toLowerCase()), - password: passwordSchema + .default("resource_session_request_param"), + dashboard_session_length_hours: z + .number() + .positive() + .gt(0) .optional() - .transform(getEnvOrYaml("USERS_SERVERADMIN_PASSWORD")) - .pipe(passwordSchema) - }) - }), - flags: z - .object({ - require_email_verification: z.boolean().optional(), - disable_signup_without_invite: z.boolean().optional(), - disable_user_create_org: z.boolean().optional(), - allow_raw_resources: z.boolean().optional(), - allow_base_domain_resources: z.boolean().optional(), - allow_local_sites: z.boolean().optional(), - enable_integration_api: z.boolean().optional() - }) - .optional() -}); + .default(720), + resource_session_length_hours: z + .number() + .positive() + .gt(0) + .optional() + .default(720), + cors: z + .object({ + origins: z.array(z.string()).optional(), + methods: z.array(z.string()).optional(), + allowed_headers: z.array(z.string()).optional(), + credentials: z.boolean().optional() + }) + .optional(), + trust_proxy: z.boolean().optional().default(true), + secret: z + .string() + .optional() + .transform(getEnvOrYaml("SERVER_SECRET")) + .pipe(z.string().min(8)) + }), + postgres: z + .object({ + connection_string: z.string(), + replicas: z + .array( + z.object({ + connection_string: z.string() + }) + ) + .optional() + }) + .optional(), + redis: z + .object({ + host: z.string(), + port: portSchema, + password: z.string().optional(), + db: z.number().int().nonnegative().optional().default(0), + tls: z + .object({ + rejectUnauthorized: z.boolean().optional().default(true) + }) + .optional() + }) + .optional(), + traefik: z + .object({ + http_entrypoint: z.string().optional().default("web"), + https_entrypoint: z.string().optional().default("websecure"), + additional_middlewares: z.array(z.string()).optional() + }) + .optional() + .default({}), + gerbil: z + .object({ + start_port: portSchema + .optional() + .default(51820) + .transform(stoi) + .pipe(portSchema), + base_endpoint: z + .string() + .optional() + .pipe(z.string()) + .transform((url) => url.toLowerCase()), + use_subdomain: z.boolean().optional().default(false), + subnet_group: z.string().optional().default("100.89.137.0/20"), + block_size: z.number().positive().gt(0).optional().default(24), + site_block_size: z + .number() + .positive() + .gt(0) + .optional() + .default(30) + }) + .optional() + .default({}), + rate_limits: z + .object({ + global: z + .object({ + window_minutes: z + .number() + .positive() + .gt(0) + .optional() + .default(1), + max_requests: z + .number() + .positive() + .gt(0) + .optional() + .default(500) + }) + .optional() + .default({}), + auth: z + .object({ + window_minutes: z.number().positive().gt(0), + max_requests: z.number().positive().gt(0) + }) + .optional() + }) + .optional() + .default({}), + email: z + .object({ + smtp_host: z.string().optional(), + smtp_port: portSchema.optional(), + smtp_user: z.string().optional(), + smtp_pass: z.string().optional(), + smtp_secure: z.boolean().optional(), + smtp_tls_reject_unauthorized: z.boolean().optional(), + no_reply: z.string().email().optional() + }) + .optional(), + users: z.object({ + server_admin: z.object({ + email: z + .string() + .email() + .optional() + .transform(getEnvOrYaml("USERS_SERVERADMIN_EMAIL")) + .pipe(z.string().email()) + .transform((v) => v.toLowerCase()), + password: passwordSchema + .optional() + .transform(getEnvOrYaml("USERS_SERVERADMIN_PASSWORD")) + .pipe(passwordSchema) + }) + }), + flags: z + .object({ + require_email_verification: z.boolean().optional(), + disable_signup_without_invite: z.boolean().optional(), + disable_user_create_org: z.boolean().optional(), + allow_raw_resources: z.boolean().optional(), + allow_base_domain_resources: z.boolean().optional(), + allow_local_sites: z.boolean().optional(), + enable_integration_api: z.boolean().optional(), + enable_redis: z.boolean().optional() + }) + .optional() + }) + .refine( + (data) => { + if (data.flags?.enable_redis) { + return data?.redis !== undefined; + } + return true; + }, + { + message: "If Redis is enabled, configuration details must be provided" + } + ); export function readConfigFile() { const loadConfig = (configPath: string) => {