feat: автоматизация установки и настройки WireGuard сервера и клиента
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
|
*.swp
|
||||||
|
.env
|
||||||
|
.DS_Store
|
||||||
271
README.md
Normal file
271
README.md
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
# WireGuard Server + Client Automation
|
||||||
|
|
||||||
|
Проект автоматизирует установку и настройку WireGuard-сервера и WireGuard-клиента на Debian/Ubuntu.
|
||||||
|
|
||||||
|
Основная идея: один репозиторий с понятными Bash-скриптами, которые можно повторно запускать без бессмысленной поломки уже существующей конфигурации.
|
||||||
|
|
||||||
|
## Назначение проекта
|
||||||
|
|
||||||
|
- Быстро развернуть WireGuard-сервер (`wg-quick@wg0`) с автозапуском через `systemd`.
|
||||||
|
- Включить IP forwarding и NAT для выхода клиентов в интернет через сервер.
|
||||||
|
- Установить легкий GUI для управления (`wireguard-ui` в Docker).
|
||||||
|
- Автоматизировать добавление клиента с клиентской машины через SSH на сервер.
|
||||||
|
- Поддержать 2 режима маршрутизации клиента:
|
||||||
|
- полный туннель (весь трафик через VPN)
|
||||||
|
- выборочный туннель (только заданные сети)
|
||||||
|
|
||||||
|
## Поддерживаемые ОС
|
||||||
|
|
||||||
|
- Debian 11/12
|
||||||
|
- Ubuntu 22.04/24.04
|
||||||
|
|
||||||
|
Скрипты ориентированы на Debian-family (APT, systemd).
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
- `README.md`
|
||||||
|
- `.gitignore`
|
||||||
|
- `lib/common.sh` — общие функции
|
||||||
|
- `templates/wg0.conf.template` — шаблон базового `wg0.conf`
|
||||||
|
- `server/install_server.sh` — установка сервера + GUI
|
||||||
|
- `server/wg-peerctl.sh` — helper для регистрации peer на сервере
|
||||||
|
- `client/install_client.sh` — установка и автонастройка клиента
|
||||||
|
|
||||||
|
## Архитектура решения
|
||||||
|
|
||||||
|
- Сервер разворачивается нативно на `wg-quick` + `systemd` (стабильность после reboot).
|
||||||
|
- GUI (`wireguard-ui`) запускается в Docker, но работает с тем же `/etc/wireguard`, где лежит серверный конфиг.
|
||||||
|
- Клиентский скрипт:
|
||||||
|
1. генерирует ключи локально,
|
||||||
|
2. подключается к серверу по SSH,
|
||||||
|
3. вызывает `/usr/local/sbin/wg-peerctl add ...`,
|
||||||
|
4. получает параметры подключения,
|
||||||
|
5. пишет локальный `/etc/wireguard/wg0.conf`,
|
||||||
|
6. запускает и включает `wg-quick@wg0`.
|
||||||
|
|
||||||
|
## Почему выбран GUI `wireguard-ui`
|
||||||
|
|
||||||
|
- Легкий для VPS (один контейнер).
|
||||||
|
- Понятный веб-интерфейс.
|
||||||
|
- Не требует переносить основной WireGuard в Docker: VPN остается в нативном `systemd`.
|
||||||
|
- Проще обслуживание: серверная сеть и NAT остаются под полным контролем Bash-скрипта.
|
||||||
|
|
||||||
|
## Какие пакеты устанавливаются
|
||||||
|
|
||||||
|
### Сервер
|
||||||
|
|
||||||
|
- `wireguard`, `wireguard-tools`
|
||||||
|
- `iproute2`, `iptables`
|
||||||
|
- `curl`, `ca-certificates`, `openssl`, `qrencode`
|
||||||
|
- `docker.io`, `docker-compose-plugin`
|
||||||
|
|
||||||
|
### Клиент
|
||||||
|
|
||||||
|
- `wireguard`, `wireguard-tools`
|
||||||
|
- `iproute2`
|
||||||
|
- `openssh-client`, `sshpass`
|
||||||
|
- `ca-certificates`
|
||||||
|
|
||||||
|
## Установка сервера
|
||||||
|
|
||||||
|
1. Клонировать/открыть репозиторий.
|
||||||
|
2. Запустить:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo bash server/install_server.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт интерактивно спросит недостающие данные:
|
||||||
|
- публичный IP (если не определился автоматически)
|
||||||
|
- порт WireGuard
|
||||||
|
- параметры GUI (логин/пароль)
|
||||||
|
|
||||||
|
### Non-interactive пример
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo bash server/install_server.sh \
|
||||||
|
--non-interactive \
|
||||||
|
--wg-interface wg0 \
|
||||||
|
--wg-port 51820 \
|
||||||
|
--wg-network 10.66.66.0/24 \
|
||||||
|
--wg-address 10.66.66.1/24 \
|
||||||
|
--server-public-ip 203.0.113.10 \
|
||||||
|
--default-iface eth0 \
|
||||||
|
--gui-enable yes \
|
||||||
|
--gui-host vpn.example.com \
|
||||||
|
--gui-port 5000 \
|
||||||
|
--gui-user admin \
|
||||||
|
--gui-password 'StrongPass123!'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Установка клиента
|
||||||
|
|
||||||
|
Запускать на клиентской машине:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo bash client/install_client.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт попросит:
|
||||||
|
- адрес сервера
|
||||||
|
- SSH-учетные данные
|
||||||
|
- режим маршрутизации (full/split)
|
||||||
|
- список сетей (если `split`)
|
||||||
|
|
||||||
|
### Non-interactive пример (SSH-ключ)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo bash client/install_client.sh \
|
||||||
|
--non-interactive \
|
||||||
|
--server-host 203.0.113.10 \
|
||||||
|
--server-user root \
|
||||||
|
--ssh-auth key \
|
||||||
|
--mode full \
|
||||||
|
--client-name laptop-01
|
||||||
|
```
|
||||||
|
|
||||||
|
### Non-interactive пример (SSH-пароль)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo bash client/install_client.sh \
|
||||||
|
--non-interactive \
|
||||||
|
--server-host 203.0.113.10 \
|
||||||
|
--server-user root \
|
||||||
|
--ssh-auth password \
|
||||||
|
--ssh-password 'your_password' \
|
||||||
|
--mode split \
|
||||||
|
--allowed-ips 10.0.0.0/8,192.168.0.0/16 \
|
||||||
|
--client-name laptop-02
|
||||||
|
```
|
||||||
|
|
||||||
|
## Как создаются ключи
|
||||||
|
|
||||||
|
- Сервер: `/etc/wireguard/server_private.key` и `/etc/wireguard/server_public.key`.
|
||||||
|
- Клиент: `/etc/wireguard/wg0_client_private.key`, `..._public.key`, `..._psk.key`.
|
||||||
|
- Повторный запуск не перегенерирует ключи без необходимости.
|
||||||
|
|
||||||
|
## Как задаются маршруты
|
||||||
|
|
||||||
|
Клиентский скрипт поддерживает режимы:
|
||||||
|
|
||||||
|
1. `full` — `AllowedIPs = 0.0.0.0/0,::/0`
|
||||||
|
2. `split` — `AllowedIPs` задается списком сетей
|
||||||
|
|
||||||
|
Для `full` добавляется route-exception до IP WireGuard-сервера, чтобы не потерять SSH/доступ при переключении default route.
|
||||||
|
|
||||||
|
## Как включается автозапуск
|
||||||
|
|
||||||
|
Используется `systemd`:
|
||||||
|
|
||||||
|
- Сервер: `wg-quick@wg0.service`
|
||||||
|
- Клиент: `wg-quick@wg0.service`
|
||||||
|
|
||||||
|
Скрипты выполняют `systemctl enable --now ...`.
|
||||||
|
|
||||||
|
## Как открыть GUI
|
||||||
|
|
||||||
|
После установки сервера GUI доступен по адресу:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://<GUI_HOST>:<GUI_PORT>
|
||||||
|
```
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://203.0.113.10:5000
|
||||||
|
```
|
||||||
|
|
||||||
|
Логин/пароль задаются во время установки сервера.
|
||||||
|
|
||||||
|
## Взаимодействие клиента с сервером
|
||||||
|
|
||||||
|
- Клиент генерирует локальные ключи.
|
||||||
|
- Клиент по SSH вызывает на сервере `wg-peerctl add`.
|
||||||
|
- Серверный helper:
|
||||||
|
- выделяет IP клиенту внутри VPN-сети,
|
||||||
|
- добавляет peer идемпотентно,
|
||||||
|
- применяет конфиг,
|
||||||
|
- возвращает параметры подключения.
|
||||||
|
- Клиент собирает локальный `wg0.conf`, запускает интерфейс и автозапуск.
|
||||||
|
|
||||||
|
## Примеры использования
|
||||||
|
|
||||||
|
### Проверка статуса на сервере
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl status wg-quick@wg0
|
||||||
|
sudo wg show
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ручное добавление peer через helper
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo /usr/local/sbin/wg-peerctl add \
|
||||||
|
--client-name test-client \
|
||||||
|
--client-public-key '<client_pub_key>'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проверка клиента
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo wg show
|
||||||
|
ip route
|
||||||
|
curl -4 ifconfig.me
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность и ограничения
|
||||||
|
|
||||||
|
- Приватные ключи не пишутся в лог.
|
||||||
|
- Конфиги и ключи сохраняются с ограниченными правами (`600`).
|
||||||
|
- Перед перезаписью конфигов делаются backup-файлы.
|
||||||
|
- SSH по паролю поддержан, но менее безопасен, чем SSH-ключи.
|
||||||
|
- В non-interactive режиме пароль в командной строке может попасть в shell history — лучше использовать SSH-ключ.
|
||||||
|
- Скрипты предполагают root-доступ на сервере для изменения `/etc/wireguard` и `systemd`.
|
||||||
|
- GUI запускается без TLS по умолчанию (HTTP). Для production рекомендуется ставить reverse proxy (Nginx/Caddy) с HTTPS и ограничением доступа.
|
||||||
|
|
||||||
|
## Диагностика и устранение проблем
|
||||||
|
|
||||||
|
1. WireGuard не поднялся:
|
||||||
|
```bash
|
||||||
|
sudo systemctl status wg-quick@wg0
|
||||||
|
sudo journalctl -u wg-quick@wg0 -n 100 --no-pager
|
||||||
|
sudo wg show
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Не работает интернет у клиента:
|
||||||
|
```bash
|
||||||
|
ip route
|
||||||
|
sudo wg show
|
||||||
|
```
|
||||||
|
Проверьте `AllowedIPs`, `ip_forward`, NAT и внешний интерфейс сервера.
|
||||||
|
|
||||||
|
3. Клиент не добавляется по SSH:
|
||||||
|
- Проверьте доступность SSH (`ssh user@server`).
|
||||||
|
- Проверьте, что на сервере установлен helper:
|
||||||
|
```bash
|
||||||
|
ls -l /usr/local/sbin/wg-peerctl
|
||||||
|
```
|
||||||
|
|
||||||
|
4. GUI недоступен:
|
||||||
|
```bash
|
||||||
|
sudo docker ps
|
||||||
|
sudo docker logs wireguard-ui --tail=100
|
||||||
|
sudo ss -tulpn | grep 5000
|
||||||
|
```
|
||||||
|
|
||||||
|
## Важные пути
|
||||||
|
|
||||||
|
- Серверный конфиг: `/etc/wireguard/wg0.conf`
|
||||||
|
- Метаданные сервера: `/etc/wireguard/wg-meta.env`
|
||||||
|
- Клиентский конфиг: `/etc/wireguard/wg0.conf`
|
||||||
|
- Server install log: `/var/log/wireguard-server-install.log`
|
||||||
|
- Client install log: `/var/log/wireguard-client-install.log`
|
||||||
|
- Peer helper log: `/var/log/wireguard-peerctl.log`
|
||||||
|
|
||||||
|
## Справка по ключам
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash server/install_server.sh --help
|
||||||
|
bash client/install_client.sh --help
|
||||||
|
```
|
||||||
350
client/install_client.sh
Executable file
350
client/install_client.sh
Executable file
@@ -0,0 +1,350 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||||
|
# shellcheck source=../lib/common.sh
|
||||||
|
source "${PROJECT_ROOT}/lib/common.sh"
|
||||||
|
|
||||||
|
LOG_FILE="/var/log/wireguard-client-install.log"
|
||||||
|
NON_INTERACTIVE=0
|
||||||
|
|
||||||
|
WG_INTERFACE="wg0"
|
||||||
|
CLIENT_NAME=""
|
||||||
|
CLIENT_DNS=""
|
||||||
|
TUNNEL_MODE=""
|
||||||
|
SPLIT_ALLOWED_IPS=""
|
||||||
|
|
||||||
|
SERVER_HOST=""
|
||||||
|
SERVER_USER="root"
|
||||||
|
SSH_PORT="22"
|
||||||
|
SSH_AUTH_METHOD="key"
|
||||||
|
SSH_PASSWORD=""
|
||||||
|
|
||||||
|
KEEPALIVE="25"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Установка WireGuard-клиента и автоматическая регистрация на сервере.
|
||||||
|
|
||||||
|
Использование:
|
||||||
|
install_client.sh [опции]
|
||||||
|
|
||||||
|
Опции:
|
||||||
|
--non-interactive Режим без вопросов
|
||||||
|
--interface <name> Интерфейс клиента (по умолчанию: wg0)
|
||||||
|
--client-name <name> Имя клиента (по умолчанию: hostname)
|
||||||
|
--mode <full|split> full: весь трафик, split: только выбранные сети
|
||||||
|
--allowed-ips <cidr,cidr> Для режима split
|
||||||
|
--dns <ip> DNS для клиента (если не задан, берется с сервера)
|
||||||
|
|
||||||
|
--server-host <host> IP/домен WireGuard-сервера
|
||||||
|
--server-user <user> SSH-пользователь (по умолчанию: root)
|
||||||
|
--ssh-port <port> SSH-порт (по умолчанию: 22)
|
||||||
|
--ssh-auth <key|password> Тип SSH-аутентификации (по умолчанию: key)
|
||||||
|
--ssh-password <password> Пароль SSH (используйте осторожно)
|
||||||
|
|
||||||
|
-h, --help Показать помощь
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
on_error() {
|
||||||
|
local code=$?
|
||||||
|
log_error "Клиентская настройка завершилась с ошибкой (код: ${code}). Лог: ${LOG_FILE}"
|
||||||
|
exit "$code"
|
||||||
|
}
|
||||||
|
trap on_error ERR
|
||||||
|
|
||||||
|
parse_args() {
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--non-interactive)
|
||||||
|
NON_INTERACTIVE=1; shift ;;
|
||||||
|
--interface)
|
||||||
|
WG_INTERFACE="$2"; shift 2 ;;
|
||||||
|
--client-name)
|
||||||
|
CLIENT_NAME="$2"; shift 2 ;;
|
||||||
|
--mode)
|
||||||
|
TUNNEL_MODE="$2"; shift 2 ;;
|
||||||
|
--allowed-ips)
|
||||||
|
SPLIT_ALLOWED_IPS="$2"; shift 2 ;;
|
||||||
|
--dns)
|
||||||
|
CLIENT_DNS="$2"; shift 2 ;;
|
||||||
|
--server-host)
|
||||||
|
SERVER_HOST="$2"; shift 2 ;;
|
||||||
|
--server-user)
|
||||||
|
SERVER_USER="$2"; shift 2 ;;
|
||||||
|
--ssh-port)
|
||||||
|
SSH_PORT="$2"; shift 2 ;;
|
||||||
|
--ssh-auth)
|
||||||
|
SSH_AUTH_METHOD="$2"; shift 2 ;;
|
||||||
|
--ssh-password)
|
||||||
|
SSH_PASSWORD="$2"; shift 2 ;;
|
||||||
|
-h|--help)
|
||||||
|
usage; exit 0 ;;
|
||||||
|
*)
|
||||||
|
die "Неизвестный аргумент: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_inputs() {
|
||||||
|
is_valid_port "$SSH_PORT" || die "Некорректный SSH-порт: $SSH_PORT"
|
||||||
|
[[ "$SSH_AUTH_METHOD" == "key" || "$SSH_AUTH_METHOD" == "password" ]] || die "--ssh-auth должен быть key или password"
|
||||||
|
|
||||||
|
if [[ -z "$SERVER_HOST" ]]; then
|
||||||
|
die "Не указан --server-host"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$TUNNEL_MODE" == "split" ]]; then
|
||||||
|
[[ -n "$SPLIT_ALLOWED_IPS" ]] || die "Для режима split укажите --allowed-ips"
|
||||||
|
is_valid_cidr_list "$SPLIT_ALLOWED_IPS" || die "Некорректный список --allowed-ips"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
collect_inputs() {
|
||||||
|
if [[ -z "$CLIENT_NAME" ]]; then
|
||||||
|
CLIENT_NAME="$(hostname -s)"
|
||||||
|
fi
|
||||||
|
CLIENT_NAME="$(sanitize_name "$CLIENT_NAME")"
|
||||||
|
[[ -n "$CLIENT_NAME" ]] || die "Некорректное имя клиента"
|
||||||
|
|
||||||
|
if [[ -z "$SERVER_HOST" && ! $NON_INTERACTIVE -eq 1 ]]; then
|
||||||
|
read -r -p "Введите IP/домен сервера: " SERVER_HOST
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$TUNNEL_MODE" ]]; then
|
||||||
|
if ((NON_INTERACTIVE)); then
|
||||||
|
TUNNEL_MODE="full"
|
||||||
|
else
|
||||||
|
echo "Выберите режим маршрутизации:"
|
||||||
|
echo " 1) весь трафик через VPN (full)"
|
||||||
|
echo " 2) только выбранные сети (split)"
|
||||||
|
read -r -p "Ваш выбор [1/2, по умолчанию 1]: " mode_choice
|
||||||
|
case "${mode_choice:-1}" in
|
||||||
|
1) TUNNEL_MODE="full" ;;
|
||||||
|
2) TUNNEL_MODE="split" ;;
|
||||||
|
*) die "Некорректный выбор" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$TUNNEL_MODE" == "split" && -z "$SPLIT_ALLOWED_IPS" ]]; then
|
||||||
|
if ((NON_INTERACTIVE)); then
|
||||||
|
die "В non-interactive для split укажите --allowed-ips"
|
||||||
|
fi
|
||||||
|
read -r -p "Введите CIDR-сети через запятую (например 10.0.0.0/8,192.168.0.0/16): " SPLIT_ALLOWED_IPS
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$SSH_AUTH_METHOD" == "password" && -z "$SSH_PASSWORD" ]]; then
|
||||||
|
if ((NON_INTERACTIVE)); then
|
||||||
|
die "Для --ssh-auth password нужно указать --ssh-password"
|
||||||
|
fi
|
||||||
|
ask_secret "Введите SSH-пароль для ${SERVER_USER}@${SERVER_HOST}" SSH_PASSWORD
|
||||||
|
fi
|
||||||
|
|
||||||
|
validate_inputs
|
||||||
|
}
|
||||||
|
|
||||||
|
install_packages() {
|
||||||
|
apt_install_if_missing wireguard wireguard-tools iproute2 openssh-client sshpass ca-certificates
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh_base_cmd() {
|
||||||
|
echo "ssh -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10 -p ${SSH_PORT} ${SERVER_USER}@${SERVER_HOST}"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_ssh() {
|
||||||
|
local remote_cmd="$1"
|
||||||
|
local base
|
||||||
|
base="$(ssh_base_cmd)"
|
||||||
|
|
||||||
|
if [[ "$SSH_AUTH_METHOD" == "password" ]]; then
|
||||||
|
require_cmd sshpass
|
||||||
|
SSHPASS="$SSH_PASSWORD" sshpass -e bash -c "$base \"$remote_cmd\""
|
||||||
|
else
|
||||||
|
bash -c "$base \"$remote_cmd\""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_server_ip() {
|
||||||
|
getent ahostsv4 "$SERVER_HOST" | awk '{print $1; exit}'
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_keys() {
|
||||||
|
mkdir -p /etc/wireguard
|
||||||
|
chmod 700 /etc/wireguard
|
||||||
|
|
||||||
|
CLIENT_PRIV_KEY_PATH="/etc/wireguard/${WG_INTERFACE}_client_private.key"
|
||||||
|
CLIENT_PUB_KEY_PATH="/etc/wireguard/${WG_INTERFACE}_client_public.key"
|
||||||
|
CLIENT_PSK_PATH="/etc/wireguard/${WG_INTERFACE}_client_psk.key"
|
||||||
|
|
||||||
|
if [[ ! -f "$CLIENT_PRIV_KEY_PATH" ]]; then
|
||||||
|
umask 077
|
||||||
|
wg genkey | tee "$CLIENT_PRIV_KEY_PATH" | wg pubkey > "$CLIENT_PUB_KEY_PATH"
|
||||||
|
wg genpsk > "$CLIENT_PSK_PATH"
|
||||||
|
log_success "Сгенерированы клиентские ключи"
|
||||||
|
else
|
||||||
|
if [[ ! -f "$CLIENT_PUB_KEY_PATH" ]]; then
|
||||||
|
wg pubkey < "$CLIENT_PRIV_KEY_PATH" > "$CLIENT_PUB_KEY_PATH"
|
||||||
|
fi
|
||||||
|
if [[ ! -f "$CLIENT_PSK_PATH" ]]; then
|
||||||
|
wg genpsk > "$CLIENT_PSK_PATH"
|
||||||
|
fi
|
||||||
|
log_info "Ключи клиента уже существуют, переиспользую"
|
||||||
|
fi
|
||||||
|
|
||||||
|
safe_chmod_600 "$CLIENT_PRIV_KEY_PATH"
|
||||||
|
safe_chmod_600 "$CLIENT_PUB_KEY_PATH"
|
||||||
|
safe_chmod_600 "$CLIENT_PSK_PATH"
|
||||||
|
}
|
||||||
|
|
||||||
|
register_peer_on_server() {
|
||||||
|
local pub psk remote_cmd response
|
||||||
|
pub="$(cat "$CLIENT_PUB_KEY_PATH")"
|
||||||
|
psk="$(cat "$CLIENT_PSK_PATH")"
|
||||||
|
|
||||||
|
remote_cmd="/usr/local/sbin/wg-peerctl add --client-name '${CLIENT_NAME}' --client-public-key '${pub}' --client-preshared-key '${psk}' --persistent-keepalive '${KEEPALIVE}'"
|
||||||
|
response="$(run_ssh "$remote_cmd")"
|
||||||
|
|
||||||
|
SERVER_RESPONSE_RAW="$response"
|
||||||
|
|
||||||
|
SERVER_STATUS="$(echo "$response" | awk -F= '/^STATUS=/{print $2; exit}')"
|
||||||
|
CLIENT_ADDRESS="$(echo "$response" | awk -F= '/^CLIENT_ADDRESS=/{print $2; exit}')"
|
||||||
|
SERVER_PUBLIC_KEY="$(echo "$response" | awk -F= '/^SERVER_PUBLIC_KEY=/{print $2; exit}')"
|
||||||
|
SERVER_ENDPOINT="$(echo "$response" | awk -F= '/^SERVER_ENDPOINT=/{print $2; exit}')"
|
||||||
|
SERVER_DNS_REMOTE="$(echo "$response" | awk -F= '/^SERVER_DNS=/{print $2; exit}')"
|
||||||
|
|
||||||
|
[[ -n "$SERVER_PUBLIC_KEY" ]] || die "Сервер не вернул SERVER_PUBLIC_KEY"
|
||||||
|
[[ -n "$CLIENT_ADDRESS" ]] || die "Сервер не вернул CLIENT_ADDRESS"
|
||||||
|
[[ -n "$SERVER_ENDPOINT" ]] || die "Сервер не вернул SERVER_ENDPOINT"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_allowed_ips() {
|
||||||
|
if [[ "$TUNNEL_MODE" == "full" ]]; then
|
||||||
|
ALLOWED_IPS="0.0.0.0/0,::/0"
|
||||||
|
else
|
||||||
|
ALLOWED_IPS="$SPLIT_ALLOWED_IPS"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
build_route_hooks_if_needed() {
|
||||||
|
PRE_UP=""
|
||||||
|
POST_DOWN=""
|
||||||
|
|
||||||
|
if [[ "$TUNNEL_MODE" != "full" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local server_ip gw iface
|
||||||
|
server_ip="$(resolve_server_ip || true)"
|
||||||
|
gw="$(detect_default_gateway || true)"
|
||||||
|
iface="$(detect_default_iface || true)"
|
||||||
|
|
||||||
|
if [[ -n "$server_ip" && -n "$gw" && -n "$iface" ]]; then
|
||||||
|
PRE_UP="PreUp = ip route add ${server_ip}/32 via ${gw} dev ${iface} 2>/dev/null || true"
|
||||||
|
POST_DOWN="PostDown = ip route del ${server_ip}/32 via ${gw} dev ${iface} 2>/dev/null || true"
|
||||||
|
else
|
||||||
|
log_warn "Не удалось вычислить route-exception до сервера. Использую стандартное поведение wg-quick."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
write_client_config() {
|
||||||
|
local conf="/etc/wireguard/${WG_INTERFACE}.conf"
|
||||||
|
local dns
|
||||||
|
|
||||||
|
dns="$CLIENT_DNS"
|
||||||
|
if [[ -z "$dns" ]]; then
|
||||||
|
dns="${SERVER_DNS_REMOTE:-1.1.1.1}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$conf" ]]; then
|
||||||
|
backup_file "$conf"
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "[Interface]"
|
||||||
|
echo "PrivateKey = $(cat "$CLIENT_PRIV_KEY_PATH")"
|
||||||
|
echo "Address = ${CLIENT_ADDRESS}"
|
||||||
|
echo "DNS = ${dns}"
|
||||||
|
if [[ -n "$PRE_UP" ]]; then
|
||||||
|
echo "$PRE_UP"
|
||||||
|
fi
|
||||||
|
if [[ -n "$POST_DOWN" ]]; then
|
||||||
|
echo "$POST_DOWN"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
echo "[Peer]"
|
||||||
|
echo "PublicKey = ${SERVER_PUBLIC_KEY}"
|
||||||
|
echo "PresharedKey = $(cat "$CLIENT_PSK_PATH")"
|
||||||
|
echo "Endpoint = ${SERVER_ENDPOINT}"
|
||||||
|
echo "AllowedIPs = ${ALLOWED_IPS}"
|
||||||
|
echo "PersistentKeepalive = ${KEEPALIVE}"
|
||||||
|
} > "$conf"
|
||||||
|
|
||||||
|
safe_chmod_600 "$conf"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_client_config() {
|
||||||
|
local unit="wg-quick@${WG_INTERFACE}.service"
|
||||||
|
if systemctl list-unit-files | grep -q "^${unit}"; then
|
||||||
|
systemctl restart "$unit" || true
|
||||||
|
fi
|
||||||
|
systemd_enable_now "$unit"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_routes_info() {
|
||||||
|
log_info "Текущая таблица маршрутов клиента (до применения уже была прочитана системой):"
|
||||||
|
ip route show | sed 's/^/ /'
|
||||||
|
}
|
||||||
|
|
||||||
|
print_summary() {
|
||||||
|
local st
|
||||||
|
st="$(systemctl is-active "wg-quick@${WG_INTERFACE}" 2>/dev/null || true)"
|
||||||
|
|
||||||
|
cat <<EOF_SUMMARY
|
||||||
|
|
||||||
|
================ ИТОГОВАЯ СВОДКА ================
|
||||||
|
Клиент: ${CLIENT_NAME}
|
||||||
|
Интерфейс: ${WG_INTERFACE}
|
||||||
|
Сервис: ${st}
|
||||||
|
Конфиг клиента: /etc/wireguard/${WG_INTERFACE}.conf
|
||||||
|
Endpoint сервера: ${SERVER_ENDPOINT}
|
||||||
|
Маршруты (AllowedIPs):${ALLOWED_IPS}
|
||||||
|
Режим туннеля: ${TUNNEL_MODE}
|
||||||
|
SSH сервер: ${SERVER_USER}@${SERVER_HOST}:${SSH_PORT}
|
||||||
|
Статус регистрации: ${SERVER_STATUS}
|
||||||
|
Лог: ${LOG_FILE}
|
||||||
|
=================================================
|
||||||
|
EOF_SUMMARY
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
parse_args "$@"
|
||||||
|
|
||||||
|
require_root
|
||||||
|
check_os_supported
|
||||||
|
require_cmd awk
|
||||||
|
require_cmd sed
|
||||||
|
require_cmd ip
|
||||||
|
require_cmd ssh
|
||||||
|
|
||||||
|
collect_inputs
|
||||||
|
|
||||||
|
print_routes_info
|
||||||
|
install_packages
|
||||||
|
generate_keys
|
||||||
|
register_peer_on_server
|
||||||
|
build_allowed_ips
|
||||||
|
build_route_hooks_if_needed
|
||||||
|
write_client_config
|
||||||
|
apply_client_config
|
||||||
|
print_summary
|
||||||
|
|
||||||
|
# Не держим пароль в переменной дольше необходимого.
|
||||||
|
unset SSH_PASSWORD
|
||||||
|
log_success "Настройка клиента завершена"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
231
lib/common.sh
Executable file
231
lib/common.sh
Executable file
@@ -0,0 +1,231 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Общие функции для скриптов проекта WireGuard.
|
||||||
|
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
C_RESET='\033[0m'
|
||||||
|
C_RED='\033[0;31m'
|
||||||
|
C_GREEN='\033[0;32m'
|
||||||
|
C_YELLOW='\033[1;33m'
|
||||||
|
C_BLUE='\033[0;34m'
|
||||||
|
else
|
||||||
|
C_RESET=''
|
||||||
|
C_RED=''
|
||||||
|
C_GREEN=''
|
||||||
|
C_YELLOW=''
|
||||||
|
C_BLUE=''
|
||||||
|
fi
|
||||||
|
|
||||||
|
LOG_FILE="${LOG_FILE:-}"
|
||||||
|
|
||||||
|
timestamp() {
|
||||||
|
date '+%Y-%m-%d %H:%M:%S'
|
||||||
|
}
|
||||||
|
|
||||||
|
_write_log() {
|
||||||
|
local level="$1"
|
||||||
|
local msg="$2"
|
||||||
|
local line="[$(timestamp)] [$level] $msg"
|
||||||
|
if [[ -n "${LOG_FILE}" ]]; then
|
||||||
|
mkdir -p "$(dirname "$LOG_FILE")"
|
||||||
|
printf '%s\n' "$line" >> "$LOG_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
local msg="$*"
|
||||||
|
printf '%b[INFO]%b %s\n' "$C_BLUE" "$C_RESET" "$msg"
|
||||||
|
_write_log "INFO" "$msg"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
local msg="$*"
|
||||||
|
printf '%b[WARN]%b %s\n' "$C_YELLOW" "$C_RESET" "$msg"
|
||||||
|
_write_log "WARN" "$msg"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
local msg="$*"
|
||||||
|
printf '%b[ERR ]%b %s\n' "$C_RED" "$C_RESET" "$msg" >&2
|
||||||
|
_write_log "ERROR" "$msg"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
local msg="$*"
|
||||||
|
printf '%b[ OK ]%b %s\n' "$C_GREEN" "$C_RESET" "$msg"
|
||||||
|
_write_log "SUCCESS" "$msg"
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
log_error "$*"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
require_root() {
|
||||||
|
if [[ "${EUID}" -ne 0 ]]; then
|
||||||
|
die "Скрипт должен выполняться от root."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
require_cmd() {
|
||||||
|
local cmd="$1"
|
||||||
|
command -v "$cmd" >/dev/null 2>&1 || die "Не найдена команда: $cmd"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_os_supported() {
|
||||||
|
if [[ ! -r /etc/os-release ]]; then
|
||||||
|
die "Не найден файл /etc/os-release, определить ОС не удалось."
|
||||||
|
fi
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source /etc/os-release
|
||||||
|
local id="${ID:-}"
|
||||||
|
local like="${ID_LIKE:-}"
|
||||||
|
if [[ "$id" != "debian" && "$id" != "ubuntu" && "$like" != *"debian"* ]]; then
|
||||||
|
die "Поддерживаются только Debian/Ubuntu. Обнаружено: ${PRETTY_NAME:-unknown}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
apt_install_if_missing() {
|
||||||
|
local pkgs=()
|
||||||
|
local pkg
|
||||||
|
for pkg in "$@"; do
|
||||||
|
if ! dpkg -s "$pkg" >/dev/null 2>&1; then
|
||||||
|
pkgs+=("$pkg")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if ((${#pkgs[@]} == 0)); then
|
||||||
|
log_info "Все необходимые пакеты уже установлены."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Устанавливаю пакеты: ${pkgs[*]}"
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt-get update -y
|
||||||
|
apt-get install -y "${pkgs[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
backup_file() {
|
||||||
|
local file="$1"
|
||||||
|
if [[ -f "$file" ]]; then
|
||||||
|
local backup="${file}.bak.$(date +%Y%m%d_%H%M%S)"
|
||||||
|
cp -a "$file" "$backup"
|
||||||
|
log_info "Создана резервная копия: $backup"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_line_in_file() {
|
||||||
|
local line="$1"
|
||||||
|
local file="$2"
|
||||||
|
touch "$file"
|
||||||
|
if ! grep -Fxq "$line" "$file"; then
|
||||||
|
printf '%s\n' "$line" >> "$file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
set_kv_in_file() {
|
||||||
|
local key="$1"
|
||||||
|
local value="$2"
|
||||||
|
local file="$3"
|
||||||
|
touch "$file"
|
||||||
|
if grep -Eq "^${key}=" "$file"; then
|
||||||
|
sed -i "s|^${key}=.*|${key}=${value}|" "$file"
|
||||||
|
else
|
||||||
|
printf '%s=%s\n' "$key" "$value" >> "$file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
is_valid_port() {
|
||||||
|
local p="$1"
|
||||||
|
[[ "$p" =~ ^[0-9]+$ ]] || return 1
|
||||||
|
((p >= 1 && p <= 65535))
|
||||||
|
}
|
||||||
|
|
||||||
|
is_valid_cidr_list() {
|
||||||
|
local input="$1"
|
||||||
|
local cidr
|
||||||
|
IFS=',' read -r -a _cidrs <<< "$input"
|
||||||
|
for cidr in "${_cidrs[@]}"; do
|
||||||
|
cidr="$(echo "$cidr" | xargs)"
|
||||||
|
[[ -n "$cidr" ]] || return 1
|
||||||
|
if ! [[ "$cidr" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[12][0-9]|3[0-2])$|^::/0$ ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
ask_with_default() {
|
||||||
|
local prompt="$1"
|
||||||
|
local default="$2"
|
||||||
|
local var_name="$3"
|
||||||
|
local answer
|
||||||
|
|
||||||
|
read -r -p "$prompt [$default]: " answer
|
||||||
|
answer="${answer:-$default}"
|
||||||
|
printf -v "$var_name" '%s' "$answer"
|
||||||
|
}
|
||||||
|
|
||||||
|
ask_secret() {
|
||||||
|
local prompt="$1"
|
||||||
|
local var_name="$2"
|
||||||
|
local answer
|
||||||
|
read -r -s -p "$prompt: " answer
|
||||||
|
echo
|
||||||
|
printf -v "$var_name" '%s' "$answer"
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm() {
|
||||||
|
local prompt="$1"
|
||||||
|
local ans
|
||||||
|
read -r -p "$prompt [y/N]: " ans
|
||||||
|
[[ "$ans" =~ ^([yY][eE][sS]|[yY])$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_public_ip() {
|
||||||
|
local ip
|
||||||
|
for url in "https://api.ipify.org" "https://ifconfig.me/ip" "https://icanhazip.com"; do
|
||||||
|
if ip="$(curl -4fsS --max-time 5 "$url" 2>/dev/null | tr -d '[:space:]')"; then
|
||||||
|
if [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
||||||
|
echo "$ip"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_default_iface() {
|
||||||
|
ip route show default 2>/dev/null | awk '{print $5; exit}'
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_default_gateway() {
|
||||||
|
ip route show default 2>/dev/null | awk '{print $3; exit}'
|
||||||
|
}
|
||||||
|
|
||||||
|
safe_chmod_600() {
|
||||||
|
local file="$1"
|
||||||
|
[[ -f "$file" ]] || return 0
|
||||||
|
chmod 600 "$file"
|
||||||
|
}
|
||||||
|
|
||||||
|
safe_chmod_700() {
|
||||||
|
local dir="$1"
|
||||||
|
[[ -d "$dir" ]] || return 0
|
||||||
|
chmod 700 "$dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
systemd_enable_now() {
|
||||||
|
local unit="$1"
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now "$unit"
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitize_name() {
|
||||||
|
local input="$1"
|
||||||
|
echo "$input" | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9_.-' | sed 's/^[-.]*//;s/[-.]*$//'
|
||||||
|
}
|
||||||
|
|
||||||
|
random_alnum() {
|
||||||
|
local len="${1:-20}"
|
||||||
|
tr -dc 'A-Za-z0-9' </dev/urandom | head -c "$len"
|
||||||
|
}
|
||||||
352
server/install_server.sh
Executable file
352
server/install_server.sh
Executable file
@@ -0,0 +1,352 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||||
|
# shellcheck source=../lib/common.sh
|
||||||
|
source "${PROJECT_ROOT}/lib/common.sh"
|
||||||
|
|
||||||
|
LOG_FILE="/var/log/wireguard-server-install.log"
|
||||||
|
NON_INTERACTIVE=0
|
||||||
|
|
||||||
|
WG_INTERFACE="wg0"
|
||||||
|
WG_PORT="51820"
|
||||||
|
WG_NETWORK="10.66.66.0/24"
|
||||||
|
WG_ADDRESS="10.66.66.1/24"
|
||||||
|
SERVER_PUBLIC_IP=""
|
||||||
|
SERVER_DNS="1.1.1.1"
|
||||||
|
DEFAULT_IFACE=""
|
||||||
|
|
||||||
|
GUI_ENABLE="yes"
|
||||||
|
GUI_HOST=""
|
||||||
|
GUI_PORT="5000"
|
||||||
|
GUI_USER="admin"
|
||||||
|
GUI_PASSWORD=""
|
||||||
|
GUI_SESSION_SECRET=""
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Установка WireGuard-сервера и GUI (Debian/Ubuntu).
|
||||||
|
|
||||||
|
Использование:
|
||||||
|
install_server.sh [опции]
|
||||||
|
|
||||||
|
Опции:
|
||||||
|
--non-interactive Режим без вопросов (используются аргументы/дефолты)
|
||||||
|
--wg-interface <name> Имя интерфейса WireGuard (по умолчанию: wg0)
|
||||||
|
--wg-port <port> Порт WireGuard (по умолчанию: 51820)
|
||||||
|
--wg-network <cidr> Подсеть VPN (по умолчанию: 10.66.66.0/24)
|
||||||
|
--wg-address <cidr> Адрес сервера в VPN (по умолчанию: 10.66.66.1/24)
|
||||||
|
--server-public-ip <ip> Публичный IP сервера
|
||||||
|
--server-dns <ip> DNS для клиентов (по умолчанию: 1.1.1.1)
|
||||||
|
--default-iface <iface> Внешний интерфейс для NAT
|
||||||
|
|
||||||
|
--gui-enable <yes|no> Включить GUI wireguard-ui (по умолчанию: yes)
|
||||||
|
--gui-host <host> Домен/IP для открытия GUI
|
||||||
|
--gui-port <port> Порт GUI (по умолчанию: 5000)
|
||||||
|
--gui-user <user> Логин GUI (по умолчанию: admin)
|
||||||
|
--gui-password <pass> Пароль GUI (если не указан, будет запрос)
|
||||||
|
|
||||||
|
-h, --help Показать помощь
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
on_error() {
|
||||||
|
local code=$?
|
||||||
|
log_error "Установка прервана с ошибкой (код: ${code}). Подробности в ${LOG_FILE}"
|
||||||
|
exit "$code"
|
||||||
|
}
|
||||||
|
trap on_error ERR
|
||||||
|
|
||||||
|
parse_args() {
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--non-interactive)
|
||||||
|
NON_INTERACTIVE=1; shift ;;
|
||||||
|
--wg-interface)
|
||||||
|
WG_INTERFACE="$2"; shift 2 ;;
|
||||||
|
--wg-port)
|
||||||
|
WG_PORT="$2"; shift 2 ;;
|
||||||
|
--wg-network)
|
||||||
|
WG_NETWORK="$2"; shift 2 ;;
|
||||||
|
--wg-address)
|
||||||
|
WG_ADDRESS="$2"; shift 2 ;;
|
||||||
|
--server-public-ip)
|
||||||
|
SERVER_PUBLIC_IP="$2"; shift 2 ;;
|
||||||
|
--server-dns)
|
||||||
|
SERVER_DNS="$2"; shift 2 ;;
|
||||||
|
--default-iface)
|
||||||
|
DEFAULT_IFACE="$2"; shift 2 ;;
|
||||||
|
--gui-enable)
|
||||||
|
GUI_ENABLE="$2"; shift 2 ;;
|
||||||
|
--gui-host)
|
||||||
|
GUI_HOST="$2"; shift 2 ;;
|
||||||
|
--gui-port)
|
||||||
|
GUI_PORT="$2"; shift 2 ;;
|
||||||
|
--gui-user)
|
||||||
|
GUI_USER="$2"; shift 2 ;;
|
||||||
|
--gui-password)
|
||||||
|
GUI_PASSWORD="$2"; shift 2 ;;
|
||||||
|
-h|--help)
|
||||||
|
usage; exit 0 ;;
|
||||||
|
*)
|
||||||
|
die "Неизвестный аргумент: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_inputs() {
|
||||||
|
is_valid_port "$WG_PORT" || die "Некорректный порт WireGuard: $WG_PORT"
|
||||||
|
is_valid_port "$GUI_PORT" || die "Некорректный порт GUI: $GUI_PORT"
|
||||||
|
|
||||||
|
if [[ ! "$WG_NETWORK" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[12][0-9]|3[0-2])$ ]]; then
|
||||||
|
die "Некорректная сеть WG: $WG_NETWORK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! "$WG_ADDRESS" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[12][0-9]|3[0-2])$ ]]; then
|
||||||
|
die "Некорректный адрес WG сервера: $WG_ADDRESS"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
collect_inputs() {
|
||||||
|
if [[ -z "$DEFAULT_IFACE" ]]; then
|
||||||
|
DEFAULT_IFACE="$(detect_default_iface || true)"
|
||||||
|
fi
|
||||||
|
if [[ -z "$DEFAULT_IFACE" ]]; then
|
||||||
|
if ((NON_INTERACTIVE)); then
|
||||||
|
die "Не удалось определить внешний интерфейс. Передайте --default-iface"
|
||||||
|
fi
|
||||||
|
read -r -p "Введите внешний интерфейс (например eth0): " DEFAULT_IFACE
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$SERVER_PUBLIC_IP" ]]; then
|
||||||
|
SERVER_PUBLIC_IP="$(detect_public_ip || true)"
|
||||||
|
fi
|
||||||
|
if [[ -z "$SERVER_PUBLIC_IP" ]]; then
|
||||||
|
if ((NON_INTERACTIVE)); then
|
||||||
|
die "Не удалось определить публичный IP. Передайте --server-public-ip"
|
||||||
|
fi
|
||||||
|
read -r -p "Введите публичный IP сервера: " SERVER_PUBLIC_IP
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$GUI_HOST" ]]; then
|
||||||
|
GUI_HOST="$SERVER_PUBLIC_IP"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$GUI_ENABLE" == "yes" ]]; then
|
||||||
|
if [[ -z "$GUI_PASSWORD" ]]; then
|
||||||
|
if ((NON_INTERACTIVE)); then
|
||||||
|
GUI_PASSWORD="$(random_alnum 16)"
|
||||||
|
log_warn "Пароль GUI не задан. Сгенерирован случайный пароль."
|
||||||
|
else
|
||||||
|
ask_secret "Введите пароль GUI (${GUI_USER})" GUI_PASSWORD
|
||||||
|
if [[ -z "$GUI_PASSWORD" ]]; then
|
||||||
|
GUI_PASSWORD="$(random_alnum 16)"
|
||||||
|
log_warn "Пустой пароль не допускается. Сгенерирован случайный пароль: $GUI_PASSWORD"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
GUI_SESSION_SECRET="$(random_alnum 32)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
validate_inputs
|
||||||
|
}
|
||||||
|
|
||||||
|
install_packages() {
|
||||||
|
apt_install_if_missing \
|
||||||
|
wireguard wireguard-tools iproute2 iptables curl ca-certificates openssl \
|
||||||
|
qrencode docker.io docker-compose-plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_sysctl() {
|
||||||
|
local f="/etc/sysctl.d/99-wireguard-forwarding.conf"
|
||||||
|
cat > "$f" <<EOF_SYSCTL
|
||||||
|
net.ipv4.ip_forward=1
|
||||||
|
net.ipv6.conf.all.forwarding=1
|
||||||
|
EOF_SYSCTL
|
||||||
|
sysctl --system >/dev/null
|
||||||
|
log_success "IP forwarding включен"
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_keys() {
|
||||||
|
mkdir -p /etc/wireguard
|
||||||
|
chmod 700 /etc/wireguard
|
||||||
|
|
||||||
|
local priv="/etc/wireguard/server_private.key"
|
||||||
|
local pub="/etc/wireguard/server_public.key"
|
||||||
|
|
||||||
|
if [[ ! -f "$priv" ]]; then
|
||||||
|
umask 077
|
||||||
|
wg genkey | tee "$priv" | wg pubkey > "$pub"
|
||||||
|
log_success "Сгенерированы ключи сервера"
|
||||||
|
else
|
||||||
|
if [[ ! -f "$pub" ]]; then
|
||||||
|
wg pubkey < "$priv" > "$pub"
|
||||||
|
fi
|
||||||
|
log_info "Ключи сервера уже существуют, переиспользую"
|
||||||
|
fi
|
||||||
|
|
||||||
|
safe_chmod_600 "$priv"
|
||||||
|
safe_chmod_600 "$pub"
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_wg_config() {
|
||||||
|
local conf="/etc/wireguard/${WG_INTERFACE}.conf"
|
||||||
|
if [[ -f "$conf" ]]; then
|
||||||
|
log_warn "Конфиг ${conf} уже существует. Автосоздание пропущено."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local server_priv
|
||||||
|
server_priv="$(cat /etc/wireguard/server_private.key)"
|
||||||
|
|
||||||
|
sed -e "s|__WG_ADDRESS__|${WG_ADDRESS}|g" \
|
||||||
|
-e "s|__WG_PORT__|${WG_PORT}|g" \
|
||||||
|
-e "s|__SERVER_PRIVATE_KEY__|${server_priv}|g" \
|
||||||
|
-e "s|__WG_INTERFACE__|${WG_INTERFACE}|g" \
|
||||||
|
-e "s|__WG_NETWORK__|${WG_NETWORK}|g" \
|
||||||
|
-e "s|__DEFAULT_IFACE__|${DEFAULT_IFACE}|g" \
|
||||||
|
"${PROJECT_ROOT}/templates/wg0.conf.template" > "$conf"
|
||||||
|
|
||||||
|
safe_chmod_600 "$conf"
|
||||||
|
log_success "Создан базовый конфиг: $conf"
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_meta() {
|
||||||
|
local meta="/etc/wireguard/wg-meta.env"
|
||||||
|
touch "$meta"
|
||||||
|
safe_chmod_600 "$meta"
|
||||||
|
|
||||||
|
set_kv_in_file "WG_INTERFACE" "$WG_INTERFACE" "$meta"
|
||||||
|
set_kv_in_file "WG_PORT" "$WG_PORT" "$meta"
|
||||||
|
set_kv_in_file "WG_NETWORK" "$WG_NETWORK" "$meta"
|
||||||
|
set_kv_in_file "WG_ADDRESS" "$WG_ADDRESS" "$meta"
|
||||||
|
set_kv_in_file "SERVER_PUBLIC_IP" "$SERVER_PUBLIC_IP" "$meta"
|
||||||
|
set_kv_in_file "SERVER_DNS" "$SERVER_DNS" "$meta"
|
||||||
|
set_kv_in_file "DEFAULT_IFACE" "$DEFAULT_IFACE" "$meta"
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_wg_service() {
|
||||||
|
systemd_enable_now "wg-quick@${WG_INTERFACE}.service"
|
||||||
|
log_success "WireGuard сервис запущен и включен в автозапуск"
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_ufw_if_active() {
|
||||||
|
if command -v ufw >/dev/null 2>&1; then
|
||||||
|
if ufw status 2>/dev/null | grep -q "Status: active"; then
|
||||||
|
ufw allow "${WG_PORT}/udp" || true
|
||||||
|
if [[ "$GUI_ENABLE" == "yes" ]]; then
|
||||||
|
ufw allow "${GUI_PORT}/tcp" || true
|
||||||
|
fi
|
||||||
|
log_info "UFW активен: правила для WireGuard/GUI добавлены"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_peer_helper() {
|
||||||
|
mkdir -p /usr/local/lib/wireguard-automation/{lib,server}
|
||||||
|
install -m 640 "${PROJECT_ROOT}/lib/common.sh" /usr/local/lib/wireguard-automation/lib/common.sh
|
||||||
|
install -m 750 "${PROJECT_ROOT}/server/wg-peerctl.sh" /usr/local/lib/wireguard-automation/server/wg-peerctl.sh
|
||||||
|
|
||||||
|
cat > /usr/local/sbin/wg-peerctl <<'EOF_HELPER'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
exec /usr/local/lib/wireguard-automation/server/wg-peerctl.sh "$@"
|
||||||
|
EOF_HELPER
|
||||||
|
chmod 750 /usr/local/sbin/wg-peerctl
|
||||||
|
|
||||||
|
log_success "Установлен helper: /usr/local/sbin/wg-peerctl"
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_gui() {
|
||||||
|
[[ "$GUI_ENABLE" == "yes" ]] || { log_warn "GUI отключен (GUI_ENABLE=no)"; return; }
|
||||||
|
|
||||||
|
mkdir -p /opt/wireguard-ui/{db,data}
|
||||||
|
safe_chmod_700 /opt/wireguard-ui
|
||||||
|
|
||||||
|
cat > /opt/wireguard-ui/docker-compose.yml <<EOF_COMPOSE
|
||||||
|
services:
|
||||||
|
wireguard-ui:
|
||||||
|
image: ngoduykhanh/wireguard-ui:latest
|
||||||
|
container_name: wireguard-ui
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${GUI_PORT}:5000"
|
||||||
|
environment:
|
||||||
|
- WGUI_USERNAME=${GUI_USER}
|
||||||
|
- WGUI_PASSWORD=${GUI_PASSWORD}
|
||||||
|
- SESSION_SECRET=${GUI_SESSION_SECRET}
|
||||||
|
- WGUI_MANAGE_START=true
|
||||||
|
- WGUI_MANAGE_RESTART=true
|
||||||
|
volumes:
|
||||||
|
- /etc/wireguard:/etc/wireguard
|
||||||
|
- /opt/wireguard-ui/db:/app/db
|
||||||
|
- /opt/wireguard-ui/data:/app/data
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
EOF_COMPOSE
|
||||||
|
|
||||||
|
systemd_enable_now docker.service
|
||||||
|
(cd /opt/wireguard-ui && docker compose up -d)
|
||||||
|
log_success "GUI wireguard-ui запущен"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_summary() {
|
||||||
|
local service_status gui_status
|
||||||
|
service_status="$(systemctl is-active "wg-quick@${WG_INTERFACE}" 2>/dev/null || true)"
|
||||||
|
gui_status="disabled"
|
||||||
|
|
||||||
|
if [[ "$GUI_ENABLE" == "yes" ]]; then
|
||||||
|
gui_status="$(docker ps --filter name=wireguard-ui --format '{{.Status}}' || true)"
|
||||||
|
[[ -n "$gui_status" ]] || gui_status="not running"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOF_SUMMARY
|
||||||
|
|
||||||
|
================ ИТОГОВАЯ СВОДКА ================
|
||||||
|
WireGuard сервис: ${service_status}
|
||||||
|
Интерфейс: ${WG_INTERFACE}
|
||||||
|
Порт WireGuard: ${WG_PORT}/udp
|
||||||
|
Конфиг сервера: /etc/wireguard/${WG_INTERFACE}.conf
|
||||||
|
Ключи сервера: /etc/wireguard/server_private.key, /etc/wireguard/server_public.key
|
||||||
|
Подсеть VPN: ${WG_NETWORK}
|
||||||
|
Маршрутизация NAT: через интерфейс ${DEFAULT_IFACE}
|
||||||
|
|
||||||
|
GUI: ${GUI_ENABLE}
|
||||||
|
GUI адрес: http://${GUI_HOST}:${GUI_PORT}
|
||||||
|
GUI логин: ${GUI_USER}
|
||||||
|
GUI статус: ${gui_status}
|
||||||
|
|
||||||
|
Helper для peer: /usr/local/sbin/wg-peerctl
|
||||||
|
Лог установки: ${LOG_FILE}
|
||||||
|
=================================================
|
||||||
|
EOF_SUMMARY
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
parse_args "$@"
|
||||||
|
|
||||||
|
require_root
|
||||||
|
check_os_supported
|
||||||
|
require_cmd ip
|
||||||
|
require_cmd awk
|
||||||
|
require_cmd sed
|
||||||
|
|
||||||
|
collect_inputs
|
||||||
|
|
||||||
|
log_info "Начинаю установку WireGuard-сервера"
|
||||||
|
install_packages
|
||||||
|
setup_sysctl
|
||||||
|
setup_keys
|
||||||
|
setup_wg_config
|
||||||
|
setup_meta
|
||||||
|
setup_wg_service
|
||||||
|
setup_ufw_if_active
|
||||||
|
install_peer_helper
|
||||||
|
setup_gui
|
||||||
|
print_summary
|
||||||
|
|
||||||
|
log_success "Установка завершена"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
200
server/wg-peerctl.sh
Executable file
200
server/wg-peerctl.sh
Executable file
@@ -0,0 +1,200 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
if [[ -f "${SCRIPT_DIR}/../lib/common.sh" ]]; then
|
||||||
|
# shellcheck source=../lib/common.sh
|
||||||
|
source "${SCRIPT_DIR}/../lib/common.sh"
|
||||||
|
elif [[ -f "/usr/local/lib/wireguard-automation/lib/common.sh" ]]; then
|
||||||
|
# shellcheck source=/usr/local/lib/wireguard-automation/lib/common.sh
|
||||||
|
source "/usr/local/lib/wireguard-automation/lib/common.sh"
|
||||||
|
else
|
||||||
|
echo "Не найден common.sh" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
LOG_FILE="/var/log/wireguard-peerctl.log"
|
||||||
|
WG_META_FILE="/etc/wireguard/wg-meta.env"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Использование:
|
||||||
|
wg-peerctl.sh add \
|
||||||
|
--client-name <name> \
|
||||||
|
--client-public-key <pubkey> \
|
||||||
|
[--client-address <10.66.66.X/32>] \
|
||||||
|
[--client-preshared-key <psk>] \
|
||||||
|
[--persistent-keepalive 25]
|
||||||
|
|
||||||
|
Описание:
|
||||||
|
Скрипт добавляет peer в конфигурацию WireGuard-сервера идемпотентно.
|
||||||
|
Если peer с таким public key уже существует, повторно не добавляет.
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
load_meta() {
|
||||||
|
[[ -f "$WG_META_FILE" ]] || die "Не найден $WG_META_FILE. Сначала выполните install_server.sh"
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$WG_META_FILE"
|
||||||
|
|
||||||
|
WG_INTERFACE="${WG_INTERFACE:-wg0}"
|
||||||
|
WG_NETWORK="${WG_NETWORK:-10.66.66.0/24}"
|
||||||
|
WG_PORT="${WG_PORT:-51820}"
|
||||||
|
SERVER_PUBLIC_IP="${SERVER_PUBLIC_IP:-}"
|
||||||
|
SERVER_DNS="${SERVER_DNS:-1.1.1.1}"
|
||||||
|
WG_CONF="/etc/wireguard/${WG_INTERFACE}.conf"
|
||||||
|
}
|
||||||
|
|
||||||
|
next_client_ip() {
|
||||||
|
local network="$1"
|
||||||
|
local base
|
||||||
|
base="${network%.*}"
|
||||||
|
|
||||||
|
local used
|
||||||
|
used="$(grep -E '^AllowedIPs\s*=\s*' "$WG_CONF" | awk -F'=' '{print $2}' | tr ',' '\n' | sed 's/ //g' | grep -E '^10\.[0-9]+\.[0-9]+\.[0-9]+/32$' | sed 's#/32##' || true)"
|
||||||
|
|
||||||
|
local i candidate
|
||||||
|
for i in $(seq 2 254); do
|
||||||
|
candidate="${base}.${i}"
|
||||||
|
if ! grep -qx "$candidate" <<< "$used"; then
|
||||||
|
echo "${candidate}/32"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
peer_exists_by_pubkey() {
|
||||||
|
local pubkey="$1"
|
||||||
|
grep -Fq "PublicKey = $pubkey" "$WG_CONF"
|
||||||
|
}
|
||||||
|
|
||||||
|
extract_peer_address_by_pubkey() {
|
||||||
|
local pubkey="$1"
|
||||||
|
awk -v pk="$pubkey" '
|
||||||
|
$0 ~ /^\[Peer\]/ {in_peer=1; key=""; addr=""}
|
||||||
|
in_peer && $0 ~ /^PublicKey[[:space:]]*=/ {
|
||||||
|
sub(/^[^=]*=[[:space:]]*/, "", $0); key=$0
|
||||||
|
}
|
||||||
|
in_peer && $0 ~ /^AllowedIPs[[:space:]]*=/ {
|
||||||
|
sub(/^[^=]*=[[:space:]]*/, "", $0); addr=$0
|
||||||
|
}
|
||||||
|
in_peer && key==pk && addr!="" {print addr; exit}
|
||||||
|
' "$WG_CONF" | awk -F',' '{print $1}' | xargs
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_config() {
|
||||||
|
if systemctl is-active --quiet "wg-quick@${WG_INTERFACE}"; then
|
||||||
|
wg syncconf "$WG_INTERFACE" <(wg-quick strip "$WG_CONF")
|
||||||
|
else
|
||||||
|
systemctl restart "wg-quick@${WG_INTERFACE}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_add() {
|
||||||
|
local client_name=""
|
||||||
|
local client_pubkey=""
|
||||||
|
local client_address=""
|
||||||
|
local client_psk=""
|
||||||
|
local keepalive="25"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--client-name)
|
||||||
|
client_name="$2"; shift 2 ;;
|
||||||
|
--client-public-key)
|
||||||
|
client_pubkey="$2"; shift 2 ;;
|
||||||
|
--client-address)
|
||||||
|
client_address="$2"; shift 2 ;;
|
||||||
|
--client-preshared-key)
|
||||||
|
client_psk="$2"; shift 2 ;;
|
||||||
|
--persistent-keepalive)
|
||||||
|
keepalive="$2"; shift 2 ;;
|
||||||
|
*)
|
||||||
|
die "Неизвестный аргумент: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$client_name" ]] || die "Не указан --client-name"
|
||||||
|
[[ -n "$client_pubkey" ]] || die "Не указан --client-public-key"
|
||||||
|
|
||||||
|
client_name="$(sanitize_name "$client_name")"
|
||||||
|
[[ -n "$client_name" ]] || die "Некорректное имя клиента"
|
||||||
|
|
||||||
|
load_meta
|
||||||
|
[[ -f "$WG_CONF" ]] || die "Не найден конфиг WireGuard: $WG_CONF"
|
||||||
|
|
||||||
|
local server_pubkey
|
||||||
|
server_pubkey="$(cat /etc/wireguard/server_public.key)"
|
||||||
|
|
||||||
|
if peer_exists_by_pubkey "$client_pubkey"; then
|
||||||
|
local existing_addr
|
||||||
|
existing_addr="$(extract_peer_address_by_pubkey "$client_pubkey")"
|
||||||
|
cat <<EOF_OUT
|
||||||
|
STATUS=exists
|
||||||
|
CLIENT_NAME=$client_name
|
||||||
|
CLIENT_ADDRESS=${existing_addr:-unknown}
|
||||||
|
SERVER_PUBLIC_KEY=$server_pubkey
|
||||||
|
SERVER_ENDPOINT=${SERVER_PUBLIC_IP}:${WG_PORT}
|
||||||
|
SERVER_DNS=${SERVER_DNS}
|
||||||
|
WG_INTERFACE=${WG_INTERFACE}
|
||||||
|
EOF_OUT
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$client_address" ]]; then
|
||||||
|
client_address="$(next_client_ip "$WG_NETWORK")" || die "Не удалось выделить IP клиенту в сети $WG_NETWORK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
backup_file "$WG_CONF"
|
||||||
|
|
||||||
|
{
|
||||||
|
echo
|
||||||
|
echo "# managed-by=wg-peerctl client=${client_name} created_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
|
echo "[Peer]"
|
||||||
|
echo "PublicKey = ${client_pubkey}"
|
||||||
|
if [[ -n "$client_psk" ]]; then
|
||||||
|
echo "PresharedKey = ${client_psk}"
|
||||||
|
fi
|
||||||
|
echo "AllowedIPs = ${client_address}"
|
||||||
|
echo "PersistentKeepalive = ${keepalive}"
|
||||||
|
} >> "$WG_CONF"
|
||||||
|
|
||||||
|
apply_config
|
||||||
|
|
||||||
|
cat <<EOF_OUT
|
||||||
|
STATUS=created
|
||||||
|
CLIENT_NAME=$client_name
|
||||||
|
CLIENT_ADDRESS=$client_address
|
||||||
|
SERVER_PUBLIC_KEY=$server_pubkey
|
||||||
|
SERVER_ENDPOINT=${SERVER_PUBLIC_IP}:${WG_PORT}
|
||||||
|
SERVER_DNS=${SERVER_DNS}
|
||||||
|
WG_INTERFACE=${WG_INTERFACE}
|
||||||
|
EOF_OUT
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local cmd="${1:-}"
|
||||||
|
if [[ -z "$cmd" ]]; then
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
shift || true
|
||||||
|
|
||||||
|
case "$cmd" in
|
||||||
|
add)
|
||||||
|
require_root
|
||||||
|
check_os_supported
|
||||||
|
cmd_add "$@"
|
||||||
|
;;
|
||||||
|
-h|--help|help)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "Неизвестная команда: $cmd"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
7
templates/wg0.conf.template
Normal file
7
templates/wg0.conf.template
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[Interface]
|
||||||
|
Address = __WG_ADDRESS__
|
||||||
|
ListenPort = __WG_PORT__
|
||||||
|
PrivateKey = __SERVER_PRIVATE_KEY__
|
||||||
|
SaveConfig = true
|
||||||
|
PostUp = iptables -A FORWARD -i __WG_INTERFACE__ -j ACCEPT; iptables -A FORWARD -o __WG_INTERFACE__ -j ACCEPT; iptables -t nat -A POSTROUTING -s __WG_NETWORK__ -o __DEFAULT_IFACE__ -j MASQUERADE
|
||||||
|
PostDown = iptables -D FORWARD -i __WG_INTERFACE__ -j ACCEPT; iptables -D FORWARD -o __WG_INTERFACE__ -j ACCEPT; iptables -t nat -D POSTROUTING -s __WG_NETWORK__ -o __DEFAULT_IFACE__ -j MASQUERADE
|
||||||
Reference in New Issue
Block a user