Improve update and migration flow
This commit is contained in:
@@ -175,10 +175,17 @@ This copies `/etc/skel/.config/hypr/hyprlock.conf` to `~/.config/hypr/hyprlock.c
|
|||||||
|
|
||||||
# Migrations
|
# Migrations
|
||||||
|
|
||||||
To create a new migration, run `omarchy-dev-add-migration --no-edit`. This creates a migration file named after the unix timestamp of the last commit.
|
Read `docs/migrations.md` before creating or changing migrations.
|
||||||
|
|
||||||
|
Migrations are split by execution scope:
|
||||||
|
|
||||||
|
- `migrations/system/<timestamp>.sh` — root, noninteractive, safe to run from pacman via `omarchy-migrate-system` (`omarchy` `post_upgrade` calls it). Use for `/etc`, `/usr`, `/boot`, services, hardware quirks, and other system state. Do not prompt.
|
||||||
|
- `migrations/user/<timestamp>.sh` — current user/session, may touch `~/.config`, `~/.local`, user systemd, browser prefs, DBus/session state, and may prompt if necessary. Runs through `omarchy-migrate` / `omarchy-migrate-user`; pending state is per-user based on missing files under `~/.local/state/omarchy/migrations/user/`.
|
||||||
|
|
||||||
|
To create a new migration, run `omarchy-dev-add-migration system --no-edit` or `omarchy-dev-add-migration user --no-edit` based on scope.
|
||||||
|
|
||||||
New migration format:
|
New migration format:
|
||||||
- File permissions must be `0644` (`-rw-r--r--`); migrations are sourced, not executed directly
|
- File permissions must be `0644` (`-rw-r--r--`); migration runners execute them with `bash -euo pipefail`, not through executable bits
|
||||||
- No shebang line
|
- No shebang line
|
||||||
- Start with an `echo` describing what the migration does
|
- Start with an `echo` describing what the migration does
|
||||||
- Use `$OMARCHY_PATH` to reference the omarchy directory
|
- Use `$OMARCHY_PATH` to reference the omarchy directory
|
||||||
|
|||||||
@@ -1,12 +1,29 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# omarchy:summary=Create a new Omarchy migration in the current source tree.
|
# omarchy:summary=Create a new Omarchy migration in the current source tree.
|
||||||
|
# omarchy:args=<system|user> [--no-edit]
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
scope="${1:-}"
|
||||||
|
case "$scope" in
|
||||||
|
system|user)
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help|"")
|
||||||
|
echo "Usage: omarchy-dev-add-migration <system|user> [--no-edit]"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown migration scope: $scope" >&2
|
||||||
|
echo "Usage: omarchy-dev-add-migration <system|user> [--no-edit]" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
cd "${OMARCHY_PATH:-$(pwd)}"
|
cd "${OMARCHY_PATH:-$(pwd)}"
|
||||||
mkdir -p migrations
|
mkdir -p "migrations/$scope"
|
||||||
migration_file="migrations/$(git log -1 --format=%cd --date=unix).sh"
|
migration_file="migrations/$scope/$(git log -1 --format=%cd --date=unix).sh"
|
||||||
touch "$migration_file"
|
touch "$migration_file"
|
||||||
|
|
||||||
if [[ ${1:-} != "--no-edit" ]]; then
|
if [[ ${1:-} != "--no-edit" ]]; then
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ For shipped configs see /etc/skel (new users) and omarchy-reinstall-configs
|
|||||||
(existing users explicitly resyncing).
|
(existing users explicitly resyncing).
|
||||||
|
|
||||||
--first-install is used by the ISO in the target chroot. It marks shipped
|
--first-install is used by the ISO in the target chroot. It marks shipped
|
||||||
migrations complete for the freshly-created user.
|
user migrations complete for the freshly-created user.
|
||||||
|
|
||||||
Idempotency marker: ~/.local/state/omarchy/finalize-user.done
|
Idempotency marker: ~/.local/state/omarchy/finalize-user.done
|
||||||
USAGE
|
USAGE
|
||||||
@@ -123,9 +123,9 @@ xdg-settings set default-web-browser chromium.desktop
|
|||||||
xdg-mime default HEY.desktop x-scheme-handler/mailto
|
xdg-mime default HEY.desktop x-scheme-handler/mailto
|
||||||
|
|
||||||
if (( first_install )); then
|
if (( first_install )); then
|
||||||
mkdir -p "$state_dir/migrations"
|
mkdir -p "$state_dir/migrations/user"
|
||||||
for migration in "$OMARCHY_PATH"/migrations/*.sh; do
|
for migration in "$OMARCHY_PATH"/migrations/user/*.sh; do
|
||||||
[[ -f $migration ]] && touch "$state_dir/migrations/$(basename "$migration")"
|
[[ -f $migration ]] && touch "$state_dir/migrations/user/$(basename "$migration")"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,17 @@ run_first_run_step() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wait_for_notifications
|
||||||
|
|
||||||
|
MIGRATION_NOTIFY_WATCH_MARKER="$state_dir/user-migration-notify-watch-enabled"
|
||||||
|
if [[ ! -f $MIGRATION_NOTIFY_WATCH_MARKER || $force -eq 1 ]]; then
|
||||||
|
if systemctl --user enable --now omarchy-update-user-notify.path >/dev/null 2>&1; then
|
||||||
|
touch "$MIGRATION_NOTIFY_WATCH_MARKER"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_first_run_step "notify about pending user migrations" omarchy-migrate-notify
|
||||||
|
|
||||||
USER_MARKER="$state_dir/first-run-user.done"
|
USER_MARKER="$state_dir/first-run-user.done"
|
||||||
if [[ ! -f $USER_MARKER || $force -eq 1 ]]; then
|
if [[ ! -f $USER_MARKER || $force -eq 1 ]]; then
|
||||||
run_first_run_step "install Voxtype post-update hook" \
|
run_first_run_step "install Voxtype post-update hook" \
|
||||||
|
|||||||
+89
-11
@@ -1,19 +1,97 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# omarchy:summary=Run pending Omarchy migrations for the current user.
|
# omarchy:summary=Run pending Omarchy system and user migrations.
|
||||||
|
# omarchy:args=[--pending [all|system|user]]
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
STATE_DIR="$HOME/.local/state/omarchy/migrations"
|
mode="run"
|
||||||
mkdir -p "$STATE_DIR"
|
pending_scope="all"
|
||||||
|
|
||||||
for file in "$OMARCHY_PATH"/migrations/*.sh; do
|
usage() {
|
||||||
[[ -f $file ]] || continue
|
echo "Usage: omarchy-migrate [--pending [all|system|user]]"
|
||||||
filename=$(basename "$file")
|
}
|
||||||
|
|
||||||
if [[ ! -f $STATE_DIR/$filename ]]; then
|
while (($#)); do
|
||||||
echo -e "\e[32m\nRunning migration (${filename%.sh})\e[0m"
|
case "$1" in
|
||||||
bash "$file"
|
--pending|--check)
|
||||||
touch "$STATE_DIR/$filename"
|
mode="pending"
|
||||||
fi
|
shift
|
||||||
|
if [[ ${1:-} == "all" || ${1:-} == "system" || ${1:-} == "user" ]]; then
|
||||||
|
pending_scope="$1"
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
--pending=all|--pending=system|--pending=user)
|
||||||
|
mode="pending"
|
||||||
|
pending_scope="${1#--pending=}"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
pending_output=""
|
||||||
|
|
||||||
|
append_pending() {
|
||||||
|
local scope="$1"
|
||||||
|
local command="$2"
|
||||||
|
local output=""
|
||||||
|
local migration=""
|
||||||
|
|
||||||
|
if output=$("$command" --pending 2>/dev/null); then
|
||||||
|
while IFS= read -r migration; do
|
||||||
|
[[ -n $migration ]] || continue
|
||||||
|
pending_output+="$scope/$migration"$'\n'
|
||||||
|
done <<<"$output"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ $mode == "pending" ]]; then
|
||||||
|
case "$pending_scope" in
|
||||||
|
all)
|
||||||
|
append_pending system omarchy-migrate-system
|
||||||
|
append_pending user omarchy-migrate-user
|
||||||
|
;;
|
||||||
|
system)
|
||||||
|
append_pending system omarchy-migrate-system
|
||||||
|
;;
|
||||||
|
user)
|
||||||
|
append_pending user omarchy-migrate-user
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ -n $pending_output ]]; then
|
||||||
|
printf '%s' "$pending_output"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
wait_for_pacman_transaction() {
|
||||||
|
local lock_file=/var/lib/pacman/db.lck
|
||||||
|
|
||||||
|
[[ -e $lock_file ]] || return 0
|
||||||
|
echo "Waiting for pacman transaction to finish before running Omarchy migrations..."
|
||||||
|
|
||||||
|
for _ in {1..900}; do
|
||||||
|
[[ -e $lock_file ]] || return 0
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Pacman transaction is still running; Omarchy migrations will retry on next login."
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_pacman_transaction
|
||||||
|
|
||||||
|
omarchy-migrate-system
|
||||||
|
omarchy-migrate-user
|
||||||
|
|||||||
Executable
+35
@@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Notify the user when Omarchy has pending user migrations
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
pending_migrations=$(omarchy-migrate --pending user 2>/dev/null) || exit 0
|
||||||
|
pending_count=$(printf '%s\n' "$pending_migrations" | sed '/^[[:space:]]*$/d' | wc -l)
|
||||||
|
|
||||||
|
if (( pending_count == 1 )); then
|
||||||
|
message="1 new Omarchy user migration is available. Click to run it in a terminal."
|
||||||
|
else
|
||||||
|
message="$pending_count new Omarchy user migrations are available. Click to run them in a terminal."
|
||||||
|
fi
|
||||||
|
|
||||||
|
notify_command=$(printf 'if [[ -n $(omarchy-notification-send -u critical -g "Run Omarchy Migrations" %q -a) ]]; then omarchy-launch-floating-terminal-with-presentation omarchy-migrate; fi' "$message")
|
||||||
|
|
||||||
|
if command -v systemd-run >/dev/null 2>&1; then
|
||||||
|
unit="omarchy-migrations-notification-$(date +%Y%m%d%H%M%S)"
|
||||||
|
systemd-run --user --scope --unit="$unit" bash -lc "$notify_command" >/dev/null 2>&1 && exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_pending_migrations() {
|
||||||
|
echo "Omarchy has pending user migrations. Run omarchy-migrate in a terminal to apply them:"
|
||||||
|
while IFS= read -r migration; do
|
||||||
|
[[ -n $migration ]] || continue
|
||||||
|
printf ' %s\n' "$migration"
|
||||||
|
done <<<"$pending_migrations"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
print_pending_migrations
|
||||||
|
else
|
||||||
|
print_pending_migrations >&2
|
||||||
|
fi
|
||||||
Executable
+74
@@ -0,0 +1,74 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Run pending Omarchy system migrations as root.
|
||||||
|
# omarchy:args=[--pending]
|
||||||
|
# omarchy:requires-sudo=true
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
mode="run"
|
||||||
|
while (($#)); do
|
||||||
|
case "$1" in
|
||||||
|
--pending|--check)
|
||||||
|
mode="pending"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
echo "Usage: omarchy-migrate-system [--pending]"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
OMARCHY_PATH="${OMARCHY_PATH:-/usr/share/omarchy}"
|
||||||
|
STATE_DIR="${OMARCHY_SYSTEM_MIGRATION_STATE:-/var/lib/omarchy/migrations/system}"
|
||||||
|
MIGRATIONS_DIR="$OMARCHY_PATH/migrations/system"
|
||||||
|
|
||||||
|
pending_migrations() {
|
||||||
|
[[ -d $MIGRATIONS_DIR ]] || return 0
|
||||||
|
|
||||||
|
for file in "$MIGRATIONS_DIR"/*.sh; do
|
||||||
|
[[ -f $file ]] || continue
|
||||||
|
filename=$(basename "$file")
|
||||||
|
|
||||||
|
if [[ ! -f $STATE_DIR/$filename ]]; then
|
||||||
|
printf '%s\n' "$filename"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ $mode == "pending" ]]; then
|
||||||
|
pending_output=$(pending_migrations)
|
||||||
|
if [[ -n $pending_output ]]; then
|
||||||
|
printf '%s\n' "$pending_output"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( EUID != 0 )) && [[ -z ${OMARCHY_SYSTEM_MIGRATION_STATE:-} ]]; then
|
||||||
|
if ! "$0" --pending >/dev/null; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec sudo --preserve-env=OMARCHY_PATH "$0" "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$STATE_DIR"
|
||||||
|
[[ -d $MIGRATIONS_DIR ]] || exit 0
|
||||||
|
|
||||||
|
for file in "$MIGRATIONS_DIR"/*.sh; do
|
||||||
|
[[ -f $file ]] || continue
|
||||||
|
filename=$(basename "$file")
|
||||||
|
|
||||||
|
if [[ ! -f $STATE_DIR/$filename ]]; then
|
||||||
|
echo -e "\e[32m\nRunning system migration (${filename%.sh})\e[0m"
|
||||||
|
OMARCHY_PATH="$OMARCHY_PATH" bash -euo pipefail "$file"
|
||||||
|
touch "$STATE_DIR/$filename"
|
||||||
|
fi
|
||||||
|
done
|
||||||
Executable
+65
@@ -0,0 +1,65 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Run pending Omarchy migrations for the current user.
|
||||||
|
# omarchy:args=[--pending]
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
mode="run"
|
||||||
|
while (($#)); do
|
||||||
|
case "$1" in
|
||||||
|
--pending|--check)
|
||||||
|
mode="pending"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
echo "Usage: omarchy-migrate-user [--pending]"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
OMARCHY_PATH="${OMARCHY_PATH:-/usr/share/omarchy}"
|
||||||
|
STATE_DIR="${OMARCHY_USER_MIGRATION_STATE:-$HOME/.local/state/omarchy/migrations/user}"
|
||||||
|
MIGRATIONS_DIR="$OMARCHY_PATH/migrations/user"
|
||||||
|
|
||||||
|
pending_migrations() {
|
||||||
|
[[ -d $MIGRATIONS_DIR ]] || return 0
|
||||||
|
|
||||||
|
for file in "$MIGRATIONS_DIR"/*.sh; do
|
||||||
|
[[ -f $file ]] || continue
|
||||||
|
filename=$(basename "$file")
|
||||||
|
|
||||||
|
if [[ ! -f $STATE_DIR/$filename ]]; then
|
||||||
|
printf '%s\n' "$filename"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ $mode == "pending" ]]; then
|
||||||
|
pending_output=$(pending_migrations)
|
||||||
|
if [[ -n $pending_output ]]; then
|
||||||
|
printf '%s\n' "$pending_output"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$STATE_DIR"
|
||||||
|
[[ -d $MIGRATIONS_DIR ]] || exit 0
|
||||||
|
|
||||||
|
for file in "$MIGRATIONS_DIR"/*.sh; do
|
||||||
|
[[ -f $file ]] || continue
|
||||||
|
filename=$(basename "$file")
|
||||||
|
|
||||||
|
if [[ ! -f $STATE_DIR/$filename ]]; then
|
||||||
|
echo -e "\e[32m\nRunning user migration (${filename%.sh})\e[0m"
|
||||||
|
OMARCHY_PATH="$OMARCHY_PATH" bash -euo pipefail "$file"
|
||||||
|
touch "$STATE_DIR/$filename"
|
||||||
|
fi
|
||||||
|
done
|
||||||
@@ -23,4 +23,4 @@ sudo cp -f "$OMARCHY_PATH/default/pacman/mirrorlist-$channel" /etc/pacman.d/mirr
|
|||||||
omarchy-hook pre-refresh-pacman
|
omarchy-hook pre-refresh-pacman
|
||||||
|
|
||||||
# Reset all package DBs and then update
|
# Reset all package DBs and then update
|
||||||
sudo pacman -Syyuu --noconfirm
|
sudo env OMARCHY_UPDATE_PACMAN=1 pacman -Syyuu --noconfirm
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ set -e
|
|||||||
omarchy-refresh-pacman
|
omarchy-refresh-pacman
|
||||||
|
|
||||||
# Downgrade any packages to the stable setup
|
# Downgrade any packages to the stable setup
|
||||||
sudo pacman -Suu --noconfirm
|
sudo env OMARCHY_UPDATE_PACMAN=1 pacman -Suu --noconfirm
|
||||||
|
|
||||||
# Ensure all packages are installed
|
# Ensure all packages are installed
|
||||||
mapfile -t packages < <(grep -v '^#' "$OMARCHY_PATH/install/omarchy-base.packages" | grep -v '^$')
|
mapfile -t packages < <(grep -v '^#' "$OMARCHY_PATH/install/omarchy-base.packages" | grep -v '^$')
|
||||||
sudo pacman -Syu --noconfirm --needed "${packages[@]}"
|
sudo env OMARCHY_UPDATE_PACMAN=1 pacman -Syu --noconfirm --needed "${packages[@]}"
|
||||||
|
|||||||
+88
-2
@@ -7,14 +7,100 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [[ -z $OMARCHY_UPDATE_LOGGED ]]; then
|
if [[ -z ${OMARCHY_UPDATE_LOGGED:-} ]]; then
|
||||||
script_command=$(printf '%q ' "$0" "$@")
|
script_command=$(printf '%q ' "$0" "$@")
|
||||||
exec env OMARCHY_UPDATE_LOGGED=1 script -qefc "$script_command" "/tmp/omarchy-update.log"
|
exec env OMARCHY_UPDATE_LOGGED=1 script -qefc "$script_command" "/tmp/omarchy-update.log"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
acquire_update_lock() {
|
||||||
|
local lock_dir="${XDG_RUNTIME_DIR:-/tmp}"
|
||||||
|
local lock_path="$lock_dir/omarchy-update.lock"
|
||||||
|
local lock_fd_path=""
|
||||||
|
|
||||||
|
mkdir -p "$lock_dir" 2>/dev/null || true
|
||||||
|
|
||||||
|
if [[ -n ${OMARCHY_UPDATE_LOCK_FD:-} && -e /proc/$$/fd/$OMARCHY_UPDATE_LOCK_FD ]]; then
|
||||||
|
lock_fd_path=$(readlink -f "/proc/$$/fd/$OMARCHY_UPDATE_LOCK_FD" 2>/dev/null || true)
|
||||||
|
if [[ $lock_fd_path == "$(readlink -m "$lock_path")" ]] && flock -n "$OMARCHY_UPDATE_LOCK_FD"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec {OMARCHY_UPDATE_LOCK_FD}>"$lock_path"
|
||||||
|
if ! flock -n "$OMARCHY_UPDATE_LOCK_FD"; then
|
||||||
|
echo "An Omarchy update is already running."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
export OMARCHY_UPDATE_LOCK_FD
|
||||||
|
}
|
||||||
|
|
||||||
|
update_disabled_idle=0
|
||||||
|
sleep_inhibit_pid=""
|
||||||
|
|
||||||
|
disable_sleep_for_update() {
|
||||||
|
command -v systemd-inhibit >/dev/null 2>&1 || return 0
|
||||||
|
|
||||||
|
systemd-inhibit \
|
||||||
|
--what=sleep:idle \
|
||||||
|
--who=omarchy-update \
|
||||||
|
--why="Omarchy update in progress" \
|
||||||
|
--mode=block \
|
||||||
|
sleep infinity >/dev/null 2>&1 &
|
||||||
|
sleep_inhibit_pid=$!
|
||||||
|
}
|
||||||
|
|
||||||
|
disable_idle_for_update() {
|
||||||
|
local stay_awake_state="$HOME/.local/state/omarchy/indicators/stay-awake"
|
||||||
|
|
||||||
|
if [[ ! -f $stay_awake_state ]]; then
|
||||||
|
omarchy-toggle-idle stay-awake >/dev/null 2>&1 || true
|
||||||
|
update_disabled_idle=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
restore_update_inhibitors() {
|
||||||
|
if (( update_disabled_idle )); then
|
||||||
|
omarchy-toggle-idle allow-idle >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n $sleep_inhibit_pid ]]; then
|
||||||
|
kill "$sleep_inhibit_pid" >/dev/null 2>&1 || true
|
||||||
|
wait "$sleep_inhibit_pid" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_update_pipeline() {
|
||||||
|
disable_sleep_for_update
|
||||||
|
disable_idle_for_update
|
||||||
|
|
||||||
|
omarchy-update-keyring
|
||||||
|
omarchy-update-system-pkgs
|
||||||
|
omarchy-migrate
|
||||||
|
omarchy-hook post-update
|
||||||
|
omarchy-update-aur-pkgs
|
||||||
|
omarchy-update-mise
|
||||||
|
omarchy-update-orphan-pkgs
|
||||||
|
|
||||||
|
omarchy-update-analyze-logs
|
||||||
|
|
||||||
|
# Re-check after updates so the status bar reflects any remaining updates.
|
||||||
|
# This clears the state when everything is current, so a separate reset command
|
||||||
|
# is not needed.
|
||||||
|
if omarchy-update-available >/dev/null; then
|
||||||
|
omarchy-shell -q omarchy.system-update refresh
|
||||||
|
else
|
||||||
|
omarchy-shell -q omarchy.system-update clear
|
||||||
|
fi
|
||||||
|
|
||||||
|
omarchy-update-restart
|
||||||
|
}
|
||||||
|
|
||||||
|
acquire_update_lock
|
||||||
|
|
||||||
|
trap restore_update_inhibitors EXIT
|
||||||
trap 'echo ""; echo -e "\033[0;31mSomething went wrong during the update!\n\nPlease review the output above carefully, correct the error, and retry the update.\n\nIf you need assistance, get help from the community at https://omarchy.org/discord\033[0m"' ERR
|
trap 'echo ""; echo -e "\033[0;31mSomething went wrong during the update!\n\nPlease review the output above carefully, correct the error, and retry the update.\n\nIf you need assistance, get help from the community at https://omarchy.org/discord\033[0m"' ERR
|
||||||
|
|
||||||
if [[ ${1:-} == "-y" ]] || omarchy-update-confirm; then
|
if [[ ${1:-} == "-y" ]] || omarchy-update-confirm; then
|
||||||
omarchy-snapshot create || (($? == 127))
|
omarchy-snapshot create || (($? == 127))
|
||||||
omarchy-update-perform
|
run_update_pipeline
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,15 +1,119 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# omarchy:summary=Check whether the Omarchy package has an available update.
|
# omarchy:summary=Check whether system or AUR packages have available updates.
|
||||||
|
|
||||||
set -e
|
set -euo pipefail
|
||||||
|
|
||||||
if pacman -Qu omarchy >/tmp/omarchy-update-available.$$ 2>/dev/null; then
|
CHECK_TIMEOUT="${OMARCHY_UPDATE_CHECK_TIMEOUT:-60}"
|
||||||
cat /tmp/omarchy-update-available.$$
|
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/updates"
|
||||||
rm -f /tmp/omarchy-update-available.$$
|
TMP_PARENT="${TMPDIR:-/tmp}"
|
||||||
|
|
||||||
|
mkdir -p "$STATE_DIR"
|
||||||
|
|
||||||
|
tmp_dir=$(mktemp -d "$TMP_PARENT/omarchy-update-available.XXXXXX")
|
||||||
|
trap 'rm -rf "$tmp_dir"' EXIT
|
||||||
|
|
||||||
|
packages_raw="$tmp_dir/packages.raw"
|
||||||
|
packages_tmp="$tmp_dir/packages"
|
||||||
|
aur_raw="$tmp_dir/aur.raw"
|
||||||
|
aur_tmp="$tmp_dir/aur"
|
||||||
|
errors_tmp="$tmp_dir/errors"
|
||||||
|
|
||||||
|
packages_file="$STATE_DIR/packages"
|
||||||
|
aur_file="$STATE_DIR/aur"
|
||||||
|
available_file="$STATE_DIR/available"
|
||||||
|
checked_at_file="$STATE_DIR/checked-at"
|
||||||
|
error_file="$STATE_DIR/error"
|
||||||
|
|
||||||
|
: >"$packages_raw"
|
||||||
|
: >"$packages_tmp"
|
||||||
|
: >"$aur_raw"
|
||||||
|
: >"$aur_tmp"
|
||||||
|
: >"$errors_tmp"
|
||||||
|
|
||||||
|
strip_update_output() {
|
||||||
|
local source="$1"
|
||||||
|
local destination="$2"
|
||||||
|
|
||||||
|
sed -E 's/\x1B\[[0-9;]*m//g' "$source" | sed '/^[[:space:]]*$/d' >"$destination"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_system_packages() {
|
||||||
|
local status=0
|
||||||
|
|
||||||
|
if command -v checkupdates >/dev/null 2>&1 && command -v fakeroot >/dev/null 2>&1; then
|
||||||
|
CHECKUPDATES_DB="$tmp_dir/checkupdates-db" timeout "$CHECK_TIMEOUT" checkupdates --nocolor >"$packages_raw" 2>"$tmp_dir/checkupdates.err" || status=$?
|
||||||
|
|
||||||
|
case "$status" in
|
||||||
|
0|2)
|
||||||
|
;;
|
||||||
|
124)
|
||||||
|
echo "System package update check timed out" >>"$errors_tmp"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "System package update check failed" >>"$errors_tmp"
|
||||||
|
sed '/^[[:space:]]*$/d' "$tmp_dir/checkupdates.err" >>"$errors_tmp" || true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
elif command -v pacman >/dev/null 2>&1; then
|
||||||
|
# Fallback for systems that have not picked up pacman-contrib/fakeroot yet.
|
||||||
|
# This only reports updates already known to the local sync database.
|
||||||
|
pacman -Qu --color never >"$packages_raw" 2>/dev/null || true
|
||||||
|
else
|
||||||
|
echo "pacman is not available" >>"$errors_tmp"
|
||||||
|
fi
|
||||||
|
|
||||||
|
strip_update_output "$packages_raw" "$packages_tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_aur_packages() {
|
||||||
|
local status=0
|
||||||
|
|
||||||
|
command -v pacman >/dev/null 2>&1 || return 0
|
||||||
|
command -v yay >/dev/null 2>&1 || return 0
|
||||||
|
pacman -Qem >/dev/null 2>&1 || return 0
|
||||||
|
|
||||||
|
timeout "$CHECK_TIMEOUT" yay --color never -Qua >"$aur_raw" 2>"$tmp_dir/yay.err" || status=$?
|
||||||
|
|
||||||
|
case "$status" in
|
||||||
|
0|1)
|
||||||
|
;;
|
||||||
|
124)
|
||||||
|
echo "AUR package update check timed out" >>"$errors_tmp"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "AUR package update check failed" >>"$errors_tmp"
|
||||||
|
sed '/^[[:space:]]*$/d' "$tmp_dir/yay.err" >>"$errors_tmp" || true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
strip_update_output "$aur_raw" "$aur_tmp"
|
||||||
|
grep -vw '\[ignored\]$' "$aur_tmp" >"$aur_tmp.filtered" || true
|
||||||
|
mv "$aur_tmp.filtered" "$aur_tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_system_packages
|
||||||
|
check_aur_packages
|
||||||
|
|
||||||
|
cp "$packages_tmp" "$packages_file"
|
||||||
|
cp "$aur_tmp" "$aur_file"
|
||||||
|
cat "$packages_tmp" "$aur_tmp" >"$available_file"
|
||||||
|
date --iso-8601=seconds >"$checked_at_file"
|
||||||
|
|
||||||
|
if [[ -s $errors_tmp ]]; then
|
||||||
|
cp "$errors_tmp" "$error_file"
|
||||||
|
else
|
||||||
|
rm -f "$error_file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -s $available_file ]]; then
|
||||||
|
cat "$available_file"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f /tmp/omarchy-update-available.$$
|
if [[ -s $error_file ]]; then
|
||||||
echo "Omarchy is up to date"
|
cat "$error_file" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "System is up to date"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# omarchy:summary=Ensure the status bar icon offering the available update is removed
|
|
||||||
|
|
||||||
omarchy-shell -q SystemUpdate refresh
|
|
||||||
exit 0
|
|
||||||
@@ -1,13 +1,29 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# omarchy:summary=Remove orphaned system packages after updates
|
# omarchy:summary=Review and optionally remove orphaned system packages after updates
|
||||||
# omarchy:requires-sudo=true
|
# omarchy:requires-sudo=true
|
||||||
|
|
||||||
orphans=$(pacman -Qtdq || true)
|
set -e
|
||||||
if [[ -n $orphans ]]; then
|
|
||||||
echo -e "\e[32m\nRemove orphan system packages\e[0m"
|
mapfile -t orphans < <(pacman -Qtdq 2>/dev/null || true)
|
||||||
for pkg in $orphans; do
|
(( ${#orphans[@]} )) || exit 0
|
||||||
sudo pacman -Rs --noconfirm "$pkg" || true
|
|
||||||
done
|
echo -e "\e[32m\nOrphan system packages\e[0m"
|
||||||
|
printf ' %s\n' "${orphans[@]}"
|
||||||
|
echo
|
||||||
|
|
||||||
|
if [[ ! -t 0 || ! -t 1 ]]; then
|
||||||
|
echo "${#orphans[@]} orphaned package(s) found. Re-run omarchy-update-orphan-pkgs in a terminal to review/remove them."
|
||||||
echo
|
echo
|
||||||
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if ! gum confirm --default=false "Remove ${#orphans[@]} orphaned package(s)?"; then
|
||||||
|
echo "Keeping orphaned packages."
|
||||||
|
echo
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "\e[32m\nRemoving orphan system packages\e[0m"
|
||||||
|
sudo pacman -Rns "${orphans[@]}"
|
||||||
|
echo
|
||||||
|
|||||||
Executable
+64
@@ -0,0 +1,64 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Prevent direct pacman system upgrades from bypassing omarchy update.
|
||||||
|
# omarchy:hidden=true
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ ${OMARCHY_UPDATE_PACMAN:-} == "1" || ${OMARCHY_ALLOW_DIRECT_PACMAN:-} == "1" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
pacman_args=()
|
||||||
|
if [[ -n ${OMARCHY_PACMAN_CMDLINE:-} ]]; then
|
||||||
|
read -r -a pacman_args <<<"$OMARCHY_PACMAN_CMDLINE"
|
||||||
|
elif [[ -r /proc/$PPID/cmdline ]]; then
|
||||||
|
mapfile -d '' -t pacman_args <"/proc/$PPID/cmdline" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
is_system_upgrade=0
|
||||||
|
has_sync=0
|
||||||
|
has_sysupgrade=0
|
||||||
|
|
||||||
|
for arg in "${pacman_args[@]}"; do
|
||||||
|
case "$arg" in
|
||||||
|
--sync)
|
||||||
|
has_sync=1
|
||||||
|
;;
|
||||||
|
--sysupgrade)
|
||||||
|
has_sysupgrade=1
|
||||||
|
;;
|
||||||
|
--*)
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
[[ $arg == *S* ]] && has_sync=1
|
||||||
|
[[ $arg == *u* ]] && has_sysupgrade=1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( has_sync && has_sysupgrade )); then
|
||||||
|
is_system_upgrade=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
(( is_system_upgrade )) || exit 0
|
||||||
|
|
||||||
|
cat >&2 <<'MESSAGE'
|
||||||
|
|
||||||
|
Woah partner...
|
||||||
|
|
||||||
|
This looks like a direct pacman system upgrade. Omarchy updates should normally
|
||||||
|
run through:
|
||||||
|
|
||||||
|
omarchy update
|
||||||
|
|
||||||
|
That path handles the update transcript, snapshot, keyrings, package upgrade,
|
||||||
|
migrations, post-update hooks, shell update state, and restart checks together.
|
||||||
|
|
||||||
|
If you really meant to bypass Omarchy for this transaction, rerun pacman with:
|
||||||
|
|
||||||
|
sudo env OMARCHY_ALLOW_DIRECT_PACMAN=1 pacman -Syu
|
||||||
|
|
||||||
|
MESSAGE
|
||||||
|
|
||||||
|
exit 1
|
||||||
@@ -1,26 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# omarchy:summary=Run the full Omarchy update pipeline
|
# omarchy:summary=Compatibility wrapper for omarchy-update -y.
|
||||||
|
# omarchy:hidden=true
|
||||||
# omarchy:requires-sudo=true
|
# omarchy:requires-sudo=true
|
||||||
|
|
||||||
set -e
|
exec omarchy-update -y
|
||||||
|
|
||||||
# Ensure screensaver/sleep doesn't set in during updates
|
|
||||||
hyprctl dispatch 'hl.dsp.window.tag({ tag = "+noidle" })' &>/dev/null || hyprctl dispatch tagwindow +noidle &>/dev/null || true
|
|
||||||
|
|
||||||
# Perform all update steps
|
|
||||||
omarchy-update-keyring
|
|
||||||
omarchy-update-available-reset
|
|
||||||
omarchy-update-system-pkgs
|
|
||||||
omarchy-migrate
|
|
||||||
omarchy-update-aur-pkgs
|
|
||||||
omarchy-update-mise
|
|
||||||
omarchy-update-orphan-pkgs
|
|
||||||
omarchy-hook post-update
|
|
||||||
|
|
||||||
omarchy-update-analyze-logs
|
|
||||||
|
|
||||||
omarchy-update-restart
|
|
||||||
|
|
||||||
# Re-enable screensaver/sleep after updates
|
|
||||||
hyprctl dispatch 'hl.dsp.window.tag({ tag = "-noidle" })' &>/dev/null || hyprctl dispatch tagwindow -- -noidle &>/dev/null || true
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ echo -e "\e[32m\nUpdate system packages\e[0m"
|
|||||||
# Transition --overwrite: previous script-installed Omarchy wrote these paths
|
# Transition --overwrite: previous script-installed Omarchy wrote these paths
|
||||||
# as unowned files; pacman would refuse the omarchy-settings upgrade on first
|
# as unowned files; pacman would refuse the omarchy-settings upgrade on first
|
||||||
# encounter. Drop each entry once the transition release is the baseline.
|
# encounter. Drop each entry once the transition release is the baseline.
|
||||||
sudo pacman -Syyu --noconfirm \
|
sudo env OMARCHY_UPDATE_PACMAN=1 pacman -Syu --noconfirm \
|
||||||
--overwrite '/etc/docker/daemon.json' \
|
--overwrite '/etc/docker/daemon.json' \
|
||||||
--overwrite '/etc/gnupg/dirmngr.conf' \
|
--overwrite '/etc/gnupg/dirmngr.conf' \
|
||||||
--overwrite '/etc/modprobe.d/omarchy-usb-autosuspend.conf' \
|
--overwrite '/etc/modprobe.d/omarchy-usb-autosuspend.conf' \
|
||||||
|
|||||||
Executable
+6
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy:summary=Compatibility wrapper for omarchy-migrate-notify.
|
||||||
|
# omarchy:hidden=true
|
||||||
|
|
||||||
|
exec omarchy-migrate-notify "$@"
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# omarchy:summary=No-op now that omarchy-update-perform is responsible for idle management.
|
|
||||||
# omarchy:hidden=true
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[Trigger]
|
||||||
|
Operation = Upgrade
|
||||||
|
Type = Package
|
||||||
|
Target = *
|
||||||
|
|
||||||
|
[Action]
|
||||||
|
Description = Checking Omarchy update entrypoint...
|
||||||
|
When = PreTransaction
|
||||||
|
Depends = omarchy
|
||||||
|
Exec = /usr/bin/omarchy-update-pacman-guard
|
||||||
|
AbortOnFail
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Watch for Omarchy user migrations
|
||||||
|
|
||||||
|
[Path]
|
||||||
|
PathExistsGlob=/usr/share/omarchy/migrations/user/*.sh
|
||||||
|
PathModified=/usr/share/omarchy/migrations/user
|
||||||
|
Unit=omarchy-update-user-notify.service
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=graphical-session.target
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Notify about pending Omarchy user migrations
|
||||||
|
ConditionPathExistsGlob=/usr/share/omarchy/migrations/user/*.sh
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/bin/omarchy-migrate-notify
|
||||||
+50
-8
@@ -54,12 +54,16 @@ bin/omarchy-debug,
|
|||||||
bin/omarchy-debug-idle,
|
bin/omarchy-debug-idle,
|
||||||
bin/omarchy-upload-log ──► omarchy-settings /usr/bin/ (needed before omarchy is installed)
|
bin/omarchy-upload-log ──► omarchy-settings /usr/bin/ (needed before omarchy is installed)
|
||||||
|
|
||||||
|
default/libalpm/hooks/00-omarchy-update-guard.hook
|
||||||
|
──► omarchy /usr/share/libalpm/hooks/00-omarchy-update-guard.hook
|
||||||
|
|
||||||
install/** ──► omarchy /usr/share/omarchy/install/
|
install/** ──► omarchy /usr/share/omarchy/install/
|
||||||
migrations/** ──► omarchy /usr/share/omarchy/migrations/
|
migrations/system/** ──► omarchy /usr/share/omarchy/migrations/system/
|
||||||
|
migrations/user/** ──► omarchy /usr/share/omarchy/migrations/user/
|
||||||
themes/** ──► omarchy /usr/share/omarchy/themes/
|
themes/** ──► omarchy /usr/share/omarchy/themes/
|
||||||
shell/** ──► omarchy /usr/share/omarchy/shell/
|
shell/** ──► omarchy /usr/share/omarchy/shell/
|
||||||
version ──► omarchy /usr/share/omarchy/version
|
version ──► omarchy /usr/share/omarchy/version
|
||||||
+ /etc/skel/.local/state/omarchy/migrations/*
|
+ /etc/skel/.local/state/omarchy/migrations/user/*
|
||||||
|
|
||||||
config/** ──► omarchy-settings /etc/skel/.config/** (seeds new users)
|
config/** ──► omarchy-settings /etc/skel/.config/** (seeds new users)
|
||||||
/usr/share/omarchy/config/** (resync source)
|
/usr/share/omarchy/config/** (resync source)
|
||||||
@@ -95,7 +99,7 @@ default/** ──► omarchy-settings /usr/share/omarchy
|
|||||||
│ + symlink /etc/fonts/conf.d/30-omarchy.conf
|
│ + symlink /etc/fonts/conf.d/30-omarchy.conf
|
||||||
├─ xdg-terminal-exec/*.list /usr/share/xdg-terminal-exec/
|
├─ xdg-terminal-exec/*.list /usr/share/xdg-terminal-exec/
|
||||||
├─ applications/mimeapps.list /usr/share/applications/mimeapps.list
|
├─ applications/mimeapps.list /usr/share/applications/mimeapps.list
|
||||||
├─ systemd/user/*.service /usr/lib/systemd/user/
|
├─ systemd/user/*.{service,path} /usr/lib/systemd/user/
|
||||||
├─ systemd/system-sleep/unmount-fuse /usr/lib/systemd/system-sleep/
|
├─ systemd/system-sleep/unmount-fuse /usr/lib/systemd/system-sleep/
|
||||||
├─ fonts/omarchy/omarchy.ttf /usr/share/fonts/omarchy/
|
├─ fonts/omarchy/omarchy.ttf /usr/share/fonts/omarchy/
|
||||||
├─ sddm/omarchy/ /usr/share/sddm/themes/omarchy/
|
├─ sddm/omarchy/ /usr/share/sddm/themes/omarchy/
|
||||||
@@ -164,7 +168,7 @@ It only does the things `/etc/skel` can't:
|
|||||||
- `omarchy-refresh-applications` (composes generated `.desktop` launchers).
|
- `omarchy-refresh-applications` (composes generated `.desktop` launchers).
|
||||||
- Sources `install/user/all.sh` — theme, git, mise, keyring, per-user
|
- Sources `install/user/all.sh` — theme, git, mise, keyring, per-user
|
||||||
hardware quirks (asus mic/mixer, framework f13 audio, …).
|
hardware quirks (asus mic/mixer, framework f13 audio, …).
|
||||||
- On `--first-install`, marks every shipped migration as already applied
|
- On `--first-install`, marks every shipped user migration as already applied
|
||||||
for the freshly-created user.
|
for the freshly-created user.
|
||||||
|
|
||||||
Idempotency marker: `~/.local/state/omarchy/finalize-user.done`.
|
Idempotency marker: `~/.local/state/omarchy/finalize-user.done`.
|
||||||
@@ -173,6 +177,43 @@ The ISO calls it as `omarchy-finalize-user --force --first-install` in the
|
|||||||
target chroot as the install user, after `omarchy-setup-system` has finished
|
target chroot as the install user, after `omarchy-setup-system` has finished
|
||||||
the root-side work.
|
the root-side work.
|
||||||
|
|
||||||
|
## Migrations (`omarchy-migrate`)
|
||||||
|
|
||||||
|
See [`migrations.md`](migrations.md) for the full migration model, authoring
|
||||||
|
guidelines, and troubleshooting notes.
|
||||||
|
|
||||||
|
Omarchy migrations are split by scope:
|
||||||
|
|
||||||
|
- `migrations/system/*.sh` — root, noninteractive, safe to run from pacman.
|
||||||
|
The `omarchy` package `post_upgrade()` runs `omarchy-migrate-system` after an
|
||||||
|
`omarchy` package upgrade, so even explicit direct-pacman bypasses still get
|
||||||
|
system migrations applied. Completion state lives in
|
||||||
|
`/var/lib/omarchy/migrations/system/`.
|
||||||
|
- `migrations/user/*.sh` — current user/session, may be interactive, and may
|
||||||
|
touch `~/.config`, `~/.local`, user systemd, DBus, browser prefs, etc.
|
||||||
|
Completion state lives in `~/.local/state/omarchy/migrations/user/`.
|
||||||
|
|
||||||
|
Package upgrades happen as root, but user migrations must run as each user and
|
||||||
|
may be interactive. There is no root-owned "user migration required" marker. A
|
||||||
|
user migration is pending only when a script exists in `migrations/user/` and
|
||||||
|
that user's matching state file is missing.
|
||||||
|
|
||||||
|
Each graphical user has `omarchy-update-user-notify.path` watching the packaged
|
||||||
|
user migration directory. When that directory changes, or when the path unit is
|
||||||
|
started on login, `omarchy-update-user-notify.service` runs
|
||||||
|
`omarchy-migrate-notify` as that user. The notifier checks
|
||||||
|
`omarchy-migrate --pending user`. If this user has missing migration state, it
|
||||||
|
shows a notification that opens a terminal for `omarchy-migrate`. The notifier
|
||||||
|
never runs user migrations in the background.
|
||||||
|
|
||||||
|
`omarchy-migrate` waits for any active pacman transaction to finish, runs
|
||||||
|
pending system migrations, then runs pending user migrations. It does not need
|
||||||
|
`--force`; migrations happen when state files are missing. For watchers and
|
||||||
|
diagnostics, `omarchy-migrate --pending [all|system|user]` prints
|
||||||
|
scope-prefixed pending migration filenames and exits `0` when any are pending.
|
||||||
|
`omarchy update` runs `omarchy-migrate` after the package transaction in the
|
||||||
|
already-visible update terminal, then runs `omarchy-hook post-update`.
|
||||||
|
|
||||||
## First-run (`omarchy-first-run`)
|
## First-run (`omarchy-first-run`)
|
||||||
|
|
||||||
Runs once on first interactive login, after the user manager is live. Used
|
Runs once on first interactive login, after the user manager is live. Used
|
||||||
@@ -183,9 +224,9 @@ systemd instance:
|
|||||||
Voxtype post-update hook.
|
Voxtype post-update hook.
|
||||||
- `install/user/first-run/enable-user-units.sh` — `systemctl --user enable`
|
- `install/user/first-run/enable-user-units.sh` — `systemctl --user enable`
|
||||||
the shipped user units (`bt-agent`, `omarchy-sleep-lock`,
|
the shipped user units (`bt-agent`, `omarchy-sleep-lock`,
|
||||||
`omarchy-recover-internal-monitor`). Done here, not at finalize, because
|
`omarchy-recover-internal-monitor`, `omarchy-update-user-notify.path`). Done here, not at finalize, because
|
||||||
the user manager isn't reachable from the ISO chroot; `ConditionPath*`
|
the user manager isn't reachable from the ISO chroot; `ConditionPath*`
|
||||||
in the unit files keeps them inert on hardware they don't apply to.
|
in the unit files keeps services inert when they don't apply.
|
||||||
- `install/user/first-run/gnome-theme.sh`,
|
- `install/user/first-run/gnome-theme.sh`,
|
||||||
`install/user/first-run/gtk-primary-paste.sh` — GNOME/GTK settings that
|
`install/user/first-run/gtk-primary-paste.sh` — GNOME/GTK settings that
|
||||||
need the dconf daemon.
|
need the dconf daemon.
|
||||||
@@ -236,10 +277,11 @@ without backup.
|
|||||||
| Default file at `~/.config/foo/` | `config/foo/` |
|
| Default file at `~/.config/foo/` | `config/foo/` |
|
||||||
| `/etc/` drop-in we own outright | `etc/` |
|
| `/etc/` drop-in we own outright | `etc/` |
|
||||||
| `/etc/` file owned by an upstream package | `default/`, then add to `etc-overrides` in `omarchy-settings` PKGBUILD + scriptlet |
|
| `/etc/` file owned by an upstream package | `default/`, then add to `etc-overrides` in `omarchy-settings` PKGBUILD + scriptlet |
|
||||||
| Package-owned system file (e.g. systemd user service in `/usr/lib`) | `default/`, document the mapping in `default/package-defaults.tsv`, then add the `install -Dm644` line in `omarchy-settings` PKGBUILD |
|
| Package-owned system file (e.g. systemd user service/path in `/usr/lib`) | `default/`, document the mapping in `default/package-defaults.tsv`, then add the `install -Dm644` line in `omarchy-settings` PKGBUILD |
|
||||||
| Per-user file that's static but lives outside `~/.config` | `default/`, then add `install -Dm644 ... $pkgdir/etc/skel/...` in `omarchy-settings` PKGBUILD |
|
| Per-user file that's static but lives outside `~/.config` | `default/`, then add `install -Dm644 ... $pkgdir/etc/skel/...` in `omarchy-settings` PKGBUILD |
|
||||||
| Runtime tweak that needs `$HOME` or live system state | extend `omarchy-finalize-user`, or add a per-user leaf under `install/user/` and wire into `install/user/all.sh` |
|
| Runtime tweak that needs `$HOME` or live system state | extend `omarchy-finalize-user`, or add a per-user leaf under `install/user/` and wire into `install/user/all.sh` |
|
||||||
| One-time root-side setup step | `install/config/*.sh` or `install/hardware/*.sh`, wire into `omarchy-setup-system` or `install/hardware/all.sh` |
|
| One-time root-side setup step | `install/config/*.sh` or `install/hardware/*.sh`, wire into `omarchy-setup-system` or `install/hardware/all.sh` |
|
||||||
|
| Noninteractive root fix for existing installs | `migrations/system/<unix-timestamp>.sh` |
|
||||||
|
| User/session fix for existing installs | `migrations/user/<unix-timestamp>.sh` |
|
||||||
| User-facing `omarchy-*` command | `bin/omarchy-<group>-<verb>` — see `GROUP_DESCRIPTIONS` in `bin/omarchy` |
|
| User-facing `omarchy-*` command | `bin/omarchy-<group>-<verb>` — see `GROUP_DESCRIPTIONS` in `bin/omarchy` |
|
||||||
| New theme | `themes/<name>/` (+ matching templates under `default/themed/` if they need theme colors) |
|
| New theme | `themes/<name>/` (+ matching templates under `default/themed/` if they need theme colors) |
|
||||||
| One-shot fix for installed systems | `migrations/<unix-timestamp>.sh` (use `omarchy-dev-add-migration --no-edit`) |
|
|
||||||
|
|||||||
@@ -0,0 +1,307 @@
|
|||||||
|
# Omarchy migrations
|
||||||
|
|
||||||
|
Omarchy migrations are one-time repair scripts for existing installs. They are
|
||||||
|
used when a package update needs to change state that pacman cannot safely own by
|
||||||
|
itself.
|
||||||
|
|
||||||
|
They are for both humans and agents:
|
||||||
|
|
||||||
|
- Users should know when migrations run, where state is stored, and how to retry
|
||||||
|
or inspect pending work.
|
||||||
|
- Agents should know which migration scope to use, how to write safe migration
|
||||||
|
scripts, and what should **not** be a migration.
|
||||||
|
|
||||||
|
## The two migration scopes
|
||||||
|
|
||||||
|
Omarchy migrations are split by execution context:
|
||||||
|
|
||||||
|
```text
|
||||||
|
migrations/system/*.sh
|
||||||
|
migrations/user/*.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### System migrations
|
||||||
|
|
||||||
|
System migrations run as root and must be noninteractive.
|
||||||
|
|
||||||
|
Use system migrations for machine-wide state, for example:
|
||||||
|
|
||||||
|
- `/etc` drop-ins or legacy config cleanup
|
||||||
|
- `/boot` / bootloader cleanup
|
||||||
|
- systemd system units or service state
|
||||||
|
- hardware quirks
|
||||||
|
- package-owned layout transitions
|
||||||
|
|
||||||
|
State lives in:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/var/lib/omarchy/migrations/system/<migration filename>
|
||||||
|
```
|
||||||
|
|
||||||
|
### User migrations
|
||||||
|
|
||||||
|
User migrations run as the current graphical/login user and may be interactive.
|
||||||
|
|
||||||
|
Use user migrations for per-user or session-aware state, for example:
|
||||||
|
|
||||||
|
- `~/.config` changes
|
||||||
|
- `~/.local` state
|
||||||
|
- user systemd units
|
||||||
|
- browser/editor preferences
|
||||||
|
- DBus/session-dependent updates
|
||||||
|
- prompts that need to happen in a visible terminal
|
||||||
|
|
||||||
|
State lives in:
|
||||||
|
|
||||||
|
```text
|
||||||
|
~/.local/state/omarchy/migrations/user/<migration filename>
|
||||||
|
```
|
||||||
|
|
||||||
|
A user migration is pending for a given user only when the script exists under
|
||||||
|
`/usr/share/omarchy/migrations/user/` and that user's matching state file is
|
||||||
|
missing.
|
||||||
|
|
||||||
|
There is no global root-owned “user migrations required” queue.
|
||||||
|
|
||||||
|
## When migrations run
|
||||||
|
|
||||||
|
### During `omarchy update`
|
||||||
|
|
||||||
|
`omarchy update` is the normal update path. It runs package updates, then:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omarchy-migrate
|
||||||
|
omarchy-hook post-update
|
||||||
|
```
|
||||||
|
|
||||||
|
`omarchy-migrate`:
|
||||||
|
|
||||||
|
1. waits for any active pacman transaction to finish
|
||||||
|
2. runs pending system migrations
|
||||||
|
3. runs pending user migrations for the current user
|
||||||
|
|
||||||
|
System migrations usually already ran during the pacman transaction via the
|
||||||
|
`omarchy` package `post_upgrade()`, but the second check is intentional and
|
||||||
|
idempotent.
|
||||||
|
|
||||||
|
### During direct pacman updates
|
||||||
|
|
||||||
|
Raw `sudo pacman -Syu` is guarded. Users should normally run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omarchy update
|
||||||
|
```
|
||||||
|
|
||||||
|
If a user explicitly bypasses the guard, the `omarchy` package still runs system
|
||||||
|
migrations from `post_upgrade()`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omarchy-migrate-system
|
||||||
|
```
|
||||||
|
|
||||||
|
User migrations are never run by pacman. Instead, the user session watches the
|
||||||
|
packaged user migration directory and runs a notifier. The notifier checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omarchy-migrate --pending user
|
||||||
|
```
|
||||||
|
|
||||||
|
If that user has pending user migrations, it shows a notification that opens a
|
||||||
|
terminal for:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omarchy-migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
The notifier never runs user migrations silently in the background.
|
||||||
|
|
||||||
|
### Manually
|
||||||
|
|
||||||
|
Users can safely run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omarchy-migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
at any time. Already-completed migrations are skipped.
|
||||||
|
|
||||||
|
## Inspecting pending migrations
|
||||||
|
|
||||||
|
Use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omarchy-migrate --pending
|
||||||
|
omarchy-migrate --pending system
|
||||||
|
omarchy-migrate --pending user
|
||||||
|
```
|
||||||
|
|
||||||
|
Exit behavior:
|
||||||
|
|
||||||
|
- `0` — one or more matching migrations are pending
|
||||||
|
- non-zero — no matching migrations are pending
|
||||||
|
|
||||||
|
Output is scope-prefixed:
|
||||||
|
|
||||||
|
```text
|
||||||
|
system/100-system.sh
|
||||||
|
user/200-user.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The lower-level runners also support `--pending` and print filenames without the
|
||||||
|
scope prefix:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omarchy-migrate-system --pending
|
||||||
|
omarchy-migrate-user --pending
|
||||||
|
```
|
||||||
|
|
||||||
|
Prefer `omarchy-migrate --pending ...` in user-facing tools and watchers.
|
||||||
|
|
||||||
|
## Writing migrations
|
||||||
|
|
||||||
|
Create a migration with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omarchy-dev-add-migration system --no-edit
|
||||||
|
omarchy-dev-add-migration user --no-edit
|
||||||
|
```
|
||||||
|
|
||||||
|
Choose the scope based on the state being changed, not on convenience.
|
||||||
|
|
||||||
|
### File format
|
||||||
|
|
||||||
|
Migrations are plain shell files:
|
||||||
|
|
||||||
|
- filename: unix timestamp, generated by `omarchy-dev-add-migration`
|
||||||
|
- mode: `0644`
|
||||||
|
- no shebang
|
||||||
|
- start with an `echo` describing the migration
|
||||||
|
- use `$OMARCHY_PATH` for Omarchy-owned files
|
||||||
|
|
||||||
|
Example system migration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "Remove legacy Limine config"
|
||||||
|
|
||||||
|
rm -f /boot/EFI/limine/limine.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
Example user migration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "Refresh user app launchers"
|
||||||
|
|
||||||
|
omarchy-refresh-applications
|
||||||
|
```
|
||||||
|
|
||||||
|
### Execution model
|
||||||
|
|
||||||
|
Migration runners execute scripts with strict shell settings:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash -euo pipefail <migration>
|
||||||
|
```
|
||||||
|
|
||||||
|
`$OMARCHY_PATH` is exported into the migration process.
|
||||||
|
|
||||||
|
A migration is marked complete only after the script exits successfully. If it
|
||||||
|
fails, the marker is not written and the migration will be retried later.
|
||||||
|
|
||||||
|
Because failed migrations can be retried after doing partial work, migrations
|
||||||
|
must be idempotent. Prefer operations that are safe to run more than once:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.config/example
|
||||||
|
rm -f ~/.config/example/legacy.conf
|
||||||
|
install -Dm644 "$OMARCHY_PATH/default/example.conf" ~/.config/example/example.conf
|
||||||
|
systemctl --user enable --now some-unit.service || true
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `|| true` only when a failure is genuinely acceptable.
|
||||||
|
|
||||||
|
## What belongs in a migration?
|
||||||
|
|
||||||
|
Good migration uses:
|
||||||
|
|
||||||
|
- deleting or moving legacy files that block the packaged layout
|
||||||
|
- marking or enabling new service state
|
||||||
|
- migrating old config paths to new config paths
|
||||||
|
- repairing known bad state from an earlier Omarchy release
|
||||||
|
- one-time compatibility for existing installs
|
||||||
|
|
||||||
|
Bad migration uses:
|
||||||
|
|
||||||
|
- fresh-install setup that belongs in `install/` or `/etc/skel`
|
||||||
|
- package installation that belongs in package dependencies or package lists
|
||||||
|
- recurring maintenance
|
||||||
|
- arbitrary cleanup that should be a user command
|
||||||
|
- pre-4 upgrade work that belongs in `omarchy-upgrade-to-4`
|
||||||
|
- hidden user/session work from pacman
|
||||||
|
|
||||||
|
## Fresh installs and new users
|
||||||
|
|
||||||
|
Fresh users created from the packaged layout should not have to run historical
|
||||||
|
user migrations. The package seeds user migration markers into `/etc/skel` so
|
||||||
|
new users start with shipped user migrations marked complete.
|
||||||
|
|
||||||
|
The ISO/finalization path also marks shipped user migrations complete for the
|
||||||
|
freshly-created install user when running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omarchy-finalize-user --first-install
|
||||||
|
```
|
||||||
|
|
||||||
|
Existing users keep their own migration state and run only migrations whose
|
||||||
|
state files are missing.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### See what is pending
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omarchy-migrate --pending
|
||||||
|
```
|
||||||
|
|
||||||
|
### Retry failed migrations
|
||||||
|
|
||||||
|
Fix the underlying problem, then run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omarchy-migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
A failed migration is not marked complete, so it will retry.
|
||||||
|
|
||||||
|
### Re-run a completed migration manually
|
||||||
|
|
||||||
|
Remove its marker, then run the migration command again:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm ~/.local/state/omarchy/migrations/user/<migration>.sh
|
||||||
|
omarchy-migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
For system migrations:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo rm /var/lib/omarchy/migrations/system/<migration>.sh
|
||||||
|
omarchy-migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
Be careful: migrations should be idempotent, but manually removing markers is an
|
||||||
|
advanced troubleshooting step.
|
||||||
|
|
||||||
|
## Agent checklist
|
||||||
|
|
||||||
|
Before adding a migration:
|
||||||
|
|
||||||
|
1. Is this a one-time repair for existing installs?
|
||||||
|
2. Is it system state or user/session state?
|
||||||
|
3. Can it run safely more than once?
|
||||||
|
4. Will it fail loudly if the important work fails?
|
||||||
|
5. Does it avoid hidden interactive work from pacman?
|
||||||
|
6. Does it belong in `omarchy-upgrade-to-4` instead?
|
||||||
|
7. Did you add or update tests if behavior changed?
|
||||||
|
|
||||||
|
If the answer to any of these is unclear, stop and ask before adding the
|
||||||
|
migration.
|
||||||
@@ -0,0 +1,318 @@
|
|||||||
|
# Omarchy update process
|
||||||
|
|
||||||
|
This document describes the intended update behavior now that Omarchy is
|
||||||
|
package-backed. It covers the blessed update path plus what happens when a user attempts to
|
||||||
|
bypass it:
|
||||||
|
|
||||||
|
1. `omarchy update` — the blessed interactive Omarchy update flow.
|
||||||
|
2. `sudo pacman -Syu` — guarded by Omarchy and aborted with instructions unless
|
||||||
|
the user explicitly bypasses the guard.
|
||||||
|
|
||||||
|
The design goal is:
|
||||||
|
|
||||||
|
- System/root migrations should run automatically from package transactions when
|
||||||
|
safe.
|
||||||
|
- User/session migrations should never run invisibly from pacman because they may
|
||||||
|
need `$HOME`, DBus/session state, a graphical session, or user interaction.
|
||||||
|
- Users who bypass `omarchy update` should still get system migrations from
|
||||||
|
pacman and should be notified only when their own user migration state is
|
||||||
|
missing a shipped user migration.
|
||||||
|
|
||||||
|
## State and coordination files
|
||||||
|
|
||||||
|
| Path | Owner | Purpose |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `${XDG_RUNTIME_DIR:-/tmp}/omarchy-update.lock` | user | Prevent overlapping update runs. Owned by `omarchy-update`; compatibility wrappers inherit/respect it. |
|
||||||
|
| `/tmp/omarchy-update.log` | user | Transcript of `omarchy update`, used by `omarchy-update-analyze-logs`. |
|
||||||
|
| `~/.local/state/omarchy/updates/` | user | Update-check state for the shell widget (`packages`, `aur`, `available`, `checked-at`, `error`). |
|
||||||
|
| `/var/lib/omarchy/migrations/system/` | root | System migration markers. |
|
||||||
|
| `~/.local/state/omarchy/migrations/user/` | user | User migration markers. |
|
||||||
|
| `~/.local/state/omarchy/reboot-required` | user | Optional reboot marker checked by `omarchy-update-restart`. |
|
||||||
|
| `~/.local/state/omarchy/restart-*-required` | user | Optional service/app restart markers checked by `omarchy-update-restart`. |
|
||||||
|
|
||||||
|
## Migration layout
|
||||||
|
|
||||||
|
See [`migrations.md`](migrations.md) for the full migration model, authoring
|
||||||
|
guidelines, and troubleshooting notes.
|
||||||
|
|
||||||
|
Migrations are scoped by directory:
|
||||||
|
|
||||||
|
```text
|
||||||
|
migrations/system/*.sh
|
||||||
|
migrations/user/*.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### System migrations
|
||||||
|
|
||||||
|
System migrations are root-owned, noninteractive fixes for existing installs.
|
||||||
|
They may touch `/etc`, `/usr`, `/boot`, system services, hardware configuration,
|
||||||
|
and other machine-wide state.
|
||||||
|
|
||||||
|
Runner:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omarchy-migrate-system
|
||||||
|
```
|
||||||
|
|
||||||
|
Completion state:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/var/lib/omarchy/migrations/system/<migration filename>
|
||||||
|
```
|
||||||
|
|
||||||
|
Pacman integration:
|
||||||
|
|
||||||
|
```text
|
||||||
|
pkgbuilds/omarchy/omarchy.install post_upgrade()
|
||||||
|
```
|
||||||
|
|
||||||
|
The `omarchy` package calls `omarchy-migrate-system` from `post_upgrade()`.
|
||||||
|
The runner is idempotent, so repeated calls only check the state files.
|
||||||
|
|
||||||
|
### User migrations
|
||||||
|
|
||||||
|
User migrations are current-user/session fixes. They may touch `~/.config`,
|
||||||
|
`~/.local`, user systemd units, browser/editor preferences, DBus/session state,
|
||||||
|
or ask questions.
|
||||||
|
|
||||||
|
Runner:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omarchy-migrate-user
|
||||||
|
```
|
||||||
|
|
||||||
|
Public command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omarchy-migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
Completion state:
|
||||||
|
|
||||||
|
```text
|
||||||
|
~/.local/state/omarchy/migrations/user/<migration filename>
|
||||||
|
```
|
||||||
|
|
||||||
|
A user migration is pending only when a script exists in `migrations/user/` and
|
||||||
|
that user's matching state file is missing. There is no global root-owned queue
|
||||||
|
for user migrations.
|
||||||
|
|
||||||
|
`omarchy-migrate` is the public command. It waits for any active pacman
|
||||||
|
transaction to finish, runs pending system migrations, then runs pending user
|
||||||
|
migrations. It does not need `--force`; migrations should happen when they are
|
||||||
|
pending.
|
||||||
|
|
||||||
|
For watchers and diagnostics, `omarchy-migrate --pending [all|system|user]`
|
||||||
|
prints pending migration names and exits `0` when any are pending. Output is
|
||||||
|
scope-prefixed, for example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
system/100-system.sh
|
||||||
|
user/200-user.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
When no matching migrations are pending, it prints nothing and exits non-zero.
|
||||||
|
|
||||||
|
## Raw pacman guard
|
||||||
|
|
||||||
|
The `omarchy` package installs an ALPM pre-transaction hook alongside its guard
|
||||||
|
binary:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/usr/share/libalpm/hooks/00-omarchy-update-guard.hook
|
||||||
|
/usr/bin/omarchy-update-pacman-guard
|
||||||
|
```
|
||||||
|
|
||||||
|
It triggers on package upgrades and runs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omarchy-update-pacman-guard
|
||||||
|
```
|
||||||
|
|
||||||
|
The guard detects direct pacman system-upgrade commands like `pacman -Syu` or
|
||||||
|
`pacman --sync --refresh --sysupgrade`. If the upgrade was not launched by an
|
||||||
|
Omarchy update command, the hook exits non-zero with `AbortOnFail`, which stops
|
||||||
|
the transaction before packages are changed.
|
||||||
|
|
||||||
|
`omarchy-update-system-pkgs`, `omarchy-refresh-pacman`, `omarchy-reinstall-pkgs`,
|
||||||
|
and the v4 upgrader run pacman through:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
env OMARCHY_UPDATE_PACMAN=1 pacman ...
|
||||||
|
```
|
||||||
|
|
||||||
|
so the guard allows Omarchy-owned update flows. A user can intentionally bypass
|
||||||
|
the guard with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo env OMARCHY_ALLOW_DIRECT_PACMAN=1 pacman -Syu
|
||||||
|
```
|
||||||
|
|
||||||
|
The guard does not start `omarchy update` itself because pacman is already in a
|
||||||
|
transaction setup path; it only aborts with instructions.
|
||||||
|
|
||||||
|
## Path 1: `omarchy update`
|
||||||
|
|
||||||
|
High-level flow:
|
||||||
|
|
||||||
|
```text
|
||||||
|
omarchy-update
|
||||||
|
├─ ensure transcript logging through script(1) → /tmp/omarchy-update.log
|
||||||
|
├─ acquire update lock
|
||||||
|
├─ confirm unless -y
|
||||||
|
├─ create snapper snapshot, if snapper is installed
|
||||||
|
└─ run update pipeline
|
||||||
|
├─ block system sleep and temporarily enable shell stay-awake mode
|
||||||
|
├─ omarchy-update-keyring
|
||||||
|
├─ omarchy-update-system-pkgs
|
||||||
|
├─ omarchy-migrate
|
||||||
|
├─ omarchy-hook post-update
|
||||||
|
├─ omarchy-update-aur-pkgs
|
||||||
|
├─ omarchy-update-mise
|
||||||
|
├─ omarchy-update-orphan-pkgs
|
||||||
|
├─ omarchy-update-analyze-logs
|
||||||
|
├─ omarchy-update-available, then refresh/clear shell indicator
|
||||||
|
├─ omarchy-update-restart
|
||||||
|
└─ release sleep inhibitor and restore shell idle state, if changed
|
||||||
|
```
|
||||||
|
|
||||||
|
Important behavior:
|
||||||
|
|
||||||
|
- `omarchy update` checks/runs system and user migrations in the same visible
|
||||||
|
terminal via `omarchy-migrate`.
|
||||||
|
- `omarchy update` still benefits from pacman-triggered system migrations
|
||||||
|
because the system migration hook runs as part of the pacman transaction.
|
||||||
|
- A failure should leave enough output in `/tmp/omarchy-update.log` and the
|
||||||
|
terminal transcript to debug.
|
||||||
|
|
||||||
|
## Path 2: direct `sudo pacman -Syu` attempt
|
||||||
|
|
||||||
|
High-level flow:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sudo pacman -Syu
|
||||||
|
├─ pre-transaction guard aborts and tells the user to run omarchy update
|
||||||
|
└─ if explicitly bypassed, upgrades omarchy and related packages
|
||||||
|
├─ omarchy post_upgrade runs omarchy-migrate-system as root
|
||||||
|
└─ user session notices migration directory changes
|
||||||
|
├─ omarchy-update-user-notify.path triggers, if enabled
|
||||||
|
├─ omarchy-migrate-notify checks omarchy-migrate --pending user
|
||||||
|
├─ if this user has missing migration state, show notification
|
||||||
|
└─ click opens terminal: omarchy-migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
Fallbacks:
|
||||||
|
|
||||||
|
- `omarchy-first-run` enables the user notification path unit.
|
||||||
|
- `omarchy-first-run` also invokes `omarchy-migrate-notify` on graphical
|
||||||
|
startup, so users who updated before the path unit existed still get prompted
|
||||||
|
if they have missing user migration state.
|
||||||
|
- The notifier is only a prompt. It does not run user migrations in the
|
||||||
|
background.
|
||||||
|
- Direct pacman updates do not run `omarchy-hook post-update` unless the user
|
||||||
|
explicitly runs that hook; without a package-update marker, the only user-side
|
||||||
|
pending state we can derive is missing user migration markers.
|
||||||
|
|
||||||
|
## Shell update indicator
|
||||||
|
|
||||||
|
The bar widget `omarchy.system-update` runs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omarchy-update-available
|
||||||
|
```
|
||||||
|
|
||||||
|
`omarchy-update-available` uses `checkupdates` with a temporary database when
|
||||||
|
available, plus `yay -Qua` for AUR updates when foreign packages are installed.
|
||||||
|
It stores the result in:
|
||||||
|
|
||||||
|
```text
|
||||||
|
~/.local/state/omarchy/updates/packages
|
||||||
|
~/.local/state/omarchy/updates/aur
|
||||||
|
~/.local/state/omarchy/updates/available
|
||||||
|
~/.local/state/omarchy/updates/checked-at
|
||||||
|
~/.local/state/omarchy/updates/error
|
||||||
|
```
|
||||||
|
|
||||||
|
Exit codes:
|
||||||
|
|
||||||
|
- `0` — updates are available; stdout is the update list.
|
||||||
|
- non-zero — no updates are available; stdout says the system is up to date.
|
||||||
|
|
||||||
|
The widget uses the line count to show/hide itself. It also watches the
|
||||||
|
`available` state file, so manually running `omarchy-update-available` updates
|
||||||
|
the widget as soon as the file changes. Hovering the update icon opens a panel
|
||||||
|
that lists Omarchy packages first, then all other package updates.
|
||||||
|
|
||||||
|
## Update-related binaries
|
||||||
|
|
||||||
|
This inventory is intentionally opinionated. Some commands are useful as stable
|
||||||
|
leaf commands; others exist mostly because the old update flow accreted small
|
||||||
|
scripts.
|
||||||
|
|
||||||
|
| Binary | Current purpose | Keep? / Question |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `omarchy-update` | Public user command. Adds transcript logging, lock, confirmation, snapshot, sleep/idle inhibitors, package updates, migrations, hooks, update-state refresh, and restart checks. | **Keep.** This is the blessed entry point and owns the update pipeline. |
|
||||||
|
| `omarchy-update-perform` | Hidden compatibility wrapper for `omarchy-update -y`. | **Temporary.** Keep only for old callers; new code should call `omarchy-update` directly. |
|
||||||
|
| `omarchy-update-confirm` | Gum confirmation copy for `omarchy update`. | **Question.** Could be inlined into `omarchy-update`; separate file only helps keep copy isolated. |
|
||||||
|
| `omarchy-update-keyring` | Ensures Omarchy keyring and Arch keyring are current before the main transaction. | **Keep, but review.** It uses targeted `pacman -Sy` for keyring bootstrapping; acceptable for this special case but should remain tightly scoped. |
|
||||||
|
| `omarchy-update-system-pkgs` | Runs `sudo env OMARCHY_UPDATE_PACMAN=1 pacman -Syu --noconfirm` with targeted transition `--overwrite` entries so the ALPM guard allows the transaction and early package-layout conflicts are handled. | **Keep for now.** Small leaf command, clear/testable. |
|
||||||
|
| `omarchy-migrate-system` | Runs root/system migrations from `migrations/system`. Called by `omarchy` package `post_upgrade()` and by `omarchy-migrate` when system migration state is missing. | **Keep.** This is the important direct-`pacman -Syu` integration. |
|
||||||
|
| `omarchy-migrate-user` | Checks `migrations/user` against this user's state and runs only missing user migrations. Supports `--pending` and prints pending filenames. | **Keep internal.** Public users should generally run `omarchy-migrate`. |
|
||||||
|
| `omarchy-migrate` | Public migration command. Waits for pacman, then runs pending system and user migrations. Supports `--pending [all|system|user]` and prints scope-prefixed pending filenames. | **Keep.** This replaces the discarded `omarchy-update-user-finalize` name and no longer needs `--force`. |
|
||||||
|
| `omarchy-update-pacman-guard` | ALPM pre-transaction guard that aborts direct `pacman -Syu` style upgrades unless Omarchy set `OMARCHY_UPDATE_PACMAN=1` or the user explicitly set `OMARCHY_ALLOW_DIRECT_PACMAN=1`. | **Keep internal/hidden.** This is what nudges users back to `omarchy update`. |
|
||||||
|
| `omarchy-migrate-notify` | Internal notification helper for direct pacman updates. Uses `omarchy-migrate --pending user` and shows notification only when this user has pending migrations. | **Keep internal/hidden.** Clear name now that the public command is `omarchy-migrate`. |
|
||||||
|
| `omarchy-update-user-notify` | Hidden compatibility wrapper for `omarchy-migrate-notify`. | **Temporary.** Keep only for old callers. |
|
||||||
|
| `omarchy-update-available` | Update checker for shell widget and post-update refresh. Writes update state. | **Keep.** Could eventually be renamed `omarchy-update-check`, but current name matches widget semantics. |
|
||||||
|
| `omarchy-update-aur-pkgs` | Updates AUR packages with `yay -Sua` if foreign packages exist and AUR is reachable. | **Question.** Omarchy is package-backed now, but users may still install AUR packages. Keep for now. |
|
||||||
|
| `omarchy-update-mise` | Runs `mise up` for mise-managed tools. | **Keep.** Mise-managed tools are intentionally part of the blessed update path. |
|
||||||
|
| `omarchy-update-orphan-pkgs` | Lists orphans and prompts before removal; noninteractive mode never removes. | **Keep for now.** Safe because it is prompt-only. |
|
||||||
|
| `omarchy-update-analyze-logs` | Scans `/tmp/omarchy-update.log` for known failure patterns, currently initramfs generation. | **Keep/expand.** Useful safety net; should grow only for high-signal checks. |
|
||||||
|
| `omarchy-update-restart` | Prompts for reboot after kernel/Hyprland updates and restarts components with `restart-*-required` markers. | **Keep.** Important final step; may eventually include service-restart checks. |
|
||||||
|
| `omarchy-update-firmware` | Manual firmware update command using fwupd. Not part of the normal update pipeline. | **Keep separate.** Firmware is not a routine system update step. |
|
||||||
|
| `omarchy-update-time` | Restarts `systemd-timesyncd`. | **Question.** Not really an update command. Consider renaming/moving under system/time maintenance. |
|
||||||
|
|
||||||
|
## Closed decisions
|
||||||
|
|
||||||
|
1. **System migrations run from the `omarchy` package**
|
||||||
|
- `omarchy.install post_upgrade()` calls `omarchy-migrate-system`.
|
||||||
|
- We do not need a separate system-migration ALPM hook from
|
||||||
|
`omarchy-settings`.
|
||||||
|
|
||||||
|
2. **Root vs user migrations stay separate**
|
||||||
|
- Do not use `su {user}` from pacman to run user migrations.
|
||||||
|
- User migrations need the user's real session and should be visible.
|
||||||
|
- Pending user work is determined only by comparing `migrations/user/*.sh`
|
||||||
|
against `~/.local/state/omarchy/migrations/user/`.
|
||||||
|
|
||||||
|
3. **Migration notification naming**
|
||||||
|
- The real helper is `omarchy-migrate-notify`.
|
||||||
|
- `omarchy-update-user-notify` remains only as a hidden compatibility wrapper.
|
||||||
|
|
||||||
|
4. **Update pipeline ownership**
|
||||||
|
- `omarchy-update` owns the full update pipeline now.
|
||||||
|
- `omarchy-update-perform` is only a hidden compatibility wrapper for
|
||||||
|
`omarchy-update -y`.
|
||||||
|
|
||||||
|
5. **Mise remains in the blessed update path**
|
||||||
|
- `omarchy-update-mise` intentionally runs as part of `omarchy update`.
|
||||||
|
|
||||||
|
6. **Orphan cleanup stays in the update path for now**
|
||||||
|
- It is prompt-only and never removes packages noninteractively.
|
||||||
|
|
||||||
|
7. **Direct pacman user follow-up is based on actual migration state**
|
||||||
|
- Direct `sudo pacman -Syu` no longer uses a fake user-update marker.
|
||||||
|
- User notifications are shown only when `omarchy-migrate --pending user`
|
||||||
|
finds missing per-user migration state.
|
||||||
|
|
||||||
|
## Remaining concerns
|
||||||
|
|
||||||
|
1. **Pacman guard scope**
|
||||||
|
- The guard detects direct pacman sysupgrade invocations and allows Omarchy
|
||||||
|
commands that set `OMARCHY_UPDATE_PACMAN=1`.
|
||||||
|
- We may regret blocking some legitimate package-manager frontends or
|
||||||
|
maintenance flows. Keep an eye on what should be allowed versus redirected
|
||||||
|
to `omarchy update`.
|
||||||
|
|
||||||
|
2. **Pacnew/pacsave handling is still missing**
|
||||||
|
- Package-backed Omarchy should warn about or help process `.pacnew` and
|
||||||
|
`.pacsave` files after updates.
|
||||||
@@ -30,6 +30,7 @@ evince
|
|||||||
exfatprogs
|
exfatprogs
|
||||||
expac
|
expac
|
||||||
eza
|
eza
|
||||||
|
fakeroot
|
||||||
fastfetch
|
fastfetch
|
||||||
fcitx5
|
fcitx5
|
||||||
fcitx5-gtk
|
fcitx5-gtk
|
||||||
@@ -91,6 +92,7 @@ nvim
|
|||||||
obs-studio
|
obs-studio
|
||||||
obsidian
|
obsidian
|
||||||
omarchy-nvim
|
omarchy-nvim
|
||||||
|
pacman-contrib
|
||||||
pamixer
|
pamixer
|
||||||
pinta
|
pinta
|
||||||
plocate
|
plocate
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ set -euo pipefail
|
|||||||
systemctl --user enable --now \
|
systemctl --user enable --now \
|
||||||
bt-agent.service \
|
bt-agent.service \
|
||||||
omarchy-recover-internal-monitor.service \
|
omarchy-recover-internal-monitor.service \
|
||||||
omarchy-sleep-lock.service
|
omarchy-sleep-lock.service \
|
||||||
|
omarchy-update-user-notify.path
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import qs.Commons
|
import qs.Commons
|
||||||
@@ -9,11 +10,86 @@ BarWidget {
|
|||||||
moduleName: "omarchy.system-update"
|
moduleName: "omarchy.system-update"
|
||||||
|
|
||||||
property bool updateAvailable: false
|
property bool updateAvailable: false
|
||||||
|
property int updateCount: 0
|
||||||
|
property string updateOutput: ""
|
||||||
|
property var updateLines: []
|
||||||
|
property var omarchyUpdateLines: []
|
||||||
|
property var otherUpdateLines: []
|
||||||
|
property bool popupOpen: false
|
||||||
|
property bool buttonHovered: false
|
||||||
|
property bool popupHovered: popup.containsMouse
|
||||||
|
|
||||||
|
readonly property string stateHome: Quickshell.env("XDG_STATE_HOME") || (Quickshell.env("HOME") + "/.local/state")
|
||||||
|
readonly property string availableStatePath: stateHome + "/omarchy/updates/available"
|
||||||
|
|
||||||
|
function close() { popupOpen = false }
|
||||||
|
|
||||||
function refresh() {
|
function refresh() {
|
||||||
|
updateOutput = ""
|
||||||
if (!updateProc.running) updateProc.running = true
|
if (!updateProc.running) updateProc.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clear() {
|
||||||
|
updateOutput = ""
|
||||||
|
updateLines = []
|
||||||
|
omarchyUpdateLines = []
|
||||||
|
otherUpdateLines = []
|
||||||
|
updateCount = 0
|
||||||
|
updateAvailable = false
|
||||||
|
popupOpen = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function packageName(line) {
|
||||||
|
return String(line || "").trim().split(/\s+/)[0] || ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOmarchyPackage(line) {
|
||||||
|
var pkg = packageName(line)
|
||||||
|
return pkg === "omarchy" || pkg.indexOf("omarchy-") === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function countLabel(count) {
|
||||||
|
return count === 1 ? "1 update" : count + " updates"
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseUpdateText(text) {
|
||||||
|
var lines = String(text || "").split(/\r?\n/).filter(function(line) {
|
||||||
|
return line.trim().length > 0
|
||||||
|
})
|
||||||
|
var omarchyLines = []
|
||||||
|
var otherLines = []
|
||||||
|
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
if (isOmarchyPackage(lines[i])) omarchyLines.push(lines[i])
|
||||||
|
else otherLines.push(lines[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
omarchyUpdateLines = omarchyLines
|
||||||
|
otherUpdateLines = otherLines
|
||||||
|
updateLines = omarchyLines.concat(otherLines)
|
||||||
|
updateCount = updateLines.length
|
||||||
|
updateAvailable = updateCount > 0
|
||||||
|
if (!updateAvailable) popupOpen = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyUpdateOutput(exitCode) {
|
||||||
|
var output = String(updateStdout.text || updateOutput || "")
|
||||||
|
parseUpdateText(exitCode === 0 ? output : "")
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPopup() {
|
||||||
|
hideTimer.stop()
|
||||||
|
if (updateAvailable) popupOpen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleHide() {
|
||||||
|
hideTimer.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
onButtonHoveredChanged: buttonHovered ? showPopup() : scheduleHide()
|
||||||
|
onPopupHoveredChanged: popupHovered ? hideTimer.stop() : scheduleHide()
|
||||||
|
onUpdateAvailableChanged: if (!updateAvailable) popupOpen = false
|
||||||
|
|
||||||
visible: updateAvailable
|
visible: updateAvailable
|
||||||
implicitWidth: button.implicitWidth
|
implicitWidth: button.implicitWidth
|
||||||
implicitHeight: button.implicitHeight
|
implicitHeight: button.implicitHeight
|
||||||
@@ -24,17 +100,31 @@ BarWidget {
|
|||||||
function refresh(): void {
|
function refresh(): void {
|
||||||
root.refresh()
|
root.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clear(): void {
|
||||||
|
root.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: updateProc
|
id: updateProc
|
||||||
command: ["bash", "-lc", "omarchy-update-available"]
|
command: ["bash", "-lc", "omarchy-update-available"]
|
||||||
stdout: StdioCollector { waitForEnd: true }
|
stdout: StdioCollector { id: updateStdout; waitForEnd: true; onStreamFinished: root.updateOutput = text }
|
||||||
onExited: function(exitCode) {
|
onExited: function(exitCode) {
|
||||||
root.updateAvailable = exitCode === 0
|
root.applyUpdateOutput(exitCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: availableState
|
||||||
|
path: root.availableStatePath
|
||||||
|
watchChanges: true
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: root.parseUpdateText(text())
|
||||||
|
onLoadFailed: root.clear()
|
||||||
|
onFileChanged: reload()
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
interval: 21600000
|
interval: 21600000
|
||||||
running: true
|
running: true
|
||||||
@@ -43,13 +133,134 @@ BarWidget {
|
|||||||
onTriggered: root.refresh()
|
onTriggered: root.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: hideTimer
|
||||||
|
interval: 220
|
||||||
|
onTriggered: {
|
||||||
|
if (!root.buttonHovered && !root.popupHovered) root.popupOpen = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
WidgetButton {
|
WidgetButton {
|
||||||
id: button
|
id: button
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
bar: root.bar
|
bar: root.bar
|
||||||
text: root.updateAvailable ? "\uf021" : ""
|
text: root.updateAvailable ? "\uf021" : ""
|
||||||
fontSize: Style.font.caption
|
fontSize: Style.font.caption
|
||||||
tooltipText: root.updateAvailable ? "Omarchy update available" : ""
|
tooltipText: ""
|
||||||
onPressed: function() { root.bar.run("omarchy-launch-floating-terminal-with-presentation omarchy-update") }
|
onPressed: function() { root.bar.run("omarchy-launch-floating-terminal-with-presentation omarchy-update") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
id: hoverHandler
|
||||||
|
target: button
|
||||||
|
onHoveredChanged: root.buttonHovered = hovered
|
||||||
|
}
|
||||||
|
|
||||||
|
PopupCard {
|
||||||
|
id: popup
|
||||||
|
anchorItem: button
|
||||||
|
owner: root
|
||||||
|
bar: root.bar
|
||||||
|
open: root.popupOpen && root.updateAvailable
|
||||||
|
triggerMode: "hover"
|
||||||
|
contentWidth: popup.fittedContentWidth(Style.space(460))
|
||||||
|
contentHeight: popup.fittedContentHeight(panelColumn.implicitHeight, Style.space(520))
|
||||||
|
|
||||||
|
Flickable {
|
||||||
|
id: updateFlick
|
||||||
|
anchors.fill: parent
|
||||||
|
contentWidth: width
|
||||||
|
contentHeight: panelColumn.implicitHeight
|
||||||
|
clip: true
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
flickableDirection: Flickable.VerticalFlick
|
||||||
|
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: panelColumn
|
||||||
|
width: updateFlick.width
|
||||||
|
spacing: Style.space(12)
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Style.space(8)
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "\uf021"
|
||||||
|
color: root.bar ? root.bar.urgent : Color.urgent
|
||||||
|
font.family: root.bar ? root.bar.fontFamily : Style.font.family
|
||||||
|
font.pixelSize: Style.font.title
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - Style.space(28)
|
||||||
|
spacing: Style.space(2)
|
||||||
|
|
||||||
|
Text {
|
||||||
|
width: parent.width
|
||||||
|
text: countLabel(root.updateCount) + " available"
|
||||||
|
color: Color.popups.text
|
||||||
|
font.family: root.bar ? root.bar.fontFamily : Style.font.family
|
||||||
|
font.pixelSize: Style.font.body
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
width: parent.width
|
||||||
|
text: "Click to run omarchy update"
|
||||||
|
color: Qt.darker(Color.popups.text, 1.35)
|
||||||
|
font.family: root.bar ? root.bar.fontFamily : Style.font.family
|
||||||
|
font.pixelSize: Style.font.bodySmall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateSection {
|
||||||
|
title: "Omarchy"
|
||||||
|
lines: root.omarchyUpdateLines
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateSection {
|
||||||
|
title: "Other packages"
|
||||||
|
lines: root.otherUpdateLines
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component UpdateSection: Column {
|
||||||
|
id: section
|
||||||
|
|
||||||
|
property string title: ""
|
||||||
|
property var lines: []
|
||||||
|
|
||||||
|
visible: lines.length > 0
|
||||||
|
spacing: Style.space(6)
|
||||||
|
|
||||||
|
Text {
|
||||||
|
width: section.width
|
||||||
|
text: section.title
|
||||||
|
color: root.bar ? root.bar.foreground : Color.foreground
|
||||||
|
font.family: root.bar ? root.bar.fontFamily : Style.font.family
|
||||||
|
font.pixelSize: Style.font.bodySmall
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: section.lines
|
||||||
|
|
||||||
|
delegate: Text {
|
||||||
|
width: section.width
|
||||||
|
text: modelData
|
||||||
|
color: Color.popups.text
|
||||||
|
font.family: root.bar ? root.bar.fontFamily : Style.font.family
|
||||||
|
font.pixelSize: Style.font.bodySmall
|
||||||
|
wrapMode: Text.WrapAnywhere
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,7 +177,10 @@ assert_output_contains "theme set help names binary" "$output" "omarchy-theme-se
|
|||||||
|
|
||||||
output=$(timeout 5 "$CLI" update --help)
|
output=$(timeout 5 "$CLI" update --help)
|
||||||
assert_output_contains "mutating command help does not execute target" "$output" "omarchy-update"
|
assert_output_contains "mutating command help does not execute target" "$output" "omarchy-update"
|
||||||
assert_output_contains "root command help shows related child commands" "$output" "omarchy update perform"
|
if [[ $output == *"omarchy update perform"* ]]; then
|
||||||
|
fail "hidden update perform compatibility wrapper is not shown"
|
||||||
|
fi
|
||||||
|
pass "hidden update perform compatibility wrapper is not shown"
|
||||||
|
|
||||||
output=$("$CLI" screenshot --help)
|
output=$("$CLI" screenshot --help)
|
||||||
assert_output_contains "root alias resolves to command help" "$output" "omarchy-capture-screenshot"
|
assert_output_contains "root alias resolves to command help" "$output" "omarchy-capture-screenshot"
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
root = Path(os.environ["ROOT"])
|
root = Path(os.environ["ROOT"])
|
||||||
pkgbuild = (root.parent / "omarchy-pkgs/pkgbuilds/omarchy-settings/PKGBUILD").read_text()
|
pkgbuild = (root.parent / "omarchy-pkgs/pkgbuilds/omarchy-settings/PKGBUILD").read_text()
|
||||||
|
omarchy_pkgbuild = (root.parent / "omarchy-pkgs/pkgbuilds/omarchy/PKGBUILD").read_text()
|
||||||
errors = []
|
errors = []
|
||||||
package_defaults = [
|
package_defaults = [
|
||||||
("default/uwsm/env.d/10-omarchy", "/usr/share/uwsm/env.d/10-omarchy", "uwsm/env"),
|
("default/uwsm/env.d/10-omarchy", "/usr/share/uwsm/env.d/10-omarchy", "uwsm/env"),
|
||||||
@@ -105,6 +106,8 @@ package_defaults = [
|
|||||||
("default/systemd/user/bt-agent.service", "/usr/lib/systemd/user/bt-agent.service", "systemd/user/bt-agent.service"),
|
("default/systemd/user/bt-agent.service", "/usr/lib/systemd/user/bt-agent.service", "systemd/user/bt-agent.service"),
|
||||||
("default/systemd/user/omarchy-sleep-lock.service", "/usr/lib/systemd/user/omarchy-sleep-lock.service", "systemd/user/omarchy-sleep-lock.service"),
|
("default/systemd/user/omarchy-sleep-lock.service", "/usr/lib/systemd/user/omarchy-sleep-lock.service", "systemd/user/omarchy-sleep-lock.service"),
|
||||||
("default/systemd/user/omarchy-recover-internal-monitor.service", "/usr/lib/systemd/user/omarchy-recover-internal-monitor.service", "systemd/user/omarchy-recover-internal-monitor.service"),
|
("default/systemd/user/omarchy-recover-internal-monitor.service", "/usr/lib/systemd/user/omarchy-recover-internal-monitor.service", "systemd/user/omarchy-recover-internal-monitor.service"),
|
||||||
|
("default/systemd/user/omarchy-update-user-notify.service", "/usr/lib/systemd/user/omarchy-update-user-notify.service", "systemd/user/omarchy-update-user-notify.service"),
|
||||||
|
("default/systemd/user/omarchy-update-user-notify.path", "/usr/lib/systemd/user/omarchy-update-user-notify.path", "systemd/user/omarchy-update-user-notify.path"),
|
||||||
("default/fonts/omarchy/omarchy.ttf", "/usr/share/fonts/omarchy/omarchy.ttf", "omarchy.ttf"),
|
("default/fonts/omarchy/omarchy.ttf", "/usr/share/fonts/omarchy/omarchy.ttf", "omarchy.ttf"),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -116,6 +119,13 @@ for source, destination, legacy in package_defaults:
|
|||||||
if destination and (source not in pkgbuild or destination not in pkgbuild):
|
if destination and (source not in pkgbuild or destination not in pkgbuild):
|
||||||
errors.append(f"PKGBUILD does not explicitly install {source} -> {destination}")
|
errors.append(f"PKGBUILD does not explicitly install {source} -> {destination}")
|
||||||
|
|
||||||
|
guard_hook = "default/libalpm/hooks/00-omarchy-update-guard.hook"
|
||||||
|
guard_destination = "/usr/share/libalpm/hooks/00-omarchy-update-guard.hook"
|
||||||
|
if not (root / guard_hook).exists():
|
||||||
|
errors.append(f"missing package default source: {guard_hook}")
|
||||||
|
if guard_hook not in omarchy_pkgbuild or guard_destination not in omarchy_pkgbuild:
|
||||||
|
errors.append(f"omarchy PKGBUILD does not install {guard_hook} -> {guard_destination}")
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
print("\n".join(errors), file=sys.stderr)
|
print("\n".join(errors), file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source "$(dirname "$0")/base-test.sh"
|
||||||
|
|
||||||
|
test_tmp=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$test_tmp"' EXIT
|
||||||
|
|
||||||
|
stub_bin="$test_tmp/bin"
|
||||||
|
test_home="$test_tmp/home"
|
||||||
|
mkdir -p "$stub_bin" "$test_home"
|
||||||
|
|
||||||
|
cat >"$stub_bin/omarchy-migrate-user" <<'SH'
|
||||||
|
#!/bin/bash
|
||||||
|
if [[ ${1:-} == "--pending" && ${OMARCHY_TEST_PENDING_MIGRATIONS:-0} == 1 ]]; then
|
||||||
|
echo 200-user.sh
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
SH
|
||||||
|
chmod +x "$stub_bin/omarchy-migrate-user"
|
||||||
|
|
||||||
|
cat >"$stub_bin/systemd-run" <<'SH'
|
||||||
|
#!/bin/bash
|
||||||
|
exit 1
|
||||||
|
SH
|
||||||
|
chmod +x "$stub_bin/systemd-run"
|
||||||
|
|
||||||
|
run_notify() {
|
||||||
|
HOME="$test_home" \
|
||||||
|
PATH="$stub_bin:$ROOT/bin:$PATH" \
|
||||||
|
OMARCHY_TEST_PENDING_MIGRATIONS="$1" \
|
||||||
|
"$ROOT/bin/omarchy-migrate-notify"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_notify 0 >"$test_tmp/not-pending.out" 2>"$test_tmp/not-pending.err"
|
||||||
|
[[ ! -s $test_tmp/not-pending.out ]] || fail "migration notifier stays quiet on stdout without pending migrations"
|
||||||
|
[[ ! -s $test_tmp/not-pending.err ]] || fail "migration notifier stays quiet on stderr without pending migrations"
|
||||||
|
pass "migration notifier ignores users with no pending migrations"
|
||||||
|
|
||||||
|
run_notify 1 >"$test_tmp/pending.out" 2>"$test_tmp/pending.err"
|
||||||
|
grep -q 'Omarchy has pending user migrations' "$test_tmp/pending.err" || fail "migration notifier explains pending migrations without notification system"
|
||||||
|
grep -q 'user/200-user.sh' "$test_tmp/pending.err" || fail "migration notifier lists pending migration names"
|
||||||
|
pass "migration notifier reports pending user migrations"
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source "$(dirname "$0")/base-test.sh"
|
||||||
|
|
||||||
|
test_tmp=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$test_tmp"' EXIT
|
||||||
|
|
||||||
|
test_root="$test_tmp/omarchy"
|
||||||
|
test_home="$test_tmp/home"
|
||||||
|
system_state="$test_tmp/system-state"
|
||||||
|
mkdir -p "$test_root/migrations/system" "$test_root/migrations/user" "$test_home" "$system_state"
|
||||||
|
|
||||||
|
cat >"$test_root/migrations/system/100-system.sh" <<'SH'
|
||||||
|
[[ $OMARCHY_PATH == "$TEST_EXPECTED_OMARCHY_PATH" ]]
|
||||||
|
echo system >>"$TEST_CALLS"
|
||||||
|
SH
|
||||||
|
cat >"$test_root/migrations/user/200-user.sh" <<'SH'
|
||||||
|
[[ $OMARCHY_PATH == "$TEST_EXPECTED_OMARCHY_PATH" ]]
|
||||||
|
echo user >>"$TEST_CALLS"
|
||||||
|
SH
|
||||||
|
|
||||||
|
calls="$test_tmp/calls"
|
||||||
|
|
||||||
|
if ! HOME="$test_home" OMARCHY_PATH="$test_root" "$ROOT/bin/omarchy-migrate-user" --pending >"$test_tmp/user-pending.out"; then
|
||||||
|
fail "user migration runner reports pending migrations before state exists"
|
||||||
|
fi
|
||||||
|
grep -q '^200-user\.sh$' "$test_tmp/user-pending.out" || fail "user migration runner lists pending migration filename"
|
||||||
|
pass "user migration runner detects pending migrations"
|
||||||
|
|
||||||
|
HOME="$test_home" \
|
||||||
|
OMARCHY_PATH="$test_root" \
|
||||||
|
TEST_EXPECTED_OMARCHY_PATH="$test_root" \
|
||||||
|
TEST_CALLS="$calls" \
|
||||||
|
"$ROOT/bin/omarchy-migrate-user" >"$test_tmp/user-first.out"
|
||||||
|
[[ $(grep -c '^user$' "$calls") -eq 1 ]] || fail "user migration runner runs user migrations"
|
||||||
|
! grep -q '^system$' "$calls" || fail "user migration runner does not run system migrations"
|
||||||
|
[[ -f $test_home/.local/state/omarchy/migrations/user/200-user.sh ]] || fail "user migration runner records user migration marker"
|
||||||
|
pass "user migration runner only runs user migrations"
|
||||||
|
|
||||||
|
HOME="$test_home" \
|
||||||
|
OMARCHY_PATH="$test_root" \
|
||||||
|
TEST_EXPECTED_OMARCHY_PATH="$test_root" \
|
||||||
|
TEST_CALLS="$calls" \
|
||||||
|
"$ROOT/bin/omarchy-migrate-user" >"$test_tmp/user-second.out"
|
||||||
|
[[ $(grep -c '^user$' "$calls") -eq 1 ]] || fail "user migration runner skips completed migrations"
|
||||||
|
pass "user migration runner skips completed migrations"
|
||||||
|
|
||||||
|
if HOME="$test_home" OMARCHY_PATH="$test_root" "$ROOT/bin/omarchy-migrate-user" --pending >"$test_tmp/user-not-pending.out"; then
|
||||||
|
fail "user migration runner reports no pending migrations after state exists"
|
||||||
|
fi
|
||||||
|
pass "user migration runner detects no pending migrations"
|
||||||
|
|
||||||
|
if ! OMARCHY_PATH="$test_root" OMARCHY_SYSTEM_MIGRATION_STATE="$system_state" "$ROOT/bin/omarchy-migrate-system" --pending >"$test_tmp/system-pending.out"; then
|
||||||
|
fail "system migration runner reports pending migrations before state exists"
|
||||||
|
fi
|
||||||
|
grep -q '^100-system\.sh$' "$test_tmp/system-pending.out" || fail "system migration runner lists pending migration filename"
|
||||||
|
|
||||||
|
OMARCHY_PATH="$test_root" \
|
||||||
|
OMARCHY_SYSTEM_MIGRATION_STATE="$system_state" \
|
||||||
|
TEST_EXPECTED_OMARCHY_PATH="$test_root" \
|
||||||
|
TEST_CALLS="$calls" \
|
||||||
|
"$ROOT/bin/omarchy-migrate-system" >"$test_tmp/system-first.out"
|
||||||
|
[[ $(grep -c '^system$' "$calls") -eq 1 ]] || fail "system migration runner runs system migrations"
|
||||||
|
[[ -f $system_state/100-system.sh ]] || fail "system migration runner records system migration marker"
|
||||||
|
pass "system migration runner only runs system migrations"
|
||||||
|
|
||||||
|
if OMARCHY_PATH="$test_root" OMARCHY_SYSTEM_MIGRATION_STATE="$system_state" "$ROOT/bin/omarchy-migrate-system" --pending >"$test_tmp/system-not-pending.out"; then
|
||||||
|
fail "system migration runner reports no pending migrations after state exists"
|
||||||
|
fi
|
||||||
|
pass "system migration runner detects no pending migrations"
|
||||||
|
|
||||||
|
failure_root="$test_tmp/failure-omarchy"
|
||||||
|
failure_home="$test_tmp/failure-home"
|
||||||
|
failure_system_state="$test_tmp/failure-system-state"
|
||||||
|
mkdir -p "$failure_root/migrations/system" "$failure_root/migrations/user" "$failure_home" "$failure_system_state"
|
||||||
|
|
||||||
|
cat >"$failure_root/migrations/user/300-fail-user.sh" <<'SH'
|
||||||
|
echo user-before-fail >>"$TEST_CALLS"
|
||||||
|
false
|
||||||
|
echo user-after-fail >>"$TEST_CALLS"
|
||||||
|
SH
|
||||||
|
|
||||||
|
set +e
|
||||||
|
HOME="$failure_home" \
|
||||||
|
OMARCHY_PATH="$failure_root" \
|
||||||
|
TEST_CALLS="$calls" \
|
||||||
|
"$ROOT/bin/omarchy-migrate-user" >"$test_tmp/user-failure.out" 2>"$test_tmp/user-failure.err"
|
||||||
|
user_failure_status=$?
|
||||||
|
set -e
|
||||||
|
[[ $user_failure_status -ne 0 ]] || fail "user migration runner exits non-zero when a migration fails"
|
||||||
|
[[ ! -f $failure_home/.local/state/omarchy/migrations/user/300-fail-user.sh ]] || fail "user migration runner does not mark failed migration complete"
|
||||||
|
grep -q '^user-before-fail$' "$calls" || fail "user migration runner started failing migration"
|
||||||
|
! grep -q '^user-after-fail$' "$calls" || fail "user migration runner stops failing migration under strict mode"
|
||||||
|
pass "user migration runner does not mark failed migrations complete"
|
||||||
|
|
||||||
|
cat >"$failure_root/migrations/system/400-fail-system.sh" <<'SH'
|
||||||
|
echo system-before-fail >>"$TEST_CALLS"
|
||||||
|
false
|
||||||
|
echo system-after-fail >>"$TEST_CALLS"
|
||||||
|
SH
|
||||||
|
|
||||||
|
set +e
|
||||||
|
OMARCHY_PATH="$failure_root" \
|
||||||
|
OMARCHY_SYSTEM_MIGRATION_STATE="$failure_system_state" \
|
||||||
|
TEST_CALLS="$calls" \
|
||||||
|
"$ROOT/bin/omarchy-migrate-system" >"$test_tmp/system-failure.out" 2>"$test_tmp/system-failure.err"
|
||||||
|
system_failure_status=$?
|
||||||
|
set -e
|
||||||
|
[[ $system_failure_status -ne 0 ]] || fail "system migration runner exits non-zero when a migration fails"
|
||||||
|
[[ ! -f $failure_system_state/400-fail-system.sh ]] || fail "system migration runner does not mark failed migration complete"
|
||||||
|
grep -q '^system-before-fail$' "$calls" || fail "system migration runner started failing migration"
|
||||||
|
! grep -q '^system-after-fail$' "$calls" || fail "system migration runner stops failing migration under strict mode"
|
||||||
|
pass "system migration runner does not mark failed migrations complete"
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source "$(dirname "$0")/base-test.sh"
|
||||||
|
|
||||||
|
test_tmp=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$test_tmp"' EXIT
|
||||||
|
|
||||||
|
stub_bin="$test_tmp/bin"
|
||||||
|
test_home="$test_tmp/home"
|
||||||
|
mkdir -p "$stub_bin" "$test_home"
|
||||||
|
|
||||||
|
cat >"$stub_bin/omarchy-migrate-system" <<'SH'
|
||||||
|
#!/bin/bash
|
||||||
|
if [[ ${1:-} == "--pending" ]]; then
|
||||||
|
[[ ${OMARCHY_TEST_PENDING_SYSTEM:-1} == 1 ]] || exit 1
|
||||||
|
echo 100-system.sh
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo system >>"$TEST_CALLS"
|
||||||
|
SH
|
||||||
|
chmod +x "$stub_bin/omarchy-migrate-system"
|
||||||
|
|
||||||
|
cat >"$stub_bin/omarchy-migrate-user" <<'SH'
|
||||||
|
#!/bin/bash
|
||||||
|
if [[ ${1:-} == "--pending" ]]; then
|
||||||
|
[[ ${OMARCHY_TEST_PENDING_USER:-1} == 1 ]] || exit 1
|
||||||
|
echo 200-user.sh
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo user >>"$TEST_CALLS"
|
||||||
|
SH
|
||||||
|
chmod +x "$stub_bin/omarchy-migrate-user"
|
||||||
|
|
||||||
|
run_migrate() {
|
||||||
|
HOME="$test_home" \
|
||||||
|
PATH="$stub_bin:$PATH" \
|
||||||
|
TEST_CALLS="$test_tmp/calls" \
|
||||||
|
"$ROOT/bin/omarchy-migrate" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
: >"$test_tmp/calls"
|
||||||
|
run_migrate >"$test_tmp/migrate.out"
|
||||||
|
[[ $(sed -n '1p' "$test_tmp/calls") == "system" ]] || fail "omarchy-migrate runs system migrations first"
|
||||||
|
[[ $(sed -n '2p' "$test_tmp/calls") == "user" ]] || fail "omarchy-migrate runs user migrations second"
|
||||||
|
[[ $(wc -l <"$test_tmp/calls") -eq 2 ]] || fail "omarchy-migrate only delegates to system and user migration runners"
|
||||||
|
pass "omarchy-migrate runs system and user migrations without force"
|
||||||
|
|
||||||
|
run_migrate --pending >"$test_tmp/pending-all.out"
|
||||||
|
grep -q '^system/100-system\.sh$' "$test_tmp/pending-all.out" || fail "omarchy-migrate --pending lists pending system migrations"
|
||||||
|
grep -q '^user/200-user\.sh$' "$test_tmp/pending-all.out" || fail "omarchy-migrate --pending lists pending user migrations"
|
||||||
|
pass "omarchy-migrate --pending lists all pending migrations"
|
||||||
|
|
||||||
|
run_migrate --pending user >"$test_tmp/pending-user.out"
|
||||||
|
grep -q '^user/200-user\.sh$' "$test_tmp/pending-user.out" || fail "omarchy-migrate --pending user lists user migrations"
|
||||||
|
! grep -q '^system/' "$test_tmp/pending-user.out" || fail "omarchy-migrate --pending user omits system migrations"
|
||||||
|
pass "omarchy-migrate --pending user scopes pending output"
|
||||||
|
|
||||||
|
if OMARCHY_TEST_PENDING_SYSTEM=0 OMARCHY_TEST_PENDING_USER=0 run_migrate --pending >"$test_tmp/not-pending.out"; then
|
||||||
|
fail "omarchy-migrate --pending exits non-zero without pending migrations"
|
||||||
|
fi
|
||||||
|
[[ ! -s $test_tmp/not-pending.out ]] || fail "omarchy-migrate --pending stays quiet without pending migrations"
|
||||||
|
pass "omarchy-migrate --pending reports no pending migrations"
|
||||||
|
|
||||||
|
if run_migrate --force >"$test_tmp/force.out" 2>&1; then
|
||||||
|
fail "omarchy-migrate rejects obsolete --force option"
|
||||||
|
fi
|
||||||
|
grep -q 'Unknown option: --force' "$test_tmp/force.out" || fail "omarchy-migrate reports obsolete --force option"
|
||||||
|
pass "omarchy-migrate no longer needs --force"
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source "$(dirname "$0")/base-test.sh"
|
||||||
|
|
||||||
|
test_tmp=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$test_tmp"' EXIT
|
||||||
|
|
||||||
|
stub_bin="$test_tmp/bin"
|
||||||
|
test_home="$test_tmp/home"
|
||||||
|
state_home="$test_tmp/state"
|
||||||
|
mkdir -p "$stub_bin" "$test_home" "$state_home"
|
||||||
|
|
||||||
|
cat >"$stub_bin/fakeroot" <<'SH'
|
||||||
|
#!/bin/bash
|
||||||
|
exit 0
|
||||||
|
SH
|
||||||
|
chmod +x "$stub_bin/fakeroot"
|
||||||
|
|
||||||
|
cat >"$stub_bin/checkupdates" <<'SH'
|
||||||
|
#!/bin/bash
|
||||||
|
case "${TEST_CHECKUPDATES:-updates}" in
|
||||||
|
updates)
|
||||||
|
printf 'linux 6.1-1 -> 6.1-2\nomarchy 4.0.0-1 -> 4.0.1-1\n'
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
none)
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
fail)
|
||||||
|
echo "check failed" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
SH
|
||||||
|
chmod +x "$stub_bin/checkupdates"
|
||||||
|
|
||||||
|
cat >"$stub_bin/pacman" <<'SH'
|
||||||
|
#!/bin/bash
|
||||||
|
case "$1" in
|
||||||
|
-Qem)
|
||||||
|
[[ ${TEST_AUR_INSTALLED:-0} == "1" ]] && exit 0 || exit 1
|
||||||
|
;;
|
||||||
|
-Qu)
|
||||||
|
if [[ ${TEST_PACMAN_FALLBACK_UPDATES:-0} == "1" ]]; then
|
||||||
|
echo "fallback-pkg 1-1 -> 1-2"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
exit 0
|
||||||
|
SH
|
||||||
|
chmod +x "$stub_bin/pacman"
|
||||||
|
|
||||||
|
cat >"$stub_bin/yay" <<'SH'
|
||||||
|
#!/bin/bash
|
||||||
|
if [[ ${TEST_YAY_UPDATES:-0} == "1" ]]; then
|
||||||
|
echo "aur-helper 1-1 -> 1-2"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
SH
|
||||||
|
chmod +x "$stub_bin/yay"
|
||||||
|
|
||||||
|
run_checker() {
|
||||||
|
HOME="$test_home" \
|
||||||
|
XDG_STATE_HOME="$state_home" \
|
||||||
|
PATH="$stub_bin:$PATH" \
|
||||||
|
OMARCHY_UPDATE_CHECK_TIMEOUT=5 \
|
||||||
|
"$ROOT/bin/omarchy-update-available"
|
||||||
|
}
|
||||||
|
|
||||||
|
capture_checker() {
|
||||||
|
local stdout_file="$1"
|
||||||
|
local stderr_file="$2"
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
set +e
|
||||||
|
(
|
||||||
|
export "$@"
|
||||||
|
run_checker
|
||||||
|
) >"$stdout_file" 2>"$stderr_file"
|
||||||
|
local status=$?
|
||||||
|
set -e
|
||||||
|
return "$status"
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout="$test_tmp/stdout"
|
||||||
|
stderr="$test_tmp/stderr"
|
||||||
|
|
||||||
|
if capture_checker "$stdout" "$stderr" TEST_CHECKUPDATES=updates TEST_AUR_INSTALLED=0; then
|
||||||
|
status=0
|
||||||
|
else
|
||||||
|
status=$?
|
||||||
|
fi
|
||||||
|
[[ $status -eq 0 ]] || fail "update checker exits successfully when system updates are available"
|
||||||
|
grep -q '^linux ' "$stdout" || fail "update checker prints system package updates"
|
||||||
|
grep -q '^omarchy ' "$state_home/omarchy/updates/packages" || fail "update checker stores system package updates"
|
||||||
|
[[ $(wc -l <"$state_home/omarchy/updates/available") -eq 2 ]] || fail "update checker stores combined update list"
|
||||||
|
pass "update checker detects system package updates"
|
||||||
|
|
||||||
|
if capture_checker "$stdout" "$stderr" TEST_CHECKUPDATES=none TEST_AUR_INSTALLED=1 TEST_YAY_UPDATES=1; then
|
||||||
|
status=0
|
||||||
|
else
|
||||||
|
status=$?
|
||||||
|
fi
|
||||||
|
[[ $status -eq 0 ]] || fail "update checker exits successfully when AUR updates are available"
|
||||||
|
grep -q '^aur-helper ' "$stdout" || fail "update checker prints AUR package updates"
|
||||||
|
grep -q '^aur-helper ' "$state_home/omarchy/updates/aur" || fail "update checker stores AUR package updates"
|
||||||
|
pass "update checker detects AUR package updates"
|
||||||
|
|
||||||
|
if capture_checker "$stdout" "$stderr" TEST_CHECKUPDATES=none TEST_AUR_INSTALLED=0; then
|
||||||
|
status=0
|
||||||
|
else
|
||||||
|
status=$?
|
||||||
|
fi
|
||||||
|
[[ $status -eq 1 ]] || fail "update checker exits non-zero when no updates are available"
|
||||||
|
grep -q '^System is up to date$' "$stdout" || fail "update checker prints up-to-date message"
|
||||||
|
[[ ! -s "$state_home/omarchy/updates/available" ]] || fail "update checker clears combined update list when up to date"
|
||||||
|
pass "update checker reports up-to-date systems"
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source "$(dirname "$0")/base-test.sh"
|
||||||
|
|
||||||
|
test_tmp=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$test_tmp"' EXIT
|
||||||
|
|
||||||
|
stub_bin="$test_tmp/bin"
|
||||||
|
test_home="$test_tmp/home"
|
||||||
|
runtime_dir="$test_tmp/runtime"
|
||||||
|
mkdir -p "$stub_bin" "$test_home" "$runtime_dir"
|
||||||
|
|
||||||
|
run_with_lock_env() {
|
||||||
|
HOME="$test_home" \
|
||||||
|
XDG_RUNTIME_DIR="$runtime_dir" \
|
||||||
|
XDG_STATE_HOME="$test_tmp/state" \
|
||||||
|
PATH="$stub_bin:$ROOT/bin:$PATH" \
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_stub() {
|
||||||
|
local name="$1"
|
||||||
|
local body="$2"
|
||||||
|
|
||||||
|
cat >"$stub_bin/$name" <<SH
|
||||||
|
#!/bin/bash
|
||||||
|
$body
|
||||||
|
SH
|
||||||
|
chmod +x "$stub_bin/$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
for command in \
|
||||||
|
omarchy-toggle-idle \
|
||||||
|
systemd-inhibit \
|
||||||
|
omarchy-update-keyring \
|
||||||
|
omarchy-update-system-pkgs \
|
||||||
|
omarchy-migrate \
|
||||||
|
omarchy-update-aur-pkgs \
|
||||||
|
omarchy-update-mise \
|
||||||
|
omarchy-update-orphan-pkgs \
|
||||||
|
omarchy-hook \
|
||||||
|
omarchy-update-analyze-logs \
|
||||||
|
omarchy-shell \
|
||||||
|
omarchy-update-restart; do
|
||||||
|
write_stub "$command" 'exit 0'
|
||||||
|
done
|
||||||
|
write_stub omarchy-update-available 'exit 1'
|
||||||
|
|
||||||
|
# omarchy-update should hold the lock before snapshotting, so a second update
|
||||||
|
# cannot even enter its pre-update snapshot.
|
||||||
|
update_snapshot_marker="$test_tmp/update-snapshot-started"
|
||||||
|
write_stub omarchy-snapshot 'echo started >"$TEST_MARKER"; sleep 2; exit 0'
|
||||||
|
|
||||||
|
OMARCHY_UPDATE_LOGGED=1 TEST_MARKER="$update_snapshot_marker" run_with_lock_env "$ROOT/bin/omarchy-update" -y >"$test_tmp/update-first.out" 2>&1 &
|
||||||
|
update_pid=$!
|
||||||
|
|
||||||
|
for _ in {1..50}; do
|
||||||
|
[[ -f $update_snapshot_marker ]] && break
|
||||||
|
sleep 0.05
|
||||||
|
done
|
||||||
|
[[ -f $update_snapshot_marker ]] || fail "first omarchy-update reached snapshot under lock"
|
||||||
|
|
||||||
|
set +e
|
||||||
|
OMARCHY_UPDATE_LOGGED=1 TEST_MARKER="$test_tmp/update-second-snapshot-started" run_with_lock_env "$ROOT/bin/omarchy-update" -y >"$test_tmp/update-second.out" 2>&1
|
||||||
|
update_second_status=$?
|
||||||
|
set -e
|
||||||
|
|
||||||
|
wait "$update_pid"
|
||||||
|
|
||||||
|
[[ $update_second_status -ne 0 ]] || fail "second omarchy-update exits non-zero while update lock is held"
|
||||||
|
grep -q "already running" "$test_tmp/update-second.out" || fail "second omarchy-update reports held update lock"
|
||||||
|
[[ ! -f $test_tmp/update-second-snapshot-started ]] || fail "second omarchy-update did not snapshot while lock was held"
|
||||||
|
pass "omarchy-update prevents overlapping top-level updates"
|
||||||
|
|
||||||
|
# omarchy-update-perform is now only a compatibility wrapper around
|
||||||
|
# omarchy-update -y, but it should still respect the same update lock.
|
||||||
|
perform_marker="$test_tmp/perform-started"
|
||||||
|
write_stub omarchy-update-keyring 'echo started >"$TEST_MARKER"; sleep 2; exit 0'
|
||||||
|
|
||||||
|
TEST_MARKER="$perform_marker" run_with_lock_env "$ROOT/bin/omarchy-update-perform" >"$test_tmp/perform-first.out" 2>&1 &
|
||||||
|
perform_pid=$!
|
||||||
|
|
||||||
|
for _ in {1..50}; do
|
||||||
|
[[ -f $perform_marker ]] && break
|
||||||
|
sleep 0.05
|
||||||
|
done
|
||||||
|
[[ -f $perform_marker ]] || fail "first omarchy-update-perform delegated to update under lock"
|
||||||
|
|
||||||
|
set +e
|
||||||
|
TEST_MARKER="$test_tmp/perform-second-started" run_with_lock_env "$ROOT/bin/omarchy-update-perform" >"$test_tmp/perform-second.out" 2>&1
|
||||||
|
perform_second_status=$?
|
||||||
|
set -e
|
||||||
|
|
||||||
|
wait "$perform_pid"
|
||||||
|
|
||||||
|
[[ $perform_second_status -ne 0 ]] || fail "second omarchy-update-perform exits non-zero while update lock is held"
|
||||||
|
grep -q "already running" "$test_tmp/perform-second.out" || fail "second omarchy-update-perform reports held update lock"
|
||||||
|
[[ ! -f $test_tmp/perform-second-started ]] || fail "second omarchy-update-perform did not snapshot while lock was held"
|
||||||
|
pass "omarchy-update-perform compatibility wrapper respects update lock"
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source "$(dirname "$0")/base-test.sh"
|
||||||
|
|
||||||
|
test_tmp=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$test_tmp"' EXIT
|
||||||
|
|
||||||
|
stub_bin="$test_tmp/bin"
|
||||||
|
test_home="$test_tmp/home"
|
||||||
|
mkdir -p "$stub_bin" "$test_home"
|
||||||
|
|
||||||
|
write_stub() {
|
||||||
|
local name="$1"
|
||||||
|
local body="$2"
|
||||||
|
|
||||||
|
cat >"$stub_bin/$name" <<SH
|
||||||
|
#!/bin/bash
|
||||||
|
$body
|
||||||
|
SH
|
||||||
|
chmod +x "$stub_bin/$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_orphan_checker() {
|
||||||
|
HOME="$test_home" PATH="$stub_bin:$PATH" "$ROOT/bin/omarchy-update-orphan-pkgs"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_stub pacman 'if [[ $1 == "-Qtdq" ]]; then printf "old-lib\nunused-tool\n"; exit 0; fi; exit 1'
|
||||||
|
write_stub sudo 'echo "sudo should not be called" >&2; exit 99'
|
||||||
|
write_stub gum 'echo "gum should not be called" >&2; exit 99'
|
||||||
|
|
||||||
|
run_orphan_checker >"$test_tmp/noninteractive.out" 2>"$test_tmp/noninteractive.err"
|
||||||
|
grep -q '^ old-lib$' "$test_tmp/noninteractive.out" || fail "orphan checker lists orphan packages"
|
||||||
|
grep -q 'Re-run omarchy-update-orphan-pkgs in a terminal' "$test_tmp/noninteractive.out" || fail "orphan checker does not remove packages non-interactively"
|
||||||
|
pass "orphan checker only reports orphans non-interactively"
|
||||||
|
|
||||||
|
write_stub pacman 'if [[ $1 == "-Qtdq" ]]; then exit 0; fi; exit 1'
|
||||||
|
run_orphan_checker >"$test_tmp/none.out" 2>"$test_tmp/none.err"
|
||||||
|
[[ ! -s $test_tmp/none.out ]] || fail "orphan checker stays quiet when no orphans exist"
|
||||||
|
pass "orphan checker stays quiet without orphans"
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source "$(dirname "$0")/base-test.sh"
|
||||||
|
|
||||||
|
test_tmp=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$test_tmp"' EXIT
|
||||||
|
|
||||||
|
run_guard() {
|
||||||
|
OMARCHY_PACMAN_CMDLINE="$1" "$ROOT/bin/omarchy-update-pacman-guard"
|
||||||
|
}
|
||||||
|
|
||||||
|
if run_guard "pacman -Syu --noconfirm" >"$test_tmp/direct.out" 2>"$test_tmp/direct.err"; then
|
||||||
|
fail "pacman guard blocks direct system upgrades"
|
||||||
|
fi
|
||||||
|
grep -q 'omarchy update' "$test_tmp/direct.err" || fail "pacman guard explains omarchy update entrypoint"
|
||||||
|
pass "pacman guard blocks direct pacman -Syu"
|
||||||
|
|
||||||
|
if run_guard "pacman --sync --refresh --sysupgrade" >"$test_tmp/long.out" 2>"$test_tmp/long.err"; then
|
||||||
|
fail "pacman guard blocks long-form system upgrades"
|
||||||
|
fi
|
||||||
|
pass "pacman guard blocks long-form pacman sysupgrade"
|
||||||
|
|
||||||
|
OMARCHY_UPDATE_PACMAN=1 run_guard "pacman -Syu --noconfirm" >"$test_tmp/omarchy.out" 2>"$test_tmp/omarchy.err"
|
||||||
|
[[ ! -s $test_tmp/omarchy.err ]] || fail "pacman guard stays quiet for omarchy update pacman call"
|
||||||
|
pass "pacman guard allows omarchy update pacman call"
|
||||||
|
|
||||||
|
OMARCHY_ALLOW_DIRECT_PACMAN=1 run_guard "pacman -Syu --noconfirm" >"$test_tmp/override.out" 2>"$test_tmp/override.err"
|
||||||
|
[[ ! -s $test_tmp/override.err ]] || fail "pacman guard stays quiet for explicit direct pacman override"
|
||||||
|
pass "pacman guard allows explicit direct pacman override"
|
||||||
|
|
||||||
|
run_guard "pacman -S firefox" >"$test_tmp/install.out" 2>"$test_tmp/install.err"
|
||||||
|
[[ ! -s $test_tmp/install.err ]] || fail "pacman guard stays quiet for non-sysupgrade pacman command"
|
||||||
|
pass "pacman guard ignores non-system-upgrade transactions"
|
||||||
Reference in New Issue
Block a user