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