Compare commits

...

56 Commits

Author SHA1 Message Date
07492b5b57 refactor(cleanup): add configurable verify_boot, boot_timeout, and remove_on_failure defaults 2026-02-20 23:02:24 +01:00
14913bcd3d refactor: move playbook-root templates into their respective roles 2026-02-20 23:01:38 +01:00
041650c287 refactor: add loop_control labels to dict-based loops across all roles 2026-02-20 23:00:53 +01:00
a63ffbc731 refactor(partitioning): move btrfs home quota to configurable default 2026-02-20 22:55:37 +01:00
9d2f1cc5bd fix(environment): detect RHEL ISO device dynamically instead of hardcoded /dev/sr paths 2026-02-20 22:54:42 +01:00
f72f9feb9a refactor(global_defaults): split system.yml into composable normalization stages 2026-02-20 22:54:05 +01:00
417737f904 refactor(global_defaults): extract OS family lists to single source of truth 2026-02-20 22:52:55 +01:00
a06c2ebdcf fix(partitioning): add failed_when to all blkid commands to catch empty UUIDs 2026-02-20 22:52:18 +01:00
e174ecda42 fix(partitioning): add default fallbacks for is_rhel, os, os_version in defaults 2026-02-20 22:51:37 +01:00
5246a905bb fix(virtualization): use hostname variable instead of hardcoded archiso in cloud-user-data 2026-02-20 22:51:32 +01:00
d00d84b69c fix(virtualization): avoid no-handler lint finding in xen VM created tracking 2026-02-20 22:29:03 +01:00
4dafa8c596 fix(partitioning): fix line length violation in home size calculation 2026-02-20 22:28:58 +01:00
53584b8730 fix(configuration): add pipefail to root password shell pipe 2026-02-20 22:28:54 +01:00
ce40468b77 fix(bootstrap): use release map for ubuntu version detection 2026-02-20 22:27:46 +01:00
4b4fab3c33 chore: add .yamllint matching main project conventions 2026-02-20 22:27:31 +01:00
db2fab5e7d fix(configuration): use chpasswd for root password and separate shell setting 2026-02-20 22:27:17 +01:00
42be0a5919 fix(configuration): add explicit LUKS auto-decrypt fallback state tracking and logging 2026-02-20 22:26:47 +01:00
17400fa6ff refactor(partitioning): externalize hardcoded LVM and disk sizing constants to defaults 2026-02-20 22:26:23 +01:00
deb14d2c94 fix(virtualization): add xen VM existence check and improve changed_when 2026-02-20 22:25:10 +01:00
65c5b1029b fix(cis): add pipefail to sshd version detection and define binary defaults 2026-02-20 22:24:14 +01:00
a1fbb7c21d feat(cleanup): gate RHEL ISO disk and fstab handling on rhel_repo.source 2026-02-20 21:51:20 +01:00
d076ac8fef feat(global_defaults): add system.features.rhel_repo option (iso|satellite|none) 2026-02-20 21:51:16 +01:00
c82e4afc4d fix(encryption): add warning before silent TPM2-to-keyfile fallback 2026-02-20 21:51:12 +01:00
ac72fdc4a6 fix(partitioning): correct wipefs changed_when to report actual disk modification 2026-02-20 21:51:09 +01:00
b2e050c467 fix(validation): require password for primary user in system.users[0] 2026-02-20 21:51:06 +01:00
914d7dd9d1 fix(system_check): move no_log from block to individual API tasks 2026-02-20 21:51:02 +01:00
21bf8f79e2 fix(cis): make mlkem768x25519-sha256 KexAlgorithm conditional on OpenSSH 9.9+ 2026-02-20 21:50:58 +01:00
38feff4369 fix(cis): use is_rhel for journald config path instead of fedora-only check 2026-02-20 21:50:55 +01:00
404529e8a4 refactor(configuration): add conditional dispatch to task includes 2026-02-20 21:16:52 +01:00
3db18858c3 refactor(cis): move OS-specific binary resolution to vars/main.yml 2026-02-20 21:16:48 +01:00
72a9576abe refactor(configuration): split network.yml into per-init-system dispatch files 2026-02-20 21:16:45 +01:00
462c2c7dfe refactor(bootstrap): restructure conditional package lists to list concatenation 2026-02-20 21:16:40 +01:00
ef8bfeaf84 refactor(configuration): convert services.yml to list-based loop 2026-02-20 21:16:37 +01:00
ba6be037ac refactor(virt): adopt module_defaults for hypervisor credentials 2026-02-20 21:16:33 +01:00
5ca1c7f570 refactor(cleanup): restructure dispatch to use hypervisor_type include 2026-02-20 21:16:28 +01:00
cd8e477534 refactor(partitioning): extract VG name to defaults variable 2026-02-20 21:16:25 +01:00
c439e9741e fix(configuration): remove trailing blank line from extras.yml 2026-02-20 20:20:33 +01:00
0a5c70e49f docs(environment): document RPM GPG policy relaxation 2026-02-20 20:19:57 +01:00
19f2c9efe2 chore(bootstrap): align ansible.cfg with main project settings 2026-02-20 20:19:46 +01:00
230c74fd9b feat(system_check): add safety check for physical installs 2026-02-20 20:19:37 +01:00
a2c19e2e49 fix(cleanup): fix vmware CD-ROM omit fragility and add cross-role defaults 2026-02-20 20:19:25 +01:00
9f9a4b38b8 fix(virtualization): add XML safety attributes and switch xen to virtio 2026-02-20 20:18:49 +01:00
524356cf8d fix(cis): remove deprecated sshd options and update hardening values 2026-02-20 20:17:52 +01:00
a2993212ca fix(configuration): disambiguate BLS task names and clean up misc noise 2026-02-20 20:17:05 +01:00
fba2e5fc94 refactor(configuration): relocate login banner and fix blockinfile markers 2026-02-20 20:16:19 +01:00
cf68a93b45 fix(configuration): use short hostname and allow per-user shell 2026-02-20 20:15:49 +01:00
3000268a0e fix(partitioning): mount extra disks by UUID instead of device path 2026-02-20 20:15:25 +01:00
196c5be67a fix(partitioning): correct LVM swap sizing and harden UUID fallbacks 2026-02-20 20:15:00 +01:00
33bad193b4 fix(configuration): add trailing semicolons to NM keyfile DNS fields 2026-02-20 20:14:06 +01:00
d5277802f7 fix(bootstrap): add missing packages and remove duplicates 2026-02-20 20:13:53 +01:00
28e6cf50d1 fix(bootstrap): add devpts mount and use ephemeral state for RHEL DVD 2026-02-20 20:12:59 +01:00
42cb5071c2 fix(bootstrap): unify resolv.conf to live environment DNS symlink 2026-02-20 20:12:42 +01:00
23a798a63a fix(global_defaults): add no_log to hypervisor tasks and expand validation 2026-02-20 20:11:37 +01:00
5dd84c6b39 fix: configurable OVMF/machine type, routes syntax, package lists, interface names 2026-02-20 18:47:12 +01:00
d0ae20911b fix(cleanup): keep RHEL ISO ide1 attached as local repo 2026-02-20 18:41:40 +01:00
b6d06dd96d fix: deep analysis audit — no_log, resolv.conf, service conflicts, lint 2026-02-20 18:34:59 +01:00
73 changed files with 1367 additions and 1074 deletions

19
.yamllint Normal file
View File

@@ -0,0 +1,19 @@
---
extends: default
rules:
document-start: disable
line-length:
max: 200
allow-non-breakable-words: true
allow-non-breakable-inline-mappings: true
truthy:
allowed-values: ["true", "false"]
check-keys: false
comments:
min-spaces-from-content: 1
comments-indentation: disable
braces:
max-spaces-inside: 1
octal-values:
forbid-implicit-octal: true

View File

@@ -1,2 +1,5 @@
[defaults]
hash_behaviour = merge
interpreter_python = auto_silent
deprecation_warnings = False
host_key_checking = False

View File

@@ -16,12 +16,6 @@
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

View File

@@ -6,13 +6,6 @@
lookup('vars', bootstrap_var_key) | 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

View File

@@ -16,12 +16,6 @@
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

View File

@@ -22,6 +22,9 @@
- { src: proc, path: proc, fstype: proc }
- { src: sysfs, path: sys, fstype: sysfs }
- { src: /dev, path: dev, fstype: none, opts: bind }
- { src: devpts, path: dev/pts, fstype: devpts, opts: "gid=5,mode=620" }
loop_control:
label: "{{ item.path }}"
- name: Run OS-specific bootstrap process
vars:
@@ -39,3 +42,10 @@
void: void.yml
bootstrap_var_key: "{{ 'bootstrap_' + (os | replace('-lts', '') | replace('-', '_')) }}"
ansible.builtin.include_tasks: "{{ bootstrap_os_task_map[os] }}"
- name: Ensure chroot uses live environment DNS
ansible.builtin.file:
src: /run/NetworkManager/resolv.conf
dest: /mnt/etc/resolv.conf
state: link
force: true

View File

@@ -6,13 +6,6 @@
lookup('vars', bootstrap_var_key) | 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

View File

@@ -13,18 +13,6 @@
- bootstrap_result.rc != 0
- "'grub2-common' not in (bootstrap_result.stderr | default(''))"
- name: Write resolv.conf into chroot
ansible.builtin.copy:
dest: /mnt/etc/resolv.conf
mode: "0644"
content: |
{% for dns in system_cfg.network.dns.servers %}
nameserver {{ dns }}
{% endfor %}
{% if system_cfg.network.dns.search | default([]) | length > 0 %}
search {{ system_cfg.network.dns.search | join(' ') }}
{% endif %}
- name: Ensure chroot RHEL DVD directory exists
ansible.builtin.file:
path: /mnt/usr/local/install/redhat/dvd
@@ -37,7 +25,7 @@
path: /mnt/usr/local/install/redhat/dvd
fstype: none
opts: bind
state: mounted
state: ephemeral
- name: Rebuild RPM database inside chroot
ansible.builtin.command: "{{ chroot_command }} rpm --rebuilddb"

View File

@@ -16,12 +16,6 @@
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

View File

@@ -1,8 +1,11 @@
---
- name: Bootstrap Ubuntu System
vars:
bootstrap_ubuntu_release: >-
{{ 'plucky' if os == 'ubuntu' else 'noble' }}
# ubuntu = latest non-LTS, ubuntu-lts = latest LTS
bootstrap_ubuntu_release_map:
ubuntu: plucky
ubuntu-lts: noble
bootstrap_ubuntu_release: "{{ bootstrap_ubuntu_release_map[os] | default('noble') }}"
bootstrap_ubuntu_package_config: >-
{{
lookup('vars', bootstrap_var_key)
@@ -47,13 +50,6 @@
register: bootstrap_ubuntu_base_result
changed_when: bootstrap_ubuntu_base_result.rc == 0
- name: Ensure chroot has resolv.conf
ansible.builtin.copy:
src: /etc/resolv.conf
dest: /mnt/etc/resolv.conf
remote_src: true
mode: "0644"
- name: Enable universe repository
ansible.builtin.command: "{{ chroot_command }} sed -i '1s|$| universe|' /etc/apt/sources.list"
register: bootstrap_ubuntu_repo_result

View File

@@ -6,13 +6,6 @@
lookup('vars', bootstrap_var_key) | 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

View File

@@ -1,15 +1,18 @@
---
# Common conditional packages shared across distributions.
# Arch overrides nftables with iptables-nft; SSH package names vary per distro.
bootstrap_common_conditional:
- "{{ '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 '' }}"
- "{{ 'qemu-guest-agent' if hypervisor_type in ['libvirt', 'proxmox'] else '' }}"
- "{{ 'open-vm-tools' if hypervisor_type == 'vmware' else '' }}"
# Common feature-gated packages. Built as a clean list (no empty strings).
# Arch overrides nftables iptables-nft; SSH package names vary per distro.
bootstrap_common_conditional: >-
{{
(
(['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' and system_cfg.features.firewall.enabled | bool else [])
+ (['nftables'] if system_cfg.features.firewall.toolkit == 'nftables' and system_cfg.features.firewall.enabled | bool else [])
+ (['cryptsetup', 'tpm2-tools'] if system_cfg.luks.enabled | bool else [])
+ (['qemu-guest-agent'] if hypervisor_type in ['libvirt', 'proxmox'] else [])
+ (['open-vm-tools'] if hypervisor_type == 'vmware' else [])
)
}}
bootstrap_rhel_base: >-
{{
@@ -20,13 +23,15 @@ bootstrap_rhel_base: >-
+ bootstrap_common_conditional
}}
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_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: "{{ bootstrap_rhel_base + bootstrap_rhel_versioned }}"
@@ -34,14 +39,14 @@ bootstrap_almalinux: >-
{{
bootstrap_rhel_base
+ ['grub2', 'grub2-efi', 'dbus-daemon', 'lrzsz',
'nfsv4-client-utils', 'nc', 'ppp', 'zram-generator']
'nfsv4-client-utils', 'nc', 'ppp', 'python3', 'zram-generator']
}}
bootstrap_rocky: >-
{{
bootstrap_rhel_base
+ ['grub2', 'grub2-efi', 'nfsv4-client-utils', 'nc', 'ppp',
'telnet', 'util-linux-core', 'wget', 'zram-generator']
'python3', 'telnet', 'util-linux-core', 'wget', 'zram-generator']
}}
bootstrap_fedora: >-
@@ -51,28 +56,18 @@ bootstrap_fedora: >-
'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', 'vim-default-editor',
'python3', 'ripgrep', 'shim', 'tmux', 'vim-default-editor',
'wget', 'zoxide', 'zram-generator', 'zstd']
+ bootstrap_common_conditional
}}
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_base_common: >-
{{
['btrfs-progs', 'cron', 'gnupg', 'grub-efi', 'grub-efi-amd64-signed',
'grub2-common', 'locales', 'logrotate', 'lvm2', 'python3', 'xfsprogs']
+ (['cryptsetup-initramfs'] if system_cfg.luks.enabled | bool else [])
+ (['openssh-server'] if system_cfg.features.ssh.enabled | bool else [])
}}
bootstrap_debian_extra_common:
- apparmor-utils
@@ -101,14 +96,16 @@ bootstrap_debian_extra_common:
- 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 ['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_extra_versioned: >-
{{
['linux-image-amd64']
+ (['duf'] if (os_version | string) not in ['10', '11'] else [])
+ (['fastfetch'] if (os_version | string) in ['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 }}"
@@ -138,30 +135,36 @@ bootstrap_archlinux: >-
['base', 'btrfs-progs', 'cronie', 'dhcpcd', 'efibootmgr', 'fastfetch',
'fish', 'fzf', 'grub', 'htop', 'libpwquality', 'linux', 'logrotate',
'lrzsz', 'lsof', 'lvm2', 'ncdu', 'networkmanager', 'nfs-utils',
'ppp', 'prometheus-node-exporter', 'python-psycopg2', 'reflector',
'rsync', 'sudo', 'tldr', 'tmux', 'vim', 'wireguard-tools', 'zram-generator']
+ [('openssh' if system_cfg.features.ssh.enabled | bool else '')]
+ [('iptables-nft' if system_cfg.features.firewall.toolkit == 'nftables' else '')]
'ppp', 'python', 'reflector',
'rsync', 'sudo', 'tldr', 'tmux', 'vim', 'zram-generator']
+ (['openssh'] if system_cfg.features.ssh.enabled | bool else [])
+ (['iptables-nft'] if system_cfg.features.firewall.toolkit == 'nftables' and system_cfg.features.firewall.enabled | bool else [])
+ (bootstrap_common_conditional | reject('equalto', 'nftables') | list)
}}
bootstrap_alpine: >-
{{
['alpine-base', 'vim']
+ [('openssh' if system_cfg.features.ssh.enabled | bool else '')]
['alpine-base', 'btrfs-progs', 'chrony', 'curl', 'e2fsprogs',
'linux-lts', 'logrotate', 'lvm2', 'python3', 'rsync', 'sudo',
'util-linux', 'vim', 'xfsprogs']
+ (['openssh'] if system_cfg.features.ssh.enabled | bool else [])
+ bootstrap_common_conditional
}}
bootstrap_opensuse: >-
{{
['vim']
+ [('openssh' if system_cfg.features.ssh.enabled | bool else '')]
['btrfs-progs', 'chrony', 'curl', 'e2fsprogs',
'glibc-locale', 'kernel-default', 'logrotate', 'lvm2', 'NetworkManager',
'python3', 'rsync', 'sudo', 'vim', 'xfsprogs']
+ (['openssh'] if system_cfg.features.ssh.enabled | bool else [])
+ bootstrap_common_conditional
}}
bootstrap_void: >-
{{
['vim']
+ [('openssh' if system_cfg.features.ssh.enabled | bool else '')]
['btrfs-progs', 'chrony', 'curl', 'dhcpcd', 'e2fsprogs',
'logrotate', 'lvm2', 'python3', 'rsync', 'sudo',
'vim', 'xfsprogs']
+ (['openssh'] if system_cfg.features.ssh.enabled | bool else [])
+ bootstrap_common_conditional
}}

View File

@@ -1,21 +1,17 @@
---
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")
}}
# Platform-specific binary names for CIS permission targets
cis_fusermount_binary: "{{ 'fusermount3' if is_rhel | default(false) | bool else 'fusermount' }}"
cis_write_binary: "{{ 'write' if is_rhel | default(false) | bool else 'wall' }}"
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" }
- { path: "/mnt/usr/bin/{{ cis_fusermount_binary }}", mode: "0755" }
- { path: "/mnt/usr/bin/{{ cis_write_binary }}", mode: "0755" }

View File

@@ -5,11 +5,19 @@
regexp: "^(\\s*)umask\\s+\\d+"
line: "umask 027"
# Non-RHEL/non-Debian distros: loop evaluates to [] (intentional skip)
- 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
loop: >-
{{
['/mnt/etc/pam.d/system-auth', '/mnt/etc/pam.d/password-auth']
if is_rhel | bool
else (
['/mnt/etc/pam.d/common-auth', '/mnt/etc/pam.d/common-password']
if is_debian | bool
else []
)
}}

View File

@@ -1,6 +1,6 @@
---
- name: Configure System Cryptography Policy
when: os == "rhel" or os in ["almalinux", "rocky"]
when: os in (os_family_rhel | difference(['fedora']))
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"
@@ -9,4 +9,4 @@
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
changed_when: "'Created symlink' in cis_mask_services_result.stderr"

View File

@@ -10,6 +10,7 @@
install hfs /bin/false
install hfsplus /bin/false
install cramfs /bin/false
# Note: disabling squashfs breaks snap (Ubuntu). Remove for snap-dependent hosts.
install squashfs /bin/false
install udf /bin/false
install usb-storage /bin/false

View File

@@ -3,6 +3,8 @@
ansible.builtin.stat:
path: "{{ item.path }}"
loop: "{{ cis_permission_targets }}"
loop_control:
label: "{{ item.path }}"
register: cis_permission_stats
changed_when: false
@@ -13,4 +15,6 @@
group: "{{ item.item.group | default(omit) }}"
mode: "{{ item.item.mode }}"
loop: "{{ cis_permission_stats.results }}"
loop_control:
label: "{{ item.item.path }}"
when: item.stat.exists

View File

@@ -11,8 +11,8 @@
- { 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/{{ "bashrc" if is_rhel else "bash.bashrc" }}', content: export TMOUT=900 }
- { path: '/mnt/{{ "usr/lib/systemd/journald.conf" if is_rhel | bool 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: >-
@@ -44,3 +44,5 @@
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" }
loop_control:
label: "{{ item.content }}"

View File

@@ -21,7 +21,7 @@
- { option: GSSAPIAuthentication, value: "no" }
- { option: AllowAgentForwarding, value: "no" }
- { option: AllowTcpForwarding, value: "no" }
- { option: ChallengeResponseAuthentication, value: "no" }
- { option: KbdInteractiveAuthentication, value: "no" }
- { option: GatewayPorts, value: "no" }
- { option: X11Forwarding, value: "no" }
- { option: PermitUserEnvironment, value: "no" }
@@ -29,17 +29,34 @@
- { option: ClientAliveCountMax, value: "1" }
- { option: PermitTunnel, value: "no" }
- { option: Banner, value: /etc/issue.net }
loop_control:
label: "{{ item.option }}"
- name: Detect target OpenSSH version
ansible.builtin.shell: >-
set -o pipefail && {{ chroot_command }} ssh -V 2>&1 | grep -oP 'OpenSSH_\K[0-9]+\.[0-9]+'
args:
executable: /bin/bash
register: cis_sshd_openssh_version
changed_when: false
failed_when: false
- name: Append CIS specific configurations to sshd_config
vars:
cis_sshd_has_mlkem: "{{ (cis_sshd_openssh_version.stdout | default('0.0') is version('9.9', '>=')) }}"
cis_sshd_kex: >-
{{
(['mlkem768x25519-sha256'] if cis_sshd_has_mlkem | bool else [])
+ ['curve25519-sha256@libssh.org', 'ecdh-sha2-nistp521', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp256']
}}
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
KexAlgorithms {{ cis_sshd_kex | join(',') }}
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
###########################

View File

@@ -5,9 +5,12 @@
mode: "0644"
content: |
## CIS Sysctl configurations
fs.suid_dumpable=0
kernel.dmesg_restrict=1
kernel.yama.ptrace_scope=1
kernel.randomize_va_space=2
# Network
# Disable forwarding; override in inventory for routers/containers
net.ipv4.ip_forward=0
net.ipv4.tcp_syncookies=1
net.ipv4.icmp_echo_ignore_broadcasts=1
@@ -24,6 +27,7 @@
net.ipv4.conf.default.send_redirects=0
net.ipv4.conf.default.accept_redirects=0
net.ipv6.conf.all.accept_redirects=0
# Disable IPv6; override in inventory if IPv6 is needed
net.ipv6.conf.all.disable_ipv6=1
net.ipv6.conf.default.accept_redirects=0
net.ipv6.conf.default.disable_ipv6=1

21
roles/cis/vars/main.yml Normal file
View File

@@ -0,0 +1,21 @@
---
# OS-specific binary names for CIS permission targets.
# fusermount3 is the modern name; older distros still use fusermount.
cis_fusermount_binary: >-
{{
'fusermount3'
if (
os in ['archlinux', 'fedora', 'rocky', 'rhel']
or (os == 'debian' and (os_version | string) not in ['10', '11'])
or (os == 'almalinux')
)
else 'fusermount'
}}
# write.ul is the Debian 11 name; all others use write.
cis_write_binary: >-
{{
'write.ul'
if (os == 'debian' and (os_version | string) == '11')
else 'write'
}}

View File

@@ -1,4 +1,10 @@
---
# Post-reboot verification
cleanup_verify_boot: true
cleanup_boot_timeout: 300
cleanup_remove_on_failure: true
# Libvirt paths
cleanup_libvirt_image_dir: >-
{{
system_cfg.path

View File

@@ -99,6 +99,7 @@
name: "{{ hostname }}"
state: running
# delegate_to inventory_hostname: overrides play-level localhost to run wait_for_connection against the VM
- name: Wait for VM to boot up
delegate_to: "{{ inventory_hostname }}"
ansible.builtin.wait_for_connection:

View File

@@ -3,25 +3,32 @@
when: hypervisor_type == "proxmox"
delegate_to: localhost
become: false
block:
- name: Cleanup Setup Disks
module_defaults:
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 }}"
block:
- name: Cleanup Setup Disks
community.proxmox.proxmox_disk:
name: "{{ hostname }}"
vmid: "{{ system_cfg.id }}"
disk: "{{ item }}"
state: absent
loop: >-
{{
['ide0', 'ide2']
+ (['ide1'] if not (os == 'rhel' and system_cfg.features.rhel_repo.source == 'iso') else [])
}}
failed_when: false
no_log: true
- name: Start the VM
community.proxmox.proxmox_kvm:
vmid: "{{ system_cfg.id }}"
state: restarted

View File

@@ -6,16 +6,7 @@
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
ansible.builtin.include_tasks: "{{ hypervisor_type }}.yml"
- name: Determine post-reboot connectivity
ansible.builtin.set_fact:
@@ -34,25 +25,27 @@
)
) | bool
}}
changed_when: false
- name: Check VM accessibility after reboot
when:
- cleanup_verify_boot | bool
- 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
timeout: "{{ cleanup_boot_timeout }}"
register: cleanup_vm_connection_check
failed_when: false
changed_when: false
- name: VM failed to boot - initiate cleanup
when:
- cleanup_remove_on_failure | bool
- cleanup_vm_connection_check is defined
- cleanup_vm_connection_check.failed | bool
- virtualization_vm_created_in_run | default(false) | bool
block:
- name: VM boot failure detected - removing VM
ansible.builtin.debug:
@@ -61,32 +54,23 @@
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
- name: Remove failed libvirt VM
when: hypervisor_type == "libvirt"
delegate_to: localhost
become: false
block:
- name: Destroy libvirt VM
community.libvirt.virt:
name: "{{ hostname }}"
state: destroyed
failed_when: false
- name: Undefine VM for libvirt
when:
- hypervisor_type == "libvirt"
- virtualization_vm_created_in_run | default(false) | bool
delegate_to: localhost
become: false
- name: Undefine libvirt VM
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
- name: Remove libvirt VM disks
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
@@ -94,83 +78,66 @@
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
- name: Remove libvirt cloud-init disk
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
- name: Remove failed Proxmox VM
when: hypervisor_type == "proxmox"
delegate_to: localhost
become: false
module_defaults:
community.proxmox.proxmox_kvm:
api_host: "{{ hypervisor_cfg.url }}"
api_user: "{{ hypervisor_cfg.username }}"
api_password: "{{ hypervisor_cfg.password }}"
node: "{{ hypervisor_cfg.host }}"
no_log: true
block:
- name: Stop Proxmox VM
community.proxmox.proxmox_kvm:
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
- name: Delete Proxmox VM
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
- name: Remove failed VMware VM
when: hypervisor_type == "vmware"
delegate_to: localhost
become: false
module_defaults:
community.vmware.vmware_guest:
hostname: "{{ hypervisor_cfg.url }}"
username: "{{ hypervisor_cfg.username }}"
password: "{{ hypervisor_cfg.password }}"
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
no_log: true
block:
- name: Power off VMware VM
community.vmware.vmware_guest:
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
- name: Delete VMware VM
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
- name: Remove failed Xen VM
when: hypervisor_type == "xen"
delegate_to: localhost
become: false
block:
- name: Destroy Xen VM if running
ansible.builtin.command:
argv:
- xl
@@ -180,12 +147,7 @@
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
- name: Remove Xen VM disks
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
@@ -194,11 +156,6 @@
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

View File

@@ -3,36 +3,55 @@
when: hypervisor_type == "vmware"
delegate_to: localhost
become: false
block:
- name: Remove CD-ROM from VM in vCenter
module_defaults:
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
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 }}"
no_log: true
block:
- name: Remove CD-ROM from VM in vCenter
community.vmware.vmware_guest:
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,
'state': 'absent'
}
]
if (rhel_iso is defined and rhel_iso | length > 0
and not (os == 'rhel' and system_cfg.features.rhel_repo.source == 'iso'))
else []
)
}}
failed_when: false
- name: Start VM in vCenter
vmware.vmware.vm_powerstate:
name: "{{ hostname }}"
state: powered-on

View File

@@ -3,6 +3,8 @@
when: hypervisor_type == "xen"
delegate_to: localhost
become: false
vars:
xen_installer_media_enabled: "{{ xen_installer_media_enabled | default(false) }}"
block:
- name: Ensure Xen disk definitions exist
when: virtualization_xen_disks is not defined

View File

@@ -23,6 +23,22 @@
- /mnt/etc/motd.d/insights-client
failed_when: false
- 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: Configure sudo banner
when: system_cfg.features.banner.sudo | bool
block:

View File

@@ -21,19 +21,19 @@
- name: Check existing EFI boot entries
ansible.builtin.command: efibootmgr
register: _efi_entries
register: configuration_efi_entries
changed_when: false
- name: Ensure EFI boot entry exists
when: ('* ' + _efi_vendor) not in _efi_entries.stdout
when: ('* ' + _efi_vendor) not in configuration_efi_entries.stdout
ansible.builtin.command: >-
efibootmgr -c
-L '{{ _efi_vendor }}'
-d '{{ install_drive }}'
-p 1
-l '\EFI\{{ _efi_vendor }}\{{ _efi_loader }}'
register: _efi_entry_result
changed_when: _efi_entry_result.rc == 0
register: configuration_efi_entry_result
changed_when: configuration_efi_entry_result.rc == 0
- name: Ensure lvm2 for non btrfs filesystems
when: os == "archlinux" and system_cfg.filesystem != "btrfs"

View File

@@ -59,6 +59,14 @@
when: configuration_luks_auto_method == 'keyfile'
ansible.builtin.include_tasks: encryption/keyfile.yml
- name: Record final LUKS auto-decrypt method
ansible.builtin.set_fact:
configuration_luks_final_method: "{{ configuration_luks_auto_method }}"
- name: Report LUKS auto-decrypt configuration
ansible.builtin.debug:
msg: "LUKS auto-decrypt method: {{ configuration_luks_final_method }}"
- name: Build LUKS parameters
vars:
luks_keyfile_in_use: "{{ configuration_luks_auto_method == 'keyfile' }}"
@@ -142,7 +150,7 @@
regexp: "^HOOKS="
line: >-
HOOKS=(base systemd autodetect microcode modconf kms keyboard sd-vconsole
block sd-encrypt lvm2 filesystems fsck)
block sd-encrypt{{ ' lvm2' if system_cfg.filesystem != 'btrfs' else '' }} filesystems fsck)
- name: Read mkinitcpio configuration
when: os == 'archlinux'
@@ -246,7 +254,7 @@
mode: "0644"
content: "{{ configuration_kernel_cmdline_new }}\n"
- name: Find BLS entries
- name: Find BLS entries for encryption kernel cmdline
when: is_rhel | bool
ansible.builtin.find:
paths: /mnt/boot/loader/entries

View File

@@ -104,6 +104,13 @@
failed_when: false
no_log: true
- name: Warn about keyfile enrollment failure
when: (configuration_luks_keyfile_unlock_test_after.rc | default(1)) != 0
ansible.builtin.debug:
msg: >-
LUKS keyfile enrollment failed — falling back to manual unlock at boot.
The system will prompt for the LUKS passphrase during startup.
- 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:

View File

@@ -1,6 +1,7 @@
---
- name: Enroll TPM2 for LUKS
block:
# Tempfile in chroot /tmp — accessible by both chroot and host commands
- name: Create temporary passphrase file for TPM2 enrollment
ansible.builtin.tempfile:
path: /mnt/tmp
@@ -78,6 +79,12 @@
chroot stderr={{ configuration_luks_tpm2_enroll_chroot.stderr | default('') }},
host stderr={{ configuration_luks_tpm2_enroll_host.stderr | default('') }}
rescue:
- name: Warn about TPM2 enrollment failure
ansible.builtin.debug:
msg: >-
TPM2 enrollment failed — falling back to keyfile auto-decrypt.
The system will use a keyfile instead of TPM2 for automatic LUKS unlock.
- name: Fallback to keyfile auto-decrypt
ansible.builtin.set_fact:
configuration_luks_auto_method: keyfile
@@ -87,4 +94,3 @@
ansible.builtin.file:
path: "{{ configuration_luks_tpm2_passphrase_tempfile.path }}"
state: absent
changed_when: false

View File

@@ -9,7 +9,7 @@
set smartindent
set mouse=a
insertafter: EOF
marker: ""
marker: "# {mark} CUSTOM VIM CONFIG"
failed_when: false
- name: Add memory tuning parameters
@@ -22,7 +22,7 @@
vm.dirty_background_ratio=1
vm.dirty_ratio=10
vm.page-cluster=10
marker: ""
marker: "# {mark} MEMORY TUNING"
mode: "0644"
- name: Create zram config
@@ -45,28 +45,3 @@
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

@@ -23,8 +23,19 @@
regexp: "(xfs.*?)(attr2)"
replace: "\\1allocsize=64m"
- name: Remove RHEL ISO fstab entry when not using local repo
when:
- os == "rhel"
- system_cfg.features.rhel_repo.source != "iso"
ansible.builtin.lineinfile:
path: /mnt/etc/fstab
regexp: "^.*\\/dvd.*$"
state: absent
- name: Replace ISO UUID entry with /dev/sr0 in fstab
when: os == "rhel"
when:
- os == "rhel"
- system_cfg.features.rhel_repo.source == "iso"
vars:
configuration_fstab_dvd_line: >-
{{
@@ -39,7 +50,10 @@
state: present
- name: Write image from RHEL ISO to the target machine
when: os == "rhel" and hypervisor_type == 'vmware'
when:
- os == "rhel"
- hypervisor_type == "vmware"
- system_cfg.features.rhel_repo.source == "iso"
ansible.builtin.command:
argv:
- dd
@@ -63,3 +77,4 @@
- { regexp: "^tmpfs\\s+/dev/shm\\s+", line: "tmpfs /dev/shm tmpfs defaults,nosuid,nodev,noexec 0 0" }
loop_control:
loop_var: fstab_entry
label: "{{ fstab_entry.regexp }}"

View File

@@ -10,6 +10,8 @@
line: GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3"
- regexp: ^GRUB_TIMEOUT=
line: GRUB_TIMEOUT=1
loop_control:
label: "{{ item.line }}"
- name: Ensure grub defaults file exists for RHEL-based systems
when: is_rhel | bool
@@ -95,7 +97,7 @@
mode: "0644"
content: "{{ configuration_kernel_cmdline_base }}\n"
- name: Find BLS entries
- name: Find BLS entries for GRUB configuration
ansible.builtin.find:
paths: /mnt/boot/loader/entries
patterns: "*.conf"

View File

@@ -21,6 +21,8 @@
line: "{{ item.line }}"
loop:
- { regex: "{{ system_cfg.locale }} UTF-8", line: "{{ system_cfg.locale }} UTF-8" }
loop_control:
label: "{{ item.line }}"
- name: Generate locales
when: not is_rhel | bool
@@ -45,7 +47,7 @@
- name: Set hostname
ansible.builtin.copy:
content: "{{ configuration_hostname_fqdn }}"
content: "{{ configuration_hostname_fqdn.split('.')[0] }}"
dest: /mnt/etc/hostname
mode: "0644"

View File

@@ -1,19 +1,23 @@
---
- name: Include configuration tasks
ansible.builtin.include_tasks: "{{ configuration_task }}"
when: configuration_task.when | default(true)
ansible.builtin.include_tasks: "{{ configuration_task.file }}"
loop:
- banner.yml
- fstab.yml
- locales.yml
- ssh.yml
- services.yml
- grub.yml
- encryption.yml
- bootloader.yml
- extras.yml
- network.yml
- users.yml
- sudo.yml
- selinux.yml
- file: banner.yml
- file: fstab.yml
- file: locales.yml
- file: ssh.yml
- file: services.yml
- file: grub.yml
- file: encryption.yml
when: "{{ system_cfg.luks.enabled | bool }}"
- file: bootloader.yml
- file: extras.yml
- file: network.yml
- file: users.yml
- file: sudo.yml
- file: selinux.yml
when: "{{ is_rhel | bool }}"
loop_control:
loop_var: configuration_task
label: "{{ configuration_task.file }}"

View File

@@ -29,88 +29,9 @@
- configuration_detected_interfaces | length > 0
fail_msg: Failed to detect any network interfaces.
- name: Configure NetworkManager profiles
when: os not in ["alpine", "void"]
block:
- name: Copy NetworkManager keyfile per interface
- name: Configure networking
vars:
configuration_iface: "{{ item }}"
configuration_iface_name: "{{ configuration_detected_interfaces[idx] | default('eth' ~ idx) }}"
configuration_net_uuid: "{{ ('LAN-' ~ idx ~ '-' ~ hostname) | ansible.builtin.to_uuid }}"
ansible.builtin.template:
src: network.j2
dest: "/mnt/etc/NetworkManager/system-connections/LAN-{{ idx }}.nmconnection"
mode: "0600"
loop: "{{ system_cfg.network.interfaces }}"
loop_control:
index_var: idx
label: "LAN-{{ idx }}"
- name: Fix Ubuntu unmanaged devices
when: os 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 == "alpine"
vars:
configuration_dns_list: "{{ system_cfg.network.dns.servers | default([]) }}"
block:
- name: Write Alpine network interfaces
ansible.builtin.copy:
dest: /mnt/etc/network/interfaces
mode: "0644"
content: |
auto lo
iface lo inet loopback
{% for iface in system_cfg.network.interfaces %}
{% set iface_name = configuration_detected_interfaces[loop.index0] | default(iface.name | default('eth' ~ loop.index0)) %}
{% set has_static = (iface.ip | default('') | string | length) > 0 %}
auto {{ iface_name }}
iface {{ iface_name }} inet {{ 'static' if has_static else 'dhcp' }}
{% if has_static %}
address {{ iface.ip }}/{{ iface.prefix }}
{% if iface.gateway | default('') | string | length %}
gateway {{ iface.gateway }}
{% endif %}
{% endif %}
{% endfor %}
- 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 == "void"
vars:
configuration_dns_list: "{{ system_cfg.network.dns.servers | default([]) }}"
block:
- name: Write dhcpcd configuration
ansible.builtin.copy:
dest: /mnt/etc/dhcpcd.conf
mode: "0644"
content: |
{% for iface in system_cfg.network.interfaces %}
{% set iface_name = configuration_detected_interfaces[loop.index0] | default(iface.name | default('eth' ~ loop.index0)) %}
{% set has_static = (iface.ip | default('') | string | length) > 0 %}
{% if has_static %}
interface {{ iface_name }}
static ip_address={{ iface.ip }}/{{ iface.prefix }}
{% if iface.gateway | default('') | string | length %}
static routers={{ iface.gateway }}
{% endif %}
{% if loop.index0 == 0 and configuration_dns_list | length > 0 %}
static domain_name_servers={{ configuration_dns_list | join(' ') }}
{% endif %}
{% endif %}
{% endfor %}
configuration_network_task_map:
alpine: network_alpine.yml
void: network_void.yml
ansible.builtin.include_tasks: "{{ configuration_network_task_map[os] | default('network_nm.yml') }}"

View File

@@ -0,0 +1,37 @@
---
- name: Write Alpine network interfaces
vars:
configuration_dns_list: "{{ system_cfg.network.dns.servers | default([]) }}"
ansible.builtin.copy:
dest: /mnt/etc/network/interfaces
mode: "0644"
content: |
auto lo
iface lo inet loopback
{% for iface in system_cfg.network.interfaces %}
{% set inv_name = iface.name | default('') | string %}
{% set det_name = configuration_detected_interfaces[loop.index0] | default('eth' ~ loop.index0) %}
{% set iface_name = inv_name if inv_name | length > 0 else det_name %}
{% set has_static = (iface.ip | default('') | string | length) > 0 %}
auto {{ iface_name }}
iface {{ iface_name }} inet {{ 'static' if has_static else 'dhcp' }}
{% if has_static %}
address {{ iface.ip }}/{{ iface.prefix }}
{% if iface.gateway | default('') | string | length %}
gateway {{ iface.gateway }}
{% endif %}
{% endif %}
{% endfor %}
- name: Set Alpine DNS resolvers
vars:
configuration_dns_list: "{{ system_cfg.network.dns.servers | default([]) }}"
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 %}

View File

@@ -0,0 +1,26 @@
---
- name: Copy NetworkManager keyfile per interface
vars:
configuration_iface: "{{ item }}"
configuration_iface_name: >-
{{
item.name
if (item.name | default('') | string | length) > 0
else (configuration_detected_interfaces[idx] | default('eth' ~ idx))
}}
configuration_net_uuid: "{{ ('LAN-' ~ idx ~ '-' ~ hostname) | ansible.builtin.to_uuid }}"
ansible.builtin.template:
src: network.j2
dest: "/mnt/etc/NetworkManager/system-connections/LAN-{{ idx }}.nmconnection"
mode: "0600"
loop: "{{ system_cfg.network.interfaces }}"
loop_control:
index_var: idx
label: "LAN-{{ idx }}"
- name: Fix Ubuntu unmanaged devices
when: os in ["ubuntu", "ubuntu-lts"]
ansible.builtin.file:
path: /mnt/etc/NetworkManager/conf.d/10-globally-managed-devices.conf
state: touch
mode: "0644"

View File

@@ -0,0 +1,25 @@
---
- name: Write dhcpcd configuration
vars:
configuration_dns_list: "{{ system_cfg.network.dns.servers | default([]) }}"
ansible.builtin.copy:
dest: /mnt/etc/dhcpcd.conf
mode: "0644"
content: |
{% for iface in system_cfg.network.interfaces %}
{% set inv_name = iface.name | default('') | string %}
{% set det_name = configuration_detected_interfaces[loop.index0] | default('eth' ~ loop.index0) %}
{% set iface_name = inv_name if inv_name | length > 0 else det_name %}
{% set has_static = (iface.ip | default('') | string | length) > 0 %}
{% if has_static %}
interface {{ iface_name }}
static ip_address={{ iface.ip }}/{{ iface.prefix }}
{% if iface.gateway | default('') | string | length %}
static routers={{ iface.gateway }}
{% endif %}
{% if loop.index0 == 0 and configuration_dns_list | length > 0 %}
static domain_name_servers={{ configuration_dns_list | join(' ') }}
{% endif %}
{% endif %}
{% endfor %}

View File

@@ -1,20 +1,19 @@
---
- name: Enable Systemd Services
- name: Enable systemd services
when: os 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 '' }}
vars:
configuration_systemd_services: >-
{{
(' ssh' if is_debian | bool else ' sshd')
if system_cfg.features.ssh.enabled | bool else ''
['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-timesyncd'] if os == 'archlinux' else [])
}}
{{
'logrotate systemd-resolved systemd-timesyncd systemd-networkd'
if os == 'archlinux' else ''
}}
register: configuration_enable_services_result
changed_when: configuration_enable_services_result.rc == 0
ansible.builtin.command: "{{ chroot_command }} systemctl enable {{ item }}"
loop: "{{ configuration_systemd_services }}"
register: configuration_enable_service_result
changed_when: configuration_enable_service_result.rc == 0
- name: Enable OpenRC services
when: os == 'alpine'
@@ -37,7 +36,6 @@
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:
@@ -45,6 +43,8 @@
dest: "/mnt/etc/runlevels/default/{{ item.item }}"
state: link
loop: "{{ configuration_openrc_service_stats.results }}"
loop_control:
label: "{{ item.item }}"
when: item.stat.exists
- name: Enable runit services
@@ -68,7 +68,6 @@
path: "/mnt/etc/sv/{{ item }}"
loop: "{{ configuration_runit_services }}"
register: configuration_runit_service_stats
changed_when: false
- name: Enable runit services
ansible.builtin.file:
@@ -76,4 +75,6 @@
dest: "/mnt/var/service/{{ item.item }}"
state: link
loop: "{{ configuration_runit_service_stats.results }}"
loop_control:
label: "{{ item.item }}"
when: item.stat.exists

View File

@@ -1,22 +1,30 @@
---
- name: Set root password
vars:
configuration_root_cmd: >-
{{ chroot_command }} /usr/sbin/usermod --password
'{{ system_cfg.root.password | password_hash('sha512') }}' root --shell /bin/bash
ansible.builtin.command: "{{ configuration_root_cmd }}"
ansible.builtin.shell: >-
set -o pipefail &&
echo 'root:{{ system_cfg.root.password | password_hash("sha512") }}' | {{ chroot_command }} chpasswd -e
args:
executable: /bin/bash
register: configuration_root_result
changed_when: configuration_root_result.rc == 0
no_log: true
- name: Set root shell
ansible.builtin.command: >-
{{ chroot_command }} /usr/sbin/usermod --shell {{ system_cfg.root.shell | default('/bin/bash') }} root
register: configuration_root_shell_result
changed_when: configuration_root_shell_result.rc == 0
- name: Create user accounts
vars:
configuration_user_group: >-
{{ "sudo" if is_debian | bool else "wheel" }}
# UID starts at 1000; safe for fresh installs only
configuration_useradd_cmd: >-
{{ chroot_command }} /usr/sbin/useradd --create-home --user-group
--uid {{ 1000 + ansible_loop.index0 }}
--groups {{ configuration_user_group }} {{ item.name }}
--password {{ item.password | password_hash('sha512') }} --shell /bin/bash
--password {{ item.password | password_hash('sha512') }} --shell {{ item.shell | default('/bin/bash') }}
ansible.builtin.command: "{{ configuration_useradd_cmd }}"
loop: "{{ system_cfg.users }}"
loop_control:
@@ -24,6 +32,7 @@
label: "{{ item.name }}"
register: configuration_user_result
changed_when: configuration_user_result.rc == 0
no_log: true
- name: Ensure .ssh directory exists
when: item['keys'] | default([]) | length > 0

View File

@@ -15,11 +15,11 @@ method=manual
method=auto
{% endif %}
{% if idx | int == 0 and dns_list %}
dns={{ dns_list | join(';') }}
dns={{ dns_list | join(';') }};
ignore-auto-dns=true
{% endif %}
{% if idx | int == 0 and search_list %}
dns-search={{ search_list | join(';') }}
dns-search={{ search_list | join(';') }};
{% endif %}
[ipv6]

View File

@@ -132,6 +132,8 @@
replace: "PermitEmptyPasswords yes"
- regexp: "^#?PermitRootLogin.*"
replace: "PermitRootLogin yes"
loop_control:
label: "{{ item.replace }}"
- name: Reload SSH service to apply changes
ansible.builtin.service:
@@ -175,6 +177,8 @@
- { name: debootstrap, os: [debian, ubuntu, ubuntu-lts] }
- { name: debian-archive-keyring, os: [debian] }
- { name: ubuntu-keyring, os: [ubuntu, ubuntu-lts] }
loop_control:
label: "{{ item.name }}"
retries: 4
delay: 15
@@ -187,15 +191,29 @@
state: directory
mode: "0755"
- name: Detect RHEL ISO device
ansible.builtin.command: lsblk -rno NAME,TYPE
register: environment_lsblk_result
changed_when: false
- name: Select RHEL ISO device
vars:
_rom_devices: >-
{{
environment_lsblk_result.stdout_lines
| map('split', ' ')
| selectattr('1', 'equalto', 'rom')
| map('first')
| map('regex_replace', '^', '/dev/')
| list
}}
ansible.builtin.set_fact:
environment_rhel_iso_device: >-
{{
'/dev/sr2'
if hypervisor_type == 'libvirt'
else '/dev/sr1'
_rom_devices[-1]
if _rom_devices | length > 1
else (_rom_devices[0] | default('/dev/sr1'))
}}
changed_when: false
- name: Mount RHEL ISO
ansible.posix.mount:
@@ -205,6 +223,10 @@
opts: "ro,loop"
state: mounted
# Security note: RPM Sequoia signature policy is relaxed to allow
# bootstrapping RHEL-family distros from the Arch ISO, where the
# host rpm/dnf does not trust target distro GPG keys. Package
# integrity is verified by the target system's own rpm after reboot.
- name: Relax RPM Sequoia signature policy for RHEL bootstrap
when: is_rhel | bool
ansible.builtin.copy:

View File

@@ -1,4 +1,27 @@
---
# OS family lists — single source of truth for platform detection and validation
os_family_rhel:
- almalinux
- fedora
- rhel
- rocky
os_family_debian:
- debian
- ubuntu
- ubuntu-lts
os_supported:
- almalinux
- alpine
- archlinux
- debian
- fedora
- opensuse
- rhel
- rocky
- ubuntu
- ubuntu-lts
- void
# User input. Normalized into hypervisor_cfg + hypervisor_type.
hypervisor:
type: "none"
@@ -83,6 +106,9 @@ system_defaults:
banner:
motd: false
sudo: true
rhel_repo:
source: "iso" # iso|satellite|none — how RHEL systems get packages post-install
url: "" # Satellite/custom repo URL when source=satellite
chroot:
tool: "arch-chroot" # arch-chroot|chroot|systemd-nspawn

View File

@@ -0,0 +1,100 @@
---
- 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: []
- 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) }}"
- 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 }}"

View File

@@ -0,0 +1,143 @@
---
- 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 | string)
if (system_raw.network.prefix | default('') | string | length) > 0
else ''
}}
gateway: "{{ system_raw.network.gateway | default('') | string }}"
dns:
servers: "{{ system_raw.network.dns.servers | default([]) }}"
search: "{{ system_raw.network.dns.search | default([]) }}"
interfaces: >-
{{
system_raw.network.interfaces
if (system_raw.network.interfaces | default([]) | length > 0)
else (
[{
'name': '',
'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 | string)
if (system_raw.network.prefix | default('') | string | length) > 0
else ''
),
'gateway': system_raw.network.gateway | default('') | string
}]
if (system_raw.network.bridge | default('') | string | length > 0)
else []
)
}}
timezone: "{{ system_raw.timezone | default('Europe/Vienna') | string }}"
locale: "{{ system_raw.locale | default('en_US.UTF-8') | string }}"
keymap: "{{ system_raw.keymap | default('us') | string }}"
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([]) }}"
users: "{{ system_raw.users | default([]) }}"
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 }}"
rhel_repo:
source: "{{ system_raw.features.rhel_repo.source | default('iso') | string | lower }}"
url: "{{ system_raw.features.rhel_repo.url | default('') | string }}"
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 }}"
no_log: true
- name: Populate primary network fields from first interface
when:
- system_cfg.network.interfaces | length > 0
- system_cfg.network.bridge | default('') | string | length == 0
vars:
_primary: "{{ system_cfg.network.interfaces[0] }}"
ansible.builtin.set_fact:
system_cfg: >-
{{
system_cfg | combine({
'network': system_cfg.network | combine({
'bridge': _primary.bridge | default(''),
'vlan': _primary.vlan | default(''),
'ip': _primary.ip | default(''),
'prefix': _primary.prefix | default(''),
'gateway': _primary.gateway | default('')
})
}, recursive=True)
}}

View File

@@ -0,0 +1,57 @@
---
- name: Ensure system input is a dictionary
ansible.builtin.set_fact:
system: "{{ system | default({}) }}"
- name: Validate system input types
ansible.builtin.assert:
that:
- system is mapping
- system.network is not defined or system.network is mapping
- system.users is not defined or (system.users is iterable and system.users is not string and system.users is not 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, root, luks, features) must be dictionaries; system.users must be a list."
quiet: true
- name: Validate DNS lists (not strings)
when: system.network is defined and system.network.dns is defined
ansible.builtin.assert:
that:
- system.network.dns.servers is not defined or (system.network.dns.servers is iterable and system.network.dns.servers is not string)
- system.network.dns.search is not defined or (system.network.dns.search is iterable and system.network.dns.search is not string)
fail_msg: "system.network.dns.servers and system.network.dns.search must be lists, not strings."
quiet: true
- name: Validate system.users entries
when: system.users is defined and system.users | length > 0
ansible.builtin.assert:
that:
- item is mapping
- item.name is defined and (item.name | string | length) > 0
- item['keys'] is not defined or (item['keys'] is iterable and item['keys'] is not string)
fail_msg: "Each system.users[] entry must be a dict with 'name'; 'keys' must be a list."
quiet: true
loop: "{{ system.users }}"
loop_control:
label: "{{ item.name | default('(unnamed)') }}"
- 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

View File

@@ -18,3 +18,4 @@
ansible.builtin.set_fact:
hypervisor_cfg: "{{ merged }}"
hypervisor_type: "{{ merged.type | string | lower }}"
no_log: true

View File

@@ -14,8 +14,8 @@
- name: Set OS family flags
ansible.builtin.set_fact:
is_rhel: "{{ os in ['almalinux', 'fedora', 'rhel', 'rocky'] }}"
is_debian: "{{ os in ['debian', 'ubuntu', 'ubuntu-lts'] }}"
is_rhel: "{{ os in os_family_rhel }}"
is_debian: "{{ os in os_family_debian }}"
- name: Normalize OS version for keying
when:
@@ -49,6 +49,7 @@
ansible_password: "{{ system_cfg.users[0].password }}"
ansible_become_password: "{{ system_cfg.users[0].password }}"
ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
no_log: true
- name: Set connection for VMware
when: hypervisor_type == "vmware"

View File

@@ -1,297 +1,9 @@
---
- name: Ensure system input is a dictionary
ansible.builtin.set_fact:
system: "{{ system | default({}) }}"
- name: Validate raw system input types
ansible.builtin.include_tasks: _validate_input.yml
- name: Validate system input types
ansible.builtin.assert:
that:
- system is mapping
- system.network is not defined or system.network is mapping
- system.users is not defined or (system.users is iterable and system.users is not string and system.users is not 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, root, luks, features) must be dictionaries; system.users must be a list."
quiet: true
- name: Validate DNS lists (not strings)
when: system.network is defined and system.network.dns is defined
ansible.builtin.assert:
that:
- system.network.dns.servers is not defined or (system.network.dns.servers is iterable and system.network.dns.servers is not string)
- system.network.dns.search is not defined or (system.network.dns.search is iterable and system.network.dns.search is not string)
fail_msg: "system.network.dns.servers and system.network.dns.search must be lists, not strings."
quiet: true
- name: Validate system.users entries
when: system.users is defined and system.users | length > 0
ansible.builtin.assert:
that:
- item is mapping
- item.name is defined and (item.name | string | length) > 0
- item['keys'] is not defined or (item['keys'] is iterable and item['keys'] is not string)
fail_msg: "Each system.users[] entry must be a dict with 'name'; 'keys' must be a list."
quiet: true
loop: "{{ system.users }}"
loop_control:
label: "{{ item.name | default('(unnamed)') }}"
- 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 | default([]) }}"
search: "{{ system_raw.network.dns.search | default([]) }}"
interfaces: >-
{{
system_raw.network.interfaces
if (system_raw.network.interfaces | default([]) | length > 0)
else (
[{
'name': 'eth0',
'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 | string)
if (system_raw.network.prefix | default('') | string | length) > 0
else ''
),
'gateway': system_raw.network.gateway | default('') | string
}]
if (system_raw.network.bridge | default('') | string | length > 0)
else []
)
}}
timezone: "{{ system_raw.timezone | default('Europe/Vienna') | string }}"
locale: "{{ system_raw.locale | default('en_US.UTF-8') | string }}"
keymap: "{{ system_raw.keymap | default('us') | string }}"
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([]) }}"
users: "{{ system_raw.users | default([]) }}"
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 }}"
- name: Populate primary network fields from first interface
when:
- system_cfg.network.interfaces | length > 0
- system_cfg.network.bridge | default('') | string | length == 0
vars:
_primary: "{{ system_cfg.network.interfaces[0] }}"
ansible.builtin.set_fact:
system_cfg: >-
{{
system_cfg | combine({
'network': system_cfg.network | combine({
'bridge': _primary.bridge | default(''),
'vlan': _primary.vlan | default(''),
'ip': _primary.ip | default(''),
'prefix': _primary.prefix | default(''),
'gateway': _primary.gateway | default('')
})
}, recursive=True)
}}
- 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: []
- 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) }}"
- 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 }}"
- name: Normalize system configuration
ansible.builtin.include_tasks: _normalize_system.yml
- name: Normalize disk configuration
ansible.builtin.include_tasks: _normalize_disks.yml

View File

@@ -114,7 +114,7 @@
ansible.builtin.assert:
that:
- os is defined
- os in ["almalinux", "alpine", "archlinux", "debian", "fedora", "opensuse", "rhel", "rocky", "ubuntu", "ubuntu-lts", "void"]
- os in os_supported
- >-
os not in ["debian", "fedora", "rocky", "almalinux", "rhel"]
or (os_version is defined and (os_version | string | length) > 0)
@@ -123,7 +123,7 @@
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"]
os == "fedora" and (os_version | int) >= 38 and (os_version | int) <= 45
) or (
os in ["rocky", "almalinux"]
and (os_version | string) is match("^(8|9|10)(\\.\\d+)?$")
@@ -131,7 +131,9 @@
os == "rhel"
and (os_version | string) is match("^(8|9|10)(\\.\\d+)?$")
) or (
os in ["alpine", "archlinux", "opensuse", "ubuntu", "ubuntu-lts", "void"]
os in ["ubuntu", "ubuntu-lts"]
) or (
os in ["alpine", "archlinux", "opensuse", "void"]
)
fail_msg: "Invalid os/version specified. Please check README.md for supported values."
quiet: true
@@ -162,6 +164,7 @@
Missing required Proxmox inputs. Define hypervisor.(url,username,password,host,storage),
system.id, and system.network.bridge (or system.network.interfaces[]).
quiet: true
no_log: true
- name: Validate VMware hypervisor inputs
when:
@@ -182,6 +185,7 @@
Missing required VMware inputs. Define hypervisor.(url,username,password,datacenter,cluster,storage)
and system.network.bridge (or system.network.interfaces[]).
quiet: true
no_log: true
- name: Validate Xen hypervisor inputs
when:
@@ -195,6 +199,18 @@
fail_msg: "Missing required Xen inputs. Define system.network.bridge (or system.network.interfaces[])."
quiet: true
- name: Validate libvirt hypervisor inputs
when:
- system_cfg.type == "virtual"
- hypervisor_type == "libvirt"
ansible.builtin.assert:
that:
- >-
(system_cfg.network.bridge | default('') | string | length > 0)
or (system_cfg.network.interfaces | default([]) | length > 0)
fail_msg: "Missing required libvirt inputs. Define system.network.bridge (or system.network.interfaces[])."
quiet: true
- name: Validate virtual installer ISO requirement
ansible.builtin.assert:
that:
@@ -245,6 +261,28 @@
fail_msg: "Invalid system sizing. Check system.cpus, system.memory, and system.disks[0].size."
quiet: true
- name: Validate at least one user is defined
ansible.builtin.assert:
that:
- system_cfg.users | default([]) | length > 0
- system_cfg.users[0].name is defined and (system_cfg.users[0].name | string | length) > 0
- system_cfg.users[0].password is defined and (system_cfg.users[0].password | string | length) > 0
fail_msg: "At least one user with a name and password must be defined in system.users[]."
quiet: true
no_log: true
- name: Validate DNS servers is a list
when:
- system_cfg.network.dns.servers is defined
- system_cfg.network.dns.servers | length > 0
ansible.builtin.assert:
that:
- system_cfg.network.dns.servers is iterable
- system_cfg.network.dns.servers is not string
- system_cfg.network.dns.servers is not mapping
fail_msg: "system.network.dns.servers must be a list."
quiet: true
- name: Validate all virtual disks have a positive size
when: system_cfg.type == "virtual"
ansible.builtin.assert:

View File

@@ -6,20 +6,40 @@ 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_vg_name: sys
partitioning_use_full_disk: true
# LVM logical volume sizing
partitioning_lvm_var_gb: 2
partitioning_lvm_var_log_gb: 2
partitioning_lvm_var_log_audit_gb: 1.5
# Disk overhead subtracted from available space in swap/home calculations
partitioning_disk_overhead_gb: 20
# CIS-required reserved space for /var, /var/log, /var/log/audit, /home
partitioning_cis_reserved_gb: 7.5
# Home allocation: percentage of (disk - overhead), bounded by min/max
partitioning_home_allocation_pct: 0.1
partitioning_home_min_gb: 2
partitioning_home_max_gb: 20
# Btrfs home quota (applied when CIS is enabled)
partitioning_btrfs_home_quota: 2G
partitioning_separate_boot: >-
{{
(
(system_cfg.luks.enabled | bool)
or (system_cfg.filesystem != 'btrfs')
)
and (os not in ['archlinux'])
and ((os | default('')) not in ['archlinux'])
}}
partitioning_boot_fs_fstype: >-
{{
system_cfg.filesystem
if system_cfg.filesystem != 'btrfs'
else ('xfs' if is_rhel else 'ext4')
else ('xfs' if (is_rhel | default(false) | bool) else 'ext4')
}}
partitioning_boot_fs_partition_suffix: >-
{{
@@ -37,7 +57,7 @@ partitioning_efi_mountpoint: >-
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']))
if (is_rhel | default(false) | bool) or ((os | default('')) in ['ubuntu', 'ubuntu-lts'] or ((os | default('')) == 'debian' and (os_version | default('') | string) in ['11', '12', '13']))
else '/boot'
)
}}

View File

@@ -54,13 +54,17 @@
- { subvol: pkg }
- { subvol: var_log }
- { subvol: var_log_audit }
loop_control:
label: "{{ item.subvol }}"
register: partitioning_btrfs_subvol_result
- name: Set quotas for subvolumes
when: system_cfg.features.cis.enabled
ansible.builtin.command: btrfs qgroup limit {{ item.quota }} /mnt/{{ '@' if item.subvol == 'root' else '@' + item.subvol }}
loop:
- { subvol: home, quota: 2G }
- { subvol: home, quota: "{{ partitioning_btrfs_home_quota }}" }
loop_control:
label: "{{ item.subvol }}"
register: partitioning_btrfs_qgroup_result
changed_when: false

View File

@@ -2,7 +2,7 @@
- name: Create and format ext4 logical volumes
when: system_cfg.features.cis.enabled or item.lv not in ['home', 'var', 'var_log', 'var_log_audit']
community.general.filesystem:
dev: /dev/sys/{{ item.lv }}
dev: /dev/{{ partitioning_vg_name }}/{{ item.lv }}
fstype: ext4
force: true
loop:
@@ -11,17 +11,21 @@
- { lv: var }
- { lv: var_log }
- { lv: var_log_audit }
loop_control:
label: "{{ item.lv }}"
- name: Remove Unsupported features for older Systems
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/{{ partitioning_vg_name }}/{{ item.lv }}"
loop:
- { lv: root }
- { lv: home }
- { lv: var }
- { lv: var_log }
- { lv: var_log_audit }
loop_control:
label: "{{ item.lv }}"
register: partitioning_ext4_tune_result
changed_when: partitioning_ext4_tune_result.rc == 0

View File

@@ -7,7 +7,6 @@
| selectattr('mount.path')
| list
}}
changed_when: false
- name: Validate additional disks do not target install_drive
when: partitioning_extra_disks | length > 0
@@ -59,7 +58,16 @@
dev: "{{ item.partition }}"
fstype: "{{ item.mount.fstype }}"
opts: "{{ _all_opts }}"
force: true
loop: "{{ partitioning_extra_disks }}"
loop_control:
label: "{{ item.partition }}"
- name: Collect extra disk UUIDs
when: partitioning_extra_disks | length > 0
ansible.builtin.command: "blkid -s UUID -o value {{ item.partition }}"
register: partitioning_extra_disk_uuids
changed_when: false
failed_when: partitioning_extra_disk_uuids.rc != 0 or (partitioning_extra_disk_uuids.stdout | trim | length) == 0
loop: "{{ partitioning_extra_disks }}"
loop_control:
label: "{{ item.partition }}"
@@ -79,11 +87,11 @@
- 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') }}"
path: "/mnt{{ item.0.mount.path }}"
src: "UUID={{ item.1.stdout }}"
fstype: "{{ item.0.mount.fstype }}"
opts: "{{ item.0.mount.opts | default('defaults') }}"
state: mounted
loop: "{{ partitioning_extra_disks }}"
loop: "{{ partitioning_extra_disks | zip(partitioning_extra_disk_uuids.results) | list }}"
loop_control:
label: "{{ item.mount.path }}"
label: "{{ item.0.mount.path }}"

View File

@@ -3,7 +3,7 @@
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
- (system_cfg.memory | default(0) | float) <= 0
block:
- name: Read system memory
ansible.builtin.command: awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo
@@ -65,7 +65,7 @@
- name: Remove LVM volume group
community.general.lvg:
vg: sys
vg: "{{ partitioning_vg_name }}"
state: absent
force: true
failed_when: false
@@ -93,10 +93,10 @@
failed_when: false
- name: Wipe filesystem signatures
ansible.builtin.command: >-
ansible.builtin.shell: >-
find /dev -wholename "{{ install_drive }}*" -exec wipefs --force --all {} \;
register: partitioning_wipefs_result
changed_when: false
changed_when: partitioning_wipefs_result.rc == 0
failed_when: false
- name: Refresh kernel partition table
@@ -122,6 +122,8 @@
flags: "{{ item.flags | default(omit) }}"
state: present
loop: "{{ partitioning_layout }}"
loop_control:
label: "{{ item.name }}"
rescue:
- name: Refresh kernel partition table after failure
ansible.builtin.command: "{{ item }}"
@@ -144,6 +146,8 @@
flags: "{{ item.flags | default(omit) }}"
state: present
loop: "{{ partitioning_layout }}"
loop_control:
label: "{{ item.name }}"
- name: Settle partition table
ansible.builtin.command: "{{ item }}"
@@ -251,7 +255,7 @@
block:
- name: Create LVM volume group
community.general.lvg:
vg: sys
vg: "{{ partitioning_vg_name }}"
pvs: "{{ partitioning_root_device }}"
- name: Create LVM logical volumes
@@ -272,10 +276,11 @@
partitioning_lvm_swap_target_gb: >-
{{
(
[
(partitioning_memory_mb | float / 1024),
4
] | max | float
((partitioning_memory_mb | float / 1024) >= 16.0)
| ternary(
(partitioning_memory_mb | float / 2048),
[(partitioning_memory_mb | float / 1024), 4] | max | float
)
)
if system_cfg.features.swap.enabled | bool
else 0
@@ -285,7 +290,7 @@
(
4
+ [
(partitioning_disk_size_gb | float) - 20,
(partitioning_disk_size_gb | float) - (partitioning_disk_overhead_gb | float),
0
] | max
)
@@ -310,7 +315,7 @@
(
(partitioning_disk_size_gb | float)
- (partitioning_reserved_gb | float)
- (system_cfg.features.cis.enabled | ternary(7.5, 0))
- (system_cfg.features.cis.enabled | ternary(partitioning_cis_reserved_gb | float, 0))
- partitioning_lvm_extent_reserve_gb
- 4
),
@@ -325,14 +330,22 @@
(
(partitioning_disk_size_gb | float)
- (partitioning_reserved_gb | float)
- (system_cfg.features.cis.enabled | ternary(7.5, 0))
- (system_cfg.features.cis.enabled | ternary(partitioning_cis_reserved_gb | float, 0))
- partitioning_lvm_extent_reserve_gb
- partitioning_lvm_swap_target_limited_gb
) | float
}}
partitioning_lvm_home_raw_gb: >-
{{
((partitioning_disk_size_gb | float) - (partitioning_disk_overhead_gb | float))
* (partitioning_home_allocation_pct | float)
}}
partitioning_lvm_home_gb: >-
{{
([([(((partitioning_disk_size_gb | float) - 20) * 0.1), 2] | max), 20] | min)
[
[(partitioning_lvm_home_raw_gb | float), (partitioning_home_min_gb | float)] | max,
(partitioning_home_max_gb | float)
] | min
}}
partitioning_lvm_root_default_gb: >-
{{
@@ -374,7 +387,10 @@
- (partitioning_lvm_swap_gb | float)
- partitioning_lvm_extent_reserve_gb
- (
(partitioning_lvm_home_gb | float) + 5.5
(partitioning_lvm_home_gb | float)
+ (partitioning_lvm_var_gb | float)
+ (partitioning_lvm_var_log_gb | float)
+ (partitioning_lvm_var_log_audit_gb | float)
if system_cfg.features.cis.enabled
else 0
)
@@ -389,7 +405,7 @@
else partitioning_lvm_root_default_gb
}}
community.general.lvol:
vg: sys
vg: "{{ partitioning_vg_name }}"
lv: "{{ item.lv }}"
size: "{{ item.size }}"
state: present
@@ -400,9 +416,11 @@
size: "{{ partitioning_lvm_swap_gb | string + 'G' }}"
- 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" }
- { lv: var, size: "{{ partitioning_lvm_var_gb }}G" }
- { lv: var_log, size: "{{ partitioning_lvm_var_log_gb }}G" }
- { lv: var_log_audit, size: "{{ partitioning_lvm_var_log_audit_gb }}G" }
loop_control:
label: "{{ item.lv }}"
- name: Create filesystems
block:
@@ -438,7 +456,7 @@
- system_cfg.features.swap.enabled | bool
community.general.filesystem:
fstype: swap
dev: /dev/sys/swap
dev: /dev/{{ partitioning_vg_name }}/swap
- name: Create filesystem
ansible.builtin.include_tasks: "{{ system_cfg.filesystem }}.yml"
@@ -447,6 +465,7 @@
ansible.builtin.command: blkid -s UUID -o value '{{ install_drive }}{{ partitioning_boot_partition_suffix }}'
register: partitioning_boot_uuid
changed_when: false
failed_when: partitioning_boot_uuid.rc != 0 or (partitioning_boot_uuid.stdout | trim | length) == 0
- name: Get UUID for /boot filesystem
when: partitioning_separate_boot | bool
@@ -454,57 +473,65 @@
blkid -s UUID -o value '{{ install_drive }}{{ partitioning_boot_fs_partition_suffix }}'
register: partitioning_boot_fs_uuid
changed_when: false
failed_when: partitioning_boot_fs_uuid.rc != 0 or (partitioning_boot_fs_uuid.stdout | trim | length) == 0
- name: Get UUID for main filesystem
ansible.builtin.command: blkid -s UUID -o value '{{ partitioning_root_device }}'
register: partitioning_main_uuid
changed_when: false
failed_when: partitioning_main_uuid.rc != 0 or (partitioning_main_uuid.stdout | trim | length) == 0
- name: Get UUID for LVM root filesystem
when: system_cfg.filesystem != 'btrfs'
ansible.builtin.command: blkid -s UUID -o value /dev/sys/root
ansible.builtin.command: blkid -s UUID -o value /dev/{{ partitioning_vg_name }}/root
register: partitioning_uuid_root_result
changed_when: false
failed_when: partitioning_uuid_root_result.rc != 0 or (partitioning_uuid_root_result.stdout | trim | length) == 0
- 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
ansible.builtin.command: blkid -s UUID -o value /dev/{{ partitioning_vg_name }}/swap
register: partitioning_uuid_swap_result
changed_when: false
failed_when: partitioning_uuid_swap_result.rc != 0 or (partitioning_uuid_swap_result.stdout | trim | length) == 0
- 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
ansible.builtin.command: blkid -s UUID -o value /dev/{{ partitioning_vg_name }}/home
register: partitioning_uuid_home_result
changed_when: false
failed_when: partitioning_uuid_home_result.rc != 0 or (partitioning_uuid_home_result.stdout | trim | length) == 0
- 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
ansible.builtin.command: blkid -s UUID -o value /dev/{{ partitioning_vg_name }}/var
register: partitioning_uuid_var_result
changed_when: false
failed_when: partitioning_uuid_var_result.rc != 0 or (partitioning_uuid_var_result.stdout | trim | length) == 0
- 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
ansible.builtin.command: blkid -s UUID -o value /dev/{{ partitioning_vg_name }}/var_log
register: partitioning_uuid_var_log_result
changed_when: false
failed_when: partitioning_uuid_var_log_result.rc != 0 or (partitioning_uuid_var_log_result.stdout | trim | length) == 0
- 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
ansible.builtin.command: blkid -s UUID -o value /dev/{{ partitioning_vg_name }}/var_log_audit
register: partitioning_uuid_var_log_audit_result
changed_when: false
failed_when: partitioning_uuid_var_log_audit_result.rc != 0 or (partitioning_uuid_var_log_audit_result.stdout | trim | length) == 0
- name: Assign UUIDs to Variables
when: system_cfg.filesystem != 'btrfs'
@@ -514,31 +541,31 @@
{{
partitioning_uuid_swap_result.stdout_lines | default([])
if system_cfg.features.swap.enabled | bool
else ''
else []
}}
partitioning_uuid_home: >-
{{
partitioning_uuid_home_result.stdout_lines | default([])
if system_cfg.features.cis.enabled
else ''
else []
}}
partitioning_uuid_var: >-
{{
partitioning_uuid_var_result.stdout_lines | default([])
if system_cfg.features.cis.enabled
else ''
else []
}}
partitioning_uuid_var_log: >-
{{
partitioning_uuid_var_log_result.stdout_lines | default([])
if system_cfg.features.cis.enabled
else ''
else []
}}
partitioning_uuid_var_log_audit: >-
{{
partitioning_uuid_var_log_audit_result.stdout_lines | default([])
if system_cfg.features.cis.enabled
else ''
else []
}}
- name: Mount filesystems
@@ -562,6 +589,7 @@
opts: "{{ item.opts }}"
state: mounted
loop:
# ssd: no-op on kernels 5.15+ (btrfs auto-detects); kept for older kernel compat
- path: ""
uuid: "{{ partitioning_uuid_root[0] | default(omit) }}"
opts: >-
@@ -636,6 +664,8 @@
'ssd', 'space_cache=v2', 'discard=async', 'subvol=@var_log_audit'
] | reject('equalto', '') | join(',')
}}
loop_control:
label: "{{ item.path }}"
- name: Mount /boot filesystem
when: partitioning_separate_boot | bool

View File

@@ -2,7 +2,7 @@
- name: Create and format XFS logical volumes
when: system_cfg.features.cis.enabled or item.lv not in ['home', 'var', 'var_log', 'var_log_audit']
community.general.filesystem:
dev: /dev/sys/{{ item.lv }}
dev: /dev/{{ partitioning_vg_name }}/{{ item.lv }}
fstype: xfs
opts: "{{ '-m bigtime=0 -i nrext64=0,exchange=0 -n parent=0' if is_rhel | bool else omit }}"
force: true
@@ -12,3 +12,5 @@
- { lv: var }
- { lv: var_log }
- { lv: var_log_audit }
loop_control:
label: "{{ item.lv }}"

View File

@@ -1,4 +1,14 @@
---
- name: Physical install safety confirmation
when: system_cfg.type == "physical"
ansible.builtin.assert:
that:
- physical_install_confirmed | default(false) | bool
fail_msg: >-
DANGER: Physical install will WIPE {{ install_drive }} on {{ inventory_hostname }}.
Set physical_install_confirmed=true in inventory to proceed.
quiet: true
- name: VM existence protection check
when: system_cfg.type == "virtual"
block:
@@ -23,23 +33,27 @@
Please choose a different hostname or remove the existing VM manually before proceeding.
quiet: true
- name: Check if VM already exists on Proxmox
- name: Check VM existence on Proxmox
when: hypervisor_type == "proxmox"
delegate_to: localhost
become: false
module_defaults:
community.proxmox.proxmox_vm_info:
api_host: "{{ hypervisor_cfg.url }}"
api_user: "{{ hypervisor_cfg.username }}"
api_password: "{{ hypervisor_cfg.password }}"
block:
- name: Query Proxmox for existing VM
community.proxmox.proxmox_vm_info:
node: "{{ hypervisor_cfg.host }}"
vmid: "{{ system_cfg.id }}"
name: "{{ hostname }}"
type: qemu
register: system_check_proxmox_check_result
changed_when: false
no_log: true
- 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
@@ -49,23 +63,27 @@
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
- name: Check VM existence in vCenter
when: hypervisor_type == "vmware"
delegate_to: localhost
module_defaults:
community.vmware.vmware_guest_info:
hostname: "{{ hypervisor_cfg.url }}"
username: "{{ hypervisor_cfg.username }}"
password: "{{ hypervisor_cfg.password }}"
validate_certs: "{{ hypervisor_cfg.certs | bool }}"
block:
- name: Query vCenter for existing VM
community.vmware.vmware_guest_info:
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
no_log: true
- name: Fail if vCenter lookup failed unexpectedly
when: hypervisor_type == "vmware"
ansible.builtin.assert:
that:
- not system_check_vmware_check_result.failed
@@ -76,7 +94,6 @@
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

View File

@@ -11,6 +11,10 @@ virtualization_libvirt_cloudinit_path: >-
{{ [virtualization_libvirt_image_dir, hostname ~ '-cloudinit.iso'] | ansible.builtin.path_join }}
virtualization_xen_disk_path: /var/lib/xen/images
virtualization_libvirt_machine_type: q35
virtualization_libvirt_ovmf_code: /usr/share/edk2/x64/OVMF_CODE.secboot.4m.fd
virtualization_libvirt_ovmf_vars: /usr/share/edk2/x64/OVMF_VARS.4m.fd
virtualization_tpm2_enabled: >-
{{
(system_cfg.luks.enabled | bool)

View File

@@ -23,7 +23,6 @@
loop_control:
label: "{{ item | to_json }}"
extended: true
changed_when: false
- name: Create VM disks
delegate_to: localhost
@@ -61,6 +60,7 @@
- "/tmp/cloud-network-config-{{ hostname }}.yml"
creates: "{{ virtualization_libvirt_cloudinit_path }}"
# uri defaults to qemu:///system (local libvirtd)
- name: Create VM using libvirt
delegate_to: localhost
community.libvirt.virt:

View File

@@ -1,6 +1,14 @@
---
- name: Deploy VM on Proxmox
delegate_to: localhost
module_defaults:
community.proxmox.proxmox_kvm:
api_host: "{{ hypervisor_cfg.url }}"
api_user: "{{ hypervisor_cfg.username }}"
api_password: "{{ hypervisor_cfg.password }}"
node: "{{ hypervisor_cfg.host }}"
block:
- name: Create VM on Proxmox
vars:
virtualization_proxmox_scsi: >-
{%- set out = {} -%}
@@ -31,13 +39,9 @@
{%- endfor -%}
{{ out }}
community.proxmox.proxmox_kvm:
api_host: "{{ hypervisor_cfg.url }}"
api_user: "{{ hypervisor_cfg.username }}"
api_password: "{{ hypervisor_cfg.password }}"
ciuser: "{{ system_cfg.users[0].name }}"
cipassword: "{{ system_cfg.users[0].password }}"
ciupgrade: false
node: "{{ hypervisor_cfg.host }}"
vmid: "{{ system_cfg.id }}"
name: "{{ hostname }}"
cpu: host
@@ -74,17 +78,14 @@
searchdomains: "{{ system_cfg.network.dns.search if system_cfg.network.dns.search | length else omit }}"
onboot: true
state: present
no_log: true
- name: Start VM on Proxmox
delegate_to: localhost
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: started
no_log: true
register: virtualization_proxmox_start_result
- name: Set VM created fact

View File

@@ -10,10 +10,31 @@
loop: "{{ system_cfg.disks }}"
loop_control:
label: "{{ item | to_json }}"
changed_when: false
- name: Create VM in vCenter
- name: Deploy VM in vCenter
delegate_to: localhost
module_defaults:
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 }}"
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 }}"
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 }}"
block:
# community.vmware: full-featured guest management
- name: Create VM in vCenter
vars:
virtualization_vmware_networks: >-
{%- set ns = namespace(out=[]) -%}
@@ -26,14 +47,10 @@
{%- endfor -%}
{{ ns.out }}
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 }}"
cluster: "{{ hypervisor_cfg.cluster }}"
folder: "{{ system_cfg.path if system_cfg.path | string | length > 0 else omit }}"
name: "{{ hostname }}"
# Generic guest ID — VMware auto-detects OS post-install
guest_id: otherLinux64Guest
annotation: |
{{ note if note is defined else '' }}
@@ -65,6 +82,7 @@
} ] if rhel_iso is defined and rhel_iso | length > 0 else [] )
}}
networks: "{{ virtualization_vmware_networks }}"
no_log: true
register: virtualization_vmware_create_result
- name: Set VM created fact when VM was powered on during creation
@@ -77,28 +95,19 @@
- 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
no_log: true
# vmware.vmware: modern collection for power operations
- 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
no_log: true
register: virtualization_vmware_start_result
- name: Set VM created fact when VM was started separately (TPM2 case)

View File

@@ -49,6 +49,16 @@
dest: /tmp/xen-{{ hostname }}.cfg
mode: "0644"
- name: Check if Xen VM already exists
delegate_to: localhost
ansible.builtin.command:
argv:
- xl
- list
register: virtualization_xen_pre_check
changed_when: false
failed_when: false
- name: Create Xen VM
delegate_to: localhost
ansible.builtin.command:
@@ -58,8 +68,11 @@
- /tmp/xen-{{ hostname }}.cfg
register: virtualization_xen_create_result
changed_when: virtualization_xen_create_result.rc == 0
when: >-
not (virtualization_xen_pre_check.stdout | default('')
is search('(?m)^' ~ (hostname | ansible.builtin.regex_escape) ~ '\\s+\\d+\\s'))
- name: Ensure VM is running
- name: Verify VM is running
delegate_to: localhost
ansible.builtin.command:
argv:
@@ -67,13 +80,10 @@
- list
register: virtualization_xen_list_result
changed_when: false
failed_when: false
failed_when: >-
not (virtualization_xen_list_result.stdout | default('')
is search('(?m)^' ~ (hostname | ansible.builtin.regex_escape) ~ '\\s+\\d+\\s'))
- 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')
virtualization_vm_created_in_run: "{{ (virtualization_xen_create_result.rc | default(1)) == 0 }}"

View File

@@ -13,7 +13,9 @@ network:
addresses:
- "{{ iface.ip }}/{{ iface.prefix }}"
{% if iface.gateway | default('') | string | length %}
gateway4: "{{ iface.gateway }}"
routes:
- to: default
via: "{{ iface.gateway }}"
{% endif %}
{% else %}
dhcp4: true

View File

@@ -1,5 +1,5 @@
#cloud-config
hostname: "archiso"
hostname: "{{ hostname }}"
ssh_pwauth: true
package_update: false
package_upgrade: false

View File

@@ -1,15 +1,15 @@
<domain type='kvm'>
<name>{{ hostname }}</name>
<memory>{{ system_cfg.memory | int * 1024 }}</memory>
{% if system_cfg.balloon is defined and system_cfg.balloon | int > 0 %}<currentMemory>{{ system_cfg.balloon | int * 1024 }}</currentMemory>{% endif %}
<memory unit='KiB'>{{ system_cfg.memory | int * 1024 }}</memory>
{% if system_cfg.balloon is defined and system_cfg.balloon | int > 0 %}<currentMemory unit='KiB'>{{ system_cfg.balloon | int * 1024 }}</currentMemory>{% endif %}
<vcpu placement='static'>{{ system_cfg.cpus }}</vcpu>
<os>
<type arch='x86_64' machine="pc-q35-8.0">hvm</type>
<type arch='x86_64' machine="{{ virtualization_libvirt_machine_type }}">hvm</type>
<bootmenu enable='no'/>
<boot dev='hd'/>
<boot dev='cdrom'/>
<loader readonly="yes" type="pflash">/usr/share/edk2/x64/OVMF_CODE.secboot.4m.fd</loader>
<nvram template="/usr/share/edk2/x64/OVMF_VARS.4m.fd"/>
<loader readonly="yes" type="pflash">{{ virtualization_libvirt_ovmf_code }}</loader>
<nvram template="{{ virtualization_libvirt_ovmf_vars }}"/>
</os>
<features>
<acpi/>
@@ -33,17 +33,20 @@
<driver name="qemu" type="raw"/>
<source file="{{ boot_iso }}"/>
<target dev="sda" bus="sata"/>
<readonly/>
</disk>
<disk type="file" device="cdrom">
<driver name="qemu" type="raw"/>
<source file="{{ virtualization_libvirt_cloudinit_path }}"/>
<target dev="sdb" bus="sata"/>
<readonly/>
</disk>
{% if rhel_iso is defined and rhel_iso | length > 0 %}
<disk type="file" device="cdrom">
<driver name="qemu" type="raw"/>
<source file="{{ rhel_iso }}"/>
<target dev="sdc" bus="sata"/>
<readonly/>
</disk>
{% endif %}
{% for iface in system_cfg.network.interfaces %}

View File

@@ -12,7 +12,7 @@ disk = [
]
vif = [
{%- for iface in system_cfg.network.interfaces -%}
'bridge={{ iface.bridge }},model=e1000'{% if not loop.last %}, {% endif %}
'bridge={{ iface.bridge }},model=virtio'{% if not loop.last %}, {% endif %}
{%- endfor -%}
]
boot = "{{ 'dc' if xen_installer_media_enabled | bool else 'c' }}"