deployment
Разворачивание и обновление прибора — docker-образ, compose для прибора, install.sh, клонирование данных, сборка образа на GitHub. Использовать при правке Dockerfile, compose-файлов, deploy/, workflow
SKILL.md.claude/skills/deployment/SKILL.md
GitHubРазворачивание
Прибор получает один docker-образ с бэкендом и собранным фронтом; на самом
приборе живут только docker, chromium с cage и каталог /opt/s2l из трёх вещей:
compose.yml, .env, data/. Инструкция для того, кто ставит, — docs/deploy.md;
здесь — почему устроено так.
Образ
- Сборка не на приборе. J6412 ставил бы зависимости минут десять на каждое
обновление. Образ собирает GitHub Actions (
.github/workflows/docker.yml) и кладёт вghcr.io/webdev1881/scale-s2l:latestсmain,v*с тегов, хеш коммита — всегда, чтобы откатиться на точный образ, когдаlatestподвёл. - Две стадии. Фронт собирается на node и в готовый образ попадает только
dist; бэкенд отдаёт его сам, nginx не нужен. Раскладка путей внутри повторяет репозиторий (/opt/s2l/backend,/opt/s2l/frontend/dist,/opt/s2l/docs/prod.xlsx): бэкенд ищет фронт и выгрузку относительно себя, и менять это ради образа незачем. fonts-dejavu-coreобязателен: растр этикетки берёт шрифт с кириллицей из/usr/share/fonts/truetype/dejavu, без него Pillow упадёт на встроенный шрифт без кириллицы (см. скиллlabel-printing).- Не от root. Внутри работает пользователь
s2l; доступ к весовой плате и принтеру выдаётся группамиdialout(20) иlp(7) черезgroup_add— те же номера, что в Ubuntu на приборе, поэтому проброшенные устройства читаются безprivileged. .dockerignoreрежет всё лишнее — данные прибора,.env, окружения, сборки. Данные в образ попасть не должны: у каждого прибора они свои.
Два compose-файла
compose.ymlв корне — локальная сборка и запуск на симуляторе (fake), порт 8000, безымянный том. Для проверки образа на машине разработчика.deploy/docker/compose.yml— прибор: образ из реестра,network_mode: host(киоск ходит на127.0.0.1:8000, как и systemd-вариант),./dataтомом, устройства из.env. Устройства должны существовать до запуска — иначе compose откажется стартовать; на симуляторе этот файл не используют.
Данные прибора
data/— единственное, что отличает один прибор от другого:settings.json,s2l.db,labels/. Обновление образа их не трогает.- Клон — основной путь на новые весы: скопировать
data/с готового прибора и отдать егоinstall.shаргументом. Тот же каталог, те же цены, те же настройки. - Демо-каталог на железе не заводится:
seed_if_emptyработает только приS2L_HAL_BACKEND=fake. Демо-товары ссылаются на снимки, которых в сборке давно нет, и на приборе они выглядели бы битыми карточками. Пустой прибор заполняют клоном илиtools/import_products.pyизнутри контейнера — выгрузка лежит в образе.
install.sh
deploy/docker/install.sh — идемпотентный: ставит пакеты, заводит s2l (UID 1000,
согласован с XDG_RUNTIME_DIR в юните киоска), кладёт файлы в /opt/s2l, ставит
udev-правила и юнит киоска, тянет образ и поднимает контейнер. Повторный запуск
только обновляет образ. Юнит s2l-kiosk.service ждёт docker.service, а не
отдельный юнит бэкенда: контейнер поднимает сам docker (restart: unless-stopped),
а пока он не готов, chromium показывает экран «Оновлення» и переподключается.
Старый deploy/systemd/s2l-backend.service (venv без docker) оставлен как запасной
путь для прибора, где docker поставить нельзя.
Ловушки
- Открытая вкладка киоска после обновления образа держит прежнюю сборку фронта до
перезагрузки (
systemctl restart s2l-kiosk). Автоперезагрузка по смене сборки обсуждалась и отложена. - Сеть хоста означает, что админка доступна с любого адреса прибора без пароля — как и до docker. Закрывать её — задача сети магазина, не образа.
docker compose execработает от пользователяs2lвнутри контейнера, поэтому скрипты изtools/пишут вdata/без правки прав.
Инструкции
docs/new-device.md — пошаговая, для того, кто ставит прибор в магазине: имя
прибора, Ubuntu, install.sh, .env, железо, туннель Cloudflare, элемент в 1С,
настройка, чистка, контрольный список, таблица «симптом → куда смотреть».
docs/deploy.md — справочник: клон данных, .env, обновление, проверка, без
реестра, что где лежит. Меняя процесс постановки, править обе.
Доступ 1С к прибору
Сервер 1С доступен только по RDP, а приборы стоят за NAT провайдера без белого IP
(проверено на машине разработчика: WAN роутера — приватный адрес, проброс снаружи
не отвечает). Поэтому прибор сам держит исходящий туннель: сервис tunnel
(cloudflared) в deploy/docker/compose.yml, профиль tunnel, токен в
CLOUDFLARE_TUNNEL_TOKEN. В 1С — Сервер = <имя прибора в домене>, SSL = Истина,
ресурс /api/catalog/1c-import, заголовок X-API-Key. Не Tailscale, потому что на
сервер 1С ставить клиента нельзя; Cloudflare Tunnel требует только имя и HTTPS.
Через туннель наружу смотрит и админка, поэтому в .env прибора обязателен
S2L_ADMIN_PASSWORD (скилл admin-ui). Подробности — integrations/1c/README.md и
docs/deploy.md.