chore: доделываем поднятие сервера
This commit is contained in:
1 parent
adab286884
commit
8532557ef6
18 files changed
+749
-37
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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 }}"
|
||||
Reference in new issue
Block a user