2026-01-02 11:26:21 +01:00

Ansible Bootstrap

Automated Linux system bootstrap using the Arch Linux ISO as a universal installer. Deploys any supported distribution on virtual or physical targets via Infrastructure-as-Code.

Non-Arch targets require the appropriate package manager available from the ISO environment (e.g. dnf for RHEL-family). Set system.features.chroot.tool if arch-chroot is unavailable.

Table of Contents

  1. Supported Platforms
  2. Compatibility Notes
  3. Configuration Model
  4. Variable Reference
  5. Execution Pipeline
  6. Usage
  7. Security
  8. Safety

1. Supported Platforms

Distributions

system.os Distribution system.version
almalinux AlmaLinux 9, 10
archlinux Arch Linux latest (rolling)
debian Debian 12, 13, unstable
fedora Fedora 43, 44
rhel Red Hat Enterprise Linux 9, 10
rocky Rocky Linux 9, 10
ubuntu Ubuntu (latest non-LTS) optional (tracks 25.10 questing)
ubuntu-lts Ubuntu LTS optional (tracks 26.04 resolute)

Hypervisors

Hypervisor hypervisor.type
libvirt libvirt
Proxmox VE proxmox
VMware vmware
Xen xen
Bare metal none

2. Compatibility Notes

  • rhel_iso is required for system.os: rhel.
  • RHEL installs should use ext4 or xfs (not btrfs).
  • custom_iso: true skips ArchISO validation; your installer must provide required tooling.
  • On non-Arch installers, set system.features.chroot.tool explicitly.

3. Configuration Model

Two dict-based variables drive the entire configuration:

  • system -- host, network, users, disk layout, encryption, and feature toggles
  • hypervisor -- virtualization backend credentials and targeting

An optional third dict cis overrides CIS hardening parameters when system.features.cis.enabled: true.

All three are standard Ansible variables. Place them in group_vars/, host_vars/, or inline inventory. With hash_behaviour = merge, dictionaries merge across scopes, so shared values go in group vars and host-specific overrides go per-host.

Variable Placement

Location Scope Typical use
group_vars/all.yml All hosts Shared hypervisor, system.filesystem, boot_iso
group_vars/<group>.yml Group Environment-specific defaults
host_vars/<host>.yml Single host Host-specific overrides (system.network.ip, system.id, etc.)

Example Inventory

all:
  vars:
    system:
      filesystem: btrfs
    boot_iso: "local:iso/archlinux-x86_64.iso"
    hypervisor:
      type: proxmox
      url: pve01.example.com
      username: root@pam
      password: !vault |
        $ANSIBLE_VAULT...
      node: pve01
      storage: local-lvm

  children:
    bootstrap:
      hosts:
        app01.example.com:
          ansible_host: 10.0.0.10
          system:
            type: virtual
            os: debian
            version: "12"
            name: app01.example.com
            id: 101
            cpus: 2
            memory: 4096
            network:
              bridge: vmbr0
              ip: 10.0.0.10
              prefix: 24
              gateway: 10.0.0.1
              dns:
                servers: [1.1.1.1, 1.0.0.1]
                search: [example.com]
            disks:
              - size: 40
              - size: 120
                mount:
                  path: /data
                  fstype: xfs
            users:
              ops:
                password: !vault |
                  $ANSIBLE_VAULT...
                keys:
                  - "ssh-ed25519 AAAA..."
                sudo: true
            root:
              password: !vault |
                $ANSIBLE_VAULT...
            luks:
              enabled: true
              passphrase: !vault |
                $ANSIBLE_VAULT...
              method: tpm2
              tpm2:
                pcrs: "7"
            features:
              cis:
                enabled: true
              firewall:
                enabled: true
                backend: firewalld
                toolkit: nftables

4. Variable Reference

4.1 Core Variables

Top-level variables outside system/hypervisor/cis.

Variable Type Default Description
boot_iso string -- Boot ISO path (required for virtual installs)
rhel_iso string -- RHEL ISO path (required when system.os: rhel)
custom_iso bool false Skip ArchISO validation and pacman setup
thirdparty_tasks string dropins/preparation.yml Drop-in task file included during environment setup

4.2 system Dictionary

Key Type Default Description
type string virtual virtual or physical
os string -- Target distribution (see table)
version string -- Version selector for versioned distros
filesystem string ext4 btrfs, ext4, or xfs
name string inventory hostname Final hostname
timezone string Europe/Vienna System timezone (tz database name)
locale string en_US.UTF-8 System locale
keymap string us Console keymap
id int/string -- VMID (required for Proxmox)
cpus int 0 vCPU count (required for virtual)
memory int 0 Memory in MiB (required for virtual)
balloon int 0 Balloon memory in MiB (Proxmox)
path string -- Hypervisor folder/path (falls back to hypervisor.folder)
mirror string per-distro default Override package mirror (Debian/Ubuntu)
packages list [] Additional packages installed post-reboot
network dict see below Network configuration
disks list [] Disk layout (see Multi-Disk Schema)
users dict {} User accounts (keyed by username)
root dict see below Root account settings
luks dict see below Encryption settings
features dict see below Feature toggles

system.network

Key Type Default Description
bridge string -- Hypervisor network/bridge name
vlan string/int -- VLAN tag
ip string -- Static IP (omit for DHCP)
prefix int -- CIDR prefix (1-32, required with ip)
gateway string -- Default gateway
dns.servers list [] DNS resolvers (must be a YAML list)
dns.search list [] Search domains (must be a YAML list)
interfaces list [] Multi-NIC config (overrides flat fields above)

When interfaces is empty, the flat fields (bridge, ip, prefix, gateway, vlan) are auto-wrapped into a single-entry list. When interfaces is set, it takes precedence. Each entry supports: name, bridge (required), vlan, ip, prefix, gateway.

system.users

Dict keyed by username. At least one user must have a password (used for SSH access during bootstrap). Users without a password get locked accounts (key-only auth).

system:
  users:
    svcansible:
      password: "vault_lookup"
      keys:
        - "ssh-ed25519 AAAA..."
    appuser:
      sudo: "ALL=(ALL) NOPASSWD: ALL"
      keys:
        - "ssh-ed25519 BBBB..."
Key Type Default Description
(dict key) string -- Username (required)
password string -- User password (required for at least one user)
keys list [] SSH public keys
sudo bool/string -- true for NOPASSWD ALL, or custom sudoers string

Users must be defined in inventory. The dict format enables additive merging across inventory layers with hash_behaviour=merge.

system.root

Key Type Default Description
password string -- Root password
shell string /bin/bash Login shell

system.luks

Key Type Default Description
enabled bool false Enable encrypted root
passphrase string -- Passphrase for format/open/enroll
mapper string SYSTEM_DECRYPTED Mapper name under /dev/mapper
auto bool true Auto-unlock toggle
method string tpm2 Auto-unlock backend: tpm2 or keyfile
keysize int 64 Keyfile size in bytes
options string discard,tries=3 Additional crypttab options
type string luks2 LUKS format type
cipher string aes-xts-plain64 Cipher
hash string sha512 Hash algorithm
iter int 4000 PBKDF iteration time (ms)
bits int 512 Key size (bits)
pbkdf string argon2id PBKDF algorithm

system.luks.tpm2

Key Type Default Description
device string auto TPM2 device selector
pcrs string/list -- PCR binding policy (e.g. "7" or "0+7"); empty = no PCR binding

TPM2 auto-unlock: Uses systemd-cryptenroll on all distros. The user-set passphrase remains as a backup unlock method. TPM2 enrollment runs in the chroot during bootstrap; if it fails (e.g. no TPM2 hardware), the system boots with passphrase-only unlock and TPM2 can be enrolled post-deployment via systemd-cryptenroll --tpm2-device=auto <device>.

On Debian/Ubuntu, TPM2 auto-unlock requires dracut (initramfs-tools does not support tpm2-device). The bootstrap auto-switches to dracut when method: tpm2 is set. Override via features.initramfs.generator.

system.features

Key Type Default Description
cis.enabled bool false Enable CIS hardening (see 4.4)
selinux.enabled bool true SELinux management
firewall.enabled bool true Firewall setup
firewall.backend string firewalld firewalld or ufw
firewall.toolkit string nftables nftables or iptables
ssh.enabled bool true SSH service/package management
zstd.enabled bool true zstd-related tuning
swap.enabled bool true Swap setup
banner.motd bool false MOTD banner
banner.sudo bool true Sudo banner
chroot.tool string arch-chroot arch-chroot, chroot, or systemd-nspawn
initramfs.generator string auto-detected Override initramfs generator (see below)
rhel_repo.source string iso RHEL post-install repo source: iso, satellite, or none
rhel_repo.url string -- Satellite/custom repo URL when source: satellite
secure_boot.enabled bool false Enable Secure Boot (Arch via sbctl, others via shim)
secure_boot.method string -- Arch only: sbctl (default) or uki
desktop.* dict see below Desktop environment settings (see 4.2.5)
firmware.* dict see below Vendor firmware blobs and CPU microcode (see 4.2.6)
gpu.* dict see below Mesa/Vulkan and per-vendor GPU userspace (see 4.2.7)
peripherals.* dict see below Fingerprint readers, webcams, DisplayLink (see 4.2.8)
hardware.* dict see below Hardware-detection profile override (see 4.2.9)

Initramfs generator auto-detection: RedHat -> dracut, Arch -> mkinitcpio, Debian/Ubuntu -> initramfs-tools. Override with dracut, mkinitcpio, or initramfs-tools. When LUKS TPM2 auto-unlock is enabled and the native generator does not support tpm2-device, the generator is automatically upgraded to dracut. On distros with older dracut (no tpm2-tss module), clevis is used as a fallback for TPM2 binding.

4.2.5 system.features.desktop

Key Type Default Description
enabled bool false Install desktop environment
environment string "" gnome, kde, sway, or hyprland
display_manager string auto-detected Override DM: gdm, sddm, plasma-login-manager, greetd, or ly
autologin bool | string false false to disable, or a username from system.users to auto-login that user
session string auto-from-environment Session to autologin into; overrides the per-environment default (sddm .desktop basename / greetd command)
groups list [] Opt-in package groups installed on top of the base set (keys of desktop_package_groups, e.g. dev)

All desktop environments are Wayland-only. sway and hyprland are available on Arch only; gnome and kde are available on all three families. On enterprise Linux (almalinux/rocky/rhel) the base desktop installs browser, PDF and image viewers but no video player - none is packaged in the EL base repositories, and no third-party repo is pulled in; add one from rpmfusion/flatpak if you need it.

When enabled: true, the bootstrap installs the desktop environment packages, enables the display manager and bluetooth services, and sets the systemd default target to graphical.target.

Display manager auto-detection: gnome to gdm; kde to plasma-login-manager on Arch and Fedora 44+ (Plasma 6.6), else sddm; sway and hyprland to greetd.

ly is an explicit-only override (never auto-selected), available on Arch only, and is desktop-agnostic - it can front any environment. It runs on tty2 with getty@tty2 masked, and its autologin is written to /etc/ly/config.ini; set session to the target session's .desktop basename (sway and hyprland resolve automatically).

When autologin names a user, the matching display manager is configured to log that user in without a password prompt. session is resolved automatically per environment when left empty (gdm picks its default, sddm uses plasma.desktop for kde, greetd runs the compositor command for sway/hyprland), so it only needs setting to override that choice.

4.2.6 system.features.firmware

Key Type Default Description
enabled bool | auto auto Install vendor firmware blobs. auto = on for physical, off for virtual
microcode bool | auto auto Install CPU microcode. auto follows firmware.enabled

Defaults are designed so a baremetal install picks up firmware automatically with no inventory entry needed, while VMs skip it (the hypervisor handles those). The environment role detects CPU/GPU/wireless vendors from the live host (via lscpu and lspci) and the bootstrap role installs only the matching firmware packages. On Arch, this uses the vendor splits (linux-firmware-amdgpu, linux-firmware-realtek, etc.) so the install stays minimal. On Debian, it uses the equivalent firmware-* packages. Distros without firmware splits fall back to a single meta package.

4.2.7 system.features.gpu

Key Type Default Description
enabled bool false Install Mesa, Vulkan, and per-GPU userspace
nvidia_driver string auto One of auto, open, proprietary, nouveau

Pair with desktop.enabled: true for a working desktop. The package set is determined by the same hardware profile as firmware. The nvidia_driver: auto default picks open (nvidia-open kernel modules) for Turing or newer GPUs, falls back to proprietary for older cards on distros that ship the proprietary driver, and falls back to nouveau elsewhere. Force a specific flavor by setting the value explicitly.

Proprietary and open Nvidia drivers on Fedora require RPMFusion non-free, which the bootstrap enables automatically when needed. Debian uses nvidia-driver from the non-free component (already enabled in the managed sources.list). Ubuntu uses restricted. Arch ships both nvidia-open-dkms and nvidia-dkms in the extra repository - no third-party setup required.

4.2.8 system.features.peripherals

Key Type Default Description
enabled bool | auto auto Master switch. auto follows desktop.enabled
fingerprint bool | auto auto fprintd/libfprint. auto = install when reader detected
webcam bool | auto auto v4l-utils and userspace tooling. auto follows enabled
displaylink bool false DisplayLink dock support (explicit opt-in; see notes)

Fingerprint detection scans lsusb for known reader vendor IDs (Synaptics, Validity, Goodix, Elan, Egis, Broadcom, AuthenTec, Upek, Futronic). When fingerprint: auto and a reader is present, fprintd and the PAM helper are installed. PAM enrollment must be done post-install (fprintd-enroll).

DisplayLink ships proprietary userspace that distros do not package consistently. The bootstrap installs the in-tree evdi-dkms kernel module on Debian/Ubuntu and the evdi module on Fedora, but the userspace blob must still be installed manually from DisplayLink's site after first boot. Arch users typically use AUR (displaylink); this is not wired into the bootstrap.

4.2.9 system.features.hardware

Key Type Default Description
profile dict {} Hardware-detection override; empty means autodetect from live host

When empty, hardware is detected at the start of the bootstrap. When set, detection is skipped and the supplied profile drives package selection - this is the golden-image flow: bake an image with a fixed profile, snapshot it, and reuse the same profile on every deploy of that hardware class.

Profile shape:

system:
  features:
    hardware:
      profile:
        cpu: intel                    # intel | amd
        gpus: [intel, nvidia]         # any of: intel, amd, nvidia
        nvidia_supports_open: true    # set false to force proprietary/nouveau
        wireless: [intel]             # any of: intel, amd, atheros, broadcom,
                                      # mediatek, marvell, realtek, qcom, cirrus
        fingerprint: false            # set true to force fprintd install

4.3 hypervisor Dictionary

Key Type Default Description
type string -- libvirt, proxmox, vmware, xen, or none
url string -- API host (Proxmox/VMware)
username string -- API username
password string -- API password
node string -- Target compute node (Proxmox node / VMware ESXi host; mutually exclusive with cluster on VMware)
storage string -- Storage identifier (Proxmox/VMware)
datacenter string -- VMware datacenter
cluster string -- VMware cluster
certs bool false TLS certificate validation (VMware)
ssh bool false Enable SSH on guest and switch connection (VMware)

4.4 cis Dictionary

When system.features.cis.enabled: true, the CIS role applies hardening. All values have sensible defaults; override specific keys via the cis dict.

Key Type Default Description
modules_blacklist list see below Kernel modules to blacklist via modprobe
sysctl dict see below Sysctl key/value pairs written to 10-cis.conf
sshd_options list see below SSHD options applied via lineinfile
pwquality_minlen int 14 Minimum password length
tmout int 900 Shell timeout (seconds)
umask string 077 Default umask in bashrc
umask_profile string 027 Default umask in /etc/profile
faillock_deny int 5 Failed login attempts before lockout
faillock_unlock_time int 900 Lockout duration (seconds)
password_remember int 5 Password history depth

Default modules blacklist: freevxfs, jffs2, hfs, hfsplus, cramfs, udf, usb-storage, dccp, sctp, rds, tipc, firewire-core, firewire-sbp2, thunderbolt. squashfs is added automatically except on Ubuntu (snap dependency).

Default sysctl settings include: kernel.yama.ptrace_scope=2, kernel.kptr_restrict=2, kernel.perf_event_paranoid=3, kernel.unprivileged_bpf_disabled=1, IPv4/IPv6 hardening, ARP protection, and IPv6 disabled by default. Override individual keys:

cis:
  sysctl:
    net.ipv6.conf.all.disable_ipv6: 0    # re-enable IPv6
    net.ipv4.ip_forward: 1               # enable for routers/containers

Default SSHD options enforce: PermitRootLogin no, PasswordAuthentication no, X11Forwarding no, AllowTcpForwarding no, MaxAuthTries 4, and post-quantum KEX (mlkem768x25519-sha256 on OpenSSH 9.9+). Override per-option:

cis:
  sshd_options:
    - { option: X11Forwarding, value: "yes" }
    - { option: AllowTcpForwarding, value: "yes" }

Note: providing sshd_options replaces the entire list. Copy the defaults from roles/cis/defaults/main.yml and modify as needed.

4.5 VMware Guest Operations

When hypervisor.type: vmware uses the vmware_tools connection:

Variable Description
ansible_vmware_tools_user Guest OS username
ansible_vmware_tools_password Guest OS password
ansible_vmware_guest_path VM inventory path
ansible_vmware_host vCenter/ESXi hostname
ansible_vmware_user vCenter/ESXi API username
ansible_vmware_password vCenter/ESXi API password
ansible_vmware_validate_certs TLS certificate validation

4.6 Multi-Disk Schema

system.disks[0] is the OS disk (no mount.path). Additional entries define data disks.

Key Type Description
size number Disk size in GB (required for virtual)
device string Block device path (required for physical data disks)
partition string Derived from device during normalization (not user input)
mount.path string Mount point (additional disks only)
mount.fstype string btrfs, ext4, or xfs
mount.label string Filesystem label
mount.opts string Mount options (default: defaults)
system:
  disks:
    - size: 80                    # OS disk
    - size: 200                   # Data disk
      mount:
        path: /data
        fstype: xfs
        label: DATA

4.7 Advanced Partitioning Overrides

Variable Default Description
partitioning_efi_size_mib 512 EFI system partition size in MiB
partitioning_boot_size_mib 1024 Separate /boot size in MiB
partitioning_separate_boot auto-derived Force a separate /boot partition
partitioning_boot_fs_fstype auto-derived Filesystem for /boot
partitioning_use_full_disk true Use remaining VG space for root LV

Swap sizing: RAM >= 16GB gets swap = RAM/2. RAM < 16GB gets swap = max(RAM_GB, 2GB). Further capped to prevent over-allocation on small disks.

LVM layout (when not using btrfs): root, swap, and when CIS is enabled: /home (2-20GB, 10% of disk), /var (2GB), /var/log (2GB), /var/log/audit (1.5GB).

4.8 Cleanup Defaults

Post-install verification and recovery settings.

Variable Default Description
cleanup_verify_boot true Check VM accessibility after reboot
cleanup_boot_timeout 300 Timeout in seconds for boot verification
cleanup_remove_on_failure true Auto-remove VMs that fail to boot (created this run only)

5. Execution Pipeline

Roles execute in this order:

  1. global_defaults -- normalize inputs, validate, set OS flags
  2. system_check -- detect installer environment, verify live/non-prod target
  3. virtualization -- create VM (if virtual), attach disks, cloud-init
  4. environment -- prepare installer: mount ISO, configure repos, setup pacman, detect hardware
  5. partitioning -- create partitions, LVM, LUKS, mount filesystems
  6. bootstrap -- install base system, packages, and vendor-matched hardware bits
  7. configuration -- users, fstab, locales, bootloader, encryption enrollment, networking
  8. cis -- CIS hardening (when system.features.cis.enabled: true)
  9. cleanup -- unmount, shutdown installer, remove media, verify boot

6. Usage

ansible-playbook -i inventory.yml main.yml
ansible-playbook -i inventory.yml main.yml -e @vars.yml

All credentials (system.users, system.root.password) must be defined in inventory or passed via -e.

Example inventory files are included:

  • inventory_example.yml -- Proxmox virtual setup
  • inventory_libvirt_example.yml -- libvirt virtual setup
  • inventory_baremetal_example.yml -- bare-metal physical setup

7. Security

Use Ansible Vault for all sensitive values (hypervisor.password, system.luks.passphrase, user passwords in system.users, system.root.password).

8. Safety

The playbook aborts on non-live/production targets. It refuses to touch pre-existing VMs and only cleans up VMs created in the current run.

Description
An Ansible playbook for automating system bootstrap processes in an Infrastructure-as-Code manner, utilizing ArchISO as the foundational tool.
Readme 3.1 MiB
Languages
Jinja 98.4%
Shell 1.6%