nixos/autosuspend: init at version 4.3.0

`autosuspend` is a daemon that periodically runs user-defined checks to
verify whether the system should be suspended. It's already available
in nixpkgs. This adds a NixOS module which starts the daemon as a
systemd service.

Co-authored-by: pennae <82953136+pennae@users.noreply.github.com>

authored by

Xavier Lambein
pennae
and committed by
pennae
3f3524a4 31833f54

+240
+7
nixos/doc/manual/from_md/release-notes/rl-2305.section.xml
··· 130 <link xlink:href="options.html#opt-services.photoprism.enable">services.photoprism</link>. 131 </para> 132 </listitem> 133 </itemizedlist> 134 </section> 135 <section xml:id="sec-release-23.05-incompatibilities">
··· 130 <link xlink:href="options.html#opt-services.photoprism.enable">services.photoprism</link>. 131 </para> 132 </listitem> 133 + <listitem> 134 + <para> 135 + <link xlink:href="https://github.com/languitar/autosuspend">autosuspend</link>, 136 + a python daemon that suspends a system if certain conditions 137 + are met, or not met. 138 + </para> 139 + </listitem> 140 </itemizedlist> 141 </section> 142 <section xml:id="sec-release-23.05-incompatibilities">
+2
nixos/doc/manual/release-notes/rl-2305.section.md
··· 42 43 - [photoprism](https://photoprism.app/), a AI-Powered Photos App for the Decentralized Web. Available as [services.photoprism](options.html#opt-services.photoprism.enable). 44 45 ## Backward Incompatibilities {#sec-release-23.05-incompatibilities} 46 47 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
··· 42 43 - [photoprism](https://photoprism.app/), a AI-Powered Photos App for the Decentralized Web. Available as [services.photoprism](options.html#opt-services.photoprism.enable). 44 45 + - [autosuspend](https://github.com/languitar/autosuspend), a python daemon that suspends a system if certain conditions are met, or not met. 46 + 47 ## Backward Incompatibilities {#sec-release-23.05-incompatibilities} 48 49 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+1
nixos/modules/module-list.nix
··· 571 ./services/misc/atuin.nix 572 ./services/misc/autofs.nix 573 ./services/misc/autorandr.nix 574 ./services/misc/bazarr.nix 575 ./services/misc/beanstalkd.nix 576 ./services/misc/bees.nix
··· 571 ./services/misc/atuin.nix 572 ./services/misc/autofs.nix 573 ./services/misc/autorandr.nix 574 + ./services/misc/autosuspend.nix 575 ./services/misc/bazarr.nix 576 ./services/misc/beanstalkd.nix 577 ./services/misc/bees.nix
+230
nixos/modules/services/misc/autosuspend.nix
···
··· 1 + { config, pkgs, lib, ... }: 2 + let 3 + inherit (lib) mapAttrs' nameValuePair filterAttrs types mkEnableOption 4 + mdDoc mkPackageOptionMD mkOption literalExpression mkIf flatten 5 + maintainers attrValues; 6 + 7 + cfg = config.services.autosuspend; 8 + 9 + settingsFormat = pkgs.formats.ini { }; 10 + 11 + checks = 12 + mapAttrs' 13 + (n: v: nameValuePair "check.${n}" (filterAttrs (_: v: v != null) v)) 14 + cfg.checks; 15 + wakeups = 16 + mapAttrs' 17 + (n: v: nameValuePair "wakeup.${n}" (filterAttrs (_: v: v != null) v)) 18 + cfg.wakeups; 19 + 20 + # Whether the given check is enabled 21 + hasCheck = class: 22 + (filterAttrs 23 + (n: v: v.enabled && (if v.class == null then n else v.class) == class) 24 + cfg.checks) 25 + != { }; 26 + 27 + # Dependencies needed by specific checks 28 + dependenciesForChecks = { 29 + "Smb" = pkgs.samba; 30 + "XIdleTime" = [ pkgs.xprintidle pkgs.sudo ]; 31 + }; 32 + 33 + autosuspend-conf = 34 + settingsFormat.generate "autosuspend.conf" ({ general = cfg.settings; } // checks // wakeups); 35 + 36 + autosuspend = cfg.package; 37 + 38 + checkType = types.submodule { 39 + freeformType = settingsFormat.type.nestedTypes.elemType; 40 + 41 + options.enabled = mkEnableOption (mdDoc "this activity check") // { default = true; }; 42 + 43 + options.class = mkOption { 44 + default = null; 45 + type = with types; nullOr (enum [ 46 + "ActiveCalendarEvent" 47 + "ActiveConnection" 48 + "ExternalCommand" 49 + "JsonPath" 50 + "Kodi" 51 + "KodiIdleTime" 52 + "LastLogActivity" 53 + "Load" 54 + "LogindSessionsIdle" 55 + "Mpd" 56 + "NetworkBandwidth" 57 + "Ping" 58 + "Processes" 59 + "Smb" 60 + "Users" 61 + "XIdleTime" 62 + "XPath" 63 + ]); 64 + description = mdDoc '' 65 + Name of the class implementing the check. If this option is not specified, the check's 66 + name must represent a valid internal check class. 67 + ''; 68 + }; 69 + }; 70 + 71 + wakeupType = types.submodule { 72 + freeformType = settingsFormat.type.nestedTypes.elemType; 73 + 74 + options.enabled = mkEnableOption (mdDoc "this wake-up check") // { default = true; }; 75 + 76 + options.class = mkOption { 77 + default = null; 78 + type = with types; nullOr (enum [ 79 + "Calendar" 80 + "Command" 81 + "File" 82 + "Periodic" 83 + "SystemdTimer" 84 + "XPath" 85 + "XPathDelta" 86 + ]); 87 + description = mdDoc '' 88 + Name of the class implementing the check. If this option is not specified, the check's 89 + name must represent a valid internal check class. 90 + ''; 91 + }; 92 + }; 93 + in 94 + { 95 + options = { 96 + services.autosuspend = { 97 + enable = mkEnableOption (mdDoc "the autosuspend daemon"); 98 + 99 + package = mkPackageOptionMD pkgs "autosuspend" { }; 100 + 101 + settings = mkOption { 102 + type = types.submodule { 103 + freeformType = settingsFormat.type.nestedTypes.elemType; 104 + 105 + options = { 106 + # Provide reasonable defaults for these two (required) options 107 + suspend_cmd = mkOption { 108 + default = "systemctl suspend"; 109 + type = with types; str; 110 + description = mdDoc '' 111 + The command to execute in case the host shall be suspended. This line can contain 112 + additional command line arguments to the command to execute. 113 + ''; 114 + }; 115 + wakeup_cmd = mkOption { 116 + default = ''sh -c 'echo 0 > /sys/class/rtc/rtc0/wakealarm && echo {timestamp:.0f} > /sys/class/rtc/rtc0/wakealarm' ''; 117 + type = with types; str; 118 + description = mdDoc '' 119 + The command to execute for scheduling a wake up of the system. The given string is 120 + processed using Python’s `str.format()` and a format argument called `timestamp` 121 + encodes the UTC timestamp of the planned wake up time (float). Additionally `iso` 122 + can be used to acquire the timestamp in ISO 8601 format. 123 + ''; 124 + }; 125 + }; 126 + }; 127 + default = { }; 128 + example = literalExpression '' 129 + { 130 + enable = true; 131 + interval = 30; 132 + idle_time = 120; 133 + } 134 + ''; 135 + description = mdDoc '' 136 + Configuration for autosuspend, see 137 + <https://autosuspend.readthedocs.io/en/latest/configuration_file.html#general-configuration> 138 + for supported values. 139 + ''; 140 + }; 141 + 142 + checks = mkOption { 143 + default = { }; 144 + type = with types; attrsOf checkType; 145 + description = mdDoc '' 146 + Checks for activity. For more information, see: 147 + - <https://autosuspend.readthedocs.io/en/latest/configuration_file.html#activity-check-configuration> 148 + - <https://autosuspend.readthedocs.io/en/latest/available_checks.html> 149 + ''; 150 + example = literalExpression '' 151 + { 152 + # Basic activity check configuration. 153 + # The check class name is derived from the section header (Ping in this case). 154 + # Remember to enable desired checks. They are disabled by default. 155 + Ping = { 156 + hosts = "192.168.0.7"; 157 + }; 158 + 159 + # This check is disabled. 160 + Smb.enabled = false; 161 + 162 + # Example for a custom check name. 163 + # This will use the Users check with the custom name RemoteUsers. 164 + # Custom names are necessary in case a check class is used multiple times. 165 + # Custom names can also be used for clarification. 166 + RemoteUsers = { 167 + class = "Users"; 168 + name = ".*"; 169 + terminal = ".*"; 170 + host = "[0-9].*"; 171 + }; 172 + 173 + # Here the Users activity check is used again with different settings and a different name 174 + LocalUsers = { 175 + class = "Users"; 176 + name = ".*"; 177 + terminal = ".*"; 178 + host = "localhost"; 179 + }; 180 + } 181 + ''; 182 + }; 183 + 184 + wakeups = mkOption { 185 + default = { }; 186 + type = with types; attrsOf wakeupType; 187 + description = mdDoc '' 188 + Checks for wake up. For more information, see: 189 + - <https://autosuspend.readthedocs.io/en/latest/configuration_file.html#wake-up-check-configuration> 190 + - <https://autosuspend.readthedocs.io/en/latest/available_wakeups.html> 191 + ''; 192 + example = literalExpression '' 193 + { 194 + # Wake up checks reuse the same configuration mechanism as activity checks. 195 + Calendar = { 196 + url = "http://example.org/test.ics"; 197 + }; 198 + } 199 + ''; 200 + }; 201 + }; 202 + }; 203 + 204 + config = mkIf cfg.enable { 205 + systemd.services.autosuspend = { 206 + description = "A daemon to suspend your server in case of inactivity"; 207 + documentation = [ "https://autosuspend.readthedocs.io/en/latest/systemd_integration.html" ]; 208 + wantedBy = [ "multi-user.target" ]; 209 + after = [ "network.target" ]; 210 + path = flatten (attrValues (filterAttrs (n: _: hasCheck n) dependenciesForChecks)); 211 + serviceConfig = { 212 + ExecStart = ''${autosuspend}/bin/autosuspend -l ${autosuspend}/etc/autosuspend-logging.conf -c ${autosuspend-conf} daemon''; 213 + }; 214 + }; 215 + 216 + systemd.services.autosuspend-detect-suspend = { 217 + description = "Notifies autosuspend about suspension"; 218 + documentation = [ "https://autosuspend.readthedocs.io/en/latest/systemd_integration.html" ]; 219 + wantedBy = [ "sleep.target" ]; 220 + after = [ "sleep.target" ]; 221 + serviceConfig = { 222 + ExecStart = ''${autosuspend}/bin/autosuspend -l ${autosuspend}/etc/autosuspend-logging.conf -c ${autosuspend-conf} presuspend''; 223 + }; 224 + }; 225 + }; 226 + 227 + meta = { 228 + maintainers = with maintainers; [ xlambein ]; 229 + }; 230 + }