name: Build Windows Installer on: schedule: - cron: '0 19 20 * *' workflow_call: inputs: semVerNum: type: string description: 'The Major.Minor.Patch part of the version' required: true revisionNum: type: string description: 'The revision number' required: true semVerSuffix: type: string description: 'The suffix of the version, including dash' required: true sign: description: 'Sign binaries' default: true type: boolean upload-to-draft: type: boolean default: true outputs: sha256-msi: description: "SHA256 sum of the x64 msi" value: ${{ jobs.build-msi.outputs.sha256sum}} sha256-exe: description: "SHA256 sum of the x64 exe" value: ${{ jobs.build-exe.outputs.sha256sum}} workflow_dispatch: inputs: semVerNum: description: 'The Major.Minor.Patch part of the version' required: false revisionNum: description: 'The revision number' required: false semVerSuffix: description: 'The suffix of the version, including dash' required: false default: '-SNAPSHOT' sign: description: 'Sign binaries' required: false type: boolean default: false push: branches-ignore: - 'dependabot/**' paths: - '.github/workflows/win-exe.yml' - 'dist/win/**' env: VERSION_NUM: ${{ inputs.semVerNum || '99.99.99'}} REVISION_NUM: ${{ inputs.revisionNum || '0' }} VERSION_SUFFIX: ${{ inputs.semVerSuffix || ''}} OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/25.0.2/openjfx-25.0.2_windows-x64_bin-jmods.zip' OPENJFX_JMODS_AMD64_HASH: '33d878dfac85590c4d77c518ed413e512d34a8479d90132b230a7ddd173576b3' WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.1/winfsp-2.1.25156.msi' WINFSP_MSI_HASH: '073a70e00f77423e34bed98b86e600def93393ba5822204fac57a29324db9f7a' WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/latest/download/winfsp-uninstaller.exe' WIX_VERSION: '6.0.2' defaults: run: shell: bash jobs: build-msi: name: Build .msi Installer runs-on: ${{ matrix.os }} outputs: sha256sum: ${{ steps.sha256sum.outputs.value }} strategy: matrix: include: - arch: x64 os: windows-latest java-dist: 'zulu' #cannot use temurin, see https://github.com/cryptomator/cryptomator/issues/3824#issuecomment-2829827427 java-version: '25.0.1+8' java-package: 'jdk' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Java uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: ${{ matrix.java-dist }} java-version: ${{ matrix.java-version }} java-package: ${{ matrix.java-package }} check-latest: true cache: 'maven' - name: Install wix and extensions run: | dotnet tool install --global wix --version ${WIX_VERSION} wix.exe extension add --global WixToolset.UI.wixext/${WIX_VERSION} wix.exe extension add --global WixToolset.Util.wixext/${WIX_VERSION} env: WIX_VERSION: ${{ env.WIX_VERSION }} - name: Download and extract JavaFX jmods from Gluon if: matrix.arch == 'x64' #In the last step we move all jmods files a dir level up because jmods are placed inside a directory in the zip run: | curl --silent --fail-with-body --proto "=https" -L "${{ env.OPENJFX_JMODS_AMD64 }}" --output openjfx-jmods.zip if(!(Get-FileHash -Path openjfx-jmods.zip -Algorithm SHA256).Hash.ToLower().equals("${{ env.OPENJFX_JMODS_AMD64_HASH }}")) { throw "Wrong checksum of JMOD archive downloaded from ${{ env.OPENJFX_JMODS_AMD64 }}."; } Expand-Archive -Path openjfx-jmods.zip -DestinationPath openjfx-jmods Get-ChildItem -Path openjfx-jmods -Recurse -Filter "*.jmod" | ForEach-Object { Move-Item -Path $_ -Destination $_.Directory.Parent} shell: pwsh - name: Ensure major jfx version in pom and in jmods is the same if: matrix.arch == 'x64' run: | JMOD_VERSION_AMD64=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1) JMOD_VERSION_AMD64=${JMOD_VERSION_AMD64#*@} JMOD_VERSION_AMD64=${JMOD_VERSION_AMD64%%.*} POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout) POM_JFX_VERSION=${POM_JFX_VERSION#*@} POM_JFX_VERSION=${POM_JFX_VERSION%%.*} if [ $POM_JFX_VERSION -ne $JMOD_VERSION_AMD64 ]; then >&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != amd64 jmod version (${JMOD_VERSION_AMD64})" exit 1 fi - name: Set version run: mvn versions:set -DnewVersion="${VERSION_NUM}${VERSION_SUFFIX}" - name: Run maven run: mvn -B clean package -Pwin -DskipTests - name: Patch target dir run: | cp LICENSE.txt target cp target/cryptomator-*.jar target/mods - name: Run jlink with help option id: jep-493-check run: | JMOD_PATHS="openjfx-jmods" if ! $(${JAVA_HOME}/bin/jlink --help | grep -q "Linking from run-time image enabled"); then JMOD_PATHS="${JAVA_HOME}/jmods;${JMOD_PATHS}" fi echo "jmod_paths=${JMOD_PATHS}" >> "$GITHUB_OUTPUT" - name: Run jlink # Remark: no compression is applied for improved build compression later (here msi) run: > ${JAVA_HOME}/bin/jlink --verbose --output runtime --module-path "${{ steps.jep-493-check.outputs.jmod_paths }}" --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.crypto.mscapi,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler --strip-native-commands --no-header-files --no-man-pages --strip-debug --compress zip-0 - name: Run jpackage run: > ${JAVA_HOME}/bin/jpackage --verbose --type app-image --runtime-image runtime --input target/libs --module-path target/mods --module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator --dest appdir --name Cryptomator --vendor "Skymatic GmbH" --copyright "(C) 2016 - 2026 Skymatic GmbH" --app-version "${VERSION_NUM}.${REVISION_NUM}" --java-options "--enable-preview" --java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.win,org.cryptomator.integrations.win" --java-options "-Xss5m" --java-options "-Xmx256m" --java-options "-Dcryptomator.appVersion=\"${VERSION_NUM}${VERSION_SUFFIX}\"" --java-options "-Dfile.encoding=\"utf-8\"" --java-options "-Djava.net.useSystemProxies=true" --java-options "-Dcryptomator.adminConfigPath=\"C:/ProgramData/Cryptomator/config.properties\"" --java-options "-Dcryptomator.logDir=\"@{localappdata}/Cryptomator\"" --java-options "-Dcryptomator.settingsPath=\"@{appdata}/Cryptomator/settings.json;@{userhome}/AppData/Roaming/Cryptomator/settings.json\"" --java-options "-Dcryptomator.p12Path=\"@{appdata}/Cryptomator/key.p12;@{userhome}/AppData/Roaming/Cryptomator/key.p12\"" --java-options "-Dcryptomator.ipcSocketPath=\"@{localappdata}/Cryptomator/ipc.socket\"" --java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Cryptomator\"" --java-options "-Dcryptomator.loopbackAlias=\"cryptomator-vault\"" --java-options "-Dcryptomator.showTrayIcon=true" --java-options "-Dcryptomator.buildNumber=\"msi-${REVISION_NUM}\"" --java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=\"Cryptomator\"" --java-options "-Dcryptomator.integrationsWin.keychainPaths=\"@{appdata}/Cryptomator/keychain.json;@{userhome}/AppData/Roaming/Cryptomator/keychain.json\"" --java-options "-Dcryptomator.integrationsWin.windowsHelloKeychainPaths=\"@{appdata}/Cryptomator/windowsHelloKeychain.json\"" --java-options "-Dcryptomator.disableUpdateCheck=false" --java-options "-XX:ErrorFile=C:/cryptomator/cryptomator_crash.log" --java-options "-Dcryptomator.hub.enableTrustOnFirstUse=true" --resource-dir dist/win/resources --icon dist/win/resources/Cryptomator.ico --add-launcher "Cryptomator (Debug)=dist/win/debug-launcher.properties" - name: Patch Application Directory run: | cp dist/win/contrib/* appdir/Cryptomator - name: Fix permissions run: | attrib -r appdir/Cryptomator/Cryptomator.exe attrib -r "appdir/Cryptomator/Cryptomator (Debug).exe" shell: pwsh - name: Extract jars with DLLs for Codesigning shell: pwsh run: | Add-Type -AssemblyName "System.io.compression.filesystem" $jarFolder = Resolve-Path ".\appdir\Cryptomator\app\mods" $jarExtractDir = New-Item -Path ".\appdir\jar-extract" -ItemType Directory #for all jars inspect Get-ChildItem -Path $jarFolder -Filter "*.jar" | ForEach-Object { $jar = [Io.compression.zipfile]::OpenRead($_.FullName) if (@($jar.Entries | Where-Object {$_.Name.ToString().EndsWith(".dll")} | Select-Object -First 1).Count -gt 0) { #jars containing dlls extract Set-Location $jarExtractDir Expand-Archive -Path $_.FullName } $jar.Dispose() } - name: Extract wixhelper.dll for Codesigning #see https://github.com/cryptomator/cryptomator/issues/3130 shell: pwsh run: | New-Item -Path appdir/jpackage-jmod -ItemType Directory & $env:JAVA_HOME\bin\jmod.exe extract --dir jpackage-jmod "${env:JAVA_HOME}\jmods\jdk.jpackage.jmod" Get-ChildItem -Recurse -Path "jpackage-jmod" -File wixhelper.dll | Select-Object -Last 1 | Copy-Item -Destination "appdir" - name: Sign DLLs with Azure Trusted Signing if: inputs.sign || github.event_name == 'schedule' uses: ./.github/actions/win-sign-action with: base-dir: ${{ github.workspace }}\appdir recursive: true append-signature: true tenant-id: ${{ secrets.AZURE_TENANT_ID }} client-id: ${{ secrets.AZURE_CLIENT_ID }} client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} - name: Replace DLLs inside jars with signed ones shell: pwsh run: | $jarExtractDir = Resolve-Path ".\appdir\jar-extract" $jarFolder = Resolve-Path ".\appdir\Cryptomator\app\mods" Get-ChildItem -Path $jarExtractDir | ForEach-Object { $jarName = $_.Name $jarFile = "${jarFolder}\${jarName}.jar" Set-Location $_ Get-ChildItem -Path $_ -Recurse -File "*.dll" | ForEach-Object { # update jar with signed dll jar --file="$jarFile" --update $(Resolve-Path -Relative -Path $_) } } - name: Generate license for MSI run: > mvn -B license:add-third-party "-Dlicense.thirdPartyFilename=license.rtf" "-Dlicense.outputDirectory=dist/win/resources" "-Dlicense.fileTemplate=dist/win/resources/licenseTemplate.ftl" "-Dlicense.includedScopes=compile" "-Dlicense.excludedGroups=^org\.cryptomator" "-Dlicense.failOnMissing=true" "-Dlicense.licenseMergesUrl=file:///${{ github.workspace }}/license/merges" shell: pwsh - name: Create MSI run: > ${JAVA_HOME}/bin/jpackage --verbose --type msi --win-upgrade-uuid bda45523-42b1-4cae-9354-a45475ed4775 --app-image appdir/Cryptomator --dest installer --name Cryptomator --vendor "Skymatic GmbH" --copyright "(C) 2016 - 2026 Skymatic GmbH" --app-version "${VERSION_NUM}.${REVISION_NUM}" --win-menu --win-dir-chooser --win-shortcut-prompt --win-update-url "https:\\cryptomator.org\downloads" --win-menu-group Cryptomator --resource-dir dist/win/resources --license-file dist/win/resources/license.rtf --file-associations dist/win/resources/FAvaultFile.properties env: JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs JP_WIXHELPER_DIR: ${{ github.workspace }}\appdir - name: Sign MSI with Azure Trusted Signing if: inputs.sign || github.event_name == 'schedule' uses: ./.github/actions/win-sign-action with: base-dir: ${{ github.workspace }}\installer file-extensions: msi description: 'Cryptomator Installer' tenant-id: ${{ secrets.AZURE_TENANT_ID }} client-id: ${{ secrets.AZURE_CLIENT_ID }} client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} - id: sha256sum run: | read -ra CMD_OUTPUT < <(sha256sum installer/Cryptomator-*.msi) echo "value=${CMD_OUTPUT[0]}" >> $GITHUB_OUTPUT - name: Add possible alpha/beta tags and architecture to installer name run: mv installer/Cryptomator-*.msi "Cryptomator-${VERSION_NUM}${VERSION_SUFFIX}-${{ matrix.arch }}.msi" - name: Create detached GPG signature with key 615D449FE6E6A235 run: | echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a Cryptomator-*.msi env: GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} - name: Upload artifacts uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: msi-${{ matrix.arch }} path: | Cryptomator-*.msi Cryptomator-*.asc if-no-files-found: error build-exe: name: Build .exe installer runs-on: ${{ matrix.os }} needs: [ build-msi ] outputs: sha256sum: ${{ steps.sha256sum.outputs.value }} strategy: matrix: include: - arch: x64 os: windows-latest executable-suffix: x64 java-dist: 'zulu' java-version: '24.0.1+9' java-package: 'jdk' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install wix and extensions run: | dotnet tool install --global wix --version ${WIX_VERSION} wix.exe extension add --global WixToolset.BootstrapperApplications.wixext/${WIX_VERSION} wix.exe extension add --global WixToolset.Util.wixext/${WIX_VERSION} env: WIX_VERSION: ${{ env.WIX_VERSION }} - name: Download .msi uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: msi-${{ matrix.arch }} path: dist/win/bundle/resources - name: Strip version info from msi file name run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi - name: Setup Java uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: ${{ matrix.java-dist }} java-version: ${{ matrix.java-version }} java-package: ${{ matrix.java-package }} check-latest: true cache: 'maven' - name: Generate license for exe run: > mvn -B license:add-third-party "-Dlicense.thirdPartyFilename=license.rtf" "-Dlicense.fileTemplate=dist/win/bundle/resources/licenseTemplate.ftl" "-Dlicense.outputDirectory=dist/win/bundle/resources" "-Dlicense.includedScopes=compile" "-Dlicense.excludedGroups=^org\.cryptomator" "-Dlicense.failOnMissing=true" "-Dlicense.licenseMergesUrl=file:///${{ github.workspace }}/license/merges" shell: pwsh - name: Download WinFsp run: | curl --silent --fail-with-body --proto "=https" -L "$env:WINFSP_MSI" --output $env:WINFSP_PATH $computedHash = (Get-FileHash -Path "$env:WINFSP_PATH" -Algorithm SHA256).Hash.ToLower() if ($computedHash -ne "$env:WINFSP_MSI_HASH") { throw "Checksum mismatch for ${env:WINFSP_PATH} (expected ${env:WINFSP_MSI_HASH}, got $computedHash)." } env: WINFSP_PATH: 'dist/win/bundle/resources/winfsp.msi' shell: pwsh - name: Download Legacy-WinFsp uninstaller run: | curl --silent --fail-with-body --proto "=https" -L ${{ env.WINFSP_UNINSTALLER }} --output dist/win/bundle/resources/winfsp-uninstaller.exe shell: pwsh - name: Create Wix Burn bundle working-directory: dist/win run: > wix build -define BundleName="Cryptomator" -define BundleVersion="${VERSION_NUM}.${REVISION_NUM}" -define BundleVendor="Skymatic GmbH" -define BundleCopyright="(C) 2016 - 2026 Skymatic GmbH" -define AboutUrl="https://cryptomator.org" -define HelpUrl="https://cryptomator.org/contact" -define UpdateUrl="https://cryptomator.org/downloads/" -ext "WixToolset.Util.wixext" -ext "WixToolset.BootstrapperApplications.wixext" ./bundle/bundleWithWinfsp.wxs -out "../../installer/Cryptomator-Installer.exe" - name: Detach burn engine in preparation to sign if: inputs.sign || github.event_name == 'schedule' run: > wix burn detach installer/Cryptomator-Installer.exe -engine tmp/engine.exe - name: Sign WiX burn engine with Azure Trusted Signing if: inputs.sign || github.event_name == 'schedule' uses: ./.github/actions/win-sign-action with: base-dir: ${{ github.workspace }}\tmp file-extensions: exe append-signature: true description: 'Cryptomator Bundle Installer' tenant-id: ${{ secrets.AZURE_TENANT_ID }} client-id: ${{ secrets.AZURE_CLIENT_ID }} client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} - name: Reattach signed burn engine to installer if: inputs.sign || github.event_name == 'schedule' shell: pwsh run: | Move-Item -Path installer/Cryptomator-Installer.exe -Destination tmp/Cryptomator-Installer.exe wix burn reattach tmp/Cryptomator-Installer.exe -engine tmp/engine.exe -o installer/Cryptomator-Installer.exe - name: Sign EXE installer with Azure Trusted Signing if: inputs.sign || github.event_name == 'schedule' uses: ./.github/actions/win-sign-action with: base-dir: ${{ github.workspace }}\installer file-extensions: exe append-signature: true description: 'Cryptomator Bundle Installer' tenant-id: ${{ secrets.AZURE_TENANT_ID }} client-id: ${{ secrets.AZURE_CLIENT_ID }} client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} - id: sha256sum run: | read -ra CMD_OUTPUT < <(sha256sum installer/Cryptomator-*.exe) echo "value=${CMD_OUTPUT[0]}" >> $GITHUB_OUTPUT - name: Add possible alpha/beta tags to installer name run: mv installer/Cryptomator-Installer.exe "Cryptomator-${VERSION_NUM}${VERSION_SUFFIX}-${{ matrix.executable-suffix }}.exe" - name: Create detached GPG signature with key 615D449FE6E6A235 run: | echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a Cryptomator-*.exe env: GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} - name: Upload artifacts uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: exe-${{ matrix.executable-suffix }} path: | Cryptomator-*.exe Cryptomator-*.asc if-no-files-found: error publish: name: Publish installers to the github release if: inputs.upload-to-draft runs-on: ubuntu-latest needs: [ build-msi, build-exe ] steps: - name: Download installers uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: merge-multiple: true - name: Publish installers on GitHub Releases id: publish uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 with: draft: true fail_on_unmatched_files: true token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} files: | *x64.msi *x64.exe *.asc