{ config, lib, pkgs, namespace, ... }: let cfg = config.${namespace}.rtl8125-recovery; recovery = pkgs.writeShellApplication { name = "rtl8125-recover"; runtimeInputs = with pkgs; [ coreutils gnugrep pciutils ethtool systemd ]; text = builtins.readFile ./recover.sh; }; in { options.${namespace}.rtl8125-recovery = { enable = lib.mkEnableOption "RTL8125 PCI recovery after boot and resume"; pciAddress = lib.mkOption { type = lib.types.str; default = "0000:0a:00.0"; description = "PCI address of the onboard RTL8125 controller."; }; bridgeAddress = lib.mkOption { type = lib.types.str; default = "0000:06:03.0"; description = "Immediate parent PCI bridge of the controller."; }; }; config = lib.mkIf cfg.enable { systemd.services.rtl8125-recovery = { description = "Recover RTL8125 from failed PCI power restoration"; wantedBy = [ "multi-user.target" ]; after = [ "NetworkManager.service" ]; environment = { NIC_PCI = cfg.pciAddress; NIC_BRIDGE = cfg.bridgeAddress; }; serviceConfig = { Type = "oneshot"; ExecStart = lib.getExe recovery; TimeoutStartSec = "90s"; StateDirectory = "rtl8125-recovery"; StateDirectoryMode = "0700"; UMask = "0077"; }; }; # sleep.target is stopped after resume. Queue recovery separately so its # grace period does not hold up the sleep transaction or user sessions. systemd.services.rtl8125-resume = { description = "Queue RTL8125 recovery after waking"; wantedBy = [ "sleep.target" ]; before = [ "sleep.target" ]; unitConfig.StopWhenUnneeded = true; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; ExecStart = "${pkgs.coreutils}/bin/true"; ExecStop = "${pkgs.systemd}/bin/systemctl --no-block start rtl8125-recovery.service"; }; }; }; }