diff options
Diffstat (limited to 'nixpkgs/nixos/modules/services/misc')
100 files changed, 15198 insertions, 0 deletions
diff --git a/nixpkgs/nixos/modules/services/misc/airsonic.nix b/nixpkgs/nixos/modules/services/misc/airsonic.nix new file mode 100644 index 000000000000..8b2ec82c7705 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/airsonic.nix @@ -0,0 +1,153 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.airsonic; +in { + options = { + + services.airsonic = { + enable = mkEnableOption "Airsonic, the Free and Open Source media streaming server (fork of Subsonic and Libresonic)"; + + user = mkOption { + type = types.str; + default = "airsonic"; + description = "User account under which airsonic runs."; + }; + + home = mkOption { + type = types.path; + default = "/var/lib/airsonic"; + description = '' + The directory where Airsonic will create files. + Make sure it is writable. + ''; + }; + + virtualHost = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Name of the nginx virtualhost to use and setup. If null, do not setup any virtualhost. + ''; + }; + + listenAddress = mkOption { + type = types.string; + default = "127.0.0.1"; + description = '' + The host name or IP address on which to bind Airsonic. + Only relevant if you have multiple network interfaces and want + to make Airsonic available on only one of them. The default value + will bind Airsonic to all available network interfaces. + ''; + }; + + port = mkOption { + type = types.int; + default = 4040; + description = '' + The port on which Airsonic will listen for + incoming HTTP traffic. Set to 0 to disable. + ''; + }; + + contextPath = mkOption { + type = types.path; + default = "/"; + description = '' + The context path, i.e., the last part of the Airsonic + URL. Typically '/' or '/airsonic'. Default '/' + ''; + }; + + maxMemory = mkOption { + type = types.int; + default = 100; + description = '' + The memory limit (max Java heap size) in megabytes. + Default: 100 + ''; + }; + + transcoders = mkOption { + type = types.listOf types.path; + default = [ "${pkgs.ffmpeg.bin}/bin/ffmpeg" ]; + defaultText= [ "\${pkgs.ffmpeg.bin}/bin/ffmpeg" ]; + description = '' + List of paths to transcoder executables that should be accessible + from Airsonic. Symlinks will be created to each executable inside + ${cfg.home}/transcoders. + ''; + }; + + jvmOptions = mkOption { + description = '' + Extra command line options for the JVM running AirSonic. + Useful for sending jukebox output to non-default alsa + devices. + ''; + default = [ + ]; + type = types.listOf types.str; + example = [ + "-Djavax.sound.sampled.Clip='#CODEC [plughw:1,0]'" + "-Djavax.sound.sampled.Port='#Port CODEC [hw:1]'" + "-Djavax.sound.sampled.SourceDataLine='#CODEC [plughw:1,0]'" + "-Djavax.sound.sampled.TargetDataLine='#CODEC [plughw:1,0]'" + ]; + }; + + }; + }; + + config = mkIf cfg.enable { + systemd.services.airsonic = { + description = "Airsonic Media Server"; + after = [ "local-fs.target" "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + preStart = '' + # Install transcoders. + rm -rf ${cfg.home}/transcode + mkdir -p ${cfg.home}/transcode + for exe in ${toString cfg.transcoders}; do + ln -sf "$exe" ${cfg.home}/transcode + done + ''; + serviceConfig = { + ExecStart = '' + ${pkgs.jre}/bin/java -Xmx${toString cfg.maxMemory}m \ + -Dairsonic.home=${cfg.home} \ + -Dserver.address=${cfg.listenAddress} \ + -Dserver.port=${toString cfg.port} \ + -Dairsonic.contextPath=${cfg.contextPath} \ + -Djava.awt.headless=true \ + ${optionalString (cfg.virtualHost != null) + "-Dserver.use-forward-headers=true"} \ + ${toString cfg.jvmOptions} \ + -verbose:gc \ + -jar ${pkgs.airsonic}/webapps/airsonic.war + ''; + Restart = "always"; + User = "airsonic"; + UMask = "0022"; + }; + }; + + services.nginx = mkIf (cfg.virtualHost != null) { + enable = true; + virtualHosts."${cfg.virtualHost}" = { + locations."${cfg.contextPath}".proxyPass = "http://${cfg.listenAddress}:${toString cfg.port}"; + }; + }; + + users.users.airsonic = { + description = "Airsonic service user"; + name = cfg.user; + home = cfg.home; + createHome = true; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/apache-kafka.nix b/nixpkgs/nixos/modules/services/misc/apache-kafka.nix new file mode 100644 index 000000000000..363ac4411e11 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/apache-kafka.nix @@ -0,0 +1,160 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.apache-kafka; + + serverProperties = + if cfg.serverProperties != null then + cfg.serverProperties + else + '' + # Generated by nixos + broker.id=${toString cfg.brokerId} + port=${toString cfg.port} + host.name=${cfg.hostname} + log.dirs=${concatStringsSep "," cfg.logDirs} + zookeeper.connect=${cfg.zookeeper} + ${toString cfg.extraProperties} + ''; + + serverConfig = pkgs.writeText "server.properties" serverProperties; + logConfig = pkgs.writeText "log4j.properties" cfg.log4jProperties; + +in { + + options.services.apache-kafka = { + enable = mkOption { + description = "Whether to enable Apache Kafka."; + default = false; + type = types.bool; + }; + + brokerId = mkOption { + description = "Broker ID."; + default = -1; + type = types.int; + }; + + port = mkOption { + description = "Port number the broker should listen on."; + default = 9092; + type = types.int; + }; + + hostname = mkOption { + description = "Hostname the broker should bind to."; + default = "localhost"; + type = types.string; + }; + + logDirs = mkOption { + description = "Log file directories"; + default = [ "/tmp/kafka-logs" ]; + type = types.listOf types.path; + }; + + zookeeper = mkOption { + description = "Zookeeper connection string"; + default = "localhost:2181"; + type = types.string; + }; + + extraProperties = mkOption { + description = "Extra properties for server.properties."; + type = types.nullOr types.lines; + default = null; + }; + + serverProperties = mkOption { + description = '' + Complete server.properties content. Other server.properties config + options will be ignored if this option is used. + ''; + type = types.nullOr types.lines; + default = null; + }; + + log4jProperties = mkOption { + description = "Kafka log4j property configuration."; + default = '' + log4j.rootLogger=INFO, stdout + + log4j.appender.stdout=org.apache.log4j.ConsoleAppender + log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + log4j.appender.stdout.layout.ConversionPattern=[%d] %p %m (%c)%n + ''; + type = types.lines; + }; + + jvmOptions = mkOption { + description = "Extra command line options for the JVM running Kafka."; + default = [ + "-server" + "-Xmx1G" + "-Xms1G" + "-XX:+UseCompressedOops" + "-XX:+UseParNewGC" + "-XX:+UseConcMarkSweepGC" + "-XX:+CMSClassUnloadingEnabled" + "-XX:+CMSScavengeBeforeRemark" + "-XX:+DisableExplicitGC" + "-Djava.awt.headless=true" + "-Djava.net.preferIPv4Stack=true" + ]; + type = types.listOf types.str; + example = [ + "-Djava.net.preferIPv4Stack=true" + "-Dcom.sun.management.jmxremote" + "-Dcom.sun.management.jmxremote.local.only=true" + ]; + }; + + package = mkOption { + description = "The kafka package to use"; + default = pkgs.apacheKafka; + defaultText = "pkgs.apacheKafka"; + type = types.package; + }; + + }; + + config = mkIf cfg.enable { + + environment.systemPackages = [cfg.package]; + + users.users = singleton { + name = "apache-kafka"; + uid = config.ids.uids.apache-kafka; + description = "Apache Kafka daemon user"; + home = head cfg.logDirs; + }; + + systemd.services.apache-kafka = { + description = "Apache Kafka Daemon"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + ExecStart = '' + ${pkgs.jre}/bin/java \ + -cp "${cfg.package}/libs/*" \ + -Dlog4j.configuration=file:${logConfig} \ + ${toString cfg.jvmOptions} \ + kafka.Kafka \ + ${serverConfig} + ''; + User = "apache-kafka"; + PermissionsStartOnly = true; + SuccessExitStatus = "0 143"; + }; + preStart = '' + mkdir -m 0700 -p ${concatStringsSep " " cfg.logDirs} + if [ "$(id -u)" = 0 ]; then + chown apache-kafka ${concatStringsSep " " cfg.logDirs}; + fi + ''; + }; + + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/autofs.nix b/nixpkgs/nixos/modules/services/misc/autofs.nix new file mode 100644 index 000000000000..f1742177326a --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/autofs.nix @@ -0,0 +1,97 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.autofs; + + autoMaster = pkgs.writeText "auto.master" cfg.autoMaster; + +in + +{ + + ###### interface + + options = { + + services.autofs = { + + enable = mkOption { + default = false; + description = '' + Mount filesystems on demand. Unmount them automatically. + You may also be interested in afuse. + ''; + }; + + autoMaster = mkOption { + type = types.str; + example = literalExample '' + let + mapConf = pkgs.writeText "auto" ''' + kernel -ro,soft,intr ftp.kernel.org:/pub/linux + boot -fstype=ext2 :/dev/hda1 + windoze -fstype=smbfs ://windoze/c + removable -fstype=ext2 :/dev/hdd + cd -fstype=iso9660,ro :/dev/hdc + floppy -fstype=auto :/dev/fd0 + server -rw,hard,intr / -ro myserver.me.org:/ \ + /usr myserver.me.org:/usr \ + /home myserver.me.org:/home + '''; + in ''' + /auto file:''${mapConf} + ''' + ''; + description = '' + Contents of <literal>/etc/auto.master</literal> file. See <command>auto.master(5)</command> and <command>autofs(5)</command>. + ''; + }; + + timeout = mkOption { + default = 600; + description = "Set the global minimum timeout, in seconds, until directories are unmounted"; + }; + + debug = mkOption { + default = false; + description = '' + Pass -d and -7 to automount and write log to the system journal. + ''; + }; + + }; + + }; + + + ###### implementation + + config = mkIf cfg.enable { + + boot.kernelModules = [ "autofs4" ]; + + systemd.services.autofs = + { description = "Automounts filesystems on demand"; + after = [ "network.target" "ypbind.service" "sssd.service" "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + + preStart = '' + # There should be only one autofs service managed by systemd, so this should be safe. + rm -f /tmp/autofs-running + ''; + + serviceConfig = { + Type = "forking"; + PIDFile = "/run/autofs.pid"; + ExecStart = "${pkgs.autofs5}/bin/automount ${optionalString cfg.debug "-d"} -p /run/autofs.pid -t ${builtins.toString cfg.timeout} ${autoMaster}"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + }; + }; + + }; + +} diff --git a/nixpkgs/nixos/modules/services/misc/autorandr.nix b/nixpkgs/nixos/modules/services/misc/autorandr.nix new file mode 100644 index 000000000000..4708e16e2a6c --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/autorandr.nix @@ -0,0 +1,52 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.autorandr; + +in { + + options = { + + services.autorandr = { + enable = mkEnableOption "handling of hotplug and sleep events by autorandr"; + + defaultTarget = mkOption { + default = "default"; + type = types.str; + description = '' + Fallback if no monitor layout can be detected. See the docs + (https://github.com/phillipberndt/autorandr/blob/v1.0/README.md#how-to-use) + for further reference. + ''; + }; + }; + + }; + + config = mkIf cfg.enable { + + services.udev.packages = [ pkgs.autorandr ]; + + environment.systemPackages = [ pkgs.autorandr ]; + + systemd.services.autorandr = { + wantedBy = [ "sleep.target" ]; + description = "Autorandr execution hook"; + after = [ "sleep.target" ]; + + serviceConfig = { + StartLimitInterval = 5; + StartLimitBurst = 1; + ExecStart = "${pkgs.autorandr}/bin/autorandr --batch --change --default ${cfg.defaultTarget}"; + Type = "oneshot"; + RemainAfterExit = false; + }; + }; + + }; + + meta.maintainers = with maintainers; [ gnidorah ma27 ]; +} diff --git a/nixpkgs/nixos/modules/services/misc/beanstalkd.nix b/nixpkgs/nixos/modules/services/misc/beanstalkd.nix new file mode 100644 index 000000000000..06e881406b52 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/beanstalkd.nix @@ -0,0 +1,52 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.beanstalkd; + pkg = pkgs.beanstalkd; +in + +{ + # interface + + options = { + services.beanstalkd = { + enable = mkEnableOption "the Beanstalk work queue"; + + listen = { + port = mkOption { + type = types.int; + description = "TCP port that will be used to accept client connections."; + default = 11300; + }; + + address = mkOption { + type = types.str; + description = "IP address to listen on."; + default = "127.0.0.1"; + example = "0.0.0.0"; + }; + }; + }; + }; + + # implementation + + config = mkIf cfg.enable { + + environment.systemPackages = [ pkg ]; + + systemd.services.beanstalkd = { + description = "Beanstalk Work Queue"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + DynamicUser = true; + Restart = "always"; + ExecStart = "${pkg}/bin/beanstalkd -l ${cfg.listen.address} -p ${toString cfg.listen.port}"; + }; + }; + + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/bees.nix b/nixpkgs/nixos/modules/services/misc/bees.nix new file mode 100644 index 000000000000..b0ed2d5c2862 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/bees.nix @@ -0,0 +1,123 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.beesd; + + logLevels = { emerg = 0; alert = 1; crit = 2; err = 3; warning = 4; notice = 5; info = 6; debug = 7; }; + + fsOptions = with types; { + options.spec = mkOption { + type = str; + description = '' + Description of how to identify the filesystem to be duplicated by this + instance of bees. Note that deduplication crosses subvolumes; one must + not configure multiple instances for subvolumes of the same filesystem + (or block devices which are part of the same filesystem), but only for + completely independent btrfs filesystems. + </para> + <para> + This must be in a format usable by findmnt; that could be a key=value + pair, or a bare path to a mount point. + ''; + example = "LABEL=MyBulkDataDrive"; + }; + options.hashTableSizeMB = mkOption { + type = types.addCheck types.int (n: mod n 16 == 0); + default = 1024; # 1GB; default from upstream beesd script + description = '' + Hash table size in MB; must be a multiple of 16. + </para> + <para> + A larger ratio of index size to storage size means smaller blocks of + duplicate content are recognized. + </para> + <para> + If you have 1TB of data, a 4GB hash table (which is to say, a value of + 4096) will permit 4KB extents (the smallest possible size) to be + recognized, whereas a value of 1024 -- creating a 1GB hash table -- + will recognize only aligned duplicate blocks of 16KB. + ''; + }; + options.verbosity = mkOption { + type = types.enum (attrNames logLevels ++ attrValues logLevels); + apply = v: if isString v then logLevels.${v} else v; + default = "info"; + description = "Log verbosity (syslog keyword/level)."; + }; + options.workDir = mkOption { + type = str; + default = ".beeshome"; + description = '' + Name (relative to the root of the filesystem) of the subvolume where + the hash table will be stored. + ''; + }; + options.extraOptions = mkOption { + type = listOf str; + default = []; + description = '' + Extra command-line options passed to the daemon. See upstream bees documentation. + ''; + example = literalExample '' + [ "--thread-count" "4" ] + ''; + }; + }; + +in { + + options.services.beesd = { + filesystems = mkOption { + type = with types; attrsOf (submodule fsOptions); + description = "BTRFS filesystems to run block-level deduplication on."; + default = { }; + example = literalExample '' + { + root = { + spec = "LABEL=root"; + hashTableSizeMB = 2048; + verbosity = "crit"; + extraOptions = [ "--loadavg-target" "5.0" ]; + }; + } + ''; + }; + }; + config = { + systemd.services = mapAttrs' (name: fs: nameValuePair "beesd@${name}" { + description = "Block-level BTRFS deduplication for %i"; + after = [ "sysinit.target" ]; + + serviceConfig = let + configOpts = [ + fs.spec + "verbosity=${toString fs.verbosity}" + "idxSizeMB=${toString fs.hashTableSizeMB}" + "workDir=${fs.workDir}" + ]; + configOptsStr = escapeShellArgs configOpts; + in { + # Values from https://github.com/Zygo/bees/blob/v0.6.1/scripts/beesd%40.service.in + ExecStart = "${pkgs.bees}/bin/bees-service-wrapper run ${configOptsStr} -- --no-timestamps ${escapeShellArgs fs.extraOptions}"; + ExecStopPost = "${pkgs.bees}/bin/bees-service-wrapper cleanup ${configOptsStr}"; + CPUAccounting = true; + CPUWeight = 12; + IOSchedulingClass = "idle"; + IOSchedulingPriority = 7; + IOWeight = 10; + KillMode = "control-group"; + KillSignal = "SIGTERM"; + MemoryAccounting = true; + Nice = 19; + Restart = "on-abnormal"; + StartupCPUWeight = 25; + StartupIOWeight = 25; + SyslogIdentifier = "bees"; # would otherwise be "bees-service-wrapper" + }; + wantedBy = ["multi-user.target"]; + }) cfg.filesystems; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/bepasty.nix b/nixpkgs/nixos/modules/services/misc/bepasty.nix new file mode 100644 index 000000000000..006feca42b32 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/bepasty.nix @@ -0,0 +1,183 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + gunicorn = pkgs.python3Packages.gunicorn; + bepasty = pkgs.bepasty; + gevent = pkgs.python3Packages.gevent; + python = pkgs.python3Packages.python; + cfg = config.services.bepasty; + user = "bepasty"; + group = "bepasty"; + default_home = "/var/lib/bepasty"; +in +{ + options.services.bepasty = { + enable = mkEnableOption "Bepasty servers"; + + servers = mkOption { + default = {}; + description = '' + configure a number of bepasty servers which will be started with + gunicorn. + ''; + type = with types ; attrsOf (submodule ({ config, ... } : { + + options = { + + bind = mkOption { + type = types.str; + description = '' + Bind address to be used for this server. + ''; + example = "0.0.0.0:8000"; + default = "127.0.0.1:8000"; + }; + + dataDir = mkOption { + type = types.str; + description = '' + Path to the directory where the pastes will be saved to + ''; + default = default_home+"/data"; + }; + + defaultPermissions = mkOption { + type = types.str; + description = '' + default permissions for all unauthenticated accesses. + ''; + example = "read,create,delete"; + default = "read"; + }; + + extraConfig = mkOption { + type = types.lines; + description = '' + Extra configuration for bepasty server to be appended on the + configuration. + see https://bepasty-server.readthedocs.org/en/latest/quickstart.html#configuring-bepasty + for all options. + ''; + default = ""; + example = '' + PERMISSIONS = { + 'myadminsecret': 'admin,list,create,read,delete', + } + MAX_ALLOWED_FILE_SIZE = 5 * 1000 * 1000 + ''; + }; + + secretKey = mkOption { + type = types.str; + description = '' + server secret for safe session cookies, must be set. + + Warning: this secret is stored in the WORLD-READABLE Nix store! + + It's recommended to use <option>secretKeyFile</option> + which takes precedence over <option>secretKey</option>. + ''; + default = ""; + }; + + secretKeyFile = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + A file that contains the server secret for safe session cookies, must be set. + + <option>secretKeyFile</option> takes precedence over <option>secretKey</option>. + + Warning: when <option>secretKey</option> is non-empty <option>secretKeyFile</option> + defaults to a file in the WORLD-READABLE Nix store containing that secret. + ''; + }; + + workDir = mkOption { + type = types.str; + description = '' + Path to the working directory (used for config and pidfile). + Defaults to the users home directory. + ''; + default = default_home; + }; + + }; + config = { + secretKeyFile = mkDefault ( + if config.secretKey != "" + then toString (pkgs.writeTextFile { + name = "bepasty-secret-key"; + text = config.secretKey; + }) + else null + ); + }; + })); + }; + }; + + config = mkIf cfg.enable { + + environment.systemPackages = [ bepasty ]; + + # creates gunicorn systemd service for each configured server + systemd.services = mapAttrs' (name: server: + nameValuePair ("bepasty-server-${name}-gunicorn") + ({ + description = "Bepasty Server ${name}"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + restartIfChanged = true; + + environment = let + penv = python.buildEnv.override { + extraLibs = [ bepasty gevent ]; + }; + in { + BEPASTY_CONFIG = "${server.workDir}/bepasty-${name}.conf"; + PYTHONPATH= "${penv}/${python.sitePackages}/"; + }; + + serviceConfig = { + Type = "simple"; + PrivateTmp = true; + ExecStartPre = assert !isNull server.secretKeyFile; pkgs.writeScript "bepasty-server.${name}-init" '' + #!/bin/sh + mkdir -p "${server.workDir}" + mkdir -p "${server.dataDir}" + chown ${user}:${group} "${server.workDir}" "${server.dataDir}" + cat > ${server.workDir}/bepasty-${name}.conf <<EOF + SITENAME="${name}" + STORAGE_FILESYSTEM_DIRECTORY="${server.dataDir}" + SECRET_KEY="$(cat "${server.secretKeyFile}")" + DEFAULT_PERMISSIONS="${server.defaultPermissions}" + ${server.extraConfig} + EOF + ''; + ExecStart = ''${gunicorn}/bin/gunicorn bepasty.wsgi --name ${name} \ + -u ${user} \ + -g ${group} \ + --workers 3 --log-level=info \ + --bind=${server.bind} \ + --pid ${server.workDir}/gunicorn-${name}.pid \ + -k gevent + ''; + }; + }) + ) cfg.servers; + + users.users = [{ + uid = config.ids.uids.bepasty; + name = user; + group = group; + home = default_home; + }]; + + users.groups = [{ + name = group; + gid = config.ids.gids.bepasty; + }]; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/calibre-server.nix b/nixpkgs/nixos/modules/services/misc/calibre-server.nix new file mode 100644 index 000000000000..84c04f403d3a --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/calibre-server.nix @@ -0,0 +1,63 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.calibre-server; + +in + +{ + + ###### interface + + options = { + + services.calibre-server = { + + enable = mkEnableOption "calibre-server"; + + libraryDir = mkOption { + description = '' + The directory where the Calibre library to serve is. + ''; + type = types.path; + }; + + }; + + }; + + + ###### implementation + + config = mkIf cfg.enable { + + systemd.services.calibre-server = + { + description = "Calibre Server"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + User = "calibre-server"; + Restart = "always"; + ExecStart = "${pkgs.calibre}/bin/calibre-server ${cfg.libraryDir}"; + }; + + }; + + environment.systemPackages = [ pkgs.calibre ]; + + users.users.calibre-server = { + uid = config.ids.uids.calibre-server; + group = "calibre-server"; + }; + + users.groups.calibre-server = { + gid = config.ids.gids.calibre-server; + }; + + }; + +} diff --git a/nixpkgs/nixos/modules/services/misc/canto-daemon.nix b/nixpkgs/nixos/modules/services/misc/canto-daemon.nix new file mode 100644 index 000000000000..db51a263aab5 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/canto-daemon.nix @@ -0,0 +1,37 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + +cfg = config.services.canto-daemon; + +in { + +##### interface + + options = { + + services.canto-daemon = { + enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable the canto RSS daemon."; + }; + }; + + }; + +##### implementation + + config = mkIf cfg.enable { + + systemd.user.services.canto-daemon = { + description = "Canto RSS Daemon"; + after = [ "network.target" ]; + wantedBy = [ "default.target" ]; + serviceConfig.ExecStart = "${pkgs.canto-daemon}/bin/canto-daemon"; + }; + }; + +} diff --git a/nixpkgs/nixos/modules/services/misc/cfdyndns.nix b/nixpkgs/nixos/modules/services/misc/cfdyndns.nix new file mode 100644 index 000000000000..dcf416022734 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/cfdyndns.nix @@ -0,0 +1,70 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.cfdyndns; +in +{ + options = { + services.cfdyndns = { + enable = mkEnableOption "Cloudflare Dynamic DNS Client"; + + email = mkOption { + type = types.str; + description = '' + The email address to use to authenticate to CloudFlare. + ''; + }; + + apikey = mkOption { + type = types.str; + description = '' + The API Key to use to authenticate to CloudFlare. + ''; + }; + + records = mkOption { + default = []; + example = [ "host.tld" ]; + type = types.listOf types.str; + description = '' + The records to update in CloudFlare. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.cfdyndns = { + description = "CloudFlare Dynamic DNS Client"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + startAt = "5 minutes"; + serviceConfig = { + Type = "simple"; + User = config.ids.uids.cfdyndns; + Group = config.ids.gids.cfdyndns; + ExecStart = "/bin/sh -c '${pkgs.cfdyndns}/bin/cfdyndns'"; + }; + environment = { + CLOUDFLARE_EMAIL="${cfg.email}"; + CLOUDFLARE_APIKEY="${cfg.apikey}"; + CLOUDFLARE_RECORDS="${concatStringsSep "," cfg.records}"; + }; + }; + + users.users = { + cfdyndns = { + group = "cfdyndns"; + uid = config.ids.uids.cfdyndns; + }; + }; + + users.groups = { + cfdyndns = { + gid = config.ids.gids.cfdyndns; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/cgminer.nix b/nixpkgs/nixos/modules/services/misc/cgminer.nix new file mode 100644 index 000000000000..b1cf5a7d1104 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/cgminer.nix @@ -0,0 +1,145 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.cgminer; + + convType = with builtins; + v: if isBool v then boolToString v else toString v; + mergedHwConfig = + mapAttrsToList (n: v: ''"${n}": "${(concatStringsSep "," (map convType v))}"'') + (foldAttrs (n: a: [n] ++ a) [] cfg.hardware); + mergedConfig = with builtins; + mapAttrsToList (n: v: ''"${n}": ${if isBool v then "" else ''"''}${convType v}${if isBool v then "" else ''"''}'') + cfg.config; + + cgminerConfig = pkgs.writeText "cgminer.conf" '' + { + ${concatStringsSep ",\n" mergedHwConfig}, + ${concatStringsSep ",\n" mergedConfig}, + "pools": [ + ${concatStringsSep ",\n" + (map (v: ''{"url": "${v.url}", "user": "${v.user}", "pass": "${v.pass}"}'') + cfg.pools)}] + } + ''; +in +{ + ###### interface + options = { + + services.cgminer = { + + enable = mkOption { + default = false; + description = '' + Whether to enable cgminer, an ASIC/FPGA/GPU miner for bitcoin and + litecoin. + ''; + }; + + package = mkOption { + default = pkgs.cgminer; + defaultText = "pkgs.cgminer"; + description = "Which cgminer derivation to use."; + type = types.package; + }; + + user = mkOption { + default = "cgminer"; + description = "User account under which cgminer runs"; + }; + + pools = mkOption { + default = []; # Run benchmark + description = "List of pools where to mine"; + example = [{ + url = "http://p2pool.org:9332"; + username = "17EUZxTvs9uRmPsjPZSYUU3zCz9iwstudk"; + password="X"; + }]; + }; + + hardware = mkOption { + default = []; # Run without options + description= "List of config options for every GPU"; + example = [ + { + intensity = 9; + gpu-engine = "0-985"; + gpu-fan = "0-85"; + gpu-memclock = 860; + gpu-powertune = 20; + temp-cutoff = 95; + temp-overheat = 85; + temp-target = 75; + } + { + intensity = 9; + gpu-engine = "0-950"; + gpu-fan = "0-85"; + gpu-memclock = 825; + gpu-powertune = 20; + temp-cutoff = 95; + temp-overheat = 85; + temp-target = 75; + }]; + }; + + config = mkOption { + default = {}; + description = "Additional config"; + example = { + auto-fan = true; + auto-gpu = true; + expiry = 120; + failover-only = true; + gpu-threads = 2; + log = 5; + queue = 1; + scan-time = 60; + temp-histeresys = 3; + }; + }; + }; + }; + + + ###### implementation + + config = mkIf config.services.cgminer.enable { + + users.users = optionalAttrs (cfg.user == "cgminer") (singleton + { name = "cgminer"; + uid = config.ids.uids.cgminer; + description = "Cgminer user"; + }); + + environment.systemPackages = [ cfg.package ]; + + systemd.services.cgminer = { + path = [ pkgs.cgminer ]; + + after = [ "network.target" "display-manager.service" ]; + wantedBy = [ "multi-user.target" ]; + + environment = { + LD_LIBRARY_PATH = ''/run/opengl-driver/lib:/run/opengl-driver-32/lib''; + DISPLAY = ":${toString config.services.xserver.display}"; + GPU_MAX_ALLOC_PERCENT = "100"; + GPU_USE_SYNC_OBJECTS = "1"; + }; + + serviceConfig = { + ExecStart = "${pkgs.cgminer}/bin/cgminer --syslog --text-only --config ${cgminerConfig}"; + User = cfg.user; + RestartSec = "30s"; + Restart = "always"; + StartLimitInterval = "1m"; + }; + }; + + }; + +} diff --git a/nixpkgs/nixos/modules/services/misc/clipmenu.nix b/nixpkgs/nixos/modules/services/misc/clipmenu.nix new file mode 100644 index 000000000000..3ba050044cac --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/clipmenu.nix @@ -0,0 +1,31 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.clipmenu; +in { + + options.services.clipmenu = { + enable = mkEnableOption "clipmenu, the clipboard management daemon"; + + package = mkOption { + type = types.package; + default = pkgs.clipmenu; + defaultText = "pkgs.clipmenu"; + description = "clipmenu derivation to use."; + }; + }; + + config = mkIf cfg.enable { + systemd.user.services.clipmenu = { + enable = true; + description = "Clipboard management daemon"; + wantedBy = [ "graphical-session.target" ]; + after = [ "graphical-session.target" ]; + serviceConfig.ExecStart = "${cfg.package}/bin/clipmenud"; + }; + + environment.systemPackages = [ cfg.package ]; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/confd.nix b/nixpkgs/nixos/modules/services/misc/confd.nix new file mode 100755 index 000000000000..8e9bec15dd4f --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/confd.nix @@ -0,0 +1,90 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.confd; + + confdConfig = '' + backend = "${cfg.backend}" + confdir = "${cfg.confDir}" + interval = ${toString cfg.interval} + nodes = [ ${concatMapStringsSep "," (s: ''"${s}"'') cfg.nodes}, ] + prefix = "${cfg.prefix}" + log-level = "${cfg.logLevel}" + watch = ${boolToString cfg.watch} + ''; + +in { + options.services.confd = { + enable = mkEnableOption "confd service"; + + backend = mkOption { + description = "Confd config storage backend to use."; + default = "etcd"; + type = types.enum ["etcd" "consul" "redis" "zookeeper"]; + }; + + interval = mkOption { + description = "Confd check interval."; + default = 10; + type = types.int; + }; + + nodes = mkOption { + description = "Confd list of nodes to connect to."; + default = [ "http://127.0.0.1:2379" ]; + type = types.listOf types.str; + }; + + watch = mkOption { + description = "Confd, whether to watch etcd config for changes."; + default = true; + type = types.bool; + }; + + prefix = mkOption { + description = "The string to prefix to keys."; + default = "/"; + type = types.path; + }; + + logLevel = mkOption { + description = "Confd log level."; + default = "info"; + type = types.enum ["info" "debug"]; + }; + + confDir = mkOption { + description = "The path to the confd configs."; + default = "/etc/confd"; + type = types.path; + }; + + package = mkOption { + description = "Confd package to use."; + default = pkgs.confd; + defaultText = "pkgs.confd"; + type = types.package; + }; + }; + + config = mkIf cfg.enable { + systemd.services.confd = { + description = "Confd Service."; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + ExecStart = "${cfg.package.bin}/bin/confd"; + }; + }; + + environment.etc = { + "confd/confd.toml".text = confdConfig; + }; + + environment.systemPackages = [ cfg.package ]; + + services.etcd.enable = mkIf (cfg.backend == "etcd") (mkDefault true); + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/couchpotato.nix b/nixpkgs/nixos/modules/services/misc/couchpotato.nix new file mode 100644 index 000000000000..70aa895f76d8 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/couchpotato.nix @@ -0,0 +1,50 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.couchpotato; + +in +{ + options = { + services.couchpotato = { + enable = mkEnableOption "CouchPotato Server"; + }; + }; + + config = mkIf cfg.enable { + systemd.services.couchpotato = { + description = "CouchPotato Server"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + preStart = '' + mkdir -p /var/lib/couchpotato + chown -R couchpotato:couchpotato /var/lib/couchpotato + ''; + + serviceConfig = { + Type = "simple"; + User = "couchpotato"; + Group = "couchpotato"; + PermissionsStartOnly = "true"; + ExecStart = "${pkgs.couchpotato}/bin/couchpotato"; + Restart = "on-failure"; + }; + }; + + users.users = singleton + { name = "couchpotato"; + group = "couchpotato"; + home = "/var/lib/couchpotato/"; + description = "CouchPotato daemon user"; + uid = config.ids.uids.couchpotato; + }; + + users.groups = singleton + { name = "couchpotato"; + gid = config.ids.gids.couchpotato; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/cpuminer-cryptonight.nix b/nixpkgs/nixos/modules/services/misc/cpuminer-cryptonight.nix new file mode 100644 index 000000000000..f31526f8d107 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/cpuminer-cryptonight.nix @@ -0,0 +1,66 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.cpuminer-cryptonight; + + json = builtins.toJSON ( + cfg // { + enable = null; + threads = + if cfg.threads == 0 then null else toString cfg.threads; + } + ); + + confFile = builtins.toFile "cpuminer.json" json; +in +{ + + options = { + + services.cpuminer-cryptonight = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable the cpuminer cryptonight miner. + ''; + }; + url = mkOption { + type = types.string; + description = "URL of mining server"; + }; + user = mkOption { + type = types.string; + description = "Username for mining server"; + }; + pass = mkOption { + type = types.string; + default = "x"; + description = "Password for mining server"; + }; + threads = mkOption { + type = types.int; + default = 0; + description = "Number of miner threads, defaults to available processors"; + }; + }; + + }; + + config = mkIf config.services.cpuminer-cryptonight.enable { + + systemd.services.cpuminer-cryptonight = { + description = "Cryptonight cpuminer"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + ExecStart = "${pkgs.cpuminer-multi}/bin/minerd --syslog --config=${confFile}"; + User = "nobody"; + }; + }; + + }; + +} \ No newline at end of file diff --git a/nixpkgs/nixos/modules/services/misc/defaultUnicornConfig.rb b/nixpkgs/nixos/modules/services/misc/defaultUnicornConfig.rb new file mode 100644 index 000000000000..0b58c59c7a51 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/defaultUnicornConfig.rb @@ -0,0 +1,69 @@ +worker_processes 3 + +listen ENV["UNICORN_PATH"] + "/tmp/sockets/gitlab.socket", :backlog => 1024 +listen "/run/gitlab/gitlab.socket", :backlog => 1024 + +working_directory ENV["GITLAB_PATH"] + +pid ENV["UNICORN_PATH"] + "/tmp/pids/unicorn.pid" + +timeout 60 + +# combine Ruby 2.0.0dev or REE with "preload_app true" for memory savings +# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow +preload_app true +GC.respond_to?(:copy_on_write_friendly=) and + GC.copy_on_write_friendly = true + +check_client_connection false + +before_fork do |server, worker| + # the following is highly recommended for Rails + "preload_app true" + # as there's no need for the master process to hold a connection + defined?(ActiveRecord::Base) and + ActiveRecord::Base.connection.disconnect! + + # The following is only recommended for memory/DB-constrained + # installations. It is not needed if your system can house + # twice as many worker_processes as you have configured. + # + # This allows a new master process to incrementally + # phase out the old master process with SIGTTOU to avoid a + # thundering herd (especially in the "preload_app false" case) + # when doing a transparent upgrade. The last worker spawned + # will then kill off the old master process with a SIGQUIT. + old_pid = "#{server.config[:pid]}.oldbin" + if old_pid != server.pid + begin + sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU + Process.kill(sig, File.read(old_pid).to_i) + rescue Errno::ENOENT, Errno::ESRCH + end + end + + # Throttle the master from forking too quickly by sleeping. Due + # to the implementation of standard Unix signal handlers, this + # helps (but does not completely) prevent identical, repeated signals + # from being lost when the receiving process is busy. + # sleep 1 +end + +after_fork do |server, worker| + # per-process listener ports for debugging/admin/migrations + # addr = "127.0.0.1:#{9293 + worker.nr}" + # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true) + + # the following is *required* for Rails + "preload_app true", + defined?(ActiveRecord::Base) and + ActiveRecord::Base.establish_connection + + # reset prometheus client, this will cause any opened metrics files to be closed + defined?(::Prometheus::Client.reinitialize_on_pid_change) && + Prometheus::Client.reinitialize_on_pid_change + + # if preload_app is true, then you may also want to check and + # restart any other shared sockets/descriptors such as Memcached, + # and Redis. TokyoCabinet file handles are safe to reuse + # between any number of forked children (assuming your kernel + # correctly implements pread()/pwrite() system calls) +end diff --git a/nixpkgs/nixos/modules/services/misc/devmon.nix b/nixpkgs/nixos/modules/services/misc/devmon.nix new file mode 100644 index 000000000000..9dc8fee2964b --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/devmon.nix @@ -0,0 +1,30 @@ +{ pkgs, config, lib, ... }: + +with lib; + +let + cfg = config.services.devmon; + +in { + options = { + services.devmon = { + enable = mkOption { + default = false; + description = '' + Whether to enable devmon, an automatic device mounting daemon. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.user.services.devmon = { + description = "devmon automatic device mounting daemon"; + wantedBy = [ "default.target" ]; + path = [ pkgs.udevil pkgs.procps pkgs.udisks2 pkgs.which ]; + serviceConfig.ExecStart = "${pkgs.udevil}/bin/devmon"; + }; + + services.udisks2.enable = true; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/dictd.nix b/nixpkgs/nixos/modules/services/misc/dictd.nix new file mode 100644 index 000000000000..d7b0997a1fc5 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/dictd.nix @@ -0,0 +1,73 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.dictd; +in + +{ + + ###### interface + + options = { + + services.dictd = { + + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable the DICT.org dictionary server. + ''; + }; + + DBs = mkOption { + type = types.listOf types.package; + default = with pkgs.dictdDBs; [ wiktionary wordnet ]; + defaultText = "with pkgs.dictdDBs; [ wiktionary wordnet ]"; + example = literalExample "[ pkgs.dictdDBs.nld2eng ]"; + description = ''List of databases to make available.''; + }; + + }; + + }; + + + ###### implementation + + config = let dictdb = pkgs.dictDBCollector { dictlist = map (x: { + name = x.name; + filename = x; } ) cfg.DBs; }; + in mkIf cfg.enable { + + # get the command line client on system path to make some use of the service + environment.systemPackages = [ pkgs.dict ]; + + environment.etc."dict.conf".text = '' + server localhost + ''; + + users.users = singleton + { name = "dictd"; + group = "dictd"; + description = "DICT.org dictd server"; + home = "${dictdb}/share/dictd"; + uid = config.ids.uids.dictd; + }; + + users.groups = singleton + { name = "dictd"; + gid = config.ids.gids.dictd; + }; + + systemd.services.dictd = { + description = "DICT.org Dictionary Server"; + wantedBy = [ "multi-user.target" ]; + environment = { LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive"; }; + serviceConfig.Type = "forking"; + script = "${pkgs.dict}/sbin/dictd -s -c ${dictdb}/share/dictd/dictd.conf --locale en_US.UTF-8"; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/disnix.nix b/nixpkgs/nixos/modules/services/misc/disnix.nix new file mode 100644 index 000000000000..c21cb2afc3ca --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/disnix.nix @@ -0,0 +1,97 @@ +# Disnix server +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.disnix; + +in + +{ + + ###### interface + + options = { + + services.disnix = { + + enable = mkOption { + default = false; + description = "Whether to enable Disnix"; + }; + + enableMultiUser = mkOption { + type = types.bool; + default = true; + description = "Whether to support multi-user mode by enabling the Disnix D-Bus service"; + }; + + useWebServiceInterface = mkOption { + default = false; + description = "Whether to enable the DisnixWebService interface running on Apache Tomcat"; + }; + + package = mkOption { + type = types.path; + description = "The Disnix package"; + default = pkgs.disnix; + defaultText = "pkgs.disnix"; + }; + + }; + + }; + + ###### implementation + + config = mkIf cfg.enable { + dysnomia.enable = true; + + environment.systemPackages = [ pkgs.disnix ] ++ optional cfg.useWebServiceInterface pkgs.DisnixWebService; + + services.dbus.enable = true; + services.dbus.packages = [ pkgs.disnix ]; + + services.tomcat.enable = cfg.useWebServiceInterface; + services.tomcat.extraGroups = [ "disnix" ]; + services.tomcat.javaOpts = "${optionalString cfg.useWebServiceInterface "-Djava.library.path=${pkgs.libmatthew_java}/lib/jni"} "; + services.tomcat.sharedLibs = optional cfg.useWebServiceInterface "${pkgs.DisnixWebService}/share/java/DisnixConnection.jar" + ++ optional cfg.useWebServiceInterface "${pkgs.dbus_java}/share/java/dbus.jar"; + services.tomcat.webapps = optional cfg.useWebServiceInterface pkgs.DisnixWebService; + + users.groups = singleton + { name = "disnix"; + gid = config.ids.gids.disnix; + }; + + systemd.services = { + disnix = mkIf cfg.enableMultiUser { + description = "Disnix server"; + wants = [ "dysnomia.target" ]; + wantedBy = [ "multi-user.target" ]; + after = [ "dbus.service" ] + ++ optional config.services.httpd.enable "httpd.service" + ++ optional config.services.mysql.enable "mysql.service" + ++ optional config.services.postgresql.enable "postgresql.service" + ++ optional config.services.tomcat.enable "tomcat.service" + ++ optional config.services.svnserve.enable "svnserve.service" + ++ optional config.services.mongodb.enable "mongodb.service"; + + restartIfChanged = false; + + path = [ config.nix.package cfg.package config.dysnomia.package "/run/current-system/sw" ]; + + environment = { + HOME = "/root"; + } + // (if config.environment.variables ? DYSNOMIA_CONTAINERS_PATH then { inherit (config.environment.variables) DYSNOMIA_CONTAINERS_PATH; } else {}) + // (if config.environment.variables ? DYSNOMIA_MODULES_PATH then { inherit (config.environment.variables) DYSNOMIA_MODULES_PATH; } else {}); + + serviceConfig.ExecStart = "${cfg.package}/bin/disnix-service"; + }; + + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/docker-registry.nix b/nixpkgs/nixos/modules/services/misc/docker-registry.nix new file mode 100644 index 000000000000..c87607d2666a --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/docker-registry.nix @@ -0,0 +1,155 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.dockerRegistry; + + blobCache = if cfg.enableRedisCache + then "redis" + else "inmemory"; + + registryConfig = { + version = "0.1"; + log.fields.service = "registry"; + storage = { + cache.blobdescriptor = blobCache; + delete.enabled = cfg.enableDelete; + } // (if cfg.storagePath != null + then { filesystem.rootdirectory = cfg.storagePath; } + else {}); + http = { + addr = "${cfg.listenAddress}:${builtins.toString cfg.port}"; + headers.X-Content-Type-Options = ["nosniff"]; + }; + health.storagedriver = { + enabled = true; + interval = "10s"; + threshold = 3; + }; + }; + + registryConfig.redis = mkIf cfg.enableRedisCache { + addr = "${cfg.redisUrl}"; + password = "${cfg.redisPassword}"; + db = 0; + dialtimeout = "10ms"; + readtimeout = "10ms"; + writetimeout = "10ms"; + pool = { + maxidle = 16; + maxactive = 64; + idletimeout = "300s"; + }; + }; + + configFile = pkgs.writeText "docker-registry-config.yml" (builtins.toJSON (recursiveUpdate registryConfig cfg.extraConfig)); + +in { + options.services.dockerRegistry = { + enable = mkEnableOption "Docker Registry"; + + listenAddress = mkOption { + description = "Docker registry host or ip to bind to."; + default = "127.0.0.1"; + type = types.str; + }; + + port = mkOption { + description = "Docker registry port to bind to."; + default = 5000; + type = types.int; + }; + + storagePath = mkOption { + type = types.nullOr types.path; + default = "/var/lib/docker-registry"; + description = '' + Docker registry storage path for the filesystem storage backend. Set to + null to configure another backend via extraConfig. + ''; + }; + + enableDelete = mkOption { + type = types.bool; + default = false; + description = "Enable delete for manifests and blobs."; + }; + + enableRedisCache = mkEnableOption "redis as blob cache"; + + redisUrl = mkOption { + type = types.str; + default = "localhost:6379"; + description = "Set redis host and port."; + }; + + redisPassword = mkOption { + type = types.str; + default = ""; + description = "Set redis password."; + }; + + extraConfig = mkOption { + description = '' + Docker extra registry configuration via environment variables. + ''; + default = {}; + type = types.attrs; + }; + + enableGarbageCollect = mkEnableOption "garbage collect"; + + garbageCollectDates = mkOption { + default = "daily"; + type = types.str; + description = '' + Specification (in the format described by + <citerefentry><refentrytitle>systemd.time</refentrytitle> + <manvolnum>7</manvolnum></citerefentry>) of the time at + which the garbage collect will occur. + ''; + }; + }; + + config = mkIf cfg.enable { + systemd.services.docker-registry = { + description = "Docker Container Registry"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + script = '' + ${pkgs.docker-distribution}/bin/registry serve ${configFile} + ''; + + serviceConfig = { + User = "docker-registry"; + WorkingDirectory = cfg.storagePath; + AmbientCapabilities = mkIf (cfg.port < 1024) "cap_net_bind_service"; + }; + }; + + systemd.services.docker-registry-garbage-collect = { + description = "Run Garbage Collection for docker registry"; + + restartIfChanged = false; + unitConfig.X-StopOnRemoval = false; + + serviceConfig.Type = "oneshot"; + + script = '' + ${pkgs.docker-distribution}/bin/registry garbage-collect ${configFile} + ${pkgs.systemd}/bin/systemctl restart docker-registry.service + ''; + + startAt = optional cfg.enableGarbageCollect cfg.garbageCollectDates; + }; + + users.users.docker-registry = + if cfg.storagePath != null + then { + createHome = true; + home = cfg.storagePath; + } + else {}; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/dysnomia.nix b/nixpkgs/nixos/modules/services/misc/dysnomia.nix new file mode 100644 index 000000000000..61ea822890ed --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/dysnomia.nix @@ -0,0 +1,216 @@ +{pkgs, lib, config, ...}: + +with lib; + +let + cfg = config.dysnomia; + + printProperties = properties: + concatMapStrings (propertyName: + let + property = properties."${propertyName}"; + in + if isList property then "${propertyName}=(${lib.concatMapStrings (elem: "\"${toString elem}\" ") (properties."${propertyName}")})\n" + else "${propertyName}=\"${toString property}\"\n" + ) (builtins.attrNames properties); + + properties = pkgs.stdenv.mkDerivation { + name = "dysnomia-properties"; + buildCommand = '' + cat > $out << "EOF" + ${printProperties cfg.properties} + EOF + ''; + }; + + containersDir = pkgs.stdenv.mkDerivation { + name = "dysnomia-containers"; + buildCommand = '' + mkdir -p $out + cd $out + + ${concatMapStrings (containerName: + let + containerProperties = cfg.containers."${containerName}"; + in + '' + cat > ${containerName} <<EOF + ${printProperties containerProperties} + type=${containerName} + EOF + '' + ) (builtins.attrNames cfg.containers)} + ''; + }; + + linkMutableComponents = {containerName}: + '' + mkdir ${containerName} + + ${concatMapStrings (componentName: + let + component = cfg.components."${containerName}"."${componentName}"; + in + "ln -s ${component} ${containerName}/${componentName}\n" + ) (builtins.attrNames (cfg.components."${containerName}" or {}))} + ''; + + componentsDir = pkgs.stdenv.mkDerivation { + name = "dysnomia-components"; + buildCommand = '' + mkdir -p $out + cd $out + + ${concatMapStrings (containerName: + linkMutableComponents { inherit containerName; } + ) (builtins.attrNames cfg.components)} + ''; + }; +in +{ + options = { + dysnomia = { + + enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable Dysnomia"; + }; + + enableAuthentication = mkOption { + type = types.bool; + default = false; + description = "Whether to publish privacy-sensitive authentication credentials"; + }; + + package = mkOption { + type = types.path; + description = "The Dysnomia package"; + }; + + properties = mkOption { + description = "An attribute set in which each attribute represents a machine property. Optionally, these values can be shell substitutions."; + default = {}; + }; + + containers = mkOption { + description = "An attribute set in which each key represents a container and each value an attribute set providing its configuration properties"; + default = {}; + }; + + components = mkOption { + description = "An atttribute set in which each key represents a container and each value an attribute set in which each key represents a component and each value a derivation constructing its initial state"; + default = {}; + }; + + extraContainerProperties = mkOption { + description = "An attribute set providing additional container settings in addition to the default properties"; + default = {}; + }; + + extraContainerPaths = mkOption { + description = "A list of paths containing additional container configurations that are added to the search folders"; + default = []; + }; + + extraModulePaths = mkOption { + description = "A list of paths containing additional modules that are added to the search folders"; + default = []; + }; + }; + }; + + config = mkIf cfg.enable { + + environment.etc = { + "dysnomia/containers" = { + source = containersDir; + }; + "dysnomia/components" = { + source = componentsDir; + }; + "dysnomia/properties" = { + source = properties; + }; + }; + + environment.variables = { + DYSNOMIA_STATEDIR = "/var/state/dysnomia-nixos"; + DYSNOMIA_CONTAINERS_PATH = "${lib.concatMapStrings (containerPath: "${containerPath}:") cfg.extraContainerPaths}/etc/dysnomia/containers"; + DYSNOMIA_MODULES_PATH = "${lib.concatMapStrings (modulePath: "${modulePath}:") cfg.extraModulePaths}/etc/dysnomia/modules"; + }; + + environment.systemPackages = [ cfg.package ]; + + dysnomia.package = pkgs.dysnomia.override (origArgs: { + enableApacheWebApplication = config.services.httpd.enable; + enableAxis2WebService = config.services.tomcat.axis2.enable; + enableEjabberdDump = config.services.ejabberd.enable; + enableMySQLDatabase = config.services.mysql.enable; + enablePostgreSQLDatabase = config.services.postgresql.enable; + enableSubversionRepository = config.services.svnserve.enable; + enableTomcatWebApplication = config.services.tomcat.enable; + enableMongoDatabase = config.services.mongodb.enable; + }); + + dysnomia.properties = { + hostname = config.networking.hostName; + inherit (config.nixpkgs.localSystem) system; + + supportedTypes = (import "${pkgs.stdenv.mkDerivation { + name = "supportedtypes"; + buildCommand = '' + ( echo -n "[ " + cd ${cfg.package}/libexec/dysnomia + for i in * + do + echo -n "\"$i\" " + done + echo -n " ]") > $out + ''; + }}"); + }; + + dysnomia.containers = lib.recursiveUpdate ({ + process = {}; + wrapper = {}; + } + // lib.optionalAttrs (config.services.httpd.enable) { apache-webapplication = { + documentRoot = config.services.httpd.documentRoot; + }; } + // lib.optionalAttrs (config.services.tomcat.axis2.enable) { axis2-webservice = {}; } + // lib.optionalAttrs (config.services.ejabberd.enable) { ejabberd-dump = { + ejabberdUser = config.services.ejabberd.user; + }; } + // lib.optionalAttrs (config.services.mysql.enable) { mysql-database = { + mysqlPort = config.services.mysql.port; + } // lib.optionalAttrs cfg.enableAuthentication { + mysqlUsername = "root"; + mysqlPassword = builtins.readFile (config.services.mysql.rootPassword); + }; + } + // lib.optionalAttrs (config.services.postgresql.enable) { postgresql-database = { + } // lib.optionalAttrs (cfg.enableAuthentication) { + postgresqlUsername = "postgres"; + }; + } + // lib.optionalAttrs (config.services.tomcat.enable) { tomcat-webapplication = { + tomcatPort = 8080; + }; } + // lib.optionalAttrs (config.services.mongodb.enable) { mongo-database = {}; } + // lib.optionalAttrs (config.services.svnserve.enable) { subversion-repository = { + svnBaseDir = config.services.svnserve.svnBaseDir; + }; }) cfg.extraContainerProperties; + + system.activationScripts.dysnomia = '' + mkdir -p /etc/systemd-mutable/system + if [ ! -f /etc/systemd-mutable/system/dysnomia.target ] + then + ( echo "[Unit]" + echo "Description=Services that are activated and deactivated by Dysnomia" + echo "After=final.target" + ) > /etc/systemd-mutable/system/dysnomia.target + fi + ''; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/emby.nix b/nixpkgs/nixos/modules/services/misc/emby.nix new file mode 100644 index 000000000000..0ad4a3f7376f --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/emby.nix @@ -0,0 +1,76 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.emby; +in +{ + options = { + services.emby = { + enable = mkEnableOption "Emby Media Server"; + + user = mkOption { + type = types.str; + default = "emby"; + description = "User account under which Emby runs."; + }; + + group = mkOption { + type = types.str; + default = "emby"; + description = "Group under which emby runs."; + }; + + dataDir = mkOption { + type = types.path; + default = "/var/lib/emby/ProgramData-Server"; + description = "Location where Emby stores its data."; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.emby = { + description = "Emby Media Server"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + preStart = '' + if [ -d ${cfg.dataDir} ] + then + for plugin in ${cfg.dataDir}/plugins/* + do + echo "Correcting permissions of plugin: $plugin" + chmod u+w $plugin + done + else + echo "Creating initial Emby data directory in ${cfg.dataDir}" + mkdir -p ${cfg.dataDir} + chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir} + fi + ''; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + PermissionsStartOnly = "true"; + ExecStart = "${pkgs.emby}/bin/emby -programdata ${cfg.dataDir}"; + Restart = "on-failure"; + }; + }; + + users.users = mkIf (cfg.user == "emby") { + emby = { + group = cfg.group; + uid = config.ids.uids.emby; + }; + }; + + users.groups = mkIf (cfg.group == "emby") { + emby = { + gid = config.ids.gids.emby; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/errbot.nix b/nixpkgs/nixos/modules/services/misc/errbot.nix new file mode 100644 index 000000000000..ac6ba2181de2 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/errbot.nix @@ -0,0 +1,101 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.errbot; + pluginEnv = plugins: pkgs.buildEnv { + name = "errbot-plugins"; + paths = plugins; + }; + mkConfigDir = instanceCfg: dataDir: pkgs.writeTextDir "config.py" '' + import logging + BACKEND = '${instanceCfg.backend}' + BOT_DATA_DIR = '${dataDir}' + BOT_EXTRA_PLUGIN_DIR = '${pluginEnv instanceCfg.plugins}' + + BOT_LOG_LEVEL = logging.${instanceCfg.logLevel} + BOT_LOG_FILE = False + + BOT_ADMINS = (${concatMapStringsSep "," (name: "'${name}'") instanceCfg.admins}) + + BOT_IDENTITY = ${builtins.toJSON instanceCfg.identity} + + ${instanceCfg.extraConfig} + ''; +in { + options = { + services.errbot.instances = mkOption { + default = {}; + description = "Errbot instance configs"; + type = types.attrsOf (types.submodule { + options = { + dataDir = mkOption { + type = types.nullOr types.path; + default = null; + description = "Data directory for errbot instance."; + }; + + plugins = mkOption { + type = types.listOf types.package; + default = []; + description = "List of errbot plugin derivations."; + }; + + logLevel = mkOption { + type = types.str; + default = "INFO"; + description = "Errbot log level"; + }; + + admins = mkOption { + type = types.listOf types.str; + default = []; + description = "List of identifiers of errbot admins."; + }; + + backend = mkOption { + type = types.str; + default = "XMPP"; + description = "Errbot backend name."; + }; + + identity = mkOption { + type = types.attrs; + description = "Errbot identity configuration"; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = "String to be appended to the config verbatim"; + }; + }; + }); + }; + }; + + config = mkIf (cfg.instances != {}) { + users.users.errbot.group = "errbot"; + users.groups.errbot = {}; + + systemd.services = mapAttrs' (name: instanceCfg: nameValuePair "errbot-${name}" ( + let + dataDir = if !isNull instanceCfg.dataDir then instanceCfg.dataDir else + "/var/lib/errbot/${name}"; + in { + after = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + preStart = '' + mkdir -p ${dataDir} + chown -R errbot:errbot ${dataDir} + ''; + serviceConfig = { + User = "errbot"; + Restart = "on-failure"; + ExecStart = "${pkgs.errbot}/bin/errbot -c ${mkConfigDir instanceCfg dataDir}/config.py"; + PermissionsStartOnly = true; + }; + })) cfg.instances; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/etcd.nix b/nixpkgs/nixos/modules/services/misc/etcd.nix new file mode 100644 index 000000000000..e4d5322f9b5f --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/etcd.nix @@ -0,0 +1,196 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.etcd; + +in { + + options.services.etcd = { + enable = mkOption { + description = "Whether to enable etcd."; + default = false; + type = types.bool; + }; + + name = mkOption { + description = "Etcd unique node name."; + default = config.networking.hostName; + type = types.str; + }; + + advertiseClientUrls = mkOption { + description = "Etcd list of this member's client URLs to advertise to the rest of the cluster."; + default = cfg.listenClientUrls; + type = types.listOf types.str; + }; + + listenClientUrls = mkOption { + description = "Etcd list of URLs to listen on for client traffic."; + default = ["http://127.0.0.1:2379"]; + type = types.listOf types.str; + }; + + listenPeerUrls = mkOption { + description = "Etcd list of URLs to listen on for peer traffic."; + default = ["http://127.0.0.1:2380"]; + type = types.listOf types.str; + }; + + initialAdvertisePeerUrls = mkOption { + description = "Etcd list of this member's peer URLs to advertise to rest of the cluster."; + default = cfg.listenPeerUrls; + type = types.listOf types.str; + }; + + initialCluster = mkOption { + description = "Etcd initial cluster configuration for bootstrapping."; + default = ["${cfg.name}=http://127.0.0.1:2380"]; + type = types.listOf types.str; + }; + + initialClusterState = mkOption { + description = "Etcd initial cluster configuration for bootstrapping."; + default = "new"; + type = types.enum ["new" "existing"]; + }; + + initialClusterToken = mkOption { + description = "Etcd initial cluster token for etcd cluster during bootstrap."; + default = "etcd-cluster"; + type = types.str; + }; + + discovery = mkOption { + description = "Etcd discovery url"; + default = ""; + type = types.str; + }; + + clientCertAuth = mkOption { + description = "Whether to use certs for client authentication"; + default = false; + type = types.bool; + }; + + trustedCaFile = mkOption { + description = "Certificate authority file to use for clients"; + default = null; + type = types.nullOr types.path; + }; + + certFile = mkOption { + description = "Cert file to use for clients"; + default = null; + type = types.nullOr types.path; + }; + + keyFile = mkOption { + description = "Key file to use for clients"; + default = null; + type = types.nullOr types.path; + }; + + peerCertFile = mkOption { + description = "Cert file to use for peer to peer communication"; + default = cfg.certFile; + type = types.nullOr types.path; + }; + + peerKeyFile = mkOption { + description = "Key file to use for peer to peer communication"; + default = cfg.keyFile; + type = types.nullOr types.path; + }; + + peerTrustedCaFile = mkOption { + description = "Certificate authority file to use for peer to peer communication"; + default = cfg.trustedCaFile; + type = types.nullOr types.path; + }; + + peerClientCertAuth = mkOption { + description = "Whether to check all incoming peer requests from the cluster for valid client certificates signed by the supplied CA"; + default = false; + type = types.bool; + }; + + extraConf = mkOption { + description = '' + Etcd extra configuration. See + <link xlink:href='https://github.com/coreos/etcd/blob/master/Documentation/op-guide/configuration.md#configuration-flags' /> + ''; + type = types.attrsOf types.str; + default = {}; + example = literalExample '' + { + "CORS" = "*"; + "NAME" = "default-name"; + "MAX_RESULT_BUFFER" = "1024"; + "MAX_CLUSTER_SIZE" = "9"; + "MAX_RETRY_ATTEMPTS" = "3"; + } + ''; + }; + + dataDir = mkOption { + type = types.path; + default = "/var/lib/etcd"; + description = "Etcd data directory."; + }; + }; + + config = mkIf cfg.enable { + systemd.tmpfiles.rules = [ + "d '${cfg.dataDir}' 0700 etcd - - -" + ]; + + systemd.services.etcd = { + description = "etcd key-value store"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + environment = (filterAttrs (n: v: v != null) { + ETCD_NAME = cfg.name; + ETCD_DISCOVERY = cfg.discovery; + ETCD_DATA_DIR = cfg.dataDir; + ETCD_ADVERTISE_CLIENT_URLS = concatStringsSep "," cfg.advertiseClientUrls; + ETCD_LISTEN_CLIENT_URLS = concatStringsSep "," cfg.listenClientUrls; + ETCD_LISTEN_PEER_URLS = concatStringsSep "," cfg.listenPeerUrls; + ETCD_INITIAL_ADVERTISE_PEER_URLS = concatStringsSep "," cfg.initialAdvertisePeerUrls; + ETCD_PEER_TRUSTED_CA_FILE = cfg.peerTrustedCaFile; + ETCD_PEER_CERT_FILE = cfg.peerCertFile; + ETCD_PEER_KEY_FILE = cfg.peerKeyFile; + ETCD_CLIENT_CERT_AUTH = toString cfg.peerClientCertAuth; + ETCD_TRUSTED_CA_FILE = cfg.trustedCaFile; + ETCD_CERT_FILE = cfg.certFile; + ETCD_KEY_FILE = cfg.keyFile; + }) // (optionalAttrs (cfg.discovery == ""){ + ETCD_INITIAL_CLUSTER = concatStringsSep "," cfg.initialCluster; + ETCD_INITIAL_CLUSTER_STATE = cfg.initialClusterState; + ETCD_INITIAL_CLUSTER_TOKEN = cfg.initialClusterToken; + }) // (mapAttrs' (n: v: nameValuePair "ETCD_${n}" v) cfg.extraConf); + + unitConfig = { + Documentation = "https://github.com/coreos/etcd"; + }; + + serviceConfig = { + Type = "notify"; + ExecStart = "${pkgs.etcd.bin}/bin/etcd"; + User = "etcd"; + LimitNOFILE = 40000; + }; + }; + + environment.systemPackages = [ pkgs.etcdctl ]; + + users.users = singleton { + name = "etcd"; + uid = config.ids.uids.etcd; + description = "Etcd daemon user"; + home = cfg.dataDir; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/exhibitor.nix b/nixpkgs/nixos/modules/services/misc/exhibitor.nix new file mode 100644 index 000000000000..665084a8ae05 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/exhibitor.nix @@ -0,0 +1,420 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.exhibitor; + 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} + ''; + # NB: toString rather than lib.boolToString on cfg.autoManageInstances is intended. + # Exhibitor tests if it's an integer not equal to 0, so the empty string (toString false) + # will operate in the same fashion as a 0. + configDir = pkgs.writeTextDir "exhibitor.properties" exhibitorConfig; + cliOptionsCommon = { + configtype = cfg.configType; + defaultconfig = "${configDir}/exhibitor.properties"; + port = toString cfg.port; + hostname = cfg.hostname; + headingtext = if (cfg.headingText != null) then (lib.escapeShellArg cfg.headingText) else null; + nodemodification = lib.boolToString cfg.nodeModification; + configcheckms = toString cfg.configCheckMs; + jquerystyle = cfg.jqueryStyle; + loglines = toString cfg.logLines; + servo = lib.boolToString cfg.servo; + timeout = toString cfg.timeout; + }; + 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; + }; + nodeModification = mkOption { + type = types.bool; + description = '' + Whether the Explorer UI will allow nodes to be modified (use with caution). + ''; + default = true; + }; + configCheckMs = mkOption { + type = types.int; + description = '' + Period (ms) to check for shared config updates. + ''; + default = 30000; + }; + headingText = mkOption { + type = types.nullOr types.str; + description = '' + Extra text to display in UI header + ''; + default = null; + }; + jqueryStyle = mkOption { + type = types.enum [ "red" "black" "custom" ]; + description = '' + Styling used for the JQuery-based UI. + ''; + default = "red"; + }; + logLines = mkOption { + type = types.int; + description = '' + Max lines of logging to keep in memory for display. + ''; + default = 1000; + }; + servo = mkOption { + type = types.bool; + description = '' + ZooKeeper will be queried once a minute for its state via the 'mntr' four + letter word (this requires ZooKeeper 3.4.x+). Servo will be used to publish + this data via JMX. + ''; + default = false; + }; + timeout = mkOption { + type = types.int; + description = '' + Connection timeout (ms) for ZK connections. + ''; + default = 30000; + }; + 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 = { + sleepMs = mkOption { + type = types.int; + default = 1000; + description = '' + Retry sleep time connecting to the ZooKeeper config + ''; + }; + retryQuantity = mkOption { + type = types.int; + default = 3; + description = '' + Retries connecting to the ZooKeeper config + ''; + }; + }; + zkConfigZPath = mkOption { + type = types.str; + description = '' + The base ZPath that Exhibitor should use + ''; + example = "/exhibitor/config"; + }; + + # Config options for s3 configType + s3Config = { + 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 + replace_what=$(echo ${pkgs.zookeeper} | sed 's/[\/&]/\\&/g') + replace_with=$(echo ${cfg.baseDir}/zookeeper | sed 's/[\/&]/\\&/g') + sed -i 's/'"$replace_what"'/'"$replace_with"'/g' ${cfg.baseDir}/zookeeper/bin/zk*.sh + ''; + }; + users.users = singleton { + name = "zookeeper"; + uid = config.ids.uids.zookeeper; + description = "Zookeeper daemon user"; + home = cfg.baseDir; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/felix.nix b/nixpkgs/nixos/modules/services/misc/felix.nix new file mode 100644 index 000000000000..1c5ece868258 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/felix.nix @@ -0,0 +1,109 @@ +# Felix server +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.felix; + +in + +{ + + ###### interface + + options = { + + services.felix = { + + enable = mkOption { + default = false; + description = "Whether to enable the Apache Felix OSGi service"; + }; + + bundles = mkOption { + type = types.listOf types.package; + default = [ pkgs.felix_remoteshell ]; + defaultText = "[ pkgs.felix_remoteshell ]"; + description = "List of bundles that should be activated on startup"; + }; + + user = mkOption { + default = "osgi"; + description = "User account under which Apache Felix runs."; + }; + + group = mkOption { + default = "osgi"; + description = "Group account under which Apache Felix runs."; + }; + + }; + + }; + + + ###### implementation + + config = mkIf cfg.enable { + users.groups = singleton + { name = "osgi"; + gid = config.ids.gids.osgi; + }; + + users.users = singleton + { name = "osgi"; + uid = config.ids.uids.osgi; + description = "OSGi user"; + home = "/homeless-shelter"; + }; + + systemd.services.felix = { + description = "Felix server"; + wantedBy = [ "multi-user.target" ]; + + preStart = '' + # Initialise felix instance on first startup + if [ ! -d /var/felix ] + then + # Symlink system files + + mkdir -p /var/felix + chown ${cfg.user}:${cfg.group} /var/felix + + for i in ${pkgs.felix}/* + do + if [ "$i" != "${pkgs.felix}/bundle" ] + then + ln -sfn $i /var/felix/$(basename $i) + fi + done + + # Symlink bundles + mkdir -p /var/felix/bundle + chown ${cfg.user}:${cfg.group} /var/felix/bundle + + for i in ${pkgs.felix}/bundle/* ${toString cfg.bundles} + do + if [ -f $i ] + then + ln -sfn $i /var/felix/bundle/$(basename $i) + elif [ -d $i ] + then + for j in $i/bundle/* + do + ln -sfn $j /var/felix/bundle/$(basename $j) + done + fi + done + fi + ''; + + script = '' + cd /var/felix + ${pkgs.su}/bin/su -s ${pkgs.bash}/bin/sh ${cfg.user} -c '${pkgs.jre}/bin/java -jar bin/felix.jar' + ''; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/folding-at-home.nix b/nixpkgs/nixos/modules/services/misc/folding-at-home.nix new file mode 100644 index 000000000000..122c89ce0680 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/folding-at-home.nix @@ -0,0 +1,68 @@ +{ config, lib, pkgs, ... }: +with lib; +let + stateDir = "/var/lib/foldingathome"; + cfg = config.services.foldingAtHome; + fahUser = "foldingathome"; +in { + + ###### interface + + options = { + + services.foldingAtHome = { + + enable = mkOption { + default = false; + description = '' + Whether to enable the Folding@Home to use idle CPU time. + ''; + }; + + nickname = mkOption { + default = "Anonymous"; + description = '' + A unique handle for statistics. + ''; + }; + + config = mkOption { + default = ""; + description = '' + Extra configuration. Contents will be added verbatim to the + configuration file. + ''; + }; + + }; + + }; + + ###### implementation + + config = mkIf cfg.enable { + + users.users = singleton + { name = fahUser; + uid = config.ids.uids.foldingathome; + description = "Folding@Home user"; + home = stateDir; + }; + + systemd.services.foldingathome = { + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + preStart = '' + mkdir -m 0755 -p ${stateDir} + chown ${fahUser} ${stateDir} + cp -f ${pkgs.writeText "client.cfg" cfg.config} ${stateDir}/client.cfg + ''; + script = "${pkgs.su}/bin/su -s ${pkgs.runtimeShell} ${fahUser} -c 'cd ${stateDir}; ${pkgs.foldingathome}/bin/fah6'"; + }; + + services.foldingAtHome.config = '' + [settings] + username=${cfg.nickname} + ''; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/fstrim.nix b/nixpkgs/nixos/modules/services/misc/fstrim.nix new file mode 100644 index 000000000000..15f283f093c0 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/fstrim.nix @@ -0,0 +1,46 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.fstrim; + +in { + + options = { + + services.fstrim = { + enable = mkEnableOption "periodic SSD TRIM of mounted partitions in background"; + + interval = mkOption { + type = types.string; + default = "weekly"; + description = '' + How often we run fstrim. For most desktop and server systems + a sufficient trimming frequency is once a week. + + The format is described in + <citerefentry><refentrytitle>systemd.time</refentrytitle> + <manvolnum>7</manvolnum></citerefentry>. + ''; + }; + }; + + }; + + config = mkIf cfg.enable { + + systemd.packages = [ pkgs.utillinux ]; + + systemd.timers.fstrim = { + timerConfig = { + OnCalendar = cfg.interval; + }; + wantedBy = [ "timers.target" ]; + }; + + }; + + meta.maintainers = with maintainers; [ gnidorah ]; +} diff --git a/nixpkgs/nixos/modules/services/misc/gammu-smsd.nix b/nixpkgs/nixos/modules/services/misc/gammu-smsd.nix new file mode 100644 index 000000000000..3057d7fd1a09 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/gammu-smsd.nix @@ -0,0 +1,253 @@ +{ pkgs, lib, config, ... }: + +with lib; +let + cfg = config.services.gammu-smsd; + + configFile = pkgs.writeText "gammu-smsd.conf" '' + [gammu] + Device = ${cfg.device.path} + Connection = ${cfg.device.connection} + SynchronizeTime = ${if cfg.device.synchronizeTime then "yes" else "no"} + LogFormat = ${cfg.log.format} + ${if (cfg.device.pin != null) then "PIN = ${cfg.device.pin}" else ""} + ${cfg.extraConfig.gammu} + + + [smsd] + LogFile = ${cfg.log.file} + Service = ${cfg.backend.service} + + ${optionalString (cfg.backend.service == "files") '' + InboxPath = ${cfg.backend.files.inboxPath} + OutboxPath = ${cfg.backend.files.outboxPath} + SentSMSPath = ${cfg.backend.files.sentSMSPath} + ErrorSMSPath = ${cfg.backend.files.errorSMSPath} + ''} + + ${optionalString (cfg.backend.service == "sql" && cfg.backend.sql.driver == "sqlite") '' + Driver = ${cfg.backend.sql.driver} + DBDir = ${cfg.backend.sql.database} + ''} + + ${optionalString (cfg.backend.service == "sql" && cfg.backend.sql.driver == "native_pgsql") ( + with cfg.backend; '' + Driver = ${sql.driver} + ${if (sql.database!= null) then "Database = ${sql.database}" else ""} + ${if (sql.host != null) then "Host = ${sql.host}" else ""} + ${if (sql.user != null) then "User = ${sql.user}" else ""} + ${if (sql.password != null) then "Password = ${sql.password}" else ""} + '')} + + ${cfg.extraConfig.smsd} + ''; + + initDBDir = "share/doc/gammu/examples/sql"; + + gammuPackage = with cfg.backend; (pkgs.gammu.override { + dbiSupport = (service == "sql" && sql.driver == "sqlite"); + postgresSupport = (service == "sql" && sql.driver == "native_pgsql"); + }); + +in { + options = { + services.gammu-smsd = { + + enable = mkEnableOption "gammu-smsd daemon"; + + user = mkOption { + type = types.str; + default = "smsd"; + description = "User that has access to the device"; + }; + + device = { + path = mkOption { + type = types.path; + description = "Device node or address of the phone"; + example = "/dev/ttyUSB2"; + }; + + group = mkOption { + type = types.str; + default = "root"; + description = "Owner group of the device"; + example = "dialout"; + }; + + connection = mkOption { + type = types.str; + default = "at"; + description = "Protocol which will be used to talk to the phone"; + }; + + synchronizeTime = mkOption { + type = types.bool; + default = true; + description = "Whether to set time from computer to the phone during starting connection"; + }; + + pin = mkOption { + type = types.nullOr types.str; + default = null; + description = "PIN code for the simcard"; + }; + }; + + + log = { + file = mkOption { + type = types.str; + default = "syslog"; + description = "Path to file where information about communication will be stored"; + }; + + format = mkOption { + type = types.enum [ "nothing" "text" "textall" "textalldate" "errors" "errorsdate" "binary" ]; + default = "errors"; + description = "Determines what will be logged to the LogFile"; + }; + }; + + + extraConfig = { + gammu = mkOption { + type = types.lines; + default = ""; + description = "Extra config lines to be added into [gammu] section"; + }; + + + smsd = mkOption { + type = types.lines; + default = ""; + description = "Extra config lines to be added into [smsd] section"; + }; + }; + + + backend = { + service = mkOption { + type = types.enum [ "null" "files" "sql" ]; + default = "null"; + description = "Service to use to store sms data."; + }; + + files = { + inboxPath = mkOption { + type = types.path; + default = "/var/spool/sms/inbox/"; + description = "Where the received SMSes are stored"; + }; + + outboxPath = mkOption { + type = types.path; + default = "/var/spool/sms/outbox/"; + description = "Where SMSes to be sent should be placed"; + }; + + sentSMSPath = mkOption { + type = types.path; + default = "/var/spool/sms/sent/"; + description = "Where the transmitted SMSes are placed"; + }; + + errorSMSPath = mkOption { + type = types.path; + default = "/var/spool/sms/error/"; + description = "Where SMSes with error in transmission is placed"; + }; + }; + + sql = { + driver = mkOption { + type = types.enum [ "native_mysql" "native_pgsql" "odbc" "dbi" ]; + description = "DB driver to use"; + }; + + sqlDialect = mkOption { + type = types.nullOr types.str; + default = null; + description = "SQL dialect to use (odbc driver only)"; + }; + + database = mkOption { + type = types.str; + default = null; + description = "Database name to store sms data"; + }; + + host = mkOption { + type = types.str; + default = "localhost"; + description = "Database server address"; + }; + + user = mkOption { + type = types.nullOr types.str; + default = null; + description = "User name used for connection to the database"; + }; + + password = mkOption { + type = types.nullOr types.str; + default = null; + description = "User password used for connetion to the database"; + }; + }; + }; + }; + }; + + config = mkIf cfg.enable { + users.users.${cfg.user} = { + description = "gammu-smsd user"; + uid = config.ids.uids.gammu-smsd; + extraGroups = [ "${cfg.device.group}" ]; + }; + + environment.systemPackages = with cfg.backend; [ gammuPackage ] + ++ optionals (service == "sql" && sql.driver == "sqlite") [ pkgs.sqlite ]; + + systemd.services.gammu-smsd = { + description = "gammu-smsd daemon"; + + wantedBy = [ "multi-user.target" ]; + + wants = with cfg.backend; [ ] + ++ optionals (service == "sql" && sql.driver == "native_pgsql") [ "postgresql.service" ]; + + preStart = with cfg.backend; + + optionalString (service == "files") (with files; '' + mkdir -m 755 -p ${inboxPath} ${outboxPath} ${sentSMSPath} ${errorSMSPath} + chown ${cfg.user} -R ${inboxPath} + chown ${cfg.user} -R ${outboxPath} + chown ${cfg.user} -R ${sentSMSPath} + chown ${cfg.user} -R ${errorSMSPath} + '') + + optionalString (service == "sql" && sql.driver == "sqlite") '' + cat "${gammuPackage}/${initDBDir}/sqlite.sql" \ + | ${pkgs.sqlite.bin}/bin/sqlite3 ${sql.database} + '' + + (let execPsql = extraArgs: concatStringsSep " " [ + (optionalString (sql.password != null) "PGPASSWORD=${sql.password}") + "${config.services.postgresql.package}/bin/psql" + (optionalString (sql.host != null) "-h ${sql.host}") + (optionalString (sql.user != null) "-U ${sql.user}") + "$extraArgs" + "${sql.database}" + ]; in optionalString (service == "sql" && sql.driver == "native_pgsql") '' + echo '\i '"${gammuPackage}/${initDBDir}/pgsql.sql" | ${execPsql ""} + ''); + + serviceConfig = { + User = "${cfg.user}"; + Group = "${cfg.device.group}"; + PermissionsStartOnly = true; + ExecStart = "${gammuPackage}/bin/gammu-smsd -c ${configFile}"; + }; + + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/geoip-updater.nix b/nixpkgs/nixos/modules/services/misc/geoip-updater.nix new file mode 100644 index 000000000000..baf0a8d73d19 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/geoip-updater.nix @@ -0,0 +1,306 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.geoip-updater; + + dbBaseUrl = "https://geolite.maxmind.com/download/geoip/database"; + + randomizedTimerDelaySec = "3600"; + + # Use writeScriptBin instead of writeScript, so that argv[0] (logged to the + # journal) doesn't include the long nix store path hash. (Prefixing the + # ExecStart= command with '@' doesn't work because we start a shell (new + # process) that creates a new argv[0].) + geoip-updater = pkgs.writeScriptBin "geoip-updater" '' + #!${pkgs.runtimeShell} + skipExisting=0 + debug() + { + echo "<7>$@" + } + info() + { + echo "<6>$@" + } + error() + { + echo "<3>$@" + } + die() + { + error "$@" + exit 1 + } + waitNetworkOnline() + { + ret=1 + for i in $(seq 6); do + curl_out=$("${pkgs.curl.bin}/bin/curl" \ + --silent --fail --show-error --max-time 60 "${dbBaseUrl}" 2>&1) + if [ $? -eq 0 ]; then + debug "Server is reachable (try $i)" + ret=0 + break + else + debug "Server is unreachable (try $i): $curl_out" + sleep 10 + fi + done + return $ret + } + dbFnameTmp() + { + dburl=$1 + echo "${cfg.databaseDir}/.$(basename "$dburl")" + } + dbFnameTmpDecompressed() + { + dburl=$1 + echo "${cfg.databaseDir}/.$(basename "$dburl")" | sed 's/\.\(gz\|xz\)$//' + } + dbFname() + { + dburl=$1 + echo "${cfg.databaseDir}/$(basename "$dburl")" | sed 's/\.\(gz\|xz\)$//' + } + downloadDb() + { + dburl=$1 + curl_out=$("${pkgs.curl.bin}/bin/curl" \ + --silent --fail --show-error --max-time 900 -L -o "$(dbFnameTmp "$dburl")" "$dburl" 2>&1) + if [ $? -ne 0 ]; then + error "Failed to download $dburl: $curl_out" + return 1 + fi + } + decompressDb() + { + fn=$(dbFnameTmp "$1") + ret=0 + case "$fn" in + *.gz) + cmd_out=$("${pkgs.gzip}/bin/gzip" --decompress --force "$fn" 2>&1) + ;; + *.xz) + cmd_out=$("${pkgs.xz.bin}/bin/xz" --decompress --force "$fn" 2>&1) + ;; + *) + cmd_out=$(echo "File \"$fn\" is neither a .gz nor .xz file") + false + ;; + esac + if [ $? -ne 0 ]; then + error "$cmd_out" + ret=1 + fi + } + atomicRename() + { + dburl=$1 + mv "$(dbFnameTmpDecompressed "$dburl")" "$(dbFname "$dburl")" + } + removeIfNotInConfig() + { + # Arg 1 is the full path of an installed DB. + # If the corresponding database is not specified in the NixOS config we + # remove it. + db=$1 + for cdb in ${lib.concatStringsSep " " cfg.databases}; do + confDb=$(echo "$cdb" | sed 's/\.\(gz\|xz\)$//') + if [ "$(basename "$db")" = "$(basename "$confDb")" ]; then + return 0 + fi + done + rm "$db" + if [ $? -eq 0 ]; then + debug "Removed $(basename "$db") (not listed in services.geoip-updater.databases)" + else + error "Failed to remove $db" + fi + } + removeUnspecifiedDbs() + { + for f in "${cfg.databaseDir}/"*; do + test -f "$f" || continue + case "$f" in + *.dat|*.mmdb|*.csv) + removeIfNotInConfig "$f" + ;; + *) + debug "Not removing \"$f\" (unknown file extension)" + ;; + esac + done + } + downloadAndInstall() + { + dburl=$1 + if [ "$skipExisting" -eq 1 -a -f "$(dbFname "$dburl")" ]; then + debug "Skipping existing file: $(dbFname "$dburl")" + return 0 + fi + downloadDb "$dburl" || return 1 + decompressDb "$dburl" || return 1 + atomicRename "$dburl" || return 1 + info "Updated $(basename "$(dbFname "$dburl")")" + } + for arg in "$@"; do + case "$arg" in + --skip-existing) + skipExisting=1 + info "Option --skip-existing is set: not updating existing databases" + ;; + *) + error "Unknown argument: $arg";; + esac + done + waitNetworkOnline || die "Network is down (${dbBaseUrl} is unreachable)" + test -d "${cfg.databaseDir}" || die "Database directory (${cfg.databaseDir}) doesn't exist" + debug "Starting update of GeoIP databases in ${cfg.databaseDir}" + all_ret=0 + for db in ${lib.concatStringsSep " \\\n " cfg.databases}; do + downloadAndInstall "${dbBaseUrl}/$db" || all_ret=1 + done + removeUnspecifiedDbs || all_ret=1 + if [ $all_ret -eq 0 ]; then + info "Completed GeoIP database update in ${cfg.databaseDir}" + else + error "Completed GeoIP database update in ${cfg.databaseDir}, with error(s)" + fi + # Hack to work around systemd journal race: + # https://github.com/systemd/systemd/issues/2913 + sleep 2 + exit $all_ret + ''; + +in + +{ + options = { + services.geoip-updater = { + enable = mkOption { + default = false; + type = types.bool; + description = '' + Whether to enable periodic downloading of GeoIP databases from + maxmind.com. You might want to enable this if you, for instance, use + ntopng or Wireshark. + ''; + }; + + interval = mkOption { + type = types.str; + default = "weekly"; + description = '' + Update the GeoIP databases at this time / interval. + The format is described in + <citerefentry><refentrytitle>systemd.time</refentrytitle> + <manvolnum>7</manvolnum></citerefentry>. + To prevent load spikes on maxmind.com, the timer interval is + randomized by an additional delay of ${randomizedTimerDelaySec} + seconds. Setting a shorter interval than this is not recommended. + ''; + }; + + databaseDir = mkOption { + type = types.path; + default = "/var/lib/geoip-databases"; + description = '' + Directory that will contain GeoIP databases. + ''; + }; + + databases = mkOption { + type = types.listOf types.str; + default = [ + "GeoLiteCountry/GeoIP.dat.gz" + "GeoIPv6.dat.gz" + "GeoLiteCity.dat.xz" + "GeoLiteCityv6-beta/GeoLiteCityv6.dat.gz" + "asnum/GeoIPASNum.dat.gz" + "asnum/GeoIPASNumv6.dat.gz" + "GeoLite2-Country.mmdb.gz" + "GeoLite2-City.mmdb.gz" + ]; + description = '' + Which GeoIP databases to update. The full URL is ${dbBaseUrl}/ + + <literal>the_database</literal>. + ''; + }; + + }; + + }; + + config = mkIf cfg.enable { + + assertions = [ + { assertion = (builtins.filter + (x: builtins.match ".*\\.(gz|xz)$" x == null) cfg.databases) == []; + message = '' + services.geoip-updater.databases supports only .gz and .xz databases. + + Current value: + ${toString cfg.databases} + + Offending element(s): + ${toString (builtins.filter (x: builtins.match ".*\\.(gz|xz)$" x == null) cfg.databases)}; + ''; + } + ]; + + users.users.geoip = { + group = "root"; + description = "GeoIP database updater"; + uid = config.ids.uids.geoip; + }; + + systemd.timers.geoip-updater = + { description = "GeoIP Updater Timer"; + partOf = [ "geoip-updater.service" ]; + wantedBy = [ "timers.target" ]; + timerConfig.OnCalendar = cfg.interval; + timerConfig.Persistent = "true"; + timerConfig.RandomizedDelaySec = randomizedTimerDelaySec; + }; + + systemd.services.geoip-updater = { + description = "GeoIP Updater"; + after = [ "network-online.target" "nss-lookup.target" ]; + wants = [ "network-online.target" ]; + preStart = '' + mkdir -p "${cfg.databaseDir}" + chmod 755 "${cfg.databaseDir}" + chown geoip:root "${cfg.databaseDir}" + ''; + serviceConfig = { + ExecStart = "${geoip-updater}/bin/geoip-updater"; + User = "geoip"; + PermissionsStartOnly = true; + }; + }; + + systemd.services.geoip-updater-setup = { + description = "GeoIP Updater Setup"; + after = [ "network-online.target" "nss-lookup.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + conflicts = [ "geoip-updater.service" ]; + preStart = '' + mkdir -p "${cfg.databaseDir}" + chmod 755 "${cfg.databaseDir}" + chown geoip:root "${cfg.databaseDir}" + ''; + serviceConfig = { + ExecStart = "${geoip-updater}/bin/geoip-updater --skip-existing"; + User = "geoip"; + PermissionsStartOnly = true; + # So it won't be (needlessly) restarted: + RemainAfterExit = true; + }; + }; + + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/gitea.nix b/nixpkgs/nixos/modules/services/misc/gitea.nix new file mode 100644 index 000000000000..be4d38719785 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/gitea.nix @@ -0,0 +1,412 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.gitea; + gitea = cfg.package; + pg = config.services.postgresql; + useMysql = cfg.database.type == "mysql"; + usePostgresql = cfg.database.type == "postgres"; + configFile = pkgs.writeText "app.ini" '' + APP_NAME = ${cfg.appName} + RUN_USER = ${cfg.user} + RUN_MODE = prod + + [database] + DB_TYPE = ${cfg.database.type} + HOST = ${if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port} + NAME = ${cfg.database.name} + USER = ${cfg.database.user} + PASSWD = #dbpass# + PATH = ${cfg.database.path} + ${optionalString usePostgresql '' + SSL_MODE = disable + ''} + + [repository] + ROOT = ${cfg.repositoryRoot} + + [server] + DOMAIN = ${cfg.domain} + HTTP_ADDR = ${cfg.httpAddress} + HTTP_PORT = ${toString cfg.httpPort} + ROOT_URL = ${cfg.rootUrl} + STATIC_ROOT_PATH = ${cfg.staticRootPath} + + [session] + COOKIE_NAME = session + COOKIE_SECURE = ${boolToString cfg.cookieSecure} + + [security] + SECRET_KEY = #secretkey# + INSTALL_LOCK = true + + [log] + ROOT_PATH = ${cfg.log.rootPath} + LEVEL = ${cfg.log.level} + + [service] + DISABLE_REGISTRATION = ${boolToString cfg.disableRegistration} + + ${cfg.extraConfig} + ''; +in + +{ + options = { + services.gitea = { + enable = mkOption { + default = false; + type = types.bool; + description = "Enable Gitea Service."; + }; + + package = mkOption { + default = pkgs.gitea; + type = types.package; + defaultText = "pkgs.gitea"; + description = "gitea derivation to use"; + }; + + useWizard = mkOption { + default = false; + type = types.bool; + description = "Do not generate a configuration and use gitea' installation wizard instead. The first registered user will be administrator."; + }; + + stateDir = mkOption { + default = "/var/lib/gitea"; + type = types.str; + description = "gitea data directory."; + }; + + log = { + rootPath = mkOption { + default = "${cfg.stateDir}/log"; + type = types.str; + description = "Root path for log files."; + }; + level = mkOption { + default = "Trace"; + type = types.enum [ "Trace" "Debug" "Info" "Warn" "Error" "Critical" ]; + description = "General log level."; + }; + }; + + user = mkOption { + type = types.str; + default = "gitea"; + description = "User account under which gitea runs."; + }; + + database = { + type = mkOption { + type = types.enum [ "sqlite3" "mysql" "postgres" ]; + example = "mysql"; + default = "sqlite3"; + description = "Database engine to use."; + }; + + host = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "Database host address."; + }; + + port = mkOption { + type = types.int; + default = (if !usePostgresql then 3306 else pg.port); + description = "Database host port."; + }; + + name = mkOption { + type = types.str; + default = "gitea"; + description = "Database name."; + }; + + user = mkOption { + type = types.str; + default = "gitea"; + description = "Database user."; + }; + + password = mkOption { + type = types.str; + default = ""; + description = '' + The password corresponding to <option>database.user</option>. + Warning: this is stored in cleartext in the Nix store! + Use <option>database.passwordFile</option> instead. + ''; + }; + + passwordFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/keys/gitea-dbpassword"; + description = '' + A file containing the password corresponding to + <option>database.user</option>. + ''; + }; + + socket = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/mysqld/mysqld.sock"; + description = "Path to the unix socket file to use for authentication."; + }; + + path = mkOption { + type = types.str; + default = "${cfg.stateDir}/data/gitea.db"; + description = "Path to the sqlite3 database file."; + }; + + createDatabase = mkOption { + type = types.bool; + default = true; + description = '' + Whether to create a local postgresql database automatically. + This only applies if database type "postgres" is selected. + ''; + }; + }; + + dump = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Enable a timer that runs gitea dump to generate backup-files of the + current gitea database and repositories. + ''; + }; + + interval = mkOption { + type = types.str; + default = "04:31"; + example = "hourly"; + description = '' + Run a gitea dump at this interval. Runs by default at 04:31 every day. + + The format is described in + <citerefentry><refentrytitle>systemd.time</refentrytitle> + <manvolnum>7</manvolnum></citerefentry>. + ''; + }; + }; + + appName = mkOption { + type = types.str; + default = "gitea: Gitea Service"; + description = "Application name."; + }; + + repositoryRoot = mkOption { + type = types.str; + default = "${cfg.stateDir}/repositories"; + description = "Path to the git repositories."; + }; + + domain = mkOption { + type = types.str; + default = "localhost"; + description = "Domain name of your server."; + }; + + rootUrl = mkOption { + type = types.str; + default = "http://localhost:3000/"; + description = "Full public URL of gitea server."; + }; + + httpAddress = mkOption { + type = types.str; + default = "0.0.0.0"; + description = "HTTP listen address."; + }; + + httpPort = mkOption { + type = types.int; + default = 3000; + description = "HTTP listen port."; + }; + + cookieSecure = mkOption { + type = types.bool; + default = false; + description = '' + Marks session cookies as "secure" as a hint for browsers to only send + them via HTTPS. This option is recommend, if gitea is being served over HTTPS. + ''; + }; + + staticRootPath = mkOption { + type = types.str; + default = "${gitea.data}"; + example = "/var/lib/gitea/data"; + description = "Upper level of template and static files path."; + }; + + disableRegistration = mkEnableOption "the registration lock" // { + description = '' + By default any user can create an account on this <literal>gitea</literal> instance. + This can be disabled by using this option. + + <emphasis>Note:</emphasis> please keep in mind that this should be added after the initial + deploy unless <link linkend="opt-services.gitea.useWizard">services.gitea.useWizard</link> + is <literal>true</literal> as the first registered user will be the administrator if + no install wizard is used. + ''; + }; + + extraConfig = mkOption { + type = types.str; + default = ""; + description = "Configuration lines appended to the generated gitea configuration file."; + }; + }; + }; + + config = mkIf cfg.enable { + services.postgresql.enable = mkIf usePostgresql (mkDefault true); + + systemd.services.gitea = { + description = "gitea"; + after = [ "network.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service"; + wantedBy = [ "multi-user.target" ]; + path = [ gitea.bin pkgs.gitAndTools.git ]; + + preStart = let + runConfig = "${cfg.stateDir}/custom/conf/app.ini"; + secretKey = "${cfg.stateDir}/custom/conf/secret_key"; + in '' + # Make sure that the stateDir exists, as well as the conf dir in there + mkdir -p ${cfg.stateDir}/conf + + # copy custom configuration and generate a random secret key if needed + ${optionalString (cfg.useWizard == false) '' + mkdir -p ${cfg.stateDir}/custom/conf + cp -f ${configFile} ${runConfig} + + if [ ! -e ${secretKey} ]; then + head -c 16 /dev/urandom | base64 > ${secretKey} + fi + + KEY=$(head -n1 ${secretKey}) + DBPASS=$(head -n1 ${cfg.database.passwordFile}) + sed -e "s,#secretkey#,$KEY,g" \ + -e "s,#dbpass#,$DBPASS,g" \ + -i ${runConfig} + chmod 640 ${runConfig} ${secretKey} + ''} + + mkdir -p ${cfg.repositoryRoot} + # update all hooks' binary paths + HOOKS=$(find ${cfg.repositoryRoot} -mindepth 4 -maxdepth 6 -type f -wholename "*git/hooks/*") + if [ "$HOOKS" ] + then + sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${gitea.bin}/bin/gitea,g' $HOOKS + sed -ri 's,/nix/store/[a-z0-9.-]+/bin/env,${pkgs.coreutils}/bin/env,g' $HOOKS + sed -ri 's,/nix/store/[a-z0-9.-]+/bin/bash,${pkgs.bash}/bin/bash,g' $HOOKS + sed -ri 's,/nix/store/[a-z0-9.-]+/bin/perl,${pkgs.perl}/bin/perl,g' $HOOKS + fi + # If we have a folder or symlink with gitea locales, remove it + if [ -e ${cfg.stateDir}/conf/locale ] + then + rm -r ${cfg.stateDir}/conf/locale + fi + # And symlink the current gitea locales in place + ln -s ${gitea.out}/locale ${cfg.stateDir}/conf/locale + # update command option in authorized_keys + if [ -r ${cfg.stateDir}/.ssh/authorized_keys ] + then + sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${gitea.bin}/bin/gitea,g' ${cfg.stateDir}/.ssh/authorized_keys + fi + '' + optionalString (usePostgresql && cfg.database.createDatabase) '' + if ! test -e "${cfg.stateDir}/db-created"; then + echo "CREATE ROLE ${cfg.database.user} + WITH ENCRYPTED PASSWORD '$(head -n1 ${cfg.database.passwordFile})' + NOCREATEDB NOCREATEROLE LOGIN" | + ${pkgs.sudo}/bin/sudo -u ${pg.superUser} ${pg.package}/bin/psql + ${pkgs.sudo}/bin/sudo -u ${pg.superUser} \ + ${pg.package}/bin/createdb \ + --owner=${cfg.database.user} \ + --encoding=UTF8 \ + --lc-collate=C \ + --lc-ctype=C \ + --template=template0 \ + ${cfg.database.name} + touch "${cfg.stateDir}/db-created" + fi + '' + '' + chown ${cfg.user} -R ${cfg.stateDir} + ''; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + WorkingDirectory = cfg.stateDir; + PermissionsStartOnly = true; + ExecStart = "${gitea.bin}/bin/gitea web"; + Restart = "always"; + }; + + environment = { + USER = cfg.user; + HOME = cfg.stateDir; + GITEA_WORK_DIR = cfg.stateDir; + }; + }; + + users = mkIf (cfg.user == "gitea") { + users.gitea = { + description = "Gitea Service"; + home = cfg.stateDir; + createHome = true; + useDefaultShell = true; + }; + }; + + warnings = optional (cfg.database.password != "") + ''config.services.gitea.database.password will be stored as plaintext + in the Nix store. Use database.passwordFile instead.''; + + # Create database passwordFile default when password is configured. + services.gitea.database.passwordFile = + (mkDefault (toString (pkgs.writeTextFile { + name = "gitea-database-password"; + text = cfg.database.password; + }))); + + systemd.services.gitea-dump = mkIf cfg.dump.enable { + description = "gitea dump"; + after = [ "gitea.service" ]; + wantedBy = [ "default.target" ]; + path = [ gitea.bin ]; + + environment = { + USER = cfg.user; + HOME = cfg.stateDir; + GITEA_WORK_DIR = cfg.stateDir; + }; + + serviceConfig = { + Type = "oneshot"; + User = cfg.user; + ExecStart = "${gitea.bin}/bin/gitea dump"; + WorkingDirectory = cfg.stateDir; + }; + }; + + systemd.timers.gitea-dump = mkIf cfg.dump.enable { + description = "Update timer for gitea-dump"; + partOf = [ "gitea-dump.service" ]; + wantedBy = [ "timers.target" ]; + timerConfig.OnCalendar = cfg.dump.interval; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/gitit.nix b/nixpkgs/nixos/modules/services/misc/gitit.nix new file mode 100644 index 000000000000..1ec030549f98 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/gitit.nix @@ -0,0 +1,724 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.gitit; + + homeDir = "/var/lib/gitit"; + + toYesNo = b: if b then "yes" else "no"; + + gititShared = with cfg.haskellPackages; gitit + "/share/" + pkgs.stdenv.hostPlatform.system + "-" + ghc.name + "/" + gitit.pname + "-" + gitit.version; + + gititWithPkgs = hsPkgs: extras: hsPkgs.ghcWithPackages (self: with self; [ gitit ] ++ (extras self)); + + gititSh = hsPkgs: extras: with pkgs; let + env = gititWithPkgs hsPkgs extras; + in writeScript "gitit" '' + #!${runtimeShell} + cd $HOME + export NIX_GHC="${env}/bin/ghc" + export NIX_GHCPKG="${env}/bin/ghc-pkg" + export NIX_GHC_DOCDIR="${env}/share/doc/ghc/html" + export NIX_GHC_LIBDIR=$( $NIX_GHC --print-libdir ) + ${env}/bin/gitit -f ${configFile} + ''; + + gititOptions = { + + enable = mkOption { + type = types.bool; + default = false; + description = "Enable the gitit service."; + }; + + haskellPackages = mkOption { + default = pkgs.haskellPackages; + defaultText = "pkgs.haskellPackages"; + example = literalExample "pkgs.haskell.packages.ghc784"; + description = "haskellPackages used to build gitit and plugins."; + }; + + extraPackages = mkOption { + default = self: []; + example = literalExample '' + haskellPackages: [ + haskellPackages.wreq + ] + ''; + description = '' + Extra packages available to ghc when running gitit. The + value must be a function which receives the attrset defined + in <varname>haskellPackages</varname> as the sole argument. + ''; + }; + + address = mkOption { + type = types.str; + default = "0.0.0.0"; + description = "IP address on which the web server will listen."; + }; + + port = mkOption { + type = types.int; + default = 5001; + description = "Port on which the web server will run."; + }; + + wikiTitle = mkOption { + type = types.str; + default = "Gitit!"; + description = "The wiki title."; + }; + + repositoryType = mkOption { + type = types.enum ["git" "darcs" "mercurial"]; + default = "git"; + description = "Specifies the type of repository used for wiki content."; + }; + + repositoryPath = mkOption { + type = types.path; + default = homeDir + "/wiki"; + description = '' + Specifies the path of the repository directory. If it does not + exist, gitit will create it on startup. + ''; + }; + + requireAuthentication = mkOption { + type = types.enum [ "none" "modify" "read" ]; + default = "modify"; + description = '' + If 'none', login is never required, and pages can be edited + anonymously. If 'modify', login is required to modify the wiki + (edit, add, delete pages, upload files). If 'read', login is + required to see any wiki pages. + ''; + }; + + authenticationMethod = mkOption { + type = types.enum [ "form" "http" "generic" "github" ]; + default = "form"; + description = '' + 'form' means that users will be logged in and registered using forms + in the gitit web interface. 'http' means that gitit will assume that + HTTP authentication is in place and take the logged in username from + the "Authorization" field of the HTTP request header (in addition, + the login/logout and registration links will be suppressed). + 'generic' means that gitit will assume that some form of + authentication is in place that directly sets REMOTE_USER to the name + of the authenticated user (e.g. mod_auth_cas on apache). 'rpx' means + that gitit will attempt to log in through https://rpxnow.com. This + requires that 'rpx-domain', 'rpx-key', and 'base-url' be set below, + and that 'curl' be in the system path. + ''; + }; + + userFile = mkOption { + type = types.path; + default = homeDir + "/gitit-users"; + description = '' + Specifies the path of the file containing user login information. If + it does not exist, gitit will create it (with an empty user list). + This file is not used if 'http' is selected for + authentication-method. + ''; + }; + + sessionTimeout = mkOption { + type = types.int; + default = 60; + description = '' + Number of minutes of inactivity before a session expires. + ''; + }; + + staticDir = mkOption { + type = types.path; + default = gititShared + "/data/static"; + description = '' + Specifies the path of the static directory (containing javascript, + css, and images). If it does not exist, gitit will create it and + populate it with required scripts, stylesheets, and images. + ''; + }; + + defaultPageType = mkOption { + type = types.enum [ "markdown" "rst" "latex" "html" "markdown+lhs" "rst+lhs" "latex+lhs" ]; + default = "markdown"; + description = '' + Specifies the type of markup used to interpret pages in the wiki. + Possible values are markdown, rst, latex, html, markdown+lhs, + rst+lhs, and latex+lhs. (the +lhs variants treat the input as + literate Haskell. See pandoc's documentation for more details.) If + Markdown is selected, pandoc's syntax extensions (for footnotes, + delimited code blocks, etc.) will be enabled. Note that pandoc's + restructuredtext parser is not complete, so some pages may not be + rendered correctly if rst is selected. The same goes for latex and + html. + ''; + }; + + math = mkOption { + type = types.enum [ "mathml" "raw" "mathjax" "jsmath" "google" ]; + default = "mathml"; + description = '' + Specifies how LaTeX math is to be displayed. Possible values are + mathml, raw, mathjax, jsmath, and google. If mathml is selected, + gitit will convert LaTeX math to MathML and link in a script, + MathMLinHTML.js, that allows the MathML to be seen in Gecko browsers, + IE + mathplayer, and Opera. In other browsers you may get a jumble of + characters. If raw is selected, the LaTeX math will be displayed as + raw LaTeX math. If mathjax is selected, gitit will link to the + remote mathjax script. If jsMath is selected, gitit will link to the + script /js/jsMath/easy/load.js, and will assume that jsMath has been + installed into the js/jsMath directory. This is the most portable + solution. If google is selected, the google chart API is called to + render the formula as an image. This requires a connection to google, + and might raise a technical or a privacy problem. + ''; + }; + + mathJaxScript = mkOption { + type = types.str; + default = "https://d3eoax9i5htok0.cloudfront.net/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"; + description = '' + Specifies the path to MathJax rendering script. You might want to + use your own MathJax script to render formulas without Internet + connection or if you want to use some special LaTeX packages. Note: + path specified there cannot be an absolute path to a script on your + hdd, instead you should run your (local if you wish) HTTP server + which will serve the MathJax.js script. You can easily (in four lines + of code) serve MathJax.js using + http://happstack.com/docs/crashcourse/FileServing.html Do not forget + the "http://" prefix (e.g. http://localhost:1234/MathJax.js). + ''; + }; + + showLhsBirdTracks = mkOption { + type = types.bool; + default = false; + description = '' + Specifies whether to show Haskell code blocks in "bird style", with + "> " at the beginning of each line. + ''; + }; + + templatesDir = mkOption { + type = types.path; + default = gititShared + "/data/templates"; + description = '' + Specifies the path of the directory containing page templates. If it + does not exist, gitit will create it with default templates. Users + may wish to edit the templates to customize the appearance of their + wiki. The template files are HStringTemplate templates. Variables to + be interpolated appear between $\'s. Literal $\'s must be + backslash-escaped. + ''; + }; + + logFile = mkOption { + type = types.path; + default = homeDir + "/gitit.log"; + description = '' + Specifies the path of gitit's log file. If it does not exist, gitit + will create it. The log is in Apache combined log format. + ''; + }; + + logLevel = mkOption { + type = types.enum [ "DEBUG" "INFO" "NOTICE" "WARNING" "ERROR" "CRITICAL" "ALERT" "EMERGENCY" ]; + default = "ERROR"; + description = '' + Determines how much information is logged. Possible values (from + most to least verbose) are DEBUG, INFO, NOTICE, WARNING, ERROR, + CRITICAL, ALERT, EMERGENCY. + ''; + }; + + frontPage = mkOption { + type = types.str; + default = "Front Page"; + description = '' + Specifies which wiki page is to be used as the wiki's front page. + Gitit creates a default front page on startup, if one does not exist + already. + ''; + }; + + noDelete = mkOption { + type = types.str; + default = "Front Page, Help"; + description = '' + Specifies pages that cannot be deleted through the web interface. + (They can still be deleted directly using git or darcs.) A + comma-separated list of page names. Leave blank to allow every page + to be deleted. + ''; + }; + + noEdit = mkOption { + type = types.str; + default = "Help"; + description = '' + Specifies pages that cannot be edited through the web interface. + Leave blank to allow every page to be edited. + ''; + }; + + defaultSummary = mkOption { + type = types.str; + default = ""; + description = '' + Specifies text to be used in the change description if the author + leaves the "description" field blank. If default-summary is blank + (the default), the author will be required to fill in the description + field. + ''; + }; + + tableOfContents = mkOption { + type = types.bool; + default = true; + description = '' + Specifies whether to print a tables of contents (with links to + sections) on each wiki page. + ''; + }; + + plugins = mkOption { + type = with types; listOf str; + default = [ (gititShared + "/plugins/Dot.hs") ]; + description = '' + Specifies a list of plugins to load. Plugins may be specified either + by their path or by their module name. If the plugin name starts + with Gitit.Plugin., gitit will assume that the plugin is an installed + module and will not try to find a source file. + ''; + }; + + useCache = mkOption { + type = types.bool; + default = false; + description = '' + Specifies whether to cache rendered pages. Note that if use-feed is + selected, feeds will be cached regardless of the value of use-cache. + ''; + }; + + cacheDir = mkOption { + type = types.path; + default = homeDir + "/cache"; + description = "Path where rendered pages will be cached."; + }; + + maxUploadSize = mkOption { + type = types.str; + default = "1000K"; + description = '' + Specifies an upper limit on the size (in bytes) of files uploaded + through the wiki's web interface. To disable uploads, set this to + 0K. This will result in the uploads link disappearing and the + _upload url becoming inactive. + ''; + }; + + maxPageSize = mkOption { + type = types.str; + default = "1000K"; + description = "Specifies an upper limit on the size (in bytes) of pages."; + }; + + debugMode = mkOption { + type = types.bool; + default = false; + description = "Causes debug information to be logged while gitit is running."; + }; + + compressResponses = mkOption { + type = types.bool; + default = true; + description = "Specifies whether HTTP responses should be compressed."; + }; + + mimeTypesFile = mkOption { + type = types.path; + default = "/etc/mime/types.info"; + description = '' + Specifies the path of a file containing mime type mappings. Each + line of the file should contain two fields, separated by whitespace. + The first field is the mime type, the second is a file extension. + For example: +<programlisting> +video/x-ms-wmx wmx +</programlisting> + If the file is not found, some simple defaults will be used. + ''; + }; + + useReCaptcha = mkOption { + type = types.bool; + default = false; + description = '' + If true, causes gitit to use the reCAPTCHA service + (http://recaptcha.net) to prevent bots from creating accounts. + ''; + }; + + reCaptchaPrivateKey = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Specifies the private key for the reCAPTCHA service. To get + these, you need to create an account at http://recaptcha.net. + ''; + }; + + reCaptchaPublicKey = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Specifies the public key for the reCAPTCHA service. To get + these, you need to create an account at http://recaptcha.net. + ''; + }; + + accessQuestion = mkOption { + type = types.str; + default = "What is the code given to you by Ms. X?"; + description = '' + Specifies a question that users must answer when they attempt to + create an account + ''; + }; + + accessQuestionAnswers = mkOption { + type = types.str; + default = "RED DOG, red dog"; + description = '' + Specifies a question that users must answer when they attempt to + create an account, along with a comma-separated list of acceptable + answers. This can be used to institute a rudimentary password for + signing up as a user on the wiki, or as an alternative to reCAPTCHA. + Example: + access-question: What is the code given to you by Ms. X? + access-question-answers: RED DOG, red dog + ''; + }; + + rpxDomain = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Specifies the domain and key of your RPX account. The domain is just + the prefix of the complete RPX domain, so if your full domain is + 'https://foo.rpxnow.com/', use 'foo' as the value of rpx-domain. + ''; + }; + + rpxKey = mkOption { + type = with types; nullOr str; + default = null; + description = "RPX account access key."; + }; + + mailCommand = mkOption { + type = types.str; + default = "sendmail %s"; + description = '' + Specifies the command to use to send notification emails. '%s' will + be replaced by the destination email address. The body of the + message will be read from stdin. If this field is left blank, + password reset will not be offered. + ''; + }; + + resetPasswordMessage = mkOption { + type = types.lines; + default = '' + > From: gitit@$hostname$ + > To: $useremail$ + > Subject: Wiki password reset + > + > Hello $username$, + > + > To reset your password, please follow the link below: + > http://$hostname$:$port$$resetlink$ + > + > Regards + ''; + description = '' + Gives the text of the message that will be sent to the user should + she want to reset her password, or change other registration info. + The lines must be indented, and must begin with '>'. The initial + spaces and '> ' will be stripped off. $username$ will be replaced by + the user's username, $useremail$ by her email address, $hostname$ by + the hostname on which the wiki is running (as returned by the + hostname system call), $port$ by the port on which the wiki is + running, and $resetlink$ by the relative path of a reset link derived + from the user's existing hashed password. If your gitit wiki is being + proxied to a location other than the root path of $port$, you should + change the link to reflect this: for example, to + http://$hostname$/path/to/wiki$resetlink$ or + http://gitit.$hostname$$resetlink$ + ''; + }; + + useFeed = mkOption { + type = types.bool; + default = false; + description = '' + Specifies whether an ATOM feed should be enabled (for the site and + for individual pages). + ''; + }; + + baseUrl = mkOption { + type = with types; nullOr str; + default = null; + description = '' + The base URL of the wiki, to be used in constructing feed IDs and RPX + token_urls. Set this if useFeed is false or authentication-method + is 'rpx'. + ''; + }; + + absoluteUrls = mkOption { + type = types.bool; + default = false; + description = '' + Make wikilinks absolute with respect to the base-url. So, for + example, in a wiki served at the base URL '/wiki', on a page + Sub/Page, the wikilink '[Cactus]()' will produce a link to + '/wiki/Cactus' if absoluteUrls is true, and a relative link to + 'Cactus' (referring to '/wiki/Sub/Cactus') if absolute-urls is 'no'. + ''; + }; + + feedDays = mkOption { + type = types.int; + default = 14; + description = "Number of days to be included in feeds."; + }; + + feedRefreshTime = mkOption { + type = types.int; + default = 60; + description = "Number of minutes to cache feeds before refreshing."; + }; + + pdfExport = mkOption { + type = types.bool; + default = false; + description = '' + If true, PDF will appear in export options. PDF will be created using + pdflatex, which must be installed and in the path. Note that PDF + exports create significant additional server load. + ''; + }; + + pandocUserData = mkOption { + type = with types; nullOr path; + default = null; + description = '' + If a directory is specified, this will be searched for pandoc + customizations. These can include a templates/ directory for custom + templates for various export formats, an S5 directory for custom S5 + styles, and a reference.odt for ODT exports. If no directory is + specified, $HOME/.pandoc will be searched. See pandoc's README for + more information. + ''; + }; + + xssSanitize = mkOption { + type = types.bool; + default = true; + description = '' + If true, all HTML (including that produced by pandoc) is filtered + through xss-sanitize. Set to no only if you trust all of your users. + ''; + }; + + oauthClientId = mkOption { + type = with types; nullOr str; + default = null; + description = "OAuth client ID"; + }; + + oauthClientSecret = mkOption { + type = with types; nullOr str; + default = null; + description = "OAuth client secret"; + }; + + oauthCallback = mkOption { + type = with types; nullOr str; + default = null; + description = "OAuth callback URL"; + }; + + oauthAuthorizeEndpoint = mkOption { + type = with types; nullOr str; + default = null; + description = "OAuth authorize endpoint"; + }; + + oauthAccessTokenEndpoint = mkOption { + type = with types; nullOr str; + default = null; + description = "OAuth access token endpoint"; + }; + + githubOrg = mkOption { + type = with types; nullOr str; + default = null; + description = "Github organization"; + }; + }; + + configFile = pkgs.writeText "gitit.conf" '' + address: ${cfg.address} + port: ${toString cfg.port} + wiki-title: ${cfg.wikiTitle} + repository-type: ${cfg.repositoryType} + repository-path: ${cfg.repositoryPath} + require-authentication: ${cfg.requireAuthentication} + authentication-method: ${cfg.authenticationMethod} + user-file: ${cfg.userFile} + session-timeout: ${toString cfg.sessionTimeout} + static-dir: ${cfg.staticDir} + default-page-type: ${cfg.defaultPageType} + math: ${cfg.math} + mathjax-script: ${cfg.mathJaxScript} + show-lhs-bird-tracks: ${toYesNo cfg.showLhsBirdTracks} + templates-dir: ${cfg.templatesDir} + log-file: ${cfg.logFile} + log-level: ${cfg.logLevel} + front-page: ${cfg.frontPage} + no-delete: ${cfg.noDelete} + no-edit: ${cfg.noEdit} + default-summary: ${cfg.defaultSummary} + table-of-contents: ${toYesNo cfg.tableOfContents} + plugins: ${concatStringsSep "," cfg.plugins} + use-cache: ${toYesNo cfg.useCache} + cache-dir: ${cfg.cacheDir} + max-upload-size: ${cfg.maxUploadSize} + max-page-size: ${cfg.maxPageSize} + debug-mode: ${toYesNo cfg.debugMode} + compress-responses: ${toYesNo cfg.compressResponses} + mime-types-file: ${cfg.mimeTypesFile} + use-recaptcha: ${toYesNo cfg.useReCaptcha} + recaptcha-private-key: ${toString cfg.reCaptchaPrivateKey} + recaptcha-public-key: ${toString cfg.reCaptchaPublicKey} + access-question: ${cfg.accessQuestion} + access-question-answers: ${cfg.accessQuestionAnswers} + rpx-domain: ${toString cfg.rpxDomain} + rpx-key: ${toString cfg.rpxKey} + mail-command: ${cfg.mailCommand} + reset-password-message: ${cfg.resetPasswordMessage} + use-feed: ${toYesNo cfg.useFeed} + base-url: ${toString cfg.baseUrl} + absolute-urls: ${toYesNo cfg.absoluteUrls} + feed-days: ${toString cfg.feedDays} + feed-refresh-time: ${toString cfg.feedRefreshTime} + pdf-export: ${toYesNo cfg.pdfExport} + pandoc-user-data: ${toString cfg.pandocUserData} + xss-sanitize: ${toYesNo cfg.xssSanitize} + + [Github] + oauthclientid: ${toString cfg.oauthClientId} + oauthclientsecret: ${toString cfg.oauthClientSecret} + oauthcallback: ${toString cfg.oauthCallback} + oauthauthorizeendpoint: ${toString cfg.oauthAuthorizeEndpoint} + oauthaccesstokenendpoint: ${toString cfg.oauthAccessTokenEndpoint} + github-org: ${toString cfg.githubOrg} + ''; + +in + +{ + + options.services.gitit = gititOptions; + + config = mkIf cfg.enable { + + users.users.gitit = { + group = config.users.groups.gitit.name; + description = "Gitit user"; + home = homeDir; + createHome = true; + uid = config.ids.uids.gitit; + }; + + users.groups.gitit.gid = config.ids.gids.gitit; + + systemd.services.gitit = let + uid = toString config.ids.uids.gitit; + gid = toString config.ids.gids.gitit; + in { + description = "Git and Pandoc Powered Wiki"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + path = with pkgs; [ curl ] + ++ optional cfg.pdfExport texlive.combined.scheme-basic + ++ optional (cfg.repositoryType == "darcs") darcs + ++ optional (cfg.repositoryType == "mercurial") mercurial + ++ optional (cfg.repositoryType == "git") git; + + preStart = let + gm = "gitit@${config.networking.hostName}"; + in + with cfg; '' + chown ${uid}:${gid} -R ${homeDir} + for dir in ${repositoryPath} ${staticDir} ${templatesDir} ${cacheDir} + do + if [ ! -d $dir ] + then + mkdir -p $dir + find $dir -type d -exec chmod 0750 {} + + find $dir -type f -exec chmod 0640 {} + + fi + done + cd ${repositoryPath} + ${ + if repositoryType == "darcs" then + '' + if [ ! -d _darcs ] + then + ${pkgs.darcs}/bin/darcs initialize + echo "${gm}" > _darcs/prefs/email + '' + else if repositoryType == "mercurial" then + '' + if [ ! -d .hg ] + then + ${pkgs.mercurial}/bin/hg init + cat >> .hg/hgrc <<NAMED +[ui] +username = gitit ${gm} +NAMED + '' + else + '' + if [ ! -d .git ] + then + ${pkgs.git}/bin/git init + ${pkgs.git}/bin/git config user.email "${gm}" + ${pkgs.git}/bin/git config user.name "gitit" + ''} + chown ${uid}:${gid} -R ${repositoryPath} + fi + cd - + ''; + + serviceConfig = { + User = config.users.users.gitit.name; + Group = config.users.groups.gitit.name; + ExecStart = with cfg; gititSh haskellPackages extraPackages; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/gitlab.nix b/nixpkgs/nixos/modules/services/misc/gitlab.nix new file mode 100644 index 000000000000..71277b48ecd9 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/gitlab.nix @@ -0,0 +1,702 @@ +{ config, lib, pkgs, ... }: + +# TODO: support non-postgresql + +with lib; + +let + cfg = config.services.gitlab; + + ruby = cfg.packages.gitlab.ruby; + + gitlabSocket = "${cfg.statePath}/tmp/sockets/gitlab.socket"; + gitalySocket = "${cfg.statePath}/tmp/sockets/gitaly.socket"; + pathUrlQuote = url: replaceStrings ["/"] ["%2F"] url; + pgSuperUser = config.services.postgresql.superUser; + + databaseConfig = { + production = { + adapter = "postgresql"; + database = cfg.databaseName; + host = cfg.databaseHost; + password = cfg.databasePassword; + username = cfg.databaseUsername; + encoding = "utf8"; + pool = cfg.databasePool; + } // cfg.extraDatabaseConfig; + }; + + gitalyToml = pkgs.writeText "gitaly.toml" '' + socket_path = "${lib.escape ["\""] gitalySocket}" + bin_dir = "${cfg.packages.gitaly}/bin" + prometheus_listen_addr = "localhost:9236" + + [git] + bin_path = "${pkgs.git}/bin/git" + + [gitaly-ruby] + dir = "${cfg.packages.gitaly.ruby}" + + [gitlab-shell] + dir = "${cfg.packages.gitlab-shell}" + + ${concatStringsSep "\n" (attrValues (mapAttrs (k: v: '' + [[storage]] + name = "${lib.escape ["\""] k}" + path = "${lib.escape ["\""] v.path}" + '') gitlabConfig.production.repositories.storages))} + ''; + + gitlabShellConfig = { + user = cfg.user; + gitlab_url = "http+unix://${pathUrlQuote gitlabSocket}"; + http_settings.self_signed_cert = false; + repos_path = "${cfg.statePath}/repositories"; + secret_file = "${cfg.statePath}/config/gitlab_shell_secret"; + log_file = "${cfg.statePath}/log/gitlab-shell.log"; + custom_hooks_dir = "${cfg.statePath}/custom_hooks"; + redis = { + bin = "${pkgs.redis}/bin/redis-cli"; + host = "127.0.0.1"; + port = 6379; + database = 0; + namespace = "resque:gitlab"; + }; + }; + + redisConfig.production.url = "redis://localhost:6379/"; + + secretsConfig.production = { + secret_key_base = cfg.secrets.secret; + otp_key_base = cfg.secrets.otp; + db_key_base = cfg.secrets.db; + openid_connect_signing_key = cfg.secrets.jws; + }; + + gitlabConfig = { + # These are the default settings from config/gitlab.example.yml + production = flip recursiveUpdate cfg.extraConfig { + gitlab = { + host = cfg.host; + port = cfg.port; + https = cfg.https; + user = cfg.user; + email_enabled = true; + email_display_name = "GitLab"; + email_reply_to = "noreply@localhost"; + default_theme = 2; + default_projects_features = { + issues = true; + merge_requests = true; + wiki = true; + snippets = true; + builds = true; + container_registry = true; + }; + }; + repositories.storages.default.path = "${cfg.statePath}/repositories"; + repositories.storages.default.gitaly_address = "unix:${gitalySocket}"; + artifacts.enabled = true; + lfs.enabled = true; + gravatar.enabled = true; + cron_jobs = { }; + gitlab_ci.builds_path = "${cfg.statePath}/builds"; + ldap.enabled = false; + omniauth.enabled = false; + shared.path = "${cfg.statePath}/shared"; + gitaly.client_path = "${cfg.packages.gitaly}/bin"; + backup.path = "${cfg.backupPath}"; + gitlab_shell = { + path = "${cfg.packages.gitlab-shell}"; + hooks_path = "${cfg.statePath}/shell/hooks"; + secret_file = "${cfg.statePath}/config/gitlab_shell_secret"; + upload_pack = true; + receive_pack = true; + }; + workhorse.secret_file = "${cfg.statePath}/.gitlab_workhorse_secret"; + git.bin_path = "git"; + monitoring = { + ip_whitelist = [ "127.0.0.0/8" "::1/128" ]; + sidekiq_exporter = { + enable = true; + address = "localhost"; + port = 3807; + }; + }; + extra = {}; + uploads.storage_path = cfg.statePath; + }; + }; + + gitlabEnv = { + HOME = "${cfg.statePath}/home"; + UNICORN_PATH = "${cfg.statePath}/"; + GITLAB_PATH = "${cfg.packages.gitlab}/share/gitlab/"; + GITLAB_STATE_PATH = cfg.statePath; + GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads"; + SCHEMA = "${cfg.statePath}/db/schema.rb"; + GITLAB_LOG_PATH = "${cfg.statePath}/log"; + GITLAB_SHELL_PATH = "${cfg.packages.gitlab-shell}"; + GITLAB_SHELL_CONFIG_PATH = "${cfg.statePath}/shell/config.yml"; + GITLAB_SHELL_SECRET_PATH = "${cfg.statePath}/config/gitlab_shell_secret"; + GITLAB_SHELL_HOOKS_PATH = "${cfg.statePath}/shell/hooks"; + GITLAB_REDIS_CONFIG_FILE = pkgs.writeText "redis.yml" (builtins.toJSON redisConfig); + prometheus_multiproc_dir = "/run/gitlab"; + RAILS_ENV = "production"; + }; + + gitlab-rake = pkgs.stdenv.mkDerivation rec { + name = "gitlab-rake"; + buildInputs = [ pkgs.makeWrapper ]; + dontBuild = true; + unpackPhase = ":"; + installPhase = '' + mkdir -p $out/bin + makeWrapper ${cfg.packages.gitlab.rubyEnv}/bin/rake $out/bin/gitlab-rake \ + ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") gitlabEnv)} \ + --set PATH '${lib.makeBinPath [ pkgs.nodejs pkgs.gzip pkgs.git pkgs.gnutar config.services.postgresql.package pkgs.coreutils pkgs.procps ]}:$PATH' \ + --set RAKEOPT '-f ${cfg.packages.gitlab}/share/gitlab/Rakefile' \ + --run 'cd ${cfg.packages.gitlab}/share/gitlab' + ''; + }; + + gitlab-rails = pkgs.stdenv.mkDerivation rec { + name = "gitlab-rails"; + buildInputs = [ pkgs.makeWrapper ]; + dontBuild = true; + unpackPhase = ":"; + installPhase = '' + mkdir -p $out/bin + makeWrapper ${cfg.packages.gitlab.rubyEnv}/bin/rails $out/bin/gitlab-rails \ + ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") gitlabEnv)} \ + --set PATH '${lib.makeBinPath [ pkgs.nodejs pkgs.gzip pkgs.git pkgs.gnutar config.services.postgresql.package pkgs.coreutils pkgs.procps ]}:$PATH' \ + --run 'cd ${cfg.packages.gitlab}/share/gitlab' + ''; + }; + + extraGitlabRb = pkgs.writeText "extra-gitlab.rb" cfg.extraGitlabRb; + + smtpSettings = pkgs.writeText "gitlab-smtp-settings.rb" '' + if Rails.env.production? + Rails.application.config.action_mailer.delivery_method = :smtp + + ActionMailer::Base.delivery_method = :smtp + ActionMailer::Base.smtp_settings = { + address: "${cfg.smtp.address}", + port: ${toString cfg.smtp.port}, + ${optionalString (cfg.smtp.username != null) ''user_name: "${cfg.smtp.username}",''} + ${optionalString (cfg.smtp.password != null) ''password: "${cfg.smtp.password}",''} + domain: "${cfg.smtp.domain}", + ${optionalString (cfg.smtp.authentication != null) "authentication: :${cfg.smtp.authentication},"} + enable_starttls_auto: ${toString cfg.smtp.enableStartTLSAuto}, + openssl_verify_mode: '${cfg.smtp.opensslVerifyMode}' + } + end + ''; + +in { + + options = { + services.gitlab = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Enable the gitlab service. + ''; + }; + + packages.gitlab = mkOption { + type = types.package; + default = pkgs.gitlab; + defaultText = "pkgs.gitlab"; + description = "Reference to the gitlab package"; + example = "pkgs.gitlab-ee"; + }; + + packages.gitlab-shell = mkOption { + type = types.package; + default = pkgs.gitlab-shell; + defaultText = "pkgs.gitlab-shell"; + description = "Reference to the gitlab-shell package"; + }; + + packages.gitlab-workhorse = mkOption { + type = types.package; + default = pkgs.gitlab-workhorse; + defaultText = "pkgs.gitlab-workhorse"; + description = "Reference to the gitlab-workhorse package"; + }; + + packages.gitaly = mkOption { + type = types.package; + default = pkgs.gitaly; + defaultText = "pkgs.gitaly"; + description = "Reference to the gitaly package"; + }; + + statePath = mkOption { + type = types.str; + default = "/var/gitlab/state"; + description = "Gitlab state directory, logs are stored here."; + }; + + backupPath = mkOption { + type = types.str; + default = cfg.statePath + "/backup"; + description = "Gitlab path for backups."; + }; + + databaseHost = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "Gitlab database hostname."; + }; + + databasePassword = mkOption { + type = types.str; + description = "Gitlab database user password."; + }; + + databaseName = mkOption { + type = types.str; + default = "gitlab"; + description = "Gitlab database name."; + }; + + databaseUsername = mkOption { + type = types.str; + default = "gitlab"; + description = "Gitlab database user."; + }; + + databasePool = mkOption { + type = types.int; + default = 5; + description = "Database connection pool size."; + }; + + extraDatabaseConfig = mkOption { + type = types.attrs; + default = {}; + description = "Extra configuration in config/database.yml."; + }; + + extraGitlabRb = mkOption { + type = types.str; + default = ""; + example = '' + if Rails.env.production? + Rails.application.config.action_mailer.delivery_method = :sendmail + ActionMailer::Base.delivery_method = :sendmail + ActionMailer::Base.sendmail_settings = { + location: "/run/wrappers/bin/sendmail", + arguments: "-i -t" + } + end + ''; + description = '' + Extra configuration to be placed in config/extra-gitlab.rb. This can + be used to add configuration not otherwise exposed through this module's + options. + ''; + }; + + host = mkOption { + type = types.str; + default = config.networking.hostName; + description = "Gitlab host name. Used e.g. for copy-paste URLs."; + }; + + port = mkOption { + type = types.int; + default = 8080; + description = '' + Gitlab server port for copy-paste URLs, e.g. 80 or 443 if you're + service over https. + ''; + }; + + https = mkOption { + type = types.bool; + default = false; + description = "Whether gitlab prints URLs with https as scheme."; + }; + + user = mkOption { + type = types.str; + default = "gitlab"; + description = "User to run gitlab and all related services."; + }; + + group = mkOption { + type = types.str; + default = "gitlab"; + description = "Group to run gitlab and all related services."; + }; + + initialRootEmail = mkOption { + type = types.str; + default = "admin@local.host"; + description = '' + Initial email address of the root account if this is a new install. + ''; + }; + + initialRootPassword = mkOption { + type = types.str; + description = '' + Initial password of the root account if this is a new install. + ''; + }; + + smtp = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable gitlab mail delivery over SMTP."; + }; + + address = mkOption { + type = types.str; + default = "localhost"; + description = "Address of the SMTP server for Gitlab."; + }; + + port = mkOption { + type = types.int; + default = 465; + description = "Port of the SMTP server for Gitlab."; + }; + + username = mkOption { + type = types.nullOr types.str; + default = null; + description = "Username of the SMTP server for Gitlab."; + }; + + password = mkOption { + type = types.nullOr types.str; + default = null; + description = "Password of the SMTP server for Gitlab."; + }; + + domain = mkOption { + type = types.str; + default = "localhost"; + description = "HELO domain to use for outgoing mail."; + }; + + authentication = mkOption { + type = types.nullOr types.str; + default = null; + description = "Authentitcation type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html"; + }; + + enableStartTLSAuto = mkOption { + type = types.bool; + default = true; + description = "Whether to try to use StartTLS."; + }; + + opensslVerifyMode = mkOption { + type = types.str; + default = "peer"; + description = "How OpenSSL checks the certificate, see http://api.rubyonrails.org/classes/ActionMailer/Base.html"; + }; + }; + + secrets.secret = mkOption { + type = types.str; + description = '' + The secret is used to encrypt variables in the DB. If + you change or lose this key you will be unable to access variables + stored in database. + + Make sure the secret is at least 30 characters and all random, + no regular words or you'll be exposed to dictionary attacks. + ''; + }; + + secrets.db = mkOption { + type = types.str; + description = '' + The secret is used to encrypt variables in the DB. If + you change or lose this key you will be unable to access variables + stored in database. + + Make sure the secret is at least 30 characters and all random, + no regular words or you'll be exposed to dictionary attacks. + ''; + }; + + secrets.otp = mkOption { + type = types.str; + description = '' + The secret is used to encrypt secrets for OTP tokens. If + you change or lose this key, users which have 2FA enabled for login + won't be able to login anymore. + + Make sure the secret is at least 30 characters and all random, + no regular words or you'll be exposed to dictionary attacks. + ''; + }; + + secrets.jws = mkOption { + type = types.str; + description = '' + The secret is used to encrypt session keys. If you change or lose + this key, users will be disconnected. + + Make sure the secret is an RSA private key in PEM format. You can + generate one with + + openssl genrsa 2048 + ''; + }; + + extraConfig = mkOption { + type = types.attrs; + default = {}; + example = { + gitlab = { + default_projects_features = { + builds = false; + }; + }; + }; + description = '' + Extra options to be merged into config/gitlab.yml as nix + attribute set. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + + environment.systemPackages = [ pkgs.git gitlab-rake gitlab-rails cfg.packages.gitlab-shell ]; + + # Redis is required for the sidekiq queue runner. + services.redis.enable = mkDefault true; + # We use postgres as the main data store. + services.postgresql.enable = mkDefault true; + # Use postfix to send out mails. + services.postfix.enable = mkDefault true; + + users.users = [ + { name = cfg.user; + group = cfg.group; + home = "${cfg.statePath}/home"; + shell = "${pkgs.bash}/bin/bash"; + uid = config.ids.uids.gitlab; + } + ]; + + users.groups = [ + { name = cfg.group; + gid = config.ids.gids.gitlab; + } + ]; + + systemd.tmpfiles.rules = [ + "d /run/gitlab 0755 ${cfg.user} ${cfg.group} -" + "d ${gitlabEnv.HOME} 0750 ${cfg.user} ${cfg.group} -" + "d ${cfg.backupPath} 0750 ${cfg.user} ${cfg.group} -" + "d ${cfg.statePath}/builds 0750 ${cfg.user} ${cfg.group} -" + "d ${cfg.statePath}/config 0750 ${cfg.user} ${cfg.group} -" + "d ${cfg.statePath}/db 0750 ${cfg.user} ${cfg.group} -" + "d ${cfg.statePath}/log 0750 ${cfg.user} ${cfg.group} -" + "d ${cfg.statePath}/repositories 2770 ${cfg.user} ${cfg.group} -" + "d ${cfg.statePath}/shell 0750 ${cfg.user} ${cfg.group} -" + "d ${cfg.statePath}/tmp/pids 0750 ${cfg.user} ${cfg.group} -" + "d ${cfg.statePath}/tmp/sockets 0750 ${cfg.user} ${cfg.group} -" + "d ${cfg.statePath}/uploads 0700 ${cfg.user} ${cfg.group} -" + "d ${cfg.statePath}/custom_hooks/pre-receive.d 0700 ${cfg.user} ${cfg.group} -" + "d ${cfg.statePath}/custom_hooks/post-receive.d 0700 ${cfg.user} ${cfg.group} -" + "d ${cfg.statePath}/custom_hooks/update.d 0700 ${cfg.user} ${cfg.group} -" + "d ${gitlabConfig.production.shared.path}/artifacts 0750 ${cfg.user} ${cfg.group} -" + "d ${gitlabConfig.production.shared.path}/lfs-objects 0750 ${cfg.user} ${cfg.group} -" + "d ${gitlabConfig.production.shared.path}/pages 0750 ${cfg.user} ${cfg.group} -" + ]; + + systemd.services.gitlab-sidekiq = { + after = [ "network.target" "redis.service" "gitlab.service" ]; + wantedBy = [ "multi-user.target" ]; + environment = gitlabEnv; + path = with pkgs; [ + config.services.postgresql.package + gitAndTools.git + ruby + openssh + nodejs + gnupg + ]; + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + TimeoutSec = "infinity"; + Restart = "on-failure"; + WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab"; + ExecStart="${cfg.packages.gitlab.rubyEnv}/bin/sidekiq -C \"${cfg.packages.gitlab}/share/gitlab/config/sidekiq_queues.yml\" -e production -P ${cfg.statePath}/tmp/sidekiq.pid"; + }; + }; + + systemd.services.gitaly = { + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + path = with pkgs; [ + openssh + procps # See https://gitlab.com/gitlab-org/gitaly/issues/1562 + gitAndTools.git + cfg.packages.gitaly.rubyEnv + cfg.packages.gitaly.rubyEnv.wrappedRuby + gzip + bzip2 + ]; + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + TimeoutSec = "infinity"; + Restart = "on-failure"; + WorkingDirectory = gitlabEnv.HOME; + ExecStart = "${cfg.packages.gitaly}/bin/gitaly ${gitalyToml}"; + }; + }; + + systemd.services.gitlab-workhorse = { + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + path = with pkgs; [ + gitAndTools.git + gnutar + gzip + openssh + gitlab-workhorse + ]; + serviceConfig = { + PermissionsStartOnly = true; # preStart must be run as root + Type = "simple"; + User = cfg.user; + Group = cfg.group; + TimeoutSec = "infinity"; + Restart = "on-failure"; + WorkingDirectory = gitlabEnv.HOME; + ExecStart = + "${cfg.packages.gitlab-workhorse}/bin/gitlab-workhorse " + + "-listenUmask 0 " + + "-listenNetwork unix " + + "-listenAddr /run/gitlab/gitlab-workhorse.socket " + + "-authSocket ${gitlabSocket} " + + "-documentRoot ${cfg.packages.gitlab}/share/gitlab/public " + + "-secretPath ${cfg.statePath}/.gitlab_workhorse_secret"; + }; + }; + + systemd.services.gitlab = { + after = [ "gitlab-workhorse.service" "gitaly.service" "network.target" "postgresql.service" "redis.service" ]; + requires = [ "gitlab-sidekiq.service" ]; + wantedBy = [ "multi-user.target" ]; + environment = gitlabEnv; + path = with pkgs; [ + config.services.postgresql.package + gitAndTools.git + openssh + nodejs + procps + gnupg + ]; + preStart = '' + cp -rf ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db + rm -rf ${cfg.statePath}/config + mkdir ${cfg.statePath}/config + if [ -e ${cfg.statePath}/lib ]; then + rm ${cfg.statePath}/lib + fi + + ln -sf ${cfg.packages.gitlab}/share/gitlab/lib ${cfg.statePath}/lib + [ -L /run/gitlab/config ] || ln -sf ${cfg.statePath}/config /run/gitlab/config + [ -L /run/gitlab/log ] || ln -sf ${cfg.statePath}/log /run/gitlab/log + [ -L /run/gitlab/tmp ] || ln -sf ${cfg.statePath}/tmp /run/gitlab/tmp + [ -L /run/gitlab/uploads ] || ln -sf ${cfg.statePath}/uploads /run/gitlab/uploads + cp ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION + cp -rf ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config + ln -sf ${extraGitlabRb} ${cfg.statePath}/config/initializers/extra-gitlab.rb + ${optionalString cfg.smtp.enable '' + ln -sf ${smtpSettings} ${cfg.statePath}/config/initializers/smtp_settings.rb + ''} + ${pkgs.openssl}/bin/openssl rand -hex 32 > ${cfg.statePath}/config/gitlab_shell_secret + + # JSON is a subset of YAML + ln -sf ${pkgs.writeText "gitlab.yml" (builtins.toJSON gitlabConfig)} ${cfg.statePath}/config/gitlab.yml + ln -sf ${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} ${cfg.statePath}/config/database.yml + ln -sf ${pkgs.writeText "secrets.yml" (builtins.toJSON secretsConfig)} ${cfg.statePath}/config/secrets.yml + ln -sf ${./defaultUnicornConfig.rb} ${cfg.statePath}/config/unicorn.rb + + # Install the shell required to push repositories + ln -sf ${pkgs.writeText "config.yml" (builtins.toJSON gitlabShellConfig)} /run/gitlab/shell-config.yml + [ -L ${cfg.statePath}/shell/hooks ] || ln -sf ${cfg.packages.gitlab-shell}/hooks ${cfg.statePath}/shell/hooks + ${cfg.packages.gitlab-shell}/bin/install + + chown -R ${cfg.user}:${cfg.group} ${cfg.statePath}/ + chmod -R ug+rwX,o-rwx+X ${cfg.statePath}/ + chown -R ${cfg.user}:${cfg.group} /run/gitlab + + if ! test -e "${cfg.statePath}/db-created"; then + if [ "${cfg.databaseHost}" = "127.0.0.1" ]; then + ${pkgs.sudo}/bin/sudo -u ${pgSuperUser} psql postgres -c "CREATE ROLE ${cfg.databaseUsername} WITH LOGIN NOCREATEDB NOCREATEROLE ENCRYPTED PASSWORD '${cfg.databasePassword}'" + ${pkgs.sudo}/bin/sudo -u ${pgSuperUser} ${config.services.postgresql.package}/bin/createdb --owner ${cfg.databaseUsername} ${cfg.databaseName} + + # enable required pg_trgm extension for gitlab + ${pkgs.sudo}/bin/sudo -u ${pgSuperUser} psql ${cfg.databaseName} -c "CREATE EXTENSION IF NOT EXISTS pg_trgm" + fi + + ${pkgs.sudo}/bin/sudo -u ${cfg.user} -H ${gitlab-rake}/bin/gitlab-rake db:schema:load + + touch "${cfg.statePath}/db-created" + fi + + # Always do the db migrations just to be sure the database is up-to-date + ${pkgs.sudo}/bin/sudo -u ${cfg.user} -H ${gitlab-rake}/bin/gitlab-rake db:migrate + + if ! test -e "${cfg.statePath}/db-seeded"; then + ${pkgs.sudo}/bin/sudo -u ${cfg.user} ${gitlab-rake}/bin/gitlab-rake db:seed_fu \ + GITLAB_ROOT_PASSWORD='${cfg.initialRootPassword}' GITLAB_ROOT_EMAIL='${cfg.initialRootEmail}' + touch "${cfg.statePath}/db-seeded" + fi + + # The gitlab:shell:create_hooks task seems broken for fixing links + # so we instead delete all the hooks and create them anew + rm -f ${cfg.statePath}/repositories/**/*.git/hooks + ${pkgs.sudo}/bin/sudo -u ${cfg.user} -H ${gitlab-rake}/bin/gitlab-rake gitlab:shell:create_hooks + + ${pkgs.sudo}/bin/sudo -u ${cfg.user} -H ${pkgs.git}/bin/git config --global core.autocrlf "input" + + # Change permissions in the last step because some of the + # intermediary scripts like to create directories as root. + chmod -R u+rwX,go-rwx+X ${gitlabEnv.HOME} + chmod -R ug+rwX,o-rwx ${cfg.statePath}/repositories + chmod -R ug-s ${cfg.statePath}/repositories + find ${cfg.statePath}/repositories -type d -print0 | xargs -0 chmod g+s + ''; + + serviceConfig = { + PermissionsStartOnly = true; # preStart must be run as root + Type = "simple"; + User = cfg.user; + Group = cfg.group; + TimeoutSec = "infinity"; + Restart = "on-failure"; + WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab"; + ExecStart = "${cfg.packages.gitlab.rubyEnv}/bin/unicorn -c ${cfg.statePath}/config/unicorn.rb -E production"; + }; + + }; + + }; + + meta.doc = ./gitlab.xml; + +} diff --git a/nixpkgs/nixos/modules/services/misc/gitlab.xml b/nixpkgs/nixos/modules/services/misc/gitlab.xml new file mode 100644 index 000000000000..ab99d7bd3a60 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/gitlab.xml @@ -0,0 +1,150 @@ +<chapter xmlns="http://docbook.org/ns/docbook" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:xi="http://www.w3.org/2001/XInclude" + version="5.0" + xml:id="module-services-gitlab"> + <title>Gitlab</title> + <para> + Gitlab is a feature-rich git hosting service. + </para> + <section xml:id="module-services-gitlab-prerequisites"> + <title>Prerequisites</title> + + <para> + The gitlab service exposes only an Unix socket at + <literal>/run/gitlab/gitlab-workhorse.socket</literal>. You need to + configure a webserver to proxy HTTP requests to the socket. + </para> + + <para> + For instance, the following configuration could be used to use nginx as + frontend proxy: +<programlisting> +<link linkend="opt-services.nginx.enable">services.nginx</link> = { + <link linkend="opt-services.nginx.enable">enable</link> = true; + <link linkend="opt-services.nginx.recommendedGzipSettings">recommendedGzipSettings</link> = true; + <link linkend="opt-services.nginx.recommendedOptimisation">recommendedOptimisation</link> = true; + <link linkend="opt-services.nginx.recommendedProxySettings">recommendedProxySettings</link> = true; + <link linkend="opt-services.nginx.recommendedTlsSettings">recommendedTlsSettings</link> = true; + <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link>."git.example.com" = { + <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true; + <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true; + <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.proxyPass">locations."/".proxyPass</link> = "http://unix:/run/gitlab/gitlab-workhorse.socket"; + }; +}; +</programlisting> + </para> + </section> + <section xml:id="module-services-gitlab-configuring"> + <title>Configuring</title> + + <para> + Gitlab depends on both PostgreSQL and Redis and will automatically enable + both services. In the case of PostgreSQL, a database and a role will be + created. + </para> + + <para> + The default state dir is <literal>/var/gitlab/state</literal>. This is where + all data like the repositories and uploads will be stored. + </para> + + <para> + A basic configuration with some custom settings could look like this: +<programlisting> +services.gitlab = { + <link linkend="opt-services.gitlab.enable">enable</link> = true; + <link linkend="opt-services.gitlab.databasePassword">databasePassword</link> = "eXaMpl3"; + <link linkend="opt-services.gitlab.initialRootPassword">initialRootPassword</link> = "UseNixOS!"; + <link linkend="opt-services.gitlab.https">https</link> = true; + <link linkend="opt-services.gitlab.host">host</link> = "git.example.com"; + <link linkend="opt-services.gitlab.port">port</link> = 443; + <link linkend="opt-services.gitlab.user">user</link> = "git"; + <link linkend="opt-services.gitlab.group">group</link> = "git"; + smtp = { + <link linkend="opt-services.gitlab.smtp.enable">enable</link> = true; + <link linkend="opt-services.gitlab.smtp.address">address</link> = "localhost"; + <link linkend="opt-services.gitlab.smtp.port">port</link> = 25; + }; + secrets = { + <link linkend="opt-services.gitlab.secrets.db">db</link> = "uPgq1gtwwHiatiuE0YHqbGa5lEIXH7fMsvuTNgdzJi8P0Dg12gibTzBQbq5LT7PNzcc3BP9P1snHVnduqtGF43PgrQtU7XL93ts6gqe9CBNhjtaqUwutQUDkygP5NrV6"; + <link linkend="opt-services.gitlab.secrets.secret">secret</link> = "devzJ0Tz0POiDBlrpWmcsjjrLaltyiAdS8TtgT9YNBOoUcDsfppiY3IXZjMVtKgXrFImIennFGOpPN8IkP8ATXpRgDD5rxVnKuTTwYQaci2NtaV1XxOQGjdIE50VGsR3"; + <link linkend="opt-services.gitlab.secrets.otp">otp</link> = "e1GATJVuS2sUh7jxiPzZPre4qtzGGaS22FR50Xs1TerRVdgI3CBVUi5XYtQ38W4xFeS4mDqi5cQjExE838iViSzCdcG19XSL6qNsfokQP9JugwiftmhmCadtsnHErBMI"; + <link linkend="opt-services.gitlab.secrets.jws">jws</link> = '' + -----BEGIN RSA PRIVATE KEY----- + MIIEpAIBAAKCAQEArrtx4oHKwXoqUbMNqnHgAklnnuDon3XG5LJB35yPsXKv/8GK + ke92wkI+s1Xkvsp8tg9BIY/7c6YK4SR07EWL+dB5qwctsWR2Q8z+/BKmTx9D99pm + hnsjuNIXTF7BXrx3RX6BxZpH5Vzzh9nCwWKT/JCFqtwH7afNGGL7aMf+hdaiUg/Q + SD05yRObioiO4iXDolsJOhrnbZvlzVHl1ZYxFJv0H6/Snc0BBA9Fl/3uj6ANpbjP + eXF1SnJCqT87bj46r5NdVauzaRxAsIfqHroHK4UZ98X5LjGQFGvSqTvyjPBS4I1i + s7VJU28ObuutHxIxSlH0ibn4HZqWmKWlTS652wIDAQABAoIBAGtPcUTTw2sJlR3x + 4k2wfAvLexkHNbZhBdKEa5JiO5mWPuLKwUiZEY2CU7Gd6csG3oqNWcm7/IjtC7dz + xV8p4yp8T4yq7vQIJ93B80NqTLtBD2QTvG2RCMJEPMzJUObWxkVmyVpLQyZo7KOd + KE/OM+aj94OUeEYLjRkSCScz1Gvq/qFG/nAy7KPCmN9JDHuhX26WHo2Rr1OnPNT/ + 7diph0bB9F3b8gjjNTqXDrpdAqVOgR/PsjEBz6DMY+bdyMIn87q2yfmMexxRofN6 + LulpzSaa6Yup8N8H6PzVO6KAkQuf1aQRj0sMwGk1IZEnj6I0KbuHIZkw21Nc6sf2 + ESFySDECgYEA1PnCNn5tmLnwe62Ttmrzl20zIS3Me1gUVJ1NTfr6+ai0I9iMYU21 + 5czuAjJPm9JKQF2vY8UAaCj2ZoObtHa/anb3xsCd8NXoM3iJq5JDoXI1ldz3Y+ad + U/bZUg1DLRvAniTuXmw9iOTwTwPxlDIGq5k+wG2Xmi1lk7zH8ezr9BMCgYEA0gfk + EhgcmPH8Z5cU3YYwOdt6HSJOM0OyN4k/5gnkv+HYVoJTj02gkrJmLr+mi1ugKj46 + 7huYO9TVnrKP21tmbaSv1dp5hS3letVRIxSloEtVGXmmdvJvBRzDWos+G+KcvADi + fFCz6w8v9NmO40CB7y/3SxTmSiSxDQeoi9LhDBkCgYEAsPgMWm25sfOnkY2NNUIv + wT8bAlHlHQT2d8zx5H9NttBpR3P0ShJhuF8N0sNthSQ7ULrIN5YGHYcUH+DyLAWU + TuomP3/kfa+xL7vUYb269tdJEYs4AkoppxBySoz8qenqpz422D0G8M6TpIS5Y5Qi + GMrQ6uLl21YnlpiCaFOfSQMCgYEAmZxj1kgEQmhZrnn1LL/D7czz1vMMNrpAUhXz + wg9iWmSXkU3oR1sDIceQrIhHCo2M6thwyU0tXjUft93pEQocM/zLDaGoVxtmRxxV + J08mg8IVD3jFoyFUyWxsBIDqgAKRl38eJsXvkO+ep3mm49Z+Ma3nM+apN3j2dQ0w + 3HLzXaECgYBFLMEAboVFwi5+MZjGvqtpg2PVTisfuJy2eYnPwHs+AXUgi/xRNFjI + YHEa7UBPb5TEPSzWImQpETi2P5ywcUYL1EbN/nqPWmjFnat8wVmJtV4sUpJhubF4 + Vqm9LxIWc1uQ1q1HDCejRIxIN3aSH+wgRS3Kcj8kCTIoXd1aERb04g== + -----END RSA PRIVATE KEY----- + ''; + }; + <link linkend="opt-services.gitlab.extraConfig">extraConfig</link> = { + gitlab = { + email_from = "gitlab-no-reply@example.com"; + email_display_name = "Example GitLab"; + email_reply_to = "gitlab-no-reply@example.com"; + default_projects_features = { builds = false; }; + }; + }; +}; +</programlisting> + </para> + + <para> + If you're setting up a new Gitlab instance, generate new secrets. You for + instance use <literal>tr -dc A-Za-z0-9 < /dev/urandom | head -c + 128</literal> to generate a new secret. Gitlab encrypts sensitive data + stored in the database. If you're restoring an existing Gitlab instance, you + must specify the secrets secret from <literal>config/secrets.yml</literal> + located in your Gitlab state folder. + </para> + + <para> + Refer to <xref linkend="ch-options" /> for all available configuration + options for the + <link linkend="opt-services.gitlab.enable">services.gitlab</link> module. + </para> + </section> + <section xml:id="module-services-gitlab-maintenance"> + <title>Maintenance</title> + + <para> + You can run Gitlab's rake tasks with <literal>gitlab-rake</literal> which + will be available on the system when gitlab is enabled. You will have to run + the command as the user that you configured to run gitlab with. + </para> + + <para> + For example, to backup a Gitlab instance: +<programlisting> +$ sudo -u git -H gitlab-rake gitlab:backup:create +</programlisting> + A list of all availabe rake tasks can be obtained by running: +<programlisting> +$ sudo -u git -H gitlab-rake -T +</programlisting> + </para> + </section> +</chapter> diff --git a/nixpkgs/nixos/modules/services/misc/gitolite.nix b/nixpkgs/nixos/modules/services/misc/gitolite.nix new file mode 100644 index 000000000000..c7f2a168f8ab --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/gitolite.nix @@ -0,0 +1,220 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.gitolite; + # Use writeTextDir to not leak Nix store hash into file name + pubkeyFile = (pkgs.writeTextDir "gitolite-admin.pub" cfg.adminPubkey) + "/gitolite-admin.pub"; + hooks = lib.concatMapStrings (hook: "${hook} ") cfg.commonHooks; +in +{ + options = { + services.gitolite = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Enable gitolite management under the + <literal>gitolite</literal> user. After + switching to a configuration with Gitolite enabled, you can + then run <literal>git clone + gitolite@host:gitolite-admin.git</literal> to manage it further. + ''; + }; + + dataDir = mkOption { + type = types.str; + default = "/var/lib/gitolite"; + description = '' + Gitolite home directory (used to store all the repositories). + ''; + }; + + adminPubkey = mkOption { + type = types.str; + description = '' + Initial administrative public key for Gitolite. This should + be an SSH Public Key. Note that this key will only be used + once, upon the first initialization of the Gitolite user. + The key string cannot have any line breaks in it. + ''; + }; + + enableGitAnnex = mkOption { + type = types.bool; + default = false; + description = '' + Enable git-annex support. Uses the <literal>extraGitoliteRc</literal> option + to apply the necessary configuration. + ''; + }; + + commonHooks = mkOption { + type = types.listOf types.path; + default = []; + description = '' + A list of custom git hooks that get copied to <literal>~/.gitolite/hooks/common</literal>. + ''; + }; + + extraGitoliteRc = mkOption { + type = types.lines; + default = ""; + example = literalExample '' + $RC{UMASK} = 0027; + $RC{SITE_INFO} = 'This is our private repository host'; + push( @{$RC{ENABLE}}, 'Kindergarten' ); # enable the command/feature + @{$RC{ENABLE}} = grep { $_ ne 'desc' } @{$RC{ENABLE}}; # disable the command/feature + ''; + description = '' + Extra configuration to append to the default <literal>~/.gitolite.rc</literal>. + + This should be Perl code that modifies the <literal>%RC</literal> + configuration variable. The default <literal>~/.gitolite.rc</literal> + content is generated by invoking <literal>gitolite print-default-rc</literal>, + and extra configuration from this option is appended to it. The result + is placed to Nix store, and the <literal>~/.gitolite.rc</literal> file + becomes a symlink to it. + + If you already have a customized (or otherwise changed) + <literal>~/.gitolite.rc</literal> file, NixOS will refuse to replace + it with a symlink, and the `gitolite-init` initialization service + will fail. In this situation, in order to use this option, you + will need to take any customizations you may have in + <literal>~/.gitolite.rc</literal>, convert them to appropriate Perl + statements, add them to this option, and remove the file. + + See also the <literal>enableGitAnnex</literal> option. + ''; + }; + + user = mkOption { + type = types.str; + default = "gitolite"; + description = '' + Gitolite user account. This is the username of the gitolite endpoint. + ''; + }; + + group = mkOption { + type = types.str; + default = "gitolite"; + description = '' + Primary group of the Gitolite user account. + ''; + }; + }; + }; + + config = mkIf cfg.enable ( + let + manageGitoliteRc = cfg.extraGitoliteRc != ""; + rcDir = pkgs.runCommand "gitolite-rc" { preferLocalBuild = true; } rcDirScript; + rcDirScript = + '' + mkdir "$out" + export HOME=temp-home + mkdir -p "$HOME/.gitolite/logs" # gitolite can't run without it + '${pkgs.gitolite}'/bin/gitolite print-default-rc >>"$out/gitolite.rc.default" + cat <<END >>"$out/gitolite.rc" + # This file is managed by NixOS. + # Use services.gitolite options to control it. + + END + cat "$out/gitolite.rc.default" >>"$out/gitolite.rc" + '' + + optionalString (cfg.extraGitoliteRc != "") '' + echo -n ${escapeShellArg '' + + # Added by NixOS: + ${removeSuffix "\n" cfg.extraGitoliteRc} + + # per perl rules, this should be the last line in such a file: + 1; + ''} >>"$out/gitolite.rc" + ''; + in { + services.gitolite.extraGitoliteRc = optionalString cfg.enableGitAnnex '' + # Enable git-annex support: + push( @{$RC{ENABLE}}, 'git-annex-shell ua'); + ''; + + users.users.${cfg.user} = { + description = "Gitolite user"; + home = cfg.dataDir; + createHome = true; + uid = config.ids.uids.gitolite; + group = cfg.group; + useDefaultShell = true; + }; + users.groups."${cfg.group}".gid = config.ids.gids.gitolite; + + systemd.services."gitolite-init" = { + description = "Gitolite initialization"; + wantedBy = [ "multi-user.target" ]; + unitConfig.RequiresMountsFor = cfg.dataDir; + + serviceConfig.User = "${cfg.user}"; + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + + path = [ pkgs.gitolite pkgs.git pkgs.perl pkgs.bash pkgs.diffutils config.programs.ssh.package ]; + script = + let + rcSetupScriptIfCustomFile = + if manageGitoliteRc then '' + cat <<END + <3>ERROR: NixOS can't apply declarative configuration + <3>to your .gitolite.rc file, because it seems to be + <3>already customized manually. + <3>See the services.gitolite.extraGitoliteRc option + <3>in "man configuration.nix" for more information. + END + # Not sure if the line below addresses the issue directly or just + # adds a delay, but without it our error message often doesn't + # show up in `systemctl status gitolite-init`. + journalctl --flush + exit 1 + '' else '' + : + ''; + rcSetupScriptIfDefaultFileOrStoreSymlink = + if manageGitoliteRc then '' + ln -sf "${rcDir}/gitolite.rc" "$GITOLITE_RC" + '' else '' + [[ -L "$GITOLITE_RC" ]] && rm -f "$GITOLITE_RC" + ''; + in + '' + cd ${cfg.dataDir} + mkdir -p .gitolite/logs + + GITOLITE_RC=.gitolite.rc + GITOLITE_RC_DEFAULT=${rcDir}/gitolite.rc.default + if ( [[ ! -e "$GITOLITE_RC" ]] && [[ ! -L "$GITOLITE_RC" ]] ) || + ( [[ -f "$GITOLITE_RC" ]] && diff -q "$GITOLITE_RC" "$GITOLITE_RC_DEFAULT" >/dev/null ) || + ( [[ -L "$GITOLITE_RC" ]] && [[ "$(readlink "$GITOLITE_RC")" =~ ^/nix/store/ ]] ) + then + '' + rcSetupScriptIfDefaultFileOrStoreSymlink + + '' + else + '' + rcSetupScriptIfCustomFile + + '' + fi + + if [ ! -d repositories ]; then + gitolite setup -pk ${pubkeyFile} + fi + if [ -n "${hooks}" ]; then + cp -f ${hooks} .gitolite/hooks/common/ + chmod +x .gitolite/hooks/common/* + fi + gitolite setup # Upgrade if needed + ''; + }; + + environment.systemPackages = [ pkgs.gitolite pkgs.git ] + ++ optional cfg.enableGitAnnex pkgs.gitAndTools.git-annex; + }); +} diff --git a/nixpkgs/nixos/modules/services/misc/gitweb.nix b/nixpkgs/nixos/modules/services/misc/gitweb.nix new file mode 100644 index 000000000000..ca21366b7796 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/gitweb.nix @@ -0,0 +1,59 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.gitweb; + +in +{ + + options.services.gitweb = { + + projectroot = mkOption { + default = "/srv/git"; + type = types.path; + description = '' + Path to git projects (bare repositories) that should be served by + gitweb. Must not end with a slash. + ''; + }; + + extraConfig = mkOption { + default = ""; + type = types.lines; + description = '' + Verbatim configuration text appended to the generated gitweb.conf file. + ''; + example = '' + $feature{'highlight'}{'default'} = [1]; + $feature{'ctags'}{'default'} = [1]; + $feature{'avatar'}{'default'} = ['gravatar']; + ''; + }; + + gitwebTheme = mkOption { + default = false; + type = types.bool; + description = '' + Use an alternative theme for gitweb, strongly inspired by GitHub. + ''; + }; + + gitwebConfigFile = mkOption { + default = pkgs.writeText "gitweb.conf" '' + # path to git projects (<project>.git) + $projectroot = "${cfg.projectroot}"; + $highlight_bin = "${pkgs.highlight}/bin/highlight"; + ${cfg.extraConfig} + ''; + type = types.path; + readOnly = true; + internal = true; + }; + + }; + + meta.maintainers = with maintainers; [ gnidorah ]; + +} diff --git a/nixpkgs/nixos/modules/services/misc/gogs.nix b/nixpkgs/nixos/modules/services/misc/gogs.nix new file mode 100644 index 000000000000..ee99967c261b --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/gogs.nix @@ -0,0 +1,279 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.gogs; + configFile = pkgs.writeText "app.ini" '' + APP_NAME = ${cfg.appName} + RUN_USER = ${cfg.user} + RUN_MODE = prod + + [database] + DB_TYPE = ${cfg.database.type} + HOST = ${cfg.database.host}:${toString cfg.database.port} + NAME = ${cfg.database.name} + USER = ${cfg.database.user} + PASSWD = #dbpass# + PATH = ${cfg.database.path} + + [repository] + ROOT = ${cfg.repositoryRoot} + + [server] + DOMAIN = ${cfg.domain} + HTTP_ADDR = ${cfg.httpAddress} + HTTP_PORT = ${toString cfg.httpPort} + ROOT_URL = ${cfg.rootUrl} + STATIC_ROOT_PATH = ${cfg.staticRootPath} + + [session] + COOKIE_NAME = session + COOKIE_SECURE = ${boolToString cfg.cookieSecure} + + [security] + SECRET_KEY = #secretkey# + INSTALL_LOCK = true + + [log] + ROOT_PATH = ${cfg.stateDir}/log + + ${cfg.extraConfig} + ''; +in + +{ + options = { + services.gogs = { + enable = mkOption { + default = false; + type = types.bool; + description = "Enable Go Git Service."; + }; + + useWizard = mkOption { + default = false; + type = types.bool; + description = "Do not generate a configuration and use Gogs' installation wizard instead. The first registered user will be administrator."; + }; + + stateDir = mkOption { + default = "/var/lib/gogs"; + type = types.str; + description = "Gogs data directory."; + }; + + user = mkOption { + type = types.str; + default = "gogs"; + description = "User account under which Gogs runs."; + }; + + group = mkOption { + type = types.str; + default = "gogs"; + description = "Group account under which Gogs runs."; + }; + + database = { + type = mkOption { + type = types.enum [ "sqlite3" "mysql" "postgres" ]; + example = "mysql"; + default = "sqlite3"; + description = "Database engine to use."; + }; + + host = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "Database host address."; + }; + + port = mkOption { + type = types.int; + default = 3306; + description = "Database host port."; + }; + + name = mkOption { + type = types.str; + default = "gogs"; + description = "Database name."; + }; + + user = mkOption { + type = types.str; + default = "gogs"; + description = "Database user."; + }; + + password = mkOption { + type = types.str; + default = ""; + description = '' + The password corresponding to <option>database.user</option>. + Warning: this is stored in cleartext in the Nix store! + Use <option>database.passwordFile</option> instead. + ''; + }; + + passwordFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/keys/gogs-dbpassword"; + description = '' + A file containing the password corresponding to + <option>database.user</option>. + ''; + }; + + path = mkOption { + type = types.str; + default = "${cfg.stateDir}/data/gogs.db"; + description = "Path to the sqlite3 database file."; + }; + }; + + appName = mkOption { + type = types.str; + default = "Gogs: Go Git Service"; + description = "Application name."; + }; + + repositoryRoot = mkOption { + type = types.str; + default = "${cfg.stateDir}/repositories"; + description = "Path to the git repositories."; + }; + + domain = mkOption { + type = types.str; + default = "localhost"; + description = "Domain name of your server."; + }; + + rootUrl = mkOption { + type = types.str; + default = "http://localhost:3000/"; + description = "Full public URL of Gogs server."; + }; + + httpAddress = mkOption { + type = types.str; + default = "0.0.0.0"; + description = "HTTP listen address."; + }; + + httpPort = mkOption { + type = types.int; + default = 3000; + description = "HTTP listen port."; + }; + + cookieSecure = mkOption { + type = types.bool; + default = false; + description = '' + Marks session cookies as "secure" as a hint for browsers to only send + them via HTTPS. This option is recommend, if Gogs is being served over HTTPS. + ''; + }; + + staticRootPath = mkOption { + type = types.str; + default = "${pkgs.gogs.data}"; + example = "/var/lib/gogs/data"; + description = "Upper level of template and static files path."; + }; + + extraConfig = mkOption { + type = types.str; + default = ""; + description = "Configuration lines appended to the generated Gogs configuration file."; + }; + }; + }; + + config = mkIf cfg.enable { + + systemd.services.gogs = { + description = "Gogs (Go Git Service)"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pkgs.gogs.bin ]; + + preStart = let + runConfig = "${cfg.stateDir}/custom/conf/app.ini"; + secretKey = "${cfg.stateDir}/custom/conf/secret_key"; + in '' + mkdir -p ${cfg.stateDir} + + # copy custom configuration and generate a random secret key if needed + ${optionalString (cfg.useWizard == false) '' + mkdir -p ${cfg.stateDir}/custom/conf + cp -f ${configFile} ${runConfig} + + if [ ! -e ${secretKey} ]; then + head -c 16 /dev/urandom | base64 > ${secretKey} + fi + + KEY=$(head -n1 ${secretKey}) + DBPASS=$(head -n1 ${cfg.database.passwordFile}) + sed -e "s,#secretkey#,$KEY,g" \ + -e "s,#dbpass#,$DBPASS,g" \ + -i ${runConfig} + chmod 440 ${runConfig} ${secretKey} + ''} + + mkdir -p ${cfg.repositoryRoot} + # update all hooks' binary paths + HOOKS=$(find ${cfg.repositoryRoot} -mindepth 4 -maxdepth 4 -type f -wholename "*git/hooks/*") + if [ "$HOOKS" ] + then + sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gogs,${pkgs.gogs.bin}/bin/gogs,g' $HOOKS + sed -ri 's,/nix/store/[a-z0-9.-]+/bin/env,${pkgs.coreutils}/bin/env,g' $HOOKS + sed -ri 's,/nix/store/[a-z0-9.-]+/bin/bash,${pkgs.bash}/bin/bash,g' $HOOKS + sed -ri 's,/nix/store/[a-z0-9.-]+/bin/perl,${pkgs.perl}/bin/perl,g' $HOOKS + fi + ''; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + WorkingDirectory = cfg.stateDir; + ExecStart = "${pkgs.gogs.bin}/bin/gogs web"; + Restart = "always"; + }; + + environment = { + USER = cfg.user; + HOME = cfg.stateDir; + GOGS_WORK_DIR = cfg.stateDir; + }; + }; + + users = mkIf (cfg.user == "gogs") { + users.gogs = { + description = "Go Git Service"; + uid = config.ids.uids.gogs; + group = "gogs"; + home = cfg.stateDir; + createHome = true; + shell = pkgs.bash; + }; + groups.gogs.gid = config.ids.gids.gogs; + }; + + warnings = optional (cfg.database.password != "") + ''config.services.gogs.database.password will be stored as plaintext + in the Nix store. Use database.passwordFile instead.''; + + # Create database passwordFile default when password is configured. + services.gogs.database.passwordFile = + (mkDefault (toString (pkgs.writeTextFile { + name = "gogs-database-password"; + text = cfg.database.password; + }))); + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/gollum.nix b/nixpkgs/nixos/modules/services/misc/gollum.nix new file mode 100644 index 000000000000..d1823bc6d4df --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/gollum.nix @@ -0,0 +1,113 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.gollum; +in + +{ + options.services.gollum = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable the Gollum service."; + }; + + address = mkOption { + type = types.str; + default = "0.0.0.0"; + description = "IP address on which the web server will listen."; + }; + + port = mkOption { + type = types.int; + default = 4567; + description = "Port on which the web server will run."; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = "Content of the configuration file"; + }; + + mathjax = mkOption { + type = types.bool; + default = false; + description = "Enable support for math rendering using MathJax"; + }; + + allowUploads = mkOption { + type = types.nullOr (types.enum [ "dir" "page" ]); + default = null; + description = "Enable uploads of external files"; + }; + + emoji = mkOption { + type = types.bool; + default = false; + description = "Parse and interpret emoji tags"; + }; + + branch = mkOption { + type = types.str; + default = "master"; + example = "develop"; + description = "Git branch to serve"; + }; + + stateDir = mkOption { + type = types.path; + default = "/var/lib/gollum"; + description = "Specifies the path of the repository directory. If it does not exist, Gollum will create it on startup."; + }; + + }; + + config = mkIf cfg.enable { + + users.users.gollum = { + group = config.users.users.gollum.name; + description = "Gollum user"; + createHome = false; + }; + + users.groups.gollum = { }; + + systemd.services.gollum = { + description = "Gollum wiki"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pkgs.git ]; + + preStart = let + userName = config.users.users.gollum.name; + groupName = config.users.groups.gollum.name; + in '' + # All of this is safe to be run on an existing repo + mkdir -p ${cfg.stateDir} + git init ${cfg.stateDir} + chmod 755 ${cfg.stateDir} + chown -R ${userName}:${groupName} ${cfg.stateDir} + ''; + + serviceConfig = { + User = config.users.users.gollum.name; + Group = config.users.groups.gollum.name; + PermissionsStartOnly = true; + ExecStart = '' + ${pkgs.gollum}/bin/gollum \ + --port ${toString cfg.port} \ + --host ${cfg.address} \ + --config ${builtins.toFile "gollum-config.rb" cfg.extraConfig} \ + --ref ${cfg.branch} \ + ${optionalString cfg.mathjax "--mathjax"} \ + ${optionalString cfg.emoji "--emoji"} \ + ${optionalString (cfg.allowUploads != null) "--allow-uploads ${cfg.allowUploads}"} \ + ${cfg.stateDir} + ''; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/gpsd.nix b/nixpkgs/nixos/modules/services/misc/gpsd.nix new file mode 100644 index 000000000000..3bfcb636a3c6 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/gpsd.nix @@ -0,0 +1,119 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + uid = config.ids.uids.gpsd; + gid = config.ids.gids.gpsd; + cfg = config.services.gpsd; + +in + +{ + + ###### interface + + options = { + + services.gpsd = { + + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable `gpsd', a GPS service daemon. + ''; + }; + + device = mkOption { + type = types.str; + default = "/dev/ttyUSB0"; + description = '' + A device may be a local serial device for GPS input, or a URL of the form: + <literal>[{dgpsip|ntrip}://][user:passwd@]host[:port][/stream]</literal> + in which case it specifies an input source for DGPS or ntrip data. + ''; + }; + + readonly = mkOption { + type = types.bool; + default = true; + description = '' + Whether to enable the broken-device-safety, otherwise + known as read-only mode. Some popular bluetooth and USB + receivers lock up or become totally inaccessible when + probed or reconfigured. This switch prevents gpsd from + writing to a receiver. This means that gpsd cannot + configure the receiver for optimal performance, but it + also means that gpsd cannot break the receiver. A better + solution would be for Bluetooth to not be so fragile. A + platform independent method to identify + serial-over-Bluetooth devices would also be nice. + ''; + }; + + nowait = mkOption { + type = types.bool; + default = false; + description = '' + don't wait for client connects to poll GPS + ''; + }; + + port = mkOption { + type = types.int; + default = 2947; + description = '' + The port where to listen for TCP connections. + ''; + }; + + debugLevel = mkOption { + type = types.int; + default = 0; + description = '' + The debugging level. + ''; + }; + + }; + + }; + + + ###### implementation + + config = mkIf cfg.enable { + + users.users = singleton + { name = "gpsd"; + inherit uid; + description = "gpsd daemon user"; + home = "/var/empty"; + }; + + users.groups = singleton + { name = "gpsd"; + inherit gid; + }; + + systemd.services.gpsd = { + description = "GPSD daemon"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + Type = "forking"; + ExecStart = '' + ${pkgs.gpsd}/sbin/gpsd -D "${toString cfg.debugLevel}" \ + -S "${toString cfg.port}" \ + ${optionalString cfg.readonly "-b"} \ + ${optionalString cfg.nowait "-n"} \ + "${cfg.device}" + ''; + }; + }; + + }; + +} diff --git a/nixpkgs/nixos/modules/services/misc/headphones.nix b/nixpkgs/nixos/modules/services/misc/headphones.nix new file mode 100644 index 000000000000..4a77045be28e --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/headphones.nix @@ -0,0 +1,87 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + name = "headphones"; + + cfg = config.services.headphones; + +in + +{ + + ###### interface + + options = { + services.headphones = { + enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable the headphones server."; + }; + dataDir = mkOption { + type = types.path; + default = "/var/lib/${name}"; + description = "Path where to store data files."; + }; + configFile = mkOption { + type = types.path; + default = "${cfg.dataDir}/config.ini"; + description = "Path to config file."; + }; + host = mkOption { + type = types.str; + default = "localhost"; + description = "Host to listen on."; + }; + port = mkOption { + type = types.ints.u16; + default = 8181; + description = "Port to bind to."; + }; + user = mkOption { + type = types.str; + default = name; + description = "User to run the service as"; + }; + group = mkOption { + type = types.str; + default = name; + description = "Group to run the service as"; + }; + }; + }; + + + ###### implementation + + config = mkIf cfg.enable { + + users.users = optionalAttrs (cfg.user == name) (singleton { + name = name; + uid = config.ids.uids.headphones; + group = cfg.group; + description = "headphones user"; + home = cfg.dataDir; + createHome = true; + }); + + users.groups = optionalAttrs (cfg.group == name) (singleton { + name = name; + gid = config.ids.gids.headphones; + }); + + systemd.services.headphones = { + description = "Headphones Server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + User = cfg.user; + Group = cfg.group; + ExecStart = "${pkgs.headphones}/bin/headphones --datadir ${cfg.dataDir} --config ${cfg.configFile} --host ${cfg.host} --port ${toString cfg.port}"; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/home-assistant.nix b/nixpkgs/nixos/modules/services/misc/home-assistant.nix new file mode 100644 index 000000000000..7f8d31bcf0b8 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/home-assistant.nix @@ -0,0 +1,258 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.home-assistant; + + # cfg.config != null can be assumed here + configJSON = pkgs.writeText "configuration.json" + (builtins.toJSON (if cfg.applyDefaultConfig then + (recursiveUpdate defaultConfig cfg.config) else cfg.config)); + configFile = pkgs.runCommand "configuration.yaml" { preferLocalBuild = true; } '' + ${pkgs.remarshal}/bin/json2yaml -i ${configJSON} -o $out + ''; + + lovelaceConfigJSON = pkgs.writeText "ui-lovelace.json" + (builtins.toJSON cfg.lovelaceConfig); + lovelaceConfigFile = pkgs.runCommand "ui-lovelace.yaml" { preferLocalBuild = true; } '' + ${pkgs.remarshal}/bin/json2yaml -i ${lovelaceConfigJSON} -o $out + ''; + + availableComponents = cfg.package.availableComponents; + + # Given component "parentConfig.platform", returns whether config.parentConfig + # is a list containing a set with set.platform == "platform". + # + # For example, the component sensor.luftdaten is used as follows: + # config.sensor = [ { + # platform = "luftdaten"; + # ... + # } ]; + # + # Beginning with 0.87 Home Assistant is migrating their components to the + # scheme "platform.subComponent", e.g. "hue.light" instead of "light.hue". + # See https://developers.home-assistant.io/blog/2019/02/19/the-great-migration.html. + # Hence, we also check whether we find an entry in the config when interpreting + # the first part of the path as the component. + useComponentPlatform = component: + let + path = splitString "." component; + # old: platform is the last part of path + parentConfig = attrByPath (init path) null cfg.config; + platform = last path; + # new: platform is the first part of the path + parentConfig' = attrByPath (tail path) null cfg.config; + platform' = head path; + in + (isList parentConfig && any (item: item.platform or null == platform) parentConfig) + || (isList parentConfig' && any (item: item.platform or null == platform') parentConfig'); + + # Returns whether component is used in config + useComponent = component: + hasAttrByPath (splitString "." component) cfg.config + || useComponentPlatform component; + + # List of components used in config + extraComponents = filter useComponent availableComponents; + + package = if (cfg.autoExtraComponents && cfg.config != null) + then (cfg.package.override { inherit extraComponents; }) + else cfg.package; + + # If you are changing this, please update the description in applyDefaultConfig + defaultConfig = { + homeassistant.time_zone = config.time.timeZone; + http.server_port = cfg.port; + } // optionalAttrs (cfg.lovelaceConfig != null) { + lovelace.mode = "yaml"; + }; + +in { + meta.maintainers = with maintainers; [ dotlambda ]; + + options.services.home-assistant = { + enable = mkEnableOption "Home Assistant"; + + configDir = mkOption { + default = "/var/lib/hass"; + type = types.path; + description = "The config directory, where your <filename>configuration.yaml</filename> is located."; + }; + + port = mkOption { + default = 8123; + type = types.int; + description = "The port on which to listen."; + }; + + applyDefaultConfig = mkOption { + default = true; + type = types.bool; + description = '' + Setting this option enables a few configuration options for HA based on NixOS configuration (such as time zone) to avoid having to manually specify configuration we already have. + </para> + <para> + Currently one side effect of enabling this is that the <literal>http</literal> component will be enabled. + </para> + <para> + This only takes effect if <literal>config != null</literal> in order to ensure that a manually managed <filename>configuration.yaml</filename> is not overwritten. + ''; + }; + + config = mkOption { + default = null; + type = with types; nullOr attrs; + example = literalExample '' + { + homeassistant = { + name = "Home"; + time_zone = "UTC"; + }; + frontend = { }; + http = { }; + feedreader.urls = [ "https://nixos.org/blogs.xml" ]; + } + ''; + description = '' + Your <filename>configuration.yaml</filename> as a Nix attribute set. + Beware that setting this option will delete your previous <filename>configuration.yaml</filename>. + ''; + }; + + configWritable = mkOption { + default = false; + type = types.bool; + description = '' + Whether to make <filename>configuration.yaml</filename> writable. + This only has an effect if <option>config</option> is set. + This will allow you to edit it from Home Assistant's web interface. + However, bear in mind that it will be overwritten at every start of the service. + ''; + }; + + lovelaceConfig = mkOption { + default = null; + type = with types; nullOr attrs; + # from https://www.home-assistant.io/lovelace/yaml-mode/ + example = literalExample '' + { + title = "My Awesome Home"; + views = [ { + title = "Example"; + cards = [ { + type = "markdown"; + title = "Lovelace"; + content = "Welcome to your **Lovelace UI**."; + } ]; + } ]; + } + ''; + description = '' + Your <filename>ui-lovelace.yaml</filename> as a Nix attribute set. + Setting this option will automatically add + <literal>lovelace.mode = "yaml";</literal> to your <option>config</option>. + Beware that setting this option will delete your previous <filename>ui-lovelace.yaml</filename> + ''; + }; + + lovelaceConfigWritable = mkOption { + default = false; + type = types.bool; + description = '' + Whether to make <filename>ui-lovelace.yaml</filename> writable. + This only has an effect if <option>lovelaceConfig</option> is set. + This will allow you to edit it from Home Assistant's web interface. + However, bear in mind that it will be overwritten at every start of the service. + ''; + }; + + package = mkOption { + default = pkgs.home-assistant; + defaultText = "pkgs.home-assistant"; + type = types.package; + example = literalExample '' + pkgs.home-assistant.override { + extraPackages = ps: with ps; [ colorlog ]; + } + ''; + description = '' + Home Assistant package to use. + Override <literal>extraPackages</literal> or <literal>extraComponents</literal> in order to add additional dependencies. + If you specify <option>config</option> and do not set <option>autoExtraComponents</option> + to <literal>false</literal>, overriding <literal>extraComponents</literal> will have no effect. + ''; + }; + + autoExtraComponents = mkOption { + default = true; + type = types.bool; + description = '' + If set to <literal>true</literal>, the components used in <literal>config</literal> + are set as the specified package's <literal>extraComponents</literal>. + This in turn adds all packaged dependencies to the derivation. + You might still see import errors in your log. + In this case, you will need to package the necessary dependencies yourself + or ask for someone else to package them. + If a dependency is packaged but not automatically added to this list, + you might need to specify it in <literal>extraPackages</literal>. + ''; + }; + + openFirewall = mkOption { + default = false; + type = types.bool; + description = "Whether to open the firewall for the specified port."; + }; + }; + + config = mkIf cfg.enable { + networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ]; + + systemd.services.home-assistant = { + description = "Home Assistant"; + after = [ "network.target" ]; + preStart = optionalString (cfg.config != null) (if cfg.configWritable then '' + cp --no-preserve=mode ${configFile} "${cfg.configDir}/configuration.yaml" + '' else '' + rm -f "${cfg.configDir}/configuration.yaml" + ln -s ${configFile} "${cfg.configDir}/configuration.yaml" + '') + optionalString (cfg.lovelaceConfig != null) (if cfg.lovelaceConfigWritable then '' + cp --no-preserve=mode ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml" + '' else '' + rm -f "${cfg.configDir}/ui-lovelace.yaml" + ln -s ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml" + ''); + serviceConfig = { + ExecStart = "${package}/bin/hass --config '${cfg.configDir}'"; + User = "hass"; + Group = "hass"; + Restart = "on-failure"; + ProtectSystem = "strict"; + ReadWritePaths = "${cfg.configDir}"; + KillSignal = "SIGINT"; + PrivateTmp = true; + RemoveIPC = true; + }; + path = [ + "/run/wrappers" # needed for ping + ]; + }; + + systemd.targets.home-assistant = rec { + description = "Home Assistant"; + wantedBy = [ "multi-user.target" ]; + wants = [ "home-assistant.service" ]; + after = wants; + }; + + users.users.hass = { + home = cfg.configDir; + createHome = true; + group = "hass"; + uid = config.ids.uids.hass; + }; + + users.groups.hass.gid = config.ids.gids.hass; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/ihaskell.nix b/nixpkgs/nixos/modules/services/misc/ihaskell.nix new file mode 100644 index 000000000000..11597706d0d1 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/ihaskell.nix @@ -0,0 +1,62 @@ +{ pkgs, lib, config, ... }: + +with lib; + +let + + cfg = config.services.ihaskell; + ihaskell = pkgs.ihaskell.override { + packages = self: cfg.extraPackages self; + }; + +in + +{ + options = { + services.ihaskell = { + enable = mkOption { + default = false; + description = "Autostart an IHaskell notebook service."; + }; + + extraPackages = mkOption { + default = self: []; + example = literalExample '' + haskellPackages: [ + haskellPackages.wreq + haskellPackages.lens + ] + ''; + description = '' + Extra packages available to ghc when running ihaskell. The + value must be a function which receives the attrset defined + in <varname>haskellPackages</varname> as the sole argument. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + + users.users.ihaskell = { + group = config.users.groups.ihaskell.name; + description = "IHaskell user"; + home = "/var/lib/ihaskell"; + createHome = true; + uid = config.ids.uids.ihaskell; + }; + + users.groups.ihaskell.gid = config.ids.gids.ihaskell; + + systemd.services.ihaskell = { + description = "IHaskell notebook instance"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + User = config.users.users.ihaskell.name; + Group = config.users.groups.ihaskell.name; + ExecStart = "${pkgs.runtimeShell} -c \"cd $HOME;${ihaskell}/bin/ihaskell-notebook\""; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/irkerd.nix b/nixpkgs/nixos/modules/services/misc/irkerd.nix new file mode 100644 index 000000000000..993d77ba424c --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/irkerd.nix @@ -0,0 +1,67 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.irkerd; + ports = [ 6659 ]; +in +{ + options.services.irkerd = { + enable = mkOption { + description = "Whether to enable irker, an IRC notification daemon."; + default = false; + type = types.bool; + }; + + openPorts = mkOption { + description = "Open ports in the firewall for irkerd"; + default = false; + type = types.bool; + }; + + listenAddress = mkOption { + default = "localhost"; + example = "0.0.0.0"; + type = types.str; + description = '' + Specifies the bind address on which the irker daemon listens. + The default is localhost. + + Irker authors strongly warn about the risks of running this on + a publicly accessible interface, so change this with caution. + ''; + }; + + nick = mkOption { + default = "irker"; + type = types.str; + description = "Nick to use for irker"; + }; + }; + + config = mkIf cfg.enable { + systemd.services.irkerd = { + description = "Internet Relay Chat (IRC) notification daemon"; + documentation = [ "man:irkerd(8)" "man:irkerhook(1)" "man:irk(1)" ]; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${pkgs.irker}/bin/irkerd -H ${cfg.listenAddress} -n ${cfg.nick}"; + User = "irkerd"; + }; + }; + + environment.systemPackages = [ pkgs.irker ]; + + users.users.irkerd = { + description = "Irker daemon user"; + isSystemUser = true; + group = "irkerd"; + }; + users.groups.irkerd = {}; + + networking.firewall.allowedTCPPorts = mkIf cfg.openPorts ports; + networking.firewall.allowedUDPPorts = mkIf cfg.openPorts ports; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/jackett.nix b/nixpkgs/nixos/modules/services/misc/jackett.nix new file mode 100644 index 000000000000..a07f20e5c24b --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/jackett.nix @@ -0,0 +1,75 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.jackett; + +in +{ + options = { + services.jackett = { + enable = mkEnableOption "Jackett"; + + dataDir = mkOption { + type = types.str; + default = "/var/lib/jackett/.config/Jackett"; + description = "The directory where Jackett stores its data files."; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = "Open ports in the firewall for the Jackett web interface."; + }; + + user = mkOption { + type = types.str; + default = "jackett"; + description = "User account under which Jackett runs."; + }; + + group = mkOption { + type = types.str; + default = "jackett"; + description = "Group under which Jackett runs."; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.tmpfiles.rules = [ + "d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -" + ]; + + systemd.services.jackett = { + description = "Jackett"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + ExecStart = "${pkgs.jackett}/bin/Jackett --NoUpdates --DataFolder '${cfg.dataDir}'"; + Restart = "on-failure"; + }; + }; + + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ 9117 ]; + }; + + users.users = mkIf (cfg.user == "jackett") { + jackett = { + group = cfg.group; + home = cfg.dataDir; + uid = config.ids.uids.jackett; + }; + }; + + users.groups = mkIf (cfg.group == "jackett") { + jackett.gid = config.ids.gids.jackett; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/leaps.nix b/nixpkgs/nixos/modules/services/misc/leaps.nix new file mode 100644 index 000000000000..d4e88ecbebdb --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/leaps.nix @@ -0,0 +1,62 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.leaps; + stateDir = "/var/lib/leaps/"; +in +{ + options = { + services.leaps = { + enable = mkEnableOption "leaps"; + port = mkOption { + type = types.int; + default = 8080; + description = "A port where leaps listens for incoming http requests"; + }; + address = mkOption { + default = ""; + type = types.str; + example = "127.0.0.1"; + description = "Hostname or IP-address to listen to. By default it will listen on all interfaces."; + }; + path = mkOption { + default = "/"; + type = types.path; + description = "Subdirectory used for reverse proxy setups"; + }; + }; + }; + + config = mkIf cfg.enable { + users = { + users.leaps = { + uid = config.ids.uids.leaps; + description = "Leaps server user"; + group = "leaps"; + home = stateDir; + createHome = true; + }; + + groups.leaps = { + gid = config.ids.gids.leaps; + }; + }; + + systemd.services.leaps = { + description = "leaps service"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + User = "leaps"; + Group = "leaps"; + Restart = "on-failure"; + WorkingDirectory = stateDir; + PrivateTmp = true; + ExecStart = "${pkgs.leaps.bin}/bin/leaps -path ${toString cfg.path} -address ${cfg.address}:${toString cfg.port}"; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/lidarr.nix b/nixpkgs/nixos/modules/services/misc/lidarr.nix new file mode 100644 index 000000000000..f466402abfc7 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/lidarr.nix @@ -0,0 +1,41 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.lidarr; +in +{ + options = { + services.lidarr = { + enable = mkEnableOption "Lidarr"; + }; + }; + + config = mkIf cfg.enable { + systemd.services.lidarr = { + description = "Lidarr"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Type = "simple"; + User = "lidarr"; + Group = "lidarr"; + ExecStart = "${pkgs.lidarr}/bin/Lidarr"; + Restart = "on-failure"; + + StateDirectory = "lidarr"; + StateDirectoryMode = "0770"; + }; + }; + + users.users.lidarr = { + uid = config.ids.uids.lidarr; + home = "/var/lib/lidarr"; + group = "lidarr"; + }; + + users.groups.lidarr.gid = config.ids.gids.lidarr; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/logkeys.nix b/nixpkgs/nixos/modules/services/misc/logkeys.nix new file mode 100644 index 000000000000..ad13d9eaa674 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/logkeys.nix @@ -0,0 +1,30 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.logkeys; +in { + options.services.logkeys = { + enable = mkEnableOption "logkeys service"; + + device = mkOption { + description = "Use the given device as keyboard input event device instead of /dev/input/eventX default."; + default = null; + type = types.nullOr types.string; + example = "/dev/input/event15"; + }; + }; + + config = mkIf cfg.enable { + systemd.services.logkeys = { + description = "LogKeys Keylogger Daemon"; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${pkgs.logkeys}/bin/logkeys -s${lib.optionalString (cfg.device != null) " -d ${cfg.device}"}"; + ExecStop = "${pkgs.logkeys}/bin/logkeys -k"; + Type = "forking"; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/mantisbt.nix b/nixpkgs/nixos/modules/services/misc/mantisbt.nix new file mode 100644 index 000000000000..7e3474feb672 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/mantisbt.nix @@ -0,0 +1,68 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.services.mantisbt; + + freshInstall = cfg.extraConfig == ""; + + # combined code+config directory + mantisbt = let + config_inc = pkgs.writeText "config_inc.php" ("<?php\n" + cfg.extraConfig); + src = pkgs.fetchurl { + url = "mirror://sourceforge/mantisbt/${name}.tar.gz"; + sha256 = "1pl6xn793p3mxc6ibpr2bhg85vkdlcf57yk7pfc399g47l8x4508"; + }; + name = "mantisbt-1.2.19"; + in + # We have to copy every time; otherwise config won't be found. + pkgs.runCommand name + { preferLocalBuild = true; allowSubstitutes = false; } + ('' + mkdir -p "$out" + cd "$out" + tar -xf '${src}' --strip-components=1 + ln -s '${config_inc}' config_inc.php + '' + + lib.optionalString (!freshInstall) "rm -r admin/" + ); +in +{ + options.services.mantisbt = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Enable the mantisbt web service. + This switches on httpd with PHP and database. + ''; + }; + urlPrefix = mkOption { + type = types.string; + default = "/mantisbt"; + description = "The URL prefix under which the mantisbt service appears."; + }; + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + The contents of config_inc.php, without leading <?php. + If left empty, the admin directory will be accessible. + ''; + }; + }; + + + config = mkIf cfg.enable { + services.mysql.enable = true; + services.httpd.enable = true; + services.httpd.enablePHP = true; + # The httpd sub-service showing mantisbt. + services.httpd.extraSubservices = [ { function = { ... }: { + extraConfig = + '' + Alias ${cfg.urlPrefix} "${mantisbt}" + ''; + };}]; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/mathics.nix b/nixpkgs/nixos/modules/services/misc/mathics.nix new file mode 100644 index 000000000000..c588a30d76cd --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/mathics.nix @@ -0,0 +1,54 @@ +{ pkgs, lib, config, ... }: + +with lib; + +let + cfg = config.services.mathics; + +in { + options = { + services.mathics = { + enable = mkEnableOption "Mathics notebook service"; + + external = mkOption { + type = types.bool; + default = false; + description = "Listen on all interfaces, rather than just localhost?"; + }; + + port = mkOption { + type = types.int; + default = 8000; + description = "TCP port to listen on."; + }; + }; + }; + + config = mkIf cfg.enable { + + users.users.mathics = { + group = config.users.groups.mathics.name; + description = "Mathics user"; + home = "/var/lib/mathics"; + createHome = true; + uid = config.ids.uids.mathics; + }; + + users.groups.mathics.gid = config.ids.gids.mathics; + + systemd.services.mathics = { + description = "Mathics notebook server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + User = config.users.users.mathics.name; + Group = config.users.groups.mathics.name; + ExecStart = concatStringsSep " " [ + "${pkgs.mathics}/bin/mathicsserver" + "--port" (toString cfg.port) + (if cfg.external then "--external" else "") + ]; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/matrix-synapse-log_config.yaml b/nixpkgs/nixos/modules/services/misc/matrix-synapse-log_config.yaml new file mode 100644 index 000000000000..d85bdd1208f9 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/matrix-synapse-log_config.yaml @@ -0,0 +1,25 @@ +version: 1 + +# In systemd's journal, loglevel is implicitly stored, so let's omit it +# from the message text. +formatters: + journal_fmt: + format: '%(name)s: [%(request)s] %(message)s' + +filters: + context: + (): synapse.util.logcontext.LoggingContextFilter + request: "" + +handlers: + journal: + class: systemd.journal.JournalHandler + formatter: journal_fmt + filters: [context] + SYSLOG_IDENTIFIER: synapse + +root: + level: INFO + handlers: [journal] + +disable_existing_loggers: False diff --git a/nixpkgs/nixos/modules/services/misc/matrix-synapse.nix b/nixpkgs/nixos/modules/services/misc/matrix-synapse.nix new file mode 100644 index 000000000000..00c8e7408030 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/matrix-synapse.nix @@ -0,0 +1,702 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.matrix-synapse; + pg = config.services.postgresql; + usePostgresql = cfg.database_type == "psycopg2"; + logConfigFile = pkgs.writeText "log_config.yaml" cfg.logConfig; + mkResource = r: ''{names: ${builtins.toJSON r.names}, compress: ${boolToString r.compress}}''; + mkListener = l: ''{port: ${toString l.port}, bind_address: "${l.bind_address}", type: ${l.type}, tls: ${boolToString l.tls}, x_forwarded: ${boolToString l.x_forwarded}, resources: [${concatStringsSep "," (map mkResource l.resources)}]}''; + configFile = pkgs.writeText "homeserver.yaml" '' +${optionalString (cfg.tls_certificate_path != null) '' +tls_certificate_path: "${cfg.tls_certificate_path}" +''} +${optionalString (cfg.tls_private_key_path != null) '' +tls_private_key_path: "${cfg.tls_private_key_path}" +''} +${optionalString (cfg.tls_dh_params_path != null) '' +tls_dh_params_path: "${cfg.tls_dh_params_path}" +''} +no_tls: ${boolToString cfg.no_tls} +${optionalString (cfg.bind_port != null) '' +bind_port: ${toString cfg.bind_port} +''} +${optionalString (cfg.unsecure_port != null) '' +unsecure_port: ${toString cfg.unsecure_port} +''} +${optionalString (cfg.bind_host != null) '' +bind_host: "${cfg.bind_host}" +''} +server_name: "${cfg.server_name}" +pid_file: "/run/matrix-synapse.pid" +web_client: ${boolToString cfg.web_client} +${optionalString (cfg.public_baseurl != null) '' +public_baseurl: "${cfg.public_baseurl}" +''} +listeners: [${concatStringsSep "," (map mkListener cfg.listeners)}] +database: { + name: "${cfg.database_type}", + args: { + ${concatStringsSep ",\n " ( + mapAttrsToList (n: v: "\"${n}\": ${builtins.toJSON v}") cfg.database_args + )} + } +} +event_cache_size: "${cfg.event_cache_size}" +verbose: ${cfg.verbose} +log_config: "${logConfigFile}" +rc_messages_per_second: ${cfg.rc_messages_per_second} +rc_message_burst_count: ${cfg.rc_message_burst_count} +federation_rc_window_size: ${cfg.federation_rc_window_size} +federation_rc_sleep_limit: ${cfg.federation_rc_sleep_limit} +federation_rc_sleep_delay: ${cfg.federation_rc_sleep_delay} +federation_rc_reject_limit: ${cfg.federation_rc_reject_limit} +federation_rc_concurrent: ${cfg.federation_rc_concurrent} +media_store_path: "${cfg.dataDir}/media" +uploads_path: "${cfg.dataDir}/uploads" +max_upload_size: "${cfg.max_upload_size}" +max_image_pixels: "${cfg.max_image_pixels}" +dynamic_thumbnails: ${boolToString cfg.dynamic_thumbnails} +url_preview_enabled: ${boolToString cfg.url_preview_enabled} +${optionalString (cfg.url_preview_enabled == true) '' +url_preview_ip_range_blacklist: ${builtins.toJSON cfg.url_preview_ip_range_blacklist} +url_preview_ip_range_whitelist: ${builtins.toJSON cfg.url_preview_ip_range_whitelist} +url_preview_url_blacklist: ${builtins.toJSON cfg.url_preview_url_blacklist} +''} +recaptcha_private_key: "${cfg.recaptcha_private_key}" +recaptcha_public_key: "${cfg.recaptcha_public_key}" +enable_registration_captcha: ${boolToString cfg.enable_registration_captcha} +turn_uris: ${builtins.toJSON cfg.turn_uris} +turn_shared_secret: "${cfg.turn_shared_secret}" +enable_registration: ${boolToString cfg.enable_registration} +${optionalString (cfg.registration_shared_secret != null) '' +registration_shared_secret: "${cfg.registration_shared_secret}" +''} +recaptcha_siteverify_api: "https://www.google.com/recaptcha/api/siteverify" +turn_user_lifetime: "${cfg.turn_user_lifetime}" +user_creation_max_duration: ${cfg.user_creation_max_duration} +bcrypt_rounds: ${cfg.bcrypt_rounds} +allow_guest_access: ${boolToString cfg.allow_guest_access} +trusted_third_party_id_servers: ${builtins.toJSON cfg.trusted_third_party_id_servers} +room_invite_state_types: ${builtins.toJSON cfg.room_invite_state_types} +${optionalString (cfg.macaroon_secret_key != null) '' + macaroon_secret_key: "${cfg.macaroon_secret_key}" +''} +expire_access_token: ${boolToString cfg.expire_access_token} +enable_metrics: ${boolToString cfg.enable_metrics} +report_stats: ${boolToString cfg.report_stats} +signing_key_path: "${cfg.dataDir}/homeserver.signing.key" +key_refresh_interval: "${cfg.key_refresh_interval}" +perspectives: + servers: { + ${concatStringsSep "},\n" (mapAttrsToList (n: v: '' + "${n}": { + "verify_keys": { + ${concatStringsSep "},\n" (mapAttrsToList (n: v: '' + "${n}": { + "key": "${v}" + }'') v)} + } + '') cfg.servers)} + } + } +app_service_config_files: ${builtins.toJSON cfg.app_service_config_files} + +${cfg.extraConfig} +''; +in { + options = { + services.matrix-synapse = { + enable = mkEnableOption "matrix.org synapse"; + package = mkOption { + type = types.package; + default = pkgs.matrix-synapse; + defaultText = "pkgs.matrix-synapse"; + description = '' + Overridable attribute of the matrix synapse server package to use. + ''; + }; + no_tls = mkOption { + type = types.bool; + default = false; + description = '' + Don't bind to the https port + ''; + }; + bind_port = mkOption { + type = types.nullOr types.int; + default = null; + example = 8448; + description = '' + DEPRECATED: Use listeners instead. + The port to listen for HTTPS requests on. + For when matrix traffic is sent directly to synapse. + ''; + }; + unsecure_port = mkOption { + type = types.nullOr types.int; + default = null; + example = 8008; + description = '' + DEPRECATED: Use listeners instead. + The port to listen for HTTP requests on. + For when matrix traffic passes through loadbalancer that unwraps TLS. + ''; + }; + bind_host = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + DEPRECATED: Use listeners instead. + Local interface to listen on. + The empty string will cause synapse to listen on all interfaces. + ''; + }; + tls_certificate_path = mkOption { + type = types.nullOr types.str; + default = null; + example = "${cfg.dataDir}/homeserver.tls.crt"; + description = '' + PEM encoded X509 certificate for TLS. + You can replace the self-signed certificate that synapse + autogenerates on launch with your own SSL certificate + key pair + if you like. Any required intermediary certificates can be + appended after the primary certificate in hierarchical order. + ''; + }; + tls_private_key_path = mkOption { + type = types.nullOr types.str; + default = null; + example = "${cfg.dataDir}/homeserver.tls.key"; + description = '' + PEM encoded private key for TLS. Specify null if synapse is not + speaking TLS directly. + ''; + }; + tls_dh_params_path = mkOption { + type = types.nullOr types.str; + default = null; + example = "${cfg.dataDir}/homeserver.tls.dh"; + description = '' + PEM dh parameters for ephemeral keys + ''; + }; + server_name = mkOption { + type = types.str; + example = "example.com"; + default = config.networking.hostName; + description = '' + The domain name of the server, with optional explicit port. + This is used by remote servers to connect to this server, + e.g. matrix.org, localhost:8080, etc. + This is also the last part of your UserID. + ''; + }; + web_client = mkOption { + type = types.bool; + default = false; + description = '' + Whether to serve a web client from the HTTP/HTTPS root resource. + ''; + }; + public_baseurl = mkOption { + type = types.nullOr types.str; + default = null; + example = "https://example.com:8448/"; + description = '' + The public-facing base URL for the client API (not including _matrix/...) + ''; + }; + listeners = mkOption { + type = types.listOf (types.submodule { + options = { + port = mkOption { + type = types.int; + example = 8448; + description = '' + The port to listen for HTTP(S) requests on. + ''; + }; + bind_address = mkOption { + type = types.str; + default = ""; + example = "203.0.113.42"; + description = '' + Local interface to listen on. + The empty string will cause synapse to listen on all interfaces. + ''; + }; + type = mkOption { + type = types.str; + default = "http"; + description = '' + Type of listener. + ''; + }; + tls = mkOption { + type = types.bool; + default = true; + description = '' + Whether to listen for HTTPS connections rather than HTTP. + ''; + }; + x_forwarded = mkOption { + type = types.bool; + default = false; + description = '' + Use the X-Forwarded-For (XFF) header as the client IP and not the + actual client IP. + ''; + }; + resources = mkOption { + type = types.listOf (types.submodule { + options = { + names = mkOption { + type = types.listOf types.str; + description = '' + List of resources to host on this listener. + ''; + example = ["client" "webclient" "federation"]; + }; + compress = mkOption { + type = types.bool; + description = '' + Should synapse compress HTTP responses to clients that support it? + This should be disabled if running synapse behind a load balancer + that can do automatic compression. + ''; + }; + }; + }); + description = '' + List of HTTP resources to serve on this listener. + ''; + }; + }; + }); + default = [{ + port = 8448; + bind_address = ""; + type = "http"; + tls = true; + x_forwarded = false; + resources = [ + { names = ["client" "webclient"]; compress = true; } + { names = ["federation"]; compress = false; } + ]; + }]; + description = '' + List of ports that Synapse should listen on, their purpose and their configuration. + ''; + }; + verbose = mkOption { + type = types.str; + default = "0"; + description = "Logging verbosity level."; + }; + rc_messages_per_second = mkOption { + type = types.str; + default = "0.2"; + description = "Number of messages a client can send per second"; + }; + rc_message_burst_count = mkOption { + type = types.str; + default = "10.0"; + description = "Number of message a client can send before being throttled"; + }; + federation_rc_window_size = mkOption { + type = types.str; + default = "1000"; + description = "The federation window size in milliseconds"; + }; + federation_rc_sleep_limit = mkOption { + type = types.str; + default = "10"; + description = '' + The number of federation requests from a single server in a window + before the server will delay processing the request. + ''; + }; + federation_rc_sleep_delay = mkOption { + type = types.str; + default = "500"; + description = '' + The duration in milliseconds to delay processing events from + remote servers by if they go over the sleep limit. + ''; + }; + federation_rc_reject_limit = mkOption { + type = types.str; + default = "50"; + description = '' + The maximum number of concurrent federation requests allowed + from a single server + ''; + }; + federation_rc_concurrent = mkOption { + type = types.str; + default = "3"; + description = "The number of federation requests to concurrently process from a single server"; + }; + database_type = mkOption { + type = types.enum [ "sqlite3" "psycopg2" ]; + default = if versionAtLeast config.system.stateVersion "18.03" + then "psycopg2" + else "sqlite3"; + description = '' + The database engine name. Can be sqlite or psycopg2. + ''; + }; + create_local_database = mkOption { + type = types.bool; + default = true; + description = '' + Whether to create a local database automatically. + ''; + }; + database_name = mkOption { + type = types.str; + default = "matrix-synapse"; + description = "Database name."; + }; + database_user = mkOption { + type = types.str; + default = "matrix-synapse"; + description = "Database user name."; + }; + database_args = mkOption { + type = types.attrs; + default = { + sqlite3 = { database = "${cfg.dataDir}/homeserver.db"; }; + psycopg2 = { + user = cfg.database_user; + database = cfg.database_name; + }; + }."${cfg.database_type}"; + description = '' + Arguments to pass to the engine. + ''; + }; + event_cache_size = mkOption { + type = types.str; + default = "10K"; + description = "Number of events to cache in memory."; + }; + url_preview_enabled = mkOption { + type = types.bool; + default = false; + description = '' + Is the preview URL API enabled? If enabled, you *must* specify an + explicit url_preview_ip_range_blacklist of IPs that the spider is + denied from accessing. + ''; + }; + url_preview_ip_range_blacklist = mkOption { + type = types.listOf types.str; + default = [ + "127.0.0.0/8" + "10.0.0.0/8" + "172.16.0.0/12" + "192.168.0.0/16" + "100.64.0.0/10" + "169.254.0.0/16" + ]; + description = '' + List of IP address CIDR ranges that the URL preview spider is denied + from accessing. + ''; + }; + url_preview_ip_range_whitelist = mkOption { + type = types.listOf types.str; + default = []; + description = '' + List of IP address CIDR ranges that the URL preview spider is allowed + to access even if they are specified in + url_preview_ip_range_blacklist. + ''; + }; + url_preview_url_blacklist = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Optional list of URL matches that the URL preview spider is + denied from accessing. + ''; + }; + recaptcha_private_key = mkOption { + type = types.str; + default = ""; + description = '' + This Home Server's ReCAPTCHA private key. + ''; + }; + recaptcha_public_key = mkOption { + type = types.str; + default = ""; + description = '' + This Home Server's ReCAPTCHA public key. + ''; + }; + enable_registration_captcha = mkOption { + type = types.bool; + default = false; + description = '' + Enables ReCaptcha checks when registering, preventing signup + unless a captcha is answered. Requires a valid ReCaptcha + public/private key. + ''; + }; + turn_uris = mkOption { + type = types.listOf types.str; + default = []; + description = '' + The public URIs of the TURN server to give to clients + ''; + }; + turn_shared_secret = mkOption { + type = types.str; + default = ""; + description = '' + The shared secret used to compute passwords for the TURN server + ''; + }; + turn_user_lifetime = mkOption { + type = types.str; + default = "1h"; + description = "How long generated TURN credentials last"; + }; + enable_registration = mkOption { + type = types.bool; + default = false; + description = '' + Enable registration for new users. + ''; + }; + registration_shared_secret = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + If set, allows registration by anyone who also has the shared + secret, even if registration is otherwise disabled. + ''; + }; + enable_metrics = mkOption { + type = types.bool; + default = false; + description = '' + Enable collection and rendering of performance metrics + ''; + }; + report_stats = mkOption { + type = types.bool; + default = false; + description = '' + ''; + }; + servers = mkOption { + type = types.attrsOf (types.attrsOf types.str); + default = { + "matrix.org" = { + "ed25519:auto" = "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw"; + }; + }; + description = '' + The trusted servers to download signing keys from. + ''; + }; + max_upload_size = mkOption { + type = types.str; + default = "10M"; + description = "The largest allowed upload size in bytes"; + }; + max_image_pixels = mkOption { + type = types.str; + default = "32M"; + description = "Maximum number of pixels that will be thumbnailed"; + }; + dynamic_thumbnails = mkOption { + type = types.bool; + default = false; + description = '' + Whether to generate new thumbnails on the fly to precisely match + the resolution requested by the client. If true then whenever + a new resolution is requested by the client the server will + generate a new thumbnail. If false the server will pick a thumbnail + from a precalculated list. + ''; + }; + user_creation_max_duration = mkOption { + type = types.str; + default = "1209600000"; + description = '' + Sets the expiry for the short term user creation in + milliseconds. The default value is two weeks. + ''; + }; + bcrypt_rounds = mkOption { + type = types.str; + default = "12"; + description = '' + Set the number of bcrypt rounds used to generate password hash. + Larger numbers increase the work factor needed to generate the hash. + ''; + }; + allow_guest_access = mkOption { + type = types.bool; + default = false; + description = '' + Allows users to register as guests without a password/email/etc, and + participate in rooms hosted on this server which have been made + accessible to anonymous users. + ''; + }; + trusted_third_party_id_servers = mkOption { + type = types.listOf types.str; + default = [ + "matrix.org" + "vector.im" + ]; + description = '' + The list of identity servers trusted to verify third party identifiers by this server. + ''; + }; + room_invite_state_types = mkOption { + type = types.listOf types.str; + default = ["m.room.join_rules" "m.room.canonical_alias" "m.room.avatar" "m.room.name"]; + description = '' + A list of event types that will be included in the room_invite_state + ''; + }; + macaroon_secret_key = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Secret key for authentication tokens + ''; + }; + expire_access_token = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable access token expiration. + ''; + }; + key_refresh_interval = mkOption { + type = types.str; + default = "1d"; + description = '' + How long key response published by this server is valid for. + Used to set the valid_until_ts in /key/v2 APIs. + Determines how quickly servers will query to check which keys + are still valid. + ''; + }; + app_service_config_files = mkOption { + type = types.listOf types.path; + default = [ ]; + description = '' + A list of application service config file to use + ''; + }; + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Extra config options for matrix-synapse. + ''; + }; + extraConfigFiles = mkOption { + type = types.listOf types.path; + default = []; + description = '' + Extra config files to include. + + The configuration files will be included based on the command line + argument --config-path. This allows to configure secrets without + having to go through the Nix store, e.g. based on deployment keys if + NixOPS is in use. + ''; + }; + logConfig = mkOption { + type = types.lines; + default = readFile ./matrix-synapse-log_config.yaml; + description = '' + A yaml python logging config file + ''; + }; + dataDir = mkOption { + type = types.str; + default = "/var/lib/matrix-synapse"; + description = '' + The directory where matrix-synapse stores its stateful data such as + certificates, media and uploads. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + users.users = [ + { name = "matrix-synapse"; + group = "matrix-synapse"; + home = cfg.dataDir; + createHome = true; + shell = "${pkgs.bash}/bin/bash"; + uid = config.ids.uids.matrix-synapse; + } ]; + + users.groups = [ + { name = "matrix-synapse"; + gid = config.ids.gids.matrix-synapse; + } ]; + + services.postgresql.enable = mkIf usePostgresql (mkDefault true); + + systemd.services.matrix-synapse = { + description = "Synapse Matrix homeserver"; + after = [ "network.target" "postgresql.service" ]; + wantedBy = [ "multi-user.target" ]; + preStart = '' + ${cfg.package}/bin/homeserver \ + --config-path ${configFile} \ + --keys-directory ${cfg.dataDir} \ + --generate-keys + '' + optionalString (usePostgresql && cfg.create_local_database) '' + if ! test -e "${cfg.dataDir}/db-created"; then + ${pkgs.sudo}/bin/sudo -u ${pg.superUser} \ + ${pg.package}/bin/createuser \ + --login \ + --no-createdb \ + --no-createrole \ + --encrypted \ + ${cfg.database_user} + ${pkgs.sudo}/bin/sudo -u ${pg.superUser} \ + ${pg.package}/bin/createdb \ + --owner=${cfg.database_user} \ + --encoding=UTF8 \ + --lc-collate=C \ + --lc-ctype=C \ + --template=template0 \ + ${cfg.database_name} + touch "${cfg.dataDir}/db-created" + fi + ''; + serviceConfig = { + Type = "simple"; + User = "matrix-synapse"; + Group = "matrix-synapse"; + WorkingDirectory = cfg.dataDir; + PermissionsStartOnly = true; + ExecStart = '' + ${cfg.package}/bin/homeserver \ + ${ concatMapStringsSep "\n " (x: "--config-path ${x} \\") ([ configFile ] ++ cfg.extraConfigFiles) } + --keys-directory ${cfg.dataDir} + ''; + ExecReload = "${pkgs.utillinux}/bin/kill -HUP $MAINPID"; + Restart = "on-failure"; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/mbpfan.nix b/nixpkgs/nixos/modules/services/misc/mbpfan.nix new file mode 100644 index 000000000000..e22d1ed61f99 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/mbpfan.nix @@ -0,0 +1,109 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.mbpfan; + verbose = if cfg.verbose then "v" else ""; + +in { + options.services.mbpfan = { + enable = mkEnableOption "mbpfan, fan controller daemon for Apple Macs and MacBooks"; + + package = mkOption { + type = types.package; + default = pkgs.mbpfan; + defaultText = "pkgs.mbpfan"; + description = '' + The package used for the mbpfan daemon. + ''; + }; + + minFanSpeed = mkOption { + type = types.int; + default = 2000; + description = '' + The minimum fan speed. + ''; + }; + + maxFanSpeed = mkOption { + type = types.int; + default = 6200; + description = '' + The maximum fan speed. + ''; + }; + + lowTemp = mkOption { + type = types.int; + default = 63; + description = '' + The low temperature. + ''; + }; + + highTemp = mkOption { + type = types.int; + default = 66; + description = '' + The high temperature. + ''; + }; + + maxTemp = mkOption { + type = types.int; + default = 86; + description = '' + The maximum temperature. + ''; + }; + + pollingInterval = mkOption { + type = types.int; + default = 7; + description = '' + The polling interval. + ''; + }; + + verbose = mkOption { + type = types.bool; + default = false; + description = '' + If true, sets the log level to verbose. + ''; + }; + }; + + config = mkIf cfg.enable { + boot.kernelModules = [ "coretemp" "applesmc" ]; + + environment = { + etc."mbpfan.conf".text = '' + [general] + min_fan_speed = ${toString cfg.minFanSpeed} + max_fan_speed = ${toString cfg.maxFanSpeed} + low_temp = ${toString cfg.lowTemp} + high_temp = ${toString cfg.highTemp} + max_temp = ${toString cfg.maxTemp} + polling_interval = ${toString cfg.pollingInterval} + ''; + systemPackages = [ cfg.package ]; + }; + + systemd.services.mbpfan = { + description = "A fan manager daemon for MacBook Pro"; + wantedBy = [ "sysinit.target" ]; + after = [ "syslog.target" "sysinit.target" ]; + restartTriggers = [ config.environment.etc."mbpfan.conf".source ]; + serviceConfig = { + Type = "simple"; + ExecStart = "${cfg.package}/bin/mbpfan -f${verbose}"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + PIDFile = "/run/mbpfan.pid"; + Restart = "always"; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/mediatomb.nix b/nixpkgs/nixos/modules/services/misc/mediatomb.nix new file mode 100644 index 000000000000..e8e9c0946d7f --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/mediatomb.nix @@ -0,0 +1,288 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + gid = config.ids.gids.mediatomb; + cfg = config.services.mediatomb; + + mtConf = pkgs.writeText "config.xml" '' + <?xml version="1.0" encoding="UTF-8"?> + <config version="2" xmlns="http://mediatomb.cc/config/2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://mediatomb.cc/config/2 http://mediatomb.cc/config/2.xsd"> + <server> + <ui enabled="yes" show-tooltips="yes"> + <accounts enabled="no" session-timeout="30"> + <account user="mediatomb" password="mediatomb"/> + </accounts> + </ui> + <name>${cfg.serverName}</name> + <udn>uuid:${cfg.uuid}</udn> + <home>${cfg.dataDir}</home> + <webroot>${pkgs.mediatomb}/share/mediatomb/web</webroot> + <storage> + <sqlite3 enabled="yes"> + <database-file>mediatomb.db</database-file> + </sqlite3> + </storage> + <protocolInfo extend="${if cfg.ps3Support then "yes" else "no"}"/> + ${if cfg.dsmSupport then '' + <custom-http-headers> + <add header="X-User-Agent: redsonic"/> + </custom-http-headers> + + <manufacturerURL>redsonic.com</manufacturerURL> + <modelNumber>105</modelNumber> + '' else ""} + ${if cfg.tg100Support then '' + <upnp-string-limit>101</upnp-string-limit> + '' else ""} + <extended-runtime-options> + <mark-played-items enabled="yes" suppress-cds-updates="yes"> + <string mode="prepend">*</string> + <mark> + <content>video</content> + </mark> + </mark-played-items> + </extended-runtime-options> + </server> + <import hidden-files="no"> + <scripting script-charset="UTF-8"> + <common-script>${pkgs.mediatomb}/share/mediatomb/js/common.js</common-script> + <playlist-script>${pkgs.mediatomb}/share/mediatomb/js/playlists.js</playlist-script> + <virtual-layout type="builtin"> + <import-script>${pkgs.mediatomb}/share/mediatomb/js/import.js</import-script> + </virtual-layout> + </scripting> + <mappings> + <extension-mimetype ignore-unknown="no"> + <map from="mp3" to="audio/mpeg"/> + <map from="ogx" to="application/ogg"/> + <map from="ogv" to="video/ogg"/> + <map from="oga" to="audio/ogg"/> + <map from="ogg" to="audio/ogg"/> + <map from="ogm" to="video/ogg"/> + <map from="asf" to="video/x-ms-asf"/> + <map from="asx" to="video/x-ms-asf"/> + <map from="wma" to="audio/x-ms-wma"/> + <map from="wax" to="audio/x-ms-wax"/> + <map from="wmv" to="video/x-ms-wmv"/> + <map from="wvx" to="video/x-ms-wvx"/> + <map from="wm" to="video/x-ms-wm"/> + <map from="wmx" to="video/x-ms-wmx"/> + <map from="m3u" to="audio/x-mpegurl"/> + <map from="pls" to="audio/x-scpls"/> + <map from="flv" to="video/x-flv"/> + <map from="mkv" to="video/x-matroska"/> + <map from="mka" to="audio/x-matroska"/> + ${if cfg.ps3Support then '' + <map from="avi" to="video/divx"/> + '' else ""} + ${if cfg.dsmSupport then '' + <map from="avi" to="video/avi"/> + '' else ""} + </extension-mimetype> + <mimetype-upnpclass> + <map from="audio/*" to="object.item.audioItem.musicTrack"/> + <map from="video/*" to="object.item.videoItem"/> + <map from="image/*" to="object.item.imageItem"/> + </mimetype-upnpclass> + <mimetype-contenttype> + <treat mimetype="audio/mpeg" as="mp3"/> + <treat mimetype="application/ogg" as="ogg"/> + <treat mimetype="audio/ogg" as="ogg"/> + <treat mimetype="audio/x-flac" as="flac"/> + <treat mimetype="audio/x-ms-wma" as="wma"/> + <treat mimetype="audio/x-wavpack" as="wv"/> + <treat mimetype="image/jpeg" as="jpg"/> + <treat mimetype="audio/x-mpegurl" as="playlist"/> + <treat mimetype="audio/x-scpls" as="playlist"/> + <treat mimetype="audio/x-wav" as="pcm"/> + <treat mimetype="audio/L16" as="pcm"/> + <treat mimetype="video/x-msvideo" as="avi"/> + <treat mimetype="video/mp4" as="mp4"/> + <treat mimetype="audio/mp4" as="mp4"/> + <treat mimetype="application/x-iso9660" as="dvd"/> + <treat mimetype="application/x-iso9660-image" as="dvd"/> + </mimetype-contenttype> + </mappings> + <online-content> + <YouTube enabled="no" refresh="28800" update-at-start="no" purge-after="604800" racy-content="exclude" format="mp4" hd="no"> + <favorites user="mediatomb"/> + <standardfeed feed="most_viewed" time-range="today"/> + <playlists user="mediatomb"/> + <uploads user="mediatomb"/> + <standardfeed feed="recently_featured" time-range="today"/> + </YouTube> + </online-content> + </import> + <transcoding enabled="${if cfg.transcoding then "yes" else "no"}"> + <mimetype-profile-mappings> + <transcode mimetype="video/x-flv" using="vlcmpeg"/> + <transcode mimetype="application/ogg" using="vlcmpeg"/> + <transcode mimetype="application/ogg" using="oggflac2raw"/> + <transcode mimetype="audio/x-flac" using="oggflac2raw"/> + </mimetype-profile-mappings> + <profiles> + <profile name="oggflac2raw" enabled="no" type="external"> + <mimetype>audio/L16</mimetype> + <accept-url>no</accept-url> + <first-resource>yes</first-resource> + <accept-ogg-theora>no</accept-ogg-theora> + <agent command="ogg123" arguments="-d raw -o byteorder:big -f %out %in"/> + <buffer size="1048576" chunk-size="131072" fill-size="262144"/> + </profile> + <profile name="vlcmpeg" enabled="no" type="external"> + <mimetype>video/mpeg</mimetype> + <accept-url>yes</accept-url> + <first-resource>yes</first-resource> + <accept-ogg-theora>yes</accept-ogg-theora> + <agent command="vlc" arguments="-I dummy %in --sout #transcode{venc=ffmpeg,vcodec=mp2v,vb=4096,fps=25,aenc=ffmpeg,acodec=mpga,ab=192,samplerate=44100,channels=2}:standard{access=file,mux=ps,dst=%out} vlc:quit"/> + <buffer size="14400000" chunk-size="512000" fill-size="120000"/> + </profile> + </profiles> + </transcoding> + </config> + ''; + +in { + + + ###### interface + + options = { + + services.mediatomb = { + + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable the mediatomb DLNA server. + ''; + }; + + serverName = mkOption { + type = types.string; + default = "mediatomb"; + description = '' + How to identify the server on the network. + ''; + }; + + ps3Support = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable ps3 specific tweaks. + WARNING: incompatible with DSM 320 support. + ''; + }; + + dsmSupport = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable D-Link DSM 320 specific tweaks. + WARNING: incompatible with ps3 support. + ''; + }; + + tg100Support = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable Telegent TG100 specific tweaks. + ''; + }; + + transcoding = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable transcoding. + ''; + }; + + dataDir = mkOption { + type = types.path; + default = "/var/lib/mediatomb"; + description = '' + The directory where mediatomb stores its state, data, etc. + ''; + }; + + user = mkOption { + default = "mediatomb"; + description = "User account under which mediatomb runs."; + }; + + group = mkOption { + default = "mediatomb"; + description = "Group account under which mediatomb runs."; + }; + + port = mkOption { + default = 49152; + description = '' + The network port to listen on. + ''; + }; + + interface = mkOption { + default = ""; + description = '' + A specific interface to bind to. + ''; + }; + + uuid = mkOption { + default = "fdfc8a4e-a3ad-4c1d-b43d-a2eedb03a687"; + description = '' + A unique (on your network) to identify the server by. + ''; + }; + + customCfg = mkOption { + type = types.bool; + default = false; + description = '' + Allow mediatomb to create and use its own config file inside ${cfg.dataDir}. + ''; + }; + }; + }; + + + ###### implementation + + config = mkIf cfg.enable { + systemd.services.mediatomb = { + description = "MediaTomb media Server"; + after = [ "local-fs.target" "network.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pkgs.mediatomb ]; + serviceConfig.ExecStart = "${pkgs.mediatomb}/bin/mediatomb -p ${toString cfg.port} ${if cfg.interface!="" then "-e ${cfg.interface}" else ""} ${if cfg.customCfg then "" else "-c ${mtConf}"} -m ${cfg.dataDir}"; + serviceConfig.User = "${cfg.user}"; + }; + + users.groups = optionalAttrs (cfg.group == "mediatomb") (singleton { + name = "mediatomb"; + gid = gid; + }); + + users.users = optionalAttrs (cfg.user == "mediatomb") (singleton { + name = "mediatomb"; + isSystemUser = true; + group = cfg.group; + home = "${cfg.dataDir}"; + createHome = true; + description = "Mediatomb DLNA Server User"; + }); + + networking.firewall = { + allowedUDPPorts = [ 1900 cfg.port ]; + allowedTCPPorts = [ cfg.port ]; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/mesos-master.nix b/nixpkgs/nixos/modules/services/misc/mesos-master.nix new file mode 100644 index 000000000000..572a9847e46c --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/mesos-master.nix @@ -0,0 +1,125 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.mesos.master; + +in { + + options.services.mesos = { + + master = { + enable = mkOption { + description = "Whether to enable the Mesos Master."; + default = false; + type = types.bool; + }; + + ip = mkOption { + description = "IP address to listen on."; + default = "0.0.0.0"; + type = types.str; + }; + + port = mkOption { + description = "Mesos Master port"; + default = 5050; + type = types.int; + }; + + advertiseIp = mkOption { + description = "IP address advertised to reach this master."; + default = null; + type = types.nullOr types.str; + }; + + advertisePort = mkOption { + description = "Port advertised to reach this Mesos master."; + default = null; + type = types.nullOr types.int; + }; + + zk = mkOption { + description = '' + ZooKeeper URL (used for leader election amongst masters). + May be one of: + zk://host1:port1,host2:port2,.../mesos + zk://username:password@host1:port1,host2:port2,.../mesos + ''; + type = types.str; + }; + + workDir = mkOption { + description = "The Mesos work directory."; + default = "/var/lib/mesos/master"; + type = types.str; + }; + + extraCmdLineOptions = mkOption { + description = '' + Extra command line options for Mesos Master. + + See https://mesos.apache.org/documentation/latest/configuration/ + ''; + default = [ "" ]; + type = types.listOf types.str; + example = [ "--credentials=VALUE" ]; + }; + + quorum = mkOption { + description = '' + The size of the quorum of replicas when using 'replicated_log' based + registry. It is imperative to set this value to be a majority of + masters i.e., quorum > (number of masters)/2. + + If 0 will fall back to --registry=in_memory. + ''; + default = 0; + type = types.int; + }; + + logLevel = mkOption { + description = '' + The logging level used. Possible values: + 'INFO', 'WARNING', 'ERROR' + ''; + default = "INFO"; + type = types.str; + }; + + }; + + + }; + + + config = mkIf cfg.enable { + systemd.tmpfiles.rules = [ + "d '${cfg.workDir}' 0700 - - - -" + ]; + systemd.services.mesos-master = { + description = "Mesos Master"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + ExecStart = '' + ${pkgs.mesos}/bin/mesos-master \ + --ip=${cfg.ip} \ + --port=${toString cfg.port} \ + ${optionalString (cfg.advertiseIp != null) "--advertise_ip=${cfg.advertiseIp}"} \ + ${optionalString (cfg.advertisePort != null) "--advertise_port=${toString cfg.advertisePort}"} \ + ${if cfg.quorum == 0 + then "--registry=in_memory" + else "--zk=${cfg.zk} --registry=replicated_log --quorum=${toString cfg.quorum}"} \ + --work_dir=${cfg.workDir} \ + --logging_level=${cfg.logLevel} \ + ${toString cfg.extraCmdLineOptions} + ''; + Restart = "on-failure"; + }; + }; + }; + +} + diff --git a/nixpkgs/nixos/modules/services/misc/mesos-slave.nix b/nixpkgs/nixos/modules/services/misc/mesos-slave.nix new file mode 100644 index 000000000000..170065d0065e --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/mesos-slave.nix @@ -0,0 +1,220 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.mesos.slave; + + mkAttributes = + attrs: concatStringsSep ";" (mapAttrsToList + (k: v: "${k}:${v}") + (filterAttrs (k: v: v != null) attrs)); + attribsArg = optionalString (cfg.attributes != {}) + "--attributes=${mkAttributes cfg.attributes}"; + + containerizersArg = concatStringsSep "," ( + lib.unique ( + cfg.containerizers ++ (optional cfg.withDocker "docker") + ) + ); + + imageProvidersArg = concatStringsSep "," ( + lib.unique ( + cfg.imageProviders ++ (optional cfg.withDocker "docker") + ) + ); + + isolationArg = concatStringsSep "," ( + lib.unique ( + cfg.isolation ++ (optionals cfg.withDocker [ "filesystem/linux" "docker/runtime"]) + ) + ); + +in { + + options.services.mesos = { + slave = { + enable = mkOption { + description = "Whether to enable the Mesos Slave."; + default = false; + type = types.bool; + }; + + ip = mkOption { + description = "IP address to listen on."; + default = "0.0.0.0"; + type = types.str; + }; + + port = mkOption { + description = "Port to listen on."; + default = 5051; + type = types.int; + }; + + advertiseIp = mkOption { + description = "IP address advertised to reach this agent."; + default = null; + type = types.nullOr types.str; + }; + + advertisePort = mkOption { + description = "Port advertised to reach this agent."; + default = null; + type = types.nullOr types.int; + }; + + containerizers = mkOption { + description = '' + List of containerizer implementations to compose in order to provide + containerization. Available options are mesos and docker. + The order the containerizers are specified is the order they are tried. + ''; + default = [ "mesos" ]; + type = types.listOf types.str; + }; + + imageProviders = mkOption { + description = "List of supported image providers, e.g., APPC,DOCKER."; + default = [ ]; + type = types.listOf types.str; + }; + + imageProvisionerBackend = mkOption { + description = '' + Strategy for provisioning container rootfs from images, + e.g., aufs, bind, copy, overlay. + ''; + default = "copy"; + type = types.str; + }; + + isolation = mkOption { + description = '' + Isolation mechanisms to use, e.g., posix/cpu,posix/mem, or + cgroups/cpu,cgroups/mem, or network/port_mapping, or `gpu/nvidia` for nvidia + specific gpu isolation. + ''; + default = [ "posix/cpu" "posix/mem" ]; + type = types.listOf types.str; + }; + + master = mkOption { + description = '' + May be one of: + zk://host1:port1,host2:port2,.../path + zk://username:password@host1:port1,host2:port2,.../path + ''; + type = types.str; + }; + + withHadoop = mkOption { + description = "Add the HADOOP_HOME to the slave."; + default = false; + type = types.bool; + }; + + withDocker = mkOption { + description = "Enable the docker containerizer."; + default = config.virtualisation.docker.enable; + type = types.bool; + }; + + dockerRegistry = mkOption { + description = '' + The default url for pulling Docker images. + It could either be a Docker registry server url, + or a local path in which Docker image archives are stored. + ''; + default = null; + type = types.nullOr (types.either types.str types.path); + }; + + workDir = mkOption { + description = "The Mesos work directory."; + default = "/var/lib/mesos/slave"; + type = types.str; + }; + + extraCmdLineOptions = mkOption { + description = '' + Extra command line options for Mesos Slave. + + See https://mesos.apache.org/documentation/latest/configuration/ + ''; + default = [ "" ]; + type = types.listOf types.str; + example = [ "--gc_delay=3days" ]; + }; + + logLevel = mkOption { + description = '' + The logging level used. Possible values: + 'INFO', 'WARNING', 'ERROR' + ''; + default = "INFO"; + type = types.str; + }; + + attributes = mkOption { + description = '' + Machine attributes for the slave instance. + + Use caution when changing this; you may need to manually reset slave + metadata before the slave can re-register. + ''; + default = {}; + type = types.attrsOf types.str; + example = { rack = "aa"; + host = "aabc123"; + os = "nixos"; }; + }; + + executorEnvironmentVariables = mkOption { + description = '' + The environment variables that should be passed to the executor, and thus subsequently task(s). + ''; + default = { + PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"; + }; + type = types.attrsOf types.str; + }; + }; + + }; + + config = mkIf cfg.enable { + systemd.tmpfiles.rules = [ + "d '${cfg.workDir}' 0701 - - - -" + ]; + systemd.services.mesos-slave = { + description = "Mesos Slave"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ] ++ optionals cfg.withDocker [ "docker.service" ] ; + path = [ pkgs.runtimeShellPackage ]; + serviceConfig = { + ExecStart = '' + ${pkgs.mesos}/bin/mesos-slave \ + --containerizers=${containerizersArg} \ + --image_providers=${imageProvidersArg} \ + --image_provisioner_backend=${cfg.imageProvisionerBackend} \ + --isolation=${isolationArg} \ + --ip=${cfg.ip} \ + --port=${toString cfg.port} \ + ${optionalString (cfg.advertiseIp != null) "--advertise_ip=${cfg.advertiseIp}"} \ + ${optionalString (cfg.advertisePort != null) "--advertise_port=${toString cfg.advertisePort}"} \ + --master=${cfg.master} \ + --work_dir=${cfg.workDir} \ + --logging_level=${cfg.logLevel} \ + ${attribsArg} \ + ${optionalString cfg.withHadoop "--hadoop-home=${pkgs.hadoop}"} \ + ${optionalString cfg.withDocker "--docker=${pkgs.docker}/libexec/docker/docker"} \ + ${optionalString (cfg.dockerRegistry != null) "--docker_registry=${cfg.dockerRegistry}"} \ + --executor_environment_variables=${lib.escapeShellArg (builtins.toJSON cfg.executorEnvironmentVariables)} \ + ${toString cfg.extraCmdLineOptions} + ''; + }; + }; + }; + +} diff --git a/nixpkgs/nixos/modules/services/misc/mwlib.nix b/nixpkgs/nixos/modules/services/misc/mwlib.nix new file mode 100644 index 000000000000..a8edecff2a1e --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/mwlib.nix @@ -0,0 +1,259 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.mwlib; + pypkgs = pkgs.python27Packages; + + inherit (pypkgs) python mwlib; + + user = mkOption { + default = "nobody"; + type = types.str; + description = "User to run as."; + }; + +in +{ + + options.services.mwlib = { + + nserve = { + enable = mkOption { + default = false; + type = types.bool; + description = '' + Whether to enable nserve. Nserve is a HTTP + server. The Collection extension is talking to + that program directly. Nserve uses at least + one qserve instance in order to distribute + and manage jobs. + ''; + }; # nserve.enable + + port = mkOption { + default = 8899; + type = types.int; + description = "Specify port to listen on."; + }; # nserve.port + + address = mkOption { + default = "127.0.0.1"; + type = types.str; + description = "Specify network interface to listen on."; + }; # nserve.address + + qserve = mkOption { + default = [ "${cfg.qserve.address}:${toString cfg.qserve.port}" ]; + type = types.listOf types.str; + description = "Register qserve instance."; + }; # nserve.qserve + + inherit user; + }; # nserve + + qserve = { + enable = mkOption { + default = false; + type = types.bool; + description = '' + A job queue server used to distribute and manage + jobs. You should start one qserve instance + for each machine that is supposed to render pdf + files. Unless you’re operating the Wikipedia + installation, one machine should suffice. + ''; + }; # qserve.enable + + port = mkOption { + default = 14311; + type = types.int; + description = "Specify port to listen on."; + }; # qserve.port + + address = mkOption { + default = "127.0.0.1"; + type = types.str; + description = "Specify network interface to listen on."; + }; # qserve.address + + datadir = mkOption { + default = "/var/lib/mwlib-qserve"; + type = types.path; + description = "qserve data directory (FIXME: unused?)"; + }; # qserve.datadir + + allow = mkOption { + default = [ "127.0.0.1" ]; + type = types.listOf types.str; + description = "List of allowed client IPs. Empty means any."; + }; # qserve.allow + + inherit user; + }; # qserve + + nslave = { + enable = mkOption { + default = cfg.qserve.enable; + type = types.bool; + description = '' + Pulls new jobs from exactly one qserve instance + and calls the zip and render programs + in order to download article collections and + convert them to different output formats. Nslave + uses a cache directory to store the generated + documents. Nslave also starts an internal http + server serving the content of the cache directory. + ''; + }; # nslave.enable + + cachedir = mkOption { + default = "/var/cache/mwlib-nslave"; + type = types.path; + description = "Directory to store generated documents."; + }; # nslave.cachedir + + numprocs = mkOption { + default = 10; + type = types.int; + description = "Number of parallel jobs to be executed."; + }; # nslave.numprocs + + http = mkOption { + default = {}; + description = '' + Internal http server serving the content of the cache directory. + You have to enable it, or use your own way for serving files + and set the http.url option accordingly. + ''; + type = types.submodule ({ + options = { + enable = mkOption { + default = true; + type = types.bool; + description = "Enable internal http server."; + }; # nslave.http.enable + + port = mkOption { + default = 8898; + type = types.int; + description = "Port to listen to when serving files from cache."; + }; # nslave.http.port + + address = mkOption { + default = "127.0.0.1"; + type = types.str; + description = "Specify network interface to listen on."; + }; # nslave.http.address + + url = mkOption { + default = "http://localhost:${toString cfg.nslave.http.port}/cache"; + type = types.str; + description = '' + Specify URL for accessing generated files from cache. + The Collection extension of Mediawiki won't be able to + download files without it. + ''; + }; # nslave.http.url + }; + }); # types.submodule + }; # nslave.http + + inherit user; + }; # nslave + + }; # options.services + + config = { + + systemd.services.mwlib-nserve = mkIf cfg.nserve.enable + { + description = "mwlib network interface"; + + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" "mwlib-qserve.service" ]; + + serviceConfig = { + ExecStart = concatStringsSep " " ( + [ + "${mwlib}/bin/nserve" + "--port ${toString cfg.nserve.port}" + "--interface ${cfg.nserve.address}" + ] ++ cfg.nserve.qserve + ); + User = cfg.nserve.user; + }; + }; # systemd.services.mwlib-nserve + + systemd.services.mwlib-qserve = mkIf cfg.qserve.enable + { + description = "mwlib job queue server"; + + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" "local-fs.target" ]; + + preStart = '' + mkdir -pv '${cfg.qserve.datadir}' + chown -Rc ${cfg.qserve.user}:`id -ng ${cfg.qserve.user}` '${cfg.qserve.datadir}' + chmod -Rc u=rwX,go= '${cfg.qserve.datadir}' + ''; + + serviceConfig = { + ExecStart = concatStringsSep " " ( + [ + "${mwlib}/bin/mw-qserve" + "-p ${toString cfg.qserve.port}" + "-i ${cfg.qserve.address}" + "-d ${cfg.qserve.datadir}" + ] ++ map (a: "-a ${a}") cfg.qserve.allow + ); + User = cfg.qserve.user; + PermissionsStartOnly = true; + }; + }; # systemd.services.mwlib-qserve + + systemd.services.mwlib-nslave = mkIf cfg.nslave.enable + { + description = "mwlib worker"; + + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" "local-fs.target" ]; + + preStart = '' + mkdir -pv '${cfg.nslave.cachedir}' + chown -Rc ${cfg.nslave.user}:`id -ng ${cfg.nslave.user}` '${cfg.nslave.cachedir}' + chmod -Rc u=rwX,go= '${cfg.nslave.cachedir}' + ''; + + path = with pkgs; [ imagemagick pdftk ]; + environment = { + PYTHONPATH = concatMapStringsSep ":" + (m: "${pypkgs.${m}}/lib/${python.libPrefix}/site-packages") + [ "mwlib-rl" "mwlib-ext" "pygments" "pyfribidi" ]; + }; + + serviceConfig = { + ExecStart = concatStringsSep " " ( + [ + "${mwlib}/bin/nslave" + "--cachedir ${cfg.nslave.cachedir}" + "--numprocs ${toString cfg.nslave.numprocs}" + "--url ${cfg.nslave.http.url}" + ] ++ ( + if cfg.nslave.http.enable then + [ + "--serve-files-port ${toString cfg.nslave.http.port}" + "--serve-files-address ${cfg.nslave.http.address}" + ] else + [ + "--no-serve-files" + ] + )); + User = cfg.nslave.user; + PermissionsStartOnly = true; + }; + }; # systemd.services.mwlib-nslave + + }; # config +} diff --git a/nixpkgs/nixos/modules/services/misc/nix-daemon.nix b/nixpkgs/nixos/modules/services/misc/nix-daemon.nix new file mode 100644 index 000000000000..665215822af8 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/nix-daemon.nix @@ -0,0 +1,483 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.nix; + + nix = cfg.package.out; + + isNix20 = versionAtLeast (getVersion nix) "2.0pre"; + + makeNixBuildUser = nr: + { name = "nixbld${toString nr}"; + description = "Nix build user ${toString nr}"; + + /* For consistency with the setgid(2), setuid(2), and setgroups(2) + calls in `libstore/build.cc', don't add any supplementary group + here except "nixbld". */ + uid = builtins.add config.ids.uids.nixbld nr; + group = "nixbld"; + extraGroups = [ "nixbld" ]; + }; + + nixbldUsers = map makeNixBuildUser (range 1 cfg.nrBuildUsers); + + nixConf = + let + # In Nix < 2.0, If we're using sandbox for builds, then provide + # /bin/sh in the sandbox as a bind-mount to bash. This means we + # also need to include the entire closure of bash. Nix >= 2.0 + # provides a /bin/sh by default. + sh = pkgs.runtimeShell; + binshDeps = pkgs.writeReferencesToFile sh; + in + pkgs.runCommand "nix.conf" { preferLocalBuild = true; extraOptions = cfg.extraOptions; } ('' + ${optionalString (!isNix20) '' + extraPaths=$(for i in $(cat ${binshDeps}); do if test -d $i; then echo $i; fi; done) + ''} + cat > $out <<END + # WARNING: this file is generated from the nix.* options in + # your NixOS configuration, typically + # /etc/nixos/configuration.nix. Do not edit it! + build-users-group = nixbld + ${if isNix20 then "max-jobs" else "build-max-jobs"} = ${toString (cfg.maxJobs)} + ${if isNix20 then "cores" else "build-cores"} = ${toString (cfg.buildCores)} + ${if isNix20 then "sandbox" else "build-use-sandbox"} = ${if (builtins.isBool cfg.useSandbox) then boolToString cfg.useSandbox else cfg.useSandbox} + ${if isNix20 then "extra-sandbox-paths" else "build-sandbox-paths"} = ${toString cfg.sandboxPaths} ${optionalString (!isNix20) "/bin/sh=${sh} $(echo $extraPaths)"} + ${if isNix20 then "substituters" else "binary-caches"} = ${toString cfg.binaryCaches} + ${if isNix20 then "trusted-substituters" else "trusted-binary-caches"} = ${toString cfg.trustedBinaryCaches} + ${if isNix20 then "trusted-public-keys" else "binary-cache-public-keys"} = ${toString cfg.binaryCachePublicKeys} + auto-optimise-store = ${boolToString cfg.autoOptimiseStore} + ${if isNix20 then '' + require-sigs = ${if cfg.requireSignedBinaryCaches then "true" else "false"} + '' else '' + signed-binary-caches = ${if cfg.requireSignedBinaryCaches then "*" else ""} + ''} + trusted-users = ${toString cfg.trustedUsers} + allowed-users = ${toString cfg.allowedUsers} + ${optionalString (isNix20 && !cfg.distributedBuilds) '' + builders = + ''} + $extraOptions + END + '' + optionalString cfg.checkConfig ( + if pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform then '' + echo "Ignore nix.checkConfig when cross-compiling" + '' else '' + echo "Checking that Nix can read nix.conf..." + ln -s $out ./nix.conf + NIX_CONF_DIR=$PWD ${cfg.package}/bin/nix show-config >/dev/null + '') + ); + +in + +{ + + ###### interface + + options = { + + nix = { + + package = mkOption { + type = types.package; + default = pkgs.nix; + defaultText = "pkgs.nix"; + description = '' + This option specifies the Nix package instance to use throughout the system. + ''; + }; + + maxJobs = mkOption { + type = types.either types.int (types.enum ["auto"]); + default = 1; + example = 64; + description = '' + This option defines the maximum number of jobs that Nix will try + to build in parallel. The default is 1. You should generally + set it to the total number of logical cores in your system (e.g., 16 + for two CPUs with 4 cores each and hyper-threading). + ''; + }; + + autoOptimiseStore = mkOption { + type = types.bool; + default = false; + example = true; + description = '' + If set to true, Nix automatically detects files in the store that have + identical contents, and replaces them with hard links to a single copy. + This saves disk space. If set to false (the default), you can still run + nix-store --optimise to get rid of duplicate files. + ''; + }; + + buildCores = mkOption { + type = types.int; + default = 0; + example = 64; + description = '' + This option defines the maximum number of concurrent tasks during + one build. It affects, e.g., -j option for make. + The special value 0 means that the builder should use all + available CPU cores in the system. Some builds may become + non-deterministic with this option; use with care! Packages will + only be affected if enableParallelBuilding is set for them. + ''; + }; + + useSandbox = mkOption { + type = types.either types.bool (types.enum ["relaxed"]); + default = true; + description = " + If set, Nix will perform builds in a sandboxed environment that it + will set up automatically for each build. This prevents impurities + in builds by disallowing access to dependencies outside of the Nix + store by using network and mount namespaces in a chroot environment. + This is enabled by default even though it has a possible performance + impact due to the initial setup time of a sandbox for each build. It + doesn't affect derivation hashes, so changing this option will not + trigger a rebuild of packages. + "; + }; + + sandboxPaths = mkOption { + type = types.listOf types.str; + default = []; + example = [ "/dev" "/proc" ]; + description = + '' + Directories from the host filesystem to be included + in the sandbox. + ''; + }; + + extraOptions = mkOption { + type = types.lines; + default = ""; + example = '' + gc-keep-outputs = true + gc-keep-derivations = true + ''; + description = "Additional text appended to <filename>nix.conf</filename>."; + }; + + distributedBuilds = mkOption { + type = types.bool; + default = false; + description = '' + Whether to distribute builds to the machines listed in + <option>nix.buildMachines</option>. + ''; + }; + + daemonNiceLevel = mkOption { + type = types.int; + default = 0; + description = '' + Nix daemon process priority. This priority propagates to build processes. + 0 is the default Unix process priority, 19 is the lowest. + ''; + }; + + daemonIONiceLevel = mkOption { + type = types.int; + default = 0; + description = '' + Nix daemon process I/O priority. This priority propagates to build processes. + 0 is the default Unix process I/O priority, 7 is the lowest. + ''; + }; + + buildMachines = mkOption { + type = types.listOf types.attrs; + default = []; + example = literalExample '' + [ { hostName = "voila.labs.cs.uu.nl"; + sshUser = "nix"; + sshKey = "/root/.ssh/id_buildfarm"; + system = "powerpc-darwin"; + maxJobs = 1; + } + { hostName = "linux64.example.org"; + sshUser = "buildfarm"; + sshKey = "/root/.ssh/id_buildfarm"; + system = "x86_64-linux"; + maxJobs = 2; + speedFactor = 2; + supportedFeatures = [ "kvm" ]; + mandatoryFeatures = [ "perf" ]; + } + ] + ''; + description = '' + This option lists the machines to be used if distributed + builds are enabled (see + <option>nix.distributedBuilds</option>). Nix will perform + derivations on those machines via SSH by copying the inputs + to the Nix store on the remote machine, starting the build, + then copying the output back to the local Nix store. Each + element of the list should be an attribute set containing + the machine's host name (<varname>hostname</varname>), the + user name to be used for the SSH connection + (<varname>sshUser</varname>), the Nix system type + (<varname>system</varname>, e.g., + <literal>"i686-linux"</literal>), the maximum number of + jobs to be run in parallel on that machine + (<varname>maxJobs</varname>), the path to the SSH private + key to be used to connect (<varname>sshKey</varname>), a + list of supported features of the machine + (<varname>supportedFeatures</varname>) and a list of + mandatory features of the machine + (<varname>mandatoryFeatures</varname>). The SSH private key + should not have a passphrase, and the corresponding public + key should be added to + <filename>~<replaceable>sshUser</replaceable>/authorized_keys</filename> + on the remote machine. + ''; + }; + + # Environment variables for running Nix. + envVars = mkOption { + type = types.attrs; + internal = true; + default = {}; + description = "Environment variables used by Nix."; + }; + + nrBuildUsers = mkOption { + type = types.int; + description = '' + Number of <literal>nixbld</literal> user accounts created to + perform secure concurrent builds. If you receive an error + message saying that “all build users are currently in use”, + you should increase this value. + ''; + }; + + readOnlyStore = mkOption { + type = types.bool; + default = true; + description = '' + If set, NixOS will enforce the immutability of the Nix store + by making <filename>/nix/store</filename> a read-only bind + mount. Nix will automatically make the store writable when + needed. + ''; + }; + + binaryCaches = mkOption { + type = types.listOf types.str; + default = [ https://cache.nixos.org/ ]; + description = '' + List of binary cache URLs used to obtain pre-built binaries + of Nix packages. + ''; + }; + + trustedBinaryCaches = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ http://hydra.nixos.org/ ]; + description = '' + List of binary cache URLs that non-root users can use (in + addition to those specified using + <option>nix.binaryCaches</option>) by passing + <literal>--option binary-caches</literal> to Nix commands. + ''; + }; + + requireSignedBinaryCaches = mkOption { + type = types.bool; + default = true; + description = '' + If enabled (the default), Nix will only download binaries from binary caches if + they are cryptographically signed with any of the keys listed in + <option>nix.binaryCachePublicKeys</option>. If disabled, signatures are neither + required nor checked, so it's strongly recommended that you use only + trustworthy caches and https to prevent man-in-the-middle attacks. + ''; + }; + + binaryCachePublicKeys = mkOption { + type = types.listOf types.str; + example = [ "hydra.nixos.org-1:CNHJZBh9K4tP3EKF6FkkgeVYsS3ohTl+oS0Qa8bezVs=" ]; + description = '' + List of public keys used to sign binary caches. If + <option>nix.requireSignedBinaryCaches</option> is enabled, + then Nix will use a binary from a binary cache if and only + if it is signed by <emphasis>any</emphasis> of the keys + listed here. By default, only the key for + <uri>cache.nixos.org</uri> is included. + ''; + }; + + trustedUsers = mkOption { + type = types.listOf types.str; + default = [ "root" ]; + example = [ "root" "alice" "@wheel" ]; + description = '' + A list of names of users that have additional rights when + connecting to the Nix daemon, such as the ability to specify + additional binary caches, or to import unsigned NARs. You + can also specify groups by prefixing them with + <literal>@</literal>; for instance, + <literal>@wheel</literal> means all users in the wheel + group. + ''; + }; + + allowedUsers = mkOption { + type = types.listOf types.str; + default = [ "*" ]; + example = [ "@wheel" "@builders" "alice" "bob" ]; + description = '' + A list of names of users (separated by whitespace) that are + allowed to connect to the Nix daemon. As with + <option>nix.trustedUsers</option>, you can specify groups by + prefixing them with <literal>@</literal>. Also, you can + allow all users by specifying <literal>*</literal>. The + default is <literal>*</literal>. Note that trusted users are + always allowed to connect. + ''; + }; + + nixPath = mkOption { + type = types.listOf types.str; + default = + [ + "nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos" + "nixos-config=/etc/nixos/configuration.nix" + "/nix/var/nix/profiles/per-user/root/channels" + ]; + description = '' + The default Nix expression search path, used by the Nix + evaluator to look up paths enclosed in angle brackets + (e.g. <literal><nixpkgs></literal>). + ''; + }; + + checkConfig = mkOption { + type = types.bool; + default = true; + description = '' + If enabled (the default), checks that Nix can parse the generated nix.conf. + ''; + }; + }; + + }; + + + ###### implementation + + config = { + + nix.binaryCachePublicKeys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ]; + + environment.etc."nix/nix.conf".source = nixConf; + + # List of machines for distributed Nix builds in the format + # expected by build-remote.pl. + environment.etc."nix/machines" = + { enable = cfg.buildMachines != []; + text = + concatMapStrings (machine: + "${if machine ? sshUser then "${machine.sshUser}@" else ""}${machine.hostName} " + + machine.system or (concatStringsSep "," machine.systems) + + " ${machine.sshKey or "-"} ${toString machine.maxJobs or 1} " + + toString (machine.speedFactor or 1) + + " " + + concatStringsSep "," (machine.mandatoryFeatures or [] ++ machine.supportedFeatures or []) + + " " + + concatStringsSep "," machine.mandatoryFeatures or [] + + "\n" + ) cfg.buildMachines; + }; + + systemd.packages = [ nix ]; + + systemd.sockets.nix-daemon.wantedBy = [ "sockets.target" ]; + + systemd.services.nix-daemon = + { path = [ nix pkgs.utillinux config.programs.ssh.package ] + ++ optionals cfg.distributedBuilds [ pkgs.gzip ] + ++ optionals (!isNix20) [ pkgs.openssl.bin ]; + + environment = cfg.envVars + // { CURL_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt"; } + // config.networking.proxy.envVars; + + unitConfig.RequiresMountsFor = "/nix/store"; + + serviceConfig = + { Nice = cfg.daemonNiceLevel; + IOSchedulingPriority = cfg.daemonIONiceLevel; + LimitNOFILE = 4096; + }; + + restartTriggers = [ nixConf ]; + }; + + nix.envVars = + optionalAttrs (!isNix20) { + NIX_CONF_DIR = "/etc/nix"; + + # Enable the copy-from-other-stores substituter, which allows + # builds to be sped up by copying build results from remote + # Nix stores. To do this, mount the remote file system on a + # subdirectory of /run/nix/remote-stores. + NIX_OTHER_STORES = "/run/nix/remote-stores/*/nix"; + } + + // optionalAttrs (cfg.distributedBuilds && !isNix20) { + NIX_BUILD_HOOK = "${nix}/libexec/nix/build-remote.pl"; + }; + + # Set up the environment variables for running Nix. + environment.sessionVariables = cfg.envVars // + { NIX_PATH = cfg.nixPath; + }; + + environment.extraInit = optionalString (!isNix20) + '' + # Set up secure multi-user builds: non-root users build through the + # Nix daemon. + if [ "$USER" != root -o ! -w /nix/var/nix/db ]; then + export NIX_REMOTE=daemon + fi + '' + '' + if [ -e "$HOME/.nix-defexpr/channels" ]; then + export NIX_PATH="$HOME/.nix-defexpr/channels''${NIX_PATH:+:$NIX_PATH}" + fi + ''; + + nix.nrBuildUsers = mkDefault (lib.max 32 cfg.maxJobs); + + users.users = nixbldUsers; + + services.xserver.displayManager.hiddenUsers = map ({ name, ... }: name) nixbldUsers; + + # FIXME: use systemd-tmpfiles to create Nix directories. + system.activationScripts.nix = stringAfter [ "etc" "users" ] + '' + # Nix initialisation. + install -m 0755 -d \ + /nix/var/nix/gcroots \ + /nix/var/nix/temproots \ + /nix/var/nix/userpool \ + /nix/var/nix/profiles \ + /nix/var/nix/db \ + /nix/var/log/nix/drvs + install -m 1777 -d \ + /nix/var/nix/gcroots/per-user \ + /nix/var/nix/profiles/per-user \ + /nix/var/nix/gcroots/tmp + ''; + + }; + +} diff --git a/nixpkgs/nixos/modules/services/misc/nix-gc.nix b/nixpkgs/nixos/modules/services/misc/nix-gc.nix new file mode 100644 index 000000000000..12bed05757ad --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/nix-gc.nix @@ -0,0 +1,61 @@ +{ config, lib, ... }: + +with lib; + +let + cfg = config.nix.gc; +in + +{ + + ###### interface + + options = { + + nix.gc = { + + automatic = mkOption { + default = false; + type = types.bool; + description = "Automatically run the garbage collector at a specific time."; + }; + + dates = mkOption { + default = "03:15"; + type = types.str; + description = '' + Specification (in the format described by + <citerefentry><refentrytitle>systemd.time</refentrytitle> + <manvolnum>7</manvolnum></citerefentry>) of the time at + which the garbage collector will run. + ''; + }; + + options = mkOption { + default = ""; + example = "--max-freed $((64 * 1024**3))"; + type = types.str; + description = '' + Options given to <filename>nix-collect-garbage</filename> when the + garbage collector is run automatically. + ''; + }; + + }; + + }; + + + ###### implementation + + config = { + + systemd.services.nix-gc = + { description = "Nix Garbage Collector"; + script = "exec ${config.nix.package.out}/bin/nix-collect-garbage ${cfg.options}"; + startAt = optional cfg.automatic cfg.dates; + }; + + }; + +} diff --git a/nixpkgs/nixos/modules/services/misc/nix-optimise.nix b/nixpkgs/nixos/modules/services/misc/nix-optimise.nix new file mode 100644 index 000000000000..416529f690e0 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/nix-optimise.nix @@ -0,0 +1,51 @@ +{ config, lib, ... }: + +with lib; + +let + cfg = config.nix.optimise; +in + +{ + + ###### interface + + options = { + + nix.optimise = { + + automatic = mkOption { + default = false; + type = types.bool; + description = "Automatically run the nix store optimiser at a specific time."; + }; + + dates = mkOption { + default = ["03:45"]; + type = types.listOf types.str; + description = '' + Specification (in the format described by + <citerefentry><refentrytitle>systemd.time</refentrytitle> + <manvolnum>7</manvolnum></citerefentry>) of the time at + which the optimiser will run. + ''; + }; + }; + }; + + + ###### implementation + + config = { + + systemd.services.nix-optimise = + { description = "Nix Store Optimiser"; + # No point running it inside a nixos-container. It should be on the host instead. + unitConfig.ConditionVirtualization = "!container"; + serviceConfig.ExecStart = "${config.nix.package}/bin/nix-store --optimise"; + startAt = optionals cfg.automatic cfg.dates; + }; + + }; + +} diff --git a/nixpkgs/nixos/modules/services/misc/nix-ssh-serve.nix b/nixpkgs/nixos/modules/services/misc/nix-ssh-serve.nix new file mode 100644 index 000000000000..7ce3841be2f5 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/nix-ssh-serve.nix @@ -0,0 +1,61 @@ +{ config, lib, ... }: + +with lib; +let cfg = config.nix.sshServe; + command = + if cfg.protocol == "ssh" + then "nix-store --serve" + else "nix-daemon --stdio"; +in { + options = { + + nix.sshServe = { + + enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable serving the Nix store as a remote store via SSH."; + }; + + keys = mkOption { + type = types.listOf types.str; + default = []; + example = [ "ssh-dss AAAAB3NzaC1k... alice@example.org" ]; + description = "A list of SSH public keys allowed to access the binary cache via SSH."; + }; + + protocol = mkOption { + type = types.enum [ "ssh" "ssh-ng" ]; + default = "ssh"; + description = "The specific Nix-over-SSH protocol to use."; + }; + + }; + + }; + + config = mkIf cfg.enable { + + users.users.nix-ssh = { + description = "Nix SSH store user"; + uid = config.ids.uids.nix-ssh; + useDefaultShell = true; + }; + + services.openssh.enable = true; + + services.openssh.extraConfig = '' + Match User nix-ssh + AllowAgentForwarding no + AllowTcpForwarding no + PermitTTY no + PermitTunnel no + X11Forwarding no + ForceCommand ${config.nix.package.out}/bin/${command} + Match All + ''; + + users.users.nix-ssh.openssh.authorizedKeys.keys = cfg.keys; + + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/nixos-manual.nix b/nixpkgs/nixos/modules/services/misc/nixos-manual.nix new file mode 100644 index 000000000000..df3e71c80dea --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/nixos-manual.nix @@ -0,0 +1,73 @@ +# This module optionally starts a browser that shows the NixOS manual +# on one of the virtual consoles which is useful for the installation +# CD. + +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.nixosManual; + cfgd = config.documentation; +in + +{ + + options = { + + # TODO(@oxij): rename this to `.enable` eventually. + services.nixosManual.showManual = mkOption { + type = types.bool; + default = false; + description = '' + Whether to show the NixOS manual on one of the virtual + consoles. + ''; + }; + + services.nixosManual.ttyNumber = mkOption { + type = types.int; + default = 8; + description = '' + Virtual console on which to show the manual. + ''; + }; + + services.nixosManual.browser = mkOption { + type = types.path; + default = "${pkgs.w3m-nographics}/bin/w3m"; + description = '' + Browser used to show the manual. + ''; + }; + + }; + + + config = mkMerge [ + (mkIf cfg.showManual { + assertions = singleton { + assertion = cfgd.enable && cfgd.nixos.enable; + message = "Can't enable `services.nixosManual.showManual` without `documentation.nixos.enable`"; + }; + }) + (mkIf (cfg.showManual && cfgd.enable && cfgd.nixos.enable) { + boot.extraTTYs = [ "tty${toString cfg.ttyNumber}" ]; + + systemd.services."nixos-manual" = { + description = "NixOS Manual"; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${cfg.browser} ${config.system.build.manual.manualHTMLIndex}"; + StandardInput = "tty"; + StandardOutput = "tty"; + TTYPath = "/dev/tty${toString cfg.ttyNumber}"; + TTYReset = true; + TTYVTDisallocate = true; + Restart = "always"; + }; + }; + }) + ]; + +} diff --git a/nixpkgs/nixos/modules/services/misc/novacomd.nix b/nixpkgs/nixos/modules/services/misc/novacomd.nix new file mode 100644 index 000000000000..7cfc68d2b673 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/novacomd.nix @@ -0,0 +1,31 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.novacomd; + +in { + + options = { + services.novacomd = { + enable = mkEnableOption "Novacom service for connecting to WebOS devices"; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ pkgs.webos.novacom ]; + + systemd.services.novacomd = { + description = "Novacom WebOS daemon"; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + ExecStart = "${pkgs.webos.novacomd}/sbin/novacomd"; + }; + }; + }; + + meta.maintainers = with maintainers; [ dtzWill ]; +} diff --git a/nixpkgs/nixos/modules/services/misc/nzbget.nix b/nixpkgs/nixos/modules/services/misc/nzbget.nix new file mode 100644 index 000000000000..6ab98751c57b --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/nzbget.nix @@ -0,0 +1,119 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.nzbget; + dataDir = builtins.dirOf cfg.configFile; +in { + options = { + services.nzbget = { + enable = mkEnableOption "NZBGet"; + + package = mkOption { + type = types.package; + default = pkgs.nzbget; + defaultText = "pkgs.nzbget"; + description = "The NZBGet package to use"; + }; + + dataDir = mkOption { + type = types.str; + default = "/var/lib/nzbget"; + description = "The directory where NZBGet stores its configuration files."; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = '' + Open ports in the firewall for the NZBGet web interface + ''; + }; + + user = mkOption { + type = types.str; + default = "nzbget"; + description = "User account under which NZBGet runs"; + }; + + group = mkOption { + type = types.str; + default = "nzbget"; + description = "Group under which NZBGet runs"; + }; + + configFile = mkOption { + type = types.str; + default = "/var/lib/nzbget/nzbget.conf"; + description = "Path for NZBGet's config file. (If this doesn't exist, the default config template is copied here.)"; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.nzbget = { + description = "NZBGet Daemon"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + path = with pkgs; [ + unrar + p7zip + ]; + preStart = '' + cfgtemplate=${cfg.package}/share/nzbget/nzbget.conf + if [ ! -f ${cfg.configFile} ]; then + echo "${cfg.configFile} not found. Copying default config $cfgtemplate to ${cfg.configFile}" + install -m 0700 $cfgtemplate ${cfg.configFile} + echo "Setting temporary \$MAINDIR variable in default config required in order to allow nzbget to complete initial start" + echo "Remember to change this to a proper value once NZBGet startup has been completed" + sed -i -e 's/MainDir=.*/MainDir=\/tmp/g' ${cfg.configFile} + fi + ''; + + script = '' + args="--daemon --configfile ${cfg.configFile}" + # The script in preStart (above) copies nzbget's config template to datadir on first run, containing paths that point to the nzbget derivation installed at the time. + # These paths break when nzbget is upgraded & the original derivation is garbage collected. If such broken paths are found in the config file, override them to point to + # the currently installed nzbget derivation. + cfgfallback () { + local hit=`grep -Po "(?<=^$1=).*+" "${cfg.configFile}" | sed 's/[ \t]*$//'` # Strip trailing whitespace + ( test $hit && test -e $hit ) || { + echo "In ${cfg.configFile}, valid $1 not found; falling back to $1=$2" + args+=" -o $1=$2" + } + } + cfgfallback ConfigTemplate ${cfg.package}/share/nzbget/nzbget.conf + cfgfallback WebDir ${cfg.package}/share/nzbget/webui + ${cfg.package}/bin/nzbget $args + ''; + + serviceConfig = { + StateDirectory = dataDir; + StateDirectoryMode = "0700"; + Type = "forking"; + User = cfg.user; + Group = cfg.group; + PermissionsStartOnly = "true"; + Restart = "on-failure"; + }; + }; + + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ 8989 ]; + }; + + users.users = mkIf (cfg.user == "nzbget") { + nzbget = { + group = cfg.group; + uid = config.ids.uids.nzbget; + }; + }; + + users.groups = mkIf (cfg.group == "nzbget") { + nzbget = { + gid = config.ids.gids.nzbget; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/octoprint.nix b/nixpkgs/nixos/modules/services/misc/octoprint.nix new file mode 100644 index 000000000000..baa7c3ade52e --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/octoprint.nix @@ -0,0 +1,129 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.octoprint; + + baseConfig = { + plugins.cura.cura_engine = "${pkgs.curaengine_stable}/bin/CuraEngine"; + server.host = cfg.host; + server.port = cfg.port; + webcam.ffmpeg = "${pkgs.ffmpeg.bin}/bin/ffmpeg"; + }; + + fullConfig = recursiveUpdate cfg.extraConfig baseConfig; + + cfgUpdate = pkgs.writeText "octoprint-config.yaml" (builtins.toJSON fullConfig); + + pluginsEnv = pkgs.python.buildEnv.override { + extraLibs = cfg.plugins pkgs.octoprint-plugins; + }; + +in +{ + ##### interface + + options = { + + services.octoprint = { + + enable = mkEnableOption "OctoPrint, web interface for 3D printers"; + + host = mkOption { + type = types.str; + default = "0.0.0.0"; + description = '' + Host to bind OctoPrint to. + ''; + }; + + port = mkOption { + type = types.int; + default = 5000; + description = '' + Port to bind OctoPrint to. + ''; + }; + + user = mkOption { + type = types.str; + default = "octoprint"; + description = "User for the daemon."; + }; + + group = mkOption { + type = types.str; + default = "octoprint"; + description = "Group for the daemon."; + }; + + stateDir = mkOption { + type = types.path; + default = "/var/lib/octoprint"; + description = "State directory of the daemon."; + }; + + plugins = mkOption { + default = plugins: []; + defaultText = "plugins: []"; + example = literalExample "plugins: [ m3d-fio ]"; + description = "Additional plugins."; + }; + + extraConfig = mkOption { + type = types.attrs; + default = {}; + description = "Extra options which are added to OctoPrint's YAML configuration file."; + }; + + }; + + }; + + ##### implementation + + config = mkIf cfg.enable { + + users.users = optionalAttrs (cfg.user == "octoprint") (singleton + { name = "octoprint"; + group = cfg.group; + uid = config.ids.uids.octoprint; + }); + + users.groups = optionalAttrs (cfg.group == "octoprint") (singleton + { name = "octoprint"; + gid = config.ids.gids.octoprint; + }); + + systemd.services.octoprint = { + description = "OctoPrint, web interface for 3D printers"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + path = [ pluginsEnv ]; + environment.PYTHONPATH = makeSearchPathOutput "lib" pkgs.python.sitePackages [ pluginsEnv ]; + + preStart = '' + mkdir -p "${cfg.stateDir}" + if [ -e "${cfg.stateDir}/config.yaml" ]; then + ${pkgs.yaml-merge}/bin/yaml-merge "${cfg.stateDir}/config.yaml" "${cfgUpdate}" > "${cfg.stateDir}/config.yaml.tmp" + mv "${cfg.stateDir}/config.yaml.tmp" "${cfg.stateDir}/config.yaml" + else + cp "${cfgUpdate}" "${cfg.stateDir}/config.yaml" + chmod 600 "${cfg.stateDir}/config.yaml" + fi + chown -R ${cfg.user}:${cfg.group} "${cfg.stateDir}" + ''; + + serviceConfig = { + ExecStart = "${pkgs.octoprint}/bin/octoprint serve -b ${cfg.stateDir}"; + User = cfg.user; + Group = cfg.group; + PermissionsStartOnly = true; + }; + }; + + }; + +} diff --git a/nixpkgs/nixos/modules/services/misc/osrm.nix b/nixpkgs/nixos/modules/services/misc/osrm.nix new file mode 100644 index 000000000000..f89f37ccd9df --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/osrm.nix @@ -0,0 +1,85 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.osrm; +in + +{ + options.services.osrm = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable the OSRM service."; + }; + + address = mkOption { + type = types.str; + default = "0.0.0.0"; + description = "IP address on which the web server will listen."; + }; + + port = mkOption { + type = types.int; + default = 5000; + description = "Port on which the web server will run."; + }; + + threads = mkOption { + type = types.int; + default = 4; + description = "Number of threads to use."; + }; + + algorithm = mkOption { + type = types.enum [ "CH" "CoreCH" "MLD" ]; + default = "MLD"; + description = "Algorithm to use for the data. Must be one of CH, CoreCH, MLD"; + }; + + extraFlags = mkOption { + type = types.listOf types.str; + default = []; + example = [ "--max-table-size 1000" "--max-matching-size 1000" ]; + description = "Extra command line arguments passed to osrm-routed"; + }; + + dataFile = mkOption { + type = types.path; + example = "/var/lib/osrm/berlin-latest.osrm"; + description = "Data file location"; + }; + + }; + + config = mkIf cfg.enable { + + users.users.osrm = { + group = config.users.users.osrm.name; + description = "OSRM user"; + createHome = false; + }; + + users.groups.osrm = { }; + + systemd.services.osrm = { + description = "OSRM service"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + User = config.users.users.osrm.name; + ExecStart = '' + ${pkgs.osrm-backend}/bin/osrm-routed \ + --ip ${cfg.address} \ + --port ${toString cfg.port} \ + --threads ${toString cfg.threads} \ + --algorithm ${cfg.algorithm} \ + ${toString cfg.extraFlags} \ + ${cfg.dataFile} + ''; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/packagekit.nix b/nixpkgs/nixos/modules/services/misc/packagekit.nix new file mode 100644 index 000000000000..bce21e8acff3 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/packagekit.nix @@ -0,0 +1,58 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.packagekit; + + packagekitConf = '' +[Daemon] +KeepCache=false + ''; + + vendorConf = '' +[PackagesNotFound] +DefaultUrl=https://github.com/NixOS/nixpkgs +CodecUrl=https://github.com/NixOS/nixpkgs +HardwareUrl=https://github.com/NixOS/nixpkgs +FontUrl=https://github.com/NixOS/nixpkgs +MimeUrl=https://github.com/NixOS/nixpkgs + ''; + +in + +{ + + options = { + + services.packagekit = { + enable = mkEnableOption + '' + PackageKit provides a cross-platform D-Bus abstraction layer for + installing software. Software utilizing PackageKit can install + software regardless of the package manager. + ''; + }; + + }; + + config = mkIf cfg.enable { + + services.dbus.packages = [ pkgs.packagekit ]; + + systemd.services.packagekit = { + description = "PackageKit Daemon"; + wantedBy = [ "multi-user.target" ]; + serviceConfig.ExecStart = "${pkgs.packagekit}/libexec/packagekitd"; + serviceConfig.User = "root"; + serviceConfig.BusName = "org.freedesktop.PackageKit"; + serviceConfig.Type = "dbus"; + }; + + environment.etc."PackageKit/PackageKit.conf".text = packagekitConf; + environment.etc."PackageKit/Vendor.conf".text = vendorConf; + + }; + +} diff --git a/nixpkgs/nixos/modules/services/misc/parsoid.nix b/nixpkgs/nixos/modules/services/misc/parsoid.nix new file mode 100644 index 000000000000..c757093e5c1b --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/parsoid.nix @@ -0,0 +1,104 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.parsoid; + + parsoid = pkgs.nodePackages."parsoid-git://github.com/abbradar/parsoid#stable"; + + confTree = { + worker_heartbeat_timeout = 300000; + logging = { level = "info"; }; + services = [{ + module = "lib/index.js"; + entrypoint = "apiServiceWorker"; + conf = { + mwApis = map (x: if isAttrs x then x else { uri = x; }) cfg.wikis; + serverInterface = cfg.interface; + serverPort = cfg.port; + }; + }]; + }; + + confFile = pkgs.writeText "config.yml" (builtins.toJSON (recursiveUpdate confTree cfg.extraConfig)); + +in +{ + ##### interface + + options = { + + services.parsoid = { + + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable Parsoid -- bidirectional + wikitext parser. + ''; + }; + + wikis = mkOption { + type = types.listOf (types.either types.str types.attrs); + example = [ "http://localhost/api.php" ]; + description = '' + Used MediaWiki API endpoints. + ''; + }; + + workers = mkOption { + type = types.int; + default = 2; + description = '' + Number of Parsoid workers. + ''; + }; + + interface = mkOption { + type = types.str; + default = "127.0.0.1"; + description = '' + Interface to listen on. + ''; + }; + + port = mkOption { + type = types.int; + default = 8000; + description = '' + Port to listen on. + ''; + }; + + extraConfig = mkOption { + type = types.attrs; + default = {}; + description = '' + Extra configuration to add to parsoid configuration. + ''; + }; + + }; + + }; + + ##### implementation + + config = mkIf cfg.enable { + + systemd.services.parsoid = { + description = "Bidirectional wikitext parser"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + User = "nobody"; + ExecStart = "${parsoid}/lib/node_modules/parsoid/bin/server.js -c ${confFile} -n ${toString cfg.workers}"; + }; + }; + + }; + +} diff --git a/nixpkgs/nixos/modules/services/misc/phd.nix b/nixpkgs/nixos/modules/services/misc/phd.nix new file mode 100644 index 000000000000..e605ce5de16e --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/phd.nix @@ -0,0 +1,52 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.phd; + +in + +{ + + ###### interface + + options = { + + services.phd = { + + enable = mkOption { + default = false; + description = " + Enable daemons for phabricator. + "; + }; + + }; + + }; + + ###### implementation + + config = mkIf cfg.enable { + + systemd.services.phd = { + path = [ pkgs.phabricator pkgs.php pkgs.mercurial pkgs.git pkgs.subversion ]; + + after = [ "httpd.service" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + ExecStart = "${pkgs.phabricator}/phabricator/bin/phd start"; + ExecStop = "${pkgs.phabricator}/phabricator/bin/phd stop"; + User = "wwwrun"; + RestartSec = "30s"; + Restart = "always"; + StartLimitInterval = "1m"; + }; + }; + + }; + +} diff --git a/nixpkgs/nixos/modules/services/misc/plex.nix b/nixpkgs/nixos/modules/services/misc/plex.nix new file mode 100644 index 000000000000..7efadf1b9bb1 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/plex.nix @@ -0,0 +1,151 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.plex; +in +{ + options = { + services.plex = { + enable = mkEnableOption "Plex Media Server"; + + dataDir = mkOption { + type = types.str; + default = "/var/lib/plex"; + description = '' + The directory where Plex stores its data files. + ''; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = '' + Open ports in the firewall for the media server. + ''; + }; + + user = mkOption { + type = types.str; + default = "plex"; + description = '' + User account under which Plex runs. + ''; + }; + + group = mkOption { + type = types.str; + default = "plex"; + description = '' + Group under which Plex runs. + ''; + }; + + managePlugins = mkOption { + type = types.bool; + default = true; + description = '' + If set to true, this option will cause all of the symlinks in Plex's + plugin directory to be removed and symlinks for paths specified in + <option>extraPlugins</option> to be added. + ''; + }; + + extraPlugins = mkOption { + type = types.listOf types.path; + default = []; + description = '' + A list of paths to extra plugin bundles to install in Plex's plugin + directory. Every time the systemd unit for Plex starts up, all of the + symlinks in Plex's plugin directory will be cleared and this module + will symlink all of the paths specified here to that directory. If + this behavior is undesired, set <option>managePlugins</option> to + false. + ''; + }; + + package = mkOption { + type = types.package; + default = pkgs.plex; + defaultText = "pkgs.plex"; + description = '' + The Plex package to use. Plex subscribers may wish to use their own + package here, pointing to subscriber-only server versions. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + # Most of this is just copied from the RPM package's systemd service file. + systemd.services.plex = { + description = "Plex Media Server"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + + # Run the pre-start script with full permissions (the "!" prefix) so it + # can create the data directory if necessary. + ExecStartPre = let + preStartScript = pkgs.writeScript "plex-run-prestart" '' + #!${pkgs.bash}/bin/bash + + # Create data directory if it doesn't exist + if ! test -d "$PLEX_DATADIR"; then + echo "Creating initial Plex data directory in: $PLEX_DATADIR" + install -d -m 0755 -o "${cfg.user}" -g "${cfg.group}" "$PLEX_DATADIR" + fi + ''; + in + "!${preStartScript}"; + + ExecStart = "${cfg.package}/bin/plexmediaserver"; + KillSignal = "SIGQUIT"; + Restart = "on-failure"; + }; + + environment = { + # Configuration for our FHS userenv script + PLEX_DATADIR=cfg.dataDir; + PLEX_PLUGINS=concatMapStringsSep ":" builtins.toString cfg.extraPlugins; + + # The following variables should be set by the FHS userenv script: + # PLEX_MEDIA_SERVER_APPLICATION_SUPPORT_DIR + # PLEX_MEDIA_SERVER_HOME + + # Allow access to GPU acceleration; the Plex LD_LIBRARY_PATH is added + # by the FHS userenv script. + LD_LIBRARY_PATH="/run/opengl-driver/lib"; + + PLEX_MEDIA_SERVER_MAX_PLUGIN_PROCS="6"; + PLEX_MEDIA_SERVER_TMPDIR="/tmp"; + PLEX_MEDIA_SERVER_USE_SYSLOG="true"; + LC_ALL="en_US.UTF-8"; + LANG="en_US.UTF-8"; + }; + }; + + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ 32400 3005 8324 32469 ]; + allowedUDPPorts = [ 1900 5353 32410 32412 32413 32414 ]; + }; + + users.users = mkIf (cfg.user == "plex") { + plex = { + group = cfg.group; + uid = config.ids.uids.plex; + }; + }; + + users.groups = mkIf (cfg.group == "plex") { + plex = { + gid = config.ids.gids.plex; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/pykms.nix b/nixpkgs/nixos/modules/services/misc/pykms.nix new file mode 100644 index 000000000000..ef90d124a284 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/pykms.nix @@ -0,0 +1,77 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.pykms; + +in { + meta.maintainers = with lib.maintainers; [ peterhoeg ]; + + options = { + services.pykms = rec { + enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable the PyKMS service."; + }; + + listenAddress = mkOption { + type = types.str; + default = "0.0.0.0"; + description = "The IP address on which to listen."; + }; + + port = mkOption { + type = types.int; + default = 1688; + description = "The port on which to listen."; + }; + + verbose = mkOption { + type = types.bool; + default = false; + description = "Show verbose output."; + }; + + openFirewallPort = mkOption { + type = types.bool; + default = false; + description = "Whether the listening port should be opened automatically."; + }; + + memoryLimit = mkOption { + type = types.str; + default = "64M"; + description = "How much memory to use at most."; + }; + }; + }; + + config = mkIf cfg.enable { + networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewallPort [ cfg.port ]; + + systemd.services.pykms = let + home = "/var/lib/pykms"; + in { + description = "Python KMS"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + # python programs with DynamicUser = true require HOME to be set + environment.HOME = home; + serviceConfig = with pkgs; { + DynamicUser = true; + StateDirectory = baseNameOf home; + ExecStartPre = "${getBin pykms}/bin/create_pykms_db.sh ${home}/clients.db"; + ExecStart = lib.concatStringsSep " " ([ + "${getBin pykms}/bin/server.py" + cfg.listenAddress + (toString cfg.port) + ] ++ lib.optional cfg.verbose "--verbose"); + WorkingDirectory = home; + Restart = "on-failure"; + MemoryLimit = cfg.memoryLimit; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/radarr.nix b/nixpkgs/nixos/modules/services/misc/radarr.nix new file mode 100644 index 000000000000..74444e24043f --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/radarr.nix @@ -0,0 +1,75 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.radarr; + +in +{ + options = { + services.radarr = { + enable = mkEnableOption "Radarr"; + + dataDir = mkOption { + type = types.str; + default = "/var/lib/radarr/.config/Radarr"; + description = "The directory where Radarr stores its data files."; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = "Open ports in the firewall for the Radarr web interface."; + }; + + user = mkOption { + type = types.str; + default = "radarr"; + description = "User account under which Radarr runs."; + }; + + group = mkOption { + type = types.str; + default = "radarr"; + description = "Group under which Radarr runs."; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.tmpfiles.rules = [ + "d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -" + ]; + + systemd.services.radarr = { + description = "Radarr"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + ExecStart = "${pkgs.radarr}/bin/Radarr -nobrowser -data='${cfg.dataDir}'"; + Restart = "on-failure"; + }; + }; + + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ 7878 ]; + }; + + users.users = mkIf (cfg.user == "radarr") { + radarr = { + group = cfg.group; + home = cfg.dataDir; + uid = config.ids.uids.radarr; + }; + }; + + users.groups = mkIf (cfg.group == "radarr") { + radarr.gid = config.ids.gids.radarr; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/redmine.nix b/nixpkgs/nixos/modules/services/misc/redmine.nix new file mode 100644 index 000000000000..91ddf2c3edf3 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/redmine.nix @@ -0,0 +1,365 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.redmine; + + bundle = "${cfg.package}/share/redmine/bin/bundle"; + + databaseYml = pkgs.writeText "database.yml" '' + production: + adapter: ${cfg.database.type} + database: ${cfg.database.name} + host: ${cfg.database.host} + port: ${toString cfg.database.port} + username: ${cfg.database.user} + password: #dbpass# + ${optionalString (cfg.database.socket != null) "socket: ${cfg.database.socket}"} + ''; + + configurationYml = pkgs.writeText "configuration.yml" '' + default: + scm_subversion_command: ${pkgs.subversion}/bin/svn + scm_mercurial_command: ${pkgs.mercurial}/bin/hg + scm_git_command: ${pkgs.gitAndTools.git}/bin/git + scm_cvs_command: ${pkgs.cvs}/bin/cvs + scm_bazaar_command: ${pkgs.bazaar}/bin/bzr + scm_darcs_command: ${pkgs.darcs}/bin/darcs + + ${cfg.extraConfig} + ''; + + additionalEnvironment = pkgs.writeText "additional_environment.rb" '' + config.logger = Logger.new("${cfg.stateDir}/log/production.log", 14, 1048576) + config.logger.level = Logger::INFO + + ${cfg.extraEnv} + ''; + + unpackTheme = unpack "theme"; + unpackPlugin = unpack "plugin"; + unpack = id: (name: source: + pkgs.stdenv.mkDerivation { + name = "redmine-${id}-${name}"; + buildInputs = [ pkgs.unzip ]; + buildCommand = '' + mkdir -p $out + cd $out + unpackFile ${source} + ''; + }); + +in + +{ + options = { + services.redmine = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable the Redmine service."; + }; + + # default to the 4.x series not forcing major version upgrade of those on the 3.x series + package = mkOption { + type = types.package; + default = if versionAtLeast config.system.stateVersion "19.03" + then pkgs.redmine_4 + else pkgs.redmine + ; + defaultText = "pkgs.redmine"; + description = '' + Which Redmine package to use. This defaults to version 3.x if + <literal>system.stateVersion < 19.03</literal> and version 4.x + otherwise. + ''; + example = "pkgs.redmine_4.override { ruby = pkgs.ruby_2_4; }"; + }; + + user = mkOption { + type = types.str; + default = "redmine"; + description = "User under which Redmine is ran."; + }; + + group = mkOption { + type = types.str; + default = "redmine"; + description = "Group under which Redmine is ran."; + }; + + port = mkOption { + type = types.int; + default = 3000; + description = "Port on which Redmine is ran."; + }; + + stateDir = mkOption { + type = types.str; + default = "/var/lib/redmine"; + description = "The state directory, logs and plugins are stored here."; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Extra configuration in configuration.yml. + + See https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration + ''; + example = literalExample '' + email_delivery: + delivery_method: smtp + smtp_settings: + address: mail.example.com + port: 25 + ''; + }; + + extraEnv = mkOption { + type = types.lines; + default = ""; + description = '' + Extra configuration in additional_environment.rb. + + See https://svn.redmine.org/redmine/trunk/config/additional_environment.rb.example + ''; + example = literalExample '' + config.logger.level = Logger::DEBUG + ''; + }; + + themes = mkOption { + type = types.attrsOf types.path; + default = {}; + description = "Set of themes."; + example = literalExample '' + { + dkuk-redmine_alex_skin = builtins.fetchurl { + url = https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip; + sha256 = "0hrin9lzyi50k4w2bd2b30vrf1i4fi1c0gyas5801wn8i7kpm9yl"; + }; + } + ''; + }; + + plugins = mkOption { + type = types.attrsOf types.path; + default = {}; + description = "Set of plugins."; + example = literalExample '' + { + redmine_env_auth = builtins.fetchurl { + url = https://github.com/Intera/redmine_env_auth/archive/0.6.zip; + sha256 = "0yyr1yjd8gvvh832wdc8m3xfnhhxzk2pk3gm2psg5w9jdvd6skak"; + }; + } + ''; + }; + + database = { + type = mkOption { + type = types.enum [ "mysql2" "postgresql" ]; + example = "postgresql"; + default = "mysql2"; + description = "Database engine to use."; + }; + + host = mkOption { + type = types.str; + default = (if cfg.database.socket != null then "localhost" else "127.0.0.1"); + description = "Database host address."; + }; + + port = mkOption { + type = types.int; + default = 3306; + description = "Database host port."; + }; + + name = mkOption { + type = types.str; + default = "redmine"; + description = "Database name."; + }; + + user = mkOption { + type = types.str; + default = "redmine"; + description = "Database user."; + }; + + password = mkOption { + type = types.str; + default = ""; + description = '' + The password corresponding to <option>database.user</option>. + Warning: this is stored in cleartext in the Nix store! + Use <option>database.passwordFile</option> instead. + ''; + }; + + passwordFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/keys/redmine-dbpassword"; + description = '' + A file containing the password corresponding to + <option>database.user</option>. + ''; + }; + + socket = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/mysqld/mysqld.sock"; + description = "Path to the unix socket file to use for authentication."; + }; + }; + }; + }; + + config = mkIf cfg.enable { + + assertions = [ + { assertion = cfg.database.passwordFile != null || cfg.database.password != "" || cfg.database.socket != null; + message = "one of services.redmine.database.socket, services.redmine.database.passwordFile, or services.redmine.database.password must be set"; + } + { assertion = cfg.database.socket != null -> (cfg.database.type == "mysql2"); + message = "Socket authentication is only available for the mysql2 database type"; + } + ]; + + environment.systemPackages = [ cfg.package ]; + + # create symlinks for the basic directory layout the redmine package expects + systemd.tmpfiles.rules = [ + "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -" + "d '${cfg.stateDir}/cache' 0750 ${cfg.user} ${cfg.group} - -" + "d '${cfg.stateDir}/config' 0750 ${cfg.user} ${cfg.group} - -" + "d '${cfg.stateDir}/files' 0750 ${cfg.user} ${cfg.group} - -" + "d '${cfg.stateDir}/log' 0750 ${cfg.user} ${cfg.group} - -" + "d '${cfg.stateDir}/plugins' 0750 ${cfg.user} ${cfg.group} - -" + "d '${cfg.stateDir}/public' 0750 ${cfg.user} ${cfg.group} - -" + "d '${cfg.stateDir}/public/plugin_assets' 0750 ${cfg.user} ${cfg.group} - -" + "d '${cfg.stateDir}/public/themes' 0750 ${cfg.user} ${cfg.group} - -" + "d '${cfg.stateDir}/tmp' 0750 ${cfg.user} ${cfg.group} - -" + + "d /run/redmine - - - - -" + "d /run/redmine/public - - - - -" + "L+ /run/redmine/config - - - - ${cfg.stateDir}/config" + "L+ /run/redmine/files - - - - ${cfg.stateDir}/files" + "L+ /run/redmine/log - - - - ${cfg.stateDir}/log" + "L+ /run/redmine/plugins - - - - ${cfg.stateDir}/plugins" + "L+ /run/redmine/public/plugin_assets - - - - ${cfg.stateDir}/public/plugin_assets" + "L+ /run/redmine/public/themes - - - - ${cfg.stateDir}/public/themes" + "L+ /run/redmine/tmp - - - - ${cfg.stateDir}/tmp" + ]; + + systemd.services.redmine = { + after = [ "network.target" (if cfg.database.type == "mysql2" then "mysql.service" else "postgresql.service") ]; + wantedBy = [ "multi-user.target" ]; + environment.RAILS_ENV = "production"; + environment.RAILS_CACHE = "${cfg.stateDir}/cache"; + environment.REDMINE_LANG = "en"; + environment.SCHEMA = "${cfg.stateDir}/cache/schema.db"; + path = with pkgs; [ + imagemagick + bazaar + cvs + darcs + gitAndTools.git + mercurial + subversion + ]; + preStart = '' + rm -rf "${cfg.stateDir}/plugins/"* + rm -rf "${cfg.stateDir}/public/themes/"* + + # start with a fresh config directory + # the config directory is copied instead of linked as some mutable data is stored in there + find "${cfg.stateDir}/config" ! -name "secret_token.rb" -type f -exec rm -f {} + + cp -r ${cfg.package}/share/redmine/config.dist/* "${cfg.stateDir}/config/" + + chmod -R u+w "${cfg.stateDir}/config" + + # link in the application configuration + ln -fs ${configurationYml} "${cfg.stateDir}/config/configuration.yml" + + # link in the additional environment configuration + ln -fs ${additionalEnvironment} "${cfg.stateDir}/config/additional_environment.rb" + + + # link in all user specified themes + for theme in ${concatStringsSep " " (mapAttrsToList unpackTheme cfg.themes)}; do + ln -fs $theme/* "${cfg.stateDir}/public/themes" + done + + # link in redmine provided themes + ln -sf ${cfg.package}/share/redmine/public/themes.dist/* "${cfg.stateDir}/public/themes/" + + + # link in all user specified plugins + for plugin in ${concatStringsSep " " (mapAttrsToList unpackPlugin cfg.plugins)}; do + ln -fs $plugin/* "${cfg.stateDir}/plugins/''${plugin##*-redmine-plugin-}" + done + + + # handle database.passwordFile & permissions + DBPASS=$(head -n1 ${cfg.database.passwordFile}) + cp -f ${databaseYml} "${cfg.stateDir}/config/database.yml" + sed -e "s,#dbpass#,$DBPASS,g" -i "${cfg.stateDir}/config/database.yml" + chmod 440 "${cfg.stateDir}/config/database.yml" + + + # generate a secret token if required + if ! test -e "${cfg.stateDir}/config/initializers/secret_token.rb"; then + ${bundle} exec rake generate_secret_token + chmod 440 "${cfg.stateDir}/config/initializers/secret_token.rb" + fi + + # execute redmine required commands prior to starting the application + ${bundle} exec rake db:migrate + ${bundle} exec rake redmine:plugins:migrate + ${bundle} exec rake redmine:load_default_data + ''; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + TimeoutSec = "300"; + WorkingDirectory = "${cfg.package}/share/redmine"; + ExecStart="${bundle} exec rails server webrick -e production -p ${toString cfg.port} -P '${cfg.stateDir}/redmine.pid'"; + }; + + }; + + users.users = optionalAttrs (cfg.user == "redmine") (singleton + { name = "redmine"; + group = cfg.group; + home = cfg.stateDir; + uid = config.ids.uids.redmine; + }); + + users.groups = optionalAttrs (cfg.group == "redmine") (singleton + { name = "redmine"; + gid = config.ids.gids.redmine; + }); + + warnings = optional (cfg.database.password != "") + ''config.services.redmine.database.password will be stored as plaintext + in the Nix store. Use database.passwordFile instead.''; + + # Create database passwordFile default when password is configured. + services.redmine.database.passwordFile = + (mkDefault (toString (pkgs.writeTextFile { + name = "redmine-database-password"; + text = cfg.database.password; + }))); + + }; + +} diff --git a/nixpkgs/nixos/modules/services/misc/ripple-data-api.nix b/nixpkgs/nixos/modules/services/misc/ripple-data-api.nix new file mode 100644 index 000000000000..042b496d35ee --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/ripple-data-api.nix @@ -0,0 +1,194 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.rippleDataApi; + + deployment_env_config = builtins.toJSON { + production = { + port = toString cfg.port; + maxSockets = 150; + batchSize = 100; + startIndex = 32570; + rippleds = cfg.rippleds; + redis = { + enable = cfg.redis.enable; + host = cfg.redis.host; + port = cfg.redis.port; + options.auth_pass = null; + }; + }; + }; + + db_config = builtins.toJSON { + production = { + username = optional (cfg.couchdb.pass != "") cfg.couchdb.user; + password = optional (cfg.couchdb.pass != "") cfg.couchdb.pass; + host = cfg.couchdb.host; + port = cfg.couchdb.port; + database = cfg.couchdb.db; + protocol = "http"; + }; + }; + +in { + options = { + services.rippleDataApi = { + enable = mkEnableOption "ripple data api"; + + port = mkOption { + description = "Ripple data api port"; + default = 5993; + type = types.int; + }; + + importMode = mkOption { + description = "Ripple data api import mode."; + default = "liveOnly"; + type = types.enum ["live" "liveOnly"]; + }; + + minLedger = mkOption { + description = "Ripple data api minimal ledger to fetch."; + default = null; + type = types.nullOr types.int; + }; + + maxLedger = mkOption { + description = "Ripple data api maximal ledger to fetch."; + default = null; + type = types.nullOr types.int; + }; + + redis = { + enable = mkOption { + description = "Whether to enable caching of ripple data to redis."; + default = true; + type = types.bool; + }; + + host = mkOption { + description = "Ripple data api redis host."; + default = "localhost"; + type = types.str; + }; + + port = mkOption { + description = "Ripple data api redis port."; + default = 5984; + type = types.int; + }; + }; + + couchdb = { + host = mkOption { + description = "Ripple data api couchdb host."; + default = "localhost"; + type = types.str; + }; + + port = mkOption { + description = "Ripple data api couchdb port."; + default = 5984; + type = types.int; + }; + + db = mkOption { + description = "Ripple data api couchdb database."; + default = "rippled"; + type = types.str; + }; + + user = mkOption { + description = "Ripple data api couchdb username."; + default = "rippled"; + type = types.str; + }; + + pass = mkOption { + description = "Ripple data api couchdb password."; + default = ""; + type = types.str; + }; + + create = mkOption { + description = "Whether to create couchdb database needed by ripple data api."; + type = types.bool; + default = true; + }; + }; + + rippleds = mkOption { + description = "List of rippleds to be used by ripple data api."; + default = [ + "http://s_east.ripple.com:51234" + "http://s_west.ripple.com:51234" + ]; + type = types.listOf types.str; + }; + }; + }; + + config = mkIf (cfg.enable) { + services.couchdb.enable = mkDefault true; + services.couchdb.bindAddress = mkDefault "0.0.0.0"; + services.redis.enable = mkDefault true; + + systemd.services.ripple-data-api = { + after = [ "couchdb.service" "redis.service" "ripple-data-api-importer.service" ]; + wantedBy = [ "multi-user.target" ]; + + environment = { + NODE_ENV = "production"; + DEPLOYMENT_ENVS_CONFIG = pkgs.writeText "deployment.environment.json" deployment_env_config; + DB_CONFIG = pkgs.writeText "db.config.json" db_config; + }; + + serviceConfig = { + ExecStart = "${pkgs.ripple-data-api}/bin/api"; + Restart = "always"; + User = "ripple-data-api"; + }; + }; + + systemd.services.ripple-data-importer = { + after = [ "couchdb.service" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pkgs.curl ]; + + environment = { + NODE_ENV = "production"; + DEPLOYMENT_ENVS_CONFIG = pkgs.writeText "deployment.environment.json" deployment_env_config; + DB_CONFIG = pkgs.writeText "db.config.json" db_config; + LOG_FILE = "/dev/null"; + }; + + serviceConfig = let + importMode = + if cfg.minLedger != null && cfg.maxLedger != null then + "${toString cfg.minLedger} ${toString cfg.maxLedger}" + else + cfg.importMode; + in { + ExecStart = "${pkgs.ripple-data-api}/bin/importer ${importMode} debug"; + Restart = "always"; + User = "ripple-data-api"; + }; + + preStart = mkMerge [ + (mkIf (cfg.couchdb.create) '' + HOST="http://${optionalString (cfg.couchdb.pass != "") "${cfg.couchdb.user}:${cfg.couchdb.pass}@"}${cfg.couchdb.host}:${toString cfg.couchdb.port}" + curl -X PUT $HOST/${cfg.couchdb.db} || true + '') + "${pkgs.ripple-data-api}/bin/update-views" + ]; + }; + + users.users = singleton + { name = "ripple-data-api"; + description = "Ripple data api user"; + uid = config.ids.uids.ripple-data-api; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/rippled.nix b/nixpkgs/nixos/modules/services/misc/rippled.nix new file mode 100644 index 000000000000..cdf61730de33 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/rippled.nix @@ -0,0 +1,432 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.rippled; + + b2i = val: if val then "1" else "0"; + + dbCfg = db: '' + type=${db.type} + path=${db.path} + ${optionalString (db.compression != null) ("compression=${b2i db.compression}") } + ${optionalString (db.onlineDelete != null) ("online_delete=${toString db.onlineDelete}")} + ${optionalString (db.advisoryDelete != null) ("advisory_delete=${b2i db.advisoryDelete}")} + ${db.extraOpts} + ''; + + rippledCfg = '' + [server] + ${concatMapStringsSep "\n" (n: "port_${n}") (attrNames cfg.ports)} + + ${concatMapStrings (p: '' + [port_${p.name}] + ip=${p.ip} + port=${toString p.port} + protocol=${concatStringsSep "," p.protocol} + ${optionalString (p.user != "") "user=${p.user}"} + ${optionalString (p.password != "") "user=${p.password}"} + admin=${concatStringsSep "," p.admin} + ${optionalString (p.ssl.key != null) "ssl_key=${p.ssl.key}"} + ${optionalString (p.ssl.cert != null) "ssl_cert=${p.ssl.cert}"} + ${optionalString (p.ssl.chain != null) "ssl_chain=${p.ssl.chain}"} + '') (attrValues cfg.ports)} + + [database_path] + ${cfg.databasePath} + + [node_db] + ${dbCfg cfg.nodeDb} + + ${optionalString (cfg.tempDb != null) '' + [temp_db] + ${dbCfg cfg.tempDb}''} + + ${optionalString (cfg.importDb != null) '' + [import_db] + ${dbCfg cfg.importDb}''} + + [ips] + ${concatStringsSep "\n" cfg.ips} + + [ips_fixed] + ${concatStringsSep "\n" cfg.ipsFixed} + + [validators] + ${concatStringsSep "\n" cfg.validators} + + [node_size] + ${cfg.nodeSize} + + [ledger_history] + ${toString cfg.ledgerHistory} + + [fetch_depth] + ${toString cfg.fetchDepth} + + [validation_quorum] + ${toString cfg.validationQuorum} + + [sntp_servers] + ${concatStringsSep "\n" cfg.sntpServers} + + ${optionalString cfg.statsd.enable '' + [insight] + server=statsd + address=${cfg.statsd.address} + prefix=${cfg.statsd.prefix} + ''} + + [rpc_startup] + { "command": "log_level", "severity": "${cfg.logLevel}" } + '' + cfg.extraConfig; + + portOptions = { name, ...}: { + options = { + name = mkOption { + internal = true; + default = name; + }; + + ip = mkOption { + default = "127.0.0.1"; + description = "Ip where rippled listens."; + type = types.str; + }; + + port = mkOption { + description = "Port where rippled listens."; + type = types.int; + }; + + protocol = mkOption { + description = "Protocols expose by rippled."; + type = types.listOf (types.enum ["http" "https" "ws" "wss" "peer"]); + }; + + user = mkOption { + description = "When set, these credentials will be required on HTTP/S requests."; + type = types.str; + default = ""; + }; + + password = mkOption { + description = "When set, these credentials will be required on HTTP/S requests."; + type = types.str; + default = ""; + }; + + admin = mkOption { + description = "A comma-separated list of admin IP addresses."; + type = types.listOf types.str; + default = ["127.0.0.1"]; + }; + + ssl = { + key = mkOption { + description = '' + Specifies the filename holding the SSL key in PEM format. + ''; + default = null; + type = types.nullOr types.path; + }; + + cert = mkOption { + description = '' + Specifies the path to the SSL certificate file in PEM format. + This is not needed if the chain includes it. + ''; + default = null; + type = types.nullOr types.path; + }; + + chain = mkOption { + description = '' + If you need a certificate chain, specify the path to the + certificate chain here. The chain may include the end certificate. + ''; + default = null; + type = types.nullOr types.path; + }; + }; + }; + }; + + dbOptions = { + options = { + type = mkOption { + description = "Rippled database type."; + type = types.enum ["rocksdb" "nudb"]; + default = "rocksdb"; + }; + + path = mkOption { + description = "Location to store the database."; + type = types.path; + default = cfg.databasePath; + }; + + compression = mkOption { + description = "Whether to enable snappy compression."; + type = types.nullOr types.bool; + default = null; + }; + + onlineDelete = mkOption { + description = "Enable automatic purging of older ledger information."; + type = types.nullOr (types.addCheck types.int (v: v > 256)); + default = cfg.ledgerHistory; + }; + + advisoryDelete = mkOption { + description = '' + If set, then require administrative RPC call "can_delete" + to enable online deletion of ledger records. + ''; + type = types.nullOr types.bool; + default = null; + }; + + extraOpts = mkOption { + description = "Extra database options."; + type = types.lines; + default = ""; + }; + }; + }; + +in + +{ + + ###### interface + + options = { + services.rippled = { + enable = mkEnableOption "rippled"; + + package = mkOption { + description = "Which rippled package to use."; + type = types.package; + default = pkgs.rippled; + defaultText = "pkgs.rippled"; + }; + + ports = mkOption { + description = "Ports exposed by rippled"; + type = with types; attrsOf (submodule portOptions); + default = { + rpc = { + port = 5005; + admin = ["127.0.0.1"]; + protocol = ["http"]; + }; + + peer = { + port = 51235; + ip = "0.0.0.0"; + protocol = ["peer"]; + }; + + ws_public = { + port = 5006; + ip = "0.0.0.0"; + protocol = ["ws" "wss"]; + }; + }; + }; + + nodeDb = mkOption { + description = "Rippled main database options."; + type = with types; nullOr (submodule dbOptions); + default = { + type = "rocksdb"; + extraOpts = '' + open_files=2000 + filter_bits=12 + cache_mb=256 + file_size_pb=8 + file_size_mult=2; + ''; + }; + }; + + tempDb = mkOption { + description = "Rippled temporary database options."; + type = with types; nullOr (submodule dbOptions); + default = null; + }; + + importDb = mkOption { + description = "Settings for performing a one-time import."; + type = with types; nullOr (submodule dbOptions); + default = null; + }; + + nodeSize = mkOption { + description = '' + Rippled size of the node you are running. + "tiny", "small", "medium", "large", and "huge" + ''; + type = types.enum ["tiny" "small" "medium" "large" "huge"]; + default = "small"; + }; + + ips = mkOption { + description = '' + List of hostnames or ips where the Ripple protocol is served. + For a starter list, you can either copy entries from: + https://ripple.com/ripple.txt or if you prefer you can let it + default to r.ripple.com 51235 + + A port may optionally be specified after adding a space to the + address. By convention, if known, IPs are listed in from most + to least trusted. + ''; + type = types.listOf types.str; + default = ["r.ripple.com 51235"]; + }; + + ipsFixed = mkOption { + description = '' + List of IP addresses or hostnames to which rippled should always + attempt to maintain peer connections with. This is useful for + manually forming private networks, for example to configure a + validation server that connects to the Ripple network through a + public-facing server, or for building a set of cluster peers. + + A port may optionally be specified after adding a space to the address + ''; + type = types.listOf types.str; + default = []; + }; + + validators = mkOption { + description = '' + List of nodes to always accept as validators. Nodes are specified by domain + or public key. + ''; + type = types.listOf types.str; + default = [ + "n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 RL1" + "n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj RL2" + "n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C RL3" + "n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS RL4" + "n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5" + ]; + }; + + databasePath = mkOption { + description = '' + Path to the ripple database. + ''; + type = types.path; + default = "/var/lib/rippled"; + }; + + validationQuorum = mkOption { + description = '' + The minimum number of trusted validations a ledger must have before + the server considers it fully validated. + ''; + type = types.int; + default = 3; + }; + + ledgerHistory = mkOption { + description = '' + The number of past ledgers to acquire on server startup and the minimum + to maintain while running. + ''; + type = types.either types.int (types.enum ["full"]); + default = 1296000; # 1 month + }; + + fetchDepth = mkOption { + description = '' + The number of past ledgers to serve to other peers that request historical + ledger data (or "full" for no limit). + ''; + type = types.either types.int (types.enum ["full"]); + default = "full"; + }; + + sntpServers = mkOption { + description = '' + IP address or domain of NTP servers to use for time synchronization.; + ''; + type = types.listOf types.str; + default = [ + "time.windows.com" + "time.apple.com" + "time.nist.gov" + "pool.ntp.org" + ]; + }; + + logLevel = mkOption { + description = "Logging verbosity."; + type = types.enum ["debug" "error" "info"]; + default = "error"; + }; + + statsd = { + enable = mkEnableOption "statsd monitoring for rippled"; + + address = mkOption { + description = "The UDP address and port of the listening StatsD server."; + default = "127.0.0.1:8125"; + type = types.str; + }; + + prefix = mkOption { + description = "A string prepended to each collected metric."; + default = ""; + type = types.str; + }; + }; + + extraConfig = mkOption { + default = ""; + description = '' + Extra lines to be added verbatim to the rippled.cfg configuration file. + ''; + }; + + config = mkOption { + internal = true; + default = pkgs.writeText "rippled.conf" rippledCfg; + }; + }; + }; + + + ###### implementation + + config = mkIf cfg.enable { + + users.users = singleton + { name = "rippled"; + description = "Ripple server user"; + uid = config.ids.uids.rippled; + home = cfg.databasePath; + createHome = true; + }; + + systemd.services.rippled = { + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + ExecStart = "${cfg.package}/bin/rippled --fg --conf ${cfg.config}"; + User = "rippled"; + Restart = "on-failure"; + LimitNOFILE=10000; + }; + }; + + environment.systemPackages = [ cfg.package ]; + + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/rogue.nix b/nixpkgs/nixos/modules/services/misc/rogue.nix new file mode 100644 index 000000000000..aae02e384c97 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/rogue.nix @@ -0,0 +1,62 @@ +# Execute the game `rogue' on tty 9. Mostly used by the NixOS +# installation CD. + +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.rogue; + +in + +{ + ###### interface + + options = { + + services.rogue.enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable the Rogue game on one of the virtual + consoles. + ''; + }; + + services.rogue.tty = mkOption { + type = types.str; + default = "tty9"; + description = '' + Virtual console on which to run Rogue. + ''; + }; + + }; + + + ###### implementation + + config = mkIf cfg.enable { + + boot.extraTTYs = [ cfg.tty ]; + + systemd.services.rogue = + { description = "Rogue dungeon crawling game"; + wantedBy = [ "multi-user.target" ]; + serviceConfig = + { ExecStart = "${pkgs.rogue}/bin/rogue"; + StandardInput = "tty"; + StandardOutput = "tty"; + TTYPath = "/dev/${cfg.tty}"; + TTYReset = true; + TTYVTDisallocate = true; + WorkingDirectory = "/tmp"; + Restart = "always"; + }; + }; + + }; + +} diff --git a/nixpkgs/nixos/modules/services/misc/safeeyes.nix b/nixpkgs/nixos/modules/services/misc/safeeyes.nix new file mode 100644 index 000000000000..1a33971d9227 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/safeeyes.nix @@ -0,0 +1,50 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.safeeyes; + +in + +{ + + ###### interface + + options = { + + services.safeeyes = { + + enable = mkOption { + default = false; + description = "Whether to enable the safeeyes OSGi service"; + }; + + }; + + }; + + ###### implementation + + config = mkIf cfg.enable { + + systemd.user.services.safeeyes = { + description = "Safeeyes"; + + wantedBy = [ "graphical-session.target" ]; + partOf = [ "graphical-session.target" ]; + + serviceConfig = { + ExecStart = '' + ${pkgs.safeeyes}/bin/safeeyes + ''; + Restart = "on-failure"; + RestartSec = 3; + StartLimitInterval = 350; + StartLimitBurst = 10; + }; + }; + + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/serviio.nix b/nixpkgs/nixos/modules/services/misc/serviio.nix new file mode 100644 index 000000000000..8808f2d21931 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/serviio.nix @@ -0,0 +1,92 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.serviio; + + serviioStart = pkgs.writeScript "serviio.sh" '' + #!${pkgs.bash}/bin/sh + + SERVIIO_HOME=${pkgs.serviio} + + # Setup the classpath + SERVIIO_CLASS_PATH="$SERVIIO_HOME/lib/*:$SERVIIO_HOME/config" + + # Setup Serviio specific properties + JAVA_OPTS="-Djava.net.preferIPv4Stack=true -Djava.awt.headless=true -Dorg.restlet.engine.loggerFacadeClass=org.restlet.ext.slf4j.Slf4jLoggerFacade + -Dderby.system.home=${cfg.dataDir}/library -Dserviio.home=${cfg.dataDir} -Dffmpeg.location=${pkgs.ffmpeg}/bin/ffmpeg -Ddcraw.location=${pkgs.dcraw}/bin/dcraw" + + # Execute the JVM in the foreground + exec ${pkgs.jre}/bin/java -Xmx512M -Xms20M -XX:+UseG1GC -XX:GCTimeRatio=1 -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=20 $JAVA_OPTS -classpath "$SERVIIO_CLASS_PATH" org.serviio.MediaServer "$@" + ''; + +in { + + ###### interface + options = { + services.serviio = { + + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable the Serviio Media Server. + ''; + }; + + dataDir = mkOption { + type = types.path; + default = "/var/lib/serviio"; + description = '' + The directory where serviio stores its state, data, etc. + ''; + }; + + }; + }; + + ###### implementation + + config = mkIf cfg.enable { + systemd.services.serviio = { + description = "Serviio Media Server"; + after = [ "local-fs.target" "network.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pkgs.serviio ]; + serviceConfig = { + User = "serviio"; + Group = "serviio"; + ExecStart = "${serviioStart}"; + ExecStop = "${serviioStart} -stop"; + }; + }; + + users.users = [ + { + name = "serviio"; + group = "serviio"; + home = cfg.dataDir; + description = "Serviio Media Server User"; + createHome = true; + isSystemUser = true; + } + ]; + + users.groups = [ + { name = "serviio";} + ]; + + networking.firewall = { + allowedTCPPorts = [ + 8895 # serve UPnP responses + 23423 # console + 23424 # mediabrowser + ]; + allowedUDPPorts = [ + 1900 # UPnP service discovey + ]; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/sickbeard.nix b/nixpkgs/nixos/modules/services/misc/sickbeard.nix new file mode 100644 index 000000000000..5cfbbe516ae1 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/sickbeard.nix @@ -0,0 +1,92 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + name = "sickbeard"; + + cfg = config.services.sickbeard; + sickbeard = cfg.package; + +in +{ + + ###### interface + + options = { + services.sickbeard = { + enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable the sickbeard server."; + }; + package = mkOption { + type = types.package; + default = pkgs.sickbeard; + example = literalExample "pkgs.sickrage"; + description ='' + Enable <literal>pkgs.sickrage</literal> or <literal>pkgs.sickgear</literal> + as an alternative to SickBeard + ''; + }; + dataDir = mkOption { + type = types.path; + default = "/var/lib/${name}"; + description = "Path where to store data files."; + }; + configFile = mkOption { + type = types.path; + default = "${cfg.dataDir}/config.ini"; + description = "Path to config file."; + }; + port = mkOption { + type = types.ints.u16; + default = 8081; + description = "Port to bind to."; + }; + user = mkOption { + type = types.str; + default = name; + description = "User to run the service as"; + }; + group = mkOption { + type = types.str; + default = name; + description = "Group to run the service as"; + }; + }; + }; + + + ###### implementation + + config = mkIf cfg.enable { + + users.users = optionalAttrs (cfg.user == name) (singleton { + name = name; + uid = config.ids.uids.sickbeard; + group = cfg.group; + description = "sickbeard user"; + home = cfg.dataDir; + createHome = true; + }); + + users.groups = optionalAttrs (cfg.group == name) (singleton { + name = name; + gid = config.ids.gids.sickbeard; + }); + + systemd.services.sickbeard = { + description = "Sickbeard Server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + User = cfg.user; + Group = cfg.group; + ExecStart = "${sickbeard}/SickBeard.py --datadir ${cfg.dataDir} --config ${cfg.configFile} --port ${toString cfg.port}"; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/siproxd.nix b/nixpkgs/nixos/modules/services/misc/siproxd.nix new file mode 100644 index 000000000000..dcaf73aca448 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/siproxd.nix @@ -0,0 +1,180 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.siproxd; + + conf = '' + daemonize = 0 + rtp_proxy_enable = 1 + user = siproxd + if_inbound = ${cfg.ifInbound} + if_outbound = ${cfg.ifOutbound} + sip_listen_port = ${toString cfg.sipListenPort} + rtp_port_low = ${toString cfg.rtpPortLow} + rtp_port_high = ${toString cfg.rtpPortHigh} + rtp_dscp = ${toString cfg.rtpDscp} + sip_dscp = ${toString cfg.sipDscp} + ${optionalString (cfg.hostsAllowReg != []) "hosts_allow_reg = ${concatStringsSep "," cfg.hostsAllowReg}"} + ${optionalString (cfg.hostsAllowSip != []) "hosts_allow_sip = ${concatStringsSep "," cfg.hostsAllowSip}"} + ${optionalString (cfg.hostsDenySip != []) "hosts_deny_sip = ${concatStringsSep "," cfg.hostsDenySip}"} + ${if (cfg.passwordFile != "") then "proxy_auth_pwfile = ${cfg.passwordFile}" else ""} + ${cfg.extraConfig} + ''; + + confFile = builtins.toFile "siproxd.conf" conf; + +in +{ + ##### interface + + options = { + + services.siproxd = { + + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable the Siproxd SIP + proxy/masquerading daemon. + ''; + }; + + ifInbound = mkOption { + type = types.str; + example = "eth0"; + description = "Local network interface"; + }; + + ifOutbound = mkOption { + type = types.str; + example = "ppp0"; + description = "Public network interface"; + }; + + hostsAllowReg = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "192.168.1.0/24" "192.168.2.0/24" ]; + description = '' + Acess control list for incoming SIP registrations. + ''; + }; + + hostsAllowSip = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "123.45.0.0/16" "123.46.0.0/16" ]; + description = '' + Acess control list for incoming SIP traffic. + ''; + }; + + hostsDenySip = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "10.0.0.0/8" "11.0.0.0/8" ]; + description = '' + Acess control list for denying incoming + SIP registrations and traffic. + ''; + }; + + sipListenPort = mkOption { + type = types.int; + default = 5060; + description = '' + Port to listen for incoming SIP messages. + ''; + }; + + rtpPortLow = mkOption { + type = types.int; + default = 7070; + description = '' + Bottom of UDP port range for incoming and outgoing RTP traffic + ''; + }; + + rtpPortHigh = mkOption { + type = types.int; + default = 7089; + description = '' + Top of UDP port range for incoming and outgoing RTP traffic + ''; + }; + + rtpTimeout = mkOption { + type = types.int; + default = 300; + description = '' + Timeout for an RTP stream. If for the specified + number of seconds no data is relayed on an active + stream, it is considered dead and will be killed. + ''; + }; + + rtpDscp = mkOption { + type = types.int; + default = 46; + description = '' + DSCP (differentiated services) value to be assigned + to RTP packets. Allows QOS aware routers to handle + different types traffic with different priorities. + ''; + }; + + sipDscp = mkOption { + type = types.int; + default = 0; + description = '' + DSCP (differentiated services) value to be assigned + to SIP packets. Allows QOS aware routers to handle + different types traffic with different priorities. + ''; + }; + + passwordFile = mkOption { + type = types.str; + default = ""; + description = '' + Path to per-user password file. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Extra configuration to add to siproxd configuration. + ''; + }; + + }; + + }; + + ##### implementation + + config = mkIf cfg.enable { + + users.users = singleton { + name = "siproxyd"; + uid = config.ids.uids.siproxd; + }; + + systemd.services.siproxd = { + description = "SIP proxy/masquerading daemon"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + ExecStart = "${pkgs.siproxd}/sbin/siproxd -c ${confFile}"; + }; + }; + + }; + +} diff --git a/nixpkgs/nixos/modules/services/misc/snapper.nix b/nixpkgs/nixos/modules/services/misc/snapper.nix new file mode 100644 index 000000000000..62b344d11b06 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/snapper.nix @@ -0,0 +1,152 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.snapper; +in + +{ + options.services.snapper = { + + snapshotInterval = mkOption { + type = types.str; + default = "hourly"; + description = '' + Snapshot interval. + + The format is described in + <citerefentry><refentrytitle>systemd.time</refentrytitle> + <manvolnum>7</manvolnum></citerefentry>. + ''; + }; + + cleanupInterval = mkOption { + type = types.str; + default = "1d"; + description = '' + Cleanup interval. + + The format is described in + <citerefentry><refentrytitle>systemd.time</refentrytitle> + <manvolnum>7</manvolnum></citerefentry>. + ''; + }; + + filters = mkOption { + type = types.nullOr types.lines; + default = null; + description = '' + Global display difference filter. See man:snapper(8) for more details. + ''; + }; + + configs = mkOption { + default = { }; + example = literalExample { + "home" = { + subvolume = "/home"; + extraConfig = '' + ALLOW_USERS="alice" + ''; + }; + }; + + description = '' + Subvolume configuration + ''; + + type = types.attrsOf (types.submodule { + options = { + subvolume = mkOption { + type = types.path; + description = '' + Path of the subvolume or mount point. + This path is a subvolume and has to contain a subvolume named + .snapshots. + See also man:snapper(8) section PERMISSIONS. + ''; + }; + + fstype = mkOption { + type = types.enum [ "btrfs" ]; + default = "btrfs"; + description = '' + Filesystem type. Only btrfs is stable and tested. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Additional configuration next to SUBVOLUME and FSTYPE. + See man:snapper-configs(5). + ''; + }; + }; + }); + }; + }; + + config = mkIf (cfg.configs != {}) (let + documentation = [ "man:snapper(8)" "man:snapper-configs(5)" ]; + in { + + environment = { + + systemPackages = [ pkgs.snapper ]; + + # Note: snapper/config-templates/default is only needed for create-config + # which is not the NixOS way to configure. + etc = { + + "sysconfig/snapper".text = '' + SNAPPER_CONFIGS="${lib.concatStringsSep " " (builtins.attrNames cfg.configs)}" + ''; + + } + // (mapAttrs' (name: subvolume: nameValuePair "snapper/configs/${name}" ({ + text = '' + ${subvolume.extraConfig} + FSTYPE="${subvolume.fstype}" + SUBVOLUME="${subvolume.subvolume}" + ''; + })) cfg.configs) + // (lib.optionalAttrs (cfg.filters != null) { + "snapper/filters/default.txt".text = cfg.filters; + }); + + }; + + services.dbus.packages = [ pkgs.snapper ]; + + systemd.services.snapper-timeline = { + description = "Timeline of Snapper Snapshots"; + inherit documentation; + serviceConfig.ExecStart = "${pkgs.snapper}/lib/snapper/systemd-helper --timeline"; + }; + + systemd.timers.snapper-timeline = { + description = "Timeline of Snapper Snapshots"; + inherit documentation; + wantedBy = [ "basic.target" ]; + timerConfig.OnCalendar = cfg.snapshotInterval; + }; + + systemd.services.snapper-cleanup = { + description = "Cleanup of Snapper Snapshots"; + inherit documentation; + serviceConfig.ExecStart = "${pkgs.snapper}/lib/snapper/systemd-helper --cleanup"; + }; + + systemd.timers.snapper-cleanup = { + description = "Cleanup of Snapper Snapshots"; + inherit documentation; + wantedBy = [ "basic.target" ]; + timerConfig.OnBootSec = "10m"; + timerConfig.OnUnitActiveSec = cfg.cleanupInterval; + }; + }); +} + diff --git a/nixpkgs/nixos/modules/services/misc/sonarr.nix b/nixpkgs/nixos/modules/services/misc/sonarr.nix new file mode 100644 index 000000000000..77c7f0582d0b --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/sonarr.nix @@ -0,0 +1,76 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.sonarr; +in +{ + options = { + services.sonarr = { + enable = mkEnableOption "Sonarr"; + + dataDir = mkOption { + type = types.str; + default = "/var/lib/sonarr/.config/NzbDrone"; + description = "The directory where Sonarr stores its data files."; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = '' + Open ports in the firewall for the Sonarr web interface + ''; + }; + + user = mkOption { + type = types.str; + default = "sonarr"; + description = "User account under which Sonaar runs."; + }; + + group = mkOption { + type = types.str; + default = "sonarr"; + description = "Group under which Sonaar runs."; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.tmpfiles.rules = [ + "d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -" + ]; + + systemd.services.sonarr = { + description = "Sonarr"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + ExecStart = "${pkgs.sonarr}/bin/NzbDrone -nobrowser -data='${cfg.dataDir}'"; + Restart = "on-failure"; + }; + }; + + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ 8989 ]; + }; + + users.users = mkIf (cfg.user == "sonarr") { + sonarr = { + group = cfg.group; + home = cfg.dataDir; + uid = config.ids.uids.sonarr; + }; + }; + + users.groups = mkIf (cfg.group == "sonarr") { + sonarr.gid = config.ids.gids.sonarr; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/spice-vdagentd.nix b/nixpkgs/nixos/modules/services/misc/spice-vdagentd.nix new file mode 100644 index 000000000000..2dd9fcf68ab0 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/spice-vdagentd.nix @@ -0,0 +1,30 @@ +{ config, pkgs, lib, ... }: + +with lib; +let + cfg = config.services.spice-vdagentd; +in +{ + options = { + services.spice-vdagentd = { + enable = mkEnableOption "Spice guest vdagent daemon"; + }; + }; + + config = mkIf cfg.enable { + + environment.systemPackages = [ pkgs.spice-vdagent ]; + + systemd.services.spice-vdagentd = { + description = "spice-vdagent daemon"; + wantedBy = [ "graphical.target" ]; + preStart = '' + mkdir -p "/run/spice-vdagentd/" + ''; + serviceConfig = { + Type = "forking"; + ExecStart = "${pkgs.spice-vdagent}/bin/spice-vdagentd"; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/ssm-agent.nix b/nixpkgs/nixos/modules/services/misc/ssm-agent.nix new file mode 100644 index 000000000000..e951a4c7ffa8 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/ssm-agent.nix @@ -0,0 +1,46 @@ +{ config, pkgs, lib, ... }: + +with lib; +let + cfg = config.services.ssm-agent; + + # The SSM agent doesn't pay attention to our /etc/os-release yet, and the lsb-release tool + # in nixpkgs doesn't seem to work properly on NixOS, so let's just fake the two fields SSM + # looks for. See https://github.com/aws/amazon-ssm-agent/issues/38 for upstream fix. + fake-lsb-release = pkgs.writeScriptBin "lsb_release" '' + #!${pkgs.runtimeShell} + + case "$1" in + -i) echo "nixos";; + -r) echo "${config.system.nixos.version}";; + esac + ''; +in { + options.services.ssm-agent = { + enable = mkEnableOption "AWS SSM agent"; + + package = mkOption { + type = types.path; + description = "The SSM agent package to use"; + default = pkgs.ssm-agent; + defaultText = "pkgs.ssm-agent"; + }; + }; + + config = mkIf cfg.enable { + systemd.services.ssm-agent = { + inherit (cfg.package.meta) description; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + path = [ fake-lsb-release ]; + serviceConfig = { + ExecStart = "${cfg.package.bin}/bin/agent"; + KillMode = "process"; + Restart = "on-failure"; + RestartSec = "15min"; + }; + }; + }; +} + diff --git a/nixpkgs/nixos/modules/services/misc/sssd.nix b/nixpkgs/nixos/modules/services/misc/sssd.nix new file mode 100644 index 000000000000..6b64045dde88 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/sssd.nix @@ -0,0 +1,96 @@ +{ config, lib, pkgs, ... }: +with lib; +let + cfg = config.services.sssd; + nscd = config.services.nscd; +in { + options = { + services.sssd = { + enable = mkEnableOption "the System Security Services Daemon"; + + config = mkOption { + type = types.lines; + description = "Contents of <filename>sssd.conf</filename>."; + default = '' + [sssd] + config_file_version = 2 + services = nss, pam + domains = shadowutils + + [nss] + + [pam] + + [domain/shadowutils] + id_provider = proxy + proxy_lib_name = files + auth_provider = proxy + proxy_pam_target = sssd-shadowutils + proxy_fast_alias = True + ''; + }; + + sshAuthorizedKeysIntegration = mkOption { + type = types.bool; + default = false; + description = '' + Whether to make sshd look up authorized keys from SSS. + For this to work, the <literal>ssh</literal> SSS service must be enabled in the sssd configuration. + ''; + }; + }; + }; + config = mkMerge [ + (mkIf cfg.enable { + assertions = singleton { + assertion = nscd.enable; + message = "nscd must be enabled through `services.nscd.enable` for SSSD to work."; + }; + + systemd.services.sssd = { + description = "System Security Services Daemon"; + wantedBy = [ "multi-user.target" ]; + before = [ "systemd-user-sessions.service" "nss-user-lookup.target" ]; + after = [ "network-online.target" "nscd.service" ]; + requires = [ "network-online.target" "nscd.service" ]; + wants = [ "nss-user-lookup.target" ]; + restartTriggers = [ + config.environment.etc."nscd.conf".source + config.environment.etc."sssd/sssd.conf".source + ]; + script = '' + export LDB_MODULES_PATH+="''${LDB_MODULES_PATH+:}${pkgs.ldb}/modules/ldb:${pkgs.sssd}/modules/ldb" + mkdir -p /var/lib/sss/{pubconf,db,mc,pipes,gpo_cache,secrets} /var/lib/sss/pipes/private /var/lib/sss/pubconf/krb5.include.d + ${pkgs.sssd}/bin/sssd -D + ''; + serviceConfig = { + Type = "forking"; + PIDFile = "/run/sssd.pid"; + }; + }; + + environment.etc."sssd/sssd.conf" = { + text = cfg.config; + mode = "0400"; + }; + + system.nssModules = optional cfg.enable pkgs.sssd; + services.dbus.packages = [ pkgs.sssd ]; + }) + + (mkIf cfg.sshAuthorizedKeysIntegration { + # Ugly: sshd refuses to start if a store path is given because /nix/store is group-writable. + # So indirect by a symlink. + environment.etc."ssh/authorized_keys_command" = { + mode = "0755"; + text = '' + #!/bin/sh + exec ${pkgs.sssd}/bin/sss_ssh_authorizedkeys "$@" + ''; + }; + services.openssh.extraConfig = '' + AuthorizedKeysCommand /etc/ssh/authorized_keys_command + AuthorizedKeysCommandUser nobody + ''; + })]; +} diff --git a/nixpkgs/nixos/modules/services/misc/subsonic.nix b/nixpkgs/nixos/modules/services/misc/subsonic.nix new file mode 100644 index 000000000000..1612b197f35f --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/subsonic.nix @@ -0,0 +1,165 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let cfg = config.services.subsonic; in { + options = { + services.subsonic = { + enable = mkEnableOption "Subsonic daemon"; + + home = mkOption { + type = types.path; + default = "/var/lib/subsonic"; + description = '' + The directory where Subsonic will create files. + Make sure it is writable. + ''; + }; + + listenAddress = mkOption { + type = types.string; + default = "0.0.0.0"; + description = '' + The host name or IP address on which to bind Subsonic. + Only relevant if you have multiple network interfaces and want + to make Subsonic available on only one of them. The default value + will bind Subsonic to all available network interfaces. + ''; + }; + + port = mkOption { + type = types.int; + default = 4040; + description = '' + The port on which Subsonic will listen for + incoming HTTP traffic. Set to 0 to disable. + ''; + }; + + httpsPort = mkOption { + type = types.int; + default = 0; + description = '' + The port on which Subsonic will listen for + incoming HTTPS traffic. Set to 0 to disable. + ''; + }; + + contextPath = mkOption { + type = types.path; + default = "/"; + description = '' + The context path, i.e., the last part of the Subsonic + URL. Typically '/' or '/subsonic'. Default '/' + ''; + }; + + maxMemory = mkOption { + type = types.int; + default = 100; + description = '' + The memory limit (max Java heap size) in megabytes. + Default: 100 + ''; + }; + + defaultMusicFolder = mkOption { + type = types.path; + default = "/var/music"; + description = '' + Configure Subsonic to use this folder for music. This option + only has effect the first time Subsonic is started. + ''; + }; + + defaultPodcastFolder = mkOption { + type = types.path; + default = "/var/music/Podcast"; + description = '' + Configure Subsonic to use this folder for Podcasts. This option + only has effect the first time Subsonic is started. + ''; + }; + + defaultPlaylistFolder = mkOption { + type = types.path; + default = "/var/playlists"; + description = '' + Configure Subsonic to use this folder for playlists. This option + only has effect the first time Subsonic is started. + ''; + }; + + transcoders = mkOption { + type = types.listOf types.path; + default = [ "${pkgs.ffmpeg.bin}/bin/ffmpeg" ]; + description = '' + List of paths to transcoder executables that should be accessible + from Subsonic. Symlinks will be created to each executable inside + ${cfg.home}/transcoders. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.subsonic = { + description = "Personal media streamer"; + after = [ "local-fs.target" "network.target" ]; + wantedBy = [ "multi-user.target" ]; + script = '' + ${pkgs.jre}/bin/java -Xmx${toString cfg.maxMemory}m \ + -Dsubsonic.home=${cfg.home} \ + -Dsubsonic.host=${cfg.listenAddress} \ + -Dsubsonic.port=${toString cfg.port} \ + -Dsubsonic.httpsPort=${toString cfg.httpsPort} \ + -Dsubsonic.contextPath=${cfg.contextPath} \ + -Dsubsonic.defaultMusicFolder=${cfg.defaultMusicFolder} \ + -Dsubsonic.defaultPodcastFolder=${cfg.defaultPodcastFolder} \ + -Dsubsonic.defaultPlaylistFolder=${cfg.defaultPlaylistFolder} \ + -Djava.awt.headless=true \ + -verbose:gc \ + -jar ${pkgs.subsonic}/subsonic-booter-jar-with-dependencies.jar + ''; + + preStart = '' + # Formerly this module set cfg.home to /var/subsonic. Try to move + # /var/subsonic to cfg.home. + oldHome="/var/subsonic" + if [ "${cfg.home}" != "$oldHome" ] && + ! [ -e "${cfg.home}" ] && + [ -d "$oldHome" ] && + [ $(${pkgs.coreutils}/bin/stat -c %u "$oldHome") -eq \ + ${toString config.users.users.subsonic.uid} ]; then + logger Moving "$oldHome" to "${cfg.home}" + ${pkgs.coreutils}/bin/mv -T "$oldHome" "${cfg.home}" + fi + + # Install transcoders. + ${pkgs.coreutils}/bin/rm -rf ${cfg.home}/transcode ; \ + ${pkgs.coreutils}/bin/mkdir -p ${cfg.home}/transcode ; \ + ${pkgs.bash}/bin/bash -c ' \ + for exe in "$@"; do \ + ${pkgs.coreutils}/bin/ln -sf "$exe" ${cfg.home}/transcode; \ + done' IGNORED_FIRST_ARG ${toString cfg.transcoders} + ''; + serviceConfig = { + # Needed for Subsonic to find subsonic.war. + WorkingDirectory = "${pkgs.subsonic}"; + Restart = "always"; + User = "subsonic"; + UMask = "0022"; + }; + }; + + users.users.subsonic = { + description = "Subsonic daemon user"; + home = cfg.home; + createHome = true; + group = "subsonic"; + uid = config.ids.uids.subsonic; + }; + + users.groups.subsonic.gid = config.ids.gids.subsonic; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/sundtek.nix b/nixpkgs/nixos/modules/services/misc/sundtek.nix new file mode 100644 index 000000000000..e3234518c940 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/sundtek.nix @@ -0,0 +1,33 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.sundtek; + +in +{ + options.services.sundtek = { + enable = mkEnableOption "Sundtek driver"; + }; + + config = mkIf cfg.enable { + + environment.systemPackages = [ pkgs.sundtek ]; + + systemd.services.sundtek = { + description = "Sundtek driver"; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Type = "oneshot"; + ExecStart = '' + ${pkgs.sundtek}/bin/mediasrv -d -v -p ${pkgs.sundtek}/bin ;\ + ${pkgs.sundtek}/bin/mediaclient --start --wait-for-devices + ''; + ExecStop = "${pkgs.sundtek}/bin/mediaclient --shutdown"; + RemainAfterExit = true; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/svnserve.nix b/nixpkgs/nixos/modules/services/misc/svnserve.nix new file mode 100644 index 000000000000..6292bc52b1e3 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/svnserve.nix @@ -0,0 +1,44 @@ +# SVN server +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.svnserve; + +in + +{ + + ###### interface + + options = { + + services.svnserve = { + + enable = mkOption { + default = false; + description = "Whether to enable svnserve to serve Subversion repositories through the SVN protocol."; + }; + + svnBaseDir = mkOption { + default = "/repos"; + description = "Base directory from which Subversion repositories are accessed."; + }; + }; + + }; + + + ###### implementation + + config = mkIf cfg.enable { + systemd.services.svnserve = { + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + preStart = "mkdir -p ${cfg.svnBaseDir}"; + script = "${pkgs.subversion.out}/bin/svnserve -r ${cfg.svnBaseDir} -d --foreground --pid-file=/run/svnserve.pid"; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/synergy.nix b/nixpkgs/nixos/modules/services/misc/synergy.nix new file mode 100644 index 000000000000..b89cb41ac3ad --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/synergy.nix @@ -0,0 +1,132 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfgC = config.services.synergy.client; + cfgS = config.services.synergy.server; + +in + +{ + ###### interface + + options = { + + services.synergy = { + + # !!! All these option descriptions needs to be cleaned up. + + client = { + enable = mkOption { + default = false; + description = " + Whether to enable the Synergy client (receive keyboard and mouse events from a Synergy server). + "; + }; + screenName = mkOption { + default = ""; + description = '' + Use the given name instead of the hostname to identify + ourselves to the server. + ''; + }; + serverAddress = mkOption { + description = '' + The server address is of the form: [hostname][:port]. The + hostname must be the address or hostname of the server. The + port overrides the default port, 24800. + ''; + }; + autoStart = mkOption { + default = true; + type = types.bool; + description = "Whether the Synergy client should be started automatically."; + }; + }; + + server = { + enable = mkOption { + default = false; + description = '' + Whether to enable the Synergy server (send keyboard and mouse events). + ''; + }; + configFile = mkOption { + default = "/etc/synergy-server.conf"; + description = "The Synergy server configuration file."; + }; + screenName = mkOption { + default = ""; + description = '' + Use the given name instead of the hostname to identify + this screen in the configuration. + ''; + }; + address = mkOption { + default = ""; + description = "Address on which to listen for clients."; + }; + autoStart = mkOption { + default = true; + type = types.bool; + description = "Whether the Synergy server should be started automatically."; + }; + }; + }; + + }; + + + ###### implementation + + config = mkMerge [ + (mkIf cfgC.enable { + systemd.user.services."synergy-client" = { + after = [ "network.target" "graphical-session.target" ]; + description = "Synergy client"; + wantedBy = optional cfgC.autoStart "graphical-session.target"; + path = [ pkgs.synergy ]; + serviceConfig.ExecStart = ''${pkgs.synergy}/bin/synergyc -f ${optionalString (cfgC.screenName != "") "-n ${cfgC.screenName}"} ${cfgC.serverAddress}''; + serviceConfig.Restart = "on-failure"; + }; + }) + (mkIf cfgS.enable { + systemd.user.services."synergy-server" = { + after = [ "network.target" "graphical-session.target" ]; + description = "Synergy server"; + wantedBy = optional cfgS.autoStart "graphical-session.target"; + path = [ pkgs.synergy ]; + serviceConfig.ExecStart = ''${pkgs.synergy}/bin/synergys -c ${cfgS.configFile} -f ${optionalString (cfgS.address != "") "-a ${cfgS.address}"} ${optionalString (cfgS.screenName != "") "-n ${cfgS.screenName}" }''; + serviceConfig.Restart = "on-failure"; + }; + }) + ]; + +} + +/* SYNERGY SERVER example configuration file +section: screens + laptop: + dm: + win: +end +section: aliases + laptop: + 192.168.5.5 + dm: + 192.168.5.78 + win: + 192.168.5.54 +end +section: links + laptop: + left = dm + dm: + right = laptop + left = win + win: + right = dm +end +*/ diff --git a/nixpkgs/nixos/modules/services/misc/sysprof.nix b/nixpkgs/nixos/modules/services/misc/sysprof.nix new file mode 100644 index 000000000000..ab91a8b586a2 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/sysprof.nix @@ -0,0 +1,19 @@ +{ config, lib, pkgs, ... }: + +{ + options = { + services.sysprof = { + enable = lib.mkEnableOption "sysprof profiling daemon"; + }; + }; + + config = lib.mkIf config.services.sysprof.enable { + environment.systemPackages = [ pkgs.sysprof ]; + + services.dbus.packages = [ pkgs.sysprof ]; + + systemd.packages = [ pkgs.sysprof ]; + }; + + meta.maintainers = pkgs.sysprof.meta.maintainers; +} diff --git a/nixpkgs/nixos/modules/services/misc/taskserver/default.nix b/nixpkgs/nixos/modules/services/misc/taskserver/default.nix new file mode 100644 index 000000000000..483bc99ad946 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/taskserver/default.nix @@ -0,0 +1,568 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.taskserver; + + taskd = "${pkgs.taskserver}/bin/taskd"; + + mkManualPkiOption = desc: mkOption { + type = types.nullOr types.path; + default = null; + description = desc + '' + <note><para> + Setting this option will prevent automatic CA creation and handling. + </para></note> + ''; + }; + + manualPkiOptions = { + ca.cert = mkManualPkiOption '' + Fully qualified path to the CA certificate. + ''; + + server.cert = mkManualPkiOption '' + Fully qualified path to the server certificate. + ''; + + server.crl = mkManualPkiOption '' + Fully qualified path to the server certificate revocation list. + ''; + + server.key = mkManualPkiOption '' + Fully qualified path to the server key. + ''; + }; + + mkAutoDesc = preamble: '' + ${preamble} + + <note><para> + This option is for the automatically handled CA and will be ignored if any + of the <option>services.taskserver.pki.manual.*</option> options are set. + </para></note> + ''; + + mkExpireOption = desc: mkOption { + type = types.nullOr types.int; + default = null; + example = 365; + apply = val: if isNull val then -1 else val; + description = mkAutoDesc '' + The expiration time of ${desc} in days or <literal>null</literal> for no + expiration time. + ''; + }; + + autoPkiOptions = { + bits = mkOption { + type = types.int; + default = 4096; + example = 2048; + description = mkAutoDesc "The bit size for generated keys."; + }; + + expiration = { + ca = mkExpireOption "the CA certificate"; + server = mkExpireOption "the server certificate"; + client = mkExpireOption "client certificates"; + crl = mkExpireOption "the certificate revocation list (CRL)"; + }; + }; + + needToCreateCA = let + notFound = path: let + dotted = concatStringsSep "." path; + in throw "Can't find option definitions for path `${dotted}'."; + findPkiDefinitions = path: attrs: let + mkSublist = key: val: let + newPath = path ++ singleton key; + in if isOption val + then attrByPath newPath (notFound newPath) cfg.pki.manual + else findPkiDefinitions newPath val; + in flatten (mapAttrsToList mkSublist attrs); + in all isNull (findPkiDefinitions [] manualPkiOptions); + + orgOptions = { ... }: { + options.users = mkOption { + type = types.uniq (types.listOf types.str); + default = []; + example = [ "alice" "bob" ]; + description = '' + A list of user names that belong to the organization. + ''; + }; + + options.groups = mkOption { + type = types.listOf types.str; + default = []; + example = [ "workers" "slackers" ]; + description = '' + A list of group names that belong to the organization. + ''; + }; + }; + + certtool = "${pkgs.gnutls.bin}/bin/certtool"; + + nixos-taskserver = pkgs.pythonPackages.buildPythonApplication { + name = "nixos-taskserver"; + + src = pkgs.runCommand "nixos-taskserver-src" { preferLocalBuild = true; } '' + mkdir -p "$out" + cat "${pkgs.substituteAll { + src = ./helper-tool.py; + inherit taskd certtool; + inherit (cfg) dataDir user group fqdn; + certBits = cfg.pki.auto.bits; + clientExpiration = cfg.pki.auto.expiration.client; + crlExpiration = cfg.pki.auto.expiration.crl; + isAutoConfig = if needToCreateCA then "True" else "False"; + }}" > "$out/main.py" + cat > "$out/setup.py" <<EOF + from setuptools import setup + setup(name="nixos-taskserver", + py_modules=["main"], + install_requires=["Click"], + entry_points="[console_scripts]\\nnixos-taskserver=main:cli") + EOF + ''; + + propagatedBuildInputs = [ pkgs.pythonPackages.click ]; + }; + +in { + options = { + services.taskserver = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable the Taskwarrior server. + + More instructions about NixOS in conjuction with Taskserver can be + found in the NixOS manual at + <olink targetdoc="manual" targetptr="module-taskserver"/>. + ''; + }; + + user = mkOption { + type = types.str; + default = "taskd"; + description = "User for Taskserver."; + }; + + group = mkOption { + type = types.str; + default = "taskd"; + description = "Group for Taskserver."; + }; + + dataDir = mkOption { + type = types.path; + default = "/var/lib/taskserver"; + description = "Data directory for Taskserver."; + }; + + ciphers = mkOption { + type = types.nullOr (types.separatedString ":"); + default = null; + example = "NORMAL:-VERS-SSL3.0"; + description = let + url = "https://gnutls.org/manual/html_node/Priority-Strings.html"; + in '' + List of GnuTLS ciphers to use. See the GnuTLS documentation about + priority strings at <link xlink:href="${url}"/> for full details. + ''; + }; + + organisations = mkOption { + type = types.attrsOf (types.submodule orgOptions); + default = {}; + example.myShinyOrganisation.users = [ "alice" "bob" ]; + example.myShinyOrganisation.groups = [ "staff" "outsiders" ]; + example.yetAnotherOrganisation.users = [ "foo" "bar" ]; + description = '' + An attribute set where the keys name the organisation and the values + are a set of lists of <option>users</option> and + <option>groups</option>. + ''; + }; + + confirmation = mkOption { + type = types.bool; + default = true; + description = '' + Determines whether certain commands are confirmed. + ''; + }; + + debug = mkOption { + type = types.bool; + default = false; + description = '' + Logs debugging information. + ''; + }; + + extensions = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Fully qualified path of the Taskserver extension scripts. + Currently there are none. + ''; + }; + + ipLog = mkOption { + type = types.bool; + default = false; + description = '' + Logs the IP addresses of incoming requests. + ''; + }; + + queueSize = mkOption { + type = types.int; + default = 10; + description = '' + Size of the connection backlog, see <citerefentry> + <refentrytitle>listen</refentrytitle> + <manvolnum>2</manvolnum> + </citerefentry>. + ''; + }; + + requestLimit = mkOption { + type = types.int; + default = 1048576; + description = '' + Size limit of incoming requests, in bytes. + ''; + }; + + allowedClientIDs = mkOption { + type = with types; either str (listOf str); + default = []; + example = [ "[Tt]ask [2-9]+" ]; + description = '' + A list of regular expressions that are matched against the reported + client id (such as <literal>task 2.3.0</literal>). + + The values <literal>all</literal> or <literal>none</literal> have + special meaning. Overidden by any entry in the option + <option>services.taskserver.disallowedClientIDs</option>. + ''; + }; + + disallowedClientIDs = mkOption { + type = with types; either str (listOf str); + default = []; + example = [ "[Tt]ask [2-9]+" ]; + description = '' + A list of regular expressions that are matched against the reported + client id (such as <literal>task 2.3.0</literal>). + + The values <literal>all</literal> or <literal>none</literal> have + special meaning. Any entry here overrides those in + <option>services.taskserver.allowedClientIDs</option>. + ''; + }; + + listenHost = mkOption { + type = types.str; + default = "localhost"; + example = "::"; + description = '' + The address (IPv4, IPv6 or DNS) to listen on. + + If the value is something else than <literal>localhost</literal> the + port defined by <option>listenPort</option> is automatically added to + <option>networking.firewall.allowedTCPPorts</option>. + ''; + }; + + listenPort = mkOption { + type = types.int; + default = 53589; + description = '' + Port number of the Taskserver. + ''; + }; + + fqdn = mkOption { + type = types.str; + default = "localhost"; + description = '' + The fully qualified domain name of this server, which is also used + as the common name in the certificates. + ''; + }; + + trust = mkOption { + type = types.enum [ "allow all" "strict" ]; + default = "strict"; + description = '' + Determines how client certificates are validated. + + The value <literal>allow all</literal> performs no client + certificate validation. This is not recommended. The value + <literal>strict</literal> causes the client certificate to be + validated against a CA. + ''; + }; + + pki.manual = manualPkiOptions; + pki.auto = autoPkiOptions; + + config = mkOption { + type = types.attrs; + example.client.cert = "/tmp/debugging.cert"; + description = '' + Configuration options to pass to Taskserver. + + The options here are the same as described in <citerefentry> + <refentrytitle>taskdrc</refentrytitle> + <manvolnum>5</manvolnum> + </citerefentry>, but with one difference: + + The <literal>server</literal> option is + <literal>server.listen</literal> here, because the + <literal>server</literal> option would collide with other options + like <literal>server.cert</literal> and we would run in a type error + (attribute set versus string). + + Nix types like integers or booleans are automatically converted to + the right values Taskserver would expect. + ''; + apply = let + mkKey = path: if path == ["server" "listen"] then "server" + else concatStringsSep "." path; + recurse = path: attrs: let + mapper = name: val: let + newPath = path ++ [ name ]; + scalar = if val == true then "true" + else if val == false then "false" + else toString val; + in if isAttrs val then recurse newPath val + else [ "${mkKey newPath}=${scalar}" ]; + in concatLists (mapAttrsToList mapper attrs); + in recurse []; + }; + }; + }; + + imports = [ + (mkRemovedOptionModule ["services" "taskserver" "extraConfig"] '' + This option was removed in favor of `services.taskserver.config` with + different semantics (it's now a list of attributes instead of lines). + + Please look up the documentation of `services.taskserver.config' to get + more information about the new way to pass additional configuration + options. + '') + ]; + + config = mkMerge [ + (mkIf cfg.enable { + environment.systemPackages = [ nixos-taskserver ]; + + users.users = optional (cfg.user == "taskd") { + name = "taskd"; + uid = config.ids.uids.taskd; + description = "Taskserver user"; + group = cfg.group; + }; + + users.groups = optional (cfg.group == "taskd") { + name = "taskd"; + gid = config.ids.gids.taskd; + }; + + services.taskserver.config = { + # systemd related + daemon = false; + log = "-"; + + # logging + debug = cfg.debug; + ip.log = cfg.ipLog; + + # general + ciphers = cfg.ciphers; + confirmation = cfg.confirmation; + extensions = cfg.extensions; + queue.size = cfg.queueSize; + request.limit = cfg.requestLimit; + + # client + client.allow = cfg.allowedClientIDs; + client.deny = cfg.disallowedClientIDs; + + # server + trust = cfg.trust; + server = { + listen = "${cfg.listenHost}:${toString cfg.listenPort}"; + } // (if needToCreateCA then { + cert = "${cfg.dataDir}/keys/server.cert"; + key = "${cfg.dataDir}/keys/server.key"; + crl = "${cfg.dataDir}/keys/server.crl"; + } else { + cert = "${cfg.pki.manual.server.cert}"; + key = "${cfg.pki.manual.server.key}"; + crl = "${cfg.pki.manual.server.crl}"; + }); + + ca.cert = if needToCreateCA then "${cfg.dataDir}/keys/ca.cert" + else "${cfg.pki.manual.ca.cert}"; + }; + + systemd.services.taskserver-init = { + wantedBy = [ "taskserver.service" ]; + before = [ "taskserver.service" ]; + description = "Initialize Taskserver Data Directory"; + + preStart = '' + mkdir -m 0770 -p "${cfg.dataDir}" + chown "${cfg.user}:${cfg.group}" "${cfg.dataDir}" + ''; + + script = '' + ${taskd} init + touch "${cfg.dataDir}/.is_initialized" + ''; + + environment.TASKDDATA = cfg.dataDir; + + unitConfig.ConditionPathExists = "!${cfg.dataDir}/.is_initialized"; + + serviceConfig.Type = "oneshot"; + serviceConfig.User = cfg.user; + serviceConfig.Group = cfg.group; + serviceConfig.PermissionsStartOnly = true; + serviceConfig.PrivateNetwork = true; + serviceConfig.PrivateDevices = true; + serviceConfig.PrivateTmp = true; + }; + + systemd.services.taskserver = { + description = "Taskwarrior Server"; + + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + environment.TASKDDATA = cfg.dataDir; + + preStart = let + jsonOrgs = builtins.toJSON cfg.organisations; + jsonFile = pkgs.writeText "orgs.json" jsonOrgs; + helperTool = "${nixos-taskserver}/bin/nixos-taskserver"; + in "${helperTool} process-json '${jsonFile}'"; + + serviceConfig = { + ExecStart = let + mkCfgFlag = flag: escapeShellArg "--${flag}"; + cfgFlags = concatMapStringsSep " " mkCfgFlag cfg.config; + in "@${taskd} taskd server ${cfgFlags}"; + ExecReload = "${pkgs.coreutils}/bin/kill -USR1 $MAINPID"; + Restart = "on-failure"; + PermissionsStartOnly = true; + PrivateTmp = true; + PrivateDevices = true; + User = cfg.user; + Group = cfg.group; + }; + }; + }) + (mkIf (cfg.enable && needToCreateCA) { + systemd.services.taskserver-ca = { + wantedBy = [ "taskserver.service" ]; + after = [ "taskserver-init.service" ]; + before = [ "taskserver.service" ]; + description = "Initialize CA for TaskServer"; + serviceConfig.Type = "oneshot"; + serviceConfig.UMask = "0077"; + serviceConfig.PrivateNetwork = true; + serviceConfig.PrivateTmp = true; + + script = '' + silent_certtool() { + if ! output="$("${certtool}" "$@" 2>&1)"; then + echo "GNUTLS certtool invocation failed with output:" >&2 + echo "$output" >&2 + fi + } + + mkdir -m 0700 -p "${cfg.dataDir}/keys" + chown root:root "${cfg.dataDir}/keys" + + if [ ! -e "${cfg.dataDir}/keys/ca.key" ]; then + silent_certtool -p \ + --bits ${toString cfg.pki.auto.bits} \ + --outfile "${cfg.dataDir}/keys/ca.key" + silent_certtool -s \ + --template "${pkgs.writeText "taskserver-ca.template" '' + cn = ${cfg.fqdn} + expiration_days = ${toString cfg.pki.auto.expiration.ca} + cert_signing_key + ca + ''}" \ + --load-privkey "${cfg.dataDir}/keys/ca.key" \ + --outfile "${cfg.dataDir}/keys/ca.cert" + + chgrp "${cfg.group}" "${cfg.dataDir}/keys/ca.cert" + chmod g+r "${cfg.dataDir}/keys/ca.cert" + fi + + if [ ! -e "${cfg.dataDir}/keys/server.key" ]; then + silent_certtool -p \ + --bits ${toString cfg.pki.auto.bits} \ + --outfile "${cfg.dataDir}/keys/server.key" + + silent_certtool -c \ + --template "${pkgs.writeText "taskserver-cert.template" '' + cn = ${cfg.fqdn} + expiration_days = ${toString cfg.pki.auto.expiration.server} + tls_www_server + encryption_key + signing_key + ''}" \ + --load-ca-privkey "${cfg.dataDir}/keys/ca.key" \ + --load-ca-certificate "${cfg.dataDir}/keys/ca.cert" \ + --load-privkey "${cfg.dataDir}/keys/server.key" \ + --outfile "${cfg.dataDir}/keys/server.cert" + + chgrp "${cfg.group}" \ + "${cfg.dataDir}/keys/server.key" \ + "${cfg.dataDir}/keys/server.cert" + + chmod g+r \ + "${cfg.dataDir}/keys/server.key" \ + "${cfg.dataDir}/keys/server.cert" + fi + + if [ ! -e "${cfg.dataDir}/keys/server.crl" ]; then + silent_certtool --generate-crl \ + --template "${pkgs.writeText "taskserver-crl.template" '' + expiration_days = ${toString cfg.pki.auto.expiration.crl} + ''}" \ + --load-ca-privkey "${cfg.dataDir}/keys/ca.key" \ + --load-ca-certificate "${cfg.dataDir}/keys/ca.cert" \ + --outfile "${cfg.dataDir}/keys/server.crl" + + chgrp "${cfg.group}" "${cfg.dataDir}/keys/server.crl" + chmod g+r "${cfg.dataDir}/keys/server.crl" + fi + + chmod go+x "${cfg.dataDir}/keys" + ''; + }; + }) + (mkIf (cfg.enable && cfg.listenHost != "localhost") { + networking.firewall.allowedTCPPorts = [ cfg.listenPort ]; + }) + ]; + + meta.doc = ./doc.xml; +} diff --git a/nixpkgs/nixos/modules/services/misc/taskserver/doc.xml b/nixpkgs/nixos/modules/services/misc/taskserver/doc.xml new file mode 100644 index 000000000000..5eac8d9ef784 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/taskserver/doc.xml @@ -0,0 +1,135 @@ +<chapter xmlns="http://docbook.org/ns/docbook" + xmlns:xlink="http://www.w3.org/1999/xlink" + version="5.0" + xml:id="module-taskserver"> + <title>Taskserver</title> + <para> + Taskserver is the server component of + <link xlink:href="https://taskwarrior.org/">Taskwarrior</link>, a free and + open source todo list application. + </para> + <para> + <emphasis>Upstream documentation:</emphasis> + <link xlink:href="https://taskwarrior.org/docs/#taskd"/> + </para> + <section xml:id="module-services-taskserver-configuration"> + <title>Configuration</title> + + <para> + Taskserver does all of its authentication via TLS using client certificates, + so you either need to roll your own CA or purchase a certificate from a + known CA, which allows creation of client certificates. These certificates + are usually advertised as <quote>server certificates</quote>. + </para> + + <para> + So in order to make it easier to handle your own CA, there is a helper tool + called <command>nixos-taskserver</command> which manages the custom CA along + with Taskserver organisations, users and groups. + </para> + + <para> + While the client certificates in Taskserver only authenticate whether a user + is allowed to connect, every user has its own UUID which identifies it as an + entity. + </para> + + <para> + With <command>nixos-taskserver</command> the client certificate is created + along with the UUID of the user, so it handles all of the credentials needed + in order to setup the Taskwarrior client to work with a Taskserver. + </para> + </section> + <section xml:id="module-services-taskserver-nixos-taskserver-tool"> + <title>The nixos-taskserver tool</title> + + <para> + Because Taskserver by default only provides scripts to setup users + imperatively, the <command>nixos-taskserver</command> tool is used for + addition and deletion of organisations along with users and groups defined + by <xref linkend="opt-services.taskserver.organisations"/> and as well for + imperative set up. + </para> + + <para> + The tool is designed to not interfere if the command is used to manually set + up some organisations, users or groups. + </para> + + <para> + For example if you add a new organisation using <command>nixos-taskserver + org add foo</command>, the organisation is not modified and deleted no + matter what you define in + <option>services.taskserver.organisations</option>, even if you're adding + the same organisation in that option. + </para> + + <para> + The tool is modelled to imitate the official <command>taskd</command> + command, documentation for each subcommand can be shown by using the + <option>--help</option> switch. + </para> + </section> + <section xml:id="module-services-taskserver-declarative-ca-management"> + <title>Declarative/automatic CA management</title> + + <para> + Everything is done according to what you specify in the module options, + however in order to set up a Taskwarrior client for synchronisation with a + Taskserver instance, you have to transfer the keys and certificates to the + client machine. + </para> + + <para> + This is done using <command>nixos-taskserver user export $orgname + $username</command> which is printing a shell script fragment to stdout + which can either be used verbatim or adjusted to import the user on the + client machine. + </para> + + <para> + For example, let's say you have the following configuration: +<screen> +{ + <xref linkend="opt-services.taskserver.enable"/> = true; + <xref linkend="opt-services.taskserver.fqdn"/> = "server"; + <xref linkend="opt-services.taskserver.listenHost"/> = "::"; + <link linkend="opt-services.taskserver.organisations._name_.users">services.taskserver.organisations.my-company.users</link> = [ "alice" ]; +} +</screen> + This creates an organisation called <literal>my-company</literal> with the + user <literal>alice</literal>. + </para> + + <para> + Now in order to import the <literal>alice</literal> user to another machine + <literal>alicebox</literal>, all we need to do is something like this: +<screen> +$ ssh server nixos-taskserver user export my-company alice | sh +</screen> + Of course, if no SSH daemon is available on the server you can also copy + & paste it directly into a shell. + </para> + + <para> + After this step the user should be set up and you can start synchronising + your tasks for the first time with <command>task sync init</command> on + <literal>alicebox</literal>. + </para> + + <para> + Subsequent synchronisation requests merely require the command <command>task + sync</command> after that stage. + </para> + </section> + <section xml:id="module-services-taskserver-manual-ca-management"> + <title>Manual CA management</title> + + <para> + If you set any options within + <link linkend="opt-services.taskserver.pki.manual.ca.cert">service.taskserver.pki.manual</link>.*, + <command>nixos-taskserver</command> won't issue certificates, but you can + still use it for adding or removing user accounts. + </para> + </section> +</chapter> diff --git a/nixpkgs/nixos/modules/services/misc/taskserver/helper-tool.py b/nixpkgs/nixos/modules/services/misc/taskserver/helper-tool.py new file mode 100644 index 000000000000..22a3d8d5311b --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/taskserver/helper-tool.py @@ -0,0 +1,688 @@ +import grp +import json +import pwd +import os +import re +import string +import subprocess +import sys + +from contextlib import contextmanager +from shutil import rmtree +from tempfile import NamedTemporaryFile + +import click + +IS_AUTO_CONFIG = @isAutoConfig@ # NOQA +CERTTOOL_COMMAND = "@certtool@" +CERT_BITS = "@certBits@" +CLIENT_EXPIRATION = "@clientExpiration@" +CRL_EXPIRATION = "@crlExpiration@" + +TASKD_COMMAND = "@taskd@" +TASKD_DATA_DIR = "@dataDir@" +TASKD_USER = "@user@" +TASKD_GROUP = "@group@" +FQDN = "@fqdn@" + +CA_KEY = os.path.join(TASKD_DATA_DIR, "keys", "ca.key") +CA_CERT = os.path.join(TASKD_DATA_DIR, "keys", "ca.cert") +CRL_FILE = os.path.join(TASKD_DATA_DIR, "keys", "server.crl") + +RE_CONFIGUSER = re.compile(r'^\s*user\s*=(.*)$') +RE_USERKEY = re.compile(r'New user key: (.+)$', re.MULTILINE) + + +def lazyprop(fun): + """ + Decorator which only evaluates the specified function when accessed. + """ + name = '_lazy_' + fun.__name__ + + @property + def _lazy(self): + val = getattr(self, name, None) + if val is None: + val = fun(self) + setattr(self, name, val) + return val + + return _lazy + + +class TaskdError(OSError): + pass + + +def run_as_taskd_user(): + uid = pwd.getpwnam(TASKD_USER).pw_uid + gid = grp.getgrnam(TASKD_GROUP).gr_gid + os.setgid(gid) + os.setuid(uid) + + +def taskd_cmd(cmd, *args, **kwargs): + """ + Invoke taskd with the specified command with the privileges of the 'taskd' + user and 'taskd' group. + + If 'capture_stdout' is passed as a keyword argument with the value True, + the return value are the contents the command printed to stdout. + """ + capture_stdout = kwargs.pop("capture_stdout", False) + fun = subprocess.check_output if capture_stdout else subprocess.check_call + return fun( + [TASKD_COMMAND, cmd, "--data", TASKD_DATA_DIR] + list(args), + preexec_fn=run_as_taskd_user, + **kwargs + ) + + +def certtool_cmd(*args, **kwargs): + """ + Invoke certtool from GNUTLS and return the output of the command. + + The provided arguments are added to the certtool command and keyword + arguments are added to subprocess.check_output(). + + Note that this will suppress all output of certtool and it will only be + printed whenever there is an unsuccessful return code. + """ + return subprocess.check_output( + [CERTTOOL_COMMAND] + list(args), + preexec_fn=lambda: os.umask(0077), + stderr=subprocess.STDOUT, + **kwargs + ) + + +def label(msg): + if sys.stdout.isatty() or sys.stderr.isatty(): + sys.stderr.write(msg + "\n") + + +def mkpath(*args): + return os.path.join(TASKD_DATA_DIR, "orgs", *args) + + +def mark_imperative(*path): + """ + Mark the specified path as being imperatively managed by creating an empty + file called ".imperative", so that it doesn't interfere with the + declarative configuration. + """ + open(os.path.join(mkpath(*path), ".imperative"), 'a').close() + + +def is_imperative(*path): + """ + Check whether the given path is marked as imperative, see mark_imperative() + for more information. + """ + full_path = [] + for component in path: + full_path.append(component) + if os.path.exists(os.path.join(mkpath(*full_path), ".imperative")): + return True + return False + + +def fetch_username(org, key): + for line in open(mkpath(org, "users", key, "config"), "r"): + match = RE_CONFIGUSER.match(line) + if match is None: + continue + return match.group(1).strip() + return None + + +@contextmanager +def create_template(contents): + """ + Generate a temporary file with the specified contents as a list of strings + and yield its path as the context. + """ + template = NamedTemporaryFile(mode="w", prefix="certtool-template") + template.writelines(map(lambda l: l + "\n", contents)) + template.flush() + yield template.name + template.close() + + +def generate_key(org, user): + if not IS_AUTO_CONFIG: + msg = "Automatic PKI handling is disabled, you need to " \ + "manually issue a client certificate for user {}.\n" + sys.stderr.write(msg.format(user)) + return + + basedir = os.path.join(TASKD_DATA_DIR, "keys", org, user) + if os.path.exists(basedir): + raise OSError("Keyfile directory for {} already exists.".format(user)) + + privkey = os.path.join(basedir, "private.key") + pubcert = os.path.join(basedir, "public.cert") + + try: + os.makedirs(basedir, mode=0700) + + certtool_cmd("-p", "--bits", CERT_BITS, "--outfile", privkey) + + template_data = [ + "organization = {0}".format(org), + "cn = {}".format(FQDN), + "expiration_days = {}".format(CLIENT_EXPIRATION), + "tls_www_client", + "encryption_key", + "signing_key" + ] + + with create_template(template_data) as template: + certtool_cmd( + "-c", + "--load-privkey", privkey, + "--load-ca-privkey", CA_KEY, + "--load-ca-certificate", CA_CERT, + "--template", template, + "--outfile", pubcert + ) + except: + rmtree(basedir) + raise + + +def revoke_key(org, user): + basedir = os.path.join(TASKD_DATA_DIR, "keys", org, user) + if not os.path.exists(basedir): + raise OSError("Keyfile directory for {} doesn't exist.".format(user)) + + pubcert = os.path.join(basedir, "public.cert") + + expiration = "expiration_days = {}".format(CRL_EXPIRATION) + + with create_template([expiration]) as template: + oldcrl = NamedTemporaryFile(mode="wb", prefix="old-crl") + oldcrl.write(open(CRL_FILE, "rb").read()) + oldcrl.flush() + certtool_cmd( + "--generate-crl", + "--load-crl", oldcrl.name, + "--load-ca-privkey", CA_KEY, + "--load-ca-certificate", CA_CERT, + "--load-certificate", pubcert, + "--template", template, + "--outfile", CRL_FILE + ) + oldcrl.close() + rmtree(basedir) + + +def is_key_line(line, match): + return line.startswith("---") and line.lstrip("- ").startswith(match) + + +def getkey(*args): + path = os.path.join(TASKD_DATA_DIR, "keys", *args) + buf = [] + for line in open(path, "r"): + if len(buf) == 0: + if is_key_line(line, "BEGIN"): + buf.append(line) + continue + + buf.append(line) + + if is_key_line(line, "END"): + return ''.join(buf) + raise IOError("Unable to get key from {}.".format(path)) + + +def mktaskkey(cfg, path, keydata): + heredoc = 'cat > "{}" <<EOF\n{}EOF'.format(path, keydata) + cmd = 'task config taskd.{} -- "{}"'.format(cfg, path) + return heredoc + "\n" + cmd + + +class User(object): + def __init__(self, org, name, key): + self.__org = org + self.name = name + self.key = key + + def export(self): + credentials = '/'.join([self.__org, self.name, self.key]) + allow_unquoted = string.ascii_letters + string.digits + "/-_." + if not all((c in allow_unquoted) for c in credentials): + credentials = "'" + credentials.replace("'", r"'\''") + "'" + + script = [] + + if IS_AUTO_CONFIG: + pubcert = getkey(self.__org, self.name, "public.cert") + privkey = getkey(self.__org, self.name, "private.key") + cacert = getkey("ca.cert") + + keydir = "${TASKDATA:-$HOME/.task}/keys" + + script += [ + "umask 0077", + 'mkdir -p "{}"'.format(keydir), + mktaskkey("certificate", os.path.join(keydir, "public.cert"), + pubcert), + mktaskkey("key", os.path.join(keydir, "private.key"), privkey), + mktaskkey("ca", os.path.join(keydir, "ca.cert"), cacert) + ] + + script.append( + "task config taskd.credentials -- {}".format(credentials) + ) + + return "\n".join(script) + "\n" + + +class Group(object): + def __init__(self, org, name): + self.__org = org + self.name = name + + +class Organisation(object): + def __init__(self, name, ignore_imperative): + self.name = name + self.ignore_imperative = ignore_imperative + + def add_user(self, name): + """ + Create a new user along with a certificate and key. + + Returns a 'User' object or None if the user already exists. + """ + if self.ignore_imperative and is_imperative(self.name): + return None + if name not in self.users.keys(): + output = taskd_cmd("add", "user", self.name, name, + capture_stdout=True) + key = RE_USERKEY.search(output) + if key is None: + msg = "Unable to find key while creating user {}." + raise TaskdError(msg.format(name)) + + generate_key(self.name, name) + newuser = User(self.name, name, key.group(1)) + self._lazy_users[name] = newuser + return newuser + return None + + def del_user(self, name): + """ + Delete a user and revoke its keys. + """ + if name in self.users.keys(): + user = self.get_user(name) + if self.ignore_imperative and \ + is_imperative(self.name, "users", user.key): + return + + # Work around https://bug.tasktools.org/browse/TD-40: + rmtree(mkpath(self.name, "users", user.key)) + + revoke_key(self.name, name) + del self._lazy_users[name] + + def add_group(self, name): + """ + Create a new group. + + Returns a 'Group' object or None if the group already exists. + """ + if self.ignore_imperative and is_imperative(self.name): + return None + if name not in self.groups.keys(): + taskd_cmd("add", "group", self.name, name) + newgroup = Group(self.name, name) + self._lazy_groups[name] = newgroup + return newgroup + return None + + def del_group(self, name): + """ + Delete a group. + """ + if name in self.users.keys(): + if self.ignore_imperative and \ + is_imperative(self.name, "groups", name): + return + taskd_cmd("remove", "group", self.name, name) + del self._lazy_groups[name] + + def get_user(self, name): + return self.users.get(name) + + @lazyprop + def users(self): + result = {} + for key in os.listdir(mkpath(self.name, "users")): + user = fetch_username(self.name, key) + if user is not None: + result[user] = User(self.name, user, key) + return result + + def get_group(self, name): + return self.groups.get(name) + + @lazyprop + def groups(self): + result = {} + for group in os.listdir(mkpath(self.name, "groups")): + result[group] = Group(self.name, group) + return result + + +class Manager(object): + def __init__(self, ignore_imperative=False): + """ + Instantiates an organisations manager. + + If ignore_imperative is True, all actions that modify data are checked + whether they're created imperatively and if so, they will result in no + operation. + """ + self.ignore_imperative = ignore_imperative + + def add_org(self, name): + """ + Create a new organisation. + + Returns an 'Organisation' object or None if the organisation already + exists. + """ + if name not in self.orgs.keys(): + taskd_cmd("add", "org", name) + neworg = Organisation(name, self.ignore_imperative) + self._lazy_orgs[name] = neworg + return neworg + return None + + def del_org(self, name): + """ + Delete and revoke keys of an organisation with all its users and + groups. + """ + org = self.get_org(name) + if org is not None: + if self.ignore_imperative and is_imperative(name): + return + for user in org.users.keys(): + org.del_user(user) + for group in org.groups.keys(): + org.del_group(group) + taskd_cmd("remove", "org", name) + del self._lazy_orgs[name] + + def get_org(self, name): + return self.orgs.get(name) + + @lazyprop + def orgs(self): + result = {} + for org in os.listdir(mkpath()): + result[org] = Organisation(org, self.ignore_imperative) + return result + + +class OrganisationType(click.ParamType): + name = 'organisation' + + def convert(self, value, param, ctx): + org = Manager().get_org(value) + if org is None: + self.fail("Organisation {} does not exist.".format(value)) + return org + +ORGANISATION = OrganisationType() + + +@click.group() +@click.pass_context +def cli(ctx): + """ + Manage Taskserver users and certificates + """ + if not IS_AUTO_CONFIG: + return + for path in (CA_KEY, CA_CERT, CRL_FILE): + if not os.path.exists(path): + msg = "CA setup not done or incomplete, missing file {}." + ctx.fail(msg.format(path)) + + +@cli.group("org") +def org_cli(): + """ + Manage organisations + """ + pass + + +@cli.group("user") +def user_cli(): + """ + Manage users + """ + pass + + +@cli.group("group") +def group_cli(): + """ + Manage groups + """ + pass + + +@user_cli.command("list") +@click.argument("organisation", type=ORGANISATION) +def list_users(organisation): + """ + List all users belonging to the specified organisation. + """ + label("The following users exists for {}:".format(organisation.name)) + for user in organisation.users.values(): + sys.stdout.write(user.name + "\n") + + +@group_cli.command("list") +@click.argument("organisation", type=ORGANISATION) +def list_groups(organisation): + """ + List all users belonging to the specified organisation. + """ + label("The following users exists for {}:".format(organisation.name)) + for group in organisation.groups.values(): + sys.stdout.write(group.name + "\n") + + +@org_cli.command("list") +def list_orgs(): + """ + List available organisations + """ + label("The following organisations exist:") + for org in Manager().orgs: + sys.stdout.write(org.name + "\n") + + +@user_cli.command("getkey") +@click.argument("organisation", type=ORGANISATION) +@click.argument("user") +def get_uuid(organisation, user): + """ + Get the UUID of the specified user belonging to the specified organisation. + """ + userobj = organisation.get_user(user) + if userobj is None: + msg = "User {} doesn't exist in organisation {}." + sys.exit(msg.format(userobj.name, organisation.name)) + + label("User {} has the following UUID:".format(userobj.name)) + sys.stdout.write(user.key + "\n") + + +@user_cli.command("export") +@click.argument("organisation", type=ORGANISATION) +@click.argument("user") +def export_user(organisation, user): + """ + Export user of the specified organisation as a series of shell commands + that can be used on the client side to easily import the certificates. + + Note that the private key will be exported as well, so use this with care! + """ + userobj = organisation.get_user(user) + if userobj is None: + msg = "User {} doesn't exist in organisation {}." + sys.exit(msg.format(user, organisation.name)) + + sys.stdout.write(userobj.export()) + + +@org_cli.command("add") +@click.argument("name") +def add_org(name): + """ + Create an organisation with the specified name. + """ + if os.path.exists(mkpath(name)): + msg = "Organisation with name {} already exists." + sys.exit(msg.format(name)) + + taskd_cmd("add", "org", name) + mark_imperative(name) + + +@org_cli.command("remove") +@click.argument("name") +def del_org(name): + """ + Delete the organisation with the specified name. + + All of the users and groups will be deleted as well and client certificates + will be revoked. + """ + Manager().del_org(name) + msg = ("Organisation {} deleted. Be sure to restart the Taskserver" + " using 'systemctl restart taskserver.service' in order for" + " the certificate revocation to apply.") + click.echo(msg.format(name), err=True) + + +@user_cli.command("add") +@click.argument("organisation", type=ORGANISATION) +@click.argument("user") +def add_user(organisation, user): + """ + Create a user for the given organisation along with a client certificate + and print the key of the new user. + + The client certificate along with it's public key can be shown via the + 'user export' subcommand. + """ + userobj = organisation.add_user(user) + if userobj is None: + msg = "User {} already exists in organisation {}." + sys.exit(msg.format(user, organisation)) + else: + mark_imperative(organisation.name, "users", userobj.key) + + +@user_cli.command("remove") +@click.argument("organisation", type=ORGANISATION) +@click.argument("user") +def del_user(organisation, user): + """ + Delete a user from the given organisation. + + This will also revoke the client certificate of the given user. + """ + organisation.del_user(user) + msg = ("User {} deleted. Be sure to restart the Taskserver using" + " 'systemctl restart taskserver.service' in order for the" + " certificate revocation to apply.") + click.echo(msg.format(user), err=True) + + +@group_cli.command("add") +@click.argument("organisation", type=ORGANISATION) +@click.argument("group") +def add_group(organisation, group): + """ + Create a group for the given organisation. + """ + groupobj = organisation.add_group(group) + if groupobj is None: + msg = "Group {} already exists in organisation {}." + sys.exit(msg.format(group, organisation)) + else: + mark_imperative(organisation.name, "groups", groupobj.name) + + +@group_cli.command("remove") +@click.argument("organisation", type=ORGANISATION) +@click.argument("group") +def del_group(organisation, group): + """ + Delete a group from the given organisation. + """ + organisation.del_group(group) + click("Group {} deleted.".format(group), err=True) + + +def add_or_delete(old, new, add_fun, del_fun): + """ + Given an 'old' and 'new' list, figure out the intersections and invoke + 'add_fun' against every element that is not in the 'old' list and 'del_fun' + against every element that is not in the 'new' list. + + Returns a tuple where the first element is the list of elements that were + added and the second element consisting of elements that were deleted. + """ + old_set = set(old) + new_set = set(new) + to_delete = old_set - new_set + to_add = new_set - old_set + for elem in to_delete: + del_fun(elem) + for elem in to_add: + add_fun(elem) + return to_add, to_delete + + +@cli.command("process-json") +@click.argument('json-file', type=click.File('rb')) +def process_json(json_file): + """ + Create and delete users, groups and organisations based on a JSON file. + + The structure of this file is exactly the same as the + 'services.taskserver.organisations' option of the NixOS module and is used + for declaratively adding and deleting users. + + Hence this subcommand is not recommended outside of the scope of the NixOS + module. + """ + data = json.load(json_file) + + mgr = Manager(ignore_imperative=True) + add_or_delete(mgr.orgs.keys(), data.keys(), mgr.add_org, mgr.del_org) + + for org in mgr.orgs.values(): + if is_imperative(org.name): + continue + add_or_delete(org.users.keys(), data[org.name]['users'], + org.add_user, org.del_user) + add_or_delete(org.groups.keys(), data[org.name]['groups'], + org.add_group, org.del_group) + + +if __name__ == '__main__': + cli() diff --git a/nixpkgs/nixos/modules/services/misc/tautulli.nix b/nixpkgs/nixos/modules/services/misc/tautulli.nix new file mode 100644 index 000000000000..50e450366478 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/tautulli.nix @@ -0,0 +1,77 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.tautulli; +in +{ + options = { + services.tautulli = { + enable = mkEnableOption "Tautulli Plex Monitor"; + + dataDir = mkOption { + type = types.str; + default = "/var/lib/plexpy"; + description = "The directory where Tautulli stores its data files."; + }; + + configFile = mkOption { + type = types.str; + default = "/var/lib/plexpy/config.ini"; + description = "The location of Tautulli's config file."; + }; + + port = mkOption { + type = types.int; + default = 8181; + description = "TCP port where Tautulli listens."; + }; + + user = mkOption { + type = types.str; + default = "plexpy"; + description = "User account under which Tautulli runs."; + }; + + group = mkOption { + type = types.str; + default = "nogroup"; + description = "Group under which Tautulli runs."; + }; + + package = mkOption { + type = types.package; + default = pkgs.tautulli; + defaultText = "pkgs.tautulli"; + description = '' + The Tautulli package to use. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.tmpfiles.rules = [ + "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -" + ]; + + systemd.services.tautulli = { + description = "Tautulli Plex Monitor"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + GuessMainPID = "false"; + ExecStart = "${cfg.package}/bin/tautulli --datadir ${cfg.dataDir} --config ${cfg.configFile} --port ${toString cfg.port} --pidfile ${cfg.dataDir}/tautulli.pid --nolaunch"; + Restart = "on-failure"; + }; + }; + + users.users = mkIf (cfg.user == "plexpy") { + plexpy = { group = cfg.group; uid = config.ids.uids.plexpy; }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/tzupdate.nix b/nixpkgs/nixos/modules/services/misc/tzupdate.nix new file mode 100644 index 000000000000..570982ced29a --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/tzupdate.nix @@ -0,0 +1,45 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.tzupdate; +in { + options.services.tzupdate = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Enable the tzupdate timezone updating service. This provides + a one-shot service which can be activated with systemctl to + update the timezone. + ''; + }; + }; + + config = mkIf cfg.enable { + # We need to have imperative time zone management for this to work. + # This will give users an error if they have set an explicit time + # zone, which is better than silently overriding it. + time.timeZone = null; + + # We provide a one-shot service which can be manually run. We could + # provide a service that runs on startup, but it's tricky to get + # a service to run after you have *internet* access. + systemd.services.tzupdate = { + description = "tzupdate timezone update service"; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + + serviceConfig = { + Type = "oneshot"; + # We could link directly into pkgs.tzdata, but at least timedatectl seems + # to expect the symlink to point directly to a file in etc. + # Setting the "debian timezone file" to point at /dev/null stops it doing anything. + ExecStart = "${pkgs.tzupdate}/bin/tzupdate -z /etc/zoneinfo -d /dev/null"; + }; + }; + }; + + meta.maintainers = [ maintainers.michaelpj ]; +} diff --git a/nixpkgs/nixos/modules/services/misc/uhub.nix b/nixpkgs/nixos/modules/services/misc/uhub.nix new file mode 100644 index 000000000000..005951b9231e --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/uhub.nix @@ -0,0 +1,186 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.uhub; + + uhubPkg = pkgs.uhub.override { tlsSupport = cfg.enableTLS; }; + + pluginConfig = "" + + optionalString cfg.plugins.authSqlite.enable '' + plugin ${uhubPkg.mod_auth_sqlite}/mod_auth_sqlite.so "file=${cfg.plugins.authSqlite.file}" + '' + + optionalString cfg.plugins.logging.enable '' + plugin ${uhubPkg.mod_logging}/mod_logging.so ${if cfg.plugins.logging.syslog then "syslog=true" else "file=${cfg.plugins.logging.file}"} + '' + + optionalString cfg.plugins.welcome.enable '' + plugin ${uhubPkg.mod_welcome}/mod_welcome.so "motd=${pkgs.writeText "motd.txt" cfg.plugins.welcome.motd} rules=${pkgs.writeText "rules.txt" cfg.plugins.welcome.rules}" + '' + + optionalString cfg.plugins.history.enable '' + plugin ${uhubPkg.mod_chat_history}/mod_chat_history.so "history_max=${toString cfg.plugins.history.max} history_default=${toString cfg.plugins.history.default} history_connect=${toString cfg.plugins.history.connect}" + ''; + + uhubConfigFile = pkgs.writeText "uhub.conf" '' + file_acl=${pkgs.writeText "users.conf" cfg.aclConfig} + file_plugins=${pkgs.writeText "plugins.conf" pluginConfig} + server_bind_addr=${cfg.address} + server_port=${toString cfg.port} + ${lib.optionalString cfg.enableTLS "tls_enable=yes"} + ${cfg.hubConfig} + ''; + +in + +{ + options = { + + services.uhub = { + + enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable the uhub ADC hub."; + }; + + port = mkOption { + type = types.int; + default = 1511; + description = "TCP port to bind the hub to."; + }; + + address = mkOption { + type = types.string; + default = "any"; + description = "Address to bind the hub to."; + }; + + enableTLS = mkOption { + type = types.bool; + default = false; + description = "Whether to enable TLS support."; + }; + + hubConfig = mkOption { + type = types.lines; + default = ""; + description = "Contents of uhub configuration file."; + }; + + aclConfig = mkOption { + type = types.lines; + default = ""; + description = "Contents of user ACL configuration file."; + }; + + plugins = { + + authSqlite = { + enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable the Sqlite authentication database plugin"; + }; + file = mkOption { + type = types.string; + example = "/var/db/uhub-users"; + description = "Path to user database. Use the uhub-passwd utility to create the database and add/remove users."; + }; + }; + + logging = { + enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable the logging plugin."; + }; + file = mkOption { + type = types.string; + default = ""; + description = "Path of log file."; + }; + syslog = mkOption { + type = types.bool; + default = false; + description = "If true then the system log is used instead of writing to file."; + }; + }; + + welcome = { + enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable the welcome plugin."; + }; + motd = mkOption { + default = ""; + type = types.lines; + description = '' + Welcome message displayed to clients after connecting + and with the <literal>!motd</literal> command. + ''; + }; + rules = mkOption { + default = ""; + type = types.lines; + description = '' + Rules message, displayed to clients with the <literal>!rules</literal> command. + ''; + }; + }; + + history = { + enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable the history plugin."; + }; + max = mkOption { + type = types.int; + default = 200; + description = "The maximum number of messages to keep in history"; + }; + default = mkOption { + type = types.int; + default = 10; + description = "When !history is provided without arguments, then this default number of messages are returned."; + }; + connect = mkOption { + type = types.int; + default = 5; + description = "The number of chat history messages to send when users connect (0 = do not send any history)."; + }; + }; + + }; + }; + + }; + + config = mkIf cfg.enable { + + users = { + users = singleton { + name = "uhub"; + uid = config.ids.uids.uhub; + }; + groups = singleton { + name = "uhub"; + gid = config.ids.gids.uhub; + }; + }; + + systemd.services.uhub = { + description = "high performance peer-to-peer hub for the ADC network"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "notify"; + ExecStart = "${uhubPkg}/bin/uhub -c ${uhubConfigFile} -u uhub -g uhub -L"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + }; + }; + }; + +} \ No newline at end of file diff --git a/nixpkgs/nixos/modules/services/misc/weechat.nix b/nixpkgs/nixos/modules/services/misc/weechat.nix new file mode 100644 index 000000000000..c6ff540ea12f --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/weechat.nix @@ -0,0 +1,58 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.weechat; +in + +{ + options.services.weechat = { + enable = mkEnableOption "weechat"; + root = mkOption { + description = "Weechat state directory."; + type = types.str; + default = "/var/lib/weechat"; + }; + sessionName = mkOption { + description = "Name of the `screen' session for weechat."; + default = "weechat-screen"; + type = types.str; + }; + binary = mkOption { + description = "Binary to execute (by default \${weechat}/bin/weechat)."; + example = literalExample '' + ''${pkgs.weechat}/bin/weechat-headless + ''; + default = "${pkgs.weechat}/bin/weechat"; + }; + }; + + config = mkIf cfg.enable { + users = { + groups.weechat = {}; + users.weechat = { + createHome = true; + group = "weechat"; + home = cfg.root; + isSystemUser = true; + }; + }; + + systemd.services.weechat = { + environment.WEECHAT_HOME = cfg.root; + serviceConfig = { + User = "weechat"; + Group = "weechat"; + RemainAfterExit = "yes"; + }; + script = "exec ${config.security.wrapperDir}/screen -Dm -S ${cfg.sessionName} ${cfg.binary}"; + wantedBy = [ "multi-user.target" ]; + wants = [ "network.target" ]; + }; + + security.wrappers.screen.source = "${pkgs.screen}/bin/screen"; + }; + + meta.doc = ./weechat.xml; +} diff --git a/nixpkgs/nixos/modules/services/misc/weechat.xml b/nixpkgs/nixos/modules/services/misc/weechat.xml new file mode 100644 index 000000000000..7255edfb9da3 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/weechat.xml @@ -0,0 +1,66 @@ +<chapter xmlns="http://docbook.org/ns/docbook" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:xi="http://www.w3.org/2001/XInclude" + version="5.0" + xml:id="module-services-weechat"> + <title>WeeChat</title> + <para> + <link xlink:href="https://weechat.org/">WeeChat</link> is a fast and + extensible IRC client. + </para> + <section xml:id="module-services-weechat-basic-usage"> + <title>Basic Usage</title> + + <para> + By default, the module creates a + <literal><link xlink:href="https://www.freedesktop.org/wiki/Software/systemd/">systemd</link></literal> + unit which runs the chat client in a detached + <literal><link xlink:href="https://www.gnu.org/software/screen/">screen</link></literal> + session. + </para> + + <para> + This can be done by enabling the <literal>weechat</literal> service: +<programlisting> +{ ... }: + +{ + <link linkend="opt-services.weechat.enable">services.weechat.enable</link> = true; +} +</programlisting> + </para> + + <para> + The service is managed by a dedicated user named <literal>weechat</literal> + in the state directory <literal>/var/lib/weechat</literal>. + </para> + </section> + <section xml:id="module-services-weechat-reattach"> + <title>Re-attaching to WeeChat</title> + + <para> + WeeChat runs in a screen session owned by a dedicated user. To explicitly + allow your another user to attach to this session, the + <literal>screenrc</literal> needs to be tweaked by adding + <link xlink:href="https://www.gnu.org/software/screen/manual/html_node/Multiuser.html#Multiuser">multiuser</link> + support: +<programlisting> +{ + <link linkend="opt-programs.screen.screenrc">programs.screen.screenrc</link> = '' + multiuser on + acladd normal_user + ''; +} +</programlisting> + Now, the session can be re-attached like this: +<programlisting> +screen -x weechat/weechat-screen +</programlisting> + </para> + + <para> + <emphasis>The session name can be changed using + <link linkend="opt-services.weechat.sessionName">services.weechat.sessionName.</link></emphasis> + </para> + </section> +</chapter> diff --git a/nixpkgs/nixos/modules/services/misc/xmr-stak.nix b/nixpkgs/nixos/modules/services/misc/xmr-stak.nix new file mode 100644 index 000000000000..a87878c31e0d --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/xmr-stak.nix @@ -0,0 +1,93 @@ +{ lib, config, pkgs, ... }: + +with lib; + +let + + cfg = config.services.xmr-stak; + + pkg = pkgs.xmr-stak.override { + inherit (cfg) openclSupport cudaSupport; + }; + +in + +{ + options = { + services.xmr-stak = { + enable = mkEnableOption "xmr-stak miner"; + openclSupport = mkEnableOption "support for OpenCL (AMD/ATI graphics cards)"; + cudaSupport = mkEnableOption "support for CUDA (NVidia graphics cards)"; + + extraArgs = mkOption { + type = types.listOf types.str; + default = []; + example = [ "--noCPU" "--currency monero" ]; + description = "List of parameters to pass to xmr-stak."; + }; + + configFiles = mkOption { + type = types.attrsOf types.str; + default = {}; + example = literalExample '' + { + "config.txt" = ''' + "verbose_level" : 4, + "h_print_time" : 60, + "tls_secure_algo" : true, + '''; + "pools.txt" = ''' + "currency" : "monero7", + "pool_list" : + [ { "pool_address" : "pool.supportxmr.com:443", + "wallet_address" : "my-wallet-address", + "rig_id" : "", + "pool_password" : "nixos", + "use_nicehash" : false, + "use_tls" : true, + "tls_fingerprint" : "", + "pool_weight" : 23 + }, + ], + '''; + } + ''; + description = '' + Content of config files like config.txt, pools.txt or cpu.txt. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.xmr-stak = { + wantedBy = [ "multi-user.target" ]; + bindsTo = [ "network-online.target" ]; + after = [ "network-online.target" ]; + environment = mkIf cfg.cudaSupport { + LD_LIBRARY_PATH = "${pkgs.linuxPackages_latest.nvidia_x11}/lib"; + }; + + preStart = concatStrings (flip mapAttrsToList cfg.configFiles (fn: content: '' + ln -sf '${pkgs.writeText "xmr-stak-${fn}" content}' '${fn}' + '')); + + serviceConfig = let rootRequired = cfg.openclSupport || cfg.cudaSupport; in { + ExecStart = "${pkg}/bin/xmr-stak ${concatStringsSep " " cfg.extraArgs}"; + # xmr-stak generates cpu and/or gpu configuration files + WorkingDirectory = "/tmp"; + PrivateTmp = true; + DynamicUser = !rootRequired; + LimitMEMLOCK = toString (1024*1024); + }; + }; + }; + + imports = [ + (mkRemovedOptionModule ["services" "xmr-stak" "configText"] '' + This option was removed in favour of `services.xmr-stak.configFiles` + because the new config file `pools.txt` was introduced. You are + now able to define all other config files like cpu.txt or amd.txt. + '') + ]; +} diff --git a/nixpkgs/nixos/modules/services/misc/zoneminder.nix b/nixpkgs/nixos/modules/services/misc/zoneminder.nix new file mode 100644 index 000000000000..2bd2f3c7cc08 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/zoneminder.nix @@ -0,0 +1,360 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.zoneminder; + pkg = pkgs.zoneminder; + + dirName = pkg.dirName; + + user = "zoneminder"; + group = { + nginx = config.services.nginx.group; + none = user; + }."${cfg.webserver}"; + + useNginx = cfg.webserver == "nginx"; + + defaultDir = "/var/lib/${user}"; + home = if useCustomDir then cfg.storageDir else defaultDir; + + useCustomDir = !(builtins.isNull cfg.storageDir); + + socket = "/run/phpfpm/${dirName}.sock"; + + zms = "/cgi-bin/zms"; + + dirs = dirList: [ dirName ] ++ map (e: "${dirName}/${e}") dirList; + + cacheDirs = [ "swap" ]; + libDirs = [ "events" "exports" "images" "sounds" ]; + + dirStanzas = baseDir: + lib.concatStringsSep "\n" (map (e: + "ZM_DIR_${lib.toUpper e}=${baseDir}/${e}" + ) libDirs); + + defaultsFile = pkgs.writeText "60-defaults.conf" '' + # 01-system-paths.conf + ${dirStanzas home} + ZM_PATH_ARP=${lib.getBin pkgs.nettools}/bin/arp + ZM_PATH_LOGS=/var/log/${dirName} + ZM_PATH_MAP=/dev/shm + ZM_PATH_SOCKS=/run/${dirName} + ZM_PATH_SWAP=/var/cache/${dirName}/swap + ZM_PATH_ZMS=${zms} + + # 02-multiserver.conf + ZM_SERVER_HOST= + + # Database + ZM_DB_TYPE=mysql + ZM_DB_HOST=${cfg.database.host} + ZM_DB_NAME=${cfg.database.name} + ZM_DB_USER=${if cfg.database.createLocally then user else cfg.database.username} + ZM_DB_PASS=${cfg.database.password} + + # Web + ZM_WEB_USER=${user} + ZM_WEB_GROUP=${group} + ''; + + configFile = pkgs.writeText "80-nixos.conf" '' + # You can override defaults here + + ${cfg.extraConfig} + ''; + + phpExtensions = with pkgs.phpPackages; [ + { pkg = apcu; name = "apcu"; } + ]; + +in { + options = { + services.zoneminder = with lib; { + enable = lib.mkEnableOption '' + ZoneMinder + </para><para> + If you intend to run the database locally, you should set + `config.services.zoneminder.database.createLocally` to true. Otherwise, + when set to `false` (the default), you will have to create the database + and database user as well as populate the database yourself. + ''; + + webserver = mkOption { + type = types.enum [ "nginx" "none" ]; + default = "nginx"; + description = '' + The webserver to configure for the PHP frontend. + </para> + <para> + + Set it to `none` if you want to configure it yourself. PRs are welcome + for support for other web servers. + ''; + }; + + hostname = mkOption { + type = types.str; + default = "localhost"; + description = '' + The hostname on which to listen. + ''; + }; + + port = mkOption { + type = types.int; + default = 8095; + description = '' + The port on which to listen. + ''; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = '' + Open the firewall port(s). + ''; + }; + + database = { + createLocally = mkOption { + type = types.bool; + default = false; + description = '' + Create the database and database user locally. + ''; + }; + + host = mkOption { + type = types.str; + default = "localhost"; + description = '' + Hostname hosting the database. + ''; + }; + + name = mkOption { + type = types.str; + default = "zm"; + description = '' + Name of database. + ''; + }; + + username = mkOption { + type = types.str; + default = "zmuser"; + description = '' + Username for accessing the database. + ''; + }; + + password = mkOption { + type = types.str; + default = "zmpass"; + description = '' + Username for accessing the database. + ''; + }; + }; + + cameras = mkOption { + type = types.int; + default = 1; + description = '' + Set this to the number of cameras you expect to support. + ''; + }; + + storageDir = mkOption { + type = types.nullOr types.str; + default = null; + example = "/storage/tank"; + description = '' + ZoneMinder can generate quite a lot of data, so in case you don't want + to use the default ${home}, you can override the path here. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Additional configuration added verbatim to the configuration file. + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + + environment.etc = { + "zoneminder/60-defaults.conf".source = defaultsFile; + "zoneminder/80-nixos.conf".source = configFile; + }; + + networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ]; + + services = { + fcgiwrap = lib.mkIf useNginx { + enable = true; + preforkProcesses = cfg.cameras; + inherit user group; + }; + + mysql = lib.mkIf cfg.database.createLocally { + ensureDatabases = [ cfg.database.name ]; + initialDatabases = [{ + inherit (cfg.database) name; schema = "${pkg}/share/zoneminder/db/zm_create.sql"; + }]; + ensureUsers = [{ + name = cfg.database.username; + ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; }; + }]; + }; + + nginx = lib.mkIf useNginx { + enable = true; + virtualHosts = { + "${cfg.hostname}" = { + default = true; + root = "${pkg}/share/zoneminder/www"; + listen = [ { addr = "0.0.0.0"; inherit (cfg) port; } ]; + extraConfig = let + fcgi = config.services.fcgiwrap; + in '' + index index.php; + + location / { + try_files $uri $uri/ /index.php?$args =404; + + location ~ /api/(css|img|ico) { + rewrite ^/api(.+)$ /api/app/webroot/$1 break; + try_files $uri $uri/ =404; + } + + location ~ \.(gif|ico|jpg|jpeg|png)$ { + access_log off; + expires 30d; + } + + location /api { + rewrite ^/api(.+)$ /api/app/webroot/index.php?p=$1 last; + } + + location /cgi-bin { + gzip off; + + include ${pkgs.nginx}/conf/fastcgi_params; + fastcgi_param SCRIPT_FILENAME ${pkg}/libexec/zoneminder/${zms}; + fastcgi_param HTTP_PROXY ""; + fastcgi_intercept_errors on; + + fastcgi_pass ${fcgi.socketType}:${fcgi.socketAddress}; + } + + location /cache { + alias /var/cache/${dirName}; + } + + location ~ \.php$ { + try_files $uri =404; + fastcgi_index index.php; + + include ${pkgs.nginx}/conf/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $request_filename; + fastcgi_param HTTP_PROXY ""; + + fastcgi_pass unix:${socket}; + } + } + ''; + }; + }; + }; + + phpfpm = lib.mkIf useNginx { + pools.zoneminder = { + listen = socket; + phpOptions = '' + date.timezone = "${config.time.timeZone}" + + ${lib.concatStringsSep "\n" (map (e: + "extension=${e.pkg}/lib/php/extensions/${e.name}.so") phpExtensions)} + ''; + extraConfig = '' + user = ${user} + group = ${group} + + listen.owner = ${user} + listen.group = ${group} + listen.mode = 0660 + + pm = dynamic + pm.start_servers = 1 + pm.min_spare_servers = 1 + pm.max_spare_servers = 2 + pm.max_requests = 500 + pm.max_children = 5 + pm.status_path = /$pool-status + ping.path = /$pool-ping + ''; + }; + }; + }; + + systemd.services = { + zoneminder = with pkgs; rec { + inherit (zoneminder.meta) description; + documentation = [ "https://zoneminder.readthedocs.org/en/latest/" ]; + path = [ + coreutils + procps + psmisc + ]; + after = [ "mysql.service" "nginx.service" ]; + wantedBy = [ "multi-user.target" ]; + restartTriggers = [ defaultsFile configFile ]; + preStart = lib.mkIf useCustomDir '' + install -dm775 -o ${user} -g ${group} ${cfg.storageDir}/{${lib.concatStringsSep "," libDirs}} + ''; + serviceConfig = { + User = user; + Group = group; + SupplementaryGroups = [ "video" ]; + ExecStart = "${zoneminder}/bin/zmpkg.pl start"; + ExecStop = "${zoneminder}/bin/zmpkg.pl stop"; + ExecReload = "${zoneminder}/bin/zmpkg.pl restart"; + PIDFile = "/run/${dirName}/zm.pid"; + Type = "forking"; + Restart = "on-failure"; + RestartSec = "10s"; + CacheDirectory = dirs cacheDirs; + RuntimeDirectory = dirName; + ReadWriteDirectories = lib.mkIf useCustomDir [ cfg.storageDir ]; + StateDirectory = dirs (if useCustomDir then [] else libDirs); + LogsDirectory = dirName; + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectKernelTunables = true; + SystemCallArchitectures = "native"; + NoNewPrivileges = true; + }; + }; + }; + + users.groups."${user}" = { + gid = config.ids.gids.zoneminder; + }; + + users.users."${user}" = { + uid = config.ids.uids.zoneminder; + group = user; + inherit home; + inherit (pkgs.zoneminder.meta) description; + }; + }; + + meta.maintainers = with lib.maintainers; [ peterhoeg ]; +} diff --git a/nixpkgs/nixos/modules/services/misc/zookeeper.nix b/nixpkgs/nixos/modules/services/misc/zookeeper.nix new file mode 100644 index 000000000000..50c84e3c6b80 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/zookeeper.nix @@ -0,0 +1,155 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.zookeeper; + + zookeeperConfig = '' + dataDir=${cfg.dataDir} + clientPort=${toString cfg.port} + autopurge.purgeInterval=${toString cfg.purgeInterval} + ${cfg.extraConf} + ${cfg.servers} + ''; + + configDir = pkgs.buildEnv { + name = "zookeeper-conf"; + paths = [ + (pkgs.writeTextDir "zoo.cfg" zookeeperConfig) + (pkgs.writeTextDir "log4j.properties" cfg.logging) + ]; + }; + +in { + + options.services.zookeeper = { + enable = mkOption { + description = "Whether to enable Zookeeper."; + default = false; + type = types.bool; + }; + + port = mkOption { + description = "Zookeeper Client port."; + default = 2181; + type = types.int; + }; + + id = mkOption { + description = "Zookeeper ID."; + default = 0; + type = types.int; + }; + + purgeInterval = mkOption { + description = '' + The time interval in hours for which the purge task has to be triggered. Set to a positive integer (1 and above) to enable the auto purging. + ''; + default = 1; + type = types.int; + }; + + extraConf = mkOption { + description = "Extra configuration for Zookeeper."; + type = types.lines; + default = '' + initLimit=5 + syncLimit=2 + tickTime=2000 + ''; + }; + + servers = mkOption { + description = "All Zookeeper Servers."; + default = ""; + type = types.lines; + example = '' + server.0=host0:2888:3888 + server.1=host1:2888:3888 + server.2=host2:2888:3888 + ''; + }; + + logging = mkOption { + description = "Zookeeper logging configuration."; + default = '' + zookeeper.root.logger=INFO, CONSOLE + log4j.rootLogger=INFO, CONSOLE + log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender + log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout + log4j.appender.CONSOLE.layout.ConversionPattern=[myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n + ''; + type = types.lines; + }; + + dataDir = mkOption { + type = types.path; + default = "/var/lib/zookeeper"; + description = '' + Data directory for Zookeeper + ''; + }; + + extraCmdLineOptions = mkOption { + description = "Extra command line options for the Zookeeper launcher."; + default = [ "-Dcom.sun.management.jmxremote" "-Dcom.sun.management.jmxremote.local.only=true" ]; + type = types.listOf types.str; + example = [ "-Djava.net.preferIPv4Stack=true" "-Dcom.sun.management.jmxremote" "-Dcom.sun.management.jmxremote.local.only=true" ]; + }; + + preferIPv4 = mkOption { + type = types.bool; + default = true; + description = '' + Add the -Djava.net.preferIPv4Stack=true flag to the Zookeeper server. + ''; + }; + + package = mkOption { + description = "The zookeeper package to use"; + default = pkgs.zookeeper; + defaultText = "pkgs.zookeeper"; + type = types.package; + }; + + }; + + + config = mkIf cfg.enable { + environment.systemPackages = [cfg.package]; + + systemd.tmpfiles.rules = [ + "d '${cfg.dataDir}' 0700 zookeeper - - -" + ]; + + systemd.services.zookeeper = { + description = "Zookeeper Daemon"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + environment = { ZOOCFGDIR = configDir; }; + serviceConfig = { + ExecStart = '' + ${pkgs.jre}/bin/java \ + -cp "${cfg.package}/lib/*:${cfg.package}/${cfg.package.name}.jar:${configDir}" \ + ${escapeShellArgs cfg.extraCmdLineOptions} \ + -Dzookeeper.datadir.autocreate=false \ + ${optionalString cfg.preferIPv4 "-Djava.net.preferIPv4Stack=true"} \ + org.apache.zookeeper.server.quorum.QuorumPeerMain \ + ${configDir}/zoo.cfg + ''; + User = "zookeeper"; + }; + preStart = '' + echo "${toString cfg.id}" > ${cfg.dataDir}/myid + ''; + }; + + users.users = singleton { + name = "zookeeper"; + uid = config.ids.uids.zookeeper; + description = "Zookeeper daemon user"; + home = cfg.dataDir; + }; + }; +} |