# SSH-туннель от веба к БД ядра Для Ubuntu/Debian и production Compose этого проекта. Адрес сервера ядра, SSH-порт и SSH-пользователь заменяются вашими значениями. Настройка выполняется на двух серверах. SSH-пароль и пароль MariaDB — разные данные. Схема: `app → core-db-tunnel:3306 → SSH → 127.0.0.1:13306 → MariaDB ядра`. Порт `3306` туннеля доступен только в сети контейнеров веба; на хост он не публикуется. Ключ хранится вне репозитория в `/etc/moonwell/ssh-tunnel`, в образ он не копируется. ## 1. На сервере ядра: доступ к MariaDB через localhost В папке с production `compose.yaml` ядра создайте дополнительный файл: ```bash cat > compose.ssh-db.yaml <<'YAML' services: db: ports: - "127.0.0.1:13306:3306" YAML docker compose -f compose.yaml -f compose.ssh-db.yaml up -d --no-deps db ``` Этот шаг пересоздаёт контейнер MariaDB, сохраняя его volume; соединения с БД прервутся на время перезапуска. Выполните его в подходящее для этого время. Если MariaDB уже доступна на localhost сервера ядра, используйте существующий порт и пропустите этот шаг. В будущих командах управления ядром сохраняйте `-f compose.ssh-db.yaml`, чтобы порт не пропал после пересоздания БД. Проверка: ```bash ss -lnt | grep ':13306' docker compose -f compose.yaml -f compose.ssh-db.yaml ps db ``` При использовании другого имени сервиса замените `db` на имя MariaDB вашего ядра. На сервере ядра SSH должен разрешать локальный TCP forwarding (`AllowTcpForwarding yes` или `local`); ограничения пользователя и ключа должны разрешать `127.0.0.1:13306`. ## 2. На сервере веба: отдельный SSH-ключ Выполняйте команды от root, как в текущем сеансе. Подставьте адрес, SSH-порт и существующего SSH-пользователя сервера ядра: ```bash CORE_HOST=IP_ИЛИ_ДОМЕН_ЯДРА CORE_PORT=22 CORE_USER=SSH_ПОЛЬЗОВАТЕЛЬ apt-get update apt-get install -y openssh-client install -d -m 700 /etc/moonwell/ssh-tunnel ssh-keygen -t ed25519 -N '' \ -C moonwell-web-db-tunnel \ -f /etc/moonwell/ssh-tunnel/id_ed25519 ssh-copy-id -p "$CORE_PORT" \ -i /etc/moonwell/ssh-tunnel/id_ed25519.pub \ -o UserKnownHostsFile=/etc/moonwell/ssh-tunnel/known_hosts \ "$CORE_USER@$CORE_HOST" ``` `ssh-copy-id` запросит SSH-пароль выбранного пользователя и сохранит публичный ключ. При первом подключении сверьте показанный fingerprint с ключом сервера ядра, проверенным через его существующую SSH-сессию или консоль: ```bash # На сервере ядра: ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub ``` Не запускайте `ssh-keygen` повторно поверх существующего ключа. ### Если пользователь допускает только ключ На веб-сервере выведите только публичную часть ключа: ```bash cat /etc/moonwell/ssh-tunnel/id_ed25519.pub ``` В существующей административной SSH-сессии **сервера ядра** замените имя пользователя и вставьте эту строку вместо `ssh-ed25519 AAAA...`: ```bash TUNNEL_USER=ИМЯ_ПОЛЬЗОВАТЕЛЯ_ТУННЕЛЯ sudo -H -u "$TUNNEL_USER" sh -c ' set -eu umask 077 mkdir -p "$HOME/.ssh" cat >> "$HOME/.ssh/authorized_keys" chmod 700 "$HOME/.ssh" chmod 600 "$HOME/.ssh/authorized_keys" ' <<'KEY' restrict,port-forwarding,permitopen="127.0.0.1:13306" ssh-ed25519 AAAA... moonwell-web-db-tunnel KEY ``` Так файл создаётся от имени самого пользователя. `restrict` отключает дополнительные возможности ключа, а `port-forwarding` разрешает forwarding к указанной в `permitopen` цели. При нестандартном `AuthorizedKeysFile` используйте путь из конфигурации SSH вместо `~/.ssh/authorized_keys`. В уже существующем блоке `Match User` этого пользователя проверьте настройки: ```text Match User ИМЯ_ПОЛЬЗОВАТЕЛЯ_ТУННЕЛЯ PubkeyAuthentication yes AuthenticationMethods publickey PasswordAuthentication no KbdInteractiveAuthentication no AllowTcpForwarding local PermitOpen 127.0.0.1:13306 MaxSessions 0 ``` `MaxSessions 0` разрешает forwarding и запрещает shell/command-сессии. После изменения SSH-конфига выполните `sudo /usr/sbin/sshd -t`, а при успешной проверке — `sudo systemctl reload ssh` (Ubuntu/Debian). Добавление самого ключа в `authorized_keys` не требует перезагрузки службы. ### Проверка входа и заполнение known_hosts На веб-сервере выполните ручное подключение с forwarding. Оно использует только публичный ключ для входа и допускает подтверждение fingerprint при первом соединении: ```bash ssh -vNT -p "$CORE_PORT" \ -i /etc/moonwell/ssh-tunnel/id_ed25519 \ -o BatchMode=no -o IdentitiesOnly=yes -o PreferredAuthentications=publickey \ -o PasswordAuthentication=no -o KbdInteractiveAuthentication=no \ -o StrictHostKeyChecking=ask -o GlobalKnownHostsFile=/dev/null \ -o UserKnownHostsFile=/etc/moonwell/ssh-tunnel/known_hosts \ -o ExitOnForwardFailure=yes \ -L 127.0.0.1:13307:127.0.0.1:13306 \ "$CORE_USER@$CORE_HOST" ``` Сверьте fingerprint с ключом сервера ядра и подтвердите его. В выводе ожидается успешная аутентификация `using "publickey"`; SSH остаётся работающим. Остановите ручную проверку через `Ctrl+C`. Команда `ssh ... true` для пользователя с `MaxSessions 0` не подходит, поскольку командные сессии ему запрещены. Не задавайте `BatchMode=yes` при первоначальном подтверждении нового host key: этот режим отключает взаимодействие с пользователем. Контейнер использует `BatchMode=yes` и строгую проверку уже сохранённого `known_hosts`. ```bash chmod 600 /etc/moonwell/ssh-tunnel/id_ed25519 /etc/moonwell/ssh-tunnel/known_hosts ``` ## 3. Настройки веба Перенесите обновлённый `compose.prod.yaml` и новые файлы этого проекта на веб-сервер: - `compose.prod.ssh-tunnel.yaml`; - `docker/ssh-tunnel/Dockerfile`; - `docker/ssh-tunnel/start.sh`. В `.env` веба укажите фактические SSH-данные и подключения через туннель: ```dotenv CORE_SSH_HOST=IP_ИЛИ_ДОМЕН_ЯДРА CORE_SSH_PORT=22 CORE_SSH_USER=SSH_ПОЛЬЗОВАТЕЛЬ CORE_DB_HOST=127.0.0.1 CORE_DB_PORT=13306 VMANGOS_AUTH_DB_HOST=core-db-tunnel VMANGOS_AUTH_DB_PORT=3306 VMANGOS_AUTH_DB_DATABASE=realmd VMANGOS_AUTH_DB_USERNAME=vmangos VMANGOS_AUTH_DB_PASSWORD="ПАРОЛЬ_БД_ЯДРА" VMANGOS_CHARACTERS_DB_HOST=core-db-tunnel VMANGOS_CHARACTERS_DB_PORT=3306 VMANGOS_CHARACTERS_DB_DATABASE=characters VMANGOS_CHARACTERS_DB_USERNAME=vmangos VMANGOS_CHARACTERS_DB_PASSWORD="ПАРОЛЬ_БД_ЯДРА" ``` Здесь `CORE_DB_HOST=127.0.0.1` означает localhost **сервера ядра**, а не веба. Порты `13306` (MariaDB на сервере ядра) и `3306` (туннель в сети веба) различаются. `CORE_SSH_PORT` — SSH-порт, он может отличаться от `22`. Настройки `DB_*` MySQL самого веба и `AWS_*` S3 остаются прежними. Удалите старые `VMANGOS_AUTH_DB_URL` и `VMANGOS_CHARACTERS_DB_URL`, если они указаны: иначе Laravel может использовать адреса из URL вместо настроек выше. `VMANGOS_DOCKER_NETWORK` для production с удалённым ядром не используется. Для проверки статуса игрового сервера укажите `MOONWELL_REALM_STATUS_HOST` равным доступному с веба адресу игрового сервера, а не `mangosd` из локального Docker. ## 4. Запуск на веб-сервере Для полного деплоя используйте обновлённый скрипт: ```bash ./scripts/deploy-production.sh --ssh-tunnel ``` Флаг добавляет файл туннеля во все production-команды Compose, собирает и запускает SSH-контейнер, ждёт его `healthy`, затем проверяет обе БД через `SELECT 1` перед миграциями. Если `VMANGOS_AUTH_DB_HOST` либо `VMANGOS_CHARACTERS_DB_HOST` уже равен `core-db-tunnel`, режим включается автоматически. Файл туннеля задаёт адреса и порты БД приложения независимо от устаревших `*_DB_HOST=db` в `.env`; имена баз и учётные данные берутся из `.env`. Удалите `VMANGOS_*_DB_URL`, чтобы они не переопределяли адрес подключения. Предупреждение про `stalwart` относится к отдельному `compose.mail.yaml`; скрипт не удаляет orphan-контейнеры. Команды ниже позволяют запустить и проверить туннель отдельно от полного деплоя. Из папки `moonwell-web`: ```bash docker compose -f compose.prod.yaml -f compose.prod.ssh-tunnel.yaml up -d --build core-db-tunnel docker compose -f compose.prod.yaml -f compose.prod.ssh-tunnel.yaml ps core-db-tunnel docker compose -f compose.prod.yaml -f compose.prod.ssh-tunnel.yaml logs --tail=30 core-db-tunnel ``` После состояния `healthy` пересоздайте приложение и nginx: ```bash docker compose -f compose.prod.yaml -f compose.prod.ssh-tunnel.yaml up -d --no-deps --force-recreate app web docker compose -f compose.prod.yaml -f compose.prod.ssh-tunnel.yaml exec -T --user app app php artisan config:cache ``` Docker запускает туннель после перезагрузки и перезапускает завершившийся SSH-процесс. SSH keepalive обнаруживает обрыв соединения. `healthy` подтверждает, что SSH создал локальный слушающий порт; доступность самой MariaDB проверяется следующим шагом. ## 5. Проверка из Laravel ```bash docker compose -f compose.prod.yaml -f compose.prod.ssh-tunnel.yaml exec -T --user app app php artisan tinker --execute='foreach (["vmangos_auth", "vmangos_characters"] as $name) { DB::connection($name)->select("SELECT 1"); echo $name." OK\n"; }' ``` Ожидаемый результат: ```text vmangos_auth OK vmangos_characters OK ``` Команда выполняет только `SELECT 1`, игровые аккаунты не меняются. Если `tinker` не установлен в вашей production-сборке, проверьте наличие `laravel/tinker` в production-зависимостях перед запуском этой команды. Ошибки: - `Permission denied (publickey)`: SSH-пользователь, публичный ключ или права файла ключа. - `Host key verification failed`: известный ключ не соответствует адресу/порту; сверьте fingerprint и обновите файл `known_hosts` через подтверждённое подключение. - `administratively prohibited`: сервер SSH запретил forwarding или цель туннеля. - `connect failed: Connection refused`: MariaDB не слушает на адресе/порту `CORE_DB_HOST:CORE_DB_PORT` на сервере ядра. - `Access denied for user`: туннель работает, но неверны учётные данные или MariaDB не разрешает этому пользователю подключение с адреса хоста Docker. Документация: [OpenSSH forwarding](https://man.openbsd.org/ssh), [OpenSSH keepalive](https://man.openbsd.org/ssh_config), [OpenSSH authorized_keys](https://man.openbsd.org/sshd), [OpenSSH server configuration](https://man.openbsd.org/sshd_config), [Docker published ports](https://docs.docker.com/engine/network/port-publishing/).