날짜별 디렉터리를 압축해 백업 위치에 두고, 보존 기간이 지난 것을 지우며, 결과를 로그로 남긴다. 스크립트 하나로 끝내되 실패했을 때 그것을 알 수 있어야 한다.
#!/usr/bin/env bash
set -uo pipefail
SRC_ROOT=/data/work
DST_ROOT=/backup
LOG=/var/log/backup/backup.log
RETENTION_DAYS=30
TODAY=$(date +%F)
YESTERDAY=$(date -d 'yesterday' +%F)
log() { printf '%s %s\n' "$(date '+%F %T')" "$*" >> "$LOG"; }
mkdir -p "$DST_ROOT" "$(dirname "$LOG")"
src="${SRC_ROOT}/${YESTERDAY}"
dst="${DST_ROOT}/work_${YESTERDAY}.tar.gz"
if [ ! -d "$src" ]; then
log "SKIP 대상 없음: $src"
exit 0
fi
log "START $src -> $dst"
if tar -czf "${dst}.tmp" -C "$SRC_ROOT" "$YESTERDAY"; then
mv "${dst}.tmp" "$dst"
log "OK $(du -h "$dst" | cut -f1) $dst"
else
rc=$?
rm -f "${dst}.tmp"
log "FAIL tar 실패 rc=${rc} $src"
exit "$rc"
fi
deleted=$(find "$DST_ROOT" -maxdepth 1 -type f -name 'work_*.tar.gz' -mtime "+${RETENTION_DAYS}" -print -delete | wc -l)
log "PRUNE ${deleted}건 삭제 (${RETENTION_DAYS}일 초과)"
log "END"
임시 이름으로 만든 뒤 옮긴다. 압축 도중 실패하거나 중단되면 반쪽짜리 .tar.gz 가 남는다. 그것이 정상 파일 이름을 갖고 있으면 복구할 때 그대로 쓰게 된다. .tmp 로 만들고 성공했을 때만 이름을 바꾼다.
어제 날짜를 쓴다. 새벽에 도는 배치가 date +%F 로 오늘 디렉터리를 잡으면 아직 쌓이는 중인 데이터를 압축한다. 하루 단위로 완결된 데이터를 다루려면 어제를 대상으로 한다.
find ... -delete 는 원본이 아니라 백업 위치에만 건다. -maxdepth 1 과 이름 패턴을 함께 주어 범위를 좁힌다. 처음 적용할 때는 -delete 를 빼고 -print 만으로 무엇이 지워질지 확인한다.
-mtime +30 은 수정 시각 기준이다. 파일을 cp 로 옮기면 시각이 갱신되어 보존 기간이 초기화된다. 보관 위치를 옮길 때는 cp -p 또는 rsync -a 로 시각을 보존한다.
rm -rf 로 원본을 지우지 않는다. 원본을 정리해야 한다면 백업이 성공했는지 확인한 뒤에, 그리고 압축 파일의 무결성을 확인한 뒤에 한다.
tar -tzf "$dst" >/dev/null && rm -rf "$src"
crontab -e
# 매일 23:00
0 23 * * * /usr/local/bin/backup.sh
# 매일 00:05
5 0 * * * /usr/local/bin/backup.sh
# 매주 일요일 02:00
0 2 * * 0 /usr/local/bin/backup.sh
요일은 0 과 7 이 모두 일요일이다.
cron 은 로그인 셸 환경을 쓰지 않는다. PATH 가 짧고 LANG 이 설정되지 않는다. 스크립트 안에서 절대경로를 쓰거나 앞부분에서 PATH 를 명시한다.
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
% 는 cron 에서 개행으로 해석되므로 date +\%F 처럼 이스케이프한다. 스크립트 안에 두면 이 문제가 없다.
즉시 시험하려면 cron 을 기다리지 말고 같은 조건으로 직접 실행한다.
env -i /bin/bash --noprofile --norc /usr/local/bin/backup.sh
tail -20 /var/log/backup/backup.log
ls -lh /backup | head
find /backup -name 'work_*.tar.gz' -mtime +30
로그가 늘어나면 logrotate 에 등록한다.
/var/log/backup/backup.log {
weekly
rotate 8
compress
missingok
notifempty
}