2025-12-14 20:46:32 +01:00
|
|
|
---
|
2025-12-23 22:06:51 +01:00
|
|
|
# ============================================================
|
|
|
|
|
# Install required packages
|
|
|
|
|
# ============================================================
|
2025-12-14 20:46:32 +01:00
|
|
|
- name: Install required packages
|
2025-12-14 21:28:45 +01:00
|
|
|
ansible.builtin.apt:
|
2025-12-14 20:46:32 +01:00
|
|
|
name: ethtool
|
|
|
|
|
state: present
|
|
|
|
|
update_cache: yes
|
|
|
|
|
|
2025-12-23 22:06:51 +01:00
|
|
|
# ============================================================
|
|
|
|
|
# Normalize and validate configuration
|
|
|
|
|
# ============================================================
|
|
|
|
|
- name: Set WOL bridges list (handle string or list)
|
|
|
|
|
ansible.builtin.set_fact:
|
|
|
|
|
wol_bridges_list: "{{ wol_bridges if wol_bridges is iterable and wol_bridges is not string else [wol_bridges] }}"
|
|
|
|
|
|
|
|
|
|
- name: Validate WOL bridges list is not empty
|
|
|
|
|
ansible.builtin.assert:
|
|
|
|
|
that:
|
|
|
|
|
- wol_bridges_list | length > 0
|
|
|
|
|
fail_msg: "wol_bridges must contain at least one bridge interface"
|
|
|
|
|
|
|
|
|
|
# ============================================================
|
|
|
|
|
# Detect physical NICs for all bridges (including bond0)
|
|
|
|
|
# ============================================================
|
2025-12-23 20:42:03 +01:00
|
|
|
- name: Get bridge link information
|
|
|
|
|
ansible.builtin.command: "bridge link show"
|
|
|
|
|
register: bridge_links
|
|
|
|
|
changed_when: false
|
2025-12-23 22:06:51 +01:00
|
|
|
check_mode: false
|
|
|
|
|
|
|
|
|
|
- name: Get bond info
|
|
|
|
|
ansible.builtin.command: "cat /proc/net/bonding/bond0"
|
|
|
|
|
register: bond_info
|
|
|
|
|
changed_when: false
|
2025-12-23 20:42:03 +01:00
|
|
|
failed_when: false
|
2025-12-23 22:06:51 +01:00
|
|
|
check_mode: false
|
2025-12-21 18:49:25 +01:00
|
|
|
|
2025-12-23 22:06:51 +01:00
|
|
|
- name: Initialize detected interfaces dictionary
|
|
|
|
|
ansible.builtin.set_fact:
|
|
|
|
|
wol_detected_interfaces: {}
|
2025-12-23 21:14:36 +01:00
|
|
|
|
2025-12-23 22:06:51 +01:00
|
|
|
- name: Detect physical NIC for each bridge
|
2025-12-14 21:28:45 +01:00
|
|
|
ansible.builtin.set_fact:
|
2025-12-23 22:06:51 +01:00
|
|
|
wol_detected_interfaces: >-
|
2025-12-21 18:23:12 +01:00
|
|
|
{{
|
2025-12-23 22:06:51 +01:00
|
|
|
wol_detected_interfaces | combine({
|
|
|
|
|
item: (
|
|
|
|
|
bridge_links.stdout_lines
|
|
|
|
|
| select('search', 'master ' ~ item)
|
|
|
|
|
| map('regex_replace', '^\\d+: ([^:@]+):.*$', '\\1')
|
|
|
|
|
| reject('search', '^(veth|tap|fw)')
|
|
|
|
|
| list
|
|
|
|
|
) | first | default('')
|
|
|
|
|
})
|
|
|
|
|
}}
|
|
|
|
|
loop: "{{ wol_bridges_list }}"
|
|
|
|
|
loop_control:
|
|
|
|
|
label: "{{ item }}"
|
2025-12-14 21:28:45 +01:00
|
|
|
|
2025-12-23 22:06:51 +01:00
|
|
|
- name: Check for bond0 backing
|
|
|
|
|
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 }}"
|
2025-12-23 21:24:47 +01:00
|
|
|
|
2025-12-23 22:06:51 +01:00
|
|
|
- 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)
|
|
|
|
|
when: bond_info.rc == 0
|
|
|
|
|
|
|
|
|
|
# ============================================================
|
|
|
|
|
# Validate configuration and resolve to physical NICs
|
|
|
|
|
# ============================================================
|
|
|
|
|
- name: Fail if any bridge backing NIC could not be detected
|
2025-12-14 21:28:45 +01:00
|
|
|
ansible.builtin.fail:
|
|
|
|
|
msg: >
|
2025-12-23 22:06:51 +01:00
|
|
|
Unable to detect physical NIC backing bridge(s): {{ unresolved_bridges | join(', ') }}.
|
|
|
|
|
Please verify bridges exist or set wol_interfaces explicitly.
|
2025-12-23 21:11:21 +01:00
|
|
|
Bridge output:
|
2025-12-23 22:06:51 +01:00
|
|
|
{{ bridge_links.stdout_lines | join('\n') }}
|
|
|
|
|
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 }}"
|
2025-12-21 18:23:12 +01:00
|
|
|
|
2025-12-23 22:06:51 +01:00
|
|
|
# ============================================================
|
2025-12-23 20:42:03 +01:00
|
|
|
# Validate WOL capability
|
2025-12-23 22:06:51 +01:00
|
|
|
# ============================================================
|
|
|
|
|
- name: Check WOL capability on all detected NICs
|
|
|
|
|
ansible.builtin.command: "ethtool {{ item }}"
|
|
|
|
|
register: wol_capabilities_check
|
2025-12-14 21:28:45 +01:00
|
|
|
changed_when: false
|
2025-12-23 22:06:51 +01:00
|
|
|
failed_when: false
|
|
|
|
|
loop: "{{ wol_final_interfaces }}"
|
|
|
|
|
loop_control:
|
|
|
|
|
label: "{{ item }}"
|
2025-12-14 21:28:45 +01:00
|
|
|
|
2025-12-23 22:06:51 +01:00
|
|
|
- name: Validate all NICs support WOL
|
|
|
|
|
ansible.builtin.assert:
|
|
|
|
|
that:
|
|
|
|
|
- item.stdout is search('Supports Wake-on:.*[gGdDpPuU]')
|
|
|
|
|
fail_msg: "Interface {{ item.item }} does not support Wake-on-LAN"
|
|
|
|
|
loop: "{{ wol_capabilities_check.results }}"
|
|
|
|
|
loop_control:
|
|
|
|
|
label: "{{ item.item }}"
|
|
|
|
|
|
|
|
|
|
# ============================================================
|
|
|
|
|
# Check current WOL status to ensure idempotency
|
|
|
|
|
# ============================================================
|
|
|
|
|
- name: Get current WOL status
|
|
|
|
|
ansible.builtin.command: "ethtool {{ item }}"
|
|
|
|
|
register: wol_current_status
|
2025-12-23 20:42:03 +01:00
|
|
|
changed_when: false
|
2025-12-23 22:06:51 +01:00
|
|
|
failed_when: false
|
|
|
|
|
loop: "{{ wol_final_interfaces }}"
|
|
|
|
|
loop_control:
|
|
|
|
|
label: "{{ item }}"
|
2025-12-14 21:28:45 +01:00
|
|
|
|
2025-12-23 22:06:51 +01:00
|
|
|
- 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
|
2025-12-23 20:42:03 +01:00
|
|
|
ansible.builtin.copy:
|
|
|
|
|
dest: /etc/udev/rules.d/90-wol.rules
|
2025-12-14 20:46:32 +01:00
|
|
|
owner: root
|
|
|
|
|
group: root
|
2025-12-23 22:10:05 +01:00
|
|
|
mode: "0644"
|
2025-12-23 20:42:03 +01:00
|
|
|
content: |
|
2025-12-23 22:06:51 +01:00
|
|
|
# Wake-on-LAN udev rules - Auto-generated by Ansible
|
|
|
|
|
# Applies to: {{ wol_final_interfaces | join(', ') }}
|
|
|
|
|
{% for rule in wol_udev_rules %}
|
|
|
|
|
{{ rule }}
|
|
|
|
|
{% endfor %}
|
|
|
|
|
register: udev_rules_changed
|
2025-12-23 20:42:03 +01:00
|
|
|
|
|
|
|
|
- name: Reload udev rules
|
|
|
|
|
ansible.builtin.command: udevadm control --reload
|
|
|
|
|
changed_when: false
|
2025-12-23 22:06:51 +01:00
|
|
|
when: udev_rules_changed is changed
|
2025-12-14 20:46:32 +01:00
|
|
|
|
2025-12-23 20:42:03 +01:00
|
|
|
- name: Trigger udev for network interfaces
|
|
|
|
|
ansible.builtin.command: udevadm trigger --subsystem-match=net
|
2025-12-14 20:46:32 +01:00
|
|
|
changed_when: false
|
2025-12-23 22:06:51 +01:00
|
|
|
when: udev_rules_changed is changed
|
2025-12-14 20:46:32 +01:00
|
|
|
|
2025-12-23 22:06:51 +01:00
|
|
|
# ============================================================
|
2025-12-23 20:42:03 +01:00
|
|
|
# Verification & Reporting
|
2025-12-23 22:06:51 +01:00
|
|
|
# ============================================================
|
2025-12-14 20:46:32 +01:00
|
|
|
- name: Verify Wake-on-LAN status
|
2025-12-23 22:06:51 +01:00
|
|
|
ansible.builtin.command: "ethtool {{ item }}"
|
2025-12-14 20:46:32 +01:00
|
|
|
register: wol_status
|
|
|
|
|
changed_when: false
|
2025-12-23 22:06:51 +01:00
|
|
|
loop: "{{ wol_final_interfaces }}"
|
|
|
|
|
loop_control:
|
|
|
|
|
label: "{{ item }}"
|
2025-12-14 20:46:32 +01:00
|
|
|
when: wol_verify
|
|
|
|
|
|
2025-12-23 22:06:51 +01:00
|
|
|
- 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
|
2025-12-23 20:42:03 +01:00
|
|
|
ansible.builtin.set_fact:
|
2025-12-23 22:06:51 +01:00
|
|
|
wol_mac_addresses: >-
|
|
|
|
|
{{
|
|
|
|
|
wol_final_interfaces
|
|
|
|
|
| map('extract', hostvars[inventory_hostname]['ansible_' ~ item] | default({}), 'macaddress')
|
|
|
|
|
| list
|
|
|
|
|
}}
|
2025-12-14 21:28:45 +01:00
|
|
|
|
2025-12-23 22:06:51 +01:00
|
|
|
- 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 %}
|
2025-12-14 21:28:45 +01:00
|
|
|
when:
|
|
|
|
|
- wol_report_mac
|
|
|
|
|
- wol_mac_address | length == 0
|
|
|
|
|
|
2025-12-23 20:42:03 +01:00
|
|
|
- name: Show WOL status
|
|
|
|
|
ansible.builtin.debug:
|
|
|
|
|
msg: "{{ wol_status.stdout_lines }}"
|
|
|
|
|
when: wol_verify
|
|
|
|
|
|
2025-12-14 21:28:45 +01:00
|
|
|
- name: Report Wake-on-LAN sender details
|
|
|
|
|
ansible.builtin.debug:
|
|
|
|
|
msg:
|
2025-12-23 20:42:03 +01:00
|
|
|
- "Wake-on-LAN enabled successfully"
|
|
|
|
|
- "Bridge: {{ wol_bridge }}"
|
|
|
|
|
- "Physical NIC: {{ wol_detected_phy }}"
|
2025-12-14 21:28:45 +01:00
|
|
|
- "MAC address: {{ wol_mac_address }}"
|
2025-12-23 20:42:03 +01:00
|
|
|
- "Example commands:"
|
2025-12-14 21:28:45 +01:00
|
|
|
- " wakeonlan {{ wol_mac_address }}"
|
|
|
|
|
- " etherwake {{ wol_mac_address }}"
|
|
|
|
|
- "Home Assistant:"
|
|
|
|
|
- " mac: {{ wol_mac_address }}"
|
|
|
|
|
when: wol_report_mac
|