1{ config, pkgs, lib, ... }:
2let
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 };
93in
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}