fix(nginx-152): корень редиректит на активную сборку — закрыта кэш-мина (ассеты Unity зовутся относительным путём)
- index.html: buildUrl="Build" -> без редиректа ассеты шли как /Build/WebGL.wasm (без номера сборки) под immutable 7д -> браузер неделю отдавал старую сборку - publish-release.sh --activate пишет /etc/nginx/game-active.inc: return 302 /<N>/ - immutable сужен до путей с номером сборки; остальное no-store - приёмка: / -> 302 /29/, браузер тянет /29/Build/* (все 200), autoclose-check 9/9, testfront цел - сборка 29 (fix webgl: дым падения и PC URP) опубликована на nginx
This commit is contained in:
@@ -258,3 +258,38 @@
|
||||
пересборку образа на каждый билд игры и не решает вопрос кэша; (б) поднять в LXC не nginx, а
|
||||
podman-контейнер с nginx — лишний слой на 10 строках конфига; (в) отдавать статику прямо с k3s-Traefik
|
||||
по PVC — не то, о чём просил владелец (он хотел nginx на проксмоксе).
|
||||
|
||||
## ADR-011-доп — корень редиректит на активную сборку (закрыта кэш-мина ADR-010) (2026-09-15)
|
||||
|
||||
**Что нашли на сборке 29 (проверка живьём, не по документам).** `index.html` Unity ссылается на
|
||||
ассеты **относительным** путём (`var buildUrl = "Build"`). Пока корень отдавал файлы напрямую из
|
||||
`/opt/game/current`, игрок получал ассеты по URL **без номера сборки** — `/Build/WebGL.wasm`,
|
||||
`/Build/WebGL.data` и т.д. Эти URL между релизами **не меняются**, а в конфиге они попадали под
|
||||
маску `immutable` (7 суток). Итог: у игравшего игрока браузер неделю держал бы ассеты старой сборки
|
||||
и молча игнорировал новый билд. То есть моя собственная маска кэша воспроизводила ровно ту мину,
|
||||
которую ADR-011 объявлял закрытой. Версионирование каталогами **само по себе** её не лечило —
|
||||
лечило только то, чтобы URL ассетов реально содержали номер сборки.
|
||||
|
||||
**Решение.** Корень больше не раздаёт файлы — он **302-редиректит на активную сборку**:
|
||||
- `publish-release.sh --activate` пишет `/etc/nginx/game-active.inc`:
|
||||
`location = / { return 302 /<N>/; }` + то же для `/index.html`;
|
||||
- `include /etc/nginx/game-active.inc;` подключён в server-блок;
|
||||
- после редиректа `location.pathname` = `/29/`, относительный `buildUrl = "Build"` резолвится
|
||||
в `/29/Build/…` — версионированный URL, старый URL перестаёт использоваться вовсе;
|
||||
- маска `immutable` сужена до путей **с номером сборки** (`~^/[0-9]+/…`); всё остальное,
|
||||
включая неверсионированные ассеты, теперь `no-store` (страховка от отравления кэша).
|
||||
|
||||
**Проверено живьём (сборка 29):** `/` → **302 → `/29/`**; браузер приземляется на `/29/` и тянет
|
||||
`/29/Build/WebGL.wasm`, `/29/Build/WebGL.data`, `/29/Build/WebGL.framework.js` — все 200, ни одной
|
||||
ошибки в консоли; `/28/`,`/27/` живы; неверсионированный `/Build/WebGL.wasm` → 200 с `cache-control:
|
||||
no-store`; `/api/state`,`/api/price`,`/solana-rpc/` → 200; сквозная ставка `autoclose-check` — **9/9**;
|
||||
`testfront` не задет (200).
|
||||
|
||||
**Почему не «отдавать корень без редиректа».** Это работало бы (неверсионированные ассеты получили бы
|
||||
`no-store`), но теряется весь смысл `immutable`-кэширования: ассеты в 52 МБ + 18 МБ перекачивались бы
|
||||
при каждом заходе. Редирект даёт и корректность, и кэш.
|
||||
|
||||
**Остаточный риск.** Игрок, у которого в кэше уже лежат ассеты по **старому** неверсионированному URL
|
||||
(`/Build/WebGL.wasm`, полученные до этого фикса), один раз может получить старый билд — URL теперь
|
||||
`no-store`, так что после первой перезагрузки браузер сходит на сервер и получит свежее. Новые
|
||||
версии этой проблемы не имеют вовсе.
|
||||
|
||||
@@ -49,8 +49,16 @@ cd ~/WORK/games/pump-game-ops && BASE=https://smart-updown.binom.pw/api \
|
||||
относительным путём (`Build/WebGL.loader.js`), поэтому без слэша `Build/...` уедет в корень → 404.
|
||||
- **`skopeo ... dir:` пишет OCI-манифест**, где `layers` — словари. `unpack-release.sh` берёт
|
||||
`digest` каждого слоя и распаковывает по имени файла `<hex>` (не `sha256:...`).
|
||||
- **Кэш больше не прилипает между релизами**: URL содержит номер сборки → `immutable` корректен.
|
||||
Внутри версии каталог не перезаписывается, поэтому ассеты действительно неизменяемы.
|
||||
- **Корень — 302 на активную сборку** (`location = / { return 302 /<N>/; }` в
|
||||
`/etc/nginx/game-active.inc`, пишет `publish-release.sh --activate`). Нужен потому, что
|
||||
`index.html` Unity зовёт ассеты **относительным** путём (`buildUrl = "Build"`): без редиректа они
|
||||
запрашивались бы как `/Build/WebGL.wasm` — без номера сборки, а такой URL между релизами НЕ
|
||||
меняется → `immutable` заставлял браузер неделю отдавать старый билд. С редиректом путь становится
|
||||
`/29/Build/…` и кэш корректен.
|
||||
- **`immutable` только для путей с номером сборки** (`~^/[0-9]+/…`). Всё остальное, включая
|
||||
неверсионированные ассеты, — `no-store` (страховка от отравления кэша).
|
||||
- ⚠️ Если правишь `game.nginx.conf` — не забудь `include /etc/nginx/game-active.inc;`, иначе корень
|
||||
снова начнёт раздавать файлы напрямую и мина вернётся.
|
||||
- **Сборки копятся** (~68 МБ на версию) — ретеншен пока ручной, см. `TASKS.md`.
|
||||
- **Откат схемы целиком**: вернуть `/opt/traefik/static/smart-updown.yaml` из
|
||||
`/root/bak-smart-updown-nginx-2026-09-15/` на VDS — домен снова пойдёт целиком в куб.
|
||||
|
||||
@@ -5,9 +5,18 @@
|
||||
# /opt/game/releases/<N>/ — файлы сборки N (Build/, TemplateData/, index.html)
|
||||
# /opt/game/current -> releases/<N> — активная сборка (симлинк, переключает publish-release.sh)
|
||||
#
|
||||
# https://smart-updown.binom.pw/ -> активная сборка (current)
|
||||
# https://smart-updown.binom.pw/28/ -> конкретная сборка 28
|
||||
# https://smart-updown.binom.pw/28/Build/WebGL.data -> ассет сборки 28
|
||||
# https://smart-updown.binom.pw/ -> 302 на активную сборку (/29/)
|
||||
# https://smart-updown.binom.pw/29/ -> конкретная сборка 29
|
||||
# https://smart-updown.binom.pw/29/Build/WebGL.data -> ассет сборки 29
|
||||
#
|
||||
# ⚠️ ПОЧЕМУ КОРЕНЬ РЕДИРЕКТИТ, А НЕ ОТДАЁТ ФАЙЛЫ:
|
||||
# index.html сборки ссылается на ассеты ОТНОСИТЕЛЬНЫМ путём (`buildUrl = "Build"`).
|
||||
# Если отдавать его прямо с корня, ассеты запрашиваются как /Build/WebGL.wasm — БЕЗ номера
|
||||
# сборки. Такой URL между релизами НЕ меняется, а мы отдаём его `immutable` на 7 суток →
|
||||
# браузер игрока неделю держит ассеты старой сборки и игнорирует новый билд (мина из ADR-010).
|
||||
# При редиректе на /<N>/ все ассеты получают версионированный URL (/29/Build/...), старый URL
|
||||
# просто перестаёт использоваться, и `immutable` становится корректным.
|
||||
# Номер активной сборки пишет publish-release.sh в отдельный include-файл /etc/nginx/game-active.inc.
|
||||
#
|
||||
# API и WebSocket nginx НЕ обслуживает: их забирает Traefik на VDS
|
||||
# (router smart-updown-api: PathPrefix /api,/ws -> куб 192.168.76.120).
|
||||
@@ -15,11 +24,10 @@
|
||||
# которые всё же дойдут до nginx (прямой заход по IP, старые сборки).
|
||||
|
||||
# Ассеты Unity внутри каталога версии неизменяемы -> immutable.
|
||||
# index.html и всё остальное -> no-store.
|
||||
# Всё, что НЕ под номером сборки, и index.html -> no-store.
|
||||
map $uri $game_cache_control {
|
||||
default "no-store";
|
||||
~^/[0-9]+/.+\.(js|wasm|data|mem|symbols|css|png|jpg|jpeg|gif|svg|woff2?)$ "public, max-age=604800, immutable";
|
||||
~^/.+\.(js|wasm|data|mem|symbols|css|png|jpg|jpeg|gif|svg|woff2?)$ "public, max-age=604800, immutable";
|
||||
}
|
||||
|
||||
server {
|
||||
@@ -39,60 +47,55 @@ server {
|
||||
|
||||
add_header Cache-Control $game_cache_control;
|
||||
|
||||
# --- корень: 302 на активную сборку ---------------------------------------
|
||||
# Файл переписывает publish-release.sh при --activate.
|
||||
include /etc/nginx/game-active.inc;
|
||||
|
||||
# --- версионированные сборки: /<число>/... -------------------------------
|
||||
# /28/index.html -> /opt/game/releases/28/index.html
|
||||
# /29/index.html -> /opt/game/releases/29/index.html
|
||||
location ~ ^/(?<build>[0-9]+)(?<subpath>/.*)?$ {
|
||||
root /opt/game/releases;
|
||||
try_files /$build$subpath /$build$subpath/ =404;
|
||||
}
|
||||
|
||||
# --- ассеты активной сборки ----------------------------------------------
|
||||
location ~* \.(js|wasm|data|mem|symbols|json|css|png|jpg|jpeg|gif|svg|woff2?)$ {
|
||||
root /opt/game/current;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# --- страховка: API -------------------------------------------------------
|
||||
# Нормальный /api идёт мимо nginx (Traefik VDS -> куб). Если запрос всё же
|
||||
# пришёл сюда — уходим в куб на k3s-Traefik с правильным Host, дальше
|
||||
# ingress smart-updown-api ведёт в шлюз :8895.
|
||||
location /api {
|
||||
proxy_pass http://192.168.76.120;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host smart-updown.binom.pw;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_connect_timeout 10s;
|
||||
proxy_read_timeout 90s;
|
||||
}
|
||||
|
||||
# страховка: WebSocket шлюза
|
||||
location /ws {
|
||||
proxy_pass http://192.168.76.120;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host smart-updown.binom.pw;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
# страховка: тонкий CORS-прокси до Solana RPC (старые сборки игры)
|
||||
# --- страховка: WebSocket -------------------------------------------------
|
||||
location /ws {
|
||||
proxy_pass http://192.168.76.120;
|
||||
proxy_set_header Host smart-updown.binom.pw;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
# --- страховка: Solana RPC -------------------------------------------------
|
||||
# /solana-rpc/ НЕ ловится PathPrefix /api на VDS, поэтому уходит в корневой
|
||||
# router, т.е. сюда. Старые сборки игры стучатся именно в этот путь.
|
||||
location /solana-rpc/ {
|
||||
proxy_pass http://192.168.76.181:8899/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host 192.168.76.181:8899;
|
||||
proxy_connect_timeout 5s;
|
||||
proxy_read_timeout 30s;
|
||||
add_header Access-Control-Allow-Origin "*" always;
|
||||
add_header Access-Control-Allow-Headers "content-type" always;
|
||||
add_header Access-Control-Allow-Methods "POST, OPTIONS" always;
|
||||
if ($request_method = OPTIONS) { return 204; }
|
||||
}
|
||||
|
||||
# --- корень = активная сборка --------------------------------------------
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
# 1. тянет образ images.binom.pw/fun/game-front2:<тег> из реестра (skopeo copy в dir);
|
||||
# 2. распаковывает статику в /opt/game/releases/<тег>/;
|
||||
# 3. без --activate оставляет активной текущую сборку (корень не меняется);
|
||||
# с --activate переключает симлинк /opt/game/current на новую версию;
|
||||
# с --activate переключает симлинк /opt/game/current на новую версию
|
||||
# И пишет /etc/nginx/game-active.inc (редирект корня на /<тег>/);
|
||||
# 4. печатает проверку.
|
||||
#
|
||||
# Итог: https://smart-updown.binom.pw/ -> активная сборка
|
||||
# Итог: https://smart-updown.binom.pw/ -> 302 на активную сборку
|
||||
# https://smart-updown.binom.pw/<тег>/ -> конкретная сборка
|
||||
set -euo pipefail
|
||||
|
||||
@@ -19,6 +20,11 @@ ACTIVATE="${2:-}"
|
||||
REG="images.binom.pw/fun/game-front2"
|
||||
CREDS="subochev:Drovosek319"
|
||||
IMG="/tmp/gameimg-${TAG}"
|
||||
ACTIVE_INC="/etc/nginx/game-active.inc"
|
||||
|
||||
case "$TAG" in
|
||||
''|*[!0-9]*) echo "тег должен быть числом (номер сборки), получено: $TAG" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
echo "=== 1/4 тяну образ ${REG}:${TAG} ==="
|
||||
rm -rf "$IMG"
|
||||
@@ -34,14 +40,28 @@ if [ "$ACTIVATE" = "--activate" ]; then
|
||||
PREV="$(readlink -f /opt/game/current || echo none)"
|
||||
rm -f /opt/game/current
|
||||
ln -s "/opt/game/releases/${TAG}" /opt/game/current
|
||||
|
||||
# корень редиректим на /<тег>/: тогда ассеты получают версионированный URL
|
||||
# и immutable-кэш перестаёт «прилипать» между релизами (мина из ADR-010).
|
||||
cat > "$ACTIVE_INC" <<EOF
|
||||
# АВТОГЕНЕРАЦИЯ — пишет publish-release.sh при --activate. Не править руками.
|
||||
location = / {
|
||||
return 302 /${TAG}/;
|
||||
}
|
||||
location = /index.html {
|
||||
return 302 /${TAG}/;
|
||||
}
|
||||
EOF
|
||||
echo "было: $PREV"
|
||||
echo "стало: $(readlink -f /opt/game/current)"
|
||||
echo "корень -> /${TAG}/ (${ACTIVE_INC})"
|
||||
else
|
||||
echo "=== 3/4 активную сборку НЕ трогаю (нет --activate) ==="
|
||||
echo "активна: $(readlink -f /opt/game/current)"
|
||||
fi
|
||||
|
||||
echo "=== 4/4 проверка ==="
|
||||
nginx -t
|
||||
systemctl reload nginx
|
||||
for u in "/" "/${TAG}/" "/${TAG}/index.html" "/${TAG}/Build/WebGL.framework.js"; do
|
||||
printf ' %-42s %s\n' "$u" "$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1${u}")"
|
||||
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# Ждём завершения последнего прогона CI fun-game-front2 и печатаем итог + УРЛ образа.
|
||||
AUTH="-u subochev:Drovosek319"
|
||||
BASE="https://git.binom.pw/api/v1"
|
||||
REPO="subochev/fun-game-front2"
|
||||
|
||||
for i in $(seq 1 120); do
|
||||
OUT=$(curl -s $AUTH "$BASE/repos/$REPO/actions/runs?limit=1")
|
||||
ST=$(echo "$OUT" | python3 -c "import sys,json;r=json.load(sys.stdin)['workflow_runs'][0];print(r['id'],r['run_number'],r['status'],r.get('conclusion'))")
|
||||
set -- $ST
|
||||
RUN_ID=$1; NUM=$2; STATUS=$3; CONC=$4
|
||||
echo "$(date +%H:%M:%S) run #$NUM статус=$STATUS вывод=$CONC"
|
||||
if [ "$STATUS" = "completed" ]; then
|
||||
echo "=== ИТОГ run #$NUM: $CONC ==="
|
||||
if [ "$CONC" != "success" ]; then
|
||||
echo "--- хвост лога провалившихся шагов ---"
|
||||
curl -s $AUTH "$BASE/repos/$REPO/actions/runs/$RUN_ID/jobs" | python3 -c "
|
||||
import sys,json
|
||||
for j in json.load(sys.stdin).get('jobs',[]):
|
||||
print('job:', j['name'], '|', j.get('conclusion'))
|
||||
for s in j.get('steps',[]):
|
||||
if s.get('conclusion') not in ('success','skipped',None):
|
||||
print(' FAILED step:', s['name'], s.get('conclusion'))
|
||||
"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
sleep 30
|
||||
done
|
||||
echo "=== ТАЙМАУТ ожидания (60 мин) ==="
|
||||
exit 1
|
||||
Reference in New Issue
Block a user