chore: доделываем поднятие сервера

This commit is contained in:
protokey committed 2026-09-25 16:56:50 +04:00
1 parent adab286884
commit 8532557ef6
18 files changed
+749 -37

No files matched your search

+10
View File
@@ -31,6 +31,16 @@
ansible.builtin.command: sysctl --system
changed_when: false
- name: Remove DNS over TLS config
ansible.builtin.file:
path: /etc/systemd/resolved.conf.d/00-serverus.conf
state: absent
# systemd-resolved остается: без него /etc/resolv.conf указывает в никуда
- name: Restart systemd-resolved
ansible.builtin.command: systemctl try-restart systemd-resolved
changed_when: false
- name: Remove sshd drop-in
ansible.builtin.file:
path: /etc/ssh/sshd_config.d/00-serverus.conf
+44
View File
@@ -0,0 +1,44 @@
# Системный резолвер: systemd-resolved, только DNS-over-TLS к Cloudflare.
# Строгий режим: без TLS запрос не уйдет, открытым текстом DNS не утекает. Резервных серверов нет.
- name: Install systemd-resolved
ansible.builtin.apt:
name: systemd-resolved
state: present
- name: Create resolved drop-in directory
ansible.builtin.file:
path: /etc/systemd/resolved.conf.d
state: directory
mode: "0755"
- name: Configure DNS over TLS
ansible.builtin.copy:
dest: /etc/systemd/resolved.conf.d/00-serverus.conf
mode: "0644"
content: |
[Resolve]
DNS=1.1.1.1#cloudflare-dns.com 1.0.0.1#cloudflare-dns.com 2606:4700:4700::1111#cloudflare-dns.com 2606:4700:4700::1001#cloudflare-dns.com
FallbackDNS=
DNSOverTLS=yes
Domains=~.
- name: Use resolved stub
ansible.builtin.file:
src: /run/systemd/resolve/stub-resolv.conf
dest: /etc/resolv.conf
state: link
force: true
- name: Restart systemd-resolved
ansible.builtin.systemd_service:
name: systemd-resolved
enabled: true
state: restarted
- name: Check DNS over TLS
ansible.builtin.command: resolvectl query deb.debian.org
register: dns_check
retries: 5
delay: 3
until: dns_check.rc == 0
changed_when: false
+39
View File
@@ -0,0 +1,39 @@
# Готовит доступ к API панели 3x-ui с самого сервера: xui_api_url и xui_api_headers.
#
# Плейбук берет свой токен serverus и перевыпускает его на каждом запуске.
# Токен, отданный вызывающей стороне (install -> api_token), при этом не меняется.
- name: Issue serverus API token
ansible.builtin.shell: "{{ xui_bin }} setting -getApiToken -tokenName serverus | grep -Eo 'apiToken: .+' | awk '{print $2}'"
register: xui_api_token
changed_when: false
failed_when: xui_api_token.rc != 0 or not xui_api_token.stdout
no_log: true
- name: Read panel address
ansible.builtin.command: "{{ xui_bin }} setting -show true"
register: xui_panel_settings
changed_when: false
no_log: true
- name: Set API access
vars:
settings: "{{ xui_panel_settings.stdout }}"
scheme: "{{ 'http' if 'not secure with SSL' in settings else 'https' }}"
api_port: "{{ settings | regex_search('port: (\\d+)', '\\1') | first }}"
base_path: "{{ (settings | regex_search('webBasePath: (\\S*)', '\\1') | first).strip('/') }}"
ansible.builtin.set_fact:
xui_api_url: "{{ scheme }}://127.0.0.1:{{ api_port }}{{ '/' ~ base_path if base_path else '' }}/panel/api"
xui_api_headers:
Authorization: "Bearer {{ xui_api_token.stdout }}"
no_log: true
# после перезапуска панели API поднимается не сразу
- name: Wait for panel API
ansible.builtin.uri:
url: "{{ xui_api_url }}/server/status"
headers: "{{ xui_api_headers }}"
validate_certs: false
register: xui_api_status
retries: 10
delay: 3
until: xui_api_status.status == 200
+74 -2
View File
@@ -1,7 +1,8 @@
# extravars: username, password, panel_port, web_base_path, domain (optional), version (optional)
# extravars: username, password, panel_port, web_base_path, sub_port, sub_path, domain (optional), version (optional)
#
# Ставит 3x-ui скриптом из README проекта в неинтерактивном режиме.
# Сертификат: на домен, если он задан, иначе на ip. Оба варианта требуют открытый 80 порт (acme).
# Системный DNS - DNS-over-TLS к Cloudflare, Xray резолвит через него. Подписка на sub_port под тем же сертификатом.
- name: Install 3x-ui panel
hosts: target
gather_facts: false
@@ -11,7 +12,9 @@
tasks:
- ansible.builtin.import_tasks: tasks/firewall.yml
vars:
firewall_tcp_ports: [ "{{ ansible_port }}", 80, 443, "{{ panel_port }}" ]
firewall_tcp_ports: [ "{{ ansible_port }}", 80, 443, "{{ panel_port }}", "{{ sub_port }}" ]
- ansible.builtin.import_tasks: tasks/dns.yml
- name: Download install script
ansible.builtin.get_url:
@@ -54,6 +57,75 @@
changed_when: false
no_log: true
- ansible.builtin.import_tasks: tasks/xui_api.yml
# настройки меняются только целиком: читаем все, меняем подписку, отправляем обратно
- name: Read panel settings
ansible.builtin.uri:
url: "{{ xui_api_url }}/setting/all"
method: POST
headers: "{{ xui_api_headers }}"
validate_certs: false
register: panel_settings
failed_when: not panel_settings.json.success | default(false)
no_log: true
- name: Enable subscription
vars:
settings: "{{ panel_settings.json.obj }}"
ansible.builtin.uri:
url: "{{ xui_api_url }}/setting/update"
method: POST
headers: "{{ xui_api_headers }}"
body_format: json
body: >-
{{ settings | combine({
'subEnable': true,
'subPort': sub_port | int,
'subPath': '/' ~ sub_path ~ '/',
'subCertFile': settings.webCertFile,
'subKeyFile': settings.webKeyFile,
}) }}
validate_certs: false
register: sub_update
failed_when: not sub_update.json.success | default(false)
no_log: true
- name: Read Xray config
ansible.builtin.uri:
url: "{{ xui_api_url }}/xray/"
method: POST
headers: "{{ xui_api_headers }}"
validate_certs: false
register: xray_setting
failed_when: not xray_setting.json.success | default(false)
# Xray не умеет DoT: отправляем его запросы в системный резолвер, а тот ходит в Cloudflare по DoT
- name: Enable Xray DNS
vars:
config: "{{ (xray_setting.json.obj | from_json).xraySetting }}"
ansible.builtin.uri:
url: "{{ xui_api_url }}/xray/update"
method: POST
headers: "{{ xui_api_headers }}"
body_format: form-urlencoded
body:
xraySetting: "{{ config | combine({'dns': {'servers': ['localhost'], 'queryStrategy': 'UseIP'}}) | to_json }}"
validate_certs: false
register: xray_update
failed_when: not xray_update.json.success | default(false)
# подписка слушает новый порт только после перезапуска панели
- name: Restart panel with subscription
ansible.builtin.systemd_service:
name: x-ui
state: restarted
- name: Wait for subscription
ansible.builtin.wait_for:
port: "{{ sub_port }}"
timeout: 60
- name: Return panel access
ansible.builtin.set_stats:
aggregate: false
+135
View File
@@ -0,0 +1,135 @@
# extravars: remark, short_ids, xhttp_path, target (optional)
#
# Создает в 3x-ui подключение vless + xhttp + reality на 443 порту с ежемесячным сбросом трафика.
# Цель reality подбирает сканер панели: TLS 1.3, h2, X25519 и валидный сертификат, из подходящих - с наименьшей задержкой.
# Без target сканируются кандидаты из настроек панели. Ключи x25519 генерирует панель.
# Если подключение с таким remark уже есть, оно не меняется: плейбук только возвращает его параметры.
- name: Create reality inbound
hosts: target
gather_facts: false
vars:
xui_bin: /usr/local/x-ui/x-ui
inbound_port: 443
fingerprint: firefox
tasks:
- ansible.builtin.import_tasks: tasks/xui_api.yml
- name: List inbounds
ansible.builtin.uri:
url: "{{ xui_api_url }}/inbounds/list"
headers: "{{ xui_api_headers }}"
validate_certs: false
register: inbounds
failed_when: not inbounds.json.success | default(false)
no_log: true
- name: Find inbound
ansible.builtin.set_fact:
inbound: "{{ inbounds.json.obj | default([], true) | selectattr('remark', 'equalto', remark) | first | default(none) }}"
no_log: true
- name: Create inbound
when: inbound is none
block:
- name: Scan reality targets
ansible.builtin.uri:
url: "{{ xui_api_url }}/server/scanRealityTargets"
method: POST
headers: "{{ xui_api_headers }}"
body_format: form-urlencoded
body:
targets: "{{ target | default('') }}"
validate_certs: false
timeout: 120
register: scan
failed_when: not scan.json.success | default(false)
- name: Choose reality target
ansible.builtin.set_fact:
reality_target: >-
{{ scan.json.obj
| selectattr('feasible') | rejectattr('privateTarget')
| selectattr('tls13') | selectattr('h2') | selectattr('x25519') | selectattr('certValid')
| sort(attribute='latencyMs') | first | default(none) }}
- name: Require reality target
ansible.builtin.assert:
that: reality_target is not none
fail_msg: "No suitable reality target: {{ scan.json.obj | map(attribute='target') | join(', ') }}"
- name: Generate x25519 keys
ansible.builtin.uri:
url: "{{ xui_api_url }}/server/getNewX25519Cert"
headers: "{{ xui_api_headers }}"
validate_certs: false
register: x25519
failed_when: not x25519.json.success | default(false)
no_log: true
- name: Add inbound
ansible.builtin.uri:
url: "{{ xui_api_url }}/inbounds/add"
method: POST
headers: "{{ xui_api_headers }}"
body_format: json
body:
remark: "{{ remark }}"
enable: true
listen: ""
port: "{{ inbound_port }}"
protocol: vless
total: 0
expiryTime: 0
trafficReset: monthly
trafficResetDay: 1
settings: "{{ {'clients': [], 'decryption': 'none', 'fallbacks': []} | to_json }}"
streamSettings: >-
{{ {
'network': 'xhttp',
'security': 'reality',
'externalProxy': [],
'xhttpSettings': {'path': xhttp_path, 'host': '', 'mode': 'auto', 'xPaddingBytes': '100-1000'},
'realitySettings': {
'show': false,
'xver': 0,
'target': reality_target.host ~ ':' ~ reality_target.port,
'serverNames': [reality_target.host],
'privateKey': x25519.json.obj.privateKey,
'minClientVer': '',
'maxClientVer': '',
'maxTimediff': 0,
'shortIds': short_ids,
'mldsa65Seed': '',
'settings': {
'publicKey': x25519.json.obj.publicKey,
'fingerprint': fingerprint,
'serverName': '',
'spiderX': '/',
'mldsa65Verify': '',
},
},
} | to_json }}
sniffing: "{{ {'enabled': true, 'destOverride': ['http', 'tls', 'quic', 'fakedns'], 'metadataOnly': false, 'routeOnly': false} | to_json }}"
validate_certs: false
register: added
failed_when: not added.json.success | default(false)
no_log: true
- name: Remember created inbound
ansible.builtin.set_fact:
inbound: "{{ added.json.obj }}"
no_log: true
- name: Return inbound
vars:
stream: "{{ inbound.streamSettings | from_json }}"
ansible.builtin.set_stats:
aggregate: false
data:
inbound_id: "{{ inbound.id }}"
port: "{{ inbound.port }}"
target: "{{ stream.realitySettings.target | default(stream.realitySettings.dest | default('')) }}"
server_name: "{{ stream.realitySettings.serverNames | first }}"
public_key: "{{ stream.realitySettings.settings.publicKey }}"
short_ids: "{{ stream.realitySettings.shortIds }}"
path: "{{ stream.xhttpSettings.path }}"