From cdbad8910ca531d93bb8eda4d82f445e823ce9de Mon Sep 17 00:00:00 2001 From: Joao Porta Date: Fri, 17 Jul 2026 17:25:16 -0300 Subject: [PATCH] Squashed commit of the following: commit bf4873eb012235b4d82cd79c4f508064ead301ab Author: Joao Porta Date: Fri Jul 17 17:24:26 2026 -0300 wake on lan --- hosts/jpporta-nixos/configuration.nix | 2 + modules/home-manager/alacritty/default.nix | 15 ++-- modules/nixos/wake-on-lan/default.nix | 71 +++++++++++++++++++ openspec/changes/wake-on-lan/.openspec.yaml | 2 + openspec/changes/wake-on-lan/design.md | 59 +++++++++++++++ openspec/changes/wake-on-lan/proposal.md | 27 +++++++ .../wake-on-lan/specs/wake-on-lan/spec.md | 51 +++++++++++++ openspec/changes/wake-on-lan/tasks.md | 26 +++++++ 8 files changed, 243 insertions(+), 10 deletions(-) create mode 100644 modules/nixos/wake-on-lan/default.nix create mode 100644 openspec/changes/wake-on-lan/.openspec.yaml create mode 100644 openspec/changes/wake-on-lan/design.md create mode 100644 openspec/changes/wake-on-lan/proposal.md create mode 100644 openspec/changes/wake-on-lan/specs/wake-on-lan/spec.md create mode 100644 openspec/changes/wake-on-lan/tasks.md diff --git a/hosts/jpporta-nixos/configuration.nix b/hosts/jpporta-nixos/configuration.nix index dacf6bf..52bf962 100644 --- a/hosts/jpporta-nixos/configuration.nix +++ b/hosts/jpporta-nixos/configuration.nix @@ -14,6 +14,7 @@ ../../modules/nixos/steam ../../modules/nixos/awsvpn ../../modules/nixos/tailscale + ../../modules/nixos/wake-on-lan ]; # Bootloader @@ -115,6 +116,7 @@ hyprland.enable = true; steam.enable = true; tailscale.enable = true; + wake-on-lan.enable = true; }; ##------------------------------ diff --git a/modules/home-manager/alacritty/default.nix b/modules/home-manager/alacritty/default.nix index 4af4893..826e596 100644 --- a/modules/home-manager/alacritty/default.nix +++ b/modules/home-manager/alacritty/default.nix @@ -20,36 +20,31 @@ size = 10.3; normal = { family = "BerkeleyMono Nerd Font Mono"; + style = "Regular"; }; bold = { family = "BerkeleyMono Nerd Font Mono"; + style = "ExtraBold"; }; italic = { family = "BerkeleyMono Nerd Font Mono"; + style = "Oblique"; }; bold_italic = { family = "BerkeleyMono Nerd Font Mono"; + style = "ExtraBold Oblique"; }; }; window = { decorations = "None"; opacity = 0.8; - blur = true; + blur = false; dynamic_padding = true; padding = { x = 10; y = 16; }; }; - keyboard = { - bindings = [ - { - key = "Return"; - mods = "Shift"; - chars = "\u001B\r"; - } - ]; - }; }; }; }; diff --git a/modules/nixos/wake-on-lan/default.nix b/modules/nixos/wake-on-lan/default.nix new file mode 100644 index 0000000..52e27bf --- /dev/null +++ b/modules/nixos/wake-on-lan/default.nix @@ -0,0 +1,71 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.custom.wake-on-lan; + + iface = "enp14s0"; + + mac = "d8:43:ae:5a:ae:12"; + + wake-jpporta-nixos = pkgs.writeShellScriptBin "wake-jpporta-nixos" '' + exec ${pkgs.wakeonlan}/bin/wakeonlan -i "''${1:-255.255.255.255}" ${mac} + ''; +in +{ + options.custom.wake-on-lan = { + enable = lib.mkEnableOption "Wake-on-LAN on the wired NIC (magic-packet mode)"; + }; + + config = lib.mkIf cfg.enable { + # Keep `wol g` applied on the NIC after the link is up (boot path). + systemd.services."wol-${iface}" = { + description = "Enable Wake-on-LAN (magic packet) on ${iface}"; + wantedBy = [ "multi-user.target" ]; + wants = [ + "network-online.target" + "NetworkManager.service" + ]; + after = [ + "network-online.target" + "NetworkManager.service" + "NetworkManager-wait-online.service" + ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = "${pkgs.ethtool}/bin/ethtool -s ${iface} wol g"; + }; + }; + + # Re-apply `wol g` after resume from suspend / hibernate so NetworkManager + # cannot silently drop the WOL flag on link renegotiation. + systemd.services."wol-${iface}-resume" = { + description = "Re-apply Wake-on-LAN (magic packet) on ${iface} after resume"; + wantedBy = [ + "suspend.target" + "hibernate.target" + "hybrid-sleep.target" + ]; + after = [ + "systemd-suspend.service" + "systemd-hibernate.service" + "systemd-hybrid-sleep.service" + ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkgs.ethtool}/bin/ethtool -s ${iface} wol g"; + }; + }; + + environment.systemPackages = [ + pkgs.ethtool + pkgs.wakeonlan + wake-jpporta-nixos + ]; + }; +} diff --git a/openspec/changes/wake-on-lan/.openspec.yaml b/openspec/changes/wake-on-lan/.openspec.yaml new file mode 100644 index 0000000..ff5f854 --- /dev/null +++ b/openspec/changes/wake-on-lan/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-07-17 diff --git a/openspec/changes/wake-on-lan/design.md b/openspec/changes/wake-on-lan/design.md new file mode 100644 index 0000000..f14cdc2 --- /dev/null +++ b/openspec/changes/wake-on-lan/design.md @@ -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 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 `), 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. \ No newline at end of file diff --git a/openspec/changes/wake-on-lan/proposal.md b/openspec/changes/wake-on-lan/proposal.md new file mode 100644 index 0000000..58013f1 --- /dev/null +++ b/openspec/changes/wake-on-lan/proposal.md @@ -0,0 +1,27 @@ +## Why + +The user wants to power on `jpporta-nixos` remotely via Wake-on-LAN (WOL). This is useful when the machine is shut down but the user wants to access it from another device (e.g., over Tailscale + SSH) without manually pressing the power button. Currently the machine has no WOL configuration: nothing keeps the NIC in a low-power listening state, and no tooling exists on the host to inspect or send WOL packets. + +## What Changes + +- Enable Wake-on-LAN on the wired Ethernet interface (`enp14s0`) so the NIC keeps listening for magic packets while the system is powered off. +- Add `ethtool` so the user can inspect the current WOL settings and supported modes on the NIC. +- Add `wol` (the `iputils`-style `wakeonlan` command) so WOL packets can be sent from this host to other machines, and from other machines back to this one (using its MAC `d8:43:ae:5a:ae:12`). +- Add a small helper script (`wake-jpporta-nixos`) that sends a WOL magic packet to this machine's MAC address, so it can be invoked by name. +- Document the manual BIOS-level steps that are required for WOL to work (these cannot be set from NixOS). + +## Capabilities + +### New Capabilities +- `wake-on-lan`: Configures Wake-on-LAN on the host's wired NIC and provides tooling/scripts to inspect and send magic packets. + +### Modified Capabilities +- None + +## Impact + +- **Code:** A new optional module `modules/nixos/wake-on-lan/` will be added and imported into `hosts/jpporta-nixos/configuration.nix`. The module will expose a `custom.wake-on-lan.enable` toggle consistent with the project's existing module pattern (see `modules/nixos/tailscale/`). +- **System:** A systemd unit will be installed that runs `ethtool -s enp14s0 wol g` on boot and on resume, persisting the WOL setting across reboots and sleep states. +- **User:** Two new commands will be on `$PATH`: `ethtool` and `wakeonlan`, plus a convenience wrapper `wake-jpporta-nixos`. +- **Hardware:** Requires BIOS-level "Wake on LAN" to be enabled on the motherboard and a stable AC power source. The user must configure these out-of-band. +- **Network:** To wake the machine from outside the LAN, the router must forward UDP port 9 (or 7) to the broadcast address. This is a router-side concern, not configurable from NixOS. \ No newline at end of file diff --git a/openspec/changes/wake-on-lan/specs/wake-on-lan/spec.md b/openspec/changes/wake-on-lan/specs/wake-on-lan/spec.md new file mode 100644 index 0000000..6178d3e --- /dev/null +++ b/openspec/changes/wake-on-lan/specs/wake-on-lan/spec.md @@ -0,0 +1,51 @@ +## ADDED Requirements + +### Requirement: Persistent Wake-on-LAN on the wired NIC +The system SHALL keep the wired Ethernet interface (`enp14s0`) configured to wake on magic-packet while powered off. + +#### Scenario: WOL applied on boot +- **WHEN** the system finishes booting +- **THEN** `ethtool enp14s0` SHALL report `Wake-on: g` + +#### Scenario: WOL applied on resume from suspend +- **WHEN** the system resumes from suspend +- **THEN** `ethtool enp14s0` SHALL report `Wake-on: g` + +### Requirement: Wake-on-LAN tooling available +The system SHALL provide `ethtool` and `wakeonlan` on `$PATH` so the user can inspect and trigger WOL. + +#### Scenario: Inspect WOL state +- **WHEN** the user runs `ethtool enp14s0` +- **THEN** the command SHALL exit 0 +- **AND** the output SHALL include `Supports Wake-on:` and `Wake-on:` lines + +#### Scenario: Send a magic packet +- **WHEN** the user runs `wakeonlan ` (or `wake-jpporta-nixos`) +- **THEN** the command SHALL exit 0 +- **AND** a UDP magic-packet SHALL be broadcast to `255.255.255.255:9` + +### Requirement: Convenience wrapper to wake this host +The system SHALL provide a `wake-jpporta-nixos` command that sends a WOL magic packet to this machine's wired NIC MAC. + +#### Scenario: Wake this machine from LAN +- **WHEN** the user runs `wake-jpporta-nixos` +- **THEN** the host's NIC SHALL receive the magic packet +- **AND** the system SHALL begin booting + +#### Scenario: Wake this machine through a custom broadcast address +- **WHEN** the user runs `wake-jpporta-nixos 192.168.0.255` +- **THEN** the magic packet SHALL be sent to the `192.168.0.255` broadcast address +- **AND** the host SHALL begin booting + +### Requirement: Reusable opt-in module +The system SHALL expose a `custom.wake-on-lan.enable` option that, when enabled, configures all of the above. + +#### Scenario: Module disabled +- **WHEN** `custom.wake-on-lan.enable` is `false` or unset +- **THEN** the system SHALL NOT install the WOL systemd unit +- **AND** the system SHALL NOT install the `ethtool`, `wakeonlan`, or `wake-jpporta-nixos` packages + +#### Scenario: Module enabled +- **WHEN** `custom.wake-on-lan.enable` is `true` +- **THEN** the WOL systemd unit SHALL be active +- **AND** the `wake-jpporta-nixos` command SHALL be on `$PATH` \ No newline at end of file diff --git a/openspec/changes/wake-on-lan/tasks.md b/openspec/changes/wake-on-lan/tasks.md new file mode 100644 index 0000000..6c5971e --- /dev/null +++ b/openspec/changes/wake-on-lan/tasks.md @@ -0,0 +1,26 @@ +## 1. Create the WOL NixOS module + +- [x] 1.1 Create `modules/nixos/wake-on-lan/default.nix` with a `custom.wake-on-lan.enable` option. +- [x] 1.2 Define a systemd `oneshot` service `wol-enp14s0.service` that runs `ethtool -s enp14s0 wol g`. +- [x] 1.3 Wire the unit's `after`/`wants` to `network-online.target` and `NetworkManager.service` so it runs after the link is up. +- [x] 1.4 Add the unit to the resume path so `wol g` is re-applied after suspend/resume (use `systemd-suspend.service` or a `path` unit hook). + +## 2. Add packages and the convenience wrapper + +- [x] 2.1 Add `pkgs.ethtool` and `pkgs.wakeonlan` to `environment.systemPackages` when the module is enabled. *(Note: `pkgs.iputils` does not provide `wakeonlan` in nixpkgs — see implementation note.)* +- [x] 2.2 Create a `wake-jpporta-nixos` shell script via `pkgs.writeShellScriptBin` that invokes `wakeonlan -i ${1:-255.255.255.255} d8:43:ae:5a:ae:12`. +- [x] 2.3 Add the wrapper to `environment.systemPackages`. + +## 3. Wire the module into the host + +- [x] 3.1 Add `../../modules/nixos/wake-on-lan` to `imports` in `hosts/jpporta-nixos/configuration.nix`. +- [x] 3.2 Add `custom.wake-on-lan.enable = true;` under the existing `custom = { ... };` block in the host config. + +## 4. Verify and document + +- [x] 4.1 Rebuild the system with `sudo nixos-rebuild switch --flake .#jpporta-nixos`. +- [x] 4.2 Run `ethtool enp14s0` and confirm `Supports Wake-on:` lists `g` and `Wake-on: g`. +- [x] 4.3 Run `systemctl status wol-enp14s0.service` and confirm the unit is `active (exited)`. +- [x] 4.4 Run `wake-jpporta-nixos` once while the machine is up to confirm the script runs without error (it will be a no-op when the host is already up). +- [x] 4.5 Shut the machine down, then run `wake-jpporta-nixos` from another machine on the same LAN and confirm `jpporta-nixos` powers on. +- [x] 4.6 Document the BIOS-level prerequisite ("Wake on LAN" enabled, "After Power Loss" → Power Off / Stay Off) in the change notes or a README so it's not lost. \ No newline at end of file