Squashed commit of the following:

commit bf4873eb012235b4d82cd79c4f508064ead301ab
Author: Joao Porta <jpedro.porta@gmail.com>
Date:   Fri Jul 17 17:24:26 2026 -0300

    wake on lan
This commit is contained in:
2026-07-17 17:25:16 -03:00
parent 85d5fa453a
commit cdbad8910c
8 changed files with 243 additions and 10 deletions
+59
View File
@@ -0,0 +1,59 @@
## Context
`jpporta-nixos` is a desktop machine with a wired Ethernet interface (`enp14s0`, MAC `d8:43:ae:5a:ae:12`) managed by NetworkManager. The user wants to power the machine on remotely via Wake-on-LAN so it can be reached over Tailscale + SSH without manual intervention.
WOL requires three layers of configuration:
1. **Firmware (BIOS/UEFI):** "Wake on LAN" must be enabled on the NIC, and the machine must remain in a low-power state after shutdown (most desktop boards default to this; some servers default to "power on after AC loss" which would defeat the purpose).
2. **Kernel/driver (NixOS):** The NIC driver must be told to keep listening for magic packets while the system is off. For most Intel NICs this means `ethtool -s <iface> wol g` (magic packet mode).
3. **Network:** Magic packets are sent to the layer-2 broadcast address (`ff:ff:ff:ff:ff:ff`) and rely on the NIC matching its own MAC. The host cannot route or filter them above the NIC, so no userspace daemon is required to *receive* a packet. A userspace tool (`wakeonlan`) is required to *send* one.
NixOS already provides `networking.wakeOnLan.enable`, but that option is hard-coded to `eth0` and is incompatible with predictable interface names (`enp14s0`). The NixOS-supported path for a named, predictable interface is a custom systemd unit that invokes `ethtool` against the specific interface. This is the approach used here because it survives reboots, resumes, and NetworkManager reconnects.
## Goals / Non-Goals
**Goals:**
- Persist `wol g` on `enp14s0` across reboots and resume-from-suspend.
- Provide `ethtool` and `wakeonlan` on `$PATH` so the user can inspect and send magic packets.
- Provide a convenience script `wake-jpporta-nixos` that sends a WOL packet to this machine's MAC.
- Wrap the configuration in a reusable module (`custom.wake-on-lan.enable`) consistent with existing patterns.
- Document the out-of-band BIOS / router configuration steps.
**Non-Goals:**
- Setting BIOS options (firmware is out of reach from NixOS).
- Configuring router port-forwarding for WOL-over-internet (router is out of scope).
- Listening for / relaying WOL packets on this host — that is the NIC's job once `wol g` is set.
- Changing the network interface naming scheme or replacing NetworkManager.
## Decisions
1. **Per-interface systemd unit over `networking.wakeOnLan.enable`.**
- The top-level `networking.wakeOnLan.enable` is hard-coded to `eth0`, which doesn't match the predictable interface name `enp14s0` used here.
- Alternative considered: `networking.interfaces.enp14s0.wakeOnLan.enable = true`. This option does exist in modern NixOS, but on this machine it interacts awkwardly with NetworkManager (which re-loads NIC settings on reconnect) and the underlying implementation still relies on an ethtool invocation that may be racy with NetworkManager bringing the link up.
- **Decision:** Use a dedicated systemd `oneshot` service with a `network-online.target` dependency. This makes the WOL setting idempotent, runs after NetworkManager is up, and can also be triggered on resume-from-suspend by adding it to the `suspend.target` resume path. Concretely we ship a unit `wol-enp14s0.service` that runs `ethtool -s enp14s0 wol g`.
2. **Package set: `ethtool` + `iputils` (which provides `wakeonlan`).**
- `pkgs.ethtool` is the standard tool for inspecting and setting NIC driver options, including WOL modes.
- `pkgs.iputils` provides `wakeonlan`, the simplest CLI for sending a magic packet to a target MAC.
- Alternative considered: `pkgs.wol` (the separate, older `wol` program). `wakeonlan` from iputils is more widely used, has a simpler interface (`wakeonlan <mac>`), and is what most WOL guides assume.
3. **Convenience wrapper script `wake-jpporta-nixos`.**
- Hard-codes the MAC of this machine's wired NIC (`d8:43:ae:5a:ae:12`) into a small `writeShellScriptBin`-packaged script.
- Rationale: avoids having to remember or look up the MAC every time. The wrapper accepts an optional broadcast IP argument so the user can target `192.168.0.255` from off-LAN once the router is configured to forward UDP 9 to that broadcast.
4. **Module pattern: `modules/nixos/wake-on-lan/default.nix` with `custom.wake-on-lan.enable`.**
- Mirrors the existing pattern used by `modules/nixos/tailscale/` and `modules/nixos/hyprland/`.
- Defaults to disabled. Host (`jpporta-nixos`) opts in via `custom.wake-on-lan.enable = true`.
## Risks / Trade-offs
- **[Risk]** `ethtool -s ... wol g` only takes effect while the kernel NIC driver is loaded. Some BIOS/firmware combinations fully power down the NIC on shutdown, making WOL impossible regardless of the OS setting.
- **Mitigation:** Document this clearly in the change notes. The user must verify with `ethtool enp14s0` after a `systemctl poweroff` that `Supports Wake-on: pumbg` and `Wake-on: g` are still present.
- **[Risk]** Some desktop boards default to "Power on after AC loss = Power on" which would make the machine boot after any power outage regardless of WOL — undermining the use case.
- **Mitigation:** Document the BIOS setting ("After Power Loss" → "Power Off" or "Stay Off") in the change notes.
- **[Risk]** NetworkManager may re-apply link settings on resume, overwriting `wol g`.
- **Mitigation:** The systemd unit is wired to run on resume via a `path`/`service` pair (or a manual `systemctl restart wol-enp14s0.service` hook). For the initial implementation we trigger it on boot and rely on the user to re-run if it ever drifts; the follow-up task in `tasks.md` adds the resume hook.
- **[Trade-off]** Hard-coding the MAC and interface name in the Nix module means the module is specific to `jpporta-nixos`. We accept this trade-off because the module is only enabled on this host and the alternative (parameterizing) would add ceremony for a single user.