feat(deploy/bootstrap): 节点幂等初始化脚本(加固+pangolin用户+监控+防火墙)
全新 Debian 12 一键初始化,后续新机复用。四块: - 系统:apt 升级+基础包(含 python3-systemd)、时区、swap 补足、sysctl(BBR/fq/句柄/队列) - Pangolin:建 pangolin 非特权系统用户(/var/lib/pangolin, nologin) - 网络安全:ufw 默认拒入站、放行 SSH+443(tcp/udp);fail2ban sshd jail (精简 Debian 无 rsyslog/auth.log → backend=systemd 读 journald) - SSH 加固:root 仅密钥、可选禁密码(仅当已有 authorized_keys 时生效,防锁死) - 监控:pangolin-monitor(systemd timer,CPU/内存/磁盘/进程/出网 → Telegram, 异常+心跳);deadman-watch(EC2 侧主动探活,补"宕机/不上报"告警) 已在 racknerd(107.172.55.251)实跑验证全部步骤通过。密钥/真实配置 gitignore。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Executable
+230
@@ -0,0 +1,230 @@
|
||||
#!/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=5
|
||||
CPU_ALERT_PCT=90
|
||||
MEM_ALERT_PCT=90
|
||||
DISK_ALERT_PCT=85
|
||||
MONITOR_UNITS="sing-box pangolin-agent"
|
||||
MONITOR_NET_PROBE="https://www.cloudflare.com/cdn-cgi/trace"
|
||||
MONITOR_SEND_OK_PULSE=true
|
||||
|
||||
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
|
||||
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_SEND_OK_PULSE=${MONITOR_SEND_OK_PULSE}
|
||||
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} 分钟上报;发送一条启动通知"
|
||||
/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 "$@"
|
||||
Reference in New Issue
Block a user