#!/bin/bash # Time-stamp: <2025-02-26 11:02:08 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 # Depend : # - gh # - full sudo permission # +------------------------------------------------------------------------------------------------+ # To do or not : # # - ajouter zpool upgrade or not # - ajouter question cleanup zroot # - regarder si un nouveau kernel existe et si oui faire ce qu'il y a a faire # - verifier que les noms des zroot/ROOT/vol sont conformes au format attendu # - verfier que 'gh auth status' fonctionne avant de l'utiliser (auth) # # - souci dans clone promote oui non !? # je fais dans la fonction askpromote # sudo zfs clone $bootfs@$snapname $parents/$sugsname # sudo zfs promote $parents/$sugsname # je fais ca pour que : promote clone dataset no longer depend on origin snapshot # # Current available bootfs: # NAME USED USEDSNAP # zroot/ROOT/debian_trixie_2025_02_03 7.58M 0B # zroot/ROOT/debian_trixie_2025_02_08 3.25M 0B # zroot/ROOT/debian_trixie_2025_02_08_2025-02-08-185711 7.57M 0B # zroot/ROOT/debian_trixie_2025-02-08-214321 2.25M 0B # zroot/ROOT/debian_trixie_2025_02_12 7.17M 0B # zroot/ROOT/debian_trixie-rollback_2025_02_12 5.15M 0B # zroot/ROOT/debian_trixie-Rn_2025_02_12 31.9G 21.6G <- fait depuis zfs boot menu # zroot/ROOT/debian_trixie_2025_02_16 2.16M 0B # zroot/ROOT/debian_trixie_2025_02_19 1.90M 0B # zroot/ROOT/debian_trixie_2025_02_22 5.72G 3.20G # # - integrer apt dist-upgrade # # root@nsob:~# apt dist-upgrade # Upgrading: # libnvpair3linux libuutil3linux zfs-dkms zfs-initramfs zfs-zed zfsutils-linux # # Installing dependencies: # libzfs6linux libzpool6linux # # REMOVING: # libzfs4linux libzpool5linux # # Summary: # Upgrading: 6, Installing: 2, Removing: 2, Not Upgrading: 0 # Download size: 4891 kB # Space needed: 538 kB / 27.3 GB available # # +------------------------------------------------------------------------------------------------+ function showsnap { zfs list -o name,used,usedbysnapshots -r zroot/ROOT -s creation | grep -v '^zroot/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 zfs list -H -o name $bootfs@$snapname 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() { # run apt update # if there were upgrade ask for upgrade or not # ask for making a snapshot # run apt upgrade # else # ask for making a snapshot printf '%s\n' 'apt update' sudo apt update if [ $(apt list --upgradable 2>/dev/null | wc -l) -gt 1 ] then printf '\n%s\n' 'apt list --upgradable' apt list --upgradable printf '\nUpgrade or not : [Y-n]' read answer if [ -z $answer ] then echo y answer='y' fi if [[ "$answer" == "y" ]] then printf 'before upgrade : ' asksnap printf '%s\n' 'apt -y upgrade' sudo apt -y upgrade else echo no fi else echo asksnap fi } function usage() { echo "Usage: $(basename $0) [-h]" echo "-b : only check zfs boot menu" echo "-s : only show zfs snap" } while getopts 'hbs' OPTION; do case "$OPTION" in h) usage exit 1 ;; b) check_ZFS_BOOT_MENU echo exit 1 ;; s) showsnap echo exit 1 ;; ?) usage exit 1 ;; esac done askupgrade askautoclean snapchange echo check_zpool_status check_ZFS_BOOT_MENU echo zpool status cleansnap printf '\nNothing else to do.\n'