Unverified Commit ee942cc4 authored by Wolfgang Walther's avatar Wolfgang Walther
Browse files

Merge branch 'master' into staging-next

parents da7a6ed8 3e0905a3
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
# This file is used by .github/workflows/labels.yml
# This version is only run for Pull Requests from protected branches like staging-next, haskell-updates or python-updates.
# This version is only run for Pull Requests from development branches like staging-next, haskell-updates or python-updates.

"4.workflow: package set update":
  - any:
+14 −1
Original line number Diff line number Diff line
@@ -14,7 +14,7 @@ permissions: {}
jobs:
  backport:
    name: Backport Pull Request
    if: github.repository_owner == 'NixOS' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name))
    if: github.repository_owner == 'NixOS' && github.event.pull_request.merged == true && (github.event.action != 'labeled' || startsWith(github.event.label.name, 'backport'))
    runs-on: ubuntu-24.04
    steps:
      # Use a GitHub App to create the PR so that CI gets triggered
@@ -33,6 +33,7 @@ jobs:
          token: ${{ steps.app-token.outputs.token }}

      - name: Create backport PRs
        id: backport
        uses: korthout/backport-action@436145e922f9561fc5ea157ff406f21af2d6b363 # v3.2.0
        with:
          # Config README: https://github.com/korthout/backport-action#backport-action
@@ -43,3 +44,15 @@ jobs:

            * [ ] Before merging, ensure that this backport is [acceptable for the release](https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md#changes-acceptable-for-releases).
              * Even as a non-commiter, if you find that it is not acceptable, leave a comment.

      - name: "Add 'has: port to stable' label"
        if: steps.backport.outputs.created_pull_numbers != ''
        env:
          GH_TOKEN: ${{ steps.app-token.outputs.token }}
          REPOSITORY: ${{ github.repository }}
          NUMBER: ${{ github.event.number }}
        run: |
          gh api \
            --method POST \
            /repos/"$REPOSITORY"/issues/"$NUMBER"/labels \
            -f "labels[]=8.has: port to stable"
+22 −4
Original line number Diff line number Diff line
@@ -20,22 +20,40 @@ jobs:
    if: "github.repository_owner == 'NixOS' && !contains(github.event.pull_request.title, '[skip treewide]')"
    steps:
      - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
        if: "!(github.pull_request.head.repo == 'NixOS' && github.ref_protected)"
        if: |
          github.event.pull_request.head.repo.owner.login != 'NixOS' || !(
            github.head_ref == 'haskell-updates' ||
            github.head_ref == 'python-updates' ||
            github.head_ref == 'staging-next' ||
            startsWith(github.head_ref, 'staging-next-')
          )
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          configuration-path: .github/labeler.yml # default
          sync-labels: true
      - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
        if: "!(github.pull_request.head.repo == 'NixOS' && github.ref_protected)"
        if: |
          github.event.pull_request.head.repo.owner.login != 'NixOS' || !(
            github.head_ref == 'haskell-updates' ||
            github.head_ref == 'python-updates' ||
            github.head_ref == 'staging-next' ||
            startsWith(github.head_ref, 'staging-next-')
          )
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          configuration-path: .github/labeler-no-sync.yml
          sync-labels: false
      - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
        # Protected branches like staging-next, haskell-updates and python-updates get special labels.
        # Development branches like staging-next, haskell-updates and python-updates get special labels.
        # This is to avoid the mass of labels there, which is mostly useless - and really annoying for
        # the backport labels.
        if: "github.pull_request.head.repo == 'NixOS' && github.ref_protected"
        if: |
          github.event.pull_request.head.repo.owner.login == 'NixOS' && (
            github.head_ref == 'haskell-updates' ||
            github.head_ref == 'python-updates' ||
            github.head_ref == 'staging-next' ||
            startsWith(github.head_ref, 'staging-next-')
          )
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          configuration-path: .github/labeler-protected-branches.yml
+1 −0
Original line number Diff line number Diff line
@@ -651,6 +651,7 @@
  ./services/hardware/nvidia-optimus.nix
  ./services/hardware/openrgb.nix
  ./services/hardware/pcscd.nix
  ./services/hardware/pid-fan-controller.nix
  ./services/hardware/pommed.nix
  ./services/hardware/power-profiles-daemon.nix
  ./services/hardware/powerstation.nix
+188 −0
Original line number Diff line number Diff line
{
  lib,
  config,
  pkgs,
  ...
}:
let
  cfg = config.services.pid-fan-controller;
  heatSource = {
    options = {
      name = lib.mkOption {
        type = lib.types.uniq lib.types.nonEmptyStr;
        description = "Name of the heat source.";
      };
      wildcardPath = lib.mkOption {
        type = lib.types.nonEmptyStr;
        description = ''
          Path of the heat source's `hwmon` `temp_input` file.
          This path can contain multiple wildcards, but has to resolve to
          exactly one result.
        '';
      };
      pidParams = {
        setPoint = lib.mkOption {
          type = lib.types.ints.unsigned;
          description = "Set point of the controller in °C.";
        };
        P = lib.mkOption {
          description = "K_p of PID controller.";
          type = lib.types.float;
        };
        I = lib.mkOption {
          description = "K_i of PID controller.";
          type = lib.types.float;
        };
        D = lib.mkOption {
          description = "K_d of PID controller.";
          type = lib.types.float;
        };
      };
    };
  };

  fan = {
    options = {
      wildcardPath = lib.mkOption {
        type = lib.types.str;
        description = ''
          Wildcard path of the `hwmon` `pwm` file.
          If the fans are not to be found in `/sys/class/hwmon/hwmon*` the corresponding
          kernel module (like `nct6775`) needs to be added to `boot.kernelModules`.
          See the [`hwmon` Documentation](https://www.kernel.org/doc/html/latest/hwmon/index.html).
        '';
      };
      minPwm = lib.mkOption {
        default = 0;
        type = lib.types.ints.u8;
        description = "Minimum PWM value.";
      };
      maxPwm = lib.mkOption {
        default = 255;
        type = lib.types.ints.u8;
        description = "Maximum PWM value.";
      };
      cutoff = lib.mkOption {
        default = false;
        type = lib.types.bool;
        description = "Whether to stop the fan when `minPwm` is reached.";
      };
      heatPressureSrcs = lib.mkOption {
        type = lib.types.nonEmptyListOf lib.types.str;
        description = "Heat pressure sources affected by the fan.";
      };
    };
  };
in
{
  options.services.pid-fan-controller = {
    enable = lib.mkEnableOption "the PID fan controller, which controls the configured fans by running a closed-loop PID control loop";
    package = lib.mkPackageOption pkgs "pid-fan-controller" { };
    settings = {
      interval = lib.mkOption {
        default = 500;
        type = lib.types.int;
        description = "Interval between controller cycles in milliseconds.";
      };
      heatSources = lib.mkOption {
        type = lib.types.listOf (lib.types.submodule heatSource);
        description = "List of heat sources to be monitored.";
        example = ''
          [
            {
              name = "cpu";
              wildcardPath = "/sys/devices/pci0000:00/0000:00:18.3/hwmon/hwmon*/temp1_input";
              pidParams = {
                setPoint = 60;
                P = -5.0e-3;
                I = -2.0e-3;
                D = -6.0e-3;
              };
            }
          ];
        '';
      };
      fans = lib.mkOption {
        type = lib.types.listOf (lib.types.submodule fan);
        description = "List of fans to be controlled.";
        example = ''
          [
            {
              wildcardPath = "/sys/devices/platform/nct6775.2592/hwmon/hwmon*/pwm1";
              minPwm = 60;
              maxPwm = 255;
              heatPressureSrcs = [
                "cpu"
                "gpu"
              ];
            }
          ];
        '';
      };
    };
  };
  config = lib.mkIf cfg.enable {
    #map camel cased attrs into snake case for config
    environment.etc."pid-fan-settings.json".text = builtins.toJSON {
      interval = cfg.settings.interval;
      heat_srcs = map (heatSrc: {
        name = heatSrc.name;
        wildcard_path = heatSrc.wildcardPath;
        PID_params = {
          set_point = heatSrc.pidParams.setPoint;
          P = heatSrc.pidParams.P;
          I = heatSrc.pidParams.I;
          D = heatSrc.pidParams.D;
        };
      }) cfg.settings.heatSources;
      fans = map (fan: {
        wildcard_path = fan.wildcardPath;
        min_pwm = fan.minPwm;
        max_pwm = fan.maxPwm;
        cutoff = fan.cutoff;
        heat_pressure_srcs = fan.heatPressureSrcs;
      }) cfg.settings.fans;
    };

    systemd.services.pid-fan-controller = {
      wantedBy = [ "multi-user.target" ];
      serviceConfig = {
        Type = "simple";
        ExecStart = [ (lib.getExe cfg.package) ];
        ExecStopPost = [ "${lib.getExe cfg.package} disable" ];
        Restart = "always";
        #This service needs to run as root to write to /sys.
        #therefore it should operate with the least amount of privileges needed
        ProtectHome = "yes";
        #strict is not possible as it needs /sys
        ProtectSystem = "full";
        ProtectProc = "invisible";
        PrivateNetwork = "yes";
        NoNewPrivileges = "yes";
        MemoryDenyWriteExecute = "yes";
        RestrictNamespaces = "~user pid net uts mnt";
        ProtectKernelModules = "yes";
        RestrictRealtime = "yes";
        SystemCallFilter = "@system-service";
        CapabilityBoundingSet = "~CAP_KILL CAP_WAKE_ALARM CAP_IPC_LOC CAP_BPF CAP_LINUX_IMMUTABLE CAP_BLOCK_SUSPEND CAP_MKNOD";
      };
      # restart unit if config changed
      restartTriggers = [ config.environment.etc."pid-fan-settings.json".source ];
    };
    #sleep hook to restart the service as it breaks otherwise
    systemd.services.pid-fan-controller-sleep = {
      before = [ "sleep.target" ];
      wantedBy = [ "sleep.target" ];
      unitConfig = {
        StopWhenUnneeded = "yes";
      };
      serviceConfig = {
        Type = "oneshot";
        RemainAfterExit = true;
        ExecStart = [ "systemctl stop pid-fan-controller.service" ];
        ExecStop = [ "systemctl restart pid-fan-controller.service" ];
      };
    };
  };
  meta.maintainers = with lib.maintainers; [ zimward ];
}
Loading