Compare commits

...

7 Commits

Author SHA1 Message Date
09b3ed44ba fix(bootstrap): RHEL 9 bootstrap from Arch ISO compatibility
- Generate resolv.conf from inventory DNS settings instead of copying
  host file (Arch ISO has systemd-resolved stub 127.0.0.53)
- Add XFS compat options for GRUB 2.06 and kernel 5.14 across LVM
  volumes, /boot partition, and data disks
- Mount API filesystems (proc, sys, dev) into chroot for RPM scriptlets
- Bypass GPG Sequoia validation with _pkgverify_level none
- Tolerate grub2-common scriptlet warnings
- Handle libvirt VM destroy gracefully during cleanup
2026-02-20 16:58:59 +01:00
603abe63cb refactor: make bootstrap host target configurable 2026-02-20 16:58:59 +01:00
1c0e6533ae fix(ubuntu): add initramfs-tools to debootstrap base packages 2026-02-20 16:58:59 +01:00
00aa614cfd fix(bootstrap): use explicit keyring for debootstrap and copy resolv.conf 2026-02-20 16:58:59 +01:00
4905d10bc0 fix(cloud-init): handle boolean sudo values in user-data template 2026-02-20 16:58:59 +01:00
b4e8ccb77f fix: re-gather facts after reboot to detect target OS package manager
The live ISO (Arch) caches ansible_pkg_mgr=pacman. After rebooting
into the target OS (e.g. Debian), package module fails because pacman
is not available. Re-gather minimal facts including pkg_mgr.
2026-02-20 16:58:59 +01:00
2a82ee4d5c fix: resolve Jinja2 .keys ambiguity, fastfetch availability, and python interpreter
- Use bracket notation item['keys'] instead of item.keys to avoid
  conflict with Python dict .keys() method
- Remove fastfetch from Debian 12 package list (only available in 13+)
- Set explicit python interpreter path for post-reboot tasks
2026-02-20 16:58:58 +01:00
13 changed files with 76 additions and 15 deletions

View File

@@ -1,6 +1,6 @@
--- ---
- name: Create and configure VMs - name: Create and configure VMs
hosts: all hosts: "{{ bootstrap_target | default('all') }}"
strategy: free # noqa: run-once[play] strategy: free # noqa: run-once[play]
gather_facts: false gather_facts: false
become: true become: true
@@ -152,6 +152,16 @@
ansible_password: "{{ system_cfg.users[0].password }}" ansible_password: "{{ system_cfg.users[0].password }}"
ansible_become_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" ansible_ssh_extra_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
ansible_python_interpreter: /usr/bin/python3
- name: Re-gather facts for target OS after reboot
when:
- post_reboot_can_connect | bool
ansible.builtin.setup:
gather_subset:
- "!all"
- min
- pkg_mgr
- name: Install post-reboot packages - name: Install post-reboot packages
when: when:

View File

@@ -1,4 +1,28 @@
--- ---
- name: Create API filesystem mountpoints in installroot
when: is_rhel | bool
ansible.builtin.file:
path: "/mnt/{{ item }}"
state: directory
mode: "0755"
loop:
- dev
- proc
- sys
- name: Mount API filesystems into installroot
when: is_rhel | bool
ansible.posix.mount:
src: "{{ item.src }}"
path: "/mnt/{{ item.path }}"
fstype: "{{ item.fstype }}"
opts: "{{ item.opts | default(omit) }}"
state: ephemeral
loop:
- { src: proc, path: proc, fstype: proc }
- { src: sysfs, path: sys, fstype: sysfs }
- { src: /dev, path: dev, fstype: none, opts: bind }
- name: Run OS-specific bootstrap process - name: Run OS-specific bootstrap process
vars: vars:
bootstrap_os_task_map: bootstrap_os_task_map:

View File

@@ -9,12 +9,21 @@
groupinstall -y core base standard groupinstall -y core base standard
register: bootstrap_result register: bootstrap_result
changed_when: bootstrap_result.rc == 0 changed_when: bootstrap_result.rc == 0
failed_when:
- bootstrap_result.rc != 0
- "'grub2-common' not in (bootstrap_result.stderr | default(''))"
- name: Ensure chroot has resolv.conf - name: Write resolv.conf into chroot
ansible.builtin.file: ansible.builtin.copy:
src: /run/NetworkManager/resolv.conf
dest: /mnt/etc/resolv.conf dest: /mnt/etc/resolv.conf
state: link 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 - name: Ensure chroot RHEL DVD directory exists
ansible.builtin.file: ansible.builtin.file:

View File

@@ -39,17 +39,20 @@
- name: Install Ubuntu base system - name: Install Ubuntu base system
ansible.builtin.command: >- ansible.builtin.command: >-
debootstrap --include={{ bootstrap_ubuntu_base_csv }} debootstrap
--keyring=/usr/share/keyrings/ubuntu-archive-keyring.gpg
--include={{ bootstrap_ubuntu_base_csv }}
{{ bootstrap_ubuntu_release }} /mnt {{ bootstrap_ubuntu_release }} /mnt
http://archive.ubuntu.com/ubuntu/ http://archive.ubuntu.com/ubuntu/
register: bootstrap_ubuntu_base_result register: bootstrap_ubuntu_base_result
changed_when: bootstrap_ubuntu_base_result.rc == 0 changed_when: bootstrap_ubuntu_base_result.rc == 0
- name: Ensure chroot has resolv.conf - name: Ensure chroot has resolv.conf
ansible.builtin.file: ansible.builtin.copy:
src: /run/NetworkManager/resolv.conf src: /etc/resolv.conf
dest: /mnt/etc/resolv.conf dest: /mnt/etc/resolv.conf
state: link remote_src: true
mode: "0644"
- name: Enable universe repository - name: Enable universe repository
ansible.builtin.command: "{{ chroot_command }} sed -i '1s|$| universe|' /etc/apt/sources.list" ansible.builtin.command: "{{ chroot_command }} sed -i '1s|$| universe|' /etc/apt/sources.list"

View File

@@ -104,7 +104,7 @@ bootstrap_debian_extra_common:
bootstrap_debian_extra_versioned: bootstrap_debian_extra_versioned:
- linux-image-amd64 - linux-image-amd64
- "{{ 'duf' if (os_version | string) not in ['10', '11'] else '' }}" - "{{ 'duf' if (os_version | string) not in ['10', '11'] else '' }}"
- "{{ 'fastfetch' if (os_version | string) in ['12', '13', 'unstable'] else '' }}" - "{{ 'fastfetch' if (os_version | string) in ['13', 'unstable'] else '' }}"
- "{{ 'neofetch' if (os_version | string) == '12' else '' }}" - "{{ 'neofetch' if (os_version | string) == '12' else '' }}"
- "{{ 'software-properties-common' if (os_version | string) not in ['13', 'unstable'] 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 '' }}" - "{{ 'systemd-zram-generator' if (os_version | string) not in ['10', '11'] else '' }}"
@@ -121,6 +121,7 @@ bootstrap_debian:
bootstrap_ubuntu: bootstrap_ubuntu:
base: base:
- initramfs-tools
- linux-image-generic - linux-image-generic
extra: >- extra: >-
{{ {{

View File

@@ -92,6 +92,7 @@
community.libvirt.virt: community.libvirt.virt:
name: "{{ hostname }}" name: "{{ hostname }}"
state: destroyed state: destroyed
failed_when: false
- name: Start the VM - name: Start the VM
community.libvirt.virt: community.libvirt.virt:

View File

@@ -26,7 +26,7 @@
changed_when: configuration_user_result.rc == 0 changed_when: configuration_user_result.rc == 0
- name: Ensure .ssh directory exists - name: Ensure .ssh directory exists
when: item.keys | default([]) | length > 0 when: item['keys'] | default([]) | length > 0
ansible.builtin.file: ansible.builtin.file:
path: "/mnt/home/{{ item.name }}/.ssh" path: "/mnt/home/{{ item.name }}/.ssh"
state: directory state: directory

View File

@@ -205,6 +205,13 @@
opts: "ro,loop" opts: "ro,loop"
state: mounted state: mounted
- name: Relax RPM Sequoia signature policy for RHEL bootstrap
when: is_rhel | bool
ansible.builtin.copy:
dest: /etc/rpm/macros
content: "%_pkgverify_level none\n"
mode: "0644"
- name: Configure RHEL Repos for installation - name: Configure RHEL Repos for installation
when: is_rhel | bool when: is_rhel | bool
block: block:

View File

@@ -30,7 +30,7 @@
that: that:
- item is mapping - item is mapping
- item.name is defined and (item.name | string | length) > 0 - 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) - 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." fail_msg: "Each system.users[] entry must be a dict with 'name'; 'keys' must be a list."
quiet: true quiet: true
loop: "{{ system.users }}" loop: "{{ system.users }}"

View File

@@ -51,10 +51,14 @@
- name: Create filesystems on additional disks - name: Create filesystems on additional disks
when: partitioning_extra_disks | length > 0 when: partitioning_extra_disks | length > 0
vars:
_label_opt: "{{ ('-L ' ~ item.mount.label) if (item.mount.label | default('') | string | length) > 0 else '' }}"
_compat_opt: "{{ '-m bigtime=0 -i nrext64=0,exchange=0 -n parent=0' if (is_rhel | bool and item.mount.fstype == 'xfs') else '' }}"
_all_opts: "{{ ([_label_opt, _compat_opt] | select | join(' ')) or omit }}"
community.general.filesystem: community.general.filesystem:
dev: "{{ item.partition }}" dev: "{{ item.partition }}"
fstype: "{{ item.mount.fstype }}" fstype: "{{ item.mount.fstype }}"
opts: "{{ ('-L ' ~ item.mount.label) if (item.mount.label | default('') | string | length) > 0 else omit }}" opts: "{{ _all_opts }}"
force: true force: true
loop: "{{ partitioning_extra_disks }}" loop: "{{ partitioning_extra_disks }}"
loop_control: loop_control:

View File

@@ -418,6 +418,7 @@
community.general.filesystem: community.general.filesystem:
dev: "{{ install_drive }}{{ partitioning_boot_fs_partition_suffix }}" dev: "{{ install_drive }}{{ partitioning_boot_fs_partition_suffix }}"
fstype: "{{ partitioning_boot_fs_fstype }}" fstype: "{{ partitioning_boot_fs_fstype }}"
opts: "{{ '-m bigtime=0 -i nrext64=0,exchange=0 -n parent=0' if (is_rhel | bool and partitioning_boot_fs_fstype == 'xfs') else omit }}"
force: true force: true
- name: Remove unsupported ext4 features from /boot - name: Remove unsupported ext4 features from /boot

View File

@@ -4,6 +4,7 @@
community.general.filesystem: community.general.filesystem:
dev: /dev/sys/{{ item.lv }} dev: /dev/sys/{{ item.lv }}
fstype: xfs fstype: xfs
opts: "{{ '-m bigtime=0 -i nrext64=0,exchange=0 -n parent=0' if is_rhel | bool else omit }}"
force: true force: true
loop: loop:
- { lv: root } - { lv: root }

View File

@@ -8,10 +8,10 @@ users:
- name: "{{ user.name }}" - name: "{{ user.name }}"
primary_group: "{{ user.name }}" primary_group: "{{ user.name }}"
groups: users groups: users
sudo: "{{ user.sudo | default('ALL=(ALL) NOPASSWD:ALL') }}" sudo: "{{ 'ALL=(ALL) NOPASSWD:ALL' if (user.sudo is defined and user.sudo is sameas true) else user.sudo | default('ALL=(ALL) NOPASSWD:ALL') }}"
passwd: "{{ user.password | password_hash('sha512') }}" passwd: "{{ user.password | password_hash('sha512') }}"
lock_passwd: false lock_passwd: false
{% set ssh_keys = user.keys | default([]) %} {% set ssh_keys = user['keys'] | default([]) %}
{% if ssh_keys | length > 0 %} {% if ssh_keys | length > 0 %}
ssh_authorized_keys: ssh_authorized_keys:
{% for key in ssh_keys %} {% for key in ssh_keys %}