diff --git a/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml
index 17769ff0b45b..8e9a81f3499a 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml
@@ -130,6 +130,13 @@
services.photoprism.
+
+
+ autosuspend,
+ a python daemon that suspends a system if certain conditions
+ are met, or not met.
+
+
diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md
index d4b31a9df1b8..845f9037e722 100644
--- a/nixos/doc/manual/release-notes/rl-2305.section.md
+++ b/nixos/doc/manual/release-notes/rl-2305.section.md
@@ -42,6 +42,8 @@ In addition to numerous new and upgraded packages, this release has the followin
- [photoprism](https://photoprism.app/), a AI-Powered Photos App for the Decentralized Web. Available as [services.photoprism](options.html#opt-services.photoprism.enable).
+- [autosuspend](https://github.com/languitar/autosuspend), a python daemon that suspends a system if certain conditions are met, or not met.
+
## Backward Incompatibilities {#sec-release-23.05-incompatibilities}
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 45a7acdedc41..e1c174a00f99 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -571,6 +571,7 @@
./services/misc/atuin.nix
./services/misc/autofs.nix
./services/misc/autorandr.nix
+ ./services/misc/autosuspend.nix
./services/misc/bazarr.nix
./services/misc/beanstalkd.nix
./services/misc/bees.nix
diff --git a/nixos/modules/services/misc/autosuspend.nix b/nixos/modules/services/misc/autosuspend.nix
new file mode 100644
index 000000000000..b3e362533a09
--- /dev/null
+++ b/nixos/modules/services/misc/autosuspend.nix
@@ -0,0 +1,230 @@
+{ config, pkgs, lib, ... }:
+let
+ inherit (lib) mapAttrs' nameValuePair filterAttrs types mkEnableOption
+ mdDoc mkPackageOptionMD mkOption literalExpression mkIf flatten
+ maintainers attrValues;
+
+ cfg = config.services.autosuspend;
+
+ settingsFormat = pkgs.formats.ini { };
+
+ checks =
+ mapAttrs'
+ (n: v: nameValuePair "check.${n}" (filterAttrs (_: v: v != null) v))
+ cfg.checks;
+ wakeups =
+ mapAttrs'
+ (n: v: nameValuePair "wakeup.${n}" (filterAttrs (_: v: v != null) v))
+ cfg.wakeups;
+
+ # Whether the given check is enabled
+ hasCheck = class:
+ (filterAttrs
+ (n: v: v.enabled && (if v.class == null then n else v.class) == class)
+ cfg.checks)
+ != { };
+
+ # Dependencies needed by specific checks
+ dependenciesForChecks = {
+ "Smb" = pkgs.samba;
+ "XIdleTime" = [ pkgs.xprintidle pkgs.sudo ];
+ };
+
+ autosuspend-conf =
+ settingsFormat.generate "autosuspend.conf" ({ general = cfg.settings; } // checks // wakeups);
+
+ autosuspend = cfg.package;
+
+ checkType = types.submodule {
+ freeformType = settingsFormat.type.nestedTypes.elemType;
+
+ options.enabled = mkEnableOption (mdDoc "this activity check") // { default = true; };
+
+ options.class = mkOption {
+ default = null;
+ type = with types; nullOr (enum [
+ "ActiveCalendarEvent"
+ "ActiveConnection"
+ "ExternalCommand"
+ "JsonPath"
+ "Kodi"
+ "KodiIdleTime"
+ "LastLogActivity"
+ "Load"
+ "LogindSessionsIdle"
+ "Mpd"
+ "NetworkBandwidth"
+ "Ping"
+ "Processes"
+ "Smb"
+ "Users"
+ "XIdleTime"
+ "XPath"
+ ]);
+ description = mdDoc ''
+ Name of the class implementing the check. If this option is not specified, the check's
+ name must represent a valid internal check class.
+ '';
+ };
+ };
+
+ wakeupType = types.submodule {
+ freeformType = settingsFormat.type.nestedTypes.elemType;
+
+ options.enabled = mkEnableOption (mdDoc "this wake-up check") // { default = true; };
+
+ options.class = mkOption {
+ default = null;
+ type = with types; nullOr (enum [
+ "Calendar"
+ "Command"
+ "File"
+ "Periodic"
+ "SystemdTimer"
+ "XPath"
+ "XPathDelta"
+ ]);
+ description = mdDoc ''
+ Name of the class implementing the check. If this option is not specified, the check's
+ name must represent a valid internal check class.
+ '';
+ };
+ };
+in
+{
+ options = {
+ services.autosuspend = {
+ enable = mkEnableOption (mdDoc "the autosuspend daemon");
+
+ package = mkPackageOptionMD pkgs "autosuspend" { };
+
+ settings = mkOption {
+ type = types.submodule {
+ freeformType = settingsFormat.type.nestedTypes.elemType;
+
+ options = {
+ # Provide reasonable defaults for these two (required) options
+ suspend_cmd = mkOption {
+ default = "systemctl suspend";
+ type = with types; str;
+ description = mdDoc ''
+ The command to execute in case the host shall be suspended. This line can contain
+ additional command line arguments to the command to execute.
+ '';
+ };
+ wakeup_cmd = mkOption {
+ default = ''sh -c 'echo 0 > /sys/class/rtc/rtc0/wakealarm && echo {timestamp:.0f} > /sys/class/rtc/rtc0/wakealarm' '';
+ type = with types; str;
+ description = mdDoc ''
+ The command to execute for scheduling a wake up of the system. The given string is
+ processed using Python’s `str.format()` and a format argument called `timestamp`
+ encodes the UTC timestamp of the planned wake up time (float). Additionally `iso`
+ can be used to acquire the timestamp in ISO 8601 format.
+ '';
+ };
+ };
+ };
+ default = { };
+ example = literalExpression ''
+ {
+ enable = true;
+ interval = 30;
+ idle_time = 120;
+ }
+ '';
+ description = mdDoc ''
+ Configuration for autosuspend, see
+
+ for supported values.
+ '';
+ };
+
+ checks = mkOption {
+ default = { };
+ type = with types; attrsOf checkType;
+ description = mdDoc ''
+ Checks for activity. For more information, see:
+ -
+ -
+ '';
+ example = literalExpression ''
+ {
+ # Basic activity check configuration.
+ # The check class name is derived from the section header (Ping in this case).
+ # Remember to enable desired checks. They are disabled by default.
+ Ping = {
+ hosts = "192.168.0.7";
+ };
+
+ # This check is disabled.
+ Smb.enabled = false;
+
+ # Example for a custom check name.
+ # This will use the Users check with the custom name RemoteUsers.
+ # Custom names are necessary in case a check class is used multiple times.
+ # Custom names can also be used for clarification.
+ RemoteUsers = {
+ class = "Users";
+ name = ".*";
+ terminal = ".*";
+ host = "[0-9].*";
+ };
+
+ # Here the Users activity check is used again with different settings and a different name
+ LocalUsers = {
+ class = "Users";
+ name = ".*";
+ terminal = ".*";
+ host = "localhost";
+ };
+ }
+ '';
+ };
+
+ wakeups = mkOption {
+ default = { };
+ type = with types; attrsOf wakeupType;
+ description = mdDoc ''
+ Checks for wake up. For more information, see:
+ -
+ -
+ '';
+ example = literalExpression ''
+ {
+ # Wake up checks reuse the same configuration mechanism as activity checks.
+ Calendar = {
+ url = "http://example.org/test.ics";
+ };
+ }
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.autosuspend = {
+ description = "A daemon to suspend your server in case of inactivity";
+ documentation = [ "https://autosuspend.readthedocs.io/en/latest/systemd_integration.html" ];
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ path = flatten (attrValues (filterAttrs (n: _: hasCheck n) dependenciesForChecks));
+ serviceConfig = {
+ ExecStart = ''${autosuspend}/bin/autosuspend -l ${autosuspend}/etc/autosuspend-logging.conf -c ${autosuspend-conf} daemon'';
+ };
+ };
+
+ systemd.services.autosuspend-detect-suspend = {
+ description = "Notifies autosuspend about suspension";
+ documentation = [ "https://autosuspend.readthedocs.io/en/latest/systemd_integration.html" ];
+ wantedBy = [ "sleep.target" ];
+ after = [ "sleep.target" ];
+ serviceConfig = {
+ ExecStart = ''${autosuspend}/bin/autosuspend -l ${autosuspend}/etc/autosuspend-logging.conf -c ${autosuspend-conf} presuspend'';
+ };
+ };
+ };
+
+ meta = {
+ maintainers = with maintainers; [ xlambein ];
+ };
+}