+ Key Features • + Wiki • + Server Pro • + Contributing • + Mailing List • + Authors • + License +
+ + ++ Figure 1: A screenshot of Overleaf Server Pro's comments and tracked changes features. +
+ +## Key Features + +[Overleaf](https://www.overleaf.com) is an open-source online real-time collaborative LaTeX editor. We run a hosted version at [www.overleaf.com](https://www.overleaf.com), but you can also run your own local version, and contribute to the development of Overleaf. + +*[If you want help installing and maintaining Overleaf in your lab or workplace, we offer an officially supported version called Overleaf Server Pro. It also comes with extra security and admin features. Click here to find out more!](https://www.overleaf.com/for/enterprises)* + +## Keeping up to date + +Sign up to the [mailing list](https://mailchi.mp/overleaf.com/community-edition-and-server-pro) to get updates on Overleaf Releases and development + +## Installation + +We have detailed installation instructions in our wiki: + +* [Overleaf Quick Start Guide](https://github.com/overleaf/overleaf/wiki/Quick-Start-Guide) + +## Upgrading + +If you are upgrading from a previous version of Overleaf, please see the [Release Notes section on the Wiki](https://github.com/overleaf/overleaf/wiki/Home) for all of the versions between your current version and the version you are upgrading to. + +## Other repositories + +This repository does not contain any code. It acts a wrapper and toolkit for managing the many different Overleaf services. These each run as their own Node.js process and have their own GitHub repository. + +| Service | Description | +| ------- | ----------- | +| **[web](https://github.com/overleaf/web)** | The front facing web server that serves all the HTML pages, CSS and JavaScript to the client. Also contains a lot of logic around creating and editing projects, and account management. | +| **[document-updater](https://github.com/overleaf/document-updater)** | Processes updates that come in from the editor when users modify documents. Ensures that the updates are applied in the right order, and that only one operation is modifying the document at a time. Also caches the documents in redis for very fast but persistent modifications. | +| **[CLSI](https://github.com/overleaf/clsi)** | The Common LaTeX Service Interface (CLSI) which provides an API for compiling LaTeX documents. | +| **[docstore](https://github.com/overleaf/docstore)** | An API for performing CRUD (Create, Read, Update and Delete) operations on text files stored in Overleaf. | +| **[real-time](https://github.com/overleaf/real-time)** | The websocket process clients connect to. | +| **[filestore](https://github.com/overleaf/filestore)** | An API for performing CRUD (Create, Read, Update and Delete) operations on binary files (like images) stored in Overleaf. | +| **[track-changes](https://github.com/overleaf/track-changes)** | An API for compressing and storing the updates applied to a document, and then rendering a diff of the changes between any two time points. | +| **[chat](https://github.com/overleaf/chat)** | The backend API for storing and fetching chat messages. | +| **[spelling](https://github.com/overleaf/spelling)** | An API for running server-side spelling checking on Overleaf documents. | + +## Overleaf Docker Image + +This repo contains two dockerfiles, `Dockerfile-base`, which builds the +`sharelatex/sharelatex-base` image, and `Dockerfile` which builds the +`sharelatex/sharelatex` (or "community") image. + +The Base image generally contains the basic dependencies like `wget` and +`aspell`, plus `texlive`. We split this out because it's a pretty heavy set of +dependencies, and it's nice to not have to rebuild all of that every time. + +The `sharelatex/sharelatex` image extends the base image and adds the actual Overleaf code +and services. + +Use `make build-base` and `make build-community` to build these images. + +We use the [Phusion base-image](https://github.com/phusion/baseimage-docker) +(which is extended by our `base` image) to provide us with a VM-like container +in which to run the Overleaf services. Baseimage uses the `runit` service +manager to manage services, and we add our init-scripts from the `./runit` +folder. + + +## Contributing + +Please see the [CONTRIBUTING](https://github.com/overleaf/overleaf/blob/master/CONTRIBUTING.md) file for information on contributing to the development of Overleaf. See [our wiki](https://github.com/overleaf/overleaf/wiki/Developer-Guidelines) for information on setting up a development environment and how to recompile and run Overleaf after modifications. + +## Authors + +[The Overleaf Team](https://www.overleaf.com/about) + +## License + +The code in this repository is released under the GNU AFFERO GENERAL PUBLIC LICENSE, version 3. A copy can be found in the `LICENSE` file. + +Copyright (c) Overleaf, 2014-2019. diff --git a/server-ce/bin/grunt b/server-ce/bin/grunt new file mode 100755 index 0000000000..03f89b52c7 --- /dev/null +++ b/server-ce/bin/grunt @@ -0,0 +1,31 @@ +#!/bin/bash +# Thin wrapper on old grunt tasks to ease migrating. + +set -e +TASK="$1" +shift 1 + +cd /var/www/sharelatex/web/modules/server-ce-scripts/scripts + +case "$TASK" in + user:create-admin) + node create-user --admin "$@" + ;; + + user:delete) + node delete-user "$@" + ;; + + check:mongo) + node check-mongodb + ;; + + check:redis) + node check-redis + ;; + + *) + echo "Unknown task $TASK" + exit 1 + ;; +esac diff --git a/server-ce/doc/logo.png b/server-ce/doc/logo.png new file mode 100644 index 0000000000..106926b095 Binary files /dev/null and b/server-ce/doc/logo.png differ diff --git a/server-ce/doc/screenshot.png b/server-ce/doc/screenshot.png new file mode 100644 index 0000000000..f126ce5137 Binary files /dev/null and b/server-ce/doc/screenshot.png differ diff --git a/server-ce/docker-compose.debug.yml b/server-ce/docker-compose.debug.yml new file mode 100644 index 0000000000..f6f5657c19 --- /dev/null +++ b/server-ce/docker-compose.debug.yml @@ -0,0 +1,20 @@ +version: '2.2' +services: + sharelatex: + ports: + - 40000:40000 + - 30150:30150 + - 30120:30120 + - 30050:30050 + - 30420:30420 + - 30030:30030 + - 30160:30160 + - 30360:30360 + - 30130:30130 + - 30100:30100 + + # Server Pro + - 30070:30070 + - 30400:30400 + environment: + DEBUG_NODE: 'true' diff --git a/server-ce/docker-compose.yml b/server-ce/docker-compose.yml new file mode 100644 index 0000000000..58240a5687 --- /dev/null +++ b/server-ce/docker-compose.yml @@ -0,0 +1,148 @@ +version: '2.2' +services: + sharelatex: + restart: always + # Server Pro users: + # image: quay.io/sharelatex/sharelatex-pro + image: sharelatex/sharelatex + container_name: sharelatex + depends_on: + mongo: + condition: service_healthy + redis: + condition: service_started + ports: + - 80:80 + links: + - mongo + - redis + volumes: + - ~/sharelatex_data:/var/lib/sharelatex + ######################################################################## + #### Server Pro: Uncomment the following line to mount the docker #### + #### socket, required for Sibling Containers to work #### + ######################################################################## + # - /var/run/docker.sock:/var/run/docker.sock + environment: + + SHARELATEX_APP_NAME: Overleaf Community Edition + + SHARELATEX_MONGO_URL: mongodb://mongo/sharelatex + + # Same property, unfortunately with different names in + # different locations + SHARELATEX_REDIS_HOST: redis + REDIS_HOST: redis + + ENABLED_LINKED_FILE_TYPES: 'url,project_file' + + # Enables Thumbnail generation using ImageMagick + ENABLE_CONVERSIONS: 'true' + + # Disables email confirmation requirement + EMAIL_CONFIRMATION_DISABLED: 'true' + + # temporary fix for LuaLaTex compiles + # see https://github.com/overleaf/overleaf/issues/695 + TEXMFVAR: /var/lib/sharelatex/tmp/texmf-var + + ## Set for SSL via nginx-proxy + #VIRTUAL_HOST: 103.112.212.22 + + # SHARELATEX_SITE_URL: http://sharelatex.mydomain.com + # SHARELATEX_NAV_TITLE: Our ShareLaTeX Instance + # SHARELATEX_HEADER_IMAGE_URL: http://somewhere.com/mylogo.png + # SHARELATEX_ADMIN_EMAIL: support@it.com + + # SHARELATEX_LEFT_FOOTER: '[{"text": "Powered by ShareLaTeX 2016"},{"text": "Another page I want to link to can be found here"} ]' + # SHARELATEX_RIGHT_FOOTER: '[{"text": "Hello I am on the Right"} ]' + + # SHARELATEX_EMAIL_FROM_ADDRESS: "team@sharelatex.com" + + # SHARELATEX_EMAIL_AWS_SES_ACCESS_KEY_ID: + # SHARELATEX_EMAIL_AWS_SES_SECRET_KEY: + + # SHARELATEX_EMAIL_SMTP_HOST: smtp.mydomain.com + # SHARELATEX_EMAIL_SMTP_PORT: 587 + # SHARELATEX_EMAIL_SMTP_SECURE: false + # SHARELATEX_EMAIL_SMTP_USER: + # SHARELATEX_EMAIL_SMTP_PASS: + # SHARELATEX_EMAIL_SMTP_TLS_REJECT_UNAUTH: true + # SHARELATEX_EMAIL_SMTP_IGNORE_TLS: false + # SHARELATEX_EMAIL_SMTP_NAME: '127.0.0.1' + # SHARELATEX_EMAIL_SMTP_LOGGER: true + # SHARELATEX_CUSTOM_EMAIL_FOOTER: "This system is run by department x" + + ################ + ## Server Pro ## + ################ + + # SANDBOXED_COMPILES: 'true' + + # SANDBOXED_COMPILES_SIBLING_CONTAINERS: 'true' + # SANDBOXED_COMPILES_HOST_DIR: '/var/sharelatex_data/data/compiles' + # SYNCTEX_BIN_HOST_PATH: '/var/sharelatex_data/bin/synctex' + + # DOCKER_RUNNER: 'false' + + ## Works with test LDAP server shown at bottom of docker compose + # SHARELATEX_LDAP_URL: 'ldap://ldap:389' + # SHARELATEX_LDAP_SEARCH_BASE: 'ou=people,dc=planetexpress,dc=com' + # SHARELATEX_LDAP_SEARCH_FILTER: '(uid={{username}})' + # SHARELATEX_LDAP_BIND_DN: 'cn=admin,dc=planetexpress,dc=com' + # SHARELATEX_LDAP_BIND_CREDENTIALS: 'GoodNewsEveryone' + # SHARELATEX_LDAP_EMAIL_ATT: 'mail' + # SHARELATEX_LDAP_NAME_ATT: 'cn' + # SHARELATEX_LDAP_LAST_NAME_ATT: 'sn' + # SHARELATEX_LDAP_UPDATE_USER_DETAILS_ON_LOGIN: 'true' + + # SHARELATEX_TEMPLATES_USER_ID: "578773160210479700917ee5" + # SHARELATEX_NEW_PROJECT_TEMPLATE_LINKS: '[ {"name":"All Templates","url":"/templates/all"}]' + + + # SHARELATEX_PROXY_LEARN: "true" + + mongo: + restart: always + image: mongo:4.0 + container_name: mongo + expose: + - 27017 + volumes: + - ~/mongo_data:/data/db + healthcheck: + test: echo 'db.stats().ok' | mongo localhost:27017/test --quiet + interval: 10s + timeout: 10s + retries: 5 + + redis: + restart: always + image: redis:5 + container_name: redis + expose: + - 6379 + volumes: + - ~/redis_data:/data + + # ldap: + # restart: always + # image: rroemhild/test-openldap + # container_name: ldap + # expose: + # - 389 + + # See https://github.com/jwilder/nginx-proxy for documentation on how to configure the nginx-proxy container, + # and https://github.com/overleaf/overleaf/wiki/HTTPS-reverse-proxy-using-Nginx for an example of some recommended + # settings. We recommend using a properly managed nginx instance outside of the Overleaf Server Pro setup, + # but the example here can be used if you'd prefer to run everything with docker-compose + + # nginx-proxy: + # image: jwilder/nginx-proxy + # container_name: nginx-proxy + # ports: + # #- "80:80" + # - "443:443" + # volumes: + # - /var/run/docker.sock:/tmp/docker.sock:ro + # - /home/sharelatex/tmp:/etc/nginx/certs diff --git a/server-ce/genScript.js b/server-ce/genScript.js new file mode 100644 index 0000000000..e8f61f2ba2 --- /dev/null +++ b/server-ce/genScript.js @@ -0,0 +1,62 @@ +const services = require('./services') + +console.log('#!/bin/bash') +console.log('set -ex') + +switch (process.argv.pop()) { + case 'checkout': + for (const service of services) { + console.log(`git clone ${service.repo} ${service.name}`) + console.log(`git -C ${service.name} checkout ${service.version}`) + } + break + case 'revisions': + for (const service of services) { + console.log(`echo -n /var/www/sharelatex/${service.name},`) + console.log(`git -C ${service.name} rev-parse HEAD`) + } + break + case 'cleanup-git': + for (const service of services) { + console.log(`rm -rf ${service.name}/.git`) + } + break + case 'install': + for (const service of services) { + console.log('pushd', service.name) + switch (service.name) { + case 'web': + console.log('npm ci') + break + default: + // TODO(das7pad): revert back to npm ci --only=production (https://github.com/overleaf/issues/issues/4544) + console.log('npm ci') + } + console.log('popd') + } + break + case 'compile': + for (const service of services) { + console.log('pushd', service.name) + switch (service.name) { + case 'web': + console.log('npm run webpack:production') + // drop webpack/babel cache + console.log('rm -rf node_modules/.cache') + break + default: + console.log(`echo ${service.name} does not require a compilation`) + } + console.log('popd') + } + break + default: + console.error('unknown command') + console.log('exit 101') + process.exit(101) +} + +console.log('set +x') +console.log( + 'rm -rf /root/.cache /root/.npm $(find /tmp/ -mindepth 1 -maxdepth 1)' +) diff --git a/server-ce/hotfix/2.0.1/Dockerfile b/server-ce/hotfix/2.0.1/Dockerfile new file mode 100644 index 0000000000..12a85378b4 --- /dev/null +++ b/server-ce/hotfix/2.0.1/Dockerfile @@ -0,0 +1,13 @@ +FROM sharelatex/sharelatex:2.0.0 + + +# Patch 1: Fixes project deletion (https://github.com/overleaf/overleaf/issues/644) +ADD disable_project_history.patch /etc/sharelatex/disable_project_history.patch +RUN cd /etc/sharelatex && \ + patch < disable_project_history.patch + + +# Patch 2: Fixes admin creation via CLI (https://github.com/overleaf/overleaf/issues/647) +ADD create_and_destroy_users.patch /var/www/sharelatex/tasks/create_and_destroy_users.patch +RUN cd /var/www/sharelatex/tasks/ && \ + patch < create_and_destroy_users.patch diff --git a/server-ce/hotfix/2.0.1/create_and_destroy_users.patch b/server-ce/hotfix/2.0.1/create_and_destroy_users.patch new file mode 100644 index 0000000000..bb2dc16898 --- /dev/null +++ b/server-ce/hotfix/2.0.1/create_and_destroy_users.patch @@ -0,0 +1,11 @@ +--- CreateAndDestoryUsers.coffee ++++ CreateAndDestoryUsers.coffee +@@ -21,7 +21,7 @@ module.exports = (grunt) -> + user.save (error) -> + throw error if error? + ONE_WEEK = 7 * 24 * 60 * 60 # seconds +- OneTimeTokenHandler.getNewToken user._id, { expiresIn: ONE_WEEK }, (err, token)-> ++ OneTimeTokenHandler.getNewToken "password", { expiresIn: ONE_WEEK, email:user.email, user_id: user._id.toString() }, (err, token)-> + return next(err) if err? + + console.log "" diff --git a/server-ce/hotfix/2.0.1/disable_project_history.patch b/server-ce/hotfix/2.0.1/disable_project_history.patch new file mode 100644 index 0000000000..830570abbe --- /dev/null +++ b/server-ce/hotfix/2.0.1/disable_project_history.patch @@ -0,0 +1,11 @@ +--- settings.coffee ++++ settings.coffee +@@ -200,6 +200,8 @@ settings = + # is not available + v1: + url: "" ++ project_history: ++ enabled: false + references:{} + notifications:undefined + diff --git a/server-ce/hotfix/2.0.2/1-anon-upload.patch b/server-ce/hotfix/2.0.2/1-anon-upload.patch new file mode 100644 index 0000000000..75037901e0 --- /dev/null +++ b/server-ce/hotfix/2.0.2/1-anon-upload.patch @@ -0,0 +1,60 @@ +--- UploadsRouter.js ++++ UploadsRouter.js +@@ -1,13 +1,3 @@ +-/* eslint-disable +- no-unused-vars, +-*/ +-// TODO: This file was created by bulk-decaffeinate. +-// Fix any style issues and re-enable lint. +-/* +- * decaffeinate suggestions: +- * DS102: Remove unnecessary code created because of implicit returns +- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md +- */ + const AuthorizationMiddleware = require('../Authorization/AuthorizationMiddleware') + const AuthenticationController = require('../Authentication/AuthenticationController') + const ProjectUploadController = require('./ProjectUploadController') +@@ -28,18 +18,30 @@ module.exports = { + ProjectUploadController.uploadProject + ) + +- return webRouter.post( +- '/Project/:Project_id/upload', +- RateLimiterMiddleware.rateLimit({ +- endpointName: 'file-upload', +- params: ['Project_id'], +- maxRequests: 200, +- timeInterval: 60 * 30 +- }), +- AuthenticationController.requireLogin(), +- AuthorizationMiddleware.ensureUserCanWriteProjectContent, +- ProjectUploadController.multerMiddleware, +- ProjectUploadController.uploadFile +- ) ++ const fileUploadEndpoint = '/Project/:Project_id/upload' ++ const fileUploadRateLimit = RateLimiterMiddleware.rateLimit({ ++ endpointName: 'file-upload', ++ params: ['Project_id'], ++ maxRequests: 200, ++ timeInterval: 60 * 30 ++ }) ++ if (Settings.allowAnonymousReadAndWriteSharing) { ++ webRouter.post( ++ fileUploadEndpoint, ++ fileUploadRateLimit, ++ AuthorizationMiddleware.ensureUserCanWriteProjectContent, ++ ProjectUploadController.multerMiddleware, ++ ProjectUploadController.uploadFile ++ ) ++ } else { ++ webRouter.post( ++ fileUploadEndpoint, ++ fileUploadRateLimit, ++ AuthenticationController.requireLogin(), ++ AuthorizationMiddleware.ensureUserCanWriteProjectContent, ++ ProjectUploadController.multerMiddleware, ++ ProjectUploadController.uploadFile ++ ) ++ } + } + } diff --git a/server-ce/hotfix/2.0.2/2-read-only-access.patch b/server-ce/hotfix/2.0.2/2-read-only-access.patch new file mode 100644 index 0000000000..246cc3e04d --- /dev/null +++ b/server-ce/hotfix/2.0.2/2-read-only-access.patch @@ -0,0 +1,11 @@ +--- TokenAccessHandler.js ++++ TokenAccessHandler.js +@@ -255,7 +255,7 @@ const TokenAccessHandler = { + + getV1DocPublishedInfo(token, callback) { + // default to allowing access +- if (!Settings.apis || !Settings.apis.v1) { ++ if (!Settings.apis.v1 || !Settings.apis.v1.url) { + return callback(null, { allow: true }) + } + V1Api.request( diff --git a/server-ce/hotfix/2.0.2/3-url-linking-1.patch b/server-ce/hotfix/2.0.2/3-url-linking-1.patch new file mode 100644 index 0000000000..173809842f --- /dev/null +++ b/server-ce/hotfix/2.0.2/3-url-linking-1.patch @@ -0,0 +1,11 @@ +--- Features.js ++++ Features.js +@@ -53,6 +53,8 @@ module.exports = Features = { + return Settings.apis.references.url != null + case 'saml': + return Settings.enableSaml ++ case 'link-url': ++ return Settings.apis.linkedUrlProxy && Settings.apis.linkedUrlProxy.url + default: + throw new Error(`unknown feature: ${feature}`) + } diff --git a/server-ce/hotfix/2.0.2/4-url-linking-2.patch b/server-ce/hotfix/2.0.2/4-url-linking-2.patch new file mode 100644 index 0000000000..587a8e6e0f --- /dev/null +++ b/server-ce/hotfix/2.0.2/4-url-linking-2.patch @@ -0,0 +1,20 @@ +--- new-file-modal.pug ++++ new-file-modal.pug +@@ -21,11 +21,12 @@ script(type='text/ng-template', id='newFileModalTemplate') + i.fa.fa-fw.fa-folder-open + | + | From Another Project +- li(ng-class="type == 'url' ? 'active' : null") +- a(href, ng-click="type = 'url'") +- i.fa.fa-fw.fa-globe +- | +- | From External URL ++ if hasFeature('link-url') ++ li(ng-class="type == 'url' ? 'active' : null") ++ a(href, ng-click="type = 'url'") ++ i.fa.fa-fw.fa-globe ++ | ++ | From External URL + != moduleIncludes("newFileModal:selector", locals) + + td(class="modal-new-file--body modal-new-file--body-{{type}}") diff --git a/server-ce/hotfix/2.0.2/5-disable-analytics-1.patch b/server-ce/hotfix/2.0.2/5-disable-analytics-1.patch new file mode 100644 index 0000000000..198ee03935 --- /dev/null +++ b/server-ce/hotfix/2.0.2/5-disable-analytics-1.patch @@ -0,0 +1,26 @@ +--- AnalyticsController.js ++++ AnalyticsController.js +@@ -3,9 +3,13 @@ const Errors = require('../Errors/Errors') + const AuthenticationController = require('../Authentication/AuthenticationController') + const InstitutionsAPI = require('../Institutions/InstitutionsAPI') + const GeoIpLookup = require('../../infrastructure/GeoIpLookup') ++const Features = require('../../infrastructure/Features') + + module.exports = { + updateEditingSession(req, res, next) { ++ if (!Features.hasFeature('analytics')) { ++ return res.send(204) ++ } + const userId = AuthenticationController.getLoggedInUserId(req) + const { projectId } = req.params + let countryCode = null +@@ -28,6 +32,9 @@ module.exports = { + }, + + recordEvent(req, res, next) { ++ if (!Features.hasFeature('analytics')) { ++ return res.send(204) ++ } + const userId = + AuthenticationController.getLoggedInUserId(req) || req.sessionID + AnalyticsManager.recordEvent(userId, req.params.event, req.body, error => diff --git a/server-ce/hotfix/2.0.2/6-disable-analytics-2.patch b/server-ce/hotfix/2.0.2/6-disable-analytics-2.patch new file mode 100644 index 0000000000..9fb41c1cba --- /dev/null +++ b/server-ce/hotfix/2.0.2/6-disable-analytics-2.patch @@ -0,0 +1,10 @@ +--- Features.js ++++ Features.js +@@ -41,6 +41,7 @@ module.exports = Features = { + case 'templates-server-pro': + return Settings.overleaf == null + case 'affiliations': ++ case 'analytics': + // Checking both properties is needed for the time being to allow + // enabling the feature in web-api and disabling in Server Pro + // see https://github.com/overleaf/web-internal/pull/2127 diff --git a/server-ce/hotfix/2.0.2/Dockerfile b/server-ce/hotfix/2.0.2/Dockerfile new file mode 100644 index 0000000000..7a75ed97a5 --- /dev/null +++ b/server-ce/hotfix/2.0.2/Dockerfile @@ -0,0 +1,31 @@ +FROM sharelatex/sharelatex:2.0.1 + + +# Patch 1: Fixes anonymous link sharing +ADD 1-anon-upload.patch /var/www/sharelatex/web/app/src/Features/Uploads/1-anon-upload.patch +RUN cd /var/www/sharelatex/web/app/src/Features/Uploads/ && \ + patch < 1-anon-upload.patch + + +# Patch 2: Fixes read-only access +ADD 2-read-only-access.patch /var/www/sharelatex/web/app/src/Features/TokenAccess/3-read-only-access.patch +RUN cd /var/www/sharelatex/web/app/src/Features/TokenAccess/ && \ + patch < 3-read-only-access.patch + + +# Patch 3: Fixes url linking +ADD 3-url-linking-1.patch /var/www/sharelatex/web/app/src/infrastructure/6-url-linking-1.patch +RUN cd /var/www/sharelatex/web/app/src/infrastructure/ && \ + patch < 6-url-linking-1.patch +ADD 4-url-linking-2.patch /var/www/sharelatex/web/app/views/project/editor/7-url-linking-2.patch +RUN cd /var/www/sharelatex/web/app/views/project/editor/ && \ + patch < 7-url-linking-2.patch + + +# Patch 4: Disables analytics +ADD 5-disable-analytics-1.patch /var/www/sharelatex/web/app/src/Features/Analytics/8-disable-analytics-1.patch +RUN cd /var/www/sharelatex/web/app/src/Features/Analytics/ && \ + patch < 8-disable-analytics-1.patch +ADD 6-disable-analytics-2.patch /var/www/sharelatex/web/app/src/infrastructure/9-disable-analytics-2.patch +RUN cd /var/www/sharelatex/web/app/src/infrastructure/ && \ + patch < 9-disable-analytics-2.patch diff --git a/server-ce/hotfix/2.1.1/Dockerfile b/server-ce/hotfix/2.1.1/Dockerfile new file mode 100644 index 0000000000..313c596545 --- /dev/null +++ b/server-ce/hotfix/2.1.1/Dockerfile @@ -0,0 +1,8 @@ +FROM sharelatex/sharelatex:2.1.0 + +# Patch: defines recaptcha config to fix share-related issues +# - https://github.com/overleaf/overleaf/issues/684 +ADD add-recaptcha-config.patch /etc/sharelatex/add-recaptcha-config.patch +RUN cd /etc/sharelatex/ && \ + patch < add-recaptcha-config.patch + diff --git a/server-ce/hotfix/2.1.1/add-recaptcha-config.patch b/server-ce/hotfix/2.1.1/add-recaptcha-config.patch new file mode 100644 index 0000000000..cdca537c46 --- /dev/null +++ b/server-ce/hotfix/2.1.1/add-recaptcha-config.patch @@ -0,0 +1,14 @@ +--- a/settings.coffee ++++ b/settings.coffee +@@ -180,6 +180,11 @@ settings = + # cookie with a secure flag (recommended). + secureCookie: process.env["SHARELATEX_SECURE_COOKIE"]? + ++ recaptcha: ++ disabled: ++ invite: true ++ register: true ++ + # If you are running ShareLaTeX behind a proxy (like Apache, Nginx, etc) + # then set this to true to allow it to correctly detect the forwarded IP + # address and http/https protocol information. diff --git a/server-ce/hotfix/2.3.1/Dockerfile b/server-ce/hotfix/2.3.1/Dockerfile new file mode 100644 index 0000000000..36f136aacc --- /dev/null +++ b/server-ce/hotfix/2.3.1/Dockerfile @@ -0,0 +1,7 @@ +FROM sharelatex/sharelatex:2.3.0 + + +# Patch: Fixes NPE when invoking synctex (https://github.com/overleaf/overleaf/issues/756) +ADD check-clsi-setting-exists.patch /var/www/sharelatex/clsi/app/js/check-clsi-setting-exists.patch +RUN cd /var/www/sharelatex/clsi/app/js && \ + patch < check-clsi-setting-exists.patch diff --git a/server-ce/hotfix/2.3.1/check-clsi-setting-exists.patch b/server-ce/hotfix/2.3.1/check-clsi-setting-exists.patch new file mode 100644 index 0000000000..6f6535bc69 --- /dev/null +++ b/server-ce/hotfix/2.3.1/check-clsi-setting-exists.patch @@ -0,0 +1,11 @@ +--- a/app/js/CompileManager.js ++++ b/app/js/CompileManager.js +@@ -536,7 +536,7 @@ module.exports = CompileManager = { + compileName, + command, + directory, +- Settings.clsi != null ? Settings.clsi.docker.image : undefined, ++ Settings.clsi && Settings.clsi.docker ? Settings.clsi.docker.image : undefined, + timeout, + {}, + function(error, output) { diff --git a/server-ce/hotfix/2.4.1/Dockerfile b/server-ce/hotfix/2.4.1/Dockerfile new file mode 100644 index 0000000000..d7655511c1 --- /dev/null +++ b/server-ce/hotfix/2.4.1/Dockerfile @@ -0,0 +1,6 @@ +FROM sharelatex/sharelatex:2.4.0 + + +# Patch: Fixes missing dependencies on web startup (https://github.com/overleaf/overleaf/issues/767) +RUN cd /var/www/sharelatex/web && \ + npm install i18next@^19.6.3 i18next-fs-backend@^1.0.7 i18next-http-middleware@^3.0.2 diff --git a/server-ce/hotfix/2.4.2/Dockerfile b/server-ce/hotfix/2.4.2/Dockerfile new file mode 100644 index 0000000000..640eea78c3 --- /dev/null +++ b/server-ce/hotfix/2.4.2/Dockerfile @@ -0,0 +1,10 @@ +FROM sharelatex/sharelatex:2.4.1 + + +# Patch: Fixes anonymous read/write sharing +COPY anonymous-metadata.patch ${baseDir} +RUN cd ${baseDir} && patch -p0 < anonymous-metadata.patch + +# Patch: Fixes left footer with html text +COPY left-footer-skip-translation.patch ${baseDir} +RUN cd ${baseDir} && patch -p0 < left-footer-skip-translation.patch diff --git a/server-ce/hotfix/2.4.2/anonymous-metadata.patch b/server-ce/hotfix/2.4.2/anonymous-metadata.patch new file mode 100644 index 0000000000..ea041abf9c --- /dev/null +++ b/server-ce/hotfix/2.4.2/anonymous-metadata.patch @@ -0,0 +1,43 @@ +--- /var/www/sharelatex/web/app/src/router.js 2020-09-14 20:21:39.741433000 +0000 ++++ /var/www/sharelatex/web/app/src/router.js 2020-09-14 20:13:08.000000000 +0000 +@@ -607,16 +607,17 @@ + ProjectDownloadsController.downloadMultipleProjects + ) + ++ console.log(`allowAnonymousReadAndWriteSharing: ${Settings.allowAnonymousReadAndWriteSharing}`) + webRouter.get( + '/project/:project_id/metadata', + AuthorizationMiddleware.ensureUserCanReadProject, +- AuthenticationController.requireLogin(), ++ Settings.allowAnonymousReadAndWriteSharing ? (req, res, next) => { next() } : AuthenticationController.requireLogin(), + MetaController.getMetadata +- ) ++ ) + webRouter.post( + '/project/:project_id/doc/:doc_id/metadata', + AuthorizationMiddleware.ensureUserCanReadProject, +- AuthenticationController.requireLogin(), ++ Settings.allowAnonymousReadAndWriteSharing ? (req, res, next) => { next() } : AuthenticationController.requireLogin(), + MetaController.broadcastMetadataForDoc + ) + privateApiRouter.post( +--- /var/www/sharelatex/web/app/src/Features/Contacts/ContactRouter.js 2020-09-14 20:21:52.243779000 +0000 ++++ /var/www/sharelatex/web/app/src/Features/Contacts/ContactRouter.js 2020-09-14 20:13:08.000000000 +0000 +@@ -5,6 +5,8 @@ + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ ++const Settings = require('settings-sharelatex') ++ + const AuthenticationController = require('../Authentication/AuthenticationController') + const ContactController = require('./ContactController') + +@@ -12,7 +14,7 @@ + apply(webRouter, apiRouter) { + return webRouter.get( + '/user/contacts', +- AuthenticationController.requireLogin(), ++ Settings.allowAnonymousReadAndWriteSharing ? (req, res, next) => { next() } : AuthenticationController.requireLogin(), + ContactController.getContacts + ) + } diff --git a/server-ce/hotfix/2.4.2/left-footer-skip-translation.patch b/server-ce/hotfix/2.4.2/left-footer-skip-translation.patch new file mode 100644 index 0000000000..ee6e33a417 --- /dev/null +++ b/server-ce/hotfix/2.4.2/left-footer-skip-translation.patch @@ -0,0 +1,12 @@ + +--- /var/www/sharelatex/web/app/views/layout/footer.pug ++++ /var/www/sharelatex/web/app/app/views/layout/footer.pug +@@ -32,7 +32,7 @@ footer.site-footer + if item.url + a(href=item.url, class=item.class) !{translate(item.text)} + else +- | !{translate(item.text)} ++ | !{item.text} + + ul.col-md-3.text-right + diff --git a/server-ce/hotfix/2.5.1/Dockerfile b/server-ce/hotfix/2.5.1/Dockerfile new file mode 100644 index 0000000000..d22f9123d1 --- /dev/null +++ b/server-ce/hotfix/2.5.1/Dockerfile @@ -0,0 +1,13 @@ +FROM sharelatex/sharelatex:2.5.0 + +# Patch #826: Fixes log path for contacts service to be picked up by logrotate +COPY contacts-run.patch /etc/service/contacts-sharelatex +RUN cd /etc/service/contacts-sharelatex && patch < contacts-run.patch + +# Patch #826: delete old logs for the contacts service +COPY delete-old-logs.patch /etc/my_init.d +RUN cd /etc/my_init.d && patch < delete-old-logs.patch \ +&& chmod +x /etc/my_init.d/10_delete_old_logs.sh + +# Patch #827: fix logrotate file permissions +RUN chmod 644 /etc/logrotate.d/sharelatex diff --git a/server-ce/hotfix/2.5.1/contacts-run.patch b/server-ce/hotfix/2.5.1/contacts-run.patch new file mode 100644 index 0000000000..81ef36ecb0 --- /dev/null +++ b/server-ce/hotfix/2.5.1/contacts-run.patch @@ -0,0 +1,8 @@ +--- a/run ++++ b/run +@@ -7,4 +7,4 @@ if [ "$DEBUG_NODE" == "true" ]; then + NODE_PARAMS="--inspect=0.0.0.0:30360" + fi + +-exec /sbin/setuser www-data /usr/bin/node $NODE_PARAMS /var/www/sharelatex/contacts/app.js >> /var/log/sharelatex/contacts 2>&1 ++exec /sbin/setuser www-data /usr/bin/node $NODE_PARAMS /var/www/sharelatex/contacts/app.js >> /var/log/sharelatex/contacts.log 2>&1 diff --git a/server-ce/hotfix/2.5.1/delete-old-logs.patch b/server-ce/hotfix/2.5.1/delete-old-logs.patch new file mode 100644 index 0000000000..bc2be14adf --- /dev/null +++ b/server-ce/hotfix/2.5.1/delete-old-logs.patch @@ -0,0 +1,10 @@ +--- /dev/null ++++ b/10_delete_old_logs.sh +@@ -0,0 +1,7 @@ ++#!/bin/sh ++set -e ++ ++# Up to version 2.5.0 the logs of the contacts service were written into a ++# file that was not picked up by logrotate. ++# The service is stable and we can safely discard any logs. ++rm -vf /var/log/sharelatex/contacts diff --git a/server-ce/hotfix/2.5.2/12_update_token_email.js b/server-ce/hotfix/2.5.2/12_update_token_email.js new file mode 100644 index 0000000000..e4d6e32254 --- /dev/null +++ b/server-ce/hotfix/2.5.2/12_update_token_email.js @@ -0,0 +1,28 @@ +const Settings = require('settings-sharelatex') +const mongojs = require('mongojs') +const db = mongojs(Settings.mongo.url, ['tokens']) +const async = require('async') + +exports.migrate = (client, done) => { + console.log(`>> Updating 'data.email' to lower case in tokens`) + + db.tokens.find({}, { 'data.email': 1 }, (err, tokens) => { + if (err) { + return done(err) + } + + async.eachSeries( + tokens, + (token, callback) => { + db.tokens.update( + { _id: token._id }, + { $set: { 'data.email': token.data.email.toLowerCase() } }, + callback + ) + }, + done + ) + }) +} + +exports.rollback = (client, done) => done() diff --git a/server-ce/hotfix/2.5.2/Dockerfile b/server-ce/hotfix/2.5.2/Dockerfile new file mode 100644 index 0000000000..ddf596deea --- /dev/null +++ b/server-ce/hotfix/2.5.2/Dockerfile @@ -0,0 +1,8 @@ +FROM sharelatex/sharelatex:2.5.1 + +# Patch: fixes registration token creation +COPY create-token-lowercase-email.patch ${baseDir} +RUN cd ${baseDir} && patch -p0 < create-token-lowercase-email.patch + +# Migration for tokens with invalid email addresses +ADD 12_update_token_email.js /var/www/sharelatex/migrations/12_update_token_email.js diff --git a/server-ce/hotfix/2.5.2/create-token-lowercase-email.patch b/server-ce/hotfix/2.5.2/create-token-lowercase-email.patch new file mode 100644 index 0000000000..23dfaa3a43 --- /dev/null +++ b/server-ce/hotfix/2.5.2/create-token-lowercase-email.patch @@ -0,0 +1,11 @@ +--- /var/www/sharelatex/web/app/src/Features/User/UserRegistrationHandler.js ++++ /var/www/sharelatex/web/app/src/Features/User/UserRegistrationHandler.js +@@ -122,7 +122,7 @@ const UserRegistrationHandler = { + const ONE_WEEK = 7 * 24 * 60 * 60 // seconds + OneTimeTokenHandler.getNewToken( + 'password', +- { user_id: user._id.toString(), email }, ++ { user_id: user._id.toString(), email: user.email }, + { expiresIn: ONE_WEEK }, + (err, token) => { + if (err != null) { diff --git a/server-ce/hotfix/2.6.1/Dockerfile b/server-ce/hotfix/2.6.1/Dockerfile new file mode 100644 index 0000000000..6df467b09b --- /dev/null +++ b/server-ce/hotfix/2.6.1/Dockerfile @@ -0,0 +1,5 @@ +FROM sharelatex/sharelatex:2.6.0-RC1 + +# Patch: fixes Project restore inserts bad projectId into deletedFiles +COPY document-deleter-object-id.patch ${baseDir} +RUN cd ${baseDir} && patch -p0 < document-deleter-object-id.patch diff --git a/server-ce/hotfix/2.6.1/document-deleter-object-id.patch b/server-ce/hotfix/2.6.1/document-deleter-object-id.patch new file mode 100644 index 0000000000..a92ce49a13 --- /dev/null +++ b/server-ce/hotfix/2.6.1/document-deleter-object-id.patch @@ -0,0 +1,10 @@ +--- /var/www/sharelatex/web/app/src/Features/Project/ProjectDeleter.js ++++ /var/www/sharelatex/web/app/src/Features/Project/ProjectDeleter.js +@@ -278,6 +278,7 @@ async function deleteProject(projectId, options = {}) { + } + + async function undeleteProject(projectId, options = {}) { ++ projectId = ObjectId(projectId) + let deletedProject = await DeletedProject.findOne({ + 'deleterData.deletedProjectId': projectId + }).exec() diff --git a/server-ce/hotfix/2.6.2/Dockerfile b/server-ce/hotfix/2.6.2/Dockerfile new file mode 100644 index 0000000000..2df365143e --- /dev/null +++ b/server-ce/hotfix/2.6.2/Dockerfile @@ -0,0 +1,5 @@ +FROM sharelatex/sharelatex:2.6.1 + +# Patch: fixes overleaf.com onboarding email being sent in CE/SP +COPY onboarding-email.patch ${baseDir} +RUN cd ${baseDir} && patch -p0 < onboarding-email.patch diff --git a/server-ce/hotfix/2.6.2/onboarding-email.patch b/server-ce/hotfix/2.6.2/onboarding-email.patch new file mode 100644 index 0000000000..2d1fed5686 --- /dev/null +++ b/server-ce/hotfix/2.6.2/onboarding-email.patch @@ -0,0 +1,25 @@ +--- /var/www/sharelatex/web/app/src/Features/User/UserCreator.js ++++ /var/www/sharelatex/web/app/src/Features/User/UserCreator.js +@@ -85,13 +85,15 @@ async function createNewUser(attributes, options = {}) { + } + + Analytics.recordEvent(user._id, 'user-registered') +- try { +- await UserOnboardingEmailQueueManager.scheduleOnboardingEmail(user) +- } catch (error) { +- logger.error( +- `Failed to schedule sending of onboarding email for user '${user._id}'`, +- error +- ) ++ if(Features.hasFeature('saas')) { ++ try { ++ await UserOnboardingEmailQueueManager.scheduleOnboardingEmail(user) ++ } catch (error) { ++ logger.error( ++ `Failed to schedule sending of onboarding email for user '${user._id}'`, ++ error ++ ) ++ } + } + + return user diff --git a/server-ce/init_scripts/00_make_sharelatex_data_dirs.sh b/server-ce/init_scripts/00_make_sharelatex_data_dirs.sh new file mode 100755 index 0000000000..0d7c643733 --- /dev/null +++ b/server-ce/init_scripts/00_make_sharelatex_data_dirs.sh @@ -0,0 +1,35 @@ +#!/bin/sh +set -e + +mkdir -p /var/lib/sharelatex/data +chown www-data:www-data /var/lib/sharelatex/data + +mkdir -p /var/lib/sharelatex/data/user_files +chown www-data:www-data /var/lib/sharelatex/data/user_files + +mkdir -p /var/lib/sharelatex/data/compiles +chown www-data:www-data /var/lib/sharelatex/data/compiles + +mkdir -p /var/lib/sharelatex/data/cache +chown www-data:www-data /var/lib/sharelatex/data/cache + +mkdir -p /var/lib/sharelatex/data/template_files +chown www-data:www-data /var/lib/sharelatex/data/template_files + +mkdir -p /var/lib/sharelatex/tmp/dumpFolder +chown www-data:www-data /var/lib/sharelatex/tmp/dumpFolder + +mkdir -p /var/lib/sharelatex/tmp +chown www-data:www-data /var/lib/sharelatex/tmp + +mkdir -p /var/lib/sharelatex/tmp/uploads +chown www-data:www-data /var/lib/sharelatex/tmp/uploads + +mkdir -p /var/lib/sharelatex/tmp/dumpFolder +chown www-data:www-data /var/lib/sharelatex/tmp/dumpFolder + +if [ ! -e "/var/lib/sharelatex/data/db.sqlite" ]; then + touch /var/lib/sharelatex/data/db.sqlite +fi + +chown www-data:www-data /var/lib/sharelatex/data/db.sqlite diff --git a/server-ce/init_scripts/00_regen_sharelatex_secrets.sh b/server-ce/init_scripts/00_regen_sharelatex_secrets.sh new file mode 100755 index 0000000000..365be0869f --- /dev/null +++ b/server-ce/init_scripts/00_regen_sharelatex_secrets.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -e -o pipefail + +# generate secrets and defines them as environment variables +# https://github.com/phusion/baseimage-docker#centrally-defining-your-own-environment-variables + +WEB_API_PASSWORD_FILE=/etc/container_environment/WEB_API_PASSWORD +CRYPTO_RANDOM_FILE=/etc/container_environment/CRYPTO_RANDOM + +if [ ! -f "$WEB_API_PASSWORD_FILE" ] || [ ! -f "$CRYPTO_RANDOM_FILE" ]; then + + echo "generating random secrets" + + SECRET=$(dd if=/dev/urandom bs=1 count=32 2>/dev/null | base64 -w 0 | rev | cut -b 2- | rev | tr -d '\n+/') + echo ${SECRET} > ${WEB_API_PASSWORD_FILE} + + SECRET=$(dd if=/dev/urandom bs=1 count=32 2>/dev/null | base64 -w 0 | rev | cut -b 2- | rev | tr -d '\n+/') + echo ${SECRET} > ${CRYPTO_RANDOM_FILE} +fi + diff --git a/server-ce/init_scripts/00_set_docker_host_ipaddress.sh b/server-ce/init_scripts/00_set_docker_host_ipaddress.sh new file mode 100755 index 0000000000..0587a9b222 --- /dev/null +++ b/server-ce/init_scripts/00_set_docker_host_ipaddress.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -e -o pipefail + +# See the bottom of http://stackoverflow.com/questions/24319662/from-inside-of-a-docker-container-how-do-i-connect-to-the-localhost-of-the-mach +echo "`route -n | awk '/UG[ \t]/{print $2}'` dockerhost" >> /etc/hosts diff --git a/server-ce/init_scripts/01_nginx_config_template.sh b/server-ce/init_scripts/01_nginx_config_template.sh new file mode 100755 index 0000000000..84f39ea637 --- /dev/null +++ b/server-ce/init_scripts/01_nginx_config_template.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +set -e + +## Generate nginx config files from templates, +## with environment variables substituted + +nginx_dir='/etc/nginx' +nginx_templates_dir="${nginx_dir}/templates" + +if ! [ -d "${nginx_templates_dir}" ]; then + echo "Nginx: no template directory found, skipping" + exit 0 +fi + +nginx_template_file="${nginx_templates_dir}/nginx.conf.template" +nginx_config_file="${nginx_dir}/nginx.conf" + +if [ -f "${nginx_template_file}" ]; then + export NGINX_WORKER_PROCESSES="${NGINX_WORKER_PROCESSES:-4}" + export NGINX_WORKER_CONNECTIONS="${NGINX_WORKER_CONNECTIONS:-768}" + + echo "Nginx: generating config file from template" + + # Note the single-quotes, they are important. + # This is a pass-list of env-vars that envsubst + # should operate on. + envsubst '${NGINX_WORKER_PROCESSES} ${NGINX_WORKER_CONNECTIONS}' \ + < "${nginx_template_file}" \ + > "${nginx_config_file}" + + echo "Nginx: reloading config" + service nginx reload +fi diff --git a/server-ce/init_scripts/10_delete_old_logs.sh b/server-ce/init_scripts/10_delete_old_logs.sh new file mode 100755 index 0000000000..1b606dbf92 --- /dev/null +++ b/server-ce/init_scripts/10_delete_old_logs.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -e + +# Up to version 2.5.0 the logs of the contacts service were written into a +# file that was not picked up by logrotate. +# The service is stable and we can safely discard any logs. +rm -vf /var/log/sharelatex/contacts diff --git a/server-ce/init_scripts/98_check_db_access.sh b/server-ce/init_scripts/98_check_db_access.sh new file mode 100755 index 0000000000..aab69e24fe --- /dev/null +++ b/server-ce/init_scripts/98_check_db_access.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -e + +echo "Checking can connect to mongo and redis" +cd /var/www/sharelatex/web/modules/server-ce-scripts/scripts +node check-mongodb +node check-redis +echo "All checks passed" diff --git a/server-ce/init_scripts/99_run_web_migrations.sh b/server-ce/init_scripts/99_run_web_migrations.sh new file mode 100755 index 0000000000..a94ce18602 --- /dev/null +++ b/server-ce/init_scripts/99_run_web_migrations.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ "${SHARELATEX_IS_SERVER_PRO:-null}" == "true" ]]; then + environment="server-pro" +else + environment="server-ce" +fi + +echo "Running migrations for $environment" +cd /var/www/sharelatex/web +npm run migrations -- migrate -t "$environment" +echo "Finished migrations" diff --git a/server-ce/logrotate/sharelatex b/server-ce/logrotate/sharelatex new file mode 100644 index 0000000000..efc51cc6c9 --- /dev/null +++ b/server-ce/logrotate/sharelatex @@ -0,0 +1,9 @@ +/var/log/sharelatex/*.log { + daily + missingok + rotate 5 + compress + copytruncate + notifempty + create 644 root adm +} \ No newline at end of file diff --git a/server-ce/nginx/nginx.conf.template b/server-ce/nginx/nginx.conf.template new file mode 100644 index 0000000000..e3cf283e85 --- /dev/null +++ b/server-ce/nginx/nginx.conf.template @@ -0,0 +1,80 @@ +## ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ## +## ! This file was generated from a template ! ## +## ! See /etc/nginx/templates/ ! ## +## ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ## +daemon off; +user www-data; +worker_processes ${NGINX_WORKER_PROCESSES}; +pid /run/nginx.pid; + +events { + worker_connections ${NGINX_WORKER_CONNECTIONS}; + # multi_accept on; +} + +http { + + ## + # Basic Settings + ## + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + # server_tokens off; + + # server_names_hash_bucket_size 64; + # server_name_in_redirect off; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + ## + # Logging Settings + ## + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + ## + # Gzip Settings + ## + + gzip on; + gzip_disable "msie6"; + + client_max_body_size 50m; + + # gzip_vary on; + # gzip_proxied any; + # gzip_comp_level 6; + # gzip_buffers 16 8k; + # gzip_http_version 1.1; + # gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; + + ## + # nginx-naxsi config + ## + # Uncomment it if you installed nginx-naxsi + ## + + #include /etc/nginx/naxsi_core.rules; + + ## + # nginx-passenger config + ## + # Uncomment it if you installed nginx-passenger + ## + + #passenger_root /usr; + #passenger_ruby /usr/bin/ruby; + + ## + # Virtual Host Configs + ## + + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; +} diff --git a/server-ce/nginx/sharelatex.conf b/server-ce/nginx/sharelatex.conf new file mode 100644 index 0000000000..723fb7fbe7 --- /dev/null +++ b/server-ce/nginx/sharelatex.conf @@ -0,0 +1,43 @@ +server { + listen 80; + server_name _; # Catch all, see http://nginx.org/en/docs/http/server_names.html + + root /var/www/sharelatex/web/public/; + + location / { + proxy_pass http://127.0.0.1:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_read_timeout 10m; + proxy_send_timeout 10m; + } + + location /socket.io { + proxy_pass http://127.0.0.1:3026; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_read_timeout 10m; + proxy_send_timeout 10m; + } + + location /stylesheets { + expires 1y; + } + + location /minjs { + expires 1y; + } + + location /img { + expires 1y; + } +} diff --git a/server-ce/runit/chat-sharelatex/run b/server-ce/runit/chat-sharelatex/run new file mode 100755 index 0000000000..c000f7d80d --- /dev/null +++ b/server-ce/runit/chat-sharelatex/run @@ -0,0 +1,9 @@ +#!/bin/bash + +NODE_PARAMS="" +if [ "$DEBUG_NODE" == "true" ]; then + echo "running debug - chat" + NODE_PARAMS="--inspect=0.0.0.0:30100" +fi + +exec /sbin/setuser www-data /usr/bin/node $NODE_PARAMS /var/www/sharelatex/chat/app.js >> /var/log/sharelatex/chat.log 2>&1 diff --git a/server-ce/runit/clsi-sharelatex/run b/server-ce/runit/clsi-sharelatex/run new file mode 100755 index 0000000000..e8e7bbaf4c --- /dev/null +++ b/server-ce/runit/clsi-sharelatex/run @@ -0,0 +1,31 @@ +#!/bin/bash + +NODE_PARAMS="" +if [ "$DEBUG_NODE" == "true" ]; then + echo "running debug - clsi" + NODE_PARAMS="--inspect=0.0.0.0:30130" +fi + +# Set permissions on docker.sock if present, +# To enable sibling-containers (see entrypoint.sh in clsi project) +if [ -e '/var/run/docker.sock' ]; then + echo ">> Setting permissions on docker socket" + DOCKER_GROUP=$(stat -c '%g' /var/run/docker.sock) + groupadd --non-unique --gid ${DOCKER_GROUP} dockeronhost + usermod -aG dockeronhost www-data +fi + +# Copies over CSLI synctex to the host mounted volume, so it +# can be subsequently mounted in TexLive containers on Sandbox Compilation +SYNCTEX=/var/lib/sharelatex/bin/synctex +if [ ! -f "$SYNCTEX" ]; then + if [ "$DISABLE_SYNCTEX_BINARY_COPY" == "true" ]; then + echo ">> Copy of synctex executable disabled by DISABLE_SYNCTEX_BINARY_COPY flag, feature may not work" + else + echo ">> Copying synctex executable to the host" + mkdir -p $(dirname $SYNCTEX ) + cp /var/www/sharelatex/clsi/bin/synctex $SYNCTEX + fi +fi + +exec /sbin/setuser www-data /usr/bin/node $NODE_PARAMS /var/www/sharelatex/clsi/app.js >> /var/log/sharelatex/clsi.log 2>&1 diff --git a/server-ce/runit/contacts-sharelatex/run b/server-ce/runit/contacts-sharelatex/run new file mode 100755 index 0000000000..8de491ac6a --- /dev/null +++ b/server-ce/runit/contacts-sharelatex/run @@ -0,0 +1,9 @@ +#!/bin/bash + +NODE_PARAMS="" +if [ "$DEBUG_NODE" == "true" ]; then + echo "running debug - contacts" + NODE_PARAMS="--inspect=0.0.0.0:30360" +fi + +exec /sbin/setuser www-data /usr/bin/node $NODE_PARAMS /var/www/sharelatex/contacts/app.js >> /var/log/sharelatex/contacts.log 2>&1 diff --git a/server-ce/runit/docstore-sharelatex/run b/server-ce/runit/docstore-sharelatex/run new file mode 100755 index 0000000000..f6b3285358 --- /dev/null +++ b/server-ce/runit/docstore-sharelatex/run @@ -0,0 +1,9 @@ +#!/bin/bash + +NODE_PARAMS="" +if [ "$DEBUG_NODE" == "true" ]; then + echo "running debug - docstore" + NODE_PARAMS="--inspect=0.0.0.0:30160" +fi + +exec /sbin/setuser www-data /usr/bin/node $NODE_PARAMS /var/www/sharelatex/docstore/app.js >> /var/log/sharelatex/docstore.log 2>&1 diff --git a/server-ce/runit/document-updater-sharelatex/run b/server-ce/runit/document-updater-sharelatex/run new file mode 100755 index 0000000000..7d688a17de --- /dev/null +++ b/server-ce/runit/document-updater-sharelatex/run @@ -0,0 +1,9 @@ +#!/bin/bash + +NODE_PARAMS="" +if [ "$DEBUG_NODE" == "true" ]; then + echo "running debug - document updater" + NODE_PARAMS="--inspect=0.0.0.0:30030" +fi + +exec /sbin/setuser www-data /usr/bin/node $NODE_PARAMS /var/www/sharelatex/document-updater/app.js >> /var/log/sharelatex/document-updater.log 2>&1 diff --git a/server-ce/runit/filestore-sharelatex/run b/server-ce/runit/filestore-sharelatex/run new file mode 100755 index 0000000000..8baccbfe0b --- /dev/null +++ b/server-ce/runit/filestore-sharelatex/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec /sbin/setuser www-data /usr/bin/node /var/www/sharelatex/filestore/app.js >> /var/log/sharelatex/filestore.log 2>&1 diff --git a/server-ce/runit/nginx/run b/server-ce/runit/nginx/run new file mode 100755 index 0000000000..9eacfb4ff2 --- /dev/null +++ b/server-ce/runit/nginx/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec nginx \ No newline at end of file diff --git a/server-ce/runit/notifications-sharelatex/run b/server-ce/runit/notifications-sharelatex/run new file mode 100755 index 0000000000..721b1cf1e9 --- /dev/null +++ b/server-ce/runit/notifications-sharelatex/run @@ -0,0 +1,9 @@ +#!/bin/bash + +NODE_PARAMS="" +if [ "$DEBUG_NODE" == "true" ]; then + echo "running debug - notifications" + NODE_PARAMS="--inspect=0.0.0.0:30420" +fi + +exec /sbin/setuser www-data /usr/bin/node $NODE_PARAMS /var/www/sharelatex/notifications/app.js >> /var/log/sharelatex/notifications.log 2>&1 diff --git a/server-ce/runit/real-time-sharelatex/run b/server-ce/runit/real-time-sharelatex/run new file mode 100755 index 0000000000..392c4525ef --- /dev/null +++ b/server-ce/runit/real-time-sharelatex/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec /sbin/setuser www-data /usr/bin/node /var/www/sharelatex/real-time/app.js >> /var/log/sharelatex/real-time.log 2>&1 diff --git a/server-ce/runit/spelling-sharelatex/run b/server-ce/runit/spelling-sharelatex/run new file mode 100755 index 0000000000..af7941a4b4 --- /dev/null +++ b/server-ce/runit/spelling-sharelatex/run @@ -0,0 +1,9 @@ +#!/bin/bash + +NODE_PARAMS="" +if [ "$DEBUG_NODE" == "true" ]; then + echo "running debug - spelling" + NODE_PARAMS="--inspect=0.0.0.0:30050" +fi + +exec /sbin/setuser www-data /usr/bin/node $NODE_PARAMS /var/www/sharelatex/spelling/app.js >> /var/log/sharelatex/spelling.log 2>&1 diff --git a/server-ce/runit/track-changes-sharelatex/run b/server-ce/runit/track-changes-sharelatex/run new file mode 100755 index 0000000000..a137098588 --- /dev/null +++ b/server-ce/runit/track-changes-sharelatex/run @@ -0,0 +1,9 @@ +#!/bin/bash + +NODE_PARAMS="" +if [ "$DEBUG_NODE" == "true" ]; then + echo "running debug - track-changes" + NODE_PARAMS="--inspect=0.0.0.0:30150" +fi + +exec /sbin/setuser www-data /usr/bin/node $NODE_PARAMS /var/www/sharelatex/track-changes/app.js >> /var/log/sharelatex/track-changes.log 2>&1 diff --git a/server-ce/runit/web-sharelatex/run b/server-ce/runit/web-sharelatex/run new file mode 100755 index 0000000000..15fba8f806 --- /dev/null +++ b/server-ce/runit/web-sharelatex/run @@ -0,0 +1,9 @@ +#!/bin/bash + +NODE_PARAMS="" +if [ "$DEBUG_NODE" == "true" ]; then + echo "running debug - web" + NODE_PARAMS="--inspect=0.0.0.0:40000" +fi + +exec /sbin/setuser www-data /usr/bin/node $NODE_PARAMS /var/www/sharelatex/web/app.js >> /var/log/sharelatex/web.log 2>&1 diff --git a/server-ce/services.js b/server-ce/services.js new file mode 100644 index 0000000000..c47186a159 --- /dev/null +++ b/server-ce/services.js @@ -0,0 +1,63 @@ +module.exports = [ + { + name: 'web', + repo: 'https://github.com/sharelatex/web-sharelatex.git', + version: 'master', + }, + { + name: 'real-time', + repo: 'https://github.com/sharelatex/real-time-sharelatex.git', + version: 'master', + }, + { + name: 'document-updater', + repo: 'https://github.com/sharelatex/document-updater-sharelatex.git', + version: 'master', + }, + { + name: 'clsi', + repo: 'https://github.com/sharelatex/clsi-sharelatex.git', + version: 'master', + }, + { + name: 'filestore', + repo: 'https://github.com/sharelatex/filestore-sharelatex.git', + version: 'master', + }, + { + name: 'track-changes', + repo: 'https://github.com/sharelatex/track-changes-sharelatex.git', + version: 'master', + }, + { + name: 'docstore', + repo: 'https://github.com/sharelatex/docstore-sharelatex.git', + version: 'master', + }, + { + name: 'chat', + repo: 'https://github.com/sharelatex/chat-sharelatex.git', + version: 'master', + }, + { + name: 'spelling', + repo: 'https://github.com/sharelatex/spelling-sharelatex.git', + version: 'master', + }, + { + name: 'contacts', + repo: 'https://github.com/sharelatex/contacts-sharelatex.git', + version: 'master', + }, + { + name: 'notifications', + repo: 'https://github.com/sharelatex/notifications-sharelatex.git', + version: 'master', + }, +] + +if (require.main === module) { + for (const service of module.exports) { + console.log(service.name) + } +} diff --git a/server-ce/settings.js b/server-ce/settings.js new file mode 100644 index 0000000000..eb5a5f015e --- /dev/null +++ b/server-ce/settings.js @@ -0,0 +1,778 @@ +/* eslint-disable + camelcase, + no-cond-assign, + no-dupe-keys, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS205: Consider reworking code to avoid use of IIFEs + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let allTexLiveDockerImageNames, allTexLiveDockerImages, redisConfig, siteUrl +let e +const Path = require('path') + +// These credentials are used for authenticating api requests +// between services that may need to go over public channels +const httpAuthUser = 'sharelatex' +const httpAuthPass = process.env.WEB_API_PASSWORD +const httpAuthUsers = {} +httpAuthUsers[httpAuthUser] = httpAuthPass + +const parse = function (option) { + if (option != null) { + try { + const opt = JSON.parse(option) + return opt + } catch (err) { + throw new Error(`problem parsing ${option}, invalid JSON`) + } + } +} + +const parseIntOrFail = function (value) { + const parsedValue = parseInt(value, 10) + if (isNaN(parsedValue)) { + throw new Error(`'${value}' is an invalid integer`) + } + return parsedValue +} + +const DATA_DIR = '/var/lib/sharelatex/data' +const TMP_DIR = '/var/lib/sharelatex/tmp' + +const settings = { + clsi: { + optimiseInDocker: process.env.OPTIMISE_PDF === 'true', + }, + + brandPrefix: '', + + allowAnonymousReadAndWriteSharing: + process.env.SHARELATEX_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING === 'true', + + // Databases + // --------- + + // ShareLaTeX's main persistent data store is MongoDB (http://www.mongodb.org/) + // Documentation about the URL connection string format can be found at: + // + // http://docs.mongodb.org/manual/reference/connection-string/ + // + // The following works out of the box with Mongo's default settings: + mongo: { + url: process.env.SHARELATEX_MONGO_URL || 'mongodb://dockerhost/sharelatex', + }, + + // Redis is used in ShareLaTeX for high volume queries, like real-time + // editing, and session management. + // + // The following config will work with Redis's default settings: + redis: { + web: (redisConfig = { + host: process.env.SHARELATEX_REDIS_HOST || 'dockerhost', + port: process.env.SHARELATEX_REDIS_PORT || '6379', + password: process.env.SHARELATEX_REDIS_PASS || undefined, + key_schema: { + // document-updater + blockingKey({ doc_id }) { + return `Blocking:${doc_id}` + }, + docLines({ doc_id }) { + return `doclines:${doc_id}` + }, + docOps({ doc_id }) { + return `DocOps:${doc_id}` + }, + docVersion({ doc_id }) { + return `DocVersion:${doc_id}` + }, + docHash({ doc_id }) { + return `DocHash:${doc_id}` + }, + projectKey({ doc_id }) { + return `ProjectId:${doc_id}` + }, + docsInProject({ project_id }) { + return `DocsIn:${project_id}` + }, + ranges({ doc_id }) { + return `Ranges:${doc_id}` + }, + // document-updater:realtime + pendingUpdates({ doc_id }) { + return `PendingUpdates:${doc_id}` + }, + // document-updater:history + uncompressedHistoryOps({ doc_id }) { + return `UncompressedHistoryOps:${doc_id}` + }, + docsWithHistoryOps({ project_id }) { + return `DocsWithHistoryOps:${project_id}` + }, + // document-updater:lock + blockingKey({ doc_id }) { + return `Blocking:${doc_id}` + }, + // track-changes:lock + historyLock({ doc_id }) { + return `HistoryLock:${doc_id}` + }, + historyIndexLock({ project_id }) { + return `HistoryIndexLock:${project_id}` + }, + // track-changes:history + uncompressedHistoryOps({ doc_id }) { + return `UncompressedHistoryOps:${doc_id}` + }, + docsWithHistoryOps({ project_id }) { + return `DocsWithHistoryOps:${project_id}` + }, + // realtime + clientsInProject({ project_id }) { + return `clients_in_project:${project_id}` + }, + connectedUser({ project_id, client_id }) { + return `connected_user:${project_id}:${client_id}` + }, + }, + }), + fairy: redisConfig, + // track-changes and document-updater + realtime: redisConfig, + documentupdater: redisConfig, + lock: redisConfig, + history: redisConfig, + websessions: redisConfig, + api: redisConfig, + pubsub: redisConfig, + project_history: redisConfig, + }, + + // The compile server (the clsi) uses a SQL database to cache files and + // meta-data. sqlite is the default, and the load is low enough that this will + // be fine in production (we use sqlite at sharelatex.com). + // + // If you want to configure a different database, see the Sequelize documentation + // for available options: + // + // https://github.com/sequelize/sequelize/wiki/API-Reference-Sequelize#example-usage + // + mysql: { + clsi: { + database: 'clsi', + username: 'clsi', + password: '', + dialect: 'sqlite', + storage: Path.join(DATA_DIR, 'db.sqlite'), + }, + }, + + // File storage + // ------------ + + // ShareLaTeX can store binary files like images either locally or in Amazon + // S3. The default is locally: + filestore: { + backend: 'fs', + stores: { + user_files: Path.join(DATA_DIR, 'user_files'), + template_files: Path.join(DATA_DIR, 'template_files'), + }, + }, + + // To use Amazon S3 as a storage backend, comment out the above config, and + // uncomment the following, filling in your key, secret, and bucket name: + // + // filestore: + // backend: "s3" + // stores: + // user_files: "BUCKET_NAME" + // s3: + // key: "AWS_KEY" + // secret: "AWS_SECRET" + // + + trackchanges: { + continueOnError: true, + }, + + // Local disk caching + // ------------------ + path: { + // If we ever need to write something to disk (e.g. incoming requests + // that need processing but may be too big for memory), then write + // them to disk here: + dumpFolder: Path.join(TMP_DIR, 'dumpFolder'), + // Where to write uploads before they are processed + uploadFolder: Path.join(TMP_DIR, 'uploads'), + // Where to write the project to disk before running LaTeX on it + compilesDir: Path.join(DATA_DIR, 'compiles'), + // Where to cache downloaded URLs for the CLSI + clsiCacheDir: Path.join(DATA_DIR, 'cache'), + // Where to write the output files to disk after running LaTeX + outputDir: Path.join(DATA_DIR, 'output'), + }, + + // Server Config + // ------------- + + // Where your instance of ShareLaTeX can be found publicly. This is used + // when emails are sent out and in generated links: + siteUrl: (siteUrl = process.env.SHARELATEX_SITE_URL || 'http://localhost'), + + // The name this is used to describe your ShareLaTeX Installation + appName: process.env.SHARELATEX_APP_NAME || 'ShareLaTeX (Community Edition)', + + restrictInvitesToExistingAccounts: + process.env.SHARELATEX_RESTRICT_INVITES_TO_EXISTING_ACCOUNTS === 'true', + + nav: { + title: + process.env.SHARELATEX_NAV_TITLE || + process.env.SHARELATEX_APP_NAME || + 'ShareLaTeX Community Edition', + }, + + // The email address which users will be directed to as the main point of + // contact for this installation of ShareLaTeX. + adminEmail: process.env.SHARELATEX_ADMIN_EMAIL || 'placeholder@example.com', + + // If provided, a sessionSecret is used to sign cookies so that they cannot be + // spoofed. This is recommended. + security: { + sessionSecret: + process.env.SHARELATEX_SESSION_SECRET || process.env.CRYPTO_RANDOM, + }, + + // These credentials are used for authenticating api requests + // between services that may need to go over public channels + httpAuthUsers, + + // Should javascript assets be served minified or not. + useMinifiedJs: true, + + // Should static assets be sent with a header to tell the browser to cache + // them. This should be false in development where changes are being made, + // but should be set to true in production. + cacheStaticAssets: true, + + // If you are running ShareLaTeX over https, set this to true to send the + // cookie with a secure flag (recommended). + secureCookie: process.env.SHARELATEX_SECURE_COOKIE != null, + + // If you are running ShareLaTeX behind a proxy (like Apache, Nginx, etc) + // then set this to true to allow it to correctly detect the forwarded IP + // address and http/https protocol information. + + behindProxy: process.env.SHARELATEX_BEHIND_PROXY || false, + + i18n: { + subdomainLang: { + www: { + lngCode: process.env.SHARELATEX_SITE_LANGUAGE || 'en', + url: siteUrl, + }, + }, + defaultLng: process.env.SHARELATEX_SITE_LANGUAGE || 'en', + }, + + currentImageName: process.env.TEX_LIVE_DOCKER_IMAGE, + + apis: { + web: { + url: 'http://localhost:3000', + user: httpAuthUser, + pass: httpAuthPass, + }, + project_history: { + enabled: false, + }, + }, + references: {}, + notifications: undefined, + + defaultFeatures: { + collaborators: -1, + dropbox: true, + versioning: true, + compileTimeout: parseIntOrFail(process.env.COMPILE_TIMEOUT || 180), + compileGroup: 'standard', + trackChanges: true, + templates: true, + references: true, + }, +} + +// # OPTIONAL CONFIGURABLE SETTINGS + +if (process.env.SHARELATEX_LEFT_FOOTER != null) { + try { + settings.nav.left_footer = JSON.parse(process.env.SHARELATEX_LEFT_FOOTER) + } catch (error) { + e = error + console.error('could not parse SHARELATEX_LEFT_FOOTER, not valid JSON') + } +} + +if (process.env.SHARELATEX_RIGHT_FOOTER != null) { + settings.nav.right_footer = process.env.SHARELATEX_RIGHT_FOOTER + try { + settings.nav.right_footer = JSON.parse(process.env.SHARELATEX_RIGHT_FOOTER) + } catch (error1) { + e = error1 + console.error('could not parse SHARELATEX_RIGHT_FOOTER, not valid JSON') + } +} + +if (process.env.SHARELATEX_HEADER_IMAGE_URL != null) { + settings.nav.custom_logo = process.env.SHARELATEX_HEADER_IMAGE_URL +} + +if (process.env.SHARELATEX_HEADER_NAV_LINKS != null) { + console.error(`\ +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# +# WARNING: SHARELATEX_HEADER_NAV_LINKS is no longer supported +# See https://github.com/sharelatex/sharelatex/wiki/Configuring-Headers,-Footers-&-Logo +# +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\ +`) +} + +if (process.env.SHARELATEX_HEADER_EXTRAS != null) { + try { + settings.nav.header_extras = JSON.parse( + process.env.SHARELATEX_HEADER_EXTRAS + ) + } catch (error2) { + e = error2 + console.error('could not parse SHARELATEX_HEADER_EXTRAS, not valid JSON') + } +} + +// Sending Email +// ------------- +// +// You must configure a mail server to be able to send invite emails from +// ShareLaTeX. The config settings are passed to nodemailer. See the nodemailer +// documentation for available options: +// +// http://www.nodemailer.com/docs/transports + +if (process.env.SHARELATEX_EMAIL_FROM_ADDRESS != null) { + settings.email = { + fromAddress: process.env.SHARELATEX_EMAIL_FROM_ADDRESS, + replyTo: process.env.SHARELATEX_EMAIL_REPLY_TO || '', + driver: process.env.SHARELATEX_EMAIL_DRIVER, + parameters: { + // AWS Creds + AWSAccessKeyID: process.env.SHARELATEX_EMAIL_AWS_SES_ACCESS_KEY_ID, + AWSSecretKey: process.env.SHARELATEX_EMAIL_AWS_SES_SECRET_KEY, + + // SMTP Creds + host: process.env.SHARELATEX_EMAIL_SMTP_HOST, + port: process.env.SHARELATEX_EMAIL_SMTP_PORT, + secure: parse(process.env.SHARELATEX_EMAIL_SMTP_SECURE), + ignoreTLS: parse(process.env.SHARELATEX_EMAIL_SMTP_IGNORE_TLS), + name: process.env.SHARELATEX_EMAIL_SMTP_NAME, + logger: process.env.SHARELATEX_EMAIL_SMTP_LOGGER === 'true', + }, + + textEncoding: process.env.SHARELATEX_EMAIL_TEXT_ENCODING, + template: { + customFooter: process.env.SHARELATEX_CUSTOM_EMAIL_FOOTER, + }, + } + + if (process.env.SHARELATEX_EMAIL_AWS_SES_REGION != null) { + settings.email.parameters.region = + process.env.SHARELATEX_EMAIL_AWS_SES_REGION + } + + if ( + process.env.SHARELATEX_EMAIL_SMTP_USER != null || + process.env.SHARELATEX_EMAIL_SMTP_PASS != null + ) { + settings.email.parameters.auth = { + user: process.env.SHARELATEX_EMAIL_SMTP_USER, + pass: process.env.SHARELATEX_EMAIL_SMTP_PASS, + } + } + + if (process.env.SHARELATEX_EMAIL_SMTP_TLS_REJECT_UNAUTH != null) { + settings.email.parameters.tls = { + rejectUnauthorized: parse( + process.env.SHARELATEX_EMAIL_SMTP_TLS_REJECT_UNAUTH + ), + } + } +} + +// i18n +if (process.env.SHARELATEX_LANG_DOMAIN_MAPPING != null) { + settings.i18n.subdomainLang = parse( + process.env.SHARELATEX_LANG_DOMAIN_MAPPING + ) +} + +// Password Settings +// ----------- +// These restrict the passwords users can use when registering +// opts are from http://antelle.github.io/passfield +if ( + process.env.SHARELATEX_PASSWORD_VALIDATION_PATTERN || + process.env.SHARELATEX_PASSWORD_VALIDATION_MIN_LENGTH || + process.env.SHARELATEX_PASSWORD_VALIDATION_MAX_LENGTH +) { + settings.passwordStrengthOptions = { + pattern: process.env.SHARELATEX_PASSWORD_VALIDATION_PATTERN || 'aA$3', + length: { + min: process.env.SHARELATEX_PASSWORD_VALIDATION_MIN_LENGTH || 8, + max: process.env.SHARELATEX_PASSWORD_VALIDATION_MAX_LENGTH || 150, + }, + } +} + +// ###################### +// ShareLaTeX Server Pro +// ###################### + +if (parse(process.env.SHARELATEX_IS_SERVER_PRO) === true) { + settings.bypassPercentageRollouts = true + settings.apis.references = { url: 'http://localhost:3040' } +} + +// LDAP - SERVER PRO ONLY +// ---------- + +if (process.env.SHARELATEX_LDAP_HOST) { + console.error(`\ +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# +# WARNING: The LDAP configuration format has changed in version 0.5.1 +# See https://github.com/sharelatex/sharelatex/wiki/Server-Pro:-LDAP-Config +# +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\ +`) +} + +if (process.env.SHARELATEX_LDAP_URL) { + let _ldap_connect_timeout, + _ldap_group_search_attribs, + _ldap_search_attribs, + _ldap_timeout + settings.externalAuth = true + settings.ldap = { + emailAtt: process.env.SHARELATEX_LDAP_EMAIL_ATT, + nameAtt: process.env.SHARELATEX_LDAP_NAME_ATT, + lastNameAtt: process.env.SHARELATEX_LDAP_LAST_NAME_ATT, + updateUserDetailsOnLogin: + process.env.SHARELATEX_LDAP_UPDATE_USER_DETAILS_ON_LOGIN === 'true', + placeholder: process.env.SHARELATEX_LDAP_PLACEHOLDER, + server: { + url: process.env.SHARELATEX_LDAP_URL, + bindDn: process.env.SHARELATEX_LDAP_BIND_DN, + bindCredentials: process.env.SHARELATEX_LDAP_BIND_CREDENTIALS, + bindProperty: process.env.SHARELATEX_LDAP_BIND_PROPERTY, + searchBase: process.env.SHARELATEX_LDAP_SEARCH_BASE, + searchScope: process.env.SHARELATEX_LDAP_SEARCH_SCOPE, + searchFilter: process.env.SHARELATEX_LDAP_SEARCH_FILTER, + searchAttributes: (_ldap_search_attribs = + process.env.SHARELATEX_LDAP_SEARCH_ATTRIBUTES) + ? (() => { + try { + return JSON.parse(_ldap_search_attribs) + } catch (error3) { + e = error3 + return console.error( + 'could not parse SHARELATEX_LDAP_SEARCH_ATTRIBUTES' + ) + } + })() + : undefined, + groupDnProperty: process.env.SHARELATEX_LDAP_GROUP_DN_PROPERTY, + groupSearchBase: process.env.SHARELATEX_LDAP_GROUP_SEARCH_BASE, + groupSearchScope: process.env.SHARELATEX_LDAP_GROUP_SEARCH_SCOPE, + groupSearchFilter: process.env.SHARELATEX_LDAP_GROUP_SEARCH_FILTER, + groupSearchAttributes: (_ldap_group_search_attribs = + process.env.SHARELATEX_LDAP_GROUP_SEARCH_ATTRIBUTES) + ? (() => { + try { + return JSON.parse(_ldap_group_search_attribs) + } catch (error4) { + e = error4 + return console.error( + 'could not parse SHARELATEX_LDAP_GROUP_SEARCH_ATTRIBUTES' + ) + } + })() + : undefined, + cache: process.env.SHARELATEX_LDAP_CACHE === 'true', + timeout: (_ldap_timeout = process.env.SHARELATEX_LDAP_TIMEOUT) + ? (() => { + try { + return parseIntOrFail(_ldap_timeout) + } catch (error5) { + e = error5 + return console.error('Cannot parse SHARELATEX_LDAP_TIMEOUT') + } + })() + : undefined, + connectTimeout: (_ldap_connect_timeout = + process.env.SHARELATEX_LDAP_CONNECT_TIMEOUT) + ? (() => { + try { + return parseIntOrFail(_ldap_connect_timeout) + } catch (error6) { + e = error6 + return console.error( + 'Cannot parse SHARELATEX_LDAP_CONNECT_TIMEOUT' + ) + } + })() + : undefined, + }, + } + + if (process.env.SHARELATEX_LDAP_TLS_OPTS_CA_PATH) { + let ca, ca_paths + try { + ca = JSON.parse(process.env.SHARELATEX_LDAP_TLS_OPTS_CA_PATH) + } catch (error7) { + e = error7 + console.error( + 'could not parse SHARELATEX_LDAP_TLS_OPTS_CA_PATH, invalid JSON' + ) + } + + if (typeof ca === 'string') { + ca_paths = [ca] + } else if ( + typeof ca === 'object' && + (ca != null ? ca.length : undefined) != null + ) { + ca_paths = ca + } else { + console.error('problem parsing SHARELATEX_LDAP_TLS_OPTS_CA_PATH') + } + + settings.ldap.server.tlsOptions = { + rejectUnauthorized: + process.env.SHARELATEX_LDAP_TLS_OPTS_REJECT_UNAUTH === 'true', + ca: ca_paths, // e.g.'/etc/ldap/ca_certs.pem' + } + } +} + +if (process.env.SHARELATEX_SAML_ENTRYPOINT) { + // NOTE: see https://github.com/node-saml/passport-saml/blob/master/README.md for docs of `server` options + let _saml_additionalAuthorizeParams, + _saml_additionalLogoutParams, + _saml_additionalParams, + _saml_expiration, + _saml_skew + settings.externalAuth = true + settings.saml = { + updateUserDetailsOnLogin: + process.env.SHARELATEX_SAML_UPDATE_USER_DETAILS_ON_LOGIN === 'true', + identityServiceName: process.env.SHARELATEX_SAML_IDENTITY_SERVICE_NAME, + emailField: + process.env.SHARELATEX_SAML_EMAIL_FIELD || + process.env.SHARELATEX_SAML_EMAIL_FIELD_NAME, + firstNameField: process.env.SHARELATEX_SAML_FIRST_NAME_FIELD, + lastNameField: process.env.SHARELATEX_SAML_LAST_NAME_FIELD, + server: { + // strings + entryPoint: process.env.SHARELATEX_SAML_ENTRYPOINT, + callbackUrl: process.env.SHARELATEX_SAML_CALLBACK_URL, + issuer: process.env.SHARELATEX_SAML_ISSUER, + decryptionPvk: process.env.SHARELATEX_SAML_DECRYPTION_PVK, + decryptionCert: process.env.SHARELATEX_SAML_DECRYPTION_CERT, + signatureAlgorithm: process.env.SHARELATEX_SAML_SIGNATURE_ALGORITHM, + identifierFormat: process.env.SHARELATEX_SAML_IDENTIFIER_FORMAT, + attributeConsumingServiceIndex: + process.env.SHARELATEX_SAML_ATTRIBUTE_CONSUMING_SERVICE_INDEX, + authnContext: process.env.SHARELATEX_SAML_AUTHN_CONTEXT, + authnRequestBinding: process.env.SHARELATEX_SAML_AUTHN_REQUEST_BINDING, + validateInResponseTo: process.env.SHARELATEX_SAML_VALIDATE_IN_RESPONSE_TO, + cacheProvider: process.env.SHARELATEX_SAML_CACHE_PROVIDER, + logoutUrl: process.env.SHARELATEX_SAML_LOGOUT_URL, + logoutCallbackUrl: process.env.SHARELATEX_SAML_LOGOUT_CALLBACK_URL, + disableRequestedAuthnContext: + process.env.SHARELATEX_SAML_DISABLE_REQUESTED_AUTHN_CONTEXT === 'true', + forceAuthn: process.env.SHARELATEX_SAML_FORCE_AUTHN === 'true', + skipRequestCompression: + process.env.SHARELATEX_SAML_SKIP_REQUEST_COMPRESSION === 'true', + acceptedClockSkewMs: (_saml_skew = + process.env.SHARELATEX_SAML_ACCEPTED_CLOCK_SKEW_MS) + ? (() => { + try { + return parseIntOrFail(_saml_skew) + } catch (error8) { + e = error8 + return console.error( + 'Cannot parse SHARELATEX_SAML_ACCEPTED_CLOCK_SKEW_MS' + ) + } + })() + : undefined, + requestIdExpirationPeriodMs: (_saml_expiration = + process.env.SHARELATEX_SAML_REQUEST_ID_EXPIRATION_PERIOD_MS) + ? (() => { + try { + return parseIntOrFail(_saml_expiration) + } catch (error9) { + e = error9 + return console.error( + 'Cannot parse SHARELATEX_SAML_REQUEST_ID_EXPIRATION_PERIOD_MS' + ) + } + })() + : undefined, + additionalParams: (_saml_additionalParams = + process.env.SHARELATEX_SAML_ADDITIONAL_PARAMS) + ? (() => { + try { + return JSON.parse(_saml_additionalParams) + } catch (error10) { + e = error10 + return console.error( + 'Cannot parse SHARELATEX_SAML_ADDITIONAL_PARAMS' + ) + } + })() + : undefined, + additionalAuthorizeParams: (_saml_additionalAuthorizeParams = + process.env.SHARELATEX_SAML_ADDITIONAL_AUTHORIZE_PARAMS) + ? (() => { + try { + return JSON.parse(_saml_additionalAuthorizeParams) + } catch (error11) { + e = error11 + return console.error( + 'Cannot parse SHARELATEX_SAML_ADDITIONAL_AUTHORIZE_PARAMS' + ) + } + })() + : undefined, + additionalLogoutParams: (_saml_additionalLogoutParams = + process.env.SHARELATEX_SAML_ADDITIONAL_LOGOUT_PARAMS) + ? (() => { + try { + return JSON.parse(_saml_additionalLogoutParams) + } catch (error12) { + e = error12 + return console.error( + 'Cannot parse SHARELATEX_SAML_ADDITIONAL_LOGOUT_PARAMS' + ) + } + })() + : undefined, + }, + } + + // SHARELATEX_SAML_CERT cannot be empty + // https://github.com/node-saml/passport-saml/commit/f6b1c885c0717f1083c664345556b535f217c102 + if (process.env.SHARELATEX_SAML_CERT) { + settings.saml.server.cert = process.env.SHARELATEX_SAML_CERT + settings.saml.server.privateKey = process.env.SHARELATEX_SAML_PRIVATE_CERT + } +} + +// Compiler +// -------- +if (process.env.SANDBOXED_COMPILES === 'true') { + settings.clsi = { + dockerRunner: true, + docker: { + image: process.env.TEX_LIVE_DOCKER_IMAGE, + env: { + HOME: '/tmp', + PATH: + process.env.COMPILER_PATH || + '/usr/local/texlive/2015/bin/x86_64-linux:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', + }, + user: 'www-data', + }, + } + + if (settings.path == null) { + settings.path = {} + } + settings.path.synctexBaseDir = () => '/compile' + if (process.env.SANDBOXED_COMPILES_SIBLING_CONTAINERS === 'true') { + console.log('Using sibling containers for sandboxed compiles') + if (process.env.SANDBOXED_COMPILES_HOST_DIR) { + settings.path.sandboxedCompilesHostDir = + process.env.SANDBOXED_COMPILES_HOST_DIR + } else { + console.error( + 'Sibling containers, but SANDBOXED_COMPILES_HOST_DIR not set' + ) + } + } +} + +// Templates +// --------- +if (process.env.SHARELATEX_TEMPLATES_USER_ID) { + settings.templates = { + mountPointUrl: '/templates', + user_id: process.env.SHARELATEX_TEMPLATES_USER_ID, + } + + settings.templateLinks = parse( + process.env.SHARELATEX_NEW_PROJECT_TEMPLATE_LINKS + ) +} + +// /Learn +// ------- +if (process.env.SHARELATEX_PROXY_LEARN != null) { + settings.proxyLearn = parse(process.env.SHARELATEX_PROXY_LEARN) +} + +// /References +// ----------- +if (process.env.SHARELATEX_ELASTICSEARCH_URL != null) { + settings.references.elasticsearch = { + host: process.env.SHARELATEX_ELASTICSEARCH_URL, + } +} + +// TeX Live Images +// ----------- +if (process.env.ALL_TEX_LIVE_DOCKER_IMAGES != null) { + allTexLiveDockerImages = process.env.ALL_TEX_LIVE_DOCKER_IMAGES.split(',') +} +if (process.env.ALL_TEX_LIVE_DOCKER_IMAGE_NAMES != null) { + allTexLiveDockerImageNames = + process.env.ALL_TEX_LIVE_DOCKER_IMAGE_NAMES.split(',') +} +if (allTexLiveDockerImages != null) { + settings.allowedImageNames = [] + for (let index = 0; index < allTexLiveDockerImages.length; index++) { + const fullImageName = allTexLiveDockerImages[index] + const imageName = Path.basename(fullImageName) + const imageDesc = + allTexLiveDockerImageNames != null + ? allTexLiveDockerImageNames[index] + : imageName + settings.allowedImageNames.push({ imageName, imageDesc }) + } +} + +// With lots of incoming and outgoing HTTP connections to different services, +// sometimes long running, it is a good idea to increase the default number +// of sockets that Node will hold open. +const http = require('http') +http.globalAgent.maxSockets = 300 +const https = require('https') +https.globalAgent.maxSockets = 300 + +module.exports = settings diff --git a/server-ce/vendor/envsubst b/server-ce/vendor/envsubst new file mode 100644 index 0000000000..f7ad8081d0 Binary files /dev/null and b/server-ce/vendor/envsubst differ