What you will learn
Ship a backup script that archives a directory tree, records a checksum manifest, and applies a tiered retention policy (keep all dailies for a week, one weekly for a month, one monthly for a year) instead of a single flat cutoff.
Before you begin
You need tar, sha256sum, and enough scratch space for one full archive. Test restoration before trusting this in production — an unverified backup is a guess, not a backup.
1. Take a consistent, checksummed snapshot
#!/usr/bin/env bash
set -Eeuo pipefail
SRC_DIR="/opt/myapp/data"
VAULT_DIR="/backups/myapp"
STAMP="$(date -u +%Y%m%d-%H%M%S)"
ARCHIVE="${VAULT_DIR}/daily/myapp-${STAMP}.tar.gz"
mkdir -p "${VAULT_DIR}/daily" "${VAULT_DIR}/weekly" "${VAULT_DIR}/monthly"
tar -C "$(dirname "$SRC_DIR")" -czf "$ARCHIVE" "$(basename "$SRC_DIR")"
sha256sum "$ARCHIVE" > "${ARCHIVE}.sha256"
Running tar with -C and a relative basename keeps absolute paths out of the archive, so it can be restored under a different root later.
2. Promote copies into the weekly/monthly tiers
DOW="$(date -u +%u)" # 1 = Monday
DOM="$(date -u +%d)"
if [[ "$DOW" == "7" ]]; then
cp -- "$ARCHIVE" "${VAULT_DIR}/weekly/"
cp -- "${ARCHIVE}.sha256" "${VAULT_DIR}/weekly/"
fi
if [[ "$DOM" == "01" ]]; then
cp -- "$ARCHIVE" "${VAULT_DIR}/monthly/"
cp -- "${ARCHIVE}.sha256" "${VAULT_DIR}/monthly/"
fi
Promotion is a plain cp, not a move — the daily tier still expires it on schedule below, independent of the copy living on in a coarser tier.
3. Expire each tier on its own horizon
prune_tier() {
local dir="$1" days="$2"
find "$dir" -maxdepth 1 -name '*.tar.gz*' -mtime +"$days" -delete
}
prune_tier "${VAULT_DIR}/daily" 7
prune_tier "${VAULT_DIR}/weekly" 31
prune_tier "${VAULT_DIR}/monthly" 366
echo "backup complete: $ARCHIVE"
Example output
backup complete: /backups/myapp/daily/myapp-20260919-030000.tar.gz
Verify the result
sha256sum -c myapp-20260919-030000.tar.gz.sha256 must report OK. Extract into a scratch directory and diff a sample of files against the source to catch silent tar corruption.
Troubleshooting
If the monthly tier never fills, confirm the cron job actually runs on the 1st in the timezone your date -u assumes — mixing local and UTC dates is the most common cause of tiers silently never promoting.
Recovery and next steps
Pair this with backup-restore-verification-job so retention isn't just "files exist" but "files restore cleanly," and route failures through webhook-alert-dispatcher.