upsnapz/upsnapz.sh
2025-03-17 11:30:10 +01:00

421 lines
11 KiB
Bash
Executable File

#!/bin/bash
# Time-stamp: <2025-03-17 11:11:18 nomad>
# Michel Le Cocq <lecocq@ipgp.fr>
# <nomad@neuronfarm.net>
# 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=./upsnapz.log
# Dependencies :
# - gh
# - full sudo permission
# +------------------------------------------------------------------------------------------------+
# To do or not :
#
# - 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)
# - regarder pour remplacer gh : Get last git tag from a remote repo without cloning
# https://stackoverflow.com/questions/10649814/get-last-git-tag-from-a-remote-repo-without-cloning
# - ameliorer gestion fichier log ... idee si jamais il y a un snapshot de fait, il faut puger le fichier
# - changer les commandes pour que le fichier de log puisse etre a la racine du BOOTFS
# +------------------------------------------------------------------------------------------------+
output()
{
# usage : output file command
# fonction pour afficher sur la sortie standard et envoyer dans un fichier log
output_file=$1
shift
command=$@
script -aefq $output_file -c "$command"
}
function showsnap
{
# fonction qui affiche le nombre de snap par rapport a chaques bootfs
# - il y a un souci avec les sed dans le for
# - cette fonction ne sert a rien, comme on fait un promote tout les anciens
# snap changent de parents...
# - du coup tout est commenté
zfs list -o name,used,usedbysnapshots -r zroot/ROOT -s creation | grep -v '^zxroot/ROOT .*' > /tmp/upsnap.tmp
# test de fonction output (sans doute a supprimer)
#output $LOG_FILE zfs list -o name,used,usedbysnapshots -r zroot/ROOT -s creation | grep -v '^zxroot/ROOT .*'
# for line in $(cat /tmp/upsnap.tmp | grep zroot | cut -d' ' -f1)
# do
# z=''
# 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
date +%Y_%m_%d-%H:%M > $LOG_FILE
output $LOG_FILE zfs list -H -o name $bootfs@$snapname
output $LOG_FILE 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()
{
# if there were upgrade ask for upgrade or not
# ask for making a snapshot
# run apt upgrade
# else
# ask for making a snapshot
output $LOG_FILE printf '\n%s\n' 'apt list --upgradable'
output $LOG_FILE apt list --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
output $LOG_FILE echo '\nAbove packages need full upgrade.\nRun full-upgrade :'
output $LOG_FILE sudo apt -y full-upgrade
elif [ $1 -eq 0 ]
then
printf 'before upgrade : '
asksnap
output $LOG_FILE printf '%s\n' 'apt -y upgrade'
output $LOG_FILE sudo apt -y upgrade
fi
else
output $LOG_FILE echo 'apt -y upgrade'
output $LOG_FILE echo 'no upgrade ... bye'
exit 1
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
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'