settings.defaults.cjs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. const Path = require('node:path')
  2. const os = require('node:os')
  3. const fs = require('node:fs')
  4. const isSpotInstance = process.env.PREEMPTIBLE === 'TRUE'
  5. const CLSI_SERVER_ID = os.hostname().replace('-ctr', '')
  6. module.exports = {
  7. compileSizeLimit: process.env.COMPILE_SIZE_LIMIT || '7mb',
  8. processLifespanLimitMs:
  9. parseInt(process.env.PROCESS_LIFE_SPAN_LIMIT_MS) || 60 * 60 * 24 * 1000 * 2,
  10. catchErrors: process.env.CATCH_ERRORS === 'true',
  11. path: {
  12. compilesDir:
  13. process.env.CLSI_COMPILES_PATH || Path.resolve(__dirname, '../compiles'),
  14. outputDir:
  15. process.env.CLSI_OUTPUT_PATH || Path.resolve(__dirname, '../output'),
  16. clsiCacheDir:
  17. process.env.CLSI_CACHE_PATH || Path.resolve(__dirname, '../cache'),
  18. uploadFolder:
  19. process.env.CLSI_UPLOAD_PATH || Path.resolve(__dirname, '../uploads'),
  20. synctexBaseDir(projectId) {
  21. return Path.join(this.compilesDir, projectId)
  22. },
  23. },
  24. conversionTimeoutSeconds:
  25. parseInt(process.env.CLSI_CONVERSION_TIMEOUT_SECONDS, 10) || 60,
  26. pandocImage: process.env.PANDOC_IMAGE || 'quay.io/sharelatex/pandoc:3.9',
  27. enablePandocConversions: process.env.ENABLE_PANDOC_CONVERSIONS === 'true',
  28. pdftocairoImage:
  29. process.env.PDFTOCAIRO_IMAGE || 'quay.io/sharelatex/pdftocairo:24.02',
  30. enablePdfConversions: process.env.ENABLE_PDF_CONVERSIONS === 'true',
  31. maxUploadSize: 50 * 1024 * 1024,
  32. preciousFilePattern: process.env.PRECIOUS_FILE_PATTERN || '',
  33. internal: {
  34. clsi: {
  35. port: 3013,
  36. host: process.env.LISTEN_ADDRESS || '127.0.0.1',
  37. },
  38. load_balancer_agent: {
  39. report_load: process.env.LOAD_BALANCER_AGENT_REPORT_LOAD !== 'false',
  40. load_port: 3048,
  41. local_port: 3049,
  42. allow_maintenance:
  43. (
  44. process.env.LOAD_BALANCER_AGENT_ALLOW_MAINTENANCE ?? ''
  45. ).toLowerCase() !== 'false',
  46. },
  47. },
  48. apis: {
  49. clsi: {
  50. // Internal requests (used by tests only at the time of writing).
  51. url: `http://${process.env.CLSI_HOST || '127.0.0.1'}:3013`,
  52. // External url prefix for output files, e.g. for requests via load-balancers.
  53. outputUrlPrefix: `${process.env.ZONE ? `/zone/${process.env.ZONE}` : ''}`,
  54. clsiServerId: process.env.CLSI_SERVER_ID || CLSI_SERVER_ID,
  55. instanceType: process.env.INSTANCE_TYPE,
  56. zone: process.env.ZONE,
  57. isSpotInstance,
  58. downloadHost: process.env.DOWNLOAD_HOST || 'http://localhost:8080',
  59. },
  60. clsiPerf: {
  61. host: `${process.env.CLSI_PERF_HOST || '127.0.0.1'}:${
  62. process.env.CLSI_PERF_PORT || '3043'
  63. }`,
  64. },
  65. clsiCache: {
  66. enabled: !!process.env.CLSI_CACHE_INSTANCES,
  67. shards: JSON.parse(process.env.CLSI_CACHE_INSTANCES || '[]').filter(
  68. ({ zone, readOnly }) => zone === process.env.ZONE && !readOnly
  69. ),
  70. currentShards: parseInt(process.env.CLSI_CACHE_CURRENT_SHARDS, 10),
  71. desiredShards: parseInt(process.env.CLSI_CACHE_DESIRED_SHARDS, 10),
  72. reshardFrom: new Date(process.env.CLSI_CACHE_RESHARD_FROM),
  73. reshardUntil: new Date(process.env.CLSI_CACHE_RESHARD_UNTIL),
  74. },
  75. filestore: {
  76. url:
  77. process.env.FILESTORE_DOMAIN_OVERRIDE ||
  78. `http://${process.env.FILESTORE_HOST || '127.0.0.1'}:3009`,
  79. },
  80. },
  81. smokeTest: process.env.SMOKE_TEST || false,
  82. project_cache_length_ms: 1000 * 60 * 60 * 24,
  83. parallelFileDownloads:
  84. parseInt(process.env.FILESTORE_PARALLEL_FILE_DOWNLOADS, 10) || 1,
  85. filestoreDomainOveride: process.env.FILESTORE_DOMAIN_OVERRIDE,
  86. texliveImageNameOveride: process.env.TEX_LIVE_IMAGE_NAME_OVERRIDE,
  87. texliveOpenoutAny: process.env.TEXLIVE_OPENOUT_ANY,
  88. texliveMaxPrintLine: process.env.TEXLIVE_MAX_PRINT_LINE,
  89. enablePdfCaching: process.env.ENABLE_PDF_CACHING === 'true',
  90. enablePdfCachingDark: process.env.ENABLE_PDF_CACHING_DARK === 'true',
  91. pdfCachingMinChunkSize:
  92. parseInt(process.env.PDF_CACHING_MIN_CHUNK_SIZE, 10) || 1024,
  93. pdfCachingMaxProcessingTime:
  94. parseInt(process.env.PDF_CACHING_MAX_PROCESSING_TIME, 10) || 10 * 1000,
  95. pdfCachingEnableWorkerPool:
  96. process.env.PDF_CACHING_ENABLE_WORKER_POOL === 'true',
  97. pdfCachingWorkerPoolSize:
  98. parseInt(process.env.PDF_CACHING_WORKER_POOL_SIZE, 10) || 4,
  99. pdfCachingWorkerPoolBackLogLimit:
  100. parseInt(process.env.PDF_CACHING_WORKER_POOL_BACK_LOG_LIMIT, 10) || 40,
  101. compileConcurrencyLimit: isSpotInstance ? 32 : 64,
  102. performanceLogSamplingPercentage:
  103. parseFloat(process.env.CLSI_PERFORMANCE_LOG_SAMPLING, 10) || 0,
  104. }
  105. if (process.env.ALLOWED_COMPILE_GROUPS) {
  106. try {
  107. module.exports.allowedCompileGroups =
  108. process.env.ALLOWED_COMPILE_GROUPS.split(' ')
  109. } catch (error) {
  110. console.error(error, 'could not apply allowed compile group setting')
  111. process.exit(1)
  112. }
  113. }
  114. if ((process.env.DOCKER_RUNNER || process.env.SANDBOXED_COMPILES) === 'true') {
  115. if (
  116. !fs.existsSync(Path.join(__dirname, '..', 'app', 'js', 'DockerRunner.js'))
  117. ) {
  118. console.error(
  119. 'Sandboxed compiles are only available with Overleaf Server Pro. Compare Server Pro with Community Edition here: https://docs.overleaf.com/on-premises/welcome/server-pro-vs.-community-edition'
  120. )
  121. process.exit(1)
  122. }
  123. module.exports.clsi = {
  124. dockerRunner: true,
  125. docker: {
  126. runtime: process.env.DOCKER_RUNTIME,
  127. image:
  128. process.env.TEXLIVE_IMAGE ||
  129. process.env.TEX_LIVE_DOCKER_IMAGE ||
  130. 'quay.io/sharelatex/texlive-full:2017.1',
  131. env: {
  132. HOME: '/tmp',
  133. CLSI: 1,
  134. },
  135. socketPath: '/var/run/docker.sock',
  136. user: process.env.TEXLIVE_IMAGE_USER || 'tex',
  137. },
  138. optimiseInDocker: true,
  139. expireProjectAfterIdleMs: 24 * 60 * 60 * 1000,
  140. checkProjectsIntervalMs: 10 * 60 * 1000,
  141. }
  142. try {
  143. // Override individual docker settings using path-based keys, e.g.:
  144. // compileGroupDockerConfigs = {
  145. // priority: { 'HostConfig.CpuShares': 100 }
  146. // beta: { 'dotted.path.here', 'value'}
  147. // }
  148. const compileGroupConfig = JSON.parse(
  149. process.env.COMPILE_GROUP_DOCKER_CONFIGS || '{}'
  150. )
  151. // Automatically clean up wordcount and synctex containers
  152. const defaultCompileGroupConfig = {
  153. wordcount: { 'HostConfig.AutoRemove': true },
  154. synctex: { 'HostConfig.AutoRemove': true },
  155. 'synctex-output': { 'HostConfig.AutoRemove': true },
  156. conversions: { 'HostConfig.AutoRemove': true },
  157. }
  158. module.exports.clsi.docker.compileGroupConfig = Object.assign(
  159. defaultCompileGroupConfig,
  160. compileGroupConfig
  161. )
  162. } catch (error) {
  163. console.error(error, 'could not apply compile group docker configs')
  164. process.exit(1)
  165. }
  166. let seccompProfilePath
  167. try {
  168. seccompProfilePath = Path.resolve(__dirname, '../seccomp/clsi-profile.json')
  169. module.exports.clsi.docker.seccomp_profile =
  170. process.env.SECCOMP_PROFILE ||
  171. JSON.stringify(
  172. JSON.parse(require('node:fs').readFileSync(seccompProfilePath))
  173. )
  174. } catch (error) {
  175. console.error(
  176. error,
  177. `could not load seccomp profile from ${seccompProfilePath}`
  178. )
  179. process.exit(1)
  180. }
  181. if (process.env.APPARMOR_PROFILE) {
  182. try {
  183. module.exports.clsi.docker.apparmor_profile = process.env.APPARMOR_PROFILE
  184. } catch (error) {
  185. console.error(error, 'could not apply apparmor profile setting')
  186. process.exit(1)
  187. }
  188. }
  189. if (process.env.NEW_APPARMOR_PROFILE) {
  190. try {
  191. module.exports.clsi.docker.new_apparmor_profile =
  192. process.env.NEW_APPARMOR_PROFILE
  193. } catch (error) {
  194. console.error(error, 'could not apply new apparmor profile setting')
  195. process.exit(1)
  196. }
  197. }
  198. if (process.env.ALLOWED_IMAGES) {
  199. try {
  200. module.exports.clsi.docker.allowedImages =
  201. process.env.ALLOWED_IMAGES.split(' ')
  202. } catch (error) {
  203. console.error(error, 'could not apply allowed images setting')
  204. process.exit(1)
  205. }
  206. }
  207. module.exports.path.synctexBaseDir = () => '/compile'
  208. module.exports.path.sandboxedCompilesHostDirCompiles =
  209. process.env.SANDBOXED_COMPILES_HOST_DIR_COMPILES ||
  210. process.env.SANDBOXED_COMPILES_HOST_DIR ||
  211. process.env.COMPILES_HOST_DIR
  212. if (!module.exports.path.sandboxedCompilesHostDirCompiles) {
  213. throw new Error(
  214. 'SANDBOXED_COMPILES enabled, but SANDBOXED_COMPILES_HOST_DIR_COMPILES not set'
  215. )
  216. }
  217. module.exports.path.sandboxedCompilesHostDirOutput =
  218. process.env.SANDBOXED_COMPILES_HOST_DIR_OUTPUT ||
  219. process.env.OUTPUT_HOST_DIR
  220. if (!module.exports.path.sandboxedCompilesHostDirOutput) {
  221. // TODO(das7pad): Enforce in a future major version of Server Pro.
  222. // throw new Error(
  223. // 'SANDBOXED_COMPILES enabled, but SANDBOXED_COMPILES_HOST_DIR_OUTPUT not set'
  224. // )
  225. }
  226. }