MAATRIX / Блог / Bash-скрипт массовой генерации клиентов WireGuard

Bash-скрипт массовой генерации клиентов WireGuard

MAATRIX

Когда в команду приходит пятый, десятый, двадцатый человек, ручное создание пира WireGuard — генерация ключей, правка серверного конфига, сборка клиентского файла, отрисовка QR — превращается в рутину, где легко перепутать IP или забыть добавить peer на сервере. Ниже — рабочий bash-скрипт, который по одной команде создаёт нужное количество клиентов: ключевые пары, конфиги, PNG и терминальные QR-коды, и сразу прописывает пиры на сервере без перезапуска туннеля.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Как устроена массовая генерация клиентов WireGuard

WireGuard не различает «сервер» и «клиент» на уровне протокола — это равноправные пиры, просто один из них слушает на публичном порту и хранит статичный конфиг с несколькими секциями [Peer]. Отсюда и вся механика скрипта: для каждого нового человека нужно

  • сгенерировать пару ключей (wg genkey / wg pubkey);
  • по желанию — общий предварительный ключ PresharedKey для дополнительного слоя защиты;
  • выдать уникальный IP из подсети туннеля;
  • собрать клиентский .conf с адресом, ключами сервера и параметрами AllowedIPs;
  • добавить блок [Peer] в серверный wg0.conf;
  • применить изменения без разрыва существующих сессий.

Вручную это 6-7 команд на одного человека. При онбординге команды из 10-15 людей — уже час монотонной работы с шансом ошибиться в последнем октете IP. Если WireGuard на сервере ещё не поднят, сначала разверните его — базовая установка описана в статье про установку WireGuard на VPS.

Что должен уметь скрипт

Прежде чем писать код, стоит определиться с требованиями — иначе получится одноразовый хак, который развалится на 20-м клиенте:

  • Идемпотентность IP — скрипт не должен выдавать один и тот же адрес дважды, даже если запускается повторно в разные дни. Нужен файл-счётчик.
  • Именование — клиенты называются осмысленно (ivan, maria, laptop-ivan), а не client1, client2 — потом проще отзывать доступ.
  • Пакетный и точечный режим — можно передать число (сгенерировать N клиентов с автонумерацией) или файл со списком имён.
  • QR сразу — большинству людей проще отсканировать QR телефоном, чем копировать конфиг.
  • Применение без даунтайма — через wg syncconf, а не systemctl restart wireguard, который рвёт все активные туннели.
  • Отдельная папка на клиента — конфиг и QR лежат рядом, архив можно целиком переслать через защищённый канал.

Арендуйте сервер под свои задачи!

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Скрипт генерации: полный код

Сохраните как /etc/wireguard/gen-clients.sh на сервере и дайте права на выполнение (chmod +x). Скрипт рассчитан на структуру, где интерфейс сервера — wg0, подсеть — 10.66.0.0/24, сервер сам занимает 10.66.0.1.

#!/usr/bin/env bash
set -euo pipefail

# --- Настройки под ваш сервер ---
WG_IFACE="wg0"
WG_DIR="/etc/wireguard"
SERVER_CONF="${WG_DIR}/${WG_IFACE}.conf"
SERVER_PUB_KEY=$(cat "${WG_DIR}/server_public.key")
SERVER_ENDPOINT="203.0.113.10:51820"     # ваш публичный IP:порт
CLIENT_SUBNET="10.66.0"
CLIENT_DNS="1.1.1.1"
ALLOWED_IPS="0.0.0.0/0, ::/0"            # или 10.66.0.0/24 для доступа только к сети
KEEPALIVE=25
OUT_DIR="${WG_DIR}/clients"
COUNTER_FILE="${OUT_DIR}/.last_octet"

mkdir -p "${OUT_DIR}"
[ -f "${COUNTER_FILE}" ] || echo 1 > "${COUNTER_FILE}"

usage() {
  echo "Использование:"
  echo "  $0 --count N [--prefix imya]      # сгенерировать N клиентов"
  echo "  $0 --file names.txt               # по одному имени на строку"
  exit 1
}

next_octet() {
  local o
  o=$(cat "${COUNTER_FILE}")
  echo $((o + 1)) > "${COUNTER_FILE}"
  echo "$o"
}

make_client() {
  local name="$1"
  local octet client_ip priv pub psk client_dir

  octet=$(next_octet)
  client_ip="${CLIENT_SUBNET}.$((octet + 1))"
  client_dir="${OUT_DIR}/${name}"
  mkdir -p "${client_dir}"

  priv=$(wg genkey)
  pub=$(echo "${priv}" | wg pubkey)
  psk=$(wg genpsk)

  # клиентский конфиг
  cat > "${client_dir}/${name}.conf" <<EOF
[Interface]
PrivateKey = ${priv}
Address = ${client_ip}/32
DNS = ${CLIENT_DNS}

[Peer]
PublicKey = ${SERVER_PUB_KEY}
PresharedKey = ${psk}
Endpoint = ${SERVER_ENDPOINT}
AllowedIPs = ${ALLOWED_IPS}
PersistentKeepalive = ${KEEPALIVE}
EOF

  # блок для серверного wg0.conf
  cat >> "${SERVER_CONF}" <<EOF

# ${name} $(date +%F)
[Peer]
PublicKey = ${pub}
PresharedKey = ${psk}
AllowedIPs = ${client_ip}/32
EOF

  # QR-коды: в терминал и PNG-файл
  qrencode -t ansiutf8 < "${client_dir}/${name}.conf"
  qrencode -t png -o "${client_dir}/${name}.png" < "${client_dir}/${name}.conf"

  echo "[+] ${name}: ${client_ip} готов -> ${client_dir}/"
}

# --- Разбор аргументов ---
MODE=""
COUNT=0
PREFIX="user"
NAMES_FILE=""

while [ $# -gt 0 ]; do
  case "$1" in
    --count) COUNT="$2"; MODE="count"; shift 2 ;;
    --prefix) PREFIX="$2"; shift 2 ;;
    --file) NAMES_FILE="$2"; MODE="file"; shift 2 ;;
    *) usage ;;
  esac
done

[ -z "${MODE}" ] && usage

if [ "${MODE}" = "count" ]; then
  for i in $(seq -w 1 "${COUNT}"); do
    make_client "${PREFIX}${i}"
  done
elif [ "${MODE}" = "file" ]; then
  [ -f "${NAMES_FILE}" ] || { echo "Файл ${NAMES_FILE} не найден"; exit 1; }
  while IFS= read -r line; do
    [ -z "${line}" ] && continue
    make_client "${line}"
  done < "${NAMES_FILE}"
fi

# Применить новые пиры без разрыва туннеля
wg syncconf "${WG_IFACE}" <(wg-quick strip "${WG_IFACE}")
echo "[✓] Готово. Пиры применены на лету."

Ключевой момент — wg syncconf ${WG_IFACE} <(wg-quick strip ${WG_IFACE}). Команда wg-quick strip вычищает из конфига директивы, которые понимает только wg-quick (Address, DNS, PostUp/PostDown), и отдаёт wg syncconf только то, что нужно ядру. В результате новые пиры добавляются в рантайм без systemctl restart, а значит уже подключённые клиенты не отваливаются.

Как запустить и что получите на выходе

Массовая генерация по номерам — например, для 12 новых стажёров:

sudo ./gen-clients.sh --count 12 --prefix intern

Получите intern01intern12. Если у вас уже есть список реальных имён (что удобнее для последующего отзыва доступа), сделайте names.txt:

ivan.petrov
maria.sidorova
dev-laptop-alex
ci-runner-01

и запустите:

sudo ./gen-clients.sh --file names.txt

На выходе — структура вида:

/etc/wireguard/clients/
├── ivan.petrov/
│   ├── ivan.petrov.conf
│   └── ivan.petrov.png
├── maria.sidorova/
│   ├── maria.sidorova.conf
│   └── maria.sidorova.png
└── ...

Каждую папку можно целиком заархивировать и передать человеку через защищённый канал (не по обычной почте — конфиг содержит приватный ключ). Проверить, что все пиры реально поднялись на сервере:

sudo wg show wg0

В выводе появится список публичных ключей с latest handshake, который заполнится, как только клиент подключится хотя бы раз.

QR-коды для онбординга без ручной работы

Для мобильных клиентов (iOS, Android) сканирование QR — самый быстрый способ импорта, без копирования текста и риска опечатки. Скрипт уже генерирует оба варианта:

  • qrencode -t ansiutf8 — печатает QR прямо в терминал SSH-сессии, удобно показать человеку по видеозвонку или сделать скриншот;
  • qrencode -t png -o file.png — сохраняет файл, который можно вставить в письмо, тикет в таск-трекере или отправить в мессенджер.

Если qrencode ещё не установлен:

sudo apt install qrencode -y      # Debian/Ubuntu
sudo dnf install qrencode -y      # RHEL/Rocky/AlmaLinux

Дальнейшая пересылка QR и импорт на телефоне уже описаны отдельно — см. подключение к WireGuard с Android и аналогичную статью про iPhone. Для десктопных клиентов проще раздать сами .conf-файлы — импорт через официальное приложение WireGuard занимает пару кликов «Import tunnel from file».

Если команда крупная и регулярно растёт, есть смысл посмотреть в сторону веб-панели поверх этой же логики — про неё есть отдельный разбор: wg-easy как веб-панель для WireGuard. Панель удобнее для повседневного управления через браузер, а bash-скрипт — быстрее и предсказуемее, когда нужно завести сразу партию клиентов или встроить генерацию в CI/CD онбординга сотрудников.

Отзыв доступа и обслуживание парка клиентов

Массовая генерация рано или поздно требует симметричной операции — массового или точечного отзыва. Простой скрипт-компаньон:

#!/usr/bin/env bash
set -euo pipefail

WG_IFACE="wg0"
SERVER_CONF="/etc/wireguard/${WG_IFACE}.conf"
NAME="$1"   # имя, под которым делали комментарий "# name <дата>"

# Удаляем блок [Peer] с этим именем из конфига
sed -i "/# ${NAME} /,/^$/d" "${SERVER_CONF}"

wg syncconf "${WG_IFACE}" <(wg-quick strip "${WG_IFACE}")
echo "[✓] Доступ для ${NAME} отозван"

Именно поэтому в основном скрипте каждый блок [Peer] помечается комментарием # имя дата — без него отзыв превращается в поиск нужного публичного ключа вручную. Список активных пиров с трафиком удобно смотреть через wg show wg0 dump — там же видно, кто давно не подключался, и таких клиентов можно вычищать пачками.

Пара практических нюансов, которые стоит держать в голове:

СитуацияЧто делать
Сотрудник уволенОтозвать конфиг сразу через скрипт выше, не дожидаясь «плановой уборки»
Утечка конфига (потерян ноутбук/телефон)Отозвать и перевыпустить с новым именем — старый IP можно не переиспользовать
Более 200 клиентов на один интерфейсРазнести на несколько wg-подсетей/интерфейсов — линейный перебор AllowedIPs в дампе начинает тормозить на слабом CPU
Нужен лимит скорости на пользователяОтдельная настройка через tc, скрипт генерации к ней не относится — см. ограничение скорости и трафика для VPN

Резервную копию wg0.conf и файла-счётчика .last_octet стоит бэкапить регулярно — без них восстановление после потери сервера означает пересборку всех клиентских конфигов заново. Практика бэкапа конфигов разобрана отдельно для Windows Server, но идея переносится и на Linux — храните копию вне самого сервера.

Арендуйте сервер под свои задачи!

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Что будет, если запустить скрипт дважды с одинаковым --count?

Ничего страшного: счётчик октетов хранится в .last_octet и не сбрасывается, поэтому второй запуск создаст новую партию клиентов со следующими IP, а не перезапишет старые.

Нужен ли PresharedKey, если и так есть основная пара ключей?

Не обязателен, но рекомендован WireGuard как дополнительный слой на случай будущей уязвимости в криптографии Curve25519 — в скрипте он включён по умолчанию и почти не добавляет накладных расходов.

Можно ли ограничить AllowedIPs так, чтобы клиент видел только внутреннюю сеть, а не весь интернет?

Да, замените ALLOWED_IPS="0.0.0.0/0, ::/0" на диапазон вашей сети, например 10.66.0.0/24 — тогда через туннель пойдёт только трафик к другим машинам в этой подсети, а не весь интернет-трафик клиента.

Как встроить это в автоматический онбординг (например, при создании учётки в HR-системе)?

Скрипт легко вызывается из вебхука или CI-джобы: передайте имя сотрудника как аргумент --file с одной строкой, заберите готовый .conf/.png по известному пути и отправьте на почту через отдельный шаг пайплайна.

Что если IPv6 тоже нужен?

Добавьте вторую подсеть (например, fd00:66::/64) и второй счётчик по аналогии с IPv4 — логика генерации ключей и QR не меняется, добавляется только вторая строка Address в клиентском конфиге.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →