Files
rippy/install.sh
T
Hitonabi e165e2a0d3
Ampel / ampel (push) Failing after 29s
feat: Komplettierung der aktuellen Rippy-Etappe
- Versionsanzeige für den Windows-Worker im UI inkl. Prüfung
- Absicherung der install.sh gegen fehlendes systemd
- Serien-Episoden-Erkennung und Laufzeitabgleich anhand TMDB-Daten (Heuristik)
- Umstellung der Windows-Worker-Installation auf SchTasks (Dienst-Ersatz)
- Kodi-Bibliotheks-Refresh über JSON-RPC integriert
2026-07-26 21:13:27 +02:00

430 lines
17 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Rippy einrichten — ein Befehl, keine Handarbeit.
#
# ## Aufbau: erst ALLES prüfen, dann erst ändern
#
# Das Skript läuft in zwei klar getrennten Phasen:
#
# Phase 1 PRÜFEN — sieht sich alles an und ändert NICHTS.
# (Weiche) — harte Mängel → Abbruch mit Liste, nichts angefasst.
# Phase 2 EINRICHTEN — Verzeichnisse, Mount, .env, bauen, starten.
#
# Warum das wichtig ist (Commander-Einwand 25.07.2026): Die erste Fassung
# prüfte und änderte abwechselnd — Verzeichnisse wurden angelegt, bevor
# überhaupt klar war, ob der Rest durchläuft. Scheiterte etwas in der Mitte,
# blieb ein halb eingerichteter Rechner zurück. Und der Prüf-Modus war als
# `if`-Zweig durch fünf Schritte gefädelt; so etwas läuft mit der Zeit
# zwangsläufig aus dem Ruder, weil jede neue Aktion daran denken müsste.
#
# Jetzt gibt es GENAU EINEN Prüf-Pfad, den beide Modi benutzen. `--nur-pruefen`
# heißt schlicht: nach Phase 1 aufhören. Damit kann die Prüfung nicht mehr etwas
# anderes behaupten als die Installation tut.
#
# Es ist absichtlich WIEDERHOLBAR: mehrfach ausführen ändert nichts kaputt, und
# es ändert NIE einen Wert, der schon in der .env steht.
#
# Aufruf: sudo ./install.sh
# ./install.sh --nur-pruefen (nichts anfassen, nur berichten)
set -u
NUR_PRUEFEN=0
[ "${1:-}" = "--nur-pruefen" ] && NUR_PRUEFEN=1
MEDIA_DIR="${RIPPY_MEDIA_DIR:-/srv/rippy/media}"
MAKEMKV_DIR="${RIPPY_MAKEMKV_DIR:-/srv/rippy/makemkv}"
rot() { printf '\033[31m%s\033[0m\n' "$*"; }
gruen() { printf '\033[32m%s\033[0m\n' "$*"; }
gelb() { printf '\033[33m%s\033[0m\n' "$*"; }
titel() { printf '\n\033[1m%s\033[0m\n' "$*"; }
# Befunde der Prüfphase. PROBLEME verhindern die Installation, HINWEISE nicht.
PROBLEME=""
HINWEISE=""
problem() { PROBLEME="${PROBLEME}$1"$'\n'; rot " X $1"; }
hinweis() { HINWEISE="${HINWEISE}$1"$'\n'; gelb " ! $1"; }
ok() { gruen " OK $1"; }
# Welches Paketwerkzeug hat dieser Host? (Commander-Frage 26.07.2026: „Was wenn
# der Unterbau nicht Debian ist?") Vorher nannte das Skript bei fehlendem
# Compose stur `sudo apt install …` — auf Arch, Fedora oder openSUSE ist das
# schlicht falsch, und der Nutzer stand vor einem Befehl, den es nicht gibt.
#
# Erkannt wird über das VORHANDENE Werkzeug, nicht über /etc/os-release: Ein
# Derivat kann sich anders nennen als sein Unterbau, aber sein Paketwerkzeug
# liegt im PATH. Findet sich keines, wird nichts behauptet.
paket_befehl() {
if command -v apt-get >/dev/null 2>&1; then echo "sudo apt install docker-compose-plugin"
elif command -v dnf >/dev/null 2>&1; then echo "sudo dnf install docker-compose-plugin"
elif command -v yum >/dev/null 2>&1; then echo "sudo yum install docker-compose-plugin"
elif command -v pacman >/dev/null 2>&1; then echo "sudo pacman -S docker-compose"
elif command -v zypper >/dev/null 2>&1; then echo "sudo zypper install docker-compose"
elif command -v apk >/dev/null 2>&1; then echo "sudo apk add docker-cli-compose"
else echo ""
fi
}
cd "$(dirname "$0")" || exit 1
echo "======================================================================"
echo " Rippy — Einrichtung"
echo "======================================================================"
echo " Phase 1 von 2: prüfen. Es wird noch NICHTS geändert."
# ===========================================================================
# PHASE 1 — PRÜFEN. Ab hier bis zur Weiche wird nichts verändert.
# ===========================================================================
# --- 1. Betriebssystem und Docker ------------------------------------------
titel "Prüfung 1/5 Betriebssystem und Docker"
if [ "$(uname -s)" != "Linux" ]; then
problem "Kein Linux. Rippy braucht einen Linux-Host mit Docker."
rot " (Docker Desktop unter Windows/macOS kann keine optischen Laufwerke"
rot " durchreichen — deshalb geht es dort grundsätzlich nicht.)"
else
ok "Linux erkannt ($(uname -r))"
fi
if ! command -v docker >/dev/null 2>&1; then
problem "Docker ist nicht installiert."
rot " Installation: curl -fsSL https://get.docker.com | sudo sh"
else
ok "Docker gefunden ($(docker --version 2>/dev/null | head -1))"
fi
COMPOSE=""
if docker compose version >/dev/null 2>&1; then
COMPOSE="docker compose"
ok "Compose-Plugin gefunden"
elif command -v docker-compose >/dev/null 2>&1; then
COMPOSE="docker-compose"
hinweis "Nur das alte docker-compose gefunden — funktioniert, ist aber veraltet."
else
problem "Docker Compose fehlt."
PAKET_BEFEHL="$(paket_befehl)"
if [ -n "$PAKET_BEFEHL" ]; then
rot " Auf diesem System: $PAKET_BEFEHL"
fi
# Der Weg, der auf JEDER Distribution funktioniert — Dockers eigenes Skript
# bringt das Compose-Plugin mit. Steht immer da, auch wenn oben schon ein
# passender Paketbefehl genannt wurde: Paketnamen wandern, dieses Skript nicht.
rot " Überall gültig: curl -fsSL https://get.docker.com | sudo sh"
fi
if [ "$(id -u)" = "0" ]; then
ok "Als root gestartet"
elif [ "$NUR_PRUEFEN" = 1 ]; then
# Die Prüfung ändert nichts und darf deshalb NICHT an root scheitern —
# sonst kann man vor der Installation nicht nachsehen, ob alles passt.
ok "Ohne root — für die Prüfung völlig in Ordnung"
else
problem "Bitte mit sudo starten: sudo ./install.sh"
rot " (Verzeichnisse unter /srv und die Mount-Propagation brauchen root.)"
rot " Nur nachsehen, ohne etwas zu ändern: ./install.sh --nur-pruefen"
fi
# --- 2. Optisches Laufwerk -------------------------------------------------
titel "Prüfung 2/5 Optisches Laufwerk"
# MakeMKV spricht Laufwerke über die SCSI-Generic-Schicht an und braucht
# deshalb ZWEI Knoten: /dev/srN und den passenden /dev/sgM. Welche sg-Nummer
# dazugehört, ist je Host anders — deshalb wird sie hier über die SCSI-Adresse
# ermittelt statt geraten. Beide Knoten zeigen in /sys auf dasselbe
# Geräteverzeichnis (z. B. 3:0:0:0), das ist der Abgleich.
SR=""
SG=""
LAUFWERK=""
for srpfad in /sys/block/sr*; do
[ -e "$srpfad" ] || continue
srname=$(basename "$srpfad")
adresse=$(basename "$(readlink -f "$srpfad/device" 2>/dev/null)" 2>/dev/null)
[ -n "$adresse" ] || continue
for sgpfad in /sys/class/scsi_generic/sg*; do
[ -e "$sgpfad" ] || continue
if [ "$(basename "$(readlink -f "$sgpfad/device" 2>/dev/null)" 2>/dev/null)" = "$adresse" ]; then
SR="/dev/$srname"
SG="/dev/$(basename "$sgpfad")"
LAUFWERK="$(cat "$srpfad/device/vendor" 2>/dev/null) $(cat "$srpfad/device/model" 2>/dev/null)"
break 2
fi
done
done
if [ -n "$SR" ]; then
ok "Laufwerk gefunden: $(echo "$LAUFWERK" | tr -s ' ')"
gruen " $SR + $SG (SCSI-Adresse abgeglichen, nicht geraten)"
else
hinweis "Kein optisches Laufwerk gefunden — Rippy kann dann nur komprimieren."
gelb " Die Installation läuft trotzdem durch; diese Maschine dient dann"
gelb " als reine KOMPRIMIER-Maschine für andere Rippy-Instanzen."
gelb " Zum Rippen fehlt das Laufwerk. Häufigste Ursachen:"
gelb " - In einer VM: Laufwerk per USB-Passthrough durchreichen"
gelb " (Proxmox: qm set <vmid> -usb0 host=<hersteller>:<produkt>,usb3=1)"
gelb " NICHT als emuliertes CD-ROM (media=cdrom) — das kann keine"
gelb " SCSI-Kommandos und MakeMKV sieht es nie."
gelb " - Auf echter Hardware: 'ls /dev/sr*' prüfen, Kabel/Strom."
fi
# --- 3. Verzeichnisse und Platz -------------------------------------------
titel "Prüfung 3/5 Verzeichnisse und Plattenplatz"
for d in "$MEDIA_DIR" "$MAKEMKV_DIR"; do
if [ -d "$d" ]; then
ok "$d liegt schon da"
else
# Anlegen können wir nur mit root — und ob der Elternpfad beschreibbar ist,
# entscheidet, ob es später klappt. Beides hier schon sagen.
eltern="$(dirname "$d")"
while [ -n "$eltern" ] && [ ! -d "$eltern" ]; do eltern="$(dirname "$eltern")"; done
if [ "$(id -u)" = "0" ] || [ -w "$eltern" ]; then
ok "$d fehlt noch — wird in Phase 2 angelegt"
else
problem "$d fehlt und $eltern ist nicht beschreibbar."
fi
fi
done
FREI_GB=$(df -BG --output=avail "$(dirname "$MEDIA_DIR")" 2>/dev/null | tail -1 | tr -dc '0-9')
if [ -n "${FREI_GB:-}" ]; then
if [ "$FREI_GB" -lt 60 ]; then
hinweis "Nur ${FREI_GB} GB frei. Eine Blu-ray braucht roh rund 40 GB, eine 4K-UHD bis 100 GB."
gelb " Rippy läuft trotzdem — lege die Ablage später besser auf eine"
gelb " NAS-Freigabe (Einstellungen → Speicherziele)."
else
ok "${FREI_GB} GB frei"
fi
fi
# --- 4. Mount-Propagation --------------------------------------------------
titel "Prüfung 4/5 Mount-Propagation (für NAS-Freigaben aus dem UI)"
# Rippy hängt NAS-Freigaben aus dem UI ein. Damit die auch in den anderen
# Containern sichtbar werden, muss der Ablage-Pfad ein 'shared' Mount sein.
# Auf den meisten systemd-Hosts ist / schon rshared und es ist nichts zu tun.
propagation_ok() {
findmnt -no PROPAGATION --target "$MEDIA_DIR" 2>/dev/null | grep -q shared
}
PROPAGATION_NOETIG=0
if propagation_ok; then
ok "$MEDIA_DIR ist bereits 'shared' — nichts zu tun"
elif [ ! -d "$MEDIA_DIR" ]; then
ok "wird nach dem Anlegen des Verzeichnisses geprüft und bei Bedarf gesetzt"
PROPAGATION_NOETIG=1
else
ok "Propagation fehlt — wird in Phase 2 eingerichtet"
PROPAGATION_NOETIG=1
fi
# --- 5. Konfiguration ------------------------------------------------------
titel "Prüfung 5/5 Konfiguration (.env)"
if [ ! -f .env ]; then
if [ -f .env.example ]; then
ok ".env fehlt — wird in Phase 2 aus .env.example erzeugt"
else
problem ".env.example fehlt. Ist das wirklich das Rippy-Repo?"
fi
else
ok ".env liegt schon da — bestehende Werte bleiben unangetastet"
fi
# Was in Phase 2 in die .env geschrieben würde — hier nur ANSAGEN.
wirksamer_wert() {
schluessel="$1"; standard="$2"
wert=$(grep -E "^${schluessel}=" .env 2>/dev/null | tail -1 | cut -d= -f2-)
[ -n "${wert:-}" ] && printf '%s' "$wert" || printf '%s' "$standard"
}
for paar in "OPTICAL_SR:$SR" "OPTICAL_SG:$SG" "MAKEMKV_DATA_HOST:$MAKEMKV_DIR"; do
schluessel="${paar%%:*}"; erkannt="${paar#*:}"
if grep -qE "^${schluessel}=.+" .env 2>/dev/null; then
echo " $schluessel steht schon in der .env — bleibt unverändert"
elif [ -n "$erkannt" ]; then
echo " $schluessel wird auf $erkannt gesetzt"
fi
done
# Gegenprobe: Zeigen die WIRKSAMEN Gerätepfade auf etwas, das es hier gibt?
# Fängt den Fall "'.env' von einem anderen Rechner kopiert" und Tippfehler ab —
# sonst startet Docker mit einem Pfad, den es nicht gibt, und die Meldung ist
# kryptisch.
for paar in "OPTICAL_SR:/dev/sr0" "OPTICAL_SG:/dev/sg1"; do
schluessel="${paar%%:*}"; standard="${paar##*:}"
# Nur prüfen, was auch wirklich gelten wird
if grep -qE "^${schluessel}=.+" .env 2>/dev/null; then
wirksam=$(wirksamer_wert "$schluessel" "$standard")
elif [ -n "$SR" ]; then
continue # wird gleich korrekt gesetzt
else
wirksam="$standard"
fi
if [ ! -e "$wirksam" ]; then
hinweis "$schluessel zeigt auf $wirksam — dieses Gerät gibt es hier NICHT."
if [ -n "$SR" ]; then
gelb " Gefunden wurde stattdessen: $SR + $SG"
gelb " Trage das in der .env ein (oder lösche die Zeile $schluessel=)."
fi
fi
done
echo " API-Keys (TMDB/OMDb) brauchst du hier NICHT einzutragen —"
echo " der Einrichtungs-Assistent im Browser fragt sie ab und prüft sie."
# ===========================================================================
# WEICHE — hier wird entschieden. Bis hierher wurde NICHTS verändert.
# ===========================================================================
titel "Ergebnis der Prüfung"
ANZ_PROBLEME=$(printf '%s' "$PROBLEME" | grep -c . || true)
ANZ_HINWEISE=$(printf '%s' "$HINWEISE" | grep -c . || true)
if [ "$ANZ_PROBLEME" -gt 0 ]; then
rot " $ANZ_PROBLEME Punkt(e) verhindern die Installation:"
printf '%s' "$PROBLEME" | grep . | while IFS= read -r z; do rot " - $z"; done
rot ""
rot " Es wurde NICHTS verändert. Bitte das Obige nachholen und erneut starten."
exit 1
fi
if [ "$ANZ_HINWEISE" -gt 0 ]; then
gelb " Alles Wichtige ist da. $ANZ_HINWEISE Hinweis(e), die dich nicht aufhalten:"
printf '%s' "$HINWEISE" | grep . | while IFS= read -r z; do gelb " - $z"; done
else
gruen " Alles in Ordnung, keine Einschränkungen."
fi
if [ "$NUR_PRUEFEN" = 1 ]; then
echo ""
gruen "Prüfung fertig — es wurde nichts verändert."
echo "Zum Installieren: sudo ./install.sh"
exit 0
fi
# ===========================================================================
# PHASE 2 — EINRICHTEN. Erst ab hier wird etwas verändert.
# ===========================================================================
echo ""
echo "======================================================================"
echo " Phase 2 von 2: einrichten"
echo "======================================================================"
titel "Schritt 1/4 Verzeichnisse anlegen"
for d in "$MEDIA_DIR" "$MAKEMKV_DIR"; do
if [ -d "$d" ]; then
ok "$d war schon da"
else
mkdir -p "$d" && ok "$d angelegt"
fi
done
echo " Ablage: $MEDIA_DIR (hier landen die fertigen Filme)"
echo " MakeMKV-Daten: $MAKEMKV_DIR (Schlüsselspeicher, rebuild-fest)"
titel "Schritt 2/4 Mount-Propagation einrichten"
if propagation_ok; then
ok "war schon 'shared' — nichts zu tun"
elif [ "$PROPAGATION_NOETIG" = 1 ]; then
mount --bind "$MEDIA_DIR" "$MEDIA_DIR" 2>/dev/null
mount --make-rshared "$MEDIA_DIR" 2>/dev/null
if propagation_ok; then
ok "Propagation eingerichtet"
# Neustart-fest machen, sonst ist es nach dem nächsten Reboot wieder weg
# und das Einhängen aus dem UI scheitert stillschweigend.
if command -v systemd-escape >/dev/null 2>&1 && command -v systemctl >/dev/null 2>&1; then
UNIT="/etc/systemd/system/$(systemd-escape -p --suffix=mount "$MEDIA_DIR")"
if [ ! -e "$UNIT" ]; then
cat > "$UNIT" <<UNITEOF
[Unit]
Description=Rippy-Ablage als shared mount (für NAS-Freigaben aus dem UI)
After=local-fs.target
[Mount]
What=$MEDIA_DIR
Where=$MEDIA_DIR
Type=none
Options=bind,rshared
[Install]
WantedBy=multi-user.target
UNITEOF
systemctl daemon-reload 2>/dev/null
if systemctl enable "$(basename "$UNIT")" >/dev/null 2>&1; then
ok "neustart-fest gemacht ($(basename "$UNIT"))"
else
gelb " ! Konnte nicht neustart-fest gemacht werden — nach einem Reboot"
gelb " dieses Skript einfach erneut ausführen."
fi
fi
else
gelb " ! Kein systemd gefunden — Mount-Propagation ist nicht neustart-fest."
gelb " Nach einem Reboot dieses Skript einfach erneut ausführen."
fi
else
gelb " ! Propagation ließ sich nicht einrichten."
gelb " Rippy läuft trotzdem — nur das Einhängen von NAS-Freigaben AUS DEM UI"
gelb " funktioniert dann nicht. Freigaben stattdessen auf dem Host einhängen,"
gelb " z. B. unter $MEDIA_DIR/nas. Betrifft nur klassisches Docker mit"
gelb " SYS_ADMIN — nicht rootless/Podman."
fi
fi
titel "Schritt 3/4 Konfiguration schreiben"
if [ ! -f .env ]; then
cp .env.example .env && ok ".env aus .env.example erzeugt"
fi
# Setzt einen Wert NUR, wenn er noch nicht (unkommentiert) gesetzt ist.
setze_wenn_leer() {
schluessel="$1"; wert="$2"
[ -f .env ] || return
if grep -qE "^${schluessel}=.+" .env; then
echo " $schluessel steht schon in der .env — unverändert gelassen"
else
sed -i "/^#*${schluessel}=/d" .env
printf '%s=%s\n' "$schluessel" "$wert" >> .env
ok "$schluessel=$wert eingetragen"
fi
}
if [ -n "$SR" ]; then
setze_wenn_leer OPTICAL_SR "$SR"
setze_wenn_leer OPTICAL_SG "$SG"
fi
setze_wenn_leer MAKEMKV_DATA_HOST "$MAKEMKV_DIR"
titel "Schritt 4/4 Bauen und starten"
echo " Der erste Bau dauert je nach Internet 3-10 Minuten. Nur beim ersten Mal."
echo ""
if ! $COMPOSE up -d --build; then
rot ""
rot "Der Start ist gescheitert. Die häufigsten Ursachen:"
rot " - Kein Platz auf der Platte: df -h /"
rot " - Port 80 schon belegt: ss -tlnp | grep ':80 '"
rot " - Download von makemkv.com gedrosselt (Cloudflare): Tarballs von"
rot " makemkv.com/download nach docker/worker/vendor/ legen, dann erneut."
rot "Die volle Meldung steht oben. Logs: $COMPOSE logs --tail=50"
exit 1
fi
IP=$(ip -4 route get 1.1.1.1 2>/dev/null | grep -oE 'src [0-9.]+' | awk '{print $2}')
[ -n "${IP:-}" ] || IP="<ip-dieses-rechners>"
echo ""
echo "======================================================================"
gruen " Fertig. Rippy läuft."
echo "======================================================================"
echo ""
echo " Jetzt im Browser öffnen: http://$IP"
echo ""
echo " Dort startet der Einrichtungs-Assistent. Er zeigt dir, ob Laufwerk,"
echo " Worker und Platz in Ordnung sind, und stellt die Kompression passend"
echo " zu deiner CPU ein. Mehr ist nicht zu tun."
echo ""
echo " Status ansehen: $COMPOSE ps"
echo " Logs ansehen: $COMPOSE logs -f"
echo " Aktualisieren: git pull && sudo ./install.sh"
echo ""