feat: Add Plymouth boot splash and fix kiosk mode

- Add Plymouth boot splash theme (VT100/DEC PDP-style green phosphor)
- Boot splash now shows DURING boot, not just at login
- Fix kiosk service: use tty7, proper VT allocation, better env vars
- Add splash parameter to GRUB menu entries
- Add initramfs configuration for Plymouth framebuffer
- RPi build: Plymouth support with ARM64 theme

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-03-30 12:28:24 +02:00
parent f221fa6844
commit c51ae2758d
3 changed files with 263 additions and 21 deletions

View File

@ -145,6 +145,7 @@ INCLUDE_PKGS+=",iproute2,iputils-ping,ethtool,net-tools,wireguard-tools"
INCLUDE_PKGS+=",sudo,less,vim-tiny,logrotate,cron,rsync,jq,dnsmasq"
INCLUDE_PKGS+=",linux-image-amd64,live-boot,live-boot-initramfs-tools,live-config,live-config-systemd"
INCLUDE_PKGS+=",grub-efi-amd64,efibootmgr,pciutils,usbutils,lsb-release"
INCLUDE_PKGS+=",plymouth,plymouth-themes"
debootstrap --arch=amd64 --include="${INCLUDE_PKGS}" \
"${SUITE}" "${ROOTFS}" "${APT_MIRROR}"
@ -547,6 +548,151 @@ chroot "${ROOTFS}" systemctl enable secubox-net-fallback.service 2>/dev/null ||
chroot "${ROOTFS}" systemctl disable systemd-networkd-wait-online.service 2>/dev/null || true
chroot "${ROOTFS}" systemctl mask systemd-networkd-wait-online.service 2>/dev/null || true
# ── Plymouth Boot Splash Theme ─────────────────────────────────────
log "Installing Plymouth boot splash..."
# Create SecuBox Plymouth theme directory
PLYMOUTH_DIR="${ROOTFS}/usr/share/plymouth/themes/secubox"
mkdir -p "${PLYMOUTH_DIR}"
# Create theme descriptor
cat > "${PLYMOUTH_DIR}/secubox.plymouth" <<'PLYTHEME'
[Plymouth Theme]
Name=SecuBox Cyber
Description=SecuBox VT100/DEC PDP-11 style boot splash
ModuleName=script
[script]
ImageDir=/usr/share/plymouth/themes/secubox
ScriptFile=/usr/share/plymouth/themes/secubox/secubox.script
PLYTHEME
# Create Plymouth script (text-based retro look)
cat > "${PLYMOUTH_DIR}/secubox.script" <<'PLYSCRIPT'
# SecuBox Plymouth Theme - VT100/DEC PDP-11 Style
# Green phosphor terminal aesthetic
# Colors
Window.SetBackgroundTopColor(0.0, 0.0, 0.0);
Window.SetBackgroundBottomColor(0.0, 0.05, 0.0);
# Logo and text positioning
screen_width = Window.GetWidth();
screen_height = Window.GetHeight();
center_x = screen_width / 2;
center_y = screen_height / 2;
# Banner text (ASCII art simulation)
banner_text = "SECUBOX CYBER DEFENSE SYSTEM";
banner_sprite = Sprite();
banner_image = Image.Text(banner_text, 0.0, 1.0, 0.0, "Fixed");
banner_sprite.SetImage(banner_image);
banner_sprite.SetPosition(center_x - banner_image.GetWidth() / 2, center_y - 100, 1);
# Version line
version_text = "DEC PDP-11/70 COMPATIBLE - SECURE BOOT SEQUENCE";
version_sprite = Sprite();
version_image = Image.Text(version_text, 0.0, 0.8, 0.0, "Fixed");
version_sprite.SetImage(version_image);
version_sprite.SetPosition(center_x - version_image.GetWidth() / 2, center_y - 60, 1);
# Separator line
sep_text = "================================================================";
sep_sprite = Sprite();
sep_image = Image.Text(sep_text, 0.0, 0.6, 0.0, "Fixed");
sep_sprite.SetImage(sep_image);
sep_sprite.SetPosition(center_x - sep_image.GetWidth() / 2, center_y - 40, 1);
# Progress indicator
progress_sprite = Sprite();
fun refresh_callback() {
# Blinking cursor effect
time = Plymouth.GetTime();
if (Math.Int(time * 2) % 2 == 0) {
cursor_text = "_";
} else {
cursor_text = " ";
}
cursor_image = Image.Text(cursor_text, 0.0, 1.0, 0.0, "Fixed");
cursor_sprite = Sprite(cursor_image);
cursor_sprite.SetPosition(center_x + 100, center_y + 60, 2);
}
Plymouth.SetRefreshFunction(refresh_callback);
# Boot progress bar
progress_box_image = Image.Text("[ ]", 0.0, 0.8, 0.0, "Fixed");
progress_box_sprite = Sprite(progress_box_image);
progress_box_sprite.SetPosition(center_x - progress_box_image.GetWidth() / 2, center_y + 20, 1);
fun boot_progress_callback(duration, progress) {
# Update progress bar fill
fill_count = Math.Int(progress * 30);
fill_text = "";
for (i = 0; i < fill_count; i++) {
fill_text = fill_text + "#";
}
for (i = fill_count; i < 30; i++) {
fill_text = fill_text + " ";
}
progress_text = "[" + fill_text + "]";
progress_image = Image.Text(progress_text, 0.0, 1.0, 0.0, "Fixed");
progress_sprite.SetImage(progress_image);
progress_sprite.SetPosition(center_x - progress_image.GetWidth() / 2, center_y + 20, 1);
}
Plymouth.SetBootProgressFunction(boot_progress_callback);
# Status message display
message_sprite = Sprite();
fun message_callback(text) {
message_image = Image.Text("> " + text, 0.0, 0.7, 0.0, "Fixed");
message_sprite.SetImage(message_image);
message_sprite.SetPosition(center_x - 200, center_y + 60, 1);
}
Plymouth.SetMessageFunction(message_callback);
# Display mode change
fun display_normal_callback() {
# Normal boot display
}
fun display_password_callback(prompt, bullets) {
# Password entry (for encrypted disks)
password_sprite = Sprite();
password_image = Image.Text(prompt + " " + bullets, 0.0, 1.0, 0.0, "Fixed");
password_sprite.SetImage(password_image);
password_sprite.SetPosition(center_x - password_image.GetWidth() / 2, center_y + 100, 1);
}
Plymouth.SetDisplayNormalFunction(display_normal_callback);
Plymouth.SetDisplayPasswordFunction(display_password_callback);
# System update messages
fun system_update_callback(progress) {
update_text = "SYSTEM UPDATE: " + Math.Int(progress * 100) + "%";
update_image = Image.Text(update_text, 0.0, 0.8, 0.0, "Fixed");
update_sprite = Sprite(update_image);
update_sprite.SetPosition(center_x - update_image.GetWidth() / 2, center_y + 80, 1);
}
Plymouth.SetSystemUpdateFunction(system_update_callback);
PLYSCRIPT
# Set SecuBox theme as default
mkdir -p "${ROOTFS}/etc/plymouth"
echo "[Daemon]" > "${ROOTFS}/etc/plymouth/plymouthd.conf"
echo "Theme=secubox" >> "${ROOTFS}/etc/plymouth/plymouthd.conf"
echo "ShowDelay=0" >> "${ROOTFS}/etc/plymouth/plymouthd.conf"
# Update alternatives to use our theme
chroot "${ROOTFS}" plymouth-set-default-theme secubox 2>/dev/null || true
ok "Plymouth SecuBox theme installed"
ok "Base configuration complete"
# ══════════════════════════════════════════════════════════════════
@ -811,8 +957,12 @@ echo "squashfs" >> "${ROOTFS}/etc/modules-load.d/live.conf"
echo "loop" >> "${ROOTFS}/etc/modules-load.d/live.conf"
echo "overlay" >> "${ROOTFS}/etc/modules-load.d/live.conf"
# Regenerate initramfs with live-boot hooks
log "Regenerating initramfs..."
# Ensure Plymouth is in initramfs
mkdir -p "${ROOTFS}/etc/initramfs-tools/conf.d"
echo "FRAMEBUFFER=y" > "${ROOTFS}/etc/initramfs-tools/conf.d/plymouth"
# Regenerate initramfs with live-boot and Plymouth hooks
log "Regenerating initramfs with Plymouth..."
chroot "${ROOTFS}" update-initramfs -u -k all 2>/dev/null || warn "initramfs update failed"
# Clean APT
@ -954,7 +1104,7 @@ set menu_color_normal=cyan/black
set menu_color_highlight=white/blue
menuentry "SecuBox Live" {
linux ($live)/live/vmlinuz boot=live live-media-path=live components persistence console=tty0
linux ($live)/live/vmlinuz boot=live live-media-path=live components persistence quiet splash
initrd ($live)/live/initrd.img
}
@ -964,7 +1114,7 @@ menuentry "SecuBox Live (Kiosk GUI)" {
}
menuentry "SecuBox Live (Bridge Mode)" {
linux ($live)/live/vmlinuz boot=live live-media-path=live components persistence console=tty0 secubox.netmode=bridge
linux ($live)/live/vmlinuz boot=live live-media-path=live components persistence quiet splash secubox.netmode=bridge
initrd ($live)/live/initrd.img
}
@ -974,7 +1124,7 @@ menuentry "SecuBox Live (Safe Mode)" {
}
menuentry "SecuBox Live (To RAM)" {
linux ($live)/live/vmlinuz boot=live live-media-path=live components toram console=tty0
linux ($live)/live/vmlinuz boot=live live-media-path=live components toram quiet splash
initrd ($live)/live/initrd.img
}

View File

@ -109,7 +109,7 @@ INCLUDE_PKGS="systemd,systemd-sysv,dbus,nftables,openssh-server"
INCLUDE_PKGS+=",python3,python3-pip,nginx,curl,wget,ca-certificates,gnupg"
INCLUDE_PKGS+=",iproute2,iputils-ping,net-tools,wireguard-tools"
INCLUDE_PKGS+=",sudo,less,vim-tiny,cron,rsync,jq"
INCLUDE_PKGS+=",linux-image-arm64,raspi-firmware"
INCLUDE_PKGS+=",linux-image-arm64,plymouth,plymouth-themes"
debootstrap --arch=arm64 --foreign --include="${INCLUDE_PKGS}" \
"${SUITE}" "${ROOTFS}" "${APT_MIRROR}"
@ -268,6 +268,85 @@ if [ -t 0 ] && [ -z "$SECUBOX_SPLASH_SHOWN" ]; then
fi
BASHRC
# ── Plymouth Boot Splash Theme ─────────────────────────────────────
log "Installing Plymouth boot splash..."
PLYMOUTH_DIR="${ROOTFS}/usr/share/plymouth/themes/secubox"
mkdir -p "${PLYMOUTH_DIR}"
cat > "${PLYMOUTH_DIR}/secubox.plymouth" <<'PLYTHEME'
[Plymouth Theme]
Name=SecuBox Cyber
Description=SecuBox VT100/DEC PDP-11 style boot splash
ModuleName=script
[script]
ImageDir=/usr/share/plymouth/themes/secubox
ScriptFile=/usr/share/plymouth/themes/secubox/secubox.script
PLYTHEME
cat > "${PLYMOUTH_DIR}/secubox.script" <<'PLYSCRIPT'
# SecuBox Plymouth Theme - Raspberry Pi Edition
Window.SetBackgroundTopColor(0.0, 0.0, 0.0);
Window.SetBackgroundBottomColor(0.0, 0.05, 0.0);
screen_width = Window.GetWidth();
screen_height = Window.GetHeight();
center_x = screen_width / 2;
center_y = screen_height / 2;
banner_text = "SECUBOX CYBER DEFENSE SYSTEM";
banner_sprite = Sprite();
banner_image = Image.Text(banner_text, 0.0, 1.0, 0.0, "Fixed");
banner_sprite.SetImage(banner_image);
banner_sprite.SetPosition(center_x - banner_image.GetWidth() / 2, center_y - 100, 1);
version_text = "RASPBERRY PI 400 - SECURE BOOT SEQUENCE";
version_sprite = Sprite();
version_image = Image.Text(version_text, 0.0, 0.8, 0.0, "Fixed");
version_sprite.SetImage(version_image);
version_sprite.SetPosition(center_x - version_image.GetWidth() / 2, center_y - 60, 1);
progress_sprite = Sprite();
fun boot_progress_callback(duration, progress) {
fill_count = Math.Int(progress * 30);
fill_text = "";
for (i = 0; i < fill_count; i++) { fill_text = fill_text + "#"; }
for (i = fill_count; i < 30; i++) { fill_text = fill_text + " "; }
progress_text = "[" + fill_text + "]";
progress_image = Image.Text(progress_text, 0.0, 1.0, 0.0, "Fixed");
progress_sprite.SetImage(progress_image);
progress_sprite.SetPosition(center_x - progress_image.GetWidth() / 2, center_y + 20, 1);
}
Plymouth.SetBootProgressFunction(boot_progress_callback);
message_sprite = Sprite();
fun message_callback(text) {
message_image = Image.Text("> " + text, 0.0, 0.7, 0.0, "Fixed");
message_sprite.SetImage(message_image);
message_sprite.SetPosition(center_x - 200, center_y + 60, 1);
}
Plymouth.SetMessageFunction(message_callback);
PLYSCRIPT
mkdir -p "${ROOTFS}/etc/plymouth"
cat > "${ROOTFS}/etc/plymouth/plymouthd.conf" <<EOF
[Daemon]
Theme=secubox
ShowDelay=0
EOF
chroot "${ROOTFS}" plymouth-set-default-theme secubox 2>/dev/null || true
mkdir -p "${ROOTFS}/etc/initramfs-tools/conf.d"
echo "FRAMEBUFFER=y" > "${ROOTFS}/etc/initramfs-tools/conf.d/plymouth"
ok "Plymouth theme installed"
ok "System configured"
# ══════════════════════════════════════════════════════════════════
@ -318,9 +397,9 @@ EOF
chroot "${ROOTFS}" apt-get update -q
# Install firmware
# Install firmware and Raspberry Pi boot files
chroot "${ROOTFS}" apt-get install -y -q --no-install-recommends \
firmware-brcm80211 firmware-misc-nonfree \
raspi-firmware firmware-brcm80211 firmware-misc-nonfree \
2>/dev/null || warn "Some firmware unavailable"
# SecuBox packages from local cache
@ -383,11 +462,15 @@ enable_uart=1
#arm_freq=2000
EOF
# cmdline.txt
# cmdline.txt (with splash for Plymouth)
cat > "${ROOTFS}/boot/firmware/cmdline.txt" <<EOF
console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait quiet
console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait quiet splash
EOF
# Regenerate initramfs with Plymouth
log "Regenerating initramfs with Plymouth..."
chroot "${ROOTFS}" update-initramfs -u -k all 2>/dev/null || warn "initramfs update issue"
ok "Pi bootloader configured"
# ══════════════════════════════════════════════════════════════════
@ -437,9 +520,9 @@ UUID=${BOOT_UUID} /boot/firmware vfat defaults 0 2
UUID=${ROOT_UUID} / ext4 defaults,noatime 0 1
EOF
# Update cmdline with UUID
# Update cmdline with UUID (splash for Plymouth boot graphics)
cat > "${MNT}/boot/firmware/cmdline.txt" <<EOF
console=serial0,115200 console=tty1 root=UUID=${ROOT_UUID} rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait quiet
console=serial0,115200 console=tty1 root=UUID=${ROOT_UUID} rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait quiet splash
EOF
# Sync and unmount

View File

@ -1,6 +1,6 @@
[Unit]
Description=SecuBox Kiosk Mode (WebUI Display)
After=network-online.target systemd-user-sessions.service
After=systemd-user-sessions.service plymouth-quit-wait.service
Wants=network-online.target
Conflicts=getty@tty1.service
@ -10,40 +10,49 @@ ConditionPathExists=/var/lib/secubox/.kiosk-enabled
[Service]
Type=simple
# Run on tty1
TTYPath=/dev/tty1
StandardInput=tty
StandardOutput=tty
StandardError=journal
# Run on tty7 (like normal display managers)
TTYPath=/dev/tty7
TTYReset=yes
TTYVHangup=yes
TTYVTDisallocate=yes
# Create runtime directory AS ROOT (before switching to User)
ExecStartPre=+/bin/mkdir -p /run/user/1000
ExecStartPre=+/bin/chown 1000:1000 /run/user/1000
ExecStartPre=+/bin/chmod 700 /run/user/1000
# Switch VT to tty7 before starting
ExecStartPre=+/bin/chvt 7
# Now switch to kiosk user
User=secubox-kiosk
Group=secubox-kiosk
# Wayland/DRM access
SupplementaryGroups=video audio input render
SupplementaryGroups=video audio input render tty
# Environment for Wayland
Environment=XDG_RUNTIME_DIR=/run/user/1000
Environment=XDG_SESSION_TYPE=wayland
Environment=KIOSK_URL=https://192.168.255.1:9443/
Environment=WAYLAND_DISPLAY=wayland-0
# wlroots environment (VM compatibility)
Environment=WLR_LIBINPUT_NO_DEVICES=1
Environment=WLR_NO_HARDWARE_CURSORS=1
Environment=WLR_RENDERER_ALLOW_SOFTWARE=1
Environment=WLR_DRM_NO_ATOMIC=1
# Cage compositor running kiosk script (no -d flag, not supported in 0.1.4)
# Cage compositor running kiosk script
ExecStart=/usr/bin/cage -s -- /home/secubox-kiosk/start-kiosk.sh
Restart=on-failure
RestartSec=5
TimeoutStartSec=60
TimeoutStartSec=90
# Resource limits
MemoryMax=1G
TasksMax=200
[Install]
WantedBy=graphical.target