#!/bin/sh
#
# KEEPALIVED-REPARATUR (PID / tmpfs)
# Behebt das Problem alter PID-Dateien durch tmpfs-Einbindung und Neuerstellung.
# ACHTUNG: Nur als root ausführen – an Ihre Umgebung anpassen.
#

set -u

BASE="/path/to/vpn-platform"
HA_DIR="${BASE}/ha"
COMPOSE_FILE="${HA_DIR}/compose-ha.yaml"
SERVICE="keepalived-vpn-ha"
CONTAINER="keepalived-vpn-ha"   # Platzhalter
LOG_DIR="${BASE}/logs/keepalived"
STAMP="$(date '+%Y-%m-%d_%H-%M-%S')"
LOG_FILE="${LOG_DIR}/keepalived-reparatur_${STAMP}.log"
LATEST_FILE="${LOG_DIR}/keepalived-reparatur_latest.log"
BACKUP_FILE="${COMPOSE_FILE}.bak-pidfix-${STAMP}"

mkdir -p "${LOG_DIR}" || {
    echo "FEHLER: Log-Verzeichnis konnte nicht erstellt werden: ${LOG_DIR}" >&2
    exit 10
}

exec 3>&1
exec >>"${LOG_FILE}" 2>&1

log_line() {
    LEVEL="$1"
    shift
    printf '%s [%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "${LEVEL}" "$*"
}

section() {
    printf '\n============================================================\n'
    log_line "SECTION" "$*"
    printf '============================================================\n'
}

stop_now() {
    CODE="$1"
    shift
    log_line "STOP" "$*"
    cp "${LOG_FILE}" "${LATEST_FILE}" 2>/dev/null || true
    printf '\nSTOPP: %s\n' "$*" >&3
    printf 'Logdatei: %s\n' "${LOG_FILE}" >&3
    exit "${CODE}"
}

run_required() {
    DESCRIPTION="$1"
    shift
    log_line "INFO" "${DESCRIPTION}"
    "$@"
    RC=$?
    log_line "RESULT" "Exit-Code=${RC}"
    [ "${RC}" -eq 0 ] || stop_now 20 "${DESCRIPTION} fehlgeschlagen."
}

compose_cmd() {
    if command -v docker-compose >/dev/null 2>&1; then
        docker-compose -f "${COMPOSE_FILE}" "$@"
    elif docker compose version >/dev/null 2>&1; then
        docker compose -f "${COMPOSE_FILE}" "$@"
    else
        return 127
    fi
}

section "START DER REPARATUR"
log_line "INFO" "Keepalived PID-/tmpfs-Reparatur"
log_line "INFO" "Logdatei: ${LOG_FILE}"

section "SICHERHEITSPRÜFUNGEN"
[ "$(id -u)" -eq 0 ] || stop_now 11 "Das Skript muss als root ausgeführt werden."

HOSTNAME_NOW="$(hostname 2>/dev/null || true)"
log_line "INFO" "Hostname=${HOSTNAME_NOW}"
# Hostnamen-Prüfung wurde entfernt – Sie können hier eigene Prüfungen einfügen.

[ -f "${COMPOSE_FILE}" ] || stop_now 13 "Compose-Datei fehlt: ${COMPOSE_FILE}"
[ -f "${HA_DIR}/keepalived.conf" ] || stop_now 14 "Keepalived-Konfiguration fehlt."
[ -f "${HA_DIR}/scripts/check-vpn.sh" ] || stop_now 15 "VPN-Healthcheck fehlt."

command -v docker >/dev/null 2>&1 || stop_now 16 "Docker-Befehl wurde nicht gefunden."
docker info >/dev/null 2>&1 || stop_now 17 "Docker ist nicht erreichbar."

# Healthcheck des VPN-Endpunkts (Beispiel: Port 9999)
if command -v curl >/dev/null 2>&1; then
    curl -fsS --max-time 10 http://127.0.0.1:9999/ >/dev/null 2>&1 \
        || stop_now 18 "Health-Endpunkt auf 127.0.0.1:9999 antwortet nicht."
else
    log_line "WARN" "curl fehlt; der Endpunkt wird mit wget geprüft."
    wget -q -T 10 -O /dev/null http://127.0.0.1:9999/ \
        || stop_now 18 "Health-Endpunkt auf 127.0.0.1:9999 antwortet nicht."
fi
log_line "INFO" "Health-Endpunkt antwortet."

section "AUSGANGSZUSTAND"
run_required "Aktuellen Keepalived-Containerstatus erfassen" \
    sh -c "docker ps -a --filter 'name=^/${CONTAINER}$' --no-trunc || true"

run_required "Letzte Keepalived-Logzeilen erfassen" \
    sh -c "docker logs --timestamps --tail 80 '${CONTAINER}' 2>&1 || true"

if docker ps -a --format '{{.Names}}' | grep -Fxq "${CONTAINER}"; then
    if docker cp "${CONTAINER}:/run/keepalived/keepalived.pid" \
        "${LOG_DIR}/pid-vor-reparatur_${STAMP}.txt" >/dev/null 2>&1; then
        PID_OLD="$(tr -cd '0-9' < "${LOG_DIR}/pid-vor-reparatur_${STAMP}.txt" 2>/dev/null)"
        log_line "WARN" "Alte PID-Datei gefunden. Inhalt=${PID_OLD:-unbekannt}"
    else
        log_line "INFO" "Alte PID-Datei konnte nicht aus dem Container kopiert werden."
    fi
else
    stop_now 19 "Der erwartete Container ${CONTAINER} wurde nicht gefunden."
fi

section "COMPOSE-DATEI SICHERN"
run_required "Compose-Datei sichern" cp -p "${COMPOSE_FILE}" "${BACKUP_FILE}"
log_line "INFO" "Sicherung: ${BACKUP_FILE}"

section "TMPFS FÜR /run EINTRAGEN"
if awk '
    /^[[:space:]]*tmpfs:[[:space:]]*$/ { in_tmpfs=1; next }
    in_tmpfs && /^[[:space:]]*-[[:space:]]*\/run([[:space:]]|$)/ { found=1 }
    in_tmpfs && /^[^[:space:]]/ { in_tmpfs=0 }
    END { exit(found ? 0 : 1) }
' "${COMPOSE_FILE}"; then
    log_line "INFO" "tmpfs für /run ist bereits eingetragen."
else
    TMP_FILE="${COMPOSE_FILE}.tmp-${STAMP}"

    awk '
        BEGIN { inserted=0 }
        {
            print
            if (!inserted && $0 ~ /^[[:space:]]*restart:[[:space:]]*unless-stopped[[:space:]]*$/) {
                match($0, /^[[:space:]]*/)
                indent=substr($0, RSTART, RLENGTH)
                print indent "tmpfs:"
                print indent "  - /run"
                inserted=1
            }
        }
        END {
            if (!inserted) {
                exit 44
            }
        }
    ' "${COMPOSE_FILE}" > "${TMP_FILE}"
    RC=$?

    if [ "${RC}" -ne 0 ]; then
        rm -f "${TMP_FILE}" 2>/dev/null || true
        cp -p "${BACKUP_FILE}" "${COMPOSE_FILE}" 2>/dev/null || true
        stop_now 21 "tmpfs-Eintrag konnte nicht automatisch ergänzt werden."
    fi

    mv "${TMP_FILE}" "${COMPOSE_FILE}" \
        || {
            cp -p "${BACKUP_FILE}" "${COMPOSE_FILE}" 2>/dev/null || true
            stop_now 22 "Geänderte Compose-Datei konnte nicht gespeichert werden."
        }

    log_line "INFO" "tmpfs für /run wurde eingetragen."
fi

section "GEÄNDERTE COMPOSE-DATEI"
cat "${COMPOSE_FILE}"

section "COMPOSE-PRÜFUNG"
compose_cmd config >/dev/null 2>&1
RC=$?
log_line "RESULT" "Compose-Konfigurationsprüfung Exit-Code=${RC}"

if [ "${RC}" -ne 0 ]; then
    cp -p "${BACKUP_FILE}" "${COMPOSE_FILE}" 2>/dev/null || true
    stop_now 23 "Compose-Konfiguration ungültig. Sicherung wurde zurückgespielt."
fi

section "KEEPALIVED-CONTAINER NEU ERSTELLEN"
log_line "INFO" "Der alte beschreibbare Container-Layer mit der PID-Datei wird ersetzt."

compose_cmd up -d --build --force-recreate "${SERVICE}"
RC=$?
log_line "RESULT" "Container-Neuerstellung Exit-Code=${RC}"

if [ "${RC}" -ne 0 ]; then
    cp -p "${BACKUP_FILE}" "${COMPOSE_FILE}" 2>/dev/null || true
    stop_now 24 "Keepalived-Container konnte nicht neu erstellt werden. Compose-Datei wurde zurückgespielt."
fi

section "STARTPHASE BEOBACHTEN"
ATTEMPT=1
RUNNING=0

while [ "${ATTEMPT}" -le 12 ]; do
    STATUS="$(docker inspect --format '{{.State.Status}}' "${CONTAINER}" 2>/dev/null || echo missing)"
    log_line "INFO" "Prüfung ${ATTEMPT}/12: Containerstatus=${STATUS}"

    if [ "${STATUS}" = "running" ]; then
        RUNNING=1
        break
    fi

    sleep 5
    ATTEMPT=$((ATTEMPT + 1))
done

if [ "${RUNNING}" -ne 1 ]; then
    docker logs --timestamps --tail 150 "${CONTAINER}" 2>&1 || true
    stop_now 25 "Keepalived läuft nach 60 Sekunden nicht."
fi

log_line "INFO" "Keepalived-Container läuft."

section "PID- UND TMPFS-PRÜFUNG"
run_required "Mount-Typ von /run im Container prüfen" \
    docker exec "${CONTAINER}" sh -c "mount | grep ' on /run '"

run_required "Aktuelle Keepalived-PID-Datei anzeigen" \
    docker exec "${CONTAINER}" sh -c "ls -la /run/keepalived && cat /run/keepalived/keepalived.pid"

run_required "Keepalived-Prozesse im Container anzeigen" \
    docker exec "${CONTAINER}" sh -c "ps | grep '[k]eepalived'"

section "VRRP- UND VIP-PRÜFUNG"
sleep 20

CURRENT_STATE="UNBEKANNT"
if [ -r "${HA_DIR}/state/current" ]; then
    CURRENT_STATE="$(cat "${HA_DIR}/state/current" 2>/dev/null || echo UNBEKANNT)"
fi
log_line "INFO" "Zustandsdatei=${CURRENT_STATE}"

VIP_PRESENT=0
if ip addr show eth0 2>/dev/null | grep -q '192\.168\.xxx\.xxx/24'; then
    VIP_PRESENT=1
    log_line "INFO" "VIP 192.168.xxx.xxx ist auf eth0 vorhanden."
else
    log_line "WARN" "VIP 192.168.xxx.xxx ist auf eth0 noch nicht vorhanden."
fi

run_required "Keepalived-Containerstatus nach Start" \
    docker inspect --format \
    'Name={{.Name}} Status={{.State.Status}} Running={{.State.Running}} StartedAt={{.State.StartedAt}} ExitCode={{.State.ExitCode}} RestartCount={{.RestartCount}}' \
    "${CONTAINER}"

run_required "Aktuelle Keepalived-Logs" \
    docker logs --timestamps --tail 200 "${CONTAINER}"

section "ABSCHLUSSBEWERTUNG"
if [ "${VIP_PRESENT}" -eq 1 ]; then
    log_line "SUCCESS" "Keepalived läuft und die VIP ist auf diesem Knoten vorhanden."
else
    log_line "WARN" "Keepalived läuft, aber die VIP ist nicht auf diesem Knoten vorhanden."
    log_line "WARN" "Das kann korrekt sein, wenn der andere Knoten aktuell MASTER ist."
    log_line "WARN" "Vor einem Failover-Test muss der Zustand beider Knoten geprüft werden."
fi

cp "${LOG_FILE}" "${LATEST_FILE}" 2>/dev/null || true

printf '\nReparaturdurchlauf abgeschlossen.\n' >&3
printf 'Logdatei:\n%s\n' "${LOG_FILE}" >&3
printf 'Aktuelle Kopie:\n%s\n' "${LATEST_FILE}" >&3
printf 'Compose-Sicherung:\n%s\n' "${BACKUP_FILE}" >&3
exit 0