#!/bin/sh
# network-restart (oneshot, quiet)
# Copyright (c)2017-2026 John Lawson & Sons
# All Rights Reserved

set -eu

DNS="8.8.8.8"
PING_COUNT=2
PING_TIMEOUT=3
RECONNECT_DELAY=3

# Reboot only if we've been offline for >= 24h
MAX_DOWNTIME=$((24 * 60 * 60))

STATE_DIR="/run/network-restart"
LAST_OK_FILE="$STATE_DIR/last_connected"
LAST_FIX_FILE="$STATE_DIR/last_fix"
STATE_FILE="$STATE_DIR/state"   # stores last logged state line

# Anti-thrash: don't do heavy recovery more often than every 2 minutes
COOLDOWN_SECS=120

LOG_TAG="network-restart"
log() { logger -t "$LOG_TAG" "$*"; }

now_s() { date +%s; }

mkdir -p "$STATE_DIR"

# Initialise last_connected if missing
if [ ! -f "$LAST_OK_FILE" ]; then
  now_s >"$LAST_OK_FILE" 2>/dev/null || true
fi

# Log only on state change
log_state() {
  msg="$*"
  prev=""
  [ -f "$STATE_FILE" ] && prev="$(cat "$STATE_FILE" 2>/dev/null || true)"
  if [ "$msg" != "$prev" ]; then
    echo "$msg" >"$STATE_FILE" 2>/dev/null || true
    log "$msg"
  fi
}

iface_up() {
  ip link show "$1" 2>/dev/null | grep -q "state UP"
}

iface_has_ipv4() {
  ip -4 addr show dev "$1" 2>/dev/null | grep -q "inet "
}

detect_iface() {
  # Prefer interface with default route (most accurate “active”)
  defiface="$(ip route 2>/dev/null | awk '/^default /{print $5; exit 0}')"
  if [ -n "${defiface:-}" ] && iface_up "$defiface" && iface_has_ipv4 "$defiface"; then
    echo "$defiface"
    return 0
  fi

  for i in eth0 wlan0; do
    if iface_up "$i" && iface_has_ipv4 "$i"; then
      echo "$i"
      return 0
    fi
  done

  echo ""
  return 1
}

ping_iface() {
  ping -q -I "$1" -c "$PING_COUNT" -W "$PING_TIMEOUT" "$DNS" >/dev/null 2>&1
}

renew_dhcp() {
  command -v networkctl >/dev/null 2>&1 || return 0
  networkctl renew "$1" >/dev/null 2>&1 || true
}

bounce_iface() {
  ip link set "$1" down 2>/dev/null || true
  sleep "$RECONNECT_DELAY"
  ip link set "$1" up 2>/dev/null || true
  sleep "$RECONNECT_DELAY"
}

bounce_wifi() {
  if systemctl list-unit-files 2>/dev/null | grep -q '^wpa_supplicant@wlan0\.service'; then
    systemctl restart wpa_supplicant@wlan0.service >/dev/null 2>&1 || true
  else
    systemctl restart wpa_supplicant.service >/dev/null 2>&1 || true
  fi
  sleep "$RECONNECT_DELAY"
}

cooldown_ok() {
  [ -f "$LAST_FIX_FILE" ] || return 0
  last="$(cat "$LAST_FIX_FILE" 2>/dev/null || echo 0)"
  now="$(now_s)"
  [ $((now - last)) -ge "$COOLDOWN_SECS" ]
}

cooldown_touch() {
  now_s >"$LAST_FIX_FILE" 2>/dev/null || true
}

maybe_restart_networkd_last_resort() {
  # Only as a last resort, and not too often
  if cooldown_ok; then
    cooldown_touch
    systemctl restart systemd-networkd >/dev/null 2>&1 || true
    sleep "$RECONNECT_DELAY"
  fi
}

mark_ok() {
  now_s >"$LAST_OK_FILE" 2>/dev/null || true
}

reboot_if_offline_too_long() {
  now="$(now_s)"
  last_ok="$(cat "$LAST_OK_FILE" 2>/dev/null || echo 0)"
  downtime=$((now - last_ok))

  if [ "$downtime" -ge "$MAX_DOWNTIME" ]; then
    log_state "OFFLINE too long (${downtime}s >= ${MAX_DOWNTIME}s) -> reboot"
    reboot
  fi
}

attempt_recovery() {
  iface="$1"

  # Always allow light actions; rate-limit heavy actions
  renew_dhcp "$iface"
  if ping_iface "$iface"; then return 0; fi

  if ! cooldown_ok; then
    return 1
  fi
  cooldown_touch

  # 1) bounce iface
  bounce_iface "$iface"
  renew_dhcp "$iface"
  if ping_iface "$iface"; then return 0; fi

  # 2) if wifi, bounce supplicant
  if [ "$iface" = "wlan0" ]; then
    bounce_wifi
    renew_dhcp "$iface"
    if ping_iface "$iface"; then return 0; fi
  fi

  # 3) last resort: restart networkd
  maybe_restart_networkd_last_resort
  renew_dhcp "$iface"
  ping_iface "$iface"
}

# ---- main ----

iface="$(detect_iface || true)"
if [ -z "$iface" ]; then
  log_state "NOIFACE (no active iface with IPv4)"
  reboot_if_offline_too_long
  exit 0
fi

if ping_iface "$iface"; then
  mark_ok
  log_state "UP on $iface"
  exit 0
fi

log_state "DOWN on $iface"
if attempt_recovery "$iface"; then
  mark_ok
  log_state "UP on $iface (recovered)"
else
  # Stay quiet unless status changes; timer will re-run forever
  reboot_if_offline_too_long
fi

exit 0
