Linux 自动备份脚本

2020-04-18

写了个用于 Linux 备份的脚本。

Bash 脚本

其中几个用于配置的常量:

  • EXCLUDE 所有不备份的绝对路径,以空格分隔,路径中不能包含空格等特殊字符

  • FILE_NAME_PREFIX 备份的文件名前缀

  • FILE_NAME 备份的文件名,默认是文件名前缀+时间

  • TEMP_PATH 临时文件存放路径

  • BACKUP_PATH 备份文件存放路径

  • BACKUP_MAX 保留备份文件的最大数目,超过时将清理旧备份

  • COPY_TO_REMOTE 是否复制到远程路径

  • REMOTE_PATH 需要保存的远程路径,例如使用 Rclone 挂载的路径

linux-backup-script/backup.sh
#!/bin/bash

set -o pipefail

EXCLUDE="/boot /dev /lost+found /media /mnt /proc /run /sys /tmp /var/cache /var/tmp"

FILE_NAME_PREFIX="armbian-backup"
FILE_NAME="${FILE_NAME_PREFIX}_$(date +%Y%m%d-%H%M)"

TEMP_PATH="/var/tmp"
BACKUP_PATH="/mnt/sdcard/armbian-backup"
BACKUP_MAX=3
COPY_TO_REMOTE=true
REMOTE_PATH="/mnt/onedrive/armbian-backup"

log() {
  echo "$(date +%F-%T)" "[email protected]"
}

[ "$(whoami)" != root ] && log "Please run as root" && exit 1

[ -d $TEMP_PATH ] || rm -f $TEMP_PATH && mkdir -p $TEMP_PATH && chmod 777 $TEMP_PATH
[ -d $BACKUP_PATH ] || rm -f $BACKUP_PATH && mkdir -p $BACKUP_PATH

check_space() {
  # KiB
  local avail
  avail=$(df -k "$1" | sed -n "2p" | awk '{print $4}')
  # reserve 512MiB
  [ $((avail - $2)) -gt $((1024 * 512)) ]
}

EXCLUDE_OPT=""
for f in $EXCLUDE; do EXCLUDE_OPT="$EXCLUDE_OPT--exclude=$f "; done
# shellcheck disable=SC2086
size=$(du -sh $EXCLUDE_OPT / | awk '{print $1}')
log "Total size is about: $size"
# shellcheck disable=SC2086
size=$(du -sk $EXCLUDE_OPT / | awk '{print $1}')

tmp_path=$TEMP_PATH
check_space $tmp_path "$size" || tmp_path=$BACKUP_PATH
if ! check_space $tmp_path "$size"; then
  log "Space not enough:"
  df -h $tmp_path
  exit 1
fi

TARBALL="$tmp_path/$FILE_NAME.tar"
log "Archiving to: $TARBALL"
# shellcheck disable=SC2086
tar -cpf - $EXCLUDE_OPT / | pv >"$TARBALL"
if [ $? -lt 2 ]; then
  log "Successfully saved to:" && ls -lh "$TARBALL"
else
  log "Error happened while archieving"
  rm -f "$TARBALL"
  exit 1
fi

echo
BACKUP="$BACKUP_PATH/$FILE_NAME.tar.xz"
log "Compressing to: $BACKUP"
size=$(du -sk "$TARBALL" | awk '{print $1}')
if dd if="$TARBALL" bs=8M status=none | pv -s "$size"K | xz -T0 >"$BACKUP"; then
  log "Successfully saved to:" && ls -lh "$BACKUP"
  log "Removing $TARBALL" && rm -f "$TARBALL"
else
  rm -f "$TARBALL"
  rm -f "$BACKUP"
  log "Failed to compress" && exit 1
fi

count=$(find $BACKUP_PATH -mindepth 1 -maxdepth 1 -type f -name "$FILE_NAME_PREFIX*.tar.xz" | wc -l)
for f in "$BACKUP_PATH"/"$FILE_NAME_PREFIX"*.tar.xz; do
  [ "$count" -le $BACKUP_MAX ] && break
  log "Removing old backup: $f" && rm -f "$f"
  ((count -= 1))
done

[ $COPY_TO_REMOTE != true ] && exit 0

echo
[ -d $REMOTE_PATH ] || rm -f $REMOTE_PATH && mkdir -p $REMOTE_PATH
size=$(du -sk "$BACKUP" | awk '{print $1}')
if ! check_space $REMOTE_PATH "$size"; then
  log "Space not enough:"
  df -h $REMOTE_PATH
  exit 1
fi

BACKUP_REMOTE="$REMOTE_PATH/$FILE_NAME.tar.xz"
log "Copying to remote: $BACKUP_REMOTE"
if pv "$BACKUP" >"$BACKUP_REMOTE"; then
  log "Successfully saved to:" && ls -lh "$BACKUP_REMOTE"
else
  log "Error happened while copying"
  rm -f "$BACKUP_REMOTE"
  exit 1
fi

将脚本放到 /root/bin/ 下并添加执行权限。

总体过程是,先用 tar 打包存放到 TEMP_PATH,再使用 xz 压缩为最终备份文件并存放到 BACKUP_PATH,如果超出最大数则清理旧备份,最后复制到远程目录。

systemd 定时任务

将以下两个文件存放到 /lib/systemd/system/ 下:

linux-backup-script/backup.service
[Unit]
Description=Run backup

[Service]
ExecStart=/bin/bash -c "/root/bin/backup.sh >>/var/log/backup.log 2>&1 && echo >>/var/log/backup.log"
linux-backup-script/backup.timer
[Unit]
Description=Run backup weekly

[Timer]
OnCalendar=Mon, 03:00
AccuracySec=1h
Persistent=true

[Install]
WantedBy=timers.target

这里设置为每周一 03:00 开始备份,AccuracySec=1h 表示可以有一小时的容错,这是为了减少唤醒 CPU。

运行并使其开机启动:

$ sudo systemctl daemon-reload
$ sudo systemctl start backup.timer
$ sudo systemctl enable backup.timer

systemctl list-timers 可以查看所有定时任务。

LinuxBashShell
本站的全部文字在署名-非商业性使用-相同方式共享 4.0 国际协议之条款下提供。

CSAPP Data Lab

缩减 IMG 镜像文件