#!/usr/bin/env bash
# init.sh —— Pangolin 节点一键初始化(幂等)。在全新 Debian 12 上以 root 运行一次,
# 重复运行安全。涵盖四块:① 系统加固 ② Pangolin 运行环境 ③ Telegram 监控 ④ 网络安全。
#
#   用法(机器上):
#     cp bootstrap.env.example bootstrap.env && vi bootstrap.env   # 按需改,填 Telegram
#     sudo bash init.sh
#
#   远程一条命令(从本机,已配 ssh 免密别名 racknerd):
#     rsync -az deploy/bootstrap/ racknerd:/root/bootstrap/ && \
#       ssh racknerd 'cd /root/bootstrap && bash init.sh'
#
# 设计红线:禁用密码登录前会先确认 root 已有 authorized_keys,否则跳过并告警(防锁死)。
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# ── 日志 ──────────────────────────────────────────────────────────────────────
log()  { printf '\033[1;32m[init]\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[init]\033[0m %s\n' "$*" >&2; }
die()  { printf '\033[1;31m[init]\033[0m %s\n' "$*" >&2; exit 1; }

[ "$(id -u)" -eq 0 ] || die "请以 root 运行:sudo bash $0"
[ -r /etc/debian_version ] || warn "非 Debian 系发行版,部分步骤可能需调整"

# ── 配置 ──────────────────────────────────────────────────────────────────────
# 默认值;bootstrap.env 覆盖。
SSH_PORT=22
DISABLE_PASSWORD_AUTH=true
TIMEZONE=Asia/Shanghai
SWAP_SIZE_MB=2048
FW_ALLOW_TCP="443"
FW_ALLOW_UDP="443"
TELEGRAM_BOT_TOKEN=""
TELEGRAM_CHAT_ID=""
NODE_NAME="$(hostname)"
MONITOR_INTERVAL_MIN=1
MONITOR_BREACH_RUNS=5
CPU_ALERT_PCT=80
MEM_ALERT_PCT=80
DISK_ALERT_PCT=80
MONITOR_UNITS="sing-box pangolin-agent"
MONITOR_NET_PROBE="https://www.cloudflare.com/cdn-cgi/trace"

if [ -f "$SCRIPT_DIR/bootstrap.env" ]; then
  log "读取配置 $SCRIPT_DIR/bootstrap.env"
  # shellcheck disable=SC1091
  . "$SCRIPT_DIR/bootstrap.env"
else
  warn "未找到 bootstrap.env,全部使用默认值(监控将因缺 Telegram 配置而跳过)"
fi

export DEBIAN_FRONTEND=noninteractive

# ── ① 系统:更新 + 基础包 + 时区 ──────────────────────────────────────────────
step_base() {
  log "[1/8] apt 更新 + 基础工具"
  apt-get update -q
  apt-get -y -o Dpkg::Options::=--force-confold upgrade
  apt-get install -y --no-install-recommends \
    ufw fail2ban chrony curl jq ca-certificates gnupg lsb-release \
    iproute2 procps python3-systemd
  # python3-systemd:精简 Debian 无 rsyslog,fail2ban 走 systemd 后端读 journald 必需。
  log "    时区 → $TIMEZONE"
  timedatectl set-timezone "$TIMEZONE" 2>/dev/null || warn "设置时区失败(无 systemd-timedated?)"
}

# ── ① swap(512MB 机器必需)────────────────────────────────────────────────────
step_swap() {
  log "[2/8] swap"
  if [ "${SWAP_SIZE_MB:-0}" -le 0 ]; then log "    SWAP_SIZE_MB=0,跳过"; return; fi
  local cur; cur="$(free -m | awk '/^Swap:/{print $2}')"
  if [ "${cur:-0}" -ge "$SWAP_SIZE_MB" ]; then
    log "    当前 swap ${cur}MB ≥ 目标 ${SWAP_SIZE_MB}MB,跳过"; return
  fi
  if [ -f /swapfile ]; then log "    /swapfile 已存在,跳过新建"; return; fi
  local add=$(( SWAP_SIZE_MB - cur ))
  log "    创建 /swapfile ${add}MB(现有 ${cur}MB → 目标 ${SWAP_SIZE_MB}MB)"
  fallocate -l "${add}M" /swapfile 2>/dev/null || dd if=/dev/zero of=/swapfile bs=1M count="$add" status=none
  chmod 600 /swapfile
  mkswap /swapfile >/dev/null
  swapon /swapfile
  grep -q '^/swapfile ' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab
}

# ── ① sysctl 网络调优(BBR 等)────────────────────────────────────────────────
step_sysctl() {
  log "[3/8] sysctl 调优(BBR/fq/文件句柄/连接队列)"
  cat > /etc/sysctl.d/99-pangolin.conf <<'SYSCTL'
# Pangolin 节点网络调优
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
net.core.somaxconn = 4096
net.core.netdev_max_backlog = 16384
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_max_syn_backlog = 8192
fs.file-max = 1048576
SYSCTL
  sysctl -q --system || warn "sysctl 应用有告警(BBR 模块可能需较新内核;Debian 12 内核已内置)"
  local cc; cc="$(sysctl -n net.ipv4.tcp_congestion_control 2>/dev/null || echo '?')"
  log "    tcp_congestion_control = $cc"
}

# ── ② Pangolin 专用非特权用户 ─────────────────────────────────────────────────
step_user() {
  log "[4/8] 创建 pangolin 系统用户(非特权)"
  if id -u pangolin >/dev/null 2>&1; then
    log "    用户 pangolin 已存在,跳过"
  else
    useradd --system --create-home --home-dir /var/lib/pangolin \
      --shell /usr/sbin/nologin pangolin
    log "    已创建 pangolin(home=/var/lib/pangolin, nologin)"
  fi
  install -d -o pangolin -g pangolin -m 750 /var/lib/pangolin
}

# ── ④ 防火墙(ufw)────────────────────────────────────────────────────────────
step_firewall() {
  log "[5/8] ufw 防火墙"
  ufw default deny incoming  >/dev/null
  ufw default allow outgoing >/dev/null
  ufw allow "${SSH_PORT}/tcp" >/dev/null
  for p in $FW_ALLOW_TCP; do [ -n "$p" ] && ufw allow "${p}/tcp" >/dev/null; done
  for p in $FW_ALLOW_UDP; do [ -n "$p" ] && ufw allow "${p}/udp" >/dev/null; done
  ufw --force enable >/dev/null
  log "    放行:SSH ${SSH_PORT}/tcp · TCP[$FW_ALLOW_TCP] · UDP[$FW_ALLOW_UDP],其余 deny"
}

# ── ④ fail2ban(SSH 暴破防护)─────────────────────────────────────────────────
step_fail2ban() {
  log "[6/8] fail2ban(sshd jail)"
  # 精简 Debian 无 rsyslog/auth.log,SSH 日志只在 journald → 用 systemd 后端读取。
  cat > /etc/fail2ban/jail.d/pangolin.local <<F2B
[DEFAULT]
allowipv6 = auto

[sshd]
enabled  = true
backend  = systemd
port     = ${SSH_PORT}
maxretry = 5
findtime = 10m
bantime  = 1h
F2B
  systemctl enable fail2ban >/dev/null 2>&1 || true
  systemctl restart fail2ban || warn "fail2ban 重启失败"
}

# ── ① SSH 加固(先验证 authorized_keys,防锁死)────────────────────────────────
step_ssh() {
  log "[7/8] SSH 加固"
  local dropin=/etc/ssh/sshd_config.d/99-pangolin.conf
  local akf=/root/.ssh/authorized_keys
  {
    echo "# Pangolin 加固(由 init.sh 生成)"
    echo "Port ${SSH_PORT}"
    echo "PubkeyAuthentication yes"
    echo "PermitRootLogin prohibit-password"
    echo "ChallengeResponseAuthentication no"
    echo "X11Forwarding no"
    echo "MaxAuthTries 4"
    if [ "${DISABLE_PASSWORD_AUTH}" = "true" ]; then
      if [ -s "$akf" ]; then
        echo "PasswordAuthentication no"
      else
        warn "    root 无 authorized_keys → 保留密码登录以防锁死(请先 ssh-copy-id 再重跑)"
        echo "PasswordAuthentication yes"
      fi
    else
      echo "PasswordAuthentication yes"
    fi
  } > "$dropin"

  if sshd -t; then
    systemctl reload ssh 2>/dev/null || systemctl reload sshd 2>/dev/null || warn "sshd reload 失败"
    log "    已应用 $dropin(Port ${SSH_PORT}, root 仅密钥)"
  else
    warn "    sshd 配置校验失败,已写入但未 reload,请人工检查 $dropin"
  fi
}

# ── ③ 监控(Telegram 上报 + 阈值告警)─────────────────────────────────────────
step_monitor() {
  log "[8/8] 监控(Telegram)"
  if [ -z "${TELEGRAM_BOT_TOKEN}" ] || [ -z "${TELEGRAM_CHAT_ID}" ]; then
    warn "    未配置 TELEGRAM_BOT_TOKEN/CHAT_ID,跳过监控安装"
    return
  fi
  install -m 755 "$SCRIPT_DIR/monitor/pangolin-monitor.sh" /usr/local/bin/pangolin-monitor
  umask 077
  # 含空格的值必须加引号:此文件既被 systemd EnvironmentFile 读,也被 monitor 脚本
  # `source`(shell),后者遇未引号的空格会把 "sing-box pangolin-agent" 拆成跑命令。
  cat > /etc/pangolin-monitor.env <<MENV
TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID}
NODE_NAME="${NODE_NAME}"
CPU_ALERT_PCT=${CPU_ALERT_PCT}
MEM_ALERT_PCT=${MEM_ALERT_PCT}
DISK_ALERT_PCT=${DISK_ALERT_PCT}
MONITOR_UNITS="${MONITOR_UNITS}"
MONITOR_NET_PROBE="${MONITOR_NET_PROBE}"
MONITOR_BREACH_RUNS=${MONITOR_BREACH_RUNS}
MENV
  umask 022
  sed -e "s/__INTERVAL__/${MONITOR_INTERVAL_MIN}/g" \
    "$SCRIPT_DIR/monitor/pangolin-monitor.timer" > /etc/systemd/system/pangolin-monitor.timer
  install -m 644 "$SCRIPT_DIR/monitor/pangolin-monitor.service" /etc/systemd/system/pangolin-monitor.service
  systemctl daemon-reload
  systemctl enable --now pangolin-monitor.timer >/dev/null
  log "    监控已装:每 ${MONITOR_INTERVAL_MIN} 分钟采集,连续 ${MONITOR_BREACH_RUNS} 次异常才告警;发送一条启动通知"
  /usr/local/bin/pangolin-monitor --boot || true
}

main() {
  step_base
  step_swap
  step_sysctl
  step_user
  step_firewall
  step_fail2ban
  step_ssh
  step_monitor
  log "✅ 初始化完成。摘要:"
  echo "    OS    : $(. /etc/os-release; echo "$PRETTY_NAME")"
  echo "    swap  : $(free -m | awk '/^Swap:/{print $2"MB"}')"
  echo "    bbr   : $(sysctl -n net.ipv4.tcp_congestion_control 2>/dev/null)"
  echo "    ufw   : $(ufw status | head -1)"
  echo "    user  : $(id pangolin 2>/dev/null || echo '未创建')"
  echo "    监控  : $(systemctl is-active pangolin-monitor.timer 2>/dev/null || echo '未装')"
}

main "$@"
