Skip to content

常见问题

HolgerHuo edited this page Dec 17, 2024 · 1 revision

调整 Overleaf 允许的最大上传文件大小限制

如需调整 Overleaf 最大上传文件大小限制,需要在 sharelatex 容器中进行以下两处更改:

  1. 调整 Nginx 配置: 在 /etc/nginx/sites-enabled/overleaf.conf 中的 server 块增加 client_max_body_size 1g;
  2. 调整 Overleaf 配置: /etc/overleaf/settings.js,增加 maxUploadSize: 1024 * 1024 * 1024, // 1 GB

可通过 docker bind mount 挂载两份配置文件进容器。

示例: 在 docker-compose.yml 中添加以下内容:

services:
    sharelatex:
        volumes:
            ...
            - ./settings.js:/etc/overleaf/settings.js:ro
            - ./overleaf.conf:/etc/nginx/sites-enabled/overleaf.conf:ro

修改好的示例配置文件如下:

  • overleaf.conf
server {
	listen         80;
	server_name    _; # Catch all, see http://nginx.org/en/docs/http/server_names.html

	root /overleaf/services/web/public/;

        client_max_body_size 1m;

	# block external access to prometheus /metrics
	location /metrics {
		internal;
	}

	location / {
		proxy_pass http://127.0.0.1:4000;
		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;
	}

  # handle output files for specific users
  location ~ ^/project/([0-9a-f]+)/user/([0-9a-f]+)/build/([0-9a-f-]+)/output/output\.([a-z]+)$ {
		proxy_pass http://127.0.0.1:8080; # clsi-nginx.conf
		proxy_http_version 1.1;
  }
  # handle output files for anonymous users
  location ~ ^/project/([0-9a-f]+)/build/([0-9a-f-]+)/output/output\.([a-z]+)$ {
		proxy_pass http://127.0.0.1:8080; # clsi-nginx.conf
		proxy_http_version 1.1;
  }
  # PDF range for specific users
  location ~ ^/project/([0-9a-f]+)/user/([0-9a-f]+)/content/([0-9a-f-]+/[0-9a-f]+)$ {
		proxy_pass http://127.0.0.1:8080; # clsi-nginx.conf
		proxy_http_version 1.1;
  }
  # PDF range for anonymous users
  location ~ ^/project/([0-9a-f]+)/content/([0-9a-f-]+/[0-9a-f]+)$ {
		proxy_pass http://127.0.0.1:8080; # clsi-nginx.conf
		proxy_http_version 1.1;
  }

  # block external access to metrics
  location ~* ^/metrics/?$ {
    return 404 'Not found';
  }

  # block external access to all health checks /health_check, /health_check/full, etc
  location ~* ^/health_check {
    return 404 'Not found';
  }

  # Load any extra configuration for this vhost
  include /etc/nginx/vhost-extras/overleaf/*.conf;
}
  • settings.js
/* 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 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 = process.env.WEB_API_USER
const httpAuthPass = process.env.WEB_API_PASSWORD
const httpAuthUsers = {}
if (httpAuthUser && httpAuthPass) {
  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/overleaf/data'
const TMP_DIR = '/var/lib/overleaf/tmp'

const images = process.env.ALL_TEX_LIVE_DOCKER_IMAGES.split(',')
const imageNames = process.env.ALL_TEX_LIVE_DOCKER_IMAGE_NAMES.split(',')

if (images.length !== imageNames.length) {
  throw new Error(`image and imageName count mismatched`)
}
const allowedImageNames = []
images.forEach((_, i) => {
  allowedImageNames.push({imageName: images[i], imageDesc: imageNames[i]})
});

const settings = {
  maxUploadSize: 1024 * 1024 * 1024, // 1 GB
  clsi: {
    optimiseInDocker: process.env.OPTIMISE_PDF === 'true',
    dockerRunner: process.env.DOCKER_RUNNER === 'true',
    docker: {
      maxContainerAge: process.env.SANDBOXED_COMPILES_CONTAINER_TIMEOUT,
      image: process.env.TEX_LIVE_DOCKER_IMAGE,
      user: process.env.DOCKER_USER,
    },
  },

  allowedImageNames: allowedImageNames,

  brandPrefix: '',

  allowAnonymousReadAndWriteSharing:
    process.env.OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING === 'true',

  // Databases
  // ---------

  // Overleaf Community Edition'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.OVERLEAF_MONGO_URL || 'mongodb://dockerhost/sharelatex',
  },

  // Redis is used in Overleaf Community Edition 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.OVERLEAF_REDIS_HOST || 'dockerhost',
      port: process.env.OVERLEAF_REDIS_PORT || '6379',
      password: process.env.OVERLEAF_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}`
        },
        // realtime
        clientsInProject({ project_id }) {
          return `clients_in_project:${project_id}`
        },
        connectedUser({ project_id, client_id }) {
          return `connected_user:${project_id}:${client_id}`
        },
      },
    }),
    fairy: redisConfig,
    // document-updater
    realtime: redisConfig,
    documentupdater: redisConfig,
    lock: redisConfig,
    history: redisConfig,
    websessions: redisConfig,
    api: redisConfig,
    pubsub: redisConfig,
    project_history: redisConfig,

    project_history_migration: {
      host: redisConfig.host,
      port: redisConfig.port,
      password: redisConfig.password,
      maxRetriesPerRequest: parseInt(
        process.env.REDIS_MAX_RETRIES_PER_REQUEST || '20'
      ),
      key_schema: {
        projectHistoryOps({ projectId }) {
          return `ProjectHistory:Ops:{${projectId}}` // NOTE: the extra braces are intentional
        },
      },
    },
  },

  // 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 intermediate file for full project history migration
    projectHistories: Path.join(TMP_DIR, 'projectHistories'),
    // 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'),
    sandboxedCompilesHostDir: process.env.SANDBOXED_COMPILES_HOST_DIR,
  },

  // Server Config
  // -------------

  // Where your instance of Overleaf Community Edition can be found publicly. This is used
  // when emails are sent out and in generated links:
  siteUrl: (siteUrl = process.env.OVERLEAF_SITE_URL || 'http://localhost'),

  // Status page URL as displayed on the maintenance/500 pages.
  statusPageUrl: process.env.OVERLEAF_STATUS_PAGE_URL,

  // The name this is used to describe your Overleaf Community Edition Installation
  appName: process.env.OVERLEAF_APP_NAME || 'Overleaf Community Edition',

  restrictInvitesToExistingAccounts:
    process.env.OVERLEAF_RESTRICT_INVITES_TO_EXISTING_ACCOUNTS === 'true',

  nav: {
    title:
      process.env.OVERLEAF_NAV_TITLE ||
      process.env.OVERLEAF_APP_NAME ||
      'Overleaf Community Edition',
  },

  // The email address which users will be directed to as the main point of
  // contact for this installation of Overleaf Community Edition.
  adminEmail: process.env.OVERLEAF_ADMIN_EMAIL || '[email protected]',

  // If provided, a sessionSecret is used to sign cookies so that they cannot be
  // spoofed. This is recommended.
  security: {
    sessionSecret:
      process.env.OVERLEAF_SESSION_SECRET || process.env.CRYPTO_RANDOM,
  },

  csp: {
    enabled: process.env.OVERLEAF_CSP_ENABLED !== 'false',
  },

  // 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 Overleaf Community Edition over https, set this to true to send the
  // cookie with a secure flag (recommended).
  secureCookie: process.env.OVERLEAF_SECURE_COOKIE != null,

  // If you are running Overleaf Community Edition 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.OVERLEAF_BEHIND_PROXY || false,
  trustedProxyIps: process.env.OVERLEAF_TRUSTED_PROXY_IPS,

  // The amount of time, in milliseconds, until the (rolling) cookie session expires
  cookieSessionLength: parseInt(
    process.env.OVERLEAF_COOKIE_SESSION_LENGTH || 5 * 24 * 60 * 60 * 1000, // default 5 days
    10
  ),

  redisLockTTLSeconds: parseInt(
    process.env.OVERLEAF_REDIS_LOCK_TTL_SECONDS || '60',
    10
  ),

  i18n: {
    subdomainLang: {
      www: {
        lngCode: process.env.OVERLEAF_SITE_LANGUAGE || 'en',
        url: siteUrl,
      },
    },
    defaultLng: process.env.OVERLEAF_SITE_LANGUAGE || 'en',
  },

  currentImageName: process.env.TEX_LIVE_DOCKER_IMAGE,

  apis: {
    web: {
      url: 'http://127.0.0.1:3000',
      user: httpAuthUser,
      pass: httpAuthPass,
    },
    project_history: {
      sendProjectStructureOps: true,
      url: 'http://127.0.0.1:3054',
    },
    v1_history: {
      url: process.env.V1_HISTORY_URL || 'http://127.0.0.1:3100/api',
      user: 'staging',
      pass: process.env.STAGING_PASSWORD,
      requestTimeout: parseInt(
        process.env.OVERLEAF_HISTORY_V1_HTTP_REQUEST_TIMEOUT || '300000', // default is 5min
        10
      ),
    },
  },
  references: {},
  notifications: undefined,

  defaultFeatures: {
    collaborators: -1,
    dropbox: true,
    versioning: true,
    compileTimeout: parseIntOrFail(process.env.COMPILE_TIMEOUT || 180),
    compileGroup: 'standard',
    trackChanges: true,
    references: true,
  },
}

// # OPTIONAL CONFIGURABLE SETTINGS

if (process.env.OVERLEAF_LEFT_FOOTER != null) {
  try {
    settings.nav.left_footer = JSON.parse(process.env.OVERLEAF_LEFT_FOOTER)
  } catch (error) {
    e = error
    console.error('could not parse OVERLEAF_LEFT_FOOTER, not valid JSON')
  }
}

if (process.env.OVERLEAF_RIGHT_FOOTER != null) {
  settings.nav.right_footer = process.env.OVERLEAF_RIGHT_FOOTER
  try {
    settings.nav.right_footer = JSON.parse(process.env.OVERLEAF_RIGHT_FOOTER)
  } catch (error1) {
    e = error1
    console.error('could not parse OVERLEAF_RIGHT_FOOTER, not valid JSON')
  }
}

if (process.env.OVERLEAF_HEADER_IMAGE_URL != null) {
  settings.nav.custom_logo = process.env.OVERLEAF_HEADER_IMAGE_URL
}

if (process.env.OVERLEAF_HEADER_EXTRAS != null) {
  try {
    settings.nav.header_extras = JSON.parse(process.env.OVERLEAF_HEADER_EXTRAS)
  } catch (error2) {
    e = error2
    console.error('could not parse OVERLEAF_HEADER_EXTRAS, not valid JSON')
  }
}

// Sending Email
// -------------
//
// You must configure a mail server to be able to send invite emails from
// Overleaf Community Edition. The config settings are passed to nodemailer. See the nodemailer
// documentation for available options:
//
//     http://www.nodemailer.com/docs/transports

if (process.env.OVERLEAF_EMAIL_FROM_ADDRESS != null) {
  settings.email = {
    fromAddress: process.env.OVERLEAF_EMAIL_FROM_ADDRESS,
    replyTo: process.env.OVERLEAF_EMAIL_REPLY_TO || '',
    driver: process.env.OVERLEAF_EMAIL_DRIVER,
    parameters: {
      // AWS Creds
      AWSAccessKeyID: process.env.OVERLEAF_EMAIL_AWS_SES_ACCESS_KEY_ID,
      AWSSecretKey: process.env.OVERLEAF_EMAIL_AWS_SES_SECRET_KEY,

      // SMTP Creds
      host: process.env.OVERLEAF_EMAIL_SMTP_HOST,
      port: process.env.OVERLEAF_EMAIL_SMTP_PORT,
      secure: parse(process.env.OVERLEAF_EMAIL_SMTP_SECURE),
      ignoreTLS: parse(process.env.OVERLEAF_EMAIL_SMTP_IGNORE_TLS),
      name: process.env.OVERLEAF_EMAIL_SMTP_NAME,
      logger: process.env.OVERLEAF_EMAIL_SMTP_LOGGER === 'true',
    },

    textEncoding: process.env.OVERLEAF_EMAIL_TEXT_ENCODING,
    template: {
      customFooter: process.env.OVERLEAF_CUSTOM_EMAIL_FOOTER,
    },
  }

  if (process.env.OVERLEAF_EMAIL_AWS_SES_REGION != null) {
    settings.email.parameters.region = process.env.OVERLEAF_EMAIL_AWS_SES_REGION
  }

  if (
    process.env.OVERLEAF_EMAIL_SMTP_USER != null ||
    process.env.OVERLEAF_EMAIL_SMTP_PASS != null
  ) {
    settings.email.parameters.auth = {
      user: process.env.OVERLEAF_EMAIL_SMTP_USER,
      pass: process.env.OVERLEAF_EMAIL_SMTP_PASS,
    }
  }

  if (process.env.OVERLEAF_EMAIL_SMTP_TLS_REJECT_UNAUTH != null) {
    settings.email.parameters.tls = {
      rejectUnauthorized: parse(
        process.env.OVERLEAF_EMAIL_SMTP_TLS_REJECT_UNAUTH
      ),
    }
  }
}

// i18n
if (process.env.OVERLEAF_LANG_DOMAIN_MAPPING != null) {
  settings.i18n.subdomainLang = parse(process.env.OVERLEAF_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.OVERLEAF_PASSWORD_VALIDATION_PATTERN ||
  process.env.OVERLEAF_PASSWORD_VALIDATION_MIN_LENGTH ||
  process.env.OVERLEAF_PASSWORD_VALIDATION_MAX_LENGTH
) {
  settings.passwordStrengthOptions = {
    pattern: process.env.OVERLEAF_PASSWORD_VALIDATION_PATTERN || 'aA$3',
    length: {
      min: process.env.OVERLEAF_PASSWORD_VALIDATION_MIN_LENGTH || 8,
      max: process.env.OVERLEAF_PASSWORD_VALIDATION_MAX_LENGTH || 72,
    },
  }
}

// /References
// -----------
if (process.env.OVERLEAF_ELASTICSEARCH_URL != null) {
  settings.references.elasticsearch = {
    host: process.env.OVERLEAF_ELASTICSEARCH_URL,
  }
}

// filestore
switch (process.env.OVERLEAF_FILESTORE_BACKEND) {
  case 's3':
    settings.filestore = {
      backend: 's3',
      stores: {
        user_files: process.env.OVERLEAF_FILESTORE_USER_FILES_BUCKET_NAME,
        template_files:
          process.env.OVERLEAF_FILESTORE_TEMPLATE_FILES_BUCKET_NAME,
      },
      s3: {
        key:
          process.env.OVERLEAF_FILESTORE_S3_ACCESS_KEY_ID ||
          process.env.AWS_ACCESS_KEY_ID,
        secret:
          process.env.OVERLEAF_FILESTORE_S3_SECRET_ACCESS_KEY ||
          process.env.AWS_SECRET_ACCESS_KEY,
        endpoint: process.env.OVERLEAF_FILESTORE_S3_ENDPOINT,
        pathStyle: process.env.OVERLEAF_FILESTORE_S3_PATH_STYLE === 'true',
        region:
          process.env.OVERLEAF_FILESTORE_S3_REGION ||
          process.env.AWS_DEFAULT_REGION,
      },
    }
    break
  default:
    settings.filestore = {
      backend: 'fs',
      stores: {
        user_files: Path.join(DATA_DIR, 'user_files'),
        template_files: Path.join(DATA_DIR, 'template_files'),
      },
    }
}

// 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
Clone this wiki locally