Files
omarchy/test/shell.d/upgrade-to-quattro-test.sh
T
David Heinemeier Hansson 5657a91f8d Drop Lazydocker from new installs (#14230)
* Drop Lazydocker from new installs

* Preserve Lazydocker during Quattro upgrades

* Remove Lazydocker from the manual
2026-10-04 08:23:00 -04:00

409 lines
23 KiB
Bash

#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
upgrade_to_quattro="$ROOT/bin/omarchy-upgrade-to-quattro"
function_body() {
awk -v name="$1" '$0 == name "() {" { inside = 1; next } inside && $0 == "}" { exit } inside' "$upgrade_to_quattro"
}
snapshot_line=$(grep -n '^create_pre_upgrade_snapshot$' "$upgrade_to_quattro" | cut -d: -f1)
pacman_line=$(grep -n '^configure_pacman_channel$' "$upgrade_to_quattro" | cut -d: -f1)
[[ -n $snapshot_line && -n $pacman_line ]] || fail "upgrade snapshot and first mutation calls exist"
(( snapshot_line < pacman_line )) || fail "upgrade snapshot runs before pacman configuration"
grep -F 'omarchy-snapshot create || (($? == 127))' "$upgrade_to_quattro" >/dev/null
pass "Omarchy 4 upgrade snapshots the system before mutation"
# The mirrors are repointed immediately before the keyrings go in, so only a
# forced refresh replaces the legacy database and its stale checksums.
grep -F 'pacman -Syy --noconfirm archlinux-keyring omarchy-keyring' "$upgrade_to_quattro" >/dev/null
if grep -F 'pacman -Sy --noconfirm archlinux-keyring omarchy-keyring' "$upgrade_to_quattro" >/dev/null; then
fail "Omarchy 4 upgrade forces a database refresh before installing keyrings"
fi
pass "Omarchy 4 upgrade forces a database refresh before installing keyrings"
grep -F 'pacman -Syu --needed' "$upgrade_to_quattro" >/dev/null
grep -F 'omarchy-update-aur-pkgs' "$upgrade_to_quattro" >/dev/null
grep -F 'omarchy-update-available' "$upgrade_to_quattro" >/dev/null
grep -F 'omarchy-update-mise' "$upgrade_to_quattro" >/dev/null
grep -F 'run_final_system_package_upgrade' "$upgrade_to_quattro" >/dev/null
pass "Omarchy 4 upgrade completes package update checks"
grep -F 'run_post_upgrade_migrations' "$upgrade_to_quattro" >/dev/null
grep -F 'omarchy-migrate' "$upgrade_to_quattro" >/dev/null
grep -F 'dust' "$upgrade_to_quattro" >/dev/null
grep -F 'satty' "$upgrade_to_quattro" >/dev/null
final_upgrade_line=$(grep -n '^run_final_system_package_upgrade$' "$upgrade_to_quattro" | cut -d: -f1)
migrations_line=$(grep -n '^run_post_upgrade_migrations$' "$upgrade_to_quattro" | cut -d: -f1)
[[ -n $final_upgrade_line && -n $migrations_line ]] ||
fail "final package upgrade and migration calls exist"
(( final_upgrade_line < migrations_line )) ||
fail "Omarchy migrations run after the final package upgrade"
pass "Omarchy 4 upgrade applies packaged migrations"
if grep -F 'skip-first-run-update-notification' "$upgrade_to_quattro" >/dev/null; then
fail "Omarchy 4 upgrade does not use notification-specific first-run state"
fi
pass "Omarchy 4 upgrade completes first-run as one lifecycle"
grep -F 'touch "$done_dir/first-run-user" "$done_dir/finalize-user"' "$upgrade_to_quattro" >/dev/null
grep -F 'rm -f "$state_dir/first-run-user.done" "$state_dir/finalize-user.done"' "$upgrade_to_quattro" >/dev/null
pass "Omarchy 4 upgrade completes first-run and migrates legacy completion markers"
# The script runs from the branch against whatever packaged tree the channel
# serves, so a packaged command missing from an older build must never be able
# to abort the upgrade partway through.
if grep -F '"$root/bin/omarchy-done"' "$upgrade_to_quattro" >/dev/null; then
fail "Omarchy 4 upgrade writes completion markers without the packaged omarchy-done"
fi
pass "Omarchy 4 upgrade writes completion markers without the packaged omarchy-done"
for guarded_step in omarchy-refresh-applications 'omarchy-bar defaults'; do
grep -F "run_as_user_omarchy $guarded_step ||" "$upgrade_to_quattro" >/dev/null ||
fail "Omarchy 4 upgrade survives a packaged tree without $guarded_step"
done
pass "Omarchy 4 upgrade survives a packaged tree missing top-level commands"
grep -F 'configure_snapper_policy' "$upgrade_to_quattro" >/dev/null
grep -F '/usr/share/omarchy/install/config/snapper.sh' "$upgrade_to_quattro" >/dev/null
grep -F 'bash -euo pipefail "$snapper_config_script"' "$upgrade_to_quattro" >/dev/null
pass "Omarchy 4 upgrade normalizes Snapper retention"
grep -F 'configure_lock_authentication' "$upgrade_to_quattro" >/dev/null
grep -F 'OMARCHY_INSTALL_USER="$target_user"' "$upgrade_to_quattro" >/dev/null
grep -F '"$apply_lock"' "$upgrade_to_quattro" >/dev/null
pass "Omarchy 4 upgrade configures lock screen authentication for the target user"
root_path_count=$(awk '/^root_path=/{ count++ } END { print count + 0 }' "$upgrade_to_quattro")
(( root_path_count == 1 )) || fail "Omarchy 4 upgrade defines exactly one root command path"
grep -Fx 'root_path=/usr/share/omarchy/bin:/usr/local/bin:/usr/bin:/bin' "$upgrade_to_quattro" >/dev/null ||
fail "Omarchy 4 upgrade limits root command lookup to trusted system directories"
if grep -E '^root_path=.*(target_home|\.local/bin)' "$upgrade_to_quattro" >/dev/null; then
fail "Omarchy 4 upgrade does not put the target user's bin directory on the root command path"
fi
grep -Fx 'package_path="$root_path:$target_home/.local/bin"' "$upgrade_to_quattro" >/dev/null ||
fail "Omarchy 4 upgrade retains the target user's bin directory for user commands"
lock_authentication_body=$(function_body configure_lock_authentication)
lock_path_assignment_count=$(awk '{ count += gsub(/(^|[[:space:]])PATH=/, "") } END { print count + 0 }' <<<"$lock_authentication_body")
(( lock_path_assignment_count == 1 )) ||
fail "Omarchy 4 upgrade gives the privileged lock helper exactly one command path"
grep -Fx ' PATH="$root_path" \' <<<"$lock_authentication_body" >/dev/null ||
fail "Omarchy 4 upgrade gives the privileged lock helper the trusted root path"
if grep -E '(package_path|target_home|\.local/bin)' <<<"$lock_authentication_body" >/dev/null; then
fail "Omarchy 4 upgrade does not give the privileged lock helper the target user's path"
fi
firewall_body=$(function_body apply_firewall_defaults)
firewall_path_assignment_count=$(awk '{ count += gsub(/(^|[[:space:]])PATH=/, "") } END { print count + 0 }' <<<"$firewall_body")
(( firewall_path_assignment_count == 1 )) ||
fail "Omarchy 4 upgrade gives the privileged firewall helper exactly one command path"
grep -Fx ' as_root env OMARCHY_PATH=/usr/share/omarchy PATH="$root_path" \' <<<"$firewall_body" >/dev/null ||
fail "Omarchy 4 upgrade gives the privileged firewall helper the trusted root path"
if grep -E '(package_path|target_home|\.local/bin)' <<<"$firewall_body" >/dev/null; then
fail "Omarchy 4 upgrade does not give the privileged firewall helper the target user's path"
fi
user_omarchy_body=$(function_body run_as_user_omarchy)
grep -F 'PATH="$package_path"' <<<"$user_omarchy_body" >/dev/null ||
fail "Omarchy 4 upgrade retains the package and user path for target-user commands"
if grep -F 'PATH="$root_path"' <<<"$user_omarchy_body" >/dev/null; then
fail "Omarchy 4 upgrade does not narrow target-user commands to the root-only path"
fi
pass "Omarchy 4 upgrade separates privileged and target-user command paths"
grep -F 'install/helpers/browser-policy.sh' "$upgrade_to_quattro" >/dev/null ||
fail "Omarchy 4 upgrade uses the shared browser-policy helper"
grep -F 'as_root test -f "$browser_policy_helper"' "$upgrade_to_quattro" >/dev/null ||
fail "Omarchy 4 upgrade survives a packaged tree without the browser-policy helper"
if grep -F 'browser_policy_setup_group' "$upgrade_to_quattro" >/dev/null; then
fail "Omarchy 4 upgrade does not create a browser-policy group"
fi
grep -F 'browser_policy_setup_dir /etc/chromium/policies/managed' "$upgrade_to_quattro" >/dev/null ||
fail "Omarchy 4 upgrade creates a root-owned Chromium policy directory"
grep -F 'BROWSER_POLICY_MANAGED_DIRS' "$upgrade_to_quattro" >/dev/null ||
fail "Omarchy 4 upgrade hardens every Chromium-family policy directory"
grep -F 'run_as_user_omarchy omarchy-theme-set-browser' "$upgrade_to_quattro" >/dev/null ||
fail "Omarchy 4 upgrade rewrites browser theme colour after a headless theme-set"
if grep -E 'install -d -m 0?[27]?777 /etc/.*/policies|chmod a\+rw|2775' "$upgrade_to_quattro" >/dev/null; then
fail "Omarchy 4 upgrade does not create a world-writable Chromium policy directory"
fi
pass "Omarchy 4 upgrade locks the Chromium policy directory to root"
grep -F 'OMARCHY_UPGRADE_TO_QUATTRO_LIVE=1' "$upgrade_to_quattro" >/dev/null
grep -F 'systemd-networkd.service' "$upgrade_to_quattro" >/dev/null
grep -F 'systemd-networkd.socket' "$upgrade_to_quattro" >/dev/null
grep -F 'systemd-networkd-resolve-hook.socket' "$upgrade_to_quattro" >/dev/null
pass "Omarchy 4 upgrade retires systemd-networkd for NetworkManager"
# Booting with both managers enabled leaves them fighting over the Wi-Fi
# adapter, so enabling NetworkManager and disabling iwd cannot be separated by
# any step that might abort in between.
migrations_body=$(function_body run_post_upgrade_migrations)
grep -F 'fail "Omarchy migrations did not complete.' <<<"$migrations_body" >/dev/null ||
fail "Omarchy 4 upgrade fails when a migration cannot complete"
grep -F 'omarchy-migrate --pending' <<<"$migrations_body" >/dev/null ||
fail "Omarchy 4 upgrade verifies that migrations actually completed"
grep -F 'fail "Omarchy migrations are still pending.' <<<"$migrations_body" >/dev/null ||
fail "Omarchy 4 upgrade fails when a successful migration command leaves pending work"
grep -F 'pending_status != 1' <<<"$migrations_body" >/dev/null ||
fail "Omarchy 4 upgrade distinguishes no pending work from a failed verification"
grep -F 'fail "Could not verify that Omarchy migrations completed.' <<<"$migrations_body" >/dev/null ||
fail "Omarchy 4 upgrade fails when it cannot verify migration state"
if grep -F 'return 0' <<<"$migrations_body" >/dev/null || grep -F 'warn ' <<<"$migrations_body" >/dev/null; then
fail "Omarchy 4 upgrade does not continue past failed migrations"
fi
exercise_post_upgrade_migrations() {
local stub_migration_status="$1" stub_pending_status="$2"
(
log() { :; }
fail() { exit 1; }
run_as_user_omarchy() {
if [[ " $* " == *" --pending "* ]]; then
return "$stub_pending_status"
else
return "$stub_migration_status"
fi
}
eval "run_post_upgrade_migrations() { $migrations_body
}"
run_post_upgrade_migrations
)
}
exercise_post_upgrade_migrations 0 1 >/dev/null 2>&1 ||
fail "Omarchy 4 upgrade accepts a completed migration queue"
if exercise_post_upgrade_migrations 1 1 >/dev/null 2>&1; then
fail "Omarchy 4 upgrade accepts a failed migration"
fi
if exercise_post_upgrade_migrations 0 0 >/dev/null 2>&1; then
fail "Omarchy 4 upgrade accepts pending migrations"
fi
if exercise_post_upgrade_migrations 0 2 >/dev/null 2>&1; then
fail "Omarchy 4 upgrade accepts a failed pending-state check"
fi
pass "Omarchy 4 upgrade cannot finish with pending migrations"
if function_body cleanup_retired_services | grep -F 'systemctl disable iwd' >/dev/null; then
fail "Omarchy 4 upgrade does not retire iwd in a step separate from the NetworkManager enable"
fi
grep -A1 -F ' enable_system_service NetworkManager.service' "$upgrade_to_quattro" |
grep -F 'as_root systemctl disable iwd.service' >/dev/null ||
fail "Omarchy 4 upgrade retires iwd in the step that enables NetworkManager"
pass "Omarchy 4 upgrade switches from iwd to NetworkManager atomically"
# set -e aborts silently, so only an explicit banner distinguishes a
# half-upgraded system from a finished one.
grep -Fx 'trap cleanup_on_exit EXIT' "$upgrade_to_quattro" >/dev/null ||
fail "Omarchy 4 upgrade reports an aborted run instead of exiting silently"
cleanup_body=$(function_body cleanup_on_exit)
grep -F 'upgrade_started && ! upgrade_completed' <<<"$cleanup_body" >/dev/null ||
fail "Omarchy 4 upgrade reports an aborted run instead of exiting silently"
grep -F 'Upgrade incomplete - do NOT reboot.' <<<"$cleanup_body" >/dev/null ||
fail "Omarchy 4 upgrade reports an aborted run instead of exiting silently"
grep -F 'exit "$exit_status"' <<<"$cleanup_body" >/dev/null ||
fail "Omarchy 4 upgrade preserves the failing exit status"
grep -F '>&2' <<<"$cleanup_body" >/dev/null ||
fail "Omarchy 4 upgrade reports an aborted run on stderr"
started_line=$(grep -n '^upgrade_started=1$' "$upgrade_to_quattro" | cut -d: -f1)
completed_line=$(grep -n '^upgrade_completed=1$' "$upgrade_to_quattro" | cut -d: -f1)
suppress_line=$(grep -n '^suppress_hyprland_config_reload$' "$upgrade_to_quattro" | cut -d: -f1)
# The reboot is the cutover, so the last mutating step hands the live session
# back rather than swapping the shell out underneath it.
last_step_line=$(grep -n '^restore_hyprland_config_reload$' "$upgrade_to_quattro" | cut -d: -f1)
[[ -n $started_line && -n $completed_line && -n $suppress_line && -n $last_step_line ]] ||
fail "upgrade progress markers and the mutating step range exist"
(( started_line < suppress_line )) || fail "the upgrade is marked started before the first mutation"
(( completed_line > last_step_line )) || fail "the upgrade is marked complete only after the last step"
pass "Omarchy 4 upgrade reports an aborted run instead of exiting silently"
# Ordering alone would still pass if either retired entry point came back, so
# name them: the reboot is the cutover, and nothing may swap the shell out from
# under the session being replaced.
! grep -q 'start_omarchy_shell_session' "$upgrade_to_quattro" ||
fail "Omarchy 4 upgrade does not start the shell in the session it is replacing"
! grep -q 'stop_retired_session_processes' "$upgrade_to_quattro" ||
fail "Omarchy 4 upgrade leaves the retired session processes running until reboot"
pass "Omarchy 4 upgrade leaves the Omarchy 3 session alone until the reboot"
grep -F 'omarchy-bar defaults' "$upgrade_to_quattro" >/dev/null
pass "Omarchy 4 upgrade restores service-aware bar defaults"
grep -F 'install_hardware_transition_packages' "$upgrade_to_quattro" >/dev/null
grep -F 'sof-firmware' "$upgrade_to_quattro" >/dev/null
grep -F 'vulkan-intel' "$upgrade_to_quattro" >/dev/null
grep -F 'apply_user_hardware_transition' "$upgrade_to_quattro" >/dev/null
grep -F 'DX13260' "$upgrade_to_quattro" >/dev/null
pass "Omarchy 4 upgrade backfills hardware support from the legacy release"
grep -F 'omarchy-refresh-applications' "$upgrade_to_quattro" >/dev/null
pass "Omarchy 4 upgrade refreshes application launchers"
grep -F '/etc/systemd/system.conf.d/99-omarchy-nofile.conf' "$upgrade_to_quattro" >/dev/null
grep -F '/etc/systemd/user.conf.d/99-omarchy-nofile.conf' "$upgrade_to_quattro" >/dev/null
pass "Omarchy 4 upgrade removes stale nofile drop-ins"
cmdline_line=$(grep -n '^preserve_kernel_cmdline_root$' "$upgrade_to_quattro" | cut -d: -f1)
packages_line=$(grep -n '^install_omarchy_quattro_packages$' "$upgrade_to_quattro" | cut -d: -f1)
verify_line=$(grep -n '^verify_kernel_cmdline_root$' "$upgrade_to_quattro" | cut -d: -f1)
[[ -n $cmdline_line && -n $packages_line && -n $verify_line ]] ||
fail "kernel cmdline preservation, verification and package install calls exist"
# The package transaction installs the += drop-in that drops root=, so the pin
# has to be on disk before it runs or the UKI it bakes is unbootable.
(( cmdline_line < packages_line )) || fail "kernel cmdline is pinned before the packages that can drop root="
# The UKIs are rebuilt by the transaction, so they can only be checked after it.
(( verify_line > packages_line )) || fail "kernel cmdline is verified after the packages are installed"
grep -F '/etc/default/limine' "$upgrade_to_quattro" >/dev/null
grep -F 'KERNEL_CMDLINE[default]+=" ${boot_params[*]}"' "$upgrade_to_quattro" >/dev/null
grep -F 'cat /proc/cmdline' "$upgrade_to_quattro" >/dev/null
grep -F 'findmnt -no UUID /' "$upgrade_to_quattro" >/dev/null
grep -F 'rootflags=subvol=' "$upgrade_to_quattro" >/dev/null
grep -F 'cryptdevice' "$upgrade_to_quattro" >/dev/null
pass "Omarchy 4 upgrade preserves the kernel cmdline root parameters"
# The tool's effective cmdline still resolves root= from /proc/cmdline until the
# first += drop-in lands, so it reads healthy on exactly the machines about to
# break. Ask the config layers whether root= is stated instead, for the default
# key alone so a fallback or kernel-specific pin cannot cover for it.
grep -F 'kernel_cmdline_root_pinned' "$upgrade_to_quattro" >/dev/null
grep -F 'KERNEL_CMDLINE\[default\]' "$upgrade_to_quattro" >/dev/null
! grep -F 'limine-entry-tool --get-cmdline' "$upgrade_to_quattro" >/dev/null ||
fail "the pin check does not depend on the fallback it is about to lose"
pass "Omarchy 4 upgrade checks whether root= is pinned in the limine config"
# Run the pin check against fixture config layers. A false positive skips the
# pin and the next boot has no root=; a false negative appends a second pin.
eval "$(sed -n '/^kernel_cmdline_root_pinned() {$/,/^}$/p' "$upgrade_to_quattro")"
pin_root=$(mktemp -d)
trap 'rm -rf "$pin_root"' EXIT
as_root() {
local script=${3//\/etc\//$pin_root/etc/}
bash -c "${script//\/usr\/share\//$pin_root/usr/share/}"
}
# Takes pairs of a config layer and a line to append to it.
root_pinned() {
rm -rf "${pin_root:?}"/{etc,usr}
mkdir -p "$pin_root/etc/default" "$pin_root/etc/limine-entry-tool.d" "$pin_root/usr/share/limine-entry-tool.d"
while (($#)); do
printf '%s\n' "$2" >>"$pin_root/$1"
shift 2
done
kernel_cmdline_root_pinned
}
root_pinned etc/default/limine 'KERNEL_CMDLINE[default]+=" root=UUID=abc rw"' || fail "a quoted root= pin is recognised"
root_pinned etc/default/limine 'KERNEL_CMDLINE[default]=root=UUID=abc' || fail "an unquoted root= pin is recognised"
root_pinned etc/default/limine 'KERNEL_CMDLINE[default]+=" dm-mod.create="foo" root=UUID=abc rw"' ||
fail "a pin with a quoted parameter before root= is recognised"
root_pinned usr/share/limine-entry-tool.d/root.conf 'KERNEL_CMDLINE[default]+="root=UUID=abc"' \
etc/default/limine 'KERNEL_CMDLINE[default]+=" rw"' || fail "an appending layer keeps an earlier pin"
root_pinned etc/default/limine 'KERNEL_CMDLINE[default] += " root=UUID=abc rw"' || fail "a pin spaced around += is recognised"
! root_pinned etc/default/limine "KERNEL_CMDLINE[default]+='root=UUID=abc rw'" ||
fail "single quotes are kept, as limine-entry-tool keeps them"
! root_pinned etc/default/limine 'OLD_KERNEL_CMDLINE[default]+=" root=UUID=abc rw"' || fail "a renamed key is not a pin"
! root_pinned etc/default/limine '# KERNEL_CMDLINE[default]+=" root=UUID=abc rw"' || fail "a commented-out pin is not a pin"
! root_pinned etc/default/limine 'KERNEL_CMDLINE[fallback]+=" root=UUID=abc rw"' || fail "a fallback-only pin does not cover default"
! root_pinned etc/default/limine 'KERNEL_CMDLINE[default]+=" rootflags=subvol=@ rw"' || fail "rootflags= is not root="
! root_pinned etc/default/limine 'KERNEL_CMDLINE[default]+=" systemd.setenv="root=UUID=abc" rw"' ||
fail "root= inside another parameter's value is not a pin"
! root_pinned etc/default/limine 'KERNEL_CMDLINE[default]+=" systemd.setenv="NOTE=x root=UUID=abc" rw"' ||
fail "root= after a space inside a quoted value is not a pin"
! root_pinned etc/default/limine 'KERNEL_CMDLINE[default]=systemd.setenv="NOTE=x root=UUID=abc rw' ||
fail "root= after an unmatched quote is not a pin"
! root_pinned etc/default/limine 'KERNEL_CMDLINE[default]=ro"x"ot=UUID=abc rw' ||
fail "a quoted segment inside a parameter name does not make it root="
! root_pinned etc/limine-entry-tool.conf 'KERNEL_CMDLINE[default]=root=UUID=abc rw' \
etc/default/limine 'KERNEL_CMDLINE[default]=quiet' || fail "a pin replaced by a later layer is not a pin"
! root_pinned etc/default/limine 'KERNEL_CMDLINE[default]+=" root=UUID=abc rw"' \
etc/default/limine 'KERNEL_CMDLINE[default]="quiet"' || fail "a pin replaced later in the same layer is not a pin"
unset -f as_root
pass "Omarchy 4 upgrade recognises exactly the root= pins that hold"
# Installing the limine packages can deploy limine on a machine that had no
# limine.conf when the pin ran, so verification pins whatever the first call skipped.
(
eval "$(sed -n '/^kernel_cmdline_root_checked=/p;/^preserve_kernel_cmdline_root() {$/,/^}$/p;/^verify_kernel_cmdline_root() {$/,/^}$/p' "$upgrade_to_quattro")"
limine_conf=0 pin_checks=0
as_root() {
if [[ $1 == "test" ]]; then
((limine_conf))
fi
}
kernel_cmdline_root_pinned() { ((++pin_checks)); }
limine-mkinitcpio() { :; }
preserve_kernel_cmdline_root
((pin_checks == 0)) || fail "the pin waits for a limine.conf"
limine_conf=1
verify_kernel_cmdline_root
((pin_checks == 1)) || fail "kernel cmdline verification pins a machine the transaction put on limine"
verify_kernel_cmdline_root
((pin_checks == 1)) || fail "kernel cmdline verification pins a machine only once"
)
pass "Omarchy 4 upgrade pins root= on machines the transaction moves to limine"
# The crypt layer hides in the parents on LVM-on-LUKS, and a partial cmdline
# for an encrypted root must not be written at all.
grep -F 'findmnt -no SOURCE --nofsroot /' "$upgrade_to_quattro" >/dev/null
grep -F 'lsblk -nso TYPE "$root_source"' "$upgrade_to_quattro" >/dev/null
grep -F 'grep -qx crypt' "$upgrade_to_quattro" >/dev/null
grep -F '((have_mount_mode)) || boot_params+=(rw)' "$upgrade_to_quattro" >/dev/null
pass "Omarchy 4 upgrade repair path refuses a partial dm-crypt cmdline"
# The cmdline that boots is the one embedded in the UKIs, and an unverified
# root= must block the reboot rather than just warn.
grep -F -- '--only-section=.cmdline' "$upgrade_to_quattro" >/dev/null
grep -F "as_root find /boot/EFI/Linux -maxdepth 1 -name 'omarchy_linux*.efi'" "$upgrade_to_quattro" >/dev/null
grep -F 'boot_cmdline_unsafe=1' "$upgrade_to_quattro" >/dev/null
unsafe_line=$(grep -n 'if (( boot_cmdline_unsafe )); then' "$upgrade_to_quattro" | cut -d: -f1)
reboot_line=$(grep -n 'Rebooting because --reboot was passed' "$upgrade_to_quattro" | cut -d: -f1)
[[ -n $unsafe_line && -n $reboot_line ]] || fail "reboot gate and reboot branch exist"
(( unsafe_line < reboot_line )) || fail "an unverified kernel cmdline blocks the reboot"
pass "Omarchy 4 upgrade verifies the UKIs and refuses to reboot unverified"
# Lazydocker is optional on fresh installs, but a pre-quattro install keeps it.
lazydocker_body=$(function_body migrate_lazydocker_package)
[[ -n $lazydocker_body ]] || fail "upgrade has a Lazydocker replacement step"
eval "migrate_lazydocker_package() { $lazydocker_body; }"
lazydocker_calls=""
package_installed_exact() { [[ $1 == "lazydocker-bin" && $legacy_lazydocker == "yes" ]]; }
log() { :; }
warn() { :; }
as_root() {
lazydocker_calls+="swap:$*"$'\n'
return "$lazydocker_swap_status"
}
mark_packages_explicit() { lazydocker_calls+="explicit:$*"$'\n'; }
legacy_lazydocker=no
lazydocker_swap_status=0
migrate_lazydocker_package
[[ -z $lazydocker_calls ]] || fail "upgrade does not install Lazydocker for users without the legacy package"
pass "upgrade does not install Lazydocker for users without the legacy package"
legacy_lazydocker=yes
migrate_lazydocker_package
grep -Fxq 'swap:pacman -S --needed --noconfirm --ask 4 lazydocker' <<<"$lazydocker_calls" ||
fail "upgrade replaces existing lazydocker-bin with lazydocker"
grep -Fxq 'explicit:lazydocker' <<<"$lazydocker_calls" || fail "upgrade retains Lazydocker as an explicit package"
pass "upgrade replaces existing lazydocker-bin with lazydocker and keeps it explicit"
lazydocker_calls=""
lazydocker_swap_status=1
migrate_lazydocker_package
if grep -Fq 'explicit:' <<<"$lazydocker_calls"; then
fail "failed Lazydocker replacement does not mark an absent package explicit"
fi
for cleanup in remove_conflicting_legacy_packages remove_retired_default_packages; do
if grep -qw lazydocker-bin <<<"$(function_body "$cleanup")"; then
fail "upgrade cleanup must not remove existing Lazydocker when replacement fails"
fi
done
pass "upgrade cleanup preserves existing Lazydocker when replacement fails"
grep -Fxq migrate_lazydocker_package "$upgrade_to_quattro" || fail "upgrade invokes the Lazydocker replacement step"
pass "upgrade invokes the Lazydocker replacement step"