2026-05-10 05:12:14 +03:00
|
|
|
|
#!/bin/bash
|
2026-05-26 21:14:51 +03:00
|
|
|
|
# Montana node — one-command install on a clean Linux VPS.
|
|
|
|
|
|
# Montana Ядро 0.1 (mainnet). Все узлы равны: после запуска новый узел
|
|
|
|
|
|
# подключается к bootstrap peers и становится полноправным участником сети,
|
|
|
|
|
|
# как в Bitcoin. Нет привилегированных «генезис-узлов».
|
2026-05-10 05:12:14 +03:00
|
|
|
|
#
|
2026-05-26 21:14:51 +03:00
|
|
|
|
# Usage (one line on the VPS):
|
2026-05-10 05:12:14 +03:00
|
|
|
|
# curl -sSL https://raw.githubusercontent.com/efir369999/Montana/main/Code/scripts/install-vps.sh | sudo bash
|
|
|
|
|
|
#
|
2026-05-26 21:14:51 +03:00
|
|
|
|
# Or after `git clone`:
|
|
|
|
|
|
# sudo bash /opt/montana/Code/scripts/install-vps.sh
|
2026-05-10 05:12:14 +03:00
|
|
|
|
#
|
2026-05-26 21:14:51 +03:00
|
|
|
|
# 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=<your XX peer_id> label=unknown`.
|
2026-05-10 05:12:14 +03:00
|
|
|
|
|
|
|
|
|
|
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"
|
2026-05-26 21:14:51 +03:00
|
|
|
|
ETC_DIR="/etc/montana"
|
2026-05-10 05:12:14 +03:00
|
|
|
|
BIN_DST="/usr/local/bin/montana-node"
|
|
|
|
|
|
USER_NAME="montana"
|
|
|
|
|
|
SERVICE_FILE="/etc/systemd/system/montana-node.service"
|
2026-05-26 21:14:51 +03:00
|
|
|
|
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"
|
2026-05-10 05:12:14 +03:00
|
|
|
|
|
2026-05-26 21:14:51 +03:00
|
|
|
|
log() { printf '\033[1;32m[install-vps]\033[0m %s\n' "$*"; }
|
2026-05-10 05:12:14 +03:00
|
|
|
|
warn() { printf '\033[1;33m[install-vps]\033[0m %s\n' "$*" >&2; }
|
2026-05-26 21:14:51 +03:00
|
|
|
|
die() { printf '\033[1;31m[install-vps] ERROR:\033[0m %s\n' "$*" >&2; exit 1; }
|
2026-05-10 05:12:14 +03:00
|
|
|
|
|
2026-05-26 21:14:51 +03:00
|
|
|
|
# Step 1: root + OS detection
|
2026-05-10 05:12:14 +03:00
|
|
|
|
if [ "$(id -u)" != "0" ]; then
|
2026-05-26 21:14:51 +03:00
|
|
|
|
die "root privileges required. Run: curl -sSL <URL> | sudo bash"
|
2026-05-10 05:12:14 +03:00
|
|
|
|
fi
|
|
|
|
|
|
if [ ! -f /etc/os-release ]; then
|
2026-05-26 21:14:51 +03:00
|
|
|
|
die "cannot detect OS — /etc/os-release missing"
|
2026-05-10 05:12:14 +03:00
|
|
|
|
fi
|
|
|
|
|
|
. /etc/os-release
|
|
|
|
|
|
OS_ID="${ID:-unknown}"
|
2026-05-26 21:14:51 +03:00
|
|
|
|
log "detected OS: ${PRETTY_NAME:-$OS_ID}"
|
2026-05-10 05:12:14 +03:00
|
|
|
|
|
2026-05-26 21:14:51 +03:00
|
|
|
|
# Step 2: system deps
|
|
|
|
|
|
log "installing system dependencies..."
|
2026-05-10 05:12:14 +03:00
|
|
|
|
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
|
|
|
|
|
|
;;
|
|
|
|
|
|
*)
|
2026-05-26 21:14:51 +03:00
|
|
|
|
die "unsupported OS: $OS_ID. Supported: ubuntu, debian, fedora, rhel, centos, rocky, almalinux, alpine"
|
2026-05-10 05:12:14 +03:00
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
|
2026-05-26 21:14:51 +03:00
|
|
|
|
# Step 3: Rust toolchain
|
2026-05-10 05:12:14 +03:00
|
|
|
|
if ! command -v cargo >/dev/null 2>&1; then
|
2026-05-26 21:14:51 +03:00
|
|
|
|
log "installing Rust toolchain via rustup..."
|
2026-05-10 05:12:14 +03:00
|
|
|
|
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
|
2026-05-26 21:14:51 +03:00
|
|
|
|
log "Rust toolchain present: $(cargo --version)"
|
2026-05-10 05:12:14 +03:00
|
|
|
|
fi
|
|
|
|
|
|
export PATH="${HOME:-/root}/.cargo/bin:/root/.cargo/bin:$PATH"
|
|
|
|
|
|
|
2026-05-26 21:14:51 +03:00
|
|
|
|
# Step 4: clone / update repo
|
2026-05-10 05:12:14 +03:00
|
|
|
|
if [ -d "$INSTALL_DIR/.git" ]; then
|
2026-05-26 21:14:51 +03:00
|
|
|
|
log "updating repository $INSTALL_DIR..."
|
2026-05-10 05:12:14 +03:00
|
|
|
|
cd "$INSTALL_DIR"
|
|
|
|
|
|
git fetch origin "$REPO_BRANCH"
|
|
|
|
|
|
git reset --hard "origin/$REPO_BRANCH"
|
|
|
|
|
|
else
|
2026-05-26 21:14:51 +03:00
|
|
|
|
log "cloning $REPO_URL (branch $REPO_BRANCH) → $INSTALL_DIR..."
|
2026-05-10 05:12:14 +03:00
|
|
|
|
rm -rf "$INSTALL_DIR"
|
|
|
|
|
|
git clone --branch "$REPO_BRANCH" --single-branch "$REPO_URL" "$INSTALL_DIR"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-26 21:14:51 +03:00
|
|
|
|
# Step 5: build release binary
|
2026-05-10 05:12:14 +03:00
|
|
|
|
SOURCE_DIR="$INSTALL_DIR/Code"
|
|
|
|
|
|
if [ ! -d "$SOURCE_DIR" ]; then
|
2026-05-26 21:14:51 +03:00
|
|
|
|
die "expected directory '$SOURCE_DIR' not found in repository"
|
2026-05-10 05:12:14 +03:00
|
|
|
|
fi
|
|
|
|
|
|
cd "$SOURCE_DIR"
|
2026-05-26 21:14:51 +03:00
|
|
|
|
log "building montana-node release (5–30 minutes on first run)..."
|
2026-05-10 05:12:14 +03:00
|
|
|
|
cargo build --release -p montana-node 2>&1 | tail -5
|
|
|
|
|
|
|
2026-05-26 21:14:51 +03:00
|
|
|
|
# Step 6: install binary
|
2026-05-10 05:12:14 +03:00
|
|
|
|
install -m 0755 target/release/montana-node "$BIN_DST"
|
2026-05-26 21:14:51 +03:00
|
|
|
|
log "binary installed: $BIN_DST"
|
2026-05-10 05:12:14 +03:00
|
|
|
|
|
2026-05-26 21:14:51 +03:00
|
|
|
|
# Step 7: system user + data dir
|
2026-05-10 05:12:14 +03:00
|
|
|
|
if ! id "$USER_NAME" >/dev/null 2>&1; then
|
2026-05-26 21:14:51 +03:00
|
|
|
|
log "creating system user $USER_NAME..."
|
2026-05-10 05:12:14 +03:00
|
|
|
|
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"
|
|
|
|
|
|
|
2026-05-26 21:14:51 +03:00
|
|
|
|
# Step 8: identity (only if missing)
|
2026-05-10 05:12:14 +03:00
|
|
|
|
if [ ! -f "$DATA_DIR/identity.bin" ]; then
|
2026-05-26 21:14:51 +03:00
|
|
|
|
log "generating identity (24-word mnemonic)..."
|
2026-05-10 05:12:14 +03:00
|
|
|
|
echo
|
|
|
|
|
|
echo "================================================================"
|
2026-05-26 21:14:51 +03:00
|
|
|
|
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 Ɉ."
|
2026-05-10 05:12:14 +03:00
|
|
|
|
echo "================================================================"
|
|
|
|
|
|
echo
|
|
|
|
|
|
sudo -u "$USER_NAME" "$BIN_DST" init --data-dir "$DATA_DIR"
|
|
|
|
|
|
echo
|
|
|
|
|
|
else
|
2026-05-26 21:14:51 +03:00
|
|
|
|
log "identity already exists ($DATA_DIR/identity.bin) — skipping init"
|
2026-05-10 05:12:14 +03:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-26 21:14:51 +03:00
|
|
|
|
# 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"
|
2026-05-10 05:12:14 +03:00
|
|
|
|
fi
|
2026-05-26 21:14:51 +03:00
|
|
|
|
install -m 0644 "$DEFAULT_MANIFEST_SRC" "$DEFAULT_MANIFEST_DST"
|
|
|
|
|
|
log "deployed default Genesis manifest to $DEFAULT_MANIFEST_DST"
|
2026-05-10 05:12:14 +03:00
|
|
|
|
else
|
2026-05-26 21:14:51 +03:00
|
|
|
|
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
|
2026-05-10 05:12:14 +03:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-26 21:14:51 +03:00
|
|
|
|
# Step 10: systemd unit
|
|
|
|
|
|
log "installing systemd unit at $SERVICE_FILE..."
|
2026-05-10 05:12:14 +03:00
|
|
|
|
cat > "$SERVICE_FILE" <<UNIT
|
|
|
|
|
|
[Unit]
|
2026-05-26 21:14:51 +03:00
|
|
|
|
Description=Montana Node (cross-machine, Proof-of-Time, Noise_PQ XX)
|
2026-05-10 05:12:14 +03:00
|
|
|
|
Documentation=https://github.com/efir369999/Montana
|
|
|
|
|
|
After=network.target
|
|
|
|
|
|
Wants=network-online.target
|
|
|
|
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
|
|
Type=simple
|
|
|
|
|
|
User=$USER_NAME
|
|
|
|
|
|
Group=$USER_NAME
|
2026-05-26 21:14:51 +03:00
|
|
|
|
ExecStart=$BIN_DST start --data-dir $DATA_DIR --listen $LISTEN_ADDR --genesis-manifest $MANIFEST_PATH
|
2026-05-10 05:12:14 +03:00
|
|
|
|
Restart=on-failure
|
|
|
|
|
|
RestartSec=10
|
|
|
|
|
|
StandardOutput=journal
|
|
|
|
|
|
StandardError=journal
|
|
|
|
|
|
|
2026-05-26 21:14:51 +03:00
|
|
|
|
# Hardening per systemd security best-practice
|
2026-05-10 05:12:14 +03:00
|
|
|
|
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
|
|
|
|
|
|
|
2026-05-26 21:14:51 +03:00
|
|
|
|
# Resource limits — node is single-threaded, one core is enough
|
2026-05-10 05:12:14 +03:00
|
|
|
|
CPUQuota=110%
|
|
|
|
|
|
LimitNOFILE=4096
|
|
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
|
WantedBy=multi-user.target
|
|
|
|
|
|
UNIT
|
|
|
|
|
|
|
|
|
|
|
|
systemctl daemon-reload
|
|
|
|
|
|
systemctl enable montana-node.service >/dev/null 2>&1
|
|
|
|
|
|
systemctl restart montana-node.service
|
|
|
|
|
|
|
2026-05-26 21:14:51 +03:00
|
|
|
|
# Final report
|
|
|
|
|
|
sleep 3
|
2026-05-10 05:12:14 +03:00
|
|
|
|
log ""
|
|
|
|
|
|
log "================================================================"
|
2026-05-26 21:14:51 +03:00
|
|
|
|
log " INSTALL COMPLETE"
|
2026-05-10 05:12:14 +03:00
|
|
|
|
log "================================================================"
|
|
|
|
|
|
log ""
|
2026-05-26 21:14:51 +03:00
|
|
|
|
log "Binary: $BIN_DST"
|
|
|
|
|
|
log "Data: $DATA_DIR"
|
|
|
|
|
|
log "User: $USER_NAME"
|
|
|
|
|
|
log "Listen: $LISTEN_ADDR"
|
|
|
|
|
|
log "Manifest: $MANIFEST_PATH"
|
2026-05-10 05:12:14 +03:00
|
|
|
|
log "Service: montana-node.service"
|
|
|
|
|
|
log ""
|
2026-05-26 21:14:51 +03:00
|
|
|
|
log "--- service status ---"
|
2026-05-10 05:12:14 +03:00
|
|
|
|
systemctl --no-pager status montana-node.service | head -10 || true
|
|
|
|
|
|
log ""
|
2026-05-26 21:14:51 +03:00
|
|
|
|
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"
|
2026-05-10 05:12:14 +03:00
|
|
|
|
log " $BIN_DST status --data-dir $DATA_DIR # phase + balance"
|
|
|
|
|
|
log ""
|
2026-05-26 21:14:51 +03:00
|
|
|
|
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)"
|
2026-05-10 05:12:14 +03:00
|
|
|
|
log ""
|
2026-05-26 21:14:51 +03:00
|
|
|
|
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 <<XRAY
|
|
|
|
|
|
{
|
|
|
|
|
|
"log": {"loglevel": "warning", "access": "/var/log/xray/access.log", "error": "/var/log/xray/error.log"},
|
|
|
|
|
|
"dns": {"servers": ["1.1.1.1", "8.8.8.8"], "queryStrategy": "UseIP"},
|
|
|
|
|
|
"inbounds": [{
|
|
|
|
|
|
"tag": "reality-entry",
|
|
|
|
|
|
"listen": "0.0.0.0",
|
|
|
|
|
|
"port": 443,
|
|
|
|
|
|
"protocol": "vless",
|
|
|
|
|
|
"settings": {
|
|
|
|
|
|
"clients": [{"id": "${VPN_UNIVERSAL_UUID}", "email": "montana-universal", "flow": "xtls-rprx-vision"}],
|
|
|
|
|
|
"decryption": "none"
|
|
|
|
|
|
},
|
|
|
|
|
|
"streamSettings": {
|
|
|
|
|
|
"network": "tcp",
|
|
|
|
|
|
"security": "reality",
|
|
|
|
|
|
"realitySettings": {
|
|
|
|
|
|
"show": false,
|
|
|
|
|
|
"dest": "${VPN_SNI}:443",
|
|
|
|
|
|
"xver": 0,
|
|
|
|
|
|
"serverNames": ["${VPN_SNI}"],
|
|
|
|
|
|
"privateKey": "${VPN_UNIVERSAL_PRIVKEY}",
|
|
|
|
|
|
"shortIds": ["${VPN_UNIVERSAL_SID}"]
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
"sniffing": {"enabled": true, "destOverride": ["http", "tls", "quic"]}
|
|
|
|
|
|
}],
|
|
|
|
|
|
"outbounds": [
|
|
|
|
|
|
{"tag": "direct", "protocol": "freedom", "settings": {"domainStrategy": "UseIP"}},
|
|
|
|
|
|
{"tag": "blocked", "protocol": "blackhole"},
|
|
|
|
|
|
{"tag": "dns-out", "protocol": "dns"}
|
|
|
|
|
|
],
|
|
|
|
|
|
"routing": {"rules": [
|
|
|
|
|
|
{"type": "field", "port": "53", "outboundTag": "dns-out"},
|
|
|
|
|
|
{"type": "field", "ip": ["geoip:private"], "outboundTag": "blocked"}
|
|
|
|
|
|
]}
|
|
|
|
|
|
}
|
|
|
|
|
|
XRAY
|
|
|
|
|
|
|
|
|
|
|
|
# Public-safe metadata file for federated /vpn/sub aggregator
|
|
|
|
|
|
VPN_PUBKEY=$(xray x25519 -i "${VPN_UNIVERSAL_PRIVKEY}" 2>/dev/null | awk -F': ' '/PublicKey/ {print $2}')
|
|
|
|
|
|
cat > /var/lib/montana-net/my-vpn.json <<META
|
|
|
|
|
|
{
|
|
|
|
|
|
"UUID": "${VPN_UNIVERSAL_UUID}",
|
|
|
|
|
|
"PBK": "${VPN_PUBKEY}",
|
|
|
|
|
|
"SID": "${VPN_UNIVERSAL_SID}",
|
|
|
|
|
|
"SNI": "${VPN_SNI}"
|
|
|
|
|
|
}
|
|
|
|
|
|
META
|
|
|
|
|
|
chmod 644 /var/lib/montana-net/my-vpn.json
|
|
|
|
|
|
|
|
|
|
|
|
systemctl daemon-reload
|
|
|
|
|
|
systemctl enable xray >/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}@<this-host>: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
|