apps.helpers.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  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 } 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 = (appName: string, form: Record<string, string>) => {
  62. const configFile: AppInfo | null = readJsonFile(`/apps/${appName}/config.json`);
  63. if (!configFile) {
  64. throw new Error(`App ${appName} not found`);
  65. }
  66. const baseEnvFile = readFile('/.env').toString();
  67. let envFile = `${baseEnvFile}\nAPP_PORT=${configFile.port}\n`;
  68. const envMap = getEnvMap(appName);
  69. configFile.form_fields?.forEach((field) => {
  70. const formValue = form[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. writeFile(`/app-data/${appName}/app.env`, envFile);
  87. };
  88. export const getAvailableApps = async (): Promise<string[]> => {
  89. const apps: string[] = [];
  90. const appsDir = readdirSync(`/repos/${config.APPS_REPO_ID}/apps`);
  91. appsDir.forEach((app) => {
  92. if (fileExists(`/repos/${config.APPS_REPO_ID}/apps/${app}/config.json`)) {
  93. const configFile: AppInfo = readJsonFile(`/repos/${config.APPS_REPO_ID}/apps/${app}/config.json`);
  94. if (configFile.available) {
  95. apps.push(app);
  96. }
  97. }
  98. });
  99. return apps;
  100. };
  101. export const getAppInfo = (id: string): AppInfo => {
  102. try {
  103. const repoId = config.APPS_REPO_ID;
  104. if (fileExists(`/apps/${id}/config.json`)) {
  105. const configFile: AppInfo = readJsonFile(`/apps/${id}/config.json`);
  106. configFile.description = readFile(`/apps/${id}/metadata/description.md`).toString();
  107. return configFile;
  108. } else if (fileExists(`/repos/${repoId}`)) {
  109. const configFile: AppInfo = readJsonFile(`/repos/${repoId}/apps/${id}/config.json`);
  110. configFile.description = readFile(`/repos/${repoId}/apps/${id}/metadata/description.md`);
  111. if (configFile.available) {
  112. return configFile;
  113. }
  114. }
  115. throw new Error('No repository found');
  116. } catch (e) {
  117. throw new Error(`Error loading app ${id}`);
  118. }
  119. };
  120. export const getUpdateInfo = async (id: string) => {
  121. const app = await App.findOne({ where: { id } });
  122. if (!app) {
  123. return null;
  124. }
  125. const repoConfig: AppInfo = readJsonFile(`/repos/${config.APPS_REPO_ID}/apps/${id}/config.json`);
  126. return {
  127. current: app.version,
  128. latest: repoConfig.tipi_version,
  129. dockerVersion: repoConfig.version,
  130. };
  131. };