Files
rippy/deploy/worker-windows/install.ps1
T
Hitonabi 87484d6863
Ampel / ampel (push) Successful in 28s
feat(worker): der externe Worker wird erwachsen - Log in Rippy, Anzeige, Slots
Commander 26.07.2026: "der externe Encoder Worker ist ein bisschen duenn - der
koennte noch viel mehr." Drei Punkte, alle am Tray.

1. LOG IN RIPPY STATT TXT-DATEI (ausdruecklich gewuenscht). Das Tray schrieb sein
   Log nach %LOCALAPPDATA% und oeffnete es im Editor - wer wissen wollte, warum
   der Worker nichts tut, musste sich an den PC setzen. Neue Bruecke
   (logbruecke.py) meldet die wichtigen Zeilen nach Rippy, Quelle "w:<name>",
   und die Logs-Seite hat jetzt Knoepfe je Quelle: "was macht mein PC" ist ein
   Klick. Der Filter konnte Quellen schon immer, es gab nur keinen Knopf.

   Durchgelassen wird WENIG und mit Grund: Die Job-Meldungen stehen laengst in
   Rippy (tasks.py schreibt sie selbst). Es fehlte, was DANEBEN passiert und den
   Worker unbrauchbar macht, ohne dass ein Job existiert - hochgefahren oder
   nicht, Verbindung zu Redis/Postgres, Abstuerze. Alles andere fliegt weg:
   Celery ist bei --loglevel=info gespraechig, die logs-Tabelle hat keine
   Aufraeumung, und ein zugemuelltes Log ist so unbrauchbar wie keins. Dazu eine
   Drossel (30 Zeilen/Minute), die MELDET, wieviel sie verschluckt hat.
   Zeilenformat woertlich aus dem laufenden Container abgenommen (Celery 5.4.0).

   Die lokale Datei bleibt - sie ist genau dann die einzige Auskunft, wenn Rippy
   nicht erreichbar ist.

2. DAS TRAY ZEIGT, WAS LAEUFT. Vorher stand dort "laeuft" oder "gestoppt" - auf
   einer Maschine, die stundenlang an einem Film rechnet, ist das keine Auskunft.
   Jetzt Titel, Prozent und Restzeit, geholt von Rippys /jobs. Bewusst dieselbe
   Quelle wie das Dashboard, damit im Tray nicht eine zweite, abweichende
   Schaetzung steht.

   Dazu: Windows schlaeft nicht mehr mitten im Encode ein
   (SetThreadExecutionState, ohne ES_DISPLAY_REQUIRED - der Bildschirm darf
   ausgehen). Die Sperre wird zurueckgenommen, sobald nichts laeuft, und auch bei
   einem harten Ende des Trays - sonst schlaeft der PC nie wieder ein und niemand
   weiss warum.

3. MEHRERE ENCODES GLEICHZEITIG. Der Worker lief fest mit --pool=solo und nahm
   genau EINEN Auftrag an. Der Installer fragt die Zahl jetzt (GUI: Feld neben
   dem Namen, mit der erkannten Kernzahl daneben), Vorbelegung ab 12 Kernen
   zwei, sonst einer: HandBrake nutzt schon alle Kerne, aber x265 skaliert nicht
   linear. Auf Windows gibt es keinen prefork-Pool (kein fork) - deshalb
   --pool=threads, was hier passt, weil die Arbeit ein Kind-Prozess ist und der
   Thread nur wartet.

GUI-Layout headless gerendert und angesehen (nichts ueberlappt, 16 Kerne
korrekt erkannt), beide .ps1 mit echtem PowerShell 5.1 geprueft, BOM und CRLF
erhalten, .exe neu gebaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 14:17:14 +02:00

276 lines
14 KiB
PowerShell

# Rippy: Nativer Windows-Transcode-Worker - Installation OHNE Docker.
#
# Was das Skript tut (alles nach -InstallDir, Standard "C:\Program Files\Rippy Worker"):
# 1. Prüft Python (3.10+), legt ein venv an, installiert die Abhängigkeiten
# 2. Lädt den Worker-Code direkt von deiner Rippy-Instanz (/api/worker-setup/paket)
# 3. Lädt HandBrakeCLI (gepinnte Version, offizielles GitHub-Release),
# falls nicht schon im PATH oder im Ordner vorhanden
# 4. Fragt Rippy, über welche Netzwerk-Freigabe die Rohdaten erreichbar sind,
# und prüft, ob DIESER PC sie wirklich sieht (RIPPY_PATH_MAP)
# 5. Erzeugt start-worker.bat - Doppelklick startet den Worker
#
# Der Worker bedient NUR die Kompressions-Queue (transcode) - gerippt wird
# weiterhin auf der Rippy-Hauptmaschine (dort hängt das Laufwerk).
#
# Aufruf (PowerShell):
# .\install.ps1 -RippyHost 192.168.178.162 [-WorkerName mein-pc] [-Autostart]
#
# -Autostart legt eine Verknüpfung im Autostart-Ordner an (Start bei Anmeldung).
param(
[Parameter(Mandatory = $true)] [string]$RippyHost,
[string]$WorkerName = $env:COMPUTERNAME.ToLower(),
[switch]$Autostart,
# Übersetzung der Rippy-Container-Pfade auf Freigaben, die DIESER PC sieht.
# Leer lassen: dann holt der Installer den Wert von Rippy selbst
# (GET /worker-setup/pfad-map, abgeleitet aus dessen eigenen Mounts).
# Von Hand nur, wenn dieser PC die Freigabe anders erreicht, z. B.
# -PfadMap "/app/media/rippy=Z:\"
[string]$PfadMap = "",
# Wie viele Kompressions-Aufträge dieser PC gleichzeitig annimmt. 0 = nach
# Kernzahl entscheiden (ab 12 Kernen zwei, sonst einer): HandBrake nutzt
# schon alle Kerne, aber x265 skaliert nicht linear — auf einer breiten CPU
# bringt ein zweiter Encode mehr Durchsatz, auf einer schmalen bremst er nur.
[int]$Slots = 0,
# Zielverzeichnis. Standard ist "Programme", wie bei jedem anderen Programm
# (Commander-Wunsch 25.07.2026). Dorthin schreiben braucht Adminrechte -
# PowerShell also "Als Administrator" starten, oder hier einen Ordner im
# eigenen Profil angeben.
[string]$InstallDir = (Join-Path ([Environment]::GetFolderPath("ProgramFiles")) "Rippy Worker")
)
$ErrorActionPreference = "Stop"
# HandBrake-Version: die NEUESTE offizielle (GitHub-Release) - so passt der
# Windows-Worker zu dem, was der Update-Check im UI meldet (früher war hier
# eine feste 1.9.2 hardcodiert, was verwirrte). Fällt auf eine bekannte
# Version zurück, falls die GitHub-API gerade nicht erreichbar ist.
$HandBrakeVersion = "1.11.2"
try {
$rel = Invoke-RestMethod "https://api.github.com/repos/HandBrake/HandBrake/releases/latest" `
-TimeoutSec 15 -Headers @{ "User-Agent" = "rippy-installer" }
if ($rel.tag_name) { $HandBrakeVersion = ([string]$rel.tag_name).TrimStart("v") }
Write-Host "Neueste HandBrake-Version: $HandBrakeVersion"
} catch { Write-Host "GitHub-API nicht erreichbar - nutze HandBrake $HandBrakeVersion" }
$HandBrakeUrl = "https://github.com/HandBrake/HandBrake/releases/download/$HandBrakeVersion/HandBrakeCLI-$HandBrakeVersion-win-x86_64.zip"
Write-Host "== Rippy Windows-Worker Installation ==" -ForegroundColor Cyan
# 1. Python finden (py-Launcher oder python im PATH)
$python = $null
foreach ($kandidat in @("py -3", "python")) {
try {
$version = Invoke-Expression "$kandidat --version" 2>&1
if ("$version" -match "Python 3\.(\d+)") {
if ([int]$Matches[1] -ge 10) { $python = $kandidat; break }
}
} catch {}
}
if (-not $python) {
Write-Host "FEHLER: Python 3.10+ nicht gefunden." -ForegroundColor Red
Write-Host "Installieren mit: winget install Python.Python.3.12 (dann Skript erneut ausführen)"
exit 1
}
Write-Host "Python gefunden: $python"
# 2. Zielordner + Worker-Code von der Rippy-Instanz laden
$ziel = $InstallDir
try {
New-Item -ItemType Directory -Force $ziel -ErrorAction Stop | Out-Null
$probe = Join-Path $ziel ".schreibtest"
Set-Content -Path $probe -Value "x" -ErrorAction Stop
Remove-Item -Force $probe -ErrorAction SilentlyContinue
} catch {
Write-Host ""
Write-Host "FEHLER: In '$ziel' darf nicht geschrieben werden." -ForegroundColor Red
Write-Host "Das ist normal bei Ordnern unter 'Programme'. Zwei Wege:" -ForegroundColor Red
Write-Host " 1. PowerShell als Administrator starten und erneut aufrufen, ODER" -ForegroundColor Red
Write-Host " 2. ein eigenes Ziel angeben, z. B.:" -ForegroundColor Red
Write-Host " -InstallDir `"`$env:LOCALAPPDATA\Rippy Worker`"" -ForegroundColor Red
exit 1
}
Write-Host "Ziel: $ziel"
Set-Location $ziel
Write-Host "Lade Worker-Code von http://$RippyHost/api/worker-setup/paket ..."
Invoke-WebRequest "http://$RippyHost/api/worker-setup/paket" -OutFile worker.zip -UseBasicParsing
Expand-Archive worker.zip -DestinationPath . -Force
Remove-Item worker.zip
# 3. venv + Abhängigkeiten
if (-not (Test-Path "venv")) {
Write-Host "Lege Python-Umgebung an ..."
Invoke-Expression "$python -m venv venv"
}
& .\venv\Scripts\python.exe -m pip install --quiet --upgrade pip
& .\venv\Scripts\pip.exe install --quiet -r requirements.txt
# Nur für den Windows-Worker: Tray-Symbol (Status, Start/Stopp, Beenden)
& .\venv\Scripts\pip.exe install --quiet pystray pillow
Write-Host "Python-Abhängigkeiten installiert."
# 4. HandBrakeCLI besorgen (falls nötig)
$hb = Get-Command HandBrakeCLI.exe -ErrorAction SilentlyContinue
if (-not $hb -and -not (Test-Path ".\HandBrakeCLI.exe")) {
Write-Host "Lade HandBrakeCLI $HandBrakeVersion (offizielles GitHub-Release) ..."
Invoke-WebRequest $HandBrakeUrl -OutFile hb.zip -UseBasicParsing
Expand-Archive hb.zip -DestinationPath . -Force
Remove-Item hb.zip
}
if (-not (Test-Path ".\HandBrakeCLI.exe") -and -not $hb) {
Write-Host "FEHLER: HandBrakeCLI.exe fehlt." -ForegroundColor Red
exit 1
}
Write-Host "HandBrakeCLI bereit."
# 5. Netzwerk-Freigabe klären (RIPPY_PATH_MAP) - der stille Blocker bis 26.07.2026
#
# Rippy schickt dem Worker Pfade wie /app/media/rippy/<job>. Das sind Pfade
# INNERHALB des Rippy-Containers; dieser PC sieht sie nur, wenn sie auf eine
# Netzwerk-Freigabe übersetzt werden. Ohne das nimmt der Worker die Aufgabe an
# und lehnt sie Millisekunden später ab ("Keine Roh-MKVs gefunden") - gemessen
# am Job 95afdc89. Geraten wird nichts: Rippy hat die Freigabe selbst
# eingehängt und kennt ihre Quelle.
if (-not $PfadMap) {
Write-Host "Frage Rippy, über welche Freigabe die Rohdaten erreichbar sind ..."
try {
$antwort = Invoke-RestMethod "http://$RippyHost/api/worker-setup/pfad-map" -TimeoutSec 15
if ($antwort.hinweis) { Write-Host $antwort.hinweis -ForegroundColor Yellow }
if ($antwort.mapping) { $PfadMap = [string]$antwort.mapping }
} catch {
Write-Host "HINWEIS: Abfrage fehlgeschlagen ($($_.Exception.Message))." -ForegroundColor Yellow
}
}
if ($PfadMap) {
Write-Host "Freigabe: $PfadMap"
foreach ($paar in ($PfadMap -split ";")) {
if ($paar -notmatch "=") { continue }
$zielPfad = ($paar -split "=", 2)[1]
if (Test-Path -LiteralPath $zielPfad -ErrorAction SilentlyContinue) {
Write-Host " OK: $zielPfad ist von hier erreichbar." -ForegroundColor Green
} else {
Write-Host " NICHT erreichbar: $zielPfad" -ForegroundColor Yellow
Write-Host " Meist fehlen nur die Zugangsdaten: die Freigabe einmal im" -ForegroundColor Yellow
Write-Host " Explorer öffnen und 'Anmeldedaten speichern' anhaken." -ForegroundColor Yellow
}
}
} else {
Write-Host "ACHTUNG: Ohne Freigabe kann dieser Worker NICHTS komprimieren." -ForegroundColor Yellow
Write-Host " Er wird trotzdem installiert; Jobs schlagen dann mit einer" -ForegroundColor Yellow
Write-Host " Klartext-Meldung fehl statt still zu hängen." -ForegroundColor Yellow
}
# Leeres RIPPY_PATH_MAP nicht setzen: pfad_lokal() liest es als "kein Mapping",
# und die Fehlermeldung im Worker unterscheidet genau diese beiden Fälle.
$mapZeile = if ($PfadMap) { "set RIPPY_PATH_MAP=$PfadMap`r`n" } else { "" }
# Gleichzeitige Aufträge. Auf Windows gibt es KEINEN prefork-Pool (kein fork) —
# `solo` bedient genau einen Auftrag, `threads` mehrere. Das passt hier, weil die
# eigentliche Arbeit ein Kind-Prozess ist (HandBrake) und der Thread nur wartet.
if ($Slots -le 0) {
$Slots = if ([Environment]::ProcessorCount -ge 12) { 2 } else { 1 }
}
$poolArg = if ($Slots -le 1) { "--pool=solo" } else { "--pool=threads --concurrency=$Slots" }
Write-Host "Gleichzeitige Aufträge: $Slots (von $([Environment]::ProcessorCount) Kernen)"
# 6. Start-Skript erzeugen
$bat = @"
@echo off
rem Rippy Windows-Transcode-Worker - verbindet sich mit $RippyHost
cd /d "%~dp0"
set REDIS_URL=redis://${RippyHost}:6379/0
set DATABASE_URL=postgresql://rippy:rippy@${RippyHost}:5432/rippy
set API_URL=http://${RippyHost}:8000
set WORKER_NAME=$WorkerName
set RIPPY_SLOTS=$Slots
${mapZeile}set PATH=%~dp0;%PATH%
venv\Scripts\celery.exe -A celery_app worker --loglevel=info -Q transcode $poolArg -n ${WorkerName}@%%h
"@
Set-Content -Path "start-worker.bat" -Value $bat -Encoding ASCII
# 7. Tray-Start (empfohlen): Symbol neben der Uhr, kein Konsolenfenster
$trayBat = @"
@echo off
rem Rippy-Worker mit Tray-Symbol - verbindet sich mit $RippyHost
cd /d "%~dp0"
set REDIS_URL=redis://${RippyHost}:6379/0
set DATABASE_URL=postgresql://rippy:rippy@${RippyHost}:5432/rippy
set API_URL=http://${RippyHost}:8000
set WORKER_NAME=$WorkerName
set RIPPY_TRAY_HOST=$RippyHost
set RIPPY_SLOTS=$Slots
${mapZeile}set PATH=%~dp0;%PATH%
start "" venv\Scripts\pythonw.exe tray.py
"@
Set-Content -Path "start-tray.bat" -Value $trayBat -Encoding ASCII
Write-Host "start-worker.bat + start-tray.bat erzeugt."
# 8. Deinstaller - MUSS-Kriterium: rückstandsfrei entfernbar
$uninstall = @"
# Rippy-Worker DEINSTALLIEREN: stoppt Prozesse, entfernt Autostart,
# meldet den Worker in Rippy ab und löscht diesen Ordner komplett.
param([switch]`$Force)
if (-not `$Force) {
`$antwort = Read-Host "Rippy-Worker '$WorkerName' wirklich deinstallieren? (j/n)"
if (`$antwort -ne "j") { Write-Host "Abgebrochen."; exit }
}
Get-CimInstance Win32_Process | Where-Object {
`$_.ExecutablePath -like "`$PSScriptRoot*"
} | ForEach-Object { Stop-Process -Id `$_.ProcessId -Force -ErrorAction SilentlyContinue }
Start-Sleep 2
# Autostart-Verknüpfung entfernen - BEIDE möglichen Orte, weil die
# Installation je nach Zielverzeichnis persönlich oder maschinenweit war.
foreach (`$ordner in @("Startup", "CommonStartup")) {
try { Remove-Item -Force (Join-Path ([Environment]::GetFolderPath(`$ordner)) "RippyWorker.lnk") -ErrorAction Stop } catch {}
}
# ... und die geplante Aufgabe aus älteren Installationen, falls vorhanden.
schtasks /delete /tn "RippyWorker" /f 2>`$null | Out-Null
try {
Invoke-RestMethod -Method Delete "http://$RippyHost/api/workers/$WorkerName" -TimeoutSec 5 | Out-Null
Write-Host "Worker-Eintrag in Rippy entfernt."
} catch { Write-Host "Hinweis: Eintrag in Rippy ggf. von Hand löschen (Einstellungen -> Worker)." }
Set-Location (Split-Path `$PSScriptRoot -Parent)
Remove-Item -Recurse -Force `$PSScriptRoot
Write-Host "Rippy-Worker deinstalliert." -ForegroundColor Green
"@
Set-Content -Path "uninstall.ps1" -Value $uninstall -Encoding ASCII
Write-Host "uninstall.ps1 erzeugt."
# 9. Optional: Autostart bei Anmeldung (mit Tray)
#
# Über den Autostart-ORDNER, nicht über schtasks: Eine Aufgabe im
# Wurzelordner der Aufgabenplanung anzulegen verlangt Administratorrechte, und
# der Installer läuft normal ohne. Auf dem Commander-PC scheiterte das am
# 25.07.2026 mit "FEHLER: Zugriff verweigert." - und weil oben
# $ErrorActionPreference = "Stop" steht, riss es die ganze Installation mit,
# obwohl nur der Autostart fehlte. Der Autostart-Ordner braucht nie Adminrechte.
if ($Autostart) {
try {
# Unter "Programme" = Installation für die Maschine, also Autostart für
# alle Benutzer. Sonst persönlich. Siehe install-gui.ps1 für die
# ausführliche Begründung (Rechtelage bei fremdem Admin-Konto).
$programme = [Environment]::GetFolderPath("ProgramFiles")
$maschinenweit = $ziel.StartsWith($programme, "OrdinalIgnoreCase")
$autostartDir = if ($maschinenweit) {
[Environment]::GetFolderPath("CommonStartup")
} else {
[Environment]::GetFolderPath("Startup")
}
$wsh = New-Object -ComObject WScript.Shell
$lnk = $wsh.CreateShortcut((Join-Path $autostartDir "RippyWorker.lnk"))
$lnk.TargetPath = (Join-Path $ziel "start-tray.bat")
$lnk.WorkingDirectory = $ziel
$lnk.Description = "Rippy Encoding-Worker"
$lnk.Save()
Write-Host "Autostart eingerichtet (Verknüpfung im Autostart-Ordner)."
} catch {
Write-Host "HINWEIS: Autostart konnte nicht eingerichtet werden ($($_.Exception.Message))." -ForegroundColor Yellow
Write-Host " Der Worker ist trotzdem fertig installiert." -ForegroundColor Yellow
Write-Host " Von Hand: start-tray.bat in shell:startup verknüpfen." -ForegroundColor Yellow
}
}
Write-Host ""
Write-Host "FERTIG." -ForegroundColor Green
Write-Host " Starten (mit Tray-Symbol): $ziel\start-tray.bat"
Write-Host " Starten (Konsole/Debug): $ziel\start-worker.bat"
Write-Host " Deinstallieren: $ziel\uninstall.ps1"
Write-Host "Der Worker erscheint in Rippy unter Einstellungen -> Worker als '$WorkerName'."