Files
pangolin/deploy/bootstrap/init.sh
T
wangjia 1d76c00f66
ci-pangolin / Lint — shellcheck (push) Has been cancelled
ci-pangolin / OpenAPI Sync Check (push) Has been cancelled
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Has been cancelled
ci-pangolin / Flutter — analyze + test (push) Has been cancelled
chore(bootstrap): 监控阈值降至 80% + 关闭正常心跳(仅异常告警)
CPU/MEM/DISK 阈值 90/90/85 → 统一 80;MONITOR_SEND_OK_PULSE true → false
(默认只在异常时推送,正常不打扰)。同步 init.sh 默认值与 bootstrap.env.example。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 07:17:22 +08:00

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=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"
MONITOR_SEND_OK_PULSE=false
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_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 "$@"