From 3c5490ea24305dcefd9e50eefbf500cf4530dc6f Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Wed, 19 Nov 2025 03:26:25 -0500 Subject: [PATCH] Initial build setup for x86_64 Stormux. --- x86_64/.gitignore | 15 ++ x86_64/50-fenrir-bluez.conf | 24 +++ x86_64/50-fenrir-console.conf | 24 +++ x86_64/50-fenrir-no-suspend.conf | 35 ++++ x86_64/airootfs/etc/audibleprompt.sh | 7 + x86_64/airootfs/etc/environment | 12 ++ x86_64/airootfs/etc/fstab | 6 + x86_64/airootfs/etc/hostname | 1 + x86_64/airootfs/etc/hosts | 5 + .../etc/mkinitcpio.conf.d/archiso.conf | 3 + x86_64/airootfs/etc/mkinitcpio.d/linux.preset | 8 + x86_64/airootfs/etc/motd | 10 ++ x86_64/airootfs/etc/pacman.conf | 104 ++++++++++++ .../pacman.d/hooks/00-stormux-repo-key.hook | 10 ++ .../etc/pacman.d/hooks/stormux-setup.hook | 16 ++ .../etc/profile.d/stormux_first_boot.sh | 63 ++++++++ x86_64/airootfs/etc/skel.custom/.bash_aliases | 4 + .../airootfs/etc/skel.custom/.bash_functions | 21 +++ x86_64/airootfs/etc/skel.custom/.bashrc | 24 +++ x86_64/airootfs/etc/skel.custom/.inputrc | 6 + x86_64/airootfs/etc/skel.custom/.screenrc | 18 +++ x86_64/airootfs/etc/sudoers.d/wheel | 1 + .../etc/sysctl.d/20-quiet-printk.conf | 1 + .../systemd/journald.conf.d/99-ramlog.conf | 3 + .../etc/systemd/resolved.conf.d/dnssec.conf | 2 + .../wait-for-audio.conf | 5 + .../fenrirscreenreader.service | 1 + .../getty@tty1.service.d/autologin.conf | 3 + .../getty@tty2.service.d/autologin.conf | 3 + .../systemd/system/log-to-ram-setup.service | 16 ++ .../system/log-to-ram-shutdown.service | 14 ++ .../systemd/system/log-to-ram-sync.service | 8 + .../etc/systemd/system/log-to-ram-sync.timer | 11 ++ .../NetworkManager.service | 1 + .../multi-user.target.wants/brltty.path | 1 + .../stormux-repo-init.service | 1 + .../system/pipewire-live-setup.service | 13 ++ .../stormux-audio-setup.service | 1 + .../system/stormux-audio-setup.service | 17 ++ .../systemd/system/stormux-repo-init.service | 13 ++ .../etc/systemd/system/stormux-speech.service | 16 ++ .../systemd/system/systemd-firstboot.service | 1 + .../systemd/system/time_sync_at_boot.service | 11 ++ x86_64/airootfs/etc/vconsole.conf | 3 + .../main.lua.d/50-analog-priority.lua | 31 ++++ .../main.lua.d/51-hdmi-deprioritize.lua | 22 +++ x86_64/airootfs/home/stormux/.bash_aliases | 4 + x86_64/airootfs/home/stormux/.bash_functions | 21 +++ x86_64/airootfs/home/stormux/.bashrc | 24 +++ .../50-fenrir-console.conf | 24 +++ .../pipewire-init-sound.service | 1 + .../systemd/user/pipewire-init-sound.service | 12 ++ .../bluetooth.conf.d/50-fenrir-bluez.conf | 24 +++ .../main.conf.d/50-fenrir-no-suspend.conf | 35 ++++ x86_64/airootfs/home/stormux/.inputrc | 6 + x86_64/airootfs/home/stormux/.screenrc | 18 +++ .../airootfs/root/.config/pulse/client.conf | 36 +++++ x86_64/airootfs/root/rename-user.sh | 103 ++++++++++++ x86_64/airootfs/skel | 1 + .../airootfs/usr/local/bin/configure-stormux | 141 ++++++++++++++++ .../usr/local/bin/init-pipewire-sound.sh | 21 +++ x86_64/airootfs/usr/local/bin/livecd-sound | 24 +++ .../usr/local/bin/setup-pipewire-live.sh | 22 +++ .../airootfs/usr/local/bin/stormux-setup.sh | 73 +++++++++ x86_64/airootfs/usr/local/bin/sync_time.sh | 4 + .../kbd/keymaps/i386/qwerty/us_alt.map.gz | Bin 0 -> 677 bytes .../usr/share/stormux/stormux_repo.pub | 29 ++++ x86_64/airootfs/var/lib/alsa/asound.state | 79 +++++++++ x86_64/build.sh | 110 +++++++++++++ x86_64/client.conf | 36 +++++ .../loader/entries/01-stormux-linux.conf | 5 + .../entries/02-stormux-linux-no-speech.conf | 5 + x86_64/efiboot/loader/loader.conf | 3 + x86_64/fix-live-iso-services.sh | 33 ++++ x86_64/grub/grub.cfg | 106 ++++++++++++ x86_64/grub/loopback.cfg | 85 ++++++++++ x86_64/mask-firstboot.sh | 10 ++ x86_64/packages.x86_64 | 151 ++++++++++++++++++ x86_64/pacman.conf | 104 ++++++++++++ x86_64/profiledef.sh | 31 ++++ x86_64/qemu-boot.sh | 87 ++++++++++ x86_64/setup-fenrir-audio-configs.sh | 26 +++ x86_64/stormux_repo.pub | 29 ++++ x86_64/syslinux/archiso_head.cfg | 28 ++++ x86_64/syslinux/archiso_pxe-linux.cfg | 32 ++++ x86_64/syslinux/archiso_pxe.cfg | 5 + x86_64/syslinux/archiso_sys-linux.cfg | 9 ++ x86_64/syslinux/archiso_sys.cfg | 8 + x86_64/syslinux/archiso_tail.cfg | 35 ++++ x86_64/syslinux/splash.png | Bin 0 -> 45400 bytes x86_64/syslinux/syslinux.cfg | 11 ++ 91 files changed, 2266 insertions(+) create mode 100644 x86_64/.gitignore create mode 100644 x86_64/50-fenrir-bluez.conf create mode 100644 x86_64/50-fenrir-console.conf create mode 100644 x86_64/50-fenrir-no-suspend.conf create mode 100755 x86_64/airootfs/etc/audibleprompt.sh create mode 100644 x86_64/airootfs/etc/environment create mode 100644 x86_64/airootfs/etc/fstab create mode 100644 x86_64/airootfs/etc/hostname create mode 100644 x86_64/airootfs/etc/hosts create mode 100644 x86_64/airootfs/etc/mkinitcpio.conf.d/archiso.conf create mode 100644 x86_64/airootfs/etc/mkinitcpio.d/linux.preset create mode 100644 x86_64/airootfs/etc/motd create mode 100644 x86_64/airootfs/etc/pacman.conf create mode 100644 x86_64/airootfs/etc/pacman.d/hooks/00-stormux-repo-key.hook create mode 100644 x86_64/airootfs/etc/pacman.d/hooks/stormux-setup.hook create mode 100755 x86_64/airootfs/etc/profile.d/stormux_first_boot.sh create mode 100644 x86_64/airootfs/etc/skel.custom/.bash_aliases create mode 100644 x86_64/airootfs/etc/skel.custom/.bash_functions create mode 100644 x86_64/airootfs/etc/skel.custom/.bashrc create mode 100644 x86_64/airootfs/etc/skel.custom/.inputrc create mode 100644 x86_64/airootfs/etc/skel.custom/.screenrc create mode 100644 x86_64/airootfs/etc/sudoers.d/wheel create mode 100644 x86_64/airootfs/etc/sysctl.d/20-quiet-printk.conf create mode 100644 x86_64/airootfs/etc/systemd/journald.conf.d/99-ramlog.conf create mode 100644 x86_64/airootfs/etc/systemd/resolved.conf.d/dnssec.conf create mode 100644 x86_64/airootfs/etc/systemd/system/fenrirscreenreader.service.d/wait-for-audio.conf create mode 120000 x86_64/airootfs/etc/systemd/system/getty.target.wants/fenrirscreenreader.service create mode 100644 x86_64/airootfs/etc/systemd/system/getty@tty1.service.d/autologin.conf create mode 100644 x86_64/airootfs/etc/systemd/system/getty@tty2.service.d/autologin.conf create mode 100644 x86_64/airootfs/etc/systemd/system/log-to-ram-setup.service create mode 100644 x86_64/airootfs/etc/systemd/system/log-to-ram-shutdown.service create mode 100644 x86_64/airootfs/etc/systemd/system/log-to-ram-sync.service create mode 100644 x86_64/airootfs/etc/systemd/system/log-to-ram-sync.timer create mode 120000 x86_64/airootfs/etc/systemd/system/multi-user.target.wants/NetworkManager.service create mode 120000 x86_64/airootfs/etc/systemd/system/multi-user.target.wants/brltty.path create mode 120000 x86_64/airootfs/etc/systemd/system/multi-user.target.wants/stormux-repo-init.service create mode 100644 x86_64/airootfs/etc/systemd/system/pipewire-live-setup.service create mode 120000 x86_64/airootfs/etc/systemd/system/sound.target.wants/stormux-audio-setup.service create mode 100644 x86_64/airootfs/etc/systemd/system/stormux-audio-setup.service create mode 100644 x86_64/airootfs/etc/systemd/system/stormux-repo-init.service create mode 100644 x86_64/airootfs/etc/systemd/system/stormux-speech.service create mode 120000 x86_64/airootfs/etc/systemd/system/systemd-firstboot.service create mode 100644 x86_64/airootfs/etc/systemd/system/time_sync_at_boot.service create mode 100644 x86_64/airootfs/etc/vconsole.conf create mode 100644 x86_64/airootfs/etc/wireplumber/main.lua.d/50-analog-priority.lua create mode 100644 x86_64/airootfs/etc/wireplumber/main.lua.d/51-hdmi-deprioritize.lua create mode 100644 x86_64/airootfs/home/stormux/.bash_aliases create mode 100644 x86_64/airootfs/home/stormux/.bash_functions create mode 100644 x86_64/airootfs/home/stormux/.bashrc create mode 100644 x86_64/airootfs/home/stormux/.config/pipewire/pipewire-pulse.conf.d/50-fenrir-console.conf create mode 120000 x86_64/airootfs/home/stormux/.config/systemd/user/default.target.wants/pipewire-init-sound.service create mode 100644 x86_64/airootfs/home/stormux/.config/systemd/user/pipewire-init-sound.service create mode 100644 x86_64/airootfs/home/stormux/.config/wireplumber/bluetooth.conf.d/50-fenrir-bluez.conf create mode 100644 x86_64/airootfs/home/stormux/.config/wireplumber/main.conf.d/50-fenrir-no-suspend.conf create mode 100644 x86_64/airootfs/home/stormux/.inputrc create mode 100644 x86_64/airootfs/home/stormux/.screenrc create mode 100644 x86_64/airootfs/root/.config/pulse/client.conf create mode 100755 x86_64/airootfs/root/rename-user.sh create mode 120000 x86_64/airootfs/skel create mode 100755 x86_64/airootfs/usr/local/bin/configure-stormux create mode 100755 x86_64/airootfs/usr/local/bin/init-pipewire-sound.sh create mode 100755 x86_64/airootfs/usr/local/bin/livecd-sound create mode 100755 x86_64/airootfs/usr/local/bin/setup-pipewire-live.sh create mode 100755 x86_64/airootfs/usr/local/bin/stormux-setup.sh create mode 100755 x86_64/airootfs/usr/local/bin/sync_time.sh create mode 100644 x86_64/airootfs/usr/share/kbd/keymaps/i386/qwerty/us_alt.map.gz create mode 100644 x86_64/airootfs/usr/share/stormux/stormux_repo.pub create mode 100644 x86_64/airootfs/var/lib/alsa/asound.state create mode 100755 x86_64/build.sh create mode 100644 x86_64/client.conf create mode 100644 x86_64/efiboot/loader/entries/01-stormux-linux.conf create mode 100644 x86_64/efiboot/loader/entries/02-stormux-linux-no-speech.conf create mode 100644 x86_64/efiboot/loader/loader.conf create mode 100755 x86_64/fix-live-iso-services.sh create mode 100644 x86_64/grub/grub.cfg create mode 100644 x86_64/grub/loopback.cfg create mode 100644 x86_64/mask-firstboot.sh create mode 100644 x86_64/packages.x86_64 create mode 100644 x86_64/pacman.conf create mode 100644 x86_64/profiledef.sh create mode 100755 x86_64/qemu-boot.sh create mode 100755 x86_64/setup-fenrir-audio-configs.sh create mode 100644 x86_64/stormux_repo.pub create mode 100644 x86_64/syslinux/archiso_head.cfg create mode 100644 x86_64/syslinux/archiso_pxe-linux.cfg create mode 100644 x86_64/syslinux/archiso_pxe.cfg create mode 100644 x86_64/syslinux/archiso_sys-linux.cfg create mode 100644 x86_64/syslinux/archiso_sys.cfg create mode 100644 x86_64/syslinux/archiso_tail.cfg create mode 100644 x86_64/syslinux/splash.png create mode 100644 x86_64/syslinux/syslinux.cfg diff --git a/x86_64/.gitignore b/x86_64/.gitignore new file mode 100644 index 0000000..bd03f34 --- /dev/null +++ b/x86_64/.gitignore @@ -0,0 +1,15 @@ +# x86_64 ISO build artifacts +out/ +work/ +logs/ + +# Build outputs +*.iso +*.iso.sig +*.sha256 + +# Documentation (not tracked) +*.md + +# User-specific +.claude/ diff --git a/x86_64/50-fenrir-bluez.conf b/x86_64/50-fenrir-bluez.conf new file mode 100644 index 0000000..c5ffd39 --- /dev/null +++ b/x86_64/50-fenrir-bluez.conf @@ -0,0 +1,24 @@ +# Fenrir console audio support +# Prevents bluetooth disconnection when switching TTY + +monitor.bluez.properties = { + # Disable logind integration to prevent bluetooth device suspension on TTY switch + bluez5.enable-sbc-xq = true + bluez5.enable-msbc = true + bluez5.enable-hw-volume = true +} + +monitor.bluez.rules = [ + { + matches = [ + { + device.name = "~bluez_card.*" + } + ] + actions = { + update-props = { + session.suspend-timeout-seconds = 0 + } + } + } +] diff --git a/x86_64/50-fenrir-console.conf b/x86_64/50-fenrir-console.conf new file mode 100644 index 0000000..e5f2a1d --- /dev/null +++ b/x86_64/50-fenrir-console.conf @@ -0,0 +1,24 @@ +# Fenrir console audio support +# Adds secondary socket for console applications like Fenrir + +pulse.properties = { + # the addresses this server listens on + server.address = [ + "unix:native" + "unix:/tmp/pulse.sock" # console access socket + ] +} + +# client/stream specific properties +pulse.rules = [ + { + # speech dispatcher asks for too small latency and then underruns. + matches = [ { application.name = "~speech-dispatcher*" } ] + actions = { + update-props = { + pulse.min.req = 1024/48000 # 21ms + pulse.min.quantum = 1024/48000 # 21ms + } + } + } +] diff --git a/x86_64/50-fenrir-no-suspend.conf b/x86_64/50-fenrir-no-suspend.conf new file mode 100644 index 0000000..4217470 --- /dev/null +++ b/x86_64/50-fenrir-no-suspend.conf @@ -0,0 +1,35 @@ +# Fenrir console audio support +# Prevents audio device suspension when switching to TTY console + +monitor.alsa.rules = [ + { + matches = [ + { + device.name = "~alsa_card.*" + } + ] + actions = { + update-props = { + api.alsa.use-acp = true + api.acp.auto-profile = false + api.acp.auto-port = false + session.suspend-timeout-seconds = 0 + } + } + } + { + matches = [ + { + node.name = "~alsa_input.*" + } + { + node.name = "~alsa_output.*" + } + ] + actions = { + update-props = { + session.suspend-timeout-seconds = 0 + } + } + } +] diff --git a/x86_64/airootfs/etc/audibleprompt.sh b/x86_64/airootfs/etc/audibleprompt.sh new file mode 100755 index 0000000..5faa9e6 --- /dev/null +++ b/x86_64/airootfs/etc/audibleprompt.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +play -qnV0 synth 3 pluck D3 pluck A3 pluck D4 pluck F4 pluck A4 delay 0 .1 .2 .3 .4 remix - chorus 0.9 0.9 38 0.75 0.3 0.5 -t & +read -rsp "$*"$'\n' password +echo "$password" + +exit 0 diff --git a/x86_64/airootfs/etc/environment b/x86_64/airootfs/etc/environment new file mode 100644 index 0000000..f387890 --- /dev/null +++ b/x86_64/airootfs/etc/environment @@ -0,0 +1,12 @@ +# +# This file is parsed by pam_env module +# +# Syntax: simple "KEY=VAL" pairs on separate lines +# +# Accessibility variables +export ACCESSIBILITY_ENABLED=1 +export GTK_MODULES=gail:atk-bridge +export GNOME_ACCESSIBILITY=1 +export QT_ACCESSIBILITY=1 +export QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1 +export SAL_USE_VCLPLUGIN=gtk3 diff --git a/x86_64/airootfs/etc/fstab b/x86_64/airootfs/etc/fstab new file mode 100644 index 0000000..49fc164 --- /dev/null +++ b/x86_64/airootfs/etc/fstab @@ -0,0 +1,6 @@ +# Static information about the filesystems. +# See fstab(5) for details. + +# +# Live ISO - root filesystem is handled by archiso, only tmpfs mounts needed +tmpfs /var/log tmpfs defaults,noatime,nosuid,nodev,noexec,mode=0755,size=128M 0 0 diff --git a/x86_64/airootfs/etc/hostname b/x86_64/airootfs/etc/hostname new file mode 100644 index 0000000..5c8e341 --- /dev/null +++ b/x86_64/airootfs/etc/hostname @@ -0,0 +1 @@ +stormux diff --git a/x86_64/airootfs/etc/hosts b/x86_64/airootfs/etc/hosts new file mode 100644 index 0000000..d6f492f --- /dev/null +++ b/x86_64/airootfs/etc/hosts @@ -0,0 +1,5 @@ +# Static table lookup for hostnames. +# See hosts(5) for details. +127.0.0.1 localhost +::1 localhost +127.0.1.1 stormux.localdomain stormux diff --git a/x86_64/airootfs/etc/mkinitcpio.conf.d/archiso.conf b/x86_64/airootfs/etc/mkinitcpio.conf.d/archiso.conf new file mode 100644 index 0000000..5c008e5 --- /dev/null +++ b/x86_64/airootfs/etc/mkinitcpio.conf.d/archiso.conf @@ -0,0 +1,3 @@ +HOOKS=(base udev microcode modconf kms memdisk archiso archiso_loop_mnt archiso_pxe_common archiso_pxe_nbd archiso_pxe_http archiso_pxe_nfs block filesystems keyboard) +COMPRESSION="xz" +COMPRESSION_OPTIONS=(-9e) diff --git a/x86_64/airootfs/etc/mkinitcpio.d/linux.preset b/x86_64/airootfs/etc/mkinitcpio.d/linux.preset new file mode 100644 index 0000000..8e85205 --- /dev/null +++ b/x86_64/airootfs/etc/mkinitcpio.d/linux.preset @@ -0,0 +1,8 @@ +# mkinitcpio preset file for the 'linux' package on archiso + +PRESETS=('archiso') + +ALL_kver='/boot/vmlinuz-linux' +archiso_config='/etc/mkinitcpio.conf.d/archiso.conf' + +archiso_image="/boot/initramfs-linux.img" diff --git a/x86_64/airootfs/etc/motd b/x86_64/airootfs/etc/motd new file mode 100644 index 0000000..7e5f0c2 --- /dev/null +++ b/x86_64/airootfs/etc/motd @@ -0,0 +1,10 @@ +Welcome to Stormux, powered by Arch Linux ARM + + Stormux Website: https://stormux.org + Arch Linux ARM Forum: https://archlinuxarm.org/forum + + Stormux IRC: #stormux on irc.stormux.org + Arch Linux ARM IRC: #archlinuxarm on irc.libera.chat + + Thank you Stormux supporters! https://ko-fi.com/stormux/leaderboard + diff --git a/x86_64/airootfs/etc/pacman.conf b/x86_64/airootfs/etc/pacman.conf new file mode 100644 index 0000000..00fe68e --- /dev/null +++ b/x86_64/airootfs/etc/pacman.conf @@ -0,0 +1,104 @@ +# +# /etc/pacman.conf +# +# See the pacman.conf(5) manpage for option and repository directives + +# +# GENERAL OPTIONS +# +[options] +# The following paths are commented out with their default values listed. +# If you wish to use different paths, uncomment and update the paths. +#RootDir = / +#DBPath = /var/lib/pacman/ +#CacheDir = /var/cache/pacman/pkg/ +#LogFile = /var/log/pacman.log +#GPGDir = /etc/pacman.d/gnupg/ +#HookDir = /etc/pacman.d/hooks/ +HoldPkg = pacman glibc +#XferCommand = /usr/bin/curl -L -C - -f -o %o %u +#XferCommand = /usr/bin/wget --passive-ftp -c -O %o %u +#CleanMethod = KeepInstalled +Architecture = auto + +# Pacman won't upgrade packages listed in IgnorePkg and members of IgnoreGroup +#IgnorePkg = +#IgnoreGroup = + +#NoUpgrade = +#NoExtract = + +# Misc options +#UseSyslog +#Color +#NoProgressBar +# We cannot check disk space from within a chroot environment +#CheckSpace +#VerbosePkgLists +ParallelDownloads = 5 +#DownloadUser = alpm +#DisableSandbox + +# By default, pacman accepts packages signed by keys that its local keyring +# trusts (see pacman-key and its man page), as well as unsigned packages. +SigLevel = Required DatabaseOptional +LocalFileSigLevel = Optional +#RemoteFileSigLevel = Required + +# NOTE: You must run `pacman-key --init` before first using pacman; the local +# keyring can then be populated with the keys of all official Arch Linux +# packagers with `pacman-key --populate archlinux`. + +# +# REPOSITORIES +# - can be defined here or included from another file +# - pacman will search repositories in the order defined here +# - local/custom mirrors can be added here or in separate files +# - repositories listed first will take precedence when packages +# have identical names, regardless of version number +# - URLs will have $repo replaced by the name of the current repo +# - URLs will have $arch replaced by the name of the architecture +# +# Repository entries are of the format: +# [repo-name] +# Server = ServerName +# Include = IncludePath +# +# The header [repo-name] is crucial - it must be present and +# uncommented to enable the repo. +# + +# The testing repositories are disabled by default. To enable, uncomment the +# repo name header and Include lines. You can add preferred servers immediately +# after the header, and they will be used before the default mirrors. + +#[core-testing] +#Include = /etc/pacman.d/mirrorlist + +[stormux] +SigLevel = Required DatabaseOptional +Server = https://packages.stormux.org/$arch + +[core] +Include = /etc/pacman.d/mirrorlist + +#[extra-testing] +#Include = /etc/pacman.d/mirrorlist + +[extra] +Include = /etc/pacman.d/mirrorlist + +# If you want to run 32 bit applications on your x86_64 system, +# enable the multilib repositories as required here. + +#[multilib-testing] +#Include = /etc/pacman.d/mirrorlist + +#[multilib] +#Include = /etc/pacman.d/mirrorlist + +# An example of a custom package repository. See the pacman manpage for +# tips on creating your own repositories. +#[custom] +#SigLevel = Optional TrustAll +#Server = file:///home/custompkgs diff --git a/x86_64/airootfs/etc/pacman.d/hooks/00-stormux-repo-key.hook b/x86_64/airootfs/etc/pacman.d/hooks/00-stormux-repo-key.hook new file mode 100644 index 0000000..c3f05e2 --- /dev/null +++ b/x86_64/airootfs/etc/pacman.d/hooks/00-stormux-repo-key.hook @@ -0,0 +1,10 @@ +[Trigger] +Operation = Install +Operation = Upgrade +Type = Package +Target = * + +[Action] +Description = Adding Stormux repository key... +When = PreTransaction +Exec = /bin/sh -c 'if [ ! -f /etc/pacman.d/.stormux-key-added ]; then pacman-key --add /usr/share/stormux/stormux_repo.pub && pacman-key --lsign-key 52ADA49000F1FF0456F8AEEFB4CDE1CD56EF8E82 && touch /etc/pacman.d/.stormux-key-added; fi' diff --git a/x86_64/airootfs/etc/pacman.d/hooks/stormux-setup.hook b/x86_64/airootfs/etc/pacman.d/hooks/stormux-setup.hook new file mode 100644 index 0000000..d90c9f3 --- /dev/null +++ b/x86_64/airootfs/etc/pacman.d/hooks/stormux-setup.hook @@ -0,0 +1,16 @@ +# remove from airootfs! +# Stormux system setup hook - creates user, configures services + +[Trigger] +Operation = Install +Operation = Upgrade +Type = Package +Target = filesystem + +[Action] +Description = Setting up Stormux user and system configuration... +When = PostTransaction +Depends = shadow +Depends = systemd +Depends = sudo +Exec = /usr/local/bin/stormux-setup.sh diff --git a/x86_64/airootfs/etc/profile.d/stormux_first_boot.sh b/x86_64/airootfs/etc/profile.d/stormux_first_boot.sh new file mode 100755 index 0000000..843df65 --- /dev/null +++ b/x86_64/airootfs/etc/profile.d/stormux_first_boot.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +if [[ "$(tty)" != "/dev/tty1" ]]; then + return +fi + +if [[ -x /opt/configure-stormux/configure-stormux.sh ]]; then + return +fi + +if ! [[ -x /usr/local/bin/configure-stormux ]]; then + return +fi + +# Volume calibration FIRST +echo "Setting up audio volume..." +volume=50 +wait=0 + +# Wait for pipewire to become available +while [[ $wait -lt 30 ]]; do + if pgrep pipewire &> /dev/null ; then + break + else + sleep 1 + ((wait++)) + fi +done + +# Volume calibration - this should be the VERY FIRST interactive part +while [[ $volume -le 130 ]]; do + clear + pactl set-sink-volume @DEFAULT_SINK@ "${volume}%" 2>/dev/null + spd-say "If this is loud enough, press enter." + if read -t4 ; then + echo "Volume set to ${volume}%" + break + else + ((volume+=5)) + fi +done + +# For audible sudo prompts: +unset sudoFlags +if [[ -x /etc/audibleprompt.sh ]]; then + export SUDO_ASKPASS=/etc/audibleprompt.sh + export sudoFlags=("-A") +fi + +clear +cat << "EOF" +Hello, and welcome to Stormux! + +Let's get you set up. After you press enter, you will be prompted for the sudo password. +When that happens, type the word stormux and press enter. +You will not receive any speech feedback for this process. +That is completely normal, and speech will return after you have typed the password. +Once again, the password is stormux in all lower case letters. + +Please press enter to continue. +EOF +read -r +sudo "${sudoFlags[@]}" configure-stormux diff --git a/x86_64/airootfs/etc/skel.custom/.bash_aliases b/x86_64/airootfs/etc/skel.custom/.bash_aliases new file mode 100644 index 0000000..10ba8f8 --- /dev/null +++ b/x86_64/airootfs/etc/skel.custom/.bash_aliases @@ -0,0 +1,4 @@ +# Raspberry Pi Information +alias pi-temp='/usr/bin/vcgencmd measure_temp' +alias pi-version='cat /sys/firmware/devicetree/base/model;echo' +alias pi-ip='ip a | grep -v "127.0.0.1" | grep -E -o "([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}"' diff --git a/x86_64/airootfs/etc/skel.custom/.bash_functions b/x86_64/airootfs/etc/skel.custom/.bash_functions new file mode 100644 index 0000000..c4d13a6 --- /dev/null +++ b/x86_64/airootfs/etc/skel.custom/.bash_functions @@ -0,0 +1,21 @@ +memuse() { + ps axo rss,comm,pid \ + | awk '{ proc_list[$2] += $1; } END \ + { for (proc in proc_list) { printf("%d\t%s\n", proc_list[proc],proc); }}' \ + | sort -n | tail -n 10 | sort -rn \ + | awk '{$1/=1024;printf "%.0fMB\t",$1}{print $2}' +} + + +pdf() +{ + if [[ $# -ne 1 ]]; then + echo 'Usage: pdf ' >&2 + else + local dir=$(mktemp -d -p /tmp pdf_conversion.XXXXXX) + local outFile="${1##*/}" + local outFile="${outFile%.*}" + pdftohtml -noframes -i -s "$1" "${dir}/${outFile}.html" + w3m -s "${dir}/${outFile}.html" + fi +} diff --git a/x86_64/airootfs/etc/skel.custom/.bashrc b/x86_64/airootfs/etc/skel.custom/.bashrc new file mode 100644 index 0000000..65f57bd --- /dev/null +++ b/x86_64/airootfs/etc/skel.custom/.bashrc @@ -0,0 +1,24 @@ +# +# ~/.bashrc +# + +# If not running interactively, don't do anything +[[ $- != *i* ]] && return + +#Change directories without using cd +shopt -s autocd + +# Keep bash history in screen +export HISTFILE="${HISTFILE}${WINDOW:+.${WINDOW}}" + +# load Aliases and functions +[[ -f ".bash_aliases" ]] && source .bash_aliases +[[ -f ".bash_functions" ]] && source .bash_functions +#Invironment variables +PS1='[\u@\h \W] \$ ' +export DIALOGOPTS='--no-lines --visit-items' +GPG_TTY=$(tty) +export GPG_TTY +# Don't put commands prefixed with space, or duplicate commands in history +export HISTCONTROL=ignoreboth + diff --git a/x86_64/airootfs/etc/skel.custom/.inputrc b/x86_64/airootfs/etc/skel.custom/.inputrc new file mode 100644 index 0000000..0dcf6ea --- /dev/null +++ b/x86_64/airootfs/etc/skel.custom/.inputrc @@ -0,0 +1,6 @@ +# Reload changes with control+x followed by control+r +set echo-control-characters off + +# History searching with up and down arrows. +"\e[A": history-search-backward +"\e[B": history-search-forward diff --git a/x86_64/airootfs/etc/skel.custom/.screenrc b/x86_64/airootfs/etc/skel.custom/.screenrc new file mode 100644 index 0000000..bd20ce3 --- /dev/null +++ b/x86_64/airootfs/etc/skel.custom/.screenrc @@ -0,0 +1,18 @@ +vbell off +bell_msg "" +hardstatus off +startup_message off +defscrollback 4096 +bind ! select 10 +bind @ select 11 +bind \# select 12 +bind $ select 13 +bind % select 14 +bind ^ select 15 +bind & select 16 +bind * select 17 +bind ( select 18 +bind ) select 19 +bind b eval "writebuf" 'exec !!! xclip -selection "clipboard" -i /tmp/screen-exchange' +#termcapinfo xterm|xterms|xs|rxvt ti@:te@ +termcapinfo xterm* ti@:te@ diff --git a/x86_64/airootfs/etc/sudoers.d/wheel b/x86_64/airootfs/etc/sudoers.d/wheel new file mode 100644 index 0000000..40695bb --- /dev/null +++ b/x86_64/airootfs/etc/sudoers.d/wheel @@ -0,0 +1 @@ +%wheel ALL=(ALL:ALL) ALL diff --git a/x86_64/airootfs/etc/sysctl.d/20-quiet-printk.conf b/x86_64/airootfs/etc/sysctl.d/20-quiet-printk.conf new file mode 100644 index 0000000..47146d2 --- /dev/null +++ b/x86_64/airootfs/etc/sysctl.d/20-quiet-printk.conf @@ -0,0 +1 @@ +kernel.printk = 3 3 3 3 diff --git a/x86_64/airootfs/etc/systemd/journald.conf.d/99-ramlog.conf b/x86_64/airootfs/etc/systemd/journald.conf.d/99-ramlog.conf new file mode 100644 index 0000000..d89ad6b --- /dev/null +++ b/x86_64/airootfs/etc/systemd/journald.conf.d/99-ramlog.conf @@ -0,0 +1,3 @@ +[Journal] +Storage=volatile +SystemMaxUse=20M diff --git a/x86_64/airootfs/etc/systemd/resolved.conf.d/dnssec.conf b/x86_64/airootfs/etc/systemd/resolved.conf.d/dnssec.conf new file mode 100644 index 0000000..d43d54a --- /dev/null +++ b/x86_64/airootfs/etc/systemd/resolved.conf.d/dnssec.conf @@ -0,0 +1,2 @@ +[Resolve] +DNSSEC=no diff --git a/x86_64/airootfs/etc/systemd/system/fenrirscreenreader.service.d/wait-for-audio.conf b/x86_64/airootfs/etc/systemd/system/fenrirscreenreader.service.d/wait-for-audio.conf new file mode 100644 index 0000000..9deed1c --- /dev/null +++ b/x86_64/airootfs/etc/systemd/system/fenrirscreenreader.service.d/wait-for-audio.conf @@ -0,0 +1,5 @@ +[Unit] +# Ensure audio and speech-dispatcher are available before starting Fenrir +# Wait for user audio initialization to complete +After=sound.target stormux-audio-setup.service speech-dispatcherd.service user@1000.service +Wants=stormux-audio-setup.service speech-dispatcherd.service user@1000.service diff --git a/x86_64/airootfs/etc/systemd/system/getty.target.wants/fenrirscreenreader.service b/x86_64/airootfs/etc/systemd/system/getty.target.wants/fenrirscreenreader.service new file mode 120000 index 0000000..6a2b2fe --- /dev/null +++ b/x86_64/airootfs/etc/systemd/system/getty.target.wants/fenrirscreenreader.service @@ -0,0 +1 @@ +/usr/lib/systemd/system/fenrirscreenreader.service \ No newline at end of file diff --git a/x86_64/airootfs/etc/systemd/system/getty@tty1.service.d/autologin.conf b/x86_64/airootfs/etc/systemd/system/getty@tty1.service.d/autologin.conf new file mode 100644 index 0000000..b9d22eb --- /dev/null +++ b/x86_64/airootfs/etc/systemd/system/getty@tty1.service.d/autologin.conf @@ -0,0 +1,3 @@ +[Service] +ExecStart= +ExecStart=-/sbin/agetty -o '-p -f -- \\u' --noclear --autologin root - $TERM diff --git a/x86_64/airootfs/etc/systemd/system/getty@tty2.service.d/autologin.conf b/x86_64/airootfs/etc/systemd/system/getty@tty2.service.d/autologin.conf new file mode 100644 index 0000000..00ba65a --- /dev/null +++ b/x86_64/airootfs/etc/systemd/system/getty@tty2.service.d/autologin.conf @@ -0,0 +1,3 @@ +[Service] +ExecStart= +ExecStart=-/sbin/agetty -o '-p -f -- \\u' --noclear --autologin stormux - $TERM diff --git a/x86_64/airootfs/etc/systemd/system/log-to-ram-setup.service b/x86_64/airootfs/etc/systemd/system/log-to-ram-setup.service new file mode 100644 index 0000000..d647d6f --- /dev/null +++ b/x86_64/airootfs/etc/systemd/system/log-to-ram-setup.service @@ -0,0 +1,16 @@ +[Unit] +Description=Setup RAM logging and restore logs from disk +DefaultDependencies=false +After=local-fs.target +Before=sysinit.target +RequiresMountsFor=/var + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStartPre=/bin/mkdir -p /var/log.hdd +ExecStartPre=/bin/sh -c 'if [ -d /var/log.hdd ] && [ "$(ls -A /var/log.hdd 2>/dev/null)" ]; then cp -au /var/log.hdd/* /var/log/ 2>/dev/null || true; fi' +ExecStart=/bin/true + +[Install] +WantedBy=sysinit.target \ No newline at end of file diff --git a/x86_64/airootfs/etc/systemd/system/log-to-ram-shutdown.service b/x86_64/airootfs/etc/systemd/system/log-to-ram-shutdown.service new file mode 100644 index 0000000..7625859 --- /dev/null +++ b/x86_64/airootfs/etc/systemd/system/log-to-ram-shutdown.service @@ -0,0 +1,14 @@ +[Unit] +Description=Sync logs to disk before shutdown +DefaultDependencies=false +Before=shutdown.target reboot.target halt.target +Conflicts=shutdown.target reboot.target halt.target + +[Service] +Type=oneshot +RemainAfterExit=true +ExecStart=/bin/sh -c 'rsync -aXv --delete --exclude="*.hdd" /var/log/ /var/log.hdd/ 2>/dev/null || true' +TimeoutStopSec=30 + +[Install] +WantedBy=shutdown.target reboot.target halt.target \ No newline at end of file diff --git a/x86_64/airootfs/etc/systemd/system/log-to-ram-sync.service b/x86_64/airootfs/etc/systemd/system/log-to-ram-sync.service new file mode 100644 index 0000000..0822563 --- /dev/null +++ b/x86_64/airootfs/etc/systemd/system/log-to-ram-sync.service @@ -0,0 +1,8 @@ +[Unit] +Description=Sync logs from RAM to disk +After=local-fs.target + +[Service] +Type=oneshot +ExecStartPre=/bin/mkdir -p /var/log.hdd +ExecStart=/bin/sh -c 'rsync -aXv --delete --exclude="*.hdd" /var/log/ /var/log.hdd/ 2>/dev/null || true' \ No newline at end of file diff --git a/x86_64/airootfs/etc/systemd/system/log-to-ram-sync.timer b/x86_64/airootfs/etc/systemd/system/log-to-ram-sync.timer new file mode 100644 index 0000000..56dad52 --- /dev/null +++ b/x86_64/airootfs/etc/systemd/system/log-to-ram-sync.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Sync logs from RAM to disk every hour +Requires=log-to-ram-sync.service + +[Timer] +OnBootSec=15min +OnUnitActiveSec=1h +Persistent=true + +[Install] +WantedBy=timers.target \ No newline at end of file diff --git a/x86_64/airootfs/etc/systemd/system/multi-user.target.wants/NetworkManager.service b/x86_64/airootfs/etc/systemd/system/multi-user.target.wants/NetworkManager.service new file mode 120000 index 0000000..e874a9b --- /dev/null +++ b/x86_64/airootfs/etc/systemd/system/multi-user.target.wants/NetworkManager.service @@ -0,0 +1 @@ +/usr/lib/systemd/system/NetworkManager.service \ No newline at end of file diff --git a/x86_64/airootfs/etc/systemd/system/multi-user.target.wants/brltty.path b/x86_64/airootfs/etc/systemd/system/multi-user.target.wants/brltty.path new file mode 120000 index 0000000..e82d99f --- /dev/null +++ b/x86_64/airootfs/etc/systemd/system/multi-user.target.wants/brltty.path @@ -0,0 +1 @@ +/usr/lib/systemd/system/brltty.path \ No newline at end of file diff --git a/x86_64/airootfs/etc/systemd/system/multi-user.target.wants/stormux-repo-init.service b/x86_64/airootfs/etc/systemd/system/multi-user.target.wants/stormux-repo-init.service new file mode 120000 index 0000000..b57059f --- /dev/null +++ b/x86_64/airootfs/etc/systemd/system/multi-user.target.wants/stormux-repo-init.service @@ -0,0 +1 @@ +../stormux-repo-init.service \ No newline at end of file diff --git a/x86_64/airootfs/etc/systemd/system/pipewire-live-setup.service b/x86_64/airootfs/etc/systemd/system/pipewire-live-setup.service new file mode 100644 index 0000000..a422bc8 --- /dev/null +++ b/x86_64/airootfs/etc/systemd/system/pipewire-live-setup.service @@ -0,0 +1,13 @@ +[Unit] +Description=Setup Pipewire for Live Environment +After=sound.target +Before=fenrirscreenreader.service speech-dispatcherd.service +DefaultDependencies=no + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/local/bin/setup-pipewire-live.sh + +[Install] +WantedBy=multi-user.target diff --git a/x86_64/airootfs/etc/systemd/system/sound.target.wants/stormux-audio-setup.service b/x86_64/airootfs/etc/systemd/system/sound.target.wants/stormux-audio-setup.service new file mode 120000 index 0000000..1fb2f20 --- /dev/null +++ b/x86_64/airootfs/etc/systemd/system/sound.target.wants/stormux-audio-setup.service @@ -0,0 +1 @@ +../stormux-audio-setup.service \ No newline at end of file diff --git a/x86_64/airootfs/etc/systemd/system/stormux-audio-setup.service b/x86_64/airootfs/etc/systemd/system/stormux-audio-setup.service new file mode 100644 index 0000000..2c2fa9e --- /dev/null +++ b/x86_64/airootfs/etc/systemd/system/stormux-audio-setup.service @@ -0,0 +1,17 @@ +[Unit] +Description=Setup audio for Stormux live environment +# Wait for audio hardware to be ready +Wants=systemd-udev-settle.service +After=systemd-udev-settle.service sound.target + +[Service] +Type=oneshot +RemainAfterExit=yes +# Enable linger so pipewire can run without login +ExecStartPre=/bin/mkdir -p /var/lib/systemd/linger +ExecStartPre=/bin/touch /var/lib/systemd/linger/stormux +# Unmute and set volume +ExecStart=/usr/local/bin/livecd-sound + +[Install] +WantedBy=sound.target diff --git a/x86_64/airootfs/etc/systemd/system/stormux-repo-init.service b/x86_64/airootfs/etc/systemd/system/stormux-repo-init.service new file mode 100644 index 0000000..70bf01f --- /dev/null +++ b/x86_64/airootfs/etc/systemd/system/stormux-repo-init.service @@ -0,0 +1,13 @@ +[Unit] +Description=Initialize Stormux repository key +After=pacman-init.service +Requires=pacman-init.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/bin/pacman-key --add /usr/share/stormux/stormux_repo.pub +ExecStart=/usr/bin/pacman-key --lsign-key 52ADA49000F1FF0456F8AEEFB4CDE1CD56EF8E82 + +[Install] +WantedBy=multi-user.target diff --git a/x86_64/airootfs/etc/systemd/system/stormux-speech.service b/x86_64/airootfs/etc/systemd/system/stormux-speech.service new file mode 100644 index 0000000..710d13a --- /dev/null +++ b/x86_64/airootfs/etc/systemd/system/stormux-speech.service @@ -0,0 +1,16 @@ +[Unit] +Description=Stormux screen reader and speech service +After=stormux-audio-setup.service sound.target +Wants=stormux-audio-setup.service +Before=getty@tty1.service + +[Service] +Type=oneshot +RemainAfterExit=yes +# Give pipewire a moment to initialize +ExecStartPre=/bin/sleep 2 +# Start Fenrir screen reader +ExecStart=/bin/systemctl start fenrirscreenreader.service + +[Install] +WantedBy=multi-user.target diff --git a/x86_64/airootfs/etc/systemd/system/systemd-firstboot.service b/x86_64/airootfs/etc/systemd/system/systemd-firstboot.service new file mode 120000 index 0000000..dc1dc0c --- /dev/null +++ b/x86_64/airootfs/etc/systemd/system/systemd-firstboot.service @@ -0,0 +1 @@ +/dev/null \ No newline at end of file diff --git a/x86_64/airootfs/etc/systemd/system/time_sync_at_boot.service b/x86_64/airootfs/etc/systemd/system/time_sync_at_boot.service new file mode 100644 index 0000000..c49ba4c --- /dev/null +++ b/x86_64/airootfs/etc/systemd/system/time_sync_at_boot.service @@ -0,0 +1,11 @@ +[Unit] +Description=Update system time from worldtimeapi.org +After=network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/sync_time.sh + +[Install] +WantedBy=multi-user.target diff --git a/x86_64/airootfs/etc/vconsole.conf b/x86_64/airootfs/etc/vconsole.conf new file mode 100644 index 0000000..aaa62c0 --- /dev/null +++ b/x86_64/airootfs/etc/vconsole.conf @@ -0,0 +1,3 @@ +# This is the fallback vconsole configuration provided by systemd. + +KEYMAP=us_alt diff --git a/x86_64/airootfs/etc/wireplumber/main.lua.d/50-analog-priority.lua b/x86_64/airootfs/etc/wireplumber/main.lua.d/50-analog-priority.lua new file mode 100644 index 0000000..b29aeaa --- /dev/null +++ b/x86_64/airootfs/etc/wireplumber/main.lua.d/50-analog-priority.lua @@ -0,0 +1,31 @@ +-- Prioritize analog and USB audio devices over HDMI +-- This ensures real sound cards are selected by default + +rule = { + matches = { + { + { "device.form-factor", "equals", "internal" }, + }, + { + { "device.form-factor", "equals", "speaker" }, + }, + { + { "device.form-factor", "equals", "headphone" }, + }, + { + { "device.bus", "equals", "usb" }, + }, + { + { "node.name", "matches", "alsa_output.pci-*analog*" }, + }, + { + { "node.name", "matches", "alsa_output.usb-*" }, + }, + }, + apply_properties = { + ["priority.driver"] = 1000, + ["priority.session"] = 1000, + }, +} + +table.insert(alsa_monitor.rules, rule) diff --git a/x86_64/airootfs/etc/wireplumber/main.lua.d/51-hdmi-deprioritize.lua b/x86_64/airootfs/etc/wireplumber/main.lua.d/51-hdmi-deprioritize.lua new file mode 100644 index 0000000..0c5113f --- /dev/null +++ b/x86_64/airootfs/etc/wireplumber/main.lua.d/51-hdmi-deprioritize.lua @@ -0,0 +1,22 @@ +-- Deprioritize HDMI audio devices +-- This ensures analog/USB audio devices are preferred over HDMI + +rule = { + matches = { + { + { "device.name", "matches", "hdmi*" }, + }, + { + { "device.name", "matches", "HDMI*" }, + }, + { + { "node.name", "matches", "alsa_output.pci-*hdmi*" }, + }, + }, + apply_properties = { + ["priority.driver"] = 100, + ["priority.session"] = 100, + }, +} + +table.insert(alsa_monitor.rules, rule) diff --git a/x86_64/airootfs/home/stormux/.bash_aliases b/x86_64/airootfs/home/stormux/.bash_aliases new file mode 100644 index 0000000..10ba8f8 --- /dev/null +++ b/x86_64/airootfs/home/stormux/.bash_aliases @@ -0,0 +1,4 @@ +# Raspberry Pi Information +alias pi-temp='/usr/bin/vcgencmd measure_temp' +alias pi-version='cat /sys/firmware/devicetree/base/model;echo' +alias pi-ip='ip a | grep -v "127.0.0.1" | grep -E -o "([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}"' diff --git a/x86_64/airootfs/home/stormux/.bash_functions b/x86_64/airootfs/home/stormux/.bash_functions new file mode 100644 index 0000000..c4d13a6 --- /dev/null +++ b/x86_64/airootfs/home/stormux/.bash_functions @@ -0,0 +1,21 @@ +memuse() { + ps axo rss,comm,pid \ + | awk '{ proc_list[$2] += $1; } END \ + { for (proc in proc_list) { printf("%d\t%s\n", proc_list[proc],proc); }}' \ + | sort -n | tail -n 10 | sort -rn \ + | awk '{$1/=1024;printf "%.0fMB\t",$1}{print $2}' +} + + +pdf() +{ + if [[ $# -ne 1 ]]; then + echo 'Usage: pdf ' >&2 + else + local dir=$(mktemp -d -p /tmp pdf_conversion.XXXXXX) + local outFile="${1##*/}" + local outFile="${outFile%.*}" + pdftohtml -noframes -i -s "$1" "${dir}/${outFile}.html" + w3m -s "${dir}/${outFile}.html" + fi +} diff --git a/x86_64/airootfs/home/stormux/.bashrc b/x86_64/airootfs/home/stormux/.bashrc new file mode 100644 index 0000000..65f57bd --- /dev/null +++ b/x86_64/airootfs/home/stormux/.bashrc @@ -0,0 +1,24 @@ +# +# ~/.bashrc +# + +# If not running interactively, don't do anything +[[ $- != *i* ]] && return + +#Change directories without using cd +shopt -s autocd + +# Keep bash history in screen +export HISTFILE="${HISTFILE}${WINDOW:+.${WINDOW}}" + +# load Aliases and functions +[[ -f ".bash_aliases" ]] && source .bash_aliases +[[ -f ".bash_functions" ]] && source .bash_functions +#Invironment variables +PS1='[\u@\h \W] \$ ' +export DIALOGOPTS='--no-lines --visit-items' +GPG_TTY=$(tty) +export GPG_TTY +# Don't put commands prefixed with space, or duplicate commands in history +export HISTCONTROL=ignoreboth + diff --git a/x86_64/airootfs/home/stormux/.config/pipewire/pipewire-pulse.conf.d/50-fenrir-console.conf b/x86_64/airootfs/home/stormux/.config/pipewire/pipewire-pulse.conf.d/50-fenrir-console.conf new file mode 100644 index 0000000..e5f2a1d --- /dev/null +++ b/x86_64/airootfs/home/stormux/.config/pipewire/pipewire-pulse.conf.d/50-fenrir-console.conf @@ -0,0 +1,24 @@ +# Fenrir console audio support +# Adds secondary socket for console applications like Fenrir + +pulse.properties = { + # the addresses this server listens on + server.address = [ + "unix:native" + "unix:/tmp/pulse.sock" # console access socket + ] +} + +# client/stream specific properties +pulse.rules = [ + { + # speech dispatcher asks for too small latency and then underruns. + matches = [ { application.name = "~speech-dispatcher*" } ] + actions = { + update-props = { + pulse.min.req = 1024/48000 # 21ms + pulse.min.quantum = 1024/48000 # 21ms + } + } + } +] diff --git a/x86_64/airootfs/home/stormux/.config/systemd/user/default.target.wants/pipewire-init-sound.service b/x86_64/airootfs/home/stormux/.config/systemd/user/default.target.wants/pipewire-init-sound.service new file mode 120000 index 0000000..b00efbc --- /dev/null +++ b/x86_64/airootfs/home/stormux/.config/systemd/user/default.target.wants/pipewire-init-sound.service @@ -0,0 +1 @@ +../pipewire-init-sound.service \ No newline at end of file diff --git a/x86_64/airootfs/home/stormux/.config/systemd/user/pipewire-init-sound.service b/x86_64/airootfs/home/stormux/.config/systemd/user/pipewire-init-sound.service new file mode 100644 index 0000000..f687e1e --- /dev/null +++ b/x86_64/airootfs/home/stormux/.config/systemd/user/pipewire-init-sound.service @@ -0,0 +1,12 @@ +[Unit] +Description=Initialize Pipewire Audio with Sound Test +After=pipewire.service pipewire-pulse.service wireplumber.service +Wants=pipewire.service pipewire-pulse.service wireplumber.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/local/bin/init-pipewire-sound.sh + +[Install] +WantedBy=default.target diff --git a/x86_64/airootfs/home/stormux/.config/wireplumber/bluetooth.conf.d/50-fenrir-bluez.conf b/x86_64/airootfs/home/stormux/.config/wireplumber/bluetooth.conf.d/50-fenrir-bluez.conf new file mode 100644 index 0000000..c5ffd39 --- /dev/null +++ b/x86_64/airootfs/home/stormux/.config/wireplumber/bluetooth.conf.d/50-fenrir-bluez.conf @@ -0,0 +1,24 @@ +# Fenrir console audio support +# Prevents bluetooth disconnection when switching TTY + +monitor.bluez.properties = { + # Disable logind integration to prevent bluetooth device suspension on TTY switch + bluez5.enable-sbc-xq = true + bluez5.enable-msbc = true + bluez5.enable-hw-volume = true +} + +monitor.bluez.rules = [ + { + matches = [ + { + device.name = "~bluez_card.*" + } + ] + actions = { + update-props = { + session.suspend-timeout-seconds = 0 + } + } + } +] diff --git a/x86_64/airootfs/home/stormux/.config/wireplumber/main.conf.d/50-fenrir-no-suspend.conf b/x86_64/airootfs/home/stormux/.config/wireplumber/main.conf.d/50-fenrir-no-suspend.conf new file mode 100644 index 0000000..4217470 --- /dev/null +++ b/x86_64/airootfs/home/stormux/.config/wireplumber/main.conf.d/50-fenrir-no-suspend.conf @@ -0,0 +1,35 @@ +# Fenrir console audio support +# Prevents audio device suspension when switching to TTY console + +monitor.alsa.rules = [ + { + matches = [ + { + device.name = "~alsa_card.*" + } + ] + actions = { + update-props = { + api.alsa.use-acp = true + api.acp.auto-profile = false + api.acp.auto-port = false + session.suspend-timeout-seconds = 0 + } + } + } + { + matches = [ + { + node.name = "~alsa_input.*" + } + { + node.name = "~alsa_output.*" + } + ] + actions = { + update-props = { + session.suspend-timeout-seconds = 0 + } + } + } +] diff --git a/x86_64/airootfs/home/stormux/.inputrc b/x86_64/airootfs/home/stormux/.inputrc new file mode 100644 index 0000000..0dcf6ea --- /dev/null +++ b/x86_64/airootfs/home/stormux/.inputrc @@ -0,0 +1,6 @@ +# Reload changes with control+x followed by control+r +set echo-control-characters off + +# History searching with up and down arrows. +"\e[A": history-search-backward +"\e[B": history-search-forward diff --git a/x86_64/airootfs/home/stormux/.screenrc b/x86_64/airootfs/home/stormux/.screenrc new file mode 100644 index 0000000..bd20ce3 --- /dev/null +++ b/x86_64/airootfs/home/stormux/.screenrc @@ -0,0 +1,18 @@ +vbell off +bell_msg "" +hardstatus off +startup_message off +defscrollback 4096 +bind ! select 10 +bind @ select 11 +bind \# select 12 +bind $ select 13 +bind % select 14 +bind ^ select 15 +bind & select 16 +bind * select 17 +bind ( select 18 +bind ) select 19 +bind b eval "writebuf" 'exec !!! xclip -selection "clipboard" -i /tmp/screen-exchange' +#termcapinfo xterm|xterms|xs|rxvt ti@:te@ +termcapinfo xterm* ti@:te@ diff --git a/x86_64/airootfs/root/.config/pulse/client.conf b/x86_64/airootfs/root/.config/pulse/client.conf new file mode 100644 index 0000000..2a09e73 --- /dev/null +++ b/x86_64/airootfs/root/.config/pulse/client.conf @@ -0,0 +1,36 @@ +# This file is part of PulseAudio. +# +# PulseAudio is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, see . + +## Configuration file for PulseAudio clients. See pulse-client.conf(5) for +## more information. Default values are commented out. Use either ; or # for +## commenting. + +; default-sink = +; default-source = +default-server = unix:/tmp/pulse.sock +; default-dbus-server = + +autospawn = no +; autospawn = yes +; daemon-binary = /usr/bin/pulseaudio +; extra-arguments = --log-target=syslog + +; cookie-file = + +; enable-shm = yes +; shm-size-bytes = 0 # setting this 0 will use the system-default, usually 64 MiB + +; auto-connect-localhost = no +; auto-connect-display = no diff --git a/x86_64/airootfs/root/rename-user.sh b/x86_64/airootfs/root/rename-user.sh new file mode 100755 index 0000000..3e66b23 --- /dev/null +++ b/x86_64/airootfs/root/rename-user.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash + +# Renames a user +# Required arguments: old user name, new user name + +set -e + +question() { + echo + echo "$@" + read -r answer + answer="${answer:0:1}" + answer="${answer^}" +} + +if [[ $# -ne 2 ]]; then + echo "Usage: $0 old-user new-user" + exit 1 +fi + +oldUser="$1" +newUser="$2" + +# Make sure old user exists +if ! id "$oldUser" >/dev/null 2>&1; then + echo "The user $oldUser does not exist." + exit 1 +fi + +# Make sure old user is not logged in +if pgrep -u "$oldUser" >/dev/null; then + echo "The user $oldUser is currently logged in or has running processes. Cannot continue." + question "Would you like to forcibly log out $oldUser? (Y/N)" + if [[ "$answer" != "Y" ]]; then + exit 1 + fi + systemctl stop display-manager + loginctl terminate-user "$oldUser" + sleep 2 + if pgrep -u "$oldUser" >/dev/null; then + echo "The user $oldUser still has running processes after termination. Cannot continue." + exit 1 + fi +fi + +# Make sure new user does not exist +if id "$newUser" >/dev/null 2>&1; then + echo "The user name $newUser is already taken." + exit 1 +fi + +# Make sure the new name is acceptable +if ! [[ "$newUser" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]]; then + echo "$newUser is not an acceptable user name." + exit 1 +fi + +# Passed all safety checks, proceed with the rename +echo "Renaming $oldUser to $newUser..." +groups=$(id -nG "$oldUser") +groups="${groups// /,}" +usermod -G "$groups" -m -d "/home/$newUser" -l "$newUser" "$oldUser" + +# Update nodm.conf if it exists +if [[ -e /etc/nodm.conf ]]; then + echo "Updating /etc/nodm.conf..." + sed -i -e "s#^NODM_USER=.*#NODM_USER='$newUser'#" -e "s#^NODM_XSESSION=.*#NODM_XSESSION='/home/$newUser/.xinitrc'#" /etc/nodm.conf +fi + +# Copy over crontab if it exists +if crontab -u "$oldUser" -l >/tmp/${oldUser}_crontab.bak 2>/dev/null; then + echo "Copying over crontab..." + if crontab -u "$newUser" "/tmp/${oldUser}_crontab.bak"; then + rm "/tmp/${oldUser}_crontab.bak" + else + echo "Warning: failed to restore crontab for $newUser." + rm "/tmp/${oldUser}_crontab.bak" + fi +fi + +# Update linger +if [[ -e "/var/lib/systemd/linger/$oldUser" ]]; then + echo "Enabling linger..." + mv "/var/lib/systemd/linger/$oldUser" "/var/lib/systemd/linger/$newUser" +fi + +# Optionally update files in new home directory +echo +echo "Would you like to search for references to $oldUser and update them to $newUser in files located in /home/$newUser?" +question "This can take a while. (Y/N)" +if [[ "$answer" == "Y" ]]; then + echo "Updating files..." + find "/home/$newUser" -type f -exec grep -Iq . "{}" \; -and -exec sed -i "s|$oldUser|$newUser|g" "{}" \; +fi + +echo +echo "Rename complete. The user, $newUser, is now available." +question "Would you like to reboot?" +if [[ "${answer}" == "Y" ]]; then + reboot +fi + +exit 0 diff --git a/x86_64/airootfs/skel b/x86_64/airootfs/skel new file mode 120000 index 0000000..7ea1b11 --- /dev/null +++ b/x86_64/airootfs/skel @@ -0,0 +1 @@ +../../../pi4/files/etc/skel \ No newline at end of file diff --git a/x86_64/airootfs/usr/local/bin/configure-stormux b/x86_64/airootfs/usr/local/bin/configure-stormux new file mode 100755 index 0000000..cd2579f --- /dev/null +++ b/x86_64/airootfs/usr/local/bin/configure-stormux @@ -0,0 +1,141 @@ +#!/usr/bin/env bash + +# For audible sudo prompts: +unset sudoFlags +if [[ -x /etc/audibleprompt.sh ]]; then + export SUDO_ASKPASS=/etc/audibleprompt.sh + export sudoFlags=("-A") +fi + +trap cleanup EXIT +cleanup() { + popd &> /dev/null + if ! [[ -x /opt/configure-stormux/configure-stormux.sh ]]; then + echo "Initial setup is not complete." + echo "To continue setup, please run:" + echo "sudo configure-stormux" + fi +} + + +if [[ -x /opt/configure-stormux/configure-stormux.sh ]]; then + pushd /opt/configure-stormux + ./configure-stormux.sh + exit 0 +fi + +# Volume calibration is now handled in stormux_first_boot.sh + +export DIALOGOPTS='--insecure --no-lines --visit-items' + +set_timezone() { + # Get the list of timezones + mapfile -t regions < <(timedatectl --no-pager list-timezones | cut -d '/' -f1 | sort -u) + + # Use the same text twice here and just hide the tag field. + region=$(dialog --backtitle "Please select your Region" \ + --no-tags \ + --menu "Use up and down arrows or page-up and page-down to navigate the list, and press 'Enter' to make your selection." 0 0 0 \ + $(for i in ${regions[@]} ; do echo "$i";echo "$i";done) --stdout) + + + mapfile -t cities < <(timedatectl --no-pager list-timezones | grep "$region" | cut -d '/' -f2 | sort -u) + + # Use the same text twice here and just hide the tag field. + city=$(dialog --backtitle "Please select a city near you" \ + --no-tags \ + --menu "Use up and down arrow or page-up and page-down to navigate the list." 0 0 10 \ + $(for i in ${cities[@]} ; do echo "$i";echo "$i";done) --stdout) + + # Set the timezone + if [[ -f /etc/localtime ]]; then + rm /etc/localtime + fi + ln -sf /usr/share/zoneinfo/${region}/${city} /etc/localtime + 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 + sed -i 's/=desktop/=laptop/' /etc/fenrirscreenreader/settings/settings.conf + clear + systemctl restart fenrirscreenreader.service +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 + # Extract base device name (handles mmcblk0p2, nvme0n1p2, sda2, etc.) + if [[ "$diskSource" =~ (.*[0-9]+)p[0-9]+$ ]]; then + # Handle mmcblk0p2, nvme0n1p2 style + diskDevice="${BASH_REMATCH[1]}" + else + # Handle sda2, sdb3 style + 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" + fi +fi + +if ! ping -c1 stormux.org &> /dev/null ; then + echo "No internet connection detected. Press enter to open NetworkManager." + read -r continue + echo "setting set focus#highlight=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock + nmtui-connect + echo "setting set focus#highlight=False" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +fi +# Check for internet connectivity +if ping -qc1 -W 1 stormux.org &> /dev/null; then + echo "Updating the clock to prevent certificate errors..." + # Get current date and time + date_time=$(curl -s http://worldtimeapi.org/api/ip | grep -oP '(?<="datetime":")[^"]*') + echo "Current date and time: $date_time" + # set date and time + date -s "$date_time" + echo "If your Pi does not have a CMOS battery and is powered off regularly, it may take a while for the time to be set correctly after boot." + echo "Stormux provides a service to sync the time after internet connection is established." + 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 + else + echo "Time sync at boot skipped." + echo "If you change your mind later, simply type:" + echo "sudo systemctl enable time_sync_at_boot.service" + fi + set_timezone +else + echo "Please connect to the internet and run ${0##*/} again." + exit 1 +fi +echo "Installing configure-stormux..." +git -C /opt clone -q https://git.stormux.org/storm/configure-stormux || exit 1 + +echo +echo "Initial setup is complete." +echo +echo "The default passwords are stormux for the stormux user" +echo "and root for the root user. It is highly recommended to change them." +echo "To change the password for stormux, run:" +echo "passwd" +echo "To change the password for root, run:" +echo "sudo passwd" +echo +echo "For more configuration options, run configure-stormux," +echo "or you may configure your system manually." +echo +echo "Thank you for choosing Stormux." + + +exit 0 diff --git a/x86_64/airootfs/usr/local/bin/init-pipewire-sound.sh b/x86_64/airootfs/usr/local/bin/init-pipewire-sound.sh new file mode 100755 index 0000000..58b9db4 --- /dev/null +++ b/x86_64/airootfs/usr/local/bin/init-pipewire-sound.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Initialize pipewire audio by attempting to play sound until it succeeds +# This ensures pipewire is fully ready before fenrir starts + +maxAttempts=30 +attempt=0 + +while [[ $attempt -lt $maxAttempts ]]; do + # Try to play a silent sound using sox + if play -qnV0 synth .1 whi norm -100 2>/dev/null; then + # Success! Pipewire is working + exit 0 + fi + + # Wait a moment before trying again + sleep 1 + ((attempt++)) +done + +# Even if we failed, exit successfully so we don't block boot +exit 0 diff --git a/x86_64/airootfs/usr/local/bin/livecd-sound b/x86_64/airootfs/usr/local/bin/livecd-sound new file mode 100755 index 0000000..8a8ff92 --- /dev/null +++ b/x86_64/airootfs/usr/local/bin/livecd-sound @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Configure audio for Stormux live environment +# Ensures pipewire is running and audio is properly configured + +# Start pipewire for the stormux user if not already running +if id stormux &>/dev/null; then + # Enable linger for stormux user + loginctl enable-linger stormux 2>/dev/null || true + + # Start pipewire user services + stormux_uid="$(id -u stormux)" + sudo -u stormux XDG_RUNTIME_DIR="/run/user/${stormux_uid}" systemctl --user enable --now pipewire.service pipewire-pulse.service wireplumber.service 2>/dev/null || true + + # Wait for pipewire to initialize + sleep 2 +fi + +# Unmute and set reasonable volumes +amixer -q set Master 70% unmute 2>/dev/null || true +amixer -q set PCM 70% unmute 2>/dev/null || true +amixer -q set Speaker 70% unmute 2>/dev/null || true +amixer -q set Headphone 70% unmute 2>/dev/null || true + +exit 0 diff --git a/x86_64/airootfs/usr/local/bin/setup-pipewire-live.sh b/x86_64/airootfs/usr/local/bin/setup-pipewire-live.sh new file mode 100755 index 0000000..cfcc284 --- /dev/null +++ b/x86_64/airootfs/usr/local/bin/setup-pipewire-live.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# Setup Pipewire for the live environment +# This ensures pipewire is running before Fenrir starts + +# Note: Do not use set -e - we want this to succeed even if commands fail + +# Enable user linger for the stormux user (allows user services to run without login) +if [ -d /var/lib/systemd/linger ]; then + touch /var/lib/systemd/linger/stormux +fi + +# Start pipewire for the stormux user +if id stormux &>/dev/null; then + # Use machinectl to start user services in the user's session + machinectl shell stormux@ /usr/bin/systemctl --user enable pipewire.service pipewire-pulse.service wireplumber.service 2>/dev/null || true + machinectl shell stormux@ /usr/bin/systemctl --user start pipewire.service pipewire-pulse.service wireplumber.service 2>/dev/null || true +fi + +# Wait a moment for pipewire to initialize +sleep 2 + +exit 0 diff --git a/x86_64/airootfs/usr/local/bin/stormux-setup.sh b/x86_64/airootfs/usr/local/bin/stormux-setup.sh new file mode 100755 index 0000000..4dca9cf --- /dev/null +++ b/x86_64/airootfs/usr/local/bin/stormux-setup.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# +# Customize the airootfs after package installation +# This script runs in the chroot environment after packages are installed + +set -e -u + +# Copy custom skel files, overwriting package defaults +if [ -d /etc/skel.custom ]; then + echo "Installing custom skel files..." + cp -f /etc/skel.custom/.* /etc/skel/ 2>/dev/null || true + rm -rf /etc/skel.custom +fi + +# Set the distribution name +echo 'Stormux \r (\l)' > /etc/issue +echo >> /etc/issue + +# Create realtime group if it doesn't exist +if ! getent group realtime > /dev/null 2>&1; then + echo "Creating realtime group..." + groupadd -r realtime +fi + +# Create stormux user with groups matching Pi build +echo "Creating stormux user..." +useradd -m -g users -G wheel,realtime,audio,video,network,brlapi -s /bin/bash stormux + +# Set passwords +echo -e "root\nroot" | passwd "root" +echo -e "stormux\nstormux" | passwd "stormux" + +# Configure git for stormux user +sudo -iu stormux bash -c 'git config --global init.defaultBranch master' + +# Create user directories +sudo -iu stormux xdg-user-dirs-update + +# Enable linger so that sound will start at login +mkdir -p /var/lib/systemd/linger +touch /var/lib/systemd/linger/stormux + +# Enable pipewire globally +# Pipewire configuration for fenrir is pre-installed in airootfs +systemctl --global enable pipewire.service pipewire-pulse.service + +# Configure sudo for wheel group +echo '%wheel ALL=(ALL) ALL' > /etc/sudoers.d/wheel + +# Set hostname +echo stormux > /etc/hostname + +# Disable systemd-networkd in favor of NetworkManager +systemctl disable systemd-networkd.service systemd-networkd.socket 2>/dev/null || true + +# Enable services +services=( + brltty.path + fenrirscreenreader.service + NetworkManager.service + stormux-audio-setup.service +) + +for service in "${services[@]}"; do + echo "Enabling $service..." + if systemctl enable "$service"; then + echo " $service: OK" + else + echo " $service: FAILED" + fi +done + +echo "Airootfs customization complete" diff --git a/x86_64/airootfs/usr/local/bin/sync_time.sh b/x86_64/airootfs/usr/local/bin/sync_time.sh new file mode 100755 index 0000000..aea1925 --- /dev/null +++ b/x86_64/airootfs/usr/local/bin/sync_time.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +date_time=$(curl -s http://worldtimeapi.org/api/ip | grep -oP '(?<="datetime":")[^"]*') +date -s "$date_time" diff --git a/x86_64/airootfs/usr/share/kbd/keymaps/i386/qwerty/us_alt.map.gz b/x86_64/airootfs/usr/share/kbd/keymaps/i386/qwerty/us_alt.map.gz new file mode 100644 index 0000000000000000000000000000000000000000..2b138542ac6f0763562b9810278a4963d2fa3491 GIT binary patch literal 677 zcmb2|=HO^QHYJ6DIkz%5u|O|3v4Ej9ba&Bh1A#S1*FUgW`_eG;G;cDMoSUZdMCtQE&EhXRKde|QY^9ugNlEMc=|9E)&U#;D(hiH*(zb6S{~ixr zskU8?BCEUtm7e_9oZ)w#tEbd=jXL`Tr&-Jv>-*HAJ+qrOS54h^=G=yZkDA}cYzocY zD)w&ip}*3<|0z7Vnf=he=hHNetHH0lx&4|ry?1fgf3j3z+0*e@)8Le@)1IoC5zm$e z1zFxS_1+Tgc6y;;q5GN-ENY3l0jtE9$#JQ0n%MM+n@lKM=C))@-|wByehOb#USjb3 zS$xj&}k=~>}TI9 zmU8cWX}#?Hl3Q-GG7?QDs2gd-9zHVdY4s|N27AR4zWdsIYeRQknxB@Cuybvh;mwUz zn_pkPqmj8)R`~9JVaM#QsRuke_P)&w_<7?d!&gWB9T&}cr22X~l*&q!YM*}m;~jEk z63<4{*0Dq1+7jIe&T|D4AAF+r*oBKzx19Sp#==rujO} z7sEQ2m_I(ip89yp*~@LM>IO=`|M-2lye0oyakJT!^ZK*Gf4#hsBqnb2N?iE5`INQQ re@@=5-~LJFb-9brGnL@S^MyA$gd2W)b7R8Ue@wpi_ugJ+XJ7yTp1Mb( literal 0 HcmV?d00001 diff --git a/x86_64/airootfs/usr/share/stormux/stormux_repo.pub b/x86_64/airootfs/usr/share/stormux/stormux_repo.pub new file mode 100644 index 0000000..28edfe6 --- /dev/null +++ b/x86_64/airootfs/usr/share/stormux/stormux_repo.pub @@ -0,0 +1,29 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGhTtIcBEADTCqY/+das1n52HTTlp3Uf5+ER5r0fNA2HgJq4GzEeFBdqdgSF +y05P9B94d4JWtWvt0ckjsNfku19O9IquKiYP5x9/sYT8RGeRUrTlN9qlCrytTsdM +a8ij9esGQOXDeyRJQTs5igbFWQ26MrJpOkmO8xMXGmfD29yOppRLzVS8Jed0mokk +aEajui3QSWcefDnFMBYNaMF04iuWgVxv/7102adh3u3+V632+fQQhFtu+x92iosU +GtWlGGe3UkEhWJIb6wf/VCYIYQAjxTQBLul2WByXqwCXmobzc7DJbte3FSEPvPis +nJiUmT0fsIIC9c1UbTJt5Gqb6o/oI61tEJEeXieBViXFE7HwBI7FUxaIXoozgiYA +BxaLHQT0Qv9k1SXbBm82xNydjqFHIAolYSp5s1wRIhErlOaG9w3+Tb8zFSekPsbg +cgS/b3PpWmnigXBJQ94Ee+6KoPMVrwEKIu/IHexRUVAm+66Xs55ccgMY+MUupIjX +Bdn9jCYB8NwpC3UNWoSv2oQ7F2hVbXZYC/dx7gKmyvzfrw5MLOX7yEoSGlJJp2iK +fLLP8nw+x7LnDFEnAjPjtmeH7e+JANH1scP/7YMKDszO1THSK3hBAK1aeq0aEFs8 +9AO++xZrNbCrF2MnqcGaXs/753k6kf8zyrW6t7A1opxjPHIz3WrQ8SbXXwARAQAB +tExTdG9ybXV4IChGb3Igc2lnbmluZyBzdG9ybXV4IHJlcG9zaXRvcnkgcGFja2Fn +ZXMpIDxzdG9ybV9kcmFnb25Ac3Rvcm11eC5vcmc+iQJOBBMBCgA4FiEEUq2kkADx +/wRW+K7vtM3hzVbvjoIFAmhTtIcCGwMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AA +CgkQtM3hzVbvjoLiLQ/9HJzlwr2JNGdR/LaM1Y6VuhASGDZsUsi5XLJ7qKUKN/eA +kq0LMO7TzHO++otsEolodMr8fTkthQTPqSnbLZsW9Y3wK9U2vajryblRSEBaY6VB +1ds0pAg5SXz7O2eI2Ne05dkYB176G72KcjrOiSanKjI9sqh6pdTHcQM3XbRxxEVn +or7fKbLs/eveFWRPW3EAwuVnWI1aw3Xw9BQ3TE1eEDPvBh2GVKZ6qskbzdwfZtUB +kxAa30M+ftlNDaV7d6mNfREefEJfZjYFrY2XiqTK3+w1Q1ZaugK0Bbd/YdAoGMdt +6gbd2h/IZtxduWW/HhvWX2wtKqn4fNxAyYcY3aWm8kuq0pXlyEZ96gZas6a2lu+f ++yLvIh3UtC1IimFMDMQnKCTbWgdhd0HMnG9ULnoOroXVGltnBQwlO0hrHSVD+qmy +NVPUxkzS3ZhqSmoW2G8hSvXpMjt/w5fhC0FJhFb/uM/7LqJWSl5aAkUHupN3t/nG +9o7chBQzzXwbpiS6DQuIt6ouyPpwNjeELFPNBjZDf0auVWj6ZZ5+B/bmyHXQ4wFS +bYKZdO9VeXhBxRUMfGpIKvIw/Lnc+1Toan1RTWoUoRH+HuK2D/LAuvsam36gtLrQ +XV3//7Mh4oQ0Zg4REG3SEfG9i3rav8wMtRmaJaSbwmMVpC6mT/uiEzCSgQhO/0c= +=HIM9 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/x86_64/airootfs/var/lib/alsa/asound.state b/x86_64/airootfs/var/lib/alsa/asound.state new file mode 100644 index 0000000..d128b82 --- /dev/null +++ b/x86_64/airootfs/var/lib/alsa/asound.state @@ -0,0 +1,79 @@ +state.ALSA { + control.1 { + iface MIXER + name 'PCM Playback Volume' + value -197 + comment { + access 'read write' + type INTEGER + count 1 + range '-10239 - 400' + dbmin -9999999 + dbmax 400 + dbvalue.0 -197 + } + } + control.2 { + iface MIXER + name 'PCM Playback Switch' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.3 { + iface MIXER + name 'PCM Playback Route' + value 1 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 2' + } + } + control.4 { + iface PCM + name 'IEC958 Playback Default' + value '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + comment { + access 'read write' + type IEC958 + count 1 + } + } + control.5 { + iface PCM + name 'IEC958 Playback Con Mask' + value '0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + comment { + access read + type IEC958 + count 1 + } + } + control.6 { + iface PCM + name 'IEC958 Playback PCM Stream' + value '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + comment { + access 'read write inactive' + type IEC958 + count 1 + } + } +} +state.vc4hdmi { + control.1 { + iface PCM + name ELD + value '100008006a10000100000000000000000469fd22415355532056533232380917070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + comment { + access 'read volatile' + type BYTES + count 128 + } + } +} diff --git a/x86_64/build.sh b/x86_64/build.sh new file mode 100755 index 0000000..7543c26 --- /dev/null +++ b/x86_64/build.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +# +# Copyright 2025, Stormux, +# +# This is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3, or (at your option) any later +# version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this package; see the file COPYING. If not, write to the Free +# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +# + +set -e + +help() { + echo "Stormux x86_64 ISO Builder" + echo + echo "Usage: $0 [options]" + echo + echo "Options:" + echo " -o Output directory (default: ./out)" + echo " -w Work directory (default: ./work)" + echo " -h Show this help" + echo + echo "This script builds a Stormux x86_64 ISO using archiso." + echo "It must be run as root on an Arch Linux system." + exit 0 +} + +# Parse command line arguments +output_dir="./out" +work_dir="./work" + +while getopts "o:w:h" opt; do + case "$opt" in + o) output_dir="$OPTARG" ;; + w) work_dir="$OPTARG" ;; + h) help ;; + *) help ;; + esac +done + +# Make sure this script is run as root +if [ "$(whoami)" != "root" ]; then + echo "Error: This script must be run as root." + exit 1 +fi + +# Check for required tools +for tool in mkarchiso pacman; do + if ! command -v "$tool" &> /dev/null; then + echo "Error: $tool is not installed. Please install archiso package." + exit 1 + fi +done + +# Get the directory where this script is located +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +pi4_files_dir="$script_dir/../pi4/files" + +echo "Building Stormux x86_64 ISO..." +echo "Profile directory: $script_dir" +echo "Output directory: $output_dir" +echo "Work directory: $work_dir" +echo + +# Clean up work directory from previous builds to ensure clean build environment +if [ -d "$work_dir" ]; then + echo "Cleaning up previous build artifacts..." + rm -rf "$work_dir" +fi + +# Note: Shared configuration files from pi4/files should already be in airootfs/ +# The airootfs/ directory is maintained as part of the x86_64 profile +# and should not be dynamically copied during build to avoid conflicts with packages +echo "Using existing airootfs configuration files..." + +# Add Stormux repository key to build host's keyring +echo "Setting up Stormux repository key on build host..." +key_file="$script_dir/stormux_repo.pub" +if [ ! -f "$key_file" ]; then + echo "Error: Stormux repository key not found at $key_file" + echo "Please ensure stormux_repo.pub exists in the x86_64 directory" + exit 1 +fi + +# Check if key is already in keyring +if ! pacman-key --list-keys 52ADA49000F1FF0456F8AEEFB4CDE1CD56EF8E82 &>/dev/null; then + echo "Adding Stormux repository key to build host keyring..." + pacman-key --add "$key_file" + pacman-key --lsign-key 52ADA49000F1FF0456F8AEEFB4CDE1CD56EF8E82 + echo "Key added successfully" +else + echo "Stormux repository key already in keyring" +fi + +# Build the ISO +mkarchiso -v -w "$work_dir" -o "$output_dir" "$script_dir" + +echo +echo "Build complete!" +echo "ISO file is in: $output_dir" diff --git a/x86_64/client.conf b/x86_64/client.conf new file mode 100644 index 0000000..2a09e73 --- /dev/null +++ b/x86_64/client.conf @@ -0,0 +1,36 @@ +# This file is part of PulseAudio. +# +# PulseAudio is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, see . + +## Configuration file for PulseAudio clients. See pulse-client.conf(5) for +## more information. Default values are commented out. Use either ; or # for +## commenting. + +; default-sink = +; default-source = +default-server = unix:/tmp/pulse.sock +; default-dbus-server = + +autospawn = no +; autospawn = yes +; daemon-binary = /usr/bin/pulseaudio +; extra-arguments = --log-target=syslog + +; cookie-file = + +; enable-shm = yes +; shm-size-bytes = 0 # setting this 0 will use the system-default, usually 64 MiB + +; auto-connect-localhost = no +; auto-connect-display = no diff --git a/x86_64/efiboot/loader/entries/01-stormux-linux.conf b/x86_64/efiboot/loader/entries/01-stormux-linux.conf new file mode 100644 index 0000000..ea26505 --- /dev/null +++ b/x86_64/efiboot/loader/entries/01-stormux-linux.conf @@ -0,0 +1,5 @@ +title Stormux Accessible Linux (%ARCH%, UEFI) with speech +sort-key 01 +linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux +initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img +options archisobasedir=%INSTALL_DIR% archisolabel=%ARCHISO_LABEL% accessibility=on diff --git a/x86_64/efiboot/loader/entries/02-stormux-linux-no-speech.conf b/x86_64/efiboot/loader/entries/02-stormux-linux-no-speech.conf new file mode 100644 index 0000000..a8cf144 --- /dev/null +++ b/x86_64/efiboot/loader/entries/02-stormux-linux-no-speech.conf @@ -0,0 +1,5 @@ +title Stormux Accessible Linux (%ARCH%, UEFI) +sort-key 02 +linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux +initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img +options archisobasedir=%INSTALL_DIR% archisolabel=%ARCHISO_LABEL% diff --git a/x86_64/efiboot/loader/loader.conf b/x86_64/efiboot/loader/loader.conf new file mode 100644 index 0000000..c64c3ed --- /dev/null +++ b/x86_64/efiboot/loader/loader.conf @@ -0,0 +1,3 @@ +timeout 15 +default 01-stormux-linux.conf +beep on diff --git a/x86_64/fix-live-iso-services.sh b/x86_64/fix-live-iso-services.sh new file mode 100755 index 0000000..61b606e --- /dev/null +++ b/x86_64/fix-live-iso-services.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Fix live ISO services for x86_64 + +set -e + +cd "$(dirname "$0")" + +echo "=== Fixing Live ISO Services for x86_64 ===" +echo + +# 1. Remove Pi-specific first boot script +if [ -f airootfs/etc/profile.d/stormux_first_boot.sh ]; then + echo "Removing Pi-specific first boot script..." + sudo rm airootfs/etc/profile.d/stormux_first_boot.sh +fi + +# 2. Mask systemd-firstboot (blocks boot with interactive prompts) +echo "Masking systemd-firstboot.service..." +sudo ln -sf /dev/null airootfs/etc/systemd/system/systemd-firstboot.service + +# 3. Install Fenrir audio configurations +echo "Installing Fenrir audio configurations..." +sudo ./setup-fenrir-audio-configs.sh + +# 4. Check/verify enabled services +echo +echo "=== Enabled Services ===" +ls -la airootfs/etc/systemd/system/multi-user.target.wants/ | grep -E "fenrir|speech|audio" || echo "No speech/audio services in multi-user.target.wants" +ls -la airootfs/etc/systemd/system/sound.target.wants/ | grep -E "audio|sound" || echo "No services in sound.target.wants" + +echo +echo "=== Configuration Complete ===" +echo "Next step: sudo ./build.sh -v" diff --git a/x86_64/grub/grub.cfg b/x86_64/grub/grub.cfg new file mode 100644 index 0000000..25ad967 --- /dev/null +++ b/x86_64/grub/grub.cfg @@ -0,0 +1,106 @@ +# Load partition table and file system modules +insmod part_gpt +insmod part_msdos +insmod fat +insmod iso9660 +insmod ntfs +insmod ntfscomp +insmod exfat +insmod udf + +# Use graphics-mode output +if loadfont "${prefix}/fonts/unicode.pf2" ; then + insmod all_video + set gfxmode="auto" + terminal_input console + terminal_output console +fi + +# Enable serial console +insmod serial +insmod usbserial_common +insmod usbserial_ftdi +insmod usbserial_pl2303 +insmod usbserial_usbdebug +if serial --unit=0 --speed=115200; then + terminal_input --append serial + terminal_output --append serial +fi + +# Get a human readable platform identifier +if [ "${grub_platform}" == 'efi' ]; then + archiso_platform='UEFI' +elif [ "${grub_platform}" == 'pc' ]; then + archiso_platform='BIOS' +else + archiso_platform="${grub_cpu}-${grub_platform}" +fi + +# Set default menu entry +default=stormux +timeout=15 +timeout_style=menu + + +# Menu entries + +menuentry "Stormux Accessible Linux (%ARCH%, ${archiso_platform})" --class stormux --class gnu-linux --class gnu --class os --id 'stormux' { + set gfxpayload=keep + linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% + initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img +} + + +if [ "${grub_platform}" == 'efi' -a "${grub_cpu}" == 'x86_64' -a -f '/boot/memtest86+/memtest.efi' ]; then + menuentry 'Run Memtest86+ (RAM test)' --class memtest86 --class memtest --class gnu --class tool { + set gfxpayload=800x600,1024x768 + linux /boot/memtest86+/memtest.efi + } +fi +if [ "${grub_platform}" == 'pc' -a -f '/boot/memtest86+/memtest' ]; then + menuentry 'Run Memtest86+ (RAM test)' --class memtest86 --class memtest --class gnu --class tool { + set gfxpayload=800x600,1024x768 + linux /boot/memtest86+/memtest + } +fi +if [ "${grub_platform}" == 'efi' ]; then + if [ "${grub_cpu}" == 'x86_64' -a -f '/shellx64.efi' ]; then + menuentry 'UEFI Shell' --class efi { + chainloader /shellx64.efi + } + elif [ "${grub_cpu}" == 'i386' -a -f '/shellia32.efi' ]; then + menuentry 'UEFI Shell' --class efi { + chainloader /shellia32.efi + } + elif [ "${grub_cpu}" == 'arm64' -a -f '/shellaa64.efi' ]; then + menuentry 'UEFI Shell' --class efi { + chainloader /shellaa64.efi + } + elif [ "${grub_cpu}" == 'riscv64' -a -f '/shellriscv64.efi' ]; then + menuentry 'UEFI Shell' --class efi { + chainloader /shellriscv64.efi + } + elif [ "${grub_cpu}" == 'loongarch64' -a -f '/shellloongarch64.efi' ]; then + menuentry 'UEFI Shell' --class efi { + chainloader /shellloongarch64.efi + } + fi + + menuentry 'UEFI Firmware Settings' --id 'uefi-firmware' { + fwsetup + } +fi + +menuentry 'System shutdown' --class shutdown --class poweroff { + echo 'System shutting down...' + halt +} + +menuentry 'System restart' --class reboot --class restart { + echo 'System rebooting...' + reboot +} + + +# GRUB init tune for accessibility +play 600 988 1 1319 4 diff --git a/x86_64/grub/loopback.cfg b/x86_64/grub/loopback.cfg new file mode 100644 index 0000000..1a48fc5 --- /dev/null +++ b/x86_64/grub/loopback.cfg @@ -0,0 +1,85 @@ +# https://www.supergrubdisk.org/wiki/Loopback.cfg + +# Search for the ISO volume +search --no-floppy --set=archiso_img_dev --file "${iso_path}" +probe --set archiso_img_dev_uuid --fs-uuid "${archiso_img_dev}" + +# Get a human readable platform identifier +if [ "${grub_platform}" == 'efi' ]; then + archiso_platform='UEFI' +elif [ "${grub_platform}" == 'pc' ]; then + archiso_platform='BIOS' +else + archiso_platform="${grub_cpu}-${grub_platform}" +fi + +# Set default menu entry +default=archlinux +timeout=15 +timeout_style=menu + + +# Menu entries + +menuentry "Arch Linux install medium (%ARCH%, ${archiso_platform})" --class arch --class gnu-linux --class gnu --class os --id 'archlinux' { + set gfxpayload=keep + linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux archisobasedir=%INSTALL_DIR% img_dev=UUID=${archiso_img_dev_uuid} img_loop="${iso_path}" + initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img +} + +menuentry "Arch Linux install medium with speakup screen reader (%ARCH%, ${archiso_platform})" --hotkey s --class arch --class gnu-linux --class gnu --class os --id 'archlinux-accessibility' { + set gfxpayload=keep + linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux archisobasedir=%INSTALL_DIR% img_dev=UUID=${archiso_img_dev_uuid} img_loop="${iso_path}" accessibility=on + initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img +} + + +if [ "${grub_platform}" == 'efi' -a "${grub_cpu}" == 'x86_64' -a -f '/boot/memtest86+/memtest.efi' ]; then + menuentry 'Run Memtest86+ (RAM test)' --class memtest86 --class memtest --class gnu --class tool { + set gfxpayload=800x600,1024x768 + linux /boot/memtest86+/memtest.efi + } +fi +if [ "${grub_platform}" == 'pc' -a -f '/boot/memtest86+/memtest' ]; then + menuentry 'Run Memtest86+ (RAM test)' --class memtest86 --class memtest --class gnu --class tool { + set gfxpayload=800x600,1024x768 + linux /boot/memtest86+/memtest + } +fi +if [ "${grub_platform}" == 'efi' ]; then + if [ "${grub_cpu}" == 'x86_64' -a -f '/shellx64.efi' ]; then + menuentry 'UEFI Shell' --class efi { + chainloader /shellx64.efi + } + elif [ "${grub_cpu}" == 'i386' -a -f '/shellia32.efi' ]; then + menuentry 'UEFI Shell' --class efi { + chainloader /shellia32.efi + } + elif [ "${grub_cpu}" == 'arm64' -a -f '/shellaa64.efi' ]; then + menuentry 'UEFI Shell' --class efi { + chainloader /shellaa64.efi + } + elif [ "${grub_cpu}" == 'riscv64' -a -f '/shellriscv64.efi' ]; then + menuentry 'UEFI Shell' --class efi { + chainloader /shellriscv64.efi + } + elif [ "${grub_cpu}" == 'loongarch64' -a -f '/shellloongarch64.efi' ]; then + menuentry 'UEFI Shell' --class efi { + chainloader /shellloongarch64.efi + } + fi + + menuentry 'UEFI Firmware Settings' --id 'uefi-firmware' { + fwsetup + } +fi + +menuentry 'System shutdown' --class shutdown --class poweroff { + echo 'System shutting down...' + halt +} + +menuentry 'System restart' --class reboot --class restart { + echo 'System rebooting...' + reboot +} diff --git a/x86_64/mask-firstboot.sh b/x86_64/mask-firstboot.sh new file mode 100644 index 0000000..920d621 --- /dev/null +++ b/x86_64/mask-firstboot.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Mask systemd-firstboot for live ISO (it blocks boot) + +cd airootfs/etc/systemd/system + +# Create mask symlink +ln -sf /dev/null systemd-firstboot.service + +echo "Masked systemd-firstboot.service" +ls -la systemd-firstboot.service diff --git a/x86_64/packages.x86_64 b/x86_64/packages.x86_64 new file mode 100644 index 0000000..1344f88 --- /dev/null +++ b/x86_64/packages.x86_64 @@ -0,0 +1,151 @@ +alsa-firmware +alsa-utils +amd-ucode +arch-install-scripts +base +base-devel +bash-completion +bcachefs-tools +bind +bluez +bluez-utils +bolt +brltty +broadcom-wl +btrfs-progs +clonezilla +cloud-init +cryptsetup +darkhttpd +ddrescue +dhcpcd +diffutils +dmidecode +dmraid +dnsmasq +dosfstools +e2fsprogs +edk2-shell +efibootmgr +espeak-ng +ethtool +exfatprogs +f2fs-tools +fatresize +fenrir +foot-terminfo +fsarchiver +git +gpart +gpm +gptfdisk +grub +hdparm +hyperv +intel-ucode +irssi +iw +iwd +jfsutils +kitty-terminfo +ldns +less +lftp +libfido2 +libusb-compat +linux +linux-atm +linux-firmware +linux-firmware-marvell +lsscsi +lvm2 +lynx +magic-wormhole +man-db +man-pages +mc +mdadm +memtest86+ +memtest86+-efi +mkinitcpio +mkinitcpio-archiso +mkinitcpio-nfs-utils +mmc-utils +modemmanager +mtools +nano +nbd +ndisc6 +networkmanager +nfs-utils +nilfs-utils +nmap +ntfs-3g +nvme-cli +open-iscsi +open-vm-tools +openconnect +openpgp-card-tools +openssh +openvpn +partclone +parted +partimage +pcsclite +pipewire +pipewire-alsa +pipewire-jack +pipewire-pulse +poppler +ppp +pptpclient +pv +python-dbus +python-pyenchant +python-pyte +python-pyperclip +qemu-guest-agent +realtime-privileges +refind +reflector +rhvoice-voice-bdl +rsync +rxvt-unicode-terminfo +screen +speech-dispatcher +sdparm +sequoia-sq +sg3_utils +smartmontools +socat +sof-firmware +sox +squashfs-tools +sudo +syslinux +systemd-resolvconf +tcpdump +terminus-font +testdisk +tmux +tpm2-tools +tpm2-tss +udftools +usb_modeswitch +usbmuxd +usbutils +vim +virtualbox-guest-utils-nox +vpnc +w3m-git +wget +wireless-regdb +wireless_tools +wireplumber +wpa_supplicant +wvdial +xdg-user-dirs +xdg-utils +xfsprogs +xl2tpd +yay diff --git a/x86_64/pacman.conf b/x86_64/pacman.conf new file mode 100644 index 0000000..00fe68e --- /dev/null +++ b/x86_64/pacman.conf @@ -0,0 +1,104 @@ +# +# /etc/pacman.conf +# +# See the pacman.conf(5) manpage for option and repository directives + +# +# GENERAL OPTIONS +# +[options] +# The following paths are commented out with their default values listed. +# If you wish to use different paths, uncomment and update the paths. +#RootDir = / +#DBPath = /var/lib/pacman/ +#CacheDir = /var/cache/pacman/pkg/ +#LogFile = /var/log/pacman.log +#GPGDir = /etc/pacman.d/gnupg/ +#HookDir = /etc/pacman.d/hooks/ +HoldPkg = pacman glibc +#XferCommand = /usr/bin/curl -L -C - -f -o %o %u +#XferCommand = /usr/bin/wget --passive-ftp -c -O %o %u +#CleanMethod = KeepInstalled +Architecture = auto + +# Pacman won't upgrade packages listed in IgnorePkg and members of IgnoreGroup +#IgnorePkg = +#IgnoreGroup = + +#NoUpgrade = +#NoExtract = + +# Misc options +#UseSyslog +#Color +#NoProgressBar +# We cannot check disk space from within a chroot environment +#CheckSpace +#VerbosePkgLists +ParallelDownloads = 5 +#DownloadUser = alpm +#DisableSandbox + +# By default, pacman accepts packages signed by keys that its local keyring +# trusts (see pacman-key and its man page), as well as unsigned packages. +SigLevel = Required DatabaseOptional +LocalFileSigLevel = Optional +#RemoteFileSigLevel = Required + +# NOTE: You must run `pacman-key --init` before first using pacman; the local +# keyring can then be populated with the keys of all official Arch Linux +# packagers with `pacman-key --populate archlinux`. + +# +# REPOSITORIES +# - can be defined here or included from another file +# - pacman will search repositories in the order defined here +# - local/custom mirrors can be added here or in separate files +# - repositories listed first will take precedence when packages +# have identical names, regardless of version number +# - URLs will have $repo replaced by the name of the current repo +# - URLs will have $arch replaced by the name of the architecture +# +# Repository entries are of the format: +# [repo-name] +# Server = ServerName +# Include = IncludePath +# +# The header [repo-name] is crucial - it must be present and +# uncommented to enable the repo. +# + +# The testing repositories are disabled by default. To enable, uncomment the +# repo name header and Include lines. You can add preferred servers immediately +# after the header, and they will be used before the default mirrors. + +#[core-testing] +#Include = /etc/pacman.d/mirrorlist + +[stormux] +SigLevel = Required DatabaseOptional +Server = https://packages.stormux.org/$arch + +[core] +Include = /etc/pacman.d/mirrorlist + +#[extra-testing] +#Include = /etc/pacman.d/mirrorlist + +[extra] +Include = /etc/pacman.d/mirrorlist + +# If you want to run 32 bit applications on your x86_64 system, +# enable the multilib repositories as required here. + +#[multilib-testing] +#Include = /etc/pacman.d/mirrorlist + +#[multilib] +#Include = /etc/pacman.d/mirrorlist + +# An example of a custom package repository. See the pacman manpage for +# tips on creating your own repositories. +#[custom] +#SigLevel = Optional TrustAll +#Server = file:///home/custompkgs diff --git a/x86_64/profiledef.sh b/x86_64/profiledef.sh new file mode 100644 index 0000000..850c390 --- /dev/null +++ b/x86_64/profiledef.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2034 + +iso_name="stormux" +iso_label="STORMUX_X64" +iso_publisher="Stormux " +iso_application="Stormux Accessible Linux Live/Install DVD" +iso_version="$(date --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y.%m.%d)" +install_dir="stormux" +buildmodes=('iso') +bootmodes=('bios.syslinux' + 'uefi.systemd-boot') +arch="x86_64" +pacman_conf="pacman.conf" +airootfs_image_type="squashfs" +airootfs_image_tool_options=('-comp' 'xz' '-Xbcj' 'x86' '-b' '1M' '-Xdict-size' '1M') +bootstrap_tarball_compression=('zstd' '-c' '-T0' '--auto-threads=logical' '--long' '-19') +file_permissions=( + ["/etc/shadow"]="0:0:400" + ["/root"]="0:0:750" + ["/root/.automated_script.sh"]="0:0:755" + ["/root/.gnupg"]="0:0:700" + ["/root/.config"]="0:0:700" + ["/usr/local/bin/choose-mirror"]="0:0:755" + ["/usr/local/bin/Installation_guide"]="0:0:755" + ["/usr/local/bin/livecd-sound"]="0:0:755" + ["/usr/local/bin/configure-stormux"]="0:0:755" + ["/usr/local/bin/stormux-setup.sh"]="0:0:755" + ["/usr/local/bin/init-pipewire-sound.sh"]="0:0:755" + ["/home/stormux"]="1000:1000:755" +) diff --git a/x86_64/qemu-boot.sh b/x86_64/qemu-boot.sh new file mode 100755 index 0000000..327e11a --- /dev/null +++ b/x86_64/qemu-boot.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# +# Copyright 2025, Stormux, +# +# This is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3, or (at your option) any later +# version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this package; see the file COPYING. If not, write to the Free +# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +# +# Boot Stormux x86_64 ISO in QEMU with audio support + +set -e + +# Get the directory where this script is located +scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +outDir="${scriptDir}/out" +logDir="${scriptDir}/logs" + +# Create logs directory if it doesn't exist +mkdir -p "$logDir" + +# Generate timestamp for log files +timestamp=$(date +%Y%m%d_%H%M%S) +qemuLog="${logDir}/qemu-boot_${timestamp}.log" +serialLog="${logDir}/serial-console_${timestamp}.log" + +# Find the most recent ISO in the out directory +if [[ ! -d "$outDir" ]]; then + echo "Error: Output directory not found: $outDir" + echo "Please build an ISO first using: sudo ./build.sh" + exit 1 +fi + +# Find the most recent ISO file +isoFile=$(find "$outDir" -name "*.iso" -type f -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2-) + +if [[ -z "$isoFile" ]]; then + echo "Error: No ISO file found in $outDir" + echo "Please build an ISO first using: sudo ./build.sh" + exit 1 +fi + +echo "Found ISO: $isoFile" +echo "Booting Stormux in QEMU with audio support..." +echo +echo "Logging to:" +echo " QEMU log: $qemuLog" +echo " Serial console: $serialLog" +echo +echo "Note: After boot, press Ctrl+Alt+F2 to switch to tty2" +echo " where the stormux user will be logged in with Fenrir screen reader." +echo " You can monitor the serial console log in real-time with:" +echo " tail -f $serialLog" +echo + +# Export DISPLAY for console usage +export DISPLAY="${DISPLAY:-:0}" + +# Boot QEMU with audio support and logging +# -D: QEMU debug log (shows QEMU errors, device issues) +# -d: Debug categories (guest_errors, cpu_reset) +# -serial: Capture guest serial console (systemd messages, boot logs) +# -audiodev: PulseAudio backend for audio +# -device intel-hda: Intel HD Audio controller +# -device hda-duplex: HD Audio codec with input/output, connected to audiodev +exec qemu-system-x86_64 \ + -enable-kvm \ + -m 2048 \ + -cdrom "$isoFile" \ + -boot d \ + -audiodev pa,id=snd0 \ + -device intel-hda \ + -device hda-duplex,audiodev=snd0 \ + -serial file:"$serialLog" \ + -D "$qemuLog" \ + -d guest_errors,cpu_reset \ + -display gtk diff --git a/x86_64/setup-fenrir-audio-configs.sh b/x86_64/setup-fenrir-audio-configs.sh new file mode 100755 index 0000000..995527b --- /dev/null +++ b/x86_64/setup-fenrir-audio-configs.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Setup Fenrir audio configurations for the live ISO + +set -e + +# Create directories +mkdir -p airootfs/etc/skel/.config/pipewire/pipewire-pulse.conf.d +mkdir -p airootfs/etc/skel/.config/wireplumber/main.conf.d +mkdir -p airootfs/etc/skel/.config/wireplumber/bluetooth.conf.d +mkdir -p airootfs/root/.config/pulse + +# Copy user configs to skel +cp 50-fenrir-console.conf airootfs/etc/skel/.config/pipewire/pipewire-pulse.conf.d/ +cp 50-fenrir-no-suspend.conf airootfs/etc/skel/.config/wireplumber/main.conf.d/ +cp 50-fenrir-bluez.conf airootfs/etc/skel/.config/wireplumber/bluetooth.conf.d/ + +# Copy root config +cp client.conf airootfs/root/.config/pulse/ + +echo "Fenrir audio configurations installed successfully!" +echo "" +echo "Created:" +echo " - User pipewire-pulse config (50-fenrir-console.conf)" +echo " - User wireplumber suspend config (50-fenrir-no-suspend.conf)" +echo " - User wireplumber bluetooth config (50-fenrir-bluez.conf)" +echo " - Root pulse client config (client.conf)" diff --git a/x86_64/stormux_repo.pub b/x86_64/stormux_repo.pub new file mode 100644 index 0000000..28edfe6 --- /dev/null +++ b/x86_64/stormux_repo.pub @@ -0,0 +1,29 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGhTtIcBEADTCqY/+das1n52HTTlp3Uf5+ER5r0fNA2HgJq4GzEeFBdqdgSF +y05P9B94d4JWtWvt0ckjsNfku19O9IquKiYP5x9/sYT8RGeRUrTlN9qlCrytTsdM +a8ij9esGQOXDeyRJQTs5igbFWQ26MrJpOkmO8xMXGmfD29yOppRLzVS8Jed0mokk +aEajui3QSWcefDnFMBYNaMF04iuWgVxv/7102adh3u3+V632+fQQhFtu+x92iosU +GtWlGGe3UkEhWJIb6wf/VCYIYQAjxTQBLul2WByXqwCXmobzc7DJbte3FSEPvPis +nJiUmT0fsIIC9c1UbTJt5Gqb6o/oI61tEJEeXieBViXFE7HwBI7FUxaIXoozgiYA +BxaLHQT0Qv9k1SXbBm82xNydjqFHIAolYSp5s1wRIhErlOaG9w3+Tb8zFSekPsbg +cgS/b3PpWmnigXBJQ94Ee+6KoPMVrwEKIu/IHexRUVAm+66Xs55ccgMY+MUupIjX +Bdn9jCYB8NwpC3UNWoSv2oQ7F2hVbXZYC/dx7gKmyvzfrw5MLOX7yEoSGlJJp2iK +fLLP8nw+x7LnDFEnAjPjtmeH7e+JANH1scP/7YMKDszO1THSK3hBAK1aeq0aEFs8 +9AO++xZrNbCrF2MnqcGaXs/753k6kf8zyrW6t7A1opxjPHIz3WrQ8SbXXwARAQAB +tExTdG9ybXV4IChGb3Igc2lnbmluZyBzdG9ybXV4IHJlcG9zaXRvcnkgcGFja2Fn +ZXMpIDxzdG9ybV9kcmFnb25Ac3Rvcm11eC5vcmc+iQJOBBMBCgA4FiEEUq2kkADx +/wRW+K7vtM3hzVbvjoIFAmhTtIcCGwMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AA +CgkQtM3hzVbvjoLiLQ/9HJzlwr2JNGdR/LaM1Y6VuhASGDZsUsi5XLJ7qKUKN/eA +kq0LMO7TzHO++otsEolodMr8fTkthQTPqSnbLZsW9Y3wK9U2vajryblRSEBaY6VB +1ds0pAg5SXz7O2eI2Ne05dkYB176G72KcjrOiSanKjI9sqh6pdTHcQM3XbRxxEVn +or7fKbLs/eveFWRPW3EAwuVnWI1aw3Xw9BQ3TE1eEDPvBh2GVKZ6qskbzdwfZtUB +kxAa30M+ftlNDaV7d6mNfREefEJfZjYFrY2XiqTK3+w1Q1ZaugK0Bbd/YdAoGMdt +6gbd2h/IZtxduWW/HhvWX2wtKqn4fNxAyYcY3aWm8kuq0pXlyEZ96gZas6a2lu+f ++yLvIh3UtC1IimFMDMQnKCTbWgdhd0HMnG9ULnoOroXVGltnBQwlO0hrHSVD+qmy +NVPUxkzS3ZhqSmoW2G8hSvXpMjt/w5fhC0FJhFb/uM/7LqJWSl5aAkUHupN3t/nG +9o7chBQzzXwbpiS6DQuIt6ouyPpwNjeELFPNBjZDf0auVWj6ZZ5+B/bmyHXQ4wFS +bYKZdO9VeXhBxRUMfGpIKvIw/Lnc+1Toan1RTWoUoRH+HuK2D/LAuvsam36gtLrQ +XV3//7Mh4oQ0Zg4REG3SEfG9i3rav8wMtRmaJaSbwmMVpC6mT/uiEzCSgQhO/0c= +=HIM9 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/x86_64/syslinux/archiso_head.cfg b/x86_64/syslinux/archiso_head.cfg new file mode 100644 index 0000000..671ab4e --- /dev/null +++ b/x86_64/syslinux/archiso_head.cfg @@ -0,0 +1,28 @@ +SERIAL 0 115200 +UI vesamenu.c32 +MENU TITLE Arch Linux +MENU BACKGROUND splash.png + +MENU WIDTH 78 +MENU MARGIN 4 +MENU ROWS 7 +MENU VSHIFT 10 +MENU TABMSGROW 14 +MENU CMDLINEROW 14 +MENU HELPMSGROW 16 +MENU HELPMSGENDROW 29 + +# Refer to https://wiki.syslinux.org/wiki/index.php/Comboot/menu.c32 + +MENU COLOR border 30;44 #40ffffff #a0000000 std +MENU COLOR title 1;36;44 #9033ccff #a0000000 std +MENU COLOR sel 7;37;40 #e0ffffff #20ffffff all +MENU COLOR unsel 37;44 #50ffffff #a0000000 std +MENU COLOR help 37;40 #c0ffffff #a0000000 std +MENU COLOR timeout_msg 37;40 #80ffffff #00000000 std +MENU COLOR timeout 1;37;40 #c0ffffff #00000000 std +MENU COLOR msg07 37;40 #90ffffff #a0000000 std +MENU COLOR tabmsg 31;40 #30ffffff #00000000 std + +MENU CLEAR +MENU IMMEDIATE diff --git a/x86_64/syslinux/archiso_pxe-linux.cfg b/x86_64/syslinux/archiso_pxe-linux.cfg new file mode 100644 index 0000000..872aae8 --- /dev/null +++ b/x86_64/syslinux/archiso_pxe-linux.cfg @@ -0,0 +1,32 @@ +LABEL arch_nbd +TEXT HELP +Boot the Arch Linux install medium using NBD. +It allows you to install Arch Linux or perform system maintenance. +ENDTEXT +MENU LABEL Arch Linux install medium (%ARCH%, NBD) +LINUX ::/%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux +INITRD ::/%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img +APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% archiso_nbd_srv=${pxeserver} cms_verify=y +SYSAPPEND 3 + +LABEL arch_nfs +TEXT HELP +Boot the Arch Linux live medium using NFS. +It allows you to install Arch Linux or perform system maintenance. +ENDTEXT +MENU LABEL Arch Linux install medium (%ARCH%, NFS) +LINUX ::/%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux +INITRD ::/%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img +APPEND archisobasedir=%INSTALL_DIR% archiso_nfs_srv=${pxeserver}:/run/archiso/bootmnt cms_verify=y +SYSAPPEND 3 + +LABEL arch_http +TEXT HELP +Boot the Arch Linux live medium using HTTP. +It allows you to install Arch Linux or perform system maintenance. +ENDTEXT +MENU LABEL Arch Linux install medium (%ARCH%, HTTP) +LINUX ::/%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux +INITRD ::/%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img +APPEND archisobasedir=%INSTALL_DIR% archiso_http_srv=http://${pxeserver}/ cms_verify=y +SYSAPPEND 3 diff --git a/x86_64/syslinux/archiso_pxe.cfg b/x86_64/syslinux/archiso_pxe.cfg new file mode 100644 index 0000000..b4c9a80 --- /dev/null +++ b/x86_64/syslinux/archiso_pxe.cfg @@ -0,0 +1,5 @@ +INCLUDE archiso_head.cfg + +INCLUDE archiso_pxe-linux.cfg + +INCLUDE archiso_tail.cfg diff --git a/x86_64/syslinux/archiso_sys-linux.cfg b/x86_64/syslinux/archiso_sys-linux.cfg new file mode 100644 index 0000000..c85a142 --- /dev/null +++ b/x86_64/syslinux/archiso_sys-linux.cfg @@ -0,0 +1,9 @@ +LABEL stormux +TEXT HELP +Boot the Stormux accessible Linux live system on BIOS. +Fenrir screen reader starts automatically for accessibility. +ENDTEXT +MENU LABEL Stormux Accessible Linux (%ARCH%, BIOS) +LINUX /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux +INITRD /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img +APPEND archisobasedir=%INSTALL_DIR% archisolabel=%ARCHISO_LABEL% accessibility=on console=tty0 console=ttyS0,115200 diff --git a/x86_64/syslinux/archiso_sys.cfg b/x86_64/syslinux/archiso_sys.cfg new file mode 100644 index 0000000..a300885 --- /dev/null +++ b/x86_64/syslinux/archiso_sys.cfg @@ -0,0 +1,8 @@ +INCLUDE archiso_head.cfg + +DEFAULT stormux +TIMEOUT 150 + +INCLUDE archiso_sys-linux.cfg + +INCLUDE archiso_tail.cfg diff --git a/x86_64/syslinux/archiso_tail.cfg b/x86_64/syslinux/archiso_tail.cfg new file mode 100644 index 0000000..e84897c --- /dev/null +++ b/x86_64/syslinux/archiso_tail.cfg @@ -0,0 +1,35 @@ +LABEL existing +TEXT HELP +Boot an existing operating system. +Press TAB to edit the disk and partition number to boot. +ENDTEXT +MENU LABEL Boot existing OS +COM32 chain.c32 +APPEND hd0 0 + +# https://www.memtest.org/ +LABEL memtest +MENU LABEL Run Memtest86+ (RAM test) +LINUX /boot/memtest86+/memtest + +# https://wiki.syslinux.org/wiki/index.php/Hdt_(Hardware_Detection_Tool) +LABEL hdt +MENU LABEL Hardware Information (HDT) +COM32 hdt.c32 +APPEND modules_alias=hdt/modalias.gz pciids=hdt/pciids.gz + +LABEL reboot +TEXT HELP +Reboot computer. +The computer's firmware must support APM. +ENDTEXT +MENU LABEL Reboot +COM32 reboot.c32 + +LABEL poweroff +TEXT HELP +Power off computer. +The computer's firmware must support APM. +ENDTEXT +MENU LABEL Power Off +COM32 poweroff.c32 diff --git a/x86_64/syslinux/splash.png b/x86_64/syslinux/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..64b959a61efb767a58d484601f122f6d4ead5a8a GIT binary patch literal 45400 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