Last active
November 28, 2025 21:46
-
-
Save ebal/43066e21ecd26a55250f837c8d8d5c66 to your computer and use it in GitHub Desktop.
BTRFS: Automatic Snapshots Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| set -e # Exit immediately if a command fails | |
| set -u # Treat unset variables as an error | |
| set -o pipefail # Catch pipeline errors | |
| ## ebal, Mon, 07 Nov 2022 08:49:37 +0200 | |
| ## ebal, Fri, 28 Nov 2025 23:41:22 +0200 | |
| ## @daily /usr/local/bin/btrfs_subvolume_snapshot.sh | |
| # Configuration | |
| # paths MUST end with '/' | |
| btrfs_paths=("/home/" "/opt/") | |
| # Snapshot directory should be on the same disk for each btrfs path | |
| btrfs_snap_dir=".Snapshot" | |
| # approximately 3m (once per day) | |
| keep_snapshots=21 | |
| # Generate timestamp - eg., 20240615_121530 | |
| timestamp=$(date +%Y%m%d_%H%M%S) | |
| # hash for unique log directory per day - e.g., 2024/06/15 | |
| yymmdd=$(date +%Y/%m/%d) | |
| # Log file setup | |
| logdir="/var/log/btrfsSnapshot/${yymmdd}" | |
| logfile="${logdir}/btrfsSnapshot_${timestamp}.log" | |
| # Create log directory | |
| mkdir -p "${logdir}" | |
| mkdir -p "${btrfs_snap_dir}" | |
| # Log start | |
| echo -e "[$(date -R)] Starting Btrfs Snapshot Process" | tee -a "${logfile}" | |
| for btrfs_path in "${btrfs_paths[@]}"; do | |
| # Echo line delimeter | |
| echo -e "\n= = = = = " | |
| BASENAME=$(basename "${btrfs_path}") | |
| # List btrfs subvolumes command | |
| btrfs_list="btrfs subvolume list -a -s -r --sort=+gen -t" | |
| ## How many Snapshots are there? | |
| list_btrfs_snap=$(${btrfs_list} "${btrfs_path}" | { grep -i "${BASENAME}" || true; } | wc -l) | |
| echo -e "\nFound ${list_btrfs_snap} btrfs subvolume snapshots for ${btrfs_path}" | tee -a "${logfile}" | |
| ## Get oldest rootfs btrfs snapshot and delete if necessary | |
| while [ "$list_btrfs_snap" -gt "$keep_snapshots" ]; do | |
| # Don't store the command as a string, execute it directly | |
| prev_btrfs_snap=$(${btrfs_list} "${btrfs_path}" | grep -i -m1 "${BASENAME}" | cut -f1 || true) | |
| echo -e "Found previous btrfs ${BASENAME} snapshot ID: ${prev_btrfs_snap}" | tee -a "${logfile}" | |
| # Check if we found a snapshot | |
| if [ -z "${prev_btrfs_snap}" ]; then | |
| echo "Error: Could not find snapshot to delete for ${BASENAME}" | tee -a "${logfile}" | |
| break | |
| fi | |
| ## Delete a btrfs snapshot by their subvolume id | |
| btrfs subvolume delete --subvolid "${prev_btrfs_snap}" "${btrfs_path}" | tee -a "${logfile}" | |
| # Update list of snapshots after deletion | |
| list_btrfs_snap=$(${btrfs_list} "${btrfs_path}" | grep -c -i "${BASENAME}" || echo "0") | |
| done | |
| ## Create a new read-only btrfs snapshot | |
| mkdir -p "${btrfs_path}/${btrfs_snap_dir}/${BASENAME}/${yymmdd}/" | |
| btrfs subvolume snapshot -r "${btrfs_path}" "${btrfs_path}/${btrfs_snap_dir}/${BASENAME}/${yymmdd}/${timestamp}" | tee -a "${logfile}" | |
| done | |
| # Echo line delimeter | |
| echo -e "\n= = = = = " | |
| echo -e "\n$(df -h -x tmpfs -x overlay -x devtmpfs -x efivarfs | column -t)\n" | tee -a "${logfile}" | |
| echo -e "[$(date -R)] Ending Btrfs Snapshot Process\n" | tee -a "${logfile}" | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment