snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 01:21:02 +05:00
commit 30810eecd7
10161 changed files with 987110 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
import { Meteor } from 'meteor/meteor';
if (Meteor.AppCache) {
Meteor.AppCache.config({
onlineOnly: ['/elements/', '/landing/', '/moment-locales/', '/scripts/'],
});
}

View File

@@ -0,0 +1,240 @@
import { CallHistory, MediaCalls } from '@rocket.chat/models';
export async function addCallHistoryTestData(uid: string, extraUid: string): Promise<void> {
const callId1 = 'rocketchat.internal.call.test';
const callId2 = 'rocketchat.internal.call.test.2';
const callId3 = 'rocketchat.external.call.test.outbound';
const callId4 = 'rocketchat.external.call.test.inbound';
const extraCallId1 = 'rocketchat.extra.call.test.1';
const extraCallId2 = 'rocketchat.extra.call.test.2';
await CallHistory.deleteMany({ uid });
await MediaCalls.deleteMany({ _id: { $in: [callId1, callId2, callId3, callId4] } });
await CallHistory.insertMany([
{
_id: 'rocketchat.internal.history.test.outbound',
ts: new Date(),
callId: callId1,
state: 'ended',
type: 'media-call',
duration: 10,
endedAt: new Date(),
external: false,
uid,
contactId: extraUid,
direction: 'outbound',
contactName: 'Pineapple', // random words used for searching
contactUsername: 'fruit-001',
},
{
_id: 'rocketchat.internal.history.test.inbound',
ts: new Date(),
callId: callId2,
state: 'not-answered',
type: 'media-call',
duration: 10,
endedAt: new Date(),
external: false,
uid,
contactId: extraUid,
direction: 'inbound',
contactName: 'Apple',
contactUsername: 'fruit-002',
},
{
_id: 'rocketchat.internal.history.test.outbound.2',
ts: new Date(),
callId: extraCallId1,
state: 'transferred',
type: 'media-call',
duration: 10,
endedAt: new Date(),
external: false,
uid,
contactId: extraUid,
direction: 'outbound',
contactName: 'Grapefruit 002',
contactUsername: 'username-001',
},
{
_id: 'rocketchat.internal.history.test.inbound.2',
ts: new Date(),
callId: extraCallId2,
state: 'transferred',
type: 'media-call',
duration: 10,
endedAt: new Date(),
external: false,
uid,
contactId: extraUid,
direction: 'inbound',
contactName: 'Pasta 1',
contactUsername: 'meal',
},
{
_id: 'rocketchat.external.history.test.outbound',
ts: new Date(),
callId: callId3,
state: 'failed',
type: 'media-call',
duration: 10,
endedAt: new Date(),
external: true,
uid,
direction: 'outbound',
contactExtension: '1001',
},
{
_id: 'rocketchat.external.history.test.inbound',
ts: new Date(),
callId: callId4,
state: 'ended',
type: 'media-call',
duration: 10,
endedAt: new Date(),
external: true,
uid,
direction: 'inbound',
contactExtension: '1002',
},
]);
await MediaCalls.insertMany([
{
_id: callId1,
service: 'webrtc',
kind: 'direct',
state: 'hangup',
createdBy: {
type: 'user',
id: uid,
},
createdAt: new Date(),
caller: {
type: 'user',
id: uid,
contractId: 'contract1',
},
callee: {
type: 'user',
id: extraUid,
contractId: 'contract2',
},
ended: true,
endedBy: {
type: 'user',
id: uid,
},
endedAt: new Date(),
hangupReason: 'normal',
expiresAt: new Date(),
acceptedAt: new Date(),
activatedAt: new Date(),
uids: [uid, extraUid],
features: ['audio'],
},
{
_id: callId2,
service: 'webrtc',
kind: 'direct',
state: 'hangup',
createdBy: {
type: 'user',
id: extraUid,
},
createdAt: new Date(),
caller: {
type: 'user',
id: extraUid,
contractId: 'contract1',
},
callee: {
type: 'user',
id: uid,
contractId: 'contract2',
},
ended: true,
endedBy: {
type: 'user',
id: uid,
},
endedAt: new Date(),
hangupReason: 'normal',
expiresAt: new Date(),
acceptedAt: new Date(),
activatedAt: new Date(),
uids: [uid, extraUid],
features: ['audio'],
},
{
_id: callId3,
service: 'webrtc',
kind: 'direct',
state: 'hangup',
createdBy: {
type: 'user',
id: uid,
},
createdAt: new Date(),
caller: {
type: 'user',
id: uid,
contractId: 'contract1',
},
callee: {
type: 'sip',
id: '1001',
contractId: 'contract2',
},
ended: true,
endedBy: {
type: 'user',
id: uid,
},
endedAt: new Date(),
hangupReason: 'normal',
expiresAt: new Date(),
acceptedAt: new Date(),
activatedAt: new Date(),
uids: [uid],
features: ['audio'],
sipCallId: 'sipCallId3',
},
{
_id: callId4,
service: 'webrtc',
kind: 'direct',
state: 'hangup',
createdBy: {
type: 'sip',
id: '1001',
},
createdAt: new Date(),
caller: {
type: 'sip',
id: '1001',
contractId: 'contract1',
},
callee: {
type: 'user',
id: uid,
contractId: 'contract2',
},
ended: true,
endedBy: {
type: 'user',
id: uid,
},
endedAt: new Date(),
hangupReason: 'normal',
expiresAt: new Date(),
acceptedAt: new Date(),
activatedAt: new Date(),
uids: [uid],
features: ['audio'],
sipCallId: 'sipCallId4',
},
]);
}

View File

@@ -0,0 +1,38 @@
import { Logger } from '@rocket.chat/logger';
import { performance } from 'universal-perf-hooks';
import { callbacks } from '../lib/callbacks';
import { metrics, StatsTracker } from '../lib/metrics';
callbacks.setLogger(new Logger('Callbacks'));
callbacks.setMetricsTrackers({
trackCallback: ({ hook, id }) => {
const start = performance.now();
const stopTimer = metrics.rocketchatCallbacks.startTimer({ hook, callback: id });
const stopHistogram = metrics.rocketchatCallbacksSeconds.startTimer({ hook, callback: id });
return (): void => {
const end = performance.now();
StatsTracker.timing('callbacks.time', end - start, [`hook:${hook}`, `callback:${id}`]);
stopTimer();
stopHistogram();
};
},
trackHook: ({ hook, length }) => {
const stopTimer = metrics.rocketchatHooks.startTimer({
hook,
callbacks_length: length,
});
const stopHistogram = metrics.rocketchatHooksSeconds.startTimer({
hook,
callbacks_length: length,
});
return (): void => {
stopTimer();
stopHistogram();
};
},
});

View File

@@ -0,0 +1,26 @@
import { Settings } from '@rocket.chat/models';
export async function ensureCloudWorkspaceRegistered(): Promise<void> {
const cloudWorkspaceClientId = await Settings.getValueById('Cloud_Workspace_Client_Id');
const cloudWorkspaceClientSecret = await Settings.getValueById('Cloud_Workspace_Client_Secret');
const showSetupWizard = await Settings.findOneById('Show_Setup_Wizard', {
projection: { value: 1, valueSource: 1, processEnvValue: 1 },
});
// skip if both fields are already set, which means the workspace is already registered
if (!!cloudWorkspaceClientId && !!cloudWorkspaceClientSecret) {
return;
}
// skip if the setup wizard still not completed
if (showSetupWizard?.value !== 'completed') {
return;
}
if (showSetupWizard.valueSource === 'processEnvValue' && showSetupWizard.value === showSetupWizard.processEnvValue) {
return;
}
// otherwise, set the setup wizard to in_progress forcing admins to complete the registration
await Settings.updateValueById('Show_Setup_Wizard', 'in_progress');
}

View File

@@ -0,0 +1,14 @@
import { BannerModule } from '../modules/core-apps/banner.module';
import { CloudAnnouncementsModule } from '../modules/core-apps/cloudAnnouncements.module';
import { CloudSubscriptionCommunication } from '../modules/core-apps/cloudSubscriptionCommunication.module';
import { MentionModule } from '../modules/core-apps/mention.module';
import { Nps } from '../modules/core-apps/nps.module';
import { VideoConfModule } from '../modules/core-apps/videoconf.module';
import { registerCoreApp } from '../services/uikit-core-app/service';
registerCoreApp(new CloudAnnouncementsModule());
registerCoreApp(new CloudSubscriptionCommunication());
registerCoreApp(new Nps());
registerCoreApp(new BannerModule());
registerCoreApp(new VideoConfModule());
registerCoreApp(new MentionModule());

View File

@@ -0,0 +1,16 @@
import { Logger } from '@rocket.chat/logger';
import { npsCron } from '../cron/nps';
import { oembedCron } from '../cron/oembed';
import { startCron } from '../cron/start';
import { temporaryUploadCleanupCron } from '../cron/temporaryUploadsCleanup';
import { usageReportCron } from '../cron/usageReport';
import { userDataDownloadsCron } from '../cron/userDataDownloads';
import { videoConferencesCron } from '../cron/videoConferences';
const logger = new Logger('SyncedCron');
export const startCronJobs = async (): Promise<void> => {
await Promise.all([startCron(), oembedCron(), usageReportCron(logger), npsCron(), temporaryUploadCleanupCron(), videoConferencesCron()]);
userDataDownloadsCron();
};

View File

@@ -0,0 +1,87 @@
import { Messages } from '@rocket.chat/models';
import { SystemLogger } from '../lib/logger/system';
const { USE_ROOM_SEARCH_INDEX = 'false' } = process.env;
// MongoDB stores a text index's key with `_fts: 'text'` / `_ftsx: 1` placeholders
// and tracks the original text fields in `weights`. Classify by looking at the
// non-placeholder prefix fields plus weights.
const classifyTextIndex = (idx: { key: Record<string, unknown>; weights?: Record<string, number> }) => {
const { weights, key } = idx;
if (weights?.msg !== 1 || Object.keys(weights).length !== 1) {
return 'other';
}
const prefix = Object.keys(key).filter((k) => k !== '_fts' && k !== '_ftsx');
if (prefix.length === 0) {
return 'default';
}
if (prefix.length === 1 && prefix[0] === 'rid' && key.rid === 1) {
return 'room-scoped';
}
return 'other';
};
export const ensureMessagesTextIndex = async (): Promise<void> => {
const desiredShape = USE_ROOM_SEARCH_INDEX === 'true' ? 'room-scoped' : 'default';
SystemLogger.debug({
msg: 'checking messages text index',
USE_ROOM_SEARCH_INDEX,
desired: desiredShape,
});
let existing;
try {
existing = await Messages.col.indexes();
} catch (err) {
SystemLogger.error({ msg: 'failed to list messages indexes; skipping text index check', err });
return;
}
const textIndexes = existing.filter((idx) => Object.values(idx.key).includes('text'));
const matching = textIndexes.find((idx) => classifyTextIndex(idx) === desiredShape);
const stale = textIndexes.filter((idx) => idx !== matching);
if (matching && stale.length === 0) {
SystemLogger.debug({ msg: 'messages text index already matches desired shape', name: matching.name });
return;
}
for (const idx of stale) {
if (!idx.name) {
continue;
}
SystemLogger.startup({
msg: 'dropping messages text index; may take several minutes on large databases',
name: idx.name,
shape: classifyTextIndex(idx),
});
try {
await Messages.col.dropIndex(idx.name);
SystemLogger.startup({ msg: 'dropped messages text index', name: idx.name });
} catch (err) {
SystemLogger.error({ msg: 'failed to drop messages text index; aborting', name: idx.name, err });
return;
}
}
if (matching) {
SystemLogger.debug({ msg: 'messages text index already matches desired shape', name: matching.name });
return;
}
SystemLogger.startup({
msg: 'creating messages text index; may take several minutes on large databases',
shape: desiredShape,
});
try {
const name = await Messages.col.createIndex(desiredShape === 'room-scoped' ? { rid: 1, msg: 'text' } : { msg: 'text' });
SystemLogger.startup({ msg: 'created messages text index', name, shape: desiredShape });
} catch (err) {
SystemLogger.error({ msg: 'failed to create messages text index', shape: desiredShape, err });
}
};

View File

@@ -0,0 +1,8 @@
import { FederationKeys } from '@rocket.chat/models';
// Create key pair if needed
export async function generateFederationKeys() {
if (!(await FederationKeys.getPublicKey())) {
await FederationKeys.generateKeys();
}
}

View File

@@ -0,0 +1,17 @@
import { WebApp } from 'meteor/webapp';
// meteor's webapp arms a 5s idle-socket timeout (and re-arms it after every
// response), which kills keep-alive conns a fronting proxy may be about to
// reuse — the root of the CI dynamic-import truncation flake (PR #41399).
// Overriding it is opt-in: only CI sets HTTP_SOCKET_TIMEOUT_MS, pointing it
// above traefik's idleConnTimeout so the proxy always closes conns first.
const timeout = parseInt(process.env.HTTP_SOCKET_TIMEOUT_MS ?? '', 10);
if (timeout > 0) {
const { httpServer, _timeoutAdjustmentRequestCallback } = WebApp as typeof WebApp & {
_timeoutAdjustmentRequestCallback: (req: unknown, res: unknown) => void;
};
httpServer.removeListener('request', _timeoutAdjustmentRequestCallback);
httpServer.setTimeout(timeout);
httpServer.keepAliveTimeout = timeout;
httpServer.headersTimeout = timeout + 1000;
}

View File

@@ -0,0 +1,29 @@
import './appcache';
import './callbacks';
import { ensureMessagesTextIndex } from './ensureMessagesTextIndex';
import './initialData';
import './serverRunning';
import './coreApps';
import { generateFederationKeys } from './generateKeys';
import './presenceTroubleshoot';
import './httpSocketTimeout';
import '../hooks';
import '../lib/rooms/roomTypes';
import '../lib/settingsRegenerator';
import { performMigrationProcedure } from './migrations';
import { isRunningMs } from '../lib/isRunningMs';
export const startup = async () => {
await performMigrationProcedure();
await generateFederationKeys();
// NOTE: cron jobs are started from main.ts after startRocketChat(), so jobs
// that contact Rocket.Chat Cloud on boot (e.g. the usage report) run only
// after the license — including the offline flag — has been applied.
setImmediate(() => ensureMessagesTextIndex());
// only starts network broker if running in micro services mode
if (!isRunningMs()) {
require('./localServices');
}
};

View File

@@ -0,0 +1,257 @@
import { UserStatus, type IUser } from '@rocket.chat/core-typings';
import { Settings, Rooms, Users, Roles } from '@rocket.chat/models';
import { validateEmail } from '@rocket.chat/tools';
import colors from 'colors/safe';
import { Accounts } from 'meteor/accounts-base';
import { Meteor } from 'meteor/meteor';
import { addCallHistoryTestData } from './callHistoryTestData';
import { FileUpload } from '../lib/media/file-upload';
import { notifyOnSettingChangedById } from '../lib/notifyListener';
import { addUserRolesAsync } from '../lib/roles/addUserRoles';
import { addUserToDefaultChannels } from '../lib/rooms/addUserToDefaultChannels';
import { checkUsernameAvailability } from '../lib/users/checkUsernameAvailability';
import { settings } from '../settings';
export async function insertAdminUserFromEnv() {
if (process.env.ADMIN_PASS) {
if ((await Roles.countUsersInRole('admin')) === 0) {
const adminUser: Partial<IUser> = {
name: process.env.ADMIN_NAME || 'Administrator',
username: 'admin',
status: UserStatus.OFFLINE,
statusDefault: UserStatus.ONLINE,
utcOffset: 0,
active: true,
type: 'user',
};
console.log(colors.green(`Name: ${adminUser.name}`));
if (process.env.ADMIN_EMAIL) {
if (validateEmail(process.env.ADMIN_EMAIL)) {
if (!(await Users.findOneByEmailAddress(process.env.ADMIN_EMAIL))) {
adminUser.emails = [
{
address: process.env.ADMIN_EMAIL,
verified: process.env.ADMIN_EMAIL_VERIFIED === 'true',
},
];
console.log(colors.green(`Email: ${process.env.ADMIN_EMAIL}`));
} else {
console.log(colors.red('Email provided already exists; Ignoring environment variables ADMIN_EMAIL'));
}
} else {
console.log(colors.red('Email provided is invalid; Ignoring environment variables ADMIN_EMAIL'));
}
}
if (process.env.ADMIN_USERNAME) {
let nameValidation;
try {
nameValidation = new RegExp(`^${settings.get('UTF8_User_Names_Validation')}$`);
} catch (error) {
nameValidation = new RegExp('^[0-9a-zA-Z-_.]+$');
}
if (nameValidation.test(process.env.ADMIN_USERNAME)) {
try {
await checkUsernameAvailability(process.env.ADMIN_USERNAME);
adminUser.username = process.env.ADMIN_USERNAME;
} catch (error) {
console.log(
colors.red('Username provided already exists or is blocked from usage; Ignoring environment variables ADMIN_USERNAME'),
);
}
} else {
console.log(colors.red('Username provided is invalid; Ignoring environment variables ADMIN_USERNAME'));
}
}
console.log(colors.green(`Username: ${adminUser.username}`));
const { insertedId: userId } = await Users.create(adminUser);
await Accounts.setPasswordAsync(userId, process.env.ADMIN_PASS);
await addUserRolesAsync(userId, ['admin']);
} else {
console.log(colors.red('Users with admin role already exist; Ignoring environment variables ADMIN_PASS'));
}
}
}
Meteor.startup(async () => {
const dynamicImport = {
'dynamic-import': {
useLocationOrigin: true,
},
};
if (!Meteor.settings) {
Meteor.settings = {
public: {
packages: {
'dynamic-import': dynamicImport,
},
},
};
}
if (!Meteor.settings.public) {
Meteor.settings.public = {
packages: {
'dynamic-import': dynamicImport,
},
};
}
if (!Meteor.settings.public.packages) {
Meteor.settings.public.packages = dynamicImport;
}
Meteor.settings.public.packages['dynamic-import'] = dynamicImport['dynamic-import'];
if (!settings.get('Initial_Channel_Created')) {
const exists = await Rooms.findOneById('GENERAL', { projection: { _id: 1 } });
if (!exists) {
await Rooms.createWithIdTypeAndName('GENERAL', 'c', 'general', {
default: true,
});
}
(await Settings.updateValueById('Initial_Channel_Created', true)).modifiedCount &&
void notifyOnSettingChangedById('Initial_Channel_Created');
}
try {
if (!(await Users.findOneById('rocket.cat', { projection: { _id: 1 } }))) {
await Users.create({
_id: 'rocket.cat',
name: 'Rocket.Cat',
username: 'rocket.cat',
status: UserStatus.ONLINE,
statusDefault: UserStatus.ONLINE,
utcOffset: 0,
active: true,
type: 'bot',
});
await addUserRolesAsync('rocket.cat', ['bot']);
const asset = await Assets.getBinaryAsync('avatars/rocketcat.png');
if (asset) {
const buffer = Buffer.from(asset);
const fileStore = FileUpload.getStore('Avatars');
await fileStore.deleteByName('rocket.cat');
const file = {
userId: 'rocket.cat',
type: 'image/png',
size: buffer.length,
};
const upload = await fileStore.insert(file, buffer);
await Users.setAvatarData('rocket.cat', 'local', upload.etag);
}
}
} catch (error) {
console.log(
'Error creating default `rocket.cat` user, if you created a user with this username please remove it and restart the server',
);
throw error;
}
await insertAdminUserFromEnv();
if (typeof process.env.INITIAL_USER === 'string' && process.env.INITIAL_USER.length > 0) {
try {
const initialUser = JSON.parse(process.env.INITIAL_USER);
if (!initialUser._id) {
console.log(colors.red('No _id provided; Ignoring environment variable INITIAL_USER'));
} else if (!(await Users.findOneById(initialUser._id))) {
console.log(colors.green('Inserting initial user:'));
console.log(colors.green(JSON.stringify(initialUser, null, 2)));
await Users.create(initialUser);
await addUserToDefaultChannels(initialUser, true);
}
} catch (e) {
console.log(colors.red('Error processing environment variable INITIAL_USER'), e);
}
}
if ((await Roles.countUsersInRole('admin')) === 0) {
const oldestUser = await Users.getOldest({ projection: { _id: 1, username: 1, name: 1 } });
if (oldestUser) {
await addUserRolesAsync(oldestUser._id, ['admin']);
console.log(`No admins are found. Set ${oldestUser.username || oldestUser.name} as admin for being the oldest user`);
}
}
if ((await Roles.countUsersInRole('admin')) !== 0) {
if (settings.get('Show_Setup_Wizard') === 'pending') {
console.log('Setting Setup Wizard to "in_progress" because, at least, one admin was found');
(await Settings.updateValueById('Show_Setup_Wizard', 'in_progress')).modifiedCount &&
void notifyOnSettingChangedById('Show_Setup_Wizard');
}
}
await Users.removeById('rocketchat.internal.admin.test');
if (process.env.TEST_MODE === 'true' || process.env.TEST_MODE === 'api') {
console.log(colors.green('Inserting admin test user:'));
const adminUser: Omit<IUser, 'createdAt' | 'roles' | '_updatedAt'> = {
_id: 'rocketchat.internal.admin.test',
name: 'RocketChat Internal Admin Test',
username: 'rocketchat.internal.admin.test',
emails: [
{
address: 'rocketchat.internal.admin.test@rocket.chat',
verified: true,
},
],
status: UserStatus.OFFLINE,
statusDefault: UserStatus.ONLINE,
utcOffset: 0,
active: true,
type: 'user',
};
console.log(colors.green(`Name: ${adminUser.name}`));
console.log(colors.green(`Email: ${adminUser.emails![0].address}`));
console.log(colors.green(`Username: ${adminUser.username}`));
console.log(colors.green(`Password: ${adminUser._id}`));
if (await Users.findOneByEmailAddress(adminUser.emails![0].address)) {
throw new Meteor.Error(`Email ${adminUser.emails![0].address} already exists`, "Rocket.Chat can't run in test mode");
}
if (!(await checkUsernameAvailability(adminUser.username!))) {
throw new Meteor.Error(`Username ${adminUser.username} already exists`, "Rocket.Chat can't run in test mode");
}
await Users.create(adminUser);
await Accounts.setPasswordAsync(adminUser._id, adminUser._id);
await addUserRolesAsync(adminUser._id, ['admin']);
if (settings.get('Show_Setup_Wizard') === 'pending') {
(await Settings.updateValueById('Show_Setup_Wizard', 'in_progress')).modifiedCount &&
void notifyOnSettingChangedById('Show_Setup_Wizard');
}
await addUserToDefaultChannels(adminUser as IUser, true);
// Create sample call history for API tests
return addCallHistoryTestData('rocketchat.internal.admin.test', 'rocket.cat');
}
});

View File

@@ -0,0 +1,12 @@
import { api, LocalBroker } from '@rocket.chat/core-services';
import { StreamerCentral } from '../modules/streamer/streamer.module';
const broker = new LocalBroker();
broker.onBroadcast((eventName: string, args: unknown[]) => {
StreamerCentral.emit('broadcast', 'local', 'broadcast', [{ eventName, args }]);
});
api.setBroker(broker);
void api.start();

View File

@@ -0,0 +1,45 @@
import './minimumVersion';
import './v293';
import './v294';
import './v295';
import './v296';
import './v297';
import './v298';
import './v299';
import './v300';
import './v301';
import './v303';
import './v304';
import './v305';
import './v306';
import './v307';
import './v308';
import './v309';
import './v310';
import './v311';
import './v312';
import './v313';
import './v314';
import './v315';
import './v316';
import './v317';
import './v318';
import './v319';
import './v320';
import './v321';
import './v322';
import './v323';
import './v324';
import './v325';
import './v326';
import './v327';
import './v328';
import './v329';
import './v330';
import './v331';
import './v332';
import './v333';
import './v334';
import './v335';
export * from './xrun';

View File

@@ -0,0 +1,20 @@
import { addMigration } from '../../lib/migrations';
// this was the first migration added on version 6.0, so if this needs to run,
// it means the server was never updated to 6.x, which is not supported.
addMigration({
version: 292,
up() {
throw new Error(
[
'UPGRADE NOT SUPPORTED!',
'',
`It seems you're trying to upgrade from an unsupported version!`,
'',
'To be able to update to version 7.x.y you need to update to version 6.x first.',
'',
'Read more: https://go.rocket.chat/i/how-to-upgrade',
].join('\n'),
);
},
});

View File

@@ -0,0 +1,9 @@
import { upsertPermissions } from '../../lib/authorization/upsertPermissions';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 293,
async up() {
await upsertPermissions();
},
});

View File

@@ -0,0 +1,37 @@
import { Apps } from '@rocket.chat/apps';
import type { IAppStorageItem } from '@rocket.chat/apps/dist/server/storage/IAppStorageItem';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 294,
async up() {
if (!Apps.self) {
throw new Error('Apps Orchestrator not registered.');
}
Apps.initialize();
const sigMan = Apps.getManager().getSignatureManager();
const appsStorage = Apps.getStorage();
const apps = await appsStorage.retrieveAll();
for (const app of apps.values()) {
if (app.installationSource && app.signature) {
continue;
}
const updatedApp = {
...app,
migrated: true,
installationSource: 'marketplaceInfo' in app ? 'marketplace' : 'private',
} as IAppStorageItem;
await appsStorage.updatePartialAndReturnDocument({
...updatedApp,
signature: await sigMan.signApp(updatedApp),
});
}
},
});

View File

@@ -0,0 +1,45 @@
import type { ISetting } from '@rocket.chat/core-typings';
import { Settings } from '@rocket.chat/models';
import { SystemLogger } from '../../lib/logger/system';
import { addMigration } from '../../lib/migrations';
import { updateAuditedBySystem } from '../../settings/lib/auditedSettingUpdates';
addMigration({
version: 295,
name: 'Change old "LDAP_Background_Sync_Interval" and "CROWD_Sync_Interval" to a pre-defined values instead of accept any input from the user',
async up() {
const oldLdapDefault = 'Every 24 hours';
const oldCrowdDefault = 'Every 60 mins';
const newLdapDefault = 'every_24_hours';
const newCrowdDefault = 'every_1_hours';
const ldapSyncInterval = await Settings.findOneById<Pick<ISetting, 'value'>>('LDAP_Background_Sync_Interval', {
projection: { value: 1 },
});
const crowdSyncInterval = await Settings.findOneById<Pick<ISetting, 'value'>>('CROWD_Sync_Interval', { projection: { value: 1 } });
// update setting values
await updateAuditedBySystem({
reason: 'Migration 295',
})(Settings.updateValueById, 'LDAP_Background_Sync_Interval', newLdapDefault);
await updateAuditedBySystem({
reason: 'Migration 295',
})(Settings.updateValueById, 'CROWD_Sync_Interval', newCrowdDefault);
// notify user about the changes if the value was different from the default
if (ldapSyncInterval && ldapSyncInterval.value !== oldLdapDefault) {
SystemLogger.warn(
`The value of the setting 'LDAP background synchronization interval' has changed from "${ldapSyncInterval.value}" to "${newLdapDefault}". Please review your settings.`,
);
}
if (crowdSyncInterval && crowdSyncInterval.value !== oldCrowdDefault) {
SystemLogger.warn(
`The value of the setting 'CROWD background synchronization interval' has changed from "${crowdSyncInterval.value}" to "${newCrowdDefault}". Please review your settings.`,
);
}
},
});

View File

@@ -0,0 +1,22 @@
import { Settings } from '@rocket.chat/models';
import { SystemLogger } from '../../lib/logger/system';
import { addMigration } from '../../lib/migrations';
import { settings } from '../../settings';
addMigration({
version: 296,
name: 'Reset the default value of Login Terms setting and replace by empty string',
async up() {
const oldLoginTermsValue =
'By proceeding you are agreeing to our <a href="terms-of-service">Terms of Service</a>, <a href="privacy-policy">Privacy Policy</a> and <a href="legal-notice">Legal Notice</a>.';
const loginTermsValue = settings.get('Layout_Login_Terms');
if (loginTermsValue === oldLoginTermsValue) {
// TODO: audit
await Settings.updateOne({ _id: 'Layout_Login_Terms' }, { $set: { value: '', packageValue: '' } });
SystemLogger.warn(`The default value of the setting 'Login Terms' has changed to an empty string. Please review your settings.`);
}
},
});

View File

@@ -0,0 +1,9 @@
import { upsertPermissions } from '../../lib/authorization/upsertPermissions';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 297,
async up() {
await upsertPermissions();
},
});

View File

@@ -0,0 +1,20 @@
import { Integrations } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 298,
name: 'Set overrideDestinationChannelEnabled for all incoming webhook integrations',
async up() {
await Integrations.updateMany(
{
type: 'webhook-incoming',
},
{
$set: {
overrideDestinationChannelEnabled: true,
},
},
);
},
});

View File

@@ -0,0 +1,11 @@
import { Settings } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 299,
name: 'Add public field to existing custom OAuth settings',
async up() {
await Settings.updateMany({ _id: /^Accounts_OAuth_Custom.+/, group: 'OAuth' }, { $set: { public: false } });
},
});

View File

@@ -0,0 +1,27 @@
import { Settings } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
import { settingsRegistry } from '../../settings';
addMigration({
version: 300,
async up() {
const customOauthServices = await Settings.find({ _id: /Accounts_OAuth_Custom-[^-]+$/ }, { projection: { _id: 1 } }).toArray();
const serviceNames = customOauthServices.map(({ _id }) => _id.replace('Accounts_OAuth_Custom-', ''));
for await (const serviceName of serviceNames) {
await settingsRegistry.add(`Accounts_OAuth_Custom-${serviceName}-merge_users_distinct_services`, false, {
type: 'boolean',
group: 'OAuth',
section: `Custom OAuth: ${serviceName}`,
i18nLabel: 'Accounts_OAuth_Custom_Merge_Users_Distinct_Services',
i18nDescription: 'Accounts_OAuth_Custom_Merge_Users_Distinct_Services_Description',
enableQuery: {
_id: `Accounts_OAuth_Custom-${serviceName}-merge_users`,
value: true,
},
persistent: true,
});
}
},
});

View File

@@ -0,0 +1,10 @@
import { Permissions } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 301,
async up() {
await Permissions.updateOne({ _id: 'call-management', roles: { $ne: 'user' } }, { $addToSet: { roles: 'user' } });
},
});

View File

@@ -0,0 +1,16 @@
import { Users } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 303,
name: 'Add roles field and set empty for users without it',
async up() {
await Users.updateMany(
{
roles: { $exists: false },
},
{ $set: { roles: [] } },
);
},
});

View File

@@ -0,0 +1,22 @@
import { Analytics } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 304,
name: 'Drop wrong index from analytics collection',
async up() {
const indexes = await Analytics.col.indexes();
if (
indexes.find(
(index) => index.name === 'room._id_1_date_1' && index.partialFilterExpression && index.partialFilterExpression.type === 'rooms',
)
) {
return;
}
await Analytics.col.dropIndex('room._id_1_date_1');
await Analytics.createIndexes();
},
});

View File

@@ -0,0 +1,11 @@
import { Permissions } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 305,
name: 'Remove unused view-history permission',
async up() {
await Permissions.deleteOne({ _id: 'view-history' });
},
});

View File

@@ -0,0 +1,22 @@
import { Rooms } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 306,
name: 'Adds missing ignoreThreads parameter for old rooms with retention policy overridden',
async up() {
await Rooms.updateMany(
{
'retention.enabled': true,
'retention.overrideGlobal': true,
'retention.ignoreThreads': { $exists: false },
},
{
$set: {
'retention.ignoreThreads': false,
},
},
);
},
});

View File

@@ -0,0 +1,38 @@
import { Apps } from '@rocket.chat/apps';
import type { IAppStorageItem } from '@rocket.chat/apps/dist/server/storage/IAppStorageItem';
import { License } from '@rocket.chat/license';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 307,
name: "Mark all installed private apps as 'migrated'",
async up() {
const isEE = License.hasValidLicense();
if (isEE) {
return;
}
if (!Apps.self) {
throw new Error('Apps Orchestrator not registered.');
}
Apps.initialize();
const sigMan = Apps.getManager().getSignatureManager();
const appsStorage = Apps.getStorage();
const apps = await appsStorage.retrieveAllPrivate();
for (const app of apps.values()) {
const updatedApp = {
...app,
migrated: true,
} as IAppStorageItem;
await appsStorage.updatePartialAndReturnDocument({
...updatedApp,
signature: await sigMan.signApp(updatedApp),
});
}
},
});

View File

@@ -0,0 +1,32 @@
import type { ISetting } from '@rocket.chat/core-typings';
import { Settings } from '@rocket.chat/models';
import { isValidCron } from 'cron-validator';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 308,
name: 'Update packageValue from LDAP interval settings',
async up() {
const newAvatarSyncPackageValue = '0 0 * * *';
const newAutoLogoutPackageValue = '*/5 * * * *';
const ldapAvatarSyncInterval = await Settings.findOneById<Pick<ISetting, 'value'>>('LDAP_Background_Sync_Avatars_Interval', {
projection: { value: 1 },
});
const ldapAutoLogoutInterval = await Settings.findOneById<Pick<ISetting, 'value'>>('LDAP_Sync_AutoLogout_Interval', {
projection: { value: 1 },
});
const isValidAvatarSyncInterval = ldapAvatarSyncInterval && isValidCron(ldapAvatarSyncInterval.value as string);
const isValidAutoLogoutInterval = ldapAutoLogoutInterval && isValidCron(ldapAutoLogoutInterval.value as string);
// TODO audit
await Settings.updateOne(
{ _id: 'LDAP_Background_Sync_Avatars_Interval' },
{ $set: { packageValue: newAvatarSyncPackageValue, ...(!isValidAvatarSyncInterval && { value: newAvatarSyncPackageValue }) } },
);
await Settings.updateOne(
{ _id: 'LDAP_Sync_AutoLogout_Interval' },
{ $set: { packageValue: newAutoLogoutPackageValue, ...(!isValidAutoLogoutInterval && { value: newAutoLogoutPackageValue }) } },
);
},
});

View File

@@ -0,0 +1,11 @@
import { Settings } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 309,
name: 'Remove unused UI_Click_Direct_Message setting',
async up() {
await Settings.removeById('UI_Click_Direct_Message');
},
});

View File

@@ -0,0 +1,29 @@
import type { ISetting } from '@rocket.chat/core-typings';
import { Settings } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 310,
name: 'Update translation key on "Forgot password" e-mail body setting',
async up() {
const forgotPasswordEmail = await Settings.findOneById<Pick<ISetting, 'value'>>('Forgot_Password_Email', { projection: { value: 1 } });
if (!forgotPasswordEmail) {
return;
}
const newPackageValue =
'<h2>{Forgot_password}</h2><p>{Lets_get_you_new_one_}</p><a class="btn" href="[Forgot_Password_Url]">{Reset}</a><p class="advice">{If_you_didnt_ask_for_reset_ignore_this_email}</p>';
// TODO: audit
await Settings.updateOne(
{ _id: 'Forgot_Password_Email' },
{
$set: {
packageValue: newPackageValue,
value: (forgotPasswordEmail.value as string).replace(/{Lets_get_you_new_one}/g, '{Lets_get_you_new_one_}'),
},
},
);
},
});

View File

@@ -0,0 +1,22 @@
import { Settings } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 311,
name: 'Update default behavior of E2E_Enabled_Mentions setting, to allow mentions in encrypted messages by default.',
async up() {
// TODO: audit
await Settings.updateOne(
{
_id: 'E2E_Enabled_Mentions',
},
{
$set: {
value: true,
packageValue: true,
},
},
);
},
});

View File

@@ -0,0 +1,20 @@
import { LivechatRooms, Rooms, Subscriptions, Users } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 312,
async up() {
try {
await Promise.allSettled([
LivechatRooms.col.dropIndex('v.token_1'),
Rooms.col.dropIndex('t_1'),
Subscriptions.col.dropIndex('rid_1'),
Users.col.dropIndex('active_1'),
]);
} catch (error: unknown) {
console.warn('Error dropping redundant indexes, continuing...');
console.warn(error);
}
},
});

View File

@@ -0,0 +1,11 @@
import { Settings } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 313,
name: 'Remove "Call_Center" setting group',
async up() {
await Settings.deleteOne({ _id: 'Call_Center', type: 'group' });
},
});

View File

@@ -0,0 +1,22 @@
import { Settings } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 314,
name: 'Update default behavior of E2E_Allow_Unencrypted_Messages setting, to not allow un-encrypted messages by default.',
async up() {
// TODO: audit
await Settings.updateOne(
{
_id: 'E2E_Allow_Unencrypted_Messages',
},
{
$set: {
value: false,
packageValue: false,
},
},
);
},
});

View File

@@ -0,0 +1,27 @@
import type { IPermission } from '@rocket.chat/core-typings';
import { Permissions } from '@rocket.chat/models';
import { upsertPermissions } from '../../lib/authorization/upsertPermissions';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 315,
name: 'Copy roles from add-team-channel permission to new create-team-channel, create-team-group and move-room-to-team permissions',
async up() {
// Calling upsertPermissions on purpose so that the new permissions are added before the migration runs
await upsertPermissions();
const addTeamChannelPermission = await Permissions.findOneById<Pick<IPermission, 'roles'>>('add-team-channel', {
projection: { roles: 1 },
});
if (addTeamChannelPermission) {
await Permissions.updateMany(
{ _id: { $in: ['create-team-channel', 'create-team-group', 'move-room-to-team'] } },
{ $set: { roles: addTeamChannelPermission.roles } },
);
await Permissions.deleteOne({ _id: 'add-team-channel' });
}
},
});

View File

@@ -0,0 +1,20 @@
import { Settings } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 316,
name: 'Remove Cloud_Workspace_Access_Token and Cloud_Workspace_Access_Token_Expires_At from the settings collection',
async up() {
const accessToken = ((await Settings.getValueById('Cloud_Workspace_Access_Token')) as string) || '';
const expirationDate = ((await Settings.getValueById('Cloud_Workspace_Access_Token_Expires_At')) as Date) || new Date(0);
if (accessToken) {
await Settings.removeById('Cloud_Workspace_Access_Token');
}
if (expirationDate) {
await Settings.removeById('Cloud_Workspace_Access_Token_Expires_At');
}
},
});

View File

@@ -0,0 +1,124 @@
import type { ILoginServiceConfiguration, OAuthConfiguration } from '@rocket.chat/core-typings';
import { Settings, LoginServiceConfiguration } from '@rocket.chat/models';
import { isTruthy } from '@rocket.chat/tools';
import { SystemLogger } from '../../lib/logger/system';
import { addMigration } from '../../lib/migrations';
const newDefaultButtonColor = '#e4e7ea';
const newDefaultButtonLabelColor = '#1f2329';
const settingsToUpdate = [
// button background colors
{ key: 'SAML_Custom_Default_button_color', defaultValue: '#1d74f5', newValue: newDefaultButtonColor },
{ key: 'CAS_button_color', defaultValue: '#1d74f5', newValue: newDefaultButtonColor },
{ key: 'Accounts_OAuth_Nextcloud_button_color', defaultValue: '#0082c9', newValue: newDefaultButtonColor },
{ key: 'Accounts_OAuth_Dolphin_button_color', defaultValue: '#1d74f5', newValue: newDefaultButtonColor },
// button label colors
{ key: 'SAML_Custom_Default_button_label_color', defaultValue: '#1d74f5', newValue: newDefaultButtonLabelColor },
{ key: 'CAS_button_label_color', defaultValue: '#1d74f5', newValue: newDefaultButtonLabelColor },
{ key: 'Accounts_OAuth_Nextcloud_button_label_color', defaultValue: '#1d74f5', newValue: newDefaultButtonLabelColor },
{ key: 'Accounts_OAuth_Dolphin_button_label_color', defaultValue: '#1d74f5', newValue: newDefaultButtonLabelColor },
];
const getSettingValue = async (key: string) => Settings.getValueById(key);
async function updateOAuthServices(): Promise<void> {
const services = await Settings.find({ _id: { $regex: /^(Accounts_OAuth_|Accounts_OAuth_Custom-)[a-z0-9_]+$/i } }).toArray();
const filteredServices = services.filter(({ value }) => typeof value === 'boolean');
for await (const { _id: key, value } of filteredServices) {
if (value !== true) {
continue;
}
let serviceName = key.replace('Accounts_OAuth_', '');
if (serviceName === 'Meteor') {
serviceName = 'meteor-developer';
}
if (/Accounts_OAuth_Custom-/.test(key)) {
serviceName = key.replace('Accounts_OAuth_Custom-', '');
}
const serviceKey = serviceName.toLowerCase();
const data: Partial<ILoginServiceConfiguration & Omit<OAuthConfiguration, '_id'>> = {};
if (/Accounts_OAuth_Custom-/.test(key)) {
data.buttonLabelColor = (await getSettingValue(`${key}-button_label_color`)) as string;
data.buttonColor = (await getSettingValue(`${key}-button_color`)) as string;
}
if (serviceName === 'Nextcloud') {
data.buttonLabelColor = (await getSettingValue('Accounts_OAuth_Nextcloud_button_label_color')) as string;
data.buttonColor = (await getSettingValue('Accounts_OAuth_Nextcloud_button_color')) as string;
}
await LoginServiceConfiguration.createOrUpdateService(serviceKey, data);
}
}
addMigration({
version: 317,
name: 'Change default color of OAuth login services buttons',
async up() {
// TODO: audit
const promises = settingsToUpdate
.map(async ({ key, defaultValue, newValue }) => {
const oldSettingValue = await getSettingValue(key);
if (!oldSettingValue || oldSettingValue !== defaultValue) {
return;
}
SystemLogger.warn({ msg: 'The default value of the setting has changed. Please review your settings.', key, newValue });
return Settings.updateOne({ _id: key }, { $set: { value: newValue } });
})
.filter(isTruthy);
await Promise.all(promises);
const customOAuthButtonColors = await Settings.find({
_id: { $regex: /^Accounts_OAuth_Custom-[a-zA-Z0-9_-]+-button_color$/ },
}).toArray();
const customOAuthButtonLabelColors = await Settings.find({
_id: { $regex: /^Accounts_OAuth_Custom-[a-zA-Z0-9_-]+-button_label_color$/ },
}).toArray();
const buttonColorPromises = customOAuthButtonColors
.map(({ _id, value, packageValue }) => {
if (packageValue !== value) {
return;
}
SystemLogger.warn({
msg: 'The default value of the custom setting has changed. Please review your settings.',
_id,
newValue: newDefaultButtonColor,
});
return Settings.updateOne({ _id }, { $set: { value: newDefaultButtonColor } });
})
.filter(isTruthy);
const buttonLabelColorPromises = customOAuthButtonLabelColors
.map(({ _id, value, packageValue }) => {
if (packageValue !== value) {
return;
}
SystemLogger.warn({
msg: 'The default value of the custom setting has changed. Please review your settings.',
_id,
newValue: newDefaultButtonLabelColor,
});
return Settings.updateOne({ _id }, { $set: { value: newDefaultButtonLabelColor } });
})
.filter(isTruthy);
await Promise.all([...buttonColorPromises, ...buttonLabelColorPromises]);
// update login service configurations
await updateOAuthServices();
},
});

View File

@@ -0,0 +1,11 @@
import { Settings } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 318,
name: 'Remove "E2E Encryption" setting group',
async up() {
await Settings.deleteOne({ _id: 'E2E Encryption', type: 'group' });
},
});

View File

@@ -0,0 +1,15 @@
import { LivechatRooms } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 319,
name: 'Remove "pdfTranscriptRequested" index from LivechatRooms collection',
async up() {
try {
await LivechatRooms.col.dropIndex('pdfTranscriptRequested_1');
} catch {
console.error('Error dropping index');
}
},
});

View File

@@ -0,0 +1,10 @@
import { Settings } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 320,
async up() {
await Settings.deleteOne({ _id: 'API_Use_REST_For_DDP_Calls' });
},
});

View File

@@ -0,0 +1,11 @@
import { Settings } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 321,
name: 'Remove voxtelesys & mobex related settings',
async up() {
await Promise.all([Settings.deleteMany({ section: { $in: ['Voxtelesys', 'Mobex'] } })]);
},
});

View File

@@ -0,0 +1,21 @@
import { Settings } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 322,
name: 'Remove Tokenpass settings',
async up() {
await Settings.deleteMany({
_id: {
$in: [
'Accounts_OAuth_Tokenpass',
'API_Tokenpass_URL',
'Accounts_OAuth_Tokenpass_id',
'Accounts_OAuth_Tokenpass_secret',
'Accounts_OAuth_Tokenpass_callback_url',
],
},
});
},
});

View File

@@ -0,0 +1,27 @@
import { AppLogs } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
// Remove outdated indexes on rocketchat_apps_logs
addMigration({
version: 323,
name: 'Remove outdated indexes on rocketchat_apps_logs',
async up() {
// Idempotent drops: ignore "IndexNotFound" (code 27)
try {
await AppLogs.col.dropIndex('appId_indexed_query');
} catch (e: any) {
if (e?.code !== 27 && e?.codeName !== 'IndexNotFound') {
throw e;
}
}
try {
await AppLogs.col.dropIndex('general_logs_index');
} catch (e: any) {
if (e?.code !== 27 && e?.codeName !== 'IndexNotFound') {
throw e;
}
}
},
});

View File

@@ -0,0 +1,11 @@
import { Settings } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 324,
name: 'Remove Log_View_Limit setting',
async up() {
await Settings.deleteOne({ _id: 'Log_View_Limit' });
},
});

View File

@@ -0,0 +1,43 @@
import { Settings, Permissions } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 325,
name: 'Remove VoIP_Omnichannel Settings and Permissions',
async up() {
await Settings.deleteMany({
_id: {
$in: [
'VoIP_Omnichannel',
'VoIP_Enabled',
'VoIP_JWT_Secret',
'Voip_Server_Configuration',
'VoIP_Server_Name',
'VoIP_Server_Websocket_Path',
'VoIP_Retry_Count',
'VoIP_Enable_Keep_Alive_For_Unstable_Networks',
'VoIP_Management_Server_Host',
'VoIP_Management_Server_Port',
'VoIP_Management_Server_Name',
'VoIP_Management_Server_Username',
'VoIP_Management_Server_Password',
],
},
});
await Permissions.deleteMany({
_id: {
$in: [
'manage-voip-call-settings',
'manage-voip-contact-center-settings',
'manage-agent-extension-association',
'inbound-voip-calls',
'spy-voip-calls',
'outbound-voip-calls',
'view-agent-extension-association',
],
},
});
},
});

View File

@@ -0,0 +1,23 @@
import { Rooms } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 326,
name: 'Close Omnichannel VoIP Rooms',
async up() {
await Rooms.updateMany(
{
t: 'v' as any,
open: {
$exists: true,
},
},
{
$unset: {
open: 1,
},
},
);
},
});

View File

@@ -0,0 +1,22 @@
import { Settings } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 327,
name: 'Remove FreeSwitch Settings',
async up() {
await Settings.deleteMany({
_id: {
$in: [
'VoIP_TeamCollab_FreeSwitch',
'VoIP_TeamCollab_FreeSwitch_Host',
'VoIP_TeamCollab_FreeSwitch_Port',
'VoIP_TeamCollab_FreeSwitch_Password',
'VoIP_TeamCollab_FreeSwitch_Timeout',
'VoIP_TeamCollab_FreeSwitch_WebSocket_Path',
],
},
});
},
});

View File

@@ -0,0 +1,15 @@
import { Permissions } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 328,
name: 'Remove Old Voip Permissions',
async up() {
await Permissions.deleteMany({
_id: {
$in: ['manage-voip-extensions', 'view-user-voip-extension', 'view-voip-extension-details'],
},
});
},
});

View File

@@ -0,0 +1,11 @@
import { Settings } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 329,
name: 'Remove VoIP Toggle Setting',
async up() {
await Settings.deleteOne({ _id: 'VoIP_TeamCollab_Enabled' });
},
});

View File

@@ -0,0 +1,11 @@
import { Settings } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 330,
name: 'Remove ECDH setting',
async up() {
await Settings.deleteOne({ _id: 'ECDH_Enabled' });
},
});

View File

@@ -0,0 +1,11 @@
import { Permissions } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 331,
name: 'Remove view-livechat-current-chats permission',
async up() {
await Permissions.deleteOne({ _id: 'view-livechat-current-chats' });
},
});

View File

@@ -0,0 +1,53 @@
import { CallHistory, Users } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 332,
name: 'Fill contact information on older call history entries',
async up() {
const cursor = CallHistory.col.aggregate([
{
$match: {
external: false,
contactId: { $exists: true },
contactName: { $exists: false },
contactUsername: { $exists: false },
},
},
{
$lookup: {
from: Users.col.collectionName,
localField: 'contactId',
foreignField: '_id',
as: 'contactDetails',
},
},
{
$addFields: {
contactName: { $first: '$contactDetails.name' },
contactUsername: { $first: '$contactDetails.username' },
},
},
{
$project: {
contactName: 1,
contactUsername: 1,
},
},
{
$merge: {
into: CallHistory.col.collectionName,
on: '_id',
whenMatched: 'merge',
},
},
]);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for await (const _item of cursor) {
//
}
},
});

View File

@@ -0,0 +1,35 @@
import { Settings } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 333,
name: 'Remove Voxtelesys & Mobex SMS settings by ID',
async up() {
const ids = [
// Voxtelesys settings
'SMS_Voxtelesys_authToken',
'SMS_Voxtelesys_URL',
'SMS_Voxtelesys_FileUpload_Enabled',
'SMS_Voxtelesys_FileUpload_MediaTypeWhiteList',
// Mobex settings
'SMS_Mobex_gateway_address',
'SMS_Mobex_gateway_address_desc',
'SMS_Mobex_from_number',
'SMS_Mobex_from_number_desc',
'SMS_Mobex_from_numbers_list',
'SMS_Mobex_from_numbers_list_desc',
'SMS_Mobex_password',
'SMS_Mobex_restful_address',
'SMS_Mobex_restful_address_desc',
'SMS_Mobex_username',
];
await Settings.deleteMany({
_id: { $in: ids },
});
await Settings.findOneAndUpdate({ _id: 'SMS_Service', value: { $ne: 'twilio' } }, { $set: { value: 'twilio' } });
},
});

View File

@@ -0,0 +1,22 @@
import { Settings } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 334,
name: 'Remove webRTC settings',
async up() {
await Settings.deleteMany({
_id: {
$in: [
'WebRTC_Enabled',
'WebRTC_Enable_Channel',
'WebRTC_Enable_Private',
'WebRTC_Enable_Direct',
'WebRTC_Calls_Count',
'WebRTC_Servers',
],
},
});
},
});

View File

@@ -0,0 +1,11 @@
import { Settings } from '@rocket.chat/models';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 335,
name: 'Remove OTR_Enable and OTR_Count settings',
async up() {
await Settings.deleteMany({ _id: { $in: ['OTR_Enable', 'OTR_Count'] } });
},
});

View File

@@ -0,0 +1,102 @@
import { Permissions, Roles, Settings, Users } from '@rocket.chat/models';
import type { UpdateResult } from 'mongodb';
import { upsertPermissions } from '../../lib/authorization/upsertPermissions';
import { migrateDatabase, onServerVersionChange } from '../../lib/migrations';
import { settings } from '../../settings';
import { ensureCloudWorkspaceRegistered } from '../cloudRegistration';
const { MIGRATION_VERSION = 'latest' } = process.env;
const [version, ...subcommands] = MIGRATION_VERSION.split(',');
const maxAgeSettingMap = new Map([
['RetentionPolicy_MaxAge_Channels', 'RetentionPolicy_TTL_Channels'],
['RetentionPolicy_MaxAge_Groups', 'RetentionPolicy_TTL_Groups'],
['RetentionPolicy_MaxAge_DMs', 'RetentionPolicy_TTL_DMs'],
]);
const moveRetentionSetting = async () => {
const convertDaysToMs = (days: number) => days * 24 * 60 * 60 * 1000;
const promises: Array<Promise<UpdateResult>> = [];
await Settings.find(
{ _id: { $in: Array.from(maxAgeSettingMap.keys()) }, value: { $ne: -1 } },
{ projection: { _id: 1, value: 1 } },
).forEach(({ _id, value }) => {
const newSettingId = maxAgeSettingMap.get(_id);
if (!newSettingId) {
throw new Error(`moveRetentionSetting - Setting ${_id} equivalent does not exist`);
}
const newValue = convertDaysToMs(Number(value));
promises.push(
Settings.updateOne(
{
_id: maxAgeSettingMap.get(_id),
},
{
$set: {
value: newValue,
},
},
),
);
const currentCache = settings.getSetting(newSettingId);
if (!currentCache) {
return;
}
settings.set({ ...currentCache, value: newValue });
});
await Promise.all(promises);
await Settings.updateMany({ _id: { $in: Array.from(maxAgeSettingMap.keys()) } }, { $set: { value: -1 } });
};
async function setPermissionsToNewRole() {
const role = await Roles.findOneById('federated-external');
// if role was found it means it was already created with the permissions, so we can skip it
if (role) {
return;
}
// if federation was used before the new role was created, we need to update their roles with the new role
await Users.updateMany({ federated: true }, { $addToSet: { roles: 'federated-external' } });
const rolePermissions = [
'create-c',
'create-d',
'create-p',
'delete-own-message',
'leave-c',
'leave-p',
'mention-all',
'mention-here',
'start-discussion',
'start-discussion-other-user',
'view-c-room',
'view-d-room',
'view-p-room',
'preview-c-room',
'view-outside-room',
'mobile-upload-file',
'access-federation',
];
// since this is a one time query, no need to create a method in model class for it
await Permissions.updateMany({ _id: { $in: rolePermissions } }, { $addToSet: { roles: 'federated-external' } });
}
export const performMigrationProcedure = async (): Promise<void> => {
await migrateDatabase(version === 'latest' ? version : parseInt(version), subcommands);
// perform operations when the server is starting with a different version
await onServerVersionChange(async () => {
await setPermissionsToNewRole();
await upsertPermissions();
await ensureCloudWorkspaceRegistered();
await moveRetentionSetting();
});
};

View File

@@ -0,0 +1,12 @@
import { Presence } from '@rocket.chat/core-services';
import { settings } from '../settings';
// maybe this setting should disable the listener to 'presence.status' event on listerners.module.ts
settings.watch('Troubleshoot_Disable_Presence_Broadcast', async (value) => {
try {
await Presence.toggleBroadcast(!value);
} catch (e) {
// do nothing
}
});

View File

@@ -0,0 +1,239 @@
import { Logger } from '@rocket.chat/logger';
import { DDPRateLimiter } from 'meteor/ddp-rate-limiter';
import { Meteor } from 'meteor/meteor';
import { RateLimiter } from 'meteor/rate-limit';
import _ from 'underscore';
import { sleep } from '../../lib/utils/sleep';
import { metrics } from '../lib/metrics';
import { settings } from '../settings';
const logger = new Logger('RateLimiter');
const slowDownRate = parseInt(process.env.RATE_LIMITER_SLOWDOWN_RATE);
const rateLimiterConsoleLog = ({ msg, reply, input }) => {
console.warn('DDP RATE LIMIT:', msg);
console.warn(JSON.stringify({ reply, input }, null, 2));
};
const rateLimiterLogger = ({ msg, reply, input }) => logger.info({ msg, reply, input });
const rateLimiterLog = String(process.env.RATE_LIMITER_LOGGER) === 'console' ? rateLimiterConsoleLog : rateLimiterLogger;
// Get initial set of names already registered for rules
const names = new Set(
Object.values(DDPRateLimiter.printRules())
.map((rule) => rule._matchers)
.filter((match) => typeof match.name === 'string')
.map((match) => match.name),
);
// Override the addRule to save new names added after this point
const { addRule } = DDPRateLimiter;
DDPRateLimiter.addRule = (matcher, calls, time, callback) => {
if (matcher && typeof matcher.name === 'string') {
names.add(matcher.name);
}
return addRule.call(DDPRateLimiter, matcher, calls, time, callback);
};
const { _increment } = DDPRateLimiter;
DDPRateLimiter._increment = function (input) {
const session = Meteor.server.sessions.get(input.connectionId);
input.broadcastAuth = (session && session.connectionHandle && session.connectionHandle.broadcastAuth) === true;
return _increment.call(DDPRateLimiter, input);
};
// Need to override the meteor's code duo to a problem with the callback reply
// being shared among all matchs
RateLimiter.prototype.check = function (input) {
// ==== BEGIN OVERRIDE ====
const session = Meteor.server.sessions.get(input.connectionId);
input.broadcastAuth = (session && session.connectionHandle && session.connectionHandle.broadcastAuth) === true;
// ==== END OVERRIDE ====
const self = this;
const reply = {
allowed: true,
timeToReset: 0,
numInvocationsLeft: Infinity,
};
const matchedRules = self._findAllMatchingRules(input);
_.each(matchedRules, (rule) => {
// ==== BEGIN OVERRIDE ====
const callbackReply = {
allowed: true,
timeToReset: 0,
numInvocationsLeft: Infinity,
};
// ==== END OVERRIDE ====
const ruleResult = rule.apply(input);
let numInvocations = rule.counters[ruleResult.key];
if (ruleResult.timeToNextReset < 0) {
// Reset all the counters since the rule has reset
rule.resetCounter();
ruleResult.timeSinceLastReset = new Date().getTime() - rule._lastResetTime;
ruleResult.timeToNextReset = rule.options.intervalTime;
numInvocations = 0;
}
if (numInvocations > rule.options.numRequestsAllowed) {
// Only update timeToReset if the new time would be longer than the
// previously set time. This is to ensure that if this input triggers
// multiple rules, we return the longest period of time until they can
// successfully make another call
if (reply.timeToReset < ruleResult.timeToNextReset) {
reply.timeToReset = ruleResult.timeToNextReset;
}
reply.allowed = false;
reply.numInvocationsLeft = 0;
// ==== BEGIN OVERRIDE ====
callbackReply.timeToReset = ruleResult.timeToNextReset;
callbackReply.allowed = false;
callbackReply.numInvocationsLeft = 0;
callbackReply.numInvocationsExceeded = numInvocations - rule.options.numRequestsAllowed;
rule._executeCallback(callbackReply, input);
// ==== END OVERRIDE ====
} else {
// If this is an allowed attempt and we haven't failed on any of the
// other rules that match, update the reply field.
if (rule.options.numRequestsAllowed - numInvocations < reply.numInvocationsLeft && reply.allowed) {
reply.timeToReset = ruleResult.timeToNextReset;
reply.numInvocationsLeft = rule.options.numRequestsAllowed - numInvocations;
}
// ==== BEGIN OVERRIDE ====
callbackReply.timeToReset = ruleResult.timeToNextReset;
callbackReply.numInvocationsLeft = rule.options.numRequestsAllowed - numInvocations;
rule._executeCallback(callbackReply, input);
// ==== END OVERRIDE ====
}
});
return reply;
};
const checkNameNonStream = (name) => name && !names.has(name) && !name.startsWith('stream-');
const checkNameForStream = (name) => name && !names.has(name) && name.startsWith('stream-');
const ruleIds = {};
const callback = (msg, name) => async (reply, input) => {
if (reply.allowed === false) {
rateLimiterLog({ msg, reply, input });
const rateLimitLabels = {
limit_name: name,
user_id: input.userId,
client_address: input.clientAddress,
type: input.type,
name: input.name,
connection_id: input.connectionId,
};
metrics.ddpRateLimitExceeded.inc(rateLimitLabels);
metrics.ddpRateLimitExceededTotal.inc(rateLimitLabels);
// sleep before sending the error to slow down next requests
if (slowDownRate > 0 && reply.numInvocationsExceeded) {
await sleep(slowDownRate * reply.numInvocationsExceeded);
}
// } else {
// console.log('DDP RATE LIMIT:', message);
// console.log(JSON.stringify({ ...reply, ...input }, null, 2));
}
};
const messages = {
IP: 'address',
User: 'userId',
Connection: 'connectionId',
User_By_Method: 'userId per method',
Connection_By_Method: 'connectionId per method',
};
const reconfigureLimit = Meteor.bindEnvironment((name, rules, factor = 1) => {
if (ruleIds[name + factor]) {
DDPRateLimiter.removeRule(ruleIds[name + factor]);
}
if (!settings.get(`DDP_Rate_Limit_${name}_Enabled`)) {
return;
}
ruleIds[name + factor] = addRule(
rules,
settings.get(`DDP_Rate_Limit_${name}_Requests_Allowed`) * factor,
settings.get(`DDP_Rate_Limit_${name}_Interval_Time`) * factor,
callback(`limit by ${messages[name]}`, name),
);
});
const configIP = _.debounce(() => {
reconfigureLimit('IP', {
broadcastAuth: false,
clientAddress: (clientAddress) => clientAddress !== '127.0.0.1',
});
}, 1000);
const configUser = _.debounce(() => {
reconfigureLimit('User', {
broadcastAuth: false,
userId: (userId) => userId != null,
});
}, 1000);
const configConnection = _.debounce(() => {
reconfigureLimit('Connection', {
broadcastAuth: false,
connectionId: () => true,
});
}, 1000);
const configUserByMethod = _.debounce(() => {
reconfigureLimit('User_By_Method', {
broadcastAuth: false,
type: () => true,
name: checkNameNonStream,
userId: (userId) => userId != null,
});
reconfigureLimit(
'User_By_Method',
{
broadcastAuth: false,
type: () => true,
name: checkNameForStream,
userId: (userId) => userId != null,
},
4,
);
}, 1000);
const configConnectionByMethod = _.debounce(() => {
reconfigureLimit('Connection_By_Method', {
broadcastAuth: false,
type: () => true,
name: checkNameNonStream,
connectionId: () => true,
});
reconfigureLimit(
'Connection_By_Method',
{
broadcastAuth: false,
type: () => true,
name: checkNameForStream,
connectionId: () => true,
},
4,
);
}, 1000);
if (!process.env.TEST_MODE) {
settings.watchByRegex(/^DDP_Rate_Limit_IP_.+/, configIP);
settings.watchByRegex(/^DDP_Rate_Limit_User_[^B].+/, configUser);
settings.watchByRegex(/^DDP_Rate_Limit_Connection_[^B].+/, configConnection);
settings.watchByRegex(/^DDP_Rate_Limit_User_By_Method_.+/, configUserByMethod);
settings.watchByRegex(/^DDP_Rate_Limit_Connection_By_Method_.+/, configConnectionByMethod);
}

View File

@@ -0,0 +1,11 @@
import { Meteor } from 'meteor/meteor';
import { WebApp } from 'meteor/webapp';
import { settings } from '../settings';
Meteor.startup(() => {
return WebApp.connectHandlers.use('/robots.txt', (req, res /* , next*/) => {
res.writeHead(200);
res.end(settings.get('Robot_Instructions_File_Content'));
});
});

View File

@@ -0,0 +1,131 @@
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import { License } from '@rocket.chat/license';
// import { Users } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import semver from 'semver';
import { Info } from '../../app/utils/rocketchat.info';
import { showErrorBox, showSuccessBox, showWarningBox } from '../lib/logger/showBox';
import { getMongoInfo } from '../lib/utils/functions/getMongoInfo';
// import { i18n } from '../lib/i18n';
// import { isRunningMs } from '../lib/isRunningMs';
// import { sendMessagesToAdmins } from '../lib/sendMessagesToAdmins';
import { settings } from '../settings';
const exitIfNotBypassed = (ignore: string | undefined, errorCode = 1) => {
if (typeof ignore === 'string' && ['yes', 'true'].includes(ignore.toLowerCase())) {
return;
}
process.exit(errorCode);
};
const skipMongoDbDeprecationCheck =
['yes', 'true'].includes(String(process.env.SKIP_MONGODEPRECATION_CHECK).toLowerCase()) ||
process.env.TEST_MODE === 'true' ||
process.env.TEST_MODE === 'api' ||
process.env.NODE_ENV === 'development';
// const skipMongoDbDeprecationBanner = ['yes', 'true'].includes(String(process.env.SKIP_MONGODEPRECATION_BANNER).toLowerCase());
Meteor.startup(async () => {
const { mongoVersion, mongoStorageEngine } = await getMongoInfo();
const desiredNodeVersion = semver.clean(fs.readFileSync(path.join(process.cwd(), '../../.node_version.txt')).toString());
const parsedSemVer = semver.parse(desiredNodeVersion);
if (!parsedSemVer) {
console.error('Failed to parse desired Node.js version from .node_version.txt');
process.exit(1);
}
const desiredNodeVersionMajor = String(parsedSemVer.major);
return setTimeout(async () => {
let msg: string | string[] = [
`Rocket.Chat Version: ${Info.version}`,
` NodeJS Version: ${process.versions.node} - ${process.arch}`,
` MongoDB Version: ${mongoVersion}`,
` MongoDB Engine: ${mongoStorageEngine}`,
` Platform: ${process.platform}`,
` Process Port: ${process.env.PORT}`,
` Site URL: ${settings.get<string>('Site_Url')}`,
` Hashed Site URL: ${License.getHashedWorkspaceUrl()}`,
` OpenSSL Version: ${process.versions.openssl}`,
` FIPS Provider: ${crypto.getFips() ? 'Enabled' : 'Disabled'}`,
];
if (Info.commit?.hash) {
msg.push(` Commit Hash: ${Info.commit.hash.substr(0, 10)}`);
}
if (Info.commit?.branch) {
msg.push(` Commit Branch: ${Info.commit.branch}`);
}
msg = msg.join('\n');
if (!semver.satisfies(process.versions.node, desiredNodeVersionMajor)) {
msg += [
'',
'',
'YOUR CURRENT NODEJS VERSION IS NOT SUPPORTED,',
`PLEASE UPGRADE / DOWNGRADE TO VERSION ${desiredNodeVersionMajor}.X.X`,
].join('\n');
showErrorBox('SERVER ERROR', msg);
exitIfNotBypassed(process.env.BYPASS_NODEJS_VALIDATION);
}
const mongoSemver = semver.coerce(mongoVersion);
if (!mongoSemver || semver.satisfies(mongoSemver, '<7.0.0')) {
msg += ['', '', 'YOUR CURRENT MONGODB VERSION IS NOT SUPPORTED BY ROCKET.CHAT,', 'PLEASE UPGRADE TO VERSION 8.0 OR LATER'].join('\n');
showErrorBox('SERVER ERROR', msg);
exitIfNotBypassed(process.env.BYPASS_MONGO_VALIDATION);
}
showSuccessBox('SERVER RUNNING', msg);
// Deprecation
if (!skipMongoDbDeprecationCheck && semver.satisfies(semver.coerce(mongoVersion)!, '<8.0.0')) {
msg = [
`DEPRECATED: MONGODB VERSION ${mongoVersion}.`,
'SUPPORT FOR MONGODB <8.0 WILL BE REMOVED IN ROCKET.CHAT 9.0.0',
'PLEASE UPGRADE MONGODB TO VERSION 8.0 OR GREATER',
].join('\n');
showWarningBox('DEPRECATION', msg);
// const id = `mongodbDeprecation_${mongoVersion.replace(/[^0-9]/g, '_')}`;
// const title = 'MongoDB_Deprecated';
// const text = 'MongoDB_version_s_is_deprecated_please_upgrade_your_installation';
// const link = 'https://go.rocket.chat/i/mongodb-deprecated';
// if (!(await Users.bannerExistsById(id))) {
// if (skipMongoDbDeprecationBanner || process.env.TEST_MODE) {
// return;
// }
// sendMessagesToAdmins({
// msgs: async ({ adminUser }) => [
// {
// msg: `*${i18n.t(title, adminUser.language)}*\n${i18n.t(text, { postProcess: 'sprintf', sprintf: [mongoVersion] }, adminUser.language)}\n${link}`,
// },
// ],
// banners: [
// {
// id,
// priority: 100,
// title,
// text,
// textArguments: [mongoVersion],
// modifiers: ['danger'],
// link,
// },
// ],
// });
// }
}
}, 100);
});