chore: настройка панелей
This commit is contained in:
1 parent
8532557ef6
commit
96213e182e
16 files changed
+529
-64
No files matched your search
@@ -0,0 +1,64 @@
|
||||
# vars: cert_host - домен или ip, на который выпускается сертификат
|
||||
#
|
||||
# Сертификат Let's Encrypt через acme.sh в режиме standalone: проверка HTTP-01 на 80 порту, он должен быть открыт.
|
||||
# На ip выдается только короткий сертификат (~6 дней), acme.sh продлевает его по cron и перезапускает панель.
|
||||
# Ставит сертификат панели и подписке. Если выпустить не удалось, падает, а не оставляет панель на http.
|
||||
- name: Set certificate paths
|
||||
ansible.builtin.set_fact:
|
||||
cert_is_ip: "{{ cert_host is match('^[0-9.]+$') or ':' in cert_host }}"
|
||||
cert_dir: "/root/cert/{{ cert_host }}"
|
||||
acme: /root/.acme.sh/acme.sh
|
||||
|
||||
- name: Install acme.sh dependencies
|
||||
ansible.builtin.apt:
|
||||
name: [ curl, socat, cron ]
|
||||
state: present
|
||||
|
||||
- name: Download acme.sh
|
||||
ansible.builtin.get_url:
|
||||
url: https://get.acme.sh
|
||||
dest: /root/acme-install.sh
|
||||
mode: "0700"
|
||||
when: not acme is file
|
||||
|
||||
- name: Install acme.sh
|
||||
ansible.builtin.shell: sh /root/acme-install.sh
|
||||
args:
|
||||
chdir: /root
|
||||
creates: "{{ acme }}"
|
||||
|
||||
- name: Use Let's Encrypt
|
||||
ansible.builtin.command: "{{ acme }} --set-default-ca --server letsencrypt"
|
||||
changed_when: false
|
||||
|
||||
# код 2 - действующий сертификат уже есть, продлевать рано
|
||||
- name: Issue certificate
|
||||
ansible.builtin.command: >-
|
||||
{{ acme }} --issue -d {{ cert_host }} --standalone --httpport 80 --server letsencrypt
|
||||
{{ '--certificate-profile shortlived --days 6' if cert_is_ip else '' }}
|
||||
register: cert_issue
|
||||
changed_when: cert_issue.rc == 0
|
||||
failed_when: cert_issue.rc not in [0, 2]
|
||||
|
||||
- name: Create certificate directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ cert_dir }}"
|
||||
state: directory
|
||||
mode: "0700"
|
||||
|
||||
- name: Install certificate
|
||||
ansible.builtin.command: >-
|
||||
{{ acme }} --installcert -d {{ cert_host }}
|
||||
--key-file {{ cert_dir }}/privkey.pem --fullchain-file {{ cert_dir }}/fullchain.pem
|
||||
--reloadcmd "systemctl restart x-ui"
|
||||
changed_when: true
|
||||
|
||||
- name: Check certificate
|
||||
ansible.builtin.command: "openssl x509 -in {{ cert_dir }}/fullchain.pem -noout -checkend 3600 -ext subjectAltName"
|
||||
register: cert_check
|
||||
changed_when: false
|
||||
failed_when: cert_check.rc != 0 or cert_host not in cert_check.stdout
|
||||
|
||||
- name: Use certificate for panel
|
||||
ansible.builtin.command: "{{ xui_bin }} cert -webCert {{ cert_dir }}/fullchain.pem -webCertKey {{ cert_dir }}/privkey.pem"
|
||||
changed_when: true
|
||||
@@ -0,0 +1,24 @@
|
||||
# vars: xui_settings_patch - поля настроек панели, которые нужно поменять. Нужен 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: xui_settings
|
||||
failed_when: not xui_settings.json.success | default(false)
|
||||
no_log: true
|
||||
|
||||
- name: Update panel settings
|
||||
ansible.builtin.uri:
|
||||
url: "{{ xui_api_url }}/setting/update"
|
||||
method: POST
|
||||
headers: "{{ xui_api_headers }}"
|
||||
body_format: json
|
||||
body: "{{ xui_settings.json.obj | combine(xui_settings_patch) }}"
|
||||
validate_certs: false
|
||||
register: xui_settings_update
|
||||
failed_when: not xui_settings_update.json.success | default(false)
|
||||
no_log: true
|
||||
@@ -1,7 +1,9 @@
|
||||
# extravars: username, password, panel_port, web_base_path, sub_port, sub_path, domain (optional), version (optional)
|
||||
# extravars: username, password, panel_port, web_base_path, sub_port, sub_path, domain (optional), ssl (optional),
|
||||
# version (optional)
|
||||
#
|
||||
# Ставит 3x-ui скриптом из README проекта в неинтерактивном режиме.
|
||||
# Сертификат: на домен, если он задан, иначе на ip. Оба варианта требуют открытый 80 порт (acme).
|
||||
# Ставит 3x-ui скриптом из README проекта в неинтерактивном режиме, SSL скрипта выключен: он при ошибке молча оставляет
|
||||
# панель на http. Сертификат выпускает tasks/xui_cert.yml на домен, если он задан, иначе на ip. Нужен открытый 80 порт.
|
||||
# ssl=false - без сертификата, для серверов без публичного адреса.
|
||||
# Системный DNS - DNS-over-TLS к Cloudflare, Xray резолвит через него. Подписка на sub_port под тем же сертификатом.
|
||||
- name: Install 3x-ui panel
|
||||
hosts: target
|
||||
@@ -9,6 +11,8 @@
|
||||
vars:
|
||||
xui_bin: /usr/local/x-ui/x-ui
|
||||
xui_domain: "{{ domain | default('') }}"
|
||||
xui_ssl: "{{ ssl | default(true) | bool }}"
|
||||
cert_host: "{{ xui_domain or ansible_host }}"
|
||||
tasks:
|
||||
- ansible.builtin.import_tasks: tasks/firewall.yml
|
||||
vars:
|
||||
@@ -33,8 +37,7 @@
|
||||
XUI_PASSWORD: "{{ password }}"
|
||||
XUI_PANEL_PORT: "{{ panel_port }}"
|
||||
XUI_WEB_BASE_PATH: "{{ web_base_path }}"
|
||||
XUI_SSL_MODE: "{{ 'domain' if xui_domain else 'ip' }}"
|
||||
XUI_DOMAIN: "{{ xui_domain }}"
|
||||
XUI_SSL_MODE: none
|
||||
XUI_SERVER_IP: "{{ ansible_host }}"
|
||||
no_log: true
|
||||
|
||||
@@ -45,6 +48,9 @@
|
||||
-port {{ panel_port }} -webBasePath {{ web_base_path | quote }}
|
||||
no_log: true
|
||||
|
||||
- ansible.builtin.import_tasks: tasks/xui_cert.yml
|
||||
when: xui_ssl
|
||||
|
||||
- name: Restart panel
|
||||
ansible.builtin.systemd_service:
|
||||
name: x-ui
|
||||
@@ -59,37 +65,14 @@
|
||||
|
||||
- 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
|
||||
- ansible.builtin.import_tasks: tasks/xui_settings.yml
|
||||
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
|
||||
xui_settings_patch:
|
||||
subEnable: true
|
||||
subPort: "{{ sub_port | int }}"
|
||||
subPath: "/{{ sub_path }}/"
|
||||
subCertFile: "{{ cert_dir ~ '/fullchain.pem' if xui_ssl else '' }}"
|
||||
subKeyFile: "{{ cert_dir ~ '/privkey.pem' if xui_ssl else '' }}"
|
||||
|
||||
- name: Read Xray config
|
||||
ansible.builtin.uri:
|
||||
@@ -131,4 +114,6 @@
|
||||
aggregate: false
|
||||
data:
|
||||
api_token: "{{ api_token.stdout }}"
|
||||
panel_host: "{{ cert_host if xui_ssl else ansible_host }}"
|
||||
ssl: "{{ xui_ssl }}"
|
||||
no_log: true
|
||||
@@ -0,0 +1,37 @@
|
||||
# extravars: domain (optional)
|
||||
#
|
||||
# Выпускает сертификат панели и подписки на domain, без него - на ip. Так можно поставить домен после установки на ip
|
||||
# или вернуться с домена на ip. Домен должен указывать на этот сервер (или на сервер маскировки перед ним),
|
||||
# 80 порт должен быть доступен снаружи.
|
||||
- name: Issue panel certificate
|
||||
hosts: target
|
||||
gather_facts: false
|
||||
vars:
|
||||
xui_bin: /usr/local/x-ui/x-ui
|
||||
cert_host: "{{ domain | default('', true) or ansible_host }}"
|
||||
tasks:
|
||||
- ansible.builtin.import_tasks: tasks/xui_cert.yml
|
||||
|
||||
- name: Restart panel
|
||||
ansible.builtin.systemd_service:
|
||||
name: x-ui
|
||||
state: restarted
|
||||
|
||||
- ansible.builtin.import_tasks: tasks/xui_api.yml
|
||||
|
||||
- ansible.builtin.import_tasks: tasks/xui_settings.yml
|
||||
vars:
|
||||
xui_settings_patch:
|
||||
subCertFile: "{{ cert_dir }}/fullchain.pem"
|
||||
subKeyFile: "{{ cert_dir }}/privkey.pem"
|
||||
|
||||
- name: Restart panel with subscription certificate
|
||||
ansible.builtin.systemd_service:
|
||||
name: x-ui
|
||||
state: restarted
|
||||
|
||||
- name: Return panel host
|
||||
ansible.builtin.set_stats:
|
||||
aggregate: false
|
||||
data:
|
||||
panel_host: "{{ cert_host }}"
|
||||
@@ -0,0 +1,113 @@
|
||||
# extravars: proxy_outbound (outbound Xray или null), warp_keys ({private_key, public_key} или null)
|
||||
#
|
||||
# Ставит исходящий прокси первым outbound Xray: он становится выходом по умолчанию для всего трафика клиентов.
|
||||
# warp_keys - регистрирует устройство Cloudflare WARP через панель и выходит через него по wireguard.
|
||||
# Оба null - прокси убирается, трафик идет напрямую. Перед применением прокси проверяется через панель.
|
||||
- name: Set outbound proxy
|
||||
hosts: target
|
||||
gather_facts: false
|
||||
vars:
|
||||
xui_bin: /usr/local/x-ui/x-ui
|
||||
proxy_tags: [ proxy, warp ]
|
||||
tasks:
|
||||
- ansible.builtin.import_tasks: tasks/xui_api.yml
|
||||
|
||||
- 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)
|
||||
|
||||
- name: Use proxy outbound
|
||||
ansible.builtin.set_fact:
|
||||
new_outbound: "{{ proxy_outbound }}"
|
||||
no_log: true
|
||||
|
||||
- name: Register Cloudflare WARP
|
||||
when: warp_keys is not none
|
||||
block:
|
||||
- name: Register WARP device
|
||||
ansible.builtin.uri:
|
||||
url: "{{ xui_api_url }}/xray/warp/reg"
|
||||
method: POST
|
||||
headers: "{{ xui_api_headers }}"
|
||||
body_format: form-urlencoded
|
||||
body:
|
||||
privateKey: "{{ warp_keys.private_key }}"
|
||||
publicKey: "{{ warp_keys.public_key }}"
|
||||
validate_certs: false
|
||||
register: warp_reg
|
||||
failed_when: not warp_reg.json.success | default(false)
|
||||
no_log: true
|
||||
|
||||
- name: Read WARP config
|
||||
ansible.builtin.set_fact:
|
||||
warp: "{{ warp_reg.json.obj | from_json }}"
|
||||
no_log: true
|
||||
|
||||
# reserved - байты client_id, в Jinja нет ord
|
||||
- name: Decode WARP client id
|
||||
ansible.builtin.command:
|
||||
argv: [ python3, -c, "import base64, json, sys; print(json.dumps(list(base64.b64decode(sys.argv[1]))))", "{{ warp.config.config.client_id | default(warp.data.client_id) }}" ]
|
||||
register: warp_reserved
|
||||
changed_when: false
|
||||
|
||||
- name: Use WARP outbound
|
||||
vars:
|
||||
config: "{{ warp.config.config }}"
|
||||
addresses: "{{ config.interface.addresses }}"
|
||||
ansible.builtin.set_fact:
|
||||
new_outbound:
|
||||
tag: warp
|
||||
protocol: wireguard
|
||||
settings:
|
||||
mtu: 1420
|
||||
secretKey: "{{ warp.data.private_key }}"
|
||||
address: "{{ ([addresses.v4 ~ '/32'] if addresses.v4 | default('') else []) + ([addresses.v6 ~ '/128'] if addresses.v6 | default('') else []) }}"
|
||||
reserved: "{{ warp_reserved.stdout | from_json }}"
|
||||
domainStrategy: ForceIPv4v6
|
||||
noKernelTun: true
|
||||
peers:
|
||||
- publicKey: "{{ config.peers[0].public_key }}"
|
||||
endpoint: "{{ config.peers[0].endpoint.host }}"
|
||||
no_log: true
|
||||
|
||||
- name: Test outbound
|
||||
when: new_outbound is not none
|
||||
ansible.builtin.uri:
|
||||
url: "{{ xui_api_url }}/xray/testOutbound"
|
||||
method: POST
|
||||
headers: "{{ xui_api_headers }}"
|
||||
body_format: form-urlencoded
|
||||
body:
|
||||
outbound: "{{ new_outbound | to_json }}"
|
||||
validate_certs: false
|
||||
timeout: 60
|
||||
register: outbound_test
|
||||
failed_when: not outbound_test.json.success | default(false)
|
||||
no_log: true
|
||||
|
||||
- name: Require working outbound
|
||||
when: new_outbound is not none
|
||||
ansible.builtin.assert:
|
||||
that: outbound_test.json.obj.success | default(false)
|
||||
fail_msg: "Outbound test failed: {{ outbound_test.json.obj.error | default(outbound_test.json.msg) }}"
|
||||
|
||||
- name: Apply outbound
|
||||
vars:
|
||||
config: "{{ (xray_setting.json.obj | from_json).xraySetting }}"
|
||||
outbounds: "{{ ([new_outbound] if new_outbound is not none else []) + (config.outbounds | rejectattr('tag', 'in', proxy_tags) | list) }}"
|
||||
ansible.builtin.uri:
|
||||
url: "{{ xui_api_url }}/xray/update"
|
||||
method: POST
|
||||
headers: "{{ xui_api_headers }}"
|
||||
body_format: form-urlencoded
|
||||
body:
|
||||
xraySetting: "{{ config | combine({'outbounds': outbounds}) | to_json }}"
|
||||
validate_certs: false
|
||||
register: xray_update
|
||||
failed_when: not xray_update.json.success | default(false)
|
||||
no_log: true
|
||||
Reference in new issue
Block a user