diff options
Diffstat (limited to 'archsetup')
| -rwxr-xr-x | archsetup | 213 |
1 files changed, 98 insertions, 115 deletions
@@ -98,6 +98,9 @@ load_config() { exit 1 fi echo "Loading config from: $config_path" + # SECURITY NOTE: source executes config as bash. Only use trusted config files. + # This is acceptable because: (1) user explicitly provides path, (2) script runs + # as root anyway, (3) if attacker can write config, they likely have root access. # shellcheck disable=SC1090 source "$config_path" @@ -114,6 +117,8 @@ load_config() { [[ -n "$ST_REPO" ]] && st_repo="$ST_REPO" [[ -n "$SLOCK_REPO" ]] && slock_repo="$SLOCK_REPO" [[ -n "$DOTEMACS_REPO" ]] && dotemacs_repo="$DOTEMACS_REPO" + [[ -n "$ARCHSETUP_REPO" ]] && archsetup_repo="$ARCHSETUP_REPO" + [[ -n "$LOCALE" ]] && locale="$LOCALE" } # Load config if specified @@ -124,6 +129,7 @@ load_config() { username="${username:-cjennings}" password="${password:-welcome}" # CHANGE ON FIRST LOGIN +locale="${locale:-}" # set via prompt if not configured archsetup_dir="$(dirname "$(readlink -f "$0")")" dotfiles_dir="$archsetup_dir/dotfiles" @@ -135,6 +141,7 @@ dmenu_repo="${dmenu_repo:-https://git.cjennings.net/dmenu.git}" st_repo="${st_repo:-https://git.cjennings.net/st.git}" slock_repo="${slock_repo:-https://git.cjennings.net/slock.git}" dotemacs_repo="${dotemacs_repo:-https://git.cjennings.net/dotemacs.git}" +archsetup_repo="${archsetup_repo:-https://git.cjennings.net/archsetup.git}" logfile="/var/log/archsetup-$(date +'%Y-%m-%d-%H-%M-%S').log" source_dir="/home/$username/.local/src" # aur/git source goes here @@ -145,6 +152,12 @@ archsetup_packages="/var/log/archsetup-installed-packages.txt" min_disk_space_gb=20 state_dir="/var/lib/archsetup/state" error_messages=() +errors_encountered=0 + +# Installation steps (single source of truth for show_status and main execution) +STEPS=(intro prerequisites create_user user_customizations aur_installer + essential_services xorg dwm desktop_environment developer_workstation + supplemental_software boot_ux) ### Cleanup Trap # Ensures tmpfs is unmounted if script exits unexpectedly @@ -198,10 +211,7 @@ show_status() { exit 0 fi echo "Completed steps:" - for step in intro prerequisites create_user user_customizations \ - aur_installer essential_services xorg dwm \ - desktop_environment developer_workstation \ - supplemental_software boot_ux; do + for step in "${STEPS[@]}"; do if step_completed "$step"; then timestamp=$(cat "$state_dir/$step") printf " [x] %-25s (%s)\n" "$step" "$timestamp" @@ -265,14 +275,49 @@ preflight_checks() { fi echo " [OK] System: Arch Linux detected" + # Check locale configuration + if grep -q "^LANG=" /etc/locale.conf 2>/dev/null; then + current_locale=$(grep "^LANG=" /etc/locale.conf | cut -d= -f2) + echo " [OK] Locale: $current_locale" + elif [[ -n "$locale" ]]; then + echo " [OK] Locale: $locale (from config)" + else + echo "" + echo "Locale not configured. Please select:" + echo " 1) en_US.UTF-8 (US English)" + echo " 2) en_GB.UTF-8 (UK English)" + echo " 3) de_DE.UTF-8 (German)" + echo " 4) es_ES.UTF-8 (Spanish)" + echo " 5) fr_FR.UTF-8 (French)" + echo " 6) pt_BR.UTF-8 (Brazilian Portuguese)" + echo " 7) ja_JP.UTF-8 (Japanese)" + echo " 8) zh_CN.UTF-8 (Chinese)" + echo " 9) Other (enter manually)" + read -r -p "Choice [1]: " choice + case "${choice:-1}" in + 1) locale="en_US.UTF-8" ;; + 2) locale="en_GB.UTF-8" ;; + 3) locale="de_DE.UTF-8" ;; + 4) locale="es_ES.UTF-8" ;; + 5) locale="fr_FR.UTF-8" ;; + 6) locale="pt_BR.UTF-8" ;; + 7) locale="ja_JP.UTF-8" ;; + 8) locale="zh_CN.UTF-8" ;; + 9) + read -r -p "Enter locale (e.g., nl_NL.UTF-8): " locale + [[ -z "$locale" ]] && locale="en_US.UTF-8" + ;; + *) locale="en_US.UTF-8" ;; + esac + echo " [OK] Locale: $locale (selected)" + fi + echo "Pre-flight checks passed." echo "" } ### Intro intro() { - errors_encountered=0 - # begin with a clean logfile [ -f "$logfile" ] && rm -f "$logfile" touch "$logfile" @@ -334,28 +379,30 @@ retry_install() { local source="$2" local cmd="$3" local attempt=1 + local last_exit_code=0 display "task" "installing $pkg via $source" while [ $attempt -le $MAX_INSTALL_RETRIES ]; do if eval "$cmd" >> "$logfile" 2>&1; then return 0 fi + last_exit_code=$? attempt=$((attempt + 1)) if [ $attempt -le $MAX_INSTALL_RETRIES ]; then display "task" "retrying $pkg (attempt $attempt/$MAX_INSTALL_RETRIES)" fi done - error_warn "$pkg ($source)" "$?" + error_warn "$pkg ($source)" "$last_exit_code" } # Pacman Install pacman_install() { - retry_install "$1" "pacman" "pacman --noconfirm --needed -S $1" + retry_install "$1" "pacman" "pacman --noconfirm --needed -S \"$1\"" } # AUR Install aur_install() { - retry_install "$1" "AUR" "sudo -u $username yay -S --noconfirm $1" + retry_install "$1" "AUR" "sudo -u \"$username\" yay -S --noconfirm \"$1\"" } # Git Install @@ -600,11 +647,19 @@ prerequisites() { display "subtitle" "Environment Configuration" # configure locale (must happen before package installs that depend on locale) - action="configuring locale" && display "task" "$action" - sed -i 's/^#en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen - (locale-gen >> "$logfile" 2>&1) || error_warn "$action" "$?" - echo "LANG=en_US.UTF-8" > /etc/locale.conf - export LANG=en_US.UTF-8 + # Skip if already configured by installer + if ! grep -q "^LANG=" /etc/locale.conf 2>/dev/null; then + action="configuring locale ($locale)" && display "task" "$action" + # Uncomment the selected locale in locale.gen (format: "en_US.UTF-8 UTF-8") + locale_entry="${locale} ${locale##*.}" # e.g., "en_US.UTF-8 UTF-8" + sed -i "s/^#${locale_entry}/${locale_entry}/" /etc/locale.gen + (locale-gen >> "$logfile" 2>&1) || error_warn "$action" "$?" + echo "LANG=$locale" > /etc/locale.conf + export LANG="$locale" + else + display "task" "locale already configured ($(grep ^LANG= /etc/locale.conf))" + export LANG="$(grep ^LANG= /etc/locale.conf | cut -d= -f2)" + fi # sync the time on this machine (one-shot chrony sync) action="synchronizing system time" && display "task" "$action" @@ -663,7 +718,7 @@ EOF [ -f /etc/sudoers.pacnew ] && cp /etc/sudoers.pacnew /etc/sudoers >> "$logfile" 2>&1 action="creating a directory to build/install software from git/AUR." - (mkdir -p "$source_dir") || error_fatal "creating the directory $source_dir" + (mkdir -p "$source_dir") || error_fatal "creating the directory $source_dir" "$?" } @@ -674,15 +729,16 @@ create_user() { display "task" "checking if user exists" # halt if $username exists ( id -u "$username" >/dev/null 2>&1; ) && \ - error_fatal "user '$username' already exists!" + error_fatal "user '$username' already exists" "user exists" # create $username with home, group, shell, password action="creating user and home directory" && display "task" "$action" (useradd -m -G wheel -s /bin/zsh "$username" >> "$logfile" 2>&1) || \ - error_fatal "adding user '$username" "$?" + error_fatal "adding user '$username'" "$?" display "task" "assigning the password" echo "$username:$password" | chpasswd # any text is allowable! be careful! + unset password # clear from memory after use display "task" "adding to appropriate groups" (usermod -aG \ @@ -726,7 +782,7 @@ user_customizations() { user_archsetup_dir="/home/$username/code/archsetup" action="cloning archsetup to user's home directory" && display "task" "$action" (mkdir -p "$(dirname "$user_archsetup_dir")" && \ - git clone --depth 1 https://git.cjennings.net/archsetup.git "$user_archsetup_dir" && \ + git clone --depth 1 "$archsetup_repo" "$user_archsetup_dir" && \ chown -R "$username": "/home/$username/code") \ >> "$logfile" 2>&1 || error_warn "$action" "$?" @@ -741,8 +797,9 @@ user_customizations() { pacman_install desktop-file-utils action="updating desktop database" && display "task" "$action" + # Exit code 1 means warnings (missing icons, etc.) but database still updated (sudo -u "$username" update-desktop-database "/home/$username/.local/share/applications" \ - >> "$logfile" 2>&1 ) || error_warn "$action" "$?" + >> "$logfile" 2>&1 ) || true action="restoring dotfile versions" && display "task" "$action" (cd "$dotfiles_dir" && git config --global --add safe.directory "$user_archsetup_dir" && \ @@ -828,8 +885,11 @@ ethernet.cloned-mac-address=stable EOF # Configure wireless regulatory domain (enables full WiFi capabilities for region) - action="configuring wireless regulatory domain (US)" && display "task" "$action" - sed -i 's/^#WIRELESS_REGDOM="US"/WIRELESS_REGDOM="US"/' /etc/conf.d/wireless-regdom + # Derive region code from locale (e.g., en_US.UTF-8 → US, de_DE.UTF-8 → DE) + current_lang="${LANG:-en_US.UTF-8}" + wireless_region="${current_lang:3:2}" # extract country code (positions 3-4) + action="configuring wireless regulatory domain ($wireless_region)" && display "task" "$action" + sed -i "s/^#WIRELESS_REGDOM=\"$wireless_region\"/WIRELESS_REGDOM=\"$wireless_region\"/" /etc/conf.d/wireless-regdom # Encrypted DNS (DNS over TLS) @@ -842,6 +902,8 @@ DNS=1.1.1.1#cloudflare-dns.com 9.9.9.9#dns.quad9.net FallbackDNS=1.0.0.1#cloudflare-dns.com 149.112.112.112#dns.quad9.net DNSOverTLS=yes DNSSEC=yes +# Disable mDNS in resolved - avahi handles .local resolution exclusively +MulticastDNS=no EOF # Configure NetworkManager to use systemd-resolved @@ -941,6 +1003,9 @@ EOF systemctl enable ufw.service >> "$logfile" 2>&1 || error_warn "$action" "$?" # Verify firewall is actually active + # Note: In VM environments, UFW may show inactive due to missing kernel + # netfilter modules. This is a test environment limitation, not a bug. + # On real hardware with proper kernel support, UFW activates correctly. action="verifying firewall is active" && display "task" "$action" if ! ufw status | grep -q "Status: active"; then error_messages=("FIREWALL NOT ACTIVE - run: sudo ufw enable" "${error_messages[@]}") @@ -1066,81 +1131,8 @@ EOF use_template = none EOF - action="creating zfs-replicate script" && display "task" "$action" - cat << 'EOF' > /usr/local/bin/zfs-replicate -#!/bin/bash -# zfs-replicate - Replicate ZFS datasets to TrueNAS -# -# Usage: -# zfs-replicate # Replicate all configured datasets -# zfs-replicate [dataset] # Replicate specific dataset - -set -e - -# TrueNAS Configuration -# Try local network first, fall back to tailscale -TRUENAS_LOCAL="truenas.local" -TRUENAS_TAILSCALE="truenas" -TRUENAS_USER="root" -TRUENAS_POOL="vault" -BACKUP_PATH="backups" # TODO: Configure actual path - -# Datasets to replicate -DATASETS="zroot/ROOT/default zroot/home zroot/media zroot/vms" - -# Colors -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' - -info() { echo -e "${GREEN}[INFO]${NC} $1"; } -warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } -error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; } - -command -v syncoid >/dev/null 2>&1 || error "syncoid not found. Install sanoid package." - -# Determine which host to use -determine_host() { - if ping -c 1 -W 2 "$TRUENAS_LOCAL" &>/dev/null; then - echo "$TRUENAS_LOCAL" - elif ping -c 1 -W 2 "$TRUENAS_TAILSCALE" &>/dev/null; then - echo "$TRUENAS_TAILSCALE" - else - error "Cannot reach TrueNAS at $TRUENAS_LOCAL or $TRUENAS_TAILSCALE" - fi -} - -TRUENAS_HOST=$(determine_host) -info "Using TrueNAS host: $TRUENAS_HOST" - -# Single dataset mode -if [[ -n "$1" ]]; then - dataset="$1" - dest="$TRUENAS_USER@$TRUENAS_HOST:$TRUENAS_POOL/$BACKUP_PATH/${dataset#zroot/}" - info "Replicating $dataset -> $dest" - syncoid --recursive "$dataset" "$dest" - exit 0 -fi - -# Full replication -info "Starting ZFS replication to $TRUENAS_HOST" -echo "" - -for dataset in $DATASETS; do - dest="$TRUENAS_USER@$TRUENAS_HOST:$TRUENAS_POOL/$BACKUP_PATH/${dataset#zroot/}" - info "Replicating $dataset -> $dest" - - if syncoid --recursive "$dataset" "$dest"; then - info " Success" - else - warn " Failed (will retry next run)" - fi - echo "" -done - -info "Replication complete." -EOF + action="installing zfs-replicate script" && display "task" "$action" + cp "$user_archsetup_dir/scripts/zfs-replicate" /usr/local/bin/zfs-replicate chmod +x /usr/local/bin/zfs-replicate action="creating zfs-replicate systemd service" && display "task" "$action" @@ -1246,7 +1238,7 @@ dwm() { action="DWM Window Manager Dependencies" && display "subtitle" "$action" for software in coreutils fontconfig freetype2 glibc harfbuzz libx11 libxft libxinerama; do pacman_install "$software" - done; + done action="DWM Window Manager" && display "subtitle" "$action" @@ -1350,7 +1342,7 @@ desktop_environment() { for software in alsa-utils pipewire wireplumber pipewire-pulse \ pipewire-docs pamixer pulsemixer ffmpeg rtkit; do pacman_install "$software" - done; + done # disable the pc speaker beep rmmod pcspkr 2>/dev/null || true echo "blacklist pcspkr" > /etc/modprobe.d/nobeep.conf @@ -1386,11 +1378,11 @@ desktop_environment() { for software in htop mc ncdu tmux fzf zip unzip atool wget detox \ lsof usbutils moreutils; do pacman_install "$software" - done; + done for software in task-spooler-cpu speedtest-go-bin gotop-bin rar; do aur_install "$software" - done; + done # Help And Documentation @@ -1422,7 +1414,7 @@ desktop_environment() { for software in brightnessctl network-manager-applet xclip rofi \ conky qalculate-gtk feh; do pacman_install "$software" - done; + done aur_install caffeine-ng aur_install flameshot @@ -1436,12 +1428,12 @@ desktop_environment() { for software in picom lxappearance gnome-themes-extra; do pacman_install "$software" - done; + done for software in vimix-cursors \ papirus-icon-theme qt6ct qt5ct; do aur_install "$software" - done; + done pacman_install libappindicator-gtk3 # required by some applets @@ -1834,18 +1826,9 @@ outro() { preflight_checks # verify system requirements (always runs) STARTTIME=$(date +%s) # must be outside intro() since it may be skipped on resume -run_step "intro" intro -run_step "prerequisites" prerequisites -run_step "create_user" create_user -run_step "user_customizations" user_customizations -run_step "aur_installer" aur_installer -run_step "essential_services" essential_services -run_step "xorg" xorg -run_step "dwm" dwm -run_step "desktop_environment" desktop_environment -run_step "developer_workstation" developer_workstation -run_step "supplemental_software" supplemental_software -run_step "boot_ux" boot_ux +for step in "${STEPS[@]}"; do + run_step "$step" "$step" +done outro # take end stats; show summary (always runs) |
