a879494fe1
避免瞬时尖峰/部署窗口误报(此前 sing-box 部署期 activating 触发了一次误报)。 - 采集间隔 5min → 1min(MONITOR_INTERVAL_MIN=1) - 新增 MONITOR_BREACH_RUNS=5:连续 5 次采样都异常才告警一次(状态存 /var/lib/pangolin-monitor/breach_count);恢复后发一条恢复通知并清零 - 去掉 MONITOR_SEND_OK_PULSE(被去抖逻辑取代) - 已在 racknerd 验证:连 4 次异常不报、恢复清零 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
233 lines
9.8 KiB
Bash
Executable File
233 lines
9.8 KiB
Bash
Executable File
#!/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 "$@"
|