mirror of
https://github.com/bivlked/amneziawg-installer.git
synced 2026-10-19 05:03:18 +00:00
v5.15.1 - maintenance release (audit fixes, IPv6 hardening, release gate)
Maintenance release after external code and documentation audits. No new features; hardens the v5.15.0 dual-stack IPv6 work, tightens several manage commands, fixes correctness and robustness issues, and adds a release preflight gate plus a docs-consistency check. Behavior change: split tunnel + IPv6 mirrors the IPv4 intent - a custom ALLOWED_IPS keeps that IPv4 split and adds only the tunnel ULA for IPv6 instead of forcing a full ::/0 route. A full tunnel still gets ::/0 (native IPv6) or the ULA. Fixed: vpn:// URI carries the server's real MTU/keepalive/DNS; stricter native-IPv6 detection (global non-ULA address and default route) with the host IPv6 stack re-enabled before detection; install fails fast on apt update errors; manage modify validates Endpoint/AllowedIPs; manage add is safe against name reuse and cleans partial artifacts; manage restore prunes stale clients/keys and recreates the expiry dir; manage help exit codes; manage --json keeps stdout pure JSON; log messages stop doubling percent signs. Docs/process: subnet and OS help accuracy, list --json documented, changelog compare-links and anchors restored, SECURITY/CONTRIBUTING refreshed, public RELEASE_PROCESS.md, release publish gated behind a full preflight job, new docs-consistency workflow, signing design aligned with the asset draft. Version bumped to 5.15.1 across all six scripts; SHA pins recomputed.
This commit is contained in:
parent
7eb5e9ebde
commit
1cbee098ca
24 changed files with 1134 additions and 181 deletions
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -51,7 +51,7 @@ body:
|
|||
attributes:
|
||||
label: Script Version
|
||||
description: Version of the script (shown in the log header or in the script file header comment).
|
||||
placeholder: "e.g., 5.10.0"
|
||||
placeholder: "e.g., 5.15.0"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
|
|
|||
41
.github/workflows/docs-check.yml
vendored
Normal file
41
.github/workflows/docs-check.yml
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
name: Docs consistency
|
||||
|
||||
# Lightweight, fast checks for the classes of drift that slip past the
|
||||
# shellcheck/test workflows (their paths cover only shell scripts): broken
|
||||
# internal markdown anchors, version triple drift, stale OS matrix, changelog
|
||||
# heading/reference mismatch. No build, no network, no bats.
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- '**/*.md'
|
||||
- 'docs/**'
|
||||
- '.github/**'
|
||||
- 'install_amneziawg*.sh'
|
||||
- 'manage_amneziawg*.sh'
|
||||
- 'awg_common*.sh'
|
||||
pull_request:
|
||||
paths:
|
||||
- '**/*.md'
|
||||
- 'docs/**'
|
||||
- '.github/**'
|
||||
- 'install_amneziawg*.sh'
|
||||
- 'manage_amneziawg*.sh'
|
||||
- 'awg_common*.sh'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
docs-consistency:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Run docs consistency checks
|
||||
run: bash scripts/check-docs-consistency.sh
|
||||
31
.github/workflows/release.yml
vendored
31
.github/workflows/release.yml
vendored
|
|
@ -12,7 +12,36 @@ concurrency:
|
|||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Gate: a tag must clear the full pre-tag checklist before anything is
|
||||
# published. A fresh install verifies the helper-script SHA pins over the
|
||||
# network, so a tag with stale pins would yield a broken install straight
|
||||
# from the release. This job blocks that.
|
||||
preflight:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# Full history so the merge-base for the diff checks is present.
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Fetch main for diff base
|
||||
# The tag checkout does not bring branch refs. preflight-check.sh diffs
|
||||
# against origin/main, so make that ref resolvable. A hard failure here
|
||||
# is intentional: without a base ref the punctuation/marker diff checks
|
||||
# cannot run.
|
||||
run: git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
|
||||
- name: Install shellcheck and bats
|
||||
run: sudo apt-get update && sudo apt-get install -y shellcheck bats
|
||||
|
||||
- name: Run preflight checks
|
||||
env:
|
||||
BASE_REF: origin/main
|
||||
run: bash scripts/preflight-check.sh
|
||||
|
||||
release:
|
||||
needs: preflight
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
|
|
@ -25,7 +54,7 @@ jobs:
|
|||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
if gh release view "$TAG" --repo "${{ github.repository }}" > /dev/null 2>&1; then
|
||||
echo "Release $TAG already exists — skipping"
|
||||
echo "Release $TAG already exists - skipping"
|
||||
echo "exists=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "exists=false" >> "$GITHUB_OUTPUT"
|
||||
|
|
|
|||
|
|
@ -14,6 +14,40 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||
|
||||
---
|
||||
|
||||
## [5.15.1] - 2026-06-02
|
||||
|
||||
**v5.15.1** - a maintenance release after a round of external code and documentation audits. No new features: it hardens the v5.15.0 dual-stack IPv6 work, tightens several management commands, and fixes a number of correctness and robustness issues. A default install is unchanged. Support matrix unchanged: Ubuntu 24.04 / 25.10 / 26.04, Debian 12 / 13, x86_64 + ARM.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Split tunnel + IPv6 now mirrors the IPv4 intent (behavior change).** When a client uses a custom `ALLOWED_IPS` (split tunnel), the dual-stack config keeps that IPv4 split list and adds only the tunnel ULA for IPv6 - it no longer forces a full `::/0` IPv6 route. A full tunnel (`ALLOWED_IPS=0.0.0.0/0`) still gets `::/0` with native IPv6, or the tunnel ULA without it. This affects only clients created with both a split tunnel and `--allow-ipv6-tunnel`. If you relied on the old behavior of IPv6 going full-tunnel while IPv4 stayed split, recreate the client to pick up the new routing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`vpn://` URI now carries the server's real MTU, keepalive, and DNS** instead of fixed values, so a config imported from the URI matches the `.conf` even after the server settings or a `manage modify` changed them.
|
||||
- **Stricter native-IPv6 detection.** The server is treated as natively IPv6-capable only when it has a global (non-ULA) IPv6 address and a default IPv6 route, so a client gets a full `::/0` route only when it will actually work.
|
||||
- **The IPv6 host stack is re-enabled before detection** when the tunnel needs it, so a server that had IPv6 disabled via sysctl still gets a working dual-stack tunnel.
|
||||
- **`install` fails fast if `apt update` errors out**, instead of continuing a multi-step install against a stale package cache.
|
||||
- **`manage modify` validates the Endpoint and AllowedIPs** it is given (host:port, CIDR list) before writing them.
|
||||
- **`manage add` is safe against name reuse:** it refuses a name that already exists and cleans up partial key or config artifacts if creation fails partway.
|
||||
- **`manage restore` prunes stale clients and keys** that are absent from the backup, so a restore yields exactly the backed-up state.
|
||||
- **`manage help` exits 0** for an explicit help request and 1 only for a real usage error.
|
||||
- **`manage --json` keeps stdout pure JSON** by routing info and debug logging to stderr.
|
||||
- **Log messages no longer double percent signs** ("95%" stays "95%").
|
||||
- **`manage restore` recreates the expiry directory** before restoring expiry timestamps.
|
||||
|
||||
### Documentation
|
||||
|
||||
- `--subnet` help states the `/24`-only requirement; `install --help` lists Ubuntu 26.04.
|
||||
- `manage --help` and the README document `list --json` (including the `client_ipv6` field).
|
||||
- Restored the changelog compare-links for 5.11.0-5.15.0, fixed several broken in-page anchors, refreshed `SECURITY.md` and `CONTRIBUTING.md`, and added `docs/RELEASE_PROCESS.md`.
|
||||
|
||||
### Internal
|
||||
|
||||
- Tagging a release now runs a full preflight gate (syntax, shellcheck, tests, punctuation, version and SHA-pin consistency, documentation checks) before the GitHub Release is published, plus a lightweight documentation-consistency workflow. The signing design doc was aligned with the asset-upload draft.
|
||||
|
||||
---
|
||||
|
||||
## [5.15.0] - 2026-06-01
|
||||
|
||||
**v5.15.0** - optional dual-stack IPv6 inside the tunnel. Requested by users in [#24](https://github.com/bivlked/amneziawg-installer/issues/24): the new `--allow-ipv6-tunnel` flag hands clients an IPv6 address from the private ULA subnet `fddd:2c4:2c4:2c4::/64` alongside the usual IPv4. Off by default - without the flag the install is identical to v5.14.x. Support matrix unchanged: Ubuntu 24.04 / 25.10 / 26.04, Debian 12 / 13, x86_64 + ARM.
|
||||
|
|
@ -238,7 +272,7 @@ sudo bash ./install_amneziawg_en.sh
|
|||
|
||||
### Upgrading an existing server
|
||||
|
||||
Run the latest `install_amneziawg.sh` with `--force` (if AmneziaWG is already running) — Step 5 fetches the fresh `manage_amneziawg.sh` and `awg_common.sh` with SHA256 verification. Full commands — [ADVANCED.en.md → Updating the scripts](ADVANCED.en.md#-updating-scripts).
|
||||
Run the latest `install_amneziawg.sh` with `--force` (if AmneziaWG is already running) — Step 5 fetches the fresh `manage_amneziawg.sh` and `awg_common.sh` with SHA256 verification. Full commands — [ADVANCED.en.md → Updating the scripts](ADVANCED.en.md#update-scripts-adv).
|
||||
|
||||
### Tests
|
||||
|
||||
|
|
@ -281,11 +315,11 @@ chmod +x install_amneziawg_en.sh
|
|||
sudo bash ./install_amneziawg_en.sh
|
||||
```
|
||||
|
||||
3 commands → ~20 minutes → a ready-to-use VPN server with traffic obfuscation. Details — [README → Install](README.en.md#install).
|
||||
3 commands → ~20 minutes → a ready-to-use VPN server with traffic obfuscation. Details — [README → Install](README.en.md#installation).
|
||||
|
||||
### Upgrading an existing server
|
||||
|
||||
Run a fresh `install_amneziawg_en.sh` — at step 5, `manage_amneziawg.sh` and `awg_common.sh` are updated automatically (with SHA256 verification). Full commands — [ADVANCED.en.md → How to update the scripts](ADVANCED.en.md#-how-to-update-the-scripts).
|
||||
Run a fresh `install_amneziawg_en.sh` — at step 5, `manage_amneziawg.sh` and `awg_common.sh` are updated automatically (with SHA256 verification). Full commands — [ADVANCED.en.md → How to update the scripts](ADVANCED.en.md#update-scripts-adv).
|
||||
|
||||
### Tests
|
||||
|
||||
|
|
@ -370,11 +404,11 @@ chmod +x install_amneziawg_en.sh
|
|||
sudo bash ./install_amneziawg_en.sh
|
||||
```
|
||||
|
||||
3 commands → ~20 minutes → a working VPN server with traffic obfuscation. Details — [README → Install](README.en.md#install).
|
||||
3 commands → ~20 minutes → a working VPN server with traffic obfuscation. Details — [README → Install](README.en.md#installation).
|
||||
|
||||
### Upgrading an existing server
|
||||
|
||||
Run the fresh `install_amneziawg_en.sh` — at step 5 `manage_amneziawg.sh` and `awg_common.sh` are refreshed automatically (with SHA256 verification). Full commands — [ADVANCED.en.md → Updating the scripts](ADVANCED.en.md#-updating-the-scripts).
|
||||
Run the fresh `install_amneziawg_en.sh` — at step 5 `manage_amneziawg.sh` and `awg_common.sh` are refreshed automatically (with SHA256 verification). Full commands — [ADVANCED.en.md → Updating the scripts](ADVANCED.en.md#update-scripts-adv).
|
||||
|
||||
### Tests
|
||||
|
||||
|
|
@ -406,11 +440,11 @@ chmod +x install_amneziawg_en.sh
|
|||
sudo bash ./install_amneziawg_en.sh
|
||||
```
|
||||
|
||||
3 commands → ~20 minutes → a working VPN server with traffic obfuscation. Details — [README → Install](README.en.md#install).
|
||||
3 commands → ~20 minutes → a working VPN server with traffic obfuscation. Details — [README → Install](README.en.md#installation).
|
||||
|
||||
### Upgrading an existing server
|
||||
|
||||
Run the fresh `install_amneziawg_en.sh` — at step 5 `manage_amneziawg.sh` and `awg_common.sh` are refreshed automatically (with SHA256 verification). Full commands — [ADVANCED.en.md → Updating the scripts](ADVANCED.en.md#-updating-the-scripts).
|
||||
Run the fresh `install_amneziawg_en.sh` — at step 5 `manage_amneziawg.sh` and `awg_common.sh` are refreshed automatically (with SHA256 verification). Full commands — [ADVANCED.en.md → Updating the scripts](ADVANCED.en.md#update-scripts-adv).
|
||||
|
||||
### Tests
|
||||
|
||||
|
|
@ -450,7 +484,7 @@ sudo bash ./install_amneziawg_en.sh
|
|||
|
||||
### Upgrading an existing server
|
||||
|
||||
Re-run the latest `install_amneziawg_en.sh` — step 5 refreshes `manage_amneziawg.sh` and `awg_common.sh` automatically (with SHA256 verification). Full commands — [ADVANCED.en.md → How to Update Scripts](ADVANCED.en.md#-how-to-update-scripts).
|
||||
Re-run the latest `install_amneziawg_en.sh` — step 5 refreshes `manage_amneziawg.sh` and `awg_common.sh` automatically (with SHA256 verification). Full commands — [ADVANCED.en.md → How to Update Scripts](ADVANCED.en.md#update-scripts-adv).
|
||||
|
||||
### Tests
|
||||
|
||||
|
|
@ -1194,7 +1228,24 @@ Major security and reliability update after several consecutive code audits. The
|
|||
- Diagnostic report (`--diagnostic`).
|
||||
- Full uninstall (`--uninstall`).
|
||||
|
||||
[Unreleased]: https://github.com/bivlked/amneziawg-installer/compare/v5.10.2...HEAD
|
||||
[Unreleased]: https://github.com/bivlked/amneziawg-installer/compare/v5.15.1...HEAD
|
||||
[5.15.1]: https://github.com/bivlked/amneziawg-installer/compare/v5.15.0...v5.15.1
|
||||
[5.15.0]: https://github.com/bivlked/amneziawg-installer/compare/v5.14.5...v5.15.0
|
||||
[5.14.5]: https://github.com/bivlked/amneziawg-installer/compare/v5.14.4...v5.14.5
|
||||
[5.14.4]: https://github.com/bivlked/amneziawg-installer/compare/v5.14.3...v5.14.4
|
||||
[5.14.3]: https://github.com/bivlked/amneziawg-installer/compare/v5.14.2...v5.14.3
|
||||
[5.14.2]: https://github.com/bivlked/amneziawg-installer/compare/v5.14.1...v5.14.2
|
||||
[5.14.1]: https://github.com/bivlked/amneziawg-installer/compare/v5.14.0...v5.14.1
|
||||
[5.14.0]: https://github.com/bivlked/amneziawg-installer/compare/v5.13.0...v5.14.0
|
||||
[5.13.0]: https://github.com/bivlked/amneziawg-installer/compare/v5.12.1...v5.13.0
|
||||
[5.12.1]: https://github.com/bivlked/amneziawg-installer/compare/v5.12.0...v5.12.1
|
||||
[5.12.0]: https://github.com/bivlked/amneziawg-installer/compare/v5.11.5...v5.12.0
|
||||
[5.11.5]: https://github.com/bivlked/amneziawg-installer/compare/v5.11.4...v5.11.5
|
||||
[5.11.4]: https://github.com/bivlked/amneziawg-installer/compare/v5.11.3...v5.11.4
|
||||
[5.11.3]: https://github.com/bivlked/amneziawg-installer/compare/v5.11.2...v5.11.3
|
||||
[5.11.2]: https://github.com/bivlked/amneziawg-installer/compare/v5.11.1...v5.11.2
|
||||
[5.11.1]: https://github.com/bivlked/amneziawg-installer/compare/v5.11.0...v5.11.1
|
||||
[5.11.0]: https://github.com/bivlked/amneziawg-installer/compare/v5.10.2...v5.11.0
|
||||
[5.10.2]: https://github.com/bivlked/amneziawg-installer/compare/v5.10.1...v5.10.2
|
||||
[5.10.1]: https://github.com/bivlked/amneziawg-installer/compare/v5.10.0...v5.10.1
|
||||
[5.10.0]: https://github.com/bivlked/amneziawg-installer/compare/v5.9.0...v5.10.0
|
||||
|
|
|
|||
69
CHANGELOG.md
69
CHANGELOG.md
|
|
@ -14,6 +14,40 @@
|
|||
|
||||
---
|
||||
|
||||
## [5.15.1] - 2026-06-02
|
||||
|
||||
**v5.15.1** - поддерживающий релиз после серии внешних аудитов кода и документации. Новых функций нет: укрепляет dual-stack IPv6 из v5.15.0, ужесточает несколько команд управления и устраняет ряд проблем корректности и надёжности. Установка по умолчанию не меняется. Матрица поддержки прежняя: Ubuntu 24.04 / 25.10 / 26.04, Debian 12 / 13, x86_64 + ARM.
|
||||
|
||||
### Изменено
|
||||
|
||||
- **Сплит-туннель + IPv6 теперь следует логике IPv4 (изменение поведения).** Если клиент использует свой `ALLOWED_IPS` (сплит-туннель), dual-stack конфиг сохраняет этот IPv4-список и добавляет для IPv6 только ULA туннеля - больше не навязывает полный маршрут `::/0`. Полный туннель (`ALLOWED_IPS=0.0.0.0/0`) по-прежнему получает `::/0` при нативном IPv6 либо ULA туннеля без него. Затрагивает только клиентов, созданных одновременно со сплит-туннелем и `--allow-ipv6-tunnel`. Если вы полагались на прежнее поведение, когда IPv6 уходил в полный туннель при сплит-IPv4, пересоздайте клиента, чтобы применить новую маршрутизацию.
|
||||
|
||||
### Исправлено
|
||||
|
||||
- **`vpn://` URI теперь несёт реальные MTU, keepalive и DNS сервера** вместо фиксированных значений, так что импортированный из URI конфиг совпадает с `.conf` даже после смены настроек сервера или `manage modify`.
|
||||
- **Более строгое определение нативного IPv6.** Сервер считается нативно IPv6-способным только при наличии глобального (не-ULA) IPv6-адреса и IPv6-маршрута по умолчанию, поэтому клиент получает полный маршрут `::/0` лишь тогда, когда он действительно будет работать.
|
||||
- **Хостовый IPv6-стек включается перед детектом**, если он нужен туннелю, так что сервер с ранее отключённым через sysctl IPv6 всё равно получает рабочий dual-stack туннель.
|
||||
- **`install` падает сразу при ошибке `apt update`** вместо продолжения многошаговой установки на устаревшем кэше пакетов.
|
||||
- **`manage modify` проверяет переданные Endpoint и AllowedIPs** (host:port, список CIDR) перед записью.
|
||||
- **`manage add` защищён от повторного имени:** отклоняет уже существующее имя и убирает частичные артефакты ключей или конфига, если создание прервалось.
|
||||
- **`manage restore` убирает устаревших клиентов и ключи**, которых нет в резервной копии, так что восстановление даёт ровно состояние бэкапа.
|
||||
- **`manage help` завершается с кодом 0** для явного запроса справки и 1 - только при реальной ошибке использования.
|
||||
- **`manage --json` держит stdout чистым JSON**, отправляя info- и debug-логи в stderr.
|
||||
- **Сообщения в логе больше не удваивают знак процента** ("95%" остаётся "95%").
|
||||
- **`manage restore` пересоздаёт каталог expiry** перед восстановлением меток срока действия.
|
||||
|
||||
### Документация
|
||||
|
||||
- Справка `--subnet` указывает требование только `/24`; `install --help` перечисляет Ubuntu 26.04.
|
||||
- `manage --help` и README документируют `list --json` (включая поле `client_ipv6`).
|
||||
- Восстановлены compare-ссылки changelog для 5.11.0-5.15.0, исправлено несколько битых внутренних якорей, обновлены `SECURITY.md` и `CONTRIBUTING.md`, добавлен `docs/RELEASE_PROCESS.md`.
|
||||
|
||||
### Внутреннее
|
||||
|
||||
- Простановка тега релиза теперь запускает полный preflight-гейт (синтаксис, shellcheck, тесты, пунктуация, согласованность версий и SHA-пинов, проверки документации) перед публикацией GitHub Release, плюс лёгкий workflow проверки согласованности документации. Документ дизайна подписи приведён в соответствие с черновиком загрузки ассетов.
|
||||
|
||||
---
|
||||
|
||||
## [5.15.0] - 2026-06-01
|
||||
|
||||
**v5.15.0** - опциональный dual-stack IPv6 внутри туннеля. По просьбе пользователей в [#24](https://github.com/bivlked/amneziawg-installer/issues/24): новый флаг `--allow-ipv6-tunnel` выдаёт клиентам IPv6-адрес из приватной подсети ULA `fddd:2c4:2c4:2c4::/64` дополнительно к привычному IPv4. По умолчанию выключено - без флага установка идентична v5.14.x. Поддержка операционных систем без изменений: Ubuntu 24.04 / 25.10 / 26.04, Debian 12 / 13, x86_64 + ARM.
|
||||
|
|
@ -234,11 +268,11 @@ chmod +x install_amneziawg.sh
|
|||
sudo bash ./install_amneziawg.sh
|
||||
```
|
||||
|
||||
3 команды → ~20 минут → готовый VPN-сервер с обфускацией трафика. Подробнее — [README → Установка](README.md#установка).
|
||||
3 команды → ~20 минут → готовый VPN-сервер с обфускацией трафика. Подробнее — [README → Установка](README.md#ustanovka).
|
||||
|
||||
### Обновление существующего сервера
|
||||
|
||||
Запустите `install_amneziawg.sh` свежей версии с флагом `--force` (если AmneziaWG уже работает) — на 5-м шаге `manage_amneziawg.sh` и `awg_common.sh` обновятся автоматически (с проверкой SHA256). Полные команды — [ADVANCED.md → Как обновить скрипты](ADVANCED.md#-как-обновить-скрипты).
|
||||
Запустите `install_amneziawg.sh` свежей версии с флагом `--force` (если AmneziaWG уже работает) — на 5-м шаге `manage_amneziawg.sh` и `awg_common.sh` обновятся автоматически (с проверкой SHA256). Полные команды — [ADVANCED.md → Как обновить скрипты](ADVANCED.md#update-scripts-adv).
|
||||
|
||||
### Тесты
|
||||
|
||||
|
|
@ -281,11 +315,11 @@ chmod +x install_amneziawg.sh
|
|||
sudo bash ./install_amneziawg.sh
|
||||
```
|
||||
|
||||
3 команды → ~20 минут → готовый VPN-сервер с обфускацией трафика. Подробнее — [README → Установка](README.md#установка).
|
||||
3 команды → ~20 минут → готовый VPN-сервер с обфускацией трафика. Подробнее — [README → Установка](README.md#ustanovka).
|
||||
|
||||
### Обновление существующего сервера
|
||||
|
||||
Запустите `install_amneziawg.sh` свежей версии — на 5-м шаге `manage_amneziawg.sh` и `awg_common.sh` обновятся автоматически (с проверкой SHA256). Полные команды — [ADVANCED.md → Как обновить скрипты](ADVANCED.md#-как-обновить-скрипты).
|
||||
Запустите `install_amneziawg.sh` свежей версии — на 5-м шаге `manage_amneziawg.sh` и `awg_common.sh` обновятся автоматически (с проверкой SHA256). Полные команды — [ADVANCED.md → Как обновить скрипты](ADVANCED.md#update-scripts-adv).
|
||||
|
||||
### Тесты
|
||||
|
||||
|
|
@ -370,11 +404,11 @@ chmod +x install_amneziawg.sh
|
|||
sudo bash ./install_amneziawg.sh
|
||||
```
|
||||
|
||||
3 команды → ~20 минут → готовый VPN-сервер с обфускацией трафика. Подробнее — [README → Установка](README.md#установка).
|
||||
3 команды → ~20 минут → готовый VPN-сервер с обфускацией трафика. Подробнее — [README → Установка](README.md#ustanovka).
|
||||
|
||||
### Обновление существующего сервера
|
||||
|
||||
Запустите `install_amneziawg.sh` свежей версии — на 5-м шаге `manage_amneziawg.sh` и `awg_common.sh` обновятся автоматически (с проверкой SHA256). Полные команды — [ADVANCED.md → Как обновить скрипты](ADVANCED.md#-как-обновить-скрипты).
|
||||
Запустите `install_amneziawg.sh` свежей версии — на 5-м шаге `manage_amneziawg.sh` и `awg_common.sh` обновятся автоматически (с проверкой SHA256). Полные команды — [ADVANCED.md → Как обновить скрипты](ADVANCED.md#update-scripts-adv).
|
||||
|
||||
### Тесты
|
||||
|
||||
|
|
@ -410,7 +444,7 @@ sudo bash ./install_amneziawg.sh
|
|||
|
||||
### Обновление существующего сервера
|
||||
|
||||
Запустите `install_amneziawg.sh` свежей версии — на 5-м шаге `manage_amneziawg.sh` и `awg_common.sh` обновятся автоматически (с проверкой SHA256). Полные команды — [ADVANCED.md → Как обновить скрипты](ADVANCED.md#-как-обновить-скрипты).
|
||||
Запустите `install_amneziawg.sh` свежей версии — на 5-м шаге `manage_amneziawg.sh` и `awg_common.sh` обновятся автоматически (с проверкой SHA256). Полные команды — [ADVANCED.md → Как обновить скрипты](ADVANCED.md#update-scripts-adv).
|
||||
|
||||
### Тесты
|
||||
|
||||
|
|
@ -450,7 +484,7 @@ sudo bash ./install_amneziawg.sh
|
|||
|
||||
### Обновление существующего сервера
|
||||
|
||||
Запустите `install_amneziawg.sh` свежей версии — на 5-м шаге `manage_amneziawg.sh` и `awg_common.sh` обновятся автоматически (с проверкой SHA256). Полные команды — [ADVANCED.md → Как обновить скрипты](ADVANCED.md#-как-обновить-скрипты).
|
||||
Запустите `install_amneziawg.sh` свежей версии — на 5-м шаге `manage_amneziawg.sh` и `awg_common.sh` обновятся автоматически (с проверкой SHA256). Полные команды — [ADVANCED.md → Как обновить скрипты](ADVANCED.md#update-scripts-adv).
|
||||
|
||||
### Тесты
|
||||
|
||||
|
|
@ -1194,7 +1228,24 @@ Hardening-фиксы надёжности и безопасности по ре
|
|||
- Диагностический отчет (`--diagnostic`).
|
||||
- Полная деинсталляция (`--uninstall`).
|
||||
|
||||
[Unreleased]: https://github.com/bivlked/amneziawg-installer/compare/v5.10.2...HEAD
|
||||
[Unreleased]: https://github.com/bivlked/amneziawg-installer/compare/v5.15.1...HEAD
|
||||
[5.15.1]: https://github.com/bivlked/amneziawg-installer/compare/v5.15.0...v5.15.1
|
||||
[5.15.0]: https://github.com/bivlked/amneziawg-installer/compare/v5.14.5...v5.15.0
|
||||
[5.14.5]: https://github.com/bivlked/amneziawg-installer/compare/v5.14.4...v5.14.5
|
||||
[5.14.4]: https://github.com/bivlked/amneziawg-installer/compare/v5.14.3...v5.14.4
|
||||
[5.14.3]: https://github.com/bivlked/amneziawg-installer/compare/v5.14.2...v5.14.3
|
||||
[5.14.2]: https://github.com/bivlked/amneziawg-installer/compare/v5.14.1...v5.14.2
|
||||
[5.14.1]: https://github.com/bivlked/amneziawg-installer/compare/v5.14.0...v5.14.1
|
||||
[5.14.0]: https://github.com/bivlked/amneziawg-installer/compare/v5.13.0...v5.14.0
|
||||
[5.13.0]: https://github.com/bivlked/amneziawg-installer/compare/v5.12.1...v5.13.0
|
||||
[5.12.1]: https://github.com/bivlked/amneziawg-installer/compare/v5.12.0...v5.12.1
|
||||
[5.12.0]: https://github.com/bivlked/amneziawg-installer/compare/v5.11.5...v5.12.0
|
||||
[5.11.5]: https://github.com/bivlked/amneziawg-installer/compare/v5.11.4...v5.11.5
|
||||
[5.11.4]: https://github.com/bivlked/amneziawg-installer/compare/v5.11.3...v5.11.4
|
||||
[5.11.3]: https://github.com/bivlked/amneziawg-installer/compare/v5.11.2...v5.11.3
|
||||
[5.11.2]: https://github.com/bivlked/amneziawg-installer/compare/v5.11.1...v5.11.2
|
||||
[5.11.1]: https://github.com/bivlked/amneziawg-installer/compare/v5.11.0...v5.11.1
|
||||
[5.11.0]: https://github.com/bivlked/amneziawg-installer/compare/v5.10.2...v5.11.0
|
||||
[5.10.2]: https://github.com/bivlked/amneziawg-installer/compare/v5.10.1...v5.10.2
|
||||
[5.10.1]: https://github.com/bivlked/amneziawg-installer/compare/v5.10.0...v5.10.1
|
||||
[5.10.0]: https://github.com/bivlked/amneziawg-installer/compare/v5.9.0...v5.10.0
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ Before submitting a PR, ensure:
|
|||
done
|
||||
```
|
||||
|
||||
3. **Unit tests (bats-core)** pass. Current expected baseline on `v5.14.1`: **510 tests** (v5.14.1 added 18 tests for MTU resolution priority + validation).
|
||||
3. **Unit tests (bats-core)** pass. The suite grows every release, so there is no fixed count to match - run `bats tests/` and make sure the full suite is green before opening a PR.
|
||||
```bash
|
||||
bats tests/
|
||||
```
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
<img src="https://img.shields.io/badge/Architecture-x86__64_|_ARM64_|_ARMv7-green" alt="x86_64 | ARM64 | ARMv7">
|
||||
<a href="https://github.com/bivlked/amneziawg-installer/blob/main/LICENSE"><img src="https://img.shields.io/github/license/bivlked/amneziawg-installer" alt="License"></a>
|
||||
<img src="https://img.shields.io/badge/Status-Stable-success" alt="Status">
|
||||
<a href="https://github.com/bivlked/amneziawg-installer/releases"><img src="https://img.shields.io/badge/Installer_Version-5.15.0-blue" alt="Version"></a>
|
||||
<a href="https://github.com/bivlked/amneziawg-installer/releases"><img src="https://img.shields.io/badge/Installer_Version-5.15.1-blue" alt="Version"></a>
|
||||
<img src="https://img.shields.io/badge/AmneziaWG-2.0-blueviolet" alt="AWG 2.0">
|
||||
<a href="https://github.com/bivlked/amneziawg-installer/actions/workflows/shellcheck.yml"><img src="https://github.com/bivlked/amneziawg-installer/actions/workflows/shellcheck.yml/badge.svg" alt="ShellCheck"></a>
|
||||
<a href="https://github.com/bivlked/amneziawg-installer/actions/workflows/test.yml"><img src="https://github.com/bivlked/amneziawg-installer/actions/workflows/test.yml/badge.svg" alt="Tests"></a>
|
||||
|
|
@ -363,7 +363,7 @@ sudo bash /root/awg/manage_amneziawg.sh <command> [arguments]
|
|||
| :-------- | :--------------------- | :----------------------------- | :------: |
|
||||
| `add` | `<name> [name2 ...] [--expires=DUR]` | Add client(s) (opt. with expiry) | No (auto) |
|
||||
| `remove` | `<name> [name2 ...]` | Remove client(s) | No (auto) |
|
||||
| `list` | `[-v]` | List clients (`-v` for details)| No |
|
||||
| `list` | `[-v] [--json]` | List clients (`-v` for details, `--json` machine-readable with `client_ipv6`)| No |
|
||||
| `regen` | `[client_name]` | Regenerate files (all/one) | No |
|
||||
| `modify` | `<name> <param> <val>` | Modify a client parameter | No |
|
||||
| `backup` | | Create a backup | No |
|
||||
|
|
@ -391,6 +391,7 @@ sudo bash /root/awg/manage_amneziawg.sh add my_phone # Add
|
|||
sudo bash /root/awg/manage_amneziawg.sh add my_iphone --psk # +PresharedKey (Shadowrocket iOS/macOS)
|
||||
sudo bash /root/awg/manage_amneziawg.sh remove my_phone # Remove
|
||||
sudo bash /root/awg/manage_amneziawg.sh list # List
|
||||
sudo bash /root/awg/manage_amneziawg.sh list --json # List as JSON (for scripts)
|
||||
sudo bash /root/awg/manage_amneziawg.sh regen # Regenerate
|
||||
|
||||
# Temporary client (7 days)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
<img src="https://img.shields.io/badge/Architecture-x86__64_|_ARM64_|_ARMv7-green" alt="x86_64 | ARM64 | ARMv7">
|
||||
<a href="https://github.com/bivlked/amneziawg-installer/blob/main/LICENSE"><img src="https://img.shields.io/github/license/bivlked/amneziawg-installer" alt="License"></a>
|
||||
<img src="https://img.shields.io/badge/Status-Stable-success" alt="Status">
|
||||
<a href="https://github.com/bivlked/amneziawg-installer/releases"><img src="https://img.shields.io/badge/Installer_Version-5.15.0-blue" alt="Version"></a>
|
||||
<a href="https://github.com/bivlked/amneziawg-installer/releases"><img src="https://img.shields.io/badge/Installer_Version-5.15.1-blue" alt="Version"></a>
|
||||
<img src="https://img.shields.io/badge/AmneziaWG-2.0-blueviolet" alt="AWG 2.0">
|
||||
<a href="https://github.com/bivlked/amneziawg-installer/actions/workflows/shellcheck.yml"><img src="https://github.com/bivlked/amneziawg-installer/actions/workflows/shellcheck.yml/badge.svg" alt="ShellCheck"></a>
|
||||
<a href="https://github.com/bivlked/amneziawg-installer/actions/workflows/test.yml"><img src="https://github.com/bivlked/amneziawg-installer/actions/workflows/test.yml/badge.svg" alt="Tests"></a>
|
||||
|
|
@ -363,7 +363,7 @@ sudo bash /root/awg/manage_amneziawg.sh <команда> [аргументы]
|
|||
| :-------- | :--------------------- | :--------------------------- | :-----------: |
|
||||
| `add` | `<имя> [имя2 ...] [--expires=ВРЕМЯ]` | Добавить клиента(ов) (опц. с истечением) | Нет (авто) |
|
||||
| `remove` | `<имя> [имя2 ...]` | Удалить клиента(ов) | Нет (авто) |
|
||||
| `list` | `[-v]` | Список клиентов (`-v` детали) | Нет |
|
||||
| `list` | `[-v] [--json]` | Список клиентов (`-v` детали, `--json` машиночитаемый с `client_ipv6`) | Нет |
|
||||
| `regen` | `[имя_клиента]` | Переген. файлы (всех/одного) | Нет |
|
||||
| `modify` | `<имя> <пар> <зн>` | Изменить параметр клиента | Нет |
|
||||
| `backup` | | Создать резервную копию | Нет |
|
||||
|
|
@ -391,6 +391,7 @@ sudo bash /root/awg/manage_amneziawg.sh add my_phone # Добавить
|
|||
sudo bash /root/awg/manage_amneziawg.sh add my_iphone --psk # +PresharedKey (Shadowrocket iOS/macOS)
|
||||
sudo bash /root/awg/manage_amneziawg.sh remove my_phone # Удалить
|
||||
sudo bash /root/awg/manage_amneziawg.sh list # Список
|
||||
sudo bash /root/awg/manage_amneziawg.sh list --json # Список в JSON (для скриптов)
|
||||
sudo bash /root/awg/manage_amneziawg.sh regen # Перегенерация
|
||||
|
||||
# Временный клиент (7 дней)
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@
|
|||
|
||||
| Version | Supported |
|
||||
|---------|--------------------|
|
||||
| 5.13.x | :white_check_mark: |
|
||||
| 5.12.x | Security fixes only |
|
||||
| 5.11.x | Security fixes only |
|
||||
| < 5.11 | :x: |
|
||||
| 5.15.x | :white_check_mark: |
|
||||
| 5.14.x | Security fixes only |
|
||||
| < 5.14 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
|
@ -49,6 +48,6 @@ Coordinated disclosure. 30 days for a fix before public disclosure. If a vulnera
|
|||
|
||||
## Release integrity (planned)
|
||||
|
||||
Detached `minisign` signatures for installer and management scripts are planned for v5.14+. Once the maintainer public key is published as `KEYS.txt` in the repository root, users will be able to verify a downloaded script against the maintainer's offline-held private key, independent of GitHub's TLS chain. Design and threat model: [docs/SIGNING_DESIGN.md](docs/SIGNING_DESIGN.md).
|
||||
Detached `minisign` signatures for installer and management scripts are planned for a future release and are not active yet. Once the maintainer public key is published as `KEYS.txt` in the repository root, users will be able to verify a downloaded script against the maintainer's offline-held private key, independent of GitHub's TLS chain. Design and threat model: [docs/SIGNING_DESIGN.md](docs/SIGNING_DESIGN.md).
|
||||
|
||||
Until activation, fetch the installer over HTTPS from `raw.githubusercontent.com` (default in README) - GitHub's TLS still protects the path on the wire, the missing piece is independent maintainer-level signing.
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
# ==============================================================================
|
||||
# Общая библиотека функций для AmneziaWG 2.0
|
||||
# Автор: @bivlked
|
||||
# Версия: 5.15.0
|
||||
# Дата: 2026-06-01
|
||||
# Версия: 5.15.1
|
||||
# Дата: 2026-06-02
|
||||
# Репозиторий: https://github.com/bivlked/amneziawg-installer
|
||||
# ==============================================================================
|
||||
#
|
||||
|
|
@ -924,8 +924,10 @@ _extract_mtu_from_server_conf() {
|
|||
# client_ipv6 (необязательный, 7-й аргумент): IPv6-адрес клиента без префикса
|
||||
# длины (например fddd:2c4:2c4:2c4::5). Если непустой и ALLOW_IPV6_TUNNEL=1:
|
||||
# - Address = <ipv4>/32, <ipv6>/128
|
||||
# - AllowedIPs: SERVER_HAS_NATIVE_IPV6=1 -> 0.0.0.0/0, ::/0
|
||||
# SERVER_HAS_NATIVE_IPV6=0 -> 0.0.0.0/0, <IPV6_SUBNET>
|
||||
# - AllowedIPs (зеркалю IPv4 routing mode в IPv6, intent-mirroring):
|
||||
# full tunnel (ALLOWED_IPS=0.0.0.0/0): + ::/0 (native) или + <IPV6_SUBNET> (no-native)
|
||||
# split tunnel (кастомный ALLOWED_IPS): IPv4-список БЕЗ изменений + ТОЛЬКО <IPV6_SUBNET>,
|
||||
# НИКОГДА ::/0 - нет IPv6 split-list, нельзя угонять весь IPv6 (ломает split-tunnel).
|
||||
# Если пустой (legacy-клиент): Address = <ipv4>/32, AllowedIPs без изменений.
|
||||
render_client_config() {
|
||||
local name="$1"
|
||||
|
|
@ -941,11 +943,23 @@ render_client_config() {
|
|||
local conf_file="$AWG_DIR/${name}.conf"
|
||||
local allowed_ips
|
||||
if [[ -n "$client_ipv6" ]]; then
|
||||
if [[ "${SERVER_HAS_NATIVE_IPV6:-0}" == "1" ]]; then
|
||||
allowed_ips="0.0.0.0/0, ::/0"
|
||||
# Dual-stack: зеркалю IPv4 routing intent в IPv6.
|
||||
# full tunnel (IPv4=0.0.0.0/0) -> ::/0 (native) или tunnel-ULA (no-native).
|
||||
# split tunnel (кастомный ALLOWED_IPS) -> IPv4-split AS-IS + ТОЛЬКО tunnel-ULA,
|
||||
# никогда ::/0 (нет IPv6 split-list, нельзя угонять весь IPv6).
|
||||
local ipv4_part ipv6_part
|
||||
ipv4_part="${ALLOWED_IPS:-0.0.0.0/0}"
|
||||
if [[ "$ipv4_part" == "0.0.0.0/0" && "${SERVER_HAS_NATIVE_IPV6:-0}" == "1" ]]; then
|
||||
ipv6_part="::/0"
|
||||
else
|
||||
allowed_ips="0.0.0.0/0, ${IPV6_SUBNET:-fddd:2c4:2c4:2c4::/64}"
|
||||
ipv6_part="${IPV6_SUBNET:-fddd:2c4:2c4:2c4::/64}"
|
||||
fi
|
||||
# Защитный de-dup: ALLOWED_IPS по конструкции IPv4-only, но не дублирую
|
||||
# ipv6_part если он уже присутствует токеном в списке.
|
||||
case ",${ipv4_part// /}," in
|
||||
*",${ipv6_part},"*) allowed_ips="$ipv4_part" ;;
|
||||
*) allowed_ips="${ipv4_part}, ${ipv6_part}" ;;
|
||||
esac
|
||||
else
|
||||
allowed_ips="${ALLOWED_IPS:-0.0.0.0/0}"
|
||||
fi
|
||||
|
|
@ -1391,12 +1405,25 @@ generate_vpn_uri() {
|
|||
# затягивает \r в значение, что ломает JSON.allowed_ips).
|
||||
allowed_ips=$(grep -oP 'AllowedIPs\s*=\s*\K.+' "$conf_file" | tr -d ' \r') || allowed_ips="0.0.0.0/0"
|
||||
|
||||
# MTU/PersistentKeepalive/DNS из .conf - могли быть изменены через manage modify.
|
||||
# Клиент Amnezia при импорте vpn:// использует структурные поля inner JSON
|
||||
# (awgConfigurator берёт mtu именно из структурного поля, не из embedded config),
|
||||
# поэтому хардкод рассинхронизировал бы их с .conf - тот же класс, что issue #67
|
||||
# (structured-поле psk_key было авторитетным).
|
||||
local mtu keepalive dns_line dns1 dns2
|
||||
mtu=$(grep -oP '^MTU\s*=\s*\K[0-9]+' "$conf_file" | head -n1); mtu="${mtu:-1280}"
|
||||
keepalive=$(grep -oP '^PersistentKeepalive\s*=\s*\K[0-9]+' "$conf_file" | head -n1); keepalive="${keepalive:-33}"
|
||||
dns_line=$(grep -oP '^DNS\s*=\s*\K.+' "$conf_file" | head -n1 | tr -d ' \r')
|
||||
dns1="${dns_line%%,*}"; dns1="${dns1:-1.1.1.1}"
|
||||
if [[ "$dns_line" == *,* ]]; then dns2="${dns_line#*,}"; dns2="${dns2%%,*}"; else dns2="$dns1"; fi
|
||||
|
||||
local vpn_uri perl_err
|
||||
perl_err=$(awg_mktemp) || perl_err="/tmp/awg_perl_err.$$"
|
||||
# shellcheck disable=SC2016
|
||||
vpn_uri=$(perl -MCompress::Zlib -MMIME::Base64 -e '
|
||||
my ($conf_path, $h1,$h2,$h3,$h4, $jc,$jmin,$jmax,
|
||||
$s1,$s2,$s3,$s4, $i1, $port, $ep, $cip, $cipv6, $cpk, $spk, $aips, $psk) = @ARGV;
|
||||
$s1,$s2,$s3,$s4, $i1, $port, $ep, $cip, $cipv6, $cpk, $spk, $aips, $psk,
|
||||
$mtu, $keepalive, $dns1, $dns2) = @ARGV;
|
||||
|
||||
open my $fh, "<", $conf_path or die;
|
||||
local $/; my $raw = <$fh>; close $fh;
|
||||
|
|
@ -1430,8 +1457,8 @@ generate_vpn_uri() {
|
|||
$inner .= qq("psk_key":"$epsk",);
|
||||
}
|
||||
$inner .= qq("config":"$eraw",);
|
||||
$inner .= qq("hostName":"$ep","mtu":"1280",);
|
||||
$inner .= qq("persistent_keep_alive":"33","port":$port,);
|
||||
$inner .= qq("hostName":"$ep","mtu":"$mtu",);
|
||||
$inner .= qq("persistent_keep_alive":"$keepalive","port":$port,);
|
||||
$inner .= qq("server_pub_key":"$spk"});
|
||||
|
||||
my $einner = je($inner);
|
||||
|
|
@ -1442,7 +1469,8 @@ generate_vpn_uri() {
|
|||
$outer .= qq("transport_proto":"udp"\},"container":"amnezia-awg"\}],);
|
||||
$outer .= qq("defaultContainer":"amnezia-awg",);
|
||||
$outer .= qq("description":"AWG Server",);
|
||||
$outer .= qq("dns1":"1.1.1.1","dns2":"1.0.0.1",);
|
||||
my $ed1 = je($dns1); my $ed2 = je($dns2);
|
||||
$outer .= qq("dns1":"$ed1","dns2":"$ed2",);
|
||||
$outer .= qq("hostName":"$ep"});
|
||||
|
||||
my $compressed = compress($outer);
|
||||
|
|
@ -1456,7 +1484,8 @@ generate_vpn_uri() {
|
|||
"$AWG_Jc" "$AWG_Jmin" "$AWG_Jmax" \
|
||||
"$AWG_S1" "$AWG_S2" "$AWG_S3" "$AWG_S4" \
|
||||
"$AWG_I1" "$AWG_PORT" "$endpoint" \
|
||||
"$client_ip" "$client_ipv6" "$client_privkey" "$server_pubkey" "$allowed_ips" "$client_psk" 2>"$perl_err"
|
||||
"$client_ip" "$client_ipv6" "$client_privkey" "$server_pubkey" "$allowed_ips" "$client_psk" \
|
||||
"$mtu" "$keepalive" "$dns1" "$dns2" 2>"$perl_err"
|
||||
)
|
||||
|
||||
if [[ -z "$vpn_uri" ]]; then
|
||||
|
|
@ -1520,6 +1549,13 @@ generate_qr_vpnuri() {
|
|||
return 0
|
||||
}
|
||||
|
||||
# Удаляет частично созданные артефакты клиента (ключи + .conf). Используется
|
||||
# в early-error путях generate_client - C10: не оставлять orphan-ключи при сбое
|
||||
# до коммита пира в серверный конфиг.
|
||||
_rollback_client_artifacts() {
|
||||
rm -f "$KEYS_DIR/$1.private" "$KEYS_DIR/$1.public" "$AWG_DIR/$1.conf"
|
||||
}
|
||||
|
||||
# Полный цикл создания клиента:
|
||||
# keypair → next IP → client config → add peer → QR
|
||||
# generate_client <name> [endpoint]
|
||||
|
|
@ -1561,29 +1597,40 @@ generate_client() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
# Генерация ключей
|
||||
generate_keypair "$name" || { exec {lock_fd}>&-; return 1; }
|
||||
# C6: клиент не должен уже существовать. Проверяю ПОД локом, ДО генерации
|
||||
# ключей - иначе `add <существующее_имя>` молча перезатёр бы ключи живого
|
||||
# клиента (generate_keypair перезаписывает безусловно), а параллельный add
|
||||
# того же имени гонялся бы за перезапись.
|
||||
if [[ -e "$KEYS_DIR/${name}.private" || -e "$KEYS_DIR/${name}.public" || -e "$AWG_DIR/${name}.conf" ]]; then
|
||||
log_error "Клиент '$name' уже существует. Используйте 'remove' или другое имя."
|
||||
exec {lock_fd}>&-
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Генерация ключей. С этого момента любой ранний сбой обязан удалить уже
|
||||
# созданные ключи/conf (C10) через _rollback_client_artifacts.
|
||||
generate_keypair "$name" || { _rollback_client_artifacts "$name"; exec {lock_fd}>&-; return 1; }
|
||||
|
||||
# Следующий свободный IP
|
||||
local client_ip
|
||||
client_ip=$(get_next_client_ip) || { exec {lock_fd}>&-; return 1; }
|
||||
client_ip=$(get_next_client_ip) || { _rollback_client_artifacts "$name"; exec {lock_fd}>&-; return 1; }
|
||||
|
||||
# IPv6-адрес клиента (при ALLOW_IPV6_TUNNEL=1)
|
||||
local client_ipv6=""
|
||||
if [[ "${ALLOW_IPV6_TUNNEL:-0}" == "1" ]]; then
|
||||
client_ipv6=$(get_next_client_ipv6 "$client_ip") || { exec {lock_fd}>&-; return 1; }
|
||||
client_ipv6=$(get_next_client_ipv6 "$client_ip") || { _rollback_client_artifacts "$name"; exec {lock_fd}>&-; return 1; }
|
||||
log_debug "Выделен IPv6-адрес ${client_ipv6} для клиента ${name}"
|
||||
fi
|
||||
|
||||
# Читаем ключи
|
||||
local client_privkey client_pubkey server_pubkey
|
||||
client_privkey=$(cat "$KEYS_DIR/${name}.private") || { exec {lock_fd}>&-; return 1; }
|
||||
client_pubkey=$(cat "$KEYS_DIR/${name}.public") || { exec {lock_fd}>&-; return 1; }
|
||||
client_privkey=$(cat "$KEYS_DIR/${name}.private") || { _rollback_client_artifacts "$name"; exec {lock_fd}>&-; return 1; }
|
||||
client_pubkey=$(cat "$KEYS_DIR/${name}.public") || { _rollback_client_artifacts "$name"; exec {lock_fd}>&-; return 1; }
|
||||
|
||||
# Пытаемся восстановить server_public.key из awg0.conf если кеша нет
|
||||
# (поддержка ручных установок без installer-шага 6).
|
||||
_ensure_server_public_key || { exec {lock_fd}>&-; return 1; }
|
||||
server_pubkey=$(cat "$AWG_DIR/server_public.key") || { exec {lock_fd}>&-; return 1; }
|
||||
_ensure_server_public_key || { _rollback_client_artifacts "$name"; exec {lock_fd}>&-; return 1; }
|
||||
server_pubkey=$(cat "$AWG_DIR/server_public.key") || { _rollback_client_artifacts "$name"; exec {lock_fd}>&-; return 1; }
|
||||
|
||||
# Endpoint: из аргумента → AWG_ENDPOINT (awgsetup_cfg.init) → curl до
|
||||
# внешних сервисов → локальный IP с сетевого интерфейса.
|
||||
|
|
@ -1600,22 +1647,23 @@ generate_client() {
|
|||
fi
|
||||
if [[ -z "$endpoint" ]]; then
|
||||
log_error "Не удалось определить внешний IP сервера. Используйте --endpoint=IP"
|
||||
_rollback_client_artifacts "$name"
|
||||
exec {lock_fd}>&-
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Конфиг клиента
|
||||
render_client_config "$name" "$client_ip" "$client_privkey" "$server_pubkey" "$endpoint" "${AWG_PORT}" "$client_ipv6" || {
|
||||
log_error "Откат: удаление ключей '$name'"
|
||||
rm -f "$KEYS_DIR/${name}.private" "$KEYS_DIR/${name}.public"
|
||||
log_error "Откат: удаление артефактов '$name'"
|
||||
_rollback_client_artifacts "$name"
|
||||
exec {lock_fd}>&-
|
||||
return 1
|
||||
}
|
||||
|
||||
# Добавляем пир в серверный конфиг
|
||||
if ! add_peer_to_server "$name" "$client_pubkey" "$client_ip" "$client_ipv6"; then
|
||||
log_error "Откат: удаление файлов '$name'"
|
||||
rm -f "$AWG_DIR/${name}.conf" "$KEYS_DIR/${name}.private" "$KEYS_DIR/${name}.public"
|
||||
log_error "Откат: удаление артефактов '$name'"
|
||||
_rollback_client_artifacts "$name"
|
||||
exec {lock_fd}>&-
|
||||
return 1
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
# ==============================================================================
|
||||
# Shared function library for AmneziaWG 2.0
|
||||
# Author: @bivlked
|
||||
# Version: 5.15.0
|
||||
# Date: 2026-06-01
|
||||
# Version: 5.15.1
|
||||
# Date: 2026-06-02
|
||||
# Repository: https://github.com/bivlked/amneziawg-installer
|
||||
# ==============================================================================
|
||||
#
|
||||
|
|
@ -928,8 +928,10 @@ _extract_mtu_from_server_conf() {
|
|||
# client_ipv6 (optional 7th argument): client IPv6 address without prefix
|
||||
# length (e.g. fddd:2c4:2c4:2c4::5). If non-empty and ALLOW_IPV6_TUNNEL=1:
|
||||
# - Address = <ipv4>/32, <ipv6>/128
|
||||
# - AllowedIPs: SERVER_HAS_NATIVE_IPV6=1 -> 0.0.0.0/0, ::/0
|
||||
# SERVER_HAS_NATIVE_IPV6=0 -> 0.0.0.0/0, <IPV6_SUBNET>
|
||||
# - AllowedIPs (mirror the IPv4 routing mode into IPv6, intent-mirroring):
|
||||
# full tunnel (ALLOWED_IPS=0.0.0.0/0): + ::/0 (native) or + <IPV6_SUBNET> (no-native)
|
||||
# split tunnel (custom ALLOWED_IPS): IPv4 list UNCHANGED + ONLY <IPV6_SUBNET>,
|
||||
# NEVER ::/0 - there is no IPv6 split-list, hijacking all IPv6 breaks split-tunnel.
|
||||
# If empty (legacy client): Address = <ipv4>/32, AllowedIPs unchanged.
|
||||
render_client_config() {
|
||||
local name="$1"
|
||||
|
|
@ -945,11 +947,23 @@ render_client_config() {
|
|||
local conf_file="$AWG_DIR/${name}.conf"
|
||||
local allowed_ips
|
||||
if [[ -n "$client_ipv6" ]]; then
|
||||
if [[ "${SERVER_HAS_NATIVE_IPV6:-0}" == "1" ]]; then
|
||||
allowed_ips="0.0.0.0/0, ::/0"
|
||||
# Dual-stack: mirror the IPv4 routing intent into IPv6.
|
||||
# full tunnel (IPv4=0.0.0.0/0) -> ::/0 (native) or tunnel ULA (no-native).
|
||||
# split tunnel (custom ALLOWED_IPS) -> IPv4 split AS-IS + ONLY tunnel ULA,
|
||||
# never ::/0 (no IPv6 split-list, must not hijack all IPv6).
|
||||
local ipv4_part ipv6_part
|
||||
ipv4_part="${ALLOWED_IPS:-0.0.0.0/0}"
|
||||
if [[ "$ipv4_part" == "0.0.0.0/0" && "${SERVER_HAS_NATIVE_IPV6:-0}" == "1" ]]; then
|
||||
ipv6_part="::/0"
|
||||
else
|
||||
allowed_ips="0.0.0.0/0, ${IPV6_SUBNET:-fddd:2c4:2c4:2c4::/64}"
|
||||
ipv6_part="${IPV6_SUBNET:-fddd:2c4:2c4:2c4::/64}"
|
||||
fi
|
||||
# Defensive de-dup: ALLOWED_IPS is IPv4-only by construction, but do not
|
||||
# duplicate ipv6_part if it is already present as a token in the list.
|
||||
case ",${ipv4_part// /}," in
|
||||
*",${ipv6_part},"*) allowed_ips="$ipv4_part" ;;
|
||||
*) allowed_ips="${ipv4_part}, ${ipv6_part}" ;;
|
||||
esac
|
||||
else
|
||||
allowed_ips="${ALLOWED_IPS:-0.0.0.0/0}"
|
||||
fi
|
||||
|
|
@ -1396,12 +1410,25 @@ generate_vpn_uri() {
|
|||
# captures \r into the value, which breaks JSON.allowed_ips).
|
||||
allowed_ips=$(grep -oP 'AllowedIPs\s*=\s*\K.+' "$conf_file" | tr -d ' \r') || allowed_ips="0.0.0.0/0"
|
||||
|
||||
# MTU/PersistentKeepalive/DNS from .conf - these can be changed via manage modify.
|
||||
# On vpn:// import the Amnezia client uses the structured inner-JSON fields
|
||||
# (awgConfigurator takes mtu from the structured field, not the embedded config),
|
||||
# so hardcoding them would desync from .conf - same class as issue #67 (the
|
||||
# structured psk_key field was authoritative).
|
||||
local mtu keepalive dns_line dns1 dns2
|
||||
mtu=$(grep -oP '^MTU\s*=\s*\K[0-9]+' "$conf_file" | head -n1); mtu="${mtu:-1280}"
|
||||
keepalive=$(grep -oP '^PersistentKeepalive\s*=\s*\K[0-9]+' "$conf_file" | head -n1); keepalive="${keepalive:-33}"
|
||||
dns_line=$(grep -oP '^DNS\s*=\s*\K.+' "$conf_file" | head -n1 | tr -d ' \r')
|
||||
dns1="${dns_line%%,*}"; dns1="${dns1:-1.1.1.1}"
|
||||
if [[ "$dns_line" == *,* ]]; then dns2="${dns_line#*,}"; dns2="${dns2%%,*}"; else dns2="$dns1"; fi
|
||||
|
||||
local vpn_uri perl_err
|
||||
perl_err=$(awg_mktemp) || perl_err="/tmp/awg_perl_err.$$"
|
||||
# shellcheck disable=SC2016
|
||||
vpn_uri=$(perl -MCompress::Zlib -MMIME::Base64 -e '
|
||||
my ($conf_path, $h1,$h2,$h3,$h4, $jc,$jmin,$jmax,
|
||||
$s1,$s2,$s3,$s4, $i1, $port, $ep, $cip, $cipv6, $cpk, $spk, $aips, $psk) = @ARGV;
|
||||
$s1,$s2,$s3,$s4, $i1, $port, $ep, $cip, $cipv6, $cpk, $spk, $aips, $psk,
|
||||
$mtu, $keepalive, $dns1, $dns2) = @ARGV;
|
||||
|
||||
open my $fh, "<", $conf_path or die;
|
||||
local $/; my $raw = <$fh>; close $fh;
|
||||
|
|
@ -1435,8 +1462,8 @@ generate_vpn_uri() {
|
|||
$inner .= qq("psk_key":"$epsk",);
|
||||
}
|
||||
$inner .= qq("config":"$eraw",);
|
||||
$inner .= qq("hostName":"$ep","mtu":"1280",);
|
||||
$inner .= qq("persistent_keep_alive":"33","port":$port,);
|
||||
$inner .= qq("hostName":"$ep","mtu":"$mtu",);
|
||||
$inner .= qq("persistent_keep_alive":"$keepalive","port":$port,);
|
||||
$inner .= qq("server_pub_key":"$spk"});
|
||||
|
||||
my $einner = je($inner);
|
||||
|
|
@ -1447,7 +1474,8 @@ generate_vpn_uri() {
|
|||
$outer .= qq("transport_proto":"udp"\},"container":"amnezia-awg"\}],);
|
||||
$outer .= qq("defaultContainer":"amnezia-awg",);
|
||||
$outer .= qq("description":"AWG Server",);
|
||||
$outer .= qq("dns1":"1.1.1.1","dns2":"1.0.0.1",);
|
||||
my $ed1 = je($dns1); my $ed2 = je($dns2);
|
||||
$outer .= qq("dns1":"$ed1","dns2":"$ed2",);
|
||||
$outer .= qq("hostName":"$ep"});
|
||||
|
||||
my $compressed = compress($outer);
|
||||
|
|
@ -1461,7 +1489,8 @@ generate_vpn_uri() {
|
|||
"$AWG_Jc" "$AWG_Jmin" "$AWG_Jmax" \
|
||||
"$AWG_S1" "$AWG_S2" "$AWG_S3" "$AWG_S4" \
|
||||
"$AWG_I1" "$AWG_PORT" "$endpoint" \
|
||||
"$client_ip" "$client_ipv6" "$client_privkey" "$server_pubkey" "$allowed_ips" "$client_psk" 2>"$perl_err"
|
||||
"$client_ip" "$client_ipv6" "$client_privkey" "$server_pubkey" "$allowed_ips" "$client_psk" \
|
||||
"$mtu" "$keepalive" "$dns1" "$dns2" 2>"$perl_err"
|
||||
)
|
||||
|
||||
if [[ -z "$vpn_uri" ]]; then
|
||||
|
|
@ -1526,6 +1555,13 @@ generate_qr_vpnuri() {
|
|||
return 0
|
||||
}
|
||||
|
||||
# Removes partially created client artifacts (keys + .conf). Used by the
|
||||
# early-error paths of generate_client - C10: do not leave orphan keys when a
|
||||
# step fails before the peer is committed to the server config.
|
||||
_rollback_client_artifacts() {
|
||||
rm -f "$KEYS_DIR/$1.private" "$KEYS_DIR/$1.public" "$AWG_DIR/$1.conf"
|
||||
}
|
||||
|
||||
# Full client creation cycle:
|
||||
# keypair -> next IP -> client config -> add peer -> QR
|
||||
# generate_client <name> [endpoint]
|
||||
|
|
@ -1566,29 +1602,40 @@ generate_client() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
# Generate keys
|
||||
generate_keypair "$name" || { exec {lock_fd}>&-; return 1; }
|
||||
# C6: the client must not already exist. Check UNDER the lock, BEFORE
|
||||
# generating keys - otherwise `add <existing_name>` would silently overwrite
|
||||
# a live client's keys (generate_keypair overwrites unconditionally), and a
|
||||
# concurrent same-name add would race to overwrite.
|
||||
if [[ -e "$KEYS_DIR/${name}.private" || -e "$KEYS_DIR/${name}.public" || -e "$AWG_DIR/${name}.conf" ]]; then
|
||||
log_error "Client '$name' already exists. Use 'remove' or a different name."
|
||||
exec {lock_fd}>&-
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Generate keys. From here on, any early failure must remove the freshly
|
||||
# created keys/conf (C10) via _rollback_client_artifacts.
|
||||
generate_keypair "$name" || { _rollback_client_artifacts "$name"; exec {lock_fd}>&-; return 1; }
|
||||
|
||||
# Next free IP
|
||||
local client_ip
|
||||
client_ip=$(get_next_client_ip) || { exec {lock_fd}>&-; return 1; }
|
||||
client_ip=$(get_next_client_ip) || { _rollback_client_artifacts "$name"; exec {lock_fd}>&-; return 1; }
|
||||
|
||||
# IPv6 address for client (when ALLOW_IPV6_TUNNEL=1)
|
||||
local client_ipv6=""
|
||||
if [[ "${ALLOW_IPV6_TUNNEL:-0}" == "1" ]]; then
|
||||
client_ipv6=$(get_next_client_ipv6 "$client_ip") || { exec {lock_fd}>&-; return 1; }
|
||||
client_ipv6=$(get_next_client_ipv6 "$client_ip") || { _rollback_client_artifacts "$name"; exec {lock_fd}>&-; return 1; }
|
||||
log_debug "Allocated IPv6 address ${client_ipv6} for client ${name}"
|
||||
fi
|
||||
|
||||
# Read keys
|
||||
local client_privkey client_pubkey server_pubkey
|
||||
client_privkey=$(cat "$KEYS_DIR/${name}.private") || { exec {lock_fd}>&-; return 1; }
|
||||
client_pubkey=$(cat "$KEYS_DIR/${name}.public") || { exec {lock_fd}>&-; return 1; }
|
||||
client_privkey=$(cat "$KEYS_DIR/${name}.private") || { _rollback_client_artifacts "$name"; exec {lock_fd}>&-; return 1; }
|
||||
client_pubkey=$(cat "$KEYS_DIR/${name}.public") || { _rollback_client_artifacts "$name"; exec {lock_fd}>&-; return 1; }
|
||||
|
||||
# Try to reconstruct server_public.key from awg0.conf when the cache
|
||||
# is missing (supports manual setups without the installer step 6).
|
||||
_ensure_server_public_key || { exec {lock_fd}>&-; return 1; }
|
||||
server_pubkey=$(cat "$AWG_DIR/server_public.key") || { exec {lock_fd}>&-; return 1; }
|
||||
_ensure_server_public_key || { _rollback_client_artifacts "$name"; exec {lock_fd}>&-; return 1; }
|
||||
server_pubkey=$(cat "$AWG_DIR/server_public.key") || { _rollback_client_artifacts "$name"; exec {lock_fd}>&-; return 1; }
|
||||
|
||||
# Endpoint: argument → AWG_ENDPOINT (awgsetup_cfg.init) → curl to
|
||||
# external services → local IP on a network interface.
|
||||
|
|
@ -1605,22 +1652,23 @@ generate_client() {
|
|||
fi
|
||||
if [[ -z "$endpoint" ]]; then
|
||||
log_error "Failed to determine server public IP. Use --endpoint=IP"
|
||||
_rollback_client_artifacts "$name"
|
||||
exec {lock_fd}>&-
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Client config
|
||||
render_client_config "$name" "$client_ip" "$client_privkey" "$server_pubkey" "$endpoint" "${AWG_PORT}" "$client_ipv6" || {
|
||||
log_error "Rollback: deleting keys for '$name'"
|
||||
rm -f "$KEYS_DIR/${name}.private" "$KEYS_DIR/${name}.public"
|
||||
log_error "Rollback: removing artifacts for '$name'"
|
||||
_rollback_client_artifacts "$name"
|
||||
exec {lock_fd}>&-
|
||||
return 1
|
||||
}
|
||||
|
||||
# Add peer to server config
|
||||
if ! add_peer_to_server "$name" "$client_pubkey" "$client_ip" "$client_ipv6"; then
|
||||
log_error "Rollback: deleting files for '$name'"
|
||||
rm -f "$AWG_DIR/${name}.conf" "$KEYS_DIR/${name}.private" "$KEYS_DIR/${name}.public"
|
||||
log_error "Rollback: removing artifacts for '$name'"
|
||||
_rollback_client_artifacts "$name"
|
||||
exec {lock_fd}>&-
|
||||
return 1
|
||||
fi
|
||||
|
|
|
|||
118
docs/RELEASE_PROCESS.md
Normal file
118
docs/RELEASE_PROCESS.md
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
# Release process
|
||||
|
||||
This is the maintainer runbook for cutting a tagged release of
|
||||
amneziawg-installer. It is the public, self-contained reference that
|
||||
`scripts/preflight-check.sh` points at. Contributors do not need to run a
|
||||
release, but a green `preflight-check.sh` is the bar a release branch must
|
||||
clear before it is tagged.
|
||||
|
||||
## Versioned files and the lockstep rule
|
||||
|
||||
Six scripts carry a version and must stay in lockstep:
|
||||
|
||||
- `install_amneziawg.sh`, `install_amneziawg_en.sh`
|
||||
- `manage_amneziawg.sh`, `manage_amneziawg_en.sh`
|
||||
- `awg_common.sh`, `awg_common_en.sh`
|
||||
|
||||
When any of them changes in a release:
|
||||
|
||||
1. Bump `SCRIPT_VERSION` and the `# Version:` / `# Версия:` header (plus the
|
||||
date) only in the files that actually changed.
|
||||
2. The installers download the helper scripts over the network and verify
|
||||
their SHA256 against pinned constants. If `awg_common*.sh` or
|
||||
`manage_amneziawg*.sh` changed, recompute the four pins
|
||||
(`COMMON_SCRIPT_SHA256` / `MANAGE_SCRIPT_SHA256`, RU + EN) with:
|
||||
|
||||
```bash
|
||||
bash scripts/update-sha-pins.sh # rewrite the four pins
|
||||
bash scripts/update-sha-pins.sh --verify # confirm they match
|
||||
```
|
||||
|
||||
A mismatched pin means a fresh install fails the over-the-network integrity
|
||||
check, so the pins must be recomputed after the helper scripts are final and
|
||||
before the tag is pushed.
|
||||
|
||||
## Pre-tag checklist
|
||||
|
||||
Run the full gate in one shot:
|
||||
|
||||
```bash
|
||||
BASE_REF=origin/main bash scripts/preflight-check.sh
|
||||
```
|
||||
|
||||
`preflight-check.sh` runs, in order:
|
||||
|
||||
1. `bash -n` on the six scripts.
|
||||
2. `shellcheck -s bash -S warning` on the six scripts.
|
||||
3. `bats tests/`.
|
||||
4. No newly added em-dash / en-dash (U+2013 / U+2014) in the diff against
|
||||
`BASE_REF`. New text is hyphen-minus only. Legacy dashes in existing lines
|
||||
are kept (no mass purge); a word-diff is used so that point-editing a legacy
|
||||
line does not flag a dash it already contained - only a genuinely new dash
|
||||
fails. If you do touch such a line, converting its dashes to hyphens is
|
||||
welcome but not required.
|
||||
5. No AI / tool markers introduced in the diff or in the commit-message log.
|
||||
6. No `Co-authored-by` trailers in the commit-message log.
|
||||
7. `SCRIPT_VERSION` and the six version headers agree.
|
||||
8. SHA pins in lockstep (`update-sha-pins.sh --verify`).
|
||||
9. Documentation consistency (`scripts/check-docs-consistency.sh`): internal
|
||||
anchors resolve, changelog headings have reference links and the RU/EN
|
||||
version sets match, the version triple agrees, the OS matrix is current.
|
||||
|
||||
`BASE_REF` selects the ref the diff checks compare against. If you do not set
|
||||
it, the script tries `main`, then `origin/main`. On a detached checkout with no
|
||||
local `main` (for example a freshly cloned CI runner on a tag), set it
|
||||
explicitly: `BASE_REF=origin/main`. `LOG_RANGE` (default `<base>..HEAD`)
|
||||
selects which commits the message checks read; merge commits brought in from
|
||||
`main` are outside the branch range and are not re-checked.
|
||||
|
||||
Also confirm by hand before tagging:
|
||||
|
||||
- CHANGELOG entry added to both `CHANGELOG.md` and `CHANGELOG.en.md` in the
|
||||
exact heading format `## [X.Y.Z] - YYYY-MM-DD` (the release workflow parses it
|
||||
with awk), plus the matching `[X.Y.Z]:` reference link, and the `[Unreleased]`
|
||||
link retargeted to `vX.Y.Z...HEAD`.
|
||||
- The RU and EN changelogs list the same set of versions.
|
||||
- The README version badge, `SCRIPT_VERSION`, and the newest changelog heading
|
||||
all agree.
|
||||
- Ubuntu / Debian support matrix is current everywhere it is stated (README,
|
||||
installer `--help`, issue template).
|
||||
|
||||
## Tagging and the two release workflows
|
||||
|
||||
A tag push (`git push origin vX.Y.Z`) triggers two independent workflows:
|
||||
|
||||
- `release.yml` (about 30 seconds): runs the preflight gate, then creates the
|
||||
GitHub Release from the matching `CHANGELOG.en.md` section. The publish step
|
||||
depends on the gate, so a tag that fails preflight does not produce a
|
||||
release.
|
||||
- `arm-build.yml` (about 20-30 minutes): builds the ARM prebuilt `.deb`
|
||||
packages under QEMU and publishes them to the separate `arm-packages`
|
||||
release. This is a separate, slower track and does not block the main
|
||||
release.
|
||||
|
||||
The release is not finished until both runs are green.
|
||||
|
||||
If the preflight gate fails on a pushed tag, the release is not published.
|
||||
Delete the tag (`git push origin :refs/tags/vX.Y.Z` and `git tag -d vX.Y.Z`),
|
||||
fix the branch, and re-tag.
|
||||
|
||||
## Release notes
|
||||
|
||||
`release.yml` fills the English release body from `CHANGELOG.en.md`. Add the
|
||||
Russian section by hand afterwards:
|
||||
|
||||
```bash
|
||||
gh release edit vX.Y.Z --notes-file body.md
|
||||
```
|
||||
|
||||
Follow the established bilingual template: Russian header, a
|
||||
`[English version below](#english-version)` link, the Russian content, an
|
||||
`<a id="english-version"></a>` anchor, then the English content.
|
||||
|
||||
## Release signing
|
||||
|
||||
Release signing (minisign detached signatures) is designed but not active. See
|
||||
`docs/SIGNING_DESIGN.md`. The draft workflow `docs/release-sign.yml.draft` is
|
||||
not installed under `.github/workflows/` until a maintainer public key is
|
||||
published as `KEYS.txt`.
|
||||
|
|
@ -94,11 +94,11 @@ Pros: zero CI changes, signatures generated on the trusted maintainer machine. C
|
|||
|
||||
### Option B: CI uploads signatures generated locally (asymmetric)
|
||||
|
||||
Maintainer generates `*.minisig` files locally, commits them transiently to a `signing/` directory (gitignored elsewhere), tags. A new job in `release.yml` reads them and uploads as assets. Same trust model as Option A - just automates the upload step.
|
||||
Maintainer generates `*.minisig` files locally, commits them transiently to a `signing/` directory (gitignored elsewhere), tags. A separate, manually dispatched workflow reads them and uploads as assets. Same trust model as Option A - just automates the upload step.
|
||||
|
||||
The signing of the files NEVER happens in GitHub Actions. The private key is never exposed to Actions. This is intentional and the whole point.
|
||||
|
||||
A draft of Option B is committed at `docs/release-sign.yml.draft` for review. It is NOT placed in `.github/workflows/` until the public key is published.
|
||||
A draft of Option B is committed at `docs/release-sign.yml.draft` for review. It is a standalone `workflow_dispatch` workflow (not a job added to `release.yml`), and is NOT placed in `.github/workflows/` until the public key is published. When activated it uploads, for each of the six scripts, the script itself plus its `.minisig`, and also attaches `KEYS.txt` - so everything needed to verify is available from a single release. `release.yml` itself does not publish the `.sh` files as assets; that is intentionally left to the signing workflow to avoid two code paths uploading the same files. Until signing is activated, the canonical way to obtain a script is still the documented `wget` from `raw.githubusercontent.com`.
|
||||
|
||||
## User-side verification
|
||||
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@ fi
|
|||
# ==============================================================================
|
||||
# Скрипт для установки и настройки AmneziaWG 2.0 на Ubuntu/Debian серверах
|
||||
# Автор: @bivlked
|
||||
# Версия: 5.15.0
|
||||
# Дата: 2026-06-01
|
||||
# Версия: 5.15.1
|
||||
# Дата: 2026-06-02
|
||||
# Репозиторий: https://github.com/bivlked/amneziawg-installer
|
||||
# ==============================================================================
|
||||
|
||||
# --- Безопасный режим и Константы ---
|
||||
set -o pipefail
|
||||
|
||||
SCRIPT_VERSION="5.15.0"
|
||||
SCRIPT_VERSION="5.15.1"
|
||||
AWG_DIR="/root/awg"
|
||||
CONFIG_FILE="$AWG_DIR/awgsetup_cfg.init"
|
||||
STATE_FILE="$AWG_DIR/setup_state"
|
||||
|
|
@ -33,8 +33,8 @@ MANAGE_SCRIPT_PATH="$AWG_DIR/manage_amneziawg.sh"
|
|||
# Проверяются в step5_download_scripts() после curl.
|
||||
# Если AWG_BRANCH переопределён (не v$SCRIPT_VERSION), проверка пропускается.
|
||||
# Формат: sha256sum output (hex, 64 chars).
|
||||
COMMON_SCRIPT_SHA256="3aad0993439f746fa0780f974026e0238b4d3b4587c8303e784d08385e15eac3"
|
||||
MANAGE_SCRIPT_SHA256="64453fc8d32dc21939ce6bf85b64c243685c97f10a100d889b3422d3bbe19481"
|
||||
COMMON_SCRIPT_SHA256="6e0634c89ddb69c47ca423eb8a6efd0b974368e8122fefeddb6e22c2425f4c2e"
|
||||
MANAGE_SCRIPT_SHA256="6d2cbc25a86ff8577344e416613bce7357389d7fea4b3988e2449a2e8b8a54e0"
|
||||
|
||||
# Флаги CLI
|
||||
UNINSTALL=0; HELP=0; DIAGNOSTIC=0; VERBOSE=0; NO_COLOR=0; AUTO_YES=0; NO_TWEAKS=0
|
||||
|
|
@ -92,9 +92,7 @@ log_msg() {
|
|||
local type="$1" msg="$2"
|
||||
local ts
|
||||
ts=$(date +'%F %T')
|
||||
local safe_msg
|
||||
safe_msg="${msg//%/%%}"
|
||||
local entry="[$ts] $type: $safe_msg"
|
||||
local entry="[$ts] $type: $msg"
|
||||
local color_start="" color_end=""
|
||||
|
||||
if [[ "$NO_COLOR" -eq 0 ]]; then
|
||||
|
|
@ -249,7 +247,7 @@ apt_wait_for_ppa_package() {
|
|||
show_help() {
|
||||
cat << 'EOF'
|
||||
Использование: sudo bash install_amneziawg.sh [ОПЦИИ]
|
||||
Скрипт для установки и настройки AmneziaWG 2.0 на Ubuntu (24.04 / 25.10) и Debian (12 / 13).
|
||||
Скрипт для установки и настройки AmneziaWG 2.0 на Ubuntu (24.04 / 25.10 / 26.04) и Debian (12 / 13).
|
||||
|
||||
Опции:
|
||||
-h, --help Показать эту справку и выйти
|
||||
|
|
@ -261,7 +259,7 @@ show_help() {
|
|||
--ssh-port=ПОРТ SSH-порт для правила UFW (по умолчанию определяется
|
||||
автоматически; список через запятую). Используйте, если
|
||||
SSH на нестандартном порту и автодетект недоступен
|
||||
--subnet=ПОДСЕТЬ Установить подсеть туннеля (x.x.x.x/yy) неинтерактивно
|
||||
--subnet=ПОДСЕТЬ Подсеть туннеля, только /24 (напр. 10.9.9.1/24) неинтерактивно
|
||||
--allow-ipv6 Оставить IPv6 включенным неинтерактивно
|
||||
--disallow-ipv6 Принудительно отключить IPv6 неинтерактивно
|
||||
--allow-ipv6-tunnel Включить dual-stack IPv6 внутри туннеля (ULA, opt-in)
|
||||
|
|
@ -455,7 +453,12 @@ install_packages() {
|
|||
fi
|
||||
log "Установка: ${to_install[*]}..."
|
||||
if [[ "${_APT_UPDATED:-0}" -eq 0 ]]; then
|
||||
apt_update_tolerant || log_warn "Не удалось обновить apt."
|
||||
# C4: жёсткая ошибка apt_update_tolerant (GPG / сеть на binary-репо / OOM) -
|
||||
# это НЕ source-шум, а реальный сбой; продолжать на устаревшем кэше нельзя
|
||||
# (контракт стр.138, как у вызовов 1975/2108). die завершает установку, так
|
||||
# что _APT_UPDATED=1 проставляется только при успехе - иначе повторный
|
||||
# install_packages в этой сессии молча пропустил бы update.
|
||||
apt_update_tolerant || die "Ошибка apt update."
|
||||
_APT_UPDATED=1
|
||||
fi
|
||||
if ! DEBIAN_FRONTEND=noninteractive apt install -y "${to_install[@]}"; then
|
||||
|
|
@ -510,10 +513,23 @@ configure_ipv6() {
|
|||
}
|
||||
|
||||
# Определение наличия native IPv6 на VPS.
|
||||
# Глобальный scope IPv6 (не link-local fe80::) означает выход в IPv6-интернет.
|
||||
# Эхо 1 если найден global IPv6, иначе 0.
|
||||
# Native IPv6 = глобально маршрутизируемый адрес (НЕ ULA fc00::/7, НЕ link-local
|
||||
# fe80::) И наличие IPv6 default-маршрута. Любого из условий по отдельности мало:
|
||||
# - global-адрес без default route -> нет выхода в IPv6-интернет (клиент с ::/0
|
||||
# получит black-hole);
|
||||
# - ULA (fddd::/...) для `ip` имеет scope global, но в интернет не маршрутизируется.
|
||||
# Эхо 1 только когда выполнены оба условия, иначе 0.
|
||||
detect_native_ipv6() {
|
||||
if ip -6 addr show scope global 2>/dev/null | grep -q "inet6"; then
|
||||
local have_addr=0 have_route=0
|
||||
if ip -6 addr show scope global 2>/dev/null \
|
||||
| grep -oP 'inet6\s+\K[0-9a-fA-F:]+' \
|
||||
| grep -qviE '^(fc|fd)'; then
|
||||
have_addr=1
|
||||
fi
|
||||
if ip -6 route show default 2>/dev/null | grep -q .; then
|
||||
have_route=1
|
||||
fi
|
||||
if [[ "$have_addr" -eq 1 && "$have_route" -eq 1 ]]; then
|
||||
echo 1
|
||||
else
|
||||
echo 0
|
||||
|
|
@ -527,12 +543,22 @@ configure_ipv6_tunnel() {
|
|||
ALLOW_IPV6_TUNNEL=0
|
||||
fi
|
||||
: "${IPV6_SUBNET:=fddd:2c4:2c4:2c4::/64}"
|
||||
# Native IPv6 определяю при каждом запуске (кэширую в init для client render Phase 4).
|
||||
SERVER_HAS_NATIVE_IPV6=$(detect_native_ipv6)
|
||||
if [[ "$ALLOW_IPV6_TUNNEL" -eq 1 && "$DISABLE_IPV6" -eq 1 ]]; then
|
||||
log_warn "--allow-ipv6-tunnel requires host IPv6 forwarding; overriding --disallow-ipv6 (DISABLE_IPV6=0)"
|
||||
DISABLE_IPV6=0
|
||||
# IPv6-туннель требует включённого IPv6 на хосте. Снимаю --disallow-ipv6 И
|
||||
# активно включаю IPv6 в рантайме ДО detection/render: при upgrade с дефолтной
|
||||
# прошлой установки (IPv6 был выключен в рантайме) ядро скрывает все IPv6-адреса,
|
||||
# поэтому detect_native_ipv6 дал бы false-negative, а клиент отрендерился бы с
|
||||
# IPv6 Address при выключенном в ядре IPv6 (awg-quick restart может упасть). weaq P1.
|
||||
if [[ "$ALLOW_IPV6_TUNNEL" -eq 1 ]]; then
|
||||
if [[ "$DISABLE_IPV6" -eq 1 ]]; then
|
||||
log_warn "--allow-ipv6-tunnel requires host IPv6 forwarding; overriding --disallow-ipv6 (DISABLE_IPV6=0)"
|
||||
DISABLE_IPV6=0
|
||||
fi
|
||||
sysctl -w net.ipv6.conf.all.disable_ipv6=0 >/dev/null 2>&1 || true
|
||||
sysctl -w net.ipv6.conf.default.disable_ipv6=0 >/dev/null 2>&1 || true
|
||||
sysctl -w net.ipv6.conf.lo.disable_ipv6=0 >/dev/null 2>&1 || true
|
||||
fi
|
||||
# Native IPv6 определяю ПОСЛЕ runtime-включения (кэширую в init для client render Phase 4).
|
||||
SERVER_HAS_NATIVE_IPV6=$(detect_native_ipv6)
|
||||
if [[ "$ALLOW_IPV6_TUNNEL" -eq 1 && "$SERVER_HAS_NATIVE_IPV6" -eq 0 ]]; then
|
||||
log_warn "Native IPv6 не обнаружен на VPS - туннель IPv6 будет работать peer-to-peer без выхода в IPv6-интернет."
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ fi
|
|||
# ==============================================================================
|
||||
# AmneziaWG 2.0 installation and configuration script for Ubuntu/Debian servers
|
||||
# Author: @bivlked
|
||||
# Version: 5.15.0
|
||||
# Date: 2026-06-01
|
||||
# Version: 5.15.1
|
||||
# Date: 2026-06-02
|
||||
# Repository: https://github.com/bivlked/amneziawg-installer
|
||||
# ==============================================================================
|
||||
|
||||
# --- Safe mode and Constants ---
|
||||
set -o pipefail
|
||||
SCRIPT_VERSION="5.15.0"
|
||||
SCRIPT_VERSION="5.15.1"
|
||||
|
||||
AWG_DIR="/root/awg"
|
||||
CONFIG_FILE="$AWG_DIR/awgsetup_cfg.init"
|
||||
|
|
@ -33,8 +33,8 @@ MANAGE_SCRIPT_PATH="$AWG_DIR/manage_amneziawg.sh"
|
|||
# Verified in step5_download_scripts() after curl.
|
||||
# Verification is skipped when AWG_BRANCH is overridden (test branch).
|
||||
# Format: sha256sum output (hex, 64 chars).
|
||||
COMMON_SCRIPT_SHA256="6e01a699f520a36b0cd65c2e0a1c357a92a06d14f68ffd35498551ae2f5e12f8"
|
||||
MANAGE_SCRIPT_SHA256="baa49a273539e9d9d8e342f9c2a914161fda96b58b4f016261744effa4b028b6"
|
||||
COMMON_SCRIPT_SHA256="25b741ee04b294963d082cd11afdef1fd14e18bff88a14376bef12cee13a226f"
|
||||
MANAGE_SCRIPT_SHA256="ce2d392c396d08915ea24eaa51663f12c3530dd9b044314feb3f585620a8a06b"
|
||||
|
||||
# CLI flags
|
||||
UNINSTALL=0; HELP=0; DIAGNOSTIC=0; VERBOSE=0; NO_COLOR=0; AUTO_YES=0; NO_TWEAKS=0
|
||||
|
|
@ -92,9 +92,7 @@ log_msg() {
|
|||
local type="$1" msg="$2"
|
||||
local ts
|
||||
ts=$(date +'%F %T')
|
||||
local safe_msg
|
||||
safe_msg="${msg//%/%%}"
|
||||
local entry="[$ts] $type: $safe_msg"
|
||||
local entry="[$ts] $type: $msg"
|
||||
local color_start="" color_end=""
|
||||
|
||||
if [[ "$NO_COLOR" -eq 0 ]]; then
|
||||
|
|
@ -252,7 +250,7 @@ apt_wait_for_ppa_package() {
|
|||
show_help() {
|
||||
cat << 'EOF'
|
||||
Usage: sudo bash install_amneziawg_en.sh [OPTIONS]
|
||||
Script for installation and configuration of AmneziaWG 2.0 on Ubuntu (24.04 / 25.10) and Debian (12 / 13).
|
||||
Script for installation and configuration of AmneziaWG 2.0 on Ubuntu (24.04 / 25.10 / 26.04) and Debian (12 / 13).
|
||||
|
||||
Options:
|
||||
-h, --help Show this help and exit
|
||||
|
|
@ -264,7 +262,7 @@ Options:
|
|||
--ssh-port=PORT SSH port for the UFW rule (auto-detected by default;
|
||||
comma-separated list). Use if SSH runs on a non-standard
|
||||
port and auto-detection is unavailable
|
||||
--subnet=SUBNET Set tunnel subnet (x.x.x.x/yy) non-interactively
|
||||
--subnet=SUBNET Tunnel subnet, /24 only (e.g. 10.9.9.1/24) non-interactively
|
||||
--allow-ipv6 Keep IPv6 enabled non-interactively
|
||||
--disallow-ipv6 Force-disable IPv6 non-interactively
|
||||
--allow-ipv6-tunnel Enable dual-stack IPv6 inside the tunnel (ULA, opt-in)
|
||||
|
|
@ -460,7 +458,12 @@ install_packages() {
|
|||
fi
|
||||
log "Installing: ${to_install[*]}..."
|
||||
if [[ "${_APT_UPDATED:-0}" -eq 0 ]]; then
|
||||
apt_update_tolerant || log_warn "Failed to update apt."
|
||||
# C4: a hard apt_update_tolerant failure (GPG / binary-repo network / OOM)
|
||||
# is NOT source noise but a real error; continuing on a stale cache is not
|
||||
# safe (contract line ~138, same as callers 1975/2108). die aborts the
|
||||
# install, so _APT_UPDATED=1 is set only on success - otherwise a later
|
||||
# install_packages call in this session would silently skip the update.
|
||||
apt_update_tolerant || die "apt update error."
|
||||
_APT_UPDATED=1
|
||||
fi
|
||||
if ! DEBIAN_FRONTEND=noninteractive apt install -y "${to_install[@]}"; then
|
||||
|
|
@ -515,10 +518,23 @@ configure_ipv6() {
|
|||
}
|
||||
|
||||
# Detect whether the VPS has native IPv6.
|
||||
# A global-scope IPv6 (not link-local fe80::) means egress to the IPv6 internet.
|
||||
# Echo 1 if a global IPv6 is found, otherwise 0.
|
||||
# Native IPv6 = a globally routable address (NOT ULA fc00::/7, NOT link-local
|
||||
# fe80::) AND a default IPv6 route. Either condition alone is insufficient:
|
||||
# - a global address without a default route -> no IPv6 internet egress (a client
|
||||
# with ::/0 would black-hole);
|
||||
# - a ULA (fddd::/...) has global scope to `ip` but is not internet-routable.
|
||||
# Echo 1 only when both conditions hold, otherwise 0.
|
||||
detect_native_ipv6() {
|
||||
if ip -6 addr show scope global 2>/dev/null | grep -q "inet6"; then
|
||||
local have_addr=0 have_route=0
|
||||
if ip -6 addr show scope global 2>/dev/null \
|
||||
| grep -oP 'inet6\s+\K[0-9a-fA-F:]+' \
|
||||
| grep -qviE '^(fc|fd)'; then
|
||||
have_addr=1
|
||||
fi
|
||||
if ip -6 route show default 2>/dev/null | grep -q .; then
|
||||
have_route=1
|
||||
fi
|
||||
if [[ "$have_addr" -eq 1 && "$have_route" -eq 1 ]]; then
|
||||
echo 1
|
||||
else
|
||||
echo 0
|
||||
|
|
@ -532,12 +548,23 @@ configure_ipv6_tunnel() {
|
|||
ALLOW_IPV6_TUNNEL=0
|
||||
fi
|
||||
: "${IPV6_SUBNET:=fddd:2c4:2c4:2c4::/64}"
|
||||
# Detect native IPv6 on every run (cached in init for client render in Phase 4).
|
||||
SERVER_HAS_NATIVE_IPV6=$(detect_native_ipv6)
|
||||
if [[ "$ALLOW_IPV6_TUNNEL" -eq 1 && "$DISABLE_IPV6" -eq 1 ]]; then
|
||||
log_warn "--allow-ipv6-tunnel requires host IPv6 forwarding; overriding --disallow-ipv6 (DISABLE_IPV6=0)"
|
||||
DISABLE_IPV6=0
|
||||
# The IPv6 tunnel requires host IPv6 enabled. Override --disallow-ipv6 AND
|
||||
# actively re-enable IPv6 at runtime BEFORE detection/render: on an upgrade
|
||||
# from a default past install (IPv6 was runtime-disabled), the kernel hides
|
||||
# all IPv6 addresses, so detect_native_ipv6 would false-negative and a client
|
||||
# would be rendered with an IPv6 Address while the kernel has IPv6 off
|
||||
# (awg-quick restart can fail). weaq P1.
|
||||
if [[ "$ALLOW_IPV6_TUNNEL" -eq 1 ]]; then
|
||||
if [[ "$DISABLE_IPV6" -eq 1 ]]; then
|
||||
log_warn "--allow-ipv6-tunnel requires host IPv6 forwarding; overriding --disallow-ipv6 (DISABLE_IPV6=0)"
|
||||
DISABLE_IPV6=0
|
||||
fi
|
||||
sysctl -w net.ipv6.conf.all.disable_ipv6=0 >/dev/null 2>&1 || true
|
||||
sysctl -w net.ipv6.conf.default.disable_ipv6=0 >/dev/null 2>&1 || true
|
||||
sysctl -w net.ipv6.conf.lo.disable_ipv6=0 >/dev/null 2>&1 || true
|
||||
fi
|
||||
# Detect native IPv6 AFTER the runtime re-enable (cached in init for client render in Phase 4).
|
||||
SERVER_HAS_NATIVE_IPV6=$(detect_native_ipv6)
|
||||
if [[ "$ALLOW_IPV6_TUNNEL" -eq 1 && "$SERVER_HAS_NATIVE_IPV6" -eq 0 ]]; then
|
||||
log_warn "Native IPv6 not detected on VPS - the IPv6 tunnel will work peer-to-peer only, without IPv6 internet egress."
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ fi
|
|||
# ==============================================================================
|
||||
# Скрипт для управления пользователями (пирами) AmneziaWG 2.0
|
||||
# Автор: @bivlked
|
||||
# Версия: 5.15.0
|
||||
# Дата: 2026-06-01
|
||||
# Версия: 5.15.1
|
||||
# Дата: 2026-06-02
|
||||
# Репозиторий: https://github.com/bivlked/amneziawg-installer
|
||||
# ==============================================================================
|
||||
|
||||
# --- Безопасный режим и Константы ---
|
||||
# shellcheck disable=SC2034
|
||||
SCRIPT_VERSION="5.15.0"
|
||||
SCRIPT_VERSION="5.15.1"
|
||||
set -o pipefail
|
||||
AWG_DIR="/root/awg"
|
||||
SERVER_CONF_FILE="/etc/amnezia/amneziawg/awg0.conf"
|
||||
|
|
@ -55,10 +55,11 @@ trap _manage_cleanup EXIT INT TERM
|
|||
|
||||
# --- Обработка аргументов ---
|
||||
COMMAND=""
|
||||
HELP_EXIT_RC=0 # C1: 0 = явный help (exit 0); ставится в 1 для ошибок использования
|
||||
ARGS=()
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help) COMMAND="help"; break ;;
|
||||
-h|--help) COMMAND="help"; HELP_EXIT_RC=0; break ;;
|
||||
-v|--verbose) VERBOSE_LIST=1; shift ;;
|
||||
--no-color) NO_COLOR=1; shift ;;
|
||||
--json) JSON_OUTPUT=1; shift ;;
|
||||
|
|
@ -69,7 +70,7 @@ while [[ $# -gt 0 ]]; do
|
|||
--psk) CLI_ADD_PSK=1; shift ;;
|
||||
--yes) CLI_YES=1; shift ;;
|
||||
--carrier=*) CLI_CARRIER="${1#*=}"; shift ;;
|
||||
--*) echo "Неизвестная опция: $1" >&2; COMMAND="help"; break ;;
|
||||
--*) echo "Неизвестная опция: $1" >&2; COMMAND="help"; HELP_EXIT_RC=1; break ;;
|
||||
*)
|
||||
if [[ -z "$COMMAND" ]]; then
|
||||
COMMAND=$1
|
||||
|
|
@ -97,9 +98,7 @@ log_msg() {
|
|||
local type="$1" msg="$2"
|
||||
local ts
|
||||
ts=$(date +'%F %T')
|
||||
local safe_msg
|
||||
safe_msg="${msg//%/%%}"
|
||||
local entry="[$ts] $type: $safe_msg"
|
||||
local entry="[$ts] $type: $msg"
|
||||
local color_start="" color_end=""
|
||||
|
||||
if [[ "$NO_COLOR" -eq 0 ]]; then
|
||||
|
|
@ -121,6 +120,11 @@ log_msg() {
|
|||
# для CI/automation парсинга: stdout = «данные», stderr = «диагностика»).
|
||||
if [[ "$type" == "ERROR" || "$type" == "WARN" ]]; then
|
||||
printf "${color_start}%s${color_end}\n" "$entry" >&2
|
||||
elif [[ "${JSON_OUTPUT:-0}" -eq 1 ]]; then
|
||||
# weaq P2: в режиме --json stdout обязан содержать ТОЛЬКО JSON (jq/automation).
|
||||
# INFO/DEBUG уводим в stderr, иначе list/show/stats --json печатают INFO-строки
|
||||
# перед JSON и ломают парсинг (подтверждено на biHetzner).
|
||||
printf "${color_start}%s${color_end}\n" "$entry" >&2
|
||||
else
|
||||
printf "${color_start}%s${color_end}\n" "$entry"
|
||||
fi
|
||||
|
|
@ -383,7 +387,7 @@ _restore_do_rollback() {
|
|||
[[ -d "$_rtd/keys" ]] && cp -a "$_rtd/keys/"* "$KEYS_DIR/" 2>/dev/null
|
||||
[[ -f "$_rtd/server_private.key" ]] && cp -a "$_rtd/server_private.key" "$AWG_DIR/" 2>/dev/null
|
||||
[[ -f "$_rtd/server_public.key" ]] && cp -a "$_rtd/server_public.key" "$AWG_DIR/" 2>/dev/null
|
||||
[[ -d "$_rtd/expiry" ]] && cp -a "$_rtd/expiry"/* "${EXPIRY_DIR:-$AWG_DIR/expiry}/" 2>/dev/null
|
||||
[[ -d "$_rtd/expiry" ]] && { mkdir -p "${EXPIRY_DIR:-$AWG_DIR/expiry}"; cp -a "$_rtd/expiry"/* "${EXPIRY_DIR:-$AWG_DIR/expiry}/" 2>/dev/null; }
|
||||
[[ -f "$_rtd/awg-expiry" ]] && cp -a "$_rtd/awg-expiry" /etc/cron.d/awg-expiry 2>/dev/null
|
||||
rm -rf "$_rtd"
|
||||
|
||||
|
|
@ -582,6 +586,11 @@ restore_backup() {
|
|||
|
||||
if [[ -d "$td/clients" ]]; then
|
||||
log "Восстановление файлов клиентов..."
|
||||
# C11: чистая замена, не merge. Удаляю stale client-артефакты, которых
|
||||
# нет в бэкапе (иначе клиент, удалённый после снятия бэкапа, остаётся
|
||||
# orphan .conf/.png/.vpnuri). Scope строго managed client-globs - НЕ
|
||||
# трогаю скрипты, server-ключи, backups/, логи, .lock, awgsetup_cfg.init.
|
||||
rm -f "$AWG_DIR"/*.conf "$AWG_DIR"/*.png "$AWG_DIR"/*.vpnuri 2>/dev/null || true
|
||||
if ! cp -a "$td/clients/"* "$AWG_DIR/"; then
|
||||
log_error "Ошибка копирования clients — восстановление прервано (запуск отката)."
|
||||
return 1
|
||||
|
|
@ -596,6 +605,9 @@ restore_backup() {
|
|||
if [[ -d "$td/keys" ]]; then
|
||||
log "Восстановление ключей..."
|
||||
mkdir -p "$KEYS_DIR"
|
||||
# C11: удаляю stale client-ключи, которых нет в бэкапе (server-ключи
|
||||
# лежат в AWG_DIR, не в KEYS_DIR, поэтому не затрагиваются).
|
||||
rm -f "$KEYS_DIR"/* 2>/dev/null || true
|
||||
if ! cp -a "$td/keys/"* "$KEYS_DIR/"; then
|
||||
log_error "Ошибка копирования keys — восстановление прервано (запуск отката)."
|
||||
return 1
|
||||
|
|
@ -624,6 +636,10 @@ restore_backup() {
|
|||
if [[ -d "$td/expiry" ]]; then
|
||||
log "Восстановление данных expiry..."
|
||||
mkdir -p "${EXPIRY_DIR:-$AWG_DIR/expiry}"
|
||||
# C11: expiry НЕ пруним намеренно. Orphan-метки для несуществующих клиентов
|
||||
# безвредны (cron-чистка их игнорирует), а prune здесь был бы небезопасен:
|
||||
# и rm, и последующий cp - best-effort (|| true), так что сбой copy после
|
||||
# prune молча оставил бы expiry пустым. Сами client-артефакты пруним выше.
|
||||
cp -a "$td/expiry/"* "${EXPIRY_DIR:-$AWG_DIR/expiry}/" 2>/dev/null || true
|
||||
chmod 600 "${EXPIRY_DIR:-$AWG_DIR/expiry}"/* 2>/dev/null
|
||||
fi
|
||||
|
|
@ -687,17 +703,43 @@ modify_client() {
|
|||
return 1
|
||||
fi ;;
|
||||
Endpoint)
|
||||
# C5: помимо отсечения опасных символов - позитивная проверка host:port.
|
||||
case "$value" in
|
||||
*$'\n'*|*$'\r'*|*\\*|*\"*|*\'*|"")
|
||||
*$'\n'*|*$'\r'*|*\\*|*\"*|*\'*|*' '*|*$'\t'*|"")
|
||||
log_error "Невалидный Endpoint: '$value'"
|
||||
return 1 ;;
|
||||
esac ;;
|
||||
esac
|
||||
local _eh _ept
|
||||
if [[ "$value" == \[*\]:* ]]; then
|
||||
_eh="${value%]:*}"; _eh="${_eh#\[}" # IPv6 без скобок
|
||||
_ept="${value##*]:}"
|
||||
[[ "$_eh" =~ ^[0-9A-Fa-f:]+$ ]] || { log_error "Невалидный Endpoint '$value': некорректный IPv6-хост"; return 1; }
|
||||
else
|
||||
_eh="${value%:*}"; _ept="${value##*:}"
|
||||
[[ "$_eh" =~ ^([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)*|[0-9]{1,3}(\.[0-9]{1,3}){3})$ ]] || { log_error "Невалидный Endpoint '$value': ожидается host:port (FQDN / IPv4 / [IPv6])"; return 1; }
|
||||
fi
|
||||
{ [[ "$_ept" =~ ^[0-9]+$ ]] && [[ "$_ept" -ge 1 && "$_ept" -le 65535 ]]; } || { log_error "Невалидный Endpoint '$value': порт должен быть 1-65535"; return 1; }
|
||||
;;
|
||||
AllowedIPs)
|
||||
# C5: помимо отсечения опасных символов - позитивная проверка CIDR-списка.
|
||||
case "$value" in
|
||||
*$'\n'*|*$'\r'*|*\\*|*\"*|*\'*|"")
|
||||
log_error "Невалидный AllowedIPs: '$value'"
|
||||
return 1 ;;
|
||||
esac ;;
|
||||
esac
|
||||
local _aip_tok _aip_ifs="$IFS"
|
||||
IFS=','
|
||||
for _aip_tok in $value; do
|
||||
_aip_tok="${_aip_tok//[[:space:]]/}"
|
||||
[[ -z "$_aip_tok" ]] && continue
|
||||
if ! [[ "$_aip_tok" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}(/[0-9]{1,2})?$ || "$_aip_tok" =~ ^[0-9A-Fa-f:]+(/[0-9]{1,3})?$ ]]; then
|
||||
IFS="$_aip_ifs"
|
||||
log_error "Невалидный AllowedIPs '$value': '$_aip_tok' не похож на CIDR (a.b.c.d/n или IPv6/n)"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
IFS="$_aip_ifs"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Блокировка перед state-проверками (защита от TOCTOU с concurrent remove)
|
||||
|
|
@ -1358,7 +1400,11 @@ stats_clients() {
|
|||
# ==============================================================================
|
||||
|
||||
usage() {
|
||||
exec >&2
|
||||
# C1: явный help (rc=0) -> stdout + exit 0; ошибка использования (rc!=0,
|
||||
# дефолт) -> stderr + exit 1. Явные help-вызовы передают 0, error-вызовы
|
||||
# опускают аргумент (получают 1).
|
||||
local _rc="${1:-1}"
|
||||
[[ "$_rc" -ne 0 ]] && exec >&2
|
||||
echo ""
|
||||
echo "Скрипт управления AmneziaWG 2.0 (v${SCRIPT_VERSION})"
|
||||
echo "=============================================="
|
||||
|
|
@ -1368,7 +1414,7 @@ usage() {
|
|||
echo " -h, --help Показать эту справку"
|
||||
echo " -v, --verbose Расширенный вывод (для команды list)"
|
||||
echo " --no-color Отключить цветной вывод"
|
||||
echo " --json JSON-вывод (для команды stats)"
|
||||
echo " --json Машиночитаемый JSON-вывод (для команд list / show / stats)"
|
||||
echo " --expires=ВРЕМЯ Срок действия при add (1h, 12h, 1d, 7d, 30d, 4w)"
|
||||
echo " --conf-dir=ПУТЬ Указать директорию AWG (умолч: $AWG_DIR)"
|
||||
echo " --server-conf=ПУТЬ Указать файл конфига сервера"
|
||||
|
|
@ -1383,7 +1429,7 @@ usage() {
|
|||
echo "Команды:"
|
||||
echo " add <имя> [имя2 ...] Добавить клиента(ов). --expires применяется ко всем"
|
||||
echo " remove <имя> [имя2 ...] Удалить клиента(ов)"
|
||||
echo " list [-v] Показать список клиентов"
|
||||
echo " list [-v] [--json] Показать список клиентов (--json: машиночитаемый, с client_ipv6)"
|
||||
echo " stats [--json] Статистика трафика по клиентам"
|
||||
echo " regen [имя] Перегенерировать файлы клиента(ов)"
|
||||
echo " modify <имя> <пар> <зн> Изменить параметр клиента"
|
||||
|
|
@ -1397,15 +1443,18 @@ usage() {
|
|||
echo " (dkms autoinstall + modprobe + запуск awg-quick)"
|
||||
echo " help Показать эту справку"
|
||||
echo ""
|
||||
exit 1
|
||||
exit "$_rc"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# Основная логика
|
||||
# ==============================================================================
|
||||
|
||||
if [[ "$COMMAND" == "help" || -z "$COMMAND" ]]; then
|
||||
usage
|
||||
if [[ -z "$COMMAND" ]]; then
|
||||
usage 1
|
||||
fi
|
||||
if [[ "$COMMAND" == "help" ]]; then
|
||||
usage "$HELP_EXIT_RC"
|
||||
fi
|
||||
|
||||
check_dependencies || exit 1
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ fi
|
|||
# ==============================================================================
|
||||
# AmneziaWG 2.0 peer management script
|
||||
# Author: @bivlked
|
||||
# Version: 5.15.0
|
||||
# Date: 2026-06-01
|
||||
# Version: 5.15.1
|
||||
# Date: 2026-06-02
|
||||
# Repository: https://github.com/bivlked/amneziawg-installer
|
||||
# ==============================================================================
|
||||
|
||||
# --- Safe mode and Constants ---
|
||||
# shellcheck disable=SC2034
|
||||
SCRIPT_VERSION="5.15.0"
|
||||
SCRIPT_VERSION="5.15.1"
|
||||
set -o pipefail
|
||||
AWG_DIR="/root/awg"
|
||||
SERVER_CONF_FILE="/etc/amnezia/amneziawg/awg0.conf"
|
||||
|
|
@ -55,10 +55,11 @@ trap _manage_cleanup EXIT INT TERM
|
|||
|
||||
# --- Argument handling ---
|
||||
COMMAND=""
|
||||
HELP_EXIT_RC=0 # C1: 0 = explicit help (exit 0); set to 1 for usage errors
|
||||
ARGS=()
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help) COMMAND="help"; break ;;
|
||||
-h|--help) COMMAND="help"; HELP_EXIT_RC=0; break ;;
|
||||
-v|--verbose) VERBOSE_LIST=1; shift ;;
|
||||
--no-color) NO_COLOR=1; shift ;;
|
||||
--json) JSON_OUTPUT=1; shift ;;
|
||||
|
|
@ -69,7 +70,7 @@ while [[ $# -gt 0 ]]; do
|
|||
--psk) CLI_ADD_PSK=1; shift ;;
|
||||
--yes) CLI_YES=1; shift ;;
|
||||
--carrier=*) CLI_CARRIER="${1#*=}"; shift ;;
|
||||
--*) echo "Unknown option: $1" >&2; COMMAND="help"; break ;;
|
||||
--*) echo "Unknown option: $1" >&2; COMMAND="help"; HELP_EXIT_RC=1; break ;;
|
||||
*)
|
||||
if [[ -z "$COMMAND" ]]; then
|
||||
COMMAND=$1
|
||||
|
|
@ -97,9 +98,7 @@ log_msg() {
|
|||
local type="$1" msg="$2"
|
||||
local ts
|
||||
ts=$(date +'%F %T')
|
||||
local safe_msg
|
||||
safe_msg="${msg//%/%%}"
|
||||
local entry="[$ts] $type: $safe_msg"
|
||||
local entry="[$ts] $type: $msg"
|
||||
local color_start="" color_end=""
|
||||
|
||||
if [[ "$NO_COLOR" -eq 0 ]]; then
|
||||
|
|
@ -121,6 +120,11 @@ log_msg() {
|
|||
# important for CI/automation parsing: stdout = "data", stderr = "diagnostics").
|
||||
if [[ "$type" == "ERROR" || "$type" == "WARN" ]]; then
|
||||
printf "${color_start}%s${color_end}\n" "$entry" >&2
|
||||
elif [[ "${JSON_OUTPUT:-0}" -eq 1 ]]; then
|
||||
# weaq P2: in --json mode stdout must contain ONLY JSON (jq/automation).
|
||||
# Route INFO/DEBUG to stderr, otherwise list/show/stats --json print INFO
|
||||
# lines before the JSON and break parsing (confirmed on biHetzner).
|
||||
printf "${color_start}%s${color_end}\n" "$entry" >&2
|
||||
else
|
||||
printf "${color_start}%s${color_end}\n" "$entry"
|
||||
fi
|
||||
|
|
@ -384,7 +388,7 @@ _restore_do_rollback() {
|
|||
[[ -d "$_rtd/keys" ]] && cp -a "$_rtd/keys/"* "$KEYS_DIR/" 2>/dev/null
|
||||
[[ -f "$_rtd/server_private.key" ]] && cp -a "$_rtd/server_private.key" "$AWG_DIR/" 2>/dev/null
|
||||
[[ -f "$_rtd/server_public.key" ]] && cp -a "$_rtd/server_public.key" "$AWG_DIR/" 2>/dev/null
|
||||
[[ -d "$_rtd/expiry" ]] && cp -a "$_rtd/expiry"/* "${EXPIRY_DIR:-$AWG_DIR/expiry}/" 2>/dev/null
|
||||
[[ -d "$_rtd/expiry" ]] && { mkdir -p "${EXPIRY_DIR:-$AWG_DIR/expiry}"; cp -a "$_rtd/expiry"/* "${EXPIRY_DIR:-$AWG_DIR/expiry}/" 2>/dev/null; }
|
||||
[[ -f "$_rtd/awg-expiry" ]] && cp -a "$_rtd/awg-expiry" /etc/cron.d/awg-expiry 2>/dev/null
|
||||
rm -rf "$_rtd"
|
||||
|
||||
|
|
@ -585,6 +589,12 @@ restore_backup() {
|
|||
|
||||
if [[ -d "$td/clients" ]]; then
|
||||
log "Restoring client files..."
|
||||
# C11: clean replacement, not a merge. Remove stale client artifacts that
|
||||
# are absent from the backup (otherwise a client deleted since the backup
|
||||
# lingers as orphan .conf/.png/.vpnuri). Scope strictly to managed client
|
||||
# globs - never touch scripts, server keys, backups/, logs, .lock,
|
||||
# awgsetup_cfg.init.
|
||||
rm -f "$AWG_DIR"/*.conf "$AWG_DIR"/*.png "$AWG_DIR"/*.vpnuri 2>/dev/null || true
|
||||
if ! cp -a "$td/clients/"* "$AWG_DIR/"; then
|
||||
log_error "Error copying clients — restore aborted (triggering rollback)."
|
||||
return 1
|
||||
|
|
@ -599,6 +609,9 @@ restore_backup() {
|
|||
if [[ -d "$td/keys" ]]; then
|
||||
log "Restoring keys..."
|
||||
mkdir -p "$KEYS_DIR"
|
||||
# C11: remove stale client keys absent from the backup (server keys live
|
||||
# in AWG_DIR, not KEYS_DIR, so they are not affected).
|
||||
rm -f "$KEYS_DIR"/* 2>/dev/null || true
|
||||
if ! cp -a "$td/keys/"* "$KEYS_DIR/"; then
|
||||
log_error "Error copying keys — restore aborted (triggering rollback)."
|
||||
return 1
|
||||
|
|
@ -627,6 +640,11 @@ restore_backup() {
|
|||
if [[ -d "$td/expiry" ]]; then
|
||||
log "Restoring expiry data..."
|
||||
mkdir -p "${EXPIRY_DIR:-$AWG_DIR/expiry}"
|
||||
# C11: expiry is intentionally NOT pruned. Orphan stamps for nonexistent
|
||||
# clients are harmless (cron cleanup ignores them), and a prune here would
|
||||
# be unsafe: both the rm and the following cp are best-effort (|| true), so
|
||||
# a copy failure after the prune would silently leave expiry empty. The
|
||||
# client artifacts themselves are pruned above.
|
||||
cp -a "$td/expiry/"* "${EXPIRY_DIR:-$AWG_DIR/expiry}/" 2>/dev/null || true
|
||||
chmod 600 "${EXPIRY_DIR:-$AWG_DIR/expiry}"/* 2>/dev/null
|
||||
fi
|
||||
|
|
@ -690,17 +708,43 @@ modify_client() {
|
|||
return 1
|
||||
fi ;;
|
||||
Endpoint)
|
||||
# C5: beyond rejecting dangerous chars - positive host:port check.
|
||||
case "$value" in
|
||||
*$'\n'*|*$'\r'*|*\\*|*\"*|*\'*|"")
|
||||
*$'\n'*|*$'\r'*|*\\*|*\"*|*\'*|*' '*|*$'\t'*|"")
|
||||
log_error "Invalid Endpoint: '$value'"
|
||||
return 1 ;;
|
||||
esac ;;
|
||||
esac
|
||||
local _eh _ept
|
||||
if [[ "$value" == \[*\]:* ]]; then
|
||||
_eh="${value%]:*}"; _eh="${_eh#\[}" # IPv6 without brackets
|
||||
_ept="${value##*]:}"
|
||||
[[ "$_eh" =~ ^[0-9A-Fa-f:]+$ ]] || { log_error "Invalid Endpoint '$value': malformed IPv6 host"; return 1; }
|
||||
else
|
||||
_eh="${value%:*}"; _ept="${value##*:}"
|
||||
[[ "$_eh" =~ ^([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)*|[0-9]{1,3}(\.[0-9]{1,3}){3})$ ]] || { log_error "Invalid Endpoint '$value': expected host:port (FQDN / IPv4 / [IPv6])"; return 1; }
|
||||
fi
|
||||
{ [[ "$_ept" =~ ^[0-9]+$ ]] && [[ "$_ept" -ge 1 && "$_ept" -le 65535 ]]; } || { log_error "Invalid Endpoint '$value': port must be 1-65535"; return 1; }
|
||||
;;
|
||||
AllowedIPs)
|
||||
# C5: beyond rejecting dangerous chars - positive CIDR-list check.
|
||||
case "$value" in
|
||||
*$'\n'*|*$'\r'*|*\\*|*\"*|*\'*|"")
|
||||
log_error "Invalid AllowedIPs: '$value'"
|
||||
return 1 ;;
|
||||
esac ;;
|
||||
esac
|
||||
local _aip_tok _aip_ifs="$IFS"
|
||||
IFS=','
|
||||
for _aip_tok in $value; do
|
||||
_aip_tok="${_aip_tok//[[:space:]]/}"
|
||||
[[ -z "$_aip_tok" ]] && continue
|
||||
if ! [[ "$_aip_tok" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}(/[0-9]{1,2})?$ || "$_aip_tok" =~ ^[0-9A-Fa-f:]+(/[0-9]{1,3})?$ ]]; then
|
||||
IFS="$_aip_ifs"
|
||||
log_error "Invalid AllowedIPs '$value': '$_aip_tok' is not a CIDR (a.b.c.d/n or IPv6/n)"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
IFS="$_aip_ifs"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Lock before state checks (TOCTOU protection against concurrent remove)
|
||||
|
|
@ -1360,7 +1404,11 @@ stats_clients() {
|
|||
# ==============================================================================
|
||||
|
||||
usage() {
|
||||
exec >&2
|
||||
# C1: explicit help (rc=0) -> stdout + exit 0; usage error (rc!=0, default)
|
||||
# -> stderr + exit 1. Explicit-help callers pass 0, error callers omit the
|
||||
# argument (get 1).
|
||||
local _rc="${1:-1}"
|
||||
[[ "$_rc" -ne 0 ]] && exec >&2
|
||||
echo ""
|
||||
echo "AmneziaWG 2.0 management script (v${SCRIPT_VERSION})"
|
||||
echo "=============================================="
|
||||
|
|
@ -1370,7 +1418,7 @@ usage() {
|
|||
echo " -h, --help Show this help"
|
||||
echo " -v, --verbose Verbose output (for list command)"
|
||||
echo " --no-color Disable colored output"
|
||||
echo " --json JSON output (for stats command)"
|
||||
echo " --json Machine-readable JSON output (for list / show / stats)"
|
||||
echo " --expires=DURATION Expiry time for add (1h, 12h, 1d, 7d, 30d, 4w)"
|
||||
echo " --conf-dir=PATH Specify AWG directory (default: $AWG_DIR)"
|
||||
echo " --server-conf=PATH Specify server config file"
|
||||
|
|
@ -1385,7 +1433,7 @@ usage() {
|
|||
echo "Commands:"
|
||||
echo " add <name> [name2 ...] Add client(s). --expires applies to all"
|
||||
echo " remove <name> [name2 ...] Remove client(s)"
|
||||
echo " list [-v] List clients"
|
||||
echo " list [-v] [--json] List clients (--json: machine-readable, includes client_ipv6)"
|
||||
echo " stats [--json] Client traffic statistics"
|
||||
echo " regen [name] Regenerate client file(s)"
|
||||
echo " modify <name> <p> <v> Modify a client parameter"
|
||||
|
|
@ -1399,15 +1447,18 @@ usage() {
|
|||
echo " (dkms autoinstall + modprobe + start awg-quick)"
|
||||
echo " help Show this help"
|
||||
echo ""
|
||||
exit 1
|
||||
exit "$_rc"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# Main logic
|
||||
# ==============================================================================
|
||||
|
||||
if [[ "$COMMAND" == "help" || -z "$COMMAND" ]]; then
|
||||
usage
|
||||
if [[ -z "$COMMAND" ]]; then
|
||||
usage 1
|
||||
fi
|
||||
if [[ "$COMMAND" == "help" ]]; then
|
||||
usage "$HELP_EXIT_RC"
|
||||
fi
|
||||
|
||||
check_dependencies || exit 1
|
||||
|
|
|
|||
195
scripts/check-docs-consistency.sh
Normal file
195
scripts/check-docs-consistency.sh
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
#!/bin/bash
|
||||
# check-docs-consistency.sh - быстрые проверки согласованности документации
|
||||
#
|
||||
# Лёгкий аналог preflight для документации и метаданных: ничего не собирает,
|
||||
# не качает по сети, не гоняет bats. Только дешёвые детерминированные проверки,
|
||||
# которые ловят классы рассинхрона, проскакивающие мимо shellcheck/test
|
||||
# (битые внутренние ссылки, рассогласование версий, протухшая матрица ОС).
|
||||
#
|
||||
# Запускается локально и в CI (docs-check workflow), а также включён в
|
||||
# preflight-check.sh как один из шагов.
|
||||
#
|
||||
# Использование:
|
||||
# bash scripts/check-docs-consistency.sh
|
||||
#
|
||||
# Проверки:
|
||||
# 1. Внутренние markdown-ссылки (#anchor) резолвятся в этом же файле.
|
||||
# 2. CHANGELOG: у каждого version-heading есть reference-link; набор версий
|
||||
# в RU == EN; [Unreleased] присутствует в обоих.
|
||||
# 3. Version triple: README badge == SCRIPT_VERSION == верхний changelog
|
||||
# heading (RU и EN).
|
||||
# 4. Матрица ОС: Ubuntu 26.04 присутствует во всех заявленных местах.
|
||||
# 5. SECURITY/CONTRIBUTING не протухли (текущий minor в supported-таблице;
|
||||
# нет захардкоженного test-count baseline).
|
||||
|
||||
set -o pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
cd "$REPO_ROOT" || { echo "ОШИБКА: не удалось перейти в $REPO_ROOT" >&2; exit 2; }
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
declare -a RESULTS
|
||||
|
||||
_ok() { echo "PASS: $1"; RESULTS+=("PASS: $1"); PASS=$((PASS+1)); }
|
||||
_bad() { echo "FAIL: $1" >&2; RESULTS+=("FAIL: $1"); FAIL=$((FAIL+1)); }
|
||||
|
||||
# Файлы документации с внутренними якорями.
|
||||
DOC_FILES=(
|
||||
README.md README.en.md
|
||||
ADVANCED.md ADVANCED.en.md
|
||||
CHANGELOG.md CHANGELOG.en.md
|
||||
SECURITY.md CONTRIBUTING.md INSTALL_VPS.md
|
||||
docs/SIGNING_DESIGN.md docs/RELEASE_PROCESS.md
|
||||
)
|
||||
|
||||
echo "=== check-docs-consistency ==="
|
||||
|
||||
# GitHub-совместимая slug-генерация из текста заголовка:
|
||||
# lowercase, убрать всё кроме букв/цифр/пробела/дефиса, пробелы -> дефисы,
|
||||
# срезать ведущие/хвостовые дефисы (их даёт, например, emoji в начале heading -
|
||||
# GitHub их тоже не оставляет).
|
||||
_slug() {
|
||||
local s="$1"
|
||||
s="${s#"${s%%[![:space:]]*}"}" # ltrim
|
||||
s="${s%"${s##*[![:space:]]}"}" # rtrim
|
||||
printf '%s' "$s" \
|
||||
| tr '[:upper:]' '[:lower:]' \
|
||||
| LC_ALL=C sed -E 's/[^a-z0-9 _-]//g' \
|
||||
| tr ' ' '-' \
|
||||
| sed -E 's/^-+//; s/-+$//'
|
||||
}
|
||||
|
||||
# Удалить из markdown код, чтобы примеры разметки внутри него не парсились как
|
||||
# реальные ссылки/якоря: fenced-блоки (``` ... ```) целиком + inline-спаны
|
||||
# (`...`). Иначе документированный в backticks пример вида `](#anchor)` дал бы
|
||||
# ложный FAIL.
|
||||
_strip_code() {
|
||||
awk '/^```/ { infence = !infence; next } !infence { print }' "$1" \
|
||||
| sed 's/`[^`]*`//g'
|
||||
}
|
||||
|
||||
# --- 1. Внутренние anchor-ссылки резолвятся ---
|
||||
anchor_fail=0
|
||||
for f in "${DOC_FILES[@]}"; do
|
||||
[[ -f "$f" ]] || continue
|
||||
|
||||
# Анализируем файл с вырезанным кодом (примеры в backticks - не разметка).
|
||||
stripped="$(_strip_code "$f")"
|
||||
|
||||
# Целевые якоря в файле: явные <a id=..> / <a name=..> + slug'и заголовков.
|
||||
declare -A anchors=()
|
||||
while IFS= read -r a; do
|
||||
[[ -n "$a" ]] && anchors["$a"]=1
|
||||
done < <(printf '%s\n' "$stripped" | grep -oiP '<a\s+(id|name)="\K[^"]+')
|
||||
while IFS= read -r h; do
|
||||
# Заголовки ATX: "# ...". Текст уже без ведущих #, slug'им.
|
||||
sl="$(_slug "$h")"
|
||||
[[ -n "$sl" ]] && anchors["$sl"]=1
|
||||
done < <(printf '%s\n' "$stripped" | grep -E '^#{1,6}[[:space:]]' | sed -E 's/^#{1,6}[[:space:]]+//')
|
||||
|
||||
# Внутренние ссылки вида ](#anchor) в этом файле.
|
||||
while IFS= read -r ref; do
|
||||
[[ -z "$ref" ]] && continue
|
||||
if [[ -z "${anchors[$ref]:-}" ]]; then
|
||||
echo " $f: битая внутренняя ссылка #$ref" >&2
|
||||
anchor_fail=1
|
||||
fi
|
||||
done < <(printf '%s\n' "$stripped" | grep -oP '\]\(#\K[^)]+')
|
||||
|
||||
unset anchors
|
||||
done
|
||||
if [[ "$anchor_fail" -eq 0 ]]; then _ok "внутренние anchor-ссылки резолвятся"; else _bad "битые внутренние anchor-ссылки"; fi
|
||||
|
||||
# --- 2. CHANGELOG: heading <-> reference-link, RU == EN ---
|
||||
changelog_fail=0
|
||||
_changelog_versions() { # печатает версии из "## [X]" headings
|
||||
grep -oP '^##\s+\[\K[^]]+' "$1"
|
||||
}
|
||||
_changelog_refs() { # печатает версии из "[X]:" reference-links
|
||||
grep -oP '^\[\K[^]]+(?=\]:)' "$1"
|
||||
}
|
||||
for f in CHANGELOG.md CHANGELOG.en.md; do
|
||||
[[ -f "$f" ]] || { _bad "нет $f"; changelog_fail=1; continue; }
|
||||
while IFS= read -r v; do
|
||||
[[ -z "$v" ]] && continue
|
||||
if ! _changelog_refs "$f" | grep -qxF "$v"; then
|
||||
echo " $f: heading [$v] без reference-link [$v]:" >&2
|
||||
changelog_fail=1
|
||||
fi
|
||||
done < <(_changelog_versions "$f")
|
||||
if ! _changelog_versions "$f" | grep -qxF "Unreleased"; then
|
||||
echo " $f: нет heading [Unreleased]" >&2
|
||||
changelog_fail=1
|
||||
fi
|
||||
done
|
||||
# RU == EN набор версий (отсортированные уникальные heading-списки).
|
||||
if [[ -f CHANGELOG.md && -f CHANGELOG.en.md ]]; then
|
||||
ru_set="$(_changelog_versions CHANGELOG.md | sort -u)"
|
||||
en_set="$(_changelog_versions CHANGELOG.en.md | sort -u)"
|
||||
if [[ "$ru_set" != "$en_set" ]]; then
|
||||
echo " набор версий CHANGELOG.md != CHANGELOG.en.md:" >&2
|
||||
diff <(printf '%s\n' "$ru_set") <(printf '%s\n' "$en_set") >&2 || true
|
||||
changelog_fail=1
|
||||
fi
|
||||
fi
|
||||
if [[ "$changelog_fail" -eq 0 ]]; then _ok "CHANGELOG headings/refs согласованы, RU == EN"; else _bad "CHANGELOG рассинхрон"; fi
|
||||
|
||||
# --- 3. Version triple: badge == SCRIPT_VERSION == верхний changelog heading ---
|
||||
ver_fail=0
|
||||
script_ver="$(awk -F'"' '/^SCRIPT_VERSION=/{print $2; exit}' install_amneziawg.sh)"
|
||||
# Верхний non-Unreleased heading в каждом changelog.
|
||||
top_ru="$(grep -oP '^##\s+\[\K[0-9]+\.[0-9]+\.[0-9]+' CHANGELOG.md | head -n1)"
|
||||
top_en="$(grep -oP '^##\s+\[\K[0-9]+\.[0-9]+\.[0-9]+' CHANGELOG.en.md | head -n1)"
|
||||
for pair in "README.md:$script_ver" "README.en.md:$script_ver"; do
|
||||
rf="${pair%%:*}"; expect="${pair##*:}"
|
||||
badge="$(grep -oP 'Installer_Version-\K[0-9]+\.[0-9]+\.[0-9]+' "$rf" | head -n1)"
|
||||
if [[ "$badge" != "$expect" ]]; then
|
||||
echo " $rf badge='$badge' != SCRIPT_VERSION='$expect'" >&2
|
||||
ver_fail=1
|
||||
fi
|
||||
done
|
||||
if [[ "$top_ru" != "$script_ver" ]]; then echo " CHANGELOG.md top heading '$top_ru' != SCRIPT_VERSION '$script_ver'" >&2; ver_fail=1; fi
|
||||
if [[ "$top_en" != "$script_ver" ]]; then echo " CHANGELOG.en.md top heading '$top_en' != SCRIPT_VERSION '$script_ver'" >&2; ver_fail=1; fi
|
||||
if [[ "$ver_fail" -eq 0 ]]; then _ok "version triple согласован ($script_ver)"; else _bad "version triple рассинхрон"; fi
|
||||
|
||||
# --- 4. Матрица ОС: Ubuntu 26.04 во всех заявленных местах ---
|
||||
os_fail=0
|
||||
for f in README.md README.en.md install_amneziawg.sh install_amneziawg_en.sh .github/ISSUE_TEMPLATE/bug_report.yml; do
|
||||
[[ -f "$f" ]] || { echo " нет $f (проверка 26.04)" >&2; os_fail=1; continue; }
|
||||
if ! grep -q '26\.04' "$f"; then
|
||||
echo " $f: нет упоминания Ubuntu 26.04 в матрице ОС" >&2
|
||||
os_fail=1
|
||||
fi
|
||||
done
|
||||
if [[ "$os_fail" -eq 0 ]]; then _ok "Ubuntu 26.04 присутствует в матрице ОС"; else _bad "Ubuntu 26.04 отсутствует где-то в матрице ОС"; fi
|
||||
|
||||
# --- 5. SECURITY/CONTRIBUTING не протухли ---
|
||||
stale_fail=0
|
||||
# Текущий minor (X.Y) должен фигурировать в SECURITY supported-таблице.
|
||||
minor="$(printf '%s' "$script_ver" | grep -oP '^[0-9]+\.[0-9]+')"
|
||||
if [[ -f SECURITY.md ]]; then
|
||||
if ! grep -qE "${minor//./\\.}\.[x0-9]" SECURITY.md; then
|
||||
echo " SECURITY.md: текущий minor $minor.x не найден в supported-таблице" >&2
|
||||
stale_fail=1
|
||||
fi
|
||||
else
|
||||
echo " нет SECURITY.md" >&2; stale_fail=1
|
||||
fi
|
||||
# CONTRIBUTING не должен хардкодить число тестов (хрупкий baseline).
|
||||
if [[ -f CONTRIBUTING.md ]]; then
|
||||
if grep -qiP '\b[0-9]{3,}\s+tests?\b' CONTRIBUTING.md; then
|
||||
echo " CONTRIBUTING.md: захардкоженный счётчик тестов (хрупкий baseline)" >&2
|
||||
stale_fail=1
|
||||
fi
|
||||
fi
|
||||
if [[ "$stale_fail" -eq 0 ]]; then _ok "SECURITY/CONTRIBUTING не протухли"; else _bad "SECURITY/CONTRIBUTING протухли"; fi
|
||||
|
||||
# --- Summary ---
|
||||
echo ""
|
||||
echo "=== docs-consistency summary: $PASS passed, $FAIL failed ==="
|
||||
for r in "${RESULTS[@]}"; do echo " $r"; done
|
||||
|
||||
[[ "$FAIL" -gt 0 ]] && exit 1
|
||||
exit 0
|
||||
|
|
@ -1,16 +1,19 @@
|
|||
#!/bin/bash
|
||||
# preflight-check.sh - единый прогон всех pre-tag проверок перед релизом
|
||||
#
|
||||
# Запускает в одной команде полный pre-tag чеклист (см. CLAUDE.md "Release
|
||||
# Process" и memory feedback_awg20_release_checklist). Каждая проверка печатает
|
||||
# PASS/FAIL, в конце - сводка. Exit!=0 если хоть одна провалилась.
|
||||
# Запускает в одной команде полный pre-tag чеклист (см. docs/RELEASE_PROCESS.md).
|
||||
# Каждая проверка печатает PASS/FAIL, в конце - сводка. Exit!=0 если хоть одна
|
||||
# провалилась.
|
||||
#
|
||||
# Использование:
|
||||
# bash scripts/preflight-check.sh
|
||||
# BASE_REF=origin/main bash scripts/preflight-check.sh # detached checkout
|
||||
#
|
||||
# Переменные окружения:
|
||||
# BASE_REF git-ref для diff-проверок пунктуации/маркеров. Default: main.
|
||||
# LOG_RANGE git-диапазон для проверки commit-сообщений. Default: main..HEAD
|
||||
# BASE_REF git-ref для diff-проверок пунктуации/маркеров. Если не задан,
|
||||
# берётся первый существующий из: main, origin/main. На detached
|
||||
# checkout без локального main задавайте явно BASE_REF=origin/main.
|
||||
# LOG_RANGE git-диапазон для проверки commit-сообщений. Default: <base>..HEAD
|
||||
# (только коммиты ветки релиза; merged-from-main коммиты с legacy
|
||||
# human/bot Co-authored-by трейлерами в проверку НЕ попадают).
|
||||
#
|
||||
|
|
@ -18,11 +21,12 @@
|
|||
# 1. bash -n на 6 скриптах
|
||||
# 2. shellcheck -s bash -S warning на 6 скриптах
|
||||
# 3. bats tests/
|
||||
# 4. em/en-dash (U+2013/U+2014) в diff BASE_REF...HEAD = 0
|
||||
# 4. реально добавленных em/en-dash (U+2013/U+2014) в diff BASE_REF...HEAD = 0
|
||||
# 5. AI/tool-mention в diff + commit-логе = 0
|
||||
# 6. Co-authored-by в commit-логе = 0
|
||||
# 7. SCRIPT_VERSION консистентен в 4 версионированных скриптах
|
||||
# 8. SHA-пины синхронны (update-sha-pins.sh --verify)
|
||||
# 9. Согласованность документации (check-docs-consistency.sh)
|
||||
|
||||
set -o pipefail
|
||||
|
||||
|
|
@ -30,8 +34,23 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
cd "$REPO_ROOT" || { echo "ОШИБКА: не удалось перейти в $REPO_ROOT" >&2; exit 2; }
|
||||
|
||||
BASE_REF="${BASE_REF:-main}"
|
||||
LOG_RANGE="${LOG_RANGE:-main..HEAD}"
|
||||
# BASE_REF fallback: уважаем явно заданный, иначе первый резолвящийся из
|
||||
# main / origin/main. Это переживает detached checkout (CI на теге), где
|
||||
# локального main нет, но origin/main подтянут (fetch-depth:0 + fetch main).
|
||||
if [[ -z "${BASE_REF:-}" ]]; then
|
||||
for _cand in main origin/main; do
|
||||
if git rev-parse --verify "$_cand" >/dev/null 2>&1; then
|
||||
BASE_REF="$_cand"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if [[ -z "${BASE_REF:-}" ]]; then
|
||||
echo "ОШИБКА: не найден base-ref для diff-проверок (пробовал main, origin/main)." >&2
|
||||
echo " Задайте явно, например: BASE_REF=origin/main bash scripts/preflight-check.sh" >&2
|
||||
exit 2
|
||||
fi
|
||||
LOG_RANGE="${LOG_RANGE:-${BASE_REF}..HEAD}"
|
||||
|
||||
SCRIPTS=(
|
||||
install_amneziawg.sh
|
||||
|
|
@ -97,20 +116,23 @@ else
|
|||
_bad "bats not found in PATH"
|
||||
fi
|
||||
|
||||
# --- 4. em/en-dash in diff ---
|
||||
if git rev-parse --verify "$BASE_REF" >/dev/null 2>&1; then
|
||||
# Байтовые паттерны UTF-8: en-dash U+2013 = E2 80 93, em-dash U+2014 = E2 80 94.
|
||||
# LC_ALL=C + \xHH работает на GNU grep 3.0 (Git Bash), в отличие от \x{2013}
|
||||
# (codepoint-форма падает "character value in \x{} too large" и глоталась || true).
|
||||
dash_hits=$(git diff "${BASE_REF}...HEAD" | LC_ALL=C grep -nP '^\+.*(\xe2\x80\x93|\xe2\x80\x94)' || true)
|
||||
if [[ -z "$dash_hits" ]]; then
|
||||
_ok "no em/en-dash in diff ${BASE_REF}...HEAD"
|
||||
else
|
||||
echo "$dash_hits" >&2
|
||||
_bad "em/en-dash found in diff ${BASE_REF}...HEAD"
|
||||
fi
|
||||
# --- 4. newly added em/en-dash in diff ---
|
||||
# Политика проекта (CLAUDE.md): новый текст - только hyphen-minus; legacy
|
||||
# em/en-dash в существующих строках сохраняется без mass-purge, а point-edit
|
||||
# легаси-строки НЕ обязывает переписывать тире на этой строке. Поэтому ловим
|
||||
# ТОЛЬКО реально добавленные тире. word-diff=porcelain помечает префиксом '+'
|
||||
# лишь изменённые слова, так что тире в неизменённой части правленой строки в
|
||||
# '+' не попадает (legacy не даёт ложный FAIL); под флаг идёт только заново
|
||||
# внесённый символ тире (новая строка/слово). Байты UTF-8: en-dash U+2013 =
|
||||
# E2 80 93, em-dash U+2014 = E2 80 94 (LC_ALL=C + \xHH, codepoint-форма \x{}
|
||||
# падает на GNU grep Git Bash).
|
||||
dash_added=$(git diff --word-diff=porcelain "${BASE_REF}...HEAD" \
|
||||
| LC_ALL=C grep -nP '^\+.*(\xe2\x80\x93|\xe2\x80\x94)' || true)
|
||||
if [[ -z "$dash_added" ]]; then
|
||||
_ok "no newly added em/en-dash in diff ${BASE_REF}...HEAD"
|
||||
else
|
||||
_bad "BASE_REF '$BASE_REF' not found (set BASE_REF=<ref>)"
|
||||
echo "$dash_added" >&2
|
||||
_bad "newly added em/en-dash in diff ${BASE_REF}...HEAD"
|
||||
fi
|
||||
|
||||
# --- 5. AI/tool-mention in diff + commit log ---
|
||||
|
|
@ -172,6 +194,16 @@ else
|
|||
_bad "SHA pins out of sync (run: bash scripts/update-sha-pins.sh)"
|
||||
fi
|
||||
|
||||
# --- 9. Docs consistency ---
|
||||
# Тот же скрипт гоняет docs-check workflow. Здесь - часть единого pre-tag gate.
|
||||
if bash "$SCRIPT_DIR/check-docs-consistency.sh" >/tmp/preflight-docs.$$ 2>&1; then
|
||||
_ok "docs consistency (check-docs-consistency.sh)"
|
||||
else
|
||||
cat /tmp/preflight-docs.$$ >&2
|
||||
_bad "docs consistency (run: bash scripts/check-docs-consistency.sh)"
|
||||
fi
|
||||
rm -f /tmp/preflight-docs.$$
|
||||
|
||||
# --- Summary ---
|
||||
echo ""
|
||||
echo "=== preflight summary: $PASS passed, $FAIL failed ==="
|
||||
|
|
|
|||
115
tests/test_v5151_audit_fixes.bats
Normal file
115
tests/test_v5151_audit_fixes.bats
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
#!/usr/bin/env bats
|
||||
# v5.15.1 audit fixes - regression locks for:
|
||||
# C8 / weaq P1: detect_native_ipv6 must require a globally-routable (non-ULA)
|
||||
# address AND a default IPv6 route (no false-positive -> no ::/0
|
||||
# black-hole).
|
||||
# E / C13: install --help OS line lists 26.04; --subnet help states /24.
|
||||
# C12: log_msg must not double percent signs.
|
||||
# weaq P2: log_msg must route INFO/DEBUG to stderr when JSON_OUTPUT=1.
|
||||
#
|
||||
# shellcheck disable=SC2034 # mock env vars (NO_COLOR/VERBOSE_LIST/LOG_FILE/JSON_OUTPUT) are consumed by the sourced functions
|
||||
# shellcheck disable=SC2154 # $stderr is set by bats `run --separate-stderr`
|
||||
|
||||
bats_require_minimum_version 1.5.0
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# detect_native_ipv6 (extracted from install_amneziawg.sh, `ip` mocked)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
setup_detect() {
|
||||
local installer="${BATS_TEST_DIRNAME}/../install_amneziawg.sh"
|
||||
# shellcheck source=/dev/null
|
||||
source <(sed -n '/^detect_native_ipv6() {$/,/^}$/p' "$installer")
|
||||
ip() {
|
||||
case "$*" in
|
||||
*"addr show scope global"*) printf '%s\n' "${MOCK_IP6_ADDR:-}" ;;
|
||||
*"route show default"*) printf '%s\n' "${MOCK_IP6_ROUTE:-}" ;;
|
||||
*) : ;;
|
||||
esac
|
||||
}
|
||||
export -f ip
|
||||
}
|
||||
|
||||
@test "v5.15.1 C8: native = global non-ULA address + default route -> 1" {
|
||||
setup_detect
|
||||
MOCK_IP6_ADDR=" inet6 2a01:4f8:c17:abcd::1/64 scope global"
|
||||
MOCK_IP6_ROUTE="default via fe80::1 dev eth0 proto ra"
|
||||
run detect_native_ipv6
|
||||
[ "$output" = "1" ]
|
||||
}
|
||||
|
||||
@test "v5.15.1 C8: ULA-only global address (fd..) + default route -> 0" {
|
||||
setup_detect
|
||||
MOCK_IP6_ADDR=" inet6 fddd:2c4:2c4:2c4::1/64 scope global"
|
||||
MOCK_IP6_ROUTE="default via fe80::1 dev eth0"
|
||||
run detect_native_ipv6
|
||||
[ "$output" = "0" ]
|
||||
}
|
||||
|
||||
@test "v5.15.1 C8: global address but NO default route -> 0 (avoids black-hole)" {
|
||||
setup_detect
|
||||
MOCK_IP6_ADDR=" inet6 2a01:4f8:c17:abcd::1/64 scope global"
|
||||
MOCK_IP6_ROUTE=""
|
||||
run detect_native_ipv6
|
||||
[ "$output" = "0" ]
|
||||
}
|
||||
|
||||
@test "v5.15.1 C8: no IPv6 at all -> 0" {
|
||||
setup_detect
|
||||
MOCK_IP6_ADDR=""
|
||||
MOCK_IP6_ROUTE=""
|
||||
run detect_native_ipv6
|
||||
[ "$output" = "0" ]
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# install --help text (E: 26.04, C13: /24) - source-level assertions
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@test "v5.15.1 E: RU installer help OS line includes 26.04" {
|
||||
grep -q "Ubuntu (24.04 / 25.10 / 26.04)" "${BATS_TEST_DIRNAME}/../install_amneziawg.sh"
|
||||
}
|
||||
|
||||
@test "v5.15.1 E: EN installer help OS line includes 26.04" {
|
||||
grep -q "Ubuntu (24.04 / 25.10 / 26.04)" "${BATS_TEST_DIRNAME}/../install_amneziawg_en.sh"
|
||||
}
|
||||
|
||||
@test "v5.15.1 C13: RU installer --subnet help states /24 only" {
|
||||
grep -qP 'subnet.*/24' "${BATS_TEST_DIRNAME}/../install_amneziawg.sh"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# log_msg (extracted from manage_amneziawg.sh) - C12 + weaq P2
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
setup_logmsg() {
|
||||
NO_COLOR=1
|
||||
VERBOSE_LIST=0
|
||||
LOG_FILE="${BATS_TEST_TMPDIR:-/tmp}/awg_test_$$.log"
|
||||
JSON_OUTPUT=0
|
||||
local mgr="${BATS_TEST_DIRNAME}/../manage_amneziawg.sh"
|
||||
# shellcheck source=/dev/null
|
||||
source <(sed -n '/^log_msg() {$/,/^}$/p' "$mgr")
|
||||
}
|
||||
|
||||
@test "v5.15.1 C12: log_msg does not double percent signs" {
|
||||
setup_logmsg
|
||||
run log_msg INFO "disk is 95% full"
|
||||
[[ "$output" == *"95% full"* ]]
|
||||
[[ "$output" != *"95%%"* ]]
|
||||
}
|
||||
|
||||
@test "v5.15.1 weaq P2: log_msg INFO goes to stderr (empty stdout) when JSON_OUTPUT=1" {
|
||||
setup_logmsg
|
||||
JSON_OUTPUT=1
|
||||
run --separate-stderr log_msg INFO "informational line"
|
||||
[ -z "$output" ]
|
||||
[[ "$stderr" == *"informational line"* ]]
|
||||
}
|
||||
|
||||
@test "v5.15.1 weaq P2: log_msg INFO goes to stdout when JSON_OUTPUT=0" {
|
||||
setup_logmsg
|
||||
JSON_OUTPUT=0
|
||||
run log_msg INFO "informational line"
|
||||
[[ "$output" == *"informational line"* ]]
|
||||
}
|
||||
63
tests/test_v5151_ipv6_split_tunnel_option_c.bats
Normal file
63
tests/test_v5151_ipv6_split_tunnel_option_c.bats
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
#!/usr/bin/env bats
|
||||
# v5.15.1 (audit C7) - intent-mirroring of the IPv4 routing mode into IPv6.
|
||||
#
|
||||
# When the client uses a SPLIT tunnel (custom ALLOWED_IPS, not 0.0.0.0/0),
|
||||
# render_client_config must keep the IPv4 split list AS-IS and append ONLY the
|
||||
# tunnel ULA - never ::/0. There is no IPv6 split-list, so routing all IPv6 into
|
||||
# the tunnel would silently break the user's split-tunnel intent.
|
||||
#
|
||||
# create_init_config seeds a split ALLOWED_IPS ('0.0.0.0/5, 8.0.0.0/7'), so these
|
||||
# tests exercise the split path directly without overriding it.
|
||||
|
||||
load test_helper
|
||||
|
||||
setup_split_native() {
|
||||
create_server_config
|
||||
create_init_config # ALLOWED_IPS='0.0.0.0/5, 8.0.0.0/7' (split)
|
||||
cat >> "$CONFIG_FILE" << 'CONF'
|
||||
export ALLOW_IPV6_TUNNEL=1
|
||||
export IPV6_SUBNET='fddd:2c4:2c4:2c4::/64'
|
||||
export SERVER_HAS_NATIVE_IPV6=1
|
||||
CONF
|
||||
safe_load_config "$CONFIG_FILE"
|
||||
}
|
||||
|
||||
setup_split_no_native() {
|
||||
create_server_config
|
||||
create_init_config
|
||||
cat >> "$CONFIG_FILE" << 'CONF'
|
||||
export ALLOW_IPV6_TUNNEL=1
|
||||
export IPV6_SUBNET='fddd:2c4:2c4:2c4::/64'
|
||||
export SERVER_HAS_NATIVE_IPV6=0
|
||||
CONF
|
||||
safe_load_config "$CONFIG_FILE"
|
||||
}
|
||||
|
||||
@test "v5.15.1 C7: split tunnel + native IPv6 keeps IPv4 split and appends only the ULA" {
|
||||
setup_split_native
|
||||
render_client_config "splitnat" "10.9.9.20" "FAKEPRIVKEY" "FAKEPUBKEY" "1.2.3.4" "39743" "fddd:2c4:2c4:2c4::20"
|
||||
local conf="$AWG_DIR/splitnat.conf"
|
||||
grep -q "AllowedIPs = 0.0.0.0/5, 8.0.0.0/7, fddd:2c4:2c4:2c4::/64" "$conf"
|
||||
}
|
||||
|
||||
@test "v5.15.1 C7: split tunnel + native IPv6 does NOT add ::/0" {
|
||||
setup_split_native
|
||||
render_client_config "splitnat2" "10.9.9.21" "FAKEPRIVKEY" "FAKEPUBKEY" "1.2.3.4" "39743" "fddd:2c4:2c4:2c4::21"
|
||||
local conf="$AWG_DIR/splitnat2.conf"
|
||||
! grep -q "::/0" "$conf"
|
||||
}
|
||||
|
||||
@test "v5.15.1 C7: split tunnel + no native IPv6 keeps IPv4 split and appends only the ULA" {
|
||||
setup_split_no_native
|
||||
render_client_config "splitno" "10.9.9.22" "FAKEPRIVKEY" "FAKEPUBKEY" "1.2.3.4" "39743" "fddd:2c4:2c4:2c4::22"
|
||||
local conf="$AWG_DIR/splitno.conf"
|
||||
grep -q "AllowedIPs = 0.0.0.0/5, 8.0.0.0/7, fddd:2c4:2c4:2c4::/64" "$conf"
|
||||
}
|
||||
|
||||
@test "v5.15.1 C7: split tunnel preserves the IPv4 split (not collapsed to 0.0.0.0/0)" {
|
||||
setup_split_native
|
||||
render_client_config "splitnat3" "10.9.9.23" "FAKEPRIVKEY" "FAKEPUBKEY" "1.2.3.4" "39743" "fddd:2c4:2c4:2c4::23"
|
||||
local conf="$AWG_DIR/splitnat3.conf"
|
||||
grep -qP '^AllowedIPs = 0\.0\.0\.0/5, 8\.0\.0\.0/7,' "$conf"
|
||||
! grep -qP '^AllowedIPs = 0\.0\.0\.0/0' "$conf"
|
||||
}
|
||||
|
|
@ -8,7 +8,11 @@ load test_helper
|
|||
setup_no_native() {
|
||||
create_server_config
|
||||
create_init_config
|
||||
# "route all" (full tunnel) tests: force ALLOWED_IPS=0.0.0.0/0 (overrides the
|
||||
# split default from create_init_config; safe_load_config is last-wins). With
|
||||
# no native IPv6 a full tunnel maps to the tunnel ULA, never ::/0.
|
||||
cat >> "$CONFIG_FILE" << 'CONF'
|
||||
export ALLOWED_IPS='0.0.0.0/0'
|
||||
export ALLOW_IPV6_TUNNEL=1
|
||||
export IPV6_SUBNET='fddd:2c4:2c4:2c4::/64'
|
||||
export SERVER_HAS_NATIVE_IPV6=0
|
||||
|
|
|
|||
|
|
@ -8,8 +8,12 @@ load test_helper
|
|||
setup_common() {
|
||||
create_server_config
|
||||
create_init_config
|
||||
# Append IPv6 keys to the init config
|
||||
# Append IPv6 keys to the init config. These are the "route all" (full
|
||||
# tunnel) tests, so force ALLOWED_IPS=0.0.0.0/0 (overrides the split default
|
||||
# from create_init_config; safe_load_config is last-wins). Under the
|
||||
# intent-mirroring rule a full IPv4 tunnel maps to ::/0 on native IPv6.
|
||||
cat >> "$CONFIG_FILE" << 'CONF'
|
||||
export ALLOWED_IPS='0.0.0.0/0'
|
||||
export ALLOW_IPV6_TUNNEL=1
|
||||
export IPV6_SUBNET='fddd:2c4:2c4:2c4::/64'
|
||||
export SERVER_HAS_NATIVE_IPV6=1
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env bats
|
||||
# v5.15.0 SHA256 pin lockstep test.
|
||||
# SHA256 pin lockstep test (5.15.x line).
|
||||
#
|
||||
# The installers (install_amneziawg.sh / install_amneziawg_en.sh) download the
|
||||
# helper scripts over the network and verify them against hardcoded pins
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
# ---------- RU installer pins ----------
|
||||
|
||||
@test "v5.15.0: RU installer COMMON pin matches awg_common.sh" {
|
||||
@test "RU installer COMMON pin matches awg_common.sh" {
|
||||
actual=$(sha256sum "$BATS_TEST_DIRNAME/../awg_common.sh" | cut -d' ' -f1)
|
||||
pinned=$(grep -oP 'COMMON_SCRIPT_SHA256="\K[0-9a-f]{64}' "$BATS_TEST_DIRNAME/../install_amneziawg.sh")
|
||||
[ "$actual" = "$pinned" ] || {
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
@test "v5.15.0: RU installer MANAGE pin matches manage_amneziawg.sh" {
|
||||
@test "RU installer MANAGE pin matches manage_amneziawg.sh" {
|
||||
actual=$(sha256sum "$BATS_TEST_DIRNAME/../manage_amneziawg.sh" | cut -d' ' -f1)
|
||||
pinned=$(grep -oP 'MANAGE_SCRIPT_SHA256="\K[0-9a-f]{64}' "$BATS_TEST_DIRNAME/../install_amneziawg.sh")
|
||||
[ "$actual" = "$pinned" ] || {
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
# ---------- EN installer pins ----------
|
||||
|
||||
@test "v5.15.0: EN installer COMMON pin matches awg_common_en.sh" {
|
||||
@test "EN installer COMMON pin matches awg_common_en.sh" {
|
||||
actual=$(sha256sum "$BATS_TEST_DIRNAME/../awg_common_en.sh" | cut -d' ' -f1)
|
||||
pinned=$(grep -oP 'COMMON_SCRIPT_SHA256="\K[0-9a-f]{64}' "$BATS_TEST_DIRNAME/../install_amneziawg_en.sh")
|
||||
[ "$actual" = "$pinned" ] || {
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
@test "v5.15.0: EN installer MANAGE pin matches manage_amneziawg_en.sh" {
|
||||
@test "EN installer MANAGE pin matches manage_amneziawg_en.sh" {
|
||||
actual=$(sha256sum "$BATS_TEST_DIRNAME/../manage_amneziawg_en.sh" | cut -d' ' -f1)
|
||||
pinned=$(grep -oP 'MANAGE_SCRIPT_SHA256="\K[0-9a-f]{64}' "$BATS_TEST_DIRNAME/../install_amneziawg_en.sh")
|
||||
[ "$actual" = "$pinned" ] || {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue