diff --git a/server-ce/Dockerfile-base b/server-ce/Dockerfile-base index db88d6c60e..5b6912fd89 100644 --- a/server-ce/Dockerfile-base +++ b/server-ce/Dockerfile-base @@ -23,7 +23,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ && apt-get update \ && apt-get install -y \ unattended-upgrades \ - build-essential wget net-tools unzip time imagemagick optipng strace nginx git python3 python-is-python3 zlib1g-dev libpcre3-dev gettext-base libwww-perl ca-certificates curl gnupg \ + build-essential wget net-tools unzip time poppler-utils optipng strace nginx git python3 python-is-python3 zlib1g-dev libpcre3-dev gettext-base libwww-perl ca-certificates curl gnupg \ qpdf \ # upgrade base-image, batch all the upgrades together, rather than installing them on-by-one (which is slow!) && unattended-upgrade --verbose --no-minimal-upgrade-steps \ diff --git a/server-ce/config/settings.js b/server-ce/config/settings.js index 01851043dd..7d9fd7702e 100644 --- a/server-ce/config/settings.js +++ b/server-ce/config/settings.js @@ -468,6 +468,8 @@ switch (process.env.OVERLEAF_FILESTORE_BACKEND) { } } +settings.converter = process.env.CONVERTER || 'pdftocairo' + if ( !settings.trustedProxyIps.includes('loopback') && !settings.trustedProxyIps.includes('localhost') && diff --git a/server-ce/hotfix/5.5.8/Dockerfile b/server-ce/hotfix/5.5.8/Dockerfile new file mode 100644 index 0000000000..d1bd1a9a52 --- /dev/null +++ b/server-ce/hotfix/5.5.8/Dockerfile @@ -0,0 +1,13 @@ +FROM sharelatex/sharelatex:5.5.7 + +# Apply security updates to base image +RUN apt update && apt install -y linux-libc-dev \ + && unattended-upgrade --verbose --no-minimal-upgrade-steps \ + && apt purge -y imagemagick \ + && apt autoremove -y \ + && apt install -y poppler-utils \ + && rm -rf /var/lib/apt/lists/* + +# Update converter +COPY issue_31527.patch . +RUN patch -p0 < issue_31527.patch && rm issue_31527.patch diff --git a/server-ce/hotfix/5.5.8/issue_31527.patch b/server-ce/hotfix/5.5.8/issue_31527.patch new file mode 100644 index 0000000000..5e3e7565af --- /dev/null +++ b/server-ce/hotfix/5.5.8/issue_31527.patch @@ -0,0 +1,90 @@ +--- services/filestore/app/js/FileConverter.js ++++ services/filestore/app/js/FileConverter.js +@@ -21,49 +21,44 @@ + } + + async function convert(sourcePath, requestedFormat) { +- const width = '600x' ++ const width = 1500 + return await _convert(sourcePath, requestedFormat, [ +- 'convert', +- '-define', +- `pdf:fit-page=${width}`, +- '-flatten', +- '-density', +- '300', +- `${sourcePath}[0]`, ++ 'pdftocairo', ++ '-png', ++ '-singlefile', ++ '-scale-to-x', ++ width.toString(), ++ '-scale-to-y', ++ '-1', ++ sourcePath, + ]) + } + + async function thumbnail(sourcePath) { +- const width = '260x' +- return await convert(sourcePath, 'png', [ +- 'convert', +- '-flatten', +- '-background', +- 'white', +- '-density', +- '300', +- '-define', +- `pdf:fit-page=${width}`, +- `${sourcePath}[0]`, +- '-resize', +- width, ++ const width = 700 ++ return await _convert(sourcePath, 'png', [ ++ 'pdftocairo', ++ '-png', ++ '-singlefile', ++ '-scale-to-x', ++ width.toString(), ++ '-scale-to-y', ++ '-1', ++ sourcePath, + ]) + } + + async function preview(sourcePath) { +- const width = '548x' +- return await convert(sourcePath, 'png', [ +- 'convert', +- '-flatten', +- '-background', +- 'white', +- '-density', +- '300', +- '-define', +- `pdf:fit-page=${width}`, +- `${sourcePath}[0]`, +- '-resize', +- width, ++ const width = 1000 ++ return await _convert(sourcePath, 'png', [ ++ 'pdftocairo', ++ '-png', ++ '-singlefile', ++ '-scale-to-x', ++ width.toString(), ++ '-scale-to-y', ++ '-1', ++ sourcePath, + ]) + } + +@@ -77,7 +72,7 @@ + const timer = new metrics.Timer('imageConvert') + const destPath = `${sourcePath}.${requestedFormat}` + +- command.push(destPath) ++ command.push(sourcePath) + command = Settings.commands.convertCommandPrefix.concat(command) + + try { diff --git a/server-ce/hotfix/6.1.2/Dockerfile b/server-ce/hotfix/6.1.2/Dockerfile new file mode 100644 index 0000000000..7e24f5ae23 --- /dev/null +++ b/server-ce/hotfix/6.1.2/Dockerfile @@ -0,0 +1,13 @@ +FROM sharelatex/sharelatex:6.1.1 + +# Apply security updates to base image +RUN apt update && apt install -y linux-libc-dev \ + && unattended-upgrade --verbose --no-minimal-upgrade-steps \ + && apt purge -y imagemagick \ + && apt autoremove -y \ + && apt install -y poppler-utils \ + && rm -rf /var/lib/apt/lists/* + +# Update converter +COPY issue_31527.patch . +RUN patch -p0 < issue_31527.patch && rm issue_31527.patch diff --git a/server-ce/hotfix/6.1.2/issue_31527.patch b/server-ce/hotfix/6.1.2/issue_31527.patch new file mode 100644 index 0000000000..16fa6875d7 --- /dev/null +++ b/server-ce/hotfix/6.1.2/issue_31527.patch @@ -0,0 +1,91 @@ +--- services/filestore/app/js/FileConverter.js ++++ services/filestore/app/js/FileConverter.js +@@ -22,49 +22,44 @@ export default { + } + + async function convert(sourcePath, requestedFormat) { +- const width = '600x' ++ const width = 1500 + return await _convert(sourcePath, requestedFormat, [ +- 'convert', +- '-define', +- `pdf:fit-page=${width}`, +- '-flatten', +- '-density', +- '300', +- `${sourcePath}[0]`, ++ 'pdftocairo', ++ '-png', ++ '-singlefile', ++ '-scale-to-x', ++ width.toString(), ++ '-scale-to-y', ++ '-1', ++ sourcePath, + ]) + } + + async function thumbnail(sourcePath) { +- const width = '260x' +- return await convert(sourcePath, 'png', [ +- 'convert', +- '-flatten', +- '-background', +- 'white', +- '-density', +- '300', +- '-define', +- `pdf:fit-page=${width}`, +- `${sourcePath}[0]`, +- '-resize', +- width, ++ const width = 700 ++ return await _convert(sourcePath, 'png', [ ++ 'pdftocairo', ++ '-png', ++ '-singlefile', ++ '-scale-to-x', ++ width.toString(), ++ '-scale-to-y', ++ '-1', ++ sourcePath, + ]) + } + + async function preview(sourcePath) { +- const width = '548x' +- return await convert(sourcePath, 'png', [ +- 'convert', +- '-flatten', +- '-background', +- 'white', +- '-density', +- '300', +- '-define', +- `pdf:fit-page=${width}`, +- `${sourcePath}[0]`, +- '-resize', +- width, ++ const width = 1000 ++ return await _convert(sourcePath, 'png', [ ++ 'pdftocairo', ++ '-png', ++ '-singlefile', ++ '-scale-to-x', ++ width.toString(), ++ '-scale-to-y', ++ '-1', ++ sourcePath, + ]) + } + +@@ -78,7 +73,8 @@ async function _convert(sourcePath, requestedFormat, command) { + const timer = new metrics.Timer('imageConvert') + const destPath = `${sourcePath}.${requestedFormat}` + +- command.push(destPath) ++ const outputBaseName = sourcePath ++ command.push(outputBaseName) + command = Settings.commands.convertCommandPrefix.concat(command) + + try { diff --git a/services/filestore/app/js/FileConverter.js b/services/filestore/app/js/FileConverter.js index 6419e88313..cdde44b1e7 100644 --- a/services/filestore/app/js/FileConverter.js +++ b/services/filestore/app/js/FileConverter.js @@ -22,50 +22,91 @@ export default { } async function convert(sourcePath, requestedFormat) { - const width = '600x' - return await _convert(sourcePath, requestedFormat, [ - 'convert', - '-define', - `pdf:fit-page=${width}`, - '-flatten', - '-density', - '300', - `${sourcePath}[0]`, - ]) + if (Settings.converter === 'pdftocairo') { + const width = 1500 + return await _convert(sourcePath, requestedFormat, [ + 'pdftocairo', + '-png', + '-singlefile', + '-scale-to-x', + width.toString(), + '-scale-to-y', + '-1', // maintain aspect ratio + sourcePath, + ]) + } else { + const width = '600x' + return await _convert(sourcePath, requestedFormat, [ + 'convert', + '-define', + `pdf:fit-page=${width}`, + '-flatten', + '-density', + '300', + `${sourcePath}[0]`, + ]) + } } async function thumbnail(sourcePath) { - const width = '260x' - return await convert(sourcePath, 'png', [ - 'convert', - '-flatten', - '-background', - 'white', - '-density', - '300', - '-define', - `pdf:fit-page=${width}`, - `${sourcePath}[0]`, - '-resize', - width, - ]) + if (Settings.converter === 'pdftocairo') { + const width = 700 + return await _convert(sourcePath, 'png', [ + 'pdftocairo', + '-png', + '-singlefile', + '-scale-to-x', + width.toString(), + '-scale-to-y', + '-1', // maintain aspect ratio + sourcePath, + ]) + } else { + const width = '260x' + return await convert(sourcePath, 'png', [ + 'convert', + '-flatten', + '-background', + 'white', + '-density', + '300', + '-define', + `pdf:fit-page=${width}`, + `${sourcePath}[0]`, + '-resize', + width, + ]) + } } async function preview(sourcePath) { - const width = '548x' - return await convert(sourcePath, 'png', [ - 'convert', - '-flatten', - '-background', - 'white', - '-density', - '300', - '-define', - `pdf:fit-page=${width}`, - `${sourcePath}[0]`, - '-resize', - width, - ]) + const width = 1000 + if (Settings.converter === 'pdftocairo') { + return await _convert(sourcePath, 'png', [ + 'pdftocairo', + '-png', + '-singlefile', + '-scale-to-x', + width.toString(), + '-scale-to-y', + '-1', // maintain aspect ratio + sourcePath, + ]) + } else { + return await convert(sourcePath, 'png', [ + 'convert', + '-flatten', + '-background', + 'white', + '-density', + '300', + '-define', + `pdf:fit-page=${width}`, + `${sourcePath}[0]`, + '-resize', + width, + ]) + } } async function _convert(sourcePath, requestedFormat, command) { @@ -78,7 +119,7 @@ async function _convert(sourcePath, requestedFormat, command) { const timer = new metrics.Timer('imageConvert') const destPath = `${sourcePath}.${requestedFormat}` - command.push(destPath) + command.push(Settings.converter === 'pdftocairo' ? sourcePath : destPath) command = Settings.commands.convertCommandPrefix.concat(command) try { diff --git a/services/filestore/config/settings.defaults.cjs b/services/filestore/config/settings.defaults.cjs index 00ebe69f89..c545971d92 100644 --- a/services/filestore/config/settings.defaults.cjs +++ b/services/filestore/config/settings.defaults.cjs @@ -99,6 +99,8 @@ const settings = { enableConversions: process.env.ENABLE_CONVERSIONS === 'true', + converter: process.env.CONVERTER || 'imagemagick', + gracefulShutdownDelayInMs: parseInt(process.env.GRACEFUL_SHUTDOWN_DELAY_SECONDS ?? '30', 10) * 1000, }