apps.helpers.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import portUsed from 'tcp-port-used';
  2. import { fileExists, getSeed, readdirSync, readFile, readJsonFile, runScript, writeFile } from '../fs/fs.helpers';
  3. import InternalIp from 'internal-ip';
  4. import crypto from 'crypto';
  5. import config from '../../config';
  6. import { AppInfo, AppStatusEnum } from './apps.types';
  7. import logger from '../../config/logger/logger';
  8. import App from './app.entity';
  9. export const checkAppRequirements = async (appName: string) => {
  10. let valid = true;
  11. const configFile: AppInfo | null = readJsonFile(`/repos/${config.APPS_REPO_ID}/apps/${appName}/config.json`);
  12. if (!configFile) {
  13. throw new Error(`App ${appName} not found`);
  14. }
  15. if (configFile?.requirements?.ports) {
  16. for (const port of configFile.requirements.ports) {
  17. const ip = await InternalIp.v4();
  18. const used = await portUsed.check(port, ip);
  19. if (used) valid = false;
  20. }
  21. }
  22. return valid;
  23. };
  24. export const getEnvMap = (appName: string): Map<string, string> => {
  25. const envFile = readFile(`/app-data/${appName}/app.env`).toString();
  26. const envVars = envFile.split('\n');
  27. const envVarsMap = new Map<string, string>();
  28. envVars.forEach((envVar) => {
  29. const [key, value] = envVar.split('=');
  30. envVarsMap.set(key, value);
  31. });
  32. return envVarsMap;
  33. };
  34. export const checkEnvFile = (appName: string) => {
  35. const configFile: AppInfo | null = readJsonFile(`/apps/${appName}/config.json`);
  36. const envMap = getEnvMap(appName);
  37. configFile?.form_fields?.forEach((field) => {
  38. const envVar = field.env_variable;
  39. const envVarValue = envMap.get(envVar);
  40. if (!envVarValue && field.required) {
  41. throw new Error('New info needed. App config needs to be updated');
  42. }
  43. });
  44. };
  45. export const runAppScript = async (params: string[]): Promise<void> => {
  46. return new Promise((resolve, reject) => {
  47. runScript('/scripts/app.sh', [...params, config.ROOT_FOLDER_HOST, config.APPS_REPO_ID], (err: string) => {
  48. if (err) {
  49. logger.error(err);
  50. reject(err);
  51. }
  52. resolve();
  53. });
  54. });
  55. };
  56. const getEntropy = (name: string, length: number) => {
  57. const hash = crypto.createHash('sha256');
  58. hash.update(name + getSeed());
  59. return hash.digest('hex').substring(0, length);
  60. };
  61. export const generateEnvFile = (app: App) => {
  62. const configFile: AppInfo | null = readJsonFile(`/apps/${app.id}/config.json`);
  63. if (!configFile) {
  64. throw new Error(`App ${app.id} not found`);
  65. }
  66. const baseEnvFile = readFile('/.env').toString();
  67. let envFile = `${baseEnvFile}\nAPP_PORT=${configFile.port}\n`;
  68. const envMap = getEnvMap(app.id);
  69. configFile.form_fields?.forEach((field) => {
  70. const formValue = app.config[field.env_variable];
  71. const envVar = field.env_variable;
  72. if (formValue) {
  73. envFile += `${envVar}=${formValue}\n`;
  74. } else if (field.type === 'random') {
  75. if (envMap.has(envVar)) {
  76. envFile += `${envVar}=${envMap.get(envVar)}\n`;
  77. } else {
  78. const length = field.min || 32;
  79. const randomString = getEntropy(field.env_variable, length);
  80. envFile += `${envVar}=${randomString}\n`;
  81. }
  82. } else if (field.required) {
  83. throw new Error(`Variable ${field.env_variable} is required`);
  84. }
  85. });
  86. if (app.exposed && app.domain) {
  87. envFile += 'APP_EXPOSED=true\n';
  88. envFile += `APP_DOMAIN=${app.domain}\n`;
  89. envFile += 'APP_PROTOCOL=https\n';
  90. } else {
  91. envFile += `APP_DOMAIN=${config.INTERNAL_IP}:${configFile.port}\n`;
  92. }
  93. writeFile(`/app-data/${app.id}/app.env`, envFile);
  94. };
  95. export const getAvailableApps = async (): Promise<string[]> => {
  96. const apps: string[] = [];
  97. const appsDir = readdirSync(`/repos/${config.APPS_REPO_ID}/apps`);
  98. appsDir.forEach((app) => {
  99. if (fileExists(`/repos/${config.APPS_REPO_ID}/apps/${app}/config.json`)) {
  100. const configFile: AppInfo = readJsonFile(`/repos/${config.APPS_REPO_ID}/apps/${app}/config.json`);
  101. if (configFile.available) {
  102. apps.push(app);
  103. }
  104. }
  105. });
  106. return apps;
  107. };
  108. export const getAppInfo = (id: string, status?: AppStatusEnum): AppInfo | null => {
  109. try {
  110. const repoId = config.APPS_REPO_ID;
  111. // Check if app is installed
  112. const installed = typeof status !== 'undefined' && status !== AppStatusEnum.MISSING;
  113. if (installed && fileExists(`/apps/${id}/config.json`)) {
  114. const configFile: AppInfo = readJsonFile(`/apps/${id}/config.json`);
  115. configFile.description = readFile(`/apps/${id}/metadata/description.md`).toString();
  116. return configFile;
  117. } else if (fileExists(`/repos/${repoId}/apps/${id}/config.json`)) {
  118. const configFile: AppInfo = readJsonFile(`/repos/${repoId}/apps/${id}/config.json`);
  119. configFile.description = readFile(`/repos/${repoId}/apps/${id}/metadata/description.md`);
  120. if (configFile.available) {
  121. return configFile;
  122. }
  123. }
  124. return null;
  125. } catch (e) {
  126. console.error(e);
  127. throw new Error(`Error loading app ${id}`);
  128. }
  129. };
  130. export const getUpdateInfo = async (id: string) => {
  131. const app = await App.findOne({ where: { id } });
  132. if (!app) {
  133. return null;
  134. }
  135. const repoConfig: AppInfo = readJsonFile(`/repos/${config.APPS_REPO_ID}/apps/${id}/config.json`);
  136. return {
  137. current: app.version,
  138. latest: repoConfig.tipi_version,
  139. dockerVersion: repoConfig.version,
  140. };
  141. };