Backup-Retention-Skript
Wer Backups in einen Ordner schreiben lässt, kennt das Problem, dass man die Dateien zwar relativ einfach in jedem Dateisystem anlegen lassen kann, die Bereinigung aber mitunter arbeitsintensiv ist. Das gilt vor allem dann, wenn man nicht einfach alle Dateien, die ein bestimmtes Alter überschreiten, löschen lassen will. Einfaches Löschen von Dateien, die ein bestimmtes Alter überschreiten, ist mit find
relativ einfach zu lösen. Ich stand vor ein paar Wochen vor demselben Problem. Ein Programm hatte über einen längeren Zeitraum hinweg immer wieder Backups geschrieben. Ich wollte diese aber nicht einfach nur löschen lassen, sondern eine bestimmte Löschlogik einsetzen.
Die Dateien der letzten 14 Tage sollten alle vorhanden sein. Danach sollten nur noch die jeweils letzten einer jeden Woche, bis zur 13. Woche vor dem aktuellen Datum (entspricht einem viertel Jahr). Darüber hinaus bis zu einem Zeitraum von einem Jahr vor dem aktuellen Datum von jedem Monat eine Datei.
Ich schrieb mir zur Löschung ein Backup-Retention-Skript, das diese Rahmenbedingungen erfüllt:
#!/bin/bash
# Script:
# Version: 1.0
# Autor: Marco Morath
# Lizenz: Gnu GPLv3 <https://www.gnu.org/licenses/gpl-3.0.html>
# Anwenderhinweis:
# Wird der Parameter dryrun mit übergeben, wird keine Löschung durchgeführt. Es wird nur ermittelt, welche Dateien
# gelöscht werden, wenn das Skript tatsächlich ausgeführt wird.
# Verzeichnis mit den Backups
backup_dir="/mnt/backupfolder"
# Ältere Backups behalten: 14 tägliche, 13 wöchentliche, 12 monatliche
daily_keep=14
weekly_keep=13
monthly_keep=12
# Log-Verzeichnis und -Datei
log_dir="/home/user/logdirectory"
log_file="$log_dir/$(date +%Y-%m-%d)-backup-retention.log"
# Logrotation
log_files=($(ls -1tr $log_dir/*backup-retention*.log))
log_keep=14
# Alte Logdateien löschen
if [ ${#log_files[@]} -gt $log_keep ]; then
for log in "${log_files[@]:0:$((${#log_files[@]} - $log_keep))}"; do
echo "Lösche Logdatei: $log"
rm -f "$log"
done
fi
# Neues Logfile erstellen
touch "$log_file"
# Liste der Backups in umgekehrter Reihenfolge nach Erstellungsdatum (älteste zuerst)
backups=($(ls -1tr --time=ctime $backup_dir))
# Arrays für die zu behaltenden Backups
daily_backups=()
weekly_backups=()
monthly_backups=()
# Datumswerte
today_in_seconds=$(date +%s)
daily_oldest_in_seconds=$(( $today_in_seconds - ( 86400 * $daily_keep ) ))
weekly_oldest_in_seconds=$(( $today_in_seconds - ( 86400 * 7 * $weekly_keep ) ))
monthly_oldest_in_seconds=$(( $today_in_seconds - ( 86400 * 30 * $monthly_keep ) ))
# Funktion, um die Woche eines Datums zu berechnen
get_week_of_year() {
date -d "@$1" +%Y-%V
}
# Funktion, um den Monat eines Datums zu berechnen
get_month_of_year() {
date -d "@$1" +%Y-%m
}
# Backup-Kategorisierung
declare -A weekly_latest
declare -A monthly_latest
for backup in "${backups[@]}"; do
backup_date_seconds=$(stat -c %Y "$backup_dir/$backup")
if [ $backup_date_seconds -ge $daily_oldest_in_seconds ]; then
daily_backups+=($backup)
elif [ $backup_date_seconds -ge $weekly_oldest_in_seconds ]; then
week_of_year=$(get_week_of_year $backup_date_seconds)
if [[ -z "${weekly_latest[$week_of_year]}" || $backup_date_seconds -gt $(stat -c %Y "$backup_dir/${weekly_latest[$week_of_year]}") ]]; then
weekly_latest[$week_of_year]=$backup
fi
elif [ $backup_date_seconds -ge $monthly_oldest_in_seconds ]; then
month_of_year=$(get_month_of_year $backup_date_seconds)
if [[ -z "${monthly_latest[$month_of_year]}" || $backup_date_seconds -gt $(stat -c %Y "$backup_dir/${monthly_latest[$month_of_year]}") ]]; then
monthly_latest[$month_of_year]=$backup
fi
fi
done
# Jüngste wöchentliche und monatliche Backups in Arrays speichern
for backup in "${weekly_latest[@]}"; do
weekly_backups+=($backup)
done
for backup in "${monthly_latest[@]}"; do
monthly_backups+=($backup)
done
# Alle zu behaltenden Backups zusammenführen
keep_backups=("${daily_backups[@]}" "${weekly_backups[@]}" "${monthly_backups[@]}")
# Funktion zum Protokollieren
log() {
echo "$1" | tee -a "$log_file"
}
# Beginn des Vorgangs dokumentieren
log "##### Bereinigung begonnen ( $(date +"%d-%m-%Y %H:%M:%S") ) #####"
# Überprüfen, ob der Parameter "dryrun" gesetzt ist
dryrun=false
if [ "$1" == "dryrun" ]; then
dryrun=true
log "Dry run aktiviert: Es werden keine Backups gelöscht."
fi
# Backups zum Behalten ermitteln
echo "==== Zu behaltende Backups ===="
number=0
for backup in "${backups[@]}"; do
if [[ " ${keep_backups[@]} " =~ " $backup " ]]; then
number=$(($number+1))
if $dryrun; then
log "$number Würde behalten: $backup"
else
log "$number Behalte Backup: $backup"
fi
fi
done
# Backups zum Löschen ermitteln
echo "==== Zu löschende Backups ===="
number=0
for backup in "${backups[@]}"; do
if [[ ! " ${keep_backups[@]} " =~ " $backup " ]]; then
number=$(($number+1))
if $dryrun; then
log "$number Würde löschen: $backup"
else
log "$number Lösche Backup: $backup"
rm -f "$backup_dir/$backup"
fi
fi
done
log "##### Backup-Bereinigung abgeschlossen ( $(date +"%d-%m-%Y %H:%M:%S") ) #####"
Im ersten Abschnitt des Skripts können die Zeiträume der aufzubewahrenden Versionen definiert werden. Ebenso ist der Speicherort der Backups und der Ort für die Logdatei zu definieren.
Danach kann das Skript manuell oder zeitgesteuert über die crontab aufgerufen werden.
Gibt man den Parameter dryrun
mit, wird das Skript ohne Veränderungen an den Dateien durchgeführt und man kann sehen, was bei der Ausführung passieren würde.
Das Skript wurde sorgfältig aufbereitet und selbst getestet. Dennoch können unerwartete oder ungewollte Effekte und damit auch Datenverlust nicht ausgeschlossen werden. Bitte prüft und testet das Skript daher vor der Verwendung. Für Datenverlust wird keine Haftung übernommen!