#!/usr/bin/env bash
#
# provision_edge_tvh.sh
# Provision Intel Mac mini (Ubuntu 24.04 LTS) as an edge TVHeadend transcoder node.
# - FAILS IMMEDIATELY if Tang binding fails (first step).
# - Optional static IP per scheme 10.<site-id>.<vlan>.<host> (VLAN=82, host=50+node)
#   OR keep DHCP via --dhcp-only (no Netplan changes).
# - DNS (static mode): 1.1.1.1, 1.0.0.1
# - Hostname: tvh-<site>-h<node>.<site>.tvinfra.com
# - TVHeadend via APT (default) from upstream repo, non-interactive admin preseed.
#   Fallback to snap unless --strict-apt.
# - Registers to Landscape (best-effort)
#
set -Eeuo pipefail

#############################
# Config & Defaults
#############################
VLAN_ID_DEFAULT="82"
DNS1="1.1.1.1"
DNS2="1.0.0.1"
TANG_URL_DEFAULT="https://tang.docker.fmt.prodinfra.com"
LANDSCAPE_URL="https://landscape-c1-h1.fmt.prodinfra.com"
TIMEZONE="America/Los_Angeles"
# TVHeadend Cloudsmith repository (official)
TVH_CLOUDSMITH_SETUP_URL="https://dl.cloudsmith.io/public/tvheadend/tvheadend/setup.deb.sh"
# Michael Marley PPA (fallback)
TVH_PPA="ppa:mamarley/tvheadend-git"

#############################
# Logging helpers
#############################
log()  { printf "\033[1;34m[INFO]\033[0m %s\n" "$*"; }
warn() { printf "\033[1;33m[WARN]\033[0m %s\n" "$*"; }
err()  { printf "\033[1;31m[ERROR]\033[0m %s\n" "$*" >&2; }
die()  { err "$*"; exit 1; }

trap 'err "Script failed (line $LINENO)."; exit 1' ERR

#############################
# Root + OS checks
#############################
[[ $EUID -eq 0 ]] || die "Run as root (sudo)."
. /etc/os-release
[[ "${VERSION_CODENAME:-}" == "noble" || "${VERSION_ID:-}" == "24.04" ]] || warn "Built for Ubuntu 24.04 (noble). You are on: ${PRETTY_NAME:-unknown}."
if ! lscpu | grep -qi "GenuineIntel"; then warn "CPU is not Intel; continuing anyway."; fi

#############################
# CLI args
#############################
SITE=""          # e.g., sfo
SITE_ID=""       # e.g., 20 (second octet)
NODE=""          # integer (1..254)
TUNER_TYPES=""   # e.g., "ota", or "ota,catv"
VLAN_ID="$VLAN_ID_DEFAULT"
TANG_URL="$TANG_URL_DEFAULT"
DHCP_ONLY=0
NONINTERACTIVE=0
TVH_METHOD="apt"         # apt | snap
STRICT_APT=0
TVH_ADMIN_USER=""        # optional override
TVH_ADMIN_PASS=""        # optional override
REBIND_ONLY=0            # flag for rebind-only mode
USE_CURRENT_IP=0         # flag to use current IP as-is

usage() {
  cat <<EOF
Usage: sudo bash $0 [options]

Options:
  --site <code>            Site short code (e.g., sfo, hpn)         [required]
  --site-id <id>           Site numeric ID (second octet) e.g., 20  [required]
  --node <n>               Node number (1..254)                     [required]
  --tuner-types <list>     Comma-separated: ota,catv (optional)
  --vlan <id>              VLAN ID (default: $VLAN_ID_DEFAULT)
  --tang-url <url>         Tang URL (default: $TANG_URL_DEFAULT)
  --dhcp-only              Skip static IP; keep whatever DHCP gives.
  --tvh-method <apt|snap>  TVHeadend install method (default: apt)
  --strict-apt             If apt install fails, do NOT fall back to snap.
  --tvh-admin-user <u>     TVH admin username (default: auto-generated)
  --tvh-admin-pass <p>     TVH admin password (default: auto-generated)
  --rebind-only            Only fix Tang binding, skip full provisioning
  --use-current-ip         Use current DHCP IP as static (ignore site-id scheme)
  --noninteractive         Do not prompt; fail if missing params
  -h, --help               This help

Examples:
  $0 --site sfo --site-id 20 --node 1 --tuner-types ota
  $0 --site hpn --site-id 10 --node 2 --dhcp-only
  $0 --site sfo --site-id 20 --node 3 --tvh-method snap
EOF
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --site) SITE="${2:-}"; shift 2;;
    --site-id) SITE_ID="${2:-}"; shift 2;;
    --node) NODE="${2:-}"; shift 2;;
    --tuner-types) TUNER_TYPES="${2:-}"; shift 2;;
    --vlan) VLAN_ID="${2:-}"; shift 2;;
    --tang-url) TANG_URL="${2:-}"; shift 2;;
    --dhcp-only) DHCP_ONLY=1; shift;;
    --tvh-method) TVH_METHOD="${2:-}"; shift 2;;
    --strict-apt) STRICT_APT=1; shift;;
    --tvh-admin-user) TVH_ADMIN_USER="${2:-}"; shift 2;;
    --tvh-admin-pass) TVH_ADMIN_PASS="${2:-}"; shift 2;;
    --rebind-only) REBIND_ONLY=1; shift;;
    --use-current-ip) USE_CURRENT_IP=1; shift;;
    --internal-rebind-mode) INTERNAL_REBIND=1; shift;;
    --noninteractive) NONINTERACTIVE=1; shift;;
    -h|--help) usage; exit 0;;
    *) die "Unknown argument: $1 (see --help)";;
  esac
done


prompt_if_empty() {
  local varname="$1" prompt="$2"
  if [[ -z "${!varname}" && $NONINTERACTIVE -eq 0 ]]; then
    read -rp "$prompt: " val
    eval "$varname=\"\$val\""
  fi
  [[ -n "${!varname}" ]] || die "Missing required value: $varname"
}

prompt_if_empty SITE     "Enter site code (e.g., sfo, hpn)"
prompt_if_empty SITE_ID  "Enter site ID/second octet (e.g., 20)"
prompt_if_empty NODE     "Enter node number (1..254)"
[[ "$NODE" =~ ^[0-9]+$ ]] || die "--node must be an integer"
[[ "$SITE_ID" =~ ^[0-9]+$ ]] || die "--site-id must be an integer"

#############################
# Detect primary NIC with 10.x.x.x IP (needed for IP calculations)
#############################
detect_iface_with_10x() {
  # First try to find interface with 10.x.x.x IP
  for IF in $(ls /sys/class/net | grep -E '^(en|eth)'); do
    [[ -d "/sys/class/net/$IF/device" ]] || continue
    # Check if interface has 10.x.x.x IP
    if ip addr show "$IF" 2>/dev/null | grep -q "inet 10\."; then
      echo "$IF"
      return 0
    fi
  done
  
  # Fallback: find any physical ethernet interface  
  for IF in $(ls /sys/class/net | grep -E '^(en|eth)'); do
    [[ -d "/sys/class/net/$IF/device" ]] || continue
    echo "$IF"
    return 0
  done
  
  return 1
}

IFACE="$(detect_iface_with_10x)" || die "Could not detect an Ethernet interface (en*/eth*)."
# Prefer primary IP (without 'secondary' keyword), fallback to first IP found
CURRENT_IP="$(ip addr show "$IFACE" 2>/dev/null | grep "inet 10\." | grep -v "secondary" | awk '{print $2}' | cut -d/ -f1 | head -1 || ip addr show "$IFACE" 2>/dev/null | grep "inet 10\." | awk '{print $2}' | cut -d/ -f1 | head -1 || echo "none")"
ALL_IPS="$(ip addr show "$IFACE" 2>/dev/null | grep "inet 10\." | awk '{print $2}' | cut -d/ -f1 | tr '\n' ' ')"
log "Detected interface: $IFACE (primary IP: $CURRENT_IP, all IPs: $ALL_IPS)"

# Early exit for rebind-only mode
if [[ $REBIND_ONLY -eq 1 ]]; then
  log "REBIND-ONLY MODE: Fixing Tang binding only, skipping full provisioning"
  SKIP_FULL_PROVISIONING=1
fi

if [[ -z "$TUNER_TYPES" && $NONINTERACTIVE -eq 0 ]]; then
  read -rp "Enter tuner types (e.g., ota,catv) [optional]: " TUNER_TYPES || true
fi

# Initialize TVH admin credentials (will be updated later if existing install is detected)
if [[ -z "$TVH_ADMIN_USER" ]]; then TVH_ADMIN_USER="admin"; fi
if [[ -z "$TVH_ADMIN_PASS" ]]; then
  TVH_ADMIN_PASS="$(openssl rand -base64 12 | tr -d '/+=' | head -c 12)" || TVH_ADMIN_PASS="admin$(date +%s | tail -c 6)"
fi

#############################
# Derived naming & addressing
#############################
if [[ $USE_CURRENT_IP -eq 1 ]]; then
  # Force use current IP (testing mode)
  if [[ "$CURRENT_IP" != "none" ]]; then
    IP_ADDR="$CURRENT_IP"
    GATEWAY="$(ip route show dev "$IFACE" | grep "default" | awk '{print $3}' | head -1)"
    [[ -z "$GATEWAY" ]] && GATEWAY="$(echo "$CURRENT_IP" | cut -d. -f1-3).1"
    CIDR="/24"
    
    echo
    echo "************************************************************"
    echo "* FORCED CURRENT IP MODE (TESTING)"
    echo "* Using current IP: $CURRENT_IP on $IFACE"
    echo "* Gateway: $GATEWAY"
    echo "* IGNORING site-id scheme for testing purposes"
    echo "************************************************************"
    echo
  else
    die "Cannot use current IP - no IP detected on interface $IFACE"
  fi
elif [[ $DHCP_ONLY -eq 0 ]]; then
  # Detect current IP and use it as static (smart detection)
  if [[ "$CURRENT_IP" != "none" && "$CURRENT_IP" =~ ^10\. ]]; then
    # Use the current working IP as static
    IP_ADDR="$CURRENT_IP"
    CURRENT_NETWORK="$(ip route show dev "$IFACE" | grep -E "^10\." | head -1 | awk '{print $1}')"
    GATEWAY="$(ip route show dev "$IFACE" | grep "default" | awk '{print $3}' | head -1)"
    [[ -z "$GATEWAY" ]] && GATEWAY="$(echo "$CURRENT_IP" | cut -d. -f1-3).1"
    CIDR="/24"
    
    echo
    echo "************************************************************"
    echo "* DETECTED EXISTING IP CONFIGURATION"
    echo "* Current IP: $CURRENT_IP on $IFACE"
    echo "* Will configure as STATIC to eliminate DHCP race conditions"
    echo "* Network: $CURRENT_NETWORK"
    echo "* Gateway: $GATEWAY"
    echo "************************************************************"
    echo
  else
    # Fallback to calculated IP scheme
    HOST_OCTET=$((50 + NODE))
    [[ $HOST_OCTET -ge 1 && $HOST_OCTET -le 254 ]] || die "Computed host octet invalid: $HOST_OCTET (from node=$NODE)."
    IP_ADDR="10.${SITE_ID}.${VLAN_ID}.${HOST_OCTET}"
    CIDR="/24"
    GATEWAY="10.${SITE_ID}.${VLAN_ID}.1"
    
    echo
    echo "************************************************************"
    echo "* USING CALCULATED IP SCHEME"
    echo "* Calculated IP: $IP_ADDR (site $SITE_ID, vlan $VLAN_ID, node $NODE)"
    echo "* Gateway: $GATEWAY"
    echo "************************************************************"
    echo
  fi
else
  IP_ADDR="<dhcp>"
  CIDR=""
  GATEWAY="<dhcp>"
fi

HOSTNAME_SHORT="tvh-${SITE}-h${NODE}"
HOSTNAME_FQDN="${HOSTNAME_SHORT}.${SITE}.tvinfra.com"

log "Planned hostname : $HOSTNAME_FQDN"
log "Addressing mode  : $([[ $DHCP_ONLY -eq 1 ]] && echo DHCP || echo "STATIC ${IP_ADDR}${CIDR} gw=${GATEWAY} dns=${DNS1},${DNS2}")"
log "Tang URL         : ${TANG_URL}"
log "TVH method       : ${TVH_METHOD}  (strict-apt=${STRICT_APT})"
if [[ "$TVH_ADMIN_PASS" == "<use existing credentials>" ]]; then
  log "TVH admin        : ${TVH_ADMIN_USER} / ${TVH_ADMIN_PASS}"
else
  log "TVH admin        : ${TVH_ADMIN_USER} / ${TVH_ADMIN_PASS}  (store securely)"
fi

#############################
# Detect primary NIC with 10.x.x.x IP
#############################
detect_iface_with_10x() {
  # First try to find interface with 10.x.x.x IP
  for IF in $(ls /sys/class/net | grep -E '^(en|eth)'); do
    [[ -d "/sys/class/net/$IF/device" ]] || continue
    # Check if interface has 10.x.x.x IP
    if ip addr show "$IF" 2>/dev/null | grep -q "inet 10\."; then
      echo "$IF"
      return 0
    fi
  done
  
  # Fallback: find any physical ethernet interface  
  for IF in $(ls /sys/class/net | grep -E '^(en|eth)'); do
    [[ -d "/sys/class/net/$IF/device" ]] || continue
    echo "$IF"
    return 0
  done
  
  return 1
}

IFACE="$(detect_iface_with_10x)" || die "Could not detect an Ethernet interface (en*/eth*)."
CURRENT_IP="$(ip addr show "$IFACE" 2>/dev/null | grep "inet 10\." | awk '{print $2}' | cut -d/ -f1 | head -1 || echo "none")"
log "Detected interface: $IFACE (current IP: $CURRENT_IP)"

#############################
# Pre-flight checks
#############################
log "Performing pre-flight checks..."

# Check Tang server connectivity and /adv endpoint
log "Testing Tang server connectivity: $TANG_URL"
if ! curl -fsS --max-time 10 "$TANG_URL/adv" >/dev/null; then
  die "Cannot reach Tang server at $TANG_URL/adv - network connectivity required"
fi

# Validate Tang advertisement returns valid JSON
ADV_TEST="$(curl -fsS --max-time 10 "$TANG_URL/adv")" || die "Tang server unreachable"
if ! printf '%s' "$ADV_TEST" | jq empty 2>/dev/null; then
  die "Tang server returned invalid JSON - server may be misconfigured"
fi
log "Tang server connectivity: OK"

# Check for LUKS encryption
if [[ ! -s /etc/crypttab ]]; then
  die "No /etc/crypttab found - this script requires full-disk encryption (LUKS)"
fi
if ! grep -qE '\bluks\b' /etc/crypttab; then
  die "No LUKS entries found in /etc/crypttab - full-disk encryption required"
fi
log "LUKS encryption detected: OK"

#############################
# APT warm-up
#############################
export DEBIAN_FRONTEND=noninteractive
log "Refreshing apt cache..."
apt-get update -y || warn "Initial apt update had issues, continuing anyway"

#############################
# STEP 1: TANG BIND (FAIL-FAST)
#############################
log "Installing clevis + tools for Tang binding..."
apt-get install -y clevis clevis-luks clevis-initramfs jq jose ca-certificates

# Figure LUKS device from /etc/crypttab
mapfile -t CRYPT_LINES < <(grep -vE '^\s*#' /etc/crypttab | grep -E '\bluks\b')
CRYPT_UUID="$(echo "${CRYPT_LINES[0]}" | awk '{print $2}' | sed 's/^UUID=//')"
[[ -n "$CRYPT_UUID" ]] || die "Failed to parse LUKS UUID from /etc/crypttab."
DEV="$(blkid -t UUID="$CRYPT_UUID" -o device || true)"
[[ -b "$DEV" ]] || die "Could not resolve block device for UUID=$CRYPT_UUID."
log "Target LUKS device: $DEV"

# Check if device already has Tang/clevis binding
log "Checking for existing Tang bindings..."
SKIP_TANG_BINDING=0

if command -v clevis >/dev/null 2>&1; then
  # Multiple ways to check for Tang bindings - be very thorough
  EXISTING_BINDINGS="$(clevis luks list -d "$DEV" 2>/dev/null || echo "")"
  CRYPTSETUP_INFO="$(cryptsetup luksDump "$DEV" 2>/dev/null || echo "")"
  
  # Check via clevis list
  HAS_CLEVIS_TANG=0
  if [[ -n "$EXISTING_BINDINGS" ]] && echo "$EXISTING_BINDINGS" | grep -q "tang"; then
    HAS_CLEVIS_TANG=1
  fi
  
  # Check via cryptsetup dump for clevis metadata
  HAS_CLEVIS_META=0  
  if echo "$CRYPTSETUP_INFO" | grep -q "clevis"; then
    HAS_CLEVIS_META=1
  fi
  
  # Check for any LUKS slots that might have Tang bindings
  TANG_SLOTS=""
  for slot in {0..7}; do
    if clevis luks list -d "$DEV" -s "$slot" 2>/dev/null | grep -q "tang"; then
      TANG_SLOTS="$TANG_SLOTS $slot"
    fi
  done
  
  if [[ $HAS_CLEVIS_TANG -eq 1 || $HAS_CLEVIS_META -eq 1 || -n "$TANG_SLOTS" ]]; then
    log "Found existing Tang/clevis configuration on $DEV"
    if [[ -n "$EXISTING_BINDINGS" ]]; then
      echo "$EXISTING_BINDINGS" | grep "tang" | while read -r line; do
        echo "  $line"
      done
    fi
    if [[ -n "$TANG_SLOTS" ]]; then
      log "Tang slots detected:$TANG_SLOTS"
    fi
    
    # Check if binding is to the same Tang server (if we can tell)
    if [[ -n "$EXISTING_BINDINGS" ]] && echo "$EXISTING_BINDINGS" | grep -q "$TANG_URL"; then
      log "Device already has Tang binding to this server ($TANG_URL)"
      read -rp "Skip Tang binding and continue with rest of setup? [Y/n]: " SKIP_CHOICE </dev/tty
      if [[ "${SKIP_CHOICE,,}" != "n" ]]; then
        log "Skipping Tang binding - using existing configuration"
        SKIP_TANG_BINDING=1
      else
        warn "Creating additional Tang binding to same server (not recommended)"
        SKIP_TANG_BINDING=0
      fi
    else
      log "Device has existing Tang/clevis configuration (server unknown)"
      read -rp "Skip Tang binding and continue with rest of setup? [Y/n]: " SKIP_CHOICE </dev/tty
      if [[ "${SKIP_CHOICE,,}" != "n" ]]; then
        log "Skipping Tang binding - using existing configuration"
        SKIP_TANG_BINDING=1
      else
        warn "Creating additional Tang binding"
        SKIP_TANG_BINDING=0
      fi
    fi
  else
    log "No existing Tang bindings detected"
    SKIP_TANG_BINDING=0
  fi
else
  log "Clevis not yet available - will install and bind"
  SKIP_TANG_BINDING=0
fi

# Ensure Tang reachable + fetch thumbprint
log "Fetching Tang advert from: $TANG_URL/adv"
ADV_JSON="$(curl -fsS --max-time 10 "$TANG_URL/adv")" || die "Cannot reach Tang at $TANG_URL/adv"
THP="$(printf '%s' "$ADV_JSON" | jose jwk thp -i-)" || die "Failed computing Tang JWK thumbprint."
log "Tang thumbprint: $THP"

# Save advertisement to temp file for clevis
ADV_FILE="/tmp/tang_adv_$$.json"
printf '%s' "$ADV_JSON" > "$ADV_FILE"

# Ensure initramfs networking (DHCP) for early boot unlock
log "Ensuring initramfs networking: explicit interface with 2-minute timeout for Tang unlock"

# Backup original grub config
cp /etc/default/grub /etc/default/grub.backup

# Network parameters for Tang unlock - static IP only (reliable for remote deployment)
if [[ $DHCP_ONLY -eq 0 ]]; then
  # Static IP configuration burned into initramfs (works at deployment site)
  NETWORK_PARAMS="ip=${IP_ADDR}::${GATEWAY}:255.255.255.0:${HOSTNAME_FQDN}:${IFACE}:none:${DNS1}:${DNS2}"
  
  echo
  echo "************************************************************"  
  echo "* STATIC IP BURNED IN FOR DEPLOYMENT"
  echo "* IP: $IP_ADDR"
  echo "* Gateway: $GATEWAY" 
  echo "* Interface: $IFACE"
  echo "* This device will ONLY work on networks with this IP scheme"
  echo "* No DHCP dependency during boot - reliable Tang unlock"
  echo "************************************************************"
  echo
else
  # DHCP fallback (less reliable but flexible)
  NETWORK_PARAMS="ip=::::::dhcp"
  warn "DHCP mode: Tang unlock may fail due to timing issues"
fi

# Clean and rebuild GRUB_CMDLINE_LINUX_DEFAULT
if grep -q '^GRUB_CMDLINE_LINUX_DEFAULT=' /etc/default/grub; then
  # Extract current parameters, remove network ones, add new ones
  CURRENT_LINE="$(grep '^GRUB_CMDLINE_LINUX_DEFAULT=' /etc/default/grub)"
  CURRENT_PARAMS="$(echo "$CURRENT_LINE" | sed 's/^GRUB_CMDLINE_LINUX_DEFAULT="//' | sed 's/"$//')"
  # Remove existing network parameters (all variants)
  CLEANED_PARAMS="$(echo "$CURRENT_PARAMS" | sed 's/ ip=[^ ]*//g; s/ rd\.neednet=[^ ]*//g; s/ rd\.timeout=[^ ]*//g; s/ rd\.retry=[^ ]*//g; s/ netroot=[^ ]*//g; s/ rd\.luks\.options=[^ ]*//g; s/ rootdelay=[^ ]*//g')"
  # Build new line
  NEW_LINE="GRUB_CMDLINE_LINUX_DEFAULT=\"${CLEANED_PARAMS} ${NETWORK_PARAMS}\""
  # Replace in file
  sed -i "s|^GRUB_CMDLINE_LINUX_DEFAULT=.*|${NEW_LINE}|" /etc/default/grub
else
  echo "GRUB_CMDLINE_LINUX_DEFAULT=\"quiet splash ${NETWORK_PARAMS}\"" >> /etc/default/grub
fi

log "Updated GRUB config with Ubuntu 24.04 network parameters: ${NETWORK_PARAMS}"

# Ensure initramfs includes network drivers for Mac Mini Broadcom NICs
log "Ensuring Broadcom network drivers in initramfs..."
if ! grep -q "MODULES=dep" /etc/initramfs-tools/initramfs.conf; then
  sed -i 's/^MODULES=.*/MODULES=dep/' /etc/initramfs-tools/initramfs.conf
fi

# Add both Intel and Broadcom drivers for Mac Mini variations
if ! grep -q "e1000e\|tg3" /etc/initramfs-tools/modules 2>/dev/null; then
  echo "# Added by edge-tvh provisioning for Mac Mini Tang unlock" >> /etc/initramfs-tools/modules
  echo "e1000e" >> /etc/initramfs-tools/modules    # Intel i219 Ethernet (some Mac Mini models)
  echo "tg3" >> /etc/initramfs-tools/modules       # Broadcom NetXtreme BCM57xx (other Mac Mini models)
  echo "bnx2" >> /etc/initramfs-tools/modules      # Broadcom NetXtreme II (additional coverage)
fi

# Create Tang IP configuration for robust routing
TANG_IP="$(dig +short tang.docker.fmt.prodinfra.com | head -1 2>/dev/null || echo "10.222.90.90")"
mkdir -p /etc/initramfs-tools/conf.d
echo "TANG_IP=$TANG_IP" > /etc/initramfs-tools/conf.d/clevis-network

# Create proper network wait script for Tang unlock (runs BEFORE clevis)
mkdir -p /etc/initramfs-tools/scripts/init-premount
cat > /etc/initramfs-tools/scripts/init-premount/network-wait <<'WAIT_SCRIPT'
#!/bin/sh
case "$1" in prereqs) exit 0;; esac
. /scripts/functions

# Load Tang IP if available
[ -r /conf/conf.d/clevis-network ] && . /conf/conf.d/clevis-network

log_begin_msg "Bringing up Ethernet and waiting for DHCP"

# Bring up all physical en*/eth* and try DHCP with longer timeout
for IF in $(ls /sys/class/net | grep -E '^(en|eth)'); do
  [ -e "/sys/class/net/$IF/device" ] || continue    # skip virtual
  ip link set "$IF" up 2>/dev/null
  ipconfig -t 60 -c dhcp -d "$IF" 2>/dev/null &    # 60 second timeout, run in background
done

# Wait for background DHCP processes to complete
wait

# Wait up to ~60s for a usable address/route
RETRIES=120
while [ $RETRIES -gt 0 ]; do
  # If Tang is known, prefer checking route to it
  if [ -n "$TANG_IP" ]; then
    if ip route get "$TANG_IP" 2>/dev/null | grep -q ' dev '; then
      DEV=$(ip route get "$TANG_IP" 2>/dev/null | awk '/ dev /{for(i=1;i<=NF;i++) if($i=="dev"){print $(i+1); exit}}')
      IP=$(ip -4 addr show "$DEV" 2>/dev/null | awk '/inet /{print $2}' | cut -d/ -f1 | head -n1)
      log_end_msg 0 "Network ready: $DEV has IP $IP; route to Tang OK"
      exit 0
    fi
  else
    # Otherwise accept any IPv4 lease on a physical iface
    if ip -4 addr | grep -q 'inet .* scope global'; then
      DEV=$(ip -4 addr | awk '/inet .*global/ {print $NF; exit}')
      IP=$(ip -4 addr show "$DEV" | awk '/inet /{print $2}' | cut -d/ -f1 | head -n1)
      log_end_msg 0 "Network ready: $DEV has IP $IP"
      exit 0
    fi
  fi
  sleep 0.5; RETRIES=$((RETRIES-1))
done

log_end_msg 1 "Network timeout: no DHCP lease/route to Tang"
exit 0
WAIT_SCRIPT

chmod +x /etc/initramfs-tools/scripts/init-premount/network-wait

# Update crypttab for proper initramfs handling
if [[ -f /etc/crypttab ]]; then
  log "Updating crypttab for initramfs network timeout..."
  # Check if we're using systemd in initramfs
  if [[ -f /etc/initramfs-tools/conf.d/cryptsetup ]] && grep -q "CRYPTSETUP=y" /etc/initramfs-tools/conf.d/cryptsetup; then
    # Add initramfs option only
    if ! grep -q "initramfs" /etc/crypttab; then
      sed -i '/luks/ s/$/,initramfs/' /etc/crypttab 2>/dev/null || true
    fi
  else
    log "Using basic crypttab configuration"
  fi
fi

# Update both GRUB and initramfs to apply all changes  
update-grub
update-initramfs -u -k all

# Rebind-only mode: clean up and exit early
if [[ "${SKIP_FULL_PROVISIONING:-0}" -eq 1 ]]; then
  log "REBIND-ONLY MODE: Cleaning up existing Tang bindings..."
  
  # Remove all existing Tang bindings
  for slot in {1..7}; do
    if clevis luks list -d "$DEV" -s "$slot" 2>/dev/null | grep -q "tang"; then
      log "Removing broken Tang binding from slot $slot"
      clevis luks unbind -d "$DEV" -s "$slot" -f 2>/dev/null || true
    fi
  done
  
  # Update Netplan for static IP if not DHCP-only
  if [[ $DHCP_ONLY -eq 0 ]]; then
    log "Updating Netplan for static IP: $IP_ADDR"
    cat > /etc/netplan/90-edge-tvh.yaml <<YAML
network:
  version: 2
  ethernets:
    ${IFACE}:
      dhcp4: false
      addresses: [ "${IP_ADDR}/24" ]
      gateway4: ${GATEWAY}
      nameservers:
        addresses: [ ${DNS1}, ${DNS2} ]
YAML
    netplan apply || warn "Netplan apply failed"
  fi
  
  log "REBIND-ONLY: Network configuration updated, proceeding with Tang binding..."
fi

# Bind a new LUKS slot to Tang (unless skipping)
if [[ $SKIP_TANG_BINDING -eq 1 ]]; then
  log "Skipping Tang binding - using existing binding"
else
  # Prompt for LUKS passphrase (with confirmation)
  echo
  echo "************************************************************"
  echo "* LUKS DISK ENCRYPTION PASSPHRASE REQUIRED"
  echo "* This is the passphrase set during Ubuntu installation"
  echo "* for full-disk encryption (NOT the user login password)"
  echo "************************************************************"
  while true; do
    read -rsp "* Enter LUKS passphrase: " LUKS_PASS1 </dev/tty
    echo
    read -rsp "* Confirm LUKS passphrase: " LUKS_PASS2 </dev/tty
    echo
    if [[ "$LUKS_PASS1" == "$LUKS_PASS2" ]]; then
      echo "************************************************************"
      break
    else
      err "Passphrases do not match. Please try again."
    fi
  done

  # Bind a new LUKS slot to Tang (let clevis auto-detect interface)
  log "Binding new LUKS slot via clevis (auto-detect interface)..."
  echo "$LUKS_PASS1" | clevis luks bind -d "$DEV" tang "{\"url\":\"$TANG_URL\",\"adv\":\"$ADV_FILE\"}"
  unset LUKS_PASS1 LUKS_PASS2

  # Verify binding exists
  log "Verifying Tang binding..."
  sleep 2  # Give clevis a moment to register the binding

  BINDING_CHECK="$(clevis luks list -d "$DEV" 2>/dev/null || echo "")"
  if [[ -z "$BINDING_CHECK" ]]; then
    warn "clevis luks list returned empty - checking alternative method..."
    # Alternative check: look for clevis metadata in LUKS header
    if cryptsetup luksDump "$DEV" | grep -q "clevis"; then
      log "Tang binding detected via cryptsetup (clevis metadata found)"
    else
      die "No Tang binding detected - binding may have failed"
    fi
  elif echo "$BINDING_CHECK" | grep -q "tang"; then
    log "Tang binding successful - verified via clevis list"
    echo "$BINDING_CHECK" | grep "tang" | while read -r line; do
      echo "  $line"
    done
  else
    warn "clevis list shows bindings but no Tang entries:"
    echo "$BINDING_CHECK"
    die "Tang binding verification failed"
  fi
fi

rm -f "$ADV_FILE"

# Build initramfs
update-initramfs -u
log "IMPORTANT: This node REQUIRES a live Ethernet link before power is applied, or the disk will not auto-unlock."

# Exit early for rebind-only mode
if [[ "${SKIP_FULL_PROVISIONING:-0}" -eq 1 ]]; then
  echo
  echo "======================================================================"
  echo "✅ REBIND-ONLY MODE COMPLETE"
  echo "----------------------------------------------------------------------"
  echo "Tang binding fixed with static IP: $IP_ADDR"
  echo "Interface: $IFACE"
  echo "Gateway: $GATEWAY"
  echo "----------------------------------------------------------------------"
  echo "Next step: Reboot to test automatic Tang unlock"
  echo "  sudo reboot"
  echo "======================================================================"
  exit 0
fi

#############################
# STEP 2: Hostname & /etc/hosts
#############################
log "Setting hostname to $HOSTNAME_FQDN ..."
hostnamectl set-hostname "$HOSTNAME_FQDN"
if ! grep -q "$HOSTNAME_FQDN" /etc/hosts; then
  echo "127.0.1.1   $HOSTNAME_FQDN $HOSTNAME_SHORT" >> /etc/hosts
fi

#############################
# STEP 3: Netplan (skip if --dhcp-only)
#############################
if [[ $DHCP_ONLY -eq 0 ]]; then
  log "Configuring Netplan with static IP on $IFACE ..."
  NETPLAN_FILE="/etc/netplan/90-edge-tvh.yaml"
  cat > "$NETPLAN_FILE" <<YAML
network:
  version: 2
  ethernets:
    ${IFACE}:
      dhcp4: false
      addresses: [ "${IP_ADDR}/24" ]
      gateway4: ${GATEWAY}
      nameservers:
        addresses: [ ${DNS1}, ${DNS2} ]
YAML
  netplan generate
  netplan apply
  log "Netplan applied."
else
  log "DHCP-only selected: leaving existing Netplan untouched."
fi

#############################
# STEP 4: Media stack & tools
#############################
log "Installing media drivers and tools..."
apt-get install -y ffmpeg vainfo intel-gpu-tools intel-media-va-driver-non-free || true
(vainfo >/dev/null 2>&1 && log "VAAPI available.") || warn "vainfo failed; VAAPI may require reboot/driver check."

#############################
# STEP 5: TVHeadend install
#############################

# Check if TVHeadend is already running
check_existing_tvh() {
  local tvh_running=0
  local tvh_port_active=0
  local tvh_service_active=0
  
  # Check if port 9981 is active
  if ss -tuln 2>/dev/null | grep -q ":9981 "; then
    tvh_port_active=1
  fi
  
  # Check if systemd service is active (APT install)
  if systemctl is-active --quiet tvheadend 2>/dev/null; then
    tvh_service_active=1
  fi
  
  # Check if snap is running
  if snap list tvheadend >/dev/null 2>&1 && snap services tvheadend 2>/dev/null | grep -q "active"; then
    tvh_running=1
  fi
  
  if [[ $tvh_port_active -eq 1 || $tvh_service_active -eq 1 || $tvh_running -eq 1 ]]; then
    log "Found existing TVHeadend installation:"
    if [[ $tvh_port_active -eq 1 ]]; then
      echo "  - Port 9981 is active"
    fi
    if [[ $tvh_service_active -eq 1 ]]; then
      echo "  - systemd service is running (APT installation)"
    fi
    if [[ $tvh_running -eq 1 ]]; then
      echo "  - snap service is running"
    fi
    
    read -rp "Skip TVHeadend installation and continue with remaining setup? [Y/n]: " SKIP_TVH_CHOICE </dev/tty
    if [[ "${SKIP_TVH_CHOICE,,}" != "n" ]]; then
      log "Skipping TVHeadend installation - using existing installation"
      return 1  # Skip installation
    else
      warn "Proceeding with TVHeadend installation (may conflict with existing)"
      return 0  # Continue with installation
    fi
  fi
  
  return 0  # No existing installation found, proceed
}

# Check for existing TVHeadend
if ! check_existing_tvh; then
  SKIP_TVH_INSTALL=1
  # Update credentials display for existing installations
  TVH_ADMIN_USER="<existing>"
  TVH_ADMIN_PASS="<use existing credentials>"
else
  SKIP_TVH_INSTALL=0
fi
install_tvh_cloudsmith() {
  log "Setting up TVHeadend Cloudsmith repository..."
  
  # Clean up any broken TVHeadend repositories first
  log "Cleaning up old TVHeadend repositories..."
  rm -f /etc/apt/sources.list.d/tvheadend*.list
  rm -f /usr/share/keyrings/tvheadend*.gpg
  
  # Update apt to clean state
  apt-get update -y 2>/dev/null || true
  
  # Now install Cloudsmith repository
  if curl -fsSL --max-time 30 "$TVH_CLOUDSMITH_SETUP_URL" | bash; then
    log "Cloudsmith repository setup successful"
  else
    warn "Cloudsmith repository setup failed"
    return 1
  fi
  
  apt-get update -y
  
  log "Preseeding TVHeadend admin user/pass (non-interactive)..."
  echo "tvheadend tvheadend/admin_username string ${TVH_ADMIN_USER}" | debconf-set-selections
  echo "tvheadend tvheadend/admin_password password ${TVH_ADMIN_PASS}" | debconf-set-selections
  echo "tvheadend tvheadend/admin_password_again password ${TVH_ADMIN_PASS}" | debconf-set-selections
  echo "tvheadend tvheadend/enable_server boolean true" | debconf-set-selections
  echo "tvheadend tvheadend/keep_configuration boolean true" | debconf-set-selections

  apt-get install -y tvheadend
  systemctl enable tvheadend || true
  systemctl restart tvheadend || true
}

install_tvh_ppa() {
  log "Setting up TVHeadend via Michael Marley PPA..."
  
  # Clean up any broken TVHeadend repositories first
  log "Cleaning up old TVHeadend repositories..."
  rm -f /etc/apt/sources.list.d/tvheadend*.list
  rm -f /usr/share/keyrings/tvheadend*.gpg
  
  # Update apt to clean state
  apt-get update -y 2>/dev/null || true
  
  apt-get install -y software-properties-common
  add-apt-repository -y "$TVH_PPA"
  apt-get update -y

  apt-get install -y tvheadend
  systemctl enable tvheadend || true
  systemctl restart tvheadend || true
}

install_tvh_snap() {
  log "Installing TVHeadend via snap (fallback)..."
  snap install tvheadend
  snap connect tvheadend:network-bind || true
}

if [[ $SKIP_TVH_INSTALL -eq 1 ]]; then
  log "Skipping TVHeadend installation - using existing installation"
elif [[ "$TVH_METHOD" == "apt" ]]; then
  set +e
  # Try Cloudsmith first (official), then PPA, then snap
  log "Attempting TVHeadend installation via Cloudsmith repository..."
  install_tvh_cloudsmith
  TVH_RC=$?
  
  if [[ $TVH_RC -ne 0 ]]; then
    warn "Cloudsmith installation failed, trying PPA..."
    install_tvh_ppa
    TVH_RC=$?
  fi
  
  set -e
  if [[ $TVH_RC -ne 0 ]]; then
    if [[ $STRICT_APT -eq 1 ]]; then
      die "TVHeadend APT installation failed and --strict-apt is set."
    else
      warn "All APT methods failed; falling back to snap."
      install_tvh_snap || warn "Snap install also failed."
    fi
  fi
elif [[ "$TVH_METHOD" == "snap" ]]; then
  install_tvh_snap
else
  warn "Unknown --tvh-method '$TVH_METHOD'; defaulting to APT..."
  set +e
  install_tvh_cloudsmith || install_tvh_ppa || install_tvh_snap || warn "All installation methods failed."
  set -e
fi

#############################
# STEP 6: TVHeadend Configuration (for new and existing installs)
#############################
configure_tvheadend() {
  log "Configuring TVHeadend for hardware transcoding..."
  
  # Wait for TVHeadend service to be ready
  local retries=10
  while [[ $retries -gt 0 ]]; do
    if ss -tuln 2>/dev/null | grep -q ":9981 "; then
      log "TVHeadend service is ready"
      break
    fi
    log "Waiting for TVHeadend service to start..."
    sleep 2
    ((retries--))
  done
  
  if [[ $retries -eq 0 ]]; then
    warn "TVHeadend service not responding on port 9981, skipping configuration"
    return 1
  fi
  
  # Check if user wants to configure TVHeadend
  if [[ $SKIP_TVH_INSTALL -eq 1 ]]; then
    read -rp "Configure existing TVHeadend installation for hardware transcoding? [Y/n]: " CONFIG_CHOICE </dev/tty
    if [[ "${CONFIG_CHOICE,,}" == "n" ]]; then
      log "Skipping TVHeadend configuration"
      return 0
    fi
  fi
  
  # Ensure TVHeadend user has access to video devices
  usermod -a -G video hts 2>/dev/null || true
  usermod -a -G render hts 2>/dev/null || true
  
  # Create basic transcoding configuration directory
  TVH_CONFIG_DIR="/home/hts/.hts/tvheadend"
  mkdir -p "$TVH_CONFIG_DIR/codec"
  mkdir -p "$TVH_CONFIG_DIR/profile"
  chown -R hts:hts "/home/hts/.hts" 2>/dev/null || true
  
  # Basic codec profile for Intel QuickSync (VAAPI)
  cat > "$TVH_CONFIG_DIR/codec/vaapi_h264" <<CODEC
{
  "enabled": 1,
  "name": "Intel QuickSync H.264",
  "comment": "Hardware-accelerated H.264 via Intel QuickSync/VAAPI",
  "codec": "h264_vaapi",
  "hwaccel": 1,
  "hwaccel_device": "/dev/dri/renderD128",
  "experimental": 0
}
CODEC

  # Direct passthrough profile for minimal CPU usage
  cat > "$TVH_CONFIG_DIR/profile/direct_passthrough" <<PROFILE
{
  "enabled": 1,
  "name": "Direct-Passthrough",
  "comment": "Native stream copy for LAN clients - no transcoding",
  "default": 1,
  "shield": 1,
  "timeout": 5,
  "restart": 1,
  "contaccess": 1,
  "catimeout": 2000,
  "swservice": 1,
  "prio": 1,
  "fprio": 1,
  "vcodec": "copy",
  "acodec": "copy"
}
PROFILE

  # HQ 1080p profile for large screens
  cat > "$TVH_CONFIG_DIR/profile/hq_1080p" <<PROFILE
{
  "enabled": 1,
  "name": "HQ-1080p",
  "comment": "Main remote-viewing profile for large screens via Jellyfin",
  "default": 0,
  "shield": 1,
  "timeout": 10,
  "restart": 1,
  "contaccess": 1,
  "catimeout": 5000,
  "swservice": 1,
  "prio": 3,
  "fprio": 3,
  "vcodec": "vaapi_h264",
  "vbitrate": 9000,
  "resolution": "1080p",
  "channels": 2,
  "acodec": "aac",
  "abitrate": 192,
  "language": "und"
}
PROFILE

  # Balanced 720p profile for tablets/mid-bandwidth
  cat > "$TVH_CONFIG_DIR/profile/balanced_720p" <<PROFILE
{
  "enabled": 1,
  "name": "Balanced-720p",
  "comment": "Good default for tablets and mid-bandwidth connections",
  "default": 0,
  "shield": 1,
  "timeout": 10,
  "restart": 1,
  "contaccess": 1,
  "catimeout": 5000,
  "swservice": 1,
  "prio": 4,
  "fprio": 4,
  "vcodec": "vaapi_h264",
  "vbitrate": 5000,
  "resolution": "720p",
  "channels": 2,
  "acodec": "aac",
  "abitrate": 160,
  "language": "und"
}
PROFILE

  # Mobile 480p profile for cellular users
  cat > "$TVH_CONFIG_DIR/profile/mobile_480p" <<PROFILE
{
  "enabled": 1,
  "name": "Mobile-480p",
  "comment": "Low-bandwidth cellular users - news/sports quality",
  "default": 0,
  "shield": 1,
  "timeout": 10,
  "restart": 1,
  "contaccess": 1,
  "catimeout": 5000,
  "swservice": 1,
  "prio": 5,
  "fprio": 5,
  "vcodec": "vaapi_h264",
  "vbitrate": 1750,
  "resolution": "480p",
  "channels": 2,
  "acodec": "aac",
  "abitrate": 128,
  "language": "und"
}
PROFILE
  
  chown -R hts:hts "$TVH_CONFIG_DIR" 2>/dev/null || true
  
  # Restart TVHeadend to pick up configuration
  if systemctl is-active --quiet tvheadend 2>/dev/null; then
    log "Restarting TVHeadend to apply hardware transcoding configuration..."
    systemctl restart tvheadend || warn "Failed to restart TVHeadend service"
  fi
  
  log "TVHeadend cooperative tuner network configuration completed"
  log "Configured profiles (aligned with Jellyfin recommendations):"
  log "  - 'Direct-Passthrough' (DEFAULT): Native copy for LAN/Channels app (zero CPU)"
  log "  - 'HQ-1080p': 1080p@9Mbps+192k AAC, main remote viewing (large screens)"
  log "  - 'Balanced-720p': 720p@5Mbps+160k AAC, tablets/mid-bandwidth"
  log "  - 'Mobile-480p': 480p@1.75Mbps+128k AAC, cellular users"
  log ""
  # Configure HDHomeRun tuners automatically
  if [[ -n "$TUNER_TYPES" ]]; then
    log "Auto-configuring HDHomeRun tuners in TVHeadend..."
    
    # Create all necessary directories first
    mkdir -p "$TVH_CONFIG_DIR/input/satip/adapters"
    mkdir -p "$TVH_CONFIG_DIR/dvb/networks"
    
    IFS=',' read -ra TYPES <<< "$TUNER_TYPES"
    for tuner_type in "${TYPES[@]}"; do
      tuner_type_upper="$(echo "$tuner_type" | tr '[:lower:]' '[:upper:]')"
      tuner_hostname="tuner-${SITE}-${tuner_type_upper}-1.${SITE}.tvinfra.com"
      
      log "  - Auto-configuring ${tuner_type_upper} tuner: $tuner_hostname"
      
      # Create TVHeadend SAT>IP adapter configuration for HDHomeRun
      TUNER_UUID="$(uuidgen)"
      cat > "$TVH_CONFIG_DIR/input/satip/adapters/$TUNER_UUID" <<TUNER_CONFIG
{
  "uuid": "$TUNER_UUID",
  "enabled": true,
  "satip_devicename": "HDHomeRun FLEX 4K ${tuner_type_upper}",
  "satip_bindaddr": "",
  "satip_myaddr": "",
  "satip_host": "$tuner_hostname",
  "satip_port": 5004,
  "satip_rtsp_port": 554,
  "satip_tdelay": 50,
  "fe_master": 0,
  "fe_open": 1,
  "powersave": 0,
  "priority": 10,
  "spriority": 10,
  "diseqc_version": 0,
  "grace_period": 5
}
TUNER_CONFIG
      
      # Create basic network configuration
      NETWORK_UUID="$(uuidgen)"
      NETWORK_TYPE="$(echo "$tuner_type_upper" | sed 's/CATV/DVB-C/' | sed 's/OTA/ATSC-T/')"
      cat > "$TVH_CONFIG_DIR/dvb/networks/$NETWORK_UUID" <<NETWORK_CONFIG
{
  "uuid": "$NETWORK_UUID",
  "networkname": "${SITE_ID} ${tuner_type_upper} Network",
  "enabled": true,
  "skipinitscan": false,
  "autodiscovery": true,
  "max_streams": 32,
  "max_bandwidth": 100000000,
  "priority": 1,
  "spriority": 1,
  "bouquet": "",
  "nit_name": "${tuner_type_upper}",
  "localtime": 1,
  "tsid_zero": 0
}
NETWORK_CONFIG
    done
    
    log "HDHomeRun tuner auto-configuration completed"
  fi
  
  if [[ -n "$TUNER_TYPES" ]]; then
    log "Tuners auto-configured - restart TVHeadend and verify in web UI"
  else
    log "Manual steps still required:"
    log "  1. Add tuners in TVHeadend: tuner-${SITE}-OTA-1.${SITE}.tvinfra.com or tuner-${SITE}-CATV-1.${SITE}.tvinfra.com"
    log "  2. Scan for services in TVHeadend web UI"
  fi
  log "Note: EPG data handled by central Jellyfin (SchedulesDirect) - no local EPG needed"
}

# Configure TVHeadend (both new and existing installations)
if ss -tuln 2>/dev/null | grep -q ":9981 " || systemctl is-active --quiet tvheadend 2>/dev/null; then
  configure_tvheadend
else
  warn "TVHeadend not running, skipping configuration"
fi

#############################
# STEP 7: Cloudflare Tunnel Setup (for secure remote access)
#############################
setup_cloudflare_tunnel() {
  log "Setting up Cloudflare Tunnel for secure remote access..."
  
  # Install cloudflared
  if ! command -v cloudflared >/dev/null 2>&1; then
    log "Installing cloudflared..."
    curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
    echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared noble main' | tee /etc/apt/sources.list.d/cloudflared.list
    apt-get update -y
    apt-get install -y cloudflared
  fi
  
  # Create tunnel configuration directory
  mkdir -p /etc/cloudflared
  
  # Create basic tunnel configuration
  cat > /etc/cloudflared/config.yml <<TUNNEL_CONFIG
tunnel: ${HOSTNAME_SHORT}
credentials-file: /etc/cloudflared/credentials.json

ingress:
  # TVHeadend web interface
  - hostname: ${HOSTNAME_SHORT}.tvinfra.com
    service: http://localhost:9981
    originRequest:
      connectTimeout: 30s
      tlsTimeout: 20s
      httpHostHeader: localhost:9981
  
  # Jellyfin interface (if installed separately)
  - hostname: jellyfin-${HOSTNAME_SHORT}.tvinfra.com
    service: http://localhost:8096
    originRequest:
      connectTimeout: 30s
      tlsTimeout: 20s
      httpHostHeader: localhost:8096
  
  # Catch-all rule
  - service: http_status:404
TUNNEL_CONFIG

  chown root:root /etc/cloudflared/config.yml
  chmod 600 /etc/cloudflared/config.yml
  
  log "Cloudflare Tunnel configuration created"
  log "Manual steps required:"
  log "  1. Run: cloudflared tunnel create ${HOSTNAME_SHORT}"
  log "  2. Copy credentials to: /etc/cloudflared/credentials.json"
  log "  3. Add DNS records: ${HOSTNAME_SHORT}.tvinfra.com"
  log "  4. Enable service: systemctl enable --now cloudflared"
  log "  5. Secure URLs: https://${HOSTNAME_SHORT}.tvinfra.com"
}

# Note: Cloudflare Tunnel setup available but not auto-configured
log "Note: Cloudflare Tunnel configuration available in setup_cloudflare_tunnel() function if needed"

#############################
# STEP 8: Landscape registration (best-effort)
#############################
register_landscape() {
  log "Installing Landscape client..."
  apt-get install -y landscape-client || { warn "Failed to install landscape-client."; return 1; }
  
  if ! command -v landscape-config >/dev/null 2>&1; then
    warn "landscape-config not found; skipping Landscape setup."
    return 1
  fi
  
  # Check if already registered
  if [[ -f /etc/landscape/client.conf ]]; then
    if grep -q "^computer_title.*$HOSTNAME_FQDN" /etc/landscape/client.conf 2>/dev/null; then
      log "Landscape already registered with correct hostname: $HOSTNAME_FQDN"
      if systemctl is-active --quiet landscape-client 2>/dev/null; then
        log "Landscape client service is running - skipping re-registration"
        return 0
      else
        log "Landscape client service stopped - restarting"
        systemctl restart landscape-client || true
        return 0
      fi
    else
      log "Landscape registration exists but with different hostname"
      EXISTING_TITLE="$(grep "^computer_title" /etc/landscape/client.conf 2>/dev/null | cut -d= -f2 | tr -d ' ' || echo "unknown")"
      log "Current registration: $EXISTING_TITLE"
      read -rp "Re-register Landscape with new hostname $HOSTNAME_FQDN? [Y/n]: " REREG_CHOICE </dev/tty
      if [[ "${REREG_CHOICE,,}" == "n" ]]; then
        log "Keeping existing Landscape registration"
        return 0
      fi
    fi
  fi
  
  log "Registering with Landscape..."
  /usr/bin/landscape-config \
    --silent \
    --computer-title "$HOSTNAME_FQDN" \
    --account-name standalone \
    --url "${LANDSCAPE_URL%/}/message-system" \
    --ping-url "${LANDSCAPE_URL%/}/ping" || { warn "Landscape configuration failed."; return 1; }
  
  systemctl enable landscape-client || true
  systemctl restart landscape-client || true
  
  # Verify registration and ensure persistence
  sleep 5
  if systemctl is-active --quiet landscape-client 2>/dev/null; then
    log "Landscape registration successful"
    
    # Add cron job to ensure Landscape stays connected
    CRON_JOB="*/10 * * * * root systemctl is-active --quiet landscape-client || systemctl restart landscape-client"
    if ! grep -q "landscape-client" /etc/crontab 2>/dev/null; then
      echo "# Landscape persistence check - added by edge-tvh provisioning" >> /etc/crontab
      echo "$CRON_JOB" >> /etc/crontab
      log "Added Landscape persistence cron job (restart every 10 minutes if stopped)"
    fi
    
    # Configure automatic restart on failure
    mkdir -p /etc/systemd/system/landscape-client.service.d
    cat > /etc/systemd/system/landscape-client.service.d/restart.conf <<EOF
[Service]
Restart=always
RestartSec=30
StartLimitInterval=0
EOF
    systemctl daemon-reload
    log "Configured Landscape service to auto-restart on failure"
  else
    warn "Landscape client service failed to start"
  fi
}

# Register with Landscape (with duplicate prevention)
register_landscape

#############################
# STEP 8: Banner
#############################
BANNER="/etc/motd.d/99-edge-tvh"
mkdir -p /etc/motd.d
cat > "$BANNER" <<TXT
********************************************************************
EDGE TV TRANSCODER  |  ${HOSTNAME_FQDN}
Site: ${SITE}-${NODE} (ID ${SITE_ID})   VLAN: ${VLAN_ID}   IP: $([[ $DHCP_ONLY -eq 1 ]] && echo "DHCP" || echo "${IP_ADDR}")

TVHeadend: http://$(hostname -I | awk '{print $1}'):9981
Jellyfin:  https://jellyfin.fmt.prodinfra.com

Expected Tuners: $(if [[ -n "$TUNER_TYPES" ]]; then 
  IFS=',' read -ra TYPES <<< "$TUNER_TYPES"
  for tuner_type in "${TYPES[@]}"; do
    tuner_type_upper="$(echo "$tuner_type" | tr '[:lower:]' '[:upper:]')"
    echo -n "tuner-${SITE}-${tuner_type_upper}-1.${SITE}.tvinfra.com "
  done
else
  echo "tuner-${SITE}-OTA-1.${SITE}.tvinfra.com"
fi)

RECOVERY: No network at boot → power cycle after connecting network
********************************************************************
TXT

#############################
# STEP 9: Final summary
#############################
# Determine TVHeadend status for summary
get_tvh_status() {
  if systemctl is-active --quiet tvheadend 2>/dev/null; then
    echo "running (systemd/apt)"
  elif snap list tvheadend >/dev/null 2>&1 && snap services tvheadend 2>/dev/null | grep -q "active"; then
    local snap_info="$(snap list tvheadend 2>/dev/null | awk 'NR==2{print $2" ("$3")"}')"
    echo "running (snap $snap_info)"
  elif ss -tuln 2>/dev/null | grep -q ":9981 "; then
    echo "running (port 9981 active, unknown method)"
  else
    echo "not detected"
  fi
}

TVH_STATUS="$(get_tvh_status)"

cat <<EOF

====================================================================
✅ Provisioning complete (Tang was bound successfully).
--------------------------------------------------------------------
Host          : ${HOSTNAME_FQDN}
Interface     : ${IFACE}
IP mode       : $([[ $DHCP_ONLY -eq 1 ]] && echo "DHCP" || echo "STATIC ${IP_ADDR}/24  gw=${GATEWAY}  dns=${DNS1},${DNS2}")
Tang URL      : ${TANG_URL}
Tuner types   : ${TUNER_TYPES:-<not specified>}
Landscape     : ${LANDSCAPE_URL} (best-effort registration attempted)
TVHeadend     : ${TVH_STATUS}
TVH admin     : ${TVH_ADMIN_USER} / ${TVH_ADMIN_PASS}
--------------------------------------------------------------------
Next steps:
  1) (Recommended) reboot now to verify Tang unlock flow:
       sudo reboot
  2) TVHeadend UI:
       $(if [[ "$TVH_ADMIN_PASS" == "<use existing credentials>" ]]; then
         echo "       URL: http://$(hostname -I | awk '{print $1}'):9981  (use existing login)"
       else
         echo "       APT  : http://$(hostname -I | awk '{print $1}'):9981  (user/pass above)"
         echo "       SNAP : http://$(hostname -I | awk '{print $1}'):9981  (snap defaults; set in UI)"
       fi)
  3) HDHomeRun FLEX 4K Setup:
     - Connect FLEX 4K to same network segment as Mac Mini
     - Configure tuner hostname: tuner-${SITE}-OTA-1.${SITE}.tvinfra.com$(if [[ "$TUNER_TYPES" == *"catv"* ]]; then echo " and tuner-${SITE}-CATV-1.${SITE}.tvinfra.com"; fi)
     - Configure antenna input and scan for channels
  4) Configure central Jellyfin to add this edge transcoder:
     - Access: https://jellyfin.fmt.prodinfra.com
     - Jellyfin Admin > Live TV > TV Sources > Add TVHeadend
     - URL: http://${HOSTNAME_FQDN}:9981 (via VPN)
     - This provides cooperative OTA content to central media server
  5) Remind field techs: If the box powers on without network, unplug AC,
     plug network, then re-apply AC to boot.
====================================================================
EOF