diff options
Diffstat (limited to 'nixos')
-rw-r--r-- | nixos/modules/module-list.nix | 1 | ||||
-rw-r--r-- | nixos/modules/services/misc/exhibitor.nix | 361 |
2 files changed, 362 insertions, 0 deletions
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 8f78652fbfd6..b97c3b0d816b 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -287,6 +287,7 @@ ./services/misc/emby.nix ./services/misc/errbot.nix ./services/misc/etcd.nix + ./services/misc/exhibitor.nix ./services/misc/felix.nix ./services/misc/folding-at-home.nix ./services/misc/fstrim.nix diff --git a/nixos/modules/services/misc/exhibitor.nix b/nixos/modules/services/misc/exhibitor.nix new file mode 100644 index 000000000000..33580962bf71 --- /dev/null +++ b/nixos/modules/services/misc/exhibitor.nix @@ -0,0 +1,361 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.exhibitor; + exhibitor = cfg.package; + exhibitorConfig = '' + zookeeper-install-directory=${cfg.baseDir}/zookeeper + zookeeper-data-directory=${cfg.zkDataDir} + zookeeper-log-directory=${cfg.zkLogDir} + zoo-cfg-extra=${cfg.zkExtraCfg} + client-port=${toString cfg.zkClientPort} + connect-port=${toString cfg.zkConnectPort} + election-port=${toString cfg.zkElectionPort} + cleanup-period-ms=${toString cfg.zkCleanupPeriod} + servers-spec=${concatStringsSep "," cfg.zkServersSpec} + auto-manage-instances=${toString cfg.autoManageInstances} + ${cfg.extraConf} + ''; + configDir = pkgs.writeTextDir "exhibitor.properties" exhibitorConfig; + cliOptionsCommon = { + configtype = cfg.configType; + defaultconfig = "${configDir}/exhibitor.properties"; + port = toString cfg.port; + hostname = cfg.hostname; + }; + s3CommonOptions = { s3region = cfg.s3Region; s3credentials = cfg.s3Credentials; }; + cliOptionsPerConfig = { + s3 = { + s3config = "${cfg.s3Config.bucketName}:${cfg.s3Config.objectKey}"; + s3configprefix = cfg.s3Config.configPrefix; + }; + zookeeper = { + zkconfigconnect = concatStringsSep "," cfg.zkConfigConnect; + zkconfigexhibitorpath = cfg.zkConfigExhibitorPath; + zkconfigpollms = toString cfg.zkConfigPollMs; + zkconfigretry = "${toString cfg.zkConfigRetry.sleepMs}:${toString cfg.zkConfigRetry.retryQuantity}"; + zkconfigzpath = cfg.zkConfigZPath; + zkconfigexhibitorport = toString cfg.zkConfigExhibitorPort; # NB: This might be null + }; + file = { + fsconfigdir = cfg.fsConfigDir; + fsconfiglockprefix = cfg.fsConfigLockPrefix; + fsConfigName = fsConfigName; + }; + none = { + noneconfigdir = configDir; + }; + }; + cliOptions = concatStringsSep " " (mapAttrsToList (k: v: "--${k} ${v}") (filterAttrs (k: v: v != null && v != "") (cliOptionsCommon // + cliOptionsPerConfig."${cfg.configType}" // + s3CommonOptions // + optionalAttrs cfg.s3Backup { s3backup = "true"; } // + optionalAttrs cfg.fileSystemBackup { filesystembackup = "true"; } + ))); +in +{ + options = { + services.exhibitor = { + enable = mkOption { + type = types.bool; + default = false; + description = " + Whether to enable the exhibitor server. + "; + }; + # See https://github.com/soabase/exhibitor/wiki/Running-Exhibitor for what these mean + # General options for any type of config + port = mkOption { + type = types.int; + default = 8080; + description = '' + The port for exhibitor to listen on and communicate with other exhibitors. + ''; + }; + baseDir = mkOption { + type = types.str; + default = "/var/exhibitor"; + description = '' + Baseline directory for exhibitor runtime config. + ''; + }; + configType = mkOption { + type = types.enum [ "file" "s3" "zookeeper" "none" ]; + description = '' + Which configuration type you want to use. Additional config will be + required depending on which type you are using. + ''; + }; + hostname = mkOption { + type = types.nullOr types.str; + description = '' + Hostname to use and advertise + ''; + default = null; + }; + autoManageInstances = mkOption { + type = types.bool; + description = '' + Automatically manage ZooKeeper instances in the ensemble + ''; + default = false; + }; + zkDataDir = mkOption { + type = types.str; + default = "${cfg.baseDir}/zkData"; + description = '' + The Zookeeper data directory + ''; + }; + zkLogDir = mkOption { + type = types.path; + default = "${cfg.baseDir}/zkLogs"; + description = '' + The Zookeeper logs directory + ''; + }; + extraConf = mkOption { + type = types.str; + default = ""; + description = '' + Extra Exhibitor configuration to put in the ZooKeeper config file. + ''; + }; + zkExtraCfg = mkOption { + type = types.str; + default = ''initLimit=5&syncLimit=2&tickTime=2000''; + description = '' + Extra options to pass into Zookeeper + ''; + }; + zkClientPort = mkOption { + type = types.int; + default = 2181; + description = '' + Zookeeper client port + ''; + }; + zkConnectPort = mkOption { + type = types.int; + default = 2888; + description = '' + The port to use for followers to talk to each other. + ''; + }; + zkElectionPort = mkOption { + type = types.int; + default = 3888; + description = '' + The port for Zookeepers to use for leader election. + ''; + }; + zkCleanupPeriod = mkOption { + type = types.int; + default = 0; + description = '' + How often (in milliseconds) to run the Zookeeper log cleanup task. + ''; + }; + zkServersSpec = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Zookeeper server spec for all servers in the ensemble. + ''; + example = [ "S:1:zk1.example.com" "S:2:zk2.example.com" "S:3:zk3.example.com" "O:4:zk-observer.example.com" ]; + }; + + # Backup options + s3Backup = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable backups to S3 + ''; + }; + fileSystemBackup = mkOption { + type = types.bool; + default = false; + description = '' + Enables file system backup of ZooKeeper log files + ''; + }; + + # Options for using zookeeper configType + zkConfigConnect = mkOption { + type = types.listOf types.str; + description = '' + The initial connection string for ZooKeeper shared config storage + ''; + example = ["host1:2181" "host2:2181"]; + }; + zkConfigExhibitorPath = mkOption { + type = types.string; + description = '' + If the ZooKeeper shared config is also running Exhibitor, the URI path for the REST call + ''; + default = "/"; + }; + zkConfigExhibitorPort = mkOption { + type = types.nullOr types.int; + description = '' + If the ZooKeeper shared config is also running Exhibitor, the port that + Exhibitor is listening on. IMPORTANT: if this value is not set it implies + that Exhibitor is not being used on the ZooKeeper shared config. + ''; + }; + zkConfigPollMs = mkOption { + type = types.int; + description = '' + The period in ms to check for changes in the config ensemble + ''; + default = 10000; + }; + zkConfigRetry = mkOption { + type = types.submodule { + options = { + sleepMs = mkOption { + type = types.int; + }; + retryQuantity = mkOption { + type = types.int; + }; + }; + }; + description = '' + The retry values to use + ''; + default = { sleepMs = 1000; retryQuantity = 3; }; + }; + zkConfigZPath = mkOption { + type = types.str; + description = '' + The base ZPath that Exhibitor should use + ''; + example = "/exhibitor/config"; + }; + + # Config options for s3 configType + s3Config = mkOption { + type = types.submodule { + options = { + bucketName = mkOption { + type = types.str; + description = '' + Bucket name to store config + ''; + }; + objectKey = mkOption { + type = types.str; + description = '' + S3 key name to store the config + ''; + }; + configPrefix = mkOption { + type = types.str; + description = '' + When using AWS S3 shared config files, the prefix to use for values such as locks + ''; + default = "exhibitor-"; + }; + }; + }; + }; + + # The next two are used for either s3backup or s3 configType + s3Credentials = mkOption { + type = types.nullOr types.path; + description = '' + Optional credentials to use for s3backup or s3config. Argument is the path + to an AWS credential properties file with two properties: + com.netflix.exhibitor.s3.access-key-id and com.netflix.exhibitor.s3.access-secret-key + ''; + default = null; + }; + s3Region = mkOption { + type = types.nullOr types.str; + description = '' + Optional region for S3 calls + ''; + default = null; + }; + + # Config options for file config type + fsConfigDir = mkOption { + type = types.path; + description = '' + Directory to store Exhibitor properties (cannot be used with s3config). + Exhibitor uses file system locks so you can specify a shared location + so as to enable complete ensemble management. + ''; + }; + fsConfigLockPrefix = mkOption { + type = types.str; + description = '' + A prefix for a locking mechanism used in conjunction with fsconfigdir + ''; + default = "exhibitor-lock-"; + }; + fsConfigName = mkOption { + type = types.str; + description = '' + The name of the file to store config in + ''; + default = "exhibitor.properties"; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.exhibitor = { + description = "Exhibitor Daemon"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + environment = { + ZOO_LOG_DIR = cfg.baseDir; + }; + serviceConfig = { + /*** + Exhibitor is a bit un-nixy. It wants to present to you a user interface in order to + mutate the configuration of both itself and ZooKeeper, and to coordinate changes + among the members of the Zookeeper ensemble. I'm going for a different approach here, + which is to manage all the configuration via nix and have it write out the configuration + files that exhibitor will use, and to reduce the amount of inter-exhibitor orchestration. + ***/ + ExecStart = '' + ${pkgs.exhibitor}/bin/startExhibitor.sh ${cliOptions} + ''; + User = "zookeeper"; + PermissionsStartOnly = true; + }; + # This is a bit wonky, but the reason for this is that Exhibitor tries to write to + # ${cfg.baseDir}/zookeeper/bin/../conf/zoo.cfg + # I want everything but the conf directory to be in the immutable nix store, and I want defaults + # from the nix store + # If I symlink the bin directory in, then bin/../ will resolve to the parent of the symlink in the + # immutable nix store. Bind mounting a writable conf over the existing conf might work, but it gets very + # messy with trying to copy the existing out into a mutable store. + # Another option is to try to patch upstream exhibitor, but the current package just pulls down the + # prebuild JARs off of Maven, rather than building them ourselves, as Maven support in Nix isn't + # very mature. So, it seems like a reasonable compromise is to just copy out of the immutable store + # just before starting the service, so we're running binaries from the immutable store, but we work around + # Exhibitor's desire to mutate its current installation. + preStart = '' + mkdir -m 0700 -p ${cfg.baseDir}/zookeeper + # Not doing a chown -R to keep the base ZK files owned by root + chown zookeeper ${cfg.baseDir} ${cfg.baseDir}/zookeeper + cp -Rf ${pkgs.zookeeper}/* ${cfg.baseDir}/zookeeper + chown -R zookeeper ${cfg.baseDir}/zookeeper/conf + chmod -R u+w ${cfg.baseDir}/zookeeper/conf + ''; + }; + users.extraUsers = singleton { + name = "zookeeper"; + uid = config.ids.uids.zookeeper; + description = "Zookeeper daemon user"; + home = cfg.baseDir; + }; + }; +} |