name: Build Windows Installer on: release: types: [published] workflow_dispatch: inputs: version: description: 'Version' required: false isDebug: description: 'Build debug version with console output' type: boolean default: false env: JAVA_DIST: 'temurin' JAVA_VERSION: '23.0.1+11' OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_windows-x64_bin-jmods.zip' OPENJFX_JMODS_AMD64_HASH: 'f9376d200f5c5b85327d575c1ec1482e6455f19916577f7e2fc9be2f48bb29b6' WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.0/winfsp-2.0.23075.msi' WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/latest/download/winfsp-uninstaller.exe' defaults: run: shell: bash jobs: get-version: uses: ./.github/workflows/get-version.yml with: version: ${{ inputs.version }} build-msi: name: Build .msi Installer runs-on: windows-latest needs: [get-version] env: LOOPBACK_ALIAS: 'cryptomator-vault' WIN_CONSOLE_FLAG: '' steps: - name: Upgrade WIX to latest version run: choco install wixtoolset --version 3.14.1 shell: pwsh - uses: actions/checkout@v4 - name: Setup Java uses: actions/setup-java@v4 with: distribution: ${{ env.JAVA_DIST }} java-version: ${{ env.JAVA_VERSION }} check-latest: true cache: 'maven' - name: Download and extract JavaFX jmods from Gluon #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 --output jfxjmods.zip -L "${{ env.OPENJFX_JMODS_AMD64 }}" if(!(Get-FileHash -Path jfxjmods.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 jfxjmods.zip -DestinationPath jfxjmods Get-ChildItem -Path jfxjmods -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 run: | JMOD_VERSION_AMD64=$(jmod describe jfxjmods/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=${{ needs.get-version.outputs.semVerStr }} - name: Run maven run: mvn -B clean package -Pwin -DskipTests -Djavafx.platform=win - name: Patch target dir run: | cp LICENSE.txt target cp target/cryptomator-*.jar target/mods - 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 "jfxjmods;${JAVA_HOME}/jmods" --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.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler --strip-native-commands --no-header-files --no-man-pages --strip-debug --compress zip-0 - name: Change win-console flag if debug is active if: ${{ inputs.isDebug }} run: echo "WIN_CONSOLE_FLAG=--win-console" >> $GITHUB_ENV - 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 - 2025 Skymatic GmbH" --app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}" --java-options "--enable-preview" --java-options "--enable-native-access=org.cryptomator.jfuse.win,org.cryptomator.integrations.win" --java-options "-Xss5m" --java-options "-Xmx256m" --java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\"" --java-options "-Dfile.encoding=\"utf-8\"" --java-options "-Djava.net.useSystemProxies=true" --java-options "-Dcryptomator.logDir=\"@{localappdata}/Cryptomator\"" --java-options "-Dcryptomator.pluginDir=\"@{appdata}/Cryptomator/Plugins\"" --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=\"${{ env.LOOPBACK_ALIAS }}\"" --java-options "-Dcryptomator.showTrayIcon=true" --java-options "-Dcryptomator.buildNumber=\"msi-${{ needs.get-version.outputs.revNum }}\"" --java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=\"Cryptomator\"" --java-options "-Dcryptomator.integrationsWin.keychainPaths=\"@{appdata}/Cryptomator/keychain.json;@{userhome}/AppData/Roaming/Cryptomator/keychain.json\"" --java-options "-Djavafx.verbose=${{ inputs.isDebug }}" --resource-dir dist/win/resources --icon dist/win/resources/Cryptomator.ico ${WIN_CONSOLE_FLAG} - name: Patch Application Directory run: | cp dist/win/contrib/* appdir/Cryptomator - name: Set LOOPBACK_ALIAS in patchWebDAV.bat shell: pwsh run: | $patchScript = "appdir\Cryptomator\patchWebDAV.bat" try { (Get-Content $patchScript ) -replace '::REPLACE ME', "SET LOOPBACK_ALIAS=`"${{ env.LOOPBACK_ALIAS}}`"" | Set-Content $patchScript } catch { Write-Host "Failed to set LOOPBACK_ALIAS for patchWebDAV.bat" exit 1 } - name: Fix permissions run: attrib -r appdir/Cryptomator/Cryptomator.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: Codesign uses: skymatic/code-sign-action@v3 with: certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} password: ${{ secrets.WIN_CODESIGN_P12_PW }} certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A description: Cryptomator timestampUrl: 'http://timestamp.digicert.com' folder: appdir recursive: true - 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 "-Djavafx.platform=win" "-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 - 2025 Skymatic GmbH" --app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum}}" --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: Codesign MSI uses: skymatic/code-sign-action@v3 with: certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} password: ${{ secrets.WIN_CODESIGN_P12_PW }} certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A description: Cryptomator Installer timestampUrl: 'http://timestamp.digicert.com' folder: installer - name: Add possible alpha/beta tags to installer name run: mv installer/Cryptomator-*.msi Cryptomator-${{ needs.get-version.outputs.semVerStr }}-x64.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@v4 with: name: msi path: | Cryptomator-*.msi Cryptomator-*.asc if-no-files-found: error build-exe: name: Build .exe installer runs-on: windows-latest needs: [get-version, build-msi] steps: - uses: actions/checkout@v4 - name: Download .msi uses: actions/download-artifact@v4 with: name: msi 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 - uses: actions/setup-java@v4 with: distribution: ${{ env.JAVA_DIST }} java-version: ${{ env.JAVA_VERSION }} check-latest: true cache: 'maven' - name: Generate license for exe run: > mvn -B license:add-third-party "-Djavafx.platform=win" "-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 --output dist/win/bundle/resources/winfsp.msi -L ${{ env.WINFSP_MSI }} shell: pwsh - name: Download Legacy-WinFsp uninstaller run: | curl --output dist/win/bundle/resources/winfsp-uninstaller.exe -L ${{ env.WINFSP_UNINSTALLER }} shell: pwsh - name: Compile to wixObj file run: > "${WIX}/bin/candle.exe" dist/win/bundle/bundleWithWinfsp.wxs -ext WixBalExtension -ext WixUtilExtension -out dist/win/bundle/ -dBundleVersion="${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}" -dBundleVendor="Skymatic GmbH" -dBundleCopyright="(C) 2016 - 2025 Skymatic GmbH" -dAboutUrl="https://cryptomator.org" -dHelpUrl="https://cryptomator.org/contact" -dUpdateUrl="https://cryptomator.org/downloads/" - name: Create executable with linker run: > "${WIX}/bin/light.exe" -b dist/win/ dist/win/bundle/bundleWithWinfsp.wixobj -ext WixBalExtension -ext WixUtilExtension -out installer/unsigned/Cryptomator-Installer.exe - name: Detach burn engine in preparation to sign run: > "${WIX}/bin/insignia.exe" -ib installer/unsigned/Cryptomator-Installer.exe -o tmp/engine.exe - name: Codesign burn engine uses: skymatic/code-sign-action@v3 with: certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} password: ${{ secrets.WIN_CODESIGN_P12_PW }} certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A description: Cryptomator Installer timestampUrl: 'http://timestamp.digicert.com' folder: tmp - name: Reattach signed burn engine to installer run : > "${WIX}/bin/insignia.exe" -ab tmp/engine.exe installer/unsigned/Cryptomator-Installer.exe -o installer/Cryptomator-Installer.exe - name: Codesign EXE uses: skymatic/code-sign-action@v3 with: certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} password: ${{ secrets.WIN_CODESIGN_P12_PW }} certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A description: Cryptomator Installer timestampUrl: 'http://timestamp.digicert.com' folder: installer - name: Add possible alpha/beta tags to installer name run: mv installer/Cryptomator-Installer.exe Cryptomator-${{ needs.get-version.outputs.semVerStr }}-x64.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@v4 with: name: exe path: | Cryptomator-*.exe Cryptomator-*.asc if-no-files-found: error publish: name: Publish installers to the github release if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published' runs-on: ubuntu-latest needs: [build-msi, build-exe] outputs: download-url-msi: ${{ fromJSON(steps.publish.outputs.assets)[0].browser_download_url }} download-url-exe: ${{ fromJSON(steps.publish.outputs.assets)[1].browser_download_url }} steps: - name: Download installers uses: actions/download-artifact@v4 with: merge-multiple: true - name: Publish .msi on GitHub Releases id: publish uses: softprops/action-gh-release@v2 with: fail_on_unmatched_files: true token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} # do not change ordering of filelist, required for correct job output files: | *.msi *.exe *.asc allowlist-msi: uses: ./.github/workflows/av-whitelist.yml needs: [publish] with: url: ${{ needs.publish.outputs.download-url-msi }} secrets: inherit allowlist-exe: uses: ./.github/workflows/av-whitelist.yml needs: [publish] with: url: ${{ needs.publish.outputs.download-url-exe }} secrets: inherit notify-winget: name: Notify for winget-release if: needs.get-version.outputs.versionType == 'stable' needs: [publish, get-version] runs-on: ubuntu-latest steps: - name: Slack Notification uses: rtCamp/action-slack-notify@v2 env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_USERNAME: 'Cryptobot' SLACK_ICON: false SLACK_ICON_EMOJI: ':bot:' SLACK_CHANNEL: 'cryptomator-desktop' SLACK_TITLE: "MSI of ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} published." SLACK_MESSAGE: "Ready to ." SLACK_FOOTER: false MSG_MINIMAL: true