aea26493db
DER BLOCKER aus dem SAVEPOINT v3.16 ist zu. Beide Installer (Kommandozeile und GUI) fragen GET /worker-setup/pfad-map, pruefen mit Test-Path, ob DIESER PC die Freigabe wirklich erreicht, und schreiben `set RIPPY_PATH_MAP=...` in start-tray.bat und start-worker.bat. Der Commander muss dafuer nichts ueber Container-Pfade wissen - das Feld bleibt leer, der Installer holt den Wert. Von Hand geht es trotzdem (Knopf "Von Rippy holen" bzw. -PfadMap), falls dieser PC die Freigabe anders erreicht. Ist nichts erreichbar, steht das als Klartext im Log samt dem haeufigsten Grund (fehlende Zugangsdaten - Freigabe einmal im Explorer oeffnen). Die Installation laeuft weiter: ein Worker, der sich meldet und ehrlich scheitert, ist besser als einer, der nicht existiert. Ein LEERES RIPPY_PATH_MAP wird bewusst nicht gesetzt - pfad_lokal() liest das als "kein Mapping", und die Fehlermeldung im Worker unterscheidet genau diese beiden Faelle. GUI: neues Feld samt Knopf, Fenster 560->648 px. Layout headless gerendert und angesehen (nichts ueberlappt, Umlaute korrekt), beide Dateien mit echtem PowerShell 5.1 auf Parser-Fehler geprueft, UTF-8-BOM erhalten. .exe neu gebaut. install.ps1 hatte durch ein Werkzeug LF statt CRLF bekommen - zurueckgedreht. remote-transcode-worker.yml richtiggestellt: Dort stand "mount -t nfs <rippy-host>:/srv/rippy" - das geht NICHT, auf der Rippy-Maschine laeuft kein NFS- und kein Samba-Server, sie ist selbst nur Client der NAS. Der Weg, der funktioniert: dieselbe Freigabe einhaengen, die auch Rippy nutzt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
551 lines
26 KiB
PowerShell
551 lines
26 KiB
PowerShell
# Rippy Windows-Worker - GRAFISCHER Installer (WinForms, keine externe Runtime).
|
|
# Wird über rippy-worker-setup.bat gestartet (Doppelklick). Braucht nur
|
|
# Python 3.10+ auf der Maschine - kein Docker, kein git.
|
|
#
|
|
# WICHTIG - KODIERUNG: Diese Datei MUSS als UTF-8 MIT BOM gespeichert werden.
|
|
# PowerShell 5.1 liest .ps1 ohne BOM als ANSI; Umlaute werden dann zu Kauderwelsch
|
|
# UND das Skript wirft einen Parser-Fehler, weil in "ö" ein Anführungszeichen
|
|
# steckt. Mit BOM liest 5.1 korrekt UTF-8 - am 25.07.2026 beidseitig gemessen:
|
|
# ohne BOM: "Größe" + Unerwartetes Token
|
|
# mit BOM: "Größe" + laeuft
|
|
# Nach jeder Änderung pruefen: powershell.exe -Command "Parser::ParseFile(...)"
|
|
|
|
param(
|
|
[string]$RippyHost = "",
|
|
[string]$WorkerName = $env:COMPUTERNAME.ToLower()
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
Add-Type -AssemblyName System.Windows.Forms
|
|
Add-Type -AssemblyName System.Drawing
|
|
|
|
# Standard-Zielverzeichnis: wie jedes andere Programm unter "Programme"
|
|
# (Commander-Wunsch 25.07.2026 - vorher lag der Worker im Benutzerprofil, was
|
|
# für ein Programm unüblich ist). GetFolderPath liefert den ECHTEN Pfad;
|
|
# deutsche Windows-Versionen zeigen im Explorer "Programme" an, der Pfad heißt
|
|
# trotzdem "C:\Program Files".
|
|
#
|
|
# Schreiben dorthin braucht Administratorrechte - deshalb ist die .exe mit
|
|
# -requireAdmin gebaut (build-exe.ps1) und fragt beim Start einmal per UAC.
|
|
# Ohne das wäre hier der nächste "Zugriff verweigert" programmiert.
|
|
$StandardZiel = Join-Path ([Environment]::GetFolderPath("ProgramFiles")) "Rippy Worker"
|
|
$InstallDir = $StandardZiel
|
|
|
|
# --- Farben: dieselben Töne wie das Rippy-Web-UI ---------------------------
|
|
# Übernommen aus docker/ui/src/lib/design.ts bzw. den Tailwind-Klassen des UI,
|
|
# damit der Installer nicht wie ein Fremdkörper wirkt.
|
|
$cBg = [System.Drawing.Color]::FromArgb(15, 23, 42) # slate-900, Fensterfläche
|
|
$cPanel = [System.Drawing.Color]::FromArgb(2, 6, 23) # slate-950, Log + Eingaben
|
|
$cFeld = [System.Drawing.Color]::FromArgb(30, 41, 59) # slate-800, Eingabefelder
|
|
$cText = [System.Drawing.Color]::FromArgb(226, 232, 240) # slate-200
|
|
$cGedaempft= [System.Drawing.Color]::FromArgb(148, 163, 184) # slate-400, Beschriftungen
|
|
$cIndigo = [System.Drawing.Color]::FromArgb(99, 102, 241)
|
|
$cAmber = [System.Drawing.Color]::FromArgb(245, 158, 11)
|
|
$cEmerald = [System.Drawing.Color]::FromArgb(16, 185, 129)
|
|
$cPurple = [System.Drawing.Color]::FromArgb(126, 34, 206)
|
|
|
|
# --- Fenster ---------------------------------------------------------------
|
|
$form = New-Object System.Windows.Forms.Form
|
|
$form.Text = "Rippy Encoding-Worker - Installation"
|
|
$form.ClientSize = New-Object System.Drawing.Size(560, 648)
|
|
$form.StartPosition = "CenterScreen"
|
|
$form.FormBorderStyle = "FixedDialog"
|
|
$form.MaximizeBox = $false
|
|
$form.BackColor = $cBg
|
|
$form.ForeColor = $cText
|
|
$form.Font = New-Object System.Drawing.Font("Segoe UI", 9)
|
|
|
|
# Fenster-Icon: Rippy-Disc zur Laufzeit zeichnen (Indigo-Kreis + weißes Loch)
|
|
# - kein externes .ico nötig. Erscheint in Titelleiste und Taskleiste.
|
|
try {
|
|
$ibmp = New-Object System.Drawing.Bitmap 32, 32
|
|
$ig = [System.Drawing.Graphics]::FromImage($ibmp)
|
|
$ig.SmoothingMode = "AntiAlias"
|
|
$ig.FillEllipse((New-Object System.Drawing.SolidBrush ([System.Drawing.Color]::FromArgb(79, 70, 229))), 1, 1, 30, 30)
|
|
$ig.FillEllipse([System.Drawing.Brushes]::White, 12, 12, 8, 8)
|
|
$form.Icon = [System.Drawing.Icon]::FromHandle($ibmp.GetHicon())
|
|
} catch {}
|
|
|
|
# --- Kopfbereich mit Farbverlauf --------------------------------------------
|
|
# Das Rippy-UI hat oben einen Verlauf amber -> indigo -> purple (siehe
|
|
# FirstRunWizard.tsx). WinForms kann das nicht von sich aus, also wird der
|
|
# Verlauf im Paint-Ereignis gezeichnet - inklusive Disc-Symbol.
|
|
$kopf = New-Object System.Windows.Forms.Panel
|
|
$kopf.Dock = "Top"
|
|
$kopf.Height = 104
|
|
$form.Controls.Add($kopf)
|
|
$kopf.Add_Paint({
|
|
param($absender, $e)
|
|
$flaeche = New-Object System.Drawing.Rectangle 0, 0, $absender.Width, $absender.Height
|
|
$pinsel = New-Object System.Drawing.Drawing2D.LinearGradientBrush(
|
|
$flaeche, $cAmber, $cPurple, [System.Drawing.Drawing2D.LinearGradientMode]::Horizontal)
|
|
# Dreifarbig wie im UI: der Indigo-Ton in der Mitte
|
|
$mischung = New-Object System.Drawing.Drawing2D.ColorBlend 3
|
|
$mischung.Colors = @($cAmber, $cIndigo, $cPurple)
|
|
$mischung.Positions = @(0.0, 0.45, 1.0)
|
|
$pinsel.InterpolationColors = $mischung
|
|
$e.Graphics.FillRectangle($pinsel, $flaeche)
|
|
$pinsel.Dispose()
|
|
|
|
# Disc-Symbol: Ring aussen, weisses Loch in der Mitte
|
|
$e.Graphics.SmoothingMode = "AntiAlias"
|
|
$weiss = New-Object System.Drawing.SolidBrush ([System.Drawing.Color]::FromArgb(235, 255, 255, 255))
|
|
$e.Graphics.FillEllipse($weiss, 24, 28, 48, 48)
|
|
$e.Graphics.FillEllipse((New-Object System.Drawing.SolidBrush $cIndigo), 36, 40, 24, 24)
|
|
$e.Graphics.FillEllipse($weiss, 45, 49, 6, 6)
|
|
$weiss.Dispose()
|
|
|
|
# Texte direkt zeichnen - so bleibt der Verlauf ohne Kästchen sichtbar
|
|
$fTitel = New-Object System.Drawing.Font("Segoe UI", 15, [System.Drawing.FontStyle]::Bold)
|
|
$fUnter = New-Object System.Drawing.Font("Segoe UI", 9)
|
|
$e.Graphics.DrawString("Rippy Encoding-Worker", $fTitel,
|
|
[System.Drawing.Brushes]::White, 88, 26)
|
|
$e.Graphics.DrawString(
|
|
"Diese Maschine übernimmt die Video-Kompression für Rippy." + [char]10 +
|
|
"Gerippt wird weiter auf der Rippy-Hauptmaschine.", $fUnter,
|
|
(New-Object System.Drawing.SolidBrush ([System.Drawing.Color]::FromArgb(225, 255, 255, 255))), 90, 56)
|
|
$fTitel.Dispose(); $fUnter.Dispose()
|
|
})
|
|
|
|
# Rippy-Adresse
|
|
$lblHost = New-Object System.Windows.Forms.Label
|
|
$lblHost.Text = "LAN-IP der Rippy-Maschine (Docker-Host) - NICHT dieser PC:"
|
|
$lblHost.Location = New-Object System.Drawing.Point(24, 120)
|
|
$lblHost.Size = New-Object System.Drawing.Size(500, 18)
|
|
$lblHost.ForeColor = $cGedaempft
|
|
$form.Controls.Add($lblHost)
|
|
|
|
$txtHost = New-Object System.Windows.Forms.TextBox
|
|
$txtHost.Location = New-Object System.Drawing.Point(24, 140)
|
|
$txtHost.Size = New-Object System.Drawing.Size(300, 26)
|
|
$txtHost.Text = $RippyHost
|
|
$txtHost.Font = New-Object System.Drawing.Font("Consolas", 10)
|
|
$txtHost.BackColor = $cFeld
|
|
$txtHost.ForeColor = $cText
|
|
$txtHost.BorderStyle = "FixedSingle"
|
|
$form.Controls.Add($txtHost)
|
|
|
|
# Worker-Name
|
|
$lblName = New-Object System.Windows.Forms.Label
|
|
$lblName.Text = "Name dieses Workers (frei wählbar):"
|
|
$lblName.Location = New-Object System.Drawing.Point(24, 176)
|
|
$lblName.Size = New-Object System.Drawing.Size(500, 18)
|
|
$lblName.ForeColor = $cGedaempft
|
|
$form.Controls.Add($lblName)
|
|
|
|
$txtName = New-Object System.Windows.Forms.TextBox
|
|
$txtName.Location = New-Object System.Drawing.Point(24, 196)
|
|
$txtName.Size = New-Object System.Drawing.Size(300, 26)
|
|
$txtName.Text = $WorkerName
|
|
$txtName.BackColor = $cFeld
|
|
$txtName.ForeColor = $cText
|
|
$txtName.BorderStyle = "FixedSingle"
|
|
$form.Controls.Add($txtName)
|
|
|
|
# Zielverzeichnis - frei wählbar, Standard "Programme"
|
|
$lblPfad = New-Object System.Windows.Forms.Label
|
|
$lblPfad.Text = "Installieren nach:"
|
|
$lblPfad.Location = New-Object System.Drawing.Point(24, 232)
|
|
$lblPfad.Size = New-Object System.Drawing.Size(500, 18)
|
|
$lblPfad.ForeColor = $cGedaempft
|
|
$form.Controls.Add($lblPfad)
|
|
|
|
$txtPfad = New-Object System.Windows.Forms.TextBox
|
|
$txtPfad.Location = New-Object System.Drawing.Point(24, 252)
|
|
$txtPfad.Size = New-Object System.Drawing.Size(374, 26)
|
|
$txtPfad.Text = $StandardZiel
|
|
$txtPfad.Font = New-Object System.Drawing.Font("Consolas", 9)
|
|
$txtPfad.BackColor = $cFeld
|
|
$txtPfad.ForeColor = $cText
|
|
$txtPfad.BorderStyle = "FixedSingle"
|
|
$form.Controls.Add($txtPfad)
|
|
|
|
$btnPfad = New-Object System.Windows.Forms.Button
|
|
$btnPfad.Text = "Ändern ..."
|
|
$btnPfad.Location = New-Object System.Drawing.Point(408, 251)
|
|
$btnPfad.Size = New-Object System.Drawing.Size(116, 28)
|
|
$btnPfad.FlatStyle = "Flat"
|
|
$btnPfad.BackColor = $cFeld
|
|
$btnPfad.ForeColor = $cText
|
|
$btnPfad.FlatAppearance.BorderColor = $cGedaempft
|
|
$form.Controls.Add($btnPfad)
|
|
$btnPfad.Add_Click({
|
|
$dlg = New-Object System.Windows.Forms.FolderBrowserDialog
|
|
$dlg.Description = "Ordner wählen - darin wird 'Rippy Worker' angelegt"
|
|
$dlg.ShowNewFolderButton = $true
|
|
if ($dlg.ShowDialog() -eq "OK") {
|
|
# Der Dialog liefert den ELTERN-Ordner; der Unterordner kommt von uns,
|
|
# damit die Deinstallation nie einen fremden Ordner mitlöscht.
|
|
$txtPfad.Text = (Join-Path $dlg.SelectedPath "Rippy Worker")
|
|
}
|
|
})
|
|
|
|
# Netzwerk-Freigabe fuer die Rohdaten (RIPPY_PATH_MAP)
|
|
#
|
|
# DAS entscheidet, ob dieser Worker ueberhaupt etwas komprimieren kann
|
|
# (Befund 26.07.2026): Rippy schickt ihm Pfade wie /app/media/rippy/<job> -
|
|
# Pfade INNERHALB des Rippy-Containers. Ohne Uebersetzung auf eine Freigabe
|
|
# sieht dieser PC dort nichts, nimmt die Aufgabe an und lehnt sie 182 ms
|
|
# spaeter ab. Der Nutzer soll das nicht verstehen muessen: Rippy hat die
|
|
# Freigabe selbst eingehaengt und kennt sie, der Knopf holt sie.
|
|
$lblMap = New-Object System.Windows.Forms.Label
|
|
$lblMap.Text = "Netzwerk-Freigabe mit den Rohdaten (holt Rippy selbst):"
|
|
$lblMap.Location = New-Object System.Drawing.Point(24, 288)
|
|
$lblMap.Size = New-Object System.Drawing.Size(500, 18)
|
|
$lblMap.ForeColor = $cGedaempft
|
|
$form.Controls.Add($lblMap)
|
|
|
|
$txtMap = New-Object System.Windows.Forms.TextBox
|
|
$txtMap.Location = New-Object System.Drawing.Point(24, 308)
|
|
$txtMap.Size = New-Object System.Drawing.Size(374, 26)
|
|
$txtMap.Font = New-Object System.Drawing.Font("Consolas", 9)
|
|
$txtMap.BackColor = $cFeld
|
|
$txtMap.ForeColor = $cText
|
|
$txtMap.BorderStyle = "FixedSingle"
|
|
$form.Controls.Add($txtMap)
|
|
|
|
$btnMap = New-Object System.Windows.Forms.Button
|
|
$btnMap.Text = "Von Rippy holen"
|
|
$btnMap.Location = New-Object System.Drawing.Point(408, 307)
|
|
$btnMap.Size = New-Object System.Drawing.Size(116, 28)
|
|
$btnMap.FlatStyle = "Flat"
|
|
$btnMap.BackColor = $cFeld
|
|
$btnMap.ForeColor = $cText
|
|
$btnMap.FlatAppearance.BorderColor = $cGedaempft
|
|
$form.Controls.Add($btnMap)
|
|
|
|
$lblMapHilfe = New-Object System.Windows.Forms.Label
|
|
$lblMapHilfe.Text = "Leer lassen und installieren - der Installer holt und prüft den Wert selbst."
|
|
$lblMapHilfe.Location = New-Object System.Drawing.Point(24, 338)
|
|
$lblMapHilfe.Size = New-Object System.Drawing.Size(500, 18)
|
|
$lblMapHilfe.ForeColor = $cGedaempft
|
|
$form.Controls.Add($lblMapHilfe)
|
|
|
|
# Autostart
|
|
$chkAuto = New-Object System.Windows.Forms.CheckBox
|
|
$chkAuto.Text = "Beim Anmelden automatisch starten (Tray-Symbol)"
|
|
$chkAuto.Location = New-Object System.Drawing.Point(24, 364)
|
|
$chkAuto.Size = New-Object System.Drawing.Size(400, 22)
|
|
$chkAuto.Checked = $true
|
|
$chkAuto.ForeColor = $cText
|
|
$form.Controls.Add($chkAuto)
|
|
|
|
# Log-Bereich
|
|
$log = New-Object System.Windows.Forms.TextBox
|
|
$log.Location = New-Object System.Drawing.Point(24, 396)
|
|
$log.Size = New-Object System.Drawing.Size(500, 174)
|
|
$log.Multiline = $true
|
|
$log.ReadOnly = $true
|
|
$log.ScrollBars = "Vertical"
|
|
$log.BackColor = $cPanel
|
|
$log.ForeColor = $cText
|
|
$log.BorderStyle = "FixedSingle"
|
|
$log.Font = New-Object System.Drawing.Font("Consolas", 8.5)
|
|
$form.Controls.Add($log)
|
|
|
|
# Knöpfe
|
|
$btnInstall = New-Object System.Windows.Forms.Button
|
|
$btnInstall.Text = "Installieren"
|
|
$btnInstall.Location = New-Object System.Drawing.Point(24, 584)
|
|
$btnInstall.Size = New-Object System.Drawing.Size(150, 40)
|
|
$btnInstall.BackColor = $cAmber
|
|
$btnInstall.ForeColor = $cBg
|
|
$btnInstall.FlatStyle = "Flat"
|
|
$btnInstall.Font = New-Object System.Drawing.Font("Segoe UI", 10, [System.Drawing.FontStyle]::Bold)
|
|
$form.Controls.Add($btnInstall)
|
|
|
|
$btnStart = New-Object System.Windows.Forms.Button
|
|
$btnStart.Text = "Worker starten"
|
|
$btnStart.Location = New-Object System.Drawing.Point(190, 584)
|
|
$btnStart.Size = New-Object System.Drawing.Size(150, 40)
|
|
$btnStart.BackColor = $cEmerald
|
|
$btnStart.ForeColor = [System.Drawing.Color]::White
|
|
$btnStart.FlatStyle = "Flat"
|
|
$btnStart.Font = New-Object System.Drawing.Font("Segoe UI", 10, [System.Drawing.FontStyle]::Bold)
|
|
$btnStart.Visible = $false
|
|
$form.Controls.Add($btnStart)
|
|
|
|
function Log($text) {
|
|
$log.AppendText($text + "`r`n")
|
|
[System.Windows.Forms.Application]::DoEvents()
|
|
}
|
|
|
|
# --- RIPPY_PATH_MAP: holen und wirklich pruefen -----------------------------
|
|
# Rippy leitet den Wert aus seinen eigenen eingehaengten Freigaben ab
|
|
# (GET /worker-setup/pfad-map) - hier wird nichts geraten. Der Rueckgabewert
|
|
# ist der fertige Wert fuer die .bat-Dateien, oder "" wenn Rippy keine
|
|
# Freigabe hat (dann kann dieser Worker nichts komprimieren, und das muss
|
|
# im Log stehen statt still zu passieren).
|
|
function Hole-PfadMap($rippyHost) {
|
|
try {
|
|
$antwort = Invoke-RestMethod "http://$rippyHost/api/worker-setup/pfad-map" -TimeoutSec 15
|
|
} catch {
|
|
Log "HINWEIS: Rippy nach der Freigabe zu fragen schlug fehl ($($_.Exception.Message))."
|
|
return ""
|
|
}
|
|
if ($antwort.hinweis) { Log $antwort.hinweis }
|
|
if (-not $antwort.mapping) { return "" }
|
|
return [string]$antwort.mapping
|
|
}
|
|
|
|
# Erreicht DIESER PC die Freigaben wirklich? Ohne diese Probe merkt der Nutzer
|
|
# den Fehler erst, wenn ein Job Stunden spaeter scheitert.
|
|
function Pruefe-PfadMap($mapping) {
|
|
$alleDa = $true
|
|
foreach ($paar in ($mapping -split ";")) {
|
|
if ($paar -notmatch "=") { continue }
|
|
$ziel = ($paar -split "=", 2)[1]
|
|
if (Test-Path -LiteralPath $ziel -ErrorAction SilentlyContinue) {
|
|
Log " OK: $ziel ist von hier erreichbar."
|
|
} else {
|
|
$alleDa = $false
|
|
Log " NICHT erreichbar: $ziel"
|
|
}
|
|
}
|
|
if (-not $alleDa) {
|
|
Log " Meist fehlen nur die Zugangsdaten: die Freigabe einmal im Explorer"
|
|
Log " oeffnen und 'Anmeldedaten speichern' anhaken, dann erreicht der"
|
|
Log " Worker sie auch. Die Installation laeuft trotzdem durch."
|
|
}
|
|
return $alleDa
|
|
}
|
|
|
|
# --- Installations-Logik ---------------------------------------------------
|
|
function Do-Install {
|
|
$rHost = $txtHost.Text.Trim()
|
|
$wName = $txtName.Text.Trim()
|
|
if (-not $rHost) { [System.Windows.Forms.MessageBox]::Show("Bitte die LAN-IP der Rippy-Maschine eintragen.", "Fehlt"); return }
|
|
if (-not $wName) { $wName = $env:COMPUTERNAME.ToLower() }
|
|
|
|
# Zielverzeichnis aus dem Feld. script:-Bereich, weil "Worker starten"
|
|
# und die Abschlussmeldung denselben Pfad brauchen.
|
|
$zielEingabe = $txtPfad.Text.Trim()
|
|
if (-not $zielEingabe) { $zielEingabe = $StandardZiel }
|
|
if (-not [System.IO.Path]::IsPathRooted($zielEingabe)) {
|
|
[System.Windows.Forms.MessageBox]::Show(
|
|
"Bitte einen vollständigen Pfad angeben, z. B. C:\Program Files\Rippy Worker.",
|
|
"Pfad unvollständig")
|
|
return
|
|
}
|
|
$script:InstallDir = $zielEingabe
|
|
|
|
$btnInstall.Enabled = $false
|
|
$log.Clear()
|
|
Log "Ziel: $InstallDir"
|
|
|
|
try {
|
|
# 1. Python finden
|
|
$python = $null
|
|
foreach ($k in @("py -3", "python")) {
|
|
try {
|
|
$v = Invoke-Expression "$k --version" 2>&1
|
|
if ("$v" -match "Python 3\.(\d+)" -and [int]$Matches[1] -ge 10) { $python = $k; break }
|
|
} catch {}
|
|
}
|
|
if (-not $python) {
|
|
Log "FEHLER: Python 3.10+ nicht gefunden."
|
|
Log "Installieren mit: winget install Python.Python.3.12"
|
|
Log "Danach diesen Installer erneut starten."
|
|
$btnInstall.Enabled = $true
|
|
return
|
|
}
|
|
Log "Python gefunden: $python"
|
|
|
|
# 2. Ordner + Worker-Code
|
|
# Erst PRUEFEN, ob wir dort überhaupt schreiben dürfen. Die .exe ist mit
|
|
# -requireAdmin gebaut und fragt per UAC, aber wer install-gui.ps1 direkt
|
|
# startet, hat die Rechte nicht - dann soll hier ein brauchbarer Satz
|
|
# stehen und nicht "Zugriff verweigert" aus irgendeiner Tiefe.
|
|
try {
|
|
New-Item -ItemType Directory -Force $InstallDir -ErrorAction Stop | Out-Null
|
|
$probe = Join-Path $InstallDir ".schreibtest"
|
|
Set-Content -Path $probe -Value "x" -ErrorAction Stop
|
|
Remove-Item -Force $probe -ErrorAction SilentlyContinue
|
|
} catch {
|
|
Log "FEHLER: In '$InstallDir' darf nicht geschrieben werden."
|
|
Log ""
|
|
Log "Das ist normal bei Ordnern unter 'Programme' - dafür braucht es"
|
|
Log "Administratorrechte. Zwei Wege:"
|
|
Log " 1. Diesen Installer mit Rechtsklick > 'Als Administrator"
|
|
Log " ausführen' starten (der empfohlene Weg), ODER"
|
|
Log " 2. oben über 'Ändern ...' einen Ordner im eigenen"
|
|
Log " Benutzerprofil wählen - dort geht es ohne Adminrechte."
|
|
$btnInstall.Enabled = $true
|
|
return
|
|
}
|
|
Set-Location $InstallDir
|
|
$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") }
|
|
} catch {}
|
|
|
|
Log "Lade Worker-Code von $rHost ..."
|
|
Invoke-WebRequest "http://$rHost/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")) { Log "Lege Python-Umgebung an ..."; Invoke-Expression "$python -m venv venv" }
|
|
Log "Installiere Python-Abhängigkeiten (kann 1-2 min dauern) ..."
|
|
& .\venv\Scripts\python.exe -m pip install --quiet --upgrade pip
|
|
& .\venv\Scripts\pip.exe install --quiet -r requirements.txt
|
|
& .\venv\Scripts\pip.exe install --quiet pystray pillow
|
|
Log "Abhängigkeiten installiert."
|
|
|
|
# 4. HandBrakeCLI
|
|
if (-not (Test-Path ".\HandBrakeCLI.exe") -and -not (Get-Command HandBrakeCLI.exe -ErrorAction SilentlyContinue)) {
|
|
Log "Lade HandBrakeCLI $HandBrakeVersion (offizielles Release) ..."
|
|
$url = "https://github.com/HandBrake/HandBrake/releases/download/$HandBrakeVersion/HandBrakeCLI-$HandBrakeVersion-win-x86_64.zip"
|
|
Invoke-WebRequest $url -OutFile hb.zip -UseBasicParsing
|
|
Expand-Archive hb.zip -DestinationPath . -Force
|
|
Remove-Item hb.zip
|
|
}
|
|
Log "HandBrake bereit."
|
|
|
|
# 5. Netzwerk-Freigabe klaeren, BEVOR die Start-Skripte geschrieben werden.
|
|
# Ohne RIPPY_PATH_MAP kann dieser Worker grundsaetzlich nicht
|
|
# komprimieren - das war bis zum 26.07.2026 der stille Blocker.
|
|
$mapping = $txtMap.Text.Trim()
|
|
if ($mapping) {
|
|
Log "Freigabe (von Hand eingetragen): $mapping"
|
|
} else {
|
|
Log "Frage Rippy, ueber welche Freigabe die Rohdaten erreichbar sind ..."
|
|
$mapping = Hole-PfadMap $rHost
|
|
if ($mapping) {
|
|
$txtMap.Text = $mapping
|
|
Log "Freigabe von Rippy: $mapping"
|
|
}
|
|
}
|
|
if ($mapping) {
|
|
Log "Pruefe Erreichbarkeit ..."
|
|
[void](Pruefe-PfadMap $mapping)
|
|
} else {
|
|
Log "ACHTUNG: Ohne Freigabe kann dieser Worker NICHTS komprimieren."
|
|
Log " Der Worker wird trotzdem installiert und meldet sich in"
|
|
Log " Rippy - Jobs schlagen aber mit einer Klartext-Meldung fehl."
|
|
}
|
|
|
|
# 6. Start-Skripte + Deinstaller erzeugen
|
|
# RIPPY_PATH_MAP nur setzen, wenn es einen Wert gibt - eine leere
|
|
# Zuweisung in einer .bat ist harmlos, aber ein gesetztes leeres
|
|
# RIPPY_PATH_MAP liest pfad_lokal() als "kein Mapping", und die
|
|
# Fehlermeldung im Worker unterscheidet genau diese beiden Faelle.
|
|
$mapZeile = if ($mapping) { "set RIPPY_PATH_MAP=$mapping`r`n" } else { "" }
|
|
|
|
$trayBat = "@echo off`r`ncd /d `"%~dp0`"`r`nset REDIS_URL=redis://${rHost}:6379/0`r`nset DATABASE_URL=postgresql://rippy:rippy@${rHost}:5432/rippy`r`nset API_URL=http://${rHost}:8000`r`nset WORKER_NAME=$wName`r`nset RIPPY_TRAY_HOST=$rHost`r`n${mapZeile}set PATH=%~dp0;%PATH%`r`nstart `"`" venv\Scripts\pythonw.exe tray.py"
|
|
Set-Content -Path "start-tray.bat" -Value $trayBat -Encoding ASCII
|
|
|
|
$workBat = "@echo off`r`ncd /d `"%~dp0`"`r`nset REDIS_URL=redis://${rHost}:6379/0`r`nset DATABASE_URL=postgresql://rippy:rippy@${rHost}:5432/rippy`r`nset API_URL=http://${rHost}:8000`r`nset WORKER_NAME=$wName`r`n${mapZeile}set PATH=%~dp0;%PATH%`r`nvenv\Scripts\celery.exe -A celery_app worker --loglevel=info -Q transcode --pool=solo -n ${wName}@%%h"
|
|
Set-Content -Path "start-worker.bat" -Value $workBat -Encoding ASCII
|
|
|
|
$uninstall = @"
|
|
param([switch]`$Force)
|
|
if (-not `$Force) { if ([System.Windows.Forms.MessageBox]::Show("Rippy-Worker '$wName' deinstallieren?","Rippy",4) -ne "Yes") { exit } }
|
|
Add-Type -AssemblyName System.Windows.Forms
|
|
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://$rHost/api/workers/$wName" -TimeoutSec 5 | Out-Null } catch {}
|
|
Set-Location (Split-Path `$PSScriptRoot -Parent)
|
|
Remove-Item -Recurse -Force `$PSScriptRoot
|
|
[System.Windows.Forms.MessageBox]::Show("Rippy-Worker deinstalliert.","Rippy")
|
|
"@
|
|
Set-Content -Path "uninstall.ps1" -Value $uninstall -Encoding ASCII
|
|
|
|
# 7. Autostart über den Autostart-ORDNER, nicht über schtasks.
|
|
#
|
|
# Befund 25.07.2026 (auf dem Commander-PC aufgetreten): schtasks /create
|
|
# mit /tn "RippyWorker" legt die Aufgabe im WURZELORDNER der
|
|
# Aufgabenplanung an, und das verlangt Administratorrechte. Der
|
|
# Installer läuft normal ohne. schtasks schrieb deshalb
|
|
# "FEHLER: Zugriff verweigert." nach stderr, und weil oben
|
|
# $ErrorActionPreference = "Stop" steht, wurde daraus ein Abbruch der
|
|
# GESAMTEN Installation - obwohl alles außer dem Autostart fertig war.
|
|
# Der Nutzer sah nur "FEHLER: FEHLER: Zugriff verweigert" samt dem
|
|
# irreführenden Tipp, die IP könne falsch sein.
|
|
#
|
|
# Der Autostart-Ordner braucht NIE Adminrechte, und der Nutzer kann die
|
|
# Verknüpfung dort selbst sehen und löschen. Zwei Fliegen.
|
|
if ($chkAuto.Checked) {
|
|
try {
|
|
# Liegt das Programm unter "Programme", ist es eine Installation
|
|
# für die ganze Maschine - dann gehört der Autostart in den
|
|
# Autostart-Ordner ALLER Benutzer. Das passt auch zur Rechtelage:
|
|
# der Installer läuft dafür ohnehin erhöht, und bei einer
|
|
# Erhöhung über ein FREMDES Administratorkonto wäre der
|
|
# benutzereigene Ordner der des Admins - also der falsche.
|
|
# Bei einer Installation ins eigene Profil bleibt es persönlich.
|
|
$programme = [Environment]::GetFolderPath("ProgramFiles")
|
|
$programmeX86 = [Environment]::GetFolderPath("ProgramFilesX86")
|
|
$maschinenweit = $InstallDir.StartsWith($programme, "OrdinalIgnoreCase") `
|
|
-or ($programmeX86 -and $InstallDir.StartsWith($programmeX86, "OrdinalIgnoreCase"))
|
|
$autostartDir = if ($maschinenweit) {
|
|
[Environment]::GetFolderPath("CommonStartup")
|
|
} else {
|
|
[Environment]::GetFolderPath("Startup")
|
|
}
|
|
$lnkPfad = Join-Path $autostartDir "RippyWorker.lnk"
|
|
$wsh = New-Object -ComObject WScript.Shell
|
|
$lnk = $wsh.CreateShortcut($lnkPfad)
|
|
$lnk.TargetPath = (Join-Path $InstallDir "start-tray.bat")
|
|
$lnk.WorkingDirectory = $InstallDir
|
|
$lnk.Description = "Rippy Encoding-Worker"
|
|
$lnk.Save()
|
|
if ($maschinenweit) {
|
|
Log "Autostart eingerichtet (für alle Benutzer dieser Maschine)."
|
|
} else {
|
|
Log "Autostart eingerichtet (für dich, startet bei der Anmeldung)."
|
|
}
|
|
} catch {
|
|
# Nur eine Warnung: der Worker selbst ist fertig und startbar.
|
|
Log "HINWEIS: Autostart konnte nicht eingerichtet werden ($($_.Exception.Message))."
|
|
Log " Der Worker ist trotzdem fertig installiert und läuft."
|
|
Log " Von Hand: start-tray.bat in den Autostart-Ordner verknüpfen"
|
|
Log " (Windows-Taste + R, dann shell:startup eingeben)."
|
|
}
|
|
}
|
|
|
|
Log ""
|
|
Log "FERTIG! Ordner: $InstallDir"
|
|
Log "Klick 'Worker starten' - er erscheint dann in Rippy unter Einstellungen -> Worker."
|
|
$btnStart.Visible = $true
|
|
$btnInstall.Text = "Neu installieren"
|
|
$btnInstall.Enabled = $true
|
|
} catch {
|
|
Log ""
|
|
Log "FEHLER: $_"
|
|
Log "Tipp: Ist die IP korrekt und die Rippy-Maschine im Heimnetz erreichbar?"
|
|
$btnInstall.Enabled = $true
|
|
}
|
|
}
|
|
|
|
$btnMap.Add_Click({
|
|
$rHost = $txtHost.Text.Trim()
|
|
if (-not $rHost) {
|
|
[System.Windows.Forms.MessageBox]::Show(
|
|
"Bitte zuerst oben die LAN-IP der Rippy-Maschine eintragen.", "Fehlt")
|
|
return
|
|
}
|
|
$m = Hole-PfadMap $rHost
|
|
if ($m) {
|
|
$txtMap.Text = $m
|
|
Log "Freigabe von Rippy: $m"
|
|
[void](Pruefe-PfadMap $m)
|
|
}
|
|
})
|
|
$btnInstall.Add_Click({ Do-Install })
|
|
$btnStart.Add_Click({
|
|
Start-Process -FilePath (Join-Path $InstallDir "start-tray.bat") -WindowStyle Hidden
|
|
[System.Windows.Forms.MessageBox]::Show("Worker gestartet - Symbol erscheint unten rechts neben der Uhr.", "Rippy")
|
|
$form.Close()
|
|
})
|
|
|
|
[void]$form.ShowDialog()
|