Wenn du ErsatzTV mit MPEG-TS und kontinuierlichem Stream betreibst so wie ich, kennst du das Problem vermutlich: Im Hintergrund laufende Streams erzeugen große temporäre Dateien, die die Platte nach und nach zumüllen können. Ein manueller Neustart ist mühsam und ohne Gegenmaßnahme stürzt der ErsatzTV-Service einfach irgendwann ab.
Deshalb stelle ich in nachfolgendem Blogpost mein Guard-Script vor, welches bei zu großem Speicherverbrauch automatisch Transcodingsessions stoppt und damit einen stabilen Betrieb ohne Abstürze ermöglicht.
Nachdem mein ErsatzTV-Service nach einer längeren Streaming-Session auf der VU+ (gestrige Anleitung hier) heute Abend abgestürzt ist und ich den Fehler nach etwas Recherche identifizieren konnte, habe ich kurzerhand ein Script gebaut (ok, ChatGPT hat mir sehr dabei geholfen), welches das Problem recht schick löst.
Aber was macht das Script am Ende überhaupt?
Guard-Script 4 the win!
- Läuft periodisch per systemd-Timer (z. B. jede Minute).
- Prüft die Root-Dateisystem-Auslastung (Fallback).
- Misst die Größe pro Kanal-Transcoding-Ordner (z. B. etv-transcode/500).
- Überschreitet ein Ordner den Schwellwert (z. B. größer 5 GB), wird die Session über die ErsatzTV-API gestoppt.
- Loggt alle Aktionen in /var/log/etv-guard.log.
So bleibt das System sauber, ohne dass Streams unkontrolliert weiter wachsen.
Warum das sinnvoll ist (insb. MPEG-TS & Hintergrund-Streams)
- Bei MPEG-TS gibt es keine Segment-Rotation wie bei HLS; die temporären Dateien wachsen kontinuierlich, solange der Stream läuft.
- Besonders Hintergrund-Streams (nicht aktiv auf der Vu+ getuned) laufen oft unbemerkt weiter und fressen Platz; aktive Foreground-Streams verursachen deutlich weniger Temp-Last.
- Das Script wirkt wie ein Garbage-Collector: erkennt Überlauf → stoppt gezielt die betroffene Session → Temp-Ordner wird freigegeben.
- Fallback stoppt alle Sessions, wenn das Root-FS kritisch wird, damit die Box nicht hart wegstirbt.
Vorbereitung
- ErsatzTV-API erreichbar (Standard: http://localhost:8409).
- Transcoding-Basisordner vorhanden (üblich: /root/.local/share/etv-transcode).
- Nutzer darf ins Log schreiben und die API aufrufen.
Schritt-für-Schritt-Anleitung
1) Script anlegen
Datei /usr/local/bin/etv-guard.sh erstellen:
nano /usr/local/bin/etv-guard.shund mit dem Inhalt füttern:
#!/bin/bash
set -euo pipefail
# --- Einstellungen ---
TRANSCODE_DIR="/root/.local/share/etv-transcode" # Basisordner: <TRANSCODE_DIR>/<channel>
API_BASE="http://localhost:8409" # ErsatzTV-API
THRESHOLD_GB=5 # pro Channel-Ordner: ab dieser Größe stoppen
GLOBAL_FS_PCT=92 # Fallback: wenn Root-FS-Belegung >= %, greife hart ein
MIN_FREE_GB=2 # Alternativ-Fallback: wenn freier Platz < GB
COOLDOWN_SEC=60 # Cooldown zwischen Stop-Befehlen pro Channel
LOGFILE="/var/log/etv-guard.log"
mkdir -p "$(dirname "$LOGFILE")"
now() { date '+%Y-%m-%d %H:%M:%S'; }
log() { echo "[$(now)] $*" | tee -a "$LOGFILE"; }
bytes_to_gb() { awk -v b="$1" 'BEGIN{printf "%.2f", b/1024/1024/1024}'; }
# --- Fallback: Root-FS prüfen ---
USED_PCT=$(df --output=pcent / | tail -1 | tr -dc '0-9')
FREE_GB=$(df --output=avail -B1 / | tail -1)
FREE_GB=$(( FREE_GB / 1024 / 1024 / 1024 ))
log "Check start: Root used=${USED_PCT}% free=${FREE_GB}GB"
if [[ "$USED_PCT" -ge "$GLOBAL_FS_PCT" || "$FREE_GB" -lt "$MIN_FREE_GB" ]]; then
log "WARN Root-FS kritisch (used=${USED_PCT}% free=${FREE_GB}GB) → stoppe ALLE Sessions (Safety Cutoff)."
if [[ -d "$TRANSCODE_DIR" ]]; then
for d in "$TRANSCODE_DIR"/*/; do
[[ -d "$d" ]] || continue
ch=$(basename "$d")
[[ "$ch" =~ ^[0-9]+$ ]] || continue
curl -sS -X DELETE "$API_BASE/api/session/$ch" || true
log "STOP (global) channel=$ch"
sleep 1
done
fi
log "Check end (global cutoff)."
exit 0
fi
# --- Pro Channel prüfen ---
[[ -d "$TRANSCODE_DIR" ]] || { log "Transcode-Verzeichnis fehlt ($TRANSCODE_DIR)"; exit 0; }
for d in "$TRANSCODE_DIR"/*/; do
[[ -d "$d" ]] || continue
ch=$(basename "$d")
[[ "$ch" =~ ^[0-9]+$ ]] || continue
size_bytes=$(du -sb "$d" | awk '{print $1}')
size_gb=$(bytes_to_gb "$size_bytes")
log "Channel $ch size=${size_gb}GB"
if awk "BEGIN{exit !($size_gb >= $THRESHOLD_GB)}"; then
log "WARN channel=$ch dir_size=${size_gb}GB ≥ ${THRESHOLD_GB}GB → stoppe Session über API."
if curl -sS -X DELETE "$API_BASE/api/session/$ch" >/dev/null 2>&1; then
log "OK STOP channel=$ch"
sleep "$COOLDOWN_SEC"
else
log "ERR STOP channel=$ch fehlgeschlagen"
fi
fi
done
log "Check end."
exit 0Rechte setzen:
chmod +x /usr/local/bin/etv-guard.sh2) systemd-Service & Timer
Service /etc/systemd/system/etv-guard.service erstellen:
nano /etc/systemd/system/etv-guard.serviceund mit dem Inhalt füllen:
[Unit]
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/etv-guard.sh
Nice=10Timer erstellen in /etc/systemd/system/etv-guard.timer:
nano /etc/systemd/system/etv-guard.timerUnd mit dem Inhalt füllen:
[Unit]
Description=Run etv-guard every minute
[Timer]
OnBootSec=1min
OnUnitActiveSec=1min
AccuracySec=10s
Unit=etv-guard.service
[Install]
WantedBy=timers.targetDas Script „aktivieren“:
systemctl daemon-reload
systemctl enable --now etv-guard.timer
systemctl list-timers | grep etv-guardTest & Monitoring
- Manuell starten:
systemctl start etv-guard.service- Log prüfen:
tail -n 50 /var/log/etv-guard.log- Timer prüfen:
systemctl list-timers | grep etv-guardMit dem Befehl
tail -f /var/log/etv-guard.logsollten nun regelmäßig neue Einträge zu sehen sein. Sofern das Script einen Stream automatisch neugestartet hat (hier mal im Test auf max. 0,65GB gestellt), sollte es so aussehen:
root@ersatztv:~# tail -f /var/log/etv-guard.log
[2025-09-24 01:39:27] Check start: Root used=5% free=57GB
[2025-09-24 01:39:27] Channel 500 size=0.05GB
[2025-09-24 01:39:27] Channel 501 size=0.64GB
[2025-09-24 01:39:27] Check end.
[2025-09-24 01:40:37] Check start: Root used=5% free=57GB
[2025-09-24 01:40:37] Channel 500 size=0.04GB
[2025-09-24 01:40:37] Channel 501 size=0.68GB
[2025-09-24 01:40:37] WARN channel=501 dir_size=0.68GB ≥ 0.65GB → stoppe Session über API.
[2025-09-24 01:40:37] OK STOP channel=501
[2025-09-24 01:41:37] Check end.
[2025-09-24 01:41:37] Check start: Root used=4% free=57GB
[2025-09-24 01:41:37] Channel 500 size=0.05GB
[2025-09-24 01:41:37] Channel 501 size=0.00GB
[2025-09-24 01:41:37] Check end.Optional: Automatischer Neustart von Kanälen
Theoretisch ließe sich das Script auch noch so erweitern, dass ein gestoppter Kanal direkt wieder per ErsatzTV-API gestartet wird. Dann bleibt der Kanal ohne Umschalt-Verzögerung verfügbar.
In der Praxis beginnt damit aber sofort wieder die Transcoding-Last und die Temp-Dateien wachsen von vorn. Für Vu+-Setups ist ein Neustart meist nicht notwendig, weil die Box beim Umschalten ohnehin einen frischen Stream anfordert. In diesem Fall dauert es einfach einige Sekunden, bis das Bild „kommt“ – aber so what…
Aus meinem täglichen Leben
Mit dem Guard-Script bekommt man am Ende eine robuste „Sicherheitsleine“, die ErsatzTV gerade bei MPEG-TS deutlich stabiler macht. Hintergrund-Streams ballern nicht mehr unkontrolliert die Platte voll, und bei kritischem Füllstand greift der Fallback, bevor es hässlich wird.
Evtl. kommt diese Funktion ja irgendwann in einem ErsatzTV-Update, wobei ich glaube, dass der hier beschriebene Anwendungsfall mit MPEG-TS in Kombination mit der VU+ schon sehr speziell ist.
Vielleicht teste ich auch mal einen anderen Mediaplayer auf der VU+, der feste Segmente wie HLS unterstützt. Denn damit sollte das Problem eigentlich gar nicht erst auftreten. Aber das soll dann laut Forenberichten wiederum nicht so stabil laufen wie MPEG-TS.
Wer mehr Ahnung davon hat, ist gerne eingeladen, sein Wissen per Kommentarfunktion zu teilen. Denn evtl. gibt es ja ein noch besser funktionierendes Setup…