1{ config, lib, pkgs, ... }:
2
3# openafsMod, openafsBin, mkCellServDB
4with import ./lib.nix { inherit config lib pkgs; };
5
6let
7 inherit (lib) getBin literalExpression mkOption mkIf optionalString singleton types;
8
9 cfg = config.services.openafsClient;
10
11 cellServDB = pkgs.fetchurl {
12 url = "http://dl.central.org/dl/cellservdb/CellServDB.2018-05-14";
13 sha256 = "1wmjn6mmyy2r8p10nlbdzs4nrqxy8a9pjyrdciy5nmppg4053rk2";
14 };
15
16 clientServDB = pkgs.writeText "client-cellServDB-${cfg.cellName}" (mkCellServDB cfg.cellName cfg.cellServDB);
17
18 afsConfig = pkgs.runCommand "afsconfig" { preferLocalBuild = true; } ''
19 mkdir -p $out
20 echo ${cfg.cellName} > $out/ThisCell
21 cat ${cellServDB} ${clientServDB} > $out/CellServDB
22 echo "${cfg.mountPoint}:${cfg.cache.directory}:${toString cfg.cache.blocks}" > $out/cacheinfo
23 '';
24
25in
26{
27 ###### interface
28
29 options = {
30
31 services.openafsClient = {
32
33 enable = mkOption {
34 default = false;
35 type = types.bool;
36 description = lib.mdDoc "Whether to enable the OpenAFS client.";
37 };
38
39 afsdb = mkOption {
40 default = true;
41 type = types.bool;
42 description = lib.mdDoc "Resolve cells via AFSDB DNS records.";
43 };
44
45 cellName = mkOption {
46 default = "";
47 type = types.str;
48 description = lib.mdDoc "Cell name.";
49 example = "grand.central.org";
50 };
51
52 cellServDB = mkOption {
53 default = [];
54 type = with types; listOf (submodule { options = cellServDBConfig; });
55 description = lib.mdDoc ''
56 This cell's database server records, added to the global
57 CellServDB. See CellServDB(5) man page for syntax. Ignored when
58 `afsdb` is set to `true`.
59 '';
60 example = [
61 { ip = "1.2.3.4"; dnsname = "first.afsdb.server.dns.fqdn.org"; }
62 { ip = "2.3.4.5"; dnsname = "second.afsdb.server.dns.fqdn.org"; }
63 ];
64 };
65
66 cache = {
67 blocks = mkOption {
68 default = 100000;
69 type = types.int;
70 description = lib.mdDoc "Cache size in 1KB blocks.";
71 };
72
73 chunksize = mkOption {
74 default = 0;
75 type = types.ints.between 0 30;
76 description = lib.mdDoc ''
77 Size of each cache chunk given in powers of
78 2. `0` resets the chunk size to its default
79 values (13 (8 KB) for memcache, 18-20 (256 KB to 1 MB) for
80 diskcache). Maximum value is 30. Important performance
81 parameter. Set to higher values when dealing with large files.
82 '';
83 };
84
85 directory = mkOption {
86 default = "/var/cache/openafs";
87 type = types.str;
88 description = lib.mdDoc "Cache directory.";
89 };
90
91 diskless = mkOption {
92 default = false;
93 type = types.bool;
94 description = lib.mdDoc ''
95 Use in-memory cache for diskless machines. Has no real
96 performance benefit anymore.
97 '';
98 };
99 };
100
101 crypt = mkOption {
102 default = true;
103 type = types.bool;
104 description = lib.mdDoc "Whether to enable (weak) protocol encryption.";
105 };
106
107 daemons = mkOption {
108 default = 2;
109 type = types.int;
110 description = lib.mdDoc ''
111 Number of daemons to serve user requests. Numbers higher than 6
112 usually do no increase performance. Default is sufficient for up
113 to five concurrent users.
114 '';
115 };
116
117 fakestat = mkOption {
118 default = false;
119 type = types.bool;
120 description = lib.mdDoc ''
121 Return fake data on stat() calls. If `true`,
122 always do so. If `false`, only do so for
123 cross-cell mounts (as these are potentially expensive).
124 '';
125 };
126
127 inumcalc = mkOption {
128 default = "compat";
129 type = types.strMatching "compat|md5";
130 description = lib.mdDoc ''
131 Inode calculation method. `compat` is
132 computationally less expensive, but `md5` greatly
133 reduces the likelihood of inode collisions in larger scenarios
134 involving multiple cells mounted into one AFS space.
135 '';
136 };
137
138 mountPoint = mkOption {
139 default = "/afs";
140 type = types.str;
141 description = lib.mdDoc ''
142 Mountpoint of the AFS file tree, conventionally
143 `/afs`. When set to a different value, only
144 cross-cells that use the same value can be accessed.
145 '';
146 };
147
148 packages = {
149 module = mkOption {
150 default = config.boot.kernelPackages.openafs;
151 defaultText = literalExpression "config.boot.kernelPackages.openafs";
152 type = types.package;
153 description = lib.mdDoc "OpenAFS kernel module package. MUST match the userland package!";
154 };
155 programs = mkOption {
156 default = getBin pkgs.openafs;
157 defaultText = literalExpression "getBin pkgs.openafs";
158 type = types.package;
159 description = lib.mdDoc "OpenAFS programs package. MUST match the kernel module package!";
160 };
161 };
162
163 sparse = mkOption {
164 default = true;
165 type = types.bool;
166 description = lib.mdDoc "Minimal cell list in /afs.";
167 };
168
169 startDisconnected = mkOption {
170 default = false;
171 type = types.bool;
172 description = lib.mdDoc ''
173 Start up in disconnected mode. You need to execute
174 `fs disco online` (as root) to switch to
175 connected mode. Useful for roaming devices.
176 '';
177 };
178
179 };
180 };
181
182
183 ###### implementation
184
185 config = mkIf cfg.enable {
186
187 assertions = [
188 { assertion = cfg.afsdb || cfg.cellServDB != [];
189 message = "You should specify all cell-local database servers in config.services.openafsClient.cellServDB or set config.services.openafsClient.afsdb.";
190 }
191 { assertion = cfg.cellName != "";
192 message = "You must specify the local cell name in config.services.openafsClient.cellName.";
193 }
194 ];
195
196 environment.systemPackages = [ openafsBin ];
197
198 environment.etc = {
199 clientCellServDB = {
200 source = pkgs.runCommand "CellServDB" { preferLocalBuild = true; } ''
201 cat ${cellServDB} ${clientServDB} > $out
202 '';
203 target = "openafs/CellServDB";
204 mode = "0644";
205 };
206 clientCell = {
207 text = ''
208 ${cfg.cellName}
209 '';
210 target = "openafs/ThisCell";
211 mode = "0644";
212 };
213 };
214
215 systemd.services.afsd = {
216 description = "AFS client";
217 wantedBy = [ "multi-user.target" ];
218 after = singleton (if cfg.startDisconnected then "network.target" else "network-online.target");
219 serviceConfig = { RemainAfterExit = true; };
220 restartIfChanged = false;
221
222 preStart = ''
223 mkdir -p -m 0755 ${cfg.mountPoint}
224 mkdir -m 0700 -p ${cfg.cache.directory}
225 ${pkgs.kmod}/bin/insmod ${openafsMod}/lib/modules/*/extra/openafs/libafs.ko.xz
226 ${openafsBin}/sbin/afsd \
227 -mountdir ${cfg.mountPoint} \
228 -confdir ${afsConfig} \
229 ${optionalString (!cfg.cache.diskless) "-cachedir ${cfg.cache.directory}"} \
230 -blocks ${toString cfg.cache.blocks} \
231 -chunksize ${toString cfg.cache.chunksize} \
232 ${optionalString cfg.cache.diskless "-memcache"} \
233 -inumcalc ${cfg.inumcalc} \
234 ${if cfg.fakestat then "-fakestat-all" else "-fakestat"} \
235 ${if cfg.sparse then "-dynroot-sparse" else "-dynroot"} \
236 ${optionalString cfg.afsdb "-afsdb"}
237 ${openafsBin}/bin/fs setcrypt ${if cfg.crypt then "on" else "off"}
238 ${optionalString cfg.startDisconnected "${openafsBin}/bin/fs discon offline"}
239 '';
240
241 # Doing this in preStop, because after these commands AFS is basically
242 # stopped, so systemd has nothing to do, just noticing it. If done in
243 # postStop, then we get a hang + kernel oops, because AFS can't be
244 # stopped simply by sending signals to processes.
245 preStop = ''
246 ${pkgs.util-linux}/bin/umount ${cfg.mountPoint}
247 ${openafsBin}/sbin/afsd -shutdown
248 ${pkgs.kmod}/sbin/rmmod libafs
249 '';
250 };
251 };
252}