When update or install.sh fails and there's a terminal, ask whether to send its log to the CoreDesktop team, with the option of reading exactly what would be sent first. Nothing is sent without a yes, and nothing is asked without a terminal. The new report command sends a log later (report, report FILE) or shows what would be sent (report --view). Before sending, the log is cleaned of terminal codes, and the home folder, user name, host name, email addresses, IPv4 addresses and Tailscale names are replaced. CoreDesktop's version, the kernel's, the failed steps and sibling versions go along with it. The request format the receiver on federated.sh implements is described at the top of report. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
159 lines
7.3 KiB
Bash
Executable File
159 lines
7.3 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"
|
|
}
|
|
|
|
# Make a log written by script(1) readable: drop its header and footer, color
|
|
# and cursor codes, and all but the last redraw of each progress line. LC_ALL=C
|
|
# so the ranges below mean bytes, whatever the locale.
|
|
clean_log() {
|
|
LC_ALL=C sed -i -E '1{/^Script started on /d}; /^Script done on .*COMMAND_EXIT_CODE/d;
|
|
s/\x1b\][^\x07\x1b]*(\x07|\x1b\\)//g; s#\x1b\[[0-9;?]*[ -/]*[@-~]##g;
|
|
s/\x1b[()][0-9A-Za-z]//g; s/\x1b[=>]//g; s/\r+$//; s/.*\r//' "$1"
|
|
}
|
|
|
|
# Log each update to ~/.local/state/coredesktop/logs (95-cleanup.sh deletes
|
|
# old ones). In a terminal, run under script(1), so everything still sees a
|
|
# terminal and colors, menus and prompts work as before; otherwise use tee.
|
|
# UPDATE_LOG tells the copy re-run below that it's already logging.
|
|
if [[ -z "$UPDATE_LOG" ]] && mkdir -p -m 700 "$HOME/.local/state/coredesktop/logs"; then
|
|
UPDATE_LOG="$HOME/.local/state/coredesktop/logs/update-$(date +%Y-%m-%d-%H%M%S).log"
|
|
export UPDATE_LOG
|
|
if [ -t 0 ] && [ -t 1 ] && command -v script > /dev/null; then
|
|
SHELL=/bin/bash script -q -e -f -c "$(printf '%q ' "$0" "$@")" "$UPDATE_LOG"
|
|
status=$?
|
|
clean_log "$UPDATE_LOG"
|
|
exit "$status"
|
|
fi
|
|
exec > >(tee -a "$UPDATE_LOG") 2>&1
|
|
fi
|
|
|
|
# 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
|
|
install_script "$COREDESKTOP/report" ~/.local/bin/report
|
|
|
|
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 > /dev/null 2>&1 &
|
|
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[*]}"
|
|
if [ -n "$UPDATE_LOG" ]; then
|
|
echo "The full output is in $UPDATE_LOG"
|
|
# Ask, on the terminal if there is one, whether to send it to us
|
|
[ -x ~/.local/bin/report ] && ~/.local/bin/report --offer "$UPDATE_LOG" "${FAILED[@]}"
|
|
fi
|
|
exit 1
|
|
fi
|
|
|
|
echo "Done."
|
|
exit 0
|
|
}
|