From 22cb56e52f2f35845b917aa208189a4a8d3dd0ef Mon Sep 17 00:00:00 2001 From: sindoring Date: Sun, 4 Oct 2026 13:36:43 +0400 Subject: [PATCH] =?UTF-8?q?ssh=20=D1=82=D1=83=D0=BD=D0=BD=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 56 ++++++++++- compose.prod.local-core.yaml | 12 +++ compose.prod.ssh-tunnel.yaml | 48 +++++++++ compose.prod.yaml | 4 - docker/ssh-tunnel/Dockerfile | 10 ++ docker/ssh-tunnel/start.sh | 20 ++++ docs/ssh-core-tunnel.md | 183 +++++++++++++++++++++++++++++++++++ 7 files changed, 328 insertions(+), 5 deletions(-) create mode 100644 compose.prod.local-core.yaml create mode 100644 compose.prod.ssh-tunnel.yaml create mode 100644 docker/ssh-tunnel/Dockerfile create mode 100644 docker/ssh-tunnel/start.sh create mode 100644 docs/ssh-core-tunnel.md diff --git a/README.md b/README.md index 1b302ed..e629628 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,61 @@ VMANGOS_CHARACTERS_DB_USERNAME=vmangos MOONWELL_CLIENT_VERSION=1.12.1 ``` -Для PHP, запущенного прямо на хосте, укажите `127.0.0.1:13306` вместо `db:3306`. В продакшене настройте `VMANGOS_DOCKER_NETWORK=vmangos_default` и учётные данные продакшен-БД vMaNGOS. Контейнер приложения присоединяется к указанной сети автоматически. +Для PHP, запущенного прямо на хосте, укажите `127.0.0.1:13306` вместо `db:3306`. + +## Production с удалённым ядром + +Для соединения через SSH используйте [пошаговую инструкцию](docs/ssh-core-tunnel.md) +и `compose.prod.ssh-tunnel.yaml`. + +`compose.prod.yaml` подключает приложение только к сети веба `sail`. +Внешняя Docker-сеть vMaNGOS не требуется. `VMANGOS_DOCKER_NETWORK` +в `.env` не влияет на этот режим; `DB_*` остаются настройками MySQL самого сайта. +Production Compose читает `.env`, а не `.env.production`. + +Укажите доступный из контейнера приложения адрес и порт MariaDB ядра +(прямое соединение, VPN или туннель), базы и учётные данные: + +```dotenv +VMANGOS_AUTH_DB_HOST=CORE_DB_HOST +VMANGOS_AUTH_DB_PORT=CORE_DB_PORT +VMANGOS_AUTH_DB_DATABASE=realmd +VMANGOS_AUTH_DB_USERNAME=WEB_DB_USER +VMANGOS_AUTH_DB_PASSWORD=WEB_DB_PASSWORD +VMANGOS_CHARACTERS_DB_HOST=CORE_DB_HOST +VMANGOS_CHARACTERS_DB_PORT=CORE_DB_PORT +VMANGOS_CHARACTERS_DB_DATABASE=characters +VMANGOS_CHARACTERS_DB_USERNAME=WEB_DB_USER +VMANGOS_CHARACTERS_DB_PASSWORD=WEB_DB_PASSWORD +MOONWELL_REALM_STATUS_HOST=CORE_GAME_HOST +``` + +Замените значения-заполнители своими. Порт игрового сервера `3724` не является +портом MariaDB. Адрес `db` используется только внутри сети Docker ядра; +`127.0.0.1` внутри контейнера приложения означает сам контейнер. +Если заданы `VMANGOS_AUTH_DB_URL` или `VMANGOS_CHARACTERS_DB_URL`, +обновите их либо удалите, чтобы Laravel использовал отдельные `*_DB_*` значения. +Учётной записи БД и сетевому доступу должны быть разрешены соединения с веб-сервера. + +После изменения `.env` пересоздайте контейнеры и обновите кеш: + +```bash +docker compose -f compose.prod.yaml up -d --no-deps --force-recreate app web +docker compose -f compose.prod.yaml exec -T --user app app php artisan config:cache +``` + +Проверка обеих БД из контейнера (только `SELECT 1`, без изменения аккаунтов): + +```bash +docker compose -f compose.prod.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"; }' +``` + +Для production-ядра на том же Docker-хосте добавьте +`-f compose.prod.local-core.yaml` во все production-команды Compose, +укажите `VMANGOS_DOCKER_NETWORK=vmangos_default` (либо фактическую сеть ядра) +и адрес БД `db:3306`. В локальном Sail `compose.yaml` подключение к сети +`vmangos-local_default` сохранено. + ## Запуск сайта diff --git a/compose.prod.local-core.yaml b/compose.prod.local-core.yaml new file mode 100644 index 0000000..333d1d3 --- /dev/null +++ b/compose.prod.local-core.yaml @@ -0,0 +1,12 @@ +# Optional connection to a vMaNGOS database on the same Docker host. +# docker compose -f compose.prod.yaml -f compose.prod.local-core.yaml up -d +services: + app: + networks: + - sail + - vmangos + +networks: + vmangos: + external: true + name: '${VMANGOS_DOCKER_NETWORK:-vmangos_default}' diff --git a/compose.prod.ssh-tunnel.yaml b/compose.prod.ssh-tunnel.yaml new file mode 100644 index 0000000..5cc7cf2 --- /dev/null +++ b/compose.prod.ssh-tunnel.yaml @@ -0,0 +1,48 @@ +# Use together with compose.prod.yaml. No database port is published on the web host. +services: + core-db-tunnel: + build: + context: . + dockerfile: docker/ssh-tunnel/Dockerfile + image: moonwell-core-db-tunnel:local + restart: unless-stopped + init: true + environment: + CORE_SSH_HOST: '${CORE_SSH_HOST:?Set CORE_SSH_HOST in .env}' + CORE_SSH_PORT: '${CORE_SSH_PORT:-22}' + CORE_SSH_USER: '${CORE_SSH_USER:?Set CORE_SSH_USER in .env}' + CORE_DB_HOST: '${CORE_DB_HOST:-127.0.0.1}' + CORE_DB_PORT: '${CORE_DB_PORT:-13306}' + volumes: + - type: bind + source: '${CORE_SSH_KEY_PATH:-/etc/moonwell/ssh-tunnel/id_ed25519}' + target: /run/ssh/id_ed25519 + read_only: true + bind: + create_host_path: false + - type: bind + source: '${CORE_SSH_KNOWN_HOSTS_PATH:-/etc/moonwell/ssh-tunnel/known_hosts}' + target: /run/ssh/known_hosts + read_only: true + bind: + create_host_path: false + networks: + - sail + healthcheck: + test: ['CMD', 'nc', '-z', '-w', '2', '127.0.0.1', '3306'] + interval: 10s + timeout: 3s + retries: 3 + start_period: 15s + read_only: true + tmpfs: + - /tmp + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + + app: + depends_on: + core-db-tunnel: + condition: service_healthy diff --git a/compose.prod.yaml b/compose.prod.yaml index eef84c9..57ca99e 100644 --- a/compose.prod.yaml +++ b/compose.prod.yaml @@ -23,7 +23,6 @@ services: - '/var/www/html/bootstrap/cache:uid=${WWWUSER:-1000},gid=${WWWGROUP:-1000},mode=0770' networks: - sail - - vmangos depends_on: mysql: condition: service_healthy @@ -100,9 +99,6 @@ services: networks: sail: driver: bridge - vmangos: - external: true - name: '${VMANGOS_DOCKER_NETWORK:-vmangos_default}' volumes: sail-mysql: diff --git a/docker/ssh-tunnel/Dockerfile b/docker/ssh-tunnel/Dockerfile new file mode 100644 index 0000000..aa8eccc --- /dev/null +++ b/docker/ssh-tunnel/Dockerfile @@ -0,0 +1,10 @@ +FROM debian:bookworm-slim + +RUN apt-get update \ + && apt-get install -y --no-install-recommends openssh-client ca-certificates netcat-openbsd \ + && rm -rf /var/lib/apt/lists/* + +COPY docker/ssh-tunnel/start.sh /usr/local/bin/moonwell-core-tunnel +RUN chmod 0755 /usr/local/bin/moonwell-core-tunnel + +ENTRYPOINT ["/usr/local/bin/moonwell-core-tunnel"] diff --git a/docker/ssh-tunnel/start.sh b/docker/ssh-tunnel/start.sh new file mode 100644 index 0000000..9bea664 --- /dev/null +++ b/docker/ssh-tunnel/start.sh @@ -0,0 +1,20 @@ +#!/bin/sh +set -eu + +: "${CORE_SSH_HOST:?Set CORE_SSH_HOST}" +: "${CORE_SSH_USER:?Set CORE_SSH_USER}" + +exec ssh -NT \ + -p "${CORE_SSH_PORT:-22}" \ + -i /run/ssh/id_ed25519 \ + -o BatchMode=yes \ + -o IdentitiesOnly=yes \ + -o StrictHostKeyChecking=yes \ + -o UserKnownHostsFile=/run/ssh/known_hosts \ + -o GlobalKnownHostsFile=/dev/null \ + -o ExitOnForwardFailure=yes \ + -o ServerAliveInterval=30 \ + -o ServerAliveCountMax=3 \ + -o ConnectTimeout=10 \ + -L "0.0.0.0:3306:${CORE_DB_HOST:-127.0.0.1}:${CORE_DB_PORT:-13306}" \ + "${CORE_SSH_USER}@${CORE_SSH_HOST}" diff --git a/docs/ssh-core-tunnel.md b/docs/ssh-core-tunnel.md new file mode 100644 index 0000000..f52afda --- /dev/null +++ b/docs/ssh-core-tunnel.md @@ -0,0 +1,183 @@ +# 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` повторно поверх существующего ключа. Если вход +по SSH-паролю отключён, добавьте содержимое `id_ed25519.pub` в +`~/.ssh/authorized_keys` выбранного пользователя через существующий доступ +и выполните первое подключение вручную с параметром `UserKnownHostsFile` выше. + +На веб-сервере проверьте, что вход теперь работает без пароля: + +```bash +chmod 600 /etc/moonwell/ssh-tunnel/id_ed25519 /etc/moonwell/ssh-tunnel/known_hosts +ssh -p "$CORE_PORT" \ + -i /etc/moonwell/ssh-tunnel/id_ed25519 \ + -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=yes \ + -o UserKnownHostsFile=/etc/moonwell/ssh-tunnel/known_hosts \ + "$CORE_USER@$CORE_HOST" true +``` + +## 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. Запуск на веб-сервере + +Из папки `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), +[Docker published ports](https://docs.docker.com/engine/network/port-publishing/).