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
.DEFAULT_GOAL := help
export EDITOR="nano"
ANSIBLE ?= ansible-playbook
ANSIBLE_ADHOC ?= ansible
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
cd ~/Разработка/hysteria2
git clone https://git.antropoff.ru/DevOpsTools/hysteria2.git
cd hysteria2
git checkout main
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
| Команда | Описание |
@@ -26,23 +60,24 @@ make install # установка → output/ → браузер отк
| `make init` | Создать конфиги из `.example` |
| `make ping` | Проверить SSH к VPS |
| `make status` | `systemctl status hysteria-server` |
| `make install` | Установка + экспорт + `output/index.html` + открытие в браузере |
| `make install` | Установка masquerade + экспорт 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 install EXTRA_VARS='hysteria2_open_browser=false'
make update EXTRA_VARS='hysteria2_wait_for_acme=false'
```
---
## Inventory
```yaml
@@ -70,7 +105,7 @@ all:
- bob
```
### SSH-подключение к VPS
### SSH (VPS)
| Параметр | Где | Описание |
|---|---|---|
@@ -78,7 +113,7 @@ all:
| `ansible_port` | inventory | SSH-порт (по умолчанию `22`) |
| `ansible_user` | inventory | Пользователь SSH (обычно `root`) |
| `ansible_password` | inventory + vault | Пароль из `vault_ssh_passwords` |
| `ansible_ssh_private_key_file` | inventory | Альтернатива паролю — SSH-ключ |
| `ansible_ssh_private_key_file` | inventory | Альтернатива — SSH-ключ |
```yaml
# group_vars/hysteria2_servers/vault.yml
@@ -89,9 +124,61 @@ vault_ssh_passwords:
Ключи в `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-пользователей
1. **Vault** (рекомендуется):
1. **Vault (рекомендуется):**
```yaml
vault_hysteria2_user_passwords:
@@ -99,22 +186,56 @@ vault_hysteria2_user_passwords:
friend: "Aingae0Okit1eek4eeZahFohVei4akee"
```
2. **Per-host в inventory**:
Подключается через `group_vars/hysteria2_servers/vars.yml`:
```yaml
```
2. **Per-host в inventory:**
```yaml
hysteria2_user_passwords:
friend: "custom-password"
```
3. **Автогенерация**`pwgen -s 40`, если пароль не задан.
3. **Автогенерация**Ansible `password` lookup (длина `hysteria2_password_length`), если пароль не задан.
При `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/
├── index.html ← общий каталог всех серверов (открывается в браузере)
├── index.html ← все серверы (открывается в браузере)
├── vps-de/
│ ├── index.html ← страница сервера
│ ├── my.url
@@ -126,27 +247,17 @@ output/
└── ...
```
### HTML-страницы
HTML-страницы показывают:
**`output/index.html`** — общий каталог:
- все серверы и пользователи на одной странице
- навигация по серверам
- поля ссылки/пароля с кнопкой копирования
- QR-коды
- ссылки на файлы и страницы серверов
**`output/<server>/index.html`** — страница одного сервера (тот же стиль, все пользователи сервера).
- пароль и URL каждого пользователя с **кнопкой копирования**;
- QR-коды;
- ссылки на файлы и страницы серверов.
После `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-коды
@@ -158,6 +269,8 @@ PNG генерируются средствами Ansible (без Python):
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_output_dir` | group | Папка экспорта (по умолчанию `./output`) |
| `hysteria2_output_name` | host | Имя подпапки (по умолчанию `inventory_hostname`) |
| `hysteria2_listen_port` | group | Порт Hysteria2 (443) |
| `hysteria2_generate_qr_png` | group | PNG QR через `qrencode` |
| `hysteria2_open_browser` | group | Открыть `output/index.html` после экспорта |
| `hysteria2_uninstall_remove_local_output` | extra-vars | Удалить `output/<server>/` при uninstall |
---
## Безопасность
- `output/` содержит пароли и URL — в `.gitignore`
- `output/` содержит пароли и URL — **не коммитить** (в `.gitignore`)
- `inventory/hosts.yml`, `vault.yml`, `.vault_pass` — не коммитить
- После `make init` выполните `make vault-encrypt`
---
## Требования
- Ansible 2.14+
- Debian/Ubuntu VPS с sudo
- Домен с A-записью на IP сервера
- Для авто-открытия браузера: 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
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
stdout_callback = default
result_format = yaml
interpreter_python = auto_silent
[privilege_escalation]
+1 -1
View File
@@ -2,7 +2,7 @@
# Email для Let's Encrypt (ACME)
hysteria2_acme_email: admin@example.com
# Длина автогенерируемых паролей (pwgen)
# Длина автогенерируемых паролей VPN-пользователей
hysteria2_password_length: 40
# Обновлять систему перед установкой (apt update && apt upgrade)
+1 -1
View File
@@ -9,7 +9,7 @@ hysteria2_acme_email: ""
hysteria2_users: []
# Опционально: фиксированные пароли { username: password }
# Пустое значение или отсутствие ключа — автогенерация через pwgen
# Пустое значение или отсутствие ключа — автогенерация на control node (Ansible password lookup)
hysteria2_password_length: 40
hysteria2_listen_port: 443
+2 -2
View File
@@ -11,7 +11,7 @@
ansible.builtin.apt:
upgrade: dist
- name: Install curl, micro, pwgen and qrencode
- name: Install curl, micro and qrencode
ansible.builtin.apt:
name: "{{ _hysteria2_apt_packages }}"
state: present
@@ -19,7 +19,7 @@
vars:
_hysteria2_apt_packages: >-
{{
['curl', 'micro', 'pwgen']
['curl', 'micro']
+ (['qrencode'] if hysteria2_generate_qr_png | bool else [])
}}
+16 -26
View File
@@ -60,16 +60,24 @@
- update
- export
- name: Generate missing user passwords with pwgen
ansible.builtin.command:
cmd: "pwgen -s {{ hysteria2_password_length }} 1"
register: _hysteria2_pwgen
changed_when: false
when: item.password | length == 0
- name: Generate missing user passwords
ansible.builtin.set_fact:
_hysteria2_users_with_passwords: "{{ _hysteria2_users_with_passwords | default([]) + [ _entry ] }}"
vars:
_entry:
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_control:
label: "{{ item.name }}"
index_var: _hysteria2_user_idx
tags:
- install
- update
@@ -77,25 +85,7 @@
- name: Apply generated passwords
ansible.builtin.set_fact:
hysteria2_resolved_users: "{{ hysteria2_resolved_users | default([]) + [ _entry ] }}"
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
hysteria2_resolved_users: "{{ _hysteria2_users_with_passwords }}"
tags:
- install
- update