config.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import { z } from "zod";
  2. import { fromError } from "zod-validation-error";
  3. import path from "path";
  4. import fs from "fs";
  5. import yaml from "js-yaml";
  6. import { fileURLToPath } from "url";
  7. export const __FILENAME = fileURLToPath(import.meta.url);
  8. export const __DIRNAME = path.dirname(__FILENAME);
  9. export const APP_PATH = path.join("config");
  10. const portSchema = z.number().positive().gt(0).lte(65535);
  11. const environmentSchema = z.object({
  12. app: z.object({
  13. base_url: z.string().url(),
  14. log_level: z.enum(["debug", "info", "warn", "error"]),
  15. save_logs: z.boolean()
  16. }),
  17. server: z.object({
  18. external_port: portSchema,
  19. internal_port: portSchema,
  20. next_port: portSchema,
  21. internal_hostname: z.string(),
  22. secure_cookies: z.boolean(),
  23. signup_secret: z.string().optional(),
  24. session_cookie_name: z.string(),
  25. resource_session_cookie_name: z.string()
  26. }),
  27. traefik: z.object({
  28. http_entrypoint: z.string(),
  29. https_entrypoint: z.string().optional(),
  30. cert_resolver: z.string().optional(),
  31. prefer_wildcard_cert: z.boolean().optional()
  32. }),
  33. gerbil: z.object({
  34. start_port: portSchema,
  35. base_endpoint: z.string(),
  36. use_subdomain: z.boolean(),
  37. subnet_group: z.string(),
  38. block_size: z.number().positive().gt(0)
  39. }),
  40. rate_limits: z.object({
  41. global: z.object({
  42. window_minutes: z.number().positive().gt(0),
  43. max_requests: z.number().positive().gt(0)
  44. }),
  45. auth: z
  46. .object({
  47. window_minutes: z.number().positive().gt(0),
  48. max_requests: z.number().positive().gt(0)
  49. })
  50. .optional()
  51. }),
  52. email: z
  53. .object({
  54. smtp_host: z.string().optional(),
  55. smtp_port: portSchema.optional(),
  56. smtp_user: z.string().optional(),
  57. smtp_pass: z.string().optional(),
  58. no_reply: z.string().email().optional()
  59. })
  60. .optional(),
  61. flags: z
  62. .object({
  63. allow_org_subdomain_changing: z.boolean().optional(),
  64. require_email_verification: z.boolean().optional(),
  65. disable_signup_without_invite: z.boolean().optional(),
  66. require_signup_secret: z.boolean().optional()
  67. })
  68. .optional()
  69. });
  70. const loadConfig = (configPath: string) => {
  71. try {
  72. const yamlContent = fs.readFileSync(configPath, "utf8");
  73. const config = yaml.load(yamlContent);
  74. return config;
  75. } catch (error) {
  76. if (error instanceof Error) {
  77. throw new Error(
  78. `Error loading configuration file: ${error.message}`
  79. );
  80. }
  81. throw error;
  82. }
  83. };
  84. const configFilePath1 = path.join(APP_PATH, "config.yml");
  85. const configFilePath2 = path.join(APP_PATH, "config.yaml");
  86. let environment: any;
  87. if (fs.existsSync(configFilePath1)) {
  88. environment = loadConfig(configFilePath1);
  89. } else if (fs.existsSync(configFilePath2)) {
  90. environment = loadConfig(configFilePath2);
  91. }
  92. if (!environment) {
  93. const exampleConfigPath = path.join(__DIRNAME, "config.example.yml");
  94. if (fs.existsSync(exampleConfigPath)) {
  95. try {
  96. const exampleConfigContent = fs.readFileSync(
  97. exampleConfigPath,
  98. "utf8"
  99. );
  100. fs.writeFileSync(configFilePath1, exampleConfigContent, "utf8");
  101. environment = loadConfig(configFilePath1);
  102. } catch (error) {
  103. if (error instanceof Error) {
  104. throw new Error(
  105. `Error creating configuration file from example: ${error.message}`
  106. );
  107. }
  108. throw error;
  109. }
  110. } else {
  111. throw new Error(
  112. "No configuration file found and no example configuration available"
  113. );
  114. }
  115. }
  116. if (!environment) {
  117. throw new Error("No configuration file found");
  118. }
  119. const parsedConfig = environmentSchema.safeParse(environment);
  120. if (!parsedConfig.success) {
  121. const errors = fromError(parsedConfig.error);
  122. throw new Error(`Invalid configuration file: ${errors}`);
  123. }
  124. process.env.NEXT_PORT = parsedConfig.data.server.next_port.toString();
  125. process.env.SERVER_EXTERNAL_PORT =
  126. parsedConfig.data.server.external_port.toString();
  127. process.env.SERVER_INTERNAL_PORT =
  128. parsedConfig.data.server.internal_port.toString();
  129. process.env.FLAGS_EMAIL_VERIFICATION_REQUIRED = parsedConfig.data.flags
  130. ?.require_email_verification
  131. ? "true"
  132. : "false";
  133. process.env.SESSION_COOKIE_NAME = parsedConfig.data.server.session_cookie_name;
  134. process.env.RESOURCE_SESSION_COOKIE_NAME =
  135. parsedConfig.data.server.resource_session_cookie_name;
  136. process.env.EMAIL_ENABLED = parsedConfig.data.email ? "true" : "false";
  137. export default parsedConfig.data;