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
+5 -5
View File
@@ -1,6 +1,6 @@
# Stormux Pi Image Builder
Stormux provides an Arch-based Raspberry Pi image that already includes Fenrir, Orca, and other screen-reader friendly defaults. This repository hosts the scripts maintained by the Stormux community (originally crafted by Storm) so anyone can rebuild the image, customize the overlay, and share accessible Pi spins.
Stormux provides an Arch-based Raspberry Pi image that already includes Fenrir, Cthulhu, I38, and other screen-reader friendly defaults. This repository hosts the scripts maintained by the Stormux community (originally crafted by Storm) so anyone can rebuild the image, customize the overlay, and share accessible Pi spins.
## Purpose & Scope
This repo captures the complete build pipeline for the Stormux Raspberry Pi images: bootstrapping Arch Linux ARM, layering the accessible defaults, installing Fenrir and helper tools, and producing a compressed ready-to-flash image. Running the script yourself lets you reproduce the same system the project ships for Raspberry Pi 4/5-class systems, tweak it, and publish new spins without reverse-engineering the original release.
@@ -9,7 +9,7 @@ This repo captures the complete build pipeline for the Stormux Raspberry Pi imag
- A 64-bit Arch Linux host (bare metal or VM) with at least 20GB free disk space.
- Root access on that host. The builder partitions loopback devices and must run as root.
- Required packages: `arch-install-scripts`, `dosfstools`, `parted`, `qemu-user-static`, `qemu-user-static-binfmt`, plus standard developer tools that Arch already ships.
- A Raspberry Pi 4, Raspberry Pi 400, Raspberry Pi 5, or compatible board, plus a microSD card (8 GB or larger recommended).
- A Raspberry Pi 4, Raspberry Pi 400, Raspberry Pi 5, or compatible board, plus a microSD card (16 GB or larger recommended).
Install dependencies on the build host with:
```bash
@@ -31,13 +31,13 @@ The script is designed for an x86_64 Arch host so it can emulate ARM binaries wi
1. Become root (`sudo -i`) so the script can create loop devices and mount them.
2. From `/path/to/stormux`, run:
```bash
sudo ./pi4/build/build-stormux.sh -l en_US -s 6
sudo ./pi4/build/build-stormux.sh -l en_US -s 8
```
- The builder creates an aarch64 image for Raspberry Pi 4/5-class systems.
- `-l en_US` selects the locale (use `es_ES`, `fr_FR`, etc.).
- `-s 6` sets the image size in gigabytes.
- `-s 8` sets the image size in gigabytes.
- Add `-n my-stormux.img` to override the default filename.
3. Grab coffee. The script bootstraps Arch Linux ARM, provisions packages (Fenrir, Orca, NetworkManager, PipeWire, etc.), copies the overlay from `pi4/files/`, installs the latest `sas`, cleans up, compresses the image, and writes a SHA-1 checksum.
3. Grab coffee. The script bootstraps Arch Linux ARM, provisions packages (Fenrir, Cthulhu, I38, XLibre, NetworkManager, PipeWire, etc.), copies the overlay from `pi4/files/`, installs the latest `sas`, cleans up, compresses the image, and writes a SHA-1 checksum.
4. When the `build-stormux.sh` command returns, you should have an `.img.xz` file and matching `.sha1sum` in your working directory (for example `stormux-rpi4-5-aarch64-YYYY-MM-DD.img.xz`).
If the build is interrupted, run `sudo umount -R /mnt && sudo losetup -D` to be sure nothing is still mounted.
+65 -7
View File
@@ -68,6 +68,14 @@ cleanup_image() {
return "$cleanupStatus"
}
# shellcheck disable=SC2329 # validate_image_filesystems is invoked from finish_build
validate_image_filesystems() {
local rootPartition="${loopdev}p2"
echo "Checking root filesystem..."
e2fsck -f -n "$rootPartition"
}
# shellcheck disable=SC2329 # cleanup is invoked via trap EXIT
cleanup() {
local status=$? # capture original exit status so failures propagate
@@ -86,6 +94,19 @@ cleanup() {
}
finish_build() {
if [[ $mounted -eq 0 ]]; then
if ! umount -R /mnt; then
echo "Image build commands completed, but unmounting failed."
exit 1
fi
mounted=1
fi
if ! validate_image_filesystems; then
echo "Image filesystem validation failed."
exit 1
fi
trap - EXIT
if ! cleanup_image; then
@@ -148,7 +169,7 @@ declare -A command=(
[h]="This help screen."
[l:]="Language default is en_US."
[n:]="Image name, default is stormux-rpi4-5-aarch64-<yyyy-mm-dd>.img"
[s:]="image size in GB, default is 6."
[s:]="image size in GB, default is 8."
)
# Convert the keys of the associative array to a format usable by getopts
@@ -178,7 +199,7 @@ while getopts "${args}" i ; do
done
# make sure variables are set, or use defaults.
export imageSize="${imageSize:-6G}"
export imageSize="${imageSize:-8G}"
imageName="${imageName:-stormux-rpi4-5-aarch64-$(date '+%Y-%m-%d').img}"
export imageName
export imageLanguage="${imageLanguage:-en_US.UTF-8}"
@@ -362,6 +383,7 @@ packages=(
gstreamer
gst-plugins-base
gst-plugins-good
i38
ii
# Keep Pi onboard firmware plus common USB/network chipset firmware without
# pulling in unrelated desktop/server GPU firmware.
@@ -373,6 +395,7 @@ packages=(
man
man-pages
networkmanager
nodm-dgw
openssh
parted
pipewire
@@ -382,10 +405,13 @@ packages=(
poppler
python-dbus
python-gobject
python-msgpack
python-pyenchant
python-pyte
python-pyperclip
python-tornado
raspberrypi-utils
cthulhu
socat
realtime-privileges
rhvoice-voice-bdl
@@ -401,6 +427,11 @@ packages=(
vi
xdg-user-dirs
xdg-utils
xlibre-input-libinput
xlibre-video-dummy-with-vt
xlibre-video-fbdev
xlibre-xserver
xorg-xinit
yay
)
@@ -413,7 +444,7 @@ install_sas
# See: https://archlinuxarm.org/forum/viewtopic.php?t=16672
if [[ -f /etc/mkinitcpio.d/linux-rpi.preset ]]; then
echo "Configuring mkinitcpio preset to skip kms hook..."
sed -i "s/^default_options=.*/default_options=\"--skiphook kms\"/" /etc/mkinitcpio.d/linux-rpi.preset
sed -i "s/^default_options=.*/default_options=\"-S autodetect --skiphook kms\"/" /etc/mkinitcpio.d/linux-rpi.preset
sed -i "s/^fallback_options=.*/fallback_options=\"-S autodetect --skiphook kms\"/" /etc/mkinitcpio.d/linux-rpi.preset
fi
@@ -435,8 +466,10 @@ echo 'Stormux \r (\l)' > /etc/issue
echo >> /etc/issue
# Create the stormux user
useradd -m -g users -G wheel,realtime,audio,video,network,brlapi -s /bin/bash stormux
# Grant sudo privileges to the stormux user for package installation.
echo 'stormux ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers.d/wheel
# Grant normal sudo privileges plus temporary first-boot passwordless sudo.
echo '%wheel ALL=(ALL) ALL' > /etc/sudoers.d/wheel
echo 'stormux ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/zz-stormux-first-boot
chmod 440 /etc/sudoers.d/wheel /etc/sudoers.d/zz-stormux-first-boot
# Set the password for the root user
echo -e "root\nroot" | passwd "root"
# Set the password for the stormux user
@@ -449,13 +482,39 @@ git config --global init.defaultBranch master
# Create desktop, downloads, music, and other directories.
xdg-user-dirs-update
exit
mkdir -p /home/stormux/git
chown stormux:users /home/stormux/git
if ! sudo -u stormux git clone --depth 1 https://git.stormux.org/storm/I38 /home/stormux/git/I38; then
echo "Failed to clone I38."
exit 1
fi
cd /home/stormux/git/I38
sudo -u stormux ./i38.sh -D
cd /
if [[ -f /etc/nodm.conf ]]; then
sed -i \
-e "s/{user}/stormux/g" \
-e "s#^NODM_USER=.*#NODM_USER='stormux'#" \
-e "s#^NODM_XSESSION=.*#NODM_XSESSION='/home/stormux/.xinitrc'#" \
/etc/nodm.conf
fi
if command -v spd-conf > /dev/null 2>&1; then
spd-conf -n -C || echo "Warning: Speech Dispatcher system configuration failed."
fi
if [[ -f /etc/speech-dispatcher/speechd.conf ]]; then
if grep -qE '^[[:space:]]*#?[[:space:]]*DefaultModule[[:space:]]+' /etc/speech-dispatcher/speechd.conf; then
sed -i -E 's/^[[:space:]]*#?[[:space:]]*DefaultModule[[:space:]]+.*/DefaultModule rhvoice/' /etc/speech-dispatcher/speechd.conf
else
printf '\nDefaultModule rhvoice\n' >> /etc/speech-dispatcher/speechd.conf
fi
fi
# Enable linger so that hopefully sound will start at login.
mkdir -p /var/lib/systemd/linger
touch /var/lib/systemd/linger/stormux
systemctl --global enable pipewire.service pipewire-pulse.service
/usr/share/fenrirscreenreader/tools/configure_pipewire.sh
sudo -u stormux /usr/share/fenrirscreenreader/tools/configure_pipewire.sh
# Configure sudo for group wheel, remove nopasswd for the stormux user
# Ensure normal sudo remains configured after first-boot cleanup removes NOPASSWD.
echo '%wheel ALL=(ALL) ALL' > /etc/sudoers.d/wheel
# Set the hostname
echo stormux > /etc/hostname
@@ -466,7 +525,6 @@ services=(
brltty.path
cronie.service
fake-hwclock.service
fenrirscreenreader.service
log-to-ram-setup.service
log-to-ram-sync.timer
log-to-ram-shutdown.service