Refactored installer into modular library structure with improved error handling and logging.

The changes include:
- Split monolithic script into lib/, config/, profiles/, and files/ directories
- Added error handling with cleanup on failure
- Added installation logging to /var/log/arch-install.log
- Added username validation
This commit is contained in:
2026-01-17 10:23:17 -05:00
parent f8f2d5a3ce
commit 6b70ce8a97
40 changed files with 2324 additions and 574 deletions

104
lib/system/base.sh Normal file
View File

@@ -0,0 +1,104 @@
#!/bin/bash
# Copyright 2026 Logan Fick
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# base.sh - Base system installation functions
#
# Installs and configures the core Arch Linux system:
# - Runs pacstrap with base packages defined in defaults.conf
# - Detects CPU vendor and installs appropriate microcode (Intel/AMD)
# - Generates /etc/fstab with UUIDs
# - Provides chroot helper functions for running commands in new system
# - Copies configuration files from installer to target system
# Run a command in the chroot environment
# Arguments:
# $@ - command and arguments
chroot_run() {
arch-chroot "${MOUNT_POINT}" "$@"
}
# Install packages in the chroot environment
# Arguments:
# $@ - package names
chroot_install() {
run_visible_cmd chroot_run pacman --noconfirm -S "$@"
}
# Enable a systemd service in the chroot environment
# Arguments:
# $@ - service names
chroot_enable() {
run_visible_cmd chroot_run systemctl enable "$@"
}
# Install base Arch Linux packages
install_base_packages() {
print "Installing Arch Linux base..."
run_visible_cmd pacstrap -K "${MOUNT_POINT}" "${BASE_PACKAGES[@]}"
}
# Detect CPU vendor
# Outputs:
# "intel", "amd", or "unknown"
detect_cpu_vendor() {
local vendor
vendor=$(grep -m 1 'vendor_id' /proc/cpuinfo | awk '{print $3}')
case "$vendor" in
"GenuineIntel")
echo "intel"
;;
"AuthenticAMD")
echo "amd"
;;
*)
echo "unknown"
;;
esac
}
# Install CPU microcode based on detected vendor
install_microcode() {
local vendor
vendor=$(detect_cpu_vendor)
print "Installing CPU microcode..."
case "$vendor" in
"intel")
chroot_install intel-ucode
;;
"amd")
chroot_install amd-ucode
;;
*)
print_warning "Unknown CPU vendor: ${vendor}. Please install microcode manually after installation, if available."
;;
esac
}
# Generate /etc/fstab
generate_fstab() {
print "Generating /etc/fstab..."
genfstab -U "${MOUNT_POINT}" >> "${MOUNT_POINT}/etc/fstab"
}
# Copy configuration files from installer to target system
copy_config_files() {
print "Installing default configuration files..."
cp -r "${CONFIG_SRC_DIR}" "${MOUNT_POINT}"
}

88
lib/system/bootloader.sh Normal file
View File

@@ -0,0 +1,88 @@
#!/bin/bash
# Copyright 2026 Logan Fick
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# bootloader.sh - systemd-boot configuration
#
# Installs and configures systemd-boot as the bootloader:
# - Runs bootctl install to set up EFI boot manager
# - Creates boot entry with LUKS unlock parameters
# - Supports both single-disk and RAID1 configurations
# - Configures loader.conf timeout setting
# Install systemd-boot bootloader
install_bootloader() {
print "Installing bootloader..."
run_visible_cmd chroot_run bootctl install
}
# Create boot entry for single-disk installation
# Arguments:
# $1 - LUKS UUID
create_boot_entry_single() {
local luks_uuid="$1"
chroot_run sh -c "cat > /boot/loader/entries/arch.conf" <<EOF
title Arch Linux
linux /vmlinuz-linux
initrd /initramfs-linux.img
options rd.luks.name=${luks_uuid}=cryptroot rd.luks.options=discard root=/dev/mapper/cryptroot
EOF
}
# Create boot entry for RAID1 installation
# Arguments:
# $1 - first LUKS UUID
# $2 - second LUKS UUID
create_boot_entry_raid1() {
local luks_uuid_1="$1"
local luks_uuid_2="$2"
chroot_run sh -c "cat > /boot/loader/entries/arch.conf" <<EOF
title Arch Linux
linux /vmlinuz-linux
initrd /initramfs-linux.img
options rd.luks.name=${luks_uuid_1}=cryptroot-primary rd.luks.name=${luks_uuid_2}=cryptroot-secondary rd.luks.options=${luks_uuid_1}=discard rd.luks.options=${luks_uuid_2}=discard root=/dev/mapper/cryptroot-primary
EOF
}
# Create appropriate boot entry based on storage mode
# Arguments:
# $1 - storage mode (single, raid1)
create_boot_entry() {
local storage_mode="$1"
if [ "$storage_mode" = "raid1" ]; then
create_boot_entry_raid1 "$LUKS_UUID" "$LUKS_UUID_2"
else
create_boot_entry_single "$LUKS_UUID"
fi
}
# Configure loader.conf timeout
configure_loader() {
chroot_run sed -i '/^#timeout 3/s/^#//' /boot/loader/loader.conf
}
# Full bootloader setup
# Arguments:
# $1 - storage mode
setup_bootloader() {
local storage_mode="$1"
install_bootloader
create_boot_entry "$storage_mode"
configure_loader
}

43
lib/system/locale.sh Normal file
View File

@@ -0,0 +1,43 @@
#!/bin/bash
# Copyright 2026 Logan Fick
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# locale.sh - Locale and timezone configuration
#
# Configures locale (en_US.UTF-8) and runs systemd-firstboot for timezone,
# keymap, and hostname setup.
# Configure system locale (en_US.UTF-8)
configure_locale() {
print "Setting up locale..."
chroot_run sed -i '/^#.*en_US.UTF-8 UTF-8/s/^#//' /etc/locale.gen
run_visible_cmd chroot_run locale-gen
chroot_run systemd-firstboot --locale=en_US.UTF-8
}
# Run interactive firstboot setup for timezone, keymap, hostname
run_firstboot() {
print "Entering first time setup..."
print "Your keymap is probably 'us' and the time zone is probably 'America/New_York'."
run_visible_cmd chroot_run systemd-firstboot --prompt
}
# Full locale and timezone setup
setup_locale() {
configure_locale
run_firstboot
}

95
lib/system/network.sh Normal file
View File

@@ -0,0 +1,95 @@
#!/bin/bash
# Copyright 2026 Logan Fick
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# network.sh - Network configuration
#
# Handles network setup for both installation and target system:
# - Verifies internet connectivity before installation begins
# - Checks system time synchronization via systemd-timesyncd
# - Configures pacman mirrorlist to use private mirror
# - Enables systemd-resolved, systemd-networkd, and systemd-timesyncd
# - Optionally installs iwd for Wi-Fi support
# Check internet connectivity
check_internet() {
print "Checking internet connectivity..."
if curl -s --head "$INTERNET_CHECK_URL" | grep "200" >/dev/null; then
print_success "Internet connection is available!"
return 0
else
print_error "Internet connection appears not available (HTTP request to \"$INTERNET_CHECK_URL\" failed). Please check network settings and re-run this script."
return 1
fi
}
# Check system time synchronization
check_time_sync() {
print "Checking system time synchronization state..."
if timedatectl status | grep -q "System clock synchronized: yes"; then
print_success "System time is synchronized!"
return 0
else
print_error "The system time is not synchronized. Please check systemd-timesyncd and re-run this script."
return 1
fi
}
# Configure pacman mirrorlist
configure_mirrorlist() {
print "Setting mirrorlist to use private mirror..."
echo "Server = ${MIRROR_URL}" > /etc/pacman.d/mirrorlist
}
# Enable systemd-resolved
enable_resolved() {
print "Enabling systemd-resolved..."
chroot_enable systemd-resolved.service
ln -sf ../run/systemd/resolve/stub-resolv.conf "${MOUNT_POINT}/etc/resolv.conf"
}
# Enable systemd-networkd
enable_networkd() {
print "Enabling systemd-networkd..."
chroot_enable systemd-networkd.service
}
# Enable systemd-timesyncd
enable_timesyncd() {
print "Enabling systemd-timesyncd..."
chroot_enable systemd-timesyncd.service
}
# Prompt and install iwd for Wi-Fi support
prompt_install_wifi() {
print "Would you like to install iwd for Wi-Fi support? Enter 'y' exactly for yes, otherwise anything else to skip."
read -r install_iwd
if [ "$install_iwd" = "y" ]; then
print "Installing iwd..."
chroot_install iwd
chroot_enable iwd.service
fi
}
# Full network setup
setup_network() {
enable_resolved
enable_networkd
enable_timesyncd
}

131
lib/system/security.sh Normal file
View File

@@ -0,0 +1,131 @@
#!/bin/bash
# Copyright 2026 Logan Fick
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# security.sh - Security hardening functions
#
# Applies security hardening to the installed system:
# - Configures mkinitcpio with sd-encrypt hook for LUKS
# - Enables sudo access for wheel group
# - Disables root account login
# - Enables nftables firewall, smartd, and fstrim timer
# - Configures OpenSSH with restricted settings
# - Installs custom CA certificate to system trust store
# - Sets up USBGuard to whitelist connected devices
# Configure mkinitcpio hooks for encrypted root
configure_initramfs() {
print "Configuring initramfs..."
local default_line="HOOKS=(base systemd autodetect microcode modconf kms keyboard keymap sd-vconsole block filesystems fsck)"
local new_line="HOOKS=(systemd autodetect microcode modconf kms keyboard sd-vconsole block sd-encrypt filesystems fsck)"
chroot_run sed -i "s|^${default_line}|${new_line}|" /etc/mkinitcpio.conf
run_visible_cmd chroot_run mkinitcpio -P
}
# Enable fstrim timer for SSD maintenance
enable_fstrim() {
print "Enabling fstrim timer..."
chroot_enable fstrim.timer
}
# Enable BTRFS scrub timer
# Arguments:
# $1 - filesystem type
enable_btrfs_scrub() {
local filesystem="$1"
if [ "$filesystem" = "btrfs" ] || [ "$filesystem" = "btrfs-dup" ]; then
print "Enabling btrfs scrub timer..."
chroot_enable btrfs-scrub@-.timer
fi
}
# Configure sudo access for wheel group
configure_sudo() {
print "Enabling sudo access for wheel group..."
chroot_run sed -i "s|^# %wheel ALL=(ALL:ALL) ALL|%wheel ALL=(ALL:ALL) ALL|" /etc/sudoers
}
# Disable root account login
disable_root() {
print "Disabling root account..."
chroot_run passwd -l root
}
# Enable nftables firewall
enable_firewall() {
print "Enabling nftables firewall..."
chroot_enable nftables.service
}
# Enable smartd for drive monitoring
enable_smartd() {
print "Enabling smartd..."
chroot_enable smartd.service
}
# Configure SSH server
# Arguments:
# $1 - username to allow SSH access
configure_ssh() {
local username="$1"
print "Setting up and enabling OpenSSH server..."
chroot_run sed -i "s|PLACEHOLDER|${username}|" /etc/ssh/sshd_config
run_visible_cmd chroot_run ssh-keygen -t ed25519 -C "" -N "" -f /etc/ssh/ssh_host_ed25519_key
chroot_enable sshd.service
}
# Display SSH host key fingerprint
show_ssh_fingerprint() {
print "Public SSH key fingerprint of this host:"
run_visible_cmd chroot_run ssh-keygen -lvf /etc/ssh/ssh_host_ed25519_key.pub
}
# Install custom CA certificate
install_ca_certificate() {
print "Adding LogalNet Internal Certification Authority to system CA store..."
cp "${CA_CERT_PATH}" "${MOUNT_POINT}"
chroot_run trust anchor --store /logalnet-internal-ca.crt
chroot_run rm /logalnet-internal-ca.crt
}
# Configure USBGuard
configure_usbguard() {
print "Please add or remove any USB devices, including the installer drive, to form the standard configuration for this system. USBGuard will be configured to only allow the USB devices connected at the time you press enter to be used; everything else will be blocked."
print "When ready to proceed, press enter."
read -r
chroot_run sh -c "usbguard generate-policy > /etc/usbguard/rules.conf"
chroot_enable usbguard.service
}
# Full security setup
# Arguments:
# $1 - filesystem type
setup_security() {
local filesystem="$1"
configure_sudo
disable_root
enable_firewall
enable_smartd
enable_fstrim
enable_btrfs_scrub "$filesystem"
}

66
lib/system/user.sh Normal file
View File

@@ -0,0 +1,66 @@
#!/bin/bash
# Copyright 2026 Logan Fick
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# user.sh - User account management
#
# Creates the primary user account with wheel group membership.
# Prompt for username
# Sets:
# USERNAME - the entered username
prompt_username() {
local username
while true; do
print "Please enter the username you'd like to use for your account:"
read -r username
if validate_username "$username"; then
USERNAME="$username"
return 0
fi
print "Please try again."
done
}
# Create a user account
# Arguments:
# $1 - username
create_user() {
local username="$1"
chroot_run useradd -m -G wheel "$username"
}
# Set password for a user
# Arguments:
# $1 - username
set_user_password() {
local username="$1"
print "Please set the password for your new account."
chroot_run passwd "$username"
}
# Full user setup
# Sets:
# USERNAME - the created username
setup_user() {
prompt_username
create_user "$USERNAME"
set_user_password "$USERNAME"
}