Skip to content

0.7.1: защита SSH от перебора и фиксы установки (Xray 26.10, PPA, AWG на Debian 12) - #32

Merged
Ap3x0s merged 20 commits into
mainfrom
dev
Oct 24, 2026
Merged

Ap3x0s merged 20 commits into
mainfrom
dev

Conversation

@Ap3x0s

@Ap3x0s Ap3x0s commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

Что это

Патч-релиз 0.7.1: одна новая возможность (защита SSH от перебора) и пять исправлений, без которых разворачивание ломалось на новых ядрах и дистрибутивах. 20 коммитов, 46 files changed, 2238 insertions(+), 54 deletions(-).

Блоки изменений

1. Защита SSH от перебора (8078d7b, aa39e03, 0054cf6, 0d64df8, GUI 28a24db, ddadbd7, b846d84, e972a2c)

Замер на живом сервере 23.10: 9 953 неудачных входа за сутки со 115 адресов, root пускал по паролю, ufw — 22/tcp без limit.

  • xrayebator ssh-protect (--status, --enable, --disable, --unban IP, --prune-ignore, --quiet) ставит fail2ban с джейлом sshd: 4 неудачи за 10 минут → бан 30 минут, повторным — до суток; плюс drop-in sshd_config.d с MaxAuthTries 3 / LoginGraceTime 30.
  • Все ключи — внутри [sshd], а не в [DEFAULT]: иначе автоматическая установка меняла бы чужие джейлы fail2ban.
  • Парольный вход осознанно не выключается: GUI управляет сервером как root по SSH-паролю. Режим «только ключи» — отдельное решение оператора.
  • Конфиг sshd проверяется sshd -t до применения, ssh перезапускается через reload — открытые сессии не рвутся.
  • Исключение для адреса деплоя на 24 часа: ignoreip + метка срока, снятие таймером xrayebator-ssh-ignoreip.timer (OnCalendar=*:0/30). Живая проверка: 6 неверных паролей с исключённого адреса — счётчики fail2ban нулевые, бана нет.
  • Пункт меню 15, вкладка «Безопасность и TLS» в GUI (блок внутри панели подписки), автовключение в install.sh и quickstart (поле ssh_protect в JSON).

2. Чистая установка на Xray 26.10+ (ae14797)

install.sh валидировал mktemp-файл config.json.tmp.XXXXXX. С 26.10 ядро определяет формат по расширению и игнорирует -format json → main/confloader/external: unsupported config file extension, установка падала на любом чистом сервере (на проде стоит 26.3.27, где флаг ещё работал). Теперь временный конфиг *.json; тот же дефект исправлен в двух временных конфигах update.sh (там молча пропускалось обновление DNS); при провале печатается вывод ядра.

3. PPA AmneziaWG без ключа ломал apt (1b4ee20)

В ветке без add-apt-repository репозиторий писался без ключа → apt-get update возвращал rc=100, ломались все apt-операции (включая плагин certbot для DNS-01), а apt install amneziawg не находил пакет. Теперь репозиторий — только с signed-by, нет ключа — нет репозитория; испорченные серверы чинит миграция awg_ppa_signed_2026 (main_menu/quickstart/happ-setup).

4. AmneziaWG на Debian 12 (8595a0c)

Пакет из PPA ставился, но DKMS не собирал модуль: Debian не ставит linux-headers, а для ядра старше репозитория точного пакета нет (живой случай: запущено 6.1.0-45, в репо 47/50/53/54), dkms status оставался added. Теперь установщик ставит заголовки сам; если доступны только новые — собирает под новое ядро, ставит и образ и отдаёт {ok:false, reboot_required:true, running_kernel, built_for, error}. Ручная сборка распаковывает linux-source вместо симлинка на архив.

5. GUI больше не теряет отказ бэкенда (72562e6)

runBackendAction ждал только исключение, а {ok:false, error} из JSON проглатывал: на живом сервере awg-install вернул reboot_required с текстом, а карточка осталась «Не установлено». Теперь результат разбирается общим хелпером и причина показывается в интерфейсе — для всех операций бэкендов.

6. Ярлык AmneziaWG (84b59c9)

«AmneziaWG 2.0» в пункте меню, баннере, справке CLI и доках → нейтральный ярлык, версия формата указана там, где полезна.

Документация

security.md, configuration.md, desktop-gui.md, testing.md, troubleshooting.md, architecture.md — все три локали; README ×3; CHANGELOG; релизные заметки docs/releases/v0.7.1.{md,en.md}. Проверки: ссылки/якоря 0 битых, расхождения таблиц 0, кодировка чистая, parity без расхождений.

Как проверено

  • 45 Bash-валидаций (две новые: test-ssh-protect.sh, test-config-temp-json-suffix.sh; расширены test-awg-lifecycle.sh, test-main-menu-numbering.sh), bash -n всех скриптов.
  • 174 unit-теста (173 зелёных на Windows; красный — известный win32-кейс shell-command без /bin/sh), npm run typecheck, npm run build.
  • Живые прогоны: Ubuntu 22.04 (прод: защита включена, 8/8 маршрутов, подписка 200, 16 банов), Ubuntu 24.04 (с нуля: install → quickstart → 7/7, потом PQ → 8/8), Debian 12 (install, reboot_required до перезагрузки, после — модуль, awg0, 3.1 с HPK/RandomTrailers, пир, 7/7 маршрутов).

Риски и ограничения

  • Защита включается автоматически только при установке/деплое; на уже развёрнутом сервере после обновления её нужно включить один раз (ssh-protect --enable / кнопка в GUI). Это указано в релизных заметках.
  • Простой пароль от 4 опечаток защищён 24-часовым исключением для адреса деплоя; после суток правило обычное.
  • AWG-рукопожатие живым клиентом в этом цикле не перепроверялось (проверены интерфейс, параметры 3.1 и пир).
  • Breaking changes нет.

После мёржа

  1. Тег v0.7.1 на merge-коммит → workflow соберёт и опубликует релиз (нужен секрет AP3X0 с Contents: Read and write, иначе релиз снова выйдет от github-actions[bot]).
  2. Проверить, что тело релиза взялось из docs/releases/v0.7.1.en.md (preflight проверяет его наличие).

Ap3x0s added 20 commits October 22, 2026 16:00
Аудит документации против кода: пункт меню 12, баннер подменю, строка помощи CLI и
описания в docs/{,ru/,zh-CN/}configuration.md и docs/{,ru/,zh-CN/}architecture.md
называли бэкенд «AmneziaWG 2.0», хотя с этапа 3.1 формат включён по умолчанию —
в реестре `three_enabled: true`, в конфиге HeaderProtectionKey, RandomTrailers,
S3/S4. Это тот же класс ошибки, что уже починили в баннере установки: экран
противоречил своему же junk-блоку, а документация — поведению по умолчанию.

Теперь ярлык нейтральный — «AmneziaWG (системный VPN)», а версия формата указана
там, где она действительно полезна: в документации «3.1 по умолчанию», а в
подменю — переключатель режима с текущим состоянием.

Ширина рамки баннера сохранена: inner 47 символов, как у соседнего меню Hysteria,
поэтому подменю не разъезжается.

Проверено: bash -n xrayebator; validation/test-awg-lifecycle.sh — зелёный;
проверки документов (ссылки/якоря, таблицы, кодировка) — 0 расхождений.
Фолбэк-ветка установки (когда в системе нет add-apt-repository) писала
/etc/apt/sources.list.d/amneziawg.list без ключа. На живом сервере это дало
`apt-get update` rc=100 («NO_PUBKEY 4166F2C257290828 / repository is not
signed»): переставали работать ЛЮБЫЕ apt-операции — в том числе установка
плагина certbot для DNS-01 — а amneziawg из PPA больше не находился. Модуль
на том сервере собрался ручной сборкой, поэтому дефект не всплыл сразу и жил
с 19.10.

Теперь репозиторий пишется единственным путём — после того как ключ подписи
лёг в брелок (signed-by). Нет ключа или сети — репозиторий не добавляется
вовсе: apt остаётся рабочим, а установка честно уходит на ручную сборку.
add-apt-repository после неудачи ещё и откатывается, потому что он умеет
оставить за собой неподписанный PPA.

Уже испорченные серверы чинит разовая миграция migrate_awg_ppa_signed_2026
(main_menu, quickstart, happ-setup): подписывает репозиторий, а если ключ не
достать — убирает его. Xray не перезапускается: правка не касается его
конфига, поэтому это не run_migration(), а отдельная миграция по маркеру.

Guarded by validation/test-awg-ppa-repo.sh: строка репозитория проверяется
функционально, ремонт — на временных путях с подменённым apt-get update.
Публичный SSH-порт на VPN-сервере перебирают постоянно. Замер на живом
сервере 2.26.125.228 (2026-10-23): 9 953 неудачных попытки за сутки со 115
адресов, топ — по 1 197 попыток с шести адресов 109.160.32.0/24, при этом
root пускался по паролю, а ufw разрешал 22/tcp без limit.

Добавлена команда `xrayebator ssh-protect` (--status/--enable/--disable,
--unban IP, --quiet для скриптов): ставит fail2ban с джейлом sshd
(journald-backend, 4 неудачи за 10 минут → бан 30 минут, повторным — до
суток) и drop-in sshd с MaxAuthTries 3 / LoginGraceTime 30. Парольный вход
осознанно НЕ выключается: GUI и деплой ходят на сервер по SSH-паролю, режим
key-only сломал бы управление сервером из приложения. Ключи — отдельное
решение оператора.

Включение идемпотентно: файлы перезаписываются, конфиг sshd проверяется
`sshd -t` до применения (битый drop-in откатывается к прежнему), ssh
перечитывает конфиг через reload, а не restart — открытые сессии GUI не
рвутся. В меню появился пункт 15 «Безопасность», защита включается сама при
установке (install.sh) и при деплое (quickstart; в JSON добавлено поле
ssh_protect, чтобы GUI знал результат).

Guarded by validation/test-ssh-protect.sh (конфиги, сохранение парольного
входа, статус-JSON, снятие защиты, проводка) и обновлённым
test-main-menu-numbering.sh (теперь 15 пунктов).
Защита SSH уже есть командой, но оператор живёт в GUI: без кнопки он о ней
не узнает. На странице сервера появилась четвёртая вкладка «Безопасность» с
карточкой: состояние fail2ban, действующие лимиты sshd (MaxAuthTries /
LoginGraceTime), неудачные попытки за сутки, сколько адресов в бане и какие
именно, кнопки «Включить/Выключить защиту» и «Обновить».

Проводка стандартная для серверных операций: MaintenanceManager →
IPC (`security:sshProtectStatus|Enable|Disable`) → preload → экран. Статус
тянется best-effort параллельно с профилями: сервер со старым менеджером
защиту не знает, и это не должно ронять страницу.

Парольный вход приложение не выключает и прямо пишет об этом в карточке —
иначе оно само потеряло бы доступ к серверу (authMethod: password). Ключи
остаются осознанным решением оператора, а не побочным эффектом кнопки.

Ключи i18n добавлены во все три локали (ru/en/zh) — паритет проверяется
тестом; серверный ответ разбирается с защитными значениями по умолчанию,
поэтому частичный JSON не ломает экран.

Guarded by tests/unit/server-settings-render.test.ts: разделов теперь четыре,
у каждого своя панель с aria-связкой.
Описано то, что появилось в двух предыдущих коммитах, во всех трёх локалях:

- security.md — раздел «Brute-force protection (fail2ban)»: цифры живого замера
  (9 953 неудачи за сутки со 115 адресов), что именно пишется в систему, почему
  парольный вход остаётся включён (GUI управляет сервером по SSH-паролю),
  где это доступно (пункт меню 15, вкладка «Безопасность», автовключение
  в install.sh и quickstart) и как разбанить себя;
- configuration.md — строка в таблице команд с флагами и поведением;
- desktop-gui.md — `ssh-protect` в списке команд, которые вызывает приложение,
  и абзац про вкладку «Безопасность»;
- testing.md — два новых скрипта валидации;
- troubleshooting.md — раздел про падающую установку AWG: симптом
  `NO_PUBKEY … / repository is not signed`, причина (репозиторий без ключа),
  что чинит миграция и как поправить руками;
- README ×3 — строка в таблице возможностей;
- CHANGELOG — Added (защита SSH) и Fixed (неподписанный PPA).

Проверено: check-links (0 битых ссылок и расхождений колонок),
check-encoding (35 файлов, вне алфавита 0), parity (расхождения заголовков и
таблиц не появились; остаётся прежний шум переводов в zh-CN).
По скриншоту с живого сервера: отдельная четвёртая вкладка «Безопасность»
оказалась лишним кликом ради одной карточки, а вкладка «Подписка и TLS» и без
того про безопасность периметра. Теперь карточка защиты SSH — второй блок
внутри той же панели, а панель называется «Безопасность и TLS» (Security and
TLS, 安全与 TLS).

Панель остаётся одна: разделы снова create/backends/subscription, отдельного
секции security нет — тест разделов возвращён к трём id, а карточки друг под
другом держит sectionPane с gap 20px.

Ключ settings.sectionSecurity удалён из всех трёх локалей (стал не нужен),
значение sectionSubscription переписано.
Доки ушли на dev ещё в 3fc6d78, но после переезда карточки внутрь панели
(ddadbd7) в них осталось старое имя вкладки — «Безопасность» / Security /
安全. Это тот же класс расхождения, что мы уже ловили в ярлыке AmneziaWG:
написанное перестало соответствовать экрану через один коммит.

Теперь везде «Безопасность и TLS» (Security and TLS, 安全与 TLS) и явно
сказано, что карточка защиты SSH — блок внутри этой панели, а не отдельная
вкладка: security.md ×3, desktop-gui.md ×3, configuration.md ×3, README ×3,
CHANGELOG.

Карточка подписки внутри панели по-прежнему называется «Подписка и TLS» —
это её собственный заголовок (settings.dns01Title), и он не менялся.

Проверено: ссылки/якоря 0 битых, таблицы 0 расхождений, кодировка 35/35,
parity без расхождений заголовков и таблиц.
… пароль

Две проблемы со скриншота живого сервера.

1. Строка «Парольный вход оставлен — без него приложение потеряет доступ к
   серверу» читалась как «включил защиту — потерял вход по паролю», хотя
   fail2ban вообще не трогает способ аутентификации: она только банит
   источник перебора. Это была моя пояснительная ремарка про архитектурное
   решение, и в интерфейсе она только пугает. Из карточки убрана совсем;
   обоснование осталось там, где ему место — в docs/security.md.

2. Состояние читалось только по бейджу, а иконка была одна на всё. Теперь
   щит говорит сам: зелёный — защита работает, перечёркнутый красноватый —
   установлена и остановлена, нейтральный — нет данных. При включённой
   защите подзаголовок показывает само правило («4 неудачи за 10 минут →
   бан 30 минут, повторным — до суток»), при выключенной — что именно
   сделает включение.

Заодно убран обрезанный четвёртый столбец «ДЖЕЙЛ» («fail2ban · sshd, 4
неудачи за 10 …»): правило переехало в подзаголовок, а в сетке остались три
числовых значения — неудачи за сутки, баны сейчас и лимиты sshd.

Проверено offscreen-прогоном собранного renderer'а в обоих состояниях
(включено/выключено) — layout не разъезжается, обрезаний нет.
Две правки формулировок в карточке защиты SSH по замечаниям с экрана.

1. Бейдж выключенного состояния: «остановлена» → «выключена». «Остановлена»
   читается как «служба упала», а не как «оператор её выключил» — при том что
   рядом стоит кнопка «Включить защиту».

2. Правило под заголовком: «4 неудачи за 10 минут → бан 30 минут, повторным —
   до суток» → «4 неудачи за 10 минут → бан 30 минут, повторная попытка
   перебора → бан до суток». «Повторным» без пояснения читалось как «повторным
   неудачам», хотя речь про повторный заход уже забаненного адреса
   (bantime.increment в fail2ban).

Локали: enabled/disabled и правило переписаны в ru/en/zh одинаково по смыслу.
Логика и серверная часть не менялись — правка только в строках интерфейса.
Живой случай 2026-10-24 (свежий VPS, Ubuntu 22.04 + Xray 26.10.21): install.sh
падал на «Сгенерированный config.json не прошёл валидацию — live-конфиг не
изменён». Причина не в самом конфиге, а в имени временного файла.

Ядро 26.10.21 определяет формат конфига по РАСШИРЕНИЮ файла, а флаг
`-format json` (на который опирался install.sh) его больше не переопределяет:

  Failed to start: ... > main/confloader/external:
  unsupported config file extension: /usr/local/etc/xray/config.json.tmp.GbsZMJ

mktemp даёт имя вида config.json.tmp.XXXXXX, то есть расширение «GbsZMJ», —
валидация падала на ЛЮБОЙ чистой установке с этим ядром. На проде стоит
26.3.27, где -format json ещё работал, поэтому баг не всплывал раньше.

Теперь временный конфиг создаётся как config.json.tmp.XXXXXX.json
(mktemp --suffix=.json) с фолбэком на переименование, если mktemp старый.
Тот же приём применён к двум временным конфигам в update.sh (правка DNS там
молча пропускалась по той же причине).

Заодно install.sh теперь печатает вывод `xray run -test` при провале: без него
сообщение «не прошёл валидацию» не говорит, что именно не понравилось ядру, и
причина ищется вслепую.

Guarded by validation/test-config-temp-json-suffix.sh: статически проверяет
суффикс в install.sh/update.sh и печать вывода ядра, функционально — что
mktemp --suffix=.json действительно даёт *.json (и что установленный xray
отвергает конфиг без расширения).
Описан дефект, из-за которого чистая установка падала на Xray 26.10.21:
ядро определяет формат конфига по расширению файла и игнорирует
`-format json`, а установщик проверял mktemp-файл без .json.

- CHANGELOG → Fixed: симптом, причина, что исправлено в install.sh и
  update.sh, и что провал теперь печатает вывод ядра;
- testing.md ×3 → строка нового скрипта
  `test-config-temp-json-suffix.sh` и счётчик скриптов 42 → 45
  (в трёх местах: вводный абзац, список и CI-раздел).

Проверки документов: ссылки/якоря 0 битых, таблицы 0 расхождений,
кодировка 35/35, parity без новых расхождений.
Защита включается автоматически при установке (install.sh, quickstart), а
записывала настройки в секцию [DEFAULT] конфига fail2ban. На сервере, где
оператор уже держит свой fail2ban с другими джейлами, это молча меняло бы
bantime/findtime/maxretry и bantime.increment для ВСЕХ джейлов — то есть
автоматическая защита вмешивалась бы в чужую настройку.

Теперь все ключи живут внутри [sshd]: backend=systemd, bantime=30m,
findtime=10m, maxretry=4, bantime.increment/factor/maxtime. Поведение нашей
защиты не меняется, чужие джейлы не затрагиваются.

Тест дополнен: `[DEFAULT]` в файле запрещён, а maxretry обязан идти после
заголовка секции — иначе регрессия вернётся молча.
Досинхронизировал то, чего не хватало после фикса установщика:

- troubleshooting.md ×3 — раздел про
  «Сгенерированный config.json не прошёл валидацию — live-конфиг не изменён»
  на Xray 26.10.21+: симптом, настоящая причина (имя временного файла и
  определение формата по расширению), что исправлено и что делать на старой
  сборке. Раньше об этой ошибке в доках не было ничего, хотя это полный стоп
  установки;
- security.md ×3 — уточнено, что настройки джейла лежат внутри `[sshd]`,
  поэтому уже настроенный оператором fail2ban с другими джейлами не меняется.
  Это прямое следствие того, что защита включается автоматически при установке.

Проверки документов: ссылки/якоря 0 битых, таблицы 0 расхождений,
кодировка 35/35, parity без новых расхождений.
Защита включается автоматически, и это создаёт новый способ запереться:
4 неверных пароля за 10 минут — и собственный адрес в бане на 30 минут,
причём GUI ходит с того же адреса. Для оператора это полчаса без управления
сервером, а на свежей машине без консоли хостера — вообще тупик.

Теперь при включении защиты адрес, с которого пришла установка/деплой,
попадает в ignoreip джейла на 24 часа (SSH_CONNECTION/SSH_CLIENT — sshd
отдаёт его и для неинтерактивных команд, поэтому GUI-деплой тоже виден).
Рядом пишется `# xrayebator-ignore-until: <epoch>`, а таймер
xrayebator-ssh-ignoreip (каждые 30 минут + при загрузке) вызывает
`ssh-protect --prune-ignore` и снимает строку по истечении срока.

Детали, которые важны:
- `ignoreip` живёт внутри `[sshd]` — чужие джейлы fail2ban не затрагиваются;
- базовый `ignoreip = 127.0.0.1/8 ::1` пишется ВСЕГДА: без строки политика
  игнора джейла становится пустой (проверено live: после снятия адреса
  `fail2ban-client get sshd ignoreip` показывал «No IP address/network»);
- если включение идёт не по SSH (консоль хостера), действующее исключение
  сохраняется, а не выкидывается;
- `--disable` снимает и таймер, и исключение;
- статус-JSON получил `ignore_ip`/`ignore_until` (в GUI не показываем —
  по просьбе, это служебное поле для CLI и тестов).

Проверено live на тестовом сервере: после `--enable` по SSH
`fail2ban-client get sshd ignoreip` показывает наш адрес, таймер активен,
`--prune-ignore` при будущем сроке ничего не делает, а после подмены срока на
истёкший — снимает строку, перезагружает джейл и удаляет юниты таймера.

Guarded by validation/test-ssh-protect.sh: конфиг со сроком, разбор адреса из
SSH_CONNECTION/SSH_CLIENT, отказ для loopback, снятие по сроку и сохранение
loopback-базы.
Описал новое поведение защиты SSH во всех трёх локалях:

- security.md — адрес, с которого шла установка/деплой, исключён из банов на
  24 часа: `ignoreip` + метка `# xrayebator-ignore-until`, снятие таймером
  `xrayebator-ssh-ignoreip.timer` (30 минут), базовая строка loopback остаётся
  навсегда; это деталь CLI/JSON и в GUI не показывается;
- configuration.md — в строке команды добавлен `--prune-ignore` и краткое
  описание исключения;
- architecture.md — новый пункт про то, как устроены джейл, исключение и
  таймер (рядом с трафиком и мониторингом);
- testing.md — строка `test-ssh-protect.sh` дополнена покрытием исключения:
  разбор адреса из SSH_CONNECTION/SSH_CLIENT, отказ для loopback, снятие по
  сроку, сохранение loopback-базы;
- CHANGELOG — тот же смысл в записи про защиту SSH.

Проверки документов: ссылки/якоря 0 битых, таблицы 0 расхождений,
кодировка 35/35, parity без новых расхождений.
Живая находка на Ubuntu 24.04: после цикла «срок истёк → prune удалил юниты
таймера → повторный --enable создал их заново» таймер оставался active, но
следующий запуск не планировался:

  TimersMonotonic={ OnUnitActiveUSec=30min ; next_elapse=0 }
  NextElapseUSecMonotonic=infinity

То есть исключение из банов могло не сняться само — ровно в том сценарии,
ради которого таймер и заведён (24 часа прошли, строка осталась). Причина:
OnUnitActiveSec считается от последней активации сервиса, а после
пересоздания юнита systemd не видит для неё будущего момента.

Теперь расписание календарное: OnCalendar=*:0/30 (:00 и :30 каждый час) плюс
OnBootSec=10min и Persistent=true. Проверено на сервере: после prune →
--enable list-timers показывает NEXT 16:00, сервис по запуску отдаёт
{"pruned":false,"active":true}, исключение на месте.

Тест дополнен: таймер обязан использовать OnCalendar, иначе тихая регрессия
(активный таймер без расписания) вернётся незамеченной.
Живой случай 2026-10-24 (Debian 12, ядро 6.1.0-45-amd64, установка через GUI):
пакет amneziawg из PPA встал, но модуль не собрался. Причина — DKMS без
заголовков: Debian не ставит linux-headers по умолчанию, а точного пакета
`linux-headers-6.1.0-45-amd64` в репозитории уже нет (там 47/50/53/54).
`dkms status` показывал «amneziawg/1.0.0: added», DKMS молчал, и установка
падала общим «Не удалось установить amneziawg (PPA и ручная сборка)» — по
такому сообщению причину не найти.

Что сделано:
- новый шаг `_awg_install_kernel_headers`: сначала точный пакет запущенного
  ядра, затем метапакеты `linux-headers-<arch>` + `linux-image-<arch>` (на
  Ubuntu — `linux-headers-generic`). Образ ставится вместе с заголовками
  намеренно: без него DKMS собрал бы модуль под ядро, в которое сервер никогда
  не загрузится;
- PPA-путь при отсутствии модуля теперь сначала проверяет заголовки, потом
  пересобирает DKMS, и отдельно распознаёт «собрано для другого ядра»
  (`return 2`), а `_awg_install` превращает это в `return 3`;
- `awg-install` на этот случай отдаёт `{"ok":false,"reboot_required":true,
  "running_kernel":…,"built_for":…,"error":"…"}` с кодом 0: GUI показывает
  текст из JSON, а не первую строку прогресса из stderr. В stderr печатается
  пошаговая инструкция (перезагрузить и повторить awg-install);
- ручная сборка больше не линкует архив: `linux-source` на Debian/Ubuntu лежит
  как `linux-source-*.tar.xz`, его распаковывают в рабочий каталог и на дерево
  ссылаются уже оттуда;
- в ручной сборке заголовки ставятся тем же хелпером.

Проверено на живом Debian 12: после фикса `awg-install` доустановил заголовки
6.1.0-54 вместе с образом, DKMS собрал модуль именно под него и вернул
`reboot_required` вместо «не удалось установить».

Guarded by validation/test-awg-lifecycle.sh (хелпер заголовков, порядок
«заголовки → DKMS», отдельный статус перезагрузки, распаковка linux-source).
Описал живой случай 2026-10-24 (Debian 12, ядро 6.1.0-45, установка через GUI):

- troubleshooting.md ×3 — новый пункт в «Установка AWG падает»: `dkms status` показывает
  «amneziawg/1.0.0: added», а модуля нет. Причина — нет заголовков ядра: Debian/Ubuntu
  не ставят их по умолчанию, а для старого ядра точного пакета уже нет в репозитории.
  Что делает установщик (ставит заголовки, при необходимости собирает под новое ядро
  вместе с его образом), что значит `reboot_required: true` и что делать дальше;
- configuration.md ×3 — в разделе AmneziaWG добавлено, что DKMS нужны заголовки,
  что образ нового ядра ставится вместе с заголовками (иначе ребут не поможет) и что
  после перезагрузки команда завершает настройку;
- CHANGELOG → Fixed с тем же содержанием и упоминанием распаковки linux-source в
  ручной сборке.

Проверки документов: ссылки/якоря 0 битых, таблицы 0 расхождений, кодировка 35/35,
parity без новых расхождений.
Живой случай 2026-10-24, установка AmneziaWG из GUI на Debian 12: сервер
вернул `{"ok":false,"reboot_required":true,"error":"…Перезагрузите сервер…"}`,
но карточка просто осталась «Не установлено» — без единого слова о причине.
Причина в `runBackendAction`: он ждал только выброшенное исключение, а
`{ok:false, error}` из JSON молча проглатывал. Выглядело как «нажал —
ничего не произошло».

Теперь результат разбирается: `backendActionFailure` (новый чистый модуль)
возвращает `{failed, error}`, и при `ok:false` GUI показывает текст от сервера
в общем блоке ошибки, не перерисовывая карточку. Правило действует для ВСЕХ
операций бэкендов (установка/запуск/гранты/тумблеры), потому что все они идут
через `runBackendAction`.

Ключ `settings.backendActionFailed` добавлен во все три локали — на случай,
когда сервер не приложил текст.

Проверено: ui-contracts получил контракт разбора (ok:false с текстом, без
текста, пробельный текст, ok:true, undefined, null, строка);
typecheck чистый, сборка собрана.
Патч-релиз: защита SSH от перебора и исправления, без которых ломалось
развёртывание на новых ядрах и дистрибутивах.

- package.json / package-lock.json → 0.7.1;
- CHANGELOG: Unreleased превращён в [0.7.1] - 2026-10-24 с вводным абзацем,
  добавлен Fixed про GUI, который терял отказ {ok:false};
- docs/releases/v0.7.1.md и v0.7.1.en.md — релизные заметки (английская
  версия используется workflow как тело релиза);
- troubleshooting ×3: формулировки «после 0.7.0» → «в 0.7.1», потому что фиксы
  теперь действительно входят в релиз.

Цифры в заметках — проверяемые: 45 Bash-валидаций, 174 unit-теста (173
зелёных на Windows, известный win32-кейс shell-command), живые прогоны на
Ubuntu 22.04 (прод), Ubuntu 24.04 (с нуля, 8/8 маршрутов) и Debian 12
(установка AWG с путём reboot_required).
@Ap3x0s
Ap3x0s merged commit 31eec69 into main Oct 24, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant