From 72e95db811c9f4507f870e5eeba102b9f5843197 Mon Sep 17 00:00:00 2001 From: "Andrew K. Choi" Date: Mon, 17 Nov 2025 06:44:43 +0900 Subject: [PATCH] feat: add bot control script to prevent multiple instances - Add bot_control.sh script for safe bot management - Prevent 'Conflict: terminated by other getUpdates' error - Add Makefile commands: bot-start, bot-stop, bot-restart, bot-status, bot-logs - Add BOT_MANAGEMENT.md with usage instructions - Use PID file to track single bot instance - Auto-stop all old processes before starting - Add .bot.pid to .gitignore Fixes issue where multiple bot instances cause command processing failures --- .gitignore | 2 +- BOT_MANAGEMENT.md | 151 ++++++++++++++++++++++++++++++++++++++++++++++ Makefile | 16 +++++ bot_control.sh | 125 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 BOT_MANAGEMENT.md create mode 100755 bot_control.sh diff --git a/.gitignore b/.gitignore index 238abb5..8cf6833 100644 --- a/.gitignore +++ b/.gitignore @@ -57,4 +57,4 @@ venv.bak/ # Системные файлы .DS_Store -Thumbs.db \ No newline at end of file +Thumbs.db.bot.pid diff --git a/BOT_MANAGEMENT.md b/BOT_MANAGEMENT.md new file mode 100644 index 0000000..f3dbc7b --- /dev/null +++ b/BOT_MANAGEMENT.md @@ -0,0 +1,151 @@ +# 🤖 Управление ботом + +## Проблема множественных экземпляров + +Если бот перестал реагировать на команды и в логах появляются ошибки: +``` +ERROR - TelegramConflictError: Conflict: terminated by other getUpdates request +``` + +Это означает, что запущено **несколько экземпляров бота одновременно**, и они конфликтуют друг с другом. + +## Решение + +Используйте скрипт `bot_control.sh` для управления ботом: + +### Команды управления через Makefile + +```bash +# Запустить бота (остановит все старые процессы) +make bot-start + +# Остановить бота +make bot-stop + +# Перезапустить бота +make bot-restart + +# Проверить статус бота +make bot-status + +# Показать логи бота в реальном времени +make bot-logs +``` + +### Прямое использование скрипта + +```bash +# Запуск +./bot_control.sh start + +# Остановка +./bot_control.sh stop + +# Перезапуск +./bot_control.sh restart + +# Статус +./bot_control.sh status + +# Логи +./bot_control.sh logs +``` + +## Что делает скрипт? + +1. **bot-start**: + - Проверяет, не запущен ли уже бот + - Останавливает все старые процессы `python main.py` + - Запускает ТОЛЬКО ОДИН экземпляр бота + - Создает PID-файл для отслеживания процесса + +2. **bot-stop**: + - Корректно останавливает бот (SIGTERM, затем SIGKILL) + - Удаляет PID-файл + - Проверяет что все процессы остановлены + +3. **bot-restart**: + - Останавливает бота + - Запускает заново + +4. **bot-status**: + - Показывает состояние бота (работает/не работает) + - Показывает PID и использование ресурсов + - Проверяет логи на ошибки конфликта + - Предупреждает если найдено несколько процессов + +5. **bot-logs**: + - Показывает логи бота в реальном времени + - Нажмите Ctrl+C для выхода + +## Файлы + +- **bot_control.sh** - скрипт управления ботом +- **.bot.pid** - файл с PID текущего процесса бота +- **/tmp/bot_single.log** - логи бота + +## Диагностика проблем + +### Проверить сколько процессов запущено: + +```bash +ps aux | grep "python main.py" | grep -v grep +``` + +Должна быть **только одна строка**. Если больше - используйте `make bot-restart`. + +### Проверить логи на ошибки: + +```bash +tail -n 100 /tmp/bot_single.log | grep "ERROR" +``` + +### Остановить ВСЕ процессы бота вручную: + +```bash +pkill -9 -f "python main.py" +``` + +Затем запустите через `make bot-start`. + +## ⚠️ Важно + +- **НЕ используйте** `make run` для продакшена - он не контролирует множественные запуски +- **ВСЕГДА используйте** `make bot-start` или `./bot_control.sh start` +- Перед запуском нового экземпляра **всегда проверяйте** статус: `make bot-status` + +## Автозапуск при загрузке системы (опционально) + +Если нужно автоматически запускать бота при загрузке сервера: + +```bash +# Создать systemd service +sudo nano /etc/systemd/system/lottery-bot.service +``` + +Содержимое файла: +```ini +[Unit] +Description=Lottery Bot +After=network.target postgresql.service + +[Service] +Type=simple +User=trevor +WorkingDirectory=/home/trevor/new_lottery_bot +ExecStart=/home/trevor/new_lottery_bot/bot_control.sh start +ExecStop=/home/trevor/new_lottery_bot/bot_control.sh stop +Restart=on-failure +RestartSec=10 + +[Install] +WantedBy=multi-user.target +``` + +Активация: +```bash +sudo systemctl daemon-reload +sudo systemctl enable lottery-bot +sudo systemctl start lottery-bot +sudo systemctl status lottery-bot +``` diff --git a/Makefile b/Makefile index 57f243a..3167fd9 100644 --- a/Makefile +++ b/Makefile @@ -68,6 +68,22 @@ run: @echo "🚀 Запуск бота..." . .venv/bin/activate && python main.py +# Управление ботом через скрипт (безопасный запуск одного экземпляра) +bot-start: + @./bot_control.sh start + +bot-stop: + @./bot_control.sh stop + +bot-restart: + @./bot_control.sh restart + +bot-status: + @./bot_control.sh status + +bot-logs: + @./bot_control.sh logs + # Создание миграции migration: @echo "📄 Создание новой миграции..." diff --git a/bot_control.sh b/bot_control.sh new file mode 100755 index 0000000..a8afaef --- /dev/null +++ b/bot_control.sh @@ -0,0 +1,125 @@ +#!/bin/bash +# Скрипт для управления ботом (запуск/остановка/перезапуск) + +BOT_DIR="/home/trevor/new_lottery_bot" +LOG_FILE="/tmp/bot_single.log" +PID_FILE="$BOT_DIR/.bot.pid" + +case "$1" in + start) + echo "🚀 Запуск бота..." + cd "$BOT_DIR" + + # Проверяем не запущен ли уже + if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if ps -p "$PID" > /dev/null 2>&1; then + echo "⚠️ Бот уже запущен (PID: $PID)" + exit 1 + fi + fi + + # Останавливаем все старые процессы + pkill -9 -f "python main.py" 2>/dev/null + sleep 2 + + # Запускаем бота + . .venv/bin/activate + nohup python main.py > "$LOG_FILE" 2>&1 & + NEW_PID=$! + echo $NEW_PID > "$PID_FILE" + + sleep 3 + if ps -p $NEW_PID > /dev/null; then + echo "✅ Бот запущен (PID: $NEW_PID)" + echo "📋 Логи: tail -f $LOG_FILE" + else + echo "❌ Не удалось запустить бота" + rm -f "$PID_FILE" + exit 1 + fi + ;; + + stop) + echo "🛑 Остановка бота..." + if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if ps -p "$PID" > /dev/null 2>&1; then + kill -15 "$PID" + sleep 2 + if ps -p "$PID" > /dev/null 2>&1; then + kill -9 "$PID" + fi + echo "✅ Бот остановлен" + else + echo "⚠️ Процесс не найден" + fi + rm -f "$PID_FILE" + else + # Останавливаем все процессы python main.py на всякий случай + pkill -9 -f "python main.py" 2>/dev/null + echo "✅ Все процессы остановлены" + fi + ;; + + restart) + echo "🔄 Перезапуск бота..." + $0 stop + sleep 2 + $0 start + ;; + + status) + if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if ps -p "$PID" > /dev/null 2>&1; then + echo "✅ Бот работает (PID: $PID)" + echo "📊 Статистика процесса:" + ps aux | grep "$PID" | grep -v grep + + # Проверяем последние ошибки + if grep -q "ERROR.*Conflict" "$LOG_FILE" 2>/dev/null; then + echo "⚠️ В логах обнаружены ошибки конфликта!" + echo "Последние ошибки:" + tail -n 100 "$LOG_FILE" | grep "ERROR.*Conflict" | tail -3 + else + echo "✅ Ошибок конфликта не обнаружено" + fi + else + echo "❌ Бот не работает (PID файл существует, но процесс не найден)" + rm -f "$PID_FILE" + fi + else + # Проверяем запущенные процессы + COUNT=$(ps aux | grep "python main.py" | grep -v grep | wc -l) + if [ "$COUNT" -gt 0 ]; then + echo "⚠️ Найдено $COUNT процессов бота (без PID файла)" + ps aux | grep "python main.py" | grep -v grep + else + echo "❌ Бот не запущен" + fi + fi + ;; + + logs) + if [ -f "$LOG_FILE" ]; then + tail -f "$LOG_FILE" + else + echo "❌ Файл логов не найден: $LOG_FILE" + fi + ;; + + *) + echo "Использование: $0 {start|stop|restart|status|logs}" + echo "" + echo "Команды:" + echo " start - Запустить бота" + echo " stop - Остановить бота" + echo " restart - Перезапустить бота" + echo " status - Проверить статус бота" + echo " logs - Показать логи бота (Ctrl+C для выхода)" + exit 1 + ;; +esac + +exit 0