498201b413
Обёртка с первого инкремента Фазы 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::манифест_разбирается_плагином_обновлений`).
35 lines
1018 B
Go
35 lines
1018 B
Go
package server
|
||
|
||
import (
|
||
"net/http"
|
||
"strings"
|
||
|
||
"glchat/internal/httpx"
|
||
)
|
||
|
||
// reservedPrefixes are handled by the API, the gateway or the file CDN; an
|
||
// unknown path under them is a real 404 and must not receive the SPA shell.
|
||
// `/updates/` — статика автообновления: обёртка ждёт JSON или 404, а не HTML.
|
||
var reservedPrefixes = []string{"/api/", "/gateway", "/files/", "/rtc", "/updates/"}
|
||
|
||
func isReservedPath(path string) bool {
|
||
for _, prefix := range reservedPrefixes {
|
||
if path == strings.TrimSuffix(prefix, "/") || strings.HasPrefix(path, prefix) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func (s *Server) handleFallback(w http.ResponseWriter, r *http.Request) {
|
||
if isReservedPath(r.URL.Path) {
|
||
httpx.WriteError(w, httpx.NewError(httpx.CodeNotFound, "resource not found"))
|
||
return
|
||
}
|
||
if r.Method == http.MethodGet || r.Method == http.MethodHead {
|
||
s.static.serve(w, r)
|
||
return
|
||
}
|
||
httpx.WriteError(w, httpx.NewError(httpx.CodeNotFound, "resource not found"))
|
||
}
|