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}