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}