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:
54
config/defaults.conf
Normal file
54
config/defaults.conf
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
# defaults.conf - Default configuration values for the Arch Linux Installer
|
||||||
|
#
|
||||||
|
# Central configuration file for paths, URLs, and base packages.
|
||||||
|
|
||||||
|
# Installer identification
|
||||||
|
INSTALLER_NAME="LogalDeveloper's Arch Linux Installer"
|
||||||
|
|
||||||
|
# Network configuration
|
||||||
|
INTERNET_CHECK_URL="https://logal.dev/"
|
||||||
|
MIRROR_URL='https://mirrors.logal.dev/archlinux/$repo/os/$arch'
|
||||||
|
|
||||||
|
# Paths
|
||||||
|
CA_CERT_PATH="${SCRIPT_DIR}/files/certs/logalnet-internal-ca.crt"
|
||||||
|
CONFIG_SRC_DIR="${SCRIPT_DIR}/files/etc"
|
||||||
|
HOME_CONFIG_DIR="${SCRIPT_DIR}/files/home"
|
||||||
|
MOUNT_POINT="/mnt"
|
||||||
|
|
||||||
|
# Base packages to install with pacstrap
|
||||||
|
BASE_PACKAGES=(
|
||||||
|
base
|
||||||
|
linux
|
||||||
|
linux-firmware
|
||||||
|
bash-completion
|
||||||
|
btrfs-progs
|
||||||
|
smartmontools
|
||||||
|
lm_sensors
|
||||||
|
man-db
|
||||||
|
btop
|
||||||
|
htop
|
||||||
|
nano
|
||||||
|
less
|
||||||
|
tmux
|
||||||
|
rsync
|
||||||
|
sudo
|
||||||
|
iptables-nft
|
||||||
|
openssh
|
||||||
|
usbguard
|
||||||
|
)
|
||||||
28
config/luks.conf
Normal file
28
config/luks.conf
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
# luks.conf - LUKS encryption parameters
|
||||||
|
#
|
||||||
|
# Cryptographic settings for full-disk encryption using LUKS2.
|
||||||
|
|
||||||
|
LUKS_TYPE="luks2"
|
||||||
|
LUKS_CIPHER="aes-xts-plain64"
|
||||||
|
LUKS_HASH="sha512"
|
||||||
|
LUKS_KEY_SIZE="512"
|
||||||
|
LUKS_PBKDF="argon2id"
|
||||||
|
LUKS_PBKDF_ITERATIONS="8"
|
||||||
|
LUKS_PBKDF_MEMORY="4194304"
|
||||||
|
LUKS_PBKDF_PARALLEL="4"
|
||||||
@@ -6,594 +6,225 @@
|
|||||||
#
|
#
|
||||||
# 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.
|
# 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.
|
||||||
|
|
||||||
# Stop the script if any command exits non-zero.
|
#===============================================================================
|
||||||
set -e
|
# INITIALIZATION
|
||||||
|
#===============================================================================
|
||||||
# A wrapper for "echo" which prepends a prefix so output from the script can be easily differentiated from command output.
|
|
||||||
print() {
|
# Determine script directory for sourcing modules
|
||||||
echo "[LogalDeveloper's Arch Linux Installer] $1"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
}
|
|
||||||
|
# Source configuration
|
||||||
## Arch Linux Installation Guide Step 1.7 - Connect to the internet
|
source "${SCRIPT_DIR}/config/defaults.conf"
|
||||||
# Checks DNS and internet connectivity by making an HTTPS request. If it fails, assume no internet connection.
|
source "${SCRIPT_DIR}/config/luks.conf"
|
||||||
print "Checking internet connectivity..."
|
|
||||||
internet_check_url="https://logal.dev/"
|
# Source core libraries
|
||||||
if curl -s --head $internet_check_url | grep "200" >/dev/null; then
|
source "${SCRIPT_DIR}/lib/core/common.sh"
|
||||||
print "Internet connection is available!"
|
source "${SCRIPT_DIR}/lib/core/validation.sh"
|
||||||
else
|
source "${SCRIPT_DIR}/lib/core/error.sh"
|
||||||
print "Internet connection appears not available (HTTP request to "$internet_check_url" failed). Please check network settings and re-run this script."
|
source "${SCRIPT_DIR}/lib/core/logging.sh"
|
||||||
exit 1
|
|
||||||
fi
|
# Source disk operation modules
|
||||||
|
source "${SCRIPT_DIR}/lib/disk/partition.sh"
|
||||||
## Arch Linux Installation Guide Step 1.8 - Update the system clock
|
source "${SCRIPT_DIR}/lib/disk/luks.sh"
|
||||||
# Checks systemd-timesyncd to verify the system time is synchronized.
|
source "${SCRIPT_DIR}/lib/disk/filesystem.sh"
|
||||||
print "Checking system time synchronization state..."
|
|
||||||
if timedatectl status | grep -q "System clock synchronized: yes"; then
|
# Source system configuration modules
|
||||||
print "System time is synchronized!"
|
source "${SCRIPT_DIR}/lib/system/base.sh"
|
||||||
else
|
source "${SCRIPT_DIR}/lib/system/bootloader.sh"
|
||||||
print "The system time is not synchronized. Please check systemd-timesyncd and re-run this script."
|
source "${SCRIPT_DIR}/lib/system/locale.sh"
|
||||||
exit 1
|
source "${SCRIPT_DIR}/lib/system/network.sh"
|
||||||
fi
|
source "${SCRIPT_DIR}/lib/system/security.sh"
|
||||||
|
source "${SCRIPT_DIR}/lib/system/user.sh"
|
||||||
print "Setting mirrorlist to use private mirror..."
|
|
||||||
echo "Server = https://mirrors.logal.dev/archlinux/\$repo/os/\$arch" > /etc/pacman.d/mirrorlist
|
# Source desktop modules
|
||||||
|
source "${SCRIPT_DIR}/lib/desktop/xfce.sh"
|
||||||
# Provide the tip from the Arch Linux Installation Guide regarding optimal logical sector sizes.
|
source "${SCRIPT_DIR}/lib/desktop/drivers.sh"
|
||||||
print "Please check the following items before proceding:"
|
|
||||||
print " - If you intend to use an Advanced Format (e.g. NVMe) drive, verify the optimal sector size is selected. (https://wiki.archlinux.org/title/Advanced_Format)"
|
# Source profile system
|
||||||
print "If you need to go back, press Ctrl+C. Otherwise, press enter to continue."
|
source "${SCRIPT_DIR}/profiles/registry.sh"
|
||||||
read
|
|
||||||
|
# Enable error handling
|
||||||
print "Select storage and filesystem configuration:"
|
trap_errors
|
||||||
print " 1 - ext4 (Single disk)"
|
|
||||||
print " 2 - BTRFS (Single disk) [Recommended over ext4]"
|
#===============================================================================
|
||||||
print " 3 - BTRFS DUP (Single disk with duplicate data and metadata)"
|
# STORAGE CONFIGURATION
|
||||||
print " 4 - BTRFS RAID1 (Two disks with full redundancy)"
|
#===============================================================================
|
||||||
read storage_choice
|
|
||||||
|
# Prompt user for storage mode selection
|
||||||
case $storage_choice in
|
# Sets:
|
||||||
"1")
|
# STORAGE_MODE - "single" or "raid1"
|
||||||
storage_mode="single"
|
# FILESYSTEM - "ext4", "btrfs", or "btrfs-dup"
|
||||||
filesystem="ext4"
|
select_storage_mode() {
|
||||||
print "ext4 on single disk selected."
|
print "Select storage and filesystem configuration:"
|
||||||
;;
|
print " 1 - ext4 (Single disk)"
|
||||||
"3")
|
print " 2 - BTRFS (Single disk) [Recommended over ext4]"
|
||||||
storage_mode="single"
|
print " 3 - BTRFS DUP (Single disk with duplicate data and metadata)"
|
||||||
filesystem="btrfs-dup"
|
print " 4 - BTRFS RAID1 (Two disks with full redundancy)"
|
||||||
print "BTRFS dup mode selected. Data and metadata will be duplicated on the same disk."
|
read -r storage_choice
|
||||||
;;
|
|
||||||
"4")
|
case "$storage_choice" in
|
||||||
storage_mode="raid1"
|
"1")
|
||||||
filesystem="btrfs"
|
STORAGE_MODE="single"
|
||||||
print "BTRFS RAID1 mode selected. You will need two disks of similar size."
|
FILESYSTEM="ext4"
|
||||||
;;
|
print "ext4 on single disk selected."
|
||||||
*)
|
|
||||||
storage_mode="single"
|
|
||||||
filesystem="btrfs"
|
|
||||||
print "BTRFS on single disk selected."
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
## Arch Linux Installation Guide Step 1.9 - Partition the disks
|
|
||||||
# Provide the user a listing of the disks and ask them which they'd like to install to.
|
|
||||||
fdisk -l
|
|
||||||
|
|
||||||
if [ "$storage_mode" = "raid1" ]; then
|
|
||||||
print "Disk information from 'fdisk -l' is provided above. Please enter the path to the FIRST disk for RAID1 (e.g. /dev/sda)."
|
|
||||||
read install_disk
|
|
||||||
print "Please confirm your selection by entering the same path again."
|
|
||||||
read disk_confirm
|
|
||||||
|
|
||||||
if [ "$install_disk" != "$disk_confirm" ]; then
|
|
||||||
print "The same disk was not entered both times. Exiting..."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
print "Now enter the path to the SECOND disk for RAID1 (e.g. /dev/sdb). This must be a DIFFERENT disk."
|
|
||||||
read install_disk_2
|
|
||||||
print "Please confirm your selection by entering the same path again."
|
|
||||||
read disk_confirm_2
|
|
||||||
|
|
||||||
if [ "$install_disk_2" != "$disk_confirm_2" ]; then
|
|
||||||
print "The same disk was not entered both times. Exiting..."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$install_disk" = "$install_disk_2" ]; then
|
|
||||||
print "Error: Both disks must be different. You entered the same disk twice. Exiting..."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Final confirmation for RAID1
|
|
||||||
print "Last warning: Are you sure you want to install Arch Linux in RAID1 mode to '$install_disk' and '$install_disk_2'? All data on BOTH disks will be wiped. Enter 'I am sure' exactly to confirm, or anything else to cancel."
|
|
||||||
read final_confirmation
|
|
||||||
|
|
||||||
if [ "$final_confirmation" != "I am sure" ]; then
|
|
||||||
print "Confirmation failed. Exiting..."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
# Single disk mode
|
|
||||||
print "Disk information from 'fdisk -l' is provided above. Please enter the path to the disk you would like to install Arch Linux to (e.g. /dev/sda)."
|
|
||||||
read install_disk
|
|
||||||
print "Please confirm your selection by entering the same path again."
|
|
||||||
read disk_confirm
|
|
||||||
|
|
||||||
if [ "$install_disk" != "$disk_confirm" ]; then
|
|
||||||
print "The same disk was not entered both times. Exiting..."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Triple check the user wants to continue installing to install disk.
|
|
||||||
print "Last warning: Are you sure you want to install Arch Linux to '$install_disk'? All data on this disk will be wiped. Enter 'I am sure' exactly to confirm, or anything else to cancel."
|
|
||||||
read final_confirmation
|
|
||||||
|
|
||||||
if [ "$final_confirmation" != "I am sure" ]; then
|
|
||||||
print "Confirmation failed. Exiting..."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Wipe all previous file systems from the install disk(s).
|
|
||||||
if [ "$storage_mode" = "raid1" ]; then
|
|
||||||
print "Wiping existing partition tables from $install_disk and $install_disk_2..."
|
|
||||||
wipefs -a $install_disk
|
|
||||||
wipefs -a $install_disk_2
|
|
||||||
else
|
|
||||||
print "Wiping existing partition table from $install_disk..."
|
|
||||||
wipefs -a $install_disk
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Partition install disk.
|
|
||||||
print "Partitioning $install_disk..."
|
|
||||||
sgdisk --new 1:0:1G $install_disk # New GPT table, make a new partition 1G in size at the start
|
|
||||||
sgdisk --typecode 1:ef00 $install_disk # Mark it as EFI System Partition
|
|
||||||
sgdisk --new 2:0:0 $install_disk # Add a second partition taking up the rest of the remaining space.
|
|
||||||
sgdisk --type-code 2:8309 $install_disk # Mark it as Linux LUKS
|
|
||||||
|
|
||||||
# /dev/nvme has an extra charater to identify partition number.
|
|
||||||
if [[ $install_disk == /dev/nvme* ]]; then
|
|
||||||
# Use "p" in the partition paths for NVMe drives.
|
|
||||||
partition_prefix="${install_disk}p"
|
|
||||||
else
|
|
||||||
# Use just numbers for other drives.
|
|
||||||
partition_prefix="${install_disk}"
|
|
||||||
fi
|
|
||||||
efi_partition=${partition_prefix}1
|
|
||||||
root_partition=${partition_prefix}2
|
|
||||||
|
|
||||||
if [ "$storage_mode" = "raid1" ]; then
|
|
||||||
print "Partitioning $install_disk_2..."
|
|
||||||
sgdisk --new 1:0:1G $install_disk_2
|
|
||||||
sgdisk --typecode 1:ef00 $install_disk_2
|
|
||||||
sgdisk --new 2:0:0 $install_disk_2
|
|
||||||
sgdisk --type-code 2:8309 $install_disk_2
|
|
||||||
|
|
||||||
# Handle NVMe naming for second disk
|
|
||||||
if [[ $install_disk_2 == /dev/nvme* ]]; then
|
|
||||||
partition_prefix_2="${install_disk_2}p"
|
|
||||||
else
|
|
||||||
partition_prefix_2="${install_disk_2}"
|
|
||||||
fi
|
|
||||||
efi_partition_2=${partition_prefix_2}1
|
|
||||||
root_partition_2=${partition_prefix_2}2
|
|
||||||
fi
|
|
||||||
|
|
||||||
## Arch Linux Installation Guide Step 1.10 - Format the partitions
|
|
||||||
print "Formatting ${efi_partition} as FAT32..."
|
|
||||||
mkfs.fat -F 32 ${efi_partition}
|
|
||||||
|
|
||||||
if [ "$storage_mode" = "raid1" ]; then
|
|
||||||
print "Formatting ${efi_partition_2} as FAT32..."
|
|
||||||
mkfs.fat -F 32 ${efi_partition_2}
|
|
||||||
fi
|
|
||||||
|
|
||||||
print "Setting up disk encryption..."
|
|
||||||
if [ "$storage_mode" = "raid1" ]; then
|
|
||||||
print "Please enter your desired encryption passphrase. This will be used for both disks."
|
|
||||||
else
|
|
||||||
print "Please enter your desired encryption passphrase."
|
|
||||||
fi
|
|
||||||
read -s encryption_password
|
|
||||||
echo
|
|
||||||
print "Please confirm your encryption passphrase."
|
|
||||||
read -s encryption_password_confirm
|
|
||||||
echo
|
|
||||||
|
|
||||||
if [ "$encryption_password" != "$encryption_password_confirm" ]; then
|
|
||||||
print "Passphrases do not match. Exiting..."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
print "Setting up encryption on ${root_partition}..."
|
|
||||||
echo -n "$encryption_password" | cryptsetup luksFormat --type luks2 --cipher aes-xts-plain64 --hash sha512 --key-size 512 --pbkdf argon2id --pbkdf-force-iterations 8 --pbkdf-memory 4194304 --pbkdf-parallel 4 --use-urandom --key-file - ${root_partition}
|
|
||||||
|
|
||||||
print "Unlocking ${root_partition}..."
|
|
||||||
if [ "$storage_mode" = "raid1" ]; then
|
|
||||||
echo -n "$encryption_password" | cryptsetup open --allow-discards --key-file - ${root_partition} cryptroot-primary
|
|
||||||
else
|
|
||||||
echo -n "$encryption_password" | cryptsetup open --allow-discards --key-file - ${root_partition} cryptroot
|
|
||||||
fi
|
|
||||||
luks_uuid=$(cryptsetup luksDump ${root_partition} | grep 'UUID:' | awk '{print $2}')
|
|
||||||
|
|
||||||
if [ "$storage_mode" = "raid1" ]; then
|
|
||||||
print "Setting up encryption on ${root_partition_2}..."
|
|
||||||
echo -n "$encryption_password" | cryptsetup luksFormat --type luks2 --cipher aes-xts-plain64 --hash sha512 --key-size 512 --pbkdf argon2id --pbkdf-force-iterations 8 --pbkdf-memory 4194304 --pbkdf-parallel 4 --use-urandom --key-file - ${root_partition_2}
|
|
||||||
|
|
||||||
print "Unlocking ${root_partition_2}..."
|
|
||||||
echo -n "$encryption_password" | cryptsetup open --allow-discards --key-file - ${root_partition_2} cryptroot-secondary
|
|
||||||
luks_uuid_2=$(cryptsetup luksDump ${root_partition_2} | grep 'UUID:' | awk '{print $2}')
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clear the password from memory
|
|
||||||
unset encryption_password
|
|
||||||
unset encryption_password_confirm
|
|
||||||
|
|
||||||
case $filesystem in
|
|
||||||
"ext4")
|
|
||||||
if [ "$storage_mode" = "raid1" ]; then
|
|
||||||
print "Error: ext4 cannot be used with RAID1. Exiting..."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
print "Formatting /dev/mapper/cryptroot as ext4..."
|
|
||||||
mkfs.ext4 /dev/mapper/cryptroot
|
|
||||||
;;
|
|
||||||
|
|
||||||
"btrfs-dup")
|
|
||||||
print "Formatting /dev/mapper/cryptroot as btrfs with dup profile..."
|
|
||||||
mkfs.btrfs --csum xxhash --data dup --metadata dup /dev/mapper/cryptroot
|
|
||||||
;;
|
|
||||||
|
|
||||||
*)
|
|
||||||
if [ "$storage_mode" = "raid1" ]; then
|
|
||||||
print "Formatting /dev/mapper/cryptroot-primary and /dev/mapper/cryptroot-secondary as btrfs RAID1..."
|
|
||||||
mkfs.btrfs --csum xxhash --data raid1 --metadata raid1 /dev/mapper/cryptroot-primary /dev/mapper/cryptroot-secondary
|
|
||||||
else
|
|
||||||
print "Formatting /dev/mapper/cryptroot as btrfs..."
|
|
||||||
mkfs.btrfs --csum xxhash /dev/mapper/cryptroot
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
## Arch Linux Installation Guide Step 1.11 - Mount the file systems
|
|
||||||
print "Mounting partitions..."
|
|
||||||
|
|
||||||
case $filesystem in
|
|
||||||
"ext4")
|
|
||||||
mount -o "noatime,discard" /dev/mapper/cryptroot /mnt
|
|
||||||
;;
|
|
||||||
|
|
||||||
*)
|
|
||||||
if [ "$storage_mode" = "raid1" ]; then
|
|
||||||
mount -o "noatime,discard=async" /dev/mapper/cryptroot-primary /mnt
|
|
||||||
else
|
|
||||||
mount -o "noatime,discard=async" /dev/mapper/cryptroot /mnt
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
mount --mkdir -o "fmask=0077,dmask=0077" ${efi_partition} /mnt/boot
|
|
||||||
|
|
||||||
## Arch Linux Installation Guide Step 2.2 - Install essential packages
|
|
||||||
print "Installing Arch Linux base..."
|
|
||||||
pacstrap -K /mnt base \
|
|
||||||
linux \
|
|
||||||
linux-firmware \
|
|
||||||
bash-completion \
|
|
||||||
btrfs-progs \
|
|
||||||
smartmontools \
|
|
||||||
lm_sensors \
|
|
||||||
man-db \
|
|
||||||
btop \
|
|
||||||
htop \
|
|
||||||
nano \
|
|
||||||
less \
|
|
||||||
tmux \
|
|
||||||
rsync \
|
|
||||||
sudo \
|
|
||||||
iptables-nft \
|
|
||||||
openssh \
|
|
||||||
usbguard
|
|
||||||
|
|
||||||
print "Installing CPU microcode..."
|
|
||||||
cpu_vendor=$(grep -m 1 'vendor_id' /proc/cpuinfo | awk '{print $3}')
|
|
||||||
if [[ "${cpu_vendor}" == "GenuineIntel" ]]; then
|
|
||||||
arch-chroot /mnt pacman --noconfirm -S intel-ucode
|
|
||||||
elif [[ "${cpu_vendor}" == "AuthenticAMD" ]]; then
|
|
||||||
arch-chroot /mnt pacman --noconfirm -S amd-ucode
|
|
||||||
else
|
|
||||||
echo "Unknown CPU vendor: ${cpu_vendor}. Please install microcode manually after installation, if available."
|
|
||||||
fi
|
|
||||||
|
|
||||||
## Arch Linux Installation Guide Step 3.1 - Fstab
|
|
||||||
print "Generating /etc/fstab..."
|
|
||||||
genfstab -U /mnt >> /mnt/etc/fstab
|
|
||||||
|
|
||||||
## Arch Linux Installation Guide Step 3.4 - Localization
|
|
||||||
print "Setting up locale..."
|
|
||||||
arch-chroot /mnt sed -i '/^#.*en_US.UTF-8 UTF-8/s/^#//' /etc/locale.gen
|
|
||||||
arch-chroot /mnt locale-gen
|
|
||||||
arch-chroot /mnt systemd-firstboot --locale=en_US.UTF-8
|
|
||||||
|
|
||||||
## Arch Linux Installation Guide Step 3.3 - Time
|
|
||||||
## Arch Linux Installation Guide Step 3.4 - Localization
|
|
||||||
## Arch Linux Installation Guide Step 3.5 - Network configuration
|
|
||||||
print "Entering first time setup..."
|
|
||||||
print "Your keymap is probably 'us' and the time zone is probably 'America/New_York'."
|
|
||||||
arch-chroot /mnt systemd-firstboot --prompt
|
|
||||||
|
|
||||||
## Arch Linux Installation Guide Step 3.6 - Initramfs
|
|
||||||
default_mkinitcpio_line="HOOKS=(base systemd autodetect microcode modconf kms keyboard keymap sd-vconsole block filesystems fsck)"
|
|
||||||
new_mkinitcpio_line="HOOKS=(systemd autodetect microcode modconf kms keyboard sd-vconsole block sd-encrypt filesystems fsck)"
|
|
||||||
arch-chroot /mnt sed -i "s|^${default_mkinitcpio_line}|${new_mkinitcpio_line}|" /etc/mkinitcpio.conf
|
|
||||||
arch-chroot /mnt mkinitcpio -P
|
|
||||||
|
|
||||||
## Arch Linux Installation Guide Step 3.8 - Boot loader
|
|
||||||
print "Installing bootloader..."
|
|
||||||
arch-chroot /mnt bootctl install
|
|
||||||
|
|
||||||
if [ "$storage_mode" = "raid1" ]; then
|
|
||||||
# RAID1 mode: Must unlock both LUKS devices at boot
|
|
||||||
arch-chroot /mnt 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-primary rd.luks.name=${luks_uuid_2}=cryptroot-secondary rd.luks.options=${luks_uuid}=discard rd.luks.options=${luks_uuid_2}=discard root=/dev/mapper/cryptroot-primary
|
|
||||||
EOF
|
|
||||||
else
|
|
||||||
# Single disk mode (original)
|
|
||||||
arch-chroot /mnt 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
|
|
||||||
fi
|
|
||||||
|
|
||||||
arch-chroot /mnt sed -i '/^#timeout 3/s/^#//' /boot/loader/loader.conf
|
|
||||||
|
|
||||||
print "Enabling fstrim timer..."
|
|
||||||
arch-chroot /mnt systemctl enable fstrim.timer
|
|
||||||
|
|
||||||
if [ "$filesystem" = "btrfs" ] || [ "$filesystem" = "btrfs-dup" ]; then
|
|
||||||
print "Enabling scrub timer..."
|
|
||||||
arch-chroot /mnt systemctl enable btrfs-scrub@-.timer
|
|
||||||
fi
|
|
||||||
|
|
||||||
print "Enabling sudo access for wheel group..."
|
|
||||||
arch-chroot /mnt sed -i "s|^# %wheel ALL=(ALL:ALL) ALL|%wheel ALL=(ALL:ALL) ALL|" /etc/sudoers
|
|
||||||
|
|
||||||
print "Disabling root account..."
|
|
||||||
arch-chroot /mnt passwd -l root
|
|
||||||
|
|
||||||
print "Please enter the username you'd like to use for your account"
|
|
||||||
read username
|
|
||||||
arch-chroot /mnt useradd -m -G wheel $username
|
|
||||||
print "Please set the password for your new account."
|
|
||||||
arch-chroot /mnt passwd $username
|
|
||||||
|
|
||||||
print "Installing default configuration files..."
|
|
||||||
cp -r ./etc /mnt
|
|
||||||
|
|
||||||
print "Enabling systemd-resolved..."
|
|
||||||
arch-chroot /mnt systemctl enable systemd-resolved.service
|
|
||||||
ln -sf ../run/systemd/resolve/stub-resolv.conf /mnt/etc/resolv.conf
|
|
||||||
|
|
||||||
print "Enabling systemd-networkd..."
|
|
||||||
arch-chroot /mnt systemctl enable systemd-networkd.service
|
|
||||||
|
|
||||||
print "Enabling systemd-timesyncd..."
|
|
||||||
arch-chroot /mnt systemctl enable systemd-timesyncd.service
|
|
||||||
|
|
||||||
print "Enabling nftables firewall..."
|
|
||||||
arch-chroot /mnt systemctl enable nftables.service
|
|
||||||
|
|
||||||
print "Enabling smartd..."
|
|
||||||
arch-chroot /mnt systemctl enable smartd.service
|
|
||||||
|
|
||||||
print "Would you like to install iwd for Wi-Fi support? Enter 'y' exactly for yes, otherwise anything else to skip."
|
|
||||||
read install_iwd
|
|
||||||
|
|
||||||
if [ "$install_iwd" == "y" ]; then
|
|
||||||
print "Installing iwd..."
|
|
||||||
arch-chroot /mnt pacman --noconfirm -S iwd
|
|
||||||
arch-chroot /mnt systemctl enable iwd.service
|
|
||||||
fi
|
|
||||||
|
|
||||||
print "Setting up and enabling OpenSSH server..."
|
|
||||||
arch-chroot /mnt sed -i "s|PLACEHOLDER|${username}|" /etc/ssh/sshd_config
|
|
||||||
arch-chroot /mnt ssh-keygen -t ed25519 -C "" -N "" -f /etc/ssh/ssh_host_ed25519_key
|
|
||||||
arch-chroot /mnt systemctl enable sshd.service
|
|
||||||
|
|
||||||
print "Adding LogalNet Internal Certification Authority to system CA store..."
|
|
||||||
cp ./logalnet-internal-ca.crt /mnt
|
|
||||||
arch-chroot /mnt trust anchor --store /logalnet-internal-ca.crt
|
|
||||||
arch-chroot /mnt rm /logalnet-internal-ca.crt
|
|
||||||
|
|
||||||
install_base_xfce() {
|
|
||||||
arch-chroot /mnt pacman --noconfirm -S lightdm \
|
|
||||||
lightdm-gtk-greeter \
|
|
||||||
lightdm-gtk-greeter-settings \
|
|
||||||
thunar \
|
|
||||||
thunar-archive-plugin \
|
|
||||||
gvfs \
|
|
||||||
xfce4-panel \
|
|
||||||
xfce4-power-manager \
|
|
||||||
xfce4-session \
|
|
||||||
xfce4-settings \
|
|
||||||
xfce4-terminal \
|
|
||||||
xfdesktop \
|
|
||||||
xfwm4 \
|
|
||||||
papirus-icon-theme \
|
|
||||||
xfce4-battery-plugin \
|
|
||||||
xfce4-notifyd \
|
|
||||||
xfce4-whiskermenu-plugin \
|
|
||||||
xfce4-screensaver \
|
|
||||||
xfce4-screenshooter \
|
|
||||||
mousepad \
|
|
||||||
noto-fonts \
|
|
||||||
noto-fonts-cjk \
|
|
||||||
noto-fonts-emoji \
|
|
||||||
noto-fonts-extra \
|
|
||||||
pipewire \
|
|
||||||
pipewire-alsa \
|
|
||||||
pipewire-pulse \
|
|
||||||
pipewire-jack \
|
|
||||||
wireplumber \
|
|
||||||
pavucontrol \
|
|
||||||
xfce4-pulseaudio-plugin \
|
|
||||||
ristretto \
|
|
||||||
webp-pixbuf-loader \
|
|
||||||
libopenraw \
|
|
||||||
xarchiver \
|
|
||||||
7zip \
|
|
||||||
xreader
|
|
||||||
arch-chroot /mnt systemctl enable lightdm.service
|
|
||||||
|
|
||||||
cp -r ./default-home-directory-config /mnt/home/$username/.config
|
|
||||||
|
|
||||||
arch-chroot /mnt sh -c "cat > /etc/lightdm/lightdm-gtk-greeter.conf" <<EOF
|
|
||||||
[greeter]
|
|
||||||
hide-user-image = true
|
|
||||||
font-name = Noto Sans 10
|
|
||||||
clock-format = %A, %B %d, %Y,%l:%M:%S %p
|
|
||||||
theme-name = Adwaita-dark
|
|
||||||
icon-theme-name = Papirus-Dark
|
|
||||||
screensaver-timeout = 10
|
|
||||||
user-background = false
|
|
||||||
background = #77767b
|
|
||||||
indicators = ~host;~spacer;~clock;~spacer;~power
|
|
||||||
EOF
|
|
||||||
|
|
||||||
mkdir -p /mnt/home/$username/.config/systemd/user
|
|
||||||
ln -s /dev/null /mnt/home/$username/.config/systemd/user/tumblerd.service
|
|
||||||
|
|
||||||
chown -R 1000:1000 /mnt/home/$username/.config
|
|
||||||
|
|
||||||
print "Would you like to install graphics drivers? Type 'intel' exactly for Intel graphics drivers, 'nvidia' for NVIDIA graphics drivers, or anything else to skip"
|
|
||||||
read driver
|
|
||||||
|
|
||||||
case $driver in
|
|
||||||
"intel")
|
|
||||||
arch-chroot /mnt pacman --noconfirm -S mesa \
|
|
||||||
vulkan-intel \
|
|
||||||
intel-media-driver \
|
|
||||||
libva-intel-driver
|
|
||||||
;;
|
;;
|
||||||
"nvidia")
|
"3")
|
||||||
arch-chroot /mnt pacman --noconfirm -S mesa \
|
STORAGE_MODE="single"
|
||||||
nvidia-open \
|
FILESYSTEM="btrfs-dup"
|
||||||
libva-nvidia-driver
|
print "BTRFS dup mode selected. Data and metadata will be duplicated on the same disk."
|
||||||
|
;;
|
||||||
|
"4")
|
||||||
|
STORAGE_MODE="raid1"
|
||||||
|
FILESYSTEM="btrfs"
|
||||||
|
print "BTRFS RAID1 mode selected. You will need two disks of similar size."
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
print "Skipping graphics driver installation."
|
STORAGE_MODE="single"
|
||||||
|
FILESYSTEM="btrfs"
|
||||||
|
print "BTRFS on single disk selected."
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
print "Base install complete. Select profile to install for this system:"
|
#===============================================================================
|
||||||
print " 1 - Minimal"
|
# PRE-INSTALLATION NOTES
|
||||||
print " Base Arch Linux system, no additional packages."
|
#===============================================================================
|
||||||
print " 2 - Server"
|
|
||||||
print " Adds Restic, Docker, and Docker Compose."
|
|
||||||
print " 3 - Minimal Desktop"
|
|
||||||
print " XFCE 4 with no additional applications."
|
|
||||||
print " 4 - Home Theater PC"
|
|
||||||
print " XFCE 4 with Chromium and VLC media player."
|
|
||||||
print " 5 - Home Theater PC with Gaming"
|
|
||||||
print " XFCE 4 with Chromium, VLC media player, and Dolphin."
|
|
||||||
print " 6 - Office Workstation"
|
|
||||||
print " XFCE 4 with a full suite of desktop applications aimed at general office work."
|
|
||||||
print " 7 - Software Development Workstation"
|
|
||||||
print " XFCE 4 with a suite of software development applications."
|
|
||||||
read profile
|
|
||||||
|
|
||||||
case $profile in
|
show_pre_install_notes() {
|
||||||
"1")
|
print "Please check the following items before proceeding:"
|
||||||
# Do nothing...
|
print " - If you intend to use an Advanced Format (e.g. NVMe) drive, verify the optimal sector size is selected. (https://wiki.archlinux.org/title/Advanced_Format)"
|
||||||
;;
|
print "If you need to go back, press Ctrl+C. Otherwise, press enter to continue."
|
||||||
|
read -r
|
||||||
|
}
|
||||||
|
|
||||||
"2")
|
#===============================================================================
|
||||||
arch-chroot /mnt pacman --noconfirm -S restic \
|
# MAIN INSTALLATION FLOW
|
||||||
docker \
|
#===============================================================================
|
||||||
docker-compose
|
|
||||||
arch-chroot /mnt systemctl enable docker.service
|
|
||||||
;;
|
|
||||||
|
|
||||||
"3")
|
main() {
|
||||||
install_base_xfce
|
# Initialize logging
|
||||||
;;
|
init_logging
|
||||||
|
|
||||||
"4")
|
# Show banner
|
||||||
install_base_xfce
|
print_banner
|
||||||
arch-chroot /mnt pacman --noconfirm -S chromium \
|
|
||||||
vlc \
|
|
||||||
vlc-plugin-ffmpeg
|
|
||||||
;;
|
|
||||||
|
|
||||||
"5")
|
#---------------------------------------------------------------------------
|
||||||
install_base_xfce
|
# Phase 1: Pre-flight Checks
|
||||||
arch-chroot /mnt pacman --noconfirm -S dolphin-emu \
|
#---------------------------------------------------------------------------
|
||||||
chromium \
|
set_phase "Pre-flight Checks"
|
||||||
vlc \
|
|
||||||
vlc-plugin-ffmpeg
|
|
||||||
;;
|
|
||||||
|
|
||||||
"6")
|
if ! check_internet; then
|
||||||
install_base_xfce
|
exit 1
|
||||||
arch-chroot /mnt pacman --noconfirm -S ffmpeg \
|
fi
|
||||||
chromium \
|
|
||||||
gimp \
|
|
||||||
git \
|
|
||||||
gnucash \
|
|
||||||
hunspell-en_us \
|
|
||||||
keepassxc \
|
|
||||||
libreoffice-fresh \
|
|
||||||
qalculate-gtk \
|
|
||||||
syncthing \
|
|
||||||
tenacity \
|
|
||||||
vlc \
|
|
||||||
vlc-plugin-ffmpeg
|
|
||||||
;;
|
|
||||||
|
|
||||||
"7")
|
if ! check_time_sync; then
|
||||||
install_base_xfce
|
exit 1
|
||||||
arch-chroot /mnt pacman --noconfirm -S code \
|
fi
|
||||||
docker \
|
|
||||||
docker-compose \
|
|
||||||
ffmpeg \
|
|
||||||
chromium \
|
|
||||||
gimp \
|
|
||||||
git \
|
|
||||||
go \
|
|
||||||
hunspell-en_us \
|
|
||||||
intellij-idea-community-edition \
|
|
||||||
jdk-openjdk \
|
|
||||||
keepassxc \
|
|
||||||
libreoffice-fresh \
|
|
||||||
pycharm-community-edition \
|
|
||||||
python \
|
|
||||||
python-virtualenv \
|
|
||||||
qalculate-gtk \
|
|
||||||
syncthing \
|
|
||||||
tenacity \
|
|
||||||
vlc \
|
|
||||||
vlc-plugin-ffmpeg \
|
|
||||||
wireshark-qt
|
|
||||||
;;
|
|
||||||
|
|
||||||
*)
|
configure_mirrorlist
|
||||||
echo -n "Unknown profile, defaulting to minimal install."
|
show_pre_install_notes
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
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."
|
# Phase 2: Storage Configuration
|
||||||
read
|
#---------------------------------------------------------------------------
|
||||||
arch-chroot /mnt sh -c "usbguard generate-policy > /etc/usbguard/rules.conf"
|
set_phase "Storage Configuration"
|
||||||
arch-chroot /mnt systemctl enable usbguard.service
|
|
||||||
|
|
||||||
echo "\n\n\n\n\n"
|
select_storage_mode
|
||||||
print "Installation complete!"
|
|
||||||
|
|
||||||
print "Public SSH key fingerprint of this host:"
|
if [ "$STORAGE_MODE" = "raid1" ]; then
|
||||||
arch-chroot /mnt ssh-keygen -lvf /etc/ssh/ssh_host_ed25519_key.pub
|
if ! select_raid1_disks; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if ! select_single_disk; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Phase 3: Disk Preparation
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
set_phase "Disk Preparation"
|
||||||
|
|
||||||
|
partition_disks "$STORAGE_MODE"
|
||||||
|
|
||||||
|
# Setup encryption
|
||||||
|
if [ "$STORAGE_MODE" = "raid1" ]; then
|
||||||
|
setup_encryption_raid1 "$ROOT_PARTITION" "$ROOT_PARTITION_2"
|
||||||
|
else
|
||||||
|
setup_encryption_single "$ROOT_PARTITION"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Format and mount filesystems
|
||||||
|
format_and_mount_filesystems "$FILESYSTEM" "$STORAGE_MODE"
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Phase 4: Base System Installation
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
set_phase "Base System Installation"
|
||||||
|
|
||||||
|
install_base_packages
|
||||||
|
install_microcode
|
||||||
|
generate_fstab
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Phase 5: System Configuration
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
set_phase "System Configuration"
|
||||||
|
|
||||||
|
setup_locale
|
||||||
|
configure_initramfs
|
||||||
|
setup_bootloader "$STORAGE_MODE"
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Phase 6: User Account Setup
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
set_phase "User Account Setup"
|
||||||
|
|
||||||
|
setup_user
|
||||||
|
copy_config_files
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Phase 7: Network Configuration
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
set_phase "Network Configuration"
|
||||||
|
|
||||||
|
setup_network
|
||||||
|
prompt_install_wifi
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Phase 8: Security Configuration
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
set_phase "Security Configuration"
|
||||||
|
|
||||||
|
setup_security "$FILESYSTEM"
|
||||||
|
configure_ssh "$USERNAME"
|
||||||
|
install_ca_certificate
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Phase 9: Profile Installation
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
set_phase "Profile Installation"
|
||||||
|
|
||||||
|
select_and_install_profile "$USERNAME"
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Phase 10: Finalization
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
set_phase "Finalization"
|
||||||
|
|
||||||
|
configure_usbguard
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Finish
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
finalize_logging
|
||||||
|
|
||||||
|
echo -e "\n\n\n\n\n"
|
||||||
|
print_success "Installation complete!"
|
||||||
|
|
||||||
|
show_ssh_fingerprint
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run main function
|
||||||
|
main "$@"
|
||||||
|
|||||||
222
lib/core/common.sh
Normal file
222
lib/core/common.sh
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
# common.sh - Core utility functions for messaging and user interaction
|
||||||
|
#
|
||||||
|
# Provides colored output, user prompts, and progress tracking used throughout
|
||||||
|
# the installer.
|
||||||
|
|
||||||
|
# Color codes for terminal output
|
||||||
|
readonly COLOR_RED='\033[0;31m'
|
||||||
|
readonly COLOR_GREEN='\033[0;32m'
|
||||||
|
readonly COLOR_YELLOW='\033[0;33m'
|
||||||
|
readonly COLOR_BLUE='\033[0;34m'
|
||||||
|
readonly COLOR_CYAN='\033[0;36m'
|
||||||
|
readonly COLOR_BG_GRAY='\033[48;5;236m'
|
||||||
|
readonly COLOR_RESET='\033[0m'
|
||||||
|
|
||||||
|
# Current installation phase (set by set_phase)
|
||||||
|
CURRENT_PHASE=""
|
||||||
|
|
||||||
|
# Step counter for progress indicator
|
||||||
|
CURRENT_STEP=0
|
||||||
|
TOTAL_STEPS=10
|
||||||
|
|
||||||
|
# Print the installer banner (call once at start)
|
||||||
|
print_banner() {
|
||||||
|
echo ""
|
||||||
|
echo -e "${COLOR_BLUE}:: ${INSTALLER_NAME} ::${COLOR_RESET}"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print a standard message with arrow prefix
|
||||||
|
print() {
|
||||||
|
echo -e "${COLOR_BLUE}→${COLOR_RESET} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print an informational message
|
||||||
|
print_info() {
|
||||||
|
echo -e "${COLOR_CYAN}ℹ${COLOR_RESET} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run a command with gray background for its output
|
||||||
|
# Use this for commands that produce visible output (fdisk, pacstrap, pacman, etc.)
|
||||||
|
run_visible_cmd() {
|
||||||
|
echo -ne "${COLOR_BG_GRAY}"
|
||||||
|
"$@"
|
||||||
|
local exit_code=$?
|
||||||
|
echo -e "${COLOR_RESET}"
|
||||||
|
return $exit_code
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print an installation step/phase header with progress indicator
|
||||||
|
print_step() {
|
||||||
|
local step="$1"
|
||||||
|
CURRENT_STEP=$((CURRENT_STEP + 1))
|
||||||
|
CURRENT_PHASE="$step"
|
||||||
|
echo ""
|
||||||
|
echo -e "${COLOR_BLUE}=== [${CURRENT_STEP}/${TOTAL_STEPS}] ${step} ===${COLOR_RESET}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print a success message
|
||||||
|
print_success() {
|
||||||
|
echo -e "${COLOR_GREEN}[OK]${COLOR_RESET} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print a warning message
|
||||||
|
print_warning() {
|
||||||
|
echo -e "${COLOR_YELLOW}[WARNING]${COLOR_RESET} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print an error message
|
||||||
|
print_error() {
|
||||||
|
echo -e "${COLOR_RED}[ERROR]${COLOR_RESET} $1" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ask for yes/no confirmation
|
||||||
|
# Arguments:
|
||||||
|
# $1 - prompt message
|
||||||
|
# Returns:
|
||||||
|
# 0 if user confirms, 1 otherwise
|
||||||
|
confirm() {
|
||||||
|
local prompt="$1"
|
||||||
|
local response
|
||||||
|
|
||||||
|
print "${prompt} [y/N]: "
|
||||||
|
read -r response
|
||||||
|
|
||||||
|
case "$response" in
|
||||||
|
[yY][eE][sS]|[yY])
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Require "I am sure" confirmation for destructive operations
|
||||||
|
# Arguments:
|
||||||
|
# $1 - warning message
|
||||||
|
# Returns:
|
||||||
|
# 0 if user confirms, 1 otherwise
|
||||||
|
require_confirmation() {
|
||||||
|
local warning="$1"
|
||||||
|
local response
|
||||||
|
|
||||||
|
print "${warning}"
|
||||||
|
print "Enter 'I am sure' exactly to confirm, or anything else to cancel."
|
||||||
|
read -r response
|
||||||
|
|
||||||
|
if [ "$response" = "I am sure" ]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
print "Confirmation failed. Exiting..."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prompt for user input
|
||||||
|
# Arguments:
|
||||||
|
# $1 - prompt message
|
||||||
|
# $2 - variable name to store result
|
||||||
|
prompt() {
|
||||||
|
local prompt_msg="$1"
|
||||||
|
local var_name="$2"
|
||||||
|
local response
|
||||||
|
|
||||||
|
print "$prompt_msg"
|
||||||
|
read -r response
|
||||||
|
eval "$var_name='$response'"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prompt for secret input (no echo)
|
||||||
|
# Arguments:
|
||||||
|
# $1 - prompt message
|
||||||
|
# $2 - variable name to store result
|
||||||
|
prompt_secret() {
|
||||||
|
local prompt_msg="$1"
|
||||||
|
local var_name="$2"
|
||||||
|
local response
|
||||||
|
|
||||||
|
print "$prompt_msg"
|
||||||
|
read -rs response
|
||||||
|
echo
|
||||||
|
eval "$var_name='$response'"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prompt for password with confirmation
|
||||||
|
# Arguments:
|
||||||
|
# $1 - prompt message
|
||||||
|
# $2 - variable name to store result
|
||||||
|
# Returns:
|
||||||
|
# 0 on success, 1 if passwords don't match
|
||||||
|
prompt_password() {
|
||||||
|
local prompt_msg="$1"
|
||||||
|
local var_name="$2"
|
||||||
|
local password
|
||||||
|
local password_confirm
|
||||||
|
|
||||||
|
print "$prompt_msg"
|
||||||
|
read -rs password
|
||||||
|
echo
|
||||||
|
|
||||||
|
print "Please confirm your password."
|
||||||
|
read -rs password_confirm
|
||||||
|
echo
|
||||||
|
|
||||||
|
if [ "$password" != "$password_confirm" ]; then
|
||||||
|
print_error "Passwords do not match."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval "$var_name='$password'"
|
||||||
|
unset password password_confirm
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Display a menu and get user selection
|
||||||
|
# Arguments:
|
||||||
|
# $1 - menu title
|
||||||
|
# $@ - menu options (remaining arguments)
|
||||||
|
# Returns:
|
||||||
|
# Selected option number in MENU_SELECTION variable
|
||||||
|
prompt_menu() {
|
||||||
|
local title="$1"
|
||||||
|
shift
|
||||||
|
local options=("$@")
|
||||||
|
local i=1
|
||||||
|
|
||||||
|
print "$title"
|
||||||
|
for option in "${options[@]}"; do
|
||||||
|
print " $i - $option"
|
||||||
|
((i++))
|
||||||
|
done
|
||||||
|
|
||||||
|
read -r MENU_SELECTION
|
||||||
|
}
|
||||||
|
|
||||||
|
# Wait for user to press enter
|
||||||
|
wait_for_enter() {
|
||||||
|
local message="${1:-Press enter to continue.}"
|
||||||
|
print "$message"
|
||||||
|
read -r
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get the directory containing the main script
|
||||||
|
get_script_dir() {
|
||||||
|
echo "$SCRIPT_DIR"
|
||||||
|
}
|
||||||
124
lib/core/error.sh
Normal file
124
lib/core/error.sh
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
# error.sh - Error handling framework
|
||||||
|
#
|
||||||
|
# Implements robust error handling for the installation process:
|
||||||
|
# - Sets up bash strict mode (set -e) and ERR trap
|
||||||
|
# - Provides detailed error messages with phase, line number, and failed command
|
||||||
|
# - Offers automatic cleanup on failure (unmount filesystems, close LUKS)
|
||||||
|
# - Includes retry helper for transient failures
|
||||||
|
|
||||||
|
# Enable strict error handling
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Set up error trap
|
||||||
|
trap_errors() {
|
||||||
|
trap 'handle_error $? $LINENO "$BASH_COMMAND"' ERR
|
||||||
|
}
|
||||||
|
|
||||||
|
# Error handler function
|
||||||
|
# Arguments:
|
||||||
|
# $1 - exit code
|
||||||
|
# $2 - line number
|
||||||
|
# $3 - failed command
|
||||||
|
handle_error() {
|
||||||
|
local exit_code=$1
|
||||||
|
local line_number=$2
|
||||||
|
local command="$3"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
print_error "Installation failed!"
|
||||||
|
print_error "Phase: ${CURRENT_PHASE:-unknown}"
|
||||||
|
print_error "Exit code: $exit_code at line $line_number"
|
||||||
|
print_error "Command: $command"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Offer cleanup
|
||||||
|
print "Would you like to attempt cleanup? [y/N]: "
|
||||||
|
read -r response
|
||||||
|
|
||||||
|
case "$response" in
|
||||||
|
[yY][eE][sS]|[yY])
|
||||||
|
cleanup_on_error
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cleanup function for error recovery
|
||||||
|
cleanup_on_error() {
|
||||||
|
print_warning "Cleaning up after error..."
|
||||||
|
|
||||||
|
# Unmount filesystems (ignore errors)
|
||||||
|
umount -R "${MOUNT_POINT}" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Close LUKS containers (ignore errors)
|
||||||
|
cryptsetup close cryptroot 2>/dev/null || true
|
||||||
|
cryptsetup close cryptroot-primary 2>/dev/null || true
|
||||||
|
cryptsetup close cryptroot-secondary 2>/dev/null || true
|
||||||
|
|
||||||
|
print "Cleanup complete. You may retry the installation."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set the current installation phase for better error messages
|
||||||
|
# Arguments:
|
||||||
|
# $1 - phase name
|
||||||
|
set_phase() {
|
||||||
|
CURRENT_PHASE="$1"
|
||||||
|
print_step "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run a command with error context
|
||||||
|
# Arguments:
|
||||||
|
# $1 - description
|
||||||
|
# $@ - command and arguments
|
||||||
|
safe_run() {
|
||||||
|
local description="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
print " $description..."
|
||||||
|
if ! "$@"; then
|
||||||
|
print_error "Failed: $description"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Retry a command with exponential backoff
|
||||||
|
# Arguments:
|
||||||
|
# $1 - max attempts
|
||||||
|
# $2 - initial delay in seconds
|
||||||
|
# $@ - command and arguments
|
||||||
|
retry() {
|
||||||
|
local max_attempts="$1"
|
||||||
|
local delay="$2"
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
local attempt=1
|
||||||
|
while [ $attempt -le $max_attempts ]; do
|
||||||
|
if "$@"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
print_warning "Attempt $attempt/$max_attempts failed. Retrying in ${delay}s..."
|
||||||
|
sleep "$delay"
|
||||||
|
((attempt++))
|
||||||
|
((delay *= 2))
|
||||||
|
done
|
||||||
|
|
||||||
|
print_error "All $max_attempts attempts failed."
|
||||||
|
return 1
|
||||||
|
}
|
||||||
71
lib/core/logging.sh
Normal file
71
lib/core/logging.sh
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
# logging.sh - Installation logging
|
||||||
|
#
|
||||||
|
# Captures the complete installation session for troubleshooting:
|
||||||
|
# - Redirects all stdout/stderr to both console and log file
|
||||||
|
# - Records timestamps at start and end of installation
|
||||||
|
# - Includes git commit hash for version tracking
|
||||||
|
# - Copies final log to /var/log/arch-install.log on installed system
|
||||||
|
|
||||||
|
# Temp location during installation (installed system's /var/log doesn't exist yet)
|
||||||
|
LOG_FILE_TEMP="/tmp/arch-install.log"
|
||||||
|
# Final location on the installed system
|
||||||
|
LOG_FILE="/var/log/arch-install.log"
|
||||||
|
GITEA_URL="https://git.logal.dev/LogalDeveloper/Arch-Linux-Installer"
|
||||||
|
|
||||||
|
# Print installer URL with commit if available
|
||||||
|
print_installer_url() {
|
||||||
|
if command -v git &>/dev/null && git -C "$SCRIPT_DIR" rev-parse --git-dir &>/dev/null 2>&1; then
|
||||||
|
local commit
|
||||||
|
commit=$(git -C "$SCRIPT_DIR" rev-parse HEAD)
|
||||||
|
echo "=== Installer: ${GITEA_URL}/commit/${commit} ==="
|
||||||
|
else
|
||||||
|
echo "=== Installer: ${GITEA_URL} ==="
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initialize logging - tee all output to log file
|
||||||
|
init_logging() {
|
||||||
|
# Create log file with secure permissions
|
||||||
|
touch "$LOG_FILE_TEMP"
|
||||||
|
chown root:root "$LOG_FILE_TEMP"
|
||||||
|
chmod 640 "$LOG_FILE_TEMP"
|
||||||
|
|
||||||
|
# Redirect stdout and stderr to both console and log
|
||||||
|
exec > >(tee -a "$LOG_FILE_TEMP") 2>&1
|
||||||
|
|
||||||
|
# Write log header
|
||||||
|
echo "=== Installation started at $(date) ==="
|
||||||
|
print_installer_url
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copy log file to installed system
|
||||||
|
finalize_logging() {
|
||||||
|
local final_log="${MOUNT_POINT}${LOG_FILE}"
|
||||||
|
|
||||||
|
# Write log footer
|
||||||
|
echo ""
|
||||||
|
echo "=== Installation finished at $(date) ==="
|
||||||
|
print_installer_url
|
||||||
|
echo "=== Log saved to: ${LOG_FILE} ==="
|
||||||
|
|
||||||
|
cp "$LOG_FILE_TEMP" "$final_log"
|
||||||
|
chown root:root "$final_log"
|
||||||
|
chmod 640 "$final_log"
|
||||||
|
}
|
||||||
138
lib/core/validation.sh
Normal file
138
lib/core/validation.sh
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
# validation.sh - Input validation functions
|
||||||
|
#
|
||||||
|
# Validates user input (disks, usernames, menu selections) to prevent errors
|
||||||
|
# during installation.
|
||||||
|
|
||||||
|
# Validate that a disk exists
|
||||||
|
# Arguments:
|
||||||
|
# $1 - disk path (e.g., /dev/sda)
|
||||||
|
# Returns:
|
||||||
|
# 0 if disk exists, 1 otherwise
|
||||||
|
validate_disk_exists() {
|
||||||
|
local disk="$1"
|
||||||
|
|
||||||
|
if [ ! -b "$disk" ]; then
|
||||||
|
print_error "Disk '$disk' does not exist or is not a block device."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate that a disk is not mounted
|
||||||
|
# Arguments:
|
||||||
|
# $1 - disk path
|
||||||
|
# Returns:
|
||||||
|
# 0 if not mounted, 1 if mounted
|
||||||
|
validate_disk_not_mounted() {
|
||||||
|
local disk="$1"
|
||||||
|
|
||||||
|
if mount | grep -q "^${disk}"; then
|
||||||
|
print_error "Disk '$disk' appears to be mounted. Please unmount it first."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate that two disks are different (for RAID1)
|
||||||
|
# Arguments:
|
||||||
|
# $1 - first disk path
|
||||||
|
# $2 - second disk path
|
||||||
|
# Returns:
|
||||||
|
# 0 if different, 1 if same
|
||||||
|
validate_disks_different() {
|
||||||
|
local disk1="$1"
|
||||||
|
local disk2="$2"
|
||||||
|
|
||||||
|
if [ "$disk1" = "$disk2" ]; then
|
||||||
|
print_error "Both disks must be different. You entered the same disk twice."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate username format
|
||||||
|
# Arguments:
|
||||||
|
# $1 - username
|
||||||
|
# Returns:
|
||||||
|
# 0 if valid, 1 otherwise
|
||||||
|
validate_username() {
|
||||||
|
local username="$1"
|
||||||
|
|
||||||
|
# Check if empty
|
||||||
|
if [ -z "$username" ]; then
|
||||||
|
print_error "Username cannot be empty."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check length (max 32 chars)
|
||||||
|
if [ ${#username} -gt 32 ]; then
|
||||||
|
print_error "Username must be 32 characters or less."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check format (lowercase letters, digits, underscore, hyphen; must start with letter)
|
||||||
|
if ! [[ "$username" =~ ^[a-z][a-z0-9_-]*$ ]]; then
|
||||||
|
print_error "Username must start with a lowercase letter and contain only lowercase letters, digits, underscores, and hyphens."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate menu selection is in range
|
||||||
|
# Arguments:
|
||||||
|
# $1 - selection
|
||||||
|
# $2 - minimum value
|
||||||
|
# $3 - maximum value
|
||||||
|
# Returns:
|
||||||
|
# 0 if valid, 1 otherwise
|
||||||
|
validate_menu_selection() {
|
||||||
|
local selection="$1"
|
||||||
|
local min="$2"
|
||||||
|
local max="$3"
|
||||||
|
|
||||||
|
# Check if it's a number
|
||||||
|
if ! [[ "$selection" =~ ^[0-9]+$ ]]; then
|
||||||
|
print_error "Please enter a number between $min and $max."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check range
|
||||||
|
if [ "$selection" -lt "$min" ] || [ "$selection" -gt "$max" ]; then
|
||||||
|
print_error "Please enter a number between $min and $max."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate password is not empty
|
||||||
|
# Arguments:
|
||||||
|
# $1 - password
|
||||||
|
# Returns:
|
||||||
|
# 0 if valid, 1 otherwise
|
||||||
|
validate_password_not_empty() {
|
||||||
|
local password="$1"
|
||||||
|
|
||||||
|
if [ -z "$password" ]; then
|
||||||
|
print_error "Password cannot be empty."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
64
lib/desktop/drivers.sh
Normal file
64
lib/desktop/drivers.sh
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
# drivers.sh - Graphics driver installation
|
||||||
|
#
|
||||||
|
# Prompts the user to select and install graphics drivers (Intel, NVIDIA, or skip).
|
||||||
|
|
||||||
|
# Intel graphics packages
|
||||||
|
INTEL_PACKAGES=(
|
||||||
|
mesa
|
||||||
|
vulkan-intel
|
||||||
|
intel-media-driver
|
||||||
|
libva-intel-driver
|
||||||
|
)
|
||||||
|
|
||||||
|
# NVIDIA graphics packages
|
||||||
|
NVIDIA_PACKAGES=(
|
||||||
|
mesa
|
||||||
|
nvidia-open
|
||||||
|
libva-nvidia-driver
|
||||||
|
)
|
||||||
|
|
||||||
|
# Install Intel graphics drivers
|
||||||
|
install_intel_graphics() {
|
||||||
|
print "Installing Intel graphics drivers..."
|
||||||
|
chroot_install "${INTEL_PACKAGES[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install NVIDIA graphics drivers
|
||||||
|
install_nvidia_graphics() {
|
||||||
|
print "Installing NVIDIA graphics drivers..."
|
||||||
|
chroot_install "${NVIDIA_PACKAGES[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prompt user for graphics driver selection and install
|
||||||
|
prompt_install_graphics() {
|
||||||
|
print "Would you like to install graphics drivers? Type 'intel' exactly for Intel graphics drivers, 'nvidia' for NVIDIA graphics drivers, or anything else to skip."
|
||||||
|
read -r driver
|
||||||
|
|
||||||
|
case "$driver" in
|
||||||
|
"intel")
|
||||||
|
install_intel_graphics
|
||||||
|
;;
|
||||||
|
"nvidia")
|
||||||
|
install_nvidia_graphics
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
print "Skipping graphics driver installation."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
115
lib/desktop/xfce.sh
Normal file
115
lib/desktop/xfce.sh
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
# xfce.sh - XFCE desktop environment installation
|
||||||
|
#
|
||||||
|
# Installs XFCE4 with LightDM and copies pre-configured user settings.
|
||||||
|
|
||||||
|
# XFCE base packages
|
||||||
|
XFCE_PACKAGES=(
|
||||||
|
lightdm
|
||||||
|
lightdm-gtk-greeter
|
||||||
|
lightdm-gtk-greeter-settings
|
||||||
|
thunar
|
||||||
|
thunar-archive-plugin
|
||||||
|
gvfs
|
||||||
|
xfce4-panel
|
||||||
|
xfce4-power-manager
|
||||||
|
xfce4-session
|
||||||
|
xfce4-settings
|
||||||
|
xfce4-terminal
|
||||||
|
xfdesktop
|
||||||
|
xfwm4
|
||||||
|
papirus-icon-theme
|
||||||
|
xfce4-battery-plugin
|
||||||
|
xfce4-notifyd
|
||||||
|
xfce4-whiskermenu-plugin
|
||||||
|
xfce4-screensaver
|
||||||
|
xfce4-screenshooter
|
||||||
|
mousepad
|
||||||
|
noto-fonts
|
||||||
|
noto-fonts-cjk
|
||||||
|
noto-fonts-emoji
|
||||||
|
noto-fonts-extra
|
||||||
|
pipewire
|
||||||
|
pipewire-alsa
|
||||||
|
pipewire-pulse
|
||||||
|
pipewire-jack
|
||||||
|
wireplumber
|
||||||
|
pavucontrol
|
||||||
|
xfce4-pulseaudio-plugin
|
||||||
|
ristretto
|
||||||
|
webp-pixbuf-loader
|
||||||
|
libopenraw
|
||||||
|
xarchiver
|
||||||
|
7zip
|
||||||
|
xreader
|
||||||
|
)
|
||||||
|
|
||||||
|
# Install XFCE base packages
|
||||||
|
install_xfce_packages() {
|
||||||
|
chroot_install "${XFCE_PACKAGES[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Enable LightDM display manager
|
||||||
|
enable_lightdm() {
|
||||||
|
chroot_enable lightdm.service
|
||||||
|
}
|
||||||
|
|
||||||
|
# Configure LightDM greeter
|
||||||
|
configure_lightdm() {
|
||||||
|
chroot_run sh -c "cat > /etc/lightdm/lightdm-gtk-greeter.conf" <<EOF
|
||||||
|
[greeter]
|
||||||
|
hide-user-image = true
|
||||||
|
font-name = Noto Sans 10
|
||||||
|
clock-format = %A, %B %d, %Y,%l:%M:%S %p
|
||||||
|
theme-name = Adwaita-dark
|
||||||
|
icon-theme-name = Papirus-Dark
|
||||||
|
screensaver-timeout = 10
|
||||||
|
user-background = false
|
||||||
|
background = #77767b
|
||||||
|
indicators = ~host;~spacer;~clock;~spacer;~power
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copy default XFCE configuration to user home
|
||||||
|
# Arguments:
|
||||||
|
# $1 - username
|
||||||
|
copy_xfce_config() {
|
||||||
|
local username="$1"
|
||||||
|
local home_dir="${MOUNT_POINT}/home/${username}"
|
||||||
|
|
||||||
|
cp -r "${HOME_CONFIG_DIR}" "${home_dir}/.config"
|
||||||
|
|
||||||
|
# Disable tumblerd (thumbnail service)
|
||||||
|
mkdir -p "${home_dir}/.config/systemd/user"
|
||||||
|
ln -s /dev/null "${home_dir}/.config/systemd/user/tumblerd.service"
|
||||||
|
|
||||||
|
# Set correct ownership (UID 1000 is typically first user)
|
||||||
|
chown -R 1000:1000 "${home_dir}/.config"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Full XFCE installation
|
||||||
|
# Arguments:
|
||||||
|
# $1 - username
|
||||||
|
install_xfce() {
|
||||||
|
local username="$1"
|
||||||
|
|
||||||
|
install_xfce_packages
|
||||||
|
enable_lightdm
|
||||||
|
configure_lightdm
|
||||||
|
copy_xfce_config "$username"
|
||||||
|
}
|
||||||
163
lib/disk/filesystem.sh
Normal file
163
lib/disk/filesystem.sh
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
# filesystem.sh - Filesystem creation and mounting functions
|
||||||
|
#
|
||||||
|
# Creates and mounts filesystems on encrypted volumes:
|
||||||
|
# - Formats EFI partition as FAT32
|
||||||
|
# - Supports ext4, BTRFS, BTRFS with DUP, and BTRFS RAID1
|
||||||
|
# - Uses xxhash checksum for BTRFS filesystems
|
||||||
|
# - Mounts root with noatime and appropriate discard options
|
||||||
|
# - Mounts EFI partition at /boot with restrictive permissions
|
||||||
|
|
||||||
|
# Format a partition as FAT32 (for EFI)
|
||||||
|
# Arguments:
|
||||||
|
# $1 - partition path
|
||||||
|
format_efi_partition() {
|
||||||
|
local partition="$1"
|
||||||
|
|
||||||
|
print "Formatting ${partition} as FAT32..."
|
||||||
|
run_visible_cmd mkfs.fat -F 32 "$partition"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Format a device as ext4
|
||||||
|
# Arguments:
|
||||||
|
# $1 - device path (e.g., /dev/mapper/cryptroot)
|
||||||
|
format_ext4() {
|
||||||
|
local device="$1"
|
||||||
|
|
||||||
|
print "Formatting ${device} as ext4..."
|
||||||
|
run_visible_cmd mkfs.ext4 "$device"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Format a device as BTRFS (single disk)
|
||||||
|
# Arguments:
|
||||||
|
# $1 - device path
|
||||||
|
format_btrfs() {
|
||||||
|
local device="$1"
|
||||||
|
|
||||||
|
print "Formatting ${device} as btrfs..."
|
||||||
|
run_visible_cmd mkfs.btrfs --csum xxhash "$device"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Format a device as BTRFS with dup profile
|
||||||
|
# Arguments:
|
||||||
|
# $1 - device path
|
||||||
|
format_btrfs_dup() {
|
||||||
|
local device="$1"
|
||||||
|
|
||||||
|
print "Formatting ${device} as btrfs with dup profile..."
|
||||||
|
run_visible_cmd mkfs.btrfs --csum xxhash --data dup --metadata dup "$device"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Format two devices as BTRFS RAID1
|
||||||
|
# Arguments:
|
||||||
|
# $1 - first device path
|
||||||
|
# $2 - second device path
|
||||||
|
format_btrfs_raid1() {
|
||||||
|
local device1="$1"
|
||||||
|
local device2="$2"
|
||||||
|
|
||||||
|
print "Formatting ${device1} and ${device2} as btrfs RAID1..."
|
||||||
|
run_visible_cmd mkfs.btrfs --csum xxhash --data raid1 --metadata raid1 "$device1" "$device2"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Format the root filesystem based on configuration
|
||||||
|
# Arguments:
|
||||||
|
# $1 - filesystem type (ext4, btrfs, btrfs-dup)
|
||||||
|
# $2 - storage mode (single, raid1)
|
||||||
|
format_root_filesystem() {
|
||||||
|
local filesystem="$1"
|
||||||
|
local storage_mode="$2"
|
||||||
|
|
||||||
|
case "$filesystem" in
|
||||||
|
"ext4")
|
||||||
|
if [ "$storage_mode" = "raid1" ]; then
|
||||||
|
print_error "ext4 cannot be used with RAID1."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
format_ext4 /dev/mapper/cryptroot
|
||||||
|
;;
|
||||||
|
|
||||||
|
"btrfs-dup")
|
||||||
|
format_btrfs_dup /dev/mapper/cryptroot
|
||||||
|
;;
|
||||||
|
|
||||||
|
"btrfs"|*)
|
||||||
|
if [ "$storage_mode" = "raid1" ]; then
|
||||||
|
format_btrfs_raid1 /dev/mapper/cryptroot-primary /dev/mapper/cryptroot-secondary
|
||||||
|
else
|
||||||
|
format_btrfs /dev/mapper/cryptroot
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mount the root filesystem with appropriate options
|
||||||
|
# Arguments:
|
||||||
|
# $1 - filesystem type (ext4, btrfs, btrfs-dup)
|
||||||
|
# $2 - storage mode (single, raid1)
|
||||||
|
mount_root_filesystem() {
|
||||||
|
local filesystem="$1"
|
||||||
|
local storage_mode="$2"
|
||||||
|
|
||||||
|
print "Mounting partitions..."
|
||||||
|
|
||||||
|
case "$filesystem" in
|
||||||
|
"ext4")
|
||||||
|
mount -o "noatime,discard" /dev/mapper/cryptroot "${MOUNT_POINT}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
if [ "$storage_mode" = "raid1" ]; then
|
||||||
|
mount -o "noatime,discard=async" /dev/mapper/cryptroot-primary "${MOUNT_POINT}"
|
||||||
|
else
|
||||||
|
mount -o "noatime,discard=async" /dev/mapper/cryptroot "${MOUNT_POINT}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mount the EFI partition
|
||||||
|
# Arguments:
|
||||||
|
# $1 - EFI partition path
|
||||||
|
mount_efi_partition() {
|
||||||
|
local efi_partition="$1"
|
||||||
|
|
||||||
|
mount --mkdir -o "fmask=0077,dmask=0077" "$efi_partition" "${MOUNT_POINT}/boot"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Format and mount all filesystems
|
||||||
|
# Arguments:
|
||||||
|
# $1 - filesystem type
|
||||||
|
# $2 - storage mode
|
||||||
|
format_and_mount_filesystems() {
|
||||||
|
local filesystem="$1"
|
||||||
|
local storage_mode="$2"
|
||||||
|
|
||||||
|
# Format EFI partition(s)
|
||||||
|
format_efi_partition "$EFI_PARTITION"
|
||||||
|
if [ "$storage_mode" = "raid1" ]; then
|
||||||
|
format_efi_partition "$EFI_PARTITION_2"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Format and mount root
|
||||||
|
format_root_filesystem "$filesystem" "$storage_mode"
|
||||||
|
mount_root_filesystem "$filesystem" "$storage_mode"
|
||||||
|
|
||||||
|
# Mount EFI
|
||||||
|
mount_efi_partition "$EFI_PARTITION"
|
||||||
|
}
|
||||||
169
lib/disk/luks.sh
Normal file
169
lib/disk/luks.sh
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
# luks.sh - LUKS encryption functions
|
||||||
|
#
|
||||||
|
# Manages full-disk encryption using LUKS2:
|
||||||
|
# - Prompts for encryption passphrase with confirmation
|
||||||
|
# - Formats partitions with LUKS2 using secure defaults from luks.conf
|
||||||
|
# - Opens/unlocks encrypted containers for filesystem creation
|
||||||
|
# - Supports both single-disk and RAID1 encryption setups
|
||||||
|
# - Securely clears passwords from memory after use
|
||||||
|
|
||||||
|
# Format a partition with LUKS encryption
|
||||||
|
# Arguments:
|
||||||
|
# $1 - partition path
|
||||||
|
# $2 - encryption password
|
||||||
|
setup_luks_encryption() {
|
||||||
|
local partition="$1"
|
||||||
|
local password="$2"
|
||||||
|
|
||||||
|
print "Setting up encryption on ${partition}..."
|
||||||
|
|
||||||
|
# Wrapped manually (not using run_visible_cmd) due to piped password input
|
||||||
|
echo -ne "${COLOR_BG_GRAY}"
|
||||||
|
echo -n "$password" | cryptsetup luksFormat \
|
||||||
|
--type "$LUKS_TYPE" \
|
||||||
|
--cipher "$LUKS_CIPHER" \
|
||||||
|
--hash "$LUKS_HASH" \
|
||||||
|
--key-size "$LUKS_KEY_SIZE" \
|
||||||
|
--pbkdf "$LUKS_PBKDF" \
|
||||||
|
--pbkdf-force-iterations "$LUKS_PBKDF_ITERATIONS" \
|
||||||
|
--pbkdf-memory "$LUKS_PBKDF_MEMORY" \
|
||||||
|
--pbkdf-parallel "$LUKS_PBKDF_PARALLEL" \
|
||||||
|
--use-urandom \
|
||||||
|
--key-file - \
|
||||||
|
"$partition"
|
||||||
|
echo -e "${COLOR_RESET}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Open (unlock) a LUKS container
|
||||||
|
# Arguments:
|
||||||
|
# $1 - partition path
|
||||||
|
# $2 - encryption password
|
||||||
|
# $3 - mapper name (e.g., cryptroot)
|
||||||
|
open_luks_container() {
|
||||||
|
local partition="$1"
|
||||||
|
local password="$2"
|
||||||
|
local mapper_name="$3"
|
||||||
|
|
||||||
|
print "Unlocking ${partition}..."
|
||||||
|
|
||||||
|
# Wrapped manually (not using run_visible_cmd) due to piped password input
|
||||||
|
echo -ne "${COLOR_BG_GRAY}"
|
||||||
|
echo -n "$password" | cryptsetup open \
|
||||||
|
--allow-discards \
|
||||||
|
--key-file - \
|
||||||
|
"$partition" \
|
||||||
|
"$mapper_name"
|
||||||
|
echo -e "${COLOR_RESET}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get the UUID of a LUKS container
|
||||||
|
# Arguments:
|
||||||
|
# $1 - partition path
|
||||||
|
# Outputs:
|
||||||
|
# UUID to stdout
|
||||||
|
get_luks_uuid() {
|
||||||
|
local partition="$1"
|
||||||
|
|
||||||
|
cryptsetup luksDump "$partition" | grep 'UUID:' | awk '{print $2}'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Close a LUKS container
|
||||||
|
# Arguments:
|
||||||
|
# $1 - mapper name
|
||||||
|
close_luks_container() {
|
||||||
|
local mapper_name="$1"
|
||||||
|
|
||||||
|
cryptsetup close "$mapper_name" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prompt for encryption password with confirmation
|
||||||
|
# Sets:
|
||||||
|
# ENCRYPTION_PASSWORD - the entered password
|
||||||
|
# Returns:
|
||||||
|
# 0 on success, 1 on mismatch
|
||||||
|
prompt_encryption_password() {
|
||||||
|
local password
|
||||||
|
local password_confirm
|
||||||
|
|
||||||
|
print "Please enter your desired encryption passphrase."
|
||||||
|
read -rs password
|
||||||
|
echo
|
||||||
|
|
||||||
|
print "Please confirm your encryption passphrase."
|
||||||
|
read -rs password_confirm
|
||||||
|
echo
|
||||||
|
|
||||||
|
if [ "$password" != "$password_confirm" ]; then
|
||||||
|
print_error "Passphrases do not match."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ENCRYPTION_PASSWORD="$password"
|
||||||
|
unset password password_confirm
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Setup encryption for single-disk mode
|
||||||
|
# Arguments:
|
||||||
|
# $1 - root partition path
|
||||||
|
# Sets:
|
||||||
|
# LUKS_UUID - UUID of the LUKS container
|
||||||
|
setup_encryption_single() {
|
||||||
|
local root_partition="$1"
|
||||||
|
|
||||||
|
if ! prompt_encryption_password; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
setup_luks_encryption "$root_partition" "$ENCRYPTION_PASSWORD"
|
||||||
|
open_luks_container "$root_partition" "$ENCRYPTION_PASSWORD" "cryptroot"
|
||||||
|
LUKS_UUID=$(get_luks_uuid "$root_partition")
|
||||||
|
|
||||||
|
# Clear password from memory
|
||||||
|
unset ENCRYPTION_PASSWORD
|
||||||
|
}
|
||||||
|
|
||||||
|
# Setup encryption for RAID1 mode
|
||||||
|
# Arguments:
|
||||||
|
# $1 - first root partition path
|
||||||
|
# $2 - second root partition path
|
||||||
|
# Sets:
|
||||||
|
# LUKS_UUID - UUID of the first LUKS container
|
||||||
|
# LUKS_UUID_2 - UUID of the second LUKS container
|
||||||
|
setup_encryption_raid1() {
|
||||||
|
local root_partition_1="$1"
|
||||||
|
local root_partition_2="$2"
|
||||||
|
|
||||||
|
if ! prompt_encryption_password; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Setup first disk
|
||||||
|
setup_luks_encryption "$root_partition_1" "$ENCRYPTION_PASSWORD"
|
||||||
|
open_luks_container "$root_partition_1" "$ENCRYPTION_PASSWORD" "cryptroot-primary"
|
||||||
|
LUKS_UUID=$(get_luks_uuid "$root_partition_1")
|
||||||
|
|
||||||
|
# Setup second disk
|
||||||
|
setup_luks_encryption "$root_partition_2" "$ENCRYPTION_PASSWORD"
|
||||||
|
open_luks_container "$root_partition_2" "$ENCRYPTION_PASSWORD" "cryptroot-secondary"
|
||||||
|
LUKS_UUID_2=$(get_luks_uuid "$root_partition_2")
|
||||||
|
|
||||||
|
# Clear password from memory
|
||||||
|
unset ENCRYPTION_PASSWORD
|
||||||
|
}
|
||||||
187
lib/disk/partition.sh
Normal file
187
lib/disk/partition.sh
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
# partition.sh - Disk partitioning functions
|
||||||
|
#
|
||||||
|
# Handles disk selection and GPT partitioning for both single and RAID1 modes:
|
||||||
|
# - Interactive disk selection with confirmation prompts
|
||||||
|
# - Wipes existing partition tables using wipefs
|
||||||
|
# - Creates GPT layout: 1GB EFI System Partition + LUKS root partition
|
||||||
|
# - Handles NVMe vs SATA partition naming conventions (nvme0n1p1 vs sda1)
|
||||||
|
|
||||||
|
# Detect the correct partition prefix for a disk
|
||||||
|
# NVMe drives use "p" before partition number (e.g., /dev/nvme0n1p1)
|
||||||
|
# SATA/SAS drives append number directly (e.g., /dev/sda1)
|
||||||
|
# Arguments:
|
||||||
|
# $1 - disk path
|
||||||
|
# Outputs:
|
||||||
|
# Partition prefix to stdout
|
||||||
|
detect_partition_prefix() {
|
||||||
|
local disk="$1"
|
||||||
|
|
||||||
|
if [[ "$disk" == /dev/nvme* ]]; then
|
||||||
|
echo "${disk}p"
|
||||||
|
else
|
||||||
|
echo "$disk"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Display disk information using fdisk
|
||||||
|
show_disk_info() {
|
||||||
|
run_visible_cmd fdisk -l
|
||||||
|
}
|
||||||
|
|
||||||
|
# Select a disk with confirmation
|
||||||
|
# Arguments:
|
||||||
|
# $1 - prompt message
|
||||||
|
# $2 - variable name to store selected disk
|
||||||
|
# Returns:
|
||||||
|
# 0 on success, 1 on validation failure
|
||||||
|
select_disk() {
|
||||||
|
local prompt_msg="$1"
|
||||||
|
local var_name="$2"
|
||||||
|
local selected_disk
|
||||||
|
local disk_confirm
|
||||||
|
|
||||||
|
print "$prompt_msg"
|
||||||
|
read -r selected_disk
|
||||||
|
|
||||||
|
# Validate disk exists
|
||||||
|
if ! validate_disk_exists "$selected_disk"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Confirm selection
|
||||||
|
print "Please confirm your selection by entering the same path again."
|
||||||
|
read -r disk_confirm
|
||||||
|
|
||||||
|
if [ "$selected_disk" != "$disk_confirm" ]; then
|
||||||
|
print_error "The same disk was not entered both times."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval "$var_name='$selected_disk'"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Wipe all filesystem signatures from a disk
|
||||||
|
# Arguments:
|
||||||
|
# $1 - disk path
|
||||||
|
wipe_disk() {
|
||||||
|
local disk="$1"
|
||||||
|
|
||||||
|
print "Wiping existing partition table from $disk..."
|
||||||
|
run_visible_cmd wipefs -a "$disk"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create GPT partitions for EFI + root
|
||||||
|
# Arguments:
|
||||||
|
# $1 - disk path
|
||||||
|
# Sets:
|
||||||
|
# EFI_PARTITION - path to EFI partition
|
||||||
|
# ROOT_PARTITION - path to root partition
|
||||||
|
create_gpt_partitions() {
|
||||||
|
local disk="$1"
|
||||||
|
local prefix
|
||||||
|
|
||||||
|
print "Partitioning $disk..."
|
||||||
|
|
||||||
|
# Create GPT table with 1GB EFI partition
|
||||||
|
run_visible_cmd sgdisk --new 1:0:1G "$disk"
|
||||||
|
run_visible_cmd sgdisk --typecode 1:ef00 "$disk"
|
||||||
|
|
||||||
|
# Create root partition using remaining space
|
||||||
|
run_visible_cmd sgdisk --new 2:0:0 "$disk"
|
||||||
|
run_visible_cmd sgdisk --typecode 2:8309 "$disk"
|
||||||
|
|
||||||
|
# Set partition paths
|
||||||
|
prefix=$(detect_partition_prefix "$disk")
|
||||||
|
EFI_PARTITION="${prefix}1"
|
||||||
|
ROOT_PARTITION="${prefix}2"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Select and configure disks for single-disk installation
|
||||||
|
# Sets:
|
||||||
|
# INSTALL_DISK - primary disk path
|
||||||
|
# EFI_PARTITION - EFI partition path
|
||||||
|
# ROOT_PARTITION - root partition path
|
||||||
|
select_single_disk() {
|
||||||
|
show_disk_info
|
||||||
|
|
||||||
|
if ! select_disk "Please enter the path to the disk you would like to install Arch Linux to (e.g. /dev/sda)." INSTALL_DISK; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! require_confirmation "Last warning: Are you sure you want to install Arch Linux to '$INSTALL_DISK'? All data on this disk will be wiped."; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Select and configure disks for RAID1 installation
|
||||||
|
# Sets:
|
||||||
|
# INSTALL_DISK - first disk path
|
||||||
|
# INSTALL_DISK_2 - second disk path
|
||||||
|
# EFI_PARTITION, EFI_PARTITION_2 - EFI partition paths
|
||||||
|
# ROOT_PARTITION, ROOT_PARTITION_2 - root partition paths
|
||||||
|
select_raid1_disks() {
|
||||||
|
show_disk_info
|
||||||
|
|
||||||
|
if ! select_disk "Please enter the path to the FIRST disk for RAID1 (e.g. /dev/sda)." INSTALL_DISK; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! select_disk "Enter the path to the SECOND disk for RAID1 (e.g. /dev/sdb). This must be a DIFFERENT disk." INSTALL_DISK_2; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! validate_disks_different "$INSTALL_DISK" "$INSTALL_DISK_2"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! require_confirmation "Last warning: Are you sure you want to install Arch Linux in RAID1 mode to '$INSTALL_DISK' and '$INSTALL_DISK_2'? All data on BOTH disks will be wiped."; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Partition disks based on storage mode
|
||||||
|
# Arguments:
|
||||||
|
# $1 - storage mode (single or raid1)
|
||||||
|
partition_disks() {
|
||||||
|
local storage_mode="$1"
|
||||||
|
|
||||||
|
if [ "$storage_mode" = "raid1" ]; then
|
||||||
|
wipe_disk "$INSTALL_DISK"
|
||||||
|
wipe_disk "$INSTALL_DISK_2"
|
||||||
|
|
||||||
|
create_gpt_partitions "$INSTALL_DISK"
|
||||||
|
local primary_efi="$EFI_PARTITION"
|
||||||
|
local primary_root="$ROOT_PARTITION"
|
||||||
|
|
||||||
|
create_gpt_partitions "$INSTALL_DISK_2"
|
||||||
|
EFI_PARTITION_2="$EFI_PARTITION"
|
||||||
|
ROOT_PARTITION_2="$ROOT_PARTITION"
|
||||||
|
|
||||||
|
EFI_PARTITION="$primary_efi"
|
||||||
|
ROOT_PARTITION="$primary_root"
|
||||||
|
else
|
||||||
|
wipe_disk "$INSTALL_DISK"
|
||||||
|
create_gpt_partitions "$INSTALL_DISK"
|
||||||
|
fi
|
||||||
|
}
|
||||||
104
lib/system/base.sh
Normal file
104
lib/system/base.sh
Normal 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
88
lib/system/bootloader.sh
Normal 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
43
lib/system/locale.sh
Normal 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
95
lib/system/network.sh
Normal 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
131
lib/system/security.sh
Normal 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
66
lib/system/user.sh
Normal 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"
|
||||||
|
}
|
||||||
73
profiles/packages.sh
Normal file
73
profiles/packages.sh
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
# packages.sh - Shared package group definitions
|
||||||
|
#
|
||||||
|
# Defines reusable package arrays used by installation profiles.
|
||||||
|
|
||||||
|
# Media packages (browser, video player)
|
||||||
|
PACKAGES_MEDIA=(
|
||||||
|
chromium
|
||||||
|
vlc
|
||||||
|
vlc-plugin-ffmpeg
|
||||||
|
)
|
||||||
|
|
||||||
|
# Office productivity packages
|
||||||
|
PACKAGES_OFFICE=(
|
||||||
|
hunspell-en_us
|
||||||
|
libreoffice-fresh
|
||||||
|
keepassxc
|
||||||
|
qalculate-gtk
|
||||||
|
)
|
||||||
|
|
||||||
|
# General productivity packages
|
||||||
|
PACKAGES_PRODUCTIVITY=(
|
||||||
|
ffmpeg
|
||||||
|
gimp
|
||||||
|
syncthing
|
||||||
|
tenacity
|
||||||
|
)
|
||||||
|
|
||||||
|
# Development base packages
|
||||||
|
PACKAGES_DEV_BASE=(
|
||||||
|
git
|
||||||
|
docker
|
||||||
|
docker-compose
|
||||||
|
)
|
||||||
|
|
||||||
|
# Python development packages
|
||||||
|
PACKAGES_DEV_PYTHON=(
|
||||||
|
python
|
||||||
|
python-virtualenv
|
||||||
|
pycharm-community-edition
|
||||||
|
)
|
||||||
|
|
||||||
|
# Java development packages
|
||||||
|
PACKAGES_DEV_JAVA=(
|
||||||
|
jdk-openjdk
|
||||||
|
intellij-idea-community-edition
|
||||||
|
)
|
||||||
|
|
||||||
|
# Go development packages
|
||||||
|
PACKAGES_DEV_GO=(
|
||||||
|
go
|
||||||
|
)
|
||||||
|
|
||||||
|
# Additional development tools
|
||||||
|
PACKAGES_DEV_TOOLS=(
|
||||||
|
code
|
||||||
|
wireshark-qt
|
||||||
|
)
|
||||||
184
profiles/registry.sh
Normal file
184
profiles/registry.sh
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
# registry.sh - Profile registry and management
|
||||||
|
#
|
||||||
|
# Defines installation profiles that customize the system for different use cases.
|
||||||
|
# Each profile specifies which packages to install and services to enable.
|
||||||
|
|
||||||
|
# Source shared package definitions
|
||||||
|
source "${SCRIPT_DIR}/profiles/packages.sh"
|
||||||
|
|
||||||
|
# Profile definitions
|
||||||
|
declare -A PROFILE_NAMES=(
|
||||||
|
[1]="Minimal"
|
||||||
|
[2]="Server"
|
||||||
|
[3]="Minimal Desktop"
|
||||||
|
[4]="Home Theater PC"
|
||||||
|
[5]="Home Theater PC with Gaming"
|
||||||
|
[6]="Office Workstation"
|
||||||
|
[7]="Software Development Workstation"
|
||||||
|
)
|
||||||
|
|
||||||
|
declare -A PROFILE_DESCRIPTIONS=(
|
||||||
|
[1]="Base Arch Linux system, no additional packages."
|
||||||
|
[2]="Adds Restic, Docker, and Docker Compose."
|
||||||
|
[3]="XFCE 4 with no additional applications."
|
||||||
|
[4]="XFCE 4 with Chromium and VLC media player."
|
||||||
|
[5]="XFCE 4 with Chromium, VLC media player, and Dolphin."
|
||||||
|
[6]="XFCE 4 with a full suite of desktop applications aimed at general office work."
|
||||||
|
[7]="XFCE 4 with a suite of software development applications."
|
||||||
|
)
|
||||||
|
|
||||||
|
declare -A PROFILE_REQUIRES_XFCE=(
|
||||||
|
[1]=false
|
||||||
|
[2]=false
|
||||||
|
[3]=true
|
||||||
|
[4]=true
|
||||||
|
[5]=true
|
||||||
|
[6]=true
|
||||||
|
[7]=true
|
||||||
|
)
|
||||||
|
|
||||||
|
# Display available profiles
|
||||||
|
list_profiles() {
|
||||||
|
print "Base install complete. Select profile to install for this system:"
|
||||||
|
|
||||||
|
for i in {1..7}; do
|
||||||
|
print " $i - ${PROFILE_NAMES[$i]}"
|
||||||
|
print " ${PROFILE_DESCRIPTIONS[$i]}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get packages for a profile
|
||||||
|
# Arguments:
|
||||||
|
# $1 - profile number
|
||||||
|
# Outputs:
|
||||||
|
# Package list to stdout
|
||||||
|
get_profile_packages() {
|
||||||
|
local profile="$1"
|
||||||
|
|
||||||
|
case "$profile" in
|
||||||
|
"1")
|
||||||
|
# Minimal - no additional packages
|
||||||
|
;;
|
||||||
|
"2")
|
||||||
|
# Server
|
||||||
|
echo "restic docker docker-compose"
|
||||||
|
;;
|
||||||
|
"3")
|
||||||
|
# Minimal Desktop - XFCE only, no additional packages
|
||||||
|
;;
|
||||||
|
"4")
|
||||||
|
# Home Theater PC
|
||||||
|
echo "${PACKAGES_MEDIA[*]}"
|
||||||
|
;;
|
||||||
|
"5")
|
||||||
|
# Home Theater PC with Gaming
|
||||||
|
echo "dolphin-emu ${PACKAGES_MEDIA[*]}"
|
||||||
|
;;
|
||||||
|
"6")
|
||||||
|
# Office Workstation
|
||||||
|
echo "${PACKAGES_MEDIA[*]} ${PACKAGES_OFFICE[*]} ${PACKAGES_PRODUCTIVITY[*]} git gnucash"
|
||||||
|
;;
|
||||||
|
"7")
|
||||||
|
# Software Development Workstation
|
||||||
|
echo "${PACKAGES_MEDIA[*]} ${PACKAGES_OFFICE[*]} ${PACKAGES_PRODUCTIVITY[*]}"
|
||||||
|
echo "${PACKAGES_DEV_BASE[*]} ${PACKAGES_DEV_PYTHON[*]} ${PACKAGES_DEV_JAVA[*]}"
|
||||||
|
echo "${PACKAGES_DEV_GO[*]} ${PACKAGES_DEV_TOOLS[*]}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get services to enable for a profile
|
||||||
|
# Arguments:
|
||||||
|
# $1 - profile number
|
||||||
|
# Outputs:
|
||||||
|
# Service list to stdout
|
||||||
|
get_profile_services() {
|
||||||
|
local profile="$1"
|
||||||
|
|
||||||
|
case "$profile" in
|
||||||
|
"2"|"7")
|
||||||
|
echo "docker.service"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if profile requires XFCE
|
||||||
|
# Arguments:
|
||||||
|
# $1 - profile number
|
||||||
|
# Returns:
|
||||||
|
# 0 if requires XFCE, 1 otherwise
|
||||||
|
profile_requires_xfce() {
|
||||||
|
local profile="$1"
|
||||||
|
|
||||||
|
if [ "${PROFILE_REQUIRES_XFCE[$profile]}" = "true" ]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install a profile
|
||||||
|
# Arguments:
|
||||||
|
# $1 - profile number
|
||||||
|
# $2 - username
|
||||||
|
install_profile() {
|
||||||
|
local profile="$1"
|
||||||
|
local username="$2"
|
||||||
|
local packages
|
||||||
|
local services
|
||||||
|
|
||||||
|
# Install XFCE if required
|
||||||
|
if profile_requires_xfce "$profile"; then
|
||||||
|
install_xfce "$username"
|
||||||
|
prompt_install_graphics
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get and install profile packages
|
||||||
|
packages=$(get_profile_packages "$profile")
|
||||||
|
if [ -n "$packages" ]; then
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
chroot_install $packages
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Enable profile services
|
||||||
|
services=$(get_profile_services "$profile")
|
||||||
|
if [ -n "$services" ]; then
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
chroot_enable $services
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prompt user for profile selection and install
|
||||||
|
# Arguments:
|
||||||
|
# $1 - username
|
||||||
|
select_and_install_profile() {
|
||||||
|
local username="$1"
|
||||||
|
local profile
|
||||||
|
|
||||||
|
list_profiles
|
||||||
|
read -r profile
|
||||||
|
|
||||||
|
# Validate selection
|
||||||
|
if ! [[ "$profile" =~ ^[1-7]$ ]]; then
|
||||||
|
print_warning "Unknown profile, defaulting to minimal install."
|
||||||
|
profile="1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
install_profile "$profile" "$username"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user