#!/bin/bash # Montana node — one-command install on a clean Linux VPS. # Montana Ядро 0.1 (mainnet). Все узлы равны: после запуска новый узел # подключается к bootstrap peers и становится полноправным участником сети, # как в Bitcoin. Нет привилегированных «генезис-узлов». # # Usage (one line on the VPS): # curl -sSL https://raw.githubusercontent.com/efir369999/Montana/main/Code/scripts/install-vps.sh | sudo bash # # Or after `git clone`: # sudo bash /opt/montana/Code/scripts/install-vps.sh # # Defaults: # - listen on /ip4/0.0.0.0/tcp/8444 (Noise_PQ XX over TCP) # - dial the bootstrap nodes from Code/scripts/genesis-manifest.json # - the new node joins as a candidate; live mesh heartbeats appear in the # bootstrap peers' logs within seconds of `systemctl start` # # Overrides: # MONTANA_LISTEN=/ip4/0.0.0.0/tcp/PORT change listen port (default 8444) # MONTANA_GENESIS_MANIFEST=/path/to/file use a custom manifest file # MONTANA_REPO_BRANCH=main override branch (default main) # INSTALL_VPN=1 also install Xray Reality VPN # backend on :443 (joins the # federated /vpn/sub pool) # # Steps: # 1. Verify root and detect OS (Ubuntu / Debian / Fedora / RHEL / Alpine) # 2. Install system build dependencies # 3. Install Rust toolchain via rustup (skip if cargo already present) # 4. Clone or fast-forward the repository at /opt/montana # 5. Build the release binary # 6. Create system user `montana` and data directory /var/lib/montana # 7. Generate identity (prints 24-word recovery mnemonic — save it!) # 8. Deploy /etc/montana/genesis-manifest.json from the repo bundle # 9. Install systemd unit with hardening + cross-machine networking # 10. Enable and start the service # # After step 10, the local node dials the bootstrap peers, negotiates # Noise_PQ XX (/montana/noise-pq-xx/1.0.0), and starts exchanging Ping/Pong # heartbeats. The new node appears in mos/fra/zel journals as # `[network] CONNECTION ESTABLISHED peer= label=unknown`. 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" ETC_DIR="/etc/montana" BIN_DST="/usr/local/bin/montana-node" USER_NAME="montana" SERVICE_FILE="/etc/systemd/system/montana-node.service" DEFAULT_LISTEN="/ip4/0.0.0.0/tcp/8444" DEFAULT_MANIFEST_SRC="$INSTALL_DIR/Code/scripts/genesis-manifest.json" DEFAULT_MANIFEST_DST="$ETC_DIR/genesis-manifest.json" 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] ERROR:\033[0m %s\n' "$*" >&2; exit 1; } # Step 1: root + OS detection if [ "$(id -u)" != "0" ]; then die "root privileges required. Run: curl -sSL | sudo bash" fi if [ ! -f /etc/os-release ]; then die "cannot detect OS — /etc/os-release missing" fi . /etc/os-release OS_ID="${ID:-unknown}" log "detected OS: ${PRETTY_NAME:-$OS_ID}" # Step 2: system deps log "installing 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 "unsupported OS: $OS_ID. Supported: ubuntu, debian, fedora, rhel, centos, rocky, almalinux, alpine" ;; esac # Step 3: Rust toolchain if ! command -v cargo >/dev/null 2>&1; then log "installing Rust toolchain via 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 present: $(cargo --version)" fi export PATH="${HOME:-/root}/.cargo/bin:/root/.cargo/bin:$PATH" # Step 4: clone / update repo if [ -d "$INSTALL_DIR/.git" ]; then log "updating repository $INSTALL_DIR..." cd "$INSTALL_DIR" git fetch origin "$REPO_BRANCH" git reset --hard "origin/$REPO_BRANCH" else log "cloning $REPO_URL (branch $REPO_BRANCH) → $INSTALL_DIR..." rm -rf "$INSTALL_DIR" git clone --branch "$REPO_BRANCH" --single-branch "$REPO_URL" "$INSTALL_DIR" fi # Step 5: build release binary SOURCE_DIR="$INSTALL_DIR/Code" if [ ! -d "$SOURCE_DIR" ]; then die "expected directory '$SOURCE_DIR' not found in repository" fi cd "$SOURCE_DIR" log "building montana-node release (5–30 minutes on first run)..." cargo build --release -p montana-node 2>&1 | tail -5 # Step 6: install binary install -m 0755 target/release/montana-node "$BIN_DST" log "binary installed: $BIN_DST" # Step 7: system user + data dir if ! id "$USER_NAME" >/dev/null 2>&1; then log "creating system user $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" # Step 8: identity (only if missing) if [ ! -f "$DATA_DIR/identity.bin" ]; then log "generating identity (24-word mnemonic)..." echo echo "================================================================" echo " NOTE: 24 mnemonic words will be printed below." echo " Write them down in a safe place — this is the entire backup." echo " Lose them → lose the node and all earned Ɉ." echo "================================================================" echo sudo -u "$USER_NAME" "$BIN_DST" init --data-dir "$DATA_DIR" echo else log "identity already exists ($DATA_DIR/identity.bin) — skipping init" fi # Step 9: deploy Genesis manifest LISTEN_ADDR="${MONTANA_LISTEN:-$DEFAULT_LISTEN}" MANIFEST_PATH="${MONTANA_GENESIS_MANIFEST:-$DEFAULT_MANIFEST_DST}" mkdir -p "$ETC_DIR" chmod 0755 "$ETC_DIR" if [ -z "${MONTANA_GENESIS_MANIFEST:-}" ]; then # default path: copy the bundled manifest to /etc/montana if [ ! -f "$DEFAULT_MANIFEST_SRC" ]; then die "expected bundled manifest at $DEFAULT_MANIFEST_SRC not found in repository" fi install -m 0644 "$DEFAULT_MANIFEST_SRC" "$DEFAULT_MANIFEST_DST" log "deployed default Genesis manifest to $DEFAULT_MANIFEST_DST" else if [ ! -f "$MANIFEST_PATH" ]; then warn "custom manifest $MANIFEST_PATH not yet present — node will retry on systemd restart loop" else log "using custom manifest at $MANIFEST_PATH" fi fi # Step 10: systemd unit log "installing systemd unit at $SERVICE_FILE..." cat > "$SERVICE_FILE" </dev/null 2>&1 systemctl restart montana-node.service # Final report sleep 3 log "" log "================================================================" log " INSTALL COMPLETE" log "================================================================" log "" log "Binary: $BIN_DST" log "Data: $DATA_DIR" log "User: $USER_NAME" log "Listen: $LISTEN_ADDR" log "Manifest: $MANIFEST_PATH" log "Service: montana-node.service" log "" log "--- service status ---" systemctl --no-pager status montana-node.service | head -10 || true log "" log "Useful commands:" log " systemctl status montana-node # current status" log " journalctl -u montana-node -f # follow logs" log " systemctl stop montana-node # stop the node" log " systemctl restart montana-node # restart" log " $BIN_DST status --data-dir $DATA_DIR # phase + balance" log "" log "Node lifecycle:" log " Phase 1: Bootstrap → CandidateVdf (sequential SHA-256 chain to vdf_chain_length ≥ τ₂)" log " Phase 2: CandidateVdf → Registered (NodeRegistration via canonical apply_*)" log " Phase 3: Registered → Active (selection event on next W where W % 336 == 0)" log " Phase 4: Active (emission 13 Ɉ per window via apply_proposal)" log "" log "This is the spec-defined Sybil barrier — there is no shortcut." log "The node survives VPS restarts (Restart=on-failure) and resumes at the same window." log "" log "Within seconds of start, your node negotiates Noise_PQ XX with the three" log "Genesis peers (moscow / frankfurt / helsinki) listed in the manifest. To" log "confirm the connection appears in the live mesh, ask one of the Genesis" log "operators to grep their journal for your local XX PeerId." # ─────────────────────────────────────────────────────────────────────── # Optional: Montana Xray Reality VPN backend # ─────────────────────────────────────────────────────────────────────── # Run with INSTALL_VPN=1 to also stand up the Reality VPN endpoint on :443 # alongside montana-node. The endpoint joins the federated /vpn/sub pool # served at https://montana.quest/vpn/sub. Universal shared keypair is # used so every Montana VPN-backend node accepts the same client config # (see project_montana_vpn_universal_key.md). # # Defaults (overridable via env): # INSTALL_VPN=0 — set to 1 to install # VPN_UNIVERSAL_UUID — shared UUID for all Montana VPN clients # VPN_UNIVERSAL_PRIVKEY — shared Reality x25519 private key # VPN_UNIVERSAL_SID — shared Reality short_id # VPN_SNI — Reality dest SNI if [ "${INSTALL_VPN:-0}" = "1" ]; then log "--- installing Xray Reality VPN backend on :443 ---" VPN_UNIVERSAL_UUID="${VPN_UNIVERSAL_UUID:-e6d355e2-2d79-4c96-a373-3b0e6b6f4b0d}" VPN_UNIVERSAL_PRIVKEY="${VPN_UNIVERSAL_PRIVKEY:-cL7D6FCqH5nWcQlHCKH9uNr-RNwCt5peRAqt8tl9mXs}" VPN_UNIVERSAL_SID="${VPN_UNIVERSAL_SID:-302805bc0c25e504}" VPN_SNI="${VPN_SNI:-www.googletagmanager.com}" XRAY_VERSION="${XRAY_VERSION:-26.2.6}" if ! command -v xray >/dev/null 2>&1; then log " installing xray-core v${XRAY_VERSION}..." bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" \ @ install --version "${XRAY_VERSION}" -u root >/dev/null 2>&1 || \ log " WARN xray install failed; continuing" fi if command -v xray >/dev/null 2>&1; then mkdir -p /usr/local/etc/xray /var/log/xray /var/lib/montana-net cat > /usr/local/etc/xray/config.json </dev/null | awk -F': ' '/PublicKey/ {print $2}') cat > /var/lib/montana-net/my-vpn.json </dev/null 2>&1 || true systemctl restart xray sleep 2 if systemctl is-active --quiet xray; then log " xray active on :443" log " vless://${VPN_UNIVERSAL_UUID}@:443?flow=xtls-rprx-vision&security=reality&sni=${VPN_SNI}&pbk=${VPN_PUBKEY}&sid=${VPN_UNIVERSAL_SID}&type=tcp" log "" log "To enroll this node in https://montana.quest/vpn/sub aggregator," log "ask the orchestrator operator to POST to /api/orchestrator/register" log "with this node's IP, alias, country, and label." else log " WARN xray failed to start; check journalctl -u xray" fi fi fi