uboot: (firmwareOdroidC2/C4) don't invoke patch tool, use patches = [] instead

https://github.com/NixOS/nixpkgs/blob/master/pkgs/stdenv/generic/setup.sh#L948
this can do it nicely.

Signed-off-by: Anton Arapov <anton@deadbeef.mx>
This commit is contained in:
Anton Arapov 2021-04-03 12:58:10 +02:00 committed by Alan Daniels
commit 56de2bcd43
30691 changed files with 3076956 additions and 0 deletions

View file

@ -0,0 +1,155 @@
{ config, lib, pkgs, ... }:
with lib;
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 concatStringsSep "\n" (mapAttrsToList f (canonicalHandlers // cfg.handlers))
}
'';
in
{
###### interface
options = {
services.acpid = {
enable = mkEnableOption "the ACPI daemon";
logEvents = mkOption {
type = types.bool;
default = false;
description = "Log all event activity.";
};
handlers = mkOption {
type = types.attrsOf (types.submodule {
options = {
event = mkOption {
type = types.str;
example = literalExpression ''"button/power.*" "button/lid.*" "ac_adapter.*" "button/mute.*" "button/volumedown.*" "cd/play.*" "cd/next.*"'';
description = "Event type.";
};
action = mkOption {
type = types.lines;
description = "Shell commands to execute when the event is triggered.";
};
};
});
description = ''
Event handlers.
<note><para>
Handler can be a single command.
</para></note>
'';
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 = mkOption {
type = types.lines;
default = "";
description = "Shell commands to execute on a button/power.* event.";
};
lidEventCommands = mkOption {
type = types.lines;
default = "";
description = "Shell commands to execute on a button/lid.* event.";
};
acEventCommands = mkOption {
type = types.lines;
default = "";
description = "Shell commands to execute on an ac_adapter.* event.";
};
};
};
###### implementation
config = mkIf cfg.enable {
systemd.services.acpid = {
description = "ACPI Daemon";
documentation = [ "man:acpid(8)" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = escapeShellArgs
([ "${pkgs.acpid}/bin/acpid"
"--foreground"
"--netlink"
"--confdir" "${acpiConfDir}"
] ++ optional cfg.logEvents "--logevents"
);
};
unitConfig = {
ConditionVirtualization = "!systemd-nspawn";
ConditionPathExists = [ "/proc/acpi" ];
};
};
};
}

View file

@ -0,0 +1,133 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.actkbd;
configFile = pkgs.writeText "actkbd.conf" ''
${concatMapStringsSep "\n"
({ keys, events, attributes, command, ... }:
''${concatMapStringsSep "+" toString keys}:${concatStringsSep "," events}:${concatStringsSep "," attributes}:${command}''
)
cfg.bindings}
${cfg.extraConfig}
'';
bindingCfg = { ... }: {
options = {
keys = mkOption {
type = types.listOf types.int;
description = "List of keycodes to match.";
};
events = mkOption {
type = types.listOf (types.enum ["key" "rep" "rel"]);
default = [ "key" ];
description = "List of events to match.";
};
attributes = mkOption {
type = types.listOf types.str;
default = [ "exec" ];
description = "List of attributes.";
};
command = mkOption {
type = types.str;
default = "";
description = "What to run.";
};
};
};
in
{
###### interface
options = {
services.actkbd = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable the <command>actkbd</command> key mapping daemon.
Turning this on will start an <command>actkbd</command>
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 &lt;100 bytes of memory per
instance).
This allows binding keys globally without the need for e.g.
X11.
'';
};
bindings = mkOption {
type = types.listOf (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</command>.
See <command>actkbd</command> <filename>README</filename> for documentation.
The example shows a piece of what <option>sound.mediaKeys.enable</option> does when enabled.
'';
};
extraConfig = mkOption {
type = types.lines;
default = "";
description = ''
Literal contents to append to the end of actkbd configuration file.
'';
};
};
};
###### implementation
config = 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,24 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.auto-cpufreq;
in {
options = {
services.auto-cpufreq = {
enable = mkEnableOption "auto-cpufreq daemon";
};
};
config = 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 ];
};
};
};
}

View file

@ -0,0 +1,141 @@
{ config, lib, pkgs, ... }:
let
cfg = config.hardware.bluetooth;
package = cfg.package;
inherit (lib)
mkDefault mkEnableOption mkIf mkOption
mkRenamedOptionModule mkRemovedOptionModule
concatStringsSep escapeShellArgs literalExpression
optional optionals 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 = mkOption {
type = types.package;
default = pkgs.bluez;
defaultText = literalExpression "pkgs.bluez";
example = literalExpression "pkgs.bluezFull";
description = ''
Which BlueZ package to use.
<note><para>
Use the <literal>pkgs.bluezFull</literal> package to enable all
bluez plugins.
</para></note>
'';
};
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).";
};
};
};
###### implementation
config = mkIf cfg.enable {
environment.systemPackages = [ package ]
++ optional cfg.hsphfpd.enable pkgs.hsphfpd;
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" ];
serviceConfig.ExecStart = [
""
"${package}/libexec/bluetooth/bluetoothd ${escapeShellArgs args}"
];
# restarting can leave people without a mouse/keyboard
unitConfig.X-RestartIfChanged = false;
};
}
// (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,34 @@
# Thunderbolt 3 device manager
{ config, lib, pkgs, ...}:
with lib;
{
options = {
services.hardware.bolt = {
enable = mkOption {
type = 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.
'';
};
};
};
config = mkIf config.services.hardware.bolt.enable {
environment.systemPackages = [ pkgs.bolt ];
services.udev.packages = [ pkgs.bolt ];
systemd.packages = [ pkgs.bolt ];
};
}

View file

@ -0,0 +1,57 @@
{ config, lib, pkgs, ... }:
with lib;
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 = mkOption {
type = types.bool;
default = false;
description = "Whether to enable the BRLTTY daemon.";
};
};
config = mkIf cfg.enable {
users.users.brltty = {
description = "BRLTTY daemon user";
group = "brltty";
};
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,39 @@
{ 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,48 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.hardware.fancontrol;
configFile = pkgs.writeText "fancontrol.conf" cfg.config;
in
{
options.hardware.fancontrol = {
enable = mkEnableOption "software fan control (requires fancontrol.config)";
config = mkOption {
type = types.lines;
description = "Required fancontrol configuration file content. See <citerefentry><refentrytitle>pwmconfig</refentrytitle><manvolnum>8</manvolnum></citerefentry> 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 = 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 = "${pkgs.lm_sensors}/sbin/fancontrol ${configFile}";
};
};
};
meta.maintainers = [ maintainers.evils ];
}

View file

@ -0,0 +1,64 @@
{ config, lib, pkgs, utils, ... }:
with lib;
let
cfg = config.services.freefall;
in {
options.services.freefall = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to protect HP/Dell laptop hard drives (not SSDs) in free fall.
'';
};
package = mkOption {
type = types.package;
default = pkgs.freefall;
defaultText = literalExpression "pkgs.freefall";
description = ''
freefall derivation to use.
'';
};
devices = mkOption {
type = types.listOf 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
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 mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
systemd.services = builtins.listToAttrs (map mkService cfg.devices);
};
}

View file

@ -0,0 +1,134 @@
# fwupd daemon.
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.fwupd;
customEtc = {
"fwupd/daemon.conf" = {
source = pkgs.writeText "daemon.conf" ''
[fwupd]
DisabledDevices=${lib.concatStringsSep ";" cfg.disabledDevices}
DisabledPlugins=${lib.concatStringsSep ";" cfg.disabledPlugins}
'';
};
"fwupd/uefi.conf" = {
source = pkgs.writeText "uefi.conf" ''
[uefi]
OverrideESPMountPoint=${config.boot.loader.efi.efiSysMountPoint}
'';
};
};
originalEtc =
let
mkEtcFile = n: nameValuePair n { source = "${cfg.package}/etc/${n}"; };
in listToAttrs (map mkEtcFile cfg.package.filesInstalledToEtc);
extraTrustedKeys =
let
mkName = p: "pki/fwupd/${baseNameOf (toString p)}";
mkEtcFile = p: nameValuePair (mkName p) { source = p; };
in listToAttrs (map mkEtcFile cfg.extraTrustedKeys);
# 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.
testRemote = if cfg.enableTestRemote then {
"fwupd/remotes.d/fwupd-tests.conf" = {
source = pkgs.runCommand "fwupd-tests-enabled.conf" {} ''
sed "s,^Enabled=false,Enabled=true," \
"${cfg.package.installedTests}/etc/fwupd/remotes.d/fwupd-tests.conf" > "$out"
'';
};
} else {};
in {
###### interface
options = {
services.fwupd = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable fwupd, a DBus service that allows
applications to update firmware.
'';
};
disabledDevices = mkOption {
type = types.listOf types.str;
default = [];
example = [ "2082b5e0-7a64-478a-b1b2-e3404fab6dad" ];
description = ''
Allow disabling specific devices by their GUID
'';
};
disabledPlugins = mkOption {
type = types.listOf types.str;
default = [];
example = [ "udev" ];
description = ''
Allow disabling specific plugins
'';
};
extraTrustedKeys = mkOption {
type = types.listOf types.path;
default = [];
example = 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.
'';
};
enableTestRemote = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable test remote. This is used by
<link xlink:href="https://github.com/fwupd/fwupd/blob/master/data/installed-tests/README.md">installed tests</link>.
'';
};
package = mkOption {
type = types.package;
default = pkgs.fwupd;
defaultText = literalExpression "pkgs.fwupd";
description = ''
Which fwupd package to use.
'';
};
};
};
imports = [
(mkRenamedOptionModule [ "services" "fwupd" "blacklistDevices"] [ "services" "fwupd" "disabledDevices" ])
(mkRenamedOptionModule [ "services" "fwupd" "blacklistPlugins"] [ "services" "fwupd" "disabledPlugins" ])
];
###### implementation
config = mkIf cfg.enable {
# Disable test related plug-ins implicitly so that users do not have to care about them.
services.fwupd.disabledPlugins = cfg.package.defaultDisabledPlugins;
environment.systemPackages = [ cfg.package ];
# customEtc overrides some files from the package
environment.etc = originalEtc // customEtc // extraTrustedKeys // testRemote;
services.dbus.packages = [ cfg.package ];
services.udev.packages = [ cfg.package ];
systemd.packages = [ cfg.package ];
};
meta = {
maintainers = pkgs.fwupd.meta.maintainers;
};
}

View file

@ -0,0 +1,36 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.illum;
in {
options = {
services.illum = {
enable = mkOption {
default = false;
type = types.bool;
description = ''
Enable illum, a daemon for controlling screen brightness with brightness buttons.
'';
};
};
};
config = 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,62 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.interception-tools;
in {
options.services.interception-tools = {
enable = mkOption {
type = types.bool;
default = false;
description = "Whether to enable the interception tools service.";
};
plugins = mkOption {
type = types.listOf types.package;
default = [ pkgs.interception-tools-plugins.caps2esc ];
defaultText = 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 = mkOption {
type = types.either types.str 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 = 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,24 @@
#
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.irqbalance;
in
{
options.services.irqbalance.enable = mkEnableOption "irqbalance daemon";
config = mkIf cfg.enable {
environment.systemPackages = [ pkgs.irqbalance ];
systemd.services.irqbalance.wantedBy = ["multi-user.target"];
systemd.packages = [ pkgs.irqbalance ];
};
}

View file

@ -0,0 +1,36 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.joycond;
kernelPackages = config.boot.kernelPackages;
in
with lib;
{
options.services.joycond = {
enable = mkEnableOption "support for Nintendo Pro Controllers and Joycons";
package = mkOption {
type = types.package;
default = pkgs.joycond;
defaultText = "pkgs.joycond";
description = ''
The joycond package to use.
'';
};
};
config = mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
boot.extraModulePackages = optional (versionOlder kernelPackages.kernel.version "5.16") kernelPackages.hid-nintendo;
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,171 @@
{ 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.
</para>
<para>
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</option> and
<option>services.hardware.lcd.usbPid</option>. In order to find the
values, you can run the <command>lsusb</command> command. Example
output:
</para>
<para>
<literal>
Bus 005 Device 002: ID 0403:c630 Future Technology Devices International, Ltd lcd2usb interface
</literal>
</para>
<para>
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,100 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.lirc;
in {
###### interface
options = {
services.lirc = {
enable = mkEnableOption "LIRC daemon";
options = mkOption {
type = types.lines;
example = ''
[lircd]
nodaemon = False
'';
description = "LIRC default options descriped in man:lircd(8) (<filename>lirc_options.conf</filename>)";
};
configs = mkOption {
type = types.listOf types.lines;
description = "Configurations for lircd to load, see man:lircd.conf(5) for details (<filename>lircd.conf</filename>)";
};
extraArguments = mkOption {
type = types.listOf types.str;
default = [];
description = "Extra arguments to lircd.";
};
};
};
###### implementation
config = mkIf cfg.enable {
# Note: LIRC executables raises a warning, if lirc_options.conf do not exists
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 \
${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,43 @@
{ 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"];
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,73 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfgFile = pkgs.writeText "reader.conf" config.services.pcscd.readerConfig;
pluginEnv = pkgs.buildEnv {
name = "pcscd-plugins";
paths = map (p: "${p}/pcsc/drivers") config.services.pcscd.plugins;
};
in
{
###### interface
options.services.pcscd = {
enable = mkEnableOption "PCSC-Lite daemon";
plugins = mkOption {
type = types.listOf types.package;
default = [ pkgs.ccid ];
defaultText = literalExpression "[ pkgs.ccid ]";
example = literalExpression "[ pkgs.pcsc-cyberjack ]";
description = "Plugin packages to be used for PCSC-Lite.";
};
readerConfig = mkOption {
type = 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 <citerefentry><refentrytitle>reader.conf</refentrytitle>
<manvolnum>5</manvolnum></citerefentry> for valid options.
'';
};
};
###### implementation
config = mkIf config.services.pcscd.enable {
environment.etc."reader.conf".source = cfgFile;
environment.systemPackages = [ pkgs.pcsclite ];
systemd.packages = [ (getBin pkgs.pcsclite) ];
systemd.sockets.pcscd.wantedBy = [ "sockets.target" ];
systemd.services.pcscd = {
environment.PCSCLITE_HP_DROPDIR = pluginEnv;
restartTriggers = [ "/etc/reader.conf" ];
# 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 = [ "" "${getBin pkgs.pcsclite}/bin/pcscd -f -x -c ${cfgFile}" ];
};
};
}

View file

@ -0,0 +1,50 @@
{ config, lib, pkgs, ... }:
with lib;
let cfg = config.services.hardware.pommed;
defaultConf = "${pkgs.pommed_light}/etc/pommed.conf.mactel";
in {
options = {
services.hardware.pommed = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to use the pommed tool to handle Apple laptop
keyboard hotkeys.
'';
};
configFile = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
The path to the <filename>pommed.conf</filename> file. Leave
to null to use the default config file
(<filename>/etc/pommed.conf.mactel</filename>). See the
files <filename>/etc/pommed.conf.mactel</filename> and
<filename>/etc/pommed.conf.pmac</filename> for examples to
build on.
'';
};
};
};
config = 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,55 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.power-profiles-daemon;
package = pkgs.power-profiles-daemon;
in
{
###### interface
options = {
services.power-profiles-daemon = {
enable = mkOption {
type = 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.
'';
};
};
};
###### implementation
config = 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;
'';
}
];
environment.systemPackages = [ package ];
services.dbus.packages = [ package ];
services.udev.packages = [ package ];
systemd.packages = [ package ];
};
}

View file

@ -0,0 +1,170 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.hardware.rasdaemon;
in
{
options.hardware.rasdaemon = {
enable = mkEnableOption "RAS logging daemon";
record = mkOption {
type = types.bool;
default = true;
description = "record events via sqlite3, required for ras-mc-ctl";
};
mainboard = mkOption {
type = types.lines;
default = "";
description = "Custom mainboard description, see <citerefentry><refentrytitle>ras-mc-ctl</refentrytitle><manvolnum>8</manvolnum></citerefentry> 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 = mkOption {
type = 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 = mkOption {
type = 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 = mkOption {
type = types.listOf types.str;
default = [];
description = "extra kernel modules to load";
example = [ "i7core_edac" ];
};
testing = mkEnableOption "error injection infrastructure";
};
config = 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 = [ pkgs.rasdaemon ]
++ optionals (cfg.testing) (with pkgs.error-inject; [
edac-inject
mce-inject
aer-inject
]);
boot.initrd.kernelModules = cfg.extraModules
++ 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 = 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 = optionalString (cfg.record) "rasdaemon";
ExecStart = "${pkgs.rasdaemon}/bin/rasdaemon --foreground"
+ optionalString (cfg.record) " --record";
ExecStop = "${pkgs.rasdaemon}/bin/rasdaemon --disable";
Restart = "on-abort";
# src/misc/rasdaemon.service.in shows this:
# ExecStartPost = ${pkgs.rasdaemon}/bin/rasdaemon --enable
# but that results in unpredictable existence of the database
# and everything seems to be enabled without this...
};
};
ras-mc-ctl = mkIf (cfg.labels != "") {
description = "register DIMM labels on startup";
documentation = [ "man:ras-mc-ctl(8)" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.rasdaemon}/bin/ras-mc-ctl --register-labels";
RemainAfterExit = true;
};
};
};
};
meta.maintainers = [ maintainers.evils ];
}

View file

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

View file

@ -0,0 +1,197 @@
{ config, lib, pkgs, ... }:
with lib;
let
pkg = pkgs.sane-backends.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 = config.hardware.sane.configDir;
LD_LIBRARY_PATH = [ "${saneConfig}/lib/sane" ];
};
backends = [ pkg netConf ] ++ 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 = mkOption {
type = types.bool;
default = false;
description = ''
Enable support for SANE scanners.
<note><para>
Users in the "scanner" group will gain access to the scanner, or the "lp" group if it's also a printer.
</para></note>
'';
};
hardware.sane.snapshot = mkOption {
type = types.bool;
default = false;
description = "Use a development snapshot of SANE scanner drivers.";
};
hardware.sane.extraBackends = mkOption {
type = types.listOf types.path;
default = [];
description = ''
Packages providing extra SANE backends to enable.
<note><para>
The example contains the package for HP scanners.
</para></note>
'';
example = literalExpression "[ pkgs.hplipWithPlugin ]";
};
hardware.sane.disabledDefaultBackends = mkOption {
type = types.listOf types.str;
default = [];
example = [ "v4l" ];
description = ''
Names of backends which are enabled by default but should be disabled.
See <literal>$SANE_CONFIG_DIR/dll.conf</literal> for the list of possible names.
'';
};
hardware.sane.configDir = mkOption {
type = types.str;
internal = true;
description = "The value of SANE_CONFIG_DIR.";
};
hardware.sane.netConf = mkOption {
type = types.lines;
default = "";
example = "192.168.0.16";
description = ''
Network hosts that should be probed for remote scanners.
'';
};
hardware.sane.drivers.scanSnap.enable = mkOption {
type = 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 = mkOption {
type = types.package;
default = pkgs.sane-drivers.epjitsu;
defaultText = literalExpression "pkgs.sane-drivers.epjitsu";
description = ''
Epjitsu driver package to use. Useful if you want to extract the driver files yourself.
The process is described in the <literal>/etc/sane.d/epjitsu.conf</literal> file in
the <literal>sane-backends</literal> package.
'';
};
services.saned.enable = mkOption {
type = types.bool;
default = false;
description = ''
Enable saned network daemon for remote connection to scanners.
saned would be runned from <literal>scanner</literal> user; to allow
access to hardware that doesn't have <literal>scanner</literal> group
you should add needed groups to this user.
'';
};
services.saned.extraConfig = mkOption {
type = types.lines;
default = "";
example = "192.168.0.0/24";
description = ''
Extra saned configuration lines.
'';
};
};
###### implementation
config = mkMerge [
(mkIf enabled {
hardware.sane.configDir = mkDefault "${saneConfig}/etc/sane.d";
environment.systemPackages = backends;
environment.sessionVariables = env;
services.udev.packages = backends;
users.groups.scanner.gid = config.ids.gids.scanner;
})
(mkIf config.services.saned.enable {
networking.firewall.connectionTrackingModules = [ "sane" ];
systemd.services."saned@" = {
description = "Scanner Service";
environment = 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" ] ++ optionals config.services.avahi.enable [ "avahi" ];
};
})
];
}

View file

@ -0,0 +1,112 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.hardware.sane.brscan4;
netDeviceList = attrValues cfg.netDevices;
etcFiles = pkgs.callPackage ./brscan4_etc_files.nix { netDevices = netDeviceList; };
netDeviceOpts = { name, ... }: {
options = {
name = mkOption {
type = types.str;
description = ''
The friendly name you give to the network device. If undefined,
the name of attribute will be used.
'';
example = "office1";
};
model = mkOption {
type = types.str;
description = ''
The model of the network device.
'';
example = "MFC-7860DW";
};
ip = mkOption {
type = with 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 = mkOption {
type = with 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 = mkDefault name;
};
};
in
{
options = {
hardware.sane.brscan4.enable =
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 = mkOption {
default = {};
example =
{ office1 = { model = "MFC-7860DW"; ip = "192.168.1.2"; };
office2 = { model = "MFC-7860DW"; nodename = "BRW0080927AFBCE"; };
};
type = with types; attrsOf (submodule netDeviceOpts);
description = ''
The list of network devices that will be registered against the brscan4
sane backend.
'';
};
};
config = 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 = 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,68 @@
{ 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 {
name = "brscan4-etc-files-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,110 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.hardware.sane.brscan5;
netDeviceList = attrValues cfg.netDevices;
etcFiles = pkgs.callPackage ./brscan5_etc_files.nix { netDevices = netDeviceList; };
netDeviceOpts = { name, ... }: {
options = {
name = mkOption {
type = types.str;
description = ''
The friendly name you give to the network device. If undefined,
the name of attribute will be used.
'';
example = "office1";
};
model = mkOption {
type = types.str;
description = ''
The model of the network device.
'';
example = "ADS-1200";
};
ip = mkOption {
type = with 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 = mkOption {
type = with 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 = mkDefault name;
};
};
in
{
options = {
hardware.sane.brscan5.enable =
mkEnableOption "the Brother brscan5 sane backend";
hardware.sane.brscan5.netDevices = mkOption {
default = {};
example =
{ office1 = { model = "MFC-7860DW"; ip = "192.168.1.2"; };
office2 = { model = "MFC-7860DW"; nodename = "BRW0080927AFBCE"; };
};
type = with types; attrsOf (submodule netDeviceOpts);
description = ''
The list of network devices that will be registered against the brscan5
sane backend.
'';
};
};
config = 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/brother.conf";
assertions = [
{ assertion = 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,77 @@
{ 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,26 @@
{ config, lib, pkgs, ... }:
with lib;
{
options = {
hardware.sane.dsseries.enable =
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 = 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,24 @@
{ config, lib, pkgs, ... }:
with lib;
let cfg = config.hardware.spacenavd;
in {
options = {
hardware.spacenavd = {
enable = mkEnableOption "spacenavd to support 3DConnexion devices";
};
};
config = mkIf cfg.enable {
systemd.user.services.spacenavd = {
description = "Daemon for the Spacenavigator 6DOF mice by 3Dconnexion";
wantedBy = [ "graphical.target" ];
serviceConfig = {
ExecStart = "${pkgs.spacenavd}/bin/spacenavd -d -l syslog";
};
};
};
}

View file

@ -0,0 +1,162 @@
# tcsd daemon.
{ config, options, pkgs, lib, ... }:
with 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 = mkOption {
default = false;
type = 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 = mkOption {
default = "tss";
type = types.str;
description = "User account under which tcsd runs.";
};
group = mkOption {
default = "tss";
type = types.str;
description = "Group account under which tcsd runs.";
};
stateDir = mkOption {
default = "/var/lib/tpm";
type = 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 = mkOption {
default = "0,1,2,3,4,5,6,7";
type = types.str;
description = "PCR indices used in the TPM for firmware measurements.";
};
kernelPCRs = mkOption {
default = "8,9,10,11,12";
type = types.str;
description = "PCR indices used in the TPM for kernel measurements.";
};
platformCred = mkOption {
default = "${cfg.stateDir}/platform.cert";
defaultText = literalExpression ''"''${config.${opt.stateDir}}/platform.cert"'';
type = 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 = mkOption {
default = "${cfg.stateDir}/conformance.cert";
defaultText = literalExpression ''"''${config.${opt.stateDir}}/conformance.cert"'';
type = types.path;
description = ''
Path to the conformance credential for your TPM.
See also the platformCred option'';
};
endorsementCred = mkOption {
default = "${cfg.stateDir}/endorsement.cert";
defaultText = literalExpression ''"''${config.${opt.stateDir}}/endorsement.cert"'';
type = types.path;
description = ''
Path to the endorsement credential for your TPM.
See also the platformCred option'';
};
};
};
###### implementation
config = 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 = optionalAttrs (cfg.user == "tss") {
tss = {
group = "tss";
isSystemUser = true;
};
};
users.groups = optionalAttrs (cfg.group == "tss") { tss = {}; };
};
}

View file

@ -0,0 +1,57 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.thermald;
in
{
###### interface
options = {
services.thermald = {
enable = mkEnableOption "thermald, the temperature management daemon";
debug = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable debug logging.
'';
};
configFile = mkOption {
type = types.nullOr types.path;
default = null;
description = "the thermald manual configuration file.";
};
package = mkOption {
type = types.package;
default = pkgs.thermald;
defaultText = literalExpression "pkgs.thermald";
description = "Which thermald package to use.";
};
};
};
###### implementation
config = mkIf cfg.enable {
services.dbus.packages = [ cfg.package ];
systemd.services.thermald = {
description = "Thermal Daemon Service";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
PrivateNetwork = true;
ExecStart = ''
${cfg.package}/sbin/thermald \
--no-daemon \
${optionalString cfg.debug "--loglevel=debug"} \
${optionalString (cfg.configFile != null) "--config-file ${cfg.configFile}"} \
--dbus-enable \
--adaptive
'';
};
};
};
}

View file

@ -0,0 +1,224 @@
{ config, lib, pkgs, ... }:
with lib;
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 types;
let
tuple = ts: mkOptionType {
name = "tuple";
merge = mergeOneOption;
check = xs: all id (zipListsWith (t: x: t.check x) ts xs);
description = "tuple of" + 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: types.submodule {
freeformType = types.attrsOf settingsFormat.type;
options = {
type = mkOption {
type = types.enum [ "hwmon" "atasmart" "tpacpi" "nvml" ];
description = ''
The ${name} type, can be
<literal>hwmon</literal> for standard ${name}s,
<literal>atasmart</literal> to read the temperature via
S.M.A.R.T (requires smartSupport to be enabled),
<literal>tpacpi</literal> for the legacy thinkpac_acpi driver, or
<literal>nvml</literal> for the (proprietary) nVidia driver.
'';
};
query = mkOption {
type = 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><para>
When multiple ${name}s match, the query can be restricted using the
<option>name</option> or <option>indices</option> options.
</para></note>
'';
};
indices = mkOption {
type = with types; nullOr (listOf ints.unsigned);
default = null;
description = ''
A list of ${name}s to pick in case multiple ${name}s match the query.
<note><para>Indices start from 0.</para></note>
'';
};
} // optionalAttrs (name == "sensor") {
correction = mkOption {
type = with 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:
(filterAttrs (k: v: v != null && !(elem k ["type" "query"])) args)
// { "${type}" = query; };
syntaxNote = name: ''
<note><para>
This section slightly departs from the thinkfan.conf syntax.
The type and path must be specified like this:
<literal>
type = "tpacpi";
query = "/proc/acpi/ibm/${name}";
</literal>
instead of a single declaration like:
<literal>
- tpacpi: /proc/acpi/ibm/${name}
</literal>
</para></note>
'';
in {
options = {
services.thinkfan = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable thinkfan, a fan control program.
<note><para>
This module targets IBM/Lenovo thinkpads by default, for
other hardware you will have configure it more carefully.
</para></note>
'';
relatedPackages = [ "thinkfan" ];
};
smartSupport = mkOption {
type = types.bool;
default = false;
description = ''
Whether to build thinkfan with S.M.A.R.T. support to read temperatures
directly from hard disks.
'';
};
sensors = mkOption {
type = types.listOf (sensorType "sensor");
default = [
{ type = "tpacpi";
query = "/proc/acpi/ibm/thermal";
}
];
description = ''
List of temperature sensors thinkfan will monitor.
'' + syntaxNote "thermal";
};
fans = mkOption {
type = types.listOf (sensorType "fan");
default = [
{ type = "tpacpi";
query = "/proc/acpi/ibm/fan";
}
];
description = ''
List of fans thinkfan will control.
'' + syntaxNote "fan";
};
levels = mkOption {
type = 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 = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "-b" "0" ];
description = ''
A list of extra command line arguments to pass to thinkfan.
Check the thinkfan(1) manpage for available arguments.
'';
};
settings = mkOption {
type = 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 <literal>thinkfan.conf(5)</literal>
manpage and take a look at the example config file at
<link xlink:href="https://github.com/vmatare/thinkfan/blob/master/examples/thinkfan.yaml"/>
'';
};
};
};
config = mkIf cfg.enable {
environment.systemPackages = [ thinkfan ];
services.thinkfan.settings = mapAttrs (k: v: mkDefault v) {
sensors = map sensorToConf cfg.sensors;
fans = map sensorToConf cfg.fans;
levels = cfg.levels;
};
systemd.packages = [ thinkfan ];
systemd.services = {
thinkfan.environment.THINKFAN_ARGS = escapeShellArgs ([ "-c" configFile ] ++ cfg.extraArgs);
# 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,36 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.throttled;
in {
options = {
services.throttled = {
enable = mkEnableOption "fix for Intel CPU throttling";
extraConfig = mkOption {
type = types.str;
default = "";
description = "Alternative configuration";
};
};
};
config = 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.lenovo_fix.wantedBy = [ "multi-user.target" ];
environment.etc."lenovo_fix.conf".source =
if cfg.extraConfig != ""
then pkgs.writeText "lenovo_fix.conf" cfg.extraConfig
else "${pkgs.throttled}/etc/lenovo_fix.conf";
# Kernel 5.9 spams warnings whenever userspace writes to CPU MSRs.
# See https://github.com/erpalma/throttled/issues/215
boot.kernelParams =
optional (versionAtLeast config.boot.kernelPackages.kernel.version "5.9")
"msr.allow_writes=on";
};
}

View file

@ -0,0 +1,124 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.tlp;
enableRDW = config.networking.networkmanager.enable;
tlp = pkgs.tlp.override { inherit enableRDW; };
# TODO: Use this for having proper parameters in the future
mkTlpConfig = tlpConfig: generators.toKeyValue {
mkKeyValue = generators.mkKeyValueDefault {
mkValueString = val:
if isList val then "\"" + (toString val) + "\""
else toString val;
} "=";
} tlpConfig;
in
{
###### interface
options = {
services.tlp = {
enable = mkOption {
type = types.bool;
default = false;
description = "Whether to enable the TLP power management daemon.";
};
settings = mkOption {type = with 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 = mkOption {
type = types.lines;
default = "";
description = ''
Verbatim additional configuration variables for TLP.
DEPRECATED: use services.tlp.settings instead.
'';
};
};
};
###### implementation
config = mkIf cfg.enable {
boot.kernelModules = [ "msr" ];
warnings = 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;
} // optionalAttrs enableRDW {
"NetworkManager/dispatcher.d/99tlp-rdw-nm".source =
"${tlp}/etc/NetworkManager/dispatcher.d/99tlp-rdw-nm";
};
environment.systemPackages = [ tlp ];
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 = [ tlp ];
systemd = {
# use native tlp instead because it can also differentiate between AC/BAT
services.cpufreq.enable = false;
packages = [ tlp ];
# 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,70 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.trezord;
in {
### docs
meta = {
doc = ./trezord.xml;
};
### interface
options = {
services.trezord = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Enable Trezor bridge daemon, for use with Trezor hardware bitcoin wallets.
'';
};
emulator.enable = mkOption {
type = types.bool;
default = false;
description = ''
Enable Trezor emulator support.
'';
};
emulator.port = mkOption {
type = types.port;
default = 21324;
description = ''
Listening port for the Trezor emulator.
'';
};
};
};
### implementation
config = 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 ${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,26 @@
<chapter xmlns="http://docbook.org/ns/docbook"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xi="http://www.w3.org/2001/XInclude"
version="5.0"
xml:id="trezor">
<title>Trezor</title>
<para>
Trezor is an open-source cryptocurrency hardware wallet and security token
allowing secure storage of private keys.
</para>
<para>
It offers advanced features such U2F two-factor authorization, SSH login
through
<link xlink:href="https://wiki.trezor.io/Apps:SSH_agent">Trezor SSH agent</link>,
<link xlink:href="https://wiki.trezor.io/GPG">GPG</link> and a
<link xlink:href="https://wiki.trezor.io/Trezor_Password_Manager">password manager</link>.
For more information, guides and documentation, see <link xlink:href="https://wiki.trezor.io"/>.
</para>
<para>
To enable Trezor support, add the following to your <filename>configuration.nix</filename>:
<programlisting>
<xref linkend="opt-services.trezord.enable"/> = true;
</programlisting>
This will add all necessary udev rules and start Trezor Bridge.
</para>
</chapter>

View file

@ -0,0 +1,122 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.triggerhappy;
socket = "/run/thd.socket";
configFile = pkgs.writeText "triggerhappy.conf" ''
${concatMapStringsSep "\n"
({ keys, event, cmd, ... }:
''${concatMapStringsSep "+" (x: "KEY_" + x) keys} ${toString { press = 1; hold = 2; release = 0; }.${event}} ${cmd}''
)
cfg.bindings}
${cfg.extraConfig}
'';
bindingCfg = { ... }: {
options = {
keys = mkOption {
type = types.listOf types.str;
description = "List of keys to match. Key names as defined in linux/input-event-codes.h";
};
event = mkOption {
type = types.enum ["press" "hold" "release"];
default = "press";
description = "Event to match.";
};
cmd = mkOption {
type = types.str;
description = "What to run.";
};
};
};
in
{
###### interface
options = {
services.triggerhappy = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable the <command>triggerhappy</command> hotkey daemon.
'';
};
user = mkOption {
type = types.str;
default = "nobody";
example = "root";
description = ''
User account under which <command>triggerhappy</command> runs.
'';
};
bindings = mkOption {
type = types.listOf (types.submodule bindingCfg);
default = [];
example = lib.literalExpression ''
[ { keys = ["PLAYPAUSE"]; cmd = "''${pkgs.mpc-cli}/bin/mpc -q toggle"; } ]
'';
description = ''
Key bindings for <command>triggerhappy</command>.
'';
};
extraConfig = mkOption {
type = types.lines;
default = "";
description = ''
Literal contents to append to the end of <command>triggerhappy</command> configuration file.
'';
};
};
};
###### implementation
config = 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";
serviceConfig = {
ExecStart = "${pkgs.triggerhappy}/bin/thd ${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,448 @@
{ config, lib, pkgs, ... }:
with lib;
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
'';
# networkd link files are used early by udev to set up interfaces early.
# This must be done in stage 1 to avoid race conditions between udev and
# network daemons.
# TODO move this into the initrd-network module when it exists
initrdLinkUnits = pkgs.runCommand "initrd-link-units" {} ''
mkdir -p $out
ln -s ${udev}/lib/systemd/network/*.link $out/
${lib.concatMapStringsSep "\n" (file: "ln -s ${file} $out/") (lib.mapAttrsToList (n: v: "${v.unit}/${n}") (lib.filterAttrs (n: _: hasSuffix ".link" n) config.systemd.network.units))}
'';
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 = ''
# Miscellaneous devices.
KERNEL=="kvm", MODE="0666"
# Needed for gpm.
SUBSYSTEM=="input", KERNEL=="mice", TAG+="systemd"
'';
# Perform substitutions in all udev rules files.
udevRulesFor = { name, udevPackages, udevPath, udev, systemd, binPackages, initrdBin ? null }: pkgs.runCommand name
{ preferLocalBuild = true;
allowSubstitutes = false;
packages = unique (map toString udevPackages);
}
''
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 \"/sbin/modprobe \"${pkgs.kmod}/bin/modprobe \
--replace \"/sbin/mdadm \"${pkgs.mdadm}/sbin/mdadm \
--replace \"/sbin/blkid \"${pkgs.util-linux}/sbin/blkid \
--replace \"/bin/mount \"${pkgs.util-linux}/bin/mount \
--replace /usr/bin/readlink ${pkgs.coreutils}/bin/readlink \
--replace /usr/bin/basename ${pkgs.coreutils}/bin/basename
${optionalString (initrdBin != null) ''
substituteInPlace $i --replace '/run/current-system/systemd' "${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
grep -l '\B\(/usr\)\?/s\?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
# If auto-configuration is disabled, then remove
# udev's 80-drivers.rules file, which contains rules for
# automatically calling modprobe.
${optionalString (!config.boot.hardwareScan) ''
ln -s /dev/null $out/80-drivers.rules
''}
'';
hwdbBin = pkgs.runCommand "hwdb.bin"
{ preferLocalBuild = true;
allowSubstitutes = false;
packages = 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.udev}/bin/udevadm hwdb --update --root=$(pwd) 2>&1)"
echo "$res"
[ -z "$(echo "$res" | egrep '^Error')" ]
mv etc/udev/hwdb.bin $out
'';
compressFirmware = if config.boot.kernelPackages.kernelAtLeast "5.3" then
pkgs.compressFirmwareXz
else
id;
# 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 = mkOption {
type = 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 = {
packages = mkOption {
type = types.listOf types.path;
default = [];
description = ''
List of packages containing <command>udev</command> rules.
All files found in
<filename><replaceable>pkg</replaceable>/etc/udev/rules.d</filename> and
<filename><replaceable>pkg</replaceable>/lib/udev/rules.d</filename>
will be included.
'';
apply = map getBin;
};
path = mkOption {
type = types.listOf types.path;
default = [];
description = ''
Packages added to the <envar>PATH</envar> environment variable when
executing programs from Udev rules.
'';
};
extraRules = mkOption {
default = "";
example = ''
ENV{ID_VENDOR_ID}=="046d", ENV{ID_MODEL_ID}=="0825", ENV{PULSE_IGNORE}="1"
'';
type = types.lines;
description = ''
Additional <command>udev</command> rules. They'll be written
into file <filename>99-local.rules</filename>. Thus they are
read and applied after all other rules.
'';
};
extraHwdb = mkOption {
default = "";
example = ''
evdev:input:b0003v05AFp8277*
KEYBOARD_KEY_70039=leftalt
KEYBOARD_KEY_700e2=leftctrl
'';
type = types.lines;
description = ''
Additional <command>hwdb</command> files. They'll be written
into file <filename>99-local.hwdb</filename>. Thus they are
read after all other files.
'';
};
};
hardware.firmware = mkOption {
type = types.listOf 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;
};
};
networking.usePredictableInterfaceNames = mkOption {
default = true;
type = types.bool;
description = ''
Whether to assign <link
xlink:href='http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames'>predictable
names to network interfaces</link>. If enabled, interfaces
are assigned names that contain topology information
(e.g. <literal>wlp3s0</literal>) 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
<literal>eth0</literal> and <literal>eth1</literal> flipping
unpredictably.
'';
};
boot.initrd.services.udev = {
packages = mkOption {
type = types.listOf types.path;
default = [];
visible = false;
description = ''
<emphasis>This will only be used when systemd is used in stage 1.</emphasis>
List of packages containing <command>udev</command> rules that will be copied to stage 1.
All files found in
<filename><replaceable>pkg</replaceable>/etc/udev/rules.d</filename> and
<filename><replaceable>pkg</replaceable>/lib/udev/rules.d</filename>
will be included.
'';
};
binPackages = mkOption {
type = types.listOf types.path;
default = [];
visible = false;
description = ''
<emphasis>This will only be used when systemd is used in stage 1.</emphasis>
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 getBin;
};
rules = mkOption {
default = "";
example = ''
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:1D:60:B9:6D:4F", KERNEL=="eth*", NAME="my_fast_network_card"
'';
type = types.lines;
description = ''
<command>udev</command> rules to include in the initrd
<emphasis>only</emphasis>. They'll be written into file
<filename>99-local.rules</filename>. Thus they are read and applied
after the essential initrd rules.
'';
};
};
};
###### implementation
config = mkIf (!config.boot.isContainer) {
services.udev.extraRules = nixosRules;
services.udev.packages = [ extraUdevRules extraHwdbFile ];
services.udev.path = [ pkgs.coreutils pkgs.gnused pkgs.gnugrep pkgs.util-linux udev ];
boot.kernelParams = mkIf (!config.networking.usePredictableInterfaceNames) [ "net.ifnames=0" ];
boot.initrd.extraUdevRulesCommands = optionalString (!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.systemd.additionalUpstreamUnits = [
# TODO: "initrd-udevadm-cleanup-db.service" is commented out because of https://github.com/systemd/systemd/issues/12953
"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 ];
};
"/etc/systemd/network".source = initrdLinkUnits;
};
# Insert initrd rules
boot.initrd.services.udev.packages = [
initrdUdevRules
(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;
};
system.requiredKernelConfig = with config.lib.kernelConfig; [
(isEnabled "UNIX")
(isYes "INOTIFY_USER")
(isYes "NET")
];
# We don't place this into `extraModprobeConfig` so that stage-1 ramdisk doesn't bloat.
environment.etc."modprobe.d/firmware.conf".text = "options firmware_class path=${config.hardware.firmware}/lib/firmware";
system.activationScripts.udevd =
''
# 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 = cfg.packages;
};
};
imports = [
(mkRenamedOptionModule [ "services" "udev" "initrdRules" ] [ "boot" "initrd" "services" "udev" "rules" ])
];
}

View file

@ -0,0 +1,85 @@
# Udisks daemon.
{ config, lib, pkgs, ... }:
with lib;
let
settingsFormat = pkgs.formats.ini {
listToValue = concatMapStringsSep "," (generators.mkValueStringDefault {});
};
configFiles = mapAttrs (name: value: (settingsFormat.generate name value)) (mapAttrs' (name: value: nameValuePair name value ) config.services.udisks2.settings);
in
{
###### interface
options = {
services.udisks2 = {
enable = mkOption {
type = types.bool;
default = true;
description = ''
Whether to enable Udisks, a DBus service that allows
applications to query and manipulate storage devices.
'';
};
settings = mkOption rec {
type = types.attrsOf settingsFormat.type;
apply = recursiveUpdate default;
default = {
"udisks2.conf" = {
udisks2 = {
modules = [ "*" ];
modules_load_preference = "ondemand";
};
defaults = {
encryption = "luks2";
};
};
};
example = literalExpression ''
{
"WDC-WD10EZEX-60M2NA0-WD-WCC3F3SJ0698.conf" = {
ATA = {
StandbyTimeout = 50;
};
};
};
'';
description = ''
Options passed to udisksd.
See <link xlink:href="http://manpages.ubuntu.com/manpages/latest/en/man5/udisks2.conf.5.html">here</link> and
drive configuration in <link xlink:href="http://manpages.ubuntu.com/manpages/latest/en/man8/udisks.8.html">here</link> for supported options.
'';
};
};
};
###### implementation
config = mkIf config.services.udisks2.enable {
environment.systemPackages = [ pkgs.udisks2 ];
environment.etc = mapAttrs' (name: value: nameValuePair "udisks2/${name}" { source = value; } ) configFiles;
security.polkit.enable = true;
services.dbus.packages = [ pkgs.udisks2 ];
systemd.tmpfiles.rules = [ "d /var/lib/udisks2 0755 root root -" ];
services.udev.packages = [ pkgs.udisks2 ];
systemd.packages = [ pkgs.udisks2 ];
};
}

View file

@ -0,0 +1,190 @@
{ config, pkgs, lib, ... }:
with lib;
let
cfg = config.services.undervolt;
mkPLimit = limit: window:
if (isNull limit && isNull window) then null
else assert asserts.assertMsg (!isNull limit && !isNull window) "Both power limit and window must be set";
"${toString limit} ${toString window}";
cliArgs = lib.cli.toGNUCommandLine {} {
inherit (cfg)
verbose
temp
;
# `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 = 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 = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable verbose logging.
'';
};
package = mkOption {
type = types.package;
default = pkgs.undervolt;
defaultText = literalExpression "pkgs.undervolt";
description = ''
undervolt derivation to use.
'';
};
coreOffset = mkOption {
type = types.nullOr types.int;
default = null;
description = ''
The amount of voltage in mV to offset the CPU cores by.
'';
};
gpuOffset = mkOption {
type = types.nullOr types.int;
default = null;
description = ''
The amount of voltage in mV to offset the GPU by.
'';
};
uncoreOffset = mkOption {
type = types.nullOr types.int;
default = null;
description = ''
The amount of voltage in mV to offset uncore by.
'';
};
analogioOffset = mkOption {
type = types.nullOr types.int;
default = null;
description = ''
The amount of voltage in mV to offset analogio by.
'';
};
temp = mkOption {
type = types.nullOr types.int;
default = null;
description = ''
The temperature target in Celsius degrees.
'';
};
tempAc = mkOption {
type = types.nullOr types.int;
default = null;
description = ''
The temperature target on AC power in Celsius degrees.
'';
};
tempBat = mkOption {
type = types.nullOr types.int;
default = null;
description = ''
The temperature target on battery power in Celsius degrees.
'';
};
p1.limit = mkOption {
type = with types; nullOr int;
default = null;
description = ''
The P1 Power Limit in Watts.
Both limit and window must be set.
'';
};
p1.window = mkOption {
type = with types; nullOr (oneOf [ float int ]);
default = null;
description = ''
The P1 Time Window in seconds.
Both limit and window must be set.
'';
};
p2.limit = mkOption {
type = with types; nullOr int;
default = null;
description = ''
The P2 Power Limit in Watts.
Both limit and window must be set.
'';
};
p2.window = mkOption {
type = with types; nullOr (oneOf [ float int ]);
default = null;
description = ''
The P2 Time Window in seconds.
Both limit and window must be set.
'';
};
useTimer = mkOption {
type = 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 = mkIf cfg.enable {
boot.kernelModules = [ "msr" ];
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 = 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,239 @@
# Upower daemon.
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.upower;
in
{
###### interface
options = {
services.upower = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable Upower, a DBus service that provides power
management support to applications.
'';
};
package = mkOption {
type = types.package;
default = pkgs.upower;
defaultText = literalExpression "pkgs.upower";
description = ''
Which upower package to use.
'';
};
enableWattsUpPro = mkOption {
type = 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:
<itemizedlist>
<listitem><para>Sparkfun FT232 breakout board</para></listitem>
<listitem><para>Parallax Propeller</para></listitem>
</itemizedlist>
'';
};
noPollBatteries = mkOption {
type = 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 = mkOption {
type = 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
logind.conf(5).
'';
};
usePercentageForPolicy = mkOption {
type = 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 = mkOption {
type = types.ints.unsigned;
default = 10;
description = ''
When <literal>usePercentageForPolicy</literal> is
<literal>true</literal>, 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 <literal>percentageLow</literal>,
<literal>percentageCritical</literal> and
<literal>percentageAction</literal>) is invalid, or not in descending
order, the defaults will be used.
'';
};
percentageCritical = mkOption {
type = types.ints.unsigned;
default = 3;
description = ''
When <literal>usePercentageForPolicy</literal> is
<literal>true</literal>, 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 <literal>percentageLow</literal>,
<literal>percentageCritical</literal> and
<literal>percentageAction</literal>) is invalid, or not in descending
order, the defaults will be used.
'';
};
percentageAction = mkOption {
type = types.ints.unsigned;
default = 2;
description = ''
When <literal>usePercentageForPolicy</literal> is
<literal>true</literal>, 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 <literal>percentageLow</literal>,
<literal>percentageCritical</literal> and
<literal>percentageAction</literal>) is invalid, or not in descending
order, the defaults will be used.
'';
};
timeLow = mkOption {
type = types.ints.unsigned;
default = 1200;
description = ''
When <literal>usePercentageForPolicy</literal> is
<literal>false</literal>, the time remaining in seconds at which
UPower will consider the battery low.
If any value (of <literal>timeLow</literal>,
<literal>timeCritical</literal> and <literal>timeAction</literal>) is
invalid, or not in descending order, the defaults will be used.
'';
};
timeCritical = mkOption {
type = types.ints.unsigned;
default = 300;
description = ''
When <literal>usePercentageForPolicy</literal> is
<literal>false</literal>, the time remaining in seconds at which
UPower will consider the battery critical.
If any value (of <literal>timeLow</literal>,
<literal>timeCritical</literal> and <literal>timeAction</literal>) is
invalid, or not in descending order, the defaults will be used.
'';
};
timeAction = mkOption {
type = types.ints.unsigned;
default = 120;
description = ''
When <literal>usePercentageForPolicy</literal> is
<literal>false</literal>, the time remaining in seconds at which
UPower will take action for the critical battery level.
If any value (of <literal>timeLow</literal>,
<literal>timeCritical</literal> and <literal>timeAction</literal>) is
invalid, or not in descending order, the defaults will be used.
'';
};
criticalPowerAction = mkOption {
type = types.enum [ "PowerOff" "Hibernate" "HybridSleep" ];
default = "HybridSleep";
description = ''
The action to take when <literal>timeAction</literal> or
<literal>percentageAction</literal> has been reached for the batteries
(UPS or laptop batteries) supplying the computer
'';
};
};
};
###### implementation
config = mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
services.dbus.packages = [ cfg.package ];
services.udev.packages = [ cfg.package ];
systemd.packages = [ cfg.package ];
environment.etc."UPower/UPower.conf".text = 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;
CriticalPowerAction = cfg.criticalPowerAction;
};
};
};
}

View file

@ -0,0 +1,76 @@
{ config, lib, pkgs, ... }:
with lib;
let
defaultUserGroup = "usbmux";
apple = "05ac";
cfg = config.services.usbmuxd;
in
{
options.services.usbmuxd = {
enable = mkOption {
type = 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 = mkOption {
type = types.str;
default = defaultUserGroup;
description = ''
The user usbmuxd should use to run after startup.
'';
};
group = mkOption {
type = types.str;
default = defaultUserGroup;
description = ''
The group usbmuxd should use to run after startup.
'';
};
};
config = mkIf cfg.enable {
users.users = optionalAttrs (cfg.user == defaultUserGroup) {
${cfg.user} = {
description = "usbmuxd user";
group = cfg.group;
isSystemUser = true;
};
};
users.groups = 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 = "${pkgs.udev}/bin/udevadm trigger -s usb -a idVendor=${apple}";
ExecStart = "${pkgs.usbmuxd}/bin/usbmuxd -U ${cfg.user} -f";
};
};
};
}

View file

@ -0,0 +1,47 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.usbrelayd;
in
{
options.services.usbrelayd = with types; {
enable = mkEnableOption "USB Relay MQTT daemon";
broker = mkOption {
type = str;
description = "Hostname or IP address of your MQTT Broker.";
default = "127.0.0.1";
example = [
"mqtt"
"192.168.1.1"
];
};
clientName = mkOption {
type = str;
description = "Name, your client connects as.";
default = "MyUSBRelay";
};
};
config = 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.users.usbrelay = {
isSystemUser = true;
group = "usbrelay";
};
users.groups.usbrelay = { };
};
meta = {
maintainers = with lib.maintainers; [ wentasah ];
};
}

View file

@ -0,0 +1,82 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.vdr;
libDir = "/var/lib/vdr";
in {
###### interface
options = {
services.vdr = {
enable = mkEnableOption "VDR. Please put config into ${libDir}";
package = mkOption {
type = types.package;
default = pkgs.vdr;
defaultText = literalExpression "pkgs.vdr";
example = literalExpression "pkgs.wrapVdr.override { plugins = with pkgs.vdrPlugins; [ hello ]; }";
description = "Package to use.";
};
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";
};
};
###### implementation
config = mkIf cfg.enable (mkMerge [{
systemd.tmpfiles.rules = [
"d ${cfg.videoDir} 0755 vdr vdr -"
"Z ${cfg.videoDir} - vdr vdr -"
];
systemd.services.vdr = {
description = "VDR";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = ''
${cfg.package}/bin/vdr \
--video="${cfg.videoDir}" \
--config="${libDir}" \
${escapeShellArgs cfg.extraArguments}
'';
User = "vdr";
CacheDirectory = "vdr";
StateDirectory = "vdr";
Restart = "on-failure";
};
};
users.users.vdr = {
group = "vdr";
home = libDir;
isSystemUser = true;
};
users.groups.vdr = {};
}
(mkIf cfg.enableLirc {
services.lirc.enable = true;
users.users.vdr.extraGroups = [ "lirc" ];
services.vdr.extraArguments = [
"--lirc=${config.passthru.lirc.socket}"
];
})]);
}

View file

@ -0,0 +1,20 @@
{ config, pkgs, lib, ... }:
let
cfg = config.services.hardware.xow;
in {
options.services.hardware.xow = {
enable = lib.mkEnableOption "xow as a systemd service";
};
config = lib.mkIf cfg.enable {
hardware.uinput.enable = true;
boot.extraModprobeConfig = lib.readFile "${pkgs.xow}/lib/modprobe.d/xow-blacklist.conf";
systemd.packages = [ pkgs.xow ];
systemd.services.xow.wantedBy = [ "multi-user.target" ];
services.udev.packages = [ pkgs.xow ];
};
}