本文目录导读:

设计一个既安全又可靠的日常备份脚本,核心在于:加密传输与存储、自动验证完整性、3-2-1备份策略、以及防御性编程。
安全是指数据不会被泄露或篡改(防止勒索软件同步备份、防止中间人攻击),可靠是指备份真正可用,而不是一堆损坏的文件。
以下是设计这样一个脚本的详细指南,包含核心原则、关键代码片段和完整模板。
核心原则(安全+可靠)
- 3-2-1 备份策略:至少 3 份副本(1份源+2份备份),存储在 2 种不同介质上(如本地硬盘+云存储),1 份在异地(不同城市/机房)。
- 加密(重中之重):备份数据必须端到端加密,且密钥与备份数据分开存放,推荐使用 GPG 或 age。
- 验证机制:备份完成后,必须自动验证(如校验 SHA256 哈希、测试解压或挂载)。
- 传输安全:远程传输使用 SSH over SFTP、Rsync over SSH 或 HTTPS,绝对避免明文 FTP。
- 幂等性与增量备份:设计脚本使其可以反复运行而不产生重复数据,使用
rsync或restic的增量特性。 - 防御性编程:脚本中必须有错误处理、日志记录、锁文件(防止并发)、以及失败告警。
推荐的技术栈
| 功能 | 推荐工具 | 理由 |
|---|---|---|
| 加密存储 | restic |
自带加密、去重、增量、云备份、验证功能,是目前最安全可靠的方案之一。 |
| 文件同步 | rsync |
增量、校验、可通过 SSH 加密传输,适合本地或纯文件同步。 |
| 加密文件 | age / gpg |
轻量级加密,age 更现代更易用。 |
| 完整性校验 | sha256sum |
标准工具,生成哈希文件。 |
| 通知告警 | curl + Telegram/Slack/邮件 |
实时反馈异常。 |
| 调度器 | systemd timer |
比 crontab 更可控,支持依赖、日志、资源限制。 |
建议:如果你的主要诉求是“安全+可靠”,强烈推荐直接使用 restic,它几乎解决了所有痛点,如果必须手动写 rsync,请参考后面的脚本模板。
完整脚本设计(基于 restic,最安全可靠)
以下脚本满足:增量备份、端到端加密、自动清理、完整性检查、错误告警。
环境准备
# 安装 restic wget -qO- https://github.com/restic/restic/releases/latest/download/restic_linux_amd64.bz2 | bzcat > /usr/local/bin/restic chmod +x /usr/local/bin/restic # 初始化仓库(执行一次) export RESTIC_REPOSITORY="/mnt/backup/my-backup" # 本地或远程路径 export RESTIC_PASSWORD="your-very-strong-password-here" # 加密密码,绝对不能写死!使用密钥文件 restic init
备份脚本 (/usr/local/bin/safe-backup.sh)
#!/bin/bash
set -euo pipefail # 严格模式:遇到错误立即退出,变量未定义时报错
# ---------- 配置区域(安全处理) ----------
# 使用密钥文件而不是环境变量,避免进程列表泄露密码
RESTIC_REPOSITORY="/mnt/backup/my-backup"
RESTIC_PASSWORD_FILE="/etc/restic/password.txt" # 权限为 600,只给 root 读
BACKUP_SOURCE="/var/www/html /home /etc"
EXCLUDE_FILE="/etc/restic/exclude.txt" # *.log, cache/
BACKUP_TAG="daily-auto"
LOG_FILE="/var/log/backup-daily.log"
LOCK_FILE="/tmp/backup-daily.lock"
# 通知配置(使用独立 API 密钥)
TELEGRAM_BOT_TOKEN="your-token"
TELEGRAM_CHAT_ID="your-chat-id"
# ---------- 防御性编程:单例模式 ----------
if [ -f "$LOCK_FILE" ]; then
echo "$(date): 另一个备份进程正在运行,跳过本次执行。" >> "$LOG_FILE"
exit 1
fi
trap 'rm -f "$LOCK_FILE"; exit' EXIT INT TERM
touch "$LOCK_FILE"
# ---------- 日志函数 ----------
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
send_alert() {
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-d "chat_id=${TELEGRAM_CHAT_ID}" \
-d "text=⚠️ 备份失败: $1" > /dev/null
}
# ---------- 核心备份逻辑 ----------
log "开始备份: ${BACKUP_SOURCE}"
if restic backup \
--repo "$RESTIC_REPOSITORY" \
--password-file "$RESTIC_PASSWORD_FILE" \
--one-file-system \
--tag "$BACKUP_TAG" \
--exclude-file "$EXCLUDE_FILE" \
--verbose \
$BACKUP_SOURCE 2>&1 | tee -a "$LOG_FILE"; then
log "备份上传完成,开始验证..."
# 可靠性:检查最新快照是否可读
if restic check \
--repo "$RESTIC_REPOSITORY" \
--password-file "$RESTIC_PASSWORD_FILE" \
--read-data-subset=1% 2>&1 | tee -a "$LOG_FILE"; then
log "✅ 完整性验证通过"
else
log "❌ 完整性验证失败,数据可能损坏!"
send_alert "完整性验证失败,请立即检查仓库: ${RESTIC_REPOSITORY}"
exit 2
fi
# 清理策略:保留最近7天每天,最近4周每周,最近6个月每月,其余删除
log "执行清理策略..."
restic forget \
--repo "$RESTIC_REPOSITORY" \
--password-file "$RESTIC_PASSWORD_FILE" \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
--prune 2>&1 | tee -a "$LOG_FILE"
log "✅ 备份成功完成"
else
log "❌ 备份执行失败"
send_alert "备份执行失败,日志在: ${LOG_FILE}"
exit 3 # 退出码 3 表示备份错误
fi
exit 0
如何运行
# 设置密钥文件(绝对安全) echo "your-very-strong-password-here" > /etc/restic/password.txt chmod 600 /etc/restic/password.txt chown root:root /etc/restic/password.txt # 授予执行权限 chmod +x /usr/local/bin/safe-backup.sh # 测试运行(手动) bash /usr/local/bin/safe-backup.sh
设置为定时任务 (推荐 systemd timer)
创建服务文件 /etc/systemd/system/backup-daily.service:
[Unit] Description=Daily Safe Backup After=network.target [Service] Type=oneshot ExecStart=/usr/local/bin/safe-backup.sh # 安全限制:禁止网络访问?根据需要调整 PrivateTmp=true ProtectSystem=full ReadWritePaths=/var/log
创建定时器文件 /etc/systemd/system/backup-daily.timer:
[Unit] Description=Daily Backup Timer Requires=backup-daily.service [Timer] OnCalendar=daily RandomizedDelaySec=1h # 随机延迟,避免所有任务同时启动 Persistent=true [Install] WantedBy=timers.target
激活定时器:
systemctl daemon-reload systemctl enable --now backup-daily.timer
如果必须使用 rsync(带加密的同步方案)
rsync 本身不加密,需要配合 SSH 或 GPG 使用,以下是一个安全+可靠的示例:
#!/bin/bash
set -euo pipefail
REMOTE_HOST="backup-server.example.com"
REMOTE_USER="backupuser"
LOCAL_SOURCE="/home/user/Documents"
REMOTE_DEST="/backup/incremental/"
SSH_PORT=2222 # 非默认端口
SNAPSHOT_DIR="/snapshots/backup_$(date +%Y%m%d_%H%M%S)"
LATEST_LINK="/snapshots/latest_backup"
# 1. 创建日期后缀的增量备份目录(硬链接方案,节省空间)
mkdir -p "$SNAPSHOT_DIR"
# 2. 使用 rsync 通过 SSH,启用压缩、校验、硬链接
rsync -avz \
--link-dest="$LATEST_LINK" \ # 与上次备份建立硬链接,节省空间但保持完整目录
--delete \ # 删除源中不再存在的文件
--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r \
--log-file="/var/log/rsync_backup.log" \
-e "ssh -p ${SSH_PORT} -i /root/.ssh/backup_key" \
"$LOCAL_SOURCE" \
"${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DEST}${SNAPSHOT_DIR}/"
# 3. 更新最新链接
ssh -p "$SSH_PORT" -i /root/.ssh/backup_key "${REMOTE_USER}@${REMOTE_HOST}" \
"rm -f ${REMOTE_DEST}${LATEST_LINK}; ln -s ${SNAPSHOT_DIR} ${REMOTE_DEST}${LATEST_LINK}"
# 4. 验证:使用校验和的子集
echo "Verifying checksums..."
cd "$LOCAL_SOURCE"
find . -type f -name "*.txt" -exec sha256sum {} \; > /tmp/checksums_local.txt
ssh -p "$SSH_PORT" -i /root/.ssh/backup_key "${REMOTE_USER}@${REMOTE_HOST}" \
"cd ${REMOTE_DEST}${SNAPSHOT_DIR} && sha256sum -c /tmp/checksums_local.txt"
echo "✅ Checksum verification passed."
# 5. 清理:保留最近30天的快照
ssh -p "$SSH_PORT" -i /root/.ssh/backup_key "${REMOTE_USER}@${REMOTE_HOST}" \
"find ${REMOTE_DEST} -maxdepth 1 -type d -name 'backup_*' -mtime +30 -exec rm -rf {} \;"
注意:这种做法的安全性取决于 SSH 密钥的权限管理,如果远程服务器被入侵,所有数据都会暴露,因此强烈推荐使用 restic。
最佳实践总结
| 安全性措施 | 实现方式 |
|---|---|
| 加密 | 使用 restic 或 age/gpg 加密,密钥文件权限 600 |
| 传输加密 | SSH (SFTP)、Rsync over SSH、HTTPS |
| 防止篡改 | 定期对备份仓库执行 restic check 或 sha256sum |
| 防勒索软件 | 备份目标不能自动挂载且可写(使用 WORM 媒体或云对象存储不可变版本) |
| 防误删 | 配置 restic forget --keep-daily 7 自动清理,留有余地 |
| 记录审计 | 日志文件定期轮转,记录每次操作的退出码和时间 |
| 告警 | 失败后通过 Telegram/邮件/Webhook 通知,退出码用于监控系统(如 Prometheus) |
也是最重要的:定期进行恢复演练。 每季度至少一次从备份中恢复到一个临时环境并验证数据完整性,没有经过验证的备份,等于没有备份。