Files
Wireguard_server/server/install_server.sh

456 lines
15 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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_PASSWORD_GENERATED=0
GUI_RESET_DB="no"
usage() {
cat <<'USAGE'
Установка WireGuard-сервера и встроенного WG Admin GUI (Debian/Ubuntu).
Каждый запуск выполняет полный reset прошлой инсталляции и поднимает все с нуля.
Использование:
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> Включить WG Admin GUI (по умолчанию: yes)
--gui-host <host> Домен/IP для открытия GUI
--gui-port <port> Порт GUI (по умолчанию: 5000)
--gui-user <user> Логин GUI (по умолчанию: admin)
--gui-password <pass> Пароль GUI (если не указан, будет сгенерирован)
--gui-reset-db <yes|no> Устарело: reset БД теперь выполняется автоматически
-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 ;;
--gui-reset-db)
GUI_RESET_DB="$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"
[[ "$GUI_ENABLE" == "yes" || "$GUI_ENABLE" == "no" ]] || die "--gui-enable должен быть yes или no"
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
}
reset_existing_install() {
log_warn "Выполняю полный reset предыдущей инсталляции WireGuard/GUI"
if [[ -d /etc/wireguard ]]; then
local conf iface
shopt -s nullglob
for conf in /etc/wireguard/*.conf; do
iface="$(basename "$conf" .conf)"
systemctl disable --now "wg-syncconf@${iface}.path" >/dev/null 2>&1 || true
systemctl stop "wg-syncconf@${iface}.service" >/dev/null 2>&1 || true
systemctl disable --now "wg-quick@${iface}.service" >/dev/null 2>&1 || true
wg-quick down "$iface" >/dev/null 2>&1 || true
done
shopt -u nullglob
rm -f /etc/wireguard/*.conf /etc/wireguard/*.key /etc/wireguard/wg-meta.env
log_info "Очищены конфиги/ключи WireGuard в /etc/wireguard"
fi
systemctl disable --now wg-admin-gui.service >/dev/null 2>&1 || true
rm -f /etc/systemd/system/wg-admin-gui.service
if command -v docker >/dev/null 2>&1; then
if [[ -f /opt/wireguard-ui/docker-compose.yml ]]; then
(cd /opt/wireguard-ui && docker compose down --remove-orphans >/dev/null 2>&1) || true
(cd /opt/wireguard-ui && docker-compose down --remove-orphans >/dev/null 2>&1) || true
fi
docker rm -f wireguard-ui wg-admin-postgres >/dev/null 2>&1 || true
fi
rm -rf /opt/wireguard-ui /opt/wg-admin-gui
log_info "Очищено состояние GUI"
}
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
GUI_PASSWORD="$(random_alnum 10)"
GUI_PASSWORD_GENERATED=1
log_warn "Пароль GUI не задан. Сгенерирован пароль: ${GUI_PASSWORD}"
if (( ! NON_INTERACTIVE )); then
local replace_or_password=""
read -r -p "Хотите заменить сгенерированный пароль GUI? [y/N] (или введите пароль сразу): " replace_or_password
if [[ "$replace_or_password" =~ ^([yY][eE][sS]|[yY])$ ]]; then
local custom_gui_password=""
ask_secret "Введите новый пароль GUI (${GUI_USER})" custom_gui_password
if [[ -n "$custom_gui_password" ]]; then
GUI_PASSWORD="$custom_gui_password"
GUI_PASSWORD_GENERATED=0
fi
elif [[ -n "$replace_or_password" && ! "$replace_or_password" =~ ^([nN][oO]?|[nN])$ ]]; then
GUI_PASSWORD="$replace_or_password"
GUI_PASSWORD_GENERATED=0
fi
fi
fi
[[ "$GUI_RESET_DB" == "yes" || "$GUI_RESET_DB" == "no" ]] || die "--gui-reset-db должен быть yes или no"
fi
validate_inputs
}
install_packages() {
apt_install_if_missing \
wireguard wireguard-tools iproute2 iptables curl ca-certificates openssl \
python3 python3-venv python3-pip
}
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"
umask 077
wg genkey | tee "$priv" | wg pubkey > "$pub"
safe_chmod_600 "$priv"
safe_chmod_600 "$pub"
log_success "Сгенерированы ключи сервера"
}
setup_wg_config() {
local conf="/etc/wireguard/${WG_INTERFACE}.conf"
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"
}
install_wg_sync_watcher() {
cat > /usr/local/sbin/wg-syncconf-apply <<'EOF_SYNC_APPLY'
#!/usr/bin/env bash
set -euo pipefail
iface="${1:-wg0}"
conf="/etc/wireguard/${iface}.conf"
[[ -f "$conf" ]] || exit 0
if systemctl is-active --quiet "wg-quick@${iface}.service"; then
wg syncconf "$iface" <(wg-quick strip "$conf")
else
systemctl start "wg-quick@${iface}.service"
fi
EOF_SYNC_APPLY
chmod 750 /usr/local/sbin/wg-syncconf-apply
cat > /etc/systemd/system/wg-syncconf@.service <<'EOF_SYNC_SERVICE'
[Unit]
Description=Apply WireGuard config changes for %i
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/wg-syncconf-apply %i
EOF_SYNC_SERVICE
cat > /etc/systemd/system/wg-syncconf@.path <<'EOF_SYNC_PATH'
[Unit]
Description=Watch WireGuard config changes for %i
[Path]
PathExists=/etc/wireguard/%i.conf
PathModified=/etc/wireguard/%i.conf
[Install]
WantedBy=multi-user.target
EOF_SYNC_PATH
systemctl daemon-reload
systemctl enable --now "wg-syncconf@${WG_INTERFACE}.path"
log_success "Включено авто-применение изменений /etc/wireguard/${WG_INTERFACE}.conf -> ${WG_INTERFACE}"
}
setup_gui() {
[[ "$GUI_ENABLE" == "yes" ]] || { log_warn "GUI отключен (GUI_ENABLE=no)"; return; }
mkdir -p /opt/wg-admin-gui/{app,data}
safe_chmod_700 /opt/wg-admin-gui
cp -a "${PROJECT_ROOT}/gui/." /opt/wg-admin-gui/app/
python3 -m venv /opt/wg-admin-gui/venv
/opt/wg-admin-gui/venv/bin/pip install --upgrade pip >/dev/null
/opt/wg-admin-gui/venv/bin/pip install -r /opt/wg-admin-gui/app/requirements.txt >/dev/null
cat > /opt/wg-admin-gui/wg-admin-gui.env <<EOF_ENV
DB_PATH=/opt/wg-admin-gui/data/wgadmin.db
WG_INTERFACE=${WG_INTERFACE}
WG_META_FILE=/etc/wireguard/wg-meta.env
ADMIN_USER=${GUI_USER}
ADMIN_PASSWORD=${GUI_PASSWORD}
APP_SECRET=$(random_alnum 32)
APP_PORT=${GUI_PORT}
EOF_ENV
chmod 600 /opt/wg-admin-gui/wg-admin-gui.env
cat > /etc/systemd/system/wg-admin-gui.service <<EOF_SERVICE
[Unit]
Description=WG Admin GUI
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/opt/wg-admin-gui/app
EnvironmentFile=/opt/wg-admin-gui/wg-admin-gui.env
ExecStart=/opt/wg-admin-gui/venv/bin/gunicorn -w 2 -b 0.0.0.0:${GUI_PORT} app:app
Restart=always
RestartSec=2
User=root
[Install]
WantedBy=multi-user.target
EOF_SERVICE
systemctl daemon-reload
systemctl enable --now wg-admin-gui.service
log_success "WG Admin GUI запущен"
}
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="$(systemctl is-active wg-admin-gui.service 2>/dev/null || true)"
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}
$(if [[ "$GUI_ENABLE" == "yes" && "$GUI_PASSWORD_GENERATED" -eq 1 ]]; then echo "GUI пароль: ${GUI_PASSWORD} (сгенерирован, рекомендуется заменить)"; fi)
Helper для peer: /usr/local/sbin/wg-peerctl
Auto-apply GUI->WG: enabled (wg-syncconf@${WG_INTERFACE}.path)
Лог установки: ${LOG_FILE}
=================================================
EOF_SUMMARY
}
main() {
parse_args "$@"
require_root
check_os_supported
require_cmd ip
require_cmd awk
require_cmd sed
require_cmd python3
collect_inputs
log_info "Начинаю установку WireGuard-сервера (чистый запуск)"
reset_existing_install
install_packages
setup_sysctl
setup_keys
setup_wg_config
setup_meta
setup_wg_service
setup_ufw_if_active
install_peer_helper
install_wg_sync_watcher
setup_gui
print_summary
log_success "Установка завершена"
}
main "$@"