9.3 KiB
Media Server Dashboard
Statisches Homelab-Dashboard mit Live-Speicheranzeige für einen gemounteten Pfad (/downloads). Die Weboberfläche listet Media-Server-Dienste als Kacheln und zeigt den aktuellen Speicherplatz des Download-Laufwerks an.
Übersicht
| Komponente | Aufgabe |
|---|---|
| nginx | Liefert die statische Website (index.html) und die Datei disk.json aus |
| diskinfo | Liest per df den Speicher des gemounteten Pfads und schreibt alle 30 Sekunden disk.json |
| Browser | Lädt disk.json per Javalescript und aktualisiert die Speicheranzeige |
┌─────────────┐ GET /disk.json ┌─────────────┐
│ Browser │ ◄────────────────────── │ nginx │
└─────────────┘ │ (Port 80) │
└──────▲──────┘
│ liest
/opt/nginx/html/disk.json
▲
│ schreibt alle 30s
┌──────┴──────┐
│ diskinfo │
│ (alpine) │
└──────┬──────┘
│ df -h
/mnt/ssd → /downloads
Projektstruktur
.
├── docker-compose.yml # Container-Definition (nginx + diskinfo)
├── html/
│ ├── index.html # Dashboard (Apps + Speicheranzeige)
│ ├── disk.json # Wird vom diskinfo-Container geschrieben
│ ├── filebot-media-browser.svg
│ ├── redirect.html
│ └── bk_index.htm # Backup der alten index.html
└── scripts/
└── update-disk.sh # Speicher auslesen + Endlosschleife
Voraussetzungen
- Docker und Docker Compose auf dem Host
- Gemounteter Speicherpfad auf dem Host (Standard:
/mnt/ssd) - Port 80 frei
Deployment
1. Dateien auf den Server kopieren
Auf dem Server liegt das Projekt typischerweise unter /opt/nginx:
/opt/nginx/
├── docker-compose.yml
├── html/
└── scripts/
└── update-disk.sh
Dateien aus diesem Repository dorthin kopieren, z. B. per git clone, rsync oder scp.
Alternativ kann update-disk.sh direkt auf dem Server angelegt werden:
cat > /opt/nginx/scripts/update-disk.sh << 'EOF'
#!/bin/sh
MOUNT="/downloads"
OUTPUT="/html/disk.json"
update_disk() {
LINE=$(df -h "$MOUNT" 2>/dev/null | awk 'NR==2')
if [ -z "$LINE" ]; then
echo "ERROR: df failed for $MOUNT" >&2
return 1
fi
TOTAL=$(echo "$LINE" | awk '{print $2}')
USED=$(echo "$LINE" | awk '{print $3}')
AVAILABLE=$(echo "$LINE" | awk '{print $4}')
USE_PERCENT=$(echo "$LINE" | awk '{print $5}')
cat > "$OUTPUT" <<JSON
{
"total": "$TOTAL",
"used": "$USED",
"available": "$AVAILABLE",
"use_percent": "$USE_PERCENT",
"updated_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
}
JSON
echo "disk.json updated: $USE_PERCENT used ($USED / $TOTAL)"
}
while true; do
update_disk || true
sleep 30
done
EOF
Vorher sicherstellen, dass das Verzeichnis existiert:
mkdir -p /opt/nginx/scripts
2. Pfade in docker-compose.yml anpassen
Im Repository sind relative Pfade (./html, ./scripts) gesetzt. Auf dem Server können absolute Pfade verwendet werden:
services:
nginx:
volumes:
- /opt/nginx/html:/usr/share/nginx/html:ro
- /mnt/ssd:/downloads:ro
diskinfo:
volumes:
- /mnt/ssd:/downloads:ro
- /opt/nginx/html:/html
- /opt/nginx/scripts/update-disk.sh:/update-disk.sh:ro
Wichtig: Der Host-Pfad /mnt/ssd muss auf das Laufwerk zeigen, dessen Speicher angezeigt werden soll. Beide Container mounten ihn als /downloads (nur lesend).
3. Container starten
cd /opt/nginx
docker compose up -d
4. Funktion prüfen
# Container-Status
docker compose ps
# diskinfo-Logs (sollte alle ~30s eine Zeile ausgeben)
docker compose logs -f diskinfo
# Generierte JSON-Datei
cat /opt/nginx/html/disk.json
Erwartete Log-Ausgabe:
diskinfo | disk.json updated: 48% used (213.7G / 468.2G)
Erwarteter Inhalt von disk.json:
{
"total": "468.2G",
"used": "213.7G",
"available": "254.5G",
"use_percent": "48%",
"updated_at": "2026-06-05T12:08:16Z"
}
Das Feld updated_at dient zur Kontrolle, ob die Datei regelmäßig aktualisiert wird.
Wie die Speicheranzeige funktioniert
Backend (scripts/update-disk.sh)
df -h /downloadsliefert Belegung des gemounteten Pfads- Werte werden als JSON nach
/html/disk.jsongeschrieben - Eine
while true-Schleife wiederholt das alle 30 Sekunden
Die Schleife liegt bewusst im Shell-Skript und nicht in der docker-compose.yml. So werden YAML-Parsing-Probleme (z. B. in Portainer) vermieden.
Frontend (html/index.html)
- Beim Laden der Seite:
fetch('/disk.json') - Alle 30 Sekunden: erneuter Abruf per
setInterval - Anzeige: Prozent, Belegt, Frei, Gesamt sowie Fortschrittsbalken
Docker Compose – Referenz
services:
nginx:
image: nginx:latest
container_name: nginx
restart: unless-stopped
ports:
- 80:80
volumes:
- ./html:/usr/share/nginx/html:ro
- /mnt/ssd:/downloads:ro
diskinfo:
image: alpine:latest
container_name: diskinfo
restart: unless-stopped
volumes:
- /mnt/ssd:/downloads:ro
- ./html:/html
- ./scripts/update-disk.sh:/update-disk.sh:ro
command: ["/bin/sh", "/update-disk.sh"]
Hinweise:
- Beim
diskinfo-Service keinentrypointsetzen commandals Array:["/bin/sh", "/update-disk.sh"]- Die
while-Schleife nicht incommandder Compose-Datei definieren
Was nicht funktioniert
Diese Variante führt zu Syntaxfehlern, weil YAML/Docker den Befehl an Semikolons zerlegt:
# ❌ Nicht verwenden
entrypoint: ["/bin/sh", "-c"]
command: while true; do sh /update-disk.sh; sleep 30; done
Fehler im Log:
diskinfo | true: line 0: syntax error: unexpected end of file (expecting "do")
Apps anpassen
Die Dienste-Kacheln werden in html/index.html im apps-Array konfiguriert:
const apps = [
{ name: "Dockage", port: 5001, icon: "...", color: "#3498db" },
{ name: "SabNZBd", port: 8080, icon: "...", color: "#3498db" },
// ...
];
| Feld | Beschreibung |
|---|---|
name |
Anzeigename |
port |
Port auf dem Host (IP = window.location.hostname) |
icon |
URL oder Pfad zum Icon |
color |
Kachel-Hintergrundfarbe |
protocol |
Optional: http (Standard) oder https |
vnc |
Optional: VNC-Badge und Hinweis-Toast |
extra |
Optional: URL-Suffix (z. B. für Krusader VNC) |
Nach Änderungen an index.html reicht ein Browser-Reload; kein Container-Neustart nötig.
Fehlerbehebung
diskinfo exited with code 0 (ständig neu startend)
Ursache: Alte Version von update-disk.sh ohne while true-Schleife. Das Skript läuft einmal durch und beendet sich.
Prüfen:
tail -5 /opt/nginx/scripts/update-disk.sh
Am Ende muss stehen:
while true; do
update_disk || true
sleep 30
done
Lösung: Aktuelles Skript aus diesem Repository nach /opt/nginx/scripts/update-disk.sh kopieren (siehe Deployment) oder per cat > ... << 'EOF' direkt auf dem Server anlegen, dann Container neu erstellen:
docker compose up -d --force-recreate diskinfo
disk.json bleibt bei 93 Bytes / alte Werte
- Altes JSON-Format ohne
updated_at→ Skript auf dem Server ist veraltet - Neues Format ist ca. 130+ Bytes groß
Speicheranzeige zeigt „Fehler“
# Mount im Container prüfen
docker compose exec diskinfo df -h /downloads
# Schreibrechte prüfen
docker compose exec diskinfo sh -c 'touch /html/test && rm /html/test && echo OK'
diskinfo zeigt ERROR: df failed for /downloads
- Host-Pfad
/mnt/ssdexistiert nicht oder ist nicht gemountet - Volume-Mapping in
docker-compose.ymlprüfen
Nginx liefert disk.json, Werte ändern sich aber nicht
docker compose ps diskinfo→ Status mussUpsein (nichtRestarting)docker compose logs diskinfo→ regelmäßigedisk.json updated-Zeilenwatch -n 5 cat /opt/nginx/html/disk.json→updated_atmuss sich ändern
Wartung
# Container neu starten
docker compose restart
# Nur diskinfo neu erstellen (nach Skript-Update)
docker compose up -d --force-recreate diskinfo
# Logs anzeigen
docker compose logs -f
# Container stoppen
docker compose down
Bekannte Einschränkungen
- Die Speicheranzeige bezieht sich auf das Filesystem des Mounts, nicht auf den Inhalt eines Unterordners
- nginx mountet
/downloadsnur lesend; Schreibzugriff erfolgt ausschließlich überdiskinfoauf/html - Service-Status (grüner Punkt) nutzt
fetchmitno-corsund kann je nach Browser/Dienst ungenau sein - Apple Touch Icons (
apple-touch-icon.pngetc.) sind nicht vorhanden → 404 in den nginx-Logs (harmlos)