From 747b7ebdb72fbe0db43de30d2a0e766e803ad63e Mon Sep 17 00:00:00 2001 From: Sir Blob Date: Mon, 21 Sep 2026 02:10:47 +0000 Subject: [PATCH] Install the boot splash theme and set up GRUB without Limine --- bin/blob-boot | 138 ++++++++++++++++++++++++++++++--- default/plymouth/blob.plymouth | 8 ++ default/plymouth/blob.script | 70 +++++++++++++++++ docs/install.md | 7 +- docs/packages.md | 30 +++---- packages/blob.packages | 1 + 6 files changed, 228 insertions(+), 26 deletions(-) create mode 100644 default/plymouth/blob.plymouth create mode 100644 default/plymouth/blob.script diff --git a/bin/blob-boot b/bin/blob-boot index 4240e03..80095d1 100755 --- a/bin/blob-boot +++ b/bin/blob-boot @@ -3,7 +3,9 @@ # blob-boot - boot splash and bootloader management. # # blob-boot [image] Apply a Plymouth boot splash image (default action) -# blob-boot grub Migrate from Limine back to GRUB, detecting every OS +# blob-boot grub Install GRUB and detect every other OS on the machine. +# Migrates off Limine when it is there; on a machine +# that never had it, only GRUB is installed # --purge removes Limine in the same run instead of # keeping it for one fallback boot # blob-boot detect Rescan for other operating systems, rebuild the menu @@ -16,8 +18,12 @@ set -euo pipefail -DEFAULT_IMAGE="$HOME/Documents/dotfiles/branding/boot_flash.png" -PLYMOUTH_LOGO="/usr/share/plymouth/themes/blob/logo.png" +BLOB_PATH="${BLOB_PATH:-$HOME/.local/share/blob}" +DEFAULT_IMAGE="$BLOB_PATH/branding/boot_flash.png" +THEME_SOURCE="$BLOB_PATH/default/plymouth" +THEME_DIR="/usr/share/plymouth/themes/blob" +PLYMOUTH_LOGO="$THEME_DIR/logo.png" +MKINITCPIO_DROPIN="/etc/mkinitcpio.conf.d/blob-plymouth.conf" log() { printf '\n\033[1;36m==> %s\033[0m\n' "$*"; } info() { printf ' %s\n' "$*"; } @@ -43,17 +49,24 @@ apply_splash() { fi [ -f "$image_path" ] || die "File '$image_path' does not exist." - [ -d "$(dirname "$PLYMOUTH_LOGO")" ] || die "Plymouth blob theme is not installed." + command -v plymouth-set-default-theme >/dev/null \ + || die "The 'plymouth' package is not installed. Install it with: sudo pacman -S plymouth" log "Applying boot splash image: $image_path" require_sudo - sudo cp "$image_path" "$PLYMOUTH_LOGO" - sudo chmod 644 "$PLYMOUTH_LOGO" + install_theme "$image_path" + install_initramfs_hook + enable_plymouth_units + + log "Setting the default Plymouth theme" + sudo plymouth-set-default-theme blob log "Rebuilding initramfs" rebuild_initramfs + report_kernel_cmdline + # Under GRUB the menu references the initramfs by path, so it only needs # regenerating if the file set changed - but it is cheap and keeps the menu # honest after a kernel or os-prober change. @@ -65,6 +78,80 @@ apply_splash() { ok "Boot splash successfully updated!" } +install_theme() { + local image_path=$1 + + [ -d "$THEME_SOURCE" ] || die "Theme source '$THEME_SOURCE' is missing from the checkout." + + sudo install -d -m 0755 "$THEME_DIR" + sudo install -m 0644 "$THEME_SOURCE/blob.plymouth" "$THEME_DIR/blob.plymouth" + sudo install -m 0644 "$THEME_SOURCE/blob.script" "$THEME_DIR/blob.script" + sudo install -m 0644 "$image_path" "$PLYMOUTH_LOGO" + info "installed the blob theme to $THEME_DIR" +} + +# A drop-in rather than an edit of HOOKS: mkinitcpio.conf.d is sourced after +# the main config, so the hook can be inserted without rewriting a line the +# user or another tool owns. plymouth has to come after udev, which starts the +# devices it draws on. +install_initramfs_hook() { + sudo install -d -m 0755 "$(dirname "$MKINITCPIO_DROPIN")" + sudo tee "$MKINITCPIO_DROPIN" >/dev/null <<'DROPIN' +# Written by blob-boot. Adds the plymouth hook after udev, once. +if [[ " ${HOOKS[*]} " != *" plymouth "* ]]; then + _hooks=() + for _hook in "${HOOKS[@]}"; do + _hooks+=("$_hook") + [[ $_hook == "udev" || $_hook == "systemd" ]] && _hooks+=("plymouth") + done + HOOKS=("${_hooks[@]}") + unset _hooks _hook +fi +DROPIN + info "wrote $MKINITCPIO_DROPIN" +} + +# install.sh masks these so an unconfigured plymouth cannot hold the console and +# leave the machine with no greeter and no TTY. A configured one needs them: if +# plymouth-quit-wait stays masked the splash never hands the display back. +enable_plymouth_units() { + local unit + for unit in plymouth-start.service plymouth-quit-wait.service; do + if [ "$(systemctl is-enabled "$unit" 2>/dev/null)" = "masked" ]; then + sudo systemctl unmask "$unit" >/dev/null + info "unmasked $unit" + fi + done +} + +# plymouth only draws when the kernel was told to be quiet and to splash. The +# bootloader owns that line, so report rather than rewrite anything but GRUB's. +report_kernel_cmdline() { + local params="quiet splash vt.global_cursor_default=0" + + if grep -qw splash /proc/cmdline; then + info "kernel command line already carries 'splash'" + return 0 + fi + + log "One step left: the kernel command line" + if using_grub; then + warn "GRUB is in charge. Add to GRUB_CMDLINE_LINUX_DEFAULT in /etc/default/grub:" + warn " $params" + warn "then: sudo grub-mkconfig -o /boot/grub/grub.cfg" + elif [ -f /boot/limine.conf ]; then + warn "Limine is in charge. Add to the cmdline in /boot/limine.conf:" + warn " $params" + elif [ -d /boot/loader/entries ]; then + warn "systemd-boot is in charge. Add to the options line in /boot/loader/entries/*.conf:" + warn " $params" + else + warn "Add these to your bootloader's kernel command line:" + warn " $params" + fi + warn "Without them the splash is drawn over by kernel messages." +} + # Call mkinitcpio directly: limine-mkinitcpio-hook installs a wrapper at # /usr/local/bin/mkinitcpio that stops to ask whether to rebuild Limine # entries, which is useless in a script. While Limine is still the active @@ -82,6 +169,26 @@ using_grub() { [ -f /boot/grub/grub.cfg ] && [ -f /boot/EFI/GRUB/grubx64.efi ] } +# Everything Limine-specific below is skipped on a machine that never had it: +# reclaiming its space, removing its hooks, and rewriting the mkinitcpio preset +# its hooks needed. Rewriting that preset elsewhere would drop the fallback +# initramfs for no reason. +limine_present() { + pacman -Qq limine &>/dev/null || [ -f /boot/limine.conf ] || [ -d /boot/EFI/limine ] +} + +install_grub_packages() { + local missing=() + command -v grub-install >/dev/null || missing+=(grub) + command -v os-prober >/dev/null || missing+=(os-prober) + + (( ${#missing[@]} == 0 )) && return 0 + + log "Installing ${missing[*]}" + sudo pacman -S --needed --noconfirm "${missing[@]}" \ + || die "Could not install: ${missing[*]}" +} + # -------------------------------------------------------------------------- # Limine -> GRUB migration # -------------------------------------------------------------------------- @@ -92,10 +199,9 @@ migrate_to_grub() { [ -d /sys/firmware/efi ] || die "Not booted in UEFI mode; this script assumes UEFI." mountpoint -q /boot || die "/boot is not mounted." - command -v grub-install >/dev/null || die "The 'grub' package is not installed." - command -v os-prober >/dev/null || die "The 'os-prober' package is not installed." require_sudo + install_grub_packages local machine_id esp_uuid backup machine_id=$(/dev/null 2>&1 || true info "saved." - reclaim_esp_space "$machine_id" "$backup" - restore_standard_initramfs + if limine_present; then + reclaim_esp_space "$machine_id" "$backup" + restore_standard_initramfs + else + info "Limine is not installed; leaving the initramfs layout alone" + log "Building the initramfs" + rebuild_initramfs + fi configure_grub_scripts install_grub "$esp_uuid" fix_failing_boot_units @@ -529,6 +641,12 @@ set_boot_order() { # Delete every trace of Limine and hand its EFI fallback path to GRUB. purge_limine() { + if ! limine_present; then + log "Claiming the removable EFI fallback path for GRUB" + sudo grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB --removable --recheck + return 0 + fi + log "Removing Limine" drop_limine_pkgs limine limine-mkinitcpio-hook limine-snapper-sync diff --git a/default/plymouth/blob.plymouth b/default/plymouth/blob.plymouth new file mode 100644 index 0000000..0c8d130 --- /dev/null +++ b/default/plymouth/blob.plymouth @@ -0,0 +1,8 @@ +[Plymouth Theme] +Name=Blob +Description=Blob boot splash +ModuleName=script + +[script] +ImageDir=/usr/share/plymouth/themes/blob +ScriptFile=/usr/share/plymouth/themes/blob/blob.script diff --git a/default/plymouth/blob.script b/default/plymouth/blob.script new file mode 100644 index 0000000..79dbdd7 --- /dev/null +++ b/default/plymouth/blob.script @@ -0,0 +1,70 @@ +Window.SetBackgroundTopColor(0.059, 0.063, 0.071); +Window.SetBackgroundBottomColor(0.059, 0.063, 0.071); + +screen.width = Window.GetWidth(); +screen.height = Window.GetHeight(); + +logo.image = Image("logo.png"); +logo.width = logo.image.GetWidth(); +logo.height = logo.image.GetHeight(); + +scale_x = screen.width / logo.width; +scale_y = screen.height / logo.height; + +scale = scale_x; +if (scale_y < scale_x) { + scale = scale_y; +} + +if (scale != 1) { + logo.image = logo.image.Scale(logo.width * scale, logo.height * scale); + logo.width = logo.image.GetWidth(); + logo.height = logo.image.GetHeight(); +} + +logo.sprite = Sprite(logo.image); +logo.sprite.SetX((screen.width - logo.width) / 2); +logo.sprite.SetY((screen.height - logo.height) / 2); +logo.sprite.SetZ(0); + +prompt.sprite = Sprite(); +prompt.sprite.SetZ(10); +message.sprite = Sprite(); +message.sprite.SetZ(10); + +fun draw_prompt(text) { + if (text == "") { + prompt.sprite.SetImage(Image.Text(" ", 1, 1, 1)); + return; + } + + prompt.image = Image.Text(text, 0.8, 0.82, 0.85); + prompt.sprite.SetImage(prompt.image); + prompt.sprite.SetX((screen.width - prompt.image.GetWidth()) / 2); + prompt.sprite.SetY(screen.height * 0.82); +} + +fun display_password_callback(prompt_text, bullets) { + dots = ""; + index = 0; + while (index < bullets) { + dots += "*"; + index++; + } + draw_prompt(prompt_text + " " + dots); +} + +fun display_normal_callback() { + draw_prompt(""); +} + +fun message_callback(text) { + message.image = Image.Text(text, 0.8, 0.82, 0.85); + message.sprite.SetImage(message.image); + message.sprite.SetX((screen.width - message.image.GetWidth()) / 2); + message.sprite.SetY(screen.height * 0.9); +} + +Plymouth.SetDisplayPasswordFunction(display_password_callback); +Plymouth.SetDisplayNormalFunction(display_normal_callback); +Plymouth.SetMessageFunction(message_callback); diff --git a/docs/install.md b/docs/install.md index 0ac4c8d..4e2376d 100644 --- a/docs/install.md +++ b/docs/install.md @@ -74,9 +74,10 @@ with `--check`. avahi, docker, power-profiles-daemon, and oomd. `NetworkManager-wait-online.service` is masked so a slow DHCP lease cannot hold up the login screen, and `plymouth-start.service` and - `plymouth-quit-wait.service` are masked because Blob configures no boot - splash: an unconfigured plymouth holds the console and leaves the machine - with neither a greeter nor a TTY. See [packages.md](packages.md). + `plymouth-quit-wait.service` are masked because an unconfigured plymouth + holds the console and leaves the machine with neither a greeter nor a TTY. + `blob boot` unmasks them when it sets the splash up. See + [packages.md](packages.md#the-boot-splash). 9. **The login screen** is SDDM. It has to be installed first: if `sddm.service` is not there, the installer says whether the package is missing or whether systemd has simply not re-read its units yet, and carries diff --git a/docs/packages.md b/docs/packages.md index a8caedb..f0396f2 100644 --- a/docs/packages.md +++ b/docs/packages.md @@ -72,7 +72,7 @@ Two are worth knowing about: `install.sh` installs what is missing before it writes any config: ``` -packages/blob.packages 121 packages from core, extra, multilib +packages/blob.packages 123 packages from core, extra, multilib packages/blob-aur.packages 11 packages with no official equivalent ``` @@ -136,23 +136,27 @@ Omarchy's own package list, because they were installed by hand on this machine: `playerctl` is in official `extra`, so it sits in the repo list. -## No boot splash +## The boot splash -`plymouth` is deliberately not on either list. Blob configures no splash: there -is no mkinitcpio hook, no kernel command line, and `default/plymouth/` ships no -theme assets, so `blob-plymouth-set` has nothing to publish either. An installed -but unconfigured plymouth is not harmless - it takes the console at boot and -never hands it back, and `getty@tty1` waits on `plymouth-quit-wait.service`, so -the machine reaches neither the greeter nor a TTY. - -`install.sh` masks `plymouth-start.service` and `plymouth-quit-wait.service` for -that reason, which un-breaks a machine that already has the package. To go -further: +`plymouth` is installed but left inert, and the splash is opt-in through one +command: ``` -sudo pacman -Rns plymouth +blob boot # branding/boot_flash.png +blob boot ~/some/image.png # or any other image ``` +The reason it is opt-in is that an installed but *unconfigured* plymouth is not +harmless: it takes the console at boot and never hands it back, and +`getty@tty1` waits on `plymouth-quit-wait.service`, so the machine reaches +neither the greeter nor a TTY. `install.sh` therefore masks +`plymouth-start.service` and `plymouth-quit-wait.service`, and `blob boot` +unmasks them as part of configuring everything else - the theme in +`default/plymouth/`, the `plymouth` mkinitcpio hook, and the initramfs rebuild. +It finishes by printing the kernel parameters your bootloader still needs +(`quiet splash vt.global_cursor_default=0`), because that line is the one thing +it will not rewrite for a bootloader other than GRUB. + ## Updating ``` diff --git a/packages/blob.packages b/packages/blob.packages index 23d6b04..4208955 100644 --- a/packages/blob.packages +++ b/packages/blob.packages @@ -22,6 +22,7 @@ wl-clipboard wtype socat inotify-tools +plymouth polkit # Shell widget backends