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