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) |
| 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) |
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","encryption":"no","luks_password":"","hyprland":"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, 512 MiB EFI partition, rest = root.systemd-boot: 512 MiB EFI partition, rest = root.
- 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 mounted at/mnt. - Package set:
- Base set:
base base-devel linux linux-firmware sudo networkmanager openssh git jq x-release kitty pipewire pipewire-pulse pipewire-alsa wireplumber alsa-utils sddm, plusgrub efibootmgrfor GRUB andcryptsetupfor LUKS. fullprofile: adds every package in the manifest pointed to byX_PKGLIST(default/root/x-installer/packages.x86_64).coreprofile: adds onlyvim zsh.
- Base set:
- Wait for network (DNS check against
geo.mirror.pkgbuild.com, up to ~120 s) and runpacstrap -K /mnt <pkgs>from the official mirrors plus the[x]repository. - 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 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).
- 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 config snapshot shipped in the package (/usr/share/x/config); it only clones the externalxscriptor-colors/hyprlandrepo (branchmain) as a fallback, and never configures NVIDIA (that is handled by the system hardware phase). - 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. For LUKS theGRUB_CMDLINE_LINUXis set tocryptdevice=UUID=<luks-uuid>:xroot root=/dev/mapper/xroot rw.systemd-boot:bootctl --esp-path=/boot install, aBOOTX64.EFIremovable fallback if needed, and a loader entryX Linux(UEFI only) with the matchingroot=orcryptdevice=cmdline.
- 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
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","encryption":"no","hyprland":"no"}
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.
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.