68 lines
11 KiB
Markdown
68 lines
11 KiB
Markdown
---
|
||
title: books-vds Gitea Actions выпилены — сборка только локально (решение vitya 2026-08-23)
|
||
status: live
|
||
tags: [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/tasks` → `config.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`).
|