name: Build Windows Desktop Client on: workflow_dispatch: jobs: build-windows-client: name: Build Windows Client runs-on: ubuntu-latest defaults: run: shell: bash working-directory: desktop-client steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: "22" - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: targets: x86_64-pc-windows-msvc - name: Install system dependencies run: | sudo apt-get update sudo apt-get install -y \ build-essential \ clang \ lld \ llvm \ curl \ wget \ file \ nsis \ osslsigncode \ pkg-config \ libssl-dev \ libayatana-appindicator3-dev \ librsvg2-dev \ patchelf - name: Install cargo-xwin run: | if ! cargo xwin --version >/dev/null 2>&1; then cargo install --locked cargo-xwin fi - name: Install desktop client dependencies run: npm install - name: Build bundled Windows tunnel helper run: npm run helper:windows-x64 - name: Prepare signing certificate if: ${{ secrets.WINDOWS_SIGN_PFX_B64 != '' && secrets.WINDOWS_SIGN_PFX_PASSWORD != '' }} run: | printf '%s' "${{ secrets.WINDOWS_SIGN_PFX_B64 }}" | base64 -d > /tmp/nexavpn-signing-cert.pfx - name: Sign bundled Windows tunnel helper if: ${{ secrets.WINDOWS_SIGN_PFX_B64 != '' && secrets.WINDOWS_SIGN_PFX_PASSWORD != '' }} run: | set -euo pipefail timestamp_url="${{ secrets.WINDOWS_SIGN_TIMESTAMP_URL }}" if [ -z "${timestamp_url}" ]; then timestamp_url="http://timestamp.digicert.com" fi helper_path="src-tauri/bundled/windows-x64/nexavpn-tunnel-helper.exe" signed_path="${helper_path}.signed" osslsigncode sign \ -pkcs12 /tmp/nexavpn-signing-cert.pfx \ -pass "${{ secrets.WINDOWS_SIGN_PFX_PASSWORD }}" \ -h sha256 \ -ts "${timestamp_url}" \ -in "${helper_path}" \ -out "${signed_path}" mv "${signed_path}" "${helper_path}" - name: Build Windows installer run: npm run tauri:build:windows-x64:linux - name: Sign Windows desktop executable and installer if: ${{ secrets.WINDOWS_SIGN_PFX_B64 != '' && secrets.WINDOWS_SIGN_PFX_PASSWORD != '' }} run: | set -euo pipefail timestamp_url="${{ secrets.WINDOWS_SIGN_TIMESTAMP_URL }}" if [ -z "${timestamp_url}" ]; then timestamp_url="http://timestamp.digicert.com" fi sign_file() { local input_path="$1" local output_path="${input_path}.signed" osslsigncode sign \ -pkcs12 /tmp/nexavpn-signing-cert.pfx \ -pass "${{ secrets.WINDOWS_SIGN_PFX_PASSWORD }}" \ -h sha256 \ -ts "${timestamp_url}" \ -in "${input_path}" \ -out "${output_path}" mv "${output_path}" "${input_path}" } sign_file "src-tauri/target/x86_64-pc-windows-msvc/release/nexavpn-desktop.exe" shopt -s nullglob for installer in src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe; do sign_file "${installer}" done - name: Cleanup signing certificate if: ${{ always() && secrets.WINDOWS_SIGN_PFX_B64 != '' && secrets.WINDOWS_SIGN_PFX_PASSWORD != '' }} run: rm -f /tmp/nexavpn-signing-cert.pfx - name: Upload Windows installer uses: christopherhx/gitea-upload-artifact@v4 with: name: NexaVPN-windows-installer path: | desktop-client/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe desktop-client/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*.msi if-no-files-found: error - name: Upload raw Windows build outputs uses: christopherhx/gitea-upload-artifact@v4 with: name: NexaVPN-windows-raw-build path: | desktop-client/src-tauri/target/x86_64-pc-windows-msvc/release/nexavpn-desktop.exe desktop-client/src-tauri/bundled/windows-x64/nexavpn-tunnel-helper.exe desktop-client/src-tauri/bundled/windows-x64/wireguard-installer.msi if-no-files-found: error