--- # Bootstrap pipeline — role execution order: # 1. global_defaults — normalize + validate system/hypervisor/disk input # 2. system_check — pre-flight hardware/environment safety checks # 3. virtualization — create VM on hypervisor (libvirt/proxmox/vmware/xen) # 4. environment — detect live ISO, configure installer network, install tools # 5. partitioning — partition disk, create FS, LUKS, LVM, mount everything # 6. bootstrap — debootstrap/pacstrap/dnf install the target OS into /mnt # 7. configuration — users, network, encryption, fstab, bootloader, services # 8. cis — CIS hardening (optional, per system.features.cis.enabled) # 9. cleanup — unmount, remove cloud-init artifacts, reboot/shutdown - name: Create and configure VMs hosts: "{{ bootstrap_target | default('all') }}" strategy: free # noqa: run-once[play] gather_facts: false become: true vars_prompt: - name: user_name prompt: | What is your username? private: false - name: user_public_key prompt: | What is your ssh key? private: false - name: user_password prompt: | What is your password? confirm: true - name: root_password prompt: | What is your root password? confirm: true pre_tasks: - name: Apply prompted authentication values to system input no_log: true vars: system_input: "{{ system | default({}) }}" system_users_input: "{{ system_input.users | default({}) }}" _first_entry: "{{ system_users_input | dict2items | first | default({'key': '', 'value': {}}) }}" _first_name: "{{ _first_entry.key }}" _first_attrs: "{{ _first_entry.value if _first_entry.value is mapping else {} }}" system_root_input: "{{ (system_input.root | default({})) if (system_input.root is mapping) else {} }}" prompt_user_name: "{{ user_name | default(system_user_name | default(''), true) | string }}" prompt_user_key: "{{ user_public_key | default(user_key | default(system_user_key | default(''), true), true) | string | trim }}" prompt_user_password: "{{ user_password | default(system_user_password | default(''), true) | string }}" prompt_root_password: "{{ root_password | default(system_root_password | default(''), true) | string }}" resolved_name: "{{ _first_name if (_first_name | length > 0) else prompt_user_name }}" resolved_attrs: keys: >- {{ _first_attrs['keys'] if (_first_attrs['keys'] is defined and _first_attrs['keys'] is iterable and _first_attrs['keys'] is not string and _first_attrs['keys'] | length > 0) else ( [prompt_user_key] if (prompt_user_key | length > 0) else [] ) }} password: >- {{ _first_attrs.password | string if (_first_attrs.password | default('') | string | length) > 0 else prompt_user_password }} ansible.builtin.set_fact: system: >- {{ system_input | combine( { 'users': system_users_input | combine({resolved_name: (_first_attrs | combine(resolved_attrs, recursive=True))}), 'root': { 'password': ( (system_root_input.password | default('') | string | length) > 0 ) | ternary(system_root_input.password | string, prompt_root_password) } }, recursive=True ) }} - name: Load global defaults ansible.builtin.import_role: name: global_defaults - name: Perform safety checks ansible.builtin.import_role: name: system_check tasks: - name: Bootstrap pipeline block: - name: Record that no pre-existing VM was found ansible.builtin.set_fact: _vm_absent_before_bootstrap: true - name: Create virtual machine when: system_cfg.type == "virtual" ansible.builtin.include_role: name: virtualization public: true vars: ansible_connection: local ansible_become: false - name: Configure environment ansible.builtin.include_role: name: environment public: true - name: Partition disks ansible.builtin.include_role: name: partitioning public: true vars: partitioning_boot_partition_suffix: 1 partitioning_main_partition_suffix: 2 - name: Install base system ansible.builtin.include_role: name: bootstrap public: true - name: Apply system configuration ansible.builtin.include_role: name: configuration public: true - name: Apply CIS hardening when: system_cfg.features.cis.enabled | bool ansible.builtin.include_role: name: cis public: true - name: Clean up and finalize when: system_cfg.type in ["virtual", "physical"] ansible.builtin.include_role: name: cleanup public: true rescue: - name: Delete VM on bootstrap failure when: - _vm_absent_before_bootstrap | default(false) | bool - virtualization_vm_created_in_run | default(false) | bool - system_cfg.type == "virtual" ansible.builtin.include_role: name: virtualization tasks_from: delete vars: ansible_connection: local ansible_become: false tags: - rescue_cleanup - name: Fail host after bootstrap rescue ansible.builtin.fail: msg: >- Bootstrap failed for {{ hostname }}. {{ 'VM was deleted to allow clean retry.' if (virtualization_vm_created_in_run | default(false)) else 'VM was not created in this run (kept).' }} post_tasks: - name: Set post-reboot connection flags ansible.builtin.set_fact: post_reboot_can_connect: >- {{ (ansible_connection | default('ssh')) != 'ssh' or ((system_cfg.network.ip | default('') | string | length) > 0) or ( system_cfg.type == 'physical' and (ansible_host | default('') | string | length) > 0 ) }} - name: Reset SSH connection before post-reboot tasks when: - post_reboot_can_connect | bool ansible.builtin.meta: reset_connection - name: Set final SSH credentials for post-reboot tasks when: - post_reboot_can_connect | bool no_log: true vars: _primary: "{{ (system_cfg.users | dict2items | selectattr('value.password', 'defined') | first) }}" ansible.builtin.set_fact: ansible_user: "{{ _primary.key }}" ansible_password: "{{ _primary.value.password }}" ansible_become_password: "{{ _primary.value.password }}" 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 when: - post_reboot_can_connect | bool - system_cfg.packages is defined - system_cfg.packages | length > 0 ansible.builtin.package: name: "{{ system_cfg.packages }}" state: present