apps.service.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import { createFolder, readFile, readJsonFile } from '../fs/fs.helpers';
  2. import { checkAppRequirements, checkEnvFile, generateEnvFile, getAvailableApps, runAppScript } from './apps.helpers';
  3. import { AppInfo, AppStatusEnum, ListAppsResonse } from './apps.types';
  4. import App from './app.entity';
  5. import logger from '../../config/logger/logger';
  6. const sortApps = (a: AppInfo, b: AppInfo) => a.name.localeCompare(b.name);
  7. const startAllApps = async (): Promise<void> => {
  8. const apps = await App.find({ where: { status: AppStatusEnum.RUNNING } });
  9. await Promise.all(
  10. apps.map(async (app) => {
  11. // Regenerate env file
  12. try {
  13. generateEnvFile(app.id, app.config);
  14. checkEnvFile(app.id);
  15. await App.update({ id: app.id }, { status: AppStatusEnum.STARTING });
  16. await runAppScript(['start', app.id]);
  17. await App.update({ id: app.id }, { status: AppStatusEnum.RUNNING });
  18. } catch (e) {
  19. await App.update({ id: app.id }, { status: AppStatusEnum.STOPPED });
  20. logger.error(e);
  21. }
  22. }),
  23. );
  24. };
  25. const startApp = async (appName: string): Promise<App> => {
  26. let app = await App.findOne({ where: { id: appName } });
  27. if (!app) {
  28. throw new Error(`App ${appName} not found`);
  29. }
  30. // Regenerate env file
  31. generateEnvFile(appName, app.config);
  32. checkEnvFile(appName);
  33. await App.update({ id: appName }, { status: AppStatusEnum.STARTING });
  34. // Run script
  35. try {
  36. await runAppScript(['start', appName]);
  37. await App.update({ id: appName }, { status: AppStatusEnum.RUNNING });
  38. } catch (e) {
  39. await App.update({ id: appName }, { status: AppStatusEnum.STOPPED });
  40. throw e;
  41. }
  42. app = (await App.findOne({ where: { id: appName } })) as App;
  43. return app;
  44. };
  45. const installApp = async (id: string, form: Record<string, string>): Promise<App> => {
  46. let app = await App.findOne({ where: { id } });
  47. if (app) {
  48. await startApp(id);
  49. } else {
  50. const appIsValid = await checkAppRequirements(id);
  51. if (!appIsValid) {
  52. throw new Error(`App ${id} requirements not met`);
  53. }
  54. // Create app folder
  55. createFolder(`/app-data/${id}`);
  56. // Create env file
  57. generateEnvFile(id, form);
  58. app = await App.create({ id, status: AppStatusEnum.INSTALLING, config: form }).save();
  59. // Run script
  60. try {
  61. await runAppScript(['install', id]);
  62. } catch (e) {
  63. await App.delete({ id });
  64. throw e;
  65. }
  66. }
  67. await App.update({ id }, { status: AppStatusEnum.RUNNING });
  68. app = (await App.findOne({ where: { id } })) as App;
  69. return app;
  70. };
  71. const listApps = async (): Promise<ListAppsResonse> => {
  72. const apps: AppInfo[] = getAvailableApps()
  73. .map((app) => {
  74. try {
  75. return readJsonFile(`/apps/${app}/config.json`);
  76. } catch (e) {
  77. return null;
  78. }
  79. })
  80. .filter(Boolean);
  81. apps.forEach((app) => {
  82. app.description = readFile(`/apps/${app.id}/metadata/description.md`);
  83. });
  84. return { apps: apps.sort(sortApps), total: apps.length };
  85. };
  86. const updateAppConfig = async (id: string, form: Record<string, string>): Promise<App> => {
  87. let app = await App.findOne({ where: { id } });
  88. if (!app) {
  89. throw new Error(`App ${id} not found`);
  90. }
  91. generateEnvFile(id, form);
  92. await App.update({ id }, { config: form });
  93. app = (await App.findOne({ where: { id } })) as App;
  94. return app;
  95. };
  96. const stopApp = async (id: string): Promise<App> => {
  97. let app = await App.findOne({ where: { id } });
  98. if (!app) {
  99. throw new Error(`App ${id} not found`);
  100. }
  101. // Run script
  102. await App.update({ id }, { status: AppStatusEnum.STOPPING });
  103. try {
  104. await runAppScript(['stop', id]);
  105. await App.update({ id }, { status: AppStatusEnum.STOPPED });
  106. } catch (e) {
  107. await App.update({ id }, { status: AppStatusEnum.RUNNING });
  108. throw e;
  109. }
  110. app = (await App.findOne({ where: { id } })) as App;
  111. return app;
  112. };
  113. const uninstallApp = async (id: string): Promise<App> => {
  114. let app = await App.findOne({ where: { id } });
  115. if (!app) {
  116. throw new Error(`App ${id} not found`);
  117. }
  118. if (app.status === AppStatusEnum.RUNNING) {
  119. await stopApp(id);
  120. }
  121. await App.update({ id }, { status: AppStatusEnum.UNINSTALLING });
  122. // Run script
  123. try {
  124. await runAppScript(['uninstall', id]);
  125. } catch (e) {
  126. await App.update({ id }, { status: AppStatusEnum.STOPPED });
  127. throw e;
  128. }
  129. await App.delete({ id });
  130. return { id, status: AppStatusEnum.MISSING, config: {} } as App;
  131. };
  132. const getApp = async (id: string): Promise<App> => {
  133. let app = await App.findOne({ where: { id } });
  134. if (!app) {
  135. app = { id, status: AppStatusEnum.MISSING, config: {} } as App;
  136. }
  137. return app;
  138. };
  139. export default { installApp, startApp, listApps, getApp, updateAppConfig, stopApp, uninstallApp, startAllApps };