feat: автоматизация установки и настройки WireGuard сервера и клиента

This commit is contained in:
Ruslan
2026-04-14 00:04:06 +03:00
commit a31f1a1090
8 changed files with 1416 additions and 0 deletions

352
server/install_server.sh Executable file
View 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 "$@"