Add initial files

This commit is contained in:
2026-10-11 22:44:49 +03:00
parent ed60d44ad7
commit 48cec1c1b0
3 changed files with 682 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/.idea/
+481
View File
@@ -0,0 +1,481 @@
# WireGuard Manager: план MVP
## 1. Назначение проекта
WireGuard Manager — локальный Bash-скрипт для администрирования WireGuard на том же Linux-сервере, где запущен WireGuard.
Основные задачи проекта:
- просмотр состояния WireGuard;
- просмотр списка клиентов;
- добавление клиентов;
- удаление клиентов;
- получение клиентского конфига и QR-кода;
- диагностика конфигурации, сервиса и сети;
- ведение локального журнала административных операций;
- создание резервных копий перед изменением конфигурации.
Существующий `add-user.sh` используется как исходный пример, но его логика будет переработана в безопасные переиспользуемые функции.
## 2. Модель распространения и запуска
Проект не устанавливается в систему и не добавляется в `PATH`. Скрипт и сопутствующие файлы находятся в переносимой директории, например:
```text
/root/wireguard-manager/
├── wg-manager.sh
├── wg-manager.conf
├── add-user.sh
├── bin/
│ └── gum
├── backups/
├── logs/
└── tests/
```
Основная точка входа:
```bash
sudo /root/wireguard-manager/wg-manager.sh
```
CLI-команды вызываются через тот же файл:
```bash
sudo /root/wireguard-manager/wg-manager.sh status
sudo /root/wireguard-manager/wg-manager.sh list
sudo /root/wireguard-manager/wg-manager.sh add alice
sudo /root/wireguard-manager/wg-manager.sh diagnose
```
Скрипт определяет свою директорию через `BASH_SOURCE`, поэтому всю директорию проекта можно перемещать. Скрипты `install.sh` и `uninstall.sh` не нужны.
Установку или размещение зависимостей выполняет администратор сервера.
## 3. TUI
Для терминального интерфейса используется `gum`. Встроенный TUI на чистом Bash, а также интерфейсы на `dialog` и `whiptail` в MVP не планируются.
Поиск бинарника `gum` выполняется в следующем порядке:
1. Путь из переменной окружения `WG_MANAGER_GUM`.
2. `${SCRIPT_DIR}/bin/gum` рядом со скриптом.
3. Команда `gum` из `PATH`.
Если `gum` отсутствует:
- CLI-команды продолжают работать;
- при попытке открыть TUI выводится понятное сообщение об ошибке;
- скрипт не устанавливает и не скачивает `gum` автоматически.
### Главное меню
```text
WireGuard Manager
Интерфейс: wg0
Состояние: активен
Клиентов: 12 Активны недавно: 4
1. Состояние WireGuard
2. Список клиентов
3. Добавить клиента
4. Информация о клиенте
5. Показать QR-код / конфиг
6. Удалить клиента
7. Диагностика
8. Журнал операций
9. Резервные копии
0. Выход
```
TUI должен использовать возможности `gum` для меню, фильтрации списка клиентов, ввода данных, таблиц, подтверждений, просмотра длинного текста и индикации выполнения операций.
## 4. Архитектурный принцип
TUI не содержит бизнес-логику. Он только собирает ввод пользователя и вызывает те же функции, которые доступны через CLI.
Логические слои внутри `wg-manager.sh`:
1. Загрузка и проверка конфигурации.
2. Проверка прав и зависимостей.
3. Чтение состояния WireGuard.
4. Управление клиентами.
5. Безопасное изменение серверного конфига.
6. Резервное копирование и восстановление.
7. Диагностика.
8. Audit log.
9. CLI-маршрутизация.
10. TUI на `gum`.
Для MVP эти функции могут находиться в одном `wg-manager.sh`. Если файл станет слишком большим, их можно будет позднее вынести в `lib/*.sh`, сохранив прежнюю точку входа.
## 5. Конфигурация менеджера
Из `add-user.sh` необходимо убрать жёстко заданные параметры и перенести их в `wg-manager.conf`.
Пример состава конфигурации:
```bash
WG_INTERFACE="wg0"
WG_CONF="/etc/wireguard/wg0.conf"
CLIENT_DIR="/etc/wireguard/clients"
SERVER_ENDPOINT="vpn.example.com"
SERVER_PORT="51820"
SERVER_VPN_IP="10.9.0.1"
CLIENT_DNS="10.9.0.1,192.168.0.200"
VPN_SUBNET_PREFIX="10.9.0"
START_HOST="2"
END_HOST="254"
USE_IPV6="0"
IPV6_PREFIX="fd05:13:53::"
DEFAULT_CLIENT_MODE="full-tunnel"
RECENT_HANDSHAKE_SECONDS="180"
```
Конфигурация должна проверяться до выполнения любой операции. Неизвестные или небезопасные значения не должны напрямую подставляться в команды.
Рекомендуемые права:
```bash
chmod 700 /root/wireguard-manager
chmod 700 /root/wireguard-manager/wg-manager.sh
chmod 600 /root/wireguard-manager/wg-manager.conf
```
## 6. Функции MVP
### 6.1. Состояние WireGuard
Экран состояния показывает:
- имя интерфейса;
- состояние интерфейса и `wg-quick`-сервиса;
- публичный ключ сервера;
- UDP-порт;
- количество peers;
- время работы или доступную информацию о запуске сервиса;
- наличие предупреждений диагностики.
### 6.2. Список клиентов
Для каждого клиента показываются:
- имя;
- VPN-адрес;
- сокращённый публичный ключ;
- endpoint, если он известен;
- время последнего handshake;
- принятый и переданный трафик;
- вычисляемое состояние;
- признак, управляется ли клиент этим скриптом.
Предлагаемые состояния:
- `active` — handshake был недавно;
- `inactive` — клиент ранее подключался, но давно неактивен;
- `never` — handshake ещё не было;
- `unmanaged` — peer найден в WireGuard, но его имя и метаданные неизвестны менеджеру.
Источником текущего состояния является `wg show ... dump`. Серверный конфиг используется для постоянных параметров и сопоставления peers.
### 6.3. Добавление клиента
Операция добавления:
1. Проверяет имя клиента.
2. Проверяет отсутствие клиента, ключей и файлов с таким именем.
3. Находит свободный IP в заданном диапазоне.
4. Проверяет конфликты во всех `AllowedIPs`.
5. Создаёт private key, public key и preshared key.
6. Формирует серверный peer-блок.
7. Формирует клиентский конфиг.
8. Проверяет будущую серверную конфигурацию.
9. Создаёт резервную копию.
10. Атомарно сохраняет и применяет конфигурацию.
11. Сохраняет метаданные клиента.
12. Записывает административное действие в журнал.
Имя клиента допускает только безопасный ограниченный набор символов: латинские буквы, цифры, точку, подчёркивание и дефис.
MVP поддерживает два режима маршрутизации клиента:
- `full-tunnel` — весь IPv4-трафик идёт через VPN;
- `split-tunnel` — через VPN идут только указанные сети.
### 6.4. Просмотр и экспорт клиента
Для выбранного клиента доступны:
- сведения о peer;
- путь к клиентскому конфигу;
- просмотр конфига после отдельного подтверждения;
- QR-код через `qrencode`, если команда доступна;
- понятное сообщение и путь к конфигу, если `qrencode` отсутствует.
Приватные ключи и содержимое клиентских конфигов не записываются в audit log.
### 6.5. Удаление клиента
Удаление требует выбора клиента и явного подтверждения. Перед изменением создаётся резервная копия.
Операция:
1. Находит peer по сохранённому публичному ключу, а не только по имени.
2. Удаляет peer из будущей серверной конфигурации.
3. Проверяет новый конфиг.
4. Применяет изменение.
5. Удаляет или архивирует клиентские файлы согласно выбранной политике.
6. Записывает действие в audit log без секретов.
Для MVP безопаснее по умолчанию перемещать клиентские файлы в локальный архив, а не уничтожать безвозвратно.
### 6.6. Диагностика
Диагностика проверяет:
- запуск с необходимыми правами;
- наличие `wg`, `wg-quick`, `ip` и других обязательных команд;
- наличие серверного конфига;
- права на конфиги и ключи;
- наличие и состояние интерфейса;
- состояние `wg-quick@<interface>` при использовании systemd;
- возможность прочитать активную конфигурацию;
- корректность адресов и `AllowedIPs`;
- дублирующиеся адреса и пересекающиеся сети;
- включён ли IPv4 forwarding;
- IPv6 forwarding, если IPv6 включён в настройках;
- маршруты сервера;
- наличие ожидаемых NAT/firewall-правил;
- прослушивание настроенного UDP-порта;
- расхождение между конфигом на диске и активным состоянием;
- последние релевантные сообщения systemd journal.
Результат каждой проверки имеет статус `OK`, `WARN`, `FAIL` или `SKIP`. Команда диагностики возвращает ненулевой exit code при критических ошибках.
WireGuard не предоставляет полноценную историю клиентских подключений. Менеджер показывает текущее состояние, последние handshake и доступные системные журналы, но не заявляет их как полную историю активности пользователей.
### 6.7. Журнал операций
Локальный audit log содержит:
- дату и время;
- выполненную операцию;
- имя клиента, если применимо;
- результат;
- краткое описание ошибки;
- пользователя или UID, запустившего операцию.
В журнал запрещено записывать:
- private keys;
- preshared keys;
- полные клиентские конфиги;
- команды, содержащие секретные значения.
### 6.8. Резервные копии
Перед каждым изменением создаётся резервная копия серверного конфига и относящихся к операции метаданных.
MVP предоставляет:
- создание резервной копии;
- список резервных копий;
- восстановление выбранной копии с подтверждением;
- автоматический rollback при ошибке применения нового конфига;
- ограничение количества автоматически сохраняемых копий.
## 7. Безопасное изменение конфигурации
Изменение `wg0.conf` должно быть транзакционным:
1. Получить эксклюзивную блокировку через `flock`.
2. Повторно прочитать актуальное состояние после получения блокировки.
3. Проверить входные данные.
4. Создать backup.
5. Сформировать новый конфиг во временном файле с `umask 077`.
6. Проверить синтаксис и возможность преобразования конфига средствами WireGuard.
7. Установить правильные права и владельца.
8. Атомарно заменить рабочий файл.
9. Применить конфигурацию через `wg syncconf` и `wg-quick strip`.
10. Проверить итоговое активное состояние.
11. При ошибке восстановить предыдущий файл и активную конфигурацию.
12. Освободить блокировку и записать результат в журнал.
Нельзя сначала дописывать peer непосредственно в рабочий конфиг, как это сейчас делает `add-user.sh`, а затем надеяться, что применение завершится успешно.
## 8. CLI
Минимальный набор команд:
```text
wg-manager.sh Открыть TUI
wg-manager.sh status Состояние WireGuard
wg-manager.sh list Список клиентов
wg-manager.sh add NAME Добавить клиента
wg-manager.sh show NAME Информация о клиенте
wg-manager.sh config NAME Показать путь к клиентскому конфигу
wg-manager.sh qr NAME Показать QR-код
wg-manager.sh remove NAME Удалить клиента
wg-manager.sh diagnose Выполнить диагностику
wg-manager.sh backup Создать резервную копию
wg-manager.sh backups Показать резервные копии
wg-manager.sh restore BACKUP Восстановить резервную копию
wg-manager.sh log Показать audit log
wg-manager.sh help Показать справку
```
CLI должен иметь стабильные exit codes и пригодный для чтения текстовый вывод. Структурированный JSON-вывод не входит в обязательный объём MVP.
## 9. Метаданные клиентов
Комментарии вида `### Client NAME` можно поддерживать для читаемости и совместимости с существующим `add-user.sh`, но они не должны быть единственным источником данных.
Для управляемого клиента необходимо хранить как минимум:
- имя;
- публичный ключ;
- VPN-адрес;
- дату создания;
- путь к клиентскому конфигу;
- режим маршрутизации;
- статус управления.
Формат метаданных должен быть простым и обрабатываться стандартными средствами Bash без обязательной зависимости от `jq`. Серверный конфиг и фактическое состояние `wg` остаются источниками истины для сетевых параметров.
## 10. Зависимости
Обязательные:
- Bash;
- WireGuard tools: `wg`, `wg-quick`;
- стандартные Linux-команды, используемые скриптом;
- `flock` для защиты от параллельных изменений.
Для TUI:
- `gum`, предоставляемый или устанавливаемый администратором.
Необязательные:
- `qrencode` для QR-кодов;
- `systemctl` и `journalctl` для серверов с systemd;
- инструменты конкретного firewall: `nft` или `iptables`.
При отсутствии необязательной команды соответствующая функция должна показывать `SKIP` или понятное сообщение, а не завершать весь менеджер аварийно.
## 11. Тестирование
Большая часть тестов не должна требовать реального интерфейса WireGuard или изменения `/etc/wireguard`.
Необходимые сценарии:
- разбор серверного конфига;
- определение следующего свободного IP;
- обнаружение конфликтующих `AllowedIPs`;
- проверка имён клиентов;
- построение peer-блока;
- удаление только выбранного peer-блока;
- обработка unmanaged peers;
- успешное применение транзакции;
- rollback при ошибке применения;
- защита от параллельного запуска;
- отсутствие секретов в логах;
- работа CLI без `gum`;
- корректная ошибка TUI без `gum`;
- поиск локального и системного бинарника `gum`.
Для тестов используются fixtures конфигураций и mock-команды `wg`, `wg-quick`, `systemctl`, `ip`, `nft` и `iptables`, подставляемые через контролируемый `PATH`.
Дополнительные проверки качества:
- `shellcheck`;
- `bash -n`;
- единый формат shell-кода;
- тестирование на поддерживаемых версиях Bash.
## 12. Этапы реализации
### Этап 1. Каркас
- создать `wg-manager.sh`;
- реализовать загрузку конфигурации;
- определить директорию проекта;
- добавить обработку ошибок и временных файлов;
- добавить проверку root для изменяющих операций;
- реализовать CLI `help`, `status` и `list`.
### Этап 2. Модель клиентов
- разобрать существующий `wg0.conf`;
- связать имена, публичные ключи и адреса;
- отображать managed и unmanaged peers;
- добавить метаданные клиентов.
### Этап 3. Безопасные изменения
- перенести и переработать логику `add-user.sh`;
- добавить блокировку, backup, валидацию и rollback;
- реализовать `add` и `remove`;
- реализовать архивирование клиентских файлов.
### Этап 4. Диагностика и журнал
- добавить диагностические проверки;
- добавить статусы `OK/WARN/FAIL/SKIP`;
- добавить audit log без секретов;
- добавить просмотр резервных копий и восстановление.
### Этап 5. TUI на `gum`
- реализовать главное меню;
- таблицу и фильтрацию клиентов;
- формы добавления;
- карточку клиента;
- подтверждение удаления и восстановления;
- экраны диагностики, логов и резервных копий.
### Этап 6. Проверка MVP
- добавить fixtures и mocks;
- протестировать успешные и аварийные сценарии;
- проверить поведение на реальном тестовом интерфейсе WireGuard;
- подготовить README с примерами запуска и конфигурации.
## 13. Критерии готовности MVP
MVP считается готовым, если администратор может:
1. Скопировать или переместить директорию проекта на Linux-сервер.
2. Настроить `wg-manager.conf` без редактирования основной логики.
3. Запустить TUI через `wg-manager.sh` с предоставленным `gum`.
4. Использовать основные функции через CLI без TUI.
5. Просмотреть WireGuard и список известных клиентов.
6. Безопасно добавить клиента и получить его конфиг или QR-код.
7. Безопасно удалить клиента.
8. Выполнить диагностику и увидеть конкретные предупреждения и ошибки.
9. Просмотреть журнал административных операций без утечки ключей.
10. Восстановить конфигурацию из backup после неудачного изменения.
11. Не повредить рабочий конфиг при ошибке команды или параллельном запуске.
## 14. За пределами MVP
После завершения MVP можно добавить:
- временное отключение и повторное включение клиента без удаления;
- срок действия клиента;
- ротацию ключей;
- импорт существующих unmanaged peers;
- поддержку нескольких WireGuard-интерфейсов;
- шаблоны маршрутов и DNS для разных типов клиентов;
- экспорт диагностического отчёта;
- уведомления о проблемах;
- JSON-вывод для интеграций;
- локализацию интерфейса;
- дополнительные варианты поставки `gum` для разных архитектур.
Эти функции не должны задерживать выпуск первого работоспособного MVP.
Executable
+200
View File
@@ -0,0 +1,200 @@
#!/usr/bin/env bash
set -euo pipefail
WG_INTERFACE="wg0"
WG_CONF="/etc/wireguard/${WG_INTERFACE}.conf"
WG_DIR="/etc/wireguard"
CLIENT_DIR="${WG_DIR}/clients"
SERVER_PORT="51820"
DEFAULT_ENDPOINT="37.26.136.232"
SERVER_VPN_IP="10.9.0.1"
VPN_SUBNET_PREFIX="10.9.0"
START_HOST="2"
END_HOST="254"
USE_IPV6="0"
IPV6_PREFIX="fd05:13:53::"
umask 077
die() {
echo "Ошибка: $*" >&2
exit 1
}
need_cmd() {
command -v "$1" >/dev/null 2>&1 || die "Не найдена команда: $1"
}
usage() {
cat <<EOF
Использование:
sudo $0 CLIENT_NAME [ENDPOINT]
Примеры:
sudo $0 android-phone
sudo $0 work-laptop vpn.example.com
sudo $0 user5 37.26.136.232
Что делает:
- создаёт ключи клиента
- подбирает следующий свободный IP
- добавляет peer в ${WG_CONF}
- создаёт клиентский конфиг в ${CLIENT_DIR}
- применяет конфиг WireGuard
EOF
}
[[ $# -ge 1 ]] || { usage; exit 1; }
CLIENT_NAME="$1"
ENDPOINT="${2:-$DEFAULT_ENDPOINT}"
[[ "$EUID" -eq 0 ]] || die "Скрипт нужно запускать через sudo/root"
[[ -f "$WG_CONF" ]] || die "Не найден конфиг ${WG_CONF}"
need_cmd wg
need_cmd awk
need_cmd grep
need_cmd sed
need_cmd sort
need_cmd tee
need_cmd mkdir
need_cmd chmod
mkdir -p "$CLIENT_DIR"
if [[ ! "$CLIENT_NAME" =~ ^[a-zA-Z0-9._-]+$ ]]; then
die "Имя клиента может содержать только буквы, цифры, точку, подчёркивание и дефис"
fi
CLIENT_CONF="${CLIENT_DIR}/${WG_INTERFACE}-client-${CLIENT_NAME}.conf"
CLIENT_PRIVKEY_FILE="${CLIENT_DIR}/${CLIENT_NAME}.key"
CLIENT_PUBKEY_FILE="${CLIENT_DIR}/${CLIENT_NAME}.pub"
CLIENT_PSK_FILE="${CLIENT_DIR}/${CLIENT_NAME}.psk"
if grep -qE "^### Client ${CLIENT_NAME}$" "$WG_CONF"; then
die "Клиент с именем '${CLIENT_NAME}' уже есть в ${WG_CONF}"
fi
if [[ -e "$CLIENT_CONF" || -e "$CLIENT_PRIVKEY_FILE" || -e "$CLIENT_PUBKEY_FILE" || -e "$CLIENT_PSK_FILE" ]]; then
die "Файлы клиента '${CLIENT_NAME}' уже существуют в ${CLIENT_DIR}"
fi
find_next_ipv4() {
local used host
used="$(
grep -oE "${VPN_SUBNET_PREFIX//./\\.}\.[0-9]+/32" "$WG_CONF" \
| sed 's#/32##' \
| awk -F. '{print $4}' \
| sort -n \
| uniq
)"
for host in $(seq "$START_HOST" "$END_HOST"); do
if ! grep -qx "$host" <<<"$used"; then
echo "${VPN_SUBNET_PREFIX}.${host}"
return 0
fi
done
return 1
}
CLIENT_IPV4="$(find_next_ipv4)" || die "Не удалось найти свободный IPv4 адрес"
CLIENT_HOST_OCTET="${CLIENT_IPV4##*.}"
if [[ "$USE_IPV6" == "1" ]]; then
CLIENT_IPV6="${IPV6_PREFIX}${CLIENT_HOST_OCTET}"
fi
echo "Создаю клиента: ${CLIENT_NAME}"
echo "IPv4 клиента: ${CLIENT_IPV4}"
if [[ "$USE_IPV6" == "1" ]]; then
echo "IPv6 клиента: ${CLIENT_IPV6}"
fi
wg genkey | tee "$CLIENT_PRIVKEY_FILE" | wg pubkey > "$CLIENT_PUBKEY_FILE"
wg genpsk > "$CLIENT_PSK_FILE"
chmod 600 "$CLIENT_PRIVKEY_FILE" "$CLIENT_PUBKEY_FILE" "$CLIENT_PSK_FILE"
CLIENT_PRIVATE_KEY="$(cat "$CLIENT_PRIVKEY_FILE")"
CLIENT_PUBLIC_KEY="$(cat "$CLIENT_PUBKEY_FILE")"
CLIENT_PRESHARED_KEY="$(cat "$CLIENT_PSK_FILE")"
if command -v wg >/dev/null 2>&1 && wg show "$WG_INTERFACE" >/dev/null 2>&1; then
SERVER_PUBLIC_KEY="$(wg show "$WG_INTERFACE" public-key)"
else
SERVER_PUBLIC_KEY="$(wg pubkey < <(grep '^PrivateKey *= *' "$WG_CONF" | head -n1 | awk -F'=' '{gsub(/ /,"",$2); print $2}'))"
fi
TMP_PEER_FILE="$(mktemp)"
trap 'rm -f "$TMP_PEER_FILE"' EXIT
{
echo
echo "### Client ${CLIENT_NAME}"
echo "[Peer]"
echo "PublicKey = ${CLIENT_PUBLIC_KEY}"
echo "PresharedKey = ${CLIENT_PRESHARED_KEY}"
if [[ "$USE_IPV6" == "1" ]]; then
echo "AllowedIPs = ${CLIENT_IPV4}/32,${CLIENT_IPV6}/128"
else
echo "AllowedIPs = ${CLIENT_IPV4}/32"
fi
} > "$TMP_PEER_FILE"
cat "$TMP_PEER_FILE" >> "$WG_CONF"
if [[ "$USE_IPV6" == "1" ]]; then
CLIENT_ADDRESS="${CLIENT_IPV4}/32,${CLIENT_IPV6}/128"
CLIENT_ALLOWED_IPS="0.0.0.0/0,::/0"
else
CLIENT_ADDRESS="${CLIENT_IPV4}/32"
CLIENT_ALLOWED_IPS="0.0.0.0/0"
fi
cat > "$CLIENT_CONF" <<EOF
[Interface]
PrivateKey = ${CLIENT_PRIVATE_KEY}
Address = ${CLIENT_ADDRESS}
DNS = ${SERVER_VPN_IP},192.168.0.200
[Peer]
PublicKey = ${SERVER_PUBLIC_KEY}
PresharedKey = ${CLIENT_PRESHARED_KEY}
Endpoint = ${ENDPOINT}:${SERVER_PORT}
AllowedIPs = ${CLIENT_ALLOWED_IPS}
PersistentKeepalive = 25
EOF
chmod 600 "$CLIENT_CONF"
echo "Применяю конфиг WireGuard..."
wg syncconf "$WG_INTERFACE" <(wg-quick strip "$WG_INTERFACE")
echo
echo "Готово."
echo "Клиент: ${CLIENT_NAME}"
echo "IP: ${CLIENT_IPV4}"
echo "Конфиг: ${CLIENT_CONF}"
echo
if command -v qrencode >/dev/null 2>&1; then
echo "QR-код для клиента:"
qrencode -t ansiutf8 < "$CLIENT_CONF"
echo
else
echo "Пакет qrencode не установлен, QR-код не напечатан."
echo "Установить можно так:"
echo " sudo apt update && sudo apt install -y qrencode"
echo
fi
echo "Проверка:"
echo " sudo wg show ${WG_INTERFACE}"
echo
echo "Передать клиенту файл:"
echo " ${CLIENT_CONF}"