Embedding Prosody using AppImage:

Thanks to this commit, there is no more need to manually install Prosody
on the server.
The plugin now build and embed an AppImage of Prosody.

In this commit:
* building and using a Prosody AppImage.
* Adding a launcher in the AppImage: the first command argument tells if
  we want to run prosody or prosodyctl
* prosodyCtl functions now uses the AppImage.
* Prosody AppImage: extract once at the startup, then run the squashfs
This commit is contained in:
John Livingston
2022-11-14 16:54:08 +01:00
parent 91ea442ce6
commit 459d92cef9
11 changed files with 231 additions and 15 deletions

View File

@ -33,6 +33,8 @@ export async function diagProsody (test: string, options: RegisterServerOptions)
result.messages.push(`Prosody will use ${wantedConfig.baseApiUrl} as base uri from api calls`)
result.messages.push(`Prosody path will be '${wantedConfig.paths.exec}'`)
result.messages.push(`Prosody modules path will be '${wantedConfig.paths.modules}'`)
result.messages.push(`Prosody rooms will be grouped by '${wantedConfig.roomType}'.`)

View File

@ -54,6 +54,24 @@ async function getProsodyFilePaths (options: RegisterServerOptions): Promise<Pro
logger.debug('Calling getProsodyFilePaths')
const dir = await getWorkingDir(options)
const settings = await options.settingsManager.getSettings(['use-system-prosody'])
let exec
let execArgs: string[]
let execCtl
let execCtlArgs: string[]
let appImageToExtract
if (settings['use-system-prosody']) {
exec = 'prosody'
execArgs = []
execCtl = 'prosodyctl'
execCtlArgs = []
} else {
appImageToExtract = path.resolve(__dirname, '../../prosody/livechat-prosody-x86_64.AppImage')
exec = path.resolve(dir, 'squashfs-root/AppRun') // the AppImage will be extracted in the working dir
execArgs = ['prosody']
execCtl = exec
execCtlArgs = ['prosodyctl']
}
return {
dir: dir,
pid: path.resolve(dir, 'prosody.pid'),
@ -62,7 +80,12 @@ async function getProsodyFilePaths (options: RegisterServerOptions): Promise<Pro
config: path.resolve(dir, 'prosody.cfg.lua'),
data: path.resolve(dir, 'data'),
modules: path.resolve(__dirname, '../../prosody-modules'),
avatars: path.resolve(__dirname, '../../avatars')
avatars: path.resolve(__dirname, '../../avatars'),
exec,
execArgs,
execCtl,
execCtlArgs,
appImageToExtract
}
}

View File

@ -7,6 +7,11 @@ interface ProsodyFilePaths {
data: string
modules: string
avatars: string
exec: string
execArgs: string[]
execCtl: string
execCtlArgs: string[]
appImageToExtract?: string
}
export {

View File

@ -5,6 +5,38 @@ import { disableProxyRoute, enableProxyRoute } from '../routers/webchat'
import * as fs from 'fs'
import * as child_process from 'child_process'
/**
* This function prepares the binaries for the embeded Prosody (if needed).
* @param options
*/
async function prepareProsody (options: RegisterServerOptions): Promise<void> {
const logger = options.peertubeHelpers.logger
const filePaths = await getProsodyFilePaths(options)
const appImageToExtract = filePaths.appImageToExtract
if (!appImageToExtract) {
return
}
return new Promise((resolve, reject) => {
const spawned = child_process.spawn(appImageToExtract, ['--appimage-extract'], {
cwd: filePaths.dir,
env: {
...process.env
}
})
spawned.stdout.on('data', (data) => {
logger.debug(`AppImage extract printed: ${data as string}`)
})
spawned.stderr.on('data', (data) => {
logger.error(`AppImage extract has errors: ${data as string}`)
})
spawned.on('error', reject)
spawned.on('close', (_code) => { // 'close' and not 'exit', to be sure it is finished.
resolve()
})
})
}
interface ProsodyCtlResult {
code: number | null
stdout: string
@ -23,11 +55,13 @@ async function prosodyCtl (options: RegisterServerOptions, command: string): Pro
let d: string = ''
let e: string = ''
let m: string = ''
const spawned = child_process.spawn('prosodyctl', [
const cmdArgs = [
...filePaths.execCtlArgs,
'--config',
filePaths.config,
command
], {
]
const spawned = child_process.spawn(filePaths.execCtl, cmdArgs, {
cwd: filePaths.dir,
env: {
...process.env,
@ -44,7 +78,10 @@ async function prosodyCtl (options: RegisterServerOptions, command: string): Pro
m += data as string
})
spawned.on('error', reject)
spawned.on('exit', (code) => {
// on 'close' and not 'exit', to be sure everything is done
// (else it can cause trouble by cleaning AppImage extract too soon)
spawned.on('close', (code) => {
resolve({
code: code,
stdout: d,
@ -163,8 +200,9 @@ async function ensureProsodyRunning (options: RegisterServerOptions): Promise<vo
const filePaths = config.paths
// launch prosody
logger.info('Going to launch prosody')
const prosody = child_process.exec('prosody', {
const execCmd = filePaths.exec + (filePaths.execArgs.length ? ' ' + filePaths.execArgs.join(' ') : '')
logger.info('Going to launch prosody (' + execCmd + ')')
const prosody = child_process.exec(execCmd, {
cwd: filePaths.dir,
env: {
...process.env,
@ -248,6 +286,7 @@ export {
getProsodyAbout,
testProsodyRunning,
testProsodyCorrectlyRunning,
prepareProsody,
ensureProsodyRunning,
ensureProsodyNotRunning
}

View File

@ -27,11 +27,11 @@ function initSettings (options: RegisterServerOptions): void {
(if this button is not opening a new window, please try to refresh the page).`
})
// ********** Chat Mode
// ********** Chat Server
registerSetting({
type: 'html',
private: true,
descriptionHTML: '<h3>Chat mode</h3>'
descriptionHTML: '<h3>Chat Server</h3>'
})
registerSetting({
name: 'chat-help-builtin-prosody',
@ -39,14 +39,22 @@ function initSettings (options: RegisterServerOptions): void {
label: 'Prosody server',
descriptionHTML: `This plugin uses the Prosody XMPP server to handle chat rooms.<br>
The Peertube server will control this Prosody server.<br>
Important Note: you have to install Prosody on your server.
Please read the <a
href="https://github.com/JohnXLivingston/peertube-plugin-livechat/blob/main/documentation/prosody.md"
target="_blank"
>documentation</a>.`,
By default, this plugin comes with a Prosody AppImage.`,
private: true
})
registerSetting({
name: 'use-system-prosody',
type: 'input-checkbox',
label: 'Use system Prosody',
descriptionHTML: `Warning: don't check this settings if you are not sure of what you are doing.<br>
By checking this settings, your Peertube will use the Prosody server that comes with your system,
and not the embeded AppImage.<br>
Only use this if you encounter problems with the embedded Prosody.`,
private: true,
default: false
})
// TODO: fix the settings order. Since there is no more multiple chat-mode, the order is not good.
registerSetting({
name: 'prosody-list-rooms',
label: 'List existing rooms',

View File

@ -3,7 +3,7 @@ import { migrateSettings } from './lib/migration/settings'
import { initSettings } from './lib/settings'
import { initCustomFields } from './lib/custom-fields'
import { initRouters } from './lib/routers/index'
import { ensureProsodyRunning, ensureProsodyNotRunning } from './lib/prosody/ctl'
import { prepareProsody, ensureProsodyRunning, ensureProsodyNotRunning } from './lib/prosody/ctl'
import decache from 'decache'
// FIXME: Peertube unregister don't have any parameter.
@ -25,6 +25,7 @@ async function register (options: RegisterServerOptions): Promise<any> {
await initRouters(options)
try {
await prepareProsody(options)
await ensureProsodyRunning(options)
} catch (error) {
options.peertubeHelpers.logger.error('Error when launching Prosody: ' + (error as string))