From 0957d9ef88d64cf0f3106aad76e3400c47594d1f Mon Sep 17 00:00:00 2001 From: Egor Date: Sat, 16 Aug 2025 02:10:19 +0300 Subject: [PATCH] Update bedolaga.sh --- bedolaga.sh | 3170 ++++++++++++++++++++------------------------------- 1 file changed, 1205 insertions(+), 1965 deletions(-) diff --git a/bedolaga.sh b/bedolaga.sh index f182f5c7..f8adcdaf 100644 --- a/bedolaga.sh +++ b/bedolaga.sh @@ -1,726 +1,360 @@ #!/bin/bash -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' +# Bedolaga Bot Installer & Manager +# Автоматический установщик и менеджер для Telegram бота +# Версия: 1.1 -BOT_DIR="/opt/bedolaga-bot" -COMPOSE_FILE="$BOT_DIR/docker-compose.yml" -ENV_FILE="$BOT_DIR/.env" -SERVICE_FILE="/etc/systemd/system/bedolaga-bot.service" +set -euo pipefail -log() { - echo -e "${GREEN}[INFO]${NC} $1" -} +# Цвета для вывода +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[0;33m' +readonly BLUE='\033[0;34m' +readonly PURPLE='\033[0;35m' +readonly CYAN='\033[0;36m' +readonly WHITE='\033[1;37m' +readonly NC='\033[0m' # No Color -warn() { - echo -e "${YELLOW}[WARN]${NC} $1" -} - -error() { - echo -e "${RED}[ERROR]${NC} $1" -} +# Константы +readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly PROJECT_DIR="/opt/bedolaga-bot" +readonly SERVICE_NAME="bedolaga-bot" +readonly DOCKER_COMPOSE_FILE="$PROJECT_DIR/docker-compose.yml" +readonly ENV_FILE="$PROJECT_DIR/.env" +readonly CONFIG_FILE="$PROJECT_DIR/.installer_config" +# Проверка прав root check_root() { - if [ "$EUID" -ne 0 ]; then - error "Этот скрипт должен быть запущен от имени root (используйте sudo)" + if [[ $EUID -ne 0 ]]; then + echo -e "${RED}❌ Этот скрипт должен запускаться от имени root${NC}" + echo "Используйте: sudo $0" exit 1 fi } -check_installation() { - if [ -d "$BOT_DIR" ] && [ -f "$COMPOSE_FILE" ] && [ -f "$ENV_FILE" ]; then - return 0 - else - return 1 +# Вывод заголовка +print_header() { + clear + echo -e "${PURPLE}╔══════════════════════════════════════════════════════════════╗${NC}" + echo -e "${PURPLE}║${NC}${WHITE} BEDOLAGA BOT INSTALLER ${NC}${PURPLE}║${NC}" + echo -e "${PURPLE}║${NC}${CYAN} Автоматический установщик и менеджер ${NC}${PURPLE}║${NC}" + echo -e "${PURPLE}╚══════════════════════════════════════════════════════════════╝${NC}" + echo +} + +# Логирование +log() { + echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" >&2 +} + +warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +# Проверка системы +check_system() { + log "🔍 Проверка системы..." + + if ! command -v lsb_release &> /dev/null; then + error "lsb_release не найден. Установите пакет lsb-release" + exit 1 fi + + local distro=$(lsb_release -si) + local version=$(lsb_release -sr) + + if [[ "$distro" != "Ubuntu" ]]; then + error "Поддерживается только Ubuntu. Найдено: $distro" + exit 1 + fi + + log "✅ Система: $distro $version" } +# Обновление системы update_system() { - log "Обновление системы Ubuntu..." - apt update && apt upgrade -y - log "Система обновлена успешно" + log "🔄 Обновление системы..." + + export DEBIAN_FRONTEND=noninteractive + apt-get update -y + apt-get upgrade -y + apt-get install -y curl wget git jq htop nano vim systemctl + + log "✅ Система обновлена" } +# Установка Docker install_docker() { - log "Установка Docker..." + if command -v docker &> /dev/null; then + log "✅ Docker уже установлен" + return 0 + fi - apt remove -y docker docker-engine docker.io containerd runc + log "🐳 Установка Docker..." - apt install -y apt-transport-https ca-certificates curl gnupg lsb-release nano + # Установка зависимостей + apt-get install -y \ + apt-transport-https \ + ca-certificates \ + curl \ + gnupg \ + lsb-release + # Добавление GPG ключа Docker curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg + # Добавление репозитория echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null - apt update - apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin + # Установка Docker + apt-get update -y + apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin - systemctl enable docker + # Запуск и автозапуск systemctl start docker + systemctl enable docker - log "Docker установлен успешно" + log "✅ Docker установлен" } -ensure_nano() { - if ! command -v nano &> /dev/null; then - log "Установка nano..." - apt update - apt install -y nano - log "Nano установлен успешно" +# Поиск веб-серверов +detect_webservers() { + log "🔍 Поиск веб-серверов..." + + local webservers=() + + # Поиск Nginx + if systemctl is-active --quiet nginx 2>/dev/null; then + webservers+=("nginx-system") + log "✅ Найден: Nginx (системный)" fi + + # Поиск Caddy + if systemctl is-active --quiet caddy 2>/dev/null; then + webservers+=("caddy-system") + log "✅ Найден: Caddy (системный)" + fi + + # Поиск Docker контейнеров с веб-серверами + if command -v docker &> /dev/null; then + local containers=$(docker ps --format "table {{.Names}}\t{{.Image}}" | grep -E "(nginx|caddy)" || true) + if [[ -n "$containers" ]]; then + while IFS= read -r line; do + if [[ "$line" =~ nginx ]]; then + webservers+=("nginx-docker:$(echo "$line" | awk '{print $1}')") + log "✅ Найден: Nginx в Docker ($(echo "$line" | awk '{print $1}'))" + elif [[ "$line" =~ caddy ]]; then + webservers+=("caddy-docker:$(echo "$line" | awk '{print $1}')") + log "✅ Найден: Caddy в Docker ($(echo "$line" | awk '{print $1}'))" + fi + done <<< "$containers" + fi + fi + + if [[ ${#webservers[@]} -eq 0 ]]; then + warning "Веб-серверы не найдены" + return 1 + fi + + # Сохраняем найденные веб-серверы + printf '%s\n' "${webservers[@]}" > /tmp/detected_webservers + return 0 } -create_project_structure() { - log "Создание структуры проекта..." - - mkdir -p "$BOT_DIR" - mkdir -p "$BOT_DIR/logs" - mkdir -p "$BOT_DIR/data" - - log "Структура проекта создана в $BOT_DIR" -} - -validate_domain() { +# Настройка веб-сервера +configure_webserver() { local domain="$1" - if [[ $domain =~ ^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$ ]]; then + + if [[ ! -f /tmp/detected_webservers ]]; then + warning "Веб-серверы не найдены. Пропускаем настройку." return 0 + fi + + local webservers=($(cat /tmp/detected_webservers)) + + if [[ ${#webservers[@]} -eq 1 ]]; then + local selected_server="${webservers[0]}" + log "🔧 Автоматически выбран: $selected_server" else + echo -e "${CYAN}Найдено несколько веб-серверов:${NC}" + for i in "${!webservers[@]}"; do + echo " $((i+1))) ${webservers[i]}" + done + + while true; do + read -p "Выберите веб-сервер для настройки (1-${#webservers[@]}): " choice + if [[ "$choice" =~ ^[0-9]+$ ]] && [[ "$choice" -ge 1 ]] && [[ "$choice" -le ${#webservers[@]} ]]; then + selected_server="${webservers[$((choice-1))]}" + break + fi + echo "Неверный выбор. Попробуйте еще раз." + done + fi + + configure_selected_webserver "$selected_server" "$domain" +} + +# Настройка выбранного веб-сервера +configure_selected_webserver() { + local server="$1" + local domain="$2" + + local server_type=$(echo "$server" | cut -d':' -f1) + local container_name=$(echo "$server" | cut -d':' -f2 -s) + + case "$server_type" in + "nginx-system") + configure_nginx_system "$domain" + ;; + "nginx-docker") + configure_nginx_docker "$container_name" "$domain" + ;; + "caddy-system") + configure_caddy_system "$domain" + ;; + "caddy-docker") + configure_caddy_docker "$container_name" "$domain" + ;; + *) + error "Неизвестный тип сервера: $server_type" + ;; + esac +} + +# Настройка Nginx (системный) +configure_nginx_system() { + local domain="$1" + local config_file="/etc/nginx/sites-available/$domain" + + log "🔧 Настройка Nginx для домена: $domain" + + # Создаем или обновляем конфигурацию + if [[ -f "$config_file" ]]; then + # Добавляем location для webhook + if ! grep -q "/tribute-webhook" "$config_file"; then + sed -i '/server_name/a\\n # Tribute webhook\n location /tribute-webhook {\n proxy_pass http://127.0.0.1:8081;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n }\n\n location /webhook-health {\n proxy_pass http://127.0.0.1:8081/health;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n }' "$config_file" + fi + else + # Создаем новую конфигурацию + cat > "$config_file" << EOF +server { + listen 80; + server_name $domain; + + # Tribute webhook + location /tribute-webhook { + proxy_pass http://127.0.0.1:8081; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + } + + location /webhook-health { + proxy_pass http://127.0.0.1:8081/health; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + } + + location / { + root /var/www/html; + index index.html index.htm; + try_files \$uri \$uri/ =404; + } +} +EOF + + # Активируем сайт + ln -sf "$config_file" "/etc/nginx/sites-enabled/" + fi + + # Проверяем конфигурацию и перезагружаем + if nginx -t; then + systemctl reload nginx + log "✅ Nginx настроен и перезагружен" + else + error "Ошибка в конфигурации Nginx" return 1 fi } -create_docker_compose() { - log "Создание docker-compose.yml..." +# Настройка Caddy (Docker) +configure_caddy_docker() { + local container_name="$1" + local domain="$2" - echo "Выберите конфигурацию установки:" - echo "1) Только бот (панель RemnaWave на другом сервере)" - echo "2) Панель + бот на одном сервере (рекомендуется)" - echo "3) Расширенная - с Redis и Nginx" - echo "4) Бот с webhook через Caddy" - echo "5) Бот с webhook через Nginx" + log "🔧 Настройка Caddy Docker для домена: $domain" - while true; do - read -p "Ваш выбор (1-5): " choice - case $choice in - 1) - create_standalone_compose - break - ;; - 2) - create_panel_bot_compose - break - ;; - 3) - create_full_compose - break - ;; - 4) - create_webhook_caddy_compose - break - ;; - 5) - create_webhook_nginx_compose - break - ;; - *) - error "Неверный выбор. Попробуйте снова." - ;; - esac - done -} - -create_standalone_compose() { - cat > "$COMPOSE_FILE" << 'EOF' -services: - # PostgreSQL Database - postgres: - image: postgres:15-alpine - container_name: remnawave_bot_db - restart: unless-stopped - environment: - POSTGRES_DB: remnawave_bot - POSTGRES_USER: remnawave_user - POSTGRES_PASSWORD: secure_password_123 - POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - volumes: - - postgres_data:/var/lib/postgresql/data - networks: - - bot_network - healthcheck: - test: ["CMD-SHELL", "pg_isready -U remnawave_user -d remnawave_bot"] - interval: 15s - timeout: 10s - retries: 5 - start_period: 30s - - # RemnaWave Bot - bot: - image: fr1ngg/remnawave-bedolaga-telegram-bot:latest - container_name: remnawave_bot - restart: unless-stopped - depends_on: - postgres: - condition: service_healthy - env_file: - - .env - environment: - DATABASE_URL: postgresql+asyncpg://remnawave_user:secure_password_123@postgres:5432/remnawave_bot - volumes: - - ./logs:/app/logs - - ./data:/app/data - networks: - - bot_network - healthcheck: - test: ["CMD-SHELL", "python -c 'print(\"Bot is running\")'"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 30s - -volumes: - postgres_data: - driver: local - -networks: - bot_network: - driver: bridge -EOF - - log "Конфигурация только для бота создана" - export COMPOSE_TYPE="standalone" -} - -create_panel_bot_compose() { - cat > "$COMPOSE_FILE" << 'EOF' -services: - # PostgreSQL Database - postgres: - image: postgres:15-alpine - container_name: remnawave_bot_db - restart: unless-stopped - environment: - POSTGRES_DB: remnawave_bot - POSTGRES_USER: remnawave_user - POSTGRES_PASSWORD: secure_password_123 - POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - volumes: - - postgres_data:/var/lib/postgresql/data - networks: - - remnawave-network - healthcheck: - test: ["CMD-SHELL", "pg_isready -U remnawave_user -d remnawave_bot"] - interval: 15s - timeout: 10s - retries: 5 - start_period: 30s - - # RemnaWave Bot - bot: - image: fr1ngg/remnawave-bedolaga-telegram-bot:latest - container_name: remnawave_bot - restart: unless-stopped - depends_on: - postgres: - condition: service_healthy - env_file: - - .env - environment: - DATABASE_URL: postgresql+asyncpg://remnawave_user:secure_password_123@postgres:5432/remnawave_bot - volumes: - - ./logs:/app/logs - - ./data:/app/data - networks: - - remnawave-network - healthcheck: - test: ["CMD-SHELL", "python -c 'print(\"Bot is running\")'"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 30s - -volumes: - postgres_data: - driver: local - -networks: - remnawave-network: - name: remnawave-network - external: true -EOF - - log "Конфигурация панель + бот на одном сервере создана" - log "ВАЖНО: Убедитесь что панель RemnaWave уже установлена и создала сеть remnawave-network" - export COMPOSE_TYPE="panel_bot" -} - -create_full_compose() { - cat > "$COMPOSE_FILE" << 'EOF' -services: - # PostgreSQL Database - postgres: - image: postgres:15-alpine - container_name: remnawave_bot_db - restart: unless-stopped - environment: - POSTGRES_DB: remnawave_bot - POSTGRES_USER: remnawave_user - POSTGRES_PASSWORD: secure_password_123 - POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - volumes: - - postgres_data:/var/lib/postgresql/data - ports: - - "5432:5432" - networks: - - bot_network - healthcheck: - test: ["CMD-SHELL", "pg_isready -U remnawave_user -d remnawave_bot"] - interval: 15s - timeout: 10s - retries: 5 - start_period: 30s - - # RemnaWave Bot - bot: - image: fr1ngg/remnawave-bedolaga-telegram-bot:latest - container_name: remnawave_bot - restart: unless-stopped - depends_on: - postgres: - condition: service_healthy - env_file: - - .env - environment: - DATABASE_URL: postgresql+asyncpg://remnawave_user:secure_password_123@postgres:5432/remnawave_bot - volumes: - - ./logs:/app/logs - - ./data:/app/data - networks: - - bot_network - healthcheck: - test: ["CMD-SHELL", "python -c 'print(\"Bot is running\")'"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 30s - - # Redis (для кэширования и улучшения производительности) - redis: - image: redis:7-alpine - container_name: remnawave_bot_redis - restart: unless-stopped - command: redis-server --appendonly yes --requirepass redis_password_123 - volumes: - - redis_data:/data - ports: - - "6379:6379" - networks: - - bot_network - healthcheck: - test: ["CMD", "redis-cli", "--raw", "incr", "ping"] - interval: 10s - timeout: 3s - retries: 5 - profiles: - - with-redis - - # Nginx (для статических файлов или веб-интерфейса) - nginx: - image: nginx:alpine - container_name: remnawave_bot_nginx - restart: unless-stopped - ports: - - "80:80" - - "443:443" - volumes: - - ./nginx.conf:/etc/nginx/nginx.conf:ro - - ./ssl:/etc/nginx/ssl:ro - - ./static:/usr/share/nginx/html:ro - networks: - - bot_network - depends_on: - - bot - profiles: - - with-nginx - -volumes: - postgres_data: - driver: local - redis_data: - driver: local - -networks: - bot_network: - driver: bridge - ipam: - config: - - subnet: 172.20.0.0/16 -EOF - - log "Расширенная конфигурация Docker Compose создана" - export COMPOSE_TYPE="full" -} - -create_webhook_caddy_compose() { - while true; do - read -p "Введите домен для webhook (например: bot.example.com): " WEBHOOK_DOMAIN - if validate_domain "$WEBHOOK_DOMAIN"; then - break - else - error "Некорректный домен. Попробуйте снова." - fi - done + # Находим директорию с Caddyfile + local caddy_dir=$(docker inspect "$container_name" | jq -r '.[0].Mounts[] | select(.Destination == "/etc/caddy") | .Source' | head -1) - read -p "У вас уже установлен Caddy? (y/n): " caddy_installed - - if [[ $caddy_installed =~ ^[Yy] ]]; then - cat > "$COMPOSE_FILE" << 'EOF' -services: - # PostgreSQL Database - postgres: - image: postgres:15-alpine - container_name: remnawave_bot_db - restart: unless-stopped - environment: - POSTGRES_DB: remnawave_bot - POSTGRES_USER: remnawave_user - POSTGRES_PASSWORD: secure_password_123 - POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - volumes: - - postgres_data:/var/lib/postgresql/data - networks: - - bot_network - healthcheck: - test: ["CMD-SHELL", "pg_isready -U remnawave_user -d remnawave_bot"] - interval: 15s - timeout: 10s - retries: 5 - start_period: 30s - - # RemnaWave Bot - bot: - image: fr1ngg/remnawave-bedolaga-telegram-bot:latest - container_name: remnawave_bot - restart: unless-stopped - depends_on: - postgres: - condition: service_healthy - env_file: - - .env - environment: - DATABASE_URL: postgresql+asyncpg://remnawave_user:secure_password_123@postgres:5432/remnawave_bot - volumes: - - ./logs:/app/logs - - ./data:/app/data - ports: - - "8081:8081" - networks: - - bot_network - healthcheck: - test: ["CMD-SHELL", "python -c 'print(\"Bot is running\")'"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 30s - -volumes: - postgres_data: - driver: local - -networks: - bot_network: - driver: bridge - ipam: - config: - - subnet: 172.20.0.0/16 -EOF - export WEBHOOK_CONFIG_TYPE="external_caddy" - else - cat > "$COMPOSE_FILE" << 'EOF' -services: - # PostgreSQL Database - postgres: - image: postgres:15-alpine - container_name: remnawave_bot_db - restart: unless-stopped - environment: - POSTGRES_DB: remnawave_bot - POSTGRES_USER: remnawave_user - POSTGRES_PASSWORD: secure_password_123 - POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - volumes: - - postgres_data:/var/lib/postgresql/data - networks: - - bot_network - healthcheck: - test: ["CMD-SHELL", "pg_isready -U remnawave_user -d remnawave_bot"] - interval: 15s - timeout: 10s - retries: 5 - start_period: 30s - - # RemnaWave Bot - bot: - image: fr1ngg/remnawave-bedolaga-telegram-bot:latest - container_name: remnawave_bot - restart: unless-stopped - depends_on: - postgres: - condition: service_healthy - env_file: - - .env - environment: - DATABASE_URL: postgresql+asyncpg://remnawave_user:secure_password_123@postgres:5432/remnawave_bot - volumes: - - ./logs:/app/logs - - ./data:/app/data - networks: - - bot_network - healthcheck: - test: ["CMD-SHELL", "python -c 'print(\"Bot is running\")'"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 30s - - # Caddy reverse proxy - caddy: - image: caddy:2.9.1 - container_name: caddy-bedolaga-bot - restart: unless-stopped - ports: - - "80:80" - - "443:443" - volumes: - - ./Caddyfile:/etc/caddy/Caddyfile - - /opt/caddy/html:/var/www/html - - ./caddy-logs:/var/log/caddy - - caddy_data:/data - - caddy_config:/config - networks: - - bot_network - depends_on: - - bot - logging: - driver: "json-file" - options: - max-size: "10m" - max-file: "3" - -volumes: - postgres_data: - driver: local - caddy_data: - driver: local - caddy_config: - driver: local - -networks: - bot_network: - driver: bridge - ipam: - config: - - subnet: 172.20.0.0/16 -EOF - export WEBHOOK_CONFIG_TYPE="included_caddy" - - create_caddyfile "$WEBHOOK_DOMAIN" + if [[ -z "$caddy_dir" || "$caddy_dir" == "null" ]]; then + error "Не удалось найти директорию с Caddyfile для контейнера $container_name" + return 1 fi - log "Конфигурация бота с Caddy webhook создана" - export COMPOSE_TYPE="webhook_caddy" - export WEBHOOK_DOMAIN="$WEBHOOK_DOMAIN" -} - -create_webhook_nginx_compose() { - while true; do - read -p "Введите домен для webhook (например: bot.example.com): " WEBHOOK_DOMAIN - if validate_domain "$WEBHOOK_DOMAIN"; then - break - else - error "Некорректный домен. Попробуйте снова." - fi - done + local caddyfile="$caddy_dir/Caddyfile" - read -p "У вас уже установлен Nginx? (y/n): " nginx_installed - - if [[ $nginx_installed =~ ^[Yy] ]]; then - cat > "$COMPOSE_FILE" << 'EOF' -services: - # PostgreSQL Database - postgres: - image: postgres:15-alpine - container_name: remnawave_bot_db - restart: unless-stopped - environment: - POSTGRES_DB: remnawave_bot - POSTGRES_USER: remnawave_user - POSTGRES_PASSWORD: secure_password_123 - POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - volumes: - - postgres_data:/var/lib/postgresql/data - networks: - - bot_network - healthcheck: - test: ["CMD-SHELL", "pg_isready -U remnawave_user -d remnawave_bot"] - interval: 15s - timeout: 10s - retries: 5 - start_period: 30s - - # RemnaWave Bot - bot: - image: fr1ngg/remnawave-bedolaga-telegram-bot:latest - container_name: remnawave_bot - restart: unless-stopped - depends_on: - postgres: - condition: service_healthy - env_file: - - .env - environment: - DATABASE_URL: postgresql+asyncpg://remnawave_user:secure_password_123@postgres:5432/remnawave_bot - volumes: - - ./logs:/app/logs - - ./data:/app/data - ports: - - "8081:8081" - networks: - - bot_network - healthcheck: - test: ["CMD-SHELL", "python -c 'print(\"Bot is running\")'"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 30s - -volumes: - postgres_data: - driver: local - -networks: - bot_network: - driver: bridge - ipam: - config: - - subnet: 172.20.0.0/16 -EOF - export WEBHOOK_CONFIG_TYPE="external_nginx" - else - cat > "$COMPOSE_FILE" << 'EOF' -services: - # PostgreSQL Database - postgres: - image: postgres:15-alpine - container_name: remnawave_bot_db - restart: unless-stopped - environment: - POSTGRES_DB: remnawave_bot - POSTGRES_USER: remnawave_user - POSTGRES_PASSWORD: secure_password_123 - POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - volumes: - - postgres_data:/var/lib/postgresql/data - networks: - - bot_network - healthcheck: - test: ["CMD-SHELL", "pg_isready -U remnawave_user -d remnawave_bot"] - interval: 15s - timeout: 10s - retries: 5 - start_period: 30s - - # RemnaWave Bot - bot: - image: fr1ngg/remnawave-bedolaga-telegram-bot:latest - container_name: remnawave_bot - restart: unless-stopped - depends_on: - postgres: - condition: service_healthy - env_file: - - .env - environment: - DATABASE_URL: postgresql+asyncpg://remnawave_user:secure_password_123@postgres:5432/remnawave_bot - volumes: - - ./logs:/app/logs - - ./data:/app/data - networks: - - bot_network - healthcheck: - test: ["CMD-SHELL", "python -c 'print(\"Bot is running\")'"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 30s - - # Nginx reverse proxy - nginx: - image: nginx:alpine - container_name: nginx-bedolaga-bot - restart: unless-stopped - ports: - - "80:80" - - "443:443" - volumes: - - ./nginx.conf:/etc/nginx/nginx.conf - - ./ssl:/etc/nginx/ssl - - /var/www/html:/var/www/html - - ./nginx-logs:/var/log/nginx - networks: - - bot_network - depends_on: - - bot - -volumes: - postgres_data: - driver: local - -networks: - bot_network: - driver: bridge - ipam: - config: - - subnet: 172.20.0.0/16 -EOF - export WEBHOOK_CONFIG_TYPE="included_nginx" - - create_nginx_config "$WEBHOOK_DOMAIN" + if [[ ! -f "$caddyfile" ]]; then + error "Caddyfile не найден: $caddyfile" + return 1 fi - log "Конфигурация бота с Nginx webhook создана" - export COMPOSE_TYPE="webhook_nginx" - export WEBHOOK_DOMAIN="$WEBHOOK_DOMAIN" -} + # Создаем резервную копию + cp "$caddyfile" "$caddyfile.backup.$(date +%s)" + + # Проверяем, есть ли уже настройка для домена + if grep -q "^https://$domain" "$caddyfile" || grep -q "^$domain" "$caddyfile"; then + log "⚠️ Конфигурация для домена $domain уже существует" + + # Проверяем наличие webhook настроек + if ! grep -q "/tribute-webhook" "$caddyfile"; then + # Добавляем webhook настройки в существующий блок + sed -i "/^https:\/\/$domain\|^$domain/,/^}/ { + /handle.*{/a\\ + # Tribute webhook endpoint\\ + handle /tribute-webhook* {\\ + reverse_proxy localhost:8081 {\\ + header_up Host {host}\\ + header_up X-Real-IP {remote_host}\\ + }\\ + }\\ + \\ + # Health check для webhook сервиса\\ + handle /webhook-health {\\ + reverse_proxy localhost:8081/health {\\ + header_up Host {host}\\ + header_up X-Real-IP {remote_host}\\ + }\\ + } + }" "$caddyfile" + + log "✅ Добавлены webhook настройки в существующую конфигурацию" + else + log "✅ Webhook настройки уже присутствуют" + fi + else + # Создаем новую конфигурацию для домена + cat >> "$caddyfile" << EOF -create_caddyfile() { - local domain="$1" - - log "Создание Caddyfile для домена $domain..." - - mkdir -p "$BOT_DIR/caddy-logs" - - cat > "$BOT_DIR/Caddyfile" << EOF -$domain { - # Webhook endpoint для Telegram бота - handle /webhook* { - reverse_proxy bot:8081 { +https://$domain { + # Tribute webhook endpoint + handle /tribute-webhook* { + reverse_proxy localhost:8081 { header_up Host {host} header_up X-Real-IP {remote_host} - header_up X-Forwarded-For {remote_host} - header_up X-Forwarded-Proto {scheme} } } # Health check для webhook сервиса - handle /health { - reverse_proxy bot:8081 { + handle /webhook-health { + reverse_proxy localhost:8081/health { header_up Host {host} header_up X-Real-IP {remote_host} - header_up X-Forwarded-For {remote_host} - header_up X-Forwarded-Proto {scheme} } } @@ -742,449 +376,307 @@ $domain { } } EOF - - mkdir -p /opt/caddy/html + log "✅ Добавлена новая конфигурация для домена $domain" + fi - cat > /opt/caddy/html/index.html << EOF - - - - RemnaWave Bot Webhook - - - -

RemnaWave Bot Webhook Server

-

Сервер webhook для Telegram бота успешно запущен.

-

Домен: $domain

- - -EOF - - log "Caddyfile создан для домена $domain" -} - -create_nginx_config() { - local domain="$1" - - log "Создание конфигурации Nginx для домена $domain..." - - mkdir -p "$BOT_DIR/nginx-logs" - mkdir -p "$BOT_DIR/ssl" - mkdir -p /var/www/html - - cat > "$BOT_DIR/nginx.conf" << EOF -events { - worker_connections 1024; -} - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" ' - '\$status \$body_bytes_sent "\$http_referer" ' - '"\$http_user_agent" "\$http_x_forwarded_for"'; - - access_log /var/log/nginx/access.log main; - error_log /var/log/nginx/error.log warn; - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - - # Gzip compression - gzip on; - gzip_vary on; - gzip_min_length 1024; - gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; - - # Rate limiting - limit_req_zone \$binary_remote_addr zone=webhook:10m rate=10r/s; - - upstream bot_backend { - server bot:8081; - keepalive 32; - } - - server { - listen 80; - server_name $domain; - - # Redirect to HTTPS - return 301 https://\$server_name\$request_uri; - } - - server { - listen 443 ssl http2; - server_name $domain; - - # SSL configuration (you need to add your certificates) - # ssl_certificate /etc/nginx/ssl/cert.pem; - # ssl_certificate_key /etc/nginx/ssl/key.pem; - - # For now, use self-signed or Let's Encrypt - # Uncomment and configure SSL certificates - - # SSL security settings - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; - ssl_prefer_server_ciphers off; - ssl_session_cache shared:SSL:10m; - ssl_session_timeout 10m; - - # Security headers - add_header X-Frame-Options DENY; - add_header X-Content-Type-Options nosniff; - add_header X-XSS-Protection "1; mode=block"; - add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; - - # Webhook endpoint - location /webhook { - limit_req zone=webhook burst=20 nodelay; - - proxy_pass http://bot_backend; - proxy_set_header Host \$host; - proxy_set_header X-Real-IP \$remote_addr; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto \$scheme; - - proxy_connect_timeout 30s; - proxy_send_timeout 30s; - proxy_read_timeout 30s; - - proxy_buffering off; - proxy_request_buffering off; - } - - # Health check - location /health { - proxy_pass http://bot_backend; - proxy_set_header Host \$host; - proxy_set_header X-Real-IP \$remote_addr; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto \$scheme; - } - - # Default location - location / { - root /var/www/html; - index index.html index.htm; - try_files \$uri \$uri/ /index.html; - } - - # Deny access to hidden files - location ~ /\. { - deny all; - } - } -} -EOF - - # Создаем простую индексную страницу - cat > /var/www/html/index.html << EOF - - - - RemnaWave Bot Webhook - - - - -
-

RemnaWave Bot Webhook Server

-

✅ Сервер webhook для Telegram бота успешно запущен.

-

Домен: $domain

-

Webhook URL: https://$domain/webhook

-

Health Check: https://$domain/health

-
- - -EOF - - log "Конфигурация Nginx создана для домена $domain" - warn "ВАЖНО: Не забудьте настроить SSL сертификаты для домена $domain" - warn "Рекомендуется использовать Let's Encrypt для получения бесплатных SSL сертификатов" -} - -create_env_file() { - log "Настройка .env файла..." - - read -p "Введите BOT_TOKEN: " BOT_TOKEN - read -p "Введите BOT_USERNAME (без @): " BOT_USERNAME - read -p "Введите ADMIN_IDS (через запятую): " ADMIN_IDS - - if [ "$COMPOSE_TYPE" = "panel_bot" ]; then - log "Настройка для панель + бот на одном сервере" - REMNAWAVE_URL="http://remnawave:3000" - echo "URL панели будет: $REMNAWAVE_URL (внутренний адрес контейнера)" + # Перезагружаем Caddy + if docker exec "$container_name" caddy reload --config /etc/caddy/Caddyfile; then + log "✅ Caddy перезагружен" else - read -p "Введите REMNAWAVE_URL (например: https://your-panel.com): " REMNAWAVE_URL + error "Ошибка при перезагрузке Caddy" + # Восстанавливаем из резервной копии + cp "$caddyfile.backup.$(date +%s)" "$caddyfile" + return 1 fi +} + +# Интерактивная настройка .env +configure_env() { + log "⚙️ Интерактивная настройка параметров бота..." - read -p "Введите REMNAWAVE_TOKEN: " REMNAWAVE_TOKEN - read -p "Введите SUBSCRIPTION_BASE_URL (например: https://sub.your-domain.com): " SUBSCRIPTION_BASE_URL + # Создаем временный файл для .env + local temp_env="/tmp/bedolaga.env" - if [[ "$COMPOSE_TYPE" == webhook_* ]]; then - echo "" - echo -e "${YELLOW}=== Настройка Webhook ===${NC}" - WEBHOOK_URL="https://${WEBHOOK_DOMAIN}/webhook" - WEBHOOK_SECRET=$(openssl rand -hex 32) - echo -e "${GREEN}Webhook URL: $WEBHOOK_URL${NC}" - echo -e "${GREEN}Webhook Secret сгенерирован автоматически${NC}" - fi + echo "# Bedolaga Bot Configuration" > "$temp_env" + echo "# Generated by installer on $(date)" >> "$temp_env" + echo "" >> "$temp_env" - while true; do - read -p "Включить триал? (y/n): " trial_enabled - case $trial_enabled in - [Yy]*) - TRIAL_ENABLED="true" - read -p "Введите TRIAL_DURATION_DAYS: " TRIAL_DURATION_DAYS - read -p "Введите TRIAL_TRAFFIC_GB: " TRIAL_TRAFFIC_GB - read -p "Введите TRIAL_SQUAD_UUID: " TRIAL_SQUAD_UUID - break - ;; - [Nn]*) - TRIAL_ENABLED="false" - TRIAL_DURATION_DAYS="" - TRIAL_TRAFFIC_GB="" - TRIAL_SQUAD_UUID="" - break - ;; - *) - error "Пожалуйста, ответьте y или n." - ;; - esac - done + # Bot Configuration + echo -e "${CYAN}=== ОСНОВНЫЕ НАСТРОЙКИ БОТА ===${NC}" - read -p "Введите REFERRAL_FIRST_REWARD (сумму с .0 на конце): " REFERRAL_FIRST_REWARD - read -p "Введите REFERRAL_REFERRED_BONUS (сумму с .0 на конце): " REFERRAL_REFERRED_BONUS - read -p "Введите REFERRAL_THRESHOLD (сумму с .0 на конце): " REFERRAL_THRESHOLD - read -p "Введите REFERRAL_PERCENTAGE (с 0. в начале): " REFERRAL_PERCENTAGE + read -p "🤖 Введите токен бота (BOT_TOKEN): " bot_token + echo "BOT_TOKEN=$bot_token" >> "$temp_env" - echo "" - echo -e "${YELLOW}=== Настройка оплаты звездами Telegram ===${NC}" - while true; do - read -p "Включить оплату звездами Telegram? (y/n): " stars_enabled - case $stars_enabled in - [Yy]*) - STARS_ENABLED="true" - echo "" - echo -e "${BLUE}Настройка курсов обмена звезд на рубли:${NC}" - echo -e "${YELLOW}Введите курс обмена для каждого пакета звезд${NC}" - echo -e "${YELLOW}(например, если 100 звезд = 150 рублей, введите 150)${NC}" - echo "" - - read -p "Курс для 100 звезд (в рублях): " STARS_100_RATE - read -p "Курс для 150 звезд (в рублях): " STARS_150_RATE - read -p "Курс для 250 звезд (в рублях): " STARS_250_RATE - read -p "Курс для 350 звезд (в рублях): " STARS_350_RATE - read -p "Курс для 500 звезд (в рублях): " STARS_500_RATE - break - ;; - [Nn]*) - STARS_ENABLED="false" - STARS_100_RATE="" - STARS_150_RATE="" - STARS_250_RATE="" - STARS_350_RATE="" - STARS_500_RATE="" - break - ;; - *) - error "Пожалуйста, ответьте y или n." - ;; - esac - done + read -p "📝 Введите username бота без @ (BOT_USERNAME): " bot_username + echo "BOT_USERNAME=$bot_username" >> "$temp_env" - read -p "Введите DELETE_EXPIRED_TRIAL_DAYS: " DELETE_EXPIRED_TRIAL_DAYS - read -p "Введите DELETE_EXPIRED_REGULAR_DAYS: " DELETE_EXPIRED_REGULAR_DAYS + echo "" >> "$temp_env" + echo "# Referral System" >> "$temp_env" - cat > "$ENV_FILE" << EOF -# Bot Configuration -BOT_TOKEN=$BOT_TOKEN -BOT_USERNAME=$BOT_USERNAME - -# RemnaWave API Configuration -REMNAWAVE_URL=$REMNAWAVE_URL -REMNAWAVE_TOKEN=$REMNAWAVE_TOKEN -SUBSCRIPTION_BASE_URL=$SUBSCRIPTION_BASE_URL - -# Admin Configuration -ADMIN_IDS=$ADMIN_IDS -SUPPORT_USERNAME=support - -EOF - - if [[ "$COMPOSE_TYPE" == webhook_* ]]; then - cat >> "$ENV_FILE" << EOF -# Webhook Configuration -WEBHOOK_URL=$WEBHOOK_URL -WEBHOOK_SECRET=$WEBHOOK_SECRET -WEBHOOK_ENABLED=true - -EOF - fi - - cat >> "$ENV_FILE" << EOF -# Trial Configuration -TRIAL_ENABLED=$TRIAL_ENABLED -EOF - - if [ "$TRIAL_ENABLED" = "true" ]; then - cat >> "$ENV_FILE" << EOF -TRIAL_DURATION_DAYS=$TRIAL_DURATION_DAYS -TRIAL_TRAFFIC_GB=$TRIAL_TRAFFIC_GB -TRIAL_SQUAD_UUID=$TRIAL_SQUAD_UUID -EOF - fi - - cat >> "$ENV_FILE" << EOF - -# Referral Configuration -REFERRAL_FIRST_REWARD=$REFERRAL_FIRST_REWARD -REFERRAL_REFERRED_BONUS=$REFERRAL_REFERRED_BONUS -REFERRAL_THRESHOLD=$REFERRAL_THRESHOLD -REFERRAL_PERCENTAGE=$REFERRAL_PERCENTAGE - -# Telegram Stars Configuration -STARS_ENABLED=$STARS_ENABLED -EOF - - if [ "$STARS_ENABLED" = "true" ]; then - cat >> "$ENV_FILE" << EOF -STARS_100_RATE=$STARS_100_RATE -STARS_150_RATE=$STARS_150_RATE -STARS_250_RATE=$STARS_250_RATE -STARS_350_RATE=$STARS_350_RATE -STARS_500_RATE=$STARS_500_RATE -EOF - fi - - cat >> "$ENV_FILE" << EOF - -# Monitor Configuration -MONITOR_ENABLED=true -MONITOR_CHECK_INTERVAL=21600 -MONITOR_DAILY_CHECK_HOUR=12 -MONITOR_WARNING_DAYS=2 -DELETE_EXPIRED_TRIAL_DAYS=$DELETE_EXPIRED_TRIAL_DAYS -DELETE_EXPIRED_REGULAR_DAYS=$DELETE_EXPIRED_REGULAR_DAYS -AUTO_DELETE_ENABLED=true - -# Lucky Game Configuration -LUCKY_GAME_ENABLED=true -LUCKY_GAME_REWARD=50.0 -LUCKY_GAME_NUMBERS=30 -LUCKY_GAME_WINNING_COUNT=5 -EOF - - log ".env файл создан успешно" + read -p "💰 Награда пригласившему (REFERRAL_FIRST_REWARD): " ref_first + echo "REFERRAL_FIRST_REWARD=$ref_first" >> "$temp_env" - if [[ "$COMPOSE_TYPE" == webhook_* ]]; then - echo "" - echo -e "${YELLOW}=== ВАЖНЫЕ ИНСТРУКЦИИ ДЛЯ WEBHOOK ===${NC}" - echo -e "${GREEN}1. Webhook URL: $WEBHOOK_URL${NC}" - echo -e "${GREEN}2. Убедитесь что домен $WEBHOOK_DOMAIN указывает на этот сервер${NC}" - echo -e "${GREEN}3. SSL сертификат будет настроен автоматически (Let's Encrypt)${NC}" + read -p "🎁 Награда рефералу (REFERRAL_REFERRED_BONUS): " ref_bonus + echo "REFERRAL_REFERRED_BONUS=$ref_bonus" >> "$temp_env" + + read -p "💵 Сумма пополнения для выплаты (REFERRAL_THRESHOLD): " ref_threshold + echo "REFERRAL_THRESHOLD=$ref_threshold" >> "$temp_env" + + read -p "📊 Процент за пополнение (0.20 = 20%) (REFERRAL_PERCENTAGE): " ref_percentage + echo "REFERRAL_PERCENTAGE=$ref_percentage" >> "$temp_env" + + # RemnaWave API + echo "" >> "$temp_env" + echo "# RemnaWave API Configuration" >> "$temp_env" + + read -p "🌐 URL панели RemnaWave (https://panel.example.com): " remnawave_url + echo "REMNAWAVE_URL=$remnawave_url" >> "$temp_env" + + read -p "🔑 Токен RemnaWave API: " remnawave_token + echo "REMNAWAVE_TOKEN=$remnawave_token" >> "$temp_env" + + # Admin Configuration + echo "" >> "$temp_env" + echo "# Admin Configuration" >> "$temp_env" + + read -p "👑 ID администраторов (через запятую): " admin_ids + echo "ADMIN_IDS=$admin_ids" >> "$temp_env" + + # Support Configuration + echo "" >> "$temp_env" + echo "# Support Configuration" >> "$temp_env" + + read -p "🆘 Username для поддержки без @: " support_username + echo "SUPPORT_USERNAME=$support_username" >> "$temp_env" + + # Trial Configuration + echo -e "${CYAN}=== НАСТРОЙКИ ТРИАЛА ===${NC}" + + read -p "🆓 Включить триал? (true/false): " trial_enabled + echo "TRIAL_ENABLED=$trial_enabled" >> "$temp_env" + + if [[ "$trial_enabled" == "true" ]]; then + read -p "📅 Дней триала: " trial_days + echo "TRIAL_DURATION_DAYS=$trial_days" >> "$temp_env" - if [ "$WEBHOOK_CONFIG_TYPE" = "external_caddy" ]; then - echo -e "${YELLOW}4. Необходимо добавить в существующий Caddyfile:${NC}" - cat << EOF - -$WEBHOOK_DOMAIN { - handle /webhook* { - reverse_proxy localhost:8081 { - header_up Host {host} - header_up X-Real-IP {remote_host} - header_up X-Forwarded-For {remote_host} - header_up X-Forwarded-Proto {scheme} - } - } - - handle /health { - reverse_proxy localhost:8081 { - header_up Host {host} - header_up X-Real-IP {remote_host} - header_up X-Forwarded-For {remote_host} - header_up X-Forwarded-Proto {scheme} - } - } -} -EOF - elif [ "$WEBHOOK_CONFIG_TYPE" = "external_nginx" ]; then - echo -e "${YELLOW}4. Необходимо добавить в конфигурацию Nginx:${NC}" - echo -e "${BLUE}Файл конфигурации создан в: $BOT_DIR/nginx-site.conf${NC}" + read -p "📊 Лимит трафика ГБ: " trial_traffic + echo "TRIAL_TRAFFIC_GB=$trial_traffic" >> "$temp_env" + + read -p "🏷️ UUID сквада из панели: " trial_squad + echo "TRIAL_SQUAD_UUID=$trial_squad" >> "$temp_env" + + echo "TRIAL_PRICE=0.0" >> "$temp_env" + + read -p "🔔 Уведомления об истечении триала? (true/false): " trial_notif + echo "TRIAL_NOTIFICATION_ENABLED=$trial_notif" >> "$temp_env" + + if [[ "$trial_notif" == "true" ]]; then + read -p "⏰ Через сколько часов уведомлять: " notif_hours + echo "TRIAL_NOTIFICATION_HOURS_AFTER=$notif_hours" >> "$temp_env" - cat > "$BOT_DIR/nginx-site.conf" << EOF -server { - listen 80; - server_name $WEBHOOK_DOMAIN; - return 301 https://\$server_name\$request_uri; -} - -server { - listen 443 ssl http2; - server_name $WEBHOOK_DOMAIN; - - # SSL configuration - ssl_certificate /path/to/your/cert.pem; - ssl_certificate_key /path/to/your/key.pem; - - location /webhook { - proxy_pass http://localhost:8081; - proxy_set_header Host \$host; - proxy_set_header X-Real-IP \$remote_addr; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto \$scheme; - } - - location /health { - proxy_pass http://localhost:8081; - proxy_set_header Host \$host; - proxy_set_header X-Real-IP \$remote_addr; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto \$scheme; - } -} -EOF + read -p "🔄 Через сколько часов повторить: " notif_window + echo "TRIAL_NOTIFICATION_HOURS_WINDOW=$notif_window" >> "$temp_env" fi - echo "" fi - if [ "$COMPOSE_TYPE" = "panel_bot" ]; then - echo "" - echo -e "${YELLOW}=== ВАЖНЫЕ ИНСТРУКЦИИ ДЛЯ ПАНЕЛЬ + БОТ ===${NC}" - echo -e "${GREEN}1. Убедитесь что панель RemnaWave уже запущена${NC}" - echo -e "${GREEN}2. URL панели установлен как: $REMNAWAVE_URL${NC}" - echo -e "${GREEN}3. Бот будет подключаться к панели через внутреннюю Docker сеть${NC}" - echo -e "${YELLOW}4. Если панель не запущена, сначала запустите её!${NC}" - echo "" + # Monitor Service + echo "" >> "$temp_env" + echo "# Monitor Service Settings" >> "$temp_env" + + read -p "⏱️ Интервал проверки в секундах (3600 = час): " monitor_interval + echo "MONITOR_CHECK_INTERVAL=$monitor_interval" >> "$temp_env" + + read -p "🌅 Час ежедневной проверки (0-23): " daily_hour + echo "MONITOR_DAILY_CHECK_HOUR=$daily_hour" >> "$temp_env" + + read -p "⚠️ За сколько дней предупреждать: " warning_days + echo "MONITOR_WARNING_DAYS=$warning_days" >> "$temp_env" + + read -p "🗑️ Удалять триал через дней: " delete_trial + echo "DELETE_EXPIRED_TRIAL_DAYS=$delete_trial" >> "$temp_env" + + read -p "🗑️ Удалять обычные через дней: " delete_regular + echo "DELETE_EXPIRED_REGULAR_DAYS=$delete_regular" >> "$temp_env" + + read -p "🤖 Автоудаление? (true/false): " auto_delete + echo "AUTO_DELETE_ENABLED=$auto_delete" >> "$temp_env" + + # Lucky Game + echo "" >> "$temp_env" + echo "# Lucky Game Settings" >> "$temp_env" + + read -p "🎲 Включить игру удачи? (true/false): " lucky_enabled + echo "LUCKY_GAME_ENABLED=$lucky_enabled" >> "$temp_env" + + if [[ "$lucky_enabled" == "true" ]]; then + read -p "💰 Размер награды в рублях: " lucky_reward + echo "LUCKY_GAME_REWARD=$lucky_reward" >> "$temp_env" + + read -p "🔢 Всего чисел для выбора: " lucky_numbers + echo "LUCKY_GAME_NUMBERS=$lucky_numbers" >> "$temp_env" + + read -p "🎯 Количество выигрышных: " lucky_winning + echo "LUCKY_GAME_WINNING_COUNT=$lucky_winning" >> "$temp_env" fi + + # Telegram Stars + echo -e "${CYAN}=== TELEGRAM STARS ===${NC}" + echo "" >> "$temp_env" + echo "# Telegram Stars Configuration" >> "$temp_env" + + read -p "⭐ Включить Telegram Stars? (true/false): " stars_enabled + echo "STARS_ENABLED=$stars_enabled" >> "$temp_env" + + if [[ "$stars_enabled" == "true" ]]; then + declare -a star_amounts=("100" "150" "250" "350" "500" "750" "1000") + + for amount in "${star_amounts[@]}"; do + read -p "💫 $amount звёзд = сколько рублей: " rate + echo "STARS_${amount}_RATE=$rate" >> "$temp_env" + done + fi + + # Tribute + echo -e "${CYAN}=== TRIBUTE ДОНАТЫ ===${NC}" + echo "" >> "$temp_env" + echo "# Tribute Configuration" >> "$temp_env" + + read -p "🎁 Включить Tribute? (true/false): " tribute_enabled + echo "TRIBUTE_ENABLED=$tribute_enabled" >> "$temp_env" + + if [[ "$tribute_enabled" == "true" ]]; then + read -p "🔑 API ключ Tribute: " tribute_api + echo "TRIBUTE_API_KEY=$tribute_api" >> "$temp_env" + + read -p "🚪 Порт webhook (8081): " webhook_port + webhook_port=${webhook_port:-8081} + echo "TRIBUTE_WEBHOOK_PORT=$webhook_port" >> "$temp_env" + + echo "TRIBUTE_WEBHOOK_PATH=/tribute-webhook" >> "$temp_env" + + read -p "🔗 Ссылка на донат в Tribute: " tribute_link + echo "TRIBUTE_DONATE_LINK=$tribute_link" >> "$temp_env" + + # Спрашиваем домен для webhook + echo -e "${CYAN}=== НАСТРОЙКА ВЕБХУКА ===${NC}" + read -p "🌐 Домен для webhook (example.com): " webhook_domain + + # Настраиваем веб-сервер + configure_webserver "$webhook_domain" + fi + + # Копируем в финальное место + cp "$temp_env" "$ENV_FILE" + log "✅ Конфигурация сохранена" } -create_service() { - while true; do - read -p "Создать службу для запуска бота? (y/n): " create_service_choice - case $create_service_choice in - [Yy]*) - log "Создание службы systemd..." - - cat > "$SERVICE_FILE" << EOF +# Создание структуры проекта +create_project_structure() { + log "📁 Создание структуры проекта..." + + mkdir -p "$PROJECT_DIR"/{logs,data,backups} + chown -R root:docker "$PROJECT_DIR" 2>/dev/null || chown -R root:root "$PROJECT_DIR" + chmod -R 755 "$PROJECT_DIR" + + log "✅ Структура проекта создана" +} + +# Создание docker-compose.yml +create_docker_compose() { + log "🐳 Создание docker-compose.yml..." + + # Генерируем случайный пароль для БД + local db_password=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-25) + + cat > "$DOCKER_COMPOSE_FILE" << 'EOF' +services: + # PostgreSQL Database + postgres: + image: postgres:15-alpine + container_name: bedolaga_bot_db + restart: unless-stopped + environment: + POSTGRES_DB: bedolaga_bot + POSTGRES_USER: bedolaga_user + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - bot_network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U bedolaga_user -d bedolaga_bot"] + interval: 15s + timeout: 10s + retries: 5 + start_period: 30s + + # Bedolaga Bot + bot: + image: fr1ngg/remnawave-bedolaga-telegram-bot:latest + container_name: bedolaga_bot + restart: unless-stopped + depends_on: + postgres: + condition: service_healthy + env_file: + - .env + environment: + DATABASE_URL: postgresql+asyncpg://bedolaga_user:${DB_PASSWORD}@postgres:5432/bedolaga_bot + volumes: + - ./logs:/app/logs + - ./data:/app/data + ports: + - "${TRIBUTE_WEBHOOK_PORT:-8081}:${TRIBUTE_WEBHOOK_PORT:-8081}" + networks: + - bot_network + healthcheck: + test: ["CMD-SHELL", "python -c 'print(\"Bot is running\")'"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + +volumes: + postgres_data: + driver: local + +networks: + bot_network: + driver: bridge + ipam: + config: + - subnet: 172.20.0.0/16 +EOF + + # Добавляем пароль БД в .env + echo "" >> "$ENV_FILE" + echo "# Database Configuration" >> "$ENV_FILE" + echo "DB_PASSWORD=$db_password" >> "$ENV_FILE" + + log "✅ Docker Compose файл создан" +} + +# Создание systemd службы +create_systemd_service() { + local create_service="" + + echo -e "${CYAN}Создать systemd службу для автозапуска? (y/n):${NC}" + read -r create_service + + if [[ "$create_service" =~ ^[Yy]$ ]]; then + log "⚙️ Создание systemd службы..." + + cat > "/etc/systemd/system/${SERVICE_NAME}.service" << EOF [Unit] -Description=RemnaWave Bedolaga Bot +Description=Bedolaga Telegram Bot Requires=docker.service After=docker.service [Service] Type=oneshot -RemainAfterExit=yes -WorkingDirectory=$BOT_DIR +RemainAfterExit=true +WorkingDirectory=$PROJECT_DIR ExecStart=/usr/bin/docker compose up -d ExecStop=/usr/bin/docker compose down TimeoutStartSec=0 @@ -1192,948 +684,696 @@ TimeoutStartSec=0 [Install] WantedBy=multi-user.target EOF - - systemctl daemon-reload - systemctl enable bedolaga-bot.service - log "Служба bedolaga-bot создана и включена" - break - ;; - [Nn]*) - log "Служба не будет создана" - break - ;; - *) - error "Пожалуйста, ответьте y или n." - ;; - esac - done -} - -check_bot_status() { - if docker compose -f "$COMPOSE_FILE" ps --services --filter "status=running" | grep -q "bot"; then - return 0 - else - return 1 - fi -} - -check_remnawave_connection() { - if [ -f "$ENV_FILE" ]; then - source "$ENV_FILE" - if [ ! -z "$REMNAWAVE_URL" ]; then - if [[ "$REMNAWAVE_URL" == *"remnawave:3000"* ]]; then - if docker compose -f "$COMPOSE_FILE" ps bot | grep -q "Up"; then - return 0 - else - return 1 - fi - else - if curl -s --connect-timeout 5 "$REMNAWAVE_URL/api/system/stats" > /dev/null 2>&1; then - return 0 - else - return 1 - fi - fi - else - return 1 - fi - else - return 1 + + systemctl daemon-reload + systemctl enable "$SERVICE_NAME" + + log "✅ Systemd служба создана и включена" + echo "DB_PASSWORD" >> "$CONFIG_FILE" fi } +# Запуск бота start_bot() { - log "Запуск бота..." - cd "$BOT_DIR" + log "🚀 Запуск бота..." - if grep -q "remnawave-network" "$COMPOSE_FILE"; then - log "Обнаружена конфигурация панель + бот" - - if ! docker network ls | grep -q "remnawave-network"; then - error "Сеть remnawave-network не найдена!" - error "Убедитесь что панель RemnaWave запущена и создала сеть" - echo "" - echo "Для проверки выполните:" - echo " docker network ls | grep remnawave" - echo "" - echo "Если сети нет, сначала запустите панель RemnaWave" - return 1 - fi - - log "Сеть remnawave-network найдена ✓" - fi + cd "$PROJECT_DIR" - docker compose up -d - log "Бот запущен" - - sleep 5 - if check_bot_status; then - log "✅ Бот успешно запущен и работает" + if docker compose up -d; then + log "✅ Бот запущен успешно" - if [[ "$COMPOSE_TYPE" == webhook_* ]] && [ ! -z "$WEBHOOK_DOMAIN" ]; then - echo "" - echo -e "${GREEN}=== ИНФОРМАЦИЯ О WEBHOOK ===${NC}" - echo -e "${BLUE}Webhook URL: https://$WEBHOOK_DOMAIN/webhook${NC}" - echo -e "${BLUE}Health Check: https://$WEBHOOK_DOMAIN/health${NC}" - echo "" - echo -e "${YELLOW}Для тестирования webhook выполните:${NC}" - echo -e "${YELLOW}curl https://$WEBHOOK_DOMAIN/health${NC}" - fi + # Ждем запуска и показываем статус + sleep 5 + show_status else - warn "⚠️ Бот запущен но возможны проблемы. Проверьте логи: docker compose logs bot" + error "Ошибка запуска бота" + return 1 fi } -stop_bot() { - log "Остановка бота..." - cd "$BOT_DIR" - docker compose down - log "Бот остановлен" +# Показать статус +show_status() { + echo -e "${CYAN}=== СТАТУС СЕРВИСОВ ===${NC}" + + cd "$PROJECT_DIR" + docker compose ps + + echo -e "\n${CYAN}=== ЛОГИ БОТА (последние 20 строк) ===${NC}" + docker compose logs --tail=20 bot } -restart_bot() { - log "Перезапуск бота..." - cd "$BOT_DIR" +# Функции управления ботом +start_bot_service() { + log "🚀 Запуск бота..." + cd "$PROJECT_DIR" + docker compose up -d + log "✅ Бот запущен" +} + +stop_bot_service() { + log "⏹️ Остановка бота..." + cd "$PROJECT_DIR" + docker compose down + log "✅ Бот остановлен" +} + +restart_bot_service() { + log "🔄 Перезапуск бота..." + cd "$PROJECT_DIR" docker compose restart - log "Бот перезапущен" -} - -update_bot() { - log "Обновление бота..." - cd "$BOT_DIR" - docker compose down - docker compose pull bot - docker compose up -d - log "Бот обновлен" + log "✅ Бот перезапущен" } +# Просмотр логов view_logs() { - cd "$BOT_DIR" - docker compose logs bot -} - -view_live_logs() { - cd "$BOT_DIR" - docker compose logs -f bot -} - -backup_database() { - log "Создание резервной копии базы данных..." - cd "$BOT_DIR" + echo -e "${CYAN}Выберите режим просмотра логов:${NC}" + echo "1) Последние 50 строк" + echo "2) Последние 100 строк" + echo "3) В реальном времени (Ctrl+C для выхода)" + echo "4) Ошибки только" - if ! docker compose ps postgres | grep -q "Up"; then - log "Контейнер PostgreSQL не запущен. Запускаем PostgreSQL..." - docker compose up -d postgres + read -p "Ваш выбор (1-4): " log_choice + + cd "$PROJECT_DIR" + + case $log_choice in + 1) + docker compose logs --tail=50 bot + ;; + 2) + docker compose logs --tail=100 bot + ;; + 3) + echo -e "${YELLOW}Нажмите Ctrl+C для выхода${NC}" + docker compose logs -f bot + ;; + 4) + docker compose logs bot | grep -i error + ;; + *) + echo "Неверный выбор" + ;; + esac +} + +# Создание резервной копии +create_backup() { + log "💾 Создание резервной копии БД..." + + local backup_name="bedolaga_backup_$(date +%Y%m%d_%H%M%S).sql" + local backup_path="$PROJECT_DIR/backups/$backup_name" + + cd "$PROJECT_DIR" + + # Получаем пароль БД из .env + local db_password=$(grep "^DB_PASSWORD=" .env | cut -d'=' -f2) + + if docker compose exec -T postgres pg_dump -U bedolaga_user -d bedolaga_bot > "$backup_path"; then + log "✅ Резервная копия создана: $backup_name" - log "Ожидание готовности базы данных..." - for i in {1..30}; do - if docker compose exec postgres pg_isready -U remnawave_user -d remnawave_bot &>/dev/null; then - log "PostgreSQL готов к работе" - break - fi - if [ $i -eq 30 ]; then - error "PostgreSQL не запустился в течение 60 секунд" - return 1 - fi - sleep 2 - echo -n "." - done - echo "" - fi - - log "Проверка подключения к базе данных..." - if ! docker compose exec postgres pg_isready -U remnawave_user -d remnawave_bot &>/dev/null; then - error "База данных недоступна. Проверьте логи: docker compose logs postgres" - return 1 - fi - - BACKUP_FILE="$BOT_DIR/backup_$(date +%Y%m%d_%H%M%S).sql" - - log "Создание дампа базы данных..." - - if docker compose exec postgres pg_dump -U remnawave_user -d remnawave_bot --verbose --no-owner --no-privileges > "$BACKUP_FILE" 2>/dev/null; then - if [ -s "$BACKUP_FILE" ]; then - FILE_SIZE=$(du -h "$BACKUP_FILE" | cut -f1) - log "Резервная копия создана успешно: $(basename "$BACKUP_FILE")" - echo -e "${GREEN}Размер файла: $FILE_SIZE${NC}" - echo -e "${GREEN}Путь: $BACKUP_FILE${NC}" - - LINES_COUNT=$(wc -l < "$BACKUP_FILE") - echo -e "${BLUE}Количество строк в дампе: $LINES_COUNT${NC}" - else - error "Резервная копия создана, но файл пустой. Возможные причины:" - echo " - База данных пуста (бот еще не создал таблицы)" - echo " - Нет прав доступа к базе данных" - echo " - Неправильные параметры подключения" - rm -f "$BACKUP_FILE" - return 1 - fi + # Сжимаем резервную копию + gzip "$backup_path" + log "✅ Резервная копия сжата: ${backup_name}.gz" + + # Удаляем старые резервные копии (оставляем последние 10) + find "$PROJECT_DIR/backups" -name "*.sql.gz" -type f | sort -r | tail -n +11 | xargs rm -f + + echo -e "${GREEN}Резервная копия сохранена в: $PROJECT_DIR/backups/${backup_name}.gz${NC}" else - error "Ошибка при создании резервной копии" - echo "Попытка диагностики проблемы..." - docker compose exec postgres psql -U remnawave_user -d remnawave_bot -c "\dt" 2>&1 || { - echo "Не удается подключиться к базе данных." - echo "Проверьте логи контейнера: docker compose logs postgres" - } - rm -f "$BACKUP_FILE" 2>/dev/null + error "Ошибка создания резервной копии" return 1 fi } -restore_database() { - log "Восстановление базы данных из резервной копии" +# Восстановление из резервной копии +restore_backup() { + echo -e "${CYAN}=== ВОССТАНОВЛЕНИЕ ИЗ РЕЗЕРВНОЙ КОПИИ ===${NC}" - BACKUP_FILES=($(find "$BOT_DIR" -name "backup_*.sql" -type f 2>/dev/null)) + local backups_dir="$PROJECT_DIR/backups" - if [ ${#BACKUP_FILES[@]} -eq 0 ]; then - error "Файлы резервных копий не найдены в $BOT_DIR" - echo "Поместите файл резервной копии (.sql) в папку $BOT_DIR" + if [[ ! -d "$backups_dir" ]] || [[ -z "$(ls -A "$backups_dir"/*.sql.gz 2>/dev/null)" ]]; then + error "Резервные копии не найдены в $backups_dir" return 1 fi - echo -e "${YELLOW}Найденные резервные копии:${NC}" - for i in "${!BACKUP_FILES[@]}"; do - BACKUP_FILE="${BACKUP_FILES[$i]}" - FILE_SIZE=$(du -h "$BACKUP_FILE" | cut -f1) - FILE_DATE=$(basename "$BACKUP_FILE" | sed 's/backup_\([0-9]\{8\}_[0-9]\{6\}\)\.sql/\1/' | sed 's/_/ /') - echo "$((i+1))) $(basename "$BACKUP_FILE") - Размер: $FILE_SIZE - Дата: $FILE_DATE" - done - echo "$((${#BACKUP_FILES[@]}+1))) Указать путь к другому файлу" - echo "0) Отмена" + echo "Доступные резервные копии:" + local backups=($(ls -1t "$backups_dir"/*.sql.gz)) - while true; do - read -p "Выберите файл для восстановления: " choice - - if [ "$choice" = "0" ]; then - log "Операция отменена" - return 0 - elif [ "$choice" = "$((${#BACKUP_FILES[@]}+1))" ]; then - read -p "Введите полный путь к файлу резервной копии: " SELECTED_BACKUP - if [ ! -f "$SELECTED_BACKUP" ]; then - error "Файл не найден: $SELECTED_BACKUP" - continue - fi - break - elif [ "$choice" -ge 1 ] && [ "$choice" -le "${#BACKUP_FILES[@]}" ]; then - SELECTED_BACKUP="${BACKUP_FILES[$((choice-1))]}" - break - else - error "Неверный выбор. Попробуйте снова." - fi + for i in "${!backups[@]}"; do + local backup_file=$(basename "${backups[i]}") + local backup_date=$(echo "$backup_file" | grep -o '[0-9]\{8\}_[0-9]\{6\}') + local formatted_date=$(echo "$backup_date" | sed 's/\([0-9]\{4\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)_\([0-9]\{2\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)/\1-\2-\3 \4:\5:\6/') + echo " $((i+1))) $backup_file ($formatted_date)" done - warn "ВНИМАНИЕ! Это действие перезапишет текущую базу данных!" - warn "Убедитесь, что у вас есть резервная копия текущих данных!" - read -p "Продолжить восстановление? Введите 'YES' для подтверждения: " confirm + read -p "Выберите резервную копию для восстановления (1-${#backups[@]}): " backup_choice - if [ "$confirm" != "YES" ]; then - log "Операция отменена" + if [[ ! "$backup_choice" =~ ^[0-9]+$ ]] || [[ "$backup_choice" -lt 1 ]] || [[ "$backup_choice" -gt ${#backups[@]} ]]; then + error "Неверный выбор" + return 1 + fi + + local selected_backup="${backups[$((backup_choice-1))]}" + + echo -e "${RED}⚠️ ВНИМАНИЕ: Это действие полностью заменит текущую базу данных!${NC}" + read -p "Вы уверены? Введите 'yes' для подтверждения: " confirm + + if [[ "$confirm" != "yes" ]]; then + echo "Восстановление отменено" return 0 fi - cd "$BOT_DIR" + log "🔄 Восстановление из $selected_backup..." - if ! docker compose ps postgres | grep -q "Up"; then - log "Запуск контейнера PostgreSQL..." - docker compose up -d postgres + cd "$PROJECT_DIR" + + # Останавливаем бота + docker compose stop bot + + # Восстанавливаем БД + if zcat "$selected_backup" | docker compose exec -T postgres psql -U bedolaga_user -d bedolaga_bot; then + log "✅ База данных восстановлена" - log "Ожидание готовности базы данных..." - for i in {1..30}; do - if docker compose exec postgres pg_isready -U remnawave_user -d remnawave_bot &>/dev/null; then - break - fi - sleep 2 - echo -n "." - done - echo "" - fi - - log "Очистка текущей базы данных..." - if docker compose exec -T postgres psql -U remnawave_user -d remnawave_bot -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;" &>/dev/null; then - log "База данных очищена" + # Запускаем бота + docker compose start bot + log "✅ Бот запущен" else - error "Ошибка при очистке базы данных" - return 1 - fi - - log "Восстановление данных из файла: $(basename "$SELECTED_BACKUP")" - if docker compose exec -T postgres psql -U remnawave_user -d remnawave_bot < "$SELECTED_BACKUP"; then - log "База данных успешно восстановлена!" - log "Перезапуск бота для применения изменений..." - docker compose restart bot - log "Восстановление завершено успешно" - else - error "Ошибка при восстановлении базы данных" + error "Ошибка восстановления базы данных" + docker compose start bot return 1 fi } -diagnose_database() { - log "Диагностика состояния базы данных..." - cd "$BOT_DIR" +# Редактирование конфигурации +edit_config() { + echo -e "${CYAN}=== РЕДАКТИРОВАНИЕ КОНФИГУРАЦИИ ===${NC}" + echo "1) Редактировать .env файл" + echo "2) Редактировать docker-compose.yml" + echo "3) Пересоздать .env интерактивно" - echo -e "${YELLOW}Состояние контейнеров:${NC}" - docker compose ps - echo "" + read -p "Ваш выбор (1-3): " edit_choice - if ! docker compose ps postgres | grep -q "Up"; then - log "PostgreSQL не запущен. Запускаем..." - docker compose up -d postgres - - log "Ожидание готовности базы данных..." - for i in {1..30}; do - if docker compose exec postgres pg_isready -U remnawave_user -d remnawave_bot &>/dev/null; then - log "PostgreSQL готов к работе" - break - fi - if [ $i -eq 30 ]; then - error "PostgreSQL не запустился в течение 60 секунд" + case $edit_choice in + 1) + if command -v nano &> /dev/null; then + nano "$ENV_FILE" + elif command -v vim &> /dev/null; then + vim "$ENV_FILE" + else + error "Редактор не найден. Установите nano или vim" return 1 fi - sleep 2 - echo -n "." - done - echo "" - fi - - echo -e "${YELLOW}Проверка доступности PostgreSQL:${NC}" - if docker compose exec postgres pg_isready -U remnawave_user -d remnawave_bot; then - echo -e "${GREEN}✓ PostgreSQL доступен${NC}" - else - echo -e "${RED}✗ PostgreSQL недоступен${NC}" - echo "Проверьте логи: docker compose logs postgres" - return 1 - fi - echo "" - - echo -e "${YELLOW}Информация о базе данных:${NC}" - docker compose exec postgres psql -U remnawave_user -d remnawave_bot -c " - SELECT - current_database() as database_name, - current_user as current_user, - version() as postgresql_version; - " 2>/dev/null || { - echo -e "${RED}Ошибка подключения к базе данных${NC}" - return 1 - } - echo "" - - echo -e "${YELLOW}Таблицы в базе данных:${NC}" - TABLES_OUTPUT=$(docker compose exec postgres psql -U remnawave_user -d remnawave_bot -c "\dt" 2>/dev/null) - if echo "$TABLES_OUTPUT" | grep -q "No relations found"; then - echo -e "${YELLOW}База данных пуста - таблицы еще не созданы${NC}" - echo -e "${BLUE}Это нормально если бот еще ни разу не запускался${NC}" - elif [ -z "$TABLES_OUTPUT" ]; then - echo -e "${YELLOW}Не удается получить список таблиц${NC}" - else - echo "$TABLES_OUTPUT" - fi - echo "" - - echo -e "${YELLOW}Размер базы данных:${NC}" - docker compose exec postgres psql -U remnawave_user -d remnawave_bot -c " - SELECT - pg_database.datname, - pg_size_pretty(pg_database_size(pg_database.datname)) AS size - FROM pg_database - WHERE datname = 'remnawave_bot'; - " 2>/dev/null || { - echo -e "${RED}Не удается получить размер базы данных${NC}" - } + + read -p "Перезапустить бота для применения изменений? (y/n): " restart_choice + if [[ "$restart_choice" =~ ^[Yy]$ ]]; then + restart_bot_service + fi + ;; + 2) + if command -v nano &> /dev/null; then + nano "$DOCKER_COMPOSE_FILE" + elif command -v vim &> /dev/null; then + vim "$DOCKER_COMPOSE_FILE" + else + error "Редактор не найден. Установите nano или vim" + return 1 + fi + + read -p "Пересоздать контейнеры для применения изменений? (y/n): " recreate_choice + if [[ "$recreate_choice" =~ ^[Yy]$ ]]; then + cd "$PROJECT_DIR" + docker compose up -d --force-recreate + fi + ;; + 3) + # Создаем резервную копию текущего .env + cp "$ENV_FILE" "$ENV_FILE.backup.$(date +%s)" + configure_env + restart_bot_service + ;; + *) + echo "Неверный выбор" + ;; + esac } -emergency_fix_database() { - log "Экстренное исправление базы данных..." +# Диагностика +run_diagnostics() { + echo -e "${CYAN}=== ДИАГНОСТИКА СИСТЕМЫ ===${NC}" - cd "$BOT_DIR" + log "🔍 Проверка Docker..." + if systemctl is-active --quiet docker; then + echo -e " ✅ Docker: ${GREEN}Запущен${NC}" + else + echo -e " ❌ Docker: ${RED}Не запущен${NC}" + fi - if ! docker compose ps bot | grep -q "Up"; then - warn "Контейнер бота не запущен. Запускаем бота..." + log "🔍 Проверка контейнеров..." + cd "$PROJECT_DIR" + + local db_status=$(docker compose ps postgres --format "{{.State}}") + local bot_status=$(docker compose ps bot --format "{{.State}}") + + echo -e " PostgreSQL: ${db_status}" + echo -e " Bot: ${bot_status}" + + log "🔍 Проверка портов..." + local webhook_port=$(grep "^TRIBUTE_WEBHOOK_PORT=" "$ENV_FILE" | cut -d'=' -f2) + webhook_port=${webhook_port:-8081} + + if netstat -tuln | grep -q ":$webhook_port "; then + echo -e " ✅ Порт $webhook_port: ${GREEN}Открыт${NC}" + else + echo -e " ❌ Порт $webhook_port: ${RED}Закрыт${NC}" + fi + + log "🔍 Проверка места на диске..." + df -h "$PROJECT_DIR" + + log "🔍 Проверка логов на ошибки..." + local error_count=$(docker compose logs bot 2>/dev/null | grep -ci error || echo "0") + echo -e " Количество ошибок в логах: $error_count" + + if [[ "$error_count" -gt 0 ]]; then + echo -e "${YELLOW}Последние ошибки:${NC}" + docker compose logs bot | grep -i error | tail -5 + fi + + log "🔍 Проверка конфигурации..." + if [[ -f "$ENV_FILE" ]]; then + echo -e " ✅ .env файл: ${GREEN}Существует${NC}" + + # Проверяем основные переменные + local required_vars=("BOT_TOKEN" "REMNAWAVE_URL" "REMNAWAVE_TOKEN") + for var in "${required_vars[@]}"; do + if grep -q "^$var=" "$ENV_FILE" && [[ -n "$(grep "^$var=" "$ENV_FILE" | cut -d'=' -f2)" ]]; then + echo -e " ✅ $var: ${GREEN}Настроено${NC}" + else + echo -e " ❌ $var: ${RED}Не настроено${NC}" + fi + done + else + echo -e " ❌ .env файл: ${RED}Не найден${NC}" + fi +} + +# Автообновление +auto_update() { + log "🔄 Автообновление бота..." + + cd "$PROJECT_DIR" + + # Создаем резервную копию перед обновлением + create_backup + + # Скачиваем новый образ + if docker compose pull bot; then + log "✅ Новый образ загружен" + + # Перезапускаем с новым образом docker compose up -d bot - log "Ожидание готовности бота..." - for i in {1..60}; do - if docker compose logs bot 2>/dev/null | grep -q "Bot started successfully\|Application startup complete\|Bot is running"; then - log "Бот готов к работе" - break - fi - if [ $i -eq 60 ]; then - warn "Бот не запустился полностью, но попробуем выполнить исправление" - break - fi - sleep 2 - echo -n "." - done - echo "" - fi - - EMERGENCY_SCRIPT="$BOT_DIR/emergency_fix.py" - - log "Создание скрипта экстренного исправления..." - cat > "$EMERGENCY_SCRIPT" << 'EOF' -""" -Экстренное исправление проблемы с отображением подписок -Этот патч добавляет недостающие поля в таблицу user_subscriptions -""" - -import asyncio -import sys -import os -from pathlib import Path - -# Добавляем корневую директорию в путь -sys.path.insert(0, str(Path(__file__).parent)) - -try: - from config import load_config - from database import Database -except ImportError: - # Если не можем импортировать, попробуем из app - sys.path.insert(0, '/app') - try: - from config import load_config - from database import Database - except ImportError: - print("⌐ Не удается импортировать модули. Проверьте структуру проекта.") - sys.exit(1) - -from sqlalchemy import text -import logging - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -async def check_and_add_column(db, column_name, column_definition): - """Проверяет и добавляет колонку в таблицу""" - try: - # Отдельная транзакция для проверки - async with db.engine.begin() as conn: - await conn.execute(text(f"SELECT {column_name} FROM user_subscriptions LIMIT 1")) - logger.info(f"✅ Поле {column_name} уже существует") - return True - except Exception: - # Отдельная транзакция для добавления колонки - try: - async with db.engine.begin() as conn: - logger.info(f"➕ Добавляю поле {column_name}...") - await conn.execute(text(f""" - ALTER TABLE user_subscriptions - ADD COLUMN {column_name} {column_definition} - """)) - logger.info(f"✅ Поле {column_name} добавлено") - return True - except Exception as e: - logger.error(f"⌐ Ошибка при добавлении {column_name}: {e}") - return False - -async def emergency_fix(): - """Экстренное исправление базы данных""" - - try: - # Загружаем конфигурацию - config = load_config() + log "✅ Бот обновлен и перезапущен" - # Подключаемся к базе данных - db = Database(config.DATABASE_URL) - - logger.info("🔧 Выполняю экстренное исправление базы данных...") - - # Проверяем существование таблицы user_subscriptions - try: - async with db.engine.begin() as conn: - result = await conn.execute(text("SELECT COUNT(*) FROM user_subscriptions")) - count = result.scalar() - logger.info(f"📊 Найдено {count} подписок в таблице user_subscriptions") - except Exception as e: - logger.error(f"⌐ Таблица user_subscriptions не найдена: {e}") - await db.close() - return - - # Добавляем поля по одному в отдельных транзакциях - success1 = await check_and_add_column(db, "auto_pay_enabled", "BOOLEAN DEFAULT FALSE") - success2 = await check_and_add_column(db, "auto_pay_days_before", "INTEGER DEFAULT 3") - - # Финальная проверка в отдельной транзакции - if success1 and success2: - try: - async with db.engine.begin() as conn: - result = await conn.execute(text(""" - SELECT id, auto_pay_enabled, auto_pay_days_before - FROM user_subscriptions LIMIT 1 - """)) - row = result.fetchone() - if row: - logger.info("✅ Все поля доступны для чтения") - logger.info(f"🔍 Пример записи: id={row[0]}, auto_pay_enabled={row[1]}, auto_pay_days_before={row[2]}") - else: - logger.info("✅ Все поля доступны, но таблица пуста") - - except Exception as e: - logger.error(f"⌐ Поля все еще недоступны: {e}") - else: - logger.error("⌐ Не удалось добавить все необходимые поля") - - await db.close() - logger.info("🎉 Экстренное исправление завершено!") - - except Exception as e: - logger.error(f"💥 Критическая ошибка: {e}") - raise - -if __name__ == "__main__": - asyncio.run(emergency_fix()) -EOF - - log "Копирование скрипта в контейнер бота..." - if docker compose exec bot test -d /app; then - docker compose cp "$EMERGENCY_SCRIPT" bot:/app/emergency_fix.py - - log "Запуск экстренного исправления в контейнере бота..." - if docker compose exec bot python emergency_fix.py; then - log "✅ Экстренное исправление выполнено успешно!" - - log "Перезапуск бота для применения изменений..." - docker compose restart bot - log "✅ Бот перезапущен" - else - error "⌐ Ошибка при выполнении экстренного исправления" - echo "Проверьте логи бота: docker compose logs bot" - fi - - docker compose exec bot rm -f /app/emergency_fix.py 2>/dev/null || true + # Показываем статус + sleep 3 + show_status else - error "⌐ Не удается найти директорию /app в контейнере бота" - echo "Проверьте, что контейнер бота запущен правильно" - fi - - rm -f "$EMERGENCY_SCRIPT" -} - -emergency_fix_database_sql() { - log "Экстренное исправление базы данных через SQL..." - - cd "$BOT_DIR" - - if ! docker compose ps postgres | grep -q "Up"; then - log "Контейнер PostgreSQL не запущен. Запускаем PostgreSQL..." - docker compose up -d postgres - - log "Ожидание готовности базы данных..." - for i in {1..30}; do - if docker compose exec postgres pg_isready -U remnawave_user -d remnawave_bot &>/dev/null; then - log "PostgreSQL готов к работе" - break - fi - if [ $i -eq 30 ]; then - error "PostgreSQL не запустился в течение 60 секунд" - return 1 - fi - sleep 2 - echo -n "." - done - echo "" - fi - - log "Проверка существования полей в таблице user_subscriptions..." - - if docker compose exec postgres psql -U remnawave_user -d remnawave_bot -c "SELECT auto_pay_enabled FROM user_subscriptions LIMIT 1" &>/dev/null; then - log "✅ Поле auto_pay_enabled уже существует" - else - log "➕ Добавление поля auto_pay_enabled..." - if docker compose exec postgres psql -U remnawave_user -d remnawave_bot -c "ALTER TABLE user_subscriptions ADD COLUMN auto_pay_enabled BOOLEAN DEFAULT FALSE" &>/dev/null; then - log "✅ Поле auto_pay_enabled добавлено" - else - error "⌐ Ошибка при добавлении поля auto_pay_enabled" - return 1 - fi - fi - - if docker compose exec postgres psql -U remnawave_user -d remnawave_bot -c "SELECT auto_pay_days_before FROM user_subscriptions LIMIT 1" &>/dev/null; then - log "✅ Поле auto_pay_days_before уже существует" - else - log "➕ Добавление поля auto_pay_days_before..." - if docker compose exec postgres psql -U remnawave_user -d remnawave_bot -c "ALTER TABLE user_subscriptions ADD COLUMN auto_pay_days_before INTEGER DEFAULT 3" &>/dev/null; then - log "✅ Поле auto_pay_days_before добавлено" - else - error "⌐ Ошибка при добавлении поля auto_pay_days_before" - return 1 - fi - fi - - log "✅ Экстренное исправление через SQL завершено!" - - if docker compose ps bot | grep -q "Up"; then - log "Перезапуск бота для применения изменений..." - docker compose restart bot - log "✅ Бот перезапущен" - fi -} - -edit_env_file() { - ensure_nano - - if [ ! -f "$ENV_FILE" ]; then - error "Файл .env не найден: $ENV_FILE" + error "Ошибка загрузки нового образа" return 1 fi - - log "Открытие .env файла для редактирования..." - log "После изменений перезапустите бота для применения настроек" - - cp "$ENV_FILE" "$ENV_FILE.backup.$(date +%Y%m%d_%H%M%S)" - - nano "$ENV_FILE" - - log "Редактирование завершено" - echo -e "${YELLOW}Не забудьте перезапустить бота для применения изменений!${NC}" } -remove_database() { - warn "ВНИМАНИЕ! Это действие удалит всю базу данных!" - warn "Все данные бота (пользователи, подписки, настройки) будут потеряны!" - read -p "Вы уверены? Введите 'YES' для подтверждения: " confirm - if [ "$confirm" = "YES" ]; then - log "Удаление базы данных..." - cd "$BOT_DIR" - - if docker compose ps --services --filter "status=running" | grep -q "."; then - log "Остановка всех контейнеров..." - docker compose down - fi - - log "Удаление volume с данными PostgreSQL..." - VOLUME_NAME=$(docker compose config --volumes 2>/dev/null | grep postgres || echo "bedolaga-bot_postgres_data") - - if docker volume ls | grep -q "$VOLUME_NAME"; then - if docker volume rm "$VOLUME_NAME" 2>/dev/null; then - log "Volume $VOLUME_NAME успешно удален" - else - log "Принудительное удаление volume..." - docker volume rm "$VOLUME_NAME" --force 2>/dev/null || true - fi - fi - - docker volume rm "$(basename $BOT_DIR)_postgres_data" 2>/dev/null || true - - log "Очистка неиспользуемых volumes..." - docker volume prune -f 2>/dev/null || true - - log "База данных удалена" - log "При следующем запуске бота будет создана новая пустая база данных" - else - log "Операция отменена" - fi -} - -remove_bot() { - warn "ВНИМАНИЕ! Это действие полностью удалит бота и все данные!" - read -p "Вы уверены? Введите 'YES' для подтверждения: " confirm - if [ "$confirm" = "YES" ]; then - log "Удаление бота..." - cd "$BOT_DIR" - docker compose down -v - systemctl disable bedolaga-bot.service 2>/dev/null || true - rm -f "$SERVICE_FILE" - rm -rf "$BOT_DIR" - log "Бот полностью удален" - exit 0 - else - log "Операция отменена" - fi -} - -check_remnawave_network() { - if docker network ls | grep -q "remnawave-network"; then - return 0 - else - return 1 - fi -} - -view_webhook_config() { - if [[ "$COMPOSE_TYPE" == webhook_* ]]; then - echo -e "${YELLOW}=== КОНФИГУРАЦИЯ WEBHOOK ===${NC}" - - if [ -f "$ENV_FILE" ]; then - source "$ENV_FILE" - echo -e "${GREEN}Webhook URL: $WEBHOOK_URL${NC}" - echo -e "${GREEN}Webhook Secret: ${WEBHOOK_SECRET:0:8}...${NC}" - echo -e "${GREEN}Домен: $WEBHOOK_DOMAIN${NC}" - fi - - if [ "$COMPOSE_TYPE" = "webhook_caddy" ] && [ -f "$BOT_DIR/Caddyfile" ]; then - echo -e "${BLUE}Caddyfile:${NC}" - cat "$BOT_DIR/Caddyfile" - elif [ "$COMPOSE_TYPE" = "webhook_nginx" ] && [ -f "$BOT_DIR/nginx.conf" ]; then - echo -e "${BLUE}Nginx конфигурация:${NC}" - echo "Файл: $BOT_DIR/nginx.conf" - fi - - if [ ! -z "$WEBHOOK_DOMAIN" ]; then - echo "" - echo -e "${YELLOW}Проверка доступности webhook:${NC}" - if curl -s --connect-timeout 5 "https://$WEBHOOK_DOMAIN/health" > /dev/null 2>&1; then - echo -e "${GREEN}✅ Webhook доступен${NC}" - else - echo -e "${RED}❌ Webhook недоступен${NC}" - echo "Проверьте:" - echo "1. DNS настройки для домена $WEBHOOK_DOMAIN" - echo "2. SSL сертификат" - echo "3. Статус контейнеров: docker compose ps" - fi - fi - else - warn "Webhook не настроен для данной конфигурации" - fi -} - -show_menu() { - clear - echo -e "${BLUE}=== RemnaWave Bedolaga Bot Management ===${NC}" - echo "" - - INSTALLATION_TYPE="Неизвестно" - if [ -f "$COMPOSE_FILE" ]; then - if grep -q "remnawave-network" "$COMPOSE_FILE" && grep -q "external: true" "$COMPOSE_FILE"; then - INSTALLATION_TYPE="Панель + Бот на одном сервере" - elif grep -q "caddy:" "$COMPOSE_FILE"; then - INSTALLATION_TYPE="Бот с Caddy webhook" - elif grep -q "nginx:" "$COMPOSE_FILE"; then - INSTALLATION_TYPE="Бот с Nginx webhook" - elif grep -q "bot_network" "$COMPOSE_FILE"; then - INSTALLATION_TYPE="Только бот (внешняя панель)" - fi - fi - - echo -e "${YELLOW}Тип установки: ${NC}$INSTALLATION_TYPE" - - echo -e "${YELLOW}Статус бота:${NC}" - if check_bot_status; then - echo -e "🟢 Бот: ${GREEN}ЗАПУЩЕН${NC}" - else - echo -e "🔴 Бот: ${RED}ОСТАНОВЛЕН${NC}" - fi - - if [[ "$INSTALLATION_TYPE" == *"Панель + Бот"* ]]; then - echo -e "${YELLOW}Статус сети RemnaWave:${NC}" - if check_remnawave_network; then - echo -e "🟢 Сеть: ${GREEN}СОЗДАНА${NC}" - else - echo -e "🔴 Сеть: ${RED}НЕ НАЙДЕНА${NC} (запустите панель RemnaWave)" - fi - fi - - echo -e "${YELLOW}Подключение к RemnaWave API:${NC}" - if check_remnawave_connection; then - echo -e "🟢 API: ${GREEN}ПОДКЛЮЧЕН${NC}" - else - echo -e "🔴 API: ${RED}НЕ ПОДКЛЮЧЕН${NC}" - fi - - if [[ "$INSTALLATION_TYPE" == *"webhook"* ]]; then - echo -e "${YELLOW}Webhook статус:${NC}" - if [ -f "$ENV_FILE" ]; then - source "$ENV_FILE" - if [ ! -z "$WEBHOOK_DOMAIN" ]; then - echo -e "🌐 Домен: ${BLUE}$WEBHOOK_DOMAIN${NC}" - echo -e "📡 URL: ${BLUE}https://$WEBHOOK_DOMAIN/webhook${NC}" - fi - fi - fi - - echo "" - echo -e "${YELLOW}Доступные действия:${NC}" - - if check_bot_status; then - echo "1) Выключить бота" - echo "2) Перезапустить бота" - echo "3) Посмотреть логи в реальном времени" - echo "4) Обновить бота" - echo "5) Посмотреть логи" - echo "6) Создать резервную копию БД" - echo "7) Восстановить базу данных" - echo "8) Редактировать .env файл" - echo "9) Диагностика базы данных" - echo "10) Экстренное исправление БД (Python)" - echo "11) Экстренное исправление БД (SQL)" - if [[ "$INSTALLATION_TYPE" == *"webhook"* ]]; then - echo "12) Просмотр конфигурации webhook" - echo "13) Удалить базу данных" - echo "14) Полностью удалить бота" - else - echo "12) Удалить базу данных" - echo "13) Полностью удалить бота" - fi - echo "0) Выход" - - read -p "Выберите действие: " choice - case $choice in - 1) stop_bot; read -p "Нажмите Enter для продолжения..."; ;; - 2) restart_bot; read -p "Нажмите Enter для продолжения..."; ;; - 3) view_live_logs; ;; - 4) update_bot; read -p "Нажмите Enter для продолжения..."; ;; - 5) view_logs; read -p "Нажмите Enter для продолжения..."; ;; - 6) backup_database; read -p "Нажмите Enter для продолжения..."; ;; - 7) restore_database; read -p "Нажмите Enter для продолжения..."; ;; - 8) edit_env_file; read -p "Нажмите Enter для продолжения..."; ;; - 9) diagnose_database; read -p "Нажмите Enter для продолжения..."; ;; - 10) emergency_fix_database; read -p "Нажмите Enter для продолжения..."; ;; - 11) emergency_fix_database_sql; read -p "Нажмите Enter для продолжения..."; ;; - 12) - if [[ "$INSTALLATION_TYPE" == *"webhook"* ]]; then - view_webhook_config; read -p "Нажмите Enter для продолжения..."; - else - remove_database; read -p "Нажмите Enter для продолжения..."; - fi - ;; - 13) - if [[ "$INSTALLATION_TYPE" == *"webhook"* ]]; then - remove_database; read -p "Нажмите Enter для продолжения..."; - else - remove_bot; - fi - ;; - 14) - if [[ "$INSTALLATION_TYPE" == *"webhook"* ]]; then - remove_bot; - fi - ;; - 0) exit 0; ;; - *) error "Неверный выбор"; read -p "Нажмите Enter для продолжения..."; ;; - esac - else - echo "1) Запустить бота" - echo "2) Обновить бота" - echo "3) Посмотреть логи" - echo "4) Создать резервную копию БД" - echo "5) Восстановить базу данных" - echo "6) Редактировать .env файл" - echo "7) Диагностика базы данных" - echo "8) Экстренное исправление БД (Python)" - echo "9) Экстренное исправление БД (SQL)" - if [[ "$INSTALLATION_TYPE" == *"webhook"* ]]; then - echo "10) Просмотр конфигурации webhook" - echo "11) Удалить базу данных" - echo "12) Полностью удалить бота" - else - echo "10) Удалить базу данных" - echo "11) Полностью удалить бота" - fi - echo "0) Выход" - - read -p "Выберите действие: " choice - case $choice in - 1) start_bot; read -p "Нажмите Enter для продолжения..."; ;; - 2) update_bot; read -p "Нажмите Enter для продолжения..."; ;; - 3) view_logs; read -p "Нажмите Enter для продолжения..."; ;; - 4) backup_database; read -p "Нажмите Enter для продолжения..."; ;; - 5) restore_database; read -p "Нажмите Enter для продолжения..."; ;; - 6) edit_env_file; read -p "Нажмите Enter для продолжения..."; ;; - 7) diagnose_database; read -p "Нажмите Enter для продолжения..."; ;; - 8) emergency_fix_database; read -p "Нажмите Enter для продолжения..."; ;; - 9) emergency_fix_database_sql; read -p "Нажмите Enter для продолжения..."; ;; - 10) - if [[ "$INSTALLATION_TYPE" == *"webhook"* ]]; then - view_webhook_config; read -p "Нажмите Enter для продолжения..."; - else - remove_database; read -p "Нажмите Enter для продолжения..."; - fi - ;; - 11) - if [[ "$INSTALLATION_TYPE" == *"webhook"* ]]; then - remove_database; read -p "Нажмите Enter для продолжения..."; - else - remove_bot; - fi - ;; - 12) - if [[ "$INSTALLATION_TYPE" == *"webhook"* ]]; then - remove_bot; - fi - ;; - 0) exit 0; ;; - *) error "Неверный выбор"; read -p "Нажмите Enter для продолжения..."; ;; - esac - fi -} - -install_bot() { - log "Начало установки RemnaWave Bedolaga Bot" - - update_system - install_docker - create_project_structure - create_docker_compose - create_env_file - create_service - - log "Установка завершена!" - log "Бот установлен в: $BOT_DIR" - - if [ "$COMPOSE_TYPE" = "panel_bot" ]; then - echo "" - echo -e "${YELLOW}=== ВАЖНО ДЛЯ ПАНЕЛЬ + БОТ УСТАНОВКИ ===${NC}" - echo -e "${GREEN}1. Перед запуском бота убедитесь что панель RemnaWave запущена${NC}" - echo -e "${GREEN}2. Панель должна создать сеть 'remnawave-network'${NC}" - echo -e "${GREEN}3. Проверить сеть: docker network ls | grep remnawave${NC}" - echo -e "${YELLOW}4. Если сети нет - сначала запустите панель RemnaWave!${NC}" - echo "" - elif [[ "$COMPOSE_TYPE" == webhook_* ]]; then - echo "" - echo -e "${YELLOW}=== ВАЖНО ДЛЯ WEBHOOK УСТАНОВКИ ===${NC}" - echo -e "${GREEN}1. Webhook URL: https://$WEBHOOK_DOMAIN/webhook${NC}" - echo -e "${GREEN}2. Убедитесь что домен указывает на этот сервер${NC}" - echo -e "${GREEN}3. SSL будет настроен автоматически${NC}" - - if [ "$WEBHOOK_CONFIG_TYPE" = "external_caddy" ]; then - echo -e "${YELLOW}4. Добавьте конфигурацию в существующий Caddyfile${NC}" - elif [ "$WEBHOOK_CONFIG_TYPE" = "external_nginx" ]; then - echo -e "${YELLOW}4. Добавьте конфигурацию в существующий Nginx${NC}" - echo -e "${BLUE} Файл конфигурации: $BOT_DIR/nginx-site.conf${NC}" - fi - - echo -e "${GREEN}5. После запуска протестируйте: curl https://$WEBHOOK_DOMAIN/health${NC}" - echo "" - fi - - log "Для управления ботом используйте это меню или systemctl" - - read -p "Нажмите Enter для перехода в меню управления..." -} - -main() { - check_root - - if check_installation; then - log "Бот уже установлен. Переход в меню управления..." - ensure_nano - - if [ -f "$COMPOSE_FILE" ]; then - if grep -q "remnawave-network" "$COMPOSE_FILE" && grep -q "external: true" "$COMPOSE_FILE"; then - export COMPOSE_TYPE="panel_bot" - elif grep -q "caddy:" "$COMPOSE_FILE"; then - export COMPOSE_TYPE="webhook_caddy" - if [ -f "$BOT_DIR/Caddyfile" ]; then - export WEBHOOK_DOMAIN=$(grep -E "^[a-zA-Z0-9.-]+\s*{" "$BOT_DIR/Caddyfile" | head -1 | awk '{print $1}') - fi - elif grep -q "nginx:" "$COMPOSE_FILE"; then - export COMPOSE_TYPE="webhook_nginx" - if [ -f "$BOT_DIR/nginx.conf" ]; then - export WEBHOOK_DOMAIN=$(grep "server_name" "$BOT_DIR/nginx.conf" | grep -v "_" | head -1 | awk '{print $2}' | sed 's/;//') - fi - elif grep -q "bot_network" "$COMPOSE_FILE"; then - export COMPOSE_TYPE="standalone" - else - export COMPOSE_TYPE="full" - fi - fi - else - install_bot - fi - +# Главное меню управления +management_menu() { while true; do - show_menu + clear + print_header + + echo -e "${CYAN}=== МЕНЮ УПРАВЛЕНИЯ БОТОМ ===${NC}" + echo + echo "🚀 1) Запустить бота" + echo "⏹️ 2) Остановить бота" + echo "🔄 3) Перезапустить бота" + echo "📊 4) Показать статус" + echo "📺 5) Просмотр логов" + echo "💾 6) Создать резервную копию" + echo "🔙 7) Восстановить из резервной копии" + echo "✏️ 8) Редактировать конфигурацию" + echo "🩺 9) Диагностика" + echo "🔄 10) Автообновление" + echo "❌ 0) Выход" + echo + + read -p "Выберите действие (0-10): " choice + + case $choice in + 1) + start_bot_service + read -p "Нажмите Enter для продолжения..." + ;; + 2) + stop_bot_service + read -p "Нажмите Enter для продолжения..." + ;; + 3) + restart_bot_service + read -p "Нажмите Enter для продолжения..." + ;; + 4) + show_status + read -p "Нажмите Enter для продолжения..." + ;; + 5) + view_logs + read -p "Нажмите Enter для продолжения..." + ;; + 6) + create_backup + read -p "Нажмите Enter для продолжения..." + ;; + 7) + restore_backup + read -p "Нажмите Enter для продолжения..." + ;; + 8) + edit_config + ;; + 9) + run_diagnostics + read -p "Нажмите Enter для продолжения..." + ;; + 10) + auto_update + read -p "Нажмите Enter для продолжения..." + ;; + 0) + echo -e "${GREEN}До свидания!${NC}" + exit 0 + ;; + *) + echo -e "${RED}Неверный выбор. Попробуйте еще раз.${NC}" + sleep 2 + ;; + esac done } +# Функция для настройки Nginx (системный) +configure_nginx_system() { + local domain="$1" + local config_file="/etc/nginx/sites-available/$domain" + + log "🔧 Настройка Nginx для домена: $domain" + + # Создаем или обновляем конфигурацию + if [[ -f "$config_file" ]]; then + # Добавляем location для webhook + if ! grep -q "/tribute-webhook" "$config_file"; then + sed -i '/server_name/a\\n # Tribute webhook\n location /tribute-webhook {\n proxy_pass http://127.0.0.1:8081;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n }\n\n location /webhook-health {\n proxy_pass http://127.0.0.1:8081/health;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n }' "$config_file" + log "✅ Добавлены webhook настройки в существующую конфигурацию" + else + log "✅ Webhook настройки уже присутствуют" + fi + else + # Создаем новую конфигурацию + cat > "$config_file" << EOF +server { + listen 80; + server_name $domain; + + # Tribute webhook + location /tribute-webhook { + proxy_pass http://127.0.0.1:8081; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + } + + location /webhook-health { + proxy_pass http://127.0.0.1:8081/health; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + } + + location / { + root /var/www/html; + index index.html index.htm; + try_files \$uri \$uri/ =404; + } +} +EOF + + # Активируем сайт + ln -sf "$config_file" "/etc/nginx/sites-enabled/" + log "✅ Создана новая конфигурация для $domain" + fi + + # Проверяем конфигурацию и перезагружаем + if nginx -t; then + systemctl reload nginx + log "✅ Nginx настроен и перезагружен" + else + error "Ошибка в конфигурации Nginx" + return 1 + fi +} + +# Функция для настройки Caddy (системный) +configure_caddy_system() { + local domain="$1" + local caddyfile="/etc/caddy/Caddyfile" + + log "🔧 Настройка системного Caddy для домена: $domain" + + if [[ ! -f "$caddyfile" ]]; then + error "Caddyfile не найден: $caddyfile" + return 1 + fi + + # Создаем резервную копию + cp "$caddyfile" "$caddyfile.backup.$(date +%s)" + + # Проверяем, есть ли уже настройка для домена + if grep -q "^https://$domain\|^$domain" "$caddyfile"; then + log "⚠️ Конфигурация для домена $domain уже существует" + + # Проверяем наличие webhook настроек + if ! grep -q "/tribute-webhook" "$caddyfile"; then + # Добавляем webhook настройки в существующий блок + sed -i "/^https:\/\/$domain\|^$domain/,/^}/ { + /handle.*{/a\\ + # Tribute webhook endpoint\\ + handle /tribute-webhook* {\\ + reverse_proxy localhost:8081 {\\ + header_up Host {host}\\ + header_up X-Real-IP {remote_host}\\ + }\\ + }\\ + \\ + # Health check для webhook сервиса\\ + handle /webhook-health {\\ + reverse_proxy localhost:8081/health {\\ + header_up Host {host}\\ + header_up X-Real-IP {remote_host}\\ + }\\ + } + }" "$caddyfile" + + log "✅ Добавлены webhook настройки в существующую конфигурацию" + else + log "✅ Webhook настройки уже присутствуют" + fi + else + # Создаем новую конфигурацию для домена + cat >> "$caddyfile" << EOF + +https://$domain { + # Tribute webhook endpoint + handle /tribute-webhook* { + reverse_proxy localhost:8081 { + header_up Host {host} + header_up X-Real-IP {remote_host} + } + } + + # Health check для webhook сервиса + handle /webhook-health { + reverse_proxy localhost:8081/health { + header_up Host {host} + header_up X-Real-IP {remote_host} + } + } + + # Остальные запросы обрабатываются как обычно + handle { + root * /var/www/html + try_files {path} /index.html + file_server + } + + log { + output file /var/log/caddy/access.log { + roll_size 10MB + roll_keep 5 + roll_keep_for 720h + roll_compression gzip + } + level ERROR + } +} +EOF + log "✅ Добавлена новая конфигурация для домена $domain" + fi + + # Перезагружаем Caddy + if systemctl reload caddy; then + log "✅ Caddy перезагружен" + else + error "Ошибка при перезагрузке Caddy" + # Восстанавливаем из резервной копии + cp "$caddyfile.backup.$(date +%s)" "$caddyfile" + return 1 + fi +} + +# Функция для настройки Nginx (Docker) +configure_nginx_docker() { + local container_name="$1" + local domain="$2" + + log "🔧 Настройка Nginx Docker для домена: $domain" + + # Находим директорию с конфигурацией nginx + local nginx_dir=$(docker inspect "$container_name" | jq -r '.[0].Mounts[] | select(.Destination | contains("nginx")) | .Source' | head -1) + + if [[ -z "$nginx_dir" || "$nginx_dir" == "null" ]]; then + error "Не удалось найти директорию с конфигурацией nginx для контейнера $container_name" + return 1 + fi + + local config_file="$nginx_dir/conf.d/$domain.conf" + + # Создаем конфигурацию для домена + cat > "$config_file" << EOF +server { + listen 80; + server_name $domain; + + # Tribute webhook + location /tribute-webhook { + proxy_pass http://host.docker.internal:8081; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + } + + location /webhook-health { + proxy_pass http://host.docker.internal:8081/health; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + } + + location / { + root /var/www/html; + index index.html index.htm; + try_files \$uri \$uri/ =404; + } +} +EOF + + # Перезагружаем nginx в контейнере + if docker exec "$container_name" nginx -t && docker exec "$container_name" nginx -s reload; then + log "✅ Nginx в Docker перезагружен" + else + error "Ошибка при перезагрузке Nginx в Docker" + rm -f "$config_file" + return 1 + fi +} + +# Основная функция установки +main_install() { + print_header + + log "🚀 Начало установки Bedolaga Bot..." + + # Проверки + check_root + check_system + + # Обновление и установка + update_system + install_docker + + # Создание структуры + create_project_structure + + # Обнаружение веб-серверов + detect_webservers + + # Интерактивная настройка + configure_env + + # Создание файлов + create_docker_compose + + # Создание службы + create_systemd_service + + # Запуск + start_bot + + log "🎉 Установка завершена успешно!" + + echo -e "${GREEN}" + echo "╔════════════════════════════════════════════════════════════════╗" + echo "║ УСТАНОВКА ЗАВЕРШЕНА! ║" + echo "╠════════════════════════════════════════════════════════════════╣" + echo "║ Бот установлен в: $PROJECT_DIR" + echo "║ Для управления ботом запустите: $0 --manage" + echo "║ Или перейдите в директорию и используйте docker compose" + echo "╚════════════════════════════════════════════════════════════════╝" + echo -e "${NC}" + + read -p "Запустить меню управления сейчас? (y/n): " start_menu + if [[ "$start_menu" =~ ^[Yy]$ ]]; then + management_menu + fi +} + +# Основная логика скрипта +main() { + case "${1:-}" in + --manage|manage|-m) + if [[ ! -d "$PROJECT_DIR" ]]; then + error "Проект не найден в $PROJECT_DIR. Сначала выполните установку." + exit 1 + fi + management_menu + ;; + --install|install|-i|"") + main_install + ;; + --help|help|-h) + print_header + echo -e "${CYAN}Использование:${NC}" + echo " $0 # Установка бота" + echo " $0 --install # Установка бота" + echo " $0 --manage # Меню управления" + echo " $0 --help # Показать справку" + ;; + *) + error "Неизвестный параметр: $1" + echo "Используйте $0 --help для справки" + exit 1 + ;; + esac +} + +# Обработка сигналов +trap 'echo -e "\n${YELLOW}Установка прервана пользователем${NC}"; exit 130' INT +trap 'echo -e "\n${RED}Произошла ошибка${NC}"; exit 1' ERR + +# Запуск основной функции main "$@"