nginx на Proxmox — раздача статики игры (LXC 152)
Боевой веб-слой игры «Бинарная ракета»: корень smart-updown.binom.pw раздаётся nginx'ом
на LXC 152 с версионированием по каталогам. Бэк (API/WS) остаётся в кубе — см. ADR-011.
Что где живёт
| Что | Где |
|---|---|
| LXC | 152 game-web, 192.168.76.152 (Server3, pct exec 152 через root@192.168.76.66) |
| nginx-конфиг | /etc/nginx/sites-available/game (симлинк в sites-enabled/) |
| Сборки | /opt/game/releases/<N>/ — Build/, TemplateData/, index.html, ServiceWorker.js |
| Активная сборка | /opt/game/current → симлинк на releases/<N> |
| Инструменты | /opt/game/tools/ (unpack-release.sh, publish-release.sh) |
| Логи | /var/log/nginx/game-access.log, game-error.log |
Адреса
https://smart-updown.binom.pw/— активная сборка;https://smart-updown.binom.pw/<N>/— конкретная сборка (обязательно со слэшем);https://smart-updown.binom.pw/<N>/Build/WebGL.data— ассет конкретной сборки;/api,/ws— не nginx: их забирает внешний Traefik (VDS) → куб (ingresssmart-updown-api).
Публикация сборки
# CI fun-game-front2 пушит образ images.binom.pw/fun/game-front2:<N>
# затем на хосте Proxmox:
ssh root@192.168.76.66 "pct exec 152 -- /opt/game/tools/publish-release.sh <N> --activate"
Без --activate сборка ляжет в /opt/game/releases/<N>/ и будет доступна по /<N>/,
но корень останется прежним. Откат = publish-release.sh <старый N> --activate.
Проверка
curl -sI https://smart-updown.binom.pw/ | grep -iE 'server|last-modified' # nginx/1.24.0 (Ubuntu)
curl -s -o /dev/null -w '%{http_code}\n' https://smart-updown.binom.pw/<N>/
curl -s https://smart-updown.binom.pw/api/state | head -c 80 # JSON от шлюза в кубе
# сквозная живая приёмка (ставит реальную ставку):
cd ~/WORK/games/pump-game-ops && BASE=https://smart-updown.binom.pw/api \
WS_URL=wss://smart-updown.binom.pw/api/ws node tools/autoclose-check.cjs # ждём 9/9
Питфоллы
- Слэш в конце обязателен для версии:
/28→ 301 (редирект на/28/). Unity грузит ассеты относительным путём (Build/WebGL.loader.js), поэтому без слэшаBuild/...уедет в корень → 404. skopeo ... dir:пишет OCI-манифест, гдеlayers— словари.unpack-release.shберётdigestкаждого слоя и распаковывает по имени файла<hex>(неsha256:...).- Корень — 302 на активную сборку (
location = / { return 302 /<N>/; }в/etc/nginx/game-active.inc, пишетpublish-release.sh --activate). Нужен потому, чтоindex.htmlUnity зовёт ассеты относительным путём (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 — домен снова пойдёт целиком в куб.
⚠️ Приёмка визуально: ошибки шейдеров в headless-браузере — НЕ наш дефект
Проверка игры живым браузером (Browserbase/headless, без GPU) показывает:
прогресс замирает на 90%, в консоли ERROR: Shader Hidden/CoreSRP/CoreCopy shader is not supported on this GPU и Unity-исключение из wasm. Это артефакт headless-окружения без
аппаратного GPU.
Доказательство (контрольный опыт 15.09.2026): тот же образ game-front2:28 открыт через старый
путь (kubectl port-forward на под в кубе, http://127.0.0.1:18080/) — ровно те же ошибки
шейдеров, ровно те же 90%, те же 11 ресурсов. Значит миграция на nginx тут ни при чём.
Что реально проверяемо в headless: заголовки, коды, содержимое, инициализация движка
(Creating WebGL 2.0 context, Initialize engine version: 6000.3.16f1), факт загрузки всех
ассетов (/Build/WebGL.wasm 52 234 795 б, .data 18 437 188 б, .framework.js 489 223 б — по 200).
Отрисовку сцены проверять только на реальном железе с GPU (принятие на железе — за владельцем).
⚠️ publish-release.sh содержит креды реестра (subochev:…) в открытом виде — файл живёт только
внутри LXC 152 как root (/opt/game/tools/), в git лежит шаблон из репы ops. Не тиражировать наружу.
Именованные сборки (zip от фронтендера) — рядом с номерными
Кроме номерных релизов из CI (/opt/game/releases/<N>/) на nginx можно положить именованную
сборку — готовый zip от фронтендера, вне релизного цикла:
# zip сначала доставить на LXC 152 (через хост проксмокса):
scp build.zip root@192.168.76.66:/tmp/build.zip
ssh root@192.168.76.66 "pct push 152 /tmp/build.zip /tmp/build.zip"
# разложить под именем:
ssh root@192.168.76.66 "pct exec 152 -- /opt/game/tools/publish-zip.sh sol /tmp/build.zip"
Адрес: https://smart-updown.binom.pw/sol/. Активную сборку такая публикация не трогает —
корень по-прежнему редиректит на текущий номер.
Питфоллы именованных сборок:
- Служебные location обязаны иметь
^~(^~ /api,^~ /ws,^~ /solana-rpc/). Regex-локейшн сборок^/(?<build>[a-zA-Z0-9][a-zA-Z0-9_-]*)…иначе перехватит/api/...как «сборку с именем api» — regex в nginx приоритетнее обычного префикса. Без^~API ломается (проверено). immutable— только для номерных каталогов (~^/[0-9]+/…). Именованную папку могут перезалить тем же именем → её ассеты кэшировать нельзя.- Сборки фронтендера бывают на
.unityweb-ассетах (Build.wasm.unitywebи т.п.) — расширениеunitywebв маске кэша учтено. - Имя папки не должно совпадать со служебным путём (
api,ws,solana-rpc) — скрипт это проверяет.