Десктопные приложения — сборка, подпись, канал загрузки (ops)¶
Инфраструктура для сборки, подписи и публикации в /download/ десктопных клиентов
для macOS и Windows. Обзор кода: macOS desktop,
Windows desktop.
Инфраструктура сборки — self-hosted раннеры¶
WinUI/MSIX не собирается на macOS, а Xcode не работает на Windows, поэтому для каждой платформы используется свой выделенный self-hosted раннер GitHub Actions:
| Раннер | Хост | Метки | Workflow |
|---|---|---|---|
win11-build |
Windows 11 Pro (38.180.136.249) |
self-hosted, Windows, X64, windows-signing |
.github/workflows/windows-app.yml (job selfhosted) |
imacpro-macos |
iMac Pro (macOS 14, Intel) | self-hosted, macOS |
.github/workflows/macos-app.yml |
Раннеры работают как службы (автозапуск при перезагрузке) от пользователя deploy.
Инструментарий (.NET 8, git, Xcode, xcodegen) установлен на хосте; makeappx/signtool
для MSIX берутся из NuGet (Microsoft.Windows.SDK.BuildTools[.MSIX]), поэтому системный
Windows SDK не требуется.
Раннер офлайн
Запущенная задача требует, чтобы её раннер был онлайн — если он офлайн, задача ставится
в очередь. Статус раннеров: gh api repos/OWNER/REPO/actions/runners.
Канал загрузки — /download/¶
Деплой использует SSH с forced-command (только ci-deploy.sh), поэтому scp на сервер
невозможен. Следовательно, бинарные файлы попадают на сервер только через git-репозиторий
(deploy/downloads/):
раннер: сборка + подпись → deploy/downloads/{eid-mongolia.dmg, eid-mongolia-setup.msix, *.cer}
→ git commit + push (main)
staging ci-deploy: git reset --hard origin/main → force-recreate nginx
nginx (deploy/nginx/staging.conf, location ^~ /download/) → /srv/download (монтирование)
Кнопки загрузки на сайте: web/src/components/landing/DesktopAppSection.tsx,
web/src/app/solutions/page.tsx.
Размер git-репозитория
Файл .msix (~37 МБ) попадает в историю git при каждой публикации. При частых публикациях
переходите на Git LFS (.gitattributes: deploy/downloads/*.msix,*.dmg filter=lfs).
На сервере должен быть установлен git-lfs (иначе nginx будет отдавать файл-указатель).
Повторная публикация¶
Windows: GitHub → Actions → «Windows desktop» → Run workflow (либо
gh workflow run windows-app.yml). Job selfhosted выполняет сборку + подпись + публикацию.
macOS: GitHub → Actions → «macOS desktop» → Run workflow (либо
gh workflow run macos-app.yml). При workflow_dispatch публикуется .dmg.
Продуктивная подпись¶
Сейчас: macOS .dmg — adhoc (без подписи), Windows .msix — самоподписанный
dev-сертификат. Оба устанавливаются, но ОС выводит предупреждение. Для «чистого» публичного
распространения:
macOS — Apple Developer ID + нотаризация¶
Apple Team: CQTHTD6YJQ (Gerege Systems LLC) — тот же, что и для iOS. Добавление
следующих GitHub secrets заставит macos-app.yml автоматически подписывать и нотаризовать:
| Secret | Значение |
|---|---|
MACOS_SIGN_IDENTITY |
Developer ID Application: Gerege Systems LLC (CQTHTD6YJQ) |
MACOS_CERT_P12_BASE64 |
base64 файла .p12 Developer ID Application (base64 -i cert.p12) |
MACOS_CERT_PASSWORD |
пароль от .p12 |
MACOS_NOTARY_KEY_BASE64 |
base64 ключа .p8 из App Store Connect API |
MACOS_NOTARY_KEY_ID |
ID ключа API |
MACOS_NOTARY_ISSUER_ID |
Issuer ID |
Получение сертификата: developer.apple.com → Certificates → Developer ID Application →
экспортировать .p12 из Keychain. Ключ API: App Store Connect → Users and Access → Keys.
Windows — EV/OV сертификат подписи кода¶
Укажите в -PfxPath скрипта tools/pack-msix.ps1 путь к реальному сертификату (или
используйте Azure Trusted Signing), заменив шаг New-DevCert в windows-app.yml.
Subject DN сертификата должен в точности совпадать со значением Publisher в
Package.appxmanifest (CN=Gerege Systems LLC, O=Gerege Systems LLC, C=MN). С 2023 года
сертификат должен храниться на аппаратном токене/HSM, поэтому для CI наиболее удобен вариант
Azure Trusted Signing (~$10 в месяц).
Автообновление (ещё не настроено)¶
- macOS: Sparkle (
SUFeedURL: e-id.mn/download/appcast.xml) — требуетscripts/gen_appcast.shи ключ EdDSA (SPARKLE_ED_PRIVATE_KEY). - Windows:
.appinstaller(tools/publish-appinstaller.ps1) — положите подписанный.msixи.appinstallerвместе в/download/.