Refactored settings page.

This commit is contained in:
SamTV12345 2024-02-22 10:11:04 +01:00
parent 20a4eb0f8f
commit ab802c0683
39 changed files with 472 additions and 470 deletions

View file

@ -12,7 +12,7 @@ process.on('unhandledRejection', (err) => { throw err; });
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const querystring = require('querystring'); const querystring = require('querystring');
import {settings} from '../node/utils/Settings'; import settings from '../node/utils/Settings';
const supertest = require('supertest'); const supertest = require('supertest');
(async () => { (async () => {

View file

@ -49,7 +49,7 @@ const unescape = (val) => {
const fs = require('fs'); const fs = require('fs');
const log4js = require('log4js'); const log4js = require('log4js');
const readline = require('readline'); const readline = require('readline');
import {settings} from '../node/utils/Settings'; import settings from '../node/utils/Settings';
const ueberDB = require('ueberdb2'); const ueberDB = require('ueberdb2');
const dbWrapperSettings = { const dbWrapperSettings = {

View file

@ -14,7 +14,7 @@ process.on('unhandledRejection', (err) => { throw err; });
const dirtyDb = require('dirty'); const dirtyDb = require('dirty');
const log4js = require('log4js'); const log4js = require('log4js');
import {settings} from '../node/utils/Settings'; import settings from '../node/utils/Settings';
const ueberDB = require('ueberdb2'); const ueberDB = require('ueberdb2');
const util = require('util'); const util = require('util');
@ -22,7 +22,7 @@ process.on('unhandledRejection', (err) => { throw err; });
cache: '0', // The cache slows things down when you're mostly writing. cache: '0', // The cache slows things down when you're mostly writing.
writeInterval: 0, // Write directly to the database, don't buffer writeInterval: 0, // Write directly to the database, don't buffer
}; };
const db = new ueberDB.database( // eslint-disable-line new-cap const db = new ueberDB.Database( // eslint-disable-line new-cap
settings.dbType, settings.dbType,
settings.dbSettings, settings.dbSettings,
dbWrapperSettings, dbWrapperSettings,

View file

@ -19,7 +19,7 @@ let valueCount = 0;
(async () => { (async () => {
// initialize database // initialize database
import {settings} from '../node/utils/Settings'; import settings from '../node/utils/Settings';
const db = require('../node/db/DB'); const db = require('../node/db/DB');
await db.init(); await db.init();

View file

@ -10,7 +10,7 @@ const AttributePool = require('../../static/js/AttributePool');
const Stream = require('../utils/Stream'); const Stream = require('../utils/Stream');
const assert = require('assert').strict; const assert = require('assert').strict;
const db = require('./DB'); const db = require('./DB');
import {settings} from '../utils/Settings'; import settings from '../utils/Settings';
const authorManager = require('./AuthorManager'); const authorManager = require('./AuthorManager');
const padManager = require('./PadManager'); const padManager = require('./PadManager');
const padMessageHandler = require('../handler/PadMessageHandler'); const padMessageHandler = require('../handler/PadMessageHandler');

View file

@ -22,7 +22,7 @@
const CustomError = require('../utils/customError'); const CustomError = require('../utils/customError');
const Pad = require('../db/Pad'); const Pad = require('../db/Pad');
const db = require('./DB'); const db = require('./DB');
import {settings} from '../utils/Settings'; import settings from '../utils/Settings';
/** /**
* A cache of all loaded Pads. * A cache of all loaded Pads.

View file

@ -24,7 +24,7 @@ const hooks = require('../../static/js/pluginfw/hooks.js');
const padManager = require('./PadManager'); const padManager = require('./PadManager');
const readOnlyManager = require('./ReadOnlyManager'); const readOnlyManager = require('./ReadOnlyManager');
const sessionManager = require('./SessionManager'); const sessionManager = require('./SessionManager');
import {settings} from '../utils/Settings'; import settings from '../utils/Settings';
const webaccess = require('../hooks/express/webaccess'); const webaccess = require('../hooks/express/webaccess');
const log4js = require('log4js'); const log4js = require('log4js');
const authLogger = log4js.getLogger('auth'); const authLogger = log4js.getLogger('auth');

View file

@ -25,7 +25,7 @@ import fs from 'fs';
const hooks = require('../../static/js/pluginfw/hooks.js'); const hooks = require('../../static/js/pluginfw/hooks.js');
import path from 'path'; import path from 'path';
import resolve from 'resolve'; import resolve from 'resolve';
import {root, settings} from '../utils/Settings' import settings from '../utils/Settings'
const templateCache = new Map(); const templateCache = new Map();
@ -76,7 +76,7 @@ exports.require = (name: string, args: any, mod: any) => {
basedir = path.dirname(mod.filename); basedir = path.dirname(mod.filename);
paths = mod.paths; paths = mod.paths;
} }
paths.push(root + '/plugin_packages') paths.push(settings.root + '/plugin_packages')
const ejspath = resolve.sync(name, {paths, basedir, extensions: ['.html', '.ejs']}); const ejspath = resolve.sync(name, {paths, basedir, extensions: ['.html', '.ejs']});

View file

@ -24,7 +24,7 @@ const exporthtml = require('../utils/ExportHtml');
const exporttxt = require('../utils/ExportTxt'); const exporttxt = require('../utils/ExportTxt');
const exportEtherpad = require('../utils/ExportEtherpad'); const exportEtherpad = require('../utils/ExportEtherpad');
const fs = require('fs'); const fs = require('fs');
import {settings} from '../utils/Settings'; import settings from '../utils/Settings';
const os = require('os'); const os = require('os');
const hooks = require('../../static/js/pluginfw/hooks'); const hooks = require('../../static/js/pluginfw/hooks');
const util = require('util'); const util = require('util');

View file

@ -25,7 +25,7 @@ const padManager = require('../db/PadManager');
const padMessageHandler = require('./PadMessageHandler'); const padMessageHandler = require('./PadMessageHandler');
const fs = require('fs').promises; const fs = require('fs').promises;
const path = require('path'); const path = require('path');
import {settings} from '../utils/Settings'; import settings from '../utils/Settings';
const {Formidable} = require('formidable'); const {Formidable} = require('formidable');
const os = require('os'); const os = require('os');
const importHtml = require('../utils/ImportHtml'); const importHtml = require('../utils/ImportHtml');

View file

@ -28,7 +28,7 @@ const AttributeManager = require('../../static/js/AttributeManager');
const authorManager = require('../db/AuthorManager'); const authorManager = require('../db/AuthorManager');
const {padutils} = require('../../static/js/pad_utils'); const {padutils} = require('../../static/js/pad_utils');
const readOnlyManager = require('../db/ReadOnlyManager'); const readOnlyManager = require('../db/ReadOnlyManager');
import {abiwordAvailable, exportAvailable, randomVersionString, settings, sofficeAvailable} from '../utils/Settings'; import settings from '../utils/Settings';
const securityManager = require('../db/SecurityManager'); const securityManager = require('../db/SecurityManager');
const plugins = require('../../static/js/pluginfw/plugin_defs.js'); const plugins = require('../../static/js/pluginfw/plugin_defs.js');
const log4js = require('log4js'); const log4js = require('log4js');
@ -930,7 +930,7 @@ const handleClientReady = async (socket, message) => {
const clientVars = { const clientVars = {
skinName: settings.skinName, skinName: settings.skinName,
skinVariants: settings.skinVariants, skinVariants: settings.skinVariants,
randomVersionString: randomVersionString, randomVersionString: settings.randomVersionString,
accountPrivs: { accountPrivs: {
maxRevisions: 100, maxRevisions: 100,
}, },
@ -964,9 +964,9 @@ const handleClientReady = async (socket, message) => {
serverTimestamp: Date.now(), serverTimestamp: Date.now(),
sessionRefreshInterval: settings.cookie.sessionRefreshInterval, sessionRefreshInterval: settings.cookie.sessionRefreshInterval,
userId: sessionInfo.author, userId: sessionInfo.author,
abiwordAvailable: abiwordAvailable(), abiwordAvailable: settings.abiwordAvailable(),
sofficeAvailable: sofficeAvailable(), sofficeAvailable: settings.sofficeAvailable(),
exportAvailable: exportAvailable(), exportAvailable: settings.exportAvailable(),
plugins: { plugins: {
plugins: plugins.plugins, plugins: plugins.plugins,
parts: plugins.parts, parts: plugins.parts,

View file

@ -21,7 +21,7 @@
*/ */
const log4js = require('log4js'); const log4js = require('log4js');
import {settings} from '../utils/Settings'; import settings from '../utils/Settings';
const stats = require('../../node/stats') const stats = require('../../node/stats')
const logger = log4js.getLogger('socket.io'); const logger = log4js.getLogger('socket.io');

View file

@ -14,7 +14,7 @@ import fs from 'fs';
const hooks = require('../../static/js/pluginfw/hooks'); const hooks = require('../../static/js/pluginfw/hooks');
import log4js from 'log4js'; import log4js from 'log4js';
const SessionStore = require('../db/SessionStore'); const SessionStore = require('../db/SessionStore');
import {getEpVersion, getGitCommit, settings} from '../utils/Settings'; import settings from '../utils/Settings';
const stats = require('../stats') const stats = require('../stats')
import util from 'util'; import util from 'util';
const webaccess = require('./express/webaccess'); const webaccess = require('./express/webaccess');
@ -68,9 +68,9 @@ const closeServer = async () => {
exports.createServer = async () => { exports.createServer = async () => {
console.log('Report bugs at https://github.com/ether/etherpad-lite/issues'); console.log('Report bugs at https://github.com/ether/etherpad-lite/issues');
serverName = `Etherpad ${getGitCommit()} (https://etherpad.org)`; serverName = `Etherpad ${settings.getGitCommit()} (https://etherpad.org)`;
console.log(`Your Etherpad version is ${getEpVersion()} (${getGitCommit()})`); console.log(`Your Etherpad version is ${settings.getEpVersion()} (${settings.getGitCommit()})`);
await exports.restartServer(); await exports.restartServer();

View file

@ -7,7 +7,7 @@ import {QueryType} from "../../types/QueryType";
import {PluginType} from "../../types/Plugin"; import {PluginType} from "../../types/Plugin";
const eejs = require('../../eejs'); const eejs = require('../../eejs');
import {getEpVersion, getGitCommit} from '../../utils/Settings'; import settings from '../../utils/Settings';
const installer = require('../../../static/js/pluginfw/installer'); const installer = require('../../../static/js/pluginfw/installer');
const pluginDefs = require('../../../static/js/pluginfw/plugin_defs'); const pluginDefs = require('../../../static/js/pluginfw/plugin_defs');
const plugins = require('../../../static/js/pluginfw/plugins'); const plugins = require('../../../static/js/pluginfw/plugins');
@ -24,8 +24,8 @@ exports.expressCreateServer = (hookName:string, args: ArgsExpressType, cb:Functi
}); });
args.app.get('/admin/plugins/info', (req:any, res:any) => { args.app.get('/admin/plugins/info', (req:any, res:any) => {
const gitCommit = getGitCommit(); const gitCommit = settings.getGitCommit();
const epVersion = getEpVersion(); const epVersion = settings.getEpVersion();
res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins-info.html', { res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins-info.html', {
gitCommit, gitCommit,

View file

@ -4,7 +4,7 @@ const eejs = require('../../eejs');
const fsp = require('fs').promises; const fsp = require('fs').promises;
const hooks = require('../../../static/js/pluginfw/hooks'); const hooks = require('../../../static/js/pluginfw/hooks');
const plugins = require('../../../static/js/pluginfw/plugins'); const plugins = require('../../../static/js/pluginfw/plugins');
import {reloadSettings, settings} from '../../utils/Settings'; import settings from '../../utils/Settings';
exports.expressCreateServer = (hookName:string, {app}:any) => { exports.expressCreateServer = (hookName:string, {app}:any) => {
app.get('/admin/settings', (req:any, res:any) => { app.get('/admin/settings', (req:any, res:any) => {
@ -44,7 +44,7 @@ exports.socketio = (hookName:string, {io}:any) => {
socket.on('restartServer', async () => { socket.on('restartServer', async () => {
console.log('Admin request to restart server through a socket on /admin/settings'); console.log('Admin request to restart server through a socket on /admin/settings');
reloadSettings(); settings.reloadSettings();
await plugins.update(); await plugins.update();
await hooks.aCallAll('loadSettings', {settings}); await hooks.aCallAll('loadSettings', {settings});
await hooks.aCallAll('restartServer'); await hooks.aCallAll('restartServer');

View file

@ -3,7 +3,7 @@
import {ArgsExpressType} from "../../types/ArgsExpressType"; import {ArgsExpressType} from "../../types/ArgsExpressType";
const hasPadAccess = require('../../padaccess'); const hasPadAccess = require('../../padaccess');
import {exportAvailable, settings} from '../../utils/Settings'; import settings from '../../utils/Settings';
const exportHandler = require('../../handler/ExportHandler'); const exportHandler = require('../../handler/ExportHandler');
const importHandler = require('../../handler/ImportHandler'); const importHandler = require('../../handler/ImportHandler');
const padManager = require('../../db/PadManager'); const padManager = require('../../db/PadManager');
@ -35,7 +35,7 @@ exports.expressCreateServer = (hookName:string, args:ArgsExpressType, cb:Functio
} }
// if abiword is disabled, and this is a format we only support with abiword, output a message // if abiword is disabled, and this is a format we only support with abiword, output a message
if (exportAvailable() === 'no' && if (settings.exportAvailable() === 'no' &&
['odt', 'pdf', 'doc'].indexOf(req.params.type) !== -1) { ['odt', 'pdf', 'doc'].indexOf(req.params.type) !== -1) {
console.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format.` + console.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format.` +
' There is no converter configured'); ' There is no converter configured');

View file

@ -24,7 +24,7 @@ const cloneDeep = require('lodash.clonedeep');
const createHTTPError = require('http-errors'); const createHTTPError = require('http-errors');
const apiHandler = require('../../handler/APIHandler'); const apiHandler = require('../../handler/APIHandler');
import {settings} from '../../utils/Settings'; import settings from '../../utils/Settings';
const log4js = require('log4js'); const log4js = require('log4js');
const logger = log4js.getLogger('API'); const logger = log4js.getLogger('API');

View file

@ -6,7 +6,7 @@ const events = require('events');
const express = require('../express'); const express = require('../express');
const log4js = require('log4js'); const log4js = require('log4js');
const proxyaddr = require('proxy-addr'); const proxyaddr = require('proxy-addr');
import {settings} from '../../utils/Settings'; import settings from '../../utils/Settings';
import {Server} from 'socket.io' import {Server} from 'socket.io'
const socketIORouter = require('../../handler/SocketIORouter'); const socketIORouter = require('../../handler/SocketIORouter');
const hooks = require('../../../static/js/pluginfw/hooks'); const hooks = require('../../../static/js/pluginfw/hooks');

View file

@ -6,7 +6,7 @@ const fs = require('fs');
const fsp = fs.promises; const fsp = fs.promises;
const toolbar = require('../../utils/toolbar'); const toolbar = require('../../utils/toolbar');
const hooks = require('../../../static/js/pluginfw/hooks'); const hooks = require('../../../static/js/pluginfw/hooks');
import {getEpVersion, root, settings} from '../../utils/Settings'; import settings from '../../utils/Settings';
const util = require('util'); const util = require('util');
const webaccess = require('./webaccess'); const webaccess = require('./webaccess');
@ -17,7 +17,7 @@ exports.expressPreSession = async (hookName, {app}) => {
res.set('Content-Type', 'application/health+json'); res.set('Content-Type', 'application/health+json');
res.json({ res.json({
status: 'pass', status: 'pass',
releaseId: getEpVersion(), releaseId: settings.getEpVersion(),
}); });
}); });
@ -31,11 +31,11 @@ exports.expressPreSession = async (hookName, {app}) => {
app.get('/robots.txt', (req, res) => { app.get('/robots.txt', (req, res) => {
let filePath = let filePath =
path.join(root, 'src', 'static', 'skins', settings.skinName, 'robots.txt'); path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'robots.txt');
res.sendFile(filePath, (err) => { res.sendFile(filePath, (err) => {
// there is no custom robots.txt, send the default robots.txt which dissallows all // there is no custom robots.txt, send the default robots.txt which dissallows all
if (err) { if (err) {
filePath = path.join(root, 'src', 'static', 'robots.txt'); filePath = path.join(settings.root, 'src', 'static', 'robots.txt');
res.sendFile(filePath); res.sendFile(filePath);
} }
}); });
@ -54,9 +54,9 @@ exports.expressPreSession = async (hookName, {app}) => {
const fns = [ const fns = [
...(settings.favicon ? [path.resolve(root, settings.favicon)] : []), ...(settings.favicon ? [path.resolve(settings.root, settings.favicon)] : []),
path.join(root, 'src', 'static', 'skins', settings.skinName, 'favicon.ico'), path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'favicon.ico'),
path.join(root, 'src', 'static', 'favicon.ico'), path.join(settings.root, 'src', 'static', 'favicon.ico'),
]; ];
for (const fn of fns) { for (const fn of fns) {
try { try {

View file

@ -5,7 +5,7 @@ const fs = require('fs').promises;
const minify = require('../../utils/Minify'); const minify = require('../../utils/Minify');
const path = require('path'); const path = require('path');
const plugins = require('../../../static/js/pluginfw/plugin_defs'); const plugins = require('../../../static/js/pluginfw/plugin_defs');
import {root, settings} from '../../utils/Settings'; import {settings} from '../../utils/Settings';
const CachingMiddleware = require('../../utils/caching_middleware'); const CachingMiddleware = require('../../utils/caching_middleware');
const Yajsml = require('etherpad-yajsml'); const Yajsml = require('etherpad-yajsml');
@ -18,7 +18,7 @@ const getTar = async () => {
return `ep_etherpad-lite/static/js/${path}`; return `ep_etherpad-lite/static/js/${path}`;
} }
}; };
const tarJson = await fs.readFile(path.join(root, 'src/node/utils/tar.json'), 'utf8'); const tarJson = await fs.readFile(path.join(settings.root, 'src/node/utils/tar.json'), 'utf8');
const tar = {}; const tar = {};
for (const [key, relativeFiles] of Object.entries(JSON.parse(tarJson))) { for (const [key, relativeFiles] of Object.entries(JSON.parse(tarJson))) {
// @ts-ignore // @ts-ignore

View file

@ -4,7 +4,7 @@ const path = require('path');
const fsp = require('fs').promises; const fsp = require('fs').promises;
const plugins = require('../../../static/js/pluginfw/plugin_defs'); const plugins = require('../../../static/js/pluginfw/plugin_defs');
const sanitizePathname = require('../../utils/sanitizePathname'); const sanitizePathname = require('../../utils/sanitizePathname');
import {root, settings} from '../../utils/Settings'; import {settings} from '../../utils/Settings';
// Returns all *.js files under specDir (recursively) as relative paths to specDir, using '/' // Returns all *.js files under specDir (recursively) as relative paths to specDir, using '/'
// instead of path.sep to separate pathname components. // instead of path.sep to separate pathname components.
@ -57,7 +57,7 @@ exports.expressPreSession = async (hookName, {app}) => {
})().catch((err) => next(err || new Error(err))); })().catch((err) => next(err || new Error(err)));
}); });
const rootTestFolder = path.join(root, 'src/tests/frontend/'); const rootTestFolder = path.join(settings.root, 'src/tests/frontend/');
app.get('/tests/frontend/index.html', (req, res) => { app.get('/tests/frontend/index.html', (req, res) => {
res.redirect(['./', ...req.url.split('?').slice(1)].join('?')); res.redirect(['./', ...req.url.split('?').slice(1)].join('?'));

View file

@ -9,7 +9,7 @@ const path = require('path');
const _ = require('underscore'); const _ = require('underscore');
const pluginDefs = require('../../static/js/pluginfw/plugin_defs.js'); const pluginDefs = require('../../static/js/pluginfw/plugin_defs.js');
const existsSync = require('../utils/path_exists'); const existsSync = require('../utils/path_exists');
import {settings, root} from '../utils/Settings'; import {settings} from '../utils/Settings';
// returns all existing messages merged together and grouped by langcode // returns all existing messages merged together and grouped by langcode
// {es: {"foo": "string"}, en:...} // {es: {"foo": "string"}, en:...}
@ -40,7 +40,7 @@ const getAllLocales = () => {
}; };
// add core supported languages first // add core supported languages first
extractLangs(path.join(root, 'src/locales')); extractLangs(path.join(settings.root, 'src/locales'));
// add plugins languages (if any) // add plugins languages (if any)
for (const {package: {path: pluginPath}} of Object.values<I18nPluginDefs>(pluginDefs.plugins)) { for (const {package: {path: pluginPath}} of Object.values<I18nPluginDefs>(pluginDefs.plugins)) {

View file

@ -1,4 +1,5 @@
export type SettingsObj = { export type SettingsObj = {
root: string;
settingsFilename: string; settingsFilename: string;
credentialsFilename: string; credentialsFilename: string;
title: string; title: string;
@ -111,4 +112,14 @@ export type SettingsObj = {
enableAdminUITests: boolean; enableAdminUITests: boolean;
lowerCasePadIds: boolean; lowerCasePadIds: boolean;
randomVersionString: string; randomVersionString: string;
getEpVersion: ()=>string;
abiwordAvailable: ()=>string;
sofficeAvailable: ()=>string;
exportAvailable: ()=>string;
getGitCommit: ()=>string;
storeSettings: (newSettings: SettingsObj)=>void;
coerceValue: (stringVal: string)=> string | number | boolean | null | undefined;
lookupEnvironmentVariables: (settings: SettingsObj)=>SettingsObj;
parseSettings: (settingsFilename: string, isSettings: boolean) => any;
reloadSettings: ()=>void;
} }

View file

@ -21,7 +21,7 @@
* limitations under the License. * limitations under the License.
*/ */
import {root, settings} from './Settings'; import {settings} from './Settings';
const fs = require('fs').promises; const fs = require('fs').promises;
const path = require('path'); const path = require('path');
const plugins = require('../../static/js/pluginfw/plugin_defs'); const plugins = require('../../static/js/pluginfw/plugin_defs');
@ -33,7 +33,7 @@ const sanitizePathname = require('./sanitizePathname');
const logger = log4js.getLogger('Minify'); const logger = log4js.getLogger('Minify');
const ROOT_DIR = path.join(root, 'src/static/'); const ROOT_DIR = path.join(settings.root, 'src/static/');
const threadsPool = new Threads.Pool(() => Threads.spawn(new Threads.Worker('./MinifyWorker')), 2); const threadsPool = new Threads.Pool(() => Threads.spawn(new Threads.Worker('./MinifyWorker')), 2);

View file

@ -78,9 +78,9 @@ const initLogging = (config: any) => {
initLogging(defaultLogConfig(defaultLogLevel)); initLogging(defaultLogConfig(defaultLogLevel));
export const root = absolutePaths.findEtherpadRoot() const root = absolutePaths.findEtherpadRoot();
export const settings: SettingsObj = { export const settings: SettingsObj = {
root: absolutePaths.findEtherpadRoot(),
settingsFilename: absolutePaths.makeAbsolute(argv.settings || 'settings.json'), settingsFilename: absolutePaths.makeAbsolute(argv.settings || 'settings.json'),
credentialsFilename: absolutePaths.makeAbsolute(argv.credentials || 'credentials.json'), credentialsFilename: absolutePaths.makeAbsolute(argv.credentials || 'credentials.json'),
/** /**
@ -403,112 +403,98 @@ export const settings: SettingsObj = {
* e.g. /p/EtHeRpAd to /p/etherpad * e.g. /p/EtHeRpAd to /p/etherpad
*/ */
lowerCasePadIds: false, lowerCasePadIds: false,
randomVersionString: randomString(4) randomVersionString: randomString(4),
} // Return etherpad version from package.json
getEpVersion:()=>require('../../package.json').version,
/* Root path of the installation */ // checks if abiword is avaiable
logger.info('All relative paths will be interpreted relative to the identified ' + abiwordAvailable: () => {
`Etherpad base dir: ${root}`); if (settings.abiword != null) {
return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes';
// checks if abiword is avaiable
export const abiwordAvailable = () => {
if (settings.abiword != null) {
return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes';
} else {
return 'no';
}
};
export const sofficeAvailable = () => {
if (settings.soffice != null) {
return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes';
} else {
return 'no';
}
};
export const exportAvailable = () => {
const abiword = abiwordAvailable();
const soffice = sofficeAvailable();
if (abiword === 'no' && soffice === 'no') {
return 'no';
} else if ((abiword === 'withoutPDF' && soffice === 'no') ||
(abiword === 'no' && soffice === 'withoutPDF')) {
return 'withoutPDF';
} else {
return 'yes';
}
};
// Provide git version if available
export const getGitCommit = () => {
let version = '';
try {
let rootPath = root;
if (fs.lstatSync(`${rootPath}/.git`).isFile()) {
rootPath = fs.readFileSync(`${rootPath}/.git`, 'utf8');
rootPath = rootPath.split(' ').pop().trim();
} else { } else {
rootPath += '/.git'; return 'no';
} }
const ref = fs.readFileSync(`${rootPath}/HEAD`, 'utf-8'); },
if (ref.startsWith('ref: ')) { sofficeAvailable: () => {
const refPath = `${rootPath}/${ref.substring(5, ref.indexOf('\n'))}`; if (settings.soffice != null) {
version = fs.readFileSync(refPath, 'utf-8'); return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes';
} else { } else {
version = ref; return 'no';
} }
version = version.substring(0, 7); },
} catch (e: any) { exportAvailable: () => {
logger.warn(`Can't get git version for server header\n${e.message}`); const abiword = settings.abiwordAvailable();
} const soffice = settings.sofficeAvailable();
return version;
};
// Return etherpad version from package.json if (abiword === 'no' && soffice === 'no') {
export const getEpVersion = () => require('../../package.json').version; return 'no';
} else if ((abiword === 'withoutPDF' && soffice === 'no') ||
/** (abiword === 'no' && soffice === 'withoutPDF')) {
* Receives a settingsObj and, if the property name is a valid configuration return 'withoutPDF';
* item, stores it in the module's exported properties via a side effect. } else {
* return 'yes';
* This code refactors a previous version that copied & pasted the same code for
* both "settings.json" and "credentials.json".
*/
const storeSettings = (settingsObj: MapArrayType<any>) => {
for (const i of Object.keys(settingsObj || {})) {
if (nonSettings.includes(i)) {
logger.warn(`Ignoring setting: '${i}'`);
continue;
} }
},
// test if the setting starts with a lowercase character // Provide git version if available
if (i.charAt(0).search('[a-z]') !== 0) { getGitCommit: () => {
logger.warn(`Settings should start with a lowercase character: '${i}'`); let version = '';
} try {
let rootPath = root;
// we know this setting, so we overwrite it, if (fs.lstatSync(`${rootPath}/.git`).isFile()) {
// or it's a settings hash, specific to a plugin rootPath = fs.readFileSync(`${rootPath}/.git`, 'utf8');
// @ts-ignore rootPath = rootPath.split(' ').pop().trim();
if (settings[i] !== undefined || i.indexOf('ep_') === 0) {
if (_.isObject(settingsObj[i]) && !Array.isArray(settingsObj[i])) {
// @ts-ignore
settings[i] = _.defaults(settingsObj[i], settings[i]);
} else { } else {
// @ts-ignore rootPath += '/.git';
settings[i] = settingsObj[i];
} }
} else { const ref = fs.readFileSync(`${rootPath}/HEAD`, 'utf-8');
// this setting is unknown, output a warning and throw it away if (ref.startsWith('ref: ')) {
logger.warn(`Unknown Setting: '${i}'. This setting doesn't exist or it was removed`); const refPath = `${rootPath}/${ref.substring(5, ref.indexOf('\n'))}`;
version = fs.readFileSync(refPath, 'utf-8');
} else {
version = ref;
}
version = version.substring(0, 7);
} catch (e: any) {
logger.warn(`Can't get git version for server header\n${e.message}`);
} }
} return version;
}; },
/**
* Receives a settingsObj and, if the property name is a valid configuration
* item, stores it in the module's exported properties via a side effect.
*
* This code refactors a previous version that copied & pasted the same code for
* both "settings.json" and "credentials.json".
*/
storeSettings: (settingsObj: MapArrayType<any>) => {
for (const i of Object.keys(settingsObj || {})) {
if (nonSettings.includes(i)) {
logger.warn(`Ignoring setting: '${i}'`);
continue;
}
/* // test if the setting starts with a lowercase character
if (i.charAt(0).search('[a-z]') !== 0) {
logger.warn(`Settings should start with a lowercase character: '${i}'`);
}
// we know this setting, so we overwrite it,
// or it's a settings hash, specific to a plugin
// @ts-ignore
if (settings[i] !== undefined || i.indexOf('ep_') === 0) {
if (_.isObject(settingsObj[i]) && !Array.isArray(settingsObj[i])) {
// @ts-ignore
settings[i] = _.defaults(settingsObj[i], settings[i]);
} else {
// @ts-ignore
settings[i] = settingsObj[i];
}
} else {
// this setting is unknown, output a warning and throw it away
logger.warn(`Unknown Setting: '${i}'. This setting doesn't exist or it was removed`);
}
}
},
/*
* If stringValue is a numeric string, or its value is "true" or "false", coerce * If stringValue is a numeric string, or its value is "true" or "false", coerce
* them to appropriate JS types. Otherwise return stringValue as-is. * them to appropriate JS types. Otherwise return stringValue as-is.
* *
@ -519,337 +505,341 @@ const storeSettings = (settingsObj: MapArrayType<any>) => {
* If the user wants a variable to be null by default, he'll have to use the * If the user wants a variable to be null by default, he'll have to use the
* short syntax "${ABIWORD}", and not "${ABIWORD:null}": the latter would result * short syntax "${ABIWORD}", and not "${ABIWORD:null}": the latter would result
* in the literal string "null", instead. * in the literal string "null", instead.
*/ * */
const coerceValue = (stringValue: string) => { coerceValue: (stringValue: string) => {
// cooked from https://stackoverflow.com/questions/175739/built-in-way-in-javascript-to-check-if-a-string-is-a-valid-number // cooked from https://stackoverflow.com/questions/175739/built-in-way-in-javascript-to-check-if-a-string-is-a-valid-number
// @ts-ignore // @ts-ignore
const isNumeric = !isNaN(stringValue) && !isNaN(parseFloat(stringValue) && isFinite(stringValue)); const isNumeric = !isNaN(stringValue) && !isNaN(parseFloat(stringValue) && isFinite(stringValue));
if (isNumeric) { if (isNumeric) {
// detected numeric string. Coerce to a number // detected numeric string. Coerce to a number
return +stringValue; return +stringValue;
}
switch (stringValue) {
case 'true':
return true;
case 'false':
return false;
case 'undefined':
return undefined;
case 'null':
return null;
default:
return stringValue;
}
};
/**
* Takes a javascript object containing Etherpad's configuration, and returns
* another object, in which all the string properties whose value is of the form
* "${ENV_VAR}" or "${ENV_VAR:default_value}" got their value replaced with the
* contents of the given environment variable, or with a default value.
*
* By definition, an environment variable's value is always a string. However,
* the code base makes use of the various json types. To maintain compatiblity,
* some heuristics is applied:
*
* - if ENV_VAR does not exist in the environment, null is returned;
* - if ENV_VAR's value is "true" or "false", it is converted to the js boolean
* values true or false;
* - if ENV_VAR's value looks like a number, it is converted to a js number
* (details in the code).
*
* The following is a scheme of the behaviour of this function:
*
* +---------------------------+---------------+------------------+
* | Configuration string in | Value of | Resulting confi- |
* | settings.json | ENV_VAR | guration value |
* |---------------------------|---------------|------------------|
* | "${ENV_VAR}" | "some_string" | "some_string" |
* | "${ENV_VAR}" | "9001" | 9001 |
* | "${ENV_VAR}" | undefined | null |
* | "${ENV_VAR:some_default}" | "some_string" | "some_string" |
* | "${ENV_VAR:some_default}" | undefined | "some_default" |
* +---------------------------+---------------+------------------+
*
* IMPLEMENTATION NOTE: variable substitution is performed doing a round trip
* conversion to/from json, using a custom replacer parameter in
* JSON.stringify(), and parsing the JSON back again. This ensures that
* environment variable replacement is performed even on nested objects.
*
* see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter
*/
const lookupEnvironmentVariables = (obj: object) => {
const stringifiedAndReplaced = JSON.stringify(obj, (key, value) => {
/*
* the first invocation of replacer() is with an empty key. Just go on, or
* we would zap the entire object.
*/
if (key === '') {
return value;
} }
/* switch (stringValue) {
* If we received from the configuration file a number, a boolean or case 'true':
* something that is not a string, we can be sure that it was a literal return true;
* value. No need to perform any variable substitution. case 'false':
* return false;
* The environment variable expansion syntax "${ENV_VAR}" is just a string case 'undefined':
* of specific form, after all. return undefined;
*/ case 'null':
if (typeof value !== 'string') { return null;
return value; default:
return stringValue;
} }
},
/*
* Let's check if the string value looks like a variable expansion (e.g.:
* "${ENV_VAR}" or "${ENV_VAR:default_value}")
*/
// MUXATOR 2019-03-21: we could use named capture groups here once we migrate to nodejs v10
const match = value.match(/^\$\{([^:]*)(:((.|\n)*))?\}$/);
if (match == null) {
// no match: use the value literally, without any substitution
return value;
}
/*
* We found the name of an environment variable. Let's read its actual value
* and its default value, if given
*/
const envVarName = match[1];
const envVarValue = process.env[envVarName];
const defaultValue = match[3];
if ((envVarValue === undefined) && (defaultValue === undefined)) { /**
logger.warn(`Environment variable "${envVarName}" does not contain any value for ` + * Takes a javascript object containing Etherpad's configuration, and returns
`configuration key "${key}", and no default was given. Using null. ` + * another object, in which all the string properties whose value is of the form
'THIS BEHAVIOR MAY CHANGE IN A FUTURE VERSION OF ETHERPAD; you should ' + * "${ENV_VAR}" or "${ENV_VAR:default_value}" got their value replaced with the
'explicitly use "null" as the default if you want to continue to use null.'); * contents of the given environment variable, or with a default value.
*
* By definition, an environment variable's value is always a string. However,
* the code base makes use of the various json types. To maintain compatiblity,
* some heuristics is applied:
*
* - if ENV_VAR does not exist in the environment, null is returned;
* - if ENV_VAR's value is "true" or "false", it is converted to the js boolean
* values true or false;
* - if ENV_VAR's value looks like a number, it is converted to a js number
* (details in the code).
*
* The following is a scheme of the behaviour of this function:
*
* +---------------------------+---------------+------------------+
* | Configuration string in | Value of | Resulting confi- |
* | settings.json | ENV_VAR | guration value |
* |---------------------------|---------------|------------------|
* | "${ENV_VAR}" | "some_string" | "some_string" |
* | "${ENV_VAR}" | "9001" | 9001 |
* | "${ENV_VAR}" | undefined | null |
* | "${ENV_VAR:some_default}" | "some_string" | "some_string" |
* | "${ENV_VAR:some_default}" | undefined | "some_default" |
* +---------------------------+---------------+------------------+
*
* IMPLEMENTATION NOTE: variable substitution is performed doing a round trip
* conversion to/from json, using a custom replacer parameter in
* JSON.stringify(), and parsing the JSON back again. This ensures that
* environment variable replacement is performed even on nested objects.
*
* see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter
*/
lookupEnvironmentVariables: (obj: object) => {
const stringifiedAndReplaced = JSON.stringify(obj, (key, value) => {
/*
* the first invocation of replacer() is with an empty key. Just go on, or
* we would zap the entire object.
*/
if (key === '') {
return value;
}
/* /*
* We have to return null, because if we just returned undefined, the * If we received from the configuration file a number, a boolean or
* configuration item "key" would be stripped from the returned object. * something that is not a string, we can be sure that it was a literal
* value. No need to perform any variable substitution.
*
* The environment variable expansion syntax "${ENV_VAR}" is just a string
* of specific form, after all.
*/ */
if (typeof value !== 'string') {
return value;
}
/*
* Let's check if the string value looks like a variable expansion (e.g.:
* "${ENV_VAR}" or "${ENV_VAR:default_value}")
*/
// MUXATOR 2019-03-21: we could use named capture groups here once we migrate to nodejs v10
const match = value.match(/^\$\{([^:]*)(:((.|\n)*))?\}$/);
if (match == null) {
// no match: use the value literally, without any substitution
return value;
}
/*
* We found the name of an environment variable. Let's read its actual value
* and its default value, if given
*/
const envVarName = match[1];
const envVarValue = process.env[envVarName];
const defaultValue = match[3];
if ((envVarValue === undefined) && (defaultValue === undefined)) {
logger.warn(`Environment variable "${envVarName}" does not contain any value for ` +
`configuration key "${key}", and no default was given. Using null. ` +
'THIS BEHAVIOR MAY CHANGE IN A FUTURE VERSION OF ETHERPAD; you should ' +
'explicitly use "null" as the default if you want to continue to use null.');
/*
* We have to return null, because if we just returned undefined, the
* configuration item "key" would be stripped from the returned object.
*/
return null;
}
if ((envVarValue === undefined) && (defaultValue !== undefined)) {
logger.debug(`Environment variable "${envVarName}" not found for ` +
`configuration key "${key}". Falling back to default value.`);
return settings.coerceValue(defaultValue);
}
// envVarName contained some value.
/*
* For numeric and boolean strings let's convert it to proper types before
* returning it, in order to maintain backward compatibility.
*/
logger.debug(
`Configuration key "${key}" will be read from environment variable "${envVarName}"`);
return settings.coerceValue(envVarValue!);
});
return JSON.parse(stringifiedAndReplaced);
},
/**
* - reads the JSON configuration file settingsFilename from disk
* - strips the comments
* - replaces environment variables calling lookupEnvironmentVariables()
* - returns a parsed Javascript object
*
* The isSettings variable only controls the error logging.
*/
parseSettings: (settingsFilename: string, isSettings: boolean) => {
let settingsStr = '';
let settingsType, notFoundMessage, notFoundFunction;
if (isSettings) {
settingsType = 'settings';
notFoundMessage = 'Continuing using defaults!';
notFoundFunction = logger.warn.bind(logger);
} else {
settingsType = 'credentials';
notFoundMessage = 'Ignoring.';
notFoundFunction = logger.info.bind(logger);
}
try {
// read the settings file
settingsStr = fs.readFileSync(settingsFilename).toString();
} catch (e) {
notFoundFunction(`No ${settingsType} file found in ${settingsFilename}. ${notFoundMessage}`);
// or maybe undefined!
return null; return null;
} }
if ((envVarValue === undefined) && (defaultValue !== undefined)) { try {
logger.debug(`Environment variable "${envVarName}" not found for ` + settingsStr = jsonminify(settingsStr).replace(',]', ']').replace(',}', '}');
`configuration key "${key}". Falling back to default value.`);
return coerceValue(defaultValue); const settings = JSON.parse(settingsStr);
logger.info(`${settingsType} loaded from: ${settingsFilename}`);
return settings.lookupEnvironmentVariables(settings);
} catch (e: any) {
logger.error(`There was an error processing your ${settingsType} ` +
`file from ${settingsFilename}: ${e.message}`);
process.exit(1);
} }
},
reloadSettings:() => {
const settingsParsed = settings.parseSettings(settings.settingsFilename, true);
const credentials = settings.parseSettings(settings.credentialsFilename, false);
settings.storeSettings(settingsParsed);
settings.storeSettings(credentials);
// envVarName contained some value. // Init logging config
settings.logconfig = defaultLogConfig(settings.loglevel ? settings.loglevel : defaultLogLevel);
/* initLogging(settings.logconfig);
* For numeric and boolean strings let's convert it to proper types before
* returning it, in order to maintain backward compatibility.
*/
logger.debug(
`Configuration key "${key}" will be read from environment variable "${envVarName}"`);
return coerceValue(envVarValue!);
});
return JSON.parse(stringifiedAndReplaced);
};
/**
* - reads the JSON configuration file settingsFilename from disk
* - strips the comments
* - replaces environment variables calling lookupEnvironmentVariables()
* - returns a parsed Javascript object
*
* The isSettings variable only controls the error logging.
*/
export const parseSettings = (settingsFilename: string, isSettings: boolean) => {
let settingsStr = '';
let settingsType, notFoundMessage, notFoundFunction;
if (isSettings) {
settingsType = 'settings';
notFoundMessage = 'Continuing using defaults!';
notFoundFunction = logger.warn.bind(logger);
} else {
settingsType = 'credentials';
notFoundMessage = 'Ignoring.';
notFoundFunction = logger.info.bind(logger);
}
try {
// read the settings file
settingsStr = fs.readFileSync(settingsFilename).toString();
} catch (e) {
notFoundFunction(`No ${settingsType} file found in ${settingsFilename}. ${notFoundMessage}`);
// or maybe undefined!
return null;
}
try {
settingsStr = jsonminify(settingsStr).replace(',]', ']').replace(',}', '}');
const settings = JSON.parse(settingsStr);
logger.info(`${settingsType} loaded from: ${settingsFilename}`);
return lookupEnvironmentVariables(settings);
} catch (e: any) {
logger.error(`There was an error processing your ${settingsType} ` +
`file from ${settingsFilename}: ${e.message}`);
process.exit(1);
}
};
export let randomVersionString: string | undefined
export const reloadSettings = () => {
const settingsParsed = parseSettings(settings.settingsFilename, true);
const credentials = parseSettings(settings.credentialsFilename, false);
storeSettings(settingsParsed);
storeSettings(credentials);
// Init logging config
settings.logconfig = defaultLogConfig(settings.loglevel ? settings.loglevel : defaultLogLevel);
initLogging(settings.logconfig);
if (!settings.skinName) {
logger.warn('No "skinName" parameter found. Please check out settings.json.template and ' +
'update your settings.json. Falling back to the default "colibris".');
settings.skinName = 'colibris';
}
// checks if skinName has an acceptable value, otherwise falls back to "colibris"
if (settings.skinName) {
const skinBasePath = path.join(root, 'src', 'static', 'skins');
const countPieces = settings.skinName.split(path.sep).length;
if (countPieces !== 1) {
logger.error(`skinName must be the name of a directory under "${skinBasePath}". This is ` +
`not valid: "${settings.skinName}". Falling back to the default "colibris".`);
if (!settings.skinName) {
logger.warn('No "skinName" parameter found. Please check out settings.json.template and ' +
'update your settings.json. Falling back to the default "colibris".');
settings.skinName = 'colibris'; settings.skinName = 'colibris';
} }
// informative variable, just for the log messages // checks if skinName has an acceptable value, otherwise falls back to "colibris"
let skinPath = path.join(skinBasePath, settings.skinName); if (settings.skinName) {
const skinBasePath = path.join(root, 'src', 'static', 'skins');
const countPieces = settings.skinName.split(path.sep).length;
// what if someone sets skinName == ".." or "."? We catch him! if (countPieces !== 1) {
if (absolutePaths.isSubdir(skinBasePath, skinPath) === false) { logger.error(`skinName must be the name of a directory under "${skinBasePath}". This is ` +
logger.error(`Skin path ${skinPath} must be a subdirectory of ${skinBasePath}. ` + `not valid: "${settings.skinName}". Falling back to the default "colibris".`);
'Falling back to the default "colibris".');
settings.skinName = 'colibris'; settings.skinName = 'colibris';
skinPath = path.join(skinBasePath, settings.skinName); }
// informative variable, just for the log messages
let skinPath = path.join(skinBasePath, settings.skinName);
// what if someone sets skinName == ".." or "."? We catch him!
if (absolutePaths.isSubdir(skinBasePath, skinPath) === false) {
logger.error(`Skin path ${skinPath} must be a subdirectory of ${skinBasePath}. ` +
'Falling back to the default "colibris".');
settings.skinName = 'colibris';
skinPath = path.join(skinBasePath, settings.skinName);
}
if (!fs.existsSync(skinPath)) {
logger.error(`Skin path ${skinPath} does not exist. Falling back to the default "colibris".`);
settings.skinName = 'colibris';
skinPath = path.join(skinBasePath, settings.skinName);
}
logger.info(`Using skin "${settings.skinName}" in dir: ${skinPath}`);
} }
if (!fs.existsSync(skinPath)) { if (settings.abiword) {
logger.error(`Skin path ${skinPath} does not exist. Falling back to the default "colibris".`); // Check abiword actually exists
settings.skinName = 'colibris'; if (settings.abiword != null) {
skinPath = path.join(skinBasePath, settings.skinName); fs.exists(settings.abiword, (exists) => {
} if (!exists) {
const abiwordError = 'Abiword does not exist at this path, check your settings file.';
logger.info(`Using skin "${settings.skinName}" in dir: ${skinPath}`); if (!settings.suppressErrorsInPadText) {
} settings.defaultPadText += `\nError: ${abiwordError}${suppressDisableMsg}`;
}
if (settings.abiword) { logger.error(`${abiwordError} File location: ${settings.abiword}`);
// Check abiword actually exists settings.abiword = null;
if (settings.abiword != null) {
fs.exists(settings.abiword, (exists) => {
if (!exists) {
const abiwordError = 'Abiword does not exist at this path, check your settings file.';
if (!settings.suppressErrorsInPadText) {
settings.defaultPadText += `\nError: ${abiwordError}${suppressDisableMsg}`;
} }
logger.error(`${abiwordError} File location: ${settings.abiword}`); });
settings.abiword = null; }
}
if (settings.soffice) {
fs.exists(settings.soffice, (exists) => {
if (!exists) {
const sofficeError =
'soffice (libreoffice) does not exist at this path, check your settings file.';
if (!settings.suppressErrorsInPadText) {
settings.defaultPadText += `\nError: ${sofficeError}${suppressDisableMsg}`;
}
logger.error(`${sofficeError} File location: ${settings.soffice}`);
settings.soffice = null;
} }
}); });
} }
}
if (settings.soffice) { const sessionkeyFilename = absolutePaths.makeAbsolute(argv.sessionkey || './SESSIONKEY.txt');
fs.exists(settings.soffice, (exists) => { if (!settings.sessionKey) {
if (!exists) { try {
const sofficeError = settings.sessionKey = fs.readFileSync(sessionkeyFilename, 'utf8');
'soffice (libreoffice) does not exist at this path, check your settings file.'; logger.info(`Session key loaded from: ${sessionkeyFilename}`);
} catch (err) { /* ignored */
if (!settings.suppressErrorsInPadText) {
settings.defaultPadText += `\nError: ${sofficeError}${suppressDisableMsg}`;
}
logger.error(`${sofficeError} File location: ${settings.soffice}`);
settings.soffice = null;
} }
}); const keyRotationEnabled = settings.cookie.keyRotationInterval && settings.cookie.sessionLifetime;
} if (!settings.sessionKey && !keyRotationEnabled) {
logger.info(
const sessionkeyFilename = absolutePaths.makeAbsolute(argv.sessionkey || './SESSIONKEY.txt'); `Session key file "${sessionkeyFilename}" not found. Creating with random contents.`);
if (!settings.sessionKey) { settings.sessionKey = randomString(32);
try { fs.writeFileSync(sessionkeyFilename, settings.sessionKey, 'utf8');
settings.sessionKey = fs.readFileSync(sessionkeyFilename, 'utf8'); }
logger.info(`Session key loaded from: ${sessionkeyFilename}`); } else {
} catch (err) { /* ignored */ logger.warn('Declaring the sessionKey in the settings.json is deprecated. ' +
'This value is auto-generated now. Please remove the setting from the file. -- ' +
'If you are seeing this error after restarting using the Admin User ' +
'Interface then you can ignore this message.');
} }
const keyRotationEnabled = settings.cookie.keyRotationInterval && settings.cookie.sessionLifetime; if (settings.sessionKey) {
if (!settings.sessionKey && !keyRotationEnabled) { logger.warn(`The sessionKey setting and ${sessionkeyFilename} file are deprecated; ` +
logger.info( 'use automatic key rotation instead (see the cookie.keyRotationInterval setting).');
`Session key file "${sessionkeyFilename}" not found. Creating with random contents.`);
settings.sessionKey = randomString(32);
fs.writeFileSync(sessionkeyFilename, settings.sessionKey, 'utf8');
}
} else {
logger.warn('Declaring the sessionKey in the settings.json is deprecated. ' +
'This value is auto-generated now. Please remove the setting from the file. -- ' +
'If you are seeing this error after restarting using the Admin User ' +
'Interface then you can ignore this message.');
}
if (settings.sessionKey) {
logger.warn(`The sessionKey setting and ${sessionkeyFilename} file are deprecated; ` +
'use automatic key rotation instead (see the cookie.keyRotationInterval setting).');
}
if (settings.dbType === 'dirty') {
const dirtyWarning = 'DirtyDB is used. This is not recommended for production.';
if (!settings.suppressErrorsInPadText) {
settings.defaultPadText += `\nWarning: ${dirtyWarning}${suppressDisableMsg}`;
} }
settings.dbSettings.filename = absolutePaths.makeAbsolute(settings.dbSettings.filename); if (settings.dbType === 'dirty') {
logger.warn(`${dirtyWarning} File location: ${settings.dbSettings.filename}`); const dirtyWarning = 'DirtyDB is used. This is not recommended for production.';
if (!settings.suppressErrorsInPadText) {
settings.defaultPadText += `\nWarning: ${dirtyWarning}${suppressDisableMsg}`;
}
settings.dbSettings.filename = absolutePaths.makeAbsolute(settings.dbSettings.filename);
logger.warn(`${dirtyWarning} File location: ${settings.dbSettings.filename}`);
}
if (settings.ip === '') {
// using Unix socket for connectivity
logger.warn('The settings file contains an empty string ("") for the "ip" parameter. The ' +
'"port" parameter will be interpreted as the path to a Unix socket to bind at.');
}
/*
* At each start, Etherpad generates a random string and appends it as query
* parameter to the URLs of the static assets, in order to force their reload.
* Subsequent requests will be cached, as long as the server is not reloaded.
*
* For the rationale behind this choice, see
* https://github.com/ether/etherpad-lite/pull/3958
*
* ACHTUNG: this may prevent caching HTTP proxies to work
* TODO: remove the "?v=randomstring" parameter, and replace with hashed filenames instead
*/
logger.info(`Random string used for versioning assets: ${settings.randomVersionString}`);
} }
}
if (settings.ip === '') { /* Root path of the installation */
// using Unix socket for connectivity logger.info('All relative paths will be interpreted relative to the identified ' +
logger.warn('The settings file contains an empty string ("") for the "ip" parameter. The ' + `Etherpad base dir: ${root}`);
'"port" parameter will be interpreted as the path to a Unix socket to bind at.');
}
/*
* At each start, Etherpad generates a random string and appends it as query
* parameter to the URLs of the static assets, in order to force their reload.
* Subsequent requests will be cached, as long as the server is not reloaded.
*
* For the rationale behind this choice, see
* https://github.com/ether/etherpad-lite/pull/3958
*
* ACHTUNG: this may prevent caching HTTP proxies to work
* TODO: remove the "?v=randomstring" parameter, and replace with hashed filenames instead
*/
randomVersionString = randomString(4);
logger.info(`Random string used for versioning assets: ${randomVersionString}`);
};
export const exportedForTestingOnly = {
parseSettings,
};
// initially load settings // initially load settings
reloadSettings(); settings.reloadSettings
export default settings

View file

@ -1,9 +1,9 @@
'use strict'; 'use strict';
const semver = require('semver'); const semver = require('semver');
import {getEpVersion, settings} from './Settings'; import {settings} from './Settings';
const axios = require('axios'); const axios = require('axios');
const headers = { const headers = {
'User-Agent': 'Etherpad/' + getEpVersion(), 'User-Agent': 'Etherpad/' + settings.getEpVersion(),
} }
type Infos = { type Infos = {
@ -45,7 +45,7 @@ exports.getLatestVersion = () => {
exports.needsUpdate = async (cb: Function) => { exports.needsUpdate = async (cb: Function) => {
await loadEtherpadInformations() await loadEtherpadInformations()
.then((info:Infos) => { .then((info:Infos) => {
if (semver.gt(info.latestVersion, getEpVersion())) { if (semver.gt(info.latestVersion, settings.getEpVersion())) {
if (cb) return cb(true); if (cb) return cb(true);
} }
}).catch((err: Error) => { }).catch((err: Error) => {

View file

@ -21,7 +21,7 @@ const fs = require('fs');
const fsp = fs.promises; const fsp = fs.promises;
const path = require('path'); const path = require('path');
const zlib = require('zlib'); const zlib = require('zlib');
import {root} from './Settings'; import {settings} from './Settings';
const existsSync = require('./path_exists'); const existsSync = require('./path_exists');
const util = require('util'); const util = require('util');
@ -40,7 +40,7 @@ const util = require('util');
const _crypto = require('crypto'); const _crypto = require('crypto');
let CACHE_DIR = path.join(root, 'var/'); let CACHE_DIR = path.join(settings.root, 'var/');
CACHE_DIR = existsSync(CACHE_DIR) ? CACHE_DIR : undefined; CACHE_DIR = existsSync(CACHE_DIR) ? CACHE_DIR : undefined;
type Headers = { type Headers = {

View file

@ -5,7 +5,7 @@ import {ChildProcess} from "node:child_process";
import {PromiseWithStd} from "../types/PromiseWithStd"; import {PromiseWithStd} from "../types/PromiseWithStd";
import {Readable} from "node:stream"; import {Readable} from "node:stream";
import {root} from "./Settings"; import {settings} from "./Settings";
import spawn from 'cross-spawn'; import spawn from 'cross-spawn';
import log4js from 'log4js'; import log4js from 'log4js';
@ -77,7 +77,7 @@ const logLines = (readable: undefined | Readable | null, logLineFn: (arg0: (stri
module.exports = exports = (args: string[], opts:any = {}) => { module.exports = exports = (args: string[], opts:any = {}) => {
logger.debug(`Executing command: ${args.join(' ')}`); logger.debug(`Executing command: ${args.join(' ')}`);
opts = {cwd: root, ...opts}; opts = {cwd: settings.root, ...opts};
logger.debug(`cwd: ${opts.cwd}`); logger.debug(`cwd: ${opts.cwd}`);
// Log stdout and stderr by default. // Log stdout and stderr by default.
@ -112,8 +112,8 @@ module.exports = exports = (args: string[], opts:any = {}) => {
opts.env = { opts.env = {
...env, // Copy env to avoid modifying process.env or the caller's supplied env. ...env, // Copy env to avoid modifying process.env or the caller's supplied env.
[pathVarName]: [ [pathVarName]: [
path.join(root, 'src', 'node_modules', '.bin'), path.join(settings.root, 'src', 'node_modules', '.bin'),
path.join(root, 'node_modules', '.bin'), path.join(settings.root, 'node_modules', '.bin'),
...(PATH ? PATH.split(path.delimiter) : []), ...(PATH ? PATH.split(path.delimiter) : []),
].join(path.delimiter), ].join(path.delimiter),
}; };

View file

@ -86,6 +86,7 @@
"@types/formidable": "^3.4.5", "@types/formidable": "^3.4.5",
"@types/jsonminify": "^0.4.3", "@types/jsonminify": "^0.4.3",
"@types/node": "^20.11.19", "@types/node": "^20.11.19",
"@types/resolve": "^1.20.6",
"@types/underscore": "^1.11.15", "@types/underscore": "^1.11.15",
"cypress": "^13.6.4", "cypress": "^13.6.4",
"eslint": "^8.56.0", "eslint": "^8.56.0",

View file

@ -4,7 +4,7 @@ const log4js = require('log4js');
const plugins = require('./plugins'); const plugins = require('./plugins');
const hooks = require('./hooks'); const hooks = require('./hooks');
const runCmd = require('../../../node/utils/run_cmd'); const runCmd = require('../../../node/utils/run_cmd');
import {getEpVersion, reloadSettings, root, settings} from '../../../node/utils/Settings'; import {settings} from '../../../node/utils/Settings';
const axios = require('axios'); const axios = require('axios');
const {PluginManager} = require('live-plugin-manager-pnpm'); const {PluginManager} = require('live-plugin-manager-pnpm');
const {promises: fs} = require('fs'); const {promises: fs} = require('fs');
@ -14,18 +14,18 @@ const logger = log4js.getLogger('plugins');
exports.manager = new PluginManager(); exports.manager = new PluginManager();
const installedPluginsPath = path.join(root, 'var/installed_plugins.json'); const installedPluginsPath = path.join(settings.root, 'var/installed_plugins.json');
const onAllTasksFinished = async () => { const onAllTasksFinished = async () => {
await plugins.update(); await plugins.update();
await persistInstalledPlugins(); await persistInstalledPlugins();
reloadSettings(); settings.reloadSettings();
await hooks.aCallAll('loadSettings', {settings}); await hooks.aCallAll('loadSettings', {settings});
await hooks.aCallAll('restartServer'); await hooks.aCallAll('restartServer');
}; };
const headers = { const headers = {
'User-Agent': `Etherpad/${getEpVersion()}`, 'User-Agent': `Etherpad/${settings.getEpVersion()}`,
}; };
let tasks = 0; let tasks = 0;

View file

@ -9,7 +9,7 @@ const tsort = require('./tsort');
const pluginUtils = require('./shared'); const pluginUtils = require('./shared');
const defs = require('./plugin_defs'); const defs = require('./plugin_defs');
const {manager} = require('./installer'); const {manager} = require('./installer');
import {getEpVersion, root, settings} from "../../../node/utils/Settings"; import {settings} from "../../../node/utils/Settings";
const logger = log4js.getLogger('plugins'); const logger = log4js.getLogger('plugins');
@ -121,9 +121,9 @@ exports.getPackages = async () => {
newDependencies['ep_etherpad-lite'] = { newDependencies['ep_etherpad-lite'] = {
name: 'ep_etherpad-lite', name: 'ep_etherpad-lite',
version: getEpVersion(), version: settings.getEpVersion(),
path: path.join(root, 'node_modules/ep_etherpad-lite'), path: path.join(settings.root, 'node_modules/ep_etherpad-lite'),
realPath: path.join(root, 'src'), realPath: path.join(settings.root, 'src'),
}; };
return newDependencies; return newDependencies;

View file

@ -1,5 +1,5 @@
<% <%
var settings = require("ep_etherpad-lite/node/utils/Settings").settings var settings = require("ep_etherpad-lite/node/utils/Settings")
%> %>
<!doctype html> <!doctype html>
<html> <html>

View file

@ -1,5 +1,5 @@
<% <%
var settings = require("ep_etherpad-lite/node/utils/Settings").settings var settings = require("ep_etherpad-lite/node/utils/Settings")
, langs = require("ep_etherpad-lite/node/hooks/i18n").availableLangs , langs = require("ep_etherpad-lite/node/hooks/i18n").availableLangs
, pluginUtils = require('ep_etherpad-lite/static/js/pluginfw/shared') , pluginUtils = require('ep_etherpad-lite/static/js/pluginfw/shared')
; ;
@ -455,10 +455,10 @@
<!-- Bootstrap page --> <!-- Bootstrap page -->
<script type="text/javascript"> <script type="text/javascript">
// @license magnet:?xt=urn:btih:8e4f440f4c65981c5bf93c76d35135ba5064d8b7&dn=apache-2.0.txt // @license magnet:?xt=urn:btih:8e4f440f4c65981c5bf93c76d35135ba5064d8b7&dn=apache-2.0.txt
var clientVars = { const clientVars = {
// This is needed to fetch /pluginfw/plugin-definitions.json, which happens before the // This is needed to fetch /pluginfw/plugin-definitions.json, which happens before the
// server sends the CLIENT_VARS message. // server sends the CLIENT_VARS message.
randomVersionString: <%-JSON.stringify(settings.randomVersionString)%>, randomVersionString: <;%-JSON.stringify(settings.randomVersionString)%>,
}; };
(function () { (function () {
var pathComponents = location.pathname.split('/'); var pathComponents = location.pathname.split('/');

View file

@ -1,5 +1,5 @@
<% <%
var settings = require("ep_etherpad-lite/node/utils/Settings").settings var settings = require("ep_etherpad-lite/node/utils/Settings")
, langs = require("ep_etherpad-lite/node/hooks/i18n").availableLangs , langs = require("ep_etherpad-lite/node/hooks/i18n").availableLangs
%> %>
<!doctype html> <!doctype html>

View file

@ -5,7 +5,7 @@ const common = require('../common');
const fs = require('fs'); const fs = require('fs');
const fsp = fs.promises; const fsp = fs.promises;
const path = require('path'); const path = require('path');
import {root, settings} from '../../../node/utils/Settings' import {settings} from '../../../node/utils/Settings'
const superagent = require('superagent'); const superagent = require('superagent');
describe(__filename, function () { describe(__filename, function () {
@ -19,13 +19,13 @@ describe(__filename, function () {
before(async function () { before(async function () {
agent = await common.init(); agent = await common.init();
wantCustomIcon = await fsp.readFile(path.join(__dirname, 'favicon-test-custom.png')); wantCustomIcon = await fsp.readFile(path.join(__dirname, 'favicon-test-custom.png'));
wantDefaultIcon = await fsp.readFile(path.join(root, 'src', 'static', 'favicon.ico')); wantDefaultIcon = await fsp.readFile(path.join(settings.root, 'src', 'static', 'favicon.ico'));
wantSkinIcon = await fsp.readFile(path.join(__dirname, 'favicon-test-skin.png')); wantSkinIcon = await fsp.readFile(path.join(__dirname, 'favicon-test-skin.png'));
}); });
beforeEach(async function () { beforeEach(async function () {
backupSettings = {...settings}; backupSettings = {...settings};
skinDir = await fsp.mkdtemp(path.join(root, 'src', 'static', 'skins', 'test-')); skinDir = await fsp.mkdtemp(path.join(settings.root, 'src', 'static', 'skins', 'test-'));
settings.skinName = path.basename(skinDir); settings.skinName = path.basename(skinDir);
}); });
@ -43,7 +43,7 @@ describe(__filename, function () {
it('uses custom favicon if set (relative pathname)', async function () { it('uses custom favicon if set (relative pathname)', async function () {
settings.favicon = settings.favicon =
path.relative(root, path.join(__dirname, 'favicon-test-custom.png')); path.relative(settings.root, path.join(__dirname, 'favicon-test-custom.png'));
assert(!path.isAbsolute(settings.favicon)); assert(!path.isAbsolute(settings.favicon));
const {body: gotIcon} = await agent.get('/favicon.ico') const {body: gotIcon} = await agent.get('/favicon.ico')
.accept('png').buffer(true).parse(superagent.parse.image) .accept('png').buffer(true).parse(superagent.parse.image)

View file

@ -2,7 +2,7 @@
const assert = require('assert').strict; const assert = require('assert').strict;
const common = require('../common'); const common = require('../common');
import * as settings from '../../../node/utils/Settings'; import settings from '../../../node/utils/Settings';
const superagent = require('superagent'); const superagent = require('superagent');
describe(__filename, function () { describe(__filename, function () {

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
const assert = require('assert').strict; const assert = require('assert').strict;
import {parseSettings} from '../../../node/utils/Settings'; import settings from '../../../node/utils/Settings';
const path = require('path'); const path = require('path');
const process = require('process'); const process = require('process');
@ -21,7 +21,7 @@ describe(__filename, function () {
before(async function () { before(async function () {
for (const tc of envVarSubstTestCases) process.env[tc.var] = tc.val; for (const tc of envVarSubstTestCases) process.env[tc.var] = tc.val;
delete process.env.UNSET_VAR; delete process.env.UNSET_VAR;
settings = parseSettings(path.join(__dirname, 'settings.json'), true); settings = settings.parseSettings(path.join(__dirname, 'settings.json'), true);
assert(settings != null); assert(settings != null);
}); });

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
const common = require('../common'); const common = require('../common');
import * as settings from '../../../node/utils/Settings'; import settings from '../../../node/utils/Settings';
describe(__filename, function () { describe(__filename, function () {
this.timeout(30000); this.timeout(30000);

View file

@ -3,7 +3,7 @@
const assert = require('assert').strict; const assert = require('assert').strict;
const common = require('../common'); const common = require('../common');
const plugins = require('../../../static/js/pluginfw/plugin_defs'); const plugins = require('../../../static/js/pluginfw/plugin_defs');
import * as settings from '../../../node/utils/Settings'; import settings from '../../../node/utils/Settings';
describe(__filename, function () { describe(__filename, function () {
this.timeout(30000); this.timeout(30000);