# WireGuard VPS Tunnel — Docker Edition Автоматическая настройка WireGuard-туннеля в Docker-контейнерах между VPS (с публичным IP) и домашним сервером за NAT (Raspberry Pi, Orange Pi, Starlink и т.д.). **Поддерживаемые архитектуры:** `linux/amd64` (Intel/AMD VPS) и `linux/arm64` (Raspberry Pi 4/5, Orange Pi). ## Архитектура ``` Интернет │ ┌────────┴────────┐ │ VPS (сервер) │ │ Публичный IP │ │ например: │ │ 2.59.219.234 │ │ │ │ ┌──────────────┐ │ │ │ Docker │ │ │ │ wireguard- │ │ │ │ server │ │ │ │ │ │ │ │ wg0:10.0.0.1 │ │ │ │ :51820 (UDP) │ │ │ │ │ │ │ │ iptables │ │ │ │ DNAT: │ │ │ │ :80→10.0.0.2 │ │ │ │ :443→10.0.0.2│ │ │ └──────┬───────┘ │ └────────┼────────┘ │ ╔══════════════╧══════════════╗ ║ WireGuard UDP :51820 ║ ║ (PersistentKeepalive=21) ║ ╚══════════════╤══════════════╝ │ ┌────────┴────────┐ │ Домашний сервер │ │ (за NAT/CGNAT) │ │ Raspberry Pi │ │ Starlink │ │ │ │ ┌──────────────┐│ │ │ Docker ││ │ │ wireguard- ││ │ │ client ││ │ │ ││ │ │ wg0:10.0.0.2 ││ │ │ ││ │ │ Watchdog ││ │ │ (встроенный) ││ │ └──────┬───────┘│ └────────┼────────┘ │ ┌────────┴────────┐ │ Nginx Proxy │ │ Manager :80,443│ │ → backend │ └─────────────────┘ ``` **Как это работает:** 1. VPS принимает входящие соединения на порты 80 и 443 2. iptables (внутри Docker-контейнера) перенаправляет трафик через WireGuard-туннель на домашний сервер (10.0.0.2) 3. Домашний сервер (RPi) инициирует соединение с VPS — работает через любой NAT 4. `PersistentKeepalive = 21` секунда поддерживает туннель активным даже при отсутствии трафика 5. Встроенный Watchdog на клиенте мониторит состояние туннеля и перезапускает при обрыве 6. Всё изолировано в Docker-контейнерах — не затрагивает хост-систему ## 1-Command Deployment (deploy.sh) Разверните туннель одной командой с любой машины, имеющей SSH-доступ к VPS и домашнему серверу. **Ключи генерируются локально, обмен происходит автоматически — никакого ручного копирования.** ```bash # Клонируйте репозиторий на любую машину (не обязательно VPS или RPi) git clone http://192.168.2.28:3000/OpenDoor/wireguard-vps-tunnel.git cd wireguard-vps-tunnel # Запустите одной командой ./deploy.sh --vps root@2.59.219.234 --client root@192.168.2.44 --ports 80,443 ``` ### С парольной аутентификацией SSH ```bash ./deploy.sh \ --vps root@2.59.219.234 --vps-pass wumN8inGSTiFk3Jcy5 \ --client root@192.168.2.44 --client-pass retrowest \ --ports 80,443 ``` ### Что делает deploy.sh 1. Генерирует все 4 ключа WireGuard локально (на машине, где запущен скрипт) 2. Определяет публичный IP и сетевой интерфейс VPS 3. Создаёт `.env` файлы для сервера и клиента с предзаполненными ключами 4. Копирует проект на обе машины в `/opt/wireguard-vps-tunnel/` 5. Останавливает host-level WireGuard (если запущен) 6. Включает IP-форвардинг на VPS 7. Запускает Docker-контейнеры на обеих машинах 8. Ждёт healthcheck и проверяет туннель (ping + HTTP) ### Опции deploy.sh | Опция | По умолчанию | Описание | |-------|-------------|----------| | `--vps HOST` | **обязательно** | Адрес VPS (root@IP) | | `--client HOST` | **обязательно** | Адрес клиента (root@IP) | | `--vps-pass PASS` | — | Пароль SSH для VPS | | `--client-pass PASS` | — | Пароль SSH для клиента | | `--ports PORT,...` | `80,443` | Порты для проброса | | `--wg-port PORT` | `51820` | Порт WireGuard | | `--keepalive SEC` | `21` | PersistentKeepalive (сек) | | `--force` | — | Остановить существующие контейнеры и переразвернуть | ### Требования для deploy.sh - **На локальной машине:** `ssh`, `scp`, `wg` (или Docker для генерации ключей) - **На VPS и клиенте:** Docker 20.10+, Docker Compose plugin - **SSH-доступ:** ключевая или парольная аутентификация - **Права root:** на обеих удалённых машинах ### Идемпотентность `deploy.sh` безопасно запускать повторно: - Существующие контейнеры перезапускаются с новыми ключами - Старые ключи заменяются новыми - IP-форвардинг настраивается повторно (без дублирования в sysctl.conf) - Флаг `--force` принудительно останавливает контейнеры перед развёртыванием ## Быстрый старт (install.sh — пошагово) ### На VPS: ```bash git clone http://192.168.2.28:3000/OpenDoor/wireguard-vps-tunnel.git cd wireguard-vps-tunnel sudo ./install.sh --vps --ports 80,443 ``` ### На домашнем сервере (Raspberry Pi / Orange Pi): ```bash git clone http://192.168.2.28:3000/OpenDoor/wireguard-vps-tunnel.git cd wireguard-vps-tunnel sudo ./install.sh --client --vps-ip 2.59.219.234 ``` > **Примечание:** Docker-образы автоматически выбираются под архитектуру вашего устройства > (amd64 для VPS, arm64 для Raspberry Pi). Сборка не требуется — образы загружаются из > Gitea Container Registry. Для локальной сборки добавьте флаг `--build`. ## Ручная установка через Docker Compose ### 1. Клонирование репозитория ```bash git clone http://192.168.2.28:3000/OpenDoor/wireguard-vps-tunnel.git cd wireguard-vps-tunnel ``` ### 2. Генерация ключей ```bash # Установите wireguard-tools если ещё нет sudo apt-get install -y wireguard-tools # Сгенерируйте ключи mkdir -p config wg genkey | tee config/server_private.key | wg pubkey > config/server_public.key wg genkey | tee config/client_private.key | wg pubkey > config/client_public.key chmod 600 config/*.key ``` ### 3. Настройка VPS (сервер) Создайте `.env` файл: ```env CLIENT_PUBLIC_KEY=<ключ из config/client_public.key> SERVER_PUBLIC_IP=2.59.219.234 FORWARD_PORTS=80,443 ``` Запустите: ```bash sudo docker compose -f docker-compose.server.yml up -d ``` ### 4. Настройка клиента (домашний сервер) Создайте `.env` файл: ```env VPS_PUBLIC_IP=2.59.219.234 SERVER_PUBLIC_KEY=<ключ из config/server_public.key> PERSISTENT_KEEPALIVE=21 ``` Запустите: ```bash sudo docker compose -f docker-compose.client.yml up -d ``` ### 5. Проверка ```bash # На VPS docker exec wireguard-server wg show docker exec wireguard-server ping 10.0.0.2 # На домашнем сервере docker exec wireguard-client wg show docker exec wireguard-client ping 10.0.0.1 ``` ## Обмен ключами (пошагово) ### Шаг 1: На VPS — генерация ключей и запуск ```bash sudo ./install.sh --vps --ports 80,443 ``` Скрипт выведет публичный ключ сервера. **Скопируйте его.** ### Шаг 2: На клиенте — запуск с ключом сервера ```bash # Отредактируйте .env, вставив SERVER_PUBLIC_KEY из шага 1 sudo ./install.sh --client --vps-ip 2.59.219.234 ``` Скрипт выведет публичный ключ клиента. **Скопируйте его.** ### Шаг 3: На VPS — добавление ключа клиента ```bash # Отредактируйте .env, вставив CLIENT_PUBLIC_KEY из шага 2 # Перезапустите контейнер: sudo docker compose -f docker-compose.server.yml up -d --force-recreate ``` ## Переменные окружения ### Сервер (VPS) | Переменная | По умолчанию | Описание | |-----------|-------------|----------| | `CLIENT_PUBLIC_KEY` | **обязательно** | Публичный ключ клиента | | `SERVER_WG_IP` | `10.0.0.1` | WireGuard IP сервера | | `SERVER_WG_PORT` | `51820` | Порт WireGuard | | `FORWARD_PORTS` | `80,443` | Порты для проброса (через запятую) | | `SERVER_PUBLIC_IP` | авто | Публичный IP VPS | | `SERVER_PUBLIC_IFACE` | авто | Внешний сетевой интерфейс | ### Клиент (домашний сервер) | Переменная | По умолчанию | Описание | |-----------|-------------|----------| | `VPS_PUBLIC_IP` | **обязательно** | Публичный IP VPS | | `SERVER_PUBLIC_KEY` | **обязательно** | Публичный ключ сервера | | `CLIENT_WG_IP` | `10.0.0.2` | WireGuard IP клиента | | `VPS_WG_PORT` | `51820` | Порт WireGuard на VPS | | `PERSISTENT_KEEPALIVE` | `21` | Интервал keepalive (сек) | ## Опции командной строки (install.sh) | Опция | Описание | По умолчанию | |-------|----------|-------------| | `--vps` | Режим VPS (сервер) | — | | `--client` | Режим клиента (домашний сервер) | — | | `--vps-ip IP` | Публичный IP VPS | автоопределение | | `--ports PORT,...` | Порты для проброса (VPS) | `80,443` | | `--wg-port PORT` | Порт WireGuard | `51820` | | `--keepalive SEC` | PersistentKeepalive (сек) | `21` | | `--build` | Локальная сборка образа (вместо загрузки из registry) | `pull` | | `--server-key KEY` | Публичный ключ сервера (для неинтерактивной настройки клиента) | — | | `--client-key KEY` | Публичный ключ клиента (для неинтерактивной настройки сервера) | — | ## Watchdog (мониторинг туннеля) Watchdog встроен в клиентский контейнер и выполняет: 1. Проверяет, что интерфейс `wg0` поднят 2. Проверяет доступность VPS через туннель (`ping 10.0.0.1`) 3. Проверяет доступность VPS через интернет (`ping VPS_IP`) 4. Если туннель не работает, но VPS доступен — перезапускает WireGuard 5. Использует экспоненциальную отсрочку при повторных сбоях: - 10с → 20с → 40с → 80с → 120с (максимум) 6. После 3 успешных проверок подряд сбрасывает отсрочку **Просмотр логов:** ```bash docker compose -f docker-compose.client.yml logs -f ``` ## Starlink: особенности Starlink использует CGNAT (Carrier-Grade NAT), что означает: - **Нет публичного IPv4** — домашний сервер недоступен из интернета напрямую - **Высокая задержка** — 25-60 мс (спутниковая связь) - **Кратковременные обрывы** — при переключении между спутниками - **UDP работает лучше TCP** — меньше проблем с TCP congestion control **Рекомендации для Starlink:** 1. `PERSISTENT_KEEPALIVE=21` — оптимальное значение (каждые 21 сек отправляет keepalive-пакет) 2. Watchdog обязателен — автоматически восстанавливает туннель после обрывов 3. Не используйте SSH reverse tunnel — TCP-over-TCP на спутниковом канале работает плохо 4. MTU = 1420 (стандартный для WireGuard) — не меняйте без необходимости ## Multi-Arch (Поддержка архитектур) Docker-образы собираются автоматически для двух архитектур: | Архитектура | Устройства | Образ | |-------------|-----------|-------| | `linux/amd64` | VPS (Intel/AMD), десктопы | `git.softuniq.eu/opendoor/wireguard-vps-tunnel-server:latest` | | `linux/arm64` | Raspberry Pi 4/5, Orange Pi, Mac M1/M2 | `git.softuniq.eu/opendoor/wireguard-vps-tunnel-client:latest` | Установщик автоматически определяет архитектуру хоста и загружает соответствующий образ. Принудительная локальная сборка: ```bash # Собрать образ локально (для текущей архитектуры) sudo ./install.sh --vps --build # Или через docker compose docker compose -f docker-compose.server.yml build docker compose -f docker-compose.client.yml build ``` ### CI/CD При пуше в ветку `main` или создании тега `v*` Gitea Actions автоматически собирает и пушит multi-arch образы: - `git.softuniq.eu/opendoor/wireguard-vps-tunnel-server:latest` - `git.softuniq.eu/opendoor/wireguard-vps-tunnel-client:latest` Сборка использует QEMU для кросс-компиляции и Docker Buildx для multi-arch образов. ## Сравнение: Docker vs Host-Based | Критерий | Docker | Host-Based | |----------|--------|------------| | Изоляция | Полная (контейнер) | Нет (хост-система) | | Зависимости | Только Docker | wireguard-tools, iptables, systemd | | Multi-arch | ✅ amd64 + arm64 | Ручная установка под каждую ОС | | Обновление | `docker compose pull && up -d` | Ручная замена скриптов | | Откат | `docker compose down && up` (предыдущий образ) | Ручной откат конфигов | | Переносимость | Любой Linux с Docker | Debian/Ubuntu/Armbian | | Безопасность | Минимальная поверхность атаки (Alpine) | Полный доступ к хосту | | Watchdog | Встроен в контейнер | Отдельный systemd-сервис | | Логи | `docker logs` | journald + файлы | | Ресурсы | ~15 МБ образ, ~20 МБ RAM | ~5 МБ RAM (без контейнера) | ## Сравнение: WireGuard vs SSH Reverse Tunnel | Критерий | WireGuard | SSH Reverse Tunnel | |----------|-----------|-------------------| | Протокол | UDP (kernel) | TCP (userspace) | | Производительность | ~1 Gbps | ~100 Mbps | | Задержка | Минимальная | TCP-over-TCP проблема | | Starlink | ✅ Оптимально (UDP) | ❌ Плохо (TCP через TCP) | | Переподключение | Мгновенное | Заметная задержка | | NAT traversal | PersistentKeepalive | Autossh + мониторинг | | Проброс портов | iptables DNAT | `ssh -R` | | Отказоустойчивость | Встроенная | Требует autossh | | Нагрузка на CPU | Минимальная (kernel) | Заметная (шифрование в userspace) | ## Устранение неполадок ### Контейнер не запускается ```bash # Проверить логи docker compose -f docker-compose.server.yml logs docker compose -f docker-compose.client.yml logs # Проверить статус docker compose -f docker-compose.server.yml ps docker compose -f docker-compose.client.yml ps ``` ### Туннель не поднимается ```bash # Проверить статус WireGuard внутри контейнера docker exec wireguard-server wg show docker exec wireguard-client wg show # Проверить, что порт открыт на VPS nc -zvu 51820 ``` ### Порты не пробрасываются ```bash # Проверить правила iptables внутри контейнера docker exec wireguard-server iptables -t nat -L PREROUTING -n docker exec wireguard-server iptables -L FORWARD -n # Проверить IP-форвардинг docker exec wireguard-server sysctl net.ipv4.ip_forward ``` ### Пинг не проходит ```bash # Проверить что интерфейс поднят docker exec wireguard-server ip link show wg0 docker exec wireguard-client ip link show wg0 # Проверить IP-адреса docker exec wireguard-server ip addr show wg0 docker exec wireguard-client ip addr show wg0 ``` ### Полный сброс и переустановка ```bash # Деинсталляция sudo ./uninstall.sh --remove-images --remove-config # Переустановка sudo ./install.sh --vps --ports 80,443 ``` ## Деинсталляция ```bash sudo ./uninstall.sh ``` Опции: - `--remove-images` — также удалить Docker-образы - `--remove-config` — также удалить директорию `config/` с ключами и `.env` Деинсталлятор: - Останавливает и удаляет Docker-контейнеры - Опционально удаляет образы и конфигурацию - **НЕ трогает** SSH-сервер, Docker и другие сервисы - **НЕ изменяет** `sshd_config` (PasswordAuthentication остаётся без изменений) ## Безопасность - **Ключи**: приватные ключи хранятся с правами `600` (только root) - **Изоляция**: контейнеры работают в изолированном окружении (Alpine Linux) - **Фаервол**: iptables правила применяются атомарно через `PostUp`/`PostDown` - **SSH**: парольная аутентификация **НЕ отключается** — скрипт не трогает sshd_config - **Порты**: открываются только указанные порты (по умолчанию 80 и 443) - **WireGuard**: использует современную криптографию (Curve25519, ChaCha20, BLAKE2s) - **Docker**: `--cap-add=NET_ADMIN --cap-add=SYS_MODULE` — минимально необходимые capabilities - **Read-only**: `/lib/modules` монтируется в режиме `ro` (только чтение) ## Структура проекта ``` wireguard-vps-tunnel/ ├── server/ │ ├── Dockerfile # Docker-образ сервера (Alpine 3.20) │ ├── entrypoint.sh # Точка входа: генерация конфига, запуск WG │ └── healthcheck.sh # Healthcheck: wg show wg0 ├── client/ │ ├── Dockerfile # Docker-образ клиента (Alpine 3.20) │ ├── entrypoint.sh # Точка входа: генерация конфига, запуск WG + watchdog │ ├── watchdog.sh # Watchdog: мониторинг и автовосстановление туннеля │ └── healthcheck.sh # Healthcheck: ping VPS WG IP ├── docker-compose.server.yml # Docker Compose для VPS ├── docker-compose.client.yml # Docker Compose для клиента ├── .env.example # Шаблон переменных окружения ├── install.sh # 1-Click установщик (Docker) ├── uninstall.sh # Деинсталлятор (Docker) ├── .dockerignore # Исключения для сборочного контекста ├── .gitea/ │ └── workflows/ │ └── build.yml # CI/CD: сборка Docker-образов └── README.md # Документация ``` ## Нюансы развёртывания (выявлены на практике) ### 1. IP-форвардинг на хосте (обязательно) Контейнер не может записать в `/proc/sys/net/ipv4/ip_forward` (read-only в Docker). Включите на хосте: ```bash # Временно echo 1 > /proc/sys/net/ipv4/ip_forward echo 1 > /proc/sys/net/ipv6/conf/all/forwarding # Постоянно echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf echo 'net.ipv6.conf.all.forwarding = 1' >> /etc/sysctl.conf sysctl -p ``` Без этого DNAT-трафик не будет перенаправляться. ### 2. Обмен ключами: двухэтапный запуск Ключи генерируются **внутри контейнера** при первом запуске. Поэтому: 1. Запустите сервер → он сгенерирует ключи и выведет `SERVER_PUBLIC_KEY=...` 2. Впишите `SERVER_PUBLIC_KEY` в `.env` на клиенте 3. Запустите клиент → он выведет `CLIENT_PUBLIC_KEY=...` 4. Впишите `CLIENT_PUBLIC_KEY` в `.env` на сервере 5. Перезапустите сервер: `docker compose -f docker-compose.server.yml restart` Конфигурация `wg0.conf` пересоздаётся при каждом перезапуске, поэтому обновление ключей не требует ручного редактирования. ### 3. Удаление старого интерфейса wg0 При перезапуске контейнера интерфейс `wg0` может остаться от предыдущего запуска. Entrypoint автоматически удаляет его перед запуском `wg-quick up`. Если на хосте остался host-level WireGuard — остановите его: ```bash systemctl stop wg-quick@wg0 systemctl disable wg-quick@wg0 ip link del wg0 2>/dev/null rm -f /etc/wireguard/wg0.conf ``` ### 4. Docker Compose `version` устарел Поле `version: "3.8"` в docker-compose вызывает предупреждение: `the attribute 'version' is obsolete`. Оно удалено из файлов проекта. Если вы видите это предупреждение — просто удалите строку `version:` из compose-файла. ### 5. Multi-arch сборка требует Buildx Стандартный `docker build` не поддерживает `platforms: linux/amd64,linux/arm64`. Для multi-arch сборки через CI/CD используется Docker Buildx с QEMU. Для локальной сборки на текущей архитектуре достаточно убрать `platforms:` из compose-файла. ### 6. network_mode: host и порты Контейнеры используют `network_mode: host`. Секция `ports:` в compose-файле игнорируется (Docker выведет предупреждение). Все порты доступны напрямую через iptables DNAT. ### 7. Конфликт с Docker iptables Правила DNAT вставляются через `iptables -I FORWARD 1` — **перед** цепочкой DOCKER-USER. Это гарантирует, что WireGuard-трафик не блокируется Docker. Не меняйте приоритет без необходимости. ### 8. Парольная аутентификация SSH Скрипт **НЕ отключает** `PasswordAuthentication` в sshd. Если вы хотите отключить, сделайте это вручную и обязательно добавьте SSH-ключ в `authorized_keys` **до** отключения пароля, иначе потеряете доступ. ## Поддерживаемые ОС - Любой Linux с Docker 20.10+ - Debian 11+ (Bullseye, Bookworm) - Ubuntu 20.04+ (Focal, Jammy, Noble) - Armbian (Orange Pi, Banana Pi, etc.) - Raspberry Pi OS (Raspbian) ## Лицензия MIT