170 lines
7.0 KiB
Bash
170 lines
7.0 KiB
Bash
|
|
#!/bin/bash
|
|||
|
|
# Montana VPN — установщик xray Reality endpoint на Linux VPS.
|
|||
|
|
#
|
|||
|
|
# Что делает:
|
|||
|
|
# 1. Ставит system deps (xray, nginx, ufw, curl, jq)
|
|||
|
|
# 2. Генерирует свежие Reality keys + UUID + shortId на самом хосте
|
|||
|
|
# 3. Подставляет их в xray-config.json.template → /usr/local/etc/xray/config.json
|
|||
|
|
# 4. Поднимает nginx :80 с decoy-страницей (камуфляж от пассивных проверяющих)
|
|||
|
|
# 5. Прописывает systemd unit + drop-in для xray
|
|||
|
|
# 6. Открывает 22/80/443 в ufw, остальное закрывает
|
|||
|
|
# 7. Включает BBR + fq_codel через sysctl
|
|||
|
|
# 8. Запускает xray, печатает VLESS URL клиенту
|
|||
|
|
#
|
|||
|
|
# Использование:
|
|||
|
|
# sudo bash install.sh # с настройками по умолчанию
|
|||
|
|
# sudo DECOY_HOST=www.googletagmanager.com bash install.sh
|
|||
|
|
# sudo CLIENT_EMAIL=alice@montana bash install.sh
|
|||
|
|
#
|
|||
|
|
# Идемпотентен: повторный запуск не ломает настройки и не пересоздаёт ключи
|
|||
|
|
# (они в /etc/montana-vpn/state.env, удалить файл = пересоздать).
|
|||
|
|
|
|||
|
|
set -euo pipefail
|
|||
|
|
|
|||
|
|
DECOY_HOST="${DECOY_HOST:-www.googletagmanager.com}"
|
|||
|
|
CLIENT_EMAIL="${CLIENT_EMAIL:-montana-client}"
|
|||
|
|
STATE_DIR="/etc/montana-vpn"
|
|||
|
|
STATE_FILE="$STATE_DIR/state.env"
|
|||
|
|
XRAY_CONF_DIR="/usr/local/etc/xray"
|
|||
|
|
XRAY_CONF="$XRAY_CONF_DIR/config.json"
|
|||
|
|
XRAY_LOG_DIR="/var/log/xray"
|
|||
|
|
DECOY_ROOT="/var/www/decoy"
|
|||
|
|
NGINX_SITE="/etc/nginx/sites-available/decoy"
|
|||
|
|
SYSCTL_FILE="/etc/sysctl.d/99-montana-vpn.conf"
|
|||
|
|
|
|||
|
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|||
|
|
TEMPLATE_DIR="$SCRIPT_DIR/config-template"
|
|||
|
|
|
|||
|
|
log() { printf '\033[1;32m[montana-vpn]\033[0m %s\n' "$*"; }
|
|||
|
|
warn() { printf '\033[1;33m[montana-vpn]\033[0m %s\n' "$*" >&2; }
|
|||
|
|
die() { printf '\033[1;31m[montana-vpn] ОШИБКА:\033[0m %s\n' "$*" >&2; exit 1; }
|
|||
|
|
|
|||
|
|
[ "$(id -u)" = "0" ] || die "требуется sudo/root"
|
|||
|
|
[ -f /etc/os-release ] || die "/etc/os-release отсутствует"
|
|||
|
|
. /etc/os-release
|
|||
|
|
OS_ID="${ID:-unknown}"
|
|||
|
|
log "OS: ${PRETTY_NAME:-$OS_ID}"
|
|||
|
|
|
|||
|
|
[ -d "$TEMPLATE_DIR" ] || die "config-template/ не найден рядом со скриптом ($TEMPLATE_DIR)"
|
|||
|
|
|
|||
|
|
log "ставлю system deps..."
|
|||
|
|
case "$OS_ID" in
|
|||
|
|
ubuntu|debian)
|
|||
|
|
export DEBIAN_FRONTEND=noninteractive
|
|||
|
|
apt-get update -qq
|
|||
|
|
apt-get install -y -qq nginx ufw curl jq ca-certificates >/dev/null
|
|||
|
|
;;
|
|||
|
|
fedora|rhel|centos|rocky|almalinux)
|
|||
|
|
dnf install -y -q nginx firewalld curl jq ca-certificates >/dev/null
|
|||
|
|
;;
|
|||
|
|
alpine)
|
|||
|
|
apk add --no-cache nginx curl jq ca-certificates >/dev/null
|
|||
|
|
;;
|
|||
|
|
*)
|
|||
|
|
die "неподдерживаемый OS: $OS_ID"
|
|||
|
|
;;
|
|||
|
|
esac
|
|||
|
|
|
|||
|
|
if ! command -v xray >/dev/null 2>&1; then
|
|||
|
|
log "ставлю xray (официальный installer)..."
|
|||
|
|
bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install >/dev/null
|
|||
|
|
else
|
|||
|
|
log "xray уже установлен: $(xray version 2>&1 | head -1)"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
mkdir -p "$STATE_DIR" "$XRAY_CONF_DIR" "$XRAY_LOG_DIR" "$DECOY_ROOT"
|
|||
|
|
chmod 0700 "$STATE_DIR"
|
|||
|
|
|
|||
|
|
if [ -f "$STATE_FILE" ]; then
|
|||
|
|
log "переиспользую ключи из $STATE_FILE (удалите файл чтобы перегенерить)"
|
|||
|
|
# shellcheck disable=SC1090
|
|||
|
|
. "$STATE_FILE"
|
|||
|
|
else
|
|||
|
|
log "генерирую свежие Reality keys + UUID + shortId..."
|
|||
|
|
KEYS=$(xray x25519)
|
|||
|
|
REALITY_PRIVATE_KEY=$(echo "$KEYS" | awk '/Private key:|PrivateKey:/ {print $NF}')
|
|||
|
|
REALITY_PUBLIC_KEY=$(echo "$KEYS" | awk '/Public key:|Password:/ {print $NF}')
|
|||
|
|
CLIENT_UUID=$(xray uuid)
|
|||
|
|
REALITY_SHORT_ID=$(openssl rand -hex 8)
|
|||
|
|
cat > "$STATE_FILE" <<STATE
|
|||
|
|
DECOY_HOST="$DECOY_HOST"
|
|||
|
|
CLIENT_EMAIL="$CLIENT_EMAIL"
|
|||
|
|
CLIENT_UUID="$CLIENT_UUID"
|
|||
|
|
REALITY_PRIVATE_KEY="$REALITY_PRIVATE_KEY"
|
|||
|
|
REALITY_PUBLIC_KEY="$REALITY_PUBLIC_KEY"
|
|||
|
|
REALITY_SHORT_ID="$REALITY_SHORT_ID"
|
|||
|
|
STATE
|
|||
|
|
chmod 0600 "$STATE_FILE"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
log "генерирую xray config.json из шаблона..."
|
|||
|
|
sed \
|
|||
|
|
-e "s|{{CLIENT_UUID}}|$CLIENT_UUID|g" \
|
|||
|
|
-e "s|{{CLIENT_EMAIL}}|$CLIENT_EMAIL|g" \
|
|||
|
|
-e "s|{{DECOY_HOST}}|$DECOY_HOST|g" \
|
|||
|
|
-e "s|{{REALITY_PRIVATE_KEY}}|$REALITY_PRIVATE_KEY|g" \
|
|||
|
|
-e "s|{{REALITY_SHORT_ID}}|$REALITY_SHORT_ID|g" \
|
|||
|
|
"$TEMPLATE_DIR/xray-config.json.template" > "$XRAY_CONF"
|
|||
|
|
chmod 0644 "$XRAY_CONF"
|
|||
|
|
chown -R nobody:nogroup "$XRAY_LOG_DIR" 2>/dev/null || chown -R nobody:nobody "$XRAY_LOG_DIR" 2>/dev/null || true
|
|||
|
|
|
|||
|
|
log "ставлю xray.service + drop-in..."
|
|||
|
|
install -m 0644 "$TEMPLATE_DIR/xray.service" /etc/systemd/system/xray.service
|
|||
|
|
install -d -m 0755 /etc/systemd/system/xray.service.d
|
|||
|
|
install -m 0644 "$TEMPLATE_DIR/xray.service.d/10-donot_touch_single_conf.conf" /etc/systemd/system/xray.service.d/10-donot_touch_single_conf.conf
|
|||
|
|
|
|||
|
|
log "поднимаю nginx :80 decoy..."
|
|||
|
|
install -m 0644 "$TEMPLATE_DIR/decoy-index.html" "$DECOY_ROOT/index.html"
|
|||
|
|
install -m 0644 "$TEMPLATE_DIR/nginx-decoy.conf" "$NGINX_SITE"
|
|||
|
|
ln -sf "$NGINX_SITE" /etc/nginx/sites-enabled/decoy
|
|||
|
|
rm -f /etc/nginx/sites-enabled/default
|
|||
|
|
nginx -t >/dev/null 2>&1 || die "nginx config невалиден"
|
|||
|
|
systemctl enable nginx >/dev/null 2>&1 || true
|
|||
|
|
systemctl restart nginx
|
|||
|
|
|
|||
|
|
log "включаю BBR + fq_codel через sysctl..."
|
|||
|
|
install -m 0644 "$TEMPLATE_DIR/sysctl-bbr.conf" "$SYSCTL_FILE"
|
|||
|
|
sysctl -p "$SYSCTL_FILE" >/dev/null 2>&1 || true
|
|||
|
|
|
|||
|
|
if command -v ufw >/dev/null 2>&1; then
|
|||
|
|
log "настраиваю ufw (22, 80, 443)..."
|
|||
|
|
ufw allow 22/tcp comment 'SSH' >/dev/null 2>&1 || true
|
|||
|
|
ufw allow 80/tcp comment 'decoy nginx' >/dev/null 2>&1 || true
|
|||
|
|
ufw allow 443/tcp comment 'VLESS+TCP+Reality' >/dev/null 2>&1 || true
|
|||
|
|
ufw --force enable >/dev/null 2>&1 || true
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
log "запускаю xray..."
|
|||
|
|
systemctl daemon-reload
|
|||
|
|
systemctl enable xray.service >/dev/null 2>&1
|
|||
|
|
systemctl restart xray.service
|
|||
|
|
sleep 2
|
|||
|
|
|
|||
|
|
PUBLIC_IP=$(curl -s4 https://api.ipify.org 2>/dev/null || hostname -I | awk '{print $1}')
|
|||
|
|
|
|||
|
|
log ""
|
|||
|
|
log "================================================================"
|
|||
|
|
log " УСТАНОВКА VPN ЗАВЕРШЕНА"
|
|||
|
|
log "================================================================"
|
|||
|
|
log ""
|
|||
|
|
log "Server: $PUBLIC_IP:443"
|
|||
|
|
log "Decoy SNI: $DECOY_HOST"
|
|||
|
|
log "Client UUID: $CLIENT_UUID"
|
|||
|
|
log "Reality PK: $REALITY_PUBLIC_KEY"
|
|||
|
|
log "Reality SID: $REALITY_SHORT_ID"
|
|||
|
|
log "Flow: xtls-rprx-vision"
|
|||
|
|
log ""
|
|||
|
|
log "VLESS URL для клиента (импортировать в v2rayN/Hiddify/Streisand):"
|
|||
|
|
log ""
|
|||
|
|
echo "vless://${CLIENT_UUID}@${PUBLIC_IP}:443?encryption=none&flow=xtls-rprx-vision&security=reality&sni=${DECOY_HOST}&fp=chrome&pbk=${REALITY_PUBLIC_KEY}&sid=${REALITY_SHORT_ID}&type=tcp#montana-vpn"
|
|||
|
|
log ""
|
|||
|
|
log "Полные ключи и состояние: $STATE_FILE (mode 0600)"
|
|||
|
|
log ""
|
|||
|
|
log "Управление:"
|
|||
|
|
log " systemctl status xray # статус VPN"
|
|||
|
|
log " systemctl restart xray # перезапуск"
|
|||
|
|
log " journalctl -u xray -f # логи"
|
|||
|
|
log ""
|
|||
|
|
log "Узел Montana — отдельный слой; устанавливается scripts/install-vps.sh"
|
|||
|
|
log "(или scripts/install-vps-full.sh — узел + VPN одной командой)."
|