Nach fast einer Woche probieren und erstellen von etwa einem dutzend Images und Containern habe ich es just auf Heilgabend 2020 endlich geschafft Open Streaming Platform in einem Podman Container hinter einem Apache 2 Proxy zum laufen zu bringen! Yay!

Hier ist nun eine kleine Anleitung wie ich das gemacht habe.

Anmerkung: Ich habe das ganze in einem OpenSUSE Hostsystem realisiert. Unter Umständen müssen für ander Hostsysteme einige Anpassungen vorgenommen werden.

Dockerfile erstellen:

Ich habe mir folgendes Dockerfile mit den benötigten Packeten zusammengestellt.
MySQL konnte ich nicht dazu nehmen da dies beim Erstellen des Images immer zu einem Fehler und Abbruch geführt hat.

#Download base image ubuntu
FROM ubuntu:latest
LABEL maintainer="PMJ <info@pmj.rocks>"

ARG TZ=Europe/Zurich

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \
  && echo $TZ > /etc/timezone \
  && apt-get update \
  && apt-get install -y init \
  && apt-get install -y apt-utils \
  && apt-get install -y git wget unzip make python3 python3-dev python3-pip python3-setuptools build-essential libpcre3 libpcre3-dev libssl-dev libpq-dev redis gunicorn python3-gunicorn uwsgi-plugin-python3 logrotate ffmpeg nano curl dialog sudo telnet \
  && ln -s /usr/bin/gunicorn /usr/local/bin/gunicorn
CMD [ "/sbin/init" ]

Image erstellen:

Dann erstellt man das Image wie folgt:

podman build --format=docker -t pmj/ospinstall .

Dies dauerte bei mir zwischen 7-10 Minuten.

Container erstellen:

Der Container muss zwingen detached erstellt werden da sonst systemd mit der falschen PID startet!

podman run -t -d \
  -p 8585:80 \
  -p 8553:443 \
  -p 1935:1935 \
  -p 5222:5222 \
  --mount type=bind,source=/srv/osp/www,target=/var/www \
  --restart always \
  --cap-add MKNOD \
  --name osp pmj/ospinstall

Anmerkung: Ich bin mir nicht ganz sicher welche Ports tatsächlich geöffnet werden müssen, definitiv aber die Ports 80, 443 und 1935!
Da ich zuerst Probleme hatte den Chat zum laufen zu bringen, habe ich auch noch die Ports 5222 und 5269 geöffnet.
Das Problem lag aber daran, dass ich in den Settings von OSP zuerst alles auf http anstatt https laufen liess.

Die Ports 5222 (Client) und 5269 (Server) müssen grundsätzlich nur geöffnet werden, wenn man sich mit einem externen XMPP-Client oder -Server in die Stream-Chats einklinken will.

Configs

Wer auch gerne die Konfiguration ausserhalb des Containers haben möchte um nicht ständig innerhalb des Containers zwischen den Verzeichnissen hin und her wechseln will, der/die kann noch folgende Mounts einbinden:

--mount type=bind,source=/srv/osp/config/nginx,target=/usr/local/nginx/conf \
--mount type=bind,source=/srv/osp/config/ejabberd,target=/usr/local/ejabberd/conf \
--mount type=bind,source=/srv/osp/config/osp,target=/opt/osp/conf

Logs

Wer auch gerne die Logs ausserhalb des Containers haben möchte um nicht ständig innerhalb des Containers zwischen den Verzeichnissen hin und her wechseln will, der/die kann noch folgende Mounts einbinden:

--mount type=bind,source=/srv/osp/logs/osp,target=/opt/osp/logs \
--mount type=bind,source=/srv/osp/logs/ejabberd,target=/usr/local/ejabberd/logs \
--mount type=bind,source=/srv/osp/logs/nginx,target=/usr/local/nginx/logs

Templates

Um eigene Templates zu erstellen bzw. die installierten Templates besser bearbeiten zu können, kann auch der Templatepfad gemountet werden:

--mount type=bind,source=/srv/osp/templates,target=/opt/osp/templates

Apache 2 Reverse Proxy:

Ich habe Apache wie folgt konfiguriert:

<VirtualHost *:80>
  ServerName your.streaming.domain
  ErrorLog /path/to/your/your.streaming.domain_error_log
  CustomLog /path/to/your/your.streaming.domain_access_log combined
  RewriteEngine on

  # OPENSTREAMINGPLATFORM
  RewriteCond %{HTTP:Upgrade} websocket [NC]
  RewriteCond %{HTTP:Connection} upgrade [NC]
  RewriteRule .* "ws://127.0.0.1:8585%{REQUEST_URI}" [P]

  # Encoded slashes need to be allowed
  AllowEncodedSlashes NoDecode

  # keep the host
  ProxyPreserveHost On

  ProxyPass           / http://127.0.0.1:8585/ retry=0
  ProxyPassReverse    / http://127.0.0.1:8585/
</VirtualHost>
<VirtualHost *:443>
  ServerName your.streaming.domain
  ErrorLog /path/to/your/your.streaming.domain_error_log
  CustomLog /path/to/your/your.streaming.domain_access_log combined
  RewriteEngine on
  SSLEngine on
  SSLCertificateFile /path/to/your/your.streaming.domain-ssl.cert
  SSLCertificateKeyFile /path/to/your/your.streaming.domain-ssl.key
  SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1

  # OPENSTREAMINGPLATFORM
  RewriteCond %{HTTP:Upgrade} websocket [NC]
  RewriteCond %{HTTP:Connection} upgrade [NC]
  RewriteRule .* "ws://127.0.0.1:8585%{REQUEST_URI}" [P]

  # Encoded slashes need to be allowed
  AllowEncodedSlashes NoDecode

  # Container uses a unique non-signed certificate
  SSLProxyEngine On
  SSLProxyVerify None
  SSLProxyCheckPeerCN Off
  SSLProxyCheckPeerName Off

  # keep the host
  ProxyPreserveHost On

  ProxyPass           / http://127.0.0.1:8585/ retry=0
  ProxyPassReverse    / http://127.0.0.1:8585/
</VirtualHost>

Let's Encrypt

Damit eJabberd erstens von aussen erreichbar ist und zweitens die Zertifikate erstellen lassen kann, muss man für alle 3 subdomains eine eigene vHost Konfiguration anlegen und zwar sowohl für http wie auch für https.

<VirtualHost *:80 *:443>
  ServerName conference.your.streaming.domain
  SSLEngine on
  SSLCertificateFile /path/to/your/your.streaming.domain-ssl.cert
  SSLCertificateKeyFile /path/to/your/your.streaming.domain-ssl.key
  SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
  
  # Encoded slashes need to be allowed
  AllowEncodedSlashes NoDecode
  
  # Container uses a unique non-signed certificate
  SSLProxyEngine On
  SSLProxyVerify None
  SSLProxyCheckPeerCN Off
  SSLProxyCheckPeerName Off

  # keep the host
  ProxyPreserveHost On

  ProxyPass           / http://127.0.0.1:8585/ retry=0
  ProxyPassReverse    / http://127.0.0.1:8585/
</VirtualHost>
<VirtualHost *:80 *:443>
  ServerName proxy.your.streaming.domain
  SSLEngine on
  SSLCertificateFile /path/to/your/your.streaming.domain-ssl.cert
  SSLCertificateKeyFile /path/to/your/your.streaming.domain-ssl.key
  SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
  
  # Encoded slashes need to be allowed
  AllowEncodedSlashes NoDecode
  
  # Container uses a unique non-signed certificate
  SSLProxyEngine On
  SSLProxyVerify None
  SSLProxyCheckPeerCN Off
  SSLProxyCheckPeerName Off

  # keep the host
  ProxyPreserveHost On

  ProxyPass           / http://127.0.0.1:8585/ retry=0
  ProxyPassReverse    / http://127.0.0.1:8585/
</VirtualHost>
<VirtualHost *:80 *:443>
  ServerName pubsub.your.streaming.domain
  SSLEngine on
  SSLCertificateFile /path/to/your/your.streaming.domain-ssl.cert
  SSLCertificateKeyFile /path/to/your/your.streaming.domain-ssl.key
  SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
  
  # Encoded slashes need to be allowed
  AllowEncodedSlashes NoDecode
  
  # Container uses a unique non-signed certificate
  SSLProxyEngine On
  SSLProxyVerify None
  SSLProxyCheckPeerCN Off
  SSLProxyCheckPeerName Off

  # keep the host
  ProxyPreserveHost On

  ProxyPass           / http://127.0.0.1:8585/ retry=0
  ProxyPassReverse    / http://127.0.0.1:8585/
</VirtualHost>

Nach der Installtion von OSP müssen in der /usr/local/nginx/conf/locations/ejabberd.conf folgende Zeilen hinzugefügt werden...

location /.well-known/ {
    proxy_pass  http://localhost:5280/.well-known/;
}

...und unter /usr/local/nginx/conf/servers eine neue Datei ejabberd.conf mit folgendem Inhalt angelegt werden.

# Ejabberd Reverse Proxy Config to Allow for ejabberd acme-challenge
# Uncomment and change server_name to match
server {
  listen       80;
  server_name conference.your.streaming.domain;
  location / {
    proxy_pass http://localhost:5280;
  }
}
server {
  listen       80;
  server_name proxy.your.streaming.domain;
  location / {
    proxy_pass http://localhost:5280;
  }
}
server {
  listen       80;
  server_name pubsub.your.streaming.domain;
  location / {
    proxy_pass http://localhost:5280;
  }
}

Anmerkung: Damit Let's Encrypt sowohl für eJabberd als auch für meinen Proxy ein Zertifikat erstellt bzw. die Challenge durchführen kann musste ich einen extra Host definieren, der als Hauptdomain die Challenge auch für die anderen Domains akzeptieren kann.
Diese Domain habe ich dann Let's Encrypt als Hauptdomain übergeben und die anderen als zusatzliche Domains in das Zertifikat schreiben lassen.

<VirtualHost *:80>
  ServerName acme.your.streaming.domain
  DocumentRoot /path/to/your/public_html
  DirectoryIndex index.html index.htm index.php index.php4 index.php5
</VirtualHost>

OSP installieren

Sobald der Container läuft kann man OSP wie folgt installieren...

...Einloggen in den Container...

podman exec -ti osp bash

... und dann das Installationsskript ausführen...

cd /opt
git clone https://gitlab.com/Deamos/flask-nginx-rtmp-manager.git
cd flask-nginx-rtmp-manager
sudo bash osp-config.sh

Bei mir benötigte das Skript bis zu 20 Minuten bis es abgeschlossen war!
Zwischendurch muss man die Domain eingeben auf welcher OSP schlussendlich erreichbar sein soll.

Anmerkung: Bei mir hat es das Skript nicht ganz geschafft die gunicorn worker sauber zu starten.
Dies kann man mit der eingabe von

systemctl

überprüfen.
Ausser dem systemd-logind.service sollte kein Eintrag Rot dargestellt werden.
Falls die Worker nicht oder nur teilweise gestartet wurden kann man einfach OSP iwe folgt neu starten

systemctl restart osp.target

Nun kann man die Installation durch Aufrufen der gewählten Domain abschliessen und dann anfangen zu Streamen.

Happy Streaming!

ps: Graceful

Es kann sein, dass der Container beim Herunterfahren länger als die standardmässig eingestellten 10 Sekunden benötigt und dann einfach per SIGKILL runterfährt.
Dem kann man entgegenwirken indem man dem stop-Befehl noch die Optione -t=30 (oder 60, was man auch immer für sinnvoll hält) hinzufügt.

podman stop -t=30 osp