Files
Ai-Single-Page/deepseek/shell/sftp_setup.sh
T
2025-10-24 22:05:37 +00:00

254 lines
8.4 KiB
Bash

#!/bin/bash
# ==============================================================================
# 脚本名称: sftp_setup.sh
# 功能描述: 自动化配置一个安全的、chrooted的SFTP用户,用于文件备份。
# 用户将被限制在特定目录,无法SSH登录,也无法访问系统其他部分。
# 调用方式: sudo ./sftp_setup.sh
# 作者: AI Assistant
# 版本: 1.4 (修复Subsystem重复定义问题)
# ==============================================================================
# --- 颜色定义,用于美化输出 ---
COLOR_GREEN='\033[0;32m'
COLOR_RED='\033[0;31m'
COLOR_YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# --- 辅助函数 ---
log_info() {
echo -e "${COLOR_GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${COLOR_YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${COLOR_RED}[ERROR]${NC} $1"
exit 1
}
# 检查是否以root权限运行
check_root() {
if [[ $EUID -ne 0 ]]; then
log_error "此脚本必须以 root 权限运行。请使用 'sudo $0'"
fi
}
# 清理旧的SFTP配置
cleanup_old_sftp_config() {
local config_file="$1"
log_info "正在清理旧的 SFTP 配置..."
# 创建临时文件
local temp_file=$(mktemp)
# 使用更精确的模式来清理配置
# 删除所有Subsystem sftp相关的行(包括注释和空白行后的注释)
awk '
/^[[:space:]]*#?[[:space:]]*Subsystem[[:space:]]+sftp/ {
# 跳过这一行(删除)
next
}
/^[[:space:]]*#.*sftp.*subsystem/ {
# 删除包含sftp subsystem的注释行
next
}
/^Match[[:space:]]+User/ {
# 如果遇到新的Match User块,停止删除
in_old_block = 0
}
in_old_block && /^[[:space:]]+#?[[:space:]]*(ChrootDirectory|ForceCommand|AllowTcpForwarding|X11Forwarding|PermitTunnel)/ {
# 在旧块中删除相关配置行
next
}
{
# 打印其他所有行
print
}
' "$config_file" > "$temp_file"
# 检查是否有变化
if ! diff "$config_file" "$temp_file" &>/dev/null; then
mv "$temp_file" "$config_file"
log_info "已清理旧的 SFTP 配置。"
else
rm -f "$temp_file"
log_info "未发现需要清理的旧配置。"
fi
}
# ==============================================================================
# 主程序
# ==============================================================================
log_info "开始 SFTP 用户自动配置流程..."
# 1. 检查root权限
check_root
# 2. 交互式获取用户输入
echo "--- 请输入 SFTP 用户配置信息 ---"
read -p "1. 请输入新的 SFTP 用户名: " SFTP_USERNAME
read -s -p "2. 请输入该用户的密码: " SFTP_PASSWORD
echo "" # 换行
read -s -p "3. 请再次输入密码以确认: " SFTP_PASSWORD_CONFIRM
echo "" # 换行
read -p "4. 请输入备份文件的根目录 (例如 /sftp/backup): " SFTP_ROOT_DIR
# 3. 验证输入
if [[ -z "$SFTP_USERNAME" || -z "$SFTP_PASSWORD" || -z "$SFTP_ROOT_DIR" ]]; then
log_error "用户名、密码和根目录不能为空!"
fi
if [[ "$SFTP_PASSWORD" != "$SFTP_PASSWORD_CONFIRM" ]]; then
log_error "两次输入的密码不匹配!"
fi
if [[ ! "$SFTP_ROOT_DIR" = /* ]]; then
log_error "根目录必须以 '/' 开头,为绝对路径!"
fi
# 定义SFTP组和上传目录名
SFTP_GROUP="sftpgroup"
UPLOAD_DIR_NAME="uploads"
SFTP_UPLOAD_DIR="${SFTP_ROOT_DIR}/${UPLOAD_DIR_NAME}"
log_info "配置信息确认无误,开始执行配置..."
# 4. 创建SFTP组
if ! grep -q "^${SFTP_GROUP}:" /etc/group; then
log_info "创建 SFTP 专用组: ${SFTP_GROUP}"
groupadd "$SFTP_GROUP"
else
log_warn "组 '${SFTP_GROUP}' 已存在,跳过创建。"
fi
# 5. 创建SFTP用户
if id "$SFTP_USERNAME" &>/dev/null; then
log_warn "用户 '${SFTP_USERNAME}' 已存在,将跳过用户创建,但会重置密码和目录权限。"
else
log_info "创建 SFTP 用户: ${SFTP_USERNAME}"
useradd -G "$SFTP_GROUP" -s /sbin/nologin -d "$SFTP_ROOT_DIR" "$SFTP_USERNAME"
fi
# 6. 设置用户密码
log_info "为用户 '${SFTP_USERNAME}' 设置密码..."
echo "${SFTP_USERNAME}:${SFTP_PASSWORD}" | chpasswd
# 7. 创建目录结构
log_info "创建 SFTP 目录结构..."
mkdir -p "$SFTP_ROOT_DIR"
mkdir -p "$SFTP_UPLOAD_DIR"
# 8. 设置目录权限 (最关键的一步)
log_info "设置目录权限..."
chown root:root "$SFTP_ROOT_DIR"
chmod 755 "$SFTP_ROOT_DIR"
chown "${SFTP_USERNAME}:${SFTP_GROUP}" "$SFTP_UPLOAD_DIR"
chmod 755 "$SFTP_UPLOAD_DIR"
log_info "权限设置完成。"
# 9. 配置 SSHD (修复版)
SSHD_CONFIG="/etc/ssh/sshd_config"
SSHD_BACKUP="${SSHD_CONFIG}.bak.$(date +%F_%H-%M-%S)"
log_info "配置 SSH 守护进程..."
# 备份原始文件
cp "$SSHD_CONFIG" "$SSHD_BACKUP"
log_info "已备份原始 sshd_config 到 ${SSHD_BACKUP}"
# --- 修复的配置逻辑 ---
# 1. 首先清理所有旧的SFTP相关配置
cleanup_old_sftp_config "$SSHD_CONFIG"
# 2. 检查是否已存在Subsystem sftp配置
if grep -q "^Subsystem[[:space:]]\+sftp" "$SSHD_CONFIG"; then
log_warn "发现已存在的 Subsystem sftp 配置,将使用现有配置。"
else
log_info "添加新的 'Subsystem sftp internal-sftp' 配置。"
echo "Subsystem sftp internal-sftp" >> "$SSHD_CONFIG"
fi
# 3. 清理该用户可能存在的旧配置
log_info "清理用户 '${SFTP_USERNAME}' 的旧配置..."
# 删除该用户的所有Match块
sed -i -e "/^Match User ${SFTP_USERNAME}$/,/^Match User\|^$/d" "$SSHD_CONFIG"
# 4. 添加新的用户配置
log_info "为用户 '${SFTP_USERNAME}' 添加新的 Chroot 配置..."
{
printf "\n# --- SFTP Chroot for user: %s (由脚本自动添加) ---\n" "${SFTP_USERNAME}"
printf "Match User %s\n" "${SFTP_USERNAME}"
printf " ForceCommand internal-sftp\n"
printf " ChrootDirectory %s\n" "${SFTP_ROOT_DIR}"
printf " AllowTcpForwarding no\n"
printf " X11Forwarding no\n"
printf " PermitTunnel no\n"
} >> "$SSHD_CONFIG"
log_info "SSH 配置更新完成。"
# --- 配置逻辑结束 ---
# 10. 测试SSH配置并重启服务
log_info "正在测试 SSH 配置文件语法..."
if sshd -t; then
log_info "配置文件语法正确。"
log_info "正在重启 SSH 服务以应用配置..."
if command -v systemctl &> /dev/null; then
systemctl restart sshd
# 检查服务状态
if systemctl is-active --quiet sshd; then
log_info "SSH 服务重启成功。"
else
log_error "SSH 服务启动失败,请检查系统日志。"
fi
else
service ssh restart
log_info "SSH 服务重启完成。"
fi
else
log_error "SSH 配置文件语法错误!服务未重启。"
log_error "以下是所有相关的 SFTP 配置行,请检查:"
grep -n -A5 -B2 "Subsystem sftp\|Match User ${SFTP_USERNAME}" "$SSHD_CONFIG" || true
log_error "请检查 /etc/ssh/sshd_config 文件,或使用备份文件 ${SSHD_BACKUP} 恢复。"
exit 1
fi
# 11. 验证配置
log_info "验证 SFTP 配置..."
if grep -q "Match User ${SFTP_USERNAME}" "$SSHD_CONFIG" && \
grep -q "ChrootDirectory ${SFTP_ROOT_DIR}" "$SSHD_CONFIG"; then
log_info "SFTP 配置验证成功。"
else
log_warn "SFTP 配置可能未正确应用,请手动检查。"
fi
# 12. 完成
echo "=================================================="
log_info "SFTP 用户配置完成!"
echo "=================================================="
echo -e "用户名: ${COLOR_YELLOW}${SFTP_USERNAME}${NC}"
echo -e "密码: ${COLOR_YELLOW}[已设置]${NC}"
echo -e "SFTP根目录: ${COLOR_YELLOW}${SFTP_ROOT_DIR}${NC}"
echo -e "文件上传目录: ${COLOR_YELLOW}${SFTP_UPLOAD_DIR}${NC}"
echo ""
echo -e "${COLOR_GREEN}现在,您可以在您的备份脚本中使用以下信息:${NC}"
echo -e " SERVER_USER=\"${SFTP_USERNAME}\""
echo -e " REMOTE_DIR=\"/${UPLOAD_DIR_NAME}\""
echo ""
echo -e "${COLOR_YELLOW}测试命令 (在服务器本机执行):${NC}"
echo -e " sftp ${SFTP_USERNAME}@localhost"
echo ""
echo -e "${COLOR_YELLOW}重要提醒:${NC}"
echo -e " 1. 确保根目录 ${SFTP_ROOT_DIR} 的所有者是 root:root"
echo -e " 2. 用户只能访问 ${SFTP_UPLOAD_DIR} 目录"
echo -e " 3. 用户无法通过SSH登录系统"
echo "=================================================="
exit 0