2026-09-22 21:55:19 +03:00
|
|
|
|
# desktop — desktop-обёртка glchat (Tauri 2)
|
|
|
|
|
|
|
|
|
|
|
|
Тот же веб-клиент из `web/`, обёрнутый в Tauri 2 (docs/client-tauri.md). Отдельного
|
|
|
|
|
|
UI-кода нет: страница инстанса загружается в системный webview, а обёртка даёт
|
|
|
|
|
|
платформенную интеграцию — трей, нативные уведомления, глобальный push-to-talk,
|
|
|
|
|
|
автозапуск, deep links и автообновление.
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
desktop/
|
|
|
|
|
|
└── src-tauri/ # Rust-проект обёртки
|
|
|
|
|
|
├── src/ # окно, трей, шорткаты, уведомления, deep links, обновления
|
|
|
|
|
|
├── capabilities/ # allowlist IPC: локальная страница настроек и домен инстанса
|
|
|
|
|
|
├── ui/ # локальная страница настроек обёртки (tauri://localhost)
|
|
|
|
|
|
├── icons/ # иконки приложения (сгенерированы из web/public/icons)
|
|
|
|
|
|
└── tauri.conf.json # конфигурация сборки и плагинов
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## Сборка и запуск
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
make desktop-check # cargo fmt --check, clippy, тесты (кэш в .cache/)
|
|
|
|
|
|
make desktop-build # бандлы текущей ОС (macOS: .app и .dmg)
|
2026-09-22 23:17:48 +03:00
|
|
|
|
make desktop-build-app # только .app + артефакты обновления, без .dmg
|
2026-09-22 21:55:19 +03:00
|
|
|
|
open desktop/src-tauri/target/release/bundle/macos/glchat.app
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Требуется Rust 1.77+ и Node 22 (Tauri CLI ставится через `npx`). Кэши `cargo` и
|
|
|
|
|
|
`npm` держатся в `.cache/` — как у остальных инструментов проекта.
|
|
|
|
|
|
|
2026-09-22 23:17:48 +03:00
|
|
|
|
`make desktop-build-app` (`tauri build --bundles app`) нужен там, где `.dmg`
|
|
|
|
|
|
собрать нельзя: `bundle_dmg.sh` монтирует образ через `hdiutil` и управляет
|
|
|
|
|
|
Finder через AppleScript, поэтому из песочницы агента и из headless-окружения он
|
|
|
|
|
|
падает (см. «Что не сделано»).
|
|
|
|
|
|
|
2026-09-26 18:12:42 +03:00
|
|
|
|
`open --args` из песочницы агента аргументы не доставляет (проверено на
|
|
|
|
|
|
macOS 27: и на бандле glchat, и на системном TextEdit — процесс запускается без
|
|
|
|
|
|
них; из обычного терминала то же самое работает). Поэтому `make desktop-run
|
|
|
|
|
|
ARGS='--minimized'` и замер свёрнутого окна в `scripts/desktop-perf.py` надо
|
|
|
|
|
|
запускать из обычного терминала, а не из песочницы.
|
|
|
|
|
|
|
2026-09-22 21:55:19 +03:00
|
|
|
|
Сборка релиза с артефактами автообновления использует ключ подписи:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# ключ и пароль не коммитятся: приватный ключ живёт в CI-секретах,
|
|
|
|
|
|
# локально — в build/desktop-keys/ (каталог build/ в .gitignore)
|
|
|
|
|
|
npx @tauri-apps/cli@^2 signer generate -w build/desktop-keys/glchat-updater.key -p '…'
|
|
|
|
|
|
make desktop-build
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Публичный ключ уже вписан в `src-tauri/tauri.conf.json` и в `UPDATER_PUBKEY`
|
|
|
|
|
|
(`src/updates.rs`). Без локального ключа `make desktop-build` собирает бандл без
|
|
|
|
|
|
артефактов обновления, чтобы сборка не падала у тех, у кого ключа нет.
|
|
|
|
|
|
|
|
|
|
|
|
## Аргументы командной строки
|
|
|
|
|
|
|
|
|
|
|
|
| Аргумент | Что делает |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| `--instance <url>` | адрес инстанса вместо сохранённого (`--instance=https://…` тоже работает) |
|
|
|
|
|
|
| `--minimized` | запуск свёрнутым в трей (используется автозапуском) |
|
|
|
|
|
|
| `--settings` | сразу открыть страницу настроек обёртки |
|
|
|
|
|
|
| `--autostart on\|off` | включить или выключить автозапуск при установке скриптом |
|
2026-09-26 18:12:42 +03:00
|
|
|
|
| `--update-now` | сразу проверить обновление и поставить его без диалога (тихое обновление) |
|
2026-09-22 21:55:19 +03:00
|
|
|
|
|
|
|
|
|
|
## Настройки
|
|
|
|
|
|
|
|
|
|
|
|
Хранятся в `settings.json` каталога конфигурации приложения
|
|
|
|
|
|
(`~/Library/Application Support/su.mhspx.glchat/` на macOS):
|
|
|
|
|
|
|
|
|
|
|
|
| Ключ | Смысл |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| `instanceUrl` | адрес клиента, по умолчанию `https://gl.mhspx.su` |
|
|
|
|
|
|
| `closeToTray` | закрытие окна сворачивает приложение в трей |
|
|
|
|
|
|
| `autostart` | запуск при входе в систему (`--minimized`) |
|
|
|
|
|
|
| `notifications` | показывать системные уведомления |
|
|
|
|
|
|
| `pttShortcut`, `muteShortcut`, `deafenShortcut` | глобальные комбинации (пусто — выключено) |
|
|
|
|
|
|
|
|
|
|
|
|
Меняются из меню трея и со страницы настроек обёртки (трей → «Настройки обёртки…»).
|
|
|
|
|
|
Сессия и токены живут только в cookie webview: обёртка их не хранит.
|
|
|
|
|
|
|
|
|
|
|
|
## Deep links
|
|
|
|
|
|
|
|
|
|
|
|
Схема `glchat://` (регистрируется бандлом):
|
|
|
|
|
|
|
|
|
|
|
|
- `glchat://invite/<code>` → страница приглашения;
|
|
|
|
|
|
- `glchat://guild/<guildId>/channel/<channelId>` → сервер и комната.
|
|
|
|
|
|
|
|
|
|
|
|
Ссылка, пришедшая до загрузки клиента (холодный старт), ждёт в очереди и
|
|
|
|
|
|
доставляется после `desktop_ready`.
|
|
|
|
|
|
|
2026-09-22 23:17:48 +03:00
|
|
|
|
## Автообновление
|
|
|
|
|
|
|
|
|
|
|
|
Обёртка проверяет обновления сама: фоновая проверка через 30 секунд после старта
|
|
|
|
|
|
и вручную — трей → «Проверить обновления…». Адрес манифеста —
|
|
|
|
|
|
`GET {instance}/updates/{target}/{arch}/{current_version}` (target:
|
|
|
|
|
|
`darwin`/`windows`/`linux`, arch: `aarch64`/`x86_64`; см. `src/updates.rs`).
|
|
|
|
|
|
|
2026-09-26 18:12:42 +03:00
|
|
|
|
Найденное обновление показывается в интерфейсе, установка спрашивается
|
|
|
|
|
|
отдельным диалогом («во время звонка перезапуск без согласия недопустим»).
|
|
|
|
|
|
Для управляемых машин есть тихий режим — флаг `--update-now`:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
open -a /Applications/glchat.app --args --update-now # из обычного терминала
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
С этим флагом обёртка проверяет обновление сразу при старте (не через 30 секунд)
|
|
|
|
|
|
и ставит найденное без вопросов, после чего перезапускается; диалогов нет вовсе,
|
|
|
|
|
|
а если обновления нет — приложение просто работает дальше. По умолчанию флаг
|
|
|
|
|
|
выключен, поведение обычного запуска не меняется. Подпись артефакта проверяется
|
|
|
|
|
|
в обоих режимах: флаг убирает только вопрос пользователю, но не проверку
|
|
|
|
|
|
(docs/DECISIONS.md, D-077).
|
|
|
|
|
|
|
|
|
|
|
|
В журнале (`~/Library/Logs/su.mhspx.glchat/glchat.log`) видны и версия запущенной
|
|
|
|
|
|
сборки, и ход обновления:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
обёртка запущена: версия=0.1.0, адрес=https://gl.mhspx.su, окно=видимо, автозапуск=true
|
|
|
|
|
|
тихое обновление по флагу --update-now: проверка сразу при старте
|
|
|
|
|
|
доступно обновление 0.1.1
|
|
|
|
|
|
тихое обновление по флагу: 0.1.0 → 0.1.1
|
|
|
|
|
|
обновление 0.1.1 установлено, перезапуск
|
|
|
|
|
|
обёртка запущена: версия=0.1.1, …
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-09-22 23:17:48 +03:00
|
|
|
|
Манифест отдаёт сам инстанс (`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)
|
2026-09-26 20:14:25 +03:00
|
|
|
|
├── 0.1.1.json # манифест текущего релиза
|
2026-09-22 23:17:48 +03:00
|
|
|
|
└── latest.json # манифест последнего релиза — его и отдаёт инстанс
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-09-26 20:14:25 +03:00
|
|
|
|
Манифестов прошлых релизов в каталоге быть не должно: обёртка спрашивает
|
|
|
|
|
|
`/updates/{target}/{arch}/{своя версия}`, и оставшийся `<старая версия>.json`
|
|
|
|
|
|
перекрыл бы новый релиз — клиент со старой версией получил бы «обновлений нет».
|
|
|
|
|
|
Публикующий скрипт сам убирает такие файлы (см. ниже).
|
2026-09-22 23:17:48 +03:00
|
|
|
|
|
2026-09-26 20:14:25 +03:00
|
|
|
|
## Выпуск релиза
|
|
|
|
|
|
|
|
|
|
|
|
Версия живёт в одном месте — файл `VERSION` в корне репозитория; из него её
|
|
|
|
|
|
берут `Makefile` (версия сервера и образов), `deploy/install.sh` (`IMAGE_TAG`)
|
|
|
|
|
|
и desktop-обёртка (`desktop/src-tauri/tauri.conf.json` + `Cargo.toml` — их
|
|
|
|
|
|
значения должны совпадать с `VERSION`).
|
|
|
|
|
|
|
|
|
|
|
|
1. Поднять версию:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
echo 0.1.2 > VERSION # версия сервера и обёртки
|
|
|
|
|
|
# те же значения — в desktop/src-tauri/tauri.conf.json, Cargo.toml, Cargo.lock
|
|
|
|
|
|
make web-build && make build # сервер собирается с новой версией
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
2. Проверки и сборка бандла:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
make go-lint && go test -tags sqlite_fts5 -race -count=1 ./internal/...
|
|
|
|
|
|
cd web && npm run check && npm run lint && npm test && cd ..
|
|
|
|
|
|
make desktop-check # fmt, clippy, cargo test
|
|
|
|
|
|
make desktop-build # .app + .dmg + артефакты обновления
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
3. Зафиксировать и пометить релиз:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
git add VERSION Makefile deploy/install.sh desktop/src-tauri scripts/desktop-release.sh
|
|
|
|
|
|
git commit -m "release: 0.1.2"
|
|
|
|
|
|
git tag -a v0.1.2 -m "glchat 0.1.2" && git push origin main --tags
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
4. Опубликовать обновление на инстансе:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
sudo make desktop-release # локальный инстанс
|
|
|
|
|
|
make desktop-release-upload HOST=user@server # по SSH (sudo спросит пароль)
|
|
|
|
|
|
make desktop-release-upload HOST=user@server \
|
|
|
|
|
|
GLCHAT_SSH_OPTS="-i ~/.ssh/key -o IdentitiesOnly=yes" # свой ключ, без TTY (CI)
|
|
|
|
|
|
make desktop-release ARGS='--dry-run' # посмотреть план
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
5. Проверить канал и живьём обновиться со старой версии — см. следующий раздел.
|
2026-09-22 23:17:48 +03:00
|
|
|
|
|
|
|
|
|
|
Скрипт `scripts/desktop-release.sh` берёт версию из `tauri.conf.json`, сам
|
|
|
|
|
|
определяет target/arch по имени артефакта, копирует артефакт и `.sig`, собирает
|
2026-09-26 20:14:25 +03:00
|
|
|
|
манифест (`version`, `notes`, `pub_date`, `platforms.<target>-<arch>.signature/url`),
|
|
|
|
|
|
кладёт его и как `<version>.json`, и как `latest.json`, а манифесты прошлых
|
|
|
|
|
|
релизов убирает. Несколько платформ одной версии (macOS + Windows + Linux)
|
|
|
|
|
|
складываются в один манифест: запускайте скрипт по разу на артефакт. Каталог
|
|
|
|
|
|
обновлений на инстансе обычно принадлежит root — тогда rsync идёт через sudo;
|
|
|
|
|
|
если каталог записываем (стенд), sudo не нужен и загрузка работает без
|
|
|
|
|
|
терминала.
|
2026-09-22 23:17:48 +03:00
|
|
|
|
|
|
|
|
|
|
`latest.json` нужен потому, что обёртка приходит со **своей установленной**
|
|
|
|
|
|
версией: «новее ли релиз» решает клиент сравнением semver
|
|
|
|
|
|
(`tauri-plugin-updater`), поэтому на запрос любой версии сервер отдаёт
|
|
|
|
|
|
последний манифест, а если есть манифест конкретной версии — его. Без этого
|
|
|
|
|
|
обновление не получил бы никто, кроме тех, у кого уже стоит последняя версия.
|
|
|
|
|
|
|
|
|
|
|
|
Владельцу прокси добавлять ничего не нужно: `/updates/...` уходит в
|
|
|
|
|
|
catch-all `handle` и доходит до приложения (так же, как статика клиента).
|
|
|
|
|
|
Вариант «раздавать обновления статикой мимо приложения» требует правила
|
|
|
|
|
|
`rewrite` на `latest.json` с подстановкой `target`/`arch` в Caddy — лишняя
|
|
|
|
|
|
работа без выгоды, поэтому не рекомендуется.
|
|
|
|
|
|
|
|
|
|
|
|
Проверка (стенд):
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-09-26 20:14:25 +03:00
|
|
|
|
curl -s https://gl.mhspx.su/updates/darwin/aarch64/0.1.0 | jq . # манифест для старой версии
|
|
|
|
|
|
curl -sI https://gl.mhspx.su/updates/darwin/aarch64/glchat.app.tar.gz | head -3 # 200 + размер
|
2026-09-22 23:17:48 +03:00
|
|
|
|
# в логе обёртки (~/Library/Logs/su.mhspx.glchat/glchat.log) —
|
|
|
|
|
|
# «обновлений нет» (манифест разобран) вместо
|
|
|
|
|
|
# «проверка обновлений не удалась: … 404»
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Порядок обновления: проверка → уведомление в интерфейсе → скачивание →
|
|
|
|
|
|
подтверждение в диалоге (во время звонка перезапуск без согласия не делается) →
|
|
|
|
|
|
подпись проверяется до установки, downgrade отклоняется.
|
|
|
|
|
|
|
2026-09-26 20:14:25 +03:00
|
|
|
|
### Приёмка релиза на живом инстансе (26.09.2026, 0.1.0 → 0.1.1)
|
|
|
|
|
|
|
|
|
|
|
|
Проверено на боевом канале стенда, без локальных заглушек:
|
|
|
|
|
|
|
|
|
|
|
|
- манифест опубликован `scripts/desktop-release.sh --remote`, артефакт
|
|
|
|
|
|
`glchat.app.tar.gz` (3 204 953 Б) и подпись лежат в
|
|
|
|
|
|
`/opt/glchat/updates/darwin/aarch64/`;
|
|
|
|
|
|
- обёртка 0.1.0, запущенная с `--update-now`, в журнале (UTC) показала:
|
|
|
|
|
|
`доступно обновление 0.1.1` → `тихое обновление по флагу: 0.1.0 → 0.1.1` →
|
|
|
|
|
|
`обновление 0.1.1 установлено, перезапуск` → `обёртка запущена: версия=0.1.1` →
|
|
|
|
|
|
`обновлений нет` (17:08:23–17:08:25Z);
|
|
|
|
|
|
- запрос старой версии `GET /updates/darwin/aarch64/0.1.0` отдаёт манифест
|
|
|
|
|
|
**0.1.1** — это и есть та точка, где раньше мешал оставшийся `0.1.0.json`;
|
|
|
|
|
|
- `/Applications/glchat.app` после установки — 0.1.1, ярлык на рабочем столе
|
|
|
|
|
|
(`~/Desktop/glchat.app`) указывает на него.
|
|
|
|
|
|
|
|
|
|
|
|
**Важно про место установки.** `tauri-plugin-updater` распаковывает обновление
|
|
|
|
|
|
во временный каталог и переносит бандл `rename(2)`. Если приложение лежит на
|
|
|
|
|
|
другом томе, чем временный каталог (например, на внешнем диске
|
|
|
|
|
|
`/Volumes/…`, а `TMPDIR` — на загрузочном), установка падает с
|
|
|
|
|
|
`установка обновления не удалась: Cross-device link (os error 18)`, хотя
|
|
|
|
|
|
скачивание и проверка подписи проходят. Поэтому приложение для автообновления
|
|
|
|
|
|
надо держать на загрузочном томе — так делает `.dmg` (ставит в `/Applications`);
|
|
|
|
|
|
ярлык на рабочем столе лучше вести на `/Applications/glchat.app`, а не на
|
|
|
|
|
|
сборку в репозитории.
|
|
|
|
|
|
|
2026-09-26 18:12:42 +03:00
|
|
|
|
### Локальная проверка «поверх старой версии» (без стенда)
|
|
|
|
|
|
|
|
|
|
|
|
Установка обновления проверена локально на настоящем сервере инстанса: сборка
|
|
|
|
|
|
0.1.0 → манифест 0.1.1 → скачивание → проверка подписи → установка → перезапуск
|
|
|
|
|
|
на 0.1.1. Порядок действий (все данные — в каталоге репозитория, стенд не
|
|
|
|
|
|
затрагивается):
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 1. артефакт текущей версии и приложение 0.1.0 (с ключом подписи)
|
|
|
|
|
|
make desktop-build-app
|
|
|
|
|
|
# 2. версия 0.1.1 в desktop/src-tauri/tauri.conf.json и Cargo.toml, затем:
|
|
|
|
|
|
make desktop-build-app
|
|
|
|
|
|
# 3. раскладка манифеста в свой каталог, ссылки — на локальный сервер
|
|
|
|
|
|
bash scripts/desktop-release.sh --artifact \
|
|
|
|
|
|
desktop/src-tauri/target/release/bundle/macos/glchat.app.tar.gz \
|
|
|
|
|
|
--version 0.1.1 --base-url http://127.0.0.1:8100 --dir /tmp/updates
|
|
|
|
|
|
# 4. настоящий сервер инстанса со своим каталогом обновлений и клиентом
|
|
|
|
|
|
LISTEN_ADDR=127.0.0.1:8099 DATA_DIR=/tmp/glchat-e2e/data WEB_ROOT=$PWD/web/dist \
|
|
|
|
|
|
UPDATES_DIR=/tmp/updates DOMAIN=127.0.0.1:8099 TLS_ENABLED=false \
|
|
|
|
|
|
APP_VERSION=0.1.0-local SESSION_PEPPER=… MASTER_KEY=… TOTP_ENCRYPTION_KEY=… \
|
|
|
|
|
|
./build/glchat
|
|
|
|
|
|
# 5. запуск 0.1.0 с тихим обновлением (адрес инстанса — локальный)
|
|
|
|
|
|
/Applications/glchat.app/Contents/MacOS/glchat-desktop \
|
|
|
|
|
|
--instance http://127.0.0.1:8099 --update-now
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Что нужно знать, повторяя это:
|
|
|
|
|
|
|
|
|
|
|
|
- **локальный стенд — http, а `tauri-plugin-updater` по умолчанию требует
|
|
|
|
|
|
`https`** («The configured updater endpoint must use a secure protocol»), для
|
|
|
|
|
|
проверки сборку делают с временным флагом
|
|
|
|
|
|
`tauri build --config '{"plugins":{"updater":{"dangerousInsecureTransportProtocol":true}}}'`.
|
|
|
|
|
|
В релизной сборке этого флага быть не должно: боевой адрес — `https://`;
|
|
|
|
|
|
- **приложение должно лежать на пути без символических ссылок**: macOS-проверка
|
|
|
|
|
|
`tauri-utils` отвергает `current_exe()` с symlink-компонентом, поэтому
|
|
|
|
|
|
`/tmp/glchat-e2e/…` не годится (`/tmp` — ссылка на `/private/tmp`), а
|
|
|
|
|
|
`/private/tmp/glchat-e2e/…` годится. Тот же смысл у требования ставить
|
|
|
|
|
|
приложение в `/Applications`, а не запускать через ссылку;
|
|
|
|
|
|
- **`--update-now` обязателен**: без него установку спрашивают диалогом, а
|
|
|
|
|
|
кликнуть в headless-прогоне некому;
|
|
|
|
|
|
- диагностика в логе: `GET /updates/darwin/aarch64/0.1.0` в журнале сервера,
|
|
|
|
|
|
затем `доступно обновление 0.1.1` → `тихое обновление по флагу: 0.1.0 → 0.1.1`
|
|
|
|
|
|
→ `обновление 0.1.1 установлено, перезапуск` → строка запуска с
|
|
|
|
|
|
`версия=0.1.1` и запрос манифеста уже для `0.1.1`;
|
|
|
|
|
|
- проверка подписи проверяется и «от обратного»: если испортить байт в
|
|
|
|
|
|
артефакте, установка обязана упасть с `The signature verification failed`,
|
|
|
|
|
|
а версия на диске — остаться прежней.
|
|
|
|
|
|
|
2026-09-22 23:17:48 +03:00
|
|
|
|
## Уведомления и переход в канал
|
|
|
|
|
|
|
|
|
|
|
|
Решение «показывать ли уведомление» принимает веб-клиент (упоминания и личные
|
|
|
|
|
|
сообщения, окно неактивно или комната не открыта), обёртка показывает системное
|
|
|
|
|
|
уведомление и обновляет счётчик.
|
|
|
|
|
|
|
|
|
|
|
|
Клик по уведомлению: у `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) |
|
|
|
|
|
|
|---|---|---|
|
2026-09-22 23:21:52 +03:00
|
|
|
|
| Холодный старт (`open` → процесс → строка «загружена страница») | 773–898 мс (три прогона) | ≤ 2 с |
|
|
|
|
|
|
| Из них: запуск процесса | 74–78 мс | — |
|
|
|
|
|
|
| Из них: загрузка страницы клиента | 696–821 мс | — |
|
|
|
|
|
|
| RAM, клиент открыт и простаивает (RSS: обёртка + 4 процесса WebKit) | 254 МБ (117 + 137) | ≤ 350 МБ |
|
|
|
|
|
|
| RAM, окно свёрнуто в трей (`--minimized`) | 256 МБ | ≤ 350 МБ |
|
|
|
|
|
|
| Физический след (Activity Monitor) | 106 МБ | — |
|
2026-09-22 23:17:48 +03:00
|
|
|
|
|
|
|
|
|
|
Наблюдения:
|
|
|
|
|
|
|
|
|
|
|
|
- свёрнутое в трей окно памяти не освобождает: webview с загруженным клиентом
|
|
|
|
|
|
продолжает жить (так работают WKWebView и трей-режим) — экономия была бы
|
|
|
|
|
|
только при выгрузке страницы, а это потеря мгновенного возврата в клиент;
|
|
|
|
|
|
- первый запуск сразу после сборки бандла (холодный кэш файловой системы и
|
|
|
|
|
|
страницы) дал 1,26 с — тоже в бюджете;
|
|
|
|
|
|
- почти вся память — WebKit (страница клиента), сама обёртка занимает ~117 МБ.
|
|
|
|
|
|
|
|
|
|
|
|
Повторить замер:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
make desktop-build-app # свежий .app (+ артефакты обновления)
|
|
|
|
|
|
python3 scripts/desktop-perf.py --runs 3 # 3 холодных старта + простой в трее
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-09-22 21:55:19 +03:00
|
|
|
|
## Что не сделано (открытые пункты Фазы 6)
|
|
|
|
|
|
|
2026-09-26 18:12:42 +03:00
|
|
|
|
- **Автообновление проверено локально «поверх старой версии»** (2026-09-26):
|
|
|
|
|
|
0.1.0 → манифест 0.1.1 → скачивание → проверка подписи → установка →
|
|
|
|
|
|
перезапуск на 0.1.1, подробности и команды — «Локальная проверка» выше.
|
|
|
|
|
|
Открытым остаётся публикация реального релиза на стенде (номер версии
|
|
|
|
|
|
поднимает оператор) и то, что локальная проверка шла по http с временным
|
|
|
|
|
|
флагом `dangerousInsecureTransportProtocol` — на стенде канал https.
|
2026-09-22 21:55:19 +03:00
|
|
|
|
- **Аватары в уведомлениях** (thumb/буква) и группировка — сейчас иконка приложения.
|
|
|
|
|
|
- **Захват экрана и микрофон**: разрешения macOS выдаёт webview, отдельных
|
|
|
|
|
|
экранов-инструкций у обёртки нет.
|
2026-09-22 23:17:48 +03:00
|
|
|
|
- **Developer ID и нотаризация** macOS, EV/Trusted Signing для Windows — только
|
|
|
|
|
|
в CI с платными сертификатами (ниже — точные команды).
|
2026-09-26 18:12:42 +03:00
|
|
|
|
- **`.dmg`**: собирается из обычного терминала (Finder/AppleScript для раскладки
|
|
|
|
|
|
окна); в песочнице агента `hdiutil create` и AppleScript запрещены, но образ
|
|
|
|
|
|
**без оформления окна** собирается флагом `--sandbox-safe` к `bundle_dmg.sh`
|
|
|
|
|
|
(проверено 2026-09-26, см. ниже).
|
2026-09-22 21:55:19 +03:00
|
|
|
|
- **Windows/Linux-бандлы**: конфигурация кросс-платформенная, но собирались и
|
|
|
|
|
|
проверялись только macOS-arm64.
|
2026-09-22 23:17:48 +03:00
|
|
|
|
|
|
|
|
|
|
## Что осталось оператору (точные команды)
|
|
|
|
|
|
|
|
|
|
|
|
### macOS: `.dmg`
|
|
|
|
|
|
|
2026-09-26 18:12:42 +03:00
|
|
|
|
`bundle_dmg.sh` создаёт образ через `hdiutil create` и раскладывает иконки через
|
|
|
|
|
|
AppleScript/Finder. Из песочницы агента и из headless-сессии обе операции
|
|
|
|
|
|
запрещены (`hdiutil create` → «Операция не разрешена», AppleScript → -10004).
|
|
|
|
|
|
Из обычного терминала на macOS:
|
2026-09-22 23:17:48 +03:00
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
cd /Volumes/Samsung/projects/glchat
|
|
|
|
|
|
make desktop-build # .app + .dmg + артефакты обновления
|
|
|
|
|
|
ls -lh desktop/src-tauri/target/release/bundle/dmg/
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-09-26 18:12:42 +03:00
|
|
|
|
Если терминала с графической сессией нет (CI, headless), тот же образ
|
|
|
|
|
|
собирается без оформления окна — тем же скриптом Tauri с флагом
|
|
|
|
|
|
`--sandbox-safe` (он пропускает AppleScript и `hdiutil create`):
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
cd /Volumes/Samsung/projects/glchat
|
|
|
|
|
|
make desktop-build-app # .app и артефакты обновления
|
|
|
|
|
|
rm -rf /tmp/dmg-stage && mkdir -p /tmp/dmg-stage
|
|
|
|
|
|
cp -R desktop/src-tauri/target/release/bundle/macos/glchat.app /tmp/dmg-stage/
|
|
|
|
|
|
cd desktop/src-tauri/target/release/bundle/dmg
|
|
|
|
|
|
bash bundle_dmg.sh --volname glchat --volicon icon.icns \
|
|
|
|
|
|
--icon glchat.app 180 170 --app-drop-link 480 170 --hide-extension glchat.app \
|
|
|
|
|
|
--sandbox-safe glchat_0.1.0_aarch64.dmg /tmp/dmg-stage
|
|
|
|
|
|
hdiutil verify glchat_0.1.0_aarch64.dmg
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Такой `.dmg` монтируется и содержит `glchat.app` и ссылку `Applications`, но без
|
|
|
|
|
|
красивой раскладки иконок — для локальных проверок и CI этого достаточно, для
|
|
|
|
|
|
публикации лучше собрать обычным `make desktop-build`.
|
|
|
|
|
|
|
2026-09-22 23:17:48 +03:00
|
|
|
|
Артефакты без `.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`). Публичный ключ уже вшит в сборку и меняется только
|
|
|
|
|
|
вместе с ключом: старые сборки отвергнут манифест, подписанный новым ключом.
|