From 7f799a914dc0ce44f0c513f6332b68a363089794 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Sun, 22 Mar 2026 03:35:34 +0300 Subject: [PATCH] feat: add NSIS Windows installer to release workflow - installer.nsi: per-user install to AppData, Start Menu shortcuts, optional desktop shortcut and autostart, clean uninstall (preserves data/), Add/Remove Programs registration - build-dist-windows.sh: runs makensis after ZIP if available - release.yml: install nsis in CI, upload both ZIP and setup.exe - Fix Docker registry login (sed -E for https:// stripping) --- .gitea/workflows/release.yml | 49 ++++++++---- build-dist-windows.sh | 25 +++++- installer.nsi | 148 +++++++++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 19 deletions(-) create mode 100644 installer.nsi diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index e54a936..6776bfb 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -63,37 +63,50 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y --no-install-recommends zip libportaudio2 + sudo apt-get install -y --no-install-recommends zip libportaudio2 nsis - name: Cross-build Windows distribution run: | chmod +x build-dist-windows.sh ./build-dist-windows.sh "${{ gitea.ref_name }}" - - name: Upload build artifact + - name: Upload build artifacts uses: actions/upload-artifact@v3 with: name: LedGrab-${{ gitea.ref_name }}-win-x64 - path: build/LedGrab-*.zip + path: | + build/LedGrab-*.zip + build/LedGrab-*-setup.exe retention-days: 90 - - name: Attach ZIP to release + - name: Attach assets to release env: GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} run: | - TAG="${{ gitea.ref_name }}" RELEASE_ID="${{ needs.create-release.outputs.release_id }}" BASE_URL="${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}" + + # Upload ZIP ZIP_FILE=$(ls build/LedGrab-*.zip | head -1) - ZIP_NAME=$(basename "$ZIP_FILE") + if [ -f "$ZIP_FILE" ]; then + curl -s -X POST \ + "$BASE_URL/releases/$RELEASE_ID/assets?name=$(basename "$ZIP_FILE")" \ + -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/octet-stream" \ + --data-binary "@$ZIP_FILE" + echo "Uploaded: $(basename "$ZIP_FILE")" + fi - curl -s -X POST \ - "$BASE_URL/releases/$RELEASE_ID/assets?name=$ZIP_NAME" \ - -H "Authorization: token $GITEA_TOKEN" \ - -H "Content-Type: application/octet-stream" \ - --data-binary "@$ZIP_FILE" - - echo "Uploaded: $ZIP_NAME" + # Upload installer + SETUP_FILE=$(ls build/LedGrab-*-setup.exe 2>/dev/null | head -1) + if [ -f "$SETUP_FILE" ]; then + curl -s -X POST \ + "$BASE_URL/releases/$RELEASE_ID/assets?name=$(basename "$SETUP_FILE")" \ + -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/octet-stream" \ + --data-binary "@$SETUP_FILE" + echo "Uploaded: $(basename "$SETUP_FILE")" + fi # ── Linux tarball ────────────────────────────────────────── build-linux: @@ -165,17 +178,19 @@ jobs: run: | TAG="${{ gitea.ref_name }}" VERSION="${TAG#v}" - REGISTRY="${{ gitea.server_url }}/${{ gitea.repository }}" - # Lowercase the registry path (Docker requires it) - REGISTRY=$(echo "$REGISTRY" | tr '[:upper:]' '[:lower:]' | sed 's|https\?://||') + # Strip protocol and lowercase for Docker registry path + SERVER_HOST=$(echo "${{ gitea.server_url }}" | sed -E 's|https?://||') + REPO=$(echo "${{ gitea.repository }}" | tr '[:upper:]' '[:lower:]') + REGISTRY="${SERVER_HOST}/${REPO}" echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "registry=$REGISTRY" >> "$GITHUB_OUTPUT" + echo "server_host=$SERVER_HOST" >> "$GITHUB_OUTPUT" - name: Login to Gitea Container Registry run: | echo "${{ secrets.GITEA_TOKEN }}" | docker login \ - "$(echo '${{ gitea.server_url }}' | sed 's|https\?://||')" \ + "${{ steps.meta.outputs.server_host }}" \ -u "${{ gitea.actor }}" --password-stdin - name: Build Docker image diff --git a/build-dist-windows.sh b/build-dist-windows.sh index abd7c9e..611ab97 100644 --- a/build-dist-windows.sh +++ b/build-dist-windows.sh @@ -390,8 +390,29 @@ rm -f "$ZIP_PATH" (cd "$BUILD_DIR" && zip -rq "$ZIP_NAME" "$DIST_NAME") ZIP_SIZE=$(du -h "$ZIP_PATH" | cut -f1) + +# ── Build NSIS installer (if makensis is available) ────────── + +SETUP_NAME="LedGrab-v${VERSION_CLEAN}-win-x64-setup.exe" +SETUP_PATH="$BUILD_DIR/$SETUP_NAME" + +if command -v makensis &>/dev/null; then + echo "[9/8] Building NSIS installer..." + makensis -DVERSION="${VERSION_CLEAN}" "$SCRIPT_DIR/installer.nsi" >/dev/null 2>&1 + if [ -f "$SETUP_PATH" ]; then + SETUP_SIZE=$(du -h "$SETUP_PATH" | cut -f1) + echo " Installer: $SETUP_PATH ($SETUP_SIZE)" + else + echo " WARNING: makensis ran but installer not found at $SETUP_PATH" + fi +else + echo "[9/8] Skipping installer (makensis not found — install nsis to enable)" +fi + echo "" echo "=== Build complete ===" -echo " Archive: $ZIP_PATH" -echo " Size: $ZIP_SIZE" +echo " ZIP: $ZIP_PATH ($ZIP_SIZE)" +if [ -f "$SETUP_PATH" ]; then + echo " Installer: $SETUP_PATH ($SETUP_SIZE)" +fi echo "" diff --git a/installer.nsi b/installer.nsi new file mode 100644 index 0000000..ce2ce22 --- /dev/null +++ b/installer.nsi @@ -0,0 +1,148 @@ +; LedGrab NSIS Installer Script +; Cross-compilable on Linux: apt install nsis && makensis installer.nsi +; +; Expects the portable build to already exist at build/LedGrab/ +; (run build-dist-windows.sh first) + +!include "MUI2.nsh" +!include "FileFunc.nsh" + +; ── Metadata ──────────────────────────────────────────────── + +!define APPNAME "LedGrab" +!define DESCRIPTION "Ambient lighting system — captures screen content and drives LED strips in real time" +!define VERSIONMAJOR 0 +!define VERSIONMINOR 1 +!define VERSIONBUILD 0 + +; Set from command line: makensis -DVERSION=0.1.0 installer.nsi +!ifndef VERSION + !define VERSION "${VERSIONMAJOR}.${VERSIONMINOR}.${VERSIONBUILD}" +!endif + +Name "${APPNAME} v${VERSION}" +OutFile "build\${APPNAME}-v${VERSION}-win-x64-setup.exe" +InstallDir "$LOCALAPPDATA\${APPNAME}" +InstallDirRegKey HKCU "Software\${APPNAME}" "InstallDir" +RequestExecutionLevel user +SetCompressor /SOLID lzma + +; ── Modern UI Configuration ───────────────────────────────── + +!define MUI_ABORTWARNING +!define MUI_ICON "server\src\wled_controller\static\icon-192.png" +!define MUI_UNICON "server\src\wled_controller\static\icon-192.png" + +; ── Pages ─────────────────────────────────────────────────── + +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_COMPONENTS +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH + +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES + +!insertmacro MUI_LANGUAGE "English" + +; ── Installer Sections ────────────────────────────────────── + +Section "!${APPNAME} (required)" SecCore + SectionIn RO + + SetOutPath "$INSTDIR" + + ; Copy the entire portable build + File /r "build\LedGrab\python" + File /r "build\LedGrab\app" + File "build\LedGrab\LedGrab.bat" + + ; Create data and logs directories + CreateDirectory "$INSTDIR\data" + CreateDirectory "$INSTDIR\logs" + + ; Create uninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + ; Start Menu shortcuts + CreateDirectory "$SMPROGRAMS\${APPNAME}" + CreateShortcut "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk" "$INSTDIR\LedGrab.bat" \ + "" "" "" SW_SHOWMINIMIZED "" "${DESCRIPTION}" + CreateShortcut "$SMPROGRAMS\${APPNAME}\Uninstall.lnk" "$INSTDIR\uninstall.exe" + + ; Registry: install location + Add/Remove Programs entry + WriteRegStr HKCU "Software\${APPNAME}" "InstallDir" "$INSTDIR" + WriteRegStr HKCU "Software\${APPNAME}" "Version" "${VERSION}" + + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \ + "DisplayName" "${APPNAME}" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \ + "DisplayVersion" "${VERSION}" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \ + "UninstallString" '"$INSTDIR\uninstall.exe"' + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \ + "InstallLocation" "$INSTDIR" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \ + "Publisher" "Alexei Dolgolyov" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \ + "URLInfoAbout" "https://git.dolgolyov-family.by/alexei.dolgolyov/wled-screen-controller-mixed" + WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \ + "NoModify" 1 + WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \ + "NoRepair" 1 + + ; Calculate installed size for Add/Remove Programs + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \ + "EstimatedSize" "$0" +SectionEnd + +Section "Desktop shortcut" SecDesktop + CreateShortcut "$DESKTOP\${APPNAME}.lnk" "$INSTDIR\LedGrab.bat" \ + "" "" "" SW_SHOWMINIMIZED "" "${DESCRIPTION}" +SectionEnd + +Section "Start with Windows" SecAutostart + CreateShortcut "$SMSTARTUP\${APPNAME}.lnk" "$INSTDIR\LedGrab.bat" \ + "" "" "" SW_SHOWMINIMIZED "" "${DESCRIPTION}" +SectionEnd + +; ── Section Descriptions ──────────────────────────────────── + +!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${SecCore} \ + "Install ${APPNAME} server and all required files." + !insertmacro MUI_DESCRIPTION_TEXT ${SecDesktop} \ + "Create a shortcut on your desktop." + !insertmacro MUI_DESCRIPTION_TEXT ${SecAutostart} \ + "Start ${APPNAME} automatically when you log in." +!insertmacro MUI_FUNCTION_DESCRIPTION_END + +; ── Uninstaller ───────────────────────────────────────────── + +Section "Uninstall" + ; Remove shortcuts + Delete "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk" + Delete "$SMPROGRAMS\${APPNAME}\Uninstall.lnk" + RMDir "$SMPROGRAMS\${APPNAME}" + Delete "$DESKTOP\${APPNAME}.lnk" + Delete "$SMSTARTUP\${APPNAME}.lnk" + + ; Remove application files (but NOT data/ — preserve user config) + RMDir /r "$INSTDIR\python" + RMDir /r "$INSTDIR\app" + Delete "$INSTDIR\LedGrab.bat" + Delete "$INSTDIR\uninstall.exe" + + ; Remove logs (but keep data/) + RMDir /r "$INSTDIR\logs" + + ; Try to remove install dir (only succeeds if empty — data/ may remain) + RMDir "$INSTDIR" + + ; Remove registry keys + DeleteRegKey HKCU "Software\${APPNAME}" + DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" +SectionEnd