1{ config, lib, pkgs, ... }:
2let
3 inherit (lib) mkEnableOption mkIf mkOption types;
4
5 cfg = config.hardware.sata.timeout;
6
7 buildRule = d:
8 lib.concatStringsSep ", " [
9 ''ACTION=="add"''
10 ''SUBSYSTEM=="block"''
11 ''ENV{ID_${lib.toUpper d.idBy}}=="${d.name}"''
12 ''TAG+="systemd"''
13 ''ENV{SYSTEMD_WANTS}="${unitName d}"''
14 ];
15
16 devicePath = device:
17 "/dev/disk/by-${device.idBy}/${device.name}";
18
19 unitName = device:
20 "sata-timeout-${lib.strings.sanitizeDerivationName device.name}";
21
22 startScript =
23 pkgs.writeShellScript "sata-timeout.sh" ''
24 set -eEuo pipefail
25
26 device="$1"
27
28 ${pkgs.smartmontools}/bin/smartctl \
29 -l scterc,${toString cfg.deciSeconds},${toString cfg.deciSeconds} \
30 --quietmode errorsonly \
31 "$device"
32 '';
33
34in
35{
36 meta.maintainers = with lib.maintainers; [ peterhoeg ];
37
38 options.hardware.sata.timeout = {
39 enable = mkEnableOption "SATA drive timeouts";
40
41 deciSeconds = mkOption {
42 example = 70;
43 type = types.int;
44 description = ''
45 Set SCT Error Recovery Control timeout in deciseconds for use in RAID configurations.
46
47 Values are as follows:
48 0 = disable SCT ERT
49 70 = default in consumer drives (7 seconds)
50
51 Maximum is disk dependant but probably 60 seconds.
52 '';
53 };
54
55 drives = mkOption {
56 description = "List of drives for which to configure the timeout.";
57 type = types.listOf
58 (types.submodule {
59 options = {
60 name = mkOption {
61 description = "Drive name without the full path.";
62 type = types.str;
63 };
64
65 idBy = mkOption {
66 description = "The method to identify the drive.";
67 type = types.enum [ "path" "wwn" ];
68 default = "path";
69 };
70 };
71 });
72 };
73 };
74
75 config = mkIf cfg.enable {
76 services.udev.extraRules = lib.concatMapStringsSep "\n" buildRule cfg.drives;
77
78 systemd.services = lib.listToAttrs (map
79 (e:
80 lib.nameValuePair (unitName e) {
81 description = "SATA timeout for ${e.name}";
82 wantedBy = [ "sata-timeout.target" ];
83 serviceConfig = {
84 Type = "oneshot";
85 ExecStart = "${startScript} '${devicePath e}'";
86 PrivateTmp = true;
87 PrivateNetwork = true;
88 ProtectHome = "tmpfs";
89 ProtectSystem = "strict";
90 };
91 }
92 )
93 cfg.drives);
94
95 systemd.targets.sata-timeout = {
96 description = "SATA timeout";
97 wantedBy = [ "multi-user.target" ];
98 };
99 };
100}