6438beec05
Ampel / ampel (push) Successful in 31s
Zwei Meldungen, beide auf dieselbe Wurzel zurueckgefuehrt: Der Worker startet
Konsolenprogramme, und das hat unter Windows Nebenwirkungen.
1. "Es geht immer alle paar Sekunden ne CMD auf." Kein Geist, sondern der
HERZSCHLAG. Der Worker laeuft als pythonw.exe, also ohne eigene Konsole. Wer
ohne Konsole ein Konsolenprogramm startet, bekommt von Windows eine NEUE - und
die ist sichtbar. `capture_output=True` hilft nicht: es leitet die Datenstroeme
um, unterdrueckt aber kein Fenster. Und caps.py fragt jede Minute
`HandBrakeCLI --help`, `--preset-list` und `--version` ab, dazu makemkvcon aus
der Schluessel-Automatik: vier bis fuenf Fenster pro Minute, in Schueben.
Neues winlauf.py haelt CREATE_NO_WINDOW an EINER Stelle; alle elf Aufrufe in
caps.py, ripping.py und schluessel.py benutzen es. Auf Linux ist der Wert 0 und
creationflags=0 eine Nulloperation - im Worker-Container gegengeprueft, damit
derselbe Code auf beiden Seiten laeuft.
2. Die 7200 Sekunden sind CELERYS EIGENE UMRECHNUNG, nicht die Uhren. Erst fiel
auf, dass die Sekundenbruchteile nur 40 ms auseinanderliegen (.177269 vs
.134799) - derselbe Augenblick, zweimal verschieden gerechnet. Die Quelle sagt
warum (celery/utils/time.py):
def utcoffset(): # Sekunden WEST von UTC, in Stunden
return time.altzone // 3600 # CEST -> -2 ; UTC -> 0
def adjust_timestamp(ts, offset, here=utcoffset):
return ts - (offset - here()) * 3600
Absender VM (offset 0), Empfaenger Windows-PC (here() = -2):
ts - (0 - (-2)) * 3600 = ts - 7200. Celery verschiebt den empfangenen Stempel
also selbst und vergleicht ihn dann mit der eigenen Uhr. Die Annahme dahinter -
Ereignisse truegen Ortszeit - stimmt nicht, `Event()` stempelt Epoch.
Abhilfe gemessen, nicht geraten: Mit TZ=UTC meldet Windows utcoffset 0
(timezone=0, isdst=0 - auf dem Commander-PC geprueft), damit wird die
Umrechnung zur Nulloperation. Beide .bat-Dateien setzen es jetzt. Nebeneffekt
ist erwuenscht: Der Worker rechnet dann in derselben Zone wie die VM und wie
Rippys UI.
Fuer Rippy war die Meldung ohnehin folgenlos - /capabilities fragt per
Celery-Ping (Frage/Antwort, ohne Zeitstempel), nicht ueber die
Gossip-Ereignisse. Aber eine Warnung, die bei jedem Blick ins Log steht und
nichts bedeutet, kostet Vertrauen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
741 lines
36 KiB
PowerShell
741 lines
36 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, 672)
|
||
$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(240, 26)
|
||
$txtName.Text = $WorkerName
|
||
$txtName.BackColor = $cFeld
|
||
$txtName.ForeColor = $cText
|
||
$txtName.BorderStyle = "FixedSingle"
|
||
$form.Controls.Add($txtName)
|
||
|
||
# Gleichzeitige Auftraege (Commander 26.07.2026: "Mehrere Encodes gleichzeitig").
|
||
# Vorher lief der Worker fest mit --pool=solo und nahm genau EINEN Auftrag an.
|
||
# Vorbelegung nach Kernzahl: ab 12 Kernen zwei, sonst einer. Grund: 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.
|
||
$lblSlots = New-Object System.Windows.Forms.Label
|
||
$lblSlots.Text = "Gleichzeitig:"
|
||
$lblSlots.Location = New-Object System.Drawing.Point(280, 176)
|
||
$lblSlots.Size = New-Object System.Drawing.Size(120, 18)
|
||
$lblSlots.ForeColor = $cGedaempft
|
||
$form.Controls.Add($lblSlots)
|
||
|
||
$numSlots = New-Object System.Windows.Forms.NumericUpDown
|
||
$numSlots.Location = New-Object System.Drawing.Point(280, 196)
|
||
$numSlots.Size = New-Object System.Drawing.Size(70, 26)
|
||
$numSlots.Minimum = 1
|
||
$numSlots.Maximum = 8
|
||
$numSlots.Value = $(if ([Environment]::ProcessorCount -ge 12) { 2 } else { 1 })
|
||
$numSlots.BackColor = $cFeld
|
||
$numSlots.ForeColor = $cText
|
||
$numSlots.BorderStyle = "FixedSingle"
|
||
$form.Controls.Add($numSlots)
|
||
|
||
$lblKerne = New-Object System.Windows.Forms.Label
|
||
$lblKerne.Text = "von $([Environment]::ProcessorCount) Kernen"
|
||
$lblKerne.Location = New-Object System.Drawing.Point(358, 201)
|
||
$lblKerne.Size = New-Object System.Drawing.Size(166, 18)
|
||
$lblKerne.ForeColor = $cGedaempft
|
||
$form.Controls.Add($lblKerne)
|
||
|
||
# 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)
|
||
|
||
# Verknuepfung auf dem Desktop (Commander-Wunsch 26.07.2026). Vorher lagen die
|
||
# Startdateien nur im Programmordner - wer den Worker von Hand starten wollte,
|
||
# musste erst "C:\Program Files\Rippy Worker" suchen.
|
||
$chkDesktop = New-Object System.Windows.Forms.CheckBox
|
||
$chkDesktop.Text = "Verknüpfung auf dem Desktop anlegen"
|
||
$chkDesktop.Location = New-Object System.Drawing.Point(24, 388)
|
||
$chkDesktop.Size = New-Object System.Drawing.Size(400, 22)
|
||
$chkDesktop.Checked = $true
|
||
$chkDesktop.ForeColor = $cText
|
||
$form.Controls.Add($chkDesktop)
|
||
|
||
# Log-Bereich
|
||
$log = New-Object System.Windows.Forms.TextBox
|
||
$log.Location = New-Object System.Drawing.Point(24, 420)
|
||
$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, 608)
|
||
$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, 608)
|
||
$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 { "" }
|
||
|
||
# Gleichzeitige Auftraege. 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 darauf wartet.
|
||
$slots = [int]$numSlots.Value
|
||
$poolArg = if ($slots -le 1) { "--pool=solo" } else { "--pool=threads --concurrency=$slots" }
|
||
Log "Gleichzeitige Auftraege: $slots"
|
||
|
||
$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`nset RIPPY_SLOTS=$slots`r`nset TZ=UTC`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`nset RIPPY_SLOTS=$slots`r`nset TZ=UTC`r`n${mapZeile}set PATH=%~dp0;%PATH%`r`nvenv\Scripts\celery.exe -A celery_app worker --loglevel=info -Q transcode $poolArg -n ${wName}@%%h"
|
||
Set-Content -Path "start-worker.bat" -Value $workBat -Encoding ASCII
|
||
|
||
# --- Deinstaller ---------------------------------------------------
|
||
#
|
||
# ⚠️ REPARIERT 26.07.2026. Der Commander meldete: „Der Uninstaller vom
|
||
# Worker funktioniert nicht mehr." Reproduziert mit echtem PowerShell:
|
||
#
|
||
# Der Typ [System.Windows.Forms.MessageBox] wurde nicht gefunden.
|
||
#
|
||
# Ursache: `Add-Type -AssemblyName System.Windows.Forms` stand EINE ZEILE
|
||
# ZU SPÄT — die MessageBox wurde davor benutzt. Der Deinstaller starb also
|
||
# in seiner ersten Arbeitszeile, jedes Mal.
|
||
#
|
||
# Zwei weitere Mängel gleich mit erledigt:
|
||
# - Kodierung war ASCII, obwohl Umlaute drin stehen (Kauderwelsch).
|
||
# - `Remove-Item -Recurse -Force $PSScriptRoot` löscht den Ordner, in dem
|
||
# das laufende Skript selbst liegt. Das klappt auf Windows nicht
|
||
# zuverlässig (die venv-DLLs sind noch geladen). Jetzt räumt ein
|
||
# losgelöstes cmd den Ordner ab, nachdem PowerShell beendet ist.
|
||
$uninstall = @"
|
||
# Rippy-Worker DEINSTALLIEREN — rückstandsfrei (MUSS-Kriterium).
|
||
# Aufruf ohne Parameter fragt nach; -Force fragt nicht (so ruft das Tray auf).
|
||
param([switch]`$Force)
|
||
|
||
# ZUERST die Assembly laden, DANN die MessageBox benutzen. Genau daran ist der
|
||
# alte Deinstaller gescheitert.
|
||
Add-Type -AssemblyName System.Windows.Forms
|
||
|
||
if (-not `$Force) {
|
||
`$antwort = [System.Windows.Forms.MessageBox]::Show(
|
||
"Den Rippy-Worker '$wName' von diesem PC entfernen?",
|
||
"Rippy Worker deinstallieren",
|
||
[System.Windows.Forms.MessageBoxButtons]::YesNo,
|
||
[System.Windows.Forms.MessageBoxIcon]::Warning)
|
||
if (`$antwort -ne [System.Windows.Forms.DialogResult]::Yes) { exit }
|
||
}
|
||
|
||
`$ordner = `$PSScriptRoot
|
||
|
||
# ⚠️ ADMINRECHTE. Der Standard-Zielordner liegt unter "C:\Program Files", und
|
||
# der gehört nicht dem Benutzer — jedes Remove-Item darin scheitert mit "Der
|
||
# Zugriff auf den Pfad wurde verweigert" (Commander-Befund 26.07.2026, an
|
||
# HandBrakes doc\LICENSE und Konsorten aufgelaufen). Die Installer-.exe ist mit
|
||
# -requireAdmin gebaut und fragt beim Start per UAC; der Deinstaller hatte
|
||
# nichts Vergleichbares und war damit im Standardfall nutzlos.
|
||
#
|
||
# GEMESSEN statt angenommen: Es wird ein Schreibversuch gemacht. Wer den Worker
|
||
# ins eigene Profil installiert hat, braucht keine Erhöhung und bekommt auch
|
||
# keine UAC-Frage.
|
||
`$schreibbar = `$false
|
||
try {
|
||
`$probe = Join-Path `$ordner ".deinstall-probe"
|
||
Set-Content -Path `$probe -Value "x" -ErrorAction Stop
|
||
Remove-Item -Force `$probe -ErrorAction SilentlyContinue
|
||
`$schreibbar = `$true
|
||
} catch { }
|
||
|
||
`$istAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
|
||
).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||
|
||
if (-not `$schreibbar -and -not `$istAdmin) {
|
||
# Sich selbst erhöht neu starten. -Force, weil oben schon zugestimmt wurde —
|
||
# sonst käme die Frage zweimal.
|
||
try {
|
||
Start-Process powershell.exe -Verb RunAs -ArgumentList @(
|
||
"-NoProfile", "-ExecutionPolicy", "Bypass",
|
||
"-File", ('"' + `$PSCommandPath + '"'), "-Force")
|
||
} catch {
|
||
[System.Windows.Forms.MessageBox]::Show(
|
||
"Zum Entfernen sind Administratorrechte nötig (der Ordner liegt " +
|
||
"unter 'Programme'), und die Anfrage wurde abgelehnt.`n`n" +
|
||
"Alternative: PowerShell mit Rechtsklick 'Als Administrator " +
|
||
"ausführen' starten und dieses Skript erneut aufrufen.",
|
||
"Rippy Worker", 0, [System.Windows.Forms.MessageBoxIcon]::Warning) | Out-Null
|
||
}
|
||
exit
|
||
}
|
||
|
||
# 1. Alles beenden, was aus diesem Ordner läuft (Tray, celery, HandBrake).
|
||
Get-CimInstance Win32_Process | Where-Object {
|
||
`$_.ExecutablePath -like "`$ordner*"
|
||
} | ForEach-Object { Stop-Process -Id `$_.ProcessId -Force -ErrorAction SilentlyContinue }
|
||
Start-Sleep 3
|
||
|
||
# 2. Verknüpfungen entfernen — Autostart UND Desktop, jeweils an BEIDEN möglichen
|
||
# Orten, weil die Installation je nach Zielverzeichnis persönlich oder
|
||
# maschinenweit war. „Rückstandsfrei entfernbar" ist ein MUSS-Kriterium; eine
|
||
# tote Verknüpfung auf dem Desktop wäre genau so ein Rückstand.
|
||
foreach (`$ort in @("Startup", "CommonStartup")) {
|
||
`$lnk = Join-Path ([Environment]::GetFolderPath(`$ort)) "RippyWorker.lnk"
|
||
if (Test-Path `$lnk) { Remove-Item -Force `$lnk -ErrorAction SilentlyContinue }
|
||
}
|
||
foreach (`$ort in @("Desktop", "CommonDesktopDirectory")) {
|
||
`$lnk = Join-Path ([Environment]::GetFolderPath(`$ort)) "Rippy Worker.lnk"
|
||
if (Test-Path `$lnk) { Remove-Item -Force `$lnk -ErrorAction SilentlyContinue }
|
||
}
|
||
# ... und die geplante Aufgabe aus älteren Installationen, falls vorhanden.
|
||
schtasks /delete /tn "RippyWorker" /f 2>`$null | Out-Null
|
||
|
||
# 3. In Rippy abmelden, damit dort keine Worker-Leiche stehen bleibt.
|
||
try {
|
||
Invoke-RestMethod -Method Delete "http://$rHost/api/workers/$wName" -TimeoutSec 5 | Out-Null
|
||
} catch { }
|
||
|
||
# 4. Ordner abräumen. NICHT von hier aus: Dieses Skript liegt darin, und die
|
||
# venv-DLLs sind noch geladen. Ein losgelöstes cmd wartet, bis PowerShell
|
||
# weg ist, und räumt dann auf.
|
||
Set-Location `$env:TEMP
|
||
Start-Process cmd.exe -ArgumentList '/c timeout /t 4 /nobreak >nul & rd /s /q "'"`$ordner"'"' -WindowStyle Hidden
|
||
|
||
[System.Windows.Forms.MessageBox]::Show(
|
||
"Rippy-Worker entfernt. Der Ordner verschwindet in wenigen Sekunden.",
|
||
"Rippy Worker") | Out-Null
|
||
"@
|
||
# UTF-8 MIT BOM, nicht ASCII: Die Datei enthält Umlaute, und PowerShell
|
||
# 5.1 liest .ps1 ohne BOM als ANSI (dokumentiert im Kopf dieser Datei).
|
||
[System.IO.File]::WriteAllText(
|
||
(Join-Path $InstallDir "uninstall.ps1"), $uninstall,
|
||
(New-Object System.Text.UTF8Encoding $true))
|
||
|
||
# 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"
|
||
# WindowStyle 7 = minimiert. start-tray.bat startet pythonw (kein
|
||
# Konsolenfenster), aber die .bat selbst blitzt sonst kurz auf.
|
||
$lnk.WindowStyle = 7
|
||
$ico = Join-Path $InstallDir "rippy.ico"
|
||
if (Test-Path $ico) { $lnk.IconLocation = $ico }
|
||
$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)."
|
||
}
|
||
}
|
||
|
||
# 8. Verknüpfung auf dem Desktop (Commander-Wunsch 26.07.2026: „wenn man
|
||
# den Worker installiert braucht man auch eine exe auf dem Desktop
|
||
# zum Starten"). Vorher lagen die Startdateien nur im Programmordner —
|
||
# wer den Worker von Hand starten wollte, musste erst
|
||
# "C:\Program Files\Rippy Worker" suchen.
|
||
#
|
||
# Dieselbe Rechte-Überlegung wie beim Autostart: bei einer Installation
|
||
# unter "Programme" auf den Desktop ALLER Benutzer, sonst auf den
|
||
# eigenen. Und ein Fehlschlag hier ist nur eine Warnung — der Worker
|
||
# läuft davon unberührt.
|
||
if ($chkDesktop.Checked) {
|
||
try {
|
||
$programme = [Environment]::GetFolderPath("ProgramFiles")
|
||
$programmeX86 = [Environment]::GetFolderPath("ProgramFilesX86")
|
||
$maschinenweit = $InstallDir.StartsWith($programme, "OrdinalIgnoreCase") `
|
||
-or ($programmeX86 -and $InstallDir.StartsWith($programmeX86, "OrdinalIgnoreCase"))
|
||
$desktopDir = if ($maschinenweit) {
|
||
[Environment]::GetFolderPath("CommonDesktopDirectory")
|
||
} else {
|
||
[Environment]::GetFolderPath("Desktop")
|
||
}
|
||
$wsh = New-Object -ComObject WScript.Shell
|
||
$lnk = $wsh.CreateShortcut((Join-Path $desktopDir "Rippy Worker.lnk"))
|
||
$lnk.TargetPath = (Join-Path $InstallDir "start-tray.bat")
|
||
$lnk.WorkingDirectory = $InstallDir
|
||
$lnk.Description = "Rippy Encoding-Worker starten (Symbol neben der Uhr)"
|
||
$lnk.WindowStyle = 7 # minimiert, damit die .bat nicht aufblitzt
|
||
$ico = Join-Path $InstallDir "rippy.ico"
|
||
if (Test-Path $ico) { $lnk.IconLocation = $ico }
|
||
$lnk.Save()
|
||
Log "Verknüpfung 'Rippy Worker' auf dem Desktop angelegt."
|
||
} catch {
|
||
Log "HINWEIS: Desktop-Verknüpfung ging nicht ($($_.Exception.Message))."
|
||
Log " Der Worker ist trotzdem fertig — starten über"
|
||
Log " $InstallDir\start-tray.bat"
|
||
}
|
||
}
|
||
|
||
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()
|