{ config, lib, pkgs, ... }: let # TODO: Add missing options. # TODO: Add descriptions for every option. # TODO: Add merging/concatenating of options with extraConfig or remove extraConfig entirely. # TODO: Make sure settings submodule is not shown when rendered in documentation. -> Use # `visible = "shallow"` in options? cfg = config.myNixos.services.lact; settingsFormat = pkgs.formats.yaml { }; gpusType = lib.mkOption { default = null; type = lib.types.nullOr ( lib.types.attrsOf ( lib.types.submodule { options = { fan_control_enabled = lib.mkOption { default = false; example = true; type = lib.types.bool; }; fan_control_settings = lib.mkOption { default = null; type = lib.types.nullOr ( lib.types.submodule { options = { mode = lib.mkOption { default = "curve"; example = "static"; type = lib.types.enum [ "curve" "static" ]; }; static_speed = lib.mkOption { default = 0.5; example = 1.0; description = "number between 0 and 1"; type = lib.types.numbers.between 0 1; }; temperature_key = lib.mkOption { default = "edge"; example = "junction"; type = lib.types.enum [ "edge" "junction" ]; }; interval_ms = lib.mkOption { default = 500; example = 1000; description = "integer greater 0"; type = lib.types.ints.positive; }; curve = lib.mkOption { # Can the key be type checked? default = { "40" = 0.2; "50" = 0.35; "60" = 0.5; "70" = 0.75; "80" = 1.0; }; example = { "40" = 0.2; "50" = 0.35; "60" = 0.5; "70" = 0.75; "80" = 1.0; }; type = lib.types.attrsOf lib.types.numbers.nonnegative; }; spindown_delay_ms = lib.mkOption { default = 0; example = 5000; type = lib.types.ints.between 0 29990; }; change_threshold = lib.mkOption { default = 0; example = 3; type = lib.types.ints.between 0 9; }; }; } ); }; power_cap = lib.mkOption { default = null; example = 320.0; type = lib.types.nullOr lib.types.ints.positive; }; performance_level = lib.mkOption { default = "auto"; example = "manual"; type = lib.types.nullOr ( lib.types.enum [ "auto" "low" "high" "manual" ] ); }; power_profile_mode_index = lib.mkOption { # TODO: Add check if performance_level is set to manual. default = null; example = 1; type = lib.types.nullOr lib.types.ints.unsigned; # How many indices are there? }; min_core_clock = lib.mkOption { default = null; example = 300; type = lib.types.nullOr lib.types.ints.unsigned; }; min_memory_clock = lib.mkOption { default = null; example = 500; type = lib.types.nullOr lib.types.ints.unsigned; }; min_voltage = lib.mkOption { default = null; example = 900; type = lib.types.nullOr lib.types.ints.unsigned; }; max_core_clock = lib.mkOption { default = null; example = 1630; type = lib.types.nullOr lib.types.ints.positive; }; max_memory_clock = lib.mkOption { default = null; example = 800; type = lib.types.nullOr lib.types.ints.positive; }; max_voltage = lib.mkOption { default = null; example = 1200; type = lib.types.nullOr lib.types.ints.positive; }; voltage_offset = lib.mkOption { default = null; example = -50; type = lib.types.nullOr lib.types.int; }; }; } ) ); }; in { options.myNixos.services.lact = { enable = lib.mkEnableOption "LACT Linux GPU Control Application"; extraConfig = lib.mkOption { default = ""; example = "tcp_listen_address: 127.0.0.1:12853"; type = lib.types.lines; }; package = lib.mkPackageOption pkgs "lact" { }; settings = lib.mkOption { default = { }; type = lib.types.submodule { options = { daemon = { log_level = lib.mkOption { default = "info"; type = lib.types.enum [ "error" "warn" "info" "debug" "trace" ]; }; admin_groups = lib.mkOption { default = [ "wheel" "sudo" ]; example = [ "sudo" ]; type = lib.types.listOf lib.types.str; }; disable_clocks_cleanup = lib.mkOption { default = false; example = true; type = lib.types.bool; }; }; apply_settings_timer = lib.mkOption { default = 5; example = 10; type = lib.types.ints.unsigned; }; gpus = gpusType; profiles = lib.mkOption { default = null; type = lib.types.nullOr ( lib.types.attrsOf ( lib.types.submodule { options = { gpus = gpusType; # NOTE: Rules is not supported yet by current lact version in nixpkgs. rules = lib.mkOption { default = null; type = lib.types.nullOr ( lib.types.submodule { options = { type = lib.mkOption { example = "process"; type = lib.types.enum [ "gamemode" "process" ]; }; rule = lib.mkOption { default = null; type = lib.types.nullOr ( lib.types.submodule { options = { name = lib.mkOption { example = "vkcube"; type = lib.types.str; }; args = lib.mkOption { default = null; example = "--my-arg"; type = lib.types.nullOr lib.types.str; }; }; } ); }; }; } ); }; }; } ) ); }; current_profile = lib.mkOption { default = null; example = "vkcube"; type = lib.types.nullOr lib.types.str; }; auto_switch_profiles = lib.mkOption { default = null; example = true; type = lib.types.nullOr lib.types.bool; }; }; }; }; }; config = lib.mkIf cfg.enable { environment = { etc."lact/config.yaml" = lib.mkIf (cfg.settings != { }) { # NOTE: Feel free to let me know or open a pull request if there is a cleaner way to # concatenate the config with extraConfig. source = let # Recursively remove attributes with value null. filteredConfig = lib.filterAttrsRecursive (n: v: v != null) cfg; initialConfig = settingsFormat.generate "etc-lact-config-initial-config.yaml" filteredConfig.settings; # Workaround for lact’s strict integer key requirement. postProcessedYaml = pkgs.runCommand "etc-lact-config-processed.yaml" { nativeBuildInputs = with pkgs; [ python3 python3Packages.pyyaml ]; } '' cp ${initialConfig} initial-config.yaml python3 <