Compare commits

...

6 Commits

23 changed files with 667 additions and 131 deletions

View File

@@ -257,7 +257,15 @@ Users must be defined in inventory. The dict format enables additive merging acr
| Key | Type | Default | Description | | Key | Type | Default | Description |
| -------- | ------------- | ------- | ---------------------------------------------- | | -------- | ------------- | ------- | ---------------------------------------------- |
| `device` | string | `auto` | TPM2 device selector | | `device` | string | `auto` | TPM2 device selector |
| `pcrs` | string/list | -- | PCR binding policy (e.g. `"7"` or `"0+7"`) | | `pcrs` | string/list | -- | PCR binding policy (e.g. `"7"` or `"0+7"`); empty = no PCR binding |
**TPM2 auto-unlock:** Uses `systemd-cryptenroll` on all distros. The user-set passphrase
remains as a backup unlock method. TPM2 enrollment runs in the chroot during bootstrap;
if it fails (e.g. no TPM2 hardware), the system boots with passphrase-only unlock and
TPM2 can be enrolled post-deployment via `systemd-cryptenroll --tpm2-device=auto <device>`.
On Debian/Ubuntu, TPM2 auto-unlock requires dracut (initramfs-tools does not support `tpm2-device`).
The bootstrap auto-switches to dracut when `method: tpm2` is set. Override via `features.initramfs.generator`.
#### `system.features` #### `system.features`
@@ -274,6 +282,26 @@ Users must be defined in inventory. The dict format enables additive merging acr
| `banner.motd` | bool | `false` | MOTD banner | | `banner.motd` | bool | `false` | MOTD banner |
| `banner.sudo` | bool | `true` | Sudo banner | | `banner.sudo` | bool | `true` | Sudo banner |
| `chroot.tool` | string | `arch-chroot` | `arch-chroot`, `chroot`, or `systemd-nspawn` | | `chroot.tool` | string | `arch-chroot` | `arch-chroot`, `chroot`, or `systemd-nspawn` |
| `initramfs.generator` | string | auto-detected | Override initramfs generator (see below) |
| `desktop.*` | dict | see below | Desktop environment settings (see [4.2.5](#425-systemfeaturesdesktop)) |
**Initramfs generator auto-detection:** RedHat → dracut, Arch → mkinitcpio, Debian/Ubuntu → initramfs-tools.
Override with `dracut`, `mkinitcpio`, or `initramfs-tools`. When LUKS TPM2 auto-unlock is enabled and the
native generator does not support `tpm2-device`, the generator is automatically upgraded to dracut.
On distros with older dracut (no `tpm2-tss` module), clevis is used as a fallback for TPM2 binding.
#### 4.2.5 `system.features.desktop`
| Key | Type | Default | Description |
| ----------------- | ------ | -------------- | ----------------------------------------- |
| `enabled` | bool | `false` | Install desktop environment |
| `environment` | string | -- | `gnome`, `kde`, `xfce`, `sway`, `hyprland`, `cinnamon`, `mate`, `lxqt`, `budgie` |
| `display_manager` | string | auto-detected | Override DM: `gdm`, `sddm`, `lightdm`, `ly`, `greetd` |
When `enabled: true`, the bootstrap installs the desktop environment packages, enables the display manager
and bluetooth services, and sets the systemd default target to `graphical.target`.
Display manager auto-detection: gnome→gdm, kde→sddm, xfce→lightdm, sway→greetd, hyprland→ly.
### 4.3 `hypervisor` Dictionary ### 4.3 `hypervisor` Dictionary

View File

@@ -3,3 +3,6 @@ hash_behaviour = merge
interpreter_python = auto_silent interpreter_python = auto_silent
deprecation_warnings = False deprecation_warnings = False
host_key_checking = False host_key_checking = False
[ssh_connection]
ssh_args = -C -o ControlMaster=auto -o ControlPersist=600s -o ServerAliveInterval=30 -o ServerAliveCountMax=10

View File

@@ -0,0 +1,48 @@
---
- name: Load desktop package definitions
ansible.builtin.include_vars:
file: desktop.yml
- name: Resolve desktop packages
vars:
_de: "{{ system_cfg.features.desktop.environment }}"
_family_pkgs: "{{ bootstrap_desktop_packages[os_family] | default({}) }}"
_de_config: "{{ _family_pkgs[_de] | default({}) }}"
ansible.builtin.set_fact:
_desktop_groups: "{{ _de_config.groups | default([]) }}"
_desktop_packages: "{{ _de_config.packages | default([]) }}"
- name: Validate desktop environment is supported
ansible.builtin.assert:
that:
- (_desktop_groups | length > 0) or (_desktop_packages | length > 0)
fail_msg: >-
Desktop environment '{{ system_cfg.features.desktop.environment }}'
is not defined for os_family '{{ os_family }}'.
Supported: {{ (bootstrap_desktop_packages[os_family] | default({})).keys() | join(', ') }}
quiet: true
- name: Install desktop package groups
when: _desktop_groups | length > 0
ansible.builtin.command: >-
{{ chroot_command }} dnf --releasever={{ os_version }}
--setopt=install_weak_deps=False group install -y {{ _desktop_groups | join(' ') }}
register: _desktop_group_result
changed_when: _desktop_group_result.rc == 0
- name: Install desktop packages
when: _desktop_packages | length > 0
vars:
_install_commands:
RedHat: >-
{{ chroot_command }} dnf --releasever={{ os_version }}
--setopt=install_weak_deps=False install -y {{ _desktop_packages | join(' ') }}
Debian: >-
{{ chroot_command }} apt install -y {{ _desktop_packages | join(' ') }}
Archlinux: >-
pacstrap /mnt {{ _desktop_packages | join(' ') }}
Suse: >-
{{ chroot_command }} zypper install -y {{ _desktop_packages | join(' ') }}
ansible.builtin.command: "{{ _install_commands[os_family] }}"
register: _desktop_pkg_result
changed_when: _desktop_pkg_result.rc == 0

View File

@@ -18,6 +18,9 @@
groupinstall -y {{ _dnf_groups }} groupinstall -y {{ _dnf_groups }}
register: bootstrap_dnf_base_result register: bootstrap_dnf_base_result
changed_when: bootstrap_dnf_base_result.rc == 0 changed_when: bootstrap_dnf_base_result.rc == 0
failed_when:
- bootstrap_dnf_base_result.rc != 0
- "'scriptlet' not in bootstrap_dnf_base_result.stderr"
- name: Ensure chroot has DNS resolution - name: Ensure chroot has DNS resolution
ansible.builtin.file: ansible.builtin.file:

View File

@@ -34,6 +34,10 @@
bootstrap_var_key: "{{ 'bootstrap_' + (os | replace('-lts', '') | replace('-', '_')) }}" bootstrap_var_key: "{{ 'bootstrap_' + (os | replace('-lts', '') | replace('-', '_')) }}"
ansible.builtin.include_tasks: "{{ bootstrap_os_task_map[os] }}" ansible.builtin.include_tasks: "{{ bootstrap_os_task_map[os] }}"
- name: Install desktop environment packages
when: system_cfg.features.desktop.enabled | bool
ansible.builtin.include_tasks: _desktop.yml
- name: Ensure chroot uses live environment DNS - name: Ensure chroot uses live environment DNS
ansible.builtin.file: ansible.builtin.file:
src: /run/NetworkManager/resolv.conf src: /run/NetworkManager/resolv.conf

View File

@@ -0,0 +1,149 @@
---
# Per-family desktop environment package definitions.
# Keyed by os_family -> environment -> groups (dnf groupinstall) / packages.
# Kept intentionally minimal: base DE + essential tools, no full suites.
bootstrap_desktop_packages:
RedHat:
gnome:
groups:
- workstation-product-environment
packages: []
kde:
groups: []
packages:
- plasma-desktop
- plasma-nm
- plasma-pa
- plasma-systemmonitor
- sddm
- konsole
- dolphin
- kate
- kscreen
- kde-gtk-config
- xdg-user-dirs
- xdg-desktop-portal-kde
- bluez
- pipewire
- wireplumber
xfce:
groups:
- xfce-desktop-environment
packages:
- lightdm
Debian:
gnome:
groups: []
packages:
- gnome-core
- gdm3
- gnome-tweaks
- xdg-user-dirs
kde:
groups: []
packages:
- plasma-desktop
- plasma-nm
- plasma-pa
- sddm
- konsole
- dolphin
- kate
- kscreen
- xdg-user-dirs
- xdg-desktop-portal-kde
- bluez
- pipewire
- wireplumber
xfce:
groups: []
packages:
- xfce4
- xfce4-goodies
- lightdm
- xdg-user-dirs
Archlinux:
gnome:
groups: []
packages:
- gnome
- gdm
- xdg-user-dirs
kde:
groups: []
packages:
- plasma-desktop
- plasma-nm
- plasma-pa
- sddm
- konsole
- dolphin
- kate
- kscreen
- kde-gtk-config
- xdg-user-dirs
- xdg-desktop-portal-kde
- bluez
- pipewire
- wireplumber
xfce:
groups: []
packages:
- xfce4
- xfce4-goodies
- lightdm
- xdg-user-dirs
sway:
groups: []
packages:
- sway
- waybar
- foot
- wofi
- greetd
- xdg-user-dirs
- xdg-desktop-portal-wlr
- bluez
- pipewire
- wireplumber
hyprland:
groups: []
packages:
- hyprland
- kitty
- wofi
- waybar
- ly
- xdg-user-dirs
- xdg-desktop-portal-hyprland
- polkit-kde-agent
- qt5-wayland
- qt6-wayland
- bluez
- pipewire
- wireplumber
Suse:
gnome:
groups: []
packages:
- patterns-gnome-gnome_basic
- gdm
- xdg-user-dirs
kde:
groups: []
packages:
- patterns-kde-kde_plasma
- sddm
- xdg-user-dirs
# Display manager auto-detection from desktop environment.
bootstrap_desktop_dm_map:
gnome: gdm
kde: sddm
xfce: lightdm
sway: greetd
hyprland: ly@tty2
cinnamon: lightdm
mate: lightdm
lxqt: sddm
budgie: gdm

View File

@@ -72,6 +72,12 @@
| trim | trim
}} }}
- name: Ensure boot device is set to hard disk in VM XML
when: "'<boot ' not in cleanup_libvirt_domain_xml_clean"
ansible.builtin.set_fact:
cleanup_libvirt_domain_xml_clean: >-
{{ cleanup_libvirt_domain_xml_clean | regex_replace('(</type>)', '\1\n <boot dev="hd"/>') }}
- name: Update VM definition without installer media - name: Update VM definition without installer media
community.libvirt.virt: community.libvirt.virt:
command: define command: define

View File

@@ -34,6 +34,16 @@
register: configuration_efi_entry_result register: configuration_efi_entry_result
changed_when: configuration_efi_entry_result.rc == 0 changed_when: configuration_efi_entry_result.rc == 0
- name: Set installed OS as first EFI boot entry
ansible.builtin.shell:
cmd: >-
set -o pipefail &&
efibootmgr | grep -i '{{ _efi_vendor }}' | grep -oP 'Boot\K[0-9A-F]+' | head -1
| xargs -I{} efibootmgr -o {}
executable: /bin/bash
register: _efi_bootorder_result
changed_when: _efi_bootorder_result.rc == 0
- name: Ensure lvm2 for non btrfs filesystems - name: Ensure lvm2 for non btrfs filesystems
when: os == "archlinux" and system_cfg.filesystem != "btrfs" when: os == "archlinux" and system_cfg.filesystem != "btrfs"
ansible.builtin.lineinfile: ansible.builtin.lineinfile:
@@ -48,14 +58,48 @@
register: configuration_initramfs_result register: configuration_initramfs_result
changed_when: configuration_initramfs_result.rc == 0 changed_when: configuration_initramfs_result.rc == 0
- name: Generate grub config - name: Generate grub config (RedHat)
vars: when: os_family == 'RedHat'
configuration_grub_cfg_cmd: >- ansible.builtin.command: >-
{{ {{ chroot_command }} /usr/sbin/{{ _configuration_platform.grub_mkconfig_prefix }}
'/usr/sbin/' + _configuration_platform.grub_mkconfig_prefix + ' -o /boot/grub2/grub.cfg' -o /boot/grub2/grub.cfg
if os_family == 'RedHat' register: configuration_grub_result
else '/usr/sbin/grub-mkconfig -o /boot/grub/grub.cfg' changed_when: configuration_grub_result.rc == 0
}}
ansible.builtin.command: "{{ chroot_command }} {{ configuration_grub_cfg_cmd }}" - name: Fix btrfs BLS boot variable in grub config
when:
- os_family == 'RedHat'
- system_cfg.filesystem == 'btrfs'
ansible.builtin.replace:
path: /mnt/boot/grub2/grub.cfg
regexp: 'search --no-floppy --fs-uuid --set=boot \S+'
replace: 'set boot=$root'
- name: Create EFI grub.cfg wrapper for RedHat
when: os_family == 'RedHat'
vars:
_grub2_path: >-
{{
'/grub2'
if (partitioning_separate_boot | bool)
else ('/@/boot/grub2' if system_cfg.filesystem == 'btrfs' else '/boot/grub2')
}}
ansible.builtin.shell:
cmd: |
set -o pipefail
uuid=$(grep -m1 'search.*--set=root' /mnt/boot/grub2/grub.cfg | grep -oP '[\da-f]{8}(-[\da-f]{4}){3}-[\da-f]{12}')
cat > /mnt{{ partitioning_efi_mountpoint }}/EFI/{{ _efi_vendor }}/grub.cfg <<GRUBEOF
search --no-floppy --fs-uuid --set=dev $uuid
set prefix=(\$dev){{ _grub2_path }}
export \$prefix
configfile \$prefix/grub.cfg
GRUBEOF
executable: /bin/bash
register: _grub_wrapper_result
changed_when: _grub_wrapper_result.rc == 0
- name: Generate grub config (non-RedHat)
when: os_family != 'RedHat'
ansible.builtin.command: "{{ chroot_command }} /usr/sbin/grub-mkconfig -o /boot/grub/grub.cfg"
register: configuration_grub_result register: configuration_grub_result
changed_when: configuration_grub_result.rc == 0 changed_when: configuration_grub_result.rc == 0

View File

@@ -36,6 +36,12 @@
configuration_luks_tpm2_device: "{{ system_cfg.luks.tpm2.device }}" configuration_luks_tpm2_device: "{{ system_cfg.luks.tpm2.device }}"
configuration_luks_tpm2_pcrs: "{{ luks_tpm2_pcrs }}" configuration_luks_tpm2_pcrs: "{{ luks_tpm2_pcrs }}"
configuration_luks_keyfile_path: "/etc/cryptsetup-keys.d/{{ system_cfg.luks.mapper }}.key" configuration_luks_keyfile_path: "/etc/cryptsetup-keys.d/{{ system_cfg.luks.mapper }}.key"
configuration_luks_tpm2_token_lib: >-
{{
'/usr/lib/x86_64-linux-gnu/cryptsetup/libcryptsetup-token-systemd-tpm2.so'
if os_family == 'Debian'
else '/usr/lib64/cryptsetup/libcryptsetup-token-systemd-tpm2.so'
}}
- name: Validate LUKS UUID is available - name: Validate LUKS UUID is available
ansible.builtin.assert: ansible.builtin.assert:
@@ -51,8 +57,13 @@
fail_msg: system.luks.passphrase must be set for LUKS auto-decrypt. fail_msg: system.luks.passphrase must be set for LUKS auto-decrypt.
no_log: true no_log: true
- name: Enroll TPM2 for LUKS - name: Detect TPM2 unlock method
when: configuration_luks_auto_method == 'tpm2' ansible.builtin.include_tasks: encryption/initramfs_detect.yml
- name: Enroll TPM2 via systemd-cryptenroll
when:
- configuration_luks_auto_method == 'tpm2'
- _tpm2_method | default('systemd-cryptenroll') == 'systemd-cryptenroll'
ansible.builtin.include_tasks: encryption/tpm2.yml ansible.builtin.include_tasks: encryption/tpm2.yml
- name: Configure LUKS keyfile auto-decrypt - name: Configure LUKS keyfile auto-decrypt
@@ -78,7 +89,7 @@
}} }}
luks_tpm2_option_list: >- luks_tpm2_option_list: >-
{{ {{
(configuration_luks_auto_method == 'tpm2') (configuration_luks_auto_method == 'tpm2' and (_tpm2_method | default('systemd-cryptenroll')) == 'systemd-cryptenroll')
| ternary( | ternary(
['tpm2-device=' + configuration_luks_tpm2_device] ['tpm2-device=' + configuration_luks_tpm2_device]
+ (['tpm2-pcrs=' + configuration_luks_tpm2_pcrs] + (['tpm2-pcrs=' + configuration_luks_tpm2_pcrs]
@@ -122,16 +133,16 @@
path: /mnt{{ configuration_luks_keyfile_path }} path: /mnt{{ configuration_luks_keyfile_path }}
state: absent state: absent
- name: Configure initramfs for LUKS
ansible.builtin.include_tasks: encryption/initramfs.yml
- name: Configure crypttab - name: Configure crypttab
ansible.builtin.include_tasks: encryption/crypttab.yml ansible.builtin.include_tasks: encryption/crypttab.yml
- name: Configure initramfs - name: Configure dracut for LUKS
ansible.builtin.include_tasks: encryption/initramfs.yml when: _initramfs_generator | default('') == 'dracut'
- name: Configure dracut
when: os_family == 'RedHat'
ansible.builtin.include_tasks: encryption/dracut.yml ansible.builtin.include_tasks: encryption/dracut.yml
- name: Configure GRUB for LUKS - name: Configure GRUB for LUKS
when: not os_family == 'RedHat' when: _initramfs_generator | default('') != 'dracut' or os_family != 'RedHat'
ansible.builtin.include_tasks: encryption/grub.yml ansible.builtin.include_tasks: encryption/grub.yml

View File

@@ -9,48 +9,58 @@
ansible.builtin.copy: ansible.builtin.copy:
dest: /mnt/etc/dracut.conf.d/crypt.conf dest: /mnt/etc/dracut.conf.d/crypt.conf
content: | content: |
add_dracutmodules+=" crypt " add_dracutmodules+=" crypt systemd "
{% if configuration_luks_keyfile_in_use %} {% if configuration_luks_keyfile_in_use | default(false) %}
install_items+=" {{ configuration_luks_keyfile_path }} " install_items+=" {{ configuration_luks_keyfile_path }} "
{% endif %} {% endif %}
{% if configuration_luks_auto_method == 'tpm2' %}
install_items+=" {{ configuration_luks_tpm2_token_lib | default('') }} "
{% endif %}
mode: "0644" mode: "0644"
- name: Read kernel cmdline defaults # --- Kernel cmdline: write rd.luks.* args for dracut ---
- name: Ensure kernel cmdline directory exists
ansible.builtin.file:
path: /mnt/etc/kernel
state: directory
mode: "0755"
- name: Read existing kernel cmdline
ansible.builtin.slurp: ansible.builtin.slurp:
src: /mnt/etc/kernel/cmdline src: /mnt/etc/kernel/cmdline
register: configuration_kernel_cmdline_slurp register: _kernel_cmdline_slurp
failed_when: false
- name: Build kernel cmdline with LUKS args - name: Build kernel cmdline with LUKS args
vars: vars:
kernel_cmdline_current: >- _cmdline_current: >-
{{ configuration_kernel_cmdline_slurp.content | b64decode | trim }} {{ (_kernel_cmdline_slurp.content | default('') | b64decode | default('')) | trim }}
kernel_cmdline_list: >- _cmdline_list: >-
{{ _cmdline_current.split() if _cmdline_current | length > 0 else [] }}
_cmdline_filtered: >-
{{ {{
kernel_cmdline_current.split() _cmdline_list
if kernel_cmdline_current | length > 0 else []
}}
kernel_cmdline_filtered: >-
{{
kernel_cmdline_list
| reject('match', '^rd\\.luks\\.(name|options|key)=' ~ configuration_luks_uuid ~ '=') | reject('match', '^rd\\.luks\\.(name|options|key)=' ~ configuration_luks_uuid ~ '=')
| list | list
}} }}
kernel_cmdline_new: >- _cmdline_new: >-
{{ {{
(kernel_cmdline_filtered + configuration_luks_kernel_args.split()) (_cmdline_filtered + configuration_luks_kernel_args.split())
| unique | unique
| join(' ') | join(' ')
}} }}
ansible.builtin.set_fact: ansible.builtin.set_fact:
configuration_kernel_cmdline_new: "{{ kernel_cmdline_new }}" _dracut_kernel_cmdline: "{{ _cmdline_new }}"
- name: Write kernel cmdline with LUKS args - name: Write kernel cmdline with LUKS args
ansible.builtin.copy: ansible.builtin.copy:
dest: /mnt/etc/kernel/cmdline dest: /mnt/etc/kernel/cmdline
mode: "0644" mode: "0644"
content: "{{ configuration_kernel_cmdline_new }}\n" content: "{{ _dracut_kernel_cmdline }}\n"
# --- BLS entries: RedHat-specific ---
- name: Update BLS entries with LUKS kernel cmdline - name: Update BLS entries with LUKS kernel cmdline
when: os_family == 'RedHat'
vars: vars:
_bls_cmdline: "{{ configuration_kernel_cmdline_new }}" _bls_cmdline: "{{ _dracut_kernel_cmdline }}"
ansible.builtin.include_tasks: ../_bls_update.yml ansible.builtin.include_tasks: ../_bls_update.yml

View File

@@ -1,8 +1,94 @@
--- ---
- name: Ensure keyfile pattern for initramfs-tools # Initramfs configuration for LUKS auto-unlock.
# Runs AFTER Build LUKS parameters (so configuration_luks_keyfile_in_use is set).
# _initramfs_generator and _tpm2_method are set by initramfs_detect.yml.
# --- clevis: install and bind TPM2 ---
- name: Install clevis in target system
when: when:
- os_family == 'Debian' - configuration_luks_auto_method == 'tpm2'
- configuration_luks_keyfile_in_use - _tpm2_method | default('') == 'clevis'
ansible.builtin.command: >-
{{ chroot_command }} apt install -y clevis clevis-luks clevis-tpm2 clevis-initramfs tpm2-tools
register: _clevis_install_result
changed_when: _clevis_install_result.rc == 0
- name: Install clevis on installer for LUKS binding
when:
- configuration_luks_auto_method == 'tpm2'
- _tpm2_method | default('') == 'clevis'
community.general.pacman:
name:
- clevis
- tpm2-tools
state: present
retries: 3
delay: 5
- name: Create clevis passphrase file
when:
- configuration_luks_auto_method == 'tpm2'
- _tpm2_method | default('') == 'clevis'
ansible.builtin.copy:
dest: /mnt/root/.luks-enroll-key
content: "{{ configuration_luks_passphrase }}"
mode: "0600"
no_log: true
- name: Ensure TPM device accessible for clevis
when:
- configuration_luks_auto_method == 'tpm2'
- _tpm2_method | default('') == 'clevis'
ansible.builtin.shell: >-
ls /mnt/dev/tpmrm0 2>/dev/null
|| (ls /dev/tpmrm0 && cp -a /dev/tpmrm0 /mnt/dev/tpmrm0)
changed_when: false
failed_when: false
- name: Bind LUKS to TPM2 via clevis
when:
- configuration_luks_auto_method == 'tpm2'
- _tpm2_method | default('') == 'clevis'
vars:
_clevis_config: >-
{{
'{"pcr_ids":"' + configuration_luks_tpm2_pcrs + '"}'
if configuration_luks_tpm2_pcrs | length > 0
else '{}'
}}
ansible.builtin.command: >-
clevis luks bind -f -k /mnt/root/.luks-enroll-key
-d {{ configuration_luks_device }} tpm2 '{{ _clevis_config }}'
register: _clevis_bind_result
changed_when: _clevis_bind_result.rc == 0
failed_when: false
# Initramfs regeneration is handled by the bootloader task which runs after
# encryption configuration. Clevis hooks are included automatically by
# update-initramfs when clevis-initramfs is installed.
- name: Remove clevis passphrase file
when:
- configuration_luks_auto_method == 'tpm2'
- _tpm2_method | default('') == 'clevis'
ansible.builtin.file:
path: /mnt/root/.luks-enroll-key
state: absent
- name: Report clevis binding result
when:
- configuration_luks_auto_method == 'tpm2'
- _tpm2_method | default('') == 'clevis'
ansible.builtin.debug:
msg: >-
{{ 'Clevis TPM2 binding succeeded' if (_clevis_bind_result.rc | default(1)) == 0
else 'Clevis TPM2 binding failed: ' + (_clevis_bind_result.stderr | default('unknown')) + '. System will require passphrase at boot.' }}
# --- initramfs-tools: keyfile support (non-TPM2) ---
- name: Configure initramfs-tools keyfile pattern
when:
- _initramfs_generator | default('') == 'initramfs-tools'
- configuration_luks_keyfile_in_use | default(false) | bool
ansible.builtin.lineinfile: ansible.builtin.lineinfile:
path: /mnt/etc/cryptsetup-initramfs/conf-hook path: /mnt/etc/cryptsetup-initramfs/conf-hook
regexp: "^KEYFILE_PATTERN=" regexp: "^KEYFILE_PATTERN="
@@ -10,8 +96,9 @@
create: true create: true
mode: "0644" mode: "0644"
# --- mkinitcpio: systemd + sd-encrypt hooks ---
- name: Configure mkinitcpio hooks for LUKS - name: Configure mkinitcpio hooks for LUKS
when: os == 'archlinux' when: _initramfs_generator | default('') == 'mkinitcpio'
ansible.builtin.lineinfile: ansible.builtin.lineinfile:
path: /mnt/etc/mkinitcpio.conf path: /mnt/etc/mkinitcpio.conf
regexp: "^HOOKS=" regexp: "^HOOKS="
@@ -20,13 +107,13 @@
block sd-encrypt{{ ' lvm2' if system_cfg.filesystem != 'btrfs' else '' }} filesystems fsck) block sd-encrypt{{ ' lvm2' if system_cfg.filesystem != 'btrfs' else '' }} filesystems fsck)
- name: Read mkinitcpio configuration - name: Read mkinitcpio configuration
when: os == 'archlinux' when: _initramfs_generator | default('') == 'mkinitcpio'
ansible.builtin.slurp: ansible.builtin.slurp:
src: /mnt/etc/mkinitcpio.conf src: /mnt/etc/mkinitcpio.conf
register: configuration_mkinitcpio_slurp register: configuration_mkinitcpio_slurp
- name: Build mkinitcpio FILES list - name: Build mkinitcpio FILES list
when: os == 'archlinux' when: _initramfs_generator | default('') == 'mkinitcpio'
vars: vars:
mkinitcpio_files_list: >- mkinitcpio_files_list: >-
{{ {{
@@ -42,7 +129,7 @@
{{ {{
( (
(mkinitcpio_files_list + [configuration_luks_keyfile_path]) (mkinitcpio_files_list + [configuration_luks_keyfile_path])
if configuration_luks_keyfile_in_use if (configuration_luks_keyfile_in_use | default(false))
else ( else (
mkinitcpio_files_list mkinitcpio_files_list
| reject('equalto', configuration_luks_keyfile_path) | reject('equalto', configuration_luks_keyfile_path)
@@ -55,7 +142,7 @@
configuration_mkinitcpio_files_list_new: "{{ mkinitcpio_files_list_new }}" configuration_mkinitcpio_files_list_new: "{{ mkinitcpio_files_list_new }}"
- name: Configure mkinitcpio FILES list - name: Configure mkinitcpio FILES list
when: os == 'archlinux' when: _initramfs_generator | default('') == 'mkinitcpio'
ansible.builtin.lineinfile: ansible.builtin.lineinfile:
path: /mnt/etc/mkinitcpio.conf path: /mnt/etc/mkinitcpio.conf
regexp: "^FILES=" regexp: "^FILES="

View File

@@ -0,0 +1,98 @@
---
# Resolve initramfs generator and TPM2 unlock method.
# Sets _initramfs_generator and _tpm2_method facts.
#
# Generator detection: derived from the platform's initramfs_cmd
# (dracut → dracut, mkinitcpio → mkinitcpio, else → initramfs-tools)
# TPM2 method: systemd-cryptenroll when generator supports tpm2-device,
# clevis fallback otherwise. Non-native dracut installed automatically.
- name: Resolve initramfs generator
vars:
_user_generator: "{{ system_cfg.features.initramfs.generator | default('') }}"
_native_generator: >-
{{
'dracut' if _configuration_platform.initramfs_cmd is search('dracut')
else ('mkinitcpio' if _configuration_platform.initramfs_cmd is search('mkinitcpio')
else 'initramfs-tools')
}}
ansible.builtin.set_fact:
_initramfs_generator: >-
{{ _user_generator if _user_generator | length > 0 else _native_generator }}
_initramfs_native_generator: "{{ _native_generator }}"
# --- Install non-native dracut if overridden or needed ---
- name: Install dracut in chroot when not native
when:
- _initramfs_generator == 'dracut'
- _initramfs_native_generator != 'dracut'
ansible.builtin.shell: >-
{{ chroot_command }} sh -c '
command -v apt >/dev/null 2>&1 && apt install -y dracut ||
command -v pacman >/dev/null 2>&1 && pacman -S --noconfirm dracut ||
command -v dnf >/dev/null 2>&1 && dnf install -y dracut
'
register: _dracut_install_result
changed_when: _dracut_install_result.rc == 0
failed_when: false
- name: Override initramfs command to dracut
when:
- _initramfs_generator == 'dracut'
- _initramfs_native_generator != 'dracut'
vars:
# Generate dracut initramfs with output name matching what GRUB expects:
# mkinitcpio native: /boot/initramfs-linux.img (Arch convention)
# initramfs-tools native: /boot/initrd.img-<kver> (Debian convention)
_dracut_cmd: >-
{{
'bash -c "for kver in /lib/modules/*/; do kver=$(basename $kver); dracut --force /boot/initramfs-linux.img $kver; done"'
if _initramfs_native_generator == 'mkinitcpio'
else 'bash -c "for kver in /lib/modules/*/; do kver=$(basename $kver); dracut --force /boot/initrd.img-$kver $kver; done"'
}}
ansible.builtin.set_fact:
_configuration_platform: >-
{{ _configuration_platform | combine({'initramfs_cmd': _dracut_cmd}) }}
# --- TPM2 method detection ---
- name: Probe dracut for TPM2 module support
when:
- configuration_luks_auto_method == 'tpm2'
- _initramfs_generator != 'mkinitcpio'
ansible.builtin.command: "{{ chroot_command }} dracut --list-modules"
register: _dracut_modules_check
changed_when: false
failed_when: false
- name: Resolve TPM2 unlock method
when: configuration_luks_auto_method == 'tpm2'
vars:
# mkinitcpio sd-encrypt supports tpm2-device natively
# dracut with tpm2-tss module supports tpm2-device natively
# everything else needs clevis
_supports_tpm2_native: >-
{{
_initramfs_generator == 'mkinitcpio'
or ('tpm2-tss' in (_dracut_modules_check.stdout | default('')))
}}
ansible.builtin.set_fact:
_tpm2_method: "{{ 'systemd-cryptenroll' if _supports_tpm2_native | bool else 'clevis' }}"
# --- Auto-upgrade to dracut when tpm2-tss available but generator isn't dracut ---
- name: Switch to dracut for TPM2 support
when:
- configuration_luks_auto_method == 'tpm2'
- _tpm2_method == 'systemd-cryptenroll'
- _initramfs_generator not in ['dracut', 'mkinitcpio']
vars:
_dracut_cmd: >-
bash -c "for kver in /lib/modules/*/; do kver=$(basename $kver); dracut --force /boot/initrd.img-$kver $kver; done"
ansible.builtin.set_fact:
_initramfs_generator: dracut
_configuration_platform: >-
{{ _configuration_platform | combine({'initramfs_cmd': _dracut_cmd}) }}
- name: Report TPM2 configuration
when: configuration_luks_auto_method == 'tpm2'
ansible.builtin.debug:
msg: "TPM2 unlock: {{ _tpm2_method | default('none') }} | initramfs: {{ _initramfs_generator }}"

View File

@@ -1,26 +1,35 @@
--- ---
# TPM2 enrollment via systemd-cryptenroll.
# Works with dracut and mkinitcpio (sd-encrypt). The user-set passphrase
# remains as a backup unlock method — no auto-generated keyfiles.
- name: Enroll TPM2 for LUKS - name: Enroll TPM2 for LUKS
block: block:
# Tempfile in chroot /tmp — accessible by both chroot and host commands
- name: Create temporary passphrase file for TPM2 enrollment - name: Create temporary passphrase file for TPM2 enrollment
ansible.builtin.tempfile: ansible.builtin.tempfile:
path: /mnt/tmp path: /mnt/root
prefix: luks-passphrase- prefix: luks-passphrase-
state: file state: file
register: configuration_luks_tpm2_passphrase_tempfile register: _tpm2_passphrase_tempfile
- name: Write passphrase into temporary file for TPM2 enrollment - name: Write passphrase into temporary file
ansible.builtin.copy: ansible.builtin.copy:
dest: "{{ configuration_luks_tpm2_passphrase_tempfile.path }}" dest: "{{ _tpm2_passphrase_tempfile.path }}"
content: "{{ configuration_luks_passphrase }}" content: "{{ configuration_luks_passphrase }}"
owner: root owner: root
group: root group: root
mode: "0600" mode: "0600"
no_log: true no_log: true
- name: Enroll TPM2 token - name: Ensure TPM device is accessible in chroot
ansible.builtin.shell: >-
ls /mnt/dev/tpmrm0 2>/dev/null
|| (ls /dev/tpmrm0 && cp -a /dev/tpmrm0 /mnt/dev/tpmrm0)
changed_when: false
failed_when: false
- name: Enroll TPM2 token via systemd-cryptenroll
vars: vars:
configuration_luks_enroll_args: >- _enroll_args: >-
{{ {{
[ [
'/usr/bin/systemd-cryptenroll', '/usr/bin/systemd-cryptenroll',
@@ -28,69 +37,28 @@
'--tpm2-with-pin=false', '--tpm2-with-pin=false',
'--wipe-slot=tpm2', '--wipe-slot=tpm2',
'--unlock-key-file=' + ( '--unlock-key-file=' + (
configuration_luks_tpm2_passphrase_tempfile.path _tpm2_passphrase_tempfile.path | regex_replace('^/mnt', '')
| regex_replace('^/mnt', '')
) )
] ]
+ (['--tpm2-pcrs=' + configuration_luks_tpm2_pcrs] + (['--tpm2-pcrs=' + configuration_luks_tpm2_pcrs]
if configuration_luks_tpm2_pcrs | length > 0 else []) if configuration_luks_tpm2_pcrs | length > 0 else [])
+ [configuration_luks_device] + [configuration_luks_device]
}} }}
configuration_luks_enroll_chroot_cmd: >- ansible.builtin.command: "{{ chroot_command }} {{ _enroll_args | join(' ') }}"
{{ chroot_command }} {{ configuration_luks_enroll_args | join(' ') }} register: _tpm2_enroll_result
ansible.builtin.command: "{{ configuration_luks_enroll_chroot_cmd }}" changed_when: _tpm2_enroll_result.rc == 0
register: configuration_luks_tpm2_enroll_chroot
changed_when: configuration_luks_tpm2_enroll_chroot.rc == 0
failed_when: false
- name: Retry TPM2 enrollment in installer environment
when:
- (configuration_luks_tpm2_enroll_chroot.rc | default(1)) != 0
vars:
configuration_luks_enroll_args: >-
{{
[
'/usr/bin/systemd-cryptenroll',
'--tpm2-device=' + configuration_luks_tpm2_device,
'--tpm2-with-pin=false',
'--wipe-slot=tpm2',
'--unlock-key-file=' + configuration_luks_tpm2_passphrase_tempfile.path
]
+ (['--tpm2-pcrs=' + configuration_luks_tpm2_pcrs]
if configuration_luks_tpm2_pcrs | length > 0 else [])
+ [configuration_luks_device]
}}
ansible.builtin.command:
argv: "{{ configuration_luks_enroll_args }}"
register: configuration_luks_tpm2_enroll_host
changed_when: configuration_luks_tpm2_enroll_host.rc == 0
failed_when: false
- name: Validate TPM2 enrollment succeeded
ansible.builtin.assert:
that:
- >-
(configuration_luks_tpm2_enroll_chroot.rc | default(1)) == 0
or (configuration_luks_tpm2_enroll_host.rc | default(1)) == 0
fail_msg: >-
TPM2 enrollment failed.
chroot rc={{ configuration_luks_tpm2_enroll_chroot.rc | default('n/a') }},
host rc={{ configuration_luks_tpm2_enroll_host.rc | default('n/a') }},
chroot stderr={{ configuration_luks_tpm2_enroll_chroot.stderr | default('') }},
host stderr={{ configuration_luks_tpm2_enroll_host.stderr | default('') }}
rescue: rescue:
- name: Warn about TPM2 enrollment failure - name: TPM2 enrollment failed
ansible.builtin.debug: ansible.builtin.debug:
msg: >- msg: >-
WARNING: TPM2 enrollment failed — falling back to keyfile auto-decrypt. TPM2 enrollment failed: {{ _tpm2_enroll_result.stderr | default('unknown') }}.
The system will use a keyfile instead of TPM2 for automatic LUKS unlock. The system will require the passphrase for LUKS unlock on boot.
TPM2 can be enrolled post-deployment via: systemd-cryptenroll --tpm2-device=auto {{ configuration_luks_device }}
- name: Fallback to keyfile auto-decrypt
ansible.builtin.set_fact:
configuration_luks_auto_method: keyfile
always: always:
- name: Remove TPM2 enrollment passphrase file - name: Remove temporary passphrase file
when: configuration_luks_tpm2_passphrase_tempfile.path is defined when: _tpm2_passphrase_tempfile.path is defined
ansible.builtin.file: ansible.builtin.file:
path: "{{ configuration_luks_tpm2_passphrase_tempfile.path }}" path: "{{ _tpm2_passphrase_tempfile.path }}"
state: absent state: absent

View File

@@ -2,6 +2,12 @@
- name: Enable systemd services - name: Enable systemd services
when: _configuration_platform.init_system == 'systemd' when: _configuration_platform.init_system == 'systemd'
vars: vars:
_desktop_dm: >-
{{
system_cfg.features.desktop.display_manager
if (system_cfg.features.desktop.display_manager | length > 0)
else (configuration_desktop_dm_map[system_cfg.features.desktop.environment] | default(''))
}}
configuration_systemd_services: >- configuration_systemd_services: >-
{{ {{
['NetworkManager'] ['NetworkManager']
@@ -9,12 +15,31 @@
+ (['ufw'] if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else []) + (['ufw'] if system_cfg.features.firewall.backend == 'ufw' and system_cfg.features.firewall.enabled | bool else [])
+ ([_configuration_platform.ssh_service] if system_cfg.features.ssh.enabled | bool else []) + ([_configuration_platform.ssh_service] if system_cfg.features.ssh.enabled | bool else [])
+ (['logrotate', 'systemd-timesyncd'] if os == 'archlinux' else []) + (['logrotate', 'systemd-timesyncd'] if os == 'archlinux' else [])
+ ([_desktop_dm] if system_cfg.features.desktop.enabled | bool and _desktop_dm | length > 0 else [])
+ (['bluetooth'] if system_cfg.features.desktop.enabled | bool else [])
}} }}
ansible.builtin.command: "{{ chroot_command }} systemctl enable {{ item }}" ansible.builtin.command: "{{ chroot_command }} systemctl enable {{ item }}"
loop: "{{ configuration_systemd_services }}" loop: "{{ configuration_systemd_services }}"
register: configuration_enable_service_result register: configuration_enable_service_result
changed_when: configuration_enable_service_result.rc == 0 changed_when: configuration_enable_service_result.rc == 0
- name: Activate UFW firewall
when:
- system_cfg.features.firewall.backend == 'ufw'
- system_cfg.features.firewall.enabled | bool
ansible.builtin.command: "{{ chroot_command }} ufw --force enable"
register: _ufw_enable_result
changed_when: _ufw_enable_result.rc == 0
failed_when: false
- name: Set default systemd target to graphical
when:
- _configuration_platform.init_system == 'systemd'
- system_cfg.features.desktop.enabled | bool
ansible.builtin.command: "{{ chroot_command }} systemctl set-default graphical.target"
register: _desktop_target_result
changed_when: _desktop_target_result.rc == 0
- name: Enable OpenRC services - name: Enable OpenRC services
when: _configuration_platform.init_system == 'openrc' when: _configuration_platform.init_system == 'openrc'
vars: vars:

View File

@@ -65,3 +65,15 @@ configuration_platform_config:
grub_mkconfig_prefix: grub-mkconfig grub_mkconfig_prefix: grub-mkconfig
locale_gen: false locale_gen: false
init_system: runit init_system: runit
# Display manager auto-detection from desktop environment name.
configuration_desktop_dm_map:
gnome: gdm
kde: sddm
xfce: lightdm
sway: greetd
hyprland: ly@tty2
cinnamon: lightdm
mate: lightdm
lxqt: sddm
budgie: gdm

View File

@@ -68,6 +68,23 @@
Boot from a live installer (Arch, Debian, Ubuntu, etc.) and retry. Boot from a live installer (Arch, Debian, Ubuntu, etc.) and retry.
quiet: true quiet: true
- name: Harden sshd for Ansible automation
ansible.builtin.blockinfile:
path: /etc/ssh/sshd_config
marker: "# {mark} BOOTSTRAP ANSIBLE SETTINGS"
block: |
PerSourcePenalties no
MaxStartups 50:30:100
ClientAliveInterval 30
ClientAliveCountMax 10
register: _sshd_config_result
- name: Restart sshd immediately if config was changed
when: _sshd_config_result is changed
ansible.builtin.service:
name: sshd
state: restarted
- name: Abort if the host is not booted from the Arch install media - name: Abort if the host is not booted from the Arch install media
when: when:
- not (custom_iso | bool) - not (custom_iso | bool)

View File

@@ -25,6 +25,7 @@
state: latest state: latest
loop: loop:
- { name: glibc } - { name: glibc }
- { name: lua, os: [almalinux, fedora, rhel, rocky] }
- { name: dnf, os: [almalinux, fedora, rhel, rocky] } - { name: dnf, os: [almalinux, fedora, rhel, rocky] }
- { name: debootstrap, os: [debian, ubuntu, ubuntu-lts] } - { name: debootstrap, os: [debian, ubuntu, ubuntu-lts] }
- { name: debian-archive-keyring, os: [debian] } - { name: debian-archive-keyring, os: [debian] }

View File

@@ -135,6 +135,12 @@ system_defaults:
user: "_aur_builder" user: "_aur_builder"
chroot: chroot:
tool: "arch-chroot" # arch-chroot|chroot|systemd-nspawn tool: "arch-chroot" # arch-chroot|chroot|systemd-nspawn
initramfs:
generator: "" # auto-detected; override: dracut|mkinitcpio|initramfs-tools
desktop:
enabled: false
environment: "" # gnome|kde|xfce|sway|hyprland|cinnamon|mate|lxqt|budgie
display_manager: "" # auto from environment when empty; override: gdm|sddm|lightdm|greetd
# Per-hypervisor required fields — drives data-driven validation. # Per-hypervisor required fields — drives data-driven validation.
# All virtual types additionally require network bridge or interfaces. # All virtual types additionally require network bridge or interfaces.

View File

@@ -144,27 +144,13 @@
url: "{{ system_raw.features.rhel_repo.url | default('') | string }}" url: "{{ system_raw.features.rhel_repo.url | default('') | string }}"
chroot: chroot:
tool: "{{ system_raw.features.chroot.tool | string }}" tool: "{{ system_raw.features.chroot.tool | string }}"
initramfs:
generator: "{{ system_raw.features.initramfs.generator | default('') | string | lower }}"
desktop:
enabled: "{{ system_raw.features.desktop.enabled | bool }}"
environment: "{{ system_raw.features.desktop.environment | default('') | string | lower }}"
display_manager: "{{ system_raw.features.desktop.display_manager | default('') | string | lower }}"
hostname: "{{ system_name }}" hostname: "{{ system_name }}"
os: "{{ system_os_input if system_os_input | length > 0 else (physical_default_os if system_type == 'physical' else '') }}" os: "{{ system_os_input if system_os_input | length > 0 else (physical_default_os if system_type == 'physical' else '') }}"
os_version: "{{ system_raw.version | default('') | string }}" os_version: "{{ system_raw.version | default('') | string }}"
no_log: true 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

@@ -17,6 +17,27 @@
- name: Normalize disk configuration - name: Normalize disk configuration
ansible.builtin.include_tasks: _normalize_disks.yml ansible.builtin.include_tasks: _normalize_disks.yml
- name: Populate primary network fields from first interface
when:
- system_cfg is defined
- system_cfg.network.interfaces | default([]) | length > 0
- system_cfg.network.ip | 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: Check if pre-computed system_cfg needs enrichment - name: Check if pre-computed system_cfg needs enrichment
when: system_cfg is defined when: system_cfg is defined
ansible.builtin.set_fact: ansible.builtin.set_fact:

View File

@@ -9,12 +9,13 @@
- >- - >-
system_cfg.features.cis.enabled | bool or ( system_cfg.features.cis.enabled | bool or (
not (system_cfg.features.cis.enabled | bool) and ( not (system_cfg.features.cis.enabled | bool) and (
(system_cfg.filesystem == 'btrfs' and item.path in ['/home', '/var/log', '/var/cache/pacman/pkg']) (system_cfg.filesystem == 'btrfs' and item.path in ['/home', '/var/log']
+ (['/var/cache/pacman/pkg'] if os == 'archlinux' else []))
or (item.path not in ['/home', '/var', '/var/log', '/var/log/audit', '/var/cache/pacman/pkg']) or (item.path not in ['/home', '/var', '/var/log', '/var/log/audit', '/var/cache/pacman/pkg'])
) )
) )
- >- - >-
not (item.path in ['/swap', '/var/cache/pacman/pkg'] and system_cfg.filesystem != 'btrfs') not (item.path in ['/swap', '/var/cache/pacman/pkg'] and (system_cfg.filesystem != 'btrfs' or os != 'archlinux'))
- system_cfg.features.swap.enabled | bool or item.path != '/swap' - system_cfg.features.swap.enabled | bool or item.path != '/swap'
ansible.posix.mount: ansible.posix.mount:
path: /mnt{{ item.path }} path: /mnt{{ item.path }}

View File

@@ -43,6 +43,7 @@
when: when:
- system_cfg.features.cis.enabled | bool or item.subvol not in ['var_log_audit'] - system_cfg.features.cis.enabled | bool or item.subvol not in ['var_log_audit']
- system_cfg.features.swap.enabled | bool or item.subvol != 'swap' - system_cfg.features.swap.enabled | bool or item.subvol != 'swap'
- item.os is not defined or os in item.os
ansible.builtin.command: btrfs su cr /mnt/{{ '@' if item.subvol == 'root' else '@' + item.subvol }} ansible.builtin.command: btrfs su cr /mnt/{{ '@' if item.subvol == 'root' else '@' + item.subvol }}
args: args:
creates: /mnt/{{ '@' if item.subvol == 'root' else '@' + item.subvol }} creates: /mnt/{{ '@' if item.subvol == 'root' else '@' + item.subvol }}
@@ -51,12 +52,19 @@
- { subvol: swap } - { subvol: swap }
- { subvol: home } - { subvol: home }
- { subvol: var } - { subvol: var }
- { subvol: pkg } - { subvol: pkg, os: [archlinux] }
- { subvol: var_log } - { subvol: var_log }
- { subvol: var_log_audit } - { subvol: var_log_audit }
loop_control: loop_control:
label: "{{ item.subvol }}" label: "{{ item.subvol }}"
- name: Set default btrfs subvolume to @
ansible.builtin.shell: >-
btrfs subvolume list /mnt | awk '/ path @$/ {print $2}'
| xargs -I{} btrfs subvolume set-default {} /mnt
register: partitioning_btrfs_default_result
changed_when: partitioning_btrfs_default_result.rc == 0
- name: Set quotas for subvolumes - name: Set quotas for subvolumes
when: system_cfg.features.cis.enabled | bool when: system_cfg.features.cis.enabled | bool
ansible.builtin.command: btrfs qgroup limit {{ item.quota }} /mnt/{{ '@' if item.subvol == 'root' else '@' + item.subvol }} ansible.builtin.command: btrfs qgroup limit {{ item.quota }} /mnt/{{ '@' if item.subvol == 'root' else '@' + item.subvol }}

View File

@@ -40,10 +40,10 @@
failed_when: false failed_when: false
- name: Undefine libvirt VM - name: Undefine libvirt VM
community.libvirt.virt: ansible.builtin.command:
name: "{{ hostname }}" cmd: "virsh -c {{ libvirt_uri | default('qemu:///system') }} undefine {{ hostname }} --nvram"
command: undefine register: _libvirt_undefine_result
uri: "{{ libvirt_uri | default('qemu:///system') }}" changed_when: _libvirt_undefine_result.rc == 0
failed_when: false failed_when: false
- name: Remove libvirt disk images - name: Remove libvirt disk images