diff --git a/src/bin/createUserSession.js b/src/bin/createUserSession.js index 95fbce90b..6f7727bf9 100644 --- a/src/bin/createUserSession.js +++ b/src/bin/createUserSession.js @@ -12,7 +12,7 @@ process.on('unhandledRejection', (err) => { throw err; }); const fs = require('fs'); const path = require('path'); const querystring = require('querystring'); -import {settings} from '../node/utils/Settings'; +import settings from '../node/utils/Settings'; const supertest = require('supertest'); (async () => { diff --git a/src/bin/importSqlFile.js b/src/bin/importSqlFile.js index 50029a88c..98f562d8f 100644 --- a/src/bin/importSqlFile.js +++ b/src/bin/importSqlFile.js @@ -49,7 +49,7 @@ const unescape = (val) => { const fs = require('fs'); const log4js = require('log4js'); const readline = require('readline'); - import {settings} from '../node/utils/Settings'; + import settings from '../node/utils/Settings'; const ueberDB = require('ueberdb2'); const dbWrapperSettings = { diff --git a/src/bin/migrateDirtyDBtoRealDB.js b/src/bin/migrateDirtyDBtoRealDB.js index 8af06e8a6..c4b34343e 100644 --- a/src/bin/migrateDirtyDBtoRealDB.js +++ b/src/bin/migrateDirtyDBtoRealDB.js @@ -14,7 +14,7 @@ process.on('unhandledRejection', (err) => { throw err; }); const dirtyDb = require('dirty'); const log4js = require('log4js'); - import {settings} from '../node/utils/Settings'; + import settings from '../node/utils/Settings'; const ueberDB = require('ueberdb2'); 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. 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.dbSettings, dbWrapperSettings, diff --git a/src/bin/repairPad.js b/src/bin/repairPad.js index 0874572fa..e0623af9e 100644 --- a/src/bin/repairPad.js +++ b/src/bin/repairPad.js @@ -19,7 +19,7 @@ let valueCount = 0; (async () => { // initialize database - import {settings} from '../node/utils/Settings'; + import settings from '../node/utils/Settings'; const db = require('../node/db/DB'); await db.init(); diff --git a/src/node/db/Pad.js b/src/node/db/Pad.js index 57c1f68c2..cb45c09f7 100644 --- a/src/node/db/Pad.js +++ b/src/node/db/Pad.js @@ -10,7 +10,7 @@ const AttributePool = require('../../static/js/AttributePool'); const Stream = require('../utils/Stream'); const assert = require('assert').strict; const db = require('./DB'); -import {settings} from '../utils/Settings'; +import settings from '../utils/Settings'; const authorManager = require('./AuthorManager'); const padManager = require('./PadManager'); const padMessageHandler = require('../handler/PadMessageHandler'); diff --git a/src/node/db/PadManager.js b/src/node/db/PadManager.js index b07978de4..89d45a062 100644 --- a/src/node/db/PadManager.js +++ b/src/node/db/PadManager.js @@ -22,7 +22,7 @@ const CustomError = require('../utils/customError'); const Pad = require('../db/Pad'); const db = require('./DB'); -import {settings} from '../utils/Settings'; +import settings from '../utils/Settings'; /** * A cache of all loaded Pads. diff --git a/src/node/db/SecurityManager.js b/src/node/db/SecurityManager.js index 30f4819cb..03504b8bd 100644 --- a/src/node/db/SecurityManager.js +++ b/src/node/db/SecurityManager.js @@ -24,7 +24,7 @@ const hooks = require('../../static/js/pluginfw/hooks.js'); const padManager = require('./PadManager'); const readOnlyManager = require('./ReadOnlyManager'); const sessionManager = require('./SessionManager'); -import {settings} from '../utils/Settings'; +import settings from '../utils/Settings'; const webaccess = require('../hooks/express/webaccess'); const log4js = require('log4js'); const authLogger = log4js.getLogger('auth'); diff --git a/src/node/eejs/index.ts b/src/node/eejs/index.ts index 18b113f5a..bda453cb1 100644 --- a/src/node/eejs/index.ts +++ b/src/node/eejs/index.ts @@ -25,7 +25,7 @@ import fs from 'fs'; const hooks = require('../../static/js/pluginfw/hooks.js'); import path from 'path'; import resolve from 'resolve'; -import {root, settings} from '../utils/Settings' +import settings from '../utils/Settings' const templateCache = new Map(); @@ -76,7 +76,7 @@ exports.require = (name: string, args: any, mod: any) => { basedir = path.dirname(mod.filename); paths = mod.paths; } - paths.push(root + '/plugin_packages') + paths.push(settings.root + '/plugin_packages') const ejspath = resolve.sync(name, {paths, basedir, extensions: ['.html', '.ejs']}); diff --git a/src/node/handler/ExportHandler.js b/src/node/handler/ExportHandler.js index d6680b485..8463689dd 100644 --- a/src/node/handler/ExportHandler.js +++ b/src/node/handler/ExportHandler.js @@ -24,7 +24,7 @@ const exporthtml = require('../utils/ExportHtml'); const exporttxt = require('../utils/ExportTxt'); const exportEtherpad = require('../utils/ExportEtherpad'); const fs = require('fs'); -import {settings} from '../utils/Settings'; +import settings from '../utils/Settings'; const os = require('os'); const hooks = require('../../static/js/pluginfw/hooks'); const util = require('util'); diff --git a/src/node/handler/ImportHandler.js b/src/node/handler/ImportHandler.js index 63c20f46b..615d09f7e 100644 --- a/src/node/handler/ImportHandler.js +++ b/src/node/handler/ImportHandler.js @@ -25,7 +25,7 @@ const padManager = require('../db/PadManager'); const padMessageHandler = require('./PadMessageHandler'); const fs = require('fs').promises; const path = require('path'); -import {settings} from '../utils/Settings'; +import settings from '../utils/Settings'; const {Formidable} = require('formidable'); const os = require('os'); const importHtml = require('../utils/ImportHtml'); diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index dde71d362..f81ef2f6e 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -28,7 +28,7 @@ const AttributeManager = require('../../static/js/AttributeManager'); const authorManager = require('../db/AuthorManager'); const {padutils} = require('../../static/js/pad_utils'); 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 plugins = require('../../static/js/pluginfw/plugin_defs.js'); const log4js = require('log4js'); @@ -930,7 +930,7 @@ const handleClientReady = async (socket, message) => { const clientVars = { skinName: settings.skinName, skinVariants: settings.skinVariants, - randomVersionString: randomVersionString, + randomVersionString: settings.randomVersionString, accountPrivs: { maxRevisions: 100, }, @@ -964,9 +964,9 @@ const handleClientReady = async (socket, message) => { serverTimestamp: Date.now(), sessionRefreshInterval: settings.cookie.sessionRefreshInterval, userId: sessionInfo.author, - abiwordAvailable: abiwordAvailable(), - sofficeAvailable: sofficeAvailable(), - exportAvailable: exportAvailable(), + abiwordAvailable: settings.abiwordAvailable(), + sofficeAvailable: settings.sofficeAvailable(), + exportAvailable: settings.exportAvailable(), plugins: { plugins: plugins.plugins, parts: plugins.parts, diff --git a/src/node/handler/SocketIORouter.js b/src/node/handler/SocketIORouter.js index bbc05cc87..29e315592 100644 --- a/src/node/handler/SocketIORouter.js +++ b/src/node/handler/SocketIORouter.js @@ -21,7 +21,7 @@ */ const log4js = require('log4js'); -import {settings} from '../utils/Settings'; +import settings from '../utils/Settings'; const stats = require('../../node/stats') const logger = log4js.getLogger('socket.io'); diff --git a/src/node/hooks/express.ts b/src/node/hooks/express.ts index 7fff7a8a2..5ce533e92 100644 --- a/src/node/hooks/express.ts +++ b/src/node/hooks/express.ts @@ -14,7 +14,7 @@ import fs from 'fs'; const hooks = require('../../static/js/pluginfw/hooks'); import log4js from 'log4js'; const SessionStore = require('../db/SessionStore'); -import {getEpVersion, getGitCommit, settings} from '../utils/Settings'; +import settings from '../utils/Settings'; const stats = require('../stats') import util from 'util'; const webaccess = require('./express/webaccess'); @@ -68,9 +68,9 @@ const closeServer = async () => { exports.createServer = async () => { 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(); diff --git a/src/node/hooks/express/adminplugins.ts b/src/node/hooks/express/adminplugins.ts index 4de0236a9..b7b748987 100644 --- a/src/node/hooks/express/adminplugins.ts +++ b/src/node/hooks/express/adminplugins.ts @@ -7,7 +7,7 @@ import {QueryType} from "../../types/QueryType"; import {PluginType} from "../../types/Plugin"; const eejs = require('../../eejs'); -import {getEpVersion, getGitCommit} from '../../utils/Settings'; +import settings from '../../utils/Settings'; const installer = require('../../../static/js/pluginfw/installer'); const pluginDefs = require('../../../static/js/pluginfw/plugin_defs'); 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) => { - const gitCommit = getGitCommit(); - const epVersion = getEpVersion(); + const gitCommit = settings.getGitCommit(); + const epVersion = settings.getEpVersion(); res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins-info.html', { gitCommit, diff --git a/src/node/hooks/express/adminsettings.ts b/src/node/hooks/express/adminsettings.ts index ab9ebb27b..eaff4325f 100644 --- a/src/node/hooks/express/adminsettings.ts +++ b/src/node/hooks/express/adminsettings.ts @@ -4,7 +4,7 @@ const eejs = require('../../eejs'); const fsp = require('fs').promises; const hooks = require('../../../static/js/pluginfw/hooks'); const plugins = require('../../../static/js/pluginfw/plugins'); -import {reloadSettings, settings} from '../../utils/Settings'; +import settings from '../../utils/Settings'; exports.expressCreateServer = (hookName:string, {app}:any) => { app.get('/admin/settings', (req:any, res:any) => { @@ -44,7 +44,7 @@ exports.socketio = (hookName:string, {io}:any) => { socket.on('restartServer', async () => { console.log('Admin request to restart server through a socket on /admin/settings'); - reloadSettings(); + settings.reloadSettings(); await plugins.update(); await hooks.aCallAll('loadSettings', {settings}); await hooks.aCallAll('restartServer'); diff --git a/src/node/hooks/express/importexport.ts b/src/node/hooks/express/importexport.ts index 27659bbc2..d9ad6003f 100644 --- a/src/node/hooks/express/importexport.ts +++ b/src/node/hooks/express/importexport.ts @@ -3,7 +3,7 @@ import {ArgsExpressType} from "../../types/ArgsExpressType"; const hasPadAccess = require('../../padaccess'); -import {exportAvailable, settings} from '../../utils/Settings'; +import settings from '../../utils/Settings'; const exportHandler = require('../../handler/ExportHandler'); const importHandler = require('../../handler/ImportHandler'); 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 (exportAvailable() === 'no' && + if (settings.exportAvailable() === 'no' && ['odt', 'pdf', 'doc'].indexOf(req.params.type) !== -1) { console.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format.` + ' There is no converter configured'); diff --git a/src/node/hooks/express/openapi.ts b/src/node/hooks/express/openapi.ts index 51a9d73d7..ec4880c1d 100644 --- a/src/node/hooks/express/openapi.ts +++ b/src/node/hooks/express/openapi.ts @@ -24,7 +24,7 @@ const cloneDeep = require('lodash.clonedeep'); const createHTTPError = require('http-errors'); const apiHandler = require('../../handler/APIHandler'); -import {settings} from '../../utils/Settings'; +import settings from '../../utils/Settings'; const log4js = require('log4js'); const logger = log4js.getLogger('API'); diff --git a/src/node/hooks/express/socketio.ts b/src/node/hooks/express/socketio.ts index a202b02a1..8a03775cc 100644 --- a/src/node/hooks/express/socketio.ts +++ b/src/node/hooks/express/socketio.ts @@ -6,7 +6,7 @@ const events = require('events'); const express = require('../express'); const log4js = require('log4js'); const proxyaddr = require('proxy-addr'); -import {settings} from '../../utils/Settings'; +import settings from '../../utils/Settings'; import {Server} from 'socket.io' const socketIORouter = require('../../handler/SocketIORouter'); const hooks = require('../../../static/js/pluginfw/hooks'); diff --git a/src/node/hooks/express/specialpages.js b/src/node/hooks/express/specialpages.js index 6b658582d..116493724 100644 --- a/src/node/hooks/express/specialpages.js +++ b/src/node/hooks/express/specialpages.js @@ -6,7 +6,7 @@ const fs = require('fs'); const fsp = fs.promises; const toolbar = require('../../utils/toolbar'); const hooks = require('../../../static/js/pluginfw/hooks'); -import {getEpVersion, root, settings} from '../../utils/Settings'; +import settings from '../../utils/Settings'; const util = require('util'); const webaccess = require('./webaccess'); @@ -17,7 +17,7 @@ exports.expressPreSession = async (hookName, {app}) => { res.set('Content-Type', 'application/health+json'); res.json({ status: 'pass', - releaseId: getEpVersion(), + releaseId: settings.getEpVersion(), }); }); @@ -31,11 +31,11 @@ exports.expressPreSession = async (hookName, {app}) => { app.get('/robots.txt', (req, res) => { 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) => { // there is no custom robots.txt, send the default robots.txt which dissallows all if (err) { - filePath = path.join(root, 'src', 'static', 'robots.txt'); + filePath = path.join(settings.root, 'src', 'static', 'robots.txt'); res.sendFile(filePath); } }); @@ -54,9 +54,9 @@ exports.expressPreSession = async (hookName, {app}) => { const fns = [ - ...(settings.favicon ? [path.resolve(root, settings.favicon)] : []), - path.join(root, 'src', 'static', 'skins', settings.skinName, 'favicon.ico'), - path.join(root, 'src', 'static', 'favicon.ico'), + ...(settings.favicon ? [path.resolve(settings.root, settings.favicon)] : []), + path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'favicon.ico'), + path.join(settings.root, 'src', 'static', 'favicon.ico'), ]; for (const fn of fns) { try { diff --git a/src/node/hooks/express/static.ts b/src/node/hooks/express/static.ts index 3ebfb2d44..11286969b 100644 --- a/src/node/hooks/express/static.ts +++ b/src/node/hooks/express/static.ts @@ -5,7 +5,7 @@ const fs = require('fs').promises; const minify = require('../../utils/Minify'); const path = require('path'); 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 Yajsml = require('etherpad-yajsml'); @@ -18,7 +18,7 @@ const getTar = async () => { 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 = {}; for (const [key, relativeFiles] of Object.entries(JSON.parse(tarJson))) { // @ts-ignore diff --git a/src/node/hooks/express/tests.js b/src/node/hooks/express/tests.js index 3b8623207..526bd129b 100644 --- a/src/node/hooks/express/tests.js +++ b/src/node/hooks/express/tests.js @@ -4,7 +4,7 @@ const path = require('path'); const fsp = require('fs').promises; const plugins = require('../../../static/js/pluginfw/plugin_defs'); 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 '/' // instead of path.sep to separate pathname components. @@ -57,7 +57,7 @@ exports.expressPreSession = async (hookName, {app}) => { })().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) => { res.redirect(['./', ...req.url.split('?').slice(1)].join('?')); diff --git a/src/node/hooks/i18n.ts b/src/node/hooks/i18n.ts index 7dcc26de1..172bf7e7a 100644 --- a/src/node/hooks/i18n.ts +++ b/src/node/hooks/i18n.ts @@ -9,7 +9,7 @@ const path = require('path'); const _ = require('underscore'); const pluginDefs = require('../../static/js/pluginfw/plugin_defs.js'); 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 // {es: {"foo": "string"}, en:...} @@ -40,7 +40,7 @@ const getAllLocales = () => { }; // add core supported languages first - extractLangs(path.join(root, 'src/locales')); + extractLangs(path.join(settings.root, 'src/locales')); // add plugins languages (if any) for (const {package: {path: pluginPath}} of Object.values(pluginDefs.plugins)) { diff --git a/src/node/types/SettingsObj.ts b/src/node/types/SettingsObj.ts index 3a409ec63..3173e2921 100644 --- a/src/node/types/SettingsObj.ts +++ b/src/node/types/SettingsObj.ts @@ -1,4 +1,5 @@ export type SettingsObj = { + root: string; settingsFilename: string; credentialsFilename: string; title: string; @@ -111,4 +112,14 @@ export type SettingsObj = { enableAdminUITests: boolean; lowerCasePadIds: boolean; 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; } diff --git a/src/node/utils/Minify.js b/src/node/utils/Minify.js index 9b54c4eaf..dfd0adaf5 100644 --- a/src/node/utils/Minify.js +++ b/src/node/utils/Minify.js @@ -21,7 +21,7 @@ * limitations under the License. */ -import {root, settings} from './Settings'; +import {settings} from './Settings'; const fs = require('fs').promises; const path = require('path'); const plugins = require('../../static/js/pluginfw/plugin_defs'); @@ -33,7 +33,7 @@ const sanitizePathname = require('./sanitizePathname'); 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); diff --git a/src/node/utils/Settings.ts b/src/node/utils/Settings.ts index f84f6b9b8..538d86e26 100644 --- a/src/node/utils/Settings.ts +++ b/src/node/utils/Settings.ts @@ -78,9 +78,9 @@ const initLogging = (config: any) => { initLogging(defaultLogConfig(defaultLogLevel)); -export const root = absolutePaths.findEtherpadRoot() - +const root = absolutePaths.findEtherpadRoot(); export const settings: SettingsObj = { + root: absolutePaths.findEtherpadRoot(), settingsFilename: absolutePaths.makeAbsolute(argv.settings || 'settings.json'), credentialsFilename: absolutePaths.makeAbsolute(argv.credentials || 'credentials.json'), /** @@ -403,112 +403,98 @@ export const settings: SettingsObj = { * e.g. /p/EtHeRpAd to /p/etherpad */ lowerCasePadIds: false, - randomVersionString: randomString(4) -} - -/* Root path of the installation */ -logger.info('All relative paths will be interpreted relative to the identified ' + - `Etherpad base dir: ${root}`); - - - -// 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(); + randomVersionString: randomString(4), + // Return etherpad version from package.json + getEpVersion:()=>require('../../package.json').version, + // checks if abiword is avaiable + abiwordAvailable: () => { + if (settings.abiword != null) { + return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes'; } else { - rootPath += '/.git'; + return 'no'; } - const ref = fs.readFileSync(`${rootPath}/HEAD`, 'utf-8'); - if (ref.startsWith('ref: ')) { - const refPath = `${rootPath}/${ref.substring(5, ref.indexOf('\n'))}`; - version = fs.readFileSync(refPath, 'utf-8'); + }, + sofficeAvailable: () => { + if (settings.soffice != null) { + return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes'; } else { - version = ref; + return 'no'; } - version = version.substring(0, 7); - } catch (e: any) { - logger.warn(`Can't get git version for server header\n${e.message}`); - } - return version; -}; + }, + exportAvailable: () => { + const abiword = settings.abiwordAvailable(); + const soffice = settings.sofficeAvailable(); -// Return etherpad version from package.json -export const getEpVersion = () => require('../../package.json').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". - */ -const storeSettings = (settingsObj: MapArrayType) => { - for (const i of Object.keys(settingsObj || {})) { - if (nonSettings.includes(i)) { - logger.warn(`Ignoring setting: '${i}'`); - continue; + if (abiword === 'no' && soffice === 'no') { + return 'no'; + } else if ((abiword === 'withoutPDF' && soffice === 'no') || + (abiword === 'no' && soffice === 'withoutPDF')) { + return 'withoutPDF'; + } else { + return 'yes'; } - - // 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]); + }, + // Provide git version if available + 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 { - // @ts-ignore - settings[i] = settingsObj[i]; + rootPath += '/.git'; } - } 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`); + const ref = fs.readFileSync(`${rootPath}/HEAD`, 'utf-8'); + if (ref.startsWith('ref: ')) { + 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) => { + 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 * them to appropriate JS types. Otherwise return stringValue as-is. * @@ -519,337 +505,341 @@ const storeSettings = (settingsObj: MapArrayType) => { * 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 * in the literal string "null", instead. - */ -const coerceValue = (stringValue: string) => { - // cooked from https://stackoverflow.com/questions/175739/built-in-way-in-javascript-to-check-if-a-string-is-a-valid-number - // @ts-ignore - const isNumeric = !isNaN(stringValue) && !isNaN(parseFloat(stringValue) && isFinite(stringValue)); + * */ + coerceValue: (stringValue: string) => { + // cooked from https://stackoverflow.com/questions/175739/built-in-way-in-javascript-to-check-if-a-string-is-a-valid-number + // @ts-ignore + const isNumeric = !isNaN(stringValue) && !isNaN(parseFloat(stringValue) && isFinite(stringValue)); - if (isNumeric) { - // detected numeric string. Coerce to a number + if (isNumeric) { + // detected numeric string. Coerce to a number - 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; + return +stringValue; } - /* - * If we received from the configuration file a number, a boolean or - * 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; + switch (stringValue) { + case 'true': + return true; + case 'false': + return false; + case 'undefined': + return undefined; + case 'null': + return null; + 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 ` + - `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.'); + /** + * 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 + */ + 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 - * configuration item "key" would be stripped from the returned object. + * If we received from the configuration file a number, a boolean or + * 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; } - if ((envVarValue === undefined) && (defaultValue !== undefined)) { - logger.debug(`Environment variable "${envVarName}" not found for ` + - `configuration key "${key}". Falling back to default value.`); + try { + settingsStr = jsonminify(settingsStr).replace(',]', ']').replace(',}', '}'); - 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. - - /* - * 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".`); + // 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'; } - // informative variable, just for the log messages - let skinPath = path.join(skinBasePath, settings.skinName); + // 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; - // 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".'); + 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".`); - settings.skinName = 'colibris'; - skinPath = path.join(skinBasePath, settings.skinName); + settings.skinName = 'colibris'; + } + + // 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)) { - 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 (settings.abiword) { - // Check abiword actually exists - 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}`; + if (settings.abiword) { + // Check abiword actually exists + 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; } - 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) { - 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; + const sessionkeyFilename = absolutePaths.makeAbsolute(argv.sessionkey || './SESSIONKEY.txt'); + if (!settings.sessionKey) { + try { + settings.sessionKey = fs.readFileSync(sessionkeyFilename, 'utf8'); + logger.info(`Session key loaded from: ${sessionkeyFilename}`); + } catch (err) { /* ignored */ } - }); - } - - const sessionkeyFilename = absolutePaths.makeAbsolute(argv.sessionkey || './SESSIONKEY.txt'); - if (!settings.sessionKey) { - try { - settings.sessionKey = fs.readFileSync(sessionkeyFilename, 'utf8'); - logger.info(`Session key loaded from: ${sessionkeyFilename}`); - } catch (err) { /* ignored */ + const keyRotationEnabled = settings.cookie.keyRotationInterval && settings.cookie.sessionLifetime; + if (!settings.sessionKey && !keyRotationEnabled) { + logger.info( + `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.'); } - const keyRotationEnabled = settings.cookie.keyRotationInterval && settings.cookie.sessionLifetime; - if (!settings.sessionKey && !keyRotationEnabled) { - logger.info( - `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}`; + if (settings.sessionKey) { + logger.warn(`The sessionKey setting and ${sessionkeyFilename} file are deprecated; ` + + 'use automatic key rotation instead (see the cookie.keyRotationInterval setting).'); } - settings.dbSettings.filename = absolutePaths.makeAbsolute(settings.dbSettings.filename); - logger.warn(`${dirtyWarning} File location: ${settings.dbSettings.filename}`); + 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); + 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 === '') { - // 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 - */ - randomVersionString = randomString(4); - logger.info(`Random string used for versioning assets: ${randomVersionString}`); -}; - - -export const exportedForTestingOnly = { - parseSettings, -}; +/* Root path of the installation */ +logger.info('All relative paths will be interpreted relative to the identified ' + + `Etherpad base dir: ${root}`); // initially load settings -reloadSettings(); +settings.reloadSettings + + +export default settings diff --git a/src/node/utils/UpdateCheck.ts b/src/node/utils/UpdateCheck.ts index 2e7816b67..b5577dcbb 100644 --- a/src/node/utils/UpdateCheck.ts +++ b/src/node/utils/UpdateCheck.ts @@ -1,9 +1,9 @@ 'use strict'; const semver = require('semver'); -import {getEpVersion, settings} from './Settings'; +import {settings} from './Settings'; const axios = require('axios'); const headers = { - 'User-Agent': 'Etherpad/' + getEpVersion(), + 'User-Agent': 'Etherpad/' + settings.getEpVersion(), } type Infos = { @@ -45,7 +45,7 @@ exports.getLatestVersion = () => { exports.needsUpdate = async (cb: Function) => { await loadEtherpadInformations() .then((info:Infos) => { - if (semver.gt(info.latestVersion, getEpVersion())) { + if (semver.gt(info.latestVersion, settings.getEpVersion())) { if (cb) return cb(true); } }).catch((err: Error) => { diff --git a/src/node/utils/caching_middleware.ts b/src/node/utils/caching_middleware.ts index 199f03e5f..29507f072 100644 --- a/src/node/utils/caching_middleware.ts +++ b/src/node/utils/caching_middleware.ts @@ -21,7 +21,7 @@ const fs = require('fs'); const fsp = fs.promises; const path = require('path'); const zlib = require('zlib'); -import {root} from './Settings'; +import {settings} from './Settings'; const existsSync = require('./path_exists'); const util = require('util'); @@ -40,7 +40,7 @@ const util = require('util'); 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; type Headers = { diff --git a/src/node/utils/run_cmd.ts b/src/node/utils/run_cmd.ts index e976c0cb3..2c2ad204b 100644 --- a/src/node/utils/run_cmd.ts +++ b/src/node/utils/run_cmd.ts @@ -5,7 +5,7 @@ import {ChildProcess} from "node:child_process"; import {PromiseWithStd} from "../types/PromiseWithStd"; import {Readable} from "node:stream"; -import {root} from "./Settings"; +import {settings} from "./Settings"; import spawn from 'cross-spawn'; import log4js from 'log4js'; @@ -77,7 +77,7 @@ const logLines = (readable: undefined | Readable | null, logLineFn: (arg0: (stri module.exports = exports = (args: string[], opts:any = {}) => { logger.debug(`Executing command: ${args.join(' ')}`); - opts = {cwd: root, ...opts}; + opts = {cwd: settings.root, ...opts}; logger.debug(`cwd: ${opts.cwd}`); // Log stdout and stderr by default. @@ -112,8 +112,8 @@ module.exports = exports = (args: string[], opts:any = {}) => { opts.env = { ...env, // Copy env to avoid modifying process.env or the caller's supplied env. [pathVarName]: [ - path.join(root, 'src', 'node_modules', '.bin'), - path.join(root, 'node_modules', '.bin'), + path.join(settings.root, 'src', 'node_modules', '.bin'), + path.join(settings.root, 'node_modules', '.bin'), ...(PATH ? PATH.split(path.delimiter) : []), ].join(path.delimiter), }; diff --git a/src/package.json b/src/package.json index 298056d36..f439b8136 100644 --- a/src/package.json +++ b/src/package.json @@ -86,6 +86,7 @@ "@types/formidable": "^3.4.5", "@types/jsonminify": "^0.4.3", "@types/node": "^20.11.19", + "@types/resolve": "^1.20.6", "@types/underscore": "^1.11.15", "cypress": "^13.6.4", "eslint": "^8.56.0", diff --git a/src/static/js/pluginfw/installer.js b/src/static/js/pluginfw/installer.js index 9dd22d89e..d3f9488cb 100644 --- a/src/static/js/pluginfw/installer.js +++ b/src/static/js/pluginfw/installer.js @@ -4,7 +4,7 @@ const log4js = require('log4js'); const plugins = require('./plugins'); const hooks = require('./hooks'); 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 {PluginManager} = require('live-plugin-manager-pnpm'); const {promises: fs} = require('fs'); @@ -14,18 +14,18 @@ const logger = log4js.getLogger('plugins'); 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 () => { await plugins.update(); await persistInstalledPlugins(); - reloadSettings(); + settings.reloadSettings(); await hooks.aCallAll('loadSettings', {settings}); await hooks.aCallAll('restartServer'); }; const headers = { - 'User-Agent': `Etherpad/${getEpVersion()}`, + 'User-Agent': `Etherpad/${settings.getEpVersion()}`, }; let tasks = 0; diff --git a/src/static/js/pluginfw/plugins.js b/src/static/js/pluginfw/plugins.js index b57068620..e4c64147e 100644 --- a/src/static/js/pluginfw/plugins.js +++ b/src/static/js/pluginfw/plugins.js @@ -9,7 +9,7 @@ const tsort = require('./tsort'); const pluginUtils = require('./shared'); const defs = require('./plugin_defs'); const {manager} = require('./installer'); -import {getEpVersion, root, settings} from "../../../node/utils/Settings"; +import {settings} from "../../../node/utils/Settings"; const logger = log4js.getLogger('plugins'); @@ -121,9 +121,9 @@ exports.getPackages = async () => { newDependencies['ep_etherpad-lite'] = { name: 'ep_etherpad-lite', - version: getEpVersion(), - path: path.join(root, 'node_modules/ep_etherpad-lite'), - realPath: path.join(root, 'src'), + version: settings.getEpVersion(), + path: path.join(settings.root, 'node_modules/ep_etherpad-lite'), + realPath: path.join(settings.root, 'src'), }; return newDependencies; diff --git a/src/templates/index.html b/src/templates/index.html index 7df4fd1bc..ae8f8ba8d 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -1,5 +1,5 @@ <% -var settings = require("ep_etherpad-lite/node/utils/Settings").settings +var settings = require("ep_etherpad-lite/node/utils/Settings") %> diff --git a/src/templates/pad.html b/src/templates/pad.html index 5f7fa3b7a..d3b57a0fb 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -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 , pluginUtils = require('ep_etherpad-lite/static/js/pluginfw/shared') ; @@ -455,10 +455,10 @@