Zum Inhalt springen

Installation

Castmux wird als Container-Image ausgeliefert, und das ist die unterstützte Betriebsart. Das Image enthält das Go-Binary und beide Streaming-Engines sowie eine kleine Auswahl an Kommandozeilenwerkzeugen, die der Prozess aufruft.

Terminal-Fenster
sudo mkdir -p /opt/castmux/config
sudo docker run -d --name castmux --restart unless-stopped \
--network host \
--cap-add NET_RAW \
--cap-add NET_ADMIN \
-v /opt/castmux/config:/config \
-e CASTMUX_WEB_USERNAME=admin \
-e CASTMUX_WEB_PASSWORD=choose-something-better \
castmux/castmux:latest

Oder als Compose-Datei:

/opt/castmux/docker-compose.yml
services:
castmux:
image: castmux/castmux:latest
container_name: castmux
restart: unless-stopped
# Multicast must be emitted onto the real LAN, not a bridge.
network_mode: host
cap_add:
- NET_RAW # libpcap capture: viewer detection, wire bitrate
- NET_ADMIN # multicast route inspection and management
volumes:
# A host directory, not a named volume — see below.
- /opt/castmux/config:/config
environment:
CASTMUX_WEB_USERNAME: admin
CASTMUX_WEB_PASSWORD: choose-something-better

Ein Docker-Bridge-Netzwerk ist ein anderes Layer-2-Segment als das, in dem die Fernseher hängen. Dorthin gesendeter Multicast wird innerhalb der Bridge zugestellt und nirgendwo sonst. Das Fehlerbild ist ungewöhnlich wenig hilfreich: Der Sendeaufruf gelingt, die Engine meldet, dass sie streamt, die Paketzähler steigen, und kein Fernseher empfängt jemals etwas. Es gibt keinen Fehler zu finden, weil keiner aufgetreten ist.

Host-Networking ist deshalb keine Bequemlichkeit, sondern eine funktionale Voraussetzung. Es bedeutet außerdem, dass die Weboberfläche direkt an Port 8080 des Hosts gebunden wird; ein Port-Mapping ist weder nötig noch möglich.

Castmux schneidet Pakete mit libpcap im Promiscuous Mode mit. Das dient zwei Zwecken, die sich auf keinem anderen Weg erreichen lassen:

  • Zuschauererkennung. IGMP-Membership-Reports anderer Hosts sind an Gruppen adressiert, denen dieser Rechner nicht beigetreten ist, und der Kernel verwirft Multicast nicht beigetretener Gruppen, bevor ein gewöhnlicher Socket ihn sehen kann. Mitschneiden im Promiscuous Mode ist der einzige Weg zu beobachten, welche Fernseher welcher Gruppe beigetreten sind.
  • Bitrate auf der Leitung. Gezählt wird, was die Schnittstelle tatsächlich verlassen hat, statt dem zu glauben, was die Engine gesendet haben will. Das ist das einzige Lebenszeichen, das für beide Engines identisch funktioniert, und das einzige, das eine verwirrte Engine nicht vortäuschen kann.

Ohne NET_RAW hat On-Demand-Streaming keine Grundlage, und die Kacheln für die UDP-Bitrate bleiben leer. Ob die Sender dann dauerhaft oder gar nicht laufen, hängt von der Fail-open-Einstellung ab, die unter On-Demand-Streaming beschrieben ist.

Verwaltung und Prüfung der Multicast-Route. Castmux prüft die Routing-Tabelle des Hosts von außerhalb des Streaming-Pfads, weil — wie der nächste Abschnitt erklärt — ein nicht routbares Ziel aus dem Inneren eines Prozesses, der dorthin sendet, völlig unsichtbar ist.

Der gesamte Zustand liegt in einer Datei: /config/config.yaml im Container, also dort, wo Sie das Verzeichnis auf dem Host eingebunden haben.

Binden Sie ein Host-Verzeichnis ein, statt ein benanntes Volume zu verwenden. Das ist eine bewusste Empfehlung, kein Versehen. Am dringendsten brauchen Sie die Konfiguration genau dann, wenn die Weboberfläche nicht antwortet — und in diesem Moment ist ein benanntes Volume ein Verzeichnis tief im Speicherbereich der Container-Laufzeit, mit einem Namen, den sich niemand merken kann. Ein einfacher Pfad bedeutet, dass sich die Konfiguration mit einem Editor lesen, mit der von letzter Woche vergleichen, mit cp sichern und bei Bedarf für eine Änderungshistorie versionieren lässt.

Beim ersten Start wird die Datei mit Standardwerten angelegt. Danach gilt:

  • Fehlende Schlüssel werden automatisch ergänzt, sodass eine von einer älteren Version geschriebene Konfiguration nach einem Upgrade weiter funktioniert und die neuen Schlüssel mit ihren Standardwerten erhält.
  • Vor jeder Migration, die die Struktur der Datei ändert, wird daneben eine Kopie .pre-migration geschrieben. Interpretiert ein Upgrade etwas anders als von Ihnen beabsichtigt, ist das Original noch vorhanden.

Die Datei von Hand zu bearbeiten wird unterstützt. Der Prozess liest sie beim Start ein; starten Sie den Container nach einer Bearbeitung außerhalb der Oberfläche also neu. Beachten Sie, dass Änderungen, die während Ihrer Bearbeitung in der Weboberfläche vorgenommen werden, von dem Schreibvorgang überschrieben werden, der zuletzt eintrifft.

Das ist der Teil, bei dem Installationen danebengehen, und er verdient mehr Aufmerksamkeit als der Rest dieser Seite zusammen.

Über welche Schnittstelle ein Multicast-Paket hinausgeht, entscheidet der Kernel anhand der Routing-Tabelle. Das Binden der Quelladresse des UDP-Sockets wählt die ausgehende Schnittstelle nicht aus — eine verbreitete und nachvollziehbare Annahme, aber eine falsche. Entscheidend ist, wohin 224.0.0.0/4 zeigt.

Auf vielen Geräten zeigt die Route auf die Schnittstelle, die der Installateur zufällig zuerst konfiguriert hat — häufig eine, an der kein Kabel steckt. Die Streams werden dann korrekt erzeugt und gehen stillschweigend ins Leere.

Routen Sie den gesamten Multicast-Bereich auf die Schnittstelle, die zu den Fernsehern zeigt:

Terminal-Fenster
# Point the entire multicast range at the TV-facing NIC
sudo ip route replace 224.0.0.0/4 dev eth1
# Verify — this must name the TV-facing interface
ip route get 239.10.0.1

Ein ip route-Befehl ist nicht dauerhaft. Installieren Sie stattdessen die mitgelieferte systemd-Unit und den zugehörigen Timer. Die Unit:

  • wählt die erste verbundene Kandidaten-Schnittstelle, bevorzugt dabei die zu den Fernsehern zeigende Netzwerkkarte und weicht auf die Management-Netzwerkkarte aus, wenn die bevorzugte keinen Link hat;
  • wird vom Timer jede Minute neu ausgewertet, sodass die Route dem Kabel folgt, wenn jemand es umsteckt, statt dass es erst nach einem Neustart bemerkt wird.

sendto() an ein nicht routbares Multicast-Ziel meldet Erfolg. Keine Streaming-Engine kann diesen Zustand erkennen, denn aus ihrer Sicht ist nichts schiefgegangen. Daraus folgt:

  • Der Fehler muss von außerhalb des Streaming-Pfads anhand der Routing-Tabelle geprüft werden — genau das tut Castmux, und deshalb braucht es NET_ADMIN;
  • ein Neustart behebt ihn nie. Anwendung und Watchdog verweigern deshalb beide den Neustart eines Senders wegen eines Routing-Fehlers und lösen stattdessen einen Alarm aus. Eine Neustartschleife gegen eine defekte Route ist bloßer Leerlauf, der die eigentliche Ursache verdeckt.

Das Image deklariert einen Container-Healthcheck. Seien Sie sich im Klaren, was er leistet und was nicht: Reines Docker protokolliert den Gesundheitsstatus und unternimmt nichts daraufhin. Ein Container kann unbegrenzt als „unhealthy“ markiert bleiben, während die Restart-Policy, die nur auf das Beenden des Prozesses reagiert, nichts tut. Orchestrierer handeln auf Basis des Status; docker run --restart tut es nicht.

Das Handeln übernimmt deshalb eine externe Watchdog-Unit. Sie wertet zwei unabhängige Signale aus:

SignalBedeutung und Reaktion
Container-StatusDer Prozess als Ganzes antwortet nicht. Die Reaktion ist ein Neustart des Containers.
UDP-Stille eines einzelnen SendersEin Sender hat aufgehört, Pakete auf die Leitung zu legen, während die übrigen einwandfrei laufen. Die Reaktion ist ein Neustart nur dieses Senders.

Zwei Signale statt einem, weil sie Fehler mit sehr unterschiedlicher Tragweite unterscheiden. Den ganzen Container neu zu starten, um einen hängenden Sender zu reparieren, nimmt für die Dauer des Neustarts und des anschließenden gestaffelten Starts jeden anderen Sender vom Netz — bei zwanzig Sendern rund eine Minute schwarzer Bildschirme, um ein Problem zu beheben, das einen betrifft.

Wegen eines Routing-Fehlers startet der Watchdog aus dem oben genannten Grund nichts neu: Ein Neustart kann keine Route reparieren, er erzeugt also eine Schleife statt einer Behebung.

Zwei Situationen erzeugen eine zweite Instanz, ohne dass es jemand beabsichtigt:

  • Eine übrig gebliebene systemd-Unit aus einer früheren Installation ohne Container. Das Deaktivieren einer Unit verhindert ihren Start beim Booten; es hindert nichts anderes daran, sie zu starten. Verwenden Sie systemctl mask, damit sie sich überhaupt nicht mehr starten lässt.
  • Ein nächtlicher Neustart in der crontab von root. Prüfen Sie das, wenn Sie ein Gerät von einer Paketinstallation auf den Container umstellen. Ein Cron-Eintrag, der systemctl restart auf die alte Unit ausführt, startet eine deaktivierte Unit um drei Uhr morgens anstandslos und beschert Ihnen ein Split Brain, das über Nacht auftaucht und verschwunden ist, bis jemand nachsieht. Noch einmal: maskieren, nicht nur deaktivieren.

Laden Sie das neue Image und erstellen Sie den Container neu. Die Konfiguration wird automatisch vorwärts migriert, fehlende Schlüssel werden ergänzt, und vor jeder strukturellen Änderung wird eine Sicherung .pre-migration geschrieben.

Terminal-Fenster
sudo docker pull castmux/castmux:latest
sudo docker stop castmux && sudo docker rm castmux
# then re-run the same docker run command, or, in /opt/castmux:
sudo docker compose up -d

Die Sender sind für die Dauer des Neustarts und des anschließenden gestaffelten Starts nicht verfügbar — etwa drei Sekunden pro Sender. Planen Sie das Wartungsfenster bei einer großen Installation entsprechend.

Die Migration funktioniert nur vorwärts. Müssen Sie auf ein älteres Image zurück, stellen Sie zusätzlich die Kopie .pre-migration der Konfiguration wieder her; ein älteres Binary, das eine neuere Datei liest, versteht keine Schlüssel, die es bei seiner Erstellung noch nicht gab.

Kopieren Sie das Konfigurationsverzeichnis. Das ist das gesamte Verfahren, und es ist mit Absicht so kurz:

Terminal-Fenster
sudo cp -a /opt/castmux/config /backup/castmux-$(date +%F)

Das Verzeichnis enthält alles, was erhaltenswert ist: die Konfiguration, die Lizenzidentität des Geräts und den gespeicherten Programmführer. Kopieren Sie das Verzeichnis und nicht nur config.yaml, sonst meldet sich ein wiederhergestelltes Gerät als neues, nicht freigegebenes Gerät. Die Wiederherstellung ist derselbe Vorgang in umgekehrter Richtung bei gestopptem Container — siehe Sichern und Wiederherstellen. Sender-Logs sind nicht enthalten, weil sie ein Ringpuffer im Arbeitsspeicher sind und einen Neustart ohnehin nicht überdauern — siehe Die Weboberfläche → Logs.