diff options
Diffstat (limited to 'nixos/modules/services')
114 files changed, 6382 insertions, 2203 deletions
diff --git a/nixos/modules/services/amqp/rabbitmq.nix b/nixos/modules/services/amqp/rabbitmq.nix index bb6fc0a104df..c6878dd67dbf 100644 --- a/nixos/modules/services/amqp/rabbitmq.nix +++ b/nixos/modules/services/amqp/rabbitmq.nix @@ -4,14 +4,18 @@ with lib; let cfg = config.services.rabbitmq; - config_file = pkgs.writeText "rabbitmq.config" cfg.config; - config_file_wo_suffix = builtins.substring 0 ((builtins.stringLength config_file) - 7) config_file; + + inherit (builtins) concatStringsSep; + + config_file_content = lib.generators.toKeyValue {} cfg.configItems; + config_file = pkgs.writeText "rabbitmq.conf" config_file_content; + + advanced_config_file = pkgs.writeText "advanced.config" cfg.config; in { ###### interface options = { services.rabbitmq = { - enable = mkOption { default = false; description = '' @@ -20,6 +24,15 @@ in { ''; }; + package = mkOption { + default = pkgs.rabbitmq-server; + type = types.package; + defaultText = "pkgs.rabbitmq-server"; + description = '' + Which rabbitmq package to use. + ''; + }; + listenAddress = mkOption { default = "127.0.0.1"; example = ""; @@ -30,6 +43,10 @@ in { <literal>guest</literal> with password <literal>guest</literal> by default, so you should delete this user if you intend to allow external access. + + Together with 'port' setting it's mostly an alias for + configItems."listeners.tcp.1" and it's left for backwards + compatibility with previous version of this module. ''; type = types.str; }; @@ -60,11 +77,29 @@ in { ''; }; + configItems = mkOption { + default = {}; + type = types.attrsOf types.str; + example = '' + { + "auth_backends.1.authn" = "rabbit_auth_backend_ldap"; + "auth_backends.1.authz" = "rabbit_auth_backend_internal"; + } + ''; + description = '' + New style config options. + + See http://www.rabbitmq.com/configure.html + ''; + }; + config = mkOption { default = ""; type = types.str; description = '' - Verbatim configuration file contents. + Verbatim advanced configuration file contents. + Prefered way is to use configItems. + See http://www.rabbitmq.com/configure.html ''; }; @@ -74,6 +109,12 @@ in { type = types.listOf types.str; description = "The names of plugins to enable"; }; + + pluginDirs = mkOption { + default = []; + type = types.listOf types.path; + description = "The list of directories containing external plugins"; + }; }; }; @@ -81,7 +122,10 @@ in { ###### implementation config = mkIf cfg.enable { - environment.systemPackages = [ pkgs.rabbitmq_server ]; + # This is needed so we will have 'rabbitmqctl' in our PATH + environment.systemPackages = [ cfg.package ]; + + services.epmd.enable = true; users.users.rabbitmq = { description = "RabbitMQ server user"; @@ -93,44 +137,54 @@ in { users.groups.rabbitmq.gid = config.ids.gids.rabbitmq; + services.rabbitmq.configItems = { + "listeners.tcp.1" = mkDefault "${cfg.listenAddress}:${toString cfg.port}"; + }; + systemd.services.rabbitmq = { description = "RabbitMQ Server"; wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; + after = [ "network.target" "epmd.socket" ]; + wants = [ "network.target" "epmd.socket" ]; - path = [ pkgs.rabbitmq_server pkgs.procps ]; + path = [ cfg.package pkgs.procps ]; environment = { RABBITMQ_MNESIA_BASE = "${cfg.dataDir}/mnesia"; - RABBITMQ_NODE_IP_ADDRESS = cfg.listenAddress; - RABBITMQ_NODE_PORT = toString cfg.port; RABBITMQ_LOGS = "-"; - RABBITMQ_SASL_LOGS = "-"; - RABBITMQ_PID_FILE = "${cfg.dataDir}/pid"; SYS_PREFIX = ""; + RABBITMQ_CONFIG_FILE = config_file; + RABBITMQ_PLUGINS_DIR = concatStringsSep ":" cfg.pluginDirs; RABBITMQ_ENABLED_PLUGINS_FILE = pkgs.writeText "enabled_plugins" '' [ ${concatStringsSep "," cfg.plugins} ]. ''; - } // optionalAttrs (cfg.config != "") { RABBITMQ_CONFIG_FILE = config_file_wo_suffix; }; + } // optionalAttrs (cfg.config != "") { RABBITMQ_ADVANCED_CONFIG_FILE = advanced_config_file; }; serviceConfig = { - ExecStart = "${pkgs.rabbitmq_server}/sbin/rabbitmq-server"; - ExecStop = "${pkgs.rabbitmq_server}/sbin/rabbitmqctl stop"; + PermissionsStartOnly = true; # preStart must be run as root + ExecStart = "${cfg.package}/sbin/rabbitmq-server"; + ExecStop = "${cfg.package}/sbin/rabbitmqctl shutdown"; User = "rabbitmq"; Group = "rabbitmq"; WorkingDirectory = cfg.dataDir; + Type = "notify"; + NotifyAccess = "all"; + UMask = "0027"; + LimitNOFILE = "100000"; + Restart = "on-failure"; + RestartSec = "10"; + TimeoutStartSec = "3600"; }; - postStart = '' - rabbitmqctl wait ${cfg.dataDir}/pid - ''; - preStart = '' ${optionalString (cfg.cookie != "") '' echo -n ${cfg.cookie} > ${cfg.dataDir}/.erlang.cookie + chown rabbitmq:rabbitmq ${cfg.dataDir}/.erlang.cookie chmod 600 ${cfg.dataDir}/.erlang.cookie ''} + mkdir -p /var/log/rabbitmq + chown rabbitmq:rabbitmq /var/log/rabbitmq ''; }; diff --git a/nixos/modules/services/cluster/kubernetes/default.nix b/nixos/modules/services/cluster/kubernetes/default.nix index aeaa6bd66c99..e63d91eb9aca 100644 --- a/nixos/modules/services/cluster/kubernetes/default.nix +++ b/nixos/modules/services/cluster/kubernetes/default.nix @@ -622,13 +622,6 @@ in { type = types.bool; }; - # TODO: remove this deprecated flag - cadvisorPort = mkOption { - description = "Kubernetes kubelet local cadvisor port."; - default = 4194; - type = types.int; - }; - clusterDns = mkOption { description = "Use alternative DNS."; default = "10.1.0.1"; @@ -862,7 +855,6 @@ in { --hostname-override=${cfg.kubelet.hostname} \ --allow-privileged=${boolToString cfg.kubelet.allowPrivileged} \ --root-dir=${cfg.dataDir} \ - --cadvisor_port=${toString cfg.kubelet.cadvisorPort} \ ${optionalString (cfg.kubelet.clusterDns != "") "--cluster-dns=${cfg.kubelet.clusterDns}"} \ ${optionalString (cfg.kubelet.clusterDomain != "") diff --git a/nixos/modules/services/computing/slurm/slurm.nix b/nixos/modules/services/computing/slurm/slurm.nix index 1e1c5bc9f035..09174ed39f5e 100644 --- a/nixos/modules/services/computing/slurm/slurm.nix +++ b/nixos/modules/services/computing/slurm/slurm.nix @@ -8,6 +8,7 @@ let # configuration file can be generated by http://slurm.schedmd.com/configurator.html configFile = pkgs.writeTextDir "slurm.conf" '' + ClusterName=${cfg.clusterName} ${optionalString (cfg.controlMachine != null) ''controlMachine=${cfg.controlMachine}''} ${optionalString (cfg.controlAddr != null) ''controlAddr=${cfg.controlAddr}''} ${optionalString (cfg.nodeName != null) ''nodeName=${cfg.nodeName}''} @@ -105,6 +106,15 @@ in ''; }; + clusterName = mkOption { + type = types.str; + default = "default"; + example = "myCluster"; + description = '' + Necessary to distinguish accounting records in a multi-cluster environment. + ''; + }; + nodeName = mkOption { type = types.nullOr types.str; default = null; diff --git a/nixos/modules/services/databases/foundationdb.xml b/nixos/modules/services/databases/foundationdb.xml index f4090c492764..bf4b644c9b86 100644 --- a/nixos/modules/services/databases/foundationdb.xml +++ b/nixos/modules/services/databases/foundationdb.xml @@ -3,42 +3,50 @@ xmlns:xi="http://www.w3.org/2001/XInclude" version="5.0" xml:id="module-services-foundationdb"> - -<title>FoundationDB</title> - -<para><emphasis>Source:</emphasis> <filename>modules/services/databases/foundationdb.nix</filename></para> - -<para><emphasis>Upstream documentation:</emphasis> <link xlink:href="https://apple.github.io/foundationdb/"/></para> - -<para><emphasis>Maintainer:</emphasis> Austin Seipp</para> - -<para><emphasis>Available version(s):</emphasis> 5.1.x, 5.2.x, 6.0.x</para> - -<para>FoundationDB (or "FDB") is an open source, distributed, transactional -key-value store.</para> - -<section><title>Configuring and basic setup</title> - -<para>To enable FoundationDB, add the following to your -<filename>configuration.nix</filename>: - + <title>FoundationDB</title> + <para> + <emphasis>Source:</emphasis> + <filename>modules/services/databases/foundationdb.nix</filename> + </para> + <para> + <emphasis>Upstream documentation:</emphasis> + <link xlink:href="https://apple.github.io/foundationdb/"/> + </para> + <para> + <emphasis>Maintainer:</emphasis> Austin Seipp + </para> + <para> + <emphasis>Available version(s):</emphasis> 5.1.x, 5.2.x, 6.0.x + </para> + <para> + FoundationDB (or "FDB") is an open source, distributed, transactional + key-value store. + </para> + <section xml:id="module-services-foundationdb-configuring"> + <title>Configuring and basic setup</title> + + <para> + To enable FoundationDB, add the following to your + <filename>configuration.nix</filename>: <programlisting> services.foundationdb.enable = true; services.foundationdb.package = pkgs.foundationdb52; # FoundationDB 5.2.x </programlisting> -</para> - -<para>The <option>services.foundationdb.package</option> option is required, -and must always be specified. Due to the fact FoundationDB network protocols and -on-disk storage formats may change between (major) versions, and upgrades must -be explicitly handled by the user, you must always manually specify this -yourself so that the NixOS module will use the proper version. Note that minor, -bugfix releases are always compatible.</para> - -<para>After running <command>nixos-rebuild</command>, you can verify whether -FoundationDB is running by executing <command>fdbcli</command> (which is added -to <option>environment.systemPackages</option>): - + </para> + + <para> + The <option>services.foundationdb.package</option> option is required, and + must always be specified. Due to the fact FoundationDB network protocols and + on-disk storage formats may change between (major) versions, and upgrades + must be explicitly handled by the user, you must always manually specify + this yourself so that the NixOS module will use the proper version. Note + that minor, bugfix releases are always compatible. + </para> + + <para> + After running <command>nixos-rebuild</command>, you can verify whether + FoundationDB is running by executing <command>fdbcli</command> (which is + added to <option>environment.systemPackages</option>): <programlisting> $ sudo -u foundationdb fdbcli Using cluster file `/etc/foundationdb/fdb.cluster'. @@ -66,14 +74,14 @@ Cluster: fdb> </programlisting> -</para> - -<para>You can also write programs using the available client libraries. -For example, the following Python program can be run in order to grab the -cluster status, as a quick example. (This example uses -<command>nix-shell</command> shebang support to automatically supply the -necessary Python modules). - + </para> + + <para> + You can also write programs using the available client libraries. For + example, the following Python program can be run in order to grab the + cluster status, as a quick example. (This example uses + <command>nix-shell</command> shebang support to automatically supply the + necessary Python modules). <programlisting> a@link> cat fdb-status.py #! /usr/bin/env nix-shell @@ -100,255 +108,336 @@ a@link> ./fdb-status.py FoundationDB available: True a@link> </programlisting> -</para> - -<para>FoundationDB is run under the <command>foundationdb</command> user and -group by default, but this may be changed in the NixOS configuration. The -systemd unit <command>foundationdb.service</command> controls the -<command>fdbmonitor</command> process.</para> - -<para>By default, the NixOS module for FoundationDB creates a single -SSD-storage based database for development and basic usage. This storage engine -is designed for SSDs and will perform poorly on HDDs; however it can handle far -more data than the alternative "memory" engine and is a better default choice -for most deployments. (Note that you can change the storage backend on-the-fly -for a given FoundationDB cluster using <command>fdbcli</command>.)</para> - -<para>Furthermore, only 1 server process and 1 backup agent are started in the -default configuration. See below for more on scaling to increase this.</para> - -<para>FoundationDB stores all data for all server processes under -<filename>/var/lib/foundationdb</filename>. You can override this using -<option>services.foundationdb.dataDir</option>, e.g. - + </para> + + <para> + FoundationDB is run under the <command>foundationdb</command> user and group + by default, but this may be changed in the NixOS configuration. The systemd + unit <command>foundationdb.service</command> controls the + <command>fdbmonitor</command> process. + </para> + + <para> + By default, the NixOS module for FoundationDB creates a single SSD-storage + based database for development and basic usage. This storage engine is + designed for SSDs and will perform poorly on HDDs; however it can handle far + more data than the alternative "memory" engine and is a better default + choice for most deployments. (Note that you can change the storage backend + on-the-fly for a given FoundationDB cluster using + <command>fdbcli</command>.) + </para> + + <para> + Furthermore, only 1 server process and 1 backup agent are started in the + default configuration. See below for more on scaling to increase this. + </para> + + <para> + FoundationDB stores all data for all server processes under + <filename>/var/lib/foundationdb</filename>. You can override this using + <option>services.foundationdb.dataDir</option>, e.g. <programlisting> services.foundationdb.dataDir = "/data/fdb"; </programlisting> - -</para> - -<para>Similarly, logs are stored under -<filename>/var/log/foundationdb</filename> by default, and there is a -corresponding <option>services.foundationdb.logDir</option> as well.</para> - -</section> - -<section><title>Scaling processes and backup agents</title> - -<para>Scaling the number of server processes is quite easy; simply specify -<option>services.foundationdb.serverProcesses</option> to be the number of -FoundationDB worker processes that should be started on the machine.</para> - -<para>FoundationDB worker processes typically require 4GB of RAM per-process at -minimum for good performance, so this option is set to 1 by default since the -maximum amount of RAM is unknown. You're advised to abide by this restriction, -so pick a number of processes so that each has 4GB or more.</para> - -<para>A similar option exists in order to scale backup agent processes, -<option>services.foundationdb.backupProcesses</option>. Backup agents are not -as performance/RAM sensitive, so feel free to experiment with the number of -available backup processes.</para> - -</section> - -<section><title>Clustering</title> - -<para>FoundationDB on NixOS works similarly to other Linux systems, so this -section will be brief. Please refer to the full FoundationDB documentation for -more on clustering.</para> - -<para>FoundationDB organizes clusters using a set of -<emphasis>coordinators</emphasis>, which are just specially-designated worker -processes. By default, every installation of FoundationDB on NixOS will start -as its own individual cluster, with a single coordinator: the first worker -process on <command>localhost</command>.</para> - -<para>Coordinators are specified globally using the -<command>/etc/foundationdb/fdb.cluster</command> file, which all servers and -client applications will use to find and join coordinators. Note that this file -<emphasis>can not</emphasis> be managed by NixOS so easily: FoundationDB is -designed so that it will rewrite the file at runtime for all clients and nodes -when cluster coordinators change, with clients transparently handling this -without intervention. It is fundamentally a mutable file, and you should not -try to manage it in any way in NixOS.</para> - -<para>When dealing with a cluster, there are two main things you want to -do:</para> - -<itemizedlist> - <listitem><para>Add a node to the cluster for storage/compute.</para></listitem> - <listitem><para>Promote an ordinary worker to a coordinator.</para></listitem> -</itemizedlist> - -<para>A node must already be a member of the cluster in order to properly be -promoted to a coordinator, so you must always add it first if you wish to -promote it.</para> - -<para>To add a machine to a FoundationDB cluster:</para> - -<itemizedlist> - <listitem><para>Choose one of the servers to start as the initial coordinator. - </para></listitem> - <listitem><para>Copy the <command>/etc/foundationdb/fdb.cluster</command> file - from this server to all the other servers. Restart FoundationDB on all of - these other servers, so they join the cluster.</para></listitem> - <listitem><para>All of these servers are now connected and working together - in the cluster, under the chosen coordinator.</para></listitem> -</itemizedlist> - -<para>At this point, you can add as many nodes as you want by just repeating -the above steps. By default there will still be a single coordinator: you can -use <command>fdbcli</command> to change this and add new coordinators.</para> - -<para>As a convenience, FoundationDB can automatically assign coordinators -based on the redundancy mode you wish to achieve for the cluster. Once all the -nodes have been joined, simply set the replication policy, and then issue the -<command>coordinators auto</command> command</para> - -<para>For example, assuming we have 3 nodes available, we can enable double -redundancy mode, then auto-select coordinators. For double redundancy, 3 -coordinators is ideal: therefore FoundationDB will make -<emphasis>every</emphasis> node a coordinator automatically:</para> + </para> + + <para> + Similarly, logs are stored under <filename>/var/log/foundationdb</filename> + by default, and there is a corresponding + <option>services.foundationdb.logDir</option> as well. + </para> + </section> + <section xml:id="module-services-foundationdb-scaling"> + <title>Scaling processes and backup agents</title> + + <para> + Scaling the number of server processes is quite easy; simply specify + <option>services.foundationdb.serverProcesses</option> to be the number of + FoundationDB worker processes that should be started on the machine. + </para> + + <para> + FoundationDB worker processes typically require 4GB of RAM per-process at + minimum for good performance, so this option is set to 1 by default since + the maximum amount of RAM is unknown. You're advised to abide by this + restriction, so pick a number of processes so that each has 4GB or more. + </para> + + <para> + A similar option exists in order to scale backup agent processes, + <option>services.foundationdb.backupProcesses</option>. Backup agents are + not as performance/RAM sensitive, so feel free to experiment with the number + of available backup processes. + </para> + </section> + <section xml:id="module-services-foundationdb-clustering"> + <title>Clustering</title> + + <para> + FoundationDB on NixOS works similarly to other Linux systems, so this + section will be brief. Please refer to the full FoundationDB documentation + for more on clustering. + </para> + + <para> + FoundationDB organizes clusters using a set of + <emphasis>coordinators</emphasis>, which are just specially-designated + worker processes. By default, every installation of FoundationDB on NixOS + will start as its own individual cluster, with a single coordinator: the + first worker process on <command>localhost</command>. + </para> + + <para> + Coordinators are specified globally using the + <command>/etc/foundationdb/fdb.cluster</command> file, which all servers and + client applications will use to find and join coordinators. Note that this + file <emphasis>can not</emphasis> be managed by NixOS so easily: + FoundationDB is designed so that it will rewrite the file at runtime for all + clients and nodes when cluster coordinators change, with clients + transparently handling this without intervention. It is fundamentally a + mutable file, and you should not try to manage it in any way in NixOS. + </para> + + <para> + When dealing with a cluster, there are two main things you want to do: + </para> + + <itemizedlist> + <listitem> + <para> + Add a node to the cluster for storage/compute. + </para> + </listitem> + <listitem> + <para> + Promote an ordinary worker to a coordinator. + </para> + </listitem> + </itemizedlist> + + <para> + A node must already be a member of the cluster in order to properly be + promoted to a coordinator, so you must always add it first if you wish to + promote it. + </para> + + <para> + To add a machine to a FoundationDB cluster: + </para> + + <itemizedlist> + <listitem> + <para> + Choose one of the servers to start as the initial coordinator. + </para> + </listitem> + <listitem> + <para> + Copy the <command>/etc/foundationdb/fdb.cluster</command> file from this + server to all the other servers. Restart FoundationDB on all of these + other servers, so they join the cluster. + </para> + </listitem> + <listitem> + <para> + All of these servers are now connected and working together in the + cluster, under the chosen coordinator. + </para> + </listitem> + </itemizedlist> + + <para> + At this point, you can add as many nodes as you want by just repeating the + above steps. By default there will still be a single coordinator: you can + use <command>fdbcli</command> to change this and add new coordinators. + </para> + + <para> + As a convenience, FoundationDB can automatically assign coordinators based + on the redundancy mode you wish to achieve for the cluster. Once all the + nodes have been joined, simply set the replication policy, and then issue + the <command>coordinators auto</command> command + </para> + + <para> + For example, assuming we have 3 nodes available, we can enable double + redundancy mode, then auto-select coordinators. For double redundancy, 3 + coordinators is ideal: therefore FoundationDB will make + <emphasis>every</emphasis> node a coordinator automatically: + </para> <programlisting> fdbcli> configure double ssd fdbcli> coordinators auto </programlisting> -<para>This will transparently update all the servers within seconds, and -appropriately rewrite the <command>fdb.cluster</command> file, as well as -informing all client processes to do the same.</para> - -</section> - -<section><title>Client connectivity</title> - -<para>By default, all clients must use the current -<command>fdb.cluster</command> file to access a given FoundationDB cluster. -This file is located by default in -<command>/etc/foundationdb/fdb.cluster</command> on all machines with the -FoundationDB service enabled, so you may copy the active one from your cluster -to a new node in order to connect, if it is not part of the cluster.</para> - -</section> - -<section><title>Client authorization and TLS</title> - -<para>By default, any user who can connect to a FoundationDB process with the -correct cluster configuration can access anything. FoundationDB uses a -pluggable design to transport security, and out of the box it supports a -LibreSSL-based plugin for TLS support. This plugin not only does in-flight -encryption, but also performs client authorization based on the given -endpoint's certificate chain. For example, a FoundationDB server may be -configured to only accept client connections over TLS, where the client TLS -certificate is from organization <emphasis>Acme Co</emphasis> in the -<emphasis>Research and Development</emphasis> unit.</para> - -<para>Configuring TLS with FoundationDB is done using the -<option>services.foundationdb.tls</option> options in order to control the peer -verification string, as well as the certificate and its private key.</para> - -<para>Note that the certificate and its private key must be accessible to the -FoundationDB user account that the server runs under. These files are also NOT -managed by NixOS, as putting them into the store may reveal private -information.</para> - -<para>After you have a key and certificate file in place, it is not enough to -simply set the NixOS module options -- you must also configure the -<command>fdb.cluster</command> file to specify that a given set of coordinators -use TLS. This is as simple as adding the suffix <command>:tls</command> to your -cluster coordinator configuration, after the port number. For example, assuming -you have a coordinator on localhost with the default configuration, simply -specifying:</para> + <para> + This will transparently update all the servers within seconds, and + appropriately rewrite the <command>fdb.cluster</command> file, as well as + informing all client processes to do the same. + </para> + </section> + <section xml:id="module-services-foundationdb-connectivity"> + <title>Client connectivity</title> + + <para> + By default, all clients must use the current <command>fdb.cluster</command> + file to access a given FoundationDB cluster. This file is located by default + in <command>/etc/foundationdb/fdb.cluster</command> on all machines with the + FoundationDB service enabled, so you may copy the active one from your + cluster to a new node in order to connect, if it is not part of the cluster. + </para> + </section> + <section xml:id="module-services-foundationdb-authorization"> + <title>Client authorization and TLS</title> + + <para> + By default, any user who can connect to a FoundationDB process with the + correct cluster configuration can access anything. FoundationDB uses a + pluggable design to transport security, and out of the box it supports a + LibreSSL-based plugin for TLS support. This plugin not only does in-flight + encryption, but also performs client authorization based on the given + endpoint's certificate chain. For example, a FoundationDB server may be + configured to only accept client connections over TLS, where the client TLS + certificate is from organization <emphasis>Acme Co</emphasis> in the + <emphasis>Research and Development</emphasis> unit. + </para> + + <para> + Configuring TLS with FoundationDB is done using the + <option>services.foundationdb.tls</option> options in order to control the + peer verification string, as well as the certificate and its private key. + </para> + + <para> + Note that the certificate and its private key must be accessible to the + FoundationDB user account that the server runs under. These files are also + NOT managed by NixOS, as putting them into the store may reveal private + information. + </para> + + <para> + After you have a key and certificate file in place, it is not enough to + simply set the NixOS module options -- you must also configure the + <command>fdb.cluster</command> file to specify that a given set of + coordinators use TLS. This is as simple as adding the suffix + <command>:tls</command> to your cluster coordinator configuration, after the + port number. For example, assuming you have a coordinator on localhost with + the default configuration, simply specifying: + </para> <programlisting> XXXXXX:XXXXXX@127.0.0.1:4500:tls </programlisting> -<para>will configure all clients and server processes to use TLS from now -on.</para> - -</section> - -<section><title>Backups and Disaster Recovery</title> - -<para>The usual rules for doing FoundationDB backups apply on NixOS as written -in the FoundationDB manual. However, one important difference is the security -profile for NixOS: by default, the <command>foundationdb</command> systemd unit -uses <emphasis>Linux namespaces</emphasis> to restrict write access to the -system, except for the log directory, data directory, and the -<command>/etc/foundationdb/</command> directory. This is enforced by default -and cannot be disabled.</para> - -<para>However, a side effect of this is that the <command>fdbbackup</command> -command doesn't work properly for local filesystem backups: FoundationDB uses a -server process alongside the database processes to perform backups and copy the -backups to the filesystem. As a result, this process is put under the -restricted namespaces above: the backup process can only write to a limited -number of paths.</para> - -<para>In order to allow flexible backup locations on local disks, the -FoundationDB NixOS module supports a -<option>services.foundationdb.extraReadWritePaths</option> option. This option -takes a list of paths, and adds them to the systemd unit, allowing the -processes inside the service to write (and read) the specified -directories.</para> - -<para>For example, to create backups in <command>/opt/fdb-backups</command>, -first set up the paths in the module options:</para> + <para> + will configure all clients and server processes to use TLS from now on. + </para> + </section> + <section xml:id="module-services-foundationdb-disaster-recovery"> + <title>Backups and Disaster Recovery</title> + + <para> + The usual rules for doing FoundationDB backups apply on NixOS as written in + the FoundationDB manual. However, one important difference is the security + profile for NixOS: by default, the <command>foundationdb</command> systemd + unit uses <emphasis>Linux namespaces</emphasis> to restrict write access to + the system, except for the log directory, data directory, and the + <command>/etc/foundationdb/</command> directory. This is enforced by default + and cannot be disabled. + </para> + + <para> + However, a side effect of this is that the <command>fdbbackup</command> + command doesn't work properly for local filesystem backups: FoundationDB + uses a server process alongside the database processes to perform backups + and copy the backups to the filesystem. As a result, this process is put + under the restricted namespaces above: the backup process can only write to + a limited number of paths. + </para> + + <para> + In order to allow flexible backup locations on local disks, the FoundationDB + NixOS module supports a + <option>services.foundationdb.extraReadWritePaths</option> option. This + option takes a list of paths, and adds them to the systemd unit, allowing + the processes inside the service to write (and read) the specified + directories. + </para> + + <para> + For example, to create backups in <command>/opt/fdb-backups</command>, first + set up the paths in the module options: + </para> <programlisting> services.foundationdb.extraReadWritePaths = [ "/opt/fdb-backups" ]; </programlisting> -<para>Restart the FoundationDB service, and it will now be able to write to -this directory (even if it does not yet exist.) Note: this path -<emphasis>must</emphasis> exist before restarting the unit. Otherwise, systemd -will not include it in the private FoundationDB namespace (and it will not add -it dynamically at runtime).</para> + <para> + Restart the FoundationDB service, and it will now be able to write to this + directory (even if it does not yet exist.) Note: this path + <emphasis>must</emphasis> exist before restarting the unit. Otherwise, + systemd will not include it in the private FoundationDB namespace (and it + will not add it dynamically at runtime). + </para> -<para>You can now perform a backup:</para> + <para> + You can now perform a backup: + </para> <programlisting> $ sudo -u foundationdb fdbbackup start -t default -d file:///opt/fdb-backups $ sudo -u foundationdb fdbbackup status -t default </programlisting> - -</section> - -<section><title>Known limitations</title> - -<para>The FoundationDB setup for NixOS should currently be considered beta. -FoundationDB is not new software, but the NixOS compilation and integration has -only undergone fairly basic testing of all the available functionality.</para> - -<itemizedlist> - <listitem><para>There is no way to specify individual parameters for - individual <command>fdbserver</command> processes. Currently, all server - processes inherit all the global <command>fdbmonitor</command> settings. - </para></listitem> - <listitem><para>Ruby bindings are not currently installed.</para></listitem> - <listitem><para>Go bindings are not currently installed.</para></listitem> -</itemizedlist> - -</section> - -<section><title>Options</title> - -<para>NixOS's FoundationDB module allows you to configure all of the most -relevant configuration options for <command>fdbmonitor</command>, matching it -quite closely. A complete list of options for the FoundationDB module may be -found <link linkend="opt-services.foundationdb.enable">here</link>. You should -also read the FoundationDB documentation as well.</para> - -</section> - -<section><title>Full documentation</title> - -<para>FoundationDB is a complex piece of software, and requires careful -administration to properly use. Full documentation for administration can be -found here: <link xlink:href="https://apple.github.io/foundationdb/"/>.</para> - -</section> - + </section> + <section xml:id="module-services-foundationdb-limitations"> + <title>Known limitations</title> + + <para> + The FoundationDB setup for NixOS should currently be considered beta. + FoundationDB is not new software, but the NixOS compilation and integration + has only undergone fairly basic testing of all the available functionality. + </para> + + <itemizedlist> + <listitem> + <para> + There is no way to specify individual parameters for individual + <command>fdbserver</command> processes. Currently, all server processes + inherit all the global <command>fdbmonitor</command> settings. + </para> + </listitem> + <listitem> + <para> + Ruby bindings are not currently installed. + </para> + </listitem> + <listitem> + <para> + Go bindings are not currently installed. + </para> + </listitem> + </itemizedlist> + </section> + <section xml:id="module-services-foundationdb-options"> + <title>Options</title> + + <para> + NixOS's FoundationDB module allows you to configure all of the most relevant + configuration options for <command>fdbmonitor</command>, matching it quite + closely. A complete list of options for the FoundationDB module may be found + <link linkend="opt-services.foundationdb.enable">here</link>. You should + also read the FoundationDB documentation as well. + </para> + </section> + <section xml:id="module-services-foundationdb-full-docs"> + <title>Full documentation</title> + + <para> + FoundationDB is a complex piece of software, and requires careful + administration to properly use. Full documentation for administration can be + found here: <link xlink:href="https://apple.github.io/foundationdb/"/>. + </para> + </section> </chapter> diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix index f59fb1c81772..de2a757196a5 100644 --- a/nixos/modules/services/databases/postgresql.nix +++ b/nixos/modules/services/databases/postgresql.nix @@ -188,6 +188,8 @@ in uid = config.ids.uids.postgres; group = "postgres"; description = "PostgreSQL server user"; + home = "${cfg.dataDir}"; + useDefaultShell = true; }; users.groups.postgres.gid = config.ids.gids.postgres; diff --git a/nixos/modules/services/databases/postgresql.xml b/nixos/modules/services/databases/postgresql.xml index 98a631c0cd32..f89f0d653164 100644 --- a/nixos/modules/services/databases/postgresql.xml +++ b/nixos/modules/services/databases/postgresql.xml @@ -3,36 +3,39 @@ xmlns:xi="http://www.w3.org/2001/XInclude" version="5.0" xml:id="module-postgresql"> - -<title>PostgreSQL</title> - + <title>PostgreSQL</title> <!-- FIXME: render nicely --> - <!-- FIXME: source can be added automatically --> -<para><emphasis>Source:</emphasis> <filename>modules/services/databases/postgresql.nix</filename></para> - -<para><emphasis>Upstream documentation:</emphasis> <link xlink:href="http://www.postgresql.org/docs/"/></para> - + <para> + <emphasis>Source:</emphasis> + <filename>modules/services/databases/postgresql.nix</filename> + </para> + <para> + <emphasis>Upstream documentation:</emphasis> + <link xlink:href="http://www.postgresql.org/docs/"/> + </para> <!-- FIXME: more stuff, like maintainer? --> - -<para>PostgreSQL is an advanced, free relational database.<!-- MORE --></para> - -<section><title>Configuring</title> - -<para>To enable PostgreSQL, add the following to your -<filename>configuration.nix</filename>: - + <para> + PostgreSQL is an advanced, free relational database. +<!-- MORE --> + </para> + <section xml:id="module-services-postgres-configuring"> + <title>Configuring</title> + + <para> + To enable PostgreSQL, add the following to your + <filename>configuration.nix</filename>: <programlisting> <xref linkend="opt-services.postgresql.enable"/> = true; <xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql94; </programlisting> - -Note that you are required to specify the desired version of -PostgreSQL (e.g. <literal>pkgs.postgresql94</literal>). Since -upgrading your PostgreSQL version requires a database dump and reload -(see below), NixOS cannot provide a default value for -<xref linkend="opt-services.postgresql.package"/> such as the most recent -release of PostgreSQL.</para> + Note that you are required to specify the desired version of PostgreSQL + (e.g. <literal>pkgs.postgresql94</literal>). Since upgrading your PostgreSQL + version requires a database dump and reload (see below), NixOS cannot + provide a default value for + <xref linkend="opt-services.postgresql.package"/> such as the most recent + release of PostgreSQL. + </para> <!-- <para>After running <command>nixos-rebuild</command>, you can verify @@ -47,31 +50,28 @@ alice=> </screen> --> -<para>By default, PostgreSQL stores its databases in -<filename>/var/db/postgresql</filename>. You can override this using -<xref linkend="opt-services.postgresql.dataDir"/>, e.g. - + <para> + By default, PostgreSQL stores its databases in + <filename>/var/db/postgresql</filename>. You can override this using + <xref linkend="opt-services.postgresql.dataDir"/>, e.g. <programlisting> <xref linkend="opt-services.postgresql.dataDir"/> = "/data/postgresql"; </programlisting> - -</para> - -</section> - - -<section><title>Upgrading</title> - -<para>FIXME: document dump/upgrade/load cycle.</para> - -</section> - - -<section><title>Options</title> - - <para>A complete list of options for the PostgreSQL module may be found <link linkend="opt-services.postgresql.enable">here</link>.</para> - -</section> - - + </para> + </section> + <section xml:id="module-services-postgres-upgrading"> + <title>Upgrading</title> + + <para> + FIXME: document dump/upgrade/load cycle. + </para> + </section> + <section xml:id="module-services-postgres-options"> + <title>Options</title> + + <para> + A complete list of options for the PostgreSQL module may be found + <link linkend="opt-services.postgresql.enable">here</link>. + </para> + </section> </chapter> diff --git a/nixos/modules/services/desktops/accountsservice.nix b/nixos/modules/services/desktops/accountsservice.nix index 2a7450669ea0..933b9da2c83c 100644 --- a/nixos/modules/services/desktops/accountsservice.nix +++ b/nixos/modules/services/desktops/accountsservice.nix @@ -32,15 +32,21 @@ with lib; environment.systemPackages = [ pkgs.accountsservice ]; + # Accounts daemon looks for dbus interfaces in $XDG_DATA_DIRS/accountsservice + environment.pathsToLink = [ "/share/accountsservice" ]; + services.dbus.packages = [ pkgs.accountsservice ]; systemd.packages = [ pkgs.accountsservice ]; - systemd.services.accounts-daemon= { + systemd.services.accounts-daemon = { wantedBy = [ "graphical.target" ]; - } // (mkIf (!config.users.mutableUsers) { + # Accounts daemon looks for dbus interfaces in $XDG_DATA_DIRS/accountsservice + environment.XDG_DATA_DIRS = "${config.system.path}/share"; + + } // (optionalAttrs (!config.users.mutableUsers) { environment.NIXOS_USERS_PURE = "true"; }); }; diff --git a/nixos/modules/services/desktops/flatpak.xml b/nixos/modules/services/desktops/flatpak.xml index d9c8b711c450..8045d5fa14f8 100644 --- a/nixos/modules/services/desktops/flatpak.xml +++ b/nixos/modules/services/desktops/flatpak.xml @@ -3,51 +3,54 @@ xmlns:xi="http://www.w3.org/2001/XInclude" version="5.0" xml:id="module-services-flatpak"> - -<title>Flatpak</title> - -<para><emphasis>Source:</emphasis> <filename>modules/services/desktop/flatpak.nix</filename></para> - -<para><emphasis>Upstream documentation:</emphasis> <link xlink:href="https://github.com/flatpak/flatpak/wiki"/></para> - -<para>Flatpak is a system for building, distributing, and running sandboxed desktop applications on Linux.</para> - -<para> - To enable Flatpak, add the following to your <filename>configuration.nix</filename>: - - <programlisting> + <title>Flatpak</title> + <para> + <emphasis>Source:</emphasis> + <filename>modules/services/desktop/flatpak.nix</filename> + </para> + <para> + <emphasis>Upstream documentation:</emphasis> + <link xlink:href="https://github.com/flatpak/flatpak/wiki"/> + </para> + <para> + Flatpak is a system for building, distributing, and running sandboxed desktop + applications on Linux. + </para> + <para> + To enable Flatpak, add the following to your + <filename>configuration.nix</filename>: +<programlisting> <xref linkend="opt-services.flatpak.enable"/> = true; </programlisting> -</para> - -<para> - For the sandboxed apps to work correctly, desktop integration portals need to be installed. If you run GNOME, this will be handled automatically for you; in other cases, you will need to add something like the following to your <filename>configuration.nix</filename>: - - <programlisting> + </para> + <para> + For the sandboxed apps to work correctly, desktop integration portals need to + be installed. If you run GNOME, this will be handled automatically for you; + in other cases, you will need to add something like the following to your + <filename>configuration.nix</filename>: +<programlisting> <xref linkend="opt-services.flatpak.extraPortals"/> = [ pkgs.xdg-desktop-portal-gtk ]; </programlisting> -</para> - -<para> - Then, you will need to add a repository, for example, <link xlink:href="https://github.com/flatpak/flatpak/wiki">Flathub</link>, either using the following commands: - - <programlisting> + </para> + <para> + Then, you will need to add a repository, for example, + <link xlink:href="https://github.com/flatpak/flatpak/wiki">Flathub</link>, + either using the following commands: +<programlisting> flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo flatpak update </programlisting> - - or by opening the <link xlink:href="https://flathub.org/repo/flathub.flatpakrepo">repository file</link> in GNOME Software. -</para> - -<para> + or by opening the + <link xlink:href="https://flathub.org/repo/flathub.flatpakrepo">repository + file</link> in GNOME Software. + </para> + <para> Finally, you can search and install programs: - - <programlisting> +<programlisting> flatpak search bustle flatpak install flathub org.freedesktop.Bustle flatpak run org.freedesktop.Bustle </programlisting> - Again, GNOME Software offers graphical interface for these tasks. -</para> + </para> </chapter> diff --git a/nixos/modules/services/desktops/geoclue2.nix b/nixos/modules/services/desktops/geoclue2.nix index c5a000d5c6a7..dafb0af20756 100644 --- a/nixos/modules/services/desktops/geoclue2.nix +++ b/nixos/modules/services/desktops/geoclue2.nix @@ -4,6 +4,10 @@ with lib; +let + # the demo agent isn't built by default, but we need it here + package = pkgs.geoclue2.override { withDemoAgent = config.services.geoclue2.enableDemoAgent; }; +in { ###### interface @@ -21,21 +25,42 @@ with lib; ''; }; + enableDemoAgent = mkOption { + type = types.bool; + default = true; + description = '' + Whether to use the GeoClue demo agent. This should be + overridden by desktop environments that provide their own + agent. + ''; + }; + }; }; ###### implementation - config = mkIf config.services.geoclue2.enable { - environment.systemPackages = [ pkgs.geoclue2 ]; - - services.dbus.packages = [ pkgs.geoclue2 ]; - - systemd.packages = [ pkgs.geoclue2 ]; - + environment.systemPackages = [ package ]; + + services.dbus.packages = [ package ]; + + systemd.packages = [ package ]; + + # this needs to run as a user service, since it's associated with the + # user who is making the requests + systemd.user.services = mkIf config.services.geoclue2.enableDemoAgent { + "geoclue-agent" = { + description = "Geoclue agent"; + script = "${package}/libexec/geoclue-2.0/demos/agent"; + # this should really be `partOf = [ "geoclue.service" ]`, but + # we can't be part of a system service, and the agent should + # be okay with the main service coming and going + wantedBy = [ "default.target" ]; + }; + }; }; } diff --git a/nixos/modules/services/desktops/gnome3/rygel.nix b/nixos/modules/services/desktops/gnome3/rygel.nix new file mode 100644 index 000000000000..55d5e703aa19 --- /dev/null +++ b/nixos/modules/services/desktops/gnome3/rygel.nix @@ -0,0 +1,30 @@ +# rygel service. +{ config, lib, pkgs, ... }: + +with lib; + +{ + ###### interface + options = { + services.gnome3.rygel = { + enable = mkOption { + default = false; + description = '' + Whether to enable Rygel UPnP Mediaserver. + + You will need to also allow UPnP connections in firewall, see the following <link xlink:href="https://github.com/NixOS/nixpkgs/pull/45045#issuecomment-416030795">comment</link>. + ''; + type = types.bool; + }; + }; + }; + + ###### implementation + config = mkIf config.services.gnome3.rygel.enable { + environment.systemPackages = [ pkgs.gnome3.rygel ]; + + services.dbus.packages = [ pkgs.gnome3.rygel ]; + + systemd.packages = [ pkgs.gnome3.rygel ]; + }; +} diff --git a/nixos/modules/services/desktops/profile-sync-daemon.nix b/nixos/modules/services/desktops/profile-sync-daemon.nix index e3f74df3e573..4165bb64fe46 100644 --- a/nixos/modules/services/desktops/profile-sync-daemon.nix +++ b/nixos/modules/services/desktops/profile-sync-daemon.nix @@ -4,22 +4,7 @@ with lib; let cfg = config.services.psd; - - configFile = '' - ${optionalString (cfg.users != [ ]) '' - USERS="${concatStringsSep " " cfg.users}" - ''} - - ${optionalString (cfg.browsers != [ ]) '' - BROWSERS="${concatStringsSep " " cfg.browsers}" - ''} - - ${optionalString (cfg.volatile != "") "VOLATILE=${cfg.volatile}"} - ${optionalString (cfg.daemonFile != "") "DAEMON_FILE=${cfg.daemonFile}"} - ''; - in { - options.services.psd = with types; { enable = mkOption { type = bool; @@ -28,32 +13,6 @@ in { Whether to enable the Profile Sync daemon. ''; }; - - users = mkOption { - type = listOf str; - default = [ ]; - example = [ "demo" ]; - description = '' - A list of users whose browser profiles should be sync'd to tmpfs. - ''; - }; - - browsers = mkOption { - type = listOf str; - default = [ ]; - example = [ "chromium" "firefox" ]; - description = '' - A list of browsers to sync. Available choices are: - - chromium chromium-dev conkeror.mozdev.org epiphany firefox - firefox-trunk google-chrome google-chrome-beta google-chrome-unstable - heftig-aurora icecat luakit midori opera opera-developer opera-beta - qupzilla palemoon rekonq seamonkey - - An empty list will enable all browsers. - ''; - }; - resyncTimer = mkOption { type = str; default = "1h"; @@ -66,80 +25,53 @@ in { omitted. ''; }; - - volatile = mkOption { - type = str; - default = "/run/psd-profiles"; - description = '' - The directory where browser profiles should reside(this should be - mounted as a tmpfs). Do not include a trailing backslash. - ''; - }; - - daemonFile = mkOption { - type = str; - default = "/run/psd"; - description = '' - Where the pid and backup configuration files will be stored. - ''; - }; }; config = mkIf cfg.enable { - assertions = [ - { assertion = cfg.users != []; - message = "services.psd.users must contain at least one user"; - } - ]; - systemd = { - services = { - psd = { - description = "Profile Sync daemon"; - wants = [ "psd-resync.service" "local-fs.target" ]; - wantedBy = [ "multi-user.target" ]; - preStart = "mkdir -p ${cfg.volatile}"; - - path = with pkgs; [ glibc rsync gawk ]; - - unitConfig = { - RequiresMountsFor = [ "/home/" ]; + user = { + services = { + psd = { + enable = true; + description = "Profile Sync daemon"; + wants = [ "psd-resync.service" "local-fs.target" ]; + wantedBy = [ "default.target" ]; + path = with pkgs; [ rsync kmod gawk nettools profile-sync-daemon ]; + unitConfig = { + RequiresMountsFor = [ "/home/" ]; + }; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = "yes"; + ExecStart = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon sync"; + ExecStop = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon unsync"; + }; }; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = "yes"; - ExecStart = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon sync"; - ExecStop = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon unsync"; + psd-resync = { + enable = true; + description = "Timed profile resync"; + after = [ "psd.service" ]; + wants = [ "psd-resync.timer" ]; + partOf = [ "psd.service" ]; + wantedBy = [ "default.target" ]; + path = with pkgs; [ rsync kmod gawk nettools profile-sync-daemon ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon resync"; + }; }; }; - psd-resync = { - description = "Timed profile resync"; - after = [ "psd.service" ]; - wants = [ "psd-resync.timer" ]; - partOf = [ "psd.service" ]; - - path = with pkgs; [ glibc rsync gawk ]; + timers.psd-resync = { + description = "Timer for profile sync daemon - ${cfg.resyncTimer}"; + partOf = [ "psd-resync.service" "psd.service" ]; - serviceConfig = { - Type = "oneshot"; - ExecStart = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon resync"; + timerConfig = { + OnUnitActiveSec = "${cfg.resyncTimer}"; }; }; }; - - timers.psd-resync = { - description = "Timer for profile sync daemon - ${cfg.resyncTimer}"; - partOf = [ "psd-resync.service" "psd.service" ]; - - timerConfig = { - OnUnitActiveSec = "${cfg.resyncTimer}"; - }; - }; }; - - environment.etc."psd.conf".text = configFile; - }; } diff --git a/nixos/modules/services/desktops/zeitgeist.nix b/nixos/modules/services/desktops/zeitgeist.nix new file mode 100644 index 000000000000..20c82ccdd56c --- /dev/null +++ b/nixos/modules/services/desktops/zeitgeist.nix @@ -0,0 +1,26 @@ +# Zeitgeist + +{ config, lib, pkgs, ... }: + +with lib; + +{ + ###### interface + + options = { + services.zeitgeist = { + enable = mkEnableOption "zeitgeist"; + }; + }; + + ###### implementation + + config = mkIf config.services.zeitgeist.enable { + + environment.systemPackages = [ pkgs.zeitgeist ]; + + services.dbus.packages = [ pkgs.zeitgeist ]; + + systemd.packages = [ pkgs.zeitgeist ]; + }; +} diff --git a/nixos/modules/services/development/jupyter/default.nix b/nixos/modules/services/development/jupyter/default.nix new file mode 100644 index 000000000000..9fcc00431865 --- /dev/null +++ b/nixos/modules/services/development/jupyter/default.nix @@ -0,0 +1,184 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.jupyter; + + # NOTE: We don't use top-level jupyter because we don't + # want to pass in JUPYTER_PATH but use .environment instead, + # saving a rebuild. + package = pkgs.python3.pkgs.notebook; + + kernels = (pkgs.jupyter-kernel.create { + definitions = if cfg.kernels != null + then cfg.kernels + else pkgs.jupyter-kernel.default; + }); + + notebookConfig = pkgs.writeText "jupyter_config.py" '' + ${cfg.notebookConfig} + + c.NotebookApp.password = ${cfg.password} + ''; + +in { + meta.maintainers = with maintainers; [ aborsu ]; + + options.services.jupyter = { + enable = mkEnableOption "Jupyter development server"; + + ip = mkOption { + type = types.str; + default = "localhost"; + description = '' + IP address Jupyter will be listening on. + ''; + }; + + port = mkOption { + type = types.int; + default = 8888; + description = '' + Port number Jupyter will be listening on. + ''; + }; + + notebookDir = mkOption { + type = types.str; + default = "~/"; + description = '' + Root directory for notebooks. + ''; + }; + + user = mkOption { + type = types.str; + default = "jupyter"; + description = '' + Name of the user used to run the jupyter service. + For security reason, jupyter should really not be run as root. + If not set (jupyter), the service will create a jupyter user with appropriate settings. + ''; + example = "aborsu"; + }; + + group = mkOption { + type = types.str; + default = "jupyter"; + description = '' + Name of the group used to run the jupyter service. + Use this if you want to create a group of users that are able to view the notebook directory's content. + ''; + example = "users"; + }; + + password = mkOption { + type = types.str; + description = '' + Password to use with notebook. + Can be generated using: + In [1]: from notebook.auth import passwd + In [2]: passwd('test') + Out[2]: 'sha1:1b961dc713fb:88483270a63e57d18d43cf337e629539de1436ba' + NOTE: you need to keep the single quote inside the nix string. + Or you can use a python oneliner: + "open('/path/secret_file', 'r', encoding='utf8').read().strip()" + It will be interpreted at the end of the notebookConfig. + ''; + example = [ + "'sha1:1b961dc713fb:88483270a63e57d18d43cf337e629539de1436ba'" + "open('/path/secret_file', 'r', encoding='utf8').read().strip()" + ]; + }; + + notebookConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Raw jupyter config. + ''; + }; + + kernels = mkOption { + type = types.nullOr (types.attrsOf(types.submodule (import ./kernel-options.nix { + inherit lib; + }))); + + default = null; + example = literalExample '' + { + python3 = let + env = (pkgs.python3.withPackages (pythonPackages: with pythonPackages; [ + ipykernel + pandas + scikitlearn + ])); + in { + displayName = "Python 3 for machine learning"; + argv = [ + "$ {env.interpreter}" + "-m" + "ipykernel_launcher" + "-f" + "{connection_file}" + ]; + language = "python"; + logo32 = "$ {env.sitePackages}/ipykernel/resources/logo-32x32.png"; + logo64 = "$ {env.sitePackages}/ipykernel/resources/logo-64x64.png"; + }; + } + ''; + description = "Declarative kernel config + + Kernels can be declared in any language that supports and has the required + dependencies to communicate with a jupyter server. + In python's case, it means that ipykernel package must always be included in + the list of packages of the targeted environment. + "; + }; + }; + + config = mkMerge [ + (mkIf cfg.enable { + systemd.services.jupyter = { + description = "Jupyter development server"; + + wantedBy = [ "multi-user.target" ]; + + # TODO: Patch notebook so we can explicitly pass in a shell + path = [ pkgs.bash ]; # needed for sh in cell magic to work + + environment = { + JUPYTER_PATH = toString kernels; + }; + + serviceConfig = { + Restart = "always"; + ExecStart = ''${package}/bin/jupyter-notebook \ + --no-browser \ + --ip=${cfg.ip} \ + --port=${toString cfg.port} --port-retries 0 \ + --notebook-dir=${cfg.notebookDir} \ + --NotebookApp.config_file=${notebookConfig} + ''; + User = cfg.user; + Group = cfg.group; + WorkingDirectory = "~"; + }; + }; + }) + (mkIf (cfg.enable && (cfg.group == "jupyter")) { + users.groups.jupyter = {}; + }) + (mkIf (cfg.enable && (cfg.user == "jupyter")) { + users.extraUsers.jupyter = { + extraGroups = [ cfg.group ]; + home = "/var/lib/jupyter"; + createHome = true; + useDefaultShell = true; # needed so that the user can start a terminal. + }; + }) + ]; +} diff --git a/nixos/modules/services/development/jupyter/kernel-options.nix b/nixos/modules/services/development/jupyter/kernel-options.nix new file mode 100644 index 000000000000..03547637449a --- /dev/null +++ b/nixos/modules/services/development/jupyter/kernel-options.nix @@ -0,0 +1,60 @@ +# Options that can be used for creating a jupyter kernel. +{lib }: + +with lib; + +{ + options = { + + displayName = mkOption { + type = types.str; + default = ""; + example = [ + "Python 3" + "Python 3 for Data Science" + ]; + description = '' + Name that will be shown to the user. + ''; + }; + + argv = mkOption { + type = types.listOf types.str; + example = [ + "{customEnv.interpreter}" + "-m" + "ipykernel_launcher" + "-f" + "{connection_file}" + ]; + description = '' + Command and arguments to start the kernel. + ''; + }; + + language = mkOption { + type = types.str; + example = "python"; + description = '' + Language of the environment. Typically the name of the binary. + ''; + }; + + logo32 = mkOption { + type = types.nullOr types.path; + default = null; + example = "{env.sitePackages}/ipykernel/resources/logo-32x32.png"; + description = '' + Path to 32x32 logo png. + ''; + }; + logo64 = mkOption { + type = types.nullOr types.path; + default = null; + example = "{env.sitePackages}/ipykernel/resources/logo-64x64.png"; + description = '' + Path to 64x64 logo png. + ''; + }; + }; +} diff --git a/nixos/modules/services/editors/emacs.xml b/nixos/modules/services/editors/emacs.xml index dfab5ce4a79d..6cf20cf4aa7e 100644 --- a/nixos/modules/services/editors/emacs.xml +++ b/nixos/modules/services/editors/emacs.xml @@ -3,150 +3,148 @@ xmlns:xi="http://www.w3.org/2001/XInclude" version="5.0" xml:id="module-services-emacs"> - - <title>Emacs</title> - - <!-- + <title>Emacs</title> +<!-- Documentation contributors: Damien Cassou @DamienCassou Thomas Tuegel @ttuegel Rodney Lorrimar @rvl --> + <para> + <link xlink:href="http://www.gnu.org/software/emacs/">Emacs</link> is an + extensible, customizable, self-documenting real-time display editor — and + more. At its core is an interpreter for Emacs Lisp, a dialect of the Lisp + programming language with extensions to support text editing. + </para> + <para> + Emacs runs within a graphical desktop environment using the X Window System, + but works equally well on a text terminal. Under + <productname>macOS</productname>, a "Mac port" edition is available, which + uses Apple's native GUI frameworks. + </para> + <para> + <productname>Nixpkgs</productname> provides a superior environment for + running <application>Emacs</application>. It's simple to create custom builds + by overriding the default packages. Chaotic collections of Emacs Lisp code + and extensions can be brought under control using declarative package + management. <productname>NixOS</productname> even provides a + <command>systemd</command> user service for automatically starting the Emacs + daemon. + </para> + <section xml:id="module-services-emacs-installing"> + <title>Installing <application>Emacs</application></title> <para> - <link xlink:href="http://www.gnu.org/software/emacs/">Emacs</link> - is an extensible, customizable, self-documenting real-time display - editor — and more. At its core is an interpreter for Emacs Lisp, a - dialect of the Lisp programming language with extensions to - support text editing. + Emacs can be installed in the normal way for Nix (see + <xref linkend="sec-package-management" />). In addition, a NixOS + <emphasis>service</emphasis> can be enabled. </para> - <para> - Emacs runs within a graphical desktop environment using the X - Window System, but works equally well on a text terminal. Under - <productname>macOS</productname>, a "Mac port" edition is - available, which uses Apple's native GUI frameworks. - </para> + <section xml:id="module-services-emacs-releases"> + <title>The Different Releases of Emacs</title> + + <para> + <productname>Nixpkgs</productname> defines several basic Emacs packages. + The following are attributes belonging to the <varname>pkgs</varname> set: + <variablelist> + <varlistentry> + <term> + <varname>emacs</varname> + </term> + <term> + <varname>emacs25</varname> + </term> + <listitem> + <para> + The latest stable version of Emacs 25 using the + <link + xlink:href="http://www.gtk.org">GTK+ 2</link> + widget toolkit. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <varname>emacs25-nox</varname> + </term> + <listitem> + <para> + Emacs 25 built without any dependency on X11 libraries. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <varname>emacsMacport</varname> + </term> + <term> + <varname>emacs25Macport</varname> + </term> + <listitem> + <para> + Emacs 25 with the "Mac port" patches, providing a more native look and + feel under macOS. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + If those aren't suitable, then the following imitation Emacs editors are + also available in Nixpkgs: + <link xlink:href="https://www.gnu.org/software/zile/">Zile</link>, + <link xlink:href="http://homepage.boetes.org/software/mg/">mg</link>, + <link xlink:href="http://yi-editor.github.io/">Yi</link>. + </para> + </section> - <para> - <productname>Nixpkgs</productname> provides a superior environment - for running <application>Emacs</application>. It's simple to - create custom builds by overriding the default packages. Chaotic - collections of Emacs Lisp code and extensions can be brought under - control using declarative package - management. <productname>NixOS</productname> even provides a - <command>systemd</command> user service for automatically - starting the Emacs daemon. - </para> + <section xml:id="module-services-emacs-adding-packages"> + <title>Adding Packages to Emacs</title> - <section> - <title>Installing <application>Emacs</application></title> + <para> + Emacs includes an entire ecosystem of functionality beyond text editing, + including a project planner, mail and news reader, debugger interface, + calendar, and more. + </para> + <para> + Most extensions are gotten with the Emacs packaging system + (<filename>package.el</filename>) from + <link + xlink:href="https://elpa.gnu.org/">Emacs Lisp Package Archive + (<acronym>ELPA</acronym>)</link>, + <link xlink:href="https://melpa.org/"><acronym>MELPA</acronym></link>, + <link xlink:href="https://stable.melpa.org/">MELPA Stable</link>, and + <link xlink:href="http://orgmode.org/elpa.html">Org ELPA</link>. Nixpkgs is + regularly updated to mirror all these archives. + </para> + + <para> + Under NixOS, you can continue to use + <function>package-list-packages</function> and + <function>package-install</function> to install packages. You can also + declare the set of Emacs packages you need using the derivations from + Nixpkgs. The rest of this section discusses declarative installation of + Emacs packages through nixpkgs. + </para> + + <note> <para> - Emacs can be installed in the normal way for Nix (see - <xref linkend="sec-package-management" />). - In addition, a NixOS <emphasis>service</emphasis> - can be enabled. + This documentation describes the new Emacs packages framework in NixOS + 16.03 (<varname>emacsPackagesNg</varname>) which should not be confused + with the previous and deprecated framework + (<varname>emacs24Packages</varname>). </para> - - <section> - <title>The Different Releases of Emacs</title> - - <para> - <productname>Nixpkgs</productname> defines several basic Emacs - packages. The following are attributes belonging to the - <varname>pkgs</varname> set: - - <variablelist> - <varlistentry> - <term><varname>emacs</varname></term> - <term><varname>emacs25</varname></term> - <listitem> - <para> - The latest stable version of Emacs 25 using the <link - xlink:href="http://www.gtk.org">GTK+ 2</link> widget - toolkit. - </para> - </listitem> - </varlistentry> - <varlistentry> - <term><varname>emacs25-nox</varname></term> - <listitem> - <para> - Emacs 25 built without any dependency on X11 - libraries. - </para> - </listitem> - </varlistentry> - <varlistentry> - <term><varname>emacsMacport</varname></term> - <term><varname>emacs25Macport</varname></term> - <listitem> - <para> - Emacs 25 with the "Mac port" patches, providing a more - native look and feel under macOS. - </para> - </listitem> - </varlistentry> - </variablelist> - </para> - - <para> - If those aren't suitable, then the following imitation Emacs - editors are also available in Nixpkgs: - <link xlink:href="https://www.gnu.org/software/zile/">Zile</link>, - <link xlink:href="http://homepage.boetes.org/software/mg/">mg</link>, - <link xlink:href="http://yi-editor.github.io/">Yi</link>. - </para> - - </section> - <section> - <title>Adding Packages to Emacs</title> - <para> - Emacs includes an entire ecosystem of functionality beyond - text editing, including a project planner, mail and news - reader, debugger interface, calendar, and more. - </para> - - <para> - Most extensions are gotten with the Emacs packaging system - (<filename>package.el</filename>) from <link - xlink:href="https://elpa.gnu.org/">Emacs Lisp Package Archive - (<acronym>ELPA</acronym>)</link>, - <link xlink:href="https://melpa.org/"><acronym>MELPA</acronym></link>, - <link xlink:href="https://stable.melpa.org/">MELPA Stable</link>, - and <link xlink:href="http://orgmode.org/elpa.html">Org ELPA</link>. - Nixpkgs is regularly updated to mirror all these archives. - </para> - - <para> - Under NixOS, you can continue to use - <function>package-list-packages</function> and - <function>package-install</function> to install packages. You - can also declare the set of Emacs packages you need using the - derivations from Nixpkgs. The rest of this section discusses - declarative installation of Emacs packages through nixpkgs. - </para> - - <note> - <para> - This documentation describes the new Emacs packages - framework in NixOS 16.03 - (<varname>emacsPackagesNg</varname>) which should not be - confused with the previous and deprecated framework - (<varname>emacs24Packages</varname>). - </para> - </note> - - <para> - The first step to declare the list of packages you want in - your Emacs installation is to create a dedicated - derivation. This can be done in a dedicated - <filename>emacs.nix</filename> file such as: - - <example xml:id="ex-emacsNix"> - <title>Nix expression to build Emacs with packages (<filename>emacs.nix</filename>)</title> - <programlisting language="nix"> + </note> + + <para> + The first step to declare the list of packages you want in your Emacs + installation is to create a dedicated derivation. This can be done in a + dedicated <filename>emacs.nix</filename> file such as: + <example xml:id="ex-emacsNix"> + <title>Nix expression to build Emacs with packages (<filename>emacs.nix</filename>)</title> +<programlisting language="nix"> /* This is a nix expression to build Emacs and some Emacs packages I like from source on any distribution where Nix is installed. This will install @@ -181,119 +179,104 @@ in pkgs.notmuch # From main packages set <co xml:id="ex-emacsNix-7" /> ]) </programlisting> - </example> - - <calloutlist> - <callout arearefs="ex-emacsNix-1"> - <para> - The first non-comment line in this file - (<literal>{ pkgs ? ... }</literal>) - indicates that the whole file represents a function. - </para> - </callout> - - <callout arearefs="ex-emacsNix-2"> - <para> - The <varname>let</varname> expression below defines a - <varname>myEmacs</varname> binding pointing to the current - stable version of Emacs. This binding is here to separate the - choice of the Emacs binary from the specification of the - required packages. - </para> - </callout> - - <callout arearefs="ex-emacsNix-3"> - <para> - This generates an <varname>emacsWithPackages</varname> - function. It takes a single argument: a function from a - package set to a list of packages (the packages that will - be available in Emacs). - </para> - </callout> - - <callout arearefs="ex-emacsNix-4"> - <para> - The rest of the file specifies the list of packages to - install. In the example, two packages - (<varname>magit</varname> and - <varname>zerodark-theme</varname>) are taken from MELPA - stable. - </para> - </callout> - - <callout arearefs="ex-emacsNix-5"> - <para> - Two packages (<varname>undo-tree</varname> and - <varname>zoom-frm</varname>) are taken from MELPA. - </para> - </callout> - - <callout arearefs="ex-emacsNix-6"> - <para>Three packages are taken from GNU ELPA.</para> - </callout> - - <callout arearefs="ex-emacsNix-7"> - <para> - <varname>notmuch</varname> is taken from a nixpkgs derivation - which contains an Emacs mode. - </para> - </callout> - - </calloutlist> + </example> + <calloutlist> + <callout arearefs="ex-emacsNix-1"> + <para> + The first non-comment line in this file (<literal>{ pkgs ? ... + }</literal>) indicates that the whole file represents a function. </para> - + </callout> + <callout arearefs="ex-emacsNix-2"> <para> - The result of this configuration will be an - <command>emacs</command> command which launches Emacs with all - of your chosen packages in the <varname>load-path</varname>. + The <varname>let</varname> expression below defines a + <varname>myEmacs</varname> binding pointing to the current stable + version of Emacs. This binding is here to separate the choice of the + Emacs binary from the specification of the required packages. </para> - + </callout> + <callout arearefs="ex-emacsNix-3"> <para> - You can check that it works by executing this in a terminal: - + This generates an <varname>emacsWithPackages</varname> function. It + takes a single argument: a function from a package set to a list of + packages (the packages that will be available in Emacs). + </para> + </callout> + <callout arearefs="ex-emacsNix-4"> + <para> + The rest of the file specifies the list of packages to install. In the + example, two packages (<varname>magit</varname> and + <varname>zerodark-theme</varname>) are taken from MELPA stable. + </para> + </callout> + <callout arearefs="ex-emacsNix-5"> + <para> + Two packages (<varname>undo-tree</varname> and + <varname>zoom-frm</varname>) are taken from MELPA. + </para> + </callout> + <callout arearefs="ex-emacsNix-6"> + <para> + Three packages are taken from GNU ELPA. + </para> + </callout> + <callout arearefs="ex-emacsNix-7"> + <para> + <varname>notmuch</varname> is taken from a nixpkgs derivation which + contains an Emacs mode. + </para> + </callout> + </calloutlist> + </para> + + <para> + The result of this configuration will be an <command>emacs</command> + command which launches Emacs with all of your chosen packages in the + <varname>load-path</varname>. + </para> + + <para> + You can check that it works by executing this in a terminal: <screen> $ nix-build emacs.nix $ ./result/bin/emacs -q </screen> + and then typing <literal>M-x package-initialize</literal>. Check that you + can use all the packages you want in this Emacs instance. For example, try + switching to the zerodark theme through <literal>M-x load-theme <RET> + zerodark <RET> y</literal>. + </para> - and then typing <literal>M-x package-initialize</literal>. - Check that you can use all the packages you want in this - Emacs instance. For example, try switching to the zerodark - theme through - <literal>M-x load-theme <RET> zerodark <RET> y</literal>. - </para> - - <tip> - <para> - A few popular extensions worth checking out are: auctex, - company, edit-server, flycheck, helm, iedit, magit, - multiple-cursors, projectile, and yasnippet. - </para> - </tip> - - <para> - The list of available packages in the various ELPA - repositories can be seen with the following commands: - <example> - <title>Querying Emacs packages</title> - <programlisting><![CDATA[ + <tip> + <para> + A few popular extensions worth checking out are: auctex, company, + edit-server, flycheck, helm, iedit, magit, multiple-cursors, projectile, + and yasnippet. + </para> + </tip> + + <para> + The list of available packages in the various ELPA repositories can be seen + with the following commands: + <example xml:id="module-services-emacs-querying-packages"> + <title>Querying Emacs packages</title> +<programlisting><![CDATA[ nix-env -f "<nixpkgs>" -qaP -A emacsPackagesNg.elpaPackages nix-env -f "<nixpkgs>" -qaP -A emacsPackagesNg.melpaPackages nix-env -f "<nixpkgs>" -qaP -A emacsPackagesNg.melpaStablePackages nix-env -f "<nixpkgs>" -qaP -A emacsPackagesNg.orgPackages ]]></programlisting> - </example> - </para> - - <para> - If you are on NixOS, you can install this particular Emacs for - all users by adding it to the list of system packages - (see <xref linkend="sec-declarative-package-mgmt" />). Simply - modify your file <filename>configuration.nix</filename> to - make it contain: - <example> - <title>Custom Emacs in <filename>configuration.nix</filename></title> - <programlisting><![CDATA[ + </example> + </para> + + <para> + If you are on NixOS, you can install this particular Emacs for all users by + adding it to the list of system packages (see + <xref linkend="sec-declarative-package-mgmt" />). Simply modify your file + <filename>configuration.nix</filename> to make it contain: + <example xml:id="module-services-emacs-configuration-nix"> + <title>Custom Emacs in <filename>configuration.nix</filename></title> +<programlisting><![CDATA[ { environment.systemPackages = [ # [...] @@ -301,60 +284,59 @@ nix-env -f "<nixpkgs>" -qaP -A emacsPackagesNg.orgPackages ]; } ]]></programlisting> - </example> - </para> + </example> + </para> - <para> - In this case, the next <command>nixos-rebuild switch</command> - will take care of adding your <command>emacs</command> to the - <varname>PATH</varname> environment variable - (see <xref linkend="sec-changing-config" />). - </para> + <para> + In this case, the next <command>nixos-rebuild switch</command> will take + care of adding your <command>emacs</command> to the <varname>PATH</varname> + environment variable (see <xref linkend="sec-changing-config" />). + </para> <!-- fixme: i think the following is better done with config.nix https://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides --> - <para> - If you are not on NixOS or want to install this particular - Emacs only for yourself, you can do so by adding it to your - <filename>~/.config/nixpkgs/config.nix</filename> - (see <link xlink:href="http://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides">Nixpkgs manual</link>): - <example> - <title>Custom Emacs in <filename>~/.config/nixpkgs/config.nix</filename></title> - <programlisting><![CDATA[ + + <para> + If you are not on NixOS or want to install this particular Emacs only for + yourself, you can do so by adding it to your + <filename>~/.config/nixpkgs/config.nix</filename> (see + <link xlink:href="http://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides">Nixpkgs + manual</link>): + <example xml:id="module-services-emacs-config-nix"> + <title>Custom Emacs in <filename>~/.config/nixpkgs/config.nix</filename></title> +<programlisting><![CDATA[ { packageOverrides = super: let self = super.pkgs; in { myemacs = import /path/to/emacs.nix { pkgs = self; }; }; } ]]></programlisting> - </example> - </para> - - <para> - In this case, the next - <literal>nix-env -f '<nixpkgs>' -iA myemacs</literal> - will take care of adding your emacs to the - <varname>PATH</varname> environment variable. - </para> - </section> - - <section> - <title>Advanced Emacs Configuration</title> + </example> + </para> - <para> - If you want, you can tweak the Emacs package itself from your - <filename>emacs.nix</filename>. For example, if you want to - have a GTK+3-based Emacs instead of the default GTK+2-based - binary and remove the automatically generated - <filename>emacs.desktop</filename> (useful is you only use - <command>emacsclient</command>), you can change your file - <filename>emacs.nix</filename> in this way: - </para> + <para> + In this case, the next <literal>nix-env -f '<nixpkgs>' -iA + myemacs</literal> will take care of adding your emacs to the + <varname>PATH</varname> environment variable. + </para> + </section> - <example xml:id="ex-emacsGtk3Nix"> - <title>Custom Emacs build</title> - <programlisting><![CDATA[ + <section xml:id="module-services-emacs-advanced"> + <title>Advanced Emacs Configuration</title> + + <para> + If you want, you can tweak the Emacs package itself from your + <filename>emacs.nix</filename>. For example, if you want to have a + GTK+3-based Emacs instead of the default GTK+2-based binary and remove the + automatically generated <filename>emacs.desktop</filename> (useful is you + only use <command>emacsclient</command>), you can change your file + <filename>emacs.nix</filename> in this way: + </para> + + <example xml:id="ex-emacsGtk3Nix"> + <title>Custom Emacs build</title> +<programlisting><![CDATA[ { pkgs ? import <nixpkgs> {} }: let myEmacs = (pkgs.emacs.override { @@ -370,161 +352,143 @@ let }); in [...] ]]></programlisting> - </example> + </example> - <para> - After building this file as shown in <xref linkend="ex-emacsNix" />, - you will get an GTK3-based Emacs binary pre-loaded with your - favorite packages. - </para> - </section> + <para> + After building this file as shown in <xref linkend="ex-emacsNix" />, you + will get an GTK3-based Emacs binary pre-loaded with your favorite packages. + </para> </section> - -<section> + </section> + <section xml:id="module-services-emacs-running"> <title>Running Emacs as a Service</title> + <para> - <productname>NixOS</productname> provides an optional - <command>systemd</command> service which launches - <link xlink:href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html"> - Emacs daemon - </link> - with the user's login session. + <productname>NixOS</productname> provides an optional + <command>systemd</command> service which launches + <link xlink:href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html"> + Emacs daemon </link> with the user's login session. </para> <para> - <emphasis>Source:</emphasis> - <filename>modules/services/editors/emacs.nix</filename> + <emphasis>Source:</emphasis> + <filename>modules/services/editors/emacs.nix</filename> </para> - <section> - <title>Enabling the Service</title> - - <para> - To install and enable the <command>systemd</command> - user service for Emacs daemon, add the following to your - <filename>configuration.nix</filename>: + <section xml:id="module-services-emacs-enabling"> + <title>Enabling the Service</title> + <para> + To install and enable the <command>systemd</command> user service for Emacs + daemon, add the following to your <filename>configuration.nix</filename>: <programlisting> <xref linkend="opt-services.emacs.enable"/> = true; <xref linkend="opt-services.emacs.package"/> = import /home/cassou/.emacs.d { pkgs = pkgs; }; </programlisting> - </para> - - <para> - The <varname>services.emacs.package</varname> option allows a - custom derivation to be used, for example, one created by - <function>emacsWithPackages</function>. - </para> - - <para> - Ensure that the Emacs server is enabled for your user's Emacs - configuration, either by customizing the - <varname>server-mode</varname> variable, or by adding - <literal>(server-start)</literal> to - <filename>~/.emacs.d/init.el</filename>. - </para> - - <para> - To start the daemon, execute the following: - + </para> + + <para> + The <varname>services.emacs.package</varname> option allows a custom + derivation to be used, for example, one created by + <function>emacsWithPackages</function>. + </para> + + <para> + Ensure that the Emacs server is enabled for your user's Emacs + configuration, either by customizing the <varname>server-mode</varname> + variable, or by adding <literal>(server-start)</literal> to + <filename>~/.emacs.d/init.el</filename>. + </para> + + <para> + To start the daemon, execute the following: <screen> $ nixos-rebuild switch # to activate the new configuration.nix $ systemctl --user daemon-reload # to force systemd reload $ systemctl --user start emacs.service # to start the Emacs daemon </screen> - - The server should now be ready to serve Emacs clients. - </para> - + The server should now be ready to serve Emacs clients. + </para> </section> - <section> - <title>Starting the client</title> - <para> - Ensure that the emacs server is enabled, either by customizing - the <varname>server-mode</varname> variable, or by adding - <literal>(server-start)</literal> to - <filename>~/.emacs</filename>. - </para> + <section xml:id="module-services-emacs-starting-client"> + <title>Starting the client</title> - <para> - To connect to the emacs daemon, run one of the following: - <programlisting><![CDATA[ + <para> + Ensure that the emacs server is enabled, either by customizing the + <varname>server-mode</varname> variable, or by adding + <literal>(server-start)</literal> to <filename>~/.emacs</filename>. + </para> + + <para> + To connect to the emacs daemon, run one of the following: +<programlisting><![CDATA[ emacsclient FILENAME emacsclient --create-frame # opens a new frame (window) emacsclient --create-frame --tty # opens a new frame on the current terminal ]]></programlisting> - </para> + </para> </section> - <section> - <title>Configuring the <varname>EDITOR</varname> variable</title> - <!--<title><command>emacsclient</command> as the Default Editor</title>--> - - <para> - If <xref linkend="opt-services.emacs.defaultEditor"/> is - <literal>true</literal>, the <varname>EDITOR</varname> variable - will be set to a wrapper script which launches - <command>emacsclient</command>. - </para> - - <para> - Any setting of <varname>EDITOR</varname> in the shell config - files will override - <varname>services.emacs.defaultEditor</varname>. - To make sure <varname>EDITOR</varname> refers to the Emacs - wrapper script, remove any existing <varname>EDITOR</varname> - assignment from <filename>.profile</filename>, - <filename>.bashrc</filename>, <filename>.zshenv</filename> or - any other shell config file. - </para> - - <para> - If you have formed certain bad habits when editing files, - these can be corrected with a shell alias to the wrapper - script: - <programlisting>alias vi=$EDITOR</programlisting> - </para> + <section xml:id="module-services-emacs-editor-variable"> + <title>Configuring the <varname>EDITOR</varname> variable</title> + +<!--<title><command>emacsclient</command> as the Default Editor</title>--> + + <para> + If <xref linkend="opt-services.emacs.defaultEditor"/> is + <literal>true</literal>, the <varname>EDITOR</varname> variable will be set + to a wrapper script which launches <command>emacsclient</command>. + </para> + + <para> + Any setting of <varname>EDITOR</varname> in the shell config files will + override <varname>services.emacs.defaultEditor</varname>. To make sure + <varname>EDITOR</varname> refers to the Emacs wrapper script, remove any + existing <varname>EDITOR</varname> assignment from + <filename>.profile</filename>, <filename>.bashrc</filename>, + <filename>.zshenv</filename> or any other shell config file. + </para> + + <para> + If you have formed certain bad habits when editing files, these can be + corrected with a shell alias to the wrapper script: +<programlisting>alias vi=$EDITOR</programlisting> + </para> </section> - <section> - <title>Per-User Enabling of the Service</title> - - <para> - In general, <command>systemd</command> user services - are globally enabled by symlinks in - <filename>/etc/systemd/user</filename>. In the case where - Emacs daemon is not wanted for all users, it is possible to - install the service but not globally enable it: + <section xml:id="module-services-emacs-per-user"> + <title>Per-User Enabling of the Service</title> + <para> + In general, <command>systemd</command> user services are globally enabled + by symlinks in <filename>/etc/systemd/user</filename>. In the case where + Emacs daemon is not wanted for all users, it is possible to install the + service but not globally enable it: <programlisting> <xref linkend="opt-services.emacs.enable"/> = false; <xref linkend="opt-services.emacs.install"/> = true; </programlisting> - </para> - - <para> - To enable the <command>systemd</command> user service for just - the currently logged in user, run: - - <programlisting>systemctl --user enable emacs</programlisting> - - This will add the symlink - <filename>~/.config/systemd/user/emacs.service</filename>. - </para> + </para> + + <para> + To enable the <command>systemd</command> user service for just the + currently logged in user, run: +<programlisting>systemctl --user enable emacs</programlisting> + This will add the symlink + <filename>~/.config/systemd/user/emacs.service</filename>. + </para> </section> -</section> - -<section> + </section> + <section xml:id="module-services-emacs-configuring"> <title>Configuring Emacs</title> <para> - The Emacs init file should be changed to load the extension - packages at startup: - - <example> - <title>Package initialization in <filename>.emacs</filename></title> - <programlisting><![CDATA[ + The Emacs init file should be changed to load the extension packages at + startup: + <example xml:id="module-services-emacs-package-initialisation"> + <title>Package initialization in <filename>.emacs</filename></title> +<programlisting><![CDATA[ (require 'package) ;; optional. makes unpure packages archives unavailable @@ -533,66 +497,71 @@ emacsclient --create-frame --tty # opens a new frame on the current terminal (setq package-enable-at-startup nil) (package-initialize) ]]></programlisting> - </example> + </example> </para> <para> - After the declarative emacs package configuration has been - tested, previously downloaded packages can be cleaned up by - removing <filename>~/.emacs.d/elpa</filename> (do make a backup - first, in case you forgot a package). + After the declarative emacs package configuration has been tested, + previously downloaded packages can be cleaned up by removing + <filename>~/.emacs.d/elpa</filename> (do make a backup first, in case you + forgot a package). </para> - <!-- +<!-- todo: is it worth documenting customizations for server-switch-hook, server-done-hook? --> - <section> - <title>A Major Mode for Nix Expressions</title> + <section xml:id="module-services-emacs-major-mode"> + <title>A Major Mode for Nix Expressions</title> - <para> - Of interest may be <varname>melpaPackages.nix-mode</varname>, - which provides syntax highlighting for the Nix language. This is - particularly convenient if you regularly edit Nix files. - </para> + <para> + Of interest may be <varname>melpaPackages.nix-mode</varname>, which + provides syntax highlighting for the Nix language. This is particularly + convenient if you regularly edit Nix files. + </para> </section> - <section> - <title>Accessing man pages</title> - <para> - You can use <function>woman</function> to get completion of all - available man pages. For example, type <literal>M-x woman - <RET> nixos-rebuild <RET>.</literal> - </para> + <section xml:id="module-services-emacs-man-pages"> + <title>Accessing man pages</title> + + <para> + You can use <function>woman</function> to get completion of all available + man pages. For example, type <literal>M-x woman <RET> nixos-rebuild + <RET>.</literal> + </para> </section> <section xml:id="sec-emacs-docbook-xml"> - <title>Editing DocBook 5 XML Documents</title> - <para> - Emacs includes <link - xlink:href="https://www.gnu.org/software/emacs/manual/html_node/nxml-mode/Introduction.html">nXML</link>, - a major-mode for validating and editing XML documents. - When editing DocBook 5.0 documents, such as - <link linkend="book-nixos-manual">this one</link>, - nXML needs to be configured with the relevant schema, which is - not included. - </para> + <title>Editing DocBook 5 XML Documents</title> - <para> - To install the DocBook 5.0 schemas, either add - <varname>pkgs.docbook5</varname> to - <xref linkend="opt-environment.systemPackages"/> (<link + <para> + Emacs includes + <link + xlink:href="https://www.gnu.org/software/emacs/manual/html_node/nxml-mode/Introduction.html">nXML</link>, + a major-mode for validating and editing XML documents. When editing DocBook + 5.0 documents, such as <link linkend="book-nixos-manual">this one</link>, + nXML needs to be configured with the relevant schema, which is not + included. + </para> + + <para> + To install the DocBook 5.0 schemas, either add + <varname>pkgs.docbook5</varname> to + <xref linkend="opt-environment.systemPackages"/> + (<link linkend="sec-declarative-package-mgmt">NixOS</link>), or run - <literal>nix-env -i pkgs.docbook5</literal> - (<link linkend="sec-ad-hoc-packages">Nix</link>). - </para> - - <para> - Then customize the variable <varname>rng-schema-locating-files</varname> to include <filename>~/.emacs.d/schemas.xml</filename> and put the following text into that file: - <example xml:id="ex-emacs-docbook-xml"> - <title>nXML Schema Configuration (<filename>~/.emacs.d/schemas.xml</filename>)</title> - <programlisting language="xml"><![CDATA[ + <literal>nix-env -i pkgs.docbook5</literal> + (<link linkend="sec-ad-hoc-packages">Nix</link>). + </para> + + <para> + Then customize the variable <varname>rng-schema-locating-files</varname> to + include <filename>~/.emacs.d/schemas.xml</filename> and put the following + text into that file: + <example xml:id="ex-emacs-docbook-xml"> + <title>nXML Schema Configuration (<filename>~/.emacs.d/schemas.xml</filename>)</title> +<programlisting language="xml"><![CDATA[ <?xml version="1.0"?> <!-- To let emacs find this file, evaluate: @@ -612,9 +581,7 @@ emacsclient --create-frame --tty # opens a new frame on the current terminal </locatingRules> ]]></programlisting> </example> - </para> - + </para> </section> -</section> - + </section> </chapter> diff --git a/nixos/modules/services/editors/infinoted.nix b/nixos/modules/services/editors/infinoted.nix index bba21caca85d..9cc8d421270e 100644 --- a/nixos/modules/services/editors/infinoted.nix +++ b/nixos/modules/services/editors/infinoted.nix @@ -10,8 +10,8 @@ in { package = mkOption { type = types.package; - default = pkgs.libinfinity.override { daemon = true; }; - defaultText = "pkgs.libinfinity.override { daemon = true; }"; + default = pkgs.libinfinity; + defaultText = "pkgs.libinfinity"; description = '' Package providing infinoted ''; @@ -119,7 +119,7 @@ in { users.groups = optional (cfg.group == "infinoted") { name = "infinoted"; }; - + systemd.services.infinoted = { description = "Gobby Dedicated Server"; @@ -129,7 +129,7 @@ in { serviceConfig = { Type = "simple"; Restart = "always"; - ExecStart = "${cfg.package}/bin/infinoted-${versions.majorMinor cfg.package.version} --config-file=/var/lib/infinoted/infinoted.conf"; + ExecStart = "${cfg.package.infinoted} --config-file=/var/lib/infinoted/infinoted.conf"; User = cfg.user; Group = cfg.group; PermissionsStartOnly = true; diff --git a/nixos/modules/services/games/terraria.nix b/nixos/modules/services/games/terraria.nix index ddf17599296a..31f8edca20ce 100644 --- a/nixos/modules/services/games/terraria.nix +++ b/nixos/modules/services/games/terraria.nix @@ -18,6 +18,16 @@ let (boolFlag "secure" cfg.secure) (boolFlag "noupnp" cfg.noUPnP) ]; + stopScript = pkgs.writeScript "terraria-stop" '' + #!${pkgs.runtimeShell} + + if ! [ -d "/proc/$1" ]; then + exit 0 + fi + + ${getBin pkgs.tmux}/bin/tmux -S /var/lib/terraria/terraria.sock send-keys Enter exit Enter + ${getBin pkgs.coreutils}/bin/tail --pid="$1" -f /dev/null + ''; in { options = { @@ -124,10 +134,10 @@ in serviceConfig = { User = "terraria"; - Type = "oneshot"; - RemainAfterExit = true; + Type = "forking"; + GuessMainPID = true; ExecStart = "${getBin pkgs.tmux}/bin/tmux -S /var/lib/terraria/terraria.sock new -d ${pkgs.terraria-server}/bin/TerrariaServer ${concatStringsSep " " flags}"; - ExecStop = "${getBin pkgs.tmux}/bin/tmux -S /var/lib/terraria/terraria.sock send-keys Enter \"exit\" Enter"; + ExecStop = "${stopScript} $MAINPID"; }; postStart = '' diff --git a/nixos/modules/services/hardware/lirc.nix b/nixos/modules/services/hardware/lirc.nix new file mode 100644 index 000000000000..a66a7fbf495f --- /dev/null +++ b/nixos/modules/services/hardware/lirc.nix @@ -0,0 +1,85 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.lirc; +in { + + ###### interface + + options = { + services.lirc = { + + enable = mkEnableOption "LIRC daemon"; + + options = mkOption { + type = types.lines; + example = '' + [lircd] + nodaemon = False + ''; + description = "LIRC default options descriped in man:lircd(8) (<filename>lirc_options.conf</filename>)"; + }; + + configs = mkOption { + type = types.listOf types.lines; + description = "Configurations for lircd to load, see man:lircd.conf(5) for details (<filename>lircd.conf</filename>)"; + }; + + extraArguments = mkOption { + type = types.listOf types.str; + default = []; + description = "Extra arguments to lircd."; + }; + + }; + }; + + ###### implementation + + config = mkIf cfg.enable { + + # Note: LIRC executables raises a warning, if lirc_options.conf do not exists + environment.etc."lirc/lirc_options.conf".text = cfg.options; + + environment.systemPackages = [ pkgs.lirc ]; + + systemd.sockets.lircd = { + description = "LIRC daemon socket"; + wantedBy = [ "sockets.target" ]; + socketConfig = { + ListenStream = "/run/lirc/lircd"; + SocketUser = "lirc"; + SocketMode = "0660"; + }; + }; + + systemd.services.lircd = let + configFile = pkgs.writeText "lircd.conf" (builtins.concatStringsSep "\n" cfg.configs); + in { + description = "LIRC daemon service"; + after = [ "network.target" ]; + + unitConfig.Documentation = [ "man:lircd(8)" ]; + + serviceConfig = { + RuntimeDirectory = "lirc"; + ExecStart = '' + ${pkgs.lirc}/bin/lircd --nodaemon \ + ${escapeShellArgs cfg.extraArguments} \ + ${configFile} + ''; + User = "lirc"; + }; + }; + + users.users.lirc = { + uid = config.ids.uids.lirc; + group = "lirc"; + description = "LIRC user for lircd"; + }; + + users.groups.lirc.gid = config.ids.gids.lirc; + }; +} diff --git a/nixos/modules/services/hardware/ratbagd.nix b/nixos/modules/services/hardware/ratbagd.nix new file mode 100644 index 000000000000..103e1d2315ae --- /dev/null +++ b/nixos/modules/services/hardware/ratbagd.nix @@ -0,0 +1,32 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.ratbagd; +in +{ + ###### interface + + options = { + services.ratbagd = { + enable = mkOption { + default = false; + description = '' + Whether to enable ratbagd for configuring gaming mice. + ''; + }; + }; + }; + + ###### implementation + + config = mkIf cfg.enable { + # Give users access to the "ratbagctl" tool + environment.systemPackages = [ pkgs.libratbag ]; + + services.dbus.packages = [ pkgs.libratbag ]; + + systemd.packages = [ pkgs.libratbag ]; + }; +} diff --git a/nixos/modules/services/hardware/thermald.nix b/nixos/modules/services/hardware/thermald.nix index 88c3f99aed4e..69577bbe0181 100644 --- a/nixos/modules/services/hardware/thermald.nix +++ b/nixos/modules/services/hardware/thermald.nix @@ -6,16 +6,30 @@ let cfg = config.services.thermald; in { ###### interface - options = { - services.thermald = { + options = { + services.thermald = { enable = mkOption { default = false; description = '' Whether to enable thermald, the temperature management daemon. - ''; - }; - }; - }; + ''; + }; + + debug = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable debug logging. + ''; + }; + + configFile = mkOption { + type = types.nullOr types.path; + default = null; + description = "the thermald manual configuration file."; + }; + }; + }; ###### implementation config = mkIf cfg.enable { @@ -24,7 +38,15 @@ in { systemd.services.thermald = { description = "Thermal Daemon Service"; wantedBy = [ "multi-user.target" ]; - script = "exec ${pkgs.thermald}/sbin/thermald --no-daemon --dbus-enable"; + serviceConfig = { + ExecStart = '' + ${pkgs.thermald}/sbin/thermald \ + --no-daemon \ + ${optionalString cfg.debug "--loglevel=debug"} \ + ${optionalString (cfg.configFile != null) "--config-file ${cfg.configFile}"} \ + --dbus-enable + ''; + }; }; }; } diff --git a/nixos/modules/services/hardware/trezord.nix b/nixos/modules/services/hardware/trezord.nix index f2ec00a7d3e1..dfefc1171e62 100644 --- a/nixos/modules/services/hardware/trezord.nix +++ b/nixos/modules/services/hardware/trezord.nix @@ -26,15 +26,14 @@ in { name = "trezord-udev-rules"; destination = "/etc/udev/rules.d/51-trezor.rules"; text = '' - # Trezor 1 - SUBSYSTEM=="usb", ATTR{idVendor}=="534c", ATTR{idProduct}=="0001", MODE="0666", GROUP="dialout", SYMLINK+="trezor%n" - KERNEL=="hidraw*", ATTRS{idVendor}=="534c", ATTRS{idProduct}=="0001", MODE="0666", GROUP="dialout" - - # Trezor 2 (Model-T) - SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c0", MODE="0661", GROUP="dialout", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n" - SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c1", MODE="0660", GROUP="dialout", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n" - KERNEL=="hidraw*", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="53c1", MODE="0660", GROUP="dialout", TAG+="uaccess", TAG+="udev-acl" - ]; + # TREZOR v1 (One) + SUBSYSTEM=="usb", ATTR{idVendor}=="534c", ATTR{idProduct}=="0001", MODE="0666", GROUP="dialout", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n" + KERNEL=="hidraw*", ATTRS{idVendor}=="534c", ATTRS{idProduct}=="0001", MODE="0666", GROUP="dialout", TAG+="uaccess", TAG+="udev-acl" + + # TREZOR v2 (T) + SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c0", MODE="0661", GROUP="dialout", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n" + SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c1", MODE="0666", GROUP="dialout", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n" + KERNEL=="hidraw*", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="53c1", MODE="0666", GROUP="dialout", TAG+="uaccess", TAG+="udev-acl" ''; }); diff --git a/nixos/modules/services/hardware/undervolt.nix b/nixos/modules/services/hardware/undervolt.nix new file mode 100644 index 000000000000..e5ef0601de3c --- /dev/null +++ b/nixos/modules/services/hardware/undervolt.nix @@ -0,0 +1,134 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.undervolt; +in { + options.services.undervolt = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to undervolt intel cpus. + ''; + }; + + verbose = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable verbose logging. + ''; + }; + + package = mkOption { + type = types.package; + default = pkgs.undervolt; + defaultText = "pkgs.undervolt"; + description = '' + undervolt derivation to use. + ''; + }; + + coreOffset = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The amount of voltage to offset the CPU cores by. Accepts a floating point number. + ''; + }; + + gpuOffset = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The amount of voltage to offset the GPU by. Accepts a floating point number. + ''; + }; + + uncoreOffset = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The amount of voltage to offset uncore by. Accepts a floating point number. + ''; + }; + + analogioOffset = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The amount of voltage to offset analogio by. Accepts a floating point number. + ''; + }; + + temp = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The temperature target. Accepts a floating point number. + ''; + }; + + tempAc = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The temperature target on AC power. Accepts a floating point number. + ''; + }; + + tempBat = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The temperature target on battery power. Accepts a floating point number. + ''; + }; + }; + + config = mkIf cfg.enable { + boot.kernelModules = [ "msr" ]; + + environment.systemPackages = [ cfg.package ]; + + systemd.services.undervolt = { + path = [ pkgs.undervolt ]; + + description = "Intel Undervolting Service"; + serviceConfig = { + Type = "oneshot"; + Restart = "no"; + + # `core` and `cache` are both intentionally set to `cfg.coreOffset` as according to the undervolt docs: + # + # Core or Cache offsets have no effect. It is not possible to set different offsets for + # CPU Core and Cache. The CPU will take the smaller of the two offsets, and apply that to + # both CPU and Cache. A warning message will be displayed if you attempt to set different offsets. + ExecStart = '' + ${pkgs.undervolt}/bin/undervolt \ + ${optionalString cfg.verbose "--verbose"} \ + ${optionalString (cfg.coreOffset != null) "--core ${cfg.coreOffset}"} \ + ${optionalString (cfg.coreOffset != null) "--cache ${cfg.coreOffset}"} \ + ${optionalString (cfg.gpuOffset != null) "--gpu ${cfg.gpuOffset}"} \ + ${optionalString (cfg.uncoreOffset != null) "--uncore ${cfg.uncoreOffset}"} \ + ${optionalString (cfg.analogioOffset != null) "--analogio ${cfg.analogioOffset}"} \ + ${optionalString (cfg.temp != null) "--temp ${cfg.temp}"} \ + ${optionalString (cfg.tempAc != null) "--temp-ac ${cfg.tempAc}"} \ + ${optionalString (cfg.tempBat != null) "--temp-bat ${cfg.tempBat}"} + ''; + }; + }; + + systemd.timers.undervolt = { + description = "Undervolt timer to ensure voltage settings are always applied"; + partOf = [ "undervolt.service" ]; + wantedBy = [ "multi-user.target" ]; + timerConfig = { + OnBootSec = "2min"; + OnUnitActiveSec = "30"; + }; + }; + }; +} diff --git a/nixos/modules/services/logging/logstash.nix b/nixos/modules/services/logging/logstash.nix index 28d89a7463ab..aa019d855ea9 100644 --- a/nixos/modules/services/logging/logstash.nix +++ b/nixos/modules/services/logging/logstash.nix @@ -4,25 +4,12 @@ with lib; let cfg = config.services.logstash; - atLeast54 = versionAtLeast (builtins.parseDrvName cfg.package.name).version "5.4"; pluginPath = lib.concatStringsSep ":" cfg.plugins; havePluginPath = lib.length cfg.plugins > 0; ops = lib.optionalString; - verbosityFlag = - if atLeast54 - then "--log.level " + cfg.logLevel - else { - debug = "--debug"; - info = "--verbose"; - warn = ""; # intentionally empty - error = "--quiet"; - fatal = "--silent"; - }."${cfg.logLevel}"; - - pluginsPath = - if atLeast54 - then "--path.plugins ${pluginPath}" - else "--pluginpath ${pluginPath}"; + verbosityFlag = "--log.level " + cfg.logLevel; + + pluginsPath = "--path.plugins ${pluginPath}"; logstashConf = pkgs.writeText "logstash.conf" '' input { @@ -63,7 +50,7 @@ in type = types.package; default = pkgs.logstash; defaultText = "pkgs.logstash"; - example = literalExample "pkgs.logstash"; + example = literalExample "pkgs.logstash5"; description = "Logstash package to use."; }; @@ -95,12 +82,6 @@ in description = "The quantity of filter workers to run."; }; - enableWeb = mkOption { - type = types.bool; - default = false; - description = "Enable the logstash web interface."; - }; - listenAddress = mkOption { type = types.str; default = "127.0.0.1"; @@ -174,16 +155,6 @@ in ###### implementation config = mkIf cfg.enable { - assertions = [ - { assertion = atLeast54 -> !cfg.enableWeb; - message = '' - The logstash web interface is only available for versions older than 5.4. - So either set services.logstash.enableWeb = false, - or set services.logstash.package to an older logstash. - ''; - } - ]; - systemd.services.logstash = with pkgs; { description = "Logstash Daemon"; wantedBy = [ "multi-user.target" ]; @@ -193,14 +164,12 @@ in ExecStartPre = ''${pkgs.coreutils}/bin/mkdir -p "${cfg.dataDir}" ; ${pkgs.coreutils}/bin/chmod 700 "${cfg.dataDir}"''; ExecStart = concatStringsSep " " (filter (s: stringLength s != 0) [ "${cfg.package}/bin/logstash" - (ops (!atLeast54) "agent") "-w ${toString cfg.filterWorkers}" (ops havePluginPath pluginsPath) "${verbosityFlag}" "-f ${logstashConf}" - (ops atLeast54 "--path.settings ${logstashSettingsDir}") - (ops atLeast54 "--path.data ${cfg.dataDir}") - (ops cfg.enableWeb "-- web -a ${cfg.listenAddress} -p ${cfg.port}") + "--path.settings ${logstashSettingsDir}" + "--path.data ${cfg.dataDir}" ]); }; }; diff --git a/nixos/modules/services/logging/syslog-ng.nix b/nixos/modules/services/logging/syslog-ng.nix index 21be286a6e98..65e103ac2ba5 100644 --- a/nixos/modules/services/logging/syslog-ng.nix +++ b/nixos/modules/services/logging/syslog-ng.nix @@ -85,9 +85,11 @@ in { after = [ "multi-user.target" ]; # makes sure hostname etc is set serviceConfig = { Type = "notify"; + PIDFile = pidFile; StandardOutput = "null"; Restart = "on-failure"; ExecStart = "${cfg.package}/sbin/syslog-ng ${concatStringsSep " " syslogngOptions}"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; }; }; }; diff --git a/nixos/modules/services/mail/exim.nix b/nixos/modules/services/mail/exim.nix index 06c4b2811b3f..c05811291359 100644 --- a/nixos/modules/services/mail/exim.nix +++ b/nixos/modules/services/mail/exim.nix @@ -2,7 +2,7 @@ let inherit (lib) mkIf mkOption singleton types; - inherit (pkgs) coreutils exim; + inherit (pkgs) coreutils; cfg = config.services.exim; in @@ -57,6 +57,16 @@ in ''; }; + package = mkOption { + type = types.package; + default = pkgs.exim; + defaultText = "pkgs.exim"; + description = '' + The Exim derivation to use. + This can be used to enable features such as LDAP or PAM support. + ''; + }; + }; }; @@ -74,7 +84,7 @@ in spool_directory = ${cfg.spoolDir} ${cfg.config} ''; - systemPackages = [ exim ]; + systemPackages = [ cfg.package ]; }; users.users = singleton { @@ -89,14 +99,14 @@ in gid = config.ids.gids.exim; }; - security.wrappers.exim.source = "${exim}/bin/exim"; + security.wrappers.exim.source = "${cfg.package}/bin/exim"; systemd.services.exim = { description = "Exim Mail Daemon"; wantedBy = [ "multi-user.target" ]; restartTriggers = [ config.environment.etc."exim.conf".source ]; serviceConfig = { - ExecStart = "${exim}/bin/exim -bdf -q30m"; + ExecStart = "${cfg.package}/bin/exim -bdf -q30m"; ExecReload = "${coreutils}/bin/kill -HUP $MAINPID"; }; preStart = '' diff --git a/nixos/modules/services/mail/rmilter.nix b/nixos/modules/services/mail/rmilter.nix index 7f38d7570132..0d91b247cd34 100644 --- a/nixos/modules/services/mail/rmilter.nix +++ b/nixos/modules/services/mail/rmilter.nix @@ -89,7 +89,7 @@ in bindSocket.path = mkOption { type = types.str; - default = "/run/rmilter/rmilter.sock"; + default = "/run/rmilter.sock"; description = '' Path to Unix domain socket to listen on. ''; @@ -193,6 +193,9 @@ in config = mkMerge [ (mkIf cfg.enable { + warnings = [ + ''`config.services.rmilter' is deprecated, `rmilter' deprecated and unsupported by upstream, and will be removed from next releases. Use built-in rspamd milter instead.'' + ]; users.users = singleton { name = cfg.user; diff --git a/nixos/modules/services/mail/rspamd.nix b/nixos/modules/services/mail/rspamd.nix index b3dae60c2c7e..bba11796a3d3 100644 --- a/nixos/modules/services/mail/rspamd.nix +++ b/nixos/modules/services/mail/rspamd.nix @@ -115,36 +115,10 @@ let }; }; - indexOf = default: start: list: e: - if list == [] - then default - else if (head list) == e then start - else (indexOf default (start + (length (listenStreams (head list).socket))) (tail list) e); - - systemdSocket = indexOf (abort "Socket not found") 0 allSockets; - isUnixSocket = socket: hasPrefix "/" (if (isString socket) then socket else socket.socket); - isPort = hasPrefix "*:"; - isIPv4Socket = hasPrefix "*v4:"; - isIPv6Socket = hasPrefix "*v6:"; - isLocalHost = hasPrefix "localhost:"; - listenStreams = socket: - if (isLocalHost socket) then - let port = (removePrefix "localhost:" socket); - in [ "127.0.0.1:${port}" ] ++ (if config.networking.enableIPv6 then ["[::1]:${port}"] else []) - else if (isIPv6Socket socket) then [removePrefix "*v6:" socket] - else if (isPort socket) then [removePrefix "*:" socket] - else if (isIPv4Socket socket) then - throw "error: IPv4 only socket not supported in rspamd with socket activation" - else if (length (splitString " " socket)) != 1 then - throw "error: string options not supported in rspamd with socket activation" - else [socket]; - - mkBindSockets = enabled: socks: concatStringsSep "\n " (flatten (map (each: - if cfg.socketActivation && enabled != false then - let systemd = (systemdSocket each); - in (imap (idx: e: "bind_socket = \"systemd:${toString (systemd + idx - 1)}\";") (listenStreams each.socket)) - else "bind_socket = \"${each.rawEntry}\";") socks)); + + mkBindSockets = enabled: socks: concatStringsSep "\n " + (flatten (map (each: "bind_socket = \"${each.rawEntry}\";") socks)); rspamdConfFile = pkgs.writeText "rspamd.conf" '' @@ -175,18 +149,6 @@ let ${cfg.extraConfig} ''; - allMappedSockets = flatten (mapAttrsToList (name: value: - if value.enable != false - then imap (idx: each: { - name = "${name}"; - index = idx; - value = each; - }) value.bindSockets - else []) cfg.workers); - allSockets = map (e: e.value) allMappedSockets; - - allSocketNames = map (each: "rspamd-${each.name}-${toString each.index}.socket") allMappedSockets; - in { @@ -205,13 +167,6 @@ in description = "Whether to run the rspamd daemon in debug mode."; }; - socketActivation = mkOption { - type = types.bool; - description = '' - Enable systemd socket activation for rspamd. - ''; - }; - workers = mkOption { type = with types; attrsOf (submodule workerOpts); description = '' @@ -272,13 +227,6 @@ in config = mkIf cfg.enable { - services.rspamd.socketActivation = mkDefault (!opts.bindSocket.isDefined && !opts.bindUISocket.isDefined); - - assertions = [ { - assertion = !cfg.socketActivation || !(opts.bindSocket.isDefined || opts.bindUISocket.isDefined); - message = "Can't use socketActivation for rspamd when using renamed bind socket options"; - } ]; - # Allow users to run 'rspamc' and 'rspamadm'. environment.systemPackages = [ pkgs.rspamd ]; @@ -299,17 +247,14 @@ in systemd.services.rspamd = { description = "Rspamd Service"; - wantedBy = mkIf (!cfg.socketActivation) [ "multi-user.target" ]; - after = [ "network.target" ] ++ - (if cfg.socketActivation then allSocketNames else []); - requires = mkIf cfg.socketActivation allSocketNames; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; serviceConfig = { ExecStart = "${pkgs.rspamd}/bin/rspamd ${optionalString cfg.debug "-d"} --user=${cfg.user} --group=${cfg.group} --pid=/run/rspamd.pid -c ${rspamdConfFile} -f"; Restart = "always"; RuntimeDirectory = "rspamd"; PrivateTmp = true; - Sockets = mkIf cfg.socketActivation (concatStringsSep " " allSocketNames); }; preStart = '' @@ -317,24 +262,10 @@ in ${pkgs.coreutils}/bin/chown ${cfg.user}:${cfg.group} /var/lib/rspamd ''; }; - systemd.sockets = mkIf cfg.socketActivation - (listToAttrs (map (each: { - name = "rspamd-${each.name}-${toString each.index}"; - value = { - description = "Rspamd socket ${toString each.index} for worker ${each.name}"; - wantedBy = [ "sockets.target" ]; - listenStreams = (listenStreams each.value.socket); - socketConfig = { - BindIPv6Only = mkIf (isIPv6Socket each.value.socket) "ipv6-only"; - Service = "rspamd.service"; - SocketUser = mkIf (isUnixSocket each.value.socket) each.value.owner; - SocketGroup = mkIf (isUnixSocket each.value.socket) each.value.group; - SocketMode = mkIf (isUnixSocket each.value.socket) each.value.mode; - }; - }; - }) allMappedSockets)); }; imports = [ + (mkRemovedOptionModule [ "services" "rspamd" "socketActivation" ] + "Socket activation never worked correctly and could at this time not be fixed and so was removed") (mkRenamedOptionModule [ "services" "rspamd" "bindSocket" ] [ "services" "rspamd" "workers" "normal" "bindSockets" ]) (mkRenamedOptionModule [ "services" "rspamd" "bindUISocket" ] [ "services" "rspamd" "workers" "controller" "bindSockets" ]) ]; diff --git a/nixos/modules/services/misc/airsonic.nix b/nixos/modules/services/misc/airsonic.nix index 083587b8ebb1..01d7b3cf6b9d 100644 --- a/nixos/modules/services/misc/airsonic.nix +++ b/nixos/modules/services/misc/airsonic.nix @@ -73,6 +73,24 @@ in { ${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]'" + ]; + }; + }; }; @@ -98,6 +116,7 @@ in { -Dserver.port=${toString cfg.port} \ -Dairsonic.contextPath=${cfg.contextPath} \ -Djava.awt.headless=true \ + ${toString cfg.jvmOptions} \ -verbose:gc \ -jar ${pkgs.airsonic}/webapps/airsonic.war ''; diff --git a/nixos/modules/services/misc/emby.nix b/nixos/modules/services/misc/emby.nix index 64cc9c610ac3..151edd0e761a 100644 --- a/nixos/modules/services/misc/emby.nix +++ b/nixos/modules/services/misc/emby.nix @@ -36,11 +36,18 @@ in after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; preStart = '' - test -d ${cfg.dataDir} || { - echo "Creating initial Emby data directory in ${cfg.dataDir}" - mkdir -p ${cfg.dataDir} - chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir} - } + if [ -d ${cfg.dataDir} ] + then + for plugin in ${cfg.dataDir}/plugins/* + do + echo "Correcting permissions of plugin: $plugin" + chmod u+w $plugin + done + else + echo "Creating initial Emby data directory in ${cfg.dataDir}" + mkdir -p ${cfg.dataDir} + chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir} + fi ''; serviceConfig = { @@ -48,7 +55,7 @@ in User = cfg.user; Group = cfg.group; PermissionsStartOnly = "true"; - ExecStart = "${pkgs.emby}/bin/MediaBrowser.Server.Mono"; + ExecStart = "${pkgs.emby}/bin/emby"; Restart = "on-failure"; }; }; diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix index 5d664728e0b5..a222325579fe 100644 --- a/nixos/modules/services/misc/gitea.nix +++ b/nixos/modules/services/misc/gitea.nix @@ -261,7 +261,8 @@ in runConfig = "${cfg.stateDir}/custom/conf/app.ini"; secretKey = "${cfg.stateDir}/custom/conf/secret_key"; in '' - mkdir -p ${cfg.stateDir} + # Make sure that the stateDir exists, as well as the conf dir in there + mkdir -p ${cfg.stateDir}/conf # copy custom configuration and generate a random secret key if needed ${optionalString (cfg.useWizard == false) '' @@ -282,7 +283,7 @@ in mkdir -p ${cfg.repositoryRoot} # update all hooks' binary paths - HOOKS=$(find ${cfg.repositoryRoot} -mindepth 4 -maxdepth 5 -type f -wholename "*git/hooks/*") + HOOKS=$(find ${cfg.repositoryRoot} -mindepth 4 -maxdepth 6 -type f -wholename "*git/hooks/*") if [ "$HOOKS" ] then sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${gitea.bin}/bin/gitea,g' $HOOKS @@ -290,11 +291,13 @@ in sed -ri 's,/nix/store/[a-z0-9.-]+/bin/bash,${pkgs.bash}/bin/bash,g' $HOOKS sed -ri 's,/nix/store/[a-z0-9.-]+/bin/perl,${pkgs.perl}/bin/perl,g' $HOOKS fi - if [ ! -d ${cfg.stateDir}/conf/locale ] + # If we have a folder or symlink with gitea locales, remove it + if [ -e ${cfg.stateDir}/conf/locale ] then - mkdir -p ${cfg.stateDir}/conf - cp -r ${gitea.out}/locale ${cfg.stateDir}/conf/locale + rm -r ${cfg.stateDir}/conf/locale fi + # And symlink the current gitea locales in place + ln -s ${gitea.out}/locale ${cfg.stateDir}/conf/locale # update command option in authorized_keys if [ -r ${cfg.stateDir}/.ssh/authorized_keys ] then diff --git a/nixos/modules/services/misc/gitit.nix b/nixos/modules/services/misc/gitit.nix index 0025d96bd37b..1ec030549f98 100644 --- a/nixos/modules/services/misc/gitit.nix +++ b/nixos/modules/services/misc/gitit.nix @@ -10,7 +10,7 @@ let toYesNo = b: if b then "yes" else "no"; - gititShared = with cfg.haskellPackages; gitit + "/share/" + pkgs.stdenv.system + "-" + ghc.name + "/" + gitit.pname + "-" + gitit.version; + 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)); diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix index 5bf66354f487..d81aa5643e53 100644 --- a/nixos/modules/services/misc/gitlab.nix +++ b/nixos/modules/services/misc/gitlab.nix @@ -162,7 +162,7 @@ let makeWrapper ${cfg.packages.gitlab.rubyEnv}/bin/rake $out/bin/gitlab-rake \ ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") gitlabEnv)} \ --set GITLAB_CONFIG_PATH '${cfg.statePath}/config' \ - --set PATH '${lib.makeBinPath [ pkgs.nodejs pkgs.gzip pkgs.git pkgs.gnutar config.services.postgresql.package ]}:$PATH' \ + --set PATH '${lib.makeBinPath [ pkgs.nodejs pkgs.gzip pkgs.git pkgs.gnutar config.services.postgresql.package pkgs.coreutils pkgs.procps ]}:$PATH' \ --set RAKEOPT '-f ${cfg.packages.gitlab}/share/gitlab/Rakefile' \ --run 'cd ${cfg.packages.gitlab}/share/gitlab' ''; @@ -203,6 +203,7 @@ in { default = pkgs.gitlab; defaultText = "pkgs.gitlab"; description = "Reference to the gitlab package"; + example = "pkgs.gitlab-ee"; }; packages.gitlab-shell = mkOption { @@ -501,7 +502,7 @@ in { }; systemd.services.gitlab-workhorse = { - after = [ "network.target" "gitlab.service" ]; + after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; environment.HOME = gitlabEnv.HOME; environment.GITLAB_SHELL_CONFIG_PATH = gitlabEnv.GITLAB_SHELL_CONFIG_PATH; @@ -569,9 +570,9 @@ in { mkdir -p /run/gitlab mkdir -p ${cfg.statePath}/log - ln -sf ${cfg.statePath}/log /run/gitlab/log - ln -sf ${cfg.statePath}/tmp /run/gitlab/tmp - ln -sf ${cfg.statePath}/uploads /run/gitlab/uploads + [ -d /run/gitlab/log ] || ln -sf ${cfg.statePath}/log /run/gitlab/log + [ -d /run/gitlab/tmp ] || ln -sf ${cfg.statePath}/tmp /run/gitlab/tmp + [ -d /run/gitlab/uploads ] || ln -sf ${cfg.statePath}/uploads /run/gitlab/uploads ln -sf $GITLAB_SHELL_CONFIG_PATH /run/gitlab/shell-config.yml chown -R ${cfg.user}:${cfg.group} /run/gitlab @@ -629,6 +630,10 @@ in { touch "${cfg.statePath}/db-seeded" fi + # The gitlab:shell:setup regenerates the authorized_keys file so that + # the store path to the gitlab-shell in it gets updated + ${pkgs.sudo}/bin/sudo -u ${cfg.user} force=yes ${gitlab-rake}/bin/gitlab-rake gitlab:shell:setup RAILS_ENV=production + # The gitlab:shell:create_hooks task seems broken for fixing links # so we instead delete all the hooks and create them anew rm -f ${cfg.statePath}/repositories/**/*.git/hooks diff --git a/nixos/modules/services/misc/gitlab.xml b/nixos/modules/services/misc/gitlab.xml index 3306ba8e9b11..ab99d7bd3a60 100644 --- a/nixos/modules/services/misc/gitlab.xml +++ b/nixos/modules/services/misc/gitlab.xml @@ -3,20 +3,22 @@ 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><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: - + <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; @@ -31,21 +33,24 @@ webserver to proxy HTTP requests to the socket.</para> }; }; </programlisting> -</para> - -</section> - -<section><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: - + </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; @@ -105,40 +110,41 @@ services.gitlab = { }; }; </programlisting> -</para> - -<para>If you're setting up a new Gitlab instance, generate new secrets. You -for instance use <literal>tr -dc A-Za-z0-9 < /dev/urandom | head -c 128</literal> -to generate a new secret. Gitlab encrypts sensitive data stored in the database. -If you're restoring an existing Gitlab instance, you must specify the secrets -secret from <literal>config/secrets.yml</literal> located in your Gitlab state -folder.</para> - -<para>Refer to <xref linkend="ch-options" /> for all available configuration -options for the <link linkend="opt-services.gitlab.enable">services.gitlab</link> module.</para> - -</section> - -<section><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: - + </para> + + <para> + If you're setting up a new Gitlab instance, generate new secrets. You for + instance use <literal>tr -dc A-Za-z0-9 < /dev/urandom | head -c + 128</literal> to generate a new secret. Gitlab encrypts sensitive data + stored in the database. If you're restoring an existing Gitlab instance, you + must specify the secrets secret from <literal>config/secrets.yml</literal> + located in your Gitlab state folder. + </para> + + <para> + Refer to <xref linkend="ch-options" /> for all available configuration + options for the + <link linkend="opt-services.gitlab.enable">services.gitlab</link> module. + </para> + </section> + <section xml:id="module-services-gitlab-maintenance"> + <title>Maintenance</title> + + <para> + You can run Gitlab's rake tasks with <literal>gitlab-rake</literal> which + will be available on the system when gitlab is enabled. You will have to run + the command as the user that you configured to run gitlab with. + </para> + + <para> + For example, to backup a Gitlab instance: <programlisting> $ sudo -u git -H gitlab-rake gitlab:backup:create </programlisting> - -A list of all availabe rake tasks can be obtained by running: - + A list of all availabe rake tasks can be obtained by running: <programlisting> $ sudo -u git -H gitlab-rake -T </programlisting> -</para> - -</section> - + </para> + </section> </chapter> diff --git a/nixos/modules/services/misc/lidarr.nix b/nixos/modules/services/misc/lidarr.nix new file mode 100644 index 000000000000..627f22334fe8 --- /dev/null +++ b/nixos/modules/services/misc/lidarr.nix @@ -0,0 +1,46 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.lidarr; +in +{ + options = { + services.lidarr = { + enable = mkEnableOption "Lidarr"; + }; + }; + + config = mkIf cfg.enable { + systemd.services.lidarr = { + description = "Lidarr"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + preStart = '' + [ ! -d /var/lib/lidarr ] && mkdir -p /var/lib/lidarr + chown -R lidarr:lidarr /var/lib/lidarr + ''; + + serviceConfig = { + Type = "simple"; + User = "lidarr"; + Group = "lidarr"; + PermissionsStartOnly = "true"; + ExecStart = "${pkgs.lidarr}/bin/Lidarr"; + Restart = "on-failure"; + + StateDirectory = "/var/lib/lidarr/"; + StateDirectoryMode = "0770"; + }; + }; + + users.users.lidarr = { + uid = config.ids.uids.lidarr; + home = "/var/lib/lidarr"; + group = "lidarr"; + }; + + users.groups.lidarr.gid = config.ids.gids.lidarr; + }; +} diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix index 5ca879bf2664..24379ec27354 100644 --- a/nixos/modules/services/misc/nix-daemon.nix +++ b/nixos/modules/services/misc/nix-daemon.nix @@ -88,7 +88,7 @@ in }; maxJobs = mkOption { - type = types.int; + type = types.either types.int (types.enum ["auto"]); default = 1; example = 64; description = '' @@ -345,7 +345,6 @@ in type = types.listOf types.str; default = [ - "$HOME/.nix-defexpr/channels" "nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos" "nixos-config=/etc/nixos/configuration.nix" "/nix/var/nix/profiles/per-user/root/channels" @@ -436,7 +435,7 @@ in # Set up the environment variables for running Nix. environment.sessionVariables = cfg.envVars // - { NIX_PATH = concatStringsSep ":" cfg.nixPath; + { NIX_PATH = cfg.nixPath; }; environment.extraInit = optionalString (!isNix20) @@ -446,6 +445,10 @@ in if [ "$USER" != root -o ! -w /nix/var/nix/db ]; then export NIX_REMOTE=daemon fi + '' + '' + if [ -e "$HOME/.nix-defexpr/channels" ]; then + export NIX_PATH="$HOME/.nix-defexpr/channels''${NIX_PATH:+:$NIX_PATH}" + fi ''; nix.nrBuildUsers = mkDefault (lib.max 32 cfg.maxJobs); diff --git a/nixos/modules/services/misc/nixos-manual.nix b/nixos/modules/services/misc/nixos-manual.nix index 3916c3052e8b..df3e71c80dea 100644 --- a/nixos/modules/services/misc/nixos-manual.nix +++ b/nixos/modules/services/misc/nixos-manual.nix @@ -1,85 +1,21 @@ -# This module includes the NixOS man-pages in the system environment, -# and optionally starts a browser that shows the NixOS manual on one -# of the virtual consoles. The latter is useful for the installation +# This module optionally starts a browser that shows the NixOS manual +# on one of the virtual consoles which is useful for the installation # CD. -{ config, lib, pkgs, baseModules, ... }: +{ config, lib, pkgs, ... }: with lib; let - cfg = config.services.nixosManual; - - /* For the purpose of generating docs, evaluate options with each derivation - in `pkgs` (recursively) replaced by a fake with path "\${pkgs.attribute.path}". - It isn't perfect, but it seems to cover a vast majority of use cases. - Caveat: even if the package is reached by a different means, - the path above will be shown and not e.g. `${config.services.foo.package}`. */ - manual = import ../../../doc/manual rec { - inherit pkgs config; - version = config.system.nixos.release; - revision = "release-${version}"; - options = - let - scrubbedEval = evalModules { - modules = [ { nixpkgs.localSystem = config.nixpkgs.localSystem; } ] ++ baseModules; - args = (config._module.args) // { modules = [ ]; }; - specialArgs = { pkgs = scrubDerivations "pkgs" pkgs; }; - }; - scrubDerivations = namePrefix: pkgSet: mapAttrs - (name: value: - let wholeName = "${namePrefix}.${name}"; in - if isAttrs value then - scrubDerivations wholeName value - // (optionalAttrs (isDerivation value) { outPath = "\${${wholeName}}"; }) - else value - ) - pkgSet; - in scrubbedEval.options; - }; - - entry = "${manual.manual}/share/doc/nixos/index.html"; - - helpScript = pkgs.writeScriptBin "nixos-help" - '' - #! ${pkgs.runtimeShell} -e - browser="$BROWSER" - if [ -z "$browser" ]; then - browser="$(type -P xdg-open || true)" - if [ -z "$browser" ]; then - browser="$(type -P w3m || true)" - if [ -z "$browser" ]; then - echo "$0: unable to start a web browser; please set \$BROWSER" - exit 1 - fi - fi - fi - exec "$browser" ${entry} - ''; - - desktopItem = pkgs.makeDesktopItem { - name = "nixos-manual"; - desktopName = "NixOS Manual"; - genericName = "View NixOS documentation in a web browser"; - icon = "nix-snowflake"; - exec = "${helpScript}/bin/nixos-help"; - categories = "System"; - }; + cfgd = config.documentation; in { options = { - services.nixosManual.enable = mkOption { - type = types.bool; - default = true; - description = '' - Whether to build the NixOS manual pages. - ''; - }; - + # TODO(@oxij): rename this to `.enable` eventually. services.nixosManual.showManual = mkOption { type = types.bool; default = false; @@ -108,37 +44,30 @@ in }; - config = mkIf cfg.enable { - - system.build.manual = manual; - - environment.systemPackages = [] - ++ optionals config.services.xserver.enable [ desktopItem pkgs.nixos-icons ] - ++ optional config.documentation.man.enable manual.manpages - ++ optionals config.documentation.doc.enable [ manual.manual helpScript ]; - - boot.extraTTYs = mkIf cfg.showManual ["tty${toString cfg.ttyNumber}"]; - - systemd.services = optionalAttrs cfg.showManual - { "nixos-manual" = - { description = "NixOS Manual"; - wantedBy = [ "multi-user.target" ]; - serviceConfig = - { ExecStart = "${cfg.browser} ${entry}"; - StandardInput = "tty"; - StandardOutput = "tty"; - TTYPath = "/dev/tty${toString cfg.ttyNumber}"; - TTYReset = true; - TTYVTDisallocate = true; - Restart = "always"; - }; + config = mkMerge [ + (mkIf cfg.showManual { + assertions = singleton { + assertion = cfgd.enable && cfgd.nixos.enable; + message = "Can't enable `services.nixosManual.showManual` without `documentation.nixos.enable`"; + }; + }) + (mkIf (cfg.showManual && cfgd.enable && cfgd.nixos.enable) { + boot.extraTTYs = [ "tty${toString cfg.ttyNumber}" ]; + + systemd.services."nixos-manual" = { + description = "NixOS Manual"; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${cfg.browser} ${config.system.build.manual.manualHTMLIndex}"; + StandardInput = "tty"; + StandardOutput = "tty"; + TTYPath = "/dev/tty${toString cfg.ttyNumber}"; + TTYReset = true; + TTYVTDisallocate = true; + Restart = "always"; }; }; - - services.mingetty.helpLine = "\nRun `nixos-help` " - + lib.optionalString cfg.showManual "or press <Alt-F${toString cfg.ttyNumber}> " - + "for the NixOS manual."; - - }; + }) + ]; } diff --git a/nixos/modules/services/misc/redmine.nix b/nixos/modules/services/misc/redmine.nix index 9a9424449f80..f763ba21d0b2 100644 --- a/nixos/modules/services/misc/redmine.nix +++ b/nixos/modules/services/misc/redmine.nix @@ -1,121 +1,124 @@ { config, lib, pkgs, ... }: -# TODO: support non-postgresql - with lib; let cfg = config.services.redmine; - ruby = pkgs.ruby; + bundle = "${pkgs.redmine}/share/redmine/bin/bundle"; - databaseYml = '' + databaseYml = pkgs.writeText "database.yml" '' production: - adapter: postgresql - database: ${cfg.databaseName} - host: ${cfg.databaseHost} - password: ${cfg.databasePassword} - username: ${cfg.databaseUsername} - encoding: utf8 + adapter: ${cfg.database.type} + database: ${cfg.database.name} + host: ${cfg.database.host} + port: ${toString cfg.database.port} + username: ${cfg.database.user} + password: #dbpass# ''; - configurationYml = '' + configurationYml = pkgs.writeText "configuration.yml" '' default: - # Absolute path to the directory where attachments are stored. - # The default is the 'files' directory in your Redmine instance. - # Your Redmine instance needs to have write permission on this - # directory. - # Examples: - # attachments_storage_path: /var/redmine/files - # attachments_storage_path: D:/redmine/files - attachments_storage_path: ${cfg.stateDir}/files - - # Absolute path to the SCM commands errors (stderr) log file. - # The default is to log in the 'log' directory of your Redmine instance. - # Example: - # scm_stderr_log_file: /var/log/redmine_scm_stderr.log - scm_stderr_log_file: ${cfg.stateDir}/redmine_scm_stderr.log - - ${cfg.extraConfig} + scm_subversion_command: ${pkgs.subversion}/bin/svn + scm_mercurial_command: ${pkgs.mercurial}/bin/hg + scm_git_command: ${pkgs.gitAndTools.git}/bin/git + scm_cvs_command: ${pkgs.cvs}/bin/cvs + scm_bazaar_command: ${pkgs.bazaar}/bin/bzr + scm_darcs_command: ${pkgs.darcs}/bin/darcs + + ${cfg.extraConfig} ''; - unpackTheme = unpack "theme"; - unpackPlugin = unpack "plugin"; - unpack = id: (name: source: - pkgs.stdenv.mkDerivation { - name = "redmine-${id}-${name}"; - buildInputs = [ pkgs.unzip ]; - buildCommand = '' - mkdir -p $out - cd $out - unpackFile ${source} - ''; - }); - -in { +in +{ options = { services.redmine = { enable = mkOption { type = types.bool; default = false; - description = '' - Enable the redmine service. - ''; + description = "Enable the Redmine service."; }; - stateDir = mkOption { + user = mkOption { type = types.str; - default = "/var/redmine"; - description = "The state directory, logs and plugins are stored here"; - }; - - extraConfig = mkOption { - type = types.lines; - default = ""; - description = "Extra configuration in configuration.yml"; - }; - - themes = mkOption { - type = types.attrsOf types.path; - default = {}; - description = "Set of themes"; + default = "redmine"; + description = "User under which Redmine is ran."; }; - plugins = mkOption { - type = types.attrsOf types.path; - default = {}; - description = "Set of plugins"; + group = mkOption { + type = types.str; + default = "redmine"; + description = "Group under which Redmine is ran."; }; - #databaseType = mkOption { - # type = types.str; - # default = "postgresql"; - # description = "Type of database"; - #}; - - databaseHost = mkOption { + stateDir = mkOption { type = types.str; - default = "127.0.0.1"; - description = "Database hostname"; + default = "/var/lib/redmine"; + description = "The state directory, logs and plugins are stored here."; }; - databasePassword = mkOption { - type = types.str; + extraConfig = mkOption { + type = types.lines; default = ""; - description = "Database user password"; - }; + description = '' + Extra configuration in configuration.yml. - databaseName = mkOption { - type = types.str; - default = "redmine"; - description = "Database name"; + See https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration + ''; }; - databaseUsername = mkOption { - type = types.str; - default = "redmine"; - description = "Database user"; + database = { + type = mkOption { + type = types.enum [ "mysql2" "postgresql" ]; + example = "postgresql"; + default = "mysql2"; + 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 = "redmine"; + description = "Database name."; + }; + + user = mkOption { + type = types.str; + default = "redmine"; + description = "Database user."; + }; + + password = mkOption { + type = types.str; + default = ""; + description = '' + The password corresponding to <option>database.user</option>. + Warning: this is stored in cleartext in the Nix store! + Use <option>database.passwordFile</option> instead. + ''; + }; + + passwordFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/keys/redmine-dbpassword"; + description = '' + A file containing the password corresponding to + <option>database.user</option>. + ''; + }; }; }; }; @@ -123,99 +126,106 @@ in { config = mkIf cfg.enable { assertions = [ - { assertion = cfg.databasePassword != ""; - message = "services.redmine.databasePassword must be set"; + { assertion = cfg.database.passwordFile != null || cfg.database.password != ""; + message = "either services.redmine.database.passwordFile or services.redmine.database.password must be set"; } ]; - users.users = [ - { name = "redmine"; - group = "redmine"; - uid = config.ids.uids.redmine; - } ]; - - users.groups = [ - { name = "redmine"; - gid = config.ids.gids.redmine; - } ]; + environment.systemPackages = [ pkgs.redmine ]; systemd.services.redmine = { - after = [ "network.target" "postgresql.service" ]; + after = [ "network.target" (if cfg.database.type == "mysql2" then "mysql.service" else "postgresql.service") ]; wantedBy = [ "multi-user.target" ]; + environment.HOME = "${pkgs.redmine}/share/redmine"; environment.RAILS_ENV = "production"; - environment.RAILS_ETC = "${cfg.stateDir}/config"; - environment.RAILS_LOG = "${cfg.stateDir}/log"; - environment.RAILS_VAR = "${cfg.stateDir}/var"; environment.RAILS_CACHE = "${cfg.stateDir}/cache"; - environment.RAILS_PLUGINS = "${cfg.stateDir}/plugins"; - environment.RAILS_PUBLIC = "${cfg.stateDir}/public"; - environment.RAILS_TMP = "${cfg.stateDir}/tmp"; - environment.SCHEMA = "${cfg.stateDir}/cache/schema.db"; - environment.HOME = "${pkgs.redmine}/share/redmine"; environment.REDMINE_LANG = "en"; - environment.GEM_HOME = "${pkgs.redmine}/share/redmine/vendor/bundle/ruby/1.9.1"; - environment.GEM_PATH = "${pkgs.bundler}/${pkgs.bundler.ruby.gemPath}"; + environment.SCHEMA = "${cfg.stateDir}/cache/schema.db"; path = with pkgs; [ imagemagickBig - subversion - mercurial - cvs - config.services.postgresql.package bazaar + cvs + darcs gitAndTools.git - # once we build binaries for darc enable it - #darcs + mercurial + subversion ]; preStart = '' - # TODO: use env vars - for i in plugins public/plugin_assets db files log config cache var/files tmp; do + # start with a fresh config directory every time + rm -rf ${cfg.stateDir}/config + cp -r ${pkgs.redmine}/share/redmine/config.dist ${cfg.stateDir}/config + + # create the basic state directory layout pkgs.redmine expects + mkdir -p /run/redmine + + for i in config files log plugins tmp; do mkdir -p ${cfg.stateDir}/$i + ln -fs ${cfg.stateDir}/$i /run/redmine/$i done - chown -R redmine:redmine ${cfg.stateDir} - chmod -R 755 ${cfg.stateDir} + # ensure cache directory exists for db:migrate command + mkdir -p ${cfg.stateDir}/cache - rm -rf ${cfg.stateDir}/public/* - cp -R ${pkgs.redmine}/share/redmine/public/* ${cfg.stateDir}/public/ - for theme in ${concatStringsSep " " (mapAttrsToList unpackTheme cfg.themes)}; do - ln -fs $theme/* ${cfg.stateDir}/public/themes/ - done + # link in the application configuration + ln -fs ${configurationYml} ${cfg.stateDir}/config/configuration.yml - rm -rf ${cfg.stateDir}/plugins/* - for plugin in ${concatStringsSep " " (mapAttrsToList unpackPlugin cfg.plugins)}; do - ln -fs $plugin/* ${cfg.stateDir}/plugins/''${plugin##*-redmine-plugin-} - done + chmod -R ug+rwX,o-rwx+x ${cfg.stateDir}/ - ln -fs ${pkgs.writeText "database.yml" databaseYml} ${cfg.stateDir}/config/database.yml - ln -fs ${pkgs.writeText "configuration.yml" configurationYml} ${cfg.stateDir}/config/configuration.yml + # handle database.passwordFile + DBPASS=$(head -n1 ${cfg.database.passwordFile}) + cp -f ${databaseYml} ${cfg.stateDir}/config/database.yml + sed -e "s,#dbpass#,$DBPASS,g" -i ${cfg.stateDir}/config/database.yml + chmod 440 ${cfg.stateDir}/config/database.yml - if [ "${cfg.databaseHost}" = "127.0.0.1" ]; then - if ! test -e "${cfg.stateDir}/db-created"; then - psql postgres -c "CREATE ROLE redmine WITH LOGIN NOCREATEDB NOCREATEROLE ENCRYPTED PASSWORD '${cfg.databasePassword}'" - ${config.services.postgresql.package}/bin/createdb --owner redmine redmine || true - touch "${cfg.stateDir}/db-created" - fi + # 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 - cd ${pkgs.redmine}/share/redmine/ - ${ruby}/bin/rake db:migrate - ${ruby}/bin/rake redmine:plugins:migrate - ${ruby}/bin/rake redmine:load_default_data - ${ruby}/bin/rake generate_secret_token + # ensure everything is owned by ${cfg.user} + chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir} + + ${bundle} exec rake db:migrate + ${bundle} exec rake redmine:load_default_data ''; serviceConfig = { PermissionsStartOnly = true; # preStart must be run as root Type = "simple"; - User = "redmine"; - Group = "redmine"; + User = cfg.user; + Group = cfg.group; TimeoutSec = "300"; WorkingDirectory = "${pkgs.redmine}/share/redmine"; - ExecStart="${ruby}/bin/ruby ${pkgs.redmine}/share/redmine/script/rails server webrick -e production -P ${cfg.stateDir}/redmine.pid"; + ExecStart="${bundle} exec rails server webrick -e production -P ${cfg.stateDir}/redmine.pid"; }; }; + users.extraUsers = optionalAttrs (cfg.user == "redmine") (singleton + { name = "redmine"; + group = cfg.group; + home = cfg.stateDir; + createHome = true; + uid = config.ids.uids.redmine; + }); + + users.extraGroups = optionalAttrs (cfg.group == "redmine") (singleton + { name = "redmine"; + gid = config.ids.gids.redmine; + }); + + warnings = optional (cfg.database.password != "") + ''config.services.redmine.database.password will be stored as plaintext + in the Nix store. Use database.passwordFile instead.''; + + # Create database passwordFile default when password is configured. + services.redmine.database.passwordFile = + (mkDefault (toString (pkgs.writeTextFile { + name = "redmine-database-password"; + text = cfg.database.password; + }))); + }; } diff --git a/nixos/modules/services/misc/sickbeard.nix b/nixos/modules/services/misc/sickbeard.nix new file mode 100644 index 000000000000..5cfbbe516ae1 --- /dev/null +++ b/nixos/modules/services/misc/sickbeard.nix @@ -0,0 +1,92 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + name = "sickbeard"; + + cfg = config.services.sickbeard; + sickbeard = cfg.package; + +in +{ + + ###### interface + + options = { + services.sickbeard = { + enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable the sickbeard server."; + }; + package = mkOption { + type = types.package; + default = pkgs.sickbeard; + example = literalExample "pkgs.sickrage"; + description ='' + Enable <literal>pkgs.sickrage</literal> or <literal>pkgs.sickgear</literal> + as an alternative to SickBeard + ''; + }; + dataDir = mkOption { + type = types.path; + default = "/var/lib/${name}"; + description = "Path where to store data files."; + }; + configFile = mkOption { + type = types.path; + default = "${cfg.dataDir}/config.ini"; + description = "Path to config file."; + }; + port = mkOption { + type = types.ints.u16; + default = 8081; + description = "Port to bind to."; + }; + user = mkOption { + type = types.str; + default = name; + description = "User to run the service as"; + }; + group = mkOption { + type = types.str; + default = name; + description = "Group to run the service as"; + }; + }; + }; + + + ###### implementation + + config = mkIf cfg.enable { + + users.users = optionalAttrs (cfg.user == name) (singleton { + name = name; + uid = config.ids.uids.sickbeard; + group = cfg.group; + description = "sickbeard user"; + home = cfg.dataDir; + createHome = true; + }); + + users.groups = optionalAttrs (cfg.group == name) (singleton { + name = name; + gid = config.ids.gids.sickbeard; + }); + + systemd.services.sickbeard = { + description = "Sickbeard Server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + User = cfg.user; + Group = cfg.group; + ExecStart = "${sickbeard}/SickBeard.py --datadir ${cfg.dataDir} --config ${cfg.configFile} --port ${toString cfg.port}"; + }; + }; + }; +} diff --git a/nixos/modules/services/misc/synergy.nix b/nixos/modules/services/misc/synergy.nix index 7e8eadbe5f37..b89cb41ac3ad 100644 --- a/nixos/modules/services/misc/synergy.nix +++ b/nixos/modules/services/misc/synergy.nix @@ -83,20 +83,20 @@ in config = mkMerge [ (mkIf cfgC.enable { - systemd.services."synergy-client" = { - after = [ "network.target" ]; + systemd.user.services."synergy-client" = { + after = [ "network.target" "graphical-session.target" ]; description = "Synergy client"; - wantedBy = optional cfgC.autoStart "multi-user.target"; + 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.services."synergy-server" = { - after = [ "network.target" ]; + systemd.user.services."synergy-server" = { + after = [ "network.target" "graphical-session.target" ]; description = "Synergy server"; - wantedBy = optional cfgS.autoStart "multi-user.target"; + 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"; diff --git a/nixos/modules/services/misc/taskserver/doc.xml b/nixos/modules/services/misc/taskserver/doc.xml index 75493ac1394f..5eac8d9ef784 100644 --- a/nixos/modules/services/misc/taskserver/doc.xml +++ b/nixos/modules/services/misc/taskserver/doc.xml @@ -2,101 +2,93 @@ xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" xml:id="module-taskserver"> - - <title>Taskserver</title> + <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 is the server component of - <link xlink:href="https://taskwarrior.org/">Taskwarrior</link>, a free and - open source todo list application. + 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> - <emphasis>Upstream documentation:</emphasis> - <link xlink:href="https://taskwarrior.org/docs/#taskd"/> + 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> - <section> - <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> + <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> - <section> - <title>The nixos-taskserver tool</title> + <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> + 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> + 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> + 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> - <title>Declarative/automatic CA management</title> + <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> + 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> + 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: + <para> + For example, let's say you have the following configuration: <screen> { <xref linkend="opt-services.taskserver.enable"/> = true; @@ -105,40 +97,39 @@ <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> + 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: + <para> + Now in order to import the <literal>alice</literal> user to another machine + <literal>alicebox</literal>, all we need to do is something like this: <screen> $ ssh server nixos-taskserver user export my-company alice | sh </screen> - Of course, if no SSH daemon is available on the server you can also copy - & paste it directly into a shell. - </para> + Of course, if no SSH daemon is available on the server you can also copy + & paste it directly into a shell. + </para> - <para> - After this step the user should be set up and you can start synchronising - your tasks for the first time with <command>task sync init</command> on - <literal>alicebox</literal>. - </para> + <para> + 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> - <title>Manual CA management</title> + <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> + <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/nixos/modules/services/misc/weechat.nix b/nixos/modules/services/misc/weechat.nix new file mode 100644 index 000000000000..1fcfb440485d --- /dev/null +++ b/nixos/modules/services/misc/weechat.nix @@ -0,0 +1,56 @@ +{ 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 ${pkgs.screen}/bin/screen -Dm -S ${cfg.sessionName} ${cfg.binary}"; + wantedBy = [ "multi-user.target" ]; + wants = [ "network.target" ]; + }; + }; + + meta.doc = ./weechat.xml; +} diff --git a/nixos/modules/services/misc/weechat.xml b/nixos/modules/services/misc/weechat.xml new file mode 100644 index 000000000000..9c9ee0448c92 --- /dev/null +++ b/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> + <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> + <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 -r 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/nixos/modules/services/monitoring/datadog-agent.nix b/nixos/modules/services/monitoring/datadog-agent.nix new file mode 100644 index 000000000000..8fd3455a238f --- /dev/null +++ b/nixos/modules/services/monitoring/datadog-agent.nix @@ -0,0 +1,251 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.datadog-agent; + + ddConf = { + dd_url = "https://app.datadoghq.com"; + skip_ssl_validation = "no"; + confd_path = "/etc/datadog-agent/conf.d"; + additional_checksd = "/etc/datadog-agent/checks.d"; + use_dogstatsd = true; + } + // optionalAttrs (cfg.logLevel != null) { log_level = cfg.logLevel; } + // optionalAttrs (cfg.hostname != null) { inherit (cfg) hostname; } + // optionalAttrs (cfg.tags != null ) { tags = concatStringsSep ", " cfg.tags; } + // optionalAttrs (cfg.enableLiveProcessCollection) { process_config = { enabled = "true"; }; } + // cfg.extraConfig; + + # Generate Datadog configuration files for each configured checks. + # This works because check configurations have predictable paths, + # and because JSON is a valid subset of YAML. + makeCheckConfigs = entries: mapAttrsToList (name: conf: { + source = pkgs.writeText "${name}-check-conf.yaml" (builtins.toJSON conf); + target = "datadog-agent/conf.d/${name}.d/conf.yaml"; + }) entries; + + defaultChecks = { + disk = cfg.diskCheck; + network = cfg.networkCheck; + }; + + # Assemble all check configurations and the top-level agent + # configuration. + etcfiles = with pkgs; with builtins; [{ + source = writeText "datadog.yaml" (toJSON ddConf); + target = "datadog-agent/datadog.yaml"; + }] ++ makeCheckConfigs (cfg.checks // defaultChecks); + + # Apply the configured extraIntegrations to the provided agent + # package. See the documentation of `dd-agent/integrations-core.nix` + # for detailed information on this. + datadogPkg = cfg.package.overrideAttrs(_: { + python = (pkgs.datadog-integrations-core cfg.extraIntegrations).python; + }); +in { + options.services.datadog-agent = { + enable = mkOption { + description = '' + Whether to enable the datadog-agent v6 monitoring service + ''; + default = false; + type = types.bool; + }; + + package = mkOption { + default = pkgs.datadog-agent; + defaultText = "pkgs.datadog-agent"; + description = '' + Which DataDog v6 agent package to use. Note that the provided + package is expected to have an overridable `python`-attribute + which configures the Python environment with the Datadog + checks. + ''; + type = types.package; + }; + + apiKeyFile = mkOption { + description = '' + Path to a file containing the Datadog API key to associate the + agent with your account. + ''; + example = "/run/keys/datadog_api_key"; + type = types.path; + }; + + tags = mkOption { + description = "The tags to mark this Datadog agent"; + example = [ "test" "service" ]; + default = null; + type = types.nullOr (types.listOf types.str); + }; + + hostname = mkOption { + description = "The hostname to show in the Datadog dashboard (optional)"; + default = null; + example = "mymachine.mydomain"; + type = types.uniq (types.nullOr types.string); + }; + + logLevel = mkOption { + description = "Logging verbosity."; + default = null; + type = types.nullOr (types.enum ["DEBUG" "INFO" "WARN" "ERROR"]); + }; + + extraIntegrations = mkOption { + default = {}; + type = types.attrs; + + description = '' + Extra integrations from the Datadog core-integrations + repository that should be built and included. + + By default the included integrations are disk, mongo, network, + nginx and postgres. + + To include additional integrations the name of the derivation + and a function to filter its dependencies from the Python + package set must be provided. + ''; + + example = { + ntp = (pythonPackages: [ pythonPackages.ntplib ]); + }; + }; + + extraConfig = mkOption { + default = {}; + type = types.attrs; + description = '' + Extra configuration options that will be merged into the + main config file <filename>datadog.yaml</filename>. + ''; + }; + + enableLiveProcessCollection = mkOption { + description = '' + Whether to enable the live process collection agent. + ''; + default = false; + type = types.bool; + }; + checks = mkOption { + description = '' + Configuration for all Datadog checks. Keys of this attribute + set will be used as the name of the check to create the + appropriate configuration in `conf.d/$check.d/conf.yaml`. + + The configuration is converted into JSON from the plain Nix + language configuration, meaning that you should write + configuration adhering to Datadog's documentation - but in Nix + language. + + Refer to the implementation of this module (specifically the + definition of `defaultChecks`) for an example. + + Note: The 'disk' and 'network' check are configured in + separate options because they exist by default. Attempting to + override their configuration here will have no effect. + ''; + + example = { + http_check = { + init_config = null; # sic! + instances = [ + { + name = "some-service"; + url = "http://localhost:1337/healthz"; + tags = [ "some-service" ]; + } + ]; + }; + }; + + default = {}; + + # sic! The structure of the values is up to the check, so we can + # not usefully constrain the type further. + type = with types; attrsOf attrs; + }; + + diskCheck = mkOption { + description = "Disk check config"; + type = types.attrs; + default = { + init_config = {}; + instances = [ { use-mount = "no"; } ]; + }; + }; + + networkCheck = mkOption { + description = "Network check config"; + type = types.attrs; + default = { + init_config = {}; + # Network check only supports one configured instance + instances = [ { collect_connection_state = false; + excluded_interfaces = [ "lo" "lo0" ]; } ]; + }; + }; + }; + config = mkIf cfg.enable { + environment.systemPackages = [ datadogPkg pkgs.sysstat pkgs.procps ]; + + users.extraUsers.datadog = { + description = "Datadog Agent User"; + uid = config.ids.uids.datadog; + group = "datadog"; + home = "/var/log/datadog/"; + createHome = true; + }; + + users.extraGroups.datadog.gid = config.ids.gids.datadog; + + systemd.services = let + makeService = attrs: recursiveUpdate { + path = [ datadogPkg pkgs.python pkgs.sysstat pkgs.procps ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + User = "datadog"; + Group = "datadog"; + Restart = "always"; + RestartSec = 2; + }; + restartTriggers = [ datadogPkg ] ++ map (etc: etc.source) etcfiles; + } attrs; + in { + datadog-agent = makeService { + description = "Datadog agent monitor"; + preStart = '' + chown -R datadog: /etc/datadog-agent + rm -f /etc/datadog-agent/auth_token + ''; + script = '' + export DD_API_KEY=$(head -n 1 ${cfg.apiKeyFile}) + exec ${datadogPkg}/bin/agent start -c /etc/datadog-agent/datadog.yaml + ''; + serviceConfig.PermissionsStartOnly = true; + }; + + dd-jmxfetch = lib.mkIf (lib.hasAttr "jmx" cfg.checks) (makeService { + description = "Datadog JMX Fetcher"; + path = [ datadogPkg pkgs.python pkgs.sysstat pkgs.procps pkgs.jdk ]; + serviceConfig.ExecStart = "${datadogPkg}/bin/dd-jmxfetch"; + }); + + datadog-process-agent = lib.mkIf cfg.enableLiveProcessCollection (makeService { + description = "Datadog Live Process Agent"; + path = [ ]; + script = '' + export DD_API_KEY=$(head -n 1 ${cfg.apiKeyFile}) + ${pkgs.datadog-process-agent}/bin/agent --config /etc/datadog-agent/datadog.yaml + ''; + }); + }; + + environment.etc = etcfiles; + }; +} diff --git a/nixos/modules/services/monitoring/dd-agent/dd-agent.nix b/nixos/modules/services/monitoring/dd-agent/dd-agent.nix index cf65b6c28cf2..abc8d65d58f2 100644 --- a/nixos/modules/services/monitoring/dd-agent/dd-agent.nix +++ b/nixos/modules/services/monitoring/dd-agent/dd-agent.nix @@ -114,13 +114,22 @@ let in { options.services.dd-agent = { enable = mkOption { - description = "Whether to enable the dd-agent montioring service"; + description = '' + Whether to enable the dd-agent v5 monitoring service. + For datadog-agent v6, see <option>services.datadog-agent.enable</option>. + ''; default = false; type = types.bool; }; api_key = mkOption { - description = "The Datadog API key to associate the agent with your account"; + description = '' + The Datadog API key to associate the agent with your account. + + Warning: this key is stored in cleartext within the world-readable + Nix store! Consider using the new v6 + <option>services.datadog-agent</option> module instead. + ''; example = "ae0aa6a8f08efa988ba0a17578f009ab"; type = types.str; }; @@ -188,48 +197,41 @@ in { users.groups.datadog.gid = config.ids.gids.datadog; - systemd.services.dd-agent = { - description = "Datadog agent monitor"; - path = [ pkgs."dd-agent" pkgs.python pkgs.sysstat pkgs.procps pkgs.gohai ]; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - ExecStart = "${pkgs.dd-agent}/bin/dd-agent foreground"; - User = "datadog"; - Group = "datadog"; - Restart = "always"; - RestartSec = 2; + systemd.services = let + makeService = attrs: recursiveUpdate { + path = [ pkgs.dd-agent pkgs.python pkgs.sysstat pkgs.procps pkgs.gohai ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + User = "datadog"; + Group = "datadog"; + Restart = "always"; + RestartSec = 2; + PrivateTmp = true; + }; + restartTriggers = [ pkgs.dd-agent ddConf diskConfig networkConfig postgresqlConfig nginxConfig mongoConfig jmxConfig processConfig ]; + } attrs; + in { + dd-agent = makeService { + description = "Datadog agent monitor"; + serviceConfig.ExecStart = "${pkgs.dd-agent}/bin/dd-agent foreground"; }; - restartTriggers = [ pkgs.dd-agent ddConf diskConfig networkConfig postgresqlConfig nginxConfig mongoConfig jmxConfig processConfig ]; - }; - systemd.services.dogstatsd = { - description = "Datadog statsd"; - path = [ pkgs."dd-agent" pkgs.python pkgs.procps ]; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - ExecStart = "${pkgs.dd-agent}/bin/dogstatsd start"; - User = "datadog"; - Group = "datadog"; - Type = "forking"; - PIDFile = "/tmp/dogstatsd.pid"; - Restart = "always"; - RestartSec = 2; + dogstatsd = makeService { + description = "Datadog statsd"; + environment.TMPDIR = "/run/dogstatsd"; + serviceConfig = { + ExecStart = "${pkgs.dd-agent}/bin/dogstatsd start"; + Type = "forking"; + PIDFile = "/run/dogstatsd/dogstatsd.pid"; + RuntimeDirectory = "dogstatsd"; + }; }; - restartTriggers = [ pkgs.dd-agent ddConf diskConfig networkConfig postgresqlConfig nginxConfig mongoConfig jmxConfig processConfig ]; - }; - systemd.services.dd-jmxfetch = lib.mkIf (cfg.jmxConfig != null) { - description = "Datadog JMX Fetcher"; - path = [ pkgs."dd-agent" pkgs.python pkgs.sysstat pkgs.procps pkgs.jdk ]; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - ExecStart = "${pkgs.dd-agent}/bin/dd-jmxfetch"; - User = "datadog"; - Group = "datadog"; - Restart = "always"; - RestartSec = 2; + dd-jmxfetch = lib.mkIf (cfg.jmxConfig != null) { + description = "Datadog JMX Fetcher"; + path = [ pkgs.dd-agent pkgs.python pkgs.sysstat pkgs.procps pkgs.jdk ]; + serviceConfig.ExecStart = "${pkgs.dd-agent}/bin/dd-jmxfetch"; }; - restartTriggers = [ pkgs.dd-agent ddConf diskConfig networkConfig postgresqlConfig nginxConfig mongoConfig jmxConfig ]; }; environment.etc = etcfiles; diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix index 3e801f9b838d..c30647f5460b 100644 --- a/nixos/modules/services/monitoring/grafana.nix +++ b/nixos/modules/services/monitoring/grafana.nix @@ -235,7 +235,7 @@ in { but without GF_ prefix ''; default = {}; - type = types.attrsOf types.str; + type = with types; attrsOf (either str path); }; }; diff --git a/nixos/modules/services/monitoring/incron.nix b/nixos/modules/services/monitoring/incron.nix new file mode 100644 index 000000000000..1789fd9f2051 --- /dev/null +++ b/nixos/modules/services/monitoring/incron.nix @@ -0,0 +1,98 @@ + +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.incron; + +in + +{ + options = { + + services.incron = { + + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable the incron daemon. + + Note that commands run under incrontab only support common Nix profiles for the <envar>PATH</envar> provided variable. + ''; + }; + + allow = mkOption { + type = types.nullOr (types.listOf types.str); + default = null; + description = '' + Users allowed to use incrontab. + + If empty then no user will be allowed to have their own incrontab. + If <literal>null</literal> then will defer to <option>deny</option>. + If both <option>allow</option> and <option>deny</option> are null + then all users will be allowed to have their own incrontab. + ''; + }; + + deny = mkOption { + type = types.nullOr (types.listOf types.str); + default = null; + description = "Users forbidden from using incrontab."; + }; + + systab = mkOption { + type = types.lines; + default = ""; + description = "The system incrontab contents."; + example = '' + /var/mail IN_CLOSE_WRITE abc $@/$# + /tmp IN_ALL_EVENTS efg $@/$# $& + ''; + }; + + extraPackages = mkOption { + type = types.listOf types.package; + default = []; + example = literalExample "[ pkgs.rsync ]"; + description = "Extra packages available to the system incrontab."; + }; + + }; + + }; + + config = mkIf cfg.enable { + + warnings = optional (cfg.allow != null && cfg.deny != null) + ''If `services.incron.allow` is set then `services.incron.deny` will be ignored.''; + + environment.systemPackages = [ pkgs.incron ]; + + security.wrappers.incrontab.source = "${pkgs.incron}/bin/incrontab"; + + # incron won't read symlinks + environment.etc."incron.d/system" = { + mode = "0444"; + text = cfg.systab; + }; + environment.etc."incron.allow" = mkIf (cfg.allow != null) { + text = concatStringsSep "\n" cfg.allow; + }; + environment.etc."incron.deny" = mkIf (cfg.deny != null) { + text = concatStringsSep "\n" cfg.deny; + }; + + systemd.services.incron = { + description = "File System Events Scheduler"; + wantedBy = [ "multi-user.target" ]; + path = cfg.extraPackages; + serviceConfig.PIDFile = "/run/incrond.pid"; + serviceConfig.ExecStartPre = "${pkgs.coreutils}/bin/mkdir -m 710 -p /var/spool/incron"; + serviceConfig.ExecStart = "${pkgs.incron}/bin/incrond --foreground"; + }; + }; + +} diff --git a/nixos/modules/services/monitoring/prometheus/alertmanager.nix b/nixos/modules/services/monitoring/prometheus/alertmanager.nix index 8a47c9f1e7d8..8a44cf7fd8f6 100644 --- a/nixos/modules/services/monitoring/prometheus/alertmanager.nix +++ b/nixos/modules/services/monitoring/prometheus/alertmanager.nix @@ -9,6 +9,15 @@ let if cfg.configText != null then pkgs.writeText "alertmanager.yml" cfg.configText else mkConfigFile; + cmdlineArgs = cfg.extraFlags ++ [ + "--config.file ${alertmanagerYml}" + "--web.listen-address ${cfg.listenAddress}:${toString cfg.port}" + "--log.level ${cfg.logLevel}" + ] ++ (optional (cfg.webExternalUrl != null) + "--web.external-url ${cfg.webExternalUrl}" + ) ++ (optional (cfg.logFormat != null) + "--log.format ${cfg.logFormat}" + ); in { options = { services.prometheus.alertmanager = { @@ -99,6 +108,14 @@ in { Open port in firewall for incoming connections. ''; }; + + extraFlags = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Extra commandline options when launching the Alertmanager. + ''; + }; }; }; @@ -111,11 +128,7 @@ in { after = [ "network.target" ]; script = '' ${pkgs.prometheus-alertmanager.bin}/bin/alertmanager \ - --config.file ${alertmanagerYml} \ - --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ - --log.level ${cfg.logLevel} \ - ${optionalString (cfg.webExternalUrl != null) ''--web.external-url ${cfg.webExternalUrl} \''} - ${optionalString (cfg.logFormat != null) "--log.format ${cfg.logFormat}"} + ${concatStringsSep " \\\n " cmdlineArgs} ''; serviceConfig = { diff --git a/nixos/modules/services/monitoring/prometheus/exporters.xml b/nixos/modules/services/monitoring/prometheus/exporters.xml index 4f0bcb298106..7a0a1bdf2c14 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters.xml +++ b/nixos/modules/services/monitoring/prometheus/exporters.xml @@ -3,13 +3,19 @@ xmlns:xi="http://www.w3.org/2001/XInclude" version="5.0" xml:id="module-services-prometheus-exporters"> + <title>Prometheus exporters</title> + <para> + Prometheus exporters provide metrics for the + <link xlink:href="https://prometheus.io">prometheus monitoring system</link>. + </para> + <section xml:id="module-services-prometheus-exporters-configuration"> + <title>Configuration</title> -<title>Prometheus exporters</title> - -<para>Prometheus exporters provide metrics for the <link xlink:href="https://prometheus.io">prometheus monitoring system</link>.</para> - -<section><title>Configuration</title> - <para>One of the most common exporters is the <link xlink:href="https://github.com/prometheus/node_exporter">node exporter</link>, it provides hardware and OS metrics from the host it's running on. The exporter could be configured as follows: + <para> + One of the most common exporters is the + <link xlink:href="https://github.com/prometheus/node_exporter">node + exporter</link>, it provides hardware and OS metrics from the host it's + running on. The exporter could be configured as follows: <programlisting> services.promtheus.exporters.node = { enable = true; @@ -24,43 +30,88 @@ firewallFilter = "-i br0 -p tcp -m tcp --dport 9100"; }; </programlisting> -It should now serve all metrics from the collectors -that are explicitly enabled and the ones that are -<link xlink:href="https://github.com/prometheus/node_exporter#enabled-by-default">enabled by default</link>, via http under <literal>/metrics</literal>. In this example the firewall should just -allow incoming connections to the exporter's port on the bridge interface <literal>br0</literal> -(this would have to be configured seperately of course). -For more information about configuration see <literal>man configuration.nix</literal> or -search through the <link xlink:href="https://nixos.org/nixos/options.html#prometheus.exporters">available options</link>. -</para> -</section> -<section><title>Adding a new exporter</title> - <para>To add a new exporter, it has to be packaged first (see <literal>nixpkgs/pkgs/servers/monitoring/prometheus/</literal> for examples), then a module can be added. The postfix exporter is used in this example:</para> -<itemizedlist> - <listitem> + It should now serve all metrics from the collectors that are explicitly + enabled and the ones that are + <link xlink:href="https://github.com/prometheus/node_exporter#enabled-by-default">enabled + by default</link>, via http under <literal>/metrics</literal>. In this + example the firewall should just allow incoming connections to the + exporter's port on the bridge interface <literal>br0</literal> (this would + have to be configured seperately of course). For more information about + configuration see <literal>man configuration.nix</literal> or search through + the + <link xlink:href="https://nixos.org/nixos/options.html#prometheus.exporters">available + options</link>. + </para> + </section> + <section xml:id="module-services-prometheus-exporters-new-exporter"> + <title>Adding a new exporter</title> + + <para> + To add a new exporter, it has to be packaged first (see + <literal>nixpkgs/pkgs/servers/monitoring/prometheus/</literal> for + examples), then a module can be added. The postfix exporter is used in this + example: + </para> + + <itemizedlist> + <listitem> <para> - Some default options for all exporters are provided by - <literal>nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix</literal>: + Some default options for all exporters are provided by + <literal>nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix</literal>: </para> - </listitem> - <listitem override='none'> + </listitem> + <listitem override='none'> <itemizedlist> - <listitem><para><literal>enable</literal></para></listitem> - <listitem><para><literal>port</literal></para></listitem> - <listitem><para><literal>listenAddress</literal></para></listitem> - <listitem><para><literal>extraFlags</literal></para></listitem> - <listitem><para><literal>openFirewall</literal></para></listitem> - <listitem><para><literal>firewallFilter</literal></para></listitem> - <listitem><para><literal>user</literal></para></listitem> - <listitem><para><literal>group</literal></para></listitem> + <listitem> + <para> + <literal>enable</literal> + </para> + </listitem> + <listitem> + <para> + <literal>port</literal> + </para> + </listitem> + <listitem> + <para> + <literal>listenAddress</literal> + </para> + </listitem> + <listitem> + <para> + <literal>extraFlags</literal> + </para> + </listitem> + <listitem> + <para> + <literal>openFirewall</literal> + </para> + </listitem> + <listitem> + <para> + <literal>firewallFilter</literal> + </para> + </listitem> + <listitem> + <para> + <literal>user</literal> + </para> + </listitem> + <listitem> + <para> + <literal>group</literal> + </para> + </listitem> </itemizedlist> - </listitem> - <listitem> - <para>As there is already a package available, the module can now be added. - This is accomplished by adding a new file to the - <literal>nixos/modules/services/monitoring/prometheus/exporters/</literal> directory, - which will be called postfix.nix and contains all exporter specific options - and configuration: - <programlisting> + </listitem> + <listitem> + <para> + As there is already a package available, the module can now be added. This + is accomplished by adding a new file to the + <literal>nixos/modules/services/monitoring/prometheus/exporters/</literal> + directory, which will be called postfix.nix and contains all exporter + specific options and configuration: +<programlisting> # nixpgs/nixos/modules/services/prometheus/exporters/postfix.nix { config, lib, pkgs }: @@ -121,15 +172,16 @@ search through the <link xlink:href="https://nixos.org/nixos/options.html#promet } </programlisting> </para> - </listitem> - <listitem> + </listitem> + <listitem> <para> - This should already be enough for the postfix exporter. Additionally one could - now add assertions and conditional default values. This can be done in the - 'meta-module' that combines all exporter definitions and generates the submodules: - <literal>nixpkgs/nixos/modules/services/prometheus/exporters.nix</literal> + This should already be enough for the postfix exporter. Additionally one + could now add assertions and conditional default values. This can be done + in the 'meta-module' that combines all exporter definitions and generates + the submodules: + <literal>nixpkgs/nixos/modules/services/prometheus/exporters.nix</literal> </para> - </listitem> -</itemizedlist> -</section> + </listitem> + </itemizedlist> + </section> </chapter> diff --git a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix index 404cd0a1896b..0d9194124325 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix @@ -60,10 +60,10 @@ in DynamicUser = true; ExecStart = '' ${pkgs.prometheus-snmp-exporter.bin}/bin/snmp_exporter \ - -config.file ${configFile} \ - -log.format ${cfg.logFormat} \ - -log.level ${cfg.logLevel} \ - -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ + --config.file=${configFile} \ + --log.format=${cfg.logFormat} \ + --log.level=${cfg.logLevel} \ + --web.listen-address=${cfg.listenAddress}:${toString cfg.port} \ ${concatStringsSep " \\\n " cfg.extraFlags} ''; }; diff --git a/nixos/modules/services/monitoring/riemann.nix b/nixos/modules/services/monitoring/riemann.nix index 237de53456f9..13d2b1cc0602 100644 --- a/nixos/modules/services/monitoring/riemann.nix +++ b/nixos/modules/services/monitoring/riemann.nix @@ -17,9 +17,9 @@ let launcher = writeScriptBin "riemann" '' #!/bin/sh - exec ${jdk}/bin/java ${concatStringsSep "\n" cfg.extraJavaOpts} \ + exec ${jdk}/bin/java ${concatStringsSep " " cfg.extraJavaOpts} \ -cp ${classpath} \ - riemann.bin ${writeText "riemann-config.clj" riemannConfig} + riemann.bin ${cfg.configFile} ''; in { @@ -37,7 +37,8 @@ in { config = mkOption { type = types.lines; description = '' - Contents of the Riemann configuration file. + Contents of the Riemann configuration file. For more complicated + config you should use configFile. ''; }; configFiles = mkOption { @@ -47,7 +48,15 @@ in { Extra files containing Riemann configuration. These files will be loaded at runtime by Riemann (with Clojure's <literal>load-file</literal> function) at the end of the - configuration. + configuration if you use the config option, this is ignored if you + use configFile. + ''; + }; + configFile = mkOption { + type = types.str; + description = '' + A Riemann config file. Any files in the same directory as this file + will be added to the classpath by Riemann. ''; }; extraClasspathEntries = mkOption { @@ -77,6 +86,10 @@ in { group = "riemann"; }; + services.riemann.configFile = mkDefault ( + writeText "riemann-config.clj" riemannConfig + ); + systemd.services.riemann = { wantedBy = [ "multi-user.target" ]; path = [ inetutils ]; @@ -84,6 +97,7 @@ in { User = "riemann"; ExecStart = "${launcher}/bin/riemann"; }; + serviceConfig.LimitNOFILE = 65536; }; }; diff --git a/nixos/modules/services/monitoring/zabbix-agent.nix b/nixos/modules/services/monitoring/zabbix-agent.nix index 87857225e7d7..426cf9bf86ef 100644 --- a/nixos/modules/services/monitoring/zabbix-agent.nix +++ b/nixos/modules/services/monitoring/zabbix-agent.nix @@ -7,6 +7,8 @@ let cfg = config.services.zabbixAgent; + zabbix = cfg.package; + stateDir = "/var/run/zabbix"; logDir = "/var/log/zabbix"; @@ -44,6 +46,16 @@ in ''; }; + package = mkOption { + type = types.attrs; # Note: pkgs.zabbixXY isn't a derivation, but an attrset of { server = ...; agent = ...; }. + default = pkgs.zabbix; + defaultText = "pkgs.zabbix"; + example = literalExample "pkgs.zabbix34"; + description = '' + The Zabbix package to use. + ''; + }; + server = mkOption { default = "127.0.0.1"; description = '' @@ -87,14 +99,14 @@ in chown zabbix ${stateDir} ${logDir} ''; - serviceConfig.ExecStart = "@${pkgs.zabbix.agent}/sbin/zabbix_agentd zabbix_agentd --config ${configFile}"; + serviceConfig.ExecStart = "@${zabbix.agent}/sbin/zabbix_agentd zabbix_agentd --config ${configFile}"; serviceConfig.Type = "forking"; serviceConfig.RemainAfterExit = true; serviceConfig.Restart = "always"; serviceConfig.RestartSec = 2; }; - environment.systemPackages = [ pkgs.zabbix.agent ]; + environment.systemPackages = [ zabbix.agent ]; }; diff --git a/nixos/modules/services/monitoring/zabbix-server.nix b/nixos/modules/services/monitoring/zabbix-server.nix index f62d55457ed4..5f9fc12832fc 100644 --- a/nixos/modules/services/monitoring/zabbix-server.nix +++ b/nixos/modules/services/monitoring/zabbix-server.nix @@ -103,8 +103,8 @@ in chown zabbix ${stateDir} ${logDir} ${libDir} if ! test -e "${libDir}/db-created"; then - ${pkgs.postgresql}/bin/createuser --no-superuser --no-createdb --no-createrole zabbix || true - ${pkgs.postgresql}/bin/createdb --owner zabbix zabbix || true + ${pkgs.su}/bin/su -s "$SHELL" ${config.services.postgresql.superUser} -c '${pkgs.postgresql}/bin/createuser --no-superuser --no-createdb --no-createrole zabbix' || true + ${pkgs.su}/bin/su -s "$SHELL" ${config.services.postgresql.superUser} -c '${pkgs.postgresql}/bin/createdb --owner zabbix zabbix' || true cat ${pkgs.zabbix.server}/share/zabbix/db/schema/postgresql.sql | ${pkgs.su}/bin/su -s "$SHELL" zabbix -c '${pkgs.postgresql}/bin/psql zabbix' cat ${pkgs.zabbix.server}/share/zabbix/db/data/images_pgsql.sql | ${pkgs.su}/bin/su -s "$SHELL" zabbix -c '${pkgs.postgresql}/bin/psql zabbix' cat ${pkgs.zabbix.server}/share/zabbix/db/data/data.sql | ${pkgs.su}/bin/su -s "$SHELL" zabbix -c '${pkgs.postgresql}/bin/psql zabbix' diff --git a/nixos/modules/services/network-filesystems/samba.nix b/nixos/modules/services/network-filesystems/samba.nix index b23266e8d43a..10dc58311212 100644 --- a/nixos/modules/services/network-filesystems/samba.nix +++ b/nixos/modules/services/network-filesystems/samba.nix @@ -214,12 +214,10 @@ in } ]; # Always provide a smb.conf to shut up programs like smbclient and smbspool. - environment.etc = singleton - { source = - if cfg.enable then configFile - else pkgs.writeText "smb-dummy.conf" "# Samba is disabled."; - target = "samba/smb.conf"; - }; + environment.etc."samba/smb.conf".source = mkOptionDefault ( + if cfg.enable then configFile + else pkgs.writeText "smb-dummy.conf" "# Samba is disabled." + ); } (mkIf cfg.enable { diff --git a/nixos/modules/services/networking/bird.nix b/nixos/modules/services/networking/bird.nix index 9f08cc0a79ed..555733aab7c0 100644 --- a/nixos/modules/services/networking/bird.nix +++ b/nixos/modules/services/networking/bird.nix @@ -44,6 +44,7 @@ let systemd.services.${variant} = { description = "BIRD Internet Routing Daemon (${descr})"; wantedBy = [ "multi-user.target" ]; + reloadIfChanged = true; serviceConfig = { Type = "forking"; Restart = "on-failure"; diff --git a/nixos/modules/services/networking/bitlbee.nix b/nixos/modules/services/networking/bitlbee.nix index 392a8d5c2e7c..46e3b7457610 100644 --- a/nixos/modules/services/networking/bitlbee.nix +++ b/nixos/modules/services/networking/bitlbee.nix @@ -7,9 +7,10 @@ let cfg = config.services.bitlbee; bitlbeeUid = config.ids.uids.bitlbee; - bitlbeePkg = if cfg.libpurple_plugins == [] - then pkgs.bitlbee - else pkgs.bitlbee.override { enableLibPurple = true; }; + bitlbeePkg = pkgs.bitlbee.override { + enableLibPurple = cfg.libpurple_plugins != []; + enablePam = cfg.authBackend == "pam"; + }; bitlbeeConfig = pkgs.writeText "bitlbee.conf" '' @@ -20,6 +21,7 @@ let DaemonInterface = ${cfg.interface} DaemonPort = ${toString cfg.portNumber} AuthMode = ${cfg.authMode} + AuthBackend = ${cfg.authBackend} Plugindir = ${pkgs.bitlbee-plugins cfg.plugins}/lib/bitlbee ${lib.optionalString (cfg.hostName != "") "HostName = ${cfg.hostName}"} ${lib.optionalString (cfg.protocols != "") "Protocols = ${cfg.protocols}"} @@ -70,6 +72,16 @@ in ''; }; + authBackend = mkOption { + default = "storage"; + type = types.enum [ "storage" "pam" ]; + description = '' + How users are authenticated + storage -- save passwords internally + pam -- Linux PAM authentication + ''; + }; + authMode = mkOption { default = "Open"; type = types.enum [ "Open" "Closed" "Registered" ]; @@ -147,23 +159,22 @@ in ###### implementation - config = mkIf config.services.bitlbee.enable { - - users.users = singleton - { name = "bitlbee"; + config = mkMerge [ + (mkIf config.services.bitlbee.enable { + users.users = singleton { + name = "bitlbee"; uid = bitlbeeUid; description = "BitlBee user"; home = "/var/lib/bitlbee"; createHome = true; }; - users.groups = singleton - { name = "bitlbee"; + users.groups = singleton { + name = "bitlbee"; gid = config.ids.gids.bitlbee; }; - systemd.services.bitlbee = - { + systemd.services.bitlbee = { environment.PURPLE_PLUGIN_PATH = purple_plugin_path; description = "BitlBee IRC to other chat networks gateway"; after = [ "network.target" ]; @@ -172,8 +183,12 @@ in serviceConfig.ExecStart = "${bitlbeePkg}/sbin/bitlbee -F -n -c ${bitlbeeConfig}"; }; - environment.systemPackages = [ bitlbeePkg ]; + environment.systemPackages = [ bitlbeePkg ]; - }; + }) + (mkIf (config.services.bitlbee.authBackend == "pam") { + security.pam.services.bitlbee = {}; + }) + ]; } diff --git a/nixos/modules/services/networking/charybdis.nix b/nixos/modules/services/networking/charybdis.nix index 6d57faa9ac2b..3d02dc8d1375 100644 --- a/nixos/modules/services/networking/charybdis.nix +++ b/nixos/modules/services/networking/charybdis.nix @@ -90,7 +90,7 @@ in BANDB_DBPATH = "${cfg.statedir}/ban.db"; }; serviceConfig = { - ExecStart = "${charybdis}/bin/charybdis-ircd -foreground -logfile /dev/stdout -configfile ${configFile}"; + ExecStart = "${charybdis}/bin/charybdis -foreground -logfile /dev/stdout -configfile ${configFile}"; Group = cfg.group; User = cfg.user; PermissionsStartOnly = true; # preStart needs to run with root permissions diff --git a/nixos/modules/services/networking/chrony.nix b/nixos/modules/services/networking/chrony.nix index cef30661cc33..a363b545d649 100644 --- a/nixos/modules/services/networking/chrony.nix +++ b/nixos/modules/services/networking/chrony.nix @@ -3,12 +3,10 @@ with lib; let + cfg = config.services.chrony; stateDir = "/var/lib/chrony"; - - keyFile = "/etc/chrony.keys"; - - cfg = config.services.chrony; + keyFile = "${stateDir}/chrony.keys"; configFile = pkgs.writeText "chrony.conf" '' ${concatMapStringsSep "\n" (server: "server " + server) cfg.servers} @@ -19,7 +17,6 @@ let } driftfile ${stateDir}/chrony.drift - keyfile ${keyFile} ${optionalString (!config.time.hardwareClockInLocalTime) "rtconutc"} @@ -27,18 +24,11 @@ let ${cfg.extraConfig} ''; - chronyFlags = "-n -m -u chrony -f ${configFile} ${toString cfg.extraFlags}"; - + chronyFlags = "-m -u chrony -f ${configFile} ${toString cfg.extraFlags}"; in - { - - ###### interface - options = { - services.chrony = { - enable = mkOption { default = false; description = '' @@ -83,15 +73,9 @@ in description = "Extra flags passed to the chronyd command."; }; }; - }; - - ###### implementation - config = mkIf cfg.enable { - - # Make chronyc available in the system path environment.systemPackages = [ pkgs.chrony ]; users.groups = singleton @@ -113,26 +97,30 @@ in { description = "chrony NTP daemon"; wantedBy = [ "multi-user.target" ]; - wants = [ "time-sync.target" ]; - before = [ "time-sync.target" ]; - after = [ "network.target" ]; + wants = [ "time-sync.target" ]; + before = [ "time-sync.target" ]; + after = [ "network.target" ]; conflicts = [ "ntpd.service" "systemd-timesyncd.service" ]; path = [ pkgs.chrony ]; - preStart = - '' - mkdir -m 0755 -p ${stateDir} - touch ${keyFile} - chmod 0640 ${keyFile} - chown chrony:chrony ${stateDir} ${keyFile} - ''; + preStart = '' + mkdir -m 0755 -p ${stateDir} + touch ${keyFile} + chmod 0640 ${keyFile} + chown chrony:chrony ${stateDir} ${keyFile} + ''; serviceConfig = - { ExecStart = "${pkgs.chrony}/bin/chronyd ${chronyFlags}"; + { Type = "forking"; + ExecStart = "${pkgs.chrony}/bin/chronyd ${chronyFlags}"; + + ProtectHome = "yes"; + ProtectSystem = "full"; + PrivateTmp = "yes"; + + ConditionCapability = "CAP_SYS_TIME"; }; }; - }; - } diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix index de0aa1a2c2c3..efdbca5d52e8 100644 --- a/nixos/modules/services/networking/dhcpcd.nix +++ b/nixos/modules/services/networking/dhcpcd.nix @@ -161,8 +161,9 @@ in { description = "DHCP Client"; wantedBy = [ "multi-user.target" ] ++ optional (!hasDefaultGatewaySet) "network-online.target"; - after = [ "network.target" ]; - wants = [ "network.target" ]; + wants = [ "network.target" "systemd-udev-settle.service" ]; + before = [ "network.target" ]; + after = [ "systemd-udev-settle.service" ]; # Stopping dhcpcd during a reconfiguration is undesirable # because it brings down the network interfaces configured by diff --git a/nixos/modules/services/networking/dnscrypt-proxy.xml b/nixos/modules/services/networking/dnscrypt-proxy.xml index ff1088698589..f90eef69848c 100644 --- a/nixos/modules/services/networking/dnscrypt-proxy.xml +++ b/nixos/modules/services/networking/dnscrypt-proxy.xml @@ -3,67 +3,64 @@ xmlns:xi="http://www.w3.org/2001/XInclude" version="5.0" xml:id="sec-dnscrypt-proxy"> - - <title>DNSCrypt client proxy</title> + <title>DNSCrypt client proxy</title> + <para> + The DNSCrypt client proxy relays DNS queries to a DNSCrypt enabled upstream + resolver. The traffic between the client and the upstream resolver is + encrypted and authenticated, mitigating the risk of MITM attacks, DNS + poisoning attacks, and third-party snooping (assuming the upstream is + trustworthy). + </para> + <sect1 xml:id="sec-dnscrypt-proxy-configuration"> + <title>Basic configuration</title> <para> - The DNSCrypt client proxy relays DNS queries to a DNSCrypt enabled - upstream resolver. The traffic between the client and the upstream - resolver is encrypted and authenticated, mitigating the risk of MITM - attacks, DNS poisoning attacks, and third-party snooping (assuming the - upstream is trustworthy). - </para> - - <sect1><title>Basic configuration</title> - - <para> - To enable the client proxy, set - <programlisting> + To enable the client proxy, set +<programlisting> <xref linkend="opt-services.dnscrypt-proxy.enable"/> = true; </programlisting> </para> <para> - Enabling the client proxy does not alter the system nameserver; to - relay local queries, prepend <literal>127.0.0.1</literal> to - <option>networking.nameservers</option>. + Enabling the client proxy does not alter the system nameserver; to relay + local queries, prepend <literal>127.0.0.1</literal> to + <option>networking.nameservers</option>. </para> - - </sect1> - - <sect1><title>As a forwarder for another DNS client</title> + </sect1> + <sect1 xml:id="sec-dnscrypt-proxy-forwarder"> + <title>As a forwarder for another DNS client</title> <para> - To run the DNSCrypt proxy client as a forwarder for another - DNS client, change the default proxy listening port to a - non-standard value and point the other client to it: - <programlisting> + To run the DNSCrypt proxy client as a forwarder for another DNS client, + change the default proxy listening port to a non-standard value and point + the other client to it: +<programlisting> <xref linkend="opt-services.dnscrypt-proxy.localPort"/> = 43; </programlisting> </para> - <sect2><title>dnsmasq</title> - <para> - <programlisting> + <sect2 xml:id="sec-dnscrypt-proxy-forwarder-dsnmasq"> + <title>dnsmasq</title> + <para> +<programlisting> { <xref linkend="opt-services.dnsmasq.enable"/> = true; <xref linkend="opt-services.dnsmasq.servers"/> = [ "127.0.0.1#43" ]; } </programlisting> - </para> + </para> </sect2> - <sect2><title>unbound</title> - <para> - <programlisting> + <sect2 xml:id="sec-dnscrypt-proxy-forwarder-unbound"> + <title>unbound</title> + <para> +<programlisting> { <xref linkend="opt-services.unbound.enable"/> = true; <xref linkend="opt-services.unbound.forwardAddresses"/> = [ "127.0.0.1@43" ]; } </programlisting> - </para> + </para> </sect2> - - </sect1> - + </sect1> </chapter> diff --git a/nixos/modules/services/networking/epmd.nix b/nixos/modules/services/networking/epmd.nix new file mode 100644 index 000000000000..692b75e4f086 --- /dev/null +++ b/nixos/modules/services/networking/epmd.nix @@ -0,0 +1,56 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.epmd; + +in + +{ + ###### interface + options.services.epmd = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable socket activation for Erlang Port Mapper Daemon (epmd), + which acts as a name server on all hosts involved in distributed + Erlang computations. + ''; + }; + package = mkOption { + type = types.package; + default = pkgs.erlang; + description = '' + The Erlang package to use to get epmd binary. That way you can re-use + an Erlang runtime that is already installed for other purposes. + ''; + }; + }; + + ###### implementation + config = mkIf cfg.enable { + systemd.sockets.epmd = rec { + description = "Erlang Port Mapper Daemon Activation Socket"; + wantedBy = [ "sockets.target" ]; + before = wantedBy; + socketConfig = { + ListenStream = "4369"; + Accept = "false"; + }; + }; + + systemd.services.epmd = { + description = "Erlang Port Mapper Daemon"; + after = [ "network.target" ]; + requires = [ "epmd.socket" ]; + + serviceConfig = { + DynamicUser = true; + ExecStart = "${cfg.package}/bin/epmd -systemd"; + Type = "notify"; + }; + }; + }; +} diff --git a/nixos/modules/services/networking/firewall.nix b/nixos/modules/services/networking/firewall.nix index 36f1dd8d2479..86463f276c65 100644 --- a/nixos/modules/services/networking/firewall.nix +++ b/nixos/modules/services/networking/firewall.nix @@ -123,7 +123,7 @@ let # Perform a reverse-path test to refuse spoofers # For now, we just drop, as the raw table doesn't have a log-refuse yet ip46tables -t raw -N nixos-fw-rpfilter 2> /dev/null || true - ip46tables -t raw -A nixos-fw-rpfilter -m rpfilter ${optionalString (cfg.checkReversePath == "loose") "--loose"} -j RETURN + ip46tables -t raw -A nixos-fw-rpfilter -m rpfilter --validmark ${optionalString (cfg.checkReversePath == "loose") "--loose"} -j RETURN # Allows this host to act as a DHCP4 client without first having to use APIPA iptables -t raw -A nixos-fw-rpfilter -p udp --sport 67 --dport 68 -j RETURN diff --git a/nixos/modules/services/networking/hylafax/default.nix b/nixos/modules/services/networking/hylafax/default.nix new file mode 100644 index 000000000000..4c63b822d165 --- /dev/null +++ b/nixos/modules/services/networking/hylafax/default.nix @@ -0,0 +1,29 @@ +{ config, lib, pkgs, ... }: + +{ + + imports = [ + ./options.nix + ./systemd.nix + ]; + + config = lib.modules.mkIf config.services.hylafax.enable { + environment.systemPackages = [ pkgs.hylafaxplus ]; + users.users.uucp = { + uid = config.ids.uids.uucp; + group = "uucp"; + description = "Unix-to-Unix CoPy system"; + isSystemUser = true; + inherit (config.users.users.nobody) home; + }; + assertions = [{ + assertion = config.services.hylafax.modems != {}; + message = '' + HylaFAX cannot be used without modems. + Please define at least one modem with + <option>config.services.hylafax.modems</option>. + ''; + }]; + }; + +} diff --git a/nixos/modules/services/networking/hylafax/faxq-default.nix b/nixos/modules/services/networking/hylafax/faxq-default.nix new file mode 100644 index 000000000000..a2630ce66b71 --- /dev/null +++ b/nixos/modules/services/networking/hylafax/faxq-default.nix @@ -0,0 +1,12 @@ +{ ... }: + +# see man:hylafax-config(5) + +{ + + ModemGroup = [ ''"any:.*"'' ]; + ServerTracing = "0x78701"; + SessionTracing = "0x78701"; + UUCPLockDir = "/var/lock"; + +} diff --git a/nixos/modules/services/networking/hylafax/faxq-wait.sh b/nixos/modules/services/networking/hylafax/faxq-wait.sh new file mode 100755 index 000000000000..8c39e9d20c18 --- /dev/null +++ b/nixos/modules/services/networking/hylafax/faxq-wait.sh @@ -0,0 +1,29 @@ +#! @shell@ -e + +# skip this if there are no modems at all +if ! stat -t "@spoolAreaPath@"/etc/config.* >/dev/null 2>&1 +then + exit 0 +fi + +echo "faxq started, waiting for modem(s) to initialize..." + +for i in `seq @timeoutSec@0 -1 0` # gracefully timeout +do + sleep 0.1 + # done if status files exist, but don't mention initialization + if \ + stat -t "@spoolAreaPath@"/status/* >/dev/null 2>&1 \ + && \ + ! grep --silent --ignore-case 'initializing server' \ + "@spoolAreaPath@"/status/* + then + echo "modem(s) apparently ready" + exit 0 + fi + # if i reached 0, modems probably failed to initialize + if test $i -eq 0 + then + echo "warning: modem initialization timed out" + fi +done diff --git a/nixos/modules/services/networking/hylafax/hfaxd-default.nix b/nixos/modules/services/networking/hylafax/hfaxd-default.nix new file mode 100644 index 000000000000..8999dae57f41 --- /dev/null +++ b/nixos/modules/services/networking/hylafax/hfaxd-default.nix @@ -0,0 +1,10 @@ +{ ... }: + +# see man:hfaxd(8) + +{ + + ServerTracing = "0x91"; + XferLogFile = "/clientlog"; + +} diff --git a/nixos/modules/services/networking/hylafax/modem-default.nix b/nixos/modules/services/networking/hylafax/modem-default.nix new file mode 100644 index 000000000000..7529b5b0aafd --- /dev/null +++ b/nixos/modules/services/networking/hylafax/modem-default.nix @@ -0,0 +1,22 @@ +{ pkgs, ... }: + +# see man:hylafax-config(5) + +{ + + TagLineFont = "etc/LiberationSans-25.pcf"; + TagLineLocale = ''en_US.UTF-8''; + + AdminGroup = "root"; # groups that can change server config + AnswerRotary = "fax"; # don't accept anything else but faxes + LogFileMode = "0640"; + PriorityScheduling = true; + RecvFileMode = "0640"; + ServerTracing = "0x78701"; + SessionTracing = "0x78701"; + UUCPLockDir = "/var/lock"; + + SendPageCmd = ''${pkgs.coreutils}/bin/false''; # prevent pager transmit + SendUUCPCmd = ''${pkgs.coreutils}/bin/false''; # prevent UUCP transmit + +} diff --git a/nixos/modules/services/networking/hylafax/options.nix b/nixos/modules/services/networking/hylafax/options.nix new file mode 100644 index 000000000000..4ac6d3fa8432 --- /dev/null +++ b/nixos/modules/services/networking/hylafax/options.nix @@ -0,0 +1,375 @@ +{ config, lib, pkgs, ... }: + +let + + inherit (lib.options) literalExample mkEnableOption mkOption; + inherit (lib.types) bool enum int lines loaOf nullOr path str submodule; + inherit (lib.modules) mkDefault mkIf mkMerge; + + commonDescr = '' + Values can be either strings or integers + (which will be added to the config file verbatimly) + or lists thereof + (which will be translated to multiple + lines with the same configuration key). + Boolean values are translated to "Yes" or "No". + The default contains some reasonable + configuration to yield an operational system. + ''; + + str1 = lib.types.addCheck str (s: s!=""); # non-empty string + int1 = lib.types.addCheck int (i: i>0); # positive integer + + configAttrType = + # Options in HylaFAX configuration files can be + # booleans, strings, integers, or list thereof + # representing multiple config directives with the same key. + # This type definition resolves all + # those types into a list of strings. + let + inherit (lib.types) attrsOf coercedTo listOf; + innerType = coercedTo bool (x: if x then "Yes" else "No") + (coercedTo int (toString) str); + in + attrsOf (coercedTo innerType lib.singleton (listOf innerType)); + + cfg = config.services.hylafax; + + modemConfigOptions = { name, config, ... }: { + options = { + name = mkOption { + type = str1; + example = "ttyS1"; + description = '' + Name of modem device, + will be searched for in <filename>/dev</filename>. + ''; + }; + type = mkOption { + type = str1; + example = "cirrus"; + description = '' + Name of modem configuration file, + will be searched for in <filename>config</filename> + in the spooling area directory. + ''; + }; + config = mkOption { + type = configAttrType; + example = { + AreaCode = "49"; + LocalCode = "30"; + FAXNumber = "123456"; + LocalIdentifier = "LostInBerlin"; + }; + description = '' + Attribute set of values for the given modem. + ${commonDescr} + Options defined here override options in + <option>commonModemConfig</option> for this modem. + ''; + }; + }; + config.name = mkDefault name; + config.config.Include = [ "config/${config.type}" ]; + }; + + defaultConfig = + let + inherit (config.security) wrapperDir; + inherit (config.services.mail.sendmailSetuidWrapper) program; + mkIfDefault = cond: value: mkIf cond (mkDefault value); + noWrapper = config.services.mail.sendmailSetuidWrapper==null; + # If a sendmail setuid wrapper exists, + # we add the path to the default configuration file. + # Otherwise, we use `false` to provoke + # an error if hylafax tries to use it. + c.sendmailPath = mkMerge [ + (mkIfDefault noWrapper ''${pkgs.coreutils}/bin/false'') + (mkIfDefault (!noWrapper) ''${wrapperDir}/${program}'') + ]; + importDefaultConfig = file: + lib.attrsets.mapAttrs + (lib.trivial.const mkDefault) + (import file { inherit pkgs; }); + c.commonModemConfig = importDefaultConfig ./modem-default.nix; + c.faxqConfig = importDefaultConfig ./faxq-default.nix; + c.hfaxdConfig = importDefaultConfig ./hfaxd-default.nix; + in + c; + + localConfig = + let + c.hfaxdConfig.UserAccessFile = cfg.userAccessFile; + c.faxqConfig = lib.attrsets.mapAttrs + (lib.trivial.const (v: mkIf (v!=null) v)) + { + AreaCode = cfg.areaCode; + CountryCode = cfg.countryCode; + LongDistancePrefix = cfg.longDistancePrefix; + InternationalPrefix = cfg.internationalPrefix; + }; + c.commonModemConfig = c.faxqConfig; + in + c; + +in + + +{ + + + options.services.hylafax = { + + enable = mkEnableOption ''HylaFAX server''; + + autostart = mkOption { + type = bool; + default = true; + example = false; + description = '' + Autostart the HylaFAX queue manager at system start. + If this is <literal>false</literal>, the queue manager + will still be started if there are pending + jobs or if a user tries to connect to it. + ''; + }; + + countryCode = mkOption { + type = nullOr str1; + default = null; + example = "49"; + description = ''Country code for server and all modems.''; + }; + + areaCode = mkOption { + type = nullOr str1; + default = null; + example = "30"; + description = ''Area code for server and all modems.''; + }; + + longDistancePrefix = mkOption { + type = nullOr str; + default = null; + example = "0"; + description = ''Long distance prefix for server and all modems.''; + }; + + internationalPrefix = mkOption { + type = nullOr str; + default = null; + example = "00"; + description = ''International prefix for server and all modems.''; + }; + + spoolAreaPath = mkOption { + type = path; + default = "/var/spool/fax"; + description = '' + The spooling area will be created/maintained + at the location given here. + ''; + }; + + userAccessFile = mkOption { + type = path; + default = "/etc/hosts.hfaxd"; + description = '' + The <filename>hosts.hfaxd</filename> + file entry in the spooling area + will be symlinked to the location given here. + This file must exist and be + readable only by the <literal>uucp</literal> user. + See hosts.hfaxd(5) for details. + This configuration permits access for all users: + <literal> + environment.etc."hosts.hfaxd" = { + mode = "0600"; + user = "uucp"; + text = ".*"; + }; + </literal> + Note that host-based access can be controlled with + <option>config.systemd.sockets.hylafax-hfaxd.listenStreams</option>; + by default, only 127.0.0.1 is permitted to connect. + ''; + }; + + sendmailPath = mkOption { + type = path; + example = literalExample "''${pkgs.postfix}/bin/sendmail"; + # '' ; # fix vim + description = '' + Path to <filename>sendmail</filename> program. + The default uses the local sendmail wrapper + (see <option>config.services.mail.sendmailSetuidWrapper</option>), + otherwise the <filename>false</filename> + binary to cause an error if used. + ''; + }; + + hfaxdConfig = mkOption { + type = configAttrType; + example.RecvqProtection = "0400"; + description = '' + Attribute set of lines for the global + hfaxd config file <filename>etc/hfaxd.conf</filename>. + ${commonDescr} + ''; + }; + + faxqConfig = mkOption { + type = configAttrType; + example = { + InternationalPrefix = "00"; + LongDistancePrefix = "0"; + }; + description = '' + Attribute set of lines for the global + faxq config file <filename>etc/config</filename>. + ${commonDescr} + ''; + }; + + commonModemConfig = mkOption { + type = configAttrType; + example = { + InternationalPrefix = "00"; + LongDistancePrefix = "0"; + }; + description = '' + Attribute set of default values for + modem config files <filename>etc/config.*</filename>. + ${commonDescr} + Think twice before changing + paths of fax-processing scripts. + ''; + }; + + modems = mkOption { + type = loaOf (submodule [ modemConfigOptions ]); + default = {}; + example.ttyS1 = { + type = "cirrus"; + config = { + FAXNumber = "123456"; + LocalIdentifier = "Smith"; + }; + }; + description = '' + Description of installed modems. + At least on modem must be defined + to enable the HylaFAX server. + ''; + }; + + spoolExtraInit = mkOption { + type = lines; + default = ""; + example = ''chmod 0755 . # everyone may read my faxes''; + description = '' + Additional shell code that is executed within the + spooling area directory right after its setup. + ''; + }; + + faxcron.enable.spoolInit = mkEnableOption '' + Purge old files from the spooling area with + <filename>faxcron</filename> + each time the spooling area is initialized. + ''; + faxcron.enable.frequency = mkOption { + type = nullOr str1; + default = null; + example = "daily"; + description = '' + Purge old files from the spooling area with + <filename>faxcron</filename> with the given frequency + (see systemd.time(7)). + ''; + }; + faxcron.infoDays = mkOption { + type = int1; + default = 30; + description = '' + Set the expiration time for data in the + remote machine information directory in days. + ''; + }; + faxcron.logDays = mkOption { + type = int1; + default = 30; + description = '' + Set the expiration time for + session trace log files in days. + ''; + }; + faxcron.rcvDays = mkOption { + type = int1; + default = 7; + description = '' + Set the expiration time for files in + the received facsimile queue in days. + ''; + }; + + faxqclean.enable.spoolInit = mkEnableOption '' + Purge old files from the spooling area with + <filename>faxqclean</filename> + each time the spooling area is initialized. + ''; + faxqclean.enable.frequency = mkOption { + type = nullOr str1; + default = null; + example = "daily"; + description = '' + Purge old files from the spooling area with + <filename>faxcron</filename> with the given frequency + (see systemd.time(7)). + ''; + }; + faxqclean.archiving = mkOption { + type = enum [ "never" "as-flagged" "always" ]; + default = "as-flagged"; + example = "always"; + description = '' + Enable or suppress job archiving: + <literal>never</literal> disables job archiving, + <literal>as-flagged</literal> archives jobs that + have been flagged for archiving by sendfax, + <literal>always</literal> forces archiving of all jobs. + See also sendfax(1) and faxqclean(8). + ''; + }; + faxqclean.doneqMinutes = mkOption { + type = int1; + default = 15; + example = literalExample ''24*60''; + description = '' + Set the job + age threshold (in minutes) that controls how long + jobs may reside in the doneq directory. + ''; + }; + faxqclean.docqMinutes = mkOption { + type = int1; + default = 60; + example = literalExample ''24*60''; + description = '' + Set the document + age threshold (in minutes) that controls how long + unreferenced files may reside in the docq directory. + ''; + }; + + }; + + + config.services.hylafax = + mkIf + (config.services.hylafax.enable) + (mkMerge [ defaultConfig localConfig ]) + ; + +} diff --git a/nixos/modules/services/networking/hylafax/spool.sh b/nixos/modules/services/networking/hylafax/spool.sh new file mode 100755 index 000000000000..31e930e8c597 --- /dev/null +++ b/nixos/modules/services/networking/hylafax/spool.sh @@ -0,0 +1,111 @@ +#! @shell@ -e + +# The following lines create/update the HylaFAX spool directory: +# Subdirectories/files with persistent data are kept, +# other directories/files are removed/recreated, +# mostly from the template spool +# directory in the HylaFAX package. + +# This block explains how the spool area is +# derived from the spool template in the HylaFAX package: +# +# + capital letter: directory; file otherwise +# + P/p: persistent directory +# + F/f: directory with symlinks per entry +# + T/t: temporary data +# + S/s: single symlink into package +# | +# | + u: change ownership to uucp:uucp +# | + U: ..also change access mode to user-only +# | | +# archive P U +# bin S +# client T u (client connection info) +# config S +# COPYRIGHT s +# dev T u (maybe some FIFOs) +# docq P U +# doneq P U +# etc F contains customized config files! +# etc/hosts.hfaxd f +# etc/xferfaxlog f +# info P u (database of called devices) +# log P u (communication logs) +# pollq P U +# recvq P u +# sendq P U +# status T u (modem status info files) +# tmp T U + + +shopt -s dotglob # if bash sees "*", it also includes dot files +lnsym () { ln --symbol "$@" ; } +lnsymfrc () { ln --symbolic --force "$@" ; } +cprd () { cp --remove-destination "$@" ; } +update () { install --owner=@faxuser@ --group=@faxgroup@ "$@" ; } + + +## create/update spooling area + +update --mode=0750 -d "@spoolAreaPath@" +cd "@spoolAreaPath@" + +persist=(archive docq doneq info log pollq recvq sendq) + +# remove entries that don't belong here +touch dummy # ensure "*" resolves to something +for k in * +do + keep=0 + for j in "${persist[@]}" xferfaxlog clientlog faxcron.lastrun + do + if test "$k" == "$j" + then + keep=1 + break + fi + done + if test "$keep" == "0" + then + rm --recursive "$k" + fi +done + +# create persistent data directories (unless they exist already) +update --mode=0700 -d "${persist[@]}" +chmod 0755 info log recvq + +# create ``xferfaxlog``, ``faxcron.lastrun``, ``clientlog`` +touch clientlog faxcron.lastrun xferfaxlog +chown @faxuser@:@faxgroup@ clientlog faxcron.lastrun xferfaxlog + +# create symlinks for frozen directories/files +lnsym --target-directory=. "@hylafax@"/spool/{COPYRIGHT,bin,config} + +# create empty temporary directories +update --mode=0700 -d client dev status +update -d tmp + + +## create and fill etc + +install -d "@spoolAreaPath@/etc" +cd "@spoolAreaPath@/etc" + +# create symlinks to all files in template's etc +lnsym --target-directory=. "@hylafax@/spool/etc"/* + +# set LOCKDIR in setup.cache +sed --regexp-extended 's|^(UUCP_LOCKDIR=).*$|\1'"'@lockPath@'|g" --in-place setup.cache + +# etc/{xferfaxlog,lastrun} are stored in the spool root +lnsymfrc --target-directory=. ../xferfaxlog +lnsymfrc --no-target-directory ../faxcron.lastrun lastrun + +# etc/hosts.hfaxd is provided by the NixOS configuration +lnsymfrc --no-target-directory "@userAccessFile@" hosts.hfaxd + +# etc/config and etc/config.${DEVID} must be copied: +# hfaxd reads these file after locking itself up in a chroot +cprd --no-target-directory "@globalConfigPath@" config +cprd --target-directory=. "@modemConfigPath@"/* diff --git a/nixos/modules/services/networking/hylafax/systemd.nix b/nixos/modules/services/networking/hylafax/systemd.nix new file mode 100644 index 000000000000..91d9c1a37da6 --- /dev/null +++ b/nixos/modules/services/networking/hylafax/systemd.nix @@ -0,0 +1,249 @@ +{ config, lib, pkgs, ... }: + + +let + + inherit (lib) mkIf mkMerge; + inherit (lib) concatStringsSep optionalString; + + cfg = config.services.hylafax; + mapModems = lib.flip map (lib.attrValues cfg.modems); + + mkConfigFile = name: conf: + # creates hylafax config file, + # makes sure "Include" is listed *first* + let + mkLines = conf: + (lib.concatLists + (lib.flip lib.mapAttrsToList conf + (k: map (v: ''${k}: ${v}'') + ))); + include = mkLines { Include = conf.Include or []; }; + other = mkLines ( conf // { Include = []; } ); + in + pkgs.writeText ''hylafax-config${name}'' + (concatStringsSep "\n" (include ++ other)); + + globalConfigPath = mkConfigFile "" cfg.faxqConfig; + + modemConfigPath = + let + mkModemConfigFile = { config, name, ... }: + mkConfigFile ''.${name}'' + (cfg.commonModemConfig // config); + mkLine = { name, type, ... }@modem: '' + # check if modem config file exists: + test -f "${pkgs.hylafaxplus}/spool/config/${type}" + ln \ + --symbolic \ + --no-target-directory \ + "${mkModemConfigFile modem}" \ + "$out/config.${name}" + ''; + in + pkgs.runCommand "hylafax-config-modems" {} + ''mkdir --parents "$out/" ${concatStringsSep "\n" (mapModems mkLine)}''; + + setupSpoolScript = pkgs.substituteAll { + name = "hylafax-setup-spool.sh"; + src = ./spool.sh; + isExecutable = true; + inherit (pkgs.stdenv) shell; + hylafax = pkgs.hylafaxplus; + faxuser = "uucp"; + faxgroup = "uucp"; + lockPath = "/var/lock"; + inherit globalConfigPath modemConfigPath; + inherit (cfg) sendmailPath spoolAreaPath userAccessFile; + }; + + waitFaxqScript = pkgs.substituteAll { + # This script checks the modems status files + # and waits until all modems report readiness. + name = "hylafax-faxq-wait-start.sh"; + src = ./faxq-wait.sh; + isExecutable = true; + timeoutSec = toString 10; + inherit (pkgs.stdenv) shell; + inherit (cfg) spoolAreaPath; + }; + + sockets."hylafax-hfaxd" = { + description = "HylaFAX server socket"; + documentation = [ "man:hfaxd(8)" ]; + wantedBy = [ "multi-user.target" ]; + listenStreams = [ "127.0.0.1:4559" ]; + socketConfig.FreeBind = true; + socketConfig.Accept = true; + }; + + paths."hylafax-faxq" = { + description = "HylaFAX queue manager sendq watch"; + documentation = [ "man:faxq(8)" "man:sendq(5)" ]; + wantedBy = [ "multi-user.target" ]; + pathConfig.PathExistsGlob = [ ''${cfg.spoolAreaPath}/sendq/q*'' ]; + }; + + timers = mkMerge [ + ( + mkIf (cfg.faxcron.enable.frequency!=null) + { "hylafax-faxcron".timerConfig.Persistent = true; } + ) + ( + mkIf (cfg.faxqclean.enable.frequency!=null) + { "hylafax-faxqclean".timerConfig.Persistent = true; } + ) + ]; + + hardenService = + # Add some common systemd service hardening settings, + # but allow each service (here) to override + # settings by explicitely setting those to `null`. + # More hardening would be nice but makes + # customizing hylafax setups very difficult. + # If at all, it should only be added along + # with some options to customize it. + let + hardening = { + PrivateDevices = true; # breaks /dev/tty... + PrivateNetwork = true; + PrivateTmp = true; + ProtectControlGroups = true; + #ProtectHome = true; # breaks custom spool dirs + ProtectKernelModules = true; + ProtectKernelTunables = true; + #ProtectSystem = "strict"; # breaks custom spool dirs + RestrictNamespaces = true; + RestrictRealtime = true; + }; + filter = key: value: (value != null) || ! (lib.hasAttr key hardening); + apply = service: lib.filterAttrs filter (hardening // (service.serviceConfig or {})); + in + service: service // { serviceConfig = apply service; }; + + services."hylafax-spool" = { + description = "HylaFAX spool area preparation"; + documentation = [ "man:hylafax-server(4)" ]; + script = '' + ${setupSpoolScript} + cd "${cfg.spoolAreaPath}" + ${cfg.spoolExtraInit} + if ! test -f "${cfg.spoolAreaPath}/etc/hosts.hfaxd" + then + echo hosts.hfaxd is missing + exit 1 + fi + ''; + serviceConfig.ExecStop = ''${setupSpoolScript}''; + serviceConfig.RemainAfterExit = true; + serviceConfig.Type = "oneshot"; + unitConfig.RequiresMountsFor = [ cfg.spoolAreaPath ]; + }; + + services."hylafax-faxq" = { + description = "HylaFAX queue manager"; + documentation = [ "man:faxq(8)" ]; + requires = [ "hylafax-spool.service" ]; + after = [ "hylafax-spool.service" ]; + wants = mapModems ( { name, ... }: ''hylafax-faxgetty@${name}.service'' ); + wantedBy = mkIf cfg.autostart [ "multi-user.target" ]; + serviceConfig.Type = "forking"; + serviceConfig.ExecStart = ''${pkgs.hylafaxplus}/spool/bin/faxq -q "${cfg.spoolAreaPath}"''; + # This delays the "readiness" of this service until + # all modems are initialized (or a timeout is reached). + # Otherwise, sending a fax with the fax service + # stopped will always yield a failed send attempt: + # The fax service is started when the job is created with + # `sendfax`, but modems need some time to initialize. + serviceConfig.ExecStartPost = [ ''${waitFaxqScript}'' ]; + # faxquit fails if the pipe is already gone + # (e.g. the service is already stopping) + serviceConfig.ExecStop = ''-${pkgs.hylafaxplus}/spool/bin/faxquit -q "${cfg.spoolAreaPath}"''; + # disable some systemd hardening settings + serviceConfig.PrivateDevices = null; + serviceConfig.RestrictRealtime = null; + }; + + services."hylafax-hfaxd@" = { + description = "HylaFAX server"; + documentation = [ "man:hfaxd(8)" ]; + after = [ "hylafax-faxq.service" ]; + requires = [ "hylafax-faxq.service" ]; + serviceConfig.StandardInput = "socket"; + serviceConfig.StandardOutput = "socket"; + serviceConfig.ExecStart = ''${pkgs.hylafaxplus}/spool/bin/hfaxd -q "${cfg.spoolAreaPath}" -d -I''; + unitConfig.RequiresMountsFor = [ cfg.userAccessFile ]; + # disable some systemd hardening settings + serviceConfig.PrivateDevices = null; + serviceConfig.PrivateNetwork = null; + }; + + services."hylafax-faxcron" = rec { + description = "HylaFAX spool area maintenance"; + documentation = [ "man:faxcron(8)" ]; + after = [ "hylafax-spool.service" ]; + requires = [ "hylafax-spool.service" ]; + wantedBy = mkIf cfg.faxcron.enable.spoolInit requires; + startAt = mkIf (cfg.faxcron.enable.frequency!=null) cfg.faxcron.enable.frequency; + serviceConfig.ExecStart = concatStringsSep " " [ + ''${pkgs.hylafaxplus}/spool/bin/faxcron'' + ''-q "${cfg.spoolAreaPath}"'' + ''-info ${toString cfg.faxcron.infoDays}'' + ''-log ${toString cfg.faxcron.logDays}'' + ''-rcv ${toString cfg.faxcron.rcvDays}'' + ]; + }; + + services."hylafax-faxqclean" = rec { + description = "HylaFAX spool area queue cleaner"; + documentation = [ "man:faxqclean(8)" ]; + after = [ "hylafax-spool.service" ]; + requires = [ "hylafax-spool.service" ]; + wantedBy = mkIf cfg.faxqclean.enable.spoolInit requires; + startAt = mkIf (cfg.faxqclean.enable.frequency!=null) cfg.faxqclean.enable.frequency; + serviceConfig.ExecStart = concatStringsSep " " [ + ''${pkgs.hylafaxplus}/spool/bin/faxqclean'' + ''-q "${cfg.spoolAreaPath}"'' + ''-v'' + (optionalString (cfg.faxqclean.archiving!="never") ''-a'') + (optionalString (cfg.faxqclean.archiving=="always") ''-A'') + ''-j ${toString (cfg.faxqclean.doneqMinutes*60)}'' + ''-d ${toString (cfg.faxqclean.docqMinutes*60)}'' + ]; + }; + + mkFaxgettyService = { name, ... }: + lib.nameValuePair ''hylafax-faxgetty@${name}'' rec { + description = "HylaFAX faxgetty for %I"; + documentation = [ "man:faxgetty(8)" ]; + bindsTo = [ "dev-%i.device" ]; + requires = [ "hylafax-spool.service" ]; + after = bindsTo ++ requires; + before = [ "hylafax-faxq.service" "getty.target" ]; + unitConfig.StopWhenUnneeded = true; + unitConfig.AssertFileNotEmpty = ''${cfg.spoolAreaPath}/etc/config.%I''; + serviceConfig.UtmpIdentifier = "%I"; + serviceConfig.TTYPath = "/dev/%I"; + serviceConfig.Restart = "always"; + serviceConfig.KillMode = "process"; + serviceConfig.IgnoreSIGPIPE = false; + serviceConfig.ExecStart = ''-${pkgs.hylafaxplus}/spool/bin/faxgetty -q "${cfg.spoolAreaPath}" /dev/%I''; + # faxquit fails if the pipe is already gone + # (e.g. the service is already stopping) + serviceConfig.ExecStop = ''-${pkgs.hylafaxplus}/spool/bin/faxquit -q "${cfg.spoolAreaPath}" %I''; + # disable some systemd hardening settings + serviceConfig.PrivateDevices = null; + serviceConfig.RestrictRealtime = null; + }; + + modemServices = + lib.listToAttrs (mapModems mkFaxgettyService); + +in + +{ + config.systemd = mkIf cfg.enable { + inherit sockets timers paths; + services = lib.mapAttrs (lib.const hardenService) (services // modemServices); + }; +} diff --git a/nixos/modules/services/networking/i2pd.nix b/nixos/modules/services/networking/i2pd.nix index 3afafaf3fed5..0e9b354cfcaf 100644 --- a/nixos/modules/services/networking/i2pd.nix +++ b/nixos/modules/services/networking/i2pd.nix @@ -8,6 +8,17 @@ let homeDir = "/var/lib/i2pd"; + strOpt = k: v: k + " = " + v; + boolOpt = k: v: k + " = " + boolToString v; + intOpt = k: v: k + " = " + toString v; + lstOpt = k: xs: k + " = " + concatStringsSep "," xs; + optionalNullString = o: s: optional (! isNull s) (strOpt o s); + optionalNullBool = o: b: optional (! isNull b) (boolOpt o b); + optionalNullInt = o: i: optional (! isNull i) (intOpt o i); + optionalEmptyList = o: l: optional ([] != l) (lstOpt o l); + + mkEnableTrueOption = name: mkEnableOption name // { default = true; }; + mkEndpointOpt = name: addr: port: { enable = mkEnableOption name; name = mkOption { @@ -18,42 +29,54 @@ let address = mkOption { type = types.str; default = addr; - description = "Bind address for ${name} endpoint. Default: " + addr; + description = "Bind address for ${name} endpoint."; }; port = mkOption { type = types.int; default = port; - description = "Bind port for ${name} endoint. Default: " + toString port; + description = "Bind port for ${name} endoint."; }; }; - mkKeyedEndpointOpt = name: addr: port: keyFile: + i2cpOpts = name: { + length = mkOption { + type = types.int; + description = "Guaranteed minimum hops for ${name} tunnels."; + default = 3; + }; + quantity = mkOption { + type = types.int; + description = "Number of simultaneous ${name} tunnels."; + default = 5; + }; + }; + + mkKeyedEndpointOpt = name: addr: port: keyloc: (mkEndpointOpt name addr port) // { keys = mkOption { - type = types.str; - default = ""; + type = with types; nullOr str; + default = keyloc; description = '' File to persist ${lib.toUpper name} keys. ''; }; - }; - - commonTunOpts = let - i2cpOpts = { - length = mkOption { - type = types.int; - description = "Guaranteed minimum hops."; - default = 3; + inbound = i2cpOpts name; + outbound = i2cpOpts name; + latency.min = mkOption { + type = with types; nullOr int; + description = "Min latency for tunnels."; + default = null; }; - quantity = mkOption { - type = types.int; - description = "Number of simultaneous tunnels."; - default = 5; + latency.max = mkOption { + type = with types; nullOr int; + description = "Max latency for tunnels."; + default = null; }; }; - in name: { - outbound = i2cpOpts; - inbound = i2cpOpts; + + commonTunOpts = name: { + outbound = i2cpOpts name; + inbound = i2cpOpts name; crypto.tagsToSend = mkOption { type = types.int; description = "Number of ElGamal/AES tags to send."; @@ -70,94 +93,142 @@ let }; } // mkEndpointOpt name "127.0.0.1" 0; - i2pdConf = pkgs.writeText "i2pd.conf" '' - # DO NOT EDIT -- this file has been generated automatically. - loglevel = ${cfg.logLevel} - - ipv4 = ${boolToString cfg.enableIPv4} - ipv6 = ${boolToString cfg.enableIPv6} - notransit = ${boolToString cfg.notransit} - floodfill = ${boolToString cfg.floodfill} - netid = ${toString cfg.netid} - ${if isNull cfg.bandwidth then "" else "bandwidth = ${toString cfg.bandwidth}" } - ${if isNull cfg.port then "" else "port = ${toString cfg.port}"} - - [limits] - transittunnels = ${toString cfg.limits.transittunnels} - - [upnp] - enabled = ${boolToString cfg.upnp.enable} - name = ${cfg.upnp.name} - - [precomputation] - elgamal = ${boolToString cfg.precomputation.elgamal} - - [reseed] - verify = ${boolToString cfg.reseed.verify} - file = ${cfg.reseed.file} - urls = ${builtins.concatStringsSep "," cfg.reseed.urls} - - [addressbook] - defaulturl = ${cfg.addressbook.defaulturl} - subscriptions = ${builtins.concatStringsSep "," cfg.addressbook.subscriptions} - - ${flip concatMapStrings + sec = name: "\n[" + name + "]"; + notice = "# DO NOT EDIT -- this file has been generated automatically."; + i2pdConf = let + opts = [ + notice + (strOpt "loglevel" cfg.logLevel) + (boolOpt "logclftime" cfg.logCLFTime) + (boolOpt "ipv4" cfg.enableIPv4) + (boolOpt "ipv6" cfg.enableIPv6) + (boolOpt "notransit" cfg.notransit) + (boolOpt "floodfill" cfg.floodfill) + (intOpt "netid" cfg.netid) + ] ++ (optionalNullInt "bandwidth" cfg.bandwidth) + ++ (optionalNullInt "port" cfg.port) + ++ (optionalNullString "family" cfg.family) + ++ (optionalNullString "datadir" cfg.dataDir) + ++ (optionalNullInt "share" cfg.share) + ++ (optionalNullBool "ssu" cfg.ssu) + ++ (optionalNullBool "ntcp" cfg.ntcp) + ++ (optionalNullString "ntcpproxy" cfg.ntcpProxy) + ++ (optionalNullString "ifname" cfg.ifname) + ++ (optionalNullString "ifname4" cfg.ifname4) + ++ (optionalNullString "ifname6" cfg.ifname6) + ++ [ + (sec "limits") + (intOpt "transittunnels" cfg.limits.transittunnels) + (intOpt "coresize" cfg.limits.coreSize) + (intOpt "openfiles" cfg.limits.openFiles) + (intOpt "ntcphard" cfg.limits.ntcpHard) + (intOpt "ntcpsoft" cfg.limits.ntcpSoft) + (intOpt "ntcpthreads" cfg.limits.ntcpThreads) + (sec "upnp") + (boolOpt "enabled" cfg.upnp.enable) + (sec "precomputation") + (boolOpt "elgamal" cfg.precomputation.elgamal) + (sec "reseed") + (boolOpt "verify" cfg.reseed.verify) + ] ++ (optionalNullString "file" cfg.reseed.file) + ++ (optionalEmptyList "urls" cfg.reseed.urls) + ++ (optionalNullString "floodfill" cfg.reseed.floodfill) + ++ (optionalNullString "zipfile" cfg.reseed.zipfile) + ++ (optionalNullString "proxy" cfg.reseed.proxy) + ++ [ + (sec "trust") + (boolOpt "enabled" cfg.trust.enable) + (boolOpt "hidden" cfg.trust.hidden) + ] ++ (optionalEmptyList "routers" cfg.trust.routers) + ++ (optionalNullString "family" cfg.trust.family) + ++ [ + (sec "websockets") + (boolOpt "enabled" cfg.websocket.enable) + (strOpt "address" cfg.websocket.address) + (intOpt "port" cfg.websocket.port) + (sec "exploratory") + (intOpt "inbound.length" cfg.exploratory.inbound.length) + (intOpt "inbound.quantity" cfg.exploratory.inbound.quantity) + (intOpt "outbound.length" cfg.exploratory.outbound.length) + (intOpt "outbound.quantity" cfg.exploratory.outbound.quantity) + (sec "ntcp2") + (boolOpt "enabled" cfg.ntcp2.enable) + (boolOpt "published" cfg.ntcp2.published) + (intOpt "port" cfg.ntcp2.port) + (sec "addressbook") + (strOpt "defaulturl" cfg.addressbook.defaulturl) + ] ++ (optionalEmptyList "subscriptions" cfg.addressbook.subscriptions) + ++ (flip map (collect (proto: proto ? port && proto ? address && proto ? name) cfg.proto) - (proto: '' - [${proto.name}] - enabled = ${boolToString proto.enable} - address = ${proto.address} - port = ${toString proto.port} - ${if proto ? keys then "keys = ${proto.keys}" else ""} - ${if proto ? auth then "auth = ${boolToString proto.auth}" else ""} - ${if proto ? user then "user = ${proto.user}" else ""} - ${if proto ? pass then "pass = ${proto.pass}" else ""} - ${if proto ? outproxy then "outproxy = ${proto.outproxy}" else ""} - ${if proto ? outproxyPort then "outproxyport = ${toString proto.outproxyPort}" else ""} - '') - } - ''; - - i2pdTunnelConf = pkgs.writeText "i2pd-tunnels.conf" '' - # DO NOT EDIT -- this file has been generated automatically. - ${flip concatMapStrings + (proto: let protoOpts = [ + (sec proto.name) + (boolOpt "enabled" proto.enable) + (strOpt "address" proto.address) + (intOpt "port" proto.port) + ] ++ (if proto ? keys then optionalNullString "keys" proto.keys else []) + ++ (if proto ? auth then optionalNullBool "auth" proto.auth else []) + ++ (if proto ? user then optionalNullString "user" proto.user else []) + ++ (if proto ? pass then optionalNullString "pass" proto.pass else []) + ++ (if proto ? strictHeaders then optionalNullBool "strictheaders" proto.strictHeaders else []) + ++ (if proto ? hostname then optionalNullString "hostname" proto.hostname else []) + ++ (if proto ? outproxy then optionalNullString "outproxy" proto.outproxy else []) + ++ (if proto ? outproxyPort then optionalNullInt "outproxyport" proto.outproxyPort else []) + ++ (if proto ? outproxyEnable then optionalNullBool "outproxy.enabled" proto.outproxyEnable else []); + in (concatStringsSep "\n" protoOpts) + )); + in + pkgs.writeText "i2pd.conf" (concatStringsSep "\n" opts); + + tunnelConf = let opts = [ + notice + (flip map (collect (tun: tun ? port && tun ? destination) cfg.outTunnels) - (tun: '' - [${tun.name}] - type = client - destination = ${tun.destination} - destinationport = ${toString tun.destinationPort} - keys = ${tun.keys} - address = ${tun.address} - port = ${toString tun.port} - inbound.length = ${toString tun.inbound.length} - outbound.length = ${toString tun.outbound.length} - inbound.quantity = ${toString tun.inbound.quantity} - outbound.quantity = ${toString tun.outbound.quantity} - crypto.tagsToSend = ${toString tun.crypto.tagsToSend} - '') - } - ${flip concatMapStrings + (tun: let outTunOpts = [ + (sec tun.name) + "type = client" + (intOpt "port" tun.port) + (strOpt "destination" tun.destination) + ] ++ (if tun ? destinationPort then optionalNullInt "destinationport" tun.destinationPort else []) + ++ (if tun ? keys then + optionalNullString "keys" tun.keys else []) + ++ (if tun ? address then + optionalNullString "address" tun.address else []) + ++ (if tun ? inbound.length then + optionalNullInt "inbound.length" tun.inbound.length else []) + ++ (if tun ? inbound.quantity then + optionalNullInt "inbound.quantity" tun.inbound.quantity else []) + ++ (if tun ? outbound.length then + optionalNullInt "outbound.length" tun.outbound.length else []) + ++ (if tun ? outbound.quantity then + optionalNullInt "outbound.quantity" tun.outbound.quantity else []) + ++ (if tun ? crypto.tagsToSend then + optionalNullInt "crypto.tagstosend" tun.crypto.tagsToSend else []); + in concatStringsSep "\n" outTunOpts)) + (flip map (collect (tun: tun ? port && tun ? address) cfg.inTunnels) - (tun: '' - [${tun.name}] - type = server - destination = ${tun.destination} - keys = ${tun.keys} - host = ${tun.address} - port = ${toString tun.port} - inport = ${toString tun.inPort} - accesslist = ${builtins.concatStringsSep "," tun.accessList} - '') - } - ''; + (tun: let inTunOpts = [ + (sec tun.name) + "type = server" + (intOpt "port" tun.port) + (strOpt "host" tun.address) + ] ++ (if tun ? destination then + optionalNullString "destination" tun.destination else []) + ++ (if tun ? keys then + optionalNullString "keys" tun.keys else []) + ++ (if tun ? inPort then + optionalNullInt "inport" tun.inPort else []) + ++ (if tun ? accessList then + optionalEmptyList "accesslist" tun.accessList else []); + in concatStringsSep "\n" inTunOpts))]; + in pkgs.writeText "i2pd-tunnels.conf" opts; i2pdSh = pkgs.writeScriptBin "i2pd" '' #!/bin/sh exec ${pkgs.i2pd}/bin/i2pd \ ${if isNull cfg.address then "" else "--host="+cfg.address} \ + --service \ --conf=${i2pdConf} \ - --tunconf=${i2pdTunnelConf} + --tunconf=${tunnelConf} ''; in @@ -170,9 +241,7 @@ in services.i2pd = { - enable = mkOption { - type = types.bool; - default = false; + enable = mkEnableOption "I2Pd daemon" // { description = '' Enables I2Pd as a running service upon activation. Please read http://i2pd.readthedocs.io/en/latest/ for further @@ -192,6 +261,8 @@ in ''; }; + logCLFTime = mkEnableOption "Full CLF-formatted date and time to log"; + address = mkOption { type = with types; nullOr str; default = null; @@ -200,17 +271,72 @@ in ''; }; - notransit = mkOption { - type = types.bool; - default = false; + family = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Specify a family the router belongs to. + ''; + }; + + dataDir = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Alternative path to storage of i2pd data (RI, keys, peer profiles, ...) + ''; + }; + + share = mkOption { + type = types.int; + default = 100; + description = '' + Limit of transit traffic from max bandwidth in percents. + ''; + }; + + ifname = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Network interface to bind to. + ''; + }; + + ifname4 = mkOption { + type = with types; nullOr str; + default = null; + description = '' + IPv4 interface to bind to. + ''; + }; + + ifname6 = mkOption { + type = with types; nullOr str; + default = null; + description = '' + IPv6 interface to bind to. + ''; + }; + + ntcpProxy = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Proxy URL for NTCP transport. + ''; + }; + + ntcp = mkEnableTrueOption "ntcp"; + ssu = mkEnableTrueOption "ssu"; + + notransit = mkEnableOption "notransit" // { description = '' Tells the router to not accept transit tunnels during startup. ''; }; - floodfill = mkOption { - type = types.bool; - default = false; + floodfill = mkEnableOption "floodfill" // { description = '' If the router is declared to be unreachable and needs introduction nodes. ''; @@ -241,131 +367,178 @@ in ''; }; - enableIPv4 = mkOption { - type = types.bool; - default = true; + enableIPv4 = mkEnableTrueOption "IPv4 connectivity"; + enableIPv6 = mkEnableOption "IPv6 connectivity"; + nat = mkEnableTrueOption "NAT bypass"; + + upnp.enable = mkEnableOption "UPnP service discovery"; + upnp.name = mkOption { + type = types.str; + default = "I2Pd"; description = '' - Enables IPv4 connectivity. Enabled by default. + Name i2pd appears in UPnP forwardings list. ''; }; - enableIPv6 = mkOption { - type = types.bool; - default = false; + precomputation.elgamal = mkEnableTrueOption "Precomputed ElGamal tables" // { description = '' - Enables IPv6 connectivity. Disabled by default. + Whenever to use precomputated tables for ElGamal. + <command>i2pd</command> defaults to <literal>false</literal> + to save 64M of memory (and looses some performance). + + We default to <literal>true</literal> as that is what most + users want anyway. ''; }; - nat = mkOption { - type = types.bool; - default = true; + reseed.verify = mkEnableOption "SU3 signature verification"; + + reseed.file = mkOption { + type = with types; nullOr str; + default = null; description = '' - Assume router is NATed. Enabled by default. + Full path to SU3 file to reseed from. ''; }; - upnp = { - enable = mkOption { - type = types.bool; - default = false; - description = '' - Enables UPnP. - ''; - }; + reseed.urls = mkOption { + type = with types; listOf str; + default = []; + description = '' + Reseed URLs. + ''; + }; - name = mkOption { - type = types.str; - default = "I2Pd"; - description = '' - Name i2pd appears in UPnP forwardings list. - ''; - }; + reseed.floodfill = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Path to router info of floodfill to reseed from. + ''; }; - precomputation.elgamal = mkOption { - type = types.bool; - default = true; + reseed.zipfile = mkOption { + type = with types; nullOr str; + default = null; description = '' - Whenever to use precomputated tables for ElGamal. - <command>i2pd</command> defaults to <literal>false</literal> - to save 64M of memory (and looses some performance). + Path to local .zip file to reseed from. + ''; + }; - We default to <literal>true</literal> as that is what most - users want anyway. + reseed.proxy = mkOption { + type = with types; nullOr str; + default = null; + description = '' + URL for reseed proxy, supports http/socks. ''; }; - reseed = { - verify = mkOption { - type = types.bool; - default = false; - description = '' - Request SU3 signature verification - ''; - }; + addressbook.defaulturl = mkOption { + type = types.str; + default = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt"; + description = '' + AddressBook subscription URL for initial setup + ''; + }; + addressbook.subscriptions = mkOption { + type = with types; listOf str; + default = [ + "http://inr.i2p/export/alive-hosts.txt" + "http://i2p-projekt.i2p/hosts.txt" + "http://stats.i2p/cgi-bin/newhosts.txt" + ]; + description = '' + AddressBook subscription URLs + ''; + }; - file = mkOption { - type = types.str; - default = ""; - description = '' - Full path to SU3 file to reseed from - ''; - }; + trust.enable = mkEnableOption "Explicit trust options"; - urls = mkOption { - type = with types; listOf str; - default = [ - "https://reseed.i2p-project.de/" - "https://i2p.mooo.com/netDb/" - "https://netdb.i2p2.no/" - "https://us.reseed.i2p2.no:444/" - "https://uk.reseed.i2p2.no:444/" - "https://i2p.manas.ca:8443/" - ]; - description = '' - Reseed URLs - ''; - }; + trust.family = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Router Familiy to trust for first hops. + ''; }; - addressbook = { - defaulturl = mkOption { - type = types.str; - default = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt"; - description = '' - AddressBook subscription URL for initial setup - ''; - }; - subscriptions = mkOption { - type = with types; listOf str; - default = [ - "http://inr.i2p/export/alive-hosts.txt" - "http://i2p-projekt.i2p/hosts.txt" - "http://stats.i2p/cgi-bin/newhosts.txt" - ]; - description = '' - AddressBook subscription URLs - ''; - }; + trust.routers = mkOption { + type = with types; listOf str; + default = []; + description = '' + Only connect to the listed routers. + ''; + }; + + trust.hidden = mkEnableOption "Router concealment."; + + websocket = mkEndpointOpt "websockets" "127.0.0.1" 7666; + + exploratory.inbound = i2cpOpts "exploratory"; + exploratory.outbound = i2cpOpts "exploratory"; + + ntcp2.enable = mkEnableTrueOption "NTCP2."; + ntcp2.published = mkEnableOption "NTCP2 publication."; + ntcp2.port = mkOption { + type = types.int; + default = 0; + description = '' + Port to listen for incoming NTCP2 connections (0=auto). + ''; }; limits.transittunnels = mkOption { type = types.int; default = 2500; description = '' - Maximum number of active transit sessions + Maximum number of active transit sessions. + ''; + }; + + limits.coreSize = mkOption { + type = types.int; + default = 0; + description = '' + Maximum size of corefile in Kb (0 - use system limit). + ''; + }; + + limits.openFiles = mkOption { + type = types.int; + default = 0; + description = '' + Maximum number of open files (0 - use system default). + ''; + }; + + limits.ntcpHard = mkOption { + type = types.int; + default = 0; + description = '' + Maximum number of active transit sessions. + ''; + }; + + limits.ntcpSoft = mkOption { + type = types.int; + default = 0; + description = '' + Threshold to start probabalistic backoff with ntcp sessions (default: use system limit). + ''; + }; + + limits.ntcpThreads = mkOption { + type = types.int; + default = 1; + description = '' + Maximum number of threads used by NTCP DH worker. ''; }; proto.http = (mkEndpointOpt "http" "127.0.0.1" 7070) // { - auth = mkOption { - type = types.bool; - default = false; - description = '' - Enable authentication for webconsole. - ''; - }; + + auth = mkEnableOption "Webconsole authentication"; + user = mkOption { type = types.str; default = "i2pd"; @@ -373,6 +546,7 @@ in Username for webconsole access ''; }; + pass = mkOption { type = types.str; default = "i2pd"; @@ -380,11 +554,35 @@ in Password for webconsole access. ''; }; + + strictHeaders = mkOption { + type = with types; nullOr bool; + default = null; + description = '' + Enable strict host checking on WebUI. + ''; + }; + + hostname = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Expected hostname for WebUI. + ''; + }; }; - proto.httpProxy = mkKeyedEndpointOpt "httpproxy" "127.0.0.1" 4444 ""; - proto.socksProxy = (mkKeyedEndpointOpt "socksproxy" "127.0.0.1" 4447 "") + proto.httpProxy = (mkKeyedEndpointOpt "httpproxy" "127.0.0.1" 4444 "httpproxy-keys.dat") + // { + outproxy = mkOption { + type = with types; nullOr str; + default = null; + description = "Upstream outproxy bind address."; + }; + }; + proto.socksProxy = (mkKeyedEndpointOpt "socksproxy" "127.0.0.1" 4447 "socksproxy-keys.dat") // { + outproxyEnable = mkEnableOption "SOCKS outproxy"; outproxy = mkOption { type = types.str; default = "127.0.0.1"; @@ -408,8 +606,8 @@ in { name, ... }: { options = { destinationPort = mkOption { - type = types.int; - default = 0; + type = with types; nullOr int; + default = null; description = "Connect to particular port at destination."; }; } // commonTunOpts name; diff --git a/nixos/modules/services/networking/iperf3.nix b/nixos/modules/services/networking/iperf3.nix new file mode 100644 index 000000000000..742404a5692f --- /dev/null +++ b/nixos/modules/services/networking/iperf3.nix @@ -0,0 +1,87 @@ +{ config, lib, pkgs, ... }: with lib; +let + cfg = config.services.iperf3; + + api = { + enable = mkEnableOption "iperf3 network throughput testing server"; + port = mkOption { + type = types.ints.u16; + default = 5201; + description = "Server port to listen on for iperf3 client requsts."; + }; + affinity = mkOption { + type = types.nullOr types.ints.unsigned; + default = null; + description = "CPU affinity for the process."; + }; + bind = mkOption { + type = types.nullOr types.str; + default = null; + description = "Bind to the specific interface associated with the given address."; + }; + verbose = mkOption { + type = types.bool; + default = false; + description = "Give more detailed output."; + }; + forceFlush = mkOption { + type = types.bool; + default = false; + description = "Force flushing output at every interval."; + }; + debug = mkOption { + type = types.bool; + default = false; + description = "Emit debugging output."; + }; + rsaPrivateKey = mkOption { + type = types.nullOr types.path; + default = null; + description = "Path to the RSA private key (not password-protected) used to decrypt authentication credentials from the client."; + }; + authorizedUsersFile = mkOption { + type = types.nullOr types.path; + default = null; + description = "Path to the configuration file containing authorized users credentials to run iperf tests."; + }; + extraFlags = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "Extra flags to pass to iperf3(1)."; + }; + }; + + imp = { + systemd.services.iperf3 = { + description = "iperf3 daemon"; + unitConfig.Documentation = "man:iperf3(1) https://iperf.fr/iperf-doc.php"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + Restart = "on-failure"; + RestartSec = 2; + DynamicUser = true; + PrivateDevices = true; + CapabilityBoundingSet = ""; + NoNewPrivileges = true; + ExecStart = '' + ${pkgs.iperf3}/bin/iperf \ + --server \ + --port ${toString cfg.port} \ + ${optionalString (cfg.affinity != null) "--affinity ${toString cfg.affinity}"} \ + ${optionalString (cfg.bind != null) "--bind ${cfg.bind}"} \ + ${optionalString (cfg.rsaPrivateKey != null) "--rsa-private-key-path ${cfg.rsaPrivateKey}"} \ + ${optionalString (cfg.authorizedUsersFile != null) "--authorized-users-path ${cfg.authorizedUsersFile}"} \ + ${optionalString cfg.verbose "--verbose"} \ + ${optionalString cfg.debug "--debug"} \ + ${optionalString cfg.forceFlush "--forceflush"} \ + ${escapeShellArgs cfg.extraFlags} + ''; + }; + }; + }; +in { + options.services.iperf3 = api; + config = mkIf cfg.enable imp; +} diff --git a/nixos/modules/services/networking/iwd.nix b/nixos/modules/services/networking/iwd.nix index cfc536fc5b5f..18ed20e28886 100644 --- a/nixos/modules/services/networking/iwd.nix +++ b/nixos/modules/services/networking/iwd.nix @@ -20,14 +20,9 @@ in { services.dbus.packages = [ pkgs.iwd ]; - systemd.services.iwd = { - description = "Wireless daemon"; - before = [ "network.target" ]; - wants = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - - serviceConfig.ExecStart = "${pkgs.iwd}/libexec/iwd"; - }; + systemd.packages = [ pkgs.iwd ]; + + systemd.services.iwd.wantedBy = [ "multi-user.target" ]; systemd.tmpfiles.rules = [ "d /var/lib/iwd 0700 root root -" diff --git a/nixos/modules/services/networking/miniupnpd.nix b/nixos/modules/services/networking/miniupnpd.nix index 19400edb68f9..ab714a6ac75e 100644 --- a/nixos/modules/services/networking/miniupnpd.nix +++ b/nixos/modules/services/networking/miniupnpd.nix @@ -57,32 +57,12 @@ in }; config = mkIf cfg.enable { - # from miniupnpd/netfilter/iptables_init.sh networking.firewall.extraCommands = '' - iptables -t nat -N MINIUPNPD - iptables -t nat -A PREROUTING -i ${cfg.externalInterface} -j MINIUPNPD - iptables -t mangle -N MINIUPNPD - iptables -t mangle -A PREROUTING -i ${cfg.externalInterface} -j MINIUPNPD - iptables -t filter -N MINIUPNPD - iptables -t filter -A FORWARD -i ${cfg.externalInterface} ! -o ${cfg.externalInterface} -j MINIUPNPD - iptables -t nat -N MINIUPNPD-PCP-PEER - iptables -t nat -A POSTROUTING -o ${cfg.externalInterface} -j MINIUPNPD-PCP-PEER + ${pkgs.bash}/bin/bash -x ${pkgs.miniupnpd}/etc/miniupnpd/iptables_init.sh -i ${cfg.externalInterface} ''; - # from miniupnpd/netfilter/iptables_removeall.sh networking.firewall.extraStopCommands = '' - iptables -t nat -F MINIUPNPD - iptables -t nat -D PREROUTING -i ${cfg.externalInterface} -j MINIUPNPD - iptables -t nat -X MINIUPNPD - iptables -t mangle -F MINIUPNPD - iptables -t mangle -D PREROUTING -i ${cfg.externalInterface} -j MINIUPNPD - iptables -t mangle -X MINIUPNPD - iptables -t filter -F MINIUPNPD - iptables -t filter -D FORWARD -i ${cfg.externalInterface} ! -o ${cfg.externalInterface} -j MINIUPNPD - iptables -t filter -X MINIUPNPD - iptables -t nat -F MINIUPNPD-PCP-PEER - iptables -t nat -D POSTROUTING -o ${cfg.externalInterface} -j MINIUPNPD-PCP-PEER - iptables -t nat -X MINIUPNPD-PCP-PEER + ${pkgs.bash}/bin/bash -x ${pkgs.miniupnpd}/etc/miniupnpd/iptables_removeall.sh -i ${cfg.externalInterface} ''; systemd.services.miniupnpd = { diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix index b0bc1c83d6b7..2d76e0676b24 100644 --- a/nixos/modules/services/networking/networkmanager.nix +++ b/nixos/modules/services/networking/networkmanager.nix @@ -289,7 +289,7 @@ in { source = mkOption { type = types.path; description = '' - A script. + Path to the hook script. ''; }; @@ -297,12 +297,28 @@ in { type = types.enum (attrNames dispatcherTypesSubdirMap); default = "basic"; description = '' - Dispatcher hook type. Only basic hooks are currently available. + Dispatcher hook type. Look up the hooks described at + <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.html">https://developer.gnome.org/NetworkManager/stable/NetworkManager.html</link> + and choose the type depending on the output folder. + You should then filter the event type (e.g., "up"/"down") from within your script. ''; }; }; }); default = []; + example = literalExample '' + [ { + source = pkgs.writeText "upHook" ''' + + if [ "$2" != "up" ]; then + logger "exit: event $2 != up" + fi + + # coreutils and iproute are in PATH too + logger "Device $DEVICE_IFACE coming up" + '''; + type = "basic"; + } ]''; description = '' A list of scripts which will be executed in response to network events. ''; @@ -390,25 +406,25 @@ in { { source = configFile; target = "NetworkManager/NetworkManager.conf"; } - { source = "${networkmanager-openvpn}/etc/NetworkManager/VPN/nm-openvpn-service.name"; + { source = "${networkmanager-openvpn}/lib/NetworkManager/VPN/nm-openvpn-service.name"; target = "NetworkManager/VPN/nm-openvpn-service.name"; } - { source = "${networkmanager-vpnc}/etc/NetworkManager/VPN/nm-vpnc-service.name"; + { source = "${networkmanager-vpnc}/lib/NetworkManager/VPN/nm-vpnc-service.name"; target = "NetworkManager/VPN/nm-vpnc-service.name"; } - { source = "${networkmanager-openconnect}/etc/NetworkManager/VPN/nm-openconnect-service.name"; + { source = "${networkmanager-openconnect}/lib/NetworkManager/VPN/nm-openconnect-service.name"; target = "NetworkManager/VPN/nm-openconnect-service.name"; } - { source = "${networkmanager-fortisslvpn}/etc/NetworkManager/VPN/nm-fortisslvpn-service.name"; + { source = "${networkmanager-fortisslvpn}/lib/NetworkManager/VPN/nm-fortisslvpn-service.name"; target = "NetworkManager/VPN/nm-fortisslvpn-service.name"; } - { source = "${networkmanager-l2tp}/etc/NetworkManager/VPN/nm-l2tp-service.name"; + { source = "${networkmanager-l2tp}/lib/NetworkManager/VPN/nm-l2tp-service.name"; target = "NetworkManager/VPN/nm-l2tp-service.name"; } - { source = "${networkmanager_strongswan}/etc/NetworkManager/VPN/nm-strongswan-service.name"; + { source = "${networkmanager_strongswan}/lib/NetworkManager/VPN/nm-strongswan-service.name"; target = "NetworkManager/VPN/nm-strongswan-service.name"; } - { source = "${networkmanager-iodine}/etc/NetworkManager/VPN/nm-iodine-service.name"; + { source = "${networkmanager-iodine}/lib/NetworkManager/VPN/nm-iodine-service.name"; target = "NetworkManager/VPN/nm-iodine-service.name"; } ] ++ optional (cfg.appendNameservers == [] || cfg.insertNameservers == []) @@ -418,6 +434,7 @@ in { ++ lib.imap1 (i: s: { inherit (s) source; target = "NetworkManager/dispatcher.d/${dispatcherTypesSubdirMap.${s.type}}03userscript${lib.fixedWidthNumber 4 i}"; + mode = "0544"; }) cfg.dispatcherScripts ++ optional (dynamicHostsEnabled) { target = "NetworkManager/dnsmasq.d/dyndns.conf"; @@ -475,6 +492,14 @@ in { }; }; + systemd.services."NetworkManager-dispatcher" = { + wantedBy = [ "network.target" ]; + restartTriggers = [ configFile ]; + + # useful binaries for user-specified hooks + path = [ pkgs.iproute pkgs.utillinux pkgs.coreutils ]; + }; + # Turn off NixOS' network management networking = { useDHCP = false; diff --git a/nixos/modules/services/networking/nullidentdmod.nix b/nixos/modules/services/networking/nullidentdmod.nix new file mode 100644 index 000000000000..786b5227dbad --- /dev/null +++ b/nixos/modules/services/networking/nullidentdmod.nix @@ -0,0 +1,34 @@ +{ config, lib, pkgs, ... }: with lib; let + cfg = config.services.nullidentdmod; + +in { + options.services.nullidentdmod = with types; { + enable = mkEnableOption "Enable the nullidentdmod identd daemon"; + + userid = mkOption { + type = nullOr str; + description = "User ID to return. Set to null to return a random string each time."; + default = null; + example = "alice"; + }; + }; + + config = mkIf cfg.enable { + systemd.sockets.nullidentdmod = { + description = "Socket for identd (NullidentdMod)"; + listenStreams = [ "113" ]; + socketConfig.Accept = true; + wantedBy = [ "sockets.target" ]; + }; + + systemd.services."nullidentdmod@" = { + description = "NullidentdMod service"; + serviceConfig = { + DynamicUser = true; + ExecStart = "${pkgs.nullidentdmod}/bin/nullidentdmod${optionalString (cfg.userid != null) " ${cfg.userid}"}"; + StandardInput = "socket"; + StandardOutput = "socket"; + }; + }; + }; +} diff --git a/nixos/modules/services/networking/shairport-sync.nix b/nixos/modules/services/networking/shairport-sync.nix index 0b87140b0d8d..36ecb74ffc95 100644 --- a/nixos/modules/services/networking/shairport-sync.nix +++ b/nixos/modules/services/networking/shairport-sync.nix @@ -27,7 +27,7 @@ in }; arguments = mkOption { - default = "-v -o pulse"; + default = "-v -d pulse"; description = '' Arguments to pass to the daemon. Defaults to a local pulseaudio server. @@ -72,6 +72,7 @@ in serviceConfig = { User = cfg.user; ExecStart = "${pkgs.shairport-sync}/bin/shairport-sync ${cfg.arguments}"; + RuntimeDirectory = "shairport-sync"; }; }; diff --git a/nixos/modules/services/networking/teamspeak3.nix b/nixos/modules/services/networking/teamspeak3.nix index 3703921ff703..410d650b1f64 100644 --- a/nixos/modules/services/networking/teamspeak3.nix +++ b/nixos/modules/services/networking/teamspeak3.nix @@ -124,7 +124,7 @@ in dbsqlpath=${ts3}/lib/teamspeak/sql/ logpath=${cfg.logPath} \ voice_ip=${cfg.voiceIP} default_voice_port=${toString cfg.defaultVoicePort} \ filetransfer_ip=${cfg.fileTransferIP} filetransfer_port=${toString cfg.fileTransferPort} \ - query_ip=${cfg.queryIP} query_port=${toString cfg.queryPort} + query_ip=${cfg.queryIP} query_port=${toString cfg.queryPort} license_accepted=1 ''; WorkingDirectory = cfg.dataDir; User = user; diff --git a/nixos/modules/services/networking/vsftpd.nix b/nixos/modules/services/networking/vsftpd.nix index 1f9107c3ce9c..31e1e65fa9ca 100644 --- a/nixos/modules/services/networking/vsftpd.nix +++ b/nixos/modules/services/networking/vsftpd.nix @@ -99,7 +99,7 @@ let nopriv_user=vsftpd secure_chroot_dir=/var/empty syslog_enable=YES - ${optionalString (pkgs.stdenv.system == "x86_64-linux") '' + ${optionalString (pkgs.stdenv.hostPlatform.system == "x86_64-linux") '' seccomp_sandbox=NO ''} anon_umask=${cfg.anonymousUmask} diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix index 1b87b77f9768..564632a85ae5 100644 --- a/nixos/modules/services/networking/wireguard.nix +++ b/nixos/modules/services/networking/wireguard.nix @@ -190,7 +190,8 @@ let nameValuePair "wireguard-${name}" { description = "WireGuard Tunnel - ${name}"; - after = [ "network.target" ]; + requires = [ "network-online.target" ]; + after = [ "network.target" "network-online.target" ]; wantedBy = [ "multi-user.target" ]; environment.DEVICE = name; path = with pkgs; [ kmod iproute wireguard-tools ]; @@ -236,7 +237,7 @@ let ${values.postSetup} ''; - preStop = '' + postStop = '' ip link del dev ${name} ${values.postShutdown} ''; diff --git a/nixos/modules/services/networking/xrdp.nix b/nixos/modules/services/networking/xrdp.nix index 0e882873b4ba..61f22a366a02 100644 --- a/nixos/modules/services/networking/xrdp.nix +++ b/nixos/modules/services/networking/xrdp.nix @@ -93,10 +93,14 @@ in config = mkIf cfg.enable { - # copied from <nixos/modules/services/x11/xserver.nix> # xrdp can run X11 program even if "services.xserver.enable = false" - environment.pathsToLink = - [ "/etc/xdg" "/share/xdg" "/share/applications" "/share/icons" "/share/pixmaps" ]; + xdg = { + autostart.enable = true; + menus.enable = true; + mime.enable = true; + icons.enable = true; + }; + fonts.enableDefaultFonts = mkDefault true; systemd = { diff --git a/nixos/modules/services/networking/zeronet.nix b/nixos/modules/services/networking/zeronet.nix new file mode 100644 index 000000000000..8b60799891ca --- /dev/null +++ b/nixos/modules/services/networking/zeronet.nix @@ -0,0 +1,114 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.zeronet; + + zConfFile = pkgs.writeTextFile { + name = "zeronet.conf"; + + text = '' + [global] + data_dir = ${cfg.dataDir} + log_dir = ${cfg.logDir} + '' + lib.optionalString (cfg.port != null) '' + ui_port = ${toString cfg.port} + '' + lib.optionalString (cfg.torAlways) '' + tor = always + '' + cfg.extraConfig; + }; +in with lib; { + options.services.zeronet = { + enable = mkEnableOption "zeronet"; + + dataDir = mkOption { + type = types.path; + default = "/var/lib/zeronet"; + example = "/home/okina/zeronet"; + description = "Path to the zeronet data directory."; + }; + + logDir = mkOption { + type = types.path; + default = "/var/log/zeronet"; + example = "/home/okina/zeronet/log"; + description = "Path to the zeronet log directory."; + }; + + port = mkOption { + type = types.nullOr types.int; + default = null; + example = 43110; + description = "Optional zeronet web UI port."; + }; + + tor = mkOption { + type = types.bool; + default = false; + description = "Use TOR for zeronet traffic where possible."; + }; + + torAlways = mkOption { + type = types.bool; + default = false; + description = "Use TOR for all zeronet traffic."; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + + description = '' + Extra configuration. Contents will be added verbatim to the + configuration file at the end. + ''; + }; + }; + + config = mkIf cfg.enable { + services.tor = mkIf cfg.tor { + enable = true; + controlPort = 9051; + extraConfig = '' + CacheDirectoryGroupReadable 1 + CookieAuthentication 1 + CookieAuthFileGroupReadable 1 + ''; + }; + + systemd.services.zeronet = { + description = "zeronet"; + after = [ "network.target" (optionalString cfg.tor "tor.service") ]; + wantedBy = [ "multi-user.target" ]; + + preStart = '' + # Ensure folder exists or create it and permissions are correct + mkdir -p ${escapeShellArg cfg.dataDir} ${escapeShellArg cfg.logDir} + chmod 750 ${escapeShellArg cfg.dataDir} ${escapeShellArg cfg.logDir} + chown zeronet:zeronet ${escapeShellArg cfg.dataDir} ${escapeShellArg cfg.logDir} + ''; + + serviceConfig = { + PermissionsStartOnly = true; + PrivateTmp = "yes"; + User = "zeronet"; + Group = "zeronet"; + ExecStart = "${pkgs.zeronet}/bin/zeronet --config_file ${zConfFile}"; + }; + }; + + users = { + groups.zeronet.gid = config.ids.gids.zeronet; + + users.zeronet = { + description = "zeronet service user"; + home = cfg.dataDir; + createHome = true; + group = "zeronet"; + extraGroups = mkIf cfg.tor [ "tor" ]; + uid = config.ids.uids.zeronet; + }; + }; + }; + + meta.maintainers = with maintainers; [ chiiruno ]; +} diff --git a/nixos/modules/services/search/elasticsearch-curator.nix b/nixos/modules/services/search/elasticsearch-curator.nix new file mode 100644 index 000000000000..43785c392fee --- /dev/null +++ b/nixos/modules/services/search/elasticsearch-curator.nix @@ -0,0 +1,93 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.elasticsearch-curator; + curatorConfig = pkgs.writeTextFile { + name = "config.yaml"; + text = '' + --- + # Remember, leave a key empty if there is no value. None will be a string, + # not a Python "NoneType" + client: + hosts: ${builtins.toJSON cfg.hosts} + port: ${toString cfg.port} + url_prefix: + use_ssl: False + certificate: + client_cert: + client_key: + ssl_no_validate: False + http_auth: + timeout: 30 + master_only: False + logging: + loglevel: INFO + logfile: + logformat: default + blacklist: ['elasticsearch', 'urllib3'] + ''; + }; + curatorAction = pkgs.writeTextFile { + name = "action.yaml"; + text = cfg.actionYAML; + }; +in { + + options.services.elasticsearch-curator = { + + enable = mkEnableOption "elasticsearch curator"; + interval = mkOption { + description = "The frequency to run curator, a systemd.time such as 'hourly'"; + default = "hourly"; + type = types.str; + }; + hosts = mkOption { + description = "a list of elasticsearch hosts to connect to"; + type = types.listOf types.str; + default = ["localhost"]; + }; + port = mkOption { + description = "the port that elasticsearch is listening on"; + type = types.int; + default = 9200; + }; + actionYAML = mkOption { + description = "curator action.yaml file contents, alternatively use curator-cli which takes a simple action command"; + example = '' + --- + actions: + 1: + action: delete_indices + description: >- + Delete indices older than 45 days (based on index name), for logstash- + prefixed indices. Ignore the error if the filter does not result in an + actionable list of indices (ignore_empty_list) and exit cleanly. + options: + ignore_empty_list: True + disable_action: False + filters: + - filtertype: pattern + kind: prefix + value: logstash- + - filtertype: age + source: name + direction: older + timestring: '%Y.%m.%d' + unit: days + unit_count: 45 + ''; + }; + }; + + config = mkIf cfg.enable { + + systemd.services.elasticsearch-curator = { + startAt = cfg.interval; + serviceConfig = { + ExecStart = ''${pkgs.python36Packages.elasticsearch-curator}/bin/curator --config ${curatorConfig} ${curatorAction}''; + }; + }; + }; +} diff --git a/nixos/modules/services/search/elasticsearch.nix b/nixos/modules/services/search/elasticsearch.nix index b0831dcd1ca8..6b688c640d59 100644 --- a/nixos/modules/services/search/elasticsearch.nix +++ b/nixos/modules/services/search/elasticsearch.nix @@ -5,22 +5,14 @@ with lib; let cfg = config.services.elasticsearch; - es5 = builtins.compareVersions (builtins.parseDrvName cfg.package.name).version "5" >= 0; - es6 = builtins.compareVersions (builtins.parseDrvName cfg.package.name).version "6" >= 0; + es6 = builtins.compareVersions cfg.package.version "6" >= 0; esConfig = '' network.host: ${cfg.listenAddress} cluster.name: ${cfg.cluster_name} - ${if es5 then '' - http.port: ${toString cfg.port} - transport.tcp.port: ${toString cfg.tcp_port} - '' else '' - network.port: ${toString cfg.port} - network.tcp.port: ${toString cfg.tcp_port} - # TODO: find a way to enable security manager - security.manager.enabled: false - ''} + http.port: ${toString cfg.port} + transport.tcp.port: ${toString cfg.tcp_port} ${cfg.extraConf} ''; @@ -32,7 +24,7 @@ let text = esConfig; }; - loggingConfigFilename = if es5 then "log4j2.properties" else "logging.yml"; + loggingConfigFilename = "log4j2.properties"; loggingConfigFile = pkgs.writeTextFile { name = loggingConfigFilename; text = cfg.logging; @@ -41,8 +33,7 @@ let esPlugins = pkgs.buildEnv { name = "elasticsearch-plugins"; paths = cfg.plugins; - # Elasticsearch 5.x won't start when the plugins directory does not exist - postBuild = if es5 then "${pkgs.coreutils}/bin/mkdir -p $out/plugins" else ""; + postBuild = "${pkgs.coreutils}/bin/mkdir -p $out/plugins"; }; in { @@ -58,8 +49,8 @@ in { package = mkOption { description = "Elasticsearch package to use."; - default = pkgs.elasticsearch2; - defaultText = "pkgs.elasticsearch2"; + default = pkgs.elasticsearch; + defaultText = "pkgs.elasticsearch"; type = types.package; }; @@ -100,30 +91,18 @@ in { logging = mkOption { description = "Elasticsearch logging configuration."; - default = - if es5 then '' - logger.action.name = org.elasticsearch.action - logger.action.level = info - - appender.console.type = Console - appender.console.name = console - appender.console.layout.type = PatternLayout - appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n - - rootLogger.level = info - rootLogger.appenderRef.console.ref = console - '' else '' - rootLogger: INFO, console - logger: - action: INFO - com.amazonaws: WARN - appender: - console: - type: console - layout: - type: consolePattern - conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n" - ''; + default = '' + logger.action.name = org.elasticsearch.action + logger.action.level = info + + appender.console.type = Console + appender.console.name = console + appender.console.layout.type = PatternLayout + appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n + + rootLogger.level = info + rootLogger.appenderRef.console.ref = console + ''; type = types.str; }; @@ -204,9 +183,9 @@ in { cp ${elasticsearchYml} ${configDir}/elasticsearch.yml # Make sure the logging configuration for old elasticsearch versions is removed: - rm -f ${if es5 then "${configDir}/logging.yml" else "${configDir}/log4j2.properties"} + rm -f "${configDir}/logging.yml" cp ${loggingConfigFile} ${configDir}/${loggingConfigFilename} - ${optionalString es5 "mkdir -p ${configDir}/scripts"} + mkdir -p ${configDir}/scripts ${optionalString es6 "cp ${cfg.package}/config/jvm.options ${configDir}/jvm.options"} if [ "$(id -u)" = 0 ]; then chown -R elasticsearch:elasticsearch ${cfg.dataDir}; fi diff --git a/nixos/modules/services/search/kibana.nix b/nixos/modules/services/search/kibana.nix index 5885a72c6628..ca36bba58c02 100644 --- a/nixos/modules/services/search/kibana.nix +++ b/nixos/modules/services/search/kibana.nix @@ -5,43 +5,7 @@ with lib; let cfg = config.services.kibana; - atLeast54 = versionAtLeast (builtins.parseDrvName cfg.package.name).version "5.4"; - - cfgFile = if atLeast54 then cfgFile5 else cfgFile4; - - cfgFile4 = pkgs.writeText "kibana.json" (builtins.toJSON ( - (filterAttrsRecursive (n: v: v != null) ({ - host = cfg.listenAddress; - port = cfg.port; - ssl_cert_file = cfg.cert; - ssl_key_file = cfg.key; - - kibana_index = cfg.index; - default_app_id = cfg.defaultAppId; - - elasticsearch_url = cfg.elasticsearch.url; - kibana_elasticsearch_username = cfg.elasticsearch.username; - kibana_elasticsearch_password = cfg.elasticsearch.password; - kibana_elasticsearch_cert = cfg.elasticsearch.cert; - kibana_elasticsearch_key = cfg.elasticsearch.key; - ca = cfg.elasticsearch.ca; - - bundled_plugin_ids = [ - "plugins/dashboard/index" - "plugins/discover/index" - "plugins/doc/index" - "plugins/kibana/index" - "plugins/markdown_vis/index" - "plugins/metric_vis/index" - "plugins/settings/index" - "plugins/table_vis/index" - "plugins/vis_types/index" - "plugins/visualize/index" - ]; - } // cfg.extraConf) - ))); - - cfgFile5 = pkgs.writeText "kibana.json" (builtins.toJSON ( + cfgFile = pkgs.writeText "kibana.json" (builtins.toJSON ( (filterAttrsRecursive (n: v: v != null) ({ server.host = cfg.listenAddress; server.port = cfg.port; diff --git a/nixos/modules/services/security/cfssl.nix b/nixos/modules/services/security/cfssl.nix index 1eb2f65ba602..ee6d5d91fe15 100644 --- a/nixos/modules/services/security/cfssl.nix +++ b/nixos/modules/services/security/cfssl.nix @@ -146,7 +146,7 @@ in { }; }; - config = { + config = mkIf cfg.enable { users.extraGroups.cfssl = { gid = config.ids.gids.cfssl; }; @@ -159,7 +159,7 @@ in { uid = config.ids.uids.cfssl; }; - systemd.services.cfssl = mkIf cfg.enable { + systemd.services.cfssl = { description = "CFSSL CA API server"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; diff --git a/nixos/modules/services/security/clamav.nix b/nixos/modules/services/security/clamav.nix index 9ad0095252de..04b433f8f2bf 100644 --- a/nixos/modules/services/security/clamav.nix +++ b/nixos/modules/services/security/clamav.nix @@ -95,7 +95,7 @@ in environment.etc."clamav/freshclam.conf".source = freshclamConfigFile; environment.etc."clamav/clamd.conf".source = clamdConfigFile; - systemd.services.clamav-daemon = optionalAttrs cfg.daemon.enable { + systemd.services.clamav-daemon = mkIf cfg.daemon.enable { description = "ClamAV daemon (clamd)"; after = optional cfg.updater.enable "clamav-freshclam.service"; requires = optional cfg.updater.enable "clamav-freshclam.service"; @@ -116,7 +116,7 @@ in }; }; - systemd.timers.clamav-freshclam = optionalAttrs cfg.updater.enable { + systemd.timers.clamav-freshclam = mkIf cfg.updater.enable { description = "Timer for ClamAV virus database updater (freshclam)"; wantedBy = [ "timers.target" ]; timerConfig = { @@ -125,7 +125,7 @@ in }; }; - systemd.services.clamav-freshclam = optionalAttrs cfg.updater.enable { + systemd.services.clamav-freshclam = mkIf cfg.updater.enable { description = "ClamAV virus database updater (freshclam)"; restartTriggers = [ freshclamConfigFile ]; @@ -137,6 +137,7 @@ in serviceConfig = { Type = "oneshot"; ExecStart = "${pkg}/bin/freshclam"; + SuccessExitStatus = "1"; # if databases are up to date PrivateTmp = "yes"; PrivateDevices = "yes"; }; diff --git a/nixos/modules/services/security/sks.nix b/nixos/modules/services/security/sks.nix index 62308428f326..9f0261038d5b 100644 --- a/nixos/modules/services/security/sks.nix +++ b/nixos/modules/services/security/sks.nix @@ -3,78 +3,112 @@ with lib; let - cfg = config.services.sks; - sksPkg = cfg.package; -in - -{ +in { + meta.maintainers = with maintainers; [ primeos calbrecht jcumming ]; options = { services.sks = { - enable = mkEnableOption "sks"; + enable = mkEnableOption '' + SKS (synchronizing key server for OpenPGP) and start the database + server. You need to create "''${dataDir}/dump/*.gpg" for the initial + import''; package = mkOption { default = pkgs.sks; defaultText = "pkgs.sks"; type = types.package; - description = " - Which sks derivation to use. - "; + description = "Which SKS derivation to use."; + }; + + dataDir = mkOption { + type = types.path; + default = "/var/db/sks"; + example = "/var/lib/sks"; + # TODO: The default might change to "/var/lib/sks" as this is more + # common. There's also https://github.com/NixOS/nixpkgs/issues/26256 + # and "/var/db" is not FHS compliant (seems to come from BSD). + description = '' + Data directory (-basedir) for SKS, where the database and all + configuration files are located (e.g. KDB, PTree, membership and + sksconf). + ''; }; hkpAddress = mkOption { default = [ "127.0.0.1" "::1" ]; type = types.listOf types.str; - description = " - Wich ip addresses the sks-keyserver is listening on. - "; + description = '' + Domain names, IPv4 and/or IPv6 addresses to listen on for HKP + requests. + ''; }; hkpPort = mkOption { default = 11371; - type = types.int; - description = " - Which port the sks-keyserver is listening on. - "; + type = types.ints.u16; + description = "HKP port to listen on."; + }; + + webroot = mkOption { + type = types.nullOr types.path; + default = "${sksPkg.webSamples}/OpenPKG"; + defaultText = "\${pkgs.sks.webSamples}/OpenPKG"; + description = '' + Source directory (will be symlinked, if not null) for the files the + built-in webserver should serve. SKS (''${pkgs.sks.webSamples}) + provides the following examples: "HTML5", "OpenPKG", and "XHTML+ES". + The index file can be named index.html, index.htm, index.xhtm, or + index.xhtml. Files with the extensions .css, .es, .js, .jpg, .jpeg, + .png, or .gif are supported. Subdirectories and filenames with + anything other than alphanumeric characters and the '.' character + will be ignored. + ''; }; }; }; config = mkIf cfg.enable { - environment.systemPackages = [ sksPkg ]; - - users.users.sks = { - createHome = true; - home = "/var/db/sks"; - isSystemUser = true; - shell = "${pkgs.coreutils}/bin/true"; + users = { + users.sks = { + isSystemUser = true; + description = "SKS user"; + home = cfg.dataDir; + createHome = true; + group = "sks"; + useDefaultShell = true; + packages = [ sksPkg pkgs.db ]; + }; + groups.sks = { }; }; systemd.services = let hkpAddress = "'" + (builtins.concatStringsSep " " cfg.hkpAddress) + "'" ; hkpPort = builtins.toString cfg.hkpPort; - home = config.users.users.sks.home; - user = config.users.users.sks.name; in { - sks-keyserver = { + "sks-db" = { + description = "SKS database server"; + after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; preStart = '' - mkdir -p ${home}/dump - ${pkgs.sks}/bin/sks build ${home}/dump/*.gpg -n 10 -cache 100 || true #*/ - ${pkgs.sks}/bin/sks cleandb || true - ${pkgs.sks}/bin/sks pbuild -cache 20 -ptree_cache 70 || true + ${lib.optionalString (cfg.webroot != null) + "ln -sfT \"${cfg.webroot}\" web"} + mkdir -p dump + ${sksPkg}/bin/sks build dump/*.gpg -n 10 -cache 100 || true #*/ + ${sksPkg}/bin/sks cleandb || true + ${sksPkg}/bin/sks pbuild -cache 20 -ptree_cache 70 || true ''; serviceConfig = { - WorkingDirectory = home; - User = user; + WorkingDirectory = "~"; + User = "sks"; + Group = "sks"; Restart = "always"; - ExecStart = "${pkgs.sks}/bin/sks db -hkp_address ${hkpAddress} -hkp_port ${hkpPort}"; + ExecStart = "${sksPkg}/bin/sks db -hkp_address ${hkpAddress} -hkp_port ${hkpPort}"; }; }; }; diff --git a/nixos/modules/services/security/tor.nix b/nixos/modules/services/security/tor.nix index def77ba69e58..9b6d4be9bda8 100644 --- a/nixos/modules/services/security/tor.nix +++ b/nixos/modules/services/security/tor.nix @@ -208,7 +208,7 @@ in enable = mkOption { type = types.bool; default = false; - description = "Whether to enable tor transaprent proxy"; + description = "Whether to enable tor transparent proxy"; }; listenAddress = mkOption { diff --git a/nixos/modules/services/security/usbguard.nix b/nixos/modules/services/security/usbguard.nix index 5d469cabe2cb..88d2f69db572 100644 --- a/nixos/modules/services/security/usbguard.nix +++ b/nixos/modules/services/security/usbguard.nix @@ -188,11 +188,14 @@ in { wants = [ "systemd-udevd.service" "local-fs.target" ]; # make sure an empty rule file and required directories exist - preStart = ''mkdir -p $(dirname "${cfg.ruleFile}") "${cfg.IPCAccessControlFiles}" && ([ -f "${cfg.ruleFile}" ] || touch ${cfg.ruleFile})''; + preStart = '' + mkdir -p $(dirname "${cfg.ruleFile}") $(dirname "${cfg.auditFilePath}") "${cfg.IPCAccessControlFiles}" \ + && ([ -f "${cfg.ruleFile}" ] || touch ${cfg.ruleFile}) + ''; serviceConfig = { Type = "simple"; - ExecStart = ''${pkgs.usbguard}/bin/usbguard-daemon -P -d -k -c ${daemonConfFile}''; + ExecStart = ''${pkgs.usbguard}/bin/usbguard-daemon -P -k -c ${daemonConfFile}''; Restart = "on-failure"; }; }; diff --git a/nixos/modules/services/system/kerberos.nix b/nixos/modules/services/system/kerberos.nix index d151385d2f9b..e2c45ed64ac0 100644 --- a/nixos/modules/services/system/kerberos.nix +++ b/nixos/modules/services/system/kerberos.nix @@ -42,7 +42,7 @@ in protocol = "tcp"; user = "root"; server = "${pkgs.tcp_wrappers}/bin/tcpd"; - serverArgs = "${pkgs.heimdalFull}/bin/kadmind"; + serverArgs = "${pkgs.heimdalFull}/libexec/heimdal/kadmind"; }; systemd.services.kdc = { @@ -51,13 +51,13 @@ in preStart = '' mkdir -m 0755 -p ${stateDir} ''; - script = "${heimdalFull}/bin/kdc"; + script = "${heimdalFull}/libexec/heimdal/kdc"; }; systemd.services.kpasswdd = { description = "Kerberos Password Changing daemon"; wantedBy = [ "multi-user.target" ]; - script = "${heimdalFull}/bin/kpasswdd"; + script = "${heimdalFull}/libexec/heimdal/kpasswdd"; }; }; diff --git a/nixos/modules/services/system/localtime.nix b/nixos/modules/services/system/localtime.nix index b9355bbb9441..c7e897c96448 100644 --- a/nixos/modules/services/system/localtime.nix +++ b/nixos/modules/services/system/localtime.nix @@ -22,14 +22,8 @@ in { config = mkIf cfg.enable { services.geoclue2.enable = true; - security.polkit.extraConfig = '' - polkit.addRule(function(action, subject) { - if (action.id == "org.freedesktop.timedate1.set-timezone" - && subject.user == "localtimed") { - return polkit.Result.YES; - } - }); - ''; + # so polkit will pick up the rules + environment.systemPackages = [ pkgs.localtime ]; users.users = [{ name = "localtimed"; diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix index 96413d2dd563..719eb76f42c2 100644 --- a/nixos/modules/services/torrent/transmission.nix +++ b/nixos/modules/services/torrent/transmission.nix @@ -142,6 +142,7 @@ in ${getLib pkgs.attr}/lib/libattr*.so* mr, ${getLib pkgs.lz4}/lib/liblz4*.so* mr, ${getLib pkgs.libkrb5}/lib/lib*.so* mr, + ${getLib pkgs.keyutils}/lib/libkeyutils*.so* mr, @{PROC}/sys/kernel/random/uuid r, @{PROC}/sys/vm/overcommit_memory r, diff --git a/nixos/modules/services/web-apps/codimd.nix b/nixos/modules/services/web-apps/codimd.nix new file mode 100644 index 000000000000..a0af28eac7cc --- /dev/null +++ b/nixos/modules/services/web-apps/codimd.nix @@ -0,0 +1,920 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.codimd; + + prettyJSON = conf: + pkgs.runCommand "codimd-config.json" { } '' + echo '${builtins.toJSON conf}' | ${pkgs.jq}/bin/jq \ + '{production:del(.[]|nulls)|del(.[][]?|nulls)}' > $out + ''; +in +{ + options.services.codimd = { + enable = mkEnableOption "the CodiMD Markdown Editor"; + + groups = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Groups to which the codimd user should be added. + ''; + }; + + workDir = mkOption { + type = types.path; + default = "/var/lib/codimd"; + description = '' + Working directory for the CodiMD service. + ''; + }; + + configuration = { + debug = mkEnableOption "debug mode"; + domain = mkOption { + type = types.nullOr types.str; + default = null; + example = "codimd.org"; + description = '' + Domain name for the CodiMD instance. + ''; + }; + urlPath = mkOption { + type = types.nullOr types.str; + default = null; + example = "/url/path/to/codimd"; + description = '' + Path under which CodiMD is accessible. + ''; + }; + host = mkOption { + type = types.str; + default = "localhost"; + description = '' + Address to listen on. + ''; + }; + port = mkOption { + type = types.int; + default = 3000; + example = "80"; + description = '' + Port to listen on. + ''; + }; + path = mkOption { + type = types.nullOr types.str; + default = null; + example = "/var/run/codimd.sock"; + description = '' + Specify where a UNIX domain socket should be placed. + ''; + }; + allowOrigin = mkOption { + type = types.listOf types.str; + default = []; + example = [ "localhost" "codimd.org" ]; + description = '' + List of domains to whitelist. + ''; + }; + useSSL = mkOption { + type = types.bool; + default = false; + description = '' + Enable to use SSL server. This will also enable + <option>protocolUseSSL</option>. + ''; + }; + hsts = { + enable = mkOption { + type = types.bool; + default = true; + description = '' + Wheter to enable HSTS if HTTPS is also enabled. + ''; + }; + maxAgeSeconds = mkOption { + type = types.int; + default = 31536000; + description = '' + Max duration for clients to keep the HSTS status. + ''; + }; + includeSubdomains = mkOption { + type = types.bool; + default = true; + description = '' + Whether to include subdomains in HSTS. + ''; + }; + preload = mkOption { + type = types.bool; + default = true; + description = '' + Whether to allow preloading of the site's HSTS status. + ''; + }; + }; + csp = mkOption { + type = types.nullOr types.attrs; + default = null; + example = literalExample '' + { + enable = true; + directives = { + scriptSrc = "trustworthy.scripts.example.com"; + }; + upgradeInsecureRequest = "auto"; + addDefaults = true; + } + ''; + description = '' + Specify the Content Security Policy which is passed to Helmet. + For configuration details see <link xlink:href="https://helmetjs.github.io/docs/csp/" + >https://helmetjs.github.io/docs/csp/</link>. + ''; + }; + protocolUseSSL = mkOption { + type = types.bool; + default = false; + description = '' + Enable to use TLS for resource paths. + This only applies when <option>domain</option> is set. + ''; + }; + urlAddPort = mkOption { + type = types.bool; + default = false; + description = '' + Enable to add the port to callback URLs. + This only applies when <option>domain</option> is set + and only for ports other than 80 and 443. + ''; + }; + useCDN = mkOption { + type = types.bool; + default = true; + description = '' + Whether to use CDN resources or not. + ''; + }; + allowAnonymous = mkOption { + type = types.bool; + default = true; + description = '' + Whether to allow anonymous usage. + ''; + }; + allowAnonymousEdits = mkOption { + type = types.bool; + default = false; + description = '' + Whether to allow guests to edit existing notes with the `freely' permission, + when <option>allowAnonymous</option> is enabled. + ''; + }; + allowFreeURL = mkOption { + type = types.bool; + default = false; + description = '' + Whether to allow note creation by accessing a nonexistent note URL. + ''; + }; + defaultPermission = mkOption { + type = types.enum [ "freely" "editable" "limited" "locked" "private" ]; + default = "editable"; + description = '' + Default permissions for notes. + This only applies for signed-in users. + ''; + }; + dbURL = mkOption { + type = types.nullOr types.str; + default = null; + example = '' + postgres://user:pass@host:5432/dbname + ''; + description = '' + Specify which database to use. + CodiMD supports mysql, postgres, sqlite and mssql. + See <link xlink:href="https://sequelize.readthedocs.io/en/v3/"> + https://sequelize.readthedocs.io/en/v3/</link> for more information. + Note: This option overrides <option>db</option>. + ''; + }; + db = mkOption { + type = types.attrs; + default = {}; + example = literalExample '' + { + dialect = "sqlite"; + storage = "/var/lib/codimd/db.codimd.sqlite"; + } + ''; + description = '' + Specify the configuration for sequelize. + CodiMD supports mysql, postgres, sqlite and mssql. + See <link xlink:href="https://sequelize.readthedocs.io/en/v3/"> + https://sequelize.readthedocs.io/en/v3/</link> for more information. + Note: This option overrides <option>db</option>. + ''; + }; + sslKeyPath= mkOption { + type = types.nullOr types.str; + default = null; + example = "/var/lib/codimd/codimd.key"; + description = '' + Path to the SSL key. Needed when <option>useSSL</option> is enabled. + ''; + }; + sslCertPath = mkOption { + type = types.nullOr types.str; + default = null; + example = "/var/lib/codimd/codimd.crt"; + description = '' + Path to the SSL cert. Needed when <option>useSSL</option> is enabled. + ''; + }; + sslCAPath = mkOption { + type = types.listOf types.str; + default = []; + example = [ "/var/lib/codimd/ca.crt" ]; + description = '' + SSL ca chain. Needed when <option>useSSL</option> is enabled. + ''; + }; + dhParamPath = mkOption { + type = types.nullOr types.str; + default = null; + example = "/var/lib/codimd/dhparam.pem"; + description = '' + Path to the SSL dh params. Needed when <option>useSSL</option> is enabled. + ''; + }; + tmpPath = mkOption { + type = types.str; + default = "/tmp"; + description = '' + Path to the temp directory CodiMD should use. + Note that <option>serviceConfig.PrivateTmp</option> is enabled for + the CodiMD systemd service by default. + (Non-canonical paths are relative to CodiMD's base directory) + ''; + }; + defaultNotePath = mkOption { + type = types.nullOr types.str; + default = "./public/default.md"; + description = '' + Path to the default Note file. + (Non-canonical paths are relative to CodiMD's base directory) + ''; + }; + docsPath = mkOption { + type = types.nullOr types.str; + default = "./public/docs"; + description = '' + Path to the docs directory. + (Non-canonical paths are relative to CodiMD's base directory) + ''; + }; + indexPath = mkOption { + type = types.nullOr types.str; + default = "./public/views/index.ejs"; + description = '' + Path to the index template file. + (Non-canonical paths are relative to CodiMD's base directory) + ''; + }; + hackmdPath = mkOption { + type = types.nullOr types.str; + default = "./public/views/hackmd.ejs"; + description = '' + Path to the hackmd template file. + (Non-canonical paths are relative to CodiMD's base directory) + ''; + }; + errorPath = mkOption { + type = types.nullOr types.str; + default = null; + defaultText = "./public/views/error.ejs"; + description = '' + Path to the error template file. + (Non-canonical paths are relative to CodiMD's base directory) + ''; + }; + prettyPath = mkOption { + type = types.nullOr types.str; + default = null; + defaultText = "./public/views/pretty.ejs"; + description = '' + Path to the pretty template file. + (Non-canonical paths are relative to CodiMD's base directory) + ''; + }; + slidePath = mkOption { + type = types.nullOr types.str; + default = null; + defaultText = "./public/views/slide.hbs"; + description = '' + Path to the slide template file. + (Non-canonical paths are relative to CodiMD's base directory) + ''; + }; + uploadsPath = mkOption { + type = types.str; + default = "${cfg.workDir}/uploads"; + defaultText = "/var/lib/codimd/uploads"; + description = '' + Path under which uploaded files are saved. + ''; + }; + sessionName = mkOption { + type = types.str; + default = "connect.sid"; + description = '' + Specify the name of the session cookie. + ''; + }; + sessionSecret = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Specify the secret used to sign the session cookie. + If unset, one will be generated on startup. + ''; + }; + sessionLife = mkOption { + type = types.int; + default = 1209600000; + description = '' + Session life time in milliseconds. + ''; + }; + heartbeatInterval = mkOption { + type = types.int; + default = 5000; + description = '' + Specify the socket.io heartbeat interval. + ''; + }; + heartbeatTimeout = mkOption { + type = types.int; + default = 10000; + description = '' + Specify the socket.io heartbeat timeout. + ''; + }; + documentMaxLength = mkOption { + type = types.int; + default = 100000; + description = '' + Specify the maximum document length. + ''; + }; + email = mkOption { + type = types.bool; + default = true; + description = '' + Whether to enable email sign-in. + ''; + }; + allowEmailRegister = mkOption { + type = types.bool; + default = true; + description = '' + Wether to enable email registration. + ''; + }; + allowGravatar = mkOption { + type = types.bool; + default = true; + description = '' + Whether to use gravatar as profile picture source. + ''; + }; + imageUploadType = mkOption { + type = types.enum [ "imgur" "s3" "minio" "filesystem" ]; + default = "filesystem"; + description = '' + Specify where to upload images. + ''; + }; + minio = mkOption { + type = types.nullOr (types.submodule { + options = { + accessKey = mkOption { + type = types.str; + description = '' + Minio access key. + ''; + }; + secretKey = mkOption { + type = types.str; + description = '' + Minio secret key. + ''; + }; + endpoint = mkOption { + type = types.str; + description = '' + Minio endpoint. + ''; + }; + port = mkOption { + type = types.int; + default = 9000; + description = '' + Minio listen port. + ''; + }; + secure = mkOption { + type = types.bool; + default = true; + description = '' + Whether to use HTTPS for Minio. + ''; + }; + }; + }); + default = null; + description = "Configure the minio third-party integration."; + }; + s3 = mkOption { + type = types.nullOr (types.submodule { + options = { + accessKeyId = mkOption { + type = types.str; + description = '' + AWS access key id. + ''; + }; + secretAccessKey = mkOption { + type = types.str; + description = '' + AWS access key. + ''; + }; + region = mkOption { + type = types.str; + description = '' + AWS S3 region. + ''; + }; + }; + }); + default = null; + description = "Configure the s3 third-party integration."; + }; + s3bucket = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Specify the bucket name for upload types <literal>s3</literal> and <literal>minio</literal>. + ''; + }; + allowPDFExport = mkOption { + type = types.bool; + default = true; + description = '' + Whether to enable PDF exports. + ''; + }; + imgur.clientId = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Imgur API client ID. + ''; + }; + azure = mkOption { + type = types.nullOr (types.submodule { + options = { + connectionString = mkOption { + type = types.str; + description = '' + Azure Blob Storage connection string. + ''; + }; + container = mkOption { + type = types.str; + description = '' + Azure Blob Storage container name. + It will be created if non-existent. + ''; + }; + }; + }); + default = null; + description = "Configure the azure third-party integration."; + }; + oauth2 = mkOption { + type = types.nullOr (types.submodule { + options = { + authorizationURL = mkOption { + type = types.str; + description = '' + Specify the OAuth authorization URL. + ''; + }; + tokenURL = mkOption { + type = types.str; + description = '' + Specify the OAuth token URL. + ''; + }; + clientID = mkOption { + type = types.str; + description = '' + Specify the OAuth client ID. + ''; + }; + clientSecret = mkOption { + type = types.str; + description = '' + Specify the OAuth client secret. + ''; + }; + }; + }); + default = null; + description = "Configure the OAuth integration."; + }; + facebook = mkOption { + type = types.nullOr (types.submodule { + options = { + clientID = mkOption { + type = types.str; + description = '' + Facebook API client ID. + ''; + }; + clientSecret = mkOption { + type = types.str; + description = '' + Facebook API client secret. + ''; + }; + }; + }); + default = null; + description = "Configure the facebook third-party integration"; + }; + twitter = mkOption { + type = types.nullOr (types.submodule { + options = { + consumerKey = mkOption { + type = types.str; + description = '' + Twitter API consumer key. + ''; + }; + consumerSecret = mkOption { + type = types.str; + description = '' + Twitter API consumer secret. + ''; + }; + }; + }); + default = null; + description = "Configure the Twitter third-party integration."; + }; + github = mkOption { + type = types.nullOr (types.submodule { + options = { + clientID = mkOption { + type = types.str; + description = '' + GitHub API client ID. + ''; + }; + clientSecret = mkOption { + type = types.str; + description = '' + Github API client secret. + ''; + }; + }; + }); + default = null; + description = "Configure the GitHub third-party integration."; + }; + gitlab = mkOption { + type = types.nullOr (types.submodule { + options = { + baseURL = mkOption { + type = types.str; + default = ""; + description = '' + GitLab API authentication endpoint. + Only needed for other endpoints than gitlab.com. + ''; + }; + clientID = mkOption { + type = types.str; + description = '' + GitLab API client ID. + ''; + }; + clientSecret = mkOption { + type = types.str; + description = '' + GitLab API client secret. + ''; + }; + scope = mkOption { + type = types.enum [ "api" "read_user" ]; + default = "api"; + description = '' + GitLab API requested scope. + GitLab snippet import/export requires api scope. + ''; + }; + }; + }); + default = null; + description = "Configure the GitLab third-party integration."; + }; + mattermost = mkOption { + type = types.nullOr (types.submodule { + options = { + baseURL = mkOption { + type = types.str; + description = '' + Mattermost authentication endpoint. + ''; + }; + clientID = mkOption { + type = types.str; + description = '' + Mattermost API client ID. + ''; + }; + clientSecret = mkOption { + type = types.str; + description = '' + Mattermost API client secret. + ''; + }; + }; + }); + default = null; + description = "Configure the Mattermost third-party integration."; + }; + dropbox = mkOption { + type = types.nullOr (types.submodule { + options = { + clientID = mkOption { + type = types.str; + description = '' + Dropbox API client ID. + ''; + }; + clientSecret = mkOption { + type = types.str; + description = '' + Dropbox API client secret. + ''; + }; + appKey = mkOption { + type = types.str; + description = '' + Dropbox app key. + ''; + }; + }; + }); + default = null; + description = "Configure the Dropbox third-party integration."; + }; + google = mkOption { + type = types.nullOr (types.submodule { + options = { + clientID = mkOption { + type = types.str; + description = '' + Google API client ID. + ''; + }; + clientSecret = mkOption { + type = types.str; + description = '' + Google API client secret. + ''; + }; + }; + }); + default = null; + description = "Configure the Google third-party integration."; + }; + ldap = mkOption { + type = types.nullOr (types.submodule { + options = { + providerName = mkOption { + type = types.str; + default = ""; + description = '' + Optional name to be displayed at login form, indicating the LDAP provider. + ''; + }; + url = mkOption { + type = types.str; + example = "ldap://localhost"; + description = '' + URL of LDAP server. + ''; + }; + bindDn = mkOption { + type = types.str; + description = '' + Bind DN for LDAP access. + ''; + }; + bindCredentials = mkOption { + type = types.str; + description = '' + Bind credentials for LDAP access. + ''; + }; + searchBase = mkOption { + type = types.str; + example = "o=users,dc=example,dc=com"; + description = '' + LDAP directory to begin search from. + ''; + }; + searchFilter = mkOption { + type = types.str; + example = "(uid={{username}})"; + description = '' + LDAP filter to search with. + ''; + }; + searchAttributes = mkOption { + type = types.listOf types.str; + example = [ "displayName" "mail" ]; + description = '' + LDAP attributes to search with. + ''; + }; + userNameField = mkOption { + type = types.str; + default = ""; + description = '' + LDAP field which is used as the username on CodiMD. + By default <option>useridField</option> is used. + ''; + }; + useridField = mkOption { + type = types.str; + example = "uid"; + description = '' + LDAP field which is a unique identifier for users on CodiMD. + ''; + }; + tlsca = mkOption { + type = types.str; + example = "server-cert.pem,root.pem"; + description = '' + Root CA for LDAP TLS in PEM format. + ''; + }; + }; + }); + default = null; + description = "Configure the LDAP integration."; + }; + saml = mkOption { + type = types.nullOr (types.submodule { + options = { + idpSsoUrl = mkOption { + type = types.str; + example = "https://idp.example.com/sso"; + description = '' + IdP authentication endpoint. + ''; + }; + idpCert = mkOption { + type = types.path; + example = "/path/to/cert.pem"; + description = '' + Path to IdP certificate file in PEM format. + ''; + }; + issuer = mkOption { + type = types.str; + default = ""; + description = '' + Optional identity of the service provider. + This defaults to the server URL. + ''; + }; + identifierFormat = mkOption { + type = types.str; + default = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"; + description = '' + Optional name identifier format. + ''; + }; + groupAttribute = mkOption { + type = types.str; + default = ""; + example = "memberOf"; + description = '' + Optional attribute name for group list. + ''; + }; + externalGroups = mkOption { + type = types.listOf types.str; + default = []; + example = [ "Temporary-staff" "External-users" ]; + description = '' + Excluded group names. + ''; + }; + requiredGroups = mkOption { + type = types.listOf types.str; + default = []; + example = [ "Hackmd-users" "Codimd-users" ]; + description = '' + Required group names. + ''; + }; + attribute = { + id = mkOption { + type = types.str; + default = ""; + description = '' + Attribute map for `id'. + Defaults to `NameID' of SAML response. + ''; + }; + username = mkOption { + type = types.str; + default = ""; + description = '' + Attribute map for `username'. + Defaults to `NameID' of SAML response. + ''; + }; + email = mkOption { + type = types.str; + default = ""; + description = '' + Attribute map for `email'. + Defaults to `NameID' of SAML response if + <option>identifierFormat</option> has + the default value. + ''; + }; + }; + }; + }); + default = null; + description = "Configure the SAML integration."; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { assertion = cfg.configuration.db == {} -> ( + cfg.configuration.dbURL != "" && cfg.configuration.dbURL != null + ); + message = "Database configuration for CodiMD missing."; } + ]; + users.groups.codimd = {}; + users.users.codimd = { + description = "CodiMD service user"; + group = "codimd"; + extraGroups = cfg.groups; + home = cfg.workDir; + createHome = true; + }; + + systemd.services.codimd = { + description = "CodiMD Service"; + wantedBy = [ "multi-user.target" ]; + after = [ "networking.target" ]; + preStart = '' + mkdir -p ${cfg.workDir} + chown -R codimd: ${cfg.workDir} + ''; + serviceConfig = { + WorkingDirectory = cfg.workDir; + ExecStart = "${pkgs.codimd}/bin/codimd"; + Environment = [ + "CMD_CONFIG_FILE=${prettyJSON cfg.configuration}" + "NODE_ENV=production" + ]; + Restart = "always"; + User = "codimd"; + PermissionsStartOnly = true; + PrivateTmp = true; + }; + }; + }; +} diff --git a/nixos/modules/services/web-apps/matomo-doc.xml b/nixos/modules/services/web-apps/matomo-doc.xml index 456aae6cc366..510a335edc3b 100644 --- a/nixos/modules/services/web-apps/matomo-doc.xml +++ b/nixos/modules/services/web-apps/matomo-doc.xml @@ -3,28 +3,24 @@ xmlns:xi="http://www.w3.org/2001/XInclude" version="5.0" xml:id="module-services-matomo"> + <title>Matomo</title> + <para> + Matomo is a real-time web analytics application. This module configures + php-fpm as backend for Matomo, optionally configuring an nginx vhost as well. + </para> + <para> + An automatic setup is not suported by Matomo, so you need to configure Matomo + itself in the browser-based Matomo setup. + </para> + <section xml:id="module-services-matomo-database-setup"> + <title>Database Setup</title> - <title>Matomo</title> <para> - Matomo is a real-time web analytics application. - This module configures php-fpm as backend for Matomo, optionally configuring an nginx vhost as well. - </para> - - <para> - An automatic setup is not suported by Matomo, so you need to configure Matomo itself in the browser-based Matomo setup. - </para> - - - <section> - <title>Database Setup</title> - - <para> - You also need to configure a MariaDB or MySQL database and -user for Matomo yourself, - and enter those credentials in your browser. - You can use passwordless database authentication via the UNIX_SOCKET authentication plugin - with the following SQL commands: - - <programlisting> + You also need to configure a MariaDB or MySQL database and -user for Matomo + yourself, and enter those credentials in your browser. You can use + passwordless database authentication via the UNIX_SOCKET authentication + plugin with the following SQL commands: +<programlisting> # For MariaDB INSTALL PLUGIN unix_socket SONAME 'auth_socket'; CREATE DATABASE matomo; @@ -37,59 +33,58 @@ CREATE USER 'matomo'@'localhost' IDENTIFIED WITH auth_socket; GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost'; </programlisting> + Then fill in <literal>matomo</literal> as database user and database name, + and leave the password field blank. This authentication works by allowing + only the <literal>matomo</literal> unix user to authenticate as the + <literal>matomo</literal> database user (without needing a password), but no + other users. For more information on passwordless login, see + <link xlink:href="https://mariadb.com/kb/en/mariadb/unix_socket-authentication-plugin/" />. + </para> - Then fill in <literal>matomo</literal> as database user and database name, and leave the password field blank. - This authentication works by allowing only the <literal>matomo</literal> unix user to authenticate as the - <literal>matomo</literal> database user (without needing a password), but no other users. - For more information on passwordless login, see - <link xlink:href="https://mariadb.com/kb/en/mariadb/unix_socket-authentication-plugin/" />. - </para> - - <para> - Of course, you can use password based authentication as well, e.g. when the database is not on the same host. - </para> - </section> + <para> + Of course, you can use password based authentication as well, e.g. when the + database is not on the same host. + </para> + </section> + <section xml:id="module-services-matomo-backups"> + <title>Backup</title> + <para> + You only need to take backups of your MySQL database and the + <filename>/var/lib/matomo/config/config.ini.php</filename> file. Use a user + in the <literal>matomo</literal> group or root to access the file. For more + information, see + <link xlink:href="https://matomo.org/faq/how-to-install/faq_138/" />. + </para> + </section> + <section xml:id="module-services-matomo-issues"> + <title>Issues</title> - <section> - <title>Backup</title> + <itemizedlist> + <listitem> <para> - You only need to take backups of your MySQL database and the - <filename>/var/lib/matomo/config/config.ini.php</filename> file. - Use a user in the <literal>matomo</literal> group or root to access the file. - For more information, see <link xlink:href="https://matomo.org/faq/how-to-install/faq_138/" />. + Matomo's file integrity check will warn you. This is due to the patches + necessary for NixOS, you can safely ignore this. </para> - </section> - - - <section> - <title>Issues</title> - <itemizedlist> - <listitem> - <para> - Matomo's file integrity check will warn you. - This is due to the patches necessary for NixOS, you can safely ignore this. - </para> - </listitem> - - <listitem> - <para> - Matomo will warn you that the JavaScript tracker is not writable. - This is because it's located in the read-only nix store. - You can safely ignore this, unless you need a plugin that needs JavaScript tracker access. - </para> - </listitem> - </itemizedlist> - </section> - - - <section> - <title>Using other Web Servers than nginx</title> - + </listitem> + <listitem> <para> - You can use other web servers by forwarding calls for <filename>index.php</filename> and - <filename>piwik.php</filename> to the <literal>/run/phpfpm-matomo.sock</literal> fastcgi unix socket. - You can use the nginx configuration in the module code as a reference to what else should be configured. + Matomo will warn you that the JavaScript tracker is not writable. This is + because it's located in the read-only nix store. You can safely ignore + this, unless you need a plugin that needs JavaScript tracker access. </para> - </section> + </listitem> + </itemizedlist> + </section> + <section xml:id="module-services-matomo-other-web-servers"> + <title>Using other Web Servers than nginx</title> + + <para> + You can use other web servers by forwarding calls for + <filename>index.php</filename> and <filename>piwik.php</filename> to the + <literal>/run/phpfpm-matomo.sock</literal> fastcgi unix socket. You can use + the nginx configuration in the module code as a reference to what else + should be configured. + </para> + </section> </chapter> diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix new file mode 100644 index 000000000000..44c3df1d057b --- /dev/null +++ b/nixos/modules/services/web-apps/nextcloud.nix @@ -0,0 +1,463 @@ +{ config, lib, pkgs, ... }@args: + +with lib; + +let + cfg = config.services.nextcloud; + + toKeyValue = generators.toKeyValue { + mkKeyValue = generators.mkKeyValueDefault {} " = "; + }; + + phpOptionsExtensions = '' + ${optionalString cfg.caching.apcu "extension=${cfg.phpPackages.apcu}/lib/php/extensions/apcu.so"} + ${optionalString cfg.caching.redis "extension=${cfg.phpPackages.redis}/lib/php/extensions/redis.so"} + ${optionalString cfg.caching.memcached "extension=${cfg.phpPackages.memcached}/lib/php/extensions/memcached.so"} + zend_extension = opcache.so + opcache.enable = 1 + ''; + phpOptions = { + upload_max_filesize = cfg.maxUploadSize; + post_max_size = cfg.maxUploadSize; + memory_limit = cfg.maxUploadSize; + } // cfg.phpOptions; + phpOptionsStr = phpOptionsExtensions + (toKeyValue phpOptions); + + occ = pkgs.writeScriptBin "nextcloud-occ" '' + #! ${pkgs.stdenv.shell} + cd ${pkgs.nextcloud} + exec /run/wrappers/bin/sudo -u nextcloud \ + NEXTCLOUD_CONFIG_DIR="${cfg.home}/config" \ + ${config.services.phpfpm.phpPackage}/bin/php \ + -c ${pkgs.writeText "php.ini" phpOptionsStr}\ + occ $* + ''; + +in { + options.services.nextcloud = { + enable = mkEnableOption "nextcloud"; + hostName = mkOption { + type = types.str; + description = "FQDN for the nextcloud instance."; + }; + home = mkOption { + type = types.str; + default = "/var/lib/nextcloud"; + description = "Storage path of nextcloud."; + }; + https = mkOption { + type = types.bool; + default = false; + description = "Enable if there is a TLS terminating proxy in front of nextcloud."; + }; + + maxUploadSize = mkOption { + default = "512M"; + type = types.str; + description = '' + Defines the upload limit for files. This changes the relevant options + in php.ini and nginx if enabled. + ''; + }; + + skeletonDirectory = mkOption { + default = ""; + type = types.str; + description = '' + The directory where the skeleton files are located. These files will be + copied to the data directory of new users. Leave empty to not copy any + skeleton files. + ''; + }; + + nginx.enable = mkEnableOption "nginx vhost management"; + + webfinger = mkOption { + type = types.bool; + default = false; + description = '' + Enable this option if you plan on using the webfinger plugin. + The appropriate nginx rewrite rules will be added to your configuration. + ''; + }; + + phpPackages = mkOption { + type = types.attrs; + default = pkgs.php71Packages; + defaultText = "pkgs.php71Packages"; + description = '' + Overridable attribute of the PHP packages set to use. If any caching + module is enabled, it will be taken from here. Therefore it should + match the version of PHP given to + <literal>services.phpfpm.phpPackage</literal>. + ''; + }; + + phpOptions = mkOption { + type = types.attrsOf types.str; + default = { + "short_open_tag" = "Off"; + "expose_php" = "Off"; + "error_reporting" = "E_ALL & ~E_DEPRECATED & ~E_STRICT"; + "display_errors" = "stderr"; + "opcache.enable_cli" = "1"; + "opcache.interned_strings_buffer" = "8"; + "opcache.max_accelerated_files" = "10000"; + "opcache.memory_consumption" = "128"; + "opcache.revalidate_freq" = "1"; + "opcache.fast_shutdown" = "1"; + "openssl.cafile" = "/etc/ssl/certs/ca-certificates.crt"; + "catch_workers_output" = "yes"; + }; + description = '' + Options for PHP's php.ini file for nextcloud. + ''; + }; + + config = { + dbtype = mkOption { + type = types.enum [ "sqlite" "pgsql" "mysql" ]; + default = "sqlite"; + description = "Database type."; + }; + dbname = mkOption { + type = types.nullOr types.str; + default = "nextcloud"; + description = "Database name."; + }; + dbuser = mkOption { + type = types.nullOr types.str; + default = "nextcloud"; + description = "Database user."; + }; + dbpass = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Database password. Use <literal>dbpassFile</literal> to avoid this + being world-readable in the <literal>/nix/store</literal>. + ''; + }; + dbpassFile = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The full path to a file that contains the database password. + ''; + }; + dbhost = mkOption { + type = types.nullOr types.str; + default = "localhost"; + description = "Database host."; + }; + dbport = mkOption { + type = with types; nullOr (either int str); + default = null; + description = "Database port."; + }; + dbtableprefix = mkOption { + type = types.nullOr types.str; + default = null; + description = "Table prefix in Nextcloud database."; + }; + adminuser = mkOption { + type = types.str; + default = "root"; + description = "Admin username."; + }; + adminpass = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Database password. Use <literal>adminpassFile</literal> to avoid this + being world-readable in the <literal>/nix/store</literal>. + ''; + }; + adminpassFile = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The full path to a file that contains the admin's password. + ''; + }; + + extraTrustedDomains = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Trusted domains, from which the nextcloud installation will be + acessible. You don't need to add + <literal>services.nextcloud.hostname</literal> here. + ''; + }; + }; + + caching = { + apcu = mkOption { + type = types.bool; + default = true; + description = '' + Whether to load the APCu module into PHP. + ''; + }; + redis = mkOption { + type = types.bool; + default = false; + description = '' + Whether to load the Redis module into PHP. + You still need to enable Redis in your config.php. + See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html + ''; + }; + memcached = mkOption { + type = types.bool; + default = false; + description = '' + Whether to load the Memcached module into PHP. + You still need to enable Memcached in your config.php. + See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html + ''; + }; + }; + }; + + config = mkIf cfg.enable (mkMerge [ + { assertions = let acfg = cfg.config; in [ + { assertion = !(acfg.dbpass != null && acfg.dbpassFile != null); + message = "Please specify no more than one of dbpass or dbpassFile"; + } + { assertion = ((acfg.adminpass != null || acfg.adminpassFile != null) + && !(acfg.adminpass != null && acfg.adminpassFile != null)); + message = "Please specify exactly one of adminpass or adminpassFile"; + } + ]; + } + + { systemd.timers."nextcloud-cron" = { + wantedBy = [ "timers.target" ]; + timerConfig.OnBootSec = "5m"; + timerConfig.OnUnitActiveSec = "15m"; + timerConfig.Unit = "nextcloud-cron.service"; + }; + + systemd.services = { + "nextcloud-setup" = let + overrideConfig = pkgs.writeText "nextcloud-config.php" '' + <?php + $CONFIG = [ + 'apps_paths' => [ + [ 'path' => '${cfg.home}/apps', 'url' => '/apps', 'writable' => false ], + [ 'path' => '${cfg.home}/store-apps', 'url' => '/store-apps', 'writable' => true ], + ], + 'datadirectory' => '${cfg.home}/data', + 'skeletondirectory' => '${cfg.skeletonDirectory}', + ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"} + 'log_type' => 'syslog', + ]; + ''; + occInstallCmd = let + c = cfg.config; + adminpass = if c.adminpassFile != null + then ''"$(<"${toString c.adminpassFile}")"'' + else ''"${toString c.adminpass}"''; + dbpass = if c.dbpassFile != null + then ''"$(<"${toString c.dbpassFile}")"'' + else if c.dbpass != null + then ''"${toString c.dbpass}"'' + else null; + installFlags = concatStringsSep " \\\n " + (mapAttrsToList (k: v: "${k} ${toString v}") { + "--database" = ''"${c.dbtype}"''; + # The following attributes are optional depending on the type of + # database. Those that evaluate to null on the left hand side + # will be omitted. + ${if c.dbname != null then "--database-name" else null} = ''"${c.dbname}"''; + ${if c.dbhost != null then "--database-host" else null} = ''"${c.dbhost}"''; + ${if c.dbport != null then "--database-port" else null} = ''"${toString c.dbport}"''; + ${if c.dbuser != null then "--database-user" else null} = ''"${c.dbuser}"''; + ${if (any (x: x != null) [c.dbpass c.dbpassFile]) + then "--database-pass" else null} = dbpass; + ${if c.dbtableprefix != null + then "--database-table-prefix" else null} = ''"${toString c.dbtableprefix}"''; + "--admin-user" = ''"${c.adminuser}"''; + "--admin-pass" = adminpass; + "--data-dir" = ''"${cfg.home}/data"''; + }); + in '' + ${occ}/bin/nextcloud-occ maintenance:install \ + ${installFlags} + ''; + occSetTrustedDomainsCmd = concatStringsSep "\n" (imap0 + (i: v: '' + ${occ}/bin/nextcloud-occ config:system:set trusted_domains \ + ${toString i} --value="${toString v}" + '') ([ cfg.hostName ] ++ cfg.config.extraTrustedDomains)); + + in { + wantedBy = [ "multi-user.target" ]; + before = [ "phpfpm-nextcloud.service" ]; + script = '' + chmod og+x ${cfg.home} + ln -sf ${pkgs.nextcloud}/apps ${cfg.home}/ + mkdir -p ${cfg.home}/config ${cfg.home}/data ${cfg.home}/store-apps + ln -sf ${overrideConfig} ${cfg.home}/config/override.config.php + + chown -R nextcloud:nginx ${cfg.home}/config ${cfg.home}/data ${cfg.home}/store-apps + + # Do not install if already installed + if [[ ! -e ${cfg.home}/config/config.php ]]; then + ${occInstallCmd} + fi + + ${occ}/bin/nextcloud-occ upgrade + + ${occ}/bin/nextcloud-occ config:system:delete trusted_domains + ${occSetTrustedDomainsCmd} + ''; + serviceConfig.Type = "oneshot"; + }; + "nextcloud-cron" = { + environment.NEXTCLOUD_CONFIG_DIR = "${cfg.home}/config"; + serviceConfig.Type = "oneshot"; + serviceConfig.User = "nextcloud"; + serviceConfig.ExecStart = "${pkgs.php}/bin/php -f ${pkgs.nextcloud}/cron.php"; + }; + }; + + services.phpfpm = { + phpOptions = phpOptionsExtensions; + phpPackage = pkgs.php71; + pools.nextcloud = let + phpAdminValues = (toKeyValue + (foldr (a: b: a // b) {} + (mapAttrsToList (k: v: { "php_admin_value[${k}]" = v; }) + phpOptions))); + in { + listen = "/run/phpfpm/nextcloud"; + extraConfig = '' + listen.owner = nginx + listen.group = nginx + user = nextcloud + group = nginx + pm = dynamic + pm.max_children = 32 + pm.start_servers = 2 + pm.min_spare_servers = 2 + pm.max_spare_servers = 4 + env[NEXTCLOUD_CONFIG_DIR] = ${cfg.home}/config + env[PATH] = /run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin + ${phpAdminValues} + ''; + }; + }; + + users.extraUsers.nextcloud = { + home = "${cfg.home}"; + group = "nginx"; + createHome = true; + }; + + environment.systemPackages = [ occ ]; + } + + (mkIf cfg.nginx.enable { + services.nginx = { + enable = true; + virtualHosts = { + "${cfg.hostName}" = { + root = pkgs.nextcloud; + locations = { + "= /robots.txt" = { + priority = 100; + extraConfig = '' + allow all; + log_not_found off; + access_log off; + ''; + }; + "/" = { + priority = 200; + extraConfig = "rewrite ^ /index.php$uri;"; + }; + "~ ^/store-apps" = { + priority = 201; + extraConfig = "root ${cfg.home};"; + }; + "= /.well-known/carddav" = { + priority = 210; + extraConfig = "return 301 $scheme://$host/remote.php/dav;"; + }; + "= /.well-known/caldav" = { + priority = 210; + extraConfig = "return 301 $scheme://$host/remote.php/dav;"; + }; + "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/" = { + priority = 300; + extraConfig = "deny all;"; + }; + "~ ^/(?:\\.|autotest|occ|issue|indie|db_|console)" = { + priority = 300; + extraConfig = "deny all;"; + }; + "~ ^/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+)\\.php(?:$|/)" = { + priority = 500; + extraConfig = '' + include ${pkgs.nginxMainline}/conf/fastcgi.conf; + fastcgi_split_path_info ^(.+\.php)(/.*)$; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param HTTPS ${if cfg.https then "on" else "off"}; + fastcgi_param modHeadersAvailable true; + fastcgi_param front_controller_active true; + fastcgi_pass unix:/run/phpfpm/nextcloud; + fastcgi_intercept_errors on; + fastcgi_request_buffering off; + fastcgi_read_timeout 120s; + ''; + }; + "~ ^/(?:updater|ocs-provider)(?:$|/)".extraConfig = '' + try_files $uri/ =404; + index index.php; + ''; + "~ \\.(?:css|js|woff|svg|gif)$".extraConfig = '' + try_files $uri /index.php$uri$is_args$args; + add_header Cache-Control "public, max-age=15778463"; + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Robots-Tag none; + add_header X-Download-Options noopen; + add_header X-Permitted-Cross-Domain-Policies none; + access_log off; + ''; + "~ \\.(?:png|html|ttf|ico|jpg|jpeg)$".extraConfig = '' + try_files $uri /index.php$uri$is_args$args; + access_log off; + ''; + }; + extraConfig = '' + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Robots-Tag none; + add_header X-Download-Options noopen; + add_header X-Permitted-Cross-Domain-Policies none; + error_page 403 /core/templates/403.php; + error_page 404 /core/templates/404.php; + client_max_body_size ${cfg.maxUploadSize}; + fastcgi_buffers 64 4K; + gzip on; + gzip_vary on; + gzip_comp_level 4; + gzip_min_length 256; + gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; + gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy; + + ${optionalString cfg.webfinger '' + rewrite ^/.well-known/host-meta /public.php?service=host-meta last; + rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last; + ''} + ''; + }; + }; + }; + }) + ]); +} diff --git a/nixos/modules/services/web-apps/tt-rss.nix b/nixos/modules/services/web-apps/tt-rss.nix index 2b171aa1b2b2..90b35d19ea11 100644 --- a/nixos/modules/services/web-apps/tt-rss.nix +++ b/nixos/modules/services/web-apps/tt-rss.nix @@ -624,7 +624,11 @@ let }; users = optionalAttrs (cfg.user == "tt_rss") { - users.tt_rss.group = "tt_rss"; + users.tt_rss = { + description = "tt-rss service user"; + isSystemUser = true; + group = "tt_rss"; + }; groups.tt_rss = {}; }; }; diff --git a/nixos/modules/services/web-servers/hydron.nix b/nixos/modules/services/web-servers/hydron.nix index c49efaede160..ed63230bc784 100644 --- a/nixos/modules/services/web-servers/hydron.nix +++ b/nixos/modules/services/web-servers/hydron.nix @@ -16,10 +16,10 @@ in with lib; { interval = mkOption { type = types.str; - default = "hourly"; + default = "weekly"; example = "06:00"; description = '' - How often we run hydron import and possibly fetch tags. Runs by default every hour. + How often we run hydron import and possibly fetch tags. Runs by default every week. The format is described in <citerefentry><refentrytitle>systemd.time</refentrytitle> @@ -137,9 +137,13 @@ in with lib; { systemd.timers.hydron-fetch = { description = "Automatically import paths into hydron and possibly fetch tags"; - after = [ "network.target" ]; + after = [ "network.target" "hydron.service" ]; wantedBy = [ "timers.target" ]; - timerConfig.OnCalendar = cfg.interval; + + timerConfig = { + Persistent = true; + OnCalendar = cfg.interval; + }; }; users = { diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix index 355976c4b7cb..508398f03ace 100644 --- a/nixos/modules/services/web-servers/nginx/default.nix +++ b/nixos/modules/services/web-servers/nginx/default.nix @@ -16,9 +16,11 @@ let } // (optionalAttrs vhostConfig.enableACME { sslCertificate = "${acmeDirectory}/${serverName}/fullchain.pem"; sslCertificateKey = "${acmeDirectory}/${serverName}/key.pem"; + sslTrustedCertificate = "${acmeDirectory}/${serverName}/full.pem"; }) // (optionalAttrs (vhostConfig.useACMEHost != null) { sslCertificate = "${acmeDirectory}/${vhostConfig.useACMEHost}/fullchain.pem"; sslCertificateKey = "${acmeDirectory}/${vhostConfig.useACMEHost}/key.pem"; + sslTrustedCertificate = "${acmeDirectory}/${vhostConfig.useACMEHost}/full.pem"; }) ) cfg.virtualHosts; enableIPv6 = config.networking.enableIPv6; @@ -92,8 +94,18 @@ let gzip on; gzip_disable "msie6"; gzip_proxied any; - gzip_comp_level 9; - gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + gzip_comp_level 5; + gzip_types + application/atom+xml + application/javascript + application/json + application/xml + application/xml+rss + image/svg+xml + text/css + text/javascript + text/plain + text/xml; gzip_vary on; ''} @@ -218,6 +230,9 @@ let ssl_certificate ${vhost.sslCertificate}; ssl_certificate_key ${vhost.sslCertificateKey}; ''} + ${optionalString (hasSSL && vhost.sslTrustedCertificate != null) '' + ssl_trusted_certificate ${vhost.sslTrustedCertificate}; + ''} ${optionalString (vhost.basicAuthFile != null || vhost.basicAuth != {}) '' auth_basic secured; @@ -230,8 +245,8 @@ let } '' ) virtualHosts); - mkLocations = locations: concatStringsSep "\n" (mapAttrsToList (location: config: '' - location ${location} { + mkLocations = locations: concatStringsSep "\n" (map (config: '' + location ${config.location} { ${optionalString (config.proxyPass != null && !cfg.proxyResolveWhileRunning) "proxy_pass ${config.proxyPass};" } @@ -251,7 +266,18 @@ let ${config.extraConfig} ${optionalString (config.proxyPass != null && cfg.recommendedProxySettings) "include ${recommendedProxyConfig};"} } - '') locations); + '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations))); + mkBasicAuth = vhostName: authDef: let + htpasswdFile = pkgs.writeText "${vhostName}.htpasswd" ( + concatStringsSep "\n" (mapAttrsToList (user: password: '' + ${user}:{PLAIN}${password} + '') authDef) + ); + in '' + auth_basic secured; + auth_basic_user_file ${htpasswdFile}; + ''; + mkHtpasswd = vhostName: authDef: pkgs.writeText "${vhostName}.htpasswd" ( concatStringsSep "\n" (mapAttrsToList (user: password: '' ${user}:{PLAIN}${password} diff --git a/nixos/modules/services/web-servers/nginx/location-options.nix b/nixos/modules/services/web-servers/nginx/location-options.nix index 4c772734a749..9b44433d3845 100644 --- a/nixos/modules/services/web-servers/nginx/location-options.nix +++ b/nixos/modules/services/web-servers/nginx/location-options.nix @@ -71,6 +71,16 @@ with lib; These lines go to the end of the location verbatim. ''; }; + + priority = mkOption { + type = types.int; + default = 1000; + description = '' + Order of this location block in relation to the others in the vhost. + The semantics are the same as with `lib.mkOrder`. Smaller values have + a greater priority. + ''; + }; }; } diff --git a/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixos/modules/services/web-servers/nginx/vhost-options.nix index 1075b00768fd..8e2b8819e71b 100644 --- a/nixos/modules/services/web-servers/nginx/vhost-options.nix +++ b/nixos/modules/services/web-servers/nginx/vhost-options.nix @@ -129,6 +129,13 @@ with lib; description = "Path to server SSL certificate key."; }; + sslTrustedCertificate = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/root.cert"; + description = "Path to root SSL certificate for stapling and client certificates."; + }; + http2 = mkOption { type = types.bool; default = true; diff --git a/nixos/modules/services/x11/desktop-managers/enlightenment.nix b/nixos/modules/services/x11/desktop-managers/enlightenment.nix index da3287aaea6e..04e380b61530 100644 --- a/nixos/modules/services/x11/desktop-managers/enlightenment.nix +++ b/nixos/modules/services/x11/desktop-managers/enlightenment.nix @@ -33,12 +33,17 @@ in pkgs.xorg.xauth # used by kdesu pkgs.gtk2 # To get GTK+'s themes. pkgs.tango-icon-theme - pkgs.shared-mime-info + pkgs.gnome2.gnomeicontheme pkgs.xorg.xcursorthemes ]; - environment.pathsToLink = [ "/etc/enlightenment" "/etc/xdg" "/share/enlightenment" "/share/elementary" "/share/applications" "/share/locale" "/share/icons" "/share/themes" "/share/mime" "/share/desktop-directories" ]; + environment.pathsToLink = [ + "/etc/enlightenment" + "/share/enlightenment" + "/share/elementary" + "/share/locale" + ]; services.xserver.desktopManager.session = [ { name = "Enlightenment"; @@ -61,7 +66,7 @@ in ''; }]; - security.wrappers = (import (builtins.toPath "${e.enlightenment}/e-wrappers.nix")).security.wrappers; + security.wrappers = (import "${e.enlightenment}/e-wrappers.nix").security.wrappers; environment.etc = singleton { source = xcfg.xkbDir; diff --git a/nixos/modules/services/x11/desktop-managers/gnome3.nix b/nixos/modules/services/x11/desktop-managers/gnome3.nix index ee9b11928ae1..eb86f7b53bb6 100644 --- a/nixos/modules/services/x11/desktop-managers/gnome3.nix +++ b/nixos/modules/services/x11/desktop-managers/gnome3.nix @@ -33,10 +33,10 @@ let chmod -R a+w $out/share/gsettings-schemas/nixos-gsettings-overrides cat - > $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas/nixos-defaults.gschema.override <<- EOF [org.gnome.desktop.background] - picture-uri='${pkgs.nixos-artwork.wallpapers.gnome-dark}/share/artwork/gnome/Gnome_Dark.png' + picture-uri='${pkgs.nixos-artwork.wallpapers.simple-dark-gray}/share/artwork/gnome/nix-wallpaper-simple-dark-gray.png' [org.gnome.desktop.screensaver] - picture-uri='${pkgs.nixos-artwork.wallpapers.gnome-dark}/share/artwork/gnome/Gnome_Dark.png' + picture-uri='${pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom}/share/artwork/gnome/nix-wallpaper-simple-dark-gray_bottom.png' ${cfg.extraGSettingsOverrides} EOF @@ -97,6 +97,8 @@ in { services.udisks2.enable = true; services.accounts-daemon.enable = true; services.geoclue2.enable = mkDefault true; + # GNOME should have its own geoclue agent + services.geoclue2.enableDemoAgent = false; services.dleyna-renderer.enable = mkDefault true; services.dleyna-server.enable = mkDefault true; services.gnome3.at-spi2-core.enable = true; @@ -108,6 +110,7 @@ in { services.gnome3.gnome-terminal-server.enable = mkDefault true; services.gnome3.gnome-user-share.enable = mkDefault true; services.gnome3.gvfs.enable = true; + services.gnome3.rygel.enable = mkDefault true; services.gnome3.seahorse.enable = mkDefault true; services.gnome3.sushi.enable = mkDefault true; services.gnome3.tracker.enable = mkDefault true; @@ -130,6 +133,7 @@ in { fonts.fonts = [ pkgs.dejavu_fonts pkgs.cantarell-fonts ]; + services.xserver.displayManager.gdm.enable = mkDefault true; services.xserver.displayManager.extraSessionFilePackages = [ pkgs.gnome3.gnome-session ]; services.xserver.displayManager.sessionCommands = '' @@ -159,6 +163,8 @@ in { # TODO: Create nautilus-with-extensions package environment.variables.NAUTILUS_EXTENSION_DIR = "${config.system.path}/lib/nautilus/extensions-3.0"; + services.xserver.updateDbusEnvironment = true; + environment.variables.GIO_EXTRA_MODULES = [ "${lib.getLib pkgs.gnome3.dconf}/lib/gio/modules" "${pkgs.gnome3.glib-networking.out}/lib/gio/modules" "${pkgs.gnome3.gvfs}/lib/gio/modules" ]; diff --git a/nixos/modules/services/x11/desktop-managers/lumina.nix b/nixos/modules/services/x11/desktop-managers/lumina.nix index 5fe84cfb82ec..43fed2572b51 100644 --- a/nixos/modules/services/x11/desktop-managers/lumina.nix +++ b/nixos/modules/services/x11/desktop-managers/lumina.nix @@ -41,9 +41,8 @@ in # Link some extra directories in /run/current-system/software/share environment.pathsToLink = [ - "/share/desktop-directories" - "/share/icons" "/share/lumina" + # FIXME: modules should link subdirs of `/share` rather than relying on this "/share" ]; diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix index 83d1957a646a..11c1aa4315a8 100644 --- a/nixos/modules/services/x11/desktop-managers/plasma5.nix +++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix @@ -64,7 +64,7 @@ in }; security.wrappers = { - kcheckpass.source = "${lib.getBin plasma5.plasma-workspace}/lib/libexec/kcheckpass"; + kcheckpass.source = "${lib.getBin plasma5.kscreenlocker}/lib/libexec/kcheckpass"; "start_kdeinit".source = "${lib.getBin pkgs.kinit}/lib/libexec/kf5/start_kdeinit"; kwin_wayland = { source = "${lib.getBin plasma5.kwin}/bin/kwin_wayland"; @@ -81,6 +81,7 @@ in kconfig kconfigwidgets kcoreaddons + kdoctools kdbusaddons kdeclarative kded @@ -174,7 +175,10 @@ in ++ lib.optional config.services.colord.enable colord-kde ++ lib.optionals config.services.samba.enable [ kdenetwork-filesharing pkgs.samba ]; - environment.pathsToLink = [ "/share" ]; + environment.pathsToLink = [ + # FIXME: modules should link subdirs of `/share` rather than relying on this + "/share" + ]; environment.etc = singleton { source = xcfg.xkbDir; @@ -221,11 +225,8 @@ in security.pam.services.sddm.enableKwallet = true; security.pam.services.slim.enableKwallet = true; - # Update the start menu for each user that has `isNormalUser` set. - system.activationScripts.plasmaSetup = stringAfter [ "users" "groups" ] - (concatStringsSep "\n" - (mapAttrsToList (name: value: "${pkgs.su}/bin/su ${name} -c ${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5") - (filterAttrs (n: v: v.isNormalUser) config.users.users))); + # Update the start menu for each user that is currently logged in + system.userActivationScripts.plasmaSetup = "${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5"; }) ]; diff --git a/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixos/modules/services/x11/desktop-managers/xfce.nix index ae155470419d..75b9a76e1924 100644 --- a/nixos/modules/services/x11/desktop-managers/xfce.nix +++ b/nixos/modules/services/x11/desktop-managers/xfce.nix @@ -59,9 +59,6 @@ in tango-icon-theme xfce4-icon-theme - desktop-file-utils - shared-mime-info - # Needed by Xfce's xinitrc script # TODO: replace with command -v which @@ -100,8 +97,6 @@ in environment.pathsToLink = [ "/share/xfce4" "/share/themes" - "/share/mime" - "/share/desktop-directories" "/share/gtksourceview-2.0" ]; diff --git a/nixos/modules/services/x11/display-managers/auto.nix b/nixos/modules/services/x11/display-managers/auto.nix index d2aae64bf225..1068a344e0cf 100644 --- a/nixos/modules/services/x11/display-managers/auto.nix +++ b/nixos/modules/services/x11/display-managers/auto.nix @@ -41,12 +41,28 @@ in config = mkIf cfg.enable { - services.xserver.displayManager.slim = { + services.xserver.displayManager.lightdm = { enable = true; - autoLogin = true; - defaultUser = cfg.user; + autoLogin = { + enable = true; + user = cfg.user; + }; }; + # lightdm by default doesn't allow auto login for root, which is + # required by some nixos tests. Override it here. + security.pam.services.lightdm-autologin.text = lib.mkForce '' + auth requisite pam_nologin.so + auth required pam_succeed_if.so quiet + auth required pam_permit.so + + account include lightdm + + password include lightdm + + session include lightdm + ''; + }; } diff --git a/nixos/modules/services/x11/display-managers/default.nix b/nixos/modules/services/x11/display-managers/default.nix index 78095e7ce0b0..26b79730dd38 100644 --- a/nixos/modules/services/x11/display-managers/default.nix +++ b/nixos/modules/services/x11/display-managers/default.nix @@ -27,6 +27,15 @@ let Xft.hintstyle: hintslight ''; + mkCases = session: + concatStrings ( + mapAttrsToList (name: starts: '' + (${name}) + ${concatMapStringsSep "\n " (n: n.start) starts} + ;; + '') (lib.groupBy (n: n.name) session) + ); + # file provided by services.xserver.displayManager.session.wrapper xsessionWrapper = pkgs.writeScript "xsession-wrapper" '' @@ -56,10 +65,6 @@ let # Start PulseAudio if enabled. ${optionalString (config.hardware.pulseaudio.enable) '' - ${optionalString (!config.hardware.pulseaudio.systemWide) - "${config.hardware.pulseaudio.package.out}/bin/pulseaudio --start" - } - # Publish access credentials in the root window. if ${config.hardware.pulseaudio.package.out}/bin/pulseaudio --dump-modules | grep module-x11-publish &> /dev/null; then ${config.hardware.pulseaudio.package.out}/bin/pactl load-module module-x11-publish "display=$DISPLAY" @@ -143,21 +148,13 @@ let # Start the window manager. case "$windowManager" in - ${concatMapStrings (s: '' - (${s.name}) - ${s.start} - ;; - '') wm} + ${mkCases wm} (*) echo "$0: Window manager '$windowManager' not found.";; esac # Start the desktop manager. case "$desktopManager" in - ${concatMapStrings (s: '' - (${s.name}) - ${s.start} - ;; - '') dm} + ${mkCases dm} (*) echo "$0: Desktop manager '$desktopManager' not found.";; esac @@ -225,6 +222,17 @@ in description = "List of arguments for the X server."; }; + setupCommands = mkOption { + type = types.lines; + default = ""; + description = '' + Shell commands executed just after the X server has started. + + This option is only effective for display managers for which this feature + is supported; currently these are LightDM, GDM and SDDM. + ''; + }; + sessionCommands = mkOption { type = types.lines; default = ""; @@ -269,7 +277,7 @@ in session. Each session script can set the <varname>waitPID</varname> shell variable to make this script wait until the end of the user session. Each script is used - to define either a windows manager or a desktop manager. These + to define either a window manager or a desktop manager. These can be differentiated by setting the attribute <varname>manage</varname> either to <literal>"window"</literal> or <literal>"desktop"</literal>. diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix index 78dc39f7f62f..6cc30b218f4a 100644 --- a/nixos/modules/services/x11/display-managers/gdm.nix +++ b/nixos/modules/services/x11/display-managers/gdm.nix @@ -7,6 +7,13 @@ let cfg = config.services.xserver.displayManager; gdm = pkgs.gnome3.gdm; + xSessionWrapper = if (cfg.setupCommands == "") then null else + pkgs.writeScript "gdm-x-session-wrapper" '' + #!${pkgs.bash}/bin/bash + ${cfg.setupCommands} + exec "$@" + ''; + in { @@ -87,7 +94,7 @@ in } ]; - services.xserver.displayManager.slim.enable = false; + services.xserver.displayManager.lightdm.enable = false; users.users.gdm = { name = "gdm"; @@ -112,6 +119,11 @@ in GDM_SESSIONS_DIR = "${cfg.session.desktops}/share/xsessions"; # Find the mouse XCURSOR_PATH = "~/.icons:${pkgs.gnome3.adwaita-icon-theme}/share/icons"; + } // optionalAttrs (xSessionWrapper != null) { + # Make GDM use this wrapper before running the session, which runs the + # configured setupCommands. This relies on a patched GDM which supports + # this environment variable. + GDM_X_SESSION_WRAPPER = "${xSessionWrapper}"; }; execCmd = "exec ${gdm}/bin/gdm"; }; @@ -142,7 +154,10 @@ in systemd.user.services.dbus.wantedBy = [ "default.target" ]; - programs.dconf.profiles.gdm = "${gdm}/share/dconf/profile/gdm"; + programs.dconf.profiles.gdm = pkgs.writeText "dconf-gdm-profile" '' + system-db:local + ${gdm}/share/dconf/profile/gdm + ''; # Use AutomaticLogin if delay is zero, because it's immediate. # Otherwise with TimedLogin with zero seconds the prompt is still diff --git a/nixos/modules/services/x11/display-managers/lightdm.nix b/nixos/modules/services/x11/display-managers/lightdm.nix index 57a92e697015..a685dbfff2a0 100644 --- a/nixos/modules/services/x11/display-managers/lightdm.nix +++ b/nixos/modules/services/x11/display-managers/lightdm.nix @@ -15,7 +15,7 @@ let inherit (pkgs) lightdm writeScript writeText; - # lightdm runs with clearenv(), but we need a few things in the enviornment for X to startup + # lightdm runs with clearenv(), but we need a few things in the environment for X to startup xserverWrapper = writeScript "xserver-wrapper" '' #! ${pkgs.bash}/bin/bash @@ -46,6 +46,7 @@ let greeters-directory = ${cfg.greeter.package} ''} sessions-directory = ${dmcfg.session.desktops}/share/xsessions + ${cfg.extraConfig} [Seat:*] xserver-command = ${xserverWrapper} @@ -61,6 +62,12 @@ let ${optionalString hasDefaultUserSession '' user-session=${defaultSessionName} ''} + ${optionalString (dmcfg.setupCommands != "") '' + display-setup-script=${pkgs.writeScript "lightdm-display-setup" '' + #!${pkgs.bash}/bin/bash + ${dmcfg.setupCommands} + ''} + ''} ${cfg.extraSeatDefaults} ''; @@ -114,9 +121,18 @@ in }; }; + extraConfig = mkOption { + type = types.lines; + default = ""; + example = '' + user-authority-in-system-dir = true + ''; + description = "Extra lines to append to LightDM section."; + }; + background = mkOption { type = types.str; - default = "${pkgs.nixos-artwork.wallpapers.gnome-dark}/share/artwork/gnome/Gnome_Dark.png"; + default = "${pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom}/share/artwork/gnome/nix-wallpaper-simple-dark-gray_bottom.png"; description = '' The background image or color to use. ''; @@ -192,15 +208,13 @@ in } ]; - services.xserver.displayManager.slim.enable = false; - services.xserver.displayManager.job = { logToFile = true; # lightdm relaunches itself via just `lightdm`, so needs to be on the PATH execCmd = '' export PATH=${lightdm}/sbin:$PATH - exec ${lightdm}/sbin/lightdm --log-dir=/var/log --run-dir=/run + exec ${lightdm}/sbin/lightdm ''; }; @@ -210,9 +224,12 @@ in services.dbus.enable = true; services.dbus.packages = [ lightdm ]; - # lightdm uses the accounts daemon to rember language/window-manager per user + # lightdm uses the accounts daemon to remember language/window-manager per user services.accounts-daemon.enable = true; + # Enable the accounts daemon to find lightdm's dbus interface + environment.systemPackages = [ lightdm ]; + security.pam.services.lightdm = { allowNullPassword = true; startSession = true; @@ -246,12 +263,19 @@ in ''; users.users.lightdm = { - createHome = true; - home = "/var/lib/lightdm-data"; + home = "/var/lib/lightdm"; group = "lightdm"; uid = config.ids.uids.lightdm; }; + systemd.tmpfiles.rules = [ + "d /run/lightdm 0711 lightdm lightdm 0" + "d /var/cache/lightdm 0711 root lightdm -" + "d /var/lib/lightdm 1770 lightdm lightdm -" + "d /var/lib/lightdm-data 1775 lightdm lightdm -" + "d /var/log/lightdm 0711 root lightdm -" + ]; + users.groups.lightdm.gid = config.ids.gids.lightdm; services.xserver.tty = null; # We might start multiple X servers so let the tty increment themselves.. services.xserver.display = null; # We specify our own display (and logfile) in xserver-wrapper up there diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix index 2b03ed81b5ed..522a0dc92d6f 100644 --- a/nixos/modules/services/x11/display-managers/sddm.nix +++ b/nixos/modules/services/x11/display-managers/sddm.nix @@ -20,6 +20,7 @@ let Xsetup = pkgs.writeScript "Xsetup" '' #!/bin/sh ${cfg.setupScript} + ${dmcfg.setupCommands} ''; Xstop = pkgs.writeScript "Xstop" '' @@ -137,7 +138,8 @@ in xrandr --auto ''; description = '' - A script to execute when starting the display server. + A script to execute when starting the display server. DEPRECATED, please + use <option>services.xserver.displayManager.setupCommands</option>. ''; }; @@ -206,8 +208,6 @@ in } ]; - services.xserver.displayManager.slim.enable = false; - services.xserver.displayManager.job = { logToFile = true; @@ -265,7 +265,9 @@ in }; environment.etc."sddm.conf".source = cfgFile; - environment.pathsToLink = [ "/share/sddm/themes" ]; + environment.pathsToLink = [ + "/share/sddm" + ]; users.groups.sddm.gid = config.ids.gids.sddm; diff --git a/nixos/modules/services/x11/display-managers/slim.nix b/nixos/modules/services/x11/display-managers/slim.nix index 51ce5f4e2437..4e411c8ceb0b 100644 --- a/nixos/modules/services/x11/display-managers/slim.nix +++ b/nixos/modules/services/x11/display-managers/slim.nix @@ -49,7 +49,7 @@ in enable = mkOption { type = types.bool; - default = config.services.xserver.enable; + default = false; description = '' Whether to enable SLiM as the display manager. ''; diff --git a/nixos/modules/services/x11/hardware/libinput.nix b/nixos/modules/services/x11/hardware/libinput.nix index 072004d5dd91..58fe702d4969 100644 --- a/nixos/modules/services/x11/hardware/libinput.nix +++ b/nixos/modules/services/x11/hardware/libinput.nix @@ -205,7 +205,7 @@ in { }) ]; - services.udev.packages = [ pkgs.libinput ]; + services.udev.packages = [ pkgs.libinput.out ]; services.xserver.config = '' diff --git a/nixos/modules/services/x11/redshift.nix b/nixos/modules/services/x11/redshift.nix index 30d853841ea4..b7dd7debcb63 100644 --- a/nixos/modules/services/x11/redshift.nix +++ b/nixos/modules/services/x11/redshift.nix @@ -116,6 +116,9 @@ in { } ]; + # needed so that .desktop files are installed, which geoclue cares about + environment.systemPackages = [ cfg.package ]; + services.geoclue2.enable = mkIf (cfg.provider == "geoclue2") true; systemd.user.services.redshift = diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix index b45e510f6b83..297e36311656 100644 --- a/nixos/modules/services/x11/xserver.nix +++ b/nixos/modules/services/x11/xserver.nix @@ -374,6 +374,12 @@ in description = "Contents of the first Monitor section of the X server configuration file."; }; + extraConfig = mkOption { + type = types.lines; + default = ""; + description = "Additional contents (sections) included in the X server configuration file"; + }; + xrandrHeads = mkOption { default = []; example = [ @@ -535,6 +541,15 @@ in config = mkIf cfg.enable { + services.xserver.displayManager.lightdm.enable = + let dmconf = cfg.displayManager; + default = !( dmconf.auto.enable + || dmconf.gdm.enable + || dmconf.sddm.enable + || dmconf.slim.enable + || dmconf.xpra.enable ); + in mkIf (default) true; + hardware.opengl.enable = mkDefault true; services.xserver.videoDrivers = mkIf (cfg.videoDriver != null) [ cfg.videoDriver ]; @@ -616,8 +631,12 @@ in ] ++ optional (elem "virtualbox" cfg.videoDrivers) xorg.xrefresh; - environment.pathsToLink = - [ "/etc/xdg" "/share/xdg" "/share/applications" "/share/icons" "/share/pixmaps" ]; + xdg = { + autostart.enable = true; + menus.enable = true; + mime.enable = true; + icons.enable = true; + }; # The default max inotify watches is 8192. # Nowadays most apps require a good number of inotify watches, @@ -741,6 +760,7 @@ in Driver "${driver.driverName or driver.name}" ${if cfg.useGlamor then ''Option "AccelMethod" "glamor"'' else ""} ${cfg.deviceSection} + ${driver.deviceSection or ""} ${xrandrDeviceSection} EndSection @@ -752,6 +772,7 @@ in ''} ${cfg.screenSection} + ${driver.screenSection or ""} ${optionalString (cfg.defaultDepth != 0) '' DefaultDepth ${toString cfg.defaultDepth} @@ -781,6 +802,8 @@ in '')} ${xrandrMonitorSections} + + ${cfg.extraConfig} ''; fonts.enableDefaultFonts = mkDefault true; |