Last active
May 8, 2026 20:15
-
-
Save Ropid/e48b6055c7b0948aaa734e8ef4ba5d37 to your computer and use it in GitHub Desktop.
btrfs balance maintenance 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 | |
| ### Configuration ############################################################## | |
| services_to_stop=( | |
| backup.timer | |
| ) | |
| services_to_wait_for=( | |
| backup.service | |
| ) | |
| ################################################################################ | |
| set -o pipefail | |
| mapfile -t mountpoints < <( | |
| findmnt --types btrfs --nofsroot --noheading --output SOURCE | | |
| sort -u | | |
| while IFS= read -r device; do | |
| findmnt --source "$device" --output TARGET --noheading | head -1 | |
| done | |
| ) | |
| usage() { | |
| cat << EOF | |
| Usage: ${0##*/} [scrub] [MOUNTPOINT [...]] | |
| Runs a sequence of btrfs balance operations. | |
| Filesystems to run on: | |
| $(printf " * %s\n" "${mountpoints[@]}") | |
| Services to stop or wait for: | |
| $(printf " * %s\n" "${services_to_stop[@]}" "${services_to_wait_for[@]}") | |
| Options: | |
| scrub Run a btrfs scrub as well. | |
| EOF | |
| } | |
| for x; do | |
| if [[ $x = "scrub" ]]; then | |
| scrub=1 | |
| elif [[ $x = /* ]]; then | |
| mount_args=1 | |
| else | |
| usage | |
| exit 1 | |
| fi | |
| done | |
| [[ $UID -ne 0 ]] && exec sudo "$0" "$@" | |
| stopped_services=() | |
| restart_services() { | |
| local unit | |
| for unit in "${stopped_services[@]}"; do | |
| if systemctl start "$unit"; then | |
| echo "Started '$unit'." | |
| else | |
| echo "Failed to start '$unit'!" | |
| fi | |
| done | |
| } | |
| trap 'echo -e "\nInterrupted!"; exit 1' INT TERM | |
| trap 'restart_services' EXIT | |
| for unit in "${services_to_stop[@]}"; do | |
| if systemctl status "$unit" &> /dev/null; then | |
| systemctl stop "$unit" | |
| echo "Stopped '$unit'." | |
| stopped_services+=("$unit") | |
| fi | |
| done | |
| for unit in "${services_to_wait_for[@]}"; do | |
| if systemctl status "$unit" &> /dev/null; then | |
| echo -n "Waiting for '$unit' to complete... " | |
| sleep 1 | |
| while systemctl status "$unit" &> /dev/null; do | |
| sleep 1 | |
| done | |
| echo "done." | |
| fi | |
| done | |
| die() { | |
| cat << EOF | |
| +----------------------+ | |
| | There were errors! | | |
| +----------------------+ | |
| EOF | |
| exit 1 | |
| } | |
| msg() { | |
| sed 's/^/ /' | |
| } | |
| for mount in "${mountpoints[@]}"; do | |
| if (( mount_args )); then | |
| skip_mount=1 | |
| for x; do | |
| x="$(sed -r 's/\/{2,}/\//g; s/.\/$//' <<< "$x")" | |
| if [[ $x = $mount ]]; then | |
| skip_mount=0 | |
| fi | |
| done | |
| if (( skip_mount )); then | |
| continue | |
| fi | |
| fi | |
| back="----------------------------------------" | |
| echo "== $mount ${back:${#mount}}" | |
| if (( scrub )); then | |
| echo "> Scrub..." | |
| # progress bar when running in a terminal | |
| if [[ -t 1 ]]; then | |
| while sleep 1; do | |
| btrfs scrub status "$mount" | | |
| perl -n0777e ' | |
| sub quit { print " Cannot read scrub status!\e[K\r"; exit 1; } | |
| my ($bytes, $progress) = /^bytes scrubbed:\s*(\S+)\s+.*?([\d.]+)%/im | |
| or quit; | |
| my ($timeleft) = /^time left:\s*(\S+)/im or quit; | |
| my ($rate) = /^rate:\s*(.*)/im or quit; | |
| $bytes =~ s{(\d+\.\d+)([GMKB])?}{ | |
| !$2 ? sprintf "%.1f ", $1 : sprintf "%.0f %s", $1, $2 }eg; | |
| $rate =~ s{(\d+\.\d+)([MKB])?}{ | |
| !$2 ? sprintf "%.1f ", $1 : sprintf "%.0f %s", $1, $2 }eg; | |
| my $width = 20; | |
| my $bar = int($progress * $width / 100 + 0.5); | |
| printf " ETA: %s [%s>%s] %s, %s\e[K\r", | |
| $timeleft, | |
| "=" x $bar, | |
| " " x ($width - $bar), | |
| $bytes, | |
| $rate; | |
| ' | |
| done & | |
| status=$(btrfs scrub start -B "$mount" 2>&1) | |
| error=$? | |
| kill % | |
| tput cr; tput el # clear line | |
| msg <<< "$status" | |
| (( error )) && die | |
| else | |
| btrfs scrub start -B "$mount" |& msg || die | |
| fi | |
| fi | |
| #for x in 1 5 10 20 30; do | |
| for x in 0 5; do | |
| echo "> Metadata balancing, usage=$x..." | |
| btrfs balance start -musage="$x" "$mount" |& msg || die | |
| done | |
| #for x in 1 5 10 20 30 40 50; do | |
| for x in 0 5 10; do | |
| echo "> Data balancing, usage=$x..." | |
| btrfs balance start -dusage="$x" "$mount" |& msg || die | |
| done | |
| done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment