1 { config, lib, pkgs, ... }:
3 cfg = config.services.ceph;
5 # function that translates "camelCaseOptions" to "camel case options", credits to tilpner in #nixos@freenode
6 expandCamelCase = lib.replaceStrings lib.upperChars (map (s: " ${s}") lib.lowerChars);
7 expandCamelCaseAttrs = lib.mapAttrs' (name: value: lib.nameValuePair (expandCamelCase name) value);
9 makeServices = daemonType: daemonIds:
10 lib.mkMerge (map (daemonId:
11 { "ceph-${daemonType}-${daemonId}" = makeService daemonType daemonId cfg.global.clusterName cfg.${daemonType}.package; })
14 makeService = daemonType: daemonId: clusterName: ceph:
16 stateDirectory = "ceph/${if daemonType == "rgw" then "radosgw" else daemonType}/${clusterName}-${daemonId}"; in {
18 description = "Ceph ${builtins.replaceStrings lib.lowerChars lib.upperChars daemonType} daemon ${daemonId}";
19 after = [ "network-online.target" "time-sync.target" ] ++ lib.optional (daemonType == "osd") "ceph-mon.target";
20 wants = [ "network-online.target" "time-sync.target" ];
21 partOf = [ "ceph-${daemonType}.target" ];
22 wantedBy = [ "ceph-${daemonType}.target" ];
24 path = [ pkgs.getopt ];
26 # Don't start services that are not yet initialized
27 unitConfig.ConditionPathExists = "/var/lib/${stateDirectory}/keyring";
29 if daemonType == "osd" then 30 else if lib.elem daemonType ["mgr" "mds"] then 3 else 5;
30 startLimitIntervalSec = 60 * 30; # 30 mins
33 LimitNOFILE = 1048576;
35 Environment = "CLUSTER=${clusterName}";
36 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
37 PrivateDevices = "yes";
40 ProtectSystem = "full";
41 Restart = "on-failure";
42 StateDirectory = stateDirectory;
44 Group = if daemonType == "osd" then "disk" else "ceph";
45 ExecStart = ''${ceph.out}/bin/${if daemonType == "rgw" then "radosgw" else "ceph-${daemonType}"} \
46 -f --cluster ${clusterName} --id ${daemonId}'';
47 } // lib.optionalAttrs (daemonType == "osd") {
48 ExecStartPre = "${ceph.lib}/libexec/ceph/ceph-osd-prestart.sh --id ${daemonId} --cluster ${clusterName}";
50 PrivateDevices = "no"; # osd needs disk access
51 } // lib.optionalAttrs ( daemonType == "mon") {
56 makeTarget = daemonType:
58 "ceph-${daemonType}" = {
59 description = "Ceph target allowing to start/stop all ceph-${daemonType} services at once";
60 partOf = [ "ceph.target" ];
61 wantedBy = [ "ceph.target" ];
62 before = [ "ceph.target" ];
63 unitConfig.StopWhenUnneeded = true;
68 options.services.ceph = {
69 # Ceph has a monolithic configuration file but different sections for
70 # each daemon, a separate client section and a global section
71 enable = lib.mkEnableOption "Ceph global configuration";
77 433a2193-4f8a-47a0-95d2-209d7ca2cca5
80 Filesystem ID, a generated uuid, its must be generated and set before
81 attempting to start a cluster
85 clusterName = lib.mkOption {
93 mgrModulePath = lib.mkOption {
94 type = lib.types.path;
95 default = "${pkgs.ceph.lib}/lib/ceph/mgr";
96 defaultText = lib.literalExpression ''"''${pkgs.ceph.lib}/lib/ceph/mgr"'';
98 Path at which to find ceph-mgr modules.
102 monInitialMembers = lib.mkOption {
103 type = with lib.types; nullOr commas;
109 List of hosts that will be used as monitors at startup.
113 monHost = lib.mkOption {
114 type = with lib.types; nullOr commas;
117 10.10.0.1, 10.10.0.2, 10.10.0.3
120 List of hostname shortnames/IP addresses of the initial monitors.
124 maxOpenFiles = lib.mkOption {
125 type = lib.types.int;
128 Max open files for each OSD daemon.
132 authClusterRequired = lib.mkOption {
133 type = lib.types.enum [ "cephx" "none" ];
136 Enables requiring daemons to authenticate with eachother in the cluster.
140 authServiceRequired = lib.mkOption {
141 type = lib.types.enum [ "cephx" "none" ];
144 Enables requiring clients to authenticate with the cluster to access services in the cluster (e.g. radosgw, mds or osd).
148 authClientRequired = lib.mkOption {
149 type = lib.types.enum [ "cephx" "none" ];
152 Enables requiring the cluster to authenticate itself to the client.
156 publicNetwork = lib.mkOption {
157 type = with lib.types; nullOr commas;
160 10.20.0.0/24, 192.168.1.0/24
163 A comma-separated list of subnets that will be used as public networks in the cluster.
167 clusterNetwork = lib.mkOption {
168 type = with lib.types; nullOr commas;
171 10.10.0.0/24, 192.168.0.0/24
174 A comma-separated list of subnets that will be used as cluster networks in the cluster.
178 rgwMimeTypesFile = lib.mkOption {
179 type = with lib.types; nullOr path;
180 default = "${pkgs.mailcap}/etc/mime.types";
181 defaultText = lib.literalExpression ''"''${pkgs.mailcap}/etc/mime.types"'';
183 Path to mime types used by radosgw.
188 extraConfig = lib.mkOption {
189 type = with lib.types; attrsOf str;
192 "ms bind ipv6" = "true";
195 Extra configuration to add to the global section. Use for setting values that are common for all daemons in the cluster.
200 enable = lib.mkEnableOption "Ceph MGR daemon";
201 daemons = lib.mkOption {
202 type = with lib.types; listOf str;
204 example = [ "name1" "name2" ];
206 A list of names for manager daemons that should have a service created. The names correspond
207 to the id part in ceph i.e. [ "name1" ] would result in mgr.name1
210 package = lib.mkPackageOption pkgs "ceph" { };
211 extraConfig = lib.mkOption {
212 type = with lib.types; attrsOf str;
215 Extra configuration to add to the global section for manager daemons.
221 enable = lib.mkEnableOption "Ceph MON daemon";
222 daemons = lib.mkOption {
223 type = with lib.types; listOf str;
225 example = [ "name1" "name2" ];
227 A list of monitor daemons that should have a service created. The names correspond
228 to the id part in ceph i.e. [ "name1" ] would result in mon.name1
231 package = lib.mkPackageOption pkgs "ceph" { };
232 extraConfig = lib.mkOption {
233 type = with lib.types; attrsOf str;
236 Extra configuration to add to the monitor section.
242 enable = lib.mkEnableOption "Ceph OSD daemon";
243 daemons = lib.mkOption {
244 type = with lib.types; listOf str;
246 example = [ "name1" "name2" ];
248 A list of OSD daemons that should have a service created. The names correspond
249 to the id part in ceph i.e. [ "name1" ] would result in osd.name1
252 package = lib.mkPackageOption pkgs "ceph" { };
253 extraConfig = lib.mkOption {
254 type = with lib.types; attrsOf str;
256 "osd journal size" = "10000";
257 "osd pool default size" = "3";
258 "osd pool default min size" = "2";
259 "osd pool default pg num" = "200";
260 "osd pool default pgp num" = "200";
261 "osd crush chooseleaf type" = "1";
264 Extra configuration to add to the OSD section.
270 enable = lib.mkEnableOption "Ceph MDS daemon";
271 daemons = lib.mkOption {
272 type = with lib.types; listOf str;
274 example = [ "name1" "name2" ];
276 A list of metadata service daemons that should have a service created. The names correspond
277 to the id part in ceph i.e. [ "name1" ] would result in mds.name1
280 package = lib.mkPackageOption pkgs "ceph" { };
281 extraConfig = lib.mkOption {
282 type = with lib.types; attrsOf str;
285 Extra configuration to add to the MDS section.
291 enable = lib.mkEnableOption "Ceph RadosGW daemon";
292 package = lib.mkPackageOption pkgs "ceph" { };
293 daemons = lib.mkOption {
294 type = with lib.types; listOf str;
296 example = [ "name1" "name2" ];
298 A list of rados gateway daemons that should have a service created. The names correspond
299 to the id part in ceph i.e. [ "name1" ] would result in client.name1, radosgw daemons
300 aren't daemons to cluster in the sense that OSD, MGR or MON daemons are. They are simply
301 daemons, from ceph, that uses the cluster as a backend.
307 enable = lib.mkEnableOption "Ceph client configuration";
308 extraConfig = lib.mkOption {
309 type = with lib.types; attrsOf (attrsOf str);
311 example = lib.literalExpression ''
313 # This would create a section for a radosgw daemon named node0 and related
314 # configuration for it
315 "client.radosgw.node0" = { "some config option" = "true"; };
319 Extra configuration to add to the client section. Configuration for rados gateways
320 would be added here, with their own sections, see example.
326 config = lib.mkIf config.services.ceph.enable {
328 { assertion = cfg.global.fsid != "";
329 message = "fsid has to be set to a valid uuid for the cluster to function";
331 { assertion = cfg.mon.enable -> cfg.mon.daemons != [];
332 message = "have to set id of atleast one MON if you're going to enable Monitor";
334 { assertion = cfg.mds.enable -> cfg.mds.daemons != [];
335 message = "have to set id of atleast one MDS if you're going to enable Metadata Service";
337 { assertion = cfg.osd.enable -> cfg.osd.daemons != [];
338 message = "have to set id of atleast one OSD if you're going to enable OSD";
340 { assertion = cfg.mgr.enable -> cfg.mgr.daemons != [];
341 message = "have to set id of atleast one MGR if you're going to enable MGR";
345 warnings = lib.optional (cfg.global.monInitialMembers == null)
346 "Not setting up a list of members in monInitialMembers requires that you set the host variable for each mon daemon or else the cluster won't function";
348 environment.etc."ceph/ceph.conf".text = let
349 # Merge the extraConfig set for mgr daemons, as mgr don't have their own section
350 globalSection = expandCamelCaseAttrs (cfg.global // cfg.extraConfig // lib.optionalAttrs cfg.mgr.enable cfg.mgr.extraConfig);
351 # Remove all name-value pairs with null values from the attribute set to avoid making empty sections in the ceph.conf
352 globalSection' = lib.filterAttrs (name: value: value != null) globalSection;
354 global = globalSection';
355 } // lib.optionalAttrs (cfg.mon.enable && cfg.mon.extraConfig != {}) { mon = cfg.mon.extraConfig; }
356 // lib.optionalAttrs (cfg.mds.enable && cfg.mds.extraConfig != {}) { mds = cfg.mds.extraConfig; }
357 // lib.optionalAttrs (cfg.osd.enable && cfg.osd.extraConfig != {}) { osd = cfg.osd.extraConfig; }
358 // lib.optionalAttrs (cfg.client.enable && cfg.client.extraConfig != {}) cfg.client.extraConfig;
360 lib.generators.toINI {} totalConfig;
363 uid = config.ids.uids.ceph;
364 description = "Ceph daemon user";
366 extraGroups = [ "disk" ];
369 users.groups.ceph = {
370 gid = config.ids.gids.ceph;
373 systemd.services = let
375 ++ lib.optional cfg.mon.enable (makeServices "mon" cfg.mon.daemons)
376 ++ lib.optional cfg.mds.enable (makeServices "mds" cfg.mds.daemons)
377 ++ lib.optional cfg.osd.enable (makeServices "osd" cfg.osd.daemons)
378 ++ lib.optional cfg.rgw.enable (makeServices "rgw" cfg.rgw.daemons)
379 ++ lib.optional cfg.mgr.enable (makeServices "mgr" cfg.mgr.daemons);
381 lib.mkMerge services;
383 systemd.targets = let
386 description = "Ceph target allowing to start/stop all ceph service instances at once";
387 wantedBy = [ "multi-user.target" ];
388 unitConfig.StopWhenUnneeded = true;
390 ++ lib.optional cfg.mon.enable (makeTarget "mon")
391 ++ lib.optional cfg.mds.enable (makeTarget "mds")
392 ++ lib.optional cfg.osd.enable (makeTarget "osd")
393 ++ lib.optional cfg.rgw.enable (makeTarget "rgw")
394 ++ lib.optional cfg.mgr.enable (makeTarget "mgr");
398 systemd.tmpfiles.settings."10-ceph" = let
404 "/etc/ceph".d = defaultConfig;
405 "/run/ceph".d = defaultConfig // { mode = "0770"; };
406 "/var/lib/ceph".d = defaultConfig;
407 "/var/lib/ceph/mgr".d = lib.mkIf (cfg.mgr.enable) defaultConfig;
408 "/var/lib/ceph/mon".d = lib.mkIf (cfg.mon.enable) defaultConfig;
409 "/var/lib/ceph/osd".d = lib.mkIf (cfg.osd.enable) defaultConfig;