#!/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 Имя интерфейса WireGuard (по умолчанию: wg0) --wg-port Порт WireGuard (по умолчанию: 51820) --wg-network Подсеть VPN (по умолчанию: 10.66.66.0/24) --wg-address Адрес сервера в VPN (по умолчанию: 10.66.66.1/24) --server-public-ip Публичный IP сервера --server-dns DNS для клиентов (по умолчанию: 1.1.1.1) --default-iface Внешний интерфейс для NAT --gui-enable Включить WG Admin GUI (по умолчанию: yes) --gui-host Домен/IP для открытия GUI --gui-port Порт GUI (по умолчанию: 5000) --gui-user Логин GUI (по умолчанию: admin) --gui-password Пароль GUI (если не указан, будет сгенерирован) --gui-reset-db Устарело: 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" </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 < /etc/systemd/system/wg-admin-gui.service </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 <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 "$@"