commit a4a3a2aa098ca1dcc648e89d5849ec01c5c16f26 Author: test Date: Mon May 18 12:42:00 2026 +0300 Add Docker Compose stack for L2 Essence 542 with PostgreSQL 17 and install scripts. diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4303144 --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# Скопируйте в .env (install.sh создаст .env сам). +# В паролях для docker compose символ $ нужно удваивать: pa$$word +POSTGRES_USER=l2user +POSTGRES_PASSWORD=change_me_strong_password +POSTGRES_DB=l2essence +POSTGRES_PORT=5432 + +# Образ игрового сервера (когда будет свой реестр/локальная сборка) +# L2_SERVER_IMAGE=your-registry/l2-essence-542:latest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..474fc89 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.env +*.log +.DS_Store +Thumbs.db diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..594621d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,44 @@ +# Lineage 2 Essence 542 (Samurai Crow) — локальная среда +# Замените сервис l2-server на свой образ/сборку, когда будет готов Dockerfile. + +services: + postgres: + image: postgres:17-alpine + container_name: l2_postgres + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER:-l2user} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env} + POSTGRES_DB: ${POSTGRES_DB:-l2essence} + PGDATA: /var/lib/postgresql/data/pgdata + ports: + - "${POSTGRES_PORT:-5432}:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./docker/initdb:/docker-entrypoint-initdb.d:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-l2user} -d ${POSTGRES_DB:-l2essence}"] + interval: 5s + timeout: 5s + retries: 10 + + # Заглушка: подключите свой сервер (Auth/Game) или замените build: на реальный Dockerfile. + l2-server: + image: ${L2_SERVER_IMAGE:-ubuntu:24.04} + container_name: l2_server_placeholder + restart: "no" + depends_on: + postgres: + condition: service_healthy + environment: + DATABASE_URL: postgresql://${POSTGRES_USER:-l2user}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-l2essence} + # Раскомментируйте при готовности бинарей/скриптов: + # volumes: + # - ./server:/opt/l2server:ro + # command: ["/opt/l2server/start.sh"] + command: ["bash", "-lc", "echo 'L2 server placeholder: замените сервис l2-server в docker-compose.yml'; sleep infinity"] + profiles: + - placeholder + +volumes: + postgres_data: diff --git a/docker/initdb/00_extensions.sql b/docker/initdb/00_extensions.sql new file mode 100644 index 0000000..17a2e71 --- /dev/null +++ b/docker/initdb/00_extensions.sql @@ -0,0 +1,2 @@ +-- Расширения для init-скриптов (опционально). +CREATE EXTENSION IF NOT EXISTS pgcrypto; diff --git a/docker/initdb/01_login_minimal.sql b/docker/initdb/01_login_minimal.sql new file mode 100644 index 0000000..08f0db5 --- /dev/null +++ b/docker/initdb/01_login_minimal.sql @@ -0,0 +1,49 @@ +-- Минимальные таблицы под логин-сервер в стиле L2J (accounts / gameservers / account_data). +-- Подходит для многих форков; у Samurai Crow/Essence могут отличаться имена колонок или хеш пароля — +-- тогда сверьте с SQL из вашей сборки и поправьте этот файл. +-- +-- Тестовый логин: admin / admin +-- Хеш: Base64(SHA1 от UTF-8 строки «admin») — типично для L2J/Mobius. +-- Альтернатива (если сборка ждёт hex SHA1): замените поле password на +-- d033e22ae348aeb5660fc2140aec35850c4da997 + +CREATE TABLE IF NOT EXISTS accounts ( + login VARCHAR(45) NOT NULL PRIMARY KEY, + password VARCHAR(128), + lastactive BIGINT, + accesslevel INTEGER NOT NULL DEFAULT 0, + lastip VARCHAR(45), + lastserver SMALLINT DEFAULT 1 +); + +CREATE TABLE IF NOT EXISTS account_data ( + account_name VARCHAR(45) NOT NULL, + var VARCHAR(50) NOT NULL, + value VARCHAR(255), + PRIMARY KEY (account_name, var), + CONSTRAINT fk_account_data_account FOREIGN KEY (account_name) + REFERENCES accounts (login) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS gameservers ( + hexid VARCHAR(255) NOT NULL, + server_id INTEGER NOT NULL PRIMARY KEY, + host VARCHAR(255) NOT NULL +); + +INSERT INTO accounts (login, password, lastactive, accesslevel, lastip, lastserver) +VALUES ( + 'admin', + '0DPiKuNIrrVmD8IUCuw1hQxNqZc=', + (EXTRACT(EPOCH FROM NOW()) * 1000)::bigint, + 0, + '127.0.0.1', + 1 +) +ON CONFLICT (login) DO NOTHING; + +-- Игровой сервер при первом подключении к логину часто сам делает INSERT в gameservers. +-- Если нужна явная запись (hexid должен совпасть с RequestServerID / hex в конфиге GS): +-- INSERT INTO gameservers (hexid, server_id, host) +-- VALUES ('ВАШ_32_СИМВОЛА_HEX', 1, '127.0.0.1') +-- ON CONFLICT (server_id) DO UPDATE SET hexid = EXCLUDED.hexid, host = EXCLUDED.host; diff --git a/docker/initdb/02_grant_app_user.sh b/docker/initdb/02_grant_app_user.sh new file mode 100644 index 0000000..7940f5d --- /dev/null +++ b/docker/initdb/02_grant_app_user.sh @@ -0,0 +1,8 @@ +#!/bin/sh +# Выдаёт владельца таблиц логина роли из POSTGRES_USER (см. docker-compose). +set -eu +psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "$POSTGRES_DB" <<-EOSQL +ALTER TABLE IF EXISTS accounts OWNER TO "${POSTGRES_USER}"; +ALTER TABLE IF EXISTS account_data OWNER TO "${POSTGRES_USER}"; +ALTER TABLE IF EXISTS gameservers OWNER TO "${POSTGRES_USER}"; +EOSQL diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..bffd6de --- /dev/null +++ b/install.sh @@ -0,0 +1,196 @@ +#!/usr/bin/env bash +# Интерактивный установщик: Ubuntu + Docker + PostgreSQL 17 (docker compose). +# Запуск: chmod +x install.sh && ./install.sh + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +ask_yes_no() { + local prompt="$1" + local default="${2:-n}" + local hint="[y/N]" + [[ "$default" == "y" ]] && hint="[Y/n]" + while true; do + read -r -p "$prompt $hint: " ans + ans="${ans:-$default}" + case "$ans" in + [Yy]|[Yy][Ee][Ss]) return 0 ;; + [Nn]|[Nn][Oo]) return 1 ;; + *) echo "Введите y или n." ;; + esac + done +} + +info() { echo -e "${GREEN}[*]${NC} $*"; } +warn() { echo -e "${YELLOW}[!]${NC} $*"; } +err() { echo -e "${RED}[x]${NC} $*" >&2; } + +step_header() { + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo " $1" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +} + +require_cmd() { + command -v "$1" >/dev/null 2>&1 +} + +read_env_val() { + local key="$1" + grep -m1 "^${key}=" "$ENV_FILE" | sed "s/^${key}=//" +} + +# --- Шаг 0: приветствие --- +step_header "Шаг 0 — Обзор" +echo "Проект: Lineage 2 Essence 542 (Samurai Crow), окружение в Docker." +echo "Будет поднят PostgreSQL 17; игровой сервис — по профилю placeholder (заглушка), пока нет своего образа." +if ! ask_yes_no "Продолжить?" "y"; then + info "Выход." + exit 0 +fi + +# --- Шаг 1: проверка Ubuntu (мягко) --- +step_header "Шаг 1 — Система" +if [[ -f /etc/os-release ]]; then + # shellcheck source=/dev/null + source /etc/os-release + if [[ "${ID:-}" != "ubuntu" ]]; then + warn "Обнаружено не Ubuntu (${PRETTY_NAME:-unknown}). Скрипт рассчитан на Ubuntu; продолжайте на свой риск." + ask_yes_no "Всё равно продолжить?" "n" || exit 1 + else + info "ОС: ${PRETTY_NAME:-Ubuntu}" + fi +else + warn "Не удалось прочитать /etc/os-release." +fi + +# --- Шаг 2: Docker --- +step_header "Шаг 2 — Docker Engine и Compose" +if require_cmd docker && docker compose version >/dev/null 2>&1; then + info "Docker и 'docker compose' уже доступны." +else + if ask_yes_no "Установить Docker из официального репозитория Docker (apt)?" "y"; then + if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then + warn "Для установки пакетов нужны права sudo." + SUDO="sudo" + else + SUDO="" + fi + $SUDO apt-get update -y + $SUDO apt-get install -y ca-certificates curl + $SUDO install -m 0755 -d /etc/apt/keyrings + $SUDO curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc + $SUDO chmod a+r /etc/apt/keyrings/docker.asc + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ +$(. /etc/os-release && echo "${VERSION_CODENAME:-jammy}") stable" | $SUDO tee /etc/apt/sources.list.d/docker.list >/dev/null + $SUDO apt-get update -y + $SUDO apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + info "Docker установлен." + if [[ "${EUID:-$(id -u)}" -ne 0 ]] && ask_yes_no "Добавить текущего пользователя $(whoami) в группу docker?" "y"; then + $SUDO usermod -aG docker "$(whoami)" + warn "Выйдите из сессии и войдите снова (или newgrp docker), чтобы использовать docker без sudo." + fi + else + err "Без Docker дальнейшие шаги невозможны." + exit 1 + fi +fi + +# Проверка доступа к docker +if ! docker info >/dev/null 2>&1; then + err "Docker недоступен (возможно нужен sudo или перелогин после добавления в группу docker)." + exit 1 +fi + +# --- Шаг 3: .env --- +step_header "Шаг 3 — Файл .env (PostgreSQL 17)" +ENV_FILE="$SCRIPT_DIR/.env" +EXAMPLE="$SCRIPT_DIR/.env.example" +if [[ ! -f "$EXAMPLE" ]]; then + err "Нет файла .env.example" + exit 1 +fi + +if [[ -f "$ENV_FILE" ]]; then + if ! ask_yes_no ".env уже есть. Перезаписать значениями из мастера?" "n"; then + info "Оставляем существующий .env" + else + rm -f "$ENV_FILE" + fi +fi + +if [[ ! -f "$ENV_FILE" ]]; then + info "Создаём .env. Введите параметры БД (Enter — значение по умолчанию в скобках)." + read -r -p "POSTGRES_USER [l2user]: " u + u="${u:-l2user}" + while true; do + read -r -s -p "POSTGRES_PASSWORD (обязательно): " p + echo "" + [[ -n "$p" ]] && break + err "Пароль не может быть пустым." + done + read -r -p "POSTGRES_DB [l2essence]: " d + d="${d:-l2essence}" + read -r -p "POSTGRES_PORT [5432]: " port + port="${port:-5432}" + if [[ "$p" == "change_me_strong_password" ]]; then + err "Смените пароль по умолчанию." + exit 1 + fi + { + printf '%s\n' "POSTGRES_USER=$u" "POSTGRES_PASSWORD=$p" "POSTGRES_DB=$d" "POSTGRES_PORT=$port" + echo '# L2_SERVER_IMAGE=your-registry/l2-essence-542:latest' + } >"$ENV_FILE" +fi + +# --- Шаг 4: pull --- +step_header "Шаг 4 — Загрузка образов" +if ask_yes_no "Выполнить docker compose pull?" "y"; then + docker compose pull +fi + +# --- Шаг 5: запуск Postgres --- +step_header "Шаг 5 — Запуск PostgreSQL 17" +if ask_yes_no "Поднять только PostgreSQL (рекомендуется)?" "y"; then + chmod +x "$SCRIPT_DIR"/docker/initdb/*.sh 2>/dev/null || true + docker compose up -d postgres + info "Ожидание готовности PostgreSQL (pg_isready)..." + EV_USER="$(read_env_val POSTGRES_USER)" + EV_DB="$(read_env_val POSTGRES_DB)" + for _ in $(seq 1 60); do + if docker compose exec -T postgres pg_isready -U "$EV_USER" -d "$EV_DB" >/dev/null 2>&1; then + break + fi + sleep 1 + done + docker compose ps +fi + +# --- Шаг 6: заглушка L2 --- +step_header "Шаг 6 — Игровой контейнер (заглушка)" +warn "Сервис l2-server в compose помечен profile 'placeholder'. Замените образ/команду под Samurai Crow." +if ask_yes_no "Запустить контейнер-заглушку l2-server (для проверки сети к БД)?" "n"; then + docker compose --profile placeholder up -d l2-server + docker compose ps +fi + +# --- Готово --- +step_header "Готово" +EV_USER="$(read_env_val POSTGRES_USER)" +EV_DB="$(read_env_val POSTGRES_DB)" +EV_PORT="$(read_env_val POSTGRES_PORT)" +info "Строка подключения с хоста Ubuntu: postgresql://${EV_USER}:***@127.0.0.1:${EV_PORT}/${EV_DB}" +info "Из контейнеров в этой compose-сети: хост postgres, порт 5432." +echo "" +info "Полезные команды:" +echo " docker compose logs -f postgres" +echo " docker compose down" +echo " docker compose --profile placeholder down" diff --git a/manage.sh b/manage.sh new file mode 100644 index 0000000..5dc5817 --- /dev/null +++ b/manage.sh @@ -0,0 +1,193 @@ +#!/usr/bin/env bash +# Интерактивное управление: остановка, запуск, перезапуск Docker-сервисов проекта. +# Запуск: chmod +x manage.sh && ./manage.sh + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +ENV_FILE="$SCRIPT_DIR/.env" +PROFILE_PLACEHOLDER=(--profile placeholder) +# Имена из docker-compose.yml (для надёжной проверки состояния) +PG_CONTAINER="l2_postgres" +L2_CONTAINER="l2_server_placeholder" + +read_env_val() { + local key="$1" + grep -m1 "^${key}=" "$ENV_FILE" | sed "s/^${key}=//" +} + +ask_yes_no() { + local prompt="$1" + local default="${2:-n}" + local hint="[y/N]" + [[ "$default" == "y" ]] && hint="[Y/n]" + while true; do + read -r -p "$prompt $hint: " ans + ans="${ans:-$default}" + case "$ans" in + [Yy]|[Yy][Ee][Ss]) return 0 ;; + [Nn]|[Nn][Oo]) return 1 ;; + *) echo "Введите y или n." ;; + esac + done +} + +info() { echo -e "${GREEN}[*]${NC} $*"; } +warn() { echo -e "${YELLOW}[!]${NC} $*"; } +err() { echo -e "${RED}[x]${NC} $*" >&2; } + +step_header() { + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo " $1" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +} + +require_cmd() { + command -v "$1" >/dev/null 2>&1 +} + +ensure_docker() { + if ! require_cmd docker || ! docker compose version >/dev/null 2>&1; then + err "Нужны Docker и плагин «docker compose». Установите через ./install.sh" + exit 1 + fi + if ! docker info >/dev/null 2>&1; then + err "Docker недоступен (sudo или перелогин после группы docker)." + exit 1 + fi +} + +ensure_env() { + if [[ ! -f "$ENV_FILE" ]]; then + err "Нет файла .env. Сначала выполните ./install.sh" + exit 1 + fi +} + +postgres_running() { + [[ "$(docker inspect -f '{{.State.Running}}' "$PG_CONTAINER" 2>/dev/null || echo false)" == "true" ]] +} + +l2_running() { + [[ "$(docker inspect -f '{{.State.Running}}' "$L2_CONTAINER" 2>/dev/null || echo false)" == "true" ]] +} + +# --- Шаг 0 --- +step_header "Шаг 0 — Управление сервером (Docker Compose)" +echo "Проект: PostgreSQL 17 + опционально l2-server (profile placeholder)." +if ! ask_yes_no "Продолжить?" "y"; then + info "Выход." + exit 0 +fi + +ensure_docker +ensure_env + +# --- Шаг 1: статус --- +step_header "Шаг 1 — Текущий статус" +if ask_yes_no "Показать docker compose ps?" "y"; then + docker compose "${PROFILE_PLACEHOLDER[@]}" ps -a || true +fi + +# --- Шаг 2: остановка --- +step_header "Шаг 2 — Остановка" +if ask_yes_no "Остановить контейнер l2-server (если был запущен с profile placeholder)?" "n"; then + docker compose "${PROFILE_PLACEHOLDER[@]}" stop l2-server 2>/dev/null || warn "l2-server не остановлен (возможно не запускался)." +fi + +if ask_yes_no "Остановить только PostgreSQL (docker compose stop postgres)?" "n"; then + docker compose stop postgres 2>/dev/null || warn "PostgreSQL уже остановлен или не создавался." +fi + +if ask_yes_no "Полная остановка проекта: docker compose down (все сервисы, сеть; тома БД не удаляются)?" "n"; then + docker compose "${PROFILE_PLACEHOLDER[@]}" down + info "Контейнеры остановлены. Данные БД в томе postgres_data сохранены." +fi + +if ask_yes_no "Удалить том с данными PostgreSQL (docker compose down -v)? ОПАСНО: потеря БД." "n"; then + warn "Будет выполнено: docker compose --profile placeholder down -v" + if ask_yes_no "Точно удалить тома?" "n"; then + docker compose "${PROFILE_PLACEHOLDER[@]}" down -v + info "Тома удалены." + else + info "Отмена удаления томов." + fi +fi + +# --- Шаг 3: запуск --- +step_header "Шаг 3 — Запуск" +if ask_yes_no "Запустить / обновить PostgreSQL (up -d postgres)?" "n"; then + docker compose up -d postgres + info "Ожидание pg_isready..." + EV_USER="$(read_env_val POSTGRES_USER)" + EV_DB="$(read_env_val POSTGRES_DB)" + for _ in $(seq 1 60); do + if docker compose exec -T postgres pg_isready -U "$EV_USER" -d "$EV_DB" >/dev/null 2>&1; then + break + fi + sleep 1 + done +fi + +if ask_yes_no "Запустить l2-server (placeholder, нужен здоровый PostgreSQL)?" "n"; then + if ! postgres_running; then + warn "PostgreSQL не запущен. Сначала поднимите postgres." + else + docker compose "${PROFILE_PLACEHOLDER[@]}" up -d l2-server + fi +fi + +# --- Шаг 4: перезапуск --- +step_header "Шаг 4 — Перезапуск" +if ask_yes_no "Перезапустить PostgreSQL (restart postgres)?" "n"; then + if postgres_running; then + docker compose restart postgres + info "PostgreSQL перезапущен." + else + warn "PostgreSQL не в состоянии running. Выполняю up -d postgres." + docker compose up -d postgres + fi +fi + +if ask_yes_no "Перезапустить l2-server (restart l2-server)?" "n"; then + if l2_running; then + docker compose "${PROFILE_PLACEHOLDER[@]}" restart l2-server + info "l2-server перезапущен." + else + warn "l2-server не запущен. Пропуск." + fi +fi + +if ask_yes_no "Перезапустить весь стек: сначала down, затем up postgres (+ опционально l2)?" "n"; then + docker compose "${PROFILE_PLACEHOLDER[@]}" down + docker compose up -d postgres + EV_USER="$(read_env_val POSTGRES_USER)" + EV_DB="$(read_env_val POSTGRES_DB)" + for _ in $(seq 1 60); do + if docker compose exec -T postgres pg_isready -U "$EV_USER" -d "$EV_DB" >/dev/null 2>&1; then + break + fi + sleep 1 + done + if ask_yes_no "Сразу поднять l2-server (placeholder)?" "n"; then + docker compose "${PROFILE_PLACEHOLDER[@]}" up -d l2-server + fi + info "Стек перезапущен." +fi + +# --- Итог --- +step_header "Готово" +if ask_yes_no "Показать итоговый статус?" "y"; then + docker compose "${PROFILE_PLACEHOLDER[@]}" ps -a || true +fi + +info "Логи: docker compose logs -f postgres" +info "Выход из проекта без удаления томов: docker compose --profile placeholder down"