223 lines
9.3 KiB
Bash
223 lines
9.3 KiB
Bash
|
|
#!/bin/bash
|
|||
|
|
# Montana node — установка одной командой на Linux VPS.
|
|||
|
|
#
|
|||
|
|
# Использование (одна строка в терминале VPS):
|
|||
|
|
# curl -sSL https://raw.githubusercontent.com/efir369999/Montana/main/Код/scripts/install-vps.sh | sudo bash
|
|||
|
|
#
|
|||
|
|
# Либо локально после git clone:
|
|||
|
|
# sudo bash scripts/install-vps.sh
|
|||
|
|
#
|
|||
|
|
# Что делает:
|
|||
|
|
# 1. Проверяет OS (Ubuntu/Debian/Fedora/RHEL/Alpine)
|
|||
|
|
# 2. Устанавливает system deps (build-essential, clang, git, perl)
|
|||
|
|
# 3. Ставит Rust toolchain через rustup (если нет)
|
|||
|
|
# 4. Клонирует/обновляет репозиторий в /opt/montana
|
|||
|
|
# 5. Собирает release бинарь
|
|||
|
|
# 6. Создаёт системного пользователя montana и /var/lib/montana
|
|||
|
|
# 7. Генерирует identity (24-словная мнемоника выводится — ЗАПИШИТЕ!)
|
|||
|
|
# 8. Устанавливает systemd unit с hardening
|
|||
|
|
# 9. Запускает узел и включает автозапуск при старте VPS
|
|||
|
|
|
|||
|
|
set -euo pipefail
|
|||
|
|
|
|||
|
|
REPO_URL="${MONTANA_REPO_URL:-https://github.com/efir369999/Montana.git}"
|
|||
|
|
REPO_BRANCH="${MONTANA_REPO_BRANCH:-main}"
|
|||
|
|
INSTALL_DIR="/opt/montana"
|
|||
|
|
DATA_DIR="/var/lib/montana"
|
|||
|
|
BIN_DST="/usr/local/bin/montana-node"
|
|||
|
|
USER_NAME="montana"
|
|||
|
|
SERVICE_FILE="/etc/systemd/system/montana-node.service"
|
|||
|
|
|
|||
|
|
log() { printf '\033[1;32m[install-vps]\033[0m %s\n' "$*"; }
|
|||
|
|
warn() { printf '\033[1;33m[install-vps]\033[0m %s\n' "$*" >&2; }
|
|||
|
|
die() { printf '\033[1;31m[install-vps] ОШИБКА:\033[0m %s\n' "$*" >&2; exit 1; }
|
|||
|
|
|
|||
|
|
# --- Шаг 1: проверка root ---
|
|||
|
|
if [ "$(id -u)" != "0" ]; then
|
|||
|
|
die "требуется sudo/root. Запустите: curl -sSL <URL> | sudo bash"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# --- Шаг 2: detect OS ---
|
|||
|
|
if [ ! -f /etc/os-release ]; then
|
|||
|
|
die "не могу определить OS — отсутствует /etc/os-release"
|
|||
|
|
fi
|
|||
|
|
. /etc/os-release
|
|||
|
|
OS_ID="${ID:-unknown}"
|
|||
|
|
log "обнаружен OS: ${PRETTY_NAME:-$OS_ID}"
|
|||
|
|
|
|||
|
|
# --- Шаг 3: install system deps ---
|
|||
|
|
log "устанавливаю system dependencies..."
|
|||
|
|
case "$OS_ID" in
|
|||
|
|
ubuntu|debian)
|
|||
|
|
export DEBIAN_FRONTEND=noninteractive
|
|||
|
|
apt-get update -qq
|
|||
|
|
apt-get install -y -qq build-essential clang pkg-config git curl perl ca-certificates >/dev/null
|
|||
|
|
;;
|
|||
|
|
fedora|rhel|centos|rocky|almalinux)
|
|||
|
|
dnf install -y -q gcc gcc-c++ clang pkgconf-pkg-config git curl perl ca-certificates make >/dev/null
|
|||
|
|
;;
|
|||
|
|
alpine)
|
|||
|
|
apk add --no-cache build-base clang pkgconfig git curl perl linux-headers ca-certificates >/dev/null
|
|||
|
|
;;
|
|||
|
|
*)
|
|||
|
|
die "неподдерживаемый OS: $OS_ID. Поддерживаются: ubuntu, debian, fedora, rhel, centos, rocky, almalinux, alpine"
|
|||
|
|
;;
|
|||
|
|
esac
|
|||
|
|
|
|||
|
|
# --- Шаг 4: install Rust toolchain ---
|
|||
|
|
if ! command -v cargo >/dev/null 2>&1; then
|
|||
|
|
log "устанавливаю Rust toolchain (rustup)..."
|
|||
|
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
|
|||
|
|
sh -s -- -y --default-toolchain stable --profile minimal --no-modify-path
|
|||
|
|
export PATH="/root/.cargo/bin:$PATH"
|
|||
|
|
else
|
|||
|
|
log "Rust toolchain уже установлен: $(cargo --version)"
|
|||
|
|
fi
|
|||
|
|
export PATH="${HOME:-/root}/.cargo/bin:/root/.cargo/bin:$PATH"
|
|||
|
|
|
|||
|
|
# --- Шаг 5: clone/update repo ---
|
|||
|
|
if [ -d "$INSTALL_DIR/.git" ]; then
|
|||
|
|
log "обновляю репозиторий $INSTALL_DIR..."
|
|||
|
|
cd "$INSTALL_DIR"
|
|||
|
|
git fetch origin "$REPO_BRANCH"
|
|||
|
|
git reset --hard "origin/$REPO_BRANCH"
|
|||
|
|
else
|
|||
|
|
log "клонирую $REPO_URL (branch $REPO_BRANCH) → $INSTALL_DIR..."
|
|||
|
|
rm -rf "$INSTALL_DIR"
|
|||
|
|
git clone --branch "$REPO_BRANCH" --single-branch "$REPO_URL" "$INSTALL_DIR"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# --- Шаг 6: build бинарь ---
|
|||
|
|
SOURCE_DIR="$INSTALL_DIR/Код"
|
|||
|
|
if [ ! -d "$SOURCE_DIR" ]; then
|
|||
|
|
die "директория '$SOURCE_DIR' не найдена в репозитории. Возможно структура изменилась — проверьте путь к montana-node."
|
|||
|
|
fi
|
|||
|
|
cd "$SOURCE_DIR"
|
|||
|
|
log "собираю montana-node release (это занимает 5-15 минут на первом запуске)..."
|
|||
|
|
cargo build --release -p montana-node 2>&1 | tail -5
|
|||
|
|
|
|||
|
|
# --- Шаг 7: install бинарь ---
|
|||
|
|
install -m 0755 target/release/montana-node "$BIN_DST"
|
|||
|
|
log "бинарь установлен: $BIN_DST"
|
|||
|
|
|
|||
|
|
# --- Шаг 8: create user + data dir ---
|
|||
|
|
if ! id "$USER_NAME" >/dev/null 2>&1; then
|
|||
|
|
log "создаю системного пользователя $USER_NAME..."
|
|||
|
|
useradd -r -s /usr/sbin/nologin -d "$DATA_DIR" -M "$USER_NAME" 2>/dev/null \
|
|||
|
|
|| useradd -r -s /bin/false -d "$DATA_DIR" "$USER_NAME"
|
|||
|
|
fi
|
|||
|
|
mkdir -p "$DATA_DIR"
|
|||
|
|
chown -R "$USER_NAME:$USER_NAME" "$DATA_DIR"
|
|||
|
|
chmod 0750 "$DATA_DIR"
|
|||
|
|
|
|||
|
|
# --- Шаг 9: init identity (если нет) ---
|
|||
|
|
if [ ! -f "$DATA_DIR/identity.bin" ]; then
|
|||
|
|
log "генерирую identity (24-словная мнемоника)..."
|
|||
|
|
echo
|
|||
|
|
echo "================================================================"
|
|||
|
|
echo " ВНИМАНИЕ: ниже выведутся 24 слова мнемоники."
|
|||
|
|
echo " ЗАПИШИТЕ их в надёжное место — это backup всего."
|
|||
|
|
echo " Потеряете → потеряете доступ к узлу и всем заработанным Ɉ."
|
|||
|
|
echo "================================================================"
|
|||
|
|
echo
|
|||
|
|
sudo -u "$USER_NAME" "$BIN_DST" init --data-dir "$DATA_DIR"
|
|||
|
|
echo
|
|||
|
|
else
|
|||
|
|
log "identity уже существует ($DATA_DIR/identity.bin) — пропускаю генерацию"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# --- Шаг 10: M8 cross-machine networking config (опционально) ---
|
|||
|
|
# Если operator задал MONTANA_LISTEN и MONTANA_GENESIS_MANIFEST в env —
|
|||
|
|
# узел стартует в cross-machine режиме (libp2p TCP+TLS+Noise + peer discovery).
|
|||
|
|
# Иначе остаётся в singleton mode (backward compat).
|
|||
|
|
MONTANA_NODE_EXTRA_ARGS=""
|
|||
|
|
if [ -n "${MONTANA_LISTEN:-}" ] && [ -n "${MONTANA_GENESIS_MANIFEST:-}" ]; then
|
|||
|
|
MONTANA_NODE_EXTRA_ARGS="--listen $MONTANA_LISTEN --genesis-manifest $MONTANA_GENESIS_MANIFEST"
|
|||
|
|
log "cross-machine M8 mode: --listen=$MONTANA_LISTEN, manifest=$MONTANA_GENESIS_MANIFEST"
|
|||
|
|
if [ ! -f "$MONTANA_GENESIS_MANIFEST" ]; then
|
|||
|
|
warn "manifest $MONTANA_GENESIS_MANIFEST ещё не существует — узел перезапустится корректно после его создания (Restart=on-failure)"
|
|||
|
|
fi
|
|||
|
|
elif [ -n "${MONTANA_LISTEN:-}" ] || [ -n "${MONTANA_GENESIS_MANIFEST:-}" ]; then
|
|||
|
|
die "MONTANA_LISTEN и MONTANA_GENESIS_MANIFEST должны указываться вместе"
|
|||
|
|
else
|
|||
|
|
log "singleton mode (без --listen/--genesis-manifest)"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# --- Шаг 11: install systemd unit ---
|
|||
|
|
log "устанавливаю systemd unit $SERVICE_FILE..."
|
|||
|
|
cat > "$SERVICE_FILE" <<UNIT
|
|||
|
|
[Unit]
|
|||
|
|
Description=Montana Local Node (single-node, Proof-of-Time)
|
|||
|
|
Documentation=https://github.com/efir369999/Montana
|
|||
|
|
After=network.target
|
|||
|
|
Wants=network-online.target
|
|||
|
|
|
|||
|
|
[Service]
|
|||
|
|
Type=simple
|
|||
|
|
User=$USER_NAME
|
|||
|
|
Group=$USER_NAME
|
|||
|
|
ExecStart=$BIN_DST start --data-dir $DATA_DIR ${MONTANA_NODE_EXTRA_ARGS}
|
|||
|
|
Restart=on-failure
|
|||
|
|
RestartSec=10
|
|||
|
|
StandardOutput=journal
|
|||
|
|
StandardError=journal
|
|||
|
|
|
|||
|
|
# Hardening (per systemd security best-practice)
|
|||
|
|
NoNewPrivileges=yes
|
|||
|
|
PrivateTmp=yes
|
|||
|
|
ProtectSystem=strict
|
|||
|
|
ProtectHome=yes
|
|||
|
|
ReadWritePaths=$DATA_DIR
|
|||
|
|
ProtectKernelTunables=yes
|
|||
|
|
ProtectKernelModules=yes
|
|||
|
|
ProtectControlGroups=yes
|
|||
|
|
RestrictRealtime=yes
|
|||
|
|
RestrictSUIDSGID=yes
|
|||
|
|
LockPersonality=yes
|
|||
|
|
MemoryDenyWriteExecute=no
|
|||
|
|
SystemCallArchitectures=native
|
|||
|
|
|
|||
|
|
# Resource limits — узел single-thread, 1 ядро достаточно
|
|||
|
|
CPUQuota=110%
|
|||
|
|
LimitNOFILE=4096
|
|||
|
|
|
|||
|
|
[Install]
|
|||
|
|
WantedBy=multi-user.target
|
|||
|
|
UNIT
|
|||
|
|
|
|||
|
|
# --- Шаг 11: enable & start ---
|
|||
|
|
systemctl daemon-reload
|
|||
|
|
systemctl enable montana-node.service >/dev/null 2>&1
|
|||
|
|
systemctl restart montana-node.service
|
|||
|
|
|
|||
|
|
# --- Финальный отчёт ---
|
|||
|
|
sleep 2
|
|||
|
|
log ""
|
|||
|
|
log "================================================================"
|
|||
|
|
log " УСТАНОВКА ЗАВЕРШЕНА"
|
|||
|
|
log "================================================================"
|
|||
|
|
log ""
|
|||
|
|
log "Бинарь: $BIN_DST"
|
|||
|
|
log "Данные: $DATA_DIR"
|
|||
|
|
log "Пользователь: $USER_NAME"
|
|||
|
|
log "Service: montana-node.service"
|
|||
|
|
log ""
|
|||
|
|
log "--- статус узла ---"
|
|||
|
|
systemctl --no-pager status montana-node.service | head -10 || true
|
|||
|
|
log ""
|
|||
|
|
log "Полезные команды:"
|
|||
|
|
log " systemctl status montana-node # текущий статус"
|
|||
|
|
log " journalctl -u montana-node -f # логи в реальном времени"
|
|||
|
|
log " systemctl stop montana-node # остановить узел"
|
|||
|
|
log " systemctl restart montana-node # перезапустить"
|
|||
|
|
log " $BIN_DST status --data-dir $DATA_DIR # phase + balance"
|
|||
|
|
log ""
|
|||
|
|
log "Жизненный цикл узла:"
|
|||
|
|
log " Phase 1: Bootstrap → CandidateVdf (~10-14 часов VDF до vdf_chain_length ≥ τ₂)"
|
|||
|
|
log " Phase 2: CandidateVdf → Registered (NodeRegistration через canonical apply_*)"
|
|||
|
|
log " Phase 3: Registered → Active (selection event на следующем W % 336 == 0)"
|
|||
|
|
log " Phase 4: Active (эмиссия 13 Ɉ per окно через apply_proposal)"
|
|||
|
|
log ""
|
|||
|
|
log "Это spec-compliant Sybil-защита Montana — нельзя обойти быстрее."
|
|||
|
|
log "Узел переживает рестарты VPS (systemd Restart=on-failure) и продолжает с того окна где был."
|