name: Desktop Release on: push: tags: - 'v*' - 'beta-v*' workflow_dispatch: inputs: version: description: 'Version number (e.g. 0.0.15) — used for dry-run testing without a tag' required: true default: '0.0.0-test' publish: description: 'Publish to GitHub Releases' required: true type: choice options: - never - always default: 'never' permissions: contents: write id-token: write jobs: build: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: - os: macos-latest platform: --mac - os: ubuntu-latest platform: --linux - os: windows-latest platform: --win steps: - name: Checkout uses: actions/checkout@v5 - name: Extract version id: version shell: bash run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then VERSION="${{ inputs.version }}" else TAG=${GITHUB_REF#refs/tags/} VERSION=${TAG#beta-} VERSION=${VERSION#v} fi if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$'; then echo "::error::Version '$VERSION' is not valid semver (expected X.Y.Z). Fix your tag name." exit 1 fi echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT" - name: Detect Windows signing eligibility id: sign shell: bash run: | # Sign Windows builds only on production v* tags (not beta-v*, not workflow_dispatch). # This matches the single OIDC federated credential configured in Entra ID. if [ "${{ matrix.os }}" = "windows-latest" ] \ && [ "${{ github.event_name }}" = "push" ] \ && [[ "$GITHUB_REF" == refs/tags/v* ]]; then echo "enabled=true" >> "$GITHUB_OUTPUT" echo "Windows signing: ENABLED (v* tag on windows-latest)" else echo "enabled=false" >> "$GITHUB_OUTPUT" echo "Windows signing: skipped" fi - name: Setup pnpm uses: pnpm/action-setup@v5 - name: Setup Node.js uses: actions/setup-node@v5 with: node-version: 22 cache: 'pnpm' cache-dependency-path: | surfsense_web/pnpm-lock.yaml surfsense_desktop/pnpm-lock.yaml - name: Install web dependencies run: pnpm install working-directory: surfsense_web - name: Build Next.js standalone run: pnpm build working-directory: surfsense_web env: NEXT_PUBLIC_FASTAPI_BACKEND_URL: ${{ vars.NEXT_PUBLIC_FASTAPI_BACKEND_URL }} NEXT_PUBLIC_ZERO_CACHE_URL: ${{ vars.NEXT_PUBLIC_ZERO_CACHE_URL }} NEXT_PUBLIC_DEPLOYMENT_MODE: ${{ vars.NEXT_PUBLIC_DEPLOYMENT_MODE }} NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE: ${{ vars.NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE }} NEXT_PUBLIC_POSTHOG_KEY: ${{ secrets.NEXT_PUBLIC_POSTHOG_KEY }} - name: Install desktop dependencies run: pnpm install working-directory: surfsense_desktop - name: Build Electron run: pnpm build working-directory: surfsense_desktop env: HOSTED_FRONTEND_URL: ${{ vars.HOSTED_FRONTEND_URL }} POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} POSTHOG_HOST: ${{ vars.POSTHOG_HOST }} - name: Package & Publish shell: bash run: | CMD=(pnpm exec electron-builder ${{ matrix.platform }} \ --config electron-builder.yml \ --publish "${{ inputs.publish || 'always' }}" \ -c.extraMetadata.version="${{ steps.version.outputs.VERSION }}") if [ "${{ steps.sign.outputs.enabled }}" = "true" ]; then CMD+=(-c.win.azureSignOptions.publisherName="$WINDOWS_PUBLISHER_NAME") CMD+=(-c.win.azureSignOptions.endpoint="$AZURE_CODESIGN_ENDPOINT") CMD+=(-c.win.azureSignOptions.codeSigningAccountName="$AZURE_CODESIGN_ACCOUNT") CMD+=(-c.win.azureSignOptions.certificateProfileName="$AZURE_CODESIGN_PROFILE") fi "${CMD[@]}" working-directory: surfsense_desktop env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} WINDOWS_PUBLISHER_NAME: ${{ vars.WINDOWS_PUBLISHER_NAME }} AZURE_CODESIGN_ENDPOINT: ${{ vars.AZURE_CODESIGN_ENDPOINT }} AZURE_CODESIGN_ACCOUNT: ${{ vars.AZURE_CODESIGN_ACCOUNT }} AZURE_CODESIGN_PROFILE: ${{ vars.AZURE_CODESIGN_PROFILE }} # Service principal credentials for Azure.Identity EnvironmentCredential used by the # TrustedSigning PowerShell module. Only populated when signing is enabled. # electron-builder 26 does not yet support OIDC federated tokens for Azure signing, # so we fall back to client-secret auth. Rotate AZURE_CLIENT_SECRET before expiry. AZURE_TENANT_ID: ${{ steps.sign.outputs.enabled == 'true' && secrets.AZURE_TENANT_ID || '' }} AZURE_CLIENT_ID: ${{ steps.sign.outputs.enabled == 'true' && secrets.AZURE_CLIENT_ID || '' }} AZURE_CLIENT_SECRET: ${{ steps.sign.outputs.enabled == 'true' && secrets.AZURE_CLIENT_SECRET || '' }}