1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 host = config.networking.hostName or "unknown"
8 + optionalString (config.networking.domain != null) ".${config.networking.domain}";
9
10 cfg = config.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.stdenv.shell}
18 ${optionalString nm.enable ''
19 {
20 cat << EOF
21 From: smartd on ${host} <root>
22 To: undisclosed-recipients:;
23 Subject: SMART error on $SMARTD_DEVICESTRING: $SMARTD_FAILTYPE
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 cat << EOF
34 Problem detected with disk: $SMARTD_DEVICESTRING
35 Warning message from smartd is:
36
37 $SMARTD_MESSAGE
38 EOF
39 } | ${pkgs.utillinux}/bin/wall 2>/dev/null
40 ''}
41 ${optionalString nx.enable ''
42 export DISPLAY=${nx.display}
43 {
44 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 smartdOpts = { name, ... }: {
68
69 options = {
70
71 device = mkOption {
72 example = "/dev/sda";
73 type = types.str;
74 description = "Location of the device.";
75 };
76
77 options = mkOption {
78 default = "";
79 example = "-d sat";
80 type = types.separatedString " ";
81 description = "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 "smartd daemon from <literal>smartmontools</literal> package";
98
99 autodetect = mkOption {
100 default = true;
101 type = types.bool;
102 description = ''
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</option> only.
108 '';
109 };
110
111 notifications = {
112
113 mail = {
114 enable = mkOption {
115 default = config.services.mail.sendmailSetuidWrapper != null;
116 type = types.bool;
117 description = "Whenever to send e-mail notifications.";
118 };
119
120 recipient = mkOption {
121 default = "root";
122 type = types.str;
123 description = "Recipient of the notification messages.";
124 };
125
126 mailer = mkOption {
127 default = "/var/setuid-wrappers/sendmail";
128 type = types.path;
129 description = ''
130 Sendmail-compatible binary to be used to send the messages.
131
132 You should probably enable
133 <option>services.postfix</option> or some other MTA for
134 this to work.
135 '';
136 };
137 };
138
139 wall = {
140 enable = mkOption {
141 default = true;
142 type = types.bool;
143 description = "Whenever to send wall notifications to all users.";
144 };
145 };
146
147 x11 = {
148 enable = mkOption {
149 default = config.services.xserver.enable;
150 type = types.bool;
151 description = "Whenever to send X11 xmessage notifications.";
152 };
153
154 display = mkOption {
155 default = ":${toString config.services.xserver.display}";
156 type = types.str;
157 description = "DISPLAY to send X11 notifications to.";
158 };
159 };
160
161 test = mkOption {
162 default = false;
163 type = types.bool;
164 description = "Whenever to send a test notification on startup.";
165 };
166
167 };
168
169 defaults = {
170 monitored = mkOption {
171 default = "-a";
172 type = types.separatedString " ";
173 example = "-a -o on -s (S/../.././02|L/../../7/04)";
174 description = ''
175 Common default options for explicitly monitored (listed in
176 <option>services.smartd.devices</option>) devices.
177
178 The default value turns on monitoring of all the things (see
179 <literal>man 5 smartd.conf</literal>).
180
181 The example also turns on SMART Automatic Offline Testing on
182 startup, and schedules short self-tests daily, and long
183 self-tests weekly.
184 '';
185 };
186
187 autodetected = mkOption {
188 default = cfg.defaults.monitored;
189 type = types.separatedString " ";
190 description = ''
191 Like <option>services.smartd.defaults.monitored</option>, but for the
192 autodetected devices.
193 '';
194 };
195 };
196
197 devices = mkOption {
198 default = [];
199 example = [ { device = "/dev/sda"; } { device = "/dev/sdb"; options = "-d sat"; } ];
200 type = types.listOf types.optionSet;
201 options = [ smartdOpts ];
202 description = "List of devices to monitor.";
203 };
204
205 };
206
207 };
208
209
210 ###### implementation
211
212 config = mkIf cfg.enable {
213
214 assertions = [ {
215 assertion = cfg.autodetect || cfg.devices != [];
216 message = "smartd can't run with both disabled autodetect and an empty list of devices to monitor.";
217 } ];
218
219 systemd.services.smartd = {
220 description = "S.M.A.R.T. Daemon";
221
222 wantedBy = [ "multi-user.target" ];
223
224 path = [ pkgs.nettools ]; # for hostname and dnsdomanname calls in smartd
225
226 serviceConfig.ExecStart = "${pkgs.smartmontools}/sbin/smartd --no-fork --configfile=${smartdConf}";
227 };
228
229 };
230
231}