Overview/X Linux (distro)
Text installer
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) |
| Install mode | Wipe disk / Dualboot (keep existing partitions) | mode (wipe/dualboot) |
| 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) |
| Root encryption (LUKS) | yes/no | encryption (yes/no) |
| LUKS passphrase | reuse user password or dedicated | luks_password |
| Install Hyprland setup | yes/no (requires network) | hyprland (yes/no) |
| Install Xscriptor AI agents | yes/no (requires network) | agents (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 (wipe mode). The resulting
JSON looks like:
{"disk":"/dev/sda","mode":"wipe","hostname":"x","username":"x","password":"secret","language":"en","locale":"en_US.UTF-8","keyboard":"us","timezone":"UTC","profile":"full","bootloader":"grub","encryption":"no","luks_password":"","hyprland":"no","agents":"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. Indualbootmode the existing ESP is reused and never formatted. - Package set:
- Base set:
base base-devel linux linux-firmware sudo networkmanager openssh git jq x-release btrfs-progs xfetch-bin xtop-git kitty pipewire pipewire-pulse pipewire-alsa wireplumber alsa-utils sddm, plusgrub efibootmgrfor GRUB andcryptsetupfor LUKS.btrfs-progsis required by the generations engine; the X tools (xfetch,xtop) are 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 zsh.- The
fullprofile with the Hyprland desktop compiles AUR packages (quickshell-git,swayosd-git, ...): give the installer ≥6 GB RAM; on low-RAM machines it limits the build jobs automatically.
- Base set:
- Wait for network (DNS check against
geo.mirror.pkgbuild.com, up to ~120 s) and runpacstrap /mnt <pkgs>from the official mirrors plus the signed[x]repository (Required). Before that, the installer prepares the target keyring (pacman-key --gpgdir /mnt/etc/pacman.d/gnupg --init,--populate archlinux, add + locally sign/etc/pacman.d/x-repo.pub). - 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. - 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 shellbash), set the password withchpasswd, and enable%wheelinsudoers. - Provisioning with the
xCLI from thex-scriptspackage:- system phases as root:
X_HW_AUTO=0 X_GEN_SKIP=1 x setup(generation recording is deferred to the first-generation step below); - 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 (only if
hyprland=yes): 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. The tool prefers the offline equisdots snapshot shipped in the package (/usr/share/x/config/equisdots); it only clones the officialequisdots/dotsinstaller as an online fallback. 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. - 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; see the generations page in the Scripts section. - 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.
Autoinstall
Unattended installation from the live ISO: boot the autoinstall menu entry
(hotkey a, xauto=1) with a storage device labeled cidata containing
x-install.json (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","encryption":"no","hyprland":"no","kernel_params":"console=ttyS0"}
kernel_params is optional: extra kernel parameters appended to the installed
system's cmdline (validated against a safe character set), e.g.
console=ttyS0 for headless validation.
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 plus 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 with the@/@home/@snapshots/@xstatesubvolumes exactly 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=x(its own ID, never Microsoft's) plusos-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 creates the X NVRAM entry withefibootmgrwhenbootctldid not write one (common inside a chroot). 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). - In
wipemode the target disk is completely erased;dualbootonly uses the free region and leaves existing partitions untouched. 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. - The BIOS/GRUB, UEFI/systemd-boot, LUKS and dualboot install paths have been
validated end-to-end in QEMU VMs (autoinstall, generation
0001, clean boot andx gen verify).