--- - 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.user is not defined or system.user is mapping - system.root is not defined or system.root is mapping - system.luks is not defined or system.luks is mapping - system.features is not defined or system.features is mapping fail_msg: "system and its nested keys (network, user, root, luks, features) must be dictionaries." quiet: true - name: Validate DNS and user.key are 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 user.key is a list when: system.user is defined and system.user.key is defined ansible.builtin.assert: that: - system.user.key is iterable and system.user.key is not string fail_msg: "system.user.key must be a list of SSH public key strings." quiet: true - name: Validate system features input types when: system.features is defined loop: "{{ system_defaults.features | dict2items | map(attribute='key') | list }}" loop_control: label: "system.features.{{ item }}" ansible.builtin.assert: that: - (system.features[item] | default({})) is mapping fail_msg: "system.features.{{ item }} must be a dictionary." quiet: true - name: Validate system LUKS TPM2 input type when: system.luks is defined and system.luks is mapping ansible.builtin.assert: that: - system.luks.tpm2 is not defined or system.luks.tpm2 is mapping fail_msg: "system.luks.tpm2 must be a dictionary." quiet: true - name: Build normalized system configuration vars: system_raw: "{{ system_defaults | combine(system, recursive=True) }}" system_type: "{{ system_raw.type | string | lower }}" system_os_input: "{{ system_raw.os | default('') | string | lower }}" system_name: >- {{ system_raw.name | string | trim if (system_raw.name | default('') | string | trim | length) > 0 else inventory_hostname }} ansible.builtin.set_fact: system_cfg: type: "{{ system_type }}" os: "{{ system_os_input if system_os_input | length > 0 else ('archlinux' if system_type == 'physical' else '') }}" version: "{{ system_raw.version | default('') | string }}" filesystem: "{{ system_raw.filesystem | default('') | string | lower }}" name: "{{ system_name }}" id: "{{ system_raw.id | default('') | string }}" cpus: "{{ [system_raw.cpus | default(0) | int, 0] | max }}" memory: "{{ [system_raw.memory | default(0) | int, 0] | max }}" balloon: "{{ [system_raw.balloon | default(0) | int, 0] | max }}" network: bridge: "{{ system_raw.network.bridge | default('') | string }}" vlan: "{{ system_raw.network.vlan | default('') | string }}" ip: "{{ system_raw.network.ip | default('') | string }}" prefix: >- {{ (system_raw.network.prefix | int) if (system_raw.network.prefix | default('') | string | length) > 0 else '' }} gateway: "{{ system_raw.network.gateway | default('') | string }}" dns: servers: "{{ system_raw.network.dns.servers | default([]) }}" search: "{{ system_raw.network.dns.search | default([]) }}" path: "{{ system_raw.path | default('') | string }}" packages: >- {{ ( system_raw.packages if system_raw.packages is iterable and system_raw.packages is not string else (system_raw.packages | string).split(',') ) | map('trim') | reject('equalto', '') | list }} disks: "{{ system_raw.disks | default([]) }}" user: name: "{{ system_raw.user.name | string }}" password: "{{ system_raw.user.password | string }}" key: "{{ system_raw.user.key | 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: 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 }}"