feat(boot): ke-bootstrap docker image + boot:remote режим (story 6.1)
Образ ghcr.io/coopenomics/bootstrap для one-shot первичного деплоя
контрактов на KE-узел в docker-compose. Соответствует решению из
project_ke_bootstrap_strategy: boot/contracts собираются в mono,
apps-catalog только потребляет готовые образы.
Состав:
- components/boot/src/index.ts — команда `boot:remote`: ждёт RPC
по CHAIN_URL, вызывает startInfra() через eosjs (без dockerode).
Опциональные initial-data/extra-data через env-флаги.
- components/boot/src/configs/contracts.ts — env-driven CONTRACTS_DIR.
Когда задан — flat layout /contracts/<name>/<name>.{wasm,abi}
(как в dicoop/contracts), иначе старые относительные пути.
- components/boot/package.json — script `build` (unbuild) +
`boot:remote` для локального запуска.
- components/boot/docker/Dockerfile — multi-stage:
deps → build (cooptypes/factory/boot) → pnpm deploy --prod →
COPY --from=dicoop/contracts:<tag> → node:20-alpine slim runtime.
- components/boot/docker/entrypoint.sh — режимы boot:remote / verify
/ shell + проверка обязательных env'ов.
- .github/workflows/build-bootstrap.yaml — публикация в GHCR по push
в dev/testnet/main + verify smoke-test после push'а.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
name: Build bootstrap container
|
||||
|
||||
# Сборка и публикация образа `ghcr.io/coopenomics/bootstrap` —
|
||||
# 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 вручную.
|
||||
#
|
||||
# Секреты:
|
||||
# GITHUB_TOKEN — авто, для push в ghcr.io
|
||||
# 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: ''
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
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 GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_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="ghcr.io/coopenomics/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="ghcr.io/coopenomics/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] ghcr.io/coopenomics/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] Ошибка сборки ghcr.io/coopenomics/bootstrap на ${{ github.ref_name }} (sha=${GITHUB_SHA::7})"
|
||||
@@ -0,0 +1,121 @@
|
||||
# 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`.
|
||||
|
||||
# ── 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
|
||||
|
||||
# Workspace-конфиг + lockfile + корневой package.json — нужны pnpm для
|
||||
# построения графа workspace до того, как мы скопируем компоненты.
|
||||
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml lerna.json ./
|
||||
|
||||
# Рабочие пакеты, нужные для boot:
|
||||
# - cooptypes / factory — workspace:* зависимости
|
||||
# - boot — собственно сборка
|
||||
COPY components/cooptypes/ ./components/cooptypes/
|
||||
COPY components/factory/ ./components/factory/
|
||||
COPY components/boot/ ./components/boot/
|
||||
|
||||
RUN pnpm install --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 CONTRACTS_TAG=dev
|
||||
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"]
|
||||
Executable
+71
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Entrypoint для образа ke-bootstrap (`ghcr.io/coopenomics/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
|
||||
@@ -9,6 +9,7 @@
|
||||
"license": "",
|
||||
"keywords": [],
|
||||
"scripts": {
|
||||
"build": "unbuild",
|
||||
"enter": "./scripts/enter.sh",
|
||||
"cli": "esno src/index.ts",
|
||||
"deploy": "esno src/index.ts deploy",
|
||||
@@ -17,6 +18,7 @@
|
||||
"boot": "esno src/index.ts boot",
|
||||
"boot:clean": "esno src/index.ts boot:clean",
|
||||
"boot:extra": "esno src/index.ts boot:extra",
|
||||
"boot:remote": "esno src/index.ts boot:remote",
|
||||
"reboot:clean": "cd components/boot/scripts && bash clean_reboot.sh",
|
||||
"reboot:extra": "cd components/boot/scripts && bash extra_reboot.sh",
|
||||
"create-coop": "esno src/index.ts create-coop",
|
||||
|
||||
@@ -4,110 +4,128 @@ import { config } from 'dotenv'
|
||||
|
||||
config()
|
||||
|
||||
/**
|
||||
* Резолвер пути до wasm/abi пары контракта.
|
||||
*
|
||||
* Layout:
|
||||
* - **CONTRACTS_DIR не задан** (локальная сборка через `components/contracts/build*.sh`)
|
||||
* — `eosio.*` лежат в `system/contracts/<name>/`, остальные в `<name>/`.
|
||||
* - **CONTRACTS_DIR задан** (образ `dicoop/contracts`, mounted в bootstrap-контейнере)
|
||||
* — flat layout: все контракты в `<CONTRACTS_DIR>/<name>/<name>.{wasm,abi}`,
|
||||
* включая `eosio.*` (см. `.github/workflows/build-contracts.yaml` — system-контракты
|
||||
* распакованы из `system/contracts/eosio.*` в плоский корень при упаковке).
|
||||
*
|
||||
* Это позволяет одному и тому же `boot:remote` работать и из локального workspace,
|
||||
* и из bootstrap-образа, без дублирования конфигов.
|
||||
*/
|
||||
const CONTRACTS_DIR = process.env.CONTRACTS_DIR
|
||||
const userBase = CONTRACTS_DIR ?? path.resolve(process.cwd(), '../contracts/build/contracts')
|
||||
const systemBase = CONTRACTS_DIR ?? path.resolve(process.cwd(), '../contracts/build/contracts/system/contracts')
|
||||
|
||||
export default [
|
||||
{
|
||||
name: 'eosio.boot',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/system/contracts/eosio.boot'),
|
||||
path: path.join(systemBase, 'eosio.boot'),
|
||||
target: 'eosio',
|
||||
},
|
||||
{
|
||||
name: 'eosio.system',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/system/contracts/eosio.system'),
|
||||
path: path.join(systemBase, 'eosio.system'),
|
||||
target: 'eosio',
|
||||
},
|
||||
{
|
||||
name: 'eosio.token',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/system/contracts/eosio.token'),
|
||||
path: path.join(systemBase, 'eosio.token'),
|
||||
target: 'eosio.token',
|
||||
},
|
||||
{
|
||||
name: 'eosio.msig',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/system/contracts/eosio.msig'),
|
||||
path: path.join(systemBase, 'eosio.msig'),
|
||||
target: 'eosio.msig',
|
||||
},
|
||||
{
|
||||
name: 'eosio.wrap',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/system/contracts/eosio.wrap'),
|
||||
path: path.join(systemBase, 'eosio.wrap'),
|
||||
target: 'eosio.wrap',
|
||||
},
|
||||
{
|
||||
name: 'registrator',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/registrator'),
|
||||
path: path.join(userBase, 'registrator'),
|
||||
target: 'registrator',
|
||||
},
|
||||
{
|
||||
name: 'soviet',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/soviet'),
|
||||
path: path.join(userBase, 'soviet'),
|
||||
target: 'soviet',
|
||||
},
|
||||
{
|
||||
name: 'marketplace',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/marketplace'),
|
||||
path: path.join(userBase, 'marketplace'),
|
||||
target: 'marketplace',
|
||||
},
|
||||
{
|
||||
name: 'draft',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/draft'),
|
||||
path: path.join(userBase, 'draft'),
|
||||
target: 'draft',
|
||||
},
|
||||
{
|
||||
name: 'branch',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/branch'),
|
||||
path: path.join(userBase, 'branch'),
|
||||
target: 'branch',
|
||||
},
|
||||
{
|
||||
name: 'gateway',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/gateway'),
|
||||
path: path.join(userBase, 'gateway'),
|
||||
target: 'gateway',
|
||||
},
|
||||
{
|
||||
name: 'fund',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/fund'),
|
||||
path: path.join(userBase, 'fund'),
|
||||
target: 'fund',
|
||||
},
|
||||
{
|
||||
name: 'ledger',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/ledger'),
|
||||
path: path.join(userBase, 'ledger'),
|
||||
target: 'ledger',
|
||||
},
|
||||
{
|
||||
name: 'ledger2',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/ledger2'),
|
||||
path: path.join(userBase, 'ledger2'),
|
||||
target: 'ledger2',
|
||||
},
|
||||
{
|
||||
name: 'test',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/test'),
|
||||
path: path.join(userBase, 'test'),
|
||||
target: 'test',
|
||||
},
|
||||
{
|
||||
name: 'contributor',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/contributor'),
|
||||
path: path.join(userBase, 'contributor'),
|
||||
target: 'contributor',
|
||||
},
|
||||
{
|
||||
name: 'capital',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/capital'),
|
||||
path: path.join(userBase, 'capital'),
|
||||
target: 'capital',
|
||||
},
|
||||
{
|
||||
name: 'wallet',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/wallet'),
|
||||
path: path.join(userBase, 'wallet'),
|
||||
target: 'wallet',
|
||||
},
|
||||
{
|
||||
name: 'loan',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/loan'),
|
||||
path: path.join(userBase, 'loan'),
|
||||
target: 'loan',
|
||||
},
|
||||
{
|
||||
name: 'meet',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/meet'),
|
||||
path: path.join(userBase, 'meet'),
|
||||
target: 'meet',
|
||||
},
|
||||
{
|
||||
name: 'apps',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/apps'),
|
||||
path: path.join(userBase, 'apps'),
|
||||
target: 'apps',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -286,6 +286,82 @@ program
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Bootstrap режим для удалённой ноды (без управления docker-контейнером).
|
||||
*
|
||||
* Используется внутри образа `ghcr.io/coopenomics/bootstrap` (one-shot job
|
||||
* в docker-compose), запускаемого рядом с уже работающим `ke-node`-сервисом.
|
||||
* RPC URL берётся из `CHAIN_URL` (тот же конфиг, что у обычного `boot`),
|
||||
* пути до wasm/abi — из `CONTRACTS_DIR` (см. `configs/contracts.ts`).
|
||||
*
|
||||
* Поведение:
|
||||
* 1. Ждёт готовности RPC по `${CHAIN_URL}/v1/chain/get_info` (timeout
|
||||
* `RPC_WAIT_TIMEOUT_MS`, default 120 c).
|
||||
* 2. Запускает `startInfra()` — деплоит все контракты, активирует фичи,
|
||||
* создаёт токен, инициализирует системные параметры.
|
||||
* 3. Опционально — initial-data (`INSTALL_INITIAL_DATA=1`, требует MONGO/PG)
|
||||
* и extra-data (`INSTALL_EXTRA_DATA=1`).
|
||||
*
|
||||
* Не пытается запускать/останавливать docker-контейнеры — нода поднимается
|
||||
* отдельным сервисом docker-compose, bootstrap только катит на неё контракты.
|
||||
*/
|
||||
program
|
||||
.command('boot:remote')
|
||||
.description('Bootstrap protocol against an externally running KE node (no docker)')
|
||||
.action(async () => {
|
||||
const config = (await import('./configs')).default
|
||||
const { startInfra, installInitialData, installExtraData } = await import('./init/infra')
|
||||
|
||||
const url = `${config.network.protocol}://${config.network.host}${config.network.port}`
|
||||
const timeoutMs = Number(process.env.RPC_WAIT_TIMEOUT_MS ?? 120000)
|
||||
|
||||
console.log(`Waiting for RPC at ${url} (timeout ${timeoutMs}ms)...`)
|
||||
const deadline = Date.now() + timeoutMs
|
||||
let ready = false
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
const r = await fetch(`${url}/v1/chain/get_info`, {
|
||||
signal: AbortSignal.timeout(2000),
|
||||
} as RequestInit)
|
||||
if (r.ok) {
|
||||
ready = true
|
||||
break
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// RPC ещё не готов — пробуем снова
|
||||
}
|
||||
await sleep(1000)
|
||||
}
|
||||
if (!ready) {
|
||||
console.error(`RPC ${url} not ready within ${timeoutMs}ms`)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(`RPC ready at ${url}`)
|
||||
|
||||
try {
|
||||
const blockchain = await startInfra()
|
||||
console.log('startInfra: done')
|
||||
|
||||
if (process.env.INSTALL_INITIAL_DATA === '1') {
|
||||
console.log('installInitialData: start')
|
||||
await installInitialData(blockchain, false)
|
||||
}
|
||||
if (process.env.INSTALL_EXTRA_DATA === '1') {
|
||||
console.log('installInitialData (extended): start')
|
||||
await installInitialData(blockchain, true)
|
||||
await installExtraData(blockchain)
|
||||
}
|
||||
|
||||
console.log('boot:remote completed')
|
||||
process.exit(0)
|
||||
}
|
||||
catch (e) {
|
||||
console.error('boot:remote failed:', e)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
program.parse(process.argv) // Пуск парсинга аргументов
|
||||
|
||||
async function gracefulShutdown() {
|
||||
|
||||
Reference in New Issue
Block a user