Compare commits

...

3 Commits

Author SHA1 Message Date
Sergey Antropoff 3ca7dde4b2 fix: generate VPN passwords without pwgen, set EDITOR=nano
Use Ansible password lookup on the control node so install works before packages are installed on VPS and without pwgen on macOS. Export EDITOR=nano in Makefile for vault-edit.
2026-07-01 11:42:40 +03:00
Sergey Antropoff ad7846febe fix: replace removed yaml callback with default + result_format
community.general.yaml was removed in collection 12.0; use ansible.builtin.default with result_format=yaml for Ansible 2.13+.
2026-07-01 11:09:08 +03:00
Sergey Antropoff e90e2bad8b Expand README: branch comparison, ACME auto-renewal, branch switching.
Unified documentation structure aligned with salamander branch.
2026-07-01 02:22:38 +03:00
7 changed files with 225 additions and 74 deletions
+2
View File
@@ -5,6 +5,8 @@
SHELL := /bin/bash SHELL := /bin/bash
.DEFAULT_GOAL := help .DEFAULT_GOAL := help
export EDITOR="nano"
ANSIBLE ?= ansible-playbook ANSIBLE ?= ansible-playbook
ANSIBLE_ADHOC ?= ansible ANSIBLE_ADHOC ?= ansible
INVENTORY ?= inventory/hosts.yml INVENTORY ?= inventory/hosts.yml
+197 -39
View File
@@ -1,23 +1,57 @@
# Ansible-роль: Hysteria2 Server # Hysteria2 Ansible — ветка **main**
Ansible-роль для установки [Hysteria 2](https://v2.hysteria.network/) на Debian/Ubuntu VPS: ACME-сертификат, masquerade под nginx, несколько пользователей, экспорт URL/QR и HTML-каталог. > **Ветка:** `main`
> **Режим:** **masquerade** — маскировка под HTTPS-сайт nginx
> **Альтернатива:** [`salamander`](https://git.antropoff.ru/DevOpsTools/hysteria2/src/branch/salamander) — обфускация Salamander для агрессивного DPI
Ansible-роль для установки [Hysteria 2](https://v2.hysteria.network/) на Debian/Ubuntu VPS: Let's Encrypt, masquerade под nginx, несколько пользователей, экспорт URL/QR и HTML-каталог.
---
## Выбор ветки: `main` или `salamander`
| | **`main` (эта ветка)** | **`salamander`** |
|---|---|---|
| Маскировка | HTTPS-сайт nginx + Let's Encrypt | **Salamander obfs** — пакеты выглядят как шум |
| Порт 80/tcp | **Нужен** (ACME HTTP + masquerade) | **Не нужен** |
| ACME | `type: http` | `type: tls` (TLS-ALPN на 443) |
| Сайт-заглушка | `/var/www/masq` (nginx welcome) | **Нет** |
| Obfs-пароль | — | **Обязателен** (один на сервер) |
| URI клиента | `hysteria2://user:pass@domain:443` | + `obfs=salamander&obfs-password=...` |
| Лучше когда | Нужен «легитимный» сайт в браузере | Агрессивный DPI, блокировка QUIC fingerprint |
### Когда выбирать `main`
- нужен **нормальный HTTPS-сайт** при открытии домена в браузере;
- достаточно маскировки под nginx;
- хотите **минимум параметров** в URI клиента;
- провайдер не «палит» Hysteria/QUIC внутри TLS.
### Когда выбирать `salamander`
- masquerade из `main` **блокируют** или распознают по fingerprint;
- не нужен фейковый сайт;
- готовы хранить **дополнительный** obfs-пароль.
---
## Быстрый старт ## Быстрый старт
```bash ```bash
cd ~/Разработка/hysteria2 git clone https://git.antropoff.ru/DevOpsTools/hysteria2.git
cd hysteria2
git checkout main
make init # inventory, group_vars, vault, .vault_pass make init
# отредактировать: # отредактировать inventory/hosts.yml, group_vars/all.yml, vault
# inventory/hosts.yml
# group_vars/all.yml
# group_vars/hysteria2_servers/vault.yml
make vault-encrypt # зашифровать пароли VPS make vault-encrypt
make ping # проверить SSH make ping
make install # установка → output/ → браузер откроется сам make install # → output/index.html откроется в браузере
``` ```
---
## Makefile ## Makefile
| Команда | Описание | | Команда | Описание |
@@ -26,23 +60,24 @@ make install # установка → output/ → браузер отк
| `make init` | Создать конфиги из `.example` | | `make init` | Создать конфиги из `.example` |
| `make ping` | Проверить SSH к VPS | | `make ping` | Проверить SSH к VPS |
| `make status` | `systemctl status hysteria-server` | | `make status` | `systemctl status hysteria-server` |
| `make install` | Установка + экспорт + `output/index.html` + открытие в браузере | | `make install` | Установка masquerade + экспорт URL/QR/HTML |
| `make update` | Обновить бинарник, конфиг, перевыпустить экспорт | | `make update` | Обновить бинарник, конфиг, перевыпустить экспорт |
| `make export` | Только экспорт URL/QR/HTML | | `make export` | Только экспорт (URL, QR, HTML) |
| `make uninstall` | Удалить Hysteria2 с VPS | | `make uninstall` | Удалить Hysteria2 с VPS |
| `make vault-encrypt` | Зашифровать vault | | `make vault-encrypt` | Зашифровать vault |
| `make vault-edit` | Редактировать vault | | `make vault-edit` | Редактировать vault |
### Примеры
```bash ```bash
make install LIMIT=vps-de make install LIMIT=vps-de
make update LIMIT=vps-nl make update LIMIT=vps-nl
make export make export
make uninstall LIMIT=vps-de EXTRA_VARS='hysteria2_uninstall_remove_local_output=true' make uninstall LIMIT=vps-de EXTRA_VARS='hysteria2_uninstall_remove_local_output=true'
make install EXTRA_VARS='hysteria2_open_browser=false' # без авто-открытия браузера make install EXTRA_VARS='hysteria2_open_browser=false'
make update EXTRA_VARS='hysteria2_wait_for_acme=false'
``` ```
---
## Inventory ## Inventory
```yaml ```yaml
@@ -70,7 +105,7 @@ all:
- bob - bob
``` ```
### SSH-подключение к VPS ### SSH (VPS)
| Параметр | Где | Описание | | Параметр | Где | Описание |
|---|---|---| |---|---|---|
@@ -78,7 +113,7 @@ all:
| `ansible_port` | inventory | SSH-порт (по умолчанию `22`) | | `ansible_port` | inventory | SSH-порт (по умолчанию `22`) |
| `ansible_user` | inventory | Пользователь SSH (обычно `root`) | | `ansible_user` | inventory | Пользователь SSH (обычно `root`) |
| `ansible_password` | inventory + vault | Пароль из `vault_ssh_passwords` | | `ansible_password` | inventory + vault | Пароль из `vault_ssh_passwords` |
| `ansible_ssh_private_key_file` | inventory | Альтернатива паролю — SSH-ключ | | `ansible_ssh_private_key_file` | inventory | Альтернатива — SSH-ключ |
```yaml ```yaml
# group_vars/hysteria2_servers/vault.yml # group_vars/hysteria2_servers/vault.yml
@@ -89,9 +124,61 @@ vault_ssh_passwords:
Ключи в `vault_ssh_passwords` совпадают с **именами хостов** в inventory. Ключи в `vault_ssh_passwords` совпадают с **именами хостов** в inventory.
---
## Как работает masquerade в этом проекте
### Сервер (`/etc/hysteria/config.yaml`)
```yaml
listen: 0.0.0.0:443
acme:
type: http
domains:
- vpn-de.example.com
email: admin@example.com
auth:
type: userpass
userpass:
my: "..."
friend: "..."
masquerade:
type: file
file:
dir: /var/www/masq
listenHTTP: :80
listenHTTPS: :443
forceHTTPS: true
```
### Сайт-заглушка
В `/var/www/masq/index.html` — официальная страница **Welcome to nginx!**
При открытии домена в браузере — валидный HTTPS-сайт с Let's Encrypt.
### Клиент (генерируется автоматически)
```yaml
server: vpn-de.example.com:443
auth: my:password
```
### URI (пример)
```
hysteria2://my:password@vpn-de.example.com:443#my
```
Роль вызывает `hysteria share` — URI и QR формируются автоматически.
---
## Пароли VPN-пользователей ## Пароли VPN-пользователей
1. **Vault** (рекомендуется): 1. **Vault (рекомендуется):**
```yaml ```yaml
vault_hysteria2_user_passwords: vault_hysteria2_user_passwords:
@@ -99,22 +186,56 @@ vault_hysteria2_user_passwords:
friend: "Aingae0Okit1eek4eeZahFohVei4akee" friend: "Aingae0Okit1eek4eeZahFohVei4akee"
``` ```
2. **Per-host в inventory**: Подключается через `group_vars/hysteria2_servers/vars.yml`:
```yaml
```
2. **Per-host в inventory:**
```yaml ```yaml
hysteria2_user_passwords: hysteria2_user_passwords:
friend: "custom-password" friend: "custom-password"
``` ```
3. **Автогенерация**`pwgen -s 40`, если пароль не задан. 3. **Автогенерация**Ansible `password` lookup (длина `hysteria2_password_length`), если пароль не задан.
При `make update` пароли подтягиваются из `output/<server>/server-info.yml`, если не указаны в vault/inventory. При `make update` пароли подтягиваются из `output/<server>/server-info.yml`, если не указаны в vault/inventory.
---
## Let's Encrypt — обновление сертификата
**Да, Hysteria2 обновляет сертификат автоматически.**
При блоке `acme:` в конфиге встроенный ACME-клиент Hysteria2 сам получает и **продлевает** сертификат Let's Encrypt (срок ~90 дней). Повторный `make install` или certbot для продления **не нужны** — процесс `hysteria-server` делает это сам.
**Условия для авто-продления (ветка `main`):**
- сервер **запущен** и доступен из интернета;
- домен указывает на IP VPS;
- порты **80/tcp** и **443/tcp** открыты (ACME HTTP challenge + masquerade);
- конфиг `acme` не удалён.
Проверка логов: `journalctl -u hysteria-server -f`
---
## Firewall
По умолчанию открываются:
- **80/tcp** — ACME HTTP + masquerade HTTP
- **443/tcp** — HTTPS masquerade
- **443/udp** — Hysteria2
---
## Результат: папка `output/` ## Результат: папка `output/`
``` ```
output/ output/
├── index.html ← общий каталог всех серверов (открывается в браузере) ├── index.html ← все серверы (открывается в браузере)
├── vps-de/ ├── vps-de/
│ ├── index.html ← страница сервера │ ├── index.html ← страница сервера
│ ├── my.url │ ├── my.url
@@ -126,27 +247,17 @@ output/
└── ... └── ...
``` ```
### HTML-страницы HTML-страницы показывают:
**`output/index.html`** — общий каталог: - пароль и URL каждого пользователя с **кнопкой копирования**;
- все серверы и пользователи на одной странице - QR-коды;
- навигация по серверам - ссылки на файлы и страницы серверов.
- поля ссылки/пароля с кнопкой копирования
- QR-коды
- ссылки на файлы и страницы серверов
**`output/<server>/index.html`** — страница одного сервера (тот же стиль, все пользователи сервера).
После `make install`, `make update` и `make export` **`output/index.html` автоматически открывается в браузере** (macOS: `open`, Linux: `xdg-open`). После `make install`, `make update` и `make export` **`output/index.html` автоматически открывается в браузере** (macOS: `open`, Linux: `xdg-open`).
Отключить авто-открытие: Отключить: `hysteria2_open_browser: false` в `group_vars/all.yml` или `EXTRA_VARS`.
```yaml ---
# group_vars/all.yml
hysteria2_open_browser: false
```
Или: `make install EXTRA_VARS='hysteria2_open_browser=false'`
## QR-коды ## QR-коды
@@ -158,6 +269,8 @@ PNG генерируются средствами Ansible (без Python):
ASCII QR — `hysteria share --qr``user.qr.txt`. ASCII QR — `hysteria share --qr``user.qr.txt`.
---
## Переменные ## Переменные
| Переменная | Где | Описание | | Переменная | Где | Описание |
@@ -168,19 +281,64 @@ ASCII QR — `hysteria share --qr` → `user.qr.txt`.
| `hysteria2_user_passwords` | host/vault | Свои пароли VPN | | `hysteria2_user_passwords` | host/vault | Свои пароли VPN |
| `hysteria2_output_dir` | group | Папка экспорта (по умолчанию `./output`) | | `hysteria2_output_dir` | group | Папка экспорта (по умолчанию `./output`) |
| `hysteria2_output_name` | host | Имя подпапки (по умолчанию `inventory_hostname`) | | `hysteria2_output_name` | host | Имя подпапки (по умолчанию `inventory_hostname`) |
| `hysteria2_listen_port` | group | Порт Hysteria2 (443) |
| `hysteria2_generate_qr_png` | group | PNG QR через `qrencode` | | `hysteria2_generate_qr_png` | group | PNG QR через `qrencode` |
| `hysteria2_open_browser` | group | Открыть `output/index.html` после экспорта | | `hysteria2_open_browser` | group | Открыть `output/index.html` после экспорта |
| `hysteria2_uninstall_remove_local_output` | extra-vars | Удалить `output/<server>/` при uninstall | | `hysteria2_uninstall_remove_local_output` | extra-vars | Удалить `output/<server>/` при uninstall |
---
## Безопасность ## Безопасность
- `output/` содержит пароли и URL — в `.gitignore` - `output/` содержит пароли и URL — **не коммитить** (в `.gitignore`)
- `inventory/hosts.yml`, `vault.yml`, `.vault_pass` — не коммитить - `inventory/hosts.yml`, `vault.yml`, `.vault_pass` — не коммитить
- После `make init` выполните `make vault-encrypt` - После `make init` выполните `make vault-encrypt`
---
## Требования ## Требования
- Ansible 2.14+ - Ansible 2.14+
- Debian/Ubuntu VPS с sudo - Debian/Ubuntu VPS с sudo
- Домен с A-записью на IP сервера - Домен с A-записью на IP сервера
- Для авто-открытия браузера: macOS или Linux с `xdg-open` - Для авто-открытия браузера: macOS или Linux с `xdg-open`
---
## Структура роли
```
roles/hysteria2/
├── tasks/
│ ├── install.yml ← пакеты, hysteria, qrencode
│ ├── configure.yml ← masquerade + ACME + firewall
│ ├── export.yml ← URL, QR, HTML сервера
│ └── export_global.yml ← общий output/index.html
└── templates/
├── config.yaml.j2 ← acme http + masquerade file
├── client.yaml.j2
├── masq/index.html.j2
└── export/ ← HTML-каталоги
```
---
## Переключение между ветками
```bash
git fetch origin
git checkout main # masquerade + nginx (эта ветка)
git checkout salamander # Salamander obfs
```
Конфиги на **уже установленном** сервере **не меняются** при переключении ветки в git.
Чтобы применить другой режим на VPS:
```bash
git checkout salamander # или main
make install # перекатит конфиг сервера
# или
make update
```
> Переключение режима **меняет** `/etc/hysteria/config.yaml` на сервере. Клиентские URI/QR нужно **перевыпустить** (`make export` или `make update`).
+2 -1
View File
@@ -3,7 +3,8 @@ inventory = inventory/hosts.yml
roles_path = roles roles_path = roles
host_key_checking = False host_key_checking = False
retry_files_enabled = False retry_files_enabled = False
stdout_callback = yaml stdout_callback = default
result_format = yaml
interpreter_python = auto_silent interpreter_python = auto_silent
[privilege_escalation] [privilege_escalation]
+1 -1
View File
@@ -2,7 +2,7 @@
# Email для Let's Encrypt (ACME) # Email для Let's Encrypt (ACME)
hysteria2_acme_email: admin@example.com hysteria2_acme_email: admin@example.com
# Длина автогенерируемых паролей (pwgen) # Длина автогенерируемых паролей VPN-пользователей
hysteria2_password_length: 40 hysteria2_password_length: 40
# Обновлять систему перед установкой (apt update && apt upgrade) # Обновлять систему перед установкой (apt update && apt upgrade)
+1 -1
View File
@@ -9,7 +9,7 @@ hysteria2_acme_email: ""
hysteria2_users: [] hysteria2_users: []
# Опционально: фиксированные пароли { username: password } # Опционально: фиксированные пароли { username: password }
# Пустое значение или отсутствие ключа — автогенерация через pwgen # Пустое значение или отсутствие ключа — автогенерация на control node (Ansible password lookup)
hysteria2_password_length: 40 hysteria2_password_length: 40
hysteria2_listen_port: 443 hysteria2_listen_port: 443
+2 -2
View File
@@ -11,7 +11,7 @@
ansible.builtin.apt: ansible.builtin.apt:
upgrade: dist upgrade: dist
- name: Install curl, micro, pwgen and qrencode - name: Install curl, micro and qrencode
ansible.builtin.apt: ansible.builtin.apt:
name: "{{ _hysteria2_apt_packages }}" name: "{{ _hysteria2_apt_packages }}"
state: present state: present
@@ -19,7 +19,7 @@
vars: vars:
_hysteria2_apt_packages: >- _hysteria2_apt_packages: >-
{{ {{
['curl', 'micro', 'pwgen'] ['curl', 'micro']
+ (['qrencode'] if hysteria2_generate_qr_png | bool else []) + (['qrencode'] if hysteria2_generate_qr_png | bool else [])
}} }}
+16 -26
View File
@@ -60,16 +60,24 @@
- update - update
- export - export
- name: Generate missing user passwords with pwgen - name: Generate missing user passwords
ansible.builtin.command: ansible.builtin.set_fact:
cmd: "pwgen -s {{ hysteria2_password_length }} 1" _hysteria2_users_with_passwords: "{{ _hysteria2_users_with_passwords | default([]) + [ _entry ] }}"
register: _hysteria2_pwgen vars:
changed_when: false _entry:
when: item.password | length == 0 name: "{{ item.name }}"
password: >-
{{
lookup(
'password',
'/dev/null chars=ascii_letters,digits length=' ~ (hysteria2_password_length | string)
)
if item.password | length == 0
else item.password
}}
loop: "{{ hysteria2_resolved_users }}" loop: "{{ hysteria2_resolved_users }}"
loop_control: loop_control:
label: "{{ item.name }}" label: "{{ item.name }}"
index_var: _hysteria2_user_idx
tags: tags:
- install - install
- update - update
@@ -77,25 +85,7 @@
- name: Apply generated passwords - name: Apply generated passwords
ansible.builtin.set_fact: ansible.builtin.set_fact:
hysteria2_resolved_users: "{{ hysteria2_resolved_users | default([]) + [ _entry ] }}" hysteria2_resolved_users: "{{ _hysteria2_users_with_passwords }}"
vars:
_generated: >-
{{
_hysteria2_pwgen.results[_hysteria2_user_idx].stdout | default('')
if (
item.password | length == 0
and not (_hysteria2_pwgen.results[_hysteria2_user_idx].skipped | default(false))
)
else item.password
}}
_entry:
name: "{{ item.name }}"
password: "{{ _generated }}"
loop: "{{ hysteria2_resolved_users }}"
loop_control:
label: "{{ item.name }}"
index_var: _hysteria2_user_idx
when: _hysteria2_pwgen is defined
tags: tags:
- install - install
- update - update