# 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](#1-supported-platforms) 2. [Compatibility Notes](#2-compatibility-notes) 3. [Configuration Model](#3-configuration-model) 4. [Variable Reference](#4-variable-reference) - 4.1 [Core Variables](#41-core-variables) - 4.2 [`system` Dictionary](#42-system-dictionary) - 4.3 [`hypervisor` Dictionary](#43-hypervisor-dictionary) - 4.4 [CIS Hardening](#44-cis-hardening) - 4.5 [VMware Guest Operations](#45-vmware-guest-operations) - 4.6 [Multi-Disk Schema](#46-multi-disk-schema) - 4.7 [Advanced Partitioning Overrides](#47-advanced-partitioning-overrides) - 4.8 [Cleanup Defaults](#48-cleanup-defaults) 5. [Execution Pipeline](#5-execution-pipeline) 6. [Usage](#6-usage) 7. [Security](#7-security) 8. [Safety](#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 (including CIS hardening under `system.features.cis`) - **`hypervisor`** -- virtualization backend credentials and targeting Both 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/.yml` | Group | Environment-specific defaults | | `host_vars/.yml` | Single host | Host-specific overrides (`system.network.ip`, `system.id`, etc.) | ### Example Inventory ```yaml 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`. | 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](#distributions)) | | `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`) | | `content` | dict | see below | Package content source (mirror/DVD/Satellite, family-resolved) | | `packages` | list | `[]` | Additional packages installed post-reboot | | `network` | dict | see below | Network configuration | | `disks` | list | `[]` | Disk layout (see [Multi-Disk Schema](#46-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.content` Uniform package content source, family-resolved. `source: ''` defaults to `dvd` on EL and `mirror` on Debian/Ubuntu/Arch. Satellite values come from inventory/vault only, never committed code. | Key | Type | Default | Description | | -------------------------- | ------ | -------------- | ----------------------------------------------------------------- | | `source` | string | family default | `dvd`, `mirror`, `satellite`, or `none` | | `url` | string | family default | Mirror URL / EL `.repo` baseurl | | `proxy` | string | -- | `http://host:port` content proxy (dnf/apt/pacman) | | `gpgcheck` | bool | `true` | Repository GPG checking | | `satellite.host` | string | -- | EL Katello/Satellite hostname | | `satellite.ip` | string | -- | Optional `/etc/hosts` entry when DNS does not resolve the host | | `satellite.org` | string | -- | Organization label | | `satellite.activation_key` | string | -- | Activation key | | `satellite.ca_url` | string | derived | Katello CA RPM URL (default `https:///pub/katello-ca-consumer-latest.noarch.rpm`) | | `satellite.service_level` | string | -- | syspurpose service level | | `satellite.environment` | string | -- | Lifecycle environment | | `satellite.install` | bool | `false` | `false`: base from DVD/mirror then register; `true`: install from Satellite | #### `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). ```yaml 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 `. 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](#44-cis-hardening)) | | `cis.profile` | string | `default` | CIS profile: `default`, `l1`, or `l2` (see [4.4](#44-cis-hardening)) | | `cis.rules` | dict | `{}` | Per-rule CIS overrides | | `cis.params` | dict | `{}` | CIS parameter overrides | | `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) | | `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](#425-systemfeaturesdesktop)) | | `firmware.*` | dict | see below | Vendor firmware blobs and CPU microcode (see [4.2.6](#426-systemfeaturesfirmware)) | | `gpu.*` | dict | see below | Mesa/Vulkan and per-vendor GPU userspace (see [4.2.7](#427-systemfeaturesgpu)) | | `peripherals.*` | dict | see below | Fingerprint, camera, audio, bluetooth, DisplayLink (see [4.2.8](#428-systemfeaturesperipherals)) | | `hardware.*` | dict | see below | Hardware-detection profile override (see [4.2.9](#429-systemfeatureshardware)) | **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. > **Known limitation - Nvidia on Enterprise Linux (AlmaLinux/Rocky/RHEL):** the EL `akmod-nvidia*` > packages live in RPMFusion non-free, and the bootstrap only enables RPMFusion automatically on > **Fedora**, not on EL. So Nvidia on a bare EL desktop is best-effort: enable RPMFusion (or supply the > driver repo) out of band, or it falls back to `nouveau`. EL desktops are not a primary target. #### 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 | | `camera` | bool \| `auto` | `auto` | `v4l-utils` for UVC webcams. `auto` = install when a UVC/IPU6 camera is detected (IPU6 out-of-tree stack is logged, not auto-installed) | | `audio` | bool \| `auto` | `auto` | SOF firmware + ALSA UCM. `auto` = install when an audio device is detected | | `bluetooth` | bool \| `auto` | `auto` | `bluez`. `auto` = install when a Bluetooth controller is detected | | `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 | `{}` | Full override: non-empty SKIPS detection (golden image); empty = autodetect | | group fields | mixed | -- | `cpu`/`gpus`/`wireless`/`audio`/`camera`/`fingerprint`/`bluetooth`/`packages`/`disable`/`kernel_params` MERGE over autodetect (see below) | 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: ```yaml 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 ``` The same keys (minus `profile`) can also be set **directly under `hardware`** as a declarative **hardware group** that MERGES over auto-detection (auto-detect = base; the group supplements/overrides it). Unlike `profile`, which skips detection entirely, the group keeps detection running and layers on top - use it to pin everything a known device needs so nothing is ever under-set. | Key | Type | Merge semantics | | ------------------------- | ---- | -------------------------------------------------------- | | `cpu` | str | pin the CPU vendor (overrides detection when non-empty) | | `gpus`/`wireless`/`audio` | list | union with the detected vendor codes | | `camera` | dict | `{uvc, ipu6}` booleans OR'd with detection | | `fingerprint`/`bluetooth` | bool | OR'd with detection (force-on) | | `packages` | dict | per-`os_family` extra packages, added to the install set (deduped; empty entries dropped) | | `disable` | list | feature/vendor names force-off, applied last | | `kernel_params` | list | extra kernel cmdline params, appended to the bootloader | Example - a laptop with an Intel IPU6 camera (out-of-tree stack) and a Cirrus amp, pinned in a group's `group_vars`: ```yaml system: features: hardware: bluetooth: true # force-on if detection misses the combo card camera: ipu6: true # force the IPU6 path packages: # out-of-tree/AUR bits detection must not auto-install Archlinux: [intel-ipu6-dkms, v4l2-relayd, linux-firmware-cirrus] disable: [displaylink] # never pull DisplayLink on this device kernel_params: ["i915.enable_psr=0"] ``` ### 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 Hardening When `system.features.cis.enabled: true`, the CIS role applies hardening. The behaviour is driven by three keys under `system.features.cis`: | Key | Type | Default | Description | | --------- | ------ | ----------- | ----------------------------------------------------------------- | | `enabled` | bool | `false` | Apply CIS hardening at all | | `profile` | string | `default` | `default` (house baseline), `l1` (clean CIS Level 1), or `l2` | | `rules` | dict | `{}` | Per-rule on/off overrides on top of the profile | | `params` | dict | `{}` | Parameter overrides (deep-merged; list values replace wholesale) | **Profiles.** `default` is the established house baseline (CIS Level 1 plus the USB lockdown, full module blacklist, and IPv6-disable extras, minus the usability-hostile controls). `l1` is a clean CIS Level 1: it drops the L2 extras and adds password aging, AIDE, and warning banners. `l2` is `l1` plus auditd and the L2 extras. **Per-rule overrides.** Toggle an individual rule without changing profile, e.g. keep the default profile but allow USB and IPv6 on a desktop: ```yaml system: features: cis: enabled: true rules: usb_lockdown: false ipv6_disable: false ``` Rule keys: `module_blacklist`, `usb_lockdown`, `sysctl_hardening`, `ipv6_disable`, `umask_default`, `empty_password_login`, `pwquality`, `core_dumps`, `shell_timeout`, `journald_persistent`, `sudo_logfile`, `su_restriction`, `faillock`, `password_history`, `tcp_wrappers`, `crypto_policy`, `mask_services`, `cron_at_access`, `file_permissions`, `sshd_hardening`, `password_expiry`, `aide`, `warning_banners`, `auditd`, and the opt-in `grub_password` (set `rules.grub_password: true` with `params.grub_password_hash`). **Parameters.** Override baseline values under `params` (full list in `roles/cis/vars/main.yml`): ```yaml system: features: cis: enabled: true profile: l1 params: pwquality_minlen: 16 sysctl: # dict: deep-merged over the profile's set net.ipv4.ip_forward: 1 sshd_options: # list: REPLACES the entire default list - {option: X11Forwarding, value: "yes"} ``` Common params: `modules_blacklist` (list), `sysctl` (dict), `sshd_options` (list), `pwquality_minlen` (14), `tmout` (900), `umask` (077), `umask_profile` (027), `faillock_deny` (5), `faillock_unlock_time` (900), `password_remember` (5), `pass_max_days` (365), `aide_cron_hour`/`aide_cron_minute`, `banner_text`, `grub_password_hash`. ### 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`) | ```yaml 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 ```bash 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.