diff --git a/nixos/modules/services/network-filesystems/openafs/client.nix b/nixos/modules/services/network-filesystems/openafs/client.nix
index e5f89a9a0d2..3826fe3edfd 100644
--- a/nixos/modules/services/network-filesystems/openafs/client.nix
+++ b/nixos/modules/services/network-filesystems/openafs/client.nix
@@ -1,7 +1,9 @@
{ config, pkgs, lib, ... }:
+with import ./lib.nix { inherit lib; };
+
let
- inherit (lib) mkOption mkIf;
+ inherit (lib) getBin mkOption mkIf optionalString singleton types;
cfg = config.services.openafsClient;
@@ -10,14 +12,17 @@ let
sha256 = "1197z6c5xrijgf66rhaymnm5cvyg2yiy1i20y4ah4mrzmjx0m7sc";
};
+ clientServDB = pkgs.writeText "client-cellServDB-${cfg.cellName}" (mkCellServDB cfg.cellName cfg.cellServDB);
+
afsConfig = pkgs.runCommand "afsconfig" {} ''
mkdir -p $out
echo ${cfg.cellName} > $out/ThisCell
- cp ${cellServDB} $out/CellServDB
- echo "/afs:${cfg.cacheDirectory}:${cfg.cacheSize}" > $out/cacheinfo
+ cat ${cellServDB} ${clientServDB} > $out/CellServDB
+ echo "${cfg.mountPoint}:${cfg.cache.directory}:${toString cfg.cache.blocks}" > $out/cacheinfo
'';
- openafsPkgs = config.boot.kernelPackages.openafs;
+ openafsMod = config.boot.kernelPackages.openafs;
+ openafsBin = lib.getBin pkgs.openafs;
in
{
###### interface
@@ -28,34 +33,136 @@ in
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 = "grand.central.org";
+ default = "";
+ type = types.str;
description = "Cell name.";
+ example = "grand.central.org";
};
- cacheSize = mkOption {
- default = "100000";
- description = "Cache size.";
+ 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"; }
+ ]
+ '';
};
- cacheDirectory = mkOption {
- default = "/var/cache/openafs";
- description = "Cache directory.";
+ 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 = false;
+ default = true;
+ type = types.bool;
description = "Whether to enable (weak) protocol encryption.";
};
- sparse = mkOption {
+ 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.
+ '';
+ };
+
+ 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.
+ '';
+ };
+
};
};
@@ -64,26 +171,58 @@ in
config = mkIf cfg.enable {
- environment.systemPackages = [ openafsPkgs ];
-
- environment.etc = [
- { source = afsConfig;
- target = "openafs";
+ 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 = [ pkgs.openafs ];
+
+ environment.etc = {
+ clientCellServDB = {
+ source = pkgs.runCommand "CellServDB" {} ''
+ 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 = [ "network.target" ];
+ after = singleton (if cfg.startDisconnected then "network.target" else "network-online.target");
serviceConfig = { RemainAfterExit = true; };
+ restartIfChanged = false;
preStart = ''
- mkdir -p -m 0755 /afs
- mkdir -m 0700 -p ${cfg.cacheDirectory}
- ${pkgs.kmod}/bin/insmod ${openafsPkgs}/lib/openafs/libafs-*.ko || true
- ${openafsPkgs}/sbin/afsd -confdir ${afsConfig} -cachedir ${cfg.cacheDirectory} ${if cfg.sparse then "-dynroot-sparse" else "-dynroot"} -fakestat -afsdb
- ${openafsPkgs}/bin/fs setcrypt ${if cfg.crypt then "on" else "off"}
+ 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
@@ -91,8 +230,9 @@ 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 /afs
- ${openafsPkgs}/sbin/afsd -shutdown
+ ${pkgs.utillinux}/bin/umount ${cfg.mountPoint}
+ ${openafsBin}/sbin/afsd -shutdown
+ ${pkgs.kmod}/sbin/rmmod libafs
'';
};
};
diff --git a/nixos/modules/services/network-filesystems/openafs/lib.nix b/nixos/modules/services/network-filesystems/openafs/lib.nix
new file mode 100644
index 00000000000..ecfc72d2eaf
--- /dev/null
+++ b/nixos/modules/services/network-filesystems/openafs/lib.nix
@@ -0,0 +1,28 @@
+{ lib, ...}:
+
+let
+ inherit (lib) concatStringsSep mkOption types;
+
+in rec {
+
+ mkCellServDB = cellName: db: ''
+ >${cellName}
+ '' + (concatStringsSep "\n" (map (dbm: if (dbm.ip != "" && dbm.dnsname != "") then dbm.ip + " #" + dbm.dnsname else "")
+ db));
+
+ # CellServDB configuration type
+ cellServDBConfig = {
+ ip = mkOption {
+ type = types.str;
+ default = "";
+ example = "1.2.3.4";
+ description = "IP Address of a database server";
+ };
+ dnsname = mkOption {
+ type = types.str;
+ default = "";
+ example = "afs.example.org";
+ description = "DNS full-qualified domain name of a database server";
+ };
+ };
+}