summary refs log tree commit diff
path: root/nixos/modules/services/network-filesystems/openafs/client.nix
blob: c8cc5052c2ace555e1ae2081e80dfe1a4ad0771c (plain) (blame)
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
{ config, lib, pkgs, ... }:

# openafsMod, openafsBin, mkCellServDB
with import ./lib.nix { inherit config lib pkgs; };

let
  inherit (lib) getBin literalExpression mkOption mkIf optionalString singleton types;

  cfg = config.services.openafsClient;

  cellServDB = pkgs.fetchurl {
    url = "http://dl.central.org/dl/cellservdb/CellServDB.2018-05-14";
    sha256 = "1wmjn6mmyy2r8p10nlbdzs4nrqxy8a9pjyrdciy5nmppg4053rk2";
  };

  clientServDB = pkgs.writeText "client-cellServDB-${cfg.cellName}" (mkCellServDB cfg.cellName cfg.cellServDB);

  afsConfig = pkgs.runCommand "afsconfig" { preferLocalBuild = true; } ''
    mkdir -p $out
    echo ${cfg.cellName} > $out/ThisCell
    cat ${cellServDB} ${clientServDB} > $out/CellServDB
    echo "${cfg.mountPoint}:${cfg.cache.directory}:${toString cfg.cache.blocks}" > $out/cacheinfo
  '';

in
{
  ###### interface

  options = {

    services.openafsClient = {

      enable = mkOption {
        default = false;
        type = types.bool;
        description = "Whether to enable the OpenAFS client.";
      };

      afsdb = mkOption {
        default = true;
        type = types.bool;
        description = "Resolve cells via AFSDB DNS records.";
      };

      cellName = mkOption {
        default = "";
        type = types.str;
        description = "Cell name.";
        example = "grand.central.org";
      };

      cellServDB = mkOption {
        default = [];
        type = with types; listOf (submodule { options = cellServDBConfig; });
        description = ''
          This cell's database server records, added to the global
          CellServDB. See CellServDB(5) man page for syntax. Ignored when
          <literal>afsdb</literal> is set to <literal>true</literal>.
        '';
        example = [
          { ip = "1.2.3.4"; dnsname = "first.afsdb.server.dns.fqdn.org"; }
          { ip = "2.3.4.5"; dnsname = "second.afsdb.server.dns.fqdn.org"; }
        ];
      };

      cache = {
        blocks = mkOption {
          default = 100000;
          type = types.int;
          description = "Cache size in 1KB blocks.";
        };

        chunksize = mkOption {
          default = 0;
          type = types.ints.between 0 30;
          description = ''
            Size of each cache chunk given in powers of
            2. <literal>0</literal> resets the chunk size to its default
            values (13 (8 KB) for memcache, 18-20 (256 KB to 1 MB) for
            diskcache). Maximum value is 30. Important performance
            parameter. Set to higher values when dealing with large files.
          '';
        };

        directory = mkOption {
          default = "/var/cache/openafs";
          type = types.str;
          description = "Cache directory.";
        };

        diskless = mkOption {
          default = false;
          type = types.bool;
          description = ''
            Use in-memory cache for diskless machines. Has no real
            performance benefit anymore.
          '';
        };
      };

      crypt = mkOption {
        default = true;
        type = types.bool;
        description = "Whether to enable (weak) protocol encryption.";
      };

      daemons = mkOption {
        default = 2;
        type = types.int;
        description = ''
          Number of daemons to serve user requests. Numbers higher than 6
          usually do no increase performance. Default is sufficient for up
          to five concurrent users.
        '';
      };

      fakestat = mkOption {
        default = false;
        type = types.bool;
        description = ''
          Return fake data on stat() calls. If <literal>true</literal>,
          always do so. If <literal>false</literal>, only do so for
          cross-cell mounts (as these are potentially expensive).
        '';
      };

      inumcalc = mkOption {
        default = "compat";
        type = types.strMatching "compat|md5";
        description = ''
          Inode calculation method. <literal>compat</literal> is
          computationally less expensive, but <literal>md5</literal> greatly
          reduces the likelihood of inode collisions in larger scenarios
          involving multiple cells mounted into one AFS space.
        '';
      };

      mountPoint = mkOption {
        default = "/afs";
        type = types.str;
        description = ''
          Mountpoint of the AFS file tree, conventionally
          <literal>/afs</literal>. When set to a different value, only
          cross-cells that use the same value can be accessed.
        '';
      };

      packages = {
        module = mkOption {
          default = config.boot.kernelPackages.openafs;
          defaultText = literalExpression "config.boot.kernelPackages.openafs";
          type = types.package;
          description = "OpenAFS kernel module package. MUST match the userland package!";
        };
        programs = mkOption {
          default = getBin pkgs.openafs;
          defaultText = literalExpression "getBin pkgs.openafs";
          type = types.package;
          description = "OpenAFS programs package. MUST match the kernel module package!";
        };
      };

      sparse = mkOption {
        default = true;
        type = types.bool;
        description = "Minimal cell list in /afs.";
      };

      startDisconnected = mkOption {
        default = false;
        type = types.bool;
        description = ''
          Start up in disconnected mode.  You need to execute
          <literal>fs disco online</literal> (as root) to switch to
          connected mode. Useful for roaming devices.
        '';
      };

    };
  };


  ###### implementation

  config = mkIf cfg.enable {

    assertions = [
      { assertion = cfg.afsdb || cfg.cellServDB != [];
        message = "You should specify all cell-local database servers in config.services.openafsClient.cellServDB or set config.services.openafsClient.afsdb.";
      }
      { assertion = cfg.cellName != "";
        message = "You must specify the local cell name in config.services.openafsClient.cellName.";
      }
    ];

    environment.systemPackages = [ openafsBin ];

    environment.etc = {
      clientCellServDB = {
        source = pkgs.runCommand "CellServDB" { preferLocalBuild = true; } ''
          cat ${cellServDB} ${clientServDB} > $out
        '';
        target = "openafs/CellServDB";
        mode = "0644";
      };
      clientCell = {
        text = ''
          ${cfg.cellName}
        '';
        target = "openafs/ThisCell";
        mode = "0644";
      };
    };

    systemd.services.afsd = {
      description = "AFS client";
      wantedBy = [ "multi-user.target" ];
      after = singleton (if cfg.startDisconnected then  "network.target" else "network-online.target");
      serviceConfig = { RemainAfterExit = true; };
      restartIfChanged = false;

      preStart = ''
        mkdir -p -m 0755 ${cfg.mountPoint}
        mkdir -m 0700 -p ${cfg.cache.directory}
        ${pkgs.kmod}/bin/insmod ${openafsMod}/lib/modules/*/extra/openafs/libafs.ko.xz
        ${openafsBin}/sbin/afsd \
          -mountdir ${cfg.mountPoint} \
          -confdir ${afsConfig} \
          ${optionalString (!cfg.cache.diskless) "-cachedir ${cfg.cache.directory}"} \
          -blocks ${toString cfg.cache.blocks} \
          -chunksize ${toString cfg.cache.chunksize} \
          ${optionalString cfg.cache.diskless "-memcache"} \
          -inumcalc ${cfg.inumcalc} \
          ${if cfg.fakestat then "-fakestat-all" else "-fakestat"} \
          ${if cfg.sparse then "-dynroot-sparse" else "-dynroot"} \
          ${optionalString cfg.afsdb "-afsdb"}
        ${openafsBin}/bin/fs setcrypt ${if cfg.crypt then "on" else "off"}
        ${optionalString cfg.startDisconnected "${openafsBin}/bin/fs discon offline"}
      '';

      # Doing this in preStop, because after these commands AFS is basically
      # stopped, so systemd has nothing to do, just noticing it.  If done in
      # postStop, then we get a hang + kernel oops, because AFS can't be
      # stopped simply by sending signals to processes.
      preStop = ''
        ${pkgs.util-linux}/bin/umount ${cfg.mountPoint}
        ${openafsBin}/sbin/afsd -shutdown
        ${pkgs.kmod}/sbin/rmmod libafs
      '';
    };
  };
}