Weitere Palaver-Server auf einem Host
- Hauptserver plus beliebig viele weitere Server auf einem Host
- mit dem Hauptserver geteilt: Programme (
/opt/palaver/bin), Web-Client (/opt/palaver/web), LiveKit - Updates: Update-Anleitung für mehrere Server
Eigen je weiterem Server:
| Bestandteil | Wert |
|---|---|
| Name | <name>, z. B. guild |
| Domain | z. B. guild.palaver.social |
| Systembenutzer, Datenbankrolle | palaver-<name> |
| Datenbank | palaver_<name> (- wird _) |
| Konfiguration | /opt/palaver/instances/<name>/palaverd.env |
| systemd-Unit | palaverd@<name> |
| Port | ab 8091, siehe Port |
| LiveKit-Raumpräfix | <name>- (PALAVER_LIVEKIT_ROOM_PREFIX) |
| LiveKit-Schlüssel | eigener Schlüssel in /opt/palaver/etc/livekit.yaml |
| nginx | Site palaver-<name>, Snippet palaver-web-<name>.conf |
| nginx-Logs | /var/log/nginx/palaver/<name>.access.log, <name>.error.log |
1. Voraussetzungen
- Hauptserver nach Palavers
INSTALL.md(palaverd.service, Benutzer und Datenbankpalaver,/opt/palaver/etc, LiveKit, Snippetpalaver-web.conf), Port 8090 - Shell-Zugang mit
sudo; bash (${VAR//-/_}) - keine weiteren Ports: 443 und die LiveKit-Ports des Hauptservers
Prüfe, ob der Hauptserver da ist.
sudo ls /opt/palaver/etc/palaverd.env /opt/palaver/etc/livekit.yaml
2. Variablen
Setze die Variablen; gelten nur in dieser Shell.
NAME=guild
DOMAIN=guild.palaver.social
EMAIL=you@example.org
CERT=$DOMAIN
NAME:a-z,0-9,-(nicht als erstes oder letztes Zeichen), höchstens 24 Zeichen- reserviert:
palaver,www,account,konto,support,api,admin,dogan,livekit,mail,status DOMAIN: Hostname, kleingeschriebenEMAIL: Let's Encrypt; nur eigenes ZertifikatCERT: Zertifikatsname in/etc/letsencrypt/live/; eigenes Zertifikat:$DOMAIN; unter dem Wildcard-Zertifikat: dessen Name (z. B.palaver.social)
Setze die abgeleiteten Namen.
SRV_USER=palaver-$NAME
DB=palaver_${NAME//-/_}
CONF=/opt/palaver/instances/$NAME
UNIT=palaverd@$NAME
Prüfe den Namen; erwartet: ok.
[[ $NAME =~ ^[a-z0-9]([a-z0-9-]{0,22}[a-z0-9])?$ ]] && ! [[ " palaver www account konto support api admin dogan livekit mail status " == *" $NAME "* ]] && echo ok
Prüfe, ob der Name frei ist; erwartet: beide Befehle schlagen fehl.
ls -d "$CONF"
id "$SRV_USER"
3. DNS
- Wildcard-Eintrag (
*.palaver.social): nichts zu tun - sonst A-Eintrag:
$DOMAIN→ öffentliche IPv4-Adresse des Hosts
Prüfe den Eintrag.
getent ahostsv4 "$DOMAIN"
Zeige die Adressen des Hosts; erwartet: der A-Eintrag ist darunter.
ip -o addr show scope global
4. Port
- ein localhost-Port je Server; Hauptserver: 8090
- neuer Server: höchster belegter Port + 1
Zeige die belegten Ports.
sudo sh -c 'grep -H "^PALAVER_LISTEN=" /opt/palaver/etc/palaverd.env /opt/palaver/instances/*/palaverd.env 2>/dev/null'
Setze den nächsten Port.
PORT=$(( $(sudo sh -c 'cat /opt/palaver/etc/palaverd.env /opt/palaver/instances/*/palaverd.env 2>/dev/null' | sed -n 's/^PALAVER_LISTEN=.*://p' | sort -n | tail -n 1) + 1 )); echo "$PORT"
5. Unit-Vorlage
Einmal je Host; überspringen, wenn /etc/systemd/system/palaverd@.service existiert.
ls /etc/systemd/system/palaverd@.service
Schreibe die Unit-Vorlage; %i ist der Name des Servers.
sudo tee /etc/systemd/system/palaverd@.service >/dev/null <<'EOF'
[Unit]
Description=Palaver app server %i
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
User=palaver-%i
Group=palaver-%i
EnvironmentFile=/opt/palaver/instances/%i/palaverd.env
ExecStart=/opt/palaver/bin/palaverd serve
Restart=on-failure
RestartSec=2
LogNamespace=palaver
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
LockPersonality=yes
MemoryDenyWriteExecute=yes
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
6. Benutzer und Verzeichnis
Lege das Verzeichnis für alle weiteren Server an; einmal je Host, unschädlich, wenn es existiert.
sudo install -d -o root -g root -m 755 /opt/palaver/instances
Lege den Systembenutzer an.
sudo useradd --system --home-dir "$CONF" --no-create-home --shell /usr/sbin/nologin "$SRV_USER"
Lege das Konfigurationsverzeichnis an, nur für den Benutzer des Servers lesbar.
sudo install -d -o "$SRV_USER" -g "$SRV_USER" -m 700 "$CONF"
7. PostgreSQL
Lege die Datenbankrolle und die Datenbank an; Unix-Socket, Peer-Authentifizierung, kein Passwort.
sudo -u postgres createuser "$SRV_USER"
sudo -u postgres createdb -O "$SRV_USER" "$DB"
8. LiveKit-Schlüssel
Erzeuge LiveKit-API-Schlüssel und -Geheimnis des Servers; gebraucht in den Schritten 8 und 9.
KEY="API$(openssl rand -hex 8)"
SECRET=$(openssl rand -base64 36 | tr -d '/+=')
Trage den Schlüssel unter keys: in LiveKits Konfiguration ein.
sudo sed -i "/^keys:\$/a\\ $KEY: $SECRET" /opt/palaver/etc/livekit.yaml
Prüfe die Schlüssel; erwartet: eine Zeile je Server.
sudo sed -n '/^keys:/,/^[a-z]/p' /opt/palaver/etc/livekit.yaml
Starte LiveKit neu (liest Schlüssel nur beim Start); Gespräche auf allen Servern brechen kurz ab und verbinden sich neu.
sudo systemctl restart livekit
- LiveKit-Schlüssel sind nicht auf Räume begrenzt: die Konfiguration eines Servers gibt Zugriff auf die Gespräche aller Server
9. palaverd
Lege die Konfigurationsdatei an, nur für den Benutzer des Servers lesbar.
sudo install -o "$SRV_USER" -g "$SRV_USER" -m 600 /dev/null "$CONF/palaverd.env"
Schreibe die Konfiguration; letzte drei Zeilen: die Hinweise von doctor nennen diesen Server.
sudo tee "$CONF/palaverd.env" >/dev/null <<EOF
PALAVER_DATABASE_URL=postgres:///$DB?host=/var/run/postgresql
PALAVER_LISTEN=127.0.0.1:$PORT
PALAVER_PUBLIC_URL=https://$DOMAIN
PALAVER_LIVEKIT_URL=wss://$DOMAIN/livekit
PALAVER_LIVEKIT_API_URL=http://127.0.0.1:7880
PALAVER_LIVEKIT_API_KEY=$KEY
PALAVER_LIVEKIT_API_SECRET=$SECRET
PALAVER_LIVEKIT_ROOM_PREFIX=$NAME-
PALAVER_OPEN_REGISTRATION=false
PALAVER_CTL="sudo -u $SRV_USER /opt/palaver/bin/palaverd -env $CONF/palaverd.env"
PALAVER_UNIT=$UNIT
PALAVER_ENV_FILE=$CONF/palaverd.env
EOF
- weitere Einstellungen: Palavers
INSTALL.md, Konfigurationsreferenz
10. Zertifikat
Unter einem Wildcard-Zertifikat
CERT = Name des Wildcard-Zertifikats. Prüfe, ob es $DOMAIN abdeckt; erwartet: DNS:*.<übergeordnete Domain>.
sudo openssl x509 -noout -ext subjectAltName -in "/etc/letsencrypt/live/$CERT/fullchain.pem"
Weiter: Schritt 11.
Eigenes Zertifikat
CERT = $DOMAIN. Schreibe eine vorläufige Site auf Port 80 für die Let's-Encrypt-Prüfung.
sudo tee "/etc/nginx/sites-available/palaver-$NAME" >/dev/null <<EOF
server {
listen 80;
listen [::]:80;
server_name $DOMAIN;
location /.well-known/acme-challenge/ { root /var/www/html; }
location / { return 404; }
}
EOF
sudo ln -sf "/etc/nginx/sites-available/palaver-$NAME" "/etc/nginx/sites-enabled/palaver-$NAME"
sudo nginx -t && sudo systemctl reload nginx
Hole das Zertifikat; automatische Erneuerung, lädt nginx neu.
sudo certbot certonly --webroot -w /var/www/html -d "$DOMAIN" -m "$EMAIL" \
--agree-tos --no-eff-email --non-interactive \
--deploy-hook "systemctl reload nginx"
11. nginx
Schreibe das Web-Snippet des Servers (wie Palavers INSTALL.md, Schritt 9); sed setzt @DOMAIN@ ein.
sudo tee "/etc/nginx/snippets/palaver-web-$NAME.conf" >/dev/null <<'EOF'
root /opt/palaver/web;
index index.html;
location / {
try_files $uri /index.html;
add_header Cache-Control "no-cache" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; worker-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob: mediastream:; font-src 'self'; connect-src 'self' wss://@DOMAIN@; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer" always;
add_header Permissions-Policy "camera=(), geolocation=(), payment=()" always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
location /assets/ {
try_files $uri =404;
add_header Cache-Control "public, max-age=31536000, immutable" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; worker-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob: mediastream:; font-src 'self'; connect-src 'self' wss://@DOMAIN@; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
EOF
sudo sed -i "s/@DOMAIN@/$DOMAIN/g" "/etc/nginx/snippets/palaver-web-$NAME.conf"
Schreibe die vollständige Site; sed setzt @DOMAIN@, @CERT@, @PORT@, @NAME@ ein.
sudo tee "/etc/nginx/sites-available/palaver-$NAME" >/dev/null <<'EOF'
server {
listen 80;
listen [::]:80;
server_name @DOMAIN@;
access_log /var/log/nginx/palaver/@NAME@.access.log;
error_log /var/log/nginx/palaver/@NAME@.error.log;
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name @DOMAIN@;
ssl_certificate /etc/letsencrypt/live/@CERT@/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/@CERT@/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
access_log /var/log/nginx/palaver/@NAME@.access.log;
error_log /var/log/nginx/palaver/@NAME@.error.log;
client_max_body_size 64k;
# server push, one WebSocket per client
location = /api/events {
proxy_pass http://127.0.0.1:@PORT@;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 1h;
proxy_send_timeout 1h;
}
# profile pictures, server icon: 5 MiB
location ~ ^/api/(me/avatar|server/icon)$ {
client_max_body_size 5m;
proxy_pass http://127.0.0.1:@PORT@;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# attachments: 25 MiB
location ~ ^/api/channels/[0-9]+/attachments$ {
client_max_body_size 25m;
proxy_pass http://127.0.0.1:@PORT@;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /api/ {
proxy_pass http://127.0.0.1:@PORT@;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# LiveKit signalling, shared; the trailing slash strips /livekit
location /livekit/ {
proxy_pass http://127.0.0.1:7880/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 1h;
proxy_send_timeout 1h;
}
include snippets/palaver-web-@NAME@.conf;
}
EOF
sudo sed -i -e "s/@DOMAIN@/$DOMAIN/g" -e "s/@CERT@/$CERT/g" -e "s/@PORT@/$PORT/g" -e "s/@NAME@/$NAME/g" "/etc/nginx/sites-available/palaver-$NAME"
Aktiviere, prüfe und lade sie.
sudo ln -sf "/etc/nginx/sites-available/palaver-$NAME" "/etc/nginx/sites-enabled/palaver-$NAME"
sudo nginx -t && sudo systemctl reload nginx
12. Starten
Starte den Server; der erste Start legt die Datenbanktabellen an.
sudo systemctl enable --now "$UNIT"
Prüfe, ob er antwortet; erwartet: JSON.
curl -fsS "http://127.0.0.1:$PORT/api/registration"
13. Erster Admin
Erzeuge einen Einladungscode für den ersten Admin; einmal nutzbar, 7 Tage gültig.
sudo -u "$SRV_USER" /opt/palaver/bin/palaverd -env "$CONF/palaverd.env" invite -role Admin -valid 168h
- registrieren: Palaver-App → Server
https://$DOMAIN→ Registrieren → Code - erster Admin = Eigentümer des Servers
14. Prüfen
Führe doctor aus; erwartet: ok in jeder Zeile.
sudo -u "$SRV_USER" /opt/palaver/bin/palaverd -env "$CONF/palaverd.env" doctor
Prüfe, ob der Hauptserver weiter antwortet.
sudo -u palaver /opt/palaver/bin/palaverd -env /opt/palaver/etc/palaverd.env doctor
Server auflisten
Namen der weiteren Server.
ls /opt/palaver/instances/
Domain und Port jedes Servers.
sudo sh -c 'grep -H -E "^PALAVER_(PUBLIC_URL|LISTEN)=" /opt/palaver/etc/palaverd.env /opt/palaver/instances/*/palaverd.env'
Zustand jedes Servers.
systemctl list-units --all 'palaverd*'
Sicherung
- Datenbank: alle Daten des Servers
$CONF: Konfiguration, LiveKit-Schlüssel- Voraussetzung:
NAMEund abgeleitete Namen (Schritt 2)
Sichere die Datenbank.
sudo -u "$SRV_USER" pg_dump -Fc "$DB" > "palaver-$NAME-$(date +%F).dump"
Sichere die Konfiguration.
sudo tar -czf "palaver-$NAME-etc-$(date +%F).tar.gz" -C /opt/palaver/instances "$NAME"
Wiederherstellen
Voraussetzung: Server mit demselben Namen und derselben Domain, Schritte 1–12.
Halte ihn an.
sudo systemctl stop "$UNIT"
Destruktiv: Lösche die Datenbank des neuen Servers ($DB, alle seine Konten und Nachrichten).
sudo -u postgres dropdb "$DB"
Lege sie neu an und lade die Sicherung.
sudo -u postgres createdb -O "$SRV_USER" "$DB"
sudo -u "$SRV_USER" pg_restore -d "$DB" palaver-<name>-<date>.dump
Starte ihn.
sudo systemctl start "$UNIT"
Server entfernen
Voraussetzung: NAME und abgeleitete Namen (Schritt 2).
Lies Domain und LiveKit-Schlüssel aus.
DOMAIN=$(sudo sed -n 's#^PALAVER_PUBLIC_URL=https://##p' "$CONF/palaverd.env"); echo "$DOMAIN"
KEY=$(sudo sed -n 's/^PALAVER_LIVEKIT_API_KEY=//p' "$CONF/palaverd.env"); echo "$KEY"
Zeige das Zertifikat der Site; eigenes Zertifikat: Name $DOMAIN.
grep ssl_certificate "/etc/nginx/sites-available/palaver-$NAME"
Vorher sichern: Sicherung.
Halte den Dienst an und deaktiviere ihn.
sudo systemctl disable --now "$UNIT"
Entferne nginx-Site und -Snippet und lade nginx neu.
sudo rm "/etc/nginx/sites-enabled/palaver-$NAME" "/etc/nginx/sites-available/palaver-$NAME" "/etc/nginx/snippets/palaver-web-$NAME.conf"
sudo nginx -t && sudo systemctl reload nginx
Destruktiv: Lösche die Datenbank $DB (alle Konten, Nachrichten, Anhänge) und ihre Rolle $SRV_USER; nicht rückgängig zu machen.
sudo -u postgres dropdb "$DB"
sudo -u postgres dropuser "$SRV_USER"
Destruktiv: Lösche den LiveKit-Schlüssel des Servers (die Zeile $KEY: … in /opt/palaver/etc/livekit.yaml).
sudo sed -i "/^ $KEY: /d" /opt/palaver/etc/livekit.yaml
Starte LiveKit neu; Gespräche auf den anderen Servern brechen kurz ab.
sudo systemctl restart livekit
Destruktiv: Lösche die Konfiguration $CONF und den Benutzer $SRV_USER.
sudo rm -r "$CONF"
sudo userdel "$SRV_USER"
Destruktiv: Lösche das Zertifikat; nur ein eigenes ($DOMAIN), nie das Wildcard-Zertifikat.
sudo certbot delete --cert-name "$DOMAIN"
Destruktiv: Lösche die nginx-Logs des Servers (/var/log/nginx/palaver/$NAME.*).
sudo sh -c "rm /var/log/nginx/palaver/$NAME.access.log* /var/log/nginx/palaver/$NAME.error.log*"
Lösche den DNS-Eintrag von $DOMAIN; nur ein eigener Eintrag.
Nach dem letzten weiteren Server
Entferne die Unit-Vorlage.
sudo rm /etc/systemd/system/palaverd@.service
sudo systemctl daemon-reload
Entferne das leere Verzeichnis.
sudo rmdir /opt/palaver/instances
- vor dem Deinstallieren des Hauptservers (Palavers
INSTALL.md, Deinstallieren): alle weiteren Server entfernen
Optional: unbekannte Namen zum Hauptserver
- für: Wildcard-DNS-Eintrag und -Zertifikat für die Domain des Hauptservers
- unbekannte Namen (Tippfehler): Umleitung zum Hauptserver statt zu nginx' erster Site
Setze die Domain des Hauptservers und den Namen seines Zertifikats.
MAIN=palaver.social
MAIN_CERT=palaver.social
Prüfe, ob das Zertifikat *.$MAIN abdeckt.
sudo openssl x509 -noout -ext subjectAltName -in "/etc/letsencrypt/live/$MAIN_CERT/fullchain.pem"
Prüfe, ob die Platzhalter-Site der Distribution default_server beansprucht.
grep default_server /etc/nginx/sites-enabled/default
Falls ja: deaktiviere sie; bleibt in sites-available.
sudo rm /etc/nginx/sites-enabled/default
Schreibe die Catch-all-Site; sed setzt @MAIN@, @MAIN_CERT@ ein.
sudo tee /etc/nginx/sites-available/palaver-catch-all >/dev/null <<'EOF'
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
location /.well-known/acme-challenge/ { root /var/www/html; }
location / { return 301 https://@MAIN@/; }
}
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
ssl_certificate /etc/letsencrypt/live/@MAIN_CERT@/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/@MAIN_CERT@/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
return 301 https://@MAIN@/;
}
EOF
sudo sed -i -e "s/@MAIN_CERT@/$MAIN_CERT/g" -e "s/@MAIN@/$MAIN/g" /etc/nginx/sites-available/palaver-catch-all
Aktiviere, prüfe und lade sie.
sudo ln -sf /etc/nginx/sites-available/palaver-catch-all /etc/nginx/sites-enabled/palaver-catch-all
sudo nginx -t && sudo systemctl reload nginx
Optional: Wildcard-Zertifikat
- ein Zertifikat für die Hauptdomain und jeden Namen darunter (
palaver.social,*.palaver.social) - certbot-DNS-Prüfung
- palaver.social: certbot-Snap, Plugin
certbot-dns-hetzner-cloud, Zugangsdaten/root/.secrets/hetzner.ini, Deploy-Hooksystemctl reload nginx - nur ein certbot je Host
Liste die Zertifikate und die Namen auf, die sie abdecken.
sudo certbot certificates
Prüfe den Deploy-Hook des Wildcard-Zertifikats.
sudo grep -E 'authenticator|renew_hook|deploy_hook' /etc/letsencrypt/renewal/palaver.social.conf
Dateien
| Pfad | Inhalt |
|---|---|
/etc/systemd/system/palaverd@.service |
Unit-Vorlage |
/opt/palaver/instances/<name>/palaverd.env |
Konfiguration (nur Benutzer palaver-<name>) |
/opt/palaver/etc/livekit.yaml |
ein Schlüssel je Server unter keys: |
/etc/nginx/sites-available/palaver-<name> |
nginx-Site |
/etc/nginx/snippets/palaver-web-<name>.conf |
Web-Client, CSP für die Domain von <name> |
/etc/nginx/sites-available/palaver-catch-all |
unbekannte Namen (optional) |
/var/log/nginx/palaver/<name>.access.log, <name>.error.log |
nginx-Logs (rotiert von Palavers /etc/logrotate.d/palaver) |
Fehlersuche
doctor eines Servers.
sudo -u "$SRV_USER" /opt/palaver/bin/palaverd -env "$CONF/palaverd.env" doctor
Was palaverd meldet.
sudo journalctl --namespace=palaver -u "$UNIT" -n 100
Was nginx meldet.
sudo tail -n 50 "/var/log/nginx/palaver/$NAME.error.log"
| Symptom | Ursache |
|---|---|
useradd: invalid user name |
Name zu lang (höchstens 24 Zeichen) |
| Sprache geht nur auf diesem Server nicht | Schlüssel fehlt in livekit.yaml oder LiveKit nach Schritt 8 nicht neu gestartet |
| Sprache geht auf keinem Server | livekit.yaml durch eine Änderung kaputt: sudo journalctl --namespace=palaver -u livekit -n 50 |
| die Site eines anderen Servers antwortet | server_name oder der Symlink in sites-enabled fehlt |
| Web-Client im Browser ohne Sprache | CSP: wss://$DOMAIN fehlt in palaver-web-$NAME.conf |
Further Palaver servers on one host
- main server plus any number of further servers on one host
- shared with the main server: programs (
/opt/palaver/bin), web client (/opt/palaver/web), LiveKit - updates: the update guide for several servers
Own per further server:
| Part | Value |
|---|---|
| name | <name>, e.g. guild |
| domain | e.g. guild.palaver.social |
| system user, database role | palaver-<name> |
| database | palaver_<name> (- becomes _) |
| configuration | /opt/palaver/instances/<name>/palaverd.env |
| systemd unit | palaverd@<name> |
| port | from 8091, see Port |
| LiveKit room prefix | <name>- (PALAVER_LIVEKIT_ROOM_PREFIX) |
| LiveKit key | own key in /opt/palaver/etc/livekit.yaml |
| nginx | site palaver-<name>, snippet palaver-web-<name>.conf |
| nginx logs | /var/log/nginx/palaver/<name>.access.log, <name>.error.log |
1. Requirements
- main server per Palaver's
INSTALL.md(palaverd.service, user and databasepalaver,/opt/palaver/etc, LiveKit, snippetpalaver-web.conf), port 8090 - shell access with
sudo; bash (${VAR//-/_}) - no further ports: 443 and the main server's LiveKit ports
Check that the main server is there.
sudo ls /opt/palaver/etc/palaverd.env /opt/palaver/etc/livekit.yaml
2. Variables
Set the variables; valid in this shell only.
NAME=guild
DOMAIN=guild.palaver.social
EMAIL=you@example.org
CERT=$DOMAIN
NAME:a-z,0-9,-(not first or last), max. 24 characters- reserved:
palaver,www,account,konto,support,api,admin,dogan,livekit,mail,status DOMAIN: host name, lower caseEMAIL: Let's Encrypt; own certificate onlyCERT: certificate name in/etc/letsencrypt/live/; own certificate:$DOMAIN; under the wildcard certificate: its name (e.g.palaver.social)
Set the derived names.
SRV_USER=palaver-$NAME
DB=palaver_${NAME//-/_}
CONF=/opt/palaver/instances/$NAME
UNIT=palaverd@$NAME
Check the name; expected: ok.
[[ $NAME =~ ^[a-z0-9]([a-z0-9-]{0,22}[a-z0-9])?$ ]] && ! [[ " palaver www account konto support api admin dogan livekit mail status " == *" $NAME "* ]] && echo ok
Check that the name is free; expected: both commands fail.
ls -d "$CONF"
id "$SRV_USER"
3. DNS
- wildcard record (
*.palaver.social): nothing to do - otherwise A record:
$DOMAIN→ the host's public IPv4 address
Check the record.
getent ahostsv4 "$DOMAIN"
Show the host's addresses; expected: the A record among them.
ip -o addr show scope global
4. Port
- one localhost port per server; main server: 8090
- new server: highest port in use + 1
Show the ports in use.
sudo sh -c 'grep -H "^PALAVER_LISTEN=" /opt/palaver/etc/palaverd.env /opt/palaver/instances/*/palaverd.env 2>/dev/null'
Set the next port.
PORT=$(( $(sudo sh -c 'cat /opt/palaver/etc/palaverd.env /opt/palaver/instances/*/palaverd.env 2>/dev/null' | sed -n 's/^PALAVER_LISTEN=.*://p' | sort -n | tail -n 1) + 1 )); echo "$PORT"
5. Unit template
Once per host; skip if /etc/systemd/system/palaverd@.service exists.
ls /etc/systemd/system/palaverd@.service
Write the template unit; %i is the server's name.
sudo tee /etc/systemd/system/palaverd@.service >/dev/null <<'EOF'
[Unit]
Description=Palaver app server %i
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
User=palaver-%i
Group=palaver-%i
EnvironmentFile=/opt/palaver/instances/%i/palaverd.env
ExecStart=/opt/palaver/bin/palaverd serve
Restart=on-failure
RestartSec=2
LogNamespace=palaver
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
LockPersonality=yes
MemoryDenyWriteExecute=yes
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
6. User and directory
Create the directory for all further servers; once per host, harmless if it exists.
sudo install -d -o root -g root -m 755 /opt/palaver/instances
Create the system user.
sudo useradd --system --home-dir "$CONF" --no-create-home --shell /usr/sbin/nologin "$SRV_USER"
Create the configuration directory, readable by the server's user only.
sudo install -d -o "$SRV_USER" -g "$SRV_USER" -m 700 "$CONF"
7. PostgreSQL
Create the database role and the database; Unix socket, peer authentication, no password.
sudo -u postgres createuser "$SRV_USER"
sudo -u postgres createdb -O "$SRV_USER" "$DB"
8. LiveKit key
Generate the server's LiveKit API key and secret; used in steps 8 and 9.
KEY="API$(openssl rand -hex 8)"
SECRET=$(openssl rand -base64 36 | tr -d '/+=')
Add the key under keys: in LiveKit's configuration.
sudo sed -i "/^keys:\$/a\\ $KEY: $SECRET" /opt/palaver/etc/livekit.yaml
Check the keys; expected: one line per server.
sudo sed -n '/^keys:/,/^[a-z]/p' /opt/palaver/etc/livekit.yaml
Restart LiveKit (reads keys at start only); calls on every server drop briefly and reconnect.
sudo systemctl restart livekit
- LiveKit keys are not limited to rooms: one server's configuration gives access to all servers' calls
9. palaverd
Create the configuration file, readable by the server's user only.
sudo install -o "$SRV_USER" -g "$SRV_USER" -m 600 /dev/null "$CONF/palaverd.env"
Write the configuration; last three lines: doctor's hints name this server.
sudo tee "$CONF/palaverd.env" >/dev/null <<EOF
PALAVER_DATABASE_URL=postgres:///$DB?host=/var/run/postgresql
PALAVER_LISTEN=127.0.0.1:$PORT
PALAVER_PUBLIC_URL=https://$DOMAIN
PALAVER_LIVEKIT_URL=wss://$DOMAIN/livekit
PALAVER_LIVEKIT_API_URL=http://127.0.0.1:7880
PALAVER_LIVEKIT_API_KEY=$KEY
PALAVER_LIVEKIT_API_SECRET=$SECRET
PALAVER_LIVEKIT_ROOM_PREFIX=$NAME-
PALAVER_OPEN_REGISTRATION=false
PALAVER_CTL="sudo -u $SRV_USER /opt/palaver/bin/palaverd -env $CONF/palaverd.env"
PALAVER_UNIT=$UNIT
PALAVER_ENV_FILE=$CONF/palaverd.env
EOF
- other settings: Palaver's
INSTALL.md, Configuration reference
10. Certificate
Under a wildcard certificate
CERT = wildcard certificate's name. Check that it covers $DOMAIN; expected: DNS:*.<parent domain>.
sudo openssl x509 -noout -ext subjectAltName -in "/etc/letsencrypt/live/$CERT/fullchain.pem"
Next: step 11.
Own certificate
CERT = $DOMAIN. Write a temporary port-80 site for the Let's Encrypt challenge.
sudo tee "/etc/nginx/sites-available/palaver-$NAME" >/dev/null <<EOF
server {
listen 80;
listen [::]:80;
server_name $DOMAIN;
location /.well-known/acme-challenge/ { root /var/www/html; }
location / { return 404; }
}
EOF
sudo ln -sf "/etc/nginx/sites-available/palaver-$NAME" "/etc/nginx/sites-enabled/palaver-$NAME"
sudo nginx -t && sudo systemctl reload nginx
Get the certificate; automatic renewal, reloads nginx.
sudo certbot certonly --webroot -w /var/www/html -d "$DOMAIN" -m "$EMAIL" \
--agree-tos --no-eff-email --non-interactive \
--deploy-hook "systemctl reload nginx"
11. nginx
Write the server's web snippet (as Palaver's INSTALL.md, step 9); sed fills in @DOMAIN@.
sudo tee "/etc/nginx/snippets/palaver-web-$NAME.conf" >/dev/null <<'EOF'
root /opt/palaver/web;
index index.html;
location / {
try_files $uri /index.html;
add_header Cache-Control "no-cache" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; worker-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob: mediastream:; font-src 'self'; connect-src 'self' wss://@DOMAIN@; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer" always;
add_header Permissions-Policy "camera=(), geolocation=(), payment=()" always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
location /assets/ {
try_files $uri =404;
add_header Cache-Control "public, max-age=31536000, immutable" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; worker-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob: mediastream:; font-src 'self'; connect-src 'self' wss://@DOMAIN@; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000" always;
}
EOF
sudo sed -i "s/@DOMAIN@/$DOMAIN/g" "/etc/nginx/snippets/palaver-web-$NAME.conf"
Write the full site; sed fills in @DOMAIN@, @CERT@, @PORT@, @NAME@.
sudo tee "/etc/nginx/sites-available/palaver-$NAME" >/dev/null <<'EOF'
server {
listen 80;
listen [::]:80;
server_name @DOMAIN@;
access_log /var/log/nginx/palaver/@NAME@.access.log;
error_log /var/log/nginx/palaver/@NAME@.error.log;
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name @DOMAIN@;
ssl_certificate /etc/letsencrypt/live/@CERT@/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/@CERT@/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
access_log /var/log/nginx/palaver/@NAME@.access.log;
error_log /var/log/nginx/palaver/@NAME@.error.log;
client_max_body_size 64k;
# server push, one WebSocket per client
location = /api/events {
proxy_pass http://127.0.0.1:@PORT@;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 1h;
proxy_send_timeout 1h;
}
# profile pictures, server icon: 5 MiB
location ~ ^/api/(me/avatar|server/icon)$ {
client_max_body_size 5m;
proxy_pass http://127.0.0.1:@PORT@;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# attachments: 25 MiB
location ~ ^/api/channels/[0-9]+/attachments$ {
client_max_body_size 25m;
proxy_pass http://127.0.0.1:@PORT@;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /api/ {
proxy_pass http://127.0.0.1:@PORT@;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# LiveKit signalling, shared; the trailing slash strips /livekit
location /livekit/ {
proxy_pass http://127.0.0.1:7880/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 1h;
proxy_send_timeout 1h;
}
include snippets/palaver-web-@NAME@.conf;
}
EOF
sudo sed -i -e "s/@DOMAIN@/$DOMAIN/g" -e "s/@CERT@/$CERT/g" -e "s/@PORT@/$PORT/g" -e "s/@NAME@/$NAME/g" "/etc/nginx/sites-available/palaver-$NAME"
Enable, check and load it.
sudo ln -sf "/etc/nginx/sites-available/palaver-$NAME" "/etc/nginx/sites-enabled/palaver-$NAME"
sudo nginx -t && sudo systemctl reload nginx
12. Start
Start the server; the first start creates the database tables.
sudo systemctl enable --now "$UNIT"
Check that it answers; expected: JSON.
curl -fsS "http://127.0.0.1:$PORT/api/registration"
13. First admin
Create an invite code for the first admin; single use, valid 7 days.
sudo -u "$SRV_USER" /opt/palaver/bin/palaverd -env "$CONF/palaverd.env" invite -role Admin -valid 168h
- register: Palaver app → server
https://$DOMAIN→ Register → code - first admin = server owner
14. Check
Run doctor; expected: ok on every line.
sudo -u "$SRV_USER" /opt/palaver/bin/palaverd -env "$CONF/palaverd.env" doctor
Check that the main server still answers.
sudo -u palaver /opt/palaver/bin/palaverd -env /opt/palaver/etc/palaverd.env doctor
List servers
Names of the further servers.
ls /opt/palaver/instances/
Domain and port of every server.
sudo sh -c 'grep -H -E "^PALAVER_(PUBLIC_URL|LISTEN)=" /opt/palaver/etc/palaverd.env /opt/palaver/instances/*/palaverd.env'
State of every server.
systemctl list-units --all 'palaverd*'
Backup
- database: all data of the server
$CONF: configuration, LiveKit key- prerequisite:
NAMEand derived names (step 2)
Dump the database.
sudo -u "$SRV_USER" pg_dump -Fc "$DB" > "palaver-$NAME-$(date +%F).dump"
Save the configuration.
sudo tar -czf "palaver-$NAME-etc-$(date +%F).tar.gz" -C /opt/palaver/instances "$NAME"
Restore
Prerequisite: server with the same name and domain, steps 1–12.
Stop it.
sudo systemctl stop "$UNIT"
Destructive: delete the database of the new server ($DB, all its accounts and messages).
sudo -u postgres dropdb "$DB"
Create it again and load the dump.
sudo -u postgres createdb -O "$SRV_USER" "$DB"
sudo -u "$SRV_USER" pg_restore -d "$DB" palaver-<name>-<date>.dump
Start it.
sudo systemctl start "$UNIT"
Remove a server
Prerequisite: NAME and derived names (step 2).
Read the domain and the LiveKit key.
DOMAIN=$(sudo sed -n 's#^PALAVER_PUBLIC_URL=https://##p' "$CONF/palaverd.env"); echo "$DOMAIN"
KEY=$(sudo sed -n 's/^PALAVER_LIVEKIT_API_KEY=//p' "$CONF/palaverd.env"); echo "$KEY"
Show the site's certificate; own certificate: name $DOMAIN.
grep ssl_certificate "/etc/nginx/sites-available/palaver-$NAME"
Back up first: Backup.
Stop and disable the service.
sudo systemctl disable --now "$UNIT"
Remove the nginx site and snippet and reload nginx.
sudo rm "/etc/nginx/sites-enabled/palaver-$NAME" "/etc/nginx/sites-available/palaver-$NAME" "/etc/nginx/snippets/palaver-web-$NAME.conf"
sudo nginx -t && sudo systemctl reload nginx
Destructive: delete the database $DB (all accounts, messages, attachments) and its role $SRV_USER; cannot be undone.
sudo -u postgres dropdb "$DB"
sudo -u postgres dropuser "$SRV_USER"
Destructive: delete the server's LiveKit key (the line $KEY: … in /opt/palaver/etc/livekit.yaml).
sudo sed -i "/^ $KEY: /d" /opt/palaver/etc/livekit.yaml
Restart LiveKit; calls on the other servers drop briefly.
sudo systemctl restart livekit
Destructive: delete the configuration $CONF and the user $SRV_USER.
sudo rm -r "$CONF"
sudo userdel "$SRV_USER"
Destructive: delete the certificate; only an own one ($DOMAIN), never the wildcard certificate.
sudo certbot delete --cert-name "$DOMAIN"
Destructive: delete nginx's logs of the server (/var/log/nginx/palaver/$NAME.*).
sudo sh -c "rm /var/log/nginx/palaver/$NAME.access.log* /var/log/nginx/palaver/$NAME.error.log*"
Delete the DNS record of $DOMAIN; own record only.
After the last further server
Remove the unit template.
sudo rm /etc/systemd/system/palaverd@.service
sudo systemctl daemon-reload
Remove the empty directory.
sudo rmdir /opt/palaver/instances
- before uninstalling the main server (Palaver's
INSTALL.md, Uninstall): remove every further server
Optional: unknown names to the main server
- for: wildcard DNS record and certificate for the main server's domain
- unknown names (typos): redirect to the main server instead of nginx's first site
Set the main server's domain and its certificate's name.
MAIN=palaver.social
MAIN_CERT=palaver.social
Check that the certificate covers *.$MAIN.
sudo openssl x509 -noout -ext subjectAltName -in "/etc/letsencrypt/live/$MAIN_CERT/fullchain.pem"
Check whether the distribution's placeholder site claims default_server.
grep default_server /etc/nginx/sites-enabled/default
If so: disable it; stays in sites-available.
sudo rm /etc/nginx/sites-enabled/default
Write the catch-all site; sed fills in @MAIN@, @MAIN_CERT@.
sudo tee /etc/nginx/sites-available/palaver-catch-all >/dev/null <<'EOF'
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
location /.well-known/acme-challenge/ { root /var/www/html; }
location / { return 301 https://@MAIN@/; }
}
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
ssl_certificate /etc/letsencrypt/live/@MAIN_CERT@/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/@MAIN_CERT@/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
return 301 https://@MAIN@/;
}
EOF
sudo sed -i -e "s/@MAIN_CERT@/$MAIN_CERT/g" -e "s/@MAIN@/$MAIN/g" /etc/nginx/sites-available/palaver-catch-all
Enable, check and load it.
sudo ln -sf /etc/nginx/sites-available/palaver-catch-all /etc/nginx/sites-enabled/palaver-catch-all
sudo nginx -t && sudo systemctl reload nginx
Optional: wildcard certificate
- one certificate for the main domain and every name under it (
palaver.social,*.palaver.social) - certbot DNS challenge
- palaver.social: certbot snap, plugin
certbot-dns-hetzner-cloud, credentials/root/.secrets/hetzner.ini, deploy hooksystemctl reload nginx - one certbot per host only
List the certificates and the names they cover.
sudo certbot certificates
Check the deploy hook of the wildcard certificate.
sudo grep -E 'authenticator|renew_hook|deploy_hook' /etc/letsencrypt/renewal/palaver.social.conf
Files
| Path | Content |
|---|---|
/etc/systemd/system/palaverd@.service |
unit template |
/opt/palaver/instances/<name>/palaverd.env |
configuration (user palaver-<name> only) |
/opt/palaver/etc/livekit.yaml |
one key per server under keys: |
/etc/nginx/sites-available/palaver-<name> |
nginx site |
/etc/nginx/snippets/palaver-web-<name>.conf |
web client, CSP for <name>'s domain |
/etc/nginx/sites-available/palaver-catch-all |
unknown names (optional) |
/var/log/nginx/palaver/<name>.access.log, <name>.error.log |
nginx logs (rotated by Palaver's /etc/logrotate.d/palaver) |
Troubleshooting
Doctor of one server.
sudo -u "$SRV_USER" /opt/palaver/bin/palaverd -env "$CONF/palaverd.env" doctor
What palaverd says.
sudo journalctl --namespace=palaver -u "$UNIT" -n 100
What nginx says.
sudo tail -n 50 "/var/log/nginx/palaver/$NAME.error.log"
| Symptom | Cause |
|---|---|
useradd: invalid user name |
name too long (at most 24 characters) |
| voice fails on this server only | key missing in livekit.yaml or LiveKit not restarted after step 8 |
| voice fails on every server | livekit.yaml broken by an edit: sudo journalctl --namespace=palaver -u livekit -n 50 |
| another server's site answers | server_name or the symlink in sites-enabled missing |
| web client without voice in the browser | CSP: wss://$DOMAIN missing in palaver-web-$NAME.conf |