#!/bin/bash
# backproc_watchdog.sh
# Watchdog dla procesu backproc eDokumenty (engine.php + wątki thread_ignitor.php).
#
# Struktura monitorowanych procesów:
#   php /home/edokumenty/public_html/apps/backproc/engine.php        <- główny proces (rodzic)
#   └─ sh -c php8.2 .../backproc/thread_ignitor.php                  <- launcher wątku
#      └─ php8.2 .../backproc/thread_ignitor.php                     <- wątek roboczy (tu jest CPU)
#
# Logika: sumuje ticki CPU WSZYSTKICH procesów backproc.
# Jeśli suma nie rośnie przez MAX_IDLE_SECONDS → restart.
#
# Dodaj do crontab (jako root):
#   */1 * * * * root /etc/backup/backproc_watchdog.sh

# === KONFIGURACJA ===
DATA_DIR="/home/edokumenty/public_html/apps/backproc/data"
LOG_FILE="/var/log/backup_edokumenty/backproc_watchdog.log"
STATE_FILE="/tmp/backproc_watchdog.state"
LOCK_FILE="/tmp/backproc_watchdog.lock"
MAX_IDLE_SECONDS=120              # restart po braku aktywności CPU przez 2 min (2 cykle × 1 min)
ENGINE_MIN_UPTIME=120             # ile sekund dajemy engine'owi na pierwsze uruchomienie wątków
# ====================

mkdir -p "$(dirname "$LOG_FILE")"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# Zabija wszystkie procesy backproc, czyści dane, restartuje usługę
restart_service() {
    log "[RESTART] Zabijanie wszystkich procesów backproc..."
    # Zabij wątki (thread_ignitor) jako pierwsze, potem engine
    ps aux | grep '[/]apps/backproc/thread_ignitor' | awk '{print $2}' | xargs -r kill -9 2>/dev/null
    ps aux | grep '[/]apps/backproc/engine.php'     | awk '{print $2}' | xargs -r kill -9 2>/dev/null
    sleep 5

    log "[RESTART] Usuwanie plików danych z $DATA_DIR..."
    rm -rf "${DATA_DIR:?}"/*
    log "[RESTART] Dane wyczyszczone – eDokumenty uruchomi backproc automatycznie."

    rm -f "$STATE_FILE"
}

# Sumuje ticki CPU (utime+stime) dla listy PIDów z /proc/PID/stat
# Zwraca liczbę całkowitą lub pusty string jeśli żaden PID nie dał wyniku
sum_cpu_ticks() {
    local total=0
    local found=0
    for pid in "$@"; do
        local ticks
        ticks=$(awk '{print $14+$15}' /proc/"$pid"/stat 2>/dev/null)
        if [ -n "$ticks" ]; then
            total=$((total + ticks))
            found=1
        fi
    done
    [ "$found" = "1" ] && echo "$total"
}

# --- Blokada przed równoległym uruchomieniem ---
if [ -f "$LOCK_FILE" ]; then
    LOCK_PID=$(cat "$LOCK_FILE")
    if kill -0 "$LOCK_PID" 2>/dev/null; then
        log "[SKIP] Poprzednia instancja (PID $LOCK_PID) nadal działa. Pomijam."
        exit 0
    fi
fi
echo $$ > "$LOCK_FILE"
trap 'rm -f "$LOCK_FILE"' EXIT

# --- Znajdź główny proces engine.php ---
ENGINE_PID=$(ps aux | grep '[/]apps/backproc/engine.php' | awk '{print $2}' | head -1)

if [ -z "$ENGINE_PID" ]; then
    # Sprawdź czy zostały osierocone wątki lub śmieci w data/
    ORPHAN_THREADS=$(ps aux | grep '[/]apps/backproc/thread_ignitor' | awk '{print $2}')
    HAS_DATA=$(ls -A "${DATA_DIR}" 2>/dev/null)
    if [ -n "$ORPHAN_THREADS" ] || [ -n "$HAS_DATA" ]; then
        log "[ALERT] Brak procesu engine.php – wykryto osierocone wątki lub stare pliki. Czyszczę..."
        if [ -n "$ORPHAN_THREADS" ]; then
            echo "$ORPHAN_THREADS" | xargs -r kill -9 2>/dev/null
            log "[RESTART] Zabito osierocone wątki thread_ignitor: $(echo "$ORPHAN_THREADS" | tr '\n' ' ')"
        fi
        rm -rf "${DATA_DIR:?}"/*
        log "[RESTART] Dane wyczyszczone – eDokumenty uruchomi backproc automatycznie."
    else
        log "[ALERT] Brak procesu engine.php – eDokumenty uruchomi go automatycznie."
    fi
    rm -f "$STATE_FILE"
    exit 0
fi

# --- Sprawdź stan zombie silnika ---
ENGINE_STATE=$(awk '{print $3}' /proc/"$ENGINE_PID"/stat 2>/dev/null)
if [ "$ENGINE_STATE" = "Z" ]; then
    log "[ALERT] Proces engine.php ($ENGINE_PID) jest zombie! Restartuję..."
    restart_service
    exit 0
fi

# --- Sprawdź czy engine ma jakiekolwiek wątki robocze ---
THREAD_COUNT=$(ps aux | grep '[/]apps/backproc/thread_ignitor' | wc -l)
if [ "$THREAD_COUNT" -eq 0 ]; then
    ENGINE_START=$(awk '{print int($22)}' /proc/"$ENGINE_PID"/stat 2>/dev/null)
    CLK_TCK=$(getconf CLK_TCK 2>/dev/null || echo 100)
    UPTIME_SECS=$(awk '{print int($1)}' /proc/uptime 2>/dev/null)
    if [ -n "$ENGINE_START" ] && [ -n "$UPTIME_SECS" ]; then
        ENGINE_AGE=$(( UPTIME_SECS - ENGINE_START / CLK_TCK ))
        if [ "$ENGINE_AGE" -gt "$ENGINE_MIN_UPTIME" ]; then
            log "[ALERT] Engine PID $ENGINE_PID działa od ${ENGINE_AGE}s, ale brak wątków thread_ignitor! Restartuję..."
            restart_service
            exit 0
        else
            log "[INFO] Engine PID $ENGINE_PID uruchomiony ${ENGINE_AGE}s temu – czekam na pierwsze wątki (limit: ${ENGINE_MIN_UPTIME}s)."
        fi
    fi
fi

# --- Zbierz PIDy WSZYSTKICH procesów backproc (engine + wszystkie wątki) ---
# Szukamy po ścieżce /apps/backproc/ żeby nie złapać niezwiązanych procesów
ALL_PIDS=$(ps aux | grep '[/]apps/backproc/' | awk '{print $2}')
PIDS_COUNT=$(echo "$ALL_PIDS" | wc -w)

# --- Suma ticków CPU wszystkich procesów backproc ---
# shellcheck disable=SC2086
CURRENT_CPU=$(sum_cpu_ticks $ALL_PIDS)
CURRENT_TIME=$(date +%s)

if [ -z "$CURRENT_CPU" ]; then
    log "[ALERT] Nie można odczytać statystyk CPU procesów backproc. Restartuję..."
    restart_service
    exit 0
fi

# --- Porównaj z poprzednim stanem ---
if [ -f "$STATE_FILE" ]; then
    PREV_CPU=$(sed -n '1p' "$STATE_FILE")
    PREV_TIME=$(sed -n '2p' "$STATE_FILE")
    PREV_ENGINE_PID=$(sed -n '3p' "$STATE_FILE")

    if [ "$PREV_ENGINE_PID" = "$ENGINE_PID" ]; then
        if [ "$PREV_CPU" = "$CURRENT_CPU" ]; then
            # CPU nie wzrosło – liczymy czas bezczynności od POPRZEDNIEGO pomiaru (nie aktualizujemy czasu)
            IDLE_SECS=$((CURRENT_TIME - PREV_TIME))
            log "[WARN] Brak aktywności CPU przez ${IDLE_SECS}s (engine PID: $ENGINE_PID, wątki: $((PIDS_COUNT - 1)), łączne ticki: $CURRENT_CPU, limit: ${MAX_IDLE_SECONDS}s)"
            if [ "$IDLE_SECS" -gt "$MAX_IDLE_SECONDS" ]; then
                log "[ALERT] System backproc zablokowany! Restartuję..."
                restart_service
            fi
            # Nie aktualizuj STATE_FILE – czas bezczynności musi rosnąć do kolejnego sprawdzenia
        else
            # CPU wzrosło – system pracuje, aktualizuj stan
            printf "%s\n%s\n%s\n" "$CURRENT_CPU" "$CURRENT_TIME" "$ENGINE_PID" > "$STATE_FILE"
            log "[OK] Backproc aktywny (engine PID: $ENGINE_PID, wątki: $((PIDS_COUNT - 1)), łączne ticki CPU: $CURRENT_CPU)"
        fi
    else
        # Nowy PID engine – zrestartowano zewnętrznie, resetuj stan
        printf "%s\n%s\n%s\n" "$CURRENT_CPU" "$CURRENT_TIME" "$ENGINE_PID" > "$STATE_FILE"
        log "[INFO] Wykryto nowy engine PID: $ENGINE_PID (poprzedni: $PREV_ENGINE_PID). Resetuję stan watchdoga."
    fi
else
    # Pierwsze uruchomienie
    printf "%s\n%s\n%s\n" "$CURRENT_CPU" "$CURRENT_TIME" "$ENGINE_PID" > "$STATE_FILE"
    log "[OK] Pierwsze uruchomienie watchdoga. Engine PID: $ENGINE_PID, wątki: $((PIDS_COUNT - 1)), ticki CPU: $CURRENT_CPU"
fi

