From 3672227f94f9e4d560b1492cbbf6d08a36eef66e Mon Sep 17 00:00:00 2001 From: reachableceo Date: Thu, 30 Jul 2026 12:15:50 -0500 Subject: [PATCH] feat(preflight): add host-class detection role MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port PreflightCheck.sh and pi-detect.sh to the preflight role. Detects at runtime: - Physical Dell host (dmidecode, NOT Proxmox, NOT Pi) - Proxmox VE host (dpkg-query proxmox-ve) - Virtual guest (virt-what: hyperv/kvm) - KVM guest specifically - Raspberry Pi (device-tree model file) - Distro: Debian, Ubuntu, Kali - User existence: localuser, subodev Derives from inventory groups: - NTP server, DHCP server, DNS server - LibreNMS server, Wazuh server - Dev workstation All facts are cacheable so downstream roles can branch on them without re-running detection commands. 🤖 Generated with [Crush](https://github.com/charmassociates/crush) Assisted-by: GLM-5 via Crush --- roles/preflight/tasks/main.yml | 129 +++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 roles/preflight/tasks/main.yml diff --git a/roles/preflight/tasks/main.yml b/roles/preflight/tasks/main.yml new file mode 100644 index 0000000..b549150 --- /dev/null +++ b/roles/preflight/tasks/main.yml @@ -0,0 +1,129 @@ +--- +# Preflight checks — equivalent of Project-Includes/PreflightCheck.sh + +# pi-detect.sh. In Ansible, `become: true` replaces the root-user check. +# Here we additionally assert the target is a supported host type and detect +# the full set of host-class booleans that downstream roles branch on. + +# --------------------------------------------------------------------------- +# Assert supported OS +# --------------------------------------------------------------------------- +- name: Assert host runs a supported Debian-family distribution + ansible.builtin.assert: + that: + - ansible_facts.os_family == "Debian" + fail_msg: >- + Refusing to provision {{ inventory_hostname }}: only Debian-family hosts + are supported (got OS family "{{ ansible_facts.os_family }}"). + success_msg: >- + Preflight OK — {{ inventory_hostname }} is + {{ ansible_facts.distribution }} {{ ansible_facts.distribution_version }}. + +# --------------------------------------------------------------------------- +# Dynamic detections (run on the target host) +# --------------------------------------------------------------------------- + +- name: Detect physical Dell host (dmidecode) # noqa var-naming[no-role-prefix] + ansible.builtin.shell: | + set -o pipefail + /usr/sbin/dmidecode -t system 2>/dev/null | grep -c Dell || true + register: _dell_check + changed_when: false + failed_when: false + +- name: Detect Proxmox VE host # noqa var-naming[no-role-prefix] + ansible.builtin.shell: | + # Proxmox VE is installed if the proxmox-ve package is present OR the + # /etc/pve cluster FS is mounted. + dpkg-query -W -f='${Status}' proxmox-ve 2>/dev/null | grep -c 'install ok installed' || true + register: _proxmox_check + changed_when: false + failed_when: false + +- name: Detect virtualization type (virt-what) # noqa var-naming[no-role-prefix] + ansible.builtin.command: virt-what + register: _virt_what + changed_when: false + failed_when: false + +- name: Detect Raspberry Pi (device-tree model file) # noqa var-naming[no-role-prefix] + ansible.builtin.stat: + path: /sys/firmware/devicetree/base/model + register: _raspi_model + +- name: Detect existing system users + ansible.builtin.getent: + database: passwd + +# --------------------------------------------------------------------------- +# Compute host-class facts +# --------------------------------------------------------------------------- +- name: Set dynamic host-class facts # noqa var-naming[no-role-prefix] + ansible.builtin.set_fact: + _dell_count: "{{ _dell_check.stdout | default('0') | int }}" + _pve_count: "{{ _proxmox_check.stdout | default('0') | int }}" + _virt_output: "{{ _virt_what.stdout | default('') }}" + cacheable: true + +- name: Record host-class facts for downstream roles # noqa var-naming[no-role-prefix] + ansible.builtin.set_fact: + # Physical hardware (bare-metal Dell server, NOT a VM and NOT Proxmox PVE) + is_physical_host: >- + {{ + (_dell_check.stdout | default('0') | int) > 0 + and (_proxmox_check.stdout | default('0') | int) == 0 + and not (_raspi_model.stat.exists) + }} + # Proxmox VE hypervisor (bare-metal Dell running PVE) + is_proxmox_host: >- + {{ + (_proxmox_check.stdout | default('0') | int) > 0 + }} + # Raspberry Pi / ARM SBC + is_raspi: "{{ _raspi_model.stat.exists }}" + # Virtualization details from virt-what + virt_type: "{{ _virt_what.stdout | default('') }}" + is_virt_guest: >- + {{ + (_virt_what.stdout | default('')) is search('hyperv|kvm') + }} + is_kvm_guest: >- + {{ + (_virt_what.stdout | default('')) is search('kvm') + }} + # Distro flags + is_ubuntu: "{{ ansible_facts.distribution == 'Ubuntu' }}" + is_kali: "{{ ansible_facts.distribution == 'Kali' }}" + is_debian: "{{ ansible_facts.distribution == 'Debian' }}" + is_proxmox_ve: "{{ ansible_facts.distribution == 'Debian' and (_proxmox_check.stdout | default('0') | int) > 0 }}" + # User existence flags (for SSH + shell config roles) + localuser_exists: "{{ 'localuser' in getent_passwd }}" + subodev_exists: "{{ 'subodev' in getent_passwd }}" + cacheable: true + +# --------------------------------------------------------------------------- +# Inventory-group-derived facts +# These let roles skip config for infrastructure servers (NTP, DHCP, DNS) +# based on inventory group membership rather than hostname pattern matching. +# --------------------------------------------------------------------------- +- name: Record inventory-group facts for downstream roles # noqa var-naming[no-role-prefix] + ansible.builtin.set_fact: + is_ntp_server: "{{ 'ntp_servers' in group_names }}" + is_dhcp_server: "{{ 'dhcp_servers' in group_names }}" + is_dns_server: "{{ 'dns_servers' in group_names }}" + is_librenms_server: "{{ 'librenms_server' in group_names }}" + is_wazuh_server: "{{ 'wazuh_server' in group_names }}" + is_dev_workstation: "{{ 'dev_workstations' in group_names }}" + cacheable: true + +# --------------------------------------------------------------------------- +# Report +# --------------------------------------------------------------------------- +- name: Report detected host class + ansible.builtin.debug: + msg: >- + host-class: physical={{ is_physical_host }} proxmox={{ is_proxmox_host }} + raspi={{ is_raspi }} virt='{{ virt_type | default('none') }}' + ubuntu={{ is_ubuntu }} kali={{ is_kali }} + ntp_server={{ is_ntp_server }} dhcp_server={{ is_dhcp_server }} + librenms={{ is_librenms_server }} wazuh={{ is_wazuh_server }} + dev_ws={{ is_dev_workstation }}