116 lines
4.6 KiB
Bash
Executable File
116 lines
4.6 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# omarchy:summary=Retry a system package update that hit a conflict
|
|
# omarchy:hidden=true
|
|
|
|
# Internal to omarchy-update-system-pkgs. Not a command to run by hand: it acts
|
|
# on a pacman error report, and an old or hand-written one would move live files
|
|
# out of the way for an upgrade that is not happening.
|
|
#
|
|
# Paths written by a script rather than installed by a package -- an earlier
|
|
# release's installer, an in-place edit -- belong to nobody, and pacman refuses
|
|
# to install over a file it doesn't own. This clears them and runs the upgrade
|
|
# again.
|
|
#
|
|
# Packages that conflict with each other are the other blocked upgrade, and that
|
|
# one is a decision rather than a cleanup, so it goes back to the person
|
|
# watching the update.
|
|
|
|
set -e
|
|
|
|
[[ ${OMARCHY_UPDATE_CONFLICT:-} == 1 ]] || {
|
|
echo "omarchy-update-system-pkgs-when-conflicted runs as part of omarchy update" >&2
|
|
exit 1
|
|
}
|
|
|
|
errors=${1:?usage: omarchy-update-system-pkgs-when-conflicted <pacman-error-report>}
|
|
|
|
# Where a file pacman is taking over gets moved, mirroring its full path. Kept
|
|
# out of the directory it came from: SDDM reads every file in sddm.conf.d
|
|
# whatever its extension, and systemd-sleep runs every executable in
|
|
# system-sleep, so a copy left beside the original would still be live.
|
|
replaced=${OMARCHY_REPLACED_DIR:-/var/lib/omarchy/replaced}
|
|
|
|
# Anything moved that the upgrade didn't end up installing goes back, whether
|
|
# the retry failed or the move loop itself was interrupted partway. Nothing here
|
|
# should leave configuration inactive that was live when it started.
|
|
#
|
|
# -e follows symlinks, so a dangling one reads as absent. -L catches those: a
|
|
# link pacman installed, and a relative link that no longer resolves from inside
|
|
# the quarantine.
|
|
moved=()
|
|
restore_moved() {
|
|
local path restorable=()
|
|
|
|
for path in "${moved[@]}"; do
|
|
if [[ ! -e $path && ! -L $path ]] && [[ -e $replaced$path || -L $replaced$path ]]; then
|
|
restorable+=("$path")
|
|
fi
|
|
done
|
|
((${#restorable[@]})) || return 0
|
|
|
|
echo -e "\e[33m\nPutting back what the upgrade didn't take:\e[0m"
|
|
for path in "${restorable[@]}"; do
|
|
sudo mv -T "$replaced$path" "$path"
|
|
echo " $path"
|
|
done
|
|
}
|
|
|
|
# exec'd into, so the caller's EXIT trap never ran and the report is still here.
|
|
# Cleaning it up is this script's job from now on.
|
|
trap 'restore_moved; rm -f "$errors"' EXIT
|
|
trap 'exit 1' INT TERM
|
|
|
|
# Pacman answers its own conflict question with No under --noconfirm, so one
|
|
# retired package can stop every upgrade after it. Which package to drop is a
|
|
# decision -- an upstream split retires one, but so does a package the user
|
|
# picked on purpose -- so put the question to whoever started the update rather
|
|
# than guess at it. Pacman printed what conflicts just above.
|
|
if grep -q 'unresolvable package conflicts detected' "$errors"; then
|
|
# -y promised not to ask anything, and without a terminal there is nowhere to
|
|
# ask; either way pacman would sit on a prompt nobody answers. An upgrade that
|
|
# isn't running --noconfirm puts its questions on stderr and reads the answers
|
|
# from stdin, so those are the two that have to be a terminal.
|
|
if [[ ${OMARCHY_UPDATE_UNATTENDED:-} == 1 || ! -t 0 || ! -t 2 ]]; then
|
|
echo -e "\e[33m\nThis upgrade needs an answer. Run omarchy update interactively to give it.\e[0m" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "\e[33m\nA package conflict stopped this upgrade. Running it again so you can answer:\e[0m"
|
|
OMARCHY_UPDATE_INTERACTIVE=1 omarchy-update-system-pkgs
|
|
exit 0
|
|
fi
|
|
|
|
# Paths one of these packages installs that pacman doesn't own. Moving them out
|
|
# of the way is what lets the upgrade through, and works on a leftover directory
|
|
# as well as a file.
|
|
#
|
|
# "pkg: /path exists in filesystem" is unowned; the same line plus "(owned by
|
|
# x)" is not, so the end anchor takes only ours. -Qo re-checks the live database
|
|
# before anything moves.
|
|
mapfile -t leftovers < <(
|
|
grep -oP '^omarchy(-dev|-settings|-settings-dev)?: \K.+(?= exists in filesystem$)' "$errors" |
|
|
while IFS= read -r path; do pacman -Qo "$path" &>/dev/null || echo "$path"; done
|
|
)
|
|
((${#leftovers[@]})) || exit 1
|
|
|
|
# All of them or none: moving a subset leaves the retry blocked by the rest, and
|
|
# the moved files inactive for nothing.
|
|
((${#leftovers[@]} == $(grep -c ' exists in filesystem' "$errors"))) || exit 1
|
|
|
|
echo -e "\e[33m\nTaking over files pacman doesn't own yet:\e[0m"
|
|
for path in "${leftovers[@]}"; do
|
|
sudo mkdir -p "$replaced${path%/*}"
|
|
# -T so an existing directory at the destination is replaced, not moved into.
|
|
sudo mv -T --backup=numbered "$path" "$replaced$path"
|
|
moved+=("$path")
|
|
echo " $path -> $replaced$path"
|
|
done
|
|
echo
|
|
|
|
if OMARCHY_UPDATE_RETRY=1 omarchy-update-system-pkgs; then
|
|
moved=()
|
|
exit 0
|
|
fi
|
|
exit 1
|