22 KiB
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
- Supported Platforms
- Compatibility Notes
- Configuration Model
- Variable Reference
- 4.1 Core Variables
- 4.2
systemDictionary - 4.3
hypervisorDictionary - 4.4
cisDictionary - 4.5 VMware Guest Operations
- 4.6 Multi-Disk Schema
- 4.7 Advanced Partitioning Overrides
- 4.8 Cleanup Defaults
- Execution Pipeline
- Usage
- Security
- 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_isois required forsystem.os: rhel.- RHEL installs should use
ext4orxfs(notbtrfs). custom_iso: trueskips ArchISO validation; your installer must provide required tooling.- On non-Arch installers, set
system.features.chroot.toolexplicitly.
3. Configuration Model
Two dict-based variables drive the entire configuration:
system-- host, network, users, disk layout, encryption, and feature toggleshypervisor-- 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...
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) |
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) |
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) |
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:
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 | 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) |
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:
- global_defaults -- normalize inputs, validate, set OS flags
- system_check -- detect installer environment, verify live/non-prod target
- virtualization -- create VM (if virtual), attach disks, cloud-init
- environment -- prepare installer: mount ISO, configure repos, setup pacman
- partitioning -- create partitions, LVM, LUKS, mount filesystems
- bootstrap -- install base system and packages (OS-specific)
- configuration -- users, fstab, locales, bootloader, encryption enrollment, networking
- cis -- CIS hardening (when
system.features.cis.enabled: true) - 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
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 setupinventory_libvirt_example.yml-- libvirt virtual setupinventory_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.