diff --git a/x86_64/airootfs/etc/profile.d/stormux-accessibility.sh b/x86_64/airootfs/etc/profile.d/stormux-accessibility.sh new file mode 100755 index 0000000..acaffb9 --- /dev/null +++ b/x86_64/airootfs/etc/profile.d/stormux-accessibility.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# Stormux accessibility environment variables +# Available to all users and sessions + +# Accessibility support +export ACCESSIBILITY_ENABLED=1 +export GTK_MODULES=gail:atk-bridge:canberra-gtk-module +export GNOME_ACCESSIBILITY=1 +export QT_ACCESSIBILITY=1 +export QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1 + +# Dialog accessibility options +export DIALOGOPTS='--no-lines --visit-items' diff --git a/x86_64/airootfs/usr/local/bin/install-stormux b/x86_64/airootfs/usr/local/bin/install-stormux new file mode 100755 index 0000000..bdbe941 --- /dev/null +++ b/x86_64/airootfs/usr/local/bin/install-stormux @@ -0,0 +1,1506 @@ +#!/usr/bin/env bash + +# install-stormux: Interactive Arch Linux installer for Stormux +# Supports UEFI/BIOS, multiple partition layouts, and accessibility-first configuration + +set -euo pipefail + +# Global variables +logFile="/tmp/install-stormux.log" +mountPoint="/mnt" +bootMode="" # Will be "uefi" or "bios" +targetDisk="" +homeDisk="" +partitionLayout="" # "single", "separate_home", "separate_disk" +hostname="" +rootPassword="" +declare -a userNames=() +declare -a userPasswords=() +declare -a userIsAdmin=() +desktopEnvironment="" # "none", "i3", "mate" +timezone="" + + +# +# Logging functions +# + +log() { + echo "$*" | tee -a "$logFile" +} + +log_error() { + echo "ERROR: $*" | tee -a "$logFile" >&2 +} + +log_info() { + echo "INFO: $*" | tee -a "$logFile" +} + +# +# Audio feedback functions +# + +play_sound() { + local soundType="$1" + case "$soundType" in + error) + play -qV0 "|sox -n -p synth saw E2 fade 0 0.25 0.05" "|sox -n -p synth saw E2 fade 0 0.25 0.05" norm -15 2>/dev/null || true + ;; + question) + 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 2>/dev/null || true + ;; + *) + ;; + esac +} + +speak() { + if command -v espeak-ng >/dev/null 2>&1; then + espeak-ng "$*" 2>/dev/null || true + fi +} + +# +# Pre-flight checks +# + +check_root() { + log_info "Checking for root privileges..." + if [[ $EUID -ne 0 ]]; then + log_error "This script must be run as root" + echo "ERROR: This script must be run as root. Please use: sudo install-stormux" + play_sound error + exit 1 + fi + log_info "Root check passed" +} + +detect_boot_mode() { + log_info "Detecting boot mode..." + if [[ -d /sys/firmware/efi ]]; then + bootMode="uefi" + log_info "Detected UEFI boot mode" + echo "Detected UEFI boot mode" + else + bootMode="bios" + log_info "Detected BIOS boot mode" + echo "Detected BIOS boot mode" + fi +} + +check_internet() { + log_info "Checking internet connectivity..." + echo "Checking internet connectivity..." + if ping -c 1 -W 5 archlinux.org >/dev/null 2>&1; then + log_info "Internet connection verified" + echo "Internet connection: OK" + return 0 + else + log_error "No internet connection detected" + echo "ERROR: No internet connection detected." + echo "Please configure network (use 'nmtui' for NetworkManager) and try again." + play_sound error + return 1 + fi +} + +check_required_tools() { + log_info "Checking for required tools..." + local tools=(parted mkfs.vfat mkfs.ext4 pacstrap arch-chroot genfstab lsblk) + local missing=() + + for tool in "${tools[@]}"; do + if ! command -v "$tool" >/dev/null 2>&1; then + missing+=("$tool") + fi + done + + if [[ ${#missing[@]} -gt 0 ]]; then + log_error "Missing required tools: ${missing[*]}" + echo "ERROR: Missing required tools: ${missing[*]}" + play_sound error + return 1 + fi + + log_info "All required tools present" + echo "All required tools: OK" + return 0 +} + +preflight_checks() { + echo "=== Stormux Installer - Pre-flight Checks ===" + echo "" + + check_root + detect_boot_mode + check_required_tools + + if ! check_internet; then + return 1 + fi + + echo "" + echo "All pre-flight checks passed!" + echo "" + return 0 +} + +# +# Disk selection +# + +list_available_disks() { + # List disks excluding loop devices, the current root device, and ISO + lsblk -dno NAME,SIZE,TYPE | grep -E "^[^l].*disk$" | grep -v "$(df / | tail -1 | cut -d' ' -f1 | sed 's|/dev/||;s|[0-9]*$||')" || true +} + +select_disk() { + local diskVar="$1" + local prompt="$2" + + echo "$prompt" + echo "" + + mapfile -t disks < <(list_available_disks | awk '{print $1}') + + if [[ ${#disks[@]} -eq 0 ]]; then + log_error "No suitable disks found for installation" + echo "ERROR: No suitable disks found for installation" + play_sound error + return 1 + fi + + # Display disk information + echo "Available disks:" + lsblk -dno NAME,SIZE,TYPE,MODEL | grep -E "^($(IFS='|'; echo "${disks[*]}")).*disk" || true + echo "" + + play_sound question + PS3="Enter disk number: " + select diskChoice in "${disks[@]}" "Cancel"; do + if [[ "$diskChoice" == "Cancel" ]]; then + log_info "Disk selection cancelled by user" + return 1 + elif [[ -n "$diskChoice" ]]; then + eval "$diskVar=\"/dev/$diskChoice\"" + log_info "Selected disk: /dev/$diskChoice" + echo "Selected: /dev/$diskChoice" + return 0 + fi + done +} + +confirm_disk_destruction() { + local disk="$1" + echo "" + echo "WARNING: ALL DATA ON $disk WILL BE DESTROYED!" + echo "" + + # Show current partition layout + echo "Current partition layout:" + lsblk "$disk" || true + echo "" + + play_sound error + echo "Type 'YES' (in capital letters) to confirm destruction of $disk:" + read -r confirmation + + if [[ "$confirmation" == "YES" ]]; then + log_info "User confirmed destruction of $disk" + return 0 + else + log_info "Disk destruction not confirmed" + echo "Operation cancelled." + return 1 + fi +} + +# +# Partition layout selection +# + +select_partition_layout() { + echo "" + echo "=== Partition Layout Selection ===" + echo "" + echo "Choose your partition layout:" + echo "" + + local layouts=( + "Single partition (root only, /home in root partition)" + "Separate home partition (root + /home on same disk)" + "Separate home disk (root on one disk, /home on another)" + "Cancel" + ) + + play_sound question + PS3="Enter layout number: " + select layout in "${layouts[@]}"; do + case "$layout" in + "Single partition"*) + partitionLayout="single" + log_info "Selected partition layout: single" + return 0 + ;; + "Separate home partition"*) + partitionLayout="separate_home" + log_info "Selected partition layout: separate_home" + return 0 + ;; + "Separate home disk"*) + partitionLayout="separate_disk" + log_info "Selected partition layout: separate_disk" + return 0 + ;; + "Cancel") + return 1 + ;; + esac + done +} + +# +# Partitioning functions +# + +partition_disk_uefi_single() { + local disk="$1" + log_info "Creating UEFI single partition layout on $disk" + + # Wipe existing partition table + wipefs -af "$disk" + + # Create GPT partition table + parted -s "$disk" mklabel gpt + + # Create EFI partition (1GB) + parted -s "$disk" mkpart primary fat32 1MiB 1025MiB + parted -s "$disk" set 1 esp on + + # Create root partition (rest of disk) + parted -s "$disk" mkpart primary ext4 1025MiB 100% + + log_info "Partition table created successfully" +} + +partition_disk_uefi_separate_home() { + local disk="$1" + log_info "Creating UEFI separate home partition layout on $disk" + + # Get disk size in GB for calculating root partition size + local diskSizeGB + diskSizeGB=$(lsblk -dno SIZE "$disk" | grep -oE '[0-9]+' | head -1) + + # Calculate root partition size (30GB or 40% of disk, whichever is larger) + local rootSizeGB=30 + local fortyPercent=$((diskSizeGB * 40 / 100)) + if [[ $fortyPercent -gt $rootSizeGB ]]; then + rootSizeGB=$fortyPercent + fi + + # Wipe existing partition table + wipefs -af "$disk" + + # Create GPT partition table + parted -s "$disk" mklabel gpt + + # Create EFI partition (1GB) + parted -s "$disk" mkpart primary fat32 1MiB 1025MiB + parted -s "$disk" set 1 esp on + + # Create root partition + local rootEndMiB=$((1025 + rootSizeGB * 1024)) + parted -s "$disk" mkpart primary ext4 1025MiB "${rootEndMiB}MiB" + + # Create home partition (rest of disk) + parted -s "$disk" mkpart primary ext4 "${rootEndMiB}MiB" 100% + + log_info "Partition table created successfully" +} + +partition_disk_uefi_root_only() { + local disk="$1" + log_info "Creating UEFI root-only partition layout on $disk (home on separate disk)" + + # Wipe existing partition table + wipefs -af "$disk" + + # Create GPT partition table + parted -s "$disk" mklabel gpt + + # Create EFI partition (1GB) + parted -s "$disk" mkpart primary fat32 1MiB 1025MiB + parted -s "$disk" set 1 esp on + + # Create root partition (rest of disk) + parted -s "$disk" mkpart primary ext4 1025MiB 100% + + log_info "Partition table created successfully" +} + +partition_disk_uefi_home_only() { + local disk="$1" + log_info "Creating home partition on $disk" + + # Wipe existing partition table + wipefs -af "$disk" + + # Create GPT partition table + parted -s "$disk" mklabel gpt + + # Create single partition for home + parted -s "$disk" mkpart primary ext4 1MiB 100% + + log_info "Home partition created successfully" +} + +partition_disk_bios_single() { + local disk="$1" + log_info "Creating BIOS single partition layout on $disk" + + # Wipe existing partition table + wipefs -af "$disk" + + # Create MBR partition table + parted -s "$disk" mklabel msdos + + # Create root partition (entire disk) + parted -s "$disk" mkpart primary ext4 1MiB 100% + parted -s "$disk" set 1 boot on + + log_info "Partition table created successfully" +} + +partition_disk_bios_separate_home() { + local disk="$1" + log_info "Creating BIOS separate home partition layout on $disk" + + # Get disk size in GB + local diskSizeGB + diskSizeGB=$(lsblk -dno SIZE "$disk" | grep -oE '[0-9]+' | head -1) + + # Calculate root partition size (30GB or 40% of disk, whichever is larger) + local rootSizeGB=30 + local fortyPercent=$((diskSizeGB * 40 / 100)) + if [[ $fortyPercent -gt $rootSizeGB ]]; then + rootSizeGB=$fortyPercent + fi + + # Wipe existing partition table + wipefs -af "$disk" + + # Create MBR partition table + parted -s "$disk" mklabel msdos + + # Create root partition + local rootEndMiB=$((1 + rootSizeGB * 1024)) + parted -s "$disk" mkpart primary ext4 1MiB "${rootEndMiB}MiB" + parted -s "$disk" set 1 boot on + + # Create home partition (rest of disk) + parted -s "$disk" mkpart primary ext4 "${rootEndMiB}MiB" 100% + + log_info "Partition table created successfully" +} + +partition_disk_bios_root_only() { + local disk="$1" + log_info "Creating BIOS root-only partition layout on $disk" + + # Wipe existing partition table + wipefs -af "$disk" + + # Create MBR partition table + parted -s "$disk" mklabel msdos + + # Create root partition (entire disk) + parted -s "$disk" mkpart primary ext4 1MiB 100% + parted -s "$disk" set 1 boot on + + log_info "Partition table created successfully" +} + +partition_disk_bios_home_only() { + local disk="$1" + log_info "Creating home partition on $disk (BIOS)" + + # Wipe existing partition table + wipefs -af "$disk" + + # Create MBR partition table + parted -s "$disk" mklabel msdos + + # Create single partition for home + parted -s "$disk" mkpart primary ext4 1MiB 100% + + log_info "Home partition created successfully" +} + +partition_disks() { + echo "" + echo "=== Partitioning Disks ===" + echo "" + + # Wait for kernel to update device nodes + sleep 2 + partprobe "$targetDisk" 2>/dev/null || true + sleep 1 + + case "$partitionLayout" in + single) + if [[ "$bootMode" == "uefi" ]]; then + partition_disk_uefi_single "$targetDisk" + else + partition_disk_bios_single "$targetDisk" + fi + ;; + separate_home) + if [[ "$bootMode" == "uefi" ]]; then + partition_disk_uefi_separate_home "$targetDisk" + else + partition_disk_bios_separate_home "$targetDisk" + fi + ;; + separate_disk) + if [[ "$bootMode" == "uefi" ]]; then + partition_disk_uefi_root_only "$targetDisk" + partition_disk_uefi_home_only "$homeDisk" + else + partition_disk_bios_root_only "$targetDisk" + partition_disk_bios_home_only "$homeDisk" + fi + ;; + esac + + # Wait for kernel to re-read partition table + sleep 2 + partprobe "$targetDisk" 2>/dev/null || true + if [[ -n "$homeDisk" ]]; then + partprobe "$homeDisk" 2>/dev/null || true + fi + sleep 2 + + echo "Partitioning complete!" + log_info "Partitioning complete" +} + +# +# Filesystem creation and mounting +# + +get_partition_device() { + local disk="$1" + local partNum="$2" + + # Handle different naming schemes (nvme0n1p1, mmcblk0p1, sda1) + if [[ "$disk" =~ (nvme|mmcblk) ]]; then + echo "${disk}p${partNum}" + else + echo "${disk}${partNum}" + fi +} + +create_filesystems() { + echo "" + echo "=== Creating Filesystems ===" + echo "" + + local rootPart + local homePart + local efiPart + + if [[ "$bootMode" == "uefi" ]]; then + efiPart=$(get_partition_device "$targetDisk" 1) + rootPart=$(get_partition_device "$targetDisk" 2) + + log_info "Creating EFI filesystem on $efiPart" + echo "Creating EFI filesystem..." + mkfs.vfat -F32 "$efiPart" + else + rootPart=$(get_partition_device "$targetDisk" 1) + fi + + log_info "Creating root filesystem on $rootPart" + echo "Creating root filesystem..." + mkfs.ext4 -F "$rootPart" + + case "$partitionLayout" in + separate_home) + homePart=$(get_partition_device "$targetDisk" 3) + log_info "Creating home filesystem on $homePart" + echo "Creating home filesystem..." + mkfs.ext4 -F "$homePart" + ;; + separate_disk) + homePart=$(get_partition_device "$homeDisk" 1) + log_info "Creating home filesystem on $homePart" + echo "Creating home filesystem..." + mkfs.ext4 -F "$homePart" + ;; + esac + + echo "Filesystems created!" + log_info "Filesystems created successfully" +} + +mount_filesystems() { + echo "" + echo "=== Mounting Filesystems ===" + echo "" + + local rootPart + local homePart + local efiPart + + # Determine partition devices + if [[ "$bootMode" == "uefi" ]]; then + efiPart=$(get_partition_device "$targetDisk" 1) + rootPart=$(get_partition_device "$targetDisk" 2) + else + rootPart=$(get_partition_device "$targetDisk" 1) + fi + + # Mount root + log_info "Mounting root partition $rootPart to $mountPoint" + echo "Mounting root partition..." + mkdir -p "$mountPoint" + mount "$rootPart" "$mountPoint" + + # Mount EFI if UEFI + if [[ "$bootMode" == "uefi" ]]; then + log_info "Mounting EFI partition $efiPart to $mountPoint/boot" + echo "Mounting EFI partition..." + mkdir -p "$mountPoint/boot" + mount "$efiPart" "$mountPoint/boot" + fi + + # Mount home if separate + case "$partitionLayout" in + separate_home) + homePart=$(get_partition_device "$targetDisk" 3) + log_info "Mounting home partition $homePart to $mountPoint/home" + echo "Mounting home partition..." + mkdir -p "$mountPoint/home" + mount "$homePart" "$mountPoint/home" + ;; + separate_disk) + homePart=$(get_partition_device "$homeDisk" 1) + log_info "Mounting home partition $homePart to $mountPoint/home" + echo "Mounting home partition..." + mkdir -p "$mountPoint/home" + mount "$homePart" "$mountPoint/home" + ;; + esac + + echo "Filesystems mounted!" + log_info "Filesystems mounted successfully" +} + +# +# System information gathering +# + +gather_system_info() { + echo "" + echo "=== System Configuration ===" + echo "" + + # Hostname + play_sound question + read -rp "Enter hostname for the new system: " hostname + log_info "Hostname set to: $hostname" + + # Root password + echo "" + while true; do + play_sound question + read -rsp "Enter root password: " rootPassword + echo "" + read -rsp "Confirm root password: " rootPasswordConfirm + echo "" + + if [[ "$rootPassword" == "$rootPasswordConfirm" ]]; then + log_info "Root password set" + break + else + echo "Passwords do not match. Please try again." + play_sound error + fi + done + + # User accounts (loop until empty username) + echo "" + echo "=== User Account Setup ===" + echo "Press Enter without typing a username when done adding users." + echo "" + + local userCount=0 + while true; do + play_sound question + read -rp "Enter username (or press Enter to finish): " currentUser + + # If empty, break out of loop + if [[ -z "$currentUser" ]]; then + if [[ $userCount -eq 0 ]]; then + echo "ERROR: You must create at least one user account." + play_sound error + continue + else + log_info "Finished adding users. Total: $userCount" + break + fi + fi + + # Check if username already exists in array + local userExists=false + for existingUser in "${userNames[@]}"; do + if [[ "$existingUser" == "$currentUser" ]]; then + echo "ERROR: Username '$currentUser' already added." + play_sound error + userExists=true + break + fi + done + + if [[ "$userExists" == true ]]; then + continue + fi + + # Get password for this user + local currentPassword + local currentPasswordConfirm + while true; do + read -rsp "Enter password for $currentUser: " currentPassword + echo "" + read -rsp "Confirm password for $currentUser: " currentPasswordConfirm + echo "" + + if [[ "$currentPassword" == "$currentPasswordConfirm" ]]; then + break + else + echo "Passwords do not match. Please try again." + play_sound error + fi + done + + # Ask if user should have root privileges (wheel group) + local isAdmin + read -rp "Should $currentUser have root privileges (sudo access)? [y/N]: " isAdmin + if [[ "$isAdmin" =~ ^[Yy]$ ]]; then + isAdmin="yes" + echo "$currentUser will have sudo access." + log_info "User $currentUser will be added to wheel group" + else + isAdmin="no" + echo "$currentUser will NOT have sudo access." + log_info "User $currentUser will be a standard user" + fi + + # Add to arrays + userNames+=("$currentUser") + userPasswords+=("$currentPassword") + userIsAdmin+=("$isAdmin") + + userCount=$((userCount + 1)) + log_info "Added user: $currentUser" + echo "" + done + + echo "" + echo "Users to be created: ${userNames[*]}" + echo "" + + # Desktop environment + echo "Select desktop environment:" + local desktops=("Console only (Fenrir screen reader)" "i3 (tiling window manager)" "MATE (traditional desktop)") + + play_sound question + PS3="Enter desktop number: " + select desktop in "${desktops[@]}"; do + case "$desktop" in + "Console only"*) + desktopEnvironment="none" + log_info "Desktop environment: none" + break + ;; + "i3"*) + desktopEnvironment="i3" + log_info "Desktop environment: i3" + break + ;; + "MATE"*) + desktopEnvironment="mate" + log_info "Desktop environment: mate" + break + ;; + esac + done + + # Timezone detection + echo "" + + # Try to get current timezone from timedatectl + if command -v timedatectl >/dev/null 2>&1; then + currentTimezone=$(timedatectl show --property=Timezone --value 2>/dev/null || true) + if [[ -n "$currentTimezone" && "$currentTimezone" != "n/a" ]]; then + echo "Detected timezone: $currentTimezone" + read -rp "Press Enter to use this timezone, or type a new one (e.g., America/New_York): " timezoneInput + if [[ -n "$timezoneInput" ]]; then + timezone="$timezoneInput" + else + timezone="$currentTimezone" + fi + else + read -rp "Enter timezone (e.g., America/New_York): " timezone + fi + else + read -rp "Enter timezone (e.g., America/New_York): " timezone + fi + + log_info "Timezone set to: $timezone" + + echo "" + echo "Configuration summary:" + echo " Hostname: $hostname" + echo " Users: ${userNames[*]}" + echo " Desktop: $desktopEnvironment" + echo " Timezone: $timezone" + echo "" + + play_sound question + read -rp "Press Enter to continue with installation..." +} + +# +# Base system installation +# + +install_base_system() { + echo "" + echo "=== Installing Base System ===" + echo "" + + # Copy pacman config with Stormux repo + log_info "Configuring pacman with Stormux repository" + echo "Configuring package manager..." + + # Copy pacman.conf from live system + if [[ -f /etc/pacman.conf ]]; then + mkdir -p "$mountPoint/etc" + cp /etc/pacman.conf "$mountPoint/etc/pacman.conf" + fi + + # Copy Stormux repo GPG key + if [[ -f /usr/share/pacman/keyrings/stormux.gpg ]]; then + mkdir -p "$mountPoint/usr/share/pacman/keyrings" + cp /usr/share/pacman/keyrings/stormux* "$mountPoint/usr/share/pacman/keyrings/" + fi + + # Define package groups + local basePackages=( + base base-devel linux linux-firmware + networkmanager sudo dialog git rsync wget + ) + + local audioPackages=( + pipewire pipewire-alsa pipewire-pulse pipewire-jack + wireplumber alsa-utils sox + bluez bluez-utils + ) + + local accessibilityPackages=( + espeak-ng rhvoice-voice-bdl speech-dispatcher + fenrir brltty + ) + + local utilityPackages=( + vim nano htop + arch-install-scripts + ) + + # Combine all packages + local allPackages=("${basePackages[@]}" "${audioPackages[@]}" "${accessibilityPackages[@]}" "${utilityPackages[@]}") + + # Add desktop-specific packages + case "$desktopEnvironment" in + i3) + allPackages+=(i3-wm xlibre-server xlibre-input-libinput nodm-dgw xclip) + allPackages+=(clipster discount jq libnotify xfce4-notifyd pamixer playerctl) + allPackages+=(python-i3ipc python-wxpython sox yad) + ;; + mate) + allPackages+=(mate mate-extra orca xlibre-server xlibre-input-libinput lightdm lightdm-gtk-greeter) + ;; + esac + + # Add bootloader packages + if [[ "$bootMode" == "bios" ]]; then + allPackages+=(grub) + fi + + log_info "Installing packages: ${allPackages[*]}" + echo "Installing packages (this may take several minutes)..." + echo "" + + # Run pacstrap + if ! pacstrap "$mountPoint" "${allPackages[@]}"; then + log_error "pacstrap failed" + echo "ERROR: Package installation failed" + play_sound error + return 1 + fi + + # Generate fstab + log_info "Generating fstab" + echo "Generating fstab..." + genfstab -U "$mountPoint" > "$mountPoint/etc/fstab" + + echo "" + echo "Base system installed!" + log_info "Base system installation complete" +} + +# +# Audio configuration +# + +install_audio_configs() { + echo "" + echo "=== Configuring Audio for Accessibility ===" + echo "" + + # System-wide wireplumber configs + log_info "Installing wireplumber system configs" + mkdir -p "$mountPoint/etc/wireplumber/main.lua.d" + + # Analog priority config + cat > "$mountPoint/etc/wireplumber/main.lua.d/50-analog-priority.lua" <<'EOF' +-- Prioritize analog/USB audio devices over HDMI +rule = { + matches = { + { + { "device.name", "matches", "alsa_card.*" }, + }, + }, + apply_properties = { + ["device.priority"] = 1000, + }, +} + +table.insert(alsa_monitor.rules, rule) +EOF + + # HDMI deprioritize config + cat > "$mountPoint/etc/wireplumber/main.lua.d/51-hdmi-deprioritize.lua" <<'EOF' +-- Deprioritize HDMI audio devices +rule = { + matches = { + { + { "device.name", "matches", "alsa_card.*hdmi.*" }, + }, + { + { "device.name", "matches", "alsa_card.*HDMI.*" }, + }, + }, + apply_properties = { + ["device.priority"] = 100, + }, +} + +table.insert(alsa_monitor.rules, rule) +EOF + + # User skeleton pipewire configs + log_info "Installing pipewire user configs to /etc/skel" + + # Fenrir console config + mkdir -p "$mountPoint/etc/skel/.config/pipewire/pipewire-pulse.conf.d" + cat > "$mountPoint/etc/skel/.config/pipewire/pipewire-pulse.conf.d/50-fenrir-console.conf" <<'EOF' +pulse.properties = { + server.address = [ + "unix:native" + "unix:/tmp/pulse.sock" + ] +} + +pulse.rules = [ + { + matches = [ + { + application.name = "~speech-dispatcher*" + } + ] + actions = { + update-props = { + pulse.min.req = 1024/48000 + pulse.min.quantum = 1024/48000 + } + } + } +] +EOF + + # Fenrir no-suspend config + mkdir -p "$mountPoint/etc/skel/.config/wireplumber/main.conf.d" + cat > "$mountPoint/etc/skel/.config/wireplumber/main.conf.d/50-fenrir-no-suspend.conf" <<'EOF' +monitor.alsa.rules = [ + { + matches = [ + { + device.name = "~alsa_card.*" + } + ] + actions = { + update-props = { + session.suspend-timeout-seconds = 0 + } + } + } +] +EOF + + # Fenrir bluetooth config + mkdir -p "$mountPoint/etc/skel/.config/wireplumber/bluetooth.conf.d" + cat > "$mountPoint/etc/skel/.config/wireplumber/bluetooth.conf.d/50-fenrir-bluez.conf" <<'EOF' +monitor.bluez.rules = [ + { + matches = [ + { + device.name = "~bluez_card.*" + } + ] + actions = { + update-props = { + session.suspend-timeout-seconds = 0 + } + } + } +] +EOF + + # Root audio access + log_info "Installing root pulse config" + mkdir -p "$mountPoint/root/.config/pulse" + cat > "$mountPoint/root/.config/pulse/client.conf" <<'EOF' +default-server = unix:/tmp/pulse.sock +autospawn = no +EOF + + echo "Audio configuration complete!" + log_info "Audio configuration installed" +} + +# +# System configuration in chroot +# + +configure_system() { + echo "" + echo "=== Configuring System ===" + echo "" + + log_info "Entering chroot to configure system" + + # Create configuration script to run in chroot + cat > "$mountPoint/tmp/configure.sh" < /etc/locale.gen +locale-gen +echo "LANG=en_US.UTF-8" > /etc/locale.conf + +# Set hostname +echo "${hostname}" > /etc/hostname + +# Configure hosts file +cat > /etc/hosts < /etc/vconsole.conf + +# Configure environment variables for accessibility +cat > /etc/environment </dev/null || true + +# Create users +for i in "\${!userNames[@]}"; do + username="\${userNames[\$i]}" + password="\${userPasswords[\$i]}" + admin="\${userIsAdmin[\$i]}" + + # Determine groups based on admin status + if [[ "\$admin" == "yes" ]]; then + useradd -m -g users -G wheel,realtime,audio,video,network,brlapi -s /bin/bash "\$username" + else + useradd -m -g users -G realtime,audio,video,network,brlapi -s /bin/bash "\$username" + fi + + # Set password + echo "\$username:\$password" | chpasswd + + # Enable linger for user (critical for pipewire) + mkdir -p /var/lib/systemd/linger + touch /var/lib/systemd/linger/\$username + + echo "Created user: \$username" +done + +# Set root password +echo "root:${rootPassword}" | chpasswd + +# Configure sudo +echo '%wheel ALL=(ALL) ALL' > /etc/sudoers.d/wheel +chmod 440 /etc/sudoers.d/wheel + +# Enable pipewire globally +systemctl --global enable pipewire.service pipewire-pulse.service + +# Enable system services +systemctl enable NetworkManager.service +systemctl enable brltty.path +systemctl enable fenrirscreenreader.service + +# Enable bluetooth if present +systemctl enable bluetooth.service 2>/dev/null || true + +# Enable display manager based on desktop environment +if [[ "${desktopEnvironment}" == "i3" ]] || [[ "${desktopEnvironment}" == "mate" ]]; then + # Configure nodm for automatic login + sed -i "s/{user}/\${userNames[0]}/g" /etc/nodm.conf + systemctl enable nodm.service +fi + +# Setup desktop environment configurations +if [[ "${desktopEnvironment}" == "i3" ]]; then + firstUser="\${userNames[0]}" + + # Create git directory for better organization + mkdir -p /home/\$firstUser/git + chown \$firstUser:users /home/\$firstUser/git + + # Clone I38 to ~/git/I38 + echo "Cloning I38 accessibility configuration..." + sudo -u \$firstUser git clone https://git.stormux.org/storm/I38 /home/\$firstUser/git/I38 2>/dev/null || true + + # Run I38 setup to generate accessible i3 configuration + if [[ -d /home/\$firstUser/git/I38 ]]; then + echo "Configuring I38 for accessibility..." + cd /home/\$firstUser/git/I38 || exit 1 + + # Run I38 setup scripts as the user + # -x generates xinitrc and xprofile + # Main script generates i3 config with accessibility features + sudo -u \$firstUser ./i38.sh -x || true + sudo -u \$firstUser ./i38.sh || true + + cd - > /dev/null || exit 1 + fi +elif [[ "${desktopEnvironment}" == "mate" ]]; then + firstUser="\${userNames[0]}" + + # Create .xinitrc for MATE + cat > /home/\$firstUser/.xinitrc <<'XINITRC_MATE_EOF' +#!/bin/sh +# +# ~/.xinitrc +# +# Executed by startx (run your window manager from here) + +dbus-launch +[[ -f ~/.Xresources ]] && xrdb -merge -I\$HOME ~/.Xresources + +if [ -d /etc/X11/xinit/xinitrc.d ]; then + for f in /etc/X11/xinit/xinitrc.d/*; do + [ -x "\$f" ] && . "\$f" + done + unset f +fi + +# Source xprofile if it exists +[[ -f ~/.xprofile ]] && . ~/.xprofile + +exec mate-session +XINITRC_MATE_EOF + chmod 755 /home/\$firstUser/.xinitrc + chown \$firstUser:users /home/\$firstUser/.xinitrc + + # Create .xprofile for MATE with accessibility variables + cat > /home/\$firstUser/.xprofile <<'XPROFILE_MATE_EOF' +#!/bin/sh +# +# ~/.xprofile +# +# Executed by display managers and sourced by .xinitrc + +# Accessibility environment variables +export ACCESSIBILITY_ENABLED=1 +export GTK_MODULES=gail:atk-bridge:canberra-gtk-module +export GNOME_ACCESSIBILITY=1 +export QT_ACCESSIBILITY=1 +export QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1 +export DBUS_SESSION_BUS_PID +export DBUS_SESSION_BUS_ADDRESS + +# Enable Orca screen reader +gsettings set org.gnome.desktop.a11y.applications screen-reader-enabled true 2>/dev/null || true +gsettings set org.mate.interface accessibility true 2>/dev/null || true +gsettings set org.mate.applications-at-visual startup true 2>/dev/null || true +XPROFILE_MATE_EOF + chmod 755 /home/\$firstUser/.xprofile + chown \$firstUser:users /home/\$firstUser/.xprofile + + # Configure speech-dispatcher for MATE + if [[ ! -d /home/\$firstUser/.config/speech-dispatcher ]]; then + sudo -u \$firstUser spd-conf -n 2>/dev/null || true + fi +fi + +# Initialize pacman keyring +pacman-key --init +pacman-key --populate archlinux + +# Import Stormux key if present +if [[ -f /usr/share/pacman/keyrings/stormux.gpg ]]; then + pacman-key --add /usr/share/pacman/keyrings/stormux.gpg 2>/dev/null || true + pacman-key --lsign-key 52ADA49000F1FF0456F8AEEFB4CDE1CD56EF8E82 2>/dev/null || true +fi + +# Import xlibre key +pacman-key --recv-keys 73580DE2EDDFA6D6 2>/dev/null || true +pacman-key --lsign-key 73580DE2EDDFA6D6 2>/dev/null || true + +# Create system-wide accessibility configuration +cat > /etc/profile.d/stormux-accessibility.sh <<'PROFILE_EOF' +#!/bin/sh +# Stormux accessibility environment variables +# Available to all users and sessions + +# Accessibility support +export ACCESSIBILITY_ENABLED=1 +export GTK_MODULES=gail:atk-bridge:canberra-gtk-module +export GNOME_ACCESSIBILITY=1 +export QT_ACCESSIBILITY=1 +export QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1 + +# Dialog accessibility options +export DIALOGOPTS='--no-lines --visit-items' +PROFILE_EOF +chmod 755 /etc/profile.d/stormux-accessibility.sh + +echo "System configuration complete!" +CHROOT_SCRIPT + + chmod +x "$mountPoint/tmp/configure.sh" + + # Run configuration in chroot + echo "Configuring system (this may take a moment)..." + if ! arch-chroot "$mountPoint" /tmp/configure.sh; then + log_error "System configuration failed" + echo "ERROR: System configuration failed" + play_sound error + return 1 + fi + + # Clean up + rm "$mountPoint/tmp/configure.sh" + + echo "System configuration complete!" + log_info "System configuration complete" +} + +# +# Bootloader installation +# + +install_bootloader() { + echo "" + echo "=== Installing Bootloader ===" + echo "" + + if [[ "$bootMode" == "uefi" ]]; then + log_info "Installing systemd-boot for UEFI" + echo "Installing systemd-boot..." + + # Install systemd-boot + arch-chroot "$mountPoint" bootctl install + + # Create loader config + cat > "$mountPoint/boot/loader/loader.conf" < "$mountPoint/boot/loader/entries/arch.conf" < "$mountPoint/usr/local/bin/configure-stormux" <<'EOF' +#!/usr/bin/env bash +# Configure Stormux - Post-installation configuration script + +echo "Downloading latest configure-stormux script..." +cd /tmp || exit 1 +wget -q https://git.stormux.org/storm/configure-stormux/raw/branch/master/configure-stormux.sh -O configure-stormux +chmod +x configure-stormux +./configure-stormux +EOF + chmod +x "$mountPoint/usr/local/bin/configure-stormux" + log_info "configure-stormux stub created" + fi + + echo "Helper scripts installed!" + log_info "Helper scripts installation complete" +} + +# +# Cleanup and finalization +# + +cleanup_and_unmount() { + echo "" + echo "=== Finalizing Installation ===" + echo "" + + log_info "Syncing filesystems" + echo "Syncing filesystems..." + sync + + log_info "Unmounting filesystems" + echo "Unmounting filesystems..." + + # Unmount in reverse order + umount -R "$mountPoint" 2>/dev/null || true + + log_info "Installation cleanup complete" + echo "Cleanup complete!" +} + +# +# Main installation flow +# + +main() { + echo "" + echo "╔════════════════════════════════════════════╗" + echo "║ Stormux Installer for Arch Linux ║" + echo "║ Accessibility-First System Installation ║" + echo "╚════════════════════════════════════════════╝" + echo "" + + log_info "=== Stormux Installer Started ===" + log_info "Installation log: $logFile" + + # Pre-flight checks + if ! preflight_checks; then + log_error "Pre-flight checks failed" + exit 1 + fi + + # Disk selection + echo "=== Disk Selection ===" + echo "" + if ! select_disk targetDisk "Select target disk for Stormux installation:"; then + log_error "Target disk selection cancelled" + echo "Installation cancelled." + exit 1 + fi + + if ! confirm_disk_destruction "$targetDisk"; then + log_error "Disk destruction not confirmed" + echo "Installation cancelled." + exit 1 + fi + + # Partition layout selection + if ! select_partition_layout; then + log_error "Partition layout selection cancelled" + echo "Installation cancelled." + exit 1 + fi + + # If separate disk layout, select home disk + if [[ "$partitionLayout" == "separate_disk" ]]; then + echo "" + if ! select_disk homeDisk "Select disk for /home partition:"; then + log_error "Home disk selection cancelled" + echo "Installation cancelled." + exit 1 + fi + + if ! confirm_disk_destruction "$homeDisk"; then + log_error "Home disk destruction not confirmed" + echo "Installation cancelled." + exit 1 + fi + fi + + # Gather system information + if ! gather_system_info; then + log_error "System information gathering failed" + exit 1 + fi + + # Partition disks + if ! partition_disks; then + log_error "Disk partitioning failed" + echo "ERROR: Disk partitioning failed" + play_sound error + exit 1 + fi + + # Create filesystems + if ! create_filesystems; then + log_error "Filesystem creation failed" + echo "ERROR: Filesystem creation failed" + play_sound error + exit 1 + fi + + # Mount filesystems + if ! mount_filesystems; then + log_error "Filesystem mounting failed" + echo "ERROR: Filesystem mounting failed" + play_sound error + exit 1 + fi + + # Install base system + if ! install_base_system; then + log_error "Base system installation failed" + cleanup_and_unmount + exit 1 + fi + + # Install audio configurations + if ! install_audio_configs; then + log_error "Audio configuration failed" + cleanup_and_unmount + exit 1 + fi + + # Configure system + if ! configure_system; then + log_error "System configuration failed" + cleanup_and_unmount + exit 1 + fi + + # Install bootloader + if ! install_bootloader; then + log_error "Bootloader installation failed" + cleanup_and_unmount + exit 1 + fi + + # Install helper scripts + if ! install_helper_scripts; then + log_warning "Helper script installation had issues (non-fatal)" + fi + + # Cleanup and unmount + cleanup_and_unmount + + # Success message + echo "" + echo "╔════════════════════════════════════════════╗" + echo "║ Installation Complete Successfully! ║" + echo "╚════════════════════════════════════════════╝" + echo "" + echo "Next steps:" + echo " 1. Remove the installation media" + echo " 2. Reboot your system" + echo " 3. Log in with one of your user accounts: ${userNames[*]}" + + if [[ "$desktopEnvironment" == "i3" ]]; then + echo " 4. Your i3 desktop is configured with I38 for accessibility" + echo " - I38 source is available in ~/git/I38 for customization" + echo " - Press Alt+Shift+F1 for I38 help after login" + echo " 5. Run 'configure-stormux' for additional setup" + else + echo " 4. Run 'configure-stormux' for additional setup" + fi + + echo "" + echo "Installation log saved to: $logFile" + echo "" + + log_info "=== Stormux Installer Completed Successfully ===" + + read -rp "Press Enter to exit..." +} + +# Run main installation +main "$@" diff --git a/x86_64/packages.x86_64 b/x86_64/packages.x86_64 index e0ca148..cdb0da0 100644 --- a/x86_64/packages.x86_64 +++ b/x86_64/packages.x86_64 @@ -20,6 +20,7 @@ cryptsetup darkhttpd ddrescue dhcpcd +dialog diffutils dmidecode dmraid diff --git a/x86_64/pacman.conf b/x86_64/pacman.conf index 00fe68e..8fd3637 100644 --- a/x86_64/pacman.conf +++ b/x86_64/pacman.conf @@ -79,6 +79,10 @@ LocalFileSigLevel = Optional SigLevel = Required DatabaseOptional Server = https://packages.stormux.org/$arch +[xlibre] +SigLevel = Required DatabaseOptional +Server = https://x11libre.net/repo/arch_based/$arch + [core] Include = /etc/pacman.d/mirrorlist