initial commit

This commit is contained in:
2026-08-16 23:16:49 +00:00
commit 2dc83c0626
67 changed files with 1176 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
[defaults]
inventory = inventory/
roles_path = roles/
host_key_checking = False
forks = 8
[inventory]
enable_plugins = community.proxmox.proxmox, host_list, yaml, ini
cache = True
+3
View File
@@ -0,0 +1,3 @@
nft_managed_group: all
dnsmasq_managed_group: all
xray_managed_group: all
+73
View File
@@ -0,0 +1,73 @@
xray_ip_sets:
refilter:
urls:
- https://raw.githubusercontent.com/1andrevich/Re-filter-lists/refs/heads/main/community_ips.lst
- https://raw.githubusercontent.com/1andrevich/Re-filter-lists/refs/heads/main/discord_ips.lst
- https://raw.githubusercontent.com/1andrevich/Re-filter-lists/refs/heads/main/ipsum.lst
cdn:
urls:
- https://raw.githubusercontent.com/123jjck/cdn-ip-ranges/refs/heads/main/all/all_plain_ipv4.txt
telegram:
urls:
- https://raw.githubusercontent.com/fernvenue/telegram-cidr-list/refs/heads/master/CIDRv4.txt
russian_whitelist:
urls:
- https://raw.githubusercontent.com/hxehex/russia-mobile-internet-whitelist/refs/heads/main/cidrwhitelist.txt
- https://raw.githubusercontent.com/ebrasha/cidr-ip-ranges-by-country/refs/heads/master/CIDR/RU-ipv4-Hackers.Zone.txt
cloudflare:
static:
- 1.1.1.1
- 1.0.0.1
google:
urls:
- https://raw.githubusercontent.com/lord-alfred/ipranges/main/google/ipv4.txt
xray_domain_sets:
v2ray:
urls:
- https://raw.githubusercontent.com/v2ray/domain-list-community/refs/heads/master/data/spotify
- https://raw.githubusercontent.com/v2ray/domain-list-community/refs/heads/master/data/microsoft
- https://raw.githubusercontent.com/v2ray/domain-list-community/refs/heads/master/data/openai
torrent:
static:
- bt.t-ru.org
- bt2.t-ru.org
- bt3.t-ru.org
- bt4.t-ru.org
- rutracker.org
- rutracker.net
- tapochek.net
- nnmclub.to
- rutor.info
- bigfangroup.org
vps:
static:
- dev.oyacoi.ru
- vector.oyacoi.ru
terraform:
static:
- terraform.io
- hashicorp.com
xray_static_sets:
- private
xray_lists_global:
cache_dir: /var/lib/xray-lists/cache
output_dir: /var/lib/xray-lists/generated
dnsmasq_output: /var/lib/xray-lists/generated/nftsets.conf
proxy: "socks5h://127.0.0.1:1080"
proxy_user: "{{ lookup('env', 'SOCKS5_USERNAME') }}"
proxy_pass: "{{ lookup('env', 'SOCKS5_PASSWORD') }}"
http_timeout: 20
xray_tproxy_port: 61219
xray_fwmark: "0x00000001"
+5
View File
@@ -0,0 +1,5 @@
ansible_connection: community.proxmox.proxmox_pct_remote
ansible_host: 10.1.0.4
ansible_user: root
ansible_ssh_private_key_file: "~/.ssh/id_ed25519"
ansible_python_interpreter: /usr/bin/python3
+4
View File
@@ -0,0 +1,4 @@
nft_to:
- to: [workuter,oyacoi-odcm]
proto: tcp
port: 5000
+3
View File
@@ -0,0 +1,3 @@
dnsmasq:
- name: asf.oyacoi.ru
ip: 10.10.0.2
+3
View File
@@ -0,0 +1,3 @@
dnsmasq:
- name: bananawrt.oyacoi.ru
ip: 10.10.0.2
+3
View File
@@ -0,0 +1,3 @@
dnsmasq:
- name: bylampa.oyacoi.ru
ip: 10.10.0.2
+4
View File
@@ -0,0 +1,4 @@
nft_from:
- iface: [eth1,eth0.2]
to: camera0
proto: [tcp,udp]
+12
View File
@@ -0,0 +1,12 @@
nft_dst:
- iface: [eth0,eth0.2]
proto: [tcp,udp]
port: [3478,5349]
nft_from:
- iface: [eth0,eth0.2,eth0.3,eth0.4,wg0]
proto: [tcp,udp]
port: [3478,5349]
- iface: [eth0,eth0.2,eth0.3,eth0.4,wg0]
proto: udp
port: ["49152-65535"]
+3
View File
@@ -0,0 +1,3 @@
dnsmasq:
- name: proxmox.oyacoi.ru
ip: 10.10.0.2
+8
View File
@@ -0,0 +1,8 @@
nft_from:
- iface: wg0
proto: tcp
port: 22
dnsmasq:
- name: gitea.oyacoi.ru
ip: 10.10.0.2
+4
View File
@@ -0,0 +1,4 @@
nft_to:
- to: nginx
proto: tcp
port: [80, 81, 443, 444, 24445]
+3
View File
@@ -0,0 +1,3 @@
dnsmasq:
- name: jellyfin.oyacoi.ru
ip: 10.10.0.2
+13
View File
@@ -0,0 +1,13 @@
nft_dst:
- iface: [eth0,eth0.2]
proto: tcp
port: 25565
nft_from:
- iface: [eth0,wg0]
proto: tcp
port: 25565
dnsmasq:
- name: mcsmanager.oyacoi.ru
ip: 10.10.0.2
+7
View File
@@ -0,0 +1,7 @@
nft_to:
- to: workuter
proto: [tcp, udp]
port: 32765
- to: oyacoi-odcm
proto: [tcp, udp]
port: 32765
+42
View File
@@ -0,0 +1,42 @@
nft_to:
- to: vaultwarden
proto: tcp
port: 8000
- to: gitea
proto: tcp
port: 3000
- to: radicale
proto: tcp
port: 5232
- to: slskd
proto: tcp
port: 5030
- to: asf
proto: tcp
port: 1337
- to: rtorrent
proto: tcp
port: 80
- to: jellyfin
proto: tcp
port: 8096
- to: prosody
proto: tcp
port: 5280
- to: torrserver
proto: tcp
port: 8090
- to: prowlarr
proto: tcp
port: 9696
- to: prowlarr #jackett
proto: tcp
port: 9117
- to: bylampa
proto: tcp
port: 80
nft_from:
- iface: [eth0,eth0.2]
proto: tcp
port: [80,443,24444]
+3
View File
@@ -0,0 +1,3 @@
dnsmasq:
- name: ntfy.oyacoi.ru
ip: 10.10.0.2
+15
View File
@@ -0,0 +1,15 @@
nft_to:
- to: nfs
proto: [tcp, udp]
port: [2049, 111, 32765, 32767]
- to: [zone:eth0.10,zone:eth0.11,zone:eth0.12]
proto: tcp
port: 22
- to: [xiawrt,rbpi4]
proto: tcp
port: 22
xray_policy:
- bypass: private
- bypass: russian_whitelist
- proxy: all
+20
View File
@@ -0,0 +1,20 @@
nft_dst:
- iface: [eth0,eth0.2]
proto: tcp
port: [5000,5222,5223,5280,5270,5269]
nft_to:
- to: pgsql
proto: tcp
port: 5432
nft_from:
- iface: [eth0,eth0.2,wg0]
proto: tcp
port: [5000,5222,5223,5269,5270,5280]
dnsmasq:
- name: talk.oyacoi.ru
ip: 10.10.0.2
- name: upload.oyacoi.ru
ip: 10.10.0.2
+5
View File
@@ -0,0 +1,5 @@
dnsmasq:
- name: prowlarr.oyacoi.ru
ip: 10.10.0.2
- name: jackett.oyacoi.ru
ip: 10.10.0.2
+8
View File
@@ -0,0 +1,8 @@
nft_to:
- to: ps3netsrv
proto: tcp
port: 38008
dnsmasq:
- name: ps3.oyacoi.ru
ip: 10.10.0.2
+3
View File
@@ -0,0 +1,3 @@
dnsmasq:
- name: radicale.oyacoi.ru
ip: 10.10.0.2
+3
View File
@@ -0,0 +1,3 @@
dnsmasq:
- name: rustdesk.dttx.ru
ip: 176.119.157.97
+6
View File
@@ -0,0 +1,6 @@
ansible_host: 10.1.0.1
ansible_connection: ssh
ansible_user: root
ansible_ssh_private_key_file: ~/.ssh/id_ed25519
zone_iface: eth0
container_ip: 10.1.0.1
+13
View File
@@ -0,0 +1,13 @@
nft_dst:
- iface: eth1
proto: tcp
port: ["6890-6899"]
nft_from:
- iface: eth1
proto: tcp
port: ["6890-6899"]
dnsmasq:
- name: rutorrent.oyacoi.ru
ip: 10.10.0.2
+4
View File
@@ -0,0 +1,4 @@
nft_to:
- to: firebat
proto: tcp
port: [22, 8006]
+13
View File
@@ -0,0 +1,13 @@
nft_dst:
- iface: eth1
proto: tcp
port: 50300
nft_from:
- iface: eth1
proto: tcp
port: 50300
dnsmasq:
- name: slskd.oyacoi.ru
ip: 10.10.0.2
+9
View File
@@ -0,0 +1,9 @@
nft_dst:
- iface: eth0
proto: udp
port: 2456
nft_from:
- iface: [eth0,wg0]
proto: udp
port: [2456,2457]
+18
View File
@@ -0,0 +1,18 @@
nft_dst:
- iface: eth1
proto: [tcp, udp]
port: 6990
nft_to:
- to: flaresolverr
proto: tcp
port: 8191
nft_from:
- iface: eth1
proto: [tcp,udp]
port: 6990
dnsmasq:
- name: torrserver.oyacoi.ru
ip: 10.10.0.2
+8
View File
@@ -0,0 +1,8 @@
nft_to:
- to: pgsql
proto: tcp
port: 5432
dnsmasq:
- name: vaultwarden.oyacoi.ru
ip: 10.10.0.2
+10
View File
@@ -0,0 +1,10 @@
nft_to:
- to: nfs
proto: [tcp, udp]
port: [2049, 111, 32765, 32767]
- to: [zone:eth0.10,zone:eth0.11,zone:eth0.12]
proto: tcp
port: 22
- to: [xiawrt,rbpi4]
proto: tcp
port: 22
+8
View File
@@ -0,0 +1,8 @@
nft_to:
- to: zabbix
proto: tcp
port: 10051
dnsmasq:
- name: xiawrt.oyacoi.ru
ip: 10.10.0.2
+11
View File
@@ -0,0 +1,11 @@
nft_to:
- to: "zone:eth0.11"
proto: tcp
port: 10050
- to: xiawrt
proto: tcp
port: 10050
dnsmasq:
- name: zabbix.oyacoi.ru
ip: 10.10.0.2
+13
View File
@@ -0,0 +1,13 @@
plugin: community.proxmox.proxmox
url: https://10.1.0.4:8006
user: root@pam
password: "{{ lookup('env', 'PROXMOX_PASSWORD') }}"
validate_certs: false
want_facts: true
filter_by_types:
- lxc
compose:
zone_iface: "'eth0.' ~ proxmox_net0.tag"
container_ip: "proxmox_net0.ip | default('') | regex_replace('/.*', '')"
+82
View File
@@ -0,0 +1,82 @@
all:
children:
static:
hosts:
workuter:
container_ip: "10.1.0.2"
zone_iface: "eth0"
oyacoi-odcm:
container_ip: "10.1.0.3"
zone_iface: "eth0"
firebat:
container_ip: "10.1.0.4"
zone_iface: "eth0"
ansible_host: 10.1.0.4
ansible_user: root
ansible_ssh_private_key_file: "~/.ssh/id_ed25519"
ps2:
container_ip: "10.1.0.5"
zone_iface: "eth0"
ps3:
container_ip: "10.1.0.6"
zone_iface: "eth0"
tanix:
container_ip: "10.1.0.8"
zone_iface: "eth0"
bananawrt:
container_ip: "10.1.0.100"
zone_iface: "eth0"
ps4:
container_ip: "10.2.0.2"
zone_iface: "eth0.2"
note13:
container_ip: "10.2.0.3"
zone_iface: "eth0.2"
iphone11:
container_ip: "10.2.0.4"
zone_iface: "eth0.2"
huawei-tablet:
container_ip: "10.2.0.5"
zone_iface: "eth0.2"
uni:
container_ip: "10.2.0.6"
zone_iface: "eth0.2"
papperwhite:
container_ip: "10.2.0.7"
zone_iface: "eth0.2"
psp:
container_ip: "10.2.0.8"
zone_iface: "eth0.2"
dsi:
container_ip: "10.2.0.9"
zone_iface: "eth0.2"
3ds:
container_ip: "10.2.0.10"
zone_iface: "eth0.2"
camera0:
container_ip: "10.3.0.5"
zone_iface: "eth0.3"
xiawrt:
container_ip: "10.250.250.1"
zone_iface: "wg0"
rbpi4:
container_ip: "10.250.250.5"
zone_iface: "wg0"
+5
View File
@@ -0,0 +1,5 @@
---
- hosts: router
become: true
roles:
- dnsmasq
+14
View File
@@ -0,0 +1,14 @@
---
- hosts: router
become: true
roles:
- xray-lists
- dnsmasq
- nftables
tasks:
- name: enable update timer
systemd:
name: xray-lists.timer
enabled: yes
state: started
+15
View File
@@ -0,0 +1,15 @@
---
- hosts: router
become: yes
roles:
- router
- xray-lists
- dnsmasq
- nftables
tasks:
- name: enable update timer
systemd:
name: xray-lists.timer
enabled: yes
state: started
+5
View File
@@ -0,0 +1,5 @@
---
- hosts: router
become: true
roles:
- xray-lists
+2
View File
@@ -0,0 +1,2 @@
collections:
- name: community.proxmox
+5
View File
@@ -0,0 +1,5 @@
---
- name: restart dnsmasq
ansible.builtin.service:
name: dnsmasq
state: restarted
+20
View File
@@ -0,0 +1,20 @@
---
- name: ensure /etc/dnsmasq.d exists
ansible.builtin.file:
path: /etc/dnsmasq.d
state: directory
mode: "0755"
- name: render local
ansible.builtin.template:
src: 90-local.conf.j2
dest: /etc/dnsmasq.d/90-local.conf
mode: "0644"
notify: restart dnsmasq
- name: render domain
ansible.builtin.template:
src: 90-domains.conf.j2
dest: /etc/dnsmasq.d/90-domains.conf
mode: "0644"
notify: restart dnsmasq
@@ -0,0 +1,15 @@
#jinja2: trim_blocks: True, lstrip_blocks: True
{% for item in groups[dnsmasq_managed_group] | sort %}
{% set client = hostvars[item] %}
{% if 'dnsmasq' in client and client.dnsmasq %}
{% set default_ip = client.container_ip | default(client.ansible_host | default(none)) %}
{% set domains = client.dnsmasq if (client.dnsmasq is iterable and client.dnsmasq is not string) else [client.dnsmasq] %}
{% for d in domains %}
{% set entry = d if (d is mapping) else {'name': d} %}
{% set ip = entry.ip | default(default_ip) %}
{% if ip %}
host-record={{ entry.name }},{{ ip }}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
+8
View File
@@ -0,0 +1,8 @@
#jinja2: trim_blocks: True, lstrip_blocks: True
{% for item in groups[dnsmasq_managed_group] | sort %}
{% set client = hostvars[item] %}
{% set ip = client.container_ip | default(client.ansible_host | default(none)) %}
{% if ip %}
host-record={{ item }},{{ item }}.lan,{{ ip }}
{% endif %}
{% endfor %}
+49
View File
@@ -0,0 +1,49 @@
flowtable ft {
hook ingress priority filter
devices = { eth0, eth1 }
}
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
ct state invalid drop
iif lo accept
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
meta mark 0x00000001 accept
iifname eth0 tcp dport 22 accept
iifname eth0.11 tcp dport 22 accept
iifname eth1 udp dport 51820 accept
iifname { eth0, eth0.2, eth0.3, eth0.4, eth0.10, eth0.11, eth0.12 } udp dport 53 accept
iifname { eth0, eth0.2, eth0.3, eth0.4, eth0.10, eth0.11, eth0.12 } tcp dport 53 accept
iifname eth0.3 udp dport 67 accept
iifname eth1 udp dport 68 accept
#include "/etc/nftables.d/90-input.nft"
}
chain forward {
type filter hook forward priority filter; policy drop;
ct state established,related accept
ct state invalid drop
iifname { eth0, eth0.2, eth0.3, eth0.4, eth0.10, eth0.11, eth0.12 } oifname eth1 ct state new flow add @ft
iifname { eth0, eth0.2, eth0.3, eth0.4, eth0.10, eth0.11, eth0.12 } oifname eth1 accept
tcp flags syn tcp option maxseg size set rt mtu
include "/etc/nftables.d/90-forward.nft"
}
chain output {
type route hook output priority filter; policy accept;
#include "/etc/nftables.d/90-output.nft"
}
+9
View File
@@ -0,0 +1,9 @@
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
oifname eth1 masquerade
}
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
include "/etc/nftables.d/90-dstnat.nft"
}
+35
View File
@@ -0,0 +1,35 @@
chain vpn_prerouting_dnat {
type nat hook prerouting priority dstnat - 5; policy accept;
iifname wg0 ip daddr 10.250.251.0/24 counter dnat ip prefix to 10.1.0.0/24
iifname wg0 ip daddr 10.250.252.0/24 counter dnat ip prefix to 10.2.0.0/24
iifname wg0 ip daddr 10.250.253.0/24 counter dnat ip prefix to 10.10.0.0/24
iifname wg0 ip daddr 10.250.254.0/24 counter dnat ip prefix to 10.11.0.0/24
iifname wg0 ip daddr 10.250.255.0/24 counter dnat ip prefix to 10.12.0.0/24
iifname wg0 ip daddr 10.250.249.0/24 counter dnat ip prefix to 10.13.0.0/24
}
chain vpn_postrouting_snat {
type nat hook postrouting priority srcnat; policy accept;
oifname wg0 ip saddr 10.1.0.0/24 counter snat ip prefix to 10.250.251.0/24
oifname wg0 ip saddr 10.2.0.0/24 counter snat ip prefix to 10.250.252.0/24
oifname wg0 ip saddr 10.10.0.0/24 counter snat ip prefix to 10.250.253.0/24
oifname wg0 ip saddr 10.11.0.0/24 counter snat ip prefix to 10.250.254.0/24
oifname wg0 ip saddr 10.12.0.0/24 counter snat ip prefix to 10.250.255.0/24
oifname wg0 ip saddr 10.13.0.0/24 counter snat ip prefix to 10.250.249.0/24
}
chain vpn_prerouting_pbr {
type filter hook prerouting priority mangle - 10; policy accept;
iifname wg0 ct state new counter ct mark set 0x000000c7
ip daddr 10.0.0.0/8 return
iifname != "wg0" ct mark 0x000000c7 counter mark set 0x000000c7
}
chain vpn_output_pbr {
type route hook output priority mangle - 10; policy accept;
ct mark 0x000000c7 counter meta mark set 0x000000c7
}
+15
View File
@@ -0,0 +1,15 @@
chain proxy_prerouting {
type filter hook prerouting priority filter - 50; policy accept;
fib daddr type local accept
include "/etc/nftables.d/90-proxy.nft"
}
chain proxy_output {
type route hook output priority mangle; policy accept;
#meta mark 0x000000ff return
#meta l4proto { tcp, udp } ip daddr @cloudflare_ip meta mark set 0x00000001 accept
}
+6
View File
@@ -0,0 +1,6 @@
set private_ip {
type ipv4_addr
flags interval
auto-merge
elements = { 10.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.0.0.0/24, 192.0.2.0/24, 192.88.99.0/24, 192.168.0.0/16, 198.18.0.0/15, 198.51.100.0/24, 203.0.113.0/24, 224.0.0.0/4, 240.0.0.0/4 }
}
+15
View File
@@ -0,0 +1,15 @@
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
include "/etc/nftables.d/40-sets.nft"
include "/etc/nftables.d/90-sets.nft"
include "/etc/nftables.d/10-filter.nft"
include "/etc/nftables.d/20-vpn.nft"
include "/etc/nftables.d/30-proxy.nft"
}
table ip nat {
include "/etc/nftables.d/10-nat.nft"
}
+4
View File
@@ -0,0 +1,4 @@
---
- name: reload nftables
ansible.builtin.command: nft -f /etc/nftables.conf
listen: reload nftables
+41
View File
@@ -0,0 +1,41 @@
---
- name: ensure /etc/nftables.d exists
ansible.builtin.file:
path: /etc/nftables.d
state: directory
mode: "0755"
- name: deploy nftables rule
ansible.builtin.copy:
src: "{{ item }}"
dest: "/etc/nftables.d/{{ item }}"
mode: "0644"
loop:
- 10-filter.nft
- 10-nat.nft
- 20-vpn.nft
- 30-proxy.nft
- 40-sets.nft
notify: reload nftables
- name: render forward
ansible.builtin.template:
src: 90-forward.nft.j2
dest: /etc/nftables.d/90-forward.nft
mode: "0644"
notify: reload nftables
- name: render dstnat
ansible.builtin.template:
src: 90-dstnat.nft.j2
dest: /etc/nftables.d/90-dstnat.nft
mode: "0644"
notify: reload nftables
- name: deploy nftables.conf
ansible.builtin.copy:
src: nftables.conf
dest: /etc/nftables.conf
mode: "0644"
validate: "nft -c -f %s"
notify: reload nftables
+31
View File
@@ -0,0 +1,31 @@
#jinja2: trim_blocks: True, lstrip_blocks: True
{% macro render_dstnat_rule(ifaces, proto, port, target_ip, item_name) %}
{% set lines = [] %}
{% set active_ifaces = ifaces if (ifaces is iterable and ifaces is not string) else [ifaces] %}
{% for current_iface in active_ifaces %}
{% set comment_str = ' comment "' ~ current_iface ~ ' -> ' ~ item_name ~ '"' %}
{% set rule_line = 'iifname "' ~ current_iface ~ '" ' ~ proto ~ ' dport ' ~ port ~ ' counter dnat ip to ' ~ target_ip ~ ':' ~ port ~ comment_str %}
{% set _ = lines.append(rule_line) %}
{% endfor %}
{{ lines | join('\n') }}
{% endmacro %}
{% filter regex_replace('\n[ \t]*\n+', '\n') %}
{% for item in groups[nft_managed_group] | sort %}
{% set client = hostvars[item] %}
{% if 'nft_dst' in client and client.nft_dst is not none %}
{% set target_ip = client.container_ip | default(client.ansible_host | default(item)) %}
{% set raw_expose = client.nft_dst %}
{% set exposes = raw_expose if (raw_expose is iterable and raw_expose is not string and raw_expose is not mapping) else [raw_expose] %}
{% for expose in exposes %}
{% set protos = expose.proto if (expose.proto is defined and expose.proto is iterable and expose.proto is not string) else [expose.proto | default('tcp')] %}
{% set ports = expose.port if (expose.port is defined and expose.port is iterable and expose.port is not string) else [expose.port] %}
{% set ifaces = expose.iface %}
{% for p in protos | sort %}
{% for port in ports | sort %}
{{ render_dstnat_rule(ifaces, p, port, target_ip, item) }}
{% endfor %}
{% endfor %}
{% endfor %}
{% endif %}
{% endfor %}
{% endfilter %}
@@ -0,0 +1,95 @@
#jinja2: trim_blocks: True, lstrip_blocks: True
{% set ip_to_host = {} %}
{% for host in groups['all'] | default([]) %}
{% set hv = hostvars[host] | default({}) %}
{% if hv.ansible_host is defined and (hv.ansible_connection | default('')) != 'community.proxmox.proxmox_pct_remote' %}
{% set _ = ip_to_host.update({(hv.ansible_host | string): host}) %}
{% endif %}
{% if hv.container_ip is defined and hv.container_ip %}
{% set _ = ip_to_host.update({(hv.container_ip | string): host}) %}
{% endif %}
{% endfor %}
{% macro render_rule(service_name, iif, saddr, oif, daddr, protos, ports, dest_name) %}
{% set lines = [] %}
{% set iifs = iif if (iif is iterable and iif is not string) else [iif] %}
{% set oifs = oif if (oif is iterable and oif is not string) else [oif] %}
{% set active_protos = protos | sort if protos | length > 0 else [none] %}
{% set active_ports = ports if ports | length > 0 else [none] %}
{% for current_iif in iifs %}
{% for current_oif in oifs %}
{% for p in active_protos %}
{% for port in active_ports %}
{% set proto_rule = '' %}
{% if p and port %}
{% set proto_rule = p ~ ' dport ' ~ port %}
{% elif p %}
{% set proto_rule = 'meta l4proto ' ~ p %}
{% endif %}
{# Resolve source name: prefer an explicit host resolved via saddr, otherwise fall back
to the current interface for this specific line (not the whole iif list/service_name) #}
{% set resolved_service_name = service_name if service_name else current_iif %}
{% if saddr and ip_to_host[saddr | string] is defined %}
{% set resolved_service_name = ip_to_host[saddr | string] %}
{% endif %}
{# Resolve destination IP to inventory hostname only for comment #}
{% set resolved_dest_name = dest_name %}
{% if daddr and ip_to_host[daddr | string] is defined %}
{% set resolved_dest_name = ip_to_host[daddr | string] %}
{% endif %}
{% set comment_text = resolved_service_name ~ ' -> ' ~ resolved_dest_name %}
{% set comment_str = ' comment "' ~ comment_text ~ '"' %}
{% set parts = ['iifname "' ~ current_iif ~ '"'] %}
{% if saddr %}
{% set _ = parts.append('ip saddr ' ~ saddr) %}
{% endif %}
{% if current_oif %}
{% set _ = parts.append('oifname "' ~ current_oif ~ '"') %}
{% endif %}
{% if daddr %}
{% set _ = parts.append('ip daddr ' ~ daddr) %}
{% endif %}
{% if proto_rule %}
{% set _ = parts.append(proto_rule) %}
{% endif %}
{% set _ = parts.append('counter accept' ~ comment_str) %}
{% set _ = lines.append(parts | join(' ')) %}
{% endfor %}
{% endfor %}
{% endfor %}
{% endfor %}
{{ lines | join('\n') }}
{% endmacro %}
{% filter regex_replace('\n[ \t]*\n+', '\n') %}
{# === Managed Hosts Forward Rules === #}
{% for item in groups[nft_managed_group] | sort %}
{% set client = hostvars[item] %}
{% if client.nft_to is defined and client.nft_to is not none %}
{% set raw_rules = client.nft_to if (client.nft_to is iterable and client.nft_to is not string and client.nft_to is not mapping) else [client.nft_to] %}
{% for r in raw_rules %}
{% set rule_dict = r if (r is mapping) else {'to': r} %}
{% set raw_dests = rule_dict.to if (rule_dict.to is iterable and rule_dict.to is not string) else [rule_dict.to] %}
{% set protos = rule_dict.proto if (rule_dict.proto is defined and rule_dict.proto is iterable and rule_dict.proto is not string) else ([rule_dict.proto] if rule_dict.proto is defined else []) %}
{% set ports = rule_dict.port if (rule_dict.port is defined and rule_dict.port is iterable and rule_dict.port is not string) else ([rule_dict.port] if rule_dict.port is defined else []) %}
{% for dest in raw_dests %}
{% set dest_name = dest | regex_replace('^zone:', '') %}
{% if dest.startswith('zone:') %}
{{ render_rule(item, client.zone_iface, client.container_ip, dest.split(':')[1], none, protos, ports, dest_name) }}
{% else %}
{{ render_rule(item, client.zone_iface, client.container_ip, hostvars[dest].zone_iface, hostvars[dest].container_ip, protos, ports, dest_name) }}
{% endif %}
{% endfor %}
{% endfor %}
{% endif %}
{% endfor %}
{% for item in groups[nft_managed_group] | sort %}
{% set client = hostvars[item] %}
{% if client.nft_from is defined and client.nft_from is not none %}
{% set raw_from_rules = client.nft_from if (client.nft_from is iterable and client.nft_from is not string and client.nft_from is not mapping) else [client.nft_from] %}
{% for r in raw_from_rules %}
{% set protos = r.proto if (r.proto is defined and r.proto is iterable and r.proto is not string) else ([r.proto] if r.proto is defined else []) %}
{% set ports = r.port if (r.port is defined and r.port is iterable and r.port is not string) else ([r.port] if r.port is defined else []) %}
{{ render_rule(none, r.iface, none, client.zone_iface, client.container_ip, protos, ports, item) }}
{% endfor %}
{% endif %}
{% endfor %}
{% endfilter %}
+52
View File
@@ -0,0 +1,52 @@
auto lo
iface lo inet loopback
post-up ip rule add fwmark 0x1 lookup 100 2>/dev/null || true
post-up ip route add local 0.0.0.0/0 dev lo table 100 2>/dev/null || true
pre-down ip route del local 0.0.0.0/0 dev lo table 100 2>/dev/null || true
pre-down ip rule del fwmark 0x1 lookup 100 2>/dev/null || true
auto eth0
iface eth0 inet manual
address 10.1.0.1/24
auto eth0.2
iface eth0.2 inet static
address 10.2.0.1/24
vlan-raw-device eth0
auto eth0.3
iface eth0.3 inet static
address 10.3.0.1/24
vlan-raw-device eth0
auto eth0.4
iface eth0.4 inet static
address 10.4.0.1/24
vlan-raw-device eth0
auto eth0.10
iface eth0.10 inet static
address 10.10.0.1/24
vlan-raw-device eth0
auto eth0.11
iface eth0.11 inet static
address 10.11.0.1/24
vlan-raw-device eth0
auto eth0.12
iface eth0.12 inet static
address 10.12.0.1/24
vlan-raw-device eth0
auto eth1
iface eth1 inet dhcp
auto wg0
iface wg0 inet manual
post-up ip route add 10.250.250.0/24 dev wg0 2>/dev/null || true
post-up ip rule add fwmark 0xc7 lookup 199 2>/dev/null || true
post-up ip route add default dev wg0 table 199 2>/dev/null || true
pre-down ip route del default dev wg0 table 199 2>/dev/null || true
pre-down ip rule del fwmark 0xc7 lookup 199 2>/dev/null || true
pre-down ip route del 10.250.250.0/24 dev wg0 2>/dev/null || true
@@ -0,0 +1 @@
net.ipv4.ip_forward=1
@@ -0,0 +1,2 @@
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.wg0.rp_filter = 0
+3
View File
@@ -0,0 +1,3 @@
---
- name: reload ifupdown2
command: ifreload -a
+6
View File
@@ -0,0 +1,6 @@
---
- name: include network configuration
include_tasks: network.yml
- name: include xray-lists configuration
include_tasks: xray_lists.yml
+9
View File
@@ -0,0 +1,9 @@
---
- name: deploy ifupdown config
copy:
src: ifupdown2/interfaces
dest: /etc/network/interfaces
owner: root
group: root
mode: '0644'
notify: reload ifupdown2
+99
View File
@@ -0,0 +1,99 @@
---
- name: install required system packages
apt:
name:
- python3-venv
- git
state: present
update_cache: yes
- name: ensure base directories exist
file:
path: "{{ item }}"
state: directory
owner: root
group: root
mode: '0755'
loop:
- /opt/xray-lists
- /var/lib/xray-lists
- name: clone xray-lists repository
git:
repo: 'https://gitea.oyacoi.ru/pyrschtjag/xray-lists'
dest: /opt/xray-lists-src
version: main
force: yes
- name: check if xray-lists is installed
command: /opt/xray-lists/venv/bin/pip show xray-lists
register: pip_check
changed_when: false
failed_when: false
- name: install xray-lists package into venv
command: /opt/xray-lists/venv/bin/pip install -e /opt/xray-lists-src[socks]
when: pip_check.rc != 0
- name: deploy update helper script
copy:
dest: /var/lib/xray-lists/update.sh
owner: root
group: root
mode: '0755'
content: |
#!/bin/sh
set -e
out=$(/opt/xray-lists/venv/bin/xray-lists)
echo "$out"
dns_changed=0
elements_changed=0
if echo "$out" | grep -A 10 "changed:" | grep -q "nftsets.conf"; then dns_changed=1; fi
if echo "$out" | grep -A 10 "changed:" | grep -q "\.elements\.nft"; then elements_changed=1; fi
if [ "$dns_changed" -eq 1 ] && [ "$elements_changed" -eq 1 ]; then exit 12;
elif [ "$dns_changed" -eq 1 ]; then exit 10;
elif [ "$elements_changed" -eq 1 ]; then exit 11;
fi
exit 0
- name: deploy systemd service unit
copy:
dest: /etc/systemd/system/xray-lists.service
owner: root
group: root
mode: '0644'
content: |
[Unit]
Description=Update Xray lists
[Service]
Type=oneshot
ExecStart=/bin/sh -c '\
/var/lib/xray-lists/update.sh; \
rc=$$?; \
case "$$rc" in \
10) systemctl restart dnsmasq ;; \
11) nft -f /etc/nftables.conf ;; \
12) nft -f /etc/nftables.conf && systemctl restart dnsmasq ;; \
esac'
- name: deploy systemd timer unit
copy:
dest: /etc/systemd/system/xray-lists.timer
owner: root
group: root
mode: '0644'
content: |
[Unit]
Description=Run xray-lists update daily and on boot
[Timer]
OnBootSec=5min
OnUnitActiveSec=12h
Persistent=true
[Install]
WantedBy=timers.target
+16
View File
@@ -0,0 +1,16 @@
---
- name: reload nftables
ansible.builtin.command: nft -f /etc/nftables.conf
listen: reload nftables
- name: restart dnsmasq
ansible.builtin.service:
name: dnsmasq
state: restarted
- name: restart xray-lists timer
ansible.builtin.systemd:
name: xray-lists.timer
state: restarted
daemon_reload: yes
listen: restart xray-lists timer
+55
View File
@@ -0,0 +1,55 @@
---
- name: collect xray policy hosts
ansible.builtin.set_fact:
_xray_hosts_with_policy: >-
{{
(_xray_hosts_with_policy | default([]))
+ [{'inventory_hostname': item, 'xray_policy': hostvars[item].xray_policy}]
}}
loop: "{{ groups[xray_managed_group] }}"
when: hostvars[item].xray_policy is defined
- name: validate xray policy sets
ansible.builtin.assert:
that: >-
(item.1.bypass | default(item.1.proxy)) == 'all'
or (item.1.bypass | default(item.1.proxy)) in xray_ip_sets
or (item.1.bypass | default(item.1.proxy)) in xray_domain_sets
or (item.1.bypass | default(item.1.proxy)) in (xray_static_sets | default([]))
fail_msg: >-
host {{ item.0.inventory_hostname }}: unknown xray set
'{{ item.1.bypass | default(item.1.proxy) }}' in xray_policy
loop: "{{ query('ansible.builtin.subelements', _xray_hosts_with_policy | default([]), 'xray_policy', {'skip_missing': True}) }}"
loop_control:
label: "{{ item.0.inventory_hostname }} -> {{ item.1 }}"
- name: render xray-lists config
ansible.builtin.template:
src: xray-config.yaml.j2
dest: /etc/xray-lists/config.yaml
mode: "0640"
notify: restart xray-lists timer
- name: bootstrap empty config files
ansible.builtin.copy:
dest: "/etc/nftables.d/{{ item }}"
content: ""
force: false
mode: "0644"
loop:
- 90-sets.nft
- 90-proxy.nft
- name: render nft sets
ansible.builtin.template:
src: 90-sets.nft.j2
dest: /etc/nftables.d/90-sets.nft
mode: "0644"
notify: reload nftables
- name: render proxy prerouting
ansible.builtin.template:
src: 90-proxy.nft.j2
dest: /etc/nftables.d/90-proxy.nft
mode: "0644"
notify: reload nftables
@@ -0,0 +1,31 @@
#jinja2: trim_blocks: True, lstrip_blocks: True
{%- macro set_ref(name) -%}
{%- if name == 'all' -%}
0.0.0.0/0
{%- elif name in xray_ip_sets or name in (xray_static_sets | default([])) -%}
@{{ name }}_ip
{%- elif name in xray_domain_sets -%}
@{{ name_dom }}_dom
{%- else -%}
INVALID_XRAY_SET_{{ name }}
{%- endif -%}
{%- endmacro -%}
{%- set rules_list = [] -%}
{%- for item in groups[xray_managed_group] | default([]) | sort -%}
{%- set client = hostvars[item] -%}
{%- if client.xray_policy is defined -%}
{%- set src_ip = client.container_ip | default(client.ansible_host | default(item)) -%}
{%- for rule in client.xray_policy -%}
{%- if rule.bypass is defined -%}
{%- set _ = rules_list.append("meta l4proto { tcp, udp } ip saddr " ~ src_ip ~ " ip daddr " ~ set_ref(rule.bypass) ~ " accept") -%}
{%- elif rule.proxy is defined -%}
{%- set _ = rules_list.append("meta l4proto { tcp, udp } ip saddr " ~ src_ip ~ " ip daddr " ~ set_ref(rule.proxy) ~ " tproxy ip to :" ~ (xray_tproxy_port | default(61219) | string) ~ " meta mark set " ~ (xray_fwmark | default('0x00000001')) ~ " accept") -%}
{%- endif -%}
{%- endfor -%}
{%- endif -%}
{%- endfor -%}
{%- if rules_list | length > 0 -%}
{{- rules_list | join('\n') -}}
{%- endif -%}
+15
View File
@@ -0,0 +1,15 @@
#jinja2: trim_blocks: True, lstrip_blocks: True
{%- for id, item in xray_ip_sets.items() -%}
set {{ id }}_ip {
type ipv4_addr
flags interval
auto-merge
include "{{ xray_lists_global.output_dir }}/{{ id }}.elements.nft"
}
{% endfor -%}
{%- for id, item in xray_domain_sets.items() -%}
set {{ id }}_dom {
type ipv4_addr
flags interval
}
{% endfor -%}
@@ -0,0 +1,47 @@
#jinja2: trim_blocks: True, lstrip_blocks: True
global:
cache_dir: {{ xray_lists_global.cache_dir }}
output_dir: {{ xray_lists_global.output_dir }}
dnsmasq_output: {{ xray_lists_global.dnsmasq_output }}
{% if xray_lists_global.proxy is defined %}
proxy: "{{ xray_lists_global.proxy }}"
{% endif %}
{% if xray_lists_global.proxy_user is defined %}
proxy_user: "{{ xray_lists_global.proxy_user }}"
proxy_pass: "{{ xray_lists_global.proxy_pass }}"
{% endif %}
http_timeout: {{ xray_lists_global.http_timeout | default(20) }}
ip_sets:
{% for id, item in xray_ip_sets.items() %}
- id: {{ id }}
output: {{ id }}_ip.elements.nft
{% if item.static is defined %}
static:
{% for s in item.static %}
- {{ s }}
{% endfor %}
{% endif %}
{% if item.urls is defined %}
urls:
{% for u in item.urls %}
- {{ u }}
{% endfor %}
{% endif %}
{% endfor %}
domain_sets:
{% for id, item in xray_domain_sets.items() %}
- id: {{ id }}
dnsmasq_target: "4#inet#filter#{{ id }}_dom"
{% if item.static is defined %}
static:
{% for s in item.static %}
- {{ s }}
{% endfor %}
{% endif %}
{% if item.urls is defined %}
urls:
{% for u in item.urls %}
- {{ u }}
{% endfor %}
{% endif %}
{% endfor %}