Merge pull request #9280 from oxij/better-smartd

nixos: make services.smartd much more helpful

+166 -48
+3
nixos/modules/rename.nix
··· 98 98 ++ obsolete [ "boot" "initrd" "extraKernelModules" ] [ "boot" "initrd" "kernelModules" ] 99 99 ++ obsolete [ "boot" "extraKernelParams" ] [ "boot" "kernelParams" ] 100 100 101 + # smartd 102 + ++ obsolete [ "services" "smartd" "deviceOpts" ] [ "services" "smartd" "defaults" "monitored" ] 103 + 101 104 # OpenSSH 102 105 ++ obsolete [ "services" "sshd" "ports" ] [ "services" "openssh" "ports" ] 103 106 ++ alias [ "services" "sshd" "enable" ] [ "services" "openssh" "enable" ]
+163 -48
nixos/modules/services/monitoring/smartd.nix
··· 4 4 5 5 let 6 6 7 + host = config.networking.hostName or "unknown" 8 + + optionalString (config.networking.domain != null) ".${config.networking.domain}"; 9 + 7 10 cfg = config.services.smartd; 8 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 + 9 67 smartdOpts = { name, ... }: { 10 68 11 69 options = { ··· 22 80 type = types.separatedString " "; 23 81 description = "Options that determine how smartd monitors the device."; 24 82 }; 83 + 25 84 }; 26 85 27 86 }; 28 87 29 - smartdMail = pkgs.writeScript "smartdmail.sh" '' 30 - #! ${pkgs.stdenv.shell} 31 - TMPNAM=/tmp/smartd-message.$$.tmp 32 - if test -n "$SMARTD_ADDRESS"; then 33 - echo >"$TMPNAM" "From: smartd <root>" 34 - echo >>"$TMPNAM" 'To: undisclosed-recipients:;' 35 - echo >>"$TMPNAM" "Subject: $SMARTD_SUBJECT" 36 - echo >>"$TMPNAM" 37 - echo >>"$TMPNAM" "Failure on $SMARTD_DEVICESTRING: $SMARTD_FAILTYPE" 38 - echo >>"$TMPNAM" 39 - cat >>"$TMPNAM" 40 - ${pkgs.smartmontools}/sbin/smartctl >>"$TMPNAM" -a -d "$SMARTD_DEVICETYPE" "$SMARTD_DEVICE" 41 - /var/setuid-wrappers/sendmail <"$TMPNAM" -f "$SENDER" -i "$SMARTD_ADDRESS" 42 - fi 43 - ''; 44 - 45 - smartdConf = pkgs.writeText "smartd.conf" (concatMapStrings (device: 46 - '' 47 - ${device.device} -a -m root -M exec ${smartdMail} ${device.options} ${cfg.deviceOpts} 48 - '' 49 - ) cfg.devices); 50 - 51 - smartdFlags = if (cfg.devices == []) then "" else "--configfile=${smartdConf}"; 52 - 53 88 in 54 89 55 90 { ··· 59 94 60 95 services.smartd = { 61 96 62 - enable = mkOption { 63 - default = false; 97 + enable = mkEnableOption "smartd daemon from <literal>smartmontools</literal> package"; 98 + 99 + autodetect = mkOption { 100 + default = true; 64 101 type = types.bool; 65 - example = true; 66 102 description = '' 67 - Run smartd from the smartmontools package. Note that e-mail 68 - notifications will not be enabled unless you configure the list of 69 - devices with <varname>services.smartd.devices</varname> as well. 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. 70 108 ''; 71 109 }; 72 110 73 - deviceOpts = mkOption { 74 - default = ""; 75 - type = types.string; 76 - example = "-o on -s (S/../.././02|L/../../7/04)"; 77 - description = '' 78 - Additional options for each device that is monitored. The example 79 - turns on SMART Automatic Offline Testing on startup, and schedules short 80 - self-tests daily, and long self-tests weekly. 81 - ''; 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.string; 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.string; 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 + }; 82 195 }; 83 196 84 197 devices = mkOption { ··· 86 199 example = [ { device = "/dev/sda"; } { device = "/dev/sdb"; options = "-d sat"; } ]; 87 200 type = types.listOf types.optionSet; 88 201 options = [ smartdOpts ]; 89 - description = '' 90 - List of devices to monitor. By default -- if this list is empty --, 91 - smartd will monitor all devices connected to the machine at the time 92 - it's being run. Configuring this option has the added benefit of 93 - enabling e-mail notifications to "root" every time smartd detects an 94 - error. 95 - ''; 96 - }; 202 + description = "List of devices to monitor."; 203 + }; 204 + 97 205 }; 98 206 99 207 }; ··· 103 211 104 212 config = mkIf cfg.enable { 105 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 + 106 219 systemd.services.smartd = { 107 220 description = "S.M.A.R.T. Daemon"; 108 221 109 222 wantedBy = [ "multi-user.target" ]; 110 223 111 - serviceConfig.ExecStart = "${pkgs.smartmontools}/sbin/smartd --no-fork ${smartdFlags}"; 224 + path = [ pkgs.nettools ]; # for hostname and dnsdomanname calls in smartd 225 + 226 + serviceConfig.ExecStart = "${pkgs.smartmontools}/sbin/smartd --no-fork --configfile=${smartdConf}"; 112 227 }; 113 228 114 229 };