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