diff --git a/README.md b/README.md index beaf54e..0648f3e 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,192 @@ # Raven Server Install -Ansible role that installs and configures [Xray-core](https://github.com/XTLS/Xray-core) with [Raven-subscribe](https://github.com/alchemylink/raven-subscribe) on a VPS. +Languages: **English** | [Русский](README.ru.md) + +[![CI](https://github.com/AlchemyLink/Raven-server-install/actions/workflows/xray-config-test.yml/badge.svg)](https://github.com/AlchemyLink/Raven-server-install/actions/workflows/xray-config-test.yml) +[![License: MPL 2.0](https://img.shields.io/badge/License-MPL_2.0-brightgreen.svg)](LICENSE) + +Ansible playbooks for deploying a self-hosted VPN server stack based on [Xray-core](https://github.com/XTLS/Xray-core) and [Raven-subscribe](https://github.com/AlchemyLink/Raven-subscribe). **What you get:** -- Xray with VLESS + XTLS-Reality and XHTTP inbounds -- Optional VLESS Encryption (post-quantum, mlkem768x25519plus) -- Raven-subscribe — subscription server for client config distribution -- nginx TLS frontend on EU server (`nginx_frontend` role) -- nginx relay on RU server with TCP stream proxy for VLESS Reality (`relay` role) -- Systemd services with auto-restart and config validation before reload -- Ad/tracker blocking via geosite routing rules -- BBR congestion control and kernel tuning via `srv_prepare` role + +- Xray-core with VLESS + XTLS-Reality and VLESS + XHTTP inbounds +- Optional post-quantum VLESS Encryption (mlkem768x25519plus) +- Optional Hysteria2 via [sing-box](https://github.com/SagerNet/sing-box) +- [Raven-subscribe](https://github.com/AlchemyLink/Raven-subscribe) — subscription server: auto-discovers users, serves client configs via personal URLs +- nginx TLS frontend on EU VPS (`nginx_frontend` role) +- nginx relay + TCP stream proxy on RU VPS for routing through a second server (`relay` role) +- Systemd services with config validation before every reload +- Ad and tracker blocking via geosite routing rules +- BBR congestion control and sysctl tuning (`srv_prepare` role) + +--- + +## Table of Contents + +- [Architecture](#architecture) +- [Requirements](#requirements) +- [Quick Start](#quick-start) +- [Role Reference](#role-reference) +- [Secrets](#secrets) +- [Configuration](#configuration) +- [DNS Setup](#dns-setup) +- [VLESS Encryption (optional)](#vless-encryption-optional) +- [Hysteria2 / sing-box (optional)](#hysteria2--sing-box-optional) +- [Testing](#testing) +- [Related Projects](#related-projects) +- [License](#license) + +--- + +## Architecture + +This repo supports two deployment topologies: + +### Single-server (minimal) + +One VPS running Xray + Raven-subscribe + nginx frontend. + +``` +Client ──VLESS+Reality──► VPS:443 (Xray) +Client ──VLESS+XHTTP────► VPS:443 (nginx) ──► VPS:2053 (Xray) +Client ──subscription───► VPS:443 (nginx) ──► VPS:8080 (Raven) +``` + +### Dual-server with RU relay (recommended for CIS users) + +EU VPS runs Xray + nginx_frontend + Raven-subscribe. +RU VPS runs a relay that hides the EU IP from clients. + +``` +EU VPS (media.example.com) RU VPS (example.com) +┌───────────────────────────┐ ┌─────────────────────────────┐ +│ Xray :443 TCP │ │ nginx relay │ +│ nginx XHTTP :443 HTTPS │◄─────│ my.example.com → EU:8443 │ +│ nginx stream:8445 TCP │◄─────│ :8444 TCP → EU:8445 TCP │ +│ Raven :8080 local │ └─────────────────────────────┘ +│ nginx front :8443 HTTPS │ ▲ +└───────────────────────────┘ │ + clients +``` + +**Client connection flow:** +``` +VLESS Reality: client → RU:8444 (TCP relay) → EU:8445 (nginx stream) → Xray:443 +VLESS XHTTP: client → EU:443 (nginx HTTPS) → Xray:2053 +Subscription: client → my.example.com (RU relay) → EU:8443 → Raven:8080 +``` + +### Role map + +| Role | VPS | Playbook | What it does | +|------|-----|----------|--------------| +| `srv_prepare` | EU | `role_xray.yml` | BBR, sysctl, system user | +| `xray` | EU | `role_xray.yml` | Xray binary + split config in `/etc/xray/config.d/` | +| `raven_subscribe` | EU | `role_raven_subscribe.yml` | Subscription server, gRPC sync with Xray | +| `nginx_frontend` | EU | `role_nginx_frontend.yml` | nginx TLS proxy + TCP stream relay (port 8443/8445) | +| `sing-box-playbook` | EU | `role_sing-box.yml` | sing-box + Hysteria2 (optional) | +| `relay` | RU | `role_relay.yml` | nginx reverse proxy + TCP stream relay (port 8444) | + +--- ## Requirements -- Ansible >= 2.14 (ansible-core) -- Target: Debian/Ubuntu VPS with systemd -- `ansible-vault` for secrets management +- **Ansible** >= 2.14 (`ansible-core`) +- **Target OS**: Debian/Ubuntu with systemd +- **Python 3** on the target server +- **ansible-vault** for secrets management +- **Docker** (optional, for local config validation tests) + +--- ## Quick Start -### 1. Inventory +### 1. Clone + +```bash +git clone https://github.com/AlchemyLink/Raven-server-install.git +cd Raven-server-install +``` + +### 2. Create inventory + +For the **xray** and **raven_subscribe** roles, edit `roles/hosts.yml.example` (copy to `roles/hosts.yml`): -Edit `roles/hosts.yml` and point `vm_my_srv` at your server. +```yaml +all: + children: + cloud: + hosts: + vm_my_srv: + ansible_host: "EU_VPS_IP" + ansible_port: 22 + vars: + ansible_user: deploy + ansible_python_interpreter: /usr/bin/python3 + ansible_ssh_private_key_file: ~/.ssh/id_ed25519 +``` -### 2. Secrets +For **nginx_frontend** and **relay** roles, edit their respective `inventory.ini` files: + +```ini +# roles/nginx_frontend/inventory.ini +[eu] +vpn ansible_host=EU_VPS_IP ansible_user=deploy + +# roles/relay/inventory.ini +[relay] +relay ansible_host=RU_VPS_IP ansible_user=deploy +``` -Create and encrypt secrets files for each role you deploy: +### 3. Create secrets files + +Each role has a `defaults/secrets.yml.example`. Copy and fill in the values, then encrypt: ```bash -# Xray role (Reality keys, users) +# Xray cp roles/xray/defaults/secrets.yml.example roles/xray/defaults/secrets.yml +# edit roles/xray/defaults/secrets.yml ansible-vault encrypt roles/xray/defaults/secrets.yml --vault-password-file vault_password.txt -# Raven-subscribe role (admin token, server host, per-inbound overrides) +# Raven-subscribe cp roles/raven_subscribe/defaults/secrets.yml.example roles/raven_subscribe/defaults/secrets.yml +# edit roles/raven_subscribe/defaults/secrets.yml ansible-vault encrypt roles/raven_subscribe/defaults/secrets.yml --vault-password-file vault_password.txt -# nginx_frontend role (certbot email) — EU server +# nginx_frontend (EU VPS) cp roles/nginx_frontend/defaults/secrets.yml.example roles/nginx_frontend/defaults/secrets.yml +# edit roles/nginx_frontend/defaults/secrets.yml ansible-vault encrypt roles/nginx_frontend/defaults/secrets.yml --vault-password-file vault_password.txt -# relay role (upstream EU IP, certbot email) — RU server +# relay (RU VPS) cp roles/relay/defaults/secrets.yml.example roles/relay/defaults/secrets.yml +# edit roles/relay/defaults/secrets.yml ansible-vault encrypt roles/relay/defaults/secrets.yml --vault-password-file vault_password.txt ``` -To edit later: +To edit an encrypted file later: ```bash ansible-vault edit roles/xray/defaults/secrets.yml --vault-password-file vault_password.txt ``` -### 3. Generate Reality keys +### 4. Generate Reality keys ```bash # On any machine with Xray installed: xray x25519 -# Output: PrivateKey + PublicKey — put both into secrets.yml +# Output: PrivateKey + PublicKey — put both into roles/xray/defaults/secrets.yml + +openssl rand -hex 8 # generates a short_id ``` -### 4. Deploy +### 5. Deploy ```bash -# EU server: Xray +# EU server: Xray + system preparation ansible-playbook roles/role_xray.yml -i roles/hosts.yml --vault-password-file vault_password.txt -# EU server: nginx TLS frontend +# EU server: nginx TLS frontend + TCP stream relay ansible-playbook roles/role_nginx_frontend.yml -i roles/nginx_frontend/inventory.ini --vault-password-file vault_password.txt # EU server: Raven-subscribe @@ -76,24 +196,101 @@ ansible-playbook roles/role_raven_subscribe.yml -i roles/hosts.yml --vault-passw ansible-playbook roles/role_relay.yml -i roles/relay/inventory.ini --vault-password-file vault_password.txt ``` -Deploy only a specific component using tags: +Use `--tags` to deploy only a specific part: ```bash -# Update inbound configs only -ansible-playbook roles/role_xray.yml -i roles/hosts.yml --vault-password-file vault_password.txt --tags xray_inbounds +ansible-playbook roles/role_xray.yml -i roles/hosts.yml --vault-password-file vault_password.txt \ + --tags xray_inbounds ``` +--- + +## Role Reference + +### `xray` role + +Installs and configures Xray-core. Config is split across numbered JSON files in `/etc/xray/config.d/` — Xray loads them in order. + +**Task files and tags:** + +| Tag | File | What it does | +|-----|------|--------------| +| `always` | `validate.yml` | Pre-flight assertions — runs before everything | +| `xray_install` | `install.yml` | Downloads Xray binary from GitHub releases | +| `xray_base` | `base.yml` | Writes `000-log.json`, `010-stats.json` | +| `xray_api` | `api.yml` | Writes `050-api.json` (dokodemo-door on 127.0.0.1:10085) | +| `xray_inbounds` | `inbounds.yml` | Writes `200-in-vless-reality.json`, `210-in-xhttp.json` | +| `xray_dns` | `dns.yml` | Writes `100-dns.json` | +| `xray_outbounds` | `outbounds.yml` | Writes `300-outbounds.json` | +| `xray_routing` | `routing.yml` | Writes `400-routing.json` | +| `xray_service` | `service.yml` | Deploys systemd unit, enables service | +| `grpcurl` | `grpcurl.yml` | Installs grpcurl tool | + +**Config files layout:** + +| File | Content | +|------|---------| +| `000-log.json` | Log levels, file paths | +| `010-stats.json` | Traffic statistics | +| `050-api.json` | gRPC API (127.0.0.1:10085) | +| `100-dns.json` | DNS servers and query strategy | +| `200-in-vless-reality.json` | VLESS + XTLS-Reality inbound (TCP :443) | +| `210-in-xhttp.json` | VLESS + XHTTP inbound (:2053) | +| `300-outbounds.json` | Freedom + blackhole outbounds | +| `400-routing.json` | Routing rules + ad blocking | + +**Handler safety:** `Validate xray` must be defined before `Restart xray` in `handlers/main.yml`. Ansible executes handlers in definition order — this ensures an invalid config never triggers a restart. + +--- + +### `raven_subscribe` role + +Deploys [Raven-subscribe](https://github.com/AlchemyLink/Raven-subscribe) — a Go service that auto-discovers Xray users, syncs them via gRPC API, and serves personal subscription URLs. + +Listens on `127.0.0.1:8080`, proxied by nginx_frontend. + +--- + +### `nginx_frontend` role + +Deploys nginx on the EU VPS as a TLS reverse proxy. Responsibilities: + +- Obtains Let's Encrypt certificate for `nginx_frontend_domain` +- Listens on port **8443** (port 443 is taken by Xray VLESS Reality) +- Proxies XHTTP path → Xray `:2053` +- Proxies subscription/API paths → Raven-subscribe `:8080` +- **TCP stream relay**: port 8445 → `127.0.0.1:443` (passes VLESS Reality through nginx) + +--- + +### `relay` role + +Deploys nginx on the RU VPS as a relay. Responsibilities: + +- Obtains Let's Encrypt certificates for `relay_domain` and `relay_sub_my` +- Serves a static stub site on `relay_domain` (camouflage) +- Proxies `my.relay_domain` → EU VPS nginx_frontend `:8443` (Raven-subscribe) +- **TCP stream relay**: port 8444 → EU VPS `:8445` (VLESS Reality passthrough) + +--- + +### `sing-box-playbook` role + +Optional. Deploys [sing-box](https://github.com/SagerNet/sing-box) with a Hysteria2 inbound. When deployed, Raven-subscribe automatically discovers Hysteria2 users and includes them in subscriptions. + +--- + ## Secrets -Each role has its own `defaults/secrets.yml` (ansible-vault encrypted). +Each role keeps secrets in `defaults/secrets.yml` (ansible-vault encrypted, not committed). Copy from the `.example` file. -**`roles/xray/defaults/secrets.yml`** — Reality keys and VLESS users: +### `roles/xray/defaults/secrets.yml` ```yaml # Reality keys — generate with: xray x25519 xray_reality: - private_key: "..." - public_key: "..." + private_key: "YOUR_PRIVATE_KEY" + public_key: "YOUR_PUBLIC_KEY" spiderX: "/" short_id: - "a1b2c3d4e5f67890" # 8-byte hex — generate: openssl rand -hex 8 @@ -102,163 +299,208 @@ xray_reality: xray_users: - id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # UUID — generate: uuidgen flow: "xtls-rprx-vision" - email: "user@example.com" + email: "alice@example.com" ``` -**`roles/raven_subscribe/defaults/secrets.yml`** — Raven-subscribe settings: +### `roles/raven_subscribe/defaults/secrets.yml` ```yaml -raven_subscribe_admin_token: "" # openssl rand -hex 32 -raven_subscribe_server_host: "media.zirgate.com" -raven_subscribe_base_url: "https://media.zirgate.com" +# Admin token for Raven API — generate: openssl rand -hex 32 +raven_subscribe_admin_token: "YOUR_ADMIN_TOKEN" + +# Public URL used in subscription links +raven_subscribe_base_url: "https://my.example.com" + +# EU VPS public domain or IP +raven_subscribe_server_host: "media.example.com" # Per-inbound host/port overrides (optional) -# Useful when clients should connect through a relay instead of the EU server directly +# Routes different protocols through different addresses in client configs. +# Useful when clients connect via relay for some protocols. raven_subscribe_inbound_hosts: - vless-reality-in: "media.zirgate.com" - vless-xhttp-in: "media.zirgate.com" + vless-reality-in: "example.com" # RU relay domain for Reality + vless-xhttp-in: "media.example.com" raven_subscribe_inbound_ports: - vless-reality-in: 8445 # nginx stream relay port on EU server + vless-reality-in: 8444 # RU relay TCP port for Reality +``` + +### `roles/nginx_frontend/defaults/secrets.yml` + +```yaml +nginx_frontend_certbot_email: "admin@example.com" ``` -**`roles/relay/defaults/secrets.yml`** — RU server relay: +### `roles/relay/defaults/secrets.yml` ```yaml -relay_upstream_host: "1.2.3.4" # EU server IP +relay_upstream_host: "EU_VPS_IP" # EU server IP address relay_certbot_email: "admin@example.com" ``` +### `roles/sing-box-playbook/defaults/secrets.yml` + +```yaml +singbox_hysteria2_users: + - name: "alice@example.com" + password: "STRONG_RANDOM_PASSWORD" + +singbox: + tls_server_name: "media.example.com" + tls_acme_domain: "media.example.com" + tls_acme_email: "admin@example.com" +``` + +--- + ## Configuration -Key variables in `roles/xray/defaults/main.yml`: +### Xray (`roles/xray/defaults/main.yml`) | Variable | Default | Description | |----------|---------|-------------| | `xray_vless_port` | `443` | VLESS + Reality listen port | -| `xray_reality_dest` | `askubuntu.com:443` | Reality camouflage destination | -| `xray_reality_server_names` | `["askubuntu.com"]` | SNI names for Reality | +| `xray_reality_dest` | `askubuntu.com:443` | Reality camouflage destination (must be a real TLS site) | +| `xray_reality_server_names` | `["askubuntu.com"]` | SNI server names for Reality | | `xray_xhttp.port` | `2053` | XHTTP inbound port | -| `xray_dns_servers` | `tcp+local://8.8.8.8, ...` | DNS servers (no DoH — see note below) | -| `xray_dns_query_strategy` | `UseIPv4` | DNS query strategy — use `UseIPv4` if the server has no IPv6 | -| `xray_vless_decryption` | `"none"` | VLESS Encryption (optional, see below) | +| `xray_xhttp.xhttpSettings.path` | `/api/v3/data-sync` | XHTTP path (must match nginx_frontend) | +| `xray_dns_servers` | `tcp+local://8.8.8.8, ...` | DNS servers — do not use DoH (`https://`) | +| `xray_dns_query_strategy` | `UseIPv4` | `UseIPv4` if the server has no IPv6, `UseIP` otherwise | +| `xray_vless_decryption` | `"none"` | VLESS Encryption mode — see [VLESS Encryption](#vless-encryption-optional) | +| `xray_blocked_domains` | `[]` | Extra domains to block via routing rules | -Key variables in `roles/raven_subscribe/defaults/main.yml`: +### Raven-subscribe (`roles/raven_subscribe/defaults/main.yml`) | Variable | Default | Description | |----------|---------|-------------| -| `raven_subscribe_listen_addr` | `:8080` | Raven-subscribe listen address | -| `raven_subscribe_sync_interval_seconds` | `60` | User sync interval | +| `raven_subscribe_listen_addr` | `:8080` | Listen address | +| `raven_subscribe_sync_interval_seconds` | `60` | Xray config rescan interval | +| `raven_subscribe_api_inbound_tag` | `vless-reality-in` | Default inbound tag for API-created users | +| `raven_subscribe_xray_api_addr` | `127.0.0.1:10085` | Xray gRPC API address | | `raven_subscribe_inbound_hosts` | `{}` | Per-inbound host overrides (set in secrets.yml) | | `raven_subscribe_inbound_ports` | `{}` | Per-inbound port overrides (set in secrets.yml) | +| `raven_subscribe_singbox_enabled` | `false` | Enable sing-box/Hysteria2 sync | -> **DNS note:** Do not use `https://` (DoH) in `xray_dns_servers` — DoH queries route through the proxy and fail. Use `tcp+local://` instead. +### nginx_frontend (`roles/nginx_frontend/defaults/main.yml`) -## Architecture +| Variable | Default | Description | +|----------|---------|-------------| +| `nginx_frontend_domain` | `media.example.com` | EU VPS domain — set to your domain | +| `nginx_frontend_listen_port` | `8443` | nginx HTTPS listen port (not 443 — taken by Xray) | +| `nginx_frontend_xhttp_port` | `2053` | Xray XHTTP upstream port | +| `nginx_frontend_xhttp_path` | `/api/v3/data-sync` | XHTTP path (must match xray config) | +| `nginx_frontend_reality_port` | `8445` | TCP stream relay port for Reality | -``` -EU server - role_xray.yml - └── srv_prepare — system packages, BBR, sysctl tuning - └── xray — Xray binary + config - ├── validate.yml (always) — pre-flight assertions - ├── install.yml (xray_install) — download Xray binary - ├── base.yml (xray_base) — log + stats config - ├── api.yml (xray_api) — gRPC API on 127.0.0.1:10085 - ├── inbounds.yml (xray_inbounds) — VLESS+Reality, XHTTP - ├── dns.yml (xray_dns) — DNS config - ├── outbounds.yml (xray_outbounds) — direct + block outbounds - ├── routing.yml (xray_routing) — routing rules + ad blocking - ├── service.yml (xray_service) — systemd unit - └── grpcurl.yml (grpcurl) — installs grpcurl tool - - role_nginx_frontend.yml - └── nginx_frontend — nginx TLS proxy on media.zirgate.com - ├── listens on port 8443 (not 443, reserved by Xray Reality) - ├── proxies /sub/* → Raven-subscribe :8080 - └── stream TCP relay: port 8445 → 127.0.0.1:443 (Xray Reality) - - role_raven_subscribe.yml - └── raven_subscribe — subscription server - ├── listens on 127.0.0.1:8080 - ├── syncs users to Xray via gRPC API - └── serves client configs with per-inbound host/port overrides - -RU server - role_relay.yml - └── relay — nginx reverse proxy on zirgate.com - ├── my.zirgate.com → https://media.zirgate.com:8443 (Raven) - └── stream TCP relay: port 8444 → media.zirgate.com:8445 (Reality) -``` +### relay (`roles/relay/defaults/main.yml`) -Client connection flow: -``` -VLESS Reality: client → zirgate.com:8444 (RU TCP relay) → media.zirgate.com:8445 (EU nginx stream) → 127.0.0.1:443 (Xray) -VLESS XHTTP: client → media.zirgate.com:443/path → nginx_frontend:8443 → Xray :2053 -Subscription: client → my.zirgate.com (RU relay) → media.zirgate.com:8443 → Raven-subscribe :8080 -``` +| Variable | Default | Description | +|----------|---------|-------------| +| `relay_domain` | `example.com` | RU VPS domain — set to your domain | +| `relay_upstream_raven_port` | `8443` | EU nginx_frontend port (must match `nginx_frontend_listen_port`) | +| `relay_stream_port` | `8444` | RU relay TCP port for Reality (exposed to clients) | +| `relay_upstream_xray_port` | `8445` | EU nginx stream port (must match `nginx_frontend_reality_port`) | +| `relay_stub_title` | `Welcome` | Stub site page title | +| `relay_stub_description` | `Personal website` | Stub site meta description | -Xray config is split across `/etc/xray/config.d/` — files are loaded in numeric order: +--- -| File | Content | -|------|---------| -| `000-log.json` | Logging | -| `010-stats.json` | Statistics | -| `050-api.json` | gRPC API | -| `100-dns.json` | DNS | -| `200-in-vless-reality.json` | VLESS + XTLS-Reality inbound | -| `210-in-xhttp.json` | VLESS + XHTTP inbound | -| `300-outbounds.json` | Outbounds | -| `400-routing.json` | Routing rules | +## DNS Setup + +Point the following DNS A records to the correct servers: -**Handler safety:** `Validate xray` runs before `Restart xray` — invalid config never causes a service restart. +| Domain | → | Server | Purpose | +|--------|---|--------|---------| +| `media.example.com` | → | EU VPS IP | nginx_frontend (XHTTP, Raven) | +| `example.com` | → | RU VPS IP | Relay stub site | +| `my.example.com` | → | RU VPS IP | Relay → Raven-subscribe | + +The RU VPS TCP relay for Reality (port 8444) works by IP — no DNS record needed. + +--- ## VLESS Encryption (optional) -Xray-core >= 25.x supports post-quantum VLESS Encryption (PR #5067, mlkem768x25519plus). Disabled by default (`"none"`). +Xray-core >= 25.x supports post-quantum VLESS Encryption (mlkem768x25519plus). Disabled by default. + +When enabled, all clients connecting to the inbound **must** support it — do not mix encrypted and plain clients on the same inbound. -To enable: +**Generate keys:** ```bash -# Generate key pair on the server xray vlessenc -# Output: decryption string (private, for server) + encryption string (public, for clients) +# Output: decryption string (server private) + encryption string (client public) ``` -Then in `secrets.yml`: +**Add to `roles/xray/defaults/secrets.yml`:** ```yaml -xray_vless_decryption: "mlkem768x25519plus...." # server private string -xray_vless_client_encryption: "mlkem768x25519plus...." # client public string +xray_vless_decryption: "mlkem768x25519plus.PRIVATE..." # server — keep secret +xray_vless_client_encryption: "mlkem768x25519plus.PUBLIC..." # sent to clients via Raven +``` + +Both must be set together or both left as `"none"`. When enabled, `flow` is forced to `xtls-rprx-vision` for all users. + +--- + +## Hysteria2 / sing-box (optional) + +Deploy sing-box alongside Xray to provide Hysteria2 (QUIC-based protocol with Salamander obfuscation). + +```bash +# Copy and fill in secrets +cp roles/sing-box-playbook/defaults/secrets.yml.example roles/sing-box-playbook/defaults/secrets.yml +ansible-vault encrypt roles/sing-box-playbook/defaults/secrets.yml --vault-password-file vault_password.txt + +# Deploy +ansible-playbook roles/role_sing-box.yml -i roles/hosts.yml --vault-password-file vault_password.txt ``` -Both must be set together or both left as `"none"`. When enabled, all users are forced to `flow: xtls-rprx-vision`. +After deployment, set `raven_subscribe_singbox_enabled: true` in `raven_subscribe/defaults/secrets.yml` and redeploy Raven-subscribe. It will discover Hysteria2 users and serve them via `/sub/{token}/singbox` and `/sub/{token}/hysteria2` endpoints. + +**Note:** Hysteria2 uses ACME (Let's Encrypt) directly in sing-box. Set `singbox.tls_acme_domain` and `singbox.tls_acme_email` in secrets. + +--- ## Testing -Run the full test suite (Ansible render + `xray -test` via Docker): +Run the full test suite — renders all Ansible templates and validates them with `xray -test` in Docker: ```bash ./tests/run.sh ``` -Ansible-only (no Docker required): +Ansible-only (no Docker needed): ```bash SKIP_XRAY_TEST=1 ./tests/run.sh ``` -The pipeline: +**Pipeline steps:** 1. Downloads Xray binary (cached in `tests/.cache/`) -2. Generates test Reality keys +2. Generates ephemeral Reality keys → `tests/fixtures/test_secrets.yml` 3. Runs `validate.yml` assertions -4. Renders all `templates/conf/*.j2` to `tests/.output/conf.d/` +4. Renders all `templates/conf/*.j2` → `tests/.output/conf.d/` 5. Runs `xray -test -confdir` in Docker -CI runs automatically via `.github/workflows/xray-config-test.yml`. +CI runs on every push and PR via `.github/workflows/xray-config-test.yml`. + +**Run individual steps manually:** + +```bash +export ANSIBLE_CONFIG="${PWD}/tests/ansible.cfg" +tests/scripts/gen-reality-keys.sh > tests/fixtures/test_secrets.yml +ansible-playbook tests/playbooks/validate_vars.yml +ansible-playbook tests/playbooks/render_conf.yml +``` + +--- ## Related Projects -- [Raven-subscribe](https://github.com/alchemylink/raven-subscribe) — subscription server (Go) that syncs users via Xray gRPC API and serves client configs +- [Raven-subscribe](https://github.com/AlchemyLink/Raven-subscribe) — subscription server (Go): auto-discovers users from Xray config, syncs via gRPC API, serves personal subscription URLs in Xray JSON / sing-box JSON / share link formats +- [Xray-core](https://github.com/XTLS/Xray-core) — the VPN core +- [sing-box](https://github.com/SagerNet/sing-box) — alternative VPN core (Hysteria2) + +--- ## License diff --git a/README.ru.md b/README.ru.md new file mode 100644 index 0000000..3bcfe11 --- /dev/null +++ b/README.ru.md @@ -0,0 +1,507 @@ +# Raven Server Install + +Языки: [English](README.md) | **Русский** + +[![CI](https://github.com/AlchemyLink/Raven-server-install/actions/workflows/xray-config-test.yml/badge.svg)](https://github.com/AlchemyLink/Raven-server-install/actions/workflows/xray-config-test.yml) +[![License: MPL 2.0](https://img.shields.io/badge/License-MPL_2.0-brightgreen.svg)](LICENSE) + +Ansible-плейбуки для развёртывания самохостинг VPN-стека на основе [Xray-core](https://github.com/XTLS/Xray-core) и [Raven-subscribe](https://github.com/AlchemyLink/Raven-subscribe). + +**Что вы получаете:** + +- Xray-core с inbound'ами VLESS + XTLS-Reality и VLESS + XHTTP +- Опциональное пост-квантовое VLESS Encryption (mlkem768x25519plus) +- Опциональный Hysteria2 через [sing-box](https://github.com/SagerNet/sing-box) +- [Raven-subscribe](https://github.com/AlchemyLink/Raven-subscribe) — сервер подписок: автоматически находит пользователей, раздаёт клиентские конфиги по персональным ссылкам +- nginx TLS frontend на EU VPS (роль `nginx_frontend`) +- nginx relay + TCP stream proxy на RU VPS для маршрутизации через второй сервер (роль `relay`) +- systemd-сервисы с валидацией конфига перед каждым перезапуском +- Блокировка рекламы и публичных трекеров через правила маршрутизации geosite +- BBR и тюнинг sysctl (роль `srv_prepare`) + +--- + +## Содержание + +- [Архитектура](#архитектура) +- [Требования](#требования) +- [Быстрый старт](#быстрый-старт) +- [Описание ролей](#описание-ролей) +- [Секреты](#секреты) +- [Конфигурация](#конфигурация) +- [DNS-записи](#dns-записи) +- [VLESS Encryption (опционально)](#vless-encryption-опционально) +- [Hysteria2 / sing-box (опционально)](#hysteria2--sing-box-опционально) +- [Тестирование](#тестирование) +- [Связанные проекты](#связанные-проекты) +- [Лицензия](#лицензия) + +--- + +## Архитектура + +Поддерживаются две топологии деплоя. + +### Один сервер (минимальный вариант) + +Один VPS с Xray + Raven-subscribe + nginx. + +``` +Клиент ──VLESS+Reality──► VPS:443 (Xray) +Клиент ──VLESS+XHTTP────► VPS:443 (nginx) ──► VPS:2053 (Xray) +Клиент ──подписка───────► VPS:443 (nginx) ──► VPS:8080 (Raven) +``` + +### Два сервера с RU-relay (рекомендуется для пользователей из СНГ) + +EU VPS: Xray + nginx_frontend + Raven-subscribe. +RU VPS: relay — скрывает EU IP от клиентов. + +``` +EU VPS (media.example.com) RU VPS (example.com) +┌───────────────────────────┐ ┌─────────────────────────────┐ +│ Xray :443 TCP │ │ nginx relay │ +│ nginx XHTTP :443 HTTPS │◄─────│ my.example.com → EU:8443 │ +│ nginx stream:8445 TCP │◄─────│ :8444 TCP → EU:8445 TCP │ +│ Raven :8080 local │ └─────────────────────────────┘ +│ nginx front :8443 HTTPS │ ▲ +└───────────────────────────┘ │ + клиенты +``` + +**Маршруты подключения клиентов:** +``` +VLESS Reality: клиент → RU:8444 (TCP relay) → EU:8445 (nginx stream) → Xray:443 +VLESS XHTTP: клиент → EU:443 (nginx HTTPS) → Xray:2053 +Подписка: клиент → my.example.com (RU relay) → EU:8443 → Raven:8080 +``` + +### Карта ролей + +| Роль | VPS | Плейбук | Что делает | +|------|-----|---------|-----------| +| `srv_prepare` | EU | `role_xray.yml` | BBR, sysctl, системный пользователь | +| `xray` | EU | `role_xray.yml` | Бинарь Xray + split-конфиг в `/etc/xray/config.d/` | +| `raven_subscribe` | EU | `role_raven_subscribe.yml` | Сервер подписок, gRPC-синхронизация с Xray | +| `nginx_frontend` | EU | `role_nginx_frontend.yml` | nginx TLS proxy + TCP stream relay (порты 8443/8445) | +| `sing-box-playbook` | EU | `role_sing-box.yml` | sing-box + Hysteria2 (опционально) | +| `relay` | RU | `role_relay.yml` | nginx reverse proxy + TCP stream relay (порт 8444) | + +--- + +## Требования + +- **Ansible** >= 2.14 (`ansible-core`) +- **ОС на сервере**: Debian/Ubuntu с systemd +- **Python 3** на целевых серверах +- **ansible-vault** для управления секретами +- **Docker** (опционально, для локального тестирования конфигов) + +--- + +## Быстрый старт + +### 1. Клонировать репозиторий + +```bash +git clone https://github.com/AlchemyLink/Raven-server-install.git +cd Raven-server-install +``` + +### 2. Создать inventory + +Для ролей **xray** и **raven_subscribe** — отредактируйте `roles/hosts.yml.example` (скопируйте в `roles/hosts.yml`): + +```yaml +all: + children: + cloud: + hosts: + vm_my_srv: + ansible_host: "EU_VPS_IP" + ansible_port: 22 + vars: + ansible_user: deploy + ansible_python_interpreter: /usr/bin/python3 + ansible_ssh_private_key_file: ~/.ssh/id_ed25519 +``` + +Для ролей **nginx_frontend** и **relay** — отредактируйте соответствующие файлы `inventory.ini`: + +```ini +# roles/nginx_frontend/inventory.ini +[eu] +vpn ansible_host=EU_VPS_IP ansible_user=deploy + +# roles/relay/inventory.ini +[relay] +relay ansible_host=RU_VPS_IP ansible_user=deploy +``` + +### 3. Создать файлы секретов + +У каждой роли есть `defaults/secrets.yml.example`. Скопируйте, заполните и зашифруйте: + +```bash +# Xray +cp roles/xray/defaults/secrets.yml.example roles/xray/defaults/secrets.yml +# заполнить roles/xray/defaults/secrets.yml +ansible-vault encrypt roles/xray/defaults/secrets.yml --vault-password-file vault_password.txt + +# Raven-subscribe +cp roles/raven_subscribe/defaults/secrets.yml.example roles/raven_subscribe/defaults/secrets.yml +# заполнить roles/raven_subscribe/defaults/secrets.yml +ansible-vault encrypt roles/raven_subscribe/defaults/secrets.yml --vault-password-file vault_password.txt + +# nginx_frontend (EU VPS) +cp roles/nginx_frontend/defaults/secrets.yml.example roles/nginx_frontend/defaults/secrets.yml +# заполнить roles/nginx_frontend/defaults/secrets.yml +ansible-vault encrypt roles/nginx_frontend/defaults/secrets.yml --vault-password-file vault_password.txt + +# relay (RU VPS) +cp roles/relay/defaults/secrets.yml.example roles/relay/defaults/secrets.yml +# заполнить roles/relay/defaults/secrets.yml +ansible-vault encrypt roles/relay/defaults/secrets.yml --vault-password-file vault_password.txt +``` + +Редактировать зашифрованный файл: + +```bash +ansible-vault edit roles/xray/defaults/secrets.yml --vault-password-file vault_password.txt +``` + +### 4. Сгенерировать ключи Reality + +```bash +# На любой машине с установленным Xray: +xray x25519 +# Вывод: PrivateKey + PublicKey — оба вносим в roles/xray/defaults/secrets.yml + +openssl rand -hex 8 # short_id +``` + +### 5. Задеплоить + +```bash +# EU сервер: Xray + системная подготовка +ansible-playbook roles/role_xray.yml -i roles/hosts.yml --vault-password-file vault_password.txt + +# EU сервер: nginx TLS frontend + TCP stream relay +ansible-playbook roles/role_nginx_frontend.yml -i roles/nginx_frontend/inventory.ini --vault-password-file vault_password.txt + +# EU сервер: Raven-subscribe +ansible-playbook roles/role_raven_subscribe.yml -i roles/hosts.yml --vault-password-file vault_password.txt + +# RU сервер: nginx relay +ansible-playbook roles/role_relay.yml -i roles/relay/inventory.ini --vault-password-file vault_password.txt +``` + +Деплой только конкретной части через теги: + +```bash +ansible-playbook roles/role_xray.yml -i roles/hosts.yml --vault-password-file vault_password.txt \ + --tags xray_inbounds +``` + +--- + +## Описание ролей + +### Роль `xray` + +Устанавливает и настраивает Xray-core. Конфиг разделён на пронумерованные JSON-файлы в `/etc/xray/config.d/` — Xray загружает их по порядку. + +**Файлы тасков и теги:** + +| Тег | Файл | Что делает | +|-----|------|-----------| +| `always` | `validate.yml` | Проверки переменных — всегда | +| `xray_install` | `install.yml` | Скачивает бинарь с GitHub | +| `xray_base` | `base.yml` | `000-log.json`, `010-stats.json` | +| `xray_api` | `api.yml` | `050-api.json` (dokodemo на 127.0.0.1:10085) | +| `xray_inbounds` | `inbounds.yml` | `200-in-vless-reality.json`, `210-in-xhttp.json` | +| `xray_dns` | `dns.yml` | `100-dns.json` | +| `xray_outbounds` | `outbounds.yml` | `300-outbounds.json` | +| `xray_routing` | `routing.yml` | `400-routing.json` | +| `xray_service` | `service.yml` | systemd unit, запуск сервиса | +| `grpcurl` | `grpcurl.yml` | Установка grpcurl | + +**Файлы конфигурации:** + +| Файл | Содержимое | +|------|-----------| +| `000-log.json` | Уровни логирования, пути файлов | +| `010-stats.json` | Статистика трафика | +| `050-api.json` | gRPC API (127.0.0.1:10085) | +| `100-dns.json` | DNS-серверы и стратегия запросов | +| `200-in-vless-reality.json` | VLESS + XTLS-Reality inbound (TCP :443) | +| `210-in-xhttp.json` | VLESS + XHTTP inbound (:2053) | +| `300-outbounds.json` | Freedom + blackhole outbound'ы | +| `400-routing.json` | Правила маршрутизации + блокировка рекламы | + +**Безопасность handlers:** `Validate xray` должен быть определён раньше `Restart xray` в `handlers/main.yml`. Ansible выполняет handlers в порядке определения — это гарантирует, что невалидный конфиг никогда не вызовет перезапуск. + +--- + +### Роль `raven_subscribe` + +Деплоит [Raven-subscribe](https://github.com/AlchemyLink/Raven-subscribe) — Go-сервис, который автоматически находит пользователей Xray, синхронизирует их через gRPC API и раздаёт персональные ссылки подписки. + +Слушает на `127.0.0.1:8080`, проксируется через nginx_frontend. + +--- + +### Роль `nginx_frontend` + +Деплоит nginx на EU VPS как TLS reverse proxy. Функции: + +- Получает Let's Encrypt сертификат для `nginx_frontend_domain` +- Слушает на порту **8443** (порт 443 занят Xray VLESS Reality) +- Проксирует XHTTP path → Xray `:2053` +- Проксирует пути подписки/API → Raven-subscribe `:8080` +- **TCP stream relay**: порт 8445 → `127.0.0.1:443` (проброс VLESS Reality через nginx) + +--- + +### Роль `relay` + +Деплоит nginx на RU VPS как relay. Функции: + +- Получает Let's Encrypt сертификаты для `relay_domain` и `relay_sub_my` +- Отдаёт статический stub-сайт на `relay_domain` (маскировка) +- Проксирует `my.relay_domain` → EU VPS nginx_frontend `:8443` (Raven-subscribe) +- **TCP stream relay**: порт 8444 → EU VPS `:8445` (проброс VLESS Reality) + +--- + +### Роль `sing-box-playbook` + +Опционально. Деплоит [sing-box](https://github.com/SagerNet/sing-box) с inbound'ом Hysteria2. После деплоя Raven-subscribe автоматически находит Hysteria2-пользователей и включает их в подписки. + +--- + +## Секреты + +У каждой роли секреты хранятся в `defaults/secrets.yml` (зашифровано ansible-vault, не коммитится). Шаблоны — в `defaults/secrets.yml.example`. + +### `roles/xray/defaults/secrets.yml` + +```yaml +# Ключи Reality — генерация: xray x25519 +xray_reality: + private_key: "ВАШ_ПРИВАТНЫЙ_КЛЮЧ" + public_key: "ВАШ_ПУБЛИЧНЫЙ_КЛЮЧ" + spiderX: "/" + short_id: + - "a1b2c3d4e5f67890" # 8-байтный hex — генерация: openssl rand -hex 8 + +# VLESS пользователи +xray_users: + - id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # UUID — генерация: uuidgen + flow: "xtls-rprx-vision" + email: "alice@example.com" +``` + +### `roles/raven_subscribe/defaults/secrets.yml` + +```yaml +# Токен admin API — генерация: openssl rand -hex 32 +raven_subscribe_admin_token: "ВАШ_ADMIN_ТОКЕН" + +# Публичный URL для ссылок подписки +raven_subscribe_base_url: "https://my.example.com" + +# Публичный домен или IP EU VPS +raven_subscribe_server_host: "media.example.com" + +# Переопределение host/port по inbound (опционально) +# Позволяет разные адреса для разных протоколов в клиентских конфигах. +# Полезно когда клиенты подключаются через relay для части протоколов. +raven_subscribe_inbound_hosts: + vless-reality-in: "example.com" # RU relay для Reality + vless-xhttp-in: "media.example.com" +raven_subscribe_inbound_ports: + vless-reality-in: 8444 # TCP порт RU relay для Reality +``` + +### `roles/nginx_frontend/defaults/secrets.yml` + +```yaml +nginx_frontend_certbot_email: "admin@example.com" +``` + +### `roles/relay/defaults/secrets.yml` + +```yaml +relay_upstream_host: "EU_VPS_IP" # IP-адрес EU сервера +relay_certbot_email: "admin@example.com" +``` + +### `roles/sing-box-playbook/defaults/secrets.yml` + +```yaml +singbox_hysteria2_users: + - name: "alice@example.com" + password: "СИЛЬНЫЙ_СЛУЧАЙНЫЙ_ПАРОЛЬ" + +singbox: + tls_server_name: "media.example.com" + tls_acme_domain: "media.example.com" + tls_acme_email: "admin@example.com" +``` + +--- + +## Конфигурация + +### Xray (`roles/xray/defaults/main.yml`) + +| Переменная | По умолчанию | Описание | +|-----------|--------------|---------| +| `xray_vless_port` | `443` | Порт VLESS + Reality | +| `xray_reality_dest` | `askubuntu.com:443` | Camouflage-ресурс Reality (должен быть реальным TLS-сайтом) | +| `xray_reality_server_names` | `["askubuntu.com"]` | SNI имена для Reality | +| `xray_xhttp.port` | `2053` | Порт XHTTP inbound | +| `xray_xhttp.xhttpSettings.path` | `/api/v3/data-sync` | Путь XHTTP (должен совпадать с nginx_frontend) | +| `xray_dns_servers` | `tcp+local://8.8.8.8, ...` | DNS-серверы — не используйте DoH (`https://`) | +| `xray_dns_query_strategy` | `UseIPv4` | `UseIPv4` если нет глобального IPv6, иначе `UseIP` | +| `xray_vless_decryption` | `"none"` | Режим VLESS Encryption — см. [VLESS Encryption](#vless-encryption-опционально) | +| `xray_blocked_domains` | `[]` | Дополнительные домены для блокировки | + +### Raven-subscribe (`roles/raven_subscribe/defaults/main.yml`) + +| Переменная | По умолчанию | Описание | +|-----------|--------------|---------| +| `raven_subscribe_listen_addr` | `:8080` | Адрес для прослушивания | +| `raven_subscribe_sync_interval_seconds` | `60` | Интервал пересканирования конфигов Xray | +| `raven_subscribe_api_inbound_tag` | `vless-reality-in` | Inbound по умолчанию для пользователей через API | +| `raven_subscribe_xray_api_addr` | `127.0.0.1:10085` | Адрес gRPC API Xray | +| `raven_subscribe_inbound_hosts` | `{}` | Переопределение host по inbound (задать в secrets.yml) | +| `raven_subscribe_inbound_ports` | `{}` | Переопределение port по inbound (задать в secrets.yml) | +| `raven_subscribe_singbox_enabled` | `false` | Включить синхронизацию sing-box/Hysteria2 | + +### nginx_frontend (`roles/nginx_frontend/defaults/main.yml`) + +| Переменная | По умолчанию | Описание | +|-----------|--------------|---------| +| `nginx_frontend_domain` | `media.example.com` | Домен EU VPS — заменить на свой | +| `nginx_frontend_listen_port` | `8443` | Порт HTTPS nginx (не 443 — занят Xray) | +| `nginx_frontend_xhttp_port` | `2053` | Порт upstream Xray XHTTP | +| `nginx_frontend_xhttp_path` | `/api/v3/data-sync` | Путь XHTTP (должен совпадать с конфигом Xray) | +| `nginx_frontend_reality_port` | `8445` | Порт TCP stream relay для Reality | + +### relay (`roles/relay/defaults/main.yml`) + +| Переменная | По умолчанию | Описание | +|-----------|--------------|---------| +| `relay_domain` | `example.com` | Домен RU VPS — заменить на свой | +| `relay_upstream_raven_port` | `8443` | Порт nginx_frontend на EU (должен совпадать с `nginx_frontend_listen_port`) | +| `relay_stream_port` | `8444` | TCP порт RU relay для Reality (открытый для клиентов) | +| `relay_upstream_xray_port` | `8445` | Порт nginx stream на EU (должен совпадать с `nginx_frontend_reality_port`) | +| `relay_stub_title` | `Welcome` | Заголовок страницы stub-сайта | +| `relay_stub_description` | `Personal website` | Мета-описание stub-сайта | + +--- + +## DNS-записи + +Направьте следующие DNS A-записи на нужные серверы: + +| Домен | → | Сервер | Назначение | +|-------|---|--------|-----------| +| `media.example.com` | → | IP EU VPS | nginx_frontend (XHTTP, Raven) | +| `example.com` | → | IP RU VPS | Stub-сайт relay | +| `my.example.com` | → | IP RU VPS | Relay → Raven-subscribe | + +TCP relay Reality (порт 8444 на RU VPS) работает по IP — DNS-запись не нужна. + +--- + +## VLESS Encryption (опционально) + +Xray-core >= 25.x поддерживает пост-квантовое VLESS Encryption (mlkem768x25519plus). По умолчанию отключено. + +При включении **все** клиенты, подключающиеся к inbound, должны поддерживать шифрование — нельзя смешивать зашифрованных и обычных клиентов на одном inbound. + +**Генерация ключей:** + +```bash +xray vlessenc +# Вывод: decryption string (приватный, для сервера) + encryption string (публичный, для клиентов) +``` + +**Добавить в `roles/xray/defaults/secrets.yml`:** + +```yaml +xray_vless_decryption: "mlkem768x25519plus.PRIVATE..." # сервер — держать в секрете +xray_vless_client_encryption: "mlkem768x25519plus.PUBLIC..." # передаётся клиентам через Raven +``` + +Оба значения задаются одновременно или оба остаются `"none"`. При включении `flow` принудительно устанавливается в `xtls-rprx-vision` для всех пользователей. + +--- + +## Hysteria2 / sing-box (опционально) + +Задеплойте sing-box рядом с Xray для поддержки Hysteria2 (QUIC-протокол с обфускацией Salamander). + +```bash +# Скопировать и заполнить секреты +cp roles/sing-box-playbook/defaults/secrets.yml.example roles/sing-box-playbook/defaults/secrets.yml +ansible-vault encrypt roles/sing-box-playbook/defaults/secrets.yml --vault-password-file vault_password.txt + +# Задеплоить +ansible-playbook roles/role_sing-box.yml -i roles/hosts.yml --vault-password-file vault_password.txt +``` + +После деплоя установите `raven_subscribe_singbox_enabled: true` в `raven_subscribe/defaults/secrets.yml` и передеплойте Raven-subscribe. Он обнаружит Hysteria2-пользователей и будет раздавать их через эндпоинты `/sub/{token}/singbox` и `/sub/{token}/hysteria2`. + +**Примечание:** Hysteria2 использует ACME (Let's Encrypt) напрямую в sing-box. Задайте `singbox.tls_acme_domain` и `singbox.tls_acme_email` в секретах. + +--- + +## Тестирование + +Полный тестовый прогон — рендер всех Ansible-шаблонов и валидация через `xray -test` в Docker: + +```bash +./tests/run.sh +``` + +Только Ansible (без Docker): + +```bash +SKIP_XRAY_TEST=1 ./tests/run.sh +``` + +**Шаги пайплайна:** +1. Скачивает бинарь Xray (кэшируется в `tests/.cache/`) +2. Генерирует временные ключи Reality → `tests/fixtures/test_secrets.yml` +3. Запускает проверки `validate.yml` +4. Рендерит все `templates/conf/*.j2` → `tests/.output/conf.d/` +5. Запускает `xray -test -confdir` в Docker + +CI запускается автоматически на каждый push и PR через `.github/workflows/xray-config-test.yml`. + +**Запуск отдельных шагов вручную:** + +```bash +export ANSIBLE_CONFIG="${PWD}/tests/ansible.cfg" +tests/scripts/gen-reality-keys.sh > tests/fixtures/test_secrets.yml +ansible-playbook tests/playbooks/validate_vars.yml +ansible-playbook tests/playbooks/render_conf.yml +``` + +--- + +## Связанные проекты + +- [Raven-subscribe](https://github.com/AlchemyLink/Raven-subscribe) — сервер подписок (Go): автоматически находит пользователей из конфигов Xray, синхронизирует через gRPC API, раздаёт персональные ссылки подписки в форматах Xray JSON / sing-box JSON / share-ссылки +- [Xray-core](https://github.com/XTLS/Xray-core) — ядро VPN +- [sing-box](https://github.com/SagerNet/sing-box) — альтернативное ядро VPN (Hysteria2) + +--- + +## Лицензия + +[Mozilla Public License 2.0](LICENSE) diff --git a/roles/hosts.yml.example b/roles/hosts.yml.example new file mode 100644 index 0000000..ed89d18 --- /dev/null +++ b/roles/hosts.yml.example @@ -0,0 +1,13 @@ +--- +all: + children: + cloud: + hosts: + vm_my_srv: + ansible_host: "EU_VPS_IP" # Replace with your EU VPS IP + ansible_port: 22 + vars: + ansible_user: deploy + ansible_python_interpreter: /usr/bin/python3 + ansible_ssh_private_key_file: ~/.ssh/id_ed25519 + ansible_ssh_host_key_checking: false diff --git a/roles/nginx_frontend/defaults/main.yml b/roles/nginx_frontend/defaults/main.yml index 36ae905..dccf8ee 100644 --- a/roles/nginx_frontend/defaults/main.yml +++ b/roles/nginx_frontend/defaults/main.yml @@ -1,5 +1,5 @@ --- -# nginx_frontend role — TLS frontend for EU server (media.zirgate.com) +# nginx_frontend role — TLS frontend for EU VPS # # Responsibilities: # - Install nginx + certbot @@ -7,7 +7,7 @@ # - Proxy Xray XHTTP (nginx_frontend_xhttp_path) → 127.0.0.1:nginx_frontend_xhttp_port # ── Domain ──────────────────────────────────────────────────────────────────── -nginx_frontend_domain: "media.zirgate.com" +nginx_frontend_domain: "media.example.com" # Set to your EU VPS domain # ── Certbot ─────────────────────────────────────────────────────────────────── nginx_frontend_certbot_email: "" # Set in secrets.yml @@ -27,6 +27,6 @@ nginx_frontend_xhttp_path: "/api/v3/data-sync" # Must match xray_xhttp.xhttpSe # ── TCP stream relay for Xray VLESS Reality ─────────────────────────────────── # Stream proxy: nginx_frontend_reality_port → 127.0.0.1:443 (Xray) -# Allows clients to reach Reality via media.zirgate.com instead of direct EU IP. +# Allows clients to reach Reality via media.example.com instead of direct EU IP. nginx_frontend_reality_stream_enabled: true nginx_frontend_reality_port: 8445 # External TCP port for Reality stream diff --git a/roles/raven_subscribe/defaults/secrets.yml.example b/roles/raven_subscribe/defaults/secrets.yml.example index f87c35d..4fe1202 100644 --- a/roles/raven_subscribe/defaults/secrets.yml.example +++ b/roles/raven_subscribe/defaults/secrets.yml.example @@ -7,7 +7,7 @@ raven_subscribe_admin_token: "" # Public URL used in subscription links — must be the relay domain -raven_subscribe_base_url: "https://my.zirgate.com" +raven_subscribe_base_url: "https://my.example.com" # EU VPS public IP or domain (used in generated client outbound addresses) raven_subscribe_server_host: "" diff --git a/roles/relay/defaults/main.yml b/roles/relay/defaults/main.yml index 574c931..3a8d131 100644 --- a/roles/relay/defaults/main.yml +++ b/roles/relay/defaults/main.yml @@ -1,14 +1,14 @@ --- # Relay role — nginx reverse proxy on RU VPS -# Domain: zirgate.com -# zirgate.com A → RU VPS IP (static stub site) -# my.zirgate.com A → RU VPS IP (relay → Raven subscriptions + API) +# Domain layout example: +# example.com A → RU VPS IP (static stub site) +# my.example.com A → RU VPS IP (relay → Raven subscriptions + API) # # EU server (managed by nginx_frontend role, not this role): -# media.zirgate.com A → EU VPS IP (nginx_frontend → Xray XHTTP) +# media.example.com A → EU VPS IP (nginx_frontend → Xray XHTTP) # ── Domain ─────────────────────────────────────────────────────────────────── -relay_domain: "zirgate.com" +relay_domain: "example.com" # Set to your RU VPS domain relay_sub_my: "my.{{ relay_domain }}" # Raven-subscribe relay (RU VPS) # ── Upstream EU server ──────────────────────────────────────────────────────── @@ -21,7 +21,7 @@ relay_upstream_raven_port: 8443 # ── TCP stream relay (VLESS Reality) ───────────────────────────────────────── # Proxies raw TCP on relay_stream_port → EU server:relay_upstream_xray_port -# Clients connect to zirgate.com:relay_stream_port instead of EU IP directly. +# Clients connect to example.com:relay_stream_port instead of EU IP directly. relay_stream_enabled: true relay_stream_port: 8444 # Listening port on RU server (must be free) relay_upstream_xray_port: 8445 # nginx_frontend Reality stream port on EU server @@ -31,7 +31,7 @@ relay_nginx_user: "www-data" relay_webroot: "/var/www/{{ relay_domain }}" # ── Certbot ─────────────────────────────────────────────────────────────────── -relay_certbot_email: "" # Set in secrets.yml: relay_certbot_email: "admin@zirgate.com" +relay_certbot_email: "" # Set in secrets.yml: relay_certbot_email: "admin@example.com" # ── Stub site ───────────────────────────────────────────────────────────────── relay_stub_title: "Welcome" diff --git a/roles/relay/inventory.ini b/roles/relay/inventory.ini index b71a672..af0c8cf 100644 --- a/roles/relay/inventory.ini +++ b/roles/relay/inventory.ini @@ -1,2 +1,2 @@ [relay] -zirgate ansible_host=RU_VPS_IP ansible_user=deploy +relay ansible_host=RU_VPS_IP ansible_user=deploy diff --git a/roles/relay/templates/nginx/https.conf.j2 b/roles/relay/templates/nginx/https.conf.j2 index adbe8eb..2905c55 100644 --- a/roles/relay/templates/nginx/https.conf.j2 +++ b/roles/relay/templates/nginx/https.conf.j2 @@ -8,7 +8,7 @@ server { return 301 https://$host$request_uri; } -# ── zirgate.com — stub site ────────────────────────────────────────────────── +# ── {{ relay_domain }} — stub site ────────────────────────────────────────────────── server { listen 443 ssl; http2 on; @@ -27,7 +27,7 @@ server { } } -# ── my.zirgate.com — Raven-subscribe relay ─────────────────────────────────── +# ── {{ relay_sub_my }} — Raven-subscribe relay ─────────────────────────────────── server { listen 443 ssl; http2 on; diff --git a/roles/role_nginx_frontend.yml b/roles/role_nginx_frontend.yml index 1ac9e5f..8be1ff0 100644 --- a/roles/role_nginx_frontend.yml +++ b/roles/role_nginx_frontend.yml @@ -1,5 +1,5 @@ --- -# nginx frontend playbook — EU server (media.zirgate.com) +# nginx frontend playbook — EU VPS # Usage: # ansible-playbook roles/role_nginx_frontend.yml -i roles/nginx_frontend/inventory.ini \ # --vault-password-file vault_password.txt @@ -9,9 +9,10 @@ # nginx_frontend_nginx — deploy HTTP config # nginx_frontend_certbot — obtain TLS certificate # nginx_frontend_ssl — deploy HTTPS config with proxy_pass +# nginx_frontend_stream — deploy TCP stream relay for VLESS Reality -- name: Configure nginx frontend (vpn.zirgate.com) - hosts: vm_my_srv +- name: Configure nginx frontend + hosts: eu become: true vars_files: diff --git a/roles/role_relay.yml b/roles/role_relay.yml index a731db4..96d8890 100644 --- a/roles/role_relay.yml +++ b/roles/role_relay.yml @@ -1,5 +1,5 @@ --- -# Relay playbook — RU VPS (zirgate.com) +# Relay playbook — RU VPS # Usage: # ansible-playbook roles/role_relay.yml -i roles/relay/inventory.ini \ # --vault-password-file vault_password.txt @@ -10,9 +10,10 @@ # relay_nginx — deploy HTTP nginx config # relay_certbot — obtain TLS certificates # relay_nginx_ssl — deploy HTTPS nginx config with proxy_pass +# relay_stream — deploy TCP stream relay for VLESS Reality -- name: Configure relay server (zirgate.com) - hosts: vm_my_ru +- name: Configure relay server + hosts: relay become: true vars_files: diff --git a/roles/xray/defaults/raven_subscribe_secrets.yml.example b/roles/xray/defaults/raven_subscribe_secrets.yml.example index 9c2f1f1..1f09578 100644 --- a/roles/xray/defaults/raven_subscribe_secrets.yml.example +++ b/roles/xray/defaults/raven_subscribe_secrets.yml.example @@ -7,7 +7,7 @@ raven_subscribe_admin_token: "" # Public URL used in subscription links — must be the relay domain -raven_subscribe_base_url: "https://my.zirgate.com" +raven_subscribe_base_url: "https://my.example.com" # EU VPS public IP or domain (used in generated client outbound addresses) -raven_subscribe_server_host: "64.226.79.239" +raven_subscribe_server_host: "media.example.com" diff --git a/roles/xray/exampl/config.json.j2 b/roles/xray/exampl/config.json.j2 deleted file mode 100644 index ce234ec..0000000 --- a/roles/xray/exampl/config.json.j2 +++ /dev/null @@ -1,227 +0,0 @@ -{ - "log": { - "loglevel": "{{ xray_log_level }}", - "access": "{{ xray_access_log }}", - "error": "{{ xray_error_log }}", - "dnsLog": {{ xray_dns_log | to_json }} - }, - "dns": { - "servers": [ - {% for server in xray_dns_servers %} - "{{ server }}"{{ "," if not loop.last }} - {% endfor %} - ], - "disableFallback": {{ xray_dns_disable_fallback | to_json }}, - "queryStrategy": "{{ xray_dns_query_strategy }}" - }, - {% if xray_api.enable %} - "stats": {}, - "api": { - "tag": "{{ xray_api.tag }}", - "listen": "{{ xray_api.inbound.address }}:{{ xray_api.inbound.port }}", - "tag": "{{ xray_api.tag }}", - "services": [ - {% for service in xray_api.services %} - "{{ service }}"{{ "," if not loop.last }} - {% endfor %} - ] - }, - "policy": { - "levels": { - "0": { - "statsUserUplink": true, - "statsUserDownlink": true - } - }, - "system": { - "statsInboundUplink": true, - "statsInboundDownlink": true, - "statsOutboundUplink": true, - "statsOutboundDownlink": true - } - }, - {% endif %} - "inbounds": [ - {% if xray_api.enable %} - { - "listen": "{{ xray_api.inbound.address }}", - "port": {{ xray_api.inbound.port }}, - "protocol": "{{ xray_api.inbound.protocol }}", - "settings": { - "address": "{{ xray_api.inbound.address }}" - }, - "tag": "{{ xray_api.inbound.tag}}", - "sniffing": null - }, - {% endif %} - { - "port": {{ xray_vless_port }}, - "protocol": "vless", - "tag": "{{ xray_vless_tag }}", - "settings": { - "clients": [ - {% for user in xray_users %} - { - "id": "{{ user.id }}", - "flow": "{{ user.flow }}", - "email": "{{ user.email | default('') }}", - "level": 0 - }{{ "," if not loop.last }} - {% endfor %} - ], - "decryption": "none" - }, - "streamSettings": { - "network": "tcp", - "security": "reality", - "realitySettings": { - "show": false, - "dest": "{{ xray_reality_dest }}", - "spiderX": "{{ xray_reality.spiderX }}", - "xver": 0, - "serverNames": [ - {% for name in xray_reality_server_names %} - "{{ name }}"{{ "," if not loop.last }} - {% endfor %} - ], - "privateKey": "{{ xray_reality.private_key }}", - "shortIds": [ - {% for short_id in xray_reality.short_id %} - "{{ short_id }}"{{ "," if not loop.last }} - {% endfor %} - ] - } - }, - "sniffing": { - "enabled": true, - "destOverride": [ - "http", - "tls", - "quic" - ], - "routeOnly": true - } - }, - { - "port": 2053, - "protocol": "vless", - "settings": { - "clients": [ - {% for user in xray_users %} - { - "id": "{{ user.id }}", - "flow": "", - "email": "{{ user.email | default('') }}", - "level": 0 - }{{ "," if not loop.last }} - {% endfor %} - ], - "decryption": "none" - }, - "sniffing": { - "destOverride": [ - "http", - "tls", - "quic" - ], - "enabled": true - }, - "streamSettings": { - "network": "xhttp", - "realitySettings": { - "dest": "{{ xray_reality_dest }}", - "privateKey": "{{ xray_reality.private_key }}", - "serverNames": [ - {% for name in xray_reality_server_names %} - "{{ name }}"{{ "," if not loop.last }} - {% endfor %} - ], - "shortIds": [ - {% for short_id in xray_reality.short_id %} - "{{ short_id }}"{{ "," if not loop.last }} - {% endfor %} - ], - "show": false, - "xver": 0 - }, - "security": "reality", - "xhttpSettings": { - "mode": "{{ xray_xhttp.xhttpSettings.mode }}", - "path": "{{ xray_xhttp.xhttpSettings.path }}", - "scMaxPacketSize": "{{ xray_xhttp.xhttpSettings.scMaxPacketSize }}", - "xmux": { - "cids": [ - 1 - ], - "maxConcurrency": 16 - } - } - }, - "tag": "vless-xhttp-in" - } - ], - "outbounds": [ - { - "protocol": "freedom", - "settings": {}, - "tag": "freedom" - }, - { - "protocol": "blackhole", - "settings": {}, - "tag": "blocked" - } - ], - "routing": { - "domainStrategy": "IPIfNonMatch", - "rules": [ - {% if xray_api.enable %} - { - "type": "field", - "inboundTag": [ - "{{ xray_api.inbound.tag}}" - ], - "outboundTag": "{{ xray_api.tag }}" - }, - {% endif %} - { - "type": "field", - "inboundTag": [ - "{{ xray_vless_tag }}", - "{{ xray_xhttp.xray_xhttp_tag }}" - ], - "outboundTag": "freedom" - }, - { - "type": "field", - "domain": [ - "geosite:category-ads", - "geosite:category-public-tracker" - ], - "outboundTag": "blocked" - }, - { - "type": "field", - "domain": [ - {% for domain in xray_blocked_domains %} - "{{ domain }}"{{ "," if not loop.last }} - {% endfor %} - ], - "outboundTag": "blocked", - "settings": { - "response": { - "type": - "http" - } - } - }, - {% if xray_api.enable %} - { - "type": "field", - "inboundTag": ["${xray_api.inbound.tag}"], - "outboundTag": "api" - } - {% endif %} - ] - } -} \ No newline at end of file diff --git a/roles/xray/exampl/main.yml.bak b/roles/xray/exampl/main.yml.bak deleted file mode 100644 index afb6566..0000000 --- a/roles/xray/exampl/main.yml.bak +++ /dev/null @@ -1,192 +0,0 @@ -Л# File: roles/xray/tasks/main.yml - ---- -- name: "Ensure the {{ xray_group }} group exists" - ansible.builtin.group: - name: "{{ xray_group }}" - state: present - system: true - -- name: Set nologin shell path based on OS family - ansible.builtin.set_fact: - nologin_shell: | - {% if ansible_facts['os_family'] == 'Alpine' %} - /sbin/nologin - {% else %} - /usr/sbin/nologin - {% endif %} - -- name: Create a dedicated system user for Xray ({{ xray_user }}) - ansible.builtin.user: - name: "{{ xray_user }}" - state: present - system: true - shell: "{{ xray_nologin_shell }}" - group: "{{ xray_group }}" - -- name: Ensure Xray log directory exists and has correct permissions - ansible.builtin.file: - path: "{{ xray_log_dir }}" - state: directory - owner: "{{ xray_user }}" - group: "{{ xray_group }}" - mode: '0755' - recurse: true - -- name: Ensure Xray configuration directory exists and has correct permissions - ansible.builtin.file: - path: "{{ xray_config_dir }}" - state: directory - owner: "{{ xray_user }}" - group: "{{ xray_group }}" - mode: '0755' - recurse: true - -- name: "Correct ownership for Xray executable (if needed)" - ansible.builtin.file: - path: "{{ xray_bin_dir }}/xray" - owner: "{{ xray_user }}" - group: "{{ xray_group }}" - mode: '0755' - ignore_errors: true - when: xray_bin_dir is defined and xray_bin_dir != '' - -- name: Correct permissions for Xray service files (if necessary) - ansible.builtin.file: - path: "{{ xray_install_dir }}" - owner: "{{ xray_user }}" - group: "{{ xray_group }}" - -- name: Ensure Xray install directory exists - ansible.builtin.file: - path: "{{ xray_install_dir }}" - state: directory - mode: '0755' - -- name: Ensure Xray config directory exists - ansible.builtin.file: - path: "{{ xray_config_dir }}" - state: directory - mode: '0755' - -- name: Get latest Xray release version - ansible.builtin.uri: - url: "https://api.github.com/repos/{{ xray_github_repo }}/releases/latest" - method: GET - return_content: true - headers: - Accept: "application/vnd.github.v3+json" - register: xray_release_info - run_once: true - -- name: Set Xray version fact - ansible.builtin.set_fact: - xray_version: "{{ xray_release_info.json.tag_name }}" - ansible_version_full: "{{ ansible_version.full }}" - -- name: Download Xray using get_url (Ansible >= 2.16.0) - block: - - name: Download Xray archive (Ansible >= 2.16.0) - ansible.builtin.get_url: - url: "{{ xray_download_url }}" - dest: "/tmp/Xray-{{ xray_version }}-linux-64.zip" - mode: '0644' - register: download_xray - - name: Unarchive Xray - ansible.builtin.unarchive: - src: "{{ download_xray.dest }}" - dest: "{{ xray_install_dir }}" - remote_src: true - when: ansible_version_full is version('2.16.0', '>=') - notify: "Restart xray service" - -- name: Download Xray using curl (Ansible < 2.16.0) - block: - - name: Download Xray archive (Ansible < 2.16.0) - ansible.builtin.command: > - curl -L --output /tmp/Xray-{{ xray_version }}-linux-64.zip "{{ xray_download_url }}" - args: - creates: "/tmp/Xray-{{ xray_version }}-linux-64.zip" - register: download_xray - changed_when: download_xray.rc == 0 and not "already exists" in download_xray.stdout - - name: Unarchive Xray - ansible.builtin.unarchive: - src: "/tmp/Xray-{{ xray_version }}-linux-64.zip" - dest: "{{ xray_install_dir }}" - remote_src: true - when: ansible_version_full is version('2.16.0', '<') - notify: "Restart xray service" - -- name: Create symlink for Xray executable - ansible.builtin.file: - src: "{{ xray_install_dir }}/xray" - dest: /usr/local/bin/xray - state: link - force: true - -- name: Deploy Xray systemd service file - ansible.builtin.template: - src: xray.service.j2 - dest: /etc/systemd/system/{{ xray_service_name }}.service - mode: '0644' - when: ansible_facts['service_mgr'] == "systemd" - notify: - - Reload systemd - - Restart xray - -- name: Deploy Xray OpenRC service file - ansible.builtin.template: - src: xray.openrc.j2 - dest: /etc/init.d/{{ xray_service_name }} - mode: '0755' - when: ansible_facts['service_mgr'] == "openrc" - notify: - - Reload openrc - - Restart xray - -- name: Generate Xray configuration - ansible.builtin.template: - src: config.json.j2 - dest: "{{ xray_config_dir }}/config.json" - mode: '0644' - notify: "Restart xray service" - tags: - - xray_config - -- name: Ensure Xray service is started and enabled (systemd) - ansible.builtin.systemd_service: - name: "{{ xray_service_name }}" - state: started - enabled: true - register: xray_service_status_systemd - when: ansible_facts['service_mgr'] == "systemd" - tags: - - xray_config - -- name: Ensure Xray service is started and enabled (OpenRC) - ansible.builtin.service: - name: "{{ xray_service_name }}" - state: started - enabled: true - register: xray_service_status_openrc - when: ansible_facts['service_mgr'] == "openrc" - tags: - - xray_config - -- name: Fail if Xray service is not running (systemd) - ansible.builtin.fail: - msg: "Xray service failed to start! Check logs with 'journalctl -u {{ xray_service_name }}'" - when: - - ansible_facts['service_mgr'] == "systemd" - - not xray_service_status_systemd.status.ActiveState == "active" - tags: - - xray_config - -- name: Fail if Xray service is not running (OpenRC) - ansible.builtin.fail: - msg: "Xray service failed to start! Check logs with 'rc-service {{ xray_service_name }} status' or '/var/log/messages'" - when: - - ansible_facts['service_mgr'] == "openrc" - - not xray_service_status_openrc.status.active - tags: - - xray_config diff --git a/roles/xray/tasks/raven_subscribe.yml b/roles/xray/tasks/raven_subscribe.yml deleted file mode 100644 index 0801305..0000000 --- a/roles/xray/tasks/raven_subscribe.yml +++ /dev/null @@ -1,100 +0,0 @@ ---- -- name: Raven-subscribe | Validate required vars - ansible.builtin.assert: - that: - - raven_subscribe_admin_token is defined - - raven_subscribe_admin_token != '' - - raven_subscribe_server_host is defined - - raven_subscribe_server_host != '' - fail_msg: >- - raven_subscribe_admin_token and raven_subscribe_server_host must be set in secrets.yml. - Generate a strong token: openssl rand -hex 32 - success_msg: "Raven-subscribe vars are valid" - -- name: Raven-subscribe | Get latest release info - ansible.builtin.uri: - url: "https://api.github.com/repos/{{ raven_subscribe_github_repo }}/releases/latest" - method: GET - return_content: true - headers: - Accept: "application/vnd.github.v3+json" - status_code: 200 - register: raven_release_info - run_once: true - retries: 3 - delay: 3 - until: raven_release_info.status == 200 - -- name: Raven-subscribe | Set version and arch facts - ansible.builtin.set_fact: - raven_subscribe_version: "{{ raven_release_info.json.tag_name }}" - raven_subscribe_arch: >- - {{ - 'linux-amd64' if ansible_architecture in ['x86_64', 'amd64'] - else 'linux-arm64' if ansible_architecture in ['aarch64', 'arm64'] - else 'linux-arm' - }} - -- name: Raven-subscribe | Download binary - ansible.builtin.get_url: - url: "https://github.com/{{ raven_subscribe_github_repo }}/releases/download/\ - {{ raven_subscribe_version }}/xray-subscription-{{ raven_subscribe_arch }}" - dest: "{{ raven_subscribe_install_dir }}/xray-subscription" - mode: "0755" - owner: root - group: root - notify: Restart raven-subscribe - -- name: Raven-subscribe | Ensure config directory exists - ansible.builtin.file: - path: "{{ raven_subscribe_config_dir }}" - state: directory - owner: root - group: "{{ xray_group }}" - mode: "0750" - -- name: Raven-subscribe | Ensure data directory exists - ansible.builtin.file: - path: "{{ raven_subscribe_db_dir }}" - state: directory - owner: "{{ xray_user }}" - group: "{{ xray_group }}" - mode: "0750" - -- name: Raven-subscribe | Deploy config - ansible.builtin.template: - src: raven-subscribe/config.json.j2 - dest: "{{ raven_subscribe_config_dir }}/config.json" - owner: root - group: "{{ xray_group }}" - mode: "0640" - notify: Restart raven-subscribe - -- name: Raven-subscribe | Deploy systemd service - ansible.builtin.template: - src: raven-subscribe/xray-subscription.service.j2 - dest: "/etc/systemd/system/{{ raven_subscribe_service_name }}.service" - owner: root - group: root - mode: "0644" - when: ansible_facts.service_mgr == "systemd" - notify: - - Reload systemd - - Restart raven-subscribe - -- name: Raven-subscribe | Enable and start service - ansible.builtin.service: - name: "{{ raven_subscribe_service_name }}" - enabled: true - state: started - -- name: Raven-subscribe | Gather service facts - ansible.builtin.service_facts: - -- name: Raven-subscribe | Validate service is running - ansible.builtin.fail: - msg: "xray-subscription service is not running" - when: - - ansible_facts.services is defined - - ansible_facts.services[raven_subscribe_service_name + '.service'] is defined - - ansible_facts.services[raven_subscribe_service_name + '.service'].state != 'running'