#!/bin/sh

# network-restart 5.1.1
# Copyright (c)2017-2026 John Lawson & Sons
# All Rights Reserved

set -eu
umask 077

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"

# 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" 2>/dev/null || true

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

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() {
	defiface="$(ip route 2>/dev/null | awk '/^default /{print $5; exit}')"

	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

	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
	elif systemctl list-unit-files 2>/dev/null | grep -q '^wpa_supplicant\.service'; then
		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)"

	case "$last" in
		*[!0-9]*|"") last=0 ;;
	esac

	[ $((now - last)) -ge "$COOLDOWN_SECS" ]
}

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

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)"

	case "$last_ok" in
		*[!0-9]*|"") last_ok="$now" ;;
	esac

	downtime=$((now - last_ok))

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

restart_networkd_last_resort() {
	systemctl restart systemd-networkd >/dev/null 2>&1 || true
	sleep "$RECONNECT_DELAY"
}

attempt_recovery() {
	iface="$1"

	# Light action every run
	renew_dhcp "$iface"
	if ping_iface "$iface"; then
		return 0
	fi

	# Heavy recovery is rate-limited as one whole block
	if ! cooldown_ok; then
		return 1
	fi

	cooldown_touch

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

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

	# 3) last resort: restart networkd
	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
	reboot_if_offline_too_long
fi

exit 0
