docs(desktop): приёмка автообновления, .dmg в песочнице и открытые пункты

- `--update-now` в таблице аргументов и в разделе «Автообновление»: что делает,
  как запускать, что подпись проверяется так же, пример записей в журнале;
- «Локальная проверка «поверх старой версии»»: пошаговый рецепт прогона
  0.1.0 → 0.1.1 на локальном сервере инстанса и три грабли — http-эндпоинт
  требует временного `dangerousInsecureTransportProtocol`, путь приложения не
  должен содержать символических ссылок (`/tmp` → `/private/tmp`), в headless
  нужен `--update-now`;
- `.dmg`: точные команды — обычным терминалом (`make desktop-build`, с
  оформлением окна) и без графической сессии (`bundle_dmg.sh --sandbox-safe`,
  проверено: образ монтируется, `hdiutil verify` — VALID);
- «Что не сделано»: автообновление end-to-end проверено локально, открытым
  остаётся публикация реального релиза; добавлено наблюдение, что `open --args`
  из песочницы агента аргументы не доставляет (проверено на macOS 27).
This commit is contained in:
2026-09-26 18:12:42 +03:00
parent 2d013df0bb
commit e7c9c93443
+116 -8
View File
@@ -32,6 +32,12 @@ open desktop/src-tauri/target/release/bundle/macos/glchat.app
Finder через AppleScript, поэтому из песочницы агента и из headless-окружения он Finder через AppleScript, поэтому из песочницы агента и из headless-окружения он
падает (см. «Что не сделано»). падает (см. «Что не сделано»).
`open --args` из песочницы агента аргументы не доставляет (проверено на
macOS 27: и на бандле glchat, и на системном TextEdit — процесс запускается без
них; из обычного терминала то же самое работает). Поэтому `make desktop-run
ARGS='--minimized'` и замер свёрнутого окна в `scripts/desktop-perf.py` надо
запускать из обычного терминала, а не из песочницы.
Сборка релиза с артефактами автообновления использует ключ подписи: Сборка релиза с артефактами автообновления использует ключ подписи:
```bash ```bash
@@ -53,6 +59,7 @@ make desktop-build
| `--minimized` | запуск свёрнутым в трей (используется автозапуском) | | `--minimized` | запуск свёрнутым в трей (используется автозапуском) |
| `--settings` | сразу открыть страницу настроек обёртки | | `--settings` | сразу открыть страницу настроек обёртки |
| `--autostart on\|off` | включить или выключить автозапуск при установке скриптом | | `--autostart on\|off` | включить или выключить автозапуск при установке скриптом |
| `--update-now` | сразу проверить обновление и поставить его без диалога (тихое обновление) |
## Настройки ## Настройки
@@ -87,6 +94,33 @@ make desktop-build
`GET {instance}/updates/{target}/{arch}/{current_version}` (target: `GET {instance}/updates/{target}/{arch}/{current_version}` (target:
`darwin`/`windows`/`linux`, arch: `aarch64`/`x86_64`; см. `src/updates.rs`). `darwin`/`windows`/`linux`, arch: `aarch64`/`x86_64`; см. `src/updates.rs`).
Найденное обновление показывается в интерфейсе, установка спрашивается
отдельным диалогом («во время звонка перезапуск без согласия недопустим»).
Для управляемых машин есть тихий режим — флаг `--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, …
```
Манифест отдаёт сам инстанс (`internal/server/updates.go`), отдельная статика в Манифест отдаёт сам инстанс (`internal/server/updates.go`), отдельная статика в
Caddy не нужна: и встроенный Caddy профиля, и внешний прокси стенда проксируют Caddy не нужна: и встроенный Caddy профиля, и внешний прокси стенда проксируют
весь домен на приложение, поэтому `/updates/...` доходит до него без правок весь домен на приложение, поэтому `/updates/...` доходит до него без правок
@@ -145,6 +179,54 @@ curl -s https://gl.mhspx.su/updates/darwin/aarch64/0.1.0 | jq .
подтверждение в диалоге (во время звонка перезапуск без согласия не делается) → подтверждение в диалоге (во время звонка перезапуск без согласия не делается) →
подпись проверяется до установки, downgrade отклоняется. подпись проверяется до установки, downgrade отклоняется.
### Локальная проверка «поверх старой версии» (без стенда)
Установка обновления проверена локально на настоящем сервере инстанса: сборка
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`,
а версия на диске — остаться прежней.
## Уведомления и переход в канал ## Уведомления и переход в канал
Решение «показывать ли уведомление» принимает веб-клиент (упоминания и личные Решение «показывать ли уведомление» принимает веб-клиент (упоминания и личные
@@ -196,16 +278,21 @@ python3 scripts/desktop-perf.py --runs 3 # 3 холодных старта +
## Что не сделано (открытые пункты Фазы 6) ## Что не сделано (открытые пункты Фазы 6)
- **Автообновление end-to-end не проверено на реальном релизе**: манифест - **Автообновление проверено локально «поверх старой версии»** (2026-09-26):
отдаётся и разбирается (проверено на стенде), но установка «поверх старой 0.1.0 → манифест 0.1.1 → скачивание → проверка подписи → установка →
версии» требует публикации версии с бо́льшим номером — это действие оператора перезапуск на 0.1.1, подробности и команды — «Локальная проверка» выше.
(см. «Публикация релиза» выше). Открытым остаётся публикация реального релиза на стенде (номер версии
поднимает оператор) и то, что локальная проверка шла по http с временным
флагом `dangerousInsecureTransportProtocol` — на стенде канал https.
- **Аватары в уведомлениях** (thumb/буква) и группировка — сейчас иконка приложения. - **Аватары в уведомлениях** (thumb/буква) и группировка — сейчас иконка приложения.
- **Захват экрана и микрофон**: разрешения macOS выдаёт webview, отдельных - **Захват экрана и микрофон**: разрешения macOS выдаёт webview, отдельных
экранов-инструкций у обёртки нет. экранов-инструкций у обёртки нет.
- **Developer ID и нотаризация** macOS, EV/Trusted Signing для Windows — только - **Developer ID и нотаризация** macOS, EV/Trusted Signing для Windows — только
в CI с платными сертификатами (ниже — точные команды). в CI с платными сертификатами (ниже — точные команды).
- **`.dmg`**: собирается только из обычного терминала (Finder/AppleScript). - **`.dmg`**: собирается из обычного терминала (Finder/AppleScript для раскладки
окна); в песочнице агента `hdiutil create` и AppleScript запрещены, но образ
**без оформления окна** собирается флагом `--sandbox-safe` к `bundle_dmg.sh`
(проверено 2026-09-26, см. ниже).
- **Windows/Linux-бандлы**: конфигурация кросс-платформенная, но собирались и - **Windows/Linux-бандлы**: конфигурация кросс-платформенная, но собирались и
проверялись только macOS-arm64. проверялись только macOS-arm64.
@@ -213,9 +300,10 @@ python3 scripts/desktop-perf.py --runs 3 # 3 холодных старта +
### macOS: `.dmg` ### macOS: `.dmg`
`bundle_dmg.sh` монтирует образ (`hdiutil`) и управляет Finder через AppleScript, `bundle_dmg.sh` создаёт образ через `hdiutil create` и раскладывает иконки через
поэтому из песочницы агента и из headless-сессии он падает. Из обычного AppleScript/Finder. Из песочницы агента и из headless-сессии обе операции
терминала на macOS: запрещены (`hdiutil create` → «Операция не разрешена», AppleScript → -10004).
Из обычного терминала на macOS:
```bash ```bash
cd /Volumes/Samsung/projects/glchat cd /Volumes/Samsung/projects/glchat
@@ -223,6 +311,26 @@ make desktop-build # .app + .dmg + артефакты обнов
ls -lh desktop/src-tauri/target/release/bundle/dmg/ ls -lh desktop/src-tauri/target/release/bundle/dmg/
``` ```
Если терминала с графической сессией нет (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`.
Артефакты без `.dmg` (для автообновления) собираются где угодно: Артефакты без `.dmg` (для автообновления) собираются где угодно:
`make desktop-build-app`. `make desktop-build-app`.