树莓派搭建NAS之五:数据同步

数据同步

由于使用的是32GU盘作为nas存储盘,用不了几天就会出现磁盘空间被占满的情况,需要将nas中存储的录像记录,同步存储到阿里云盘中,并且删除历史的视频释放空间。

定时任务

直接通过ai写一个shell脚本,定时执行就完事了。给ai提出诉求:

  • 将源端文件存在、目标端不存在的文件进行同步
  • 仅比较文件文件大小,不比较时间和md5值(阿里网盘不允许修改文件时间)
  • 启动定时任务,1小时执行1次

vim /opt/sync_xiaomi_camera.sh 文件:

#!/bin/bash

# =============================================
# 小米摄像头视频同步脚本(带磁盘清理)
# 功能:自动清理源端旧文件 + 同步新文件
# 作者:AI 助手
# =============================================

# -------------------------------
# 配置区
# -------------------------------

# 源目录(小米摄像头存储路径)
SOURCE_DIR="/srv/dev-disk-by-uuid-0987bf77-xxxxx/smb_xiaomi_vidoes/XiaomiCamera_00_xxxxx"

# 目标目录(如阿里云盘挂载点)
DEST_DIR="/mnt/aliyun/XiaomiCamera_00_xxxxx"

# 日志文件
LOG_FILE="/var/log/sync_xiaomi_camera.log"

# 网络检测目标
PING_TARGET="223.5.5.5"
TIMEOUT=5

# 磁盘清理阈值(百分比)
DISK_USAGE_THRESHOLD_HIGH=70    # 超过此值开始清理
DISK_USAGE_THRESHOLD_LOW=50     # 清理到此值以下停止

# -------------------------------
# 日志函数
# -------------------------------

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}

# -------------------------------
# 检查网络连通性
# -------------------------------

check_network() {
    log "INFO: 正在检测网络连通性,目标: $PING_TARGET"

    if command -v ping >/dev/null 2>&1; then
        if ping -c 2 -W $TIMEOUT "$PING_TARGET" >/dev/null 2>&1; then
            log "INFO: 网络连通性检测成功"
            return 0
        else
            log "ERROR: ping 到 $PING_TARGET 失败"
            return 1
        fi
    else
        log "WARNING: 未安装 ping,尝试使用 curl 检测..."
    fi

    if command -v curl >/dev/null 2>&1; then
        if curl -m $TIMEOUT --head --silent --fail "http://alidns.com" >/dev/null 2>&1; then
            log "INFO: curl 检测成功,网络可达"
            return 0
        else
            log "ERROR: curl 检测失败,无法访问外网"
            return 1
        fi
    else
        log "ERROR: 未安装 curl 或 ping,无法检测网络"
        return 1
    fi
}

# -------------------------------
# 检查挂载点
# -------------------------------

check_mounts() {
    local mount1=$(findmnt -n -o TARGET /mnt/aliyun)
    local mount2=$(findmnt -n -o TARGET /srv/dev-disk-by-uuid-0987bf77-xxxxx)

    if [ -z "$mount1" ]; then
        log "ERROR: /mnt/aliyun 未挂载"
        return 1
    fi

    if [ -z "$mount2" ]; then
        log "ERROR: /srv/dev-disk-by-uuid-... 未挂载"
        return 1
    fi

    log "INFO: 所有挂载点已就绪"
    return 0
}

# -------------------------------
# 清理源端磁盘空间(自动删除最老文件)
# -------------------------------

cleanup_source_disk() {
    local mount_point=$(dirname "$SOURCE_DIR")

    if [ ! -d "$mount_point" ]; then
        log "ERROR: 源挂载点不存在: $mount_point"
        return 1
    fi

    # 获取当前磁盘使用率
    local current_usage
    current_usage=$(df -P "$mount_point" | tail -1 | awk '{print $5}' | tr -d '%')

    if ! [[ "$current_usage" =~ ^[0-9]+$ ]]; then
        log "ERROR: 无法获取磁盘使用率"
        return 1
    fi

    log "INFO: 当前源磁盘使用率: ${current_usage}% (阈值: >${DISK_USAGE_THRESHOLD_HIGH}% 开始清理,<${DISK_USAGE_THRESHOLD_LOW}% 停止)"

    if [ $current_usage -le $DISK_USAGE_THRESHOLD_HIGH ]; then
        log "INFO: 磁盘使用率正常,跳过清理"
        return 0
    fi

    log "WARN: 磁盘使用率过高 (${current_usage}% > ${DISK_USAGE_THRESHOLD_HIGH}%),开始清理旧文件..."

    local deleted_count=0

    # 按修改时间升序(最老的在前)删除 .mp4 文件
    while IFS= read -r file; do
        [ ! -f "$file" ] && continue

        local filename=$(basename "$file")
        log "INFO: 删除旧文件: $filename"

        if rm -f "$file"; then
            ((deleted_count++))
            log "INFO: 已删除: $filename"
        else
            log "ERROR: 删除失败: $filename"
            continue
        fi

        # 重新检查磁盘使用率
        current_usage=$(df -P "$mount_point" | tail -1 | awk '{print $5}' | tr -d '%')
        if ! [[ "$current_usage" =~ ^[0-9]+$ ]]; then
            log "ERROR: 获取磁盘使用率失败,停止清理"
            break
        fi

        log "INFO: 删除后磁盘使用率: ${current_usage}%"

        # 如果已降到 50% 以下,停止
        if [ $current_usage -le $DISK_USAGE_THRESHOLD_LOW ]; then
            log "INFO: 磁盘使用率已降至 ${current_usage}% ≤ ${DISK_USAGE_THRESHOLD_LOW}%,停止清理"
            break
        fi

    done < <(find "$SOURCE_DIR" -type f -name "*.mp4" -printf '%T@ %p\n' | sort -n | cut -d' ' -f2-)

    log "INFO: 共清理 $deleted_count 个旧文件"
}

# -------------------------------
# 执行同步(基于文件名+大小)
# -------------------------------

sync_files() {
    if [ ! -d "$SOURCE_DIR" ]; then
        log "ERROR: 源目录不存在: $SOURCE_DIR"
        return 1
    fi

    mkdir -p "$DEST_DIR"

    log "INFO: 开始同步所有 .mp4 文件(基于文件名+大小判断)"
    log "INFO: 源目录: $SOURCE_DIR"
    log "INFO: 目标目录: $DEST_DIR"

    START_TIME=$(date +%s)

    # 核心同步命令:只同步 .mp4,仅根据大小判断是否传输
    OUTPUT=$(rsync -rtv --size-only \
        --include='*.mp4' \
        --exclude='*' \
        --info=progress2,stats2 \
        "$SOURCE_DIR/" "$DEST_DIR/" 2>&1)
    RSYNC_EXIT_CODE=$?

    echo "$OUTPUT" | tee -a "$LOG_FILE"

    END_TIME=$(date +%s)
    DURATION=$((END_TIME - START_TIME))
    HOURS=$((DURATION / 3600))
    MINUTES=$(((DURATION % 3600) / 60))
    SECONDS=$((DURATION % 60))
    ELAPSED_TIME=$(printf "%02d:%02d:%02d" $HOURS $MINUTES $SECONDS)

    FILES_TRANSFERRED=$(echo "$OUTPUT" | grep "Number of files transferred" | awk '{print $4}' || echo 0)
    TOTAL_BYTES=$(echo "$OUTPUT" | grep "Total bytes sent" | awk '{print $4}' || echo 0)

    if [ $RSYNC_EXIT_CODE -eq 0 ]; then
        log "INFO: 同步成功"
    else
        log "ERROR: rsync 执行失败,退出码: $RSYNC_EXIT_CODE"
    fi

    log "INFO: 任务开始时间: $(date -d "@$START_TIME" '+%Y-%m-%d %H:%M:%S')"
    log "INFO: 任务结束时间: $(date -d "@$END_TIME" '+%Y-%m-%d %H:%M:%S')"
    log "INFO: 总耗时: $ELAPSED_TIME"
    log "INFO: 成功同步文件数量: $FILES_TRANSFERRED"
    log "INFO: 传输总大小(字节): $TOTAL_BYTES"
    log "------------------------------------------------------------"
}

# -------------------------------
# 主函数
# -------------------------------

main() {
    log "=== 新的同步任务启动 ==="

    # 1. 清理源端磁盘(如果使用率 >70%)
    cleanup_source_disk

    # 2. 检查网络
    if ! check_network; then
        log "ERROR: 网络不可达,跳过本次同步"
        log "------------------------------------------------------------"
        exit 1
    fi

    # 3. 检查挂载
    if ! check_mounts; then
        log "ERROR: 挂载点未就绪,跳过同步"
        log "------------------------------------------------------------"
        exit 1
    fi

    # 4. 执行同步
    sync_files
}

# -------------------------------
# 创建日志目录并执行
# -------------------------------

LOG_DIR=$(dirname "$LOG_FILE")
[ ! -d "$LOG_DIR" ] && mkdir -p "$LOG_DIR"

main "$@"

添加到crontab中

crontab -e

添加以下任务

0 * * * * bash /opt/sync_xiaomi_camera.sh

查看结果

tail -f /var/log/sync_xiaomi_camera.log

显示结果为:

[2025-10-03 12:40:06] === 新的同步任务启动 ===
[2025-10-03 12:40:06] INFO: 当前源磁盘使用率: 23% (阈值: >70% 开始清理,<50% 停止)
[2025-10-03 12:40:06] INFO: 磁盘使用率正常,跳过清理
[2025-10-03 12:40:06] INFO: 正在检测网络连通性,目标: 223.5.5.5
[2025-10-03 12:40:07] INFO: 网络连通性检测成功
[2025-10-03 12:40:07] INFO: 所有挂载点已就绪
[2025-10-03 12:40:08] INFO: 开始同步所有 .mp4 文件(基于文件名+大小判断)
[2025-10-03 12:40:08] INFO: 源目录: /srv/dev-disk-by-uuid-0987bf77-xxxxx/smb_xiaomi_vidoes/XiaomiCamera_00_xxxxx
[2025-10-03 12:40:08] INFO: 目标目录: /mnt/aliyun/XiaomiCamera_00_xxxxx
sending incremental file list
              0   0%    0.00kB/s    0:00:00 (xfr#0, to-chk=0/45)

Number of files: 45 (reg: 44, dir: 1)
Number of created files: 0
Number of deleted files: 0
Number of regular files transferred: 0
Total file size: 5,905,580,032 bytes
Total transferred file size: 0 bytes
Literal data: 0 bytes
Matched data: 0 bytes
File list size: 0
File list generation time: 0.001 seconds
File list transfer time: 0.000 seconds
Total bytes sent: 2,058
Total bytes received: 148

sent 2,058 bytes  received 148 bytes  4,412.00 bytes/sec
total size is 5,905,580,032  speedup is 2,677,053.50
[2025-10-03 12:40:08] INFO: 同步成功
[2025-10-03 12:40:08] INFO: 任务开始时间: 2025-10-03 12:40:08
[2025-10-03 12:40:08] INFO: 任务结束时间: 2025-10-03 12:40:08
[2025-10-03 12:40:08] INFO: 总耗时: 00:00:00
[2025-10-03 12:40:08] INFO: 成功同步文件数量: 
[2025-10-03 12:40:08] INFO: 传输总大小(字节): 2,058
[2025-10-03 12:40:08] ------------------------------------------------------------