#!/usr/bin/env bash ####################################################################################################################### ## ## Script to full update the system ## ## 0. Pre checks ## 1. Generate new boot environment (BE) ## 2. Update and Ansible highstate ## 3. Clean up ## ####################################################################################################################### ####################################################################################################################### ## Definitions ####################################################################################################################### readonly LOCK_FILE="/tmp/systemupdate.lock" readonly TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S") readonly BTRFS_ROOT="/btrfs" readonly CURRENT_SUBVOLUME=$(LC_ALL=C btrfs sub show / | LC_ALL=C grep 'Name' | cut -d: -f2 | awk '{$1=$1};1') readonly NEW_SUBVOLUME="@root_${TIMESTAMP}" readonly MOUNTPOINT='/mnt' readonly EFI_DISK=$(findmnt -T /efi -o SOURCE | tail -n 1) readonly ROOT_DISK=$(findmnt / -o SOURCE | cut -d"[" -f1 | tail -n 1) readonly BE_HISTORY_COUNT=5 readonly SYSUPGRADE="$1" readonly NEW_NOVOS_RELEASE="$2" readonly NEW_ALPINE_RELEASE="v$3" ####################################################################################################################### ## Errorhandling ####################################################################################################################### #---------------------------------------------------------------------------------------------------------------------- # systemupdate failed #---------------------------------------------------------------------------------------------------------------------- systemupdateFailed() { echo "" echo "┌──────────────────────────────────────────┐" echo "│ FAILED => clean up │" echo "└──────────────────────────────────────────┘" subtaskTitle "Unmount BE if mounted" unmountMountpoint subtaskTitle "Remove BE" removeBEFromTimestamp ${TIMESTAMP} rm -f ${LOCK_FILE} subtaskTitle "Finished with exit code 1" exit 1 } # catch ^C and other signals and clean up trap "echo -e '\n=> Interrupted with CTRL+C' >&2; systemupdateFailed" SIGINT SIGHUP SIGTERM SIGABRT ####################################################################################################################### ## Helper Functions ####################################################################################################################### #---------------------------------------------------------------------------------------------------------------------- # Subtask title output #---------------------------------------------------------------------------------------------------------------------- subtaskTitle() { echo -e "\n=> $1" } #---------------------------------------------------------------------------------------------------------------------- # Unmount ${MOUNTPOINT} #---------------------------------------------------------------------------------------------------------------------- unmountMountpoint() { # if mountpoint exists -> umount [[ $(findmnt -M "${MOUNTPOINT}") ]] && umount -R "${MOUNTPOINT}" } #---------------------------------------------------------------------------------------------------------------------- # Recursive subvolume delete #---------------------------------------------------------------------------------------------------------------------- btrfsSubDelRecursive() { btrfs sub list -o "${BTRFS_ROOT}/${1}" | cut -d " " -f 9 | while read i; do btrfsSubDelRecursive "$i" done btrfs sub del "${BTRFS_ROOT}/${1}" } #---------------------------------------------------------------------------------------------------------------------- # Remove BE from timestamp #---------------------------------------------------------------------------------------------------------------------- removeBEFromTimestamp() { # remove all subvolume with this timestamp for f in $(btrfs sub list -o /btrfs | cut -d " " -f 9 | grep "@root"); do if [[ "$f" =~ "$1" ]]; then btrfsSubDelRecursive "$f" fi done } ####################################################################################################################### ## Main ####################################################################################################################### echo "┌──────────────────────────────────────────┐" echo "│ 0. Pre checks │" echo "└──────────────────────────────────────────┘" subtaskTitle "Check if another systemupgrade is in progress" if [ -f ${LOCK_FILE} ]; then echo "[ERROR] Another systemupgrade is in progress (lockfile: ${LOCK_FILE}) => exit" >&2 exit 1 fi subtaskTitle "Check if ${MOUNTPOINT} exists" if [ ! -d ${MOUNTPOINT} ]; then mkdir -p "${MOUNTPOINT}" fi subtaskTitle "Check if ${MOUNTPOINT} is already a mountpoint" if [[ $(findmnt -M "${MOUNTPOINT}") ]]; then echo "[ERROR] ${MOUNTPOINT} is already a mountpoint => exit" >&2 exit 1 fi subtaskTitle "Checks finished and update can start" # Create lock file touch ${LOCK_FILE} || systemupdateFailed echo "" echo "┌──────────────────────────────────────────┐" echo "│ 1. Generate new boot environment (BE) │" echo "└──────────────────────────────────────────┘" subtaskTitle "Create snapshot of current running system" btrfs subvolume snapshot / ${BTRFS_ROOT}/${NEW_SUBVOLUME} || systemupdateFailed subtaskTitle "Mount new BE to ${MOUNTPOINT}" mount -o noatime,nodiratime,discard=async,space_cache=v2,subvol="${NEW_SUBVOLUME}" "${ROOT_DISK}" "${MOUNTPOINT}" || systemupdateFailed mount -o noatime,nodiratime,discard=async,space_cache=v2,subvol=@home "${ROOT_DISK}" "${MOUNTPOINT}/home" || systemupdateFailed mount -o noatime,nodiratime,discard=async,space_cache=v2,subvol=@podman "${ROOT_DISK}" "${MOUNTPOINT}/opt/podman" || systemupdateFailed mount -o noatime,nodiratime,discard=async,space_cache=v2,subvol=@mysql "${ROOT_DISK}" "${MOUNTPOINT}/var/lib/mysql" || systemupdateFailed mount -o noatime,nodiratime,discard=async,space_cache=v2,subvol=/ "${ROOT_DISK}" "${MOUNTPOINT}/btrfs" || systemupdateFailed mount -o nodev,nosuid,noexec "${EFI_DISK}" "${MOUNTPOINT}/efi" || systemupdateFailed mount -t proc /proc "${MOUNTPOINT}/proc/" || systemupdateFailed mount -t sysfs /sys "${MOUNTPOINT}/sys/" || systemupdateFailed mount -o bind /sys/firmware/efi/efivars "${MOUNTPOINT}/sys/firmware/efi/efivars/" || systemupdateFailed mount -o bind /dev "${MOUNTPOINT}/dev/" || systemupdateFailed mount -o bind /run "${MOUNTPOINT}/run/" || systemupdateFailed subtaskTitle "New BE mounted" echo "" echo "┌──────────────────────────────────────────┐" echo "│ 2. Update and Ansible highstate │" echo "└──────────────────────────────────────────┘" subtaskTitle "Update Ansible playbook" chroot "${MOUNTPOINT}" /bin/bash -c "git -C /srv/ansible/playbooks pull" || systemupdateFailed if $SYSUPGRADE; then subtaskTitle "Update release information" chroot "${MOUNTPOINT}" /bin/bash -c "grep -E '(main|community)' /etc/apk/repositories | sed \"s|$(yq .ungrouped.vars.alpine_version /srv/ansible/inventory.yml)|$NEW_ALPINE_RELEASE|\" > /etc/apk/repositories.new" chroot "${MOUNTPOINT}" /bin/bash -c "mv /etc/apk/repositories.new /etc/apk/repositories" chroot "${MOUNTPOINT}" /bin/bash -c 'yq -i .ungrouped.vars.release_version='$NEW_NOVOS_RELEASE' /srv/ansible/inventory.yml' chroot "${MOUNTPOINT}" /bin/bash -c 'yq -i .ungrouped.vars.alpine_version=\"'$NEW_ALPINE_RELEASE'\" /srv/ansible/inventory.yml' fi subtaskTitle "Update bootloader configs" chroot "${MOUNTPOINT}" /bin/bash -c "ansible-playbook /srv/ansible/playbooks/system/bootloader.ansible.yml" >/dev/null || systemupdateFailed subtaskTitle "Alpine repositories & keyring update" chroot "${MOUNTPOINT}" /bin/bash -c "apk update" || systemupdateFailed subtaskTitle "Alpine packages update" chroot "${MOUNTPOINT}" /bin/bash -c "apk upgrade" || systemupdateFailed subtaskTitle "Ansible highstate" chroot "${MOUNTPOINT}" /bin/bash -c "ansible-playbook /srv/ansible/playbooks/top.ansible.yml" >/dev/null || systemupdateFailed subtaskTitle "Generate new initial ramdisk" latest_kernel="$(chroot "${MOUNTPOINT}" /bin/bash -c 'echo $(apk search linux-lts | head -n1 | cut -d- -f3- | sed "s|r||")-lts')" chroot "${MOUNTPOINT}" /bin/bash -c "mkinitfs $latest_kernel" || systemupdateFailed subtaskTitle "Update motd" chroot "${MOUNTPOINT}" /bin/bash -c "/usr/local/noveria/bin/generate_motd" || systemupdateFailed subtaskTitle "Update GRUB" chroot "${MOUNTPOINT}" /bin/bash -c "grub-install --target=x86_64-efi --efi-directory=/efi --bootloader-id=alpine" || systemupdateFailed chroot "${MOUNTPOINT}" /bin/bash -c "/usr/local/noveria/bin/noveriablcgen --noconfirm" || systemupdateFailed subtaskTitle "Update finished" subtaskTitle "Unmount BE" unmountMountpoint echo "" echo "┌──────────────────────────────────────────┐" echo "│ 3. Clean Up │" echo "└──────────────────────────────────────────┘" subtaskTitle "Clean up finished" # Remove lock file rm -f ${LOCK_FILE}