* Pin root= before the packages that can drop it limine-entry-tool falls back to /proc/cmdline for root= only while nothing appends to KERNEL_CMDLINE. Installing omarchy-settings lands a drop-in that appends with +=, switching that fallback off, and a kernel bump in the same transaction then bakes a UKI with no root= at all. preserve_kernel_cmdline_root repaired that afterwards, so a completed upgrade booted — but the machine was unbootable for the seconds in between, and an upgrade interrupted there left it in an emergency shell. Pinning cannot wait until after the packages land, and the old guard could not be moved earlier as it was: it asked the tool for its effective cmdline, which still resolves root= through the fallback right up until the drop-in arrives, so it reported healthy on exactly the machines about to break. Ask the config layers whether root= is stated explicitly instead, for the default profile alone, and pin before the package transaction. Verification stays after it, since that is what rebuilds the UKIs. The pin no longer gates on limine-mkinitcpio being present, since it now runs before the transaction that can install it, and a machine still missing it is exactly one that needs pinning first. Verified in a VM upgrading a 3.8.0 install, sampling the UKI every 2s across the upgrade: a legacy install without the pin lost root= for ~10s, and booting that state landed in "Failed to mount '' on real root". With this change the same upgrade never loses it, and an install that already pins root= is untouched. Closes #6894 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Merge the limine config layers when checking for a root= pin, and re-pin after the transaction The pin check returned on any line that mentioned root= under KERNEL_CMDLINE[default]. limine-entry-tool merges its layers in order, /usr/share drop-ins, /etc/limine-entry-tool.conf, /etc drop-ins, then /etc/default/limine, where = replaces and += appends, so a later layer's = could remove a pin the check still counted, and a quote before root= let systemd.setenv="root=..." read as one. Both skip the pin, which is the direction that bricks the next boot. The check now merges the layers the same way, strips only the outer double quotes the tool strips, drops quoted values the kernel does not split, and looks for root= as a parameter of its own. It was checked against limine-entry-tool --get-cmdline on a worker for every fixture in the test. Installing limine-mkinitcpio-hook runs limine-install, so a machine with no /boot/limine.conf when the pin runs ahead of the transaction can have one after it, and verification only warned about the UKI it found without root=. Verification now runs the pin for any machine the first call skipped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Codex Medium <noreply@openai.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: omarchybot <omarchybot@users.noreply.github.com> Co-authored-by: Codex Medium <noreply@openai.com>
366 lines
22 KiB
Bash
366 lines
22 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"
|