From b94be8a6e47b3a64aab9f264be2efd2007891ec0 Mon Sep 17 00:00:00 2001 From: Jose Date: Fri, 26 Dec 2025 11:00:41 +0100 Subject: [PATCH] =?UTF-8?q?docs=20=F0=9F=93=9D:=20Updated=20README.md=20fo?= =?UTF-8?q?r=20clarity=20and=20improved=20readability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed minor grammatical errors and reorganized content in the README.md file. --- README.md | 29 +++--- tasks/main.yml | 202 +++++++++++++++++++++++--------------- templates/90-wol.rules.j2 | 3 + 3 files changed, 139 insertions(+), 95 deletions(-) create mode 100644 templates/90-wol.rules.j2 diff --git a/README.md b/README.md index 2ca481f..5b7d60a 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ A robust, idempotent Ansible role for enabling persistent Wake-on-LAN (WOL) on P ## Features ✅ **Fully Idempotent**: Checks current WOL status and only applies changes when needed -✅ **Multiple Bridge Support**: Configure WOL on multiple bridges simultaneously +✅ **Bridge Support**: Automatically detects physical interfaces backing configured bridges ✅ **Bond0 Detection**: Automatically detects and configures bonded interfaces ✅ **Ansible Facts-Based**: Uses Ansible facts to detect and validate WOL-capable interfaces -✅ **Safe & Persistent**: Uses udev rules for persistence across reboots +✅ **Persistent**: Uses udev rules for persistence across reboots ✅ **Comprehensive Validation**: Verifies WOL capability before configuration ✅ **Detailed Reporting**: Shows configuration status and MAC addresses for WOL senders @@ -25,12 +25,12 @@ A robust, idempotent Ansible role for enabling persistent Wake-on-LAN (WOL) on P 1. **Package Installation**: Ensures `ethtool` is installed for WOL management 2. **Interface Discovery**: Uses Ansible facts to identify all physical Ethernet interfaces -3. **WOL Validation**: Tests each interface for Wake-on-LAN capability using ethtool -4. **Bridge Mapping**: Maps configured bridges to their backing WOL-capable physical NICs -5. **Bond0 Detection**: Detects if interfaces are bonded and extracts slave information +3. **Bridge Mapping**: Detects physical interfaces backing configured bridges +4. **Bond0 Detection**: Identifies bonded interfaces and their slaves +5. **WOL Validation**: Tests each interface for Wake-on-LAN capability using ethtool 6. **Idempotency Check**: Reads current WOL status to avoid redundant changes 7. **Enable WOL**: Applies WOL settings only to interfaces that need it -8. **Persist Settings**: Creates/updates udev rules for persistence across reboots +8. **Persist Settings**: Creates udev rules for persistence across reboots 9. **Reload Udev**: Reloads udev rules and triggers network interface refresh 10. **Verification & Reporting**: Displays WOL configuration status and MAC addresses @@ -155,10 +155,7 @@ This role is fully idempotent. Running it multiple times has the same effect as ## Implementation Details ### Persistence Method -WOL settings are persisted using udev rules at `/etc/udev/rules.d/90-wol.rules`. This is the most reliable method for Debian/Proxmox systems and survives: -- System reboots -- Network service restarts -- Interface state changes +WOL settings are persisted using udev rules at `/etc/udev/rules.d/90-wol.rules`. This ensures WOL is re-enabled whenever network interfaces are added or the system reboots. Example generated udev rule: ``` @@ -167,10 +164,14 @@ ACTION=="add", SUBSYSTEM=="net", KERNEL=="eno1", RUN+="/sbin/ethtool -s eno1 wol ### Detection Logic 1. **Interface Discovery**: Uses Ansible facts to enumerate all network interfaces -2. **Physical Interface Filtering**: Filters for Ethernet interfaces, excluding virtual interfaces (veth, tap, fw*, docker, br*) -3. **WOL Capability Testing**: Tests each physical interface with ethtool to verify WOL support -4. **Bridge Mapping**: Maps configured bridges to their backing WOL-capable physical NICs -5. **Bond0 Detection**: Extracts slave interfaces from `/proc/net/bonding/bond0` when present +2. **Bridge Mapping**: Identifies physical interfaces backing configured bridges using `bridge link show` +3. **Bond0 Detection**: Detects bonded interfaces and extracts slave information from `/proc/net/bonding/bond0` +4. **WOL Capability Testing**: Tests each physical interface with ethtool to verify WOL support +5. **Idempotency Check**: Reads current WOL status to avoid redundant changes +6. **Enable WOL**: Applies WOL settings only to interfaces that need it +7. **Persist Settings**: Creates/updates udev rules for persistence +8. **Reload Udev**: Reloads udev rules and triggers network interface refresh +9. **Verification & Reporting**: Displays WOL configuration status and MAC addresses ## Troubleshooting diff --git a/tasks/main.yml b/tasks/main.yml index 936f0fd..8a1036e 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -26,7 +26,7 @@ # ansible.builtin.set_fact: # en_interfaces: "{{ ansible_facts.interfaces | select('match', '^eth|^ens|^enp') | unique | list }}" -- name: Get interfaces starting with "en or "eth" +- name: Get interfaces starting with "en" or "eth" ansible.builtin.set_fact: en_interfaces: >- {{ @@ -35,112 +35,152 @@ | list }} -- name: Display debug selected interfaces +# ============================================================ +# Detect physical interfaces backing configured bridges +# ============================================================ +- name: Get physical interfaces for configured bridges + ansible.builtin.command: bridge link show {{ item }} + register: bridge_links + loop: "{{ wol_bridges | list }}" + changed_when: false + failed_when: false + +- name: Extract physical interfaces from bridge info + ansible.builtin.set_fact: + bridge_physical: >- + {{ + bridge_links.results + | selectattr('rc', 'equalto', 0) + | map(attribute='stdout_lines') + | flatten + | map('regex_replace', '^\\d+: ([^ ]+).*', '\\1') + | select + | unique + | list + }} + +# ============================================================ +# Check for bond0 and get slaves +# ============================================================ +- name: Check if bond0 exists + ansible.builtin.stat: + path: /proc/net/bonding/bond0 + register: bond0_stat + +- name: Get bond0 slaves + ansible.builtin.command: cat /proc/net/bonding/bond0 | grep "Slave Interface" | awk '{print $3}' + register: bond0_slaves + changed_when: false + when: bond0_stat.stat.exists + +# ============================================================ +# Set final list of interfaces to configure +# ============================================================ +- name: Set final interfaces + ansible.builtin.set_fact: + wol_final_interfaces: >- + {{ + (bridge_physical if bridge_physical else en_interfaces) + + (bond0_slaves.stdout_lines if bond0_stat.stat.exists else []) + | unique + | list + }} + +- name: Display selected interfaces ansible.builtin.debug: - msg: > - {{ en_interfaces }} + msg: "Interfaces to configure for WOL: {{ wol_final_interfaces }}" - name: Check supported Wake-on-LAN modes ansible.builtin.shell: "ethtool {{ item }} | grep 'Supports Wake-on' | tail -1 | awk '{print $3}'" - loop: "{{ en_interfaces }}" + loop: "{{ wol_final_interfaces }}" register: wol_supported changed_when: false - when: en_interfaces | length > 0 + when: wol_final_interfaces | length > 0 -- name: WOL | Check if enabled - shell: > - ethtool {{ item }} | grep 'Wake-on' | tail -1 | awk '{print substr($0,length,1)}' +- name: Check if WOL is enabled + ansible.builtin.shell: "ethtool {{ item }} | grep 'Wake-on' | tail -1 | awk '{print substr($0,length,1)}'" register: wol_enabled changed_when: false failed_when: false - loop: "{{ en_interfaces }}" - when: en_interfaces | length > 0 + loop: "{{ wol_final_interfaces }}" + when: wol_final_interfaces | length > 0 -- name: "Set Wake-on-LAN to {{ wol_mode }}" +# ============================================================ +# Enable or disable WOL as needed +# ============================================================ +- name: Set Wake-on-LAN mode ansible.builtin.command: "ethtool -s {{ item.0 }} wol {{ wol_mode }}" - loop: "{{ en_interfaces | zip(wol_enabled.results, wol_supported.results) | list }}" + loop: "{{ wol_final_interfaces | zip(wol_enabled.results, wol_supported.results) | list }}" loop_control: label: "{{ item.0 }}" when: + - item.1.stdout is defined + - item.2.stdout is defined - wol_mode not in item.1.stdout - wol_mode in item.2.stdout -- name: "Disable Wake-on-LAN" - ansible.builtin.command: "ethtool -s {{ item.0 }} wol {{ wol_mode }}" - loop: "{{ en_interfaces | zip(wol_enabled.results, wol_supported.results) | list }}" - loop_control: - label: "{{ item.0 }}" - when: - - wol_mode == 'd' - - wol_mode not in item.1.stdout - -# ------------------------- -# 4. Create systemd service for persistence -# ------------------------- -- name: Create systemd service from template +# ============================================================ +# Create udev rules for persistence +# ============================================================ +- name: Create udev rule for WOL persistence ansible.builtin.template: - src: wol-interfaces.service.j2 - dest: /etc/systemd/system/wol-interfaces.service + src: 90-wol.rules.j2 + dest: /etc/udev/rules.d/90-wol.rules owner: root group: root mode: '0644' notify: - - Reload systemd - -- name: Enable and start WoL service - ansible.builtin.systemd: - name: wol-interfaces.service - enabled: yes - state: started + - 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 +# ============================================================ +# 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 and wol_final_interfaces | length > 0 -# - 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: 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: Get MAC addresses for all interfaces + ansible.builtin.set_fact: + wol_mac_addresses: >- + {{ + wol_final_interfaces + | map('extract', ansible_facts, attribute='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 %} +- name: Report WOL configuration + ansible.builtin.debug: + msg: | + Wake-on-LAN Configuration Summary: + =================================== + Bridges Configured: {{ wol_bridges | join(', ') }} + Physical Interfaces: {{ wol_final_interfaces | join(', ') }} + WOL Mode: {{ wol_mode }} + {% if bond0_stat.stat.exists %} + Bond0 Detected: Yes + Bond0 Slaves: {{ bond0_slaves.stdout_lines | 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 %} diff --git a/templates/90-wol.rules.j2 b/templates/90-wol.rules.j2 new file mode 100644 index 0000000..c6a2ecf --- /dev/null +++ b/templates/90-wol.rules.j2 @@ -0,0 +1,3 @@ +{% for interface in wol_final_interfaces %} +ACTION=="add", SUBSYSTEM=="net", KERNEL=="{{ interface }}", RUN+="/sbin/ethtool -s {{ interface }} wol {{ wol_mode }}" +{% endfor %} \ No newline at end of file