Complete Guide to Creating a Custom Arch Linux ISO

Scheduled ArchISO Build Tests

DistroTube: https://www.youtube.com/watch?v=tSGGBbJBgvk&t=1228s

Dotfiles install file structure:

AlvinArch/
├── airootfs/
│   ├── etc/skel/
│   │   ├── .config/bat/           # bat + catppuccin theme
│   │   ├── .config/fish/          # 20 files, quickshell guard
│   │   ├── .config/nvim/          # LazyVim (14 files)
│   │   ├── .config/hypr/custom/   # 8 files (kdeconnectd + cursor only)
│   │   ├── .config/starship.toml
│   │   ├── .zshrc                 # adapted (no hardcoded paths)
│   │   └── .fzf.zsh
│   └── root/customize_airootfs.sh # pre-installs oh-my-zsh, lazy.nvim, plugins
├── localrepo/
│   ├── build-end4-deps.sh         # non-interactive AUR build (all deps)
│   ├── end4-pkgbuilds/            # microtex-git, quickshell-git, bibata
│   ├── illogical-impulse-quickshell-git-*.pkg.tar.zst
│   ├── illogical-impulse-bibata-modern-classic-bin-*.pkg.tar.zst
│   └── update.sh                  # existing, regenerates repo db
└── packages.x86_64               # 420 packages (65+ end-4 deps added)

Table of Contents

  1. Overview
  2. Prerequisites
  3. Directory Structure
  4. Core Configuration Files
  5. User Configuration (skel/)
  6. System Services & Networking
  7. Local Repository (localrepo/)
  8. Boot Configuration
  9. Automated Installer (alvin-install)
  10. Build & Test
  11. Planned Additions
  12. Troubleshooting

Overview

AlvinArch is a custom Arch Linux live ISO built on the archiso framework. It ships a Hyprland desktop with end-4 dots-hyprland, a curated set of ~400 packages, and a local repository of pre-built AUR packages for packages not available in official repos. The ISO targets both real hardware and VMs (VirtualBox/Hyper-V/VMware guest services are enabled out of the box).

Key Features

  • Hyprland + end-4 — Wayland compositor with illogical-impulse dotfiles (quickshell, bibata cursors, microtex)
  • Automated installer (alvin-install) — gum TUI: LUKS with systemd initramfs or experimental touchscreen unl0kr, optional TPM2 auto-unlock, GitHub SSH key import, login manager choice (none/greetd/sddm/gdm), optional sshd (on by default), autologin; the build stamp identifies the live ISO via its /etc/motd
  • LazyVim — Pre-configured Neovim with lazy.nvim and curated plugins
  • Fish + Zsh + Starship — Dual shell setup with oh-my-zsh and a cross-shell prompt
  • Local AUR repo — Pre-built packages (end-4 deps, wallust, grub-customizer, etc.) that aren't in official repos
  • Curated packages — ~400 packages across ccache, networking, dev tools, and desktop utilities
  • Guest VM support — VirtualBox, Hyper-V, and VMware tools auto-enabled

Prerequisites

Required Tools

# Install archiso and dependencies
sudo pacman -S archiso git base-devel

# Optional but recommended
sudo pacman -S arch-install-scripts squashfs-tools

Knowledge Requirements

  • Basic Linux system administration
  • Understanding of Arch Linux package management
  • Shell scripting basics
  • Git version control (for managing your project)

Directory Structure

Complete Project Layout

AlvinArch/
├── profiledef.sh                    # ISO metadata, compression, boot modes, file permissions
├── packages.x86_64                  # ~400 live-environment packages (Hyprland, end-4, dev tools)
├── bootstrap_packages               # Minimal build-time packages (base, arch-install-scripts)
├── pacman.conf                      # Repos: cachyos → core → extra → multilib → chaotic-aur → local-aur
├── airootfs/                        # Root filesystem overlay (copied into live ISO)
│   ├── etc/
│   │   ├── os-release               # NAME="Alvin Arch Linux", ID=arch
│   │   ├── hostname                 # Default hostname: archiso
│   │   ├── motd                     # Welcome text with install instructions and color bar
│   │   ├── locale.conf              # LANG=en_AU.UTF-8
│   │   ├── locale.gen               # Locale generation file
│   │   ├── localtime → ../usr/share/zoneinfo/...  # Timezone symlink
│   │   ├── resolv.conf              # DNS resolver config
│   │   ├── passwd                   # System user accounts
│   │   ├── shadow                   # Password hashes (mode 400)
│   │   ├── clamav/                  # ClamAV antivirus config (certs, clamd, freshclam, milter)
│   │   ├── skel/                    # Home directory template for new users
│   │   │   ├── .config/
│   │   │   │   ├── bat/             # bat + Catppuccin Macchiato theme
│   │   │   │   ├── fish/            # Fish shell config (~20 files, nvm, fisher, hyprland helper)
│   │   │   │   ├── nvim/            # LazyVim setup (init.lua, plugins/, config/)
│   │   │   │   ├── hypr/custom/     # Hyprland dotfiles (keybinds, rules, execs, env, general, scripts)
│   │   │   │   └── starship.toml    # Starship cross-shell prompt
│   │   │   ├── .zshrc               # Zsh config (oh-my-zsh compatible)
│   │   │   └── .fzf.zsh             # Fzf shell integration
│   │   ├── systemd/
│   │   │   ├── system/              # Service files and enablement symlinks
│   │   │   │   ├── choose-mirror.service          # Auto mirror selection
│   │   │   │   ├── pacman-init.service             # Keyring initialization
│   │   │   │   ├── livecd-alsa-unmuter.service     # Unmute ALSA on boot
│   │   │   │   ├── livecd-talk.service              # LiveCD talk helper
│   │   │   │   ├── multi-user.target.wants/        # Enabled services (iwd, NetworkManager, sshd, vboxservice,
│   │   │   │   │                                  #   hv_kvp/vss/fcopy, vmtoolsd, ModemManager, etc.)
│   │   │   │   ├── sysinit.target.wants/           # systemd-timesyncd, systemd-time-wait-sync
│   │   │   │   ├── sockets.target.wants/           # systemd-networkd, pcscd
│   │   │   │   ├── network-online.target.wants/    # systemd-networkd-wait-online
│   │   │   │   └── sound.target.wants/             # livecd-alsa-unmuter
│   │   │   ├── networkd.conf.d/     # IPv6 privacy extensions
│   │   │   ├── resolved.conf.d/     # systemd-resolved config
│   │   │   ├── journald.conf.d/     # Volatile journal storage
│   │   │   └── logind.conf.d/       # Do-not-suspend policy
│   │   ├── pacman.d/
│   │   │   ├── hooks/
│   │   │   │   ├── uncomment-mirrors.hook          # Enable mirrors after install
│   │   │   │   └── zzzz99-remove-custom-hooks-from-airootfs.hook
│   │   │   ├── chaotic-mirrorlist   # Chaotic AUR mirrors
│   │   │   └── cachyos-mirrorlist   # CachyOS mirrors
│   │   ├── ssh/sshd_config.d/       # SSH live-environment config (10-archiso.conf)
│   │   ├── modprobe.d/              # broadcom-wl.conf (Broadcom WiFi driver)
│   │   └── mkinitcpio.conf.d/       # archiso initramfs config
│   ├── usr/
│   │   ├── lib/os-release           # usr-merge os-release
│   │   ├── local/bin/               # alvin-install, choose-mirror, Installation_guide, livecd-sound
│   │   ├── local/share/livecd-sound/ # ALSA config (asound.conf.in)
│   │   └── share/pacman/keyrings/   # CachyOS GPG keyring + trust data
│   └── root/
│       ├── .automated_script.sh     # Automated install entry point
│       ├── .zlogin                  # Zsh login hook
│       ├── .gnupg/scdaemon.conf     # GPG smartcard config
│       ├── .live-pacman.conf        # Pacman config for live session
│       └── customize_airootfs.sh    # Build-time chroot script (oh-my-zsh, lazy.nvim, plugins)
├── localrepo/                       # Pre-built AUR packages (indexed as local-aur repo)
│   ├── build-end4-deps.sh           # Non-interactive AUR builder for end-4 dependencies
│   ├── update.sh                    # Regenerates repo DB + syncs names to packages.x86_64
│   ├── end4-pkgbuilds/             # PKGBUILDs: quickshell-git, microtex-git, bibata-modern-classic-bin
│   ├── *.pkg.tar.zst               # Pre-built packages (illogical-impulse-*, wallust, grub-customizer,
│   │                               #   snapd, upscayl, meshy-git, pacserve, etc.)
│   └── local-aur.db{,.tar.gz}      # Repo database files
├── efiboot/                         # UEFI boot (systemd-boot)
│   └── loader/
│       ├── loader.conf
│       └── entries/
│           ├── 01-archiso-linux.conf
│           ├── 02-archiso-speech-linux.conf
│           └── 03-archiso-memtest86+x64.conf
├── grub/                            # GRUB boot (BIOS + UEFI)
│   ├── grub.cfg
│   └── loopback.cfg
└── syslinux/                        # BIOS boot (syslinux)
    ├── syslinux.cfg
    ├── archiso_head.cfg
    ├── archiso_pxe-linux.cfg
    ├── archiso_sys.cfg
    ├── archiso_sys-linux.cfg
    ├── archiso_tail.cfg
    └── splash.png

Core Configuration Files

profiledef.sh

Defines the entire ISO build. Key settings: iso_label="AlvinArch_...", bootmodes (BIOS syslinux + UEFI systemd-boot), airootfs_image_type="squashfs" with xz compression. Edit first to change branding or compression.

packages.x86_64

The main package list (~400 packages). Packages from allowed repos (cachyos, extra, multilib, chaotic-aur) are installed directly. Packages in localrepo/*.pkg.tar.zst are pulled from the [local-aur] repo. Keep sorted alphabetically.

bootstrap_packages

Minimal packages for the mkarchiso bootstrap phase: base and arch-install-scripts.

pacman.conf

Defines the repo order: cachyos → core → extra → multilib → chaotic-aur → local-aur. The [local-aur] repo points to the local localrepo/ directory for pre-built AUR packages.


User Configuration (skel/)

Dotfiles in airootfs/etc/skel/ are copied into every new user's home directory on the live ISO.

.config/bat/

bat with a Catppuccin Macchiato color theme. Provides bat as a cat replacement.

.config/fish/

Fish shell configuration (~20 files). Includes nvm integration, fisher plugin manager, Hyprland auto-detection (auto-Hypr.fish), frozen theme/keybindings, and custom functions (edit, update, lzd).

.config/nvim/

Full LazyVim setup powered by lazy.nvim. Includes init.lua, plugin specs for Obsidian, Arduino, and Harper (grammar), plus standard LazyVim config/options/keymaps/autocmds.

.config/hypr/custom/

Hyprland dotfiles from end-4 dots-hyprland:

File Purpose
keybinds.lua Window manager keybindings
general.lua Appearance, gaps, border settings
rules.lua Window rules (floating, size, class matching)
execs.lua Autostart commands
env.lua Environment variables
variables.lua Shared variable definitions
scripts/ Helper scripts (video wallpaper restore, paste-as-type)

.config/starship.toml

Cross-shell prompt config for Starship. Works with both Fish and Zsh.

.zshrc / .fzf.zsh

Zsh configuration adapted for oh-my-zsh (installed by customize_airootfs.sh) and fzf shell integration.


System Services & Networking

Service Purpose
iwd WiFi management
NetworkManager Network connections
sshd SSH access to live environment
vboxservice VirtualBox guest additions
hv_kvp_daemon, hv_vss_daemon, hv_fcopy_daemon Hyper-V integration
vmtoolsd, vmware-vmblock-fuse VMware guest tools
ModemManager Mobile broadband
pacman-init Initializes GPG keyring before package operations
choose-mirror Auto mirror selection (triggered by mirror kernel param)
livecd-alsa-unmuter Unmutes audio on live boot

Network Config

  • systemd-networkd with IPv6 privacy extensions enabled
  • systemd-resolved for DNS
  • DHCP auto-configured for wired (ethernet) and wireless (iwd)

Other System Config

  • ClamAV — Antivirus daemon config included in airootfs/etc/clamav/
  • mkinitcpio — Archiso-specific initramfs config
  • logind — No-suspend policy for live session
  • journald — Volatile (in-memory) log storage

Local Repository (localrepo/)

Pre-built AUR packages that aren't available in any allowed repo. The local-aur repo is configured in pacman.conf and picked up automatically by mkarchiso.

Scripts

  • build-end4-deps.sh — Non-interactive AUR build for all end-4 (illogical-impulse) dependencies. Run --quick to skip large packages.
  • update.sh — Regenerates the repo database and syncs new package names to packages.x86_64.

end4-pkgbuilds/

Custom PKGBUILDs for end-4 metapackages:

  • quickshell-git — Quickshell (Qt6/QML shell for Wayland)
  • microtex-git — LaTeX previewer
  • bibata-modern-classic-bin — Bibata cursor theme

Pre-built Packages

Packages like illogical-impulse-quickshell-git, illogical-impulse-bibata-modern-classic-bin, wallust, grub-customizer, snapd, upscayl-bin, meshy-git, xwaylandvideobridge, pacserve, etc.


Boot Configuration

UEFI — systemd-boot (efiboot/)

Three boot entries: standard Arch Linux, speech/accessibility, and MemTest86+.

BIOS — GRUB (grub/)

grub.cfg for BIOS/CSM boot, loopback.cfg for booting the ISO from a mounted image.

BIOS — syslinux (syslinux/)

Legacy BIOS boot with custom splash screen. Includes PXE network boot config.


Automated Installer (alvin-install)

The ISO ships an interactive installer at /usr/local/bin/alvin-install (source: airootfs/usr/local/bin/alvin-install, ~1,880 lines of bash; the live /etc/motd advertises it). It partitions, encrypts, installs, and fully configures a system — including the end-4 dotfiles — without network access to this repo.

Running it

alvin-install   # as root on the live medium (root autologins on tty1)

Requirements: UEFI boot (it exits if /sys/firmware/efi is missing — BIOS/CSM is not supported by the installer) and gum. A network connection is required and verified by pinging archlinux.org before any disk is touched; the network screen can open nmtui for WiFi. Every step is logged to /var/log/alvin-install.log, which is copied into the installed system.

What it asks (gum TUI screens)

Keymap → network → target disk (the live USB is hidden from the list) → LUKS encryption (optional; the LUKS passphrase is the same as your login password) → experimental unl0kr touchscreen unlock → filesystem (ext4/btrfs) → username/password/hostname → git identity + GitHub username (both optional) → timezone/locale → login manager → SSH server (enabled by default) → summary.

What it does

  1. GPT partitioning (1 GiB ESP) on the chosen disk, optional LUKS2 on the rest, format + mount at /mnt
  2. pacstrap with the package list derived from the medium's pkglist.x86_64.txt (pacman -Q of the live ISO) — archiso, syslinux, memtest86+ etc. are dropped, hardcoded essentials (kernel, GRUB, NetworkManager, cryptsetup, microcode) always appended
  3. User account, sudo, git config, GitHub SSH keys (ssh-import-id, falling back to github.com/<user>.keys)
  4. end-4 dotfiles from the staged /opt/dots-hyprland via ./setup install -f -s --skip-alldeps
  5. Initramfs in one of two modes: Mode A — systemd initramfs + sd-encrypt (default); Mode B — busybox hooks + unl0kr touchscreen unlock (experimental, mutually exclusive with TPM2; falls back to Mode A on failure)
  6. Optional TPM2 auto-unlock (systemd-cryptenroll, PCR 0+7; passphrase slot kept as fallback; warns if Secure Boot is off)
  7. GRUB (UEFI, boot entry ALVIN), login manager — none (plain tty, Hyprland autostarts), greetd+tuigreet, sddm, or gdm — falls back to none only if the package can't be resolved/installed; a failed systemctl enable keeps getty@tty1 as the login path (the summary still reports the chosen manager). Optional autologin (forced off when TPM2 is enrolled), and sshd enabled at boot when selected (default: on) — Arch's stock config allows password login with the same password as LUKS, so only accept the default on networks you trust (or harden sshd_config afterwards). The final banner reports whether the enable actually succeeded, not just what was selected

On failure an EXIT trap unmounts /mnt and closes the cryptroot mapping, so re-running is safe. If the end-4 step reports "incomplete", see the repository's AGENTS.md Gotchas for the chroot/systemd limitations that cause it.


Build & Test

Building the ISO

# Build the ISO (repo root IS the profile directory)
sudo mkarchiso -v -w work -o out/ .

# Build AUR deps for end-4 (run locally before ISO build if localrepo/ is stale)
./localrepo/build-end4-deps.sh          # build all
./localrepo/build-end4-deps.sh --quick  # skip large AUR pkgs

# Refresh localrepo DB after adding/moving .pkg.tar.zst files
./localrepo/update.sh                    # re-indexes existing packages
./localrepo/update.sh pkg1.pkg.tar.zst   # add files, then re-index

CI Checks (must pass before merge)

  • ShellCheck: all .sh files checked with shellcheck -x -s bash
  • yamllint: all .yml/.yaml files (relaxed config)
  • packages.x86_64 syntax: one package per line, no trailing whitespace, [a-zA-Z0-9._@+\-]+ only
  • Package repo check: every package in packages.x86_64 must be resolvable via pacman -Si in allowed repos (cachyos, extra, multilib, chaotic-aur), OR have a pre-built .pkg.tar.zst in localrepo/, OR appear in localrepo/build-end4-deps.sh
  • RAM: 4GB minimum, 8GB recommended
  • Video Memory: 128MB
  • 3D Acceleration: Enabled
  • Storage: 40GB minimum

Guest additions (vboxservice), Hyper-V (hv_*), and VMware (vmtoolsd) tools are all auto-enabled in the live environment.


Planned Additions

The following dotfiles are not yet included but are planned for future versions:

Component Status Notes
Alacritty terminal config 🚧 Planned Catppuccin Macchiato theme to match bat
GTK 3.0/4.0 theme config 🚧 Planned Consistent theming across GTK apps
Fontconfig custom settings 🚧 Planned Font rendering and fallback rules
Dunst notification daemon 🚧 Planned Notification appearance and behavior

Troubleshooting

ISO won't boot

  • Verify the ISO was built with sudo mkarchiso (not as a regular user)
  • Check that UEFI or BIOS/CSM is correctly set in VM firmware settings
  • For Ventoy: use the .iso directly, don't extract

No network in live environment

  • iwd handles WiFi — run iwctl to connect manually if NetworkManager doesn't detect networks
  • Ethernet via systemd-networkd with DHCP should work automatically

Package installation fails during build

  • Run ./localrepo/update.sh to refresh the localrepo database
  • Ensure localrepo/*.pkg.tar.zst files are valid and not corrupted
  • Check that the package name in packages.x86_64 matches exactly

LazyVim / oh-my-zsh not installed

  • customize_airootfs.sh clones repos at build time — requires network access during mkarchiso
  • Check build logs for git clone failures

Acknowledgements

S
Description
No description provided
Readme
729 MiB
2026-10-13 22:51:52 +11:00
Languages
Shell 91.8%
Lua 8.2%