From cc21305f6a8bb6931872c1e2d1aee2bde10b294f Mon Sep 17 00:00:00 2001 From: John Livingston Date: Tue, 13 Apr 2021 17:13:41 +0200 Subject: [PATCH] WIP on Prosody integration. --- server/lib/diagnostic/prosody.ts | 23 ++++++++++++- server/lib/prosody/config.ts | 58 ++++++++++++++++++++++++++------ server/lib/prosody/ctl.ts | 56 ++++++++++++++++++++++++++++++ server/lib/settings.ts | 23 +++++++++++-- server/main.ts | 26 +++++++++----- 5 files changed, 164 insertions(+), 22 deletions(-) create mode 100644 server/lib/prosody/ctl.ts diff --git a/server/lib/diagnostic/prosody.ts b/server/lib/diagnostic/prosody.ts index 933f101c..2334582d 100644 --- a/server/lib/diagnostic/prosody.ts +++ b/server/lib/diagnostic/prosody.ts @@ -1,5 +1,6 @@ -import { getWorkingDir } from '../prosody/config' +import { getProsodyConfigContent, getProsodyConfigPath, getWorkingDir } from '../prosody/config' import { newResult, TestResult } from './utils' +import * as fs from 'fs' export async function diagProsody (test: string, options: RegisterServerOptions): Promise { const result = newResult(test) @@ -13,6 +14,26 @@ export async function diagProsody (test: string, options: RegisterServerOptions) return result } + // Testing the prosody config file. + try { + const filePath = await getProsodyConfigPath(options) + await fs.promises.access(filePath, fs.constants.R_OK) // throw an error if file does not exist. + result.messages.push(`The prosody configuration file (${filePath}) exists`) + const actualContent = await fs.promises.readFile(filePath, { + encoding: 'utf-8' + }) + const wantedContent = await getProsodyConfigContent(options) + if (actualContent === wantedContent) { + result.messages.push('Prosody configuration file content is correct.') + } else { + result.messages.push('Prosody configuration file content is not correct.') + return result + } + } catch (error) { + result.messages.push('Error when requiring the prosody config file: ' + (error as string)) + return result + } + result.ok = true return result } diff --git a/server/lib/prosody/config.ts b/server/lib/prosody/config.ts index 5f85ca68..b12b816a 100644 --- a/server/lib/prosody/config.ts +++ b/server/lib/prosody/config.ts @@ -1,4 +1,5 @@ import * as fs from 'fs' +import * as path from 'path' import { pluginName } from '../helpers' type LogMode = 'debug' | 'info' @@ -13,10 +14,15 @@ async function getWorkingDir ({ peertubeHelpers, storageManager }: RegisterServe const tmpBaseDir = '/tmp/' await fs.promises.access(tmpBaseDir, fs.constants.W_OK) // will throw an error if no access let value: string = await storageManager.getData('tempDirId') + + function getPath (value: string): string { + return path.resolve(tmpBaseDir, pluginName + '-' + value) + } + while (!value) { peertubeHelpers.logger.info('Generating an id for temp dir') value = Math.random().toString(36).slice(2, 12) - const name = tmpBaseDir + pluginName + '-' + value + const name = getPath(value) if (fs.existsSync(name)) { peertubeHelpers.logger.info('The folder ' + name + ' already exists, generating another name...') value = '' @@ -25,7 +31,7 @@ async function getWorkingDir ({ peertubeHelpers, storageManager }: RegisterServe await storageManager.storeData('tempDirId', value) } - const name = tmpBaseDir + pluginName + '-' + value + const name = getPath(value) if (!fs.existsSync(name)) { await fs.promises.mkdir(name) } @@ -33,9 +39,25 @@ async function getWorkingDir ({ peertubeHelpers, storageManager }: RegisterServe return name } -async function getProsodyConfig (_options: RegisterServerOptions): Promise { +interface ProsodyFilePaths { + pid: string + error: string + log: string + config: string +} +async function getProsodyFilePaths (options: RegisterServerOptions): Promise { + const dir = await getWorkingDir(options) + return { + pid: path.resolve(dir, 'prosody.pid'), + error: path.resolve(dir, 'prosody.err'), + log: path.resolve(dir, 'prosody.log'), + config: path.resolve(dir, 'prosody.cfg.lua') + } +} + +async function getProsodyConfigContent (options: RegisterServerOptions): Promise { const peertubeDomain = 'localhost' - const workingDirectory = '/tmp/' + const paths = await getProsodyFilePaths(options) const logMode: LogMode = 'debug' return ` @@ -62,7 +84,7 @@ allow_registration = false daemonize = true; -pidfile = "${workingDirectory}prosody.pid"; +pidfile = "${paths.pid}"; c2s_require_encryption = false @@ -70,8 +92,8 @@ archive_expires_after = "1w" -- Remove archived messages after 1 week log = { -- Log files (change 'info' to 'debug' for debug logs): - ${logMode} = "${workingDirectory}prosody.log"; - error = "${workingDirectory}prosody.err"; + ${logMode} = "${paths.log}"; + error = "${paths.error}"; -- Syslog: -- { levels = { "error" }; to = "syslog"; }; } @@ -110,7 +132,23 @@ Component "room.localhost" "muc" ` } -export { - getProsodyConfig, - getWorkingDir +async function getProsodyConfigPath (options: RegisterServerOptions): Promise { + const paths = await getProsodyFilePaths(options) + return paths.config +} + +async function writeProsodyConfig (options: RegisterServerOptions): Promise { + const logger = options.peertubeHelpers.logger + const content = await getProsodyConfigContent(options) + const fileName = await getProsodyConfigPath(options) + logger.info(`Writing prosody configuration file to ${fileName}`) + await fs.promises.writeFile(fileName, content) +} + +export { + getProsodyConfigContent, + getWorkingDir, + getProsodyFilePaths, + getProsodyConfigPath, + writeProsodyConfig } diff --git a/server/lib/prosody/ctl.ts b/server/lib/prosody/ctl.ts new file mode 100644 index 00000000..3560abe7 --- /dev/null +++ b/server/lib/prosody/ctl.ts @@ -0,0 +1,56 @@ +import { writeProsodyConfig } from './config' + +/** + * @param options + * @returns true if prosody is running with up to date parameters. A string array of messages otherwise. + */ +async function testProsodyCorrectlyRunning (options: RegisterServerOptions): Promise { + const { peertubeHelpers } = options + peertubeHelpers.logger.info('Checking if Prosody is correctly running') + + // TODO + peertubeHelpers.logger.error('testProsodyCorrectlyRunning not implemented yet.') + return ['Process not found'] +} + +async function ensureProsodyRunning (options: RegisterServerOptions): Promise { + const { peertubeHelpers, settingsManager } = options + const logger = peertubeHelpers.logger + + const setting = await settingsManager.getSetting('chat-use-prosody') + if (!setting) { + logger.info('Prosody is not activated, we wont launch it') + return + } + + const r = await testProsodyCorrectlyRunning(options) + if (r === true) { + logger.info('Prosody is already running correctly') + return + } + logger.info('Prosody is not running correctly: ' + r.join(', ')) + // Shutting down... + await ensureProsodyNotRunning(options) + + // writing the configuration file + await writeProsodyConfig(options) + + // TODO: launch prosody + logger.error('ensureProsodyRunning not implemented yet.') + + // TODO: listen for kill signal and kill prosody? +} + +async function ensureProsodyNotRunning (options: RegisterServerOptions): Promise { + const { peertubeHelpers } = options + peertubeHelpers.logger.info('Checking if Prosody is running, and shutting it down if so') + + // TODO: implement this. + peertubeHelpers.logger.error('ensureProsodyNotRunning not implemented yet.') +} + +export { + testProsodyCorrectlyRunning, + ensureProsodyRunning, + ensureProsodyNotRunning +} diff --git a/server/lib/settings.ts b/server/lib/settings.ts index d8c17b23..8e040444 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -1,8 +1,9 @@ import { getBaseRouter } from './helpers' +import { ensureProsodyRunning, ensureProsodyNotRunning } from './prosody/ctl' + +export function initSettings (options: RegisterServerOptions): void { + const { peertubeHelpers, registerSetting, settingsManager } = options -export function initSettings ({ - registerSetting -}: RegisterServerOptions): void { registerSetting({ name: 'chat-read-documentation', label: 'I have read the documentation', @@ -157,4 +158,20 @@ export function initSettings ({ 'Example: height:400px;', private: false }) + + // settings changes management + + // FIXME: Peertube <= 3.1.0 wrongly consider that the callback should not be async + // eslint-disable-next-line @typescript-eslint/no-misused-promises + settingsManager.onSettingsChange(async (settings: any) => { + if ('chat-use-prosody' in settings) { + if (settings['chat-use-prosody'] === true) { + peertubeHelpers.logger.info('Saving settings, ensuring prosody is running') + await ensureProsodyRunning(options) + } else if (settings['chat-use-prody'] === false) { + peertubeHelpers.logger.info('Saving settings, ensuring prosody is not running') + await ensureProsodyNotRunning(options) + } + } + }) } diff --git a/server/main.ts b/server/main.ts index 1cc4f4a6..9e58c303 100644 --- a/server/main.ts +++ b/server/main.ts @@ -1,26 +1,36 @@ import { initSettings } from './lib/settings' import { initRouters } from './lib/routers/index' -import type { Logger } from 'winston' +import { ensureProsodyRunning, ensureProsodyNotRunning } from './lib/prosody/ctl' import decache from 'decache' -let logger: Logger | undefined +// FIXME: in Peertube <= 3.1.0, unregister don't have any parameter. +// Using this global variable to fix this. +let OPTIONS: RegisterServerOptions | undefined async function register (options: RegisterServerOptions): Promise { - const { peertubeHelpers } = options - - logger = peertubeHelpers.logger + OPTIONS = options await initSettings(options) await initRouters(options) + + await ensureProsodyRunning(options) } async function unregister (): Promise { + if (OPTIONS) { + try { + await ensureProsodyNotRunning(OPTIONS) + } catch (error) { + OPTIONS.peertubeHelpers.logger.error('Error when trying to unload Prosody: ' + (error as string)) + } + } + const module = __filename - logger?.info(`Unloading module ${module}...`) + OPTIONS?.peertubeHelpers.logger.info(`Unloading module ${module}...`) // In peertube <= 3.1.0 sub modules are not removed from require.cache decache(module) - logger?.info(`Successfully unloaded the module ${module}`) - logger = undefined + OPTIONS?.peertubeHelpers.logger.info(`Successfully unloaded the module ${module}`) + OPTIONS = undefined } module.exports = {