homeserver/backup.sh

108 lines
3.1 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
set -Eeuo pipefail
shopt -s inherit_errexit nullglob
# -----------------------------------------------------------------------------
# CONFIGURATION — adjust to taste
# -----------------------------------------------------------------------------
readonly RETENTION_DAYS=7
readonly BACKUP_BASE=/media/extension/backup/ubuntu
readonly MOUNT_POINT=/media/extension
# timestamp format — no spaces
#readonly NOW=$(date +'%Y-%m-%d_%H-%M-%S')
readonly NOW=$(date +'%Y-%m-%d')
readonly THIS_RUN=${BACKUP_BASE}/${NOW}
readonly LATEST=${BACKUP_BASE}/latest
# directories (or files) to back up:
declare -a SOURCE_ITEMS=(
"/home/oster/server"
"/home/oster/photo-lib"
)
# rsync exclusions — add your own
declare -a EXCLUDES=(
--exclude='.cache'
--exclude='node_modules'
--exclude='*.tmp'
)
# rsync niceties
RSYNC_OPTS=(
-aAXHv # archive, preserve ACLs/xattrs, human-readable, verbose
--delete # mirror deletions
--partial # keep partials if interrupted
--stats # give stats at end
"${EXCLUDES[@]}"
)
# Dry-run? set to "--dry-run" to test without writing
DRY_RUN=""
# -----------------------------------------------------------------------------
# LOGGING & LOCKING / CLEANUP
# -----------------------------------------------------------------------------
log() { echo "[$(date +'%F %T')] $*"; }
die() { log "FATAL: $*"; exit 1; }
lockfile="${BACKUP_BASE}/.backup.lock"
#trap 'rm -rf "$lockfile"' EXIT
#if ! touch "$lockfile" 2>/dev/null; then
# die "Another backup is running (lockfile exists)."
#fi
# -----------------------------------------------------------------------------
# FUNCTIONS
# -----------------------------------------------------------------------------
check_mount() {
if ! mountpoint -q "$MOUNT_POINT"; then
die "Backup mountpoint $MOUNT_POINT is not mounted."
fi
}
rotate_old() {
log "Rotating backups older than ${RETENTION_DAYS} days."
find "$BACKUP_BASE" -maxdepth 1 -mindepth 1 -type d \
! -name 'latest' ! -name "$(basename "$THIS_RUN")" \
-mtime +${RETENTION_DAYS} \
-print -exec rm -rf {} \;
}
# -----------------------------------------------------------------------------
# MAIN
# -----------------------------------------------------------------------------
log "=== Starting backup run: $NOW ==="
check_mount
mkdir -p "$BACKUP_BASE"
mkdir -p "$THIS_RUN"
# Determine if we can do a hardlink-incremental snapshot
if [ -d "$LATEST" ]; then
LINK_DEST="--link-dest=$(readlink -f $LATEST)"
log "Using linkdest for incremental snapshot → $LATEST"
else
LINK_DEST=""
log "No previous backup; doing full snapshot."
fi
for src in "${SOURCE_ITEMS[@]}"; do
base=$(basename "$src")
dst="${THIS_RUN}/${base}"
log "Backing up $src$dst"
nice -n 10 ionice -c2 -n7 \
rsync "${RSYNC_OPTS[@]}" $LINK_DEST $DRY_RUN \
"$src"/ "$dst"/
done
# update the "latest" symlink atomically
tmp_latest="${BACKUP_BASE}/.latest.tmp"
rm -f "$tmp_latest"
ln -sfn "$(basename "$THIS_RUN")" "$tmp_latest"
mv -T "$tmp_latest" "$LATEST"
rotate_old
log "=== Backup completed successfully ==="