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}