push sheeet
Some checks failed
Periodic Merges (6h) / master → staging-nixos (push) Failing after 12m50s
Periodic Merges (6h) / master → staging-next (push) Failing after 12m54s
Periodic Merges (24h) / merge-base(master,staging) → haskell-updates (push) Failing after 11m54s
Periodic Merges (6h) / staging-next → staging (push) Failing after 12m13s
Periodic Merges (24h) / staging-next-25.05 → staging-25.05 (push) Failing after 13m24s
Periodic Merges (24h) / release-25.05 → staging-next-25.05 (push) Failing after 14m28s

This commit is contained in:
Dark Steveneq
2025-10-09 14:15:47 +02:00
commit 646b892680
49168 changed files with 5897842 additions and 0 deletions

View File

@@ -0,0 +1,161 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.acpid;
canonicalHandlers = {
powerEvent = {
event = "button/power.*";
action = cfg.powerEventCommands;
};
lidEvent = {
event = "button/lid.*";
action = cfg.lidEventCommands;
};
acEvent = {
event = "ac_adapter.*";
action = cfg.acEventCommands;
};
};
acpiConfDir = pkgs.runCommand "acpi-events" { preferLocalBuild = true; } ''
mkdir -p $out
${
# Generate a configuration file for each event. (You can't have
# multiple events in one config file...)
let
f = name: handler: ''
fn=$out/${name}
echo "event=${handler.event}" > $fn
echo "action=${pkgs.writeShellScriptBin "${name}.sh" handler.action}/bin/${name}.sh '%e'" >> $fn
'';
in
lib.concatStringsSep "\n" (lib.mapAttrsToList f (canonicalHandlers // cfg.handlers))
}
'';
in
{
###### interface
options = {
services.acpid = {
enable = lib.mkEnableOption "the ACPI daemon";
logEvents = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Log all event activity.";
};
handlers = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule {
options = {
event = lib.mkOption {
type = lib.types.str;
example = lib.literalExpression ''"button/power.*" "button/lid.*" "ac_adapter.*" "button/mute.*" "button/volumedown.*" "cd/play.*" "cd/next.*"'';
description = "Event type.";
};
action = lib.mkOption {
type = lib.types.lines;
description = "Shell commands to execute when the event is triggered.";
};
};
}
);
description = ''
Event handlers.
::: {.note}
Handler can be a single command.
:::
'';
default = { };
example = {
ac-power = {
event = "ac_adapter/*";
action = ''
vals=($1) # space separated string to array of multiple values
case ''${vals[3]} in
00000000)
echo unplugged >> /tmp/acpi.log
;;
00000001)
echo plugged in >> /tmp/acpi.log
;;
*)
echo unknown >> /tmp/acpi.log
;;
esac
'';
};
};
};
powerEventCommands = lib.mkOption {
type = lib.types.lines;
default = "";
description = "Shell commands to execute on a button/power.* event.";
};
lidEventCommands = lib.mkOption {
type = lib.types.lines;
default = "";
description = "Shell commands to execute on a button/lid.* event.";
};
acEventCommands = lib.mkOption {
type = lib.types.lines;
default = "";
description = "Shell commands to execute on an ac_adapter.* event.";
};
};
};
###### implementation
config = lib.mkIf cfg.enable {
systemd.services.acpid = {
description = "ACPI Daemon";
documentation = [ "man:acpid(8)" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = lib.escapeShellArgs (
[
"${pkgs.acpid}/bin/acpid"
"--foreground"
"--netlink"
"--confdir"
"${acpiConfDir}"
]
++ lib.optional cfg.logEvents "--logevents"
);
};
unitConfig = {
ConditionVirtualization = "!systemd-nspawn";
ConditionPathExists = [ "/proc/acpi" ];
};
};
};
}

View File

@@ -0,0 +1,151 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.actkbd;
configFile = pkgs.writeText "actkbd.conf" ''
${lib.concatMapStringsSep "\n" (
{
keys,
events,
attributes,
command,
...
}:
''${
lib.concatMapStringsSep "+" toString keys
}:${lib.concatStringsSep "," events}:${lib.concatStringsSep "," attributes}:${command}''
) cfg.bindings}
${cfg.extraConfig}
'';
bindingCfg =
{ ... }:
{
options = {
keys = lib.mkOption {
type = lib.types.listOf lib.types.int;
description = "List of keycodes to match.";
};
events = lib.mkOption {
type = lib.types.listOf (
lib.types.enum [
"key"
"rep"
"rel"
]
);
default = [ "key" ];
description = "List of events to match.";
};
attributes = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ "exec" ];
description = "List of attributes.";
};
command = lib.mkOption {
type = lib.types.str;
default = "";
description = "What to run.";
};
};
};
in
{
###### interface
options = {
services.actkbd = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to enable the {command}`actkbd` key mapping daemon.
Turning this on will start an {command}`actkbd`
instance for every evdev input that has at least one key
(which is okay even for systems with tiny memory footprint,
since actkbd normally uses \<100 bytes of memory per
instance).
This allows binding keys globally without the need for e.g.
X11.
'';
};
bindings = lib.mkOption {
type = lib.types.listOf (lib.types.submodule bindingCfg);
default = [ ];
example = lib.literalExpression ''
[ { keys = [ 113 ]; events = [ "key" ]; command = "''${pkgs.alsa-utils}/bin/amixer -q set Master toggle"; }
]
'';
description = ''
Key bindings for {command}`actkbd`.
See {command}`actkbd` {file}`README` for documentation.
The example shows a piece of what {option}`sound.mediaKeys.enable` does when enabled.
'';
};
extraConfig = lib.mkOption {
type = lib.types.lines;
default = "";
description = ''
Literal contents to append to the end of actkbd configuration file.
'';
};
};
};
###### implementation
config = lib.mkIf cfg.enable {
services.udev.packages = lib.singleton (
pkgs.writeTextFile {
name = "actkbd-udev-rules";
destination = "/etc/udev/rules.d/61-actkbd.rules";
text = ''
ACTION=="add", SUBSYSTEM=="input", KERNEL=="event[0-9]*", ENV{ID_INPUT_KEY}=="1", TAG+="systemd", ENV{SYSTEMD_WANTS}+="actkbd@$env{DEVNAME}.service"
'';
}
);
systemd.services."actkbd@" = {
enable = true;
restartIfChanged = true;
unitConfig = {
Description = "actkbd on %I";
ConditionPathExists = "%I";
};
serviceConfig = {
Type = "forking";
ExecStart = "${pkgs.actkbd}/bin/actkbd -D -c ${configFile} -d %I";
};
};
# For testing
environment.systemPackages = [ pkgs.actkbd ];
};
}

View File

@@ -0,0 +1,71 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.hardware.amdgpu;
in
{
options.hardware.amdgpu = {
legacySupport.enable = lib.mkEnableOption ''
using `amdgpu` kernel driver instead of `radeon` for Southern Islands
(Radeon HD 7000) series and Sea Islands (Radeon HD 8000)
series cards. Note: this removes support for analog video outputs,
which is only available in the `radeon` driver
'';
initrd.enable = lib.mkEnableOption ''
loading `amdgpu` kernelModule in stage 1.
Can fix lower resolution in boot screen during initramfs phase
'';
overdrive = {
enable = lib.mkEnableOption ''`amdgpu` overdrive mode for overclocking'';
ppfeaturemask = lib.mkOption {
type = lib.types.str;
default = "0xfffd7fff";
example = "0xffffffff";
description = ''
Sets the `amdgpu.ppfeaturemask` kernel option. It can be used to enable the overdrive bit.
Default is `0xfffd7fff` as it is less likely to cause flicker issues. Setting it to
`0xffffffff` enables all features, but also can be unstable. See
[the kernel documentation](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/amd/include/amd_shared.h#n169)
for more information.
'';
};
};
opencl.enable = lib.mkEnableOption ''OpenCL support using ROCM runtime library'';
};
config = {
boot.kernelParams =
lib.optionals cfg.legacySupport.enable [
"amdgpu.si_support=1"
"amdgpu.cik_support=1"
"radeon.si_support=0"
"radeon.cik_support=0"
]
++ lib.optionals cfg.overdrive.enable [
"amdgpu.ppfeaturemask=${cfg.overdrive.ppfeaturemask}"
];
boot.initrd.kernelModules = lib.optionals cfg.initrd.enable [ "amdgpu" ];
hardware.graphics = lib.mkIf cfg.opencl.enable {
enable = lib.mkDefault true;
extraPackages = [
pkgs.rocmPackages.clr
pkgs.rocmPackages.clr.icd
];
};
};
meta = {
maintainers = with lib.maintainers; [ johnrtitor ];
};
}

View File

@@ -0,0 +1,56 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.hardware.argonone;
in
{
options.services.hardware.argonone = {
enable = lib.mkEnableOption "the driver for Argon One Raspberry Pi case fan and power button";
package = lib.mkPackageOption pkgs "argononed" { };
};
config = lib.mkIf cfg.enable {
hardware.i2c.enable = true;
hardware.deviceTree.overlays = [
{
name = "argononed";
dtboFile = "${cfg.package}/boot/overlays/argonone.dtbo";
}
{
name = "i2c1-okay-overlay";
dtsText = ''
/dts-v1/;
/plugin/;
/ {
compatible = "brcm,bcm2711";
fragment@0 {
target = <&i2c1>;
__overlay__ {
status = "okay";
};
};
};
'';
}
];
environment.systemPackages = [ cfg.package ];
systemd.services.argononed = {
description = "Argon One Raspberry Pi case Daemon Service";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "forking";
ExecStart = "${cfg.package}/bin/argononed";
PIDFile = "/run/argononed.pid";
Restart = "on-failure";
};
};
};
meta.maintainers = with lib.maintainers; [ misterio77 ];
}

View File

@@ -0,0 +1,153 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.asusd;
in
{
imports = [
(lib.mkRemovedOptionModule
[
"services"
"asusd"
"auraConfig"
]
''
This option has been replaced by `services.asusd.auraConfigs' because asusd
supports multiple aura devices since version 6.0.0.
''
)
];
options = {
services.asusd =
with lib.types;
let
configType = submodule (
{ text, source, ... }:
{
options = {
text = lib.mkOption {
default = null;
type = nullOr lines;
description = "Text of the file.";
};
source = lib.mkOption {
default = null;
type = nullOr path;
description = "Path of the source file.";
};
};
}
);
in
{
enable = lib.mkEnableOption "the asusd service for ASUS ROG laptops";
package = lib.mkPackageOption pkgs "asusctl" { };
enableUserService = lib.mkOption {
type = bool;
default = false;
description = ''
Activate the asusd-user service.
'';
};
animeConfig = lib.mkOption {
type = nullOr configType;
default = null;
description = ''
The content of /etc/asusd/anime.ron.
See <https://asus-linux.org/manual/asusctl-manual/#anime-control>.
'';
};
asusdConfig = lib.mkOption {
type = nullOr configType;
default = null;
description = ''
The content of /etc/asusd/asusd.ron.
See <https://asus-linux.org/manual/asusctl-manual/>.
'';
};
auraConfigs = lib.mkOption {
type = attrsOf configType;
default = { };
description = ''
The content of /etc/asusd/aura_<name>.ron.
See <https://asus-linux.org/manual/asusctl-manual/#led-keyboard-control>.
'';
};
profileConfig = lib.mkOption {
type = nullOr configType;
default = null;
description = ''
The content of /etc/asusd/profile.ron.
See <https://asus-linux.org/manual/asusctl-manual/#profiles>.
'';
};
fanCurvesConfig = lib.mkOption {
type = nullOr configType;
default = null;
description = ''
The content of /etc/asusd/fan_curves.ron.
See <https://asus-linux.org/manual/asusctl-manual/#fan-curves>.
'';
};
userLedModesConfig = lib.mkOption {
type = nullOr configType;
default = null;
description = ''
The content of /etc/asusd/asusd-user-ledmodes.ron.
See <https://asus-linux.org/manual/asusctl-manual/#led-keyboard-control>.
'';
};
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
environment.etc =
let
maybeConfig =
name: cfg:
lib.mkIf (cfg != null) (
(if (cfg.source != null) then { source = cfg.source; } else { text = cfg.text; })
// {
mode = "0644";
}
);
in
{
"asusd/anime.ron" = maybeConfig "anime.ron" cfg.animeConfig;
"asusd/asusd.ron" = maybeConfig "asusd.ron" cfg.asusdConfig;
"asusd/profile.ron" = maybeConfig "profile.ron" cfg.profileConfig;
"asusd/fan_curves.ron" = maybeConfig "fan_curves.ron" cfg.fanCurvesConfig;
"asusd/asusd_user_ledmodes.ron" = maybeConfig "asusd_user_ledmodes.ron" cfg.userLedModesConfig;
}
// lib.attrsets.concatMapAttrs (prod_id: value: {
"asusd/aura_${prod_id}.ron" = maybeConfig "aura_${prod_id}.ron" value;
}) cfg.auraConfigs;
services.dbus.enable = true;
systemd.packages = [ cfg.package ];
services.dbus.packages = [ cfg.package ];
services.udev.packages = [ cfg.package ];
services.supergfxd.enable = lib.mkDefault true;
systemd.user.services.asusd-user.enable = cfg.enableUserService;
};
meta.maintainers = pkgs.asusctl.meta.maintainers;
}

View File

@@ -0,0 +1,59 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.auto-cpufreq;
cfgFilename = "auto-cpufreq.conf";
cfgFile = format.generate cfgFilename cfg.settings;
format = pkgs.formats.ini { };
in
{
options = {
services.auto-cpufreq = {
enable = lib.mkEnableOption "auto-cpufreq daemon";
settings = lib.mkOption {
description = ''
Configuration for `auto-cpufreq`.
The available options can be found in [the example configuration file](https://github.com/AdnanHodzic/auto-cpufreq/blob/v${pkgs.auto-cpufreq.version}/auto-cpufreq.conf-example).
'';
default = { };
type = lib.types.submodule { freeformType = format.type; };
};
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ pkgs.auto-cpufreq ];
systemd = {
packages = [ pkgs.auto-cpufreq ];
services.auto-cpufreq = {
# Workaround for https://github.com/NixOS/nixpkgs/issues/81138
wantedBy = [ "multi-user.target" ];
path = with pkgs; [
bash
coreutils
];
serviceConfig.WorkingDirectory = "";
serviceConfig.ExecStart = [
""
"${lib.getExe pkgs.auto-cpufreq} --daemon --config ${cfgFile}"
];
};
};
};
# uses attributes of the linked package
meta = {
buildDocsInSandbox = false;
maintainers = with lib.maintainers; [ nicoo ];
};
}

View File

@@ -0,0 +1,86 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.auto-epp;
format = pkgs.formats.ini { };
inherit (lib) mkOption types;
in
{
options = {
services.auto-epp = {
enable = lib.mkEnableOption "auto-epp for amd active pstate";
package = lib.mkPackageOption pkgs "auto-epp" { };
settings = mkOption {
type = types.submodule {
freeformType = format.type;
options = {
Settings = {
epp_state_for_AC = mkOption {
type = types.str;
default = "balance_performance";
description = ''
energy_performance_preference when on plugged in
::: {.note}
See available epp states by running:
{command}`cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences`
:::
'';
};
epp_state_for_BAT = mkOption {
type = types.str;
default = "power";
description = ''
`energy_performance_preference` when on battery
::: {.note}
See available epp states by running:
{command}`cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences`
:::
'';
};
};
};
};
default = { };
description = ''
Settings for the auto-epp application.
See upstream example: <https://github.com/jothi-prasath/auto-epp/blob/master/sample-auto-epp.conf>
'';
};
};
};
config = lib.mkIf cfg.enable {
boot.kernelParams = [
"amd_pstate=active"
];
environment.etc."auto-epp.conf".source = format.generate "auto-epp.conf" cfg.settings;
systemd.packages = [ cfg.package ];
systemd.services.auto-epp = {
after = [ "multi-user.target" ];
wantedBy = [ "multi-user.target" ];
description = "auto-epp - Automatic EPP Changer for amd-pstate-epp";
serviceConfig = {
Type = "simple";
User = "root";
ExecStart = lib.getExe cfg.package;
Restart = "on-failure";
};
};
};
meta.maintainers = with lib.maintainers; [ lamarios ];
}

View File

@@ -0,0 +1,72 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.bitbox-bridge;
in
{
options = {
services.bitbox-bridge = {
enable = lib.mkEnableOption "Bitbox bridge daemon, for use with Bitbox hardware wallets.";
package = lib.mkPackageOption pkgs "bitbox-bridge" { };
port = lib.mkOption {
type = lib.types.port;
default = 8178;
description = ''
Listening port for the bitbox-bridge.
'';
};
runOnMount = lib.mkEnableOption null // {
default = true;
description = ''
Run bitbox-bridge.service only when hardware wallet is plugged, also registers the systemd device unit.
This option is enabled by default to save power, when false, bitbox-bridge service runs all the time instead.
'';
};
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
services.udev.packages = [
cfg.package
]
++ lib.optionals (cfg.runOnMount) [
(pkgs.writeTextFile {
name = "bitbox-bridge-run-on-mount-udev-rules";
destination = "/etc/udev/rules.d/99-bitbox-bridge-run-on-mount.rules";
text = ''
SUBSYSTEM=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2403", MODE="0660", GROUP="bitbox", TAG+="systemd", SYMLINK+="bitbox02", ENV{SYSTEMD_WANTS}="bitbox-bridge.service"
'';
})
];
systemd.services.bitbox-bridge = {
description = "BitBox Bridge";
wantedBy = [ "multi-user.target" ];
bindsTo = lib.optionals (cfg.runOnMount) [ "dev-bitbox02.device" ];
after = [ "network.target" ];
serviceConfig = {
Type = "simple";
ExecStart = "${cfg.package}/bin/bitbox-bridge -p ${builtins.toString cfg.port}";
User = "bitbox";
};
};
users.groups.bitbox = { };
users.users.bitbox = {
group = "bitbox";
description = "bitbox-bridge daemon user";
isSystemUser = true;
extraGroups = [ "bitbox" ];
};
};
}

View File

@@ -0,0 +1,202 @@
{
config,
lib,
pkgs,
utils,
...
}:
let
cfg = config.hardware.bluetooth;
package = cfg.package;
inherit (lib)
mkEnableOption
mkIf
mkOption
mkPackageOption
mkRenamedOptionModule
mkRemovedOptionModule
concatStringsSep
optional
optionalAttrs
recursiveUpdate
types
;
cfgFmt = pkgs.formats.ini { };
defaults = {
General.ControllerMode = "dual";
Policy.AutoEnable = cfg.powerOnBoot;
};
hasDisabledPlugins = builtins.length cfg.disabledPlugins > 0;
in
{
imports = [
(mkRenamedOptionModule [ "hardware" "bluetooth" "config" ] [ "hardware" "bluetooth" "settings" ])
(mkRemovedOptionModule [ "hardware" "bluetooth" "extraConfig" ] ''
Use hardware.bluetooth.settings instead.
This is part of the general move to use structured settings instead of raw
text for config as introduced by RFC0042:
https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md
'')
];
###### interface
options = {
hardware.bluetooth = {
enable = mkEnableOption "support for Bluetooth";
hsphfpd.enable = mkEnableOption "support for hsphfpd[-prototype] implementation";
powerOnBoot = mkOption {
type = types.bool;
default = true;
description = "Whether to power up the default Bluetooth controller on boot.";
};
package = mkPackageOption pkgs "bluez" { };
disabledPlugins = mkOption {
type = types.listOf types.str;
default = [ ];
description = "Built-in plugins to disable";
};
settings = mkOption {
type = cfgFmt.type;
default = { };
example = {
General = {
ControllerMode = "bredr";
};
};
description = ''
Set configuration for system-wide bluetooth (/etc/bluetooth/main.conf).
See <https://github.com/bluez/bluez/blob/master/src/main.conf> for full list of options.
'';
};
input = mkOption {
type = cfgFmt.type;
default = { };
example = {
General = {
IdleTimeout = 30;
ClassicBondedOnly = true;
};
};
description = ''
Set configuration for the input service (/etc/bluetooth/input.conf).
See <https://github.com/bluez/bluez/blob/master/profiles/input/input.conf> for full list of options.
'';
};
network = mkOption {
type = cfgFmt.type;
default = { };
example = {
General = {
DisableSecurity = true;
};
};
description = ''
Set configuration for the network service (/etc/bluetooth/network.conf).
See <https://github.com/bluez/bluez/blob/master/profiles/network/network.conf> for full list of options.
'';
};
};
};
###### implementation
config = mkIf cfg.enable {
environment.systemPackages = [ package ] ++ optional cfg.hsphfpd.enable pkgs.hsphfpd;
environment.etc."bluetooth/input.conf".source = cfgFmt.generate "input.conf" cfg.input;
environment.etc."bluetooth/network.conf".source = cfgFmt.generate "network.conf" cfg.network;
environment.etc."bluetooth/main.conf".source = cfgFmt.generate "main.conf" (
recursiveUpdate defaults cfg.settings
);
services.udev.packages = [ package ];
services.dbus.packages = [ package ] ++ optional cfg.hsphfpd.enable pkgs.hsphfpd;
systemd.packages = [ package ];
systemd.services = {
bluetooth =
let
# `man bluetoothd` will refer to main.conf in the nix store but bluez
# will in fact load the configuration file at /etc/bluetooth/main.conf
# so force it here to avoid any ambiguity and things suddenly breaking
# if/when the bluez derivation is changed.
args = [
"-f"
"/etc/bluetooth/main.conf"
]
++ optional hasDisabledPlugins "--noplugin=${concatStringsSep "," cfg.disabledPlugins}";
in
{
wantedBy = [ "bluetooth.target" ];
aliases = [ "dbus-org.bluez.service" ];
# restarting can leave people without a mouse/keyboard
restartIfChanged = false;
serviceConfig = {
ExecStart = [
""
"${package}/libexec/bluetooth/bluetoothd ${utils.escapeSystemdExecArgs args}"
];
CapabilityBoundingSet = [
"CAP_NET_BIND_SERVICE" # sockets and tethering
];
ConfigurationDirectoryMode = "0755";
NoNewPrivileges = true;
RestrictNamespaces = true;
ProtectControlGroups = true;
MemoryDenyWriteExecute = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = "@system-service";
LockPersonality = true;
RestrictRealtime = true;
ProtectProc = "invisible";
PrivateTmp = true;
PrivateUsers = false;
# loading hardware modules
ProtectKernelModules = false;
ProtectKernelTunables = false;
PrivateNetwork = false; # tethering
};
};
}
// (optionalAttrs cfg.hsphfpd.enable {
hsphfpd = {
after = [ "bluetooth.service" ];
requires = [ "bluetooth.service" ];
wantedBy = [ "bluetooth.target" ];
description = "A prototype implementation used for connecting HSP/HFP Bluetooth devices";
serviceConfig.ExecStart = "${pkgs.hsphfpd}/bin/hsphfpd.pl";
};
});
systemd.user.services = {
obex.aliases = [ "dbus-org.bluez.obex.service" ];
}
// optionalAttrs cfg.hsphfpd.enable {
telephony_client = {
wantedBy = [ "default.target" ];
description = "telephony_client for hsphfpd";
serviceConfig.ExecStart = "${pkgs.hsphfpd}/bin/telephony_client.pl";
};
};
};
}

View File

@@ -0,0 +1,33 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.hardware.bolt;
in
{
options = {
services.hardware.bolt = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to enable Bolt, a userspace daemon to enable
security levels for Thunderbolt 3 on GNU/Linux.
Bolt is used by GNOME 3 to handle Thunderbolt settings.
'';
};
package = lib.mkPackageOption pkgs "bolt" { };
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
services.udev.packages = [ cfg.package ];
systemd.packages = [ cfg.package ];
};
}

View File

@@ -0,0 +1,64 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.brltty;
targets = [
"default.target"
"multi-user.target"
"rescue.target"
"emergency.target"
];
genApiKey = pkgs.writers.writeDash "generate-brlapi-key" ''
if ! test -f /etc/brlapi.key; then
echo -n generating brlapi key...
${pkgs.brltty}/bin/brltty-genkey -f /etc/brlapi.key
echo done
fi
'';
in
{
options = {
services.brltty.enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to enable the BRLTTY daemon.";
};
};
config = lib.mkIf cfg.enable {
users.users.brltty = {
description = "BRLTTY daemon user";
group = "brltty";
isSystemUser = true;
};
users.groups = {
brltty = { };
brlapi = { };
};
systemd.services."brltty@".serviceConfig = {
ExecStartPre = "!${genApiKey}";
};
# Install all upstream-provided files
systemd.packages = [ pkgs.brltty ];
systemd.tmpfiles.packages = [ pkgs.brltty ];
services.udev.packages = [ pkgs.brltty ];
environment.systemPackages = [ pkgs.brltty ];
# Add missing WantedBys (see issue #81138)
systemd.paths.brltty.wantedBy = targets;
systemd.paths."brltty@".wantedBy = targets;
};
}

View File

@@ -0,0 +1,138 @@
# INTEGRATION NOTES:
# Buffyboard integrates as a virtual device in /dev/input
# which reads touch or pointer events from other input devices
# and generates events based on where those map to the keys it renders to the framebuffer.
#
# Buffyboard generates these events whether or not its onscreen keyboard is actually visible.
# Hence special care is needed if running anything which claims ownership of the display (such as a desktop environment),
# to avoid unwanted input events being triggered during normal desktop operation.
#
# Desktop users are recommended to either:
# 1. Stop buffyboard once your DE is started.
# e.g. `services.buffyboard.unitConfig.Conflicts = [ "my-de.service" ];`
# 2. Configure your DE to ignore input events from buffyboard (product-id=25209; vendor-id=26214; name=rd)
# e.g. `echo 'input "26214:25209:rd" events disabled' > ~/.config/sway/config`
{
config,
lib,
pkgs,
utils,
...
}:
let
cfg = config.services.buffyboard;
ini = pkgs.formats.ini { };
in
{
meta.maintainers = with lib.maintainers; [ colinsane ];
options = {
services.buffyboard = with lib; {
enable = mkEnableOption "buffyboard framebuffer keyboard (on-screen keyboard)";
package = mkPackageOption pkgs "buffybox" { };
extraFlags = mkOption {
type = types.listOf types.str;
default = [ ];
description = ''
Extra CLI arguments to pass to buffyboard.
'';
example = [
"--geometry=1920x1080@640,0"
"--dpi=192"
"--rotate=2"
"--verbose"
];
};
configFile = mkOption {
type = lib.types.path;
default = ini.generate "buffyboard.conf" (lib.filterAttrsRecursive (_: v: v != null) cfg.settings);
defaultText = lib.literalExpression ''ini.generate "buffyboard.conf" cfg.settings'';
description = ''
Path to an INI format configuration file to provide Buffyboard.
By default, this is generated from whatever you've set in `settings`.
If specified manually, then `settings` is ignored.
For an example config file see [here](https://gitlab.postmarketos.org/postmarketOS/buffybox/-/blob/master/buffyboard/buffyboard.conf)
'';
};
settings = mkOption {
description = ''
Settings to include in /etc/buffyboard.conf.
Every option here is strictly optional:
Buffyboard will use its own baked-in defaults for those options left unset.
'';
type = types.submodule {
freeformType = ini.type;
options.input.pointer = mkOption {
type = types.nullOr types.bool;
default = null;
description = ''
Enable or disable the use of a hardware mouse or other pointing device.
'';
};
options.input.touchscreen = mkOption {
type = types.nullOr types.bool;
default = null;
description = ''
Enable or disable the use of the touchscreen.
'';
};
options.theme.default = mkOption {
type = types.either types.str (
types.enum [
null
"adwaita-dark"
"breezy-dark"
"breezy-light"
"nord-dark"
"nord-light"
"pmos-dark"
"pmos-light"
]
);
default = null;
description = ''
Selects the default theme on boot. Can be changed at runtime to the alternative theme.
'';
};
options.quirks.fbdev_force_refresh = mkOption {
type = types.nullOr types.bool;
default = null;
description = ''
If true and using the framebuffer backend, this triggers a display refresh after every draw operation.
This has a negative performance impact.
'';
};
};
default = { };
};
};
};
config = lib.mkIf cfg.enable {
systemd.packages = [ cfg.package ];
systemd.services.buffyboard = {
# upstream provides the service (including systemd hardening): we just configure it to start by default
# and override ExecStart so as to optionally pass extra arguments
serviceConfig.ExecStart = [
"" # clear default ExecStart
(utils.escapeSystemdExecArgs (
[
(lib.getExe' cfg.package "buffyboard")
"--config-override"
cfg.configFile
]
++ cfg.extraFlags
))
];
wantedBy = [ "getty.target" ];
before = [ "getty.target" ];
};
};
}

View File

@@ -0,0 +1,40 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.ddccontrol;
in
{
###### interface
options = {
services.ddccontrol = {
enable = lib.mkEnableOption "ddccontrol for controlling displays";
};
};
###### implementation
config = lib.mkIf cfg.enable {
# Load the i2c-dev module
boot.kernelModules = [ "i2c_dev" ];
# Give users access to the "gddccontrol" tool
environment.systemPackages = [
pkgs.ddccontrol
];
services.dbus.packages = [
pkgs.ddccontrol
];
systemd.packages = [
pkgs.ddccontrol
];
};
}

View File

@@ -0,0 +1,47 @@
{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.hardware.deepcool-digital-linux;
in
{
meta.maintainers = [ lib.maintainers.NotAShelf ];
options.services.hardware.deepcool-digital-linux = {
enable = lib.mkEnableOption "DeepCool Digital monitoring daemon";
package = lib.mkPackageOption pkgs "deepcool-digital-linux" { };
extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = lib.literalExpression ''
[
# Change the update interval
"--update 750"
# Enable the alarm
"--alarm"
]
'';
description = ''
Extra command line arguments to be passed to the deepcool-digital-linux daemon.
'';
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
systemd.services.deepcool-digital-linux = {
description = "DeepCool Digital";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
StateDirectory = "deepcool-digital-linux";
WorkingDirectory = "/var/lib/deepcool-digital-linux";
ExecStart = "${lib.getExe cfg.package} ${lib.escapeShellArgs cfg.extraArgs}";
Restart = "always";
};
};
};
}

View File

@@ -0,0 +1,141 @@
# Customizing display configuration {#module-hardware-display}
This section describes how to customize display configuration using:
- kernel modes
- EDID files
Example situations it can help you with:
- display controllers (external hardware) not advertising EDID at all,
- misbehaving graphics drivers,
- loading custom display configuration before the Display Manager is running,
## Forcing display modes {#module-hardware-display-modes}
In case of very wrong monitor controller and/or video driver combination you can
[force the display to be enabled](https://mjmwired.net/kernel/Documentation/fb/modedb.txt#41)
and skip some driver-side checks by adding `video=<OUTPUT>:e` to `boot.kernelParams`.
This is exactly the case with [`amdgpu` drivers](https://gitlab.freedesktop.org/drm/amd/-/issues/615#note_1987392)
```nix
{
# force enabled output to skip `amdgpu` checks
hardware.display.outputs."DP-1".mode = "e";
# completely disable output no matter what is connected to it
hardware.display.outputs."VGA-2".mode = "d";
/*
equals
boot.kernelParams = [ "video=DP-1:e" "video=VGA-2:d" ];
*/
}
```
## Crafting custom EDID files {#module-hardware-display-edid-custom}
To make custom EDID binaries discoverable you should first create a derivation storing them at
`$out/lib/firmware/edid/` and secondly add that derivation to `hardware.display.edid.packages` NixOS option:
```nix
{
hardware.display.edid.packages = [
(pkgs.runCommand "edid-custom" { } ''
mkdir -p $out/lib/firmware/edid
base64 -d > "$out/lib/firmware/edid/custom1.bin" <<'EOF'
<insert your base64 encoded EDID file here `base64 < /sys/class/drm/card0-.../edid`>
EOF
base64 -d > "$out/lib/firmware/edid/custom2.bin" <<'EOF'
<insert your base64 encoded EDID file here `base64 < /sys/class/drm/card1-.../edid`>
EOF
'')
];
}
```
There are 2 options significantly easing preparation of EDID files:
- `hardware.display.edid.linuxhw`
- `hardware.display.edid.modelines`
## Assigning EDID files to displays {#module-hardware-display-edid-assign}
To assign available custom EDID binaries to your monitor (video output) use `hardware.display.outputs."<NAME>".edid` option.
Under the hood it adds `drm.edid_firmware` entry to `boot.kernelParams` NixOS option for each configured output:
```nix
{
hardware.display.outputs."VGA-1".edid = "custom1.bin";
hardware.display.outputs."VGA-2".edid = "custom2.bin";
/*
equals:
boot.kernelParams = [ "drm.edid_firmware=VGA-1:edid/custom1.bin,VGA-2:edid/custom2.bin" ];
*/
}
```
## Pulling files from linuxhw/EDID database {#module-hardware-display-edid-linuxhw}
`hardware.display.edid.linuxhw` utilizes `pkgs.linuxhw-edid-fetcher` to extract EDID files
from <https://github.com/linuxhw/EDID> based on simple string/regexp search identifying exact entries:
```nix
{
hardware.display.edid.linuxhw."PG278Q_2014" = [
"PG278Q"
"2014"
];
/*
equals:
hardware.display.edid.packages = [
(pkgs.linuxhw-edid-fetcher.override {
displays = {
"PG278Q_2014" = [ "PG278Q" "2014" ];
};
})
];
*/
}
```
## Using XFree86 Modeline definitions {#module-hardware-display-edid-modelines}
`hardware.display.edid.modelines` utilizes `pkgs.edid-generator` package allowing you to
conveniently use [`XFree86 Modeline`](https://en.wikipedia.org/wiki/XFree86_Modeline) entries as EDID binaries:
```nix
{
hardware.display.edid.modelines."PG278Q_60" =
" 241.50 2560 2608 2640 2720 1440 1443 1448 1481 -hsync +vsync";
hardware.display.edid.modelines."PG278Q_120" =
" 497.75 2560 2608 2640 2720 1440 1443 1448 1525 +hsync -vsync";
/*
equals:
hardware.display.edid.packages = [
(pkgs.edid-generator.overrideAttrs {
clean = true;
modelines = ''
Modeline "PG278Q_60" 241.50 2560 2608 2640 2720 1440 1443 1448 1481 -hsync +vsync
Modeline "PG278Q_120" 497.75 2560 2608 2640 2720 1440 1443 1448 1525 +hsync -vsync
'';
})
];
*/
}
```
## Complete example for Asus PG278Q {#module-hardware-display-pg278q}
And finally this is a complete working example for a 2014 (first) batch of [Asus PG278Q monitor with `amdgpu` drivers](https://gitlab.freedesktop.org/drm/amd/-/issues/615#note_1987392):
```nix
{
hardware.display.edid.modelines."PG278Q_60" =
" 241.50 2560 2608 2640 2720 1440 1443 1448 1481 -hsync +vsync";
hardware.display.edid.modelines."PG278Q_120" =
" 497.75 2560 2608 2640 2720 1440 1443 1448 1525 +hsync -vsync";
hardware.display.outputs."DP-1".edid = "PG278Q_60.bin";
hardware.display.outputs."DP-1".mode = "e";
}
```

View File

@@ -0,0 +1,207 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.hardware.display;
in
{
meta.doc = ./display.md;
meta.maintainers = with lib.maintainers; [
nazarewk
];
options = {
hardware.display.edid.enable = lib.mkOption {
type = with lib.types; bool;
default = cfg.edid.packages != null;
defaultText = lib.literalExpression "config.hardware.display.edid.packages != null";
description = ''
Enables handling of EDID files
'';
};
hardware.display.edid.packages = lib.mkOption {
type = with lib.types; listOf package;
default = [ ];
description = ''
List of packages containing EDID binary files at `$out/lib/firmware/edid`.
Such files will be available for use in `drm.edid_firmware` kernel
parameter as `edid/<filename>`.
You can craft one directly here or use sibling options `linuxhw` and `modelines`.
'';
example = lib.literalExpression ''
[
(pkgs.runCommand "edid-custom" {} '''
mkdir -p "$out/lib/firmware/edid"
base64 -d > "$out/lib/firmware/edid/custom1.bin" <<'EOF'
<insert your base64 encoded EDID file here `base64 < /sys/class/drm/card0-.../edid`>
EOF
''')
]
'';
apply =
list:
if list == [ ] then
null
else
(pkgs.buildEnv {
name = "firmware-edid";
paths = list;
pathsToLink = [ "/lib/firmware/edid" ];
ignoreCollisions = true;
})
// {
compressFirmware = false;
};
};
hardware.display.edid.linuxhw = lib.mkOption {
type = with lib.types; attrsOf (listOf str);
default = { };
description = ''
Exposes EDID files from users-sourced database at <https://github.com/linuxhw/EDID>
Attribute names will be mapped to EDID filenames `<NAME>.bin`.
Attribute values are lists of `awk` regexp patterns that (together) must match
exactly one line in either of:
- [AnalogDisplay.md](https://raw.githubusercontent.com/linuxhw/EDID/master/AnalogDisplay.md)
- [DigitalDisplay.md](https://raw.githubusercontent.com/linuxhw/EDID/master/DigitalDisplay.md)
There is no universal way of locating your device config, but here are some practical tips:
1. locate your device:
- find your model number (second column)
- locate manufacturer (first column) and go through the list manually
2. narrow down results using other columns until there is only one left:
- `Name` column
- production date (`Made` column)
- resolution `Res`
- screen diagonal (`Inch` column)
- as a last resort use `ID` from the last column
'';
example = lib.literalExpression ''
{
PG278Q_2014 = [ "PG278Q" "2014" ];
}
'';
apply =
displays:
if displays == { } then null else pkgs.linuxhw-edid-fetcher.override { inherit displays; };
};
hardware.display.edid.modelines = lib.mkOption {
type = with lib.types; attrsOf str;
default = { };
description = ''
Attribute set of XFree86 Modelines automatically converted
and exposed as `edid/<name>.bin` files in initrd.
See for more information:
- <https://en.wikipedia.org/wiki/XFree86_Modeline>
'';
example = lib.literalExpression ''
{
"PG278Q_60" = " 241.50 2560 2608 2640 2720 1440 1443 1448 1481 -hsync +vsync";
"PG278Q_120" = " 497.75 2560 2608 2640 2720 1440 1443 1448 1525 +hsync -vsync";
"U2711_60" = " 241.50 2560 2600 2632 2720 1440 1443 1448 1481 -hsync +vsync";
}
'';
apply =
modelines:
if modelines == { } then
null
else
pkgs.edid-generator.overrideAttrs {
clean = true;
passthru.config = modelines;
modelines = lib.trivial.pipe modelines [
(lib.mapAttrsToList (
name: value:
lib.throwIfNot (
builtins.stringLength name <= 12
) "Modeline name must be 12 characters or less" ''Modeline "${name}" ${value}''
))
(builtins.map (line: "${line}\n"))
(lib.strings.concatStringsSep "")
];
};
};
hardware.display.outputs = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule {
options = {
edid = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = ''
An EDID filename to be used for configured display, as in `edid/<filename>`.
See for more information:
- `hardware.display.edid.packages`
- <https://wiki.archlinux.org/title/Kernel_mode_setting#Forcing_modes_and_EDID>
'';
};
mode = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = ''
A `video` kernel parameter (framebuffer mode) configuration for the specific output:
<xres>x<yres>[M][R][-<bpp>][@<refresh>][i][m][eDd]
See for more information:
- <https://docs.kernel.org/fb/modedb.html>
- <https://wiki.archlinux.org/title/Kernel_mode_setting#Forcing_modes>
'';
example = lib.literalExpression ''
"e"
'';
};
};
}
);
description = ''
Hardware/kernel-level configuration of specific outputs.
'';
default = { };
example = lib.literalExpression ''
{
edid.modelines."PG278Q_60" = "241.50 2560 2608 2640 2720 1440 1443 1448 1481 -hsync +vsync";
outputs."DP-1".edid = "PG278Q_60.bin";
outputs."DP-1".mode = "e";
}
'';
};
};
config = lib.mkMerge [
{
hardware.display.edid.packages =
lib.optional (cfg.edid.modelines != null) cfg.edid.modelines
++ lib.optional (cfg.edid.linuxhw != null) cfg.edid.linuxhw;
boot.kernelParams =
# forcing video modes
lib.trivial.pipe cfg.outputs [
(lib.attrsets.filterAttrs (_: spec: spec.mode != null))
(lib.mapAttrsToList (output: spec: "video=${output}:${spec.mode}"))
]
# selecting EDID for displays
++ lib.trivial.pipe cfg.outputs [
(lib.attrsets.filterAttrs (_: spec: spec.edid != null))
(lib.mapAttrsToList (output: spec: "${output}:edid/${spec.edid}"))
(builtins.concatStringsSep ",")
(p: lib.optional (p != "") "drm.edid_firmware=${p}")
];
}
(lib.mkIf (cfg.edid.packages != null) {
# services.udev implements hardware.firmware option
services.udev.enable = true;
hardware.firmware = [ cfg.edid.packages ];
})
];
}

View File

@@ -0,0 +1,55 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.hardware.fancontrol;
configFile = pkgs.writeText "fancontrol.conf" cfg.config;
in
{
options.hardware.fancontrol = {
enable = lib.mkEnableOption "software fan control (requires fancontrol.config)";
config = lib.mkOption {
type = lib.types.lines;
description = "Required fancontrol configuration file content. See {manpage}`pwmconfig(8)` from the lm_sensors package.";
example = ''
# Configuration file generated by pwmconfig
INTERVAL=10
DEVPATH=hwmon3=devices/virtual/thermal/thermal_zone2 hwmon4=devices/platform/f71882fg.656
DEVNAME=hwmon3=soc_dts1 hwmon4=f71869a
FCTEMPS=hwmon4/device/pwm1=hwmon3/temp1_input
FCFANS=hwmon4/device/pwm1=hwmon4/device/fan1_input
MINTEMP=hwmon4/device/pwm1=35
MAXTEMP=hwmon4/device/pwm1=65
MINSTART=hwmon4/device/pwm1=150
MINSTOP=hwmon4/device/pwm1=0
'';
};
};
config = lib.mkIf cfg.enable {
systemd.services.fancontrol = {
documentation = [ "man:fancontrol(8)" ];
description = "software fan control";
wantedBy = [ "multi-user.target" ];
after = [ "lm_sensors.service" ];
serviceConfig = {
Restart = "on-failure";
ExecStart = "${lib.getExe' pkgs.lm_sensors "fancontrol"} ${configFile}";
};
};
# On some systems, the fancontrol service does not resume properly after sleep because the pwm status of the fans
# is not reset properly. Restarting the service fixes this, in accordance with https://github.com/lm-sensors/lm-sensors/issues/172.
powerManagement.resumeCommands = ''
systemctl restart fancontrol.service
'';
};
}

View File

@@ -0,0 +1,66 @@
{
config,
lib,
pkgs,
utils,
...
}:
let
cfg = config.services.freefall;
in
{
options.services.freefall = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to protect HP/Dell laptop hard drives (not SSDs) in free fall.
'';
};
package = lib.mkPackageOption pkgs "freefall" { };
devices = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ "/dev/sda" ];
description = ''
Device paths to all internal spinning hard drives.
'';
};
};
config =
let
mkService =
dev:
assert dev != "";
let
dev' = utils.escapeSystemdPath dev;
in
lib.nameValuePair "freefall-${dev'}" {
description = "Free-fall protection for ${dev}";
after = [ "${dev'}.device" ];
wantedBy = [ "${dev'}.device" ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/freefall ${dev}";
Restart = "on-failure";
Type = "forking";
};
};
in
lib.mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
systemd.services = builtins.listToAttrs (map mkService cfg.devices);
};
}

View File

@@ -0,0 +1,228 @@
# fwupd daemon.
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.fwupd;
format = pkgs.formats.ini {
listToValue = l: lib.concatStringsSep ";" (map (s: lib.generators.mkValueStringDefault { } s) l);
mkKeyValue = lib.generators.mkKeyValueDefault { } "=";
};
customEtc = {
"fwupd/fwupd.conf" = {
source = format.generate "fwupd.conf" (
{
fwupd = cfg.daemonSettings;
}
// lib.optionalAttrs (lib.length (lib.attrNames cfg.uefiCapsuleSettings) != 0) {
uefi_capsule = cfg.uefiCapsuleSettings;
}
);
# fwupd tries to chmod the file if it doesn't have the right permissions
mode = "0640";
};
};
originalEtc =
let
mkEtcFile = n: lib.nameValuePair n { source = "${cfg.package}/etc/${n}"; };
in
lib.listToAttrs (map mkEtcFile cfg.package.filesInstalledToEtc);
extraTrustedKeys =
let
mkName = p: "pki/fwupd/${baseNameOf (toString p)}";
mkEtcFile = p: lib.nameValuePair (mkName p) { source = p; };
in
lib.listToAttrs (map mkEtcFile cfg.extraTrustedKeys);
enableRemote = base: remote: {
"fwupd/remotes.d/${remote}.conf" = {
source = pkgs.runCommand "${remote}-enabled.conf" { } ''
sed "s,^Enabled=false,Enabled=true," \
"${base}/etc/fwupd/remotes.d/${remote}.conf" > "$out"
'';
};
};
remotes =
(lib.foldl' (
configFiles: remote: configFiles // (enableRemote cfg.package remote)
) { } cfg.extraRemotes)
// (
# We cannot include the file in $out and rely on filesInstalledToEtc
# to install it because it would create a cyclic dependency between
# the outputs. We also need to enable the remote,
# which should not be done by default.
lib.optionalAttrs (cfg.daemonSettings.TestDevices or false) (
enableRemote cfg.package.installedTests "fwupd-tests"
)
);
in
{
###### interface
options = {
services.fwupd = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to enable fwupd, a DBus service that allows
applications to update firmware.
'';
};
extraTrustedKeys = lib.mkOption {
type = lib.types.listOf lib.types.path;
default = [ ];
example = lib.literalExpression "[ /etc/nixos/fwupd/myfirmware.pem ]";
description = ''
Installing a public key allows firmware signed with a matching private key to be recognized as trusted, which may require less authentication to install than for untrusted files. By default trusted firmware can be upgraded (but not downgraded) without the user or administrator password. Only very few keys are installed by default.
'';
};
extraRemotes = lib.mkOption {
type = with lib.types; listOf str;
default = [ ];
example = [ "lvfs-testing" ];
description = ''
Enables extra remotes in fwupd. See `/etc/fwupd/remotes.d`.
'';
};
package = lib.mkPackageOption pkgs "fwupd" { };
daemonSettings = lib.mkOption {
type = lib.types.submodule {
freeformType = format.type.nestedTypes.elemType;
options = {
DisabledDevices = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [ "2082b5e0-7a64-478a-b1b2-e3404fab6dad" ];
description = ''
List of device GUIDs to be disabled.
'';
};
DisabledPlugins = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [ "udev" ];
description = ''
List of plugins to be disabled.
'';
};
EspLocation = lib.mkOption {
type = lib.types.path;
default = config.boot.loader.efi.efiSysMountPoint;
defaultText = lib.literalExpression "config.boot.loader.efi.efiSysMountPoint";
description = ''
The EFI system partition (ESP) path used if UDisks is not available
or if this partition is not mounted at /boot/efi, /boot, or /efi
'';
};
TestDevices = lib.mkOption {
internal = true;
type = lib.types.bool;
default = false;
description = ''
Create virtual test devices and remote for validating daemon flows.
This is only intended for CI testing and development purposes.
'';
};
};
};
default = { };
description = ''
Configurations for the fwupd daemon.
'';
};
uefiCapsuleSettings = lib.mkOption {
type = lib.types.submodule {
freeformType = format.type.nestedTypes.elemType;
};
default = { };
description = ''
UEFI capsule configurations for the fwupd daemon.
'';
};
};
};
imports = [
(lib.mkRenamedOptionModule
[ "services" "fwupd" "blacklistDevices" ]
[ "services" "fwupd" "daemonSettings" "DisabledDevices" ]
)
(lib.mkRenamedOptionModule
[ "services" "fwupd" "blacklistPlugins" ]
[ "services" "fwupd" "daemonSettings" "DisabledPlugins" ]
)
(lib.mkRenamedOptionModule
[ "services" "fwupd" "disabledDevices" ]
[ "services" "fwupd" "daemonSettings" "DisabledDevices" ]
)
(lib.mkRenamedOptionModule
[ "services" "fwupd" "disabledPlugins" ]
[ "services" "fwupd" "daemonSettings" "DisabledPlugins" ]
)
(lib.mkRemovedOptionModule [ "services" "fwupd" "enableTestRemote" ]
"This option was removed after being removed upstream. It only provided a method for testing fwupd functionality, and should not have been exposed for use outside of nix tests."
)
];
###### implementation
config = lib.mkIf cfg.enable {
# Disable test related plug-ins implicitly so that users do not have to care about them.
services.fwupd.daemonSettings = {
EspLocation = config.boot.loader.efi.efiSysMountPoint;
};
environment.systemPackages = [ cfg.package ];
# customEtc overrides some files from the package
environment.etc = originalEtc // customEtc // extraTrustedKeys // remotes;
services.dbus.packages = [ cfg.package ];
services.udev.packages = [ cfg.package ];
# required to update the firmware of disks
services.udisks2.enable = true;
systemd = {
packages = [ cfg.package ];
# fwupd-refresh expects a user that we do not create, so just run with DynamicUser
# instead and ensure we take ownership of /var/lib/fwupd
services.fwupd-refresh.serviceConfig = {
StateDirectory = "fwupd";
# Better for debugging, upstream sets stderr to null for some reason..
StandardError = "inherit";
};
timers.fwupd-refresh.wantedBy = [ "timers.target" ];
};
users.users.fwupd-refresh = {
isSystemUser = true;
group = "fwupd-refresh";
};
users.groups.fwupd-refresh = { };
security.polkit.enable = true;
};
meta = {
maintainers = pkgs.fwupd.meta.maintainers;
};
}

View File

@@ -0,0 +1,45 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.g810-led;
in
{
options = {
services.g810-led = {
enable = lib.mkEnableOption "g810-led, a Linux LED controller for some Logitech G Keyboards";
package = lib.mkPackageOption pkgs "g810-led" { };
profile = lib.mkOption {
type = lib.types.nullOr lib.types.lines;
default = null;
example = ''
# G810-LED Profile (turn all keys on)
# Set all keys on
a ffffff
# Commit changes
c
'';
description = ''
Keyboard profile to apply at boot time.
The upstream repository provides [example configurations](https://github.com/MatMoul/g810-led/tree/master/sample_profiles).
'';
};
};
};
config = lib.mkIf cfg.enable {
environment.etc."g810-led/profile".text = lib.mkIf (cfg.profile != null) cfg.profile;
services.udev.packages = [ cfg.package ];
};
meta.maintainers = with lib.maintainers; [ GaetanLepage ];
}

View File

@@ -0,0 +1,60 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.services.handheld-daemon;
in
{
options.services.handheld-daemon = {
enable = mkEnableOption "Handheld Daemon";
package = mkPackageOption pkgs "handheld-daemon" { };
ui = {
enable = mkEnableOption "Handheld Daemon UI";
package = mkPackageOption pkgs "handheld-daemon-ui" { };
};
user = mkOption {
type = types.str;
description = ''
The user to run Handheld Daemon with.
'';
};
};
config = mkIf cfg.enable {
services.handheld-daemon.ui.enable = mkDefault true;
environment.systemPackages = [
cfg.package
]
++ lib.optional cfg.ui.enable cfg.ui.package;
services.udev.packages = [ cfg.package ];
systemd.packages = [ cfg.package ];
systemd.services.handheld-daemon = {
description = "Handheld Daemon";
wantedBy = [ "multi-user.target" ];
restartIfChanged = true;
path = mkIf cfg.ui.enable [
cfg.ui.package
pkgs.lsof
];
serviceConfig = {
ExecStart = "${lib.getExe cfg.package} --user ${cfg.user}";
Nice = "-12";
Restart = "on-failure";
RestartSec = "10";
};
};
};
meta.maintainers = [ maintainers.appsforartists ];
}

View File

@@ -0,0 +1,203 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.hddfancontrol;
in
{
meta.maintainers = with lib.maintainers; [ philipwilk ];
imports = [
(lib.mkRemovedOptionModule [
"services"
"hddfancontrol"
"smartctl"
] "Smartctl is now automatically used when necessary, which makes this option redundant")
(lib.mkRemovedOptionModule [
"services"
"hddfancontrol"
"disks"
] "Disks should now be specified per hddfancontrol instance in its attrset")
(lib.mkRemovedOptionModule [
"services"
"hddfancontrol"
"pwmPaths"
] "Pwm Paths should now be specified per hddfancontrol instance in its attrset")
(lib.mkRemovedOptionModule [
"services"
"hddfancontrol"
"logVerbosity"
] "Log Verbosity should now be specified per hddfancontrol instance in its attrset")
(lib.mkRemovedOptionModule [
"services"
"hddfancontrol"
"extraArgs"
] "Extra Args should now be specified per hddfancontrol instance in its attrset")
];
options = {
services.hddfancontrol.enable = lib.mkEnableOption "hddfancontrol daemon";
services.hddfancontrol.package = lib.mkPackageOption pkgs "hddfancontrol" { };
services.hddfancontrol.settings = lib.mkOption {
type = lib.types.attrsWith {
placeholder = "drive-bay-name";
elemType = (
lib.types.submodule (
{ ... }:
{
options = {
disks = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
Drive(s) to get temperature from
Can also use command substitution to automatically grab all matching drives; such as all scsi (sas) drives
'';
example = [
"/dev/sda"
"`find /dev/disk/by-id -name \"scsi*\" -and -not -name \"*-part*\" -printf \"%p \"`"
];
};
pwmPaths = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
PWM filepath(s) to control fan speed (under /sys), followed by initial and fan-stop PWM values
Can also use command substitution to ensure the correct hwmonX is selected on every boot
'';
example = [
"/sys/class/hwmon/hwmon2/pwm1:30:10"
"`echo /sys/devices/platform/nct6775.656/hwmon/hwmon[[:print:]]`/pwm4:80:20"
];
};
logVerbosity = lib.mkOption {
type = lib.types.enum [
"TRACE"
"DEBUG"
"INFO"
"WARN"
"ERROR"
];
default = "INFO";
description = ''
Verbosity of the log level
'';
};
extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
Extra commandline arguments for hddfancontrol
'';
example = [
"--min-fan-speed-prct=10"
"--interval=1min"
];
};
};
}
)
);
};
default = { };
description = ''
Parameter-sets for each instance of hddfancontrol.
'';
example = lib.literalExpression ''
{
harddrives = {
disks = [
"/dev/sda"
"/dev/sdb"
"/dev/sdc"
];
pwmPaths = [
"/sys/class/hwmon/hwmon1/pwm1:25:10"
];
logVerbosity = "DEBUG";
};
ssddrives = {
disks = [
"/dev/sdd"
"/dev/sde"
"/dev/sdf"
];
pwmPaths = [
"/sys/class/hwmon/hwmon1/pwm2:25:10"
];
extraArgs = [
"--interval=30s"
];
};
}
'';
};
};
config = lib.mkIf cfg.enable (
let
args =
cnf:
lib.concatLists [
[ "-d" ]
cnf.disks
[ "-p" ]
cnf.pwmPaths
cnf.extraArgs
];
createService = cnf: {
description = "HDD fan control";
documentation = [ "man:hddfancontrol(1)" ];
after = [ "hddtemp.service" ];
wants = [ "hddtemp.service" ];
script =
let
argString = lib.strings.concatStringsSep " " (args cnf);
in
"${lib.getExe cfg.package} -v ${cnf.logVerbosity} daemon ${argString}";
serviceConfig = {
CPUSchedulingPolicy = "rr";
CPUSchedulingPriority = 49;
ProtectSystem = "strict";
PrivateTmp = true;
ProtectHome = true;
SystemCallArchitectures = "native";
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
};
wantedBy = [ "multi-user.target" ];
};
services = lib.attrsets.mergeAttrsList [
(lib.attrsets.mapAttrs' (
name: cnf: lib.nameValuePair "hddfancontrol-${name}" (createService cnf)
) cfg.settings)
{
"hddfancontrol".enable = false;
}
];
in
{
systemd.packages = [ cfg.package ];
hardware.sensor.hddtemp = {
enable = true;
drives = lib.lists.flatten (lib.attrsets.catAttrs "disks" (lib.attrsets.attrValues cfg.settings));
};
systemd.services = services;
}
);
}

View File

@@ -0,0 +1,39 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.illum;
in
{
options = {
services.illum = {
enable = lib.mkOption {
default = false;
type = lib.types.bool;
description = ''
Enable illum, a daemon for controlling screen brightness with brightness buttons.
'';
};
};
};
config = lib.mkIf cfg.enable {
systemd.services.illum = {
description = "Backlight Adjustment Service";
wantedBy = [ "multi-user.target" ];
serviceConfig.ExecStart = "${pkgs.illum}/bin/illum-d";
serviceConfig.Restart = "on-failure";
};
};
}

View File

@@ -0,0 +1,37 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.inputplumber;
in
{
options.services.inputplumber = {
enable = lib.mkEnableOption "InputPlumber";
package = lib.mkPackageOption pkgs "inputplumber" { };
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
systemd.services.inputplumber = {
description = "InputPlumber Service";
wantedBy = [ "multi-user.target" ];
environment = {
XDG_DATA_DIRS = "/run/current-system/sw/share";
};
restartIfChanged = true;
serviceConfig = {
ExecStart = "${lib.getExe cfg.package}";
Restart = "on-failure";
RestartSec = "5";
};
};
};
meta.maintainers = with lib.maintainers; [ shadowapex ];
}

View File

@@ -0,0 +1,72 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.interception-tools;
in
{
options.services.interception-tools = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to enable the interception tools service.";
};
plugins = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = [ pkgs.interception-tools-plugins.caps2esc ];
defaultText = lib.literalExpression "[ pkgs.interception-tools-plugins.caps2esc ]";
description = ''
A list of interception tools plugins that will be made available to use
inside the udevmon configuration.
'';
};
udevmonConfig = lib.mkOption {
type = lib.types.either lib.types.str lib.types.path;
default = ''
- JOB: "intercept -g $DEVNODE | caps2esc | uinput -d $DEVNODE"
DEVICE:
EVENTS:
EV_KEY: [KEY_CAPSLOCK, KEY_ESC]
'';
example = ''
- JOB: "intercept -g $DEVNODE | y2z | x2y | uinput -d $DEVNODE"
DEVICE:
EVENTS:
EV_KEY: [KEY_X, KEY_Y]
'';
description = ''
String of udevmon YAML configuration, or path to a udevmon YAML
configuration file.
'';
};
};
config = lib.mkIf cfg.enable {
systemd.services.interception-tools = {
description = "Interception tools";
path = [
pkgs.bash
pkgs.interception-tools
]
++ cfg.plugins;
serviceConfig = {
ExecStart = ''
${pkgs.interception-tools}/bin/udevmon -c \
${
if builtins.typeOf cfg.udevmonConfig == "path" then
cfg.udevmonConfig
else
pkgs.writeText "udevmon.yaml" cfg.udevmonConfig
}
'';
Nice = -20;
};
wantedBy = [ "multi-user.target" ];
};
};
}

View File

@@ -0,0 +1,63 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.iptsd;
format = pkgs.formats.ini { };
configFile = format.generate "iptsd.conf" cfg.config;
in
{
options.services.iptsd = {
enable = lib.mkEnableOption "the userspace daemon for Intel Precise Touch & Stylus";
config = lib.mkOption {
default = { };
description = ''
Configuration for IPTSD. See the
[reference configuration](https://github.com/linux-surface/iptsd/blob/master/etc/iptsd.conf)
for available options and defaults.
'';
type = lib.types.submodule {
freeformType = format.type;
options = {
Touchscreen = {
DisableOnPalm = lib.mkOption {
default = false;
description = "Ignore all touchscreen inputs if a palm was registered on the display.";
type = lib.types.bool;
};
DisableOnStylus = lib.mkOption {
default = false;
description = "Ignore all touchscreen inputs if a stylus is in proximity.";
type = lib.types.bool;
};
};
Stylus = {
Disable = lib.mkOption {
default = false;
description = "Disables the stylus. No stylus data will be processed.";
type = lib.types.bool;
};
};
};
};
};
};
config = lib.mkIf cfg.enable {
warnings = lib.optional (lib.hasAttr "Touch" cfg.config) ''
The option `services.iptsd.config.Touch` has been renamed to `services.iptsd.config.Touchscreen`.
'';
systemd.packages = [ pkgs.iptsd ];
environment.etc."iptsd.conf".source = configFile;
systemd.services."iptsd@".restartTriggers = [ configFile ];
services.udev.packages = [ pkgs.iptsd ];
};
meta.maintainers = with lib.maintainers; [ dotlambda ];
}

View File

@@ -0,0 +1,32 @@
#
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.irqbalance;
in
{
options.services.irqbalance = {
enable = lib.mkEnableOption "irqbalance daemon";
package = lib.mkPackageOption pkgs "irqbalance" { };
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
systemd.services.irqbalance.wantedBy = [ "multi-user.target" ];
systemd.packages = [ cfg.package ];
};
}

View File

@@ -0,0 +1,27 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.joycond;
in
{
options.services.joycond = {
enable = lib.mkEnableOption "support for Nintendo Pro Controllers and Joycons";
package = lib.mkPackageOption pkgs "joycond" { };
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
services.udev.packages = [ cfg.package ];
systemd.packages = [ cfg.package ];
# Workaround for https://github.com/NixOS/nixpkgs/issues/81138
systemd.services.joycond.wantedBy = [ "multi-user.target" ];
};
}

View File

@@ -0,0 +1,210 @@
{
config,
lib,
pkgs,
utils,
...
}:
let
cfg = config.services.kanata;
upstreamDoc = "See [the upstream documentation](https://github.com/jtroo/kanata/blob/main/docs/config.adoc) and [example config files](https://github.com/jtroo/kanata/tree/main/cfg_samples) for more information.";
keyboard =
{ name, config, ... }:
{
options = {
devices = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [ "/dev/input/by-id/usb-0000_0000-event-kbd" ];
description = ''
Paths to keyboard devices.
An empty list, the default value, lets kanata detect which
input devices are keyboards and intercept them all.
'';
};
config = lib.mkOption {
type = lib.types.lines;
example = ''
(defsrc
caps)
(deflayermap (default-layer)
;; tap caps lock as caps lock, hold caps lock as left control
caps (tap-hold 100 100 caps lctl))
'';
description = ''
Configuration other than `defcfg`.
${upstreamDoc}
'';
};
extraDefCfg = lib.mkOption {
type = lib.types.lines;
default = "";
example = "danger-enable-cmd yes";
description = ''
Configuration of `defcfg` other than `linux-dev` (generated
from the devices option) and
`linux-continue-if-no-devs-found` (hardcoded to be yes).
${upstreamDoc}
'';
};
configFile = lib.mkOption {
type = lib.types.path;
default = mkConfig name config;
defaultText = "A config file generated by values from other kanata module options.";
description = ''
The config file.
By default, it is generated by values from other kanata
module options.
You can also set it to your own full config file which
overrides all other kanata module options. ${upstreamDoc}
'';
};
extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Extra command line arguments passed to kanata.";
};
port = lib.mkOption {
type = lib.types.nullOr lib.types.port;
default = null;
example = 6666;
description = ''
Port to run the TCP server on. `null` will not run the server.
'';
};
};
};
mkName = name: "kanata-${name}";
mkDevices =
devices:
let
devicesString = lib.pipe devices [
(map (device: "\"" + device + "\""))
(lib.concatStringsSep " ")
];
in
lib.optionalString ((lib.length devices) > 0) "linux-dev (${devicesString})";
mkConfig =
name: keyboard:
pkgs.writeTextFile {
name = "${mkName name}-config.kdb";
text = ''
(defcfg
${keyboard.extraDefCfg}
${mkDevices keyboard.devices}
linux-continue-if-no-devs-found yes)
${keyboard.config}
'';
# Only the config file generated by this module is checked. A
# user-provided one is not checked because it may not be available
# at build time. I think this is a good balance between module
# complexity and functionality.
checkPhase = ''
${lib.getExe cfg.package} --cfg "$target" --check --debug
'';
};
mkService =
name: keyboard:
lib.nameValuePair (mkName name) {
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "notify";
ExecStart = ''
${lib.getExe cfg.package} \
--cfg ${keyboard.configFile} \
--symlink-path ''${RUNTIME_DIRECTORY}/${name} \
${lib.optionalString (keyboard.port != null) "--port ${toString keyboard.port}"} \
${utils.escapeSystemdExecArgs keyboard.extraArgs}
'';
DynamicUser = true;
RuntimeDirectory = mkName name;
SupplementaryGroups = with config.users.groups; [
input.name
uinput.name
];
# hardening
DeviceAllow = [
"/dev/uinput rw"
"char-input r"
];
CapabilityBoundingSet = [ "" ];
DevicePolicy = "closed";
IPAddressAllow = lib.optional (keyboard.port != null) "localhost";
IPAddressDeny = [ "any" ];
LockPersonality = true;
MemoryDenyWriteExecute = true;
PrivateNetwork = keyboard.port == null;
PrivateUsers = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
RestrictAddressFamilies = [ "AF_UNIX" ] ++ lib.optional (keyboard.port != null) "AF_INET";
RestrictNamespaces = true;
RestrictRealtime = true;
SystemCallArchitectures = [ "native" ];
SystemCallFilter = [
"@system-service"
"~@privileged"
"~@resources"
];
UMask = "0077";
};
};
in
{
options.services.kanata = {
enable = lib.mkEnableOption "kanata, a tool to improve keyboard comfort and usability with advanced customization";
package = lib.mkPackageOption pkgs "kanata" {
example = [ "kanata-with-cmd" ];
extraDescription = ''
::: {.note}
If {option}`danger-enable-cmd` is enabled in any of the keyboards, the
`kanata-with-cmd` package should be used.
:::
'';
};
keyboards = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule keyboard);
default = { };
description = "Keyboard configurations.";
};
};
config = lib.mkIf cfg.enable {
warnings =
let
keyboardsWithEmptyDevices = lib.filterAttrs (name: keyboard: keyboard.devices == [ ]) cfg.keyboards;
existEmptyDevices = lib.length (lib.attrNames keyboardsWithEmptyDevices) > 0;
moreThanOneKeyboard = lib.length (lib.attrNames cfg.keyboards) > 1;
in
lib.optional (existEmptyDevices && moreThanOneKeyboard)
"One device can only be intercepted by one kanata instance. Setting services.kanata.keyboards.${lib.head (lib.attrNames keyboardsWithEmptyDevices)}.devices = [ ] and using more than one services.kanata.keyboards may cause a race condition.";
hardware.uinput.enable = true;
systemd.services = lib.mapAttrs' mkService cfg.keyboards;
};
meta.maintainers = with lib.maintainers; [ linj ];
}

View File

@@ -0,0 +1,191 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.keyd;
keyboardOptions =
{ ... }:
{
options = {
ids = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ "*" ];
example = [
"*"
"-0123:0456"
];
description = ''
Device identifiers, as shown by {manpage}`keyd(1)`.
'';
};
settings = lib.mkOption {
type = (pkgs.formats.ini { }).type;
default = { };
example = {
main = {
capslock = "overload(control, esc)";
rightalt = "layer(rightalt)";
};
rightalt = {
j = "down";
k = "up";
h = "left";
l = "right";
};
};
description = ''
Configuration, except `ids` section, that is written to {file}`/etc/keyd/<keyboard>.conf`.
Appropriate names can be used to write non-alpha keys, for example "equal" instead of "=" sign (see <https://github.com/NixOS/nixpkgs/issues/236622>).
See <https://github.com/rvaiya/keyd> how to configure.
'';
};
extraConfig = lib.mkOption {
type = lib.types.lines;
default = "";
example = ''
[control+shift]
h = left
'';
description = ''
Extra configuration that is appended to the end of the file.
**Do not** write `ids` section here, use a separate option for it.
You can use this option to define compound layers that must always be defined after the layer they are comprised.
'';
};
};
};
in
{
imports = [
(lib.mkRemovedOptionModule [ "services" "keyd" "ids" ]
''Use keyboards.<filename>.ids instead. If you don't need a multi-file configuration, just add keyboards.default before the ids. See https://github.com/NixOS/nixpkgs/pull/243271.''
)
(lib.mkRemovedOptionModule [ "services" "keyd" "settings" ]
''Use keyboards.<filename>.settings instead. If you don't need a multi-file configuration, just add keyboards.default before the settings. See https://github.com/NixOS/nixpkgs/pull/243271.''
)
];
options.services.keyd = {
enable = lib.mkEnableOption "keyd, a key remapping daemon";
keyboards = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule keyboardOptions);
default = { };
example = lib.literalExpression ''
{
default = {
ids = [ "*" ];
settings = {
main = {
capslock = "overload(control, esc)";
};
};
};
externalKeyboard = {
ids = [ "1ea7:0907" ];
settings = {
main = {
esc = capslock;
};
};
};
}
'';
description = ''
Configuration for one or more device IDs. Corresponding files in the /etc/keyd/ directory are created according to the name of the keys (like `default` or `externalKeyboard`).
'';
};
};
config = lib.mkIf cfg.enable {
# Creates separate files in the `/etc/keyd/` directory for each key in the dictionary
environment.etc = lib.mapAttrs' (
name: options:
lib.nameValuePair "keyd/${name}.conf" {
text = ''
[ids]
${lib.concatStringsSep "\n" options.ids}
${lib.generators.toINI { } options.settings}
${options.extraConfig}
'';
}
) cfg.keyboards;
hardware.uinput.enable = lib.mkDefault true;
systemd.services.keyd = {
description = "Keyd remapping daemon";
documentation = [ "man:keyd(1)" ];
wantedBy = [ "multi-user.target" ];
restartTriggers = lib.mapAttrsToList (
name: options: config.environment.etc."keyd/${name}.conf".source
) cfg.keyboards;
# this is configurable in 2.4.2, later versions seem to remove this option.
# post-2.4.2 may need to set makeFlags in the derivation:
#
# makeFlags = [ "SOCKET_PATH/run/keyd/keyd.socket" ];
environment.KEYD_SOCKET = "/run/keyd/keyd.sock";
serviceConfig = {
ExecStart = "${pkgs.keyd}/bin/keyd";
Restart = "always";
# TODO investigate why it doesn't work propeprly with DynamicUser
# See issue: https://github.com/NixOS/nixpkgs/issues/226346
# DynamicUser = true;
SupplementaryGroups = [
config.users.groups.input.name
config.users.groups.uinput.name
];
RuntimeDirectory = "keyd";
# Hardening
CapabilityBoundingSet = [ "CAP_SYS_NICE" ];
DeviceAllow = [
"char-input rw"
"/dev/uinput rw"
];
ProtectClock = true;
PrivateNetwork = true;
ProtectHome = true;
ProtectHostname = true;
PrivateUsers = false;
PrivateMounts = true;
PrivateTmp = true;
RestrictNamespaces = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
MemoryDenyWriteExecute = true;
RestrictRealtime = true;
LockPersonality = true;
ProtectProc = "invisible";
SystemCallFilter = [
"nice"
"@system-service"
"~@privileged"
];
RestrictAddressFamilies = [ "AF_UNIX" ];
RestrictSUIDSGID = true;
IPAddressDeny = [ "any" ];
NoNewPrivileges = true;
ProtectSystem = "strict";
ProcSubset = "pid";
UMask = "0077";
};
};
};
}

View File

@@ -0,0 +1,262 @@
{
config,
lib,
pkgs,
utils,
...
}:
let
cfg = config.services.kmonad;
# Per-keyboard options:
keyboard =
{ name, ... }:
{
options = {
name = lib.mkOption {
type = lib.types.str;
default = name;
example = "laptop-internal";
description = "Keyboard name.";
};
device = lib.mkOption {
type = lib.types.path;
example = "/dev/input/by-id/some-dev";
description = "Path to the keyboard's device file.";
};
extraGroups = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
Extra permission groups to attach to the KMonad instance for
this keyboard.
Since KMonad runs as an unprivileged user, it may sometimes
need extra permissions in order to read the keyboard device
file. If your keyboard's device file isn't in the input
group, you'll need to list its group in this option.
'';
};
enableHardening = lib.mkOption {
type = lib.types.bool;
default = true;
example = false;
description = ''
Whether to enable systemd hardening.
::: {.note}
If KMonad is used to execute shell commands, hardening may make some of them fail.
:::
'';
};
defcfg = {
enable = lib.mkEnableOption ''
automatic generation of the defcfg block.
When this option is set to true, the config option for
this keyboard should not include a defcfg block
'';
compose = {
key = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = "ralt";
description = "The (optional) compose key to use.";
};
delay = lib.mkOption {
type = lib.types.ints.unsigned;
default = 5;
description = "The delay (in milliseconds) between compose key sequences.";
};
};
fallthrough = lib.mkEnableOption "re-emitting unhandled key events";
allowCommands = lib.mkEnableOption "keys to run shell commands";
};
config = lib.mkOption {
type = lib.types.lines;
description = "Keyboard configuration.";
};
};
};
mkName = name: "kmonad-" + name;
# Create a complete KMonad configuration file:
mkCfg =
keyboard:
let
defcfg = ''
(defcfg
input (device-file "${keyboard.device}")
output (uinput-sink "${mkName keyboard.name}")
${lib.optionalString (keyboard.defcfg.compose.key != null) ''
cmp-seq ${keyboard.defcfg.compose.key}
cmp-seq-delay ${toString keyboard.defcfg.compose.delay}
''}
fallthrough ${lib.boolToString keyboard.defcfg.fallthrough}
allow-cmd ${lib.boolToString keyboard.defcfg.allowCommands}
)
'';
in
pkgs.writeTextFile {
name = "${mkName keyboard.name}.kbd";
text = lib.optionalString keyboard.defcfg.enable (defcfg + "\n") + keyboard.config;
checkPhase = "${lib.getExe cfg.package} -d $out";
};
# Build a systemd path config that starts the service below when a
# keyboard device appears:
mkPath =
keyboard:
let
name = mkName keyboard.name;
in
lib.nameValuePair name {
description = "KMonad trigger for ${keyboard.device}";
wantedBy = [ "paths.target" ];
pathConfig = {
Unit = "${name}.service";
PathExists = keyboard.device;
};
};
# Build a systemd service that starts KMonad:
mkService =
keyboard:
lib.nameValuePair (mkName keyboard.name) {
description = "KMonad for ${keyboard.device}";
unitConfig = {
# Control rate limiting.
# Stop the restart logic if we restart more than
# StartLimitBurst times in a period of StartLimitIntervalSec.
StartLimitIntervalSec = 2;
StartLimitBurst = 5;
};
serviceConfig = {
ExecStart = ''
${lib.getExe cfg.package} ${mkCfg keyboard} \
${utils.escapeSystemdExecArgs cfg.extraArgs}
'';
Restart = "always";
# Restart at increasing intervals from 2s to 1m
RestartSec = 2;
RestartSteps = 30;
RestartMaxDelaySec = "1min";
Nice = -20;
DynamicUser = true;
User = "kmonad";
Group = "kmonad";
SupplementaryGroups = [
# These ensure that our dynamic user has access to the device node
config.users.groups.input.name
config.users.groups.uinput.name
]
++ keyboard.extraGroups;
}
// lib.optionalAttrs keyboard.enableHardening {
DeviceAllow = [
"/dev/uinput w"
"char-input r"
];
CapabilityBoundingSet = [ "" ];
DevicePolicy = "closed";
IPAddressDeny = [ "any" ];
LockPersonality = true;
MemoryDenyWriteExecute = true;
PrivateNetwork = true;
PrivateUsers = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
RestrictAddressFamilies = [ "none" ];
RestrictNamespaces = true;
RestrictRealtime = true;
SystemCallArchitectures = [ "native" ];
SystemCallErrorNumber = "EPERM";
SystemCallFilter = [
"@system-service"
"~@privileged"
"~@resources"
];
UMask = "0077";
};
# make sure the new config is used after nixos-rebuild switch
# stopIfChanged controls[0] how a service is "restarted" during
# nixos-rebuild switch. By default, stopIfChanged is true, which stops
# the old service and then starts the new service after config updates.
# Since we use path-based activation[1] here, the service unit will
# immediately[2] be started by the path unit. Probably that start is
# before config updates, which causes the service unit to use the old
# config after nixos-rebuild switch. Setting stopIfChanged to false works
# around this issue by restarting the service after config updates.
# [0]: https://nixos.org/manual/nixos/unstable/#sec-switching-systems
# [1]: man 7 daemon
# [2]: man 5 systemd.path
stopIfChanged = false;
};
in
{
options.services.kmonad = {
enable = lib.mkEnableOption "KMonad: an advanced keyboard manager";
package = lib.mkPackageOption pkgs "KMonad" { default = "kmonad"; };
keyboards = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule keyboard);
default = { };
description = "Keyboard configuration.";
};
extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [
"--log-level"
"debug"
];
description = "Extra arguments to pass to KMonad.";
};
};
config = lib.mkIf cfg.enable {
hardware.uinput.enable = true;
services.udev.extraRules =
let
mkRule = name: ''
ACTION=="add", KERNEL=="event*", SUBSYSTEM=="input", ATTRS{name}=="${name}", ATTRS{id/product}=="5679", ATTRS{id/vendor}=="1235", SYMLINK+="input/by-id/${name}"
'';
in
lib.foldlAttrs (
rules: _: keyboard:
rules + "\n" + mkRule (mkName keyboard.name)
) "" cfg.keyboards;
systemd = {
paths = lib.mapAttrs' (_: mkPath) cfg.keyboards;
services = lib.mapAttrs' (_: mkService) cfg.keyboards;
};
};
meta = {
maintainers = with lib.maintainers; [
linj
rvdp
];
};
}

View File

@@ -0,0 +1,67 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.lact;
configFormat = pkgs.formats.yaml { };
configFile = configFormat.generate "lact-config.yaml" cfg.settings;
in
{
meta.maintainers = [ lib.maintainers.johnrtitor ];
options.services.lact = {
enable = lib.mkEnableOption null // {
description = ''
Whether to enable LACT, a tool for monitoring, configuring and overclocking GPUs.
::: {.note}
If you are on an AMD GPU, it is recommended to enable overdrive mode by using
`hardware.amdgpu.overdrive.enable = true;` in your configuration.
See [LACT wiki](https://github.com/ilya-zlobintsev/LACT/wiki/Overclocking-(AMD)) for more information.
:::
'';
};
package = lib.mkPackageOption pkgs "lact" { };
settings = lib.mkOption {
default = { };
type = lib.types.submodule {
freeformType = configFormat.type;
};
description = ''
Settings for LACT.
The easiest method of acquiring the settings is to delete
{file}`/etc/lact/config.yaml`, enter your settings and look
at the file.
::: {.note}
When `settings` is populated, the config file will be a symbolic link
and thus LACT daemon will not be able to modify it through the GUI.
:::
'';
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
systemd.packages = [ cfg.package ];
environment.etc."lact/config.yaml" = lib.mkIf (cfg.settings != { }) {
source = configFile;
};
systemd.services.lactd = {
description = "LACT GPU Control Daemon";
wantedBy = [ "multi-user.target" ];
# Restart when the config file changes.
restartTriggers = lib.mkIf (cfg.settings != { }) [ configFile ];
};
};
}

View File

@@ -0,0 +1,180 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.hardware.lcd;
pkg = lib.getBin pkgs.lcdproc;
serverCfg = pkgs.writeText "lcdd.conf" ''
[server]
DriverPath=${pkg}/lib/lcdproc/
ReportToSyslog=false
Bind=${cfg.serverHost}
Port=${toString cfg.serverPort}
${cfg.server.extraConfig}
'';
clientCfg = pkgs.writeText "lcdproc.conf" ''
[lcdproc]
Server=${cfg.serverHost}
Port=${toString cfg.serverPort}
ReportToSyslog=false
${cfg.client.extraConfig}
'';
serviceCfg = {
DynamicUser = true;
Restart = "on-failure";
Slice = "lcd.slice";
};
in
with lib;
{
meta.maintainers = with maintainers; [ peterhoeg ];
options = with types; {
services.hardware.lcd = {
serverHost = mkOption {
type = str;
default = "localhost";
description = "Host on which LCDd is listening.";
};
serverPort = mkOption {
type = int;
default = 13666;
description = "Port on which LCDd is listening.";
};
server = {
enable = mkOption {
type = bool;
default = false;
description = "Enable the LCD panel server (LCDd)";
};
openPorts = mkOption {
type = bool;
default = false;
description = "Open the ports in the firewall";
};
usbPermissions = mkOption {
type = bool;
default = false;
description = ''
Set group-write permissions on a USB device.
A USB connected LCD panel will most likely require having its
permissions modified for lcdd to write to it. Enabling this option
sets group-write permissions on the device identified by
{option}`services.hardware.lcd.usbVid` and
{option}`services.hardware.lcd.usbPid`. In order to find the
values, you can run the {command}`lsusb` command. Example
output:
```
Bus 005 Device 002: ID 0403:c630 Future Technology Devices International, Ltd lcd2usb interface
```
In this case the vendor id is 0403 and the product id is c630.
'';
};
usbVid = mkOption {
type = str;
default = "";
description = "The vendor ID of the USB device to claim.";
};
usbPid = mkOption {
type = str;
default = "";
description = "The product ID of the USB device to claim.";
};
usbGroup = mkOption {
type = str;
default = "dialout";
description = "The group to use for settings permissions. This group must exist or you will have to create it.";
};
extraConfig = mkOption {
type = lines;
default = "";
description = "Additional configuration added verbatim to the server config.";
};
};
client = {
enable = mkOption {
type = bool;
default = false;
description = "Enable the LCD panel client (LCDproc)";
};
extraConfig = mkOption {
type = lines;
default = "";
description = "Additional configuration added verbatim to the client config.";
};
restartForever = mkOption {
type = bool;
default = true;
description = "Try restarting the client forever.";
};
};
};
};
config = mkIf (cfg.server.enable || cfg.client.enable) {
networking.firewall.allowedTCPPorts = mkIf (cfg.server.enable && cfg.server.openPorts) [
cfg.serverPort
];
services.udev.extraRules = mkIf (cfg.server.enable && cfg.server.usbPermissions) ''
ACTION=="add", SUBSYSTEMS=="usb", ATTRS{idVendor}=="${cfg.server.usbVid}", ATTRS{idProduct}=="${cfg.server.usbPid}", MODE="660", GROUP="${cfg.server.usbGroup}"
'';
systemd.services = {
lcdd = mkIf cfg.server.enable {
description = "LCDproc - server";
wantedBy = [ "lcd.target" ];
serviceConfig = serviceCfg // {
ExecStart = "${pkg}/bin/LCDd -f -c ${serverCfg}";
SupplementaryGroups = cfg.server.usbGroup;
};
};
lcdproc = mkIf cfg.client.enable {
description = "LCDproc - client";
after = [ "lcdd.service" ];
wantedBy = [ "lcd.target" ];
# Allow restarting for eternity
startLimitIntervalSec = lib.mkIf cfg.client.restartForever 0;
serviceConfig = serviceCfg // {
ExecStart = "${pkg}/bin/lcdproc -f -c ${clientCfg}";
# If the server is being restarted at the same time, the client will
# fail as it cannot connect, so space it out a bit.
RestartSec = "5";
};
};
};
systemd.targets.lcd = {
description = "LCD client/server";
after = [
"lcdd.service"
"lcdproc.service"
];
wantedBy = [ "multi-user.target" ];
};
};
}

View File

@@ -0,0 +1,469 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.libinput;
xorgBool = v: if v then "on" else "off";
mkConfigForDevice = deviceType: {
dev = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "/dev/input/event0";
description = ''
Path for ${deviceType} device. Set to `null` to apply to any
auto-detected ${deviceType}.
'';
};
accelProfile = lib.mkOption {
type = lib.types.enum [
"flat"
"adaptive"
"custom"
];
default = "adaptive";
example = "flat";
description = ''
Sets the pointer acceleration profile to the given profile.
Permitted values are `adaptive`, `flat`, `custom`.
Not all devices support this option or all profiles.
If a profile is unsupported, the default profile for this is used.
`flat`: Pointer motion is accelerated by a constant
(device-specific) factor, depending on the current speed.
`adaptive`: Pointer acceleration depends on the input speed.
This is the default profile for most devices.
`custom`: Allows the user to define a custom acceleration function.
To define custom functions use the accelPoints<Fallback/Motion/Scroll>
and accelStep<Fallback/Motion/Scroll> options.
'';
};
accelSpeed = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "-0.5";
description = ''
Cursor acceleration (how fast speed increases from minSpeed to maxSpeed).
This only applies to the flat or adaptive profile.
'';
};
accelPointsFallback = lib.mkOption {
type = lib.types.nullOr (lib.types.listOf lib.types.number);
default = null;
example = [
0.0
1.0
2.4
2.5
];
description = ''
Sets the points of the fallback acceleration function. The value must be a list of
floating point non-negative numbers. This only applies to the custom profile.
'';
};
accelPointsMotion = lib.mkOption {
type = lib.types.nullOr (lib.types.listOf lib.types.number);
default = null;
example = [
0.0
1.0
2.4
2.5
];
description = ''
Sets the points of the (pointer) motion acceleration function. The value must be a
list of floating point non-negative numbers. This only applies to the custom profile.
'';
};
accelPointsScroll = lib.mkOption {
type = lib.types.nullOr (lib.types.listOf lib.types.number);
default = null;
example = [
0.0
1.0
2.4
2.5
];
description = ''
Sets the points of the scroll acceleration function. The value must be a list of
floating point non-negative numbers. This only applies to the custom profile.
'';
};
accelStepFallback = lib.mkOption {
type = lib.types.nullOr lib.types.number;
default = null;
example = 0.1;
description = ''
Sets the step between the points of the fallback acceleration function. When a step of
0.0 is provided, libinput's Fallback acceleration function is used. This only applies
to the custom profile.
'';
};
accelStepMotion = lib.mkOption {
type = lib.types.nullOr lib.types.number;
default = null;
example = 0.1;
description = ''
Sets the step between the points of the (pointer) motion acceleration function. When a
step of 0.0 is provided, libinput's Fallback acceleration function is used. This only
applies to the custom profile.
'';
};
accelStepScroll = lib.mkOption {
type = lib.types.nullOr lib.types.number;
default = null;
example = 0.1;
description = ''
Sets the step between the points of the scroll acceleration function. When a step of
0.0 is provided, libinput's Fallback acceleration function is used. This only applies
to the custom profile.
'';
};
buttonMapping = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "1 6 3 4 5 0 7";
description = ''
Sets the logical button mapping for this device, see {manpage}`XSetPointerMapping(3)`. The string must
be a space-separated list of button mappings in the order of the logical buttons on the
device, starting with button 1. The default mapping is "1 2 3 ... 32". A mapping of 0 deac
tivates the button. Multiple buttons can have the same mapping. Invalid mapping strings are
discarded and the default mapping is used for all buttons. Buttons not specified in the
user's mapping use the default mapping. See section BUTTON MAPPING for more details.
'';
};
calibrationMatrix = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "0.5 0 0 0 0.8 0.1 0 0 1";
description = ''
A string of 9 space-separated floating point numbers. Sets the calibration matrix to the
3x3 matrix where the first row is (abc), the second row is (def) and the third row is (ghi).
'';
};
clickMethod = lib.mkOption {
type = lib.types.nullOr (
lib.types.enum [
"none"
"buttonareas"
"clickfinger"
]
);
default = null;
example = "buttonareas";
description = ''
Enables a click method. Permitted values are `none`,
`buttonareas`, `clickfinger`.
Not all devices support all methods, if an option is unsupported,
the default click method for this device is used.
'';
};
leftHanded = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Enables left-handed button orientation, i.e. swapping left and right buttons.";
};
middleEmulation = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Enables middle button emulation. When enabled, pressing the left and right buttons
simultaneously produces a middle mouse button click.
'';
};
naturalScrolling = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Enables or disables natural scrolling behavior.";
};
scrollButton = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
example = 1;
description = ''
Designates a button as scroll button. If the ScrollMethod is button and the button is logically
held down, x/y axis movement is converted into scroll events.
'';
};
scrollMethod = lib.mkOption {
type = lib.types.enum [
"twofinger"
"edge"
"button"
"none"
];
default = "twofinger";
example = "edge";
description = ''
Specify the scrolling method: `twofinger`, `edge`,
`button`, or `none`
'';
};
horizontalScrolling = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Enables or disables horizontal scrolling. When disabled, this driver will discard any
horizontal scroll events from libinput. This does not disable horizontal scroll events
from libinput; it merely discards the horizontal axis from any scroll events.
'';
};
sendEventsMode = lib.mkOption {
type = lib.types.enum [
"disabled"
"enabled"
"disabled-on-external-mouse"
];
default = "enabled";
example = "disabled";
description = ''
Sets the send events mode to `disabled`, `enabled`,
or `disabled-on-external-mouse`
'';
};
tapping = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Enables or disables tap-to-click behavior.
'';
};
tappingButtonMap = lib.mkOption {
type = lib.types.nullOr (
lib.types.enum [
"lrm"
"lmr"
]
);
default = null;
description = ''
Set the button mapping for 1/2/3-finger taps to left/right/middle or left/middle/right, respectively.
'';
};
tappingDragLock = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Enables or disables drag lock during tapping behavior. When enabled, a finger up during tap-
and-drag will not immediately release the button. If the finger is set down again within the
timeout, the dragging process continues.
'';
};
transformationMatrix = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "0.5 0 0 0 0.8 0.1 0 0 1";
description = ''
A string of 9 space-separated floating point numbers. Sets the transformation matrix to
the 3x3 matrix where the first row is (abc), the second row is (def) and the third row is (ghi).
'';
};
disableWhileTyping = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Disable input method while typing.
'';
};
additionalOptions = lib.mkOption {
type = lib.types.lines;
default = "";
example = ''
Option "DragLockButtons" "L1 B1 L2 B2"
'';
description = ''
Additional options for libinput ${deviceType} driver. See
{manpage}`libinput(4)`
for available options.";
'';
};
};
mkX11ConfigForDevice = deviceType: matchIs: ''
Identifier "libinput ${deviceType} configuration"
MatchDriver "libinput"
MatchIs${matchIs} "${xorgBool true}"
${lib.optionalString (cfg.${deviceType}.dev != null) ''MatchDevicePath "${cfg.${deviceType}.dev}"''}
Option "AccelProfile" "${cfg.${deviceType}.accelProfile}"
${lib.optionalString (
cfg.${deviceType}.accelSpeed != null
) ''Option "AccelSpeed" "${cfg.${deviceType}.accelSpeed}"''}
${lib.optionalString (
cfg.${deviceType}.accelPointsFallback != null
) ''Option "AccelPointsFallback" "${toString cfg.${deviceType}.accelPointsFallback}"''}
${lib.optionalString (cfg.${deviceType}.accelPointsMotion != null)
''Option "AccelPointsMotion" "${toString cfg.${deviceType}.accelPointsMotion}"''
}
${lib.optionalString (cfg.${deviceType}.accelPointsScroll != null)
''Option "AccelPointsScroll" "${toString cfg.${deviceType}.accelPointsScroll}"''
}
${lib.optionalString (cfg.${deviceType}.accelStepFallback != null)
''Option "AccelStepFallback" "${toString cfg.${deviceType}.accelStepFallback}"''
}
${lib.optionalString (cfg.${deviceType}.accelStepMotion != null)
''Option "AccelStepMotion" "${toString cfg.${deviceType}.accelStepMotion}"''
}
${lib.optionalString (cfg.${deviceType}.accelStepScroll != null)
''Option "AccelStepScroll" "${toString cfg.${deviceType}.accelStepScroll}"''
}
${lib.optionalString (cfg.${deviceType}.buttonMapping != null)
''Option "ButtonMapping" "${cfg.${deviceType}.buttonMapping}"''
}
${lib.optionalString (cfg.${deviceType}.calibrationMatrix != null)
''Option "CalibrationMatrix" "${cfg.${deviceType}.calibrationMatrix}"''
}
${lib.optionalString (
cfg.${deviceType}.transformationMatrix != null
) ''Option "TransformationMatrix" "${cfg.${deviceType}.transformationMatrix}"''}
${lib.optionalString (
cfg.${deviceType}.clickMethod != null
) ''Option "ClickMethod" "${cfg.${deviceType}.clickMethod}"''}
Option "LeftHanded" "${xorgBool cfg.${deviceType}.leftHanded}"
Option "MiddleEmulation" "${xorgBool cfg.${deviceType}.middleEmulation}"
Option "NaturalScrolling" "${xorgBool cfg.${deviceType}.naturalScrolling}"
${lib.optionalString (cfg.${deviceType}.scrollButton != null)
''Option "ScrollButton" "${toString cfg.${deviceType}.scrollButton}"''
}
Option "ScrollMethod" "${cfg.${deviceType}.scrollMethod}"
Option "HorizontalScrolling" "${xorgBool cfg.${deviceType}.horizontalScrolling}"
Option "SendEventsMode" "${cfg.${deviceType}.sendEventsMode}"
Option "Tapping" "${xorgBool cfg.${deviceType}.tapping}"
${lib.optionalString (cfg.${deviceType}.tappingButtonMap != null)
''Option "TappingButtonMap" "${cfg.${deviceType}.tappingButtonMap}"''
}
Option "TappingDragLock" "${xorgBool cfg.${deviceType}.tappingDragLock}"
Option "DisableWhileTyping" "${xorgBool cfg.${deviceType}.disableWhileTyping}"
${cfg.${deviceType}.additionalOptions}
'';
in
{
imports =
(map
(
option:
lib.mkRenamedOptionModule
[
"services"
"xserver"
"libinput"
option
]
[
"services"
"libinput"
"touchpad"
option
]
)
[
"accelProfile"
"accelSpeed"
"buttonMapping"
"calibrationMatrix"
"clickMethod"
"leftHanded"
"middleEmulation"
"naturalScrolling"
"scrollButton"
"scrollMethod"
"horizontalScrolling"
"sendEventsMode"
"tapping"
"tappingButtonMap"
"tappingDragLock"
"transformationMatrix"
"disableWhileTyping"
"additionalOptions"
]
)
++ [
(lib.mkRenamedOptionModule
[ "services" "xserver" "libinput" "enable" ]
[ "services" "libinput" "enable" ]
)
(lib.mkRenamedOptionModule
[ "services" "xserver" "libinput" "mouse" ]
[ "services" "libinput" "mouse" ]
)
(lib.mkRenamedOptionModule
[ "services" "xserver" "libinput" "touchpad" ]
[ "services" "libinput" "touchpad" ]
)
];
options = {
services.libinput = {
enable = lib.mkEnableOption "libinput" // {
default = config.services.xserver.enable;
defaultText = lib.literalExpression "config.services.xserver.enable";
};
mouse = mkConfigForDevice "mouse";
touchpad = mkConfigForDevice "touchpad";
};
};
config = lib.mkIf cfg.enable {
services.xserver.modules = [ pkgs.xorg.xf86inputlibinput ];
environment.systemPackages = [ pkgs.xorg.xf86inputlibinput ];
environment.etc =
let
cfgPath = "X11/xorg.conf.d/40-libinput.conf";
in
{
${cfgPath} = {
source = pkgs.xorg.xf86inputlibinput.out + "/share/" + cfgPath;
};
};
services.udev.packages = [ pkgs.libinput.out ];
services.xserver.inputClassSections = [
(mkX11ConfigForDevice "mouse" "Pointer")
(mkX11ConfigForDevice "touchpad" "Touchpad")
];
assertions = [
# already present in synaptics.nix
/*
{
assertion = !config.services.xserver.synaptics.enable;
message = "Synaptics and libinput are incompatible, you cannot enable both (in services.xserver).";
}
*/
];
};
}

View File

@@ -0,0 +1,108 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.lirc;
in
{
###### interface
options = {
services.lirc = {
enable = lib.mkEnableOption "the LIRC daemon, to receive and send infrared signals";
options = lib.mkOption {
type = lib.types.lines;
example = ''
[lircd]
nodaemon = False
'';
description = "LIRC default options described in man:lircd(8) ({file}`lirc_options.conf`)";
};
configs = lib.mkOption {
type = lib.types.listOf lib.types.lines;
description = "Configurations for lircd to load, see man:lircd.conf(5) for details ({file}`lircd.conf`)";
};
extraArguments = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Extra arguments to lircd.";
};
};
};
###### implementation
config = lib.mkIf cfg.enable {
# Note: LIRC executables raises a warning, if lirc_options.conf does not exist
environment.etc."lirc/lirc_options.conf".text = cfg.options;
passthru.lirc.socket = "/run/lirc/lircd";
environment.systemPackages = [ pkgs.lirc ];
systemd.sockets.lircd = {
description = "LIRC daemon socket";
wantedBy = [ "sockets.target" ];
socketConfig = {
ListenStream = config.passthru.lirc.socket;
SocketUser = "lirc";
SocketMode = "0660";
};
};
systemd.services.lircd =
let
configFile = pkgs.writeText "lircd.conf" (builtins.concatStringsSep "\n" cfg.configs);
in
{
description = "LIRC daemon service";
after = [ "network.target" ];
unitConfig.Documentation = [ "man:lircd(8)" ];
serviceConfig = {
RuntimeDirectory = [
"lirc"
"lirc/lock"
];
# Service runtime directory and socket share same folder.
# Following hacks are necessary to get everything right:
# 1. prevent socket deletion during stop and restart
RuntimeDirectoryPreserve = true;
# 2. fix runtime folder owner-ship, happens when socket activation
# creates the folder
PermissionsStartOnly = true;
ExecStartPre = [
"${pkgs.coreutils}/bin/chown lirc /run/lirc/"
];
ExecStart = ''
${pkgs.lirc}/bin/lircd --nodaemon \
${lib.escapeShellArgs cfg.extraArguments} \
${configFile}
'';
User = "lirc";
};
};
users.users.lirc = {
uid = config.ids.uids.lirc;
group = "lirc";
description = "LIRC user for lircd";
};
users.groups.lirc.gid = config.ids.gids.lirc;
};
}

View File

@@ -0,0 +1,139 @@
{
config,
lib,
pkgs,
...
}:
let
inherit (lib)
mkDefault
mkEnableOption
mkIf
mkOption
mkPackageOption
types
;
cfg = config.services.monado;
runtimeManifest = "${cfg.package}/share/openxr/1/openxr_monado.json";
in
{
options.services.monado = {
enable = mkEnableOption "Monado user service";
package = mkPackageOption pkgs "monado" { };
defaultRuntime = mkOption {
type = types.bool;
description = ''
Whether to enable Monado as the default OpenXR runtime on the system.
Note that applications can bypass this option by setting an active
runtime in a writable XDG_CONFIG_DIRS location like `~/.config`.
'';
default = false;
example = true;
};
forceDefaultRuntime = mkOption {
type = types.bool;
description = ''
Whether to ensure that Monado is the active runtime set for the current
user.
This replaces the file `XDG_CONFIG_HOME/openxr/1/active_runtime.json`
when starting the service.
'';
default = false;
example = true;
};
highPriority =
mkEnableOption "high priority capability for monado-service" // mkOption { default = true; };
};
config = mkIf cfg.enable {
security.wrappers."monado-service" = mkIf cfg.highPriority {
setuid = false;
owner = "root";
group = "root";
# cap_sys_nice needed for asynchronous reprojection
capabilities = "cap_sys_nice+eip";
source = lib.getExe' cfg.package "monado-service";
};
services.udev.packages = with pkgs; [ xr-hardware ];
systemd.user = {
services.monado = {
description = "Monado XR runtime service module";
requires = [ "monado.socket" ];
conflicts = [ "monado-dev.service" ];
unitConfig.ConditionUser = "!root";
environment = {
# Default options
# https://gitlab.freedesktop.org/monado/monado/-/blob/4548e1738591d0904f8db4df8ede652ece889a76/src/xrt/targets/service/monado.in.service#L12
XRT_COMPOSITOR_LOG = mkDefault "debug";
XRT_PRINT_OPTIONS = mkDefault "on";
IPC_EXIT_ON_DISCONNECT = mkDefault "off";
# Needed to avoid libbasalt.so: cannot open shared object file: No such file or directory
VIT_SYSTEM_LIBRARY_PATH = mkDefault "${pkgs.basalt-monado}/lib/libbasalt.so";
};
preStart = mkIf cfg.forceDefaultRuntime ''
XDG_CONFIG_HOME="''${XDG_CONFIG_HOME:-$HOME/.config}"
targetDir="$XDG_CONFIG_HOME/openxr/1"
activeRuntimePath="$targetDir/active_runtime.json"
echo "Note: Replacing active runtime at '$activeRuntimePath'"
mkdir --parents "$targetDir"
ln --symbolic --force ${runtimeManifest} "$activeRuntimePath"
'';
serviceConfig = {
ExecStart =
if cfg.highPriority then
"${config.security.wrapperDir}/monado-service"
else
lib.getExe' cfg.package "monado-service";
Restart = "no";
};
restartTriggers = [ cfg.package ];
};
sockets.monado = {
description = "Monado XR service module connection socket";
conflicts = [ "monado-dev.service" ];
unitConfig.ConditionUser = "!root";
socketConfig = {
ListenStream = "%t/monado_comp_ipc";
RemoveOnStop = true;
# If Monado crashes while starting up, we want to close incoming OpenXR connections
FlushPending = true;
};
restartTriggers = [ cfg.package ];
wantedBy = [ "sockets.target" ];
};
};
environment.systemPackages = [ cfg.package ];
environment.pathsToLink = [ "/share/openxr" ];
hardware.graphics.extraPackages = [ pkgs.monado-vulkan-layers ];
environment.etc."xdg/openxr/1/active_runtime.json" = mkIf cfg.defaultRuntime {
source = runtimeManifest;
};
};
meta.maintainers = with lib.maintainers; [ Scrumplex ];
}

View File

@@ -0,0 +1,60 @@
{
csv-files,
device-name-strategy,
discovery-mode,
mounts,
glibc,
jq,
lib,
nvidia-container-toolkit,
nvidia-driver,
runtimeShell,
writeScriptBin,
extraArgs,
}:
let
mountToCommand =
mount:
"additionalMount \"${mount.hostPath}\" \"${mount.containerPath}\" '${builtins.toJSON mount.mountOptions}'";
mountsToCommands =
mounts:
if (builtins.length mounts) == 0 then
"cat"
else
(lib.strings.concatMapStringsSep " | \\\n" mountToCommand mounts);
in
writeScriptBin "nvidia-cdi-generator" ''
#! ${runtimeShell}
function cdiGenerate {
${lib.getExe' nvidia-container-toolkit "nvidia-ctk"} cdi generate \
--format json \
${
if (builtins.length csv-files) > 0 then
lib.concatMapStringsSep "\n" (file: "--csv.file ${file} \\") csv-files
else
"\\"
}
--discovery-mode ${discovery-mode} \
--device-name-strategy ${device-name-strategy} \
--ldconfig-path ${lib.getExe' glibc "ldconfig"} \
--library-search-path ${lib.getLib nvidia-driver}/lib \
--nvidia-cdi-hook-path ${lib.getOutput "tools" nvidia-container-toolkit}/bin/nvidia-cdi-hook \
${lib.escapeShellArgs extraArgs}
}
function additionalMount {
local hostPath="$1"
local containerPath="$2"
local mountOptions="$3"
if [ -e "$hostPath" ]; then
${lib.getExe jq} ".containerEdits.mounts[.containerEdits.mounts | length] = { \"hostPath\": \"$hostPath\", \"containerPath\": \"$containerPath\", \"options\": $mountOptions }"
else
echo "Mount $hostPath ignored: could not find path in the host machine" >&2
cat
fi
}
cdiGenerate |
${mountsToCommands mounts} > $RUNTIME_DIRECTORY/nvidia-container-toolkit.json
''

View File

@@ -0,0 +1,318 @@
{
config,
lib,
pkgs,
...
}:
{
imports = [
(lib.mkRenamedOptionModule
[ "virtualisation" "containers" "cdi" "dynamic" "nvidia" "enable" ]
[ "hardware" "nvidia-container-toolkit" "enable" ]
)
];
options =
let
mountType = {
options = {
hostPath = lib.mkOption {
type = lib.types.str;
description = "Host path.";
};
containerPath = lib.mkOption {
type = lib.types.str;
description = "Container path.";
};
mountOptions = lib.mkOption {
default = [
"ro"
"nosuid"
"nodev"
"bind"
];
type = lib.types.listOf lib.types.str;
description = "Mount options.";
};
};
};
in
{
hardware.nvidia-container-toolkit = {
enable = lib.mkOption {
default = false;
type = lib.types.bool;
description = ''
Enable dynamic CDI configuration for Nvidia devices by running
nvidia-container-toolkit on boot.
'';
};
device-name-strategy = lib.mkOption {
default = "index";
type = lib.types.enum [
"index"
"uuid"
"type-index"
];
description = ''
Specify the strategy for generating device names,
passed to `nvidia-ctk cdi generate`. This will affect how
you reference the device using `nvidia.com/gpu=` in
the container runtime.
'';
};
discovery-mode = lib.mkOption {
default = "auto";
type = lib.types.enum [
"auto"
"csv"
"nvml"
"wsl"
];
description = ''
The mode to use when discovering the available entities.
'';
};
csv-files = lib.mkOption {
default = [ ];
type = lib.types.listOf lib.types.path;
description = ''
The path to the list of CSV files to use when generating the CDI specification in CSV mode.
'';
};
mounts = lib.mkOption {
type = lib.types.listOf (lib.types.submodule mountType);
default = [ ];
description = "Mounts to be added to every container under the Nvidia CDI profile.";
};
mount-nvidia-executables = lib.mkOption {
default = true;
type = lib.types.bool;
description = ''
Mount executables nvidia-smi, nvidia-cuda-mps-control, nvidia-cuda-mps-server,
nvidia-debugdump, nvidia-powerd and nvidia-ctk on containers.
'';
};
mount-nvidia-docker-1-directories = lib.mkOption {
default = true;
type = lib.types.bool;
description = ''
Mount nvidia-docker-1 directories on containers: /usr/local/nvidia/lib and
/usr/local/nvidia/lib64.
'';
};
suppressNvidiaDriverAssertion = lib.mkOption {
default = false;
type = lib.types.bool;
description = ''
Suppress the assertion for installing Nvidia driver.
Useful in WSL where drivers are mounted from Windows, not provided by NixOS.
'';
};
package = lib.mkPackageOption pkgs "nvidia-container-toolkit" { };
extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
Extra arguments to be passed to nvidia-ctk.
'';
};
};
};
config = lib.mkMerge [
(lib.mkIf config.virtualisation.docker.enableNvidia {
environment.etc."nvidia-container-runtime/config.toml".text = ''
disable-require = true
supported-driver-capabilities = "compat32,compute,display,graphics,ngx,utility,video"
[nvidia-container-cli]
environment = []
ldconfig = "@${lib.getExe' pkgs.glibc "ldconfig"}"
load-kmods = true
no-cgroups = false
path = "${lib.getExe' pkgs.libnvidia-container "nvidia-container-cli"}"
[nvidia-container-runtime]
mode = "auto"
runtimes = ["docker-runc", "runc", "crun"]
[nvidia-container-runtime-hook]
path = "${lib.getOutput "tools" config.hardware.nvidia-container-toolkit.package}/bin/nvidia-container-runtime-hook"
skip-mode-detection = false
[nvidia-ctk]
path = "${lib.getExe' config.hardware.nvidia-container-toolkit.package "nvidia-ctk"}"
'';
virtualisation.docker = {
daemon.settings = {
default-runtime = "nvidia";
runtimes.nvidia = {
path = "${lib.getOutput "tools" config.hardware.nvidia-container-toolkit.package}/bin/nvidia-container-runtime";
args = [ ];
};
};
extraPackages = [
(lib.getOutput "tools" config.hardware.nvidia-container-toolkit.package)
];
};
})
(lib.mkIf config.hardware.nvidia-container-toolkit.enable {
assertions = [
{
assertion =
config.hardware.nvidia.datacenter.enable
|| lib.elem "nvidia" config.services.xserver.videoDrivers
|| config.hardware.nvidia-container-toolkit.suppressNvidiaDriverAssertion;
message = ''`nvidia-container-toolkit` requires nvidia drivers: set `hardware.nvidia.datacenter.enable`, add "nvidia" to `services.xserver.videoDrivers`, or set `hardware.nvidia-container-toolkit.suppressNvidiaDriverAssertion` if the driver is provided by another NixOS module (e.g. from NixOS-WSL)'';
}
{
assertion =
((builtins.length config.hardware.nvidia-container-toolkit.csv-files) > 0)
-> config.hardware.nvidia-container-toolkit.discovery-mode == "csv";
message = ''When CSV files are provided, `config.hardware.nvidia-container-toolkit.discovery-mode` has to be set to `csv`.'';
}
];
warnings = lib.mkMerge [
(lib.mkIf config.virtualisation.podman.enableNvidia [
"Setting virtualisation.podman.enableNvidia has no effect and will be removed soon."
])
];
virtualisation = {
containers.containersConf.settings = {
engine = {
cdi_spec_dirs = [
"/etc/cdi"
"/var/run/cdi"
];
};
};
docker =
let
dockerVersion = config.virtualisation.docker.package.version;
in
{
daemon.settings = lib.mkIf (lib.versionAtLeast dockerVersion "25") {
features.cdi = true;
};
rootless = {
daemon.settings = lib.mkIf (lib.versionAtLeast dockerVersion "25") {
features.cdi = true;
};
extraPackages = [
(lib.getOutput "tools" config.hardware.nvidia-container-toolkit.package)
];
};
};
};
hardware = {
graphics.enable = lib.mkIf (!config.hardware.nvidia.datacenter.enable) true;
nvidia-container-toolkit.mounts =
let
nvidia-driver = config.hardware.nvidia.package;
in
(lib.mkMerge [
[
{
hostPath = pkgs.addDriverRunpath.driverLink;
containerPath = pkgs.addDriverRunpath.driverLink;
}
{
hostPath = "${lib.getLib nvidia-driver}/etc";
containerPath = "${lib.getLib nvidia-driver}/etc";
}
{
hostPath = "${lib.getLib nvidia-driver}/share";
containerPath = "${lib.getLib nvidia-driver}/share";
}
{
hostPath = "${lib.getLib pkgs.glibc}/lib";
containerPath = "${lib.getLib pkgs.glibc}/lib";
}
{
hostPath = "${lib.getLib pkgs.glibc}/lib64";
containerPath = "${lib.getLib pkgs.glibc}/lib64";
}
]
(lib.mkIf config.hardware.nvidia-container-toolkit.mount-nvidia-executables [
{
hostPath = lib.getExe' nvidia-driver "nvidia-cuda-mps-control";
containerPath = "/usr/bin/nvidia-cuda-mps-control";
}
{
hostPath = lib.getExe' nvidia-driver "nvidia-cuda-mps-server";
containerPath = "/usr/bin/nvidia-cuda-mps-server";
}
{
hostPath = lib.getExe' nvidia-driver "nvidia-debugdump";
containerPath = "/usr/bin/nvidia-debugdump";
}
{
hostPath = lib.getExe' nvidia-driver "nvidia-powerd";
containerPath = "/usr/bin/nvidia-powerd";
}
{
hostPath = lib.getExe' nvidia-driver "nvidia-smi";
containerPath = "/usr/bin/nvidia-smi";
}
])
# nvidia-docker 1.0 uses /usr/local/nvidia/lib{,64}
# e.g.
# - https://gitlab.com/nvidia/container-images/cuda/-/blob/e3ff10eab3a1424fe394899df0e0f8ca5a410f0f/dist/12.3.1/ubi9/base/Dockerfile#L44
# - https://github.com/NVIDIA/nvidia-docker/blob/01d2c9436620d7dde4672e414698afe6da4a282f/src/nvidia/volumes.go#L104-L173
(lib.mkIf config.hardware.nvidia-container-toolkit.mount-nvidia-docker-1-directories [
{
hostPath = "${lib.getLib nvidia-driver}/lib";
containerPath = "/usr/local/nvidia/lib";
}
{
hostPath = "${lib.getLib nvidia-driver}/lib";
containerPath = "/usr/local/nvidia/lib64";
}
])
]);
};
systemd.services.nvidia-container-toolkit-cdi-generator = {
description = "Container Device Interface (CDI) for Nvidia generator";
wantedBy = [ "multi-user.target" ];
after = [ "systemd-udev-settle.service" ];
serviceConfig = {
RuntimeDirectory = "cdi";
RemainAfterExit = true;
ExecStart =
let
script = pkgs.callPackage ./cdi-generate.nix {
inherit (config.hardware.nvidia-container-toolkit)
csv-files
device-name-strategy
discovery-mode
mounts
extraArgs
;
nvidia-container-toolkit = config.hardware.nvidia-container-toolkit.package;
nvidia-driver = config.hardware.nvidia.package;
};
in
lib.getExe script;
Type = "oneshot";
};
};
})
];
}

View File

@@ -0,0 +1,51 @@
{ config, lib, ... }:
let
kernel = config.boot.kernelPackages;
in
{
###### interface
options = {
hardware.nvidiaOptimus.disable = lib.mkOption {
default = false;
type = lib.types.bool;
description = ''
Completely disable the NVIDIA graphics card and use the
integrated graphics processor instead.
'';
};
};
###### implementation
config = lib.mkIf config.hardware.nvidiaOptimus.disable {
boot.blacklistedKernelModules = [
"nouveau"
"nvidia"
"nvidiafb"
"nvidia-drm"
"nvidia-uvm"
"nvidia-modeset"
];
boot.kernelModules = [ "bbswitch" ];
boot.extraModulePackages = [ kernel.bbswitch ];
systemd.services.bbswitch = {
description = "Disable NVIDIA Card";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = "${kernel.bbswitch}/bin/discrete_vga_poweroff";
ExecStop = "${kernel.bbswitch}/bin/discrete_vga_poweron";
};
path = [ kernel.bbswitch ];
};
};
}

View File

@@ -0,0 +1,78 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.services.hardware.openrgb;
in
{
options.services.hardware.openrgb = {
enable = lib.mkEnableOption "OpenRGB server, for RGB lighting control";
package = lib.mkPackageOption pkgs "openrgb" { };
motherboard = lib.mkOption {
type = lib.types.nullOr (
lib.types.enum [
"amd"
"intel"
]
);
default =
if config.hardware.cpu.intel.updateMicrocode then
"intel"
else if config.hardware.cpu.amd.updateMicrocode then
"amd"
else
null;
defaultText = lib.literalMD ''
if config.hardware.cpu.intel.updateMicrocode then "intel"
else if config.hardware.cpu.amd.updateMicrocode then "amd"
else null;
'';
description = "CPU family of motherboard. Allows for addition motherboard i2c support.";
};
server.port = lib.mkOption {
type = lib.types.port;
default = 6742;
description = "Set server port of openrgb.";
};
startupProfile = lib.mkOption {
type = lib.types.nullOr (lib.types.str);
default = null;
description = "The profile file to load from \"/var/lib/OpenRGB\" at startup.";
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
services.udev.packages = [ cfg.package ];
boot.kernelModules = [
"i2c-dev"
]
++ lib.optionals (cfg.motherboard == "amd") [ "i2c-piix4" ]
++ lib.optionals (cfg.motherboard == "intel") [ "i2c-i801" ];
systemd.services.openrgb = {
description = "OpenRGB server daemon";
after = [ "network.target" ];
wants = [ "dev-usb.device" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
StateDirectory = "OpenRGB";
WorkingDirectory = "/var/lib/OpenRGB";
ExecStart =
"${cfg.package}/bin/openrgb --server --server-port ${toString cfg.server.port}"
+ lib.optionalString (builtins.isString cfg.startupProfile) " --profile ${lib.escapeShellArg cfg.startupProfile}";
Restart = "always";
};
};
};
meta.maintainers = [ ];
}

View File

@@ -0,0 +1,138 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.pcscd;
cfgFile = pkgs.writeText "reader.conf" (
builtins.concatStringsSep "\n\n" config.services.pcscd.readerConfigs
);
package = if config.security.polkit.enable then pkgs.pcscliteWithPolkit else pkgs.pcsclite;
pluginEnv = pkgs.buildEnv {
name = "pcscd-plugins";
paths = map (p: "${p}/pcsc/drivers") config.services.pcscd.plugins;
};
in
{
imports = [
(lib.mkChangedOptionModule
[ "services" "pcscd" "readerConfig" ]
[ "services" "pcscd" "readerConfigs" ]
(
config:
let
readerConfig = lib.getAttrFromPath [ "services" "pcscd" "readerConfig" ] config;
in
[ readerConfig ]
)
)
];
options.services.pcscd = {
enable = lib.mkEnableOption "PCSC-Lite daemon, to access smart cards using SCard API (PC/SC)";
plugins = lib.mkOption {
type = lib.types.listOf lib.types.package;
defaultText = lib.literalExpression "[ pkgs.ccid ]";
example = lib.literalExpression "[ pkgs.pcsc-cyberjack ]";
description = "Plugin packages to be used for PCSC-Lite.";
};
readerConfigs = lib.mkOption {
type = lib.types.listOf lib.types.lines;
default = [ ];
example = [
''
FRIENDLYNAME "Some serial reader"
DEVICENAME /dev/ttyS0
LIBPATH /path/to/serial_reader.so
CHANNELID 1
''
];
description = ''
Configuration for devices that aren't hotpluggable.
See {manpage}`reader.conf(5)` for valid options.
'';
};
extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Extra command line arguments to be passed to the PCSC daemon.";
};
ignoreReaderNames = lib.mkOption {
type = lib.types.listOf (lib.types.strMatching "[^:]+");
default = [ ];
description = ''
List of reader name patterns for the PCSC daemon to ignore.
For more precise control, readers can be ignored through udev rules
(cf. {option}`services.udev.extraRules`) by setting the
`PCSCLITE_IGNORE` property, for example:
```
ACTION!="remove|unbind", SUBSYSTEM=="usb", ATTR{idVendor}=="20a0", ENV{PCSCLITE_IGNORE}="1"
```
'';
example = [
"Nitrokey"
"YubiKey"
];
};
extendReaderNames = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
String to append to every reader name. The special variable `$HOSTNAME`
will be expanded to the current host name.
'';
example = " $HOSTNAME";
};
};
config = lib.mkIf config.services.pcscd.enable {
environment.etc."reader.conf".source = cfgFile;
environment.systemPackages = [ package ];
systemd.packages = [ package ];
services.pcscd.plugins = [ pkgs.ccid ];
systemd.sockets.pcscd.wantedBy = [ "sockets.target" ];
systemd.services.pcscd = {
environment = {
PCSCLITE_HP_DROPDIR = pluginEnv;
PCSCLITE_FILTER_IGNORE_READER_NAMES = lib.mkIf (cfg.ignoreReaderNames != [ ]) (
lib.concatStringsSep ":" cfg.ignoreReaderNames
);
PCSCLITE_FILTER_EXTEND_READER_NAMES = lib.mkIf (
cfg.extendReaderNames != null
) cfg.extendReaderNames;
};
# If the cfgFile is empty and not specified (in which case the default
# /etc/reader.conf is assumed), pcscd will happily start going through the
# entire confdir (/etc in our case) looking for a config file and try to
# parse everything it finds. Doesn't take a lot of imagination to see how
# well that works. It really shouldn't do that to begin with, but to work
# around it, we force the path to the cfgFile.
#
# https://github.com/NixOS/nixpkgs/issues/121088
serviceConfig.ExecStart = [
""
"${lib.getExe package} -f -x -c ${cfgFile} ${lib.escapeShellArgs cfg.extraArgs}"
];
};
};
}

View File

@@ -0,0 +1,188 @@
{
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 ];
}

View File

@@ -0,0 +1,57 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.hardware.pommed;
defaultConf = "${pkgs.pommed_light}/etc/pommed.conf.mactel";
in
{
options = {
services.hardware.pommed = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to use the pommed tool to handle Apple laptop
keyboard hotkeys.
'';
};
configFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = ''
The path to the {file}`pommed.conf` file. Leave
to null to use the default config file
({file}`/etc/pommed.conf.mactel`). See the
files {file}`/etc/pommed.conf.mactel` and
{file}`/etc/pommed.conf.pmac` for examples to
build on.
'';
};
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [
pkgs.polkit
pkgs.pommed_light
];
environment.etc."pommed.conf".source =
if cfg.configFile == null then defaultConf else cfg.configFile;
systemd.services.pommed = {
description = "Pommed Apple Hotkeys Daemon";
wantedBy = [ "multi-user.target" ];
script = "${pkgs.pommed_light}/bin/pommed -f";
};
};
}

View File

@@ -0,0 +1,66 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.power-profiles-daemon;
in
{
###### interface
options = {
services.power-profiles-daemon = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to enable power-profiles-daemon, a DBus daemon that allows
changing system behavior based upon user-selected power profiles.
'';
};
package = lib.mkPackageOption pkgs "power-profiles-daemon" { };
};
};
###### implementation
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = !config.services.tlp.enable;
message = ''
You have set services.power-profiles-daemon.enable = true;
which conflicts with services.tlp.enable = true;
'';
}
{
assertion = !config.services.auto-cpufreq.enable;
message = ''
You have set services.power-profiles-daemon.enable = true;
which conflicts with services.auto-cpufreq.enable = true;
'';
}
];
environment.systemPackages = [ cfg.package ];
services.dbus.packages = [ cfg.package ];
services.udev.packages = [ cfg.package ];
systemd.packages = [ cfg.package ];
};
}

View File

@@ -0,0 +1,37 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.powerstation;
in
{
options.services.powerstation = {
enable = lib.mkEnableOption "PowerStation";
package = lib.mkPackageOption pkgs "powerstation" { };
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
systemd.services.powerstation = {
description = "PowerStation Service";
wantedBy = [ "multi-user.target" ];
after = [ "graphical-session.target" ];
environment = {
XDG_DATA_DIRS = "/run/current-system/sw/share";
};
serviceConfig = {
User = "root";
Group = "root";
ExecStart = lib.getExe cfg.package;
};
};
};
meta.maintainers = with lib.maintainers; [ shadowapex ];
}

View File

@@ -0,0 +1,179 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.hardware.rasdaemon;
in
{
options.hardware.rasdaemon = {
enable = lib.mkEnableOption "RAS logging daemon";
package = lib.mkPackageOption pkgs "rasdaemon" { };
record = lib.mkOption {
type = lib.types.bool;
default = true;
description = "record events via sqlite3, required for ras-mc-ctl";
};
mainboard = lib.mkOption {
type = lib.types.lines;
default = "";
description = "Custom mainboard description, see {manpage}`ras-mc-ctl(8)` for more details.";
example = ''
vendor = ASRock
model = B450M Pro4
# it should default to such values from
# /sys/class/dmi/id/board_[vendor|name]
# alternatively one can supply a script
# that returns the same format as above
script = <path to script>
'';
};
# TODO, accept `rasdaemon.labels = " ";` or `rasdaemon.labels = { dell = " "; asrock = " "; };'
labels = lib.mkOption {
type = lib.types.lines;
default = "";
description = "Additional memory module label descriptions to be placed in /etc/ras/dimm_labels.d/labels";
example = ''
# vendor and model may be shown by 'ras-mc-ctl --mainboard'
vendor: ASRock
product: To Be Filled By O.E.M.
model: B450M Pro4
# these labels are names for the motherboard slots
# the numbers may be shown by `ras-mc-ctl --error-count`
# they are mc:csrow:channel
DDR4_A1: 0.2.0; DDR4_B1: 0.2.1;
DDR4_A2: 0.3.0; DDR4_B2: 0.3.1;
'';
};
config = lib.mkOption {
type = lib.types.lines;
default = "";
description = ''
rasdaemon configuration, currently only used for CE PFA
for details, read rasdaemon.outPath/etc/sysconfig/rasdaemon's comments
'';
example = ''
# defaults from included config
PAGE_CE_REFRESH_CYCLE="24h"
PAGE_CE_THRESHOLD="50"
PAGE_CE_ACTION="soft"
'';
};
extraModules = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "extra kernel modules to load";
example = [ "i7core_edac" ];
};
testing = lib.mkEnableOption "error injection infrastructure";
};
config = lib.mkIf cfg.enable {
environment.etc = {
"ras/mainboard" = {
enable = cfg.mainboard != "";
text = cfg.mainboard;
};
# TODO, handle multiple cfg.labels.brand = " ";
"ras/dimm_labels.d/labels" = {
enable = cfg.labels != "";
text = cfg.labels;
};
"sysconfig/rasdaemon" = {
enable = cfg.config != "";
text = cfg.config;
};
};
environment.systemPackages = [
cfg.package
]
++ lib.optionals (cfg.testing) (
with pkgs.error-inject;
[
edac-inject
mce-inject
aer-inject
]
);
boot.initrd.kernelModules =
cfg.extraModules
++ lib.optionals (cfg.testing) [
# edac_core and amd64_edac should get loaded automatically
# i7core_edac may not be, and may not be required, but should load successfully
"edac_core"
"amd64_edac"
"i7core_edac"
"mce-inject"
"aer-inject"
];
boot.kernelPatches = lib.optionals (cfg.testing) [
{
name = "rasdaemon-tests";
patch = null;
extraConfig = ''
EDAC_DEBUG y
X86_MCE_INJECT y
PCIEPORTBUS y
PCIEAER y
PCIEAER_INJECT y
'';
}
];
# i tried to set up a group for this
# but rasdaemon needs higher permissions?
# `rasdaemon: Can't locate a mounted debugfs`
# most of this taken from src/misc/
systemd.services = {
rasdaemon = {
description = "the RAS logging daemon";
documentation = [ "man:rasdaemon(1)" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
StateDirectory = lib.optionalString (cfg.record) "rasdaemon";
ExecStart =
"${cfg.package}/bin/rasdaemon --foreground" + lib.optionalString (cfg.record) " --record";
ExecStop = "${cfg.package}/bin/rasdaemon --disable";
Restart = "on-abort";
# src/misc/rasdaemon.service.in shows this:
# ExecStartPost = ${cfg.package}/bin/rasdaemon --enable
# but that results in unpredictable existence of the database
# and everything seems to be enabled without this...
};
};
ras-mc-ctl = lib.mkIf (cfg.labels != "") {
description = "register DIMM labels on startup";
documentation = [ "man:ras-mc-ctl(8)" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${cfg.package}/bin/ras-mc-ctl --register-labels";
RemainAfterExit = true;
};
};
};
};
}

View File

@@ -0,0 +1,31 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.ratbagd;
in
{
###### interface
options = {
services.ratbagd = {
enable = lib.mkEnableOption "ratbagd for configuring gaming mice";
package = lib.mkPackageOption pkgs "libratbag" { };
};
};
###### implementation
config = lib.mkIf cfg.enable {
# Give users access to the "ratbagctl" tool
environment.systemPackages = [ cfg.package ];
services.dbus.packages = [ cfg.package ];
systemd.packages = [ cfg.package ];
};
}

View File

@@ -0,0 +1,232 @@
{
config,
lib,
pkgs,
...
}:
let
pkg = config.hardware.sane.backends-package.override {
scanSnapDriversUnfree = config.hardware.sane.drivers.scanSnap.enable;
scanSnapDriversPackage = config.hardware.sane.drivers.scanSnap.package;
};
sanedConf = pkgs.writeTextFile {
name = "saned.conf";
destination = "/etc/sane.d/saned.conf";
text = ''
localhost
${config.services.saned.extraConfig}
'';
};
netConf = pkgs.writeTextFile {
name = "net.conf";
destination = "/etc/sane.d/net.conf";
text = ''
${lib.optionalString config.services.saned.enable "localhost"}
${config.hardware.sane.netConf}
'';
};
env = {
SANE_CONFIG_DIR = "/etc/sane-config";
LD_LIBRARY_PATH = [ "/etc/sane-libs" ];
};
backends = [
pkg
netConf
]
++ lib.optional config.services.saned.enable sanedConf
++ config.hardware.sane.extraBackends;
saneConfig = pkgs.mkSaneConfig {
paths = backends;
inherit (config.hardware.sane) disabledDefaultBackends;
};
enabled = config.hardware.sane.enable || config.services.saned.enable;
in
{
###### interface
options = {
hardware.sane.enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Enable support for SANE scanners.
::: {.note}
Users in the "scanner" group will gain access to the scanner, or the "lp" group if it's also a printer.
:::
'';
};
hardware.sane.backends-package = lib.mkPackageOption pkgs "sane-backends" { };
hardware.sane.snapshot = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Use a development snapshot of SANE scanner drivers.";
};
hardware.sane.extraBackends = lib.mkOption {
type = lib.types.listOf lib.types.path;
default = [ ];
description = ''
Packages providing extra SANE backends to enable.
::: {.note}
The example contains the package for HP scanners, and the package for
Apple AirScan and Microsoft WSD support (supports many
vendors/devices).
:::
'';
example = lib.literalExpression "[ pkgs.hplipWithPlugin pkgs.sane-airscan ]";
};
hardware.sane.disabledDefaultBackends = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [ "v4l" ];
description = ''
Names of backends which are enabled by default but should be disabled.
See `$SANE_CONFIG_DIR/dll.conf` for the list of possible names.
'';
};
hardware.sane.configDir = lib.mkOption {
type = lib.types.str;
internal = true;
description = "The value of SANE_CONFIG_DIR.";
};
hardware.sane.netConf = lib.mkOption {
type = lib.types.lines;
default = "";
example = "192.168.0.16";
description = ''
Network hosts that should be probed for remote scanners.
'';
};
hardware.sane.drivers.scanSnap.enable = lib.mkOption {
type = lib.types.bool;
default = false;
example = true;
description = ''
Whether to enable drivers for the Fujitsu ScanSnap scanners.
The driver files are unfree and extracted from the Windows driver image.
'';
};
hardware.sane.drivers.scanSnap.package = lib.mkPackageOption pkgs [ "sane-drivers" "epjitsu" ] {
extraDescription = ''
Useful if you want to extract the driver files yourself.
The process is described in the {file}`/etc/sane.d/epjitsu.conf` file in
the `sane-backends` package.
'';
};
hardware.sane.openFirewall = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Open ports needed for discovery of scanners on the local network, e.g.
needed for Canon scanners (BJNP protocol).
'';
};
services.saned.enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Enable saned network daemon for remote connection to scanners.
saned would be run from `scanner` user; to allow
access to hardware that doesn't have `scanner` group
you should add needed groups to this user.
'';
};
services.saned.extraConfig = lib.mkOption {
type = lib.types.lines;
default = "";
example = "192.168.0.0/24";
description = ''
Extra saned configuration lines.
'';
};
};
###### implementation
config = lib.mkMerge [
(lib.mkIf enabled {
hardware.sane.configDir = lib.mkDefault "${saneConfig}/etc/sane.d";
environment.systemPackages = backends;
environment.sessionVariables = env;
environment.etc."sane-config".source = config.hardware.sane.configDir;
environment.etc."sane-libs".source = "${saneConfig}/lib/sane";
services.udev.packages = backends;
# sane sets up udev rules that tag scanners with `uaccess`. This way, physically logged in users
# can access them without belonging to the `scanner` group. However, the `scanner` user used by saned
# does not have a real logind seat, so `uaccess` is not enough.
services.udev.extraRules = ''
ENV{DEVNAME}!="", ENV{libsane_matched}=="yes", RUN+="${pkgs.acl}/bin/setfacl -m g:scanner:rw $env{DEVNAME}"
'';
users.groups.scanner.gid = config.ids.gids.scanner;
networking.firewall.allowedUDPPorts = lib.mkIf config.hardware.sane.openFirewall [ 8612 ];
systemd.tmpfiles.rules = [
"d /var/lock/sane 0770 root scanner - -"
];
})
(lib.mkIf config.services.saned.enable {
networking.firewall.connectionTrackingModules = [ "sane" ];
systemd.services."saned@" = {
description = "Scanner Service";
environment = lib.mapAttrs (name: val: toString val) env;
serviceConfig = {
User = "scanner";
Group = "scanner";
ExecStart = "${pkg}/bin/saned";
};
};
systemd.sockets.saned = {
description = "saned incoming socket";
wantedBy = [ "sockets.target" ];
listenStreams = [
"0.0.0.0:6566"
"[::]:6566"
];
socketConfig = {
# saned needs to distinguish between IPv4 and IPv6 to open matching data sockets.
BindIPv6Only = "ipv6-only";
Accept = true;
MaxConnections = 64;
};
};
users.users.scanner = {
uid = config.ids.uids.scanner;
group = "scanner";
extraGroups = [ "lp" ] ++ lib.optionals config.services.avahi.enable [ "avahi" ];
};
})
];
}

View File

@@ -0,0 +1,122 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.hardware.sane.brscan4;
netDeviceList = lib.attrValues cfg.netDevices;
etcFiles = pkgs.callPackage ./brscan4_etc_files.nix { netDevices = netDeviceList; };
netDeviceOpts =
{ name, ... }:
{
options = {
name = lib.mkOption {
type = lib.types.str;
description = ''
The friendly name you give to the network device. If undefined,
the name of attribute will be used.
'';
example = "office1";
};
model = lib.mkOption {
type = lib.types.str;
description = ''
The model of the network device.
'';
example = "MFC-7860DW";
};
ip = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = ''
The ip address of the device. If undefined, you will have to
provide a nodename.
'';
example = "192.168.1.2";
};
nodename = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = ''
The node name of the device. If undefined, you will have to
provide an ip.
'';
example = "BRW0080927AFBCE";
};
};
config = {
name = lib.mkDefault name;
};
};
in
{
options = {
hardware.sane.brscan4.enable = lib.mkEnableOption "Brother's brscan4 scan backend" // {
description = ''
When enabled, will automatically register the "brscan4" sane
backend and bring configuration files to their expected location.
'';
};
hardware.sane.brscan4.netDevices = lib.mkOption {
default = { };
example = {
office1 = {
model = "MFC-7860DW";
ip = "192.168.1.2";
};
office2 = {
model = "MFC-7860DW";
nodename = "BRW0080927AFBCE";
};
};
type = with lib.types; attrsOf (submodule netDeviceOpts);
description = ''
The list of network devices that will be registered against the brscan4
sane backend.
'';
};
};
config = lib.mkIf (config.hardware.sane.enable && cfg.enable) {
hardware.sane.extraBackends = [
pkgs.brscan4
];
environment.etc."opt/brother/scanner/brscan4" = {
source = "${etcFiles}/etc/opt/brother/scanner/brscan4";
};
assertions = [
{
assertion = lib.all (x: !(null != x.ip && null != x.nodename)) netDeviceList;
message = ''
When describing a network device as part of the attribute list
`hardware.sane.brscan4.netDevices`, only one of its `ip` or `nodename`
attribute should be specified, not both!
'';
}
];
};
}

View File

@@ -0,0 +1,75 @@
{
stdenv,
lib,
brscan4,
netDevices ? [ ],
}:
/*
Testing
-------
No net devices:
~~~
nix-shell -E 'with import <nixpkgs> { }; brscan4-etc-files'
~~~
Two net devices:
~~~
nix-shell -E 'with import <nixpkgs> { }; brscan4-etc-files.override{netDevices=[{name="a"; model="MFC-7860DW"; nodename="BRW0080927AFBCE";} {name="b"; model="MFC-7860DW"; ip="192.168.1.2";}];}'
~~~
*/
let
addNetDev = nd: ''
brsaneconfig4 -a \
name="${nd.name}" \
model="${nd.model}" \
${
if (lib.hasAttr "nodename" nd && nd.nodename != null) then
''nodename="${nd.nodename}"''
else
''ip="${nd.ip}"''
}'';
addAllNetDev = xs: lib.concatStringsSep "\n" (map addNetDev xs);
in
stdenv.mkDerivation {
pname = "brscan4-etc-files";
version = "0.4.3-3";
src = "${brscan4}/opt/brother/scanner/brscan4";
nativeBuildInputs = [ brscan4 ];
dontConfigure = true;
buildPhase = ''
TARGET_DIR="$out/etc/opt/brother/scanner/brscan4"
mkdir -p "$TARGET_DIR"
cp -rp "./models4" "$TARGET_DIR"
cp -rp "./Brsane4.ini" "$TARGET_DIR"
cp -rp "./brsanenetdevice4.cfg" "$TARGET_DIR"
export BRSANENETDEVICE4_CFG_FILENAME="$TARGET_DIR/brsanenetdevice4.cfg"
printf '${addAllNetDev netDevices}\n'
${addAllNetDev netDevices}
'';
dontInstall = true;
dontStrip = true;
dontPatchELF = true;
meta = with lib; {
description = "Brother brscan4 sane backend driver etc files";
homepage = "http://www.brother.com";
platforms = platforms.linux;
license = licenses.unfree;
maintainers = with maintainers; [ jraygauthier ];
};
}

View File

@@ -0,0 +1,122 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.hardware.sane.brscan5;
netDeviceList = lib.attrValues cfg.netDevices;
etcFiles = pkgs.callPackage ./brscan5_etc_files.nix { netDevices = netDeviceList; };
netDeviceOpts =
{ name, ... }:
{
options = {
name = lib.mkOption {
type = lib.types.str;
description = ''
The friendly name you give to the network device. If undefined,
the name of attribute will be used.
'';
example = "office1";
};
model = lib.mkOption {
type = lib.types.str;
description = ''
The model of the network device.
'';
example = "ADS-1200";
};
ip = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = ''
The ip address of the device. If undefined, you will have to
provide a nodename.
'';
example = "192.168.1.2";
};
nodename = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = ''
The node name of the device. If undefined, you will have to
provide an ip.
'';
example = "BRW0080927AFBCE";
};
};
config = {
name = lib.mkDefault name;
};
};
in
{
options = {
hardware.sane.brscan5.enable = lib.mkEnableOption "the Brother brscan5 sane backend";
hardware.sane.brscan5.netDevices = lib.mkOption {
default = { };
example = {
office1 = {
model = "MFC-7860DW";
ip = "192.168.1.2";
};
office2 = {
model = "MFC-7860DW";
nodename = "BRW0080927AFBCE";
};
};
type = with lib.types; attrsOf (submodule netDeviceOpts);
description = ''
The list of network devices that will be registered against the brscan5
sane backend.
'';
};
};
config = lib.mkIf (config.hardware.sane.enable && cfg.enable) {
hardware.sane.extraBackends = [
pkgs.brscan5
];
environment.etc."opt/brother/scanner/brscan5" = {
source = "${etcFiles}/etc/opt/brother/scanner/brscan5";
};
environment.etc."opt/brother/scanner/models" = {
source = "${etcFiles}/etc/opt/brother/scanner/brscan5/models";
};
environment.etc."sane.d/dll.d/brother5.conf".source =
"${pkgs.brscan5}/etc/sane.d/dll.d/brother5.conf";
assertions = [
{
assertion = lib.all (x: !(null != x.ip && null != x.nodename)) netDeviceList;
message = ''
When describing a network device as part of the attribute list
`hardware.sane.brscan5.netDevices`, only one of its `ip` or `nodename`
attribute should be specified, not both!
'';
}
];
};
}

View File

@@ -0,0 +1,83 @@
{
stdenv,
lib,
brscan5,
netDevices ? [ ],
}:
/*
Testing
-------
From nixpkgs repo
No net devices:
~~~
nix-build -E 'let pkgs = import ./. {};
brscan5-etc-files = pkgs.callPackage (import ./nixos/modules/services/hardware/sane_extra_backends/brscan5_etc_files.nix) {};
in brscan5-etc-files'
~~~
Two net devices:
~~~
nix-build -E 'let pkgs = import ./. {};
brscan5-etc-files = pkgs.callPackage (import ./nixos/modules/services/hardware/sane_extra_backends/brscan5_etc_files.nix) {};
in brscan5-etc-files.override {
netDevices = [
{name="a"; model="ADS-1200"; nodename="BRW0080927AFBCE";}
{name="b"; model="ADS-1200"; ip="192.168.1.2";}
];
}'
~~~
*/
let
addNetDev = nd: ''
brsaneconfig5 -a \
name="${nd.name}" \
model="${nd.model}" \
${
if (lib.hasAttr "nodename" nd && nd.nodename != null) then
''nodename="${nd.nodename}"''
else
''ip="${nd.ip}"''
}'';
addAllNetDev = xs: lib.concatStringsSep "\n" (map addNetDev xs);
in
stdenv.mkDerivation {
name = "brscan5-etc-files";
version = "1.2.6-0";
src = "${brscan5}/opt/brother/scanner/brscan5";
nativeBuildInputs = [ brscan5 ];
dontConfigure = true;
buildPhase = ''
TARGET_DIR="$out/etc/opt/brother/scanner/brscan5"
mkdir -p "$TARGET_DIR"
cp -rp "./models" "$TARGET_DIR"
cp -rp "./brscan5.ini" "$TARGET_DIR"
cp -rp "./brsanenetdevice.cfg" "$TARGET_DIR"
export NIX_REDIRECTS="/etc/opt/brother/scanner/brscan5/=$TARGET_DIR/"
printf '${addAllNetDev netDevices}\n'
${addAllNetDev netDevices}
'';
dontInstall = true;
meta = with lib; {
description = "Brother brscan5 sane backend driver etc files";
homepage = "https://www.brother.com";
platforms = platforms.linux;
license = licenses.unfree;
maintainers = with maintainers; [ mattchrist ];
};
}

View File

@@ -0,0 +1,27 @@
{
config,
lib,
pkgs,
...
}:
{
options = {
hardware.sane.dsseries.enable = lib.mkEnableOption "Brother DSSeries scan backend" // {
description = ''
When enabled, will automatically register the "dsseries" SANE backend.
This supports the Brother DSmobile scanner series, including the
DS-620, DS-720D, DS-820W, and DS-920DW scanners.
'';
};
};
config = lib.mkIf (config.hardware.sane.enable && config.hardware.sane.dsseries.enable) {
hardware.sane.extraBackends = [ pkgs.dsseries ];
services.udev.packages = [ pkgs.dsseries ];
boot.kernelModules = [ "sg" ];
};
}

View File

@@ -0,0 +1,154 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.scanservjs;
settings = {
scanimage = lib.getExe' config.hardware.sane.backends-package "scanimage";
convert = lib.getExe' pkgs.imagemagick "convert";
tesseract = lib.getExe pkgs.tesseract;
# it defaults to config/devices.json, but "config" dir doesn't exist by default and scanservjs doesn't create it
devicesPath = "devices.json";
}
// cfg.settings;
settingsFormat = pkgs.formats.json { };
leafs =
attrs:
builtins.concatLists (
lib.mapAttrsToList (k: v: if builtins.isAttrs v then leafs v else [ v ]) attrs
);
package = pkgs.scanservjs;
configFile = pkgs.writeText "config.local.js" ''
/* eslint-disable no-unused-vars */
module.exports = {
afterConfig(config) {
${builtins.concatStringsSep "" (
leafs (
lib.mapAttrsRecursive (path: val: ''
${builtins.concatStringsSep "." path} = ${builtins.toJSON val};
'') { config = settings; }
)
)}
${cfg.extraConfig}
},
afterDevices(devices) {
${cfg.extraDevicesConfig}
},
async afterScan(fileInfo) {
${cfg.runAfterScan}
},
actions: [
${builtins.concatStringsSep ",\n" cfg.extraActions}
],
};
'';
in
{
options.services.scanservjs = {
enable = lib.mkEnableOption "scanservjs";
stateDir = lib.mkOption {
type = lib.types.str;
default = "/var/lib/scanservjs";
description = ''
State directory for scanservjs.
'';
};
settings = lib.mkOption {
default = { };
description = ''
Config to set in config.local.js's `afterConfig`.
'';
type = lib.types.submodule {
freeformType = settingsFormat.type;
options.host = lib.mkOption {
type = lib.types.str;
description = "The IP to listen on.";
default = "127.0.0.1";
};
options.port = lib.mkOption {
type = lib.types.port;
description = "The port to listen on.";
default = 8080;
};
};
};
extraConfig = lib.mkOption {
default = "";
type = lib.types.lines;
description = ''
Extra code to add to config.local.js's `afterConfig`.
'';
};
extraDevicesConfig = lib.mkOption {
default = "";
type = lib.types.lines;
description = ''
Extra code to add to config.local.js's `afterDevices`.
'';
};
runAfterScan = lib.mkOption {
default = "";
type = lib.types.lines;
description = ''
Extra code to add to config.local.js's `afterScan`.
'';
};
extraActions = lib.mkOption {
default = [ ];
type = lib.types.listOf lib.types.lines;
description = "Actions to add to config.local.js's `actions`.";
};
};
config = lib.mkIf cfg.enable {
hardware.sane.enable = true;
users.users.scanservjs = {
group = "scanservjs";
extraGroups = [
"scanner"
"lp"
];
home = cfg.stateDir;
isSystemUser = true;
createHome = true;
};
users.groups.scanservjs = { };
systemd.services.scanservjs = {
description = "scanservjs";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
# yes, those paths are configurable, but the config option isn't always used...
# a lot of the time scanservjs just takes those from PATH
path = with pkgs; [
coreutils
config.hardware.sane.backends-package
imagemagick
tesseract
];
environment = {
NIX_SCANSERVJS_CONFIG_PATH = configFile;
SANE_CONFIG_DIR = "/etc/sane-config";
LD_LIBRARY_PATH = "/etc/sane-libs";
};
serviceConfig = {
ExecStart = lib.getExe package;
Restart = "always";
User = "scanservjs";
Group = "scanservjs";
WorkingDirectory = cfg.stateDir;
};
};
};
}

View File

@@ -0,0 +1,26 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.hardware.spacenavd;
in
{
options = {
hardware.spacenavd = {
enable = lib.mkEnableOption "spacenavd to support 3DConnexion devices";
};
};
config = lib.mkIf cfg.enable {
systemd = {
packages = [ pkgs.spacenavd ];
services.spacenavd = {
enable = true;
wantedBy = [ "graphical.target" ];
};
};
};
}

View File

@@ -0,0 +1,50 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.supergfxd;
json = pkgs.formats.json { };
in
{
options = {
services.supergfxd = {
enable = lib.mkEnableOption "the supergfxd service";
settings = lib.mkOption {
type = lib.types.nullOr json.type;
default = null;
description = ''
The content of /etc/supergfxd.conf.
See <https://gitlab.com/asus-linux/supergfxctl/#config-options-etcsupergfxdconf>.
'';
};
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ pkgs.supergfxctl ];
environment.etc."supergfxd.conf" = lib.mkIf (cfg.settings != null) {
source = json.generate "supergfxd.conf" cfg.settings;
mode = "0644";
};
services.dbus.enable = true;
systemd.packages = [ pkgs.supergfxctl ];
systemd.services.supergfxd.wantedBy = [ "multi-user.target" ];
systemd.services.supergfxd.path = [
pkgs.kmod
pkgs.pciutils
];
services.dbus.packages = [ pkgs.supergfxctl ];
services.udev.packages = [ pkgs.supergfxctl ];
};
meta.maintainers = pkgs.supergfxctl.meta.maintainers;
}

View File

@@ -0,0 +1,165 @@
# tcsd daemon.
{
config,
options,
pkgs,
lib,
...
}:
let
cfg = config.services.tcsd;
opt = options.services.tcsd;
tcsdConf = pkgs.writeText "tcsd.conf" ''
port = 30003
num_threads = 10
system_ps_file = ${cfg.stateDir}/system.data
# This is the log of each individual measurement done by the system.
# By re-calculating the PCR registers based on this information, even
# finer details about the measured environment can be inferred than
# what is available directly from the PCR registers.
firmware_log_file = /sys/kernel/security/tpm0/binary_bios_measurements
kernel_log_file = /sys/kernel/security/ima/binary_runtime_measurements
firmware_pcrs = ${cfg.firmwarePCRs}
kernel_pcrs = ${cfg.kernelPCRs}
platform_cred = ${cfg.platformCred}
conformance_cred = ${cfg.conformanceCred}
endorsement_cred = ${cfg.endorsementCred}
#remote_ops = create_key,random
#host_platform_class = server_12
#all_platform_classes = pc_11,pc_12,mobile_12
'';
in
{
###### interface
options = {
services.tcsd = {
enable = lib.mkOption {
default = false;
type = lib.types.bool;
description = ''
Whether to enable tcsd, a Trusted Computing management service
that provides TCG Software Stack (TSS). The tcsd daemon is
the only portal to the Trusted Platform Module (TPM), a hardware
chip on the motherboard.
'';
};
user = lib.mkOption {
default = "tss";
type = lib.types.str;
description = "User account under which tcsd runs.";
};
group = lib.mkOption {
default = "tss";
type = lib.types.str;
description = "Group account under which tcsd runs.";
};
stateDir = lib.mkOption {
default = "/var/lib/tpm";
type = lib.types.path;
description = ''
The location of the system persistent storage file.
The system persistent storage file holds keys and data across
restarts of the TCSD and system reboots.
'';
};
firmwarePCRs = lib.mkOption {
default = "0,1,2,3,4,5,6,7";
type = lib.types.str;
description = "PCR indices used in the TPM for firmware measurements.";
};
kernelPCRs = lib.mkOption {
default = "8,9,10,11,12";
type = lib.types.str;
description = "PCR indices used in the TPM for kernel measurements.";
};
platformCred = lib.mkOption {
default = "${cfg.stateDir}/platform.cert";
defaultText = lib.literalExpression ''"''${config.${opt.stateDir}}/platform.cert"'';
type = lib.types.path;
description = ''
Path to the platform credential for your TPM. Your TPM
manufacturer may have provided you with a set of credentials
(certificates) that should be used when creating identities
using your TPM. When a user of your TPM makes an identity,
this credential will be encrypted as part of that process.
See the 1.1b TPM Main specification section 9.3 for information
on this process. '';
};
conformanceCred = lib.mkOption {
default = "${cfg.stateDir}/conformance.cert";
defaultText = lib.literalExpression ''"''${config.${opt.stateDir}}/conformance.cert"'';
type = lib.types.path;
description = ''
Path to the conformance credential for your TPM.
See also the platformCred option'';
};
endorsementCred = lib.mkOption {
default = "${cfg.stateDir}/endorsement.cert";
defaultText = lib.literalExpression ''"''${config.${opt.stateDir}}/endorsement.cert"'';
type = lib.types.path;
description = ''
Path to the endorsement credential for your TPM.
See also the platformCred option'';
};
};
};
###### implementation
config = lib.mkIf cfg.enable {
environment.systemPackages = [ pkgs.trousers ];
services.udev.extraRules = ''
# Give tcsd ownership of all TPM devices
KERNEL=="tpm[0-9]*", MODE="0660", OWNER="${cfg.user}", GROUP="${cfg.group}"
# Tag TPM devices to create a .device unit for tcsd to depend on
ACTION=="add", KERNEL=="tpm[0-9]*", TAG+="systemd"
'';
systemd.tmpfiles.rules = [
# Initialise the state directory
"d ${cfg.stateDir} 0770 ${cfg.user} ${cfg.group} - -"
];
systemd.services.tcsd = {
description = "Manager for Trusted Computing resources";
documentation = [ "man:tcsd(8)" ];
requires = [ "dev-tpm0.device" ];
after = [ "dev-tpm0.device" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
User = cfg.user;
Group = cfg.group;
ExecStart = "${pkgs.trousers}/sbin/tcsd -f -c ${tcsdConf}";
};
};
users.users = lib.optionalAttrs (cfg.user == "tss") {
tss = {
group = "tss";
isSystemUser = true;
};
};
users.groups = lib.optionalAttrs (cfg.group == "tss") { tss = { }; };
};
}

View File

@@ -0,0 +1,67 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.thermald;
in
{
###### interface
options = {
services.thermald = {
enable = lib.mkEnableOption "thermald, the temperature management daemon";
debug = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to enable debug logging.
'';
};
ignoreCpuidCheck = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to ignore the cpuid check to allow running on unsupported platforms";
};
configFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = ''
The thermald manual configuration file.
Leave unspecified to run with the `--adaptive` flag instead which will have thermald use your computer's DPTF adaptive tables.
See `man thermald` for more information.
'';
};
package = lib.mkPackageOption pkgs "thermald" { };
};
};
###### implementation
config = lib.mkIf cfg.enable {
services.dbus.packages = [ cfg.package ];
systemd.services.thermald = {
description = "Thermal Daemon Service";
documentation = [ "man:thermald(8)" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
PrivateNetwork = true;
ExecStart = ''
${cfg.package}/sbin/thermald \
--no-daemon \
${lib.optionalString cfg.debug "--loglevel=debug"} \
${lib.optionalString cfg.ignoreCpuidCheck "--ignore-cpuid-check"} \
${if cfg.configFile != null then "--config-file ${cfg.configFile}" else "--adaptive"} \
--dbus-enable
'';
};
};
};
}

View File

@@ -0,0 +1,308 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.thinkfan;
settingsFormat = pkgs.formats.yaml { };
configFile = settingsFormat.generate "thinkfan.yaml" cfg.settings;
thinkfan = pkgs.thinkfan.override { inherit (cfg) smartSupport; };
# fan-speed and temperature levels
levelType =
with lib.types;
let
tuple =
ts:
lib.mkOptionType {
name = "tuple";
merge = lib.mergeOneOption;
check = xs: lib.all lib.id (lib.zipListsWith (t: x: t.check x) ts xs);
description = "tuple of" + lib.concatMapStrings (t: " (${t.description})") ts;
};
level = ints.unsigned;
special = enum [
"level auto"
"level full-speed"
"level disengaged"
];
in
tuple [
(either level special)
level
level
];
# sensor or fan config
sensorType =
name:
lib.types.submodule {
freeformType = lib.types.attrsOf settingsFormat.type;
options = {
type = lib.mkOption {
type = lib.types.enum [
"hwmon"
"atasmart"
"tpacpi"
"nvml"
];
description = ''
The ${name} type, can be
`hwmon` for standard ${name}s,
`atasmart` to read the temperature via
S.M.A.R.T (requires smartSupport to be enabled),
`tpacpi` for the legacy thinkpac_acpi driver, or
`nvml` for the (proprietary) nVidia driver.
'';
};
query = lib.mkOption {
type = lib.types.str;
description = ''
The query string used to match one or more ${name}s: can be
a fullpath to the temperature file (single ${name}) or a fullpath
to a driver directory (multiple ${name}s).
::: {.note}
When multiple ${name}s match, the query can be restricted using the
{option}`name` or {option}`indices` options.
:::
'';
};
indices = lib.mkOption {
type = with lib.types; nullOr (listOf ints.unsigned);
default = null;
description = ''
A list of ${name}s to pick in case multiple ${name}s match the query.
::: {.note}
Indices start from 0.
:::
'';
};
}
// lib.optionalAttrs (name == "sensor") {
correction = lib.mkOption {
type = with lib.types; nullOr (listOf int);
default = null;
description = ''
A list of values to be added to the temperature of each sensor,
can be used to equalize small discrepancies in temperature ratings.
'';
};
};
};
# removes NixOS special and unused attributes
sensorToConf =
{ type, query, ... }@args:
(lib.filterAttrs (
k: v:
v != null
&& !(lib.elem k [
"type"
"query"
])
) args)
// {
"${type}" = query;
};
syntaxNote = name: ''
::: {.note}
This section slightly departs from the thinkfan.conf syntax.
The type and path must be specified like this:
```
type = "tpacpi";
query = "/proc/acpi/ibm/${name}";
```
instead of a single declaration like:
```
- tpacpi: /proc/acpi/ibm/${name}
```
:::
'';
in
{
options = {
services.thinkfan = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to enable thinkfan, a fan control program.
::: {.note}
This module targets IBM/Lenovo thinkpads by default, for
other hardware you will have configure it more carefully.
:::
'';
relatedPackages = [ "thinkfan" ];
};
smartSupport = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to build thinkfan with S.M.A.R.T. support to read temperatures
directly from hard disks.
'';
};
sensors = lib.mkOption {
type = lib.types.listOf (sensorType "sensor");
default = [
{
type = "tpacpi";
query = "/proc/acpi/ibm/thermal";
}
];
description = ''
List of temperature sensors thinkfan will monitor.
${syntaxNote "thermal"}
'';
};
fans = lib.mkOption {
type = lib.types.listOf (sensorType "fan");
default = [
{
type = "tpacpi";
query = "/proc/acpi/ibm/fan";
}
];
description = ''
List of fans thinkfan will control.
${syntaxNote "fan"}
'';
};
levels = lib.mkOption {
type = lib.types.listOf levelType;
default = [
[
0
0
55
]
[
1
48
60
]
[
2
50
61
]
[
3
52
63
]
[
6
56
65
]
[
7
60
85
]
[
"level auto"
80
32767
]
];
description = ''
[LEVEL LOW HIGH]
LEVEL is the fan level to use: it can be an integer (0-7 with thinkpad_acpi),
"level auto" (to keep the default firmware behavior), "level full-speed" or
"level disengaged" (to run the fan as fast as possible).
LOW is the temperature at which to step down to the previous level.
HIGH is the temperature at which to step up to the next level.
All numbers are integers.
'';
};
extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [
"-b"
"0"
];
description = ''
A list of extra command line arguments to pass to thinkfan.
Check the {manpage}`thinkfan(1)` manpage for available arguments.
'';
};
settings = lib.mkOption {
type = lib.types.attrsOf settingsFormat.type;
default = { };
description = ''
Thinkfan settings. Use this option to configure thinkfan
settings not exposed in a NixOS option or to bypass one.
Before changing this, read the {manpage}`thinkfan.conf(5)`
manpage and take a look at the example config file at
<https://github.com/vmatare/thinkfan/blob/master/examples/thinkfan.yaml>
'';
};
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ thinkfan ];
services.thinkfan.settings = lib.mapAttrs (k: v: lib.mkDefault v) {
sensors = map sensorToConf cfg.sensors;
fans = map sensorToConf cfg.fans;
levels = cfg.levels;
};
systemd.packages = [ thinkfan ];
systemd.services = {
thinkfan.environment.THINKFAN_ARGS = lib.escapeShellArgs (
[
"-c"
configFile
]
++ cfg.extraArgs
);
thinkfan.serviceConfig = {
Restart = "on-failure";
RestartSec = "30s";
# Hardening
PrivateNetwork = true;
};
# must be added manually, see issue #81138
thinkfan.wantedBy = [ "multi-user.target" ];
thinkfan-wakeup.wantedBy = [ "sleep.target" ];
thinkfan-sleep.wantedBy = [ "sleep.target" ];
};
boot.extraModprobeConfig = "options thinkpad_acpi experimental=1 fan_control=1";
};
}

View File

@@ -0,0 +1,40 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.throttled;
in
{
options = {
services.throttled = {
enable = lib.mkEnableOption "fix for Intel CPU throttling";
extraConfig = lib.mkOption {
type = lib.types.str;
default = "";
description = "Alternative configuration";
};
};
};
config = lib.mkIf cfg.enable {
systemd.packages = [ pkgs.throttled ];
# The upstream package has this in Install, but that's not enough, see the NixOS manual
systemd.services.throttled.wantedBy = [ "multi-user.target" ];
environment.etc."throttled.conf".source =
if cfg.extraConfig != "" then
pkgs.writeText "throttled.conf" cfg.extraConfig
else
"${pkgs.throttled}/etc/throttled.conf";
hardware.cpu.x86.msr.enable = true;
# Kernel 5.9 spams warnings whenever userspace writes to CPU MSRs.
# See https://github.com/erpalma/throttled/issues/215
hardware.cpu.x86.msr.settings.allow-writes =
lib.mkIf (lib.versionAtLeast config.boot.kernelPackages.kernel.version "5.9") "on";
};
}

View File

@@ -0,0 +1,147 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.tlp;
enableRDW = config.networking.networkmanager.enable;
# TODO: Use this for having proper parameters in the future
mkTlpConfig =
tlpConfig:
lib.generators.toKeyValue {
mkKeyValue = lib.generators.mkKeyValueDefault {
mkValueString = val: if lib.isList val then "\"" + (toString val) + "\"" else toString val;
} "=";
} tlpConfig;
in
{
###### interface
options = {
services.tlp = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to enable the TLP power management daemon.";
};
settings = lib.mkOption {
type =
with lib.types;
attrsOf (oneOf [
bool
int
float
str
(listOf str)
]);
default = { };
example = {
SATA_LINKPWR_ON_BAT = "med_power_with_dipm";
USB_BLACKLIST_PHONE = 1;
};
description = ''
Options passed to TLP. See <https://linrunner.de/tlp> for all supported options..
'';
};
extraConfig = lib.mkOption {
type = lib.types.lines;
default = "";
description = ''
Verbatim additional configuration variables for TLP.
DEPRECATED: use services.tlp.settings instead.
'';
};
package = lib.mkOption {
type = lib.types.package;
default = pkgs.tlp.override { inherit enableRDW; };
defaultText = "pkgs.tlp.override { enableRDW = config.networking.networkmanager.enable; }";
description = "The tlp package to use.";
};
};
};
###### implementation
config = lib.mkIf cfg.enable {
hardware.cpu.x86.msr.enable = true;
warnings = lib.optional (cfg.extraConfig != "") ''
Using config.services.tlp.extraConfig is deprecated and will become unsupported in a future release. Use config.services.tlp.settings instead.
'';
assertions = [
{
assertion = cfg.enable -> config.powerManagement.scsiLinkPolicy == null;
message = ''
`services.tlp.enable` and `config.powerManagement.scsiLinkPolicy` cannot be set both.
Set `services.tlp.settings.SATA_LINKPWR_ON_AC` and `services.tlp.settings.SATA_LINKPWR_ON_BAT` instead.
'';
}
];
environment.etc = {
"tlp.conf".text = (mkTlpConfig cfg.settings) + cfg.extraConfig;
}
// lib.optionalAttrs enableRDW {
"NetworkManager/dispatcher.d/99tlp-rdw-nm".source =
"${cfg.package}/lib/NetworkManager/dispatcher.d/99tlp-rdw-nm";
};
environment.systemPackages = [ cfg.package ];
services.tlp.settings =
let
cfg = config.powerManagement;
maybeDefault = val: lib.mkIf (val != null) (lib.mkDefault val);
in
{
CPU_SCALING_GOVERNOR_ON_AC = maybeDefault cfg.cpuFreqGovernor;
CPU_SCALING_GOVERNOR_ON_BAT = maybeDefault cfg.cpuFreqGovernor;
CPU_SCALING_MIN_FREQ_ON_AC = maybeDefault cfg.cpufreq.min;
CPU_SCALING_MAX_FREQ_ON_AC = maybeDefault cfg.cpufreq.max;
CPU_SCALING_MIN_FREQ_ON_BAT = maybeDefault cfg.cpufreq.min;
CPU_SCALING_MAX_FREQ_ON_BAT = maybeDefault cfg.cpufreq.max;
};
services.udev.packages = [ cfg.package ];
systemd = {
# use native tlp instead because it can also differentiate between AC/BAT
services.cpufreq.enable = false;
packages = [ cfg.package ];
# XXX: These must always be disabled/masked according to [1].
#
# [1]: https://github.com/linrunner/TLP/blob/a9ada09e0821f275ce5f93dc80a4d81a7ff62ae4/tlp-stat.in#L319
sockets.systemd-rfkill.enable = false;
services.systemd-rfkill.enable = false;
services.tlp = {
# XXX: The service should reload whenever the configuration changes,
# otherwise newly set power options remain inactive until reboot (or
# manual unit restart.)
restartTriggers = [ config.environment.etc."tlp.conf".source ];
# XXX: When using systemd.packages (which we do above) the [Install]
# section of systemd units does not work (citation needed) so we manually
# enforce it here.
wantedBy = [ "multi-user.target" ];
};
services.tlp-sleep = {
# XXX: When using systemd.packages (which we do above) the [Install]
# section of systemd units does not work (citation needed) so we manually
# enforce it here.
before = [ "sleep.target" ];
wantedBy = [ "sleep.target" ];
# XXX: `tlp suspend` requires /var/lib/tlp to exist in order to save
# some stuff in there. There is no way, that I know of, to do this in
# the package itself, so we do it here instead making sure the unit
# won't fail due to the save dir not existing.
serviceConfig.StateDirectory = "tlp";
};
};
};
}

View File

@@ -0,0 +1,17 @@
# Trezor {#trezor}
Trezor is an open-source cryptocurrency hardware wallet and security token
allowing secure storage of private keys.
It offers advanced features such U2F two-factor authorization, SSH login
through
[Trezor SSH agent](https://wiki.trezor.io/Apps:SSH_agent),
[GPG](https://wiki.trezor.io/GPG) and a
[password manager](https://wiki.trezor.io/Trezor_Password_Manager).
For more information, guides and documentation, see <https://wiki.trezor.io>.
To enable Trezor support, add the following to your {file}`configuration.nix`:
services.trezord.enable = true;
This will add all necessary udev rules and start Trezor Bridge.

View File

@@ -0,0 +1,73 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.trezord;
in
{
### docs
meta = {
doc = ./trezord.md;
};
### interface
options = {
services.trezord = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Enable Trezor bridge daemon, for use with Trezor hardware bitcoin wallets.
'';
};
emulator.enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Enable Trezor emulator support.
'';
};
emulator.port = lib.mkOption {
type = lib.types.port;
default = 21324;
description = ''
Listening port for the Trezor emulator.
'';
};
};
};
### implementation
config = lib.mkIf cfg.enable {
services.udev.packages = [ pkgs.trezor-udev-rules ];
systemd.services.trezord = {
description = "Trezor Bridge";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
path = [ ];
serviceConfig = {
Type = "simple";
ExecStart = "${pkgs.trezord}/bin/trezord-go ${lib.optionalString cfg.emulator.enable "-e ${builtins.toString cfg.emulator.port}"}";
User = "trezord";
};
};
users.users.trezord = {
group = "trezord";
description = "Trezor bridge daemon user";
isSystemUser = true;
};
users.groups.trezord = { };
};
}

View File

@@ -0,0 +1,146 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.triggerhappy;
socket = "/run/thd.socket";
configFile = pkgs.writeText "triggerhappy.conf" ''
${lib.concatMapStringsSep "\n" (
{
keys,
event,
cmd,
...
}:
''${lib.concatMapStringsSep "+" (x: "KEY_" + x) keys} ${
toString
{
press = 1;
hold = 2;
release = 0;
}
.${event}
} ${cmd}''
) cfg.bindings}
${cfg.extraConfig}
'';
bindingCfg =
{ ... }:
{
options = {
keys = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "List of keys to match. Key names as defined in linux/input-event-codes.h";
};
event = lib.mkOption {
type = lib.types.enum [
"press"
"hold"
"release"
];
default = "press";
description = "Event to match.";
};
cmd = lib.mkOption {
type = lib.types.str;
description = "What to run.";
};
};
};
in
{
###### interface
options = {
services.triggerhappy = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to enable the {command}`triggerhappy` hotkey daemon.
'';
};
user = lib.mkOption {
type = lib.types.str;
default = "nobody";
example = "root";
description = ''
User account under which {command}`triggerhappy` runs.
'';
};
bindings = lib.mkOption {
type = lib.types.listOf (lib.types.submodule bindingCfg);
default = [ ];
example = lib.literalExpression ''
[ { keys = ["PLAYPAUSE"]; cmd = "''${lib.getExe pkgs.mpc} -q toggle"; } ]
'';
description = ''
Key bindings for {command}`triggerhappy`.
'';
};
extraConfig = lib.mkOption {
type = lib.types.lines;
default = "";
description = ''
Literal contents to append to the end of {command}`triggerhappy` configuration file.
'';
};
};
};
###### implementation
config = lib.mkIf cfg.enable {
systemd.sockets.triggerhappy = {
description = "Triggerhappy Socket";
wantedBy = [ "sockets.target" ];
socketConfig.ListenDatagram = socket;
};
systemd.services.triggerhappy = {
wantedBy = [ "multi-user.target" ];
description = "Global hotkey daemon";
documentation = [ "man:thd(1)" ];
serviceConfig = {
ExecStart = "${pkgs.triggerhappy}/bin/thd ${
lib.optionalString (cfg.user != "root") "--user ${cfg.user}"
} --socket ${socket} --triggers ${configFile} --deviceglob /dev/input/event*";
};
};
services.udev.packages = lib.singleton (
pkgs.writeTextFile {
name = "triggerhappy-udev-rules";
destination = "/etc/udev/rules.d/61-triggerhappy.rules";
text = ''
ACTION=="add", SUBSYSTEM=="input", KERNEL=="event[0-9]*", ATTRS{name}!="triggerhappy", \
RUN+="${pkgs.triggerhappy}/bin/th-cmd --socket ${socket} --passfd --udev"
'';
}
);
};
}

View File

@@ -0,0 +1,257 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.tuned;
moduleFromName = name: lib.getAttrFromPath (lib.splitString "." name) config;
settingsFormat = pkgs.formats.iniWithGlobalSection { };
profileFormat = pkgs.formats.ini { };
ppdSettingsFormat = pkgs.formats.ini { };
settingsSubmodule = {
freeformType = settingsFormat.type;
options = {
daemon = lib.mkEnableOption "the use of a daemon for TuneD" // {
default = true;
};
dynamic_tuning = lib.mkEnableOption "dynamic tuning";
sleep_interval = lib.mkOption {
type = lib.types.int;
default = 1;
description = "Interval in which the TuneD daemon is waken up and checks for events (in seconds).";
};
update_interval = lib.mkOption {
type = lib.types.int;
default = 10;
description = "Update interval for dynamic tuning (in seconds).";
};
recommend_command = lib.mkEnableOption "recommend functionality" // {
default = true;
};
reapply_sysctl =
lib.mkEnableOption "the reapplying of global sysctls after TuneD sysctls are applied"
// {
default = true;
};
default_instance_priority = lib.mkOption {
type = lib.types.int;
default = 0;
description = "Default instance (unit) priority.";
};
profile_dirs = lib.mkOption {
type = lib.types.str;
default = "/etc/tuned/profiles";
# Ensure we always have the vendored profiles available
apply = dirs: "${cfg.package}/lib/tuned/profiles," + dirs;
description = "Directories to search for profiles, separated by `,` or `;`.";
};
};
};
ppdSettingsSubmodule = {
freeformType = ppdSettingsFormat.type;
options = {
main = lib.mkOption {
type = lib.types.submodule {
options = {
default = lib.mkOption {
type = lib.types.str;
default = "balanced";
description = "Default PPD profile.";
example = "performance";
};
battery_detection = lib.mkEnableOption "battery detection" // {
default = true;
};
};
};
default = { };
description = "Core configuration for power-profiles-daemon support.";
};
profiles = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = {
power-saver = "powersave";
balanced = "balanced";
performance = "throughput-performance";
};
description = "Map of PPD profiles to native TuneD profiles.";
};
battery = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = {
balanced = "balanced-battery";
};
description = "Map of PPD battery states to TuneD profiles.";
};
};
};
in
{
options.services.tuned = {
enable = lib.mkEnableOption "TuneD";
package = lib.mkPackageOption pkgs "tuned" { };
settings = lib.mkOption {
type = lib.types.submodule settingsSubmodule;
default = { };
description = ''
Configuration for TuneD.
See {manpage}`tuned-main.conf(5)`.
'';
};
profiles = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule {
freeformType = profileFormat.type;
}
);
default = { };
description = ''
Profiles for TuneD.
See {manpage}`tuned.conf(5)`.
'';
example = {
my-cool-profile = {
main.include = "my-other-cool-profile";
my_sysctl = {
type = "sysctl";
replace = true;
"net.core.rmem_default" = 262144;
"net.core.wmem_default" = 262144;
};
};
};
};
ppdSupport = lib.mkEnableOption "translation of power-profiles-daemon API calls to TuneD" // {
default = true;
};
ppdSettings = lib.mkOption {
type = lib.types.submodule ppdSettingsSubmodule;
default = { };
description = ''
Settings for TuneD's power-profiles-daemon compatibility service.
'';
};
};
config = lib.mkIf cfg.enable {
assertions = [
# From `tuned.service`
{
assertion = config.security.polkit.enable;
message = "`services.tuned` requires `security.polkit` to be enabled.";
}
{
assertion = cfg.settings.dynamic_tuning -> cfg.settings.daemon;
message = "`services.tuned.settings.dynamic_tuning` requires `services.tuned.settings.daemon` to be `true`.";
}
{
assertion = cfg.ppdSupport -> config.services.upower.enable;
message = "`services.tuned.ppdSupport` requires `services.upower` to be enabled.";
}
]
# Declare service conflicts, also sourced from `tuned.service`
++
map
(name: {
assertion = !(moduleFromName name).enable;
message = "`services.tuned` conflicts with `${name}`.";
})
[
"services.auto-cpufreq"
"services.power-profiles-daemon"
"services.tlp"
];
environment = {
etc = lib.mkMerge [
{
"tuned/tuned-main.conf".source = settingsFormat.generate "tuned-main.conf" {
sections = { };
globalSection = cfg.settings;
};
"tuned/ppd.conf".source = lib.mkIf cfg.ppdSupport (
ppdSettingsFormat.generate "ppd.conf" cfg.ppdSettings
);
}
(lib.mapAttrs' (
name: value:
lib.nameValuePair "tuned/profiles/${name}/tuned.conf" {
source = profileFormat.generate "tuned.conf" value;
}
) cfg.profiles)
];
systemPackages = [ cfg.package ];
};
security.polkit.enable = lib.mkDefault true;
services = {
dbus.packages = [ cfg.package ];
# Many DEs (like GNOME and KDE Plasma) enable PPD by default
# Let's try to make it easier to transition by only enabling this module
power-profiles-daemon.enable = false;
# NOTE: Required by `tuned-ppd` for handling power supply changes
# (i.e., `services.tuned.ppdSettings.main.battery_detection`)
# https://github.com/NixOS/nixpkgs/issues/431105
upower.enable = lib.mkIf cfg.ppdSupport true;
};
systemd = {
packages = [ cfg.package ];
services = {
tuned = {
wantedBy = [ "multi-user.target" ];
};
tuned-ppd = lib.mkIf cfg.ppdSupport {
wantedBy = [ "graphical.target" ];
};
};
tmpfiles = {
packages = [ cfg.package ];
# NOTE(@getchoo): `cfg.package` should contain a `tuned.conf` for tmpfiles.d already. Avoid a naming conflict!
settings.tuned-profiles = {
# Required for tuned-gui
"/etc/tuned/profiles".d = { };
};
};
};
};
}

View File

@@ -0,0 +1,53 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.hardware.tuxedo-rs;
in
{
options = {
hardware.tuxedo-rs = {
enable = lib.mkEnableOption "Rust utilities for interacting with hardware from TUXEDO Computers";
tailor-gui.enable = lib.mkEnableOption "tailor-gui, an alternative to TUXEDO Control Center, written in Rust";
};
};
config = lib.mkIf cfg.enable (
lib.mkMerge [
{
hardware.tuxedo-drivers.enable = true;
systemd = {
services.tailord = {
enable = true;
description = "Tuxedo Tailor hardware control service";
after = [ "systemd-logind.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "dbus";
BusName = "com.tux.Tailor";
ExecStart = "${pkgs.tuxedo-rs}/bin/tailord";
Environment = "RUST_BACKTRACE=1";
Restart = "on-failure";
};
};
};
services.dbus.packages = [ pkgs.tuxedo-rs ];
environment.systemPackages = [ pkgs.tuxedo-rs ];
}
(lib.mkIf cfg.tailor-gui.enable {
environment.systemPackages = [ pkgs.tailor-gui ];
})
]
);
meta.maintainers = with lib.maintainers; [ mrcjkb ];
}

View File

@@ -0,0 +1,542 @@
{
config,
lib,
pkgs,
...
}:
let
udev = config.systemd.package;
cfg = config.services.udev;
initrdUdevRules = pkgs.runCommand "initrd-udev-rules" { } ''
mkdir -p $out/etc/udev/rules.d
for f in 60-cdrom_id 60-persistent-storage 75-net-description 80-drivers 80-net-setup-link; do
ln -s ${config.boot.initrd.systemd.package}/lib/udev/rules.d/$f.rules $out/etc/udev/rules.d
done
'';
extraUdevRules = pkgs.writeTextFile {
name = "extra-udev-rules";
text = cfg.extraRules;
destination = "/etc/udev/rules.d/99-local.rules";
};
extraHwdbFile = pkgs.writeTextFile {
name = "extra-hwdb-file";
text = cfg.extraHwdb;
destination = "/etc/udev/hwdb.d/99-local.hwdb";
};
nixosRules = ''
# Needed for gpm.
SUBSYSTEM=="input", KERNEL=="mice", TAG+="systemd"
'';
nixosInitrdRules = ''
# Mark dm devices as db_persist so that they are kept active after switching root
SUBSYSTEM=="block", KERNEL=="dm-[0-9]*", ACTION=="add|change", OPTIONS+="db_persist"
'';
# Perform substitutions in all udev rules files.
udevRulesFor =
{
name,
udevPackages,
udevPath,
udev,
systemd,
binPackages,
initrdBin ? null,
}:
pkgs.runCommand name
{
preferLocalBuild = true;
allowSubstitutes = false;
packages = lib.unique (map toString udevPackages);
nativeBuildInputs = [
# We only include the out output here to avoid needing to include all
# other outputs in the installer tests as well
# We only need the udevadm command anyway
pkgs.buildPackages.systemdMinimal.out
];
}
''
mkdir -p $out
shopt -s nullglob
set +o pipefail
# Set a reasonable $PATH for programs called by udev rules.
echo 'ENV{PATH}="${udevPath}/bin:${udevPath}/sbin"' > $out/00-path.rules
# Add the udev rules from other packages.
for i in $packages; do
echo "Adding rules for package $i"
for j in $i/{etc,lib}/udev/rules.d/*; do
echo "Copying $j to $out/$(basename $j)"
cat $j > $out/$(basename $j)
done
done
# Fix some paths in the standard udev rules. Hacky.
for i in $out/*.rules; do
substituteInPlace $i \
--replace-quiet \"/sbin/modprobe \"${pkgs.kmod}/bin/modprobe \
--replace-quiet \"/sbin/mdadm \"${pkgs.mdadm}/sbin/mdadm \
--replace-quiet \"/sbin/blkid \"${pkgs.util-linux}/sbin/blkid \
--replace-quiet \"/bin/mount \"${pkgs.util-linux}/bin/mount \
--replace-quiet /usr/bin/readlink ${pkgs.coreutils}/bin/readlink \
--replace-quiet /usr/bin/cat ${pkgs.coreutils}/bin/cat \
--replace-quiet /usr/bin/basename ${pkgs.coreutils}/bin/basename 2>/dev/null
${lib.optionalString (initrdBin != null) ''
substituteInPlace $i --replace-quiet '/run/current-system/systemd' "${lib.removeSuffix "/bin" initrdBin}"
''}
done
echo -n "Checking that all programs called by relative paths in udev rules exist in ${udev}/lib/udev... "
import_progs=$(grep 'IMPORT{program}="[^/$]' $out/* |
sed -e 's/.*IMPORT{program}="\([^ "]*\)[ "].*/\1/' | uniq)
run_progs=$(grep -v '^[[:space:]]*#' $out/* | grep 'RUN+="[^/$]' |
sed -e 's/.*RUN+="\([^ "]*\)[ "].*/\1/' | uniq)
for i in $import_progs $run_progs; do
if [[ ! -x ${udev}/lib/udev/$i && ! $i =~ socket:.* ]]; then
echo "FAIL"
echo "$i is called in udev rules but not installed by udev"
exit 1
fi
done
echo "OK"
echo -n "Checking that all programs called by absolute paths in udev rules exist... "
import_progs=$(grep 'IMPORT{program}="/' $out/* |
sed -e 's/.*IMPORT{program}="\([^ "]*\)[ "].*/\1/' | uniq)
run_progs=$(grep -v '^[[:space:]]*#' $out/* | grep 'RUN+="/' |
sed -e 's/.*RUN+="\([^ "]*\)[ "].*/\1/' | uniq)
for i in $import_progs $run_progs; do
# if the path refers to /run/current-system/systemd, replace with config.systemd.package
if [[ $i == /run/current-system/systemd* ]]; then
i="${systemd}/''${i#/run/current-system/systemd/}"
fi
if [[ ! -x $i ]]; then
echo "FAIL"
echo "$i is called in udev rules but is not executable or does not exist"
exit 1
fi
done
echo "OK"
filesToFixup="$(for i in "$out"/*; do
# list all files referring to (/usr)/bin paths, but allow references to /bin/sh.
grep -P -l '\B(?!\/bin\/sh\b)(\/usr)?\/bin(?:\/.*)?' "$i" || :
done)"
if [ -n "$filesToFixup" ]; then
echo "Consider fixing the following udev rules:"
echo "$filesToFixup" | while read localFile; do
remoteFile="origin unknown"
for i in ${toString binPackages}; do
for j in "$i"/*/udev/rules.d/*; do
[ -e "$out/$(basename "$j")" ] || continue
[ "$(basename "$j")" = "$(basename "$localFile")" ] || continue
remoteFile="originally from $j"
break 2
done
done
refs="$(
grep -o '\B\(/usr\)\?/s\?bin/[^ "]\+' "$localFile" \
| sed -e ':r;N;''${s/\n/ and /;br};s/\n/, /g;br'
)"
echo "$localFile ($remoteFile) contains references to $refs."
done
exit 1
fi
# Verify all the udev rules
echo "Verifying udev rules using udevadm verify..."
udevadm verify --resolve-names=never --no-style $out
echo "OK"
# If auto-configuration is disabled, then remove
# udev's 80-drivers.rules file, which contains rules for
# automatically calling modprobe.
${lib.optionalString (!config.boot.hardwareScan) ''
ln -s /dev/null $out/80-drivers.rules
''}
'';
hwdbBin =
pkgs.runCommand "hwdb.bin"
{
preferLocalBuild = true;
allowSubstitutes = false;
packages = lib.unique (map toString ([ udev ] ++ cfg.packages));
}
''
mkdir -p etc/udev/hwdb.d
for i in $packages; do
echo "Adding hwdb files for package $i"
for j in $i/{etc,lib}/udev/hwdb.d/*; do
ln -s $j etc/udev/hwdb.d/$(basename $j)
done
done
echo "Generating hwdb database..."
# hwdb --update doesn't return error code even on errors!
res="$(${pkgs.buildPackages.systemd}/bin/systemd-hwdb --root=$(pwd) update 2>&1)"
echo "$res"
[ -z "$(echo "$res" | egrep '^Error')" ]
mv etc/udev/hwdb.bin $out
'';
compressFirmware =
firmware:
if
config.hardware.firmwareCompression == "none" || (firmware.compressFirmware or true) == false
then
firmware
else if config.hardware.firmwareCompression == "zstd" then
pkgs.compressFirmwareZstd firmware
else
pkgs.compressFirmwareXz firmware;
# Udev has a 512-character limit for ENV{PATH}, so create a symlink
# tree to work around this.
udevPath = pkgs.buildEnv {
name = "udev-path";
paths = cfg.path;
pathsToLink = [
"/bin"
"/sbin"
];
ignoreCollisions = true;
};
in
{
###### interface
options = {
boot.hardwareScan = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Whether to try to load kernel modules for all detected hardware.
Usually this does a good job of providing you with the modules
you need, but sometimes it can crash the system or cause other
nasty effects.
'';
};
services.udev = {
enable = lib.mkEnableOption "udev, a device manager for the Linux kernel" // {
default = true;
};
packages = lib.mkOption {
type = lib.types.listOf lib.types.path;
default = [ ];
description = ''
List of packages containing {command}`udev` rules.
All files found in
{file}`«pkg»/etc/udev/rules.d` and
{file}`«pkg»/lib/udev/rules.d`
will be included.
'';
apply = map lib.getBin;
};
path = lib.mkOption {
type = lib.types.listOf lib.types.path;
default = [ ];
description = ''
Packages added to the {env}`PATH` environment variable when
executing programs from Udev rules.
coreutils, gnu{sed,grep}, util-linux and config.systemd.package are
automatically included.
'';
};
extraRules = lib.mkOption {
default = "";
example = ''
ENV{ID_VENDOR_ID}=="046d", ENV{ID_MODEL_ID}=="0825", ENV{PULSE_IGNORE}="1"
'';
type = lib.types.lines;
description = ''
Additional {command}`udev` rules. They'll be written
into file {file}`99-local.rules`. Thus they are
read and applied after all other rules.
'';
};
extraHwdb = lib.mkOption {
default = "";
example = ''
evdev:input:b0003v05AFp8277*
KEYBOARD_KEY_70039=leftalt
KEYBOARD_KEY_700e2=leftctrl
'';
type = lib.types.lines;
description = ''
Additional {command}`hwdb` files. They'll be written
into file {file}`99-local.hwdb`. Thus they are
read after all other files.
'';
};
};
hardware.firmware = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = [ ];
description = ''
List of packages containing firmware files. Such files
will be loaded automatically if the kernel asks for them
(i.e., when it has detected specific hardware that requires
firmware to function). If multiple packages contain firmware
files with the same name, the first package in the list takes
precedence. Note that you must rebuild your system if you add
files to any of these directories.
'';
apply =
list:
pkgs.buildEnv {
name = "firmware";
paths = map compressFirmware list;
pathsToLink = [ "/lib/firmware" ];
ignoreCollisions = true;
};
};
hardware.firmwareCompression = lib.mkOption {
type = lib.types.enum [
"xz"
"zstd"
"none"
];
default =
if config.boot.kernelPackages.kernelAtLeast "5.19" then
"zstd"
else if config.boot.kernelPackages.kernelAtLeast "5.3" then
"xz"
else
"none";
defaultText = "auto";
description = ''
Whether to compress firmware files.
Defaults depend on the kernel version.
For kernels older than 5.3, firmware files are not compressed.
For kernels 5.3 and newer, firmware files are compressed with xz.
For kernels 5.19 and newer, firmware files are compressed with zstd.
'';
};
networking.usePredictableInterfaceNames = lib.mkOption {
default = true;
type = lib.types.bool;
description = ''
Whether to assign [predictable names to network interfaces](https://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/).
If enabled, interfaces
are assigned names that contain topology information
(e.g. `wlp3s0`) and thus should be stable
across reboots. If disabled, names depend on the order in
which interfaces are discovered by the kernel, which may
change randomly across reboots; for instance, you may find
`eth0` and `eth1` flipping
unpredictably.
'';
};
boot.initrd.services.udev = {
packages = lib.mkOption {
type = lib.types.listOf lib.types.path;
default = [ ];
description = ''
*This will only be used when systemd is used in stage 1.*
List of packages containing {command}`udev` rules that will be copied to stage 1.
All files found in
{file}`«pkg»/etc/udev/rules.d` and
{file}`«pkg»/lib/udev/rules.d`
will be included.
'';
};
binPackages = lib.mkOption {
type = lib.types.listOf lib.types.path;
default = [ ];
description = ''
*This will only be used when systemd is used in stage 1.*
Packages to search for binaries that are referenced by the udev rules in stage 1.
This list always contains /bin of the initrd.
'';
apply = map lib.getBin;
};
rules = lib.mkOption {
default = "";
example = ''
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:1D:60:B9:6D:4F", KERNEL=="eth*", NAME="my_fast_network_card"
'';
type = lib.types.lines;
description = ''
{command}`udev` rules to include in the initrd
*only*. They'll be written into file
{file}`99-local.rules`. Thus they are read and applied
after the essential initrd rules.
'';
};
};
};
###### implementation
config = lib.mkIf cfg.enable {
assertions = [
{
assertion =
config.hardware.firmwareCompression == "zstd" -> config.boot.kernelPackages.kernelAtLeast "5.19";
message = ''
The firmware compression method is set to zstd, but the kernel version is too old.
The kernel version must be at least 5.3 to use zstd compression.
'';
}
{
assertion =
config.hardware.firmwareCompression == "xz" -> config.boot.kernelPackages.kernelAtLeast "5.3";
message = ''
The firmware compression method is set to xz, but the kernel version is too old.
The kernel version must be at least 5.3 to use xz compression.
'';
}
];
services.udev.extraRules = nixosRules;
services.udev.packages = [
extraUdevRules
extraHwdbFile
];
services.udev.path = [
pkgs.coreutils
pkgs.gnused
pkgs.gnugrep
pkgs.util-linux
udev
];
boot.kernelParams = lib.mkIf (!config.networking.usePredictableInterfaceNames) [ "net.ifnames=0" ];
boot.initrd.extraUdevRulesCommands =
lib.mkIf (!config.boot.initrd.systemd.enable && config.boot.initrd.services.udev.rules != "")
''
cat <<'EOF' > $out/99-local.rules
${config.boot.initrd.services.udev.rules}
EOF
'';
boot.initrd.services.udev.rules = nixosInitrdRules;
boot.initrd.systemd.additionalUpstreamUnits = [
"initrd-udevadm-cleanup-db.service"
"systemd-udevd-control.socket"
"systemd-udevd-kernel.socket"
"systemd-udevd.service"
"systemd-udev-settle.service"
"systemd-udev-trigger.service"
];
boot.initrd.systemd.storePaths = [
"${config.boot.initrd.systemd.package}/lib/systemd/systemd-udevd"
"${config.boot.initrd.systemd.package}/lib/udev/ata_id"
"${config.boot.initrd.systemd.package}/lib/udev/cdrom_id"
"${config.boot.initrd.systemd.package}/lib/udev/scsi_id"
"${config.boot.initrd.systemd.package}/lib/udev/rules.d"
]
++ map (x: "${x}/bin") config.boot.initrd.services.udev.binPackages;
# Generate the udev rules for the initrd
boot.initrd.systemd.contents = {
"/etc/udev/rules.d".source = udevRulesFor {
name = "initrd-udev-rules";
initrdBin = config.boot.initrd.systemd.contents."/bin".source;
udevPackages = config.boot.initrd.services.udev.packages;
udevPath = config.boot.initrd.systemd.contents."/bin".source;
udev = config.boot.initrd.systemd.package;
systemd = config.boot.initrd.systemd.package;
binPackages = config.boot.initrd.services.udev.binPackages ++ [
config.boot.initrd.systemd.contents."/bin".source
];
};
};
# Insert initrd rules
boot.initrd.services.udev.packages = [
initrdUdevRules
(lib.mkIf (config.boot.initrd.services.udev.rules != "") (
pkgs.writeTextFile {
name = "initrd-udev-rules";
destination = "/etc/udev/rules.d/99-local.rules";
text = config.boot.initrd.services.udev.rules;
}
))
];
environment.etc = {
"udev/rules.d".source = udevRulesFor {
name = "udev-rules";
udevPackages = cfg.packages;
systemd = config.systemd.package;
binPackages = cfg.packages;
inherit udevPath udev;
};
"udev/hwdb.bin".source = hwdbBin;
}
// lib.optionalAttrs config.boot.modprobeConfig.enable {
# We don't place this into `extraModprobeConfig` so that stage-1 ramdisk doesn't bloat.
"modprobe.d/firmware.conf".text =
"options firmware_class path=${config.hardware.firmware}/lib/firmware";
};
system.requiredKernelConfig = with config.lib.kernelConfig; [
(isEnabled "UNIX")
(isYes "INOTIFY_USER")
(isYes "NET")
];
system.activationScripts.udevd = lib.mkIf config.boot.kernel.enable ''
# The deprecated hotplug uevent helper is not used anymore
if [ -e /proc/sys/kernel/hotplug ]; then
echo "" > /proc/sys/kernel/hotplug
fi
# Allow the kernel to find our firmware.
if [ -e /sys/module/firmware_class/parameters/path ]; then
echo -n "${config.hardware.firmware}/lib/firmware" > /sys/module/firmware_class/parameters/path
fi
'';
systemd.services.systemd-udevd = {
restartTriggers = [ config.environment.etc."udev/rules.d".source ];
notSocketActivated = true;
stopIfChanged = false;
};
};
imports = [
(lib.mkRenamedOptionModule
[ "services" "udev" "initrdRules" ]
[ "boot" "initrd" "services" "udev" "rules" ]
)
];
}

View File

@@ -0,0 +1,115 @@
# Udisks daemon.
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.udisks2;
settingsFormat = pkgs.formats.ini {
listToValue = lib.concatMapStringsSep "," (lib.generators.mkValueStringDefault { });
};
configFiles = lib.mapAttrs (name: value: (settingsFormat.generate name value)) (
lib.mapAttrs' (name: value: lib.nameValuePair name value) config.services.udisks2.settings
);
in
{
###### interface
options = {
services.udisks2 = {
enable = lib.mkEnableOption "udisks2, a DBus service that allows applications to query and manipulate storage devices";
package = lib.mkPackageOption pkgs "udisks2" { };
mountOnMedia = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
When enabled, instructs udisks2 to mount removable drives under `/media/` directory, instead of the
default, ACL-controlled `/run/media/$USER/`. Since `/media/` is not mounted as tmpfs by default, it
requires cleanup to get rid of stale mountpoints; enabling this option will take care of this at boot.
'';
};
settings = lib.mkOption rec {
type = lib.types.attrsOf settingsFormat.type;
apply = lib.recursiveUpdate default;
default = {
"udisks2.conf" = {
udisks2 = {
modules = [ "*" ];
modules_load_preference = "ondemand";
};
defaults = {
encryption = "luks2";
};
};
};
example = lib.literalExpression ''
{
"WDC-WD10EZEX-60M2NA0-WD-WCC3F3SJ0698.conf" = {
ATA = {
StandbyTimeout = 50;
};
};
};
'';
description = ''
Options passed to udisksd.
See [here](http://manpages.ubuntu.com/manpages/latest/en/man5/udisks2.conf.5.html) and
drive configuration in [here](http://manpages.ubuntu.com/manpages/latest/en/man8/udisks.8.html) for supported options.
'';
};
};
};
###### implementation
config = lib.mkIf config.services.udisks2.enable {
environment.systemPackages = [ cfg.package ];
environment.etc =
(lib.mapAttrs' (name: value: lib.nameValuePair "udisks2/${name}" { source = value; }) configFiles)
// (
let
libblockdev = cfg.package.libblockdev;
majorVer = lib.versions.major libblockdev.version;
in
{
# We need to make sure /etc/libblockdev/@major_ver@/conf.d is populated to avoid
# warnings
"libblockdev/${majorVer}/conf.d/00-default.cfg".source =
"${libblockdev}/etc/libblockdev/${majorVer}/conf.d/00-default.cfg";
"libblockdev/${majorVer}/conf.d/10-lvm-dbus.cfg".source =
"${libblockdev}/etc/libblockdev/${majorVer}/conf.d/10-lvm-dbus.cfg";
}
);
security.polkit.enable = true;
services.dbus.packages = [ cfg.package ];
systemd.tmpfiles.rules = [
"d /var/lib/udisks2 0755 root root -"
]
++ lib.optional cfg.mountOnMedia "D! /media 0755 root root -";
services.udev.packages = [ cfg.package ];
services.udev.extraRules = lib.optionalString cfg.mountOnMedia ''
ENV{ID_FS_USAGE}=="filesystem", ENV{UDISKS_FILESYSTEM_SHARED}="1"
'';
systemd.packages = [ cfg.package ];
};
}

View File

@@ -0,0 +1,213 @@
{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.undervolt;
mkPLimit =
limit: window:
if (limit == null && window == null) then
null
else
assert lib.asserts.assertMsg (
limit != null && window != null
) "Both power limit and window must be set";
"${toString limit} ${toString window}";
cliArgs = lib.cli.toGNUCommandLine { } {
inherit (cfg)
verbose
temp
turbo
;
# `core` and `cache` are both intentionally set to `cfg.coreOffset` as according to the undervolt docs:
#
# Core or Cache offsets have no effect. It is not possible to set different offsets for
# CPU Core and Cache. The CPU will take the smaller of the two offsets, and apply that to
# both CPU and Cache. A warning message will be displayed if you attempt to set different offsets.
core = cfg.coreOffset;
cache = cfg.coreOffset;
gpu = cfg.gpuOffset;
uncore = cfg.uncoreOffset;
analogio = cfg.analogioOffset;
temp-bat = cfg.tempBat;
temp-ac = cfg.tempAc;
power-limit-long = mkPLimit cfg.p1.limit cfg.p1.window;
power-limit-short = mkPLimit cfg.p2.limit cfg.p2.window;
};
in
{
options.services.undervolt = {
enable = lib.mkEnableOption ''
Undervolting service for Intel CPUs.
Warning: This service is not endorsed by Intel and may permanently damage your hardware. Use at your own risk
'';
verbose = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to enable verbose logging.
'';
};
package = lib.mkPackageOption pkgs "undervolt" { };
coreOffset = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
description = ''
The amount of voltage in mV to offset the CPU cores by.
'';
};
gpuOffset = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
description = ''
The amount of voltage in mV to offset the GPU by.
'';
};
uncoreOffset = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
description = ''
The amount of voltage in mV to offset uncore by.
'';
};
analogioOffset = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
description = ''
The amount of voltage in mV to offset analogio by.
'';
};
temp = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
description = ''
The temperature target in Celsius degrees.
'';
};
tempAc = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
description = ''
The temperature target on AC power in Celsius degrees.
'';
};
tempBat = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
description = ''
The temperature target on battery power in Celsius degrees.
'';
};
turbo = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
description = ''
Changes the Intel Turbo feature status (1 is disabled and 0 is enabled).
'';
};
p1.limit = lib.mkOption {
type = with lib.types; nullOr int;
default = null;
description = ''
The P1 Power Limit in Watts.
Both limit and window must be set.
'';
};
p1.window = lib.mkOption {
type =
with lib.types;
nullOr (oneOf [
float
int
]);
default = null;
description = ''
The P1 Time Window in seconds.
Both limit and window must be set.
'';
};
p2.limit = lib.mkOption {
type = with lib.types; nullOr int;
default = null;
description = ''
The P2 Power Limit in Watts.
Both limit and window must be set.
'';
};
p2.window = lib.mkOption {
type =
with lib.types;
nullOr (oneOf [
float
int
]);
default = null;
description = ''
The P2 Time Window in seconds.
Both limit and window must be set.
'';
};
useTimer = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to set a timer that applies the undervolt settings every 30s.
This will cause spam in the journal but might be required for some
hardware under specific conditions.
Enable this if your undervolt settings don't hold.
'';
};
};
config = lib.mkIf cfg.enable {
hardware.cpu.x86.msr.enable = true;
environment.systemPackages = [ cfg.package ];
systemd.services.undervolt = {
description = "Intel Undervolting Service";
# Apply undervolt on boot, nixos generation switch and resume
wantedBy = [
"multi-user.target"
"post-resume.target"
];
after = [ "post-resume.target" ]; # Not sure why but it won't work without this
serviceConfig = {
Type = "oneshot";
Restart = "no";
ExecStart = "${cfg.package}/bin/undervolt ${toString cliArgs}";
};
};
systemd.timers.undervolt = lib.mkIf cfg.useTimer {
description = "Undervolt timer to ensure voltage settings are always applied";
partOf = [ "undervolt.service" ];
wantedBy = [ "multi-user.target" ];
timerConfig = {
OnBootSec = "2min";
OnUnitActiveSec = "30";
};
};
};
}

View File

@@ -0,0 +1,268 @@
# Upower daemon.
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.upower;
in
{
###### interface
options = {
services.upower = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to enable Upower, a DBus service that provides power
management support to applications.
'';
};
package = lib.mkPackageOption pkgs "upower" { };
enableWattsUpPro = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Enable the Watts Up Pro device.
The Watts Up Pro contains a generic FTDI USB device without a specific
vendor and product ID. When we probe for WUP devices, we can cause
the user to get a perplexing "Device or resource busy" error when
attempting to use their non-WUP device.
The generic FTDI device is known to also be used on:
- Sparkfun FT232 breakout board
- Parallax Propeller
'';
};
noPollBatteries = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Don't poll the kernel for battery level changes.
Some hardware will send us battery level changes through
events, rather than us having to poll for it. This option
allows disabling polling for hardware that sends out events.
'';
};
ignoreLid = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Do we ignore the lid state
Some laptops are broken. The lid state is either inverted, or stuck
on or off. We can't do much to fix these problems, but this is a way
for users to make the laptop panel vanish, a state that might be used
by a couple of user-space daemons. On Linux systems, see also
{manpage}`logind.conf(5)`.
'';
};
usePercentageForPolicy = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Policy for warnings and action based on battery levels
Whether battery percentage based policy should be used. The default
is to use the percentage, which
should work around broken firmwares. It is also more reliable than
the time left (frantically saving all your files is going to use more
battery than letting it rest for example).
'';
};
percentageLow = lib.mkOption {
type = lib.types.ints.unsigned;
default = 20;
description = ''
When `usePercentageForPolicy` is
`true`, the levels at which UPower will consider the
battery low.
This will also be used for batteries which don't have time information
such as that of peripherals.
If any value (of `percentageLow`,
`percentageCritical` and
`percentageAction`) is invalid, or not in descending
order, the defaults will be used.
'';
};
percentageCritical = lib.mkOption {
type = lib.types.ints.unsigned;
default = 5;
description = ''
When `usePercentageForPolicy` is
`true`, the levels at which UPower will consider the
battery critical.
This will also be used for batteries which don't have time information
such as that of peripherals.
If any value (of `percentageLow`,
`percentageCritical` and
`percentageAction`) is invalid, or not in descending
order, the defaults will be used.
'';
};
percentageAction = lib.mkOption {
type = lib.types.ints.unsigned;
default = 2;
description = ''
When `usePercentageForPolicy` is
`true`, the levels at which UPower will take action
for the critical battery level.
This will also be used for batteries which don't have time information
such as that of peripherals.
If any value (of `percentageLow`,
`percentageCritical` and
`percentageAction`) is invalid, or not in descending
order, the defaults will be used.
'';
};
timeLow = lib.mkOption {
type = lib.types.ints.unsigned;
default = 1200;
description = ''
When `usePercentageForPolicy` is
`false`, the time remaining in seconds at which
UPower will consider the battery low.
If any value (of `timeLow`,
`timeCritical` and `timeAction`) is
invalid, or not in descending order, the defaults will be used.
'';
};
timeCritical = lib.mkOption {
type = lib.types.ints.unsigned;
default = 300;
description = ''
When `usePercentageForPolicy` is
`false`, the time remaining in seconds at which
UPower will consider the battery critical.
If any value (of `timeLow`,
`timeCritical` and `timeAction`) is
invalid, or not in descending order, the defaults will be used.
'';
};
timeAction = lib.mkOption {
type = lib.types.ints.unsigned;
default = 120;
description = ''
When `usePercentageForPolicy` is
`false`, the time remaining in seconds at which
UPower will take action for the critical battery level.
If any value (of `timeLow`,
`timeCritical` and `timeAction`) is
invalid, or not in descending order, the defaults will be used.
'';
};
allowRiskyCriticalPowerAction = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Enable the risky critical power actions "Suspend" and "Ignore".
'';
};
criticalPowerAction = lib.mkOption {
type = lib.types.enum [
"PowerOff"
"Hibernate"
"HybridSleep"
"Suspend"
"Ignore"
];
default = "HybridSleep";
description = ''
The action to take when `timeAction` or
`percentageAction` has been reached for the batteries
(UPS or laptop batteries) supplying the computer.
When set to `Suspend` or `Ignore`,
{option}`services.upower.allowRiskyCriticalPowerAction` must be set
to `true`.
'';
};
};
};
###### implementation
config = lib.mkIf cfg.enable {
assertions = [
{
assertion =
let
inherit (builtins) elem;
riskyActions = [
"Suspend"
"Ignore"
];
riskyActionEnabled = elem cfg.criticalPowerAction riskyActions;
in
riskyActionEnabled -> cfg.allowRiskyCriticalPowerAction;
message = ''
services.upower.allowRiskyCriticalPowerAction must be true if
services.upower.criticalPowerAction is set to
'${cfg.criticalPowerAction}'.
'';
}
];
environment.systemPackages = [ cfg.package ];
services.dbus.packages = [ cfg.package ];
services.udev.packages = [ cfg.package ];
systemd.packages = [ cfg.package ];
environment.etc."UPower/UPower.conf".text = lib.generators.toINI { } {
UPower = {
EnableWattsUpPro = cfg.enableWattsUpPro;
NoPollBatteries = cfg.noPollBatteries;
IgnoreLid = cfg.ignoreLid;
UsePercentageForPolicy = cfg.usePercentageForPolicy;
PercentageLow = cfg.percentageLow;
PercentageCritical = cfg.percentageCritical;
PercentageAction = cfg.percentageAction;
TimeLow = cfg.timeLow;
TimeCritical = cfg.timeCritical;
TimeAction = cfg.timeAction;
AllowRiskyCriticalPowerAction = cfg.allowRiskyCriticalPowerAction;
CriticalPowerAction = cfg.criticalPowerAction;
};
};
};
}

View File

@@ -0,0 +1,91 @@
{
config,
lib,
pkgs,
...
}:
let
defaultUserGroup = "usbmux";
apple = "05ac";
cfg = config.services.usbmuxd;
in
{
options.services.usbmuxd = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Enable the usbmuxd ("USB multiplexing daemon") service. This daemon is
in charge of multiplexing connections over USB to an iOS device. This is
needed for transferring data from and to iOS devices (see ifuse). Also
this may enable plug-n-play tethering for iPhones.
'';
};
user = lib.mkOption {
type = lib.types.str;
default = defaultUserGroup;
description = ''
The user usbmuxd should use to run after startup.
'';
};
group = lib.mkOption {
type = lib.types.str;
default = defaultUserGroup;
description = ''
The group usbmuxd should use to run after startup.
'';
};
package = lib.mkOption {
type = lib.types.package;
default = pkgs.usbmuxd;
defaultText = lib.literalExpression "pkgs.usbmuxd";
description = "Which package to use for the usbmuxd daemon.";
relatedPackages = [
"usbmuxd"
"usbmuxd2"
];
};
};
config = lib.mkIf cfg.enable {
users.users = lib.optionalAttrs (cfg.user == defaultUserGroup) {
${cfg.user} = {
description = "usbmuxd user";
group = cfg.group;
isSystemUser = true;
};
};
users.groups = lib.optionalAttrs (cfg.group == defaultUserGroup) {
${cfg.group} = { };
};
# Give usbmuxd permission for Apple devices
services.udev.extraRules = ''
SUBSYSTEM=="usb", ATTR{idVendor}=="${apple}", GROUP="${cfg.group}"
'';
systemd.services.usbmuxd = {
description = "usbmuxd";
wantedBy = [ "multi-user.target" ];
unitConfig.Documentation = "man:usbmuxd(8)";
serviceConfig = {
# Trigger the udev rule manually. This doesn't require replugging the
# device when first enabling the option to get it to work
ExecStartPre = "${config.systemd.package}/bin/udevadm trigger -s usb -a idVendor=${apple}";
ExecStart = "${cfg.package}/bin/usbmuxd -U ${cfg.user} -v";
};
};
};
}

View File

@@ -0,0 +1,47 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.usbrelayd;
in
{
options.services.usbrelayd = with lib.types; {
enable = lib.mkEnableOption "USB Relay MQTT daemon";
broker = lib.mkOption {
type = str;
description = "Hostname or IP address of your MQTT Broker.";
default = "127.0.0.1";
example = [
"mqtt"
"192.168.1.1"
];
};
clientName = lib.mkOption {
type = str;
description = "Name, your client connects as.";
default = "MyUSBRelay";
};
};
config = lib.mkIf cfg.enable {
environment.etc."usbrelayd.conf".text = ''
[MQTT]
BROKER = ${cfg.broker}
CLIENTNAME = ${cfg.clientName}
'';
services.udev.packages = [ pkgs.usbrelayd ];
systemd.packages = [ pkgs.usbrelayd ];
users.groups.usbrelay = { };
};
meta = {
maintainers = with lib.maintainers; [ wentasah ];
};
}

View File

@@ -0,0 +1,111 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.vdr;
inherit (lib)
mkEnableOption
mkPackageOption
mkOption
types
mkIf
optional
;
in
{
options = {
services.vdr = {
enable = mkEnableOption "VDR, a video disk recorder";
package = mkPackageOption pkgs "vdr" {
example = "wrapVdr.override { plugins = with pkgs.vdrPlugins; [ hello ]; }";
};
videoDir = mkOption {
type = types.path;
default = "/srv/vdr/video";
description = "Recording directory";
};
extraArguments = mkOption {
type = types.listOf types.str;
default = [ ];
description = "Additional command line arguments to pass to VDR.";
};
enableLirc = mkEnableOption "LIRC";
user = mkOption {
type = types.str;
default = "vdr";
description = ''
User under which the VDR service runs.
'';
};
group = mkOption {
type = types.str;
default = "vdr";
description = ''
Group under which the VDRvdr service runs.
'';
};
};
};
config = mkIf cfg.enable {
systemd.tmpfiles.rules = [
"d ${cfg.videoDir} 0755 ${cfg.user} ${cfg.group} -"
"Z ${cfg.videoDir} - ${cfg.user} ${cfg.group} -"
];
systemd.services.vdr = {
description = "VDR";
wantedBy = [ "multi-user.target" ];
wants = optional cfg.enableLirc "lircd.service";
after = [ "network.target" ] ++ optional cfg.enableLirc "lircd.service";
serviceConfig = {
ExecStart =
let
args = [
"--video=${cfg.videoDir}"
]
++ optional cfg.enableLirc "--lirc=${config.passthru.lirc.socket}"
++ cfg.extraArguments;
in
"${cfg.package}/bin/vdr ${lib.escapeShellArgs args}";
User = cfg.user;
Group = cfg.group;
CacheDirectory = "vdr";
StateDirectory = "vdr";
RuntimeDirectory = "vdr";
Restart = "on-failure";
};
};
environment.systemPackages = [ cfg.package ];
users.users = mkIf (cfg.user == "vdr") {
vdr = {
inherit (cfg) group;
home = "/run/vdr";
isSystemUser = true;
extraGroups = [
"video"
"audio"
]
++ optional cfg.enableLirc "lirc";
};
};
users.groups = mkIf (cfg.group == "vdr") { vdr = { }; };
};
}