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::манифест_разбирается_плагином_обновлений`).
This commit is contained in:
@@ -15,6 +15,9 @@ x-app-env: &app-env
|
||||
LISTEN_ADDR: "0.0.0.0:8080"
|
||||
DATA_DIR: "/app/data"
|
||||
WEB_ROOT: "/app/web"
|
||||
# Каталог обновлений desktop-обёртки: манифесты и артефакты релиза
|
||||
# (docs/client-tauri.md §4); смонтирован только для чтения.
|
||||
UPDATES_DIR: "/app/updates"
|
||||
LOG_LEVEL: "${LOG_LEVEL}"
|
||||
LOG_FORMAT: "json"
|
||||
SQLITE_READ_POOL: "${SQLITE_READ_POOL}"
|
||||
@@ -39,6 +42,7 @@ services:
|
||||
- "${GLCHAT_DATA_DIR}/db:/app/data/db"
|
||||
- "${GLCHAT_DATA_DIR}/files:/app/data/files"
|
||||
- "${GLCHAT_DATA_DIR}/backups:/app/data/backups"
|
||||
- "${GLCHAT_UPDATES_DIR}:/app/updates:ro"
|
||||
logging: *default-logging
|
||||
deploy:
|
||||
resources:
|
||||
|
||||
@@ -16,6 +16,9 @@ x-app-env: &app-env
|
||||
LISTEN_ADDR: "0.0.0.0:8080"
|
||||
DATA_DIR: "/app/data"
|
||||
WEB_ROOT: "/app/web"
|
||||
# Каталог обновлений desktop-обёртки: манифесты и артефакты релиза
|
||||
# (docs/client-tauri.md §4); смонтирован только для чтения.
|
||||
UPDATES_DIR: "/app/updates"
|
||||
LOG_LEVEL: "${LOG_LEVEL}"
|
||||
LOG_FORMAT: "json"
|
||||
SQLITE_READ_POOL: "${SQLITE_READ_POOL}"
|
||||
@@ -46,6 +49,7 @@ services:
|
||||
- "${GLCHAT_DATA_DIR}/db:/app/data/db"
|
||||
- "${GLCHAT_DATA_DIR}/files:/app/data/files"
|
||||
- "${GLCHAT_DATA_DIR}/backups:/app/data/backups"
|
||||
- "${GLCHAT_UPDATES_DIR}:/app/updates:ro"
|
||||
logging: *default-logging
|
||||
deploy:
|
||||
resources:
|
||||
|
||||
+14
-2
@@ -122,6 +122,9 @@ GLCHAT_ASSUME_YES="${GLCHAT_ASSUME_YES:-0}"
|
||||
TARGET_DIR="${GLCHAT_ETC_DIR:-/opt/glchat}"
|
||||
GLCHAT_ETC_DIR="$TARGET_DIR"
|
||||
GLCHAT_DATA_DIR="${TARGET_DIR}/data"
|
||||
# Каталог обновлений desktop-обёртки: манифесты и артефакты раскладывает
|
||||
# scripts/desktop-release.sh, приложение отдаёт их по /updates/… (docs/client-tauri.md §4).
|
||||
GLCHAT_UPDATES_DIR="${GLCHAT_UPDATES_DIR:-${TARGET_DIR}/updates}"
|
||||
GLCHAT_INSTALL_LOG="${TARGET_DIR}/install.log"
|
||||
GLCHAT_ENV_FILE="${TARGET_DIR}/.env"
|
||||
|
||||
@@ -273,6 +276,7 @@ parse_args() {
|
||||
TARGET_DIR="$(mktemp -d /tmp/glchat-dry-run.XXXXXX)"
|
||||
GLCHAT_ETC_DIR="$TARGET_DIR"
|
||||
GLCHAT_DATA_DIR="${TARGET_DIR}/data"
|
||||
GLCHAT_UPDATES_DIR="${TARGET_DIR}/updates"
|
||||
GLCHAT_INSTALL_LOG="${TARGET_DIR}/install.log"
|
||||
GLCHAT_ENV_FILE="${TARGET_DIR}/.env"
|
||||
info "каталог ${unwritable} недоступен для записи: dry-run работает во временном ${TARGET_DIR}"
|
||||
@@ -390,6 +394,7 @@ resolve_derived_values() {
|
||||
fi
|
||||
|
||||
GLCHAT_DATA_DIR="${GLCHAT_DATA_DIR}"
|
||||
GLCHAT_UPDATES_DIR="${GLCHAT_UPDATES_DIR}"
|
||||
GLCHAT_CADDY_PROFILE="${CADDY_PROFILE}"
|
||||
GLCHAT_TURN_CERT_DIR="${TURN_CERT_DIR}"
|
||||
# TLS считается включённым и когда сертификаты обслуживает внешний прокси
|
||||
@@ -413,7 +418,7 @@ resolve_derived_values() {
|
||||
export FILES_DOMAIN_RESOLVED CADDY_PROFILE LIVEKIT_PUBLISH TURN_EXTERNAL_TLS \
|
||||
livekit_extra_codecs \
|
||||
PROFILE_LIVEKIT_DISABLE_SIMULCAST PROFILE_LIVEKIT_USE_EXTERNAL_IP \
|
||||
GLCHAT_DATA_DIR GLCHAT_CADDY_PROFILE GLCHAT_TURN_CERT_DIR \
|
||||
GLCHAT_DATA_DIR GLCHAT_UPDATES_DIR GLCHAT_CADDY_PROFILE GLCHAT_TURN_CERT_DIR \
|
||||
GLCHAT_APP_BIND GLCHAT_LIVEKIT_BIND TLS_ENABLED \
|
||||
TURN_DOMAIN_RESOLVED TURN_CERT_DIR TURN_RELAY_HOST_RESOLVED TURN_RELAY_TLS_HOST \
|
||||
TURN_RELAY_USER TURN_RELAY_PASSWORD
|
||||
@@ -672,6 +677,10 @@ generate_secrets() {
|
||||
|
||||
mkdir -p "${GLCHAT_DATA_DIR}"/{db,files,backups}
|
||||
chmod 700 "$GLCHAT_DATA_DIR"
|
||||
# Каталог обновлений монтируется в контейнер только для чтения: его
|
||||
# наполняет релизный скрипт от root, приложению нужен лишь доступ на чтение.
|
||||
mkdir -p "${GLCHAT_UPDATES_DIR}"
|
||||
chmod 755 "${GLCHAT_UPDATES_DIR}"
|
||||
# Каталоги монтируются в контейнер, который работает не от root (UID 10001):
|
||||
# без смены владельца приложение не сможет создать базу (AGENT.md 4.3).
|
||||
set_data_ownership
|
||||
@@ -751,6 +760,7 @@ TLS_BY_PROXY=@TLS_BY_PROXY@
|
||||
|
||||
# --- интерполяция compose (пути, адреса привязки, профиль) ---
|
||||
GLCHAT_DATA_DIR=@GLCHAT_DATA_DIR@
|
||||
GLCHAT_UPDATES_DIR=@GLCHAT_UPDATES_DIR@
|
||||
GLCHAT_CADDY_PROFILE=@CADDY_PROFILE@
|
||||
GLCHAT_TURN_CERT_DIR=@GLCHAT_TURN_CERT_DIR@
|
||||
GLCHAT_APP_BIND=@GLCHAT_APP_BIND@
|
||||
@@ -856,6 +866,7 @@ TLS_ENABLED=${TLS_ENABLED}
|
||||
SKIP_TLS=${SKIP_TLS}
|
||||
TLS_BY_PROXY=${TLS_BY_PROXY}
|
||||
GLCHAT_DATA_DIR=${GLCHAT_DATA_DIR}
|
||||
GLCHAT_UPDATES_DIR=${GLCHAT_UPDATES_DIR}
|
||||
CADDY_PROFILE=${CADDY_PROFILE}
|
||||
GLCHAT_TURN_CERT_DIR=${TURN_CERT_DIR}
|
||||
GLCHAT_APP_BIND=${APP_BIND_ADDR}
|
||||
@@ -1113,7 +1124,8 @@ render_all_templates() {
|
||||
"PROFILE=${PROFILE_RESOLVED}" "APP_IMAGE=${APP_IMAGE}" \
|
||||
"APP_PORT=${APP_PORT}" "LIVEKIT_WS_PORT=${LIVEKIT_WS_PORT}" \
|
||||
"LIVEKIT_TCP_PORT=${LIVEKIT_TCP_PORT}" "LIVEKIT_UDP_PORT=${LIVEKIT_UDP_PORT}" \
|
||||
"GLCHAT_DATA_DIR=${GLCHAT_DATA_DIR}" "LOG_LEVEL=${LOG_LEVEL}" \
|
||||
"GLCHAT_DATA_DIR=${GLCHAT_DATA_DIR}" "GLCHAT_UPDATES_DIR=${GLCHAT_UPDATES_DIR}" \
|
||||
"LOG_LEVEL=${LOG_LEVEL}" \
|
||||
"SQLITE_READ_POOL=${PROFILE_SQLITE_READ_POOL}" \
|
||||
"SQLITE_CACHE_SIZE=-${PROFILE_SQLITE_CACHE_MB}000" \
|
||||
"SQLITE_MMAP_SIZE=$((PROFILE_SQLITE_MMAP_MB * 1024 * 1024))" \
|
||||
|
||||
Reference in New Issue
Block a user