Files
coredesktop/update
T
davidandClaude Opus 5.5 b2a16471fd update: hand over cleanly from updates older than 2026-10-19
013acac's update (in use 2026-08-14 to 2026-10-19) copies the new update
over itself with sudo cp while bash is still reading it, so bash reads on
from byte 1484 of the new file: today, the middle of the logo, and the
update dies with a syntax error. Put a line at exactly that byte that
execs the new update, in a here-document a normal run skips. Found by
30-upgrade-from-baseline at ca658dc; the test replays 013acac's update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 14:00:59 -07:00

128 lines
5.7 KiB
Bash
Executable File

#!/bin/bash
# Updates from before 2026-10-19 (013acac's) copy this file over themselves
# while bash is still reading them, then read on from byte 1484 of the new copy.
# The exec line below starts at exactly that byte, so they run this update
# afresh; the rest of the block pads it into place, and in a normal run the
# block is a here-document nothing reads. Keep the exec line at byte 1484:
# tests/update.bats checks it.
: << 'FROM_OLD_UPDATE'
This block is padding for old updates. This block is padding for old updates.
This block is padding for old updates. This block is padding for old updates.
This block is padding for old updates. This block is padding for old updates.
This block is padding for old updates. This block is padding for old updates.
This block is padding for old updates. This block is padding for old updates.
This block is padding for old updates. This block is padding for old updates.
This block is padding for old updates. This block is padding for old updates.
This block is padding for old updates. This block is padding for old updates.
This block is padding for old updates. This block is padding for old updates.
This block is padding for old updates. This block is padding for old updates.
This block is padding for old updates. This block is padding for old updates.
This block is padding for old updates. This block is padding for old updates.
This block is padding for old updates. This block is padding for old updates.
...................
exec ~/.local/bin/update
FROM_OLD_UPDATE
# The whole script is wrapped in { ...; exit; } so bash parses all of it before
# running anything. That way, replacing this file mid-run (e.g. by git pull and
# the copy below) can't make the running copy read a mix of old and new text.
{
# Install a script atomically (copy to a temp file, then rename over the target)
install_script() {
local tmp
tmp="$(mktemp "$2.XXXXXX")" && cp "$1" "$tmp" && chmod 755 "$tmp" && mv -f "$tmp" "$2"
}
# Print the logo
print_logo() {
cat << "EOF"
██
█ ██
██ ██████ █████ ██ █████ █████ ████ ██ █████ ██
██ ██ ██ ███ ███████ ██ ██ ██ ██ ██ ████████ ██ ██ ███████
█████ ██████ ██ ██ ██████ ██ ████████ ██ █ ███████ ██ ██
██ ██████ ███████ ██████ ██ ██████ ██████ ██████ ███████
EOF
}
# Clear screen and show logo
clear
print_logo
COREDESKTOP="$HOME/.local/share/coredesktop"
echo "Updating CoreDesktop..."
if [[ -z "$UPDATE_REEXECED" ]] && ! git -C "$COREDESKTOP" pull --ff-only; then
echo "WARNING: could not pull the latest CoreDesktop; running the update hooks already on disk."
export UPDATE_PULL_FAILED=1
fi
mkdir -p "$COREDESKTOP/updates.d"
install_script "$COREDESKTOP/update" ~/.local/bin/update
install_script "$COREDESKTOP/refresh" ~/.local/bin/refresh
install_script "$COREDESKTOP/sibling" ~/.local/bin/sibling
install_script "$COREDESKTOP/version" ~/.local/bin/version
install_script "$COREDESKTOP/configure" ~/.local/bin/configure
install_script "$COREDESKTOP/audit" ~/.local/bin/audit
if [[ -z "$UPDATE_REEXECED" ]]; then
export UPDATE_REEXECED=1
exec ~/.local/bin/update
fi
# An upgrade that runs out of disk space part way through can leave the system
# half-upgraded. Set UPDATE_FORCE=1 to skip this check.
MIN_FREE_GIB=10
FREE_KIB="$(df -k --output=avail / | tail -n 1 | tr -d ' ')"
if [[ -z "$UPDATE_FORCE" && "$FREE_KIB" =~ ^[0-9]+$ ]] && (( FREE_KIB < MIN_FREE_GIB * 1024 * 1024 )); then
echo "Only $(( FREE_KIB / 1024 / 1024 )) GiB free on /; updating needs at least $MIN_FREE_GIB GiB."
echo "Free up space (sudo paccache -rk1 clears old cached packages), or run: UPDATE_FORCE=1 update"
exit 1
fi
# Ask for the password once, then keep sudo's cached credentials fresh until
# update exits, so long-running hooks don't stop for a password again.
sudo -v || exit 1
while kill -0 $$ 2>/dev/null; do sudo -n -v; sleep 60; done 2>/dev/null &
SUDO_KEEPALIVE_PID=$!
trap 'kill "$SUDO_KEEPALIVE_PID" 2>/dev/null' EXIT
FAILED=()
[[ -n "$UPDATE_PULL_FAILED" ]] && FAILED+=("git pull")
# Each hook runs in its own subshell so a cd, set -e, exit, or stray variable
# in one hook can't leak into or abort the hooks after it.
for plugin in "$COREDESKTOP/updates.d/"*.sh; do
[ -f "$plugin" ] || continue
(
unset -f is_installed run_update
source "$plugin"
# A hook without run_update does its work as it's sourced, so its
# last command's status is the hook's
status=$?
if declare -f run_update > /dev/null; then
if ! declare -f is_installed > /dev/null || is_installed; then
run_update
fi
else
exit "$status"
fi
)
# Checked separately: under `( ... ) ||`, bash would ignore set -e inside the hook
[ $? -eq 0 ] || FAILED+=("$(basename "$plugin")")
done
if (( ${#FAILED[@]} > 0 )); then
echo "Done, with errors in: ${FAILED[*]}"
exit 1
fi
echo "Done."
exit 0
}