config.js 6.4 KB

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