From abeb2e0eb9e9ba2c9a88e1e33ac936c7bda42426 Mon Sep 17 00:00:00 2001 From: pyrschtjag Date: Fri, 28 Aug 2026 11:03:45 +0000 Subject: [PATCH] add xray-lists role --- roles/xray-lists/tasks/configure.yml | 62 ++++++++++ roles/xray-lists/tasks/install.yml | 108 ++++++++++++++++++ roles/xray-lists/tasks/main.yml | 57 +-------- .../templates/90-proxy-output.nft.j2 | 11 ++ .../templates/90-proxy-prerouting.nft.j2 | 26 +++++ roles/xray-lists/templates/90-proxy.nft.j2 | 31 ----- .../xray-lists/templates/xray-config.yaml.j2 | 4 + 7 files changed, 215 insertions(+), 84 deletions(-) create mode 100644 roles/xray-lists/tasks/configure.yml create mode 100644 roles/xray-lists/tasks/install.yml create mode 100644 roles/xray-lists/templates/90-proxy-output.nft.j2 create mode 100644 roles/xray-lists/templates/90-proxy-prerouting.nft.j2 delete mode 100644 roles/xray-lists/templates/90-proxy.nft.j2 diff --git a/roles/xray-lists/tasks/configure.yml b/roles/xray-lists/tasks/configure.yml new file mode 100644 index 0000000..885567d --- /dev/null +++ b/roles/xray-lists/tasks/configure.yml @@ -0,0 +1,62 @@ +--- +#- 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) }}'" + # quiet: true + # loop: "{{ query('ansible.builtin.subelements', _xray_hosts_with_policy | default([]), 'xray_policy', {'skip_missing': True}) }}" + # loop_control: + # label: "{{ item.0.inventory_hostname }} -> {{ item.1.bypass | default(item.1.proxy) }}" + +- name: render xray-lists config + ansible.builtin.template: + src: xray-config.yaml.j2 + dest: /var/lib/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-prerouting.nft + - 90-proxy-output.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-prerouting.nft.j2 + dest: /etc/nftables.d/90-proxy-prerouting.nft + mode: "0644" + notify: reload nftables + +- name: render proxy output + ansible.builtin.template: + src: 90-proxy-output.nft.j2 + dest: /etc/nftables.d/90-proxy-output.nft + mode: "0644" + notify: reload nftables diff --git a/roles/xray-lists/tasks/install.yml b/roles/xray-lists/tasks/install.yml new file mode 100644 index 0000000..1de635e --- /dev/null +++ b/roles/xray-lists/tasks/install.yml @@ -0,0 +1,108 @@ +--- +- 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 + +- name: enable and start xray-lists + ansible.builtin.systemd: + name: xray-lists.timer + enabled: true + state: started diff --git a/roles/xray-lists/tasks/main.yml b/roles/xray-lists/tasks/main.yml index 1fc578f..fcc7f68 100644 --- a/roles/xray-lists/tasks/main.yml +++ b/roles/xray-lists/tasks/main.yml @@ -1,55 +1,6 @@ --- -- 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: include xray-lists install + ansible.builtin.include_tasks: install.yml -- 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 +- name: include xray-lists configure + ansible.builtin.include_tasks: configure.yml diff --git a/roles/xray-lists/templates/90-proxy-output.nft.j2 b/roles/xray-lists/templates/90-proxy-output.nft.j2 new file mode 100644 index 0000000..67579a1 --- /dev/null +++ b/roles/xray-lists/templates/90-proxy-output.nft.j2 @@ -0,0 +1,11 @@ +#jinja2: trim_blocks: True, lstrip_blocks: True +{% macro set_daddr(rule) %} + {%- if rule in (xray_ip_sets | default([])) or rule in (xray_static_sets | default([])) -%} +{{ rule }}_ip + {%- elif rule in (xray_domain_sets | default([])) -%} +{{ rule }}_dom + {%- endif -%} +{% endmacro %} +{% for rule in output_rules | default([]) | sort %} +ip daddr @{{ set_daddr(rule) }} meta mark set {{ xray_fwmark }} accept +{% endfor %} diff --git a/roles/xray-lists/templates/90-proxy-prerouting.nft.j2 b/roles/xray-lists/templates/90-proxy-prerouting.nft.j2 new file mode 100644 index 0000000..1264d7b --- /dev/null +++ b/roles/xray-lists/templates/90-proxy-prerouting.nft.j2 @@ -0,0 +1,26 @@ +#jinja2: trim_blocks: True, lstrip_blocks: True +{% macro set_daddr(name) %} + {%- if name == 'all' %} +0.0.0.0/0 + {%- elif name in (xray_ip_sets | default([])) or name in (xray_static_sets | default([])) %} +@{{ name }}_ip + {%- elif name in (xray_domain_sets | default([])) %} +@{{ name }}_dom + {%- else %} +invalid_xray_set_{{ name }} + {% endif %} +{% endmacro %} +{% 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 %} + {% for rule in client.xray_policy %} + {% set target_set = rule.bypass | default(rule.proxy) %} + {% if rule.bypass is defined %} +meta l4proto { tcp, udp } ip saddr {{ src_ip }} ip daddr {{ set_daddr(target_set) }} accept + {% elif rule.proxy is defined %} +meta l4proto { tcp, udp } ip saddr {{ src_ip }} ip daddr {{ set_daddr(target_set) }} tproxy ip to :{{ xray_tproxy_port }} meta mark set {{ xray_fwmark }} accept + {% endif %} + {% endfor %} + {% endif %} +{% endfor %} diff --git a/roles/xray-lists/templates/90-proxy.nft.j2 b/roles/xray-lists/templates/90-proxy.nft.j2 deleted file mode 100644 index 07bde59..0000000 --- a/roles/xray-lists/templates/90-proxy.nft.j2 +++ /dev/null @@ -1,31 +0,0 @@ -#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 - {%- 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 -%} diff --git a/roles/xray-lists/templates/xray-config.yaml.j2 b/roles/xray-lists/templates/xray-config.yaml.j2 index 3f5e5c9..9cc3e7c 100644 --- a/roles/xray-lists/templates/xray-config.yaml.j2 +++ b/roles/xray-lists/templates/xray-config.yaml.j2 @@ -45,3 +45,7 @@ domain_sets: {% endfor %} {% endif %} {% endfor %} +output_rules: + {% for rule in output_rules %} + - {{ rule }} + {% endfor %}