#!/bin/sh
# Demote CARP VIPs if critical service(s) fail (pfSense 2.8+)

# ---- CONFIG ----
# Interface + VHID list (static, no awk needed)
LAN_IF="vmx1"
LAN_VHIDS="20"
DNSBL_IF="vmx1"
DNSBL_VHIDS="21"
WAN_IF="vmx0"
WAN1_VHIDS="10"
WAN2_VHIDS="11"
WAN3_VHIDS="12"

LAN_NORMAL_SKEW=0                           # advskew value when healthy
DNSBL_NORMAL_SKEW=0                         # advskew value when healthy
WAN1_NORMAL_SKEW=0                          # advskew value when healthy
WAN2_NORMAL_SKEW=100                        # advskew value when healthy
WAN3_NORMAL_SKEW=200                        # advskew value when healthy
LAN_DEMOTE_SKEW=251                         # advskew value when demoted
DNSBL_DEMOTE_SKEW=251                       # advskew value when demoted
WAN1_DEMOTE_SKEW=251                        # advskew value when demoted
WAN2_DEMOTE_SKEW=252                        # advskew value when demoted
WAN3_DEMOTE_SKEW=253                        # advskew value when demoted


REQUIRED_SERVICES="haproxy suricata"    # extendable: e.g. "haproxy suricata unbound"
FAIL_THRESHOLD=1                        # number of consecutive failures before demotion

STATE_DIR="/var/run/carp-demote"
COUNT_FILE="$STATE_DIR/fails.count"
MODE_FILE="$STATE_DIR/mode"             # values: "normal" | "demoted"
LOGTAG="carp-demote"

mkdir -p "$STATE_DIR"

log() { logger -t "$LOGTAG" -- "$*"; }

# ---- HELPER ----
set_skew_group() {
  IFACE="$1"
  VHIDS="$2"
  TARGET_SKEW="$3"
  for V in $VHIDS; do
    # ifconfig is idempotent: always sets the desired advskew
    ifconfig "$IFACE" vhid "$V" advskew "$TARGET_SKEW"
# >/dev/null 2>&1
  done
}

services_ok=1
down=""
for s in $REQUIRED_SERVICES; do
  if ! pgrep -x "$s" >/dev/null 2>&1; then
    services_ok=0
    down="$down $s"
  fi
done
# --- additional check: HTTPS reachability (at least 1 host must respond) ---
if ! curl -fsS --max-time 5 https://haproxy-domain1.tld/alive >/dev/null 2>&1; then
  if ! curl -fsS --max-time 5 https://haproxy-domain2.tld/ >/dev/null 2>&1 && \
     ! curl -fsS --max-time 5 https://haproxy-domain3.tld/ >/dev/null 2>&1; then
    services_ok=0
    down="$down webcheck"
  fi
fi

mode="normal"
[ -f "$MODE_FILE" ] && mode="$(cat "$MODE_FILE" 2>/dev/null || echo not_exists)"
count=0
[ -f "$COUNT_FILE" ] && count="$(cat "$COUNT_FILE" 2>/dev/null || echo 0)"

if [ "$services_ok" -eq 1 ]; then
  # all services are OK -> if previously demoted, restore normal state
  if [ "$mode" = "demoted" ]; then
    TARGET_SKEW="$NORMAL_SKEW"
    echo "go up"
    set_skew_group "$WAN_IF" $WAN1_VHIDS $WAN1_NORMAL_SKEW
#    set_skew_group "$WAN_IF" $WAN2_VHIDS $WAN2_NORMAL_SKEW
#    set_skew_group "$WAN_IF" $WAN3_VHIDS $WAN3_NORMAL_SKEW
    set_skew_group "$LAN_IF" $LAN_VHIDS $LAN_NORMAL_SKEW
#    set_skew_group "$DNSBL_IF" $DNSBL_VHIDS $DNSBL_NORMAL_SKEW
    echo normal > "$MODE_FILE"
    log "Services OK; VIPs restored (advskew=$NORMAL_SKEW) [WAN:$WAN_IF vhid:$WAN_VHIDS | LAN:$LAN_IF vhid:$LAN_VHIDS | DNSBL:$DNSBL_IF vhid:$DNSBL_VHIDS]"
  elif [ "$mode" != "normal" ]; then
    log "Services and VIP OK but MODE_FILE not found!"
  fi
  echo 0 > "$COUNT_FILE"
#  exit 0
else

  # at least one service is DOWN
  count=$((count+1))
  echo "$count" > "$COUNT_FILE"

  if [ "$mode" = "normal" ] && [ "$count" -ge "$FAIL_THRESHOLD" ]; then
    echo "go down"
    TARGET_SKEW="$DEMOTE_SKEW"
    set_skew_group "$WAN_IF" $WAN1_VHIDS $WAN1_DEMOTE_SKEW
#    set_skew_group "$WAN_IF" $WAN2_VHIDS $WAN2_DEMOTE_SKEW
#    set_skew_group "$WAN_IF" $WAN3_VHIDS $WAN3_DEMOTE_SKEW
    set_skew_group "$LAN_IF" $LAN_VHIDS $LAN_DEMOTE_SKEW
#    set_skew_group "$DNSBL_IF" $DNSBL_VHIDS $DNSBL_DEMOTE_SKEW
    echo demoted > "$MODE_FILE"
    log "Service DOWN ($down), threshold=$FAIL_THRESHOLD; VIPs demoted (advskew=$DEMOTE_SKEW) [WAN:$WAN_IF vhid:$WAN_VHIDS | LAN:$LAN_IF vhid:$LAN_VHIDS | DNSBL:$DNSBL_IF vhid:$DNSBL_VHIDS]"
  fi
fi

exit 0

