config.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. import fs from "node:fs";
  2. import NodeRSA from "node-rsa";
  3. import { global as logger } from "../logger.js";
  4. const keysFile = '/data/keys.json';
  5. const mysqlEngine = 'mysql2';
  6. const postgresEngine = 'pg';
  7. const sqliteClientName = 'better-sqlite3';
  8. // Not used for new setups anymore but may exist in legacy setups
  9. const legacySqliteClientName = 'sqlite3';
  10. let instance = null;
  11. // 1. Load from config file first (not recommended anymore)
  12. // 2. Use config env variables next
  13. const configure = () => {
  14. const filename = `${process.env.NODE_CONFIG_DIR || "./config"}/${process.env.NODE_ENV || "default"}.json`;
  15. if (fs.existsSync(filename)) {
  16. let configData;
  17. try {
  18. // Load this json synchronously
  19. const rawData = fs.readFileSync(filename);
  20. configData = JSON.parse(rawData);
  21. } catch (_) {
  22. // do nothing
  23. }
  24. if (configData?.database) {
  25. logger.info(`Using configuration from file: ${filename}`);
  26. // Migrate those who have "mysql" engine to "mysql2"
  27. if (configData.database.engine === "mysql") {
  28. configData.database.engine = mysqlEngine;
  29. }
  30. instance = configData;
  31. instance.keys = getKeys();
  32. return;
  33. }
  34. }
  35. const toBool = (v) => /^(1|true|yes|on)$/i.test((v || '').trim());
  36. const envMysqlHost = process.env.DB_MYSQL_HOST || null;
  37. const envMysqlUser = process.env.DB_MYSQL_USER || null;
  38. const envMysqlName = process.env.DB_MYSQL_NAME || null;
  39. const envMysqlSSL = toBool(process.env.DB_MYSQL_SSL);
  40. const envMysqlSSLRejectUnauthorized = process.env.DB_MYSQL_SSL_REJECT_UNAUTHORIZED === undefined ? true : toBool(process.env.DB_MYSQL_SSL_REJECT_UNAUTHORIZED);
  41. const envMysqlSSLVerifyIdentity = process.env.DB_MYSQL_SSL_VERIFY_IDENTITY === undefined ? true : toBool(process.env.DB_MYSQL_SSL_VERIFY_IDENTITY);
  42. if (envMysqlHost && envMysqlUser && envMysqlName) {
  43. // we have enough mysql creds to go with mysql
  44. logger.info("Using MySQL configuration");
  45. instance = {
  46. database: {
  47. engine: mysqlEngine,
  48. host: envMysqlHost,
  49. port: process.env.DB_MYSQL_PORT || 3306,
  50. user: envMysqlUser,
  51. password: process.env.DB_MYSQL_PASSWORD,
  52. name: envMysqlName,
  53. ssl: envMysqlSSL ? { rejectUnauthorized: envMysqlSSLRejectUnauthorized, verifyIdentity: envMysqlSSLVerifyIdentity } : false,
  54. },
  55. keys: getKeys(),
  56. };
  57. return;
  58. }
  59. const envPostgresHost = process.env.DB_POSTGRES_HOST || null;
  60. const envPostgresUser = process.env.DB_POSTGRES_USER || null;
  61. const envPostgresName = process.env.DB_POSTGRES_NAME || null;
  62. if (envPostgresHost && envPostgresUser && envPostgresName) {
  63. // we have enough postgres creds to go with postgres
  64. logger.info("Using Postgres configuration");
  65. instance = {
  66. database: {
  67. engine: postgresEngine,
  68. host: envPostgresHost,
  69. port: process.env.DB_POSTGRES_PORT || 5432,
  70. user: envPostgresUser,
  71. password: process.env.DB_POSTGRES_PASSWORD,
  72. name: envPostgresName,
  73. },
  74. keys: getKeys(),
  75. };
  76. return;
  77. }
  78. const envSqliteFile = process.env.DB_SQLITE_FILE || "/data/database.sqlite";
  79. logger.info(`Using Sqlite: ${envSqliteFile}`);
  80. instance = {
  81. database: {
  82. engine: "knex-native",
  83. knex: {
  84. client: sqliteClientName,
  85. connection: {
  86. filename: envSqliteFile,
  87. },
  88. useNullAsDefault: true,
  89. },
  90. },
  91. keys: getKeys(),
  92. };
  93. };
  94. const getKeys = () => {
  95. // Get keys from file
  96. if (isDebugMode()) {
  97. logger.debug("Checking for keys file:", keysFile);
  98. }
  99. if (!fs.existsSync(keysFile)) {
  100. generateKeys();
  101. } else if (process.env.DEBUG) {
  102. logger.info("Keys file exists OK");
  103. }
  104. try {
  105. // Load this json keysFile synchronously and return the json object
  106. const rawData = fs.readFileSync(keysFile);
  107. return JSON.parse(rawData);
  108. } catch (err) {
  109. logger.error(`Could not read JWT key pair from config file: ${keysFile}`, err);
  110. process.exit(1);
  111. }
  112. };
  113. const generateKeys = () => {
  114. logger.info("Creating a new JWT key pair...");
  115. // Now create the keys and save them in the config.
  116. const key = new NodeRSA({ b: 2048 });
  117. key.generateKeyPair();
  118. const keys = {
  119. key: key.exportKey("private").toString(),
  120. pub: key.exportKey("public").toString(),
  121. };
  122. // Write keys config
  123. try {
  124. fs.writeFileSync(keysFile, JSON.stringify(keys, null, 2));
  125. } catch (err) {
  126. logger.error(`Could not write JWT key pair to config file: ${keysFile}: ${err.message}`);
  127. process.exit(1);
  128. }
  129. logger.info(`Wrote JWT key pair to config file: ${keysFile}`);
  130. };
  131. /**
  132. *
  133. * @param {string} key ie: 'database' or 'database.engine'
  134. * @returns {boolean}
  135. */
  136. const configHas = (key) => {
  137. instance === null && configure();
  138. const keys = key.split(".");
  139. let level = instance;
  140. let has = true;
  141. keys.forEach((keyItem) => {
  142. if (typeof level[keyItem] === "undefined") {
  143. has = false;
  144. } else {
  145. level = level[keyItem];
  146. }
  147. });
  148. return has;
  149. };
  150. /**
  151. * Gets a specific key from the top level
  152. *
  153. * @param {string} key
  154. * @returns {*}
  155. */
  156. const configGet = (key) => {
  157. instance === null && configure();
  158. if (key && typeof instance[key] !== "undefined") {
  159. return instance[key];
  160. }
  161. return instance;
  162. };
  163. /**
  164. * Is this a sqlite configuration?
  165. *
  166. * @returns {boolean}
  167. */
  168. const isSqlite = () => {
  169. instance === null && configure();
  170. return instance.database.knex && [sqliteClientName, legacySqliteClientName].includes(instance.database.knex.client);
  171. };
  172. /**
  173. * Is this a mysql configuration?
  174. *
  175. * @returns {boolean}
  176. */
  177. const isMysql = () => {
  178. instance === null && configure();
  179. return instance.database.engine === mysqlEngine;
  180. };
  181. /**
  182. * Is this a postgres configuration?
  183. *
  184. * @returns {boolean}
  185. */
  186. const isPostgres = () => {
  187. instance === null && configure();
  188. return instance.database.engine === postgresEngine;
  189. };
  190. /**
  191. * Are we running in debug mdoe?
  192. *
  193. * @returns {boolean}
  194. */
  195. const isDebugMode = () => !!process.env.DEBUG;
  196. /**
  197. * Are we running in CI?
  198. *
  199. * @returns {boolean}
  200. */
  201. const isCI = () => process.env.CI === 'true' && process.env.DEBUG === 'true';
  202. /**
  203. * Returns a public key
  204. *
  205. * @returns {string}
  206. */
  207. const getPublicKey = () => {
  208. instance === null && configure();
  209. return instance.keys.pub;
  210. };
  211. /**
  212. * Returns a private key
  213. *
  214. * @returns {string}
  215. */
  216. const getPrivateKey = () => {
  217. instance === null && configure();
  218. return instance.keys.key;
  219. };
  220. /**
  221. * @returns {boolean}
  222. */
  223. const useLetsencryptStaging = () => !!process.env.LE_STAGING;
  224. /**
  225. * @returns {string|null}
  226. */
  227. const useLetsencryptServer = () => {
  228. if (process.env.LE_SERVER) {
  229. return process.env.LE_SERVER;
  230. }
  231. return null;
  232. };
  233. export { isCI, configHas, configGet, isSqlite, isMysql, isPostgres, isDebugMode, getPrivateKey, getPublicKey, useLetsencryptStaging, useLetsencryptServer };