run-selfhosted.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. /*
  2. * Copyright (C) 2024-present Puter Technologies Inc.
  3. *
  4. * This file is part of Puter.
  5. *
  6. * Puter is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License as published
  8. * by the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. // surrounding_box function
  20. //
  21. // It's really hard to see an error message without using
  22. // the surrounding_box function to highlight its location.
  23. // The implementation of this in packages/backend might not
  24. // work in older versions of node, so we instead re-implement
  25. // it here.
  26. import console from 'node:console';
  27. import process from 'node:process';
  28. const surrounding_box = (col, lines) => {
  29. const lengths = lines.map(line => line.length);
  30. const max_length = Math.max(...lengths);
  31. const c = str => `\x1b[${col}m${str}\x1b[0m`;
  32. const bar = c(Array(max_length + 4).fill('━').join(''));
  33. for ( let i = 0 ; i < lines.length ; i++ ) {
  34. while ( lines[i].length < max_length ) {
  35. lines[i] += ' ';
  36. }
  37. lines[i] = `${c('┃ ')} ${lines[i]} ${c(' ┃')}`;
  38. }
  39. lines.unshift(`${c('┏')}${bar}${c('┓')}`);
  40. lines.push(`${c('┗')}${bar}${c('┛')}`);
  41. };
  42. // node version check
  43. {
  44. // Keeping track of WHY certain versions don't work
  45. const ver_info = [
  46. { under: 14, reasons: ['optional chaining is not available'] },
  47. { under: 16, reasons: ['disk usage package ABI mismatch'] },
  48. ];
  49. const lowest_allowed = Math.max(...ver_info.map(r => r.under));
  50. // ACTUAL VERSION CHECK
  51. const [major, minor] = process.versions.node.split('.').map(Number);
  52. if ( major < lowest_allowed ) {
  53. const lines = [];
  54. lines.push(`Please use a version of Node.js ${lowest_allowed} or newer.`);
  55. lines.push(`Issues with node ${process.versions.node}:`);
  56. // We also show the user the reasons in case they want to know
  57. for ( const { under, reasons } of ver_info ) {
  58. if ( major < under ) {
  59. lines.push(` - ${reasons.join(', ')}`);
  60. }
  61. }
  62. surrounding_box('31;1', lines);
  63. console.error(lines.join('\n'));
  64. process.exit(1);
  65. }
  66. }
  67. // Annoying polyfill for inconsistency in different node versions
  68. if ( ! import.meta.filename ) {
  69. Object.defineProperty(import.meta, 'filename', {
  70. get: () => import.meta.url.slice('file://'.length),
  71. })
  72. }
  73. const main = async () => {
  74. const {
  75. Kernel,
  76. EssentialModules,
  77. DatabaseModule,
  78. LocalDiskStorageModule,
  79. SelfHostedModule,
  80. BroadcastModule,
  81. TestDriversModule,
  82. PuterAIModule,
  83. PuterExecModule,
  84. InternetModule,
  85. ExternalExtrasModule,
  86. MailModule,
  87. ConvertModule,
  88. DevelopmentModule,
  89. FirebaseModule,
  90. DNSModule,
  91. } = (await import('@heyputer/backend')).default;
  92. const k = new Kernel({
  93. entry_path: import.meta.filename
  94. });
  95. for ( const mod of EssentialModules ) {
  96. k.add_module(new mod());
  97. }
  98. k.add_module(new DatabaseModule());
  99. k.add_module(new LocalDiskStorageModule());
  100. k.add_module(new SelfHostedModule());
  101. k.add_module(new BroadcastModule());
  102. k.add_module(new TestDriversModule());
  103. k.add_module(new PuterAIModule());
  104. k.add_module(new PuterExecModule());
  105. k.add_module(new InternetModule());
  106. k.add_module(new ExternalExtrasModule());
  107. k.add_module(new MailModule());
  108. k.add_module(new ConvertModule());
  109. k.add_module(new FirebaseModule());
  110. k.add_module(new DNSModule());
  111. if ( process.env.UNSAFE_PUTER_DEV ) {
  112. k.add_module(new DevelopmentModule());
  113. }
  114. k.boot();
  115. };
  116. const early_init_errors = [
  117. {
  118. text: `Cannot find package '@heyputer/backend'`,
  119. notes: [
  120. 'this usually happens if you forget `npm install`'
  121. ],
  122. suggestions: [
  123. 'try running `npm install`'
  124. ],
  125. technical_notes: [
  126. '@heyputer/backend is in an npm workspace'
  127. ]
  128. },
  129. {
  130. text: `Cannot find package`,
  131. notes: [
  132. 'this usually happens if you forget `npm install`'
  133. ],
  134. suggestions: [
  135. 'try running `npm install`'
  136. ],
  137. },
  138. {
  139. text: 'Cannot write to path',
  140. notes: [
  141. 'this usually happens when /var/puter isn\'t chown\'d to the right UID'
  142. ],
  143. suggestions: [
  144. 'check issue #645 on our github'
  145. ]
  146. }
  147. ];
  148. // null coalescing operator
  149. const nco = (...args) => {
  150. for ( const arg of args ) {
  151. if ( arg !== undefined && arg !== null ) {
  152. return arg;
  153. }
  154. }
  155. return undefined;
  156. }
  157. const _print_error_help = (error_help) => {
  158. const lines = [];
  159. lines.push(nco(error_help.title, error_help.text));
  160. for ( const note of (nco(error_help.notes, [])) ) {
  161. lines.push(`📝 ${note}`)
  162. }
  163. if ( error_help.suggestions ) {
  164. lines.push('Suggestions:');
  165. for ( const suggestion of error_help.suggestions ) {
  166. lines.push(`- ${suggestion}`);
  167. }
  168. }
  169. if ( error_help.technical_notes ) {
  170. lines.push('Technical Notes:');
  171. for ( const note of error_help.technical_notes ) {
  172. lines.push(`- ${note}`);
  173. }
  174. }
  175. surrounding_box('31;1', lines);
  176. console.error(lines.join('\n'));
  177. }
  178. (async () => {
  179. try {
  180. await main();
  181. } catch (e) {
  182. for ( const error_help of early_init_errors ) {
  183. const message = e && e.message;
  184. if ( e.message && e.message.includes(error_help.text) ) {
  185. _print_error_help(error_help);
  186. break;
  187. }
  188. }
  189. throw e;
  190. }
  191. })();