Compare commits

...

160 Commits

Author SHA1 Message Date
f8eaa41fc2 fix(partitioning): register swapoff result for changed_when handling 2026-02-11 23:47:36 +01:00
ed8da6e4e2 fix(luks): complete migration of partitioning_luks_tpm2_device reference 2026-02-11 23:28:05 +01:00
a60e6fd0d3 refactor(bootstrap): nest network fields under system.network to match main project schema 2026-02-11 23:03:37 +01:00
45c002c2dd fix(bootstrap): correct changed_when on state-changing commands 2026-02-11 21:06:10 +01:00
7a76f58384 refactor(luks): use system_cfg.luks directly across roles 2026-02-11 19:26:51 +01:00
8c0716508e fix: honor libvirt network config, preserve DHCP DNS with search-only NM config, and exact-match Xen VM names 2026-02-11 14:00:20 +01:00
e5d2720bfe docu(readme): recompose README from pre/post consolidation versions 2026-02-11 08:25:15 +01:00
cd34b41862 fix(banner): align MOTD star border and default motd to disabled 2026-02-11 08:02:27 +01:00
37130da17b fix(libvirt): restore missing virtualization_mac_address default 2026-02-11 08:02:27 +01:00
4be9e2bfe1 refactor(safety): remove redundant live environment detection from system_check 2026-02-11 08:02:27 +01:00
d8fcc6033d refactor(playbook): rename prompt variables with backwards-compatible fallbacks 2026-02-11 08:02:27 +01:00
fc8f43a25a refactor(validation): deduplicate hypervisor combine and collapse schema checks 2026-02-11 08:02:27 +01:00
70475f4082 refactor(system): simplify normalization by removing redundant intermediate merges 2026-02-11 08:02:27 +01:00
865d96c18e fix(bootstrap): repair version-specific package availability across distributions 2026-02-11 08:02:27 +01:00
920e7d3f21 docu(readme): consolidate final documentation state 2026-02-11 05:37:18 +01:00
9f6fff313b fix(config): enable dictionary merge for scoped overrides 2026-02-11 05:37:18 +01:00
e7323258fd refactor(schema): move filesystem into system dictionary 2026-02-11 05:37:18 +01:00
3d026407e5 refactor(configuration): simplify grub commandline variable assembly 2026-02-11 05:37:18 +01:00
469d89641e refactor(configuration): reduce LUKS runtime temporary facts 2026-02-11 05:37:18 +01:00
5326907ae9 refactor(schema): simplify dict normalization and schema checks 2026-02-11 05:37:18 +01:00
aac2bd0b06 docu(schema): update docs and examples to compact dict keys 2026-02-11 05:37:18 +01:00
636656214b refactor(schema): rename nested dict keys and simplify validation 2026-02-11 05:37:18 +01:00
e2a42771ab docu(schema): align docs and baremetal example with dict model 2026-02-11 05:37:18 +01:00
8894da2ea1 fix(validation): reject deprecated top-level schema keys 2026-02-11 05:37:18 +01:00
b8c672507f refactor(vars): simplify normalization and remove effective intermediates 2026-02-11 05:37:18 +01:00
04727033f1 fix(system): default physical installs to archlinux when os is omitted 2026-02-11 05:37:18 +01:00
a9db85d45e docu(readme): document dict-based variables and examples 2026-02-11 05:37:18 +01:00
fcc7c6aeb6 fix(runtime): migrate roles to nested system fields 2026-02-11 05:37:18 +01:00
db08609acf feat(disks): add standardized multi-disk mount schema 2026-02-11 05:37:18 +01:00
961c8f259c refactor(vars): enforce nested system and hypervisor schema 2026-02-11 05:37:18 +01:00
9101e12126 refactor(vars): remove legacy variable inputs 2026-02-11 05:37:18 +01:00
fc05708466 refactor(vars): add system/hypervisor dict inputs 2026-02-11 05:37:18 +01:00
c4c96dbfb5 fix(partitioning): add LVM extent headroom 2026-02-06 00:43:02 +01:00
5ff0bac9d8 fix(network): Removes hardcoded MAC-Address from NetworkManager config 2026-01-05 18:22:18 +01:00
3d8b623f66 refactor(services): remove unnecessary firewalld services disablement. 2026-01-05 18:19:14 +01:00
a093bf3e28 feat(services): implement SSH server toggeling 2026-01-05 18:18:18 +01:00
a90e08cd4f Force local stat for third-party prep tasks 2026-01-02 19:15:34 +01:00
a5a58710a2 Force local connection for third-party prep check 2026-01-02 19:14:11 +01:00
51c0f58b16 Run third-party prep check locally 2026-01-02 19:02:00 +01:00
21318b8c8a Fix localhost delegate for third-party prep check 2026-01-02 18:58:40 +01:00
c3f26f2c92 Add third-party preparation task hook 2026-01-02 18:55:45 +01:00
c62de8bf4a Make chroot command configurable 2026-01-02 18:53:55 +01:00
c5e01c3652 Add swap_enabled toggle for swap setup 2026-01-02 18:51:27 +01:00
49372309d2 Add zstd toggle for btrfs and zram 2026-01-02 18:47:32 +01:00
a669e3ddfb Update LVM swap sizing policy 2026-01-02 16:29:24 +01:00
9b070c6e8d Enforce 20GiB minimum vm_size 2026-01-02 16:18:14 +01:00
cc07a896d9 Enable full-disk LVM root sizing 2026-01-02 16:11:06 +01:00
88a8737115 Use systemd module and link timezone 2026-01-02 16:10:50 +01:00
76bbff43c0 Document partitioning overrides and inventory host vars 2026-01-02 16:10:50 +01:00
53b4390ebf Fix post-reboot extra packages task 2026-01-02 15:55:27 +01:00
8a369de5d7 Align ESP sizing to full 512 MiB 2026-01-02 15:10:35 +01:00
74fbfbccb5 Mount Debian ESP on /boot/efi without LUKS 2026-01-02 15:10:35 +01:00
41cccbb547 Drop vars.yml usage 2026-01-02 15:10:35 +01:00
ba9654b7bd Make inventory examples more generic 2026-01-02 15:10:34 +01:00
14d774ffac Inline extra package normalization 2026-01-02 15:10:34 +01:00
40df28f59b Move pre-tasks into global defaults 2026-01-02 15:10:34 +01:00
20e10c3627 Drop custom_iso_enabled and log defaults 2026-01-02 15:10:34 +01:00
2672da4187 Restore global defaults lint exclusion 2026-01-02 15:10:34 +01:00
0143517787 Map global defaults in playbook 2026-01-02 15:10:34 +01:00
e9bf0c8242 Fix lint formatting and exceptions 2026-01-02 15:10:34 +01:00
a6d71125e8 Increase EFI system partition size 2026-01-02 15:10:34 +01:00
df90672237 Add Molecule scaffolding 2026-01-02 11:26:21 +01:00
65494a6977 Add libvirt inventory matrix example 2026-01-02 11:26:06 +01:00
52c67c5a39 Move derived vars into role defaults 2026-01-02 11:25:51 +01:00
1c23055dd2 Add firewalld_enabled toggle 2026-01-02 11:25:40 +01:00
8395ad9e90 Define optional defaults and require vm_cpus 2026-01-02 11:25:06 +01:00
e59f056904 Move partitioning LUKS defaults into role 2026-01-02 11:23:31 +01:00
0bd85319f6 Remove defaults for required vars 2025-12-28 17:10:00 +01:00
8eed5c04c4 Move global defaults into role defaults 2025-12-28 16:47:53 +01:00
e7c5166128 Normalize user-facing defaults 2025-12-28 16:41:11 +01:00
cf179b0d16 Normalize LUKS boot layout and partitioning defaults 2025-12-28 16:00:49 +01:00
f4b4a669ba Update Fedora to 43 2025-12-28 04:04:27 +01:00
5288167825 Restore Debian ESP mount layout 2025-12-28 02:24:33 +01:00
7cf0dabc3c Fix Debian initramfs regeneration 2025-12-28 01:54:14 +01:00
34d70c0edc Ensure initramfs-tools for Debian/Ubuntu 2025-12-28 01:29:26 +01:00
73b42f29cb Enable GRUB cryptodisk defaults 2025-12-28 00:46:09 +01:00
b0d9adcf13 Fix bootstrap package list rendering 2025-12-28 00:12:37 +01:00
cbc88c8d03 Condition LUKS and guest tools in bootstrap vars 2025-12-27 23:52:06 +01:00
4705db7fe2 Fix Debian EFI mount layout 2025-12-27 23:49:21 +01:00
1d8d4cc4fd Docs, examples, and tooling 2025-12-27 23:07:47 +01:00
3f0408e271 CIS role split and permission safety 2025-12-27 22:27:26 +01:00
6e30bbb4ff Cleanup refactor and libvirt removal tooling 2025-12-27 21:44:33 +01:00
5914d216ce Virtualization TPM2 and cloud-init fixes 2025-12-27 20:19:11 +01:00
cc8b95463a Partitioning idempotency and filesystem tasks 2025-12-26 23:31:54 +01:00
7323781046 LUKS enrollment and RHEL cmdline/BLS 2025-12-26 22:09:08 +01:00
eba93f90b7 Configuration role refactor and network template 2025-12-26 20:38:42 +01:00
2873c8f81a Split bootstrap by OS 2025-12-25 22:12:19 +01:00
c353be967a Playbook flow and environment prep 2025-12-25 20:47:37 +01:00
259604470f Add Debian 13 (Trixie) support 2025-08-11 21:37:25 +02:00
b2f812823a Update doc to Fedora 42 2025-07-07 15:24:17 +02:00
774f9529b1 Fix rhel10 variable assertion 2025-07-06 04:36:55 +02:00
5d7778c13e use proper datacenter variable 2025-07-06 04:34:16 +02:00
caab1a8690 Update Fedora to 42 2025-07-06 04:28:59 +02:00
0989849163 Use the proper property name 2025-06-24 16:57:18 +02:00
698ffc61f1 Fix VM state after cleanup 2025-06-24 16:54:57 +02:00
d106111f15 use proper filename for role variables 2025-06-17 06:34:39 +02:00
69422a6f64 Update ubuntu to plucky release 2025-06-17 03:57:58 +02:00
93dae69781 Add rhel10 support 2025-06-17 03:13:30 +02:00
f17bdfa528 Add ncurses-term package to ubuntu for more legacy terminal descriptors 2025-05-30 09:48:55 +02:00
e036761c9a Add ncurses-term package for legacy ssh client (terminal descriptors) 2025-05-30 09:14:21 +02:00
da7f22edbe Add vm_dns_search to hostname if set 2025-05-26 14:37:28 +02:00
bafab61a37 Improve SSH CIS hardening 2025-05-04 01:41:00 +02:00
9ba38c9d74 Fix Typo 2025-04-29 20:30:02 +02:00
9f4f147b1c Improve Arch packages + Disable swap before unmounting 2025-04-29 20:28:55 +02:00
b5adfb271f Document vmware_ssh variable 2025-03-25 13:13:06 +01:00
1eaa192eaa Fix vm creation when no rhel_iso for vmware 2025-02-20 16:00:39 +01:00
e1556caccd Increase max home size to 20GB 2025-02-18 21:39:58 +01:00
cb2f7b3e93 Add guest_id since its necessary 2025-02-17 21:38:56 +01:00
b23eb9db28 Implement VMware annotation 2025-02-17 21:17:18 +01:00
cc8f5c6675 Improve Partition calculation algorithm 2025-02-17 20:43:45 +01:00
8001fe2874 Add DNS Search option 2025-02-10 15:16:15 +01:00
4c4a075560 Update README regarding SELinux 2025-02-07 20:50:20 +01:00
8882160fc4 dont fail if selinux is undefined 2025-02-07 20:47:30 +01:00
cfcf1d6107 Remove motd files for rhel 2025-02-05 17:14:17 +01:00
52af252662 Enable option to disable selinux for all osses 2025-02-05 01:41:10 +01:00
4ac6cf540e Include Standard package group for RHEL systems 2025-02-05 00:02:37 +01:00
f4ca2ca34f Make sure Volumes are safely unmounted before reboot 2025-01-22 12:34:00 +01:00
893f5995ab Fix CIS applienc for RHEL8 2025-01-21 22:34:01 +01:00
96929a260c Update package name to match correctly 2025-01-21 22:02:43 +01:00
c802d9b30e Make sure the VM truly starts 2025-01-21 21:35:47 +01:00
17f2a1a93e Do not check if VM is back on vmware with cis activated, it will fail 2025-01-21 21:30:56 +01:00
229395211c Add banner 2025-01-21 20:16:05 +01:00
c84ddd70db Add ssh key survey 2025-01-21 20:00:18 +01:00
97f91f5d11 Add missing variable 2025-01-21 19:58:07 +01:00
febd87919f CIS Adjustments 2025-01-21 19:55:36 +01:00
bf818304ef Fix variable distribution 2025-01-21 17:43:18 +01:00
758213e1ec Make Network Assignment more reliable 2025-01-21 16:59:56 +01:00
5e8d9ff29c Add nms default 2025-01-17 00:50:26 +01:00
e13db88768 Remove nms from ip since already addition already done internaly 2025-01-17 00:45:42 +01:00
39fdefc324 Do not reboot localhost! 2025-01-17 00:38:35 +01:00
b5ea94bdf3 Don't fail proxmox install if rhel_iso is not defined 2025-01-17 00:07:58 +01:00
f7f88226a9 use 24 netmask as default if not set 2025-01-17 00:03:38 +01:00
48949cc9e3 Add extra utils 2025-01-14 21:14:40 +01:00
bfa1be86d1 Set correct IP NetworkMask if defined 2025-01-14 16:08:10 +01:00
fe5c182f76 Fix typo 2025-01-14 15:03:06 +01:00
6328d40d70 Dont fail if vmware_ssh is not defined 2025-01-14 14:58:58 +01:00
83fff50d89 Add dig via bind-utils for rhel 2024-12-03 16:42:47 +01:00
dd6aff8aa1 RHEL add python package 2024-12-03 13:31:31 +01:00
530d224fd0 Do not hardcode macaddress which makes vm cloning harder 2024-12-02 18:08:48 +01:00
c81a7f1e96 Use RHEL nameing for yum repo file 2024-11-12 14:14:09 +01:00
a03f00f28b Fix DNS issue 2024-11-11 17:44:52 +01:00
f7c6c9198f Adjust never libvirt loaders 2024-11-11 17:26:37 +01:00
f5c09571c0 Add some extra packages and vi mode for bash 2024-11-05 03:36:15 +01:00
88d77cf9a6 Add final check if the VM is up and running after reboot 2024-11-01 23:58:52 +01:00
bd3f3b0478 Improve the root lv size calculations, still not perfect on bigger disk 2024-10-31 20:07:40 +01:00
a6da314d3b Preper Shutdown so VMware does not corrupt the installation 2024-10-31 18:27:31 +01:00
70bd67f7c4 improve logical volume size calculation 2024-10-31 17:32:27 +01:00
0f729b4e8a remove zram from debian11 since no support 2024-10-31 16:00:44 +01:00
99499a2f45 remove zram for rhel8 since no support 2024-10-31 15:56:42 +01:00
fe08896ed4 dont use sudo for umount 2024-10-31 15:35:22 +01:00
1d3c305688 Add umount for non RHEL systems 2024-10-31 14:23:55 +01:00
26cfbb9ce3 Fix ubuntu install issue 2024-10-31 05:56:20 +01:00
e5d4886246 Add SWAP support 2024-10-31 05:46:33 +01:00
658287c159 Add zram-generator config 2024-10-31 02:18:55 +01:00
b5f46bc812 add zram-generator package 2024-10-31 02:10:21 +01:00
64abe4daa5 Add swap optimalisations 2024-10-31 02:05:11 +01:00
ed0be16f61 Make root LV size dynamic based on VM disk size 2024-10-31 01:29:48 +01:00
d47296a918 improve VMware cleanup 2024-10-31 01:12:51 +01:00
842a68ab36 Fix riski shell pipe 2024-10-31 00:43:49 +01:00
da8480a0c9 Remove Cloud-init package which can cause issues with NetworkManager on 2024-10-31 00:41:38 +01:00
90 changed files with 6175 additions and 1448 deletions

4
.ansible-lint Normal file
View File

@@ -0,0 +1,4 @@
skip_list:
- run-once
exclude_paths:
- roles/global_defaults/

471
README.md
View File

@@ -1,111 +1,406 @@
# Ansible-Bootstrap # Ansible Bootstrap
An Ansible playbook for automating system bootstrap processes in an Infrastructure-as-Code manner, utilizing ArchISO as the foundational tool. An Ansible playbook for automating Linux system bootstrap in an Infrastructure-as-Code manner. It uses the Arch Linux ISO as a foundational tool to provide an efficient and systematic method for the automatic deployment of a variety of Linux distributions on designated target systems, ensuring a standardized setup across different platforms.
# Info Most roles are adaptable for use with systems beyond Arch Linux, requiring only that the target system can install the necessary package manager (e.g. `dnf` for RHEL-based systems). A replacement for the `arch-chroot` command may also be required; set `system.features.chroot.tool` accordingly.
Most of the roles are adaptable for use with systems beyond ArchLinux, requiring only that the target system can install a necessary package manager, such as `dnf` for RHEL-based systems. Additionally, a replacement for the `arch-chroot` command may be required for these systems.
**NOTE**:
- For RHEL 8 and RHEL 9, repository access requires the `rhel_iso` variable. This variable specifies a local ISO or proxy repository.
- RHEL systems do not support `btrfs`. Use `ext4` or `xfs` as alternatives.
- For RHEL 8, `xfs` may cause installation issues; `ext4` is recommended.
# Supported Distributions
This playbook supports multiple Linux distributions with specific versions tailored to each. Below is a list of supported distributions:
| `os` | Distribution |
|------------|------------------------------------|
| archlinux | ArchLinux (Latest rolling release) |
| almalinux | AlmaLinux 9.x |
| debian11 | Debian 11 (Bullseye) |
| debian12 | Debian 12 (Bookworm) |
| fedora | Fedora 41 |
| rhel8 | Red Hat Enterprise Linux 8 |
| rhel9 | Red Hat Enterprise Linux 9 |
| rocky | Rocky Linux 9.x |
| ubuntu | Ubuntu 24.10 (Oracular Oriole) |
| ubuntu-lts | Ubuntu 24.04 LTS (Noble Numbat) |
# Documentation
## Table of Contents ## Table of Contents
1. [Overview](#1-overview) 1. [Supported Platforms](#1-supported-platforms)
2. [Global Variables](#2-global-variables) 2. [Compatibility Notes](#2-compatibility-notes)
3. [Inventory Variables](#3-inventory-variables) 3. [Configuration Model](#3-configuration-model)
4. [How to Use the Playbook](#4-how-to-use-the-playbook) 4. [Variable Reference](#4-variable-reference)
- 4.1 [Prerequisites](#41-prerequisites) - 4.1 [Core Variables](#41-core-variables)
- 4.2 [Running the Playbook](#42-running-the-playbook) - 4.2 [`system` Dictionary](#42-system-dictionary)
- 4.3 [Example Usage](#43-example-usage) - 4.3 [`hypervisor` Dictionary](#43-hypervisor-dictionary)
- 4.4 [VMware Guest Operations](#44-vmware-guest-operations)
- 4.5 [Multi-Disk Schema](#45-multi-disk-schema)
- 4.6 [Advanced Partitioning Overrides](#46-advanced-partitioning-overrides)
5. [How to Use the Playbook](#5-how-to-use-the-playbook)
- 5.1 [Prerequisites](#51-prerequisites)
- 5.2 [Running the Playbook](#52-running-the-playbook)
- 5.3 [Example Usage](#53-example-usage)
6. [Security](#6-security)
7. [Operational Notes](#7-operational-notes)
8. [Safety](#8-safety)
## 1. Overview ## 1. Supported Platforms
The playbook uses the ArchLinux ISO as a foundational tool to provides an efficient and systematic method for the automatic deployment of a variety of Linux distributions on designated target systems. It ensures a standardized setup across different platforms, equipping each system with the essential configurations and software necessary for its designated role. ### Distributions
## 2. Global Variables | `system.os` | Distribution | `system.version` |
| ------------ | ------------------------ | ------------------------------- |
| `almalinux` | AlmaLinux | `8`, `9`, `10` |
| `alpine` | Alpine Linux | latest (rolling) |
| `archlinux` | Arch Linux | latest (rolling) |
| `debian` | Debian | `10`, `11`, `12`, `13`, `unstable` |
| `fedora` | Fedora | `40`, `41`, `42`, `43` |
| `opensuse` | openSUSE Tumbleweed | latest (rolling) |
| `rhel` | Red Hat Enterprise Linux | `8`, `9`, `10` |
| `rocky` | Rocky Linux | `8`, `9`, `10` |
| `ubuntu` | Ubuntu | latest |
| `ubuntu-lts` | Ubuntu LTS | latest |
| `void` | Void Linux | latest (rolling) |
Global variables apply across your Ansible project and are loaded from `vars.yml` by default. These variables define common settings such as hypervisor connection details and the boot ISO path. They can be overridden by inventory variables for specific hosts or VMs if needed. ### Hypervisors
| Variable | Description | Example Value | | Hypervisor | `hypervisor.type` |
|-----------------------|--------------------------------------------------------------------|-----------------------------------------| | ----------- | ----------------- |
| `boot_iso` | Path to the boot ISO image. | `local-btrfs:iso/archlinux-x86_64.iso` | | libvirt | `libvirt` |
| `rhel_iso` | Path to the RHEL ISO file, required for RHEL 8 and RHEL 9. |`local-btrfs:iso/rhel-9.4-x86_64-dvd.iso`| | Proxmox VE | `proxmox` |
| `hypervisor` | Type of hypervisor. | `libvirt`, `proxmox`, `vmware`, `none` | | VMware | `vmware` |
| `hypervisor_cluster` | Name of the hypervisor cluster. | `default-cluster` | | Xen | `xen` |
| `hypervisor_node` | Hypervisor node name. | `node01` | | Bare metal | `none` |
| `hypervisor_password` | Password for hypervisor authentication. | `123456` |
| `hypervisor_storage` | Storage identifier for VM disks. | `local-btrfs` |
| `hypervisor_url` | URL/IP address for the hypervisor interface. | `192.168.0.2` |
| `hypervisor_username` | Username for hypervisor authentication. | `root@pam` |
| `install_drive` | Drive where the system will be installed. | `/dev/sda` |
| `install_type` | Type of installation. | `virtual`, `physical` |
| `vlan_name` (optional)| VLAN for the VM's network interface. | `vlan100` |
To protect sensitive information, such as passwords, API keys, and other confidential variables (e.g., `hypervisor_password`), **it is recommended to use Ansible Vault**. ## 2. Compatibility Notes
## 3. Inventory Variables - `rhel_iso` is required for `system.os: rhel`.
- RHEL installs should use `system.filesystem: ext4` or `system.filesystem: xfs` (not `btrfs`).
- For RHEL 8 specifically, prefer `ext4` over `xfs` if you hit installer/filesystem edge cases.
- `custom_iso: true` skips ArchISO validation and pacman preparation; your installer image must already provide required tooling.
- On non-Arch installers, set `system.features.chroot.tool` (`arch-chroot`, `chroot`, or `systemd-nspawn`) explicitly as needed.
Inventory variables are defined for individual hosts or VMs in the inventory file, allowing customization of settings such as the operating system, filesystem, and compliance with CIS benchmarks. These variables can be set globally and overridden for specific hosts or VMs. ## 3. Configuration Model
| Variable | Description | Example Value | The project uses two dict-based variables:
|-------------------------|-----------------------------------------------------------------------------------|----------------------------------------------------|
| `cis` (optional) | Adjusts the installation to be CIS level 3 conformant. | `true`, `false` |
| `filesystem` | Filesystem type for the VM's primary storage. | `btrfs`, `ext4`, `xfs` |
| `hostname` | The hostname assigned to the virtual machine or system. | `vm01` |
| `os` | Operating system to be installed on the VM. | `archlinux`, `almalinux`, `debian11`, `debian12`, `fedora`, `rhel8`, `rhel9`, `rocky`, `ubuntu`, `ubuntu-lts` |
| `root_password` | Root password for the VM or system, used for initial setup or secure access. | `SecurePass123` |
| `user_name` | Username for a user account within the VM, often used with cloud-init. | `adminuser` |
| `user_password` | Password for the user account within the VM. | `UserPass123` |
| `vm_ballo` (optional) | Ballooning memory size for the VM, used to adjust memory allocation dynamically. | `2048` |
| `vm_cpus` | Number of CPU cores assigned to the virtual machine. | `4` |
| `vm_dns` | DNS server IP address(es) for the virtual machine's network configuration. | `1.0.0.1`, `1.1.1.1` |
| `vm_gw` | Default gateway IP address for the virtual machine's network configuration. | `192.168.0.1` |
| `vm_id` | Unique identifier for the virtual machine. | `101` |
| `vm_ip` | IP address assigned to the virtual machine. | `192.168.0.10` |
| `vm_nm` (optional) | IP address netmask assigned to the virtual machine. | `255.255.255.0` |
| `vm_nms` (optional) | IP address netmask assigned to the virtual machine. | `24` |
| `vm_memory` | Amount of memory (in MB) allocated to the virtual machine. | `2048` |
| `vm_nif` | Network interface type or identifier for the VM's network connection. | `vmbr0` |
| `vm_path (optional)` | Path or folder where the VM configuration or related files will be stored. | `/var/lib/libvirt/images/` |
| `vm_size` | Disk size allocated for the VM's primary storage (in GB). | `20` |
## 4. How to Use the Playbook - `system` for host/runtime/install configuration
- `hypervisor` for virtualization backend configuration
### 4.1 Prerequisites These are normal Ansible variables and belong in host/group vars. You can define them in inventory host entries, `group_vars/*`, or `host_vars/*`. Dictionary variables are merged across scopes (`group_vars` -> `host_vars`) by project config (`hash_behaviour = merge`), so you can set shared values like `system.filesystem` once in group vars and override only host-specific keys per host.
Before running the playbook, ensure you have Ansible installed and configured correctly, and your inventory file is set up with the target systems defined. ### Variable Placement
### 4.2 Running the Playbook | Location | Scope | Typical use |
| -------------------------- | ----------- | ------------------------------------------------------------ |
| `group_vars/all.yml` | All hosts | Shared defaults like `hypervisor`, `system.filesystem`, `boot_iso` |
| `group_vars/<group>.yml` | Group | Environment or role-specific defaults |
| `host_vars/<host>.yml` | Single host | Host-specific overrides |
| Inventory inline host vars | Single host | Inline definitions for quick setup |
Execute the playbook using the `ansible-playbook` command, ensuring that all necessary variables are defined, typically by specifying a `vars.yml` file containing the required configurations. ### Example Inventory
### 4.3 Example Usage ```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: CHANGE_ME
host: pve01
storage: local-lvm
An effective way to use the playbook involves defining all necessary configurations within a `vars.yml` file. This file should include all relevant global variables tailored to your specific deployment requirements. Additionally, you should prepare an inventory file (`inventory.yml`) that lists all the hosts along with any specific inventory variables they might need. Then, you can run the playbook as follows: children:
bootstrap:
```bash hosts:
ansible-playbook -i inventory.yml -e @vars.yml main.yml 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
balloon: 0
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
user:
name: ops
password: CHANGE_ME
key: "ssh-ed25519 AAAA..."
root:
password: CHANGE_ME
luks:
enabled: true
passphrase: CHANGE_ME
auto: true
method: tpm2
tpm2:
pcrs: "7"
features:
firewall:
enabled: true
backend: firewalld
toolkit: nftables
``` ```
This command prompts Ansible to execute the `main.yml` playbook, applying configurations defined in both `vars.yml` and the inventory file. ## 4. Variable Reference
### 4.1 Core Variables
These top-level variables sit outside the `system`/`hypervisor` dictionaries.
| Variable | Type | Description |
| ------------ | ------ | ------------------------------------------------ |
| `boot_iso` | string | Path to the boot ISO image (required for virtual installs). |
| `rhel_iso` | string | Path to the RHEL ISO (required when `system.os: rhel`). |
| `custom_iso` | bool | Skip ArchISO validation and pacman setup. Default `false`. |
### 4.2 `system` Dictionary
Top-level host install/runtime settings. Use these keys under `system`.
| Key | Type | Default | Description |
| ------------ | ---------- | -------------------- | ---------------------------------------- |
| `type` | string | `virtual` | `virtual` or `physical` |
| `os` | string | empty | Target distribution (see [table](#distributions)) |
| `version` | string | empty | Version selector for distro families |
| `filesystem` | string | empty | `btrfs`, `ext4`, or `xfs` |
| `name` | string | inventory hostname | Final hostname |
| `id` | int/string | empty | VMID (required for Proxmox) |
| `cpus` | int | `0` | vCPU count |
| `memory` | int | `0` | Memory in MiB |
| `balloon` | int | `0` | Balloon memory in MiB |
| `path` | string | empty | Hypervisor folder/path (libvirt/vmware) |
| `packages` | list | `[]` | Additional packages installed post-reboot |
| `network` | dict | see below | Network configuration |
| `disks` | list | `[]` | Disk layout (see [Multi-Disk Schema](#45-multi-disk-schema)) |
| `user` | dict | see below | User account settings |
| `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 | empty | Hypervisor network/bridge name |
| `vlan` | string/int | empty | VLAN tag |
| `ip` | string | empty | Static IP (omit for DHCP) |
| `prefix` | int | empty | CIDR prefix for static IP |
| `gateway` | string | empty | Default gateway (static only) |
| `dns.servers` | list/string | `[]` | DNS resolvers; comma-separated string is normalized |
| `dns.search` | list/string | `[]` | Search domains; comma-separated string is normalized |
#### `system.user`
Credentials are prompted interactively by default via `vars_prompt` in `main.yml`, but can be supplied via inventory, vars files, or `-e` for non-interactive runs.
| Key | Type | Default | Description |
| ---------- | ------ | ------- | ------------------------------------- |
| `name` | string | empty | Username created on target |
| `password` | string | empty | User password (also used for sudo) |
| `key` | string | empty | SSH public key for `authorized_keys` |
#### `system.root`
| Key | Type | Default | Description |
| ---------- | ------ | ------- | -------------- |
| `password` | string | empty | Root password |
#### `system.luks`
LUKS container, unlock, and initramfs-related settings.
| Key | Type | Default | Allowed | Description |
| ------------ | ------ | ------------------ | -------------------------- | ------------------------------------------ |
| `enabled` | bool | `false` | `true`/`false` | Enable encrypted root workflow |
| `passphrase` | string | empty | any string | Passphrase used for format/open/enroll |
| `mapper` | string | `SYSTEM_DECRYPTED` | mapper name | Mapper name under `/dev/mapper` |
| `auto` | bool | `true` | `true`/`false` | Auto-unlock behavior toggle |
| `method` | string | `tpm2` | `tpm2`, `keyfile` | Auto-unlock backend when `auto=true` |
| `keysize` | int | `64` | positive int | Keyfile size (bytes) for keyfile mode |
| `options` | string | `discard,tries=3` | crypttab opts | Additional crypttab/kernel options |
| `type` | string | `luks2` | cryptsetup type | LUKS format type |
| `cipher` | string | `aes-xts-plain64` | cipher name | Cryptsetup cipher |
| `hash` | string | `sha512` | hash name | Cryptsetup hash |
| `iter` | int | `4000` | positive int | PBKDF iteration time (ms) |
| `bits` | int | `512` | positive int | Key size (bits) |
| `pbkdf` | string | `argon2id` | pbkdf name | PBKDF algorithm |
| `urandom` | bool | `true` | `true`/`false` | Use urandom during key generation |
| `verify` | bool | `true` | `true`/`false` | Verify passphrase during format |
#### `system.luks.tpm2`
TPM2-specific policy settings used when `system.luks.method: tpm2`.
| Key | Type | Default | Allowed | Description |
| ------ | ----------- | ------- | --------------------- | --------------------------------------------------- |
| `device` | string | `auto` | `auto` or device path | TPM2 device selector |
| `pcrs` | string/list | empty | PCR expression | PCR binding policy (e.g. `"7"` or `"0+7"`) |
#### `system.features`
Feature toggles for optional system configuration.
| Key | Type | Default | Allowed | Description |
| ------------------ | ------ | -------------- | ------------------------------------------ | ---------------------------------- |
| `cis.enabled` | bool | `false` | `true`/`false` | Enable CIS hardening role |
| `selinux.enabled` | bool | `true` | `true`/`false` | SELinux management |
| `firewall.enabled` | bool | `true` | `true`/`false` | Enable firewall role actions |
| `firewall.backend` | string | `firewalld` | `firewalld`, `ufw` | Firewall service backend |
| `firewall.toolkit` | string | `nftables` | `nftables`, `iptables` | Packet filtering toolkit |
| `ssh.enabled` | bool | `true` | `true`/`false` | SSH service/package management |
| `zstd.enabled` | bool | `true` | `true`/`false` | zstd related tuning |
| `swap.enabled` | bool | `true` | `true`/`false` | Swap setup toggle |
| `banner.motd` | bool | `false` | `true`/`false` | MOTD banner management |
| `banner.sudo` | bool | `true` | `true`/`false` | Sudo banner management |
| `chroot.tool` | string | `arch-chroot` | `arch-chroot`, `chroot`, `systemd-nspawn` | Chroot wrapper command |
### 4.3 `hypervisor` Dictionary
| Key | Type | Description |
| ------------ | ------ | -------------------------------------------------------- |
| `type` | string | `libvirt`, `proxmox`, `vmware`, `xen`, or `none` |
| `url` | string | Proxmox/VMware API host |
| `username` | string | API username |
| `password` | string | API password |
| `host` | string | Proxmox node name |
| `storage` | string | Proxmox/VMware storage identifier |
| `datacenter` | string | VMware datacenter |
| `cluster` | string | VMware cluster |
| `certs` | bool | TLS certificate validation for VMware |
| `ssh` | bool | VMware: enable SSH on guest and switch connection to SSH |
### 4.4 VMware Guest Operations
When `hypervisor.type: vmware` uses the `vmware_tools` connection, these Ansible connection variables are required.
| Variable | Description |
| ------------------------------- | -------------------------------------------------- |
| `ansible_vmware_tools_user` | Guest OS username for guest operations |
| `ansible_vmware_tools_password` | Guest OS password for guest operations |
| `ansible_vmware_guest_path` | VM inventory path (`/datacenter/vm/folder/name`) |
| `ansible_vmware_host` | vCenter/ESXi hostname |
| `ansible_vmware_user` | vCenter/ESXi API username |
| `ansible_vmware_password` | vCenter/ESXi API password |
| `ansible_vmware_validate_certs` | Enable/disable TLS certificate validation |
### 4.5 Multi-Disk Schema
`system.disks[0]` is always the OS disk. Additional entries define data disks.
| Key | Type | Description |
| ------------- | ------ | ---------------------------------------------------- |
| `size` | number | Disk size in GB (required for virtual installs) |
| `device` | string | Explicit block device (required for physical data disks) |
| `mount.path` | string | Mount point (for additional disks) |
| `mount.fstype`| string | `btrfs`, `ext4`, or `xfs` |
| `mount.label` | string | Optional filesystem label |
| `mount.opts` | string | Mount options (default: `defaults`) |
Virtual install example:
```yaml
system:
disks:
- size: 80
- size: 200
mount:
path: /data
fstype: xfs
label: DATA
opts: defaults,noatime
- size: 300
mount:
path: /backup
fstype: ext4
```
Physical install example (device paths required):
```yaml
system:
type: physical
disks:
- device: /dev/sda
size: 120
- device: /dev/sdb
size: 500
mount:
path: /data
fstype: ext4
```
### 4.6 Advanced Partitioning Overrides
Use these only when you need to override the default partition layout logic.
| Variable | Description | Default |
| ------------------------------ | ------------------------------------------------- | ------------ |
| `partitioning_efi_size_mib` | EFI system partition size in MiB | `512` |
| `partitioning_boot_size_mib` | Separate `/boot` size in MiB (when used) | `1024` |
| `partitioning_separate_boot` | Force a separate `/boot` partition | auto-derived |
| `partitioning_boot_fs_fstype` | Filesystem for `/boot` when separate | auto-derived |
| `partitioning_use_full_disk` | Consume remaining VG space for root LV | `true` |
## 5. How to Use the Playbook
### 5.1 Prerequisites
- Ansible installed on the control machine.
- Inventory file with target systems defined and variables configured.
- Disposable/non-production targets (the playbook enforces production-safety checks).
### 5.2 Running the Playbook
Execute the playbook using `ansible-playbook`, ensuring that all necessary variables are defined either in the inventory, in a vars file, or passed via `-e`. Credentials (`root_password`, `user_name`, `user_password`, `user_public_key`) are prompted interactively unless supplied through inventory or extra vars.
```bash
ansible-playbook -i inventory.yml main.yml
ansible-playbook -i inventory.yml main.yml -e @vars_example.yml
```
### 5.3 Example Usage
Use the bundled example files as starting points for new inventories:
- `inventory_example.yml` -- Proxmox virtual setup
- `inventory_libvirt_example.yml` -- libvirt virtual setup
- `inventory_baremetal_example.yml` -- bare-metal physical setup
- `vars_example.yml` -- shared variable overrides
```bash
# Proxmox example
ansible-playbook -i inventory_example.yml main.yml
# libvirt example
ansible-playbook -i inventory_libvirt_example.yml main.yml
# Custom inventory with separate vars file
ansible-playbook -i inventory.yml main.yml -e @vars_example.yml
```
## 6. Security
To protect sensitive information such as passwords, API keys, and other confidential variables (e.g. `hypervisor.password`, `system.luks.passphrase`), **use Ansible Vault** instead of plaintext inventory files.
## 7. Operational Notes
- For virtual installs, `system.cpus`, `system.memory`, and `system.disks[0].size` are required and validated.
- For physical installs, sizing is derived from the detected install drive; set installer access (`ansible_user`/`ansible_password`) when the installer environment differs from the prompted user credentials.
- `system.network.dns.servers` and `system.network.dns.search` accept either YAML lists or comma-separated strings.
- `hypervisor.type` selects backend-specific provisioning and cleanup behavior.
- Guest tools are selected automatically by hypervisor: `qemu-guest-agent` for `libvirt`/`proxmox`, `open-vm-tools` for `vmware`.
- With `system.luks.method: tpm2` on virtual installs, the virtualization role enables a TPM2 device where supported (libvirt/proxmox/vmware).
- With LUKS enabled on non-Arch targets, provisioning uses an ESP (512 MiB), a separate `/boot` (1 GiB), and the encrypted root; adjust sizes via `partitioning_efi_size_mib` and `partitioning_boot_size_mib` if needed.
- For VMware, `hypervisor.ssh: true` enables SSH on the guest and switches the connection to SSH for the remaining tasks.
- Molecule is scaffolded with a delegated driver and a no-op converge for lint-only validation.
## 8. Safety
This playbook intentionally aborts if it detects a non-live/production target. It also refuses to touch pre-existing VMs and only cleans up VMs created in the current run.
Always run lint after changes:
```bash
ansible-lint
```

2
ansible.cfg Normal file
View File

@@ -0,0 +1,2 @@
[defaults]
hash_behaviour = merge

View File

@@ -0,0 +1,9 @@
---
collections:
- name: ansible.posix
- name: community.general
- name: community.libvirt
- name: community.crypto
- name: community.proxmox
- name: community.vmware
- name: vmware.vmware

View File

@@ -0,0 +1,20 @@
---
all:
vars:
hypervisor:
type: "none"
system:
filesystem: "ext4"
hosts:
baremetal01.example.com:
ansible_host: 10.0.0.162
ansible_user: root
ansible_password: "1234"
ansible_become_password: "1234"
system:
type: "physical"
os: "archlinux"
name: "baremetal01.example.com"
disks:
- device: "/dev/sda"
size: 120

View File

@@ -1,29 +0,0 @@
[promox-kvm]
192.168.122.10
192.168.122.11
[promox-kvm:vars]
vm_gw=192.168.122.1
vm_dns=1.1.1.1
[192.168.122.10]
hostname=proxy
vm_id=300
os=archlinux
filesystem=btrfs
vm_memory=2048
vm_ballo=1024
vm_cpus=2
vm_size=5
vm_nif=vmbr1
[192.168.122.11]
hostname=database
vm_id=101
os=archlinux
filesystem=btrfs
vm_memory=6144
vm_ballo=3072
vm_cpus=4
vm_size=40
vm_nif=vmbr1

View File

@@ -1,33 +1,129 @@
---
all: all:
vars: vars:
hypervisor: 'proxmox' hypervisor:
install_drive: '/dev/sda' type: "proxmox"
cis: true url: "pve01.example.com"
username: "root@pam"
password: "CHANGE_ME"
host: "pve01"
storage: "local-lvm"
boot_iso: "local:iso/archlinux-x86_64.iso"
children: children:
promox-kvm: proxmox:
hosts: hosts:
192.168.122.10: app01.example.com:
hostname: proxy ansible_host: 10.0.0.10
vm_id: 100 system:
os: archlinux filesystem: "btrfs"
filesystem: btrfs type: "virtual"
vm_memory: "2048" os: "archlinux"
vm_ballo: "1024" name: "app01.example.com"
vm_cpus: "2" id: 100
vm_size: "5" cpus: 2
vm_nif: vmbr1 memory: 4096
vm_gw: 192.168.122.1 balloon: 0
vm_dns: 1.1.1.1 network:
192.168.122.11: bridge: "vmbr0"
hostname: database ip: 10.0.0.10
vm_id: 101 prefix: 24
os: rhel9 gateway: 10.0.0.1
filesystem: xfs dns:
vm_memory: "6144" servers:
vm_ballo: "3072" - 1.1.1.1
vm_cpus: "4" - 1.0.0.1
vm_size: "40" search:
vm_nif: vmbr1 - example.com
vm_gw: 192.168.122.1 disks:
vm_dns: 1.1.1.1 - size: 40
rhel_iso: "local-btrfs:iso/rhel-9.4-x86_64-dvd.iso" - size: 80
mount:
path: /data
fstype: xfs
label: DATA
opts: defaults
user:
name: "ops"
password: "CHANGE_ME"
key: "ssh-ed25519 AAAA..."
root:
password: "CHANGE_ME"
packages:
- jq
- tmux
features:
cis:
enabled: false
selinux:
enabled: true
firewall:
enabled: true
backend: "firewalld"
toolkit: "nftables"
ssh:
enabled: true
zstd:
enabled: true
swap:
enabled: true
banner:
motd: true
sudo: true
chroot:
tool: "arch-chroot"
db01.example.com:
ansible_host: 10.0.0.11
rhel_iso: "local:iso/rhel-9.4-x86_64-dvd.iso"
system:
filesystem: "xfs"
type: "virtual"
os: "rhel"
version: "9"
name: "db01.example.com"
id: 101
cpus: 4
memory: 8192
network:
bridge: "vmbr0"
ip: 10.0.0.11
prefix: 24
gateway: 10.0.0.1
dns:
servers: "1.1.1.1,1.0.0.1"
disks:
- size: 80
- size: 200
mount:
path: /srv/data
fstype: ext4
user:
name: "dbadmin"
password: "CHANGE_ME"
key: "ssh-ed25519 AAAA..."
root:
password: "CHANGE_ME"
luks:
enabled: true
passphrase: "CHANGE_ME"
method: "keyfile"
keysize: 128
features:
cis:
enabled: true
selinux:
enabled: false
firewall:
enabled: false
backend: "firewalld"
toolkit: "nftables"
ssh:
enabled: true
zstd:
enabled: true
swap:
enabled: true
banner:
motd: true
sudo: true
chroot:
tool: "arch-chroot"

View File

@@ -0,0 +1,129 @@
---
all:
vars:
hypervisor:
type: "libvirt"
url: "localhost"
username: ""
password: ""
host: ""
storage: "default"
boot_iso: "/var/lib/libvirt/images/archlinux-x86_64.iso"
children:
libvirt:
hosts:
web01.local:
ansible_host: 192.168.122.20
system:
filesystem: "ext4"
type: "virtual"
os: "debian"
version: "12"
name: "web01.local"
cpus: 2
memory: 2048
network:
bridge: "default"
ip: 192.168.122.20
prefix: 24
gateway: 192.168.122.1
dns:
servers:
- 1.1.1.1
search:
- lab.local
path: "/var/lib/libvirt/images"
disks:
- size: 30
- size: 80
mount:
path: /var/www
fstype: xfs
user:
name: "web"
password: "CHANGE_ME"
key: "ssh-ed25519 AAAA..."
root:
password: "CHANGE_ME"
packages:
- nginx
- curl
features:
firewall:
enabled: true
backend: "ufw"
toolkit: "nftables"
db01.local:
ansible_host: 192.168.122.21
rhel_iso: "/var/lib/libvirt/images/rhel-9.4-x86_64-dvd.iso"
system:
filesystem: "xfs"
type: "virtual"
os: "rhel"
version: "9"
name: "db01.local"
cpus: 4
memory: 4096
network:
bridge: "default"
ip: 192.168.122.21
prefix: 24
gateway: 192.168.122.1
dns:
servers:
- 9.9.9.9
search:
- example.com
disks:
- size: 60
- size: 120
mount:
path: /data
fstype: ext4
user:
name: "db"
password: "CHANGE_ME"
key: "ssh-ed25519 AAAA..."
root:
password: "CHANGE_ME"
luks:
enabled: true
passphrase: "CHANGE_ME"
method: "keyfile"
features:
firewall:
enabled: false
backend: "firewalld"
toolkit: "nftables"
compute01.local:
ansible_host: 192.168.122.22
system:
filesystem: "btrfs"
type: "virtual"
os: "fedora"
version: "41"
name: "compute01.local"
cpus: 8
memory: 8192
network:
bridge: "default"
ip: 192.168.122.22
prefix: 24
gateway: 192.168.122.1
dns:
servers: "1.1.1.1,1.0.0.1"
disks:
- size: 80
- size: 200
mount:
path: /data
fstype: btrfs
user:
name: "compute"
password: "CHANGE_ME"
key: "ssh-ed25519 AAAA..."
root:
password: "CHANGE_ME"
features:
cis:
enabled: true

127
main.yml
View File

@@ -1,7 +1,7 @@
--- ---
- name: Create and configure VMs - name: Create and configure VMs
hosts: all hosts: all
strategy: free strategy: free # noqa: run-once[play]
gather_facts: false gather_facts: false
become: true become: true
vars_prompt: vars_prompt:
@@ -10,6 +10,11 @@
What is your username? What is your username?
private: false private: false
- name: user_public_key
prompt: |
What is your ssh key?
private: false
- name: user_password - name: user_password
prompt: | prompt: |
What is your password? What is your password?
@@ -19,69 +24,107 @@
prompt: | prompt: |
What is your root password? What is your root password?
confirm: true confirm: true
vars_files: vars.yml
pre_tasks: pre_tasks:
- name: Set ansible_python_interpreter - name: Apply prompted authentication values to system input
when: os | lower in ["almalinux", "rhel9", "rhel8", "rocky"] vars:
system_input: "{{ system | default({}) }}"
system_user_input: "{{ (system_input.user | default({})) if (system_input.user is mapping) else {} }}"
system_root_input: "{{ (system_input.root | default({})) if (system_input.root is mapping) else {} }}"
prompt_user_name: "{{ user_name | default(system_user_name | default(''), true) | string }}"
prompt_user_key: "{{ user_public_key | default(user_key | default(system_user_key | default(''), true), true) | string }}"
prompt_user_password: "{{ user_password | default(system_user_password | default(''), true) | string }}"
prompt_root_password: "{{ root_password | default(system_root_password | default(''), true) | string }}"
ansible.builtin.set_fact: ansible.builtin.set_fact:
ansible_python_interpreter: /usr/bin/python3 system: >-
{{
system_input
| combine(
{
'user': {
'name': (
(system_user_input.name | default('') | string | length) > 0
) | ternary(system_user_input.name | string, prompt_user_name),
'key': (
(system_user_input.key | default('') | string | length) > 0
) | ternary(system_user_input.key | string, prompt_user_key),
'password': (
(system_user_input.password | default('') | string | length) > 0
) | ternary(system_user_input.password | string, prompt_user_password)
},
'root': {
'password': (
(system_root_input.password | default('') | string | length) > 0
) | ternary(system_root_input.password | string, prompt_root_password)
}
},
recursive=True
)
}}
changed_when: false
- name: Set SSH Access - name: Load global defaults
when: hypervisor != "vmware" ansible.builtin.import_role:
ansible.builtin.set_fact: name: global_defaults
ansible_user: "{{ user_name }}"
ansible_password: "{{ user_password }}"
ansible_become_password: "{{ user_password }}"
ansible_ssh_extra_args: '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
- name: Validate variables - name: Perform safety checks
ansible.builtin.assert: ansible.builtin.import_role:
that: name: system_check
- hypervisor in ["libvirt", "proxmox", "vmware", "none"]
- filesystem in ["btrfs", "ext4", "xfs"]
- install_drive is defined
- os in ["archlinux", "almalinux", "debian11", "debian12", "fedora", "rhel8", "rhel9", "rocky", "ubuntu", "ubuntu-lts"]
- os not in ["rhel8", "rhel9"] or rhel_iso is defined
- (filesystem == "btrfs" and (vm_size | int) >= 10) or (filesystem != "btrfs" and (vm_size | int) >= 20)
fail_msg: Invalid input specified, please try again.
- name: Set connection
when: hypervisor == "vmware"
ansible.builtin.set_fact:
ansible_connection: vmware_tools
roles: roles:
- role: virtualization - role: virtualization
when: install_type == "virtual" when: system_cfg.type == "virtual"
become: false become: false
vars: vars:
ansible_connection: local ansible_connection: local
- role: environment - role: environment
vars: vars:
ansible_connection: "{{ 'vmware_tools' if hypervisor == 'vmware' else 'ssh' }}" ansible_connection: "{{ 'vmware_tools' if hypervisor_type == 'vmware' else 'ssh' }}"
- role: partitioning - role: partitioning
vars: vars:
boot_partition_suffix: 1 partitioning_boot_partition_suffix: 1
main_partition_suffix: 2 partitioning_main_partition_suffix: 2
- role: bootstrap - role: bootstrap
- role: configuration - role: configuration
- role: cis - role: cis
when: cis | bool when: system_cfg.features.cis.enabled | bool
- role: cleanup - role: cleanup
when: install_type == "virtual" when: system_cfg.type in ["virtual", "physical"]
vars: become: false
ansible_connection: local
tasks: post_tasks:
- name: Reboot system - name: Set post-reboot connection flags
when: hypervisor != "libvirt" ansible.builtin.set_fact:
ansible.builtin.command: reboot post_reboot_can_connect: >-
failed_when: false {{
changed_when: result.rc == 0 (ansible_connection | default('ssh')) != 'ssh'
register: result or ((system_cfg.network.ip | default('') | string | length) > 0)
or (
system_cfg.type == 'physical'
and (ansible_host | default('') | string | length) > 0
)
}}
changed_when: false
- name: Set final SSH credentials for post-reboot tasks
when:
- post_reboot_can_connect | bool
ansible.builtin.set_fact:
ansible_user: "{{ system_cfg.user.name }}"
ansible_password: "{{ system_cfg.user.password }}"
ansible_become_password: "{{ system_cfg.user.password }}"
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
- name: Install post-reboot packages
when:
- post_reboot_can_connect | bool
- system_cfg.packages is defined
- system_cfg.packages | length > 0
ansible.builtin.package:
name: "{{ system_cfg.packages }}"
state: present

View File

@@ -0,0 +1,8 @@
---
- name: Molecule converge placeholder
hosts: all
gather_facts: false
tasks:
- name: Skip destructive provisioning in Molecule
ansible.builtin.debug:
msg: "Molecule scenario is lint-only; run main.yml against disposable hosts."

View File

@@ -0,0 +1,19 @@
---
dependency:
name: galaxy
driver:
name: delegated
platforms:
- name: localhost
provisioner:
name: ansible
playbooks:
converge: converge.yml
inventory:
host_vars:
localhost:
ansible_connection: local
lint:
name: ansible-lint
verifier:
name: ansible

View File

@@ -0,0 +1,9 @@
---
- name: Molecule verify placeholder
hosts: all
gather_facts: false
tasks:
- name: Verify placeholder
ansible.builtin.assert:
that:
- true

View File

@@ -0,0 +1,35 @@
---
- name: Bootstrap AlmaLinux
vars:
bootstrap_almalinux_extra: >-
{{
lookup('vars', bootstrap_var_key)
| reject('equalto', '')
| join(' ')
}}
block:
- name: Install AlmaLinux base system
ansible.builtin.command: >-
dnf --releasever={{ os_version }} --best --repo=baseos --repo=appstream
--installroot=/mnt --setopt=install_weak_deps=False
groupinstall -y core
register: bootstrap_almalinux_base_result
changed_when: bootstrap_almalinux_base_result.rc == 0
- name: Ensure chroot has resolv.conf
ansible.builtin.file:
src: /run/NetworkManager/resolv.conf
dest: /mnt/etc/resolv.conf
state: link
- name: Install extra packages
ansible.builtin.command: >-
{{ chroot_command }} dnf --releasever={{ os_version }} --setopt=install_weak_deps=False
install -y {{ bootstrap_almalinux_extra }}
register: bootstrap_almalinux_extra_result
changed_when: bootstrap_almalinux_extra_result.rc == 0
- name: Reinstall kernel core
ansible.builtin.command: "{{ chroot_command }} dnf reinstall -y kernel-core"
register: bootstrap_almalinux_kernel_result
changed_when: bootstrap_almalinux_kernel_result.rc == 0

View File

@@ -0,0 +1,33 @@
---
- name: Bootstrap Alpine Linux
vars:
bootstrap_alpine_packages: >-
{{
lookup('vars', 'bootstrap_alpine') | reject('equalto', '') | join(' ')
}}
block:
- name: Ensure chroot has resolv.conf
ansible.builtin.file:
src: /run/NetworkManager/resolv.conf
dest: /mnt/etc/resolv.conf
state: link
force: true
- name: Install Alpine Linux packages
ansible.builtin.command: >
apk --root /mnt --no-cache add alpine-base
register: bootstrap_alpine_bootstrap_result
changed_when: bootstrap_alpine_bootstrap_result.rc == 0
- name: Install extra packages
when: bootstrap_alpine_packages | length > 0
ansible.builtin.command: >
apk --root /mnt add {{ bootstrap_alpine_packages }}
register: bootstrap_alpine_extra_result
changed_when: bootstrap_alpine_extra_result.rc == 0
- name: Install bootloader
ansible.builtin.command: >
apk --root /mnt add grub grub-efi efibootmgr
register: bootstrap_alpine_bootloader_result
changed_when: bootstrap_alpine_bootloader_result.rc == 0

View File

@@ -0,0 +1,11 @@
---
- name: Bootstrap ArchLinux
vars:
bootstrap_archlinux_packages: >-
{{
lookup('vars', bootstrap_var_key)
}}
ansible.builtin.command: >-
pacstrap /mnt {{ bootstrap_archlinux_packages | reject('equalto', '') | join(' ') }} --asexplicit
register: bootstrap_result
changed_when: bootstrap_result.rc == 0

View File

@@ -0,0 +1,67 @@
---
- name: Bootstrap Debian System
vars:
bootstrap_debian_release: >-
{{
'buster' if (os_version | string) == '10'
else 'bullseye' if (os_version | string) == '11'
else 'bookworm' if (os_version | string) == '12'
else 'trixie' if (os_version | string) == '13'
else 'sid' if (os_version | string) == 'unstable'
else 'trixie'
}}
bootstrap_debian_package_config: >-
{{
lookup('vars', bootstrap_var_key)
}}
bootstrap_debian_base_packages: >-
{{
bootstrap_debian_package_config.base
| default([])
| reject('equalto', '')
| list
}}
bootstrap_debian_extra_packages: >-
{{
bootstrap_debian_package_config.extra
| default([])
| reject('equalto', '')
| list
}}
bootstrap_debian_base_csv: "{{ bootstrap_debian_base_packages | join(',') }}"
bootstrap_debian_extra_args: >-
{{
bootstrap_debian_extra_packages
| join(' ')
}}
block:
- name: Validate Debian package configuration
ansible.builtin.assert:
that:
- bootstrap_debian_package_config is mapping
- bootstrap_debian_package_config.base is defined
- bootstrap_debian_package_config.base is sequence
- bootstrap_debian_package_config.base is not string
- bootstrap_debian_package_config.extra is defined
- bootstrap_debian_package_config.extra is sequence
- bootstrap_debian_package_config.extra is not string
fail_msg: "bootstrap package definition for {{ bootstrap_var_key }} must be a mapping with base/extra lists."
quiet: true
- name: Install Debian base system
ansible.builtin.command: >-
debootstrap --include={{ bootstrap_debian_base_csv }}
{{ bootstrap_debian_release }} /mnt http://deb.debian.org/debian/
register: bootstrap_debian_base_result
changed_when: bootstrap_debian_base_result.rc == 0
- name: Install extra packages
when: bootstrap_debian_extra_packages | length > 0
ansible.builtin.command: "{{ chroot_command }} apt install -y {{ bootstrap_debian_extra_args }}"
register: bootstrap_debian_extra_result
changed_when: bootstrap_debian_extra_result.rc == 0
- name: Remove unnecessary packages
ansible.builtin.command: "{{ chroot_command }} apt remove -y libcups2 libavahi-common3 libavahi-common-data"
register: bootstrap_debian_remove_result
changed_when: bootstrap_debian_remove_result.rc == 0

View File

@@ -0,0 +1,35 @@
---
- name: Bootstrap Fedora
vars:
bootstrap_fedora_extra: >-
{{
lookup('vars', bootstrap_var_key)
| reject('equalto', '')
| join(' ')
}}
block:
- name: Install Fedora base system
ansible.builtin.command: >-
dnf --releasever={{ os_version }} --best --repo=fedora --repo=fedora-updates
--installroot=/mnt --setopt=install_weak_deps=False
groupinstall -y critical-path-base core
register: bootstrap_fedora_base_result
changed_when: bootstrap_fedora_base_result.rc == 0
- name: Ensure chroot has resolv.conf
ansible.builtin.file:
src: /run/NetworkManager/resolv.conf
dest: /mnt/etc/resolv.conf
state: link
- name: Install extra packages
ansible.builtin.command: >-
{{ chroot_command }} dnf --releasever={{ os_version }} --setopt=install_weak_deps=False
install -y {{ bootstrap_fedora_extra }}
register: bootstrap_fedora_extra_result
changed_when: bootstrap_fedora_extra_result.rc == 0
- name: Reinstall kernel core
ansible.builtin.command: "{{ chroot_command }} dnf reinstall -y kernel-core"
register: bootstrap_fedora_kernel_result
changed_when: bootstrap_fedora_kernel_result.rc == 0

View File

@@ -1,106 +1,45 @@
--- ---
- name: Include Packages
ansible.builtin.include_vars:
file: packages.yml
name: role_packages
- name: Run OS-specific bootstrap process - name: Run OS-specific bootstrap process
vars:
bootstrap_os_key: "{{ (os_resolved | default(os)) | lower }}"
bootstrap_var_key: "{{ 'bootstrap_' + ((os_resolved | default(os)) | lower | replace('-', '_')) }}"
block: block:
- name: Bootstrap ArchLinux - name: Include AlmaLinux bootstrap tasks
when: os | lower == 'archlinux' when: bootstrap_os_key in ['almalinux', 'almalinux8', 'almalinux9', 'almalinux10']
ansible.builtin.command: pacstrap /mnt {{ role_packages.archlinux | join(' ') }} --asexplicit ansible.builtin.include_tasks: almalinux.yml
changed_when: result.rc == 0
register: result
- name: Bootstrap Debian System - name: Include Alpine bootstrap tasks
when: os | lower in ['debian11', 'debian12'] when: bootstrap_os_key == 'alpine'
ansible.builtin.command: "{{ item }}" ansible.builtin.include_tasks: alpine.yml
changed_when: result.rc == 0
register: result
with_items:
- debootstrap --include={{ role_packages[os].base | join(',') }} {{ 'bullseye' if os == 'debian11' else 'bookworm' }}
/mnt http://deb.debian.org/debian/
- arch-chroot /mnt apt install -y {{ role_packages[os].extra | join(' ') }}
- arch-chroot /mnt apt remove -y libcups2 libavahi-common3 libavahi-common-data
- name: Bootstrap Ubuntu System - name: Include ArchLinux bootstrap tasks
when: os | lower in ['ubuntu', 'ubuntu-lts'] when: bootstrap_os_key == 'archlinux'
ansible.builtin.command: "{{ item }}" ansible.builtin.include_tasks: archlinux.yml
changed_when: result.rc == 0
register: result
with_items:
- debootstrap --include={{ role_packages[os].base | join(',') }} {{ 'oracular' if os == 'ubuntu' else 'noble' }}
/mnt http://archive.ubuntu.com/ubuntu/
- ln -sf /run/systemd/resolve/resolv.conf /mnt/etc/resolv.conf
- arch-chroot /mnt sed -i '1s|$| universe|' /etc/apt/sources.list
- arch-chroot /mnt apt update -y
- arch-chroot /mnt apt install -y {{ role_packages[os].extra | join(' ') }}
- name: Bootstrap AlmaLinux 9 - name: Include Debian bootstrap tasks
when: os | lower == 'almalinux' when: bootstrap_os_key in ['debian10', 'debian11', 'debian12', 'debian13', 'debianunstable']
ansible.builtin.command: "{{ item }}" ansible.builtin.include_tasks: debian.yml
changed_when: result.rc == 0
register: result
with_items:
- dnf --releasever=9 --best --repo=alma-baseos --installroot=/mnt --setopt=install_weak_deps=False groupinstall -y base core
- ln -sf /run/systemd/resolve/resolv.conf /mnt/etc/resolv.conf
- arch-chroot /mnt dnf --releasever=9 --setopt=install_weak_deps=False install -y {{ role_packages.almalinux | join(' ') }}
- name: Bootstrap Fedora 41 - name: Include Fedora bootstrap tasks
when: os | lower == 'fedora' when: bootstrap_os_key in ['fedora', 'fedora40', 'fedora41', 'fedora42', 'fedora43']
ansible.builtin.command: "{{ item }}" ansible.builtin.include_tasks: fedora.yml
changed_when: result.rc == 0
register: result
with_items:
- dnf --releasever=41 --best --repo=fedora --repo=fedora-updates
--installroot=/mnt --setopt=install_weak_deps=False groupinstall -y critical-path-base core
- ln -sf /run/systemd/resolve/resolv.conf /mnt/etc/resolv.conf
- arch-chroot /mnt dnf --releasever=41 --setopt=install_weak_deps=False install -y {{ role_packages.fedora | join(' ') }}
- arch-chroot /mnt dnf reinstall -y kernel-core
- name: Bootstrap RockyLinux 9 - name: Include openSUSE bootstrap tasks
when: os | lower == 'rocky' when: bootstrap_os_key == 'opensuse'
ansible.builtin.command: "{{ item }}" ansible.builtin.include_tasks: opensuse.yml
changed_when: result.rc == 0
register: result
with_items:
- dnf --releasever=9 --best --repo=rocky-baseos --installroot=/mnt
--setopt=install_weak_deps=False --setopt=optional_metadata_types=filelists
groupinstall -y base core
- ln -sf /run/systemd/resolve/resolv.conf /mnt/etc/resolv.conf
- arch-chroot /mnt dnf --releasever=9 --setopt=install_weak_deps=False install -y {{ role_packages.rocky | join(' ') }}
- name: Bootstrap RHEL System - name: Include Rocky bootstrap tasks
when: os | lower in ['rhel8', 'rhel9'] when: bootstrap_os_key in ['rocky', 'rocky8', 'rocky9', 'rocky10']
block: ansible.builtin.include_tasks: rocky.yml
- name: Install base packages in chroot environment
ansible.builtin.command: >-
dnf --releasever={{ '8' if os == 'rhel8' else '9' }} --repo={{ os | lower }}-baseos
--installroot=/mnt
--setopt=install_weak_deps=False --setopt=optional_metadata_types=filelists
groupinstall -y base core
changed_when: result.rc == 0
register: result
- name: Prepare chroot environment - name: Include RHEL bootstrap tasks
ansible.builtin.shell: | when: bootstrap_os_key in ['rhel8', 'rhel9', 'rhel10']
ln -sf /run/systemd/resolve/resolv.conf /mnt/etc/resolv.conf ansible.builtin.include_tasks: rhel.yml
mkdir -p /mnt/usr/local/install/redhat/dvd
mount --bind /usr/local/install/redhat/dvd /mnt/usr/local/install/redhat/dvd
arch-chroot /mnt rpm --rebuilddb
changed_when: result.rc == 0
register: result
- name: Copy RHEL repo file into chroot environment - name: Include Ubuntu bootstrap tasks
ansible.builtin.copy: when: bootstrap_os_key in ['ubuntu', 'ubuntu-lts']
src: /etc/yum.repos.d/{{ os | lower }}.repo ansible.builtin.include_tasks: ubuntu.yml
dest: /mnt/etc/yum.repos.d/{{ os | lower }}.repo
mode: '0644'
remote_src: true
- name: Install additional packages in chroot - name: Include Void bootstrap tasks
ansible.builtin.command: >- when: bootstrap_os_key == 'void'
arch-chroot /mnt dnf --releasever={{ '8' if os == 'rhel8' else '9' }} ansible.builtin.include_tasks: void.yml
--setopt=install_weak_deps=False install -y {{ role_packages[os] | join(' ') }}
changed_when: result.rc == 0
register: result

View File

@@ -0,0 +1,33 @@
---
- name: Bootstrap openSUSE
vars:
bootstrap_opensuse_packages: >-
{{
lookup('vars', 'bootstrap_opensuse') | reject('equalto', '') | join(' ')
}}
block:
- name: Ensure chroot has resolv.conf
ansible.builtin.file:
src: /run/NetworkManager/resolv.conf
dest: /mnt/etc/resolv.conf
state: link
force: true
- name: Install openSUSE base packages
ansible.builtin.command: >
zypper --root /mnt --non-interactive install -t pattern patterns-base-base
register: bootstrap_opensuse_base_result
changed_when: bootstrap_opensuse_base_result.rc == 0
- name: Install openSUSE extra packages
when: bootstrap_opensuse_packages | length > 0
ansible.builtin.command: >
zypper --root /mnt --non-interactive install {{ bootstrap_opensuse_packages }}
register: bootstrap_opensuse_extra_result
changed_when: bootstrap_opensuse_extra_result.rc == 0
- name: Install bootloader
ansible.builtin.command: >
zypper --root /mnt --non-interactive install grub2 grub2-efi efibootmgr
register: bootstrap_opensuse_bootloader_result
changed_when: bootstrap_opensuse_bootloader_result.rc == 0

View File

@@ -0,0 +1,60 @@
---
- name: Bootstrap RHEL System
block:
- name: Install base packages in chroot environment
vars:
bootstrap_rhel_release: "{{ bootstrap_os_key | replace('rhel', '') }}"
ansible.builtin.command: >-
dnf --releasever={{ bootstrap_rhel_release }} --repo={{ bootstrap_os_key }}-baseos
--installroot=/mnt
--setopt=install_weak_deps=False --setopt=optional_metadata_types=filelists
groupinstall -y core base standard
register: bootstrap_result
changed_when: bootstrap_result.rc == 0
- name: Ensure chroot has resolv.conf
ansible.builtin.file:
src: /run/NetworkManager/resolv.conf
dest: /mnt/etc/resolv.conf
state: link
- name: Ensure chroot RHEL DVD directory exists
ansible.builtin.file:
path: /mnt/usr/local/install/redhat/dvd
state: directory
mode: "0755"
- name: Bind mount RHEL DVD into chroot
ansible.posix.mount:
src: /usr/local/install/redhat/dvd
path: /mnt/usr/local/install/redhat/dvd
fstype: none
opts: bind
state: mounted
- name: Rebuild RPM database inside chroot
ansible.builtin.command: "{{ chroot_command }} rpm --rebuilddb"
register: bootstrap_rpm_rebuild_result
changed_when: bootstrap_rpm_rebuild_result.rc == 0
- name: Copy RHEL repo file into chroot environment
ansible.builtin.copy:
src: /etc/yum.repos.d/{{ bootstrap_os_key }}.repo
dest: /mnt/etc/yum.repos.d/redhat.repo
mode: "0644"
remote_src: true
- name: Install additional packages in chroot
vars:
bootstrap_rhel_release: "{{ bootstrap_os_key | replace('rhel', '') }}"
bootstrap_rhel_extra: >-
{{
lookup('vars', bootstrap_var_key)
| reject('equalto', '')
| join(' ')
}}
ansible.builtin.command: >-
{{ chroot_command }} dnf --releasever={{ bootstrap_rhel_release }}
--setopt=install_weak_deps=False install -y {{ bootstrap_rhel_extra }}
register: bootstrap_result
changed_when: bootstrap_result.rc == 0

View File

@@ -0,0 +1,35 @@
---
- name: Bootstrap Rocky Linux
vars:
bootstrap_rocky_extra: >-
{{
lookup('vars', bootstrap_var_key)
| reject('equalto', '')
| join(' ')
}}
block:
- name: Install Rocky Linux base system
ansible.builtin.command: >-
dnf --releasever={{ os_version }} --best --repo=baseos --repo=appstream
--installroot=/mnt --setopt=install_weak_deps=False
groupinstall -y core
register: bootstrap_rocky_base_result
changed_when: bootstrap_rocky_base_result.rc == 0
- name: Ensure chroot has resolv.conf
ansible.builtin.file:
src: /run/NetworkManager/resolv.conf
dest: /mnt/etc/resolv.conf
state: link
- name: Install extra packages
ansible.builtin.command: >-
{{ chroot_command }} dnf --releasever={{ os_version }} --setopt=install_weak_deps=False
install -y {{ bootstrap_rocky_extra }}
register: bootstrap_rocky_extra_result
changed_when: bootstrap_rocky_extra_result.rc == 0
- name: Reinstall kernel core
ansible.builtin.command: "{{ chroot_command }} dnf reinstall -y kernel-core"
register: bootstrap_rocky_kernel_result
changed_when: bootstrap_rocky_kernel_result.rc == 0

View File

@@ -0,0 +1,68 @@
---
- name: Bootstrap Ubuntu System
vars:
bootstrap_ubuntu_release: >-
{{ 'plucky' if bootstrap_os_key == 'ubuntu' else 'noble' }}
bootstrap_ubuntu_package_config: >-
{{
lookup('vars', bootstrap_var_key)
}}
bootstrap_ubuntu_base_packages: >-
{{
bootstrap_ubuntu_package_config.base
| default([])
| reject('equalto', '')
| list
}}
bootstrap_ubuntu_extra_packages: >-
{{
bootstrap_ubuntu_package_config.extra
| default([])
| reject('equalto', '')
| list
}}
bootstrap_ubuntu_base_csv: "{{ bootstrap_ubuntu_base_packages | join(',') }}"
bootstrap_ubuntu_extra: "{{ bootstrap_ubuntu_extra_packages | join(' ') }}"
block:
- name: Validate Ubuntu package configuration
ansible.builtin.assert:
that:
- bootstrap_ubuntu_package_config is mapping
- bootstrap_ubuntu_package_config.base is defined
- bootstrap_ubuntu_package_config.base is sequence
- bootstrap_ubuntu_package_config.base is not string
- bootstrap_ubuntu_package_config.extra is defined
- bootstrap_ubuntu_package_config.extra is sequence
- bootstrap_ubuntu_package_config.extra is not string
fail_msg: "bootstrap package definition for {{ bootstrap_var_key }} must be a mapping with base/extra lists."
quiet: true
- name: Install Ubuntu base system
ansible.builtin.command: >-
debootstrap --include={{ bootstrap_ubuntu_base_csv }}
{{ bootstrap_ubuntu_release }} /mnt
http://archive.ubuntu.com/ubuntu/
register: bootstrap_ubuntu_base_result
changed_when: bootstrap_ubuntu_base_result.rc == 0
- name: Ensure chroot has resolv.conf
ansible.builtin.file:
src: /run/NetworkManager/resolv.conf
dest: /mnt/etc/resolv.conf
state: link
- name: Enable universe repository
ansible.builtin.command: "{{ chroot_command }} sed -i '1s|$| universe|' /etc/apt/sources.list"
register: bootstrap_ubuntu_repo_result
changed_when: bootstrap_ubuntu_repo_result.rc == 0
- name: Update package lists
ansible.builtin.command: "{{ chroot_command }} apt update"
register: bootstrap_ubuntu_update_result
changed_when: bootstrap_ubuntu_update_result.rc == 0
- name: Install extra packages
when: bootstrap_ubuntu_extra_packages | length > 0
ansible.builtin.command: "{{ chroot_command }} apt install -y {{ bootstrap_ubuntu_extra }}"
register: bootstrap_ubuntu_extra_result
changed_when: bootstrap_ubuntu_extra_result.rc == 0

View File

@@ -0,0 +1,33 @@
---
- name: Bootstrap Void Linux
vars:
bootstrap_void_packages: >-
{{
lookup('vars', 'bootstrap_void') | reject('equalto', '') | join(' ')
}}
block:
- name: Ensure chroot has resolv.conf
ansible.builtin.file:
src: /run/NetworkManager/resolv.conf
dest: /mnt/etc/resolv.conf
state: link
force: true
- name: Install Void Linux base packages
ansible.builtin.command: >
xbps-install -Sy -r /mnt -R https://repo-default.voidlinux.org/current void-repo-nonfree base-system
register: bootstrap_void_base_result
changed_when: bootstrap_void_base_result.rc == 0
- name: Install extra packages
when: bootstrap_void_packages | length > 0
ansible.builtin.command: >
xbps-install -Su -r /mnt {{ bootstrap_void_packages }}
register: bootstrap_void_extra_result
changed_when: bootstrap_void_extra_result.rc == 0
- name: Install bootloader
ansible.builtin.command: >
xbps-install -Sy -r /mnt grub-x86_64-efi efibootmgr
register: bootstrap_void_bootloader_result
changed_when: bootstrap_void_bootloader_result.rc == 0

View File

@@ -0,0 +1,267 @@
---
bootstrap_rhel_base:
- bind-utils
- dhcp-client
- efibootmgr
- "{{ 'firewalld' if system_cfg.features.firewall.backend == 'firewalld' and system_cfg.features.firewall.enabled | bool else '' }}"
- "{{ 'ufw' if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else '' }}"
- "{{ 'iptables' if system_cfg.features.firewall.toolkit == 'iptables' else '' }}"
- "{{ 'nftables' if system_cfg.features.firewall.toolkit == 'nftables' else '' }}"
- glibc-langpack-de
- glibc-langpack-en
- lrzsz
- lvm2
- mtr
- ncurses-term
- nfs-utils
- policycoreutils-python-utils
- shim
- tmux
- "{{ 'cryptsetup' if system_cfg.luks.enabled else '' }}"
- "{{ 'tpm2-tools' if system_cfg.luks.enabled else '' }}"
- "{{ 'qemu-guest-agent' if hypervisor_type in ['libvirt', 'proxmox'] else '' }}"
- "{{ 'open-vm-tools' if hypervisor_type == 'vmware' else '' }}"
- vim
- zstd
bootstrap_rhel_versioned:
- grub2
- "{{ 'grub2-efi-x64' if os_version_major | default('') == '8' else 'grub2-efi' }}"
- "{{ 'grub2-tools-extra' if os_version_major | default('') in ['8', '9'] else '' }}"
- "{{ 'python39' if os_version_major | default('') == '8' else 'python' }}"
- "{{ 'kernel' if os_version_major | default('') == '10' else '' }}"
- "{{ 'zram-generator' if os_version_major | default('') in ['9', '10'] else '' }}"
bootstrap_rhel_common: "{{ bootstrap_rhel_base + bootstrap_rhel_versioned }}"
bootstrap_rhel8: "{{ bootstrap_rhel_common }}"
bootstrap_rhel9: "{{ bootstrap_rhel_common }}"
bootstrap_rhel10: "{{ bootstrap_rhel_common }}"
bootstrap_almalinux:
"{{ bootstrap_rhel_base + ['grub2', 'grub2-efi', 'dbus-daemon', 'lrzsz', 'nfsv4-client-utils', 'nc', 'ppp', 'zram-generator'] }}"
bootstrap_rocky:
"{{ bootstrap_rhel_base + ['grub2', 'grub2-efi', 'nfsv4-client-utils', 'nc', 'ppp', 'telnet', 'util-linux-core', 'wget', 'zram-generator'] }}"
bootstrap_almalinux8: "{{ bootstrap_almalinux }}"
bootstrap_almalinux9: "{{ bootstrap_almalinux }}"
bootstrap_almalinux10: "{{ bootstrap_almalinux }}"
bootstrap_rocky8: "{{ bootstrap_rocky }}"
bootstrap_rocky9: "{{ bootstrap_rocky }}"
bootstrap_rocky10: "{{ bootstrap_rocky }}"
bootstrap_fedora:
- bat
- bind-utils
- btrfs-progs
- cronie
- dhcp-client
- duf
- efibootmgr
- entr
- "{{ 'firewalld' if system_cfg.features.firewall.backend == 'firewalld' and system_cfg.features.firewall.enabled | bool else '' }}"
- "{{ 'ufw' if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else '' }}"
- "{{ 'iptables' if system_cfg.features.firewall.toolkit == 'iptables' else '' }}"
- "{{ 'nftables' if system_cfg.features.firewall.toolkit == 'nftables' else '' }}"
- fish
- fzf
- glibc-langpack-de
- glibc-langpack-en
- grub2
- grub2-efi
- htop
- iperf3
- logrotate
- lrzsz
- lvm2
- nc
- nfs-utils
- nfsv4-client-utils
- polkit
- ppp
- ripgrep
- shim
- tmux
- "{{ 'cryptsetup' if system_cfg.luks.enabled else '' }}"
- "{{ 'tpm2-tools' if system_cfg.luks.enabled else '' }}"
- "{{ 'qemu-guest-agent' if hypervisor_type in ['libvirt', 'proxmox'] else '' }}"
- "{{ 'open-vm-tools' if hypervisor_type == 'vmware' else '' }}"
- vim-default-editor
- wget
- zoxide
- zram-generator
- zstd
bootstrap_fedora40: "{{ bootstrap_fedora }}"
bootstrap_fedora41: "{{ bootstrap_fedora }}"
bootstrap_fedora42: "{{ bootstrap_fedora }}"
bootstrap_fedora43: "{{ bootstrap_fedora }}"
bootstrap_debian_base_common:
- btrfs-progs
- cron
- gnupg
- grub-efi
- grub-efi-amd64-signed
- grub2-common
- "{{ 'cryptsetup' if system_cfg.luks.enabled else '' }}"
- "{{ 'cryptsetup-initramfs' if system_cfg.luks.enabled else '' }}"
- locales
- logrotate
- lvm2
- "{{ 'iptables' if system_cfg.features.firewall.toolkit == 'iptables' else '' }}"
- "{{ 'nftables' if system_cfg.features.firewall.toolkit == 'nftables' else '' }}"
- "{{ 'openssh-server' if system_cfg.features.ssh.enabled | bool else '' }}"
- python3
- xfsprogs
bootstrap_debian_extra_common:
- apparmor-utils
- bat
- chrony
- curl
- entr
- "{{ 'firewalld' if system_cfg.features.firewall.backend == 'firewalld' and system_cfg.features.firewall.enabled | bool else '' }}"
- "{{ 'ufw' if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else '' }}"
- fish
- fzf
- htop
- jq
- libpam-pwquality
- lrzsz
- mtr
- ncdu
- net-tools
- network-manager
- python-is-python3
- ripgrep
- rsync
- screen
- sudo
- syslog-ng
- tcpd
- "{{ 'tpm2-tools' if system_cfg.luks.enabled else '' }}"
- "{{ 'qemu-guest-agent' if hypervisor_type in ['libvirt', 'proxmox'] else '' }}"
- "{{ 'open-vm-tools' if hypervisor_type == 'vmware' else '' }}"
- vim
- wget
- zstd
bootstrap_debian_extra_versioned:
- linux-image-amd64
- "{{ 'duf' if (os_version | string) not in ['10', '11'] else '' }}"
- "{{ 'fastfetch' if (os_version | string) in ['12', '13', 'unstable'] else '' }}"
- "{{ 'neofetch' if (os_version | string) == '12' else '' }}"
- "{{ 'software-properties-common' if (os_version | string) not in ['13', 'unstable'] else '' }}"
- "{{ 'systemd-zram-generator' if (os_version | string) not in ['10', '11'] else '' }}"
- "{{ 'tldr' if (os_version | string) not in ['13', 'unstable'] else '' }}"
bootstrap_debian:
base: "{{ bootstrap_debian_base_common }}"
extra: "{{ bootstrap_debian_extra_common + bootstrap_debian_extra_versioned }}"
bootstrap_debian10: "{{ bootstrap_debian }}"
bootstrap_debian11: "{{ bootstrap_debian }}"
bootstrap_debian12: "{{ bootstrap_debian }}"
bootstrap_debian13: "{{ bootstrap_debian }}"
bootstrap_debianunstable: "{{ bootstrap_debian }}"
bootstrap_ubuntu:
base:
- linux-image-generic
extra: >-
{{
bootstrap_debian_base_common
+ bootstrap_debian_extra_common
+ ['bash-completion', 'dnsutils', 'duf', 'eza', 'fdupes', 'fio', 'ncurses-term', 'software-properties-common', 'systemd-zram-generator', 'tldr', 'traceroute', 'util-linux-extra', 'yq', 'zoxide']
}}
bootstrap_ubuntu_lts:
base:
- linux-image-generic
extra: >-
{{
bootstrap_debian_base_common
+ bootstrap_debian_extra_common
+ ['bash-completion', 'dnsutils', 'duf', 'eza', 'fdupes', 'fio', 'ncurses-term', 'software-properties-common', 'systemd-zram-generator', 'tldr', 'traceroute', 'util-linux-extra', 'yq', 'zoxide']
}}
bootstrap_archlinux:
- base
- btrfs-progs
- cronie
- dhcpcd
- efibootmgr
- fastfetch
- "{{ 'firewalld' if system_cfg.features.firewall.backend == 'firewalld' and system_cfg.features.firewall.enabled | bool else '' }}"
- "{{ 'ufw' if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else '' }}"
- "{{ 'iptables' if system_cfg.features.firewall.toolkit == 'iptables' else '' }}"
- "{{ 'iptables-nft' if system_cfg.features.firewall.toolkit == 'nftables' else '' }}"
- fish
- fzf
- grub
- htop
- libpwquality
- linux
- logrotate
- lrzsz
- lsof
- lvm2
- ncdu
- networkmanager
- nfs-utils
- "{{ 'openssh' if system_cfg.features.ssh.enabled | bool else '' }}"
- ppp
- prometheus-node-exporter
- python-psycopg2
- reflector
- rsync
- sudo
- tldr
- tmux
- "{{ 'cryptsetup' if system_cfg.luks.enabled else '' }}"
- "{{ 'tpm2-tools' if system_cfg.luks.enabled else '' }}"
- "{{ 'qemu-guest-agent' if hypervisor_type in ['libvirt', 'proxmox'] else '' }}"
- "{{ 'open-vm-tools' if hypervisor_type == 'vmware' else '' }}"
- vim
- wireguard-tools
- zram-generator
bootstrap_alpine:
- alpine-base
- vim
- "{{ 'openssh' if system_cfg.features.ssh.enabled | bool else '' }}"
- "{{ 'qemu-guest-agent' if hypervisor_type in ['libvirt', 'proxmox'] else '' }}"
- "{{ 'open-vm-tools' if hypervisor_type == 'vmware' else '' }}"
- "{{ 'firewalld' if system_cfg.features.firewall.backend == 'firewalld' and system_cfg.features.firewall.enabled | bool else '' }}"
- "{{ 'ufw' if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else '' }}"
- "{{ 'iptables' if system_cfg.features.firewall.toolkit == 'iptables' else '' }}"
- "{{ 'nftables' if system_cfg.features.firewall.toolkit == 'nftables' else '' }}"
- "{{ 'cryptsetup' if system_cfg.luks.enabled else '' }}"
- "{{ 'tpm2-tools' if system_cfg.luks.enabled else '' }}"
bootstrap_opensuse:
- vim
- "{{ 'openssh' if system_cfg.features.ssh.enabled | bool else '' }}"
- "{{ 'qemu-guest-agent' if hypervisor_type in ['libvirt', 'proxmox'] else '' }}"
- "{{ 'open-vm-tools' if hypervisor_type == 'vmware' else '' }}"
- "{{ 'firewalld' if system_cfg.features.firewall.backend == 'firewalld' and system_cfg.features.firewall.enabled | bool else '' }}"
- "{{ 'ufw' if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else '' }}"
- "{{ 'iptables' if system_cfg.features.firewall.toolkit == 'iptables' else '' }}"
- "{{ 'nftables' if system_cfg.features.firewall.toolkit == 'nftables' else '' }}"
- "{{ 'cryptsetup' if system_cfg.luks.enabled else '' }}"
- "{{ 'tpm2-tools' if system_cfg.luks.enabled else '' }}"
bootstrap_void:
- vim
- "{{ 'openssh' if system_cfg.features.ssh.enabled | bool else '' }}"
- "{{ 'qemu-guest-agent' if hypervisor_type in ['libvirt', 'proxmox'] else '' }}"
- "{{ 'open-vm-tools' if hypervisor_type == 'vmware' else '' }}"
- "{{ 'firewalld' if system_cfg.features.firewall.backend == 'firewalld' and system_cfg.features.firewall.enabled | bool else '' }}"
- "{{ 'ufw' if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else '' }}"
- "{{ 'iptables' if system_cfg.features.firewall.toolkit == 'iptables' else '' }}"
- "{{ 'nftables' if system_cfg.features.firewall.toolkit == 'nftables' else '' }}"
- "{{ 'cryptsetup' if system_cfg.luks.enabled else '' }}"
- "{{ 'tpm2-tools' if system_cfg.luks.enabled else '' }}"

View File

@@ -1,310 +0,0 @@
---
almalinux:
- bind-utils
- cloud-init
- dbus-daemon
- dhcp-client
- efibootmgr
- glibc-langpack-de
- glibc-langpack-en
- grub2
- grub2-efi
- lrzsz
- lvm2
- nc
- nfs-utils
- nfsv4-client-utils
- open-vm-tools
- ppp
- shim
- telnet
- vim
- wget
- zstd
archlinux:
- base
- btrfs-progs
- cloud-init
- cronie
- dhcpcd
- efibootmgr
- firewalld
- fish
- grub
- htop
- libpwquality
- linux
- logrotate
- lrzsz
- lsof
- lvm2
- ncdu
- neofetch
- networkmanager
- nfs-utils
- openssh
- open-vm-tools
- ppp
- prometheus-node-exporter
- python-psycopg2
- qemu-guest-agent
- reflector
- rsync
- screen
- sudo
- vim
- wireguard-tools
debian11:
base:
- apparmor-utils
- btrfs-progs
- chrony
- cron
- gnupg
- grub-efi
- grub-efi-amd64-signed
- grub2-common
- linux-image-amd64
- locales
- logrotate
- lvm2
- net-tools
- openssh-server
- python3
- sudo
- xfsprogs
extra:
- cloud-init
- curl
- firewalld
- fish
- htop
- libpam-pwquality
- lrzsz
- ncdu
- neofetch
- network-manager
- open-vm-tools
- python-is-python3
- rsync
- screen
- software-properties-common
- syslog-ng
- tcpd
- vim
- wget
- zstd
debian12:
base:
- btrfs-progs
- cron
- gnupg
- grub-efi
- grub-efi-amd64-signed
- grub2-common
- linux-image-amd64
- locales
- logrotate
- lvm2
- xfsprogs
extra:
- apparmor-utils
- chrony
- cloud-init
- curl
- firewalld
- fish
- htop
- libpam-pwquality
- logrotate
- lrzsz
- ncdu
- neofetch
- net-tools
- network-manager
- open-vm-tools
- openssh-server
- python-is-python3
- python3
- rsync
- screen
- software-properties-common
- sudo
- syslog-ng
- tcpd
- vim
- wget
- zstd
fedora:
- bind-utils
- btrfs-progs
- cloud-init
- cronie
- dhcp-client
- efibootmgr
- glibc-langpack-de
- glibc-langpack-en
- grub2
- grub2-efi
- logrotate
- lrzsz
- lvm2
- nc
- nfs-utils
- nfsv4-client-utils
- open-vm-tools
- polkit
- ppp
- shim
- telnet
- vim-default-editor
- wget
- zstd
rhel8:
- cloud-init
- dhcp-client
- efibootmgr
- grub2
- grub2-efi-x64
- lrzsz
- lvm2
- nfs-utils
- open-vm-tools
- shim
- telnet
- vim
- zstd
rhel9:
- cloud-init
- dhcp-client
- efibootmgr
- grub2
- grub2-efi
- lrzsz
- lvm2
- nfs-utils
- open-vm-tools
- shim
- telnet
- vim
- zstd
rocky:
- bind-utils
- cloud-init
- dbus-daemon
- dhcp-client
- efibootmgr
- glibc-langpack-de
- glibc-langpack-en
- grub2
- grub2-efi
- lrzsz
- lvm2
- nc
- nfs-utils
- nfsv4-client-utils
- open-vm-tools
- ppp
- shim
- telnet
- util-linux-core
- vim
- wget
- zstd
ubuntu:
base:
- btrfs-progs
- cron
- gnupg
- grub-efi
- grub-efi-amd64-signed
- grub2-common
- initramfs-tools
- linux-image-generic
- locales
- lvm2
- xfsprogs
extra:
- apparmor-utils
- bash-completion
- chrony
- cloud-init
- curl
- dnsutils
- firewalld
- fish
- htop
- libpam-pwquality
- logrotate
- lrzsz
- ncdu
- net-tools
- network-manager
- open-vm-tools
- openssh-server
- python-is-python3
- python3
- rsync
- screen
- software-properties-common
- sudo
- syslog-ng
- tcpd
- vim
- wget
- zstd
ubuntu-lts:
base:
- btrfs-progs
- cron
- gnupg
- grub-efi
- grub-efi-amd64-signed
- grub2-common
- initramfs-tools
- linux-image-generic
- locales
- lvm2
- xfsprogs
extra:
- apparmor-utils
- bash-completion
- chrony
- cloud-init
- curl
- dnsutils
- firewalld
- fish
- htop
- libpam-pwquality
- logrotate
- lrzsz
- ncdu
- net-tools
- network-manager
- open-vm-tools
- openssh-server
- python-is-python3
- python3
- rsync
- screen
- software-properties-common
- sudo
- syslog-ng
- tcpd
- vim
- wget
- zstd

View File

@@ -0,0 +1,21 @@
---
cis_permission_targets: >-
{{
[
{ "path": "/mnt/etc/ssh/sshd_config", "mode": "0600" },
{ "path": "/mnt/etc/cron.hourly", "mode": "0700" },
{ "path": "/mnt/etc/cron.daily", "mode": "0700" },
{ "path": "/mnt/etc/cron.weekly", "mode": "0700" },
{ "path": "/mnt/etc/cron.monthly", "mode": "0700" },
{ "path": "/mnt/etc/cron.d", "mode": "0700" },
{ "path": "/mnt/etc/crontab", "mode": "0600" },
{ "path": "/mnt/etc/logrotate.conf", "mode": "0644" },
{ "path": "/mnt/usr/sbin/pppd", "mode": "0754" } if os != "rhel" else None,
{
"path": "/mnt/usr/bin/"
+ ("fusermount3" if os in ["archlinux", "fedora", "rocky"] or os == "rhel" or (os == "debian" and (os_version | string) == "12") else "fusermount"),
"mode": "755"
},
{ "path": "/mnt/usr/bin/" + ("write.ul" if os == "debian" and (os_version | string) == "11" else "write"), "mode": "755" }
] | reject("none")
}}

15
roles/cis/tasks/auth.yml Normal file
View File

@@ -0,0 +1,15 @@
---
- name: Ensure the Default UMASK is Set Correctly
ansible.builtin.lineinfile:
path: "/mnt/etc/profile"
regexp: "^(\\s*)umask\\s+\\d+"
line: "umask 027"
- name: Prevent Login to Accounts With Empty Password
ansible.builtin.replace:
dest: "{{ item }}"
regexp: "\\s*nullok"
replace: ""
loop:
- /mnt/etc/pam.d/system-auth
- /mnt/etc/pam.d/password-auth

View File

@@ -0,0 +1,12 @@
---
- name: Configure System Cryptography Policy
when: os == "rhel" or os in ["almalinux", "rocky"]
ansible.builtin.command: "{{ chroot_command }} /usr/bin/update-crypto-policies --set DEFAULT:NO-SHA1"
register: cis_crypto_policy_result
changed_when: "'Setting system-wide crypto-policies to' in cis_crypto_policy_result.stdout"
- name: Mask Systemd Services
ansible.builtin.command: >
{{ chroot_command }} systemctl mask {{ 'nftables' if system_cfg.features.firewall.toolkit == 'iptables' else 'iptables' }} bluetooth rpcbind
register: cis_mask_services_result
changed_when: cis_mask_services_result.rc == 0

19
roles/cis/tasks/files.yml Normal file
View File

@@ -0,0 +1,19 @@
---
- name: Ensure files exist
ansible.builtin.file:
path: "{{ item }}"
state: touch
mode: "0600"
loop:
- /mnt/etc/at.allow
- /mnt/etc/cron.allow
- /mnt/etc/hosts.allow
- /mnt/etc/hosts.deny
- name: Ensure files do not exist
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- /mnt/etc/at.deny
- /mnt/etc/cron.deny

View File

@@ -1,183 +1,14 @@
--- ---
- name: Configurationg System for CIS conformity - name: Include CIS hardening tasks
block: ansible.builtin.include_tasks: "{{ cis_task }}"
- name: Disable Kernel Modules loop:
ansible.builtin.copy: - modules.yml
dest: /mnt/etc/modprobe.d/cis.conf - sysctl.yml
mode: '0644' - auth.yml
content: | - crypto.yml
CIS LVL 3 Restrictions - files.yml
install freevxfs /bin/true - security_lines.yml
install jffs2 /bin/true - permissions.yml
install hfs /bin/true - sshd.yml
install hfsplus /bin/true loop_control:
install squashfs /bin/true loop_var: cis_task
install udf /bin/true
install usb-storage /bin/true
install dccp /bin/true
install sctp /bin/true
install rds /bin/true
install tipc /bin/true
- name: Create USB Rules
ansible.builtin.copy:
dest: /mnt/etc/udev/rules.d/10-cis_usb_devices.sh
mode: '0644'
content: |
By default, disable all.
ACTION=="add", SUBSYSTEMS=="usb", TEST=="authorized_default", ATTR{authorized_default}="0"
Enable hub devices.
ACTION=="add", ATTR{bDeviceClass}=="09", TEST=="authorized", ATTR{authorized}="1"
Enables keyboard devices
ACTION=="add", ATTR{product}=="*[Kk]eyboard*", TEST=="authorized", ATTR{authorized}="1"
PS2-USB converter
ACTION=="add", ATTR{product}=="*Thinnet TM*", TEST=="authorized", ATTR{authorized}="1"
- name: Create a consolidated sysctl configuration file
ansible.builtin.copy:
dest: /mnt/etc/sysctl.d/10-cis.conf
mode: '0644'
content: |
## CIS Sysctl configurations
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.log_martians = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.default.secure_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.accept_redirects = 0
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
# - name: Adjust login.defs
# replace:
# path: /mnt/etc/login.defs
# regexp: "{{ item.regexp }}"
# replace: "{{ item.replace }}"
# loop:
# - { regexp: '^PASS_MAX_DAYS.*', replace: 'PASS_MAX_DAYS 90' }
# - { regexp: '^PASS_MIN_DAYS.*', replace: 'PASS_MIN_DAYS 7' }
# - { regexp: '^UMASK.*', replace: 'UMASK 027' }
- name: Ensure files exist
ansible.builtin.file:
path: "{{ item }}"
state: touch
mode: "0600"
loop:
- /mnt/etc/at.allow
- /mnt/etc/cron.allow
- /mnt/etc/hosts.allow
- /mnt/etc/hosts.deny
- name: Add Security related lines into config files
ansible.builtin.lineinfile:
path: "{{ item.path }}"
line: "{{ item.content }}"
loop:
- { path: /mnt/etc/security/limits.conf, content: "* hard core 0" }
- { path: /mnt/etc/security/pwquality.conf, content: minlen = 14 }
- { path: /mnt/etc/security/pwquality.conf, content: dcredit = -1 }
- { path: /mnt/etc/security/pwquality.conf, content: ucredit = -1 }
- { path: /mnt/etc/security/pwquality.conf, content: ocredit = -1 }
- { path: /mnt/etc/security/pwquality.conf, content: lcredit = -1 }
- { path: '/mnt/etc/{{ "bashrc" if os in ["almalinux", "fedora", "rhel8", "rhel9", "rocky"] else "bash.bashrc" }}', content: umask 077 }
- { path: '/mnt/etc/{{ "bashrc" if os in ["almalinux", "fedora", "rhel8", "rhel9", "rocky"] else "bash.bashrc" }}', content: export TMOUT=3000 }
- { path: '/mnt/{{ "usr/lib/systemd/journald.conf" if os == "fedora" else "etc/systemd/journald.conf" }}', content: Storage=persistent }
- { path: /mnt/etc/sudoers, content: Defaults logfile="/var/log/sudo.log" }
- { path: /mnt/etc/pam.d/su, content: auth required pam_wheel.so }
- { path: '/mnt/etc/{{ "pam.d/common-auth" if os in ["debian11", "debian12", "ubuntu", "ubuntu-lts"]
else "authselect/system-auth" if os == "fedora" else "pam.d/system-auth" }}',
content: auth required pam_faillock.so onerr=fail audit silent deny=5 unlock_time=900 }
- { path: '/mnt/etc/{{ "pam.d/common-account" if os in ["debian11", "debian12", "ubuntu", "ubuntu-lts"] else "authselect/system-auth"
if os == "fedora" else "pam.d/system-auth" }}', content: account required pam_faillock.so }
- { path: '/mnt/etc/pam.d/{{ "common-password" if os in ["debian11", "debian12", "ubuntu", "ubuntu-lts"] else "passwd" }}',
content: "password [success=1 default=ignore] pam_unix.so obscure sha512 remember=5" }
- { path: /mnt/etc/hosts.deny, content: "ALL: ALL" }
- { path: /mnt/etc/hosts.allow, content: "sshd: ALL" }
- name: Set permissions for various files and directories
ansible.builtin.file:
path: "{{ item.path }}"
owner: "{{ item.owner | default(omit) }}"
group: "{{ item.group | default(omit) }}"
mode: "{{ item.mode }}"
loop: >
{{ [
{ "path": "/mnt/etc/ssh/sshd_config", "mode": "0600" },
{ "path": "/mnt/etc/cron.hourly", "mode": "0700" },
{ "path": "/mnt/etc/cron.daily", "mode": "0700" },
{ "path": "/mnt/etc/cron.weekly", "mode": "0700" },
{ "path": "/mnt/etc/cron.monthly", "mode": "0700" },
{ "path": "/mnt/etc/cron.d", "mode": "0700" },
{ "path": "/mnt/etc/crontab", "mode": "0600" },
{ "path": "/mnt/etc/logrotate.conf", "mode": "0644" },
{ "path": "/mnt/usr/sbin/pppd", "mode": "0754" } if os not in ["rhel8", "rhel9"] else None,
{ "path": "/mnt/usr/bin/" + ("fusermount3" if os in ["almalinux", "archlinux", "debian12", "fedora", "rhel9", "rocky"]
else "fusermount"), "mode": "755" },
{ "path": "/mnt/usr/bin/" + ("write.ul" if os == "debian11" else "write"), "mode": "755" }
] | reject("none") }}
- name: Adjust SSHD config
ansible.builtin.lineinfile:
path: /mnt/etc/ssh/sshd_config
regexp: ^\s*#?{{ item.option }}\s+.*$
line: "{{ item.option }} {{ item.value }}"
with_items:
- { option: LogLevel, value: VERBOSE }
- { option: LoginGraceTime, value: "60" }
- { option: PermitRootLogin, value: "no" }
- { option: StrictModes, value: "yes" }
- { option: MaxAuthTries, value: "4" }
- { option: MaxSessions, value: "10" }
- { option: MaxStartups, value: 10:30:60 }
- { option: PubkeyAuthentication, value: "yes" }
- { option: HostbasedAuthentication, value: "no" }
- { option: IgnoreRhosts, value: "yes" }
- { option: PasswordAuthentication, value: "no" }
- { option: PermitEmptyPasswords, value: "no" }
- { option: KerberosAuthentication, value: "no" }
- { option: GSSAPIAuthentication, value: "no" }
- { option: AllowAgentForwarding, value: "no" }
- { option: AllowTcpForwarding, value: "no" }
- { option: ChallengeResponseAuthentication, value: "no" }
- { option: GatewayPorts, value: "no" }
- { option: X11Forwarding, value: "no" }
- { option: PermitUserEnvironment, value: "no" }
- { option: ClientAliveInterval, value: "300" }
- { option: ClientAliveCountMax, value: "0" }
- { option: PermitTunnel, value: "no" }
- { option: Banner, value: /etc/issue.net }
- name: Append CIS Specific configurations to sshd_config
ansible.builtin.lineinfile:
path: /mnt/etc/ssh/sshd_config
line: |2-
## CIS Specific
Protocol 2
### Ciphers and keying ###
RekeyLimit 512M 6h
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group14-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256
###########################
AllowStreamLocalForwarding no
PermitUserRC no
AllowUsers *
AllowGroups *
DenyUsers nobody
DenyGroups nobody

View File

@@ -0,0 +1,38 @@
---
- name: Disable Kernel Modules
ansible.builtin.copy:
dest: /mnt/etc/modprobe.d/cis.conf
mode: "0644"
content: |
# CIS LVL 3 Restrictions
install freevxfs /bin/false
install jffs2 /bin/false
install hfs /bin/false
install hfsplus /bin/false
install cramfs /bin/false
install squashfs /bin/false
install udf /bin/false
install usb-storage /bin/false
install dccp /bin/false
install sctp /bin/false
install rds /bin/false
install tipc /bin/false
- name: Remove old USB rules file
ansible.builtin.file:
path: /mnt/etc/udev/rules.d/10-cis_usb_devices.sh
state: absent
- name: Create USB rules
ansible.builtin.copy:
dest: /mnt/etc/udev/rules.d/10-cis_usb_devices.rules
mode: "0644"
content: |
# By default, disable all.
ACTION=="add", SUBSYSTEMS=="usb", TEST=="authorized_default", ATTR{authorized_default}="0"
# Enable hub devices.
ACTION=="add", ATTR{bDeviceClass}=="09", TEST=="authorized", ATTR{authorized}="1"
# Enable keyboard devices.
ACTION=="add", ATTR{product}=="*[Kk]eyboard*", TEST=="authorized", ATTR{authorized}="1"
# PS2-USB converter.
ACTION=="add", ATTR{product}=="*Thinnet TM*", TEST=="authorized", ATTR{authorized}="1"

View File

@@ -0,0 +1,16 @@
---
- name: Check CIS permission targets
ansible.builtin.stat:
path: "{{ item.path }}"
loop: "{{ cis_permission_targets }}"
register: cis_permission_stats
changed_when: false
- name: Set permissions for existing targets
ansible.builtin.file:
path: "{{ item.item.path }}"
owner: "{{ item.item.owner | default(omit) }}"
group: "{{ item.item.group | default(omit) }}"
mode: "{{ item.item.mode }}"
loop: "{{ cis_permission_stats.results }}"
when: item.stat.exists

View File

@@ -0,0 +1,46 @@
---
- name: Add Security related lines into config files
ansible.builtin.lineinfile:
path: "{{ item.path }}"
line: "{{ item.content }}"
loop:
- { path: /mnt/etc/security/limits.conf, content: "* hard core 0" }
- { path: /mnt/etc/security/pwquality.conf, content: minlen = 14 }
- { path: /mnt/etc/security/pwquality.conf, content: dcredit = -1 }
- { path: /mnt/etc/security/pwquality.conf, content: ucredit = -1 }
- { path: /mnt/etc/security/pwquality.conf, content: ocredit = -1 }
- { path: /mnt/etc/security/pwquality.conf, content: lcredit = -1 }
- { path: '/mnt/etc/{{ "bashrc" if is_rhel else "bash.bashrc" }}', content: umask 077 }
- { path: '/mnt/etc/{{ "bashrc" if is_rhel else "bash.bashrc" }}', content: export TMOUT=3000 }
- { path: '/mnt/{{ "usr/lib/systemd/journald.conf" if os == "fedora" else "etc/systemd/journald.conf" }}', content: Storage=persistent }
- { path: /mnt/etc/sudoers, content: Defaults logfile="/var/log/sudo.log" }
- { path: /mnt/etc/pam.d/su, content: auth required pam_wheel.so }
- path: >-
/mnt/etc/{{
"pam.d/common-auth"
if is_debian | bool
else "authselect/system-auth"
if os == "fedora"
else "pam.d/system-auth"
}}
content: >-
auth required pam_faillock.so onerr=fail audit silent deny=5 unlock_time=900
- path: >-
/mnt/etc/{{
"pam.d/common-account"
if is_debian | bool
else "authselect/system-auth"
if os == "fedora"
else "pam.d/system-auth"
}}
content: account required pam_faillock.so
- path: >-
/mnt/etc/pam.d/{{
"common-password"
if is_debian | bool
else "passwd"
}}
content: >-
password [success=1 default=ignore] pam_unix.so obscure sha512 remember=5
- { path: /mnt/etc/hosts.deny, content: "ALL: ALL" }
- { path: /mnt/etc/hosts.allow, content: "sshd: ALL" }

51
roles/cis/tasks/sshd.yml Normal file
View File

@@ -0,0 +1,51 @@
---
- name: Adjust SSHD config
ansible.builtin.lineinfile:
path: /mnt/etc/ssh/sshd_config
regexp: ^\s*#?{{ item.option }}\s+.*$
line: "{{ item.option }} {{ item.value }}"
loop:
- { option: LogLevel, value: VERBOSE }
- { option: LoginGraceTime, value: "60" }
- { option: PermitRootLogin, value: "no" }
- { option: StrictModes, value: "yes" }
- { option: MaxAuthTries, value: "4" }
- { option: MaxSessions, value: "10" }
- { option: MaxStartups, value: "10:30:60" }
- { option: PubkeyAuthentication, value: "yes" }
- { option: HostbasedAuthentication, value: "no" }
- { option: IgnoreRhosts, value: "yes" }
- { option: PasswordAuthentication, value: "no" }
- { option: PermitEmptyPasswords, value: "no" }
- { option: KerberosAuthentication, value: "no" }
- { option: GSSAPIAuthentication, value: "no" }
- { option: AllowAgentForwarding, value: "no" }
- { option: AllowTcpForwarding, value: "no" }
- { option: ChallengeResponseAuthentication, value: "no" }
- { option: GatewayPorts, value: "no" }
- { option: X11Forwarding, value: "no" }
- { option: PermitUserEnvironment, value: "no" }
- { option: ClientAliveInterval, value: "300" }
- { option: ClientAliveCountMax, value: "1" }
- { option: PermitTunnel, value: "no" }
- { option: Banner, value: /etc/issue.net }
- name: Append CIS specific configurations to sshd_config
ansible.builtin.blockinfile:
path: /mnt/etc/ssh/sshd_config
marker: "# {mark} CIS SSH HARDENING"
block: |-
## CIS Specific
Protocol 2
### Ciphers and keying ###
RekeyLimit 512M 6h
KexAlgorithms mlkem768x25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256
Ciphers aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
###########################
AllowStreamLocalForwarding no
PermitUserRC no
AllowUsers *
AllowGroups *
DenyUsers nobody
DenyGroups nobody

View File

@@ -0,0 +1,30 @@
---
- name: Create a consolidated sysctl configuration file
ansible.builtin.copy:
dest: /mnt/etc/sysctl.d/10-cis.conf
mode: "0644"
content: |
## CIS Sysctl configurations
kernel.yama.ptrace_scope=1
kernel.randomize_va_space=2
# Network
net.ipv4.ip_forward=0
net.ipv4.tcp_syncookies=1
net.ipv4.icmp_echo_ignore_broadcasts=1
net.ipv4.icmp_ignore_bogus_error_responses=1
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.accept_source_route=0
net.ipv4.conf.default.log_martians = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.default.secure_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.accept_redirects = 0
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1

View File

@@ -0,0 +1,9 @@
---
cleanup_libvirt_image_dir: >-
{{
system_cfg.path
if system_cfg is defined and (system_cfg.path | string | length) > 0
else '/var/lib/libvirt/images'
}}
cleanup_libvirt_cloudinit_path: >-
{{ [cleanup_libvirt_image_dir, hostname ~ '-cloudinit.iso'] | ansible.builtin.path_join }}

View File

@@ -0,0 +1,106 @@
---
- name: Remove Archiso and cloud-init disks
when: hypervisor_type == "libvirt"
delegate_to: localhost
become: false
block:
- name: Read current VM XML definition
community.libvirt.virt:
command: get_xml
name: "{{ hostname }}"
register: cleanup_libvirt_get_xml
changed_when: false
- name: Initialize cleaned VM XML
ansible.builtin.set_fact:
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_get_xml.get_xml }}"
changed_when: false
- name: Remove boot ISO device from VM XML (target match)
community.general.xml:
xmlstring: "{{ cleanup_libvirt_domain_xml }}"
xpath: "/domain/devices/disk[target/@dev='sda']"
state: absent
register: cleanup_libvirt_xml_strip_boot
- name: Update cleaned VM XML after removing boot ISO
ansible.builtin.set_fact:
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_boot.xmlstring }}"
changed_when: false
- name: Remove boot ISO device from VM XML (source match)
when: boot_iso is defined and boot_iso | length > 0
community.general.xml:
xmlstring: "{{ cleanup_libvirt_domain_xml }}"
xpath: "/domain/devices/disk[contains(source/@file, '{{ boot_iso | basename }}')]"
state: absent
register: cleanup_libvirt_xml_strip_boot_source
- name: Update cleaned VM XML after removing boot ISO source match
when: boot_iso is defined and boot_iso | length > 0
ansible.builtin.set_fact:
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_boot_source.xmlstring }}"
changed_when: false
- name: Remove cloud-init ISO device from VM XML (target match)
community.general.xml:
xmlstring: "{{ cleanup_libvirt_domain_xml }}"
xpath: "/domain/devices/disk[target/@dev='sdb']"
state: absent
register: cleanup_libvirt_xml_strip_cloudinit
- name: Update cleaned VM XML after removing cloud-init ISO
ansible.builtin.set_fact:
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_cloudinit.xmlstring }}"
changed_when: false
- name: Remove cloud-init ISO device from VM XML (source match)
community.general.xml:
xmlstring: "{{ cleanup_libvirt_domain_xml }}"
xpath: "/domain/devices/disk[contains(source/@file, '{{ hostname }}-cloudinit.iso')]"
state: absent
register: cleanup_libvirt_xml_strip_cloudinit_source
- name: Update cleaned VM XML after removing cloud-init ISO source match
ansible.builtin.set_fact:
cleanup_libvirt_domain_xml: "{{ cleanup_libvirt_xml_strip_cloudinit_source.xmlstring }}"
changed_when: false
- name: Strip XML declaration for libvirt define
ansible.builtin.set_fact:
cleanup_libvirt_domain_xml_clean: >-
{{
cleanup_libvirt_domain_xml
| replace('\ufeff', '')
| regex_replace("(?is)<\\?xml[^>]*\\?>", "")
| regex_replace("(?i)encoding=[\"'][^\"']+[\"']", "")
| trim
}}
changed_when: false
- name: Update VM definition without installer media
community.libvirt.virt:
command: define
xml: "{{ cleanup_libvirt_domain_xml_clean }}"
- name: Remove cloud-init disk
ansible.builtin.file:
path: "{{ cleanup_libvirt_cloudinit_path }}"
state: absent
- name: Ensure VM is powered off before restart
community.libvirt.virt:
name: "{{ hostname }}"
state: destroyed
- name: Start the VM
community.libvirt.virt:
name: "{{ hostname }}"
state: running
- name: Wait for VM to boot up
delegate_to: "{{ inventory_hostname }}"
ansible.builtin.wait_for_connection:
timeout: 300
failed_when: false
changed_when: false

View File

@@ -1,90 +1,8 @@
--- ---
- name: Unmount /mnt recursively - name: Cleanup physical install
ansible.builtin.command: umount -l /mnt when: system_cfg.type == "physical"
changed_when: result.rc == 0 ansible.builtin.include_tasks: physical.yml
register: result
- name: Setup Cleanup - name: Cleanup virtual install
when: hypervisor == "proxmox" when: system_cfg.type == "virtual"
delegate_to: localhost ansible.builtin.include_tasks: virtual.yml
become: false
block:
- name: Cleanup Setup Disks
community.general.proxmox_disk:
api_host: "{{ hypervisor_url }}"
api_user: "{{ hypervisor_username }}"
api_password: "{{ hypervisor_password }}"
name: "{{ hostname }}"
vmid: "{{ vm_id }}"
disk: "{{ item }}"
state: absent
loop:
- ide0
- ide2
- name: Remove CD-ROM from VM in vCenter
when: hypervisor == "vmware"
delegate_to: localhost
become: false
failed_when: false
community.vmware.vmware_guest:
hostname: "{{ hypervisor_url }}"
username: "{{ hypervisor_username }}"
password: "{{ hypervisor_password }}"
validate_certs: false
datacenter: "{{ hypervisor_cluster }}"
name: "{{ hostname }}"
cdrom:
- controller_number: 0
unit_number: 0
controller_type: sata
type: iso
iso_path: "{{ boot_iso }}"
state: absent
- controller_number: 0
unit_number: 1
controller_type: sata
type: iso
iso_path: "{{ rhel_iso | default(omit) }}"
state: absent
- name: Remove Archiso and cloud-init disks
when: hypervisor == "libvirt"
delegate_to: localhost
become: false
block:
- name: Stop the VM
community.libvirt.virt:
name: "{{ hostname }}"
state: shutdown
- name: Remove cloud-init disk
ansible.builtin.file:
path: "{{ vm_path | default('/var/lib/libvirt/images/') }}{{ hostname }}-cloudinit.iso"
state: absent
- name: Get list of CD-ROM devices
ansible.builtin.shell: set -o pipefail && virsh --connect qemu:///system domblklist {{ hostname }} --details | grep 'cdrom' | awk '{print $3}'
changed_when: false
register: cdrom_devices
- name: Wait for VM to spin down
ansible.builtin.wait_for:
timeout: 15
- name: Remove CD-ROM devices
when: cdrom_devices.stdout_lines | length > 0
ansible.builtin.command: virsh --connect qemu:///system detach-disk {{ hostname }} {{ item }} --persistent
with_items: "{{ cdrom_devices.stdout_lines | select('ne', 'sdc') | list }}"
changed_when: result.rc == 0
register: result
- name: Start the VM
community.libvirt.virt:
name: "{{ hostname }}"
state: running
- name: Wait for VM to boot up
delegate_to: "{{ inventory_hostname }}"
ansible.builtin.wait_for_connection:
timeout: 300

View File

@@ -0,0 +1,13 @@
---
- name: Unmount installer mounts
ansible.builtin.include_tasks: unmount.yml
- name: Trigger reboot into installed system
ansible.builtin.command:
argv:
- reboot
async: 1
poll: 0
changed_when: true
failed_when: false
ignore_unreachable: true

View File

@@ -0,0 +1,27 @@
---
- name: Setup Cleanup
when: hypervisor_type == "proxmox"
delegate_to: localhost
become: false
block:
- name: Cleanup Setup Disks
community.proxmox.proxmox_disk:
api_host: "{{ hypervisor_cfg.url }}"
api_user: "{{ hypervisor_cfg.username }}"
api_password: "{{ hypervisor_cfg.password }}"
name: "{{ hostname }}"
vmid: "{{ system_cfg.id }}"
disk: "{{ item }}"
state: absent
loop:
- ide0
- ide2
- name: Start the VM
community.proxmox.proxmox_kvm:
api_host: "{{ hypervisor_cfg.url }}"
api_user: "{{ hypervisor_cfg.username }}"
api_password: "{{ hypervisor_cfg.password }}"
node: "{{ hypervisor_cfg.host }}"
vmid: "{{ system_cfg.id }}"
state: restarted

View File

@@ -0,0 +1,4 @@
---
- name: Shutdown the VM
become: true
community.general.shutdown:

View File

@@ -0,0 +1,23 @@
---
- name: Unmount Disks
become: true
block:
- name: Disable Swap
ansible.builtin.command: swapoff -a
register: cleanup_swapoff_result
changed_when: cleanup_swapoff_result.rc == 0
- name: Unmount /mnt if mounted
ansible.builtin.command: umount -R /mnt
register: cleanup_unmount_result
changed_when: cleanup_unmount_result.rc == 0
failed_when: false
- name: Verify /mnt is no longer mounted
ansible.builtin.command: grep ' /mnt ' /proc/mounts
until: cleanup_verify_unmount.rc != 0
retries: 5
delay: 5
register: cleanup_verify_unmount
changed_when: false
failed_when: cleanup_verify_unmount.rc not in [0, 1]

View File

@@ -0,0 +1,208 @@
---
- name: Unmount installer mounts
ansible.builtin.include_tasks: unmount.yml
- name: Shutdown installer environment
ansible.builtin.include_tasks: shutdown.yml
- name: Cleanup hypervisor resources
ansible.builtin.include_tasks: proxmox.yml
- name: Cleanup vCenter resources
ansible.builtin.include_tasks: vmware.yml
- name: Cleanup libvirt resources
ansible.builtin.include_tasks: libvirt.yml
- name: Cleanup Xen resources
ansible.builtin.include_tasks: xen.yml
- name: Determine post-reboot connectivity
ansible.builtin.set_fact:
cleanup_post_reboot_can_connect: >-
{{
(
post_reboot_can_connect
if post_reboot_can_connect is defined
else (
(ansible_connection | default('ssh')) != 'ssh'
or ((system_cfg.network.ip | default('') | string | length) > 0)
or (
system_cfg.type == 'physical'
and (ansible_host | default('') | string | length) > 0
)
)
) | bool
}}
changed_when: false
- name: Check VM accessibility after reboot
when:
- system_cfg.type == "virtual"
- cleanup_post_reboot_can_connect | bool
block:
- name: Attempt to connect to VM
delegate_to: "{{ inventory_hostname }}"
ansible.builtin.wait_for_connection:
timeout: 300
register: cleanup_vm_connection_check
failed_when: false
changed_when: false
- name: VM failed to boot - initiate cleanup
when:
- cleanup_vm_connection_check is defined
- cleanup_vm_connection_check.failed | bool
block:
- name: VM boot failure detected - removing VM
ansible.builtin.debug:
msg: |
VM {{ hostname }} failed to boot after provisioning.
This VM was created in the current playbook run and will be removed
to prevent orphaned resources.
- name: Remove VM for libvirt
when:
- hypervisor_type == "libvirt"
- virtualization_vm_created_in_run | default(false) | bool
delegate_to: localhost
become: false
community.libvirt.virt:
name: "{{ hostname }}"
state: destroyed
- name: Undefine VM for libvirt
when:
- hypervisor_type == "libvirt"
- virtualization_vm_created_in_run | default(false) | bool
delegate_to: localhost
become: false
community.libvirt.virt:
name: "{{ hostname }}"
command: undefine
- name: Remove VM disk for libvirt
when:
- hypervisor_type == "libvirt"
- virtualization_vm_created_in_run | default(false) | bool
delegate_to: localhost
become: false
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop: "{{ virtualization_libvirt_disks | default([]) }}"
loop_control:
label: "{{ item.path }}"
- name: Remove cloud-init disk for libvirt
when:
- hypervisor_type == "libvirt"
- virtualization_vm_created_in_run | default(false) | bool
delegate_to: localhost
become: false
ansible.builtin.file:
path: "{{ virtualization_libvirt_cloudinit_path }}"
state: absent
- name: Remove VM for proxmox
when:
- hypervisor_type == "proxmox"
- virtualization_vm_created_in_run | default(false) | bool
delegate_to: localhost
become: false
community.proxmox.proxmox_kvm:
api_host: "{{ hypervisor_cfg.url }}"
api_user: "{{ hypervisor_cfg.username }}"
api_password: "{{ hypervisor_cfg.password }}"
node: "{{ hypervisor_cfg.host }}"
name: "{{ hostname }}"
vmid: "{{ system_cfg.id }}"
state: stopped
- name: Delete VM for proxmox
when:
- hypervisor_type == "proxmox"
- virtualization_vm_created_in_run | default(false) | bool
delegate_to: localhost
become: false
community.proxmox.proxmox_kvm:
api_host: "{{ hypervisor_cfg.url }}"
api_user: "{{ hypervisor_cfg.username }}"
api_password: "{{ hypervisor_cfg.password }}"
node: "{{ hypervisor_cfg.host }}"
name: "{{ hostname }}"
vmid: "{{ system_cfg.id }}"
state: absent
unprivileged: false
- name: Remove VM for VMware
when:
- hypervisor_type == "vmware"
- virtualization_vm_created_in_run | default(false) | bool
delegate_to: localhost
become: false
community.vmware.vmware_guest:
hostname: "{{ hypervisor_cfg.url }}"
username: "{{ hypervisor_cfg.username }}"
password: "{{ hypervisor_cfg.password }}"
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
name: "{{ hostname }}"
folder: "{{ system_cfg.path | default('/') }}"
state: poweredoff
- name: Delete VM for VMware
when:
- hypervisor_type == "vmware"
- virtualization_vm_created_in_run | default(false) | bool
delegate_to: localhost
become: false
community.vmware.vmware_guest:
hostname: "{{ hypervisor_cfg.url }}"
username: "{{ hypervisor_cfg.username }}"
password: "{{ hypervisor_cfg.password }}"
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
name: "{{ hostname }}"
folder: "{{ system_cfg.path | default('/') }}"
state: absent
- name: Destroy Xen VM if running
when:
- hypervisor_type == "xen"
- virtualization_vm_created_in_run | default(false) | bool
delegate_to: localhost
become: false
ansible.builtin.command:
argv:
- xl
- destroy
- "{{ hostname }}"
register: cleanup_xen_destroy
failed_when: false
changed_when: cleanup_xen_destroy.rc == 0
- name: Remove Xen VM disk
when:
- hypervisor_type == "xen"
- virtualization_vm_created_in_run | default(false) | bool
delegate_to: localhost
become: false
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop: "{{ virtualization_xen_disks | default([]) }}"
loop_control:
label: "{{ item.path }}"
- name: Remove Xen VM config file
when:
- hypervisor_type == "xen"
- virtualization_vm_created_in_run | default(false) | bool
delegate_to: localhost
become: false
ansible.builtin.file:
path: "/tmp/xen-{{ hostname }}.cfg"
state: absent
- name: VM cleanup completed
ansible.builtin.debug:
msg: VM {{ hostname }} has been successfully removed due to boot failure.

View File

@@ -0,0 +1,40 @@
---
- name: Clean vCenter VM
when: hypervisor_type == "vmware"
delegate_to: localhost
become: false
block:
- name: Remove CD-ROM from VM in vCenter
when: hypervisor_type == "vmware"
community.vmware.vmware_guest:
hostname: "{{ hypervisor_cfg.url }}"
username: "{{ hypervisor_cfg.username }}"
password: "{{ hypervisor_cfg.password }}"
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
datacenter: "{{ hypervisor_cfg.datacenter }}"
name: "{{ hostname }}"
cdrom:
- controller_number: 0
unit_number: 0
controller_type: sata
type: iso
iso_path: "{{ boot_iso }}"
state: absent
- controller_number: 0
unit_number: 1
controller_type: sata
type: iso
iso_path: "{{ rhel_iso if rhel_iso is defined and rhel_iso | length > 0 else omit }}"
state: absent
failed_when: false
- name: Start VM in vCenter
when: hypervisor_type == "vmware"
vmware.vmware.vm_powerstate:
hostname: "{{ hypervisor_cfg.url }}"
username: "{{ hypervisor_cfg.username }}"
password: "{{ hypervisor_cfg.password }}"
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
datacenter: "{{ hypervisor_cfg.datacenter }}"
name: "{{ hostname }}"
state: powered-on

View File

@@ -0,0 +1,58 @@
---
- name: Cleanup Xen installer media
when: hypervisor_type == "xen"
delegate_to: localhost
become: false
block:
- name: Ensure Xen disk definitions exist
when: virtualization_xen_disks is not defined
ansible.builtin.set_fact:
cleanup_xen_disks: "{{ cleanup_xen_disks | default([]) + [cleanup_xen_disk_cfg] }}"
vars:
device_letter_map: "abcdefghijklmnopqrstuvwxyz"
device_letter: "{{ device_letter_map[ansible_loop.index0] }}"
cleanup_xen_disk_cfg: >-
{{
{
'path': (
virtualization_xen_disk_path ~ '/' ~ hostname ~ '.qcow2'
if ansible_loop.index0 == 0
else virtualization_xen_disk_path ~ '/' ~ hostname ~ '-disk' ~ ansible_loop.index0 ~ '.qcow2'
),
'target': 'xvd' ~ device_letter,
'size': (item.size | float)
}
}}
loop: "{{ system_cfg.disks }}"
loop_control:
label: "{{ item | to_json }}"
extended: true
changed_when: false
- name: Render Xen VM configuration without installer media
vars:
xen_installer_media_enabled: false
virtualization_xen_disks: "{{ virtualization_xen_disks | default(cleanup_xen_disks | default([])) }}"
ansible.builtin.template:
src: xen.cfg.j2
dest: /tmp/xen-{{ hostname }}.cfg
mode: "0644"
- name: Destroy Xen VM if running
ansible.builtin.command:
argv:
- xl
- destroy
- "{{ hostname }}"
register: cleanup_xen_destroy
failed_when: false
changed_when: cleanup_xen_destroy.rc == 0
- name: Start Xen VM without installer media
ansible.builtin.command:
argv:
- xl
- create
- /tmp/xen-{{ hostname }}.cfg
register: cleanup_xen_start_result
changed_when: cleanup_xen_start_result.rc == 0

View File

@@ -0,0 +1,5 @@
---
configuration_motd_enabled: "{{ system_cfg.features.banner.motd | bool }}"
configuration_sudo_banner_enabled: "{{ system_cfg.features.banner.sudo | bool }}"
configuration_firewall_enabled: "{{ system_cfg.features.firewall.enabled | bool }}"
configuration_luks_enabled: "{{ system_cfg.luks.enabled | bool }}"

View File

@@ -0,0 +1,55 @@
---
- name: Configure MOTD
when: configuration_motd_enabled | bool
block:
- name: Create MOTD file
ansible.builtin.copy:
content: |
********************************************************************
* AUTHORIZED ACCESS ONLY. ALL ACTIVITIES ARE MONITORED AND LOGGED. *
********************************************************************
dest: /mnt/etc/motd
mode: "0644"
owner: root
group: root
- name: Remove other MOTD files
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- /mnt/etc/motd.d/99-motd
- /mnt/etc/motd.d/cockpit
- /mnt/etc/motd.d/insights-client
failed_when: false
- name: Configure sudo banner
when: configuration_sudo_banner_enabled | bool
block:
- name: Create sudoers banner directory
ansible.builtin.file:
path: /mnt/etc/sudoers.d
state: directory
mode: "0755"
owner: root
group: root
- name: Create sudo banner file
ansible.builtin.copy:
content: |
I am Groot, and I know what I'm doing.
dest: /mnt/etc/sudoers.d/banner
mode: "0644"
owner: root
group: root
- name: Enable sudo banner in sudoers
ansible.builtin.lineinfile:
path: /mnt/etc/sudoers
line: "Defaults lecture=@/etc/sudoers.d/banner"
state: present
create: true
mode: "0440"
owner: root
group: root
validate: "visudo -cf - %s"

View File

@@ -0,0 +1,65 @@
---
- name: Configure Bootloader
block:
- name: Install Bootloader
vars:
configuration_use_efibootmgr: "{{ is_rhel | bool }}"
configuration_efi_dir: "{{ partitioning_efi_mountpoint }}"
configuration_bootloader_id: >-
{{ "ubuntu" if os | lower in ["ubuntu", "ubuntu-lts"] else os }}
configuration_efi_vendor: >-
{{ "redhat" if os | lower == "rhel" else os | lower }}
configuration_efibootmgr_cmd: >-
/usr/sbin/efibootmgr -c -L '{{ os }}' -d "{{ install_drive }}" -p 1
-l '\efi\EFI\{{ configuration_efi_vendor }}\shimx64.efi'
configuration_grub_cmd: >-
/usr/sbin/grub-install --target=x86_64-efi
--efi-directory={{ configuration_efi_dir }}
--bootloader-id={{ configuration_bootloader_id }}
configuration_bootloader_cmd: >-
{{ configuration_efibootmgr_cmd if configuration_use_efibootmgr else configuration_grub_cmd }}
ansible.builtin.command: "{{ chroot_command }} {{ configuration_bootloader_cmd }}"
register: configuration_bootloader_result
changed_when: configuration_bootloader_result.rc == 0
- name: Ensure lvm2 for non btrfs filesystems
when: os | lower == "archlinux" and system_cfg.filesystem != "btrfs"
ansible.builtin.lineinfile:
path: /mnt/etc/mkinitcpio.conf
regexp: "^(HOOKS=.*block)(?!.*lvm2)(.*)"
line: "\\1 lvm2\\2"
backrefs: true
- name: Regenerate initramfs
when: os | lower not in ["alpine", "void"]
vars:
configuration_initramfs_cmd: >-
{{
'/usr/sbin/mkinitcpio -P'
if os | lower == "archlinux"
else (
'/usr/bin/env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin '
+ '/usr/sbin/update-initramfs -u -k all'
if is_debian | bool
else '/usr/bin/dracut --regenerate-all --force'
)
}}
ansible.builtin.command: "{{ chroot_command }} {{ configuration_initramfs_cmd }}"
register: configuration_initramfs_result
changed_when: configuration_initramfs_result.rc == 0
- name: Generate grub config
vars:
configuration_efi_vendor: >-
{{ "redhat" if os | lower == "rhel" else os | lower }}
configuration_grub_cfg_cmd: >-
{{
'/usr/sbin/grub2-mkconfig -o '
+ partitioning_efi_mountpoint
+ '/EFI/' + configuration_efi_vendor + '/grub.cfg'
if is_rhel | bool
else '/usr/sbin/grub-mkconfig -o /boot/grub/grub.cfg'
}}
ansible.builtin.command: "{{ chroot_command }} {{ configuration_grub_cfg_cmd }}"
register: configuration_grub_result
changed_when: configuration_grub_result.rc == 0

View File

@@ -0,0 +1,344 @@
---
- name: Configure disk encryption
when: system_cfg.luks.enabled | bool
vars:
configuration_luks_passphrase: >-
{{ system_cfg.luks.passphrase | string }}
block:
- name: Set LUKS configuration facts
vars:
luks_tpm2_pcrs: >-
{{
(
system_cfg.luks.tpm2.pcrs
if system_cfg.luks.tpm2.pcrs is string
else (system_cfg.luks.tpm2.pcrs | map('string') | join('+'))
)
| string
| replace(',', '+')
| regex_replace('\\s+', '')
| regex_replace('^\\+|\\+$', '')
}}
ansible.builtin.set_fact:
configuration_luks_mapper_name: "{{ system_cfg.luks.mapper }}"
configuration_luks_uuid: "{{ partitioning_luks_uuid | default('') }}"
configuration_luks_device: "{{ partitioning_luks_device }}"
configuration_luks_options: "{{ system_cfg.luks.options }}"
configuration_luks_auto_method: >-
{{
(system_cfg.luks.auto | bool)
| ternary(
system_cfg.luks.method,
'manual'
)
}}
configuration_luks_tpm2_device: "{{ system_cfg.luks.tpm2.device }}"
configuration_luks_tpm2_pcrs: "{{ luks_tpm2_pcrs }}"
configuration_luks_keyfile_path: "/etc/cryptsetup-keys.d/{{ system_cfg.luks.mapper }}.key"
changed_when: false
- name: Validate LUKS UUID is available
ansible.builtin.assert:
that:
- configuration_luks_uuid | length > 0
fail_msg: LUKS UUID not available. Ensure partitioning ran before configuration.
- name: Validate LUKS passphrase for auto-decrypt
when: configuration_luks_auto_method in ['tpm2', 'keyfile']
ansible.builtin.assert:
that:
- configuration_luks_passphrase | length > 0
fail_msg: system.luks.passphrase must be set for LUKS auto-decrypt.
no_log: true
- name: Enroll TPM2 for LUKS
when: configuration_luks_auto_method == 'tpm2'
ansible.builtin.include_tasks: encryption/tpm2.yml
- name: Configure LUKS keyfile auto-decrypt
when: configuration_luks_auto_method == 'keyfile'
ansible.builtin.include_tasks: encryption/keyfile.yml
- name: Build LUKS parameters
vars:
luks_keyfile_in_use: "{{ configuration_luks_auto_method == 'keyfile' }}"
luks_option_list: >-
{{
(configuration_luks_options | trim).split(',')
if configuration_luks_options | trim | length > 0
else []
}}
luks_tpm2_option_list: >-
{{
(configuration_luks_auto_method == 'tpm2')
| ternary(
['tpm2-device=' + configuration_luks_tpm2_device]
+ (['tpm2-pcrs=' + configuration_luks_tpm2_pcrs]
if configuration_luks_tpm2_pcrs | length > 0 else []),
[]
)
}}
luks_crypttab_keyfile: "{{ configuration_luks_keyfile_path if luks_keyfile_in_use else 'none' }}"
luks_crypttab_options: >-
{{
(['luks'] + luks_option_list + luks_tpm2_option_list)
| join(',')
}}
luks_rd_options: "{{ (luks_option_list + luks_tpm2_option_list) | join(',') }}"
luks_kernel_args: >-
{{
(
['rd.luks.name=' + configuration_luks_uuid + '=' + configuration_luks_mapper_name]
+ (
['rd.luks.options=' + configuration_luks_uuid + '=' + luks_rd_options]
if luks_rd_options | length > 0 else []
)
+ (
['rd.luks.key=' + configuration_luks_uuid + '=' + configuration_luks_keyfile_path]
if luks_keyfile_in_use else []
)
) | join(' ')
}}
ansible.builtin.set_fact:
configuration_luks_keyfile_in_use: "{{ luks_keyfile_in_use }}"
configuration_luks_option_list: "{{ luks_option_list }}"
configuration_luks_tpm2_option_list: "{{ luks_tpm2_option_list }}"
configuration_luks_crypttab_keyfile: "{{ luks_crypttab_keyfile }}"
configuration_luks_crypttab_options: "{{ luks_crypttab_options }}"
configuration_luks_rd_options: "{{ luks_rd_options }}"
configuration_luks_kernel_args: "{{ luks_kernel_args }}"
- name: Remove LUKS keyfile if TPM2 auto-decrypt is active
when: configuration_luks_auto_method == 'tpm2'
ansible.builtin.file:
path: /mnt{{ configuration_luks_keyfile_path }}
state: absent
- name: Write crypttab entry
ansible.builtin.lineinfile:
path: /mnt/etc/crypttab
regexp: "^{{ configuration_luks_mapper_name }}\\s"
line: >-
{{ configuration_luks_mapper_name }} UUID={{ configuration_luks_uuid }}
{{ configuration_luks_crypttab_keyfile }} {{ configuration_luks_crypttab_options }}
create: true
mode: "0600"
- name: Ensure keyfile pattern for initramfs-tools
when:
- is_debian | bool
- configuration_luks_keyfile_in_use
ansible.builtin.lineinfile:
path: /mnt/etc/cryptsetup-initramfs/conf-hook
regexp: "^KEYFILE_PATTERN="
line: "KEYFILE_PATTERN=/etc/cryptsetup-keys.d/*.key"
create: true
mode: "0644"
- name: Configure mkinitcpio hooks for LUKS
when: os | lower == 'archlinux'
ansible.builtin.lineinfile:
path: /mnt/etc/mkinitcpio.conf
regexp: "^HOOKS="
line: >-
HOOKS=(base systemd autodetect microcode modconf kms keyboard sd-vconsole
block sd-encrypt lvm2 filesystems fsck)
- name: Read mkinitcpio configuration
when: os | lower == 'archlinux'
ansible.builtin.slurp:
src: /mnt/etc/mkinitcpio.conf
register: configuration_mkinitcpio_slurp
- name: Build mkinitcpio FILES list
when: os | lower == 'archlinux'
vars:
mkinitcpio_files_list: >-
{{
(
configuration_mkinitcpio_slurp.content | b64decode
| regex_findall('^FILES=\\(([^)]*)\\)', multiline=True)
| default([])
| first
| default('')
).split()
}}
mkinitcpio_files_list_new: >-
{{
(
(mkinitcpio_files_list + [configuration_luks_keyfile_path])
if configuration_luks_keyfile_in_use
else (
mkinitcpio_files_list
| reject('equalto', configuration_luks_keyfile_path)
| list
)
)
| unique
}}
ansible.builtin.set_fact:
configuration_mkinitcpio_files_list_new: "{{ mkinitcpio_files_list_new }}"
- name: Configure mkinitcpio FILES list
when: os | lower == 'archlinux'
ansible.builtin.lineinfile:
path: /mnt/etc/mkinitcpio.conf
regexp: "^FILES="
line: >-
FILES=({{
configuration_mkinitcpio_files_list_new | join(' ')
}})
- name: Ensure dracut config directory exists
when: is_rhel | bool
ansible.builtin.file:
path: /mnt/etc/dracut.conf.d
state: directory
mode: "0755"
- name: Configure dracut for LUKS
when: is_rhel | bool
ansible.builtin.copy:
dest: /mnt/etc/dracut.conf.d/crypt.conf
content: |
add_dracutmodules+=" crypt "
{% if configuration_luks_keyfile_in_use %}
install_items+=" {{ configuration_luks_keyfile_path }} "
{% endif %}
mode: "0644"
- name: Read kernel cmdline defaults
when: is_rhel | bool
ansible.builtin.slurp:
src: /mnt/etc/kernel/cmdline
register: configuration_kernel_cmdline_slurp
- name: Build kernel cmdline with LUKS args
when: is_rhel | bool
vars:
kernel_cmdline_current: >-
{{ configuration_kernel_cmdline_slurp.content | b64decode | trim }}
kernel_cmdline_list: >-
{{
kernel_cmdline_current.split()
if kernel_cmdline_current | length > 0 else []
}}
kernel_cmdline_filtered: >-
{{
kernel_cmdline_list
| reject('match', '^rd\\.luks\\.(name|options|key)=' ~ configuration_luks_uuid ~ '=')
| list
}}
kernel_cmdline_new: >-
{{
(kernel_cmdline_filtered + configuration_luks_kernel_args.split())
| unique
| join(' ')
}}
ansible.builtin.set_fact:
configuration_kernel_cmdline_new: "{{ kernel_cmdline_new }}"
changed_when: false
- name: Write kernel cmdline with LUKS args
when: is_rhel | bool
ansible.builtin.copy:
dest: /mnt/etc/kernel/cmdline
mode: "0644"
content: "{{ configuration_kernel_cmdline_new }}\n"
- name: Find BLS entries
when: is_rhel | bool
ansible.builtin.find:
paths: /mnt/boot/loader/entries
patterns: "*.conf"
register: configuration_kernel_bls_entries
changed_when: false
- name: Update BLS options with LUKS args
when:
- is_rhel | bool
- configuration_kernel_bls_entries.files | length > 0
ansible.builtin.lineinfile:
path: "{{ item.path }}"
regexp: "^options "
line: "options {{ configuration_kernel_cmdline_new }}"
loop: "{{ configuration_kernel_bls_entries.files }}"
loop_control:
label: "{{ item.path }}"
- name: Read grub defaults
when: not is_rhel | bool
ansible.builtin.slurp:
src: /mnt/etc/default/grub
register: configuration_grub_slurp
- name: Build grub command lines with LUKS args
when: not is_rhel | bool
vars:
grub_content: "{{ configuration_grub_slurp.content | b64decode }}"
grub_cmdline_linux: >-
{{
grub_content
| regex_findall('^GRUB_CMDLINE_LINUX=\"(.*)\"', multiline=True)
| default([])
| first
| default('')
}}
grub_cmdline_default: >-
{{
grub_content
| regex_findall('^GRUB_CMDLINE_LINUX_DEFAULT=\"(.*)\"', multiline=True)
| default([])
| first
| default('')
}}
grub_cmdline_linux_list: >-
{{
grub_cmdline_linux.split()
if grub_cmdline_linux | length > 0 else []
}}
grub_cmdline_default_list: >-
{{
grub_cmdline_default.split()
if grub_cmdline_default | length > 0 else []
}}
luks_kernel_args_list: "{{ configuration_luks_kernel_args.split() }}"
grub_cmdline_linux_new: >-
{{
(
(
grub_cmdline_linux_list
| reject('match', '^rd\\.luks\\.(name|options|key)=' ~ configuration_luks_uuid ~ '=')
| list
)
+ luks_kernel_args_list
)
| unique
| join(' ')
}}
grub_cmdline_default_new: >-
{{
(
(
grub_cmdline_default_list
| reject('match', '^rd\\.luks\\.(name|options|key)=' ~ configuration_luks_uuid ~ '=')
| list
)
+ luks_kernel_args_list
)
| unique
| join(' ')
}}
ansible.builtin.set_fact:
configuration_grub_content: "{{ grub_content }}"
configuration_grub_cmdline_linux: "{{ grub_cmdline_linux }}"
configuration_grub_cmdline_default: "{{ grub_cmdline_default }}"
configuration_grub_cmdline_linux_new: "{{ grub_cmdline_linux_new }}"
configuration_grub_cmdline_default_new: "{{ grub_cmdline_default_new }}"
- name: Update GRUB_CMDLINE_LINUX_DEFAULT for LUKS
when: not is_rhel | bool
ansible.builtin.lineinfile:
path: /mnt/etc/default/grub
regexp: "^GRUB_CMDLINE_LINUX_DEFAULT="
line: 'GRUB_CMDLINE_LINUX_DEFAULT="{{ configuration_grub_cmdline_default_new }}"'

View File

@@ -0,0 +1,110 @@
---
- name: Configure LUKS keyfile auto-decrypt
block:
- name: Ensure cryptsetup key directory exists
ansible.builtin.file:
path: /mnt/etc/cryptsetup-keys.d
state: directory
owner: root
group: root
mode: "0700"
- name: Ensure LUKS keyfile exists
ansible.builtin.copy:
dest: /mnt{{ configuration_luks_keyfile_path }}
content: >-
{{
lookup(
'community.general.random_string',
length=(system_cfg.luks.keysize | int),
override_all='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
)
}}
owner: root
group: root
mode: "0600"
force: false
register: configuration_luks_keyfile_copy
no_log: true
- name: Ensure keyfile permissions
ansible.builtin.file:
path: /mnt{{ configuration_luks_keyfile_path }}
owner: root
group: root
mode: "0600"
- name: Check whether keyfile already unlocks the LUKS device
ansible.builtin.command:
argv:
- cryptsetup
- luksOpen
- --test-passphrase
- --key-file
- "/mnt{{ configuration_luks_keyfile_path }}"
- "{{ configuration_luks_device }}"
register: configuration_luks_keyfile_unlock_test
changed_when: false
failed_when: false
no_log: true
- name: Add keyfile to LUKS header
when: configuration_luks_keyfile_unlock_test.rc != 0
community.crypto.luks_device:
device: "{{ configuration_luks_device }}"
passphrase: "{{ configuration_luks_passphrase }}"
new_keyfile: "/mnt{{ configuration_luks_keyfile_path }}"
register: configuration_luks_addkey_result
failed_when: false
no_log: true
- name: Regenerate keyfile and retry adding to LUKS header
when:
- configuration_luks_keyfile_unlock_test.rc != 0
- configuration_luks_keyfile_copy is defined and configuration_luks_keyfile_copy.changed | bool
- configuration_luks_addkey_result is failed
block:
- name: Regenerate LUKS keyfile
ansible.builtin.copy:
dest: /mnt{{ configuration_luks_keyfile_path }}
content: >-
{{
lookup(
'community.general.random_string',
length=(system_cfg.luks.keysize | int),
override_all='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
)
}}
owner: root
group: root
mode: "0600"
force: true
no_log: true
- name: Retry adding keyfile to LUKS header
community.crypto.luks_device:
device: "{{ configuration_luks_device }}"
passphrase: "{{ configuration_luks_passphrase }}"
new_keyfile: "/mnt{{ configuration_luks_keyfile_path }}"
register: configuration_luks_addkey_retry
failed_when: false
no_log: true
- name: Re-check whether keyfile unlocks the LUKS device
ansible.builtin.command:
argv:
- cryptsetup
- luksOpen
- --test-passphrase
- --key-file
- "/mnt{{ configuration_luks_keyfile_path }}"
- "{{ configuration_luks_device }}"
register: configuration_luks_keyfile_unlock_test_after
changed_when: false
failed_when: false
no_log: true
- name: Fallback to manual LUKS unlock if keyfile enrollment failed
when: (configuration_luks_keyfile_unlock_test_after.rc | default(1)) != 0
ansible.builtin.set_fact:
configuration_luks_auto_method: manual

View File

@@ -0,0 +1,90 @@
---
- name: Enroll TPM2 for LUKS
block:
- name: Create temporary passphrase file for TPM2 enrollment
ansible.builtin.tempfile:
path: /mnt/tmp
prefix: luks-passphrase-
state: file
register: configuration_luks_tpm2_passphrase_tempfile
- name: Write passphrase into temporary file for TPM2 enrollment
ansible.builtin.copy:
dest: "{{ configuration_luks_tpm2_passphrase_tempfile.path }}"
content: "{{ configuration_luks_passphrase }}"
owner: root
group: root
mode: "0600"
no_log: true
- name: Enroll TPM2 token
vars:
configuration_luks_enroll_args: >-
{{
[
'/usr/bin/systemd-cryptenroll',
'--tpm2-device=' + configuration_luks_tpm2_device,
'--tpm2-with-pin=false',
'--wipe-slot=tpm2',
'--unlock-key-file=' + (
configuration_luks_tpm2_passphrase_tempfile.path
| regex_replace('^/mnt', '')
)
]
+ (['--tpm2-pcrs=' + configuration_luks_tpm2_pcrs]
if configuration_luks_tpm2_pcrs | length > 0 else [])
+ [configuration_luks_device]
}}
configuration_luks_enroll_chroot_cmd: >-
{{ chroot_command }} {{ configuration_luks_enroll_args | join(' ') }}
ansible.builtin.command: "{{ configuration_luks_enroll_chroot_cmd }}"
register: configuration_luks_tpm2_enroll_chroot
changed_when: configuration_luks_tpm2_enroll_chroot.rc == 0
failed_when: false
- name: Retry TPM2 enrollment in installer environment
when:
- (configuration_luks_tpm2_enroll_chroot.rc | default(1)) != 0
vars:
configuration_luks_enroll_args: >-
{{
[
'/usr/bin/systemd-cryptenroll',
'--tpm2-device=' + configuration_luks_tpm2_device,
'--tpm2-with-pin=false',
'--wipe-slot=tpm2',
'--unlock-key-file=' + configuration_luks_tpm2_passphrase_tempfile.path
]
+ (['--tpm2-pcrs=' + configuration_luks_tpm2_pcrs]
if configuration_luks_tpm2_pcrs | length > 0 else [])
+ [configuration_luks_device]
}}
ansible.builtin.command:
argv: "{{ configuration_luks_enroll_args }}"
register: configuration_luks_tpm2_enroll_host
changed_when: configuration_luks_tpm2_enroll_host.rc == 0
failed_when: false
- name: Validate TPM2 enrollment succeeded
ansible.builtin.assert:
that:
- >-
(configuration_luks_tpm2_enroll_chroot.rc | default(1)) == 0
or (configuration_luks_tpm2_enroll_host.rc | default(1)) == 0
fail_msg: >-
TPM2 enrollment failed.
chroot rc={{ configuration_luks_tpm2_enroll_chroot.rc | default('n/a') }},
host rc={{ configuration_luks_tpm2_enroll_host.rc | default('n/a') }},
chroot stderr={{ configuration_luks_tpm2_enroll_chroot.stderr | default('') }},
host stderr={{ configuration_luks_tpm2_enroll_host.stderr | default('') }}
rescue:
- name: Fallback to keyfile auto-decrypt
ansible.builtin.set_fact:
configuration_luks_auto_method: keyfile
always:
- name: Remove TPM2 enrollment passphrase file
when: configuration_luks_tpm2_passphrase_tempfile.path is defined
ansible.builtin.file:
path: "{{ configuration_luks_tpm2_passphrase_tempfile.path }}"
state: absent
changed_when: false

View File

@@ -0,0 +1,72 @@
---
- name: Append vim configurations to vimrc
ansible.builtin.blockinfile:
path: "{{ '/mnt/etc/vim/vimrc' if is_debian | bool else '/mnt/etc/vimrc' }}"
block: |
set encoding=utf-8
set number
set autoindent
set smartindent
set mouse=a
insertafter: EOF
marker: ""
failed_when: false
- name: Add memory tuning parameters
ansible.builtin.blockinfile:
path: /mnt/etc/sysctl.d/90-memory.conf
create: true
block: |
vm.swappiness=10
vm.vfs_cache_pressure=50
vm.dirty_background_ratio=1
vm.dirty_ratio=10
vm.page-cluster=10
marker: ""
mode: "0644"
- name: Create zram config
when:
- (os != "debian" or (os_version | string) != "11") and os != "rhel"
- os | lower not in ["alpine", "void"]
- system_cfg.features.swap.enabled | bool
ansible.builtin.copy:
dest: /mnt/etc/systemd/zram-generator.conf
content: |
[zram0]
zram-size = ram / 2
compression-algorithm = {{ 'zstd' if system_cfg.features.zstd.enabled | bool else 'lz4' }}
swap-priority = 100
fs-type = swap
mode: "0644"
- name: Copy Custom Shell config
ansible.builtin.template:
src: custom.sh.j2
dest: /mnt/etc/profile.d/custom.sh
mode: "0644"
- name: Create login banner
ansible.builtin.copy:
dest: "{{ item }}"
content: |
**************************************************************
* WARNING: Unauthorized access to this system is prohibited. *
* All activities are monitored and logged. *
* Disconnect immediately if you are not an authorized user. *
**************************************************************
owner: root
group: root
mode: "0644"
loop:
- /mnt/etc/issue
- /mnt/etc/issue.net
- name: Remove motd files
when: os == "rhel"
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- /mnt/etc/motd.d/cockpit
- /mnt/etc/motd.d/insights-client

View File

@@ -0,0 +1,65 @@
---
- name: Generate fstab content
ansible.builtin.command:
argv:
- genfstab
- -LU
- /mnt
register: configuration_fstab_result
changed_when: false
- name: Write fstab
ansible.builtin.copy:
dest: /mnt/etc/fstab
content: "{{ configuration_fstab_result.stdout }}\n"
owner: root
group: root
mode: "0644"
- name: Adjust XFS mount options and disable large extent
when: os in ["almalinux", "rocky", "rhel"] and system_cfg.filesystem == "xfs"
ansible.builtin.replace:
path: /mnt/etc/fstab
regexp: "(xfs.*?)(attr2)"
replace: "\\1allocsize=64m"
- name: Replace ISO UUID entry with /dev/sr0 in fstab
when: os == "rhel"
vars:
configuration_fstab_dvd_line: >-
{{
'/usr/local/install/redhat/rhel.iso /usr/local/install/redhat/dvd iso9660 loop,nofail 0 0'
if hypervisor_type == 'vmware'
else '/dev/sr0 /usr/local/install/redhat/dvd iso9660 ro,relatime,nojoliet,check=s,map=n,nofail 0 0'
}}
ansible.builtin.lineinfile:
path: /mnt/etc/fstab
regexp: "^.*\\/dvd.*$"
line: "{{ configuration_fstab_dvd_line }}"
state: present
- name: Write image from RHEL ISO to the target machine
when: os == "rhel" and hypervisor_type == 'vmware'
ansible.builtin.command:
argv:
- dd
- if=/dev/sr1
- of=/mnt/usr/local/install/redhat/rhel.iso
- bs=4M
creates: /mnt/usr/local/install/redhat/rhel.iso
register: configuration_rhel_iso_result
changed_when: configuration_rhel_iso_result.rc == 0
- name: Ensure TempFS is configured in fstab
ansible.builtin.lineinfile:
path: /mnt/etc/fstab
regexp: "{{ fstab_entry.regexp }}"
line: "{{ fstab_entry.line }}"
insertafter: EOF
loop:
- { regexp: "^# TempFS$", line: "# TempFS" }
- { regexp: "^tmpfs\\\\s+/tmp\\\\s+", line: "tmpfs /tmp tmpfs defaults,nosuid,nodev,noexec 0 0" }
- { regexp: "^tmpfs\\\\s+/var/tmp\\\\s+", line: "tmpfs /var/tmp tmpfs defaults,nosuid,nodev,noexec 0 0" }
- { regexp: "^tmpfs\\\\s+/dev/shm\\\\s+", line: "tmpfs /dev/shm tmpfs defaults,nosuid,nodev,noexec 0 0" }
loop_control:
loop_var: fstab_entry

View File

@@ -0,0 +1,120 @@
---
- name: Configure grub defaults
when: not is_rhel | bool
ansible.builtin.lineinfile:
dest: /mnt/etc/default/grub
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
loop:
- regexp: ^GRUB_CMDLINE_LINUX_DEFAULT=
line: GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3"
- regexp: ^GRUB_TIMEOUT=
line: GRUB_TIMEOUT=1
- name: Ensure grub defaults file exists for RHEL-based systems
when: is_rhel | bool
block:
- name: Build RHEL kernel command line defaults
vars:
grub_root_uuid: >-
{{
(
partitioning_main_uuid.stdout
if (system_cfg.filesystem | lower) == 'btrfs'
else (partitioning_uuid_root | default([]) | first | default(''))
)
| default('')
| trim
}}
grub_lvm_args: >-
{{
(
['rd.lvm.lv=sys/root']
+ (
['rd.lvm.lv=sys/swap', 'resume=/dev/mapper/sys-swap']
if system_cfg.features.swap.enabled | bool
else []
)
)
if (system_cfg.filesystem | lower) != 'btrfs'
else []
}}
grub_root_flags: >-
{{ ['rootflags=subvol=@'] if (system_cfg.filesystem | lower) == 'btrfs' else [] }}
grub_cmdline_linux_base: >-
{{
(['crashkernel=auto'] + grub_lvm_args)
| join(' ')
}}
grub_kernel_cmdline_base: >-
{{
(
(['root=UUID=' + grub_root_uuid]
if grub_root_uuid | length > 0 else [])
+ ['ro', 'crashkernel=auto']
+ grub_lvm_args
+ grub_root_flags
)
| join(' ')
}}
ansible.builtin.set_fact:
configuration_grub_cmdline_linux_base: "{{ grub_cmdline_linux_base }}"
configuration_kernel_cmdline_base: "{{ grub_kernel_cmdline_base }}"
changed_when: false
- name: Check if grub defaults file exists
ansible.builtin.stat:
path: /mnt/etc/default/grub
register: configuration_grub_defaults_stat
changed_when: false
- name: Create default grub configuration
when: not configuration_grub_defaults_stat.stat.exists
ansible.builtin.copy:
dest: /mnt/etc/default/grub
mode: "0644"
content: |
GRUB_TIMEOUT=1
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="{{ configuration_grub_cmdline_linux_base }}"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true
- name: Ensure kernel cmdline directory exists
ansible.builtin.file:
path: /mnt/etc/kernel
state: directory
mode: "0755"
- name: Write kernel cmdline defaults
ansible.builtin.copy:
dest: /mnt/etc/kernel/cmdline
mode: "0644"
content: "{{ configuration_kernel_cmdline_base }}\n"
- name: Find BLS entries
ansible.builtin.find:
paths: /mnt/boot/loader/entries
patterns: "*.conf"
register: configuration_grub_bls_entries
changed_when: false
- name: Update BLS options with kernel cmdline defaults
when: configuration_grub_bls_entries.files | length > 0
ansible.builtin.lineinfile:
path: "{{ item.path }}"
regexp: "^options "
line: "options {{ configuration_kernel_cmdline_base }}"
loop: "{{ configuration_grub_bls_entries.files }}"
loop_control:
label: "{{ item.path }}"
- name: Enable GRUB cryptodisk for encrypted /boot
when: partitioning_grub_enable_cryptodisk | bool
ansible.builtin.lineinfile:
path: /mnt/etc/default/grub
regexp: "^GRUB_ENABLE_CRYPTODISK="
line: GRUB_ENABLE_CRYPTODISK=y

View File

@@ -0,0 +1,101 @@
---
- name: Reload systemd in installer environment
when: ansible_service_mgr == 'systemd'
ansible.builtin.systemd:
daemon_reload: true
- name: Set local timezone
ansible.builtin.file:
src: /usr/share/zoneinfo/Europe/Vienna
dest: /mnt/etc/localtime
state: link
force: true
- name: Setup locales
block:
- name: Configure locale.gen
when: not is_rhel | bool
ansible.builtin.lineinfile:
dest: /mnt/etc/locale.gen
regexp: "{{ item.regex }}"
line: "{{ item.line }}"
loop:
- { regex: en_US\.UTF-8 UTF-8, line: en_US.UTF-8 UTF-8 }
- name: Generate locales
when: not is_rhel | bool
ansible.builtin.command: "{{ chroot_command }} /usr/sbin/locale-gen"
register: configuration_locale_result
changed_when: configuration_locale_result.rc == 0
- name: Set hostname
vars:
configuration_dns_domain: "{{ (system_cfg.network.dns.search | default([]) | first | default('')) | string }}"
configuration_hostname_fqdn: >-
{{
hostname
if '.' in hostname
else (
hostname + '.' + configuration_dns_domain
if configuration_dns_domain | length > 0
else hostname
)
}}
ansible.builtin.copy:
content: "{{ configuration_hostname_fqdn }}"
dest: /mnt/etc/hostname
mode: "0644"
- name: Add host entry to /etc/hosts
vars:
configuration_dns_domain: "{{ (system_cfg.network.dns.search | default([]) | first | default('')) | string }}"
configuration_hostname_fqdn: >-
{{
hostname
if '.' in hostname
else (
hostname + '.' + configuration_dns_domain
if configuration_dns_domain | length > 0
else hostname
)
}}
configuration_hostname_short: "{{ hostname.split('.')[0] }}"
configuration_hostname_entries: >-
{{ [configuration_hostname_fqdn, configuration_hostname_short] | unique | join(' ') }}
configuration_hosts_ip: >-
{{
system_cfg.network.ip
if system_cfg.network.ip is defined and (system_cfg.network.ip | string | length) > 0
else inventory_hostname
}}
configuration_hosts_line: >-
{{ configuration_hosts_ip }} {{ configuration_hostname_entries }}
ansible.builtin.lineinfile:
path: /mnt/etc/hosts
line: "{{ configuration_hosts_line }}"
state: present
- name: Create vconsole.conf
ansible.builtin.copy:
content: KEYMAP=us
dest: /mnt/etc/vconsole.conf
mode: "0644"
- name: Create locale.conf
ansible.builtin.copy:
content: LANG=en_US.UTF-8
dest: /mnt/etc/locale.conf
mode: "0644"
- name: Ensure SSH password authentication is enabled
ansible.builtin.lineinfile:
path: /mnt/etc/ssh/sshd_config
regexp: "^#?PasswordAuthentication\\s+"
line: "PasswordAuthentication yes"
- name: SSH permit root login
ansible.builtin.replace:
path: /mnt/etc/ssh/sshd_config
regexp: "^#?PermitRootLogin.*"
replace: "PermitRootLogin yes"

View File

@@ -1,267 +1,18 @@
--- ---
- name: Configuration - name: Include configuration tasks
block: ansible.builtin.include_tasks: "{{ configuration_task }}"
- name: Generate fstab loop:
ansible.builtin.shell: genfstab -LU /mnt > /mnt/etc/fstab - banner.yml
changed_when: result.rc == 0 - fstab.yml
register: result - locales.yml
- services.yml
- name: Remove depricated attr2 and disable large extent - grub.yml
when: os in ["almalinux", "rhel8", "rhel9", "rocky"] and filesystem == "xfs" - encryption.yml
ansible.builtin.replace: - bootloader.yml
path: /mnt/etc/fstab - extras.yml
regexp: '(xfs.*?)(attr2)' - network.yml
replace: '\1allocsize=64m' - users.yml
- sudo.yml
- name: Replace ISO UUID entry with /dev/sr0 in fstab - selinux.yml
when: os in ["rhel8", "rhel9"] loop_control:
ansible.builtin.lineinfile: loop_var: configuration_task
path: /mnt/etc/fstab
regexp: '^.*\/dvd.*$'
line: "{{ '/usr/local/install/redhat/rhel.iso /usr/local/install/redhat/dvd iso9660 loop,nofail 0 0' if hypervisor == 'vmware'
else '/dev/sr0 /usr/local/install/redhat/dvd iso9660 ro,relatime,nojoliet,check=s,map=n,nofail 0 0' }}"
state: present
backrefs: true
- name: Write image from RHEL ISO to the target machine
when: os in ["rhel8", "rhel9"] and hypervisor == 'vmware'
ansible.builtin.command: dd if=/dev/sr1 of=/mnt/usr/local/install/redhat/rhel.iso bs=4M
changed_when: result.rc == 0
register: result
- name: Append TempFS to fstab
ansible.builtin.lineinfile:
path: /mnt/etc/fstab
line: "{{ item }}"
insertafter: EOF
with_items:
- ""
- "# TempFS"
- tmpfs /tmp tmpfs defaults,nosuid,nodev,noexec 0 0
- tmpfs /var/tmp tmpfs defaults,nosuid,nodev,noexec 0 0
- tmpfs /dev/shm tmpfs defaults,noexec 0 0
- name: Set local timezone
ansible.builtin.command: "{{ item }}"
changed_when: result.rc == 0
register: result
with_items:
- systemctl daemon-reload
- arch-chroot /mnt ln -sf /usr/share/zoneinfo/Europe/Vienna /etc/localtime
- name: Setup locales
block:
- name: Configure locale.gen
when: os | lower not in ['almalinux', 'fedora', 'rhel8', 'rhel9', 'rocky']
ansible.builtin.lineinfile:
dest: /mnt/etc/locale.gen
regexp: "{{ item.regex }}"
line: "{{ item.line }}"
loop:
- { regex: en_US\.UTF-8 UTF-8, line: en_US.UTF-8 UTF-8 }
- name: Generate locales
when: os | lower not in ['almalinux', 'fedora', 'rhel8', 'rhel9', 'rocky']
ansible.builtin.command: arch-chroot /mnt /usr/sbin/locale-gen
changed_when: result.rc == 0
register: result
- name: Set hostname
ansible.builtin.copy:
content: "{{ hostname }}"
dest: /mnt/etc/hostname
mode: '0644'
- name: Add host entry to /etc/hosts
ansible.builtin.lineinfile:
path: /mnt/etc/hosts
line: "{{ ansible_host }} {{ hostname }}"
state: present
- name: Create vconsole.conf
ansible.builtin.copy:
content: KEYMAP=us
dest: /mnt/etc/vconsole.conf
mode: '0644'
- name: Create locale.conf
ansible.builtin.copy:
content: LANG=en_US.UTF-8
dest: /mnt/etc/locale.conf
mode: '0644'
- name: SSH permit Password
ansible.builtin.replace:
path: /mnt/etc/ssh/sshd_config
regexp: "#PasswordAuthentication yes"
replace: PasswordAuthentication yes
- name: SSH permit root login
ansible.builtin.replace:
path: /mnt/etc/ssh/sshd_config
regexp: "^#?PermitRootLogin.*"
replace: "PermitRootLogin yes"
- name: Enable Systemd Services
ansible.builtin.command: >
arch-chroot /mnt systemctl enable NetworkManager
{{
' ssh' if os | lower in ['ubuntu', 'ubuntu-lts'] else
(' sshd' if os | lower not in ['debian11', 'debian12'] else '')
}}
{{
'logrotate systemd-resolved systemd-timesyncd systemd-networkd'
if os | lower == 'archlinux' else ''
}}
changed_when: result.rc == 0
register: result
- name: Configure grub
when: os | lower not in ['almalinux', 'fedora', 'rhel8', 'rhel9', 'rocky']
block:
- name: Add commandline information to grub config
ansible.builtin.lineinfile:
dest: /mnt/etc/default/grub
regexp: ^GRUB_CMDLINE_LINUX_DEFAULT=
line: GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3"
- name: Change Grub time
ansible.builtin.lineinfile:
dest: /mnt/etc/default/grub
regexp: ^GRUB_TIMEOUT=
line: GRUB_TIMEOUT=1
- name: Configure Bootloader
block:
- name: Install Bootloader
ansible.builtin.command: arch-chroot /mnt
{% if os | lower not in ["archlinux", "debian11", "debian12", "ubuntu", "ubuntu-lts"] %} /usr/sbin/efibootmgr
-c -L '{{ os }}' -d "{{ install_drive }}" -p 1
-l '\efi\EFI\{% if os | lower in ["rhel8", "rhel9"] %}redhat{% else %}{{ os | lower }}{% endif %}\shimx64.efi'
{% else %}/usr/sbin/grub-install --target=x86_64-efi --efi-directory={{ "/boot/efi" if os | lower in ["ubuntu", "ubuntu-lts"] else "/boot" }}
--bootloader-id={{ "ubuntu" if os | lower in ["ubuntu", "ubuntu-lts"] else os }}
{% endif %}
changed_when: result.rc == 0
register: result
- name: Generate grub config
ansible.builtin.command: arch-chroot /mnt
{% if os | lower not in ["archlinux", "debian11", "debian12", "ubuntu", "ubuntu-lts"] %}
/usr/sbin/grub2-mkconfig -o /boot/efi/EFI/{% if os | lower in ["rhel8", "rhel9"] %}redhat{% else %}{{ os | lower }}{% endif %}/grub.cfg
{% else %}
/usr/sbin/grub-mkconfig -o {{ "/boot/efi/EFI/ubuntu/grub.cfg" if os | lower in ["ubuntu", "ubuntu-lts"] else "/boot/grub/grub.cfg" }}
{% endif %}
changed_when: result.rc == 0
register: result
- name: Regenerate initramfs
when: os | lower not in ["debian11", "debian12", "ubuntu", "ubuntu-lts"]
ansible.builtin.command: arch-chroot /mnt
{% if os | lower == "archlinux" %} /usr/sbin/mkinitcpio -P
{% elif os | lower not in ["debian11", "debian12", "ubuntu", "ubuntu-lts", "archlinux"] %} /usr/bin/dracut --regenerate-all --force
{% else %} echo "Skipping initramfs regeneration"
{% endif %}
changed_when: result.rc == 0
register: result
- name: Extra Configuration
block:
- name: Append lines to vimrc
failed_when: false
ansible.builtin.lineinfile:
path: "{{ '/mnt/etc/vim/vimrc' if os | lower in ['debian11', 'debian12', 'ubuntu', 'ubuntu-lts'] else '/mnt/etc/vimrc' }}"
line: "{{ item }}"
insertafter: EOF
with_items:
- set encoding=utf-8
- set number
- set autoindent
- set smartindent
- set mouse=a
- name: Copy FirstRun Script
when: os | lower != "archlinux"
ansible.builtin.template:
src: firstrun.sh.j2
dest: /mnt/root/firstrun.sh
mode: "0755"
- name: Copy Custom Shell config
ansible.builtin.template:
src: custom.sh.j2
dest: /mnt/etc/profile.d/custom.sh
mode: '0644'
- name: Setup Network
block:
- name: Generate UUID for Network Profile
ansible.builtin.command: uuidgen
changed_when: net_uuid.rc == 0
register: net_uuid
- name: Retrieve Network Interface Name
ansible.builtin.shell: set -o pipefail && ip r | awk 'NR==1 {print $5}'
changed_when: net_inf.rc == 0
register: net_inf
- name: Register MAC Address of the Network Interface
ansible.builtin.shell: ip link show "{{ net_inf.stdout }}" | awk '/link\/ether/ {print $2}' | tr '[:lower:]' '[:upper:]'
register: net_mac
changed_when: net_mac.rc == 0
- name: Copy NetworkManager keyfile
ansible.builtin.template:
src: network.j2
dest: /mnt/etc/NetworkManager/system-connections/LAN.nmconnection
mode: "0600"
- name: Fix Ubuntu unmanaged devices
when: os | lower in ["ubuntu", "ubuntu-lts"]
ansible.builtin.file:
path: /mnt/etc/NetworkManager/conf.d/10-globally-managed-devices.conf
state: touch
mode: '0644'
- name: Setup user account
block:
- name: Create user account
ansible.builtin.command: "{{ item }}"
with_items:
- arch-chroot /mnt /usr/sbin/useradd --create-home --user-group --groups
{{ "sudo" if os | lower in ["debian11", "debian12", "ubuntu", "ubuntu-lts"] else "wheel" }}
{{ user_name }} --password {{ user_password | password_hash('sha512') }} --shell /bin/bash
- arch-chroot /mnt /usr/sbin/usermod --password '{{ root_password | password_hash('sha512') }}' root --shell /bin/bash
changed_when: result.rc == 0
register: result
- name: Add SSH public key to authorized_keys
when: user_public_key is defined
ansible.builtin.lineinfile:
path: /mnt/home/{{ user_name }}/.ssh/authorized_keys
line: "{{ user_public_key }}"
owner: 1000
group: 1000
mode: "0600"
create: true
- name: Give sudo access to wheel group
ansible.builtin.copy:
content: "{{ '%sudo ALL=(ALL) ALL' if os | lower in ['debian11', 'debian12', 'ubuntu', 'ubuntu-lts'] else '%wheel ALL=(ALL) ALL' }}"
dest: /mnt/etc/sudoers.d/01-wheel
mode: "0440"
validate: /usr/sbin/visudo --check --file=%s
- name: Fix SELinux
block:
- name: Relabel the filesystem
when: os | lower in ['almalinux', 'rhel8', 'rhel9', 'rocky']
ansible.builtin.command: "arch-chroot /mnt /sbin/fixfiles onboot"
changed_when: result.rc == 0
register: result
- name: Disable SELinux
when: os | lower == "fedora"
ansible.builtin.lineinfile:
path: /mnt/etc/selinux/config
regexp: ^SELINUX=
line: SELINUX=permissive

View File

@@ -0,0 +1,165 @@
---
- name: Generate UUID for Network Profile
ansible.builtin.set_fact:
configuration_net_uuid: "{{ ('LAN-' ~ hostname) | ansible.builtin.to_uuid }}"
changed_when: false
- name: Read network interfaces
ansible.builtin.command:
argv:
- ip
- -o
- link
- show
register: configuration_ip_link
changed_when: false
failed_when: false
- name: Resolve network interface and MAC address
vars:
configuration_net_inf_from_facts: "{{ (ansible_default_ipv4 | default({})).get('interface', '') }}"
configuration_net_inf_from_ip: >-
{{
(
configuration_ip_link.stdout
| default('')
| regex_findall('^[0-9]+: ([^:]+):', multiline=True)
| reject('equalto', 'lo')
| list
| first
)
| default('')
}}
configuration_net_inf_detected: >-
{{ configuration_net_inf_from_facts | default(configuration_net_inf_from_ip, true) }}
configuration_net_inf_regex: "{{ configuration_net_inf_detected | ansible.builtin.regex_escape }}"
configuration_net_mac_from_virtualization: "{{ virtualization_mac_address | default('') }}"
configuration_net_mac_from_facts: >-
{{
(
(ansible_facts | default({})).get(configuration_net_inf_detected, {}).get('macaddress', '')
)
| default(
(ansible_facts | default({})).get('ansible_' + configuration_net_inf_detected, {}).get('macaddress', ''),
true
)
}}
configuration_net_mac_from_ip: >-
{{
(
configuration_ip_link.stdout
| default('')
| regex_findall(
'^\\d+: ' ~ configuration_net_inf_regex ~ ':.*?link/ether\\s+([0-9A-Fa-f:]{17})',
multiline=True
)
| first
)
| default('')
}}
ansible.builtin.set_fact:
configuration_net_inf: "{{ configuration_net_inf_detected }}"
configuration_net_mac: >-
{{
(
configuration_net_mac_from_virtualization
| default(configuration_net_mac_from_facts, true)
| default(configuration_net_mac_from_ip, true)
)
| upper
}}
changed_when: false
- name: Validate Network Interface Name
ansible.builtin.assert:
that:
- configuration_net_inf | length > 0
fail_msg: Failed to detect an active network interface.
- name: Validate Network Interface MAC Address
ansible.builtin.assert:
that:
- configuration_net_mac | length > 0
fail_msg: Failed to detect the MAC address for network interface {{ configuration_net_inf }}.
- name: Configure NetworkManager profile
when: os | lower not in ["alpine", "void"]
block:
- name: Copy NetworkManager keyfile
ansible.builtin.template:
src: network.j2
dest: /mnt/etc/NetworkManager/system-connections/LAN.nmconnection
mode: "0600"
- name: Fix Ubuntu unmanaged devices
when: os | lower in ["ubuntu", "ubuntu-lts"]
ansible.builtin.file:
path: /mnt/etc/NetworkManager/conf.d/10-globally-managed-devices.conf
state: touch
mode: "0644"
- name: Configure Alpine networking
when: os | lower == "alpine"
vars:
configuration_dns_list: "{{ system_cfg.network.dns.servers | default([]) }}"
configuration_alpine_static: >-
{{
system_cfg.network.ip is defined
and system_cfg.network.ip | string | length > 0
and system_cfg.network.prefix is defined
and (system_cfg.network.prefix | string | length) > 0
}}
block:
- name: Write Alpine network interfaces
ansible.builtin.copy:
dest: /mnt/etc/network/interfaces
mode: "0644"
content: |
auto lo
iface lo inet loopback
auto {{ configuration_net_inf }}
iface {{ configuration_net_inf }} inet {{ 'static' if configuration_alpine_static | bool else 'dhcp' }}
{% if configuration_alpine_static | bool %}
address {{ system_cfg.network.ip }}/{{ system_cfg.network.prefix }}
{% if system_cfg.network.gateway is defined and system_cfg.network.gateway | string | length %}
gateway {{ system_cfg.network.gateway }}
{% endif %}
{% endif %}
- name: Set Alpine DNS resolvers
when: configuration_dns_list | length > 0
ansible.builtin.copy:
dest: /mnt/etc/resolv.conf
mode: "0644"
content: |
{% for resolver in configuration_dns_list %}
nameserver {{ resolver }}
{% endfor %}
- name: Configure Void networking
when: os | lower == "void"
vars:
configuration_dns_list: "{{ system_cfg.network.dns.servers | default([]) }}"
configuration_void_static: >-
{{
system_cfg.network.ip is defined
and system_cfg.network.ip | string | length > 0
and system_cfg.network.prefix is defined
and (system_cfg.network.prefix | string | length) > 0
}}
block:
- name: Write dhcpcd configuration for static networking
when: configuration_void_static | bool
ansible.builtin.copy:
dest: /mnt/etc/dhcpcd.conf
mode: "0644"
content: |
interface {{ configuration_net_inf }}
static ip_address={{ system_cfg.network.ip }}/{{ system_cfg.network.prefix }}
{% if system_cfg.network.gateway is defined and system_cfg.network.gateway | string | length %}
static routers={{ system_cfg.network.gateway }}
{% endif %}
{% if configuration_dns_list | length > 0 %}
static domain_name_servers={{ configuration_dns_list | join(' ') }}
{% endif %}

View File

@@ -0,0 +1,19 @@
---
- name: Fix SELinux
when: is_rhel | bool
block:
- name: Fix SELinux by pre-labeling the filesystem before first boot
when: os in ['almalinux', 'rocky', 'rhel'] and system_cfg.features.selinux.enabled | bool
ansible.builtin.command: >
{{ chroot_command }} /sbin/setfiles -v -F
-e /dev -e /proc -e /sys -e /run
/etc/selinux/targeted/contexts/files/file_contexts /
register: configuration_setfiles_result
changed_when: configuration_setfiles_result.rc == 0
- name: Disable SELinux
when: os | lower == "fedora" or not system_cfg.features.selinux.enabled | bool
ansible.builtin.lineinfile:
path: /mnt/etc/selinux/config
regexp: ^SELINUX=
line: SELINUX=permissive

View File

@@ -0,0 +1,79 @@
---
- name: Enable Systemd Services
when: os | lower not in ['alpine', 'void']
ansible.builtin.command: >
{{ chroot_command }} systemctl enable NetworkManager
{{ ' firewalld' if system_cfg.features.firewall.backend == 'firewalld' and system_cfg.features.firewall.enabled | bool else '' }}
{{ ' ufw' if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else '' }}
{{
(' ssh' if is_debian | bool else ' sshd')
if system_cfg.features.ssh.enabled | bool else ''
}}
{{
'logrotate systemd-resolved systemd-timesyncd systemd-networkd'
if os | lower == 'archlinux' else ''
}}
register: configuration_enable_services_result
changed_when: configuration_enable_services_result.rc == 0
- name: Enable OpenRC services
when: os | lower == 'alpine'
vars:
configuration_openrc_services: >-
{{
['networking']
+ (['sshd'] if system_cfg.features.ssh.enabled | bool else [])
+ ([system_cfg.features.firewall.backend] if system_cfg.features.firewall.enabled | bool else [])
}}
block:
- name: Ensure OpenRC runlevel directory exists
ansible.builtin.file:
path: /mnt/etc/runlevels/default
state: directory
mode: "0755"
- name: Check OpenRC init scripts
ansible.builtin.stat:
path: "/mnt/etc/init.d/{{ item }}"
loop: "{{ configuration_openrc_services }}"
register: configuration_openrc_service_stats
changed_when: false
- name: Enable OpenRC services
ansible.builtin.file:
src: "/mnt/etc/init.d/{{ item.item }}"
dest: "/mnt/etc/runlevels/default/{{ item.item }}"
state: link
loop: "{{ configuration_openrc_service_stats.results }}"
when: item.stat.exists
- name: Enable runit services
when: os | lower == 'void'
vars:
configuration_runit_services: >-
{{
['dhcpcd']
+ (['sshd'] if system_cfg.features.ssh.enabled | bool else [])
+ ([system_cfg.features.firewall.backend] if system_cfg.features.firewall.enabled | bool else [])
}}
block:
- name: Ensure runit service directory exists
ansible.builtin.file:
path: /mnt/var/service
state: directory
mode: "0755"
- name: Check runit service definitions
ansible.builtin.stat:
path: "/mnt/etc/sv/{{ item }}"
loop: "{{ configuration_runit_services }}"
register: configuration_runit_service_stats
changed_when: false
- name: Enable runit services
ansible.builtin.file:
src: "/mnt/etc/sv/{{ item.item }}"
dest: "/mnt/var/service/{{ item.item }}"
state: link
loop: "{{ configuration_runit_service_stats.results }}"
when: item.stat.exists

View File

@@ -0,0 +1,7 @@
---
- name: Give sudo access to wheel group
ansible.builtin.copy:
content: "{{ '%sudo ALL=(ALL) ALL' if is_debian | bool else '%wheel ALL=(ALL) ALL' }}"
dest: /mnt/etc/sudoers.d/01-wheel
mode: "0440"
validate: /usr/sbin/visudo --check --file=%s

View File

@@ -0,0 +1,37 @@
---
- name: Create user account
vars:
configuration_user_group: >-
{{ "sudo" if is_debian | bool else "wheel" }}
configuration_useradd_cmd: >-
{{ chroot_command }} /usr/sbin/useradd --create-home --user-group
--groups {{ configuration_user_group }} {{ system_cfg.user.name }}
--password {{ system_cfg.user.password | password_hash('sha512') }} --shell /bin/bash
configuration_root_cmd: >-
{{ chroot_command }} /usr/sbin/usermod --password
'{{ system_cfg.root.password | password_hash('sha512') }}' root --shell /bin/bash
ansible.builtin.command: "{{ item }}"
loop:
- "{{ configuration_useradd_cmd }}"
- "{{ configuration_root_cmd }}"
register: configuration_user_result
changed_when: configuration_user_result.rc == 0
- name: Ensure .ssh directory exists
when: system_cfg.user.key | length > 0
ansible.builtin.file:
path: /mnt/home/{{ system_cfg.user.name }}/.ssh
state: directory
owner: 1000
group: 1000
mode: "0700"
- name: Add SSH public key to authorized_keys
when: system_cfg.user.key | length > 0
ansible.builtin.lineinfile:
path: /mnt/home/{{ system_cfg.user.name }}/.ssh/authorized_keys
line: "{{ system_cfg.user.key }}"
owner: 1000
group: 1000
mode: "0600"
create: true

View File

@@ -9,4 +9,7 @@ PROMPT_COMMAND="history -a;$PROMPT_COMMAND"
# History Size # History Size
HISTFILESIZE= HISTFILESIZE=
HISTSIZE= HISTSIZE=
# Enable vi mode
set -o vi

View File

@@ -1,16 +1,26 @@
[connection] [connection]
id=LAN id=LAN
uuid={{ net_uuid.stdout }} uuid={{ configuration_net_uuid }}
type=ethernet type=ethernet
interface-name={{ net_inf.stdout }}
[ethernet]
mac-address={{ net_mac.stdout }}
[ipv4] [ipv4]
address={{ vm_ip }},{{ vm_gw }} {% set dns_list = system_cfg.network.dns.servers | default([]) %}
dns={{ vm_dns }} {% set search_list = system_cfg.network.dns.search | default([]) %}
{% if system_cfg.network.ip is defined and system_cfg.network.ip | string | length %}
address1={{ system_cfg.network.ip }}/{{ system_cfg.network.prefix }}{{ (',' ~ system_cfg.network.gateway) if (system_cfg.network.gateway is defined and system_cfg.network.gateway | string | length) else '' }}
method=manual method=manual
{% else %}
method=auto
{% endif %}
{% if dns_list %}
dns={{ dns_list | join(';') }}
{% endif %}
{% if dns_list %}
ignore-auto-dns=true
{% endif %}
{% if search_list %}
dns-search={{ search_list | join(';') }}
{% endif %}
[ipv6] [ipv6]
addr-gen-mode=stable-privacy addr-gen-mode=stable-privacy

View File

@@ -1,130 +1,265 @@
--- ---
- name: Configre work environment - name: Configure work environment
become: true become: "{{ hypervisor_type != 'vmware' }}"
block: block:
- name: Wait for connection - name: Wait for connection
ansible.builtin.wait_for_connection: ansible.builtin.wait_for_connection:
timeout: 60 timeout: 180
delay: 5 delay: 5
- name: Gather facts - name: Gather facts
ansible.builtin.setup: ansible.builtin.setup:
- name: Check if host is booted from the Arch install media - name: Check for live environment markers
ansible.builtin.stat: ansible.builtin.stat:
path: /run/archiso path: "{{ item }}"
register: archiso_stat loop:
- /run/archiso
- /run/live
- /run/initramfs
- /run/initramfs/live
register: environment_live_marker_stat
changed_when: false
- name: Determine root filesystem type
ansible.builtin.set_fact:
environment_root_fstype: >-
{{
ansible_mounts
| selectattr('mount', 'equalto', '/')
| map(attribute='fstype')
| list
| first
| default('')
| lower
}}
environment_archiso_present: >-
{{
(
environment_live_marker_stat.results
| selectattr('item', 'equalto', '/run/archiso')
| selectattr('stat.exists')
| list
| length
) > 0
}}
changed_when: false
- name: Identify live environment indicators
ansible.builtin.set_fact:
environment_is_live_environment: >-
{{
(
environment_live_marker_stat.results
| selectattr('stat.exists')
| list
| length
) > 0
or environment_root_fstype in ['overlay', 'overlayfs', 'squashfs', 'aufs']
or (ansible_hostname | default('') | lower is search('live'))
}}
changed_when: false
- name: Abort if target is not a live environment
ansible.builtin.assert:
that:
- environment_is_live_environment | bool
fail_msg: |
PRODUCTION SYSTEM DETECTED - ABORTING
The target system does not appear to be a live installer environment.
This playbook must run from a live ISO to avoid wiping production data.
Boot from a live installer (Arch, Debian, Ubuntu, etc.) and retry.
quiet: true
- name: Abort if the host is not booted from the Arch install media - name: Abort if the host is not booted from the Arch install media
when:
- not (custom_iso | bool)
- not environment_archiso_present | bool
ansible.builtin.fail: ansible.builtin.fail:
msg: This host is not booted from the Arch install media! msg: This host is not booted from the Arch install media!
when: not archiso_stat.stat.exists
- name: Setect Interface - name: Select primary Network Interface
when: hypervisor == "vmware" when: hypervisor_type == "vmware"
ansible.builtin.shell: "set -o pipefail && ip l | awk -F': ' '!/lo/{print $2; exit}'" ansible.builtin.set_fact:
changed_when: interface_name.rc == 0 environment_interface_name: >-
register: interface_name {{
(
(ansible_facts.interfaces | default(ansible_facts['ansible_interfaces'] | default([])))
| reject('equalto', 'lo')
| list
| first
)
| default('')
}}
changed_when: false
- name: Set IP-Address - name: Set IP-Address
when: hypervisor == "vmware" when:
ansible.builtin.command: "ip addr replace {{ ansible_host }}/{{ vm_nms | default(24) }} dev {{ interface_name.stdout }}" - hypervisor_type == "vmware"
changed_when: result.rc == 0 - system_cfg.network.ip is defined and system_cfg.network.ip | string | length > 0
register: result ansible.builtin.command: >-
ip addr replace {{ system_cfg.network.ip }}/{{ system_cfg.network.prefix }}
dev {{ environment_interface_name }}
register: environment_ip_result
changed_when: environment_ip_result.rc == 0
- name: Set Default Gateway - name: Set Default Gateway
when: hypervisor == "vmware" when:
ansible.builtin.command: "ip route replace default via {{ vm_gw }}" - hypervisor_type == "vmware"
changed_when: result.rc == 0 - system_cfg.network.gateway is defined and system_cfg.network.gateway | string | length > 0
register: result - system_cfg.network.ip is defined and system_cfg.network.ip | string | length > 0
ansible.builtin.command: "ip route replace default via {{ system_cfg.network.gateway }}"
register: environment_gateway_result
changed_when: environment_gateway_result.rc == 0
- name: Synchronize clock via NTP - name: Synchronize clock via NTP
ansible.builtin.command: timedatectl set-ntp true ansible.builtin.command: timedatectl set-ntp true
changed_when: result.rc == 0 register: environment_ntp_result
register: result changed_when: environment_ntp_result.rc == 0
- name: Configure SSH for root login - name: Configure SSH for root login
when: hypervisor == "vmware" and vmware_ssh | bool when: hypervisor_type == "vmware" and hypervisor_cfg.ssh | bool
block: block:
- name: Allow empty passwords temporarily - name: Allow login
ansible.builtin.replace: ansible.builtin.replace:
path: /etc/ssh/sshd_config path: /etc/ssh/sshd_config
regexp: "^#?PermitEmptyPasswords.*" regexp: "{{ item.regexp }}"
replace: "PermitEmptyPasswords yes" replace: "{{ item.replace }}"
loop:
- name: Allow root login - regexp: "^#?PermitEmptyPasswords.*"
ansible.builtin.replace: replace: "PermitEmptyPasswords yes"
path: /etc/ssh/sshd_config - regexp: "^#?PermitRootLogin.*"
regexp: "^#?PermitRootLogin.*" replace: "PermitRootLogin yes"
replace: "PermitRootLogin yes"
- name: Reload SSH service to apply changes - name: Reload SSH service to apply changes
ansible.builtin.service: ansible.builtin.service:
name: sshd name: sshd
state: reloaded state: reloaded
- name: Set connection back to SSH - name: Set SSH connection for VMware
ansible.builtin.set_fact: ansible.builtin.set_fact:
ansible_connection: ssh ansible_connection: ssh
ansible_user: "root" ansible_user: root
ansible_password: ""
ansible_become_password: ""
ansible_ssh_extra_args: '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
- name: Speed-up Bootstrap process - name: Prepare installer environment
ansible.builtin.lineinfile:
path: /etc/pacman.conf
regexp: ^#ParallelDownloads =
line: ParallelDownloads = 20
- name: Wait for Pacman
ansible.builtin.wait_for:
timeout: 15
- name: Setup Pacman
community.general.pacman:
update_cache: true
force: true
name: "{{ item.name }}"
state: latest
loop:
- { name: glibc }
- { name: dnf, os: [almalinux, fedora, rhel9, rhel8, rocky] }
- { name: debootstrap, os: [debian11, debian12, ubuntu, ubuntu-lts] }
- { name: debian-archive-keyring, os: [debian11, debian12] }
- { name: ubuntu-keyring, os: [ubuntu, ubuntu-lts] }
when: "'os' not in item or os in item.os"
retries: 4
delay: 15
- name: Prepare /iso mount and repository for RHEL-based systems
when: os | lower in ["rhel8", "rhel9"]
block: block:
- name: Create /iso directory - name: Speed-up Bootstrap process
ansible.builtin.file: when: not (custom_iso | bool)
path: /usr/local/install/redhat/dvd ansible.builtin.lineinfile:
state: directory path: /etc/pacman.conf
mode: '0755' regexp: ^#ParallelDownloads =
line: ParallelDownloads = 20
- name: Mount RHEL ISO - name: Wait for pacman lock to be released
ansible.posix.mount: when: not (custom_iso | bool)
src: "{{ '/dev/sr1' if hypervisor == 'vmware' else '/dev/sr2' }}" ansible.builtin.wait_for:
path: /usr/local/install/redhat/dvd path: /var/lib/pacman/db.lck
fstype: iso9660 state: absent
opts: "ro,loop" timeout: 120
state: mounted changed_when: false
- name: Configure RHEL Repos for installation - name: Setup Pacman
when: os | lower in ["almalinux", "fedora", "rhel8", "rhel9", "rocky"] when:
- not (custom_iso | bool)
- item.os is not defined or (os_resolved | default(os)) in item.os
community.general.pacman:
update_cache: true
force: true
name: "{{ item.name }}"
state: latest
loop:
- { name: glibc }
- { name: dnf, os: [almalinux8, almalinux9, almalinux10, fedora40, fedora41, fedora42, fedora43, rhel8, rhel9, rhel10, rocky8, rocky9, rocky10] }
- { name: debootstrap, os: [debian10, debian11, debian12, debian13, debianunstable, ubuntu, ubuntu-lts] }
- { name: debian-archive-keyring, os: [debian10, debian11, debian12, debian13, debianunstable] }
- { name: ubuntu-keyring, os: [ubuntu, ubuntu-lts] }
retries: 4
delay: 15
- name: Prepare /iso mount and repository for RHEL-based systems
when: os == "rhel"
block:
- name: Create /iso directory
ansible.builtin.file:
path: /usr/local/install/redhat/dvd
state: directory
mode: "0755"
- name: Select RHEL ISO device
ansible.builtin.set_fact:
environment_rhel_iso_device: >-
{{
'/dev/sr2'
if hypervisor_type == 'libvirt'
else '/dev/sr1'
}}
changed_when: false
- name: Mount RHEL ISO
ansible.posix.mount:
src: "{{ environment_rhel_iso_device }}"
path: /usr/local/install/redhat/dvd
fstype: iso9660
opts: "ro,loop"
state: mounted
- name: Configure RHEL Repos for installation
when: is_rhel | bool
block:
- name: Select repository template
ansible.builtin.set_fact:
environment_repo_template: >-
{{
(os_resolved | default(os)) | lower
if os == 'rhel'
else os | lower
}}
changed_when: false
- name: Create directories for repository files and RPM GPG keys
ansible.builtin.file:
path: /etc/yum.repos.d
state: directory
mode: "0755"
- name: Create RHEL repository file
ansible.builtin.template:
src: "{{ environment_repo_template }}.repo.j2"
dest: /etc/yum.repos.d/{{ environment_repo_template }}.repo
mode: "0644"
- name: Check for third-party preparation tasks
run_once: true
become: false
delegate_to: localhost
vars:
ansible_connection: local
block: block:
- name: Create directories for repository files and RPM GPG keys - name: Resolve third-party preparation task path
ansible.builtin.file: ansible.builtin.set_fact:
path: /etc/yum.repos.d environment_thirdparty_tasks_path: >-
state: directory {{
mode: '0755' thirdparty_preparation_tasks_path
if thirdparty_preparation_tasks_path | regex_search('^/')
else playbook_dir + '/' + thirdparty_preparation_tasks_path
}}
changed_when: false
- name: Create RHEL repository file - name: Stat third-party preparation tasks
ansible.builtin.template: ansible.builtin.stat:
src: "{{ os | lower }}.repo.j2" path: "{{ environment_thirdparty_tasks_path }}"
dest: /etc/yum.repos.d/{{ os | lower }}.repo register: environment_thirdparty_tasks_stat
mode: '0644' changed_when: false
- name: Run third-party preparation tasks
when:
- thirdparty_preparation_tasks_path | length > 0
- environment_thirdparty_tasks_stat.stat.exists
ansible.builtin.include_tasks: >-
{{
thirdparty_preparation_tasks_path
if thirdparty_preparation_tasks_path | regex_search('^/')
else playbook_dir + '/' + thirdparty_preparation_tasks_path
}}

View File

@@ -0,0 +1,95 @@
---
# User input. Normalized into hypervisor_cfg + hypervisor_type.
hypervisor:
type: "none"
hypervisor_defaults:
type: "none"
url: ""
username: ""
password: ""
host: ""
storage: ""
datacenter: ""
cluster: ""
certs: false
ssh: false
custom_iso: false
thirdparty_preparation_tasks_path: "dropins/preparation.yml"
system_defaults:
type: "virtual" # virtual|physical
os: ""
version: ""
filesystem: ""
name: ""
id: ""
cpus: 0
memory: 0 # MiB
balloon: 0 # MiB
network:
bridge: ""
vlan: ""
ip: ""
prefix: ""
gateway: ""
dns:
servers: []
search: []
path: ""
packages: []
disks: []
user:
name: ""
password: ""
key: ""
root:
password: ""
luks:
enabled: false
passphrase: ""
mapper: "SYSTEM_DECRYPTED"
auto: true
method: "tpm2"
tpm2:
device: "auto"
pcrs: ""
keysize: 64
options: "discard,tries=3"
type: "luks2"
cipher: "aes-xts-plain64"
hash: "sha512"
iter: 4000
bits: 512
pbkdf: "argon2id"
urandom: true
verify: true
features:
cis:
enabled: false
selinux:
enabled: true
firewall:
enabled: true
backend: "firewalld" # firewalld|ufw
toolkit: "nftables" # nftables|iptables
ssh:
enabled: true
zstd:
enabled: true
swap:
enabled: true
banner:
motd: false
sudo: true
chroot:
tool: "arch-chroot" # arch-chroot|chroot|systemd-nspawn
system_disk_defaults:
size: 0
device: ""
mount:
path: ""
fstype: ""
label: ""
opts: "defaults"

View File

@@ -0,0 +1,22 @@
---
- name: Ensure hypervisor input is a dictionary
ansible.builtin.set_fact:
hypervisor: "{{ hypervisor | default({}) }}"
changed_when: false
- name: Validate hypervisor input
ansible.builtin.assert:
that:
- hypervisor is mapping
- hypervisor.type is defined
- hypervisor.type | string | length > 0
fail_msg: "hypervisor must be a dictionary and hypervisor.type must be set (e.g. libvirt|proxmox|vmware|xen|none)."
quiet: true
- name: Normalize hypervisor configuration
vars:
merged: "{{ hypervisor_defaults | combine(hypervisor, recursive=True) }}"
ansible.builtin.set_fact:
hypervisor_cfg: "{{ merged }}"
hypervisor_type: "{{ merged.type | string | lower }}"
changed_when: false

View File

@@ -0,0 +1,79 @@
---
- name: Global defaults loaded
ansible.builtin.debug:
msg: Global defaults loaded.
changed_when: false
- name: Normalize hypervisor inputs
ansible.builtin.include_tasks: hypervisor.yml
- name: Normalize system inputs
ansible.builtin.include_tasks: system.yml
- name: Validate variables
ansible.builtin.include_tasks: validation.yml
- name: Set OS family flags
ansible.builtin.set_fact:
is_rhel: "{{ os | lower in ['almalinux', 'fedora', 'rhel', 'rocky'] }}"
is_debian: "{{ os | lower in ['debian', 'ubuntu', 'ubuntu-lts'] }}"
changed_when: false
- name: Normalize OS version for keying
when:
- os_version is defined
- (os_version | string | length) > 0
ansible.builtin.set_fact:
os_version_major: "{{ (os_version | string).split('.')[0] }}"
changed_when: false
- name: Resolve final OS key with version
when:
- os_version is defined
- (os_version | string | length) > 0
ansible.builtin.set_fact:
os_resolved: >-
{{
'debian' + os_version | string if os == 'debian'
else 'fedora' + os_version | string if os == 'fedora'
else 'rocky' + os_version_major if os == 'rocky'
else 'almalinux' + os_version_major if os == 'almalinux'
else 'rhel' + os_version_major if os == 'rhel'
else os
}}
changed_when: false
- name: Set chroot command wrapper
ansible.builtin.set_fact:
chroot_command: >-
{{
'systemd-nspawn -D /mnt'
if (system_cfg.features.chroot.tool | default('arch-chroot')) == 'systemd-nspawn'
else (system_cfg.features.chroot.tool | default('arch-chroot')) ~ ' /mnt'
}}
changed_when: false
- name: Set Python interpreter for RHEL-based installers
when:
- ansible_python_interpreter is not defined
- is_rhel | bool
ansible.builtin.set_fact:
ansible_python_interpreter: /usr/bin/python3
changed_when: false
- name: Set SSH access
when:
- system_cfg.type == "virtual"
- hypervisor_type != "vmware"
ansible.builtin.set_fact:
ansible_user: "{{ system_cfg.user.name }}"
ansible_password: "{{ system_cfg.user.password }}"
ansible_become_password: "{{ system_cfg.user.password }}"
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
changed_when: false
- name: Set connection for VMware
when: hypervisor_type == "vmware"
ansible.builtin.set_fact:
ansible_connection: vmware_tools
changed_when: false

View File

@@ -0,0 +1,258 @@
---
- name: Ensure system input is a dictionary
ansible.builtin.set_fact:
system: "{{ system | default({}) }}"
changed_when: false
- name: Validate system input types
ansible.builtin.assert:
that:
- system is mapping
- system.network is not defined or system.network is mapping
- system.user is not defined or system.user is mapping
- system.root is not defined or system.root is mapping
- system.luks is not defined or system.luks is mapping
- system.features is not defined or system.features is mapping
fail_msg: "system and its nested keys (network, user, root, luks, features) must be dictionaries."
quiet: true
- name: Validate system features input types
when: system.features is defined
loop: "{{ system_defaults.features | dict2items | map(attribute='key') | list }}"
loop_control:
label: "system.features.{{ item }}"
ansible.builtin.assert:
that:
- (system.features[item] | default({})) is mapping
fail_msg: "system.features.{{ item }} must be a dictionary."
quiet: true
- name: Validate system LUKS TPM2 input type
when: system.luks is defined and system.luks is mapping
ansible.builtin.assert:
that:
- system.luks.tpm2 is not defined or system.luks.tpm2 is mapping
fail_msg: "system.luks.tpm2 must be a dictionary."
quiet: true
- name: Build normalized system configuration
vars:
system_raw: "{{ system_defaults | combine(system, recursive=True) }}"
system_type: "{{ system_raw.type | string | lower }}"
system_os_input: "{{ system_raw.os | default('') | string | lower }}"
system_name: >-
{{
system_raw.name | string | trim
if (system_raw.name | default('') | string | trim | length) > 0
else inventory_hostname
}}
ansible.builtin.set_fact:
system_cfg:
type: "{{ system_type }}"
os: "{{ system_os_input if system_os_input | length > 0 else ('archlinux' if system_type == 'physical' else '') }}"
version: "{{ system_raw.version | default('') | string }}"
filesystem: "{{ system_raw.filesystem | default('') | string | lower }}"
name: "{{ system_name }}"
id: "{{ system_raw.id | default('') | string }}"
cpus: "{{ [system_raw.cpus | default(0) | int, 0] | max }}"
memory: "{{ [system_raw.memory | default(0) | int, 0] | max }}"
balloon: "{{ [system_raw.balloon | default(0) | int, 0] | max }}"
network:
bridge: "{{ system_raw.network.bridge | default('') | string }}"
vlan: "{{ system_raw.network.vlan | default('') | string }}"
ip: "{{ system_raw.network.ip | default('') | string }}"
prefix: >-
{{
(system_raw.network.prefix | int)
if (system_raw.network.prefix | default('') | string | length) > 0
else ''
}}
gateway: "{{ system_raw.network.gateway | default('') | string }}"
dns:
servers: >-
{{
(
system_raw.network.dns.servers
if system_raw.network.dns.servers is iterable and system_raw.network.dns.servers is not string
else (system_raw.network.dns.servers | string).split(',')
)
| map('trim')
| reject('equalto', '')
| list
}}
search: >-
{{
(
system_raw.network.dns.search
if system_raw.network.dns.search is iterable and system_raw.network.dns.search is not string
else (system_raw.network.dns.search | string).split(',')
)
| map('trim')
| reject('equalto', '')
| list
}}
path: "{{ system_raw.path | default('') | string }}"
packages: >-
{{
(
system_raw.packages
if system_raw.packages is iterable and system_raw.packages is not string
else (system_raw.packages | string).split(',')
)
| map('trim')
| reject('equalto', '')
| list
}}
disks: "{{ system_raw.disks | default([]) }}"
user:
name: "{{ system_raw.user.name | string }}"
password: "{{ system_raw.user.password | string }}"
key: "{{ system_raw.user.key | string }}"
root:
password: "{{ system_raw.root.password | string }}"
luks:
enabled: "{{ system_raw.luks.enabled | bool }}"
passphrase: "{{ system_raw.luks.passphrase | string }}"
mapper: "{{ system_raw.luks.mapper | string }}"
auto: "{{ system_raw.luks.auto | bool }}"
method: "{{ system_raw.luks.method | string | lower }}"
tpm2:
device: "{{ system_raw.luks.tpm2.device | string }}"
pcrs: "{{ system_raw.luks.tpm2.pcrs | string }}"
keysize: "{{ system_raw.luks.keysize | int }}"
options: "{{ system_raw.luks.options | string }}"
type: "{{ system_raw.luks.type | string }}"
cipher: "{{ system_raw.luks.cipher | string }}"
hash: "{{ system_raw.luks.hash | string }}"
iter: "{{ system_raw.luks.iter | int }}"
bits: "{{ system_raw.luks.bits | int }}"
pbkdf: "{{ system_raw.luks.pbkdf | string }}"
urandom: "{{ system_raw.luks.urandom | bool }}"
verify: "{{ system_raw.luks.verify | bool }}"
features:
cis:
enabled: "{{ system_raw.features.cis.enabled | bool }}"
selinux:
enabled: "{{ system_raw.features.selinux.enabled | bool }}"
firewall:
enabled: "{{ system_raw.features.firewall.enabled | bool }}"
backend: "{{ system_raw.features.firewall.backend | string | lower }}"
toolkit: "{{ system_raw.features.firewall.toolkit | string | lower }}"
ssh:
enabled: "{{ system_raw.features.ssh.enabled | bool }}"
zstd:
enabled: "{{ system_raw.features.zstd.enabled | bool }}"
swap:
enabled: "{{ system_raw.features.swap.enabled | bool }}"
banner:
motd: "{{ system_raw.features.banner.motd | bool }}"
sudo: "{{ system_raw.features.banner.sudo | bool }}"
chroot:
tool: "{{ system_raw.features.chroot.tool | string }}"
hostname: "{{ system_name }}"
os: "{{ system_os_input if system_os_input | length > 0 else ('archlinux' if system_type == 'physical' else '') }}"
os_version: "{{ system_raw.version | default('') | string }}"
changed_when: false
- name: Normalize system disks input
vars:
system_disks: "{{ system_cfg.disks | default([]) }}"
system_disk_letter_map: "abcdefghijklmnopqrstuvwxyz"
system_disk_device_prefix: >-
{{
{'libvirt': '/dev/vd', 'xen': '/dev/xvd', 'proxmox': '/dev/sd', 'vmware': '/dev/sd'}.get(hypervisor_type, '')
if system_cfg.type == 'virtual'
else ''
}}
block:
- name: Validate system disks structure
ansible.builtin.assert:
that:
- system_disks is sequence
- (system_disks | length) <= 26
fail_msg: "system.disks must be a list with at most 26 entries."
quiet: true
- name: Validate system disk entries
ansible.builtin.assert:
that:
- item is mapping
- item.mount is not defined or item.mount is mapping
fail_msg: "Each disk entry must be a dictionary, and disk.mount (if set) must be a dictionary."
quiet: true
loop: "{{ system_disks }}"
loop_control:
label: "{{ item | to_json }}"
- name: Initialize normalized disk list
ansible.builtin.set_fact:
system_disks_cfg: []
changed_when: false
- name: Build normalized system disk configuration
vars:
disk_idx: "{{ ansible_loop.index0 }}"
disk_letter: "{{ system_disk_letter_map[disk_idx] }}"
disk_cfg_base: "{{ system_disk_defaults | combine(item, recursive=True) }}"
disk_mount: "{{ system_disk_defaults.mount | combine((disk_cfg_base.mount | default({})), recursive=True) }}"
disk_mount_path: "{{ (disk_mount.path | default('') | string) | trim }}"
disk_mount_fstype: >-
{{
disk_mount.fstype
if (disk_mount.fstype | default('') | string | length) > 0
else ('ext4' if disk_mount_path | length > 0 else '')
}}
disk_device: >-
{{
disk_cfg_base.device
if (disk_cfg_base.device | string | length) > 0
else (
(system_disk_device_prefix ~ disk_letter)
if system_cfg.type == 'virtual'
else ''
)
}}
disk_partition: >-
{{
disk_device ~ ('p1' if (disk_device | regex_search('\\d$')) else '1')
if disk_device | length > 0
else ''
}}
ansible.builtin.set_fact:
system_disks_cfg: >-
{{
system_disks_cfg + [
disk_cfg_base
| combine(
{
'device': disk_device,
'mount': {
'path': disk_mount_path,
'fstype': disk_mount_fstype,
'label': disk_mount.label | default('') | string,
'opts': disk_mount.opts | default('defaults') | string
},
'partition': disk_partition
},
recursive=True
)
]
}}
loop: "{{ system_disks }}"
loop_control:
loop_var: item
extended: true
label: "{{ item | to_json }}"
- name: Update system configuration with normalized disks
ansible.builtin.set_fact:
system_cfg: "{{ system_cfg | combine({'disks': system_disks_cfg}, recursive=True) }}"
changed_when: false
- name: Set install_drive from primary disk
when:
- system_disks_cfg | length > 0
- system_disks_cfg[0].device | string | length > 0
ansible.builtin.set_fact:
install_drive: "{{ system_disks_cfg[0].device }}"
changed_when: false

View File

@@ -0,0 +1,331 @@
---
- name: Validate core variables
ansible.builtin.assert:
that:
- system_cfg is defined
- system_cfg is mapping
- system_cfg.type is defined
- system_cfg.type in ["virtual", "physical"]
- hypervisor_cfg is defined
- hypervisor_cfg is mapping
- hypervisor_type is defined
- hypervisor_type in ["libvirt", "proxmox", "vmware", "xen", "none"]
- system_cfg.filesystem is defined
- system_cfg.filesystem in ["btrfs", "ext4", "xfs"]
- install_drive is defined
- install_drive | string | length > 0
- hostname is defined
- hostname | string | length > 0
fail_msg: Invalid core variables were specified, please check your inventory/vars.
quiet: true
- name: Validate hypervisor relationship
ansible.builtin.assert:
that:
- system_cfg.type == "physical" or hypervisor_type in ["libvirt", "proxmox", "vmware", "xen"]
fail_msg: "hypervisor.type must be one of: libvirt, proxmox, vmware, xen when system.type=virtual."
quiet: true
- name: Validate hypervisor schema
vars:
hypervisor_allowed_keys: "{{ hypervisor_defaults | dict2items | map(attribute='key') | list }}"
hypervisor_keys: "{{ (hypervisor | default({})) | dict2items | map(attribute='key') | list }}"
hypervisor_unknown_keys: "{{ hypervisor_keys | difference(hypervisor_allowed_keys) }}"
ansible.builtin.assert:
that:
- hypervisor_unknown_keys | length == 0
fail_msg: "Unsupported hypervisor keys: {{ hypervisor_unknown_keys | join(', ') }}"
quiet: true
- name: Validate system schema
vars:
system_allowed_keys: "{{ system_defaults | dict2items | map(attribute='key') | list }}"
system_keys: "{{ (system | default({})) | dict2items | map(attribute='key') | list }}"
system_unknown_keys: "{{ system_keys | difference(system_allowed_keys) }}"
ansible.builtin.assert:
that:
- system_unknown_keys | length == 0
fail_msg: "Unsupported system keys: {{ system_unknown_keys | join(', ') }}."
quiet: true
- name: Validate nested system mappings
loop:
- network
- user
- root
- luks
- features
loop_control:
label: "{{ item }}"
ansible.builtin.assert:
that:
- system[item] is not defined or system[item] is mapping
fail_msg: "system.{{ item }} must be a dictionary."
quiet: true
- name: Validate system sub-dict schemas
loop:
- network
- user
- root
- luks
loop_control:
label: "system.{{ item }}"
vars:
sub_input: "{{ (system[item] | default({})) | dict2items | map(attribute='key') | list }}"
sub_allowed: "{{ system_defaults[item] | dict2items | map(attribute='key') | list }}"
sub_unknown: "{{ sub_input | difference(sub_allowed) }}"
ansible.builtin.assert:
that:
- sub_unknown | length == 0
fail_msg: "Unsupported system.{{ item }} keys: {{ sub_unknown | join(', ') }}"
quiet: true
- name: Validate system.luks.tpm2 schema
vars:
tpm2_input: >-
{{
(system.luks if (system.luks is defined and system.luks is mapping) else {}).tpm2
| default({})
}}
tpm2_allowed_keys: "{{ system_defaults.luks.tpm2 | dict2items | map(attribute='key') | list }}"
tpm2_unknown: "{{ (tpm2_input | dict2items | map(attribute='key') | list) | difference(tpm2_allowed_keys) }}"
ansible.builtin.assert:
that:
- system.luks is not defined or system.luks.tpm2 is not defined or system.luks.tpm2 is mapping
- tpm2_unknown | length == 0
fail_msg: "Unsupported system.luks.tpm2 keys: {{ tpm2_unknown | join(', ') }}"
quiet: true
- name: Validate system.features schema
vars:
features_allowed_keys: "{{ system_defaults.features | dict2items | map(attribute='key') | list }}"
features_unknown: >-
{{
((system.features | default({})) | dict2items | map(attribute='key') | list)
| difference(features_allowed_keys)
}}
ansible.builtin.assert:
that:
- features_unknown | length == 0
fail_msg: "Unsupported system.features keys: {{ features_unknown | join(', ') }}"
quiet: true
- name: Validate system.features leaf schemas
loop: "{{ system_defaults.features | dict2items }}"
loop_control:
label: "system.features.{{ item.key }}"
vars:
feature_input: "{{ (system.features | default({}))[item.key] | default({}) }}"
feature_allowed_keys: "{{ item.value | dict2items | map(attribute='key') | list }}"
feature_unknown: "{{ (feature_input | dict2items | map(attribute='key') | list) | difference(feature_allowed_keys) }}"
ansible.builtin.assert:
that:
- feature_input is mapping
- feature_unknown | length == 0
fail_msg: "Unsupported system.features.{{ item.key }} keys: {{ feature_unknown | join(', ') }}"
quiet: true
- name: Validate OS and version inputs
ansible.builtin.assert:
that:
- os is defined
- os in ["almalinux", "alpine", "archlinux", "debian", "fedora", "opensuse", "rhel", "rocky", "ubuntu", "ubuntu-lts", "void"]
- >-
os not in ["debian", "fedora", "rocky", "almalinux", "rhel"]
or (os_version is defined and (os_version | string | length) > 0)
- >-
os_version is not defined or (os_version | string | length) == 0
or (
os == "debian" and (os_version | string) in ["10", "11", "12", "13", "unstable"]
) or (
os == "fedora" and (os_version | string) in ["40", "41", "42", "43"]
) or (
os in ["rocky", "almalinux"]
and (os_version | string) is match("^(8|9|10)(\\.\\d+)?$")
) or (
os == "rhel"
and (os_version | string) is match("^(8|9|10)(\\.\\d+)?$")
) or (
os in ["alpine", "archlinux", "opensuse", "ubuntu", "ubuntu-lts", "void"]
)
fail_msg: "Invalid os/version specified. Please check README.md for supported values."
quiet: true
- name: Validate RHEL ISO requirement
ansible.builtin.assert:
that:
- os != "rhel" or (rhel_iso is defined and (rhel_iso | string | length) > 0)
fail_msg: "rhel_iso is required when os=rhel."
quiet: true
- name: Validate Proxmox hypervisor inputs
when:
- system_cfg.type == "virtual"
- hypervisor_type == "proxmox"
ansible.builtin.assert:
that:
- hypervisor_cfg.url | string | length > 0
- hypervisor_cfg.username | string | length > 0
- hypervisor_cfg.password | string | length > 0
- hypervisor_cfg.host | string | length > 0
- hypervisor_cfg.storage | string | length > 0
- system_cfg.id | string | length > 0
- system_cfg.network.bridge | string | length > 0
fail_msg: "Missing required Proxmox inputs. Define hypervisor.(url,username,password,host,storage), system.id, and system.network.bridge."
quiet: true
- name: Validate VMware hypervisor inputs
when:
- system_cfg.type == "virtual"
- hypervisor_type == "vmware"
ansible.builtin.assert:
that:
- hypervisor_cfg.url | string | length > 0
- hypervisor_cfg.username | string | length > 0
- hypervisor_cfg.password | string | length > 0
- hypervisor_cfg.datacenter | string | length > 0
- hypervisor_cfg.cluster | string | length > 0
- hypervisor_cfg.storage | string | length > 0
- system_cfg.network.bridge | string | length > 0
fail_msg: "Missing required VMware inputs. Define hypervisor.(url,username,password,datacenter,cluster,storage) and system.network.bridge."
quiet: true
- name: Validate Xen hypervisor inputs
when:
- system_cfg.type == "virtual"
- hypervisor_type == "xen"
ansible.builtin.assert:
that:
- system_cfg.network.bridge | string | length > 0
fail_msg: "Missing required Xen inputs. Define system.network.bridge."
quiet: true
- name: Validate virtual installer ISO requirement
ansible.builtin.assert:
that:
- system_cfg.type == "physical" or (boot_iso is defined and (boot_iso | string | length) > 0)
fail_msg: "boot_iso is required when system.type=virtual."
quiet: true
- name: Validate firewall and feature flags
ansible.builtin.assert:
that:
- system_cfg.features.firewall.backend is defined
- system_cfg.features.firewall.backend in ["firewalld", "ufw"]
- system_cfg.features.firewall.toolkit is defined
- system_cfg.features.firewall.toolkit in ["iptables", "nftables"]
- system_cfg.features.firewall.enabled is defined
- system_cfg.features.banner.motd is defined
- system_cfg.features.banner.sudo is defined
- system_cfg.luks.enabled is defined
- system_cfg.features.chroot.tool is defined
- system_cfg.features.chroot.tool in ["arch-chroot", "chroot", "systemd-nspawn"]
fail_msg: Invalid feature flags were specified, please check your inventory/vars.
quiet: true
- name: Validate virtual system sizing
when: system_cfg.type == "virtual"
ansible.builtin.assert:
that:
- system_cfg.cpus is defined and (system_cfg.cpus | int) > 0
- system_cfg.memory is defined and (system_cfg.memory | int) > 0
- system_cfg.disks is defined and (system_cfg.disks | length) > 0
- (system_cfg.disks[0].size | float) > 0
- (system_cfg.disks[0].size | float) >= 20
- >-
system_cfg.filesystem != "btrfs"
or (
(system_cfg.disks[0].size | float)
>= (
(
(system_cfg.memory | float / 1024 >= 16.0)
| ternary(
(system_cfg.memory | float / 2048),
[system_cfg.memory | float / 1024, 4.0] | max
)
)
+ 5.5
)
)
fail_msg: "Invalid system sizing. Check system.cpus, system.memory, and system.disks[0].size."
quiet: true
- name: Validate all virtual disks have a positive size
when: system_cfg.type == "virtual"
ansible.builtin.assert:
that:
- item.size is defined
- (item.size | float) > 0
fail_msg: "Each system disk must have a positive size when system.type=virtual: {{ item | to_json }}"
quiet: true
loop: "{{ system_cfg.disks | default([]) }}"
loop_control:
label: "{{ item | to_json }}"
- name: Validate primary disk mount is not used
when:
- system_cfg.disks is defined
- system_cfg.disks | length > 0
ansible.builtin.assert:
that:
- (system_cfg.disks[0].mount.path | default('') | string | trim) == ''
fail_msg: "system.disks[0].mount.path must be empty; use system.disks[1:] for additional mounts."
quiet: true
- name: Validate disk mountpoint inputs
vars:
system_disk_mounts: >-
{{
(system_cfg.disks | default([]))
| map(attribute='mount')
| map(attribute='path')
| map('string')
| map('trim')
| reject('equalto', '')
| list
}}
ansible.builtin.assert:
that:
- system_disk_mounts | length == (system_disk_mounts | unique | list | length)
fail_msg: "Duplicate disk mountpoints found in system.disks."
quiet: true
- name: Validate disk mount definitions
when: system_cfg.disks is defined
vars:
reserved_mounts:
- /boot
- /boot/efi
- /home
- /swap
- /var
- /var/cache/pacman/pkg
- /var/log
- /var/log/audit
disk_mount: "{{ (item.mount.path | default('') | string) | trim }}"
disk_fstype: "{{ (item.mount.fstype | default('') | string) | trim }}"
disk_device: "{{ (item.device | default('') | string) | trim }}"
disk_size: "{{ item.size | default(0) }}"
ansible.builtin.assert:
that:
- disk_mount == "" or disk_mount.startswith("/")
- disk_mount == "" or disk_mount != "/"
- disk_mount == "" or disk_mount not in reserved_mounts
- disk_mount == "" or disk_fstype in ["btrfs", "ext4", "xfs"]
- disk_mount == "" or system_cfg.type == "virtual" or (disk_device | length) > 0
- disk_mount == "" or system_cfg.type != "virtual" or (disk_size | float) > 0
fail_msg: "Invalid system disk entry: {{ item | to_json }}"
quiet: true
loop: "{{ system_cfg.disks }}"
loop_control:
label: "{{ item | to_json }}"
- name: Validate static IP requirements
when: system_cfg.network.ip is defined and (system_cfg.network.ip | string | length) > 0
ansible.builtin.assert:
that:
- system_cfg.network.prefix is defined
- (system_cfg.network.prefix | int) > 0
fail_msg: "system.network.prefix is required when system.network.ip is set."
quiet: true

View File

@@ -0,0 +1,134 @@
---
partitioning_btrfs_compress_opt: "{{ 'compress=zstd:15' if system_cfg.features.zstd.enabled | bool else '' }}"
partitioning_boot_partition_suffix: 1
partitioning_main_partition_suffix: 2
partitioning_efi_size_mib: 512
partitioning_efi_start_mib: 1
partitioning_efi_end_mib: "{{ (partitioning_efi_start_mib | int) + (partitioning_efi_size_mib | int) }}"
partitioning_boot_size_mib: 1024
partitioning_use_full_disk: true
partitioning_separate_boot: >-
{{
(system_cfg.luks.enabled | bool)
and (os | lower not in ['archlinux'])
}}
partitioning_boot_fs_fstype: >-
{{
(system_cfg.filesystem | lower)
if (system_cfg.filesystem | lower) != 'btrfs'
else ('xfs' if is_rhel else 'ext4')
}}
partitioning_boot_fs_partition_suffix: >-
{{
((partitioning_boot_partition_suffix | int) + 1)
if (partitioning_separate_boot | bool) else ''
}}
partitioning_root_partition_suffix: >-
{{
(partitioning_main_partition_suffix | int)
+ (1 if (partitioning_separate_boot | bool) else 0)
}}
partitioning_efi_mountpoint: >-
{{
'/boot/efi'
if (partitioning_separate_boot | bool)
else (
'/boot/efi'
if is_rhel or (os in ['ubuntu', 'ubuntu-lts'] or (os == 'debian' and (os_version | string) in ['11', '12', '13']))
else '/boot'
)
}}
partitioning_boot_end_mib: "{{ (partitioning_efi_end_mib | int) + (partitioning_boot_size_mib | int) }}"
partitioning_reserved_gb: >-
{{
(
(partitioning_efi_size_mib | float)
+ ((partitioning_boot_size_mib | float) if (partitioning_separate_boot | bool) else 0)
) / 1024
}}
partitioning_layout: >-
{{
[
{
'number': 1,
'part_start': (partitioning_efi_start_mib | string) + 'MiB',
'part_end': (partitioning_efi_end_mib | string) + 'MiB',
'name': 'efi',
'flags': ['boot', 'esp']
},
{
'number': 2,
'part_start': (partitioning_efi_end_mib | string) + 'MiB',
'part_end': (partitioning_boot_end_mib | string) + 'MiB',
'name': 'boot'
},
{
'number': 3,
'part_start': (partitioning_boot_end_mib | string) + 'MiB',
'name': 'root'
}
]
if partitioning_separate_boot | bool else
[
{
'number': 1,
'part_start': (partitioning_efi_start_mib | string) + 'MiB',
'part_end': (partitioning_efi_end_mib | string) + 'MiB',
'name': 'boot',
'flags': ['boot', 'esp']
},
{
'number': 2,
'part_start': (partitioning_efi_end_mib | string) + 'MiB',
'name': 'root'
}
]
}}
partitioning_grub_enable_cryptodisk: >-
{{
(system_cfg.luks.enabled | bool)
and not (partitioning_separate_boot | bool)
and (partitioning_efi_mountpoint == '/boot/efi')
}}
partitioning_luks_device: "{{ install_drive ~ (partitioning_root_partition_suffix | string) }}"
partitioning_root_device: >-
{{
'/dev/mapper/' + system_cfg.luks.mapper
if (system_cfg.luks.enabled | bool)
else install_drive ~ (partitioning_root_partition_suffix | string)
}}
partitioning_disk_size_gb: >-
{{
(
partitioning_vm_size
if (partitioning_vm_size is defined and (partitioning_vm_size | float) > 0)
else (
(
(system_cfg.disks | default([]) | first | default({})).size
if system_cfg is defined
else 0
) | default(0)
)
)
| float
}}
partitioning_memory_mb: >-
{{
(
partitioning_vm_memory
if (partitioning_vm_memory is defined and (partitioning_vm_memory | float) > 0)
else (
(system_cfg.memory if system_cfg is defined else 0)
| default(0)
)
)
| float
}}
partitioning_swap_size_gb: >-
{{
((partitioning_memory_mb / 1024) >= 16.0)
| ternary(
(partitioning_memory_mb / 2048) | int,
[partitioning_memory_mb / 1024, 4.0] | max | int
)
}}

View File

@@ -3,46 +3,78 @@
block: block:
- name: Create btrfs filesystem in main volume - name: Create btrfs filesystem in main volume
community.general.filesystem: community.general.filesystem:
dev: "{{ install_drive }}{{ main_partition_suffix }}" dev: "{{ partitioning_root_device }}"
fstype: btrfs fstype: btrfs
force: true force: true
opts: >-
{{
'-K'
if (system_cfg.luks.enabled | bool)
and not ('discard' in (system_cfg.luks.options | lower))
else omit
}}
- name: Prepare BTRFS Subvolume - name: Prepare BTRFS Subvolume
ansible.posix.mount: ansible.posix.mount:
path: /mnt path: /mnt
src: "{{ install_drive }}{{ main_partition_suffix }}" src: "{{ partitioning_root_device }}"
fstype: btrfs fstype: btrfs
opts: rw,relatime,compress=zstd:15,ssd,space_cache=v2,discard=async opts: >-
{{
[
'rw',
'relatime',
partitioning_btrfs_compress_opt,
'ssd',
'space_cache=v2',
'discard=async'
]
| reject('equalto', '')
| join(',')
}}
state: mounted state: mounted
- name: Enable quotas on Btrfs filesystem - name: Enable quotas on Btrfs filesystem
ansible.builtin.command: btrfs quota enable /mnt ansible.builtin.command: btrfs quota enable /mnt
changed_when: result.rc == 0 register: partitioning_btrfs_quota_result
register: result changed_when: false
- name: Make root subvolumes - name: Make root subvolumes
when: cis | bool or item.subvol not in ['var_log', 'var_log_audit'] when:
- system_cfg.features.cis.enabled or item.subvol not in ['var_log_audit']
- system_cfg.features.swap.enabled | bool or item.subvol != 'swap'
ansible.builtin.command: btrfs su cr /mnt/{{ '@' if item.subvol == 'root' else '@' + item.subvol }} ansible.builtin.command: btrfs su cr /mnt/{{ '@' if item.subvol == 'root' else '@' + item.subvol }}
changed_when: result.rc == 0 args:
register: result creates: /mnt/{{ '@' if item.subvol == 'root' else '@' + item.subvol }}
loop: loop:
- { subvol: root } - { subvol: root }
- { subvol: swap }
- { subvol: home } - { subvol: home }
- { subvol: var } - { subvol: var }
- { subvol: pkg }
- { subvol: var_log } - { subvol: var_log }
- { subvol: var_log_audit } - { subvol: var_log_audit }
register: partitioning_btrfs_subvol_result
- name: Set quotas for subvolumes - name: Set quotas for subvolumes
when: cis | bool or item.subvol not in ['var_log', 'var_log_audit'] when: system_cfg.features.cis.enabled
ansible.builtin.command: btrfs qgroup limit {{ item.quota }} /mnt/{{ '@' if item.subvol == 'root' else '@' + item.subvol }} ansible.builtin.command: btrfs qgroup limit {{ item.quota }} /mnt/{{ '@' if item.subvol == 'root' else '@' + item.subvol }}
changed_when: result.rc == 0
register: result
loop: loop:
- { subvol: home, quota: 2G } - { subvol: home, quota: 2G }
register: partitioning_btrfs_qgroup_result
changed_when: false
- name: Create a Btrfs swap file
when: system_cfg.features.swap.enabled | bool
ansible.builtin.command: >-
btrfs filesystem mkswapfile --size {{ partitioning_swap_size_gb }}g --uuid clear /mnt/@swap/swapfile
args:
creates: /mnt/@swap/swapfile
register: partitioning_btrfs_swap_result
- name: Unmount Partition - name: Unmount Partition
ansible.posix.mount: ansible.posix.mount:
path: /mnt path: /mnt
src: "{{ install_drive }}{{ main_partition_suffix }}" src: "{{ partitioning_root_device }}"
fstype: btrfs fstype: btrfs
state: unmounted state: unmounted

View File

@@ -1,6 +1,6 @@
--- ---
- name: Create and format ext4 logical volumes - name: Create and format ext4 logical volumes
when: cis | bool or item.lv not in ['var_log', 'var_log_audit'] when: system_cfg.features.cis.enabled or item.lv not in ['home', 'var', 'var_log', 'var_log_audit']
community.general.filesystem: community.general.filesystem:
dev: /dev/sys/{{ item.lv }} dev: /dev/sys/{{ item.lv }}
fstype: ext4 fstype: ext4
@@ -13,13 +13,15 @@
- { lv: var_log_audit } - { lv: var_log_audit }
- name: Remove Unsupported features for older Systems - name: Remove Unsupported features for older Systems
when: (os | lower in ['almalinux', 'debian11', 'rhel8', 'rhel9', 'rocky']) and (cis | bool or item.lv not in ['var_log', 'var_log_audit']) when: >
(os in ['almalinux', 'rocky', 'rhel'] or (os == 'debian' and (os_version | string) == '11'))
and (system_cfg.features.cis.enabled or item.lv not in ['home', 'var', 'var_log', 'var_log_audit'])
ansible.builtin.command: tune2fs -O "^orphan_file,^metadata_csum_seed" "/dev/sys/{{ item.lv }}" ansible.builtin.command: tune2fs -O "^orphan_file,^metadata_csum_seed" "/dev/sys/{{ item.lv }}"
changed_when: result.rc == 0
register: result
loop: loop:
- { lv: root } - { lv: root }
- { lv: home } - { lv: home }
- { lv: var } - { lv: var }
- { lv: var_log } - { lv: var_log }
- { lv: var_log_audit } - { lv: var_log_audit }
register: partitioning_ext4_tune_result
changed_when: partitioning_ext4_tune_result.rc == 0

View File

@@ -0,0 +1,85 @@
---
- name: Determine additional disks to auto-mount
ansible.builtin.set_fact:
partitioning_extra_disks: >-
{{
(system_cfg.disks | default([]))[1:]
| selectattr('mount.path')
| list
}}
changed_when: false
- name: Validate additional disks do not target install_drive
when: partitioning_extra_disks | length > 0
ansible.builtin.assert:
that:
- item.device is defined
- item.device | string | length > 0
- item.device != install_drive
- item.partition is defined
- item.partition | string | length > 0
- item.mount.fstype is defined
- item.mount.fstype in ['btrfs', 'ext4', 'xfs']
- item.mount.path is defined
- item.mount.path | string | length > 0
- item.mount.path.startswith('/')
- item.mount.path != '/'
fail_msg: "Invalid additional disk definition: {{ item | to_json }}"
quiet: true
loop: "{{ partitioning_extra_disks }}"
loop_control:
label: "{{ item | to_json }}"
- name: Partition additional disks
when: partitioning_extra_disks | length > 0
community.general.parted:
device: "{{ item.device }}"
label: gpt
number: 1
part_start: "1MiB"
part_end: "100%"
name: "{{ (item.mount.label | default('') | string | length > 0) | ternary(item.mount.label, 'data') }}"
state: present
loop: "{{ partitioning_extra_disks }}"
loop_control:
label: "{{ item.device }}"
- name: Settle partition tables for additional disks
when: partitioning_extra_disks | length > 0
ansible.builtin.command: udevadm settle
changed_when: false
- name: Create filesystems on additional disks
when: partitioning_extra_disks | length > 0
community.general.filesystem:
dev: "{{ item.partition }}"
fstype: "{{ item.mount.fstype }}"
opts: "{{ ('-L ' ~ item.mount.label) if (item.mount.label | default('') | string | length) > 0 else omit }}"
force: true
loop: "{{ partitioning_extra_disks }}"
loop_control:
label: "{{ item.partition }}"
- name: Ensure mount directories exist for additional disks
when: partitioning_extra_disks | length > 0
ansible.builtin.file:
path: "/mnt{{ item.mount.path }}"
state: directory
owner: root
group: root
mode: "0755"
loop: "{{ partitioning_extra_disks }}"
loop_control:
label: "{{ item.mount.path }}"
- name: Mount additional disks for fstab generation
when: partitioning_extra_disks | length > 0
ansible.posix.mount:
path: "/mnt{{ item.mount.path }}"
src: "{{ item.partition }}"
fstype: "{{ item.mount.fstype }}"
opts: "{{ item.mount.opts | default('defaults') }}"
state: mounted
loop: "{{ partitioning_extra_disks }}"
loop_control:
label: "{{ item.mount.path }}"

View File

@@ -1,142 +1,665 @@
--- ---
- name: Detect system memory for swap sizing
when:
- system_cfg.features.swap.enabled | bool
- partitioning_vm_memory is not defined or (partitioning_vm_memory | float) <= 0
- system_cfg is not defined or (system_cfg.memory | default(0) | float) <= 0
block:
- name: Read system memory
ansible.builtin.command: awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo
register: partitioning_memtotal_mb
changed_when: false
failed_when: false
- name: Set partitioning vm memory default
ansible.builtin.set_fact:
partitioning_vm_memory: "{{ (partitioning_memtotal_mb.stdout | default('4096') | int) | float }}"
- name: Set partitioning vm_size for physical installs
when:
- system_cfg.type == "physical"
- partitioning_vm_size is not defined or (partitioning_vm_size | float) <= 0
- install_drive | length > 0
block:
- name: Detect install drive size
ansible.builtin.command: "lsblk -b -dn -o SIZE {{ install_drive }}"
register: partitioning_disk_size_bytes
changed_when: false
- name: Set partitioning vm_size from install drive size
when:
- partitioning_disk_size_bytes.stdout is defined
- (partitioning_disk_size_bytes.stdout | trim | length) > 0
ansible.builtin.set_fact:
partitioning_vm_size: >-
{{
(partitioning_disk_size_bytes.stdout | trim | int / 1024 / 1024 / 1024)
| round(2, 'floor')
}}
- name: Partition install drive - name: Partition install drive
block: block:
- name: Prepare partitions - name: Prepare partitions
failed_when: false block:
ansible.builtin.command: "{{ item.cmd }}" - name: Disable swap
changed_when: result.rc == 0 ansible.builtin.command: swapoff -a
register: result register: partitioning_swapoff_result
loop: changed_when: partitioning_swapoff_result.rc == 0
- { cmd: umount -l /mnt } failed_when: false
- { cmd: vgremove -f sys }
- { cmd: 'find /dev -wholename "{{ install_drive }}*" -exec wipefs --force --all {} \;' } - name: Find mounts under /mnt
loop_control: ansible.builtin.command: findmnt -R /mnt -n -o TARGET
label: "{{ item.cmd }}" register: partitioning_mounted_paths
changed_when: false
failed_when: false
- name: Unmount /mnt mounts
when: partitioning_mounted_paths.stdout_lines | length > 0
ansible.posix.mount:
path: "{{ item }}"
state: unmounted
loop: "{{ partitioning_mounted_paths.stdout_lines | reverse }}"
loop_control:
label: "{{ item }}"
failed_when: false
- name: Remove LVM volume group
community.general.lvg:
vg: sys
state: absent
force: true
failed_when: false
- name: Close LUKS mapper
when: system_cfg.luks.enabled | bool
community.crypto.luks_device:
name: "{{ system_cfg.luks.mapper }}"
state: closed
failed_when: false
- name: Remove LUKS mapper device
when: system_cfg.luks.enabled | bool
ansible.builtin.command: >-
dmsetup remove --force --retry {{ system_cfg.luks.mapper }}
register: partitioning_dmsetup_remove
changed_when: partitioning_dmsetup_remove.rc == 0
failed_when: false
- name: Remove LUKS signatures
when: system_cfg.luks.enabled | bool
community.crypto.luks_device:
device: "{{ partitioning_luks_device }}"
state: absent
failed_when: false
- name: Wipe filesystem signatures
ansible.builtin.command: >-
find /dev -wholename "{{ install_drive }}*" -exec wipefs --force --all {} \;
register: partitioning_wipefs_result
changed_when: false
failed_when: false
- name: Refresh kernel partition table
ansible.builtin.command: "{{ item }}"
loop:
- "partprobe {{ install_drive }}"
- "blockdev --rereadpt {{ install_drive }}"
- "udevadm settle"
register: partitioning_partprobe_result
changed_when: false
failed_when: false
- name: Define partitions - name: Define partitions
community.general.parted: block:
device: "{{ install_drive }}" - name: Create partition layout
label: gpt community.general.parted:
number: "{{ item.number }}" device: "{{ install_drive }}"
part_end: "{{ item.part_end | default(omit) }}" label: gpt
part_start: "{{ item.part_start | default(omit) }}" number: "{{ item.number }}"
name: "{{ item.name }}" part_end: "{{ item.part_end | default(omit) }}"
flags: "{{ item.flags | default(omit) }}" part_start: "{{ item.part_start | default(omit) }}"
state: present name: "{{ item.name }}"
flags: "{{ item.flags | default(omit) }}"
state: present
loop: "{{ partitioning_layout }}"
rescue:
- name: Refresh kernel partition table after failure
ansible.builtin.command: "{{ item }}"
loop:
- "partprobe {{ install_drive }}"
- "blockdev --rereadpt {{ install_drive }}"
- "udevadm settle"
register: partitioning_partprobe_retry
changed_when: false
failed_when: false
- name: Retry partition layout
community.general.parted:
device: "{{ install_drive }}"
label: gpt
number: "{{ item.number }}"
part_end: "{{ item.part_end | default(omit) }}"
part_start: "{{ item.part_start | default(omit) }}"
name: "{{ item.name }}"
flags: "{{ item.flags | default(omit) }}"
state: present
loop: "{{ partitioning_layout }}"
- name: Settle partition table
ansible.builtin.command: "{{ item }}"
loop: loop:
- { number: 1, part_end: 500MiB, name: boot, flags: [boot, esp] } - "partprobe {{ install_drive }}"
- { number: 2, part_start: 500MiB, name: root } - "udevadm settle"
register: partitioning_partprobe_settle
changed_when: false
failed_when: false
- name: Configure LUKS encryption
when: system_cfg.luks.enabled | bool
block:
- name: Validate LUKS passphrase
ansible.builtin.assert:
that:
- (system_cfg.luks.passphrase | string | length) > 0
fail_msg: system.luks.passphrase must be set when LUKS is enabled.
no_log: true
- name: Ensure LUKS container exists
community.crypto.luks_device:
device: "{{ partitioning_luks_device }}"
state: present
type: "{{ system_cfg.luks.type }}"
cipher: "{{ system_cfg.luks.cipher }}"
hash: "{{ system_cfg.luks.hash }}"
keysize: "{{ system_cfg.luks.bits }}"
pbkdf:
algorithm: "{{ system_cfg.luks.pbkdf }}"
iteration_time: "{{ (system_cfg.luks.iter | float) / 1000 }}"
passphrase: "{{ system_cfg.luks.passphrase | string }}"
register: partitioning_luks_format_result
no_log: true
- name: Force-close LUKS mapper
community.crypto.luks_device:
name: "{{ system_cfg.luks.mapper }}"
state: closed
failed_when: false
- name: Force-remove LUKS mapper device
ansible.builtin.command: >-
dmsetup remove --force --retry {{ system_cfg.luks.mapper }}
register: partitioning_dmsetup_remove_after_format
changed_when: partitioning_dmsetup_remove_after_format.rc == 0
failed_when: false
- name: Settle udev after removing LUKS mapper
ansible.builtin.command: udevadm settle
changed_when: false
failed_when: false
- name: Ensure LUKS mapper is opened
block:
- name: Open LUKS device
community.crypto.luks_device:
device: "{{ partitioning_luks_device }}"
state: opened
name: "{{ system_cfg.luks.mapper }}"
passphrase: "{{ system_cfg.luks.passphrase | string }}"
allow_discards: "{{ 'discard' in (system_cfg.luks.options | lower) }}"
register: partitioning_luks_open_result
no_log: true
rescue:
- name: Force-close stale LUKS mapper
community.crypto.luks_device:
name: "{{ system_cfg.luks.mapper }}"
state: closed
failed_when: false
- name: Force-remove stale LUKS mapper device
ansible.builtin.command: >-
dmsetup remove --force --retry {{ system_cfg.luks.mapper }}
register: partitioning_dmsetup_remove_retry
changed_when: partitioning_dmsetup_remove_retry.rc == 0
failed_when: false
- name: Settle udev after removing stale LUKS mapper
ansible.builtin.command: udevadm settle
changed_when: false
failed_when: false
- name: Retry opening LUKS device
community.crypto.luks_device:
device: "{{ partitioning_luks_device }}"
state: opened
name: "{{ system_cfg.luks.mapper }}"
passphrase: "{{ system_cfg.luks.passphrase | string }}"
allow_discards: "{{ 'discard' in (system_cfg.luks.options | lower) }}"
register: partitioning_luks_open_retry
no_log: true
- name: Get LUKS UUID
ansible.builtin.command: "cryptsetup luksUUID {{ partitioning_luks_device }}"
register: partitioning_luks_uuid_result
changed_when: false
- name: Store LUKS UUID
ansible.builtin.set_fact:
partitioning_luks_uuid: "{{ partitioning_luks_uuid_result.stdout | trim }}"
- name: Create LVM logical volumes - name: Create LVM logical volumes
when: filesystem != 'btrfs' when: system_cfg.filesystem != 'btrfs'
block: block:
- name: Create LVM volume group - name: Create LVM volume group
community.general.lvg: community.general.lvg:
vg: sys vg: sys
pvs: "{{ install_drive }}{{ main_partition_suffix }}" pvs: "{{ partitioning_root_device }}"
- name: Create LVM logical volumes - name: Create LVM logical volumes
when: cis | bool or item.lv not in ['var_log', 'var_log_audit'] when:
- system_cfg.features.cis.enabled or item.lv not in ['home', 'var', 'var_log', 'var_log_audit']
- system_cfg.features.swap.enabled | bool or item.lv != 'swap'
vars:
partitioning_lvm_extent_reserve_count: 10
partitioning_lvm_extent_size_mib: 4
partitioning_lvm_extent_reserve_gb: >-
{{
(
(partitioning_lvm_extent_reserve_count | float)
* (partitioning_lvm_extent_size_mib | float)
/ 1024
) | round(2, 'ceil')
}}
partitioning_lvm_swap_target_gb: >-
{{
(
[
(partitioning_memory_mb | float / 1024),
4
] | max | float
)
if system_cfg.features.swap.enabled | bool
else 0
}}
partitioning_lvm_swap_cap_gb: >-
{{
(
4
+ [
(partitioning_disk_size_gb | float) - 20,
0
] | max
)
if system_cfg.features.swap.enabled | bool
else 0
}}
partitioning_lvm_swap_target_limited_gb: >-
{{
(
[
partitioning_lvm_swap_target_gb,
partitioning_lvm_swap_cap_gb
] | min
)
if system_cfg.features.swap.enabled | bool
else 0
}}
partitioning_lvm_swap_max_gb: >-
{{
(
[
(
(partitioning_disk_size_gb | float)
- (partitioning_reserved_gb | float)
- (system_cfg.features.cis.enabled | ternary(7.5, 0))
- partitioning_lvm_extent_reserve_gb
- 4
),
0
] | max
)
if system_cfg.features.swap.enabled | bool
else 0
}}
partitioning_lvm_available_gb: >-
{{
(
(partitioning_disk_size_gb | float)
- (partitioning_reserved_gb | float)
- (system_cfg.features.cis.enabled | ternary(7.5, 0))
- partitioning_lvm_extent_reserve_gb
- partitioning_lvm_swap_target_limited_gb
) | float
}}
partitioning_lvm_home_gb: >-
{{
([([(((partitioning_disk_size_gb | float) - 20) * 0.1), 2] | max), 20] | min)
}}
partitioning_lvm_root_default_gb: >-
{{
[
(
((partitioning_lvm_available_gb | float) < 4)
| ternary(
4,
(
((partitioning_lvm_available_gb | float) > 12)
| ternary(
((partitioning_disk_size_gb | float) * 0.4)
| round(0, 'ceil'),
partitioning_lvm_available_gb
)
)
)
),
4
] | max
}}
partitioning_lvm_swap_gb: >-
{{
(
[
partitioning_lvm_swap_target_limited_gb,
partitioning_lvm_swap_max_gb
] | min | round(2, 'floor')
)
if system_cfg.features.swap.enabled | bool
else 0
}}
partitioning_lvm_root_full_gb: >-
{{
[
(
(partitioning_disk_size_gb | float)
- (partitioning_reserved_gb | float)
- (partitioning_lvm_swap_gb | float)
- partitioning_lvm_extent_reserve_gb
- (
(partitioning_lvm_home_gb | float) + 5.5
if system_cfg.features.cis.enabled
else 0
)
),
4
] | max | round(2, 'floor')
}}
partitioning_lvm_root_gb: >-
{{
partitioning_lvm_root_full_gb
if partitioning_use_full_disk | bool
else partitioning_lvm_root_default_gb
}}
community.general.lvol: community.general.lvol:
vg: sys vg: sys
lv: "{{ item.lv }}" lv: "{{ item.lv }}"
size: "{{ item.size }}" size: "{{ item.size }}"
state: present state: present
loop: loop:
- { lv: root, size: 12G } - lv: root
- { lv: home, size: 2G } size: "{{ partitioning_lvm_root_gb | string + 'G' }}"
- { lv: var, size: 2G } - lv: swap
- { lv: var_log, size: 2G } size: "{{ partitioning_lvm_swap_gb | string + 'G' }}"
- { lv: var_log_audit, size: 1.5G } - lv: home
size: "{{ partitioning_lvm_home_gb | string + 'G' }}"
- { lv: var, size: "2G" }
- { lv: var_log, size: "2G" }
- { lv: var_log_audit, size: "1.5G" }
- name: Create filesystems - name: Create filesystems
block: block:
- name: Create FAT32 filesystem in boot partition - name: Create FAT32 filesystem in boot partition
community.general.filesystem: community.general.filesystem:
dev: "{{ install_drive }}{{ boot_partition_suffix }}" dev: "{{ install_drive }}{{ partitioning_boot_partition_suffix }}"
fstype: vfat fstype: vfat
opts: -F32 -n BOOT opts: -F32 -n BOOT
force: true force: true
- name: Create filesystem for /boot partition
when: partitioning_separate_boot | bool
community.general.filesystem:
dev: "{{ install_drive }}{{ partitioning_boot_fs_partition_suffix }}"
fstype: "{{ partitioning_boot_fs_fstype }}"
force: true
- name: Remove unsupported ext4 features from /boot
when:
- partitioning_separate_boot | bool
- partitioning_boot_fs_fstype == 'ext4'
- os in ['almalinux', 'rocky', 'rhel'] or (os == 'debian' and (os_version | string) == '11')
ansible.builtin.command: >-
tune2fs -O "^orphan_file,^metadata_csum_seed"
"{{ install_drive }}{{ partitioning_boot_fs_partition_suffix }}"
register: partitioning_boot_ext4_tune_result
changed_when: partitioning_boot_ext4_tune_result.rc == 0
- name: Create swap filesystem
when:
- system_cfg.filesystem != 'btrfs'
- system_cfg.features.swap.enabled | bool
community.general.filesystem:
fstype: swap
dev: /dev/sys/swap
- name: Create filesystem - name: Create filesystem
ansible.builtin.include_tasks: "{{ filesystem }}.yml" ansible.builtin.include_tasks: "{{ system_cfg.filesystem }}.yml"
- name: Get UUID for boot filesystem - name: Get UUID for boot filesystem
ansible.builtin.command: blkid -s UUID -o value '{{ install_drive }}{{ boot_partition_suffix }}' ansible.builtin.command: blkid -s UUID -o value '{{ install_drive }}{{ partitioning_boot_partition_suffix }}'
register: partitioning_boot_uuid
changed_when: false
- name: Get UUID for /boot filesystem
when: partitioning_separate_boot | bool
ansible.builtin.command: >-
blkid -s UUID -o value '{{ install_drive }}{{ partitioning_boot_fs_partition_suffix }}'
register: partitioning_boot_fs_uuid
changed_when: false changed_when: false
register: boot_uuid
- name: Get UUID for main filesystem - name: Get UUID for main filesystem
ansible.builtin.command: blkid -s UUID -o value '{{ install_drive }}{{ main_partition_suffix }}' ansible.builtin.command: blkid -s UUID -o value '{{ partitioning_root_device }}'
register: partitioning_main_uuid
changed_when: false changed_when: false
register: main_uuid
- name: Get UUIDs for LVM filesystems - name: Get UUID for LVM root filesystem
when: filesystem != 'btrfs' and (cis | bool or item not in ['var_log', 'var_log_audit']) when: system_cfg.filesystem != 'btrfs'
ansible.builtin.command: blkid -s UUID -o value /dev/sys/{{ item }} ansible.builtin.command: blkid -s UUID -o value /dev/sys/root
register: partitioning_uuid_root_result
changed_when: false
- name: Get UUID for LVM swap filesystem
when:
- system_cfg.filesystem != 'btrfs'
- system_cfg.features.swap.enabled | bool
ansible.builtin.command: blkid -s UUID -o value /dev/sys/swap
register: partitioning_uuid_swap_result
changed_when: false
- name: Get UUID for LVM home filesystem
when:
- system_cfg.filesystem != 'btrfs'
- system_cfg.features.cis.enabled
ansible.builtin.command: blkid -s UUID -o value /dev/sys/home
register: partitioning_uuid_home_result
changed_when: false
- name: Get UUID for LVM var filesystem
when:
- system_cfg.filesystem != 'btrfs'
- system_cfg.features.cis.enabled
ansible.builtin.command: blkid -s UUID -o value /dev/sys/var
register: partitioning_uuid_var_result
changed_when: false
- name: Get UUID for LVM var_log filesystem
when:
- system_cfg.filesystem != 'btrfs'
- system_cfg.features.cis.enabled
ansible.builtin.command: blkid -s UUID -o value /dev/sys/var_log
register: partitioning_uuid_var_log_result
changed_when: false
- name: Get UUID for LVM var_log_audit filesystem
when:
- system_cfg.filesystem != 'btrfs'
- system_cfg.features.cis.enabled
ansible.builtin.command: blkid -s UUID -o value /dev/sys/var_log_audit
register: partitioning_uuid_var_log_audit_result
changed_when: false changed_when: false
register: uuid_result
loop:
- root
- home
- var
- var_log
- var_log_audit
- name: Assign UUIDs to Variables - name: Assign UUIDs to Variables
when: system_cfg.filesystem != 'btrfs'
ansible.builtin.set_fact: ansible.builtin.set_fact:
uuid_root: "{{ uuid_result.results[0].stdout_lines }}" partitioning_uuid_root: "{{ partitioning_uuid_root_result.stdout_lines | default([]) }}"
uuid_home: "{{ uuid_result.results[1].stdout_lines }}" partitioning_uuid_swap: >-
uuid_var: "{{ uuid_result.results[2].stdout_lines }}" {{
uuid_var_log: "{{ uuid_result.results[3].stdout_lines if cis == true else '' }}" partitioning_uuid_swap_result.stdout_lines | default([])
uuid_var_log_audit: "{{ uuid_result.results[4].stdout_lines if cis == true else '' }}" if system_cfg.features.swap.enabled | bool
when: filesystem != 'btrfs' else ''
}}
partitioning_uuid_home: >-
{{
partitioning_uuid_home_result.stdout_lines | default([])
if system_cfg.features.cis.enabled
else ''
}}
partitioning_uuid_var: >-
{{
partitioning_uuid_var_result.stdout_lines | default([])
if system_cfg.features.cis.enabled
else ''
}}
partitioning_uuid_var_log: >-
{{
partitioning_uuid_var_log_result.stdout_lines | default([])
if system_cfg.features.cis.enabled
else ''
}}
partitioning_uuid_var_log_audit: >-
{{
partitioning_uuid_var_log_audit_result.stdout_lines | default([])
if system_cfg.features.cis.enabled
else ''
}}
- name: Mount filesystems - name: Mount filesystems
block: block:
- name: Mount filesystems and subvolumes - name: Mount filesystems and subvolumes
when: cis | bool or (not cis and item.path != '/var/log' and item.path != '/var/log/audit') when:
- >-
system_cfg.features.cis.enabled or (
not system_cfg.features.cis.enabled and (
(system_cfg.filesystem == 'btrfs' and item.path in ['/home', '/var/log', '/var/cache/pacman/pkg'])
or (item.path not in ['/home', '/var', '/var/log', '/var/log/audit', '/var/cache/pacman/pkg'])
)
)
- >-
not (item.path in ['/swap', '/var/cache/pacman/pkg'] and system_cfg.filesystem != 'btrfs')
- system_cfg.features.swap.enabled | bool or item.path != '/swap'
ansible.posix.mount: ansible.posix.mount:
path: /mnt{{ item.path }} path: /mnt{{ item.path }}
src: "{{ 'UUID=' + (main_uuid.stdout if filesystem == 'btrfs' else item.uuid) }}" src: "{{ 'UUID=' + (partitioning_main_uuid.stdout if system_cfg.filesystem == 'btrfs' else item.uuid) }}"
fstype: "{{ filesystem }}" fstype: "{{ system_cfg.filesystem }}"
opts: "{{ item.opts }}" opts: "{{ item.opts }}"
state: mounted state: mounted
loop: loop:
- path: "" - path: ""
uuid: "{{ uuid_root[0] | default(omit) }}" uuid: "{{ partitioning_uuid_root[0] | default(omit) }}"
opts: "{{ 'defaults' if filesystem != 'btrfs' else 'rw,relatime,compress=zstd:15,ssd,space_cache=v2,discard=async,subvol=@' }}" opts: >-
{{
'defaults'
if system_cfg.filesystem != 'btrfs'
else [
'rw', 'relatime', partitioning_btrfs_compress_opt, 'ssd', 'space_cache=v2',
'discard=async', 'subvol=@'
] | reject('equalto', '') | join(',')
}}
- path: /swap
opts: >-
{{
[
'rw', 'nosuid', 'nodev', 'relatime', partitioning_btrfs_compress_opt, 'ssd',
'space_cache=v2', 'discard=async', 'subvol=@swap'
] | reject('equalto', '') | join(',')
}}
- path: /home - path: /home
uuid: "{{ uuid_home[0] | default(omit) }}" uuid: "{{ partitioning_uuid_home[0] | default(omit) }}"
opts: "{{ 'defaults,nosuid,nodev' if filesystem != 'btrfs' opts: >-
else 'rw,nosuid,nodev,relatime,compress=zstd:15,ssd,space_cache=v2,discard=async,subvol=@home' }}" {{
'defaults,nosuid,nodev'
if system_cfg.filesystem != 'btrfs'
else [
'rw', 'nosuid', 'nodev', 'relatime', partitioning_btrfs_compress_opt, 'ssd',
'space_cache=v2', 'discard=async', 'subvol=@home'
] | reject('equalto', '') | join(',')
}}
- path: /var - path: /var
uuid: "{{ uuid_var[0] | default(omit) }}" uuid: "{{ partitioning_uuid_var[0] | default(omit) }}"
opts: "{{ 'defaults,nosuid,nodev' if filesystem != 'btrfs' opts: >-
else 'rw,nosuid,nodev,relatime,compress=zstd:15,ssd,space_cache=v2,discard=async,subvol=@var' }}" {{
'defaults,nosuid,nodev'
if system_cfg.filesystem != 'btrfs'
else [
'rw', 'nosuid', 'nodev', 'relatime', partitioning_btrfs_compress_opt, 'ssd',
'space_cache=v2', 'discard=async', 'subvol=@var'
] | reject('equalto', '') | join(',')
}}
- path: /var/log - path: /var/log
uuid: "{{ uuid_var_log[0] | default(omit) }}" uuid: "{{ partitioning_uuid_var_log[0] | default(omit) }}"
opts: "{{ 'defaults,nosuid,nodev,noexec' if filesystem != 'btrfs' opts: >-
else 'rw,nosuid,nodev,noexec,relatime,compress=zstd:15,ssd,space_cache=v2,discard=async,subvol=@var_log' }}" {{
'defaults,nosuid,nodev,noexec'
if system_cfg.filesystem != 'btrfs'
else [
'rw', 'nosuid', 'nodev', 'noexec', 'relatime', partitioning_btrfs_compress_opt,
'ssd', 'space_cache=v2', 'discard=async', 'subvol=@var_log'
] | reject('equalto', '') | join(',')
}}
- path: /var/cache/pacman/pkg
uuid: "{{ partitioning_uuid_root | default([]) | first | default(omit) }}"
opts: >-
{{
'defaults,nosuid,nodev,noexec'
if system_cfg.filesystem != 'btrfs'
else [
'rw', 'nosuid', 'nodev', 'noexec', 'relatime', partitioning_btrfs_compress_opt,
'ssd', 'space_cache=v2', 'discard=async', 'subvol=@pkg'
] | reject('equalto', '') | join(',')
}}
- path: /var/log/audit - path: /var/log/audit
uuid: "{{ uuid_var_log_audit[0] | default(omit) }}" uuid: "{{ partitioning_uuid_var_log_audit[0] | default(omit) }}"
opts: "{{ 'defaults,nosuid,nodev,noexec' if filesystem != 'btrfs' opts: >-
else 'rw,nosuid,nodev,noexec,relatime,compress=zstd:15,ssd,space_cache=v2,discard=async,subvol=@var_log_audit' }}" {{
'defaults,nosuid,nodev,noexec'
if system_cfg.filesystem != 'btrfs'
else [
'rw', 'nosuid', 'nodev', 'noexec', 'relatime', partitioning_btrfs_compress_opt,
'ssd', 'space_cache=v2', 'discard=async', 'subvol=@var_log_audit'
] | reject('equalto', '') | join(',')
}}
- name: Mount tmp and var_tmp filesystems - name: Mount /boot filesystem
when: partitioning_separate_boot | bool
ansible.posix.mount: ansible.posix.mount:
path: /mnt{{ item.path }} path: /mnt/boot
src: tmpfs src: "UUID={{ partitioning_boot_fs_uuid.stdout }}"
fstype: tmpfs fstype: "{{ partitioning_boot_fs_fstype }}"
opts: defaults,nosuid,nodev,noexec opts: defaults
state: mounted state: mounted
loop:
- { path: /tmp }
- { path: /var/tmp }
- name: Mount boot filesystem - name: Mount boot filesystem
ansible.posix.mount: ansible.posix.mount:
path: "{{ '/mnt/boot/efi' if os | lower in ['rhel8', 'ubuntu', 'ubuntu-lts'] else '/mnt/boot' }}" path: "/mnt{{ partitioning_efi_mountpoint }}"
src: UUID={{ boot_uuid.stdout }} src: UUID={{ partitioning_boot_uuid.stdout }}
fstype: vfat fstype: vfat
state: mounted state: mounted
- name: Activate swap
when: system_cfg.features.swap.enabled | bool
vars:
partitioning_swap_cmd: >-
{{ 'swapon /mnt/swap/swapfile' if system_cfg.filesystem == 'btrfs' else 'swapon -U ' + partitioning_uuid_swap[0] }}
ansible.builtin.command: "{{ partitioning_swap_cmd }}"
register: partitioning_swap_activate_result
changed_when: partitioning_swap_activate_result.rc == 0
- name: Mount additional disks
ansible.builtin.include_tasks: extra_disks.yml

View File

@@ -1,6 +1,6 @@
--- ---
- name: Create and format XFS logical volumes - name: Create and format XFS logical volumes
when: cis | bool or item.lv not in ['var_log', 'var_log_audit'] when: system_cfg.features.cis.enabled or item.lv not in ['home', 'var', 'var_log', 'var_log_audit']
community.general.filesystem: community.general.filesystem:
dev: /dev/sys/{{ item.lv }} dev: /dev/sys/{{ item.lv }}
fstype: xfs fstype: xfs

View File

@@ -0,0 +1,113 @@
---
- name: VM existence protection check
when: system_cfg.type == "virtual"
block:
- name: Check if VM already exists on libvirt
when: hypervisor_type == "libvirt"
delegate_to: localhost
become: false
community.libvirt.virt:
command: list_vms
register: system_check_libvirt_existing_vms
changed_when: false
failed_when: false
- name: Abort if VM already exists on libvirt
when: hypervisor_type == "libvirt"
ansible.builtin.assert:
that:
- hostname not in system_check_libvirt_existing_vms.domains | default([])
fail_msg: |
VM {{ hostname }} already exists on libvirt hypervisor.
To avoid data loss, the playbook will not overwrite or delete existing VMs.
Please choose a different hostname or remove the existing VM manually before proceeding.
quiet: true
- name: Check if VM already exists on Proxmox
when: hypervisor_type == "proxmox"
delegate_to: localhost
become: false
community.proxmox.proxmox_vm_info:
api_host: "{{ hypervisor_cfg.url }}"
api_user: "{{ hypervisor_cfg.username }}"
api_password: "{{ hypervisor_cfg.password }}"
node: "{{ hypervisor_cfg.host }}"
vmid: "{{ system_cfg.id }}"
name: "{{ hostname }}"
type: qemu
register: system_check_proxmox_check_result
changed_when: false
- name: Abort if VM already exists on Proxmox
when: hypervisor_type == "proxmox"
ansible.builtin.assert:
that:
- system_check_proxmox_check_result.proxmox_vms | default([]) | length == 0
fail_msg: |
VM {{ hostname }} (ID: {{ system_cfg.id }}) already exists on Proxmox hypervisor.
To avoid data loss, the playbook will not overwrite or delete existing VMs.
Please choose a different hostname or VM ID, or remove the existing VM manually before proceeding.
quiet: true
- name: Check if VM already exists in vCenter
when: hypervisor_type == "vmware"
delegate_to: localhost
community.vmware.vmware_guest_info:
hostname: "{{ hypervisor_cfg.url }}"
username: "{{ hypervisor_cfg.username }}"
password: "{{ hypervisor_cfg.password }}"
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
datacenter: "{{ hypervisor_cfg.datacenter }}"
name: "{{ hostname }}"
folder: "{{ system_cfg.path if system_cfg.path | length > 0 else omit }}"
register: system_check_vmware_check_result
failed_when: false
changed_when: false
- name: Fail if vCenter lookup failed unexpectedly
when: hypervisor_type == "vmware"
ansible.builtin.assert:
that:
- not system_check_vmware_check_result.failed
or (system_check_vmware_check_result.msg is search('non-existing VM'))
fail_msg: |
Unable to verify VM existence in vCenter.
{{ system_check_vmware_check_result.msg | default('Unknown error') }}
quiet: true
- name: Abort if VM already exists in vCenter
when: hypervisor_type == "vmware"
ansible.builtin.assert:
that:
- system_check_vmware_check_result.instance is not defined
fail_msg: |
VM {{ hostname }} already exists in vCenter.
To avoid data loss, the playbook will not overwrite or delete existing VMs.
Please choose a different hostname or remove the existing VM manually before proceeding.
quiet: true
- name: Check if VM already exists on Xen
when: hypervisor_type == "xen"
delegate_to: localhost
ansible.builtin.command:
argv:
- xl
- list
register: system_check_xen_existing_vms
changed_when: false
failed_when: false
- name: Abort if VM already exists on Xen
when: hypervisor_type == "xen"
ansible.builtin.assert:
that:
- >-
not (
system_check_xen_existing_vms.stdout | default('')
is search('(?m)^' ~ (hostname | ansible.builtin.regex_escape) ~ '\\s+\\d+\\s')
)
fail_msg: |
VM {{ hostname }} already exists on Xen hypervisor.
To avoid data loss, the playbook will not overwrite or delete existing VMs.
Please choose a different hostname or remove the existing VM manually before proceeding.
quiet: true

View File

@@ -0,0 +1,24 @@
---
virtualization_libvirt_image_dir: >-
{{
system_cfg.path
if system_cfg is defined and (system_cfg.path | string | length) > 0
else '/var/lib/libvirt/images'
}}
virtualization_libvirt_disk_path: >-
{{ [virtualization_libvirt_image_dir, hostname ~ '.qcow2'] | ansible.builtin.path_join }}
virtualization_libvirt_cloudinit_path: >-
{{ [virtualization_libvirt_image_dir, hostname ~ '-cloudinit.iso'] | ansible.builtin.path_join }}
virtualization_mac_address: >-
{{ '52:54:00' | community.general.random_mac(seed=hostname) }}
virtualization_xen_disk_path: /var/lib/xen/images
virtualization_tpm2_enabled: >-
{{
(system_cfg.luks.enabled | bool)
and (system_cfg.luks.auto | bool)
and (
(system_cfg.luks.method | lower)
== 'tpm2'
)
}}

View File

@@ -1,41 +1,65 @@
--- ---
- name: Check if VM disk exists - name: Build disk definitions
delegate_to: localhost ansible.builtin.set_fact:
ansible.builtin.stat: virtualization_libvirt_disks: "{{ virtualization_libvirt_disks | default([]) + [virtualization_libvirt_disk_cfg] }}"
path: "{{ vm_path | default('/var/lib/libvirt/images/') }}{{ hostname }}.qcow2" vars:
register: vm_disk_stat device_letter_map: "abcdefghijklmnopqrstuvwxyz"
device_letter: "{{ device_letter_map[ansible_loop.index0] }}"
- name: Create VM disk virtualization_libvirt_disk_cfg: >-
when: not vm_disk_stat.stat.exists {{
delegate_to: localhost {
ansible.builtin.command: qemu-img create -f qcow2 {{ vm_path | default('/var/lib/libvirt/images/') }}{{ hostname }}.qcow2 {{ vm_size }}G 'path': (
changed_when: result.rc == 0 virtualization_libvirt_disk_path
register: result if ansible_loop.index0 == 0
else ([virtualization_libvirt_image_dir, hostname ~ '-disk' ~ ansible_loop.index0 ~ '.qcow2'] | ansible.builtin.path_join)
- name: Generate Random MAC Address ),
delegate_to: localhost 'target': 'vd' ~ device_letter,
ansible.builtin.shell: set -o pipefail && openssl rand -hex 5 | sed 's/\(..\)/\1:/g; s/.$//' | sed 's/^/02:/' 'bus': 'virtio',
'format': 'qcow2',
'size': (item.size | float)
}
}}
loop: "{{ system_cfg.disks }}"
loop_control:
label: "{{ item | to_json }}"
extended: true
changed_when: false changed_when: false
register: mac_address_output
- name: Create VM disks
delegate_to: localhost
ansible.builtin.command:
argv:
- qemu-img
- create
- -f
- qcow2
- "{{ item.path }}"
- "{{ item.size }}G"
creates: "{{ item.path }}"
loop: "{{ virtualization_libvirt_disks }}"
loop_control:
label: "{{ item.path }}"
- name: Render cloud config templates - name: Render cloud config templates
delegate_to: localhost delegate_to: localhost
ansible.builtin.template: ansible.builtin.template:
src: "{{ item.src }}" src: "{{ item.src }}"
dest: /tmp/{{ item.dest_prefix }}-{{ hostname }}.yml dest: /tmp/{{ item.dest_prefix }}-{{ hostname }}.yml
mode: '0644' mode: "0644"
loop: loop:
- { src: cloud-user-data.yml.j2, dest_prefix: cloud-user-data } - { src: cloud-user-data.yml.j2, dest_prefix: cloud-user-data }
- { src: cloud-network-config.yml.j2, dest_prefix: cloud-network-config } - { src: cloud-network-config.yml.j2, dest_prefix: cloud-network-config }
- name: Create cloud-init disk - name: Create cloud-init disk
delegate_to: localhost delegate_to: localhost
ansible.builtin.command: > ansible.builtin.command:
cloud-localds {{ vm_path | default('/var/lib/libvirt/images/') }}/{{ hostname }}-cloudinit.iso argv:
/tmp/cloud-user-data-{{ hostname }}.yml - cloud-localds
-N /tmp/cloud-network-config-{{ hostname }}.yml - "{{ virtualization_libvirt_cloudinit_path }}"
changed_when: result.rc == 0 - "/tmp/cloud-user-data-{{ hostname }}.yml"
register: result - -N
- "/tmp/cloud-network-config-{{ hostname }}.yml"
creates: "{{ virtualization_libvirt_cloudinit_path }}"
- name: Create VM using libvirt - name: Create VM using libvirt
delegate_to: localhost delegate_to: localhost
@@ -48,3 +72,9 @@
community.libvirt.virt: community.libvirt.virt:
name: "{{ hostname }}" name: "{{ hostname }}"
state: running state: running
register: virtualization_libvirt_start_result
- name: Set VM created fact
ansible.builtin.set_fact:
virtualization_vm_created_in_run: true
when: virtualization_libvirt_start_result is defined and virtualization_libvirt_start_result.changed | bool

View File

@@ -1,3 +1,3 @@
--- ---
- name: Create Virtual Machine - name: Create Virtual Machine
ansible.builtin.include_tasks: "{{ hypervisor }}.yml" ansible.builtin.include_tasks: "{{ hypervisor_type }}.yml"

View File

@@ -1,51 +1,80 @@
--- ---
- name: Deploy VM on Proxmox - name: Deploy VM on Proxmox
delegate_to: localhost delegate_to: localhost
community.general.proxmox_kvm: vars:
api_host: "{{ hypervisor_url }}" virtualization_proxmox_scsi: >-
api_user: "{{ hypervisor_username }}" {%- set out = {} -%}
api_password: "{{ hypervisor_password }}" {%- for disk in system_cfg.disks -%}
ciuser: "{{ user_name }}" {%- set _ = out.update({ 'scsi' ~ loop.index0: hypervisor_cfg.storage ~ ':' ~ (disk.size | int) }) -%}
cipassword: "{{ user_password }}" {%- endfor -%}
{{ out }}
community.proxmox.proxmox_kvm:
api_host: "{{ hypervisor_cfg.url }}"
api_user: "{{ hypervisor_cfg.username }}"
api_password: "{{ hypervisor_cfg.password }}"
ciuser: "{{ system_cfg.user.name }}"
cipassword: "{{ system_cfg.user.password }}"
ciupgrade: false ciupgrade: false
node: "{{ hypervisor_node }}" node: "{{ hypervisor_cfg.host }}"
vmid: "{{ vm_id }}" vmid: "{{ system_cfg.id }}"
name: "{{ hostname }}" name: "{{ hostname }}"
cpu: host cpu: host
cores: "{{ vm_cpus }}" cores: "{{ system_cfg.cpus }}"
memory: "{{ vm_memory }}" memory: "{{ system_cfg.memory }}"
balloon: "{{ vm_ballo | default(omit) }}" balloon: "{{ system_cfg.balloon if system_cfg.balloon is defined and system_cfg.balloon | int > 0 else omit }}"
numa_enabled: true numa_enabled: true
hotplug: network,disk hotplug: network,disk
update: "{{ virtualization_tpm2_enabled | bool }}"
update_unsafe: "{{ virtualization_tpm2_enabled | bool }}"
bios: ovmf bios: ovmf
machine: "{{ 'q35' if virtualization_tpm2_enabled | bool else omit }}"
boot: ac boot: ac
scsihw: virtio-scsi-single scsihw: virtio-scsi-single
scsi: scsi: "{{ virtualization_proxmox_scsi }}"
scsi0: "{{ hypervisor_storage }}:{{ vm_size }}"
efidisk0: efidisk0:
efitype: 4m efitype: 4m
format: raw format: raw
pre_enrolled_keys: false pre_enrolled_keys: false
storage: "{{ hypervisor_storage }}" storage: "{{ hypervisor_cfg.storage }}"
tpmstate0: >-
{{
{'storage': hypervisor_cfg.storage, 'version': '2.0'}
if virtualization_tpm2_enabled | bool
else omit
}}
ide: ide:
ide0: "{{ boot_iso }},media=cdrom" ide0: "{{ boot_iso }},media=cdrom"
ide1: "{{ rhel_iso | default(omit) }},media=cdrom" ide1: "{{ rhel_iso + ',media=cdrom' if rhel_iso is defined and rhel_iso | length > 0 else omit }}"
ide2: "{{ hypervisor_storage }}:cloudinit" ide2: "{{ hypervisor_cfg.storage }}:cloudinit"
net: net:
net0: virtio,bridge={{ vm_nif }}{% if vlan_name is defined and vlan_name %},tag={{ vlan_name }}{% endif %} net0: >-
virtio,bridge={{ system_cfg.network.bridge }}{% if system_cfg.network.vlan is defined and system_cfg.network.vlan | string | length > 0 %},tag={{ system_cfg.network.vlan }}{% endif %}
ipconfig: ipconfig:
ipconfig0: ip={{ vm_ip }},gw={{ vm_gw }} ipconfig0: >-
nameservers: "{{ vm_dns }}" {{
'ip=' ~ system_cfg.network.ip ~ '/' ~ system_cfg.network.prefix
~ (',gw=' ~ system_cfg.network.gateway if system_cfg.network.gateway is defined and system_cfg.network.gateway | length else '')
if system_cfg.network.ip is defined and system_cfg.network.ip | string | length
else 'ip=dhcp'
}}
nameservers: "{{ system_cfg.network.dns.servers if system_cfg.network.dns.servers | length else omit }}"
searchdomains: "{{ system_cfg.network.dns.search if system_cfg.network.dns.search | length else omit }}"
onboot: true onboot: true
state: present state: present
- name: Start VM on Proxmox - name: Start VM on Proxmox
delegate_to: localhost delegate_to: localhost
community.general.proxmox_kvm: community.proxmox.proxmox_kvm:
api_host: "{{ hypervisor_url }}" api_host: "{{ hypervisor_cfg.url }}"
api_user: "{{ hypervisor_username }}" api_user: "{{ hypervisor_cfg.username }}"
api_password: "{{ hypervisor_password }}" api_password: "{{ hypervisor_cfg.password }}"
node: "{{ hypervisor_node }}" node: "{{ hypervisor_cfg.host }}"
name: "{{ hostname }}" name: "{{ hostname }}"
vmid: "{{ vm_id }}" vmid: "{{ system_cfg.id }}"
state: started state: started
register: virtualization_proxmox_start_result
- name: Set VM created fact
ansible.builtin.set_fact:
virtualization_vm_created_in_run: true
when: virtualization_proxmox_start_result is defined and virtualization_proxmox_start_result.changed | bool

View File

@@ -1,45 +1,102 @@
---
- name: Build vCenter disk list
ansible.builtin.set_fact:
virtualization_vmware_disks: "{{ virtualization_vmware_disks | default([]) + [virtualization_vmware_disk_cfg] }}"
vars:
virtualization_vmware_disk_cfg:
size_gb: "{{ item.size | int }}"
type: thin
datastore: "{{ hypervisor_cfg.storage }}"
loop: "{{ system_cfg.disks }}"
loop_control:
label: "{{ item | to_json }}"
changed_when: false
- name: Create VM in vCenter - name: Create VM in vCenter
delegate_to: localhost delegate_to: localhost
community.vmware.vmware_guest: community.vmware.vmware_guest:
hostname: "{{ hypervisor_url }}" hostname: "{{ hypervisor_cfg.url }}"
username: "{{ hypervisor_username }}" username: "{{ hypervisor_cfg.username }}"
password: "{{ hypervisor_password }}" password: "{{ hypervisor_cfg.password }}"
validate_certs: false validate_certs: "{{ hypervisor_cfg.certs | bool }}"
datacenter: "{{ hypervisor_cluster }}" datacenter: "{{ hypervisor_cfg.datacenter }}"
cluster: "{{ hypervisor_node }}" cluster: "{{ hypervisor_cfg.cluster }}"
folder: "{{ vm_path }}" folder: "{{ system_cfg.path if system_cfg.path | string | length > 0 else omit }}"
name: "{{ hostname }}" name: "{{ hostname }}"
guest_id: otherGuest64 guest_id: otherLinux64Guest
state: poweredon annotation: |
disk: {{ note if note is defined else '' }}
- size_gb: "{{ vm_size }}" state: "{{ 'poweredoff' if virtualization_tpm2_enabled | bool else 'poweredon' }}"
type: thin disk: "{{ virtualization_vmware_disks }}"
datastore: "{{ hypervisor_storage }}"
hardware: hardware:
memory_mb: "{{ vm_memory }}" memory_mb: "{{ system_cfg.memory }}"
num_cpus: "{{ vm_cpus }}" num_cpus: "{{ system_cfg.cpus }}"
boot_firmware: efi boot_firmware: efi
secure_boot: false secure_boot: false
cdrom: cdrom: >-
- controller_number: 0 {{
unit_number: 0 [ {
controller_type: sata "controller_number": 0,
state: present "unit_number": 0,
type: iso "controller_type": "sata",
iso_path: "{{ boot_iso }}" "state": "present",
- controller_number: 0 "type": "iso",
unit_number: 1 "iso_path": boot_iso
controller_type: sata } ]
state: present +
type: iso ( [ {
iso_path: "{{ rhel_iso | default(omit) }}" "controller_number": 0,
"unit_number": 1,
"controller_type": "sata",
"state": "present",
"type": "iso",
"iso_path": rhel_iso
} ] if rhel_iso is defined and rhel_iso | length > 0 else [] )
}}
networks: networks:
- name: "{{ vm_nif }}" - name: "{{ system_cfg.network.bridge }}"
type: dhcp type: dhcp
vlan: "{{ vlan_name | default(omit) }}" vlan: "{{ system_cfg.network.vlan if system_cfg.network.vlan is defined and system_cfg.network.vlan | string | length > 0 else omit }}"
register: vmware_guest_result register: virtualization_vmware_create_result
failed_when:
- vmware_guest_result.failed is defined and vmware_guest_result.failed - name: Set VM created fact when VM was powered on during creation
- "'error' in vmware_guest_result" ansible.builtin.set_fact:
- "'failed' in vmware_guest_result" virtualization_vm_created_in_run: true
- vmware_guest_result.rc is defined and vmware_guest_result.rc != 0 when:
- virtualization_vmware_create_result is defined
- not virtualization_tpm2_enabled | bool
- virtualization_vmware_create_result.changed | bool
- name: Ensure vTPM2 is enabled when required
when: virtualization_tpm2_enabled | bool
delegate_to: localhost
community.vmware.vmware_guest_tpm:
hostname: "{{ hypervisor_cfg.url }}"
username: "{{ hypervisor_cfg.username }}"
password: "{{ hypervisor_cfg.password }}"
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
datacenter: "{{ hypervisor_cfg.datacenter }}"
folder: "{{ system_cfg.path if system_cfg.path | string | length > 0 else omit }}"
name: "{{ hostname }}"
state: present
- name: Start VM in vCenter
when: virtualization_tpm2_enabled | bool
delegate_to: localhost
vmware.vmware.vm_powerstate:
hostname: "{{ hypervisor_cfg.url }}"
username: "{{ hypervisor_cfg.username }}"
password: "{{ hypervisor_cfg.password }}"
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
datacenter: "{{ hypervisor_cfg.datacenter }}"
name: "{{ hostname }}"
state: powered-on
register: virtualization_vmware_start_result
- name: Set VM created fact when VM was started separately (TPM2 case)
ansible.builtin.set_fact:
virtualization_vm_created_in_run: true
when:
- virtualization_tpm2_enabled | bool
- virtualization_vmware_start_result is defined
- virtualization_vmware_start_result.changed | bool

View File

@@ -0,0 +1,79 @@
---
- name: Deploy VM on Xen
block:
- name: Build disk definitions
ansible.builtin.set_fact:
virtualization_xen_disks: "{{ virtualization_xen_disks | default([]) + [virtualization_xen_disk_cfg] }}"
vars:
device_letter_map: "abcdefghijklmnopqrstuvwxyz"
device_letter: "{{ device_letter_map[ansible_loop.index0] }}"
virtualization_xen_disk_cfg: >-
{{
{
'path': (
virtualization_xen_disk_path ~ '/' ~ hostname ~ '.qcow2'
if ansible_loop.index0 == 0
else virtualization_xen_disk_path ~ '/' ~ hostname ~ '-disk' ~ ansible_loop.index0 ~ '.qcow2'
),
'target': 'xvd' ~ device_letter,
'size': (item.size | float)
}
}}
loop: "{{ system_cfg.disks }}"
loop_control:
label: "{{ item | to_json }}"
extended: true
changed_when: false
- name: Create VM disks for Xen
delegate_to: localhost
ansible.builtin.command:
argv:
- qemu-img
- create
- -f
- qcow2
- "{{ item.path }}"
- "{{ item.size }}G"
creates: "{{ item.path }}"
loop: "{{ virtualization_xen_disks }}"
loop_control:
label: "{{ item.path }}"
- name: Render Xen VM configuration
delegate_to: localhost
vars:
xen_installer_media_enabled: true
ansible.builtin.template:
src: xen.cfg.j2
dest: /tmp/xen-{{ hostname }}.cfg
mode: "0644"
- name: Create Xen VM
delegate_to: localhost
ansible.builtin.command:
argv:
- xl
- create
- /tmp/xen-{{ hostname }}.cfg
register: virtualization_xen_create_result
changed_when: virtualization_xen_create_result.rc == 0
- name: Ensure VM is running
delegate_to: localhost
ansible.builtin.command:
argv:
- xl
- list
register: virtualization_xen_list_result
changed_when: false
failed_when: false
- name: Set VM created fact
ansible.builtin.set_fact:
virtualization_vm_created_in_run: true
when:
- virtualization_xen_list_result is defined
- >-
virtualization_xen_list_result.stdout | default('')
is search('(?m)^' ~ (hostname | ansible.builtin.regex_escape) ~ '\\s+\\d+\\s')

View File

@@ -3,9 +3,40 @@ network:
ethernets: ethernets:
id0: id0:
match: match:
macaddress: "{{ mac_address_output.stdout }}" macaddress: "{{ virtualization_mac_address }}"
{% set has_static = system_cfg.network.ip is defined and system_cfg.network.ip | string | length %}
{% set dns_list = system_cfg.network.dns.servers | default([]) %}
{% set search_list = system_cfg.network.dns.search | default([]) %}
{% if has_static %}
addresses: addresses:
- "{{ vm_ip }}" - "{{ system_cfg.network.ip }}/{{ system_cfg.network.prefix }}"
gateway4: "{{ vm_gw }}" {% if system_cfg.network.gateway is defined and system_cfg.network.gateway | string | length %}
gateway4: "{{ system_cfg.network.gateway }}"
{% endif %}
{% else %}
dhcp4: true
{% if dns_list | length or search_list | length %}
dhcp4-overrides:
{% if dns_list | length %}
use-dns: false
{% endif %}
{% if search_list | length %}
use-domains: false
{% endif %}
{% endif %}
{% endif %}
{% if dns_list or search_list %}
nameservers: nameservers:
addresses: ['1.1.1.1', '1.0.0.1'] {% if dns_list %}
addresses:
{% for dns in dns_list %}
- "{{ dns }}"
{% endfor %}
{% endif %}
{% if search_list %}
search:
{% for search in search_list %}
- "{{ search }}"
{% endfor %}
{% endif %}
{% endif %}

View File

@@ -1,10 +1,12 @@
#cloud-config #cloud-config
hostname: "archiso" hostname: "archiso"
ssh_pwauth: true ssh_pwauth: true
package_update: false
package_upgrade: false
users: users:
- name: "{{ user_name }}" - name: "{{ system_cfg.user.name }}"
primary_group: "{{ user_name }}" primary_group: "{{ system_cfg.user.name }}"
groups: users groups: users
sudo: ALL=(ALL) NOPASSWD:ALL sudo: ALL=(ALL) NOPASSWD:ALL
passwd: "{{ user_password | password_hash('sha512') }}" passwd: "{{ system_cfg.user.password | password_hash('sha512') }}"
lock_passwd: False lock_passwd: False

View File

@@ -1,15 +1,15 @@
<domain type='kvm'> <domain type='kvm'>
<name>{{ hostname }}</name> <name>{{ hostname }}</name>
<memory>{{ vm_memory | int * 1024 }}</memory> <memory>{{ system_cfg.memory | int * 1024 }}</memory>
{% if vm_ballo is defined %}<currentMemory>{{ vm_ballo | int * 1024 }}</currentMemory>{% endif %} {% if system_cfg.balloon is defined and system_cfg.balloon | int > 0 %}<currentMemory>{{ system_cfg.balloon | int * 1024 }}</currentMemory>{% endif %}
<vcpu placement='static'>{{ vm_cpus }}</vcpu> <vcpu placement='static'>{{ system_cfg.cpus }}</vcpu>
<os> <os>
<type arch='x86_64' machine="pc-q35-8.0">hvm</type> <type arch='x86_64' machine="pc-q35-8.0">hvm</type>
<bootmenu enable='no'/> <bootmenu enable='no'/>
<boot dev='hd'/> <boot dev='hd'/>
<boot dev='cdrom'/> <boot dev='cdrom'/>
<loader readonly="yes" type="pflash">/usr/share/edk2/x64/OVMF_CODE.secboot.fd</loader> <loader readonly="yes" type="pflash">/usr/share/edk2/x64/OVMF_CODE.secboot.4m.fd</loader>
<nvram template="/usr/share/edk2/x64/OVMF_VARS.fd"/> <nvram template="/usr/share/edk2/x64/OVMF_VARS.4m.fd"/>
</os> </os>
<features> <features>
<acpi/> <acpi/>
@@ -22,11 +22,13 @@
<on_reboot>restart</on_reboot> <on_reboot>restart</on_reboot>
<on_crash>destroy</on_crash> <on_crash>destroy</on_crash>
<devices> <devices>
{% for disk in virtualization_libvirt_disks | default([]) %}
<disk type='file' device='disk'> <disk type='file' device='disk'>
<driver name='qemu' type='qcow2'/> <driver name='qemu' type='{{ disk.format }}'/>
<source file='{{ vm_path | default('/var/lib/libvirt/images/') }}{{ hostname }}.qcow2'/> <source file='{{ disk.path }}'/>
<target dev='vda' bus='virtio'/> <target dev='{{ disk.target }}' bus='{{ disk.bus }}'/>
</disk> </disk>
{% endfor %}
<disk type="file" device="cdrom"> <disk type="file" device="cdrom">
<driver name="qemu" type="raw"/> <driver name="qemu" type="raw"/>
<source file="{{ boot_iso }}"/> <source file="{{ boot_iso }}"/>
@@ -34,10 +36,10 @@
</disk> </disk>
<disk type="file" device="cdrom"> <disk type="file" device="cdrom">
<driver name="qemu" type="raw"/> <driver name="qemu" type="raw"/>
<source file="{{ vm_path | default('/var/lib/libvirt/images/') }}{{ hostname }}-cloudinit.iso"/> <source file="{{ virtualization_libvirt_cloudinit_path }}"/>
<target dev="sdb" bus="sata"/> <target dev="sdb" bus="sata"/>
</disk> </disk>
{% if rhel_iso is defined %} {% if rhel_iso is defined and rhel_iso | length > 0 %}
<disk type="file" device="cdrom"> <disk type="file" device="cdrom">
<driver name="qemu" type="raw"/> <driver name="qemu" type="raw"/>
<source file="{{ rhel_iso }}"/> <source file="{{ rhel_iso }}"/>
@@ -45,10 +47,15 @@
</disk> </disk>
{% endif %} {% endif %}
<interface type='network'> <interface type='network'>
<mac address="{{ mac_address_output.stdout }}"/> <mac address="{{ virtualization_mac_address }}"/>
<source network='default'/> <source network='{{ system_cfg.network.bridge if (system_cfg.network.bridge | default('' ) | string | length) > 0 else "default" }}'/>
<model type='virtio'/> <model type='virtio'/>
</interface> </interface>
{% if virtualization_tpm2_enabled %}
<tpm model='tpm-crb'>
<backend type='emulator' version='2.0'/>
</tpm>
{% endif %}
<input type="tablet" bus="usb"/> <input type="tablet" bus="usb"/>
<input type="mouse" bus="ps2"/> <input type="mouse" bus="ps2"/>
<input type="keyboard" bus="ps2"/> <input type="keyboard" bus="ps2"/>

View File

@@ -8,7 +8,7 @@ metadata_expire=86400
repo_gpgcheck=0 repo_gpgcheck=0
type=rpm type=rpm
gpgcheck=1 gpgcheck=1
gpgkey=https://getfedora.org/static/fedora.gpg gpgkey=https://fedoraproject.org/fedora.gpg
skip_if_unavailable=False skip_if_unavailable=False
[fedora-updates] [fedora-updates]
@@ -21,5 +21,5 @@ repo_gpgcheck=0
type=rpm type=rpm
gpgcheck=1 gpgcheck=1
metadata_expire=86400 metadata_expire=86400
gpgkey=https://getfedora.org/static/fedora.gpg gpgkey=https://fedoraproject.org/fedora.gpg
skip_if_unavailable=False skip_if_unavailable=False

13
templates/rhel10.repo.j2 Normal file
View File

@@ -0,0 +1,13 @@
[rhel10-baseos]
name=RHEL 10 BaseOS
baseurl=file:///usr/local/install/redhat/dvd/BaseOS
enabled=1
gpgcheck=0
gpgkey=file:///usr/local/install/redhat/dvd/RPM-GPG-KEY-redhat-release
[rhel10-appstream]
name=RHEL 10 AppStream
baseurl=file:///usr/local/install/redhat/dvd/AppStream
enabled=1
gpgcheck=0
gpgkey=file:///usr/local/install/redhat/dvd/RPM-GPG-KEY-redhat-release

17
templates/xen.cfg.j2 Normal file
View File

@@ -0,0 +1,17 @@
builder = "hvm"
name = "{{ hostname }}"
memory = "{{ system_cfg.memory }}"
vcpus = "{{ system_cfg.cpus }}"
disk = [
{%- for disk in virtualization_xen_disks | default([]) -%}
'file:{{ disk.path }},{{ disk.target }},w'{% if not loop.last or xen_installer_media_enabled | bool %}, {% endif %}
{%- endfor -%}
{%- if xen_installer_media_enabled | bool -%}
'{{ boot_iso }},,hdc,cdrom'{% if rhel_iso is defined and rhel_iso | length > 0 %}, '{{ rhel_iso }},,hdd,cdrom'{% endif %}
{%- endif -%}
]
vif = [ 'bridge={{ system_cfg.network.bridge }},model=e1000' ]
boot = "{{ 'dc' if xen_installer_media_enabled | bool else 'c' }}"
on_crash = "preserve"
on_poweroff = "destroy"
serial = "pty"

View File

@@ -0,0 +1,62 @@
---
# Example variables for baremetal installs.
hypervisor:
type: "none"
system:
type: "physical"
os: "archlinux"
filesystem: "btrfs" # btrfs|ext4|xfs
name: "{{ inventory_hostname }}"
cpus: 8
memory: 16384
network:
ip: "{{ ansible_host | default('') }}"
prefix: 24
gateway: "10.0.0.1"
dns:
servers:
- "1.1.1.1"
disks:
- device: "/dev/sda"
size: 120
- device: "/dev/sdb"
size: 500
mount:
path: /data
fstype: ext4
user:
name: "admin"
password: "CHANGE_ME"
key: "ssh-ed25519 AAAA..."
root:
password: "CHANGE_ME"
luks:
enabled: true
passphrase: "CHANGE_ME"
mapper: "SYSTEM_DECRYPTED"
auto: true
method: "tpm2"
tpm2:
device: "auto"
pcrs: "7"
features:
cis:
enabled: false
selinux:
enabled: true
firewall:
enabled: true
backend: "firewalld"
toolkit: "nftables"
ssh:
enabled: true
zstd:
enabled: true
swap:
enabled: true
banner:
motd: true
sudo: true
chroot:
tool: "arch-chroot"

View File

@@ -1,21 +1,94 @@
vm_ip: "{{ inventory_hostname }}/{{ vm_nms }}" ---
install_type: "virtual" # Example variables for virtual provisioning.
cis: false custom_iso: false
hypervisor_url: "192.168.0.2" hypervisor:
hypervisor_username: "root@pam" type: "proxmox" # libvirt|proxmox|vmware|xen|none
hypervisor_password: "SomePassword" url: "pve01.example.com"
hypervisor_node: "NodeName" username: "root@pam"
hypervisor_storage: "local-btrfs" password: "CHANGE_ME"
boot_iso: "local-btrfs:iso/archlinux-x86_64.iso" host: "pve01"
rhel_iso: "local-btrfs:rhel-9.4-x86_64-dvd.iso" storage: "local-lvm"
datacenter: "dc01"
cluster: "cluster01"
certs: false
ssh: true # VMware only; enables temporary SSH in installer
# For VMware-Tools system:
ansible_vmware_host: "{{ hypervisor_url }}" type: "virtual" # virtual|physical
ansible_vmware_user: "{{ hypervisor_username }}" os: "archlinux"
ansible_vmware_password: "{{ hypervisor_password }}" version: ""
ansible_vmware_guest_path: "/{{ hypervisor_cluster }}/vm{{ vm_path }}/{{ hostname }}" filesystem: "btrfs" # btrfs|ext4|xfs
ansible_vmware_validate_certs: no name: "{{ inventory_hostname }}"
ansible_vmware_tools_user: "root" id: 100
ansible_vmware_tools_password: "" cpus: 4
vmware_ssh: true memory: 8192
balloon: 0
network:
bridge: "vmbr0"
ip: "{{ inventory_hostname }}"
prefix: 24
gateway: "10.0.0.1"
dns:
servers:
- "1.1.1.1"
- "1.0.0.1"
search:
- "example.com"
path: "/Lab/Example"
disks:
- size: 80
- size: 200
mount:
path: /data
fstype: xfs
label: DATA
opts: defaults
user:
name: "ops"
password: "CHANGE_ME"
key: "ssh-ed25519 AAAA..."
root:
password: "CHANGE_ME"
luks:
enabled: false
passphrase: "CHANGE_ME"
mapper: "SYSTEM_DECRYPTED"
auto: true
method: "tpm2"
tpm2:
device: "auto"
pcrs: "7"
keysize: 64
options: "discard,tries=3"
type: "luks2"
cipher: "aes-xts-plain64"
hash: "sha512"
iter: 4000
bits: 512
pbkdf: "argon2id"
urandom: true
verify: true
packages:
- jq
- tmux
features:
cis:
enabled: false
selinux:
enabled: true
firewall:
enabled: true
backend: "firewalld" # firewalld|ufw
toolkit: "nftables" # nftables|iptables
ssh:
enabled: true
zstd:
enabled: true
swap:
enabled: true
banner:
motd: true
sudo: true
chroot:
tool: "arch-chroot" # arch-chroot|chroot|systemd-nspawn