aboutsummaryrefslogtreecommitdiff
path: root/modules/system/raid0.nix
blob: ed946d2296766d3aa3e9fb36e0dbafffbe5b8621 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
{ config, pkgs, lib, ... }:

let

  inherit (builtins)
    attrNames
    ;

  inherit (lib)
    concatStringsSep filterAttrs foldl genAttrs mapAttrs' mapAttrsToList
    mkOption nameValuePair removePrefix replaceStrings
    ;

  inherit (lib.types)
    attrsOf enum int listOf path submodule
    ;

  groups = filterAttrs (n: _: n != "_module") config.nixsap.system.lvm.raid0;

  createLV = vg: lv: s: opts:
    let
      new = toString s;
      stripes = toString opts.stripes;
      sizeSpec = if opts.units == "%"
                 then "--extents ${new}%VG"
                 else "--size ${new}${opts.units}";
      scale = {
        "%" = "* 100 / $(vgs --unit b --noheadings --nosuffix --options vg_size ${vg})";
        "M" = "/ ${toString (1000 * 1000)}";
        "m" = "/ ${toString (1024 * 1024)}";
        "G" = "/ ${toString (1000 * 1000 * 1000)}";
        "g" = "/ ${toString (1024 * 1024 * 1024)}";
        "T" = "/ ${toString (1000 * 1000 * 1000 * 1000)}";
        "t" = "/ ${toString (1024 * 1024 * 1024 * 1024)}";
      };
    in pkgs.writeBashScript "raid0-create-${vg}-${lv}" ''
      set -eu
      device=/dev/${vg}/${lv}

      lv_size=$(lvs --unit b --noheadings --nosuffix --options lv_size "$device" || echo 0)
      old=$(( lv_size ${scale."${opts.units}"} ))

      if (( ${new} == old )) ; then
        exit 0
      elif (( old == 0 )); then
        lvcreate ${vg} --name ${lv} ${sizeSpec} --stripes ${stripes}
      elif (( ${new} < old )) ; then
        echo "Cannot shrink volume $device from $old ${opts.units} to ${new} ${opts.units}" >&2
        exit 1
      else
        lvextend "$device" ${sizeSpec}
        resize2fs "$device"
      fi
    '';

  createVG = vg: pv: pkgs.writeBashScript "raid0-create-vg-${vg}" ''
    set -eu
    for pv in ${toString pv}; do
      type=$(blkid -p -s TYPE -o value "$pv" || true)
      if [ "$type" != LVM2_member ]; then
        pvcreate "$pv"
        if ! vgs ${vg}; then
          vgcreate ${vg} "$pv"
        else
          vgextend ${vg} "$pv"
        fi
      fi
    done
  '';

  mkRaidService = vg: opts:
    let
      ExecStart = pkgs.writeBashScript "raid0-${vg}" ''
        set -eu
        ${createVG vg opts.physical}
        ${concatStringsSep "\n" (
          mapAttrsToList (v: s:
            "${createLV vg (baseNameOf v) s opts}")
            opts.fileSystems
         )}
        vgchange -ay ${vg}
        udevadm trigger --action=add
      '';

    in nameValuePair "raid0-${vg}" rec {
      wantedBy = map (v: "dev-${vg}-${baseNameOf v}.device") (attrNames opts.fileSystems);
      requires = map (pv: replaceStrings ["/"] ["-"] (removePrefix "/" pv) + ".device") opts.physical;
      after = requires;
      before = wantedBy;
      unitConfig.DefaultDependencies = false;
      path = with pkgs; [ utillinux lvm2 e2fsprogs ];
      serviceConfig = {
        inherit ExecStart;
        RemainAfterExit = true;
        Type = "oneshot";
      };
    };

in {
  options.nixsap.system = {
    lvm.raid0 = mkOption {
      description = "Set of LVM2 volume groups";
      default = {};
      type = attrsOf (submodule {
        options = {
          stripes = mkOption {
            description = "Number of stripes";
            type = int;
            example = 2;
          };
          physical = mkOption {
            description = "List of physical devices (must be even for stripes)";
            example = [ "/dev/sdb" "/dev/sdc" ];
            type = listOf path;
          };
          fileSystems = mkOption {
            description = "Filesystems and their sizes";
            type = attrsOf int;
            example = { "/mariadb/db" = 100; };
          };
          units = mkOption {
            description = "Units of size";
            type = enum [ "%" "m" "g" "t"  "M" "G" "T"];
          };
        };
      });
    };
  };

  config = {
    systemd.services = mapAttrs' mkRaidService groups;

    fileSystems = foldl (a: b: a//b) {} (
      mapAttrsToList (vg: opts: genAttrs (attrNames opts.fileSystems)
        (fs: {
          fsType = "ext4";
          autoFormat = true;
          device = "/dev/${vg}/${baseNameOf fs}";
        })
      ) groups
    );
  };
}