Files
2026-07-11 05:40:27 +02:00

336 lines
9.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`:
```bash
/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:
```bash
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:
```bash
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:
```yaml
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
```bash
cd /opt/nginx
docker compose up -d
```
### 4. Funktion prüfen
```bash
# 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`:
```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`)
1. `df -h /downloads` liefert Belegung des gemounteten Pfads
2. Werte werden als JSON nach `/html/disk.json` geschrieben
3. 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
```yaml
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 **kein** `entrypoint` setzen
- `command` als Array: `["/bin/sh", "/update-disk.sh"]`
- Die `while`-Schleife **nicht** in `command` der Compose-Datei definieren
### Was nicht funktioniert
Diese Variante führt zu Syntaxfehlern, weil YAML/Docker den Befehl an Semikolons zerlegt:
```yaml
# ❌ 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:
```javascript
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:**
```bash
tail -5 /opt/nginx/scripts/update-disk.sh
```
Am Ende muss stehen:
```sh
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](#deployment)) oder per `cat > ... << 'EOF'` direkt auf dem Server anlegen, dann Container neu erstellen:
```bash
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“
```bash
# 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/ssd` existiert nicht oder ist nicht gemountet
- Volume-Mapping in `docker-compose.yml` prüfen
### Nginx liefert `disk.json`, Werte ändern sich aber nicht
1. `docker compose ps diskinfo` → Status muss `Up` sein (nicht `Restarting`)
2. `docker compose logs diskinfo` → regelmäßige `disk.json updated`-Zeilen
3. `watch -n 5 cat /opt/nginx/html/disk.json``updated_at` muss sich ändern
## Wartung
```bash
# 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 `/downloads` nur lesend; Schreibzugriff erfolgt ausschließlich über `diskinfo` auf `/html`
- Service-Status (grüner Punkt) nutzt `fetch` mit `no-cors` und kann je nach Browser/Dienst ungenau sein
- Apple Touch Icons (`apple-touch-icon.png` etc.) sind nicht vorhanden → 404 in den nginx-Logs (harmlos)