about summary refs log tree commit diff
path: root/nixos/modules/services/hardware
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services/hardware')
-rw-r--r--nixos/modules/services/hardware/acpid.nix114
-rw-r--r--nixos/modules/services/hardware/bluetooth.nix41
-rw-r--r--nixos/modules/services/hardware/nvidia-optimus.nix43
-rw-r--r--nixos/modules/services/hardware/pcscd.nix46
-rw-r--r--nixos/modules/services/hardware/pommed.nix49
-rw-r--r--nixos/modules/services/hardware/sane.nix40
-rw-r--r--nixos/modules/services/hardware/thinkfan.nix95
-rw-r--r--nixos/modules/services/hardware/udev.nix239
-rw-r--r--nixos/modules/services/hardware/udisks.nix44
-rw-r--r--nixos/modules/services/hardware/udisks2.nix53
-rw-r--r--nixos/modules/services/hardware/upower.nix64
11 files changed, 828 insertions, 0 deletions
diff --git a/nixos/modules/services/hardware/acpid.nix b/nixos/modules/services/hardware/acpid.nix
new file mode 100644
index 0000000000000..6a595f8306bc6
--- /dev/null
+++ b/nixos/modules/services/hardware/acpid.nix
@@ -0,0 +1,114 @@
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+let
+
+  acpiConfDir = pkgs.runCommand "acpi-events" {}
+    ''
+      ensureDir $out
+      ${
+        # Generate a configuration file for each event. (You can't have
+        # multiple events in one config file...)
+        let f = event:
+          ''
+            fn=$out/${event.name}
+            echo "event=${event.event}" > $fn
+            echo "action=${pkgs.writeScript "${event.name}.sh" event.action}" >> $fn
+          '';
+        in pkgs.lib.concatMapStrings f events
+      }
+    '';
+
+  events = [powerEvent lidEvent acEvent];
+
+  # Called when the power button is pressed.
+  powerEvent =
+    { name = "power-button";
+      event = "button/power.*";
+      action =
+        ''
+          #! ${pkgs.bash}/bin/sh
+          ${config.services.acpid.powerEventCommands}
+        '';
+    };
+
+  # Called when the laptop lid is opened/closed.
+  lidEvent =
+    { name = "lid";
+      event = "button/lid.*";
+      action =
+        ''
+          #! ${pkgs.bash}/bin/sh
+          ${config.services.acpid.lidEventCommands}
+        '';
+    };
+
+  # Called when the AC power is connected or disconnected.
+  acEvent =
+    { name = "ac-power";
+      event = "ac_adapter.*";
+      action =
+        ''
+          #! ${pkgs.bash}/bin/sh
+          ${config.services.acpid.acEventCommands}
+        '';
+    };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.acpid = {
+
+      enable = mkOption {
+        default = false;
+        description = "Whether to enable the ACPI daemon.";
+      };
+
+      powerEventCommands = mkOption {
+        default = "";
+        description = "Shell commands to execute on a button/power.* event.";
+      };
+
+      lidEventCommands = mkOption {
+        default = "";
+        description = "Shell commands to execute on a button/lid.* event.";
+      };
+
+      acEventCommands = mkOption {
+        default = "";
+        description = "Shell commands to execute on an ac_adapter.* event.";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.acpid.enable {
+
+    jobs.acpid =
+      { description = "ACPI Daemon";
+
+        wantedBy = [ "multi-user.target" ];
+        after = [ "systemd-udev-settle.service" ];
+
+        path = [ pkgs.acpid ];
+
+        daemonType = "fork";
+
+        exec = "acpid --confdir ${acpiConfDir}";
+
+        unitConfig.ConditionPathExists = [ "/proc/acpi" ];
+      };
+
+  };
+
+}
diff --git a/nixos/modules/services/hardware/bluetooth.nix b/nixos/modules/services/hardware/bluetooth.nix
new file mode 100644
index 0000000000000..6bc0ad0bf7746
--- /dev/null
+++ b/nixos/modules/services/hardware/bluetooth.nix
@@ -0,0 +1,41 @@
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+{
+
+  ###### interface
+
+  options = {
+
+    hardware.bluetooth.enable = mkOption {
+      default = false;
+      description = "Whether to enable support for Bluetooth.";
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.hardware.bluetooth.enable {
+
+    environment.systemPackages = [ pkgs.bluez pkgs.openobex pkgs.obexftp ];
+
+    services.udev.packages = [ pkgs.bluez ];
+
+    services.dbus.packages = [ pkgs.bluez ];
+
+    systemd.services."dbus-org.bluez" = {
+      description = "Bluetooth service";
+      serviceConfig = {
+        Type = "dbus";
+        BusName = "org.bluez";
+        ExecStart = "${pkgs.bluez}/sbin/bluetoothd -n";
+      };
+      wantedBy = [ "bluetooth.target" ];
+    };
+
+  };
+
+}
diff --git a/nixos/modules/services/hardware/nvidia-optimus.nix b/nixos/modules/services/hardware/nvidia-optimus.nix
new file mode 100644
index 0000000000000..4c0ce794d4f7d
--- /dev/null
+++ b/nixos/modules/services/hardware/nvidia-optimus.nix
@@ -0,0 +1,43 @@
+{ config, pkgs, ... }:
+
+let kernel = config.boot.kernelPackages; in
+
+{
+
+  ###### interface
+
+  options = {
+
+    hardware.nvidiaOptimus.disable = pkgs.lib.mkOption {
+      default = false;
+      type = pkgs.lib.types.bool;
+      description = ''
+        Completely disable the NVIDIA graphics card and use the
+        integrated graphics processor instead.
+      '';
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = pkgs.lib.mkIf config.hardware.nvidiaOptimus.disable {
+    boot.blacklistedKernelModules = ["nouveau" "nvidia" "nvidiafb"];
+    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 ];
+    };
+  };
+
+}
diff --git a/nixos/modules/services/hardware/pcscd.nix b/nixos/modules/services/hardware/pcscd.nix
new file mode 100644
index 0000000000000..9f389efc06d49
--- /dev/null
+++ b/nixos/modules/services/hardware/pcscd.nix
@@ -0,0 +1,46 @@
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.pcscd = {
+
+      enable = mkOption {
+        default = false;
+        description = "Whether to enable the PCSC-Lite daemon.";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.pcscd.enable {
+
+    jobs.pcscd =
+      { description = "PCSC-Lite daemon";
+
+        startOn = "started udev";
+
+        daemonType = "fork";
+
+        # Add to the drivers directory the only drivers we have by now: ccid
+        preStart = ''
+            mkdir -p /var/lib/pcsc
+            rm -Rf /var/lib/pcsc/drivers
+            ln -s ${pkgs.ccid}/pcsc/drivers /var/lib/pcsc/
+        '';
+
+        exec = "${pkgs.pcsclite}/sbin/pcscd";
+      };
+
+  };
+
+}
diff --git a/nixos/modules/services/hardware/pommed.nix b/nixos/modules/services/hardware/pommed.nix
new file mode 100644
index 0000000000000..32599554fc12c
--- /dev/null
+++ b/nixos/modules/services/hardware/pommed.nix
@@ -0,0 +1,49 @@
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+{
+
+  options.services.hardware.pommed = {
+    enable = mkOption {
+      default = false;
+       description = ''
+        Whether to use the pommed tool to handle Apple laptop keyboard hotkeys.
+      '';
+    };
+
+    configFile = mkOption {
+      default = "${pkgs.pommed}/etc/pommed.conf";
+      description = ''
+        The contents of the pommed.conf file.
+      '';
+    };
+  };
+
+  config = mkIf config.services.hardware.pommed.enable {
+    environment.systemPackages = [ pkgs.polkit ];
+
+    environment.etc = [
+      { source = config.services.hardware.pommed.configFile;
+        target = "pommed.conf";
+      }
+    ];
+
+    services.dbus.packages = [ pkgs.pommed ];
+
+    jobs.pommed = { name = "pommed";
+
+      description = "Pommed hotkey management";
+
+      startOn = "started dbus";
+
+      postStop = "rm -f /var/run/pommed.pid";
+
+      exec = "${pkgs.pommed}/bin/pommed";
+
+      daemonType = "fork";
+
+      path = [ pkgs.eject ];
+    };
+  };
+}
diff --git a/nixos/modules/services/hardware/sane.nix b/nixos/modules/services/hardware/sane.nix
new file mode 100644
index 0000000000000..905445f22c1b5
--- /dev/null
+++ b/nixos/modules/services/hardware/sane.nix
@@ -0,0 +1,40 @@
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+{
+
+  ###### interface
+
+  options = {
+
+    hardware.sane.enable = mkOption {
+      default = false;
+      description = "Enable support for SANE scanners.";
+    };
+
+    hardware.sane.snapshot = mkOption {
+      default = false;
+      description = "Use a development snapshot of SANE scanner drivers.";
+    };
+
+  };
+
+
+  ###### implementation
+
+    config = let pkg = if config.hardware.sane.snapshot
+                          then pkgs.saneBackendsGit
+                          else pkgs.saneBackends;
+      in mkIf config.hardware.sane.enable {
+           environment.systemPackages = [ pkg ];
+           services.udev.packages = [ pkg ];
+           
+           users.extraGroups = singleton {
+             name = "scanner";
+             gid = config.ids.gids.scanner;
+           };
+
+      };
+
+}
diff --git a/nixos/modules/services/hardware/thinkfan.nix b/nixos/modules/services/hardware/thinkfan.nix
new file mode 100644
index 0000000000000..b39c9cb1d9bb0
--- /dev/null
+++ b/nixos/modules/services/hardware/thinkfan.nix
@@ -0,0 +1,95 @@
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+let
+
+  cfg = config.services.thinkfan;
+  configFile = pkgs.writeText "thinkfan.conf" ''
+    # ATTENTION: There is only very basic sanity checking on the configuration.
+    # That means you can set your temperature limits as insane as you like. You
+    # can do anything stupid, e.g. turn off your fan when your CPU reaches 70°C.
+    #
+    # That's why this program is called THINKfan: You gotta think for yourself.
+    #
+    ######################################################################
+    #
+    # IBM/Lenovo Thinkpads (thinkpad_acpi, /proc/acpi/ibm)
+    # ====================================================
+    #
+    # IMPORTANT:
+    #
+    # To keep your HD from overheating, you have to specify a correction value for
+    # the sensor that has the HD's temperature. You need to do this because
+    # thinkfan uses only the highest temperature it can find in the system, and
+    # that'll most likely never be your HD, as most HDs are already out of spec
+    # when they reach 55 °C.
+    # Correction values are applied from left to right in the same order as the
+    # temperatures are read from the file.
+    #
+    # For example:
+    # sensor /proc/acpi/ibm/thermal (0, 0, 10)
+    # will add a fixed value of 10 °C the 3rd value read from that file. Check out
+    # http://www.thinkwiki.org/wiki/Thermal_Sensors to find out how much you may
+    # want to add to certain temperatures.
+    
+    #  Syntax:
+    #  (LEVEL, LOW, HIGH)
+    #  LEVEL is the fan level to use (0-7 with thinkpad_acpi)
+    #  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.
+    #
+
+    sensor ${cfg.sensor} (0, 10, 15, 2, 10, 5, 0, 3, 0, 3)
+    
+    (0,     0,      55)
+    (1,     48,     60)
+    (2,     50,     61)
+    (3,     52,     63)
+    (6,     56,     65)
+    (7,     60,     85)
+    (127,   80,     32767)
+  '';
+
+in {
+
+  options = {
+
+    services.thinkfan = {
+
+      enable = mkOption {
+        default = false;
+        description = ''
+          Whether to enable thinkfan, fan controller for ibm/lenovo thinkpads.
+        '';
+      };
+
+      sensor = mkOption {
+        default = "/proc/acpi/ibm/thermal";
+        description =''
+          Sensor used by thinkfan
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.thinkfan ];
+
+    systemd.services.thinkfan = {
+      description = "Thinkfan";
+      after = [ "basic.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.thinkfan ];
+      serviceConfig.ExecStart = "${pkgs.thinkfan}/bin/thinkfan -n -c ${configFile}";
+    };
+
+    boot.extraModprobeConfig = "options thinkpad_acpi experimental=1 fan_control=1";
+
+  };
+
+}
diff --git a/nixos/modules/services/hardware/udev.nix b/nixos/modules/services/hardware/udev.nix
new file mode 100644
index 0000000000000..37dba8ce71dc1
--- /dev/null
+++ b/nixos/modules/services/hardware/udev.nix
@@ -0,0 +1,239 @@
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+let
+
+  inherit (pkgs) stdenv writeText procps;
+
+  udev = config.systemd.package;
+
+  cfg = config.services.udev;
+
+  extraUdevRules = pkgs.writeTextFile {
+    name = "extra-udev-rules";
+    text = cfg.extraRules;
+    destination = "/etc/udev/rules.d/10-local.rules";
+  };
+
+  nixosRules = ''
+    # Miscellaneous devices.
+    KERNEL=="kvm",                  MODE="0666"
+    KERNEL=="kqemu",                MODE="0666"
+  '';
+
+  # Perform substitutions in all udev rules files.
+  udevRules = stdenv.mkDerivation {
+    name = "udev-rules";
+    buildCommand = ''
+      mkdir -p $out
+      shopt -s nullglob
+
+      # 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 ${toString cfg.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 \"${config.system.sbin.modprobe}/sbin/modprobe \
+          --replace \"/sbin/mdadm \"${pkgs.mdadm}/sbin/mdadm \
+          --replace \"/sbin/blkid \"${pkgs.utillinux}/sbin/blkid \
+          --replace \"/bin/mount \"${pkgs.utillinux}/bin/mount
+      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 ${pkgs.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 [[ ! -x $i ]]; then
+          echo "FAIL"
+          echo "$i is called in udev rules but not installed by udev"
+          exit 1
+        fi
+      done
+      echo "OK"
+
+      echo "Consider fixing the following udev rules:"
+      for i in ${toString cfg.packages}; do
+        grep -l '\(RUN+\|IMPORT{program}\)="\(/usr\)\?/s\?bin' $i/*/udev/rules.d/* || true
+      done
+
+      ${optionalString (!config.networking.usePredictableInterfaceNames) ''
+        ln -s /dev/null $out/80-net-name-slot.rules
+      ''}
+
+      # 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
+      ''}
+    ''; # */
+  };
+
+  # 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 {
+      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 {
+        default = [];
+        merge = mergeListOption;
+        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.
+        '';
+      };
+
+      path = mkOption {
+        default = [];
+        merge = mergeListOption;
+        description = ''
+          Packages added to the <envar>PATH</envar> environment variable when
+          executing programs from Udev rules.
+        '';
+      };
+
+      extraRules = mkOption {
+        default = "";
+        example = ''
+          KERNEL=="eth*", ATTR{address}=="00:1D:60:B9:6D:4F", NAME="my_fast_network_card"
+        '';
+        type = types.lines;
+        description = ''
+          Additional <command>udev</command> rules. They'll be written
+          into file <filename>10-local.rules</filename>. Thus they are
+          read before all other rules.
+        '';
+      };
+
+    };
+
+    hardware.firmware = mkOption {
+      default = [];
+      example = [ "/root/my-firmware" ];
+      merge = mergeListOption;
+      description = ''
+        List of directories 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 more than one path contains a
+        firmware file with the same name, the first path in the list
+        takes precedence.  Note that you must rebuild your system if
+        you add files to any of these directories.  For quick testing,
+        put firmware files in /root/test-firmware and add that
+        directory to the list.
+        Note that you can also add firmware packages to this
+        list as these are directories in the nix store.
+      '';
+      apply = list: pkgs.buildEnv {
+        name = "firmware";
+        paths = list;
+        pathsToLink = [ "/" ];
+        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.
+      '';
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = {
+
+    services.udev.extraRules = nixosRules;
+
+    services.udev.packages = [ extraUdevRules ];
+
+    services.udev.path = [ pkgs.coreutils pkgs.gnused pkgs.gnugrep pkgs.utillinux udev ];
+
+    environment.etc =
+      [ { source = udevRules;
+          target = "udev/rules.d";
+        }
+      ];
+
+    system.requiredKernelConfig = with config.lib.kernelConfig; [
+      (isEnabled "UNIX")
+      (isYes "INOTIFY_USER")
+      (isYes "NET")
+    ];
+
+    boot.extraModprobeConfig = "options firmware_class path=${config.hardware.firmware}";
+
+    system.activationScripts.clearHotplug =
+      ''
+        echo "" > /proc/sys/kernel/hotplug
+      '';
+
+  };
+}
diff --git a/nixos/modules/services/hardware/udisks.nix b/nixos/modules/services/hardware/udisks.nix
new file mode 100644
index 0000000000000..1ba17c589d23a
--- /dev/null
+++ b/nixos/modules/services/hardware/udisks.nix
@@ -0,0 +1,44 @@
+# Udisks daemon.
+
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.udisks = {
+
+      enable = mkOption {
+        default = false;
+        description = ''
+          Whether to enable Udisks, a DBus service that allows
+          applications to query and manipulate storage devices.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.udisks.enable {
+
+    environment.systemPackages = [ pkgs.udisks ];
+
+    services.dbus.packages = [ pkgs.udisks ];
+
+    system.activationScripts.udisks =
+      ''
+        mkdir -m 0755 -p /var/lib/udisks
+      '';
+
+    services.udev.packages = [ pkgs.udisks ];
+  };
+
+}
diff --git a/nixos/modules/services/hardware/udisks2.nix b/nixos/modules/services/hardware/udisks2.nix
new file mode 100644
index 0000000000000..eae4172ccb3ed
--- /dev/null
+++ b/nixos/modules/services/hardware/udisks2.nix
@@ -0,0 +1,53 @@
+# Udisks daemon.
+
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.udisks2 = {
+
+      enable = mkOption {
+        default = false;
+        description = ''
+          Whether to enable Udisks, a DBus service that allows
+          applications to query and manipulate storage devices.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.udisks2.enable {
+
+    environment.systemPackages = [ pkgs.udisks2 ];
+
+    services.dbus.packages = [ pkgs.udisks2 ];
+
+    system.activationScripts.udisks2 =
+      ''
+        mkdir -m 0755 -p /var/lib/udisks2
+      '';
+
+    #services.udev.packages = [ pkgs.udisks2 ];
+    
+    systemd.services.udisks2 = {
+      description = "Udisks2 service";
+      serviceConfig = {
+        Type = "dbus";
+        BusName = "org.freedesktop.UDisks2";
+        ExecStart = "${pkgs.udisks2}/lib/udisks2/udisksd --no-debug";
+      };
+    };
+  };
+
+}
diff --git a/nixos/modules/services/hardware/upower.nix b/nixos/modules/services/hardware/upower.nix
new file mode 100644
index 0000000000000..5d1658adb75f4
--- /dev/null
+++ b/nixos/modules/services/hardware/upower.nix
@@ -0,0 +1,64 @@
+# Upower daemon.
+
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.upower = {
+
+      enable = mkOption {
+        default = false;
+        description = ''
+          Whether to enable Upower, a DBus service that provides power
+          management support to applications.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.upower.enable {
+
+    environment.systemPackages = [ pkgs.upower ];
+
+    services.dbus.packages = [ pkgs.upower ];
+
+    services.udev.packages = [ pkgs.upower ];
+
+    systemd.services.upower =
+      { description = "Power Management Daemon";
+        path = [ pkgs.glib ]; # needed for gdbus
+        serviceConfig =
+          { Type = "dbus";
+            BusName = "org.freedesktop.UPower";
+            ExecStart = "@${pkgs.upower}/libexec/upowerd upowerd";
+          };
+      };
+
+    system.activationScripts.upower =
+      ''
+        mkdir -m 0755 -p /var/lib/upower
+      '';
+
+    # The upower daemon seems to get stuck after doing a suspend
+    # (i.e. subsequent suspend requests will say "Sleep has already
+    # been requested and is pending").  So as a workaround, restart
+    # the daemon.
+    powerManagement.resumeCommands =
+      ''
+        ${config.systemd.package}/bin/systemctl try-restart upower
+      '';
+
+  };
+
+}