mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 09:09:36 +02:00
[CE/SP] Hotfix 5.5.1 (#26091)
* [CE/SP] Hotfix 5.5.1 * [web] Fix License tab in CE/SP * Added patch to improve logging * Added patch to fix create-user.mjs * Added check for `featureCompatibilityVersion` on CE/SP startup * Patch with `multer` and `tar-fs` updates * Install manually missing @paralleldrive/cuid2 on CE 5.1.1 GitOrigin-RevId: 0138dffdcb171382014a383bee13676fc873b1dd
This commit is contained in:
28
server-ce/hotfix/5.5.1/Dockerfile
Normal file
28
server-ce/hotfix/5.5.1/Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
||||
FROM sharelatex/sharelatex:5.5.0
|
||||
|
||||
|
||||
# fix tls configuration in redis for history-v1
|
||||
COPY pr_25168.patch .
|
||||
RUN patch -p1 < pr_25168.patch && rm pr_25168.patch
|
||||
|
||||
# improve logging in history system
|
||||
COPY pr_26086.patch .
|
||||
RUN patch -p1 < pr_26086.patch && rm pr_26086.patch
|
||||
|
||||
# fix create-user.mjs script
|
||||
COPY pr_26152.patch .
|
||||
RUN patch -p1 < pr_26152.patch && rm pr_26152.patch
|
||||
|
||||
# check mongo featureCompatibilityVersion
|
||||
COPY pr_26091.patch .
|
||||
RUN patch -p1 < pr_26091.patch && rm pr_26091.patch
|
||||
|
||||
# update multer and tar-fs
|
||||
RUN sed -i 's/"multer": "2.0.0"/"multer": "2.0.1"/g' package.json
|
||||
RUN sed -i 's/"dockerode": "^4.0.5"/"dockerode": "^4.0.7"/g' services/clsi/package.json
|
||||
RUN sed -i 's/"tar-fs": "^3.0.4"/"tar-fs": "^3.0.9"/g' services/clsi/package.json
|
||||
RUN sed -i 's/199c5ff05bd375c508f4074498237baead7f5148/4dbceda355efc3fc8ac3cf5c66c3778c8a6fdb23/g' services/web/package.json
|
||||
COPY package-lock.json.diff .
|
||||
RUN patch package-lock.json < package-lock.json.diff
|
||||
RUN npm install --omit=dev
|
||||
RUN npm install @paralleldrive/cuid2@2.2.2 -w services/history-v1
|
||||
2202
server-ce/hotfix/5.5.1/package-lock.json.diff
Normal file
2202
server-ce/hotfix/5.5.1/package-lock.json.diff
Normal file
File diff suppressed because it is too large
Load Diff
19
server-ce/hotfix/5.5.1/pr_25168.patch
Normal file
19
server-ce/hotfix/5.5.1/pr_25168.patch
Normal file
@@ -0,0 +1,19 @@
|
||||
--- a/services/history-v1/config/custom-environment-variables.json
|
||||
+++ b/services/history-v1/config/custom-environment-variables.json
|
||||
@@ -50,12 +50,14 @@
|
||||
"history": {
|
||||
"host": "OVERLEAF_REDIS_HOST",
|
||||
"password": "OVERLEAF_REDIS_PASS",
|
||||
- "port": "OVERLEAF_REDIS_PORT"
|
||||
+ "port": "OVERLEAF_REDIS_PORT",
|
||||
+ "tls": "OVERLEAF_REDIS_TLS"
|
||||
},
|
||||
"lock": {
|
||||
"host": "OVERLEAF_REDIS_HOST",
|
||||
"password": "OVERLEAF_REDIS_PASS",
|
||||
- "port": "OVERLEAF_REDIS_PORT"
|
||||
+ "port": "OVERLEAF_REDIS_PORT",
|
||||
+ "tls": "OVERLEAF_REDIS_TLS"
|
||||
}
|
||||
}
|
||||
}
|
||||
200
server-ce/hotfix/5.5.1/pr_26086.patch
Normal file
200
server-ce/hotfix/5.5.1/pr_26086.patch
Normal file
@@ -0,0 +1,200 @@
|
||||
--- a/services/history-v1/api/controllers/project_import.js
|
||||
+++ b/services/history-v1/api/controllers/project_import.js
|
||||
@@ -35,6 +35,7 @@ async function importSnapshot(req, res) {
|
||||
try {
|
||||
snapshot = Snapshot.fromRaw(rawSnapshot)
|
||||
} catch (err) {
|
||||
+ logger.warn({ err, projectId }, 'failed to import snapshot')
|
||||
return render.unprocessableEntity(res)
|
||||
}
|
||||
|
||||
@@ -43,6 +44,7 @@ async function importSnapshot(req, res) {
|
||||
historyId = await chunkStore.initializeProject(projectId, snapshot)
|
||||
} catch (err) {
|
||||
if (err instanceof chunkStore.AlreadyInitialized) {
|
||||
+ logger.warn({ err, projectId }, 'already initialized')
|
||||
return render.conflict(res)
|
||||
} else {
|
||||
throw err
|
||||
--- a/services/history-v1/api/controllers/projects.js
|
||||
+++ b/services/history-v1/api/controllers/projects.js
|
||||
@@ -34,6 +34,7 @@ async function initializeProject(req, res, next) {
|
||||
res.status(HTTPStatus.OK).json({ projectId })
|
||||
} catch (err) {
|
||||
if (err instanceof chunkStore.AlreadyInitialized) {
|
||||
+ logger.warn({ err, projectId }, 'failed to initialize')
|
||||
render.conflict(res)
|
||||
} else {
|
||||
throw err
|
||||
@@ -242,11 +243,15 @@ async function createProjectBlob(req, res, next) {
|
||||
const sizeLimit = new StreamSizeLimit(maxUploadSize)
|
||||
await pipeline(req, sizeLimit, fs.createWriteStream(tmpPath))
|
||||
if (sizeLimit.sizeLimitExceeded) {
|
||||
+ logger.warn(
|
||||
+ { projectId, expectedHash, maxUploadSize },
|
||||
+ 'blob exceeds size threshold'
|
||||
+ )
|
||||
return render.requestEntityTooLarge(res)
|
||||
}
|
||||
const hash = await blobHash.fromFile(tmpPath)
|
||||
if (hash !== expectedHash) {
|
||||
- logger.debug({ hash, expectedHash }, 'Hash mismatch')
|
||||
+ logger.warn({ projectId, hash, expectedHash }, 'Hash mismatch')
|
||||
return render.conflict(res, 'File hash mismatch')
|
||||
}
|
||||
|
||||
@@ -343,6 +348,10 @@ async function copyProjectBlob(req, res, next) {
|
||||
targetBlobStore.getBlob(blobHash),
|
||||
])
|
||||
if (!sourceBlob) {
|
||||
+ logger.warn(
|
||||
+ { sourceProjectId, targetProjectId, blobHash },
|
||||
+ 'missing source blob when copying across projects'
|
||||
+ )
|
||||
return render.notFound(res)
|
||||
}
|
||||
// Exit early if the blob exists in the target project.
|
||||
--- a/services/history-v1/app.js
|
||||
+++ b/services/history-v1/app.js
|
||||
@@ -100,11 +100,13 @@ function setupErrorHandling() {
|
||||
})
|
||||
}
|
||||
if (err.code === 'ENUM_MISMATCH') {
|
||||
+ logger.warn({ err, projectId }, err.message)
|
||||
return res.status(HTTPStatus.UNPROCESSABLE_ENTITY).json({
|
||||
message: 'invalid enum value: ' + err.paramName,
|
||||
})
|
||||
}
|
||||
if (err.code === 'REQUIRED') {
|
||||
+ logger.warn({ err, projectId }, err.message)
|
||||
return res.status(HTTPStatus.UNPROCESSABLE_ENTITY).json({
|
||||
message: err.message,
|
||||
})
|
||||
--- a/services/project-history/app/js/HistoryStoreManager.js
|
||||
+++ b/services/project-history/app/js/HistoryStoreManager.js
|
||||
@@ -35,7 +35,10 @@ class StringStream extends stream.Readable {
|
||||
_mocks.getMostRecentChunk = (projectId, historyId, callback) => {
|
||||
const path = `projects/${historyId}/latest/history`
|
||||
logger.debug({ projectId, historyId }, 'getting chunk from history service')
|
||||
- _requestChunk({ path, json: true }, callback)
|
||||
+ _requestChunk({ path, json: true }, (err, chunk) => {
|
||||
+ if (err) return callback(OError.tag(err))
|
||||
+ callback(null, chunk)
|
||||
+ })
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,7 +57,10 @@ export function getChunkAtVersion(projectId, historyId, version, callback) {
|
||||
{ projectId, historyId, version },
|
||||
'getting chunk from history service for version'
|
||||
)
|
||||
- _requestChunk({ path, json: true }, callback)
|
||||
+ _requestChunk({ path, json: true }, (err, chunk) => {
|
||||
+ if (err) return callback(OError.tag(err))
|
||||
+ callback(null, chunk)
|
||||
+ })
|
||||
}
|
||||
|
||||
export function getMostRecentVersion(projectId, historyId, callback) {
|
||||
@@ -68,8 +74,10 @@ export function getMostRecentVersion(projectId, historyId, callback) {
|
||||
_.sortBy(chunk.chunk.history.changes || [], x => x.timestamp)
|
||||
)
|
||||
// find the latest project and doc versions in the chunk
|
||||
- _getLatestProjectVersion(projectId, chunk, (err1, projectVersion) =>
|
||||
+ _getLatestProjectVersion(projectId, chunk, (err1, projectVersion) => {
|
||||
+ if (err1) err1 = OError.tag(err1)
|
||||
_getLatestV2DocVersions(projectId, chunk, (err2, v2DocVersions) => {
|
||||
+ if (err2) err2 = OError.tag(err2)
|
||||
// return the project and doc versions
|
||||
const projectStructureAndDocVersions = {
|
||||
project: projectVersion,
|
||||
@@ -83,7 +91,7 @@ export function getMostRecentVersion(projectId, historyId, callback) {
|
||||
chunk
|
||||
)
|
||||
})
|
||||
- )
|
||||
+ })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -211,7 +219,10 @@ export function getProjectBlob(historyId, blobHash, callback) {
|
||||
logger.debug({ historyId, blobHash }, 'getting blob from history service')
|
||||
_requestHistoryService(
|
||||
{ path: `projects/${historyId}/blobs/${blobHash}` },
|
||||
- callback
|
||||
+ (err, blob) => {
|
||||
+ if (err) return callback(OError.tag(err))
|
||||
+ callback(null, blob)
|
||||
+ }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -277,7 +288,10 @@ function createBlobFromString(historyId, data, fileId, callback) {
|
||||
(fsPath, cb) => {
|
||||
_createBlob(historyId, fsPath, cb)
|
||||
},
|
||||
- callback
|
||||
+ (err, hash) => {
|
||||
+ if (err) return callback(OError.tag(err))
|
||||
+ callback(null, hash)
|
||||
+ }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -330,7 +344,7 @@ export function createBlobForUpdate(projectId, historyId, update, callback) {
|
||||
try {
|
||||
ranges = HistoryBlobTranslator.createRangeBlobDataFromUpdate(update)
|
||||
} catch (error) {
|
||||
- return callback(error)
|
||||
+ return callback(OError.tag(error))
|
||||
}
|
||||
createBlobFromString(
|
||||
historyId,
|
||||
@@ -338,7 +352,7 @@ export function createBlobForUpdate(projectId, historyId, update, callback) {
|
||||
`project-${projectId}-doc-${update.doc}`,
|
||||
(err, fileHash) => {
|
||||
if (err) {
|
||||
- return callback(err)
|
||||
+ return callback(OError.tag(err))
|
||||
}
|
||||
if (ranges) {
|
||||
createBlobFromString(
|
||||
@@ -347,7 +361,7 @@ export function createBlobForUpdate(projectId, historyId, update, callback) {
|
||||
`project-${projectId}-doc-${update.doc}-ranges`,
|
||||
(err, rangesHash) => {
|
||||
if (err) {
|
||||
- return callback(err)
|
||||
+ return callback(OError.tag(err))
|
||||
}
|
||||
logger.debug(
|
||||
{ fileHash, rangesHash },
|
||||
@@ -415,7 +429,7 @@ export function createBlobForUpdate(projectId, historyId, update, callback) {
|
||||
},
|
||||
(err, fileHash) => {
|
||||
if (err) {
|
||||
- return callback(err)
|
||||
+ return callback(OError.tag(err))
|
||||
}
|
||||
if (update.hash && update.hash !== fileHash) {
|
||||
logger.warn(
|
||||
@@ -447,7 +461,7 @@ export function createBlobForUpdate(projectId, historyId, update, callback) {
|
||||
},
|
||||
(err, fileHash) => {
|
||||
if (err) {
|
||||
- return callback(err)
|
||||
+ return callback(OError.tag(err))
|
||||
}
|
||||
logger.debug({ fileHash }, 'created empty blob for file')
|
||||
callback(null, { file: fileHash })
|
||||
@@ -520,7 +534,10 @@ export function initializeProject(historyId, callback) {
|
||||
export function deleteProject(projectId, callback) {
|
||||
_requestHistoryService(
|
||||
{ method: 'DELETE', path: `projects/${projectId}` },
|
||||
- callback
|
||||
+ err => {
|
||||
+ if (err) return callback(OError.tag(err))
|
||||
+ callback(null)
|
||||
+ }
|
||||
)
|
||||
}
|
||||
|
||||
60
server-ce/hotfix/5.5.1/pr_26091.patch
Normal file
60
server-ce/hotfix/5.5.1/pr_26091.patch
Normal file
@@ -0,0 +1,60 @@
|
||||
--- a/services/web/modules/server-ce-scripts/scripts/check-mongodb.mjs
|
||||
+++ b/services/web/modules/server-ce-scripts/scripts/check-mongodb.mjs
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
const { ObjectId } = mongodb
|
||||
|
||||
const MIN_MONGO_VERSION = [6, 0]
|
||||
+const MIN_MONGO_FEATURE_COMPATIBILITY_VERSION = [6, 0]
|
||||
|
||||
async function main() {
|
||||
let mongoClient
|
||||
@@ -18,6 +19,7 @@ async function main() {
|
||||
}
|
||||
|
||||
await checkMongoVersion(mongoClient)
|
||||
+ await checkFeatureCompatibilityVersion(mongoClient)
|
||||
|
||||
try {
|
||||
await testTransactions(mongoClient)
|
||||
@@ -53,6 +55,41 @@ async function checkMongoVersion(mongoClient) {
|
||||
}
|
||||
}
|
||||
|
||||
+async function checkFeatureCompatibilityVersion(mongoClient) {
|
||||
+ const {
|
||||
+ featureCompatibilityVersion: { version },
|
||||
+ } = await mongoClient
|
||||
+ .db()
|
||||
+ .admin()
|
||||
+ .command({ getParameter: 1, featureCompatibilityVersion: 1 })
|
||||
+ const [major, minor] = version.split('.').map(v => parseInt(v))
|
||||
+ const [minMajor, minMinor] = MIN_MONGO_FEATURE_COMPATIBILITY_VERSION
|
||||
+
|
||||
+ if (major < minMajor || (major === minMajor && minor < minMinor)) {
|
||||
+ const minVersion = MIN_MONGO_FEATURE_COMPATIBILITY_VERSION.join('.')
|
||||
+ console.error(`
|
||||
+The MongoDB server has featureCompatibilityVersion=${version}, but Overleaf requires at least version ${minVersion}.
|
||||
+
|
||||
+Open a mongo shell:
|
||||
+- Overleaf Toolkit deployments: $ bin/mongo
|
||||
+- Legacy docker-compose.yml deployments: $ docker exec -it mongo mongosh localhost/sharelatex
|
||||
+
|
||||
+In the mongo shell:
|
||||
+> db.adminCommand( { setFeatureCompatibilityVersion: "${minMajor}.${minMinor}" } )
|
||||
+
|
||||
+Verify the new value:
|
||||
+> db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )
|
||||
+ ...
|
||||
+ {
|
||||
+ featureCompatibilityVersion: { version: ${minMajor}.${minMinor}' },
|
||||
+...
|
||||
+
|
||||
+Aborting.
|
||||
+`)
|
||||
+ process.exit(1)
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
main()
|
||||
.then(() => {
|
||||
console.error('Mongodb is up.')
|
||||
16
server-ce/hotfix/5.5.1/pr_26152.patch
Normal file
16
server-ce/hotfix/5.5.1/pr_26152.patch
Normal file
@@ -0,0 +1,16 @@
|
||||
--- a/services/web/modules/server-ce-scripts/scripts/create-user.mjs
|
||||
+++ b/services/web/modules/server-ce-scripts/scripts/create-user.mjs
|
||||
@@ -48,3 +48,13 @@ Please visit the following URL to set a password for ${email} and log in:
|
||||
)
|
||||
})
|
||||
}
|
||||
+
|
||||
+if (filename === process.argv[1]) {
|
||||
+ try {
|
||||
+ await main()
|
||||
+ process.exit(0)
|
||||
+ } catch (error) {
|
||||
+ console.error({ error })
|
||||
+ process.exit(1)
|
||||
+ }
|
||||
+}
|
||||
@@ -179,6 +179,21 @@ describe('admin panel', function () {
|
||||
cy.get('nav').findByText('Manage Users').click()
|
||||
})
|
||||
|
||||
it('displays expected tabs', () => {
|
||||
const tabs = ['Users', 'License Usage']
|
||||
cy.get('[role="tab"]').each((el, index) => {
|
||||
cy.wrap(el).findByText(tabs[index]).click()
|
||||
})
|
||||
cy.get('[role="tab"]').should('have.length', tabs.length)
|
||||
})
|
||||
|
||||
it('license usage tab', () => {
|
||||
cy.get('a').contains('License Usage').click()
|
||||
cy.findByText(
|
||||
'An active user is one who has opened a project in this Server Pro instance in the last 12 months.'
|
||||
)
|
||||
})
|
||||
|
||||
describe('create users', () => {
|
||||
beforeEach(() => {
|
||||
cy.get('a').contains('New User').click()
|
||||
|
||||
Reference in New Issue
Block a user