revert(boot): откат отдельного dicoop/bootstrap образа

Boot — это и есть бутстрап. Дубликат через отдельный образ создаёт
второй workflow и две точки сборки там, где для testnet/prod достаточно
запустить existing pnpm -F @coopenomics/boot run boot:remote внутри
dicoop/mono-base:<tag>, который уже собирается через build-containers.yaml.

Что остаётся в dev (полезное из предыдущих коммитов):
- boot:remote команда — без dockerode, для сценария когда нода
  поднята отдельным сервисом docker-compose.
- CONTRACTS_DIR env-driven path в configs/contracts.ts — позволяет
  монтировать dicoop/contracts:<tag> как volume.
- "build" script в boot/package.json для unbuild.

Удалено:
- components/boot/docker/ (Dockerfile + entrypoint + bootstrap.package.json
  + bootstrap.pnpm-workspace.yaml)
- .github/workflows/build-bootstrap.yaml

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
coopops
2026-04-30 13:07:35 +00:00
parent 01c6a2689a
commit 30af7cd90e
5 changed files with 0 additions and 362 deletions
-141
View File
@@ -1,141 +0,0 @@
name: Build bootstrap container
# Сборка и публикация образа `dicoop/bootstrap` (DockerHub) —
# one-shot оркестратора первичного деплоя контрактов на KE-узел
# (см. `components/boot/docker/Dockerfile`). Образ потребляется
# в docker-compose рядом с `dicoop/blockchain_v5.1.1`-нодой как
# `depends_on: ke-node: service_healthy`.
#
# Триггер — push в одну из веток-окружений (dev / testnet / main).
# Маппинг по convention `feedback_branch_convention`:
#
# dev → tag=dev, CONTRACTS_TAG=dev
# testnet → tag=testnet, CONTRACTS_TAG=testnet
# main → tag=main + latest, CONTRACTS_TAG=main
#
# Зависимость: образ `dicoop/contracts:<tag>` должен существовать
# (он публикуется через `build-contracts.yaml` отдельным workflow'ом).
# Если contracts ещё не собраны для целевой ветки — `docker build`
# упадёт на multi-stage `FROM dicoop/contracts:${CONTRACTS_TAG}`.
# Пересборка триггерится через `workflow_dispatch` после ручного
# обновления contracts.
#
# Path-фильтр на `components/boot/**`, `components/cooptypes/**`,
# `components/factory/**` (boot их импортирует через workspace) и
# на сам Dockerfile/workflow. Изменения контрактов сами по себе
# bootstrap не триггерят — они идут через `dicoop/contracts:<tag>`,
# и для подхвата обновлённого образа нужно либо пушнуть ветку
# заново, либо запустить workflow вручную.
#
# Секреты:
# DOCKERHUB_USERNAME / DOCKERHUB_TOKEN — публикация в dicoop/*
# TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID — уведомления
on:
push:
branches: [dev, testnet, main]
paths:
- 'components/boot/**'
- 'components/cooptypes/**'
- 'components/factory/**'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- '.github/workflows/build-bootstrap.yaml'
workflow_dispatch:
inputs:
contracts_tag:
description: 'Тег dicoop/contracts (override; default = тег ветки)'
required: false
default: ''
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Determine docker tag and contracts tag
run: |
case "${{ github.ref_name }}" in
main)
echo "DOCKER_TAG=main" >> $GITHUB_ENV
echo "EXTRA_TAG=latest" >> $GITHUB_ENV
echo "CONTRACTS_TAG=main" >> $GITHUB_ENV
;;
testnet)
echo "DOCKER_TAG=testnet" >> $GITHUB_ENV
echo "EXTRA_TAG=" >> $GITHUB_ENV
echo "CONTRACTS_TAG=testnet" >> $GITHUB_ENV
;;
dev)
echo "DOCKER_TAG=dev" >> $GITHUB_ENV
echo "EXTRA_TAG=" >> $GITHUB_ENV
echo "CONTRACTS_TAG=dev" >> $GITHUB_ENV
;;
*)
echo "Unsupported branch ${{ github.ref_name }}" >&2
exit 1
;;
esac
# workflow_dispatch override
if [ -n "${{ github.event.inputs.contracts_tag }}" ]; then
echo "CONTRACTS_TAG=${{ github.event.inputs.contracts_tag }}" >> $GITHUB_ENV
fi
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Pull base contracts image (sanity check)
run: |
docker pull "dicoop/contracts:${CONTRACTS_TAG}"
docker run --rm "dicoop/contracts:${CONTRACTS_TAG}" list | head -25
- name: Build and push image
run: |
IMAGE="dicoop/bootstrap"
docker build \
--build-arg "CONTRACTS_TAG=${CONTRACTS_TAG}" \
--label "org.opencontainers.image.revision=${{ github.sha }}" \
--label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--label "build.contracts_tag=${CONTRACTS_TAG}" \
-t "${IMAGE}:${DOCKER_TAG}" \
-f components/boot/docker/Dockerfile \
.
docker push "${IMAGE}:${DOCKER_TAG}"
if [ -n "${EXTRA_TAG}" ]; then
docker tag "${IMAGE}:${DOCKER_TAG}" "${IMAGE}:${EXTRA_TAG}"
docker push "${IMAGE}:${EXTRA_TAG}"
fi
# SHA-pinned tag для воспроизводимости (тот же подход, что и
# в build-contracts.yaml — позволяет запинить bootstrap к
# конкретному коммиту, а не двигающемуся branch-тегу).
SHORT_SHA="${GITHUB_SHA::7}"
docker tag "${IMAGE}:${DOCKER_TAG}" "${IMAGE}:${DOCKER_TAG}-${SHORT_SHA}"
docker push "${IMAGE}:${DOCKER_TAG}-${SHORT_SHA}"
- name: Verify pushed image
run: |
IMAGE="dicoop/bootstrap"
docker run --rm "${IMAGE}:${DOCKER_TAG}" verify
- name: Telegram notify success
if: ${{ success() }}
run: |
curl -s -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage \
-d chat_id=${{ secrets.TELEGRAM_CHAT_ID }} \
-d text="✅ [GITHUB MONO] dicoop/bootstrap:${DOCKER_TAG} (contracts=${CONTRACTS_TAG}, sha=${GITHUB_SHA::7})"
- name: Telegram notify failure
if: ${{ failure() }}
run: |
curl -s -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage \
-d chat_id=${{ secrets.TELEGRAM_CHAT_ID }} \
-d text="❌ [GITHUB MONO] Ошибка сборки dicoop/bootstrap на ${{ github.ref_name }} (sha=${GITHUB_SHA::7})"
-134
View File
@@ -1,134 +0,0 @@
# ke-bootstrap: образ-оркестратор первичного деплоя контрактов на KE-ноду.
#
# Назначение:
# Один раз при поднятии узла (testnet/local-dev) этот контейнер ждёт
# готовности RPC `${CHAIN_URL}/v1/chain/get_info`, затем запускает
# `boot:remote` — деплоит все контракты из примонтированного образа
# `dicoop/contracts` через RPC (`eosjs::setContract`), активирует
# protocol features, создаёт системный токен и т.д. После завершения
# контейнер выходит с кодом 0.
#
# Не управляет docker-контейнером ноды (в отличие от `boot`/`boot:clean`/
# `boot:extra` — тех режимов, что вызывают `dockerode` для запуска ноды
# рядом с собой). Нода поднимается отдельным сервисом docker-compose
# (`dicoop/blockchain_v5.1.1`), и зависимость декларируется через
# `depends_on: ke-node: service_healthy`.
#
# Параметры (env):
# CHAIN_URL — URL RPC ноды (default `http://localhost:8888`).
# EOSIO_PUB_KEY — public key для системных аккаунтов (обязателен).
# EOSIO_PRV_KEY — private key для подписи (обязателен).
# PASSWORD — пароль для cleos-wallet (используется командой `unlock`).
# CONTRACTS_DIR — корень wasm/abi (default `/contracts`).
# RPC_WAIT_TIMEOUT_MS — таймаут ожидания RPC (default 120000).
# INSTALL_INITIAL_DATA=1 — опционально: записать кооператив `voskhod` в
# MongoDB+PostgreSQL (требует `MONGO_URI`,
# `PG_HOST`/`PG_USER`/...). Без этого флага
# bootstrap делает только on-chain работу.
# INSTALL_EXTRA_DATA=1 — то же + расширенный совет (4 доп. пайщика).
#
# Layout слоёв:
# Stage 1 (deps) — `pnpm install` всего workspace, нужен для
# workspace-зависимостей `@coopenomics/factory`,
# `cooptypes` (boot их импортирует).
# Stage 2 (build) — `pnpm run build` для cooptypes/factory/boot
# (unbuild → dist/index.cjs + .mjs).
# Stage 3 (deploy) — `pnpm deploy --filter @coopenomics/boot --prod`
# складывает приложение + только prod-deps в /app.
# workspace:* → реальные пакеты, без cross-link.
# Stage 4 (contracts) — alias для `dicoop/contracts:<tag>`, отсюда
# берутся wasm/abi через `COPY --from`.
# Stage 5 (runtime) — slim node:20-alpine, `/app` + `/contracts`.
# Глобальный ARG до первого FROM — иначе он не виден в FROM следующих
# стадий (BuildKit делает ARG, объявленный после FROM, локальным для
# предыдущей стадии).
ARG CONTRACTS_TAG=dev
# ── Stage 1: install dependencies ────────────────────────────────────
FROM node:20-alpine AS deps
RUN corepack enable && corepack prepare pnpm@9.0.6 --activate \
&& apk add --no-cache git python3 make g++
WORKDIR /repo
# Минимальные root-файлы для мини-workspace (boot + его workspace-deps).
# Полный root /package.json + /pnpm-workspace.yaml сюда не годятся —
# они ссылаются на 14+ workspace-пакетов (cleos, controller, desktop,
# ...), которые в bootstrap-образе не нужны и в build-context не
# копируются. Использовать их → ERR_PNPM_WORKSPACE_PKG_NOT_FOUND.
COPY components/boot/docker/bootstrap.package.json ./package.json
COPY components/boot/docker/bootstrap.pnpm-workspace.yaml ./pnpm-workspace.yaml
# Рабочие пакеты, нужные для boot:
# - cooptypes / factory — workspace:* зависимости
# - boot — собственно сборка
COPY components/cooptypes/ ./components/cooptypes/
COPY components/factory/ ./components/factory/
COPY components/boot/ ./components/boot/
# `--no-frozen-lockfile` — мини-workspace не совпадает с корневым
# /pnpm-lock.yaml, поэтому pnpm генерирует свой lock внутри образа.
# В build-stage лок не сохраняется, поэтому drift здесь безопасен;
# воспроизводимость дают branch+sha tags на финальном образе.
RUN pnpm install --no-frozen-lockfile
# ── Stage 2: build TS пакетов ────────────────────────────────────────
FROM deps AS build
# Порядок сборки важен: cooptypes → factory → boot (downstream зависят
# от dist/ upstream-пакетов). Lerna-граф решает это автоматически через
# --include-dependencies, но явное перечисление дешевле и предсказуемее.
RUN pnpm --filter cooptypes run build \
&& pnpm --filter @coopenomics/factory run build \
&& pnpm --filter @coopenomics/boot run build
# ── Stage 3: pnpm deploy → standalone /app ───────────────────────────
FROM build AS deploy
# `pnpm deploy --prod` создаёт автономную копию пакета с разрешёнными
# `workspace:*` зависимостями (тянет реальные пакеты из workspace, не
# симлинки). devDeps отрезаются, так что esno/typescript/vitest не
# попадают в финальный образ.
RUN pnpm deploy --filter @coopenomics/boot --prod /app
# ── Stage 4: contracts (wasm/abi артефакты) ──────────────────────────
# Тег этого образа должен совпадать с тегом ke-bootstrap (см.
# `.github/workflows/build-bootstrap.yaml`). Multi-stage `COPY` не
# зависит от того, что dicoop/contracts что-то делает в ENTRYPOINT —
# нам нужен только filesystem. ARG объявлен в самом начале файла
# (до первого FROM) — здесь только используется.
FROM dicoop/contracts:${CONTRACTS_TAG} AS contracts
# ── Stage 5: runtime ─────────────────────────────────────────────────
FROM node:20-alpine AS runtime
# bash + curl — entrypoint использует bash (set -e, конструкции `[[ ]]`)
# и опционально curl для дебага healthcheck'а.
RUN apk add --no-cache bash curl
# Приложение
COPY --from=deploy /app /app
# Артефакты контрактов (flat layout: /contracts/<name>/<name>.{wasm,abi})
COPY --from=contracts /contracts /contracts
# Entrypoint скрипт лежит в исходниках boot, копируем отдельно
COPY components/boot/docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
WORKDIR /app
# Дефолтные env. CHAIN_URL переопределяется в docker-compose, остальное
# — sane defaults для bootstrap-сценария.
ENV CONTRACTS_DIR=/contracts \
CHAIN_URL=http://ke-node:8888 \
RPC_WAIT_TIMEOUT_MS=120000
LABEL org.opencontainers.image.title="coopenomics-bootstrap"
LABEL org.opencontainers.image.description="One-shot bootstrapper для KE-ноды: ждёт RPC, деплоит контракты и инициализирует протокол"
LABEL org.opencontainers.image.source="https://github.com/coopenomics/mono"
ENTRYPOINT ["/entrypoint.sh"]
CMD ["boot:remote"]
@@ -1,6 +0,0 @@
{
"name": "monocoop-bootstrap-build",
"private": true,
"packageManager": "pnpm@9.0.6",
"_comment": "Минимальный root package.json для multi-stage сборки ke-bootstrap образа. Не используется в обычном workspace — там действует /package.json. Здесь только то, что нужно pnpm для построения графа из мини-набора пакетов: boot + его workspace-зависимости (cooptypes, factory). Все другие коопенономиксовые пакеты (cleos, controller, desktop, ...) сюда не попадают, чтобы не раздувать образ."
}
@@ -1,10 +0,0 @@
# Минимальный pnpm-workspace для сборки ke-bootstrap образа.
# Состав совпадает с тем, что копируется в `components/boot/docker/Dockerfile`
# (boot + его workspace-зависимости). Полный список пакетов
# `components/*` сюда не попадает, чтобы pnpm install не пытался
# резолвить отсутствующие компоненты (cleos, controller, desktop, ...).
link-workspace-packages: true
packages:
- 'components/boot'
- 'components/cooptypes'
- 'components/factory'
-71
View File
@@ -1,71 +0,0 @@
#!/usr/bin/env bash
#
# Entrypoint для образа ke-bootstrap (`dicoop/bootstrap`).
#
# Один и тот же бинарь поддерживает несколько режимов через первый
# аргумент CMD (по умолчанию `boot:remote`). Все режимы — short-lived,
# контейнер выходит после завершения. Перезапускать его при сбое RPC не
# нужно: `boot:remote` сам ждёт готовности ноды по `CHAIN_URL`.
#
# Доступные режимы:
# boot:remote — деплой контрактов на уже работающую ноду (default)
# verify — sanity-check: вывести версию + список контрактов
# shell — открыть bash для отладки (только если контейнер
# запущен интерактивно)
#
# В CI (`build-bootstrap.yaml`) `verify` запускается после push'а как
# smoke-test, чтобы убедиться что образ хотя бы стартует.
set -euo pipefail
CMD="${1:-boot:remote}"
# Проверяем минимальный набор обязательных env'ов перед стартом — так
# падение происходит сразу с понятной ошибкой, а не где-то внутри boot
# при попытке подписать транзакцию пустым ключом.
require_env() {
local var
for var in "$@"; do
if [ -z "${!var:-}" ]; then
echo "ERROR: env $var is required for mode '$CMD'" >&2
exit 1
fi
done
}
case "$CMD" in
boot:remote)
require_env CHAIN_URL EOSIO_PUB_KEY EOSIO_PRV_KEY
echo "[ke-bootstrap] CHAIN_URL=$CHAIN_URL"
echo "[ke-bootstrap] CONTRACTS_DIR=${CONTRACTS_DIR:-/contracts}"
echo "[ke-bootstrap] starting boot:remote..."
# node dist/index.cjs — pnpm deploy укладывает unbuild-output сюда
exec node /app/dist/index.cjs boot:remote
;;
verify)
# Smoke-test для CI после push'а образа.
echo "[ke-bootstrap] node version:"; node --version
echo "[ke-bootstrap] dist/index.cjs:"; ls -la /app/dist/index.cjs || exit 1
echo "[ke-bootstrap] CONTRACTS_DIR contents:"; ls /contracts | head -30
echo "[ke-bootstrap] manifest:"
if [ -f /contracts/manifest.json ]; then
cat /contracts/manifest.json
else
echo "(missing)"
fi
echo "[ke-bootstrap] cli help (sanity-check, RPC недоступен — это ожидаемо):"
# `--help` не делает RPC-вызовов, только парсит commander
node /app/dist/index.cjs --help || true
;;
shell)
exec /bin/bash
;;
*)
# Любой другой аргумент пробрасываем дальше как arg для node (ESC-hatch
# для ad-hoc-сценариев типа `docker run ... cleos get info`).
exec node /app/dist/index.cjs "$@"
;;
esac