Compare commits

..

2 Commits

Author SHA1 Message Date
0ee4990960 Merge branch 'test' of https://repo.piave7.duckdns.org/Jose/ansible_proxmox_WOL into test
Some checks failed
ansible-lint / Ansible Lint (push) Failing after 10s
2025-12-26 11:07:22 +01:00
93d14fedbe refactor ♻️: Refactor ExecStart to use a list for commands and conditionally execute them.
Some checks failed
ansible-lint / Ansible Lint (push) Failing after 9s
This refactoring involves restructuring the `ExecStart` method to utilize a list of commands instead of a single string. This change allows for more flexible execution based on certain conditions, enhancing the modularity and maintainability of the code.
2025-12-26 11:07:00 +01:00
19 changed files with 348 additions and 785 deletions

View File

@@ -1,3 +0,0 @@
{
"MD041": false
}

View File

@@ -1,127 +0,0 @@
---
name: Ansible Best Practices
description: Ansible best practices and conventions
alwaysApply: false
globs:
- "**/*.yml"
- "**/*.yaml"
---
You are an expert Ansible automation engineer.
When working with Ansible content, always follow these rules:
## General
- Prefer **idempotent** solutions; tasks must be safe to run multiple times.
- Use **Ansible built-in modules** instead of shell or command whenever possible.
- Do not assume root access; use `become: true` only when required.
- Avoid hard-coded values; prefer variables, defaults, and group/host vars.
- Use clear, descriptive task names.
- Ensure all YAML is valid, properly indented, and ansible-lint compliant.
- Favor clarity and maintainability over cleverness.
- Add README files to complex directories.
- Document complex algorithms and business rules.
- Maintain up-to-date dependencies list.
## Security Hardening
- **Never embed secrets** directly in playbooks, roles, or templates.
- Use **Ansible Vault**, external secret managers, or injected variables for secrets.
- Mark sensitive tasks with:
```yaml
no_log: true
```
- Avoid leaking secrets via debug, register, or error messages.
- Use least privilege:
- Avoid running entire plays as root.
- Scope become to individual tasks where possible.
- Set secure file permissions explicitly:
```yaml
mode: "0640"
owner: root
group: root
```
- Validate downloaded files using checksums.
- Avoid ignore_errors for security-sensitive operations.
- Do not disable SSL/TLS validation unless explicitly required and documented.
- Prefer validate_certs: true for network modules.
- Assume hosts may be compromised—do not trust remote state blindly.
## Playbooks
- Use `hosts`, `gather_facts`, and `become` explicitly.
- Keep playbooks minimal; delegate logic to roles.
- Apply tags consistently for safe partial execution.
- Use serial for rolling updates to reduce blast radius.
- Avoid large monolithic plays.
## Roles
- Follow the standard role structure:
(`tasks/`, `handlers/`, `defaults/`, `vars/`, `templates/`, `files/`).
- Put overridable values in `defaults/main.yml`.
- Put non-overridable or internal values in `vars/main.yml`.
- Namespace all role variables (role_name_variable).
- Use meta/main.yml to define role dependencies.
- Use handlers only when a change requires a follow-up action.
## Tasks
- Always name tasks clearly and descriptively.
- Use `state: present/absent/latest` explicitly.
- Register variables only when they are actually used.
- Use `changed_when` and `failed_when` to ensure correct task status.
- Avoid `ignore_errors` unless absolutely necessary.
- Avoid shell unless absolutely unavoidable; document why if used.
- Prefer creates and removes when using command-like tasks.
- Avoid unnecessary loops; simplify logic where possible.
## Variables & Templates
- Use snake_case for variable names.
- Quote variables in YAML to prevent parsing issues.
- Namespace role variables (e.g., `nginx_port`, not `port`).
- Avoid complex logic in templates—use when instead.
- Use Jinja2 filters safely and defensively (default, bool, int).
- Do not reference undefined variables without defaults.
## Conditionals & Loops
- Use when for conditionals.
- Prefer `loop` over deprecated `with_*`.
- Use `ansible_facts` instead of shell commands for system data.
- Avoid deeply nested conditionals.
## Error Handling & Validation
- Fail fast on critical errors.
- Use assert to validate assumptions.
- Use check_mode compatibility whenever possible.
- Ensure tasks behave correctly in --diff and --check.
## Linting & Compatibility
- Code must comply with ansible-lint.
- Write code compatible with recent Ansible versions.
- Avoid deprecated modules and syntax.
- Do not rely on undefined behavior or undocumented features.
## Performance & Reliability (Tips & Tricks)
- Use gather_facts: false if facts are not needed.
- Use run_once and delegate_to when appropriate.
- Cache facts when operating at scale.
- Avoid repeated expensive operations.
- Prefer block for grouping related tasks and error handling.
## Output Expectations
- Generated YAML must be valid and properly indented.
- Provide minimal but sufficient comments when clarity is needed.
- Do not include explanations unless explicitly requested.
- Assume production usage and security-sensitive environments.

View File

@@ -1,159 +0,0 @@
---
name: Project README Standards
globs: "**/README.md"
alwaysApply: false
description: Guidelines for creating comprehensive project README files
tags:
- readme
- documentation
- markdown
- project-setup
- best-practices
---
You are an expert in:
- technical documentation
- open source best practices
- developer experience.
## README Structure
A well-structured README should include these sections in order:
1. Project Title and Description
2. Badges (build status, version, license)
3. Key Features
4. Screenshots/Demo (if applicable)
5. Quick Start
6. Installation
7. Usage Examples
8. API Reference (or link to docs)
9. Configuration
10. Contributing
11. License
## Essential Sections
### Project Header
```markdown
# Project Name
> One-line description of what this project does
[![Build Status](https://img.shields.io/github/actions/workflow/status/user/repo/ci.yml)](https://github.com/user/repo/actions)
[![npm version](https://img.shields.io/npm/v/package-name)](https://www.npmjs.com/package/package-name)
[![License](https://img.shields.io/github/license/user/repo)](LICENSE)
Brief paragraph explaining the project's purpose, main features,
and why someone would want to use it.
```
### Quick Start
```markdown
## Quick Start
Get up and running in less than 5 minutes:
\`\`\`bash
npm install package-name
npm run dev
\`\`\`
Visit http://localhost:3000 to see the application.
```
### Installation
```markdown
## Installation
### Prerequisites
- Node.js 18+
- PostgreSQL 14+
- Redis 6.2+
### Install from npm
\`\`\`bash
npm install package-name
\`\`\`
### Install from source
\`\`\`bash
git clone https://github.com/user/repo.git
cd repo
npm install
npm run build
\`\`\`
```
### Usage Examples
```markdown
## Usage
### Basic Example
\`\`\`javascript
import { Widget } from 'package-name';
const widget = new Widget({
apiKey: 'your-api-key',
theme: 'dark'
});
widget.render('#app');
\`\`\`
### Advanced Example
\`\`\`javascript
// Custom configuration with error handling
const widget = new Widget({
apiKey: process.env.API_KEY,
theme: 'dark',
onError: (error) => {
console.error('Widget error:', error);
}
});
// Add custom event handlers
widget.on('ready', () => {
console.log('Widget is ready');
});
widget.render('#app');
\`\`\`
```
## Best Practices
- Keep the README focused and concise
- Use clear, simple language
- Include code examples that actually work
- Add visuals when they help understanding
- Link to more detailed documentation
- Keep examples up-to-date with the code
- Test your installation instructions regularly
## Common Mistakes to Avoid
- Don't assume reader knowledge
- Don't skip the Quick Start section
- Don't use jargon without explanation
- Don't forget to update version numbers
- Don't include sensitive information
## Formatting Tips
- Use consistent heading levels
- Include a table of contents for long READMEs
- Use code blocks with language highlighting
- Add alt text to images
- Use tables for comparing options
- Include emoji sparingly and purposefully

View File

@@ -1,37 +0,0 @@
---
# https://github.com/kekxv/AiReviewPR
name: ai-reviews
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
name: Review PR
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
# Number of commits to fetch. 0 indicates all history for all
# branches and tags.
# Default: 1
fetch-depth: 0
# The base URL for the GitHub instance that you are trying to clone
# from, will use environment defaults to fetch from the same instance
# that the workflow is running from unless specified.
# Example URLs are https://github.com or
# https://my-ghes-server.example.com
github-server-url: ${{ vars.GIT_SERVER_URL }}
- name: Review code
uses: kekxv/AiReviewPR@v0.1.0
with:
model: ${{ vars.OLLAMA_MODEL }}
host: http://192.168.2.233:11435
# host: ${{ vars.OLLAMA_HOST }}
# ai_token: ${{ secrets.AI_TOKEN }}
REVIEW_PULL_REQUEST: false
LANGUAGE: English

View File

@@ -1,17 +1,15 @@
--- # .github/workflows/ansible-lint.yml
# .gitea/workflows/ansible-lint.yml
name: ansible-lint name: ansible-lint
on: [pull_request, issues, push] on: [pull_request, issues, push]
jobs: jobs:
build: build:
name: Ansible Lint name: Ansible Lint # Naming the build is important to use it as a status check
# Naming the build is important to use it as a status check
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v6 uses: actions/checkout@v4
with: with:
github-server-url: ${{ vars.GIT_SERVER_URL }} github-server-url: ${{ vars.GIT_SERVER_URL }}
@@ -23,11 +21,7 @@ jobs:
- name: Install ansible-lint - name: Install ansible-lint
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install ansible ansible-lint yamllint pip install ansible ansible-lint
- name: Run yamllint
run: |
yamllint .
- name: Run ansible-lint - name: Run ansible-lint
run: | run: |

View File

@@ -1,35 +0,0 @@
---
name: Gitleaks Scan
on:
push:
pull_request:
jobs:
gitleaks:
runs-on: ubuntu-latest
steps:
- name: Install Gitleaks
run: |
curl -sSL https://github.com/gitleaks/gitleaks/releases/download/v8.30.0/gitleaks_8.30.0_linux_x64.tar.gz \
| tar -xz
sudo mv gitleaks /usr/local/bin/
- name: Checkout code
uses: actions/checkout@v6
with:
github-server-url: ${{ vars.GIT_SERVER_URL }}
- name: Run Gitleaks
run: |
gitleaks dir . \
--redact=10 \
--verbose \
--exit-code 1
# gitleaks detect \
# --source . \
# --no-git \
# --redact=20 \
# --verbose \
# --exit-code 1

View File

@@ -1,71 +0,0 @@
---
# .gitea/workflows/markdown-lint.yml
name: Markdown Lint
on: [pull_request, issues, push]
jobs:
build:
name: markdown-lint
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
github-server-url: ${{ vars.GIT_SERVER_URL }}
- name: Install Node.js & markdownlint
run: |
apt-get update && apt-get install -y npm
npm install -g markdownlint-cli2
- name: Run lint
run: markdownlint-cli2 "**/*.md" "#node_modules"
# on:
# push:
# branches:
# - main
# pull_request:
# branches:
# - main
# jobs:
# markdown-lint:
# runs-on: docker
# container:
# image: node:20-alpine
# steps:
# - name: Install dependencies
# run: |
# apk add --no-cache git
# npm install -g markdownlint-cli2
# - name: Run Markdown lint
# run: |
# markdownlint-cli2 "**/*.md" "#node_modules"
#########################################à
# ---
# https://github.com/marketplace/actions/markdownlint-cli2-action
# name: Markdown Lint
# on: [pull_request, push]
# jobs:
# build:
# name: markdown-lint
# runs-on: ubuntu-latest
# steps:
# - name: Checkout code
# uses: actions/checkout@v4
# with:
# github-server-url: ${{ vars.GIT_SERVER_URL }}
# - name: Markdown lint
# uses: DavidAnson/markdownlint-cli2-action@v22
# with:
# globs: '**/*.md'
# fix: true
# continue-on-error: true

View File

@@ -1,72 +0,0 @@
---
# This workflow warns and then closes issues and PRs that have
# had no activity for a specified amount of time.
#
# You can adjust the behavior by modifying this file.
# For more information, see:
# https://github.com/actions/stale
name: Mark stale issues and pull requests
on:
schedule:
- cron: '21 3 * * *'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write # for actions/stale to close stale issues
pull-requests: write # for actions/stale to close stale PRs
steps:
# - name: Checkout code
# uses: actions/checkout@v4
# with:
# # Number of commits to fetch. 0 indicates all history for all branches
# # and tags.
# # Default: 1
# fetch-depth: 0
# # The base URL for the GitHub instance that you are trying to clone from,
# # will use environment defaults to fetch from the same instance that the
# # workflow is running from unless specified.
# # Example URLs are https://github.com or
# # https://my-ghes-server.example.com
# github-server-url: ${{ vars.GIT_SERVER_URL }}
# The 90 day stale policy
# Used for:
# - Issues & PRs
# - No PRs marked as no-stale or pinned
# - No issues marked as no-stale, help-wanted or pinned
- name: 90 days stale issues & PRs policy
uses: actions/stale@v9.1.0
with:
# repo-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
days-before-stale: 90
days-before-close: 7
operations-per-run: 150
remove-stale-when-updated: true
stale-issue-label: "stale"
exempt-issue-labels: "no-stale,help-wanted,pinned,enhancement"
stale-issue-message: >
There hasn't been any activity on this issue recently. To keep our
backlog manageable we have to clean old issues, as many of them have
already been resolved with the latest updates.
Please make sure to update to the latest version and check if that
solves the issue. Let us know if that works for you by adding a
comment 👍
This issue has now been marked as stale and will be closed if no
further activity occurs. Thank you for your contributions.
stale-pr-label: "stale"
exempt-pr-labels: "no-stale,pinned"
stale-pr-message: >
There hasn't been any activity on this pull request recently. This
pull request has been automatically marked as stale because of that
and will be closed if no further activity occurs within 7 days.
Thank you for your contributions.

View File

@@ -1,7 +0,0 @@
{
"MD013": {
"line_length": 100,
"code_block_line_length": 120,
"tables": false
}
}

View File

@@ -1,2 +0,0 @@
# ignore files completely
.continue/rules/**/*.md

View File

@@ -1,23 +0,0 @@
---
# This is my first, very own configuration file for yamllint!
# It extends the default conf by adjusting some options.
extends: default
rules:
comments-indentation: disable # don't bother me with this rule
truthy:
allowed-values: ['true', 'false', 'yes', 'no', 'on']
comments:
min-spaces-from-content: 1
braces:
max-spaces-inside: 1
octal-values:
forbid-implicit-octal: true
forbid-explicit-octal: true
line-length:
max: 120
allow-non-breakable-words: true

282
README.md
View File

@@ -1,110 +1,51 @@
# ansible_proxmox_WOL # ansible_proxmox_WOL
An Ansible role that configures **persistent WakeonLAN (WOL)** on Proxmox VE hosts. A robust, idempotent Ansible role for enabling persistent Wake-on-LAN (WOL) on Proxmox VE servers. This role automatically detects physical network interfaces with WOL capability using Ansible facts and persistently enables WOL via udev rules.
It discovers all physical Ethernet interfaces, validates WOL support, and then enables or disables
WOL on the interfaces that back the bridges you specify. Unlike many WOL setups that rely on
*udev* rules, this role uses a lightweight **systemd template unit** (`wol@.service`) so the setting
is applied automatically each time an interface comes up, and it survives reboots without any extra
steps.
---
## Table of Contents
- [Features](#features)
- [Prerequisites](#prerequisites)
- [Role Variables](#role-variables)
- [How It Works](#how-it-works)
- [Usage](#usage)
- [Common Proxmox Scenarios](#common-proxmox-scenarios)
- [Troubleshooting](#troubleshooting)
- [License](#license)
- [Author](#author)
---
## Features ## Features
| Feature | Description | **Fully Idempotent**: Checks current WOL status and only applies changes when needed
|---------|-------------| **Bridge Support**: Automatically detects physical interfaces backing configured bridges
| ✅ Fully idempotent | Only touches an interface if the desired WOL mode differs from what `ethtool` currently reports. | **Bond0 Detection**: Automatically detects and configures bonded interfaces
| ✅ Multiple bridge support | Pass a single string or a list of bridge names to `wol_bridges`. | **Ansible Facts-Based**: Uses Ansible facts to detect and validate WOL-capable interfaces
| ✅ Bond0 detection | If a bridge is built on a bond (e.g., `bond0`), all slaves receive the same WOL configuration. | **Persistent**: Uses udev rules for persistence across reboots
| ✅ Factsdriven | Uses Ansible facts (`ansible_interfaces`) to filter out virtual and nonEthernet devices. | **Comprehensive Validation**: Verifies WOL capability before configuration
| ✅ Persistent via systemd | WOL is enforced by a `wol@.service` template that is started for each enabled interface. | **Detailed Reporting**: Shows configuration status and MAC addresses for WOL senders
| ✅ Comprehensive validation | Each interface is queried with `ethtool` to confirm real WOL support before making any changes. |
| ✅ Detailed reporting | The role prints a summary of every interface it touched, including the MAC addresses of WOLcapable senders when `wol_report_mac` is `true`. |
---
## Prerequisites
| Requirement | How the role satisfies it |
|-------------|---------------------------|
| **Proxmox VE host** | Target host runs Debianbased Proxmox (the role uses Debian/Proxmox defaults). |
| **Ansible ≥2.9** | The role only uses builtin modules (`setup`, `package`, `command`, `template`, `systemd`). |
| **ethtool** | The role installs the `ethtool` package if it is not already present. |
| **Root / sudo access** | All tasks modify network configuration; `become: true` is required. |
| **BIOS/WMI WOL** | WOL must be enabled in the host BIOS and the NIC driver must expose the `WakeOnLAN` flag. |
---
## Role Variables ## Role Variables
```yaml | Variable | Default | Type | Description |
# defaults/main.yml |----------|---------|------|-------------|
wol_bridges: vmbr0 # string or list bridges that should have WOL enabled | `wol_bridges` | `vmbr0` | string/list | Bridge interface(s) to enable WOL on. Can be a single bridge as string or multiple bridges as a list. |
wol_mode: "g" # "g"=magic packet (recommended), "d"=disable, "p"/"u"/"m"/"b" for other modes | `wol_mode` | `g` | string | WOL mode: `g` (magic packet - recommended), `d` (disable), `p` (physical activity), `u` (unicast), `m` (multicast), `b` (broadcast) |
wol_verify: true # Verify the interface state after changes | `wol_verify` | `true` | boolean | Verify WOL status after configuration and display results |
wol_report_mac: true # Include MAC addresses of WOL-capable senders in the report | `wol_report_mac` | `true` | boolean | Include MAC addresses in configuration report |
```
| Variable | Default | Type | Description |
|----------------|---------|-----------|-------------|
| `wol_bridges` | `vmbr0` | string/list | Bridge(s) to configure. If you pass a string, the role treats it as a singleitem list. |
| `wol_mode` | `"g"` | string | Desired WOL mode. See the variable table above for valid options. |
| `wol_verify` | `true` | bool | Whether to run `ethtool` again after the changes and include the result in the final report. |
| `wol_report_mac` | `true` | bool | Whether to list the MAC address of each NIC that will send WOL packets. |
---
## How It Works ## How It Works
1. **Package Install** Ensures the `ethtool` binary is present. 1. **Package Installation**: Ensures `ethtool` is installed for WOL management
2. **Interface Discovery** Uses `ansible_facts.ansible_interfaces` to collect *all* interfaces, 2. **Interface Discovery**: Uses Ansible facts to identify all physical Ethernet interfaces
then filters out virtual ones (`veth*`, `tap*`, `docker*`, etc.). 3. **Bridge Mapping**: Detects physical interfaces backing configured bridges
3. **WOL Validation** For each remaining physical NIC, `ethtool <iface> | grep 'Wake-on'` 4. **Bond0 Detection**: Identifies bonded interfaces and their slaves
is used to confirm that the NIC supports WOL. 5. **WOL Validation**: Tests each interface for Wake-on-LAN capability using ethtool
4. **Bridge Mapping** Resolves each bridge name in `wol_bridges` to the physical interface(s). 6. **Idempotency Check**: Reads current WOL status to avoid redundant changes
If a bridge is built on a bond (e.g., `bond0`), every slave is treated as a candidate. 7. **Enable WOL**: Applies WOL settings only to interfaces that need it
5. **Idempotency Check** The current WOL state (`wol_enabled`) is compared to `wol_mode`. 8. **Persist Settings**: Creates udev rules for persistence across reboots
6. **Apply WOL** 9. **Reload Udev**: Reloads udev rules and triggers network interface refresh
- If `wol_mode` ≠ `'d'` and the current mode differs, `ethtool -s <iface> wol <mode>` is run. 10. **Verification & Reporting**: Displays WOL configuration status and MAC addresses
- If `wol_mode` is `'d'`, the role ensures WOL is disabled.
7. **Deploy systemd template** Copies `templates/wol@.service.j2` to `/etc/systemd/system/wol@.service`.
The template contains `ExecStart=/usr/sbin/ethtool -s %I wol {{ wol_mode }}`.
8. **Enable service per interface** For every interface, the role starts the unit
`wol@<iface>.service` and enables it to run on boot.
9. **Report** A final summary is printed, optionally listing MAC addresses if `wol_report_mac`
is `true`.
--- ## Usage Examples
## Usage
### Basic Playbook
### Basic Single Bridge (Auto-Detection)
```yaml ```yaml
- hosts: proxmox - hosts: proxmox
become: true become: true
roles: roles:
- ansible_proxmox_WOL - ansible_proxmox_WOL
``` ```
Automatically configures WOL for the default `vmbr0` bridge.
> **Result** WOL is automatically enabled on the physical NIC that backs the default `vmbr0` bridge.
### Multiple Bridges ### Multiple Bridges
```yaml ```yaml
- hosts: proxmox - hosts: proxmox
become: true become: true
@@ -116,25 +57,9 @@ is `true`.
- vmbr1 - vmbr1
- vmbr2 - vmbr2
``` ```
Configures WOL for multiple bridge interfaces simultaneously.
> **Result** All three bridges are processed; the physical NICs that belong to each bridge ### Custom Bridge with Verification Disabled
> receive the configured WOL mode.
### Disable WOL
```yaml
- hosts: proxmox
become: true
roles:
- role: ansible_proxmox_WOL
vars:
wol_mode: d
```
> **Result** WOL is disabled on every physical NIC that the role discovered, regardless of bridge.
### Turn Off Verification
```yaml ```yaml
- hosts: proxmox - hosts: proxmox
become: true become: true
@@ -145,38 +70,151 @@ is `true`.
wol_verify: false wol_verify: false
``` ```
> **Result** WOL is set on `vmbr1`s backing NIC(s) but the role does not perform a postchange check. ### Disable WOL
```yaml
- hosts: proxmox
become: true
roles:
- role: ansible_proxmox_WOL
vars:
wol_mode: d
```
--- ## Bond0 Support
The role automatically detects if configured bridges are backed by bonded interfaces (bond0). When bond0 is detected:
- The underlying physical slave interfaces are identified
- All slaves are configured with the same WOL settings
- The configuration is displayed in the summary report
Example output when bond0 is detected:
```
Bond0 Detected: Yes
Bond0 Slaves: eth0, eth1
Physical Interfaces: bond0
```
## Common Proxmox Scenarios ## Common Proxmox Scenarios
| Scenario | Bridge / NIC Layout | What the role does | ### Scenario 1: Standard vmbr0 Setup
|----------|-------------------|------------------| ```
| **Standard vmbr0** | `eno1``vmbr0` | Enables WOL on `eno1`. | Physical NIC (eno1) → vmbr0 bridge
| **Bonded NICs** | `eno1`, `eno2``bond0``vmbr0` | Detects `bond0` and sets WOL on *both* slaves. | ```
| **Multiple Bridges** | - `eno1``vmbr0` - `eno2``vmbr1` - `eno3`, `eno4``bond0``vmbr2` | One role run configures all three bridges automatically. | The role automatically configures `eno1` with WOL settings.
--- ### Scenario 2: Bonded Interface
```
Physical NICs (eno1, eno2) → bond0 → vmbr0 bridge
```
The role detects bond0 and applies WOL to bonded slaves.
### Scenario 3: Multiple Bridges
```
eno1 → vmbr0
eno2 → vmbr1
bond0 (eno3, eno4) → vmbr2
```
Configure all bridges with one role application:
```yaml
wol_bridges:
- vmbr0
- vmbr1
- vmbr2
```
## Prerequisites
- **Proxmox VE** host with bridge interfaces configured
- **Ansible** 2.9+
- **ethtool** package (installed automatically by role)
- **Root/sudo access** on target host (required for udev and ethtool)
- **BIOS Configuration**:
- Wake-on-LAN enabled in BIOS
- ErP (Energy-Related Products) disabled in BIOS
## Idempotency
This role is fully idempotent. Running it multiple times has the same effect as running it once:
- ✅ Only enables WOL on interfaces that don't already have it enabled
- ✅ Skips udev rule reload if rules haven't changed
- ✅ Uses `changed_when` conditions to accurately report actual changes
- ✅ Safe to include in recurring Ansible playbooks and AWX workflows
## Safety
- **Non-Destructive**: Never disables interfaces or changes bridge configuration
- **Validation**: Verifies NIC WOL capability before making changes
- **Error Handling**: Fails gracefully with clear error messages if:
- Bridges cannot be detected
- Physical NICs cannot be found
- NICs don't support Wake-on-LAN
- **Check Mode Support**: Fully compatible with `--check` mode for safe preview
## Implementation Details
### Persistence Method
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:
```
ACTION=="add", SUBSYSTEM=="net", KERNEL=="eno1", RUN+="/sbin/ethtool -s eno1 wol g"
```
### Detection Logic
1. **Interface Discovery**: Uses Ansible facts to enumerate all network interfaces
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 ## Troubleshooting
| Problem | Likely Cause | Fix | ### "No network interfaces found that support Wake-on-LAN"
|---------|--------------|-----| - Check BIOS settings to ensure WOL is enabled
| **No interfaces found that support WOL** | BIOS WOL disabled, NIC driver doesnt expose the feature, or interface isnt a physical NIC. | Enable WOL in BIOS, run `ansible -m setup <host> \| grep ansible_interfaces`. | - Verify NIC drivers support WOL: `ethtool <interface>`
| **Unable to detect bridge backing NIC(s)** | Bridge doesnt exist or the NIC isnt a member of it. | Verify with `bridge link show` / `brctl show`. | - Some NICs may require specific BIOS settings or driver parameters
| **WOL not persisting after reboot** | `wol@.service` isnt enabled, or `ethtool` isnt installed. | Ensure the role ran successfully, check `/etc/systemd/system/wol@.service` and `systemctl status wol@<iface>.service`. | - Check if interfaces are properly detected: `ansible -m setup <host> | grep ansible_interfaces`
| **Bond0 not detected** | Bond configuration file missing or wrong. | Check `/proc/net/bonding/bond0`. |
| **WOL mode unsupported** | NIC driver only supports a subset of modes. | Try a different `wol_mode` value (e.g., `p`, `u`, `m`, `b`). |
--- ### "Unable to detect physical NIC backing bridge(s)"
- Verify bridges exist: `bridge link show`
- Check bridge configuration: `brctl show`
- Ensure physical NIC is member of bridge
- Confirm the backing interface supports WOL (listed in "Available WOL-capable interfaces")
### "Does not support Wake-on-LAN"
- Check NIC capabilities: `ethtool <interface>`
- Verify BIOS has WOL enabled for the specific NIC
- Some NICs have disabled WOL by default (check driver documentation)
- Try different WOL modes: `p`, `u`, `m`, or `b`
### WOL not persisting after reboot
- Check udev rules: `cat /etc/udev/rules.d/90-wol.rules`
- Verify ethtool installed: `which ethtool`
- Check system logs: `journalctl -u systemd-udevd -b`
- Ensure udev service is running: `systemctl status systemd-udevd`
### Bond0 not detected
- Check bond status: `cat /proc/net/bonding/bond0`
- Verify bond is backing the configured bridge
- Check bond slave interfaces support WOL individually
## Notes for Proxmox Admins
- **Default Bridge**: Proxmox typically uses `vmbr0` as the default management bridge
- **No DHCP Changes**: This role only configures WOL; it doesn't modify IP configuration
- **Performance Impact**: WOL has negligible performance impact
- **Network Redundancy**: If using bonds or multiple bridges, all configured interfaces will be enabled for WOL
## License ## License
MIT MIT
---
## Author ## Author
Ansible Proxmox WOL Contributors Ansible Proxmox WOL Contributors

View File

@@ -1,4 +1,11 @@
--- ---
# ============================================================
# Bridge interfaces to enable Wake-on-LAN on
# Can be a string (single bridge) or list (multiple bridges)
# Supports detection of physical NICs backing bridges
# ============================================================
wol_bridges: vmbr0
# ============================================================ # ============================================================
# WOL mode options: # WOL mode options:
# g = magic packet (most common, highly recommended) # g = magic packet (most common, highly recommended)
@@ -18,5 +25,3 @@ wol_verify: true
# Report MAC addresses for WOL packet senders # Report MAC addresses for WOL packet senders
wol_report_mac: true wol_report_mac: true
wol_port: 9

View File

@@ -1,4 +1,20 @@
--- ---
- name: Reload systemd - name: Reload systemd
ansible.builtin.systemd: ansible.builtin.systemd:
daemon_reload: yes
- name: Reload systemd and restart WOL
ansible.builtin.systemd:
name: wol
daemon_reload: true daemon_reload: true
enabled: true
state: restarted
- name: Reload_udev_rules
ansible.builtin.command: udevadm control --reload
changed_when: false
- name: Trigger_udev_net
ansible.builtin.command: udevadm trigger --subsystem-match=net
changed_when: false

View File

@@ -4,9 +4,7 @@
# ============================================================ # ============================================================
- name: Install required packages - name: Install required packages
ansible.builtin.apt: ansible.builtin.apt:
name: name: ethtool
- ethtool
- wakeonlan
state: present state: present
update_cache: true update_cache: true
@@ -28,132 +26,161 @@
# ansible.builtin.set_fact: # ansible.builtin.set_fact:
# en_interfaces: "{{ ansible_facts.interfaces | select('match', '^eth|^ens|^enp') | unique | list }}" # en_interfaces: "{{ ansible_facts.interfaces | select('match', '^eth|^ens|^enp') | unique | list }}"
- name: Get interfaces starting with "en, "eth" or "nic" - name: Get interfaces starting with "en" or "eth"
ansible.builtin.set_fact: ansible.builtin.set_fact:
en_interfaces: >- en_interfaces: >-
{{ {{
ansible_facts.interfaces ansible_facts.interfaces
| select('match', '^(eth|en|nic)') | select('match', '^(eth|en)')
| list | list
}} }}
- name: Display debug selected interfaces # ============================================================
ansible.builtin.debug: # Detect physical interfaces backing configured bridges
msg: > # ============================================================
{{ en_interfaces }} - name: Get physical interfaces for configured bridges
ansible.builtin.command: bridge link show {{ item }}
- name: Check supported Wake-on-LAN modes register: bridge_links
ansible.builtin.shell: | loop: "{{ wol_bridges | list }}"
set -o pipefail
ethtool {{ item }} | grep 'Supports Wake-on' | tail -1 | awk '{print $3}'
args:
executable: /bin/bash
changed_when: false
loop: "{{ en_interfaces }}"
register: wol_supported
when: en_interfaces | length > 0
- name: WOL | Check if enabled
ansible.builtin.shell: |
set -o pipefail
ethtool {{ item }} | grep 'Wake-on' | tail -1 | awk '{print substr($0,length,1)}'
args:
executable: /bin/bash
changed_when: false changed_when: false
failed_when: false failed_when: false
loop: "{{ en_interfaces }}"
register: wol_enabled
when: en_interfaces | length > 0
- name: "Set Wake-on-LAN to {{ wol_mode }}" - 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: "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: "{{ wol_final_interfaces }}"
register: wol_supported
changed_when: false
when: wol_final_interfaces | length > 0
- 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: "{{ wol_final_interfaces }}"
when: wol_final_interfaces | length > 0
# ============================================================
# Enable or disable WOL as needed
# ============================================================
- name: Set Wake-on-LAN mode
ansible.builtin.command: "ethtool -s {{ item.0 }} wol {{ wol_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: loop_control:
label: "{{ item.0 }}" label: "{{ item.0 }}"
when: when:
- item.1.stdout is defined
- item.2.stdout is defined
- wol_mode not in item.1.stdout - wol_mode not in item.1.stdout
- wol_mode in item.2.stdout - wol_mode in item.2.stdout
changed_when: true
- name: "Disable Wake-on-LAN" # ============================================================
ansible.builtin.command: "ethtool -s {{ item.0 }} wol {{ wol_mode }}" # Create udev rules for persistence
loop: "{{ en_interfaces | zip(wol_enabled.results, wol_supported.results) | list }}" # ============================================================
loop_control: - name: Create udev rule for WOL persistence
label: "{{ item.0 }}"
when:
- wol_mode == 'd'
- wol_mode not in item.1.stdout
changed_when: true
- name: Deploy wol systemd template
ansible.builtin.template: ansible.builtin.template:
src: templates/wol@.service.j2 src: 90-wol.rules.j2
dest: /etc/systemd/system/wol@.service dest: /etc/udev/rules.d/90-wol.rules
owner: root
group: root
mode: '0644' mode: '0644'
notify: Reload systemd notify:
when: en_interfaces | length > 0 - Reload_udev_rules
- Trigger_udev_net
- name: Enable WOL systemd unit for each interface
ansible.builtin.systemd:
name: "wol@{{ item }}.service"
enabled: true
state: started
loop: "{{ en_interfaces }}"
when: en_interfaces | length > 0
- name: Get MAC addresses
# ============================================================
# 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: Get MAC addresses for all interfaces
ansible.builtin.set_fact: ansible.builtin.set_fact:
wol_mac_addresses: >- wol_mac_addresses: >-
{{ wol_mac_addresses | default([]) + [ hostvars[inventory_hostname]['ansible_' ~ item].macaddress ] }} {{
loop: "{{ en_interfaces }}" wol_final_interfaces
when: en_interfaces | length > 0 | map('extract', ansible_facts, attribute='macaddress')
| 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:
=================================== ===================================
Physical Interfaces: {{ en_interfaces | join(', ') }} Bridges Configured: {{ wol_bridges | join(', ') }}
Physical Interfaces: {{ wol_final_interfaces | join(', ') }}
WOL Mode: {{ wol_mode }} WOL Mode: {{ wol_mode }}
MAC Addresses: {{ wol_mac_addresses | join(', ') }} {% if bond0_stat.stat.exists %}
Bond0 Detected: Yes
- name: Start tcpdump to capture WOL packet Bond0 Slaves: {{ bond0_slaves.stdout_lines | join(', ') }}
become: true {% endif %}
ansible.builtin.shell: | {% if wol_report_mac and wol_mac_addresses | length > 0 %}
timeout 10 tcpdump -i {{ en_interfaces[0] }} -nn -c 1 \ MAC Addresses:
'udp and port {{ wol_port }} and (udp[8:4] = 0xffffffff)' {% for iface, mac in (wol_final_interfaces | zip(wol_mac_addresses) | list) %}
register: tcpdump_result - {{ iface }}: {{ mac | default('Unable to detect') }}
async: 12 {% endfor %}
poll: 0 {% endif %}
changed_when: false
- name: Give tcpdump time to start
ansible.builtin.pause:
seconds: 1
# - name: Send WOL packet via shell
# ansible.builtin.shell: wakeonlan {{ wol_mac_addresses[0] }}
# delegate_to: localhost
- name: Send Wake-on-LAN packet from localhost
community.general.wakeonlan:
mac: "{{ wol_mac_addresses[0] }}"
port: "{{ wol_port }}"
broadcast: 255.255.255.255
# delegate_to: localhost
- name: Wait for tcpdump to finish
ansible.builtin.async_status:
jid: "{{ tcpdump_result.ansible_job_id }}"
register: tcpdump_status
until: tcpdump_status.finished
retries: 12
delay: 1
- name: Check if WOL packet was received
ansible.builtin.assert:
that:
- tcpdump_status.rc == 0
success_msg: "✅ Wake-on-LAN magic packet received by host"
fail_msg: "❌ Wake-on-LAN magic packet NOT detected"
changed_when: tcpdump_status.rc == 0

View File

@@ -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 %}

View File

@@ -0,0 +1,18 @@
[Unit]
Description=Set Wake-on-LAN on network interfaces
After=network.target
Wants=network.target
[Service]
Type=oneshot
ExecStart=/bin/bash -c '{% set cmds = [] %}
{% for intf, enabled, supported in en_interfaces | zip(wol_enabled.results, wol_supported.results) %}
{% if wol_mode in supported.stdout and wol_mode not in enabled.stdout %}
{% set _ = cmds.append("/sbin/ethtool -s " ~ intf ~ " wol " ~ wol_mode) %}
{% endif %}
{% endfor %}
{{ cmds | join("; ") if cmds | length > 0 else ":" }}'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target

11
templates/wol.service.j2 Normal file
View File

@@ -0,0 +1,11 @@
[Unit]
Description=Enable Wake-on-LAN on {{ wol_final_interface }}
After=network.target
[Service]
Type=oneshot
ExecStart=/sbin/ethtool -s {{ wol_final_interface }} wol {{ wol_mode }}
Restart=always
[Install]
WantedBy=multi-user.target

View File

@@ -1,13 +0,0 @@
# /etc/systemd/system/wol@.service
[Unit]
Description=WakeonLAN configuration for interface %I
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/sbin/ethtool -s %I wol {{ wol_mode }}
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target