# 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` Dictionary](#44-cis-dictionary) - 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 | `8`, `9`, `10` | | `alpine` | Alpine Linux | latest (rolling) | | `archlinux` | Arch Linux | latest (rolling) | | `debian` | Debian | `10`-`13`, `unstable` | | `fedora` | Fedora | `38`-`45` | | `opensuse` | openSUSE Tumbleweed | latest (rolling) | | `rhel` | Red Hat Enterprise Linux | `8`, `9`, `10` | | `rocky` | Rocky Linux | `8`, `9`, `10` | | `ubuntu` | Ubuntu (latest non-LTS) | optional (e.g. `24.04`) | | `ubuntu-lts` | Ubuntu LTS | optional (e.g. `24.04`) | | `void` | Void Linux | latest (rolling) | ### 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/.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... host: 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: - name: 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](#distributions)) | | `version` | string | -- | Version selector for versioned distros | | `filesystem` | string | -- | `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 | | `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` | list | `[]` | User accounts | | `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` | Key | Type | Default | Description | | ---------- | ----------- | ------- | -------------------------------------------------- | | `name` | string | -- | Username (required) | | `password` | string | -- | User password (required for first user) | | `keys` | list | `[]` | SSH public keys | | `sudo` | bool/string | -- | `true` for NOPASSWD ALL, or custom sudoers string | The first user's credentials are prompted interactively via `vars_prompt` unless supplied in inventory or `-e`. #### `system.root` | Key | Type | Default | Description | | ---------- | ------ | ------- | ------------- | | `password` | string | -- | Root password | #### `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 | | `urandom` | bool | `true` | Use urandom during key generation | | `verify` | bool | `true` | Verify passphrase during format | #### `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"`) | #### `system.features` | Key | Type | Default | Description | | ------------------ | ------ | -------------- | ------------------------------------ | | `cis.enabled` | bool | `false` | Enable CIS hardening (see [4.4](#44-cis-dictionary)) | | `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` | ### 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 | | `host` | string | -- | Proxmox node name | | `storage` | string | -- | Storage identifier (Proxmox/VMware) | | `datacenter` | string | -- | VMware datacenter | | `cluster` | string | -- | VMware cluster | | `certs` | bool | `true` | 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: ```yaml 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: ```yaml 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 | Partition device path (required for physical data disks) | | `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 5. **partitioning** -- create partitions, LVM, LUKS, mount filesystems 6. **bootstrap** -- install base system and packages (OS-specific) 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 ``` Credentials for the first user and root are prompted interactively via `vars_prompt` unless already set 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`, `system.users[].password`, `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.