feat(install): ein Befehl statt Checkliste - install.sh nimmt die Handarbeit ab
Ampel / ampel (push) Successful in 28s

Commander-Rueckmeldung: "Das Docker Deployment ist mir zu kompliziert." Zu
Recht - die Installation war eine Checkliste aus sechs Schritten, von denen
zwei nur mit Fachwissen zu schaffen waren.

## Neu: sudo ./install.sh

Nimmt genau die Schritte ab, an denen man scheitern konnte:

1. Prueft Voraussetzungen (Linux, Docker, Compose) und nennt bei jedem Mangel
   den Installationsbefehl.
2. FINDET DAS LAUFWERK SELBST. Das war der schlimmste Punkt: MakeMKV braucht
   ZWEI Geraeteknoten, und die sg-Nummer ist je Host anders. Das Skript gleicht
   sie ueber die SCSI-Adresse in /sys ab statt zu raten. Auf der VM
   gegengeprueft: sr0 -> 3:0:0:0, sg1 -> 3:0:0:0, also dasselbe Geraet -
   korrekt als /dev/sr0 + /dev/sg1 erkannt.
3. Legt Ablage- und MakeMKV-Verzeichnis an.
4. Richtet die Mount-Propagation ein UND macht sie per systemd-.mount-Unit
   neustart-fest. Vorher stand in der README nur "reboot-fest persistieren" -
   ohne zu sagen wie; nach einem Reboot scheiterte das Einhaengen von
   NAS-Freigaben aus dem UI stillschweigend.
5. Schreibt die .env, ueberschreibt aber NIE einen bestehenden Wert.
6. Baut und startet, und nennt bei Fehlschlag die drei haeufigsten Ursachen
   mit Diagnosebefehl.

Wiederholbar (mehrfach ausfuehren aendert nichts kaputt) und damit auch der
Update-Weg: git pull && sudo ./install.sh

## Zwei Fallgruben, die beim Testen auffielen - beide meine eigenen

- --nur-pruefen verlangte root und brach ab. Ein Pruef-Modus, der nichts
  aendert, darf daran nicht scheitern - sonst kann man vor der Installation
  nicht nachsehen, ob alles passt. Behoben.
- .env.example hatte OPTICAL_SG=/dev/sg1 UNKOMMENTIERT vorbelegt. Damit haette
  der Installer den erkannten Wert nicht eingetragen ("steht schon drin") und
  auf jedem fremden Host still eine kaputte Konfiguration hinterlassen - genau
  das, was er verhindern soll. Beide Geraetezeilen sind jetzt auskommentiert;
  Compose hat ohnehin Vorgaben. Dazu eine Gegenprobe im Skript: zeigt ein
  wirksamer Wert auf ein Geraet, das es hier nicht gibt (".env von einem
  anderen Rechner"), wird das benannt statt kryptisch von Docker gemeldet.

## README neu aufgebaut

Vorher 293 Zeilen, in denen der Schnellstart zwischen lsscsi, sg-Knoten,
USB-Passthrough und mount --make-rshared begraben war. Jetzt: Installation in
zwei Zeilen oben, dann eine Tabelle "Wenn etwas nicht geht" mit den vier
Faellen, die praktisch alles abdecken. Alles Technische steht darunter in
aufklappbaren Abschnitten - inklusive der Handarbeits-Variante fuer die, die
sie wollen.

NEU und ausdruecklich gewuenscht: Abschnitt "Rippy schneller machen" mit dem
VM-CPU-Typ. Erklaert, warum Virtualisierer eine generische CPU ohne AVX2
geben, was das kostet (gemessene 28-55 h je 4K-Film), die genauen Schritte in
Proxmox (herunterfahren - Hardware/Processors/Type auf 'host' - starten, bzw.
qm set <vmid> --cpu host), warum ein Neustart von innen NICHT genuegt, und wie
man nachprueft: Rippy zeigt die Vektorbefehle seit v3.14 selbst an. Dazu der
Nachteil (keine Live-Migration auf andere CPUs) und die Alternative x86-64-v3
fuer Cluster.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-25 21:59:23 +02:00
parent 1eb1c91dd8
commit ca91e5b693
3 changed files with 664 additions and 272 deletions
Executable
+303
View File
@@ -0,0 +1,303 @@
#!/usr/bin/env bash
#
# Rippy einrichten — ein Befehl, keine Handarbeit.
#
# Das Skript nimmt genau die Schritte ab, an denen man vorher scheitern konnte:
# * Laufwerk finden (BEIDE Geraeteknoten — der sg-Knoten ist je Host anders,
# und ein fest eingetragener Standard ist damit auf fremden Hosts falsch)
# * Ablage- und MakeMKV-Verzeichnis anlegen
# * shared-Mount-Propagation herstellen, falls Docker sie verlangt
# * .env anlegen bzw. fehlende Werte ergaenzen, ohne Bestehendes zu ueberschreiben
# * bauen und starten
#
# Es ist absichtlich WIEDERHOLBAR: mehrfach ausfuehren aendert nichts kaputt.
# Es aendert NIE einen Wert, der schon in der .env steht.
#
# Aufruf: sudo ./install.sh
# sudo ./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}"
FEHLER=0
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' "$*"; }
echo "======================================================================"
echo " Rippy — Einrichtung"
echo "======================================================================"
[ "$NUR_PRUEFEN" = 1 ] && gelb "Nur-Pruefen-Modus: es wird nichts geaendert."
# ---------------------------------------------------------------------------
# 1. Voraussetzungen
# ---------------------------------------------------------------------------
titel "1/6 Voraussetzungen"
if [ "$(uname -s)" != "Linux" ]; then
rot " X Das hier ist 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 grundsaetzlich nicht.)"
exit 1
fi
gruen " OK Linux erkannt ($(uname -r))"
if ! command -v docker >/dev/null 2>&1; then
rot " X Docker ist nicht installiert."
rot " Installation: curl -fsSL https://get.docker.com | sudo sh"
FEHLER=1
else
gruen " OK Docker gefunden ($(docker --version 2>/dev/null | head -1))"
fi
if docker compose version >/dev/null 2>&1; then
COMPOSE="docker compose"
gruen " OK Compose-Plugin gefunden"
elif command -v docker-compose >/dev/null 2>&1; then
COMPOSE="docker-compose"
gelb " ! Nur das alte docker-compose gefunden — funktioniert, ist aber veraltet."
else
rot " X Docker Compose fehlt."
rot " Installation (Debian/Ubuntu): sudo apt install docker-compose-plugin"
FEHLER=1
fi
if [ "$(id -u)" = "0" ]; then
gruen " OK Als root gestartet"
elif [ "$NUR_PRUEFEN" = 1 ]; then
# Der Pruef-Modus aendert nichts und darf deshalb NICHT an root scheitern —
# sonst kann man vor der Installation nicht nachsehen, ob alles passt.
gelb " ! Ohne root — fuer die Pruefung in Ordnung. Die echte Installation"
gelb " braucht sudo (Verzeichnisse unter /srv, Mount-Propagation)."
else
rot " X Bitte mit sudo starten: sudo ./install.sh"
rot " (Verzeichnisse unter /srv und die Mount-Propagation brauchen root.)"
rot " Nur nachsehen, ohne etwas zu aendern: ./install.sh --nur-pruefen"
FEHLER=1
fi
[ "$FEHLER" = 1 ] && { rot ""; rot "Abgebrochen — bitte das Fehlende nachholen."; exit 1; }
# ---------------------------------------------------------------------------
# 2. Laufwerk suchen
# ---------------------------------------------------------------------------
titel "2/6 Optisches Laufwerk"
# MakeMKV spricht Laufwerke ueber die SCSI-Generic-Schicht an und braucht
# deshalb ZWEI Knoten: /dev/srN und den passenden /dev/sgM. Welche sg-Nummer
# dazugehoert, ist je Host anders — deshalb wird sie hier ueber die
# SCSI-Adresse ermittelt statt geraten. Beide Knoten zeigen in /sys auf
# dasselbe Geraeteverzeichnis (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
gruen " OK Laufwerk gefunden: $(echo "$LAUFWERK" | tr -s ' ')"
gruen " $SR + $SG (SCSI-Adresse abgeglichen, nicht geraten)"
else
gelb " ! Kein optisches Laufwerk gefunden."
gelb " Rippy laesst sich trotzdem installieren und kann dann als reine"
gelb " KOMPRIMIER-Maschine fuer andere Rippy-Instanzen dienen."
gelb " Zum Rippen fehlt das Laufwerk. Haeufigste 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*' pruefen, Kabel/Strom."
fi
# ---------------------------------------------------------------------------
# 3. Verzeichnisse
# ---------------------------------------------------------------------------
titel "3/6 Verzeichnisse"
for d in "$MEDIA_DIR" "$MAKEMKV_DIR"; do
if [ -d "$d" ]; then
gruen " OK $d liegt schon da"
elif [ "$NUR_PRUEFEN" = 1 ]; then
gelb " ! $d fehlt (wuerde angelegt)"
else
mkdir -p "$d" && gruen " OK $d angelegt"
fi
done
echo " Ablage: $MEDIA_DIR (hier landen die fertigen Filme)"
echo " MakeMKV-Daten: $MAKEMKV_DIR (Schluesselspeicher, rebuild-fest)"
# ---------------------------------------------------------------------------
# 4. Mount-Propagation
# ---------------------------------------------------------------------------
titel "4/6 Mount-Propagation (fuer NAS-Freigaben aus dem UI)"
# Rippy haengt 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
}
if propagation_ok; then
gruen " OK $MEDIA_DIR ist bereits 'shared' — nichts zu tun"
elif [ "$NUR_PRUEFEN" = 1 ]; then
gelb " ! Propagation fehlt (wuerde eingerichtet)"
else
mount --bind "$MEDIA_DIR" "$MEDIA_DIR" 2>/dev/null
mount --make-rshared "$MEDIA_DIR" 2>/dev/null
if propagation_ok; then
gruen " OK Propagation eingerichtet"
# Neustart-fest machen, sonst ist es nach dem naechsten Reboot wieder weg
# und das Einhaengen aus dem UI scheitert stillschweigend.
UNIT="/etc/systemd/system/$(systemd-escape -p --suffix=mount "$MEDIA_DIR" 2>/dev/null)"
if [ -n "${UNIT:-}" ] && [ ! -e "$UNIT" ] && command -v systemctl >/dev/null 2>&1; then
cat > "$UNIT" <<UNITEOF
[Unit]
Description=Rippy-Ablage als shared mount (fuer 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
systemctl enable "$(basename "$UNIT")" >/dev/null 2>&1 \
&& gruen " OK Neustart-fest gemacht ($(basename "$UNIT"))" \
|| gelb " ! Konnte nicht neustart-fest gemacht werden — nach einem Reboot"
gelb " ggf. dieses Skript erneut ausfuehren."
fi
else
gelb " ! Propagation liess sich nicht einrichten."
gelb " Rippy laeuft trotzdem — nur das Einhaengen von NAS-Freigaben AUS DEM UI"
gelb " funktioniert dann nicht. Freigaben stattdessen auf dem Host einhaengen,"
gelb " z. B. unter $MEDIA_DIR/nas. Betrifft nur klassisches Docker mit"
gelb " SYS_ADMIN — nicht rootless/Podman."
fi
fi
# ---------------------------------------------------------------------------
# 5. .env
# ---------------------------------------------------------------------------
titel "5/6 Konfiguration (.env)"
cd "$(dirname "$0")" || exit 1
if [ ! -f .env ]; then
if [ "$NUR_PRUEFEN" = 1 ]; then
gelb " ! .env fehlt (wuerde aus .env.example erzeugt)"
else
cp .env.example .env && gruen " OK .env aus .env.example erzeugt"
fi
else
gruen " OK .env liegt schon da — bestehende Werte bleiben unangetastet"
fi
# Setzt einen Wert NUR, wenn er noch nicht (unkommentiert) gesetzt ist.
setze_wenn_leer() {
schluessel="$1"; wert="$2"
[ "$NUR_PRUEFEN" = 1 ] && { echo " wuerde setzen: $schluessel=$wert"; return; }
[ -f .env ] || return
if grep -qE "^${schluessel}=.+" .env; then
echo " $schluessel steht schon in der .env — unveraendert gelassen"
else
sed -i "/^#*${schluessel}=/d" .env
printf '%s=%s\n' "$schluessel" "$wert" >> .env
gruen " 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"
echo " API-Keys (TMDB/OMDb) brauchst du hier NICHT einzutragen —"
echo " der Einrichtungs-Assistent im Browser fragt sie ab und prueft sie."
# Gegenprobe: Zeigen die WIRKSAMEN Werte auf Geraete, die es hier gibt? Faengt
# 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.
if [ -f .env ]; then
for paar in "OPTICAL_SR:/dev/sr0" "OPTICAL_SG:/dev/sg1"; do
schluessel="${paar%%:*}"; standard="${paar##*:}"
wirksam=$(grep -E "^${schluessel}=" .env 2>/dev/null | tail -1 | cut -d= -f2-)
[ -n "${wirksam:-}" ] || wirksam="$standard"
if [ ! -e "$wirksam" ]; then
gelb " ! $schluessel zeigt auf $wirksam — dieses Geraet gibt es hier NICHT."
if [ -n "$SR" ]; then
gelb " Gefunden wurde stattdessen: $SR + $SG"
gelb " Trage das in der .env ein (oder loesche die Zeile $schluessel=)."
else
gelb " Es wurde auch kein anderes Laufwerk gefunden — siehe Schritt 2."
fi
fi
done
fi
# ---------------------------------------------------------------------------
# 6. Bauen und starten
# ---------------------------------------------------------------------------
titel "6/6 Bauen und starten"
if [ "$NUR_PRUEFEN" = 1 ]; then
gelb " ! Uebersprungen (Nur-Pruefen-Modus)."
echo ""
gruen "Pruefung fertig. Ohne --nur-pruefen wuerde jetzt gebaut und gestartet."
exit 0
fi
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 haeufigsten 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 laeuft."
echo "======================================================================"
echo ""
echo " Jetzt im Browser oeffnen: 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 ""