#!/usr/bin/env bash set -euo pipefail # Arch + Windows dual-boot helper (Linux side) # - Safe by default: does NOT touch Windows partitions and will only format the chosen Linux root if you explicitly allow it. # - Intended to be run from the Arch install ISO (archiso). # ------------------------- # Logging # ------------------------- LOG_FILE="${LOG_FILE:-/root/archwin-linux-setup.log}" mkdir -p "$(dirname "$LOG_FILE")" exec > >(tee -a "$LOG_FILE") 2>&1 # ------------------------- # Helpers # ------------------------- die() { echo "ERROR: $*" >&2 exit 1 } warn() { echo "WARN: $*" >&2; } info() { echo -e "\n==> $*\n"; } have() { command -v "$1" >/dev/null 2>&1; } confirm() { # Usage: confirm "question" [default_no|default_yes] local q="$1" local def="${2:-default_no}" local prompt="[y/N]" [[ "$def" == "default_yes" ]] && prompt="[Y/n]" local ans # Write prompt directly to tty to avoid buffering from tee redirection printf '%s %s ' "$q" "$prompt" >/dev/tty read -r ans ' >/dev/tty read -r ans Linux root partition (e.g. /dev/nvme0n1p4) --esp EFI System Partition (FAT32; e.g. /dev/nvme0n1p1) --disk Target disk (e.g. /dev/nvme0n1). Auto-derived if omitted. --hostname Hostname (default: arch) --user Primary user (default: archuser) --timezone Timezone (default: UTC) --locale Locale (default: en_US.UTF-8) --format-root FORMAT the root partition as ext4 (DANGEROUS) --no-format-root Never format root (default) --install-base yes|no|auto Install base system with pacstrap (default: auto) --with-base-devel yes|no Install base-devel + git (default: yes) --repair-only Skip pacstrap, just repair boot configuration --no-fallback Do not create fallback initramfs (saves ESP space) --with-fallback Force fallback initramfs even on small ESPs --btrfs-subvol Btrfs subvolume to mount as / --allow-no-password Allow noninteractive install without passwords (DANGEROUS) --force-bios Allow running even if ISO booted in legacy/BIOS mode --noninteractive Do not prompt; requires --root and --esp -h, --help Show help Modes: Fresh Install: Run without --repair-only to install Arch from scratch Repair Mode: Run with --repair-only (or --install-base no) to fix boot issues on an existing Arch installation Notes: - This script does NOT resize partitions and does NOT touch Windows partitions. - For best results: boot the Arch ISO in *UEFI mode* (not legacy). EOT } while [[ $# -gt 0 ]]; do case "$1" in --root) ROOT_PART="${2:-}" shift 2 ;; --esp) ESP_PART="${2:-}" shift 2 ;; --disk) DISK="${2:-}" shift 2 ;; --hostname) HOSTNAME="${2:-}" shift 2 ;; --user) USERNAME="${2:-}" USER_EXPLICIT="yes" shift 2 ;; --timezone) TIMEZONE="${2:-}" shift 2 ;; --locale) LOCALE="${2:-}" shift 2 ;; --format-root) DO_FORMAT_ROOT="yes" shift ;; --no-format-root) DO_FORMAT_ROOT="no" shift ;; --install-base) INSTALL_BASE="${2:-auto}" INSTALL_BASE_EXPLICIT="yes" shift 2 ;; --with-base-devel) WITH_BASE_DEVEL="${2:-yes}" shift 2 ;; --repair-only) INSTALL_BASE="no" INSTALL_BASE_EXPLICIT="yes" shift ;; --no-fallback) NO_FALLBACK="yes" shift ;; --with-fallback) NO_FALLBACK="no" shift ;; --allow-no-password) ALLOW_NO_PASSWORD="yes" shift ;; --btrfs-subvol) BTRFS_SUBVOL="${2:-}" [[ -n "$BTRFS_SUBVOL" ]] || die "--btrfs-subvol requires a name (e.g., @)" shift 2 ;; --force-bios) FORCE_BIOS="yes" shift ;; --noninteractive) NONINTERACTIVE="yes" shift ;; -h | --help) usage exit 0 ;; *) die "Unknown argument: $1 (use --help)" ;; esac done if [[ "$NONINTERACTIVE" == "yes" ]]; then [[ -n "$ROOT_PART" ]] || die "--noninteractive requires --root ." [[ -n "$ESP_PART" ]] || die "--noninteractive requires --esp ." fi # ------------------------- # Preflight # ------------------------- [[ "${EUID}" -eq 0 ]] || die "Run as root (you are not root)." need_cmd lsblk need_cmd blkid need_cmd mount need_cmd umount need_cmd awk need_cmd sed need_cmd grep need_cmd findmnt need_cmd arch-chroot need_cmd genfstab need_cmd pacstrap need_cmd bootctl info "Arch+Windows dual-boot helper (Linux side)" echo "Log: $LOG_FILE" echo "Secure Boot (from firmware): $(secure_boot_state)" echo if is_uefi_boot; then IS_UEFI="yes" info "Boot mode check: UEFI OK (installer can write NVRAM boot entries)." # Check UEFI bitness per Arch Wiki recommendation uefi_bitness="" if [[ -f /sys/firmware/efi/fw_platform_size ]]; then uefi_bitness="$(cat /sys/firmware/efi/fw_platform_size 2>/dev/null || true)" echo "UEFI firmware bitness: ${uefi_bitness}-bit" if [[ "$uefi_bitness" == "32" ]]; then warn "32-bit UEFI detected. This limits boot loader choices (systemd-boot should still work)." fi fi else IS_UEFI="no" warn "You booted the Arch ISO in legacy/BIOS mode." warn "Windows 11 expects UEFI/GPT. systemd-boot will still be copied to the ESP," warn "but this environment cannot write UEFI NVRAM boot entries (BootOrder/Boot####)." warn "Best fix: reboot and select the USB entry that starts with 'UEFI: ...'." if [[ "$FORCE_BIOS" != "yes" ]]; then if [[ "$NONINTERACTIVE" == "yes" ]]; then die "Not booted in UEFI mode. Reboot the ISO in UEFI mode, or pass --force-bios." fi confirm "Continue anyway (will NOT create NVRAM boot entry)?" default_no || die "Aborted." fi fi if [[ "$(secure_boot_state)" == "enabled" ]]; then warn "Secure Boot appears ENABLED." warn "Stock Arch + systemd-boot/kernel will NOT boot under Secure Boot unless you set up signing (e.g., sbctl) or disable Secure Boot in firmware." fi # NTP (best effort) if have timedatectl; then timedatectl set-ntp true || true fi # ------------------------- # Partition detection # ------------------------- pick_partition_interactive() { local title="$1" shift local -a options=("$@") echo "$title" local i=1 for p in "${options[@]}"; do echo " [$i] $p" i=$((i + 1)) done local choice while true; do printf 'Select a number: ' >/dev/tty read -r choice = 1 && choice <= ${#options[@]})) || { echo "Out of range." continue } echo "${options[$((choice - 1))]}" return 0 done } detect_esp_candidates() { # Prefer partitions with PARTTYPE == EFI System Partition GUID and vfat/fat # Output: one PATH per line lsblk -rpno PATH,FSTYPE,PARTTYPE,PARTLABEL | awk 'BEGIN{IGNORECASE=1} ($2 ~ /vfat|fat/) && ($3 ~ /c12a7328-f81f-11d2-ba4b-00a0c93ec93b/ || $4 ~ /efi|system/) { print $1 }' } esp_contains_windows() { local part="$1" local mnt local rc=1 mnt="$(mktemp -d)" || return 1 # Mount read-only to avoid any chance of modifying it during detection. if mount -o ro "$part" "$mnt" 2>/dev/null; then if [[ -f "$mnt/EFI/Microsoft/Boot/Bootmgfw.efi" || -f "$mnt/EFI/Microsoft/Boot/bootmgfw.efi" ]]; then rc=0 fi umount "$mnt" 2>/dev/null || true fi rmdir "$mnt" 2>/dev/null || true return $rc } auto_detect_esp() { local -a cands=() while IFS= read -r line; do [[ -n "$line" ]] && cands+=("$line") done < <(detect_esp_candidates) if [[ ${#cands[@]} -eq 0 ]]; then return 1 fi # Prefer the one that contains Windows Boot Manager local p for p in "${cands[@]}"; do if esp_contains_windows "$p"; then echo "$p" return 0 fi done # Otherwise return the first candidate echo "${cands[0]}" return 0 } detect_linux_root_guid_candidates() { lsblk -rpno PATH,PARTTYPE,MOUNTPOINT | awk ' BEGIN{IGNORECASE=1} ($3 == "") && ($2 ~ /4f68bce3-e8cd-4db1-96e7-fbcaf984b709|44479540-f297-41b2-9af7-d131d5f0458a/) { print $1 }' } detect_linux_root_fs_candidates() { # ext4/btrfs/xfs candidate partitions that are not mounted lsblk -rpno PATH,FSTYPE,MOUNTPOINT | awk ' ($2 ~ /ext4|btrfs|xfs/ ) && ($3 == "" ) { print $1 }' } detect_linux_root_candidates() { # Prefer Linux root GPT type GUIDs when present, otherwise fall back to ext4/btrfs/xfs local roots roots="$(detect_linux_root_guid_candidates)" if [[ -n "$roots" ]]; then echo "$roots" return 0 fi detect_linux_root_fs_candidates } auto_detect_root() { local -a cands=() mapfile -t cands < <(detect_linux_root_guid_candidates) if [[ ${#cands[@]} -gt 0 ]]; then ROOT_CANDIDATE_SOURCE="gpt" else mapfile -t cands < <(detect_linux_root_fs_candidates) ROOT_CANDIDATE_SOURCE="fs" fi if [[ ${#cands[@]} -eq 0 ]]; then return 1 fi # If exactly one, use it if [[ ${#cands[@]} -eq 1 ]]; then echo "${cands[0]}" return 0 fi return 2 } derive_disk_from_part() { local part="$1" local pk pk="$(lsblk -no PKNAME "$part" | head -n1 || true)" [[ -n "$pk" ]] || return 1 echo "/dev/$pk" } # ESP if [[ -z "$ESP_PART" ]]; then if [[ "$NONINTERACTIVE" == "yes" ]]; then ESP_PART="$(auto_detect_esp)" || die "Could not auto-detect ESP; pass --esp ." else if ESP_PART="$(auto_detect_esp)"; then info "Detected ESP candidate: $ESP_PART" if ! confirm "Use this ESP?" default_yes; then mapfile -t esp_list < <(detect_esp_candidates) [[ ${#esp_list[@]} -gt 0 ]] || die "No ESP candidates found." ESP_PART="$(pick_partition_interactive "Choose the EFI System Partition (ESP):" "${esp_list[@]}")" fi else warn "Could not auto-detect the ESP." mapfile -t esp_list < <(detect_esp_candidates) [[ ${#esp_list[@]} -gt 0 ]] || die "No ESP candidates found. Is Windows installed in UEFI mode?" ESP_PART="$(pick_partition_interactive "Choose the EFI System Partition (ESP):" "${esp_list[@]}")" fi fi fi [[ -b "$ESP_PART" ]] || die "ESP partition not found: $ESP_PART" # Check ESP size (Wiki recommends at least 1 GiB, warns about Windows's 100 MiB default) esp_size_bytes="$(lsblk -bno SIZE "$ESP_PART" 2>/dev/null | tr -d '[:space:]' || true)" if [[ -n "$esp_size_bytes" ]]; then esp_size_mib=$((esp_size_bytes / 1024 / 1024)) if [[ "$esp_size_mib" -lt 100 ]]; then warn "ESP is very small (${esp_size_mib} MiB). This may cause issues." elif [[ "$esp_size_mib" -lt 300 ]]; then warn "ESP is only ${esp_size_mib} MiB (Windows default is 100 MiB)." warn "This may be too small for multiple kernels. Consider resizing if possible." warn "See: https://wiki.archlinux.org/title/EFI_system_partition#Replace_the_partition_with_a_larger_one" elif [[ "$esp_size_mib" -lt 512 ]]; then echo "ESP size: ${esp_size_mib} MiB (adequate for basic setup)" else echo "ESP size: ${esp_size_mib} MiB (good)" fi # Auto-disable fallback initramfs on small ESPs to prevent running out of space if [[ "$NO_FALLBACK" == "auto" && "$esp_size_mib" -lt 512 ]]; then warn "ESP is small (${esp_size_mib} MiB) - automatically disabling fallback initramfs." warn "Use --with-fallback to override if you have enough space." NO_FALLBACK="yes" fi else esp_size_mib=0 fi # Resolve auto to explicit value if [[ "$NO_FALLBACK" == "auto" ]]; then NO_FALLBACK="no" fi # Root if [[ -z "$ROOT_PART" ]]; then if [[ "$NONINTERACTIVE" == "yes" ]]; then ROOT_PART="$(auto_detect_root)" || die "Could not auto-detect Linux root partition; pass --root ." else rc=0 ROOT_PART="$(auto_detect_root)" || rc=$? if [[ $rc -eq 0 ]]; then info "Detected Linux root candidate: $ROOT_PART" root_confirm_default="default_yes" if [[ "$ROOT_CANDIDATE_SOURCE" != "gpt" ]]; then root_confirm_default="default_no" fi if ! confirm "Use this root partition?" "$root_confirm_default"; then mapfile -t root_list < <(detect_linux_root_candidates) [[ ${#root_list[@]} -gt 0 ]] || die "No Linux root candidates found." ROOT_PART="$(pick_partition_interactive "Choose the Linux root partition:" "${root_list[@]}")" fi elif [[ $rc -eq 2 ]]; then mapfile -t root_list < <(detect_linux_root_candidates) info "Multiple Linux root candidates found." ROOT_PART="$(pick_partition_interactive "Choose the Linux root partition:" "${root_list[@]}")" else warn "Could not auto-detect a Linux root partition." echo "Current disks/partitions:" lsblk -f echo if confirm "Open cfdisk to create a Linux partition now?" default_no; then # Guess disk from ESP if possible local_disk="$(derive_disk_from_part "$ESP_PART" || true)" if [[ -z "${local_disk:-}" ]]; then printf 'Enter disk (e.g. /dev/nvme0n1): ' >/dev/tty read -r local_disk /dev/null || true cleanup() { if [[ "$CLEANUP_ON_EXIT" == "yes" ]]; then umount -R /mnt 2>/dev/null || true fi } trap cleanup EXIT root_fstype="$(lsblk -no FSTYPE "$ROOT_PART" | tr -d '[:space:]' || true)" if [[ -z "$root_fstype" ]]; then root_fstype="$(blkid -s TYPE -o value "$ROOT_PART" 2>/dev/null || true)" fi root_type="$(lsblk -no TYPE "$ROOT_PART" | head -n1 | tr -d '[:space:]' || true)" case "$root_type" in crypt | lvm | raid | md) die "Root partition type '$root_type' detected. Encrypted/LVM/RAID roots are not supported by this script." ;; esac if [[ -z "$root_fstype" && "$DO_FORMAT_ROOT" != "yes" ]]; then warn "Root partition $ROOT_PART has no filesystem." if [[ "$NONINTERACTIVE" == "yes" ]]; then die "Root partition is unformatted. Re-run with --format-root or format it manually." fi if confirm "Format $ROOT_PART as ext4 now?" default_yes; then DO_FORMAT_ROOT="yes" else die "Cannot continue without a filesystem on the root partition." fi fi if [[ "$DO_FORMAT_ROOT" == "yes" ]]; then warn "You are about to FORMAT $ROOT_PART. This ERASES the Linux root partition." if [[ "$NONINTERACTIVE" == "yes" ]]; then die "Refusing to format in --noninteractive mode without explicit human confirmation." fi danger_phrase_confirm "I UNDERSTAND THIS WIPES $ROOT_PART" || die "Formatting not confirmed." need_cmd mkfs.ext4 mkfs.ext4 -F "$ROOT_PART" root_fstype="ext4" fi if [[ "$root_fstype" == "btrfs" && -n "$BTRFS_SUBVOL" ]]; then mount -o "subvol=$BTRFS_SUBVOL" "$ROOT_PART" /mnt else mount "$ROOT_PART" /mnt fi mkdir -p /mnt/boot # Mount ESP with restrictive permissions to avoid bootctl random-seed warning mount -o umask=0077 "$ESP_PART" /mnt/boot # Verify mounts mnt_root_fs="$(findmnt -no FSTYPE /mnt | tr -d '[:space:]' || true)" mnt_boot_fs="$(findmnt -no FSTYPE /mnt/boot | tr -d '[:space:]' || true)" if [[ -z "$mnt_root_fs" ]]; then die "Root is not mounted at /mnt (unexpected)." fi if [[ "$mnt_boot_fs" != "vfat" && "$mnt_boot_fs" != "fat" && "$mnt_boot_fs" != "msdos" ]]; then warn "/mnt/boot is not VFAT. It is '$mnt_boot_fs'." warn "For this setup (systemd-boot + kernels on ESP), /boot MUST be the FAT32 ESP." die "ESP not mounted correctly." fi # ------------------------- # Determine install vs repair # ------------------------- MODE="install" HAS_EXISTING_ARCH="no" if [[ -f /mnt/etc/os-release ]] && grep -qi '^ID=arch' /mnt/etc/os-release; then HAS_EXISTING_ARCH="yes" MODE="repair" fi if [[ "$INSTALL_BASE" == "yes" ]]; then MODE="install"; fi if [[ "$INSTALL_BASE" == "no" ]]; then MODE="repair"; fi if [[ "$NONINTERACTIVE" != "yes" && "$INSTALL_BASE_EXPLICIT" != "yes" && "$HAS_EXISTING_ARCH" == "yes" ]]; then info "Existing Arch installation detected." echo "Choose an action:" echo " [1] Repair existing install (recommended)" echo " [2] Install new (may overwrite existing configuration)" while true; do printf 'Select 1 or 2: ' >/dev/tty read -r choice /dev/tty read -r t /dev/tty read -r t /dev/tty read -r t /dev/tty read -r t /dev/null; then packages_base+=(intel-ucode) elif grep -qE 'AuthenticAMD' /proc/cpuinfo 2>/dev/null; then packages_base+=(amd-ucode) fi # Sound firmware (sof-firmware is needed for many onboard audio devices) packages_base+=(sof-firmware) if [[ "$MODE" == "install" ]]; then info "Installing base system with pacstrap" # Check network connectivity using curl (more reliable than ping which may be blocked) if have curl; then if ! curl -fsSL --connect-timeout 5 https://archlinux.org/ >/dev/null 2>&1; then warn "Network connectivity check failed (curl to archlinux.org)." warn "pacstrap may fail unless you have network or a local package cache." if [[ "$NONINTERACTIVE" == "yes" ]]; then die "Network not available. Configure network first or use a local mirror." fi fi elif ! ping -c1 -W2 archlinux.org >/dev/null 2>&1; then # Fallback to ping if curl not available warn "No obvious network connectivity (ping archlinux.org failed)." warn "Note: Some networks block ICMP ping but allow HTTPS - pacstrap may still work." if [[ "$NONINTERACTIVE" == "yes" ]]; then warn "Proceeding anyway in noninteractive mode..." fi fi if ! pacstrap -K /mnt "${packages_base[@]}"; then die "pacstrap failed! Check network connectivity and try again." fi info "Generating fstab" genfstab -U /mnt >/mnt/etc/fstab else info "Repair mode: NOT running pacstrap." if [[ ! -f /mnt/etc/fstab ]]; then warn "/mnt/etc/fstab not found; creating one with genfstab." genfstab -U /mnt >/mnt/etc/fstab else cp -a /mnt/etc/fstab "/mnt/etc/fstab.bak.$(timestamp)" fi fi # ------------------------- # Ensure root + /boot lines exist and are sane in fstab # ------------------------- info "Ensuring / and /boot entries exist and /boot has safe options in fstab" ROOT_UUID="$(blkid -s UUID -o value "$ROOT_PART" || true)" [[ -n "$ROOT_UUID" ]] || die "Could not read UUID for root: $ROOT_PART" ESP_UUID="$(blkid -s UUID -o value "$ESP_PART" || true)" [[ -n "$ESP_UUID" ]] || die "Could not read UUID for ESP: $ESP_PART" ROOT_FS="$mnt_root_fs" tmp_fstab="$(mktemp)" boot_line="UUID=${ESP_UUID} /boot vfat umask=0077,noatime 0 2" # Guard against btrfs subvolume installs without explicit subvol if [[ "$ROOT_FS" == "btrfs" && "$MODE" == "install" ]]; then if [[ -z "$BTRFS_SUBVOL" && ! -f /mnt/etc/os-release ]]; then die "Btrfs detected but no subvolume specified. Re-run with --btrfs-subvol (e.g., @) or pre-create/mount a root subvolume." fi if [[ -n "$BTRFS_SUBVOL" ]]; then info "Btrfs subvolume in use for /: $BTRFS_SUBVOL" fi fi awk -v root_uuid="$ROOT_UUID" -v mode="$MODE" -v boot_line="$boot_line" ' BEGIN{IGNORECASE=1; boot_written=0} $0 ~ /^[[:space:]]*#/ { print; next } $2 == "/boot" { if (boot_written != 1) { print boot_line boot_written=1 } else { print "# (disabled by archwin script - duplicate /boot) " $0 } next } # Only modify / lines in install mode - repair mode preserves existing root config # (existing installs may use LABEL, PARTUUID, subvols, special mount options, etc.) $2 == "/" && mode == "install" && $0 !~ root_uuid { print "# (disabled by archwin script - different root) " $0 next } { print } END { if (boot_written != 1) print boot_line } ' /mnt/etc/fstab >"$tmp_fstab" # Check if root line exists (any form - UUID, LABEL, PARTUUID, device path) has_root_entry=false if grep -qE "^[^#].*[[:space:]]+/[[:space:]]+" "$tmp_fstab"; then has_root_entry=true fi # Add root line if missing entirely if [[ "$has_root_entry" == "false" ]]; then echo "UUID=${ROOT_UUID} / ${ROOT_FS} defaults,noatime 0 1" >>"$tmp_fstab" info "Added / entry to fstab" fi cp -a "$tmp_fstab" /mnt/etc/fstab rm -f "$tmp_fstab" # Show final fstab for verification info "Final /etc/fstab contents:" cat /mnt/etc/fstab | sed 's/^/ /' # ------------------------- # Chroot configuration script # ------------------------- info "Creating chroot configuration script" CHROOT_SCRIPT="/mnt/root/archwin-config.sh" cat >"$CHROOT_SCRIPT" <<'EOS' #!/usr/bin/env bash set -euo pipefail die() { echo "CHROOT ERROR: $*" >&2; exit 1; } info() { echo -e "\n[chroot] ==> $*\n"; } TIMEZONE="${TIMEZONE:-UTC}" LOCALE="${LOCALE:-en_US.UTF-8}" HOSTNAME="${HOSTNAME:-arch}" USERNAME="${USERNAME:-archuser}" ROOT_PART="${ROOT_PART:-}" MODE="${MODE:-install}" NO_FALLBACK="${NO_FALLBACK:-no}" IS_UEFI="${IS_UEFI:-yes}" # In repair mode, skip identity configuration unless system is broken if [[ "$MODE" == "repair" ]]; then info "Repair mode: Checking existing system configuration" # Check if basic identity files exist if [[ -f /etc/hostname && -f /etc/locale.conf && -f /etc/localtime ]]; then echo "[chroot] Existing identity configuration found - preserving it" echo "[chroot] Hostname: $(cat /etc/hostname)" echo "[chroot] Locale: $(grep LANG /etc/locale.conf 2>/dev/null || echo 'not set')" echo "[chroot] Timezone: $(readlink /etc/localtime 2>/dev/null | sed 's|.*/zoneinfo/||' || echo 'not set')" SKIP_IDENTITY="yes" else echo "[chroot] Identity configuration incomplete - will set up" SKIP_IDENTITY="no" fi else SKIP_IDENTITY="no" fi if [[ "$SKIP_IDENTITY" != "yes" ]]; then # Timezone + clock info "Timezone: $TIMEZONE" if [[ -e "/usr/share/zoneinfo/$TIMEZONE" ]]; then ln -sf "/usr/share/zoneinfo/$TIMEZONE" /etc/localtime else echo "[chroot] WARN: Timezone file not found: /usr/share/zoneinfo/$TIMEZONE (leaving as-is)" fi hwclock --systohc || true # Locale info "Locale: $LOCALE" if [[ -f /etc/locale.gen ]]; then if grep -qE "^[#[:space:]]*${LOCALE}[[:space:]]" /etc/locale.gen; then sed -i "s/^[#[:space:]]*\\(${LOCALE}[[:space:]].*\\)/\\1/" /etc/locale.gen else echo "[chroot] WARN: $LOCALE not present in /etc/locale.gen (not modifying it)" fi locale-gen || true fi echo "LANG=${LOCALE}" > /etc/locale.conf # Console keymap (required by sd-vconsole mkinitcpio hook) if [[ ! -f /etc/vconsole.conf ]]; then echo "KEYMAP=us" > /etc/vconsole.conf fi # Hostname + hosts info "Hostname: $HOSTNAME" echo "$HOSTNAME" > /etc/hostname cat > /etc/hosts < /etc/vconsole.conf fi # Sudo: enable wheel info "Enabling sudo for wheel group" if [[ -f /etc/sudoers ]]; then sed -i 's/^# \(%wheel ALL=(ALL:ALL) ALL\)$/\1/' /etc/sudoers fi # NetworkManager info "Enabling NetworkManager" systemctl enable NetworkManager || true # Enable systemd-boot automatic updates (as of systemd 250+) info "Enabling systemd-boot automatic updates" systemctl enable systemd-boot-update.service || true # User management USER_EXPLICIT="${USER_EXPLICIT:-no}" # In repair mode, only create/modify user if explicitly requested via --user # This prevents accidentally creating 'archuser' on existing systems if [[ "$MODE" == "repair" && "$USER_EXPLICIT" != "yes" ]]; then echo "[chroot] Repair mode: Skipping user creation (use --user to create/ensure a user)" # Try to find an existing wheel user to report existing_wheel_user="$(getent group wheel | cut -d: -f4 | cut -d, -f1 || true)" if [[ -n "$existing_wheel_user" ]]; then echo "[chroot] Existing wheel user found: $existing_wheel_user" fi else info "Ensuring user exists: $USERNAME" if ! id "$USERNAME" >/dev/null 2>&1; then useradd -m -G wheel -s /bin/bash "$USERNAME" echo "[chroot] Created user: $USERNAME" else echo "[chroot] User $USERNAME already exists" # Ensure user is in wheel group usermod -aG wheel "$USERNAME" 2>/dev/null || true fi fi # Passwords - handled outside chroot for security (no env var leaking) echo "[chroot] NOTE: Password setup will be handled after chroot exits." # systemd-boot + entries info "Installing systemd-boot to ESP mounted at /boot" bootctl_args=(--path=/boot) if [[ "$IS_UEFI" != "yes" ]]; then bootctl_args+=(--no-variables) fi bootctl_args+=(install) if ! bootctl "${bootctl_args[@]}"; then echo "[chroot] ERROR: bootctl install failed!" >&2 echo "[chroot] This is a critical error - the system may not boot." >&2 exit 1 fi info "Creating loader.conf" mkdir -p /boot/loader cat > /boot/loader/loader.conf <<'EOF' default arch.conf timeout 4 console-mode max editor no auto-entries yes auto-firmware yes EOF # Build Arch entry [[ -n "$ROOT_PART" ]] || die "ROOT_PART not provided to chroot script." ROOT_UUID="$(blkid -s UUID -o value "$ROOT_PART" || true)" [[ -n "$ROOT_UUID" ]] || die "Could not determine UUID for root partition: $ROOT_PART" # Determine microcode initrd lines if present ucode_lines=() if [[ -f /boot/intel-ucode.img ]]; then ucode_lines+=( "initrd /intel-ucode.img" ) fi if [[ -f /boot/amd-ucode.img ]]; then ucode_lines+=( "initrd /amd-ucode.img" ) fi info "Writing Arch boot entries (root=UUID=$ROOT_UUID)" mkdir -p /boot/loader/entries # Main entry { echo "title Arch Linux" echo "linux /vmlinuz-linux" for l in "${ucode_lines[@]}"; do echo "$l"; done echo "initrd /initramfs-linux.img" echo "options root=UUID=${ROOT_UUID} rw" } > /boot/loader/entries/arch.conf # Fallback entry (uses fallback initramfs with all modules) if [[ "$NO_FALLBACK" != "yes" ]]; then { echo "title Arch Linux (fallback initramfs)" echo "linux /vmlinuz-linux" for l in "${ucode_lines[@]}"; do echo "$l"; done echo "initrd /initramfs-linux-fallback.img" echo "options root=UUID=${ROOT_UUID} rw" } > /boot/loader/entries/arch-fallback.conf else echo "[chroot] Skipping fallback initramfs entry (--no-fallback or small ESP)" rm -f /boot/loader/entries/arch-fallback.conf fi # Windows entry (explicit) - Note: systemd-boot auto-detects Windows Boot Manager # but we create an explicit entry for clarity and to ensure it appears if [[ -f /boot/EFI/Microsoft/Boot/Bootmgfw.efi || -f /boot/EFI/Microsoft/Boot/bootmgfw.efi ]]; then info "Writing Windows boot entry" cat > /boot/loader/entries/windows.conf <<'EOF' title Windows efi /EFI/Microsoft/Boot/Bootmgfw.efi EOF else echo "[chroot] NOTE: Windows Boot Manager not found on /boot/EFI/Microsoft." echo "[chroot] NOTE: systemd-boot will auto-detect it if present (shows as 'auto-windows')." fi # Ensure kernel + initramfs are on /boot (ESP) info "Regenerating initramfs and ensuring /boot has kernel files" if [[ ! -f /boot/vmlinuz-linux || ! -f /boot/initramfs-linux.img ]]; then echo "[chroot] NOTE: Kernel/initramfs missing from /boot; reinstalling linux package." if ! pacman -S --needed --noconfirm linux linux-firmware; then echo "[chroot] ERROR: Failed to install kernel!" >&2 echo "[chroot] The system will not boot without a kernel." >&2 exit 1 fi fi # Build only the default preset when fallback is disabled (saves ESP space) if [[ "$NO_FALLBACK" == "yes" ]]; then # Persistently disable fallback so future kernel updates don't fail on small ESPs. if [[ -f /etc/mkinitcpio.d/linux.preset ]]; then tmp_preset="$(mktemp)" awk ' BEGIN{done=0} /^[[:space:]]*PRESETS=/ { print "PRESETS=(\"default\")" done=1 next } { print } END{ if (done==0) print "PRESETS=(\"default\")" } ' /etc/mkinitcpio.d/linux.preset > "$tmp_preset" mv "$tmp_preset" /etc/mkinitcpio.d/linux.preset fi echo "[chroot] Building only default initramfs (fallback disabled for small ESP)" if ! mkinitcpio -p linux; then echo "[chroot] ERROR: mkinitcpio failed!" >&2 echo "[chroot] The system may not boot without proper initramfs." >&2 exit 1 fi # Remove fallback image if it exists (from previous installs) rm -f /boot/initramfs-linux-fallback.img else if ! mkinitcpio -P; then echo "[chroot] ERROR: mkinitcpio failed!" >&2 echo "[chroot] The system may not boot without proper initramfs." >&2 exit 1 fi fi # Verify critical boot files exist if [[ ! -f /boot/vmlinuz-linux ]]; then echo "[chroot] ERROR: /boot/vmlinuz-linux not found after install!" >&2 exit 1 fi if [[ ! -f /boot/initramfs-linux.img ]]; then echo "[chroot] ERROR: /boot/initramfs-linux.img not found after mkinitcpio!" >&2 exit 1 fi echo "[chroot] Verified: kernel and initramfs present on /boot" # Show status echo "[chroot] Boot files on ESP (/boot):" ls -la /boot | sed 's/^/[chroot] /' echo "[chroot] Entries:" ls -la /boot/loader/entries | sed 's/^/[chroot] /' echo "[chroot] arch.conf:" sed 's/^/[chroot] /' /boot/loader/entries/arch.conf EOS chmod +x "$CHROOT_SCRIPT" # ------------------------- # Collect user prompts for passwords (REQUIRED for bootable system) # ------------------------- SET_PASSWORDS="no" if [[ "$NONINTERACTIVE" != "yes" ]]; then # In repair mode, passwords are optional (system should already have them) if [[ "$MODE" == "repair" ]]; then if confirm "Update root + user passwords?" default_no; then SET_PASSWORDS="interactive" fi else # Fresh install: passwords are REQUIRED to have a bootable system warn "IMPORTANT: You must set passwords to have a bootable system!" echo "Without passwords, you will not be able to log in after reboot." echo SET_PASSWORDS="interactive" fi else # Noninteractive mode: require explicit password or warn loudly if [[ "$MODE" != "repair" ]]; then if [[ "$ALLOW_NO_PASSWORD" != "yes" ]]; then die "NONINTERACTIVE install mode requires --allow-no-password flag. Without passwords, the installed system will be UNBOOTABLE (cannot log in). If you understand this risk, re-run with --allow-no-password. Alternatively, after install completes, run: arch-chroot /mnt passwd" fi warn "NONINTERACTIVE MODE with --allow-no-password: No passwords will be set!" warn "The installed system will be UNBOOTABLE without manual password configuration." warn "After chroot completes, you MUST run: arch-chroot /mnt passwd" fi fi # ------------------------- # Run chroot config # ------------------------- info "Running configuration inside chroot" if [[ "${SKIP_CHROOT:-no}" == "yes" ]]; then warn "Skipping chroot execution (SKIP_CHROOT=yes)." else env \ TIMEZONE="$TIMEZONE" \ LOCALE="$LOCALE" \ HOSTNAME="$HOSTNAME" \ USERNAME="$USERNAME" \ USER_EXPLICIT="$USER_EXPLICIT" \ ROOT_PART="$ROOT_PART" \ MODE="$MODE" \ NO_FALLBACK="$NO_FALLBACK" \ IS_UEFI="$IS_UEFI" \ arch-chroot /mnt /bin/bash /root/archwin-config.sh fi # ------------------------- # Set passwords AFTER chroot (interactive, no env leaking) # ------------------------- if [[ "$SET_PASSWORDS" == "interactive" ]]; then info "Setting passwords (interactive)" echo "You will now set passwords for root and user '$USERNAME'." echo echo "Setting ROOT password:" while ! arch-chroot /mnt passwd root /dev/tty 2>&1; do warn "Password setting failed. Please try again." done echo echo "Setting password for user '$USERNAME':" while ! arch-chroot /mnt passwd "$USERNAME" /dev/tty 2>&1; do warn "Password setting failed. Please try again." done info "Passwords set successfully" elif [[ "$MODE" != "repair" && "$NONINTERACTIVE" != "yes" ]]; then # Fresh install without passwords - final warning warn "WARNING: No passwords were set!" warn "You MUST set passwords before rebooting or you will be locked out." echo echo "To set passwords now, run:" echo " arch-chroot /mnt passwd" echo " arch-chroot /mnt passwd $USERNAME" echo if confirm "Set passwords now?" default_yes; then echo "Setting ROOT password:" arch-chroot /mnt passwd root /dev/tty 2>&1 || true echo "Setting password for user '$USERNAME':" arch-chroot /mnt passwd "$USERNAME" /dev/tty 2>&1 || true fi fi # ------------------------- # Post checks # ------------------------- info "Post-checks" echo "ESP contents:" # shellcheck disable=SC2012 ls -la /mnt/boot/EFI 2>/dev/null | sed 's/^/ /' || true echo "systemd-boot loader:" # shellcheck disable=SC2012 ls -la /mnt/boot/EFI/systemd 2>/dev/null | sed 's/^/ /' || true echo "Boot entries:" # shellcheck disable=SC2012 ls -la /mnt/boot/loader/entries 2>/dev/null | sed 's/^/ /' || true if is_uefi_boot; then info "UEFI NVRAM entries (best effort)" if have efibootmgr; then efibootmgr -v || true else if arch-chroot /mnt efibootmgr -v; then : else warn "efibootmgr is not available (install it with: pacman -S efibootmgr)." fi fi echo echo "If 'Linux Boot Manager' exists, set it first in firmware boot order." else warn "Not booted in UEFI mode; cannot create/verify NVRAM boot entries here." echo "After you boot into installed Arch in UEFI mode, you can create one with:" partn="$(lsblk -no PARTN "$ESP_PART" | tr -d '[:space:]' || true)" echo " sudo efibootmgr -c -d $DISK -p ${partn:-} -L 'Linux Boot Manager' -l '\\EFI\\systemd\\systemd-bootx64.efi'" fi # ------------------------- # Finish # ------------------------- info "Done." echo "You can now reboot:" echo " umount -R /mnt" echo " reboot" echo echo "IMPORTANT POST-INSTALL NOTES:" echo " 1. If the machine boots straight into Windows, enter firmware setup (BIOS)" echo " and put 'Linux Boot Manager' first in the boot order." echo " 2. Windows Fast Startup should be DISABLED to prevent filesystem corruption." echo " In Windows: Control Panel > Power Options > Choose what the power buttons do" echo " > Change settings that are currently unavailable > Uncheck 'Turn on fast startup'" echo " 3. Consider disabling Windows hibernation if sharing filesystems:" echo " In Windows admin cmd: powercfg /h off" echo echo "Log saved at: $LOG_FILE"