268 lines
9.5 KiB
Bash
Executable File
268 lines
9.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# sdm-xray — установка сервера stealth-туннеля на свежую Ubuntu VPS.
|
||
# Использование:
|
||
# bash <(curl -fsSL https://git.my-handbook.ru/ded_ikar/sdm-xray/raw/main/install.sh) <domain>
|
||
# — с доменом: Let's Encrypt-сертификат (A-запись домена должна указывать на IP VPS)
|
||
# bash <(curl -fsSL https://git.my-handbook.ru/ded_ikar/sdm-xray/raw/main/install.sh) --selfsigned
|
||
# — БЕЗ домена: self-signed сертификат, клиент доверяет по pinnedPeerCertSha256
|
||
# (pin печатается в клиентском конфиге; панель сама подставляет его в ссылки/конфиги)
|
||
set -euo pipefail
|
||
|
||
SELFSIGNED=0
|
||
if [ -z "${1:-}" ] || [ "$1" = "--selfsigned" ]; then
|
||
SELFSIGNED=1
|
||
DOMAIN=""
|
||
else
|
||
DOMAIN="$1"
|
||
fi
|
||
|
||
GITEA="https://git.my-handbook.ru/ded_ikar/sdm-xray"
|
||
VERSION="v0.2.2"
|
||
BIN="xray-stealth-server-linux-amd64"
|
||
PANEL_BIN="sdm-panel-linux-amd64"
|
||
INSTALL_DIR="/usr/local/xray"
|
||
DATA_DIR="/etc/xray"
|
||
WEBROOT="/var/www/html"
|
||
VPS_IP="$(curl -fsSL --max-time 10 https://api.ipify.org 2>/dev/null || echo UNKNOWN)"
|
||
|
||
if [ "$SELFSIGNED" -eq 1 ]; then
|
||
echo "=== sdm-xray installer ${VERSION} (SELF-SIGNED, без домена) ==="
|
||
echo "vps-ip: $VPS_IP (используй его как serverName/адрес в клиенте)"
|
||
else
|
||
echo "=== sdm-xray installer ${VERSION} ==="
|
||
echo "domain: $DOMAIN vps-ip: $VPS_IP"
|
||
fi
|
||
|
||
export DEBIAN_FRONTEND=noninteractive
|
||
|
||
echo "=== apt: deps ==="
|
||
apt-get update -qq
|
||
apt-get install -y -qq curl openssl jq nginx certbot >/dev/null 2>&1
|
||
|
||
echo "=== download server binary + geo files ==="
|
||
mkdir -p "$INSTALL_DIR" "$DATA_DIR" "$WEBROOT"
|
||
curl -fsSL -o "$INSTALL_DIR/xray" "$GITEA/releases/download/$VERSION/$BIN"
|
||
curl -fsSL -o "$INSTALL_DIR/geoip.dat" "$GITEA/releases/download/$VERSION/geoip.dat"
|
||
curl -fsSL -o "$INSTALL_DIR/geosite.dat" "$GITEA/releases/download/$VERSION/geosite.dat"
|
||
chmod 755 "$INSTALL_DIR/xray"
|
||
"$INSTALL_DIR/xray" version | sed -n '1p'
|
||
|
||
echo "=== generate secrets ==="
|
||
UUID="$(cat /proc/sys/kernel/random/uuid)"
|
||
KEY="$(openssl rand -hex 8)"
|
||
echo "$UUID" > "$DATA_DIR/uuid"
|
||
echo "$KEY" > "$DATA_DIR/key"
|
||
|
||
# --- TLS: либо LE-серт (домен), либо self-signed (без домена) ---
|
||
if [ "$SELFSIGNED" -eq 1 ]; then
|
||
echo "=== self-signed certificate (CN=$VPS_IP, 10 лет) ==="
|
||
openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
|
||
-subj "/CN=$VPS_IP" \
|
||
-keyout "$DATA_DIR/server.key" -out "$DATA_DIR/server.crt" >/dev/null 2>&1
|
||
CERT_FILE="$DATA_DIR/server.crt"
|
||
KEY_FILE="$DATA_DIR/server.key"
|
||
PIN="$(openssl x509 -in "$CERT_FILE" -outform der | openssl dgst -sha256 -hex | awk '{print $NF}')"
|
||
echo "pinnedPeerCertSha256: $PIN"
|
||
echo " (панель подставит его в клиентские конфиги/ссылки автоматически)"
|
||
else
|
||
echo "=== nginx on 80 (acme) ==="
|
||
mkdir -p "$WEBROOT/.well-known/acme-challenge"
|
||
cat > /etc/nginx/sites-available/sdm <<'NGX'
|
||
server {
|
||
listen 80;
|
||
listen [::]:80;
|
||
server_name DOMAIN;
|
||
root /var/www/html;
|
||
index index.html;
|
||
location /.well-known/acme-challenge/ {
|
||
alias /var/www/html/.well-known/acme-challenge/;
|
||
}
|
||
location / {
|
||
return 301 https://$host$request_uri;
|
||
}
|
||
}
|
||
NGX
|
||
sed -i "s/DOMAIN/$DOMAIN/" /etc/nginx/sites-available/sdm
|
||
ln -sf /etc/nginx/sites-available/sdm /etc/nginx/sites-enabled/sdm
|
||
rm -f /etc/nginx/sites-enabled/default
|
||
nginx -t >/dev/null 2>&1 && systemctl reload nginx
|
||
|
||
echo "=== Let's Encrypt cert ==="
|
||
certbot certonly --webroot -w "$WEBROOT" -d "$DOMAIN" \
|
||
--register-unsafely-without-email --agree-tos --no-eff-email -n \
|
||
--cert-name "$DOMAIN" >/dev/null 2>&1 || true
|
||
CERT_DIR="/etc/letsencrypt/live/$DOMAIN"
|
||
if [ ! -f "$CERT_DIR/fullchain.pem" ]; then
|
||
echo "ERROR: сертификат не получен. Проверьте, что $DOMAIN -> $VPS_IP и порт 80 открыт."
|
||
exit 1
|
||
fi
|
||
CERT_FILE="$CERT_DIR/fullchain.pem"
|
||
KEY_FILE="$CERT_DIR/privkey.pem"
|
||
fi
|
||
|
||
echo "=== xray config ==="
|
||
cat > "$DATA_DIR/config.json" <<EOF
|
||
{
|
||
"log": {"loglevel": "warning"},
|
||
"routing": {
|
||
"rules": [
|
||
{"port": "443", "network": "udp", "outboundTag": "block"},
|
||
{"ip": ["geoip:private"], "outboundTag": "block"}
|
||
]
|
||
},
|
||
"inbounds": [
|
||
{
|
||
"listen": "0.0.0.0",
|
||
"port": 443,
|
||
"protocol": "vless",
|
||
"settings": {"clients": [{"id": "$UUID", "level": 0}], "decryption": "none"},
|
||
"streamSettings": {
|
||
"network": "stealth",
|
||
"stealthSettings": {
|
||
"key": "$KEY", "padMax": 2048, "padProb": 50, "paceMs": 0, "paceJitterMs": 0,
|
||
"paths": ["/api/t04d12b46/", "/api/v2/telemetry/", "/collect/", "/ingest/"],
|
||
"ports": [443], "bps": 0, "target": "https://www.iana.org",
|
||
"padProfile": "browser", "retries": 3
|
||
},
|
||
"security": "tls",
|
||
"tlsSettings": {
|
||
"certificates": [
|
||
{"certificateFile": "$CERT_FILE", "keyFile": "$KEY_FILE"}
|
||
]
|
||
}
|
||
},
|
||
"sniffing": {"enabled": true, "destOverride": ["http","tls","quic"]}
|
||
}
|
||
],
|
||
"outbounds": [
|
||
{"protocol": "freedom", "tag": "direct"},
|
||
{"protocol": "blackhole", "tag": "block"}
|
||
]
|
||
}
|
||
EOF
|
||
|
||
echo "=== systemd ==="
|
||
cat > /etc/systemd/system/xray.service <<'UNIT'
|
||
[Unit]
|
||
Description=sdm-xray stealth tunnel
|
||
After=network.target nss-lookup.target
|
||
|
||
[Service]
|
||
ExecStart=/usr/local/xray/xray run -c /etc/xray/config.json
|
||
Restart=on-failure
|
||
RestartPreventExitStatus=23
|
||
LimitNOFILE=65535
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
UNIT
|
||
systemctl daemon-reload
|
||
systemctl enable xray >/dev/null 2>&1 || true
|
||
systemctl restart xray
|
||
sleep 1
|
||
if ! systemctl is-active xray >/dev/null 2>&1; then
|
||
echo "ERROR: xray не запустился:"; journalctl -u xray -n 20 --no-pager || true; exit 1
|
||
fi
|
||
|
||
echo
|
||
echo "=== INSTALLED OK ==="
|
||
echo "uuid: $UUID"
|
||
echo "key: $KEY"
|
||
|
||
# --- panel ---
|
||
echo
|
||
echo "=== control panel (sdm-panel) ==="
|
||
curl -fsSL -o "$INSTALL_DIR/sdm-panel" "$GITEA/releases/download/$VERSION/$PANEL_BIN"
|
||
chmod 755 "$INSTALL_DIR/sdm-panel"
|
||
PANEL_PASS="$(openssl rand -hex 8)"
|
||
PANEL_LOGIN="admin"
|
||
if [ "$SELFSIGNED" -eq 1 ]; then
|
||
PANEL_SERVER="$VPS_IP"
|
||
else
|
||
PANEL_SERVER="$DOMAIN"
|
||
fi
|
||
cat > "$DATA_DIR/panel.json" <<EOF
|
||
{
|
||
"bind": "127.0.0.1:8080",
|
||
"login": "$PANEL_LOGIN",
|
||
"password": "$PANEL_PASS",
|
||
"serverName": "$PANEL_SERVER"
|
||
}
|
||
EOF
|
||
chmod 600 "$DATA_DIR/panel.json"
|
||
cat > /etc/systemd/system/sdm-panel.service <<'PANEL'
|
||
[Unit]
|
||
Description=sdm-xray control panel
|
||
After=network.target xray.service
|
||
|
||
[Service]
|
||
ExecStart=/usr/local/xray/sdm-panel
|
||
Restart=on-failure
|
||
RestartSec=3
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
PANEL
|
||
systemctl daemon-reload
|
||
systemctl enable sdm-panel >/dev/null 2>&1 || true
|
||
systemctl restart sdm-panel
|
||
sleep 1
|
||
echo "panel login: $PANEL_LOGIN panel password: $PANEL_PASS (запомни — больше не покажем)"
|
||
echo "Доступ к панели (SSH-туннель, шифрованный):"
|
||
echo " ssh -L 8080:127.0.0.1:8080 root@<vps-ip>"
|
||
echo " затем открой в браузере http://localhost:8080 (login: $PANEL_LOGIN / пароль выше)"
|
||
|
||
echo
|
||
echo "=== client config (client-stealth.json) ==="
|
||
if [ "$SELFSIGNED" -eq 1 ]; then
|
||
cat <<CFG
|
||
{
|
||
"log": {"loglevel": "warning"},
|
||
"inbounds": [
|
||
{"listen": "127.0.0.1", "port": 10808, "protocol": "socks"},
|
||
{"listen": "127.0.0.1", "port": 10809, "protocol": "http"}
|
||
],
|
||
"outbounds": [
|
||
{
|
||
"protocol": "vless",
|
||
"settings": {"vnext": [{"address": "$VPS_IP", "port": 443, "users": [{"id": "$UUID", "encryption": "none"}]}]},
|
||
"streamSettings": {
|
||
"network": "stealth",
|
||
"stealthSettings": {"key": "$KEY", "padMax": 2048, "padProb": 50, "paceMs": 0, "paceJitterMs": 0, "paths": ["/api/t04d12b46/", "/api/v2/telemetry/", "/collect/", "/ingest/"], "ports": [443], "bps": 0},
|
||
"security": "tls",
|
||
"tlsSettings": {"serverName": "$VPS_IP", "fingerprint": "chrome", "pinnedPeerCertSha256": "$PIN"}
|
||
}
|
||
},
|
||
{"protocol": "freedom", "tag": "direct"}
|
||
]
|
||
}
|
||
CFG
|
||
else
|
||
cat <<CFG
|
||
{
|
||
"log": {"loglevel": "warning"},
|
||
"inbounds": [
|
||
{"listen": "127.0.0.1", "port": 10808, "protocol": "socks"},
|
||
{"listen": "127.0.0.1", "port": 10809, "protocol": "http"}
|
||
],
|
||
"outbounds": [
|
||
{
|
||
"protocol": "vless",
|
||
"settings": {"vnext": [{"address": "$DOMAIN", "port": 443, "users": [{"id": "$UUID", "encryption": "none"}]}]},
|
||
"streamSettings": {
|
||
"network": "stealth",
|
||
"stealthSettings": {"key": "$KEY", "padMax": 2048, "padProb": 50, "paceMs": 0, "paceJitterMs": 0, "paths": ["/api/t04d12b46/", "/api/v2/telemetry/", "/collect/", "/ingest/"], "ports": [443], "bps": 0},
|
||
"security": "tls",
|
||
"tlsSettings": {"serverName": "$DOMAIN", "fingerprint": "chrome"}
|
||
}
|
||
},
|
||
{"protocol": "freedom", "tag": "direct"}
|
||
]
|
||
}
|
||
CFG
|
||
fi
|
||
echo
|
||
echo "=== client binaries ==="
|
||
echo " Windows: $GITEA/releases/download/$VERSION/xray-stealth-client-windows-amd64.exe"
|
||
echo " Router: $GITEA/releases/download/$VERSION/xray-stealth-client-linux-arm64"
|
||
echo " Panel: $GITEA/releases/download/$VERSION/sdm-panel-linux-amd64" |