#!/bin/bash
# wml-carrier-watcher - real-time NIC plug/unplug watchdog.
#
# Runs forever as a systemd service. Uses `ip monitor link` to watch for
# carrier-up events. When a NIC's carrier goes up:
#   - If we don't have a WAN with a valid DHCP lease yet, re-run wml-firstboot
#     WAN detection on the newly-connected NIC.
#   - If WAN already has a lease and this is a different NIC, mark it LAN.
#
# This is the firewall equivalent of "hot-plug an Ethernet cable" - it should
# Just Work, no reboot required.

set -u

LOG=/var/log/wml/carrier-watcher.log
mkdir -p /var/log/wml

log() {
    echo "$(date -Is) $*" >> "$LOG"
}

# Re-detect WAN by probing for DHCP. Same logic as wml-firstboot but runs
# whenever a new NIC comes up.
redetect_wan() {
    local iface="$1"

    # Source current assignment
    local current_wan=""
    local current_lan=""
    local current_wans=""
    local current_lans=""
    if [[ -f /etc/wml/interfaces.conf ]]; then
        current_wan=$(awk -F= '/^WAN_IF=/{gsub(/"/,"",$2); print $2}' /etc/wml/interfaces.conf)
        current_lan=$(awk -F= '/^LAN_IF=/{gsub(/"/,"",$2); print $2}' /etc/wml/interfaces.conf)
        current_wans=$(awk -F= '/^WAN_IFS=/{gsub(/"/,"",$2); print $2}' /etc/wml/interfaces.conf)
        current_lans=$(awk -F= '/^LAN_IFS=/{gsub(/"/,"",$2); print $2}' /etc/wml/interfaces.conf)
    fi

    log "Carrier UP on $iface (WANs=[$current_wans] LANs=[$current_lans])"

    # Multi-WAN: if iface is already in WAN_IFS, the wan-monitor will handle
    # health changes. Just nudge networkd to renew the lease.
    if [[ " $current_wans " == *" $iface "* ]]; then
        log "$iface is an existing WAN - nudging networkd"
        systemctl restart --no-block systemd-networkd 2>/dev/null || true
        return
    fi
    # Existing LAN port: just bring it up, nothing else to do
    if [[ " $current_lans " == *" $iface "* ]]; then
        log "$iface is an existing LAN - link came up"
        return
    fi

    # If this NIC is the known WAN: probe DHCP. If the new lease is inside
    # OUR LAN subnet, the cable was swapped — fall through to full redetect.
    if [[ "$iface" == "$current_wan" ]]; then
        log "Carrier on existing WAN ($iface) - verifying it's still upstream"
        sleep 2
        if timeout 6 dhclient -1 -v "$iface" 2>/dev/null; then
            new_lease=$(ip -o -4 addr show "$iface" | awk '{print $4}' | cut -d/ -f1 | head -1)
            lan_prefix=$(awk -F= '/^LAN_NETWORK=/{print $2}' /etc/wml/interfaces.conf 2>/dev/null | cut -d. -f1-3)
            dhclient -r "$iface" 2>/dev/null
            if [[ -n "$new_lease" && -n "$lan_prefix" && "$new_lease" == ${lan_prefix}.* ]]; then
                log "WAN cable swapped! $iface lease $new_lease is in LAN subnet — re-detecting"
                # fall through to full re-detect below
            else
                log "WAN $iface still upstream (lease $new_lease) - restarting networkd"
                systemctl restart --no-block systemd-networkd 2>/dev/null || true
                sleep 5
                /usr/local/sbin/wml-fix >/dev/null 2>&1 &
                return
            fi
        else
            log "WAN $iface no DHCP — cable likely swapped, re-detecting"
        fi
    fi
    # Known LAN: if it now gets a DHCP lease NOT in our subnet, the cables
    # were swapped (someone plugged the LAN port into the ISP).
    if [[ "$iface" == "$current_lan" ]]; then
        log "Carrier on existing LAN ($iface) - checking for upstream DHCP"
        sleep 2
        if timeout 6 dhclient -1 -v "$iface" 2>/dev/null; then
            new_lease=$(ip -o -4 addr show "$iface" | awk '{print $4}' | cut -d/ -f1 | head -1)
            lan_prefix=$(awk -F= '/^LAN_NETWORK=/{print $2}' /etc/wml/interfaces.conf 2>/dev/null | cut -d. -f1-3)
            dhclient -r "$iface" 2>/dev/null
            if [[ -n "$new_lease" && -n "$lan_prefix" && "$new_lease" != ${lan_prefix}.* ]]; then
                log "LAN cable swapped to upstream! $iface lease $new_lease — re-detecting"
                # fall through to full re-detect (this iface becomes new WAN)
            else
                log "LAN $iface unchanged"
                return
            fi
        else
            log "LAN $iface no DHCP - normal"
            return
        fi
    fi

    # New NIC came up. Probe for DHCP.
    log "Probing $iface for DHCP (newly connected)..."
    if timeout 8 dhclient -1 -v "$iface" 2>/dev/null && \
       ip -o -4 addr show "$iface" 2>/dev/null | grep -q "inet "; then
        lease_ip=$(ip -o -4 addr show "$iface" | awk '{print $4}' | cut -d/ -f1 | head -1)
        lan_prefix=$(awk -F= '/^LAN_NETWORK=/{print $2}' /etc/wml/interfaces.conf 2>/dev/null | cut -d. -f1-3)
        if [[ -n "$lease_ip" && -n "$lan_prefix" && "$lease_ip" == ${lan_prefix}.* ]]; then
            log "$iface lease $lease_ip inside LAN subnet - this is LAN side, leaving"
            dhclient -r "$iface" 2>/dev/null
            return
        fi
        log "$iface got DHCP $lease_ip - assigning as WAN"
        dhclient -r "$iface" 2>/dev/null  # release lease, networkd will renew
        # Update interfaces.conf
        local new_lan
        # Find the first NIC that ISN'T this one to be LAN
        for d in /sys/class/net/*; do
            n=$(basename "$d")
            case "$n" in
                lo|docker*|virbr*|veth*|tap*|wg*|br-*|tun*) continue ;;
            esac
            [[ -e "$d/device" ]] || continue
            if [[ "$n" != "$iface" ]]; then
                new_lan="$n"
                break
            fi
        done
        cat > /etc/wml/interfaces.conf <<EOF
WAN_IF=$iface
LAN_IF=$new_lan
LAN_IP=172.30.30.1
LAN_PREFIX=24
LAN_NETWORK=172.30.30.0/24
EOF
        # Rewrite networkd configs for new role assignment
        rm -f /etc/systemd/network/10-wml-*.network /etc/systemd/network/20-wml-*.network 2>/dev/null
        cat > /etc/systemd/network/10-wml-wan.network <<EOF
[Match]
Name=$iface

[Network]
DHCP=ipv4
IPv6AcceptRA=true

[DHCP]
UseDNS=false
UseDomains=false
UseHostname=false
EOF
        if [[ -n "$new_lan" ]]; then
            cat > /etc/systemd/network/20-wml-lan.network <<EOF
[Match]
Name=$new_lan

[Network]
Address=172.30.30.1/24
DHCPServer=no
IPMasquerade=no
EOF
        fi
        systemctl restart systemd-networkd 2>/dev/null
        sleep 3
        # Re-apply NAT + DHCP server config now that we know which is which
        /usr/local/sbin/wml-fix >/dev/null 2>&1 &
        log "WAN auto-assigned to $iface, LAN=$new_lan, NAT applied"
    else
        log "$iface got no DHCP - leaving as LAN"
        dhclient -r "$iface" 2>/dev/null
    fi
}

log "wml-carrier-watcher started"

# Use `ip monitor link` to stream link-state events. Each event line looks like:
#   3: eth1: <BROADCAST,...,UP,LOWER_UP> mtu 1500 ...
# LOWER_UP appearing = carrier just went up.
ip monitor link 2>/dev/null | while read -r line; do
    # Extract interface name and check if LOWER_UP just appeared
    if echo "$line" | grep -qE 'LOWER_UP'; then
        iface=$(echo "$line" | sed -E 's/^[0-9]+: ([^:@]+).*/\1/' | head -1)
        if [[ -n "$iface" ]] && [[ -e "/sys/class/net/$iface/device" ]]; then
            # Debounce: wait 2 sec then verify carrier is actually up
            sleep 2
            if [[ "$(cat /sys/class/net/$iface/carrier 2>/dev/null)" == "1" ]]; then
                redetect_wan "$iface"
            fi
        fi
    fi
done
