From d32e7e59c3f4236f104b5c0197a2e0ff106225b2 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Thu, 24 Sep 2026 20:39:08 +0200 Subject: [PATCH] feat(host-agent): Windows-Build aus Docker (Wine) + setup.exe (Dienst) Dockerfile.win (tobix/pywine): baut die Windows-.exe via Wine+PyInstaller und per NSIS ein setup.exe, das den Agent via nssm als Autostart-Windows-Dienst einrichtet und die .env aus %ProgramData%\ARIA-Host-Agent liest (AppDirectory). build-win.sh als Einstieg; release_agent.sh baut Windows jetzt mit (SKIP_WINDOWS=1 ueberspringt). _load_dotenv haertet: sucht .env auch neben sys.executable (onefile- .exe-Ort), nicht nur __file__/CWD. README: Windows-Build + Dienst + Release. macOS bleibt self-build (nicht aus Docker moeglich). Co-Authored-By: Claude Opus 4.8 --- host-agent/Dockerfile.win | 36 ++++++++++++++ host-agent/README.md | 44 ++++++++++++++++- host-agent/build-win.sh | 17 +++++++ host-agent/host_agent.py | 8 ++- host-agent/release_agent.sh | 26 ++++++++-- host-agent/windows/installer.nsi | 83 ++++++++++++++++++++++++++++++++ 6 files changed, 207 insertions(+), 7 deletions(-) create mode 100644 host-agent/Dockerfile.win create mode 100755 host-agent/build-win.sh create mode 100644 host-agent/windows/installer.nsi diff --git a/host-agent/Dockerfile.win b/host-agent/Dockerfile.win new file mode 100644 index 0000000..3566a71 --- /dev/null +++ b/host-agent/Dockerfile.win @@ -0,0 +1,36 @@ +# Baut die Windows-.exe des Host-Agents AUF LINUX — via Wine + Windows-Python + +# PyInstaller. tobix/pywine bringt Wine + Windows-Python 3.11 mit (PyInstaller +# kann NICHT cross-compilen, deshalb der Wine-Umweg). +# +# Zusaetzlich baut NSIS ein setup.exe, das die Agent-.exe installiert, eine .env +# in C:\ProgramData\ARIA-Host-Agent anlegt (falls keine da ist) und den Agent als +# automatisch startenden Windows-Dienst (via nssm) einrichtet. +FROM tobix/pywine:3.11 + +ARG VERSION=0.0.0 + +# NSIS (Installer-Compiler, laeuft nativ auf Linux) + Tools fuer nssm. +RUN apt-get update \ + && apt-get install -y --no-install-recommends nsis curl unzip ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /work + +# nssm — Non-Sucking Service Manager (public domain): macht aus der Konsolen-.exe +# einen sauberen Windows-Dienst (die .exe selbst spricht das SCM nicht). +RUN curl -fsSL https://nssm.cc/release/nssm-2.24.zip -o /tmp/nssm.zip \ + && unzip -q /tmp/nssm.zip -d /tmp \ + && cp /tmp/nssm-2.24/win64/nssm.exe ./nssm.exe \ + && rm -rf /tmp/nssm* + +COPY requirements.txt host_agent.py ./ +COPY windows/installer.nsi ./ + +# Windows-Python-Deps + PyInstaller, dann die Onefile-.exe bauen. +RUN wine pip install --no-cache-dir -r requirements.txt pyinstaller +RUN wine pyinstaller --onefile --name aria-host-agent --collect-all psutil host_agent.py \ + && cp dist/aria-host-agent.exe ./aria-host-agent.exe \ + && makensis -DVERSION=${VERSION} installer.nsi + +# Beide Artefakte rausreichen (dist/ wird vom build-win.sh als Volume gemountet). +CMD ["bash","-lc","cp dist/aria-host-agent.exe /out/ && cp aria-host-agent-setup.exe /out/ && echo 'OK -> /out/aria-host-agent.exe + /out/aria-host-agent-setup.exe'"] diff --git a/host-agent/README.md b/host-agent/README.md index 3172056..3c0a1d4 100644 --- a/host-agent/README.md +++ b/host-agent/README.md @@ -46,11 +46,23 @@ PyInstaller kann **nicht cross-kompilieren** — jede Binary wird auf ihrem OS g ./build-native.sh # -> dist/aria-host-agent ``` -**Windows** — PyInstaller (Python 3 im PATH nötig): +**Windows — nativ** (auf einem Windows-Rechner, Python 3 im PATH nötig): ```bat build-native.bat REM -> dist\aria-host-agent.exe ``` +**Windows — aus Docker heraus (auf Linux!), inkl. Installer** — Wine baut die +`.exe`, NSIS packt ein `setup.exe`, das den Agent als Windows-Dienst einrichtet: +```bash +./build-win.sh [version] +# -> dist/aria-host-agent.exe (Konsolen-Binary) +# -> dist/aria-host-agent-setup.exe (Installer: Dienst + .env in ProgramData) +``` +Der erste Lauf zieht das `tobix/pywine`-Image (~1–2 GB) und richtet die Wine- +Python-Umgebung ein — das dauert; Folge-Builds sind schnell. PyInstaller kann +nicht cross-compilen, deshalb der Wine-Umweg. **macOS geht so NICHT** (Apple +lässt sich nicht legal aus Docker bauen) — dort `./build-native.sh` auf einem Mac. + ### Docker scheitert? (Live-ISO / overlayfs-Root) Wenn `build.sh` mit `failed to mount … overlayfs … invalid argument` abbricht, @@ -96,6 +108,36 @@ Danach: `systemctl status aria-host-agent` · `journalctl -u aria-host-agent -f` Die Binary sucht er unter `dist/aria-host-agent` bzw. `./aria-host-agent` (oder 2. Argument). Braucht `dialog` für den Dateibrowser (bietet die Installation an). +### Windows-Dienst (setup.exe) + +`aria-host-agent-setup.exe` (aus `build-win.sh` oder dem Gitea-Release) als +Administrator ausführen. Der Installer: +- kopiert die `.exe` nach `%ProgramFiles%\ARIA Host-Agent`, +- legt `%ProgramData%\ARIA-Host-Agent\.env` an (nur falls noch keine da ist), +- richtet über **nssm** den Dienst **ARIA Host-Agent** ein (Autostart) und startet ihn. + +Danach die `.env` unter `%ProgramData%\ARIA-Host-Agent\` mit RVS-Zugang + +`CONTROL_ENABLED=true` füllen und den Dienst neu starten (`services.msc` → +*ARIA Host-Agent*, oder `nssm restart ARIAHostAgent`). Deinstallation über +*Apps & Features* → *ARIA Host-Agent* (die `.env` in ProgramData bleibt erhalten). + +## Release (Binaries als Gitea-Assets) + +`release_agent.sh ` baut alles Docker-Baubare und hängt es als +**Release-Asset** an den Tag `agent-v` — nichts landet im Git-Tree: + +```bash +./release_agent.sh 0.2.0 # Linux + Android + Windows (Wine) +SKIP_WINDOWS=1 ./release_agent.sh 0.2.0 # ohne Windows (schneller) +``` + +Assets: `aria-host-agent-linux-x64`, `aria-host-agent-android-agent-v.apk`, +`aria-host-agent-windows.exe`, `aria-host-agent-windows-setup.exe`. **macOS** ist +nicht Docker-baubar — auf einem Mac `./build-native.sh` laufen lassen und das +Ergebnis vor dem Release nach `dist/aria-host-agent-macos` legen, dann nimmt das +Skript es automatisch mit. Gitea-Zugang via `.env`/Umgebung (`GITEA_URL`, +`GITEA_REPO`, `GITEA_USER`), Kennwort wird abgefragt. + ## TLS / SNI — Agent im selben Netz wie der RVS Steht der Rechner im **selben Netz wie der RVS** (z.B. Rechenzentrum) und soll diff --git a/host-agent/build-win.sh b/host-agent/build-win.sh new file mode 100755 index 0000000..6cd8f4f --- /dev/null +++ b/host-agent/build-win.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Baut die Windows-.exe + setup.exe des Host-Agents AUF LINUX (Wine im Docker). +# ./build-win.sh [version] +# Ergebnis: +# dist/aria-host-agent.exe (Konsolen-Binary) +# dist/aria-host-agent-setup.exe (Installer: richtet Windows-Dienst ein) +# +# Hinweis: Der erste Lauf zieht das tobix/pywine-Image (~1-2 GB) + baut die +# Wine-Umgebung — das dauert. Folge-Builds sind schnell. +set -euo pipefail +cd "$(dirname "$0")" +VERSION="${1:-0.0.0}" +mkdir -p dist +docker build -f Dockerfile.win --build-arg VERSION="$VERSION" -t aria-host-agent-win . +docker run --rm -v "$(pwd)/dist:/out" aria-host-agent-win +echo +echo "Fertig: dist/aria-host-agent.exe + dist/aria-host-agent-setup.exe" diff --git a/host-agent/host_agent.py b/host-agent/host_agent.py index 1b72fca..0f713de 100644 --- a/host-agent/host_agent.py +++ b/host-agent/host_agent.py @@ -64,7 +64,13 @@ def _load_dotenv() -> None: """Laedt eine .env neben der Binary/dem Script (oder im CWD) in os.environ. Bereits gesetzte Werte gewinnen. Kein python-dotenv noetig.""" here = os.path.dirname(os.path.abspath(__file__)) - for path in (os.path.join(here, ".env"), os.path.join(os.getcwd(), ".env")): + # PyInstaller-onefile: __file__ liegt im Temp-Extract-Dir, NICHT beim .exe/ + # Binary — deshalb zusaetzlich sys.executable-Ordner (echter Binary-Ort) und + # das CWD (z.B. der ProgramData-Ordner, den der Windows-Dienst als AppDir nutzt). + exe_dir = os.path.dirname(os.path.abspath(sys.executable)) + seen = set() + candidates = [os.path.join(d, ".env") for d in (exe_dir, here, os.getcwd())] + for path in [p for p in candidates if not (p in seen or seen.add(p))]: if not os.path.isfile(path): continue try: diff --git a/host-agent/release_agent.sh b/host-agent/release_agent.sh index 6a4d71d..78f40bb 100755 --- a/host-agent/release_agent.sh +++ b/host-agent/release_agent.sh @@ -78,17 +78,33 @@ echo -e " ${GREEN}✓${NC} Linux-Binary ($(du -h "$LINUX_BIN" | cut -f1))" ( cd android && ./build.sh >/dev/null ) APK="$SCRIPT_DIR/android/dist/aria-android-agent.apk" [ -f "$APK" ] || { echo -e "${RED}APK fehlt: $APK${NC}"; exit 1; } -echo -e " ${GREEN}✓${NC} Android-APK ($(du -h "$APK" | cut -f1))\n" +echo -e " ${GREEN}✓${NC} Android-APK ($(du -h "$APK" | cut -f1))" + +# Windows (.exe + setup.exe) via Wine im Docker. Dauert (pywine-Image ~1-2 GB) — +# mit SKIP_WINDOWS=1 ./release_agent.sh ueberspringbar. +if [ "${SKIP_WINDOWS:-0}" = "1" ]; then + echo -e " ${YELLOW}Windows-Build uebersprungen (SKIP_WINDOWS=1)${NC}" +else + echo -e " ${CYAN}…${NC} Windows-.exe + setup.exe bauen (Wine, kann dauern)" + if ./build-win.sh "$VERSION" >/dev/null 2>&1; then + echo -e " ${GREEN}✓${NC} Windows-.exe + setup.exe" + else + echo -e " ${YELLOW}Windows-Build fehlgeschlagen — Release laeuft ohne Windows weiter.${NC}" + echo -e " ${YELLOW}(Einzeln testen: ./build-win.sh $VERSION)${NC}" + fi +fi +echo "" # Asset-Liste aufbauen: "lokaler_pfad::asset-name" ASSETS=( "$LINUX_BIN::aria-host-agent-linux-x64" "$APK::aria-host-agent-android-$TAG.apk" ) -# Optionale, vorgebaute native Artefakte (nur wenn vorhanden): -[ -f "$SCRIPT_DIR/dist/aria-host-agent-macos" ] && ASSETS+=("$SCRIPT_DIR/dist/aria-host-agent-macos::aria-host-agent-macos") -[ -f "$SCRIPT_DIR/dist/aria-host-agent.exe" ] && ASSETS+=("$SCRIPT_DIR/dist/aria-host-agent.exe::aria-host-agent-windows.exe") -[ -f "$SCRIPT_DIR/dist/aria-host-agent-setup.exe" ] && ASSETS+=("$SCRIPT_DIR/dist/aria-host-agent-setup.exe::aria-host-agent-setup.exe") +# Native Artefakte (nur wenn vorhanden): Windows aus build-win.sh, macOS +# vorgebaut (build-native.sh auf einem Mac -> dist/aria-host-agent-macos legen). +[ -f "$SCRIPT_DIR/dist/aria-host-agent-macos" ] && ASSETS+=("$SCRIPT_DIR/dist/aria-host-agent-macos::aria-host-agent-macos") +[ -f "$SCRIPT_DIR/dist/aria-host-agent.exe" ] && ASSETS+=("$SCRIPT_DIR/dist/aria-host-agent.exe::aria-host-agent-windows.exe") +[ -f "$SCRIPT_DIR/dist/aria-host-agent-setup.exe" ] && ASSETS+=("$SCRIPT_DIR/dist/aria-host-agent-setup.exe::aria-host-agent-windows-setup.exe") # ── Git-Tag ────────────────────────────────────────────────────────── echo -e "${GREEN}[3/5] Git-Tag $TAG...${NC}" diff --git a/host-agent/windows/installer.nsi b/host-agent/windows/installer.nsi new file mode 100644 index 0000000..0811744 --- /dev/null +++ b/host-agent/windows/installer.nsi @@ -0,0 +1,83 @@ +; ARIA Host-Agent — Windows-Installer (NSIS, auf Linux mit makensis gebaut). +; +; Installiert die Agent-.exe nach %ProgramFiles%\ARIA Host-Agent, legt eine .env +; in %ProgramData%\ARIA-Host-Agent an (nur falls noch keine da ist — User-Config +; bleibt erhalten) und richtet einen automatisch startenden Windows-Dienst via +; nssm ein. Der Dienst laeuft mit AppDirectory = ProgramData-Ordner, damit der +; Agent die .env von dort (aus dem CWD) liest. + +!ifndef VERSION + !define VERSION "0.0.0" +!endif +!define SVC "ARIAHostAgent" + +Name "ARIA Host-Agent ${VERSION}" +OutFile "aria-host-agent-setup.exe" +InstallDir "$PROGRAMFILES64\ARIA Host-Agent" +RequestExecutionLevel admin +Unicode true +ShowInstDetails show +ShowUninstDetails show + +Var DataDir + +Page directory +Page instfiles +UninstPage uninstConfirm +UninstPage instfiles + +Section "Install" + SetOutPath "$INSTDIR" + File "aria-host-agent.exe" + File "nssm.exe" + + ; ProgramData-Ordner fuer die .env bestimmen + ReadEnvStr $0 "ProgramData" + StrCmp $0 "" 0 +2 + StrCpy $0 "$PROFILE" ; Fallback, falls %ProgramData% fehlt + StrCpy $DataDir "$0\ARIA-Host-Agent" + CreateDirectory "$DataDir" + + ; .env nur schreiben, wenn noch keine existiert (User-Config nicht ueberschreiben) + IfFileExists "$DataDir\.env" env_done 0 + FileOpen $1 "$DataDir\.env" w + FileWrite $1 "# ARIA Host-Agent — Konfiguration (dieser Windows-Dienst liest diese Datei).$\r$\n" + FileWrite $1 "# Nach dem Aendern den Dienst neu starten: services.msc -> ARIA Host-Agent.$\r$\n" + FileWrite $1 "RVS_HOST=rvs.example.de$\r$\n" + FileWrite $1 "RVS_PORT=443$\r$\n" + FileWrite $1 "RVS_TLS=true$\r$\n" + FileWrite $1 "RVS_TLS_FALLBACK=true$\r$\n" + FileWrite $1 "RVS_TOKEN=$\r$\n" + FileWrite $1 "RVS_SNI=$\r$\n" + FileWrite $1 "HOST_NAME=$\r$\n" + FileWrite $1 "CONTROL_ENABLED=true$\r$\n" + FileClose $1 + env_done: + + ; Dienst (neu) einrichten — evtl. alten sauber entfernen, dann installieren + nsExec::ExecToLog '"$INSTDIR\nssm.exe" stop ${SVC}' + nsExec::ExecToLog '"$INSTDIR\nssm.exe" remove ${SVC} confirm' + nsExec::ExecToLog '"$INSTDIR\nssm.exe" install ${SVC} "$INSTDIR\aria-host-agent.exe"' + nsExec::ExecToLog '"$INSTDIR\nssm.exe" set ${SVC} AppDirectory "$DataDir"' + nsExec::ExecToLog '"$INSTDIR\nssm.exe" set ${SVC} DisplayName "ARIA Host-Agent"' + nsExec::ExecToLog '"$INSTDIR\nssm.exe" set ${SVC} Description "ARIA-Fernsteuerung dieses Rechners (RVS-Agent)."' + nsExec::ExecToLog '"$INSTDIR\nssm.exe" set ${SVC} Start SERVICE_AUTO_START' + nsExec::ExecToLog '"$INSTDIR\nssm.exe" start ${SVC}' + + ; Uninstaller + Eintrag unter "Apps & Features" + WriteUninstaller "$INSTDIR\uninstall.exe" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\ARIAHostAgent" "DisplayName" "ARIA Host-Agent" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\ARIAHostAgent" "DisplayVersion" "${VERSION}" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\ARIAHostAgent" "UninstallString" '"$INSTDIR\uninstall.exe"' +SectionEnd + +Section "Uninstall" + nsExec::ExecToLog '"$INSTDIR\nssm.exe" stop ${SVC}' + nsExec::ExecToLog '"$INSTDIR\nssm.exe" remove ${SVC} confirm' + Delete "$INSTDIR\aria-host-agent.exe" + Delete "$INSTDIR\nssm.exe" + Delete "$INSTDIR\uninstall.exe" + RMDir "$INSTDIR" + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\ARIAHostAgent" + ; Die .env in %ProgramData%\ARIA-Host-Agent bleibt bewusst erhalten (User-Config). +SectionEnd