Commit Graph

11 Commits

Author SHA1 Message Date
grendervill b9d458e133 fix(cli,web): команды аккаунтов в CLI и повтор запросов при 429
- deploy/glchat-cli.sh: reset-password/make-admin/remove-admin больше не
  заглушки «появится в фазе 1» (AGENT.md 10.7), добавлены totp-setup,
  totp-reset и delete-user; подкоманды обслуживания идут через общий app_cli
- cmd/glchat: команда delete-user (мягкое удаление как в админ-панели,
  защита от удаления инстанс-админа и подтверждение --yes), исправлен
  потерянный комментарий cliCleanup
- deploy/install.sh: убраны устаревшие формулировки про фазу 1
- web: политика повторов запросов — 429 повторяется с паузой retry_after_ms
  (до двух раз, не дольше 5 с), 4xx не повторяются; тесты web/tests/queryRetry.test.ts
- scripts/test-install.sh: проверка, что заглушек в CLI не осталось
2026-09-26 18:32:51 +03:00
grendervill 72a51f083d test(desktop): замеры холодного старта и памяти обёртки
Требование спецификации §1/§7 (idle RAM ≤ 350 МБ, холодный старт ≤ 2 с) до сих
пор не было измерено. `scripts/desktop-perf.py` запускает собранный `.app`,
меряет время от `open` до строки «загружена страница» в логе обёртки и память
после того, как клиент успокоился.

`ps` и `top` в песочнице агента недоступны, поэтому на macOS память и список
процессов читаются через `libproc` (`proc_pid_rusage`, `proc_listpids`), а для
Linux есть ветка на `/proc`. Помощники WebKit (`WebContent`, `GPU`,
`Networking`) запускаются через launchd, их родитель — не приложение, поэтому
«свои» определяются сравнением списка процессов до и после запуска: иначе RSS
главного процесса занижал бы потребление втрое.

Замеры (macOS arm64, Mac на M-серии, инстанс через внешний прокси):
холодный старт 708–740 мс, RSS открытого клиента 252 МБ (обёртка 117 + WebKit
135), в трее 251 МБ, физический след 103 МБ. Результаты — в `docs/PERF.md` и
`desktop/README.md`.

В Makefile добавлена цель `desktop-build-app` (`tauri build --bundles app`):
артефакты обновления собираются без `.dmg`, который в песочнице и headless-
окружении падает на `bundle_dmg.sh` (монтирование образа + AppleScript).
2026-09-22 23:17:39 +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
grendervill d2712c4c99 feat(ops): сверка .env инстанса с таблицей профилей
- scripts/test-profiles.sh --env <файл>: профиль берётся из .env, значения
  таблицы 10.10 пересчитываются и сверяются с производными ключами
  (SQLITE_MMAP_SIZE, MAX_UPLOAD_SIZE, APP_MEMORY_LIMIT, SHARE_SOFT_LIMIT,
  BACKUP_KEEP_DAYS и т. д.), offsite-ключи проверяются на наличие.
- make profiles-env — тот же прогон для /opt/glchat/.env.
- На стенде: профиль standard совпал (67 проверок, 0 ошибок); .env дополнен
  ключами offsite, установленный backup.sh обновлён, выгрузка и недоступный
  приёмник проверены вживую.
2026-09-22 18:46:49 +03:00
grendervill 3151327f6e feat(ops): offsite-бэкапы и проверки профилей
- deploy/backup.sh: BACKUP_OFFSITE_TARGET (rsync или rclone:remote:path)
  выгружает готовый архив после локальной ротации; неудача выгрузки только
  предупреждает. Ротация на приёмнике — опционально
  (BACKUP_OFFSITE_KEEP_DAYS, rclone delete --min-age). Имя архива получает
  суффикс, если бэкап запущен дважды в одну секунду.
- deploy/install.sh: offsite-настройки в .env (BACKUP_OFFSITE_TARGET,
  BACKUP_OFFSITE_KEEP_DAYS) отдельным разделом; детектор незаполненных
  плейсхолдеров учитывает цифры в именах (FAIL2BAN_ENABLED); dry-run больше не
  требует прав на /opt/glchat — явность GLCHAT_ETC_DIR фиксируется до
  подключения common.sh, иначе ветка временного каталога была недостижима.
- scripts/test-profiles.sh: сверка таблицы профилей 10.10 и одинакового набора
  PROFILE_* (46 проверок), make profiles-test / ops-test.
- scripts/test-backup.sh: бэкап и offsite на синтетическом инстансе — локальный
  rsync-приёмник, шим rclone, недоступный хост, нечисловой срок (12 проверок),
  make backup-test.
- scripts/test-install.sh: проверка offsite-ключей в .env после установки.
2026-09-22 18:44:50 +03:00
grendervill 8b477ac314 feat(deploy): TURN/TLS анонсируется именем хоста с сертификатом
Браузеры отклоняют TLS-соединение к TURN по IP, если сертификат выписан на
домен: релей с публичным адресом отдавал `turns:<ip>:5349`, и этот эндпоинт был
нерабочим.

- флаг `--turn-relay-tls-host` (по умолчанию `turn.<домен>`, если он резолвится,
  иначе адрес релея) попадает в `rtc.turn_servers` как TLS-эндпоинт;
- значение сохраняется в `.env` и переиспользуется при `--reconfigure`;
- `scripts/test-install.sh` проверяет, что в `livekit.yaml` попали и UDP-адрес,
  и TLS-имя релея.
2026-09-20 20:48:32 +03:00
grendervill fb221c91bb fix(install): повторный запуск установщика больше не меняет секреты
Найдено на стенде: повторный `install.sh` без `--reconfigure` генерировал новые
MASTER_KEY/SESSION_PEPPER/TOTP_ENCRYPTION_KEY, после чего email-индекс и
TOTP-секреты существующих пользователей перестали расшифровываться (вход
возвращал `auth.invalid_credentials`).

- секреты переиспользуются при любом повторном запуске, а не только с
  `--reconfigure`;
- добавлен явный флаг `--rotate-secrets` с предупреждением о последствиях;
- age-ключ бэкапов сохраняется, старые бэкапы остаются читаемыми;
- защита от потери `.env`: если база есть, а MASTER_KEY нет — установщик
  останавливается с объяснением вместо молчаливой генерации новых ключей;
- `scripts/test-install.sh` сравнивает секреты до и после повторной установки
  и падает при их изменении.
2026-09-19 22:08:47 +03:00
grendervill 8f7620026d fix(livekit): конфиг по официальной схеме livekit-server
- убраны недопустимые поля (audio.min_level, audio.smooth_intervals,
  video.dynacast, video.disable_simulcast) — из-за них контейнер livekit
  циклически перезапускался
- набор кодеков задаётся через room.enabled_codecs: VP8 на lite/standard,
  VP8+VP9+H.264+AV1 на plus/pro
- keys: {} — ключи передаются через LIVEKIT_KEYS (секрет не попадает в файл)
- smoke.sh: ветка --skip-tls больше не проверяет HTTPS, бэкап с расширением
  .age больше не помечается как незашифрованный
2026-09-19 19:55:31 +03:00
grendervill 37fa3e0668 test(deploy): разделение проверок установщика и сборки образа
- install.sh: флаг --skip-image для прогона в DinD, где сборка невозможна
  (overlay поверх overlay), с пропуском pull/up/smoke
- scripts/test-install.sh: полный набор проверок артефактов (compose config -q,
  Caddyfile, livekit.yaml, nftables, fail2ban, systemd-юниты, UFW, jails)
2026-09-19 19:34:05 +03:00
grendervill 99e0f72844 fix(deploy): надёжный рендер шаблонов и устойчивый цикл подстановки
- render_template: значения передаются через окружение (envsubst), а не
  подставляются в variadic-аргументы — многострочные значения больше не
  ломают разбор, неполный список не уводит bash в бесконечный цикл
- export "$pair": bash сам разбирает KEY=value (было export "${pair}")
- шаблоны переведены на ${VAR}-синтаксис, добавлен compose.test.yaml.tpl
  для тестового режима установщика (--skip-tls без публикации портов)
- dry-run не требует root и работает вне Linux (проверка ОС пропускается)
- install.sh: учитываются --livekit-*-порты и --turn-*-порты в livekit.yaml,
  исправлено экранирование апострофа в итоговой сводке
- scripts/debug-install.sh: трассировка установщика в контейнере
2026-09-19 19:26:26 +03:00
grendervill 4bd1770cb5 feat(deploy): установщик, docker-скелет, шаблоны Caddy/LiveKit и CLI
- deploy/install.sh: идемпотентная установка, режимы встроенного и внешнего
  прокси (§10.13), --dry-run/--skip-tls/--reconfigure, auto-детект профиля (§10.10)
- deploy/compose.yaml.tpl: app, livekit, caddy с лимитами, healthcheck,
  ротацией логов, cap_drop и no-new-privileges (§4.3)
- deploy/caddy: Caddyfile.tpl (встроенный) и external-proxy.conf.tpl (наш стенд)
- deploy/livekit: конфиг с TURN, dynacast и лимитами комнат (§10.6)
- deploy/backup.sh + restore.sh: sqlite .backup, tar, age-шифрование (§10.8)
- deploy/update.sh: бэкап → pull/сборка → up → миграции → smoke (§9.3)
- deploy/smoke.sh: проверки §10.9 (healthz/readyz/meta, порты, UFW, fail2ban, бэкап)
- deploy/security-check.sh: аудит SSH, UFW, fail2ban, прав .env и ключей
- deploy/ufw-docker.sh, nftables и fail2ban-конфиги (sshd, glchat-auth, recidive)
- deploy/glchat-cli.sh: команды §10.7, systemd-юнит glchat.service
- scripts/test-install.sh: прогон установщика в чистом Ubuntu 24.04 (systemd-контейнер)
- Dockerfile: multi-stage (node → golang CGO → debian-slim), non-root, healthcheck
- Makefile: lint/test/build/dev/generate/security/smoke и фиксация версий инструментов
2026-09-19 18:21:02 +03:00