Rocky Linux 9.5 初始化脚本

每次装完系统那一套“起手式”,比如换源、关 SELinux、调 SSH、配网络,手动敲起来确实有点烦,因此,编写一个脚本来简化操作还是很有必要的。


脚本核心逻辑

  1. 网络连通性预检:脚本跑之前先检查网络连通性,没网直接报错。
  2. SSH 登录速度优化:关掉 UseDNSGSSAPI
  3. 适配 NetworkManager:在 Rocky 9 里直接改 resolv.conf 是没用的,重启就会丢失。通过 conf.d 配置文件接管,彻底解决 DNS 被覆写的问题。
  4. SSH Root 权限控制:可选允许或禁止 Root 登录,毕竟有些环境为了安全必须关掉。

完整的脚本代码

#!/bin/bash

# =========================================================
# Rocky Linux 9.5 初始化脚本
# 功能:DNS、换源、防火墙、SELinux、SSH优化、基础软件、时区
# =========================================================

set -euo pipefail

# 定义颜色
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
NC='\033[0m'

log_info() { echo -e "${GREEN}[INFO] $1${NC}"; }
log_warn() { echo -e "${YELLOW}[WARN] $1${NC}"; }
log_err()  { echo -e "${RED}[ERROR] $1${NC}"; }

# 交互询问函数
ask_user() {
    local prompt="$1"
    while true; do
        echo -e -n "${CYAN}${prompt} (y/n): ${NC}"
        read -r answer
        case "${answer,,}" in
            y|yes ) return 0 ;;
            n|no ) return 1 ;;
            * ) echo "请输入 y 或 n。" ;;
        esac
    done
}

# 命令执行包装,带报错处理
run_cmd() {
    local desc="$1"
    shift
    echo -e "${YELLOW}---> 正在执行: ${desc}${NC}"
    set +e
    "$@"
    local ret=$?
    set -e
    if [ $ret -eq 0 ]; then
        log_info "执行成功\n"
    else
        log_err "执行失败 (退出码: $ret)\n"
        return $ret
    fi
}

# 环境与网络检查
check_env() {
    if [ "$EUID" -ne 0 ]; then
        log_err "请使用 root 用户执行!"
        exit 1
    fi

    log_info "检查网络连通性..."
    if ping -c 2 -W 3 223.5.5.5 >/dev/null 2>&1; then
        log_info "网络正常。"
    else
        echo ""
        log_warn "网络不通 (Ping 223.5.5.5 失败)!"
        if ! ask_user "是否忽略警告强制继续?"; then
            exit 1
        fi
    fi

    log_info "正在检查 DNS 域名解析..."
    if ping -c 2 -W 3 mirrors.aliyun.com >/dev/null 2>&1; then
        log_info "DNS 域名解析测试通过。"
    else
        echo ""
        log_warn "域名解析失败 (无法解析 mirrors.aliyun.com)。"
        log_warn "这通常是因为 /etc/resolv.conf 为空或 DNS 服务器不可达。"
        log_warn "可以先继续执行,然后选择菜单中的 [配置 DNS] 选项,或手动修复解析。"
        if ! ask_user "是否忽略解析警告,强制继续执行脚本?"; then
            exit 1
        fi
    fi
}

# DNS配置 (针对 Rocky 9 特化)
config_network() {
    if ask_user "是否配置DNS (114.114.114.114/8.8.8.8)?"; then
        run_cmd "配置 NetworkManager dns=none" sh -c "cat > /etc/NetworkManager/conf.d/dns.conf <<EOF
[main]
dns=none
EOF"
        systemctl restart NetworkManager
        run_cmd "写入 resolv.conf" sh -c "echo -e 'nameserver 114.114.114.114\nnameserver 8.8.8.8' > /etc/resolv.conf"
    fi
}

# 软件源配置 (阿里云)
config_repo() {
    if ask_user "是否配置阿里云镜像源并启用 CRB/EPEL?"; then
        run_cmd "备份原始 repo" cp -a /etc/yum.repos.d /etc/yum.repos.d.bak.$(date +%F_%T)
        run_cmd "替换 Rocky 官方源" sed -i -e 's|^mirrorlist=|#mirrorlist=|g' \
            -e 's|^#baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.aliyun.com/rockylinux|g' \
            /etc/yum.repos.d/rocky*.repo

        run_cmd "开启 CRB 仓库" dnf config-manager --set-enabled crb
        run_cmd "安装 EPEL 源" dnf install -y epel-release
        run_cmd "刷新 DNF 缓存" dnf makecache
    fi
}

# 安全与 SSH 配置
config_sec_ssh() {
    if ask_user "是否配置 SELinux (Permissive) 并优化 SSH?"; then
        run_cmd "设置 SELinux 为宽容模式" sed -i 's/^SELINUX=.*/SELINUX=permissive/g' /etc/selinux/config
        setenforce 0 2>/dev/null || true

        # SSH 性能优化
        run_cmd "关闭 SSH DNS 解析" sed -i 's/^#UseDNS yes/UseDNS no/g; s/^UseDNS yes/UseDNS no/g' /etc/ssh/sshd_config
        run_cmd "关闭 SSH GSSAPI 认证" sed -i 's/^GSSAPIAuthentication yes/GSSAPIAuthentication no/g' /etc/ssh/sshd_config

        # Root 登录控制
        if ask_user "是否允许 Root 用户 SSH 密码登录?"; then
            run_cmd "允许 Root 登录" sed -i 's/^#*PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config
        else
            run_cmd "禁止 Root 登录" sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/g' /etc/ssh/sshd_config
            log_warn "注意:禁止 Root 登录后,请确保你有其他用户可以登录!"
        fi
        systemctl restart sshd
    fi
}

# 基础软件与时区
install_env() {
    local pkgs=(vim wget curl net-tools lsof chrony bash-completion)
    if ask_user "是否安装常用软件 (${pkgs[*]})?"; then
        run_cmd "DNF 安装软件包" dnf install -y "${pkgs[@]}"
        run_cmd "设置上海时区" timedatectl set-timezone Asia/Shanghai
        run_cmd "启动 Chrony 时间同步" systemctl enable --now chronyd
    fi
}

# 主控入口
main() {
    check_env
    while true; do
        echo -e "\n${CYAN}--- Rocky Linux 9.5 Init Menu ---${NC}"
        echo "1) 全量执行初始化"
        echo "2) 仅配置 DNS"
        echo "3) 仅配置软件源"
        echo "4) 仅配置安全与 SSH"
        echo "5) 仅安装软件与时区"
        echo "0) 退出"
        read -p "选择 [0-5]: " ch
        case $ch in
            1) config_network; config_repo; config_sec_ssh; install_env; break ;;
            2) config_network ;;
            3) config_repo ;;
            4) config_sec_ssh ;;
            5) install_env ;;
            0) exit 0 ;;
        esac
    done
    log_info "初始化完成!"
}

main

几个值得注意的技术点

1. 为什么检查 223.5.5.5

以前写脚本喜欢 Ping 百度,但有些纯内网环境可能只通阿里源。223.5.5.5 是阿里提供的公共 DNS,拿它测连通性比较稳。

2. Rocky 9 的 DNS 坑

在 RHEL 9 系列中,NetworkManager 的优先级非常高。如果直接改 /etc/resolv.conf,只要网络服务一重启,它就会被重置回 DHCP 获取到的 DNS。脚本里的做法是先告诉 NetworkManager“别管 DNS 了”(dns=none),然后再写文件,这样才是一劳永逸。

3. 关于 set -euo pipefail

  • -e: 命令报错就停。

  • -u: 用了没定义的变量就报错。

  • -o pipefail: 管道里的命令失败也算失败。

这能过滤掉 90% 潜在的逻辑 Bug。