2 Commits

Author SHA1 Message Date
grendervill 8814fe7cf4 release: glchat 0.1.1
Версия — в одном месте: файл VERSION в корне, из него её берут Makefile,
deploy/install.sh (IMAGE_TAG) и desktop-обёртка (tauri.conf.json, Cargo.toml).

Публикация обновлений (scripts/desktop-release.sh):
- манифесты прошлых релизов удаляются: оставшийся <старая версия>.json
  перекрывал новый релиз — обёртка спрашивает манифест своей версии и видела
  «обновлений нет»;
- mktemp-каталог раскладки получает 755, каталоги на инстансе
  нормализуются: rsync -a переносил режим 0700 на корень каталога обновлений,
  и приложение не могло читать манифесты (404 при живых файлах);
- --remote работает без TTY и без sudo, если каталог обновлений записываем;
  опции ssh — в GLCHAT_SSH_OPTS;
- предупреждение при публикации версии старше уже опубликованной.

Сервер: HEAD /updates/... (chi не выводит его из GET) — размер и тип артефакта
без скачивания; тест TestUpdatesAnswersHead.

Приёмка на живом канале: 0.1.0 → 0.1.1 на стенде (скачивание, проверка
подписи, установка, перезапуск), /Applications/glchat.app = 0.1.1.
2026-09-26 20:14:25 +03:00
grendervill 498201b413 feat(server): инстанс отдаёт манифест автообновления desktop-обёртки
Обёртка с первого инкремента Фазы 6 запрашивает
`GET /updates/{target}/{arch}/{current_version}`, но на инстансе этого пути не
было: запрос попадал в SPA-заглушку, клиент получал HTML с кодом 200 и писал
в лог «error decoding response body».

Что сделано:

- `internal/server/updates.go` — статика автообновления: каталог `UPDATES_DIR`
  (в контейнере `/app/updates:ro`), раскладка `<target>/<arch>/<version>.json` +
  артефакт и `.sig`; версия без расширения отображается на `<version>.json`,
  а если его нет — на `latest.json` (обёртка приходит со своей установленной
  версией, «новее ли релиз» решает клиент сравнением semver). Для имён
  с расширением артефакта запасного варианта нет: иначе вместо архива клиент
  скачал бы манифест и подпись не сошлась бы. Сегменты пути проверяются по
  символам, выход за каталог невозможен; `/updates/` добавлен в reservedPrefixes,
  поэтому отсутствующий манифест — честный 404, а не HTML-шелл;
- `scripts/desktop-release.sh` и цели `make desktop-release` /
  `make desktop-release-upload HOST=…`: берут собранный `glchat.app.tar.gz` + `.sig`,
  определяют target/arch по имени артефакта, копируют их в каталог инстанса и
  собирают манифест (версия из `tauri.conf.json`), объединяя платформы одной
  версии; есть `--dry-run` и загрузка на инстанс по SSH;
- `deploy/install.sh` и шаблоны compose: каталог `${GLCHAT_UPDATES_DIR}`
  (`/opt/glchat/updates`) создаётся установщиком и монтируется в контейнер
  только для чтения; в `.env` добавлен `GLCHAT_UPDATES_DIR`.

Владельцу внешнего прокси добавлять ничего не нужно: `/updates/...` уходит в
catch-all `handle` и доходит до приложения, как статика клиента.

Проверено на стенде: `curl https://gl.mhspx.su/updates/darwin/aarch64/0.1.0`
→ 200 `application/json; charset=utf-8`, артефакт → 200 `application/gzip`,
отсутствующие платформа и версия → 404 (не HTML), `readyz` → 200, обёртка
в логе пишет «обновлений нет» вместо ошибки разбора. Тесты: 6 новых в
`internal/server/updates_test.go`, манифест разбирается типом самого плагина
обновлений (`updates::tests::манифест_разбирается_плагином_обновлений`).
2026-09-22 23:17:22 +03:00