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