infra/scripts/cleanup.sh
#!/bin/bash

################################################################################
# SAFE CLEANUP SCRIPT
# Безопасная очистка диска: логи, кэши, временные файлы
#
# Сохраняет:
#   - Данные пользователя
#   - Бэкапы БД
#   - Git репозитории
#   - Важные конфигурации
#
# Очищает (по времени жизни):
#   - Логи старше 7 дней
#   - /tmp старше 3 дней
#   - apt cache
#   - pip cache
#   - docker dangling images/volumes
#   - systemd journald старше 14 дней
################################################################################

set -u
umask 077

# Цвета для вывода
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Счётчики
TOTAL_FREED=0
declare -a FREED_ITEMS

# Функции
log_info() {
    echo -e "${BLUE}[INFO]${NC} $1"
}

log_success() {
    echo -e "${GREEN}[OK]${NC} $1"
}

log_warn() {
    echo -e "${YELLOW}[WARN]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# Безопасное получение размера
get_size() {
    local path="$1"
    if [ -e "$path" ]; then
        du -sh "$path" 2>/dev/null | cut -f1
    else
        echo "0B"
    fi
}

# Вычисление разницы в МБ
calc_freed() {
    local before=$1
    local after=$2

    # Конвертируем в байты
    before_bytes=$(numfmt --from=iec "$before" 2>/dev/null || echo 0)
    after_bytes=$(numfmt --from=iec "$after" 2>/dev/null || echo 0)

    freed=$((before_bytes - after_bytes))
    freed_mb=$((freed / 1024 / 1024))

    echo $freed_mb
}

################################################################################
# 1. ОЧИСТКА ЛОГОВ
################################################################################
cleanup_logs() {
    log_info "=== Очистка логов (старше 7 дней) ==="

    local before=$(get_size "/var/log")
    local count=0

    # Логи в /var/log/*.1, *.2 (ротированные)
    if [ -d "/var/log" ]; then
        # Очищаем ротированные логи по дате
        find /var/log -type f -name "*.1" -o -name "*.2" -o -name "*.3" 2>/dev/null | while read logfile; do
            # Проверяем, старше ли 7 дней
            if [ -f "$logfile" ] && [ "$(find "$logfile" -mtime +7 2>/dev/null | wc -l)" -gt 0 ]; then
                size_before=$(du -sh "$logfile" 2>/dev/null | cut -f1)
                : > "$logfile" 2>/dev/null
                ((count++))
            fi
        done

        # Очищаем сжатые логи
        find /var/log -type f \( -name "*.gz" -o -name "*.bz2" -o -name "*.xz" \) -mtime +7 2>/dev/null -delete
    fi

    local after=$(get_size "/var/log")
    local freed=$(calc_freed "$before" "$after")

    if [ $freed -gt 0 ]; then
        log_success "Очищено $freed МБ логов ($count файлов)"
        FREED_ITEMS+=("Логи: $freed МБ")
        TOTAL_FREED=$((TOTAL_FREED + freed))
    else
        log_warn "Нечего чистить в логах (нет файлов старше 7 дней)"
    fi
}

################################################################################
# 2. ОЧИСТКА APT CACHE
################################################################################
cleanup_apt() {
    log_info "=== Очистка apt cache ==="

    if command -v apt-get &> /dev/null; then
        local before=$(du -sh /var/cache/apt 2>/dev/null | cut -f1)

        apt-get clean 2>/dev/null
        apt-get autoclean 2>/dev/null

        local after=$(du -sh /var/cache/apt 2>/dev/null | cut -f1)
        local freed=$(calc_freed "$before" "$after")

        if [ $freed -gt 0 ]; then
            log_success "Очищено $freed МБ apt cache"
            FREED_ITEMS+=("apt: $freed МБ")
            TOTAL_FREED=$((TOTAL_FREED + freed))
        else
            log_warn "apt cache уже чист"
        fi
    else
        log_warn "apt не установлен, пропускаю"
    fi
}

################################################################################
# 3. ОЧИСТКА PIP CACHE
################################################################################
cleanup_pip() {
    log_info "=== Очистка pip cache ==="

    if command -v pip3 &> /dev/null; then
        # Global cache
        if [ -d "/root/.cache/pip" ]; then
            local before=$(du -sh /root/.cache/pip 2>/dev/null | cut -f1)
            rm -rf /root/.cache/pip/* 2>/dev/null
            local after=$(du -sh /root/.cache/pip 2>/dev/null | cut -f1)
            local freed=$(calc_freed "$before" "$after")

            if [ $freed -gt 0 ]; then
                log_success "Очищено $freed МБ pip cache"
                FREED_ITEMS+=("pip: $freed МБ")
                TOTAL_FREED=$((TOTAL_FREED + freed))
            fi
        fi

        # Через pip cache purge (pip 20.1+)
        pip3 cache purge 2>/dev/null || true
    else
        log_warn "pip не установлен, пропускаю"
    fi
}

################################################################################
# 4. ОЧИСТКА DOCKER
################################################################################
cleanup_docker() {
    log_info "=== Очистка Docker (dangling только) ==="

    if ! command -v docker &> /dev/null; then
        log_warn "Docker не установлен, пропускаю"
        return
    fi

    # Проверяем, работает ли docker daemon
    if ! docker info &>/dev/null; then
        log_warn "Docker не запущен, пропускаю"
        return
    fi

    local freed_total=0

    # Dangling images (не использующиеся в контейнерах, не имеют тега)
    local dangling_images=$(docker images --filter "dangling=true" -q 2>/dev/null | wc -l)
    if [ $dangling_images -gt 0 ]; then
        docker image prune -f --filter "dangling=true" >/dev/null 2>&1
        log_success "Удалено $dangling_images dangling images"
    fi

    # Dangling volumes (не привязаны ни к какому контейнеру)
    local dangling_volumes=$(docker volume ls --filter "dangling=true" -q 2>/dev/null | wc -l)
    if [ $dangling_volumes -gt 0 ]; then
        docker volume prune -f >/dev/null 2>&1
        log_success "Удалено $dangling_volumes dangling volumes"
    fi

    # Build cache - только неиспользуемый
    local build_cache=$(docker builder prune -af 2>/dev/null | grep -oE '[0-9]+B' | head -1)
    if [ -n "$build_cache" ]; then
        log_success "Удалён build cache: $build_cache"
    fi

    FREED_ITEMS+=("Docker dangling: удалено")
}

################################################################################
# 5. ОЧИСТКА /TMP
################################################################################
cleanup_tmp() {
    log_info "=== Очистка /tmp (старше 3 дней) ==="

    if [ ! -d "/tmp" ]; then
        log_warn "/tmp не существует"
        return
    fi

    local before=$(du -sh /tmp 2>/dev/null | cut -f1)
    local count=0

    # Удаляем файлы старше 3 дней (без проверки lsof т.к. она медленная)
    find /tmp -type f -mtime +3 ! -path "*/\.*" -delete 2>/dev/null
    count=$(find /tmp -type f -mtime +3 ! -path "*/\.*" 2>/dev/null | wc -l)

    local after=$(get_size "/tmp")
    local freed=$(calc_freed "$before" "$after")

    if [ $freed -gt 0 ]; then
        log_success "Очищено $freed МБ из /tmp ($count файлов)"
        FREED_ITEMS+=("/tmp: $freed МБ")
        TOTAL_FREED=$((TOTAL_FREED + freed))
    else
        log_warn "Нечего чистить в /tmp (нет файлов старше 3 дней)"
    fi
}

################################################################################
# 6. ОЧИСТКА SYSTEMD JOURNALD
################################################################################
cleanup_journald() {
    log_info "=== Очистка systemd journald (старше 14 дней) ==="

    if ! command -v journalctl &> /dev/null; then
        log_warn "journalctl не установлен, пропускаю"
        return
    fi

    local before=$(journalctl --disk-usage 2>/dev/null | grep -oE '[0-9.]+[MG]' | head -1)

    # Удаляем журналы старше 14 дней
    journalctl --vacuum-time=14d >/dev/null 2>&1

    local after=$(journalctl --disk-usage 2>/dev/null | grep -oE '[0-9.]+[MG]' | head -1)

    if [ -n "$before" ] && [ -n "$after" ]; then
        log_success "journald очищен: было $before, осталось $after"
        FREED_ITEMS+=("journald: $before → $after")
    fi
}

################################################################################
# MAIN
################################################################################
main() {
    echo -e "${BLUE}"
    echo "╔════════════════════════════════════════════════════════════╗"
    echo "║           SAFE DISK CLEANUP SCRIPT                         ║"
    echo "╚════════════════════════════════════════════════════════════╝"
    echo -e "${NC}"

    log_info "Старт очистки диска: $(date '+%Y-%m-%d %H:%M:%S')"
    echo ""

    # Выполняем очистку
    cleanup_logs
    echo ""

    cleanup_apt
    echo ""

    cleanup_pip
    echo ""

    cleanup_docker
    echo ""

    cleanup_tmp
    echo ""

    cleanup_journald
    echo ""

    # Финальный отчёт
    echo -e "${BLUE}════════════════════════════════════════════════════════════${NC}"
    log_info "ИТОГОВЫЙ ОТЧЁТ"
    echo ""

    if [ ${#FREED_ITEMS[@]} -gt 0 ]; then
        for item in "${FREED_ITEMS[@]}"; do
            echo "  • $item"
        done
        echo ""
        log_success "Всего освобождено: ~$TOTAL_FREED МБ"
    else
        log_warn "Ничего не очищено (диск уже в хорошем состоянии)"
    fi

    # Текущее состояние диска
    echo ""
    log_info "Текущее состояние:"
    df -h / | tail -1 | awk '{printf "  / : %s / %s (%s занято)\n", $3, $2, $5}'

    echo ""
    log_info "Завершено: $(date '+%Y-%m-%d %H:%M:%S')"
}

# Запуск
main "$@"