Windows-Worker: Tray-Symbol + rueckstandsfreie Deinstallation
Ampel / ampel (push) Successful in 27s
Ampel / ampel (push) Successful in 27s
- tray.py (nur nativer Windows-Worker; Docker unberuehrt): pystray+Pillow-
Tray neben der Uhr — Status, Worker starten/stoppen, Rippy oeffnen,
Log anzeigen, Beenden (stoppt den Worker mit). Celery laeuft als
Kind-Prozess ohne Konsolenfenster (CREATE_NO_WINDOW), Log in worker.log.
- install.ps1 erzeugt jetzt start-tray.bat (pythonw, empfohlen),
start-worker.bat (Konsole/Debug) und uninstall.ps1: stoppt alle
Prozesse aus dem Ordner, loescht die Autostart-Aufgabe, meldet den
Worker per DELETE /workers/{name} in Rippy ab und entfernt den Ordner
komplett. -Autostart registriert die Tray-Variante (onlogon).
- UI-Beschreibung + Anleitung entsprechend ergaenzt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,8 @@ if (-not (Test-Path "venv")) {
|
|||||||
}
|
}
|
||||||
& .\venv\Scripts\python.exe -m pip install --quiet --upgrade pip
|
& .\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 -r requirements.txt
|
||||||
|
# Nur fuer den Windows-Worker: Tray-Symbol (Status, Start/Stopp, Beenden)
|
||||||
|
& .\venv\Scripts\pip.exe install --quiet pystray pillow
|
||||||
Write-Host "Python-Abhaengigkeiten installiert."
|
Write-Host "Python-Abhaengigkeiten installiert."
|
||||||
|
|
||||||
# 4. HandBrakeCLI besorgen (falls noetig)
|
# 4. HandBrakeCLI besorgen (falls noetig)
|
||||||
@@ -90,14 +92,57 @@ set PATH=%~dp0;%PATH%
|
|||||||
venv\Scripts\celery.exe -A celery_app worker --loglevel=info -Q transcode --pool=solo -n ${WorkerName}@%%h
|
venv\Scripts\celery.exe -A celery_app worker --loglevel=info -Q transcode --pool=solo -n ${WorkerName}@%%h
|
||||||
"@
|
"@
|
||||||
Set-Content -Path "start-worker.bat" -Value $bat -Encoding ASCII
|
Set-Content -Path "start-worker.bat" -Value $bat -Encoding ASCII
|
||||||
Write-Host "start-worker.bat erzeugt."
|
|
||||||
|
|
||||||
# 6. Optional: Autostart bei Anmeldung
|
# 6. 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 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."
|
||||||
|
|
||||||
|
# 7. Deinstaller — MUSS-Kriterium: rueckstandsfrei entfernbar
|
||||||
|
$uninstall = @"
|
||||||
|
# Rippy-Worker DEINSTALLIEREN: stoppt Prozesse, entfernt Autostart,
|
||||||
|
# meldet den Worker in Rippy ab und loescht 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
|
||||||
|
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 loeschen (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."
|
||||||
|
|
||||||
|
# 8. Optional: Autostart bei Anmeldung (mit Tray)
|
||||||
if ($Autostart) {
|
if ($Autostart) {
|
||||||
schtasks /create /f /tn "RippyWorker" /tr "`"$ziel\start-worker.bat`"" /sc onlogon | Out-Null
|
schtasks /create /f /tn "RippyWorker" /tr "`"$ziel\start-tray.bat`"" /sc onlogon | Out-Null
|
||||||
Write-Host "Autostart-Aufgabe 'RippyWorker' registriert (bei Anmeldung)."
|
Write-Host "Autostart-Aufgabe 'RippyWorker' registriert (Tray bei Anmeldung)."
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "FERTIG. Worker starten mit: $ziel\start-worker.bat" -ForegroundColor Green
|
Write-Host "FERTIG." -ForegroundColor Green
|
||||||
Write-Host "Er erscheint danach in Rippy unter Einstellungen -> Worker als '$WorkerName'."
|
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'."
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ export default function WorkerVerwaltung() {
|
|||||||
<p className={`text-xs mb-3 ${theme === 'dark' ? 'text-slate-500' : 'text-slate-400'}`}>
|
<p className={`text-xs mb-3 ${theme === 'dark' ? 'text-slate-500' : 'text-slate-400'}`}>
|
||||||
{variante === 'linux'
|
{variante === 'linux'
|
||||||
? 'Auf der Linux-Maschine (mit Docker) ausführen — WORKER_NAME frei wählen, unter dem Namen taucht sie oben auf und übernimmt Kompressions-Jobs. GPU wird erkannt und angezeigt.'
|
? 'Auf der Linux-Maschine (mit Docker) ausführen — WORKER_NAME frei wählen, unter dem Namen taucht sie oben auf und übernimmt Kompressions-Jobs. GPU wird erkannt und angezeigt.'
|
||||||
: 'In einer PowerShell auf dem Windows-PC ausführen (braucht nur Python 3.10+ — kein Docker, kein git). Der Installer holt Worker-Code und HandBrake automatisch von dieser Rippy-Instanz bzw. dem offiziellen HandBrake-Release und erzeugt eine start-worker.bat. Autostart bei Anmeldung: Parameter -Autostart anhängen.'}
|
: 'In einer PowerShell auf dem Windows-PC ausführen (braucht nur Python 3.10+ — kein Docker, kein git). Der Installer holt Worker-Code und HandBrake automatisch und erzeugt: start-tray.bat (Tray-Symbol neben der Uhr mit Start/Stopp/Status), start-worker.bat (Konsole, für Fehlersuche) und uninstall.ps1 (entfernt alles rückstandsfrei inkl. Worker-Eintrag hier). Autostart bei Anmeldung: Parameter -Autostart anhängen.'}
|
||||||
</p>
|
</p>
|
||||||
<div className={`relative rounded-lg p-3 font-mono text-xs whitespace-pre overflow-x-auto ${theme === 'dark' ? 'bg-slate-950 text-slate-300' : 'bg-slate-900 text-slate-200'}`}>
|
<div className={`relative rounded-lg p-3 font-mono text-xs whitespace-pre overflow-x-auto ${theme === 'dark' ? 'bg-slate-950 text-slate-300' : 'bg-slate-900 text-slate-200'}`}>
|
||||||
{installBefehl}
|
{installBefehl}
|
||||||
|
|||||||
@@ -116,6 +116,12 @@ export default function AnleitungPage() {
|
|||||||
danach unter ihrem Namen in der Worker-Liste auf und zieht sich Kompressions-Jobs
|
danach unter ihrem Namen in der Worker-Liste auf und zieht sich Kompressions-Jobs
|
||||||
automatisch. Gerippt wird immer auf der Rippy-Hauptmaschine (dort hängt das Laufwerk).
|
automatisch. Gerippt wird immer auf der Rippy-Hauptmaschine (dort hängt das Laufwerk).
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
Der Windows-Worker läuft mit <span className={fett}>Tray-Symbol</span> neben der Uhr
|
||||||
|
(Status, Start/Stopp, „Rippy öffnen", Log) — gestartet über <code>start-tray.bat</code>.
|
||||||
|
Deinstallieren: <code>uninstall.ps1</code> im Worker-Ordner stoppt alles, entfernt den
|
||||||
|
Autostart, meldet den Worker in Rippy ab und löscht den Ordner rückstandsfrei.
|
||||||
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span className={fett}>Wichtig:</span> Worker verbinden sich direkt im Heimnetz — in den
|
<span className={fett}>Wichtig:</span> Worker verbinden sich direkt im Heimnetz — in den
|
||||||
Befehlen immer die <span className={fett}>LAN-Adresse</span> der Rippy-Maschine verwenden,
|
Befehlen immer die <span className={fett}>LAN-Adresse</span> der Rippy-Maschine verwenden,
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
"""Tray-Symbol für den NATIVEN Windows-Worker (nicht im Docker-Image genutzt).
|
||||||
|
|
||||||
|
Zeigt den Worker-Status unten rechts neben der Uhr und bietet:
|
||||||
|
Start/Stopp, Rippy im Browser öffnen, Log anzeigen, Beenden. Gestartet
|
||||||
|
wird es über start-tray.bat (setzt die Verbindungs-Umgebung und ruft
|
||||||
|
pythonw.exe auf — kein Konsolenfenster). Der Worker-Prozess (celery,
|
||||||
|
Queue transcode) läuft als Kind-Prozess und stoppt mit dem Tray.
|
||||||
|
|
||||||
|
Abhängigkeiten pystray + Pillow installiert NUR der Windows-Installer
|
||||||
|
(install.ps1) — das Docker-Image bleibt davon unberührt.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import webbrowser
|
||||||
|
|
||||||
|
try:
|
||||||
|
import pystray
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
except ImportError:
|
||||||
|
print("Tray braucht pystray + Pillow — install.ps1 erneut ausführen.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
BASIS = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
RIPPY_HOST = os.getenv("RIPPY_TRAY_HOST", "")
|
||||||
|
WORKER_NAME = os.getenv("WORKER_NAME", "windows-worker")
|
||||||
|
LOG_PFAD = os.path.join(BASIS, "worker.log")
|
||||||
|
|
||||||
|
prozess = None
|
||||||
|
|
||||||
|
|
||||||
|
def disc_bild(laeuft: bool) -> Image.Image:
|
||||||
|
"""Disc-Symbol im Rippy-Look: Indigo wenn der Worker läuft, grau sonst."""
|
||||||
|
bild = Image.new("RGBA", (64, 64), (0, 0, 0, 0))
|
||||||
|
zeichner = ImageDraw.Draw(bild)
|
||||||
|
farbe = (99, 102, 241, 255) if laeuft else (125, 132, 145, 255)
|
||||||
|
zeichner.ellipse([4, 4, 60, 60], fill=farbe)
|
||||||
|
zeichner.ellipse([24, 24, 40, 40], fill=(255, 255, 255, 255))
|
||||||
|
return bild
|
||||||
|
|
||||||
|
|
||||||
|
def worker_laeuft() -> bool:
|
||||||
|
return prozess is not None and prozess.poll() is None
|
||||||
|
|
||||||
|
|
||||||
|
def worker_starten(icon=None, item=None):
|
||||||
|
global prozess
|
||||||
|
if worker_laeuft():
|
||||||
|
return
|
||||||
|
celery = os.path.join(BASIS, "venv", "Scripts", "celery.exe")
|
||||||
|
log = open(LOG_PFAD, "a", encoding="utf-8", errors="replace")
|
||||||
|
prozess = subprocess.Popen(
|
||||||
|
[celery, "-A", "celery_app", "worker", "--loglevel=info",
|
||||||
|
"-Q", "transcode", "--pool=solo", "-n", f"{WORKER_NAME}@%h"],
|
||||||
|
cwd=BASIS, stdout=log, stderr=subprocess.STDOUT,
|
||||||
|
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
|
||||||
|
)
|
||||||
|
if icon:
|
||||||
|
icon.icon = disc_bild(True)
|
||||||
|
icon.update_menu()
|
||||||
|
|
||||||
|
|
||||||
|
def worker_stoppen(icon=None, item=None):
|
||||||
|
global prozess
|
||||||
|
if worker_laeuft():
|
||||||
|
prozess.terminate()
|
||||||
|
try:
|
||||||
|
prozess.wait(15)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
prozess.kill()
|
||||||
|
prozess = None
|
||||||
|
if icon:
|
||||||
|
icon.icon = disc_bild(False)
|
||||||
|
icon.update_menu()
|
||||||
|
|
||||||
|
|
||||||
|
def umschalten(icon, item):
|
||||||
|
if worker_laeuft():
|
||||||
|
worker_stoppen(icon)
|
||||||
|
else:
|
||||||
|
worker_starten(icon)
|
||||||
|
|
||||||
|
|
||||||
|
def rippy_oeffnen(icon, item):
|
||||||
|
if RIPPY_HOST:
|
||||||
|
webbrowser.open(f"http://{RIPPY_HOST}")
|
||||||
|
|
||||||
|
|
||||||
|
def log_anzeigen(icon, item):
|
||||||
|
if os.path.exists(LOG_PFAD):
|
||||||
|
os.startfile(LOG_PFAD) # noqa: S606 — bewusst: Standard-Editor des Nutzers
|
||||||
|
|
||||||
|
|
||||||
|
def beenden(icon, item):
|
||||||
|
worker_stoppen()
|
||||||
|
icon.stop()
|
||||||
|
|
||||||
|
|
||||||
|
menue = pystray.Menu(
|
||||||
|
pystray.MenuItem(
|
||||||
|
lambda item: f'Rippy-Worker "{WORKER_NAME}": {"läuft" if worker_laeuft() else "gestoppt"}',
|
||||||
|
None, enabled=False,
|
||||||
|
),
|
||||||
|
pystray.Menu.SEPARATOR,
|
||||||
|
pystray.MenuItem(
|
||||||
|
lambda item: "Worker stoppen" if worker_laeuft() else "Worker starten",
|
||||||
|
umschalten,
|
||||||
|
),
|
||||||
|
pystray.MenuItem("Rippy öffnen", rippy_oeffnen),
|
||||||
|
pystray.MenuItem("Log anzeigen", log_anzeigen),
|
||||||
|
pystray.Menu.SEPARATOR,
|
||||||
|
pystray.MenuItem("Beenden (stoppt den Worker)", beenden),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
tray = pystray.Icon("rippy-worker", disc_bild(False), "Rippy Worker", menue)
|
||||||
|
worker_starten()
|
||||||
|
tray.icon = disc_bild(worker_laeuft())
|
||||||
|
tray.run()
|
||||||
Reference in New Issue
Block a user