#!/bin/bash # # Copyright (c) 2020 Gaƫl PORTAY # # SPDX-License-Identifier: LGPL-2.1-or-later # # https://www.freedesktop.org/software/systemd/man/systemd.offline-updates.html set -e set -u set -o pipefail VERSION="1.1" opts=(--noconfirm --noprogressbar --cachedir /var/lib/system-update) while [[ "$#" -ne 0 ]] do if [[ "$1" =~ (-h|--help) ]] then echo "Usage: ${0##*/} [--now] [--force] [--] [pacman-extra-flags]" exit 0 elif [[ "$1" =~ (-V|--version) ]] then echo "$VERSION" exit elif [[ "$1" == "--now" ]] then now=true elif [[ "$1" == "--force" ]] then force=true elif [[ "$1" == "--" ]] then shift break else echo "$1: Invalid argument" >&2 exit 1 fi shift done if [[ -z "${now:-}" ]] then if [[ ! -e /system-update ]] || [[ "${force:-}" ]] then echo ":: Preparing offline system updates..." mkdir -p /var/lib/system-update pacman "${opts[@]}" --downloadonly -Syu "$@" mapfile -t pkgs < <(find /var/lib/system-update -name "*.pkg.tar.*") if [[ "${#pkgs[@]}" -eq 0 ]] then exit 0 fi ln -sf /var/lib/system-update /system-update if [[ "$#" -gt 0 ]] then echo "$@" >/var/lib/system-update/.flags fi fi echo ":: Please reboot the machine to trigger the offline system updates." if [[ -e /var/lib/system-update/.flags ]] then cat /var/lib/system-update/.flags fi exit fi # As the first step, an update service should check if the /system-update # symlink points to the location used by that update service. In case it does # not exist or points to a different location, the service must exit without # error. if [[ ! -L /system-update ]] || [[ "$(readlink -f /system-update)" != "/var/lib/system-update" ]] then echo ":: No offline system updates prepared." exit 0 fi # Make sure to remove the /system-update symlink as early as possible in the # update script to avoid reboot loops in case the update fails. rm -f /system-update progress() { if plymouth --ping 2>/dev/null then plymouth system-update --progress="$1" fi echo -n "$1 " } if [[ -r /var/lib/system-update/.flags ]] then read -r -a extraflags < <(cat /var/lib/system-update/.flags) if [[ "${#extraflags[@]}" -gt 0 ]] then set -- "$@" "${extraflags[@]}" fi rm -f /var/lib/system-update/.flags fi # If your script succeeds you should trigger the reboot in your own code, for # example by invoking logind's Reboot() call or calling systemctl reboot. See # logind dbus API for details. # shellcheck disable=SC2154 trap 'ret="$?"; trap - 0; if [ "$ret" -eq 0 ]; then systemctl reboot; return "$ret"; fi' 0 INT if plymouth --ping 2>/dev/null then plymouth change-mode --system-upgrade plymouth system-update --progress=0 fi i=0 step= while read -r -a words do # empty line: skip it! if [[ "${#words[@]}" -eq 0 ]] then continue fi # the pacman steps: # :: Synchronizing package databases... # :: Starting full system upgrade... # :: Proceed with installation? [Y/n] # :: Retrieving packages... # :: Running pre-transaction hooks... # :: Processing package changes... # :: Running post-transaction hooks... if [[ "${words[0]}" =~ :: ]] then i=0 step="${words[*]:1}" if [[ "$step" == "Synchronizing package databases..." ]] || [[ "$step" == "Retrieving packages..." ]] || [[ "$step" == "Running pre-transaction hooks..." ]] || [[ "$step" == "Running post-transaction hooks..." ]] then echo "$step" fi continue fi # nothing to do: exit early! if [[ "${words[0]}" == "there is nothing to do" ]] then echo "Nothing to do, rebooting..." break fi # :: Starting full system upgrade... # resolving dependencies... # looking for conflicting packages... # # Packages (4) pkg1 pkg2 pkg3 pkg4 # # Total Download Size: ##.## MiB # Total Installed Size: ##.## MiB # Net Upgrade Size: ##.## MiB # if [[ "${words[0]}" == "Packages" ]] then # Packages (4) (...) if [[ "${words[1]}" =~ ^\([0-9]+\)$ ]] then count="${words[1]:1:$((${#words[1]}-2))}" fi # :: Retrieving packages... # downloading pkg... # :: Processing package changes... # upgrading pkg... elif [[ "${words[0]}" =~ ^(downloading|installing|upgrading)$ ]] && [[ "$step" != "Synchronizing package databases..." ]] then i="$((i+1))" processing="${words[0]:0:1}" processing="${processing^^}" processing+="${words[0]:1}" if [[ -z "${count:-}" ]] then echo "$processing ${words[1]}" continue elif [[ "${words[0]}" == downloading ]] then echo "$processing ${words[1]} ($i/$count)" continue fi progress "$((20+(60*i)/count))" echo "$processing ${words[1]} ($i/$count)" continue # :: Running pre-transaction hooks... # ( #/##) Doing something... # (##/##) Doing another thing... # :: Running post-transaction hooks... # (#/#) Doing a last thing... elif [[ "$step" == "Running pre-transaction hooks..." ]] || [[ "$step" == "Running post-transaction hooks..." ]] then # ( #/##) (...): remove the parasite space. if [[ "${words[0]}" == "(" ]] then words[1]="${words[0]}${words[1]}" words[0]= words=("${words[@]:1}") fi if ! [[ "${words[0]}" =~ ^\([0-9]+/[0-9]+\)$ ]] then echo "${words[*]}" continue fi if [[ "$step" == "Running pre-transaction hooks..." ]] then base=0 else base=80 fi val="${words[0]:1:$((${#words[0]}-2))}" progress "$((base+20*${val// /}))" echo "${words[*]:1} ${words[0]}" continue fi done < <(pacman "${opts[@]}" -Su "$@") rm -f /var/lib/system-update/*.pkg.tar.* if plymouth --ping 2>/dev/null then plymouth change-mode --boot-up plymouth display-message --text="Rebooting..." fi echo "Rebooting..." sleep 5s