--- - name: Ensure system input is a dictionary ansible.builtin.set_fact: system: "{{ system | default({}) }}" changed_when: false - name: Validate system input type ansible.builtin.assert: that: - system is mapping fail_msg: "system 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 if (system_raw.name | default('') | string | length) > 0 else inventory_hostname }} system_dns_raw: "{{ system_raw.dns if system_raw.dns is mapping else {} }}" system_dns_servers_input: "{{ system_dns_raw.servers | default([]) }}" system_dns_search_input: "{{ system_dns_raw.search | default([]) }}" system_user_raw: "{{ system_raw.user if system_raw.user is mapping else {} }}" system_root_raw: "{{ system_raw.root if system_raw.root is mapping else {} }}" system_luks_raw: "{{ system_raw.luks if system_raw.luks is mapping else {} }}" system_luks_tpm2_raw: "{{ system_luks_raw.tpm2 if system_luks_raw.tpm2 is mapping else {} }}" system_features_raw: "{{ system_raw.features if system_raw.features is mapping else {} }}" system_feature_cis_raw: >- {{ system_features_raw.cis if system_features_raw.cis is defined and system_features_raw.cis is mapping else {} }} system_feature_selinux_raw: >- {{ system_features_raw.selinux if system_features_raw.selinux is defined and system_features_raw.selinux is mapping else {} }} system_feature_firewall_raw: >- {{ system_features_raw.firewall if system_features_raw.firewall is defined and system_features_raw.firewall is mapping else {} }} system_feature_ssh_raw: >- {{ system_features_raw.ssh if system_features_raw.ssh is defined and system_features_raw.ssh is mapping else {} }} system_feature_zstd_raw: >- {{ system_features_raw.zstd if system_features_raw.zstd is defined and system_features_raw.zstd is mapping else {} }} system_feature_swap_raw: >- {{ system_features_raw.swap if system_features_raw.swap is defined and system_features_raw.swap is mapping else {} }} system_feature_banner_raw: >- {{ system_features_raw.banner if system_features_raw.banner is defined and system_features_raw.banner is mapping else {} }} system_feature_chroot_raw: >- {{ system_features_raw.chroot if system_features_raw.chroot is defined and system_features_raw.chroot is mapping else {} }} system_packages_input: "{{ system_raw.packages | default([]) }}" 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 }}" 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: "{{ system_raw.network | default('') | string }}" vlan: "{{ system_raw.vlan | default('') | string }}" ip: "{{ system_raw.ip | default('') | string }}" prefix: >- {{ (system_raw.prefix | int) if (system_raw.prefix | default('') | string | length) > 0 else '' }} gateway: "{{ system_raw.gateway | default('') | string }}" dns: servers: >- {{ ( system_dns_servers_input if system_dns_servers_input is iterable and system_dns_servers_input is not string else (system_dns_servers_input | string).split(',') ) | map('trim') | reject('equalto', '') | list }} search: >- {{ ( system_dns_search_input if system_dns_search_input is iterable and system_dns_search_input is not string else (system_dns_search_input | string).split(',') ) | map('trim') | reject('equalto', '') | list }} path: "{{ system_raw.path | default('') | string }}" packages: >- {{ ( system_packages_input if system_packages_input is iterable and system_packages_input is not string else (system_packages_input | string).split(',') ) | map('trim') | reject('equalto', '') | list }} disks: "{{ system_raw.disks | default([]) }}" user: name: "{{ system_user_raw.name | default('') | string }}" password: "{{ system_user_raw.password | default('') | string }}" key: "{{ system_user_raw.key | default('') | string }}" root: password: "{{ system_root_raw.password | default('') | string }}" luks: enabled: "{{ system_luks_raw.enabled | default(system_defaults.luks.enabled) | bool }}" passphrase: "{{ system_luks_raw.passphrase | default(system_defaults.luks.passphrase) | string }}" mapper: "{{ system_luks_raw.mapper | default(system_defaults.luks.mapper) | string }}" auto: "{{ system_luks_raw.auto | default(system_defaults.luks.auto) | bool }}" method: "{{ system_luks_raw.method | default(system_defaults.luks.method) | string | lower }}" tpm2: device: "{{ system_luks_tpm2_raw.device | default(system_defaults.luks.tpm2.device) | string }}" pcrs: "{{ system_luks_tpm2_raw.pcrs | default(system_defaults.luks.tpm2.pcrs) | string }}" keysize: "{{ system_luks_raw.keysize | default(system_defaults.luks.keysize) | int }}" options: "{{ system_luks_raw.options | default(system_defaults.luks.options) | string }}" type: "{{ system_luks_raw.type | default(system_defaults.luks.type) | string }}" cipher: "{{ system_luks_raw.cipher | default(system_defaults.luks.cipher) | string }}" hash: "{{ system_luks_raw.hash | default(system_defaults.luks.hash) | string }}" iter: "{{ system_luks_raw.iter | default(system_defaults.luks.iter) | int }}" bits: "{{ system_luks_raw.bits | default(system_defaults.luks.bits) | int }}" pbkdf: "{{ system_luks_raw.pbkdf | default(system_defaults.luks.pbkdf) | string }}" urandom: "{{ system_luks_raw.urandom | default(system_defaults.luks.urandom) | bool }}" verify: "{{ system_luks_raw.verify | default(system_defaults.luks.verify) | bool }}" features: cis: enabled: "{{ system_feature_cis_raw.enabled | default(system_defaults.features.cis.enabled) | bool }}" selinux: enabled: "{{ system_feature_selinux_raw.enabled | default(system_defaults.features.selinux.enabled) | bool }}" firewall: enabled: "{{ system_feature_firewall_raw.enabled | default(system_defaults.features.firewall.enabled) | bool }}" backend: "{{ system_feature_firewall_raw.backend | default(system_defaults.features.firewall.backend) | string | lower }}" toolkit: "{{ system_feature_firewall_raw.toolkit | default(system_defaults.features.firewall.toolkit) | string | lower }}" ssh: enabled: "{{ system_feature_ssh_raw.enabled | default(system_defaults.features.ssh.enabled) | bool }}" zstd: enabled: "{{ system_feature_zstd_raw.enabled | default(system_defaults.features.zstd.enabled) | bool }}" swap: enabled: "{{ system_feature_swap_raw.enabled | default(system_defaults.features.swap.enabled) | bool }}" banner: motd: "{{ system_feature_banner_raw.motd | default(system_defaults.features.banner.motd) | bool }}" sudo: "{{ system_feature_banner_raw.sudo | default(system_defaults.features.banner.sudo) | bool }}" chroot: tool: "{{ system_feature_chroot_raw.tool | default(system_defaults.features.chroot.tool) | string }}" hostname: "{{ system_name }}" os: "{{ system_os_input if system_os_input | length > 0 else ('archlinux' if system_type == 'physical' else '') }}" os_version: "{{ system_raw.version | default('') | string }}" changed_when: false - name: Normalize system disks input vars: system_disks: "{{ system_cfg.disks | default([]) }}" system_disk_defaults: size: 0 device: "" mount: path: "" fstype: "" label: "" opts: "defaults" system_disk_letter_map: "abcdefghijklmnopqrstuvwxyz" system_disk_device_prefix: >- {{ '/dev/vd' if system_cfg.type == 'virtual' and hypervisor_type == 'libvirt' else ( '/dev/xvd' if system_cfg.type == 'virtual' and hypervisor_type == 'xen' else ( '/dev/sd' if system_cfg.type == 'virtual' and hypervisor_type in ['proxmox', 'vmware'] else '' ) ) }} block: - name: Validate system disks type ansible.builtin.assert: that: - system_disks is sequence fail_msg: "system.disks must be a list" quiet: true - name: Validate system disk items ansible.builtin.assert: that: - item is mapping fail_msg: "Each system disk entry must be a dictionary" quiet: true loop: "{{ system_disks }}" loop_control: label: "{{ item | to_json }}" - name: Validate system disk mount schema ansible.builtin.assert: that: - item.mount is not defined or item.mount is mapping fail_msg: "system.disks[].mount must be a dictionary (e.g. mount: {path: /data})." quiet: true loop: "{{ system_disks }}" loop_control: label: "{{ item | to_json }}" - name: Validate system disk count ansible.builtin.assert: that: - (system_disks | length) <= 26 fail_msg: "system.disks supports at most 26 entries." quiet: true - name: Initialize normalized disk list ansible.builtin.set_fact: system_disks_cfg: [] changed_when: false - name: Build normalized system disk configuration vars: disk_idx: "{{ ansible_loop.index0 }}" disk_letter: "{{ system_disk_letter_map[disk_idx] }}" disk_cfg_base: "{{ system_disk_defaults | combine(item, recursive=True) }}" disk_mount: "{{ system_disk_defaults.mount | combine((disk_cfg_base.mount | default({})), recursive=True) }}" disk_mount_path: "{{ (disk_mount.path | default('') | string) | trim }}" disk_mount_fstype: >- {{ disk_mount.fstype if (disk_mount.fstype | default('') | string | length) > 0 else ('ext4' if disk_mount_path | length > 0 else '') }} disk_device: >- {{ disk_cfg_base.device if (disk_cfg_base.device | string | length) > 0 else ( (system_disk_device_prefix ~ disk_letter) if system_cfg.type == 'virtual' else '' ) }} disk_partition: >- {{ disk_device ~ ('p1' if (disk_device | regex_search('\\d$')) else '1') if disk_device | length > 0 else '' }} ansible.builtin.set_fact: system_disks_cfg: >- {{ system_disks_cfg + [ disk_cfg_base | combine( { 'device': disk_device, 'mount': { 'path': disk_mount_path, 'fstype': disk_mount_fstype, 'label': disk_mount.label | default('') | string, 'opts': disk_mount.opts | default('defaults') | string }, 'partition': disk_partition }, recursive=True ) ] }} loop: "{{ system_disks }}" loop_control: loop_var: item extended: true label: "{{ item | to_json }}" - name: Update system configuration with normalized disks ansible.builtin.set_fact: system_cfg: "{{ system_cfg | combine({'disks': system_disks_cfg}, recursive=True) }}" changed_when: false - name: Set install_drive from primary disk when: - system_disks_cfg | length > 0 - system_disks_cfg[0].device | string | length > 0 ansible.builtin.set_fact: install_drive: "{{ system_disks_cfg[0].device }}" changed_when: false