Reverting work on DemoBot (it is now an external package).

This commit is contained in:
John Livingston
2021-12-11 17:12:04 +01:00
parent d01d13a69e
commit 2e7cec04d9
23 changed files with 89 additions and 1221 deletions

View File

@ -3,28 +3,15 @@ For internal API, we will generate an api Key that must be provided as
GET parameter for every API call.
*/
async function _getKey ({ storageManager }: RegisterServerOptions, key: string): Promise<string> {
let value: string = await storageManager.getData(key)
async function getAPIKey ({ storageManager }: RegisterServerOptions): Promise<string> {
let value: string = await storageManager.getData('APIKEY')
if (!value) {
value = Math.random().toString(36).slice(2, 12)
await storageManager.storeData(key, value)
await storageManager.storeData('APIKEY', value)
}
return value
}
async function getAPIKey (options: RegisterServerOptions): Promise<string> {
return _getKey(options, 'APIKEY')
}
async function getExternalComponentKey (options: RegisterServerOptions, componentName: string): Promise<string> {
if (!/^[A-Z]+$/.test(componentName)) {
throw new Error('Invalid component name: ' + componentName)
}
const key = 'EXTERNALCOMPONENTKEY_' + componentName
return _getKey(options, key)
}
export {
getAPIKey,
getExternalComponentKey
getAPIKey
}

View File

@ -1,4 +1,4 @@
import { getProsodyConfig, getWorkingDir } from '../prosody/config'
import { getProsodyConfig, getProsodyConfigContentForDiagnostic, getWorkingDir } from '../prosody/config'
import { getProsodyAbout, testProsodyCorrectlyRunning } from '../prosody/ctl'
import { newResult, TestResult } from './utils'
import { getAPIKey } from '../apikey'
@ -24,6 +24,7 @@ export async function diagProsody (test: string, options: RegisterServerOptions)
let prosodyHost: string
try {
const wantedConfig = await getProsodyConfig(options)
const filePath = wantedConfig.paths.config
result.messages.push(`Prosody will run on port '${wantedConfig.port}'`)
prosodyPort = wantedConfig.port
@ -49,44 +50,34 @@ export async function diagProsody (test: string, options: RegisterServerOptions)
}
result.messages.push(`Room content will be saved for '${wantedConfig.logExpiration.value}'`)
if (wantedConfig.bots.demobot) {
result.messages.push(`The Demo bot is active for videos: ${wantedConfig.bots.demobot.join(', ')}`)
}
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 configFiles = wantedConfig.getConfigFiles()
for (const configFile of configFiles) {
const filePath = configFile.path
const configFileKey = configFile.key
await fs.promises.access(filePath, fs.constants.R_OK) // throw an error if file does not exist.
result.messages.push(`The prosody '${configFileKey}' configuration file (${filePath}) exists`)
const actualContent = await fs.promises.readFile(filePath, {
encoding: 'utf-8'
})
result.debug.push({
title: 'Current prosody configuration',
// we have to hide secret keys and other values.
// But here, we haven't them for actualContent.
// So we will use values in wantedConfig, hopping it is enough.
message: getProsodyConfigContentForDiagnostic(wantedConfig, actualContent)
})
const wantedContent = wantedConfig.content
if (actualContent === wantedContent) {
result.messages.push('Prosody configuration file content is correct.')
} else {
result.messages.push('Prosody configuration file content is not correct.')
result.debug.push({
title: `Current prosody '${configFileKey}' configuration`,
// we have to hide secret keys and other values.
// But here, we haven't them for actualContent.
// So we will use values in wantedConfig, hopping it is enough.
message: wantedConfig.contentForDiagnostic(actualContent)
title: 'Prosody configuration should be',
// we have to hide secret keys and other values:
message: getProsodyConfigContentForDiagnostic(wantedConfig)
})
const wantedContent = configFile.content
if (actualContent === wantedContent) {
result.messages.push(`Prosody configuration file '${configFileKey}' content is correct.`)
} else {
result.messages.push(`Prosody configuration file '${configFileKey}'' content is not correct.`)
result.debug.push({
title: `Prosody configuration '${configFileKey}' should be`,
// we have to hide secret keys and other values:
message: wantedConfig.contentForDiagnostic(wantedContent)
})
return result
}
return result
}
} catch (error) {
result.messages.push('Error when testing the prosody config: ' + (error as string))
result.messages.push('Error when requiring the prosody config file: ' + (error as string))
return result
}

View File

@ -4,9 +4,8 @@ import { getBaseRouterRoute } from '../helpers'
import { ProsodyFilePaths } from './config/paths'
import { ConfigLogExpiration, ProsodyConfigContent } from './config/content'
import { getProsodyDomain } from './config/domain'
import { getAPIKey, getExternalComponentKey } from '../apikey'
import { getAPIKey } from '../apikey'
import type { ProsodyLogLevel } from './config/content'
import { parseConfigDemoBotUUIDs } from './config/bots'
async function getWorkingDir (options: RegisterServerOptions): Promise<string> {
const peertubeHelpers = options.peertubeHelpers
@ -24,9 +23,9 @@ async function getWorkingDir (options: RegisterServerOptions): Promise<string> {
/**
* Creates the working dir if needed, and returns it.
*/
async function ensureWorkingDirs (options: RegisterServerOptions): Promise<string> {
async function ensureWorkingDir (options: RegisterServerOptions): Promise<string> {
const logger = options.peertubeHelpers.logger
logger.debug('Calling ensureworkingDirs')
logger.debug('Calling ensureworkingDir')
const paths = await getProsodyFilePaths(options)
const dir = paths.dir
@ -39,12 +38,10 @@ async function ensureWorkingDirs (options: RegisterServerOptions): Promise<strin
await fs.promises.access(dir, fs.constants.W_OK) // will throw an error if no access
logger.debug(`Write access ok on ${dir}`)
for (const path of [paths.data, paths.bots.dir]) {
if (!fs.existsSync(path)) {
logger.info(`The data dir ${path} does not exists, trying to create it`)
await fs.promises.mkdir(path)
logger.debug(`Working dir ${path} was created`)
}
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
@ -62,61 +59,25 @@ async function getProsodyFilePaths (options: RegisterServerOptions): Promise<Pro
log: path.resolve(dir, 'prosody.log'),
config: path.resolve(dir, 'prosody.cfg.lua'),
data: path.resolve(dir, 'data'),
bots: {
dir: path.resolve(dir, 'bots'),
demobot: path.resolve(dir, 'bots', 'demobot.js')
},
modules: path.resolve(__dirname, '../../prosody-modules')
}
}
interface ProsodyConfigBots {
demobot?: string[] // if the demo bot is activated, here are the video UUIDS where it will be.
}
type ProsodyConfigFilesKey = 'prosody' | 'demobot'
type ProsodyConfigFiles = Array<{
key: ProsodyConfigFilesKey
path: string
interface ProsodyConfig {
content: string
}>
class ProsodyConfig {
constructor (
private readonly configFiles: ProsodyConfigFiles,
public paths: ProsodyFilePaths,
public host: string,
public port: string,
public baseApiUrl: string,
public roomType: 'video' | 'channel',
public logByDefault: boolean,
public logExpiration: ConfigLogExpiration,
public bots: ProsodyConfigBots,
public valuesToHideInDiagnostic: {[key: string]: string}
) {}
public getConfigFiles (): ProsodyConfigFiles {
return this.configFiles
}
public contentForDiagnostic (content: string): string {
let r: string = content
for (const key in this.valuesToHideInDiagnostic) {
// replaceAll not available, using trick:
r = r.split(this.valuesToHideInDiagnostic[key]).join(`***${key}***`)
}
return r
}
paths: ProsodyFilePaths
host: string
port: string
baseApiUrl: string
roomType: 'video' | 'channel'
logByDefault: boolean
logExpiration: ConfigLogExpiration
valuesToHideInDiagnostic: {[key: string]: string}
}
async function getProsodyConfig (options: RegisterServerOptions): Promise<ProsodyConfig> {
const logger = options.peertubeHelpers.logger
logger.debug('Calling getProsodyConfig')
let useExternalComponents = false
const bots: ProsodyConfigBots = {}
const valuesToHideInDiagnostic: {[key: string]: string} = {}
const settings = await options.settingsManager.getSettings([
'prosody-port',
'prosody-muc-log-by-default',
@ -124,25 +85,20 @@ async function getProsodyConfig (options: RegisterServerOptions): Promise<Prosod
'prosody-c2s',
'prosody-room-type',
'prosody-peertube-uri',
'prosody-c2s-port',
'prosody-component-port',
'chat-videos-list'
'prosody-c2s-port'
])
const valuesToHideInDiagnostic: {[key: string]: string} = {}
const port = (settings['prosody-port'] as string) || '52800'
if (!/^\d+$/.test(port)) {
throw new Error('Invalid port')
}
const externalComponentsPort = (settings['prosody-component-port'] as string) || '53470'
if (!/^\d+$/.test(externalComponentsPort)) {
throw new Error('Invalid external components 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 prosodyDomain = await getProsodyDomain(options)
const paths = await getProsodyFilePaths(options)
const roomType = (settings['prosody-room-type']) === 'channel' ? 'channel' : 'video'
const roomType = settings['prosody-room-type'] === 'channel' ? 'channel' : 'video'
const apikey = await getAPIKey(options)
valuesToHideInDiagnostic.APIKey = apikey
@ -196,57 +152,19 @@ async function getProsodyConfig (options: RegisterServerOptions): Promise<Prosod
logLevel = 'info'
}
config.setLog(logLevel)
const demoBotUUIDs = parseConfigDemoBotUUIDs((settings['chat-videos-list'] as string) || '')
let demoBotContentObj: string = 'null'
if (demoBotUUIDs?.length > 0) {
useExternalComponents = true
const componentSecret = await getExternalComponentKey(options, 'DEMOBOT')
valuesToHideInDiagnostic.ComponentSecret = componentSecret
config.useDemoBot(componentSecret)
bots.demobot = demoBotUUIDs
demoBotContentObj = JSON.stringify({
rooms: demoBotUUIDs,
service: 'xmpp://127.0.0.1:' + externalComponentsPort,
domain: 'demobot.' + prosodyDomain,
mucDomain: 'room.' + prosodyDomain,
password: componentSecret
})
}
let demoBotContent = '"use strict";\n'
demoBotContent += 'Object.defineProperty(exports, "__esModule", { value: true });\n'
demoBotContent += `function getConf () { return ${demoBotContentObj}; }` + '\n'
demoBotContent += 'exports.getConf = getConf;\n'
if (useExternalComponents) {
config.useExternalComponents(externalComponentsPort)
}
const content = config.write()
return new ProsodyConfig(
[
{
key: 'prosody',
path: paths.config,
content: content
},
{
key: 'demobot',
path: paths.bots.demobot,
content: demoBotContent
}
],
return {
content,
paths,
prosodyDomain,
port,
baseApiUrl,
host: prosodyDomain,
roomType,
logByDefault,
logExpiration,
bots,
valuesToHideInDiagnostic
)
}
}
async function writeProsodyConfig (options: RegisterServerOptions): Promise<ProsodyConfig> {
@ -254,19 +172,15 @@ async function writeProsodyConfig (options: RegisterServerOptions): Promise<Pros
logger.debug('Calling writeProsodyConfig')
logger.debug('Ensuring that the working dir exists')
await ensureWorkingDirs(options)
await ensureWorkingDir(options)
logger.debug('Computing the Prosody config content')
const config = await getProsodyConfig(options)
const content = config.content
const fileName = config.paths.config
const configFiles = config.getConfigFiles()
for (const configFile of configFiles) {
const content = configFile.content
const fileName = configFile.path
logger.info(`Writing prosody configuration file '${configFile.key}' to ${fileName}.`)
await fs.promises.writeFile(fileName, content)
logger.debug(`Prosody configuration file '${configFile.key}' writen.`)
}
logger.info(`Writing prosody configuration file to ${fileName}`)
await fs.promises.writeFile(fileName, content)
logger.debug('Prosody configuration file writen')
return config
}
@ -322,10 +236,20 @@ function readLogExpiration (options: RegisterServerOptions, logExpiration: strin
}
}
function getProsodyConfigContentForDiagnostic (config: ProsodyConfig, content?: string): string {
let r: string = content ?? config.content
for (const key in config.valuesToHideInDiagnostic) {
// replaceAll not available, using trick:
r = r.split(config.valuesToHideInDiagnostic[key]).join(`***${key}***`)
}
return r
}
export {
getProsodyConfig,
getWorkingDir,
ensureWorkingDirs,
ensureWorkingDir,
getProsodyFilePaths,
writeProsodyConfig
writeProsodyConfig,
getProsodyConfigContentForDiagnostic
}

View File

@ -1,19 +0,0 @@
function parseConfigDemoBotUUIDs (s: string): string[] {
if (!s) {
return []
}
let a = s.split('\n')
// find lines that are like:
// 6432f147-83c7-4fa3-b3b5-e49c2590e825 #!demobot
a = a.filter(line => /#!demobot\b/.test(line))
a = a.map(line => {
return line.replace(/#.*$/, '')
.replace(/^\s+/, '')
.replace(/\s+$/, '')
})
return a.filter(line => line !== '')
}
export {
parseConfigDemoBotUUIDs
}

View File

@ -102,19 +102,16 @@ class ProsodyConfigVirtualHost extends ProsodyConfigBlock {
class ProsodyConfigComponent extends ProsodyConfigBlock {
name: string
type?: string
type: string
constructor (name: string, type?: string) {
constructor (type: string, name: string) {
super(' ')
this.type = type
this.name = name
}
write (): string {
if (this.type !== undefined) {
return `Component "${this.name}" "${this.type}"\n` + super.write()
}
return `Component "${this.name}"\n` + super.write()
return `Component "${this.name}" "${this.type}"\n` + super.write()
}
}
@ -126,7 +123,6 @@ class ProsodyConfigContent {
authenticated?: ProsodyConfigVirtualHost
anon: ProsodyConfigVirtualHost
muc: ProsodyConfigComponent
externalComponents: ProsodyConfigComponent[] = []
log: string
prosodyDomain: string
@ -136,7 +132,7 @@ class ProsodyConfigContent {
this.log = ''
this.prosodyDomain = prosodyDomain
this.anon = new ProsodyConfigVirtualHost('anon.' + prosodyDomain)
this.muc = new ProsodyConfigComponent('room.' + prosodyDomain, 'muc')
this.muc = new ProsodyConfigComponent('muc', 'room.' + prosodyDomain)
this.global.set('daemonize', false)
this.global.set('allow_registration', false)
@ -285,21 +281,6 @@ class ProsodyConfigContent {
this.muc.set('peertubelivechat_test_peertube_api_url', apiurl)
}
useExternalComponents (componentsPort: string): void {
this.global.set('component_ports', [componentsPort])
this.global.set('component_interfaces', ['127.0.0.1', '::1'])
}
useDemoBot (componentSecret: string): void {
const demoBotComponent = new ProsodyConfigComponent('demobot.' + this.prosodyDomain)
demoBotComponent.set('component_secret', componentSecret)
// If we want the bot to be moderator, should do the trick:
// this.global.add('admins', 'demobot.' + this.prosodyDomain)
this.externalComponents.push(demoBotComponent)
}
setLog (level: ProsodyLogLevel, syslog?: ProsodyLogLevel[]): void {
let log = ''
log += 'log = {\n'
@ -328,11 +309,6 @@ class ProsodyConfigContent {
content += '\n\n'
content += this.muc.write()
content += '\n\n'
this.externalComponents.forEach((externalComponent) => {
content += '\n\n'
content += externalComponent.write()
content += '\n\n'
})
return content
}
}

View File

@ -5,10 +5,6 @@ interface ProsodyFilePaths {
log: string
config: string
data: string
bots: {
dir: string
demobot: string
}
modules: string
}

View File

@ -113,23 +113,20 @@ async function testProsodyCorrectlyRunning (options: RegisterServerOptions): Pro
try {
const wantedConfig = await getProsodyConfig(options)
const configFiles = wantedConfig.getConfigFiles()
for (const configFile of configFiles) {
const filePath = configFile.path
const filePath = wantedConfig.paths.config
await fs.promises.access(filePath, fs.constants.R_OK) // throw an error if file does not exist.
result.messages.push(`The prosody configuration file (${configFile.key}: ${filePath}) exists`)
const actualContent = await fs.promises.readFile(filePath, {
encoding: 'utf-8'
})
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 = configFile.content
if (actualContent === wantedContent) {
result.messages.push(`Prosody configuration file '${configFile.key}' content is correct.`)
} else {
result.messages.push(`Prosody configuration file '${configFile.key}' content is not correct.`)
return result
}
const wantedContent = wantedConfig.content
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))

View File

@ -359,22 +359,6 @@ archiving for a specific room, by editing its properties.
</ul>`
})
registerSetting({
name: 'prosody-component-port',
label: 'The port to be use for external components',
type: 'input',
default: '53470',
private: true,
descriptionHTML:
`The port that will be used for extra components used by the builtin Prosody server.<br>
This is only used when one of these special features is used:<br>
<ul>
<li>Demo bot: this is a hidden feature, for demonstration purposes. See the documentation for more information.</li>
</ul><br>
Change it if this port is already in use on your server.
`
})
registerSetting({
name: 'prosody-c2s',
label: 'Enable client to server connections',