Skip to content

Desktop app — build, signing, download channel (ops)

Infrastructure for building, signing, and publishing to /download/ the macOS and Windows desktop clients. Code overviews: macOS desktop, Windows desktop.

Build infrastructure — self-hosted runners

WinUI/MSIX does not build on macOS, and Xcode does not build on Windows, so each platform runs its own dedicated self-hosted GitHub Actions runner:

Runner Host Labels Workflow
win11-build Windows 11 Pro (38.180.136.249) self-hosted, Windows, X64, windows-signing .github/workflows/windows-app.yml (selfhosted job)
imacpro-macos iMac Pro (macOS 14, Intel) self-hosted, macOS .github/workflows/macos-app.yml

The runners run as services (auto-start on reboot). They run as the deploy user. The tooling (.NET 8, git, Xcode, xcodegen) is installed on the host; MSIX's makeappx/signtool come from NuGet (Microsoft.Windows.SDK. BuildTools[.MSIX]), so a system Windows SDK is not required.

Runner offline

A dispatched job requires that its runner be online — if it is offline the job is queued. Runner status: gh api repos/OWNER/REPO/actions/runners.

Download channel — /download/

Deploy uses forced-command SSH (only ci-deploy.sh), so scp to the server is not possible. Therefore binary files reach the server only via the git repo (deploy/downloads/):

runner: build + sign → deploy/downloads/{eid-mongolia.dmg, eid-mongolia-setup.msix, *.cer}
        → git commit + push (main)
staging ci-deploy: git reset --hard origin/main → nginx force-recreate
nginx (deploy/nginx/staging.conf, location ^~ /download/) → /srv/download (mount)

Web download buttons: web/src/components/landing/DesktopAppSection.tsx, web/src/app/solutions/page.tsx.

Git size

The .msix (~37MB) enters git history on every publish. If you publish frequently, migrate to Git LFS (.gitattributes: deploy/downloads/*.msix,*.dmg filter=lfs). git-lfs must be installed on the server (otherwise nginx serves the pointer file).

Re-publishing

Windows: GitHub → Actions → "Windows desktop" → Run workflow (or gh workflow run windows-app.yml). The selfhosted job does build+sign+publish.

macOS: GitHub → Actions → "macOS desktop" → Run workflow (or gh workflow run macos-app.yml). On workflow_dispatch it publishes the .dmg.

Production signing

Currently: macOS .dmgadhoc (unsigned), Windows .msixself-signed dev-cert. Both install, but the OS warns. For clean public distribution:

macOS — Apple Developer ID + notarization

Apple Team: CQTHTD6YJQ (Gerege Systems LLC) — same as iOS. Adding the following GitHub secrets makes macos-app.yml automatically sign + notarize:

Secret Value
MACOS_SIGN_IDENTITY Developer ID Application: Gerege Systems LLC (CQTHTD6YJQ)
MACOS_CERT_P12_BASE64 base64 of the Developer ID Application .p12 (base64 -i cert.p12)
MACOS_CERT_PASSWORD .p12 password
MACOS_NOTARY_KEY_BASE64 base64 of the App Store Connect API key .p8
MACOS_NOTARY_KEY_ID API key ID
MACOS_NOTARY_ISSUER_ID Issuer ID

Getting the cert: developer.apple.com → Certificates → Developer ID Application → export the .p12 from Keychain. API key: App Store Connect → Users and Access → Keys.

Windows — EV/OV code-signing cert

Point tools/pack-msix.ps1's -PfxPath at the real cert (or Azure Trusted Signing), replacing the New-DevCert step in windows-app.yml. The cert's subject DN must exactly match the Publisher in Package.appxmanifest (CN=Gerege Systems LLC, O=Gerege Systems LLC, C=MN). Since 2023 the cert must reside on a hardware token/HSM, so for CI Azure Trusted Signing (~$10/month) is the most suitable option.

Auto-update (not yet configured)

  • macOS: Sparkle (SUFeedURL: e-id.mn/download/appcast.xml) — requires scripts/gen_appcast.sh + an EdDSA key (SPARKLE_ED_PRIVATE_KEY).
  • Windows: .appinstaller (tools/publish-appinstaller.ps1) — place the signed .msix + .appinstaller together in /download/.