config.js 6.2 KB

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