#!/usr/bin/env bash
# Configure Raspberry Pi OS: Wi-Fi client on IF_WAN (default wlan0), Ethernet IF_LAN
# (default eth0) toward an external AP. Static LAN IP, DHCP via dnsmasq, NAT masquerade.
#
# Usage:
#   sudo ./pi-eth-lan-router.sh install
#   sudo ./pi-eth-lan-router.sh remove
#
# Environment overrides (optional):
#   IF_WAN=wlan0 IF_LAN=eth0 LAN_IP=192.168.4.1 LAN_PREFIX=24 \
#   DHCP_START=192.168.4.100 DHCP_END=192.168.4.200 \
#   DNSMASQ_DNS=1.1.1.1,8.8.8.8 \
#   sudo ./pi-eth-lan-router.sh install

set -euo pipefail

IF_WAN="${IF_WAN:-wlan0}"
IF_LAN="${IF_LAN:-eth0}"
LAN_IP="${LAN_IP:-192.168.4.1}"
LAN_PREFIX="${LAN_PREFIX:-24}"
DHCP_START="${DHCP_START:-192.168.4.100}"
DHCP_END="${DHCP_END:-192.168.4.200}"
# Comma-separated DNS for DHCP clients (Pi does not need to run a resolver).
DNSMASQ_DNS="${DNSMASQ_DNS:-1.1.1.1,8.8.8.8}"

NM_CON_NAME="pi-eth-lan-router"
MARK_BEGIN="# BEGIN pi-eth-lan-router (scripts/pi-eth-lan-router.sh)"
MARK_END="# END pi-eth-lan-router"
SYSCTL_FILE="/etc/sysctl.d/99-pi-eth-lan-router.conf"
DNSMASQ_SNIPPET="/etc/dnsmasq.d/pi-eth-lan-router.conf"
NFT_SNIPPET="/etc/nftables.d/50-pi-eth-lan-router.nft"
NFT_INCLUDE='include "/etc/nftables.d/50-pi-eth-lan-router.nft"'
NFTABLES_CONF="/etc/nftables.conf"
DHCPCD_CONF="/etc/dhcpcd.conf"

die() { echo "error: $*" >&2; exit 1; }
log() { echo "$*"; }

need_root() {
  [[ "${EUID:-0}" -eq 0 ]] || die "run as root (sudo)"
}

have_cmd() { command -v "$1" >/dev/null 2>&1; }

apt_install() {
  export DEBIAN_FRONTEND=noninteractive
  apt-get update -qq
  apt-get install -y -qq dnsmasq nftables
}

write_sysctl() {
  cat >"$SYSCTL_FILE" <<EOF
# Managed by scripts/pi-eth-lan-router.sh
net.ipv4.ip_forward=1
EOF
  sysctl --system -q 2>/dev/null || sysctl -p "$SYSCTL_FILE" || true
}

remove_sysctl() {
  rm -f "$SYSCTL_FILE"
  sysctl --system -q 2>/dev/null || true
}

write_dnsmasq() {
  local mask="255.255.255.0"
  if [[ "$LAN_PREFIX" != "24" ]]; then
    die "only LAN_PREFIX=24 is supported by this script (extend dnsmasq netmask manually)"
  fi
  cat >"$DNSMASQ_SNIPPET" <<EOF
# Managed by scripts/pi-eth-lan-router.sh
interface=$IF_LAN
bind-interfaces
dhcp-range=$DHCP_START,$DHCP_END,$mask,24h
dhcp-option=option:router,$LAN_IP
dhcp-option=option:dns-server,$DNSMASQ_DNS
EOF
}

remove_dnsmasq() {
  rm -f "$DNSMASQ_SNIPPET"
}

write_nft() {
  mkdir -p /etc/nftables.d
  cat >"$NFT_SNIPPET" <<EOF
# Managed by scripts/pi-eth-lan-router.sh
table ip pi_eth_wlan_nat {
	chain postrouting {
		type nat hook postrouting priority 100; policy accept;
		oifname "$IF_WAN" masquerade
	}
}
EOF
  if [[ -f "$NFTABLES_CONF" ]] && ! grep -qF '50-pi-eth-lan-router.nft' "$NFTABLES_CONF" 2>/dev/null; then
    printf '\n# pi-eth-lan-router\n%s\n' "$NFT_INCLUDE" >>"$NFTABLES_CONF"
  elif [[ ! -f "$NFTABLES_CONF" ]]; then
    log "warning: $NFTABLES_CONF missing; NAT was not added for boot persistence. Install/configure nftables, or add: $NFT_INCLUDE"
  fi
}

remove_nft() {
  rm -f "$NFT_SNIPPET"
  if [[ -f "$NFTABLES_CONF" ]]; then
    sed -i '/# pi-eth-lan-router/d;/50-pi-eth-lan-router\.nft/d' "$NFTABLES_CONF" || true
  fi
  nft delete table ip pi_eth_wlan_nat 2>/dev/null || true
}

apply_nft() {
  if have_cmd nft; then
    nft delete table ip pi_eth_wlan_nat 2>/dev/null || true
    nft -f "$NFT_SNIPPET"
  fi
}

configure_nm_eth() {
  have_cmd nmcli || return 1
  systemctl is-active --quiet NetworkManager 2>/dev/null || return 1

  if nmcli -t -f NAME con show --active 2>/dev/null | grep -qxF "$NM_CON_NAME"; then
    nmcli con down "$NM_CON_NAME" || true
  fi
  if nmcli -t -f NAME con show 2>/dev/null | grep -qxF "$NM_CON_NAME"; then
    nmcli con mod "$NM_CON_NAME" \
      connection.interface-name "$IF_LAN" \
      ipv4.method manual \
      ipv4.addresses "${LAN_IP}/${LAN_PREFIX}" \
      ipv4.gateway "" \
      ipv4.dns "" \
      ipv4.never-default yes \
      ipv6.method ignore
  else
    nmcli con add type ethernet con-name "$NM_CON_NAME" ifname "$IF_LAN" \
      ipv4.method manual \
      ipv4.addresses "${LAN_IP}/${LAN_PREFIX}" \
      ipv4.gateway "" \
      ipv4.dns "" \
      ipv4.never-default yes \
      ipv6.method ignore
  fi
  if ! nmcli con up "$NM_CON_NAME"; then
    log "warning: could not activate '$NM_CON_NAME' (is $IF_LAN connected?); profile saved for next boot."
  fi
  return 0
}

remove_nm_eth() {
  have_cmd nmcli || return 0
  if nmcli -t -f NAME con show 2>/dev/null | grep -qxF "$NM_CON_NAME"; then
    nmcli con delete "$NM_CON_NAME" || true
  fi
}

configure_dhcpcd_eth() {
  [[ -f "$DHCPCD_CONF" ]] || return 1
  if grep -qF "$MARK_BEGIN" "$DHCPCD_CONF" 2>/dev/null; then
    sed -i "/$MARK_BEGIN/,/$MARK_END/d" "$DHCPCD_CONF" || true
  fi
  {
    echo "$MARK_BEGIN"
    echo "interface $IF_LAN"
    echo "static ip_address=${LAN_IP}/${LAN_PREFIX}"
    echo "nohook wpa_supplicant"
    echo "$MARK_END"
  } >>"$DHCPCD_CONF"
  systemctl restart dhcpcd 2>/dev/null || true
  return 0
}

remove_dhcpcd_block() {
  [[ -f "$DHCPCD_CONF" ]] || return 0
  if grep -qF "$MARK_BEGIN" "$DHCPCD_CONF" 2>/dev/null; then
    sed -i "/$MARK_BEGIN/,/$MARK_END/d" "$DHCPCD_CONF" || true
    systemctl restart dhcpcd 2>/dev/null || true
  fi
}

configure_eth_static() {
  if configure_nm_eth; then
    log "configured $IF_LAN via NetworkManager profile '$NM_CON_NAME'"
    return 0
  fi
  if configure_dhcpcd_eth; then
    log "configured $IF_LAN via dhcpcd ($DHCPCD_CONF)"
    return 0
  fi
  die "neither NetworkManager (active) nor $DHCPCD_CONF found; set $IF_LAN to ${LAN_IP}/${LAN_PREFIX} manually"
}

remove_eth_static() {
  remove_nm_eth
  remove_dhcpcd_block
}

do_install() {
  need_root
  log "installing packages (dnsmasq, nftables)…"
  apt_install

  log "writing sysctl, dnsmasq, nftables snippets…"
  write_sysctl
  write_dnsmasq
  write_nft

  log "setting static IP on $IF_LAN…"
  configure_eth_static

  log "restarting dnsmasq…"
  systemctl enable dnsmasq
  systemctl restart dnsmasq

  log "loading NAT rules and enabling nftables…"
  apply_nft
  systemctl enable nftables 2>/dev/null || true
  systemctl restart nftables 2>/dev/null || true

  log "done. Connect $IF_LAN to the external AP (DHCP off on the AP)."
  log "Join Wi-Fi on $IF_WAN to the uplink network and complete any captive portal on the Pi."
}

do_remove() {
  need_root
  remove_eth_static
  remove_dnsmasq
  systemctl restart dnsmasq 2>/dev/null || true

  remove_nft
  systemctl restart nftables 2>/dev/null || true

  remove_sysctl
  sysctl -w net.ipv4.ip_forward=0 2>/dev/null || true

  log "removed pi-eth-lan-router configuration snippets and NM profile '$NM_CON_NAME' (if present)."
}

usage() {
  cat <<EOF
Usage: sudo $0 install|remove

WAN (Wi-Fi client): $IF_WAN
LAN (Ethernet to AP): $IF_LAN
LAN address: ${LAN_IP}/${LAN_PREFIX}
DHCP range: $DHCP_START – $DHCP_END

Override with environment variables (see script header).
EOF
}

case "${1:-}" in
  install) do_install ;;
  remove) do_remove ;;
  *) usage; exit 1 ;;
esac
