Files
..

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) → куб (ingress smart-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.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 — домен снова пойдёт целиком в куб.

⚠️ Приёмка визуально: ошибки шейдеров в 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) — скрипт это проверяет.