137 lines
5.8 KiB
YAML
137 lines
5.8 KiB
YAML
# 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:
|
|
# список отдает строку JSON, а ответ на добавление - уже объект
|
|
stream: "{{ inbound.streamSettings if inbound.streamSettings is mapping else 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 }}"
|