Three review findings on the update-hook boundary: The pre-refresh-pacman hook had been moved after the refresh transaction and, during a channel switch, deferred to the very end. That defeated the hook's purpose: custom repositories and IgnorePkg entries were not in place when the downgrade-capable -Syyuu ran. Run the hook where it used to run, after the package config is re-synced and before the transaction, but cold: revoke the timestamp, run it behind the no-update wrapper with the caller's original PATH, and revoke again before continuing. Every later privileged command authenticates with --no-update, so a detached child left by the hook has no reusable timestamp to wait for. Channel switching hands the caller's PATH to the refresh the same way the updater receives it, and no longer defers or re-runs the hook. Stay Awake was released before AUR builds, hooks and mise, so the machine could sleep during the longest part of an update. Releasing the inhibitor needs no privilege because the held command already dropped to the user, so stop it after mise and before the reboot prompt, as before. A packaged channel destination cannot be inspected before its package is installed, and a transaction can replace the running tree with a release that predates the command-scoped wrapper; from then on a bare sudo would resolve to /usr/bin/sudo and publish a timestamp, and the destination's own updater authenticates the same way. The switch used to abort only after the packages had changed, with generic rerun advice. Now it checks for the wrapper after each transaction before any further privileged step, completes what it safely can, and stops cold with instructions to run that release's update from a fresh session instead of launching it. Boundary tests pin the hook between the config copies and the transaction with a cold timestamp on both sides, the older-destination stop with its guidance and no launched updater, the new inhibitor position, and the post-update hook staying unreached on failures and signals. Docs, the manual and the sample hook describe the restored timing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
149 lines
4.9 KiB
Bash
Executable File
149 lines
4.9 KiB
Bash
Executable File
#!/bin/bash -p
|
|
|
|
# omarchy:summary=Set the Omarchy package channel.
|
|
# omarchy:args=<stable|rc|edge|dev>
|
|
# omarchy:requires-sudo=true
|
|
|
|
if [[ $- != *p* ]]; then
|
|
echo "Refusing an unsafe Bash startup for channel switching." >&2
|
|
exit 126
|
|
fi
|
|
security_entrypoint=$(/usr/bin/readlink -e -- "${BASH_SOURCE[0]}") || exit 126
|
|
source "${security_entrypoint%/*}/omarchy-security-functions" || exit 126
|
|
omarchy_security_require_privileged_bash_startup || exit 126
|
|
set -euo pipefail
|
|
omarchy_security_sanitize_bash_environment "$0" "$@"
|
|
omarchy_security_require_source_root "$0"
|
|
user_path=$PATH
|
|
omarchy_security_revoke_sudo_timestamp || exit 1
|
|
omarchy_security_install_sudo_cleanup_traps
|
|
omarchy_security_enable_no_update_sudo
|
|
|
|
usage() { echo "Usage: omarchy-channel-set [stable|rc|edge|dev]"; }
|
|
fail() { echo "Error: $*" >&2; exit 1; }
|
|
|
|
confirm_dev() {
|
|
cat <<'WARNING'
|
|
|
|
The dev channel links Omarchy directly to a checkout of the source in ~/omarchy.
|
|
It's exclusively intended for developers working on Omarchy itself.
|
|
|
|
WARNING
|
|
|
|
gum confirm --default=false "Switch to dev channel?"
|
|
}
|
|
|
|
validate_dev_checkout() {
|
|
local checkout="$1"
|
|
|
|
if [[ -e $checkout && ! -d $checkout/.git ]]; then
|
|
fail "$checkout already exists and is not a git checkout."
|
|
fi
|
|
|
|
if [[ -d $checkout/.git && ( ! -d $checkout/bin || ! -d $checkout/default || ! -d $checkout/shell ) ]]; then
|
|
fail "$checkout is a git checkout, but it does not look like Omarchy."
|
|
fi
|
|
}
|
|
|
|
link_dev_checkout() {
|
|
local checkout="$1" required
|
|
[[ -d $checkout/.git ]] || git clone https://github.com/omacom/omarchy.git "$checkout"
|
|
|
|
# Check the destination before changing /etc/omarchy.conf or sudo's path.
|
|
# An existing checkout is not pulled automatically and may predate this policy.
|
|
for required in bin/omarchy-security-functions bin/omarchy-update bin/omarchy-refresh-pacman default/omarchy/sudo-no-update/sudo; do
|
|
if [[ ! -f $checkout/$required || ! -r $checkout/$required ||
|
|
( $required != "bin/omarchy-security-functions" && ! -x $checkout/$required ) ]]; then
|
|
fail "Update the checkout before switching to dev; missing required update support in $required."
|
|
fi
|
|
done
|
|
|
|
omarchy-dev-link "$checkout" --no-reboot
|
|
}
|
|
|
|
# A packaged destination cannot be inspected before its package is installed,
|
|
# and a package transaction can replace the running tree with a release that
|
|
# predates the command-scoped wrapper. After that, a bare sudo would resolve to
|
|
# /usr/bin/sudo and publish a timestamp, and the destination's own updater
|
|
# authenticates the same way. Neither may run from a flow that has just run
|
|
# user hooks: stop at a consistent point and say how to finish from a fresh
|
|
# session.
|
|
stop_for_older_destination() {
|
|
cat >&2 <<EOF
|
|
The destination predates command-scoped sudo, so this switch stops before its update.
|
|
Packages are switched. Run 'omarchy update' from a new terminal to finish, then reboot if prompted.
|
|
EOF
|
|
exit 3
|
|
}
|
|
|
|
wrapper_present() {
|
|
[[ -f $OMARCHY_PATH/default/omarchy/sudo-no-update/sudo && -x $OMARCHY_PATH/default/omarchy/sudo-no-update/sudo ]]
|
|
}
|
|
|
|
(( $# > 0 )) || { usage; exit 1; }
|
|
|
|
dev_checkout=""
|
|
channel="$1"
|
|
leaving_dev=0
|
|
|
|
case "$channel" in
|
|
stable)
|
|
pacman_channel=stable
|
|
packages=(omarchy omarchy-settings)
|
|
;;
|
|
rc)
|
|
pacman_channel=rc
|
|
packages=(omarchy omarchy-settings)
|
|
;;
|
|
edge)
|
|
pacman_channel=edge
|
|
packages=(omarchy-dev omarchy-settings-dev)
|
|
;;
|
|
dev)
|
|
confirm_dev || { echo "Cancelled."; exit 0; }
|
|
dev_checkout="$HOME/omarchy"
|
|
validate_dev_checkout "$dev_checkout"
|
|
pacman_channel=edge
|
|
packages=(omarchy-dev omarchy-settings-dev)
|
|
;;
|
|
*)
|
|
echo "Unknown channel: $channel" >&2
|
|
usage >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# A failure past this point leaves the channel switch half-applied, so say how
|
|
# to pick it back up rather than dying silently under set -e.
|
|
trap 'echo -e "\nThe channel switch did not complete. Review the error above, then rerun: omarchy-channel-set '"$channel"'" >&2' ERR
|
|
|
|
if [[ -z $dev_checkout && $OMARCHY_PATH != "/usr/share/omarchy" ]]; then
|
|
leaving_dev=1
|
|
fi
|
|
|
|
if [[ -n $dev_checkout ]]; then
|
|
link_dev_checkout "$dev_checkout"
|
|
export OMARCHY_PATH="$dev_checkout"
|
|
omarchy_security_enable_no_update_sudo
|
|
omarchy-state set reboot-required
|
|
fi
|
|
|
|
OMARCHY_UPDATE_USER_PATH="$user_path" omarchy-refresh-pacman "$pacman_channel"
|
|
# Each transaction can have replaced this tree; check before the next sudo.
|
|
wrapper_present || stop_for_older_destination
|
|
# --ask 4 accepts omarchy <-> omarchy-dev replacement prompts without file overwrites.
|
|
omarchy-update-pacman -S --needed --noconfirm --ask 4 "${packages[@]}"
|
|
wrapper_present || stop_for_older_destination
|
|
|
|
if [[ -z $dev_checkout ]]; then
|
|
omarchy-dev-unlink --no-reboot
|
|
export OMARCHY_PATH=/usr/share/omarchy
|
|
|
|
if (( leaving_dev )); then
|
|
omarchy-state set reboot-required
|
|
fi
|
|
omarchy_security_enable_no_update_sudo 2>/dev/null || stop_for_older_destination
|
|
fi
|
|
|
|
OMARCHY_UPDATE_USER_PATH="$user_path" "$OMARCHY_PATH/bin/omarchy-update" -y
|