Files
NexaVPN/.gitea/workflows/windows-desktop-client.yml
nessi b4b3494e17 feat: add optional app version input to Windows desktop client build workflow with dynamic artifact naming
Add app_version workflow_dispatch input for specifying custom version during manual builds. Implement version application step that validates input format and updates package.json, tauri.conf.json, and Cargo.toml with requested version string.

Add artifact name suffix based on provided version to distinguish versioned build outputs. Append version to NexaVPN-windows-installer and NexaVPN-windows
2026-03-24 18:58:22 +01:00

190 lines
6.3 KiB
YAML

name: Build Windows Desktop Client
on:
workflow_dispatch:
inputs:
app_version:
description: "Optional app version for this build, e.g. 0.1.3"
required: false
default: ""
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: Apply requested app version
if: ${{ github.event.inputs.app_version != '' }}
run: |
set -euo pipefail
VERSION="${{ github.event.inputs.app_version }}"
export VERSION
case "${VERSION}" in
*[!0-9A-Za-z.+-]*|'')
echo "Invalid app_version: ${VERSION}"
exit 1
;;
esac
node -e '
const fs = require("fs");
const version = process.env.VERSION;
const pkgPath = "package.json";
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
pkg.version = version;
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
'
node -e '
const fs = require("fs");
const version = process.env.VERSION;
const configPath = "src-tauri/tauri.conf.json";
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
config.version = version;
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
'
perl -0pi -e 's/^version = ".*?"$/version = "'"${VERSION}"'"/m' src-tauri/Cargo.toml
echo "Using app version ${VERSION}"
- 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: Prepare artifact names
run: |
set -euo pipefail
VERSION="${{ github.event.inputs.app_version }}"
if [ -n "${VERSION}" ]; then
echo "ARTIFACT_SUFFIX=-${VERSION}" >> "${GITHUB_ENV}"
else
echo "ARTIFACT_SUFFIX=" >> "${GITHUB_ENV}"
fi
- 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${{ env.ARTIFACT_SUFFIX }}
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${{ env.ARTIFACT_SUFFIX }}
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