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
No known key found for this signature in database
GPG Key ID: B17B5640CE66CDBC
11 changed files with 231 additions and 15 deletions

View File

@ -21,10 +21,21 @@ the server will try to use the new mode after updating the plugin.
If you don't want the chat server to be active, just uninstall the plugin
(settings won't be lost, you just have to download it again).
### Important Notes
This version of the plugin comes with a builtin Prosody AppImage.
If you were using this plugin before this version, and if you had installed Prosody manually,
you can safely uninstall Prosody.
If you were using the custom Peertube docker image that is embedding Prosody, you can switch back to the official
Peertube image.
### New Features
* Removed deprecated modes, only keeping «Prosody server controlled by Peertube».
* BOSH proxy optimization + enabling websocket.
* Builtin Prosody AppImage. No more manual installation required.
### Changes

54
build-prosody.sh Normal file
View File

@ -0,0 +1,54 @@
#!/bin/bash
set -euo pipefail
set -x
rootdir="$(pwd)"
prosody_build_dir="$rootdir/build/prosody"
prosody_destination_dir="$rootdir/dist/server/prosody"
if [[ ! -d "$prosody_build_dir" ]]; then
mkdir -p "$prosody_build_dir"
fi
cd "$prosody_build_dir"
if [ -f "$prosody_build_dir/livechat-prosody-x86_64.AppImage" ]; then
echo "Prosody image already built."
else
echo "Prosody image must be build..."
# Prerequisite: you must have python3-venv installed on your system
if [[ ! -d "venv" ]]; then
echo "Creating the python venv..."
python3 -m venv venv
fi
echo "Activating the python venv..."
source venv/bin/activate
echo "Installing appimage-builder..."
pip3 install appimage-builder
echo "Copying appimage source files..."
cp "$rootdir/prosody/appimage_x86_64.yml" "$prosody_build_dir/appimage_x86_64.yml"
cp "$rootdir/prosody/launcher.lua" "$prosody_build_dir/launcher.lua"
echo "Building Prosody..."
appimage-builder --recipe "$prosody_build_dir/appimage_x86_64.yml"
fi
echo "Copying Prosody dist files..."
mkdir -p "$prosody_destination_dir" && cp $prosody_build_dir/livechat-prosody-x86_64.AppImage "$prosody_destination_dir/"
# For some obscur reason, if we keep AppDir and appimage-build folders,
# and if we try to install the plugin using the Peertube CLI,
# the installation fails because there are some subfolders that are right protected.
# To avoid that, we clean them:
echo "Cleaning build folders..."
rm -rf "$prosody_build_dir/AppDir"
rm -rf "$prosody_build_dir/appimage-build"
echo "Prosody AppImage OK."
exit 0

View File

@ -88,6 +88,7 @@
"clean:light": "rm -rf dist/*",
"prepare": "npm run clean && npm run build",
"build:converse": "bash conversejs/build-conversejs.sh",
"build:prosody": "bash build-prosody.sh",
"build:images": "mkdir -p dist/client/images && npx svgo -f public/images/ -o dist/client/images/",
"build:avatars": "mkdir -p dist/server/avatars && ./build-avatars.js",
"build:webpack": "webpack --mode=production",
@ -95,7 +96,7 @@
"build:serverconverse": "mkdir -p dist/server/conversejs && cp conversejs/index.html dist/server/conversejs/",
"build:prosodymodules": "mkdir -p dist/server/prosody-modules && cp -r prosody-modules/* dist/server/prosody-modules/",
"build:styles": "sass assets:dist/assets",
"build": "npm-run-all -s clean:light -p build:converse build:images build:avatars build:webpack build:server build:serverconverse build:prosodymodules build:styles",
"build": "npm-run-all -s clean:light -p build:converse build:prosody build:images build:avatars build:webpack build:server build:serverconverse build:prosodymodules build:styles",
"lint": "npm-run-all -s lint:script lint:styles",
"lint:script": "npx eslint --ext .js --ext .ts .",
"lint:styles": "stylelint 'conversejs/**/*.scss' 'assets/**/*.css'",

View File

@ -0,0 +1,59 @@
# This file is meant to be used with appimage-builder: https://appimage-builder.readthedocs.io
# See it is use in the build-prosody.sh script.
version: 1
script:
# Remove any previous build
- rm -rf AppDir | true
# Make usr dirs
- mkdir -p AppDir/usr/bin
# Copy the launcher code into the AppDir
- cp ./launcher.lua AppDir/usr/bin/
AppDir:
path: ./AppDir
app_info:
id: org.peertube-plugin-livechat.prosody
name: prosody
icon: utilities-terminal
version: 1.0.0
exec: usr/bin/lua5.2
exec_args: "$APPDIR/usr/bin/launcher.lua $@"
apt:
arch: amd64
sources:
- sourceline: 'deb [arch=amd64] https://deb.debian.org/debian/ bullseye main contrib'
key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x648ACFD622F3D138'
- sourceline: 'deb [arch=amd64] https://deb.debian.org/debian/ bullseye-backports main contrib'
include:
- lua5.2
- prosody
files:
exclude:
- usr/share/man
- usr/share/doc/*/README.*
- usr/share/doc/*/changelog.*
- usr/share/doc/*/NEWS.*
- usr/share/doc/*/TODO.*
- etc/init.d/*
- etc/logrotate.d/*
runtime:
# Here we use the path_mappings to rewrite, on runtime, all paths.
# Note: this assume that peertube-plugin-livechat is not in a subdir of one of following mappings.
# This seems a reasonable assumption.
path_mappings:
- /etc/:$APPDIR/etc/
- /lib/:$APPDIR/lib/
- /lib64/:$APPDIR/lib64/
- /runtime/:$APPDIR/runtime/
- /usr/:$APPDIR/usr/
AppImage:
arch: x86_64
file_name: 'livechat-prosody-x86_64.AppImage'

13
prosody/launcher.lua Normal file
View File

@ -0,0 +1,13 @@
#!/usr/bin/env lua
-- This file is a launcher, that takes the first argument to choose what to launch.
local what = table.remove(arg, 1);
if what == 'prosody' then
dofile('/usr/bin/prosody');
elseif what == 'prosodyctl' then
dofile('/usr/bin/prosodyctl');
else
print("Unknown command: "..what);
os.exit(1);
end

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))