config.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  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_limit: z.object({
  41. window_minutes: z.number().positive().gt(0),
  42. max_requests: z.number().positive().gt(0),
  43. }),
  44. email: z
  45. .object({
  46. smtp_host: z.string().optional(),
  47. smtp_port: portSchema.optional(),
  48. smtp_user: z.string().optional(),
  49. smtp_pass: z.string().optional(),
  50. no_reply: z.string().email().optional(),
  51. })
  52. .optional(),
  53. flags: z
  54. .object({
  55. allow_org_subdomain_changing: z.boolean().optional(),
  56. require_email_verification: z.boolean().optional(),
  57. disable_signup_without_invite: z.boolean().optional(),
  58. require_signup_secret: z.boolean().optional(),
  59. })
  60. .optional(),
  61. });
  62. const loadConfig = (configPath: string) => {
  63. try {
  64. const yamlContent = fs.readFileSync(configPath, "utf8");
  65. const config = yaml.load(yamlContent);
  66. return config;
  67. } catch (error) {
  68. if (error instanceof Error) {
  69. throw new Error(
  70. `Error loading configuration file: ${error.message}`,
  71. );
  72. }
  73. throw error;
  74. }
  75. };
  76. const configFilePath1 = path.join(APP_PATH, "config.yml");
  77. const configFilePath2 = path.join(APP_PATH, "config.yaml");
  78. let environment: any;
  79. if (fs.existsSync(configFilePath1)) {
  80. environment = loadConfig(configFilePath1);
  81. } else if (fs.existsSync(configFilePath2)) {
  82. environment = loadConfig(configFilePath2);
  83. }
  84. if (!environment) {
  85. const exampleConfigPath = path.join(__DIRNAME, "config.example.yml");
  86. if (fs.existsSync(exampleConfigPath)) {
  87. try {
  88. const exampleConfigContent = fs.readFileSync(
  89. exampleConfigPath,
  90. "utf8",
  91. );
  92. fs.writeFileSync(configFilePath1, exampleConfigContent, "utf8");
  93. environment = loadConfig(configFilePath1);
  94. } catch (error) {
  95. if (error instanceof Error) {
  96. throw new Error(
  97. `Error creating configuration file from example: ${error.message}`,
  98. );
  99. }
  100. throw error;
  101. }
  102. } else {
  103. throw new Error(
  104. "No configuration file found and no example configuration available",
  105. );
  106. }
  107. }
  108. if (!environment) {
  109. throw new Error("No configuration file found");
  110. }
  111. const parsedConfig = environmentSchema.safeParse(environment);
  112. if (!parsedConfig.success) {
  113. const errors = fromError(parsedConfig.error);
  114. throw new Error(`Invalid configuration file: ${errors}`);
  115. }
  116. process.env.NEXT_PORT = parsedConfig.data.server.next_port.toString();
  117. process.env.SERVER_EXTERNAL_PORT =
  118. parsedConfig.data.server.external_port.toString();
  119. process.env.SERVER_INTERNAL_PORT =
  120. parsedConfig.data.server.internal_port.toString();
  121. process.env.FLAGS_EMAIL_VERIFICATION_REQUIRED = parsedConfig.data.flags
  122. ?.require_email_verification
  123. ? "true"
  124. : "false";
  125. process.env.SESSION_COOKIE_NAME = parsedConfig.data.server.session_cookie_name;
  126. process.env.RESOURCE_SESSION_COOKIE_NAME =
  127. parsedConfig.data.server.resource_session_cookie_name;
  128. export default parsedConfig.data;