Files
admin/.wiki/concepts/books-vds-no-gitea-actions.md

11 KiB
Raw Blame History

title, status, tags
title status tags
books-vds Gitea Actions выпилены — сборка только локально (решение vitya 2026-08-23) live
books-vds
ci
gitea-actions
build
decision

Gitea Actions на books-vds — выпилены полностью (директива vitya 2026-08-23)

Решение юзера: никакого Gitea Actions / CI-раннера. Сборка образов — только локально на рабочей машине, деплой — прямыми PUT /api/stacks/<id> в Portainer.

Что выпилено

  • Workflow-файлы удалены и запушены: books/.gitea/workflows/{build,deploy,build-tools,build-sched-daemon}.yml, vds-ops-mcp/.gitea/workflows/build.yml (там был РЕАЛЬНЫЙ автобилд on: push — «автосборка», которую юзер увидел).
  • books-vds (89.253.255.133): раннер вычищен под ноль:
    • systemctl stop/disable gitea-runner.service + rm /etc/systemd/system/gitea-runner.service
    • rm -rf /opt/gitea-runner (бинарник, config.yaml, .runner)
    • docker rm -f GITEA-ACTIONS-TASK-* (job-контейнеры)
    • userdel -r gitea-runner
  • vds-kzntsv: следов не было (проверено).

Процедура локальной сборки + публикации (проверена 2026-08-23)

  1. git archive <sha> | tar -x -C /c/tmp/build (чистый контекст, без node_modules/.git)
  2. VERDACCIO_TOKEN=<jwt> docker build --secret id=verdaccio_token,env=VERDACCIO_TOKEN -f packages/<svc>/Dockerfile -t <svc>:<sha> .
  3. docker save → tar. Docker 29 на этой машине сразу отдаёт OCI-layout (oci-layout + index.json + blobs/sha256/*) — конвертация не нужна, --format oci отсутствует и не требуется. index.json → OCI index (multi-arch: amd64 child + attestation platform: unknown/unknown). Дочерний amd64-манифест лежит в blobs/sha256/<index-digest> (прочитать index-блоб, взять manifests[] с platform.architecture == 'amd64').
  4. Заливка блобов curl'ом: HEAD-check → если нет — POST /v2/<repo>/blobs/uploads/PUT <Location>&digest=sha256:<d> с телом (монолитный, Content-Length). Большой слой: cat blob | curl -X PATCH -T - (chunked) + финальный PUT &digest (Location брать из PATCH-ответа). Перед большим чтением файла — sync; echo 3 > /proc/sys/vm/drop_caches (books-vds 3.9GB RAM → OOM на 2GB файле).
  5. Манифест: если index.json → OCI index (multi-arch, у Docker Desktop так) — класть дочерний amd64-манифест (application/vnd.oci.image.manifest.v1+json), не индекс (реестр: MANIFEST_INVALID).
  6. Креды реестра: docker login registry.kzntsv.site -u books-ci --password-stdin (актуальный пароль books-ci — в /opt/books/sched/docker-config.json на books-vds; в /root/.docker/config.json был УСТАРЕВШИЙ → 401 на push).

Gotcha: большие пуши через traefik умирают

docker buildx --push 1.5-2GB слоёв через registry.kzntsv.site (traefik buffering middleware maxRequestBodyBytes=4GB) → Client Closed Request: traefik буферизует тело в RAM vds-kzntsv (1GB free) + чтение с buildkit-кэша медленное + OOM. curl монолитными PUT/PATCH работает (54MB/s). Связано: registry-traefik-buffering-gotcha.

Деплой: Portainer PUT gotchas (2026-08-23, деплой master-a448cad)

PUT /api/stacks/<id>?endpointId=1 — синхронный и тянет образ ВНУТРИ запроса (676МБ с registry через интернет = минуты). Клиентский таймаут (90с urllib) рвёт ожидание, но операция НЕ отменяется — Portainer доделывает server-side (файл стека обновляется, pull идёт, контейнер пересоздаётся). Правильный флоу: PUT с timeout 900+, при обрыве — НЕ пере-PUT (задвоит деплой), а проверить контейнеры:

GET /api/endpoints/1/docker/containers/json?all=1   # фильтр по Names: books-web / bookva-web → Image + Status
GET /api/endpoints/1/docker/images/json              # образ уже на VDS? (показывает in-flight pull)

Смоук/верификация: DELETE /api/fbs/pickingLists/:id{taskId}, статус GET /api/fbs/tasks/<uuid>, run в sched daemon: docker exec books-sched node -e 'fetch("http://localhost:3031/api/runs?task=<name>&limit=3",{headers:{"Authorization":"Bearer <SCHED_ADMIN_KEY>"}})...' (curl в контейнере нет, node fetch есть; ключ — из env стека). Nuxt-прокси /admin/scheduler/** для внешних проверок бесполезен: гейтится requireAdmin (JWT isAdmin), ?token= API-ключ → 403.

Sched docker-runner: pull auth + mediaType (2026-08-23, разбор createPickingListPdf)

docker CLI читает креды ТОЛЬКО из ${DOCKER_CONFIG}/config.json. Файл docker-config.json (как велели старые доки) → игнорируется → pull без auth → 401 «no basic auth credentials». Фикс: mv /opt/books/sched/docker-config.json /opt/books/sched/config.json (bookva volume уже был правильный). Рестарт daemon не нужен — файл читается при каждом pull.

Вторая засада — mediaType образа. Теги, запушенные buildx (и моим curl-флоу из OCI-save) = OCI image-index. registry 2.8.3 отдаёт их ТОЛЬКО на Accept с application/vnd.oci.image.index.v1+json; docker daemon (26.1.4) получает «manifest unknown», если child-манифест отсутствует (а он отсутствует после прежних host-GC — см. registry-oci-image-index-gc). Диагностика: curl манифеста тега с docker-only Accept → 404, с OCI-index → 200; child по digest → 404. Фикс: перепуш образа как docker schema2-манифест (mediaType application/vnd.docker.distribution.manifest.v2+json, config application/vnd.docker.container.image.v1+json, layers application/vnd.docker.image.rootfs.diff.tar.gzip) — блобы те же (content-addressed), только обёртка; pull снова работает. Сборка: docker save → OCI → взять amd64 child → трансформ mediaTypes → PUT в тег с docker Content-Type. Важно: docker config-манифест пишется container.image.v1+json, НЕ distribution.image.config.v2+json (последний docker не знает → «(unknown)» при pull).

Третий слой — конфиг daemon. createPickingListPdf рендерит print-роут: env берётся из tasks.json на VDS; плейсхолдер API_TOKEN: "<SET_IN_PROD_VOLUME_MOUNT>" = production-конфиг daemon'а не задеплоен в монтированную диру (в репо tasks.json — ${config:api.authToken}). Конфиг-дир /opt/books/sched/ должен содержать production-overrides (api.authToken, puppeteer.browserWSEndpoint, s3).

2026-08-24 — deploy sched-daemon 0.11.1 + createPickingListPdf ЗАРАБОТАЛ (admin)

Деплой демона на 0.11.1 (docker-runner config.network) + фикс tasks.json обоих тенантов → смоук оба тенанта (slovo 429, bookva 219: PDF в S3 + pdf_remote_path в БД).

Процедура bump books-sched-daemon (без CI, локальная сборка):

  1. deploy/sched/Dockerfile FROM → registry.kzntsv.site/sched-daemon:0.11.1;
  2. VERDACCIO_TOKEN=<jwt> docker build --secret id=verdaccio_token,env=VERDACCIO_TOKEN -f deploy/sched/Dockerfile -t registry.kzntsv.site/books-sched-daemon:master-0.11.1 /c/tmp/schedctx (пустой контекст — Dockerfile без COPY; НЕ . — иначе поедет node_modules);
  3. пуш schema2-флоу (docker save → OCI → amd64 child → трансформ mediaTypes → curl блобы + PUT манифеста) в теги master + master-0.11.1 (скрипт-образец /c/tmp/pushimg/push.py);
  4. Portainer PUT обоих sched-стеков (Id 50 books-sched, Id 51 bookva-sched на portainer.kzntsv.site) — DEPLOY_AT bump + pullImage:true;
  5. verify: docker exec books-sched node -e 'require("/app/node_modules/@sched/daemon/package.json").version' → 0.11.1; admin-api /api/tasksconfig.network: "proxy".

Задачи docker-тасков спавнятся на сети proxy (config.network: "proxy" в tasks.json; docker-runner 0.11.1 docker run --network proxy) → тул видит books-web:3000/chrome:3000/books-api:3021. tasks.json live-reload — директорный бинд /opt/books/sched/ (slovo) / volume bookva-sched-config (bookva), поллинг ~60с, рестарт не нужен.

Готча 1: chrome WS-эндпоинт БЕЗ --proxy-server. Эндпоинт ws://chrome:3000?token=…&stealth&--proxy-server=http://proxy-chain:8000 ЛОМАЕТ рендер внутренней страницы: proxy-chain гонит весь трафик (включая внутренний books-web:3000) во внешний пул → navigation timeout → browserless job timeout → SIGKILL → puppeteer «Protocol error (Page.navigate): Target closed». Внутренний рендер print-роута должен идти напрямую: ws://chrome:3000?token=0ffbbc13-… (без stealth, без proxy-флага). --proxy-server наследовался от мёртвого внешнего chrome.kzntsv.site.

Готча 2: bookva-api слушает 3000, НЕ 3021. (slovo books-api — 3021, bookva-api — 3000; проверка docker exec bookva-sched node -e 'fetch("http://bookva-api:3000")' → 401, на 3021 → ECONNREFUSED). В tasks.json bookva API_URL был http://bookva-api:3021 → PATCH /fbs/pickingLists/:id/pdf упал бы. Исправлено на :3000 (live volume + репо-шаблон).

Готча 3: pdf_remote_path не виден в GET /api/fbs/pickingLists/:id — api-сериализация листа его не отдаёт; проверять в БД напрямую: docker exec books-db mysql -ubooks -p… books -e "SELECT id_picking_list, pdf_remote_path FROM fbs_picking_lists WHERE id_picking_list=…" (таблица fbs_picking_lists, колонка pdf_remote_path).