Overview/X Linux (distro)
Text installer
Other languages: Español
X is installed from the live ISO using a text installer. There is no
graphical installer (Calamares was removed). Everything below lives under
airootfs/root/x-installer/ in this repository and is shipped in the live
environment at /root/x-installer/.
Entry points and when the installer runs
-
The live environment auto-logs in as
rooton TTY1 (agetty autologin) with zsh. -
/root/.zloginfirst runs/root/.automated_script.sh(the official archisoscript=mechanism) if present, and then launches the installer on TTY1, unless the kernel cmdline containsscript=orxauto=1. -
The entry point is
installer.sh(/root/x-installer/installer.sh). -
A shortcut is available in the live shell:
xinstallxinstall(/usr/local/bin/xinstall) simply execsinstaller.sh. If you are dropped to a plain shell instead of the installer, runxinstallorbash /root/x-installer/installer.sh.
Installer layout
/root/x-installer/
|-- installer.sh # entry point (configurator + install)
|-- configurator.sh # interactive configuration, writes a JSON plan
|-- install.sh # performs the actual installation
|-- autoinstall.sh # unattended entry (xauto=1 + cidata disk)
|-- ui.sh # UI helpers: gum with plain-prompt fallback
|-- packages.x86_64 # default manifest for the "full" package profile
`-- packages/ # offline x-scripts payload (*.pkg.tar.zst)
A systemd unit, x-autoinstall.service, is enabled in the live image
(airootfs/etc/systemd/system/) and drives the unattended path. See
Autoinstall below.
Interactive flow
installer.sh runs the configurator and, if it succeeds, the installer.
configurator.shcollects the options below and writes/tmp/x-install.json(mode 600).install.shreads that JSON, validates it, and performs the installation.
With X_DRY=1, the plan is shown without touching the disk (see
Environment variables).
Configurator options (configurator.sh)
The UI uses gum (choose, input, confirm) when available and falls back
to plain text prompts otherwise (ui.sh).
| Step | Options | Stored value |
|---|---|---|
| Disk | any block device of type disk (from lsblk) | disk (e.g. /dev/sda) |
| System language | English, Español, Deutsch, Français | language + locale |
| Keyboard layout | us, es, de, fr, uk, latam, br-abnt2 | keyboard |
| Timezone | UTC, Europe/Madrid, Europe/London, Europe/Berlin, America/Mexico_City, America/Argentina/Buenos_Aires, America/Los_Angeles, Asia/Tokyo | timezone |
| Hostname | free text (default x) | hostname |
| Username | free text | username |
| User password | free text (repeated) | password |
| Package profile | Full (all packages) / Core (minimal system) | profile (full/core) |
| Bootloader | GRUB (BIOS + UEFI) / systemd-boot (UEFI only) | bootloader (grub/systemd-boot) |
| Kernel | linux (stable) / linux-lts (longterm) / linux-zen (desktop) | kernel (linux/linux-lts/linux-zen; linux-hardened, linux-rt, linux-rt-lts accepted via JSON) |
| Root encryption (LUKS) | yes/no | encryption (yes/no) |
| LUKS passphrase | reuse user password or dedicated | luks_password |
| Desktop environment | Hyprland (online) / Niri (online) / KDE Plasma (online) / None | desktop (hyprland/niri/kde/none); hyprland (yes/online/no) is the legacy key |
| Install Xscriptor AI agents | yes/no (requires network) | agents (yes/no) |
| Install virtualization | yes/no (QEMU/KVM + libvirt + virt-manager GUI) | virtualization (yes/no) |
Language-to-locale mapping used by the configurator:
| Language | language | locale |
|---|---|---|
| English | en | en_US.UTF-8 |
| Español | es | es_ES.UTF-8 |
| Deutsch | de | de_DE.UTF-8 |
| Français | fr | fr_FR.UTF-8 |
Validation rules: hostname must match ^[a-zA-Z0-9][a-zA-Z0-9-]{0,62}$;
username ^[a-z_][a-z0-9_-]{0,31}$; passwords/passphrases cannot contain
" or \.
Before writing the config, the configurator asks for final confirmation that everything on the selected disk will be erased. The resulting JSON looks like:
{"disk":"/dev/sda","hostname":"x","username":"x","password":"secret","language":"en","locale":"en_US.UTF-8","keyboard":"us","timezone":"UTC","profile":"full","bootloader":"grub","kernel":"linux","encryption":"no","luks_password":"","hyprland":"no","desktop":"none","agents":"no","virtualization":"no"}
The JSON is written to the path in X_CONFIG_OUT (default
/tmp/x-install.json). Only disk, hostname, username, and password
are required; the remaining keys have sensible defaults when absent.
Installation steps (install.sh)
- Parse and validate the JSON (
disk,hostname,username), require root and a real block device. - Partition with GPT (
sgdisk --zap-allfirst):grub: 1 MiBbios_grubpartition, 1 GiB EFI partition, rest = root.systemd-boot: 1 GiB EFI partition, rest = root. (1 GiB leaves room for several generations of boot entries.)
- LUKS (if
encryption=yes):cryptsetup luksFormat --type luks2on the root partition (passphrase fromluks_password, falling back to the user password) and open it as/dev/mapper/xroot. - Format and mount: the EFI partition as FAT32 (
mkfs.vfat -F32) mounted at/mnt/boot; the root (or LUKS mapping) as btrfs with the@,@home,@snapshotsand@xstatesubvolumes mounted at/,/home,/.snapshotsand/var/lib/x./tmpis appended to the fstab as tmpfs. - Package set:
- Base set:
base base-devel linux linux-firmware sudo networkmanager openssh git jq x-release x-wallpapers btrfs-progs xfetch-bin xtop-git kitty pipewire pipewire-pulse pipewire-alsa wireplumber alsa-utils sddm zsh rsync curl python, plusgrub efibootmgrfor GRUB andcryptsetupfor LUKS.btrfs-progsis required by the generations engine; the X tools (xfetch,xtop),x-wallpapersandzshare installed in every profile. fullprofile: adds every package in the manifest pointed to byX_PKGLIST(default/root/x-installer/packages.x86_64).coreprofile: adds onlyvim.- The equisdots Hyprland setup may compile AUR packages (
quickshell-git,swayosd-git, ...) depending on upstream; give the installer ≥6 GB RAM. niri and KDE Plasma use binary packages.
- Base set:
- Prepare the keyrings and wait for network (DNS check against
geo.mirror.pkgbuild.com, up to ~120 s), thenpacstrap /mnt <pkgs>from the official mirrors plus the signed[x]repository (Required).- Live:
/etc/pacman.d/x-repo.pubis added and locally signed (pacman-key --init/--populate archlinux/--add/--lsign-key). This is re-applied at boot byx-keyring.servicebecausepacman-init.servicerecreates/etc/pacman.d/gnupgon a tmpfs;install.shrepeats it idempotently beforepacstrap(which verifies against the live keyring). - Target:
pacman-key --gpgdir /mnt/etc/pacman.d/gnupg --init,--populate archlinux, add + locally sign the same key.
- Live:
- Install
x-scriptsoffline: the payloadpackages/x-scripts-*.pkg.tar.zstpresent in the live environment is copied into the target and installed withpacman -Uinside the chroot. When thekdedesktop is selected, the bundledx-kde-settings-*.pkg.tar.zstbranding package is installed the same way. - Base configuration:
genfstab, timezone symlink,locale.gen+/etc/locale.conf,KEYMAPin/etc/vconsole.conf, hostname, copy of the working mirrorlist, and the[x]repository appended to the target'spacman.confif missing. - User: create the user (member of
wheel, login shellzsh), set the password withchpasswd, and enable%wheelinsudoers. - Provisioning with the
xCLI from thex-scriptspackage:- system phases as root:
X_HW_AUTO=0 x setup; - user phases as the new user:
X_HYPRLAND=0 X_HW_AUTO=0 x setup --user. - PipeWire/Pulse/WirePlumber are enabled for all users; the Hyprland setup
is deferred to a later step/point (not run here when
hyprland=no). - optional AI agents (
agents=yes):opencode-binis installed from[x], thenx agent install --bundle xruns as the target user withHOME=/home/<user>(Xscriptor bundle: agents, skills and commands for OpenCode).
- system phases as root:
- Hyprland setup (if
desktop=hyprland; legacyhyprland=yesoronline): a temporary passwordless-sudo drop-in is created and/usr/share/x/tools/hyprland-install.shruns as the target user. The drop-in is removed afterwards.hyprland=yesdeploys the offline equisdots snapshot shipped in the package (/usr/share/x/config/equisdots).hyprland=onlineignores the snapshot: it clonesequisdots/dots(optionally pinned withhyprland_ref), runs its officialdots install(repos, plugins and the settings merge) and fetches the app configs fromxscriptor-colors; if it fails, the installer falls back to the bundled snapshot. NVIDIA is owned by the system hardware phase; the tool falls back to the upstream equisdots NVIDIA setup only if a GPU is present without a driver.
- niri setup (if
desktop=niri): a temporary passwordless-sudo drop-in is created and/usr/share/x/tools/niri-install.shruns as the target user. The tool clonesequisdots/niri-metaonline and runsdotsniri setup -y; the drop-in is removed afterwards. niri needs a GPU with working EGL/GBM acceleration (see the desktop section below). - KDE Plasma setup (if
desktop=kde): Plasma is already installed from the official repositories; the installer disablesplasmalogin.service, enablessddm.serviceand installs the bundledx-kde-settingspackage so the X Dark look-and-feel, wallpaper and SDDM theme apply on first login. - Initramfs (LUKS only): replace
HOOKSinmkinitcpio.confto include theencrypthook and rebuild withmkinitcpio -P. - Branding:
x-release-apply(fromx-release) is run before the bootloader step so a LUKS kernel cmdline written afterwards is not overwritten. - Bootloader:
grub:grub-installforx86_64-efi(removable) andi386-pc(booting from the whole disk), thengrub-mkconfig.GRUB_CMDLINE_LINUXalways carries the root command line withrootflags=subvol=@(pluscryptdevice=UUID=<luks-uuid>:xroot root=/dev/mapper/xrootunder LUKS).systemd-boot:bootctl --esp-path=/boot install, aBOOTX64.EFIremovable fallback if needed, and a loader entryX Linux(UEFI only) with the matchingroot=orcryptdevice=cmdline.
- First generation: inside the chroot,
X_GEN_CMDLINE="$CMDROOT" X_GEN_LIVE_SUBVOL=/@ x gen new --reason install --label firstcreates/.snapshots/0001, the manifest under/var/lib/x/generations/0001and the boot entries (systemd-bootloader/entries/x-gen-0001.conf, GRUBcustom.cfg). This is the base for rollbacks and granular restores; seegenerations.md. - Cleanup: on exit, mounts are unmounted, the LUKS mapping is closed if open, and the install JSON is removed.
A message tells you the installation is complete; reboot and remove the installation medium.
Desktop environments
The configurator offers four choices, all installed online during the installation:
desktop | What is installed |
|---|---|
hyprland | The official equisdots Hyprland stack via equisdots/dots (dots install, latest main), plus the X package/login integration. |
niri | The official equisdots niri stack via equisdots/niri-meta (dotsniri setup -y): niri, Quickshell shell, niri overlays, plugins, app configs and the niri session entry. |
kde | plasma-meta + the kde-applications group from the official repositories and the x-kde-settings branding package (bundled in the live ISO; source in [x]): X Dark colors, look-and-feel, wallpaper and SDDM theme. |
none | No desktop; console only. |
Common behavior:
- SDDM is the display manager. Hyprland/niri use the equisdots static SDDM
theme; KDE uses the same X theme from
x-kde-settings. - niri requires a GPU with working EGL/GBM hardware acceleration; niri skips
software EGL renderers. In a VM, enable 3D acceleration (for example
virtio-vga-gl/VirGL) or use Hyprland. - The desktop is installed during the text install (network required), not at
first boot. User provisioning (
x setup --user) runs first; the desktop tool then deploys the user payload. - The X branding applies at every visible surface (
os-release, GRUB, SDDM, wallpaper, Plasma colors).ID_LIKE=archremains a technical identifier. hyprlandremains in the JSON for backward compatibility with existing autoinstall seeds; whendesktopis present it wins.- The legacy
hyprland=yes(bundled offline snapshot) is still honored whendesktopis absent; new seeds should usedesktop.
Virtualization (optional)
The configurator can install a ready-to-use virtualization stack on the target
("virtualization":"yes"). It is installed with pacman during the
installation, so it needs network like the rest of the target packages.
- Packages:
qemu-desktop,qemu-img,qemu-system-aarch64,libvirt,virt-manager,virt-install,libosinfo,virt-viewer,dnsmasq,iptables,edk2-ovmf,edk2-aarch64,swtpm,dmidecode. - Access: the created user is added to the
libvirtandkvmgroups (groupadd -f+usermod -aG), sovirt-managercan use the system connection and/dev/kvm. - Services:
libvirtd.socketandvirtlogd.socketare enabled (socket activation). - Network: the libvirt
defaultNAT network (virbr0) is marked for autostart, so it starts with libvirtd. It usesdnsmasqand the nftiptablesinterface. - Guests:
virt-manager/virt-install/virsh; UEFI viaedk2-ovmf, TPM viaswtpm; ARM guests viaqemu-system-aarch64+edk2-aarch64. - GUI caveat:
virt-managerneeds a graphical session. With thecoreprofile and no desktop it will not launch;virsh/virt-installremain available from the CLI. - Nested VM caveat: if
/dev/kvmis absent (X itself inside a VM without nested virtualization), the stack is installed but guests run without KVM acceleration; the installer prints a warning. - The stack is optional: without
"virtualization":"yes"the target does not change.
Autoinstall
The installer supports unattended installation from the live ISO:
- The kernel cmdline must contain
xauto=1. - A storage device labeled
cidatamust exist and contain anx-install.jsonfile (for example an extra virtual disk in QEMU).
x-autoinstall.service (enabled in the live image) runs
autoinstall.sh, which:
- Skips immediately if
xauto=1is not present on the cmdline. - Looks up the device by label (
blkid -L cidata); skips if absent. - Mounts it read-only at
/run/cidata. - Runs
install.shwithX_INSTALL_JSONpointing at/run/cidata/x-install.json. - Writes the log to
/tmp/x-install.logand echoes it to the serial console / console, then unmounts.
The JSON for an unattended run only requires the base keys, for example:
{"disk":"/dev/vda","hostname":"x-vm","username":"x","password":"secret","profile":"core","bootloader":"grub","kernel":"linux","encryption":"no","hyprland":"no","kernel_params":"console=ttyS0"}
kernel_params is optional: extra kernel command-line parameters appended to
the installed system's cmdline (restricted to a safe character set and
validated before partitioning). It is useful for headless validation, e.g.
"kernel_params":"console=ttyS0" to keep the serial console after install.
See Testing in a VM for an example cidata disk.
The other automation mechanism is the official archiso script= cmdline
(/root/.automated_script.sh downloads or copies a script and executes it).
When script= or xauto=1 is present, the interactive installer is not
launched.
Kernel cmdline reference
| Parameter | Effect |
|---|---|
script=<url or path> | Runs the official archiso automation script; interactive installer is skipped. |
xauto=1 | Enables the unattended autoinstall (needs a cidata disk with x-install.json). |
accessibility= | Sets single-line zle (screen-reader friendly TTY). |
Environment variables
| Variable | Default | Scope | Effect |
|---|---|---|---|
X_SKIP_INSTALLER | unset | live | 1 makes installer.sh print "skipped" and exit. |
X_DRY | 0 | live | 1 shows the plan only; nothing is written or erased. |
X_CONFIG_OUT | /tmp/x-install.json | configurator | Where the config JSON is written. |
X_INSTALL_JSON | /tmp/x-install.json | installer | JSON config consumed by install.sh. |
X_PKGLIST | /root/x-installer/packages.x86_64 | installer | Package manifest used by the full profile. |
X_HYPRLAND | payload default 1 | payload (x setup) | Set to 0 by the installer to defer the Hyprland setup. |
X_HW_AUTO | payload default 1 | payload (x setup) | Set to 0 during install to disable hardware auto-detection. |
Notes:
X_DRY=1ininstaller.shensures a JSON exists (with a placeholder if needed) and runsinstall.sh, which prints the install plan and exits without touching the disk. The configurator, when run withX_DRY=1, writes the JSON without the password and stops before the erase confirmation.X_HYPRLAND/X_HW_AUTObelong to thex-scriptspayload; the installer sets them when callingx setup.
Live medium vs installed system (credentials)
The live medium is intentionally permissive so it can be used without a
password: root autologin on tty1, empty root password and sshd with
PermitRootLogin yes + password authentication. All of that ships only in
airootfs (the live squashfs).
The installer never copies those files to the target: the installed system
takes /etc/shadow from the shadow package (root locked), creates the wheel
user from the seed and does not enable sshd. Keep that rule when adding
post-install automation: never copy /etc from the live into the target.
Dualboot mode
install.sh supports two modes ("mode" in the JSON): wipe (default)
erases the disk and builds a fresh GPT; dualboot installs into the
largest unallocated region, preserving every existing partition and the
Windows bootloader. The configurator asks for the mode.
dualboot is UEFI-only in this iteration and requires a GPT disk with an
existing EFI System Partition:
| Field | Values | Meaning |
|---|---|---|
mode | wipe / dualboot | install strategy |
esp | partition (optional) | reuse this ESP instead of auto-detecting the ef00 one |
min_size | GiB (default 20) | minimum free region accepted |
What it does:
- Validates UEFI + GPT + an existing ESP; never runs
sgdisk --zap-all. - Takes the largest free block (
sgdisk -F/-E), checksmin_sizeand creates only the root partition there (sgdisk -n 0:start:end -t 0:8300). Existing entries are never modified. - Mounts the existing ESP at
/mnt/bootand never formats it; btrfs +@/@home/@snapshots/@xstateexactly as in wipe mode. - Bootloader without touching
EFI/Microsoft/**:- systemd-boot:
bootctl installon the shared ESP; sd-boot auto-detects the Windows Boot Manager and lists it in the menu. The pre-existing fallbackEFI/BOOT/BOOTX64.EFI(possibly Windows') is saved and restored aroundbootctl. - GRUB:
grub-install --target=x86_64-efi --bootloader-id=xplusos-prober(GRUB_DISABLE_OS_PROBER=false) to add the Windows entry.
- systemd-boot:
- Keeps the Windows Boot Manager first in the firmware order (best effort via
efibootmgr), and X generations never overwrite Microsoft files.
Caveats: BIOS/MBR dualboot is not supported, and shrinking an existing partition to make room is out of scope (the free space must already exist).
Requirements and caveats
- Installation requires network access:
pacstrappulls from the official Arch mirrors and the[x]repository. An offline mirror bundled in the ISO is pending (see the workspace ROADMAP). - The target disk is completely erased.
systemd-bootis UEFI only; GRUB writes both a BIOS (with thebios_grubpartition) and a UEFI (removable) path, so either boot mode works.- LUKS uses LUKS2 with the legacy
encryptinitramfs hook.