From a0a7a47c8d40709a5d0839fb896ef3ff43b24cdb Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 16 Sep 2025 18:43:05 +0300 Subject: [PATCH] Refactor Docker configuration and improve database initialization - Updated `.dockerignore` to streamline ignored files and directories, focusing on essential components. - Removed obsolete `Dockerfile.bot` to simplify the build process. - Enhanced `run_helper.py` with a new `init_db` function to initialize the SQLite database if it doesn't exist, improving setup reliability. - Removed the `/status` endpoint from `server_prometheus.py` to clean up unused functionality and improve code clarity. --- .dockerignore | 116 +++++++------------------------- Dockerfile | 54 +++++++++++++++ Dockerfile.bot | 76 --------------------- helper_bot/server_prometheus.py | 91 ------------------------- run_helper.py | 48 ++++--------- 5 files changed, 93 insertions(+), 292 deletions(-) create mode 100644 Dockerfile delete mode 100644 Dockerfile.bot diff --git a/.dockerignore b/.dockerignore index 816ea09..0461c29 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,95 +1,29 @@ -# Python -__pycache__/ -*.py[cod] -*$py.class -*.so -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST +# .dockerignore +.* +!.gitignore -# Virtual environments +# Исключаем тяжелые папки +voice_users/ +logs/ +.venv/ +__pycache__ +*.pyc +*.pyo +*.pyd + +# Исключаем файлы БД (они создаются при запуске) +database/*.db +database/*.db-* + +# Служебные файлы +Dockerfile +docker-compose* +README.md .env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ +*.log -# IDE -.vscode/ -.idea/ -*.swp -*.swo -*~ - -# OS -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db - -# Git -.git/ -.gitignore - -# Logs -logs/*.log - -# Database -*.db -*.db-shm -*.db-wal - -# Tests -test_*.py -.pytest_cache/ - -# Documentation -*.md +tests/ +test/ docs/ - -# Docker -Dockerfile* -docker-compose*.yml -.dockerignore - -# Development files -*.sh - -# Stickers and media -Stick/ - -# Temporary files -*.tmp -*.temp -.cache/ - -# Backup files -*.bak -*.backup - -# Environment files -.env* -!.env.example - -# Monitoring configs (will be mounted) -prometheus.yml - +.idea/ +.vscode/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ceeccb5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,54 @@ +########################################### +# Этап 1: Сборщик (Builder) +########################################### +FROM python:3.9-alpine as builder + +# Устанавливаем инструменты для компиляции + linux-headers для psutil +RUN apk add --no-cache \ + gcc \ + g++ \ + musl-dev \ + python3-dev \ + linux-headers # ← ЭТО КРИТИЧЕСКИ ВАЖНО ДЛЯ psutil + +WORKDIR /app +COPY requirements.txt . + +# Устанавливаем зависимости +RUN pip install --no-cache-dir --target /install -r requirements.txt + + +########################################### +# Этап 2: Финальный образ (Runtime) +########################################### +FROM python:3.9-alpine as runtime + +# Минимальные рантайм-зависимости +RUN apk add --no-cache \ + libstdc++ \ + sqlite-libs + +# Создаем пользователя +RUN addgroup -g 1001 deploy && adduser -D -u 1001 -G deploy deploy + +WORKDIR /app + +# Копируем зависимости +COPY --from=builder --chown=1001:1001 /install /usr/local/lib/python3.9/site-packages + +# Создаем структуру папок +RUN mkdir -p database logs voice_users && \ + chown -R 1001:1001 /app + +# Копируем исходный код +COPY --chown=1001:1001 . . + +USER 1001 + +# Healthcheck +HEALTHCHECK --interval=30s --timeout=15s --start-period=10s --retries=5 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/health', timeout=5)" || exit 1 + +EXPOSE 8080 + +CMD ["python", "-u", "run_helper.py"] \ No newline at end of file diff --git a/Dockerfile.bot b/Dockerfile.bot deleted file mode 100644 index eaa67a0..0000000 --- a/Dockerfile.bot +++ /dev/null @@ -1,76 +0,0 @@ -# Multi-stage build for production -FROM python:3.9-slim as builder - -# Install build dependencies -RUN apt-get update && apt-get install -y \ - gcc \ - g++ \ - && rm -rf /var/lib/apt/lists/* - -# Create virtual environment -RUN python -m venv /opt/venv -ENV PATH="/opt/venv/bin:$PATH" - -# Copy and install requirements -COPY requirements.txt . -RUN pip install --no-cache-dir --upgrade pip && \ - pip install --no-cache-dir -r requirements.txt - -# Production stage -FROM python:3.9-slim - -# Set security options -ENV PYTHONDONTWRITEBYTECODE=1 \ - PYTHONUNBUFFERED=1 \ - PIP_NO_CACHE_DIR=1 \ - PIP_DISABLE_PIP_VERSION_CHECK=1 - -# Install runtime dependencies only -RUN apt-get update && apt-get upgrade -y && apt-get install -y \ - curl \ - sqlite3 \ - ca-certificates \ - && rm -rf /var/lib/apt/lists/* \ - && apt-get clean - -# Create non-root user with fixed UID -RUN groupadd -g 1001 deploy && useradd -u 1001 -g deploy deploy - -# Copy virtual environment from builder -COPY --from=builder /opt/venv /opt/venv -ENV PATH="/opt/venv/bin:$PATH" -RUN chown -R 1001:1001 /opt/venv - -# Create app directory and set permissions -WORKDIR /app -RUN mkdir -p /app/database /app/logs /app/voice_users && \ - chown -R 1001:1001 /app - -# Copy application code -COPY --chown=1001:1001 . . - -# Initialize SQLite database with schema -RUN sqlite3 /app/database/tg-bot-database.db < /app/database/schema.sql && \ - chown 1001:1001 /app/database/tg-bot-database.db && \ - chmod 644 /app/database/tg-bot-database.db - -# Switch to non-root user -USER deploy - -# Health check with better timeout handling -HEALTHCHECK --interval=30s --timeout=15s --start-period=10s --retries=5 \ - CMD curl -f --connect-timeout 5 --max-time 10 http://localhost:8080/health || exit 1 - -# Expose metrics port -EXPOSE 8080 - -# Graceful shutdown with longer timeout -STOPSIGNAL SIGTERM - -# Set environment variables for better network stability -ENV PYTHONUNBUFFERED=1 \ - PYTHONDONTWRITEBYTECODE=1 \ - PYTHONHASHSEED=random - -# Run application with proper signal handling -CMD ["python", "-u", "run_helper.py"] diff --git a/helper_bot/server_prometheus.py b/helper_bot/server_prometheus.py index 52bf192..8c427aa 100644 --- a/helper_bot/server_prometheus.py +++ b/helper_bot/server_prometheus.py @@ -31,7 +31,6 @@ class MetricsServer: # Настраиваем роуты self.app.router.add_get('/metrics', self.metrics_handler) self.app.router.add_get('/health', self.health_handler) - self.app.router.add_get('/status', self.status_handler) async def metrics_handler(self, request: web.Request) -> web.Response: """Handle /metrics endpoint for Prometheus scraping.""" @@ -103,95 +102,6 @@ class MetricsServer: status=500 ) - async def status_handler(self, request: web.Request) -> web.Response: - """Handle /status endpoint for process status information.""" - try: - import os - import time - import psutil - - # Получаем PID текущего процесса - current_pid = os.getpid() - - try: - # Получаем информацию о процессе - process = psutil.Process(current_pid) - create_time = process.create_time() - uptime_seconds = time.time() - create_time - - # Логируем для диагностики - import datetime - create_time_str = datetime.datetime.fromtimestamp(create_time).strftime('%Y-%m-%d %H:%M:%S') - current_time_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') - logger.info(f"Process PID {current_pid}: created at {create_time_str}, current time {current_time_str}, uptime {uptime_seconds:.1f}s") - - # Форматируем uptime - if uptime_seconds < 60: - uptime_str = f"{int(uptime_seconds)}с" - elif uptime_seconds < 3600: - minutes = int(uptime_seconds // 60) - uptime_str = f"{minutes}м" - elif uptime_seconds < 86400: - hours = int(uptime_seconds // 3600) - minutes = int((uptime_seconds % 3600) // 60) - uptime_str = f"{hours}ч {minutes}м" - else: - days = int(uptime_seconds // 86400) - hours = int((uptime_seconds % 86400) // 3600) - uptime_str = f"{days}д {hours}ч" - - # Проверяем, что процесс активен - if process.is_running(): - status = "running" - else: - status = "stopped" - - # Формируем ответ - response_data = { - "status": status, - "pid": current_pid, - "uptime": uptime_str, - "memory_usage_mb": round(process.memory_info().rss / 1024 / 1024, 2), - "cpu_percent": process.cpu_percent(), - "timestamp": time.time() - } - - import json - return web.Response( - text=json.dumps(response_data, ensure_ascii=False), - content_type='application/json', - status=200 - ) - - except psutil.NoSuchProcess: - # Процесс не найден - response_data = { - "status": "not_found", - "error": "Process not found", - "timestamp": time.time() - } - - import json - return web.Response( - text=json.dumps(response_data, ensure_ascii=False), - content_type='application/json', - status=404 - ) - - except Exception as e: - logger.error(f"Status check failed: {e}") - import json - response_data = { - "status": "error", - "error": str(e), - "timestamp": time.time() - } - - return web.Response( - text=json.dumps(response_data, ensure_ascii=False), - content_type='application/json', - status=500 - ) async def start(self) -> None: """Start the HTTP server.""" @@ -206,7 +116,6 @@ class MetricsServer: logger.info("Available endpoints:") logger.info(f" - /metrics - Prometheus metrics") logger.info(f" - /health - Health check") - logger.info(f" - /status - Process status") except Exception as e: logger.error(f"Failed to start metrics server: {e}") diff --git a/run_helper.py b/run_helper.py index 56d4edb..b58b847 100644 --- a/run_helper.py +++ b/run_helper.py @@ -2,6 +2,7 @@ import asyncio import os import sys import signal +import sqlite3 # Ensure project root is on sys.path for module resolution CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -13,41 +14,10 @@ from helper_bot.utils.base_dependency_factory import get_global_instance from helper_bot.utils.auto_unban_scheduler import get_auto_unban_scheduler from logs.custom_logger import logger -# Импортируем PID менеджер из инфраструктуры (если доступен) -import sys -import os - -def get_pid_manager(): - """Получение PID менеджера из инфраструктуры проекта""" - try: - # Пытаемся импортировать из инфраструктуры проекта - infra_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'infra', 'monitoring') - if infra_path not in sys.path: - sys.path.insert(0, infra_path) - - from pid_manager import get_bot_pid_manager - return get_bot_pid_manager - - except ImportError: - # В изолированном запуске PID менеджер не нужен - logger.info("PID менеджер недоступен (изолированный запуск), PID файл не создается") - return None - -# Получаем функцию создания PID менеджера -get_bot_pid_manager = get_pid_manager() async def main(): """Основная функция запуска""" - # Создаем PID менеджер для отслеживания процесса (если доступен) - pid_manager = None - if get_bot_pid_manager: - pid_manager = get_bot_pid_manager("helper_bot") - if not pid_manager.create_pid_file(): - logger.error("Не удалось создать PID файл, завершаем работу") - return - else: - logger.info("PID менеджер недоступен, запуск без PID файла") bdf = get_global_instance() @@ -111,9 +81,6 @@ async def main(): # Отменяем задачу бота bot_task.cancel() - # Очищаем PID файл (если PID менеджер доступен) - if pid_manager: - pid_manager.cleanup_pid_file() # Ждем завершения задачи бота и получаем результат main bot try: @@ -145,9 +112,22 @@ async def main(): logger.info("Бот корректно остановлен") +def init_db(): + db_path = '/app/database/tg-bot-database.db' + schema_path = '/app/database/schema.sql' + + if not os.path.exists(db_path): + print("Initializing database...") + with open(schema_path, 'r') as f: + schema = f.read() + + with sqlite3.connect(db_path) as conn: + conn.executescript(schema) + print("Database initialized successfully") if __name__ == '__main__': try: + init_db() asyncio.run(main()) except AttributeError: # Fallback for Python 3.6-3.7