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 .dmg — adhoc (unsigned), Windows .msix — self-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) — requiresscripts/gen_appcast.sh+ an EdDSA key (SPARKLE_ED_PRIVATE_KEY). - Windows:
.appinstaller(tools/publish-appinstaller.ps1) — place the signed.msix+.appinstallertogether in/download/.