#!/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 </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 < /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 "$@"