-
-
Save krisutofu/ca252cc4732acb9ae6b7e0f6a1c11b52 to your computer and use it in GitHub Desktop.
Script that safely updates with Pacman without crashes when using btrfs and snapper
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 | |
# This bash script bypasses the crashing problem when using BTRFS and Snapper with Pacman. Useful as long as the Pacman | |
# space computation bug is not fixed. | |
# This script expects only a single mountpoint to be updated, and only the root config of snapper to be used. | |
# `bc` (basic calculator) needs to be installed which did NOT come by default with my Garuda Plasma installation. | |
if [ $(id -u) != 0 ]; then | |
exec sudo -s "$0" "$@"; | |
fi | |
# does not work, it will not show the dialog for unknown reason, same with send-notify | |
true || { | |
notificationCommand=' | |
if (( $exitCode )); then | |
kdialog --warningyesno "System Update: Snapshot cleanup aborted"'\''!'\'' "Still require $(bc -q <<< "$(( requiredSpace - availableSpace )) / 1024^2" ) MiB." --yes-label "pacman -Scc" --no-label "cancel" | |
case $? in | |
0 ) pacman -Scc; exec sudo -s "'$0'" "'$@'" ;; | |
1 ) ;; | |
* ) ;; | |
esac | |
else | |
kdialog --passivepopup "System Update ready"'\''!'\'' | |
fi | |
' | |
trap "$notificationCommand" EXIT # notify user when script finished to take action for pacman | |
} | |
# debugCommand=echo; # if you want to test this script without applying changes | |
updatedMountpoint="/"; | |
snapperconfig="root"; | |
syncronizationPace=$(( 3 )); # each synchronization takes long, this tells the script how many snapshots to delete at once before syncronization of BTRFS space | |
maxSnapshotPercentToRemove=$(( 50 )); # set here, how many oldest snapshots from the entire `snapper list` may be deleted at most by this script | |
minSnapshotsPreserved=$(( 8 )); # do not delete more snapshots if the number is less equal to this limit | |
maxSnapshotPercentToRemove=$(( $maxSnapshotPercentToRemove >= 100 ? 100 : $maxSnapshotPercentToRemove )); | |
computeSpaceExpression() { | |
echo "$*" | sed -E -e 's/GiB/*1024^3/g' -e 's/GB?/*1000^3/g' -e 's/MiB/*1024^2/g' -e 's/MB?/*1000^2/g' -e 's/KiB/*1024/g' -e 's/KB?/*1000/g' | bc -q; | |
} | |
if [ -n "${debugCommand+used}" ]; then | |
requiredSpaceThreshold='0'; | |
else | |
requiredSpaceThreshold="$(computeSpaceExpression "200MiB")"; # safety gap margin in Bytes; minimum additional required space that must be available | |
fi | |
# min x, minimum x, at least x, require x, required x, > x, >= x is accepted as argument | |
if minArgument=$(echo "${*}" | pcre2grep -i -o1 '(?<=^|\s)(?:min(?:imum)|at least|required?|>=?) (\S+)' ) \ | |
&& minArgument=$(computeSpaceExpression "$minArgument") \ | |
&& minArgument=${minArgument%.*} \ | |
&& (( $minArgument > ${requiredSpaceThreshold} )); | |
then | |
requiredSpaceThreshold=$minArgument; | |
fi | |
computeAvailableSpace() { | |
if [ -n "${debugCommand+used}" ]; then echo $(( $RANDOM + ${requiredSpaceThreshold} )); return 0; fi | |
# Using grep and cut on program output is fragile in general but there is no easy usable alternative in shell languages. | |
computeSpaceExpression "$(btrfs filesystem df "$updatedMountpoint" | grep 'Data, single:' | cut -d' ' -f3- | sed -E 's/total=(.*?),.*? used=(.*?)/\1-\2/')"; | |
} | |
computeRequiredSpace() { | |
if [ -n "${debugCommand+used}" ]; then echo $(( $RANDOM + ${requiredSpaceThreshold} )); return 0; fi | |
# alternative to pacman -Qu: checkupdates (slow!) | |
computeSpaceExpression "$(pacman -Qu | cut -d' ' -f1 | xargs pacman -Si | grep 'Installed Size' | cut -d':' -f2 | tr '\n' '+' | tr ',' '.') 0"; | |
} | |
isMoreThanSnapshotLimit() (( $(wc -w <<< "$*") > ${minSnapshotsPreserved} )) | |
pacman -Sy > /dev/null; # should be automatically called when the system is updated | |
${debugCommand} btrfs subvolume sync "$updatedMountpoint"; # force update of BTRFS storage info | |
availableSpace=$(computeAvailableSpace); | |
availableSpace=${availableSpace%.*}; # availableSpace converted to int | |
requiredSpace=$(computeRequiredSpace); | |
requiredSpace=$((${requiredSpace%.*} + $requiredSpaceThreshold)); | |
if (( $availableSpace >= $requiredSpace )); then | |
echo "enough space available ${availableSpace} = $(bc -q <<<"${availableSpace} / 1024^2") MiB > required space ${requiredSpace} = $(bc -q <<<"${requiredSpace} / 1024^2") MiB"; | |
# all set, go to exit | |
else | |
snapshotNumbers=$(snapper list | grep '^ \?[[:digit:]]' | sed -E -e 's;^\s*;;g' | cut -d' ' -f1 | tr '\n' ' '); | |
toRemove=$(( $(wc -w <<< "$snapshotNumbers") * $maxSnapshotPercentToRemove / 100 )); | |
toRemove=$(( ($toRemove <= 0 && $maxSnapshotPercentToRemove > 0) ? 1 : $toRemove )); # as long as maxSnapshotPercentToRemove is set, do remove at least one snapshot | |
# remove groups of contiguous snapshots in steps until sufficient memory is available | |
while (( $availableSpace < $requiredSpace )) && (( --toRemove >= 0 )) && isMoreThanSnapshotLimit "$snapshotNumbers"; | |
do | |
echo "removing snapshot ${snapshotNumbers%% *}"; | |
${debugCommand} snapper -c "$snapperconfig" delete ${snapshotNumbers%% *}; | |
snapshotNumbers=${snapshotNumbers#* }; | |
if (( ++removedCount % $syncronizationPace != 0 )) && (( toRemove >= 0 )) && isMoreThanSnapshotLimit "$snapshotNumbers"; then | |
continue | |
fi | |
${debugCommand} btrfs subvolume sync "$updatedMountpoint"; | |
availableSpace=$(computeAvailableSpace); | |
availableSpace=${availableSpace%.*}; | |
done | |
if (( $availableSpace < $requiredSpace )); then | |
echo -e "Not enough space ${availableSpace} for system update ${requiredSpace}"'!!' | |
if (( $maxSnapshotPercentToRemove > 0 )) && isMoreThanSnapshotLimit "$snapshotNumbers"; then | |
echo "Run this script again to remove more snapshots"; | |
elif (( isPaccacheCleanupAllowed )); then | |
echo "Still require $(bc -q <<< "$(( requiredSpace - availableSpace )) / 1024^2" ) MiB." 2>&1 | |
# kdialog --warningyesno "System Update: Snapshot cleanup aborted"'!' "Still require $(bc -q <<< "$(( requiredSpace - availableSpace )) / 1024^2" ) MiB." --yes-label "pacman -Scc" --no-label "cancel" # not working | |
# case $? in | |
# 0 ) pacman -Scc; exec sudo -s "'$0'" "'$@'" ;; | |
# 1 ) ;; | |
# * ) ;; | |
# esac | |
fi | |
exit ${exitCode:=1}; | |
fi | |
echo -e "Enough space ${availableSpace} = $(bc -q <<<"${availableSpace} / 1024^2") MiB for system update ${requiredSpace} = $(bc -q <<<"${requiredSpace} / 1024^2") MiB available"'!' | |
fi | |
echo -e "Manually check the total size as depicted by Pacman"'!'"\n--------------------"; | |
# kdialog --passivepopup "System Update ready"'!' # does not show anything and stops execution | |
exit ${exitCode:=0}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Fetching the snapshotnumbers has not worked properly anymore. Either it's because of reaching snapshot 1000 or bash was updated with breaking changes. Therefore, the initial whitespace of each line in the table output is removed in the extraction of the snapshot numbers.