From 6f96a26bede23d8959a6b4a3bd9a100eec941920 Mon Sep 17 00:00:00 2001 From: Sergey Antropoff Date: Wed, 1 Jul 2026 02:02:58 +0300 Subject: [PATCH] Initial commit: Ansible role for Hysteria2 VPN server deployment. Includes install/update/uninstall playbooks, Makefile, vault-based SSH credentials, per-server and global HTML export with QR codes. --- .gitignore | 214 ++++++++++ Makefile | 113 +++++ README.md | 186 +++++++++ ansible.cfg | 14 + group_vars/all.yml.example | 24 ++ group_vars/hysteria2_servers/vars.yml.example | 5 + .../hysteria2_servers/vault.yml.example | 18 + inventory/hosts.yml.example | 30 ++ playbook-uninstall.yml | 7 + playbook.yml | 17 + roles/hysteria2/defaults/main.yml | 42 ++ roles/hysteria2/handlers/main.yml | 7 + roles/hysteria2/meta/main.yml | 18 + roles/hysteria2/tasks/configure.yml | 69 ++++ roles/hysteria2/tasks/export.yml | 63 +++ roles/hysteria2/tasks/export_global.yml | 90 ++++ roles/hysteria2/tasks/install.yml | 37 ++ roles/hysteria2/tasks/main.yml | 28 ++ roles/hysteria2/tasks/share_user.yml | 114 +++++ roles/hysteria2/tasks/uninstall.yml | 46 +++ roles/hysteria2/tasks/update.yml | 11 + roles/hysteria2/tasks/users.yml | 102 +++++ roles/hysteria2/tasks/validate.yml | 17 + roles/hysteria2/templates/client.yaml.j2 | 3 + roles/hysteria2/templates/config.yaml.j2 | 22 + .../templates/export/global-index.html.j2 | 389 ++++++++++++++++++ .../hysteria2/templates/export/index.html.j2 | 314 ++++++++++++++ roles/hysteria2/templates/masq/index.html.j2 | 27 ++ 28 files changed, 2027 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 ansible.cfg create mode 100644 group_vars/all.yml.example create mode 100644 group_vars/hysteria2_servers/vars.yml.example create mode 100644 group_vars/hysteria2_servers/vault.yml.example create mode 100644 inventory/hosts.yml.example create mode 100644 playbook-uninstall.yml create mode 100644 playbook.yml create mode 100644 roles/hysteria2/defaults/main.yml create mode 100644 roles/hysteria2/handlers/main.yml create mode 100644 roles/hysteria2/meta/main.yml create mode 100644 roles/hysteria2/tasks/configure.yml create mode 100644 roles/hysteria2/tasks/export.yml create mode 100644 roles/hysteria2/tasks/export_global.yml create mode 100644 roles/hysteria2/tasks/install.yml create mode 100644 roles/hysteria2/tasks/main.yml create mode 100644 roles/hysteria2/tasks/share_user.yml create mode 100644 roles/hysteria2/tasks/uninstall.yml create mode 100644 roles/hysteria2/tasks/update.yml create mode 100644 roles/hysteria2/tasks/users.yml create mode 100644 roles/hysteria2/tasks/validate.yml create mode 100644 roles/hysteria2/templates/client.yaml.j2 create mode 100644 roles/hysteria2/templates/config.yaml.j2 create mode 100644 roles/hysteria2/templates/export/global-index.html.j2 create mode 100644 roles/hysteria2/templates/export/index.html.j2 create mode 100644 roles/hysteria2/templates/masq/index.html.j2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aaf43d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,214 @@ +output/ +inventory/hosts.yml +group_vars/all.yml +group_vars/hysteria2_servers/vault.yml +group_vars/hysteria2_servers/vars.yml +*.retry +.vault_pass + +# ---> Ansible +*.retry + +# ---> macOS +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# ---> Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b5a7275 --- /dev/null +++ b/Makefile @@ -0,0 +1,113 @@ +# ═══════════════════════════════════════════════════════════════════════════════ +# Hysteria2 Ansible — Makefile +# ═══════════════════════════════════════════════════════════════════════════════ + +SHELL := /bin/bash +.DEFAULT_GOAL := help + +ANSIBLE ?= ansible-playbook +ANSIBLE_ADHOC ?= ansible +INVENTORY ?= inventory/hosts.yml +LIMIT ?= +EXTRA_VARS ?= +TAGS ?= +ASK_PASS ?= + +# vault: .vault_pass или интерактивный ввод +VAULT_FILE := .vault_pass +VAULT_ARGS := $(if $(wildcard $(VAULT_FILE)),--vault-password-file $(VAULT_FILE),) + +ANSIBLE_OPTS := $(VAULT_ARGS) \ + $(if $(LIMIT),--limit $(LIMIT),) \ + $(if $(EXTRA_VARS),--extra-vars "$(EXTRA_VARS)",) \ + $(if $(ASK_BECOME_PASS),--ask-become-pass,) \ + $(if $(ASK_PASS),--ask-pass,) + +CYAN := \033[0;36m +GREEN := \033[0;32m +YELLOW := \033[1;33m +BOLD := \033[1m +NC := \033[0m + +.PHONY: help init check ping status \ + install update export uninstall \ + vault-init vault-encrypt vault-edit vault-view + +help: ## Показать справку + @echo "" + @echo "$(BOLD)Hysteria2 Ansible$(NC)" + @echo "" + @grep -E '^[a-zA-Z0-9_-]+:.*##' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*## "}; {printf " $(CYAN)%-16s$(NC) %s\n", $$1, $$2}' + @echo "" + @echo " $(YELLOW)Примеры:$(NC)" + @echo " make init" + @echo " make vault-init && make vault-encrypt" + @echo " make install" + @echo " make install LIMIT=vps-de" + @echo " make update LIMIT=vps-nl" + @echo " make uninstall LIMIT=vps-de EXTRA_VARS='hysteria2_uninstall_remove_local_output=true'" + @echo "" + +init: ## Создать inventory, group_vars и .vault_pass из примеров + @test -f inventory/hosts.yml || cp inventory/hosts.yml.example inventory/hosts.yml + @test -f group_vars/all.yml || cp group_vars/all.yml.example group_vars/all.yml + @test -f group_vars/hysteria2_servers/vars.yml || cp group_vars/hysteria2_servers/vars.yml.example group_vars/hysteria2_servers/vars.yml + @test -f group_vars/hysteria2_servers/vault.yml || cp group_vars/hysteria2_servers/vault.yml.example group_vars/hysteria2_servers/vault.yml + @test -f .vault_pass || (openssl rand -base64 32 > .vault_pass && chmod 600 .vault_pass) + @grep -q 'vault_password_file' ansible.cfg || \ + (echo "" >> ansible.cfg && echo "vault_password_file = .vault_pass" >> ansible.cfg) + @echo "$(GREEN)Готово. Отредактируйте:$(NC)" + @echo " inventory/hosts.yml" + @echo " group_vars/all.yml" + @echo " group_vars/hysteria2_servers/vault.yml → затем: make vault-encrypt" + +check: ## Проверить синтаксис playbook + $(ANSIBLE) playbook.yml --syntax-check $(VAULT_ARGS) + $(ANSIBLE) playbook-uninstall.yml --syntax-check $(VAULT_ARGS) + +ping: ## Проверить SSH-доступ ко всем VPS + $(ANSIBLE_ADHOC) hysteria2_servers -m ping $(ANSIBLE_OPTS) + +status: ## Статус hysteria-server на VPS + $(ANSIBLE_ADHOC) hysteria2_servers -m shell \ + -a "systemctl status hysteria-server --no-pager || systemctl status hysteria2 --no-pager" \ + -b $(VAULT_ARGS) $(if $(LIMIT),--limit $(LIMIT),) + +install: check ## Установить Hysteria2 на VPS (+ URL и QR локально) + $(ANSIBLE) playbook.yml --tags install $(ANSIBLE_OPTS) + +update: check ## Обновить бинарник, перекатить конфиг, перевыпустить URL/QR + $(ANSIBLE) playbook.yml --tags update --extra-vars "hysteria2_wait_for_acme=false hysteria2_upgrade_system=false" $(ANSIBLE_OPTS) + +export: check ## Только перевыпустить URL и QR (без изменений на сервере) + $(ANSIBLE) playbook.yml --tags export $(ANSIBLE_OPTS) + +uninstall: ## Удалить Hysteria2 с VPS + @echo "$(YELLOW)Будет выполнено удаление Hysteria2$(NC) $(if $(LIMIT),на $(LIMIT),на всех серверах)" + @read -p "Продолжить? [y/N] " c; [[ "$$c" =~ ^[Yy]$$ ]] || exit 1 + $(ANSIBLE) playbook-uninstall.yml --tags uninstall $(ANSIBLE_OPTS) + +vault-init: ## Создать .vault_pass и включить vault_password_file в ansible.cfg + @test -f .vault_pass || (openssl rand -base64 32 > .vault_pass && chmod 600 .vault_pass) + @if ! grep -q '^vault_password_file' ansible.cfg; then \ + sed -i.bak 's/# vault_password_file/vault_password_file/' ansible.cfg; \ + rm -f ansible.cfg.bak; \ + fi + @echo "$(GREEN).vault_pass готов$(NC)" + +vault-encrypt: vault-init ## Зашифровать group_vars/hysteria2_servers/vault.yml + @if [ ! -f group_vars/hysteria2_servers/vault.yml ]; then \ + echo "$(YELLOW)Сначала: make init$(NC)"; exit 1; \ + fi + @if grep -q 'ANSIBLE_VAULT' group_vars/hysteria2_servers/vault.yml 2>/dev/null; then \ + echo "vault.yml уже зашифрован"; \ + else \ + ansible-vault encrypt group_vars/hysteria2_servers/vault.yml --vault-password-file $(VAULT_FILE); \ + fi + +vault-edit: vault-init ## Редактировать зашифрованный vault + ansible-vault edit group_vars/hysteria2_servers/vault.yml --vault-password-file $(VAULT_FILE) + +vault-view: vault-init ## Показать содержимое vault (расшифровка) + ansible-vault view group_vars/hysteria2_servers/vault.yml --vault-password-file $(VAULT_FILE) diff --git a/README.md b/README.md new file mode 100644 index 0000000..6fe7373 --- /dev/null +++ b/README.md @@ -0,0 +1,186 @@ +# Ansible-роль: Hysteria2 Server + +Ansible-роль для установки [Hysteria 2](https://v2.hysteria.network/) на Debian/Ubuntu VPS: ACME-сертификат, masquerade под nginx, несколько пользователей, экспорт URL/QR и HTML-каталог. + +## Быстрый старт + +```bash +cd ~/Разработка/hysteria2 + +make init # inventory, group_vars, vault, .vault_pass +# отредактировать: +# inventory/hosts.yml +# group_vars/all.yml +# group_vars/hysteria2_servers/vault.yml + +make vault-encrypt # зашифровать пароли VPS +make ping # проверить SSH +make install # установка → output/ → браузер откроется сам +``` + +## Makefile + +| Команда | Описание | +|---|---| +| `make help` | Справка | +| `make init` | Создать конфиги из `.example` | +| `make ping` | Проверить SSH к VPS | +| `make status` | `systemctl status hysteria-server` | +| `make install` | Установка + экспорт + `output/index.html` + открытие в браузере | +| `make update` | Обновить бинарник, конфиг, перевыпустить экспорт | +| `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' # без авто-открытия браузера +``` + +## Inventory + +```yaml +all: + children: + hysteria2_servers: + hosts: + vps-de: + ansible_host: 203.0.113.10 + ansible_port: 2222 # SSH-порт (если не 22) + ansible_user: root + ansible_password: "{{ vault_ssh_passwords['vps-de'] }}" + hysteria2_domain: vpn-de.example.com + 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-ключ | + +```yaml +# group_vars/hysteria2_servers/vault.yml +vault_ssh_passwords: + vps-de: "root-password-1" + vps-nl: "root-password-2" +``` + +Ключи в `vault_ssh_passwords` совпадают с **именами хостов** в inventory. + +## Пароли VPN-пользователей + +1. **Vault** (рекомендуется): + +```yaml +vault_hysteria2_user_passwords: + vps-de: + friend: "Aingae0Okit1eek4eeZahFohVei4akee" +``` + +2. **Per-host в inventory**: + +```yaml +hysteria2_user_passwords: + friend: "custom-password" +``` + +3. **Автогенерация** — `pwgen -s 40`, если пароль не задан. + +При `make update` пароли подтягиваются из `output//server-info.yml`, если не указаны в vault/inventory. + +## Результат: папка `output/` + +``` +output/ +├── index.html ← общий каталог всех серверов (открывается в браузере) +├── vps-de/ +│ ├── index.html ← страница сервера +│ ├── my.url +│ ├── my.png ← QR PNG +│ ├── my.qr.txt ← QR ASCII +│ ├── my.txt +│ └── server-info.yml +└── vps-nl/ + └── ... +``` + +### HTML-страницы + +**`output/index.html`** — общий каталог: +- все серверы и пользователи на одной странице +- навигация по серверам +- поля ссылки/пароля с кнопкой копирования +- 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 | + +## Безопасность + +- `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` diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..579e02f --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,14 @@ +[defaults] +inventory = inventory/hosts.yml +roles_path = roles +host_key_checking = False +retry_files_enabled = False +stdout_callback = yaml +interpreter_python = auto_silent + +[privilege_escalation] +become = True +become_method = sudo + +# Раскомментируйте после `make vault-init`: +# vault_password_file = .vault_pass diff --git a/group_vars/all.yml.example b/group_vars/all.yml.example new file mode 100644 index 0000000..10756c4 --- /dev/null +++ b/group_vars/all.yml.example @@ -0,0 +1,24 @@ +--- +# Email для Let's Encrypt (ACME) +hysteria2_acme_email: admin@example.com + +# Длина автогенерируемых паролей (pwgen) +hysteria2_password_length: 40 + +# Обновлять систему перед установкой (apt update && apt upgrade) +hysteria2_upgrade_system: true + +# Локальная папка для URL и QR-кодов (на машине, где запускается ansible-playbook) +hysteria2_output_dir: "{{ playbook_dir }}/output" + +# Имя подпапки для каждого сервера (по умолчанию — inventory_hostname) +# hysteria2_output_name: "{{ inventory_hostname }}" + +# Открывать output/index.html в браузере после install/update/export +# hysteria2_open_browser: true + +# Порт Hysteria2 +hysteria2_listen_port: 443 + +# Открывать порты в ufw (80/tcp, 443/tcp, 443/udp) +hysteria2_configure_firewall: true diff --git a/group_vars/hysteria2_servers/vars.yml.example b/group_vars/hysteria2_servers/vars.yml.example new file mode 100644 index 0000000..95c129d --- /dev/null +++ b/group_vars/hysteria2_servers/vars.yml.example @@ -0,0 +1,5 @@ +--- +# Подключение vault-переменных к хостам группы hysteria2_servers. +# Файл group_vars/hysteria2_servers/vault.yml должен быть зашифрован (make vault-encrypt). + +# Проброс VPN-паролей из vault в переменные роли (опционально) diff --git a/group_vars/hysteria2_servers/vault.yml.example b/group_vars/hysteria2_servers/vault.yml.example new file mode 100644 index 0000000..f0e2b21 --- /dev/null +++ b/group_vars/hysteria2_servers/vault.yml.example @@ -0,0 +1,18 @@ +--- +# Пароли VPS (SSH) и VPN-пользователей. +# 1. Скопируйте: cp group_vars/hysteria2_servers/vault.yml.example group_vars/hysteria2_servers/vault.yml +# 2. Заполните значения +# 3. Зашифруйте: make vault-encrypt +# +# Ключи должны совпадать с именами хостов в inventory/hosts.yml + +vault_ssh_passwords: + vps-de: "CHANGE_ME_root_password_vps_de" + vps-nl: "CHANGE_ME_root_password_vps_nl" + +# Опционально: фиксированные пароли VPN по серверам +# vault_hysteria2_user_passwords: +# vps-de: +# friend: "Aingae0Okit1eek4eeZahFohVei4akee" +# vps-nl: +# alice: "CustomAlicePassword40chars................" diff --git a/inventory/hosts.yml.example b/inventory/hosts.yml.example new file mode 100644 index 0000000..367e3a8 --- /dev/null +++ b/inventory/hosts.yml.example @@ -0,0 +1,30 @@ +--- +all: + children: + hysteria2_servers: + hosts: + vps-de: + ansible_host: 203.0.113.10 + ansible_port: 2222 + ansible_user: root + # SSH-пароль root берётся из vault (см. group_vars/hysteria2_servers/vault.yml) + ansible_password: "{{ vault_ssh_passwords['vps-de'] }}" + # Альтернатива — SSH-ключ вместо пароля: + # ansible_ssh_private_key_file: ~/.ssh/id_ed25519 + hysteria2_domain: vpn-de.example.com + hysteria2_users: + - my + - friend + - yet_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 + # Свои пароли VPN-пользователей (опционально): + # hysteria2_user_passwords: + # alice: "Aingae0Okit1eek4eeZahFohVei4akee" diff --git a/playbook-uninstall.yml b/playbook-uninstall.yml new file mode 100644 index 0000000..ad4de3d --- /dev/null +++ b/playbook-uninstall.yml @@ -0,0 +1,7 @@ +--- +- name: Uninstall Hysteria2 servers + hosts: hysteria2_servers + gather_facts: false + roles: + - role: hysteria2 + tags: [uninstall] diff --git a/playbook.yml b/playbook.yml new file mode 100644 index 0000000..0bf8bb3 --- /dev/null +++ b/playbook.yml @@ -0,0 +1,17 @@ +--- +- name: Install and configure Hysteria2 servers + hosts: hysteria2_servers + gather_facts: true + roles: + - hysteria2 + +- name: Build global output index + hosts: localhost + connection: local + gather_facts: true + tags: [install, update, export] + tasks: + - name: Generate global index.html and open in browser + ansible.builtin.include_role: + name: hysteria2 + tasks_from: export_global.yml diff --git a/roles/hysteria2/defaults/main.yml b/roles/hysteria2/defaults/main.yml new file mode 100644 index 0000000..2733dfc --- /dev/null +++ b/roles/hysteria2/defaults/main.yml @@ -0,0 +1,42 @@ +--- +# Домен сервера (A-запись → IP VPS). Задаётся per-host в inventory. +hysteria2_domain: "" + +# Email для ACME / Let's Encrypt +hysteria2_acme_email: "" + +# Список имён пользователей VPN (пароли генерируются автоматически) +hysteria2_users: [] + +# Опционально: фиксированные пароли { username: password } +# Пустое значение или отсутствие ключа — автогенерация через pwgen + +hysteria2_password_length: 40 +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/handlers/main.yml b/roles/hysteria2/handlers/main.yml new file mode 100644 index 0000000..21e22c7 --- /dev/null +++ b/roles/hysteria2/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: Restart hysteria-server + ansible.builtin.systemd: + name: "{{ hysteria2_service_name }}" + state: restarted + daemon_reload: true + become: true diff --git a/roles/hysteria2/meta/main.yml b/roles/hysteria2/meta/main.yml new file mode 100644 index 0000000..4863d73 --- /dev/null +++ b/roles/hysteria2/meta/main.yml @@ -0,0 +1,18 @@ +--- +galaxy_info: + role_name: hysteria2 + author: inecs + description: Install Hysteria2 VPN server with masquerade site and export client URLs with QR codes + license: MIT + min_ansible_version: "2.14" + platforms: + - name: Debian + versions: + - bookworm + - bullseye + - name: Ubuntu + versions: + - jammy + - noble + +dependencies: [] diff --git a/roles/hysteria2/tasks/configure.yml b/roles/hysteria2/tasks/configure.yml new file mode 100644 index 0000000..f88af1f --- /dev/null +++ b/roles/hysteria2/tasks/configure.yml @@ -0,0 +1,69 @@ +--- +- 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 + ansible.builtin.template: + src: config.yaml.j2 + dest: "{{ hysteria2_config_path }}" + mode: "0644" + notify: Restart hysteria-server + +- name: Flush handlers before service check + ansible.builtin.meta: flush_handlers + +- name: Enable and start hysteria-server + ansible.builtin.systemd: + name: "{{ hysteria2_service_name }}" + enabled: true + state: started + daemon_reload: true + +- name: Check if ufw is available and active + ansible.builtin.command: ufw status + register: _hysteria2_ufw_status + changed_when: false + failed_when: false + when: hysteria2_configure_firewall | bool + +- name: Allow HTTP and HTTPS in ufw + ansible.builtin.command: "ufw allow {{ item }}" + loop: + - 80/tcp + - 443/tcp + - 443/udp + register: _hysteria2_ufw_allow + changed_when: "'Skipping' not in (_hysteria2_ufw_allow.stdout | default(''))" + failed_when: false + when: + - hysteria2_configure_firewall | bool + - "'active' in (_hysteria2_ufw_status.stdout | default(''))" + +- name: Wait for ACME certificate (first start may take several minutes) + ansible.builtin.pause: + seconds: 30 + prompt: "Ожидание получения ACME-сертификата для {{ hysteria2_domain }}..." + when: hysteria2_wait_for_acme | default(true) | bool + +- name: Verify hysteria-server is running + ansible.builtin.command: + cmd: "systemctl is-active {{ hysteria2_service_name }}" + register: _hysteria2_service_active + changed_when: false + failed_when: _hysteria2_service_active.stdout != 'active' diff --git a/roles/hysteria2/tasks/export.yml b/roles/hysteria2/tasks/export.yml new file mode 100644 index 0000000..5f1b739 --- /dev/null +++ b/roles/hysteria2/tasks/export.yml @@ -0,0 +1,63 @@ +--- +- name: Create local output directory for this server + ansible.builtin.file: + path: "{{ hysteria2_output_dir }}/{{ hysteria2_output_name }}" + state: directory + mode: "0700" + delegate_to: localhost + become: false + +- name: Initialize export users list + ansible.builtin.set_fact: + hysteria2_export_users: [] + +- name: Install qrencode on server for PNG QR export + ansible.builtin.apt: + name: qrencode + state: present + update_cache: false + when: hysteria2_generate_qr_png | bool + +- name: Build client share data for each user + ansible.builtin.include_tasks: share_user.yml + loop: "{{ hysteria2_resolved_users }}" + loop_control: + loop_var: hysteria2_current_user + label: "{{ hysteria2_current_user.name }}" + +- name: Save server summary locally + ansible.builtin.copy: + dest: "{{ hysteria2_output_dir }}/{{ hysteria2_output_name }}/server-info.yml" + mode: "0600" + content: | + server: {{ hysteria2_output_name }} + domain: {{ hysteria2_domain }} + port: {{ hysteria2_listen_port }} + users: + {% for user in hysteria2_export_users %} + - name: {{ user.name }} + password: "{{ user.password }}" + url: "{{ user.url }}" + has_png: {{ user.has_png | bool | lower }} + url_file: {{ user.name }}.url + qr_png: {{ user.name }}.png + html: index.html + {% endfor %} + delegate_to: localhost + become: false + +- name: Generate HTML summary page + ansible.builtin.template: + src: export/index.html.j2 + dest: "{{ hysteria2_output_dir }}/{{ hysteria2_output_name }}/index.html" + mode: "0644" + vars: + generated_at: "{{ ansible_date_time.date }} {{ ansible_date_time.time }}" + delegate_to: localhost + become: false + +- name: Show export location + ansible.builtin.debug: + msg: >- + Клиентские URL, QR и index.html сохранены в + {{ hysteria2_output_dir }}/{{ hysteria2_output_name }}/ diff --git a/roles/hysteria2/tasks/export_global.yml b/roles/hysteria2/tasks/export_global.yml new file mode 100644 index 0000000..baea209 --- /dev/null +++ b/roles/hysteria2/tasks/export_global.yml @@ -0,0 +1,90 @@ +--- +- name: Ensure output root directory exists + ansible.builtin.file: + path: "{{ hysteria2_output_dir }}" + state: directory + mode: "0700" + become: false + +- name: Find server-info.yml in output directories + ansible.builtin.find: + paths: "{{ hysteria2_output_dir }}" + patterns: server-info.yml + recurse: true + register: _hysteria2_server_info_files + become: false + +- name: Load server metadata from output + ansible.builtin.slurp: + src: "{{ item.path }}" + loop: "{{ _hysteria2_server_info_files.files | default([]) }}" + register: _hysteria2_server_info_raw + when: _hysteria2_server_info_files.matched | default(0) | int > 0 + become: false + +- name: Build global servers list + ansible.builtin.set_fact: + hysteria2_global_servers: "{{ hysteria2_global_servers | default([]) + [ _entry ] }}" + loop: "{{ _hysteria2_server_info_raw.results | default([]) }}" + when: not item.skipped | default(false) + vars: + _info: "{{ item.content | b64decode | from_yaml }}" + _entry: + name: "{{ _info.server }}" + domain: "{{ _info.domain }}" + port: "{{ _info.port }}" + dir: "{{ item.item.path | dirname | basename }}" + users: "{{ _info.users }}" + become: false + +- name: Sort global servers by name + ansible.builtin.set_fact: + hysteria2_global_servers: "{{ hysteria2_global_servers | sort(attribute='name') }}" + when: hysteria2_global_servers | default([]) | length > 0 + become: false + +- name: Generate global HTML index + ansible.builtin.template: + src: export/global-index.html.j2 + dest: "{{ hysteria2_output_dir }}/index.html" + mode: "0644" + vars: + generated_at: "{{ ansible_date_time.date }} {{ ansible_date_time.time }}" + total_users: "{{ hysteria2_global_servers | map(attribute='users') | map('length') | sum }}" + when: hysteria2_global_servers | default([]) | length > 0 + become: false + +- name: Open global index in default browser (macOS) + ansible.builtin.command: + cmd: open "{{ hysteria2_output_dir }}/index.html" + when: + - hysteria2_open_browser | bool + - hysteria2_global_servers | default([]) | length > 0 + - ansible_system == 'Darwin' + changed_when: false + become: false + +- name: Open global index in default browser (Linux) + ansible.builtin.command: + cmd: xdg-open "{{ hysteria2_output_dir }}/index.html" + when: + - hysteria2_open_browser | bool + - hysteria2_global_servers | default([]) | length > 0 + - ansible_system == 'Linux' + changed_when: false + become: false + failed_when: false + +- name: Show global index location + ansible.builtin.debug: + msg: >- + Общий каталог: {{ hysteria2_output_dir }}/index.html + ({% if hysteria2_open_browser | bool and hysteria2_global_servers | default([]) | length > 0 %}открыт в браузере{% else %}откройте вручную{% endif %}) + when: hysteria2_global_servers | default([]) | length > 0 + become: false + +- name: Skip global index when no servers exported + ansible.builtin.debug: + msg: "Глобальный index.html не создан — нет server-info.yml в {{ hysteria2_output_dir }}/" + when: hysteria2_global_servers | default([]) | length == 0 + become: false diff --git a/roles/hysteria2/tasks/install.yml b/roles/hysteria2/tasks/install.yml new file mode 100644 index 0000000..cd90d5d --- /dev/null +++ b/roles/hysteria2/tasks/install.yml @@ -0,0 +1,37 @@ +--- +- name: Update system and install dependencies + when: hysteria2_upgrade_system | bool + block: + - name: apt update + ansible.builtin.apt: + update_cache: true + cache_valid_time: 3600 + + - name: apt upgrade + ansible.builtin.apt: + upgrade: dist + +- name: Install curl, micro, pwgen and qrencode + ansible.builtin.apt: + name: "{{ _hysteria2_apt_packages }}" + state: present + update_cache: "{{ not (hysteria2_upgrade_system | bool) }}" + vars: + _hysteria2_apt_packages: >- + {{ + ['curl', 'micro', 'pwgen'] + + (['qrencode'] if hysteria2_generate_qr_png | bool else []) + }} + +- name: Install Hysteria2 via official script + ansible.builtin.shell: + cmd: bash <(curl -fsSL https://get.hy2.sh/) + executable: /bin/bash + args: + creates: /usr/local/bin/hysteria + register: _hysteria2_install + +- name: Show Hysteria2 install result + ansible.builtin.debug: + msg: "{{ _hysteria2_install.stdout_lines | default(['Hysteria2 already installed']) }}" + when: _hysteria2_install.stdout_lines is defined diff --git a/roles/hysteria2/tasks/main.yml b/roles/hysteria2/tasks/main.yml new file mode 100644 index 0000000..14c8d03 --- /dev/null +++ b/roles/hysteria2/tasks/main.yml @@ -0,0 +1,28 @@ +--- +- name: Install Hysteria2 + ansible.builtin.import_tasks: validate.yml + tags: [install, update, export] + +- name: Resolve VPN users + ansible.builtin.import_tasks: users.yml + tags: [install, update, export] + +- name: Install packages and Hysteria2 binary + ansible.builtin.import_tasks: install.yml + tags: [install] + +- name: Configure Hysteria2 server + ansible.builtin.import_tasks: configure.yml + tags: [install, update] + +- name: Update Hysteria2 binary + ansible.builtin.import_tasks: update.yml + tags: [update] + +- name: Export client URLs and QR codes + ansible.builtin.import_tasks: export.yml + tags: [install, update, export] + +- name: Uninstall Hysteria2 + ansible.builtin.import_tasks: uninstall.yml + tags: [never, uninstall] diff --git a/roles/hysteria2/tasks/share_user.yml b/roles/hysteria2/tasks/share_user.yml new file mode 100644 index 0000000..bdbf798 --- /dev/null +++ b/roles/hysteria2/tasks/share_user.yml @@ -0,0 +1,114 @@ +--- +- name: Create temporary client config on server + ansible.builtin.template: + src: client.yaml.j2 + dest: "/tmp/hysteria-client-{{ hysteria2_current_user.name }}.yaml" + mode: "0600" + +- name: Get hysteria2:// URL via hysteria share + ansible.builtin.command: + cmd: >- + hysteria share + -c /tmp/hysteria-client-{{ hysteria2_current_user.name }}.yaml + register: _hysteria2_share_url + changed_when: false + +- name: Normalize share URL + ansible.builtin.set_fact: + _hysteria2_client_url: "{{ _hysteria2_share_url.stdout | trim | regex_replace('^hy2://', 'hysteria2://') }}" + +- name: Save URL to local file + ansible.builtin.copy: + dest: "{{ hysteria2_output_dir }}/{{ hysteria2_output_name }}/{{ hysteria2_current_user.name }}.url" + mode: "0600" + content: "{{ _hysteria2_client_url }}\n" + delegate_to: localhost + become: false + +- name: Save user info text file + ansible.builtin.copy: + dest: "{{ hysteria2_output_dir }}/{{ hysteria2_output_name }}/{{ hysteria2_current_user.name }}.txt" + mode: "0600" + content: | + Server: {{ hysteria2_output_name }} + Domain: {{ hysteria2_domain }}:{{ hysteria2_listen_port }} + User: {{ hysteria2_current_user.name }} + Password: {{ hysteria2_current_user.password }} + + URL: + {{ _hysteria2_client_url }} + delegate_to: localhost + become: false + +- name: Get ASCII QR code from hysteria share + ansible.builtin.command: + cmd: >- + hysteria share + -c /tmp/hysteria-client-{{ hysteria2_current_user.name }}.yaml + --qr + register: _hysteria2_share_qr + changed_when: false + +- name: Save ASCII QR to local file + ansible.builtin.copy: + dest: "{{ hysteria2_output_dir }}/{{ hysteria2_output_name }}/{{ hysteria2_current_user.name }}.qr.txt" + mode: "0600" + content: | + {{ _hysteria2_share_qr.stdout }} + + URL: {{ _hysteria2_client_url }} + delegate_to: localhost + become: false + +- name: Generate PNG QR code with qrencode on server + when: hysteria2_generate_qr_png | bool + block: + - name: Write PNG QR code on server + ansible.builtin.command: + argv: + - qrencode + - -o + - "/tmp/{{ hysteria2_current_user.name }}.png" + - -s + - "{{ hysteria2_qr_png_size | string }}" + - -m + - "{{ hysteria2_qr_png_margin | string }}" + - -l + - "{{ hysteria2_qr_png_error_correction }}" + - "{{ _hysteria2_client_url }}" + changed_when: true + + - name: Fetch PNG QR to control node + ansible.builtin.fetch: + src: "/tmp/{{ hysteria2_current_user.name }}.png" + dest: "{{ hysteria2_output_dir }}/{{ hysteria2_output_name }}/" + flat: true + + - name: Set permissions on local PNG QR + ansible.builtin.file: + path: "{{ hysteria2_output_dir }}/{{ hysteria2_output_name }}/{{ hysteria2_current_user.name }}.png" + mode: "0600" + delegate_to: localhost + become: false + + - name: Remove temporary PNG QR on server + ansible.builtin.file: + path: "/tmp/{{ hysteria2_current_user.name }}.png" + state: absent + +- name: Register user export data for HTML page + ansible.builtin.set_fact: + hysteria2_export_users: >- + {{ + hysteria2_export_users | default([]) + [{ + 'name': hysteria2_current_user.name, + 'password': hysteria2_current_user.password, + 'url': _hysteria2_client_url, + 'has_png': hysteria2_generate_qr_png | bool + }] + }} + +- name: Remove temporary client config from server + ansible.builtin.file: + path: "/tmp/hysteria-client-{{ hysteria2_current_user.name }}.yaml" + state: absent diff --git a/roles/hysteria2/tasks/uninstall.yml b/roles/hysteria2/tasks/uninstall.yml new file mode 100644 index 0000000..10c0121 --- /dev/null +++ b/roles/hysteria2/tasks/uninstall.yml @@ -0,0 +1,46 @@ +--- +- name: Stop and disable hysteria-server + ansible.builtin.systemd: + name: "{{ hysteria2_service_name }}" + enabled: false + state: stopped + failed_when: false + +- name: Remove Hysteria2 via official script + ansible.builtin.shell: + cmd: bash <(curl -fsSL https://get.hy2.sh/) --remove + executable: /bin/bash + register: _hysteria2_remove + failed_when: false + +- name: Remove Hysteria2 configuration directory + ansible.builtin.file: + path: "{{ hysteria2_config_path | dirname }}" + 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 + +- name: Show uninstall result + ansible.builtin.debug: + msg: >- + Hysteria2 удалён с {{ inventory_hostname }}. + {% if not hysteria2_uninstall_remove_local_output | bool %} + Локальные URL/QR в {{ hysteria2_output_dir }}/{{ hysteria2_output_name }}/ сохранены. + {% endif %} + +- name: Remove local exported client files + ansible.builtin.file: + path: "{{ hysteria2_output_dir }}/{{ hysteria2_output_name }}" + state: absent + delegate_to: localhost + become: false + when: hysteria2_uninstall_remove_local_output | bool diff --git a/roles/hysteria2/tasks/update.yml b/roles/hysteria2/tasks/update.yml new file mode 100644 index 0000000..716166f --- /dev/null +++ b/roles/hysteria2/tasks/update.yml @@ -0,0 +1,11 @@ +--- +- name: Update Hysteria2 binary via official script + ansible.builtin.shell: + cmd: bash <(curl -fsSL https://get.hy2.sh/) + executable: /bin/bash + register: _hysteria2_update + notify: Restart hysteria-server + +- name: Show Hysteria2 update result + ansible.builtin.debug: + msg: "{{ _hysteria2_update.stdout_lines | default([]) }}" diff --git a/roles/hysteria2/tasks/users.yml b/roles/hysteria2/tasks/users.yml new file mode 100644 index 0000000..0ad533c --- /dev/null +++ b/roles/hysteria2/tasks/users.yml @@ -0,0 +1,102 @@ +--- +- name: Check for saved passwords 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 user passwords from local export + when: _hysteria2_saved_info.stat.exists + block: + - name: Read server-info.yml + 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 passwords into lookup dict + ansible.builtin.set_fact: + _hysteria2_saved_passwords: >- + {{ + dict( + (_hysteria2_saved_info_raw.content | b64decode | from_yaml).users + | map(attribute='name') + | zip( + (_hysteria2_saved_info_raw.content | b64decode | from_yaml).users + | map(attribute='password') + ) + ) + }} + tags: + - install + - update + - export + +- name: Resolve user list with optional fixed passwords + ansible.builtin.set_fact: + hysteria2_resolved_users: "{{ hysteria2_resolved_users | default([]) + [ _entry ] }}" + vars: + _username: "{{ item if item is string else item.name }}" + _password: >- + {{ + ( + item.password if item is mapping + ) | default('', true) + }} + _entry: + name: "{{ _username }}" + password: "{{ _password }}" + loop: "{{ hysteria2_users }}" + loop_control: + label: "{{ item if item is string else item.name }}" + tags: + - install + - 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 + loop: "{{ hysteria2_resolved_users }}" + loop_control: + label: "{{ item.name }}" + index_var: _hysteria2_user_idx + tags: + - install + - update + - export + +- 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 + tags: + - install + - update + - export diff --git a/roles/hysteria2/tasks/validate.yml b/roles/hysteria2/tasks/validate.yml new file mode 100644 index 0000000..59672fa --- /dev/null +++ b/roles/hysteria2/tasks/validate.yml @@ -0,0 +1,17 @@ +--- +- name: Validate required variables + ansible.builtin.assert: + that: + - hysteria2_domain | length > 0 + - hysteria2_acme_email | length > 0 + - hysteria2_users | length > 0 + fail_msg: | + Задайте для каждого хоста: + hysteria2_domain — домен с A-записью на IP сервера + hysteria2_users — список имён пользователей, например [my, friend] + И в group_vars/all.yml: + hysteria2_acme_email — email для Let's Encrypt + tags: + - install + - update + - export diff --git a/roles/hysteria2/templates/client.yaml.j2 b/roles/hysteria2/templates/client.yaml.j2 new file mode 100644 index 0000000..8b09cef --- /dev/null +++ b/roles/hysteria2/templates/client.yaml.j2 @@ -0,0 +1,3 @@ +server: {{ hysteria2_domain }}:{{ hysteria2_listen_port }} + +auth: {{ hysteria2_current_user.name }}:{{ hysteria2_current_user.password }} diff --git a/roles/hysteria2/templates/config.yaml.j2 b/roles/hysteria2/templates/config.yaml.j2 new file mode 100644 index 0000000..9a6f4e0 --- /dev/null +++ b/roles/hysteria2/templates/config.yaml.j2 @@ -0,0 +1,22 @@ +listen: 0.0.0.0:{{ hysteria2_listen_port }} + +acme: + type: http + domains: + - {{ hysteria2_domain }} + email: {{ hysteria2_acme_email }} + +auth: + type: userpass + userpass: +{% for user in hysteria2_resolved_users %} + {{ user.name }}: "{{ user.password }}" +{% endfor %} + +masquerade: + type: file + file: + dir: {{ hysteria2_masq_dir }} + listenHTTP: :80 + listenHTTPS: :{{ hysteria2_listen_port }} + forceHTTPS: true diff --git a/roles/hysteria2/templates/export/global-index.html.j2 b/roles/hysteria2/templates/export/global-index.html.j2 new file mode 100644 index 0000000..d90a831 --- /dev/null +++ b/roles/hysteria2/templates/export/global-index.html.j2 @@ -0,0 +1,389 @@ + + + + + + Hysteria2 — все серверы + + + +
+

Hysteria2

+

Общий каталог VPN-подключений

+
+
{{ hysteria2_global_servers | length }} серверов
+
{{ total_users }} пользователей
+
+
+ + + +
+{% for server in hysteria2_global_servers %} +
+
+
+

{{ server.name | e }}

+

{{ server.domain | e }}:{{ server.port }}

+
+ Страница сервера → +
+ +
+{% for user in server.users %} +
+

{{ user.name | e }}

+ +
+ +
+ + +
+
+ +
+ +
+ + +
+
+ +{% if user.has_png | default(false) %} +
+ QR {{ user.name | e }} +
+{% endif %} + + +
+{% endfor %} +
+
+{% endfor %} +
+ +
Сгенерировано Ansible · {{ generated_at | default('') }}
+
Скопировано!
+ + + + diff --git a/roles/hysteria2/templates/export/index.html.j2 b/roles/hysteria2/templates/export/index.html.j2 new file mode 100644 index 0000000..f1f205e --- /dev/null +++ b/roles/hysteria2/templates/export/index.html.j2 @@ -0,0 +1,314 @@ + + + + + + Hysteria2 — {{ hysteria2_output_name }} + + + +
+
+

Hysteria2

+

+ Сервер: {{ hysteria2_output_name }}
+ Домен: {{ hysteria2_domain }}:{{ hysteria2_listen_port }} +

+ {{ hysteria2_export_users | length }} {{ 'пользователь' if hysteria2_export_users | length == 1 else 'пользователей' }} +
+ +{% for user in hysteria2_export_users %} + +{% endfor %} + +
+ Сгенерировано Ansible · {{ generated_at | default('') }} +
+
+ +
Скопировано!
+ + + + diff --git a/roles/hysteria2/templates/masq/index.html.j2 b/roles/hysteria2/templates/masq/index.html.j2 new file mode 100644 index 0000000..75bcddc --- /dev/null +++ b/roles/hysteria2/templates/masq/index.html.j2 @@ -0,0 +1,27 @@ + + + +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.

+ +