From 7684ed382e9ea042d0d30fa426cc1293526da17c Mon Sep 17 00:00:00 2001 From: Egor Date: Wed, 1 Oct 2025 21:55:46 +0300 Subject: [PATCH] Update install_bot.sh --- install_bot.sh | 772 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 562 insertions(+), 210 deletions(-) diff --git a/install_bot.sh b/install_bot.sh index 3c07d537..b903a465 100755 --- a/install_bot.sh +++ b/install_bot.sh @@ -3,141 +3,86 @@ set -euo pipefail SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) STATE_FILE="$SCRIPT_DIR/.bot_install_state" -DEFAULT_INSTALL_PATH="$SCRIPT_DIR" -DEFAULT_ENV_FILE=".env" +BACKUP_DIR="$SCRIPT_DIR/backups" -# Util functions -log() { - printf '\n%s\n' "$1" +# Цвета для красивого вывода +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +PURPLE='\033[0;35m' +CYAN='\033[0;36m' +WHITE='\033[1;37m' +NC='\033[0m' # No Color +BOLD='\033[1m' + +# Символы для UI +CHECK="✓" +CROSS="✗" +ARROW="➜" +STAR="★" +GEAR="⚙" + +# Утилиты для вывода +print_header() { + echo -e "\n${CYAN}${BOLD}╔════════════════════════════════════════════════════════════╗${NC}" + echo -e "${CYAN}${BOLD}║${NC} ${WHITE}${BOLD}$1${NC}${CYAN}${BOLD}║${NC}" + echo -e "${CYAN}${BOLD}╚════════════════════════════════════════════════════════════╝${NC}\n" } -error() { - printf 'Error: %s\n' "$1" >&2 +print_section() { + echo -e "\n${BLUE}${BOLD}${ARROW} $1${NC}" + echo -e "${BLUE}─────────────────────────────────────────────────────${NC}" } -ask_yes_no() { - local prompt="$1" - local default="$2" - local reply - while true; do - read -rp "$prompt" reply || reply="" - if [[ -z "$reply" ]]; then - reply="$default" - fi - case "${reply,,}" in - y|yes) - return 0 - ;; - n|no) - return 1 - ;; - *) - echo "Пожалуйста, ответьте 'y' или 'n'." - ;; - esac - done +print_success() { + echo -e "${GREEN}${CHECK} $1${NC}" } -prompt_value() { - local var_name="$1" - local prompt="$2" - local default_value="$3" - local reply - if [[ -n "$default_value" ]]; then - read -rp "$prompt [$default_value]: " reply || reply="" +print_error() { + echo -e "${RED}${CROSS} $1${NC}" >&2 +} + +print_warning() { + echo -e "${YELLOW}⚠ $1${NC}" +} + +print_info() { + echo -e "${CYAN}ℹ $1${NC}" +} + +print_status() { + local status=$1 + local text=$2 + if [[ "$status" == "running" ]]; then + echo -e "${GREEN}● ${text}${NC}" + elif [[ "$status" == "stopped" ]]; then + echo -e "${RED}● ${text}${NC}" else - read -rp "$prompt: " reply || reply="" - fi - if [[ -z "$reply" ]]; then - printf '%s' "$default_value" - else - printf '%s' "$reply" - fi -} - -escape_env_value() { - printf '%s' "$1" | sed 's/\\\\/\\\\\\\\/g; s/"/\\"/g' -} - -write_env_file() { - local env_file="$1" - shift - local entries=("$@") - { - for entry in "${entries[@]}"; do - local key=${entry%%=*} - local value=${entry#*=} - printf '%s="%s"\n' "$key" "$(escape_env_value "$value")" - done - } >"$env_file" -} - -get_env_value() { - local key="$1" - local env_file="$2" - if [[ -f "$env_file" ]]; then - local line - line=$(grep -E "^${key}=" "$env_file" | tail -n1 || true) - if [[ -n "$line" ]]; then - printf '%s' "${line#*=}" | sed 's/^"//; s/"$//' - return - fi - fi - printf '' -} - -ensure_directory_structure() { - local path="$1" - mkdir -p "$path/logs" "$path/data" "$path/data/backups" "$path/data/referral_qr" - chmod -R 755 "$path/logs" "$path/data" - if command -v sudo >/dev/null 2>&1; then - sudo chown -R 1000:1000 "$path/logs" "$path/data" || true - else - chown -R 1000:1000 "$path/logs" "$path/data" 2>/dev/null || true - fi -} - -sync_project_files() { - local destination="$1" - if [[ "$SCRIPT_DIR" == "$destination" ]]; then - return - fi - - log "Синхронизируем файлы проекта в $destination" - local excludes=(--exclude '.git' --exclude '__pycache__' --exclude 'logs' --exclude 'data' --exclude '.bot_install_state' --exclude '.env') - if command -v rsync >/dev/null 2>&1; then - rsync -a --delete "${excludes[@]}" "$SCRIPT_DIR/" "$destination/" - else - (cd "$SCRIPT_DIR" && tar cf - --exclude='.git' --exclude='__pycache__' --exclude='logs' --exclude='data' --exclude='.bot_install_state' --exclude='.env' .) \ - | (cd "$destination" && tar xf -) + echo -e "${YELLOW}● ${text}${NC}" fi } +# Загрузка состояния load_state() { if [[ -f "$STATE_FILE" ]]; then # shellcheck disable=SC1090 source "$STATE_FILE" + return 0 else - INSTALL_PATH="$DEFAULT_INSTALL_PATH" - ENV_FILE="$DEFAULT_ENV_FILE" + print_error "Установка бота не найдена. Запустите ./install.sh сначала." + exit 1 fi } -save_state() { - cat >"$STATE_FILE" </dev/null 2>&1; then COMPOSE_BIN=(docker compose) elif docker-compose version >/dev/null 2>&1; then COMPOSE_BIN=(docker-compose) else - error "Docker Compose не найден. Установите docker compose или docker-compose." + print_error "Docker Compose не найден." exit 1 fi } @@ -146,117 +91,524 @@ run_compose() { (cd "$INSTALL_PATH" && "${COMPOSE_BIN[@]}" "$@") } -show_services_state() { - log "Текущее состояние сервисов:" - run_compose ps +# Получение статуса сервисов +get_service_status() { + local service=$1 + local status + status=$(run_compose ps -q "$service" 2>/dev/null | xargs -r docker inspect -f '{{.State.Status}}' 2>/dev/null || echo "not_found") + echo "$status" } -follow_logs() { - if ask_yes_no "Хотите посмотреть последние логи бота? [y/N]: " "n"; then - run_compose logs --tail=50 bot || true - fi -} - -start_services() { - log "Запускаем сервисы бота..." - run_compose up -d - show_services_state - follow_logs -} - -perform_installation() { - INSTALL_PATH=$(prompt_value "INSTALL_PATH" "Укажите путь установки бота" "$DEFAULT_INSTALL_PATH") - INSTALL_PATH=${INSTALL_PATH%/} - if [[ ! -d "$INSTALL_PATH" ]]; then - mkdir -p "$INSTALL_PATH" - log "Создана директория установки $INSTALL_PATH" - fi - - sync_project_files "$INSTALL_PATH" - - ENV_FILE="$INSTALL_PATH/$DEFAULT_ENV_FILE" - ensure_directory_structure "$INSTALL_PATH" - - local existing_env - if [[ -f "$ENV_FILE" ]]; then - existing_env="true" - else - existing_env="false" - fi - - log "Заполняем параметры окружения (.env)." - local bot_token admin_ids api_url api_key auth_type username password secret_key web_api_token - bot_token=$(prompt_value "BOT_TOKEN" "Введите BOT_TOKEN" "$(get_env_value BOT_TOKEN "$ENV_FILE")") - admin_ids=$(prompt_value "ADMIN_IDS" "Введите ADMIN_IDS (через запятую)" "$(get_env_value ADMIN_IDS "$ENV_FILE")") - - printf '\nREMNAWAVE_API_URL должен быть в формате https://panel.example.com или http://remnawave:3000 при локальном запуске.\n' - api_url=$(prompt_value "REMNAWAVE_API_URL" "Введите REMNAWAVE_API_URL" "$(get_env_value REMNAWAVE_API_URL "$ENV_FILE")") - api_key=$(prompt_value "REMNAWAVE_API_KEY" "Введите REMNAWAVE_API_KEY" "$(get_env_value REMNAWAVE_API_KEY "$ENV_FILE")") - - local default_auth - default_auth=$(get_env_value REMNAWAVE_AUTH_TYPE "$ENV_FILE") - auth_type=$(prompt_value "REMNAWAVE_AUTH_TYPE" "Введите тип авторизации REMNAWAVE (api_key/basic_auth)" "${default_auth:-api_key}") - - username="" - password="" - if [[ "${auth_type}" == "basic_auth" ]]; then - username=$(prompt_value "REMNAWAVE_USERNAME" "Введите REMNAWAVE_USERNAME" "$(get_env_value REMNAWAVE_USERNAME "$ENV_FILE")") - password=$(prompt_value "REMNAWAVE_PASSWORD" "Введите REMNAWAVE_PASSWORD" "$(get_env_value REMNAWAVE_PASSWORD "$ENV_FILE")") - else - if ask_yes_no "Указать REMNAWAVE_USERNAME/REMNAWAVE_PASSWORD? [y/N]: " "n"; then - username=$(prompt_value "REMNAWAVE_USERNAME" "Введите REMNAWAVE_USERNAME" "$(get_env_value REMNAWAVE_USERNAME "$ENV_FILE")") - password=$(prompt_value "REMNAWAVE_PASSWORD" "Введите REMNAWAVE_PASSWORD" "$(get_env_value REMNAWAVE_PASSWORD "$ENV_FILE")") +# Мониторинг сервисов +show_monitoring() { + print_header "МОНИТОРИНГ СЕРВИСОВ БОТА" + + print_section "Статус контейнеров" + + local services=("bot" "postgres" "redis") + local all_running=true + + for service in "${services[@]}"; do + local status + status=$(get_service_status "$service") + local uptime="" + + if [[ "$status" == "running" ]]; then + uptime=$(run_compose ps "$service" 2>/dev/null | tail -n1 | awk '{for(i=1;i<=NF;i++){if($i~/Up/){print $(i+1), $(i+2); break}}}') + print_status "running" "$service: работает (uptime: $uptime)" + elif [[ "$status" == "exited" ]] || [[ "$status" == "stopped" ]]; then + print_status "stopped" "$service: остановлен" + all_running=false + else + print_status "unknown" "$service: не найден" + all_running=false fi + done + + # Статистика ресурсов + print_section "Использование ресурсов" + + local stats + stats=$(docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}" 2>/dev/null | grep -E "bot|postgres|redis" || echo "") + + if [[ -n "$stats" ]]; then + echo -e "${WHITE}${BOLD}КОНТЕЙНЕР CPU ПАМЯТЬ${NC}" + echo "$stats" | tail -n+2 | while IFS=$'\t' read -r name cpu mem; do + echo -e "${CYAN}${name}${NC} ${YELLOW}${cpu}${NC} ${PURPLE}${mem}${NC}" + done + else + print_warning "Статистика недоступна" + fi + + # Размер логов + print_section "Размер логов" + if [[ -d "$INSTALL_PATH/logs" ]]; then + local log_size + log_size=$(du -sh "$INSTALL_PATH/logs" 2>/dev/null | cut -f1) + echo -e "${CYAN}Логи: ${YELLOW}${log_size}${NC}" + fi + + # Последние ошибки + print_section "Последние ошибки (если есть)" + local errors + errors=$(run_compose logs --tail=100 bot 2>/dev/null | grep -i "error\|exception\|critical" | tail -n 5 || echo "") + + if [[ -n "$errors" ]]; then + echo "$errors" | while read -r line; do + print_error "$line" + done + else + print_success "Ошибок не обнаружено" + fi + + echo "" + if $all_running; then + print_success "Все сервисы работают нормально!" + else + print_warning "Некоторые сервисы не запущены" fi - - secret_key=$(prompt_value "REMNAWAVE_SECRET_KEY" "Введите REMNAWAVE_SECRET_KEY (для установок eGames, формат XXXXXXX:DDDDDDDD, можно оставить пустым)" "$(get_env_value REMNAWAVE_SECRET_KEY "$ENV_FILE")") - web_api_token=$(prompt_value "WEB_API_DEFAULT_TOKEN" "Введите WEB_API_DEFAULT_TOKEN" "$(get_env_value WEB_API_DEFAULT_TOKEN "$ENV_FILE")") - - local entries=() - entries+=("BOT_TOKEN=$bot_token") - entries+=("ADMIN_IDS=$admin_ids") - entries+=("REMNAWAVE_API_URL=$api_url") - entries+=("REMNAWAVE_API_KEY=$api_key") - entries+=("REMNAWAVE_AUTH_TYPE=$auth_type") - entries+=("REMNAWAVE_USERNAME=$username") - entries+=("REMNAWAVE_PASSWORD=$password") - entries+=("REMNAWAVE_SECRET_KEY=$secret_key") - entries+=("WEB_API_DEFAULT_TOKEN=$web_api_token") - - write_env_file "$ENV_FILE" "${entries[@]}" - log "Файл окружения сохранен: $ENV_FILE" - - save_state - log "Информация об установке сохранена ($STATE_FILE)." - - resolve_compose_command - start_services } -show_existing_installation() { - log "Найдена предыдущая установка бота." - log "Путь установки: $INSTALL_PATH" - log "Файл окружения: $ENV_FILE" - resolve_compose_command - show_services_state - if ask_yes_no "Перезапустить сервисы бота? [y/N]: " "n"; then - run_compose restart - show_services_state +# Обновление из Git +update_from_git() { + print_header "ОБНОВЛЕНИЕ ИЗ GIT РЕПОЗИТОРИЯ" + + if [[ ! -d "$INSTALL_PATH/.git" ]]; then + print_error "Git репозиторий не найден в $INSTALL_PATH" + print_info "Инициализируем репозиторий..." + + local repo_url + read -rp "Введите URL Git репозитория: " repo_url + + if [[ -z "$repo_url" ]]; then + print_error "URL не указан" + return 1 + fi + + (cd "$INSTALL_PATH" && git init && git remote add origin "$repo_url") fi - follow_logs + + print_section "Проверка обновлений" + + (cd "$INSTALL_PATH" && git fetch origin 2>&1) + + local current_commit + local remote_commit + current_commit=$(cd "$INSTALL_PATH" && git rev-parse HEAD 2>/dev/null || echo "unknown") + remote_commit=$(cd "$INSTALL_PATH" && git rev-parse origin/main 2>/dev/null || git rev-parse origin/master 2>/dev/null || echo "unknown") + + if [[ "$current_commit" == "$remote_commit" ]]; then + print_success "Бот уже имеет последнюю версию" + return 0 + fi + + print_info "Найдены обновления" + echo -e "${CYAN}Текущий коммит: ${YELLOW}${current_commit:0:8}${NC}" + echo -e "${CYAN}Новый коммит: ${YELLOW}${remote_commit:0:8}${NC}" + + # Показываем изменения + print_section "Список изменений" + (cd "$INSTALL_PATH" && git log --oneline HEAD..origin/main 2>/dev/null || git log --oneline HEAD..origin/master 2>/dev/null || true) + + echo "" + read -rp "$(echo -e ${YELLOW}Применить обновления? [y/N]: ${NC})" confirm + + if [[ "${confirm,,}" != "y" ]]; then + print_warning "Обновление отменено" + return 1 + fi + + # Создаем резервную копию перед обновлением + print_info "Создаем резервную копию перед обновлением..." + create_backup "pre-update" + + print_section "Применение обновлений" + + # Останавливаем бота + print_info "Останавливаем сервисы..." + run_compose down + + # Обновляем код + print_info "Обновляем код..." + (cd "$INSTALL_PATH" && git pull origin main 2>/dev/null || git pull origin master 2>/dev/null) + + # Перезапускаем + print_info "Пересобираем и запускаем сервисы..." + run_compose up -d --build + + print_success "Обновление завершено!" + + # Показываем логи + echo "" + read -rp "$(echo -e ${YELLOW}Показать логи запуска? [y/N]: ${NC})" show_logs + if [[ "${show_logs,,}" == "y" ]]; then + run_compose logs --tail=50 -f bot + fi +} + +# Создание резервной копии +create_backup() { + local backup_type=${1:-manual} + local timestamp + timestamp=$(date +%Y%m%d_%H%M%S) + local backup_name="backup_${backup_type}_${timestamp}" + local backup_path="$BACKUP_DIR/$backup_name" + + print_header "СОЗДАНИЕ РЕЗЕРВНОЙ КОПИИ" + + mkdir -p "$BACKUP_DIR" + mkdir -p "$backup_path" + + print_section "Архивирование данных" + + # Копируем конфигурацию + print_info "Сохраняем конфигурацию..." + cp "$INSTALL_PATH/.env" "$backup_path/" 2>/dev/null || true + cp "$INSTALL_PATH/docker-compose.yml" "$backup_path/" 2>/dev/null || true + + # Экспортируем базу данных + if [[ $(get_service_status "postgres") == "running" ]]; then + print_info "Экспортируем базу данных PostgreSQL..." + run_compose exec -T postgres pg_dump -U postgres remnawave_bot > "$backup_path/database.sql" 2>/dev/null || { + print_warning "Не удалось экспортировать БД" + } + fi + + # Копируем данные + if [[ -d "$INSTALL_PATH/data" ]]; then + print_info "Копируем пользовательские данные..." + cp -r "$INSTALL_PATH/data" "$backup_path/" 2>/dev/null || true + fi + + # Создаем архив + print_info "Создаем архив..." + (cd "$BACKUP_DIR" && tar -czf "${backup_name}.tar.gz" "$backup_name" && rm -rf "$backup_name") + + local backup_size + backup_size=$(du -h "$BACKUP_DIR/${backup_name}.tar.gz" | cut -f1) + + print_success "Резервная копия создана: $BACKUP_DIR/${backup_name}.tar.gz" + echo -e "${CYAN}Размер: ${YELLOW}${backup_size}${NC}" + + # Очистка старых бэкапов (оставляем последние 10) + print_info "Очистка старых бэкапов..." + (cd "$BACKUP_DIR" && ls -t backup_*.tar.gz 2>/dev/null | tail -n +11 | xargs -r rm -f) + + local backup_count + backup_count=$(ls -1 "$BACKUP_DIR"/backup_*.tar.gz 2>/dev/null | wc -l) + print_info "Всего резервных копий: $backup_count" +} + +# Восстановление из резервной копии +restore_backup() { + print_header "ВОССТАНОВЛЕНИЕ ИЗ РЕЗЕРВНОЙ КОПИИ" + + if [[ ! -d "$BACKUP_DIR" ]] || [[ -z "$(ls -A "$BACKUP_DIR"/*.tar.gz 2>/dev/null)" ]]; then + print_error "Резервные копии не найдены" + return 1 + fi + + print_section "Доступные резервные копии" + + local backups=() + local i=1 + while IFS= read -r backup; do + local backup_name + local backup_size + local backup_date + backup_name=$(basename "$backup") + backup_size=$(du -h "$backup" | cut -f1) + backup_date=$(stat -c %y "$backup" 2>/dev/null | cut -d' ' -f1,2 | cut -d'.' -f1 || stat -f "%Sm" "$backup") + + echo -e "${CYAN}[$i]${NC} ${WHITE}$backup_name${NC}" + echo -e " Размер: ${YELLOW}$backup_size${NC}, Дата: ${PURPLE}$backup_date${NC}" + backups+=("$backup") + ((i++)) + done < <(ls -t "$BACKUP_DIR"/*.tar.gz 2>/dev/null) + + echo "" + read -rp "Выберите номер резервной копии для восстановления [1-$((i-1))]: " selection + + if [[ ! "$selection" =~ ^[0-9]+$ ]] || [[ "$selection" -lt 1 ]] || [[ "$selection" -ge "$i" ]]; then + print_error "Неверный выбор" + return 1 + fi + + local selected_backup="${backups[$((selection-1))]}" + + print_warning "ВНИМАНИЕ: Текущие данные будут перезаписаны!" + read -rp "$(echo -e ${RED}${BOLD}Продолжить восстановление? [y/N]: ${NC})" confirm + + if [[ "${confirm,,}" != "y" ]]; then + print_warning "Восстановление отменено" + return 1 + fi + + # Создаем резервную копию перед восстановлением + print_info "Создаем резервную копию текущего состояния..." + create_backup "pre-restore" + + print_section "Восстановление данных" + + # Останавливаем сервисы + print_info "Останавливаем сервисы..." + run_compose down + + # Распаковываем бэкап + print_info "Распаковываем резервную копию..." + local temp_dir + temp_dir=$(mktemp -d) + tar -xzf "$selected_backup" -C "$temp_dir" + + local backup_folder + backup_folder=$(ls "$temp_dir") + + # Восстанавливаем конфигурацию + if [[ -f "$temp_dir/$backup_folder/.env" ]]; then + print_info "Восстанавливаем конфигурацию..." + cp "$temp_dir/$backup_folder/.env" "$INSTALL_PATH/" + fi + + # Восстанавливаем данные + if [[ -d "$temp_dir/$backup_folder/data" ]]; then + print_info "Восстанавливаем пользовательские данные..." + rm -rf "$INSTALL_PATH/data" + cp -r "$temp_dir/$backup_folder/data" "$INSTALL_PATH/" + fi + + # Запускаем сервисы + print_info "Запускаем сервисы..." + run_compose up -d + + # Восстанавливаем БД + if [[ -f "$temp_dir/$backup_folder/database.sql" ]]; then + print_info "Ожидаем запуска PostgreSQL..." + sleep 5 + print_info "Восстанавливаем базу данных..." + run_compose exec -T postgres psql -U postgres remnawave_bot < "$temp_dir/$backup_folder/database.sql" 2>/dev/null || { + print_warning "Не удалось восстановить БД (возможно, структура уже актуальна)" + } + fi + + # Очистка + rm -rf "$temp_dir" + + print_success "Восстановление завершено!" + + echo "" + show_monitoring +} + +# Просмотр логов +view_logs() { + print_header "ПРОСМОТР ЛОГОВ" + + echo -e "${CYAN}[1]${NC} Логи бота (последние 100 строк)" + echo -e "${CYAN}[2]${NC} Логи PostgreSQL (последние 100 строк)" + echo -e "${CYAN}[3]${NC} Логи Redis (последние 100 строк)" + echo -e "${CYAN}[4]${NC} Все логи (последние 100 строк)" + echo -e "${CYAN}[5]${NC} Следить за логами в реальном времени" + echo -e "${CYAN}[6]${NC} Поиск по логам" + + echo "" + read -rp "Выберите опцию [1-6]: " choice + + case $choice in + 1) + run_compose logs --tail=100 bot + ;; + 2) + run_compose logs --tail=100 postgres + ;; + 3) + run_compose logs --tail=100 redis + ;; + 4) + run_compose logs --tail=100 + ;; + 5) + print_info "Нажмите Ctrl+C для выхода" + run_compose logs -f + ;; + 6) + read -rp "Введите текст для поиска: " search_term + run_compose logs | grep -i "$search_term" --color=always | tail -n 50 + ;; + *) + print_error "Неверный выбор" + ;; + esac +} + +# Управление сервисами +manage_services() { + print_header "УПРАВЛЕНИЕ СЕРВИСАМИ" + + echo -e "${CYAN}[1]${NC} Запустить все сервисы" + echo -e "${CYAN}[2]${NC} Остановить все сервисы" + echo -e "${CYAN}[3]${NC} Перезапустить все сервисы" + echo -e "${CYAN}[4]${NC} Пересобрать и запустить" + echo -e "${CYAN}[5]${NC} Остановить и удалить контейнеры" + + echo "" + read -rp "Выберите опцию [1-5]: " choice + + case $choice in + 1) + print_info "Запускаем сервисы..." + run_compose up -d + print_success "Сервисы запущены" + show_monitoring + ;; + 2) + print_info "Останавливаем сервисы..." + run_compose stop + print_success "Сервисы остановлены" + ;; + 3) + print_info "Перезапускаем сервисы..." + run_compose restart + print_success "Сервисы перезапущены" + show_monitoring + ;; + 4) + print_info "Пересобираем и запускаем..." + run_compose up -d --build + print_success "Сервисы пересобраны и запущены" + show_monitoring + ;; + 5) + print_warning "Контейнеры будут удалены (данные сохранятся в volumes)" + read -rp "$(echo -e ${YELLOW}Продолжить? [y/N]: ${NC})" confirm + if [[ "${confirm,,}" == "y" ]]; then + run_compose down + print_success "Контейнеры остановлены и удалены" + fi + ;; + *) + print_error "Неверный выбор" + ;; + esac +} + +# Очистка системы +cleanup_system() { + print_header "ОЧИСТКА СИСТЕМЫ" + + echo -e "${CYAN}[1]${NC} Очистить старые логи (старше 7 дней)" + echo -e "${CYAN}[2]${NC} Очистить старые резервные копии (оставить 5 последних)" + echo -e "${CYAN}[3]${NC} Очистить неиспользуемые Docker образы" + echo -e "${CYAN}[4]${NC} Полная очистка (всё вышеперечисленное)" + + echo "" + read -rp "Выберите опцию [1-4]: " choice + + case $choice in + 1) + print_info "Очищаем старые логи..." + find "$INSTALL_PATH/logs" -type f -mtime +7 -delete 2>/dev/null || true + print_success "Старые логи удалены" + ;; + 2) + print_info "Очищаем старые бэкапы..." + (cd "$BACKUP_DIR" && ls -t backup_*.tar.gz 2>/dev/null | tail -n +6 | xargs -r rm -f) + print_success "Старые бэкапы удалены" + ;; + 3) + print_info "Очищаем неиспользуемые Docker образы..." + docker image prune -f + print_success "Неиспользуемые образы удалены" + ;; + 4) + print_info "Выполняем полную очистку..." + find "$INSTALL_PATH/logs" -type f -mtime +7 -delete 2>/dev/null || true + (cd "$BACKUP_DIR" && ls -t backup_*.tar.gz 2>/dev/null | tail -n +6 | xargs -r rm -f) + docker image prune -f + docker volume prune -f + print_success "Полная очистка завершена" + ;; + *) + print_error "Неверный выбор" + ;; + esac +} + +# Главное меню +show_menu() { + clear + echo -e "${PURPLE}${BOLD}" + cat << "EOF" +╔════════════════════════════════════════════════════════════╗ +║ ║ +║ ██████╗ ██████╗ ████████╗ ███╗ ███╗ ██████╗ ██████╗ +║ ██╔══██╗██╔═══██╗╚══██╔══╝ ████╗ ████║██╔════╝ ██╔══██╗ +║ ██████╔╝██║ ██║ ██║ ██╔████╔██║██║ ███╗██████╔╝ +║ ██╔══██╗██║ ██║ ██║ ██║╚██╔╝██║██║ ██║██╔══██╗ +║ ██████╔╝╚██████╔╝ ██║ ██║ ╚═╝ ██║╚██████╔╝██║ ██║ +║ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ +║ ║ +║ Система управления Telegram ботом ║ +╚════════════════════════════════════════════════════════════╝ +EOF + echo -e "${NC}" + + echo -e "${WHITE}${BOLD}Путь установки:${NC} ${CYAN}$INSTALL_PATH${NC}" + echo "" + + echo -e "${GREEN}${BOLD}[1]${NC} ${STAR} Мониторинг и статус сервисов" + echo -e "${BLUE}${BOLD}[2]${NC} ${GEAR} Управление сервисами" + echo -e "${YELLOW}${BOLD}[3]${NC} 📋 Просмотр логов" + echo -e "${PURPLE}${BOLD}[4]${NC} 🔄 Обновление из Git" + echo -e "${CYAN}${BOLD}[5]${NC} 💾 Создать резервную копию" + echo -e "${YELLOW}${BOLD}[6]${NC} 📦 Восстановить из резервной копии" + echo -e "${RED}${BOLD}[7]${NC} 🧹 Очистка системы" + echo -e "${WHITE}${BOLD}[0]${NC} 🚪 Выход" + + echo "" } main() { load_state - if [[ -f "$STATE_FILE" ]]; then - if ask_yes_no "Обнаружена существующая установка. Переустановить? [y/N]: " "n"; then - perform_installation - else - show_existing_installation - fi - else - perform_installation - fi + resolve_compose_command + + while true; do + show_menu + read -rp "$(echo -e ${WHITE}${BOLD}Выберите опцию: ${NC})" choice + + case $choice in + 1) + show_monitoring + ;; + 2) + manage_services + ;; + 3) + view_logs + ;; + 4) + update_from_git + ;; + 5) + create_backup "manual" + ;; + 6) + restore_backup + ;; + 7) + cleanup_system + ;; + 0) + print_success "До свидания!" + exit 0 + ;; + *) + print_error "Неверный выбор. Попробуйте снова." + ;; + esac + + echo "" + read -rp "$(echo -e ${CYAN}Нажмите Enter для продолжения...${NC})" + done } main "$@"