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