From d7252da3c70c45fba987480bd96e9f51da7315bf Mon Sep 17 00:00:00 2001 From: StepanovPlaton Date: Wed, 28 Oct 2026 18:36:47 +0400 Subject: [PATCH] Add xray --- .gitignore | 3 + README.md | 6 ++ Services.md | 83 ++++++++++++++++++++- proxy/ddns/domains.txt.example | 1 + xray/.env.example | 20 +++++ xray/config/config.json.example | 70 +++++++++++++++++ xray/docker-compose.yml | 27 +++++++ xray/setup.sh | 128 ++++++++++++++++++++++++++++++++ 8 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 xray/.env.example create mode 100644 xray/config/config.json.example create mode 100644 xray/docker-compose.yml create mode 100644 xray/setup.sh diff --git a/.gitignore b/.gitignore index a326a3b..9aa1b80 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,9 @@ gitea/config/* matrix/data/* matrix/db +xray/config/config.json +xray/config/*.tmp + cloud/db cloud/data cloud/config diff --git a/README.md b/README.md index 7a58081..771571e 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ - **BitTorrent клиент Transmission** с веб-интерфейсом - **SSH туннелирование через AutoSSH** - Можно пробросить порт на удалённый VPS, автоматически переподключается при потере соединения +- **VPN-сервер Xray (VLESS + Reality)** для безопасного доступа к домашней сети извне + - Трафик маскируется под обычный HTTPS, не требует отдельного домена и сертификата - **Nginx reverse proxy** для маршрутизации трафика к сервисам - Автоматическое получение и обновление SSL сертификатов через Let's Encrypt - Автоматическое обновление DNS записей у регистратора [рег.ру](https://www.reg.ru) @@ -121,3 +123,7 @@ #### NextCloud ![NextCloud Interface](./screenshots/nextcloud.png) + +### Xray VPN + +Сервис не имеет веб-интерфейса. После запуска `xray/setup.sh` выведет ссылку `vless://...` для подключения из клиента (v2rayN, NekoBox, Hiddify, sing-box и т.п.). Порт по умолчанию — `2053`. diff --git a/Services.md b/Services.md index 2374204..170b668 100644 --- a/Services.md +++ b/Services.md @@ -254,4 +254,85 @@ vim default.conf ```bash podman-compose -f init-compose.yml down podman-compose up -d --build -``` \ No newline at end of file +``` + +--- + +## 1️⃣3️⃣ Xray — VPN-доступ к домашней сети (VLESS + Reality) + +> VLESS + Reality маскирует трафик под обычный HTTPS. Отдельный домен и SSL-сертификат не нужны, а порт можно пробросить снаружи. Подходит для доступа к домашней сети и как личный VPN. + +```bash +cd xray +cp .env.example .env && vim .env +chmod +x setup.sh +./setup.sh +podman-compose up -d +``` + +`setup.sh` автоматически: + +- генерирует UUID, ключевую пару X25519 и Short ID; +- создаёт `config/config.json` из шаблона; +- проверяет конфигурацию через `xray run -test`; +- выводит готовую ссылку `vless://...` для клиента. + +> ⚠️ Файлы `.env` и `config/config.json` содержат секреты и исключены из Git. Ссылка подключения тоже является секретом — не публикуйте её. + +> После изменения `XRAY_PORT`, `XRAY_DEST` или `XRAY_SERVER_NAME` в `.env` запустите `./setup.sh` заново — он пересоберёт `config/config.json` и проверит его. Секреты при этом сохраняются. + +### Проброс порта + +В OPNsense: **Межсетевой экран > NAT > Переадресация портов** + +- **Interface:** WAN +- **Protocol:** TCP +- **Destination:** WAN address +- **Destination port range:** `2053` (или ваш `XRAY_PORT`) +- **Redirect target IP:** IP вашего сервера с Podman +- **Redirect target port:** `2053` (или ваш `XRAY_PORT`) +- **Filter rule association:** Add associated filter rule + +> В rootless Podman нельзя слушать порты ниже 1024, поэтому по умолчанию используется `2053`. Не занимайте `8443` — он уже используется nginx-proxy. При необходимости внешний порт можно сделать другим, но тогда клиент должен подключаться именно к внешнему порту. + +> Предупреждение Xray о порте не-443 и выбранном `dest` относится к блокировкам GFW (Китай). Для домашнего сервера в России это не критично, но `dest` лучше выбирать из доступных вам крупных сайтов с TLS 1.3. + +Если нужен стабильный адрес вместо IP, добавьте поддомен `vpn` в `proxy/ddns/domains.txt` и укажите в `.env`: + +```ini +XRAY_PUBLIC_HOST=vpn.domain.ru +``` + +### Доступ к домашней сети + +Сервер Xray не навязывает маршруты клиенту — маршрутизацию настраиваем в клиенте: + +- **Только домашняя сеть:** оставьте интернет напрямую, а подсеть `192.168.0.0/16` (или `geoip:private`) направьте в proxy. +- **Полный туннель:** весь трафик (`0.0.0.0/0`) направьте в proxy. + +Для доступа по именам добавьте локальные записи в клиент или используйте DNS домашней сети (например, AdGuard/OPNsense), направив DNS-запросы через туннель. + +### Добавление устройства + +Для каждого устройства лучше использовать отдельный UUID. Добавьте свой объект в `clients` внутри `config/config.json`: + +```json +{ + "id": "новый-uuid", + "flow": "xtls-rprx-vision" +} +``` + +После изменения перезапустите контейнер: + +```bash +podman-compose restart xray +``` + +Сгенерировать новый UUID можно командой: + +```bash +podman run --rm ghcr.io/xtls/xray-core:latest uuid +``` + +Ссылка для нового устройства строится по той же схеме, что вывел `setup.sh`: меняется только UUID в начале ссылки. \ No newline at end of file diff --git a/proxy/ddns/domains.txt.example b/proxy/ddns/domains.txt.example index 1c683b4..c6b971d 100644 --- a/proxy/ddns/domains.txt.example +++ b/proxy/ddns/domains.txt.example @@ -7,3 +7,4 @@ matrix chat todo download +vpn diff --git a/xray/.env.example b/xray/.env.example new file mode 100644 index 0000000..3ebb8b0 --- /dev/null +++ b/xray/.env.example @@ -0,0 +1,20 @@ +# Порт Xray на хосте и в контейнере (для rootless Podman должен быть > 1024). +# По умолчанию 2053, потому что 443/8443 уже заняты nginx-proxy. +XRAY_PORT=2053 + +# Уровень логирования Xray: debug, info, warning, error +XRAY_LOG_LEVEL=warning + +# Маскировочный сайт (dest) и SNI. Должны совпадать. +# Выбирайте крупный сайт с TLS 1.3, доступный с вашего сервера. +XRAY_DEST=www.cloudflare.com:443 +XRAY_SERVER_NAME=www.cloudflare.com + +# Внешний адрес сервера для ссылки подключения: белый IP или домен (без https:// и без порта) +XRAY_PUBLIC_HOST=vpn.example.com + +# Генерируются автоматически командой ./setup.sh — не редактируйте вручную +XRAY_UUID= +XRAY_PRIVATE_KEY= +XRAY_PUBLIC_KEY= +XRAY_SHORT_ID= diff --git a/xray/config/config.json.example b/xray/config/config.json.example new file mode 100644 index 0000000..9b57371 --- /dev/null +++ b/xray/config/config.json.example @@ -0,0 +1,70 @@ +{ + "log": { + "loglevel": "__LOG_LEVEL__", + "access": "none" + }, + "inbounds": [ + { + "tag": "vless-reality-in", + "listen": "0.0.0.0", + "port": __PORT__, + "protocol": "vless", + "settings": { + "clients": [ + { + "id": "__UUID__", + "flow": "xtls-rprx-vision" + } + ], + "decryption": "none" + }, + "streamSettings": { + "network": "tcp", + "security": "reality", + "realitySettings": { + "show": false, + "dest": "__DEST__", + "xver": 0, + "serverNames": [ + "__SERVER_NAME__" + ], + "privateKey": "__PRIVATE_KEY__", + "shortIds": [ + "__SHORT_ID__" + ] + } + }, + "sniffing": { + "enabled": true, + "destOverride": [ + "http", + "tls", + "quic" + ], + "routeOnly": true + } + } + ], + "outbounds": [ + { + "tag": "direct", + "protocol": "freedom" + }, + { + "tag": "block", + "protocol": "blackhole" + } + ], + "routing": { + "domainStrategy": "AsIs", + "rules": [ + { + "type": "field", + "ip": [ + "geoip:private" + ], + "outboundTag": "direct" + } + ] + } +} diff --git a/xray/docker-compose.yml b/xray/docker-compose.yml new file mode 100644 index 0000000..2e0d8c4 --- /dev/null +++ b/xray/docker-compose.yml @@ -0,0 +1,27 @@ +services: + xray: + image: ghcr.io/xtls/xray-core:latest + container_name: xray + restart: always + ports: + # Порт должен совпадать с XRAY_PORT в config/config.json (генерируется setup.sh) + - "${XRAY_PORT:?}:${XRAY_PORT:?}/tcp" + volumes: + # Монтируем каталог целиком, чтобы не использовать конфиги по умолчанию из образа + - ./config:/usr/local/etc/xray:ro,Z + environment: + - TZ=${TZ:-Europe/Samara} + deploy: + resources: + limits: + cpus: '0.5' + memory: 128M + reservations: + memory: 64M + networks: + - xray_network + +networks: + xray_network: + name: xray_network + driver: bridge diff --git a/xray/setup.sh b/xray/setup.sh new file mode 100644 index 0000000..05dcc29 --- /dev/null +++ b/xray/setup.sh @@ -0,0 +1,128 @@ +#!/bin/sh +set -eu + +cd "$(dirname "$0")" + +ENGINE="${CONTAINER_ENGINE:-podman}" +IMAGE="${XRAY_IMAGE:-ghcr.io/xtls/xray-core:latest}" + +if ! command -v "$ENGINE" >/dev/null 2>&1; then + echo "Ошибка: не найден $ENGINE. Установите Podman или задайте CONTAINER_ENGINE=docker." >&2 + exit 1 +fi + +if [ ! -f .env ]; then + cp .env.example .env + echo "Создан .env из .env.example" +fi + +# Убираем CRLF, если файл редактировали в Windows +sed -i 's/\r$//' .env 2>/dev/null || true + +# shellcheck disable=SC1091 +. ./.env + +: "${XRAY_PORT:?Укажите XRAY_PORT в .env}" +: "${XRAY_DEST:?Укажите XRAY_DEST в .env}" +: "${XRAY_SERVER_NAME:?Укажите XRAY_SERVER_NAME в .env}" + +LOG_LEVEL="${XRAY_LOG_LEVEL:-warning}" +PUBLIC_HOST="${XRAY_PUBLIC_HOST:-YOUR_SERVER_IP}" + +set_env() { + key="$1" + value="$2" + awk -v k="$key" -v v="$value" ' + BEGIN { FS = "="; OFS = "=" } + $1 == k { print k, v; next } + { print } + ' .env > .env.tmp + mv .env.tmp .env +} + +gen_uuid() { + out="$("$ENGINE" run --rm "$IMAGE" uuid)" || return 1 + printf '%s' "$out" | tr -d '\r\n' +} + +gen_x25519() { + out="$("$ENGINE" run --rm "$IMAGE" x25519)" || return 1 + printf '%s' "$out" | tr -d '\r' +} + +gen_short_id() { + if command -v openssl >/dev/null 2>&1; then + openssl rand -hex 8 | tr -d '\r\n' + else + od -An -N8 -tx1 /dev/urandom | tr -d ' \n' + fi +} + +if [ -z "${XRAY_UUID:-}" ]; then + echo "Генерация UUID..." + XRAY_UUID="$(gen_uuid)" + set_env XRAY_UUID "$XRAY_UUID" +fi + +if [ -z "${XRAY_PRIVATE_KEY:-}" ]; then + echo "Генерация ключевой пары X25519..." + KEYS="$(gen_x25519)" + XRAY_PRIVATE_KEY="$(printf '%s\n' "$KEYS" | sed -n -E 's/^(PrivateKey|Private key):[[:space:]]*//p' | head -n 1)" + XRAY_PUBLIC_KEY="$(printf '%s\n' "$KEYS" | sed -n -E 's/^(Password \(PublicKey\)|PublicKey|Public key):[[:space:]]*//p' | head -n 1)" + if [ -z "$XRAY_PRIVATE_KEY" ] || [ -z "$XRAY_PUBLIC_KEY" ]; then + echo "Не удалось разобрать вывод xray x25519:" >&2 + printf '%s\n' "$KEYS" >&2 + exit 1 + fi + set_env XRAY_PRIVATE_KEY "$XRAY_PRIVATE_KEY" + set_env XRAY_PUBLIC_KEY "$XRAY_PUBLIC_KEY" +elif [ -z "${XRAY_PUBLIC_KEY:-}" ]; then + echo "Вычисление публичного ключа..." + KEYS="$("$ENGINE" run --rm "$IMAGE" x25519 -i "$XRAY_PRIVATE_KEY")" || { + echo "Не удалось вычислить публичный ключ из XRAY_PRIVATE_KEY." >&2 + exit 1 + } + KEYS="$(printf '%s' "$KEYS" | tr -d '\r')" + XRAY_PUBLIC_KEY="$(printf '%s\n' "$KEYS" | sed -n -E 's/^(Password \(PublicKey\)|PublicKey|Public key):[[:space:]]*//p' | head -n 1)" + if [ -z "$XRAY_PUBLIC_KEY" ]; then + echo "Не удалось вычислить публичный ключ из XRAY_PRIVATE_KEY." >&2 + exit 1 + fi + set_env XRAY_PUBLIC_KEY "$XRAY_PUBLIC_KEY" +fi + +if [ -z "${XRAY_SHORT_ID:-}" ]; then + echo "Генерация Short ID..." + XRAY_SHORT_ID="$(gen_short_id)" + set_env XRAY_SHORT_ID "$XRAY_SHORT_ID" +fi + +mkdir -p config +sed \ + -e "s|__LOG_LEVEL__|$LOG_LEVEL|g" \ + -e "s|__PORT__|$XRAY_PORT|g" \ + -e "s|__UUID__|$XRAY_UUID|g" \ + -e "s|__DEST__|$XRAY_DEST|g" \ + -e "s|__SERVER_NAME__|$XRAY_SERVER_NAME|g" \ + -e "s|__PRIVATE_KEY__|$XRAY_PRIVATE_KEY|g" \ + -e "s|__SHORT_ID__|$XRAY_SHORT_ID|g" \ + config/config.json.example > config/config.json.tmp +mv config/config.json.tmp config/config.json + +echo "Проверка конфигурации..." +"$ENGINE" run --rm -v "$(pwd)/config:/usr/local/etc/xray:ro,Z" "$IMAGE" run -test -confdir /usr/local/etc/xray + +LINK="vless://${XRAY_UUID}@${PUBLIC_HOST}:${XRAY_PORT}?encryption=none&flow=xtls-rprx-vision&security=reality&sni=${XRAY_SERVER_NAME}&fp=chrome&pbk=${XRAY_PUBLIC_KEY}&sid=${XRAY_SHORT_ID}&type=tcp&headerType=none&spx=%2F#Xray-Home" + +cat <