Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Secrets — never commit real credentials
vault_password.txt
**/secrets.yml
**/*_secrets.yml
**/vault_password.txt
**/*.secret
**/*.vault
Expand Down
32 changes: 32 additions & 0 deletions roles/nginx_frontend/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
# nginx_frontend role — TLS frontend for EU server (media.zirgate.com)
#
# Responsibilities:
# - Install nginx + certbot
# - Obtain Let's Encrypt certificate for nginx_frontend_domain
# - Proxy Xray XHTTP (nginx_frontend_xhttp_path) → 127.0.0.1:nginx_frontend_xhttp_port

# ── Domain ────────────────────────────────────────────────────────────────────
nginx_frontend_domain: "media.zirgate.com"

# ── Certbot ───────────────────────────────────────────────────────────────────
nginx_frontend_certbot_email: "" # Set in secrets.yml

# ── nginx listen port ─────────────────────────────────────────────────────────
# IMPORTANT: Xray VLESS Reality already binds to 443 (TCP).
# nginx_frontend must listen on a different port (e.g., 8443, 9443).
# The relay role will proxy to this port over HTTPS with SNI.
nginx_frontend_listen_port: 8443 # Must NOT conflict with xray_vless_port (443)

# ── Raven-subscribe upstream ──────────────────────────────────────────────────
nginx_frontend_raven_port: 8080 # Must match raven_subscribe_listen_addr port

# ── Xray XHTTP upstream ───────────────────────────────────────────────────────
nginx_frontend_xhttp_port: 2053 # Must match xray_xhttp.port
nginx_frontend_xhttp_path: "/api/v3/data-sync" # Must match xray_xhttp.xhttpSettings.path

# ── 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.
nginx_frontend_reality_stream_enabled: true
nginx_frontend_reality_port: 8445 # External TCP port for Reality stream
5 changes: 5 additions & 0 deletions roles/nginx_frontend/defaults/secrets.yml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
# Copy to secrets.yml and encrypt with ansible-vault:
# ansible-vault encrypt roles/nginx_frontend/defaults/secrets.yml

nginx_frontend_certbot_email: "admin@admin.com"
10 changes: 10 additions & 0 deletions roles/nginx_frontend/handlers/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded

- name: Restart nginx
ansible.builtin.service:
name: nginx
state: restarted
2 changes: 2 additions & 0 deletions roles/nginx_frontend/inventory.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[eu]
vpn ansible_host=EU_VPS_IP ansible_user=deploy
23 changes: 23 additions & 0 deletions roles/nginx_frontend/tasks/certbot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
- name: Nginx frontend | Check if certificate exists
ansible.builtin.stat:
path: "/etc/letsencrypt/live/{{ nginx_frontend_domain }}/fullchain.pem"
register: nginx_frontend_cert

- name: Nginx frontend | Obtain Let's Encrypt certificate
ansible.builtin.command:
cmd: >
certbot certonly --webroot
--webroot-path /var/www/letsencrypt
--non-interactive
--agree-tos
--email {{ nginx_frontend_certbot_email }}
-d {{ nginx_frontend_domain }}
when: not nginx_frontend_cert.stat.exists
notify: Reload nginx

- name: Nginx frontend | Ensure certbot renewal timer is enabled
ansible.builtin.service:
name: certbot.timer
enabled: true
state: started
15 changes: 15 additions & 0 deletions roles/nginx_frontend/tasks/install.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
- name: Nginx frontend | Install nginx and certbot
ansible.builtin.apt:
name:
- nginx
- certbot
- python3-certbot-nginx
state: present
update_cache: true

- name: Nginx frontend | Ensure nginx is enabled and started
ansible.builtin.service:
name: nginx
enabled: true
state: started
24 changes: 24 additions & 0 deletions roles/nginx_frontend/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
- name: Nginx frontend | Validate
ansible.builtin.import_tasks: validate.yml
tags: always

- name: Nginx frontend | Install packages
ansible.builtin.import_tasks: install.yml
tags: nginx_frontend_install

- name: Nginx frontend | Configure HTTP (pre-certbot)
ansible.builtin.import_tasks: nginx.yml
tags: nginx_frontend_nginx

- name: Nginx frontend | Obtain TLS certificate
ansible.builtin.import_tasks: certbot.yml
tags: nginx_frontend_certbot

- name: Nginx frontend | Deploy HTTPS config
ansible.builtin.import_tasks: nginx_ssl.yml
tags: nginx_frontend_ssl

- name: Nginx frontend | Configure TCP stream relay
ansible.builtin.import_tasks: stream.yml
tags: nginx_frontend_stream
20 changes: 20 additions & 0 deletions roles/nginx_frontend/tasks/nginx.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
- name: Nginx frontend | Create letsencrypt webroot
ansible.builtin.file:
path: /var/www/letsencrypt
state: directory
owner: root
group: root
mode: "0755"

- name: Nginx frontend | Deploy HTTP config (pre-certbot)
ansible.builtin.template:
src: nginx/http.conf.j2
dest: "/etc/nginx/conf.d/{{ nginx_frontend_domain }}.conf"
owner: root
group: root
mode: "0644"
notify: Reload nginx

- name: Nginx frontend | Reload nginx before certbot
ansible.builtin.meta: flush_handlers
9 changes: 9 additions & 0 deletions roles/nginx_frontend/tasks/nginx_ssl.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
- name: Nginx frontend | Deploy HTTPS config
ansible.builtin.template:
src: nginx/https.conf.j2
dest: "/etc/nginx/conf.d/{{ nginx_frontend_domain }}.conf"
owner: root
group: root
mode: "0644"
notify: Reload nginx
36 changes: 36 additions & 0 deletions roles/nginx_frontend/tasks/stream.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
- name: Nginx frontend | Ensure stream.d directory exists
ansible.builtin.file:
path: /etc/nginx/stream.d
state: directory
owner: root
group: root
mode: "0755"

- name: Nginx frontend | Ensure stream block include in nginx.conf
ansible.builtin.blockinfile:
path: /etc/nginx/nginx.conf
marker: "# {mark} ANSIBLE MANAGED stream block"
block: |
stream {
include /etc/nginx/stream.d/*.conf;
}
insertafter: EOF
notify: Reload nginx

- name: Nginx frontend | Deploy Reality stream config
ansible.builtin.template:
src: nginx/stream.conf.j2
dest: "/etc/nginx/stream.d/{{ nginx_frontend_domain }}.conf"
owner: root
group: root
mode: "0644"
notify: Reload nginx
when: nginx_frontend_reality_stream_enabled

- name: Nginx frontend | Remove Reality stream config (disabled)
ansible.builtin.file:
path: "/etc/nginx/stream.d/{{ nginx_frontend_domain }}.conf"
state: absent
notify: Reload nginx
when: not nginx_frontend_reality_stream_enabled
11 changes: 11 additions & 0 deletions roles/nginx_frontend/tasks/validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
- name: Nginx frontend | Validate required vars
ansible.builtin.assert:
that:
- nginx_frontend_domain is defined
- nginx_frontend_domain != ''
- nginx_frontend_certbot_email is defined
- nginx_frontend_certbot_email != ''
fail_msg: >-
nginx_frontend_domain and nginx_frontend_certbot_email must be set in secrets.yml.
success_msg: "Nginx frontend vars are valid"
20 changes: 20 additions & 0 deletions roles/nginx_frontend/templates/nginx/http.conf.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# {{ nginx_frontend_domain }} — HTTP only (pre-certbot)
# Managed by Ansible nginx_frontend role
#
# This config is temporary and is replaced by https.conf.j2 after certbot obtains the certificate.
# It must allow certbot HTTP-01 validation for the Let's Encrypt certificate.

server {
listen 80;
server_name {{ nginx_frontend_domain }};

# Allow certbot HTTP-01 challenge
location /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
}

# Redirect all other traffic to HTTPS (when cert is available)
location / {
return 301 https://$host$request_uri;
}
}
76 changes: 76 additions & 0 deletions roles/nginx_frontend/templates/nginx/https.conf.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# {{ nginx_frontend_domain }} — HTTPS frontend for EU server
# Managed by Ansible nginx_frontend role
#
# Routes:
# /sub/, /c/ → Raven-subscribe (127.0.0.1:{{ nginx_frontend_raven_port }})
# /api/ → Raven-subscribe admin API
# /health → Raven-subscribe health check
# {{ nginx_frontend_xhttp_path }} → Xray XHTTP (127.0.0.1:{{ nginx_frontend_xhttp_port }})

# ── Redirect HTTP → HTTPS ────────────────────────────────────────────────────
server {
listen 80;
server_name {{ nginx_frontend_domain }};
return 301 https://$host$request_uri;
}

# ── HTTPS ─────────────────────────────────────────────────────────────────────
server {
listen {{ nginx_frontend_listen_port }} ssl;
http2 on;
server_name {{ nginx_frontend_domain }};

ssl_certificate /etc/letsencrypt/live/{{ nginx_frontend_domain }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ nginx_frontend_domain }}/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

# ── Raven-subscribe: subscriptions ──────────────────────────────────────
location ~ ^/(sub|c)/ {
proxy_pass http://127.0.0.1:{{ nginx_frontend_raven_port }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 30s;
proxy_connect_timeout 5s;
}

# ── Raven-subscribe: admin API ───────────────────────────────────────────
location /api/ {
proxy_pass http://127.0.0.1:{{ nginx_frontend_raven_port }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 30s;
proxy_connect_timeout 5s;
}

# ── Raven-subscribe: health check ────────────────────────────────────────
location /health {
proxy_pass http://127.0.0.1:{{ nginx_frontend_raven_port }};
proxy_set_header Host $host;
proxy_read_timeout 5s;
proxy_connect_timeout 5s;
}

# ── Xray XHTTP ───────────────────────────────────────────────────────────
location {{ nginx_frontend_xhttp_path }} {
proxy_pass http://127.0.0.1:{{ nginx_frontend_xhttp_port }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "";
proxy_read_timeout 3600s;
proxy_connect_timeout 5s;
proxy_buffering off;
}

# ── Everything else → 404 ────────────────────────────────────────────────
location / {
return 404;
}
}
15 changes: 15 additions & 0 deletions roles/nginx_frontend/templates/nginx/stream.conf.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# {{ nginx_frontend_domain }} — nginx stream TCP relay for Xray VLESS Reality
# Managed by Ansible nginx_frontend role
# Proxies: external:{{ nginx_frontend_reality_port }} → 127.0.0.1:443 (Xray)
# This file is included inside stream {} block in nginx.conf

upstream xray_reality_local {
server 127.0.0.1:443;
}

server {
listen {{ nginx_frontend_reality_port }};
proxy_pass xray_reality_local;
proxy_connect_timeout 10s;
proxy_timeout 600s;
}
55 changes: 55 additions & 0 deletions roles/raven_subscribe/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---

raven_subscribe_github_repo: "alchemylink/raven-subscribe"
raven_subscribe_install_dir: "/usr/local/bin"
raven_subscribe_config_dir: "/etc/xray-subscription"
raven_subscribe_db_dir: "/var/lib/xray-subscription"
raven_subscribe_service_name: "xray-subscription"

raven_subscribe_listen_addr: ":8080"
raven_subscribe_sync_interval_seconds: 60
raven_subscribe_rate_limit_sub_per_min: 60
raven_subscribe_rate_limit_admin_per_min: 30

# Balancer settings (used in generated client subscription configs)
# Strategy: random, roundRobin, leastPing, leastLoad
raven_subscribe_balancer_strategy: "leastPing"
raven_subscribe_balancer_probe_url: "https://www.gstatic.com/generate_204"
raven_subscribe_balancer_probe_interval: "30s"

# The inbound tag Raven manages users in (must match an inbound in config.d)
raven_subscribe_api_inbound_tag: "vless-reality-in"

# Xray API address for user sync via gRPC. Requires xray_api enabled.
raven_subscribe_xray_api_addr: "127.0.0.1:10085"

# Xray config.d directory (must match xray role)
raven_subscribe_xray_config_dir: "/etc/xray/config.d"

# Per-inbound host overrides. Falls back to raven_subscribe_server_host when tag not listed.
# Set in secrets.yml. Empty = all inbounds use server_host.
raven_subscribe_inbound_hosts: {}

# Per-inbound port overrides. Falls back to inbound's own port when tag not listed.
# Set in secrets.yml. Example: {"vless-reality-in": 8444} for TCP relay on RU.
raven_subscribe_inbound_ports: {}

# Path to sing-box config file. Leave empty to disable sing-box sync.
# Example: "/etc/sing-box/config.json"
raven_subscribe_singbox_config: ""

# Enable/disable Xray and sing-box sync independently.
raven_subscribe_xray_enabled: true
raven_subscribe_singbox_enabled: false

# System user/group (shared with xray role — do NOT change)
raven_subscribe_user: "xrayuser"
raven_subscribe_group: "xrayuser"

# Xray service name (for systemd After= dependency)
raven_subscribe_xray_service_name: "xray"

##### Set these in secrets.yml (ansible-vault encrypted) #####
# raven_subscribe_server_host: "" # EU VPS public IP or domain
# raven_subscribe_base_url: "" # Public URL — must be relay domain, NOT direct EU IP
# raven_subscribe_admin_token: "" # Generate: openssl rand -hex 32
13 changes: 13 additions & 0 deletions roles/raven_subscribe/defaults/secrets.yml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
# Raven-subscribe secrets — copy to secrets.yml and encrypt:
# ansible-vault encrypt roles/raven_subscribe/defaults/secrets.yml

# Admin token for Raven-subscribe API (required)
# Generate: openssl rand -hex 32
raven_subscribe_admin_token: ""

# Public URL used in subscription links — must be the relay domain
raven_subscribe_base_url: "https://my.zirgate.com"

# EU VPS public IP or domain (used in generated client outbound addresses)
raven_subscribe_server_host: ""
9 changes: 9 additions & 0 deletions roles/raven_subscribe/handlers/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
- name: Reload systemd
ansible.builtin.systemd:
daemon_reload: true

- name: Restart raven-subscribe
ansible.builtin.service:
name: "{{ raven_subscribe_service_name }}"
state: restarted
Loading
Loading