130 lines
5.1 KiB
Bash
130 lines
5.1 KiB
Bash
|
|
#!/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"
|