1{
2 config,
3 lib,
4 options,
5 pkgs,
6 ...
7}:
8let
9
10 host = config.networking.fqdnOrHostName;
11
12 cfg = config.services.smartd;
13 opt = options.services.smartd;
14
15 nm = cfg.notifications.mail;
16 ns = cfg.notifications.systembus-notify;
17 nw = cfg.notifications.wall;
18 nx = cfg.notifications.x11;
19
20 smartdNotify = pkgs.writeScript "smartd-notify.sh" ''
21 #! ${pkgs.runtimeShell}
22 ${lib.optionalString nm.enable ''
23 {
24 ${pkgs.coreutils}/bin/cat << EOF
25 From: smartd on ${host} <${nm.sender}>
26 To: ${nm.recipient}
27 Subject: $SMARTD_SUBJECT
28
29 $SMARTD_FULLMESSAGE
30 EOF
31
32 ${pkgs.smartmontools}/sbin/smartctl -a -d "$SMARTD_DEVICETYPE" "$SMARTD_DEVICE"
33 } | ${nm.mailer} -i "${nm.recipient}"
34 ''}
35 ${lib.optionalString ns.enable ''
36 ${pkgs.dbus}/bin/dbus-send --system \
37 / net.nuetzlich.SystemNotifications.Notify \
38 "string:Problem detected with disk: $SMARTD_DEVICESTRING" \
39 "string:Warning message from smartd is: $SMARTD_MESSAGE"
40 ''}
41 ${lib.optionalString nw.enable ''
42 {
43 ${pkgs.coreutils}/bin/cat << EOF
44 Problem detected with disk: $SMARTD_DEVICESTRING
45 Warning message from smartd is:
46
47 $SMARTD_MESSAGE
48 EOF
49 } | ${pkgs.util-linux}/bin/wall 2>/dev/null
50 ''}
51 ${lib.optionalString nx.enable ''
52 export DISPLAY=${nx.display}
53 {
54 ${pkgs.coreutils}/bin/cat << EOF
55 Problem detected with disk: $SMARTD_DEVICESTRING
56 Warning message from smartd is:
57
58 $SMARTD_FULLMESSAGE
59 EOF
60 } | ${pkgs.xorg.xmessage}/bin/xmessage -file - 2>/dev/null &
61 ''}
62 '';
63
64 notifyOpts = lib.optionalString (nm.enable || nw.enable || nx.enable) (
65 "-m <nomailer> -M exec ${smartdNotify} " + lib.optionalString cfg.notifications.test "-M test "
66 );
67
68 smartdConf = pkgs.writeText "smartd.conf" ''
69 # Autogenerated smartd startup config file
70 DEFAULT ${notifyOpts}${cfg.defaults.monitored}
71
72 ${lib.concatMapStringsSep "\n" (d: "${d.device} ${d.options}") cfg.devices}
73
74 ${lib.optionalString cfg.autodetect "DEVICESCAN ${notifyOpts}${cfg.defaults.autodetected}"}
75 '';
76
77 smartdDeviceOpts =
78 { ... }:
79 {
80
81 options = {
82
83 device = lib.mkOption {
84 example = "/dev/sda";
85 type = lib.types.str;
86 description = "Location of the device.";
87 };
88
89 options = lib.mkOption {
90 default = "";
91 example = "-d sat";
92 type = lib.types.separatedString " ";
93 description = "Options that determine how smartd monitors the device.";
94 };
95
96 };
97
98 };
99
100in
101
102{
103 ###### interface
104
105 options = {
106
107 services.smartd = {
108
109 enable = lib.mkEnableOption "smartd daemon from `smartmontools` package";
110
111 autodetect = lib.mkOption {
112 default = true;
113 type = lib.types.bool;
114 description = ''
115 Whenever smartd should monitor all devices connected to the
116 machine at the time it's being started (the default).
117
118 Set to false to monitor the devices listed in
119 {option}`services.smartd.devices` only.
120 '';
121 };
122
123 extraOptions = lib.mkOption {
124 default = [ ];
125 type = lib.types.listOf lib.types.str;
126 example = [
127 "-A /var/log/smartd/"
128 "--interval=3600"
129 ];
130 description = ''
131 Extra command-line options passed to the `smartd`
132 daemon on startup.
133
134 (See `man 8 smartd`.)
135 '';
136 };
137
138 notifications = {
139
140 mail = {
141 enable = lib.mkOption {
142 default = config.services.mail.sendmailSetuidWrapper != null;
143 defaultText = lib.literalExpression "config.services.mail.sendmailSetuidWrapper != null";
144 type = lib.types.bool;
145 description = "Whenever to send e-mail notifications.";
146 };
147
148 sender = lib.mkOption {
149 default = "root";
150 example = "example@domain.tld";
151 type = lib.types.str;
152 description = ''
153 Sender of the notification messages.
154 Acts as the value of `email` in the emails' `From: ...` field.
155 '';
156 };
157
158 recipient = lib.mkOption {
159 default = "root";
160 type = lib.types.str;
161 description = "Recipient of the notification messages.";
162 };
163
164 mailer = lib.mkOption {
165 default = "/run/wrappers/bin/sendmail";
166 type = lib.types.path;
167 description = ''
168 Sendmail-compatible binary to be used to send the messages.
169
170 You should probably enable
171 {option}`services.postfix` or some other MTA for
172 this to work.
173 '';
174 };
175 };
176
177 systembus-notify = {
178 enable = lib.mkOption {
179 default = false;
180 type = lib.types.bool;
181 description = ''
182 Whenever to send systembus-notify notifications.
183
184 WARNING: enabling this option (while convenient) should *not* be done on a
185 machine where you do not trust the other users as it allows any other
186 local user to DoS your session by spamming notifications.
187
188 To actually see the notifications in your GUI session, you need to have
189 `systembus-notify` running as your user, which this
190 option handles by enabling {option}`services.systembus-notify`.
191 '';
192 };
193 };
194
195 wall = {
196 enable = lib.mkOption {
197 default = true;
198 type = lib.types.bool;
199 description = "Whenever to send wall notifications to all users.";
200 };
201 };
202
203 x11 = {
204 enable = lib.mkOption {
205 default = config.services.xserver.enable;
206 defaultText = lib.literalExpression "config.services.xserver.enable";
207 type = lib.types.bool;
208 description = "Whenever to send X11 xmessage notifications.";
209 };
210
211 display = lib.mkOption {
212 default = ":${toString config.services.xserver.display}";
213 defaultText = lib.literalExpression ''":''${toString config.services.xserver.display}"'';
214 type = lib.types.str;
215 description = "DISPLAY to send X11 notifications to.";
216 };
217 };
218
219 test = lib.mkOption {
220 default = false;
221 type = lib.types.bool;
222 description = "Whenever to send a test notification on startup.";
223 };
224
225 };
226
227 defaults = {
228 monitored = lib.mkOption {
229 default = "-a";
230 type = lib.types.separatedString " ";
231 example = "-a -o on -s (S/../.././02|L/../../7/04)";
232 description = ''
233 Common default options for explicitly monitored (listed in
234 {option}`services.smartd.devices`) devices.
235
236 The default value turns on monitoring of all the things (see
237 `man 5 smartd.conf`).
238
239 The example also turns on SMART Automatic Offline Testing on
240 startup, and schedules short self-tests daily, and long
241 self-tests weekly.
242 '';
243 };
244
245 autodetected = lib.mkOption {
246 default = cfg.defaults.monitored;
247 defaultText = lib.literalExpression "config.${opt.defaults.monitored}";
248 type = lib.types.separatedString " ";
249 description = ''
250 Like {option}`services.smartd.defaults.monitored`, but for the
251 autodetected devices.
252 '';
253 };
254 };
255
256 devices = lib.mkOption {
257 default = [ ];
258 example = [
259 { device = "/dev/sda"; }
260 {
261 device = "/dev/sdb";
262 options = "-d sat";
263 }
264 ];
265 type = with lib.types; listOf (submodule smartdDeviceOpts);
266 description = "List of devices to monitor.";
267 };
268
269 };
270
271 };
272
273 ###### implementation
274
275 config = lib.mkIf cfg.enable {
276
277 assertions = [
278 {
279 assertion = cfg.autodetect || cfg.devices != [ ];
280 message = "smartd can't run with both disabled autodetect and an empty list of devices to monitor.";
281 }
282 ];
283
284 systemd.services.smartd = {
285 description = "S.M.A.R.T. Daemon";
286 wantedBy = [ "multi-user.target" ];
287 serviceConfig = {
288 Type = "notify";
289 ExecStart = "${pkgs.smartmontools}/sbin/smartd ${lib.concatStringsSep " " cfg.extraOptions} --no-fork --configfile=${smartdConf}";
290 };
291 };
292
293 services.systembus-notify.enable = lib.mkDefault ns.enable;
294
295 };
296
297}