配置SFTP服务
bash
##sftp10.12.76.20
useradd sftps20
useradd sftpg20
# 设置密码
passwd sftps20
# xxxxxxxxx
passwd sftpg20
# xxxxxxxx
mkdir -p /data/sftp/sftps20
mkdir -p /data/sftp/sftpg20
chmod 755 /data/sftp
chown sftps20:sftps20 /data/sftp/sftps20
chown sftpg20:sftpg20 /data/sftp/sftpg20
chmod 775 /data/sftp/sftps20
chmod 775 /data/sftp/sftpg20
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.250805
vi /etc/ssh/sshd_config
# 找到并注释掉原SFTP子系统配置
# Subsystem sftp /usr/libexec/openssh/sftp-server # CentOS
# Subsystem sftp /usr/lib/openssh/sftp-server # Ubuntu
# 添加新的SFTP配置(使用内置模块)
Subsystem sftp internal-sftp
# 添加用户限制配置(放在文件末尾)
# 限制特定用户(如sftpuser)仅能使用SFTP,不能SSH登录
Match User sftps20,sftpg20
ChrootDirectory /data/sftp
ForceCommand internal-sftp
AllowTcpForwarding no
X11Forwarding no
sshd -t
systemctl restart sshd
验证 SFTP 服务
bash
sftp sftps20@localhost
put test.txt /upload/
exit
安装inotify-tools
bash
cd /tmp/
mv inotify-tools-3.13.tar.gz /data/
cd /data/
tar -zxvf inotify-tools-3.13.tar.gz
cd inotify-tools-3.13/
./configure --prefix=/usr/local
cd /usr/local/
cd /data/inotify-tools-3.13/
make
make install
inotifywait -h
自动修改权限脚本
bash
cd /data/zz/
vi sftpfilechmod.sh
bash
#!/bin/bash
#SFTP file chmod
#MagicConch
#CreateAt:ember.zhang
#CreateTime:2025-08-05
# 配置:SFTP上传目录
WATCH_DIR="/data/sftp"
# 配置:日志文件路径
LOG_FILE="/var/log/sftp_permissions.log"
# 检查监控目录是否存在
if [ ! -d "$WATCH_DIR" ]; then
echo "错误:监控目录 $WATCH_DIR 不存在!"
exit 1
fi
# 创建日志文件(如果不存在)
if [ ! -f "$LOG_FILE" ]; then
touch "$LOG_FILE"
chmod 644 "$LOG_FILE"
fi
# 记录启动信息
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 启动SFTP权限监控脚本,监控目录:$WATCH_DIR" >> "$LOG_FILE"
# 监控目录事件:
# - close_write:文件写入完成(确保文件上传完整)
# - create:目录创建事件
# - moved_to:文件/目录被移动到监控目录(处理通过移动方式上传的内容)
inotifywait -m -r -e close_write,create,moved_to --format "%e %w%f" "$WATCH_DIR" | while read EVENT FILE; do
# 处理文件
if [ -f "$FILE" ]; then
# 设置文件权限为755
chmod 755 "$FILE"
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 处理文件:$FILE,事件:$EVENT,设置权限为775" >> "$LOG_FILE"
# 处理目录
elif [ -d "$FILE" ]; then
# 设置目录权限为755
chmod 755 "$FILE"
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 处理目录:$FILE,事件:$EVENT,设置权限为775" >> "$LOG_FILE"
fi
done
封装服务/etc/systemd/system/sftp-permissions.service
bash
chmod 775 sftpfilechmod.sh
# 后台运行脚本
# sudo nohup /data/zz/sftpfilechmod.sh &
# 设置开机自启(适用于systemd系统)
tee /etc/systemd/system/sftp-permissions.service <<EOF
[Unit]
Description=SFTP Upload Permissions Controller
After=network.target
[Service]
Type=simple
ExecStart=/data/zz/sftpfilechmod.sh
Restart=always
User=root
[Install]
WantedBy=multi-user.target
EOF
# 启用并启动服务
systemctl daemon-reload
systemctl enable sftp-permissions
systemctl start sftp-permissions
自动同步远端服务器脚本
bash
vi /data/zz/zsync.sh
bash
#!/bin/bash
#sync
#MagicConch
#CreateAt:ember.zhang
#CreateTime:2025-09-03
#########################################################################
# -------------------------- 1. 配置参数(需用户根据实际环境修改!)--------------------------
MONITOR_DIR="/data/sftp/sftpn21" # 本地监控根目录
REMOTE_RSYNC_USER="rsync" # 远端rsync服务端配置的用户名
REMOTE_IP="10.12.8.37" # 远端服务器IP
REMOTE_RSYNC_MODULE="lotus_nexchip" # 远端rsync服务端配置的模块名
PASSWORD_FILE="/etc/rsyncd.pass" # rsync密码文件路径
LOG_DIR="/var/log/sftp_sync" # 日志存储目录
SYNC_DELETE="false" # 是否同步删除(false时删除操作仅记录日志)
EXCLUDE_TMP_FILES="true" # 是否过滤临时文件
# -------------------------- 2. 依赖检查(自动检测,无需修改)--------------------------
check_dependency() {
local cmd=$1
if ! command -v $cmd &> /dev/null; then
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 错误:未安装依赖工具 $cmd,请先执行:yum install -y $cmd"
exit 1
fi
}
# 检查核心依赖
check_dependency "inotifywait"
check_dependency "rsync"
# -------------------------- 3. 前置检查(避免同步失败,无需修改)--------------------------
# 检查监控目录是否存在
if [ ! -d "$MONITOR_DIR" ]; then
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 错误:本地监控目录 $MONITOR_DIR 不存在,请先创建!"
exit 1
fi
# 检查密码文件是否存在且权限正确(rsync要求密码文件权限必须为600,否则报错)
if [ ! -f "$PASSWORD_FILE" ]; then
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 错误:rsync密码文件 $PASSWORD_FILE 不存在,请先创建!"
exit 1
fi
if [ "$(stat -c %a "$PASSWORD_FILE")" -ne 600 ]; then
chmod 600 "$PASSWORD_FILE"
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 提示:已自动将密码文件 $PASSWORD_FILE 权限设置为600(rsync要求)"
fi
# 检查远端rsync服务是否可连接(避免启动后无法同步)
check_remote_rsync() {
# 通过rsync命令测试远端模块是否可达(--list-only仅列目录,不实际同步)
rsync -avz --list-only "${REMOTE_RSYNC_USER}@${REMOTE_IP}::${REMOTE_RSYNC_MODULE}/" \
--password-file="${PASSWORD_FILE}" &> /dev/null
if [ $? -ne 0 ]; then
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 错误:远端rsync服务不可达!请检查:"
echo "1. 远端IP(${REMOTE_IP})是否正确;2. 远端rsync模块(${REMOTE_RSYNC_MODULE})是否存在;"
echo "3. 密码文件(${PASSWORD_FILE})中的密码是否正确;4. 远端rsync服务是否已启动"
exit 1
fi
}
check_remote_rsync
# 创建日志目录(不存在则创建)
if [ ! -d "$LOG_DIR" ]; then
mkdir -p "$LOG_DIR"
chmod 755 "$LOG_DIR"
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 提示:已自动创建日志目录 $LOG_DIR"
fi
# 日志文件(按日期拆分,避免单文件过大)
LOG_FILE="${LOG_DIR}/sftp_sync_daemon_$(date +%Y%m%d).log"
# -------------------------- 4. 日志函数(无需修改)--------------------------
write_log() {
local level=$1 # 日志级别(INFO/ERROR/WARN)
local message=$2 # 日志内容
echo "[$(date +'%Y-%m-%d %H:%M:%S')] [${level}] ${message}" >> "$LOG_FILE"
# 终端也打印关键日志(方便调试)
echo "[$(date +'%Y-%m-%d %H:%M:%S')] [${level}] ${message}"
}
# -------------------------- 5. 同步函数(核心:同步触发的文件+目录)--------------------------
sync_file_or_dir() {
local event=$1 # 触发事件(如CREATE/MODIFY/MOVED_TO/DELETE)
local path=$2 # 触发事件的文件/目录路径
# 逻辑1:删除事件处理(SYNC_DELETE=false时仅记录日志)
if [ "$SYNC_DELETE" = "false" ] && [[ "$event" =~ DELETE ]]; then
local target_type=$(if [ -d "$path" ]; then echo "目录"; else echo "文件"; fi)
write_log "INFO" "检测到${target_type}删除操作(SYNC_DELETE=false),仅记录日志:事件=${event},路径=${path}"
return 0
fi
# 逻辑2:过滤临时文件(仅过滤文件,不影响目录)
if [ "$EXCLUDE_TMP_FILES" = "true" ] && [ -f "$path" ]; then
if [[ "$path" =~ \.(swp|tmp|swx|bak|~)$ ]]; then
write_log "INFO" "跳过临时文件:${path}(事件:${event})"
return 0
fi
fi
# 核心:区分文件/目录,计算相对路径和远端目标
local target_type=$(if [ -d "$path" ]; then echo "目录"; else echo "文件"; fi)
local relative_path="${path#$MONITOR_DIR/}" # 本地相对路径(保留目录结构)
local remote_target="${REMOTE_RSYNC_USER}@${REMOTE_IP}::${REMOTE_RSYNC_MODULE}/${relative_path}"
# 构建rsync参数(目录需保留-r递归,文件无需;统一保留权限/时间配置)
local rsync_args="-avz --progress --no-group --no-owner --no-times --no-perms"
# 仅当SYNC_DELETE=true且为删除事件时,添加--delete(删除远端对应文件/目录)
if [ "$SYNC_DELETE" = "true" ] && [[ "$event" =~ DELETE ]]; then
rsync_args="${rsync_args} --delete"
write_log "WARN" "开启同步删除模式,将删除远端${target_type}:${remote_target}"
fi
# 执行同步(文件/目录通用逻辑,rsync自动适配)
write_log "INFO" "开始同步${target_type}:事件=${event},本地路径=${path}"
write_log "INFO" "执行命令:rsync ${rsync_args} '${path}' '${remote_target}' --password-file='${PASSWORD_FILE}'"
# 捕获同步日志(stdout+stderr)
rsync ${rsync_args} "${path}" "${remote_target}" \
--password-file="${PASSWORD_FILE}" &>> "$LOG_FILE"
# 判断同步结果
if [ $? -eq 0 ]; then
write_log "INFO" "${target_type}同步成功:远端目标=${remote_target}"
else
write_log "ERROR" "${target_type}同步失败:请查看日志文件 ${LOG_FILE} 中的详细错误信息"
fi
}
# -------------------------- 6. 监控主逻辑(保留目录事件,监控文件+目录)--------------------------
write_log "INFO" "==================== SFTP同步服务启动 ===================="
write_log "INFO" "本地监控目录:${MONITOR_DIR}"
write_log "INFO" "远端rsync模块:${REMOTE_RSYNC_USER}@${REMOTE_IP}::${REMOTE_RSYNC_MODULE}/"
write_log "INFO" "密码文件路径:${PASSWORD_FILE}"
write_log "INFO" "日志文件路径:${LOG_FILE}"
write_log "INFO" "同步模式:同步监控到的文件(创建/修改)和目录(创建/修改)"
write_log "INFO" "同步删除模式:$(if [ "$SYNC_DELETE" = "true" ]; then echo "开启"; else echo "关闭(删除操作仅记录日志)"; fi)"
write_log "INFO" "---------------------------------------------------------"
# 启动inotifywait监控(保留目录事件,不排除任何文件/目录)
inotifywait -mrq \
--format '%e %w%f' \
-e create,modify,moved_to,delete \
"$MONITOR_DIR" | while read -r event path; do
# 调用同步函数(处理文件+目录事件)
sync_file_or_dir "$event" "$path"
done
封装服务/usr/lib/systemd/system/sftprsync.service
bash
vi /usr/lib/systemd/system/sftprsync.service
bash
[Unit]
# 服务描述(自定义,便于识别)
Description=SFTP Directory Sync Service
# 服务依赖:网络启动后、fs 服务启动后再启动本服务(确保网络和依赖可用)
After=network.target local-fs.target
# 服务文档(可选,指向命令手册)
#Documentation=man:inotifywait(1) man:rsync(1)
[Service]
# 服务类型:simple(前台运行,适合持续监控的脚本)
Type=simple
# 执行服务的用户/组(建议用 root,避免目录、密码文件权限不足)
User=root
Group=root
# 工作目录(脚本所在目录,避免相对路径问题)
WorkingDirectory=/data/zz
# 核心:服务启动命令(脚本绝对路径,必须正确)
ExecStart=/data/zz/zsync.sh
# 服务意外退出时自动重启(确保稳定性,如脚本崩溃后恢复)
Restart=always
# 重启间隔(意外退出后,5秒再重启,避免频繁重启)
RestartSec=5
# 停止服务时,杀死所有子进程(避免 inotifywait 残留)
KillMode=control-group
# 输出重定向到 journalctl(可通过 journalctl 查看服务运行日志)
StandardOutput=journal
StandardError=journal
[Install]
# 服务安装目标:多用户模式下开机自启(适配服务器环境)
WantedBy=multi-user.target