mirror of
https://github.com/bivlked/amneziawg-installer.git
synced 2026-10-19 05:03:18 +00:00
release: v5.30.0 (#248)
Install and update commands move to releases/latest/download, so a copied line no longer goes stale with the next release and serves a signed asset. Four pins stay on purpose and the docs guard now explains which and why. A guard against oversized I1-I5, which make the interface dump loop forever upstream. Adding it exposed the same mistake in four neighbouring places: the guard sets a flag and the code after it kept building positive claims from the empty string left behind. diagnose said "I1=absent" at the moment the guard fired because I1 was oversized, and with a carrier profile turned that into a green tick; list, given a timeout in this same branch but no failure handling, turned a visible hang into a complete and entirely wrong table. All eighteen interface calls across the four scripts are bounded now, timeouts are named separately from other failures, and the honest states survive. The size parser no longer returns zero with a success status for input it did not parse, since zero means "no masking packets" and switches the protection off. ADVANCED gains a client generation matrix answering which generation a client speaks, with the weaker rows marked as inferences. Nine claims that did not match the code or the world were corrected.
This commit is contained in:
parent
c9ce1c77a6
commit
013d3c012e
21 changed files with 1354 additions and 123 deletions
|
|
@ -281,8 +281,8 @@ Since v5.25.0 the management scripts track this and **warn**: they remember the
|
|||
|
||||
None of the 3.0 features appear in generated configs, and that is deliberate:
|
||||
|
||||
- **`HeaderProtectionKey`** needs every one of your clients to understand it at the same time. As of this release `amneziawg-android` 3.0.1 is still a prerelease and the newest `amneziawg-windows-client` release is 2.0.2. Turning it on now would cut off some devices without warning.
|
||||
- **`ContentPaddingAddition`** waits on an upstream fix: right now the padding breaks keepalive detection and those packets are dropped with `Packet is neither ipv4 nor ipv6` ([issue #186](https://github.com/amnezia-vpn/amneziawg-linux-kernel-module/issues/186)).
|
||||
- **`HeaderProtectionKey`** needs every one of your clients to understand it at the same time, and that requirement bites harder than it reads: the parameter is two-sided and fails silently on a mismatch - the handshake simply never happens and nothing reports an error. Where the clients stand on 31 August 2026: `amneziawg-windows-client` has a 3.1.0 release (21 August), `amneziawg-android` has a full `v3.1.20260814` release (14 August; the separate 3.0.1 from 24 July stayed a prerelease), and the light client on Google Play has not been updated since 12 June. While some of your devices update from a store, turning the parameter on would cut them off without warning.
|
||||
- **`ContentPaddingAddition`** is left out for a historical reason that no longer applies. The padding broke keepalive detection, so an idle tunnel redid its handshake every 15 seconds instead of roughly 147. The defect is analysed in [issue #186](https://github.com/amnezia-vpn/amneziawg-linux-kernel-module/issues/186) and was fixed upstream on 5 August 2026 ([PR #208](https://github.com/amnezia-vpn/amneziawg-linux-kernel-module/pull/208)); the fix was verified on a test server - the interval went back to 147 seconds.
|
||||
- **The timer parameters** are one-sided and safe, but on their own they buy little, so they travel with the next step.
|
||||
|
||||
The situation on kernels older than 6.7 is covered separately in <a href="#debian-support-adv">Debian support</a>.
|
||||
|
|
@ -917,7 +917,7 @@ Client keys are stored in `/root/awg/keys/` (permissions 600). Server keys are i
|
|||
The installer downloads `awg_common.sh` and `manage_amneziawg.sh` from URLs pinned to the specific version tag:
|
||||
|
||||
```
|
||||
https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/awg_common.sh
|
||||
https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.30.0/awg_common.sh
|
||||
```
|
||||
|
||||
This provides **supply chain pinning**: downloaded scripts match the installer version, even if `main` has already been updated.
|
||||
|
|
@ -937,12 +937,12 @@ To update the management and shared library scripts **without reinstalling the s
|
|||
|
||||
```bash
|
||||
# Russian version:
|
||||
wget -O /root/awg/manage_amneziawg.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/manage_amneziawg.sh
|
||||
wget -O /root/awg/awg_common.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/awg_common.sh
|
||||
wget -O /root/awg/manage_amneziawg.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/manage_amneziawg.sh
|
||||
wget -O /root/awg/awg_common.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/awg_common.sh
|
||||
|
||||
# English version:
|
||||
wget -O /root/awg/manage_amneziawg.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/manage_amneziawg_en.sh
|
||||
wget -O /root/awg/awg_common.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/awg_common_en.sh
|
||||
wget -O /root/awg/manage_amneziawg.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/manage_amneziawg_en.sh
|
||||
wget -O /root/awg/awg_common.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/awg_common_en.sh
|
||||
|
||||
# Set permissions
|
||||
chmod 700 /root/awg/manage_amneziawg.sh /root/awg/awg_common.sh
|
||||
|
|
@ -955,6 +955,26 @@ chmod 700 /root/awg/manage_amneziawg.sh /root/awg/awg_common.sh
|
|||
<a id="faq-advanced-adv"></a>
|
||||
## ❓ FAQ (Additional Questions)
|
||||
|
||||
<details>
|
||||
<summary><strong>Q: Why is my config highlighted yellow in the Amnezia app?</strong></summary>
|
||||
<b>A:</b> The yellow mark reports the profile generation, not an error: that is how the Amnezia app marks second-line AmneziaWG configurations. On its own the mark does not say anything is wrong with the connection, and nothing needs redoing because of it. As of 31 August 2026 the installer issues a second-line profile, so the mark is expected on any server it sets up.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Q: How do I get a client on Android that speaks the third line?</strong></summary>
|
||||
<b>A:</b> Two paths speak the third line on Android: the flagship Amnezia VPN app, and the light client APK from the project's releases page, full release of 14 August 2026. The light client on Google Play was updated 12 June 2026, before the third-line build shipped, so the advice "install the light client from Play" will most likely leave you on the second line. The full table is in <a href="#client-compat-adv">Client compatibility</a>.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Q: Does my router speak third-line AmneziaWG?</strong></summary>
|
||||
<b>A:</b> I know of no stock firmware supporting the third line as of 31 August 2026, so for a router the second-line profile stays a working path rather than a temporary compromise. On routers the third line comes from third-party projects: AWG Manager for Keenetic (v2.17.4 of 27 August 2026), and for MikroTik and RouterOS two independent projects updated on 26 and 30 August 2026.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Q: Can a third-line config be converted back to the second line?</strong></summary>
|
||||
<b>A:</b> A third-line client config cannot be edited down to the second line: the generation is set on the server side, and some third-line parameters are two-sided. In a measurement on 30 August 2026 a header protection key left on one side only produced zero handshakes and 100% loss, silently, with no error message anywhere. Devices that stay on the second line need a second-generation server.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Q: How do I get a split exit - Russian traffic direct, the rest abroad?</strong></summary>
|
||||
<b>A:</b> This is built as a two-server cascade: the client connects to an entry server (ideally in Russia), Russian traffic exits directly from it, and everything else goes through a second server abroad. The cascade is not part of the installer (different scale), but there is a separate step-by-step guide - <a href="CASCADE.en.md">CASCADE.en.md</a>.
|
||||
|
|
@ -1642,6 +1662,39 @@ Not all clients support AWG 2.0. Check compatibility before choosing a client:
|
|||
|
||||
> If a client shows an error about an unknown parameter (S3, S4, I1, or H1 as a range), use one of the first four clients in the table.
|
||||
|
||||
### Which AmneziaWG generation a client speaks
|
||||
|
||||
Recorded 31 August 2026 from the projects' own release pages, and you can check the same way.
|
||||
Clients move every few days, so each row carries a date: it says when that version was current.
|
||||
|
||||
| Client | Generation | Version | Date | Where to get it |
|
||||
|---|---|---|---|---|
|
||||
| Amnezia VPN (flagship, all platforms) | third line | 5.0.1.5 | 21 Aug 2026 | GitHub, app stores |
|
||||
| AmneziaWG for Android (light, APK) | third line | v3.1.20260814 | 14 Aug 2026 | project releases page |
|
||||
| AmneziaWG on Google Play (light) | **an inference, not an observation: second line** | 2.0.1 in the release-notes text | listing updated **12 Jun 2026** | Google Play |
|
||||
| AmneziaWG for Windows (standalone client) | third line | 3.1.0 | 21 Aug 2026 | project releases page |
|
||||
| WG Tunnel (third-party, Android) | speaks AmneziaWG; generation not verified by behaviour | 5.6.0 | 27 Aug 2026 | Play, F-Droid, IzzyOnDroid, APK |
|
||||
| AWG Manager (Keenetic, third-party) | third line | v2.17.4 | 27 Aug 2026 | project releases page |
|
||||
| amneziawg-mikrotik-c (third-party) | third line | v1.2.8 | 26 Aug 2026 | project releases page |
|
||||
| AmneziaWG-MikroTik (third-party) | third line | Containers_3.1 | 30 Aug 2026 | project releases page |
|
||||
|
||||
⚠️ **The Google Play row is an inference, not an observation.** Google Play does not show a
|
||||
app page no longer carries a separate current-version field: the only number on it is
|
||||
`AmneziaWG 2.0.1` inside the release-notes text, which the developer writes by hand and which
|
||||
can lag what is actually being served. So the generation is inferred from dates: the listing was
|
||||
updated 12 June 2026 and the third-line build appeared on 14 August 2026, after the listing was
|
||||
last updated. The number in the notes agrees with that conclusion but does not prove it on its
|
||||
own.
|
||||
|
||||
⚠️ **The WG Tunnel row is held to the same standard.** It is a third-party project, its 5.6.0
|
||||
release from 27 August 2026 exists, but I have not verified the protocol generation by
|
||||
behaviour and name no generation number here. Until that check it stays in the table as "speaks AmneziaWG",
|
||||
nothing more.
|
||||
|
||||
> ⚠️ For third-party projects the generation comes from their authors' own documentation and
|
||||
> release notes: I read their code, but verified none of them by behaviour, and no row here is
|
||||
> marked as verified.
|
||||
|
||||
### Router Clients
|
||||
|
||||
| Project | Platform | Description |
|
||||
|
|
|
|||
67
ADVANCED.md
67
ADVANCED.md
|
|
@ -279,8 +279,8 @@ echo "module amneziawg -p" > /sys/kernel/debug/dynamic_debug/control
|
|||
|
||||
Возможностей 3.0 в генерируемых конфигах нет, и это осознанно:
|
||||
|
||||
- **`HeaderProtectionKey`** требует, чтобы его понимали ВСЕ ваши клиенты одновременно. На момент выхода этой версии `amneziawg-android` 3.0.1 остаётся предрелизом, а у `amneziawg-windows-client` последний выпуск - 2.0.2. Включить его сейчас значит отрезать часть устройств без предупреждения.
|
||||
- **`ContentPaddingAddition`** ждёт исправления в upstream: сейчас паддинг ломает распознавание служебных пакетов, они отбрасываются с сообщением `Packet is neither ipv4 nor ipv6` ([issue #186](https://github.com/amnezia-vpn/amneziawg-linux-kernel-module/issues/186)).
|
||||
- **`HeaderProtectionKey`** требует, чтобы его понимали ВСЕ ваши клиенты одновременно, и это требование жёстче, чем кажется: параметр двусторонний и при несовпадении отказывает молча - рукопожатия просто нет, ошибок никто не показывает. Состояние клиентов на 31 августа 2026: у `amneziawg-windows-client` есть выпуск 3.1.0 (21 августа), у `amneziawg-android` - полный релиз `v3.1.20260814` (14 августа; отдельный 3.0.1 от 24 июля так и остался предрелизом), а лёгкий клиент в Google Play с 12 июня не обновлялся. Пока часть ваших устройств обновляется из магазина, включение параметра отрезало бы их без предупреждения.
|
||||
- **`ContentPaddingAddition`** не включается по исторической причине, и она уже снята. Паддинг ломал распознавание служебных пакетов: простаивающий туннель переустанавливал соединение каждые 15 секунд вместо примерно 147. Дефект разобран в [issue #186](https://github.com/amnezia-vpn/amneziawg-linux-kernel-module/issues/186) и исправлен upstream 5 августа 2026 ([PR #208](https://github.com/amnezia-vpn/amneziawg-linux-kernel-module/pull/208)); исправление проверено на стенде - интервал вернулся к 147 секундам.
|
||||
- **Параметры таймингов** односторонние и безопасны, но пользы без остальной картины дают мало, поэтому идут вместе со следующим шагом.
|
||||
|
||||
Отдельный разбор ситуации на ядрах старше 6.7 - в разделе <a href="#debian-support-adv">Поддержка Debian</a>.
|
||||
|
|
@ -915,7 +915,7 @@ graph TD
|
|||
Инсталлятор скачивает `awg_common.sh` и `manage_amneziawg.sh` с URL, привязанных к конкретному тегу версии:
|
||||
|
||||
```
|
||||
https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/awg_common.sh
|
||||
https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.30.0/awg_common.sh
|
||||
```
|
||||
|
||||
Это даёт **supply chain pinning**: скачиваемые скрипты соответствуют версии инсталлятора, даже если `main` уже обновлён.
|
||||
|
|
@ -935,12 +935,12 @@ AWG_BRANCH=my-feature-branch sudo bash ./install_amneziawg.sh
|
|||
|
||||
```bash
|
||||
# Русская версия:
|
||||
wget -O /root/awg/manage_amneziawg.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/manage_amneziawg.sh
|
||||
wget -O /root/awg/awg_common.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/awg_common.sh
|
||||
wget -O /root/awg/manage_amneziawg.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/manage_amneziawg.sh
|
||||
wget -O /root/awg/awg_common.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/awg_common.sh
|
||||
|
||||
# Английская версия:
|
||||
wget -O /root/awg/manage_amneziawg.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/manage_amneziawg_en.sh
|
||||
wget -O /root/awg/awg_common.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/awg_common_en.sh
|
||||
wget -O /root/awg/manage_amneziawg.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/manage_amneziawg_en.sh
|
||||
wget -O /root/awg/awg_common.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/awg_common_en.sh
|
||||
|
||||
# Установить права
|
||||
chmod 700 /root/awg/manage_amneziawg.sh /root/awg/awg_common.sh
|
||||
|
|
@ -953,6 +953,26 @@ chmod 700 /root/awg/manage_amneziawg.sh /root/awg/awg_common.sh
|
|||
<a id="faq-advanced-adv"></a>
|
||||
## ❓ FAQ (Дополнительные вопросы)
|
||||
|
||||
<details>
|
||||
<summary><strong>В: Почему мой конфиг подсвечен жёлтым в приложении Amnezia?</strong></summary>
|
||||
<b>О:</b> Жёлтая метка означает поколение профиля, а не ошибку: так приложение Amnezia помечает конфигурации второй линии AmneziaWG. Сама по себе метка не говорит, что с соединением что-то не так, и переделывать из-за неё ничего не нужно. Установщик на 31 августа 2026 выдаёт профиль второй линии, поэтому метка ожидаема на любом поставленном им сервере.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>В: Как получить на Android клиент, понимающий третью линию?</strong></summary>
|
||||
<b>О:</b> На Android третью линию понимают два пути: флагманское приложение Amnezia VPN и APK лёгкого клиента со страницы релизов проекта, полный релиз от 14 августа 2026. Лёгкий клиент в Google Play обновлялся 12 июня 2026, то есть до выхода сборки третьей линии, поэтому совет "поставьте лёгкий клиент из Play" скорее всего оставит вас на второй линии. Полная таблица - в разделе <a href="#client-compat-adv">Совместимость клиентов</a>.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>В: Мой роутер умеет AmneziaWG третьей линии?</strong></summary>
|
||||
<b>О:</b> Стоковых прошивок с поддержкой третьей линии на 31 августа 2026 я не знаю, поэтому для роутера профиль второй линии остаётся рабочим путём, а не временным компромиссом. Третью линию на роутерах дают сторонние проекты: AWG Manager для Keenetic (v2.17.4 от 27 августа 2026), а для MikroTik и RouterOS два независимых проекта, обновлявшихся 26 и 30 августа 2026.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>В: Можно ли переделать конфиг третьей линии обратно во вторую?</strong></summary>
|
||||
<b>О:</b> Отредактировать клиентский конфиг третьей линии под вторую нельзя: поколение задаётся на стороне сервера, а часть параметров третьей линии двусторонние. По замеру от 30 августа 2026 ключ защиты заголовков, оставшийся только на одной стороне, даёт ноль рукопожатий и 100% потерь, причём молча, без сообщения об ошибке. Устройствам, оставшимся на второй линии, нужен сервер второго поколения.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>В: Как сделать раздельный выход - российский трафик напрямую, остальное через заграницу?</strong></summary>
|
||||
<b>О:</b> Это собирается каскадом из двух серверов: клиент подключается к серверу-входу (лучше в РФ), российский трафик уходит напрямую с него, остальное - через второй сервер за границей. Каскад не входит в установщик (другой масштаб), но есть отдельная пошаговая инструкция - <a href="CASCADE.md">CASCADE.md</a>.
|
||||
|
|
@ -1640,6 +1660,39 @@ AWG 2.0 поддерживается не всеми клиентами. Пер
|
|||
|
||||
> Если при импорте клиент выдаёт ошибку про неизвестный параметр (S3, S4, I1, или H1 в формате диапазона) — используйте клиент из первых четырёх строк таблицы.
|
||||
|
||||
### Какое поколение AmneziaWG понимает клиент
|
||||
|
||||
Снято 31 августа 2026 со страниц релизов проектов, проверить можно там же. Клиенты двигаются
|
||||
раз в несколько дней, поэтому у каждой строки стоит дата: она говорит, на какой момент версия
|
||||
была верна.
|
||||
|
||||
| Клиент | Поколение | Версия | Дата | Где брать |
|
||||
|---|---|---|---|---|
|
||||
| Amnezia VPN (флагман, все платформы) | третья линия | 5.0.1.5 | 21 авг 2026 | GitHub, магазины приложений |
|
||||
| AmneziaWG для Android (лёгкий, APK) | третья линия | v3.1.20260814 | 14 авг 2026 | страница релизов проекта |
|
||||
| AmneziaWG в Google Play (лёгкий) | **вывод, а не наблюдение: вторая линия** | 2.0.1 в тексте заметок | листинг обновлён **12 июн 2026** | Google Play |
|
||||
| AmneziaWG для Windows (отдельный клиент) | третья линия | 3.1.0 | 21 авг 2026 | страница релизов проекта |
|
||||
| WG Tunnel (сторонний, Android) | умеет AmneziaWG; поколение поведением не проверял | 5.6.0 | 27 авг 2026 | Play, F-Droid, IzzyOnDroid, APK |
|
||||
| AWG Manager (Keenetic, сторонний) | третья линия | v2.17.4 | 27 авг 2026 | страница релизов проекта |
|
||||
| amneziawg-mikrotik-c (сторонний) | третья линия | v1.2.8 | 26 авг 2026 | страница релизов проекта |
|
||||
| AmneziaWG-MikroTik (сторонний) | третья линия | Containers_3.1 | 30 авг 2026 | страница релизов проекта |
|
||||
|
||||
⚠️ **Строка Google Play - это вывод, а не наблюдение.** Google Play не показывает номер версии
|
||||
на странице приложения отдельного поля с текущей версией больше нет: единственный номер там -
|
||||
это `AmneziaWG 2.0.1` в тексте заметок к выпуску, а заметки пишет разработчик, и отставать от
|
||||
того, что реально раздаётся, они могут. Поэтому поколение выводится из дат: листинг обновлён
|
||||
12 июня 2026, а сборка третьей линии вышла 14 августа 2026, то есть уже после последнего
|
||||
обновления листинга. Номер в заметках с этим выводом сходится, но сам по себе доказательством
|
||||
не служит.
|
||||
|
||||
⚠️ **Та же строгость к строке WG Tunnel.** Это сторонний проект, его релиз 5.6.0 от 27 августа
|
||||
2026 существует, но поколение протокола в нём поведением не проверялось и номер поколения тут
|
||||
не называется.
|
||||
До проверки он стоит в таблице как «умеет AmneziaWG», не более.
|
||||
|
||||
> ⚠️ Поколение у сторонних проектов указано по документации и релиз-нотам их авторов: их код я
|
||||
> читал, но поведением не проверял ни один: строк, проверенных поведением, в таблице нет.
|
||||
|
||||
### Клиенты для роутеров
|
||||
|
||||
| Проект | Платформа | Описание |
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ TABLE_ID=100 # routing table number for "to the exit"
|
|||
FWMARK="0x1" # mark for traffic leaving via awg1
|
||||
RULE_PRIO=10000 # ip rule priority (uncommon, to avoid collisions)
|
||||
RU_ZONE_URL="https://www.ipdeny.com/ipblocks/data/aggregated/ru-aggregated.zone"
|
||||
RU_ZONE_FALLBACK_URL="https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/cascade/ru.zone"
|
||||
RU_ZONE_FALLBACK_URL="https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.30.0/cascade/ru.zone"
|
||||
AWG_DIR="/root/awg"
|
||||
EXTRA_RU_NETS="" # your own addresses/networks to route through Russia (space
|
||||
# separated), e.g. "203.0.113.7 198.51.100.0/24". Useful for
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ TABLE_ID=100 # номер таблицы маршру
|
|||
FWMARK="0x1" # метка для трафика, уходящего через awg1
|
||||
RULE_PRIO=10000 # приоритет правила ip rule (нестандартный, чтобы не конфликтовать)
|
||||
RU_ZONE_URL="https://www.ipdeny.com/ipblocks/data/aggregated/ru-aggregated.zone"
|
||||
RU_ZONE_FALLBACK_URL="https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/cascade/ru.zone"
|
||||
RU_ZONE_FALLBACK_URL="https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.30.0/cascade/ru.zone"
|
||||
AWG_DIR="/root/awg"
|
||||
EXTRA_RU_NETS="" # свои адреса/сети, которые вести через РФ (через пробел),
|
||||
# напр. "203.0.113.7 198.51.100.0/24". Нужно для .ru-сайтов
|
||||
|
|
|
|||
|
|
@ -12,11 +12,31 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [5.30.0] - 2026-09-01
|
||||
|
||||
**v5.30.0** - install commands that do not go stale, and a guard for oversized masking parameters.
|
||||
|
||||
The install and update commands no longer name a version, so a line copied from an article or from a search answer installs the current release rather than the one that was current when the text was written. Diagnostics warn about oversized masking parameters before the interface read hangs on them. And the documentation answers the question people actually arrive with: which generation of the protocol a given client speaks.
|
||||
|
||||
### Added
|
||||
|
||||
- A dated facts block at the top of `README.md` and `README.en.md`: installer version, release date, recommended and end-of-life systems, architectures, where the kernel module comes from, and the profile of the generated configs. The block is built by `scripts/update-facts-block.sh` from repository data (`SCRIPT_VERSION`, the CHANGELOG heading, `docs/support-matrix.json`, the `arm-build.yml` matrix) instead of being written by hand, so it cannot drift away from its sources, and both language versions are rendered from the same values.
|
||||
- `scripts/check-docs-consistency.sh`: a check that the block stored in the READMEs matches what the generator produces right now. Runs in CI and in preflight.
|
||||
- `tests/test_facts_block.bats`: mutation tests for the guard, including the case where a third-line parameter reaches a config template while the block still promises the 2.0 profile.
|
||||
- **A client generation matrix in `ADVANCED.md` and `ADVANCED.en.md`.** The documentation could say whether a client supports AmneziaWG 2.0, but not which generation of the protocol it speaks, and that is what people ask now that the vendor app marks second-line configs in yellow. Four answers sit next to the table: why a config is marked yellow, where to get a third-line client on Android, whether a given router speaks the third line, and whether a third-line config can be converted back to the second. Two rows are deliberately weaker than the rest and say so: the light client on Google Play publishes no version number on its page, and WG Tunnel ships its engine as a prebuilt artifact, so the generation cannot be read from the manifest. For the third-party projects the table states plainly that their generation comes from their own documentation and code rather than from verified behaviour: it does not vouch for another project's implementation
|
||||
- **A warning about oversized `I1`-`I5`.** A total decoded size of masking packets above a kilobyte hangs the interface read: the dump stops making progress and repeats forever, the reading side grows in memory, and on a router it takes the device down. Our generator never produces that much (256 bytes at most), but configs are also edited by hand, and nothing warned about it anywhere. `diagnose` computes the size offline, parsing the configuration file before the first call into the interface: a check placed after that call would never print in exactly the case it was written for. If the size is judged unsafe the remaining interface calls are skipped, and the ones that stay are bounded by a timeout that reports itself rather than showing zero peers. An unrecognised tag in the config produces its own warning instead of an understated number: "could not compute" must not look like "computed, all good"
|
||||
|
||||
### Changed
|
||||
|
||||
- **Install and update commands point at `releases/latest/download`.** Thirty-two commands named a specific tag: ten each in `README.md` and `README.en.md`, two in each installation guide and eight more in `ADVANCED.md` and `ADVANCED.en.md`. A copied line went stale the day the next release shipped, and search answers held on to it for months: a measurement on 31 August 2026 was still handing out `v5.14.1` from 19 May, thirty-seven releases back, with neither the critical package protection nor the `/etc/fstab` fix. The version-less address does not age in a cache or in training data, and it serves a signed release asset: `.minisig` files and `KEYS.txt` sit beside it, which a branch address cannot offer. Four pinned addresses stay on purpose: the `ADVANCED` section explains how the installer fetches its own helper scripts, and it fetches them by tag, so a version-less address there would make the section lie about the code it documents; `CASCADE` pins `cascade/ru.zone`, which is not a release asset at all. The guard in `check-docs-consistency.sh` is positive by construction: it requires at least the expected number of commands on the new address and zero on the old one, because a check that only looks for the bad form cannot tell a fixed file from an empty one
|
||||
|
||||
### Fixed
|
||||
|
||||
- **No command reports an unread state as a measured one.** When diagnostics cannot read the interface they say so, instead of the previous "I1 absent" - which was printed in exactly the case the guard exists for, an oversized `I1`. The carrier profile comparison is skipped entirely in that state: a green "I1 absent" tick would certify the very condition that made the check refuse to look. In the same situation `list` leaves clients at "No data", and `no_data` under `--json`, rather than declaring everyone without a handshake
|
||||
- **Every interface call is bounded in time.** `check`, `stats`, `show`, `list`, the diagnostics and the `--diagnostic` report can no longer wait forever, and a timeout is named separately from other errors so the cause is easy to find. That matters most for `--diagnostic`: the documentation asks people to attach its output to a report, and without a bound someone hitting the defect could not collect a report about it. The installer's own verification on a repeat run over a working server is bounded the same way
|
||||
- **Sizing the masking packets no longer reports the unparsed as zero.** A string with no recognisable tag, an unterminated bracket, junk between tags, content inside `<t>` or `<c>` where none belongs, and a count too large for shell arithmetic all used to return zero with a success status - and zero means "there are no masking packets", which switches the protection off. Each of those is now marked as "could not compute" and said out loud by the diagnostics. A tag preceded by a stray angle bracket also stopped being counted twice
|
||||
- **Two stale claims about why third-line features stay out.** The section pointed at the pre-release status of the Android client and at release 2.0.2 of the Windows client, when both have had full third-line releases for a while, and it promised that `ContentPaddingAddition` was waiting for an upstream fix, when that fix shipped on 5 August 2026 and has been verified on a test box. What is written there now is mechanism, which does not go stale when someone else ships: `HeaderProtectionKey` is two-sided and fails silently on a mismatch, with no handshake and no error from either end, so enabling it would cut off devices that update from an app store
|
||||
- **A dead assertion in `tests/test_diagnostic_report_secrets.bats`.** The assertion about masking secrets in the report was not actually checking the Russian installer: a negation inside a loop does not fail a bats test, so a defect in the Russian version passed as success. The assertion now takes a form that does not depend on how `set -e` behaves, confirmed by mutation: the old one went green on a defect the new one catches
|
||||
|
||||
## [5.29.0] - 2026-08-30
|
||||
|
||||
|
|
@ -1821,7 +1841,8 @@ 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.29.0...HEAD
|
||||
[Unreleased]: https://github.com/bivlked/amneziawg-installer/compare/v5.30.0...HEAD
|
||||
[5.30.0]: https://github.com/bivlked/amneziawg-installer/compare/v5.29.0...v5.30.0
|
||||
[5.29.0]: https://github.com/bivlked/amneziawg-installer/compare/v5.28.1...v5.29.0
|
||||
[5.28.1]: https://github.com/bivlked/amneziawg-installer/compare/v5.28.0...v5.28.1
|
||||
[5.28.0]: https://github.com/bivlked/amneziawg-installer/compare/v5.27.1...v5.28.0
|
||||
|
|
|
|||
23
CHANGELOG.md
23
CHANGELOG.md
|
|
@ -12,11 +12,31 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [5.30.0] - 2026-09-01
|
||||
|
||||
**v5.30.0** - команды установки, которые не устаревают, и защита от слишком больших параметров маскировки.
|
||||
|
||||
Команды установки и обновления больше не называют номер версии: строка, скопированная из статьи или из ответа поисковика, ставит текущий релиз, а не тот, что был актуален в день публикации. Диагностика предупреждает о слишком больших параметрах маскировки до того, как на них зависнет чтение интерфейса. Документация отвечает на вопрос, с которым люди приходят на самом деле: какое поколение протокола понимает конкретный клиент.
|
||||
|
||||
### Добавлено
|
||||
|
||||
- Блок «факты на дату» в начале `README.md` и `README.en.md`: версия установщика, дата релиза, рекомендуемые и доживающие системы, архитектуры, происхождение модуля ядра и профиль генерируемых конфигов. Блок собирается `scripts/update-facts-block.sh` из данных репозитория (`SCRIPT_VERSION`, заголовок CHANGELOG, `docs/support-matrix.json`, матрица `arm-build.yml`), а не пишется руками, поэтому не может тихо разойтись с источниками, а значения в русской и английской версиях собираются из одних и тех же данных.
|
||||
- `scripts/check-docs-consistency.sh`: проверка, что записанный в README блок совпадает с тем, что даёт генератор сейчас. Идёт в CI и в preflight.
|
||||
- `tests/test_facts_block.bats`: мутационные проверки сторожа, включая случай «параметр третьей линии попал в шаблон конфига, а блок всё ещё обещает профиль 2.0».
|
||||
- **Матрица поколений клиентов в `ADVANCED.md` и `ADVANCED.en.md`.** Прежде документация отвечала, поддерживает ли клиент AmneziaWG 2.0, но не отвечала, какое поколение протокола он понимает, а спрашивают именно об этом с тех пор, как приложение вендора помечает конфиги второй линии жёлтым. Рядом с таблицей четыре ответа: почему конфиг помечен жёлтым, где взять клиент третьей линии под Android, понимает ли третью линию мой роутер и можно ли вернуть конфиг третьей линии обратно ко второй. Две строки таблицы намеренно слабее прочих и говорят об этом вслух: лёгкий клиент в Google Play не публикует номер версии на своей странице, а WG Tunnel поставляет движок готовым артефактом, и поколение из манифеста не читается. Про сторонние проекты сказано прямо, что их поколение взято из их же документации и кода, а не проверено поведением: таблица не ручается за чужую реализацию
|
||||
- **Предупреждение о слишком больших `I1`-`I5`.** Суммарный декодированный размер маскирующих пакетов больше килобайта подвешивает чтение интерфейса: дамп перестаёт продвигаться и повторяется бесконечно, читающая сторона растёт в памяти, а на роутере это роняет устройство. Наш генератор столько не выдаёт никогда (максимум 256 байт), но конфиг правят и руками, а предупреждения об этом не было нигде. `diagnose` считает размер офлайн, разбирая файл конфигурации до первого обращения к интерфейсу: проверка, стоящая после обращения, не напечаталась бы ровно в том случае, ради которого написана. Если размер признан опасным, последующие обращения к интерфейсу пропускаются, а те, что остаются, ограничены таймаутом и сообщают о нём отдельно, вместо того чтобы показать ноль пиров. Нераспознанный тег в конфиге даёт не заниженное число, а собственное предупреждение: «посчитать не удалось» не должно выглядеть как «посчитал, всё в порядке»
|
||||
|
||||
### Изменено
|
||||
|
||||
- **Команды установки и обновления указывают на `releases/latest/download`.** Тридцать две команды называли конкретный тег: по десять в `README.md` и `README.en.md`, по две в обоих руководствах по установке и ещё восемь в `ADVANCED.md` и `ADVANCED.en.md`. Скопированная строка устаревала в день следующего релиза, а поисковые ответы держали её месяцами: замер 31 августа 2026 всё ещё раздавал `v5.14.1` от 19 мая, тридцать семь релизов назад, без защиты критичных пакетов и без исправления записи в `/etc/fstab`. Адрес без версии не стареет ни в кеше, ни в обучающих данных и отдаёт подписанный ассет релиза: рядом с ним лежат `.minisig` и `KEYS.txt`, чего адрес ветки дать не может. Четыре закреплённых адреса остались намеренно: раздел `ADVANCED` объясняет, как установщик качает свои вспомогательные скрипты, а качает он их по тегу, и адрес без версии заставил бы раздел врать о коде, который он описывает; `CASCADE` закрепляет `cascade/ru.zone`, которого в релизных ассетах нет вовсе. Сторож в `check-docs-consistency.sh` устроен положительно: он требует минимум ожидаемого числа команд на новом адресе и ноль на старом, потому что проверка, которая только ищет плохое, не отличает исправленный файл от пустого
|
||||
|
||||
### Исправлено
|
||||
|
||||
- **Ни одна команда не выдаёт непрочитанное состояние за измеренное.** Если диагностика не смогла прочитать интерфейс, она так и пишет, вместо прежнего «I1 отсутствует» - а печаталось это ровно в том случае, ради которого сторож и написан: когда `I1` слишком велик. Сравнение с профилем оператора в таком состоянии пропускается целиком: зелёная отметка «I1 отсутствует» удостоверяла бы именно то условие, из-за которого проверка отказалась смотреть. `list` в том же положении оставляет клиентам состояние «Нет данных» и `no_data` в `--json`, а не объявляет всех без рукопожатия
|
||||
- **Обращения к интерфейсу ограничены по времени во всех командах.** `check`, `stats`, `show`, `list`, диагностика и отчёт `--diagnostic` больше не могут ждать бесконечно, а превышение времени называется отдельно от прочих ошибок и подсказывает, где искать причину. Особенно это важно для `--diagnostic`: документация просит приложить его вывод к обращению, и без границы по времени попавший в дефект не смог бы собрать даже отчёт о нём. Проверка установщика при повторном запуске поверх работающего сервера ограничена так же
|
||||
- **Подсчёт размера маскирующих пакетов не выдаёт непонятое за нулевое.** Строка без единого распознанного тега, оборванная скобка, мусор между тегами, содержимое внутри `<t>` и `<c>`, где его быть не должно, и число, не помещающееся в арифметику оболочки, - всё это раньше давало ноль с признаком успеха, а ноль означает «маскирующих пакетов нет» и снимает защиту. Теперь каждый такой случай помечается как «посчитать не удалось», и диагностика говорит об этом вслух. Заодно перестал считаться дважды тег, перед которым в строке стоит посторонняя угловая скобка
|
||||
- **Два устаревших утверждения о том, почему возможности третьей линии не включены.** Раздел ссылался на предрелизный статус клиента для Android и на выпуск 2.0.2 у клиента для Windows, хотя у обоих давно есть полные релизы третьей линии, и обещал, что `ContentPaddingAddition` ждёт исправления в upstream, хотя исправление вышло 5 августа 2026 и проверено на стенде. Теперь там написана механика, которая не устаревает от чужого релиза: `HeaderProtectionKey` двусторонний и при несовпадении отказывает молча, рукопожатия просто нет и ошибки не показывает никто, поэтому его включение отрезало бы устройства, которые обновляются из магазина приложений
|
||||
- **Мёртвая проверка в `tests/test_diagnostic_report_secrets.bats`.** Утверждение о маскировке секретов в отчёте фактически не проверяло русский установщик: отрицание внутри цикла в bats не роняет тест, поэтому дефект в русской версии проходил как успех. Форма проверки заменена на ту, что не зависит от поведения `set -e`, и подтверждена мутацией: прежняя зеленела на дефекте, который новая ловит
|
||||
|
||||
## [5.29.0] - 2026-08-30
|
||||
|
||||
|
|
@ -1821,7 +1841,8 @@ Hardening-фиксы надёжности и безопасности по ре
|
|||
- Диагностический отчет (`--diagnostic`).
|
||||
- Полная деинсталляция (`--uninstall`).
|
||||
|
||||
[Unreleased]: https://github.com/bivlked/amneziawg-installer/compare/v5.29.0...HEAD
|
||||
[Unreleased]: https://github.com/bivlked/amneziawg-installer/compare/v5.30.0...HEAD
|
||||
[5.30.0]: https://github.com/bivlked/amneziawg-installer/compare/v5.29.0...v5.30.0
|
||||
[5.29.0]: https://github.com/bivlked/amneziawg-installer/compare/v5.28.1...v5.29.0
|
||||
[5.28.1]: https://github.com/bivlked/amneziawg-installer/compare/v5.28.0...v5.28.1
|
||||
[5.28.0]: https://github.com/bivlked/amneziawg-installer/compare/v5.27.1...v5.28.0
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ sudo ufw allow <your-ssh-port>/tcp
|
|||
Then:
|
||||
|
||||
```bash
|
||||
wget -O install_amneziawg_en.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/install_amneziawg_en.sh
|
||||
wget -O install_amneziawg_en.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/install_amneziawg_en.sh
|
||||
chmod +x install_amneziawg_en.sh
|
||||
sudo bash ./install_amneziawg_en.sh
|
||||
```
|
||||
|
|
@ -101,7 +101,7 @@ Per-command output formats and the compatibility promise: [ADVANCED.en.md JSON i
|
|||
Updating to a newer installer release on a server that already has a supported version running:
|
||||
|
||||
```bash
|
||||
wget -O install_amneziawg_en.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/install_amneziawg_en.sh
|
||||
wget -O install_amneziawg_en.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/install_amneziawg_en.sh
|
||||
sudo bash ./install_amneziawg_en.sh --force
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ sudo ufw allow ВАШ_ПОРТ/tcp
|
|||
Дальше сама установка:
|
||||
|
||||
```bash
|
||||
wget -O install_amneziawg.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/install_amneziawg.sh
|
||||
wget -O install_amneziawg.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/install_amneziawg.sh
|
||||
chmod +x install_amneziawg.sh
|
||||
sudo bash ./install_amneziawg.sh
|
||||
```
|
||||
|
|
@ -151,7 +151,7 @@ sudo bash /root/awg/manage_amneziawg.sh add phone --json --yes
|
|||
Обновить установщик на сервере, где уже работает поддерживаемая версия:
|
||||
|
||||
```bash
|
||||
wget -O install_amneziawg.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/install_amneziawg.sh
|
||||
wget -O install_amneziawg.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/install_amneziawg.sh
|
||||
sudo bash ./install_amneziawg.sh --force
|
||||
```
|
||||
|
||||
|
|
|
|||
24
README.en.md
24
README.en.md
|
|
@ -18,7 +18,7 @@
|
|||
<img src="https://img.shields.io/badge/Architecture-x86__64_|_ARM64_|_ARMv7-green" alt="x86_64 | ARM64 | ARMv7">
|
||||
<img src="https://img.shields.io/badge/AmneziaWG-2.0_|_3.x-blueviolet" alt="AmneziaWG 2.0 and 3.x">
|
||||
<a href="https://github.com/bivlked/amneziawg-installer/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="License"></a>
|
||||
<a href="https://github.com/bivlked/amneziawg-installer/releases"><img src="https://img.shields.io/badge/Installer_Version-5.29.0-blue" alt="Version"></a>
|
||||
<a href="https://github.com/bivlked/amneziawg-installer/releases"><img src="https://img.shields.io/badge/Installer_Version-5.30.0-blue" alt="Version"></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>
|
||||
<a href="https://github.com/bivlked/amneziawg-installer/stargazers"><img src="https://img.shields.io/github/stars/bivlked/amneziawg-installer?style=flat" alt="Stars"></a>
|
||||
<img src="https://img.shields.io/github/last-commit/bivlked/amneziawg-installer" alt="Last commit">
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
<!-- facts:begin -->
|
||||
<!-- Generated by scripts/update-facts-block.sh from repository data.
|
||||
Do not edit by hand: check-docs-consistency.sh verifies it against the sources. -->
|
||||
**Facts as of 2026-08-30.** Installer 5.29.0. OS support dates verified against vendor data on 2026-08-29.
|
||||
**Facts as of 2026-09-01.** Installer 5.30.0. OS support dates verified against vendor data on 2026-08-29.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
## 🚀 Quick Start
|
||||
|
||||
```bash
|
||||
wget -O install_amneziawg_en.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/install_amneziawg_en.sh
|
||||
wget -O install_amneziawg_en.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/install_amneziawg_en.sh
|
||||
chmod +x install_amneziawg_en.sh
|
||||
sudo bash ./install_amneziawg_en.sh
|
||||
```
|
||||
|
|
@ -352,8 +352,8 @@ Installing AmneziaWG on Ubuntu or Debian comes down to three commands: download
|
|||
|
||||
2. **Download the script:**
|
||||
```bash
|
||||
wget -O install_amneziawg_en.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/install_amneziawg_en.sh
|
||||
# or: curl -fLo install_amneziawg_en.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/install_amneziawg_en.sh
|
||||
wget -O install_amneziawg_en.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/install_amneziawg_en.sh
|
||||
# or: curl -fLo install_amneziawg_en.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/install_amneziawg_en.sh
|
||||
```
|
||||
> Minimal Debian may not ship curl (wget is usually present) - use `wget`. The installer itself adds curl in step 1.
|
||||
3. **Make it executable:**
|
||||
|
|
@ -368,7 +368,7 @@ Installing AmneziaWG on Ubuntu or Debian comes down to three commands: download
|
|||
|
||||
> **Russian version:** For Russian output, use `install_amneziawg.sh`:
|
||||
> ```bash
|
||||
> wget -O install_amneziawg.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/install_amneziawg.sh
|
||||
> wget -O install_amneziawg.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/install_amneziawg.sh
|
||||
> sudo bash ./install_amneziawg.sh
|
||||
> ```
|
||||
> The Russian version is functionally identical; only user-facing messages and logs are in Russian.
|
||||
|
|
@ -518,11 +518,11 @@ Full list: `... help` or [ADVANCED.en.md#manage-commands-adv](ADVANCED.en.md#man
|
|||
|
||||
```bash
|
||||
# Installation (English)
|
||||
wget -O install_amneziawg_en.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/install_amneziawg_en.sh
|
||||
wget -O install_amneziawg_en.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/install_amneziawg_en.sh
|
||||
sudo bash ./install_amneziawg_en.sh # Run (+ 2 reboots)
|
||||
|
||||
# Installation (Russian)
|
||||
wget -O install_amneziawg.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/install_amneziawg.sh
|
||||
wget -O install_amneziawg.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/install_amneziawg.sh
|
||||
sudo bash ./install_amneziawg.sh # Run (+ 2 reboots)
|
||||
|
||||
# Client management
|
||||
|
|
@ -610,13 +610,13 @@ For selectively routing the Russian segment through Cloudflare WARP via a BGP fe
|
|||
<b>A:</b> Download the updated scripts and replace them on the server:
|
||||
<pre>
|
||||
# English version:
|
||||
wget -O /root/awg/manage_amneziawg.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/manage_amneziawg_en.sh
|
||||
wget -O /root/awg/awg_common.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/awg_common_en.sh
|
||||
wget -O /root/awg/manage_amneziawg.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/manage_amneziawg_en.sh
|
||||
wget -O /root/awg/awg_common.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/awg_common_en.sh
|
||||
chmod 700 /root/awg/manage_amneziawg.sh /root/awg/awg_common.sh
|
||||
|
||||
# Russian version:
|
||||
wget -O /root/awg/manage_amneziawg.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/manage_amneziawg.sh
|
||||
wget -O /root/awg/awg_common.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/awg_common.sh
|
||||
wget -O /root/awg/manage_amneziawg.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/manage_amneziawg.sh
|
||||
wget -O /root/awg/awg_common.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/awg_common.sh
|
||||
chmod 700 /root/awg/manage_amneziawg.sh /root/awg/awg_common.sh
|
||||
</pre>
|
||||
Server reinstallation is not required.
|
||||
|
|
|
|||
24
README.md
24
README.md
|
|
@ -19,7 +19,7 @@
|
|||
<img src="https://img.shields.io/badge/Architecture-x86__64_|_ARM64_|_ARMv7-green" alt="x86_64 | ARM64 | ARMv7">
|
||||
<img src="https://img.shields.io/badge/AmneziaWG-2.0_|_3.x-blueviolet" alt="AmneziaWG 2.0 and 3.x">
|
||||
<a href="https://github.com/bivlked/amneziawg-installer/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="License"></a>
|
||||
<a href="https://github.com/bivlked/amneziawg-installer/releases"><img src="https://img.shields.io/badge/Installer_Version-5.29.0-blue" alt="Version"></a>
|
||||
<a href="https://github.com/bivlked/amneziawg-installer/releases"><img src="https://img.shields.io/badge/Installer_Version-5.30.0-blue" alt="Version"></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>
|
||||
<a href="https://github.com/bivlked/amneziawg-installer/stargazers"><img src="https://img.shields.io/github/stars/bivlked/amneziawg-installer?style=flat" alt="Stars"></a>
|
||||
<img src="https://img.shields.io/github/last-commit/bivlked/amneziawg-installer" alt="Last commit">
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
<!-- facts:begin -->
|
||||
<!-- Собирается scripts/update-facts-block.sh из данных репозитория.
|
||||
Руками не править: check-docs-consistency.sh сверит блок с источниками. -->
|
||||
**Факты на 2026-08-30.** Установщик 5.29.0. Сроки поддержки ОС проверены по данным вендоров на 2026-08-29.
|
||||
**Факты на 2026-09-01.** Установщик 5.30.0. Сроки поддержки ОС проверены по данным вендоров на 2026-08-29.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
## 🚀 Быстрый старт
|
||||
|
||||
```bash
|
||||
wget -O install_amneziawg.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/install_amneziawg.sh
|
||||
wget -O install_amneziawg.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/install_amneziawg.sh
|
||||
chmod +x install_amneziawg.sh
|
||||
sudo bash ./install_amneziawg.sh
|
||||
```
|
||||
|
|
@ -353,8 +353,8 @@ cat /sys/module/amneziawg/version # версия загруженного м
|
|||
|
||||
2. **Скачайте скрипт:**
|
||||
```bash
|
||||
wget -O install_amneziawg.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/install_amneziawg.sh
|
||||
# или: curl -fLo install_amneziawg.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/install_amneziawg.sh
|
||||
wget -O install_amneziawg.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/install_amneziawg.sh
|
||||
# или: curl -fLo install_amneziawg.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/install_amneziawg.sh
|
||||
```
|
||||
> На минимальном Debian curl может отсутствовать (wget обычно есть) - используйте `wget`. Сам curl установщик доставит на шаге 1.
|
||||
3. **Сделайте его исполняемым:**
|
||||
|
|
@ -369,7 +369,7 @@ cat /sys/module/amneziawg/version # версия загруженного м
|
|||
|
||||
> **English version:** Для вывода на английском используйте `install_amneziawg_en.sh`:
|
||||
> ```bash
|
||||
> wget -O install_amneziawg_en.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/install_amneziawg_en.sh
|
||||
> wget -O install_amneziawg_en.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/install_amneziawg_en.sh
|
||||
> sudo bash ./install_amneziawg_en.sh
|
||||
> ```
|
||||
> Английская версия функционально идентична; только сообщения и логи на английском.
|
||||
|
|
@ -519,11 +519,11 @@ sudo bash /root/awg/manage_amneziawg.sh <команда> [аргументы]
|
|||
|
||||
```bash
|
||||
# Установка (русский)
|
||||
wget -O install_amneziawg.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/install_amneziawg.sh
|
||||
wget -O install_amneziawg.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/install_amneziawg.sh
|
||||
sudo bash ./install_amneziawg.sh # Запуск (+ 2 перезагрузки)
|
||||
|
||||
# Установка (English)
|
||||
wget -O install_amneziawg_en.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/install_amneziawg_en.sh
|
||||
wget -O install_amneziawg_en.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/install_amneziawg_en.sh
|
||||
sudo bash ./install_amneziawg_en.sh # Запуск (+ 2 перезагрузки)
|
||||
|
||||
# Управление клиентами
|
||||
|
|
@ -611,13 +611,13 @@ sudo bash /root/awg/manage_amneziawg.sh restart # Перезапу
|
|||
<b>О:</b> Скачайте новый скрипт установки и замените скрипты управления на сервере:
|
||||
<pre>
|
||||
# Русская версия:
|
||||
wget -O /root/awg/manage_amneziawg.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/manage_amneziawg.sh
|
||||
wget -O /root/awg/awg_common.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/awg_common.sh
|
||||
wget -O /root/awg/manage_amneziawg.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/manage_amneziawg.sh
|
||||
wget -O /root/awg/awg_common.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/awg_common.sh
|
||||
chmod 700 /root/awg/manage_amneziawg.sh /root/awg/awg_common.sh
|
||||
|
||||
# Английская версия:
|
||||
wget -O /root/awg/manage_amneziawg.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/manage_amneziawg_en.sh
|
||||
wget -O /root/awg/awg_common.sh https://raw.githubusercontent.com/bivlked/amneziawg-installer/v5.29.0/awg_common_en.sh
|
||||
wget -O /root/awg/manage_amneziawg.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/manage_amneziawg_en.sh
|
||||
wget -O /root/awg/awg_common.sh https://github.com/bivlked/amneziawg-installer/releases/latest/download/awg_common_en.sh
|
||||
chmod 700 /root/awg/manage_amneziawg.sh /root/awg/awg_common.sh
|
||||
</pre>
|
||||
Переустановка сервера не требуется.
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
| Version | Supported |
|
||||
|---------|--------------------|
|
||||
| 5.29.x | :white_check_mark: |
|
||||
| 5.28.x | Security fixes only |
|
||||
| < 5.28 | :x: |
|
||||
| 5.30.x | :white_check_mark: |
|
||||
| 5.29.x | Security fixes only |
|
||||
| < 5.29 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
|
|
|||
120
awg_common.sh
120
awg_common.sh
|
|
@ -3,8 +3,8 @@
|
|||
# ==============================================================================
|
||||
# Общая библиотека функций для AmneziaWG 2.0
|
||||
# Автор: @bivlked
|
||||
# Версия: 5.29.0
|
||||
# Дата: 2026-08-30
|
||||
# Версия: 5.30.0
|
||||
# Дата: 2026-09-01
|
||||
# Репозиторий: https://github.com/bivlked/amneziawg-installer
|
||||
# ==============================================================================
|
||||
#
|
||||
|
|
@ -24,7 +24,7 @@ KEYS_DIR="${KEYS_DIR:-$AWG_DIR/keys}"
|
|||
# (обновили один файл, забыли второй) - иначе рассинхрон всплывает как
|
||||
# "command not found" в случайном месте. Бампается вместе с остальными версиями.
|
||||
# shellcheck disable=SC2034 # используется в manage-скрипте после source
|
||||
AWG_COMMON_VERSION="5.29.0"
|
||||
AWG_COMMON_VERSION="5.30.0"
|
||||
|
||||
# --- Автоочистка временных файлов ---
|
||||
# ВАЖНО: trap НЕ устанавливается здесь, чтобы не перезаписать trap вызывающего скрипта.
|
||||
|
|
@ -1772,6 +1772,120 @@ awg_warn_interface_disruption() {
|
|||
esac
|
||||
}
|
||||
|
||||
# awg_cps_decoded_size <строка I> [...] : суммарный ДЕКОДИРОВАННЫЙ размер в байтах.
|
||||
#
|
||||
# Зачем. Параметры I1-I5 попадают в атрибуты устройства, которые ядро отдаёт
|
||||
# одним netlink-сообщением дампа. Когда атрибуты устройства занимают почти весь
|
||||
# буфер, первый пир в него уже не помещается, и `wg_get_device_dump` не
|
||||
# продвигается и не падает: он возвращает ненулевую длину, netlink спрашивает
|
||||
# снова, и то же сообщение выдаётся бесконечно. Читатель крутится и растёт в
|
||||
# памяти; на роутере этого достаточно, чтобы уронить устройство. Разбор с кодом:
|
||||
# amneziawg-linux-kernel-module#228 (31 aug 2026), симптом у пользователя - #148.
|
||||
# Полоса чуть выше зацикливания отвечает `Unable to access interface: Message
|
||||
# too long`, и это ЛУЧШЕ: ошибка хотя бы останавливает. Формулировка ровно
|
||||
# такая: errno EMSGSIZE, glibc печатает «too long», и грепать свой лог надо по
|
||||
# этим словам.
|
||||
#
|
||||
# 🔴 Считается ДЕКОДИРОВАННЫЙ размер, а не длина строки. `<r 1000>` - восемь
|
||||
# символов и тысяча байт на проводе; сравнение по длине строки не заметило бы
|
||||
# ровно того случая, ради которого проверка написана.
|
||||
#
|
||||
# Набор тегов - пересечение двух реализаций, оно же документированный вендором
|
||||
# набор: `<b 0xHEX>` литеральные байты, `<r N>` случайные байты, `<rc N>`
|
||||
# случайные буквы, `<rd N>` случайные цифры, `<t>` метка времени (4 байта).
|
||||
# `<c>` считаем теми же 4 байтами: он есть в модуле ядра и отсутствует в
|
||||
# amneziawg-go, то есть влияет на переносимость, но не на размер.
|
||||
# Неизвестное не додумывается: выдуманное число хуже честного отказа. Но и
|
||||
# молчать о нём нельзя, поэтому всё неразобранное - неизвестный тег, оборванная
|
||||
# скобка, мусор между тегами, неправдоподобно большое число - метит результат
|
||||
# кодом возврата 2 «сумма занижена». Ноль с кодом 0 обязан означать «разобрал
|
||||
# всё, размера нет», иначе вызывающий примет мусор за пустоту.
|
||||
awg_cps_decoded_size() {
|
||||
local total=0 s tag n hex mat pre unknown=0
|
||||
for s in "$@"; do
|
||||
[[ -n "$s" ]] || continue
|
||||
# Форму без пробела принимаем НАРОЧНО, хотя обе реализации её
|
||||
# отвергают: ядро режет тег по пробелу (`strsep`), amneziawg-go - через
|
||||
# `strings.Fields`, поэтому `<r64>` для них неизвестный ключ и
|
||||
# интерфейс не поднимется. Считать её всё равно правильно: мы оцениваем
|
||||
# размер, и завышенная оценка приводит к предупреждению, а пропуск - к
|
||||
# зависанию. ⚠️ В конфиг такую форму писать нельзя.
|
||||
while [[ "$s" =~ \<[[:space:]]*([a-zA-Z]+)[[:space:]]*([^\>]*)\> ]]; do
|
||||
# 🔴 Совпадение сохраняем ДО case: внутри ветки `b` стоит свой
|
||||
# `[[ =~ ]]`, и он затирает BASH_REMATCH. Пока продвижение по
|
||||
# строке шло по первому `>`, это ничего не ломало; теперь строка
|
||||
# режется по совпадению, и взятое после case было бы hex-ом.
|
||||
mat="${BASH_REMATCH[0]}"
|
||||
tag="${BASH_REMATCH[1]}"
|
||||
n="${BASH_REMATCH[2]}"
|
||||
n="${n//[[:space:]]/}"
|
||||
# Всё, что стоит ПЕРЕД тегом, тегом не является. Без этой строки
|
||||
# `<><r 5>` проходил как честные пять байт.
|
||||
pre="${s%%"$mat"*}"
|
||||
[[ -z "${pre//[[:space:]]/}" ]] || unknown=1
|
||||
case "${tag,,}" in
|
||||
b)
|
||||
hex="${n#0x}"; hex="${hex#0X}"
|
||||
# Два hex-символа - один байт. Нечётный хвост не считаем:
|
||||
# такой тег реализации отвергают, додумывать за них нечего.
|
||||
if [[ "$hex" =~ ^[0-9a-fA-F]+$ && $(( ${#hex} % 2 )) -eq 0 ]]; then
|
||||
total=$(( total + ${#hex} / 2 ))
|
||||
else
|
||||
unknown=1
|
||||
fi
|
||||
;;
|
||||
r|rc|rd)
|
||||
# 🔴 `10#` обязателен. Без него bash читает число с ведущим
|
||||
# нулём как восьмеричное, `<r 08>` роняет арифметику, вся
|
||||
# функция возвращает ПУСТОТУ, и проверка на превышение
|
||||
# порога молча не срабатывает - тихий отказ ровно там, где
|
||||
# он опаснее всего. Замерено 31 aug 2026.
|
||||
# Длина ограничена девятью цифрами не из вкуса:
|
||||
# `<r 18446744073709551617>` переполняет 64-битную
|
||||
# арифметику bash и даёт в сумме ЕДИНИЦУ, то есть заведомо
|
||||
# опасное значение проскакивает под порогом. Замерено
|
||||
# 1 sep 2026. Вендор ограничивает r/rc/rd тысячей, так что
|
||||
# девять цифр - запас, а не рамка.
|
||||
if [[ "$n" =~ ^[0-9]{1,9}$ ]]; then
|
||||
total=$(( total + 10#$n ))
|
||||
else
|
||||
unknown=1
|
||||
fi
|
||||
;;
|
||||
t|c)
|
||||
# 🔴 У `<t>` и `<c>` полезной нагрузки нет, поэтому непустое
|
||||
# содержимое означает, что мы разобрали НЕ ТО. `<t <r 4096>`
|
||||
# ловится этой регуляркой как один тег со значением
|
||||
# `<r 4096`, и без проверки давал четыре байта с кодом
|
||||
# успеха: четыре тысячи байт превращались в четыре, а
|
||||
# диагностика шла в опасный вызов с чистой совестью.
|
||||
if [[ -z "$n" ]]; then
|
||||
total=$(( total + 4 ))
|
||||
else
|
||||
unknown=1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
unknown=1
|
||||
;;
|
||||
esac
|
||||
# 🔴 Продвигаемся за КОНЕЦ СОВПАДЕНИЯ. Форма `${s#*>}` резала до
|
||||
# первого `>` в строке, а он мог стоять ДО совпадения - тогда тот
|
||||
# же тег считался второй раз.
|
||||
s="${s#*"$mat"}"
|
||||
done
|
||||
# Непустой остаток тегом не является. Без этого `garbage` и оборванный
|
||||
# `<r 5` возвращали ноль с кодом 0, то есть «разобрал, размера нет».
|
||||
[[ -z "${s//[[:space:]]/}" ]] || unknown=1
|
||||
done
|
||||
printf '%s' "$total"
|
||||
# Код 2 значит «сумма занижена, встретилось неразобранное». Вызывающий
|
||||
# обязан сказать об этом вслух: занижение здесь неотличимо от измеренного
|
||||
# маленького размера, а это и есть ложное «проверил и сошлось».
|
||||
[[ "$unknown" -eq 0 ]] || return 2
|
||||
return 0
|
||||
}
|
||||
|
||||
# _awg_device_param_names : имена device-параметров AWG (2.0 и 3.0), которые
|
||||
# живут в секции [Interface] и которые syncconf НЕ снимает.
|
||||
_awg_device_param_names() {
|
||||
|
|
|
|||
123
awg_common_en.sh
123
awg_common_en.sh
|
|
@ -3,8 +3,8 @@
|
|||
# ==============================================================================
|
||||
# Shared function library for AmneziaWG 2.0
|
||||
# Author: @bivlked
|
||||
# Version: 5.29.0
|
||||
# Date: 2026-08-30
|
||||
# Version: 5.30.0
|
||||
# Date: 2026-09-01
|
||||
# Repository: https://github.com/bivlked/amneziawg-installer
|
||||
# ==============================================================================
|
||||
#
|
||||
|
|
@ -24,7 +24,7 @@ KEYS_DIR="${KEYS_DIR:-$AWG_DIR/keys}"
|
|||
# drifted apart (one file updated, the other not) - otherwise the mismatch shows
|
||||
# up as a "command not found" somewhere random. Bumped with the other versions.
|
||||
# shellcheck disable=SC2034 # used by the manage script after sourcing
|
||||
AWG_COMMON_VERSION="5.29.0"
|
||||
AWG_COMMON_VERSION="5.30.0"
|
||||
|
||||
# --- Auto-cleanup of temporary files ---
|
||||
# NOTE: trap is NOT set here to avoid overwriting the caller's trap handler.
|
||||
|
|
@ -1797,6 +1797,123 @@ awg_warn_interface_disruption() {
|
|||
esac
|
||||
}
|
||||
|
||||
# awg_cps_decoded_size <I string> [...] : total DECODED size in bytes.
|
||||
#
|
||||
# Why. The I1-I5 parameters land in the device attributes that the kernel emits
|
||||
# as a single netlink dump message. Once the device attributes fill most of the
|
||||
# buffer, the first peer no longer fits, and `wg_get_device_dump` neither
|
||||
# advances nor fails: it returns a non-zero length, netlink asks again, and the
|
||||
# same message is produced forever. The reader spins and grows; on a router that
|
||||
# is enough to take the box down. Written up with the code path in
|
||||
# amneziawg-linux-kernel-module#228 (31 aug 2026); the user-visible symptom is
|
||||
# #148. The band just above the looping one answers `Unable to access interface:
|
||||
# Message too long`, which is BETTER: an error at least stops. Those are the
|
||||
# exact words: errno EMSGSIZE, which glibc renders as "too long", so that is
|
||||
# what to grep a log for.
|
||||
#
|
||||
# 🔴 The DECODED size is counted, not the string length. `<r 1000>` is eight
|
||||
# characters and a thousand bytes on the wire; comparing string lengths would
|
||||
# miss exactly the case this check exists for.
|
||||
#
|
||||
# The tag set is the intersection of both implementations, which is also the set
|
||||
# the vendor documents: `<b 0xHEX>` literal bytes, `<r N>` random bytes,
|
||||
# `<rc N>` random letters, `<rd N>` random digits, `<t>` a timestamp (4 bytes).
|
||||
# `<c>` counts as the same 4 bytes: it exists in the kernel module and not in
|
||||
# amneziawg-go, so it affects portability rather than size.
|
||||
# Nothing unknown is guessed at: an invented number is worse than an honest
|
||||
# refusal. But it must not pass in silence either, so anything left unparsed -
|
||||
# an unknown tag, an unterminated bracket, junk between tags, an implausibly
|
||||
# large count - marks the result with exit code 2, "the sum is an under-count".
|
||||
# A zero with code 0 must mean "parsed everything, there is no size", otherwise
|
||||
# the caller reads junk as emptiness.
|
||||
awg_cps_decoded_size() {
|
||||
local total=0 s tag n hex mat pre unknown=0
|
||||
for s in "$@"; do
|
||||
[[ -n "$s" ]] || continue
|
||||
# The space-less form is accepted DELIBERATELY, even though both
|
||||
# implementations reject it: the kernel splits a tag on the space
|
||||
# (`strsep`) and amneziawg-go uses `strings.Fields`, so `<r64>` is an
|
||||
# unknown key to them and the interface will not come up. Counting it
|
||||
# is still right: this estimates a size, and an over-estimate leads to
|
||||
# a warning while a miss leads to a hang. ⚠️ Do not write this form
|
||||
# into a config.
|
||||
while [[ "$s" =~ \<[[:space:]]*([a-zA-Z]+)[[:space:]]*([^\>]*)\> ]]; do
|
||||
# 🔴 Save the match BEFORE the case: the `b` branch runs its own
|
||||
# `[[ =~ ]]`, which overwrites BASH_REMATCH. That was harmless
|
||||
# while the string advanced to the first `>`; now that it advances
|
||||
# by the match, a value read after the case would be the hex.
|
||||
mat="${BASH_REMATCH[0]}"
|
||||
tag="${BASH_REMATCH[1]}"
|
||||
n="${BASH_REMATCH[2]}"
|
||||
n="${n//[[:space:]]/}"
|
||||
# Whatever sits BEFORE the tag is not a tag. Without this line
|
||||
# `<><r 5>` passed as an honest five bytes.
|
||||
pre="${s%%"$mat"*}"
|
||||
[[ -z "${pre//[[:space:]]/}" ]] || unknown=1
|
||||
case "${tag,,}" in
|
||||
b)
|
||||
hex="${n#0x}"; hex="${hex#0X}"
|
||||
# Two hex characters make a byte. An odd tail is not
|
||||
# counted: implementations reject such a tag anyway.
|
||||
if [[ "$hex" =~ ^[0-9a-fA-F]+$ && $(( ${#hex} % 2 )) -eq 0 ]]; then
|
||||
total=$(( total + ${#hex} / 2 ))
|
||||
else
|
||||
unknown=1
|
||||
fi
|
||||
;;
|
||||
r|rc|rd)
|
||||
# 🔴 `10#` is mandatory. Without it bash reads a
|
||||
# leading-zero number as octal, `<r 08>` breaks the
|
||||
# arithmetic, the whole function returns EMPTY, and the
|
||||
# threshold check silently never fires - a silent failure
|
||||
# exactly where it hurts most. Measured 31 aug 2026.
|
||||
# The nine-digit limit is not a matter of taste:
|
||||
# `<r 18446744073709551617>` overflows 64-bit bash
|
||||
# arithmetic and sums to ONE, so a plainly dangerous value
|
||||
# slips under the threshold. Measured 1 sep 2026. The
|
||||
# vendor caps r/rc/rd at a thousand, so nine digits is
|
||||
# headroom rather than a constraint.
|
||||
if [[ "$n" =~ ^[0-9]{1,9}$ ]]; then
|
||||
total=$(( total + 10#$n ))
|
||||
else
|
||||
unknown=1
|
||||
fi
|
||||
;;
|
||||
t|c)
|
||||
# 🔴 `<t>` and `<c>` carry no payload, so non-empty content
|
||||
# means we parsed the WRONG thing. `<t <r 4096>` matches
|
||||
# this regex as a single tag whose value is `<r 4096`, and
|
||||
# without the check it counted four bytes and returned
|
||||
# success: four thousand bytes became four, and diagnostics
|
||||
# walked into the dangerous call with a clear conscience.
|
||||
if [[ -z "$n" ]]; then
|
||||
total=$(( total + 4 ))
|
||||
else
|
||||
unknown=1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
unknown=1
|
||||
;;
|
||||
esac
|
||||
# 🔴 Advance past the END OF THE MATCH. The `${s#*>}` form cut
|
||||
# to the first `>` in the string, which could sit BEFORE the
|
||||
# match - and then the same tag was counted twice.
|
||||
s="${s#*"$mat"}"
|
||||
done
|
||||
# A non-empty remainder is not a tag. Without this, `garbage` and an
|
||||
# unterminated `<r 5` returned zero with code 0, that is "parsed, no
|
||||
# size".
|
||||
[[ -z "${s//[[:space:]]/}" ]] || unknown=1
|
||||
done
|
||||
printf '%s' "$total"
|
||||
# A code of 2 means "the sum is an under-count, something was not parsed".
|
||||
# The caller must say so out loud: an under-count is indistinguishable from
|
||||
# a genuinely small size, and that is precisely a false "checked, fine".
|
||||
[[ "$unknown" -eq 0 ]] || return 2
|
||||
return 0
|
||||
}
|
||||
|
||||
# _awg_device_param_names : names of the AWG device parameters (2.0 and 3.0)
|
||||
# that live in the [Interface] section and that syncconf does NOT clear.
|
||||
_awg_device_param_names() {
|
||||
|
|
|
|||
|
|
@ -259,8 +259,12 @@ signs bytes that then change, and the tag fails verification. The trusted
|
|||
comment names the tag, so signatures cannot be carried over from the previous
|
||||
release; re-sign every time.
|
||||
|
||||
Because the workflow has never run for a tag that was not there before, the
|
||||
first time is worth doing on a throwaway tag: a semver pre-release suffix
|
||||
(`vX.Y.Z-rc1`) publishes as a pre-release and does not displace Latest. Delete
|
||||
the pre-release and its tag afterwards. Design and threat model:
|
||||
`docs/SIGNING_DESIGN.md`.
|
||||
The signing path has run on a real tag since this was first written:
|
||||
v5.29.0 on 30 August 2026 published thirteen assets, six of them signatures.
|
||||
A release is therefore no longer the first execution of untested code. A
|
||||
throwaway tag stays the cheap way to rehearse a change to the workflow itself,
|
||||
which cannot be exercised any other way because only a tag push triggers it: a
|
||||
semver pre-release suffix (`vX.Y.Z-rc1`) publishes as a pre-release and does
|
||||
not displace Latest. Delete the pre-release and its tag afterwards.
|
||||
|
||||
Design and threat model: `docs/SIGNING_DESIGN.md`.
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@ fi
|
|||
# ==============================================================================
|
||||
# Скрипт для установки и настройки AmneziaWG 2.0 на Ubuntu/Debian серверах
|
||||
# Автор: @bivlked
|
||||
# Версия: 5.29.0
|
||||
# Дата: 2026-08-30
|
||||
# Версия: 5.30.0
|
||||
# Дата: 2026-09-01
|
||||
# Репозиторий: https://github.com/bivlked/amneziawg-installer
|
||||
# ==============================================================================
|
||||
|
||||
# --- Безопасный режим и Константы ---
|
||||
set -o pipefail
|
||||
|
||||
SCRIPT_VERSION="5.29.0"
|
||||
SCRIPT_VERSION="5.30.0"
|
||||
AWG_DIR="/root/awg"
|
||||
CONFIG_FILE="$AWG_DIR/awgsetup_cfg.init"
|
||||
STATE_FILE="$AWG_DIR/setup_state"
|
||||
|
|
@ -34,8 +34,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="06407e51e5c999fd52bcdcb1b27e0b621b702083b36a065d5f2df529e8325629"
|
||||
MANAGE_SCRIPT_SHA256="601827bf9c403c230d79e365731dd2b28ad091bf63204382c129f23d5412d1b9"
|
||||
COMMON_SCRIPT_SHA256="354ab9609edc0d057c15b6d0ee9342377d7848850df0da6ff72e5a1504d3749b"
|
||||
MANAGE_SCRIPT_SHA256="a872e33aa43887ba64504db3be91453ab965de724e10056832565b96872075af"
|
||||
|
||||
# AmneziaWG 2.0 пин (H0, 31 jul 2026). Upstream влил AmneziaWG 3.0 в default-ветку
|
||||
# amneziawg-linux-kernel-module, и PPA переключился на 3.0. Тогда на ядрах старее
|
||||
|
|
@ -2508,7 +2508,9 @@ check_service_status() {
|
|||
ok=0
|
||||
fi
|
||||
|
||||
if ! awg show 2>/dev/null | grep -q "interface: awg0"; then
|
||||
# timeout: перезапуск установщика поверх сервера с раздутыми вручную
|
||||
# I1-I5 иначе повис бы здесь навсегда (#228). Отказ ниже и так громкий.
|
||||
if ! timeout 10 awg show 2>/dev/null | grep -q "interface: awg0"; then
|
||||
log_error "awg show не видит интерфейс!"
|
||||
ok=0
|
||||
fi
|
||||
|
|
@ -2528,7 +2530,7 @@ check_service_status() {
|
|||
fi
|
||||
|
||||
# Проверка AWG 2.0 параметров
|
||||
if awg show awg0 2>/dev/null | grep -q "jc:"; then
|
||||
if timeout 10 awg show awg0 2>/dev/null | grep -q "jc:"; then
|
||||
log "AWG 2.0 параметры активны."
|
||||
else
|
||||
log_warn "AWG 2.0 параметры не обнаружены в awg show."
|
||||
|
|
@ -2662,7 +2664,11 @@ create_diagnostic_report() {
|
|||
systemctl status awg-quick@awg0 --no-pager -l 2>/dev/null || echo "Service not found"
|
||||
echo ""
|
||||
echo "--- AWG Status ---"
|
||||
awg show 2>/dev/null || echo "awg show failed"
|
||||
# 🔴 timeout обязателен: при переросших I1-I5 дамп интерфейса
|
||||
# зацикливается (amneziawg-linux-kernel-module#228), а это ровно та
|
||||
# команда, которую документация просит приложить к обращению. Без
|
||||
# границы попавший в дефект не смог бы собрать даже отчёт о нём.
|
||||
timeout 10 awg show 2>/dev/null || echo "awg show failed or timed out"
|
||||
echo ""
|
||||
echo "--- AWG Version ---"
|
||||
awg --version 2>/dev/null || echo "awg --version failed"
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ fi
|
|||
# ==============================================================================
|
||||
# AmneziaWG 2.0 installation and configuration script for Ubuntu/Debian servers
|
||||
# Author: @bivlked
|
||||
# Version: 5.29.0
|
||||
# Date: 2026-08-30
|
||||
# Version: 5.30.0
|
||||
# Date: 2026-09-01
|
||||
# Repository: https://github.com/bivlked/amneziawg-installer
|
||||
# ==============================================================================
|
||||
|
||||
# --- Safe mode and Constants ---
|
||||
set -o pipefail
|
||||
SCRIPT_VERSION="5.29.0"
|
||||
SCRIPT_VERSION="5.30.0"
|
||||
|
||||
AWG_DIR="/root/awg"
|
||||
CONFIG_FILE="$AWG_DIR/awgsetup_cfg.init"
|
||||
|
|
@ -34,8 +34,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="0a3e0dced9b4e7e8efb65719ac537ec51856065687985fc1e5d82cf99bdfa227"
|
||||
MANAGE_SCRIPT_SHA256="8cecd8f30b87e660347e9b7272686b996d085d3d12dc58f1e96ad54ddd1d132a"
|
||||
COMMON_SCRIPT_SHA256="e90bc0c90969fb7fd61cddf6b5ed5b415cae32b81dc205e9e3b23011d0b93cd9"
|
||||
MANAGE_SCRIPT_SHA256="15d5b3e8144a3c69f3e847eb219de6802fbbb454a08637eeac198b4ff6ff29d3"
|
||||
|
||||
# AmneziaWG 2.0 pin (H0, 31 jul 2026). Upstream merged AmneziaWG 3.0 into the
|
||||
# amneziawg-linux-kernel-module default branch, and the PPA switched to it. Back
|
||||
|
|
@ -2535,7 +2535,10 @@ check_service_status() {
|
|||
ok=0
|
||||
fi
|
||||
|
||||
if ! awg show 2>/dev/null | grep -q "interface: awg0"; then
|
||||
# timeout: re-running the installer over a server with hand-inflated
|
||||
# I1-I5 would otherwise hang here forever (#228). The failure below is
|
||||
# already loud.
|
||||
if ! timeout 10 awg show 2>/dev/null | grep -q "interface: awg0"; then
|
||||
log_error "awg show cannot see interface!"
|
||||
ok=0
|
||||
fi
|
||||
|
|
@ -2555,7 +2558,7 @@ check_service_status() {
|
|||
fi
|
||||
|
||||
# AWG 2.0 parameter check
|
||||
if awg show awg0 2>/dev/null | grep -q "jc:"; then
|
||||
if timeout 10 awg show awg0 2>/dev/null | grep -q "jc:"; then
|
||||
log "AWG 2.0 parameters active."
|
||||
else
|
||||
log_warn "AWG 2.0 parameters not detected in awg show."
|
||||
|
|
@ -2696,7 +2699,12 @@ create_diagnostic_report() {
|
|||
systemctl status awg-quick@awg0 --no-pager -l 2>/dev/null || echo "Service not found"
|
||||
echo ""
|
||||
echo "--- AWG Status ---"
|
||||
awg show 2>/dev/null || echo "awg show failed"
|
||||
# 🔴 The timeout is mandatory: oversized I1-I5 make the interface dump
|
||||
# loop (amneziawg-linux-kernel-module#228), and this is precisely the
|
||||
# command the documentation asks people to attach to a report. Without
|
||||
# a bound, someone hitting the defect could not even collect a report
|
||||
# about it.
|
||||
timeout 10 awg show 2>/dev/null || echo "awg show failed or timed out"
|
||||
echo ""
|
||||
echo "--- AWG Version ---"
|
||||
awg --version 2>/dev/null || echo "awg --version failed"
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ fi
|
|||
# ==============================================================================
|
||||
# Скрипт для управления пользователями (пирами) AmneziaWG 2.0
|
||||
# Автор: @bivlked
|
||||
# Версия: 5.29.0
|
||||
# Дата: 2026-08-30
|
||||
# Версия: 5.30.0
|
||||
# Дата: 2026-09-01
|
||||
# Репозиторий: https://github.com/bivlked/amneziawg-installer
|
||||
# ==============================================================================
|
||||
|
||||
# --- Безопасный режим и Константы ---
|
||||
# shellcheck disable=SC2034
|
||||
SCRIPT_VERSION="5.29.0"
|
||||
SCRIPT_VERSION="5.30.0"
|
||||
set -o pipefail
|
||||
AWG_DIR="/root/awg"
|
||||
SERVER_CONF_FILE="/etc/amnezia/amneziawg/awg0.conf"
|
||||
|
|
@ -1301,8 +1301,13 @@ check_server() {
|
|||
# Раньше awg show вызывался через process substitution без проверки exit code,
|
||||
# из-за чего check мог отрапортовать "Состояние OK" даже когда awg упал.
|
||||
# Теперь захватываем вывод и проверяем exit code (audit).
|
||||
local _awg_out
|
||||
if ! _awg_out=$(awg show awg0 2>&1); then
|
||||
local _awg_out _check_rc=0
|
||||
# timeout: без него переросшие I1-I5 подвешивают check намертво
|
||||
# (amneziawg-linux-kernel-module#228). Отказ здесь и так громкий, границы
|
||||
# по времени не хватало.
|
||||
if ! _awg_out=$(timeout 10 awg show awg0 2>&1); then
|
||||
_check_rc=$?
|
||||
[[ "$_check_rc" -eq 124 ]] && _awg_out="awg show не ответил за 10 секунд - похоже на зацикленный дамп интерфейса, проверьте размер I1-I5"
|
||||
log_error " - awg show awg0 завершился с ошибкой:"
|
||||
while IFS= read -r _l; do log_error " $_l"; done <<< "$_awg_out"
|
||||
ok=0
|
||||
|
|
@ -1373,6 +1378,64 @@ _diag_line() {
|
|||
}
|
||||
|
||||
# Главная функция: пробегается по health-checks + опционально сравнивает с оператором
|
||||
# _diag_cps_guard : размер CPS из КОНФИГА, до любого `awg show` В ДИАГНОСТИКЕ.
|
||||
# ⚠️ Это утверждение про diagnose, а не про весь скрипт: у check, stats, show
|
||||
# и list своей проверки размера нет, они защищены только таймаутом.
|
||||
#
|
||||
# Отдельной функцией, а не куском diagnose_server, по двум причинам. Во-первых,
|
||||
# так её можно прогнать в тесте: `source` самого скрипта запускает main и
|
||||
# печатает справку, а извлечение функции awk-диапазоном - принятый здесь приём.
|
||||
# Во-вторых, у блока появляется имя, по которому видно, что он делает.
|
||||
#
|
||||
# Пишет в вызывающего две переменные: `_cps_unsafe` (1 = дальше интерфейс не
|
||||
# читать) и `warn` (счётчик предупреждений диагностики).
|
||||
_diag_cps_guard() {
|
||||
# 0. Размер CPS - СЧИТАЕТСЯ ИЗ ФАЙЛА И ДО ЛЮБОГО `awg show`.
|
||||
# Порядок тут не стилистический. Дальше I1 читается из `awg show`, а именно
|
||||
# этот вызов и зависает, когда I1-I5 слишком велики: дамп интерфейса
|
||||
# перестаёт продвигаться и повторяется бесконечно (upstream #228). Проверка,
|
||||
# стоящая после него, не напечаталась бы никогда - ровно в том случае, ради
|
||||
# которого написана.
|
||||
local _cps_i _cps_total=0 _cps_vals=()
|
||||
local _cps_rc=0
|
||||
if [[ -r "$SERVER_CONF_FILE" ]]; then
|
||||
for _cps_i in I1 I2 I3 I4 I5; do
|
||||
_cps_vals+=("$(sed -n "s/^[[:space:]]*${_cps_i}[[:space:]]*=[[:space:]]*//p" "$SERVER_CONF_FILE" | head -n 1)")
|
||||
done
|
||||
_cps_total=$(awg_cps_decoded_size "${_cps_vals[@]}") || _cps_rc=$?
|
||||
# Порог с запасом. Апстрим называет опасной зоной примерно 3.5 КБ, но
|
||||
# точная граница зависит от длины имени интерфейса, наличия ключа защиты
|
||||
# заголовков и семейства адреса каждого пира, то есть считать её у себя
|
||||
# мы не можем. Наш генератор даёт максимум 256 байт, документированные
|
||||
# рецепты - до 128, поэтому килобайт заведомо означает правку руками, а
|
||||
# до зацикливания остаётся кратный запас.
|
||||
if [[ "$_cps_total" =~ ^[0-9]+$ && "$_cps_total" -gt 1024 ]]; then
|
||||
_cps_unsafe=1
|
||||
_diag_line WARN "CPS (I1-I5) занимает ${_cps_total} байт - это много"
|
||||
echo " Слишком большие I1-I5 подвешивают чтение интерфейса: дамп перестаёт"
|
||||
echo " продвигаться и повторяется бесконечно, а на роутере это роняет устройство."
|
||||
# Путь берём из переменной, а не литералом: сообщение обязано
|
||||
# называть тот файл, который мы прочитали.
|
||||
echo " Fix: сократить I1-I5 в $SERVER_CONF_FILE, затем"
|
||||
echo " sudo systemctl restart awg-quick@awg0"
|
||||
warn=$((warn+1))
|
||||
elif [[ "$_cps_rc" -ne 0 || ! "$_cps_total" =~ ^[0-9]+$ ]]; then
|
||||
# Разобрать не удалось. Занижение неотличимо от «размер маленький»,
|
||||
# поэтому молчать нельзя: считаем размер небезопасным и говорим об
|
||||
# этом, а не выдаём непроверенное за проверенное.
|
||||
_cps_unsafe=1
|
||||
_diag_line WARN "размер CPS (I1-I5) определить не удалось - в конфиге нераспознанные теги"
|
||||
warn=$((warn+1))
|
||||
fi
|
||||
else
|
||||
# Тот же класс: «не смог прочитать» обязано звучать, иначе следующий
|
||||
# шаг пойдёт на опасный дамп с видом, будто проверка прошла.
|
||||
_cps_unsafe=1
|
||||
_diag_line WARN "конфиг $SERVER_CONF_FILE не прочитан - размер CPS не проверен"
|
||||
warn=$((warn+1))
|
||||
fi
|
||||
}
|
||||
|
||||
diagnose_server() {
|
||||
local carrier="${CLI_CARRIER}"
|
||||
local ok=0 warn=0 fail=0
|
||||
|
|
@ -1384,6 +1447,9 @@ diagnose_server() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
local _cps_unsafe=0
|
||||
_diag_cps_guard
|
||||
|
||||
# 1. Kernel module
|
||||
if lsmod 2>/dev/null | awk '$1 == "amneziawg" {f=1} END {exit !f}'; then
|
||||
local _d_mod_ver _d_mod_build
|
||||
|
|
@ -1492,21 +1558,88 @@ diagnose_server() {
|
|||
fi
|
||||
|
||||
# 7. Peer count
|
||||
local peer_count
|
||||
peer_count=$(awg show awg0 peers 2>/dev/null | wc -l)
|
||||
_diag_line INFO "Peers сконфигурировано: $peer_count"
|
||||
# 🔴 Дамп интерфейса пропускается, если размер CPS признан небезопасным:
|
||||
# предупредить и тут же полезть в тот самый вызов, который зацикливается,
|
||||
# значит не защитить никого. Таймаут ограничивает время, но не память, а
|
||||
# растущий читатель на роутере как раз память и съедает.
|
||||
local peer_count _peers_out _show_rc=0
|
||||
if [[ "$_cps_unsafe" -eq 1 ]]; then
|
||||
_diag_line WARN "чтение интерфейса пропущено из-за размера CPS (см. выше)"
|
||||
warn=$((warn+1))
|
||||
elif _peers_out=$(timeout 10 awg show awg0 peers 2>/dev/null); then
|
||||
peer_count=$(printf '%s\n' "$_peers_out" | grep -c . || true)
|
||||
_diag_line INFO "Peers сконфигурировано: $peer_count"
|
||||
else
|
||||
# 🔴 Раньше здесь стоял конвейер с `wc -l`, и код возврата терялся в
|
||||
# нём целиком: при таймауте печаталось «Peers сконфигурировано: 0», то
|
||||
# есть «не смог проверить» читалось как «проверил, пиров нет». Код 124
|
||||
# называется отдельно - он означает ровно тот зацикленный дамп, ради
|
||||
# которого написана вся эта проверка.
|
||||
_show_rc=$?
|
||||
if [[ "$_show_rc" -eq 124 ]]; then
|
||||
_diag_line FAIL "awg show не ответил за 10 секунд - похоже на зацикленный дамп интерфейса"
|
||||
fail=$((fail+1))
|
||||
# 🔴 Зацикливание живёт на ИНТЕРФЕЙСЕ, а сторож читает ФАЙЛ, и эти
|
||||
# два состояния расходятся: у выполнившего наш же совет «сократить
|
||||
# I1-I5» файл уже мал, а интерфейс ещё нет. Раз вызов только что
|
||||
# завис, шаг 8 обязан в него не ходить - иначе мы удваиваем ровно
|
||||
# ту экспозицию, ради которой всё это написано.
|
||||
_cps_unsafe=1
|
||||
else
|
||||
_diag_line WARN "awg show завершился с кодом $_show_rc - число пиров не определено"
|
||||
warn=$((warn+1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# 8. AWG params snapshot (один вызов awg show вместо четырёх)
|
||||
local _awg_show jc jmin jmax i1
|
||||
_awg_show=$(awg show awg0 2>/dev/null)
|
||||
jc=$(awk '/^[[:space:]]*jc:/ {print $2; exit}' <<< "$_awg_show")
|
||||
jmin=$(awk '/^[[:space:]]*jmin:/ {print $2; exit}' <<< "$_awg_show")
|
||||
jmax=$(awk '/^[[:space:]]*jmax:/ {print $2; exit}' <<< "$_awg_show")
|
||||
i1=$(awk -F': ' '/^[[:space:]]*i1:/ {print $2; exit}' <<< "$_awg_show")
|
||||
_diag_line INFO "AWG params: Jc=${jc:-?} Jmin=${jmin:-?} Jmax=${jmax:-?} I1=${i1:-absent}"
|
||||
# 🔴 Состояние ТРЁХЗНАЧНОЕ, а не «строка пустая». Пустая строка одинаково
|
||||
# означает «параметра нет» и «мы до интерфейса не дошли», и это
|
||||
# противоположные утверждения: сторож срабатывает ровно тогда, когда I1
|
||||
# огромен, а прежняя форма ${i1:-absent} печатала в этот момент
|
||||
# «I1=absent». Соврать про I1 в отчёте о слишком большом I1 - худшее, что
|
||||
# эта команда могла сделать.
|
||||
local _awg_show="" _awg_read=0 _show2_rc=0 jc jmin jmax i1
|
||||
if [[ "$_cps_unsafe" -ne 1 ]]; then
|
||||
# stderr не выбрасываем: полоса ВЫШЕ зацикливания отвечает
|
||||
# `Unable to access interface: Message too long`, и это единственная
|
||||
# строка, которая называет причину. Голый код возврата вместо неё -
|
||||
# потеря диагностики на ровном месте.
|
||||
if _awg_show=$(timeout 10 awg show awg0 2>&1); then
|
||||
_awg_read=1
|
||||
else
|
||||
_show2_rc=$?
|
||||
if [[ "$_show2_rc" -eq 124 ]]; then
|
||||
_diag_line FAIL "awg show не ответил за 10 секунд - параметры интерфейса не прочитаны"
|
||||
fail=$((fail+1))
|
||||
_cps_unsafe=1
|
||||
else
|
||||
_diag_line WARN "awg show завершился с кодом $_show2_rc - параметры интерфейса не прочитаны${_awg_show:+: ${_awg_show%%$'\n'*}}"
|
||||
warn=$((warn+1))
|
||||
fi
|
||||
_awg_show=""
|
||||
fi
|
||||
fi
|
||||
if [[ "$_awg_read" -eq 1 ]]; then
|
||||
jc=$(awk '/^[[:space:]]*jc:/ {print $2; exit}' <<< "$_awg_show")
|
||||
jmin=$(awk '/^[[:space:]]*jmin:/ {print $2; exit}' <<< "$_awg_show")
|
||||
jmax=$(awk '/^[[:space:]]*jmax:/ {print $2; exit}' <<< "$_awg_show")
|
||||
i1=$(awk -F': ' '/^[[:space:]]*i1:/ {print $2; exit}' <<< "$_awg_show")
|
||||
_diag_line INFO "AWG params: Jc=${jc:-?} Jmin=${jmin:-?} Jmax=${jmax:-?} I1=${i1:-absent}"
|
||||
else
|
||||
_diag_line INFO "AWG params: интерфейс не прочитан, значения не проверялись"
|
||||
fi
|
||||
|
||||
# 9. Carrier comparison
|
||||
if [[ -n "$carrier" ]]; then
|
||||
# 🔴 Сравнивать непрочитанное нельзя. У профиля со значением i1_mode=absent
|
||||
# проверка `[[ -z "$i1" ]]` на непрочитанном интерфейсе давала зелёный OK
|
||||
# «I1 отсутствует» - галочку, удостоверяющую ровно то условие, из-за
|
||||
# которого мы отказались смотреть, да ещё и в итоговом счётчике.
|
||||
if [[ -n "$carrier" && "$_awg_read" -ne 1 ]]; then
|
||||
echo ""
|
||||
log "Сравнение с профилем оператора '$carrier'..."
|
||||
_diag_line WARN "пропущено: параметры интерфейса не прочитаны, сравнивать не с чем"
|
||||
warn=$((warn+1))
|
||||
elif [[ -n "$carrier" ]]; then
|
||||
echo ""
|
||||
log "Сравнение с профилем оператора '$carrier'..."
|
||||
local row
|
||||
|
|
@ -1624,8 +1757,23 @@ list_clients() {
|
|||
|
||||
# Однопроходный парсинг awg show dump: pubkey → handshake timestamp
|
||||
local -A _pk_to_hs
|
||||
local awg_dump
|
||||
awg_dump=$(awg show awg0 dump 2>/dev/null) || awg_dump=""
|
||||
# 🔴 Пустой дамп НЕ означает «ни у кого нет рукопожатия». Таймаут добавлен
|
||||
# этим же релизом, и без разбора отказа он превратил заметное зависание в
|
||||
# правдоподобную и полностью неверную таблицу: всем клиентам «Нет
|
||||
# handshake», в --json тем же составом. Честное состояние в коде уже есть -
|
||||
# это дефолт "Нет данных"/no_data, надо просто его не затирать.
|
||||
local awg_dump _dump_ok=0 _dump_rc=0
|
||||
if awg_dump=$(timeout 10 awg show awg0 dump 2>/dev/null); then
|
||||
_dump_ok=1
|
||||
else
|
||||
_dump_rc=$?
|
||||
awg_dump=""
|
||||
if [[ "$_dump_rc" -eq 124 ]]; then
|
||||
log_warn "awg show dump не ответил за 10 секунд - состояние клиентов неизвестно (похоже на зацикленный дамп: проверьте размер I1-I5)"
|
||||
else
|
||||
log_warn "awg show dump завершился с кодом $_dump_rc - состояние клиентов неизвестно"
|
||||
fi
|
||||
fi
|
||||
if [[ -n "$awg_dump" ]]; then
|
||||
# shellcheck disable=SC2034
|
||||
while IFS=$'\t' read -r _dpk _dpsk _dep _daips _dhs _drx _dtx _dka; do
|
||||
|
|
@ -1690,7 +1838,11 @@ list_clients() {
|
|||
|
||||
local current_pk="${_name_to_pk[$name]:-}"
|
||||
|
||||
if [[ -n "$current_pk" ]]; then
|
||||
if [[ -n "$current_pk" && "$_dump_ok" -ne 1 ]]; then
|
||||
# Ключ известен, состояние интерфейса - нет. Оставляем дефолт
|
||||
# "Нет данных"/no_data: он и означает «не смотрели».
|
||||
pk="${current_pk:0:10}..."
|
||||
elif [[ -n "$current_pk" ]]; then
|
||||
pk="${current_pk:0:10}..."
|
||||
local handshake="${_pk_to_hs[$current_pk]:-0}"
|
||||
if [[ "$handshake" =~ ^[0-9]+$ && "$handshake" -gt 0 ]]; then
|
||||
|
|
@ -1795,9 +1947,14 @@ stats_clients() {
|
|||
fi
|
||||
|
||||
# Получаем данные awg show awg0
|
||||
local awg_dump
|
||||
awg_dump=$(awg show awg0 dump 2>/dev/null) || {
|
||||
log_error "Ошибка получения данных awg show."
|
||||
local awg_dump _stats_rc=0
|
||||
awg_dump=$(timeout 10 awg show awg0 dump 2>/dev/null) || {
|
||||
_stats_rc=$?
|
||||
if [[ "$_stats_rc" -eq 124 ]]; then
|
||||
log_error "awg show dump не ответил за 10 секунд - похоже на зацикленный дамп интерфейса, проверьте размер I1-I5."
|
||||
else
|
||||
log_error "Ошибка получения данных awg show."
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
|
|
@ -2313,7 +2470,15 @@ case $COMMAND in
|
|||
|
||||
show)
|
||||
log "Статус AmneziaWG 2.0..."
|
||||
if ! awg show; then log_error "Ошибка awg show."; _cmd_rc=1; fi
|
||||
if ! timeout 10 awg show; then
|
||||
_show_cmd_rc=$?
|
||||
if [[ "$_show_cmd_rc" -eq 124 ]]; then
|
||||
log_error "awg show не ответил за 10 секунд - похоже на зацикленный дамп интерфейса, проверьте размер I1-I5."
|
||||
else
|
||||
log_error "Ошибка awg show."
|
||||
fi
|
||||
_cmd_rc=1
|
||||
fi
|
||||
;;
|
||||
|
||||
restart)
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ fi
|
|||
# ==============================================================================
|
||||
# AmneziaWG 2.0 peer management script
|
||||
# Author: @bivlked
|
||||
# Version: 5.29.0
|
||||
# Date: 2026-08-30
|
||||
# Version: 5.30.0
|
||||
# Date: 2026-09-01
|
||||
# Repository: https://github.com/bivlked/amneziawg-installer
|
||||
# ==============================================================================
|
||||
|
||||
# --- Safe mode and Constants ---
|
||||
# shellcheck disable=SC2034
|
||||
SCRIPT_VERSION="5.29.0"
|
||||
SCRIPT_VERSION="5.30.0"
|
||||
set -o pipefail
|
||||
AWG_DIR="/root/awg"
|
||||
SERVER_CONF_FILE="/etc/amnezia/amneziawg/awg0.conf"
|
||||
|
|
@ -1316,8 +1316,13 @@ check_server() {
|
|||
# Previously awg show was called via process substitution without an exit
|
||||
# code check, so check could report "Status OK" even when awg crashed.
|
||||
# Now we capture the output and check the exit code (audit).
|
||||
local _awg_out
|
||||
if ! _awg_out=$(awg show awg0 2>&1); then
|
||||
local _awg_out _check_rc=0
|
||||
# timeout: without it oversized I1-I5 hang check outright
|
||||
# (amneziawg-linux-kernel-module#228). The failure here was already loud;
|
||||
# what was missing is a bound on time.
|
||||
if ! _awg_out=$(timeout 10 awg show awg0 2>&1); then
|
||||
_check_rc=$?
|
||||
[[ "$_check_rc" -eq 124 ]] && _awg_out="awg show did not answer within 10 seconds - looks like a looping interface dump, check the size of I1-I5"
|
||||
log_error " - awg show awg0 failed:"
|
||||
while IFS= read -r _l; do log_error " $_l"; done <<< "$_awg_out"
|
||||
ok=0
|
||||
|
|
@ -1388,6 +1393,64 @@ _diag_line() {
|
|||
}
|
||||
|
||||
# Main: runs health-checks + optional carrier comparison
|
||||
# _diag_cps_guard : CPS size from the CONFIG, before any `awg show`
|
||||
# IN DIAGNOSE. ⚠️ That claim is about diagnose, not about the whole script:
|
||||
# check, stats, show and list have no size check of their own and are
|
||||
# protected by a timeout alone.
|
||||
#
|
||||
# A function rather than a slab inside diagnose_server, for two reasons. First,
|
||||
# it can then be exercised by a test: sourcing the script itself runs main and
|
||||
# prints the usage text, whereas extracting a function by an awk range is the
|
||||
# established approach here. Second, the block gets a name that says what it is.
|
||||
#
|
||||
# Writes two variables into the caller: `_cps_unsafe` (1 = do not read the
|
||||
# interface further on) and `warn` (the diagnostic warning counter).
|
||||
_diag_cps_guard() {
|
||||
# 0. CPS size - COMPUTED FROM THE FILE AND BEFORE ANY `awg show`.
|
||||
# The order here is not cosmetic. Further down I1 is read from `awg show`,
|
||||
# and that very call is what hangs when I1-I5 grow too large: the interface
|
||||
# dump stops advancing and repeats forever (upstream #228). A check placed
|
||||
# after it would never print - in exactly the case it exists for.
|
||||
local _cps_i _cps_total=0 _cps_vals=()
|
||||
local _cps_rc=0
|
||||
if [[ -r "$SERVER_CONF_FILE" ]]; then
|
||||
for _cps_i in I1 I2 I3 I4 I5; do
|
||||
_cps_vals+=("$(sed -n "s/^[[:space:]]*${_cps_i}[[:space:]]*=[[:space:]]*//p" "$SERVER_CONF_FILE" | head -n 1)")
|
||||
done
|
||||
_cps_total=$(awg_cps_decoded_size "${_cps_vals[@]}") || _cps_rc=$?
|
||||
# A threshold with room to spare. Upstream puts the dangerous zone at
|
||||
# roughly 3.5 KB, but the exact boundary depends on the interface name
|
||||
# length, on whether a header protection key is set, and on each peer's
|
||||
# address family, so we cannot compute it here. Our generator produces
|
||||
# at most 256 bytes and the documented recipes up to 128, so a kilobyte
|
||||
# already means a hand edit, with a multiple of headroom left.
|
||||
if [[ "$_cps_total" =~ ^[0-9]+$ && "$_cps_total" -gt 1024 ]]; then
|
||||
_cps_unsafe=1
|
||||
_diag_line WARN "CPS (I1-I5) takes ${_cps_total} bytes - that is a lot"
|
||||
echo " Oversized I1-I5 hang the interface read: the dump stops advancing and"
|
||||
echo " repeats forever, which on a router is enough to take the box down."
|
||||
# The path comes from the variable rather than a literal: the
|
||||
# message must name the file we actually read.
|
||||
echo " Fix: shorten I1-I5 in $SERVER_CONF_FILE, then"
|
||||
echo " sudo systemctl restart awg-quick@awg0"
|
||||
warn=$((warn+1))
|
||||
elif [[ "$_cps_rc" -ne 0 || ! "$_cps_total" =~ ^[0-9]+$ ]]; then
|
||||
# Parsing failed. An under-count is indistinguishable from "the
|
||||
# size is small", so silence is not an option: treat the size as
|
||||
# unsafe and say so, rather than passing unchecked off as checked.
|
||||
_cps_unsafe=1
|
||||
_diag_line WARN "could not determine the CPS (I1-I5) size - unrecognised tags in the config"
|
||||
warn=$((warn+1))
|
||||
fi
|
||||
else
|
||||
# Same class: "could not read" has to be audible, or the next step
|
||||
# walks into the dangerous dump as if the check had passed.
|
||||
_cps_unsafe=1
|
||||
_diag_line WARN "could not read $SERVER_CONF_FILE - CPS size not checked"
|
||||
warn=$((warn+1))
|
||||
fi
|
||||
}
|
||||
|
||||
diagnose_server() {
|
||||
local carrier="${CLI_CARRIER}"
|
||||
local ok=0 warn=0 fail=0
|
||||
|
|
@ -1399,6 +1462,9 @@ diagnose_server() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
local _cps_unsafe=0
|
||||
_diag_cps_guard
|
||||
|
||||
# 1. Kernel module
|
||||
if lsmod 2>/dev/null | awk '$1 == "amneziawg" {f=1} END {exit !f}'; then
|
||||
local _d_mod_ver _d_mod_build
|
||||
|
|
@ -1507,21 +1573,87 @@ diagnose_server() {
|
|||
fi
|
||||
|
||||
# 7. Peer count
|
||||
local peer_count
|
||||
peer_count=$(awg show awg0 peers 2>/dev/null | wc -l)
|
||||
_diag_line INFO "Peers configured: $peer_count"
|
||||
# 🔴 The interface dump is skipped when the CPS size is judged unsafe:
|
||||
# warning and then walking straight into the very call that loops protects
|
||||
# nobody. A timeout bounds time, not memory, and on a router it is the
|
||||
# growing reader that eats the memory.
|
||||
local peer_count _peers_out _show_rc=0
|
||||
if [[ "$_cps_unsafe" -eq 1 ]]; then
|
||||
_diag_line WARN "interface read skipped because of the CPS size (see above)"
|
||||
warn=$((warn+1))
|
||||
elif _peers_out=$(timeout 10 awg show awg0 peers 2>/dev/null); then
|
||||
peer_count=$(printf '%s\n' "$_peers_out" | grep -c . || true)
|
||||
_diag_line INFO "Peers configured: $peer_count"
|
||||
else
|
||||
# 🔴 This used to be a pipeline into `wc -l`, which swallowed the
|
||||
# exit code whole: on a timeout it printed "Peers configured: 0", so
|
||||
# "could not check" read as "checked, no peers". Code 124 is named
|
||||
# separately - it means exactly the looping dump this whole check
|
||||
# exists for.
|
||||
_show_rc=$?
|
||||
if [[ "$_show_rc" -eq 124 ]]; then
|
||||
_diag_line FAIL "awg show did not answer within 10 seconds - looks like a looping interface dump"
|
||||
fail=$((fail+1))
|
||||
# 🔴 The loop lives on the INTERFACE while the guard reads the
|
||||
# FILE, and the two states diverge: someone who followed our own
|
||||
# advice to shrink I1-I5 has a small file and an unchanged
|
||||
# interface. The call just hung, so step 8 must not go back into
|
||||
# it - that would double the very exposure this exists to prevent.
|
||||
_cps_unsafe=1
|
||||
else
|
||||
_diag_line WARN "awg show exited with code $_show_rc - peer count unknown"
|
||||
warn=$((warn+1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# 8. AWG params snapshot (one awg show call instead of four)
|
||||
local _awg_show jc jmin jmax i1
|
||||
_awg_show=$(awg show awg0 2>/dev/null)
|
||||
jc=$(awk '/^[[:space:]]*jc:/ {print $2; exit}' <<< "$_awg_show")
|
||||
jmin=$(awk '/^[[:space:]]*jmin:/ {print $2; exit}' <<< "$_awg_show")
|
||||
jmax=$(awk '/^[[:space:]]*jmax:/ {print $2; exit}' <<< "$_awg_show")
|
||||
i1=$(awk -F': ' '/^[[:space:]]*i1:/ {print $2; exit}' <<< "$_awg_show")
|
||||
_diag_line INFO "AWG params: Jc=${jc:-?} Jmin=${jmin:-?} Jmax=${jmax:-?} I1=${i1:-absent}"
|
||||
# 🔴 The state is THREE-VALUED, not "the string is empty". An empty string
|
||||
# means both "the parameter is absent" and "we never reached the
|
||||
# interface", and those are opposite claims: the guard fires precisely
|
||||
# when I1 is huge, and the old ${i1:-absent} form printed "I1=absent" at
|
||||
# that exact moment. Lying about I1 in a report about an oversized I1 is
|
||||
# the worst thing this command could do.
|
||||
local _awg_show="" _awg_read=0 _show2_rc=0 jc jmin jmax i1
|
||||
if [[ "$_cps_unsafe" -ne 1 ]]; then
|
||||
# stderr is kept: the band ABOVE looping answers `Unable to access
|
||||
# interface: Message too long`, the only line that names the cause.
|
||||
# Replacing it with a bare exit code throws diagnostics away.
|
||||
if _awg_show=$(timeout 10 awg show awg0 2>&1); then
|
||||
_awg_read=1
|
||||
else
|
||||
_show2_rc=$?
|
||||
if [[ "$_show2_rc" -eq 124 ]]; then
|
||||
_diag_line FAIL "awg show did not answer within 10 seconds - interface parameters not read"
|
||||
fail=$((fail+1))
|
||||
_cps_unsafe=1
|
||||
else
|
||||
_diag_line WARN "awg show exited with code $_show2_rc - interface parameters not read${_awg_show:+: ${_awg_show%%$'\n'*}}"
|
||||
warn=$((warn+1))
|
||||
fi
|
||||
_awg_show=""
|
||||
fi
|
||||
fi
|
||||
if [[ "$_awg_read" -eq 1 ]]; then
|
||||
jc=$(awk '/^[[:space:]]*jc:/ {print $2; exit}' <<< "$_awg_show")
|
||||
jmin=$(awk '/^[[:space:]]*jmin:/ {print $2; exit}' <<< "$_awg_show")
|
||||
jmax=$(awk '/^[[:space:]]*jmax:/ {print $2; exit}' <<< "$_awg_show")
|
||||
i1=$(awk -F': ' '/^[[:space:]]*i1:/ {print $2; exit}' <<< "$_awg_show")
|
||||
_diag_line INFO "AWG params: Jc=${jc:-?} Jmin=${jmin:-?} Jmax=${jmax:-?} I1=${i1:-absent}"
|
||||
else
|
||||
_diag_line INFO "AWG params: interface not read, values not checked"
|
||||
fi
|
||||
|
||||
# 9. Carrier comparison
|
||||
if [[ -n "$carrier" ]]; then
|
||||
# 🔴 There is nothing to compare when nothing was read. For a profile with
|
||||
# i1_mode=absent the `[[ -z "$i1" ]]` test on an unread interface produced
|
||||
# a green OK "I1 absent" - a tick certifying the very condition that made
|
||||
# us refuse to look, and it fed the summary counter too.
|
||||
if [[ -n "$carrier" && "$_awg_read" -ne 1 ]]; then
|
||||
echo ""
|
||||
log "Comparing against carrier profile '$carrier'..."
|
||||
_diag_line WARN "skipped: interface parameters were not read, nothing to compare"
|
||||
warn=$((warn+1))
|
||||
elif [[ -n "$carrier" ]]; then
|
||||
echo ""
|
||||
log "Comparing against carrier profile '$carrier'..."
|
||||
local row
|
||||
|
|
@ -1638,8 +1770,24 @@ list_clients() {
|
|||
|
||||
# Single-pass awg show dump parsing: pubkey → handshake timestamp
|
||||
local -A _pk_to_hs
|
||||
local awg_dump
|
||||
awg_dump=$(awg show awg0 dump 2>/dev/null) || awg_dump=""
|
||||
# 🔴 An empty dump does NOT mean "nobody has a handshake". The timeout was
|
||||
# added in this same release, and without handling the failure it turned a
|
||||
# visible hang into a plausible, entirely wrong table: every client shown
|
||||
# as "No handshake", and the same under --json. The honest state already
|
||||
# exists in the code as the "No data"/no_data default; it just has to
|
||||
# survive.
|
||||
local awg_dump _dump_ok=0 _dump_rc=0
|
||||
if awg_dump=$(timeout 10 awg show awg0 dump 2>/dev/null); then
|
||||
_dump_ok=1
|
||||
else
|
||||
_dump_rc=$?
|
||||
awg_dump=""
|
||||
if [[ "$_dump_rc" -eq 124 ]]; then
|
||||
log_warn "awg show dump did not answer within 10 seconds - client state unknown (looks like a looping dump: check the size of I1-I5)"
|
||||
else
|
||||
log_warn "awg show dump exited with code $_dump_rc - client state unknown"
|
||||
fi
|
||||
fi
|
||||
if [[ -n "$awg_dump" ]]; then
|
||||
# shellcheck disable=SC2034
|
||||
while IFS=$'\t' read -r _dpk _dpsk _dep _daips _dhs _drx _dtx _dka; do
|
||||
|
|
@ -1704,7 +1852,12 @@ list_clients() {
|
|||
|
||||
local current_pk="${_name_to_pk[$name]:-}"
|
||||
|
||||
if [[ -n "$current_pk" ]]; then
|
||||
if [[ -n "$current_pk" && "$_dump_ok" -ne 1 ]]; then
|
||||
# The key is known, the interface state is not. Keep the
|
||||
# "No data"/no_data default: that is what "not looked at"
|
||||
# means.
|
||||
pk="${current_pk:0:10}..."
|
||||
elif [[ -n "$current_pk" ]]; then
|
||||
pk="${current_pk:0:10}..."
|
||||
local handshake="${_pk_to_hs[$current_pk]:-0}"
|
||||
if [[ "$handshake" =~ ^[0-9]+$ && "$handshake" -gt 0 ]]; then
|
||||
|
|
@ -1809,9 +1962,14 @@ stats_clients() {
|
|||
fi
|
||||
|
||||
# Get awg show data
|
||||
local awg_dump
|
||||
awg_dump=$(awg show awg0 dump 2>/dev/null) || {
|
||||
log_error "Failed to get awg show data."
|
||||
local awg_dump _stats_rc=0
|
||||
awg_dump=$(timeout 10 awg show awg0 dump 2>/dev/null) || {
|
||||
_stats_rc=$?
|
||||
if [[ "$_stats_rc" -eq 124 ]]; then
|
||||
log_error "awg show dump did not answer within 10 seconds - looks like a looping interface dump, check the size of I1-I5."
|
||||
else
|
||||
log_error "Failed to get awg show data."
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
|
|
@ -2328,7 +2486,15 @@ case $COMMAND in
|
|||
|
||||
show)
|
||||
log "AmneziaWG 2.0 status..."
|
||||
if ! awg show; then log_error "awg show error."; _cmd_rc=1; fi
|
||||
if ! timeout 10 awg show; then
|
||||
_show_cmd_rc=$?
|
||||
if [[ "$_show_cmd_rc" -eq 124 ]]; then
|
||||
log_error "awg show did not answer within 10 seconds - looks like a looping interface dump, check the size of I1-I5."
|
||||
else
|
||||
log_error "awg show failed."
|
||||
fi
|
||||
_cmd_rc=1
|
||||
fi
|
||||
;;
|
||||
|
||||
restart)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@
|
|||
# (CHANGELOG исключён - там теги исторические).
|
||||
# 6b. Форма пина: все raw-URL теги имеют вид vX.Y.Z числами. Отдельная проверка
|
||||
# потому, что регулярка #6 нечисловой пин не видит ВООБЩЕ (см. её комментарий).
|
||||
# 6c. Команды скачивания идут через releases/latest/download, а не через
|
||||
# версионный raw-URL (иначе copy-paste стареет и его заучивают поисковые
|
||||
# движки). Положительная: требует минимум по одной такой команде.
|
||||
# 7. ADVANCED: устаревшие IPv6 split-tunnel формулировки не вернулись
|
||||
# (present-tense "не поддерживается / implies full-tunnel"; past-tense
|
||||
# историческая заметка разрешена).
|
||||
|
|
@ -510,6 +513,20 @@ if [[ "$stale_fail" -eq 0 ]]; then _ok "SECURITY/CONTRIBUTING не протух
|
|||
# CASCADE.md/.en.md включены: awg-routing.sh закрепляет тег в fallback-URL снимка
|
||||
# ru.zone (raw .../vX.Y.Z/cascade/ru.zone). Пин на тег = иммутабельный снимок, а не
|
||||
# подвижный main; проверка не даёт ему протухнуть на новом релизе (бампать каждый релиз).
|
||||
#
|
||||
# ⚠️ С v5.30.0 КОМАНДЫ скачивания здесь больше не проверяются: они ушли на
|
||||
# releases/latest/download (см. 6c). Под этой проверкой осталось ровно четыре
|
||||
# пина, и каждый оставлен намеренно:
|
||||
# - ADVANCED.md/.en.md, раздел про привязку URL к версии: голый URL, которым
|
||||
# раздел ОБЪЯСНЯЕТ, как установщик тянет свои helper-скрипты. Установщик
|
||||
# действительно тянет их по тегу, поэтому адрес без версии сделал бы раздел
|
||||
# ложью о собственном коде;
|
||||
# - CASCADE.md/.en.md, RU_ZONE_FALLBACK_URL: cascade/ru.zone НЕ является
|
||||
# ассетом релиза (в релизе только шесть скриптов, их подписи и KEYS.txt),
|
||||
# поэтому latest/download на него отдаёт 404 - проверено живым запросом
|
||||
# 31 aug 2026. Тег тут единственная рабочая форма.
|
||||
# То есть пустой результат этой проверки означал бы, что она сломалась: обе
|
||||
# формы обязаны присутствовать, и 6c это закрепляет счётчиком.
|
||||
url_fail=0
|
||||
URL_DOCS=(README.md README.en.md ADVANCED.md ADVANCED.en.md INSTALL_VPS.md INSTALL_VPS.ru.md CASCADE.md CASCADE.en.md WARP-RU.md WARP-RU.en.md)
|
||||
for f in "${URL_DOCS[@]}"; do
|
||||
|
|
@ -550,6 +567,45 @@ for f in "${URL_DOCS[@]}"; do
|
|||
done
|
||||
if [[ "$form_fail" -eq 0 ]]; then _ok "форма пинов raw-URL (все vX.Y.Z числами)"; else _bad "нечисловые пины в raw-URL"; fi
|
||||
|
||||
# --- 6c. Команды скачивания идут через releases/latest/download ---
|
||||
# Зачем. Версионный адрес в команде установки стареет дважды: пользователь
|
||||
# копирует прошлый релиз, а поисковые движки заучивают его надолго (замер промо
|
||||
# 31 aug 2026: ответы до сих пор раздают v5.14.1, между ней и текущей 35
|
||||
# релизов). Адрес без версии не стареет ни в кэше, ни в обучении, и при этом
|
||||
# отдаёт ПОДПИСАННЫЙ ассет тега - рядом с ним лежат .minisig и KEYS.txt, чего у
|
||||
# raw-ветки нет вовсе.
|
||||
#
|
||||
# Почему проверка ПОЛОЖИТЕЛЬНАЯ, а не «нет запрещённого». Чистый blocklist не
|
||||
# отличает «плохого нет» от «проверка сломалась»: сорвись регулярка - пустой
|
||||
# результат прочитается как успех. Поэтому здесь два условия сразу: команд с
|
||||
# правильным адресом найдено не меньше порога И ни одна команда не осталась на
|
||||
# версионном raw-URL. Порог - именно минимум, а не точное число: доки растут, и
|
||||
# сверка «ровно N» падала бы на каждом добавленном примере.
|
||||
dl_fail=0
|
||||
DL_MIN=2 # минимум команд скачивания на файл из списка ниже
|
||||
# ADVANCED держит четыре такие команды на язык и раньше в список не входил:
|
||||
# откат одной из них на версионный raw-URL не поймала бы ни эта проверка, ни
|
||||
# соседние 6/6b - те следят лишь за ФОРМОЙ уже существующих пинов, а один пин
|
||||
# в ADVANCED оставлен намеренно. Он проверку не задевает: это голый URL в блоке
|
||||
# кода, без wget и curl в строке, поэтому счётчик bad его не видит (замерено:
|
||||
# good=4, bad=0 в обоих языках).
|
||||
DL_DOCS=(README.md README.en.md INSTALL_VPS.md INSTALL_VPS.ru.md ADVANCED.md ADVANCED.en.md)
|
||||
for f in "${DL_DOCS[@]}"; do
|
||||
[[ -f "$f" ]] || continue
|
||||
# Команда скачивания = строка с wget/curl, тянущая наш файл.
|
||||
good=$(grep -cE '(wget|curl)[^|]*github\.com/bivlked/amneziawg-installer/releases/latest/download/' "$f" || true)
|
||||
bad=$(grep -cE '(wget|curl)[^|]*raw\.githubusercontent\.com/bivlked/amneziawg-installer/v[0-9]' "$f" || true)
|
||||
if [[ "$good" -lt "$DL_MIN" ]]; then
|
||||
echo " $f: команд скачивания через releases/latest/download найдено $good, ожидалось >= $DL_MIN" >&2
|
||||
dl_fail=1
|
||||
fi
|
||||
if [[ "$bad" -ne 0 ]]; then
|
||||
echo " $f: $bad команд скачивания всё ещё на версионном raw-URL (должны идти через releases/latest/download)" >&2
|
||||
dl_fail=1
|
||||
fi
|
||||
done
|
||||
if [[ "$dl_fail" -eq 0 ]]; then _ok "команды скачивания через releases/latest/download"; else _bad "команды скачивания на версионном raw-URL"; fi
|
||||
|
||||
# --- 7. ADVANCED: устаревшие IPv6 split-tunnel формулировки не вернулись ---
|
||||
# После переписывания IPv6-раздела (v5.15.1 split-tunnel + dual-stack корректно
|
||||
# сочетаются) present-tense заявления о неподдержке не должны появиться снова.
|
||||
|
|
|
|||
439
tests/test_cps_size_guard.bats
Normal file
439
tests/test_cps_size_guard.bats
Normal file
|
|
@ -0,0 +1,439 @@
|
|||
#!/usr/bin/env bats
|
||||
# awg_cps_decoded_size and the diagnose-time warning built on it.
|
||||
#
|
||||
# The defect being guarded against is upstream
|
||||
# amneziawg-linux-kernel-module#228: once the I1-I5 attributes fill most of the
|
||||
# netlink dump buffer, the first peer no longer fits, `wg_get_device_dump`
|
||||
# neither advances nor fails, and the same message repeats forever. The reader
|
||||
# spins; on a router that is enough to take the box down.
|
||||
#
|
||||
# 🔴 Two properties matter and are tested separately, because passing one while
|
||||
# failing the other still leaves the user with a hung command:
|
||||
# 1. the size is DECODED, not the length of the string - `<r 1000>` is nine
|
||||
# characters and a thousand bytes;
|
||||
# 2. the warning is computed from the FILE and printed BEFORE any `awg show`,
|
||||
# since that call is the one that hangs.
|
||||
|
||||
setup() {
|
||||
COMMON="${BATS_TEST_DIRNAME}/../awg_common.sh"
|
||||
COMMON_EN="${BATS_TEST_DIRNAME}/../awg_common_en.sh"
|
||||
MANAGE="${BATS_TEST_DIRNAME}/../manage_amneziawg.sh"
|
||||
MANAGE_EN="${BATS_TEST_DIRNAME}/../manage_amneziawg_en.sh"
|
||||
}
|
||||
|
||||
size() { # size <lib> <args...>
|
||||
local lib="$1"; shift
|
||||
bash -c 'source "$1" >/dev/null 2>&1 || true; shift; awg_cps_decoded_size "$@"' \
|
||||
_ "$lib" "$@"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- decoded size
|
||||
|
||||
@test "cps size: a random tag counts its byte count, not its text length" {
|
||||
run size "$COMMON" '<r 1000>'
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "1000" ]
|
||||
}
|
||||
|
||||
@test "cps size: a literal tag counts two hex characters per byte" {
|
||||
run size "$COMMON" '<b 0xdeadbeef>'
|
||||
[ "$output" = "4" ]
|
||||
}
|
||||
|
||||
@test "cps size: letters and digits count like random bytes" {
|
||||
run size "$COMMON" '<rc 10><rd 5>'
|
||||
[ "$output" = "15" ]
|
||||
}
|
||||
|
||||
@test "cps size: timestamp and counter tags are four bytes each" {
|
||||
run size "$COMMON" '<t><c>'
|
||||
[ "$output" = "8" ]
|
||||
}
|
||||
|
||||
@test "cps size: a tag without a space is accepted too" {
|
||||
run size "$COMMON" '<r64>'
|
||||
[ "$output" = "64" ]
|
||||
}
|
||||
|
||||
@test "cps size: the documented DNS-shaped recipe stays small" {
|
||||
# The recipe published in ADVANCED. If a future edit inflates it, this
|
||||
# reddens rather than the users' routers.
|
||||
run size "$COMMON" '<r 2><b 0x858000010001000000000669636c6f756403636f6d0000010001c00c000100010000105a00044d583737>'
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" -lt 128 ]
|
||||
}
|
||||
|
||||
@test "cps size: I1 through I5 add up" {
|
||||
run size "$COMMON" '<r 100>' '<r 200>' '' '<b 0xaabb>' '<t>'
|
||||
[ "$output" = "306" ]
|
||||
}
|
||||
|
||||
@test "cps size: empty input is zero, not an error" {
|
||||
run size "$COMMON" ''
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "0" ]
|
||||
}
|
||||
|
||||
@test "cps size: the generator's own upper bound stays far below the threshold" {
|
||||
# generate_cps_i1 emits <r 32..256>. The guard warns above 1024.
|
||||
run size "$COMMON" '<r 256>'
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" -lt 1024 ]
|
||||
}
|
||||
|
||||
@test "cps size: a leading-zero count is decimal, not octal" {
|
||||
# 🔴 `<r 08>` is octal to bash. Before `10#` was added the arithmetic failed
|
||||
# outright, the function returned an EMPTY string, and every threshold
|
||||
# comparison built on it silently stopped firing - the guard was off and
|
||||
# said nothing. Tested ALONE on purpose: paired with a large tag the sum
|
||||
# survives on the neighbour's value and the test passes while broken, which
|
||||
# is exactly what an earlier version of this file did.
|
||||
run size "$COMMON" '<r 08>'
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "8" ]
|
||||
}
|
||||
|
||||
@test "cps size: a leading-zero count in the EN library too" {
|
||||
run size "$COMMON_EN" '<r 09>'
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "9" ]
|
||||
}
|
||||
|
||||
@test "cps size: the EN library computes the same number" {
|
||||
run size "$COMMON_EN" '<r 100>' '<b 0xaabb>'
|
||||
[ "$output" = "102" ]
|
||||
}
|
||||
|
||||
# ------------------------------------------------------- ordering in diagnose
|
||||
|
||||
@test "cps guard: the check reads the config file, not awg show" {
|
||||
# If it ever reads the size out of `awg show`, it inherits the hang it is
|
||||
# meant to warn about.
|
||||
run grep -n 'awg_cps_decoded_size' "$MANAGE"
|
||||
[ "$status" -eq 0 ]
|
||||
grep -A2 '_cps_vals+=' "$MANAGE" | grep -q 'SERVER_CONF_FILE'
|
||||
}
|
||||
|
||||
@test "cps guard: the guard runs before the first awg show in diagnose" {
|
||||
# The whole point of the ordering, measured by line numbers inside
|
||||
# diagnose_server: the guard must come first, or it never prints for the
|
||||
# user who needs it.
|
||||
#
|
||||
# Two kinds of line are skipped, and both were learned the hard way here:
|
||||
# comments, because the guard's own comment explains that it precedes
|
||||
# `awg show` and matching that sentence made the check fail on its own
|
||||
# documentation; and _diag_line calls, because the failure messages quote
|
||||
# the command name too. A guard that trips over its own explanation is a
|
||||
# pattern this repository has now paid for twice.
|
||||
for f in "$MANAGE" "$MANAGE_EN"; do
|
||||
start=$(grep -n '^diagnose_server() {' "$f" | cut -d: -f1)
|
||||
guard=$(awk -v s="$start" 'NR>s && /_diag_cps_guard/ && !/^[[:space:]]*#/ {print NR; exit}' "$f")
|
||||
show=$(awk -v s="$start" 'NR>s && /awg show/ && !/^[[:space:]]*#/ && !/_diag_line/ {print NR; exit}' "$f")
|
||||
[ -n "$guard" ]
|
||||
[ -n "$show" ]
|
||||
[ "$guard" -lt "$show" ]
|
||||
done
|
||||
}
|
||||
|
||||
@test "cps guard: awg show inside diagnose is bounded by a timeout" {
|
||||
# A bounded call fails loudly; an unbounded one hangs silently, and a hung
|
||||
# diagnostic is the worst outcome here - the user cannot even report it.
|
||||
for f in "$MANAGE" "$MANAGE_EN"; do
|
||||
start=$(grep -n '^diagnose_server() {' "$f" | cut -d: -f1)
|
||||
end=$(awk -v s="$start" 'NR>s && /^}/ {print NR; exit}' "$f")
|
||||
bare=$(awk -v s="$start" -v e="$end" \
|
||||
'NR>s && NR<e && /awg show/ && !/timeout [0-9]+ awg show/ && !/^[[:space:]]*#/ && !/_diag_line/' "$f" | wc -l)
|
||||
[ "$bare" -eq 0 ]
|
||||
done
|
||||
}
|
||||
|
||||
@test "cps guard: the threshold is stated once per language and matches" {
|
||||
ru=$(grep -c '_cps_total" -gt 1024' "$MANAGE")
|
||||
en=$(grep -c '_cps_total" -gt 1024' "$MANAGE_EN")
|
||||
[ "$ru" -eq 1 ]
|
||||
[ "$en" -eq 1 ]
|
||||
}
|
||||
|
||||
# ------------------------------------------------------ behaviour, not grep
|
||||
#
|
||||
# The checks above read the source. These run it. The difference matters: a
|
||||
# call that stays in place while its result stops mattering, a threshold line
|
||||
# turned into dead code, or a `timeout` present in the text while its exit code
|
||||
# is discarded - all of that passes a source scan and fails a user.
|
||||
#
|
||||
# The guard is extracted by awk range and eval'd, the way this suite already
|
||||
# handles diagnose helpers. Sourcing the whole script is not an option: it runs
|
||||
# main and prints the usage text.
|
||||
|
||||
# guard_out <manage script> <lib> <I1 value>
|
||||
# Prints the guard's output, then a final line "unsafe=N" carrying the flag it
|
||||
# sets in its caller.
|
||||
guard_out() {
|
||||
local script="$1" lib="$2" i1="$3"
|
||||
local dir="$BATS_TEST_TMPDIR/g"
|
||||
rm -rf "$dir"; mkdir -p "$dir"
|
||||
printf '[Interface]\nPrivateKey = x\nI1 = %s\n' "$i1" > "$dir/awg0.conf"
|
||||
|
||||
bash -c '
|
||||
SERVER_CONF_FILE="$1/awg0.conf"
|
||||
warn=0
|
||||
_cps_unsafe=0
|
||||
# Minimal stand-ins: the guard only needs a way to print a line.
|
||||
_diag_line() { echo "[$1] ${*:2}"; }
|
||||
eval "$(awk "/^awg_cps_decoded_size\\(\\) \\{/,/^}\$/" "$3")"
|
||||
eval "$(awk "/^_diag_cps_guard\\(\\) \\{/,/^}\$/" "$2")"
|
||||
_diag_cps_guard
|
||||
echo "unsafe=$_cps_unsafe"
|
||||
' _ "$dir" "$script" "$lib"
|
||||
}
|
||||
|
||||
@test "cps behaviour: an oversized I1 warns and names the size" {
|
||||
run guard_out "$MANAGE" "$COMMON" '<r 4096>'
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"4096"* ]]
|
||||
[[ "$output" == *"unsafe=1"* ]]
|
||||
}
|
||||
|
||||
# 🔴 The property the whole change exists for. Warning and then walking into
|
||||
# the very call that loops would protect nobody: a timeout bounds time, not the
|
||||
# memory a spinning reader consumes.
|
||||
@test "cps behaviour: an oversized I1 marks the interface read unsafe" {
|
||||
run guard_out "$MANAGE" "$COMMON" '<r 4096>'
|
||||
[[ "$output" == *"unsafe=1"* ]]
|
||||
}
|
||||
|
||||
@test "cps behaviour: a normal I1 leaves the interface read enabled" {
|
||||
# The mirror. Without it a guard that always skips would look perfect while
|
||||
# breaking diagnostics for everyone.
|
||||
run guard_out "$MANAGE" "$COMMON" '<r 64>'
|
||||
[[ "$output" == *"unsafe=0"* ]]
|
||||
}
|
||||
|
||||
@test "cps behaviour: an unparsable tag is reported, not counted as small" {
|
||||
run guard_out "$MANAGE" "$COMMON" '<nosuchtag 9>'
|
||||
[[ "$output" == *"unsafe=1"* ]]
|
||||
[[ "$output" == *"WARN"* ]]
|
||||
}
|
||||
|
||||
@test "cps behaviour: a leading-zero count does not silently disable the guard" {
|
||||
# `<r 08>` is octal to bash. Before the fix the arithmetic failed, the
|
||||
# function returned an empty string, and the threshold comparison never
|
||||
# fired - the guard was off and said nothing.
|
||||
#
|
||||
# 🔴 The assertion is the SUM, not `unsafe=1`. Measured on a mutant with
|
||||
# `10#` removed: it also prints `unsafe=1`, by the "could not parse"
|
||||
# branch, so an assertion on the flag alone stays green on the defect it
|
||||
# names. 4104 = 8 + 4096, and only the fixed code can produce it.
|
||||
run guard_out "$MANAGE" "$COMMON" '<r 08><r 4096>'
|
||||
[[ "$output" == *"unsafe=1"* ]]
|
||||
[[ "$output" == *"4104"* ]]
|
||||
}
|
||||
|
||||
@test "cps behaviour: an unreadable config is reported rather than passed over" {
|
||||
run bash -c '
|
||||
SERVER_CONF_FILE="/nonexistent/awg0.conf"
|
||||
warn=0
|
||||
_cps_unsafe=0
|
||||
_diag_line() { echo "[$1] ${*:2}"; }
|
||||
eval "$(awk "/^awg_cps_decoded_size\(\) \{/,/^}$/" "'"$COMMON"'")"
|
||||
eval "$(awk "/^_diag_cps_guard\(\) \{/,/^}$/" "'"$MANAGE"'")"
|
||||
_diag_cps_guard
|
||||
echo "unsafe=$_cps_unsafe"
|
||||
'
|
||||
[[ "$output" == *"unsafe=1"* ]]
|
||||
[[ "$output" == *"WARN"* ]]
|
||||
}
|
||||
|
||||
@test "cps behaviour: the EN guard behaves the same" {
|
||||
run guard_out "$MANAGE_EN" "$COMMON_EN" '<r 4096>'
|
||||
[[ "$output" == *"4096"* ]]
|
||||
[[ "$output" == *"unsafe=1"* ]]
|
||||
}
|
||||
|
||||
# ------------------------------------------- контракт «занижение не молчит»
|
||||
#
|
||||
# 🔴 Эти тесты проверяют КОД ВОЗВРАТА, а не только напечатанное число, и в этом
|
||||
# всё дело. Прежний тест на неизвестный тег утверждал только `output = 0`:
|
||||
# удали из библиотеки `return 2` - и он остался бы зелёным, хотя вызывающий
|
||||
# перестал бы отличать «разобрал, размера нет» от «разобрать не смог». Ноль,
|
||||
# полученный от неразобранной строки, ведёт диагностику прямиком в тот вызов,
|
||||
# который зависает.
|
||||
|
||||
@test "cps size: an unknown tag is flagged, not merely under-counted" {
|
||||
run size "$COMMON" '<nosuchtag 99>'
|
||||
[ "$output" = "0" ]
|
||||
[ "$status" -eq 2 ]
|
||||
}
|
||||
|
||||
@test "cps size: input with no tags at all is flagged rather than called empty" {
|
||||
# Measured before the fix: 0 with status 0, indistinguishable from an
|
||||
# absent I1 - and an absent I1 is exactly what makes the guard stand aside.
|
||||
run size "$COMMON" 'garbagewithnotags'
|
||||
[ "$status" -eq 2 ]
|
||||
}
|
||||
|
||||
@test "cps size: an unterminated tag is flagged" {
|
||||
run size "$COMMON" '<r 5'
|
||||
[ "$status" -eq 2 ]
|
||||
}
|
||||
|
||||
@test "cps size: junk after a valid tag is flagged" {
|
||||
run size "$COMMON" '<b 0xaa> tail'
|
||||
[ "$status" -eq 2 ]
|
||||
}
|
||||
|
||||
@test "cps size: a stray bracket does not make a tag count twice" {
|
||||
# `${s#*>}` used to cut to the first `>` in the whole string, which could
|
||||
# sit before the match, so the same tag was consumed twice. Measured: 128
|
||||
# for a single `<r 64>`.
|
||||
run size "$COMMON" 'a>b<r 64>'
|
||||
[ "$output" = "64" ]
|
||||
[ "$status" -eq 2 ]
|
||||
}
|
||||
|
||||
@test "cps size: a count too large for bash arithmetic does not wrap into safety" {
|
||||
# Measured before the fix: `<r 18446744073709551617>` overflowed to 1 with
|
||||
# status 0, so a plainly dangerous value passed under the threshold.
|
||||
run size "$COMMON" '<r 18446744073709551617>'
|
||||
[ "$status" -eq 2 ]
|
||||
[ "$output" -lt 1024 ]
|
||||
}
|
||||
|
||||
@test "cps size: surrounding whitespace is not mistaken for junk" {
|
||||
# The mirror of the four tests above. Without it a parser that flagged
|
||||
# everything would pass them all and warn on every healthy config.
|
||||
run size "$COMMON" ' <r 64> '
|
||||
[ "$output" = "64" ]
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "cps size: the EN library enforces the same contract" {
|
||||
run size "$COMMON_EN" 'garbagewithnotags'
|
||||
[ "$status" -eq 2 ]
|
||||
run size "$COMMON_EN" 'a>b<r 64>'
|
||||
[ "$output" = "64" ]
|
||||
run size "$COMMON_EN" ' <r 64> '
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "cps behaviour: an I1 with no recognisable tag marks the read unsafe" {
|
||||
run guard_out "$MANAGE" "$COMMON" 'notatagatall'
|
||||
[[ "$output" == *"unsafe=1"* ]]
|
||||
[[ "$output" == *"WARN"* ]]
|
||||
}
|
||||
|
||||
# ------------------------------------- «не прочитал» против «параметра нет»
|
||||
#
|
||||
# Шаг 8 diagnose печатал `I1=${i1:-absent}`. Пустая строка означает и «в
|
||||
# конфиге параметра нет», и «до интерфейса мы не дошли», а это противоположные
|
||||
# утверждения: сторож срабатывает ровно тогда, когда I1 огромен, и в этот
|
||||
# момент прежний код печатал «I1 absent». Блок вырезается по комментариям-
|
||||
# границам и исполняется с заглушками - так же, как guard_out выше.
|
||||
|
||||
step8() { # step8 <manage> <cps_unsafe>
|
||||
bash -c '
|
||||
MANAGE="$1"; _cps_unsafe="$2"
|
||||
warn=0; fail=0
|
||||
_diag_line() { echo "[$1] ${*:2}"; }
|
||||
awg() { echo " jc: 4"; echo " i1: <r 171>"; }
|
||||
# `timeout` - внешний бинарь, и функцию-заглушку awg он не увидит.
|
||||
# Без этой строки зеркальный тест падал бы на харнессе, а читался
|
||||
# как провал кода.
|
||||
timeout() { shift; "$@"; }
|
||||
eval "$(awk "/# 8\. AWG params snapshot/,/# 9\. Carrier comparison/" "$MANAGE" | sed "\$d")"
|
||||
echo "warn=$warn fail=$fail"
|
||||
' _ "$1" "$2"
|
||||
}
|
||||
|
||||
@test "diagnose: a skipped interface read is not reported as an absent I1" {
|
||||
run step8 "$MANAGE" 1
|
||||
[[ "$output" == *"не прочитан"* ]]
|
||||
run bash -c 'echo "$1"' _ "$output"
|
||||
[[ "$output" != *"I1=absent"* ]]
|
||||
}
|
||||
|
||||
@test "diagnose: a successful read still prints the parameters" {
|
||||
# The mirror. A branch that always says "not read" would pass the test
|
||||
# above while making diagnose useless on a healthy server.
|
||||
run step8 "$MANAGE" 0
|
||||
[[ "$output" == *"I1=<r 171>"* ]]
|
||||
}
|
||||
|
||||
@test "diagnose: the EN version reports a skipped read the same way" {
|
||||
run step8 "$MANAGE_EN" 1
|
||||
[[ "$output" == *"not read"* ]]
|
||||
}
|
||||
|
||||
# ------------------------------------------- структурные проверки-минимумы
|
||||
#
|
||||
# ⚠️ Ниже именно СТРУКТУРНЫЕ проверки, и это слабейший вид: они смотрят на
|
||||
# форму кода, а не на его поведение. Стоят здесь потому, что оба места лежат
|
||||
# внутри длинных функций, поведенческий вызов которых требует поднятого
|
||||
# интерфейса. Заменить на поведенческие при первой возможности.
|
||||
|
||||
@test "list: the handshake default is unreachable when the dump was not read" {
|
||||
# Без этого условия непрочитанный дамп давал «Нет handshake» ВСЕМ
|
||||
# клиентам и то же самое в --json: правдоподобная и полностью неверная
|
||||
# таблица вместо заметного зависания.
|
||||
for f in "$MANAGE" "$MANAGE_EN"; do
|
||||
run grep -c '\-n "\$current_pk" && "\$_dump_ok" -ne 1' "$f"
|
||||
[ "$output" = "1" ]
|
||||
run grep -c '_dump_ok=1' "$f"
|
||||
[ "$output" = "1" ]
|
||||
done
|
||||
}
|
||||
|
||||
@test "diagnose: the carrier comparison is skipped when nothing was read" {
|
||||
# Профиль со значением i1_mode=absent давал зелёный OK «I1 отсутствует»
|
||||
# на непрочитанном интерфейсе - галочку об условии, из-за которого мы
|
||||
# отказались смотреть.
|
||||
for f in "$MANAGE" "$MANAGE_EN"; do
|
||||
run grep -c '\-n "\$carrier" && "\$_awg_read" -ne 1' "$f"
|
||||
[ "$output" = "1" ]
|
||||
done
|
||||
}
|
||||
|
||||
@test "diagnose: a timed-out peer read stops step 8 from trying again" {
|
||||
# Зацикливание живёт на интерфейсе, а сторож читает файл; после таймаута
|
||||
# повторный заход удваивал экспозицию, ради которой всё написано.
|
||||
for f in "$MANAGE" "$MANAGE_EN"; do
|
||||
run bash -c 'awk "/_show_rc\" -eq 124/,/^ else$/" "$1" | grep -c "_cps_unsafe=1"' _ "$f"
|
||||
[ "$output" = "1" ]
|
||||
done
|
||||
}
|
||||
|
||||
@test "every awg show call site is bounded by a timeout" {
|
||||
# check, stats, show и --diagnostic оставались без границы вовсе, а
|
||||
# --diagnostic это ровно та команда, которую документация просит приложить
|
||||
# к обращению: попавший в дефект не собрал бы даже отчёт о нём.
|
||||
#
|
||||
# Считаем ВЫЗОВЫ, а не вхождения подстроки: строковые литералы вырезаются
|
||||
# (в них живут тексты сообщений и справка), комментарии отбрасываются.
|
||||
# Первая редакция этого теста считала всё подряд и краснела на уже
|
||||
# исправленном коде: проверка, не отличающая вызов от рассказа о нём,
|
||||
# бесполезна в обе стороны.
|
||||
for f in "$MANAGE" "$MANAGE_EN" "${BATS_TEST_DIRNAME}/../install_amneziawg.sh" "${BATS_TEST_DIRNAME}/../install_amneziawg_en.sh"; do
|
||||
run bash -c 'strip() { sed "s/\"[^\"]*\"//g" "$1" | grep -vE "^[[:space:]]*#"; }
|
||||
calls=$(strip "$1" | grep -cE "(^|[^_[:alnum:]-])awg show") || true
|
||||
bounded=$(strip "$1" | grep -E "(^|[^_[:alnum:]-])awg show" | grep -cE "timeout [0-9]+ awg show") || true
|
||||
echo "$calls $bounded"' _ "$f"
|
||||
# Печатаются оба числа нарочно: равенство при нуле означало бы, что
|
||||
# вызовов не нашли вовсе, то есть проверка сломалась, а не прошла.
|
||||
set -- $output
|
||||
[ "$1" -gt 0 ]
|
||||
[ "$1" = "$2" ]
|
||||
done
|
||||
}
|
||||
|
||||
@test "cps size: a payload smuggled into a timestamp tag is flagged" {
|
||||
# `<t <r 4096>` matches as one `t` tag whose value is `<r 4096`. Before the
|
||||
# fix it counted four bytes and returned success: four thousand bytes
|
||||
# reported as four, and the caller walked into the dangerous read.
|
||||
run size "$COMMON" '<t <r 4096>'
|
||||
[ "$status" -eq 2 ]
|
||||
run size "$COMMON_EN" '<c junk>'
|
||||
[ "$status" -eq 2 ]
|
||||
run size "$COMMON" '<t><c>'
|
||||
[ "$output" = "8" ]
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
|
@ -112,10 +112,18 @@ assert_wiring() {
|
|||
# The report is meant to be pasted into a public issue. An empty or truncated
|
||||
# file announced with a success line and exit 0 is worse than a loud failure:
|
||||
# log_error returns success, so without die the branch ends with exit 0.
|
||||
# The `log_error` half is written with `run` and an explicit status check,
|
||||
# not as a bare `! grep`. `!` exempts a command from errexit entirely, so
|
||||
# inside a loop only the FINAL iteration has any effect at all - and that
|
||||
# one only because its status becomes the function's return value. A
|
||||
# failure on the first iteration (the RU installer) was silently discarded,
|
||||
# so this assertion used to cover the EN installer alone. `run` records the
|
||||
# status instead of letting the shell decide, which works in any position.
|
||||
for inst in "$(RU_INSTALL)" "$(EN_INSTALL)"; do
|
||||
body=$(report_fn "$inst")
|
||||
grep -qE '_mask_report_secrets > "\$rf" \|\| die ' <<< "$body"
|
||||
! grep -qE '_mask_report_secrets > "\$rf" \|\| log_error' <<< "$body"
|
||||
run grep -qE '_mask_report_secrets > "\$rf" \|\| log_error' <<< "$body"
|
||||
[ "$status" -ne 0 ]
|
||||
done
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue