#!/usr/bin/env bash # Bootstrap нового узла Montana из Genesis 2026-05-09 00:00 UTC. # # Скрипт делает четыре вещи: # 1. Находит живой источник Genesis (перебор IP-пула, далее TON DNS). # 2. Клонирует репозиторий `Ничто` локально. # 3. Проверяет МАНИФЕСТ.md по ed25519-подписи Genesis root pubkey. # 4. Сверяет SHA-256 каждого файла с тем, что в манифесте. # # Использование: # curl -sfL https://<любой-mirror>/montana.git/raw/main/.../bootstrap-узла.sh | bash # или # bash ./bootstrap-узла.sh [целевая_папка] # # Запуск без root. Зависимости: git, python3, curl, openssl. set -euo pipefail # --- Genesis-параметры (вшиты, не должны меняться) --- ROOT_PUBKEY_B64="MtE485L/O87feImV8XjSdPPskgestVd7mqJvKo9MRas=" ROOT_FP="e4eb11c9d198ab80" GENESIS_FOLDER="Montana/Russian/Genesis/ГЕНЕЗИС_2026-05-09_00-00_UTC" # IP-пул реплицируемых узлов (обновляется только при новом Genesis) IP_POOL=( "91.132.142.42" # Helsinki (Hetzner AS24940) "89.19.208.158" # Frankfurt (Timeweb AS9123) "176.124.208.93" # Moscow (Timeweb AS9123, master/Gitea) ) # Доменные источники (DNS + TON DNS) DOMAINS=( "hub.montana.quest/efir369999/montana" "junomoneta.ton/montana" ) TARGET="${1:-$HOME/montana-node}" log() { echo "[bootstrap] $*" >&2; } fail() { echo "[bootstrap] FATAL: $*" >&2; exit 1; } # --- 1. Найти живой источник --- SOURCE="" log "Поиск живого Genesis-источника..." for dom in "${DOMAINS[@]}"; do if curl -fsI --max-time 5 "https://${dom}.git/info/refs?service=git-upload-pack" >/dev/null 2>&1; then SOURCE="https://${dom}.git" log "Найден: $SOURCE" break fi done if [[ -z "$SOURCE" ]]; then for ip in "${IP_POOL[@]}"; do if curl -fsI --max-time 5 -k "https://${ip}/montana.git/info/refs?service=git-upload-pack" >/dev/null 2>&1; then SOURCE="https://${ip}/montana.git" log "Найден IP-fallback: $SOURCE" break fi done fi [[ -z "$SOURCE" ]] && fail "ни один Genesis-источник не отвечает (домены и IP-пул)" # --- 2. Клонирование --- log "Клонирование $SOURCE → $TARGET" mkdir -p "$(dirname "$TARGET")" GIT_LFS_SKIP_SMUDGE=1 git clone --depth=1 --no-tags "$SOURCE" "$TARGET" # --- 3. Проверка подписи манифеста --- GDIR="$TARGET/$GENESIS_FOLDER" [[ -d "$GDIR" ]] || fail "Genesis-папка не найдена в репозитории: $GDIR" MANIFEST="$GDIR/МАНИФЕСТ.md" [[ -f "$MANIFEST" ]] || fail "МАНИФЕСТ.md отсутствует" log "Проверка ed25519-подписи манифеста против root pubkey $ROOT_FP" python3 - "$MANIFEST" "$ROOT_PUBKEY_B64" "$GDIR" <<'PY' import sys, base64, re, hashlib, pathlib from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey from cryptography.exceptions import InvalidSignature manifest_path, pubkey_b64, gdir = sys.argv[1], sys.argv[2], sys.argv[3] text = pathlib.Path(manifest_path).read_text() m_files = re.search(r'## Файлы\n\n```\n(.*?)\n```', text, re.S) m_sig = re.search(r'## Подпись.*?```\n(\S+)\n```', text, re.S) m_pk = re.search(r'## Корневой ключ.*?```\n(\S+)\n```', text, re.S) if not (m_files and m_sig and m_pk): sys.exit("[bootstrap] FATAL: манифест повреждён, не удалось распарсить блоки") if m_pk.group(1) != pubkey_b64: sys.exit(f"[bootstrap] FATAL: pubkey в манифесте ({m_pk.group(1)[:20]}…) не совпадает со вшитым корнем") body = m_files.group(1) + '\n' sig = base64.b64decode(m_sig.group(1)) pk = Ed25519PublicKey.from_public_bytes(base64.b64decode(pubkey_b64)) try: pk.verify(sig, body.encode()) except InvalidSignature: sys.exit("[bootstrap] FATAL: подпись манифеста НЕ ПРОВЕРЕНА — источник возможно подменён") print("[bootstrap] ✓ подпись манифеста валидна") bad = [] for line in body.strip().split('\n'): sha, fname = line.split(' ', 1) p = pathlib.Path(gdir) / fname if not p.exists(): bad.append(f" отсутствует: {fname}") continue actual = hashlib.sha256(p.read_bytes()).hexdigest() if actual != sha: bad.append(f" hash mismatch {fname}: expected {sha[:12]}…, got {actual[:12]}…") if bad: print("[bootstrap] FATAL: содержимое не совпадает с манифестом") for b in bad: print(b) sys.exit(1) print(f"[bootstrap] ✓ {len(body.strip().splitlines())} файлов прошли SHA-256-сверку") PY log "Bootstrap завершён. Genesis: $GDIR" log "Дальше: montana-node init --from-folder $TARGET --genesis $GENESIS_FOLDER"