config.ts 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import fs from "fs";
  2. import yaml from "js-yaml";
  3. import path from "path";
  4. import { z } from "zod";
  5. import { fromError } from "zod-validation-error";
  6. <<<<<<< HEAD
  7. import { __DIRNAME, APP_PATH, configFilePath1, configFilePath2 } from "@server/lib/consts";
  8. =======
  9. import {
  10. __DIRNAME,
  11. configFilePath1,
  12. configFilePath2
  13. } from "@server/lib/consts";
  14. >>>>>>> c3d19454f7f5c5546a7efb47c23fc040dbbf94d2
  15. import { loadAppVersion } from "@server/lib/loadAppVersion";
  16. import { passwordSchema } from "@server/auth/passwordSchema";
  17. const portSchema = z.number().positive().gt(0).lte(65535);
  18. const hostnameSchema = z
  19. .string()
  20. .regex(
  21. <<<<<<< HEAD
  22. /^(?!-)[a-zA-Z0-9-]{1,63}(?<!-)(\.[a-zA-Z]{2,})*$/,
  23. "Invalid hostname. Must be a valid hostname like 'localhost' or 'test.example.com'."
  24. );
  25. =======
  26. /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)+([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$/
  27. )
  28. .or(z.literal("localhost"));
  29. >>>>>>> c3d19454f7f5c5546a7efb47c23fc040dbbf94d2
  30. const environmentSchema = z.object({
  31. app: z.object({
  32. dashboard_url: z
  33. .string()
  34. .url()
  35. .transform((url) => url.toLowerCase()),
  36. base_domain: hostnameSchema,
  37. log_level: z.enum(["debug", "info", "warn", "error"]),
  38. save_logs: z.boolean()
  39. }),
  40. server: z.object({
  41. external_port: portSchema,
  42. internal_port: portSchema,
  43. next_port: portSchema,
  44. internal_hostname: z.string().transform((url) => url.toLowerCase()),
  45. secure_cookies: z.boolean(),
  46. session_cookie_name: z.string(),
  47. resource_session_cookie_name: z.string()
  48. }),
  49. traefik: z.object({
  50. http_entrypoint: z.string(),
  51. https_entrypoint: z.string().optional(),
  52. cert_resolver: z.string().optional(),
  53. prefer_wildcard_cert: z.boolean().optional()
  54. }),
  55. gerbil: z.object({
  56. start_port: portSchema,
  57. base_endpoint: z.string().transform((url) => url.toLowerCase()),
  58. use_subdomain: z.boolean(),
  59. subnet_group: z.string(),
  60. block_size: z.number().positive().gt(0)
  61. }),
  62. rate_limits: z.object({
  63. global: z.object({
  64. window_minutes: z.number().positive().gt(0),
  65. max_requests: z.number().positive().gt(0)
  66. }),
  67. auth: z
  68. .object({
  69. window_minutes: z.number().positive().gt(0),
  70. max_requests: z.number().positive().gt(0)
  71. })
  72. .optional()
  73. }),
  74. email: z
  75. .object({
  76. smtp_host: z.string(),
  77. smtp_port: portSchema,
  78. smtp_user: z.string(),
  79. smtp_pass: z.string(),
  80. no_reply: z.string().email()
  81. })
  82. .optional(),
  83. users: z.object({
  84. server_admin: z.object({
  85. email: z.string().email(),
  86. password: passwordSchema
  87. })
  88. }),
  89. flags: z
  90. .object({
  91. require_email_verification: z.boolean().optional(),
  92. disable_signup_without_invite: z.boolean().optional(),
  93. disable_user_create_org: z.boolean().optional()
  94. })
  95. .optional()
  96. });
  97. export class Config {
  98. private rawConfig!: z.infer<typeof environmentSchema>;
  99. constructor() {
  100. this.loadConfig();
  101. }
  102. public loadConfig() {
  103. const loadConfig = (configPath: string) => {
  104. try {
  105. const yamlContent = fs.readFileSync(configPath, "utf8");
  106. const config = yaml.load(yamlContent);
  107. return config;
  108. } catch (error) {
  109. if (error instanceof Error) {
  110. throw new Error(
  111. `Error loading configuration file: ${error.message}`
  112. );
  113. }
  114. throw error;
  115. }
  116. };
  117. let environment: any;
  118. if (fs.existsSync(configFilePath1)) {
  119. environment = loadConfig(configFilePath1);
  120. } else if (fs.existsSync(configFilePath2)) {
  121. environment = loadConfig(configFilePath2);
  122. }
  123. if (!environment) {
  124. const exampleConfigPath = path.join(
  125. __DIRNAME,
  126. "config.example.yml"
  127. );
  128. if (fs.existsSync(exampleConfigPath)) {
  129. try {
  130. const exampleConfigContent = fs.readFileSync(
  131. exampleConfigPath,
  132. "utf8"
  133. );
  134. fs.writeFileSync(
  135. configFilePath1,
  136. exampleConfigContent,
  137. "utf8"
  138. );
  139. environment = loadConfig(configFilePath1);
  140. } catch (error) {
  141. if (error instanceof Error) {
  142. throw new Error(
  143. `Error creating configuration file from example: ${
  144. error.message
  145. }`
  146. );
  147. }
  148. throw error;
  149. }
  150. } else {
  151. throw new Error(
  152. "No configuration file found and no example configuration available"
  153. );
  154. }
  155. }
  156. if (!environment) {
  157. throw new Error("No configuration file found");
  158. }
  159. const parsedConfig = environmentSchema.safeParse(environment);
  160. if (!parsedConfig.success) {
  161. const errors = fromError(parsedConfig.error);
  162. throw new Error(`Invalid configuration file: ${errors}`);
  163. }
  164. const appVersion = loadAppVersion();
  165. if (!appVersion) {
  166. throw new Error("Could not load the application version");
  167. }
  168. process.env.APP_VERSION = appVersion;
  169. process.env.NEXT_PORT = parsedConfig.data.server.next_port.toString();
  170. process.env.SERVER_EXTERNAL_PORT =
  171. parsedConfig.data.server.external_port.toString();
  172. process.env.SERVER_INTERNAL_PORT =
  173. parsedConfig.data.server.internal_port.toString();
  174. process.env.FLAGS_EMAIL_VERIFICATION_REQUIRED = parsedConfig.data.flags
  175. ?.require_email_verification
  176. ? "true"
  177. : "false";
  178. process.env.SESSION_COOKIE_NAME =
  179. parsedConfig.data.server.session_cookie_name;
  180. process.env.RESOURCE_SESSION_COOKIE_NAME =
  181. parsedConfig.data.server.resource_session_cookie_name;
  182. process.env.EMAIL_ENABLED = parsedConfig.data.email ? "true" : "false";
  183. process.env.DISABLE_SIGNUP_WITHOUT_INVITE = parsedConfig.data.flags
  184. ?.disable_signup_without_invite
  185. ? "true"
  186. : "false";
  187. process.env.DISABLE_USER_CREATE_ORG = parsedConfig.data.flags
  188. ?.disable_user_create_org
  189. ? "true"
  190. : "false";
  191. this.rawConfig = parsedConfig.data;
  192. }
  193. public getRawConfig() {
  194. return this.rawConfig;
  195. }
  196. public getBaseDomain(): string {
  197. return this.rawConfig.app.base_domain;
  198. }
  199. }
  200. export const config = new Config();
  201. export default config;