Ship I38 in Pi images by default

Add the I38 desktop stack to the Raspberry Pi image build, including Cthulhu, XLibre, nodm, xorg-xinit, and I38 runtime Python dependencies.

Generate the default stormux user's I38 configuration during image creation with the non-interactive I38 mode, configure nodm for that generated xinitrc, and set Speech Dispatcher to use RHVoice by default.

Harden the Pi build for larger GUI images by raising the default image size to 8 GB, skipping mkinitcpio autodetect for the target image initramfs, and validating the root filesystem before compression.

Rework first boot so volume calibration runs before Fenrir starts, temporary tty1 autologin and passwordless sudo are removed during setup, I38/nodm startup is selected explicitly, and physical versus headless X configuration is written before nodm starts.

Document the Pi GUI rollout in gui.md and refresh README size and desktop references.
This commit is contained in:
Storm Dragon
2026-06-29 15:59:33 -04:00
parent 5ec1d7727d
commit d67ca0f3e0
6 changed files with 237 additions and 34 deletions
+119 -15
View File
@@ -28,6 +28,81 @@ fi
# Volume calibration is now handled in stormux_first_boot.sh
export DIALOGOPTS='--insecure --no-lines --visit-items'
i38StartAtBoot=0
firstBootSudoersFile="/etc/sudoers.d/zz-stormux-first-boot"
firstBootAutologinFile="/etc/systemd/system/getty@tty1.service.d/stormux-first-boot-autologin.conf"
firstBootAutologinDir="${firstBootAutologinFile%/*}"
read_yes_no() {
local answer
while true; do
echo "$1"
echo "Press y for yes or n for no followed by enter."
read -r answer
answer="${answer::1}"
case "${answer,,}" in
y)
return 0
;;
n)
return 1
;;
esac
echo "Please answer y or n."
done
}
cleanup_first_boot_access() {
rm -f "$firstBootSudoersFile" "$firstBootAutologinFile"
rmdir --ignore-fail-on-non-empty "$firstBootAutologinDir"
systemctl daemon-reload
}
configure_physical_screen() {
sudo "${sudoFlags[@]}" mkdir -p /etc/X11/xorg.conf.d
sudo "${sudoFlags[@]}" tee /etc/X11/xorg.conf.d/10-screendriver.conf > /dev/null << 'EOF'
Section "OutputClass"
Identifier "vc4"
MatchDriver "vc4"
Driver "modesetting"
Option "PrimaryGPU" "true"
EndSection
EOF
}
configure_headless_screen() {
sudo "${sudoFlags[@]}" mkdir -p /etc/X11/xorg.conf.d
sudo "${sudoFlags[@]}" tee /etc/X11/xorg.conf.d/10-screendriver.conf > /dev/null << 'EOF'
Section "Monitor"
Identifier "dummy_monitor"
HorizSync 28.0-80.0
VertRefresh 48.0-75.0
Modeline "1920x1080" 172.80 1920 2040 2248 2576 1080 1081 1084 1118
EndSection
Section "Device"
Identifier "dummy_card"
Driver "dummy"
VideoRam 256000
Option "UseFBDev" "false"
EndSection
Section "Screen"
Identifier "dummy_screen"
Device "dummy_card"
Monitor "dummy_monitor"
SubSection "Display"
Depth 24
Modes "1920x1080"
EndSubSection
EndSection
Section "ServerLayout"
Identifier "dummy_layout"
Screen 0 "dummy_screen"
EndSection
EOF
}
set_timezone() {
# Get the list of timezones
@@ -58,26 +133,40 @@ set_timezone() {
timedatectl set-ntp true
}
# Offer to switch fenrir layout.
echo "Would you like to switch Fenrir to laptop layout?"
echo "Press y for yes or n for no followed by enter."
read -r continue
continue="${continue::1}"
if [[ "${continue,}" == "y" ]];then
if read_yes_no "Would you like to switch Fenrir to laptop layout?"; then
sed -i 's/=desktop/=laptop/' /etc/fenrirscreenreader/settings/settings.conf
clear
systemctl restart fenrirscreenreader.service
fi
if command -v i3 > /dev/null 2>&1 && systemctl list-unit-files nodm.service > /dev/null 2>&1; then
if read_yes_no "Will this Pi be connected to a physical screen?"; then
configure_physical_screen
else
configure_headless_screen
fi
if read_yes_no "Should I38 start at boot?"; then
sudo "${sudoFlags[@]}" systemctl --quiet enable nodm.service
i38StartAtBoot=1
if read_yes_no "Should Fenrir run in the console at boot?"; then
sudo "${sudoFlags[@]}" systemctl --quiet enable fenrirscreenreader.service
else
sudo "${sudoFlags[@]}" systemctl --quiet disable fenrirscreenreader.service
fi
else
sudo "${sudoFlags[@]}" systemctl --quiet disable nodm.service
sudo "${sudoFlags[@]}" systemctl --quiet enable fenrirscreenreader.service
fi
fi
# Check for possible resize
diskSource="$(df --output='source' / | tail -1)"
diskSize="$(df -h --output='size' / | tail -1 | tr -cd '[:digit:].')"
diskSize=${diskSize%.*}
if [[ $diskSize -le 7 ]]; then
echo "$diskSource is only $diskSize gigs, which means it probably needs to be resized. Would you like to do this now?"
echo "Press y for yes or n for no followed by enter."
read -r continue
continue="${continue::1}"
if [[ "${continue,}" == "y" ]];then
resizeThreshold=9
if [[ $diskSize -le $resizeThreshold ]]; then
if read_yes_no "$diskSource is only $diskSize gigs, which means it probably needs to be resized. Would you like to do this now?"; then
resizeLog="/tmp/stormux-resize.log"
# Extract base device name (handles mmcblk0p2, nvme0n1p2, sda2, etc.)
if [[ "$diskSource" =~ (.*[0-9]+)p[0-9]+$ ]]; then
# Handle mmcblk0p2, nvme0n1p2 style
@@ -87,14 +176,19 @@ if [[ $diskSize -le 7 ]]; then
# shellcheck disable=SC2001
diskDevice="$(echo "$diskSource" | sed 's/[0-9]*$//')"
fi
echo "Yes" | sudo "${sudoFlags[@]}" parted ---pretend-input-tty "$diskDevice" resizepart 2 100%
sudo "${sudoFlags[@]}" resize2fs -f "$diskSource"
echo "Resizing the filesystem. This may take a few minutes."
if { echo "Yes" | sudo "${sudoFlags[@]}" parted ---pretend-input-tty "$diskDevice" resizepart 2 100%; } > "$resizeLog" 2>&1 \
&& { sudo "${sudoFlags[@]}" resize2fs -f "$diskSource"; } >> "$resizeLog" 2>&1; then
echo "Resize complete."
else
echo "Resize failed. Details are in $resizeLog."
fi
fi
fi
if ! ping -c1 stormux.org &> /dev/null ; then
echo "No internet connection detected. Press enter to open NetworkManager."
read -r continue
read -r _
echo "setting set focus#highlight=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock
nmtui-connect
echo "setting set focus#highlight=False" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock
@@ -112,7 +206,7 @@ if ping -qc1 -W 1 stormux.org &> /dev/null; then
read -rp "Would you like the time to sync as soon as the Pi connects to the internet? " answer
answer="${answer:0:1}"
if [[ "${answer,,}" == "y" ]]; then
systemctl enable time_sync_at_boot.service
systemctl --quiet enable time_sync_at_boot.service
else
echo "Time sync at boot skipped."
echo "If you change your mind later, simply type:"
@@ -141,5 +235,15 @@ echo "or you may configure your system manually."
echo
echo "Thank you for choosing Stormux."
echo
read -rp "Press enter to continue."
cleanup_first_boot_access
if [[ $i38StartAtBoot -eq 1 ]]; then
echo
echo "Starting I38..."
sudo "${sudoFlags[@]}" systemctl --quiet start nodm.service
fi
exit 0