#!/bin/bash # Time-stamp: <2025-03-10 19:33:49 nomad> # Michel Le Cocq # # Licence Art Libre 1.3 (LAL 1.3) # upsnapz.sh # Script to upgrade your system install on zfs root with zfs boot menu (only Debian for the moment). # # refresh apt # zfs snapshot # apt upgrade # apt autoremove # zfs clone promote # check zfs boot menu release # check zfs pool status # tunable variable # /!\ il faut ecrire ce fichier dans le vol bootfs sinon ca a peu d'interet LOG_FILE=/root/upsnapz.log # Dependencies : # - gh # - full sudo permission # +------------------------------------------------------------------------------------------------+ # To do or not : # # - il n'y a plus la sortie couleur sur stdout # - regarder si un nouveau kernel existe et si oui faire ce qu'il y a a faire # uname -r # - verifier que les noms des zroot/ROOT/vol sont conformes au format attendu # - verfier que 'gh auth status' fonctionne avant de l'utiliser (auth) # - ameliorer l'integration de : apt full-upgrade # la c'est un peu brouillon # - souci avec tee : desactivation temporaire le temps de trouver une alternative # Configuration file '/etc/mime.types' # ==> Modified (by you or by a script) since installation. # ==> Package distributor has shipped an updated version. # What would you like to do about it ? Your options are: # Y or I : install the package maintainer's version # N or O : keep your currently-installed version # D : show the differences between the versions # Z : start a shell to examine the situation # The default action is to keep your current version. # *** mime.types (Y/I/N/O/D/Z) [default=N] ? # N # +------------------------------------------------------------------------------------------------+ function showsnap { zfs list -o name,used,usedbysnapshots -r zroot/ROOT -s creation | grep -v '^zxroot/ROOT .*' > /tmp/upsnap.tmp for line in $(cat /tmp/upsnap.tmp | grep zroot | cut -d' ' -f1) do nbr=$(zfs list -H -t snapshot -r $line | wc -l) z=$(echo $line | cut -d'/' -f3) sed -i "/$z/ s/$/ $nbr/" /tmp/upsnap.tmp done sed -i "/USEDSNAP/ s/$/ NBRSNAP/" /tmp/upsnap.tmp cat /tmp/upsnap.tmp rm /tmp/upsnap.* } function cleansnap { printf '\nClean snapshot.\n' printf 'Current available bootfs:\n' showsnap oldest=$(zfs list -H -o name -r zroot/ROOT -s creation | grep -v '^zroot/ROOT$' | head -n 1) oldestdate=$(echo $oldest | cut -d "_" -f 3- | tr '_' '-') current=$(zpool get -H bootfs zroot -o value) printf '\ncurrent bootfs: %s\n' $current printf 'oldest bootfs: %s\n' $oldest printf '\nzroot/ROOT status : \n' zfs list -o used,available,quota,usedbychildren zroot/ROOT printf '\nsnapshot to clean older than %s:\n' $oldestdate zfs list -H -o name -t snapshot -s creation -r $current | grep $oldestdate -B10000 | grep -v $oldestdate if [ $? -ne 1 ] then printf '\nDo snapshots cleanup ? : %s [Y-n]' $sugsname read docleanup if [ -z $dopromote ] then dopromote='y' fi if [[ "$dopromote" == "y" ]] then for z in $(zfs list -H -o name -t snapshot -s creation -r $current | grep $oldestdate -B10000 | grep -v $oldestdate) do printf 'zfs destroy %s\n' $z sudo zfs destroy $z done fi usedbysnapshots=$(zfs list -H -o usedbysnapshots $current) printf '\nused by snapshot : %s\n' $usedbysnapshots else printf '\tno snapshot older than %s\n' $oldestdate fi } function check_zpool_status { # check zpool status if it's upgradable printf 'Check ZFS pool status:\n' e=$(zpool status | grep action | grep 'zpool upgrade') if [ $? -eq 0 ] then printf 'One or more ZFS pool can be upgraded\n' zpool status printf 'In case of ZFS Root be carefull that your bootloader is up to date and compatible with this new ZFS feature.\n' else printf 'every things ok for ZFS pool.\n' fi } function check_ZFS_BOOT_MENU { # check last zfs boot menu release # compare if we are on last release # pas tout à fait satisfait de cette fonction ! printf '\nCheck ZFS_BOOT_MENU:\n' PA="https://get.zfsbootmenu.org/sha256.txt" echo " Hit:zfsbootmenu "$PA curl -s -o /tmp/sha256.txt -L $PA e=$(grep $(sha256sum /boot/efi/EFI/ZBM/VMLINUZ.EFI | awk '{print $1}') /tmp/sha256.txt | awk -F"[()]" '{print $2}' ) echo -n " Installed Release : " e=$(echo $e | awk -F'-' '{print $4}') if [ -z $e ] then echo " /!\ sha256sum of ZBM/VMLINUZ.EFI" echo " not in sync with : "$PA echo ' avaible release :' cat /tmp/sha256.txt | grep EFI | grep release | awk -F"[()]" '{print $2}' else echo $e # pas tout a fait satisfait de cette methode avec gh # force a s'authentifier sur github ! dpkg -l gh &> /dev/null if [ $? -eq 0 ] then # ici souci ! si non authentifié sur github ne "fonctionne pas" f=$(gh release list --repo https://github.com/zbm-dev/zfsbootmenu | grep Latest | awk -F' ' '{print $4}') if [ "$e" != "$f" ] then echo " /!\ Installed Release and lastest are not same !"$PA printf ' Latest Release : ' echo $f fi fi fi rm /tmp/sha256.txt } function askpromote() { # ask if we wanted to promote that last snapshot # promote last bootfs snapshot to a new bootfs env # suggest name for new bootfs env # clone promote activate new bootfs # ask to reboot to new bootfs snapname=$1 bootfs=$2 parents=$(zfs list -o name | grep $bootfs | awk 'BEGIN{FS=OFS="/"}{NF--; print}') printf '\nclone and promote before upgrade %s or not \nhost will reboot to new bootfs : [Y-n]' $bootfs@$snapname read dopromote if [ -z $dopromote ] then dopromote='y' echo y fi if [[ "$dopromote" == "y" ]] then printf '\nactual bootfs : %s\n' $bootfs printf 'other bootfs :\n' $bootfs zfs list -o name,creation -r $parents -s creation | grep -v ^$parents$ e=$(lsb_release -is) f=$(date +%b) sugsname="${e,,}_"$(lsb_release -cs)"_"$(date +%Y)"_"$(date +%m)"_"$(date +%d) zfs list -H -o name | grep $sugsname if [ $? -eq 0 ] then sugsname=$sugsname"_"$(date +%Y-%m-%d-%H%M%S) fi printf '\nname suggestion for new zpool bootfs : %s [Y-n]' $sugsname read namepros if [ -z $namepros ] then echo y namepros='y' fi if [[ "$namepros" == "n" ]] then printf 'new bootfs name :' read sugsname if [ $(zfs list -H -o name | grep $sugsname) -eq 0 ] then sugsname=$newname_$(date +%Y-%m-%d-%H%M%S) printf '%s already exist I siggest : %s\n' fi printf 'name suggestion : %s\n' $sugsname fi sudo zfs clone $bootfs@$snapname $parents/$sugsname sudo zfs promote $parents/$sugsname sudo zfs set -u mountpoint=/ $parents/$sugsname sudo zfs set canmount=noauto $parents/$sugsname sudo zpool set bootfs=$parents/$sugsname zroot printf '%s will reboot to new bootfs %s in 5s\nHit Ctrl^c to abort' $(hostname) $sugsname printf "\nRun again current script $(basename $0) after reboot.\n" echo -n '.'; sleep 1; echo -n '.'; sleep 1; echo -n '.'; sleep 1; echo -n '.'; sleep 1; echo -n '.';sleep 1; echo -n '.';sleep 1; echo -n '.' sudo reboot else echo no fi } function askautoclean() { # ask to autoremove and zfs snapshot before printf '\napt-get -s autoremove\n' if [ $(apt-get -s autoremove | sed -n '/The following packages will be REMOVED:/,/^[0-9+] upgraded, [0-9+] newly installed, [0-9+] to remove and [0-9+] not upgraded.$/p' | wc -l) -gt 0 ] then apt-get -s autoremove | sed -n '/The following packages will be REMOVED:/,/^[0-9+] upgraded, [0-9+] newly installed, [0-9+] to remove and [0-9+] not upgraded.$/p' printf '\n%s\n' 'Do we run an autoremove ? : [Y-n]' read autoclean if [ -z $autoclean ] then echo y autoclean='y' fi if [[ "$autoclean" == "y" ]] then printf 'before autoremove : ' asksnap sudo apt-get -y autoremove else echo fi fi sudo apt autoclean echo } function asksnap() { # print creation info of last bootfs snapshot # ask if we want a snapshot of bootfs # snapshot bootfs bootfs=$(zpool get bootfs -o value -H zroot) l=$(zfs list -H -t snapshot -o creation,name -S creation $(zpool get bootfs -o value -H zroot) | head -n1) printf 'last snapshot : \n%s\n' "$l" printf '%s <- now\n' "$(date '+%a %b %d %H:%M %Y')" printf 'snapshot bootfs %s or not ? : [Y-n]' $bootfs read dosnap if [ -z $dosnap ] then echo y dosnap='y' fi if [[ "$dosnap" == "y" ]] then snapname=$(date +%Y-%m-%d-%H%M%S) sudo zfs snapshot $bootfs@$snapname #sudo date +%Y_%m_%d-%H:%M > $LOG_FILE zfs list -H -o name $bootfs@$snapname # | tee -a $LOG_FILE askpromote $snapname $bootfs else echo no fi } function snapchange { # check/show snap change/refresh dpkg -l snapd &> /dev/null if [ $? -eq 0 ] then printf '%s\n' 'snap refresh && snap changes' sudo snap refresh snap changes fi } function askupgrade() { # if there were upgrade ask for upgrade or not # ask for making a snapshot # run apt upgrade # else # ask for making a snapshot printf '\n%s\n' 'apt list --upgradable' #| tee /tmp/upsnapz-upgradable apt list --upgradable #| tee -a /tmp/upsnapz-upgradable printf '\nUpgrade or not : [Y-n]' read answer if [ -z $answer ] then echo y answer='y' fi if [[ "$answer" == "y" ]] then if [ $1 -eq 1 ] then echo '\nAbove packages need full upgrade.\nRun full-upgrade :' \ # | tee -a /tmp/upsnapz-upgradable sudo apt -y full-upgrade # | tee -a /tmp/upsnapz-upgradable elif [ $1 -eq 0 ] then printf 'before upgrade : ' asksnap printf '%s\n' 'apt -y upgrade' sudo apt -y upgrade # | tee -a /tmp/upsnapz-upgradable fi #sudo cat /tmp/upsnapz-upgradable >> $LOG_FILE else echo no fi } function checkupgrade() { # run apt update printf '%s\n' 'apt update' sudo apt update if [ $(apt list --upgradable 2>/dev/null | wc -l) -gt 1 ] then askupgrade 0 sudo apt update 2>/dev/null if [ $(apt list --upgradable 2>/dev/null | wc -l) -gt 1 ] then askupgrade 1 fi echo asksnap fi } function usage() { echo "Usage: $(basename $0) [-h]" echo "-b : run only check zfs boot menu" echo "-s : run only show zfs snap" echo "-S : ron only snanpshot bootfs" } while getopts 'hbsS' OPTION; do case "$OPTION" in h) usage exit 0 ;; b) check_ZFS_BOOT_MENU echo exit 0 ;; s) showsnap echo exit 0 ;; S) asksnap echo exit 0 ;; ?) usage exit 1 ;; esac done checkupgrade askautoclean snapchange if [ -e /tmp/upsnapz-upgradable ]; then rm /tmp/upsnapz-upgradable; fi echo check_zpool_status check_ZFS_BOOT_MENU echo zpool status cleansnap printf '\nNothing else to do.\n'