aboutsummaryrefslogtreecommitdiff
path: root/infra/libkookie/nixpkgs/nixos/modules/services/misc
diff options
context:
space:
mode:
Diffstat (limited to 'infra/libkookie/nixpkgs/nixos/modules/services/misc')
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/airsonic.nix155
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/ankisyncd.nix79
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/apache-kafka.nix154
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/autofs.nix99
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/autorandr.nix52
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/bazarr.nix76
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/beanstalkd.nix63
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/bees.nix123
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/bepasty.nix179
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/calibre-server.nix86
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/canto-daemon.nix37
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/cfdyndns.nix82
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/cgminer.nix140
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/clipmenu.nix31
-rwxr-xr-xinfra/libkookie/nixpkgs/nixos/modules/services/misc/confd.nix90
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/couchpotato.nix42
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/cpuminer-cryptonight.nix66
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/defaultUnicornConfig.rb69
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/devmon.nix25
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/dictd.nix65
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/disnix.nix98
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/docker-registry.nix157
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/domoticz.nix51
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/dwm-status.nix73
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/dysnomia.nix257
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/errbot.nix104
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/etcd.nix195
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/ethminer.nix117
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/exhibitor.nix419
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/felix.nix102
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/freeswitch.nix105
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/fstrim.nix46
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/gammu-smsd.nix253
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/geoip-updater.nix306
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/gitea.nix609
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/gitit.nix724
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/gitlab.nix967
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/gitlab.xml132
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/gitolite.nix232
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/gitweb.nix59
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/gogs.nix271
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/gollum.nix118
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/gpsd.nix115
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/greenclip.nix31
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/headphones.nix87
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/home-assistant.nix280
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/ihaskell.nix63
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/irkerd.nix67
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/jackett.nix82
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/jellyfin.nix110
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/klipper.nix59
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/leaps.nix62
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/lidarr.nix89
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/logkeys.nix30
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/mame.nix67
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/matrix-appservice-discord.nix162
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/matrix-synapse-log_config.yaml25
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/matrix-synapse.nix737
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/matrix-synapse.xml227
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/mautrix-telegram.nix166
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/mbpfan.nix109
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/mediatomb.nix391
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/metabase.nix103
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/mwlib.nix258
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/n8n.nix78
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/nix-daemon.nix599
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/nix-gc.nix61
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/nix-optimise.nix51
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/nix-ssh-serve.nix61
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/novacomd.nix31
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/nzbget.nix99
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/octoprint.nix129
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/osrm.nix86
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/packagekit.nix65
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/paperless.nix183
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/parsoid.nix129
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/pinnwand.nix78
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/plex.nix151
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/pykms.nix91
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/radarr.nix75
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/redmine.nix383
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/ripple-data-api.nix193
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/rippled.nix431
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/safeeyes.nix47
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/serviio.nix87
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/sickbeard.nix92
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/siproxd.nix179
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/snapper.nix162
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/sonarr.nix76
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/spice-vdagentd.nix30
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/ssm-agent.nix66
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/sssd.nix97
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/subsonic.nix165
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/sundtek.nix33
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/svnserve.nix45
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/synergy.nix124
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/sysprof.nix19
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/taskserver/default.nix568
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/taskserver/doc.xml135
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/taskserver/helper-tool.py688
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/tautulli.nix81
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/tiddlywiki.nix52
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/tzupdate.nix45
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/uhub.nix180
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/weechat.nix58
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/weechat.xml66
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/xmr-stak.nix93
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/zigbee2mqtt.nix99
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/zoneminder.nix370
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/misc/zookeeper.nix155
110 files changed, 17214 insertions, 0 deletions
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/airsonic.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/airsonic.nix
new file mode 100644
index 000000000000..5cc2ff7f4bd1
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/airsonic.nix
@@ -0,0 +1,155 @@
+{ 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.str;
+ 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 = [ "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;
+ recommendedProxySettings = 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;
+ isSystemUser = true;
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/ankisyncd.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/ankisyncd.nix
new file mode 100644
index 000000000000..5fc19649d3d9
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/ankisyncd.nix
@@ -0,0 +1,79 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.ankisyncd;
+
+ name = "ankisyncd";
+
+ stateDir = "/var/lib/${name}";
+
+ authDbPath = "${stateDir}/auth.db";
+
+ sessionDbPath = "${stateDir}/session.db";
+
+ configFile = pkgs.writeText "ankisyncd.conf" (lib.generators.toINI {} {
+ sync_app = {
+ host = cfg.host;
+ port = cfg.port;
+ data_root = stateDir;
+ auth_db_path = authDbPath;
+ session_db_path = sessionDbPath;
+
+ base_url = "/sync/";
+ base_media_url = "/msync/";
+ };
+ });
+in
+ {
+ options.services.ankisyncd = {
+ enable = mkEnableOption "ankisyncd";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.ankisyncd;
+ defaultText = literalExample "pkgs.ankisyncd";
+ description = "The package to use for the ankisyncd command.";
+ };
+
+ host = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "ankisyncd host";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 27701;
+ description = "ankisyncd port";
+ };
+
+ 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 ];
+
+ environment.etc."ankisyncd/ankisyncd.conf".source = configFile;
+
+ systemd.services.ankisyncd = {
+ description = "ankisyncd - Anki sync server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ cfg.package ];
+
+ serviceConfig = {
+ Type = "simple";
+ DynamicUser = true;
+ StateDirectory = name;
+ ExecStart = "${cfg.package}/bin/ankisyncd";
+ Restart = "always";
+ };
+ };
+ };
+ }
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/apache-kafka.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/apache-kafka.nix
new file mode 100644
index 000000000000..f3a650a260f1
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/apache-kafka.nix
@@ -0,0 +1,154 @@
+{ 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.str;
+ };
+
+ 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.str;
+ };
+
+ 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.apache-kafka = {
+ uid = config.ids.uids.apache-kafka;
+ description = "Apache Kafka daemon user";
+ home = head cfg.logDirs;
+ };
+
+ systemd.tmpfiles.rules = map (logDir: "d '${logDir}' 0700 apache-kafka - - -") 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";
+ SuccessExitStatus = "0 143";
+ };
+ };
+
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/autofs.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/autofs.nix
new file mode 100644
index 000000000000..5e7c1e668288
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/autofs.nix
@@ -0,0 +1,99 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.autofs;
+
+ autoMaster = pkgs.writeText "auto.master" cfg.autoMaster;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.autofs = {
+
+ enable = mkOption {
+ type = types.bool;
+ 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 {
+ type = types.bool;
+ 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/infra/libkookie/nixpkgs/nixos/modules/services/misc/autorandr.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/autorandr.nix
new file mode 100644
index 000000000000..dfb418af6ede
--- /dev/null
+++ b/infra/libkookie/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" ];
+
+ startLimitIntervalSec = 5;
+ startLimitBurst = 1;
+ serviceConfig = {
+ ExecStart = "${pkgs.autorandr}/bin/autorandr --batch --change --default ${cfg.defaultTarget}";
+ Type = "oneshot";
+ RemainAfterExit = false;
+ };
+ };
+
+ };
+
+ meta.maintainers = with maintainers; [ gnidorah ];
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/bazarr.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/bazarr.nix
new file mode 100644
index 000000000000..d3fd5b08cc84
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/bazarr.nix
@@ -0,0 +1,76 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.bazarr;
+in
+{
+ options = {
+ services.bazarr = {
+ enable = mkEnableOption "bazarr, a subtitle manager for Sonarr and Radarr";
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Open ports in the firewall for the bazarr web interface.";
+ };
+
+ listenPort = mkOption {
+ type = types.port;
+ default = 6767;
+ description = "Port on which the bazarr web interface should listen";
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "bazarr";
+ description = "User account under which bazarr runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "bazarr";
+ description = "Group under which bazarr runs.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.bazarr = {
+ description = "bazarr";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = rec {
+ Type = "simple";
+ User = cfg.user;
+ Group = cfg.group;
+ StateDirectory = "bazarr";
+ SyslogIdentifier = "bazarr";
+ ExecStart = pkgs.writeShellScript "start-bazarr" ''
+ ${pkgs.bazarr}/bin/bazarr \
+ --config '/var/lib/${StateDirectory}' \
+ --port ${toString cfg.listenPort} \
+ --no-update True
+ '';
+ Restart = "on-failure";
+ };
+ };
+
+ networking.firewall = mkIf cfg.openFirewall {
+ allowedTCPPorts = [ cfg.listenPort ];
+ };
+
+ users.users = mkIf (cfg.user == "bazarr") {
+ bazarr = {
+ group = cfg.group;
+ home = "/var/lib/${config.systemd.services.bazarr.serviceConfig.StateDirectory}";
+ };
+ };
+
+ users.groups = mkIf (cfg.group == "bazarr") {
+ bazarr = {};
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/beanstalkd.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/beanstalkd.nix
new file mode 100644
index 000000000000..1c674a5b23bf
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/beanstalkd.nix
@@ -0,0 +1,63 @@
+{ 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";
+ };
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to open ports in the firewall for the server.";
+ };
+ };
+ };
+
+ # implementation
+
+ config = mkIf cfg.enable {
+
+ networking.firewall = mkIf cfg.openFirewall {
+ allowedTCPPorts = [ cfg.listen.port ];
+ };
+
+ 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} -b $STATE_DIRECTORY";
+ StateDirectory = "beanstalkd";
+ };
+ };
+
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/bees.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/bees.nix
new file mode 100644
index 000000000000..b0ed2d5c2862
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/bepasty.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/bepasty.nix
new file mode 100644
index 000000000000..f69832e5b2bd
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/bepasty.nix
@@ -0,0 +1,179 @@
+{ 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 server.secretKeyFile != null; 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.${user} =
+ { uid = config.ids.uids.bepasty;
+ group = group;
+ home = default_home;
+ };
+
+ users.groups.${group}.gid = config.ids.gids.bepasty;
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/calibre-server.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/calibre-server.nix
new file mode 100644
index 000000000000..2467d34b524a
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/calibre-server.nix
@@ -0,0 +1,86 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.calibre-server;
+
+in
+
+{
+ imports = [
+ (mkChangedOptionModule [ "services" "calibre-server" "libraryDir" ] [ "services" "calibre-server" "libraries" ]
+ (config:
+ let libraryDir = getAttrFromPath [ "services" "calibre-server" "libraryDir" ] config;
+ in [ libraryDir ]
+ )
+ )
+ ];
+
+ ###### interface
+
+ options = {
+ services.calibre-server = {
+
+ enable = mkEnableOption "calibre-server";
+
+ libraries = mkOption {
+ description = ''
+ The directories of the libraries to serve. They must be readable for the user under which the server runs.
+ '';
+ type = types.listOf types.path;
+ };
+
+ user = mkOption {
+ description = "The user under which calibre-server runs.";
+ type = types.str;
+ default = "calibre-server";
+ };
+
+ group = mkOption {
+ description = "The group under which calibre-server runs.";
+ type = types.str;
+ default = "calibre-server";
+ };
+
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.calibre-server = {
+ description = "Calibre Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ User = cfg.user;
+ Restart = "always";
+ ExecStart = "${pkgs.calibre}/bin/calibre-server ${lib.concatStringsSep " " cfg.libraries}";
+ };
+
+ };
+
+ environment.systemPackages = [ pkgs.calibre ];
+
+ users.users = optionalAttrs (cfg.user == "calibre-server") {
+ calibre-server = {
+ home = "/var/lib/calibre-server";
+ createHome = true;
+ uid = config.ids.uids.calibre-server;
+ group = cfg.group;
+ };
+ };
+
+ users.groups = optionalAttrs (cfg.group == "calibre-server") {
+ calibre-server = {
+ gid = config.ids.gids.calibre-server;
+ };
+ };
+
+ };
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/canto-daemon.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/canto-daemon.nix
new file mode 100644
index 000000000000..db51a263aab5
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/cfdyndns.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/cfdyndns.nix
new file mode 100644
index 000000000000..15af1f50da1d
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/cfdyndns.nix
@@ -0,0 +1,82 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.cfdyndns;
+in
+{
+ imports = [
+ (mkRemovedOptionModule
+ [ "services" "cfdyndns" "apikey" ]
+ "Use services.cfdyndns.apikeyFile instead.")
+ ];
+
+ options = {
+ services.cfdyndns = {
+ enable = mkEnableOption "Cloudflare Dynamic DNS Client";
+
+ email = mkOption {
+ type = types.str;
+ description = ''
+ The email address to use to authenticate to CloudFlare.
+ '';
+ };
+
+ apikeyFile = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The path to a file containing the API Key
+ used to authenticate with 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;
+ };
+ environment = {
+ CLOUDFLARE_EMAIL="${cfg.email}";
+ CLOUDFLARE_RECORDS="${concatStringsSep "," cfg.records}";
+ };
+ script = ''
+ ${optionalString (cfg.apikeyFile != null) ''
+ export CLOUDFLARE_APIKEY="$(cat ${escapeShellArg cfg.apikeyFile})"
+ ''}
+ ${pkgs.cfdyndns}/bin/cfdyndns
+ '';
+ };
+
+ users.users = {
+ cfdyndns = {
+ group = "cfdyndns";
+ uid = config.ids.uids.cfdyndns;
+ };
+ };
+
+ users.groups = {
+ cfdyndns = {
+ gid = config.ids.gids.cfdyndns;
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/cgminer.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/cgminer.nix
new file mode 100644
index 000000000000..fa9c8c54509e
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/cgminer.nix
@@ -0,0 +1,140 @@
+{ 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 = mkEnableOption "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") {
+ 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";
+ };
+
+ startLimitIntervalSec = 60; # 1 min
+ serviceConfig = {
+ ExecStart = "${pkgs.cgminer}/bin/cgminer --syslog --text-only --config ${cgminerConfig}";
+ User = cfg.user;
+ RestartSec = "30s";
+ Restart = "always";
+ };
+ };
+
+ };
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/clipmenu.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/clipmenu.nix
new file mode 100644
index 000000000000..3ba050044cac
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/confd.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/confd.nix
new file mode 100755
index 000000000000..c1ebdb3dde91
--- /dev/null
+++ b/infra/libkookie/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/confd";
+ };
+ };
+
+ environment.etc = {
+ "confd/confd.toml".text = confdConfig;
+ };
+
+ environment.systemPackages = [ cfg.package ];
+
+ services.etcd.enable = mkIf (cfg.backend == "etcd") (mkDefault true);
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/couchpotato.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/couchpotato.nix
new file mode 100644
index 000000000000..f5163cf86cf5
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/couchpotato.nix
@@ -0,0 +1,42 @@
+{ 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" ];
+
+ serviceConfig = {
+ Type = "simple";
+ User = "couchpotato";
+ Group = "couchpotato";
+ StateDirectory = "couchpotato";
+ ExecStart = "${pkgs.couchpotato}/bin/couchpotato";
+ Restart = "on-failure";
+ };
+ };
+
+ users.users.couchpotato =
+ { group = "couchpotato";
+ home = "/var/lib/couchpotato/";
+ description = "CouchPotato daemon user";
+ uid = config.ids.uids.couchpotato;
+ };
+
+ users.groups.couchpotato =
+ { gid = config.ids.gids.couchpotato; };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/cpuminer-cryptonight.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/cpuminer-cryptonight.nix
new file mode 100644
index 000000000000..907b9d90da29
--- /dev/null
+++ b/infra/libkookie/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.str;
+ description = "URL of mining server";
+ };
+ user = mkOption {
+ type = types.str;
+ description = "Username for mining server";
+ };
+ pass = mkOption {
+ type = types.str;
+ 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";
+ };
+ };
+
+ };
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/defaultUnicornConfig.rb b/infra/libkookie/nixpkgs/nixos/modules/services/misc/defaultUnicornConfig.rb
new file mode 100644
index 000000000000..0b58c59c7a51
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/devmon.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/devmon.nix
new file mode 100644
index 000000000000..e4a3348646b1
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/devmon.nix
@@ -0,0 +1,25 @@
+{ pkgs, config, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.devmon;
+
+in {
+ options = {
+ services.devmon = {
+ enable = mkEnableOption "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/infra/libkookie/nixpkgs/nixos/modules/services/misc/dictd.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/dictd.nix
new file mode 100644
index 000000000000..d175854d2d1e
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/dictd.nix
@@ -0,0 +1,65 @@
+{ 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 ];
+
+ users.users.dictd =
+ { group = "dictd";
+ description = "DICT.org dictd server";
+ home = "${dictdb}/share/dictd";
+ uid = config.ids.uids.dictd;
+ };
+
+ users.groups.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/infra/libkookie/nixpkgs/nixos/modules/services/misc/disnix.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/disnix.nix
new file mode 100644
index 000000000000..41483d80a2dd
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/disnix.nix
@@ -0,0 +1,98 @@
+# Disnix server
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.disnix;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.disnix = {
+
+ enable = mkEnableOption "Disnix";
+
+ enableMultiUser = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to support multi-user mode by enabling the Disnix D-Bus service";
+ };
+
+ useWebServiceInterface = mkEnableOption "the DisnixWebService interface running on Apache Tomcat";
+
+ package = mkOption {
+ type = types.path;
+ description = "The Disnix package";
+ default = pkgs.disnix;
+ defaultText = "pkgs.disnix";
+ };
+
+ enableProfilePath = mkEnableOption "exposing the Disnix profiles in the system's PATH";
+
+ profiles = mkOption {
+ type = types.listOf types.string;
+ default = [ "default" ];
+ example = [ "default" ];
+ description = "Names of the Disnix profiles to expose in the system's PATH";
+ };
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+ dysnomia.enable = true;
+
+ environment.systemPackages = [ pkgs.disnix ] ++ optional cfg.useWebServiceInterface pkgs.DisnixWebService;
+ environment.variables.PATH = lib.optionals cfg.enableProfilePath (map (profileName: "/nix/var/nix/profiles/disnix/${profileName}/bin" ) cfg.profiles);
+
+ 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.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"
+ ++ optional config.services.influxdb.enable "influxdb.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/infra/libkookie/nixpkgs/nixos/modules/services/misc/docker-registry.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/docker-registry.nix
new file mode 100644
index 000000000000..1c2e2cc53590
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/docker-registry.nix
@@ -0,0 +1,157 @@
+{ 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}
+ /run/current-system/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 {}) // {
+ isSystemUser = true;
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/domoticz.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/domoticz.nix
new file mode 100644
index 000000000000..b1353d484048
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/domoticz.nix
@@ -0,0 +1,51 @@
+{ lib, pkgs, config, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.domoticz;
+ pkgDesc = "Domoticz home automation";
+
+in {
+
+ options = {
+
+ services.domoticz = {
+ enable = mkEnableOption pkgDesc;
+
+ bind = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = "IP address to bind to.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 8080;
+ description = "Port to bind to for HTTP, set to 0 to disable HTTP.";
+ };
+
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ systemd.services."domoticz" = {
+ description = pkgDesc;
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network-online.target" ];
+ serviceConfig = {
+ DynamicUser = true;
+ StateDirectory = "domoticz";
+ Restart = "always";
+ ExecStart = ''
+ ${pkgs.domoticz}/bin/domoticz -noupdates -www ${toString cfg.port} -wwwbind ${cfg.bind} -sslwww 0 -userdata /var/lib/domoticz -approot ${pkgs.domoticz}/share/domoticz/ -pidfile /var/run/domoticz.pid
+ '';
+ };
+ };
+
+ };
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/dwm-status.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/dwm-status.nix
new file mode 100644
index 000000000000..b98a42e6a6d2
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/dwm-status.nix
@@ -0,0 +1,73 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.dwm-status;
+
+ order = concatMapStringsSep "," (feature: ''"${feature}"'') cfg.order;
+
+ configFile = pkgs.writeText "dwm-status.toml" ''
+ order = [${order}]
+
+ ${cfg.extraConfig}
+ '';
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.dwm-status = {
+
+ enable = mkEnableOption "dwm-status user service";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.dwm-status;
+ defaultText = "pkgs.dwm-status";
+ example = "pkgs.dwm-status.override { enableAlsaUtils = false; }";
+ description = ''
+ Which dwm-status package to use.
+ '';
+ };
+
+ order = mkOption {
+ type = types.listOf (types.enum [ "audio" "backlight" "battery" "cpu_load" "network" "time" ]);
+ description = ''
+ List of enabled features in order.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra config in TOML format.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ services.upower.enable = elem "battery" cfg.order;
+
+ systemd.user.services.dwm-status = {
+ description = "Highly performant and configurable DWM status service";
+ wantedBy = [ "graphical-session.target" ];
+ partOf = [ "graphical-session.target" ];
+
+ serviceConfig.ExecStart = "${cfg.package}/bin/dwm-status ${configFile}";
+ };
+
+ };
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/dysnomia.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/dysnomia.nix
new file mode 100644
index 000000000000..eb94791fbbff
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/dysnomia.nix
@@ -0,0 +1,257 @@
+{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)}
+ '';
+ };
+
+ dysnomiaFlags = {
+ enableApacheWebApplication = config.services.httpd.enable;
+ enableAxis2WebService = config.services.tomcat.axis2.enable;
+ enableDockerContainer = config.virtualisation.docker.enable;
+ enableEjabberdDump = config.services.ejabberd.enable;
+ enableMySQLDatabase = config.services.mysql.enable;
+ enablePostgreSQLDatabase = config.services.postgresql.enable;
+ enableTomcatWebApplication = config.services.tomcat.enable;
+ enableMongoDatabase = config.services.mongodb.enable;
+ enableSubversionRepository = config.services.svnserve.enable;
+ enableInfluxDatabase = config.services.influxdb.enable;
+ };
+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 = [];
+ };
+
+ enableLegacyModules = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to enable Dysnomia legacy process and wrapper modules";
+ };
+ };
+ };
+
+ 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: dysnomiaFlags // lib.optionalAttrs (cfg.enableLegacyModules) {
+ enableLegacy = builtins.trace ''
+ WARNING: Dysnomia has been configured to use the legacy 'process' and 'wrapper'
+ modules for compatibility reasons! If you rely on these modules, consider
+ migrating to better alternatives.
+
+ More information: https://raw.githubusercontent.com/svanderburg/dysnomia/f65a9a84827bcc4024d6b16527098b33b02e4054/README-legacy.md
+
+ If you have migrated already or don't rely on these Dysnomia modules, you can
+ disable legacy mode with the following NixOS configuration option:
+
+ dysnomia.enableLegacyModules = false;
+
+ In a future version of Dysnomia (and NixOS) the legacy option will go away!
+ '' true;
+ });
+
+ dysnomia.properties = {
+ hostname = config.networking.hostName;
+ inherit (config.nixpkgs.localSystem) system;
+
+ supportedTypes = [
+ "echo"
+ "fileset"
+ "process"
+ "wrapper"
+
+ # These are not base modules, but they are still enabled because they work with technology that are always enabled in NixOS
+ "systemd-unit"
+ "sysvinit-script"
+ "nixos-configuration"
+ ]
+ ++ optional (dysnomiaFlags.enableApacheWebApplication) "apache-webapplication"
+ ++ optional (dysnomiaFlags.enableAxis2WebService) "axis2-webservice"
+ ++ optional (dysnomiaFlags.enableDockerContainer) "docker-container"
+ ++ optional (dysnomiaFlags.enableEjabberdDump) "ejabberd-dump"
+ ++ optional (dysnomiaFlags.enableInfluxDatabase) "influx-database"
+ ++ optional (dysnomiaFlags.enableMySQLDatabase) "mysql-database"
+ ++ optional (dysnomiaFlags.enablePostgreSQLDatabase) "postgresql-database"
+ ++ optional (dysnomiaFlags.enableTomcatWebApplication) "tomcat-webapplication"
+ ++ optional (dysnomiaFlags.enableMongoDatabase) "mongo-database"
+ ++ optional (dysnomiaFlags.enableSubversionRepository) "subversion-repository";
+ };
+
+ dysnomia.containers = lib.recursiveUpdate ({
+ process = {};
+ wrapper = {};
+ }
+ // lib.optionalAttrs (config.services.httpd.enable) { apache-webapplication = {
+ documentRoot = config.services.httpd.virtualHosts.localhost.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;
+ mysqlSocket = "/run/mysqld/mysqld.sock";
+ } // lib.optionalAttrs cfg.enableAuthentication {
+ mysqlUsername = "root";
+ };
+ }
+ // 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.influxdb.enable) {
+ influx-database = {
+ influxdbUsername = config.services.influxdb.user;
+ influxdbDataDir = "${config.services.influxdb.dataDir}/data";
+ influxdbMetaDir = "${config.services.influxdb.dataDir}/meta";
+ };
+ }
+ // 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/infra/libkookie/nixpkgs/nixos/modules/services/misc/errbot.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/errbot.nix
new file mode 100644
index 000000000000..b447ba5d438d
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/errbot.nix
@@ -0,0 +1,104 @@
+{ 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";
+ isSystemUser = true;
+ };
+ users.groups.errbot = {};
+
+ systemd.services = mapAttrs' (name: instanceCfg: nameValuePair "errbot-${name}" (
+ let
+ dataDir = if instanceCfg.dataDir != null 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/infra/libkookie/nixpkgs/nixos/modules/services/misc/etcd.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/etcd.nix
new file mode 100644
index 000000000000..32360d43768a
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/etcd.nix
@@ -0,0 +1,195 @@
+{ 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/etcd";
+ User = "etcd";
+ LimitNOFILE = 40000;
+ };
+ };
+
+ environment.systemPackages = [ pkgs.etcdctl ];
+
+ users.users.etcd = {
+ uid = config.ids.uids.etcd;
+ description = "Etcd daemon user";
+ home = cfg.dataDir;
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/ethminer.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/ethminer.nix
new file mode 100644
index 000000000000..95afb0460fb8
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/ethminer.nix
@@ -0,0 +1,117 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.ethminer;
+ poolUrl = escapeShellArg "stratum1+tcp://${cfg.wallet}@${cfg.pool}:${toString cfg.stratumPort}/${cfg.rig}/${cfg.registerMail}";
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.ethminer = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable ethminer ether mining.";
+ };
+
+ recheckInterval = mkOption {
+ type = types.int;
+ default = 2000;
+ description = "Interval in milliseconds between farm rechecks.";
+ };
+
+ toolkit = mkOption {
+ type = types.enum [ "cuda" "opencl" ];
+ default = "cuda";
+ description = "Cuda or opencl toolkit.";
+ };
+
+ apiPort = mkOption {
+ type = types.int;
+ default = -3333;
+ description = "Ethminer api port. minus sign puts api in read-only mode.";
+ };
+
+ wallet = mkOption {
+ type = types.str;
+ example = "0x0123456789abcdef0123456789abcdef01234567";
+ description = "Ethereum wallet address.";
+ };
+
+ pool = mkOption {
+ type = types.str;
+ example = "eth-us-east1.nanopool.org";
+ description = "Mining pool address.";
+ };
+
+ stratumPort = mkOption {
+ type = types.port;
+ default = 9999;
+ description = "Stratum protocol tcp port.";
+ };
+
+ rig = mkOption {
+ type = types.str;
+ default = "mining-rig-name";
+ description = "Mining rig name.";
+ };
+
+ registerMail = mkOption {
+ type = types.str;
+ example = "email%40example.org";
+ description = "Url encoded email address to register with pool.";
+ };
+
+ maxPower = mkOption {
+ type = types.int;
+ default = 113;
+ description = "Miner max watt usage.";
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.ethminer = {
+ path = [ pkgs.cudatoolkit ];
+ description = "ethminer ethereum mining service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ DynamicUser = true;
+ ExecStartPre = "${pkgs.ethminer}/bin/.ethminer-wrapped --list-devices";
+ ExecStartPost = optional (cfg.toolkit == "cuda") "+${getBin config.boot.kernelPackages.nvidia_x11}/bin/nvidia-smi -pl ${toString cfg.maxPower}";
+ Restart = "always";
+ };
+
+ environment = {
+ LD_LIBRARY_PATH = "${config.boot.kernelPackages.nvidia_x11}/lib";
+ };
+
+ script = ''
+ ${pkgs.ethminer}/bin/.ethminer-wrapped \
+ --farm-recheck ${toString cfg.recheckInterval} \
+ --report-hashrate \
+ --${cfg.toolkit} \
+ --api-port ${toString cfg.apiPort} \
+ --pool ${poolUrl}
+ '';
+
+ };
+
+ };
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/exhibitor.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/exhibitor.nix
new file mode 100644
index 000000000000..f8c79f892da3
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/exhibitor.nix
@@ -0,0 +1,419 @@
+{ 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.str;
+ 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.zookeeper = {
+ uid = config.ids.uids.zookeeper;
+ description = "Zookeeper daemon user";
+ home = cfg.baseDir;
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/felix.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/felix.nix
new file mode 100644
index 000000000000..21740c8c0b72
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/felix.nix
@@ -0,0 +1,102 @@
+# Felix server
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.felix;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.felix = {
+
+ enable = mkEnableOption "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.osgi.gid = config.ids.gids.osgi;
+
+ users.users.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/infra/libkookie/nixpkgs/nixos/modules/services/misc/freeswitch.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/freeswitch.nix
new file mode 100644
index 000000000000..b42f36e86637
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/freeswitch.nix
@@ -0,0 +1,105 @@
+{ config, lib, pkgs, ...}:
+with lib;
+let
+ cfg = config.services.freeswitch;
+ pkg = cfg.package;
+ configDirectory = pkgs.runCommand "freeswitch-config-d" { } ''
+ mkdir -p $out
+ cp -rT ${cfg.configTemplate} $out
+ chmod -R +w $out
+ ${concatStringsSep "\n" (mapAttrsToList (fileName: filePath: ''
+ mkdir -p $out/$(dirname ${fileName})
+ cp ${filePath} $out/${fileName}
+ '') cfg.configDir)}
+ '';
+ configPath = if cfg.enableReload
+ then "/etc/freeswitch"
+ else configDirectory;
+in {
+ options = {
+ services.freeswitch = {
+ enable = mkEnableOption "FreeSWITCH";
+ enableReload = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Issue the <literal>reloadxml</literal> command to FreeSWITCH when configuration directory changes (instead of restart).
+ See <link xlink:href="https://freeswitch.org/confluence/display/FREESWITCH/Reloading">FreeSWITCH documentation</link> for more info.
+ The configuration directory is exposed at <filename>/etc/freeswitch</filename>.
+ See also <literal>systemd.services.*.restartIfChanged</literal>.
+ '';
+ };
+ configTemplate = mkOption {
+ type = types.path;
+ default = "${config.services.freeswitch.package}/share/freeswitch/conf/vanilla";
+ defaultText = literalExample "\${config.services.freeswitch.package}/share/freeswitch/conf/vanilla";
+ example = literalExample "\${config.services.freeswitch.package}/share/freeswitch/conf/minimal";
+ description = ''
+ Configuration template to use.
+ See available templates in <link xlink:href="https://github.com/signalwire/freeswitch/tree/master/conf">FreeSWITCH repository</link>.
+ You can also set your own configuration directory.
+ '';
+ };
+ configDir = mkOption {
+ type = with types; attrsOf path;
+ default = { };
+ example = literalExample ''
+ {
+ "freeswitch.xml" = ./freeswitch.xml;
+ "dialplan/default.xml" = pkgs.writeText "dialplan-default.xml" '''
+ [xml lines]
+ ''';
+ }
+ '';
+ description = ''
+ Override file in FreeSWITCH config template directory.
+ Each top-level attribute denotes a file path in the configuration directory, its value is the file path.
+ See <link xlink:href="https://freeswitch.org/confluence/display/FREESWITCH/Default+Configuration">FreeSWITCH documentation</link> for more info.
+ Also check available templates in <link xlink:href="https://github.com/signalwire/freeswitch/tree/master/conf">FreeSWITCH repository</link>.
+ '';
+ };
+ package = mkOption {
+ type = types.package;
+ default = pkgs.freeswitch;
+ defaultText = literalExample "pkgs.freeswitch";
+ example = literalExample "pkgs.freeswitch";
+ description = ''
+ FreeSWITCH package.
+ '';
+ };
+ };
+ };
+ config = mkIf cfg.enable {
+ environment.etc.freeswitch = mkIf cfg.enableReload {
+ source = configDirectory;
+ };
+ systemd.services.freeswitch-config-reload = mkIf cfg.enableReload {
+ before = [ "freeswitch.service" ];
+ wantedBy = [ "multi-user.target" ];
+ restartTriggers = [ configDirectory ];
+ serviceConfig = {
+ ExecStart = "/run/current-system/systemd/bin/systemctl try-reload-or-restart freeswitch.service";
+ RemainAfterExit = true;
+ Type = "oneshot";
+ };
+ };
+ systemd.services.freeswitch = {
+ description = "Free and open-source application server for real-time communication";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ DynamicUser = true;
+ StateDirectory = "freeswitch";
+ ExecStart = "${pkg}/bin/freeswitch -nf \\
+ -mod ${pkg}/lib/freeswitch/mod \\
+ -conf ${configPath} \\
+ -base /var/lib/freeswitch";
+ ExecReload = "${pkg}/bin/fs_cli -x reloadxml";
+ Restart = "on-failure";
+ RestartSec = "5s";
+ CPUSchedulingPolicy = "fifo";
+ };
+ };
+ environment.systemPackages = [ pkg ];
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/fstrim.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/fstrim.nix
new file mode 100644
index 000000000000..5258f5acb410
--- /dev/null
+++ b/infra/libkookie/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.str;
+ 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.util-linux ];
+
+ systemd.timers.fstrim = {
+ timerConfig = {
+ OnCalendar = cfg.interval;
+ };
+ wantedBy = [ "timers.target" ];
+ };
+
+ };
+
+ meta.maintainers = with maintainers; [ gnidorah ];
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/gammu-smsd.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/gammu-smsd.nix
new file mode 100644
index 000000000000..552725f1384d
--- /dev/null
+++ b/infra/libkookie/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.nullOr 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/infra/libkookie/nixpkgs/nixos/modules/services/misc/geoip-updater.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/geoip-updater.nix
new file mode 100644
index 000000000000..baf0a8d73d19
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/gitea.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/gitea.nix
new file mode 100644
index 000000000000..af80e99746be
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/gitea.nix
@@ -0,0 +1,609 @@
+{ 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";
+ useSqlite = cfg.database.type == "sqlite3";
+ configFile = pkgs.writeText "app.ini" ''
+ APP_NAME = ${cfg.appName}
+ RUN_USER = ${cfg.user}
+ RUN_MODE = prod
+
+ ${generators.toINI {} cfg.settings}
+
+ ${optionalString (cfg.extraConfig != null) 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 = if (cfg.database.createDatabase && usePostgresql) then "/run/postgresql" else if (cfg.database.createDatabase && useMysql) then "/run/mysqld/mysqld.sock" else null;
+ defaultText = "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 database automatically.";
+ };
+ };
+
+ 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>.
+ '';
+ };
+
+ backupDir = mkOption {
+ type = types.str;
+ default = "${cfg.stateDir}/dump";
+ description = "Path to the dump files.";
+ };
+ };
+
+ ssh = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Enable external SSH feature.";
+ };
+
+ clonePort = mkOption {
+ type = types.int;
+ default = 22;
+ example = 2222;
+ description = ''
+ SSH port displayed in clone URL.
+ The option is required to configure a service when the external visible port
+ differs from the local listening port i.e. if port forwarding is used.
+ '';
+ };
+ };
+
+ lfs = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enables git-lfs support.";
+ };
+
+ contentDir = mkOption {
+ type = types.str;
+ default = "${cfg.stateDir}/data/lfs";
+ description = "Where to store LFS files.";
+ };
+ };
+
+ 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.";
+ };
+
+ enableUnixSocket = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Configure Gitea to listen on a unix socket instead of the default TCP 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.";
+ };
+
+ mailerPasswordFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "/var/lib/secrets/gitea/mailpw";
+ description = "Path to a file containing the SMTP password.";
+ };
+
+ 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.
+ '';
+ };
+
+ settings = mkOption {
+ type = with types; attrsOf (attrsOf (oneOf [ bool int str ]));
+ default = {};
+ description = ''
+ Gitea configuration. Refer to <link xlink:href="https://docs.gitea.io/en-us/config-cheat-sheet/"/>
+ for details on supported values.
+ '';
+ example = literalExample ''
+ {
+ "cron.sync_external_users" = {
+ RUN_AT_START = true;
+ SCHEDULE = "@every 24h";
+ UPDATE_EXISTING = true;
+ };
+ mailer = {
+ ENABLED = true;
+ MAILER_TYPE = "sendmail";
+ FROM = "do-not-reply@example.org";
+ SENDMAIL_PATH = "${pkgs.system-sendmail}/bin/sendmail";
+ };
+ other = {
+ SHOW_FOOTER_VERSION = false;
+ };
+ }
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = "Configuration lines appended to the generated gitea configuration file.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [
+ { assertion = cfg.database.createDatabase -> cfg.database.user == cfg.user;
+ message = "services.gitea.database.user must match services.gitea.user if the database is to be automatically provisioned";
+ }
+ ];
+
+ services.gitea.settings = {
+ database = mkMerge [
+ {
+ DB_TYPE = cfg.database.type;
+ }
+ (mkIf (useMysql || usePostgresql) {
+ 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#";
+ })
+ (mkIf useSqlite {
+ PATH = cfg.database.path;
+ })
+ (mkIf usePostgresql {
+ SSL_MODE = "disable";
+ })
+ ];
+
+ repository = {
+ ROOT = cfg.repositoryRoot;
+ };
+
+ server = mkMerge [
+ {
+ DOMAIN = cfg.domain;
+ STATIC_ROOT_PATH = cfg.staticRootPath;
+ LFS_JWT_SECRET = "#jwtsecret#";
+ ROOT_URL = cfg.rootUrl;
+ }
+ (mkIf cfg.enableUnixSocket {
+ PROTOCOL = "unix";
+ HTTP_ADDR = "/run/gitea/gitea.sock";
+ })
+ (mkIf (!cfg.enableUnixSocket) {
+ HTTP_ADDR = cfg.httpAddress;
+ HTTP_PORT = cfg.httpPort;
+ })
+ (mkIf cfg.ssh.enable {
+ DISABLE_SSH = false;
+ SSH_PORT = cfg.ssh.clonePort;
+ })
+ (mkIf (!cfg.ssh.enable) {
+ DISABLE_SSH = true;
+ })
+ (mkIf cfg.lfs.enable {
+ LFS_START_SERVER = true;
+ LFS_CONTENT_PATH = cfg.lfs.contentDir;
+ })
+
+ ];
+
+ session = {
+ COOKIE_NAME = "session";
+ COOKIE_SECURE = cfg.cookieSecure;
+ };
+
+ security = {
+ SECRET_KEY = "#secretkey#";
+ INSTALL_LOCK = true;
+ };
+
+ log = {
+ ROOT_PATH = cfg.log.rootPath;
+ LEVEL = cfg.log.level;
+ };
+
+ service = {
+ DISABLE_REGISTRATION = cfg.disableRegistration;
+ };
+
+ mailer = mkIf (cfg.mailerPasswordFile != null) {
+ PASSWD = "#mailerpass#";
+ };
+ };
+
+ services.postgresql = optionalAttrs (usePostgresql && cfg.database.createDatabase) {
+ enable = mkDefault true;
+
+ ensureDatabases = [ cfg.database.name ];
+ ensureUsers = [
+ { name = cfg.database.user;
+ ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+ }
+ ];
+ };
+
+ services.mysql = optionalAttrs (useMysql && cfg.database.createDatabase) {
+ enable = mkDefault true;
+ package = mkDefault pkgs.mariadb;
+
+ ensureDatabases = [ cfg.database.name ];
+ ensureUsers = [
+ { name = cfg.database.user;
+ ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+ }
+ ];
+ };
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dump.backupDir}' 0750 ${cfg.user} gitea - -"
+ "z '${cfg.dump.backupDir}' 0750 ${cfg.user} gitea - -"
+ "Z '${cfg.dump.backupDir}' - ${cfg.user} gitea - -"
+ "d '${cfg.lfs.contentDir}' 0750 ${cfg.user} gitea - -"
+ "z '${cfg.lfs.contentDir}' 0750 ${cfg.user} gitea - -"
+ "Z '${cfg.lfs.contentDir}' - ${cfg.user} gitea - -"
+ "d '${cfg.repositoryRoot}' 0750 ${cfg.user} gitea - -"
+ "z '${cfg.repositoryRoot}' 0750 ${cfg.user} gitea - -"
+ "Z '${cfg.repositoryRoot}' - ${cfg.user} gitea - -"
+ "d '${cfg.stateDir}' 0750 ${cfg.user} gitea - -"
+ "d '${cfg.stateDir}/conf' 0750 ${cfg.user} gitea - -"
+ "d '${cfg.stateDir}/custom' 0750 ${cfg.user} gitea - -"
+ "d '${cfg.stateDir}/custom/conf' 0750 ${cfg.user} gitea - -"
+ "d '${cfg.stateDir}/log' 0750 ${cfg.user} gitea - -"
+ "z '${cfg.stateDir}' 0750 ${cfg.user} gitea - -"
+ "z '${cfg.stateDir}/.ssh' 0700 ${cfg.user} gitea - -"
+ "z '${cfg.stateDir}/conf' 0750 ${cfg.user} gitea - -"
+ "z '${cfg.stateDir}/custom' 0750 ${cfg.user} gitea - -"
+ "z '${cfg.stateDir}/custom/conf' 0750 ${cfg.user} gitea - -"
+ "z '${cfg.stateDir}/log' 0750 ${cfg.user} gitea - -"
+ "Z '${cfg.stateDir}' - ${cfg.user} gitea - -"
+
+ # If we have a folder or symlink with gitea locales, remove it
+ # And symlink the current gitea locales in place
+ "L+ '${cfg.stateDir}/conf/locale' - - - - ${gitea.out}/locale"
+ ];
+
+ systemd.services.gitea = {
+ description = "gitea";
+ after = [ "network.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
+ wantedBy = [ "multi-user.target" ];
+ path = [ gitea pkgs.gitAndTools.git ];
+
+ preStart = let
+ runConfig = "${cfg.stateDir}/custom/conf/app.ini";
+ secretKey = "${cfg.stateDir}/custom/conf/secret_key";
+ jwtSecret = "${cfg.stateDir}/custom/conf/jwt_secret";
+ in ''
+ # copy custom configuration and generate a random secret key if needed
+ ${optionalString (cfg.useWizard == false) ''
+ cp -f ${configFile} ${runConfig}
+
+ if [ ! -e ${secretKey} ]; then
+ ${gitea}/bin/gitea generate secret SECRET_KEY > ${secretKey}
+ fi
+
+ if [ ! -e ${jwtSecret} ]; then
+ ${gitea}/bin/gitea generate secret LFS_JWT_SECRET > ${jwtSecret}
+ fi
+
+ KEY="$(head -n1 ${secretKey})"
+ DBPASS="$(head -n1 ${cfg.database.passwordFile})"
+ JWTSECRET="$(head -n1 ${jwtSecret})"
+ ${if (cfg.mailerPasswordFile == null) then ''
+ MAILERPASSWORD="#mailerpass#"
+ '' else ''
+ MAILERPASSWORD="$(head -n1 ${cfg.mailerPasswordFile} || :)"
+ ''}
+ sed -e "s,#secretkey#,$KEY,g" \
+ -e "s,#dbpass#,$DBPASS,g" \
+ -e "s,#jwtsecret#,$JWTSECRET,g" \
+ -e "s,#mailerpass#,$MAILERPASSWORD,g" \
+ -i ${runConfig}
+ chmod 640 ${runConfig} ${secretKey} ${jwtSecret}
+ ''}
+
+ # 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/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
+
+ # 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/gitea,g' ${cfg.stateDir}/.ssh/authorized_keys
+ fi
+ '';
+
+ serviceConfig = {
+ Type = "simple";
+ User = cfg.user;
+ Group = "gitea";
+ WorkingDirectory = cfg.stateDir;
+ ExecStart = "${gitea}/bin/gitea web --pid /run/gitea/gitea.pid";
+ Restart = "always";
+ # Runtime directory and mode
+ RuntimeDirectory = "gitea";
+ RuntimeDirectoryMode = "0755";
+ # Access write directories
+ ReadWritePaths = [ cfg.dump.backupDir cfg.repositoryRoot cfg.stateDir cfg.lfs.contentDir ];
+ UMask = "0027";
+ # Capabilities
+ CapabilityBoundingSet = "";
+ # Security
+ NoNewPrivileges = true;
+ # Sandboxing
+ ProtectSystem = "strict";
+ ProtectHome = true;
+ PrivateTmp = true;
+ PrivateDevices = true;
+ PrivateUsers = true;
+ ProtectHostname = true;
+ ProtectClock = true;
+ ProtectKernelTunables = true;
+ ProtectKernelModules = true;
+ ProtectKernelLogs = true;
+ ProtectControlGroups = true;
+ RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ];
+ LockPersonality = true;
+ MemoryDenyWriteExecute = true;
+ RestrictRealtime = true;
+ RestrictSUIDSGID = true;
+ PrivateMounts = true;
+ # System Call Filtering
+ SystemCallArchitectures = "native";
+ SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @memlock @module @mount @obsolete @raw-io @reboot @resources @setuid @swap";
+ };
+
+ environment = {
+ USER = cfg.user;
+ HOME = cfg.stateDir;
+ GITEA_WORK_DIR = cfg.stateDir;
+ };
+ };
+
+ users.users = mkIf (cfg.user == "gitea") {
+ gitea = {
+ description = "Gitea Service";
+ home = cfg.stateDir;
+ useDefaultShell = true;
+ group = "gitea";
+ isSystemUser = true;
+ };
+ };
+
+ users.groups.gitea = {};
+
+ warnings =
+ optional (cfg.database.password != "") ''
+ config.services.gitea.database.password will be stored as plaintext in the Nix store. Use database.passwordFile instead.'' ++
+ optional (cfg.extraConfig != null) ''
+ services.gitea.`extraConfig` is deprecated, please use services.gitea.`settings`.
+ '';
+
+ # 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 ];
+
+ environment = {
+ USER = cfg.user;
+ HOME = cfg.stateDir;
+ GITEA_WORK_DIR = cfg.stateDir;
+ };
+
+ serviceConfig = {
+ Type = "oneshot";
+ User = cfg.user;
+ ExecStart = "${gitea}/bin/gitea dump";
+ WorkingDirectory = cfg.dump.backupDir;
+ };
+ };
+
+ 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;
+ };
+ };
+ meta.maintainers = with lib.maintainers; [ srhb ];
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/gitit.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/gitit.nix
new file mode 100644
index 000000000000..1ec030549f98
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/gitlab.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/gitlab.nix
new file mode 100644
index 000000000000..3ee7a81dc375
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/gitlab.nix
@@ -0,0 +1,967 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+
+let
+ cfg = config.services.gitlab;
+
+ ruby = cfg.packages.gitlab.ruby;
+
+ postgresqlPackage = if config.services.postgresql.enable then
+ config.services.postgresql.package
+ else
+ pkgs.postgresql;
+
+ gitlabSocket = "${cfg.statePath}/tmp/sockets/gitlab.socket";
+ gitalySocket = "${cfg.statePath}/tmp/sockets/gitaly.socket";
+ pathUrlQuote = url: replaceStrings ["/"] ["%2F"] url;
+
+ databaseConfig = {
+ production = {
+ adapter = "postgresql";
+ database = cfg.databaseName;
+ host = cfg.databaseHost;
+ username = cfg.databaseUsername;
+ encoding = "utf8";
+ pool = cfg.databasePool;
+ } // cfg.extraDatabaseConfig;
+ };
+
+ # We only want to create a database if we're actually going to connect to it.
+ databaseActuallyCreateLocally = cfg.databaseCreateLocally && cfg.databaseHost == "";
+
+ 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}"
+
+ [gitlab]
+ secret_file = "${cfg.statePath}/gitlab_shell_secret"
+ url = "http+unix://${pathUrlQuote gitlabSocket}"
+
+ [gitlab.http-settings]
+ self_signed_cert = false
+
+ ${concatStringsSep "\n" (attrValues (mapAttrs (k: v: ''
+ [[storage]]
+ name = "${lib.escape ["\""] k}"
+ path = "${lib.escape ["\""] v.path}"
+ '') gitlabConfig.production.repositories.storages))}
+ '';
+
+ gitlabShellConfig = flip recursiveUpdate cfg.extraShellConfig {
+ user = cfg.user;
+ gitlab_url = "http+unix://${pathUrlQuote gitlabSocket}";
+ http_settings.self_signed_cert = false;
+ repos_path = "${cfg.statePath}/repositories";
+ secret_file = "${cfg.statePath}/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 = cfg.redisUrl;
+
+ pagesArgs = [
+ "-pages-domain" gitlabConfig.production.pages.host
+ "-pages-root" "${gitlabConfig.production.shared.path}/pages"
+ ] ++ cfg.pagesExtraArgs;
+
+ 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}/gitlab_shell_secret";
+ upload_pack = true;
+ receive_pack = true;
+ };
+ workhorse.secret_file = "${cfg.statePath}/.gitlab_workhorse_secret";
+ gitlab_kas.secret_file = "${cfg.statePath}/.gitlab_kas_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/";
+ SCHEMA = "${cfg.statePath}/db/structure.sql";
+ GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads";
+ GITLAB_LOG_PATH = "${cfg.statePath}/log";
+ GITLAB_REDIS_CONFIG_FILE = pkgs.writeText "redis.yml" (builtins.toJSON redisConfig);
+ prometheus_multiproc_dir = "/run/gitlab";
+ RAILS_ENV = "production";
+ };
+
+ gitlab-rake = pkgs.stdenv.mkDerivation {
+ name = "gitlab-rake";
+ buildInputs = [ pkgs.makeWrapper ];
+ dontBuild = true;
+ dontUnpack = true;
+ 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 postgresqlPackage 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 {
+ name = "gitlab-rails";
+ buildInputs = [ pkgs.makeWrapper ];
+ dontBuild = true;
+ dontUnpack = true;
+ 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 postgresqlPackage 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.passwordFile != null) ''password: "@smtpPassword@",''}
+ domain: "${cfg.smtp.domain}",
+ ${optionalString (cfg.smtp.authentication != null) "authentication: :${cfg.smtp.authentication},"}
+ enable_starttls_auto: ${boolToString cfg.smtp.enableStartTLSAuto},
+ ca_file: "/etc/ssl/certs/ca-certificates.crt",
+ openssl_verify_mode: '${cfg.smtp.opensslVerifyMode}'
+ }
+ end
+ '';
+
+in {
+
+ imports = [
+ (mkRenamedOptionModule [ "services" "gitlab" "stateDir" ] [ "services" "gitlab" "statePath" ])
+ (mkRemovedOptionModule [ "services" "gitlab" "satelliteDir" ] "")
+ ];
+
+ 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";
+ };
+
+ packages.pages = mkOption {
+ type = types.package;
+ default = pkgs.gitlab-pages;
+ defaultText = "pkgs.gitlab-pages";
+ description = "Reference to the gitlab-pages package";
+ };
+
+ statePath = mkOption {
+ type = types.str;
+ default = "/var/gitlab/state";
+ description = ''
+ Gitlab state directory. Configuration, repositories and
+ logs, among other things, are stored here.
+
+ The directory will be created automatically if it doesn't
+ exist already. Its parent directories must be owned by
+ either <literal>root</literal> or the user set in
+ <option>services.gitlab.user</option>.
+ '';
+ };
+
+ backupPath = mkOption {
+ type = types.str;
+ default = cfg.statePath + "/backup";
+ description = "Gitlab path for backups.";
+ };
+
+ databaseHost = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Gitlab database hostname. An empty string means <quote>use
+ local unix socket connection</quote>.
+ '';
+ };
+
+ databasePasswordFile = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ description = ''
+ File containing the Gitlab database user password.
+
+ This should be a string, not a nix path, since nix paths are
+ copied into the world-readable nix store.
+ '';
+ };
+
+ databaseCreateLocally = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether a database should be automatically created on the
+ local host. Set this to <literal>false</literal> if you plan
+ on provisioning a local database yourself. This has no effect
+ if <option>services.gitlab.databaseHost</option> is customized.
+ '';
+ };
+
+ 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.";
+ };
+
+ redisUrl = mkOption {
+ type = types.str;
+ default = "redis://localhost:6379/";
+ description = "Redis URL for all GitLab services except gitlab-shell";
+ };
+
+ 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.
+ '';
+ };
+
+ initialRootPasswordFile = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ description = ''
+ File containing the initial password of the root account if
+ this is a new install.
+
+ This should be a string, not a nix path, since nix paths are
+ copied into the world-readable nix store.
+ '';
+ };
+
+ 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 = with types; nullOr str;
+ default = null;
+ description = "Username of the SMTP server for Gitlab.";
+ };
+
+ passwordFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ File containing the password of the SMTP server for Gitlab.
+
+ This should be a string, not a nix path, since nix paths
+ are copied into the world-readable nix store.
+ '';
+ };
+
+ domain = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "HELO domain to use for outgoing mail.";
+ };
+
+ authentication = mkOption {
+ type = with types; nullOr 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";
+ };
+ };
+
+ pagesExtraArgs = mkOption {
+ type = types.listOf types.str;
+ default = [ "-listen-proxy" "127.0.0.1:8090" ];
+ description = "Arguments to pass to the gitlab-pages daemon";
+ };
+
+ secrets.secretFile = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ description = ''
+ A file containing the secret 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.
+
+ This should be a string, not a nix path, since nix paths are
+ copied into the world-readable nix store.
+ '';
+ };
+
+ secrets.dbFile = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ description = ''
+ A file containing the secret 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.
+
+ This should be a string, not a nix path, since nix paths are
+ copied into the world-readable nix store.
+ '';
+ };
+
+ secrets.otpFile = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ description = ''
+ A file containing the secret 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.
+
+ This should be a string, not a nix path, since nix paths are
+ copied into the world-readable nix store.
+ '';
+ };
+
+ secrets.jwsFile = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ description = ''
+ A file containing the secret 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
+
+ This should be a string, not a nix path, since nix paths are
+ copied into the world-readable nix store.
+ '';
+ };
+
+ extraShellConfig = mkOption {
+ type = types.attrs;
+ default = {};
+ description = "Extra configuration to merge into shell-config.yml";
+ };
+
+ extraConfig = mkOption {
+ type = types.attrs;
+ default = {};
+ example = literalExample ''
+ {
+ gitlab = {
+ default_projects_features = {
+ builds = false;
+ };
+ };
+ omniauth = {
+ enabled = true;
+ auto_sign_in_with_provider = "openid_connect";
+ allow_single_sign_on = ["openid_connect"];
+ block_auto_created_users = false;
+ providers = [
+ {
+ name = "openid_connect";
+ label = "OpenID Connect";
+ args = {
+ name = "openid_connect";
+ scope = ["openid" "profile"];
+ response_type = "code";
+ issuer = "https://keycloak.example.com/auth/realms/My%20Realm";
+ discovery = true;
+ client_auth_method = "query";
+ uid_field = "preferred_username";
+ client_options = {
+ identifier = "gitlab";
+ secret = { _secret = "/var/keys/gitlab_oidc_secret"; };
+ redirect_uri = "https://git.example.com/users/auth/openid_connect/callback";
+ };
+ };
+ }
+ ];
+ };
+ };
+ '';
+ description = ''
+ Extra options to be added under
+ <literal>production</literal> in
+ <filename>config/gitlab.yml</filename>, as a nix attribute
+ set.
+
+ Options containing secret data should be set to an attribute
+ set containing the attribute <literal>_secret</literal> - a
+ string pointing to a file containing the value the option
+ should be set to. See the example to get a better picture of
+ this: in the resulting
+ <filename>config/gitlab.yml</filename> file, the
+ <literal>production.omniauth.providers[0].args.client_options.secret</literal>
+ key will be set to the contents of the
+ <filename>/var/keys/gitlab_oidc_secret</filename> file.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ {
+ assertion = databaseActuallyCreateLocally -> (cfg.user == cfg.databaseUsername);
+ message = ''For local automatic database provisioning (services.gitlab.databaseCreateLocally == true) with peer authentication (services.gitlab.databaseHost == "") to work services.gitlab.user and services.gitlab.databaseUsername must be identical.'';
+ }
+ {
+ assertion = (cfg.databaseHost != "") -> (cfg.databasePasswordFile != null);
+ message = "When services.gitlab.databaseHost is customized, services.gitlab.databasePasswordFile must be set!";
+ }
+ {
+ assertion = cfg.initialRootPasswordFile != null;
+ message = "services.gitlab.initialRootPasswordFile must be set!";
+ }
+ {
+ assertion = cfg.secrets.secretFile != null;
+ message = "services.gitlab.secrets.secretFile must be set!";
+ }
+ {
+ assertion = cfg.secrets.dbFile != null;
+ message = "services.gitlab.secrets.dbFile must be set!";
+ }
+ {
+ assertion = cfg.secrets.otpFile != null;
+ message = "services.gitlab.secrets.otpFile must be set!";
+ }
+ {
+ assertion = cfg.secrets.jwsFile != null;
+ message = "services.gitlab.secrets.jwsFile must be set!";
+ }
+ ];
+
+ 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 = optionalAttrs databaseActuallyCreateLocally {
+ enable = true;
+ ensureUsers = singleton { name = cfg.databaseUsername; };
+ };
+
+ # The postgresql module doesn't currently support concepts like
+ # objects owners and extensions; for now we tack on what's needed
+ # here.
+ systemd.services.gitlab-postgresql = let pgsql = config.services.postgresql; in mkIf databaseActuallyCreateLocally {
+ after = [ "postgresql.service" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ pgsql.package ];
+ script = ''
+ set -eu
+
+ PSQL="${pkgs.util-linux}/bin/runuser -u ${pgsql.superUser} -- psql --port=${toString pgsql.port}"
+
+ $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${cfg.databaseName}'" | grep -q 1 || $PSQL -tAc 'CREATE DATABASE "${cfg.databaseName}" OWNER "${cfg.databaseUsername}"'
+ current_owner=$($PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${cfg.databaseName}'")
+ if [[ "$current_owner" != "${cfg.databaseUsername}" ]]; then
+ $PSQL -tAc 'ALTER DATABASE "${cfg.databaseName}" OWNER TO "${cfg.databaseUsername}"'
+ if [[ -e "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}" ]]; then
+ echo "Reassigning ownership of database ${cfg.databaseName} to user ${cfg.databaseUsername} failed on last boot. Failing..."
+ exit 1
+ fi
+ touch "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}"
+ $PSQL "${cfg.databaseName}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${cfg.databaseUsername}\""
+ rm "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}"
+ fi
+ $PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm"
+ $PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS btree_gist;"
+ '';
+
+ serviceConfig = {
+ Type = "oneshot";
+ };
+ };
+
+ # Use postfix to send out mails.
+ services.postfix.enable = mkDefault true;
+
+ users.users.${cfg.user} =
+ { group = cfg.group;
+ home = "${cfg.statePath}/home";
+ shell = "${pkgs.bash}/bin/bash";
+ uid = config.ids.uids.gitlab;
+ };
+
+ users.groups.${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} -"
+ "z ${gitlabEnv.HOME}/.ssh/authorized_keys 0600 ${cfg.user} ${cfg.group} -"
+ "d ${cfg.backupPath} 0750 ${cfg.user} ${cfg.group} -"
+ "d ${cfg.statePath} 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}/config/initializers 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 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 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} 0750 ${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} -"
+ "L+ /run/gitlab/config - - - - ${cfg.statePath}/config"
+ "L+ /run/gitlab/log - - - - ${cfg.statePath}/log"
+ "L+ /run/gitlab/tmp - - - - ${cfg.statePath}/tmp"
+ "L+ /run/gitlab/uploads - - - - ${cfg.statePath}/uploads"
+
+ "L+ /run/gitlab/shell-config.yml - - - - ${pkgs.writeText "config.yml" (builtins.toJSON gitlabShellConfig)}"
+
+ "L+ ${cfg.statePath}/config/unicorn.rb - - - - ${./defaultUnicornConfig.rb}"
+ ];
+
+ systemd.services.gitlab-sidekiq = {
+ after = [ "network.target" "redis.service" "gitlab.service" ];
+ wantedBy = [ "multi-user.target" ];
+ environment = gitlabEnv;
+ path = with pkgs; [
+ postgresqlPackage
+ gitAndTools.git
+ ruby
+ openssh
+ nodejs
+ gnupg
+
+ # Needed for GitLab project imports
+ gnutar
+ gzip
+ ];
+ 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";
+ };
+ };
+
+ systemd.services.gitaly = {
+ after = [ "network.target" "gitlab.service" ];
+ bindsTo = [ "gitlab.service" ];
+ 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-pages = mkIf (gitlabConfig.production.pages.enabled or false) {
+ description = "GitLab static pages daemon";
+ after = [ "network.target" "redis.service" "gitlab.service" ]; # gitlab.service creates configs
+ wantedBy = [ "multi-user.target" ];
+
+ path = [ pkgs.unzip ];
+
+ serviceConfig = {
+ Type = "simple";
+ TimeoutSec = "infinity";
+ Restart = "on-failure";
+
+ User = cfg.user;
+ Group = cfg.group;
+
+ ExecStart = "${cfg.packages.pages}/bin/gitlab-pages ${escapeShellArgs pagesArgs}";
+ WorkingDirectory = gitlabEnv.HOME;
+ };
+ };
+
+ systemd.services.gitlab-workhorse = {
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = with pkgs; [
+ exiftool
+ gitAndTools.git
+ gnutar
+ gzip
+ openssh
+ gitlab-workhorse
+ ];
+ serviceConfig = {
+ 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-mailroom = mkIf (gitlabConfig.production.incoming_email.enabled or false) {
+ description = "GitLab incoming mail daemon";
+ after = [ "network.target" "redis.service" "gitlab.service" ]; # gitlab.service creates configs
+ wantedBy = [ "multi-user.target" ];
+ environment = gitlabEnv;
+ serviceConfig = {
+ Type = "simple";
+ TimeoutSec = "infinity";
+ Restart = "on-failure";
+
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart = "${cfg.packages.gitlab.rubyEnv}/bin/bundle exec mail_room -c ${cfg.packages.gitlab}/share/gitlab/config.dist/mail_room.yml";
+ WorkingDirectory = gitlabEnv.HOME;
+ };
+ };
+
+ systemd.services.gitlab = {
+ after = [ "gitlab-workhorse.service" "network.target" "gitlab-postgresql.service" "redis.service" ];
+ requires = [ "gitlab-sidekiq.service" ];
+ wantedBy = [ "multi-user.target" ];
+ environment = gitlabEnv;
+ path = with pkgs; [
+ postgresqlPackage
+ gitAndTools.git
+ openssh
+ nodejs
+ procps
+ gnupg
+ ];
+
+ serviceConfig = {
+ Type = "simple";
+ User = cfg.user;
+ Group = cfg.group;
+ TimeoutSec = "infinity";
+ Restart = "on-failure";
+ WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
+ ExecStartPre = let
+ preStartFullPrivileges = ''
+ shopt -s dotglob nullglob
+ set -eu
+
+ chown --no-dereference '${cfg.user}':'${cfg.group}' '${cfg.statePath}'/*
+ chown --no-dereference '${cfg.user}':'${cfg.group}' '${cfg.statePath}'/config/*
+ '';
+ preStart = ''
+ set -eu
+
+ cp -f ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION
+ rm -rf ${cfg.statePath}/db/*
+ rm -rf ${cfg.statePath}/config/initializers/*
+ rm -f ${cfg.statePath}/lib
+ cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config
+ cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db
+ ln -sf ${extraGitlabRb} ${cfg.statePath}/config/initializers/extra-gitlab.rb
+
+ ${cfg.packages.gitlab-shell}/bin/install
+
+ ${optionalString cfg.smtp.enable ''
+ install -m u=rw ${smtpSettings} ${cfg.statePath}/config/initializers/smtp_settings.rb
+ ${optionalString (cfg.smtp.passwordFile != null) ''
+ smtp_password=$(<'${cfg.smtp.passwordFile}')
+ ${pkgs.replace}/bin/replace-literal -e '@smtpPassword@' "$smtp_password" '${cfg.statePath}/config/initializers/smtp_settings.rb'
+ ''}
+ ''}
+
+ (
+ umask u=rwx,g=,o=
+
+ ${pkgs.openssl}/bin/openssl rand -hex 32 > ${cfg.statePath}/gitlab_shell_secret
+
+ if [[ -h '${cfg.statePath}/config/database.yml' ]]; then
+ rm '${cfg.statePath}/config/database.yml'
+ fi
+
+ ${if cfg.databasePasswordFile != null then ''
+ export db_password="$(<'${cfg.databasePasswordFile}')"
+
+ if [[ -z "$db_password" ]]; then
+ >&2 echo "Database password was an empty string!"
+ exit 1
+ fi
+
+ ${pkgs.jq}/bin/jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \
+ '.production.password = $ENV.db_password' \
+ >'${cfg.statePath}/config/database.yml'
+ ''
+ else ''
+ ${pkgs.jq}/bin/jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \
+ >'${cfg.statePath}/config/database.yml'
+ ''
+ }
+
+ ${utils.genJqSecretsReplacementSnippet
+ gitlabConfig
+ "${cfg.statePath}/config/gitlab.yml"
+ }
+
+ if [[ -h '${cfg.statePath}/config/secrets.yml' ]]; then
+ rm '${cfg.statePath}/config/secrets.yml'
+ fi
+
+ export secret="$(<'${cfg.secrets.secretFile}')"
+ export db="$(<'${cfg.secrets.dbFile}')"
+ export otp="$(<'${cfg.secrets.otpFile}')"
+ export jws="$(<'${cfg.secrets.jwsFile}')"
+ ${pkgs.jq}/bin/jq -n '{production: {secret_key_base: $ENV.secret,
+ otp_key_base: $ENV.otp,
+ db_key_base: $ENV.db,
+ openid_connect_signing_key: $ENV.jws}}' \
+ > '${cfg.statePath}/config/secrets.yml'
+ )
+
+ initial_root_password="$(<'${cfg.initialRootPasswordFile}')"
+ ${gitlab-rake}/bin/gitlab-rake gitlab:db:configure GITLAB_ROOT_PASSWORD="$initial_root_password" \
+ GITLAB_ROOT_EMAIL='${cfg.initialRootEmail}' > /dev/null
+
+ # We remove potentially broken links to old gitlab-shell versions
+ rm -Rf ${cfg.statePath}/repositories/**/*.git/hooks
+
+ ${pkgs.git}/bin/git config --global core.autocrlf "input"
+ '';
+ in [
+ "+${pkgs.writeShellScript "gitlab-pre-start-full-privileges" preStartFullPrivileges}"
+ "${pkgs.writeShellScript "gitlab-pre-start" preStart}"
+ ];
+ ExecStart = "${cfg.packages.gitlab.rubyEnv}/bin/unicorn -c ${cfg.statePath}/config/unicorn.rb -E production";
+ };
+
+ };
+
+ };
+
+ meta.doc = ./gitlab.xml;
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/gitlab.xml b/infra/libkookie/nixpkgs/nixos/modules/services/misc/gitlab.xml
new file mode 100644
index 000000000000..19a3df0a5f66
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/gitlab.xml
@@ -0,0 +1,132 @@
+<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.databasePasswordFile">databasePasswordFile</link> = "/var/keys/gitlab/db_password";
+ <link linkend="opt-services.gitlab.initialRootPasswordFile">initialRootPasswordFile</link> = "/var/keys/gitlab/root_password";
+ <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.dbFile">dbFile</link> = "/var/keys/gitlab/db";
+ <link linkend="opt-services.gitlab.secrets.secretFile">secretFile</link> = "/var/keys/gitlab/secret";
+ <link linkend="opt-services.gitlab.secrets.otpFile">otpFile</link> = "/var/keys/gitlab/otp";
+ <link linkend="opt-services.gitlab.secrets.jwsFile">jwsFile</link> = "/var/keys/gitlab/jws";
+ };
+ <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 &lt;
+ /dev/urandom | head -c 128 &gt; /var/keys/gitlab/db</literal> to
+ generate a new db secret. Make sure the files can be read by, and
+ only by, the user specified by <link
+ linkend="opt-services.gitlab.user">services.gitlab.user</link>. 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>
+ When <literal>icoming_mail.enabled</literal> is set to <literal>true</literal>
+ in <link linkend="opt-services.gitlab.extraConfig">extraConfig</link> an additional
+ service called <literal>gitlab-mailroom</literal> is enabled for fetching incoming mail.
+ </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:
+<screen>
+<prompt>$ </prompt>sudo -u git -H gitlab-rake gitlab:backup:create
+</screen>
+ A list of all availabe rake tasks can be obtained by running:
+<screen>
+<prompt>$ </prompt>sudo -u git -H gitlab-rake -T
+</screen>
+ </para>
+ </section>
+</chapter>
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/gitolite.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/gitolite.nix
new file mode 100644
index 000000000000..59cbdac319c8
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/gitolite.nix
@@ -0,0 +1,232 @@
+{ 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 = ''
+ The gitolite home directory used to store all repositories. If left as the default value
+ this directory will automatically be created before the gitolite server starts, otherwise
+ the sysadmin is responsible for ensuring the directory exists with appropriate ownership
+ and permissions.
+ '';
+ };
+
+ 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;
+ 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;
+
+ environment = {
+ GITOLITE_RC = ".gitolite.rc";
+ GITOLITE_RC_DEFAULT = "${rcDir}/gitolite.rc.default";
+ };
+
+ serviceConfig = mkMerge [
+ (mkIf (cfg.dataDir == "/var/lib/gitolite") {
+ StateDirectory = "gitolite gitolite/.gitolite gitolite/.gitolite/logs";
+ StateDirectoryMode = "0750";
+ })
+ {
+ Type = "oneshot";
+ User = cfg.user;
+ Group = cfg.group;
+ WorkingDirectory = "~";
+ 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
+ ''
+ 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/infra/libkookie/nixpkgs/nixos/modules/services/misc/gitweb.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/gitweb.nix
new file mode 100644
index 000000000000..ca21366b7796
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/gogs.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/gogs.nix
new file mode 100644
index 000000000000..d7233f10c7cb
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/gogs.nix
@@ -0,0 +1,271 @@
+{ 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}
+
+ [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.
+ '';
+ };
+
+ 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 ];
+
+ 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/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/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/gollum.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/gollum.nix
new file mode 100644
index 000000000000..0c9c7548305b
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/gollum.nix
@@ -0,0 +1,118 @@
+{ 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";
+ };
+
+ h1-title = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Use the first h1 as page title";
+ };
+
+ 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;
+ isSystemUser = true;
+ };
+
+ users.groups.gollum = { };
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.stateDir}' - ${config.users.users.gollum.name} ${config.users.groups.gollum.name} - -"
+ ];
+
+ systemd.services.gollum = {
+ description = "Gollum wiki";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.git ];
+
+ preStart = ''
+ # This is safe to be run on an existing repo
+ git init ${cfg.stateDir}
+ '';
+
+ serviceConfig = {
+ User = config.users.users.gollum.name;
+ Group = config.users.groups.gollum.name;
+ ExecStart = ''
+ ${pkgs.gollum}/bin/gollum \
+ --port ${toString cfg.port} \
+ --host ${cfg.address} \
+ --config ${pkgs.writeText "gollum-config.rb" cfg.extraConfig} \
+ --ref ${cfg.branch} \
+ ${optionalString cfg.mathjax "--mathjax"} \
+ ${optionalString cfg.emoji "--emoji"} \
+ ${optionalString cfg.h1-title "--h1-title"} \
+ ${optionalString (cfg.allowUploads != null) "--allow-uploads ${cfg.allowUploads}"} \
+ ${cfg.stateDir}
+ '';
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/gpsd.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/gpsd.nix
new file mode 100644
index 000000000000..f954249942a8
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/gpsd.nix
@@ -0,0 +1,115 @@
+{ 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.gpsd =
+ { inherit uid;
+ description = "gpsd daemon user";
+ home = "/var/empty";
+ };
+
+ users.groups.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/infra/libkookie/nixpkgs/nixos/modules/services/misc/greenclip.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/greenclip.nix
new file mode 100644
index 000000000000..9152a782d7f0
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/greenclip.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.greenclip;
+in {
+
+ options.services.greenclip = {
+ enable = mkEnableOption "Greenclip daemon";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.haskellPackages.greenclip;
+ defaultText = "pkgs.haskellPackages.greenclip";
+ description = "greenclip derivation to use.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.greenclip = {
+ enable = true;
+ description = "greenclip daemon";
+ wantedBy = [ "graphical-session.target" ];
+ after = [ "graphical-session.target" ];
+ serviceConfig.ExecStart = "${cfg.package}/bin/greenclip daemon";
+ };
+
+ environment.systemPackages = [ cfg.package ];
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/headphones.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/headphones.nix
new file mode 100644
index 000000000000..3ee0a4458bd0
--- /dev/null
+++ b/infra/libkookie/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) {
+ ${name} = {
+ uid = config.ids.uids.headphones;
+ group = cfg.group;
+ description = "headphones user";
+ home = cfg.dataDir;
+ createHome = true;
+ };
+ };
+
+ users.groups = optionalAttrs (cfg.group == 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/infra/libkookie/nixpkgs/nixos/modules/services/misc/home-assistant.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/home-assistant.nix
new file mode 100644
index 000000000000..1f2e13f37325
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/home-assistant.nix
@@ -0,0 +1,280 @@
+{ 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
+ # Hack to support custom yaml objects,
+ # i.e. secrets: https://www.home-assistant.io/docs/configuration/secrets/
+ sed -i -e "s/'\!\([a-z_]\+\) \(.*\)'/\!\1 \2/;s/^\!\!/\!/;" $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;
+
+ usedPlatforms = config:
+ if isAttrs config then
+ optional (config ? platform) config.platform
+ ++ concatMap usedPlatforms (attrValues config)
+ else if isList config then
+ concatMap usedPlatforms config
+ else [ ];
+
+ # Given a component "platform", looks up whether it is used in the config
+ # as `platform = "platform";`.
+ #
+ # For example, the component mqtt.sensor is used as follows:
+ # config.sensor = [ {
+ # platform = "mqtt";
+ # ...
+ # } ];
+ useComponentPlatform = component: elem component (usedPlatforms cfg.config);
+
+ # 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;
+ # Migrate to new option types later: https://github.com/NixOS/nixpkgs/pull/75584
+ type = with lib.types; let
+ valueType = nullOr (oneOf [
+ bool
+ int
+ float
+ str
+ (lazyAttrsOf valueType)
+ (listOf valueType)
+ ]) // {
+ description = "Yaml value";
+ emptyValue.value = {};
+ };
+ in valueType;
+ example = literalExample ''
+ {
+ homeassistant = {
+ name = "Home";
+ latitude = "!secret latitude";
+ longitude = "!secret longitude";
+ elevation = "!secret elevation";
+ unit_system = "metric";
+ time_zone = "UTC";
+ };
+ frontend = {
+ themes = "!include_dir_merge_named themes";
+ };
+ 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>.
+ <link xlink:href="https://www.home-assistant.io/docs/configuration/secrets/">Secrets</link>
+ are encoded as strings as shown in the example.
+ '';
+ };
+
+ 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}'";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ User = "hass";
+ Group = "hass";
+ Restart = "on-failure";
+ ProtectSystem = "strict";
+ ReadWritePaths = let
+ cfgPath = [ "config" "homeassistant" "allowlist_external_dirs" ];
+ value = attrByPath cfgPath [] cfg;
+ allowPaths = if isList value then value else singleton value;
+ in [ "${cfg.configDir}" ] ++ allowPaths;
+ KillSignal = "SIGINT";
+ PrivateTmp = true;
+ RemoveIPC = true;
+ AmbientCapabilities = "cap_net_raw,cap_net_admin+eip";
+ };
+ 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";
+ extraGroups = [ "dialout" ];
+ uid = config.ids.uids.hass;
+ };
+
+ users.groups.hass.gid = config.ids.gids.hass;
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/ihaskell.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/ihaskell.nix
new file mode 100644
index 000000000000..684a242d7385
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/ihaskell.nix
@@ -0,0 +1,63 @@
+{ 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 {
+ type = types.bool;
+ 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/infra/libkookie/nixpkgs/nixos/modules/services/misc/irkerd.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/irkerd.nix
new file mode 100644
index 000000000000..993d77ba424c
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/jackett.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/jackett.nix
new file mode 100644
index 000000000000..f2dc6635df93
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/jackett.nix
@@ -0,0 +1,82 @@
+{ 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.";
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.jackett;
+ defaultText = "pkgs.jackett";
+ description = "Jackett package to use.";
+ };
+ };
+ };
+
+ 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 = "${cfg.package}/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/jellyfin.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/jellyfin.nix
new file mode 100644
index 000000000000..6a47dc3628f4
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/jellyfin.nix
@@ -0,0 +1,110 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.jellyfin;
+in
+{
+ options = {
+ services.jellyfin = {
+ enable = mkEnableOption "Jellyfin Media Server";
+
+ user = mkOption {
+ type = types.str;
+ default = "jellyfin";
+ description = "User account under which Jellyfin runs.";
+ };
+
+ package = mkOption {
+ type = types.package;
+ example = literalExample "pkgs.jellyfin";
+ description = ''
+ Jellyfin package to use.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "jellyfin";
+ description = "Group under which jellyfin runs.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.jellyfin = {
+ description = "Jellyfin Media Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = rec {
+ User = cfg.user;
+ Group = cfg.group;
+ StateDirectory = "jellyfin";
+ CacheDirectory = "jellyfin";
+ ExecStart = "${cfg.package}/bin/jellyfin --datadir '/var/lib/${StateDirectory}' --cachedir '/var/cache/${CacheDirectory}'";
+ Restart = "on-failure";
+
+ # Security options:
+
+ NoNewPrivileges = true;
+
+ AmbientCapabilities = "";
+ CapabilityBoundingSet = "";
+
+ # ProtectClock= adds DeviceAllow=char-rtc r
+ DeviceAllow = "";
+
+ LockPersonality = true;
+
+ PrivateTmp = true;
+ PrivateDevices = true;
+ PrivateUsers = true;
+
+ ProtectClock = true;
+ ProtectControlGroups = true;
+ ProtectHostname = true;
+ ProtectKernelLogs = true;
+ ProtectKernelModules = true;
+ ProtectKernelTunables = true;
+
+ RemoveIPC = true;
+
+ RestrictNamespaces = true;
+ # AF_NETLINK needed because Jellyfin monitors the network connection
+ RestrictAddressFamilies = [ "AF_NETLINK" "AF_INET" "AF_INET6" ];
+ RestrictRealtime = true;
+ RestrictSUIDSGID = true;
+
+ SystemCallArchitectures = "native";
+ SystemCallErrorNumber = "EPERM";
+ SystemCallFilter = [
+ "@system-service"
+
+ "~@chown" "~@cpu-emulation" "~@debug" "~@keyring" "~@memlock" "~@module"
+ "~@obsolete" "~@privileged" "~@setuid"
+ ];
+ };
+ };
+
+ services.jellyfin.package = mkDefault (
+ if versionAtLeast config.system.stateVersion "20.09" then pkgs.jellyfin
+ else pkgs.jellyfin_10_5
+ );
+
+ users.users = mkIf (cfg.user == "jellyfin") {
+ jellyfin = {
+ group = cfg.group;
+ isSystemUser = true;
+ };
+ };
+
+ users.groups = mkIf (cfg.group == "jellyfin") {
+ jellyfin = {};
+ };
+
+ };
+
+ meta.maintainers = with lib.maintainers; [ minijackson ];
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/klipper.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/klipper.nix
new file mode 100644
index 000000000000..2f04c011a650
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/klipper.nix
@@ -0,0 +1,59 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ cfg = config.services.klipper;
+ package = pkgs.klipper;
+ format = pkgs.formats.ini { mkKeyValue = generators.mkKeyValueDefault {} ":"; };
+in
+{
+ ##### interface
+ options = {
+ services.klipper = {
+ enable = mkEnableOption "Klipper, the 3D printer firmware";
+
+ octoprintIntegration = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Allows Octoprint to control Klipper.";
+ };
+
+ settings = mkOption {
+ type = format.type;
+ default = { };
+ description = ''
+ Configuration for Klipper. See the <link xlink:href="https://www.klipper3d.org/Overview.html#configuration-and-tuning-guides">documentation</link>
+ for supported values.
+ '';
+ };
+ };
+ };
+
+ ##### implementation
+ config = mkIf cfg.enable {
+ assertions = [{
+ assertion = cfg.octoprintIntegration -> config.services.octoprint.enable;
+ message = "Option klipper.octoprintIntegration requires Octoprint to be enabled on this system. Please enable services.octoprint to use it.";
+ }];
+
+ environment.etc."klipper.cfg".source = format.generate "klipper.cfg" cfg.settings;
+
+ systemd.services.klipper = {
+ description = "Klipper 3D Printer Firmware";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ ExecStart = "${package}/lib/klipper/klippy.py --input-tty=/run/klipper/tty /etc/klipper.cfg";
+ RuntimeDirectory = "klipper";
+ SupplementaryGroups = [ "dialout" ];
+ WorkingDirectory = "${package}/lib";
+ } // (if cfg.octoprintIntegration then {
+ Group = config.services.octoprint.group;
+ User = config.services.octoprint.user;
+ } else {
+ DynamicUser = true;
+ User = "klipper";
+ });
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/leaps.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/leaps.nix
new file mode 100644
index 000000000000..ef89d3e64d0c
--- /dev/null
+++ b/infra/libkookie/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/leaps -path ${toString cfg.path} -address ${cfg.address}:${toString cfg.port}";
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/lidarr.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/lidarr.nix
new file mode 100644
index 000000000000..8ff1adadcf23
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/lidarr.nix
@@ -0,0 +1,89 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.lidarr;
+in
+{
+ options = {
+ services.lidarr = {
+ enable = mkEnableOption "Lidarr";
+
+ dataDir = mkOption {
+ type = types.str;
+ default = "/var/lib/lidarr/.config/Lidarr";
+ description = "The directory where Lidarr stores its data files.";
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.lidarr;
+ defaultText = "pkgs.lidarr";
+ description = "The Lidarr package to use";
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Open ports in the firewall for Lidarr
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "lidarr";
+ description = ''
+ User account under which Lidarr runs.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "lidarr";
+ description = ''
+ Group under which Lidarr runs.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -"
+ ];
+
+ systemd.services.lidarr = {
+ description = "Lidarr";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ Type = "simple";
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart = "${cfg.package}/bin/Lidarr -nobrowser -data='${cfg.dataDir}'";
+ Restart = "on-failure";
+ };
+ };
+
+ networking.firewall = mkIf cfg.openFirewall {
+ allowedTCPPorts = [ 8686 ];
+ };
+
+ users.users = mkIf (cfg.user == "lidarr") {
+ lidarr = {
+ group = cfg.group;
+ home = "/var/lib/lidarr";
+ uid = config.ids.uids.lidarr;
+ };
+ };
+
+ users.groups = mkIf (cfg.group == "lidarr") {
+ lidarr = {
+ gid = config.ids.gids.lidarr;
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/logkeys.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/logkeys.nix
new file mode 100644
index 000000000000..0082db63a06a
--- /dev/null
+++ b/infra/libkookie/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.str;
+ 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/infra/libkookie/nixpkgs/nixos/modules/services/misc/mame.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/mame.nix
new file mode 100644
index 000000000000..c5d5e9e48371
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/mame.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.mame;
+ mame = "mame${lib.optionalString pkgs.stdenv.is64bit "64"}";
+in
+{
+ options = {
+ services.mame = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to setup TUN/TAP Ethernet interface for MAME emulator.
+ '';
+ };
+ user = mkOption {
+ type = types.str;
+ description = ''
+ User from which you run MAME binary.
+ '';
+ };
+ hostAddr = mkOption {
+ type = types.str;
+ description = ''
+ IP address of the host system. Usually an address of the main network
+ adapter or the adapter through which you get an internet connection.
+ '';
+ example = "192.168.31.156";
+ };
+ emuAddr = mkOption {
+ type = types.str;
+ description = ''
+ IP address of the guest system. The same you set inside guest OS under
+ MAME. Should be on the same subnet as <option>services.mame.hostAddr</option>.
+ '';
+ example = "192.168.31.155";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.mame ];
+
+ security.wrappers."${mame}" = {
+ source = "${pkgs.mame}/bin/${mame}";
+ capabilities = "cap_net_admin,cap_net_raw+eip";
+ };
+
+ systemd.services.mame = {
+ description = "MAME TUN/TAP Ethernet interface";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.iproute ];
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ ExecStart = "${pkgs.mame}/bin/taputil.sh -c ${cfg.user} ${cfg.emuAddr} ${cfg.hostAddr} -";
+ ExecStop = "${pkgs.mame}/bin/taputil.sh -d ${cfg.user}";
+ };
+ };
+ };
+
+ meta.maintainers = with lib.maintainers; [ gnidorah ];
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/matrix-appservice-discord.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/matrix-appservice-discord.nix
new file mode 100644
index 000000000000..49c41ff637a8
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/matrix-appservice-discord.nix
@@ -0,0 +1,162 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ dataDir = "/var/lib/matrix-appservice-discord";
+ registrationFile = "${dataDir}/discord-registration.yaml";
+ appDir = "${pkgs.matrix-appservice-discord}/lib/node_modules/matrix-appservice-discord";
+ cfg = config.services.matrix-appservice-discord;
+ # TODO: switch to configGen.json once RFC42 is implemented
+ settingsFile = pkgs.writeText "matrix-appservice-discord-settings.json" (builtins.toJSON cfg.settings);
+
+in {
+ options = {
+ services.matrix-appservice-discord = {
+ enable = mkEnableOption "a bridge between Matrix and Discord";
+
+ settings = mkOption rec {
+ # TODO: switch to types.config.json as prescribed by RFC42 once it's implemented
+ type = types.attrs;
+ apply = recursiveUpdate default;
+ default = {
+ database = {
+ filename = "${dataDir}/discord.db";
+
+ # TODO: remove those old config keys once the following issues are solved:
+ # * https://github.com/Half-Shot/matrix-appservice-discord/issues/490
+ # * https://github.com/Half-Shot/matrix-appservice-discord/issues/498
+ userStorePath = "${dataDir}/user-store.db";
+ roomStorePath = "${dataDir}/room-store.db";
+ };
+
+ # empty values necessary for registration file generation
+ # actual values defined in environmentFile
+ auth = {
+ clientID = "";
+ botToken = "";
+ };
+ };
+ example = literalExample ''
+ {
+ bridge = {
+ domain = "public-domain.tld";
+ homeserverUrl = "http://public-domain.tld:8008";
+ };
+ }
+ '';
+ description = ''
+ <filename>config.yaml</filename> configuration as a Nix attribute set.
+ </para>
+
+ <para>
+ Configuration options should match those described in
+ <link xlink:href="https://github.com/Half-Shot/matrix-appservice-discord/blob/master/config/config.sample.yaml">
+ config.sample.yaml</link>.
+ </para>
+
+ <para>
+ <option>config.bridge.domain</option> and <option>config.bridge.homeserverUrl</option>
+ should be set to match the public host name of the Matrix homeserver for webhooks and avatars to work.
+ </para>
+
+ <para>
+ Secret tokens should be specified using <option>environmentFile</option>
+ instead of this world-readable attribute set.
+ '';
+ };
+
+ environmentFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ File containing environment variables to be passed to the matrix-appservice-discord service,
+ in which secret tokens can be specified securely by defining values for
+ <literal>APPSERVICE_DISCORD_AUTH_CLIENT_I_D</literal> and
+ <literal>APPSERVICE_DISCORD_AUTH_BOT_TOKEN</literal>.
+ '';
+ };
+
+ url = mkOption {
+ type = types.str;
+ default = "http://localhost:${toString cfg.port}";
+ description = ''
+ The URL where the application service is listening for HS requests.
+ '';
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 9005; # from https://github.com/Half-Shot/matrix-appservice-discord/blob/master/package.json#L11
+ description = ''
+ Port number on which the bridge should listen for internal communication with the Matrix homeserver.
+ '';
+ };
+
+ localpart = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ The user_id localpart to assign to the AS.
+ '';
+ };
+
+ serviceDependencies = mkOption {
+ type = with types; listOf str;
+ default = optional config.services.matrix-synapse.enable "matrix-synapse.service";
+ description = ''
+ List of Systemd services to require and wait for when starting the application service,
+ such as the Matrix homeserver if it's running on the same host.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.matrix-appservice-discord = {
+ description = "A bridge between Matrix and Discord.";
+
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
+ after = [ "network-online.target" ] ++ cfg.serviceDependencies;
+
+ preStart = ''
+ if [ ! -f '${registrationFile}' ]; then
+ ${pkgs.matrix-appservice-discord}/bin/matrix-appservice-discord \
+ --generate-registration \
+ --url=${escapeShellArg cfg.url} \
+ ${optionalString (cfg.localpart != null) "--localpart=${escapeShellArg cfg.localpart}"} \
+ --config='${settingsFile}' \
+ --file='${registrationFile}'
+ fi
+ '';
+
+ serviceConfig = {
+ Type = "simple";
+ Restart = "always";
+
+ ProtectSystem = "strict";
+ ProtectHome = true;
+ ProtectKernelTunables = true;
+ ProtectKernelModules = true;
+ ProtectControlGroups = true;
+
+ DynamicUser = true;
+ PrivateTmp = true;
+ WorkingDirectory = appDir;
+ StateDirectory = baseNameOf dataDir;
+ UMask = 0027;
+ EnvironmentFile = cfg.environmentFile;
+
+ ExecStart = ''
+ ${pkgs.matrix-appservice-discord}/bin/matrix-appservice-discord \
+ --file='${registrationFile}' \
+ --config='${settingsFile}' \
+ --port='${toString cfg.port}'
+ '';
+ };
+ };
+ };
+
+ meta.maintainers = with maintainers; [ pacien ];
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/matrix-synapse-log_config.yaml b/infra/libkookie/nixpkgs/nixos/modules/services/misc/matrix-synapse-log_config.yaml
new file mode 100644
index 000000000000..d85bdd1208f9
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/matrix-synapse.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/matrix-synapse.nix
new file mode 100644
index 000000000000..3abb9b7d69c8
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/matrix-synapse.nix
@@ -0,0 +1,737 @@
+{ 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)}]}'';
+ pluginsEnv = cfg.package.python.buildEnv.override {
+ extraLibs = cfg.plugins;
+ };
+ 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"
+${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}
+
+account_threepid_delegates:
+ ${optionalString (cfg.account_threepid_delegates.email != null) "email: ${cfg.account_threepid_delegates.email}"}
+ ${optionalString (cfg.account_threepid_delegates.msisdn != null) "msisdn: ${cfg.account_threepid_delegates.msisdn}"}
+
+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)}
+ }
+ }
+redaction_retention_period: ${toString cfg.redaction_retention_period}
+app_service_config_files: ${builtins.toJSON cfg.app_service_config_files}
+
+${cfg.extraConfig}
+'';
+
+ hasLocalPostgresDB = let args = cfg.database_args; in
+ usePostgresql && (!(args ? host) || (elem args.host [ "localhost" "127.0.0.1" "::1" ]));
+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.
+ '';
+ };
+ plugins = mkOption {
+ type = types.listOf types.package;
+ default = [ ];
+ example = literalExample ''
+ with config.services.matrix-synapse.package.plugins; [
+ matrix-synapse-ldap3
+ matrix-synapse-pam
+ ];
+ '';
+ description = ''
+ List of additional Matrix plugins to make available.
+ '';
+ };
+ 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.
+ '';
+ };
+ 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.
+ '';
+ };
+ 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"
+ "::1/128"
+ "fe80::/64"
+ "fc00::/7"
+ ];
+ 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.
+ '';
+ };
+ account_threepid_delegates.email = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Delegate email sending to https://example.org
+ '';
+ };
+ account_threepid_delegates.msisdn = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Delegate SMS sending to this local process (https://localhost:8090)
+ '';
+ };
+ 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
+ '';
+ };
+ redaction_retention_period = mkOption {
+ type = types.int;
+ default = 7;
+ description = ''
+ How long to keep redacted events in unredacted form in the database.
+ '';
+ };
+ 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 {
+ assertions = [
+ { assertion = hasLocalPostgresDB -> config.services.postgresql.enable;
+ message = ''
+ Cannot deploy matrix-synapse with a configuration for a local postgresql database
+ and a missing postgresql service. Since 20.03 it's mandatory to manually configure the
+ database (please read the thread in https://github.com/NixOS/nixpkgs/pull/80447 for
+ further reference).
+
+ If you
+ - try to deploy a fresh synapse, you need to configure the database yourself. An example
+ for this can be found in <nixpkgs/nixos/tests/matrix-synapse.nix>
+ - update your existing matrix-synapse instance, you simply need to add `services.postgresql.enable = true`
+ to your configuration.
+
+ For further information about this update, please read the release-notes of 20.03 carefully.
+ '';
+ }
+ ];
+
+ users.users.matrix-synapse = {
+ group = "matrix-synapse";
+ home = cfg.dataDir;
+ createHome = true;
+ shell = "${pkgs.bash}/bin/bash";
+ uid = config.ids.uids.matrix-synapse;
+ };
+
+ users.groups.matrix-synapse = {
+ gid = config.ids.gids.matrix-synapse;
+ };
+
+ systemd.services.matrix-synapse = {
+ description = "Synapse Matrix homeserver";
+ after = [ "network.target" ] ++ optional hasLocalPostgresDB "postgresql.service";
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ ${cfg.package}/bin/homeserver \
+ --config-path ${configFile} \
+ --keys-directory ${cfg.dataDir} \
+ --generate-keys
+ '';
+ environment.PYTHONPATH = makeSearchPathOutput "lib" cfg.package.python.sitePackages [ pluginsEnv ];
+ serviceConfig = {
+ Type = "notify";
+ User = "matrix-synapse";
+ Group = "matrix-synapse";
+ WorkingDirectory = cfg.dataDir;
+ ExecStart = ''
+ ${cfg.package}/bin/homeserver \
+ ${ concatMapStringsSep "\n " (x: "--config-path ${x} \\") ([ configFile ] ++ cfg.extraConfigFiles) }
+ --keys-directory ${cfg.dataDir}
+ '';
+ ExecReload = "${pkgs.util-linux}/bin/kill -HUP $MAINPID";
+ Restart = "on-failure";
+ };
+ };
+ };
+
+ imports = [
+ (mkRemovedOptionModule [ "services" "matrix-synapse" "trusted_third_party_id_servers" ] ''
+ The `trusted_third_party_id_servers` option as been removed in `matrix-synapse` v1.4.0
+ as the behavior is now obsolete.
+ '')
+ (mkRemovedOptionModule [ "services" "matrix-synapse" "create_local_database" ] ''
+ Database configuration must be done manually. An exemplary setup is demonstrated in
+ <nixpkgs/nixos/tests/matrix-synapse.nix>
+ '')
+ (mkRemovedOptionModule [ "services" "matrix-synapse" "web_client" ] "")
+ ];
+
+ meta.doc = ./matrix-synapse.xml;
+ meta.maintainers = teams.matrix.members;
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/matrix-synapse.xml b/infra/libkookie/nixpkgs/nixos/modules/services/misc/matrix-synapse.xml
new file mode 100644
index 000000000000..fbfa838b168b
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/matrix-synapse.xml
@@ -0,0 +1,227 @@
+<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-matrix">
+ <title>Matrix</title>
+ <para>
+ <link xlink:href="https://matrix.org/">Matrix</link> is an open standard for
+ interoperable, decentralised, real-time communication over IP. It can be used
+ to power Instant Messaging, VoIP/WebRTC signalling, Internet of Things
+ communication - or anywhere you need a standard HTTP API for publishing and
+ subscribing to data whilst tracking the conversation history.
+ </para>
+ <para>
+ This chapter will show you how to set up your own, self-hosted Matrix
+ homeserver using the Synapse reference homeserver, and how to serve your own
+ copy of the Element web client. See the
+ <link xlink:href="https://matrix.org/docs/projects/try-matrix-now.html">Try
+ Matrix Now!</link> overview page for links to Element Apps for Android and iOS,
+ desktop clients, as well as bridges to other networks and other projects
+ around Matrix.
+ </para>
+ <section xml:id="module-services-matrix-synapse">
+ <title>Synapse Homeserver</title>
+
+ <para>
+ <link xlink:href="https://github.com/matrix-org/synapse">Synapse</link> is
+ the reference homeserver implementation of Matrix from the core development
+ team at matrix.org. The following configuration example will set up a
+ synapse server for the <literal>example.org</literal> domain, served from
+ the host <literal>myhostname.example.org</literal>. For more information,
+ please refer to the
+ <link xlink:href="https://github.com/matrix-org/synapse#synapse-installation">
+ installation instructions of Synapse </link>.
+<programlisting>
+{ pkgs, ... }:
+let
+ fqdn =
+ let
+ join = hostName: domain: hostName + optionalString (domain != null) ".${domain}";
+ in join config.networking.hostName config.networking.domain;
+in {
+ networking = {
+ <link linkend="opt-networking.hostName">hostName</link> = "myhostname";
+ <link linkend="opt-networking.domain">domain</link> = "example.org";
+ };
+ <link linkend="opt-networking.firewall.allowedTCPPorts">networking.firewall.allowedTCPPorts</link> = [ 80 443 ];
+
+ <link linkend="opt-services.postgresql.enable">services.postgresql.enable</link> = true;
+ <link linkend="opt-services.postgresql.initialScript">services.postgresql.initialScript</link> = pkgs.writeText "synapse-init.sql" ''
+ CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
+ CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
+ TEMPLATE template0
+ LC_COLLATE = "C"
+ LC_CTYPE = "C";
+ '';
+
+ services.nginx = {
+ <link linkend="opt-services.nginx.enable">enable</link> = true;
+ # only recommendedProxySettings and recommendedGzipSettings are strictly required,
+ # but the rest make sense as well
+ <link linkend="opt-services.nginx.recommendedTlsSettings">recommendedTlsSettings</link> = true;
+ <link linkend="opt-services.nginx.recommendedOptimisation">recommendedOptimisation</link> = true;
+ <link linkend="opt-services.nginx.recommendedGzipSettings">recommendedGzipSettings</link> = true;
+ <link linkend="opt-services.nginx.recommendedProxySettings">recommendedProxySettings</link> = true;
+
+ <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = {
+ # This host section can be placed on a different host than the rest,
+ # i.e. to delegate from the host being accessible as ${config.networking.domain}
+ # to another host actually running the Matrix homeserver.
+ "${config.networking.domain}" = {
+ <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."= /.well-known/matrix/server".extraConfig</link> =
+ let
+ # use 443 instead of the default 8448 port to unite
+ # the client-server and server-server port for simplicity
+ server = { "m.server" = "${fqdn}:443"; };
+ in ''
+ add_header Content-Type application/json;
+ return 200 '${builtins.toJSON server}';
+ '';
+ <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."= /.well-known/matrix/client".extraConfig</link> =
+ let
+ client = {
+ "m.homeserver" = { "base_url" = "https://${fqdn}"; };
+ "m.identity_server" = { "base_url" = "https://vector.im"; };
+ };
+ # ACAO required to allow element-web on any URL to request this json file
+ in ''
+ add_header Content-Type application/json;
+ add_header Access-Control-Allow-Origin *;
+ return 200 '${builtins.toJSON client}';
+ '';
+ };
+
+ # Reverse proxy for Matrix client-server and server-server communication
+ ${fqdn} = {
+ <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;
+ <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
+
+ # Or do a redirect instead of the 404, or whatever is appropriate for you.
+ # But do not put a Matrix Web client here! See the Element web section below.
+ <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."/".extraConfig</link> = ''
+ return 404;
+ '';
+
+ # forward all Matrix API calls to the synapse Matrix homeserver
+ locations."/_matrix" = {
+ <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.proxyPass">proxyPass</link> = "http://[::1]:8008"; # without a trailing /
+ };
+ };
+ };
+ };
+ services.matrix-synapse = {
+ <link linkend="opt-services.matrix-synapse.enable">enable</link> = true;
+ <link linkend="opt-services.matrix-synapse.server_name">server_name</link> = config.networking.domain;
+ <link linkend="opt-services.matrix-synapse.listeners">listeners</link> = [
+ {
+ <link linkend="opt-services.matrix-synapse.listeners._.port">port</link> = 8008;
+ <link linkend="opt-services.matrix-synapse.listeners._.bind_address">bind_address</link> = "::1";
+ <link linkend="opt-services.matrix-synapse.listeners._.type">type</link> = "http";
+ <link linkend="opt-services.matrix-synapse.listeners._.tls">tls</link> = false;
+ <link linkend="opt-services.matrix-synapse.listeners._.x_forwarded">x_forwarded</link> = true;
+ <link linkend="opt-services.matrix-synapse.listeners._.resources">resources</link> = [
+ {
+ <link linkend="opt-services.matrix-synapse.listeners._.resources._.names">names</link> = [ "client" "federation" ];
+ <link linkend="opt-services.matrix-synapse.listeners._.resources._.compress">compress</link> = false;
+ }
+ ];
+ }
+ ];
+ };
+};
+</programlisting>
+ </para>
+
+ <para>
+ If the <code>A</code> and <code>AAAA</code> DNS records on
+ <literal>example.org</literal> do not point on the same host as the records
+ for <code>myhostname.example.org</code>, you can easily move the
+ <code>/.well-known</code> virtualHost section of the code to the host that
+ is serving <literal>example.org</literal>, while the rest stays on
+ <literal>myhostname.example.org</literal> with no other changes required.
+ This pattern also allows to seamlessly move the homeserver from
+ <literal>myhostname.example.org</literal> to
+ <literal>myotherhost.example.org</literal> by only changing the
+ <code>/.well-known</code> redirection target.
+ </para>
+
+ <para>
+ If you want to run a server with public registration by anybody, you can
+ then enable <literal><link linkend="opt-services.matrix-synapse.enable_registration">services.matrix-synapse.enable_registration</link> =
+ true;</literal>. Otherwise, or you can generate a registration secret with
+ <command>pwgen -s 64 1</command> and set it with
+ <option><link linkend="opt-services.matrix-synapse.registration_shared_secret">services.matrix-synapse.registration_shared_secret</link></option>. To
+ create a new user or admin, run the following after you have set the secret
+ and have rebuilt NixOS:
+<screen>
+<prompt>$ </prompt>nix run nixpkgs.matrix-synapse
+<prompt>$ </prompt>register_new_matrix_user -k <replaceable>your-registration-shared-secret</replaceable> http://localhost:8008
+<prompt>New user localpart: </prompt><replaceable>your-username</replaceable>
+<prompt>Password:</prompt>
+<prompt>Confirm password:</prompt>
+<prompt>Make admin [no]:</prompt>
+Success!
+</screen>
+ In the example, this would create a user with the Matrix Identifier
+ <literal>@your-username:example.org</literal>. Note that the registration
+ secret ends up in the nix store and therefore is world-readable by any user
+ on your machine, so it makes sense to only temporarily activate the
+ <link linkend="opt-services.matrix-synapse.registration_shared_secret">registration_shared_secret</link>
+ option until a better solution for NixOS is in place.
+ </para>
+ </section>
+ <section xml:id="module-services-matrix-element-web">
+ <title>Element (formerly known as Riot) Web Client</title>
+
+ <para>
+ <link xlink:href="https://github.com/vector-im/riot-web/">Element Web</link> is
+ the reference web client for Matrix and developed by the core team at
+ matrix.org. Element was formerly known as Riot.im, see the
+ <link xlink:href="https://element.io/blog/welcome-to-element/">Element introductory blog post</link>
+ for more information. The following snippet can be optionally added to the code before
+ to complete the synapse installation with a web client served at
+ <code>https://element.myhostname.example.org</code> and
+ <code>https://element.example.org</code>. Alternatively, you can use the hosted
+ copy at <link xlink:href="https://app.element.io/">https://app.element.io/</link>,
+ or use other web clients or native client applications. Due to the
+ <literal>/.well-known</literal> urls set up done above, many clients should
+ fill in the required connection details automatically when you enter your
+ Matrix Identifier. See
+ <link xlink:href="https://matrix.org/docs/projects/try-matrix-now.html">Try
+ Matrix Now!</link> for a list of existing clients and their supported
+ featureset.
+<programlisting>
+{
+ services.nginx.virtualHosts."element.${fqdn}" = {
+ <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_.serverAliases">serverAliases</link> = [
+ "element.${config.networking.domain}"
+ ];
+
+ <link linkend="opt-services.nginx.virtualHosts._name_.root">root</link> = pkgs.element-web.override {
+ conf = {
+ default_server_config."m.homeserver" = {
+ "base_url" = "${config.networking.domain}";
+ "server_name" = "${fqdn}";
+ };
+ };
+ };
+ };
+}
+</programlisting>
+ </para>
+
+ <para>
+ Note that the Element developers do not recommend running Element and your Matrix
+ homeserver on the same fully-qualified domain name for security reasons. In
+ the example, this means that you should not reuse the
+ <literal>myhostname.example.org</literal> virtualHost to also serve Element,
+ but instead serve it on a different subdomain, like
+ <literal>element.example.org</literal> in the example. See the
+ <link xlink:href="https://github.com/vector-im/riot-web#important-security-note">Element
+ Important Security Notes</link> for more information on this subject.
+ </para>
+ </section>
+</chapter>
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/mautrix-telegram.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/mautrix-telegram.nix
new file mode 100644
index 000000000000..caeb4b04164f
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/mautrix-telegram.nix
@@ -0,0 +1,166 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ dataDir = "/var/lib/mautrix-telegram";
+ registrationFile = "${dataDir}/telegram-registration.yaml";
+ cfg = config.services.mautrix-telegram;
+ # TODO: switch to configGen.json once RFC42 is implemented
+ settingsFile = pkgs.writeText "mautrix-telegram-settings.json" (builtins.toJSON cfg.settings);
+
+in {
+ options = {
+ services.mautrix-telegram = {
+ enable = mkEnableOption "Mautrix-Telegram, a Matrix-Telegram hybrid puppeting/relaybot bridge";
+
+ settings = mkOption rec {
+ # TODO: switch to types.config.json as prescribed by RFC42 once it's implemented
+ type = types.attrs;
+ apply = recursiveUpdate default;
+ default = {
+ appservice = rec {
+ database = "sqlite:///${dataDir}/mautrix-telegram.db";
+ database_opts = {};
+ hostname = "0.0.0.0";
+ port = 8080;
+ address = "http://localhost:${toString port}";
+ };
+
+ bridge = {
+ permissions."*" = "relaybot";
+ relaybot.whitelist = [ ];
+ double_puppet_server_map = {};
+ login_shared_secret_map = {};
+ };
+
+ logging = {
+ version = 1;
+
+ formatters.precise.format = "[%(levelname)s@%(name)s] %(message)s";
+
+ handlers.console = {
+ class = "logging.StreamHandler";
+ formatter = "precise";
+ };
+
+ loggers = {
+ mau.level = "INFO";
+ telethon.level = "INFO";
+
+ # prevent tokens from leaking in the logs:
+ # https://github.com/tulir/mautrix-telegram/issues/351
+ aiohttp.level = "WARNING";
+ };
+
+ # log to console/systemd instead of file
+ root = {
+ level = "INFO";
+ handlers = [ "console" ];
+ };
+ };
+ };
+ example = literalExample ''
+ {
+ homeserver = {
+ address = "http://localhost:8008";
+ domain = "public-domain.tld";
+ };
+
+ appservice.public = {
+ prefix = "/public";
+ external = "https://public-appservice-address/public";
+ };
+
+ bridge.permissions = {
+ "example.com" = "full";
+ "@admin:example.com" = "admin";
+ };
+ }
+ '';
+ description = ''
+ <filename>config.yaml</filename> configuration as a Nix attribute set.
+ Configuration options should match those described in
+ <link xlink:href="https://github.com/tulir/mautrix-telegram/blob/master/example-config.yaml">
+ example-config.yaml</link>.
+ </para>
+
+ <para>
+ Secret tokens should be specified using <option>environmentFile</option>
+ instead of this world-readable attribute set.
+ '';
+ };
+
+ environmentFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ File containing environment variables to be passed to the mautrix-telegram service,
+ in which secret tokens can be specified securely by defining values for
+ <literal>MAUTRIX_TELEGRAM_APPSERVICE_AS_TOKEN</literal>,
+ <literal>MAUTRIX_TELEGRAM_APPSERVICE_HS_TOKEN</literal>,
+ <literal>MAUTRIX_TELEGRAM_TELEGRAM_API_ID</literal>,
+ <literal>MAUTRIX_TELEGRAM_TELEGRAM_API_HASH</literal> and optionally
+ <literal>MAUTRIX_TELEGRAM_TELEGRAM_BOT_TOKEN</literal>.
+ '';
+ };
+
+ serviceDependencies = mkOption {
+ type = with types; listOf str;
+ default = optional config.services.matrix-synapse.enable "matrix-synapse.service";
+ description = ''
+ List of Systemd services to require and wait for when starting the application service.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.mautrix-telegram = {
+ description = "Mautrix-Telegram, a Matrix-Telegram hybrid puppeting/relaybot bridge.";
+
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
+ after = [ "network-online.target" ] ++ cfg.serviceDependencies;
+
+ preStart = ''
+ # generate the appservice's registration file if absent
+ if [ ! -f '${registrationFile}' ]; then
+ ${pkgs.mautrix-telegram}/bin/mautrix-telegram \
+ --generate-registration \
+ --base-config='${pkgs.mautrix-telegram}/${pkgs.mautrix-telegram.pythonModule.sitePackages}/mautrix_telegram/example-config.yaml' \
+ --config='${settingsFile}' \
+ --registration='${registrationFile}'
+ fi
+
+ # run automatic database init and migration scripts
+ ${pkgs.mautrix-telegram.alembic}/bin/alembic -x config='${settingsFile}' upgrade head
+ '';
+
+ serviceConfig = {
+ Type = "simple";
+ Restart = "always";
+
+ ProtectSystem = "strict";
+ ProtectHome = true;
+ ProtectKernelTunables = true;
+ ProtectKernelModules = true;
+ ProtectControlGroups = true;
+
+ DynamicUser = true;
+ PrivateTmp = true;
+ WorkingDirectory = pkgs.mautrix-telegram; # necessary for the database migration scripts to be found
+ StateDirectory = baseNameOf dataDir;
+ UMask = 0027;
+ EnvironmentFile = cfg.environmentFile;
+
+ ExecStart = ''
+ ${pkgs.mautrix-telegram}/bin/mautrix-telegram \
+ --config='${settingsFile}'
+ '';
+ };
+ };
+ };
+
+ meta.maintainers = with maintainers; [ pacien vskilet ];
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/mbpfan.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/mbpfan.nix
new file mode 100644
index 000000000000..e22d1ed61f99
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/mediatomb.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/mediatomb.nix
new file mode 100644
index 000000000000..a19b73889ce4
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/mediatomb.nix
@@ -0,0 +1,391 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ gid = config.ids.gids.mediatomb;
+ cfg = config.services.mediatomb;
+ name = cfg.package.pname;
+ pkg = cfg.package;
+ optionYesNo = option: if option then "yes" else "no";
+ # configuration on media directory
+ mediaDirectory = {
+ options = {
+ path = mkOption {
+ type = types.str;
+ description = ''
+ Absolute directory path to the media directory to index.
+ '';
+ };
+ recursive = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether the indexation must take place recursively or not.";
+ };
+ hidden-files = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to index the hidden files or not.";
+ };
+ };
+ };
+ toMediaDirectory = d: "<directory location=\"${d.path}\" mode=\"inotify\" recursive=\"${optionYesNo d.recursive}\" hidden-files=\"${optionYesNo d.hidden-files}\" />\n";
+
+ transcodingConfig = if cfg.transcoding then with pkgs; ''
+ <transcoding enabled="yes">
+ <mimetype-profile-mappings>
+ <transcode mimetype="video/x-flv" using="vlcmpeg" />
+ <transcode mimetype="application/ogg" using="vlcmpeg" />
+ <transcode mimetype="audio/ogg" using="ogg2mp3" />
+ <transcode mimetype="audio/x-flac" using="oggflac2raw"/>
+ </mimetype-profile-mappings>
+ <profiles>
+ <profile name="ogg2mp3" enabled="no" type="external">
+ <mimetype>audio/mpeg</mimetype>
+ <accept-url>no</accept-url>
+ <first-resource>yes</first-resource>
+ <accept-ogg-theora>no</accept-ogg-theora>
+ <agent command="${ffmpeg}/bin/ffmpeg" arguments="-y -i %in -f mp3 %out" />
+ <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="${libsForQt5.vlc}/bin/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>
+'' else ''
+ <transcoding enabled="no">
+ </transcoding>
+'';
+
+ configText = optionalString (! cfg.customCfg) ''
+<?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="${name}" password="${name}"/>
+ </accounts>
+ </ui>
+ <name>${cfg.serverName}</name>
+ <udn>uuid:${cfg.uuid}</udn>
+ <home>${cfg.dataDir}</home>
+ <interface>${cfg.interface}</interface>
+ <webroot>${pkg}/share/${name}/web</webroot>
+ <pc-directory upnp-hide="${optionYesNo cfg.pcDirectoryHide}"/>
+ <storage>
+ <sqlite3 enabled="yes">
+ <database-file>${name}.db</database-file>
+ </sqlite3>
+ </storage>
+ <protocolInfo extend="${optionYesNo cfg.ps3Support}"/>
+ ${optionalString cfg.dsmSupport ''
+ <custom-http-headers>
+ <add header="X-User-Agent: redsonic"/>
+ </custom-http-headers>
+
+ <manufacturerURL>redsonic.com</manufacturerURL>
+ <modelNumber>105</modelNumber>
+ ''}
+ ${optionalString cfg.tg100Support ''
+ <upnp-string-limit>101</upnp-string-limit>
+ ''}
+ <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">
+ <autoscan use-inotify="auto">
+ ${concatMapStrings toMediaDirectory cfg.mediaDirectories}
+ </autoscan>
+ <scripting script-charset="UTF-8">
+ <common-script>${pkg}/share/${name}/js/common.js</common-script>
+ <playlist-script>${pkg}/share/${name}/js/playlists.js</playlist-script>
+ <virtual-layout type="builtin">
+ <import-script>${pkg}/share/${name}/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"/>
+ ${optionalString cfg.ps3Support ''
+ <map from="avi" to="video/divx"/>
+ ''}
+ ${optionalString cfg.dsmSupport ''
+ <map from="avi" to="video/avi"/>
+ ''}
+ </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="${name}"/>
+ <standardfeed feed="most_viewed" time-range="today"/>
+ <playlists user="${name}"/>
+ <uploads user="${name}"/>
+ <standardfeed feed="recently_featured" time-range="today"/>
+ </YouTube>
+ </online-content>
+ </import>
+ ${transcodingConfig}
+ </config>
+'';
+ defaultFirewallRules = {
+ # udp 1900 port needs to be opened for SSDP (not configurable within
+ # mediatomb/gerbera) cf.
+ # http://docs.gerbera.io/en/latest/run.html?highlight=udp%20port#network-setup
+ allowedUDPPorts = [ 1900 cfg.port ];
+ allowedTCPPorts = [ cfg.port ];
+ };
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.mediatomb = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the Gerbera/Mediatomb DLNA server.
+ '';
+ };
+
+ serverName = mkOption {
+ type = types.str;
+ default = "Gerbera (Mediatomb)";
+ description = ''
+ How to identify the server on the network.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ example = literalExample "pkgs.mediatomb";
+ default = pkgs.gerbera;
+ description = ''
+ Underlying package to be used with the module (default: pkgs.gerbera).
+ '';
+ };
+
+ 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/${name}";
+ description = ''
+ The directory where Gerbera/Mediatomb stores its state, data, etc.
+ '';
+ };
+
+ pcDirectoryHide = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to list the top-level directory or not (from upnp client standpoint).
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "mediatomb";
+ description = "User account under which ${name} runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "mediatomb";
+ description = "Group account under which ${name} runs.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 49152;
+ description = ''
+ The network port to listen on.
+ '';
+ };
+
+ interface = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ A specific interface to bind to.
+ '';
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If false (the default), this is up to the user to declare the firewall rules.
+ If true, this opens port 1900 (tcp and udp) and the port specified by
+ <option>sercvices.mediatomb.port</option>.
+
+ If the option <option>services.mediatomb.interface</option> is set,
+ the firewall rules opened are dedicated to that interface. Otherwise,
+ those rules are opened globally.
+ '';
+ };
+
+ uuid = mkOption {
+ type = types.str;
+ default = "fdfc8a4e-a3ad-4c1d-b43d-a2eedb03a687";
+ description = ''
+ A unique (on your network) to identify the server by.
+ '';
+ };
+
+ mediaDirectories = mkOption {
+ type = with types; listOf (submodule mediaDirectory);
+ default = {};
+ description = ''
+ Declare media directories to index.
+ '';
+ example = [
+ { path = "/data/pictures"; recursive = false; hidden-files = false; }
+ { path = "/data/audio"; recursive = true; hidden-files = false; }
+ ];
+ };
+
+ customCfg = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Allow ${name} to create and use its own config file inside the <literal>dataDir</literal> as
+ configured by <option>services.mediatomb.dataDir</option>.
+ Deactivated by default, the service then runs with the configuration generated from this module.
+ Otherwise, when enabled, no service configuration is generated. Gerbera/Mediatomb then starts using
+ config.xml within the configured <literal>dataDir</literal>. It's up to the user to make a correct
+ configuration file.
+ '';
+ };
+
+ };
+ };
+
+
+ ###### implementation
+
+ config = let binaryCommand = "${pkg}/bin/${name}";
+ interfaceFlag = optionalString ( cfg.interface != "") "--interface ${cfg.interface}";
+ configFlag = optionalString (! cfg.customCfg) "--config ${pkgs.writeText "config.xml" configText}";
+ in mkIf cfg.enable {
+ systemd.services.mediatomb = {
+ description = "${cfg.serverName} media Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig.ExecStart = "${binaryCommand} --port ${toString cfg.port} ${interfaceFlag} ${configFlag} --home ${cfg.dataDir}";
+ serviceConfig.User = cfg.user;
+ };
+
+ users.groups = optionalAttrs (cfg.group == "mediatomb") {
+ mediatomb.gid = gid;
+ };
+
+ users.users = optionalAttrs (cfg.user == "mediatomb") {
+ mediatomb = {
+ isSystemUser = true;
+ group = cfg.group;
+ home = cfg.dataDir;
+ createHome = true;
+ description = "${name} DLNA Server User";
+ };
+ };
+
+ # Open firewall only if users enable it
+ networking.firewall = mkMerge [
+ (mkIf (cfg.openFirewall && cfg.interface != "") {
+ interfaces."${cfg.interface}" = defaultFirewallRules;
+ })
+ (mkIf (cfg.openFirewall && cfg.interface == "") defaultFirewallRules)
+ ];
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/metabase.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/metabase.nix
new file mode 100644
index 000000000000..e78100a046a2
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/metabase.nix
@@ -0,0 +1,103 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.services.metabase;
+
+ inherit (lib) mkEnableOption mkIf mkOption;
+ inherit (lib) optional optionalAttrs types;
+
+ dataDir = "/var/lib/metabase";
+
+in {
+
+ options = {
+
+ services.metabase = {
+ enable = mkEnableOption "Metabase service";
+
+ listen = {
+ ip = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = ''
+ IP address that Metabase should listen on.
+ '';
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 3000;
+ description = ''
+ Listen port for Metabase.
+ '';
+ };
+ };
+
+ ssl = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable SSL (https) support.
+ '';
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 8443;
+ description = ''
+ Listen port over SSL (https) for Metabase.
+ '';
+ };
+
+ keystore = mkOption {
+ type = types.nullOr types.path;
+ default = "${dataDir}/metabase.jks";
+ example = "/etc/secrets/keystore.jks";
+ description = ''
+ <link xlink:href="https://www.digitalocean.com/community/tutorials/java-keytool-essentials-working-with-java-keystores">Java KeyStore</link> file containing the certificates.
+ '';
+ };
+
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Open ports in the firewall for Metabase.
+ '';
+ };
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ systemd.services.metabase = {
+ description = "Metabase server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network-online.target" ];
+ environment = {
+ MB_PLUGINS_DIR = "${dataDir}/plugins";
+ MB_DB_FILE = "${dataDir}/metabase.db";
+ MB_JETTY_HOST = cfg.listen.ip;
+ MB_JETTY_PORT = toString cfg.listen.port;
+ } // optionalAttrs (cfg.ssl.enable) {
+ MB_JETTY_SSL = true;
+ MB_JETTY_SSL_PORT = toString cfg.ssl.port;
+ MB_JETTY_SSL_KEYSTORE = cfg.ssl.keystore;
+ };
+ serviceConfig = {
+ DynamicUser = true;
+ StateDirectory = baseNameOf dataDir;
+ ExecStart = "${pkgs.metabase}/bin/metabase";
+ };
+ };
+
+ networking.firewall = mkIf cfg.openFirewall {
+ allowedTCPPorts = [ cfg.listen.port ] ++ optional cfg.ssl.enable cfg.ssl.port;
+ };
+
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/mwlib.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/mwlib.nix
new file mode 100644
index 000000000000..6b41b552a86d
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/mwlib.nix
@@ -0,0 +1,258 @@
+{ 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" ];
+
+ 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" ];
+
+ 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/infra/libkookie/nixpkgs/nixos/modules/services/misc/n8n.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/n8n.nix
new file mode 100644
index 000000000000..516d0f70ef0b
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/n8n.nix
@@ -0,0 +1,78 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.n8n;
+ format = pkgs.formats.json {};
+ configFile = format.generate "n8n.json" cfg.settings;
+in
+{
+ options.services.n8n = {
+
+ enable = mkEnableOption "n8n server";
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Open ports in the firewall for the n8n web interface.";
+ };
+
+ settings = mkOption {
+ type = format.type;
+ default = {};
+ description = ''
+ Configuration for n8n, see <link xlink:href="https://docs.n8n.io/reference/configuration.html"/>
+ for supported values.
+ '';
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ services.n8n.settings = {
+ # We use this to open the firewall, so we need to know about the default at eval time
+ port = lib.mkDefault 5678;
+ };
+
+ systemd.services.n8n = {
+ description = "N8N service";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ environment = {
+ # This folder must be writeable as the application is storing
+ # its data in it, so the StateDirectory is a good choice
+ N8N_USER_FOLDER = "/var/lib/n8n";
+ N8N_CONFIG_FILES = "${configFile}";
+ };
+ serviceConfig = {
+ Type = "simple";
+ ExecStart = "${pkgs.n8n}/bin/n8n";
+ Restart = "on-failure";
+ StateDirectory = "n8n";
+
+ # Basic Hardening
+ NoNewPrivileges = "yes";
+ PrivateTmp = "yes";
+ PrivateDevices = "yes";
+ DevicePolicy = "closed";
+ DynamicUser = "true";
+ ProtectSystem = "strict";
+ ProtectHome = "read-only";
+ ProtectControlGroups = "yes";
+ ProtectKernelModules = "yes";
+ ProtectKernelTunables = "yes";
+ RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
+ RestrictNamespaces = "yes";
+ RestrictRealtime = "yes";
+ RestrictSUIDSGID = "yes";
+ MemoryDenyWriteExecute = "yes";
+ LockPersonality = "yes";
+ };
+ };
+
+ networking.firewall = mkIf cfg.openFirewall {
+ allowedTCPPorts = [ cfg.settings.port ];
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/nix-daemon.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/nix-daemon.nix
new file mode 100644
index 000000000000..0eeff31d6c4d
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/nix-daemon.nix
@@ -0,0 +1,599 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.nix;
+
+ nix = cfg.package.out;
+
+ nixVersion = getVersion nix;
+
+ isNix23 = versionAtLeast nixVersion "2.3pre";
+
+ makeNixBuildUser = nr: {
+ name = "nixbld${toString nr}";
+ value = {
+ 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 = listToAttrs (map makeNixBuildUser (range 1 cfg.nrBuildUsers));
+
+ nixConf =
+ assert versionAtLeast nixVersion "2.2";
+ pkgs.runCommand "nix.conf" { preferLocalBuild = true; extraOptions = cfg.extraOptions; } (
+ ''
+ 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
+ max-jobs = ${toString (cfg.maxJobs)}
+ cores = ${toString (cfg.buildCores)}
+ sandbox = ${if (builtins.isBool cfg.useSandbox) then boolToString cfg.useSandbox else cfg.useSandbox}
+ extra-sandbox-paths = ${toString cfg.sandboxPaths}
+ substituters = ${toString cfg.binaryCaches}
+ trusted-substituters = ${toString cfg.trustedBinaryCaches}
+ trusted-public-keys = ${toString cfg.binaryCachePublicKeys}
+ auto-optimise-store = ${boolToString cfg.autoOptimiseStore}
+ require-sigs = ${boolToString cfg.requireSignedBinaryCaches}
+ trusted-users = ${toString cfg.trustedUsers}
+ allowed-users = ${toString cfg.allowedUsers}
+ ${optionalString (!cfg.distributedBuilds) ''
+ builders =
+ ''}
+ system-features = ${toString cfg.systemFeatures}
+ ${optionalString isNix23 ''
+ sandbox-fallback = false
+ ''}
+ $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 ${optionalString isNix23 "--no-net --option experimental-features nix-command"} >/dev/null
+ '')
+ );
+
+in
+
+{
+ imports = [
+ (mkRenamedOptionModule [ "nix" "useChroot" ] [ "nix" "useSandbox" ])
+ (mkRenamedOptionModule [ "nix" "chrootDirs" ] [ "nix" "sandboxPaths" ])
+ ];
+
+ ###### 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 = "auto";
+ example = 64;
+ description = ''
+ This option defines the maximum number of jobs that Nix will try to
+ build in parallel. The default is auto, which means it will use all
+ available logical cores. It is recommend to 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 = ''
+ keep-outputs = true
+ 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.submodule ({
+ options = {
+ hostName = mkOption {
+ type = types.str;
+ example = "nixbuilder.example.org";
+ description = ''
+ The hostname of the build machine.
+ '';
+ };
+ system = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "x86_64-linux";
+ description = ''
+ The system type the build machine can execute derivations on.
+ Either this attribute or <varname>systems</varname> must be
+ present, where <varname>system</varname> takes precedence if
+ both are set.
+ '';
+ };
+ systems = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "x86_64-linux" "aarch64-linux" ];
+ description = ''
+ The system types the build machine can execute derivations on.
+ Either this attribute or <varname>system</varname> must be
+ present, where <varname>system</varname> takes precedence if
+ both are set.
+ '';
+ };
+ sshUser = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "builder";
+ description = ''
+ The username to log in as on the remote host. This user must be
+ able to log in and run nix commands non-interactively. It must
+ also be privileged to build derivations, so must be included in
+ <option>nix.trustedUsers</option>.
+ '';
+ };
+ sshKey = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "/root/.ssh/id_buildhost_builduser";
+ description = ''
+ The path to the SSH private key with which to authenticate on
+ the build machine. The private key must not have a passphrase.
+ If null, the building user (root on NixOS machines) must have an
+ appropriate ssh configuration to log in non-interactively.
+
+ Note that for security reasons, this path must point to a file
+ in the local filesystem, *not* to the nix store.
+ '';
+ };
+ maxJobs = mkOption {
+ type = types.int;
+ default = 1;
+ description = ''
+ The number of concurrent jobs the build machine supports. The
+ build machine will enforce its own limits, but this allows hydra
+ to schedule better since there is no work-stealing between build
+ machines.
+ '';
+ };
+ speedFactor = mkOption {
+ type = types.int;
+ default = 1;
+ description = ''
+ The relative speed of this builder. This is an arbitrary integer
+ that indicates the speed of this builder, relative to other
+ builders. Higher is faster.
+ '';
+ };
+ mandatoryFeatures = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "big-parallel" ];
+ description = ''
+ A list of features mandatory for this builder. The builder will
+ be ignored for derivations that don't require all features in
+ this list. All mandatory features are automatically included in
+ <varname>supportedFeatures</varname>.
+ '';
+ };
+ supportedFeatures = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "kvm" "big-parallel" ];
+ description = ''
+ A list of features supported by this builder. The builder will
+ be ignored for derivations that require features not in this
+ list.
+ '';
+ };
+ };
+ }));
+ default = [];
+ 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.
+ '';
+ };
+
+ # 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;
+ description = ''
+ List of binary cache URLs used to obtain pre-built binaries
+ of Nix packages.
+
+ By default https://cache.nixos.org/ is added,
+ to override it use <literal>lib.mkForce []</literal>.
+ '';
+ };
+
+ trustedBinaryCaches = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "https://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>&lt;nixpkgs&gt;</literal>).
+ '';
+ };
+
+ systemFeatures = mkOption {
+ type = types.listOf types.str;
+ example = [ "kvm" "big-parallel" "gccarch-skylake" ];
+ description = ''
+ The supported features of a machine
+ '';
+ };
+
+ checkConfig = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ If enabled (the default), checks that Nix can parse the generated nix.conf.
+ '';
+ };
+
+ registry = mkOption {
+ type = types.attrsOf (types.submodule (
+ let
+ inputAttrs = types.attrsOf (types.oneOf [types.str types.int types.bool types.package]);
+ in
+ { config, name, ... }:
+ { options = {
+ from = mkOption {
+ type = inputAttrs;
+ example = { type = "indirect"; id = "nixpkgs"; };
+ description = "The flake reference to be rewritten.";
+ };
+ to = mkOption {
+ type = inputAttrs;
+ example = { type = "github"; owner = "my-org"; repo = "my-nixpkgs"; };
+ description = "The flake reference to which <option>from></option> is to be rewritten.";
+ };
+ flake = mkOption {
+ type = types.unspecified;
+ default = null;
+ example = literalExample "nixpkgs";
+ description = ''
+ The flake input to which <option>from></option> is to be rewritten.
+ '';
+ };
+ exact = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether the <option>from</option> reference needs to match exactly. If set,
+ a <option>from</option> reference like <literal>nixpkgs</literal> does not
+ match with a reference like <literal>nixpkgs/nixos-20.03</literal>.
+ '';
+ };
+ };
+ config = {
+ from = mkDefault { type = "indirect"; id = name; };
+ to = mkIf (config.flake != null)
+ ({ type = "path";
+ path = config.flake.outPath;
+ } // lib.filterAttrs
+ (n: v: n == "lastModified" || n == "rev" || n == "revCount" || n == "narHash")
+ config.flake);
+ };
+ }
+ ));
+ default = {};
+ description = ''
+ A system-wide flake registry.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = {
+
+ nix.binaryCachePublicKeys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ];
+ nix.binaryCaches = [ "https://cache.nixos.org/" ];
+
+ environment.systemPackages =
+ [ nix
+ pkgs.nix-info
+ ]
+ ++ optional (config.programs.bash.enableCompletion && !versionAtLeast nixVersion "2.4pre") pkgs.nix-bash-completions;
+
+ environment.etc."nix/nix.conf".source = nixConf;
+
+ environment.etc."nix/registry.json".text = builtins.toJSON {
+ version = 2;
+ flakes = mapAttrsToList (n: v: { inherit (v) from to exact; }) cfg.registry;
+ };
+
+ # 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 != null then "${machine.sshUser}@" else ""}${machine.hostName} "
+ + (if machine.system != null then machine.system else concatStringsSep "," machine.systems)
+ + " ${if machine.sshKey != null then machine.sshKey else "-"} ${toString machine.maxJobs} "
+ + toString (machine.speedFactor)
+ + " "
+ + concatStringsSep "," (machine.mandatoryFeatures ++ machine.supportedFeatures)
+ + " "
+ + concatStringsSep "," machine.mandatoryFeatures
+ + "\n"
+ ) cfg.buildMachines;
+ };
+
+ systemd.packages = [ nix ];
+
+ systemd.sockets.nix-daemon.wantedBy = [ "sockets.target" ];
+
+ systemd.services.nix-daemon =
+ { path = [ nix pkgs.util-linux config.programs.ssh.package ]
+ ++ optionals cfg.distributedBuilds [ pkgs.gzip ];
+
+ 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 ];
+ };
+
+ # Set up the environment variables for running Nix.
+ environment.sessionVariables = cfg.envVars //
+ { NIX_PATH = cfg.nixPath;
+ };
+
+ environment.extraInit =
+ ''
+ 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 (if cfg.maxJobs == "auto" then 0 else cfg.maxJobs));
+
+ users.users = nixbldUsers;
+
+ services.xserver.displayManager.hiddenUsers = attrNames nixbldUsers;
+
+ system.activationScripts.nix = stringAfter [ "etc" "users" ]
+ ''
+ install -m 0755 -d /nix/var/nix/{gcroots,profiles}/per-user
+
+ # Subscribe the root user to the NixOS channel by default.
+ if [ ! -e "/root/.nix-channels" ]; then
+ echo "${config.system.defaultChannel} nixos" > "/root/.nix-channels"
+ fi
+ '';
+
+ nix.systemFeatures = mkDefault (
+ [ "nixos-test" "benchmark" "big-parallel" "kvm" ] ++
+ optionals (pkgs.hostPlatform.platform ? gcc.arch) (
+ # a builder can run code for `platform.gcc.arch` and inferior architectures
+ [ "gccarch-${pkgs.hostPlatform.platform.gcc.arch}" ] ++
+ map (x: "gccarch-${x}") lib.systems.architectures.inferiors.${pkgs.hostPlatform.platform.gcc.arch}
+ )
+ );
+
+ };
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/nix-gc.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/nix-gc.nix
new file mode 100644
index 000000000000..12bed05757ad
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/nix-optimise.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/nix-optimise.nix
new file mode 100644
index 000000000000..e02026d5f76c
--- /dev/null
+++ b/infra/libkookie/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 this if the nix daemon (and thus the nix store) is outside
+ unitConfig.ConditionPathIsReadWrite = "/nix/var/nix/daemon-socket";
+ serviceConfig.ExecStart = "${config.nix.package}/bin/nix-store --optimise";
+ startAt = optionals cfg.automatic cfg.dates;
+ };
+
+ };
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/nix-ssh-serve.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/nix-ssh-serve.nix
new file mode 100644
index 000000000000..7ce3841be2f5
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/novacomd.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/novacomd.nix
new file mode 100644
index 000000000000..7cfc68d2b673
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/nzbget.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/nzbget.nix
new file mode 100644
index 000000000000..715ec891cd68
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/nzbget.nix
@@ -0,0 +1,99 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.nzbget;
+ pkg = pkgs.nzbget;
+ stateDir = "/var/lib/nzbget";
+ configFile = "${stateDir}/nzbget.conf";
+ configOpts = concatStringsSep " " (mapAttrsToList (name: value: "-o ${name}=${value}") nixosOpts);
+
+ nixosOpts = {
+ # allows nzbget to run as a "simple" service
+ OutputMode = "loggable";
+ # use journald for logging
+ WriteLog = "none";
+ ErrorTarget = "screen";
+ WarningTarget = "screen";
+ InfoTarget = "screen";
+ DetailTarget = "screen";
+ # required paths
+ ConfigTemplate = "${pkg}/share/nzbget/nzbget.conf";
+ WebDir = "${pkg}/share/nzbget/webui";
+ # nixos handles package updates
+ UpdateCheck = "none";
+ };
+
+in
+{
+ imports = [
+ (mkRemovedOptionModule [ "services" "misc" "nzbget" "configFile" ] "The configuration of nzbget is now managed by users through the web interface.")
+ (mkRemovedOptionModule [ "services" "misc" "nzbget" "dataDir" ] "The data directory for nzbget is now /var/lib/nzbget.")
+ (mkRemovedOptionModule [ "services" "misc" "nzbget" "openFirewall" ] "The port used by nzbget is managed through the web interface so you should adjust your firewall rules accordingly.")
+ ];
+
+ # interface
+
+ options = {
+ services.nzbget = {
+ enable = mkEnableOption "NZBGet";
+
+ 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";
+ };
+ };
+ };
+
+ # implementation
+
+ config = mkIf cfg.enable {
+ systemd.services.nzbget = {
+ description = "NZBGet Daemon";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = with pkgs; [
+ unrar
+ p7zip
+ ];
+ preStart = ''
+ if [ ! -f ${configFile} ]; then
+ ${pkgs.coreutils}/bin/install -m 0700 ${pkg}/share/nzbget/nzbget.conf ${configFile}
+ fi
+ '';
+
+ serviceConfig = {
+ StateDirectory = "nzbget";
+ StateDirectoryMode = "0750";
+ User = cfg.user;
+ Group = cfg.group;
+ UMask = "0002";
+ Restart = "on-failure";
+ ExecStart = "${pkg}/bin/nzbget --server --configfile ${stateDir}/nzbget.conf ${configOpts}";
+ ExecStop = "${pkg}/bin/nzbget --quit";
+ };
+ };
+
+ users.users = mkIf (cfg.user == "nzbget") {
+ nzbget = {
+ home = stateDir;
+ group = cfg.group;
+ uid = config.ids.uids.nzbget;
+ };
+ };
+
+ users.groups = mkIf (cfg.group == "nzbget") {
+ nzbget = {
+ gid = config.ids.gids.nzbget;
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/octoprint.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/octoprint.nix
new file mode 100644
index 000000000000..a69e65073050
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/octoprint.nix
@@ -0,0 +1,129 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.octoprint;
+
+ baseConfig = {
+ plugins.curalegacy.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 = package.python.withPackages (ps: [ps.octoprint] ++ (cfg.plugins ps));
+
+ package = pkgs.octoprint;
+
+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: with plugins; [ themeify stlviewer ]";
+ description = "Additional plugins to be used. Available plugins are passed through the plugins input.";
+ };
+
+ 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") {
+ octoprint = {
+ group = cfg.group;
+ uid = config.ids.uids.octoprint;
+ };
+ };
+
+ users.groups = optionalAttrs (cfg.group == "octoprint") {
+ octoprint.gid = config.ids.gids.octoprint;
+ };
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
+ ];
+
+ systemd.services.octoprint = {
+ description = "OctoPrint, web interface for 3D printers";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ path = [ pluginsEnv ];
+
+ preStart = ''
+ 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
+ '';
+
+ serviceConfig = {
+ ExecStart = "${pluginsEnv}/bin/octoprint serve -b ${cfg.stateDir}";
+ User = cfg.user;
+ Group = cfg.group;
+ };
+ };
+
+ };
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/osrm.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/osrm.nix
new file mode 100644
index 000000000000..79c347ab7e0e
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/osrm.nix
@@ -0,0 +1,86 @@
+{ 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;
+ isSystemUser = true;
+ };
+
+ 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/infra/libkookie/nixpkgs/nixos/modules/services/misc/packagekit.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/packagekit.nix
new file mode 100644
index 000000000000..325c4e84e0d8
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/packagekit.nix
@@ -0,0 +1,65 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.packagekit;
+
+ packagekitConf = ''
+ [Daemon]
+ DefaultBackend=${cfg.backend}
+ 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.
+ '';
+
+ # TODO: integrate with PolicyKit if the nix backend matures to the point
+ # where it will require elevated permissions
+ backend = mkOption {
+ type = types.enum [ "test_nop" ];
+ default = "test_nop";
+ description = ''
+ PackageKit supports multiple different backends and <literal>auto</literal> which
+ should do the right thing.
+ </para>
+ <para>
+ On NixOS however, we do not have a backend compatible with nix 2.0
+ (refer to <link xlink:href="https://github.com/NixOS/nix/issues/233">this issue</link> so we have to force
+ it to <literal>test_nop</literal> for now.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ services.dbus.packages = with pkgs; [ packagekit ];
+
+ systemd.packages = with pkgs; [ packagekit ];
+
+ environment.etc."PackageKit/PackageKit.conf".text = packagekitConf;
+ environment.etc."PackageKit/Vendor.conf".text = vendorConf;
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/paperless.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/paperless.nix
new file mode 100644
index 000000000000..bfaf760fb836
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/paperless.nix
@@ -0,0 +1,183 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+ cfg = config.services.paperless;
+
+ defaultUser = "paperless";
+
+ manage = cfg.package.withConfig {
+ config = {
+ PAPERLESS_CONSUMPTION_DIR = cfg.consumptionDir;
+ PAPERLESS_INLINE_DOC = "true";
+ PAPERLESS_DISABLE_LOGIN = "true";
+ } // cfg.extraConfig;
+ inherit (cfg) dataDir ocrLanguages;
+ paperlessPkg = cfg.package;
+ };
+in
+{
+ options.services.paperless = {
+ enable = mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = ''
+ Enable Paperless.
+
+ When started, the Paperless database is automatically created if it doesn't
+ exist and updated if the Paperless package has changed.
+ Both tasks are achieved by running a Django migration.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.str;
+ default = "/var/lib/paperless";
+ description = "Directory to store the Paperless data.";
+ };
+
+ consumptionDir = mkOption {
+ type = types.str;
+ default = "${cfg.dataDir}/consume";
+ defaultText = "\${dataDir}/consume";
+ description = "Directory from which new documents are imported.";
+ };
+
+ consumptionDirIsPublic = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether all users can write to the consumption dir.";
+ };
+
+ ocrLanguages = mkOption {
+ type = with types; nullOr (listOf str);
+ default = null;
+ description = ''
+ Languages available for OCR via Tesseract, specified as
+ <literal>ISO 639-2/T</literal> language codes.
+ If unset, defaults to all available languages.
+ '';
+ example = [ "eng" "spa" "jpn" ];
+ };
+
+ address = mkOption {
+ type = types.str;
+ default = "localhost";
+ description = "Server listening address.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = 28981;
+ description = "Server port to listen on.";
+ };
+
+ extraConfig = mkOption {
+ type = types.attrs;
+ default = {};
+ description = ''
+ Extra paperless config options.
+
+ The config values are evaluated as double-quoted Bash string literals.
+
+ See <literal>paperless-src/paperless.conf.example</literal> for available options.
+
+ To enable user authentication, set <literal>PAPERLESS_DISABLE_LOGIN = "false"</literal>
+ and run the shell command <literal>$dataDir/paperless-manage createsuperuser</literal>.
+
+ To define secret options without storing them in /nix/store, use the following pattern:
+ <literal>PAPERLESS_PASSPHRASE = "$(&lt; /etc/my_passphrase_file)"</literal>
+ '';
+ example = literalExample ''
+ {
+ PAPERLESS_OCR_LANGUAGE = "deu";
+ }
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = defaultUser;
+ description = "User under which Paperless runs.";
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.paperless;
+ defaultText = "pkgs.paperless";
+ description = "The Paperless package to use.";
+ };
+
+ manage = mkOption {
+ type = types.package;
+ readOnly = true;
+ default = manage;
+ description = ''
+ A script to manage the Paperless instance.
+ It wraps Django's manage.py and is also available at
+ <literal>$dataDir/manage-paperless</literal>
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ systemd.tmpfiles.rules = [
+ "d '${cfg.dataDir}' - ${cfg.user} ${config.users.users.${cfg.user}.group} - -"
+ ] ++ (optional cfg.consumptionDirIsPublic
+ "d '${cfg.consumptionDir}' 777 - - - -"
+ # If the consumption dir is not created here, it's automatically created by
+ # 'manage' with the default permissions.
+ );
+
+ systemd.services.paperless-consumer = {
+ description = "Paperless document consumer";
+ serviceConfig = {
+ User = cfg.user;
+ ExecStart = "${manage} document_consumer";
+ Restart = "always";
+ };
+ after = [ "systemd-tmpfiles-setup.service" ];
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ if [[ $(readlink ${cfg.dataDir}/paperless-manage) != ${manage} ]]; then
+ ln -sf ${manage} ${cfg.dataDir}/paperless-manage
+ fi
+
+ ${manage.setupEnv}
+ # Auto-migrate on first run or if the package has changed
+ versionFile="$PAPERLESS_DBDIR/src-version"
+ if [[ $(cat "$versionFile" 2>/dev/null) != ${cfg.package} ]]; then
+ python $paperlessSrc/manage.py migrate
+ echo ${cfg.package} > "$versionFile"
+ fi
+ '';
+ };
+
+ systemd.services.paperless-server = {
+ description = "Paperless document server";
+ serviceConfig = {
+ User = cfg.user;
+ ExecStart = "${manage} runserver --noreload ${cfg.address}:${toString cfg.port}";
+ Restart = "always";
+ };
+ # Bind to `paperless-consumer` so that the server never runs
+ # during migrations
+ bindsTo = [ "paperless-consumer.service" ];
+ after = [ "paperless-consumer.service" ];
+ wantedBy = [ "multi-user.target" ];
+ };
+
+ users = optionalAttrs (cfg.user == defaultUser) {
+ users.${defaultUser} = {
+ group = defaultUser;
+ uid = config.ids.uids.paperless;
+ home = cfg.dataDir;
+ };
+
+ groups.${defaultUser} = {
+ gid = config.ids.gids.paperless;
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/parsoid.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/parsoid.nix
new file mode 100644
index 000000000000..09b7f977bfbf
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/parsoid.nix
@@ -0,0 +1,129 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.parsoid;
+
+ parsoid = pkgs.nodePackages.parsoid;
+
+ 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
+{
+ imports = [
+ (mkRemovedOptionModule [ "services" "parsoid" "interwikis" ] "Use services.parsoid.wikis instead")
+ ];
+
+ ##### 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 = {
+ ExecStart = "${parsoid}/lib/node_modules/parsoid/bin/server.js -c ${confFile} -n ${toString cfg.workers}";
+
+ DynamicUser = true;
+ User = "parsoid";
+ Group = "parsoid";
+
+ CapabilityBoundingSet = "";
+ NoNewPrivileges = true;
+ ProtectSystem = "strict";
+ ProtectHome = true;
+ PrivateTmp = true;
+ PrivateDevices = true;
+ ProtectHostname = true;
+ ProtectKernelTunables = true;
+ ProtectKernelModules = true;
+ ProtectControlGroups = true;
+ RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+ RestrictNamespaces = true;
+ LockPersonality = true;
+ #MemoryDenyWriteExecute = true;
+ RestrictRealtime = true;
+ RestrictSUIDSGID = true;
+ RemoveIPC = true;
+ };
+ };
+
+ };
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/pinnwand.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/pinnwand.nix
new file mode 100644
index 000000000000..aa1ee5cfaa77
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/pinnwand.nix
@@ -0,0 +1,78 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.pinnwand;
+
+ format = pkgs.formats.toml {};
+ configFile = format.generate "pinnwand.toml" cfg.settings;
+in
+{
+ options.services.pinnwand = {
+ enable = mkEnableOption "Pinnwand";
+
+ port = mkOption {
+ type = types.port;
+ description = "The port to listen on.";
+ default = 8000;
+ };
+
+ settings = mkOption {
+ type = format.type;
+ description = ''
+ Your <filename>pinnwand.toml</filename> as a Nix attribute set. Look up
+ possible options in the <link xlink:href="https://github.com/supakeen/pinnwand/blob/master/pinnwand.toml-example">pinnwand.toml-example</link>.
+ '';
+ default = {
+ # https://github.com/supakeen/pinnwand/blob/master/pinnwand.toml-example
+ database_uri = "sqlite:///var/lib/pinnwand/pinnwand.db";
+ preferred_lexeres = [];
+ paste_size = 262144;
+ paste_help = ''
+ <p>Welcome to pinnwand, this site is a pastebin. It allows you to share code with others. If you write code in the text area below and press the paste button you will be given a link you can share with others so they can view your code as well.</p><p>People with the link can view your pasted code, only you can remove your paste and it expires automatically. Note that anyone could guess the URI to your paste so don't rely on it being private.</p>
+ '';
+ footer = ''
+ View <a href="//github.com/supakeen/pinnwand" target="_BLANK">source code</a>, the <a href="/removal">removal</a> or <a href="/expiry">expiry</a> stories, or read the <a href="/about">about</a> page.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.pinnwand = {
+ description = "Pinnwannd HTTP Server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ unitConfig.Documentation = "https://pinnwand.readthedocs.io/en/latest/";
+ serviceConfig = {
+ ExecStart = "${pkgs.pinnwand}/bin/pinnwand --configuration-path ${configFile} http --port ${toString(cfg.port)}";
+ StateDirectory = "pinnwand";
+ StateDirectoryMode = "0700";
+
+ AmbientCapabilities = [];
+ CapabilityBoundingSet = "";
+ DevicePolicy = "closed";
+ DynamicUser = true;
+ LockPersonality = true;
+ MemoryDenyWriteExecute = true;
+ PrivateDevices = true;
+ PrivateUsers = true;
+ ProtectClock = true;
+ ProtectControlGroups = true;
+ ProtectKernelLogs = true;
+ ProtectHome = true;
+ ProtectHostname = true;
+ ProtectKernelModules = true;
+ ProtectKernelTunables = true;
+ RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+ RestrictNamespaces = true;
+ RestrictRealtime = true;
+ SystemCallArchitectures = "native";
+ SystemCallFilter = "@system-service";
+ UMask = "0077";
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/plex.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/plex.nix
new file mode 100644
index 000000000000..7efadf1b9bb1
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/pykms.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/pykms.nix
new file mode 100644
index 000000000000..d6aeae48ccb6
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/pykms.nix
@@ -0,0 +1,91 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.pykms;
+ libDir = "/var/lib/pykms";
+
+in {
+ meta.maintainers = with lib.maintainers; [ peterhoeg ];
+
+ imports = [
+ (mkRemovedOptionModule [ "services" "pykms" "verbose" ] "Use services.pykms.logLevel instead")
+ ];
+
+ options = {
+ services.pykms = {
+ 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.";
+ };
+
+ 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.";
+ };
+
+ logLevel = mkOption {
+ type = types.enum [ "CRITICAL" "ERROR" "WARNING" "INFO" "DEBUG" "MINI" ];
+ default = "INFO";
+ description = "How much to log";
+ };
+
+ extraArgs = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "Additional arguments";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewallPort [ cfg.port ];
+
+ systemd.services.pykms = {
+ description = "Python KMS";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ # python programs with DynamicUser = true require HOME to be set
+ environment.HOME = libDir;
+ serviceConfig = with pkgs; {
+ DynamicUser = true;
+ StateDirectory = baseNameOf libDir;
+ ExecStartPre = "${getBin pykms}/libexec/create_pykms_db.sh ${libDir}/clients.db";
+ ExecStart = lib.concatStringsSep " " ([
+ "${getBin pykms}/bin/server"
+ "--logfile STDOUT"
+ "--loglevel ${cfg.logLevel}"
+ ] ++ cfg.extraArgs ++ [
+ cfg.listenAddress
+ (toString cfg.port)
+ ]);
+ ProtectHome = "tmpfs";
+ WorkingDirectory = libDir;
+ SyslogIdentifier = "pykms";
+ Restart = "on-failure";
+ MemoryLimit = cfg.memoryLimit;
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/radarr.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/radarr.nix
new file mode 100644
index 000000000000..74444e24043f
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/redmine.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/redmine.nix
new file mode 100644
index 000000000000..1313bdaccc49
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/redmine.nix
@@ -0,0 +1,383 @@
+{ config, lib, pkgs, ... }:
+
+let
+ inherit (lib) mkBefore mkDefault mkEnableOption mkIf mkOption mkRemovedOptionModule types;
+ inherit (lib) concatStringsSep literalExample mapAttrsToList;
+ inherit (lib) optional optionalAttrs optionalString;
+
+ cfg = config.services.redmine;
+ format = pkgs.formats.yaml {};
+ bundle = "${cfg.package}/share/redmine/bin/bundle";
+
+ databaseYml = pkgs.writeText "database.yml" ''
+ production:
+ adapter: ${cfg.database.type}
+ database: ${cfg.database.name}
+ host: ${if (cfg.database.type == "postgresql" && cfg.database.socket != null) then cfg.database.socket else cfg.database.host}
+ port: ${toString cfg.database.port}
+ username: ${cfg.database.user}
+ password: #dbpass#
+ ${optionalString (cfg.database.type == "mysql2" && cfg.database.socket != null) "socket: ${cfg.database.socket}"}
+ '';
+
+ configurationYml = format.generate "configuration.yml" cfg.settings;
+ additionalEnvironment = pkgs.writeText "additional_environment.rb" 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}
+ '';
+ });
+
+ mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql2";
+ pgsqlLocal = cfg.database.createLocally && cfg.database.type == "postgresql";
+
+in
+{
+ imports = [
+ (mkRemovedOptionModule [ "services" "redmine" "extraConfig" ] "Use services.redmine.settings instead.")
+ (mkRemovedOptionModule [ "services" "redmine" "database" "password" ] "Use services.redmine.database.passwordFile instead.")
+ ];
+
+ # interface
+ options = {
+ services.redmine = {
+ enable = mkEnableOption "Redmine";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.redmine;
+ description = "Which Redmine package to use.";
+ example = "pkgs.redmine.override { ruby = pkgs.ruby_2_7; }";
+ };
+
+ 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.";
+ };
+
+ settings = mkOption {
+ type = format.type;
+ default = {};
+ description = ''
+ Redmine configuration (<filename>configuration.yml</filename>). Refer to
+ <link xlink:href="https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration"/>
+ for details.
+ '';
+ 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 <link xlink:href="https://svn.redmine.org/redmine/trunk/config/additional_environment.rb.example"/>
+ for details.
+ '';
+ 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 = "localhost";
+ description = "Database host address.";
+ };
+
+ port = mkOption {
+ type = types.int;
+ default = if cfg.database.type == "postgresql" then 5432 else 3306;
+ defaultText = "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.";
+ };
+
+ 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 =
+ if mysqlLocal then "/run/mysqld/mysqld.sock"
+ else if pgsqlLocal then "/run/postgresql"
+ else null;
+ defaultText = "/run/mysqld/mysqld.sock";
+ example = "/run/mysqld/mysqld.sock";
+ description = "Path to the unix socket file to use for authentication.";
+ };
+
+ createLocally = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Create the database and database user locally.";
+ };
+ };
+ };
+ };
+
+ # implementation
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = cfg.database.passwordFile != null || cfg.database.socket != null;
+ message = "one of services.redmine.database.socket or services.redmine.database.passwordFile must be set";
+ }
+ { assertion = cfg.database.createLocally -> cfg.database.user == cfg.user;
+ message = "services.redmine.database.user must be set to ${cfg.user} if services.redmine.database.createLocally is set true";
+ }
+ { assertion = cfg.database.createLocally -> cfg.database.socket != null;
+ message = "services.redmine.database.socket must be set if services.redmine.database.createLocally is set to true";
+ }
+ { assertion = cfg.database.createLocally -> cfg.database.host == "localhost";
+ message = "services.redmine.database.host must be set to localhost if services.redmine.database.createLocally is set to true";
+ }
+ ];
+
+ services.redmine.settings = {
+ production = {
+ 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.breezy}/bin/bzr";
+ scm_darcs_command = "${pkgs.darcs}/bin/darcs";
+ };
+ };
+
+ services.redmine.extraEnv = mkBefore ''
+ config.logger = Logger.new("${cfg.stateDir}/log/production.log", 14, 1048576)
+ config.logger.level = Logger::INFO
+ '';
+
+ services.mysql = mkIf mysqlLocal {
+ enable = true;
+ package = mkDefault pkgs.mariadb;
+ ensureDatabases = [ cfg.database.name ];
+ ensureUsers = [
+ { name = cfg.database.user;
+ ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+ }
+ ];
+ };
+
+ services.postgresql = mkIf pgsqlLocal {
+ enable = true;
+ ensureDatabases = [ cfg.database.name ];
+ ensureUsers = [
+ { name = cfg.database.user;
+ ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+ }
+ ];
+ };
+
+ # 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" ] ++ optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "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
+ breezy
+ 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=${optionalString (cfg.database.passwordFile != null) "$(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") {
+ redmine = {
+ group = cfg.group;
+ home = cfg.stateDir;
+ uid = config.ids.uids.redmine;
+ };
+ };
+
+ users.groups = optionalAttrs (cfg.group == "redmine") {
+ redmine.gid = config.ids.gids.redmine;
+ };
+
+ };
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/ripple-data-api.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/ripple-data-api.nix
new file mode 100644
index 000000000000..9fab462f7e3b
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/ripple-data-api.nix
@@ -0,0 +1,193 @@
+{ 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.ripple-data-api =
+ { description = "Ripple data api user";
+ uid = config.ids.uids.ripple-data-api;
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/rippled.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/rippled.nix
new file mode 100644
index 000000000000..ef34e3a779f0
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/rippled.nix
@@ -0,0 +1,431 @@
+{ 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.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/infra/libkookie/nixpkgs/nixos/modules/services/misc/safeeyes.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/safeeyes.nix
new file mode 100644
index 000000000000..1e748195e41a
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/safeeyes.nix
@@ -0,0 +1,47 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.safeeyes;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.safeeyes = {
+
+ enable = mkEnableOption "the safeeyes OSGi service";
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.user.services.safeeyes = {
+ description = "Safeeyes";
+
+ wantedBy = [ "graphical-session.target" ];
+ partOf = [ "graphical-session.target" ];
+
+ startLimitIntervalSec = 350;
+ startLimitBurst = 10;
+ serviceConfig = {
+ ExecStart = ''
+ ${pkgs.safeeyes}/bin/safeeyes
+ '';
+ Restart = "on-failure";
+ RestartSec = 3;
+ };
+ };
+
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/serviio.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/serviio.nix
new file mode 100644
index 000000000000..0ead6a816918
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/serviio.nix
@@ -0,0 +1,87 @@
+{ 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 = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ pkgs.serviio ];
+ serviceConfig = {
+ User = "serviio";
+ Group = "serviio";
+ ExecStart = "${serviioStart}";
+ ExecStop = "${serviioStart} -stop";
+ };
+ };
+
+ users.users.serviio =
+ { group = "serviio";
+ home = cfg.dataDir;
+ description = "Serviio Media Server User";
+ createHome = true;
+ isSystemUser = true;
+ };
+
+ users.groups.serviio = { };
+
+ networking.firewall = {
+ allowedTCPPorts = [
+ 8895 # serve UPnP responses
+ 23423 # console
+ 23424 # mediabrowser
+ ];
+ allowedUDPPorts = [
+ 1900 # UPnP service discovey
+ ];
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/sickbeard.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/sickbeard.nix
new file mode 100644
index 000000000000..a32dbfa3108f
--- /dev/null
+++ b/infra/libkookie/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) {
+ ${name} = {
+ uid = config.ids.uids.sickbeard;
+ group = cfg.group;
+ description = "sickbeard user";
+ home = cfg.dataDir;
+ createHome = true;
+ };
+ };
+
+ users.groups = optionalAttrs (cfg.group == 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/infra/libkookie/nixpkgs/nixos/modules/services/misc/siproxd.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/siproxd.nix
new file mode 100644
index 000000000000..20fe0793b84b
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/siproxd.nix
@@ -0,0 +1,179 @@
+{ 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.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/infra/libkookie/nixpkgs/nixos/modules/services/misc/snapper.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/snapper.nix
new file mode 100644
index 000000000000..3560d08520b7
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/snapper.nix
@@ -0,0 +1,162 @@
+{ 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.snapperd = {
+ description = "DBus interface for snapper";
+ inherit documentation;
+ serviceConfig = {
+ Type = "dbus";
+ BusName = "org.opensuse.Snapper";
+ ExecStart = "${pkgs.snapper}/bin/snapperd";
+ };
+ };
+
+ 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/infra/libkookie/nixpkgs/nixos/modules/services/misc/sonarr.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/sonarr.nix
new file mode 100644
index 000000000000..77c7f0582d0b
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/spice-vdagentd.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/spice-vdagentd.nix
new file mode 100644
index 000000000000..2dd9fcf68ab0
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/ssm-agent.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/ssm-agent.nix
new file mode 100644
index 000000000000..e50b07e0b862
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/ssm-agent.nix
@@ -0,0 +1,66 @@
+{ 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 pkgs.coreutils ];
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/amazon-ssm-agent";
+ KillMode = "process";
+ Restart = "on-failure";
+ RestartSec = "15min";
+ };
+ };
+
+ # Add user that Session Manager needs, and give it sudo.
+ # This is consistent with Amazon Linux 2 images.
+ security.sudo.extraRules = [
+ {
+ users = [ "ssm-user" ];
+ commands = [
+ {
+ command = "ALL";
+ options = [ "NOPASSWD" ];
+ }
+ ];
+ }
+ ];
+ # On Amazon Linux 2 images, the ssm-user user is pretty much a
+ # normal user with its own group. We do the same.
+ users.groups.ssm-user = {};
+ users.users.ssm-user = {
+ isNormalUser = true;
+ group = "ssm-user";
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/sssd.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/sssd.nix
new file mode 100644
index 000000000000..386281e2b7cc
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/sssd.nix
@@ -0,0 +1,97 @@
+{ 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 {
+ 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 = [ pkgs.sssd ];
+ system.nssDatabases = {
+ group = [ "sss" ];
+ passwd = [ "sss" ];
+ services = [ "sss" ];
+ shadow = [ "sss" ];
+ };
+ 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.authorizedKeysCommand = "/etc/ssh/authorized_keys_command";
+ services.openssh.authorizedKeysCommandUser = "nobody";
+ })];
+
+ meta.maintainers = with maintainers; [ bbigras ];
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/subsonic.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/subsonic.nix
new file mode 100644
index 000000000000..152917d345cc
--- /dev/null
+++ b/infra/libkookie/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.str;
+ 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 = [ "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/infra/libkookie/nixpkgs/nixos/modules/services/misc/sundtek.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/sundtek.nix
new file mode 100644
index 000000000000..e3234518c940
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/svnserve.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/svnserve.nix
new file mode 100644
index 000000000000..f70e3ca7fef0
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/svnserve.nix
@@ -0,0 +1,45 @@
+# SVN server
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.svnserve;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.svnserve = {
+
+ enable = mkOption {
+ type = types.bool;
+ 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/infra/libkookie/nixpkgs/nixos/modules/services/misc/synergy.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/synergy.nix
new file mode 100644
index 000000000000..5b7cf3ac46c3
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/synergy.nix
@@ -0,0 +1,124 @@
+{ 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 = mkEnableOption "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 = mkEnableOption "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/infra/libkookie/nixpkgs/nixos/modules/services/misc/sysprof.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/sysprof.nix
new file mode 100644
index 000000000000..ab91a8b586a2
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/taskserver/default.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/taskserver/default.nix
new file mode 100644
index 000000000000..a894caed1a34
--- /dev/null
+++ b/infra/libkookie/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 val == null 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 (x: x == null) (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 = optionalAttrs (cfg.user == "taskd") {
+ taskd = {
+ uid = config.ids.uids.taskd;
+ description = "Taskserver user";
+ group = cfg.group;
+ };
+ };
+
+ users.groups = optionalAttrs (cfg.group == "taskd") {
+ 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}";
+ ${mapNullable (_: "crl") cfg.pki.manual.server.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/infra/libkookie/nixpkgs/nixos/modules/services/misc/taskserver/doc.xml b/infra/libkookie/nixpkgs/nixos/modules/services/misc/taskserver/doc.xml
new file mode 100644
index 000000000000..5656bb85b373
--- /dev/null
+++ b/infra/libkookie/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>
+<prompt>$ </prompt>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
+ &amp; 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/infra/libkookie/nixpkgs/nixos/modules/services/misc/taskserver/helper-tool.py b/infra/libkookie/nixpkgs/nixos/modules/services/misc/taskserver/helper-tool.py
new file mode 100644
index 000000000000..22a3d8d5311b
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/tautulli.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/tautulli.nix
new file mode 100644
index 000000000000..aded33629f1c
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/tautulli.nix
@@ -0,0 +1,81 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.services.tautulli;
+in
+{
+ imports = [
+ (mkRenamedOptionModule [ "services" "plexpy" ] [ "services" "tautulli" ])
+ ];
+
+ 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/infra/libkookie/nixpkgs/nixos/modules/services/misc/tiddlywiki.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/tiddlywiki.nix
new file mode 100644
index 000000000000..2adc08f6cfed
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/tiddlywiki.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.tiddlywiki;
+ listenParams = concatStrings (mapAttrsToList (n: v: " '${n}=${toString v}' ") cfg.listenOptions);
+ exe = "${pkgs.nodePackages.tiddlywiki}/lib/node_modules/.bin/tiddlywiki";
+ name = "tiddlywiki";
+ dataDir = "/var/lib/" + name;
+
+in {
+
+ options.services.tiddlywiki = {
+
+ enable = mkEnableOption "TiddlyWiki nodejs server";
+
+ listenOptions = mkOption {
+ type = types.attrs;
+ default = {};
+ example = {
+ credentials = "../credentials.csv";
+ readers="(authenticated)";
+ port = 3456;
+ };
+ description = ''
+ Parameters passed to <literal>--listen</literal> command.
+ Refer to <link xlink:href="https://tiddlywiki.com/#WebServer"/>
+ for details on supported values.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd = {
+ services.tiddlywiki = {
+ description = "TiddlyWiki nodejs server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ Type = "simple";
+ Restart = "on-failure";
+ DynamicUser = true;
+ StateDirectory = name;
+ ExecStartPre = "-${exe} ${dataDir} --init server";
+ ExecStart = "${exe} ${dataDir} --listen ${listenParams}";
+ };
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/tzupdate.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/tzupdate.nix
new file mode 100644
index 000000000000..eac1e1112a5a
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/uhub.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/uhub.nix
new file mode 100644
index 000000000000..d1b388310280
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/uhub.nix
@@ -0,0 +1,180 @@
+{ 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.str;
+ 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.path;
+ 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.str;
+ 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.uhub.uid = config.ids.uids.uhub;
+ groups.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";
+ };
+ };
+ };
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/weechat.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/weechat.nix
new file mode 100644
index 000000000000..c6ff540ea12f
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/weechat.xml b/infra/libkookie/nixpkgs/nixos/modules/services/misc/weechat.xml
new file mode 100644
index 000000000000..7255edfb9da3
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/xmr-stak.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/xmr-stak.nix
new file mode 100644
index 000000000000..a87878c31e0d
--- /dev/null
+++ b/infra/libkookie/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/infra/libkookie/nixpkgs/nixos/modules/services/misc/zigbee2mqtt.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/zigbee2mqtt.nix
new file mode 100644
index 000000000000..cd987eb76c76
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/zigbee2mqtt.nix
@@ -0,0 +1,99 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.zigbee2mqtt;
+
+ configJSON = pkgs.writeText "configuration.json"
+ (builtins.toJSON (recursiveUpdate defaultConfig cfg.config));
+ configFile = pkgs.runCommand "configuration.yaml" { preferLocalBuild = true; } ''
+ ${pkgs.remarshal}/bin/json2yaml -i ${configJSON} -o $out
+ '';
+
+ # the default config contains all required settings,
+ # so the service starts up without crashing.
+ defaultConfig = {
+ homeassistant = false;
+ permit_join = false;
+ mqtt = {
+ base_topic = "zigbee2mqtt";
+ server = "mqtt://localhost:1883";
+ };
+ serial.port = "/dev/ttyACM0";
+ # put device configuration into separate file because configuration.yaml
+ # is copied from the store on startup
+ devices = "devices.yaml";
+ };
+in
+{
+ meta.maintainers = with maintainers; [ sweber ];
+
+ options.services.zigbee2mqtt = {
+ enable = mkEnableOption "enable zigbee2mqtt service";
+
+ package = mkOption {
+ description = "Zigbee2mqtt package to use";
+ default = pkgs.zigbee2mqtt.override {
+ dataDir = cfg.dataDir;
+ };
+ defaultText = "pkgs.zigbee2mqtt";
+ type = types.package;
+ };
+
+ dataDir = mkOption {
+ description = "Zigbee2mqtt data directory";
+ default = "/var/lib/zigbee2mqtt";
+ type = types.path;
+ };
+
+ config = mkOption {
+ default = {};
+ type = with types; nullOr attrs;
+ example = literalExample ''
+ {
+ homeassistant = config.services.home-assistant.enable;
+ permit_join = true;
+ serial = {
+ port = "/dev/ttyACM1";
+ };
+ }
+ '';
+ description = ''
+ Your <filename>configuration.yaml</filename> as a Nix attribute set.
+ '';
+ };
+ };
+
+ config = mkIf (cfg.enable) {
+ systemd.services.zigbee2mqtt = {
+ description = "Zigbee2mqtt Service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ environment.ZIGBEE2MQTT_DATA = cfg.dataDir;
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/zigbee2mqtt";
+ User = "zigbee2mqtt";
+ WorkingDirectory = cfg.dataDir;
+ Restart = "on-failure";
+ ProtectSystem = "strict";
+ ReadWritePaths = cfg.dataDir;
+ PrivateTmp = true;
+ RemoveIPC = true;
+ };
+ preStart = ''
+ cp --no-preserve=mode ${configFile} "${cfg.dataDir}/configuration.yaml"
+ '';
+ };
+
+ users.users.zigbee2mqtt = {
+ home = cfg.dataDir;
+ createHome = true;
+ group = "zigbee2mqtt";
+ extraGroups = [ "dialout" ];
+ uid = config.ids.uids.zigbee2mqtt;
+ };
+
+ users.groups.zigbee2mqtt.gid = config.ids.gids.zigbee2mqtt;
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/misc/zoneminder.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/zoneminder.nix
new file mode 100644
index 000000000000..d9d34b7fac9b
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/misc/zoneminder.nix
@@ -0,0 +1,370 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.services.zoneminder;
+ fpm = config.services.phpfpm.pools.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 = cfg.storageDir != null;
+
+ 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=${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}
+ '';
+
+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.
+ Additionally, you will need to run `zmupdate.pl` yourself when
+ upgrading to a newer version.
+ '';
+
+ 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.
+ Not used if <literal>createLocally</literal> is set.
+ '';
+ };
+ };
+
+ 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 {
+
+ assertions = [
+ { assertion = cfg.database.createLocally -> cfg.database.username == user;
+ message = "services.zoneminder.database.username must be set to ${user} if services.zoneminder.database.createLocally is set true";
+ }
+ ];
+
+ environment.etc = {
+ "zoneminder/60-defaults.conf".source = defaultsFile;
+ "zoneminder/80-nixos.conf".source = configFile;
+ };
+
+ networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [
+ cfg.port
+ 6802 # zmtrigger
+ ];
+
+ services = {
+ fcgiwrap = lib.mkIf useNginx {
+ enable = true;
+ preforkProcesses = cfg.cameras;
+ inherit user group;
+ };
+
+ mysql = lib.mkIf cfg.database.createLocally {
+ enable = true;
+ package = lib.mkDefault pkgs.mariadb;
+ ensureDatabases = [ cfg.database.name ];
+ 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;
+
+ rewrite ^/skins/.*/css/fonts/(.*)$ /fonts/$1 permanent;
+
+ 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:${fpm.socket};
+ }
+ }
+ '';
+ };
+ };
+ };
+
+ phpfpm = lib.mkIf useNginx {
+ pools.zoneminder = {
+ inherit user group;
+ phpPackage = pkgs.php.withExtensions ({ enabled, all }: enabled ++ [ all.apcu ]);
+ phpOptions = ''
+ date.timezone = "${config.time.timeZone}"
+ '';
+ settings = lib.mapAttrs (name: lib.mkDefault) {
+ "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; {
+ inherit (zoneminder.meta) description;
+ documentation = [ "https://zoneminder.readthedocs.org/en/latest/" ];
+ path = [
+ coreutils
+ procps
+ psmisc
+ ];
+ after = [ "nginx.service" ] ++ lib.optional cfg.database.createLocally "mysql.service";
+ wantedBy = [ "multi-user.target" ];
+ restartTriggers = [ defaultsFile configFile ];
+ preStart = lib.optionalString useCustomDir ''
+ install -dm775 -o ${user} -g ${group} ${cfg.storageDir}/{${lib.concatStringsSep "," libDirs}}
+ '' + lib.optionalString cfg.database.createLocally ''
+ if ! test -e "/var/lib/${dirName}/db-created"; then
+ ${config.services.mysql.package}/bin/mysql < ${pkg}/share/zoneminder/db/zm_create.sql
+ touch "/var/lib/${dirName}/db-created"
+ fi
+
+ ${zoneminder}/bin/zmupdate.pl -nointeractive
+ '';
+ 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/infra/libkookie/nixpkgs/nixos/modules/services/misc/zookeeper.nix b/infra/libkookie/nixpkgs/nixos/modules/services/misc/zookeeper.nix
new file mode 100644
index 000000000000..f6af7c75ebae
--- /dev/null
+++ b/infra/libkookie/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 - - -"
+ "Z '${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.zookeeper = {
+ uid = config.ids.uids.zookeeper;
+ description = "Zookeeper daemon user";
+ home = cfg.dataDir;
+ };
+ };
+}