From 6d7d59d2ae2f624b97e8cc27e4fced9453ca5c65 Mon Sep 17 00:00:00 2001 From: poignatov Date: Sat, 3 Jan 2026 17:08:42 +0300 Subject: [PATCH] Add init/run scripts and Cursor/VSCode configurations - Add init.sh: initializes app with Docker, creates prod backup, restores to local DB - Add run.sh: restarts all containers - Update restore-db.sh: auto-selects latest dump, terminates active connections before restore - Add .cursor/commands.json: Cursor commands (init, run, backupFromProd, restoreToLocal) - Add .vscode/tasks.json: VSCode tasks for running scripts - Add .vscode/launch.json: launch configurations for restarting server - Remove play-life-backend/env.example (unified .env in root) --- .cursor/commands.json | 33 +++++++ .vscode/launch.json | 28 ++++++ .vscode/tasks.json | 82 +++++++++++++++++ init.sh | 160 ++++++++++++++++++++++++++++++++++ play-life-backend/env.example | 18 ---- restore-db.sh | 49 +++++++++-- run.sh | 65 ++++++++++++++ 7 files changed, 409 insertions(+), 26 deletions(-) create mode 100644 .cursor/commands.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100755 init.sh delete mode 100644 play-life-backend/env.example create mode 100755 run.sh diff --git a/.cursor/commands.json b/.cursor/commands.json new file mode 100644 index 0000000..d777428 --- /dev/null +++ b/.cursor/commands.json @@ -0,0 +1,33 @@ +{ + "commands": [ + { + "name": "init", + "description": "Инициализация Play Life: остановка контейнеров, поднятие сервисов, создание дампа с продакшена и восстановление в локальную базу", + "command": "./init.sh", + "type": "shell", + "cwd": "${workspaceFolder}" + }, + { + "name": "run", + "description": "Перезапуск Play Life: перезапуск всех контейнеров", + "command": "./run.sh", + "type": "shell", + "cwd": "${workspaceFolder}" + }, + { + "name": "backupFromProd", + "description": "Создание дампа базы данных с продакшена", + "command": "./dump-db.sh", + "type": "shell", + "cwd": "${workspaceFolder}" + }, + { + "name": "restoreToLocal", + "description": "Восстановление базы данных из самого свежего дампа в локальную базу (автоматически выбирает последний дамп)", + "command": "./restore-db.sh", + "type": "shell", + "cwd": "${workspaceFolder}" + } + ] +} + diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..222a929 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,28 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Restart Server", + "type": "node", + "request": "launch", + "runtimeExecutable": "bash", + "runtimeArgs": ["${workspaceFolder}/run.sh"], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "skipFiles": ["/**"] + }, + { + "name": "Init Server", + "type": "node", + "request": "launch", + "runtimeExecutable": "bash", + "runtimeArgs": ["${workspaceFolder}/init.sh"], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "skipFiles": ["/**"] + } + ] +} + diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..82e2630 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,82 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "init", + "type": "shell", + "command": "./init.sh", + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false + }, + "problemMatcher": [], + "detail": "Инициализация Play Life: остановка контейнеров, поднятие сервисов, создание дампа с продакшена и восстановление в локальную базу" + }, + { + "label": "run", + "type": "shell", + "command": "./run.sh", + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false + }, + "problemMatcher": [], + "detail": "Перезапуск Play Life: перезапуск всех контейнеров" + }, + { + "label": "backupFromProd", + "type": "shell", + "command": "./dump-db.sh", + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false + }, + "problemMatcher": [], + "detail": "Создание дампа базы данных с продакшена" + }, + { + "label": "restoreToLocal", + "type": "shell", + "command": "./restore-db.sh", + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false + }, + "problemMatcher": [], + "detail": "Восстановление базы данных из самого свежего дампа в локальную базу (автоматически выбирает последний дамп)" + } + ] +} + diff --git a/init.sh b/init.sh new file mode 100755 index 0000000..6a31379 --- /dev/null +++ b/init.sh @@ -0,0 +1,160 @@ +#!/bin/bash + +# Скрипт для первоначальной настройки и запуска приложения +# Использование: ./init.sh + +set -e + +# Цвета для вывода +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Проверка наличия .env файла +if [ ! -f ".env" ]; then + echo -e "${RED}❌ Файл .env не найден!${NC}" + echo " Создайте файл .env на основе env.example" + exit 1 +fi + +# Загружаем переменные окружения +export $(cat .env | grep -v '^#' | grep -v '^$' | xargs) + +# Значения по умолчанию +DB_USER=${DB_USER:-playeng} +DB_PASSWORD=${DB_PASSWORD:-playeng} +DB_NAME=${DB_NAME:-playeng} +DB_PORT=${DB_PORT:-5432} +PORT=${PORT:-8080} +WEB_PORT=${WEB_PORT:-3001} + +echo -e "${GREEN}🚀 Инициализация Play Life...${NC}" +echo "" + +# 1. Остановка и удаление существующих контейнеров +echo -e "${YELLOW}1. Остановка существующих контейнеров...${NC}" +docker-compose down -v 2>/dev/null || true +echo -e "${GREEN} ✅ Контейнеры остановлены${NC}" + +# Удаляем старые образы postgres, если они есть +echo -e "${YELLOW} Удаление старых образов postgres...${NC}" +docker images | grep -E "postgres:(15|16|17|18|latest)" | awk '{print $3}' | xargs -r docker rmi -f 2>/dev/null || true +echo -e "${GREEN} ✅ Старые образы postgres удалены${NC}" +echo "" + +# 2. Поднятие всех сервисов +echo -e "${YELLOW}2. Поднятие сервисов через Docker Compose...${NC}" +echo " - База данных PostgreSQL 18.0 (порт: $DB_PORT)" +echo " - Backend сервер (порт: $PORT)" +echo " - Frontend приложение (порт: $WEB_PORT)" +docker-compose up -d --build +echo -e "${GREEN} ✅ Сервисы запущены${NC}" +echo "" + +# 3. Ожидание готовности базы данных +echo -e "${YELLOW}3. Ожидание готовности базы данных...${NC}" +MAX_WAIT=60 +WAIT_COUNT=0 +while ! docker-compose exec -T db pg_isready -U "$DB_USER" >/dev/null 2>&1; do + if [ $WAIT_COUNT -ge $MAX_WAIT ]; then + echo -e "${RED} ❌ База данных не готова за $MAX_WAIT секунд${NC}" + exit 1 + fi + echo -n "." + sleep 1 + WAIT_COUNT=$((WAIT_COUNT + 1)) +done +echo "" +echo -e "${GREEN} ✅ База данных готова${NC}" +echo "" + +# 4. Поиск самого свежего дампа +echo -e "${YELLOW}4. Поиск самого свежего дампа...${NC}" +DUMP_DIR="database-dumps" + +if [ ! -d "$DUMP_DIR" ]; then + echo -e "${YELLOW} ⚠️ Директория дампов не найдена, создаём...${NC}" + mkdir -p "$DUMP_DIR" +fi + +# Ищем все дампы (сначала .sql.gz, потом .sql) +LATEST_DUMP=$(ls -t "$DUMP_DIR"/*.{sql.gz,sql} 2>/dev/null | head -n 1) + +if [ -z "$LATEST_DUMP" ]; then + echo -e "${YELLOW} ⚠️ Дампы не найдены${NC}" + echo "" + + # Создаём дамп с продакшена используя креденшелы из .env + echo -e "${YELLOW}5. Создание дампа с продакшена...${NC}" + echo -e "${BLUE} 📦 Используются креденшелы из .env${NC}" + echo " Используется скрипт dump-db.sh" + + if [ -f "./dump-db.sh" ]; then + chmod +x ./dump-db.sh + DUMP_NAME="prod_backup_$(date +%Y%m%d_%H%M%S)" + + # Временно останавливаем контейнер db, чтобы dump-db.sh не использовал docker-compose exec + # и подключился напрямую к продакшен базе по креденшелам из .env + echo -e "${BLUE} ⏸️ Временно останавливаем локальный контейнер db для создания дампа с продакшена...${NC}" + docker-compose stop db 2>/dev/null || true + + # Используем dump-db.sh с креденшелами из .env (по умолчанию) + # Теперь он подключится напрямую к продакшен базе, а не через docker-compose + ./dump-db.sh "$DUMP_NAME" + + # Запускаем контейнер db обратно + echo -e "${BLUE} ▶️ Запускаем локальный контейнер db обратно...${NC}" + docker-compose start db 2>/dev/null || docker-compose up -d db + + # Проверяем, был ли создан дамп + CREATED_DUMP=$(ls -t "$DUMP_DIR"/"$DUMP_NAME".sql.gz 2>/dev/null | head -n 1) + if [ -n "$CREATED_DUMP" ]; then + echo -e "${GREEN} ✅ Дамп с продакшена создан: $(basename "$CREATED_DUMP")${NC}" + LATEST_DUMP="$CREATED_DUMP" + # Продолжаем с восстановлением ниже + else + echo -e "${RED} ❌ Не удалось создать дамп с продакшена${NC}" + echo -e "${YELLOW} ⚠️ Проверьте креденшелы в .env и доступность базы данных${NC}" + exit 1 + fi + else + echo -e "${RED} ❌ Скрипт dump-db.sh не найден${NC}" + exit 1 + fi +fi + +# Если дамп найден или создан, восстанавливаем его +if [ -n "$LATEST_DUMP" ]; then + LATEST_DUMP_NAME=$(basename "$LATEST_DUMP") + echo -e "${GREEN} ✅ Найден дамп: $LATEST_DUMP_NAME${NC}" + echo "" + + # 6. Восстановление базы данных + echo -e "${YELLOW}6. Восстановление базы данных из дампа...${NC}" + echo " Файл: $LATEST_DUMP_NAME" + echo " Используется скрипт restore-db.sh (восстановление в локальную базу)" + + # Используем restore-db.sh, который автоматически восстанавливает в локальную базу при использовании .env + # restore-db.sh автоматически выберет самый свежий дамп, если имя не указано + if [ -f "./restore-db.sh" ]; then + chmod +x ./restore-db.sh + # Автоматически подтверждаем восстановление + # restore-db.sh сам выберет самый свежий дамп из database-dumps/ + echo "yes" | ./restore-db.sh + else + echo -e "${RED} ❌ Скрипт restore-db.sh не найден${NC}" + exit 1 + fi +fi + +echo "" +echo -e "${GREEN}✅ Инициализация завершена!${NC}" +echo "" +echo -e "${BLUE}📋 Статус сервисов:${NC}" +docker-compose ps + diff --git a/play-life-backend/env.example b/play-life-backend/env.example deleted file mode 100644 index fa5a06c..0000000 --- a/play-life-backend/env.example +++ /dev/null @@ -1,18 +0,0 @@ -# Database Configuration -DB_HOST=localhost -DB_PORT=5432 -DB_USER=playeng -DB_PASSWORD=playeng -DB_NAME=playeng - -# Server Configuration -PORT=8080 - -# Telegram Bot Configuration -# Bot Token и Chat ID настраиваются через UI приложения в разделе "Интеграции" -> "Telegram" -# Get token from @BotFather in Telegram: https://t.me/botfather - -# Scheduler Configuration -# Часовой пояс для планировщика (формат IANA: Europe/Moscow, America/New_York и т.д.) -# ВАЖНО: Если не указан, используется UTC! -TIMEZONE=Europe/Moscow diff --git a/restore-db.sh b/restore-db.sh index d4487ff..f833565 100755 --- a/restore-db.sh +++ b/restore-db.sh @@ -47,14 +47,32 @@ DB_USER=${DB_USER:-playeng} DB_PASSWORD=${DB_PASSWORD:-playeng} DB_NAME=${DB_NAME:-playeng} -# Проверяем наличие дампа +# Если используется .env (по умолчанию), всегда восстанавливаем в локальную базу +# Переопределяем DB_HOST для локального подключения +if [ "$ENV_FILE" = "$DEFAULT_ENV_FILE" ] || [ "$ENV_FILE" = ".env" ]; then + DB_HOST=localhost + echo "📋 Восстановление в локальную базу (DB_HOST=localhost, DB_PORT=$DB_PORT)" +fi + +# Если дамп не указан, выбираем самый свежий if [ -z "$DUMP_FILE" ]; then - echo "❌ Ошибка: Укажите имя дампа" - echo "Использование: ./restore-db.sh [--env-file FILE] [имя_дампа.sql.gz]" + DUMP_DIR="database-dumps" + if [ ! -d "$DUMP_DIR" ]; then + echo "❌ Ошибка: Директория дампов не найдена: $DUMP_DIR" + exit 1 + fi + + # Ищем самый свежий дамп + LATEST_DUMP=$(ls -t "$DUMP_DIR"/*.{sql.gz,sql} 2>/dev/null | head -n 1) + + if [ -z "$LATEST_DUMP" ]; then + echo "❌ Ошибка: Дампы не найдены в директории $DUMP_DIR" + exit 1 + fi + + DUMP_FILE=$(basename "$LATEST_DUMP") + echo "📦 Автоматически выбран самый свежий дамп: $DUMP_FILE" echo "" - echo "Доступные дампы:" - ls -lh database-dumps/*.sql.gz 2>/dev/null | awk '{print " " $9}' | sed "s|database-dumps/||g" || echo " (нет дампов)" - exit 1 fi # Определяем полный путь к файлу @@ -86,7 +104,11 @@ if [ ! -f "$FULL_DUMP_PATH" ]; then fi echo "⚠️ ВНИМАНИЕ: Это действие удалит все данные в базе $DB_NAME!" -echo " Хост: $DB_HOST:$DB_PORT" +if [ "$ENV_FILE" = "$DEFAULT_ENV_FILE" ] || [ "$ENV_FILE" = ".env" ]; then + echo " Восстановление в локальную базу: $DB_HOST:$DB_PORT" +else + echo " Хост: $DB_HOST:$DB_PORT" +fi echo " Пользователь: $DB_USER" read -p " Продолжить? (yes/no): " confirm @@ -97,7 +119,11 @@ fi echo "🔄 Восстановление базы данных из дампа..." echo " База: $DB_NAME" -echo " Хост: $DB_HOST:$DB_PORT" +if [ "$ENV_FILE" = "$DEFAULT_ENV_FILE" ] || [ "$ENV_FILE" = ".env" ]; then + echo " Восстановление в локальную базу: $DB_HOST:$DB_PORT" +else + echo " Хост: $DB_HOST:$DB_PORT" +fi echo " Файл: $FULL_DUMP_PATH" # Распаковываем и модифицируем дамп @@ -121,6 +147,9 @@ echo " Владелец таблиц в дампе заменён на: $DB_US # Восстанавливаем через docker-compose, если контейнер запущен if docker-compose ps db 2>/dev/null | grep -q "Up"; then echo " Используется docker-compose..." + # Завершаем все активные подключения к базе данных + echo " Завершение активных подключений к базе $DB_NAME..." + docker-compose exec -T db psql -U "$DB_USER" -d postgres -c "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '$DB_NAME' AND pid <> pg_backend_pid();" 2>/dev/null || true # Очищаем базу и восстанавливаем docker-compose exec -T db psql -U "$DB_USER" -d postgres -c "DROP DATABASE IF EXISTS $DB_NAME;" docker-compose exec -T db psql -U "$DB_USER" -d postgres -c "CREATE DATABASE $DB_NAME;" @@ -128,6 +157,10 @@ if docker-compose ps db 2>/dev/null | grep -q "Up"; then elif command -v psql &> /dev/null; then # Или напрямую через psql echo " Используется локальный psql..." + # Завершаем все активные подключения к базе данных + echo " Завершение активных подключений к базе $DB_NAME..." + PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres -c "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '$DB_NAME' AND pid <> pg_backend_pid();" 2>/dev/null || true + # Очищаем базу и восстанавливаем PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres -c "DROP DATABASE IF EXISTS $DB_NAME;" PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres -c "CREATE DATABASE $DB_NAME;" PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" < "$TEMP_DUMP" diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..1eff938 --- /dev/null +++ b/run.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +# Скрипт для перезапуска уже настроенного приложения +# Использование: ./run.sh + +set -e + +# Цвета для вывода +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Проверка наличия .env файла +if [ ! -f ".env" ]; then + echo -e "${RED}❌ Файл .env не найден!${NC}" + echo " Создайте файл .env на основе env.example" + exit 1 +fi + +# Загружаем переменные окружения +export $(cat .env | grep -v '^#' | grep -v '^$' | xargs) + +# Значения по умолчанию +DB_USER=${DB_USER:-playeng} +DB_PASSWORD=${DB_PASSWORD:-playeng} +DB_NAME=${DB_NAME:-playeng} +DB_PORT=${DB_PORT:-5432} +PORT=${PORT:-8080} +WEB_PORT=${WEB_PORT:-3001} + +echo -e "${GREEN}🔄 Перезапуск Play Life...${NC}" +echo "" + +# Проверяем, запущены ли контейнеры +if docker-compose ps | grep -q "Up"; then + echo -e "${YELLOW}Перезапуск существующих контейнеров...${NC}" + docker-compose restart + echo -e "${GREEN}✅ Контейнеры перезапущены${NC}" +else + echo -e "${YELLOW}Запуск контейнеров...${NC}" + echo " - База данных PostgreSQL 18.0 (порт: $DB_PORT)" + echo " - Backend сервер (порт: $PORT)" + echo " - Frontend приложение (порт: $WEB_PORT)" + docker-compose up -d + echo -e "${GREEN}✅ Контейнеры запущены${NC}" +fi + +echo "" +echo -e "${BLUE}📋 Статус сервисов:${NC}" +docker-compose ps + +echo "" +echo -e "${GREEN}✅ Готово!${NC}" +echo "" +echo -e "${BLUE}ℹ️ Используются креденшелы из .env:${NC}" +echo " - DB_USER: $DB_USER" +echo " - DB_NAME: $DB_NAME" +echo " - DB_PORT: $DB_PORT (внешний порт)" +echo " - Внутри Docker-сети: DB_HOST=db, DB_PORT=5432" +