From cc77f646d7b66a50442009aaf54143e8dc4a06c0 Mon Sep 17 00:00:00 2001 From: Sandwich Date: Sun, 28 Dec 2025 16:00:49 +0100 Subject: [PATCH] Normalize LUKS boot layout and partitioning defaults --- README.md | 4 + roles/configuration/tasks/bootloader.yml | 22 ++-- roles/configuration/tasks/encryption.yml | 6 +- roles/configuration/tasks/grub.yml | 3 +- roles/partitioning/defaults/main.yml | 119 ++++++++++++++++++++ roles/partitioning/tasks/main.yml | 133 ++++++++++------------- 6 files changed, 196 insertions(+), 91 deletions(-) create mode 100644 roles/partitioning/defaults/main.yml diff --git a/README.md b/README.md index e490067..6f90704 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,10 @@ Use `inventory_example.yml`, `vars_example.yml`, and the bare-metal examples as - `vm_dns` and `vm_dns_search` accept comma-separated strings or YAML lists. - `hypervisor` determines which backend-specific roles run. - Guest tools are installed based on `hypervisor`: `qemu-guest-agent` for `libvirt`/`proxmox`, `open-vm-tools` for `vmware`, otherwise none. +- With LUKS enabled on Debian/Ubuntu and RHEL-based systems, provisioning uses an ESP (50 MiB), a separate `/boot` + (1 GiB, same as `filesystem` unless `btrfs` uses ext4 on Debian/Ubuntu or xfs on RHEL-based), and the encrypted root; + adjust sizes via + `partitioning_efi_size_mib` and `partitioning_boot_size_mib` if needed. - With `luks_auto_decrypt_method: tpm2` on virtual installs, the virtualization role enables a TPM2 device (libvirt/proxmox/vmware). - For VMware, `vmware_ssh: true` enables SSH on the guest and switches the connection to SSH for the remaining tasks. - For physical installs, set `ansible_user`/`ansible_password` for the installer environment when it differs from the prompted user credentials. diff --git a/roles/configuration/tasks/bootloader.yml b/roles/configuration/tasks/bootloader.yml index 41c4413..84364dc 100644 --- a/roles/configuration/tasks/bootloader.yml +++ b/roles/configuration/tasks/bootloader.yml @@ -6,9 +6,12 @@ configuration_use_efibootmgr: "{{ is_rhel | default(false) }}" configuration_efi_dir: >- {{ - "/boot/efi" - if (is_rhel | default(false)) or (os | lower in ["ubuntu", "ubuntu-lts"]) - else "/boot" + partitioning_efi_mountpoint + | default( + "/boot/efi" + if (is_rhel | default(false)) or (os | lower in ["ubuntu", "ubuntu-lts"]) + else "/boot" + ) }} configuration_bootloader_id: >- {{ "ubuntu" if os | lower in ["ubuntu", "ubuntu-lts"] else os }} @@ -57,13 +60,12 @@ configuration_efi_vendor: >- {{ "redhat" if os | lower in ["rhel8", "rhel9", "rhel10"] else os | lower }} configuration_grub_cfg_cmd: >- - {{ '/usr/sbin/grub2-mkconfig -o /boot/efi/EFI/' + configuration_efi_vendor + '/grub.cfg' - if is_rhel | default(false) - else '/usr/sbin/grub-mkconfig -o ' + ( - '/boot/efi/EFI/ubuntu/grub.cfg' - if os | lower in ["ubuntu", "ubuntu-lts"] - else '/boot/grub/grub.cfg' - ) + {{ + '/usr/sbin/grub2-mkconfig -o ' + + (partitioning_efi_mountpoint | default('/boot/efi')) + + '/EFI/' + configuration_efi_vendor + '/grub.cfg' + if is_rhel | default(false) + else '/usr/sbin/grub-mkconfig -o /boot/grub/grub.cfg' }} ansible.builtin.command: "arch-chroot /mnt {{ configuration_grub_cfg_cmd }}" register: configuration_grub_result diff --git a/roles/configuration/tasks/encryption.yml b/roles/configuration/tasks/encryption.yml index 59aa257..50879c5 100644 --- a/roles/configuration/tasks/encryption.yml +++ b/roles/configuration/tasks/encryption.yml @@ -17,7 +17,11 @@ partitioning_luks_device | default( install_drive - ~ (partitioning_main_partition_suffix | default(2) | string) + ~ ( + partitioning_root_partition_suffix + | default(partitioning_main_partition_suffix | default(2)) + | string + ) ) }} configuration_luks_tpm2_pcrs_raw: >- diff --git a/roles/configuration/tasks/grub.yml b/roles/configuration/tasks/grub.yml index 400ac0c..c4fb4d0 100644 --- a/roles/configuration/tasks/grub.yml +++ b/roles/configuration/tasks/grub.yml @@ -106,8 +106,7 @@ label: "{{ item.path }}" - name: Enable GRUB cryptodisk for encrypted /boot - when: - - partitioning_luks_enabled | default(luks_enabled | default(false)) | bool + when: partitioning_grub_enable_cryptodisk | default(false) | bool ansible.builtin.lineinfile: path: /mnt/etc/default/grub regexp: '^GRUB_ENABLE_CRYPTODISK=' diff --git a/roles/partitioning/defaults/main.yml b/roles/partitioning/defaults/main.yml new file mode 100644 index 0000000..59f02f9 --- /dev/null +++ b/roles/partitioning/defaults/main.yml @@ -0,0 +1,119 @@ +--- +partitioning_luks_enabled: "{{ luks_enabled | default(false) | bool }}" +partitioning_luks_mapper_name: "{{ luks_mapper_name | default('SYSTEM_DECRYPTED') }}" +partitioning_luks_type: "{{ luks_type | default('luks2') }}" +partitioning_luks_cipher: "{{ luks_cipher | default('aes-xts-plain64') }}" +partitioning_luks_hash: "{{ luks_hash | default('sha512') }}" +partitioning_luks_iter_time: "{{ luks_iter_time | default(4000) }}" +partitioning_luks_key_size: "{{ luks_key_size | default(512) }}" +partitioning_luks_pbkdf: "{{ luks_pbkdf | default('argon2id') }}" +partitioning_luks_use_urandom: "{{ luks_use_urandom | default(true) | bool }}" +partitioning_luks_verify_passphrase: "{{ luks_verify_passphrase | default(true) | bool }}" +partitioning_luks_auto_decrypt: "{{ luks_auto_decrypt | default(true) | bool }}" +partitioning_luks_auto_decrypt_method: "{{ luks_auto_decrypt_method | default('tpm2') }}" +partitioning_luks_tpm2_device: "{{ luks_tpm2_device | default('auto') }}" +partitioning_luks_tpm2_pcrs: "{{ luks_tpm2_pcrs | default('') }}" +partitioning_luks_keyfile_size: "{{ luks_keyfile_size | default(64) }}" +partitioning_luks_options: "{{ luks_options | default('discard,tries=3') }}" +partitioning_boot_partition_suffix: 1 +partitioning_main_partition_suffix: 2 +partitioning_efi_size_mib: 50 +partitioning_boot_size_mib: 1024 +partitioning_separate_boot: >- + {{ + (partitioning_luks_enabled | bool) + and (os | default('') | lower not in ['archlinux']) + }} +partitioning_boot_fs_fstype: >- + {{ + (filesystem | default('') | lower) + if (filesystem | default('') | lower) != 'btrfs' + else ('xfs' if (is_rhel | default(false)) else 'ext4') + }} +partitioning_boot_fs_partition_suffix: >- + {{ + ((partitioning_boot_partition_suffix | int) + 1) + if (partitioning_separate_boot | bool) else '' + }} +partitioning_root_partition_suffix: >- + {{ + (partitioning_main_partition_suffix | int) + + (1 if (partitioning_separate_boot | bool) else 0) + }} +partitioning_efi_mountpoint: >- + {{ + '/boot/efi' + if (partitioning_separate_boot | bool) + else ( + '/boot/efi' + if (is_rhel | default(false)) or (os | default('') | lower in ['ubuntu', 'ubuntu-lts']) + else '/boot' + ) + }} +partitioning_boot_end_mib: "{{ (partitioning_efi_size_mib | int) + (partitioning_boot_size_mib | int) }}" +partitioning_reserved_gb: >- + {{ + ( + (partitioning_efi_size_mib | float) + + ((partitioning_boot_size_mib | float) if (partitioning_separate_boot | bool) else 0) + ) / 1024 + }} +partitioning_layout: >- + {{ + [ + { + 'number': 1, + 'part_end': (partitioning_efi_size_mib | string) + 'MiB', + 'name': 'efi', + 'flags': ['boot', 'esp'] + }, + { + 'number': 2, + 'part_start': (partitioning_efi_size_mib | string) + 'MiB', + 'part_end': (partitioning_boot_end_mib | string) + 'MiB', + 'name': 'boot' + }, + { + 'number': 3, + 'part_start': (partitioning_boot_end_mib | string) + 'MiB', + 'name': 'root' + } + ] + if partitioning_separate_boot | bool else + [ + { + 'number': 1, + 'part_end': (partitioning_efi_size_mib | string) + 'MiB', + 'name': 'boot', + 'flags': ['boot', 'esp'] + }, + { + 'number': 2, + 'part_start': (partitioning_efi_size_mib | string) + 'MiB', + 'name': 'root' + } + ] + }} +partitioning_grub_enable_cryptodisk: >- + {{ + (partitioning_luks_enabled | bool) + and not (partitioning_separate_boot | bool) + and (partitioning_efi_mountpoint == '/boot/efi') + }} +partitioning_luks_device: "{{ install_drive ~ (partitioning_root_partition_suffix | string) }}" +partitioning_root_device: >- + {{ + '/dev/mapper/' + partitioning_luks_mapper_name + if (partitioning_luks_enabled | bool) + else install_drive ~ (partitioning_root_partition_suffix | string) + }} +partitioning_vm_size_effective: "{{ (partitioning_vm_size | default(vm_size | default(0))) | float }}" +partitioning_vm_memory_effective: "{{ (partitioning_vm_memory | default(vm_memory | default(0))) | float }}" +partitioning_swap_size_gb: >- + {{ + ((partitioning_vm_memory_effective / 1024) >= 16.0) + | ternary( + (partitioning_vm_memory_effective / 2048) | int, + [partitioning_vm_memory_effective / 1024, 4.0] | max | int + ) + }} diff --git a/roles/partitioning/tasks/main.yml b/roles/partitioning/tasks/main.yml index e15da57..3758a54 100644 --- a/roles/partitioning/tasks/main.yml +++ b/roles/partitioning/tasks/main.yml @@ -1,46 +1,8 @@ --- -- name: Set partitioning defaults - ansible.builtin.set_fact: - partitioning_luks_enabled: "{{ partitioning_luks_enabled | default(luks_enabled | default(false)) | bool }}" - partitioning_luks_mapper_name: "{{ partitioning_luks_mapper_name | default(luks_mapper_name | default('SYSTEM_DECRYPTED')) }}" - partitioning_luks_type: "{{ partitioning_luks_type | default(luks_type | default('luks2')) }}" - partitioning_luks_cipher: "{{ partitioning_luks_cipher | default(luks_cipher | default('aes-xts-plain64')) }}" - partitioning_luks_hash: "{{ partitioning_luks_hash | default(luks_hash | default('sha512')) }}" - partitioning_luks_iter_time: "{{ partitioning_luks_iter_time | default(luks_iter_time | default(4000)) }}" - partitioning_luks_key_size: "{{ partitioning_luks_key_size | default(luks_key_size | default(512)) }}" - partitioning_luks_pbkdf: "{{ partitioning_luks_pbkdf | default(luks_pbkdf | default('argon2id')) }}" - partitioning_luks_use_urandom: "{{ partitioning_luks_use_urandom | default(luks_use_urandom | default(true)) | bool }}" - partitioning_luks_verify_passphrase: "{{ partitioning_luks_verify_passphrase | default(luks_verify_passphrase | default(true)) | bool }}" - partitioning_luks_auto_decrypt: "{{ partitioning_luks_auto_decrypt | default(luks_auto_decrypt | default(true)) | bool }}" - partitioning_luks_auto_decrypt_method: "{{ partitioning_luks_auto_decrypt_method | default(luks_auto_decrypt_method | default('tpm2')) }}" - partitioning_luks_tpm2_device: "{{ partitioning_luks_tpm2_device | default(luks_tpm2_device | default('auto')) }}" - partitioning_luks_tpm2_pcrs: "{{ partitioning_luks_tpm2_pcrs | default(luks_tpm2_pcrs | default('')) }}" - partitioning_luks_keyfile_size: "{{ partitioning_luks_keyfile_size | default(luks_keyfile_size | default(64)) }}" - partitioning_luks_options: "{{ partitioning_luks_options | default(luks_options | default('discard,tries=3')) }}" - partitioning_luks_device: >- - {{ install_drive ~ (partitioning_main_partition_suffix | string) }} - -- name: Set partitioning root device - ansible.builtin.set_fact: - partitioning_root_device: >- - {{ - '/dev/mapper/' + partitioning_luks_mapper_name - if partitioning_luks_enabled | bool - else install_drive ~ (partitioning_main_partition_suffix | string) - }} - -- name: Set partitioning vm_size from input - when: vm_size is defined or partitioning_vm_size is defined - ansible.builtin.set_fact: - partitioning_vm_size: "{{ (partitioning_vm_size | default(vm_size)) | float }}" - -- name: Set partitioning vm memory from input - when: vm_memory is defined or partitioning_vm_memory is defined - ansible.builtin.set_fact: - partitioning_vm_memory: "{{ (partitioning_vm_memory | default(vm_memory)) | float }}" - - name: Detect system memory for swap sizing - when: partitioning_vm_memory is not defined + when: + - partitioning_vm_memory is not defined + - vm_memory is not defined block: - name: Read system memory ansible.builtin.command: awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo @@ -52,21 +14,11 @@ ansible.builtin.set_fact: partitioning_vm_memory: "{{ (partitioning_memtotal_mb.stdout | default('4096') | int) | float }}" -- name: Calculate swap size - ansible.builtin.set_fact: - partitioning_swap_size_gb: >- - {{ - ((partitioning_vm_memory | float / 1024) >= 16.0) - | ternary( - (partitioning_vm_memory | float / 2048) | int, - [partitioning_vm_memory | float / 1024, 4.0] | max | int - ) - }} - - name: Set partitioning vm_size for physical installs when: - install_type == "physical" - partitioning_vm_size is not defined + - vm_size is not defined - install_drive is defined block: - name: Detect install drive size @@ -168,9 +120,7 @@ name: "{{ item.name }}" flags: "{{ item.flags | default(omit) }}" state: present - loop: - - {number: 1, part_end: 500MiB, name: boot, flags: [boot, esp]} - - {number: 2, part_start: 500MiB, name: root} + loop: "{{ partitioning_layout }}" rescue: - name: Refresh kernel partition table after failure ansible.builtin.command: "{{ item }}" @@ -192,9 +142,7 @@ name: "{{ item.name }}" flags: "{{ item.flags | default(omit) }}" state: present - loop: - - {number: 1, part_end: 500MiB, name: boot, flags: [boot, esp]} - - {number: 2, part_start: 500MiB, name: root} + loop: "{{ partitioning_layout }}" - name: Settle partition table ansible.builtin.command: "{{ item }}" @@ -318,28 +266,28 @@ loop: - lv: root size: >- - {{ [(((((partitioning_vm_size | float) - 0.5 - ((cis | bool) | ternary(7.5, 0)) - (((partitioning_vm_memory | float / 1024) > 16.0) - | ternary(((partitioning_vm_memory | float / 2048) | int), (partitioning_vm_memory | float / 1024)))) < 4) - | ternary(4,((((partitioning_vm_size | float) - 0.5 - ((cis | bool) | ternary(7.5, 0)) - - (((partitioning_vm_memory | float / 1024) > 16.0) + {{ [(((((partitioning_vm_size_effective | float) - (partitioning_reserved_gb | float) - ((cis | bool) | ternary(7.5, 0)) - (((partitioning_vm_memory_effective | float / 1024) > 16.0) + | ternary(((partitioning_vm_memory_effective | float / 2048) | int), (partitioning_vm_memory_effective | float / 1024)))) < 4) + | ternary(4,((((partitioning_vm_size_effective | float) - (partitioning_reserved_gb | float) - ((cis | bool) | ternary(7.5, 0)) - + (((partitioning_vm_memory_effective | float / 1024) > 16.0) | ternary( - ((partitioning_vm_memory | float / 2048) | int), - (partitioning_vm_memory | float / 1024) + ((partitioning_vm_memory_effective | float / 2048) | int), + (partitioning_vm_memory_effective | float / 1024) ))) > 12) - | ternary(((partitioning_vm_size | float) * 0.4) | round(0, 'ceil'),((partitioning_vm_size | float) - 0.5 - ((cis | bool) - | ternary(7.5, 0)) - (((partitioning_vm_memory | float / 1024) > 16.0) - | ternary(((partitioning_vm_memory | float / 2048) | int), (partitioning_vm_memory | float / 1024))))))))), 4 ] | max | string + + | ternary(((partitioning_vm_size_effective | float) * 0.4) | round(0, 'ceil'),((partitioning_vm_size_effective | float) - (partitioning_reserved_gb | float) - ((cis | bool) + | ternary(7.5, 0)) - (((partitioning_vm_memory_effective | float / 1024) > 16.0) + | ternary(((partitioning_vm_memory_effective | float / 2048) | int), (partitioning_vm_memory_effective | float / 1024))))))))), 4 ] | max | string + 'G' }} - lv: swap size: >- - {{ ((((partitioning_vm_size | float) - 0.5 - ((cis | bool) | ternary(7.5, 0))) - (((partitioning_vm_memory | float / 1024) > 16.0) - | ternary(((partitioning_vm_memory | float / 2048) | int), (partitioning_vm_memory | float / 1024)))) < 4) - | ternary((((partitioning_vm_size | float) - 0.5 - ((cis | bool) | ternary(7.5, 0))) - 4), (((partitioning_vm_memory | float / 1024) + {{ ((((partitioning_vm_size_effective | float) - (partitioning_reserved_gb | float) - ((cis | bool) | ternary(7.5, 0))) - (((partitioning_vm_memory_effective | float / 1024) > 16.0) + | ternary(((partitioning_vm_memory_effective | float / 2048) | int), (partitioning_vm_memory_effective | float / 1024)))) < 4) + | ternary((((partitioning_vm_size_effective | float) - (partitioning_reserved_gb | float) - ((cis | bool) | ternary(7.5, 0))) - 4), (((partitioning_vm_memory_effective | float / 1024) > 16.0) - | ternary(((partitioning_vm_memory | float / 2048) | int), (partitioning_vm_memory | float / 1024)))) | string + 'G' }} + | ternary(((partitioning_vm_memory_effective | float / 2048) | int), (partitioning_vm_memory_effective | float / 1024)))) | string + 'G' }} - lv: home - size: "{{ ([([(((partitioning_vm_size | float) - 20) * 0.1), 2] | max), 20] | min) | string + 'G' }}" + size: "{{ ([([(((partitioning_vm_size_effective | float) - 20) * 0.1), 2] | max), 20] | min) | string + 'G' }}" - {lv: var, size: "2G"} - {lv: var_log, size: "2G"} - {lv: var_log_audit, size: "1.5G"} @@ -353,6 +301,24 @@ opts: -F32 -n BOOT force: true + - name: Create filesystem for /boot partition + when: partitioning_separate_boot | bool + community.general.filesystem: + dev: "{{ install_drive }}{{ partitioning_boot_fs_partition_suffix }}" + fstype: "{{ partitioning_boot_fs_fstype }}" + force: true + + - name: Remove unsupported ext4 features from /boot + when: + - partitioning_separate_boot | bool + - partitioning_boot_fs_fstype == 'ext4' + - os | lower in ['almalinux', 'debian11', 'rhel8', 'rhel9', 'rocky'] + ansible.builtin.command: >- + tune2fs -O "^orphan_file,^metadata_csum_seed" + "{{ install_drive }}{{ partitioning_boot_fs_partition_suffix }}" + register: partitioning_boot_ext4_tune_result + changed_when: partitioning_boot_ext4_tune_result.rc == 0 + - name: Create swap filesystem when: filesystem != 'btrfs' community.general.filesystem: @@ -367,6 +333,13 @@ register: partitioning_boot_uuid changed_when: false + - name: Get UUID for /boot filesystem + when: partitioning_separate_boot | bool + ansible.builtin.command: >- + blkid -s UUID -o value '{{ install_drive }}{{ partitioning_boot_fs_partition_suffix }}' + register: partitioning_boot_fs_uuid + changed_when: false + - name: Get UUID for main filesystem ansible.builtin.command: blkid -s UUID -o value '{{ partitioning_root_device }}' register: partitioning_main_uuid @@ -490,14 +463,18 @@ ] | join(',') }} + - name: Mount /boot filesystem + when: partitioning_separate_boot | bool + ansible.posix.mount: + path: /mnt/boot + src: "UUID={{ partitioning_boot_fs_uuid.stdout }}" + fstype: "{{ partitioning_boot_fs_fstype }}" + opts: defaults + state: mounted + - name: Mount boot filesystem ansible.posix.mount: - path: >- - {{ - '/mnt/boot/efi' - if (is_rhel | default(false)) or (os | lower in ['ubuntu', 'ubuntu-lts']) - else '/mnt/boot' - }} + path: "/mnt{{ partitioning_efi_mountpoint }}" src: UUID={{ partitioning_boot_uuid.stdout }} fstype: vfat state: mounted