diff --git a/etc/limine-entry-tool.d/omarchy-defaults.conf b/etc/limine-entry-tool.d/omarchy-defaults.conf index ec0fa96f..1e421365 100644 --- a/etc/limine-entry-tool.d/omarchy-defaults.conf +++ b/etc/limine-entry-tool.d/omarchy-defaults.conf @@ -9,6 +9,15 @@ KERNEL_CMDLINE[default]+=" quiet splash loglevel=0 systemd.show_status=false rd. # is fixed upstream. KERNEL_CMDLINE[default]+=" initramfs_async=0" +# systemd 257 and later asks the console for its size with ANSI escape +# sequences when PID 1 starts, and waits for the reply. With Plymouth holding +# the VT the reply sometimes never comes, and the boot sits on a black screen +# at "Starting Switch Root" until a key is pressed: about 1 boot in 10. A dumb +# $TERM for the console makes systemd skip the query. Nothing visible changes +# under "quiet splash". Drop it once a systemd release boots clean in a +# repeated boot test. +KERNEL_CMDLINE[default]+=" systemd.tty.term.console=dumb" + CUSTOM_UKI_NAME="omarchy" ENABLE_LIMINE_FALLBACK=yes diff --git a/migrations/1791581004.sh b/migrations/1791581004.sh new file mode 100644 index 00000000..233f3aeb --- /dev/null +++ b/migrations/1791581004.sh @@ -0,0 +1,54 @@ +echo "Stop the boot from waiting on a black screen for the console to answer" + +# systemd asks the console for its size when PID 1 starts and waits for the +# reply. With Plymouth holding the screen the reply sometimes never comes, and +# about 1 boot in 10 sits on a black screen until a key is pressed. +# omarchy-defaults.conf now carries systemd.tty.term.console=dumb, which makes +# systemd skip the query. The package has already written that file by the +# time this runs; the boot image still holds the old command line until it is +# rebuilt. + +defaults_conf="${OMARCHY_LIMINE_DEFAULTS_CONF:-/etc/limine-entry-tool.d/omarchy-defaults.conf}" +running_cmdline="${OMARCHY_RUNNING_CMDLINE:-/proc/cmdline}" +rebuild_marker="${OMARCHY_LIMINE_REBUILD_MARKER:-/var/lib/omarchy/migrations/1791581004}" +param="systemd.tty.term.console=dumb" + +omarchy-cmd-present limine-mkinitcpio || exit 0 +[[ -f $defaults_conf && -r $running_cmdline ]] || exit 0 + +# The running kernel keeps its old command line until reboot, so a marker +# records the machine-wide rebuild: another user's migration must not repeat +# it before then, while a missing marker still retries an interrupted rebuild. +[[ ! -e $rebuild_marker ]] || exit 0 + +# A machine that boots with it already, or whose defaults were edited to leave +# it out, has nothing to rebuild. +[[ " $(<"$running_cmdline") " != *" $param "* ]] || exit 0 +grep -Eq "^KERNEL_CMDLINE\[default\]\+=\".*${param//./\\.}.*\"" "$defaults_conf" || exit 0 + +echo "The booted kernel is missing $param; rebuilding the boot image" +sudo limine-mkinitcpio + +# The boot menu is on the ESP, which /etc/default/limine names; /boot when it +# does not, or is not there. +limine_conf="${OMARCHY_LIMINE_CONF:-}" +if [[ -z $limine_conf ]]; then + esp="" + if [[ -r /etc/default/limine ]]; then + esp=$(sed -n 's/^ESP_PATH=["'\'']\?\([^"'\'']*\).*/\1/p' /etc/default/limine | tail -n 1) + fi + limine_conf="${esp:-/boot}/limine.conf" +fi + +# limine-mkinitcpio carries on past a kernel it could not build, a full boot +# partition for one, and still exits 0. Done means a boot entry has the +# parameter: without that the migration stays pending and the next update +# tries again. A menu with no command lines in it (they are inside the boot +# images then) has nothing to look at, and is taken at its word. +if sudo grep -Eq '^[[:space:]]*cmdline:' "$limine_conf" 2>/dev/null && + ! sudo grep -Eq "^[[:space:]]*cmdline:.*[[:space:]]${param//./\\.}([[:space:]]|\$)" "$limine_conf"; then + echo "The boot menu still has no entry with $param after the rebuild" >&2 + exit 1 +fi + +sudo install -Dm644 /dev/null "$rebuild_marker" diff --git a/test/shell.d/console-no-ansi-query-migration-test.sh b/test/shell.d/console-no-ansi-query-migration-test.sh new file mode 100644 index 00000000..3c977546 --- /dev/null +++ b/test/shell.d/console-no-ansi-query-migration-test.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# +# The migration that puts systemd.tty.term.console=dumb into effect on an +# existing install: the package has already written the parameter into +# omarchy-defaults.conf, so what is left is rebuilding the boot image, once per +# machine, and only where it is needed. + +set -euo pipefail + +source "$(dirname "${BASH_SOURCE[0]}")/base-test.sh" + +migration="$ROOT/migrations/1791581004.sh" +test_dir=$(mktemp -d) +trap 'rm -rf "$test_dir"' EXIT +stub_bin="$test_dir/bin" +mkdir -p "$stub_bin" + +# sudo just drops the prefix; limine-mkinitcpio records that it ran, and fails +# when told to. +printf '#!/bin/bash\nexec "$@"\n' >"$stub_bin/sudo" +# A rebuild writes the new command line into the boot menu, unless it is the +# kind that builds nothing and still exits 0. +cat >"$stub_bin/limine-mkinitcpio" <<'STUB' +#!/bin/bash +echo rebuilt >>"${REBUILDS:?}" +[[ -z ${REBUILD_FAILS:-} ]] || exit 1 +[[ -n ${REBUILD_BUILDS_NOTHING:-} ]] || printf ' cmdline: %s\n' "${NEW_CMDLINE:?}" >"${LIMINE_CONF:?}" +STUB +chmod +x "$stub_bin"/* + +old_cmdline="root=PARTUUID=1 rw quiet splash initramfs_async=0" +new_cmdline="$old_cmdline systemd.tty.term.console=dumb" + +run() { # booted cmdline, defaults conf + : >"$test_dir/rebuilds" + printf '%s\n' "$1" >"$test_dir/cmdline" + printf ' cmdline: %s\n' "$1" >"$test_dir/limine.conf" + PATH="$stub_bin:$ROOT/bin:$PATH" REBUILDS="$test_dir/rebuilds" \ + LIMINE_CONF="$test_dir/limine.conf" NEW_CMDLINE="$new_cmdline" OMARCHY_LIMINE_CONF="$test_dir/limine.conf" \ + OMARCHY_RUNNING_CMDLINE="$test_dir/cmdline" OMARCHY_LIMINE_DEFAULTS_CONF="$2" \ + OMARCHY_LIMINE_REBUILD_MARKER="$test_dir/marker" bash -euo pipefail "$migration" >/dev/null +} +rebuilds() { wc -l <"$test_dir/rebuilds"; } + +# omarchy-migrate runs a migration with bash -euo pipefail, and so does run: +# that is what stops a failed rebuild before the marker is written. +if REBUILD_FAILS=1 run "$old_cmdline" "$ROOT/etc/limine-entry-tool.d/omarchy-defaults.conf"; then + fail "a rebuild that failed was reported as a success" +fi +[[ $(rebuilds) == 1 && ! -e $test_dir/marker ]] || fail "a failed rebuild was marked as done" +pass "a rebuild that fails stops the migration and leaves no marker" + +# limine-mkinitcpio exits 0 past a kernel it could not build. +if REBUILD_BUILDS_NOTHING=1 run "$old_cmdline" "$ROOT/etc/limine-entry-tool.d/omarchy-defaults.conf"; then + fail "a rebuild that changed nothing was reported as a success" +fi +[[ ! -e $test_dir/marker ]] || fail "a rebuild that changed nothing was marked as done" +pass "a rebuild that exits 0 without changing the boot menu leaves the migration pending" + +run "$old_cmdline" "$ROOT/etc/limine-entry-tool.d/omarchy-defaults.conf" +[[ $(rebuilds) == 1 && -e $test_dir/marker ]] || + fail "a machine booted without the parameter did not get its boot image rebuilt" +pass "the next run rebuilds the boot image, with the packaged defaults, and marks it" + +run "$old_cmdline" "$ROOT/etc/limine-entry-tool.d/omarchy-defaults.conf" +[[ $(rebuilds) == 0 ]] || fail "the rebuild ran a second time before a reboot" +pass "the rebuild runs once per machine, not once per user" + +rm -f "$test_dir/marker" +run "$new_cmdline" "$ROOT/etc/limine-entry-tool.d/omarchy-defaults.conf" +[[ $(rebuilds) == 0 && ! -e $test_dir/marker ]] || fail "a machine that already boots with the parameter was rebuilt" +pass "a machine that already boots with the parameter is left alone" + +grep -v "tty.term.console" "$ROOT/etc/limine-entry-tool.d/omarchy-defaults.conf" >"$test_dir/edited.conf" +run "$old_cmdline" "$test_dir/edited.conf" +[[ $(rebuilds) == 0 ]] || fail "a machine whose defaults leave the parameter out was rebuilt anyway" +pass "a machine whose defaults were edited to leave the parameter out is left alone" + +# No Limine: the stand-in goes, and the PATH holds only what the migration +# needs, so a limine-mkinitcpio installed on the machine running this test is +# not found either. +rm "$stub_bin/limine-mkinitcpio" +mkdir -p "$test_dir/tools" +for tool in bash grep install cat sed tail; do ln -s "$(command -v "$tool")" "$test_dir/tools/$tool"; done +: >"$test_dir/rebuilds" +printf '%s\n' "$old_cmdline" >"$test_dir/cmdline" +PATH="$stub_bin:$ROOT/bin:$test_dir/tools" REBUILDS="$test_dir/rebuilds" \ + OMARCHY_RUNNING_CMDLINE="$test_dir/cmdline" OMARCHY_LIMINE_DEFAULTS_CONF="$ROOT/etc/limine-entry-tool.d/omarchy-defaults.conf" \ + OMARCHY_LIMINE_REBUILD_MARKER="$test_dir/marker" "$test_dir/tools/bash" -euo pipefail "$migration" >/dev/null +[[ ! -e $test_dir/marker ]] || fail "a machine without Limine was marked as rebuilt" +pass "a machine without Limine is left alone" diff --git a/test/shell.d/limine-defaults-test.sh b/test/shell.d/limine-defaults-test.sh index 21adc94f..f69e9535 100644 --- a/test/shell.d/limine-defaults-test.sh +++ b/test/shell.d/limine-defaults-test.sh @@ -10,6 +10,10 @@ grep -Fq 'KERNEL_CMDLINE[default]+=" initramfs_async=0"' "$packaged_defaults" || fail "the packaged Limine defaults still unpack the initramfs synchronously" pass "packaged Limine defaults keep Plymouth alive at the LUKS prompt" +grep -Fxq 'KERNEL_CMDLINE[default]+=" systemd.tty.term.console=dumb"' "$packaged_defaults" || + fail "the packaged Limine defaults let PID 1 wait for the console to answer a size query" +pass "packaged Limine defaults keep PID 1 from waiting on the console" + grep -Fxq 'BOOT_ORDER="linux-t2, linux-omarchy, linux-omarchy-*, *, *fallback, Snapshots"' "$packaged_defaults" || fail "packaged Limine defaults protect T2 Macs and prefer the exact Omarchy kernel elsewhere" pass "packaged Limine defaults protect T2 Macs and prefer the exact Omarchy kernel elsewhere"