feat(installer): fertige .exe mit Rippy-Icon + ohne Konsolenfenster (statt .vbs)
Ampel / ampel (push) Successful in 28s
Ampel / ampel (push) Successful in 28s
Commander-Einwand: .vbs ist abgekuendigt (Win11 24H2 Feature-on-Demand) und wird oft als gefaehrlich geflaggt. Loesung: echte .exe. - RippyWorkerSetup.exe (50 KB): install-gui.ps1 via ps2exe kompiliert — eingebettetes Rippy-Disc-Icon (deploy/worker-windows/rippy.ico), kein Konsolenfenster (-noConsole), WinForms (-STA). E2E getestet: Fenster oeffnet, conhost-Zaehler unveraendert (kein Konsolenfenster). Vorgebaut auf Windows (build-exe.ps1) + committet — eine Windows-.exe kann nicht von Linux (Rippy-Host) gebaut werden. WICHTIG: friert KEINE Versionen ein — HandBrake wird zur Laufzeit (GitHub latest) und der Worker-Code live von Rippy geholt; nur bei GUI-Aenderungen neu bauen. - API: GET /worker-setup/windows-exe (FileResponse); Dockerfile kopiert die .exe ins Image. .gitattributes: *.exe/*.ico als binary. - UI (Worker -> Windows): 'Installer herunterladen (.exe)' als Haupt-Weg (generisch, GUI fragt Adresse ab; die einzutragende IP wird als Hinweis angezeigt). .vbs-Erzeugung entfernt. CLI bleibt Profi-Alternative. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
# Shell-Skripte brauchen LF — mit CRLF scheitert der Interpreter im Container
|
# Shell-Skripte brauchen LF — mit CRLF scheitert der Interpreter im Container
|
||||||
# ("/bin/sh^M: bad interpreter"). Wir entwickeln auf Windows, gebaut wird auf Linux.
|
# ("/bin/sh^M: bad interpreter"). Wir entwickeln auf Windows, gebaut wird auf Linux.
|
||||||
*.sh text eol=lf
|
*.sh text eol=lf
|
||||||
|
*.exe binary
|
||||||
|
*.ico binary
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,59 @@
|
|||||||
|
# Baut rippy.ico + RippyWorkerSetup.exe aus install-gui.ps1 (ps2exe).
|
||||||
|
#
|
||||||
|
# WANN NEU BAUEN: nur wenn sich install-gui.ps1 (die Installer-Oberflaeche)
|
||||||
|
# aendert. NICHT bei MakeMKV-/HandBrake-Updates — die .exe holt HandBrake zur
|
||||||
|
# Laufzeit (GitHub latest) und den Worker-Code live von Rippy, friert also
|
||||||
|
# keine Versionen ein.
|
||||||
|
# AUSFUEHREN AUF WINDOWS (eine Windows-.exe kann nicht von Linux gebaut werden):
|
||||||
|
# powershell -ExecutionPolicy Bypass -File build-exe.ps1
|
||||||
|
# Danach RippyWorkerSetup.exe committen (die API serviert sie).
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
Add-Type -AssemblyName System.Drawing
|
||||||
|
Set-Location $PSScriptRoot
|
||||||
|
|
||||||
|
$guiSrc = Join-Path $PSScriptRoot "install-gui.ps1"
|
||||||
|
|
||||||
|
# 1. Rippy-Disc-Icon 256x256 zeichnen -> PNG -> ICO-Container
|
||||||
|
$bmp = New-Object System.Drawing.Bitmap 256, 256
|
||||||
|
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
||||||
|
$g.SmoothingMode = "AntiAlias"
|
||||||
|
$g.Clear([System.Drawing.Color]::Transparent)
|
||||||
|
$g.FillEllipse((New-Object System.Drawing.SolidBrush ([System.Drawing.Color]::FromArgb(99, 102, 241))), 8, 8, 240, 240)
|
||||||
|
$g.FillEllipse((New-Object System.Drawing.SolidBrush ([System.Drawing.Color]::FromArgb(147, 51, 234))), 40, 40, 176, 176)
|
||||||
|
$g.FillEllipse([System.Drawing.Brushes]::White, 104, 104, 48, 48)
|
||||||
|
$ms = New-Object System.IO.MemoryStream
|
||||||
|
$bmp.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)
|
||||||
|
$png = $ms.ToArray()
|
||||||
|
|
||||||
|
$icoStream = New-Object System.IO.MemoryStream
|
||||||
|
$bw = New-Object System.IO.BinaryWriter $icoStream
|
||||||
|
$bw.Write([UInt16]0); $bw.Write([UInt16]1); $bw.Write([UInt16]1) # ICONDIR
|
||||||
|
$bw.Write([Byte]0); $bw.Write([Byte]0); $bw.Write([Byte]0); $bw.Write([Byte]0) # 256x256, colors, res
|
||||||
|
$bw.Write([UInt16]1); $bw.Write([UInt16]32) # planes, bpp
|
||||||
|
$bw.Write([UInt32]$png.Length); $bw.Write([UInt32]22) # size, offset
|
||||||
|
$bw.Write($png); $bw.Flush()
|
||||||
|
[System.IO.File]::WriteAllBytes("$PSScriptRoot\rippy.ico", $icoStream.ToArray())
|
||||||
|
Write-Host "rippy.ico erzeugt ($($png.Length) Bytes PNG)"
|
||||||
|
|
||||||
|
# 2. ps2exe sicherstellen — TLS 1.2 ist der Fix fuer den NuGet-Bootstrap-Fehler
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072
|
||||||
|
if (-not (Get-Module -ListAvailable ps2exe)) {
|
||||||
|
Write-Host "Bootstrappe NuGet + PSGallery ..."
|
||||||
|
try { Get-PackageProvider -Name NuGet -ErrorAction Stop | Out-Null }
|
||||||
|
catch { Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser | Out-Null }
|
||||||
|
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -ErrorAction SilentlyContinue
|
||||||
|
Write-Host "Installiere ps2exe ..."
|
||||||
|
Install-Module ps2exe -Scope CurrentUser -Force -AllowClobber
|
||||||
|
}
|
||||||
|
Import-Module ps2exe
|
||||||
|
|
||||||
|
# 3. Kompilieren (WinForms -> noConsole + STA, Icon eingebettet)
|
||||||
|
Invoke-ps2exe -inputFile $guiSrc -outputFile "$PSScriptRoot\RippyWorkerSetup.exe" `
|
||||||
|
-iconFile "$PSScriptRoot\rippy.ico" -noConsole -STA `
|
||||||
|
-title "Rippy Worker Setup" -product "Rippy" -company "Rippy" -version "1.0.0.0"
|
||||||
|
|
||||||
|
if (Test-Path "$PSScriptRoot\RippyWorkerSetup.exe") {
|
||||||
|
Write-Host "OK: RippyWorkerSetup.exe = $((Get-Item "$PSScriptRoot\RippyWorkerSetup.exe").Length) Bytes"
|
||||||
|
} else {
|
||||||
|
Write-Host "FEHLER: keine .exe erzeugt"
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
@@ -22,5 +22,9 @@ COPY docker/api/ .
|
|||||||
# die Zielmaschine braucht weder git noch Docker.
|
# die Zielmaschine braucht weder git noch Docker.
|
||||||
COPY docker/worker/*.py docker/worker/requirements.txt worker_dist/
|
COPY docker/worker/*.py docker/worker/requirements.txt worker_dist/
|
||||||
COPY deploy/worker-windows/install.ps1 deploy/worker-windows/install-gui.ps1 worker_dist/
|
COPY deploy/worker-windows/install.ps1 deploy/worker-windows/install-gui.ps1 worker_dist/
|
||||||
|
# Vorgebaute .exe (Rippy-Icon, kein Konsolenfenster) — auf Windows via
|
||||||
|
# deploy/worker-windows/build-exe.ps1 erzeugt (Windows-.exe geht nicht von
|
||||||
|
# Linux). Nur bei GUI-Aenderungen neu bauen, nicht bei Tool-Updates.
|
||||||
|
COPY deploy/worker-windows/RippyWorkerSetup.exe worker_dist/
|
||||||
|
|
||||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--app-dir", "/app"]
|
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--app-dir", "/app"]
|
||||||
|
|||||||
+18
-2
@@ -1091,14 +1091,30 @@ async def worker_setup_windows():
|
|||||||
|
|
||||||
@app.get("/worker-setup/windows-gui")
|
@app.get("/worker-setup/windows-gui")
|
||||||
async def worker_setup_windows_gui():
|
async def worker_setup_windows_gui():
|
||||||
"""Grafischer Windows-Installer (WinForms) — vom .bat-Launcher geladen und
|
"""Grafischer Windows-Installer (WinForms, PowerShell-Quelle) — Rückfall
|
||||||
gestartet, damit der Nutzer ein Fenster statt CLI bekommt."""
|
für Fortgeschrittene; der Normalweg ist die .exe unten."""
|
||||||
pfad = "worker_dist/install-gui.ps1"
|
pfad = "worker_dist/install-gui.ps1"
|
||||||
if not os.path.isfile(pfad):
|
if not os.path.isfile(pfad):
|
||||||
raise HTTPException(status_code=404, detail="GUI-Installer nicht im Image — API neu bauen")
|
raise HTTPException(status_code=404, detail="GUI-Installer nicht im Image — API neu bauen")
|
||||||
return FileResponse(pfad, media_type="text/plain", filename="rippy-worker-gui.ps1")
|
return FileResponse(pfad, media_type="text/plain", filename="rippy-worker-gui.ps1")
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/worker-setup/windows-exe")
|
||||||
|
async def worker_setup_windows_exe():
|
||||||
|
"""Fertige Windows-Installer-.exe (Rippy-Icon, kein Konsolenfenster).
|
||||||
|
|
||||||
|
Vorgebaut auf Windows (deploy/worker-windows/build-exe.ps1) — eine
|
||||||
|
Windows-.exe lässt sich nicht auf Linux bauen. Generisch: die GUI fragt
|
||||||
|
die Rippy-Adresse selbst ab, HandBrake + Worker-Code kommen zur Laufzeit."""
|
||||||
|
pfad = "worker_dist/RippyWorkerSetup.exe"
|
||||||
|
if not os.path.isfile(pfad):
|
||||||
|
raise HTTPException(status_code=404, detail="Installer-.exe nicht im Image — API neu bauen")
|
||||||
|
return FileResponse(
|
||||||
|
pfad, media_type="application/vnd.microsoft.portable-executable",
|
||||||
|
filename="RippyWorkerSetup.exe",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/worker-setup/paket")
|
@app.get("/worker-setup/paket")
|
||||||
async def worker_setup_paket():
|
async def worker_setup_paket():
|
||||||
"""Worker-Quellcode als Zip — der Windows-Installer lädt ihn von hier.
|
"""Worker-Quellcode als Zip — der Windows-Installer lädt ihn von hier.
|
||||||
|
|||||||
@@ -91,28 +91,14 @@ export default function WorkerVerwaltung() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grafischer Installer: erzeugt eine .vbs CLIENT-seitig mit der eingetragenen
|
// Fertige Installer-.exe (Rippy-Icon, kein Konsolenfenster) direkt laden.
|
||||||
// LAN-IP (der Browser kennt sie, die API nicht sicher). Doppelklick startet
|
// Generisch — die GUI fragt die Rippy-Adresse selbst ab.
|
||||||
// via wscript PowerShell KOMPLETT VERSTECKT (kein Konsolenfenster) und lädt
|
const exeHerunterladen = () => {
|
||||||
// den WinForms-Installer von Rippy — nur das Installer-Fenster erscheint.
|
|
||||||
const guiInstallerHerunterladen = () => {
|
|
||||||
const host = rippyHost.trim()
|
|
||||||
if (!host) { toast('error', 'Erst die LAN-IP der Rippy-Maschine eintragen'); return }
|
|
||||||
const ps = `try { Invoke-WebRequest 'http://${host}/api/worker-setup/windows-gui' -OutFile ($env:TEMP + '\\rippy-gui.ps1') -UseBasicParsing; & ($env:TEMP + '\\rippy-gui.ps1') -RippyHost '${host}' } catch { Add-Type -AssemblyName System.Windows.Forms; [void][System.Windows.Forms.MessageBox]::Show($_.Exception.Message, 'Rippy Installer') }`
|
|
||||||
// VBScript verdoppelt innere Anführungszeichen; Fensterstil 0 = versteckt
|
|
||||||
const psEscaped = ps.replace(/"/g, '""')
|
|
||||||
const vbs = [
|
|
||||||
"' Rippy Encoding-Worker - grafische Installation (ohne Konsolenfenster)",
|
|
||||||
'Set sh = CreateObject("WScript.Shell")',
|
|
||||||
`sh.Run "powershell -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -STA -Command ""${psEscaped}""", 0, False`,
|
|
||||||
].join('\r\n')
|
|
||||||
const blob = new Blob([vbs], { type: 'application/octet-stream' })
|
|
||||||
const a = document.createElement('a')
|
const a = document.createElement('a')
|
||||||
a.href = URL.createObjectURL(blob)
|
a.href = '/api/worker-setup/windows-exe'
|
||||||
a.download = 'rippy-worker-setup.vbs'
|
a.download = 'RippyWorkerSetup.exe'
|
||||||
document.body.appendChild(a); a.click(); a.remove()
|
document.body.appendChild(a); a.click(); a.remove()
|
||||||
URL.revokeObjectURL(a.href)
|
toast('success', 'RippyWorkerSetup.exe wird heruntergeladen — doppelklicken zum Installieren')
|
||||||
toast('success', 'rippy-worker-setup.vbs heruntergeladen — doppelklicken zum Installieren')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -224,20 +210,24 @@ export default function WorkerVerwaltung() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Windows: grafischer Installer als Haupt-Weg (Doppelklick statt CLI) */}
|
{/* Windows: fertige .exe als Haupt-Weg (Doppelklick, Icon, kein Konsolenfenster) */}
|
||||||
{variante === 'windows' && (
|
{variante === 'windows' && (
|
||||||
<div className="rounded-xl p-4 border border-amber-500/30 bg-amber-500/5 space-y-2">
|
<div className="rounded-xl p-4 border border-amber-500/30 bg-amber-500/5 space-y-2">
|
||||||
<p className="text-sm text-slate-700 dark:text-slate-300">
|
<p className="text-sm text-slate-700 dark:text-slate-300">
|
||||||
<strong>Einfachster Weg:</strong> grafischen Installer herunterladen, doppelklicken,
|
<strong>Einfachster Weg:</strong> Installer (.exe) herunterladen, doppelklicken,
|
||||||
Fenster ausfüllen, „Installieren". Braucht nur Python 3.10+ auf dem PC — kein Docker, kein git.
|
im Fenster Adresse + Name eintragen, „Installieren". Kein Konsolenfenster.
|
||||||
|
Braucht nur Python 3.10+ auf dem PC — kein Docker, kein git.
|
||||||
</p>
|
</p>
|
||||||
<Button variant="amber" onClick={guiInstallerHerunterladen} disabled={!rippyHost.trim()}>
|
<Button variant="amber" onClick={exeHerunterladen}>
|
||||||
<Download size={16} /> Grafischen Installer herunterladen
|
<Download size={16} /> Installer herunterladen (.exe)
|
||||||
</Button>
|
</Button>
|
||||||
|
<p className="text-xs text-slate-700 dark:text-slate-300">
|
||||||
|
Im Installer-Fenster als Rippy-Adresse eintragen: <code className="font-semibold">{rippyHost.trim() || '<LAN-IP der Rippy-Maschine>'}</code>
|
||||||
|
</p>
|
||||||
<p className="text-xs text-slate-500 dark:text-slate-400">
|
<p className="text-xs text-slate-500 dark:text-slate-400">
|
||||||
Öffnet direkt das Installer-Fenster — kein Konsolenfenster. Windows warnt beim Doppelklick evtl.
|
Windows warnt beim ersten Start evtl. („Windows hat Ihren PC geschützt", unbekannter Herausgeber)
|
||||||
(„Windows hat Ihren PC geschützt") → „Weitere Informationen" → „Trotzdem ausführen". Die Datei
|
→ „Weitere Informationen" → „Trotzdem ausführen". Die .exe holt Worker-Code und HandBrake nur von
|
||||||
lädt den Installer nur von deiner Rippy-Maschine.
|
deiner Rippy-Maschine bzw. dem offiziellen HandBrake-Release.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user