Stop the boot from waiting on a black screen for the console to answer

About 1 boot in 10 of an installed system stops on a black screen at
"Starting Switch Root" until a key is pressed. 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.

Measured in QEMU on repeated boots of one install: 3 hangs in 30 and 1
in 40 on the prebuilt UKI, 7 in 40 on the stock initramfs, every one of
them ended by pressing Enter; none in 40 with a serial console, none in
40 with plymouth.enable=0, and none in 80 with
systemd.tty.term.console=dumb, which makes systemd skip the query.
systemd fixed two variants of this in v261; Arch's 261.2 still shows it.

The parameter goes where the last Plymouth boot fix went (3f725531,
initramfs_async=0): into the packaged omarchy-defaults.conf for fresh
installs, and a migration rebuilds the boot image on existing systems,
once per machine, only where the booted command line lacks it. Nothing
visible changes under "quiet splash".

Refs #2931
This commit is contained in:
Emir Beganovic committed 2026-10-09 23:26:20 +02:00
1 parent 2519fd9a97
commit f03bf06742
4 files changed
+110

No files matched your search

@@ -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
+31
View File
@@ -0,0 +1,31 @@
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
sudo install -Dm644 /dev/null "$rebuild_marker"
@@ -0,0 +1,66 @@
#!/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.
printf '#!/bin/bash\nexec "$@"\n' >"$stub_bin/sudo"
printf '#!/bin/bash\necho rebuilt >>"${REBUILDS:?}"\n' >"$stub_bin/limine-mkinitcpio"
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"
PATH="$stub_bin:$ROOT/bin:$PATH" REBUILDS="$test_dir/rebuilds" \
OMARCHY_RUNNING_CMDLINE="$test_dir/cmdline" OMARCHY_LIMINE_DEFAULTS_CONF="$2" \
OMARCHY_LIMINE_REBUILD_MARKER="$test_dir/marker" bash "$migration" >/dev/null
}
rebuilds() { wc -l <"$test_dir/rebuilds"; }
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 "a machine booted without the parameter gets its boot image rebuilt, with the packaged defaults"
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; 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" "$migration" >/dev/null
[[ ! -e $test_dir/marker ]] || fail "a machine without Limine was marked as rebuilt"
pass "a machine without Limine is left alone"
+4
View File
@@ -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"