docs(desktop): манифест обновлений, поведение при клике по уведомлению, замеры и открытые пункты
README обёртки приведён в соответствие с кодом: - раздел «Автообновление»: раскладка каталога `UPDATES_DIR`, публикация релиза (`make desktop-release`, `make desktop-release-upload HOST=…`), зачем нужен `latest.json`, проверка через curl и лог обёртки; отдельно отмечено, что владельцу внешнего прокси добавлять ничего не нужно; - раздел «Уведомления и переход в канал»: реализованное поведение по активации приложения и честное ограничение (ОС не сообщает, что активация вызвана кликом по уведомлению); - раздел «Ресурсы и холодный старт»: замеры 708–740 мс и 252 МБ RSS, как повторить (`scripts/desktop-perf.py`); - «Что осталось оператору»: точные команды для `.dmg` (`make desktop-build` из обычного терминала), подписи Developer ID и нотаризации (`APPLE_*`, `stapler validate`, `spctl`), сборки и подписи Windows/Linux, требования к ключу обновлений; отдельно — поведение на Wayland (нативная тряска окна не работает, остаётся CSS).
This commit is contained in:
+201
-7
@@ -20,12 +20,18 @@ desktop/
|
||||
```bash
|
||||
make desktop-check # cargo fmt --check, clippy, тесты (кэш в .cache/)
|
||||
make desktop-build # бандлы текущей ОС (macOS: .app и .dmg)
|
||||
make desktop-build-app # только .app + артефакты обновления, без .dmg
|
||||
open desktop/src-tauri/target/release/bundle/macos/glchat.app
|
||||
```
|
||||
|
||||
Требуется Rust 1.77+ и Node 22 (Tauri CLI ставится через `npx`). Кэши `cargo` и
|
||||
`npm` держатся в `.cache/` — как у остальных инструментов проекта.
|
||||
|
||||
`make desktop-build-app` (`tauri build --bundles app`) нужен там, где `.dmg`
|
||||
собрать нельзя: `bundle_dmg.sh` монтирует образ через `hdiutil` и управляет
|
||||
Finder через AppleScript, поэтому из песочницы агента и из headless-окружения он
|
||||
падает (см. «Что не сделано»).
|
||||
|
||||
Сборка релиза с артефактами автообновления использует ключ подписи:
|
||||
|
||||
```bash
|
||||
@@ -74,17 +80,205 @@ make desktop-build
|
||||
Ссылка, пришедшая до загрузки клиента (холодный старт), ждёт в очереди и
|
||||
доставляется после `desktop_ready`.
|
||||
|
||||
## Автообновление
|
||||
|
||||
Обёртка проверяет обновления сама: фоновая проверка через 30 секунд после старта
|
||||
и вручную — трей → «Проверить обновления…». Адрес манифеста —
|
||||
`GET {instance}/updates/{target}/{arch}/{current_version}` (target:
|
||||
`darwin`/`windows`/`linux`, arch: `aarch64`/`x86_64`; см. `src/updates.rs`).
|
||||
|
||||
Манифест отдаёт сам инстанс (`internal/server/updates.go`), отдельная статика в
|
||||
Caddy не нужна: и встроенный Caddy профиля, и внешний прокси стенда проксируют
|
||||
весь домен на приложение, поэтому `/updates/...` доходит до него без правок
|
||||
чужих конфигов.
|
||||
|
||||
Раскладка каталога (`UPDATES_DIR`; на стенде `/opt/glchat/updates`, в контейнере
|
||||
смонтирован как `/app/updates:ro`):
|
||||
|
||||
```
|
||||
/opt/glchat/updates/
|
||||
└── darwin/aarch64/
|
||||
├── glchat.app.tar.gz # артефакт релиза (создаёт tauri build)
|
||||
├── glchat.app.tar.gz.sig # подпись ed25519 (формат minisign)
|
||||
├── 0.1.0.json # манифест версии
|
||||
└── latest.json # манифест последнего релиза — его и отдаёт инстанс
|
||||
```
|
||||
|
||||
Публикация релиза:
|
||||
|
||||
```bash
|
||||
make desktop-build # собрать бандл (.app/.dmg) и артефакты обновления
|
||||
sudo make desktop-release # разложить в /opt/glchat/updates + создать манифест
|
||||
make desktop-release-upload HOST=user@server # то же, но по SSH (sudo спросит пароль в терминале)
|
||||
make desktop-release ARGS='--dry-run' # посмотреть план, ничего не записывая
|
||||
```
|
||||
|
||||
Скрипт `scripts/desktop-release.sh` берёт версию из `tauri.conf.json`, сам
|
||||
определяет target/arch по имени артефакта, копирует артефакт и `.sig`, собирает
|
||||
манифест (`version`, `notes`, `pub_date`, `platforms.<target>-<arch>.signature/url`)
|
||||
и кладёт его и как `<version>.json`, и как `latest.json`. Несколько платформ
|
||||
одной версии (macOS + Windows + Linux) складываются в один манифест: запускайте
|
||||
скрипт по разу на артефакт.
|
||||
|
||||
`latest.json` нужен потому, что обёртка приходит со **своей установленной**
|
||||
версией: «новее ли релиз» решает клиент сравнением semver
|
||||
(`tauri-plugin-updater`), поэтому на запрос любой версии сервер отдаёт
|
||||
последний манифест, а если есть манифест конкретной версии — его. Без этого
|
||||
обновление не получил бы никто, кроме тех, у кого уже стоит последняя версия.
|
||||
|
||||
Владельцу прокси добавлять ничего не нужно: `/updates/...` уходит в
|
||||
catch-all `handle` и доходит до приложения (так же, как статика клиента).
|
||||
Вариант «раздавать обновления статикой мимо приложения» требует правила
|
||||
`rewrite` на `latest.json` с подстановкой `target`/`arch` в Caddy — лишняя
|
||||
работа без выгоды, поэтому не рекомендуется.
|
||||
|
||||
Проверка (стенд):
|
||||
|
||||
```bash
|
||||
curl -s https://gl.mhspx.su/updates/darwin/aarch64/0.1.0 | jq .
|
||||
# в логе обёртки (~/Library/Logs/su.mhspx.glchat/glchat.log) —
|
||||
# «обновлений нет» (манифест разобран) вместо
|
||||
# «проверка обновлений не удалась: … 404»
|
||||
```
|
||||
|
||||
Порядок обновления: проверка → уведомление в интерфейсе → скачивание →
|
||||
подтверждение в диалоге (во время звонка перезапуск без согласия не делается) →
|
||||
подпись проверяется до установки, downgrade отклоняется.
|
||||
|
||||
## Уведомления и переход в канал
|
||||
|
||||
Решение «показывать ли уведомление» принимает веб-клиент (упоминания и личные
|
||||
сообщения, окно неактивно или комната не открыта), обёртка показывает системное
|
||||
уведомление и обновляет счётчик.
|
||||
|
||||
Клик по уведомлению: у `tauri-plugin-notification` на desktop нет колбэка
|
||||
действия, поэтому реализовано лучшее доступное поведение — уведомление,
|
||||
показанное при неактивном окне, запоминается, и когда приложение
|
||||
активируется в течение 10 секунд (окно получило фокус или macOS прислала
|
||||
`Reopen`), окно поднимается и открывается комната из уведомления
|
||||
(`glchat://guild/<id>/channel/<id>`, для личной беседы `glchat://dm/<id>`).
|
||||
|
||||
Ограничение (честно): ОС не сообщает, что активация вызвана именно кликом по
|
||||
уведомлению, поэтому переход сработает и при обычном возврате в приложение в
|
||||
течение этих 10 секунд. Клик по уведомлению старше 10 секунд только поднимает
|
||||
окно. Полноценный колбэк требует своего моста к `UNUserNotificationCenter`
|
||||
(`UNUserNotificationCenterDelegate`) — отдельная задача.
|
||||
|
||||
## Ресурсы и холодный старт
|
||||
|
||||
Замеры собраны скриптом `scripts/desktop-perf.py` (macOS arm64, Mac на M-серии,
|
||||
инстанс `gl.mhspx.su` через внешний прокси; подробности — `docs/PERF.md`):
|
||||
|
||||
| Метрика | Значение | Бюджет (docs/client-tauri.md §1) |
|
||||
|---|---|---|
|
||||
| Холодный старт (`open` → процесс → строка «загружена страница») | 708–740 мс (три прогона) | ≤ 2 с |
|
||||
| Из них: запуск процесса | 45–74 мс | — |
|
||||
| Из них: загрузка страницы клиента | 637–682 мс | — |
|
||||
| RAM, клиент открыт и простаивает (RSS: обёртка + 4 процесса WebKit) | 252 МБ (117 + 135) | ≤ 350 МБ |
|
||||
| RAM, окно свёрнуто в трей (`--minimized`) | 251 МБ | ≤ 350 МБ |
|
||||
| Физический след (Activity Monitor) | 103 МБ | — |
|
||||
|
||||
Наблюдения:
|
||||
|
||||
- свёрнутое в трей окно памяти не освобождает: webview с загруженным клиентом
|
||||
продолжает жить (так работают WKWebView и трей-режим) — экономия была бы
|
||||
только при выгрузке страницы, а это потеря мгновенного возврата в клиент;
|
||||
- первый запуск сразу после сборки бандла (холодный кэш файловой системы и
|
||||
страницы) дал 1,26 с — тоже в бюджете;
|
||||
- почти вся память — WebKit (страница клиента), сама обёртка занимает ~117 МБ.
|
||||
|
||||
Повторить замер:
|
||||
|
||||
```bash
|
||||
make desktop-build-app # свежий .app (+ артефакты обновления)
|
||||
python3 scripts/desktop-perf.py --runs 3 # 3 холодных старта + простой в трее
|
||||
```
|
||||
|
||||
## Что не сделано (открытые пункты Фазы 6)
|
||||
|
||||
- **Манифест обновлений на сервере**: `GET /updates/{target}/{arch}/{current_version}`
|
||||
ещё не публикуется — нужна статика на стороне инстанса (Caddy) и релизный
|
||||
контур с приватным ключом в CI. Клиентская часть готова: подпись проверяется,
|
||||
downgrade отклоняется, при недоступном манифесте ошибка только логируется.
|
||||
- **Клик по уведомлению** → открытие канала: у `tauri-plugin-notification` нет
|
||||
колбэка действия на desktop, нужен свой мост к `UNUserNotificationCenter`.
|
||||
- **Автообновление end-to-end не проверено на реальном релизе**: манифест
|
||||
отдаётся и разбирается (проверено на стенде), но установка «поверх старой
|
||||
версии» требует публикации версии с бо́льшим номером — это действие оператора
|
||||
(см. «Публикация релиза» выше).
|
||||
- **Аватары в уведомлениях** (thumb/буква) и группировка — сейчас иконка приложения.
|
||||
- **Захват экрана и микрофон**: разрешения macOS выдаёт webview, отдельных
|
||||
экранов-инструкций у обёртки нет.
|
||||
- **Подпись и нотаризация сборки** macOS/Windows: Developer ID/EV только в CI.
|
||||
- **Developer ID и нотаризация** macOS, EV/Trusted Signing для Windows — только
|
||||
в CI с платными сертификатами (ниже — точные команды).
|
||||
- **`.dmg`**: собирается только из обычного терминала (Finder/AppleScript).
|
||||
- **Windows/Linux-бандлы**: конфигурация кросс-платформенная, но собирались и
|
||||
проверялись только macOS-arm64.
|
||||
|
||||
## Что осталось оператору (точные команды)
|
||||
|
||||
### macOS: `.dmg`
|
||||
|
||||
`bundle_dmg.sh` монтирует образ (`hdiutil`) и управляет Finder через AppleScript,
|
||||
поэтому из песочницы агента и из headless-сессии он падает. Из обычного
|
||||
терминала на macOS:
|
||||
|
||||
```bash
|
||||
cd /Volumes/Samsung/projects/glchat
|
||||
make desktop-build # .app + .dmg + артефакты обновления
|
||||
ls -lh desktop/src-tauri/target/release/bundle/dmg/
|
||||
```
|
||||
|
||||
Артефакты без `.dmg` (для автообновления) собираются где угодно:
|
||||
`make desktop-build-app`.
|
||||
|
||||
### macOS: подпись Developer ID и нотаризация
|
||||
|
||||
Нужны сертификат Developer ID Application в связке ключей и пароль приложения
|
||||
(appleid.apple.com → App-Specific Passwords):
|
||||
|
||||
```bash
|
||||
security find-identity -v -p codesigning # проверить наличие сертификата
|
||||
export APPLE_SIGNING_IDENTITY="Developer ID Application: … (TEAMID)"
|
||||
export APPLE_ID="…@example.com"
|
||||
export APPLE_PASSWORD="app-specific-password"
|
||||
export APPLE_TEAM_ID="TEAMID"
|
||||
make desktop-build # Tauri подпишет и отправит на нотаризацию
|
||||
xcrun stapler validate desktop/src-tauri/target/release/bundle/macos/glchat.app
|
||||
spctl --assess --type execute --verbose=4 desktop/src-tauri/target/release/bundle/macos/glchat.app
|
||||
```
|
||||
|
||||
Переменные читает сам Tauri (`APPLE_*`), в репозиторий они не попадают.
|
||||
Проверка «не подписано»: `codesign -dv --verbose=4 <app>` покажет `Signature=adhoc`.
|
||||
|
||||
### Windows (x64): MSI/NSIS + подпись
|
||||
|
||||
Сборка идёт на Windows-машине или в CI (`windows-latest`), Rust + Node 22:
|
||||
|
||||
```powershell
|
||||
npx @tauri-apps/cli@^2 build --bundles nsis,msi
|
||||
# подпись (EV/Trusted Signing или signtool с сертификатом):
|
||||
signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 `
|
||||
/f cert.pfx /p $env:CERT_PASSWORD `
|
||||
desktop\src-tauri\target\release\bundle\nsis\glchat_0.1.0_x64-setup.exe
|
||||
```
|
||||
|
||||
Артефакты обновления (`*.nsis.zip`/`*.msi` + `.sig`) публикуются тем же
|
||||
скриптом: `bash scripts/desktop-release.sh --artifact <файл> --target windows --arch x86_64`.
|
||||
|
||||
### Linux (AppImage + deb)
|
||||
|
||||
Сборка на Ubuntu 24.04/26.04 (нужны `libwebkit2gtk-4.1-dev`, `librsvg2-dev`,
|
||||
`patchelf`, `libssl-dev`, `build-essential`):
|
||||
|
||||
```bash
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev librsvg2-dev patchelf libssl-dev build-essential
|
||||
npx @tauri-apps/cli@^2 build --bundles appimage,deb
|
||||
bash scripts/desktop-release.sh --artifact desktop/src-tauri/target/release/bundle/appimage/*.AppImage.tar.gz \
|
||||
--target linux --arch x86_64
|
||||
```
|
||||
|
||||
На Wayland нативная тряска окна при несохранённых изменениях не работает
|
||||
(композитор запрещает `set_position`) — остаётся CSS-тряска контента; это
|
||||
ожидаемое поведение, а не дефект.
|
||||
|
||||
### Ключ подписи обновлений
|
||||
|
||||
Приватный ключ — только в CI-секретах (`TAURI_SIGNING_PRIVATE_KEY`,
|
||||
`TAURI_SIGNING_PRIVATE_KEY_PASSWORD`); локально — `build/desktop-keys/`
|
||||
(каталог в `.gitignore`). Публичный ключ уже вшит в сборку и меняется только
|
||||
вместе с ключом: старые сборки отвергнут манифест, подписанный новым ключом.
|
||||
|
||||
Reference in New Issue
Block a user