const Path = require('node:path') const os = require('node:os') const fs = require('node:fs') const isPreEmptible = process.env.PREEMPTIBLE === 'TRUE' const CLSI_SERVER_ID = os.hostname().replace('-ctr', '') module.exports = { compileSizeLimit: process.env.COMPILE_SIZE_LIMIT || '7mb', processLifespanLimitMs: parseInt(process.env.PROCESS_LIFE_SPAN_LIMIT_MS) || 60 * 60 * 24 * 1000 * 2, catchErrors: process.env.CATCH_ERRORS === 'true', path: { compilesDir: process.env.CLSI_COMPILES_PATH || Path.resolve(__dirname, '../compiles'), outputDir: process.env.CLSI_OUTPUT_PATH || Path.resolve(__dirname, '../output'), clsiCacheDir: process.env.CLSI_CACHE_PATH || Path.resolve(__dirname, '../cache'), uploadFolder: process.env.CLSI_UPLOAD_PATH || Path.resolve(__dirname, '../uploads'), synctexBaseDir(projectId) { return Path.join(this.compilesDir, projectId) }, }, conversionTimeoutSeconds: parseInt(process.env.CLSI_CONVERSION_TIMEOUT_SECONDS, 10) || 60, pandocImage: process.env.PANDOC_IMAGE || 'quay.io/sharelatex/pandoc:3.9', enablePandocConversions: process.env.ENABLE_PANDOC_CONVERSIONS === 'true', maxUploadSize: 50 * 1024 * 1024, internal: { clsi: { port: 3013, host: process.env.LISTEN_ADDRESS || '127.0.0.1', }, load_balancer_agent: { report_load: process.env.LOAD_BALANCER_AGENT_REPORT_LOAD !== 'false', load_port: 3048, local_port: 3049, allow_maintenance: ( process.env.LOAD_BALANCER_AGENT_ALLOW_MAINTENANCE ?? '' ).toLowerCase() !== 'false', }, }, apis: { clsi: { // Internal requests (used by tests only at the time of writing). url: `http://${process.env.CLSI_HOST || '127.0.0.1'}:3013`, // External url prefix for output files, e.g. for requests via load-balancers. outputUrlPrefix: `${process.env.ZONE ? `/zone/${process.env.ZONE}` : ''}`, clsiServerId: process.env.CLSI_SERVER_ID || CLSI_SERVER_ID, downloadHost: process.env.DOWNLOAD_HOST || 'http://localhost:8080', }, clsiPerf: { host: `${process.env.CLSI_PERF_HOST || '127.0.0.1'}:${ process.env.CLSI_PERF_PORT || '3043' }`, }, clsiCache: { enabled: !!process.env.CLSI_CACHE_INSTANCES, shards: JSON.parse(process.env.CLSI_CACHE_INSTANCES || '[]').filter( ({ zone, readOnly }) => zone === process.env.ZONE && !readOnly ), currentShards: parseInt(process.env.CLSI_CACHE_CURRENT_SHARDS, 10), desiredShards: parseInt(process.env.CLSI_CACHE_DESIRED_SHARDS, 10), reshardFrom: new Date(process.env.CLSI_CACHE_RESHARD_FROM), reshardUntil: new Date(process.env.CLSI_CACHE_RESHARD_UNTIL), }, filestore: { url: process.env.FILESTORE_DOMAIN_OVERRIDE || `http://${process.env.FILESTORE_HOST || '127.0.0.1'}:3009`, }, }, smokeTest: process.env.SMOKE_TEST || false, project_cache_length_ms: 1000 * 60 * 60 * 24, parallelFileDownloads: parseInt(process.env.FILESTORE_PARALLEL_FILE_DOWNLOADS, 10) || 1, filestoreDomainOveride: process.env.FILESTORE_DOMAIN_OVERRIDE, texliveImageNameOveride: process.env.TEX_LIVE_IMAGE_NAME_OVERRIDE, texliveOpenoutAny: process.env.TEXLIVE_OPENOUT_ANY, texliveMaxPrintLine: process.env.TEXLIVE_MAX_PRINT_LINE, enablePdfCaching: process.env.ENABLE_PDF_CACHING === 'true', enablePdfCachingDark: process.env.ENABLE_PDF_CACHING_DARK === 'true', pdfCachingMinChunkSize: parseInt(process.env.PDF_CACHING_MIN_CHUNK_SIZE, 10) || 1024, pdfCachingMaxProcessingTime: parseInt(process.env.PDF_CACHING_MAX_PROCESSING_TIME, 10) || 10 * 1000, pdfCachingEnableWorkerPool: process.env.PDF_CACHING_ENABLE_WORKER_POOL === 'true', pdfCachingWorkerPoolSize: parseInt(process.env.PDF_CACHING_WORKER_POOL_SIZE, 10) || 4, pdfCachingWorkerPoolBackLogLimit: parseInt(process.env.PDF_CACHING_WORKER_POOL_BACK_LOG_LIMIT, 10) || 40, compileConcurrencyLimit: isPreEmptible ? 32 : 64, performanceLogSamplingPercentage: parseFloat(process.env.CLSI_PERFORMANCE_LOG_SAMPLING, 10) || 0, } if (process.env.ALLOWED_COMPILE_GROUPS) { try { module.exports.allowedCompileGroups = process.env.ALLOWED_COMPILE_GROUPS.split(' ') } catch (error) { console.error(error, 'could not apply allowed compile group setting') process.exit(1) } } if ((process.env.DOCKER_RUNNER || process.env.SANDBOXED_COMPILES) === 'true') { if ( !fs.existsSync(Path.join(__dirname, '..', 'app', 'js', 'DockerRunner.js')) ) { console.error( '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' ) process.exit(1) } module.exports.clsi = { dockerRunner: true, docker: { runtime: process.env.DOCKER_RUNTIME, image: process.env.TEXLIVE_IMAGE || process.env.TEX_LIVE_DOCKER_IMAGE || 'quay.io/sharelatex/texlive-full:2017.1', env: { HOME: '/tmp', CLSI: 1, }, socketPath: '/var/run/docker.sock', user: process.env.TEXLIVE_IMAGE_USER || 'tex', }, optimiseInDocker: true, expireProjectAfterIdleMs: 24 * 60 * 60 * 1000, checkProjectsIntervalMs: 10 * 60 * 1000, } try { // Override individual docker settings using path-based keys, e.g.: // compileGroupDockerConfigs = { // priority: { 'HostConfig.CpuShares': 100 } // beta: { 'dotted.path.here', 'value'} // } const compileGroupConfig = JSON.parse( process.env.COMPILE_GROUP_DOCKER_CONFIGS || '{}' ) // Automatically clean up wordcount and synctex containers const defaultCompileGroupConfig = { wordcount: { 'HostConfig.AutoRemove': true }, synctex: { 'HostConfig.AutoRemove': true }, 'synctex-output': { 'HostConfig.AutoRemove': true }, conversions: { 'HostConfig.AutoRemove': true }, } module.exports.clsi.docker.compileGroupConfig = Object.assign( defaultCompileGroupConfig, compileGroupConfig ) } catch (error) { console.error(error, 'could not apply compile group docker configs') process.exit(1) } let seccompProfilePath try { seccompProfilePath = Path.resolve(__dirname, '../seccomp/clsi-profile.json') module.exports.clsi.docker.seccomp_profile = process.env.SECCOMP_PROFILE || JSON.stringify( JSON.parse(require('node:fs').readFileSync(seccompProfilePath)) ) } catch (error) { console.error( error, `could not load seccomp profile from ${seccompProfilePath}` ) process.exit(1) } if (process.env.APPARMOR_PROFILE) { try { module.exports.clsi.docker.apparmor_profile = process.env.APPARMOR_PROFILE } catch (error) { console.error(error, 'could not apply apparmor profile setting') process.exit(1) } } if (process.env.ALLOWED_IMAGES) { try { module.exports.clsi.docker.allowedImages = process.env.ALLOWED_IMAGES.split(' ') } catch (error) { console.error(error, 'could not apply allowed images setting') process.exit(1) } } module.exports.path.synctexBaseDir = () => '/compile' module.exports.path.sandboxedCompilesHostDirCompiles = process.env.SANDBOXED_COMPILES_HOST_DIR_COMPILES || process.env.SANDBOXED_COMPILES_HOST_DIR || process.env.COMPILES_HOST_DIR if (!module.exports.path.sandboxedCompilesHostDirCompiles) { throw new Error( 'SANDBOXED_COMPILES enabled, but SANDBOXED_COMPILES_HOST_DIR_COMPILES not set' ) } module.exports.path.sandboxedCompilesHostDirOutput = process.env.SANDBOXED_COMPILES_HOST_DIR_OUTPUT || process.env.OUTPUT_HOST_DIR if (!module.exports.path.sandboxedCompilesHostDirOutput) { // TODO(das7pad): Enforce in a future major version of Server Pro. // throw new Error( // 'SANDBOXED_COMPILES enabled, but SANDBOXED_COMPILES_HOST_DIR_OUTPUT not set' // ) } }