From 0aec9e6e540330249e831f35d9148ee870eafa1a Mon Sep 17 00:00:00 2001
From: Sergey Antropoff
Date: Wed, 1 Jul 2026 02:17:22 +0300
Subject: [PATCH] Add Salamander obfs branch: replace masquerade with packet
obfuscation.
- ACME TLS challenge on 443 (no port 80 or nginx decoy)
- Auto-generate and persist obfs password per server
- Update client export, HTML catalog, and vault examples
- Document Salamander vs main and ACME auto-renewal in README
---
README.md | 307 ++++++++++++------
group_vars/all.yml.example | 7 +-
group_vars/hysteria2_servers/vars.yml.example | 2 +
.../hysteria2_servers/vault.yml.example | 7 +-
roles/hysteria2/defaults/main.yml | 18 +-
roles/hysteria2/meta/main.yml | 2 +-
roles/hysteria2/tasks/configure.yml | 26 +-
roles/hysteria2/tasks/export.yml | 3 +
roles/hysteria2/tasks/export_global.yml | 2 +
roles/hysteria2/tasks/main.yml | 4 +
roles/hysteria2/tasks/obfs.yml | 65 ++++
roles/hysteria2/tasks/share_user.yml | 2 +
roles/hysteria2/tasks/uninstall.yml | 8 +-
roles/hysteria2/tasks/validate.yml | 2 +-
roles/hysteria2/templates/client.yaml.j2 | 5 +
roles/hysteria2/templates/config.yaml.j2 | 13 +-
.../templates/export/global-index.html.j2 | 27 +-
.../hysteria2/templates/export/index.html.j2 | 20 ++
roles/hysteria2/templates/masq/index.html.j2 | 27 --
19 files changed, 365 insertions(+), 182 deletions(-)
create mode 100644 roles/hysteria2/tasks/obfs.yml
delete mode 100644 roles/hysteria2/templates/masq/index.html.j2
diff --git a/README.md b/README.md
index 6fe7373..5446395 100644
--- a/README.md
+++ b/README.md
@@ -1,48 +1,64 @@
-# Ansible-роль: Hysteria2 Server
+# Hysteria2 Ansible — ветка **Salamander**
-Ansible-роль для установки [Hysteria 2](https://v2.hysteria.network/) на Debian/Ubuntu VPS: ACME-сертификат, masquerade под nginx, несколько пользователей, экспорт URL/QR и HTML-каталог.
+> **Ветка:** `salamander`
+> **Режим:** обфускация **Salamander** (без masquerade-сайта nginx)
+> **Основная ветка `main`:** masquerade под HTTPS-сайт — см. другую ветку
+
+Ansible-роль для установки [Hysteria 2](https://v2.hysteria.network/) на Debian/Ubuntu VPS с **Salamander obfs** — запутывание пакетов для обхода DPI, когда masquerade под «обычный сайт» недостаточен.
+
+---
+
+## Чем Salamander отличается от `main`
+
+| | **`main` (masquerade)** | **`salamander` (эта ветка)** |
+|---|---|---|
+| Маскировка | HTTPS-сайт nginx + Let's Encrypt | **Salamander obfs** — пакеты выглядят как шум |
+| Порт 80/tcp | Нужен (ACME HTTP + masquerade) | **Не нужен** |
+| ACME | `type: http` | `type: tls` (TLS-ALPN на 443) |
+| Сайт-заглушка | `/var/www/masq` | **Нет** |
+| Obfs-пароль | — | **Обязателен** (один на сервер) |
+| URI клиента | `hysteria2://user:pass@domain:443` | + `obfs=salamander&obfs-password=...` |
+| Лучше когда | Нужен «легитимный» сайт в браузере | Агрессивный DPI, блокировка QUIC fingerprint |
+
+---
## Быстрый старт
```bash
-cd ~/Разработка/hysteria2
+git clone https://git.antropoff.ru/DevOpsTools/hysteria2.git
+cd hysteria2
+git checkout salamander
-make init # inventory, group_vars, vault, .vault_pass
-# отредактировать:
-# inventory/hosts.yml
-# group_vars/all.yml
-# group_vars/hysteria2_servers/vault.yml
+make init
+# отредактировать inventory/hosts.yml, group_vars/all.yml, vault
-make vault-encrypt # зашифровать пароли VPS
-make ping # проверить SSH
-make install # установка → output/ → браузер откроется сам
+make vault-encrypt
+make ping
+make install # → output/index.html откроется в браузере
```
+---
+
## Makefile
| Команда | Описание |
|---|---|
-| `make help` | Справка |
| `make init` | Создать конфиги из `.example` |
| `make ping` | Проверить SSH к VPS |
-| `make status` | `systemctl status hysteria-server` |
-| `make install` | Установка + экспорт + `output/index.html` + открытие в браузере |
+| `make install` | Установка Salamander + экспорт URL/QR/HTML |
| `make update` | Обновить бинарник, конфиг, перевыпустить экспорт |
-| `make export` | Только экспорт URL/QR/HTML |
+| `make export` | Только экспорт (URL, QR, HTML) |
| `make uninstall` | Удалить Hysteria2 с VPS |
| `make vault-encrypt` | Зашифровать vault |
-| `make vault-edit` | Редактировать vault |
-
-### Примеры
```bash
make install LIMIT=vps-de
-make update LIMIT=vps-nl
-make export
-make uninstall LIMIT=vps-de EXTRA_VARS='hysteria2_uninstall_remove_local_output=true'
-make install EXTRA_VARS='hysteria2_open_browser=false' # без авто-открытия браузера
+make update EXTRA_VARS='hysteria2_wait_for_acme=false'
+make install EXTRA_VARS='hysteria2_open_browser=false'
```
+---
+
## Inventory
```yaml
@@ -55,132 +71,217 @@ all:
ansible_port: 2222 # SSH-порт (если не 22)
ansible_user: root
ansible_password: "{{ vault_ssh_passwords['vps-de'] }}"
- hysteria2_domain: vpn-de.example.com
+ hysteria2_domain: vpn-de.example.com # для TLS/SNI и ACME
hysteria2_users:
- my
- friend
-
- vps-nl:
- ansible_host: 203.0.113.20
- ansible_user: root
- ansible_password: "{{ vault_ssh_passwords['vps-nl'] }}"
- hysteria2_domain: vpn-nl.dynu.net
- hysteria2_users:
- - alice
- - bob
```
-### SSH-подключение к VPS
-
-| Параметр | Где | Описание |
-|---|---|---|
-| `ansible_host` | inventory | IP VPS |
-| `ansible_port` | inventory | SSH-порт (по умолчанию `22`) |
-| `ansible_user` | inventory | Пользователь SSH (обычно `root`) |
-| `ansible_password` | inventory + vault | Пароль из `vault_ssh_passwords` |
-| `ansible_ssh_private_key_file` | inventory | Альтернатива паролю — SSH-ключ |
+### SSH (VPS)
```yaml
# group_vars/hysteria2_servers/vault.yml
vault_ssh_passwords:
- vps-de: "root-password-1"
- vps-nl: "root-password-2"
+ vps-de: "root-password"
```
-Ключи в `vault_ssh_passwords` совпадают с **именами хостов** в inventory.
+Пароли VPN и Salamander obfs тоже можно хранить в vault — см. ниже.
-## Пароли VPN-пользователей
+---
-1. **Vault** (рекомендуется):
+## Как работает Salamander в этом проекте
+
+### Сервер (`/etc/hysteria/config.yaml`)
```yaml
-vault_hysteria2_user_passwords:
- vps-de:
- friend: "Aingae0Okit1eek4eeZahFohVei4akee"
+listen: 0.0.0.0:443
+
+acme:
+ type: tls # сертификат без порта 80
+ domains:
+ - vpn-de.example.com
+ email: admin@example.com
+
+auth:
+ type: userpass
+ userpass:
+ my: "..."
+ friend: "..."
+
+obfs:
+ type: salamander
+ salamander:
+ password: "общий_obfs_пароль_сервера"
```
-2. **Per-host в inventory**:
+### Клиент (генерируется автоматически)
```yaml
-hysteria2_user_passwords:
- friend: "custom-password"
+server: vpn-de.example.com:443
+auth: my:password
+obfs:
+ type: salamander
+ salamander:
+ password: общий_obfs_пароль_сервера
```
-3. **Автогенерация** — `pwgen -s 40`, если пароль не задан.
+### URI (пример)
-При `make update` пароли подтягиваются из `output//server-info.yml`, если не указаны в vault/inventory.
+```
+hysteria2://my:password@vpn-de.example.com:443?obfs=salamander&obfs-password=OBFS_PASS#my
+```
+
+Роль вызывает `hysteria share` — URI и QR уже содержат параметры Salamander.
+
+---
+
+## Пароли
+
+### VPN-пользователи (`userpass`)
+
+1. **Vault:** `vault_hysteria2_user_passwords`
+2. **Inventory:** `hysteria2_user_passwords`
+3. **Авто:** `pwgen -s 40`
+
+### Salamander obfs (один на сервер)
+
+1. **Vault (рекомендуется):**
+
+```yaml
+vault_hysteria2_obfs_passwords:
+ vps-de: "cry_me_a_r1ver_salamander_obfs"
+```
+
+Подключается через `group_vars/hysteria2_servers/vars.yml`:
+
+```yaml
+```
+
+2. **Авто:** `pwgen -s 32` при первой установке
+3. **При update:** загружается из `output//server-info.yml`
+
+> **Важно:** obfs-пароль на сервере и клиенте должен **совпадать**. При `make update` без vault пароль сохраняется из предыдущего экспорта.
+
+---
+
+## Let's Encrypt — обновление сертификата
+
+**Да, Hysteria2 обновляет сертификат автоматически.**
+
+При `acme:` в конфиге встроенный ACME-клиент Hysteria2 сам получает и **продлевает** сертификат Let's Encrypt (срок ~90 дней). Перезапуск ansible для продления **не нужен** — процесс `hysteria-server` делает это сам (подтверждено maintainer проекта).
+
+**Условия для авто-продления:**
+
+- Сервер **запущен** и доступен из интернета
+- Домен по-прежнему указывает на IP VPS
+- Порт **443** открыт (для `acme type: tls` — TLS-ALPN challenge)
+- Конфиг `acme` не удалён
+
+Проверка логов: `journalctl -u hysteria-server -f`
+
+---
+
+## Firewall
+
+Открываются только порты Hysteria2 (по умолчанию **443/tcp** и **443/udp**).
+
+**Порт 80 не используется** — в отличие от ветки `main`.
+
+---
## Результат: папка `output/`
```
output/
-├── index.html ← общий каталог всех серверов (открывается в браузере)
+├── index.html ← все серверы (открывается в браузере)
├── vps-de/
-│ ├── index.html ← страница сервера
-│ ├── my.url
-│ ├── my.png ← QR PNG
-│ ├── my.qr.txt ← QR ASCII
+│ ├── index.html ← страница сервера + obfs-пароль
+│ ├── my.url ← URI с obfs=salamander
+│ ├── my.png ← QR
│ ├── my.txt
-│ └── server-info.yml
+│ └── server-info.yml ← mode, obfs_password, users
└── vps-nl/
└── ...
```
-### HTML-страницы
+HTML-страницы показывают:
-**`output/index.html`** — общий каталог:
-- все серверы и пользователи на одной странице
-- навигация по серверам
-- поля ссылки/пароля с кнопкой копирования
+- режим **Salamander**
+- **obfs-password** с кнопкой копирования
+- пароль и URL каждого пользователя
- QR-коды
-- ссылки на файлы и страницы серверов
-**`output//index.html`** — страница одного сервера (тот же стиль, все пользователи сервера).
-
-После `make install`, `make update` и `make export` **`output/index.html` автоматически открывается в браузере** (macOS: `open`, Linux: `xdg-open`).
-
-Отключить авто-открытие:
-
-```yaml
-# group_vars/all.yml
-hysteria2_open_browser: false
-```
-
-Или: `make install EXTRA_VARS='hysteria2_open_browser=false'`
-
-## QR-коды
-
-PNG генерируются средствами Ansible (без Python):
-
-1. `apt install qrencode` на VPS (остаётся установленным)
-2. `qrencode -o user.png "hysteria2://..."`
-3. `fetch` — скачивание PNG на control node
-
-ASCII QR — `hysteria share --qr` → `user.qr.txt`.
+---
## Переменные
| Переменная | Где | Описание |
|---|---|---|
-| `hysteria2_domain` | host | Домен с A-записью на IP |
-| `hysteria2_users` | host | Список имён VPN-пользователей |
-| `hysteria2_acme_email` | group | Email для Let's Encrypt |
-| `hysteria2_user_passwords` | host/vault | Свои пароли VPN |
-| `hysteria2_output_dir` | group | Папка экспорта (по умолчанию `./output`) |
-| `hysteria2_output_name` | host | Имя подпапки (по умолчанию `inventory_hostname`) |
-| `hysteria2_generate_qr_png` | group | PNG QR через `qrencode` |
-| `hysteria2_open_browser` | group | Открыть `output/index.html` после экспорта |
-| `hysteria2_uninstall_remove_local_output` | extra-vars | Удалить `output//` при uninstall |
+| `hysteria2_mode` | defaults | `salamander` (информативно) |
+| `hysteria2_domain` | host | Домен для TLS/SNI/ACME |
+| `hysteria2_users` | host | VPN-пользователи |
+| `hysteria2_acme_email` | group | Email Let's Encrypt |
+| `hysteria2_obfs_password` | host/vault | Пароль Salamander (или авто) |
+| `hysteria2_obfs_password_length` | group | Длина автопароля obfs (32) |
+| `hysteria2_listen_port` | group | Порт (443) |
+| `hysteria2_open_browser` | group | Открыть `output/index.html` |
+| `vault_hysteria2_obfs_passwords` | vault | obfs-пароли по серверам |
+
+---
+
+## Когда выбирать эту ветку
+
+**Используйте `salamander`, если:**
+
+- masquerade из `main` блокируют или «палят» по fingerprint
+- не нужен фейковый сайт в браузере
+- готовы хранить **дополнительный** obfs-пароль
+
+**Используйте `main`, если:**
+
+- нужен «нормальный» HTTPS-сайт при проверке домена
+- достаточно маскировки под nginx
+- хотите минимум параметров в URI
+
+---
## Безопасность
-- `output/` содержит пароли и URL — в `.gitignore`
-- `inventory/hosts.yml`, `vault.yml`, `.vault_pass` — не коммитить
-- После `make init` выполните `make vault-encrypt`
+- `output/` содержит пароли, obfs-ключи и URL — **не коммитить**
+- `make vault-encrypt` обязателен после `make init`
+- Salamander **не делает** вас невидимым: IP VPS и постоянный UDP-поток всё ещё можно анализировать
+
+---
## Требования
- Ansible 2.14+
-- Debian/Ubuntu VPS с sudo
-- Домен с A-записью на IP сервера
-- Для авто-открытия браузера: macOS или Linux с `xdg-open`
+- Debian/Ubuntu VPS
+- Домен с A-записью на IP (для ACME TLS)
+- Клиент Hysteria2 с поддержкой Salamander (Shadowrocket, NekoBox, Hiddify и др.)
+
+---
+
+## Структура роли
+
+```
+roles/hysteria2/
+├── tasks/
+│ ├── obfs.yml ← генерация/загрузка obfs-пароля
+│ ├── configure.yml ← без masquerade, только Salamander
+│ └── export_global.yml ← общий output/index.html
+└── templates/
+ ├── config.yaml.j2 ← acme tls + obfs salamander
+ └── client.yaml.j2 ← клиент с obfs
+```
+
+---
+
+## Переключение между ветками
+
+```bash
+git checkout main # masquerade + nginx
+git checkout salamander # Salamander obfs
+```
+
+Конфиги на **уже установленном** сервере **не меняются** при переключении ветки в git — нужен `make install` или `make update` на целевой ветке (это **перекатит** конфиг сервера).
diff --git a/group_vars/all.yml.example b/group_vars/all.yml.example
index 10756c4..167cb26 100644
--- a/group_vars/all.yml.example
+++ b/group_vars/all.yml.example
@@ -17,8 +17,11 @@ hysteria2_output_dir: "{{ playbook_dir }}/output"
# Открывать output/index.html в браузере после install/update/export
# hysteria2_open_browser: true
-# Порт Hysteria2
+# Порт Hysteria2 (Salamander — достаточно открыть только его, 80/tcp не нужен)
hysteria2_listen_port: 443
-# Открывать порты в ufw (80/tcp, 443/tcp, 443/udp)
+# Открывать порты в ufw ({{ hysteria2_listen_port }}/tcp и /udp)
hysteria2_configure_firewall: true
+
+# Длина пароля Salamander obfs (pwgen)
+# hysteria2_obfs_password_length: 32
diff --git a/group_vars/hysteria2_servers/vars.yml.example b/group_vars/hysteria2_servers/vars.yml.example
index 95c129d..649fbd2 100644
--- a/group_vars/hysteria2_servers/vars.yml.example
+++ b/group_vars/hysteria2_servers/vars.yml.example
@@ -3,3 +3,5 @@
# Файл group_vars/hysteria2_servers/vault.yml должен быть зашифрован (make vault-encrypt).
# Проброс VPN-паролей из vault в переменные роли (опционально)
+
+# Опционально: фиксированный пароль Salamander obfs для сервера
diff --git a/group_vars/hysteria2_servers/vault.yml.example b/group_vars/hysteria2_servers/vault.yml.example
index f0e2b21..8b44498 100644
--- a/group_vars/hysteria2_servers/vault.yml.example
+++ b/group_vars/hysteria2_servers/vault.yml.example
@@ -14,5 +14,8 @@ vault_ssh_passwords:
# vault_hysteria2_user_passwords:
# vps-de:
# friend: "Aingae0Okit1eek4eeZahFohVei4akee"
-# vps-nl:
-# alice: "CustomAlicePassword40chars................"
+
+# Опционально: пароль Salamander obfs (один на сервер)
+# vault_hysteria2_obfs_passwords:
+# vps-de: "cry_me_a_r1ver_salamander_obfs_pass"
+# vps-nl: "another_obfs_password_32chars!!"
diff --git a/roles/hysteria2/defaults/main.yml b/roles/hysteria2/defaults/main.yml
index 2733dfc..e795a1c 100644
--- a/roles/hysteria2/defaults/main.yml
+++ b/roles/hysteria2/defaults/main.yml
@@ -1,5 +1,8 @@
---
-# Домен сервера (A-запись → IP VPS). Задаётся per-host в inventory.
+# Режим развёртывания (ветка salamander)
+hysteria2_mode: salamander
+
+# Домен сервера (A-запись → IP VPS). Используется для TLS/SNI и ACME.
hysteria2_domain: ""
# Email для ACME / Let's Encrypt
@@ -9,34 +12,31 @@ hysteria2_acme_email: ""
hysteria2_users: []
# Опционально: фиксированные пароли { username: password }
-# Пустое значение или отсутствие ключа — автогенерация через pwgen
+
+# Пароль обфускации Salamander (общий для сервера).
+# Пусто — автогенерация через pwgen или загрузка из output//server-info.yml
+hysteria2_obfs_password: ""
hysteria2_password_length: 40
+hysteria2_obfs_password_length: 32
hysteria2_listen_port: 443
hysteria2_upgrade_system: true
hysteria2_configure_firewall: true
-hysteria2_masq_dir: /var/www/masq
hysteria2_config_path: /etc/hysteria/config.yaml
hysteria2_service_name: hysteria-server
-# Локальный каталог для экспорта URL и QR (на control node)
hysteria2_output_dir: "{{ playbook_dir }}/output"
hysteria2_output_name: "{{ inventory_hostname }}"
-# Генерировать PNG QR-коды через qrencode (apt на VPS, fetch на control node)
hysteria2_generate_qr_png: true
hysteria2_qr_png_size: 6
hysteria2_qr_png_margin: 2
hysteria2_qr_png_error_correction: M
-# Ждать ACME при первом запуске (отключите при update: make update)
hysteria2_wait_for_acme: true
-
-# Открыть output/index.html в браузере после install/update/export
hysteria2_open_browser: true
# --- uninstall ---
hysteria2_uninstall_remove_config: true
-hysteria2_uninstall_remove_masq: true
hysteria2_uninstall_remove_local_output: false
diff --git a/roles/hysteria2/meta/main.yml b/roles/hysteria2/meta/main.yml
index 4863d73..5fd57ac 100644
--- a/roles/hysteria2/meta/main.yml
+++ b/roles/hysteria2/meta/main.yml
@@ -2,7 +2,7 @@
galaxy_info:
role_name: hysteria2
author: inecs
- description: Install Hysteria2 VPN server with masquerade site and export client URLs with QR codes
+ description: Install Hysteria2 VPN server with Salamander obfs and export client URLs with QR codes
license: MIT
min_ansible_version: "2.14"
platforms:
diff --git a/roles/hysteria2/tasks/configure.yml b/roles/hysteria2/tasks/configure.yml
index f88af1f..15b6e7e 100644
--- a/roles/hysteria2/tasks/configure.yml
+++ b/roles/hysteria2/tasks/configure.yml
@@ -1,24 +1,11 @@
---
-- name: Create masquerade web directory
- ansible.builtin.file:
- path: "{{ hysteria2_masq_dir }}"
- state: directory
- mode: "0755"
-
-- name: Deploy masquerade index.html
- ansible.builtin.template:
- src: masq/index.html.j2
- dest: "{{ hysteria2_masq_dir }}/index.html"
- mode: "0644"
- notify: Restart hysteria-server
-
- name: Remove default Hysteria config if present
ansible.builtin.file:
path: "{{ hysteria2_config_path }}"
state: absent
when: not ansible_check_mode
-- name: Deploy Hysteria2 server config
+- name: Deploy Hysteria2 Salamander server config
ansible.builtin.template:
src: config.yaml.j2
dest: "{{ hysteria2_config_path }}"
@@ -42,12 +29,11 @@
failed_when: false
when: hysteria2_configure_firewall | bool
-- name: Allow HTTP and HTTPS in ufw
+- name: Allow Hysteria2 port in ufw (Salamander — только {{ hysteria2_listen_port }})
ansible.builtin.command: "ufw allow {{ item }}"
loop:
- - 80/tcp
- - 443/tcp
- - 443/udp
+ - "{{ hysteria2_listen_port }}/tcp"
+ - "{{ hysteria2_listen_port }}/udp"
register: _hysteria2_ufw_allow
changed_when: "'Skipping' not in (_hysteria2_ufw_allow.stdout | default(''))"
failed_when: false
@@ -55,10 +41,10 @@
- hysteria2_configure_firewall | bool
- "'active' in (_hysteria2_ufw_status.stdout | default(''))"
-- name: Wait for ACME certificate (first start may take several minutes)
+- name: Wait for ACME TLS certificate (first start may take several minutes)
ansible.builtin.pause:
seconds: 30
- prompt: "Ожидание получения ACME-сертификата для {{ hysteria2_domain }}..."
+ prompt: "Ожидание получения ACME TLS-сертификата для {{ hysteria2_domain }}..."
when: hysteria2_wait_for_acme | default(true) | bool
- name: Verify hysteria-server is running
diff --git a/roles/hysteria2/tasks/export.yml b/roles/hysteria2/tasks/export.yml
index 5f1b739..a540396 100644
--- a/roles/hysteria2/tasks/export.yml
+++ b/roles/hysteria2/tasks/export.yml
@@ -31,8 +31,10 @@
mode: "0600"
content: |
server: {{ hysteria2_output_name }}
+ mode: salamander
domain: {{ hysteria2_domain }}
port: {{ hysteria2_listen_port }}
+ obfs_password: "{{ hysteria2_obfs_password }}"
users:
{% for user in hysteria2_export_users %}
- name: {{ user.name }}
@@ -53,6 +55,7 @@
mode: "0644"
vars:
generated_at: "{{ ansible_date_time.date }} {{ ansible_date_time.time }}"
+ hysteria2_obfs_password: "{{ hysteria2_obfs_password }}"
delegate_to: localhost
become: false
diff --git a/roles/hysteria2/tasks/export_global.yml b/roles/hysteria2/tasks/export_global.yml
index baea209..6a2918c 100644
--- a/roles/hysteria2/tasks/export_global.yml
+++ b/roles/hysteria2/tasks/export_global.yml
@@ -33,6 +33,8 @@
name: "{{ _info.server }}"
domain: "{{ _info.domain }}"
port: "{{ _info.port }}"
+ mode: "{{ _info.mode | default('salamander') }}"
+ obfs_password: "{{ _info.obfs_password | default('') }}"
dir: "{{ item.item.path | dirname | basename }}"
users: "{{ _info.users }}"
become: false
diff --git a/roles/hysteria2/tasks/main.yml b/roles/hysteria2/tasks/main.yml
index 14c8d03..e9cbf98 100644
--- a/roles/hysteria2/tasks/main.yml
+++ b/roles/hysteria2/tasks/main.yml
@@ -7,6 +7,10 @@
ansible.builtin.import_tasks: users.yml
tags: [install, update, export]
+- name: Resolve Salamander obfs password
+ ansible.builtin.import_tasks: obfs.yml
+ tags: [install, update, export]
+
- name: Install packages and Hysteria2 binary
ansible.builtin.import_tasks: install.yml
tags: [install]
diff --git a/roles/hysteria2/tasks/obfs.yml b/roles/hysteria2/tasks/obfs.yml
new file mode 100644
index 0000000..0ed50f2
--- /dev/null
+++ b/roles/hysteria2/tasks/obfs.yml
@@ -0,0 +1,65 @@
+---
+- name: Check for saved Salamander password from previous install
+ ansible.builtin.stat:
+ path: "{{ hysteria2_output_dir }}/{{ hysteria2_output_name }}/server-info.yml"
+ register: _hysteria2_saved_info
+ delegate_to: localhost
+ become: false
+ tags:
+ - install
+ - update
+ - export
+
+- name: Load saved Salamander obfs password
+ when: _hysteria2_saved_info.stat.exists
+ block:
+ - name: Read server-info.yml for obfs password
+ ansible.builtin.slurp:
+ path: "{{ hysteria2_output_dir }}/{{ hysteria2_output_name }}/server-info.yml"
+ register: _hysteria2_saved_info_raw
+ delegate_to: localhost
+ become: false
+
+ - name: Parse saved obfs password
+ ansible.builtin.set_fact:
+ _hysteria2_saved_obfs_password: "{{ (_hysteria2_saved_info_raw.content | b64decode | from_yaml).obfs_password | default('') }}"
+ delegate_to: localhost
+ become: false
+ tags:
+ - install
+ - update
+ - export
+
+- name: Resolve Salamander obfs password
+ ansible.builtin.set_fact:
+ hysteria2_obfs_password: >-
+ {{
+ hysteria2_obfs_password
+ | default(_hysteria2_saved_obfs_password | default(''), true)
+ }}
+ tags:
+ - install
+ - update
+ - export
+
+- name: Generate Salamander obfs password with pwgen
+ ansible.builtin.command:
+ cmd: "pwgen -s {{ hysteria2_obfs_password_length }} 1"
+ register: _hysteria2_obfs_pwgen
+ changed_when: false
+ when: hysteria2_obfs_password | length == 0
+ tags:
+ - install
+ - update
+ - export
+
+- name: Apply generated Salamander obfs password
+ ansible.builtin.set_fact:
+ hysteria2_obfs_password: "{{ _hysteria2_obfs_pwgen.stdout }}"
+ when:
+ - _hysteria2_obfs_pwgen is defined
+ - not (_hysteria2_obfs_pwgen.skipped | default(false))
+ tags:
+ - install
+ - update
+ - export
diff --git a/roles/hysteria2/tasks/share_user.yml b/roles/hysteria2/tasks/share_user.yml
index bdbf798..7a0fdcd 100644
--- a/roles/hysteria2/tasks/share_user.yml
+++ b/roles/hysteria2/tasks/share_user.yml
@@ -34,6 +34,8 @@
Domain: {{ hysteria2_domain }}:{{ hysteria2_listen_port }}
User: {{ hysteria2_current_user.name }}
Password: {{ hysteria2_current_user.password }}
+ Mode: salamander
+ Obfs password: {{ hysteria2_obfs_password }}
URL:
{{ _hysteria2_client_url }}
diff --git a/roles/hysteria2/tasks/uninstall.yml b/roles/hysteria2/tasks/uninstall.yml
index 10c0121..aad4b04 100644
--- a/roles/hysteria2/tasks/uninstall.yml
+++ b/roles/hysteria2/tasks/uninstall.yml
@@ -19,12 +19,6 @@
state: absent
when: hysteria2_uninstall_remove_config | bool
-- name: Remove masquerade web directory
- ansible.builtin.file:
- path: "{{ hysteria2_masq_dir }}"
- state: absent
- when: hysteria2_uninstall_remove_masq | bool
-
- name: Reload systemd after uninstall
ansible.builtin.systemd:
daemon_reload: true
@@ -32,7 +26,7 @@
- name: Show uninstall result
ansible.builtin.debug:
msg: >-
- Hysteria2 удалён с {{ inventory_hostname }}.
+ Hysteria2 (Salamander) удалён с {{ inventory_hostname }}.
{% if not hysteria2_uninstall_remove_local_output | bool %}
Локальные URL/QR в {{ hysteria2_output_dir }}/{{ hysteria2_output_name }}/ сохранены.
{% endif %}
diff --git a/roles/hysteria2/tasks/validate.yml b/roles/hysteria2/tasks/validate.yml
index 59672fa..eedd78b 100644
--- a/roles/hysteria2/tasks/validate.yml
+++ b/roles/hysteria2/tasks/validate.yml
@@ -7,7 +7,7 @@
- hysteria2_users | length > 0
fail_msg: |
Задайте для каждого хоста:
- hysteria2_domain — домен с A-записью на IP сервера
+ hysteria2_domain — домен с A-записью на IP сервера (используется для TLS/SNI)
hysteria2_users — список имён пользователей, например [my, friend]
И в group_vars/all.yml:
hysteria2_acme_email — email для Let's Encrypt
diff --git a/roles/hysteria2/templates/client.yaml.j2 b/roles/hysteria2/templates/client.yaml.j2
index 8b09cef..43420cd 100644
--- a/roles/hysteria2/templates/client.yaml.j2
+++ b/roles/hysteria2/templates/client.yaml.j2
@@ -1,3 +1,8 @@
server: {{ hysteria2_domain }}:{{ hysteria2_listen_port }}
auth: {{ hysteria2_current_user.name }}:{{ hysteria2_current_user.password }}
+
+obfs:
+ type: salamander
+ salamander:
+ password: {{ hysteria2_obfs_password }}
diff --git a/roles/hysteria2/templates/config.yaml.j2 b/roles/hysteria2/templates/config.yaml.j2
index 9a6f4e0..c393df1 100644
--- a/roles/hysteria2/templates/config.yaml.j2
+++ b/roles/hysteria2/templates/config.yaml.j2
@@ -1,7 +1,7 @@
listen: 0.0.0.0:{{ hysteria2_listen_port }}
acme:
- type: http
+ type: tls
domains:
- {{ hysteria2_domain }}
email: {{ hysteria2_acme_email }}
@@ -13,10 +13,7 @@ auth:
{{ user.name }}: "{{ user.password }}"
{% endfor %}
-masquerade:
- type: file
- file:
- dir: {{ hysteria2_masq_dir }}
- listenHTTP: :80
- listenHTTPS: :{{ hysteria2_listen_port }}
- forceHTTPS: true
+obfs:
+ type: salamander
+ salamander:
+ password: "{{ hysteria2_obfs_password }}"
diff --git a/roles/hysteria2/templates/export/global-index.html.j2 b/roles/hysteria2/templates/export/global-index.html.j2
index d90a831..ec63c7a 100644
--- a/roles/hysteria2/templates/export/global-index.html.j2
+++ b/roles/hysteria2/templates/export/global-index.html.j2
@@ -160,6 +160,17 @@
font-weight: 600;
}
.server-link:hover { background: var(--accent-soft); border-color: var(--accent); }
+ .badge-salamander {
+ display: inline-block;
+ margin-top: 0.35rem;
+ padding: 0.2rem 0.6rem;
+ border-radius: 999px;
+ font-size: 0.72rem;
+ font-weight: 700;
+ background: var(--accent-soft-2);
+ color: var(--accent-2);
+ }
+ .server-obfs { margin-top: 0.75rem; width: 100%; }
.users-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
@@ -286,8 +297,8 @@
- Hysteria2
- Общий каталог VPN-подключений
+ Hysteria2 · Salamander
+ Общий каталог VPN-подключений с обфускацией Salamander
{{ hysteria2_global_servers | length }} серверов
{{ total_users }} пользователей
@@ -307,6 +318,18 @@
{{ server.name | e }}
{{ server.domain | e }}:{{ server.port }}
+
Salamander
+{% if server.obfs_password | default('') | length > 0 %}
+
+{% endif %}
Страница сервера →
diff --git a/roles/hysteria2/templates/export/index.html.j2 b/roles/hysteria2/templates/export/index.html.j2
index f1f205e..9371a3a 100644
--- a/roles/hysteria2/templates/export/index.html.j2
+++ b/roles/hysteria2/templates/export/index.html.j2
@@ -75,6 +75,15 @@
font-size: 0.8rem;
font-weight: 600;
}
+ .badge-salamander {
+ margin-left: 0.35rem;
+ background: rgba(163, 113, 247, 0.18);
+ color: #a371f7;
+ }
+ .server-obfs {
+ margin-top: 1.25rem;
+ max-width: 100%;
+ }
.user-card {
background: var(--bg-card);
border: 1px solid var(--border);
@@ -235,6 +244,17 @@
Домен: {{ hysteria2_domain }}:{{ hysteria2_listen_port }}
{{ hysteria2_export_users | length }} {{ 'пользователь' if hysteria2_export_users | length == 1 else 'пользователей' }}
+ Salamander
+
+
+
Пароль обфускации (Salamander)
+
+
{% for user in hysteria2_export_users %}
diff --git a/roles/hysteria2/templates/masq/index.html.j2 b/roles/hysteria2/templates/masq/index.html.j2
deleted file mode 100644
index 75bcddc..0000000
--- a/roles/hysteria2/templates/masq/index.html.j2
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-Welcome to nginx!
-
-
-
-Welcome to nginx!
-If you see this page, nginx is successfully installed and working.
-Further configuration is required for the web server, reverse proxy,
-API gateway, load balancer, content cache, or other features.
-
-For online documentation and support please refer to
-nginx.org .
-To engage with the community please visit
-community.nginx.org .
-For enterprise grade support, professional services, additional
-security features and capabilities please refer to
-f5.com/nginx .
-
-Thank you for using nginx.
-
-