diff --git a/Russian/Genesis/ГЕНЕЗИС_2026-05-09_00-00_UTC/bootstrap-узла.sh b/Russian/Genesis/ГЕНЕЗИС_2026-05-09_00-00_UTC/bootstrap-узла.sh new file mode 100644 index 00000000..e549b93c --- /dev/null +++ b/Russian/Genesis/ГЕНЕЗИС_2026-05-09_00-00_UTC/bootstrap-узла.sh @@ -0,0 +1,129 @@ +#!/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"