--- # ============================================================ # Install required packages # ============================================================ - name: Install required packages ansible.builtin.apt: name: ethtool state: present update_cache: true # ============================================================ # Detect physical NICs with WOL support using Ansible facts # ============================================================ - name: Gather network interface facts ansible.builtin.setup: gather_subset: - network when: ansible_facts.interfaces is not defined - name: Display interfaces ansible.builtin.debug: msg: > {{ ansible_facts.interfaces }} # - name: Get interfaces starting with "en" or "eth" # ansible.builtin.set_fact: # en_interfaces: "{{ ansible_facts.interfaces | select('match', '^eth|^ens|^enp') | unique | list }}" - name: Get interfaces starting with "en or "eth" ansible.builtin.set_fact: en_interfaces: >- {{ ansible_facts.interfaces | select('match', '^(eth|en)') | list }} - name: Display selected interfaces ansible.builtin.debug: msg: > {{ en_interfaces }} - name: Check WoL status with ethtool shell: "ethtool {{ item }} | grep 'Wake-on'" register: wol_status changed_when: false failed_when: false loop: "{{ en_interfaces }}" when: en_interfaces | length > 0 - name: Display WoL status debug: msg: "{{ wol_status.stdout}}" - name: Display WOL status summary ansible.builtin.debug: msg: > Interface {{ item.key }}: Supported={{ item.value.supports_magic }}, Current={{ item.value.current_state }} loop: "{{ wol_info | dict2items }}" # - name: Enable Wake-on-LAN (magic packet) when supported # ansible.builtin.command: "ethtool -s {{ item.key }} wol g" # when: # - item.value.supports_magic # - item.value.current_state != 'g' # loop: "{{ wol_info | dict2items }}" # # ============================================================ # # Map bridges to physical NICs using Ansible facts # # ============================================================ # - name: Get bridge link information # ansible.builtin.command: "bridge link show" # register: bridge_links # changed_when: false # check_mode: false # - name: Initialize detected interfaces dictionary # ansible.builtin.set_fact: # wol_detected_interfaces: {} # - name: Detect physical NIC for each bridge # ansible.builtin.set_fact: # wol_detected_interfaces: >- # {{ # wol_detected_interfaces | combine({ # item: ( # bridge_links.stdout_lines # | select('search', 'master ' ~ item) # | map('regex_replace', '^\\d+: ([a-z0-9@.]+):.*$', '\\1') # | reject('search', '^(veth|tap|fw)') # | reject('search', '^\\d+:') # | select('in', wol_supported_interfaces) # | first | default('') # ) # }) # }} # loop: "{{ wol_bridges_list }}" # loop_control: # label: "{{ item }}" # - name: Check for bond0 backing # when: bond_info.rc == 0 # block: # - name: Detect if any bridge is backed by bond0 # ansible.builtin.set_fact: # wol_has_bond0: "{{ wol_detected_interfaces.values() | select('search', 'bond0') | length > 0 }}" # - name: Extract bond0 slaves if present # ansible.builtin.set_fact: # wol_bond0_slaves: >- # {{ # (bond_info.stdout | regex_findall('Slave Interface: ([a-zA-Z0-9]+)')) | list # }} # when: wol_has_bond0 | default(false) # # ============================================================ # # Validate configuration and resolve to physical NICs # # ============================================================ # - name: Fail if any bridge backing NIC could not be detected # ansible.builtin.fail: # msg: > # Unable to detect physical NIC backing bridge(s): {{ unresolved_bridges | join(', ') }}. # Please verify bridges exist and are backed by WOL-capable interfaces. # Available WOL-capable interfaces: {{ wol_supported_interfaces | join(', ') }} # vars: # unresolved_bridges: "{{ wol_detected_interfaces | dict2items | selectattr('value', 'equalto', '') | map(attribute='key') | list }}" # when: unresolved_bridges | length > 0 # - name: Build final WOL interfaces list (deduped physical NICs) # ansible.builtin.set_fact: # wol_final_interfaces: "{{ wol_detected_interfaces.values() | unique | list }}" # # ============================================================ # # Check current WOL status to ensure idempotency # # ============================================================ # - name: Get current WOL status # ansible.builtin.command: "ethtool {{ item }}" # register: wol_current_status # changed_when: false # failed_when: false # loop: "{{ wol_final_interfaces }}" # loop_control: # label: "{{ item }}" # - name: Build list of NICs needing WOL enabled # ansible.builtin.set_fact: # wol_needs_enable: >- # {{ # wol_current_status.results # | selectattr('stdout', 'search', 'Wake-on: [^g]') # | map(attribute='item') # | list # }} # # ============================================================ # # Enable WOL immediately (only if needed) # # ============================================================ # - name: Enable Wake-on-LAN immediately on NICs # ansible.builtin.command: "ethtool -s {{ item }} wol {{ wol_mode }}" # register: wol_enable_result # changed_when: true # loop: "{{ wol_needs_enable }}" # loop_control: # label: "{{ item }}" # when: wol_needs_enable | length > 0 # # ============================================================ # # Persist WOL via udev rules (safe and idempotent) # # ============================================================ # - name: Create udev rule content # ansible.builtin.set_fact: # wol_udev_rules: >- # {{ # wol_final_interfaces # | map('regex_replace', '^(.+)$', 'ACTION=="add", SUBSYSTEM=="net", KERNEL=="\1", RUN+="/sbin/ethtool -s \1 wol {{ wol_mode }}"') # | list # }} # - name: Create/Update udev rules file # ansible.builtin.copy: # dest: /etc/udev/rules.d/90-wol.rules # owner: root # group: root # mode: "0644" # content: | # # Wake-on-LAN udev rules - Auto-generated by Ansible # # Applies to: {{ wol_final_interfaces | join(', ') }} # {% for rule in wol_udev_rules %} # {{ rule }} # {% endfor %} # notify: # - Reload_udev_rules # - Trigger_udev_net # # ============================================================ # # Verification & Reporting # # ============================================================ # - name: Verify Wake-on-LAN status # ansible.builtin.command: "ethtool {{ item }}" # register: wol_status # changed_when: false # loop: "{{ wol_final_interfaces }}" # loop_control: # label: "{{ item }}" # when: wol_verify # - name: Display WOL status per interface # ansible.builtin.debug: # msg: > # Interface {{ item.item }} WOL Status: # {{ item.stdout_lines | select('search', 'Wake-on:') | first | default('Status Unknown') }} # loop: "{{ wol_status.results | default([]) }}" # loop_control: # label: "{{ item.item }}" # when: wol_verify # - name: Get MAC addresses for all interfaces # ansible.builtin.set_fact: # wol_mac_addresses: >- # {{ # wol_final_interfaces # | map('extract', hostvars[inventory_hostname]['ansible_' ~ item] | default({}), 'macaddress') # | list # }} # - name: Report WOL configuration # ansible.builtin.debug: # msg: | # Wake-on-LAN Configuration Summary: # =================================== # Bridges Configured: {{ wol_bridges_list | join(', ') }} # Physical Interfaces: {{ wol_final_interfaces | join(', ') }} # WOL Mode: {{ wol_mode }} # {% if wol_has_bond0 | default(false) %} # Bond0 Detected: Yes # Bond0 Slaves: {{ wol_bond0_slaves | join(', ') }} # {% endif %} # {% if wol_report_mac and wol_mac_addresses | length > 0 %} # MAC Addresses: # {% for iface, mac in (wol_final_interfaces | zip(wol_mac_addresses) | list) %} # - {{ iface }}: {{ mac | default('Unable to detect') }} # {% endfor %} # {% endif %}