import type { ProsodyLogLevel } from './config/content' ; import * as fs from 'fs' ; import * as path from 'path' ; import { getBaseRouterRoute } from '../helpers' ; import { ProsodyFilePaths } from './config/paths' ; import { ConfigLogExpiration , ProsodyConfigContent } from './config/content' ; import { getProsodyDomain } from './config/domain' ; import { getAPIKey } from '../apikey' ; import { parseExternalComponents } from './config/components' ; async function getWorkingDir ( options ) { const peertubeHelpers = options . peertubeHelpers ; const logger = peertubeHelpers . logger ; logger . debug ( 'Calling getWorkingDir' ) ; if ( ! peertubeHelpers . plugin ) { throw new Error ( 'Missing peertubeHelpers.plugin, have you the correct Peertube version?' ) ; } const dir = path . resolve ( peertubeHelpers . plugin . getDataDirectoryPath ( ) , 'prosody' ) ; logger . debug ( 'getWorkingDir will return the dir ' + dir ) ; return dir ; } /**
 * Creates the working dir if needed, and returns it.
 */ async function ensureWorkingDir ( options ) { const logger = options . peertubeHelpers . logger ; logger . debug ( 'Calling ensureworkingDir' ) ; const paths = await getProsodyFilePaths ( options ) ; const dir = paths . dir ; if ( ! fs . existsSync ( dir ) ) { logger . info ( `The working dir ${ dir } does not exists, trying to create it` ) ; await fs . promises . mkdir ( dir ) ; logger . debug ( `Working dir ${ dir } was created` ) ; } logger . debug ( `Testing write access on ${ dir }` ) ; await fs . promises . access ( dir , fs . constants . W_OK ) ; logger . debug ( `Write access ok on ${ dir }` ) ; if ( ! fs . existsSync ( paths . data ) ) { logger . info ( `The data dir ${ paths . data } does not exists, trying to create it` ) ; await fs . promises . mkdir ( paths . data ) ; logger . debug ( `Working dir ${ paths . data } was created` ) ; } return dir ; } async function getProsodyFilePaths ( options ) { const logger = options . peertubeHelpers . logger ; logger . debug ( 'Calling getProsodyFilePaths' ) ; const dir = await getWorkingDir ( options ) ; return { dir : dir , 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' ) , data : path . resolve ( dir , 'data' ) , modules : path . resolve ( __dirname , '../../prosody-modules' ) , avatars : path . resolve ( __dirname , '../../avatars' ) } ; } interface ProsodyConfig { content : string ; paths : ProsodyFilePaths ; host : string ; port : string ; baseApiUrl : string ; roomType : 'video' | 'channel' ; logByDefault : boolean ; logExpiration : ConfigLogExpiration ; valuesToHideInDiagnostic : Map < string , string > ; } async function getProsodyConfig ( options ) { const logger = options . peertubeHelpers . logger ; logger . debug ( 'Calling getProsodyConfig' ) ; const settings = await options . settingsManager . getSettings ( [ 'prosody-port' , 'prosody-muc-log-by-default' , 'prosody-muc-expiration' , 'prosody-c2s' , 'prosody-c2s-port' , 'prosody-room-type' , 'prosody-peertube-uri' , 'prosody-components' , 'prosody-components-port' , 'prosody-components-list' ] ) ; const valuesToHideInDiagnostic = new Map < string , string > ( ) ; const port = ( settings [ 'prosody-port' ] as string ) || '52800' ; if ( ! /^\d+$/ . test ( port ) ) { throw new Error ( 'Invalid port' ) ; } const logByDefault = ( settings [ 'prosody-muc-log-by-default' ] as boolean ) ?? true ; const logExpirationSetting = ( settings [ 'prosody-muc-expiration' ] as string ) ?? DEFAULTLOGEXPIRATION ; const enableC2s = ( settings [ 'prosody-c2s' ] as boolean ) || false ; const enableComponents = ( settings [ 'prosody-components' ] as boolean ) || false ; const prosodyDomain = await getProsodyDomain ( options ) ; const paths = await getProsodyFilePaths ( options ) ; const roomType = settings [ 'prosody-room-type' ] === 'channel' ? 'channel' : 'video' ; const apikey = await getAPIKey ( options ) ; valuesToHideInDiagnostic . set ( 'APIKey' , apikey ) ; let basePeertubeUrl = settings [ 'prosody-peertube-uri' ] as string ; if ( basePeertubeUrl && ! /^https?:\/\/[a-z0-9.-_]+(?::\d+)?$/ . test ( basePeertubeUrl ) ) { throw new Error ( 'Invalid prosody-peertube-uri' ) ; } if ( ! basePeertubeUrl ) { basePeertubeUrl = options . peertubeHelpers . config . getWebserverUrl ( ) ; } const baseApiUrl = basePeertubeUrl + getBaseRouterRoute ( options ) + 'api/' ; const authApiUrl = baseApiUrl + 'user' ; const roomApiUrl = baseApiUrl + 'room?apikey=' + apikey + '&jid={room.jid|jid_node}' ; const testApiUrl = baseApiUrl + 'test?apikey=' + apikey ; const config = new ProsodyConfigContent ( paths , prosodyDomain ) ; config . useHttpAuthentication ( authApiUrl ) ; config . usePeertubeBosh ( prosodyDomain , port ) ; config . useMucHttpDefault ( roomApiUrl ) ; if ( enableC2s ) { const c2sPort = ( settings [ 'prosody-c2s-port' ] as string ) || '52822' ; if ( ! /^\d+$/ . test ( c2sPort ) ) { throw new Error ( 'Invalid c2s port' ) ; } config . useC2S ( c2sPort ) ; } if ( enableComponents ) { const componentsPort = ( settings [ 'prosody-components-port' ] as string ) || '53470' ; if ( ! /^\d+$/ . test ( componentsPort ) ) { throw new Error ( 'Invalid external components port' ) ; } const components = parseExternalComponents ( ( settings [ 'prosody-components-list' ] as string ) || '' , prosodyDomain ) ; for ( const component of components ) { valuesToHideInDiagnostic . set ( 'Component ' + component . name + ' secret' , component . secret ) ; } config . useExternalComponents ( componentsPort , components ) ; } const logExpiration = readLogExpiration ( options , logExpirationSetting ) ; config . useMam ( logByDefault , logExpiration ) ; config . useDefaultPersistent ( ) ; config . useListRoomsApi ( apikey ) ; config . usePeertubeVCards ( basePeertubeUrl ) ; config . useAnonymousRandomVCards ( paths . avatars ) ; config . useTestModule ( apikey , testApiUrl ) ; let logLevel ; if ( logger . level && ( typeof logger . level === 'string' ) ) { if ( logger . level === 'error' || logger . level === 'info' || logger . level === 'debug' ) { logLevel = logger . level ; } else if ( logger . level === 'warn' || logger . level === 'warning' ) { logLevel = 'warn' ; } } if ( logLevel === undefined ) { logger . info ( 'No log level found in Peertube, will use default "info" for Prosody' ) ; logLevel = 'info' ; } config . setLog ( logLevel ) ; const content = config . write ( ) ; return { content , paths , port , baseApiUrl , host : prosodyDomain , roomType , logByDefault , logExpiration , valuesToHideInDiagnostic } ; } async function writeProsodyConfig ( options ) { const logger = options . peertubeHelpers . logger ; logger . debug ( 'Calling writeProsodyConfig' ) ; logger . debug ( 'Ensuring that the working dir exists' ) ; await ensureWorkingDir ( options ) ; logger . debug ( 'Computing the Prosody config content' ) ; const config = await getProsodyConfig ( options ) ; const content = config . content ; const fileName = config . paths . config ; logger . info ( `Writing prosody configuration file to ${ fileName }` ) ; await fs . promises . writeFile ( fileName , content ) ; logger . debug ( 'Prosody configuration file writen' ) ; return config ; } const DEFAULTLOGEXPIRATION = '1w' ; const DEFAULTLOGEXPIRATIONTYPE = 'period' ; function readLogExpiration ( options , logExpiration ) { const logger = options . peertubeHelpers . logger ; logExpiration = logExpiration ?. trim ( ) ; if ( logExpiration === 'never' ) { return { value : 'never' , type : 'never' } ; } if ( /^\d+$/ . test ( logExpiration ) ) { if ( logExpiration === '0' ) { logger . error ( 'Invalid prosody-muc-expiration value, cannot be 0.' ) ; return { value : DEFAULTLOGEXPIRATION , type : DEFAULTLOGEXPIRATIONTYPE , error : '0 is not an acceptable value.' } ; } return { value : logExpiration , type : 'seconds' , seconds : parseInt ( logExpiration ) } ; } const matches = logExpiration . match ( /^(\d+)([d|w|m|y])$/ ) ; if ( matches ) { const d = matches [ 1 ] ; if ( d === '0' ) { logger . error ( `Invalid prosody-muc-expiration value, cannot be ${ logExpiration }.` ) ; return { value : DEFAULTLOGEXPIRATION , type : DEFAULTLOGEXPIRATIONTYPE , error : '0 is not an acceptable value.' } ; } return { value : logExpiration , type : 'period' } ; } logger . error ( `Invalid prosody-muc-expiration value '${ logExpiration }'.` ) ; return { value : DEFAULTLOGEXPIRATION , type : DEFAULTLOGEXPIRATIONTYPE , error : `Invalid value '${ logExpiration }'.` } ; } function getProsodyConfigContentForDiagnostic ( config , content ? ) { let r = content ?? config . content ; for ( const [ key , value ] of config . valuesToHideInDiagnostic . entries ( ) ) { r = r . split ( value ) . join ( `***${ key }***` ) ; } return r ; } export { getProsodyConfig , getWorkingDir , ensureWorkingDir , getProsodyFilePaths , writeProsodyConfig , getProsodyConfigContentForDiagnostic } ;