#!/usr/bin/env bash
# linux-general-network-tune.sh
# General Ubuntu/Debian network tuning for high TCP connection count and high-throughput servers.
#
# Run:
#   sudo bash -c 'bash <(curl -Ls https://public.moaidownloader.info/files/linux-general-network-tune.sh) --apply-now'
#
# Or without applying live immediately:
#   sudo bash -c 'bash <(curl -Ls https://public.moaidownloader.info/files/linux-general-network-tune.sh)'
#
# Optional:
#   sudo bash -c 'bash <(curl -Ls https://public.moaidownloader.info/files/linux-general-network-tune.sh) --apply-now --make-swap=2'
#
# What it does:
# - Detects CPU cores, RAM, and primary network interface
# - Enables BBR + fq
# - Tunes backlog, conntrack, TCP lifecycle, keepalive, file descriptors
# - Enables RPS/RFS based on CPU core count
# - Writes managed config files, so repeated runs are safe/idempotent
#
# What it does NOT do:
# - Touch Docker
# - Restart services
# - Find or modify Xray
# - Change application configs

set -Eeuo pipefail

SCRIPT_URL="https://public.moaidownloader.info/files/linux-general-network-tune.sh"

APPLY_NOW=0
MAKE_SWAP=0
SWAP_GB=""

for arg in "$@"; do
  case "$arg" in
    --apply-now)
      APPLY_NOW=1
      ;;
    --make-swap=*)
      MAKE_SWAP=1
      SWAP_GB="${arg#*=}"
      ;;
    -h|--help)
      sed -n '1,60p' "$0"
      exit 0
      ;;
    *)
      echo "Unknown argument: $arg" >&2
      echo "Use --help for usage." >&2
      exit 1
      ;;
  esac
done

need_root() {
  if [ "${EUID:-$(id -u)}" -ne 0 ]; then
    echo
    echo "This script must be run as root."
    echo
    echo "Run it like this:"
    echo "  sudo bash -c 'bash <(curl -Ls ${SCRIPT_URL}) --apply-now'"
    echo
    echo "Alternative command:"
    echo "  curl -Ls ${SCRIPT_URL} | sudo bash -s -- --apply-now"
    echo
    exit 1
  fi
}

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

pow2_floor() {
  local n="$1" p=1
  while [ $((p * 2)) -le "$n" ]; do
    p=$((p * 2))
  done
  echo "$p"
}

clamp() {
  local v="$1" lo="$2" hi="$3"
  if [ "$v" -lt "$lo" ]; then
    echo "$lo"
  elif [ "$v" -gt "$hi" ]; then
    echo "$hi"
  else
    echo "$v"
  fi
}

detect_primary_iface() {
  local iface
  iface="$(ip route show default 2>/dev/null | awk '/default/ {print $5; exit}')"

  if [ -z "$iface" ]; then
    iface="$(ip -br link 2>/dev/null | awk '$1 != "lo" && $2 == "UP" {print $1; exit}')"
  fi

  echo "$iface"
}

all_cpu_mask() {
  local cores="$1"
  local full_hex="" groups rem group_val

  groups=$((cores / 32))
  rem=$((cores % 32))

  if [ "$rem" -gt 0 ]; then
    group_val=$(( (1 << rem) - 1 ))
    full_hex="$(printf "%x" "$group_val")"
  fi

  while [ "$groups" -gt 0 ]; do
    if [ -n "$full_hex" ]; then
      full_hex="${full_hex},ffffffff"
    else
      full_hex="ffffffff"
    fi
    groups=$((groups - 1))
  done

  if [ -z "$full_hex" ]; then
    full_hex="1"
  fi

  echo "$full_hex"
}

enable_bbr_module() {
  mkdir -p /etc/modules-load.d
  echo "tcp_bbr" > /etc/modules-load.d/bbr.conf
  modprobe tcp_bbr 2>/dev/null || true
}

write_sysctl() {
  local cores="$1" mem_mb="$2"
  local conntrack_max file_max nr_open somax syn_backlog netdev_backlog
  local rps_entries tcp_rmem_max tcp_wmem_max
  local pages tcp_mem_low tcp_mem_pressure tcp_mem_high

  # Conntrack scaling:
  # 4GB RAM => 262144
  # 8GB RAM => 524288
  # 16GB+   => 1048576+
  conntrack_max=$(( mem_mb * 64 ))
  conntrack_max="$(clamp "$conntrack_max" 262144 2097152)"
  conntrack_max="$(pow2_floor "$conntrack_max")"

  # Global file descriptor limits.
  file_max=$(( mem_mb * 512 ))
  file_max="$(clamp "$file_max" 1048576 8388608)"
  nr_open="$file_max"

  # Listen/backlog scaling.
  somax=$(( cores * 32768 ))
  somax="$(clamp "$somax" 65535 262144)"
  syn_backlog="$somax"

  netdev_backlog=$(( cores * 32768 ))
  netdev_backlog="$(clamp "$netdev_backlog" 65535 250000)"

  # RPS flow table.
  rps_entries=$(( cores * 32768 ))
  rps_entries="$(clamp "$rps_entries" 32768 262144)"

  # TCP buffers. Conservative values for small VPS nodes.
  tcp_rmem_max=$(( mem_mb * 1024 ))
  tcp_wmem_max=$(( mem_mb * 1024 ))
  tcp_rmem_max="$(clamp "$tcp_rmem_max" 16777216 134217728)"
  tcp_wmem_max="$(clamp "$tcp_wmem_max" 16777216 134217728)"

  # tcp_mem is in memory pages, not bytes.
  pages=$(( mem_mb * 256 ))
  tcp_mem_low=$(( pages / 16 ))
  tcp_mem_pressure=$(( pages / 8 ))
  tcp_mem_high=$(( pages / 4 ))

  cat > /etc/sysctl.d/99-general-network-tuning.conf <<EOF
# Managed by linux-general-network-tune.sh
# Generated for CPU cores=${cores}, RAM=${mem_mb}MB

# BBR congestion control
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr

# TCP Fast Open: client+server side
net.ipv4.tcp_fastopen=3

# High connection backlog
net.core.somaxconn=${somax}
net.ipv4.tcp_max_syn_backlog=${syn_backlog}
net.core.netdev_max_backlog=${netdev_backlog}

# Wider ephemeral port range
net.ipv4.ip_local_port_range=1024 65535

# TCP lifecycle and keepalive
net.ipv4.tcp_fin_timeout=15
net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_keepalive_time=300
net.ipv4.tcp_keepalive_intvl=30
net.ipv4.tcp_keepalive_probes=5

# TCP memory and socket buffers
net.ipv4.tcp_rmem=4096 87380 ${tcp_rmem_max}
net.ipv4.tcp_wmem=4096 65536 ${tcp_wmem_max}
net.ipv4.tcp_mem=${tcp_mem_low} ${tcp_mem_pressure} ${tcp_mem_high}

# Conntrack table
net.netfilter.nf_conntrack_max=${conntrack_max}

# Global file descriptor limits
fs.file-max=${file_max}
fs.nr_open=${nr_open}

# RPS/RFS global entries
net.core.rps_sock_flow_entries=${rps_entries}
EOF

  echo "Wrote /etc/sysctl.d/99-general-network-tuning.conf"
}

write_limits() {
  cat > /etc/security/limits.d/99-general-network-tuning.conf <<'EOF'
# Managed by linux-general-network-tune.sh
* soft nofile 1048576
* hard nofile 1048576
root soft nofile 1048576
root hard nofile 1048576
EOF

  echo "Wrote /etc/security/limits.d/99-general-network-tuning.conf"
}

write_rps_service() {
  local iface="$1" cores="$2" mask="$3"
  local flow_entries rx_flow_cnt

  flow_entries=$(( cores * 32768 ))
  flow_entries="$(clamp "$flow_entries" 32768 262144)"

  rx_flow_cnt=$(( flow_entries / cores ))
  rx_flow_cnt="$(pow2_floor "$rx_flow_cnt")"

  cat > /usr/local/sbin/apply-general-rps.sh <<EOF
#!/usr/bin/env bash
set -e

IFACE="${iface}"
MASK="${mask}"
RX_FLOW_CNT="${rx_flow_cnt}"

if [ ! -d "/sys/class/net/\$IFACE" ]; then
  exit 0
fi

for q in /sys/class/net/\$IFACE/queues/rx-*/rps_cpus; do
  [ -e "\$q" ] && echo "\$MASK" > "\$q" || true
done

for q in /sys/class/net/\$IFACE/queues/rx-*/rps_flow_cnt; do
  [ -e "\$q" ] && echo "\$RX_FLOW_CNT" > "\$q" || true
done
EOF

  chmod +x /usr/local/sbin/apply-general-rps.sh

  cat > /etc/systemd/system/general-rps.service <<'EOF'
[Unit]
Description=Apply general RPS/RFS network tuning
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/apply-general-rps.sh

[Install]
WantedBy=multi-user.target
EOF

  systemctl daemon-reload
  systemctl enable general-rps.service >/dev/null 2>&1 || true

  echo "Wrote and enabled general-rps.service for iface=${iface}, mask=${mask}"
}

make_swap_if_requested() {
  if [ "$MAKE_SWAP" -ne 1 ]; then
    return 0
  fi

  if swapon --show | grep -q '^'; then
    echo "Swap already exists; skipping swap creation."
    return 0
  fi

  if ! [[ "$SWAP_GB" =~ ^[0-9]+$ ]]; then
    echo "Invalid --make-swap value. Example: --make-swap=2" >&2
    return 1
  fi

  fallocate -l "${SWAP_GB}G" /swapfile || dd if=/dev/zero of=/swapfile bs=1M count="$((SWAP_GB * 1024))"
  chmod 600 /swapfile
  mkswap /swapfile
  swapon /swapfile
  grep -q '^/swapfile ' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab

  echo "Created ${SWAP_GB}G swapfile."
}

print_report() {
  local iface="$1"

  echo
  echo "=== Live report ==="
  echo "CPU cores: $(nproc)"
  echo "Primary interface: ${iface:-unknown}"

  echo
  echo "Memory:"
  free -h || true

  echo
  echo "Sysctl:"
  sysctl \
    net.ipv4.tcp_congestion_control \
    net.core.default_qdisc \
    net.netfilter.nf_conntrack_count \
    net.netfilter.nf_conntrack_max \
    net.core.somaxconn \
    net.ipv4.tcp_max_syn_backlog \
    net.core.netdev_max_backlog \
    fs.file-max \
    fs.nr_open 2>/dev/null || true

  echo
  echo "Socket summary:"
  cat /proc/net/sockstat 2>/dev/null || true
  cat /proc/net/sockstat6 2>/dev/null || true

  echo
  echo "TCP states:"
  ss -tan 2>/dev/null | awk 'NR>1 {state[$1]++} END {for (s in state) print s, state[s]}' || true

  echo
  echo "Softirq NET:"
  grep -E 'NET_RX|NET_TX' /proc/softirqs 2>/dev/null || true

  if [ -n "$iface" ] && [ -d "/sys/class/net/$iface" ]; then
    echo
    echo "RPS for ${iface}:"
    cat /sys/class/net/"$iface"/queues/rx-*/rps_cpus 2>/dev/null || true

    echo
    echo "RPS flow count for ${iface}:"
    cat /sys/class/net/"$iface"/queues/rx-*/rps_flow_cnt 2>/dev/null || true
  fi
}

main() {
  need_root

  local cores mem_kb mem_mb iface mask

  if ! has_cmd ip; then
    echo "Missing required command: ip" >&2
    echo "Install it with: apt update && apt install -y iproute2" >&2
    exit 1
  fi

  cores="$(nproc)"
  mem_kb="$(awk '/MemTotal:/ {print $2}' /proc/meminfo)"
  mem_mb=$(( mem_kb / 1024 ))
  iface="$(detect_primary_iface)"
  mask="$(all_cpu_mask "$cores")"

  echo "Detected: CPU cores=${cores}, RAM=${mem_mb}MB, primary_iface=${iface:-unknown}, cpu_mask=${mask}"

  enable_bbr_module
  write_sysctl "$cores" "$mem_mb"
  write_limits

  if [ -n "$iface" ]; then
    write_rps_service "$iface" "$cores" "$mask"
  else
    echo "Could not detect primary interface; skipping RPS service."
  fi

  make_swap_if_requested

  systemctl daemon-reload

  if [ "$APPLY_NOW" -eq 1 ]; then
    sysctl --system

    if [ -n "$iface" ]; then
      /usr/local/sbin/apply-general-rps.sh || true
    fi
  else
    echo
    echo "Not applying live because --apply-now was not provided."
    echo "Run:"
    echo "  sudo bash -c 'bash <(curl -Ls ${SCRIPT_URL}) --apply-now'"
  fi

  print_report "$iface"

  echo
  echo "Done."
  echo
  echo "Recommended checks:"
  echo "  sysctl net.ipv4.tcp_congestion_control net.core.default_qdisc"
  echo "  sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max"
  echo "  cat /sys/class/net/${iface:-eth0}/queues/rx-*/rps_cpus 2>/dev/null"
  echo
  echo "Re-run after CPU/RAM upgrades:"
  echo "  sudo bash -c 'bash <(curl -Ls ${SCRIPT_URL}) --apply-now'"
}

main "$@"
