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