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}