One-command Arch Linux installer — Zen kernel, KDE Plasma 6, LUKS2 encryption, and a fully wired developer environment, curlable from any Arch ISO.
- Overview
- Key Features
- Architecture
- Script Distribution
- Installation Flow
- OS Configuration
- Curlable Scripts
- Technology Stack
- USB Rescue & Recovery
- Troubleshooting & Known Issues
- Stale Arch ISO Keyring
- Clock Drift Causes TLS/Signature Failures
- Previous Partial Install —
/mntBusy /cryptrootAlready Open - Partition Table Not Refreshed After
sgdisk - Disk Detected as Removable / Wrong Target Disk
- Hidden Password Prompt Broken on Arch ISO Console
- LUKS Passphrase Fails When Running via
curl | bash - Intel QAT Firmware Warnings in mkinitcpio
- Leftover Non-Zen Kernels Consuming Disk / Boot Entries
/bootFails to Mount — Emergency Shell on Bootmodules_disabled=1Prevents vfat Module from Loading- Stale
pacman.db.lckBlocks Chroot Repair - DNS Not Available Inside
arch-chroot - efivarfs Not Mounted in Chroot —
bootctl installFails - RTL8821CE Wi-Fi Doesn't Work After Install
- KDE Dark Theme Only Applies to Qt Apps — GTK Apps Stay Light
- Diagnostic Commands Quick Reference
- Core Capabilities
- Project Roadmap
- Development Status
- Contributing
- License
arch-custom is a collection of battle-tested Bash scripts that automate the complete Arch Linux setup lifecycle — from raw disk to a privacy-hardened, developer-ready desktop — in a single curl | bash command.
It targets solo developers, power users, and teams who want a reproducible, opinionated Arch Linux environment without manual configuration overhead. Every script is idempotent, ShellCheck-clean, and designed to run safely against a clean Arch ISO or a live system.
Important
These scripts partition and format disks. Back up all data before running arch-install.sh on any machine. The installer targets /dev/mmcblk0 by default — override with the TARGET_DISK environment variable before running.
Domain: Linux automation / DevOps / Developer tooling Who it is for: Developers who reinstall frequently, homelab engineers, privacy-conscious power users, and AI-assisted development workflows (VS Code + GitHub Copilot Beastmode).
| Icon | Feature | Description | Impact | Status |
|---|---|---|---|---|
| 🔒 | LUKS2 Full-Disk Encryption | Root partition encrypted at rest; passphrase prompted at boot | Data security at rest | ✅ Stable |
| ⚡ | linux-zen Kernel | Desktop-optimised kernel with low-latency and performance patches | Snappier desktop UX | ✅ Stable |
| 🖥️ | KDE Plasma 6 + SDDM | Full Wayland-capable desktop with automatic dark-mode configuration | Zero manual KDE setup | ✅ Stable |
| 🔁 | Idempotent One-liners | Every script can be re-run: skips already-completed steps automatically | Safe to run twice | ✅ Stable |
| 🤖 | Copilot Beastmode | Pre-wires VS Code Copilot agents and chatmodes for autonomous development | Instant AI dev env | ✅ Stable |
| 🛠️ | Project Bootstrapper | Scaffolds memory-bank, CI, .vscode, .github, .copilot, and docs for any project | Consistent repo setup | ✅ Stable |
| 🚑 | USB Rescue Suite | Reinstall bootloader, fix vfat, chroot-repair from an Arch USB stick | System recovery | ✅ Stable |
| 🎨 | KDE Dark Theme Fix | Auto-applies GTK/QT dark-theme integration so apps are visually consistent | GTK app theming | ✅ Stable |
| ⚙️ | systemd-boot + GRUB | Primary bootloader is systemd-boot; GRUB restore scripts included | Dual bootloader support | ✅ Stable |
- Privacy-first defaults: Brave browser, tracking-protection stack, and KDE lockscreen privacy hardening deployed automatically during first-login setup.
- AUR-native tooling: Installs
visual-studio-code-bin(the official Microsoft VS Code) via AUR, with automatic fallback handling. - ShellCheck CI: All scripts are linted on every push. Zero shellcheck warnings are enforced by GitHub Actions.
- Configurable targets:
TARGET_DISK,DEFAULT_PASS, and other variables are overridable via environment before piping to bash.
The project is structured as three concentric layers: Installation, Configuration, and Recovery.
flowchart TD
A[Arch Linux ISO] --> B[arch-install.sh]
B --> C{Phase 1: Disk Setup}
C --> D[Partition: 512 MB EFI + LUKS2 Root]
D --> E[Format and Mount Partitions]
E --> F[pacstrap: base + linux-zen + KDE]
F --> G[systemd-boot + mkinitcpio]
G --> H[Create User + Enable Services]
H --> I[Reboot into New System]
I --> J[arch-user-setup.sh]
J --> K{Phase 2: Dev Environment}
K --> L[VS Code via AUR]
K --> M[Brave Browser]
K --> N[enable-beastmode.sh]
K --> O[enable-copilot-autopilot.sh]
K --> P[Privacy Hardening Stack]
Q[USB Rescue Boot] --> R[arch-usb-rescue.sh]
R --> S[Mount and chroot into install]
S --> T[fix-grub-from-usb.sh]
S --> U[fix-vfat-from-usb.sh]
S --> V[fix-boot-mount-emergency.sh]
style A fill:#1793D1,color:#fff
style I fill:#4EAA25,color:#fff
style Q fill:#cc3333,color:#fff
Component responsibilities:
| Layer | Scripts | Responsibility |
|---|---|---|
| Installation | arch-install.sh, arch-config.sh |
Disk partition, LUKS2, base system, KDE, bootloader |
| Configuration | arch-user-setup.sh, enable-beastmode.sh, enable-copilot-autopilot.sh, kde-dark-theme-fix.sh |
Post-login developer tools, AI tooling, theming |
| Scaffolding | project-bootstrap.sh |
Reproducible project structure for any repository |
| Recovery | arch-usb-rescue.sh, arch-usb-repair-all.sh, fix-* |
Chroot repair, bootloader reinstall, vfat fixes |
External integrations: AUR (yay/makepkg), GitHub raw content CDN (curlable delivery), systemd, cryptsetup, pacman.
pie title Script Category Distribution
"Installation" : 2
"Configuration and Dev Tools" : 4
"Recovery and Repair" : 5
"Scaffolding" : 1
"Utility" : 1
| Category | Count | Scripts |
|---|---|---|
| Installation | 2 | arch-install.sh, arch-config.sh |
| Configuration & Dev Tools | 4 | arch-user-setup.sh, enable-beastmode.sh, enable-copilot-autopilot.sh, kde-dark-theme-fix.sh |
| Recovery & Repair | 5 | arch-usb-rescue.sh, arch-usb-repair-all.sh, fix-grub-from-usb.sh, fix-vfat-from-usb.sh, fix-boot-mount-debug.sh |
| Scaffolding | 1 | project-bootstrap.sh |
| Utility | 1 | fix-boot-mount-emergency.sh |
sequenceDiagram
participant Dev as Developer
participant ISO as Arch ISO
participant Script as arch-install.sh
participant Disk as Target Disk
participant Sys as New System
Dev->>ISO: Boot from USB in UEFI mode
Dev->>ISO: Connect to internet via iwctl
Dev->>Script: curl -fsSL arch-install.sh | bash
Script->>Disk: Partition 512 MB EFI + LUKS2 root
Script->>Disk: Format and open cryptroot
Script->>Sys: pacstrap base + linux-zen + KDE Plasma 6
Script->>Sys: systemd-boot + mkinitcpio + locale
Script->>Sys: Create user, enable SDDM and NetworkManager
Script-->>Dev: Done - reboot and enter LUKS passphrase
Dev->>Sys: Login at SDDM
Dev->>Sys: curl -fsSL arch-user-setup.sh | bash
Sys-->>Dev: VS Code + Brave + Beastmode ready
Boot from Arch Linux ISO, connect to the internet, then run:
curl -fsSL https://raw.githubusercontent.com/hkevin01/arch-custom/main/arch-install.sh | bashTip
Override the target disk before running if your system does not use /dev/mmcblk0:
export TARGET_DISK=/dev/sda
curl -fsSL https://raw.githubusercontent.com/hkevin01/arch-custom/main/arch-install.sh | bashAfter the script completes, unmount and reboot:
umount -R /mnt
cryptsetup close cryptroot
rebootOn first boot:
- Enter your LUKS encryption passphrase at the prompt
- Log in at the SDDM display manager
After rebooting into the new system, run as your normal user (not root):
curl -fsSL https://raw.githubusercontent.com/hkevin01/arch-custom/main/arch-user-setup.sh | bashThis installs VS Code (official Microsoft build), Brave browser, Beastmode, Copilot autopilot settings, and the full privacy-hardening stack.
Run inside any project directory to add memory-bank, CI, .vscode, .github, .copilot, and docs:
curl -fsSL https://raw.githubusercontent.com/hkevin01/arch-custom/main/project-bootstrap.sh | bashOr target a specific directory:
curl -fsSL https://raw.githubusercontent.com/hkevin01/arch-custom/main/project-bootstrap.sh | bash -s -- /path/to/projectarch-install.sh configures systemd-boot as the default bootloader. It is the simplest UEFI-native option with zero extra dependencies.
EFI layout after install:
/boot/
├── EFI/
│ └── systemd/
│ └── systemd-bootx64.efi
├── loader/
│ ├── loader.conf ← timeout and default entry
│ └── entries/
│ └── arch.conf ← boot entry for linux-zen + LUKS
└── vmlinuz-linux-zen
└── initramfs-linux-zen.img
/boot/loader/loader.conf (written by the installer):
default arch.conf
timeout 5
console-mode max
editor no/boot/loader/entries/arch.conf (written by the installer):
title Arch Linux (linux-zen)
linux /vmlinuz-linux-zen
initrd /initramfs-linux-zen.img
options cryptdevice=UUID=<UUID>:cryptroot root=/dev/mapper/cryptroot rw quiet splashManage entries manually after install:
# Update bootloader binary to latest
bootctl update
# List installed entries
bootctl list
# Re-install to EFI (use after EFI partition repair)
bootctl install --esp-path=/bootIf systemd-boot fails or a dual-boot setup requires GRUB, use the included repair script or install GRUB manually:
# From a running system
sudo pacman -S --noconfirm grub efibootmgr os-prober
sudo grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB
sudo grub-mkconfig -o /boot/grub/grub.cfgFrom Arch USB rescue environment (system won't boot):
# Automated GRUB reinstall from USB
curl -fsSL https://raw.githubusercontent.com/hkevin01/arch-custom/main/fix-grub-from-usb.sh | sudo bash
# Or with passphrase pre-supplied
export LUKS_PASSPHRASE=yourpassphrase
curl -fsSL https://raw.githubusercontent.com/hkevin01/arch-custom/main/fix-grub-from-usb.sh | sudo -E bash/etc/default/grub recommended settings for LUKS2 + linux-zen:
GRUB_DEFAULT=0
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="Arch"
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash loglevel=3"
GRUB_CMDLINE_LINUX="cryptdevice=UUID=<UUID>:cryptroot root=/dev/mapper/cryptroot"
GRUB_PRELOAD_MODULES="part_gpt part_msdos luks2 cryptodisk"
GRUB_ENABLE_CRYPTODISK=yWarning
After editing /etc/default/grub, always regenerate the config:
sudo grub-mkconfig -o /boot/grub/grub.cfgThe installer writes these linux-zen kernel parameters to the boot entry. They can be tuned after install by editing /boot/loader/entries/arch.conf (systemd-boot) or /etc/default/grub (GRUB).
| Parameter | Purpose | Default |
|---|---|---|
cryptdevice=UUID=...:cryptroot |
Unlock LUKS2 root at boot | Required — set by installer |
root=/dev/mapper/cryptroot |
Mount decrypted root | Required |
rw |
Mount root read-write | Required |
quiet |
Suppress most boot messages | Enabled |
splash |
Show Plymouth boot splash | Enabled |
loglevel=3 |
Kernel log level (errors only) | Optional |
mitigations=off |
Disable CPU spectre/meltdown mitigations | Optional — improves performance on trusted hardware |
nowatchdog |
Disable hardware watchdog timers | Optional — reduces latency |
nmi_watchdog=0 |
Disable NMI watchdog | Optional |
transparent_hugepage=madvise |
Memory page policy for desktop workloads | Recommended |
Performance-tuned kernel params for linux-zen desktop (append to options line):
# /boot/loader/entries/arch.conf — options line
options cryptdevice=UUID=<UUID>:cryptroot root=/dev/mapper/cryptroot rw quiet \
splash loglevel=3 nowatchdog nmi_watchdog=0 transparent_hugepage=madviseThe installer configures /etc/mkinitcpio.conf with the hooks required for LUKS2 decryption at boot.
/etc/mkinitcpio.conf HOOKS line:
HOOKS=(base udev autodetect microcode modconf kms keyboard keymap consolefont block encrypt filesystems fsck)Key hooks explained:
| Hook | Role |
|---|---|
keyboard |
Ensures keyboard is available for passphrase entry |
keymap |
Loads the correct keymap before passphrase prompt |
encrypt |
Unlocks LUKS2 device before root is mounted |
filesystems |
Mounts all filesystems after decryption |
Regenerate initramfs after any hook or kernel change:
sudo mkinitcpio -PNote
After installing a new kernel or changing HOOKS, always run mkinitcpio -P and verify the boot entry still points to the correct initramfs-linux-zen.img.
Install GPU drivers after first login. The correct package set depends on your GPU.
🟥 AMD GPU (recommended for linux-zen)
# Mesa + Vulkan — open source, best linux-zen compatibility
sudo pacman -S --noconfirm mesa lib32-mesa vulkan-radeon lib32-vulkan-radeon \
xf86-video-amdgpu libva-mesa-driver mesa-vdpau
# Hardware video acceleration
sudo pacman -S --noconfirm libva-utils vdpauinfoFor ROCm / AI/ML workloads:
sudo pacman -S --noconfirm rocm-opencl-runtime rocm-hip-sdk🟩 NVIDIA GPU
# NVIDIA proprietary driver — requires linux-zen-headers
sudo pacman -S --noconfirm linux-zen-headers nvidia-dkms nvidia-utils \
lib32-nvidia-utils nvidia-settings
# Regenerate initramfs after install
sudo mkinitcpio -P
# Enable DRM kernel mode setting (add to kernel params)
# options line in /boot/loader/entries/arch.conf:
# nvidia-drm.modeset=1[!WARNING] After every kernel update, DKMS will rebuild the NVIDIA module automatically. If it fails, you may boot to a black screen — keep an Arch USB ready.
🔵 Intel GPU
# Intel integrated graphics
sudo pacman -S --noconfirm mesa lib32-mesa vulkan-intel lib32-vulkan-intel \
intel-media-driver libva-intel-driver
# GuC / HuC firmware (11th gen+ recommended)
# Add to /etc/modprobe.d/i915.conf:
echo "options i915 enable_guc=3" | sudo tee /etc/modprobe.d/i915.conf
sudo mkinitcpio -Plinux-zen benefits from zram for compressed in-memory swap. Recommended for systems with 8–32 GB RAM.
# Install zram-generator
sudo pacman -S --noconfirm zram-generator
# Configure: /etc/systemd/zram-generator.conf
sudo tee /etc/systemd/zram-generator.conf <<'EOF'
[zram0]
zram-size = ram / 2
compression-algorithm = zstd
swap-priority = 100
EOF
# Enable immediately
sudo systemctl daemon-reload
sudo systemctl start systemd-zram-setup@zram0.service
# Verify
zramctl
swapon --showFor a traditional swap file (alternative to zram):
sudo dd if=/dev/zero of=/swapfile bs=1M count=8192 status=progress
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstabKeep system firmware up to date with fwupd:
# Install fwupd
sudo pacman -S --noconfirm fwupd
# Refresh firmware metadata
sudo fwupdmgr refresh
# Check for updates
sudo fwupdmgr get-updates
# Apply all available firmware updates
sudo fwupdmgr updateNote
fwupd covers UEFI firmware, NVMe SSDs, USB controllers, and many peripheral firmwares. Run after first boot and periodically thereafter.
arch-user-setup.sh uses raw makepkg for AUR builds. For ongoing AUR management, install yay or paru:
# Install yay (AUR helper written in Go)
cd /tmp
git clone https://aur.archlinux.org/yay-bin.git
cd yay-bin && makepkg -si --noconfirm
# Usage
yay -S package-name # install from AUR
yay -Syu # update all packages including AUR
yay -Ss search-term # search AUR + repos# Install paru (Rust-based yay alternative, stricter security)
cd /tmp
git clone https://aur.archlinux.org/paru-bin.git
cd paru-bin && makepkg -si --noconfirm✅ Full Post-Install Verification Checklist
Boot & Encryption:
- System boots and prompts for LUKS passphrase
- SDDM login manager appears after decryption
-
lsblkshowscryptrootmapped over the root partition - Bootloader entry visible via
bootctl listorgrub-bios-setup
Desktop:
- KDE Plasma 6 loads in Wayland session
- Audio working:
pactl infoshows PipeWire running - Wi-Fi connecting via NetworkManager (
nmcli device status) - Dark theme applied to both Qt and GTK apps (
kde-dark-theme-fix.sh)
Developer Tools:
- VS Code launches and shows Microsoft branding (not code-oss)
- GitHub Copilot extension authenticates
- Beastmode agent listed in VS Code agent selector
-
git,curl,base-develavailable from terminal
Security:
-
cryptsetup status cryptrootshows active LUKS2 encryption - No world-readable secrets in
~/.configor/etc - Brave browser installed and opens with uBlock Origin active
-
fwupdmgr get-updatesrun and any critical firmware applied
Performance (optional):
-
zramctlshows zram0 active with zstd compression -
systemd-analyze blamereviewed — no unexpectedly slow services - GPU driver installed and
glxinfo | grep OpenGLreturns correct renderer
All scripts are delivered over GitHub raw content and are safe to pipe directly into bash from any Arch environment.
| Script | Category | Purpose |
|---|---|---|
arch-install.sh |
Installation | Full Arch install: LUKS2, linux-zen, KDE Plasma 6, systemd-boot |
arch-config.sh |
Installation | Post-install system configuration and hardening |
arch-user-setup.sh |
Configuration | First-login: VS Code, Brave, Beastmode, privacy tools |
enable-beastmode.sh |
Configuration | VS Code Beastmode agent + chatmode installer |
enable-copilot-autopilot.sh |
Configuration | Copilot agent settings (auto-installs jq) |
kde-dark-theme-fix.sh |
Configuration | GTK/QT dark-theme fix for KDE |
project-bootstrap.sh |
Scaffolding | Scaffold any project with memory-bank, CI, .vscode |
arch-usb-rescue.sh |
Recovery | Chroot into LUKS install from USB: Wi-Fi + boot repair |
arch-usb-repair-all.sh |
Recovery | Full repair sweep from USB rescue environment |
fix-grub-from-usb.sh |
Recovery | Reinstall GRUB EFI bootloader from Arch USB |
fix-vfat-from-usb.sh |
Recovery | Repair VFAT EFI partition from USB |
fix-boot-mount-debug.sh |
Recovery | Debug boot mount failures with verbose output |
fix-boot-mount-emergency.sh |
Recovery | Emergency single-command boot mount repair |
scripts/diagnose-install.sh |
Diagnostics | Capture full system state to a timestamped log for debugging |
| Technology | Purpose | Why Chosen | Alternatives Considered |
|---|---|---|---|
| Bash | All scripting | Universal on any Arch ISO; no runtime deps | Python (too heavy for early-boot), POSIX sh (too limited) |
| linux-zen | Kernel | Desktop-optimised, low-latency patches baked in | linux (mainline), linux-lts (too conservative) |
| KDE Plasma 6 | Desktop | Wayland-native, highly configurable, active upstream | GNOME (less configurable), Sway (no GUI config) |
| LUKS2 + cryptsetup | Disk encryption | Industry standard; systemd-boot + initramfs integration | VeraCrypt (no initramfs hooks), plain dm-crypt |
| systemd-boot | Primary bootloader | UEFI-native, zero config overhead, fast boot times | GRUB (heavier — repair scripts included as fallback) |
| GRUB | Fallback bootloader | Repair scripts support GRUB reinstall from USB for dual-boot | systemd-boot (default) |
| PipeWire | Audio | Drop-in PulseAudio replacement; lower latency, better Bluetooth | PulseAudio (legacy), JACK (pro-audio only) |
| mkinitcpio | initramfs builder | Arch-native, hooks for encrypt + filesystems | dracut (less common on Arch) |
| zram-generator | Compressed swap | In-memory swap with zstd; no disk wear | swapfile (slower), zswap (less flexible) |
| fwupd | Firmware updates | LVFS-backed updates for UEFI, NVMe, USB controllers | Manual vendor flashing |
| GitHub Actions | CI/CD | Free, native GitHub integration, fast shellcheck runs | GitLab CI, Circle CI |
| ShellCheck | Linting | Catches shell pitfalls and portability issues automatically | Bash-lint (less comprehensive) |
🚑 Full USB Rescue Procedure
Boot from an Arch Linux USB stick in UEFI mode, connect to Wi-Fi, then run:
# Full automated rescue (mounts, chroots, reinstalls boot)
curl -fsSL https://raw.githubusercontent.com/hkevin01/arch-custom/main/arch-usb-rescue.sh | sudo bashTo target a different disk (default is /dev/mmcblk0):
export TARGET_DISK=/dev/sda
export EFI_PART=/dev/sda1
export ROOT_PART=/dev/sda2
curl -fsSL https://raw.githubusercontent.com/hkevin01/arch-custom/main/arch-usb-rescue.sh | sudo bashBootloader-only repair — GRUB:
export LUKS_PASSPHRASE=yourpassphrase
curl -fsSL https://raw.githubusercontent.com/hkevin01/arch-custom/main/fix-grub-from-usb.sh | sudo -E bashBootloader-only repair — systemd-boot:
# After chrooting manually or via arch-usb-rescue.sh:
bootctl install --esp-path=/boot
bootctl updatevfat / EFI partition repair:
curl -fsSL https://raw.githubusercontent.com/hkevin01/arch-custom/main/fix-vfat-from-usb.sh | sudo bashDebug boot mount failures:
curl -fsSL https://raw.githubusercontent.com/hkevin01/arch-custom/main/fix-boot-mount-debug.sh | sudo bash📋 Recovery Script Environment Variables
| Variable | Default | Description |
|---|---|---|
TARGET_DISK |
/dev/mmcblk0 |
Target block device |
EFI_PART |
${TARGET_DISK}p1 |
EFI partition |
ROOT_PART |
${TARGET_DISK}p2 |
LUKS root partition |
CRYPT_NAME |
cryptroot |
dm-crypt mapper name |
DEFAULT_PASS |
password |
Fallback LUKS passphrase (override this!) |
LUKS_PASSPHRASE |
(empty) | Pass via env to skip interactive prompt |
PASS_ATTEMPTS |
3 |
Max passphrase retry attempts |
[!WARNING] Always set
DEFAULT_PASSorLUKS_PASSPHRASEvia environment variable — never hardcode passphrases in scripts or commit them to version control.
Real problems encountered on this specific machine during live installs. Every workaround listed here is codified in the scripts — this section explains why.
Symptom: pacman -S fails with signature from ... is invalid or unknown trust errors during pacstrap.
Cause: The ISO keyring is frozen at ISO build time. After a few months the keyring is too old to verify modern packages.
Fix (automated in arch-install.sh):
pacman -Sy archlinux-keyringThis is run as a non-fatal pre-flight step before pacstrap. If the network is available, the keyring is refreshed. If it fails the install continues because pacstrap will pull a fresh keyring anyway.
Symptom: pacstrap or pacman -Sy fail with certificate/signature errors even after keyring refresh. Error messages mention HTTPS or GPG signature verification.
Cause: The Arch ISO RTC clock may be minutes or hours off when booting bare metal. TLS certificates and GPG timestamps require a roughly correct system clock.
Fix (automated in arch-install.sh and all rescue scripts):
timedatectl set-ntp trueCalled before pacstrap and before any pacman sync in the rescue environment.
Symptom: On a second or third install attempt: mount: /mnt: already mounted, Device cryptroot already exists, or swapon: /dev/... already active.
Cause: A previous failed or interrupted run left /mnt mounted, the LUKS mapper open, or swap active.
Fix (automated in arch-install.sh via cleanup_previous_install_state()):
swapoff -a
umount -R /mnt 2>/dev/null || true
cryptsetup close cryptroot 2>/dev/null || true
udevadm settleRun this manually before retrying, or just rerun arch-install.sh — the cleanup function runs automatically at the start of every install.
Symptom: mkfs.fat or cryptsetup luksFormat fail immediately after sgdisk with "device or resource busy" or "no such file or directory" for the new partition nodes.
Cause: sgdisk writes the new partition table but the kernel may not see the updated partition nodes immediately.
Fix (automated in arch-install.sh):
partprobe "$TARGET_DISK"
sleep 2The sleep 2 gives udev time to settle and create the new device nodes.
Symptom: The installer picks the wrong disk or fails to detect any non-removable disk.
Cause: The install USB itself shows up as a block device. On eMMC or NVMe hardware the disk name is mmcblk0 or nvme0n1 rather than sda.
Fix (automated in arch-install.sh):
The disk detector iterates mmcblk0 nvme0n1 sda sdb vda and reads /sys/block/<dev>/removable. Only disks reporting 0 (non-removable) are considered. Override at any time:
TARGET_DISK=/dev/sda bash arch-install.shNote
Partition naming differs by disk type:
mmcblk0/nvme0n1→ partitions arep1,p2(e.g./dev/mmcblk0p1)sda/sdb→ partitions are1,2(e.g./dev/sda1)
Hidden Password Prompt Broken on Arch ISO Console
Symptom: Pressing Enter after typing a passphrase appears to do nothing, or the password is accepted as empty. The prompt hangs or the install proceeds with a blank passphrase.
Cause: read -rs (silent/hidden input) behaves inconsistently on some TTY configurations in the Arch ISO, especially on non-US keyboards or when accessed over a serial console. Some keyboards also inject a trailing \r (carriage return) that gets included in the captured password.
Fix (automated in arch-install.sh):
- Password prompts default to visible input.
- Set
HIDDEN_PASS=trueto opt-in to hidden prompts if your console supports it. - Trailing
\ris stripped from all captured passwords automatically.
HIDDEN_PASS=true bash arch-install.shSymptom: cryptsetup open fails with interactive passphrase prompts when the script is piped through bash. The passphrase read never receives input.
Cause: When a script runs via curl | bash, stdin is the pipe carrying the script itself, not the terminal. Interactive read commands receive EOF immediately.
Fix (automated in all rescue and repair scripts):
# Scripts read from /dev/tty instead of stdin for LUKS passphrases
read -r -s -p "Enter LUKS passphrase: " passphrase < /dev/tty
# Or pass passphrase via environment variable to bypass interactive prompt
LUKS_PASSPHRASE="yourpassphrase" bash fix-vfat-from-usb.sh
DEFAULT_PASS="yourpassphrase" bash arch-usb-rescue.shSymptom: mkinitcpio -P prints multiple warnings like:
==> WARNING: Possibly missing firmware for module: qat_4xxx
==> WARNING: Possibly missing firmware for module: qat_c3xxx
Cause: Intel Quick Assist Technology (QAT) modules are pulled in as MODULES= entries by mkinitcpio autodetect on some Intel systems. The QAT firmware blobs are not in linux-firmware. The warnings are cosmetic — boot succeeds — but they clutter output and cause confusion.
Fix (automated in arch-install.sh, arch-config.sh, and arch-usb-rescue.sh):
# Remove qat_* entries from MODULES= in /etc/mkinitcpio.conf
sed -i -E 's/qat_[^ )]+//g' /etc/mkinitcpio.conf
sed -i -E 's/[[:space:]]+/ /g' /etc/mkinitcpio.conf
sed -i -E 's/\( /(/g; s/ \)/)/g' /etc/mkinitcpio.conf
mkinitcpio -PSymptom: /boot fills up. bootctl list shows multiple Arch entries pointing to vmlinuz-linux or vmlinuz-linux-lts that do not match the installed linux-zen kernel. Old kernels may fail to boot after a linux-zen upgrade if headers diverge.
Fix (automated in arch-install.sh and arch-usb-rescue.sh):
# Remove vanilla and LTS kernels after confirming linux-zen is installed
pacman -Rns --noconfirm linux linux-headers 2>/dev/null || true
pacman -Rns --noconfirm linux-lts linux-lts-headers 2>/dev/null || true
# Remove stale boot entries
find /boot/loader/entries -maxdepth 1 -type f \( -name '*linux.conf' -o -name '*lts.conf' \) -deleteSymptom: Boot drops to an emergency shell with errors:
[FAILED] Failed to mount /boot.
[DEPEND] Dependency failed for Local File Systems.
journalctl -xb shows boot.mount failing with a UUID not found or FAT read error.
Common causes:
/etc/fstabhas a stale UUID for the EFI partition (relabeled or recreated)- The vfat filesystem on the EFI partition is corrupted
vfatkernel module fails to load (seemodules_disabledissue below)/bootfstab entry missing entirely
Fix — from emergency shell (automated in fix-boot-mount-emergency.sh):
# Remount root read-write
mount -o remount,rw /
# Get the real UUID of the EFI partition
blkid /dev/mmcblk0p1
# Repair FAT filesystem
fsck.vfat -a /dev/mmcblk0p1
# Rewrite fstab /boot entry with correct UUID
sed -i '\|[[:space:]]/boot[[:space:]]|d' /etc/fstab
echo "UUID=<your-efi-uuid> /boot vfat rw,nofail,x-systemd.device-timeout=10,umask=0077 0 2" >> /etc/fstab
# Test the mount
mount /boot
# Apply
systemctl daemon-reload
systemctl restart local-fs.target
rebootTip
The scripts use nofail and x-systemd.device-timeout=10 in the fstab options. nofail prevents a missing /boot from blocking the entire boot into an emergency shell. This is intentional — the system can still boot (without kernel updates or EFI access) while you diagnose.
Symptom: /boot mount fails at boot with "unknown filesystem type vfat" even though the EFI partition is valid FAT32. The vfat and fat modules are present in the initramfs but fail to load at runtime.
Cause: A modules_disabled=1 line in any file under /etc/sysctl.d/, /etc/sysctl.conf, or /usr/lib/sysctl.d/ locks the module loading subsystem after the last sysctl application — preventing any new modules from loading, including vfat.
Fix (automated in fix-vfat-from-usb.sh):
# Comment out the offending line in all sysctl config files
for f in /etc/sysctl.conf /etc/sysctl.d/*.conf /usr/lib/sysctl.d/*.conf; do
[[ -f "$f" ]] || continue
sed -i -E 's/^([^#].*modules_disabled\s*=\s*1.*)$/# disabled by fix: \1/' "$f"
done
# Ensure vfat is loaded at boot via modules-load.d
echo vfat > /etc/modules-load.d/vfat.conf
mkinitcpio -PSymptom: pacman inside a chroot (or on the live USB) hangs or fails with error: failed to init transaction (unable to lock database).
Common causes:
- A previous
pacmanrun was killed mid-operation leaving/var/lib/pacman/db.lck - Running
arch-chrootwhilepacmanis already active on the host
Fix (automated in arch-usb-rescue.sh):
# On the live USB (before chroot)
rm -f /var/lib/pacman/db.lck
# In the target system (before chroot pacman calls)
rm -f /mnt/var/lib/pacman/db.lckThe rescue script waits up to 90 seconds for any legitimate pacman process to finish before forcibly removing a lock.
Symptom: pacman -Sy inside arch-chroot /mnt fails with unable to resolve host or curl: Could not resolve host: mirror.rackspace.com.
Cause: The chroot does not inherit the host's /etc/resolv.conf. The installed system's /etc/resolv.conf may point to 127.0.0.1 (NetworkManager) which is not running in the chroot.
Fix (automated in all chroot-based scripts):
cp -f /etc/resolv.conf /mnt/etc/resolv.confCopies the live USB's working nameserver config into the chroot before any pacman calls.
Symptom: bootctl --path=/boot install inside a chroot fails with:
EFI variables are not supported on this system.
or writes an entry but it is not visible to efibootmgr.
Cause: efivarfs is a kernel filesystem that must be explicitly mounted inside the chroot. It is not auto-mounted by arch-chroot.
Fix (automated in all chroot scripts):
mount -t efivarfs efivarfs /sys/firmware/efi/efivars 2>/dev/null || true
bootctl --path=/boot installSymptom: Wi-Fi adapter not detected or unstable — repeatedly disconnects. ip link may show wlan0 but no networks are found or association fails.
Cause: The RTL8821CE (Realtek 802.11ac, PCI ID 10ec:c821) is not supported by the mainline rtw88 in-tree driver included in linux-firmware. It requires the out-of-tree rtl8821ce-dkms-git driver from the AUR.
Diagnosis:
lspci -nn | grep -i realtek
# Look for: 10ec:c821 (RTL8821CE)
dmesg | grep -i rtl
# Check for rtw88 errors or firmware load failuresFix (installer exits with instructions if RTL8821CE is detected):
# Install as your normal user (not root)
/usr/local/sbin/install-rtl8821ce-aur.shIf the driver installs but Wi-Fi is still unstable (random disconnects, high error rates):
# The rtw88 in-kernel module may conflict with the DKMS driver
# Blacklist it:
echo "blacklist rtw88_8821ce" | sudo tee /etc/modprobe.d/rtw88-blacklist.conf
sudo mkinitcpio -P
rebootSymptom: After enabling Breeze Dark in KDE System Settings, Qt apps (Konsole, Dolphin, Kate) are dark but GTK apps (Firefox, GIMP, Nautilus, most Electron apps) remain light.
Cause: KDE's theme settings apply to Qt apps through QT_QPA_PLATFORMTHEME=kde. GTK apps use a separate theming stack (GTK 3.0 and GTK 4.0 settings files) and require the kde-gtk-config bridge package to be installed.
Fix (automated in kde-dark-theme-fix.sh):
# Install the KDE-GTK bridge
sudo pacman -S --needed kde-gtk-config
# Set Qt platform theme globally
echo 'QT_QPA_PLATFORMTHEME=kde' | sudo tee -a /etc/environment
# Write GTK dark theme config for your user
mkdir -p ~/.config/gtk-3.0 ~/.config/gtk-4.0
cat > ~/.config/gtk-3.0/settings.ini <<'EOF'
[Settings]
gtk-theme-name=Breeze-Dark
gtk-application-prefer-dark-theme=1
EOF
cat > ~/.config/gtk-4.0/settings.ini <<'EOF'
[Settings]
gtk-theme-name=Breeze-Dark
gtk-application-prefer-dark-theme=1
EOFLog out and back in after applying. Then open System Settings → Colors & Themes → Application Style and confirm GTK theme is listed as Breeze Dark.
If System Settings itself renders incorrectly:
env QT_QUICK_CONTROLS_STYLE=org.kde.desktop systemsettingsSymptom: systemd-boot-update.service logs a warning:
/boot/loader/random-seed has wrong file permissions, ignoring.
Or TPM sealing related operations fail silently.
Fix (automated in all scripts that touch /boot):
chmod 600 /boot/loader/random-seed
chown root:root /boot/loader/random-seedAnswer: Yes. Both intel-ucode and amd-ucode are installed and both are referenced as initrd lines in the boot entry:
initrd /intel-ucode.img
initrd /amd-ucode.img
initrd /initramfs-linux-zen.img
The CPU detects which microcode image applies and applies only the correct one. Loading both is safe and makes the same USB installer and boot configuration work on any x86-64 machine without knowing the CPU brand in advance.
# Check which disk the system is on
lsblk -f
blkid
# Check if LUKS root is open
ls /dev/mapper/
# Check UEFI boot entries
bootctl list
efibootmgr -v
# Check current kernel
uname -r
pacman -Q linux-zen
# Check systemd-boot health
bootctl status
# Check mkinitcpio hooks
grep ^HOOKS /etc/mkinitcpio.conf
# Check /boot fstab entry
grep /boot /etc/fstab
# Check systemd boot mount status
systemctl --no-pager -l status boot.mount local-fs.target
# Full boot journal
journalctl -xb --no-pager | tail -150
# Wi-Fi diagnostics
rfkill list
ip -brief link
iw dev
dmesg | grep -i rtl
# Pacman lock
ls -la /var/lib/pacman/db.lck- LUKS2 full-disk encryption on the root partition with a passphrase prompted interactively at install time
- Password input supports both visible and hidden modes to accommodate quirky Arch ISO keyboard/console combinations
cryptsetup close cryptrootenforced after install before reboot to prevent data exposure- No hardcoded secrets in any script — all sensitive values passed via environment variables or interactive prompt
GRUB_ENABLE_CRYPTODISK=yset in GRUB config so the bootloader itself can prompt for passphrase pre-boot
KDE Plasma 6 is installed and configured non-interactively:
pacman -S --noconfirm plasma-desktop sddm plasma-nm pipewire pipewire-pulse \
wireplumber bluedevil dolphin konsole kate ark
systemctl enable sddm NetworkManagerPress Alt+F2 to open KRunner after first login. Press Super to open the application launcher. Press Ctrl+Alt+T to open Konsole.
enable-beastmode.sh deploys a pre-configured VS Code agent with access to all relevant tools for autonomous development:
tools: changes, codebase, editFiles, fetch, findTestFiles, new, problems,
runInTerminal, runNotebooks, runTasks, runTests, search, searchResults,
testFailure, usages, vscodeAPI, terminalLastCommand, terminalSelection
project-bootstrap.sh scaffolds a consistent directory tree for any project:
├── memory-bank/
│ ├── implementation-plans/
│ └── architecture-decisions/
├── docs/
├── scripts/
├── data/
├── assets/
├── .github/
│ ├── workflows/
│ ├── ISSUE_TEMPLATE/
│ └── pull_request_template.md
├── .copilot/
└── .vscode/
Note
project-bootstrap.sh is idempotent — it skips files that already exist, making it safe to run on an existing project to add missing scaffolding without overwriting anything.
gantt
title arch-custom Roadmap
dateFormat YYYY-MM-DD
section Phase 1: Base Install
Disk partition and LUKS2 :done, p1a, 2025-03-01, 2025-03-15
linux-zen and KDE Plasma 6 :done, p1b, 2025-03-15, 2025-03-25
systemd-boot config :done, p1c, 2025-03-25, 2025-04-01
section Phase 2: Developer Env
VS Code and AUR tooling :done, p2a, 2025-04-01, 2025-04-10
Beastmode and Copilot config :done, p2b, 2025-04-10, 2025-04-20
Brave and privacy hardening :done, p2c, 2025-04-10, 2025-04-25
section Phase 3: Recovery Suite
USB rescue and chroot :done, p3a, 2025-04-15, 2025-04-28
fix-grub fix-vfat fix-boot :done, p3b, 2025-04-20, 2025-05-01
section Phase 4: CI and Quality
ShellCheck CI enforcement :done, p4a, 2025-04-25, 2025-05-05
500-line cap lint check :done, p4b, 2025-05-01, 2025-05-10
section Phase 5: Expansion
Automated VM smoke tests :active, p5a, 2026-04-01, 2026-06-01
BTRFS subvolume support : p5b, 2026-05-01, 2026-07-01
GNOME profile variant : p5c, 2026-06-01, 2026-08-01
GPU driver auto-detection : p5d, 2026-06-01, 2026-09-01
| Phase | Goals | Target | Status |
|---|---|---|---|
| Phase 1 | Disk setup, LUKS2, linux-zen, KDE, systemd-boot | Q1–Q2 2025 | ✅ Complete |
| Phase 2 | Developer tools, Copilot Beastmode, privacy hardening | Q2 2025 | ✅ Complete |
| Phase 3 | USB rescue suite, GRUB + systemd-boot repair, vfat fix | Q2 2025 | ✅ Complete |
| Phase 4 | ShellCheck CI, 500-line lint cap, project scaffold | Q2 2025 | ✅ Complete |
| Phase 5 | VM smoke tests, BTRFS, GNOME variant, GPU auto-detection | Q2–Q3 2026 | 🟡 In Progress |
| Item | Value |
|---|---|
| Version | 1.x (main branch) |
| Stability | Stable |
| Primary Language | Bash (100%) |
| Total Scripts | 13 |
| ShellCheck Status | ✅ Zero warnings |
| CI | GitHub Actions (shellcheck + lint) |
| Line-cap Enforcement | 500 lines per script |
| UEFI Support | ✅ Full |
| BIOS/MBR Support | ❌ Not supported |
| Known Limitations | Default disk is /dev/mmcblk0; BIOS/MBR not supported; GPU drivers require manual selection |
Note
No runtime code executes on import — all scripts are plain Bash and require explicit invocation. There are no daemons, services, or background processes installed by the tooling itself.
- UEFI system (not BIOS/MBR)
- Booted from Arch Linux ISO
- Active internet connection during install
- At least 16 GB disk space (32 GB+ recommended for development use)
📦 Packages Installed by arch-install.sh
| Package Group | Packages |
|---|---|
| Base system | base, base-devel, linux-zen, linux-zen-headers, linux-firmware |
| Desktop | plasma-desktop, sddm, plasma-nm, bluedevil, dolphin, konsole, kate, ark |
| Audio | pipewire, pipewire-pulse, pipewire-alsa, wireplumber |
| Network | networkmanager, iwd, wireless-regdb |
| Security | cryptsetup, tpm2-tools |
| Boot | efibootmgr, systemd-boot (built-in to systemd) |
| Firmware | fwupd, linux-firmware |
| Dev tools | git, curl, wget, vim, htop, fastfetch |
Contributions are welcome. All scripts must remain ShellCheck-clean and under the 500-line cap.
📋 Contribution Workflow
# 1. Fork the repo and create a feature branch
git checkout -b feat/your-script-name
# 2. Write your script following existing conventions:
# - set -euo pipefail at the top
# - Colored output helpers: info(), ok(), warn(), die()
# - Idempotent: check before acting, skip if already done
# - No hardcoded secrets or credentials
# - Environment variable overrides for any path/device/password
# 3. Lint with ShellCheck
shellcheck your-script.sh
# 4. Verify line count
wc -l your-script.sh # must be <= 500
# 5. Commit with a conventional message
git commit -m "feat: add your-script.sh — brief description"
# 6. Open a pull requestPR Requirements:
-
shellcheckpasses with zero warnings - Script is idempotent (safe to run twice)
- No hardcoded passwords, tokens, or secrets
- All device paths / passphrases overridable via environment variables
- Line count ≤ 500
- README updated if a new script is added to the Curlable Scripts table
Caution
Do not bypass the ShellCheck CI check with blanket # shellcheck disable suppressions. If ShellCheck flags something, fix the root cause.
MIT — free to use, modify, and distribute. Attribution appreciated but not required.
See LICENSE for the full text.