自動化腳本:上架首小時的冪等初始化
新服務器上架第一小時的標準動作寫成冪等腳本:跑十遍與跑一遍結果相同。本文給出完整可復制的 first-hour-init 腳本骨架(SSH 加固、防火牆、更新策略、基線採集),以及冪等設計的三條原則。
手工初始化服務器的問題不在慢,在不可復現:十臺機器十個樣子,出事時沒人說得清哪臺改過什麼。解法是把上架第一小時的標準動作寫成冪等腳本——跑十遍與跑一遍結果相同,新機器一條命令到位。本篇給出可直接抄走的腳本骨架與設計原則。本文屬於安全基線中心系列的工具分支;每個動作的原理與清單在SSH 加固清單與防火牆基線。
冪等的三條設計原則
- 檢查後動作,不是盲目動作:改配置前先 grep 目標狀態(已是目標值就跳過)——
grep -q '^PasswordAuthentication no' sshd_config || sed -i ...的模式貫穿全腳本; - 追加用標記塊,不整文件覆蓋:自定義配置寫進帶標記的塊(# BEGIN ax-init / # END ax-init),重跑時先刪舊塊再寫新塊——不動發行版原生行;
- 失敗要響,成功要靜:
set -euo pipefail起手,每步失敗立即退出並留日志;成功路徑只輸出摘要——腳本的輸出是審計記錄,不是噪音。
腳本骨架:first-hour-init.sh(可直接抄)
#!/usr/bin/env bash
# first-hour-init.sh —— 上架首小時冪等初始化(Debian/Ubuntu 口徑,其他發行版改包管理器)
set -euo pipefail
LOG=/var/log/first-hour-init.log; exec >>("$LOG") 2>&1; echo "=== $(date -u +%FT%TZ) 開始 ==="
# ── 0. 前置:必須以 root 或 sudo 運行;先確認 SSH 密鑰已可用再禁密碼 ──
[ "$(id -u)" = 0 ] || { echo "需要 root"; exit 1; }
[ -s /root/.ssh/authorized_keys ] || { echo "❌ 先配置密鑰再運行(禁用密碼前必須驗證密鑰可登入)"; exit 1; }
# ── 1. SSH 加固(冪等:sshd_config.d 獨立文件,存在即覆蓋)──
cat > /etc/ssh/sshd_config.d/99-hardening.conf <<'EOF'
PermitRootLogin prohibit-password
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
LoginGraceTime 20
EOF
systemd-delta 2>/dev/null | head -1 || true
systemctl reload ssh 2>/dev/null || systemctl reload sshd
# ── 2. 防火牆基線:默認拒絕入站 + 最小白名單(冪等:先重置再聲明)──
if command -v ufw >/dev/null; then
ufw --force reset >/dev/null
ufw default deny incoming; ufw default allow outgoing
ufw allow 22/tcp comment 'ssh' # 改了 SSH 端口的同步改這裏
[ -n "${WEB:-}" ] && { ufw allow 80/tcp; ufw allow 443/tcp; }
ufw --force enable; ufw status numbered
fi
# ── 3. fail2ban(存在即啓用,配置用標記塊冪等寫入)──
if command -v fail2ban-client >/dev/null; then
printf '[sshd]
enabled = true
maxretry = 5
findtime = 600
bantime = 3600
' > /etc/fail2ban/jail.d/sshd.local
systemctl enable --now fail2ban; fail2ban-client status sshd | head -5
fi
# ── 4. 自動安全更新(只裝補丁,不跨大版本——紀律見正文)──
if command -v apt >/dev/null; then
apt-get install -y unattended-upgrades >/dev/null
dpkg-reconfigure -f noninteractive unattended-upgrades >/dev/null || true
fi
# ── 5. 基線採集(裝機快照:日後「變沒變」的對照物)──
mkdir -p /var/log/baseline
{ echo "# $(date -u +%FT%TZ)"; uname -a; lscpu | grep -E "Model name|^CPU\(s\)"; free -h; df -h /;
ss -tlnp; ip -brief addr; } > /var/log/baseline/boot-$(date +%F).txt
# ── 6. 時間同步與健康行 ──
timedatectl set-ntp true 2>/dev/null || true
echo "=== 完成:SSH/防火牆/fail2ban/更新/基線 五段全部就位 ==="
用法與驗證
# 上傳並執行(新機器第一條命令):
scp first-hour-init.sh root@新服務器IP:/root/ && ssh root@新服務器IP "bash /root/first-hour-init.sh"
# 冪等驗證:再跑一遍,結果應無差異、無報錯:
ssh root@新服務器IP "bash /root/first-hour-init.sh && tail -3 /var/log/first-hour-init.log"
# 效果驗證(從外部):
ssh -o PreferredAuthentications=password 新服務器IP exit # 應被拒絕(密碼登入已禁)
nc -zv -w3 新服務器IP 22 && echo "22 開(預期)"
nc -zv -w3 新服務器IP 3306 2>/dev/null || echo "3306 關(預期:默認拒絕生效)"
腳本之外的三個配套動作
- 版本化:腳本進你的 Git 倉庫(私有庫即可),每次修改留 commit——「誰在什麼時候改了初始化邏輯」有賬可查;配套的作品版(帶注釋的教學版)在創作社區的 first-hour-init.sh 作品頁;
- 分層擴展:首小時腳本只管安全基線;業務棧(Nginx/數據庫/應用)另寫第二層腳本,兩層分開跑——基線層穩定不動,業務層隨需求迭代;多機器用 Ansible 把兩層編排起來(單腳本是起點不是終點);
- 恢復演練聯動:備份腳本與恢復演練的自動化口徑見面板與備份——初始化的最後一塊拼圖是「這臺機器沒了,多久能重建」:有 first-hour-init + 備份 + 配置倉庫,答案是一臺新機器的開通時間——這個「重建時間」本身就是你架構成熟度的最好度量。
第二層腳本:業務棧的最小樣例
基線層之上,業務棧腳本同樣守冪等紀律。以最常見的 Nginx + 靜態站爲例,骨架三段:①安裝段——command -v nginx || apt-get install -y nginx(已裝即跳過);②配置段——站點配置寫成獨立文件放入 sites-available 並軟鏈到 sites-enabled,配置內容用「校驗後重載」模式:nginx -t && systemctl reload nginx(校驗失敗不重載,壞配置進不了生產);③驗證段——腳本末尾自測:curl -sf -o /dev/null http://127.0.0.1/ && echo "nginx ok",失敗即非零退出。三層結構(裝/配/驗)適用於一切業務棧腳本;機器多起來後把兩層腳本翻譯成 Ansible role(tasks 即命令、handlers 即重載、inventory 即機器清單)——腳本是 Ansible 的草稿,先寫對腳本再談編排。
常見翻車點清單
- 禁密碼前沒驗證密鑰 → 把自己鎖在門外(腳本第 0 段的前置檢查就是防這個;真鎖了用機房 IPMI/VNC 通道救回);
- ufw allow 順序錯 → 改過 SSH 端口的機器先 allow 新端口再 deny 22,順序反了同樣鎖門外;
- sed 盲改配置文件 → 重跑一次改一次,配置越跑越花——用獨立 drop-in 文件(sshd_config.d 模式)替代 sed 直改;
- 把密鑰/密碼寫進腳本 → 腳本進 Git 就是泄露:憑據永遠走環境變量或部署時的 secret 注入,腳本裏只留佔位符。
下一步
把腳本抄走、按你的發行版與端口改三行、進你的倉庫——第一臺機器開始就用冪等初始化,運維債從源頭少一半。安全動作的原理與完整清單在安全基線中心(SSH 十二動作/防火牆基線/備份演練三篇是一組);新機器開通入口在在售配置(入門 VPS $5.34/月起,練手成本最低的沙盒)。