From fe72a4c71ba5579a64ba1f2a803a83293f05ee03 Mon Sep 17 00:00:00 2001 From: The_miro Date: Fri, 26 Jun 2026 10:44:02 +0200 Subject: [PATCH] feat(plymouth): add M-Archy boot splash module with skull logo + spinner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Installs a custom Plymouth theme (m-archy) with bg-skull.svg converted to PNG (Plymouth is PNG-only via libpng — no SVG support) and a 12-dot magenta spinner animation. Enabled by default in tui-install.sh; also available as an optional module in install-modules.sh. Archiso image remains Plymouth-free. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01SyBNiWy3wpawrWb9ryVk7p --- setup/install-modules.sh | 10 +- setup/modules/optional-Modules/plymouth.sh | 175 +++++++++++++++++++++ setup/tui-install.sh | 38 +++-- 3 files changed, 203 insertions(+), 20 deletions(-) create mode 100644 setup/modules/optional-Modules/plymouth.sh diff --git a/setup/install-modules.sh b/setup/install-modules.sh index c198139..53b7e57 100755 --- a/setup/install-modules.sh +++ b/setup/install-modules.sh @@ -111,6 +111,7 @@ count_steps() { [[ "$sel" == *"ffmpeg"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"localtunnel"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"butter"* ]] && TOTAL=$(( TOTAL + 1 )) + [[ "$sel" == *"plymouth"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"tlp"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"steam"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"vesktop"* ]] && TOTAL=$(( TOTAL + 1 )) @@ -207,6 +208,7 @@ SELECTED=$(dialog --backtitle "$BACKTITLE" \ "ffmpeg" "FFmpeg extras thumbnailer · GStreamer codecs" off \ "localtunnel" "LocalTunnel expose localhost via tunnel" off \ "butter" "butter btrfs snapshot backup (AUR)" off \ + "plymouth" "Plymouth boot splash — skull logo + spinner" off \ "tlp" "TLP laptop power management" off \ \ "steam" "Steam gaming platform" off \ @@ -278,7 +280,8 @@ SUMMARY="" [[ "$SELECTED" == *"imagemagick"* ]] && SUMMARY+=" ✦ ImageMagick\n" [[ "$SELECTED" == *"ffmpeg"* ]] && SUMMARY+=" ✦ FFmpeg extras\n" [[ "$SELECTED" == *"localtunnel"* ]] && SUMMARY+=" ✦ LocalTunnel\n" -[[ "$SELECTED" == *"butter"* ]] && SUMMARY+=" ✦ butter (btrfs backup)\n" +[[ "$SELECTED" == *"butter"* ]] && SUMMARY+=" ✦ butter (btrfs backup)\n" +[[ "$SELECTED" == *"plymouth"* ]] && SUMMARY+=" ✦ Plymouth boot splash\n" [[ "$SELECTED" == *"tlp"* ]] && SUMMARY+=" ✦ TLP\n" [[ "$SELECTED" == *"steam"* ]] && SUMMARY+=" ✦ Steam\n" [[ "$SELECTED" == *"vesktop"* ]] && SUMMARY+=" ✦ Vesktop\n" @@ -351,8 +354,9 @@ DE_DIR="$MODULES/Desktop-Environments" [[ "$SELECTED" == *"imagemagick"* ]] && run_module "ImageMagick" "$APPS/imagemagick.sh" [[ "$SELECTED" == *"ffmpeg"* ]] && run_module "FFmpeg extras" "$APPS/ffmpeg.sh" [[ "$SELECTED" == *"localtunnel"* ]] && run_module "LocalTunnel" "$APPS/localtunnel.sh" -[[ "$SELECTED" == *"butter"* ]] && run_module "butter" "$APPS/butter.sh" -[[ "$SELECTED" == *"tlp"* ]] && run_module "TLP" "$APPS/tlp.sh" +[[ "$SELECTED" == *"butter"* ]] && run_module "butter" "$APPS/butter.sh" +[[ "$SELECTED" == *"plymouth"* ]] && run_module "Plymouth" "$MODULES/optional-Modules/plymouth.sh" +[[ "$SELECTED" == *"tlp"* ]] && run_module "TLP" "$APPS/tlp.sh" [[ "$SELECTED" == *"steam"* ]] && run_module "Steam" "$APPS/steam.sh" [[ "$SELECTED" == *"vesktop"* ]] && run_module "Vesktop" "$APPS/vesktop.sh" [[ "$SELECTED" == *"spotify"* ]] && run_module "Spotify" "$APPS/spotify.sh" diff --git a/setup/modules/optional-Modules/plymouth.sh b/setup/modules/optional-Modules/plymouth.sh new file mode 100644 index 0000000..e287f66 --- /dev/null +++ b/setup/modules/optional-Modules/plymouth.sh @@ -0,0 +1,175 @@ +#!/bin/bash +# plymouth.sh — Plymouth boot splash installer +# +# Installs the M-Archy Plymouth theme: skull logo centred on a dark background +# with a magenta spinning-dot animation below it. +# +# SVG NOTE: Plymouth's image loader (ply-image) is PNG-only — it links against +# libpng and has no SVG or gdk-pixbuf dependency. The ply-image.h header is +# even commented "png file loader". bg-skull.svg must be converted to PNG with +# rsvg-convert (higher fidelity than ImageMagick for SVG) before deployment. +# +# Steps: +# 1. Install plymouth (extra repo) +# 2. Install librsvg (rsvg-convert) + imagemagick if absent +# 3. Convert ~/Pictures/bg-skull.svg → logo.png (300 px wide) +# 4. Generate a 10×10 magenta dot.png for the spinner +# 5. Write the m-archy theme (.plymouth descriptor + .script animation) +# 6. Register with plymouth-set-default-theme +# 7. Inject plymouth / sd-plymouth hook into /etc/mkinitcpio.conf +# 8. Add 'quiet splash' to GRUB_CMDLINE_LINUX_DEFAULT +# 9. Regenerate GRUB config and initramfs + +set -euo pipefail +source "$(dirname "${BASH_SOURCE[0]}")/../lib/logging.sh" + +THEME_DIR="/usr/share/plymouth/themes/m-archy" +LOGO_SVG="$HOME/Pictures/bg-skull.svg" + +# ── Install Plymouth ────────────────────────────────────────────────────────── +log "Installing Plymouth..." +sudo pacman -S --noconfirm --needed plymouth + +# ── Ensure conversion tools ─────────────────────────────────────────────────── +# Plymouth only loads PNG (libpng); rsvg-convert gives the best SVG→PNG output. +if ! command -v rsvg-convert &>/dev/null; then + log "Installing librsvg (rsvg-convert) for SVG→PNG conversion..." + sudo pacman -S --noconfirm --needed librsvg +fi +if ! command -v convert &>/dev/null; then + log "Installing imagemagick (dot generation)..." + sudo pacman -S --noconfirm --needed imagemagick +fi + +# ── Convert logo SVG → PNG ──────────────────────────────────────────────────── +TMP_LOGO="$(mktemp /tmp/plymouth-logo.XXXXXX.png)" +TMP_DOT="/tmp/plymouth-dot.png" +trap 'rm -f "$TMP_LOGO" "$TMP_DOT"' EXIT + +if [[ -f "$LOGO_SVG" ]]; then + log "Converting $LOGO_SVG → PNG (300 px wide)..." + rsvg-convert -w 300 "$LOGO_SVG" -o "$TMP_LOGO" +else + warn "$LOGO_SVG not found — using transparent placeholder." + warn "Place bg-skull.svg in ~/Pictures and re-run this module to update the logo." + convert -size 300x300 xc:transparent "$TMP_LOGO" +fi + +# ── Generate spinner dot ────────────────────────────────────────────────────── +# 10×10 magenta circle — centred at (5,5), radius 4 px +convert -size 10x10 xc:transparent \ + -fill '#E40046' \ + -draw 'circle 5,5 5,1' \ + "$TMP_DOT" + +# ── Install theme files ─────────────────────────────────────────────────────── +log "Installing M-Archy Plymouth theme..." +sudo mkdir -p "$THEME_DIR" +sudo cp "$TMP_LOGO" "$THEME_DIR/logo.png" +sudo cp "$TMP_DOT" "$THEME_DIR/dot.png" + +sudo tee "$THEME_DIR/m-archy.plymouth" > /dev/null <<'EOF' +[Plymouth Theme] +Name=M-Archy +Description=M-Archy boot splash — skull logo with spinning dots +ModuleName=script + +[script] +ImageDir=/usr/share/plymouth/themes/m-archy +ScriptFile=/usr/share/plymouth/themes/m-archy/m-archy.script +EOF + +sudo tee "$THEME_DIR/m-archy.script" > /dev/null <<'EOF' +# M-Archy Plymouth splash — skull logo + magenta spinner + +Window.SetBackgroundTopColor (0.10, 0.10, 0.10); +Window.SetBackgroundBottomColor (0.07, 0.07, 0.07); + +screen_width = Window.GetWidth (); +screen_height = Window.GetHeight (); + +# Centred skull logo +logo.image = Image ("logo.png"); +logo.sprite = Sprite (logo.image); +logo.sprite.SetX (Math.Int (screen_width / 2 - logo.image.GetWidth () / 2)); +logo.sprite.SetY (Math.Int (screen_height / 2 - logo.image.GetHeight () / 2)); +logo.sprite.SetZ (10); + +# 12-dot spinner below the logo +num_dots = 12; +dot_r = 5; +orbit = 35; +cx = screen_width / 2; +cy = screen_height / 2 + logo.image.GetHeight () / 2 + 55; +dot.image = Image ("dot.png"); + +for (i = 0; i < num_dots; i++) { + dot[i].sprite = Sprite (dot.image); + dot[i].sprite.SetZ (20); +} + +frame = 0; + +fun refresh_callback () { + frame++; + step = 2 * Math.Pi / num_dots; + for (i = 0; i < num_dots; i++) { + angle = step * i + frame * 0.15; + dot[i].sprite.SetX (Math.Int (cx + Math.Cos (angle) * orbit - dot_r)); + dot[i].sprite.SetY (Math.Int (cy + Math.Sin (angle) * orbit - dot_r)); + t = (i + Math.Int (frame / 4) % num_dots) % num_dots; + f = t / num_dots; + dot[i].sprite.SetOpacity (0.15 + 0.85 * f * f); + } +} + +Plymouth.SetRefreshFunction (refresh_callback); +EOF + +# ── Register theme ──────────────────────────────────────────────────────────── +log "Registering m-archy as default Plymouth theme..." +sudo plymouth-set-default-theme m-archy + +# ── mkinitcpio: inject Plymouth hook ───────────────────────────────────────── +log "Adding Plymouth hook to /etc/mkinitcpio.conf..." +if grep -q '\bplymouth\b\|sd-plymouth' /etc/mkinitcpio.conf; then + skip "Plymouth hook already present in mkinitcpio.conf" +else + # systemd hook → sd-plymouth goes after systemd + # traditional udev hook → plymouth goes after udev + if grep -qE 'HOOKS=\([^)]*\bsystemd\b' /etc/mkinitcpio.conf; then + sudo sed -Ei 's/(\bsystemd\b)( |\))/\1 sd-plymouth\2/' /etc/mkinitcpio.conf + log "Injected sd-plymouth hook after systemd" + else + sudo sed -Ei 's/(\budev\b)( |\))/\1 plymouth\2/' /etc/mkinitcpio.conf + log "Injected plymouth hook after udev" + fi + + if ! grep -q '\bplymouth\b\|sd-plymouth' /etc/mkinitcpio.conf; then + warn "Could not auto-inject Plymouth hook." + warn "Manually add 'plymouth' after 'udev' in /etc/mkinitcpio.conf." + fi +fi + +# ── GRUB: add quiet splash ──────────────────────────────────────────────────── +GRUB_CONF="/etc/default/grub" +if [[ -f "$GRUB_CONF" ]]; then + if grep -q '\bsplash\b' "$GRUB_CONF"; then + skip "'splash' already present in $GRUB_CONF" + else + log "Adding 'quiet splash' to GRUB_CMDLINE_LINUX_DEFAULT..." + sudo sed -i 's/^GRUB_CMDLINE_LINUX_DEFAULT="\(.*\)"/GRUB_CMDLINE_LINUX_DEFAULT="\1 quiet splash"/' "$GRUB_CONF" + sudo sed -i 's/quiet quiet/quiet/g' "$GRUB_CONF" + fi + log "Regenerating GRUB config..." + sudo grub-mkconfig -o /boot/grub/grub.cfg +else + warn "/etc/default/grub not found." + warn "If using systemd-boot, add 'quiet splash' to your loader entry manually." +fi + +# ── Rebuild initramfs ───────────────────────────────────────────────────────── +log "Rebuilding initramfs (this bakes the theme into the initrd)..." +sudo mkinitcpio -P + +log "Plymouth m-archy theme installed. Reboot to see the splash screen." diff --git a/setup/tui-install.sh b/setup/tui-install.sh index 53e7ebb..461dcef 100755 --- a/setup/tui-install.sh +++ b/setup/tui-install.sh @@ -152,10 +152,11 @@ count_steps() { local c="$1" de="$2" a="${3:-}" TOTAL=0 # Base components: each keyword maps to exactly one module script. - [[ "$c" == *"pkg"* ]] && TOTAL=$(( TOTAL + 1 )) - [[ "$c" == *"core"* ]] && TOTAL=$(( TOTAL + 1 )) - [[ "$c" == *"svc"* ]] && TOTAL=$(( TOTAL + 1 )) - [[ "$c" == *"shell"* ]] && TOTAL=$(( TOTAL + 1 )) + [[ "$c" == *"pkg"* ]] && TOTAL=$(( TOTAL + 1 )) + [[ "$c" == *"core"* ]] && TOTAL=$(( TOTAL + 1 )) + [[ "$c" == *"svc"* ]] && TOTAL=$(( TOTAL + 1 )) + [[ "$c" == *"shell"* ]] && TOTAL=$(( TOTAL + 1 )) + [[ "$c" == *"plymouth"* ]] && TOTAL=$(( TOTAL + 1 )) # A non-"none" DE selection always installs exactly one DE module. [[ "$de" != "none" ]] && TOTAL=$(( TOTAL + 1 )) # Optional app modules: one glob check per app, one increment per match. @@ -395,11 +396,12 @@ else # Esc / Cancel returns exit code 1; the '|| { ...; exit 0; }' treats that as a clean abort. COMPONENTS=$(dialog --backtitle "$BACKTITLE" \ --title " Select Components " \ - --checklist "Space toggles · Enter confirms · Esc quits" 15 68 4 \ - "pkg" "Package managers yay · nvm · rust" on \ - "core" "Core packages 100+ base system packages" on \ - "svc" "Core services NetworkManager · cronie · fail2ban" on \ - "shell" "Shell setup zsh · nvim · yazi · micro · starship" on \ + --checklist "Space toggles · Enter confirms · Esc quits" 16 68 5 \ + "pkg" "Package managers yay · nvm · rust" on \ + "core" "Core packages 100+ base system packages" on \ + "svc" "Core services NetworkManager · cronie · fail2ban" on \ + "shell" "Shell setup zsh · nvim · yazi · micro · starship" on \ + "plymouth" "Plymouth boot splash — skull logo + spinner" on \ 3>&1 1>&2 2>&3) || { clear; echo "Aborted."; exit 0; } fi @@ -539,10 +541,11 @@ if ! $ANSWERFILE_MODE; then # user can review the full list before any changes are made to the system. SUMMARY="" [[ -n "$HOSTNAME_SET" ]] && SUMMARY+=" ✦ Hostname: $HOSTNAME_SET\n" - [[ "$COMPONENTS" == *"pkg"* ]] && SUMMARY+=" ✦ Package managers (yay, nvm, rust)\n" - [[ "$COMPONENTS" == *"core"* ]] && SUMMARY+=" ✦ Core packages\n" - [[ "$COMPONENTS" == *"svc"* ]] && SUMMARY+=" ✦ Core services\n" - [[ "$COMPONENTS" == *"shell"* ]] && SUMMARY+=" ✦ Shell setup\n" + [[ "$COMPONENTS" == *"pkg"* ]] && SUMMARY+=" ✦ Package managers (yay, nvm, rust)\n" + [[ "$COMPONENTS" == *"core"* ]] && SUMMARY+=" ✦ Core packages\n" + [[ "$COMPONENTS" == *"svc"* ]] && SUMMARY+=" ✦ Core services\n" + [[ "$COMPONENTS" == *"shell"* ]] && SUMMARY+=" ✦ Shell setup\n" + [[ "$COMPONENTS" == *"plymouth"* ]] && SUMMARY+=" ✦ Plymouth boot splash\n" [[ "$DE" != "none" && "$DE" != "" ]] && SUMMARY+=" ✦ Desktop environment: $DE\n" [[ "$SHELL_RC" == "dotfiles" ]] && SUMMARY+=" ✦ Shell rc files → /etc/skel (dotfiles)\n" \ || SUMMARY+=" ✦ Shell rc files → /etc/skel (system defaults)\n" @@ -639,10 +642,11 @@ count_steps "$COMPONENTS" "$DE" "$SELECTED_APPS" # ── Installation: base components ───────────────────────────────────────────── # Each guard uses glob matching against the space-separated COMPONENTS string. # Order matters: package managers must be installed before packages that need yay/rust. -[[ "$COMPONENTS" == *"pkg"* ]] && run_module "Package Managers" "$MODULES/package-managers.sh" -[[ "$COMPONENTS" == *"core"* ]] && run_module "Core Packages" "$MODULES/core-packages.sh" -[[ "$COMPONENTS" == *"svc"* ]] && run_module "Core Services" "$MODULES/core.sh" -[[ "$COMPONENTS" == *"shell"* ]] && run_module "Shell Setup" "$MODULES/shell-setup.sh" +[[ "$COMPONENTS" == *"pkg"* ]] && run_module "Package Managers" "$MODULES/package-managers.sh" +[[ "$COMPONENTS" == *"core"* ]] && run_module "Core Packages" "$MODULES/core-packages.sh" +[[ "$COMPONENTS" == *"svc"* ]] && run_module "Core Services" "$MODULES/core.sh" +[[ "$COMPONENTS" == *"shell"* ]] && run_module "Shell Setup" "$MODULES/shell-setup.sh" +[[ "$COMPONENTS" == *"plymouth"* ]] && run_module "Plymouth" "$MODULES/optional-Modules/plymouth.sh" # Route the single selected DE value to its corresponding install script. # "none" is the skip sentinel — no case branch matches it intentionally.