feat ✨: Add Ansible playbook for configuring Wake-on-LAN (WOL)
All checks were successful
ansible-lint / Ansible Lint (push) Successful in 14s
All checks were successful
ansible-lint / Ansible Lint (push) Successful in 14s
The new playbook automates the setup of WOL on specified network interfaces, including gathering facts, identifying capable interfaces, mapping bridges to physical NICs, enabling WOL, and persisting configuration with udev rules. It also includes reporting and verification steps.
This commit is contained in:
430
tasks/main.yml
430
tasks/main.yml
@@ -8,240 +8,240 @@
|
|||||||
state: present
|
state: present
|
||||||
update_cache: true
|
update_cache: true
|
||||||
|
|
||||||
# ============================================================
|
# # ============================================================
|
||||||
# Normalize and validate configuration
|
# # Normalize and validate configuration
|
||||||
# ============================================================
|
# # ============================================================
|
||||||
- name: Set WOL bridges list (handle string or list)
|
# - name: Set WOL bridges list (handle string or list)
|
||||||
ansible.builtin.set_fact:
|
# ansible.builtin.set_fact:
|
||||||
wol_bridges_list: "{{ wol_bridges if wol_bridges is iterable and wol_bridges is not string else [wol_bridges] }}"
|
# 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
|
# - name: Validate WOL bridges list is not empty
|
||||||
ansible.builtin.assert:
|
# ansible.builtin.assert:
|
||||||
that:
|
# that:
|
||||||
- wol_bridges_list | length > 0
|
# - wol_bridges_list | length > 0
|
||||||
fail_msg: "wol_bridges must contain at least one bridge interface"
|
# fail_msg: "wol_bridges must contain at least one bridge interface"
|
||||||
|
|
||||||
# ============================================================
|
# # ============================================================
|
||||||
# Detect physical NICs with WOL support using Ansible facts
|
# # Detect physical NICs with WOL support using Ansible facts
|
||||||
# ============================================================
|
# # ============================================================
|
||||||
- name: Gather network interface facts
|
# - name: Gather network interface facts
|
||||||
ansible.builtin.setup:
|
# ansible.builtin.setup:
|
||||||
gather_subset:
|
# gather_subset:
|
||||||
- network
|
# - network
|
||||||
when: ansible_facts.interfaces is not defined
|
# when: ansible_facts.interfaces is not defined
|
||||||
|
|
||||||
- name: Get all physical network interfaces with WOL support
|
# - name: Get all physical network interfaces with WOL support
|
||||||
ansible.builtin.set_fact:
|
# ansible.builtin.set_fact:
|
||||||
wol_capable_interfaces: >-
|
# wol_capable_interfaces: >-
|
||||||
{{
|
# {{
|
||||||
ansible_facts.interfaces
|
# ansible_facts.interfaces
|
||||||
| map('extract', hostvars[inventory_hostname]['ansible_' ~ item] | default({}))
|
# | map('extract', hostvars[inventory_hostname]['ansible_' ~ item] | default({}))
|
||||||
| selectattr('type', 'defined')
|
# | selectattr('type', 'defined')
|
||||||
| selectattr('type', 'equalto', 'ether')
|
# | selectattr('type', 'equalto', 'ether')
|
||||||
| selectattr('device', 'defined')
|
# | selectattr('device', 'defined')
|
||||||
| rejectattr('device', 'search', '^(veth|tap|fw|lo|docker|br)')
|
# | rejectattr('device', 'search', '^(veth|tap|fw|lo|docker|br)')
|
||||||
| map(attribute='device')
|
# | map(attribute='device')
|
||||||
| list
|
# | list
|
||||||
}}
|
# }}
|
||||||
|
|
||||||
- name: Validate WOL capability using ethtool for detected interfaces
|
# - name: Validate WOL capability using ethtool for detected interfaces
|
||||||
ansible.builtin.command: "ethtool {{ item }}"
|
# ansible.builtin.command: "ethtool {{ item }}"
|
||||||
register: wol_capabilities_check
|
# register: wol_capabilities_check
|
||||||
changed_when: false
|
# changed_when: false
|
||||||
failed_when: false
|
# failed_when: false
|
||||||
loop: "{{ wol_capable_interfaces }}"
|
# loop: "{{ wol_capable_interfaces }}"
|
||||||
loop_control:
|
# loop_control:
|
||||||
label: "{{ item }}"
|
# label: "{{ item }}"
|
||||||
|
|
||||||
- name: Filter interfaces that actually support WOL
|
# - name: Filter interfaces that actually support WOL
|
||||||
ansible.builtin.set_fact:
|
# ansible.builtin.set_fact:
|
||||||
wol_supported_interfaces: >-
|
# wol_supported_interfaces: >-
|
||||||
{{
|
# {{
|
||||||
wol_capabilities_check.results
|
# wol_capabilities_check.results
|
||||||
| selectattr('stdout', 'search', 'Supports Wake-on:.*[gGdDpPuU]')
|
# | selectattr('stdout', 'search', 'Supports Wake-on:.*[gGdDpPuU]')
|
||||||
| map(attribute='item')
|
# | map(attribute='item')
|
||||||
| list
|
# | list
|
||||||
}}
|
# }}
|
||||||
|
|
||||||
- name: Fail if no interfaces support WOL
|
# - name: Fail if no interfaces support WOL
|
||||||
ansible.builtin.assert:
|
# ansible.builtin.assert:
|
||||||
that:
|
# that:
|
||||||
- wol_supported_interfaces | length > 0
|
# - wol_supported_interfaces | length > 0
|
||||||
fail_msg: "No network interfaces found that support Wake-on-LAN. Check BIOS settings and NIC capabilities."
|
# fail_msg: "No network interfaces found that support Wake-on-LAN. Check BIOS settings and NIC capabilities."
|
||||||
|
|
||||||
# ============================================================
|
# # ============================================================
|
||||||
# Map bridges to physical NICs using Ansible facts
|
# # Map bridges to physical NICs using Ansible facts
|
||||||
# ============================================================
|
# # ============================================================
|
||||||
- name: Get bridge link information
|
# - name: Get bridge link information
|
||||||
ansible.builtin.command: "bridge link show"
|
# ansible.builtin.command: "bridge link show"
|
||||||
register: bridge_links
|
# register: bridge_links
|
||||||
changed_when: false
|
# changed_when: false
|
||||||
check_mode: false
|
# check_mode: false
|
||||||
|
|
||||||
- name: Initialize detected interfaces dictionary
|
# - name: Initialize detected interfaces dictionary
|
||||||
ansible.builtin.set_fact:
|
# ansible.builtin.set_fact:
|
||||||
wol_detected_interfaces: {}
|
# wol_detected_interfaces: {}
|
||||||
|
|
||||||
- name: Detect physical NIC for each bridge
|
# - name: Detect physical NIC for each bridge
|
||||||
ansible.builtin.set_fact:
|
# ansible.builtin.set_fact:
|
||||||
wol_detected_interfaces: >-
|
# wol_detected_interfaces: >-
|
||||||
{{
|
# {{
|
||||||
wol_detected_interfaces | combine({
|
# wol_detected_interfaces | combine({
|
||||||
item: (
|
# item: (
|
||||||
bridge_links.stdout_lines
|
# bridge_links.stdout_lines
|
||||||
| select('search', 'master ' ~ item)
|
# | select('search', 'master ' ~ item)
|
||||||
| map('regex_replace', '^\\d+: ([a-z0-9@.]+):.*$', '\\1')
|
# | map('regex_replace', '^\\d+: ([a-z0-9@.]+):.*$', '\\1')
|
||||||
| reject('search', '^(veth|tap|fw)')
|
# | reject('search', '^(veth|tap|fw)')
|
||||||
| reject('search', '^\\d+:')
|
# | reject('search', '^\\d+:')
|
||||||
| select('in', wol_supported_interfaces)
|
# | select('in', wol_supported_interfaces)
|
||||||
| first | default('')
|
# | first | default('')
|
||||||
)
|
# )
|
||||||
})
|
# })
|
||||||
}}
|
# }}
|
||||||
loop: "{{ wol_bridges_list }}"
|
# loop: "{{ wol_bridges_list }}"
|
||||||
loop_control:
|
# loop_control:
|
||||||
label: "{{ item }}"
|
# label: "{{ item }}"
|
||||||
|
|
||||||
- name: Check for bond0 backing
|
# - name: Check for bond0 backing
|
||||||
when: bond_info.rc == 0
|
# when: bond_info.rc == 0
|
||||||
block:
|
# block:
|
||||||
- name: Detect if any bridge is backed by bond0
|
# - name: Detect if any bridge is backed by bond0
|
||||||
ansible.builtin.set_fact:
|
# ansible.builtin.set_fact:
|
||||||
wol_has_bond0: "{{ wol_detected_interfaces.values() | select('search', 'bond0') | length > 0 }}"
|
# wol_has_bond0: "{{ wol_detected_interfaces.values() | select('search', 'bond0') | length > 0 }}"
|
||||||
|
|
||||||
- name: Extract bond0 slaves if present
|
# - name: Extract bond0 slaves if present
|
||||||
ansible.builtin.set_fact:
|
# ansible.builtin.set_fact:
|
||||||
wol_bond0_slaves: >-
|
# wol_bond0_slaves: >-
|
||||||
{{
|
# {{
|
||||||
(bond_info.stdout | regex_findall('Slave Interface: ([a-zA-Z0-9]+)')) | list
|
# (bond_info.stdout | regex_findall('Slave Interface: ([a-zA-Z0-9]+)')) | list
|
||||||
}}
|
# }}
|
||||||
when: wol_has_bond0 | default(false)
|
# when: wol_has_bond0 | default(false)
|
||||||
|
|
||||||
# ============================================================
|
# # ============================================================
|
||||||
# Validate configuration and resolve to physical NICs
|
# # Validate configuration and resolve to physical NICs
|
||||||
# ============================================================
|
# # ============================================================
|
||||||
- name: Fail if any bridge backing NIC could not be detected
|
# - name: Fail if any bridge backing NIC could not be detected
|
||||||
ansible.builtin.fail:
|
# ansible.builtin.fail:
|
||||||
msg: >
|
# msg: >
|
||||||
Unable to detect physical NIC backing bridge(s): {{ unresolved_bridges | join(', ') }}.
|
# Unable to detect physical NIC backing bridge(s): {{ unresolved_bridges | join(', ') }}.
|
||||||
Please verify bridges exist and are backed by WOL-capable interfaces.
|
# Please verify bridges exist and are backed by WOL-capable interfaces.
|
||||||
Available WOL-capable interfaces: {{ wol_supported_interfaces | join(', ') }}
|
# Available WOL-capable interfaces: {{ wol_supported_interfaces | join(', ') }}
|
||||||
vars:
|
# vars:
|
||||||
unresolved_bridges: "{{ wol_detected_interfaces | dict2items | selectattr('value', 'equalto', '') | map(attribute='key') | list }}"
|
# unresolved_bridges: "{{ wol_detected_interfaces | dict2items | selectattr('value', 'equalto', '') | map(attribute='key') | list }}"
|
||||||
when: unresolved_bridges | length > 0
|
# when: unresolved_bridges | length > 0
|
||||||
|
|
||||||
- name: Build final WOL interfaces list (deduped physical NICs)
|
# - name: Build final WOL interfaces list (deduped physical NICs)
|
||||||
ansible.builtin.set_fact:
|
# ansible.builtin.set_fact:
|
||||||
wol_final_interfaces: "{{ wol_detected_interfaces.values() | unique | list }}"
|
# wol_final_interfaces: "{{ wol_detected_interfaces.values() | unique | list }}"
|
||||||
|
|
||||||
# ============================================================
|
# # ============================================================
|
||||||
# Check current WOL status to ensure idempotency
|
# # Check current WOL status to ensure idempotency
|
||||||
# ============================================================
|
# # ============================================================
|
||||||
- name: Get current WOL status
|
# - name: Get current WOL status
|
||||||
ansible.builtin.command: "ethtool {{ item }}"
|
# ansible.builtin.command: "ethtool {{ item }}"
|
||||||
register: wol_current_status
|
# register: wol_current_status
|
||||||
changed_when: false
|
# changed_when: false
|
||||||
failed_when: false
|
# failed_when: false
|
||||||
loop: "{{ wol_final_interfaces }}"
|
# loop: "{{ wol_final_interfaces }}"
|
||||||
loop_control:
|
# loop_control:
|
||||||
label: "{{ item }}"
|
# label: "{{ item }}"
|
||||||
|
|
||||||
- name: Build list of NICs needing WOL enabled
|
# - name: Build list of NICs needing WOL enabled
|
||||||
ansible.builtin.set_fact:
|
# ansible.builtin.set_fact:
|
||||||
wol_needs_enable: >-
|
# wol_needs_enable: >-
|
||||||
{{
|
# {{
|
||||||
wol_current_status.results
|
# wol_current_status.results
|
||||||
| selectattr('stdout', 'search', 'Wake-on: [^g]')
|
# | selectattr('stdout', 'search', 'Wake-on: [^g]')
|
||||||
| map(attribute='item')
|
# | map(attribute='item')
|
||||||
| list
|
# | list
|
||||||
}}
|
# }}
|
||||||
|
|
||||||
# ============================================================
|
# # ============================================================
|
||||||
# Enable WOL immediately (only if needed)
|
# # Enable WOL immediately (only if needed)
|
||||||
# ============================================================
|
# # ============================================================
|
||||||
- name: Enable Wake-on-LAN immediately on NICs
|
# - name: Enable Wake-on-LAN immediately on NICs
|
||||||
ansible.builtin.command: "ethtool -s {{ item }} wol {{ wol_mode }}"
|
# ansible.builtin.command: "ethtool -s {{ item }} wol {{ wol_mode }}"
|
||||||
register: wol_enable_result
|
# register: wol_enable_result
|
||||||
changed_when: true
|
# changed_when: true
|
||||||
loop: "{{ wol_needs_enable }}"
|
# loop: "{{ wol_needs_enable }}"
|
||||||
loop_control:
|
# loop_control:
|
||||||
label: "{{ item }}"
|
# label: "{{ item }}"
|
||||||
when: wol_needs_enable | length > 0
|
# when: wol_needs_enable | length > 0
|
||||||
|
|
||||||
# ============================================================
|
# # ============================================================
|
||||||
# Persist WOL via udev rules (safe and idempotent)
|
# # Persist WOL via udev rules (safe and idempotent)
|
||||||
# ============================================================
|
# # ============================================================
|
||||||
- name: Create udev rule content
|
# - name: Create udev rule content
|
||||||
ansible.builtin.set_fact:
|
# ansible.builtin.set_fact:
|
||||||
wol_udev_rules: >-
|
# wol_udev_rules: >-
|
||||||
{{
|
# {{
|
||||||
wol_final_interfaces
|
# wol_final_interfaces
|
||||||
| map('regex_replace', '^(.+)$', 'ACTION=="add", SUBSYSTEM=="net", KERNEL=="\1", RUN+="/sbin/ethtool -s \1 wol {{ wol_mode }}"')
|
# | map('regex_replace', '^(.+)$', 'ACTION=="add", SUBSYSTEM=="net", KERNEL=="\1", RUN+="/sbin/ethtool -s \1 wol {{ wol_mode }}"')
|
||||||
| list
|
# | list
|
||||||
}}
|
# }}
|
||||||
|
|
||||||
- name: Create/Update udev rules file
|
# - name: Create/Update udev rules file
|
||||||
ansible.builtin.copy:
|
# ansible.builtin.copy:
|
||||||
dest: /etc/udev/rules.d/90-wol.rules
|
# dest: /etc/udev/rules.d/90-wol.rules
|
||||||
owner: root
|
# owner: root
|
||||||
group: root
|
# group: root
|
||||||
mode: "0644"
|
# mode: "0644"
|
||||||
content: |
|
# content: |
|
||||||
# Wake-on-LAN udev rules - Auto-generated by Ansible
|
# # Wake-on-LAN udev rules - Auto-generated by Ansible
|
||||||
# Applies to: {{ wol_final_interfaces | join(', ') }}
|
# # Applies to: {{ wol_final_interfaces | join(', ') }}
|
||||||
{% for rule in wol_udev_rules %}
|
# {% for rule in wol_udev_rules %}
|
||||||
{{ rule }}
|
# {{ rule }}
|
||||||
{% endfor %}
|
# {% endfor %}
|
||||||
notify:
|
# notify:
|
||||||
- Reload_udev_rules
|
# - Reload_udev_rules
|
||||||
- Trigger_udev_net
|
# - Trigger_udev_net
|
||||||
|
|
||||||
# ============================================================
|
# # ============================================================
|
||||||
# Verification & Reporting
|
# # Verification & Reporting
|
||||||
# ============================================================
|
# # ============================================================
|
||||||
- name: Verify Wake-on-LAN status
|
# - name: Verify Wake-on-LAN status
|
||||||
ansible.builtin.command: "ethtool {{ item }}"
|
# ansible.builtin.command: "ethtool {{ item }}"
|
||||||
register: wol_status
|
# register: wol_status
|
||||||
changed_when: false
|
# changed_when: false
|
||||||
loop: "{{ wol_final_interfaces }}"
|
# loop: "{{ wol_final_interfaces }}"
|
||||||
loop_control:
|
# loop_control:
|
||||||
label: "{{ item }}"
|
# label: "{{ item }}"
|
||||||
when: wol_verify
|
# when: wol_verify
|
||||||
|
|
||||||
- name: Display WOL status per interface
|
# - name: Display WOL status per interface
|
||||||
ansible.builtin.debug:
|
# ansible.builtin.debug:
|
||||||
msg: >
|
# msg: >
|
||||||
Interface {{ item.item }} WOL Status:
|
# Interface {{ item.item }} WOL Status:
|
||||||
{{ item.stdout_lines | select('search', 'Wake-on:') | first | default('Status Unknown') }}
|
# {{ item.stdout_lines | select('search', 'Wake-on:') | first | default('Status Unknown') }}
|
||||||
loop: "{{ wol_status.results | default([]) }}"
|
# loop: "{{ wol_status.results | default([]) }}"
|
||||||
loop_control:
|
# loop_control:
|
||||||
label: "{{ item.item }}"
|
# label: "{{ item.item }}"
|
||||||
when: wol_verify
|
# when: wol_verify
|
||||||
|
|
||||||
- name: Get MAC addresses for all interfaces
|
# - name: Get MAC addresses for all interfaces
|
||||||
ansible.builtin.set_fact:
|
# ansible.builtin.set_fact:
|
||||||
wol_mac_addresses: >-
|
# wol_mac_addresses: >-
|
||||||
{{
|
# {{
|
||||||
wol_final_interfaces
|
# wol_final_interfaces
|
||||||
| map('extract', hostvars[inventory_hostname]['ansible_' ~ item] | default({}), 'macaddress')
|
# | map('extract', hostvars[inventory_hostname]['ansible_' ~ item] | default({}), 'macaddress')
|
||||||
| list
|
# | list
|
||||||
}}
|
# }}
|
||||||
|
|
||||||
- name: Report WOL configuration
|
# - name: Report WOL configuration
|
||||||
ansible.builtin.debug:
|
# ansible.builtin.debug:
|
||||||
msg: |
|
# msg: |
|
||||||
Wake-on-LAN Configuration Summary:
|
# Wake-on-LAN Configuration Summary:
|
||||||
===================================
|
# ===================================
|
||||||
Bridges Configured: {{ wol_bridges_list | join(', ') }}
|
# Bridges Configured: {{ wol_bridges_list | join(', ') }}
|
||||||
Physical Interfaces: {{ wol_final_interfaces | join(', ') }}
|
# Physical Interfaces: {{ wol_final_interfaces | join(', ') }}
|
||||||
WOL Mode: {{ wol_mode }}
|
# WOL Mode: {{ wol_mode }}
|
||||||
{% if wol_has_bond0 | default(false) %}
|
# {% if wol_has_bond0 | default(false) %}
|
||||||
Bond0 Detected: Yes
|
# Bond0 Detected: Yes
|
||||||
Bond0 Slaves: {{ wol_bond0_slaves | join(', ') }}
|
# Bond0 Slaves: {{ wol_bond0_slaves | join(', ') }}
|
||||||
{% endif %}
|
# {% endif %}
|
||||||
{% if wol_report_mac and wol_mac_addresses | length > 0 %}
|
# {% if wol_report_mac and wol_mac_addresses | length > 0 %}
|
||||||
MAC Addresses:
|
# MAC Addresses:
|
||||||
{% for iface, mac in (wol_final_interfaces | zip(wol_mac_addresses) | list) %}
|
# {% for iface, mac in (wol_final_interfaces | zip(wol_mac_addresses) | list) %}
|
||||||
- {{ iface }}: {{ mac | default('Unable to detect') }}
|
# - {{ iface }}: {{ mac | default('Unable to detect') }}
|
||||||
{% endfor %}
|
# {% endfor %}
|
||||||
{% endif %}
|
# {% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user