--- # 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([]) }}" system_first_user: >- {{ system_users_input[0] if (system_users_input is iterable and system_users_input is not string and system_users_input is not mapping and system_users_input | length > 0) 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_user: name: >- {{ system_first_user.name | string if (system_first_user.name | default('') | string | length) > 0 else prompt_user_name }} keys: >- {{ system_first_user['keys'] if (system_first_user['keys'] is defined and system_first_user['keys'] is iterable and system_first_user['keys'] is not string and system_first_user['keys'] | length > 0) else ( [prompt_user_key] if (prompt_user_key | length > 0) else [] ) }} password: >- {{ system_first_user.password | string if (system_first_user.password | default('') | string | length) > 0 else prompt_user_password }} ansible.builtin.set_fact: system: >- {{ system_input | combine( { 'users': ( [resolved_user] + (system_users_input[1:] if (system_users_input is sequence and system_users_input is not string and system_users_input | length > 1) else []) ), '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 vars: ansible_become: false 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 ansible.builtin.set_fact: ansible_user: "{{ system_cfg.users[0].name }}" ansible_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_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