ansible-devops/scripts/nfs.sh

533 lines
25 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
set -euo pipefail # 严格模式:报错即退出、禁止未定义变量、管道错误传递
##############################################################################
# 常量定义(默认值,可通过参数覆盖)
##############################################################################
DEFAULT_SHARE_DIRS="/opt/data" # 默认共享目录
EXPORTS_FILE="/etc/exports" # NFS配置文件路径
BACKUP_SUFFIX=$(date +%Y%m%d%H%M%S) # 备份文件后缀(时间戳,避免覆盖)
# NFS共享权限模板{DIR}会自动替换为实际共享目录)
# 权限说明rw(读写)、sync(同步写入)、no_root_squash(保留root权限)、no_all_squash(保留用户权限)、insecure(允许非特权端口访问)
NFS_CONFIG_TEMPLATE="{DIR} *(rw,sync,no_root_squash,no_all_squash,insecure)"
TEST_MOUNT_DIR_PREFIX="/mnt/nfs_test_$$" # 临时测试挂载点前缀($$为进程ID确保唯一性
##############################################################################
# 全局变量(通过命令行参数初始化)
##############################################################################
ACTION="" # 操作类型install(安装) / uninstall(卸载)
SHARE_DIRS=() # 共享目录数组(支持多个目录)
OS_TYPE="" # 系统类型(提前检测,避免重复调用)
##############################################################################
# 工具函数(中文注释)
##############################################################################
# 日志输出(带时间戳,便于排查问题)
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1"
}
# 错误退出(打印错误信息后终止脚本)
error_exit() {
log "ERROR: $1"
exit 1
}
# 检查是否为root用户NFS安装/卸载需要root权限
check_root() {
if [ $EUID -ne 0 ]; then
error_exit "请使用root用户运行脚本执行 sudo -i 或 su - 切换)"
fi
}
# 检测系统发行版返回centos/ubuntu/debian适配不同系统的包管理
detect_os() {
if [ -f /etc/os-release ]; then
. /etc/os-release
case $ID in
centos|rhel) echo "centos" ;;
ubuntu) echo "ubuntu" ;;
debian) echo "debian" ;;
*) error_exit "不支持的系统发行版:$ID仅支持CentOS/RHEL 7+、Ubuntu 18.04+、Debian 9+" ;;
esac
else
error_exit "无法检测系统发行版(缺少/etc/os-release文件"
fi
}
# 目录路径转义(将 / 替换为 \/避免sed命令语法冲突
escape_dir() {
local dir="$1"
echo "${dir//\//\\/}" # 替换所有 / 为 \/
}
# 检查服务是否正在运行
is_service_running() {
local service_name=$1
systemctl is-active --quiet "$service_name" 2>/dev/null
}
# 修复Ubuntu/Debian下NFS服务启动异常核心修复函数
fix_ubuntu_nfs_start() {
log "开始修复NFS服务启动环境Ubuntu/Debian"
# 1. 停止残留服务进程,避免状态冲突
log "清理残留NFS相关进程"
pkill -f nfsd || true
pkill -f rpc.mountd || true
# 2. 确保依赖服务rpcbind启动Ubuntu 18.04+ 部分版本rpcbind默认未启动
log "启动依赖服务rpcbind"
systemctl enable --now rpcbind || error_exit "rpcbind服务启动失败NFS核心依赖"
# 3. 校验/etc/exports配置语法避免配置错误导致服务启动失败
log "校验NFS配置文件语法$EXPORTS_FILE"
if ! exportfs -r; then # 重载配置并检查语法
error_exit "NFS配置文件语法错误请检查 $EXPORTS_FILE 内容(比如目录路径、权限参数是否正确)"
fi
log "NFS配置语法校验通过"
# 4. 重启nfs-kernel-server服务分两步停止→启动避免状态残留
log "重启nfs-kernel-server服务"
systemctl stop nfs-kernel-server || true # 确保服务已停止
sleep 2 # 等待进程清理
if systemctl start nfs-kernel-server; then
log "nfs-kernel-server服务启动成功"
systemctl enable nfs-kernel-server || log "警告设置nfs-kernel-server开机自启失败不影响当前使用"
else
# 启动失败时输出详细日志,便于排查
log "nfs-kernel-server服务启动失败查看详细错误日志"
journalctl -xeu nfs-kernel-server | tail -20
error_exit "nfs-kernel-server服务启动失败见上方错误日志"
fi
}
# 解析命令行参数(处理--install/--uninstall/--share-dirs参数
parse_args() {
if [ $# -eq 0 ]; then
show_help # 无参数时显示帮助信息
exit 0
fi
# 遍历所有传入参数
for arg in "$@"; do
case $arg in
--install)
ACTION="install"
;;
--uninstall)
ACTION="uninstall"
;;
--share-dirs=*)
# 提取--share-dirs后的目录字符串逗号分隔
local dir_str=${arg#--share-dirs=}
# 按逗号分割为数组
IFS=',' read -r -a dir_array <<< "$dir_str"
# 处理目录数组:去重、过滤空值、校验绝对路径
for dir in "${dir_array[@]}"; do
dir=$(echo "$dir" | xargs) # 去除目录名前后的空格
if [ -z "$dir" ]; then
log "警告:跳过空的共享目录(可能是逗号前后多写了空格)"
continue
fi
# 校验是否为绝对路径(必须以/开头)
if [[ ! "$dir" =~ ^/ ]]; then
error_exit "共享目录必须是绝对路径(当前输入:$dir),例如 /opt/data"
fi
# 去重:如果目录已在数组中,不再重复添加
if ! [[ " ${SHARE_DIRS[@]} " =~ " $dir " ]]; then
SHARE_DIRS+=("$dir")
fi
done
;;
-h|--help)
show_help
exit 0
;;
*)
error_exit "无效参数:$arg,请使用 --help 查看正确用法"
;;
esac
done
# 校验必填参数:必须指定--install或--uninstall
if [ -z "$ACTION" ]; then
error_exit "必须指定操作类型:--install安装 或 --uninstall卸载"
fi
# 若未指定--share-dirs使用默认共享目录
if [ ${#SHARE_DIRS[@]} -eq 0 ]; then
SHARE_DIRS=($DEFAULT_SHARE_DIRS)
log "未指定共享目录,使用默认值:${SHARE_DIRS[*]}"
else
log "已指定共享目录列表:${SHARE_DIRS[*]}"
fi
}
# 显示帮助信息(中文说明,方便用户使用)
show_help() {
echo "======================================= NFS一键安装卸载脚本 ======================================="
echo "用法:$0 [选项]"
echo "核心选项:"
echo " --install 安装NFS服务端+客户端并配置共享目录全程使用root权限"
echo " --uninstall 卸载NFS服务端+客户端,清理相关配置(保留数据)"
echo " --share-dirs=DIR1,DIR2 指定共享目录(多个目录用逗号分隔,必须为绝对路径)"
echo " --help/-h 显示当前帮助信息"
echo ""
echo "使用示例:"
echo " 1. 安装NFS使用默认共享目录 /opt/data"
echo " $0 --install"
echo " 2. 安装NFS指定多个共享目录"
echo " $0 --install --share-dirs=/opt/data1,/opt/data2,/mnt/nfs_share"
echo " 3. 卸载NFS清理默认共享目录的配置"
echo " $0 --uninstall"
echo " 4. 卸载NFS清理指定共享目录的配置"
echo " $0 --uninstall --share-dirs=/opt/data1,/mnt/nfs_share"
echo "=================================================================================================="
}
# 打印NFS常用维护命令用户要求新增
print_maintain_commands() {
log "======================================= NFS常用维护命令 ======================================="
log "📊 查看NFS服务状态"
if [ "$OS_TYPE" = "centos" ]; then
log " systemctl status nfs-server"
else
log " systemctl status nfs-kernel-server"
fi
log ""
log "🔄 重启NFS服务"
if [ "$OS_TYPE" = "centos" ]; then
log " systemctl restart nfs-server"
else
log " systemctl restart nfs-kernel-server"
fi
log ""
log "📁 查看已配置的NFS共享目录"
log " exportfs -v"
log ""
log "⚙️ 重载NFS配置修改/etc/exports后执行"
log " exportfs -r"
log ""
log "🖥️ 客户端挂载NFS共享示例"
log " mount -t nfs 服务器IP:/共享目录 本地目标目录mount -t nfs 192.168.1.100:/opt/data /mnt/client_data"
log ""
log "🗑️ 客户端卸载NFS共享"
log " umount 本地目标目录umount /mnt/client_data"
log ""
log "🔥 查看NFS服务日志排查问题"
if [ "$OS_TYPE" = "centos" ]; then
log " journalctl -u nfs-server -f"
else
log " journalctl -u nfs-kernel-server -f"
fi
log "=================================================================================================="
}
##############################################################################
# 安装NFS服务支持多共享目录中文步骤说明
##############################################################################
install_nfs() {
log "开始安装NFS服务端+客户端(系统:$OS_TYPE,共享目录:${SHARE_DIRS[*]}全程使用root权限"
##########################################################################
# 步骤1安装NFS依赖包根据系统类型选择yum或apt
##########################################################################
log "步骤1/6安装NFS相关依赖包"
case $OS_TYPE in
centos)
# CentOS/RHELnfs-utils服务端+客户端、rpcbind端口映射
yum install -y nfs-utils rpcbind || error_exit "YUM安装依赖失败请检查yum源是否正常"
;;
ubuntu|debian)
# Ubuntu/Debiannfs-kernel-server服务端、nfs-common客户端、rpcbind依赖
apt update -y >/dev/null 2>&1 || error_exit "APT更新源失败请检查网络或apt源配置"
apt install -y nfs-kernel-server nfs-common rpcbind || error_exit "APT安装依赖失败"
;;
esac
log "依赖包安装完成"
##########################################################################
# 步骤2创建共享目录并设置全权限777+root属主满足用户要求
##########################################################################
log "步骤2/6创建共享目录并配置全权限${#SHARE_DIRS[@]}个目录root属主"
for dir in "${SHARE_DIRS[@]}"; do
if [ -d "$dir" ]; then
log "共享目录 $dir 已存在,跳过创建"
else
# -p递归创建父目录如/opt/data不存在时自动创建/opt和/opt/data
mkdir -p "$dir" || error_exit "创建共享目录 $dir 失败(权限不足或路径非法)"
log "成功创建共享目录:$dir"
fi
# 设置全权限777保留root属主创建目录时默认就是root无需额外chown
chmod 777 "$dir" || error_exit "设置 $dir 权限为777失败"
log "目录 $dir权限配置完成全权限777属主root:root"
done
##########################################################################
# 步骤3配置/etc/exportsNFS核心配置文件先备份再修改
##########################################################################
log "步骤3/6配置NFS共享自动备份原有配置"
# 备份原有配置文件(添加时间戳后缀,便于回滚)
if [ -f "$EXPORTS_FILE" ]; then
cp "$EXPORTS_FILE" "${EXPORTS_FILE}.bak.${BACKUP_SUFFIX}" || error_exit "备份 $EXPORTS_FILE 失败"
log "已备份原有配置文件:${EXPORTS_FILE}.bak.${BACKUP_SUFFIX}"
fi
# 批量添加/更新共享目录配置(核心修复:使用转义后的目录路径)
for dir in "${SHARE_DIRS[@]}"; do
local escaped_dir=$(escape_dir "$dir") # 转义目录中的 /
# 替换配置模板中的{DIR}为实际目录
local nfs_config=${NFS_CONFIG_TEMPLATE//\{DIR\}/$dir}
# 若该目录已存在配置,先删除旧配置(避免重复)
if grep -q "^${escaped_dir}" "$EXPORTS_FILE" 2>/dev/null; then
log "目录 $dir 的NFS配置已存在更新为最新配置"
# sed命令使用转义后的目录避免语法冲突
sed -i "/^${escaped_dir}/d" "$EXPORTS_FILE" || error_exit "删除 $dir 的旧配置失败"
fi
# 写入新配置到/etc/exports
echo "$nfs_config" >> "$EXPORTS_FILE" || error_exit "写入 $dir 的NFS配置失败"
log "目录 $dir:配置已添加到 $EXPORTS_FILE"
done
##########################################################################
# 步骤4启动NFS服务并设置开机自启优化启动逻辑解决启动失败问题
##########################################################################
log "步骤4/6启动NFS服务并配置开机自启"
case $OS_TYPE in
centos)
# CentOS先启动rpcbind再启动nfs-server
systemctl enable --now rpcbind || error_exit "启动rpcbind服务失败"
systemctl stop nfs-server || true
sleep 2
if systemctl start nfs-server; then
systemctl enable nfs-server || log "警告设置nfs-server开机自启失败"
log "nfs-server服务启动成功"
else
log "nfs-server启动失败详细日志"
journalctl -xeu nfs-server | tail -20
error_exit "nfs-server服务启动失败"
fi
;;
ubuntu|debian)
# Ubuntu/Debian使用修复函数启动服务处理依赖、配置校验、残留清理
fix_ubuntu_nfs_start
;;
esac
# 验证服务是否启动成功
local service_name=$([ "$OS_TYPE" = "centos" ] && echo "nfs-server" || echo "nfs-kernel-server")
if ! is_service_running "$service_name"; then
error_exit "$service_name服务启动失败请检查系统日志journalctl -u $service_name"
fi
log "$service_name服务启动成功(已配置开机自启)"
##########################################################################
# 步骤5开放防火墙端口企业环境必备确保客户端能访问NFS服务
##########################################################################
log "步骤5/6开放防火墙NFS相关端口"
case $OS_TYPE in
centos)
# CentOS使用firewalld
if is_service_running "firewalld"; then
firewall-cmd --permanent --add-service=nfs || error_exit "开放NFS端口2049失败"
firewall-cmd --permanent --add-service=rpc-bind || error_exit "开放rpc-bind端口111失败"
firewall-cmd --permanent --add-service=mountd || error_exit "开放mountd端口失败"
firewall-cmd --reload || error_exit "防火墙重载配置失败"
log "Firewalld已开放NFS相关端口2049、111等"
else
log "Firewalld未运行跳过端口开放若后续客户端无法连接请手动开放端口"
fi
;;
ubuntu|debian)
# Ubuntu/Debian使用ufw
if is_service_running "ufw"; then
ufw allow nfs || error_exit "开放NFS端口失败"
ufw allow 111/tcp || error_exit "开放rpcbind端口111失败"
ufw reload || error_exit "UFW重载配置失败"
log "UFW已开放NFS端口2049和rpcbind端口111"
else
log "UFW未运行跳过端口开放若后续客户端无法连接请手动开放端口"
fi
;;
esac
##########################################################################
# 步骤6测试验证每个共享目录都执行挂载+读写测试,确保可用)
##########################################################################
log "步骤6/6测试所有共享目录的可用性"
for dir in "${SHARE_DIRS[@]}"; do
# 为每个目录创建唯一的临时挂载点(替换/为_避免路径冲突
local test_mount_dir="${TEST_MOUNT_DIR_PREFIX}_$(echo "$dir" | tr '/' '_')"
# 创建临时挂载点
mkdir -p "$test_mount_dir" || error_exit "创建测试挂载点 $test_mount_dir 失败"
# 本地挂载测试使用localhost模拟客户端
log "正在测试挂载localhost:$dir -> $test_mount_dir"
mount -t nfs localhost:"$dir" "$test_mount_dir" || error_exit "目录 $dir 挂载测试失败NFS配置可能有误"
# 读写测试:创建测试文件并验证内容
local test_file="${test_mount_dir}/nfs_test_${BACKUP_SUFFIX}.txt"
echo "NFS测试内容$(date) - 共享目录:$dirroot权限" > "$test_file" || error_exit "目录 $dir 写入测试失败"
if [ -f "$test_file" ] && grep -q "NFS测试内容" "$test_file"; then
log "目录 $dir:读写测试成功(测试文件路径:$test_file"
else
error_exit "目录 $dir:读写测试失败(文件创建或读取异常)"
fi
# 卸载临时挂载点并清理
umount "$test_mount_dir" || error_exit "卸载测试挂载点 $test_mount_dir 失败"
rm -rf "$test_mount_dir" || log "清理测试挂载点 $test_mount_dir 失败(可手动删除)"
done
##########################################################################
# 安装完成提示+常用维护命令
##########################################################################
log "========================================"
log "🎉 NFS服务安装完成"
log "📁 共享目录列表:${SHARE_DIRS[*]}"
log "🔑 共享权限:全权限(读写+同步写入+保留root权限"
log "👤 目录属主root:root全程使用root权限"
log "⚙️ 配置文件:$EXPORTS_FILE(备份文件:${EXPORTS_FILE}.bak.${BACKUP_SUFFIX}"
log "🖥️ 客户端挂载命令mount -t nfs 服务器IP:共享目录 本地目标目录"
log "========================================"
# 打印常用维护命令(用户要求)
print_maintain_commands
}
##############################################################################
# 卸载NFS服务支持多共享目录清理中文步骤说明
##############################################################################
uninstall_nfs() {
log "开始卸载NFS服务端+客户端(系统:$OS_TYPE,清理目录:${SHARE_DIRS[*]}"
##########################################################################
# 步骤1卸载所有NFS挂载点避免服务删除时因挂载占用失败
##########################################################################
log "步骤1/5卸载所有关联的NFS挂载点"
for dir in "${SHARE_DIRS[@]}"; do
local escaped_dir=$(escape_dir "$dir") # 转义目录中的 /
# 匹配所有挂载了该共享目录的挂载点(转义/避免正则匹配错误)
local mount_points=$(mount | grep -E "nfs.*${escaped_dir}" | awk '{print $3}')
if [ -n "$mount_points" ]; then
for mp in $mount_points; do
log "正在卸载挂载点(关联目录:$dir$mp"
# -l强制卸载即使占用-f强制杀死占用进程
umount -lf "$mp" || log "卸载 $mp 失败(可能已被卸载)"
done
fi
done
# 清理剩余的NFS挂载点避免遗漏
local remaining_mounts=$(mount | grep -E "nfs|nfs4" | awk '{print $3}')
if [ -n "$remaining_mounts" ]; then
log "清理剩余的NFS挂载点$remaining_mounts"
for mp in $remaining_mounts; do
umount -lf "$mp" || true
done
fi
log "挂载点清理完成"
##########################################################################
# 步骤2停止NFS相关服务并禁用开机自启
##########################################################################
log "步骤2/5停止NFS相关服务"
case $OS_TYPE in
centos)
systemctl stop nfs-server rpcbind || true
systemctl disable nfs-server rpcbind || true
;;
ubuntu|debian)
systemctl stop nfs-kernel-server rpcbind || true
systemctl disable nfs-kernel-server rpcbind || true
;;
esac
log "NFS相关服务已停止并禁用开机自启"
##########################################################################
# 步骤3清理/etc/exports配置核心修复使用转义后的目录路径
##########################################################################
log "步骤3/5清理指定共享目录的NFS配置"
if [ -f "$EXPORTS_FILE" ]; then
for dir in "${SHARE_DIRS[@]}"; do
local escaped_dir=$(escape_dir "$dir") # 转义目录中的 /
if grep -q "^${escaped_dir}" "$EXPORTS_FILE" 2>/dev/null; then
# sed命令使用转义后的目录避免语法冲突
sed -i "/^${escaped_dir}/d" "$EXPORTS_FILE" || error_exit "清理 $dir 的NFS配置失败"
log "已清理 $dir 的NFS配置"
else
log "$dir 的NFS配置不存在跳过清理"
fi
done
# 若配置文件为空,直接删除(避免残留空文件)
if [ ! -s "$EXPORTS_FILE" ]; then
rm -f "$EXPORTS_FILE" || log "删除空配置文件 $EXPORTS_FILE 失败"
log "已删除空的NFS配置文件"
fi
else
log "NFS配置文件 $EXPORTS_FILE 不存在,跳过清理"
fi
##########################################################################
# 步骤4卸载NFS相关依赖包彻底清理安装文件
##########################################################################
log "步骤4/5卸载NFS相关依赖包"
case $OS_TYPE in
centos)
yum remove -y nfs-utils rpcbind || error_exit "YUM卸载依赖包失败"
;;
ubuntu|debian)
apt purge -y nfs-kernel-server nfs-common rpcbind || error_exit "APT卸载依赖包失败"
# 自动清理无用依赖
apt autoremove -y >/dev/null 2>&1 || true
;;
esac
log "NFS相关依赖包已卸载完成"
##########################################################################
# 步骤5提示共享目录数据处理默认保留避免误删业务数据
##########################################################################
log "步骤5/5共享目录数据处理提示"
for dir in "${SHARE_DIRS[@]}"; do
if [ -d "$dir" ]; then
log "共享目录 $dir 仍存在已保留其中数据属主root:root如需删除请执行rm -rf '$dir'"
else
log "共享目录 $dir 不存在,跳过提示"
fi
done
##########################################################################
# 卸载完成提示+常用维护命令(用户要求)
##########################################################################
log "========================================"
log "🗑️ NFS服务卸载完成"
log "✅ 已清理内容NFS服务、指定共享目录配置、相关依赖包"
log "⚠️ 未清理内容:共享目录数据(需手动删除,避免误删重要文件)"
log "📋 已清理的共享目录列表:${SHARE_DIRS[*]}"
log "========================================"
# 打印常用维护命令(用户要求)
print_maintain_commands
}
##############################################################################
# 主逻辑(脚本入口)
##############################################################################
main() {
check_root # 先检查是否为root用户
parse_args "$@" # 解析命令行参数
OS_TYPE=$(detect_os) # 检测系统类型并保存(避免重复调用)
# 根据操作类型执行对应函数
case $ACTION in
install)
install_nfs
;;
uninstall)
uninstall_nfs
;;
*)
error_exit "无效操作类型:$ACTION(仅支持 install 或 uninstall"
;;
esac
}
# 启动主逻辑
main "$@"