From 970e1072615570fe57cf5311cd1f152ec2f3f3e8 Mon Sep 17 00:00:00 2001 From: Alex Ant Date: Sun, 24 May 2026 12:09:31 +0500 Subject: [PATCH] DEV setup on macos --- .npmrc | 6 ++ DEV-SETUP-MACOS.md | 139 +++++++++++++++++++++++++++++++++++++ scripts/dev-setup-macos.sh | 111 +++++++++++++++++++++++++++++ 3 files changed, 256 insertions(+) create mode 100644 DEV-SETUP-MACOS.md create mode 100755 scripts/dev-setup-macos.sh diff --git a/.npmrc b/.npmrc index 1f890f729a6..c572ab427b5 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,7 @@ store-dir=./.pnpm-store +supported-architectures.os[]=darwin +supported-architectures.os[]=linux +supported-architectures.cpu[]=x64 +supported-architectures.cpu[]=arm64 +supported-architectures.libc[]=glibc + diff --git a/DEV-SETUP-MACOS.md b/DEV-SETUP-MACOS.md new file mode 100644 index 00000000000..0c3e2e8fb99 --- /dev/null +++ b/DEV-SETUP-MACOS.md @@ -0,0 +1,139 @@ +# Запуск mono dev-стека на macOS + +Полная пошаговая инструкция, как поднять backend (`coopback`) + parser (`cooparser`) + блокчейн-ноду + БД на macOS через Docker Desktop. Прошёл — отметь, ниже разобраны типичные грабли. + +## TL;DR + +```bash +cd ~/dacom-code/foundation/monocoop +docker compose up -d # 1. поднять базу +./scripts/dev-setup-macos.sh # 2. одной командой (см. ниже скрипт) +``` + +Если скрипта ещё нет — выполняй шаги ниже руками. + +## Шаги + +### 1. Поднять контейнеры + +```bash +docker compose up -d +``` + +Поднимутся: `node` (NodeOS), `mongo`, `monoredis`, `postgres`, `coopback`, `cooparser`. **MinIO** входит в дефолт. **OpenSearch** — нет (тяжёлый, см. опц. сервисы). + +### 2. Проверить чтоб .env'ы указывали на service-имена, а не localhost + +`components/controller/.env`: +- `MONGODB_URL=mongodb://mongo:27017/cooperative-x` +- `REDIS_HOST=monoredis` +- `POSTGRES_HOST=postgres`, `POSTGRES_PASSWORD=postgres!23!23` +- `BLOCKCHAIN_RPC=http://node:8888` +- `SIMPLE_EXPLORER_API=http://cooparser:4000` +- `MINIO_ENDPOINT=minio:9000` +- `CHAIN_ID=<реальный chain_id, см. ниже>` + +`components/parser/.env`: +- `MONGO_EXPLORER_URI=mongodb://mongo:27017/cooperative-x` +- `REDIS_HOST=monoredis` +- `API=http://node:8888` +- `SHIP=ws://node:8080` + +**Почему service-имена:** все контейнеры в bridge-сети `monocoop_default`. Внутри контейнера `127.0.0.1` = сам контейнер, не host. На macOS `network_mode: host` в Docker Desktop работает плохо — используем bridge + service names. + +### 3. Узнать живой chain_id + +```bash +curl -s http://localhost:8888/v1/chain/get_info | jq -r .chain_id +``` + +И вписать в `components/controller/.env` → `CHAIN_ID=...`. **Если chain_id в .env не совпадает с живым — on-chain транзакции упадут на verify, хотя приложение запустится.** + +### 4. Если правил .env — пересоздать контейнер (не restart!) + +```bash +docker compose up -d --force-recreate --no-deps coopback cooparser +``` + +`docker restart` **не перечитывает** `env_file`. `--no-deps` — чтобы не пересоздавать БД (потеряются данные). + +### 5. Native binding libxmljs2 — пересборка под Linux + +На свежем чекауте `pnpm install` запускается на macOS, и `libxmljs2` собирает Mach-O бинарник. В Linux-контейнере он падает: + +``` +Error: .../xmljs.node: invalid ELF header ERR_DLOPEN_FAILED +``` + +**Фикс** (один раз после `pnpm install`): + +```bash +docker exec monocoop-coopback-1 sh -c ' + cd /app/node_modules/.pnpm/libxmljs2@0.37.0/node_modules/libxmljs2 \ + && rm -rf build \ + && PATH="$PWD/node_modules/.bin:$PATH" npm run install +' +docker restart monocoop-coopback-1 +``` + +Проверка — magic bytes Linux ELF: +```bash +docker exec monocoop-coopback-1 sh -c \ + 'head -c 4 /app/node_modules/.pnpm/libxmljs2@0.37.0/node_modules/libxmljs2/build/Release/xmljs.node | od -c -N 4' +# Должно быть: 177 E L F +# Если cf fa ed fe — это macOS Mach-O, фикс не применился. +``` + +### 6. Дождаться cold-start coopback (~5–6 минут) + +`ts-node` без cache + DI tree NestJS = долго. Первые логи появляются спустя ~5 минут после старта контейнера. + +```bash +docker logs -f monocoop-coopback-1 | grep -E 'NestJS app|Nest application|Error' +``` + +Готово, когда видишь: +``` +Nest application successfully started +NestJS app with Express routes running on port 2998 +``` + +Проверка: +```bash +curl -s -X POST -H 'Content-Type: application/json' \ + -d '{"query":"{__typename}"}' http://localhost:2998/v1/graphql +# {"data":{"__typename":"Query"}} +``` + +## Опциональные сервисы + +### OpenSearch (поиск) + +```bash +docker compose --profile search up -d opensearch +``` + +Затем в `components/controller/.env`: +``` +OPENSEARCH_ENABLED=true +``` +И пересоздать coopback: +```bash +docker compose up -d --force-recreate --no-deps coopback +``` + +## Грабли (если опять провозился пол-дня) + +| Симптом | Причина | Фикс | +|---|---|---| +| `invalid ELF header` | host'овый `pnpm install` положил Mach-O | См. п. 5 — пересборка libxmljs2 в контейнере | +| `MongooseServerSelectionError: ECONNREFUSED 127.0.0.1:27017` | .env на localhost вместо service-имени | См. п. 2 | +| `password authentication failed for user "postgres"` | в .env `POSTGRES_PASSWORD=postgres` вместо `postgres!23!23` | См. п. 2 | +| Coopback ушёл в restart-loop, но рядом есть другой `monocoop-coopback-1` Up | Случайно запустился старый `components/controller/docker-compose.yml` (network_mode: host, без bind-mount, без CMD) | `docker stop coopback && docker rm coopback` | +| `docker restart` не подхватил новые переменные .env | `restart` не перечитывает `env_file` | `docker compose up -d --force-recreate --no-deps ` | +| Coopback зависает на 5+ минут, лога нет | Это нормально для cold-start ts-node | Подождать. CPU должен крутиться ≥50%. Если CPU = 0% — другая проблема | +| `[VaultDomainService] Ошибка при получении WIF... bad decrypt` | SERVER_SECRET не совпадает с тем, чем шифровали WIF в БД | См. CLAUDE.md → раздел Vault & SERVER_SECRET. Дефолт — `SECRET` | +| Транзакции падают на on-chain verify | CHAIN_ID в .env не совпадает с живым | См. п. 3 | +| `Custom endpoint \`minio://9000\` was not a valid URI` на bootstrap | `MINIO_ENDPOINT` без схемы | `MINIO_ENDPOINT=http://minio:9000` (со схемой!) | +| nodeos в restart-loop с `Database dirty flag set` после sleep/crash Docker Desktop | unclean shutdown chain state | `docker stop monocoop-node-1`; `mv blockchain-data/state-history{,.broken-$(date +%s)}`; `docker compose up -d node` — nodeos сам сделает replay | +| Cold-start coopback 10+ минут (вместо обычных 3-5) | ts-node без cache + bind-mount через osxfs + параллельная нагрузка (nodeos replay) | Подождать; в будущем — `tsx` / `ts-node --swc` для dev-режима | diff --git a/scripts/dev-setup-macos.sh b/scripts/dev-setup-macos.sh new file mode 100755 index 00000000000..6e7976aea9d --- /dev/null +++ b/scripts/dev-setup-macos.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +# Поднять mono dev-стек на macOS с нуля. Идемпотентно — можно гонять повторно. +# См. DEV-SETUP-MACOS.md для контекста. + +set -euo pipefail + +cd "$(dirname "$0")/.." +ROOT="$PWD" + +log() { printf '\033[1;36m[dev-setup] %s\033[0m\n' "$*"; } +warn() { printf '\033[1;33m[dev-setup] %s\033[0m\n' "$*"; } +die() { printf '\033[1;31m[dev-setup] %s\033[0m\n' "$*" >&2; exit 1; } + +# 1. Docker daemon +if ! docker info >/dev/null 2>&1; then + log "Docker daemon не отвечает — пробую запустить Docker Desktop..." + open -a Docker + log "Жду готовности Docker daemon (до 3 мин)..." + for _ in $(seq 1 180); do + docker info >/dev/null 2>&1 && break + sleep 1 + done + docker info >/dev/null 2>&1 || die "Docker Desktop не запустился. Открой его руками и повтори." +fi + +# 2. Снести зомби-контейнер coopback (если есть) +if docker ps -a --format '{{.Names}}' | grep -qx 'coopback'; then + warn "Найден старый контейнер 'coopback' (не monocoop-*). Сношу — он дубль от components/controller/docker-compose.yml." + docker stop coopback >/dev/null 2>&1 || true + docker rm coopback >/dev/null 2>&1 || true +fi + +# 3. Поднять стек +log "docker compose up -d" +docker compose up -d + +# 4. Дождаться NodeOS RPC +log "Жду NodeOS RPC (http://localhost:8888)..." +for _ in $(seq 1 60); do + curl -fsS --max-time 2 http://localhost:8888/v1/chain/get_info >/dev/null 2>&1 && break + sleep 2 +done +CHAIN_INFO=$(curl -fsS --max-time 5 http://localhost:8888/v1/chain/get_info) \ + || die "NodeOS RPC не отвечает. Глянь логи: docker logs monocoop-node-1" + +LIVE_CHAIN_ID=$(printf '%s' "$CHAIN_INFO" | python3 -c "import sys, json; print(json.load(sys.stdin)['chain_id'])") +log "Живой chain_id: $LIVE_CHAIN_ID" + +# 5. Сверить и при необходимости подправить CHAIN_ID в .env +ENV_FILE="components/controller/.env" +if [[ -f "$ENV_FILE" ]]; then + ENV_CHAIN_ID=$(grep -E '^CHAIN_ID=' "$ENV_FILE" | head -1 | cut -d= -f2- || true) + if [[ "$ENV_CHAIN_ID" != "$LIVE_CHAIN_ID" ]]; then + warn "CHAIN_ID в $ENV_FILE ($ENV_CHAIN_ID) != живому ($LIVE_CHAIN_ID). Обновляю." + # macOS BSD sed + sed -i '' "s|^CHAIN_ID=.*|CHAIN_ID=$LIVE_CHAIN_ID|" "$ENV_FILE" + NEED_RECREATE=1 + fi +fi + +# 6. Native binding libxmljs2 — проверить magic bytes, пересобрать если Mach-O +log "Проверяю libxmljs2 native binding..." +LIBXML_NODE=/app/node_modules/.pnpm/libxmljs2@0.37.0/node_modules/libxmljs2/build/Release/xmljs.node +MAGIC=$(docker exec monocoop-coopback-1 sh -c "head -c 4 $LIBXML_NODE 2>/dev/null | od -An -c | tr -d ' '" 2>/dev/null || echo "") +case "$MAGIC" in + '177ELF'|*'ELF'*) + log "libxmljs2 — Linux ELF, OK." + ;; + '') + warn "libxmljs2 файла нет — собираю..." + REBUILD_XMLJS=1 + ;; + *) + warn "libxmljs2 не Linux ELF ($MAGIC) — пересобираю под Linux..." + REBUILD_XMLJS=1 + ;; +esac + +if [[ "${REBUILD_XMLJS:-0}" == "1" ]]; then + docker exec monocoop-coopback-1 sh -c ' + cd /app/node_modules/.pnpm/libxmljs2@0.37.0/node_modules/libxmljs2 \ + && rm -rf build \ + && PATH="$PWD/node_modules/.bin:$PATH" npm run install + ' + NEED_RECREATE=1 +fi + +# 7. Если что-то правили — пересоздать контейнеры +if [[ "${NEED_RECREATE:-0}" == "1" ]]; then + log "Пересоздаю coopback/cooparser, чтобы подхватить новый env / новые бинарники..." + docker compose up -d --force-recreate --no-deps coopback cooparser +fi + +# 8. Дождаться coopback HTTP +log "Жду пока coopback ответит по http://localhost:2998/v1/graphql (cold-start ts-node ~5–6 мин)..." +DEADLINE=$(($(date +%s) + 600)) +while [[ $(date +%s) -lt $DEADLINE ]]; do + if curl -fsS --max-time 3 -X POST -H 'Content-Type: application/json' \ + -d '{"query":"{__typename}"}' http://localhost:2998/v1/graphql >/dev/null 2>&1; then + log "Coopback готов." + break + fi + if docker logs --tail 5 monocoop-coopback-1 2>&1 | grep -qiE 'crashed|FATAL|invalid ELF|ERR_INVALID_PACKAGE'; then + docker logs --tail 60 monocoop-coopback-1 + die "Coopback падает — см. логи выше." + fi + sleep 5 +done + +log "Готово. Backend: http://localhost:2998/v1/graphql" +log "Опц. сервисы: docker compose --profile search up -d opensearch"