{ config, lib, pkgs, ... }: # openafsMod, openafsBin, mkCellServDB with import ./lib.nix { inherit config lib pkgs; }; let inherit (lib) getBin mkOption mkIf optionalString singleton types; cfg = config.services.openafsClient; cellServDB = pkgs.fetchurl { url = "http://dl.central.org/dl/cellservdb/CellServDB.2018-05-14"; sha256 = "1wmjn6mmyy2r8p10nlbdzs4nrqxy8a9pjyrdciy5nmppg4053rk2"; }; clientServDB = pkgs.writeText "client-cellServDB-${cfg.cellName}" (mkCellServDB cfg.cellName cfg.cellServDB); afsConfig = pkgs.runCommand "afsconfig" { preferLocalBuild = true; } '' mkdir -p $out echo ${cfg.cellName} > $out/ThisCell cat ${cellServDB} ${clientServDB} > $out/CellServDB echo "${cfg.mountPoint}:${cfg.cache.directory}:${toString cfg.cache.blocks}" > $out/cacheinfo ''; in { ###### interface options = { services.openafsClient = { enable = mkOption { default = false; type = types.bool; description = "Whether to enable the OpenAFS client."; }; afsdb = mkOption { default = true; type = types.bool; description = "Resolve cells via AFSDB DNS records."; }; cellName = mkOption { default = ""; type = types.str; description = "Cell name."; example = "grand.central.org"; }; cellServDB = mkOption { default = []; type = with types; listOf (submodule { options = cellServDBConfig; }); description = '' This cell's database server records, added to the global CellServDB. See CellServDB(5) man page for syntax. Ignored when afsdb is set to true. ''; example = '' [ { ip = "1.2.3.4"; dnsname = "first.afsdb.server.dns.fqdn.org"; } { ip = "2.3.4.5"; dnsname = "second.afsdb.server.dns.fqdn.org"; } ] ''; }; cache = { blocks = mkOption { default = 100000; type = types.int; description = "Cache size in 1KB blocks."; }; chunksize = mkOption { default = 0; type = types.ints.between 0 30; description = '' Size of each cache chunk given in powers of 2. 0 resets the chunk size to its default values (13 (8 KB) for memcache, 18-20 (256 KB to 1 MB) for diskcache). Maximum value is 30. Important performance parameter. Set to higher values when dealing with large files. ''; }; directory = mkOption { default = "/var/cache/openafs"; type = types.str; description = "Cache directory."; }; diskless = mkOption { default = false; type = types.bool; description = '' Use in-memory cache for diskless machines. Has no real performance benefit anymore. ''; }; }; crypt = mkOption { default = true; type = types.bool; description = "Whether to enable (weak) protocol encryption."; }; daemons = mkOption { default = 2; type = types.int; description = '' Number of daemons to serve user requests. Numbers higher than 6 usually do no increase performance. Default is sufficient for up to five concurrent users. ''; }; fakestat = mkOption { default = false; type = types.bool; description = '' Return fake data on stat() calls. If true, always do so. If false, only do so for cross-cell mounts (as these are potentially expensive). ''; }; inumcalc = mkOption { default = "compat"; type = types.strMatching "compat|md5"; description = '' Inode calculation method. compat is computationally less expensive, but md5 greatly reduces the likelihood of inode collisions in larger scenarios involving multiple cells mounted into one AFS space. ''; }; mountPoint = mkOption { default = "/afs"; type = types.str; description = '' Mountpoint of the AFS file tree, conventionally /afs. When set to a different value, only cross-cells that use the same value can be accessed. ''; }; packages = { module = mkOption { default = config.boot.kernelPackages.openafs; defaultText = "config.boot.kernelPackages.openafs"; type = types.package; description = "OpenAFS kernel module package. MUST match the userland package!"; }; programs = mkOption { default = getBin pkgs.openafs; defaultText = "getBin pkgs.openafs"; type = types.package; description = "OpenAFS programs package. MUST match the kernel module package!"; }; }; sparse = mkOption { default = true; type = types.bool; description = "Minimal cell list in /afs."; }; startDisconnected = mkOption { default = false; type = types.bool; description = '' Start up in disconnected mode. You need to execute fs disco online (as root) to switch to connected mode. Useful for roaming devices. ''; }; }; }; ###### implementation config = mkIf cfg.enable { assertions = [ { assertion = cfg.afsdb || cfg.cellServDB != []; message = "You should specify all cell-local database servers in config.services.openafsClient.cellServDB or set config.services.openafsClient.afsdb."; } { assertion = cfg.cellName != ""; message = "You must specify the local cell name in config.services.openafsClient.cellName."; } ]; environment.systemPackages = [ openafsBin ]; environment.etc = { clientCellServDB = { source = pkgs.runCommand "CellServDB" { preferLocalBuild = true; } '' cat ${cellServDB} ${clientServDB} > $out ''; target = "openafs/CellServDB"; mode = "0644"; }; clientCell = { text = '' ${cfg.cellName} ''; target = "openafs/ThisCell"; mode = "0644"; }; }; systemd.services.afsd = { description = "AFS client"; wantedBy = [ "multi-user.target" ]; after = singleton (if cfg.startDisconnected then "network.target" else "network-online.target"); serviceConfig = { RemainAfterExit = true; }; restartIfChanged = false; preStart = '' mkdir -p -m 0755 ${cfg.mountPoint} mkdir -m 0700 -p ${cfg.cache.directory} ${pkgs.kmod}/bin/insmod ${openafsMod}/lib/modules/*/extra/openafs/libafs.ko.xz ${openafsBin}/sbin/afsd \ -mountdir ${cfg.mountPoint} \ -confdir ${afsConfig} \ ${optionalString (!cfg.cache.diskless) "-cachedir ${cfg.cache.directory}"} \ -blocks ${toString cfg.cache.blocks} \ -chunksize ${toString cfg.cache.chunksize} \ ${optionalString cfg.cache.diskless "-memcache"} \ -inumcalc ${cfg.inumcalc} \ ${if cfg.fakestat then "-fakestat-all" else "-fakestat"} \ ${if cfg.sparse then "-dynroot-sparse" else "-dynroot"} \ ${optionalString cfg.afsdb "-afsdb"} ${openafsBin}/bin/fs setcrypt ${if cfg.crypt then "on" else "off"} ${optionalString cfg.startDisconnected "${openafsBin}/bin/fs discon offline"} ''; # Doing this in preStop, because after these commands AFS is basically # stopped, so systemd has nothing to do, just noticing it. If done in # postStop, then we get a hang + kernel oops, because AFS can't be # stopped simply by sending signals to processes. preStop = '' ${pkgs.utillinux}/bin/umount ${cfg.mountPoint} ${openafsBin}/sbin/afsd -shutdown ${pkgs.kmod}/sbin/rmmod libafs ''; }; }; }