diff options
Diffstat (limited to 'nixos/modules/services')
67 files changed, 2529 insertions, 876 deletions
diff --git a/nixos/modules/services/audio/spotifyd.nix b/nixos/modules/services/audio/spotifyd.nix new file mode 100644 index 000000000000..e3556b2559c2 --- /dev/null +++ b/nixos/modules/services/audio/spotifyd.nix @@ -0,0 +1,42 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.spotifyd; + spotifydConf = pkgs.writeText "spotifyd.conf" cfg.config; +in +{ + options = { + services.spotifyd = { + enable = mkEnableOption "spotifyd, a Spotify playing daemon"; + + config = mkOption { + default = ""; + type = types.lines; + description = '' + Configuration for Spotifyd. For syntax and directives, see + https://github.com/Spotifyd/spotifyd#Configuration. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.spotifyd = { + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" "sound.target" ]; + description = "spotifyd, a Spotify playing daemon"; + serviceConfig = { + ExecStart = "${pkgs.spotifyd}/bin/spotifyd --no-daemon --cache_path /var/cache/spotifyd --config ${spotifydConf}"; + Restart = "always"; + RestartSec = 12; + DynamicUser = true; + CacheDirectory = "spotifyd"; + SupplementaryGroups = ["audio"]; + }; + }; + }; + + meta.maintainers = [ maintainers.anderslundstedt ]; +} diff --git a/nixos/modules/services/backup/tsm.nix b/nixos/modules/services/backup/tsm.nix new file mode 100644 index 000000000000..3b2bb37491b5 --- /dev/null +++ b/nixos/modules/services/backup/tsm.nix @@ -0,0 +1,106 @@ +{ config, lib, ... }: + +let + + inherit (lib.attrsets) hasAttr; + inherit (lib.modules) mkDefault mkIf; + inherit (lib.options) mkEnableOption mkOption; + inherit (lib.types) nullOr strMatching; + + options.services.tsmBackup = { + enable = mkEnableOption '' + automatic backups with the + IBM Spectrum Protect (Tivoli Storage Manager, TSM) client. + This also enables + <option>programs.tsmClient.enable</option> + ''; + command = mkOption { + type = strMatching ".+"; + default = "backup"; + example = "incr"; + description = '' + The actual command passed to the + <literal>dsmc</literal> executable to start the backup. + ''; + }; + servername = mkOption { + type = strMatching ".+"; + example = "mainTsmServer"; + description = '' + Create a systemd system service + <literal>tsm-backup.service</literal> that starts + a backup based on the given servername's stanza. + Note that this server's + <option>passwdDir</option> will default to + <filename>/var/lib/tsm-backup/password</filename> + (but may be overridden); + also, the service will use + <filename>/var/lib/tsm-backup</filename> as + <literal>HOME</literal> when calling + <literal>dsmc</literal>. + ''; + }; + autoTime = mkOption { + type = nullOr (strMatching ".+"); + default = null; + example = "12:00"; + description = '' + The backup service will be invoked + automatically at the given date/time, + which must be in the format described in + <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>5</manvolnum></citerefentry>. + The default <literal>null</literal> + disables automatic backups. + ''; + }; + }; + + cfg = config.services.tsmBackup; + cfgPrg = config.programs.tsmClient; + + assertions = [ + { + assertion = hasAttr cfg.servername cfgPrg.servers; + message = "TSM service servername not found in list of servers"; + } + { + assertion = cfgPrg.servers.${cfg.servername}.genPasswd; + message = "TSM service requires automatic password generation"; + } + ]; + +in + +{ + + inherit options; + + config = mkIf cfg.enable { + inherit assertions; + programs.tsmClient.enable = true; + programs.tsmClient.servers."${cfg.servername}".passwdDir = + mkDefault "/var/lib/tsm-backup/password"; + systemd.services.tsm-backup = { + description = "IBM Spectrum Protect (Tivoli Storage Manager) Backup"; + # DSM_LOG needs a trailing slash to have it treated as a directory. + # `/var/log` would be littered with TSM log files otherwise. + environment.DSM_LOG = "/var/log/tsm-backup/"; + # TSM needs a HOME dir to store certificates. + environment.HOME = "/var/lib/tsm-backup"; + # for exit status description see + # https://www.ibm.com/support/knowledgecenter/en/SSEQVQ_8.1.8/client/c_sched_rtncode.html + serviceConfig.SuccessExitStatus = "4 8"; + # The `-se` option must come after the command. + # The `-optfile` option suppresses a `dsm.opt`-not-found warning. + serviceConfig.ExecStart = + "${cfgPrg.wrappedPackage}/bin/dsmc ${cfg.command} -se='${cfg.servername}' -optfile=/dev/null"; + serviceConfig.LogsDirectory = "tsm-backup"; + serviceConfig.StateDirectory = "tsm-backup"; + serviceConfig.StateDirectoryMode = "0750"; + startAt = mkIf (cfg.autoTime!=null) cfg.autoTime; + }; + }; + + meta.maintainers = [ lib.maintainers.yarny ]; + +} diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index c2f458c03794..d8e2c715afb9 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -237,8 +237,8 @@ in config = mkIf cfg.enable { assertions = [ { - assertion = cfg.rootpwFile != null || cfg.rootpw != null; - message = "Either services.openldap.rootpw or services.openldap.rootpwFile must be set"; + assertion = cfg.configDir != null || cfg.rootpwFile != null || cfg.rootpw != null; + message = "services.openldap: Unless configDir is set, either rootpw or rootpwFile must be set"; } ]; diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix index 5661edbee2db..37d44e30fbeb 100644 --- a/nixos/modules/services/databases/postgresql.nix +++ b/nixos/modules/services/databases/postgresql.nix @@ -6,23 +6,10 @@ let cfg = config.services.postgresql; - # see description of extraPlugins - postgresqlAndPlugins = pg: - if cfg.extraPlugins == [] then pg - else pkgs.buildEnv { - name = "postgresql-and-plugins-${(builtins.parseDrvName pg.name).version}"; - paths = [ pg pg.lib ] ++ cfg.extraPlugins; - buildInputs = [ pkgs.makeWrapper ]; - postBuild = - '' - mkdir -p $out/bin - rm $out/bin/{pg_config,postgres,pg_ctl} - cp --target-directory=$out/bin ${pg}/bin/{postgres,pg_config,pg_ctl} - wrapProgram $out/bin/postgres --set NIX_PGLIBDIR $out/lib - ''; - }; - - postgresql = postgresqlAndPlugins cfg.package; + postgresql = + if cfg.extraPlugins == [] + then cfg.package + else cfg.package.withPackages (_: cfg.extraPlugins); # The main PostgreSQL configuration file. configFile = pkgs.writeText "postgresql.conf" @@ -55,7 +42,7 @@ in package = mkOption { type = types.package; - example = literalExample "pkgs.postgresql_9_6"; + example = literalExample "pkgs.postgresql_11"; description = '' PostgreSQL package to use. ''; @@ -71,7 +58,7 @@ in dataDir = mkOption { type = types.path; - example = "/var/lib/postgresql/9.6"; + example = "/var/lib/postgresql/11"; description = '' Data directory for PostgreSQL. ''; @@ -192,17 +179,11 @@ in extraPlugins = mkOption { type = types.listOf types.path; default = []; - example = literalExample "[ (pkgs.postgis.override { postgresql = pkgs.postgresql_9_4; }) ]"; + example = literalExample "with pkgs.postgresql_11.pkgs; [ postgis pg_repack ]"; description = '' - When this list contains elements a new store path is created. - PostgreSQL and the elements are symlinked into it. Then pg_config, - postgres and pg_ctl are copied to make them use the new - $out/lib directory as pkglibdir. This makes it possible to use postgis - without patching the .sql files which reference $libdir/postgis-1.5. + List of PostgreSQL plugins. PostgreSQL version for each plugin should + match version for <literal>services.postgresql.package</literal> value. ''; - # Note: the duplication of executables is about 4MB size. - # So a nicer solution was patching postgresql to allow setting the - # libdir explicitely. }; extraConfig = mkOption { diff --git a/nixos/modules/services/databases/postgresql.xml b/nixos/modules/services/databases/postgresql.xml index 00bb02dcc5bf..72d4a8249a32 100644 --- a/nixos/modules/services/databases/postgresql.xml +++ b/nixos/modules/services/databases/postgresql.xml @@ -27,10 +27,10 @@ <filename>configuration.nix</filename>: <programlisting> <xref linkend="opt-services.postgresql.enable"/> = true; -<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_9_4; +<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_11; </programlisting> Note that you are required to specify the desired version of PostgreSQL - (e.g. <literal>pkgs.postgresql_9_4</literal>). Since upgrading your + (e.g. <literal>pkgs.postgresql_11</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 @@ -52,7 +52,7 @@ Type "help" for help. <para> By default, PostgreSQL stores its databases in - <filename>/var/db/postgresql</filename>. You can override this using + <filename>/var/lib/postgresql/$psqlSchema</filename>. You can override this using <xref linkend="opt-services.postgresql.dataDir"/>, e.g. <programlisting> <xref linkend="opt-services.postgresql.dataDir"/> = "/data/postgresql"; @@ -74,4 +74,70 @@ Type "help" for help. <link linkend="opt-services.postgresql.enable">here</link>. </para> </section> + <section xml:id="module-services-postgres-plugins"> + <title>Plugins</title> + + <para> + Plugins collection for each PostgreSQL version can be accessed with + <literal>.pkgs</literal>. For example, for + <literal>pkgs.postgresql_11</literal> package, its plugin collection is + accessed by <literal>pkgs.postgresql_11.pkgs</literal>: +<screen> +<prompt>$ </prompt>nix repl '<nixpkgs>' + +Loading '<nixpkgs>'... +Added 10574 variables. + +<prompt>nix-repl> </prompt>postgresql_11.pkgs.<TAB><TAB> +postgresql_11.pkgs.cstore_fdw postgresql_11.pkgs.pg_repack +postgresql_11.pkgs.pg_auto_failover postgresql_11.pkgs.pg_safeupdate +postgresql_11.pkgs.pg_bigm postgresql_11.pkgs.pg_similarity +postgresql_11.pkgs.pg_cron postgresql_11.pkgs.pg_topn +postgresql_11.pkgs.pg_hll postgresql_11.pkgs.pgjwt +postgresql_11.pkgs.pg_partman postgresql_11.pkgs.pgroonga +... +</screen> + </para> + <para> + To add plugins via NixOS configuration, set <literal>services.postgresql.extraPlugins</literal>: +<programlisting> +<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_11; +<xref linkend="opt-services.postgresql.extraPlugins"/> = with pkgs.postgresql_11.pkgs; [ + pg_repack + postgis +]; +</programlisting> + </para> + <para> + You can build custom PostgreSQL-with-plugins (to be used outside of NixOS) using + function <literal>.withPackages</literal>. For example, creating a custom + PostgreSQL package in an overlay can look like: +<programlisting> +self: super: { + postgresql_custom = self.postgresql_11.withPackages (ps: [ + ps.pg_repack + ps.postgis + ]); +} +</programlisting> + </para> + <para> + Here's a recipe on how to override a particular plugin through an overlay: +<programlisting> +self: super: { + postgresql_11 = super.postgresql_11.override { this = self.postgresql_11; } // { + pkgs = super.postgresql_11.pkgs // { + pg_repack = super.postgresql_11.pkgs.pg_repack.overrideAttrs (_: { + name = "pg_repack-v20181024"; + src = self.fetchzip { + url = "https://github.com/reorg/pg_repack/archive/923fa2f3c709a506e111cc963034bf2fd127aa00.tar.gz"; + sha256 = "17k6hq9xaax87yz79j773qyigm4fwk8z4zh5cyp6z0sxnwfqxxw5"; + }; + }); + }; + }; +} +</programlisting> + </para> + </section> </chapter> diff --git a/nixos/modules/services/desktops/flatpak.nix b/nixos/modules/services/desktops/flatpak.nix index cfca1893bd82..1492d855aa03 100644 --- a/nixos/modules/services/desktops/flatpak.nix +++ b/nixos/modules/services/desktops/flatpak.nix @@ -15,38 +15,28 @@ in { options = { services.flatpak = { enable = mkEnableOption "flatpak"; - - extraPortals = mkOption { - type = types.listOf types.package; - default = []; - description = '' - List of additional portals to add to path. Portals allow interaction - with system, like choosing files or taking screenshots. At minimum, - a desktop portal implementation should be listed. GNOME already - adds <package>xdg-desktop-portal-gtk</package>; for KDE, there - is <package>xdg-desktop-portal-kde</package>. Other desktop - environments will probably want to do the same. - ''; - }; }; }; ###### implementation config = mkIf cfg.enable { + + assertions = [ + { assertion = (config.xdg.portal.enable == true); + message = "To use Flatpak you must enable XDG Desktop Portals with xdg.portal.enable."; + } + ]; + environment.systemPackages = [ pkgs.flatpak ]; - services.dbus.packages = [ pkgs.flatpak pkgs.xdg-desktop-portal ] ++ cfg.extraPortals; + services.dbus.packages = [ pkgs.flatpak ]; - systemd.packages = [ pkgs.flatpak pkgs.xdg-desktop-portal ] ++ cfg.extraPortals; + systemd.packages = [ pkgs.flatpak ]; environment.profiles = [ "$HOME/.local/share/flatpak/exports" "/var/lib/flatpak/exports" ]; - - environment.variables = { - XDG_DESKTOP_PORTAL_PATH = map (p: "${p}/share/xdg-desktop-portal/portals") cfg.extraPortals; - }; }; } diff --git a/nixos/modules/services/desktops/flatpak.xml b/nixos/modules/services/desktops/flatpak.xml index fb27bd1f62b2..8f080b250228 100644 --- a/nixos/modules/services/desktops/flatpak.xml +++ b/nixos/modules/services/desktops/flatpak.xml @@ -29,7 +29,7 @@ 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 ]; + <xref linkend="opt-xdg.portal.extraPortals"/> = [ pkgs.xdg-desktop-portal-gtk ]; </programlisting> </para> <para> diff --git a/nixos/modules/services/logging/graylog.nix b/nixos/modules/services/logging/graylog.nix index c8c4a9ff06db..a889a44d4b2b 100644 --- a/nixos/modules/services/logging/graylog.nix +++ b/nixos/modules/services/logging/graylog.nix @@ -150,6 +150,9 @@ in rm -rf /var/lib/graylog/plugins || true mkdir -p /var/lib/graylog/plugins -m 755 + mkdir -p "$(dirname ${cfg.nodeIdFile})" + chown -R ${cfg.user} "$(dirname ${cfg.nodeIdFile})" + for declarativeplugin in `ls ${glPlugins}/bin/`; do ln -sf ${glPlugins}/bin/$declarativeplugin /var/lib/graylog/plugins/$declarativeplugin done diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix index d43733484ffa..dab1b29aa4be 100644 --- a/nixos/modules/services/mail/postfix.nix +++ b/nixos/modules/services/mail/postfix.nix @@ -13,6 +13,7 @@ let || cfg.extraAliases != ""; haveTransport = cfg.transport != ""; haveVirtual = cfg.virtual != ""; + haveLocalRecipients = cfg.localRecipients != null; clientAccess = optional (cfg.dnsBlacklistOverrides != "") @@ -244,6 +245,7 @@ let aliasesFile = pkgs.writeText "postfix-aliases" aliases; virtualFile = pkgs.writeText "postfix-virtual" cfg.virtual; + localRecipientMapFile = pkgs.writeText "postfix-local-recipient-map" (concatMapStrings (x: x + " ACCEPT\n") cfg.localRecipients); checkClientAccessFile = pkgs.writeText "postfix-check-client-access" cfg.dnsBlacklistOverrides; mainCfFile = pkgs.writeText "postfix-main.cf" mainCf; masterCfFile = pkgs.writeText "postfix-master.cf" masterCfContent; @@ -506,6 +508,19 @@ in ''; }; + localRecipients = mkOption { + type = with types; nullOr (listOf string); + default = null; + description = '' + List of accepted local users. Specify a bare username, an + <literal>"@domain.tld"</literal> wild-card, or a complete + <literal>"user@domain.tld"</literal> address. If set, these names end + up in the local recipient map -- see the local(8) man-page -- and + effectively replace the system user database lookup that's otherwise + used by default. + ''; + }; + transport = mkOption { default = ""; description = " @@ -742,6 +757,7 @@ in // optionalAttrs haveAliases { alias_maps = [ "${cfg.aliasMapType}:/etc/postfix/aliases" ]; } // optionalAttrs haveTransport { transport_maps = [ "hash:/etc/postfix/transport" ]; } // optionalAttrs haveVirtual { virtual_alias_maps = [ "${cfg.virtualMapType}:/etc/postfix/virtual" ]; } + // optionalAttrs haveLocalRecipients { local_recipient_maps = [ "hash:/etc/postfix/local_recipients" ] ++ optional haveAliases "$alias_maps"; } // optionalAttrs (cfg.dnsBlacklists != []) { smtpd_client_restrictions = clientRestrictions; } // optionalAttrs cfg.useSrs { sender_canonical_maps = [ "tcp:127.0.0.1:10001" ]; @@ -869,6 +885,9 @@ in (mkIf haveVirtual { services.postfix.mapFiles."virtual" = virtualFile; }) + (mkIf haveLocalRecipients { + services.postfix.mapFiles."local_recipients" = localRecipientMapFile; + }) (mkIf cfg.enableHeaderChecks { services.postfix.mapFiles."header_checks" = headerChecksFile; }) diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix index 5a964e672ede..59c1c104b9b9 100644 --- a/nixos/modules/services/misc/gitea.nix +++ b/nixos/modules/services/misc/gitea.nix @@ -55,6 +55,11 @@ let [service] DISABLE_REGISTRATION = ${boolToString cfg.disableRegistration} + ${optionalString (cfg.mailerPasswordFile != null) '' + [mailer] + PASSWD = #mailerpass# + ''} + ${cfg.extraConfig} ''; in @@ -255,6 +260,13 @@ in description = "Upper level of template and static files path."; }; + mailerPasswordFile = mkOption { + type = types.nullOr types.str; + default = null; + example = "/var/lib/secrets/gitea/mailpw"; + description = "Path to a file containing the SMTP password."; + }; + disableRegistration = mkEnableOption "the registration lock" // { description = '' By default any user can create an account on this <literal>gitea</literal> instance. @@ -344,9 +356,15 @@ in KEY="$(head -n1 ${secretKey})" DBPASS="$(head -n1 ${cfg.database.passwordFile})" JWTSECRET="$(head -n1 ${jwtSecret})" + ${if (cfg.mailerPasswordFile == null) then '' + MAILERPASSWORD="#mailerpass#" + '' else '' + MAILERPASSWORD="$(head -n1 ${cfg.mailerPasswordFile} || :)" + ''} sed -e "s,#secretkey#,$KEY,g" \ -e "s,#dbpass#,$DBPASS,g" \ -e "s,#jwtsecet#,$JWTSECET,g" \ + -e "s,#mailerpass#,$MAILERPASSWORD,g" \ -i ${runConfig} chmod 640 ${runConfig} ${secretKey} ${jwtSecret} ''} diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix index 576ca7bfc055..2f3f76d79ff3 100644 --- a/nixos/modules/services/misc/gitlab.nix +++ b/nixos/modules/services/misc/gitlab.nix @@ -52,7 +52,7 @@ let gitlab_url = "http+unix://${pathUrlQuote gitlabSocket}"; http_settings.self_signed_cert = false; repos_path = "${cfg.statePath}/repositories"; - secret_file = "${cfg.statePath}/config/gitlab_shell_secret"; + secret_file = "${cfg.statePath}/gitlab_shell_secret"; log_file = "${cfg.statePath}/log/gitlab-shell.log"; custom_hooks_dir = "${cfg.statePath}/custom_hooks"; redis = { @@ -109,7 +109,7 @@ let gitlab_shell = { path = "${cfg.packages.gitlab-shell}"; hooks_path = "${cfg.statePath}/shell/hooks"; - secret_file = "${cfg.statePath}/config/gitlab_shell_secret"; + secret_file = "${cfg.statePath}/gitlab_shell_secret"; upload_pack = true; receive_pack = true; }; @@ -132,14 +132,9 @@ let HOME = "${cfg.statePath}/home"; UNICORN_PATH = "${cfg.statePath}/"; GITLAB_PATH = "${cfg.packages.gitlab}/share/gitlab/"; - GITLAB_STATE_PATH = cfg.statePath; - GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads"; SCHEMA = "${cfg.statePath}/db/schema.rb"; + GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads"; GITLAB_LOG_PATH = "${cfg.statePath}/log"; - GITLAB_SHELL_PATH = "${cfg.packages.gitlab-shell}"; - GITLAB_SHELL_CONFIG_PATH = "${cfg.statePath}/shell/config.yml"; - GITLAB_SHELL_SECRET_PATH = "${cfg.statePath}/config/gitlab_shell_secret"; - GITLAB_SHELL_HOOKS_PATH = "${cfg.statePath}/shell/hooks"; GITLAB_REDIS_CONFIG_FILE = pkgs.writeText "redis.yml" (builtins.toJSON redisConfig); prometheus_multiproc_dir = "/run/gitlab"; RAILS_ENV = "production"; @@ -502,23 +497,44 @@ in { systemd.tmpfiles.rules = [ "d /run/gitlab 0755 ${cfg.user} ${cfg.group} -" "d ${gitlabEnv.HOME} 0750 ${cfg.user} ${cfg.group} -" + "z ${gitlabEnv.HOME}/.ssh/authorized_keys 0600 ${cfg.user} ${cfg.group} -" "d ${cfg.backupPath} 0750 ${cfg.user} ${cfg.group} -" + "d ${cfg.statePath} 0750 ${cfg.user} ${cfg.group} -" "d ${cfg.statePath}/builds 0750 ${cfg.user} ${cfg.group} -" "d ${cfg.statePath}/config 0750 ${cfg.user} ${cfg.group} -" + "d ${cfg.statePath}/config/initializers 0750 ${cfg.user} ${cfg.group} -" "d ${cfg.statePath}/db 0750 ${cfg.user} ${cfg.group} -" "d ${cfg.statePath}/log 0750 ${cfg.user} ${cfg.group} -" "d ${cfg.statePath}/repositories 2770 ${cfg.user} ${cfg.group} -" "d ${cfg.statePath}/shell 0750 ${cfg.user} ${cfg.group} -" + "d ${cfg.statePath}/tmp 0750 ${cfg.user} ${cfg.group} -" "d ${cfg.statePath}/tmp/pids 0750 ${cfg.user} ${cfg.group} -" "d ${cfg.statePath}/tmp/sockets 0750 ${cfg.user} ${cfg.group} -" "d ${cfg.statePath}/uploads 0700 ${cfg.user} ${cfg.group} -" + "d ${cfg.statePath}/custom_hooks 0700 ${cfg.user} ${cfg.group} -" "d ${cfg.statePath}/custom_hooks/pre-receive.d 0700 ${cfg.user} ${cfg.group} -" "d ${cfg.statePath}/custom_hooks/post-receive.d 0700 ${cfg.user} ${cfg.group} -" "d ${cfg.statePath}/custom_hooks/update.d 0700 ${cfg.user} ${cfg.group} -" + "d ${gitlabConfig.production.shared.path} 0750 ${cfg.user} ${cfg.group} -" "d ${gitlabConfig.production.shared.path}/artifacts 0750 ${cfg.user} ${cfg.group} -" "d ${gitlabConfig.production.shared.path}/lfs-objects 0750 ${cfg.user} ${cfg.group} -" "d ${gitlabConfig.production.shared.path}/pages 0750 ${cfg.user} ${cfg.group} -" - ]; + "L+ ${cfg.statePath}/lib - - - - ${cfg.packages.gitlab}/share/gitlab/lib" + "L+ /run/gitlab/config - - - - ${cfg.statePath}/config" + "L+ /run/gitlab/log - - - - ${cfg.statePath}/log" + "L+ /run/gitlab/tmp - - - - ${cfg.statePath}/tmp" + "L+ /run/gitlab/uploads - - - - ${cfg.statePath}/uploads" + + "L+ /run/gitlab/shell-config.yml - - - - ${pkgs.writeText "config.yml" (builtins.toJSON gitlabShellConfig)}" + + "L+ ${cfg.statePath}/config/gitlab.yml - - - - ${pkgs.writeText "gitlab.yml" (builtins.toJSON gitlabConfig)}" + "L+ ${cfg.statePath}/config/database.yml - - - - ${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)}" + "L+ ${cfg.statePath}/config/secrets.yml - - - - ${pkgs.writeText "secrets.yml" (builtins.toJSON secretsConfig)}" + "L+ ${cfg.statePath}/config/unicorn.rb - - - - ${./defaultUnicornConfig.rb}" + + "L+ ${cfg.statePath}/config/initializers/extra-gitlab.rb - - - - ${extraGitlabRb}" + ] ++ optional cfg.smtp.enable + "L+ ${cfg.statePath}/config/initializers/smtp_settings.rb - - - - ${smtpSettings}" ; systemd.services.gitlab-sidekiq = { after = [ "network.target" "redis.service" "gitlab.service" ]; @@ -570,6 +586,7 @@ in { after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; path = with pkgs; [ + exiftool gitAndTools.git gnutar gzip @@ -609,40 +626,14 @@ in { gnupg ]; preStart = '' - cp -rf ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db - rm -rf ${cfg.statePath}/config - mkdir ${cfg.statePath}/config - if [ -e ${cfg.statePath}/lib ]; then - rm ${cfg.statePath}/lib - fi + ${pkgs.sudo}/bin/sudo -u ${cfg.user} cp -f ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION + ${pkgs.sudo}/bin/sudo -u ${cfg.user} rm -rf ${cfg.statePath}/db/* + ${pkgs.sudo}/bin/sudo -u ${cfg.user} cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config + ${pkgs.sudo}/bin/sudo -u ${cfg.user} cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db - ln -sf ${cfg.packages.gitlab}/share/gitlab/lib ${cfg.statePath}/lib - [ -L /run/gitlab/config ] || ln -sf ${cfg.statePath}/config /run/gitlab/config - [ -L /run/gitlab/log ] || ln -sf ${cfg.statePath}/log /run/gitlab/log - [ -L /run/gitlab/tmp ] || ln -sf ${cfg.statePath}/tmp /run/gitlab/tmp - [ -L /run/gitlab/uploads ] || ln -sf ${cfg.statePath}/uploads /run/gitlab/uploads - cp ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION - cp -rf ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config - ln -sf ${extraGitlabRb} ${cfg.statePath}/config/initializers/extra-gitlab.rb - ${optionalString cfg.smtp.enable '' - ln -sf ${smtpSettings} ${cfg.statePath}/config/initializers/smtp_settings.rb - ''} - ${pkgs.openssl}/bin/openssl rand -hex 32 > ${cfg.statePath}/config/gitlab_shell_secret - - # JSON is a subset of YAML - ln -sf ${pkgs.writeText "gitlab.yml" (builtins.toJSON gitlabConfig)} ${cfg.statePath}/config/gitlab.yml - ln -sf ${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} ${cfg.statePath}/config/database.yml - ln -sf ${pkgs.writeText "secrets.yml" (builtins.toJSON secretsConfig)} ${cfg.statePath}/config/secrets.yml - ln -sf ${./defaultUnicornConfig.rb} ${cfg.statePath}/config/unicorn.rb - - # Install the shell required to push repositories - ln -sf ${pkgs.writeText "config.yml" (builtins.toJSON gitlabShellConfig)} /run/gitlab/shell-config.yml - [ -L ${cfg.statePath}/shell/hooks ] || ln -sf ${cfg.packages.gitlab-shell}/hooks ${cfg.statePath}/shell/hooks - ${cfg.packages.gitlab-shell}/bin/install - - chown -R ${cfg.user}:${cfg.group} ${cfg.statePath}/ - chmod -R ug+rwX,o-rwx+X ${cfg.statePath}/ - chown -R ${cfg.user}:${cfg.group} /run/gitlab + ${pkgs.openssl}/bin/openssl rand -hex 32 > ${cfg.statePath}/gitlab_shell_secret + + ${pkgs.sudo}/bin/sudo -u ${cfg.user} ${cfg.packages.gitlab-shell}/bin/install if ! test -e "${cfg.statePath}/db-created"; then if [ "${cfg.databaseHost}" = "127.0.0.1" ]; then @@ -655,7 +646,7 @@ in { ${pkgs.sudo}/bin/sudo -u ${cfg.user} -H ${gitlab-rake}/bin/gitlab-rake db:schema:load - touch "${cfg.statePath}/db-created" + ${pkgs.sudo}/bin/sudo -u ${cfg.user} touch "${cfg.statePath}/db-created" fi # Always do the db migrations just to be sure the database is up-to-date @@ -664,22 +655,13 @@ in { if ! test -e "${cfg.statePath}/db-seeded"; then ${pkgs.sudo}/bin/sudo -u ${cfg.user} ${gitlab-rake}/bin/gitlab-rake db:seed_fu \ GITLAB_ROOT_PASSWORD='${cfg.initialRootPassword}' GITLAB_ROOT_EMAIL='${cfg.initialRootEmail}' - touch "${cfg.statePath}/db-seeded" + ${pkgs.sudo}/bin/sudo -u ${cfg.user} touch "${cfg.statePath}/db-seeded" fi - # The gitlab:shell:create_hooks task seems broken for fixing links - # so we instead delete all the hooks and create them anew + # We remove potentially broken links to old gitlab-shell versions rm -f ${cfg.statePath}/repositories/**/*.git/hooks - ${pkgs.sudo}/bin/sudo -u ${cfg.user} -H ${gitlab-rake}/bin/gitlab-rake gitlab:shell:create_hooks ${pkgs.sudo}/bin/sudo -u ${cfg.user} -H ${pkgs.git}/bin/git config --global core.autocrlf "input" - - # Change permissions in the last step because some of the - # intermediary scripts like to create directories as root. - chmod -R u+rwX,go-rwx+X ${gitlabEnv.HOME} - chmod -R ug+rwX,o-rwx ${cfg.statePath}/repositories - chmod -R ug-s ${cfg.statePath}/repositories - find ${cfg.statePath}/repositories -type d -print0 | xargs -0 chmod g+s ''; serviceConfig = { diff --git a/nixos/modules/services/misc/greenclip.nix b/nixos/modules/services/misc/greenclip.nix new file mode 100644 index 000000000000..9152a782d7f0 --- /dev/null +++ b/nixos/modules/services/misc/greenclip.nix @@ -0,0 +1,31 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.greenclip; +in { + + options.services.greenclip = { + enable = mkEnableOption "Greenclip daemon"; + + package = mkOption { + type = types.package; + default = pkgs.haskellPackages.greenclip; + defaultText = "pkgs.haskellPackages.greenclip"; + description = "greenclip derivation to use."; + }; + }; + + config = mkIf cfg.enable { + systemd.user.services.greenclip = { + enable = true; + description = "greenclip daemon"; + wantedBy = [ "graphical-session.target" ]; + after = [ "graphical-session.target" ]; + serviceConfig.ExecStart = "${cfg.package}/bin/greenclip daemon"; + }; + + environment.systemPackages = [ cfg.package ]; + }; +} diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix index d8f90f1539c1..6bc88c66dc19 100644 --- a/nixos/modules/services/misc/nix-daemon.nix +++ b/nixos/modules/services/misc/nix-daemon.nix @@ -467,7 +467,7 @@ in fi ''; - nix.nrBuildUsers = mkDefault (lib.max 32 cfg.maxJobs); + nix.nrBuildUsers = mkDefault (lib.max 32 (if cfg.maxJobs == "auto" then 0 else cfg.maxJobs)); users.users = nixbldUsers; diff --git a/nixos/modules/services/misc/taskserver/default.nix b/nixos/modules/services/misc/taskserver/default.nix index 07dbee69db0c..8a57277fafe7 100644 --- a/nixos/modules/services/misc/taskserver/default.nix +++ b/nixos/modules/services/misc/taskserver/default.nix @@ -411,7 +411,7 @@ in { } else { cert = "${cfg.pki.manual.server.cert}"; key = "${cfg.pki.manual.server.key}"; - crl = "${cfg.pki.manual.server.crl}"; + ${mapNullable (_: "crl") cfg.pki.manual.server.crl} = "${cfg.pki.manual.server.crl}"; }); ca.cert = if needToCreateCA then "${cfg.dataDir}/keys/ca.cert" diff --git a/nixos/modules/services/misc/tiddlywiki.nix b/nixos/modules/services/misc/tiddlywiki.nix new file mode 100644 index 000000000000..2adc08f6cfed --- /dev/null +++ b/nixos/modules/services/misc/tiddlywiki.nix @@ -0,0 +1,52 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.tiddlywiki; + listenParams = concatStrings (mapAttrsToList (n: v: " '${n}=${toString v}' ") cfg.listenOptions); + exe = "${pkgs.nodePackages.tiddlywiki}/lib/node_modules/.bin/tiddlywiki"; + name = "tiddlywiki"; + dataDir = "/var/lib/" + name; + +in { + + options.services.tiddlywiki = { + + enable = mkEnableOption "TiddlyWiki nodejs server"; + + listenOptions = mkOption { + type = types.attrs; + default = {}; + example = { + credentials = "../credentials.csv"; + readers="(authenticated)"; + port = 3456; + }; + description = '' + Parameters passed to <literal>--listen</literal> command. + Refer to <link xlink:href="https://tiddlywiki.com/#WebServer"/> + for details on supported values. + ''; + }; + }; + + config = mkIf cfg.enable { + systemd = { + services.tiddlywiki = { + description = "TiddlyWiki nodejs server"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "simple"; + Restart = "on-failure"; + DynamicUser = true; + StateDirectory = name; + ExecStartPre = "-${exe} ${dataDir} --init server"; + ExecStart = "${exe} ${dataDir} --listen ${listenParams}"; + }; + }; + }; + }; +} diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix index 5d3f2e6ac28f..c2f6b585d493 100644 --- a/nixos/modules/services/monitoring/grafana.nix +++ b/nixos/modules/services/monitoring/grafana.nix @@ -552,6 +552,8 @@ in { description = "Grafana user"; home = cfg.dataDir; createHome = true; + group = "grafana"; }; + users.groups.grafana = {}; }; } diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix index 20e7eba43412..2ab8910ff9db 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters.nix @@ -1,8 +1,10 @@ -{ config, pkgs, lib, ... }: - -with lib; +{ config, pkgs, lib, options, ... }: let + inherit (lib) concatStrings foldl foldl' genAttrs literalExample maintainers + mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption + optional types; + cfg = config.services.prometheus.exporters; # each attribute in `exporterOpts` is expected to have specified: @@ -17,25 +19,30 @@ let # Note that `extraOpts` is optional, but a script for the exporter's # systemd service must be provided by specifying either # `serviceOpts.script` or `serviceOpts.serviceConfig.ExecStart` - exporterOpts = { - blackbox = import ./exporters/blackbox.nix { inherit config lib pkgs; }; - collectd = import ./exporters/collectd.nix { inherit config lib pkgs; }; - dnsmasq = import ./exporters/dnsmasq.nix { inherit config lib pkgs; }; - dovecot = import ./exporters/dovecot.nix { inherit config lib pkgs; }; - fritzbox = import ./exporters/fritzbox.nix { inherit config lib pkgs; }; - json = import ./exporters/json.nix { inherit config lib pkgs; }; - minio = import ./exporters/minio.nix { inherit config lib pkgs; }; - nginx = import ./exporters/nginx.nix { inherit config lib pkgs; }; - node = import ./exporters/node.nix { inherit config lib pkgs; }; - postfix = import ./exporters/postfix.nix { inherit config lib pkgs; }; - snmp = import ./exporters/snmp.nix { inherit config lib pkgs; }; - surfboard = import ./exporters/surfboard.nix { inherit config lib pkgs; }; - tor = import ./exporters/tor.nix { inherit config lib pkgs; }; - unifi = import ./exporters/unifi.nix { inherit config lib pkgs; }; - varnish = import ./exporters/varnish.nix { inherit config lib pkgs; }; - bind = import ./exporters/bind.nix { inherit config lib pkgs; }; - wireguard = import ./exporters/wireguard.nix { inherit config lib pkgs; }; - }; + + exporterOpts = genAttrs [ + "bind" + "blackbox" + "collectd" + "dnsmasq" + "dovecot" + "fritzbox" + "json" + "mail" + "minio" + "nginx" + "node" + "postfix" + "postgres" + "snmp" + "surfboard" + "tor" + "unifi" + "varnish" + "wireguard" + ] (name: + import (./. + "/exporters/${name}.nix") { inherit config lib pkgs options; } + ); mkExporterOpts = ({ name, port }: { enable = mkEnableOption "the prometheus ${name} exporter"; @@ -81,7 +88,7 @@ let }; user = mkOption { type = types.str; - default = "nobody"; + default = "${name}-exporter"; description = '' User name under which the ${name} exporter shall be run. Has no effect when <option>systemd.services.prometheus-${name}-exporter.serviceConfig.DynamicUser</option> is true. @@ -89,7 +96,7 @@ let }; group = mkOption { type = types.str; - default = "nobody"; + default = "${name}-exporter"; description = '' Group under which the ${name} exporter shall be run. Has no effect when <option>systemd.services.prometheus-${name}-exporter.serviceConfig.DynamicUser</option> is true. @@ -97,9 +104,10 @@ let }; }); - mkSubModule = { name, port, extraOpts, ... }: { + mkSubModule = { name, port, extraOpts, imports }: { ${name} = mkOption { type = types.submodule { + inherit imports; options = (mkExporterOpts { inherit name port; } // extraOpts); @@ -112,13 +120,30 @@ let mkSubModules = (foldl' (a: b: a//b) {} (mapAttrsToList (name: opts: mkSubModule { inherit name; - inherit (opts) port serviceOpts; + inherit (opts) port; extraOpts = opts.extraOpts or {}; + imports = opts.imports or []; }) exporterOpts) ); mkExporterConf = { name, conf, serviceOpts }: + let + enableDynamicUser = serviceOpts.serviceConfig.DynamicUser or true; + in mkIf conf.enable { + warnings = conf.warnings or []; + users.users = (mkIf (conf.user == "${name}-exporter" && !enableDynamicUser) { + "${name}-exporter" = { + description = '' + Prometheus ${name} exporter service user + ''; + isSystemUser = true; + inherit (conf) group; + }; + }); + users.groups = (mkIf (conf.group == "${name}-exporter" && !enableDynamicUser) { + "${name}-exporter" = {}; + }); networking.firewall.extraCommands = mkIf conf.openFirewall (concatStrings [ "ip46tables -A nixos-fw ${conf.firewallFilter} " "-m comment --comment ${name}-exporter -j nixos-fw-accept" @@ -129,7 +154,8 @@ let serviceConfig.Restart = mkDefault "always"; serviceConfig.PrivateTmp = mkDefault true; serviceConfig.WorkingDirectory = mkDefault /tmp; - } serviceOpts ] ++ optional (!(serviceOpts.serviceConfig.DynamicUser or false)) { + serviceConfig.DynamicUser = mkDefault enableDynamicUser; + } serviceOpts ] ++ optional (!enableDynamicUser) { serviceConfig.User = conf.user; serviceConfig.Group = conf.group; }); @@ -154,13 +180,19 @@ in }; config = mkMerge ([{ - assertions = [{ + assertions = [ { assertion = (cfg.snmp.configurationPath == null) != (cfg.snmp.configuration == null); message = '' Please ensure you have either `services.prometheus.exporters.snmp.configuration' or `services.prometheus.exporters.snmp.configurationPath' set! ''; - }]; + } { + assertion = (cfg.mail.configFile == null) != (cfg.mail.configuration == {}); + message = '' + Please specify either 'services.prometheus.exporters.mail.configuration' + or 'services.prometheus.exporters.mail.configFile'. + ''; + } ]; }] ++ [(mkIf config.services.minio.enable { services.prometheus.exporters.minio.minioAddress = mkDefault "http://localhost:9000"; services.prometheus.exporters.minio.minioAccessKey = mkDefault config.services.minio.accessKey; diff --git a/nixos/modules/services/monitoring/prometheus/exporters.xml b/nixos/modules/services/monitoring/prometheus/exporters.xml index 81ac998729be..c2d4b05996a4 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters.xml +++ b/nixos/modules/services/monitoring/prometheus/exporters.xml @@ -17,7 +17,7 @@ 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 = { + services.prometheus.exporters.node = { enable = true; enabledCollectors = [ "logind" @@ -113,7 +113,7 @@ specific options and configuration: <programlisting> # nixpgs/nixos/modules/services/prometheus/exporters/postfix.nix -{ config, lib, pkgs }: +{ config, lib, pkgs, options }: with lib; @@ -159,8 +159,10 @@ in # `serviceOpts.script` and `serviceOpts.serviceConfig.ExecStart` # has to be specified here. This will be merged with the default # service confiuration. + # Note that by default 'DynamicUser' is 'true'. serviceOpts = { serviceConfig = { + DynamicUser = false; ExecStart = '' ${pkgs.prometheus-postfix-exporter}/bin/postfix_exporter \ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ @@ -184,4 +186,42 @@ in </listitem> </itemizedlist> </section> + <section xml:id="module-services-prometheus-exporters-update-exporter-module"> + <title>Updating an exporter module</title> + <para> + Should an exporter option change at some point, it is possible to add + information about the change to the exporter definition similar to + <literal>nixpkgs/nixos/modules/rename.nix</literal>: +<programlisting> +{ config, lib, pkgs, options }: + +with lib; + +let + cfg = config.services.prometheus.exporters.nginx; +in +{ + port = 9113; + extraOpts = { + # additional module options + # ... + }; + serviceOpts = { + # service configuration + # ... + }; + imports = [ + # 'services.prometheus.exporters.nginx.telemetryEndpoint' -> 'services.prometheus.exporters.nginx.telemetryPath' + (mkRenamedOptionModule [ "telemetryEndpoint" ] [ "telemetryPath" ]) + + # removed option 'services.prometheus.exporters.nginx.insecure' + (mkRemovedOptionModule [ "insecure" ] '' + This option was replaced by 'prometheus.exporters.nginx.sslVerify' which defaults to true. + '') + ({ options.warnings = options.warnings; }) + ]; +} +</programlisting> + </para> + </section> </chapter> diff --git a/nixos/modules/services/monitoring/prometheus/exporters/bind.nix b/nixos/modules/services/monitoring/prometheus/exporters/bind.nix index a9746c4d65d5..972632b5a24a 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/bind.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/bind.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs }: +{ config, lib, pkgs, options }: with lib; @@ -39,7 +39,6 @@ in }; serviceOpts = { serviceConfig = { - DynamicUser = true; ExecStart = '' ${pkgs.prometheus-bind-exporter}/bin/bind_exporter \ -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ diff --git a/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix b/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix index d09d1c4f3663..f69b389760f7 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs }: +{ config, lib, pkgs, options }: with lib; @@ -18,7 +18,6 @@ in serviceOpts = { serviceConfig = { AmbientCapabilities = [ "CAP_NET_RAW" ]; # for ping probes - DynamicUser = true; ExecStart = '' ${pkgs.prometheus-blackbox-exporter}/bin/blackbox_exporter \ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ diff --git a/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix b/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix index 0eba3527162d..1cc346418091 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs }: +{ config, lib, pkgs, options }: with lib; @@ -64,7 +64,6 @@ in '' else ""; in { serviceConfig = { - DynamicUser = true; ExecStart = '' ${pkgs.prometheus-collectd-exporter}/bin/collectd_exporter \ -log.format ${cfg.logFormat} \ diff --git a/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix b/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix index b1fab85109af..e9fa26cb1f5a 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs }: +{ config, lib, pkgs, options }: with lib; @@ -26,7 +26,6 @@ in }; serviceOpts = { serviceConfig = { - DynamicUser = true; ExecStart = '' ${pkgs.prometheus-dnsmasq-exporter}/bin/dnsmasq_exporter \ --listen ${cfg.listenAddress}:${toString cfg.port} \ diff --git a/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix b/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix index c47e87a3dc35..a01074758ff8 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs }: +{ config, lib, pkgs, options }: with lib; @@ -39,8 +39,8 @@ in mail_plugins = $mail_plugins old_stats service old-stats { unix_listener old-stats { - user = nobody - group = nobody + user = dovecot-exporter + group = dovecot-exporter } } '''; @@ -59,6 +59,7 @@ in }; serviceOpts = { serviceConfig = { + DynamicUser = false; ExecStart = '' ${pkgs.prometheus-dovecot-exporter}/bin/dovecot_exporter \ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ diff --git a/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix b/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix index 530206681d36..9526597b8c96 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs }: +{ config, lib, pkgs, options }: with lib; @@ -26,7 +26,6 @@ in }; serviceOpts = { serviceConfig = { - DynamicUser = true; ExecStart = '' ${pkgs.prometheus-fritzbox-exporter}/bin/exporter \ -listen-address ${cfg.listenAddress}:${toString cfg.port} \ diff --git a/nixos/modules/services/monitoring/prometheus/exporters/json.nix b/nixos/modules/services/monitoring/prometheus/exporters/json.nix index a5494e85e016..82a55bafc982 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/json.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/json.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs }: +{ config, lib, pkgs, options }: with lib; @@ -24,7 +24,6 @@ in }; serviceOpts = { serviceConfig = { - DynamicUser = true; ExecStart = '' ${pkgs.prometheus-json-exporter}/bin/prometheus-json-exporter \ --port ${toString cfg.port} \ diff --git a/nixos/modules/services/monitoring/prometheus/exporters/mail.nix b/nixos/modules/services/monitoring/prometheus/exporters/mail.nix new file mode 100644 index 000000000000..7d8c6fb61404 --- /dev/null +++ b/nixos/modules/services/monitoring/prometheus/exporters/mail.nix @@ -0,0 +1,157 @@ +{ config, lib, pkgs, options }: + +with lib; + +let + cfg = config.services.prometheus.exporters.mail; + + configurationFile = pkgs.writeText "prometheus-mail-exporter.conf" (builtins.toJSON ( + # removes the _module attribute, null values and converts attrNames to lowercase + mapAttrs' (name: value: + if name == "servers" + then nameValuePair (toLower name) + ((map (srv: (mapAttrs' (n: v: nameValuePair (toLower n) v) + (filterAttrs (n: v: !(n == "_module" || v == null)) srv) + ))) value) + else nameValuePair (toLower name) value + ) (filterAttrs (n: _: !(n == "_module")) cfg.configuration) + )); + + serverOptions.options = { + name = mkOption { + type = types.str; + description = '' + Value for label 'configname' which will be added to all metrics. + ''; + }; + server = mkOption { + type = types.str; + description = '' + Hostname of the server that should be probed. + ''; + }; + port = mkOption { + type = types.int; + example = 587; + description = '' + Port to use for SMTP. + ''; + }; + from = mkOption { + type = types.str; + example = "exporteruser@domain.tld"; + description = '' + Content of 'From' Header for probing mails. + ''; + }; + to = mkOption { + type = types.str; + example = "exporteruser@domain.tld"; + description = '' + Content of 'To' Header for probing mails. + ''; + }; + detectionDir = mkOption { + type = types.path; + example = "/var/spool/mail/exporteruser/new"; + description = '' + Directory in which new mails for the exporter user are placed. + Note that this needs to exist when the exporter starts. + ''; + }; + login = mkOption { + type = types.nullOr types.str; + default = null; + example = "exporteruser@domain.tld"; + description = '' + Username to use for SMTP authentication. + ''; + }; + passphrase = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Password to use for SMTP authentication. + ''; + }; + }; + + exporterOptions.options = { + monitoringInterval = mkOption { + type = types.str; + example = "10s"; + description = '' + Time interval between two probe attempts. + ''; + }; + mailCheckTimeout = mkOption { + type = types.str; + description = '' + Timeout until mails are considered "didn't make it". + ''; + }; + disableFileDelition = mkOption { + type = types.bool; + default = false; + description = '' + Disables the exporter's function to delete probing mails. + ''; + }; + servers = mkOption { + type = types.listOf (types.submodule serverOptions); + default = []; + example = literalExample '' + [ { + name = "testserver"; + server = "smtp.domain.tld"; + port = 587; + from = "exporteruser@domain.tld"; + to = "exporteruser@domain.tld"; + detectionDir = "/path/to/Maildir/new"; + } ] + ''; + description = '' + List of servers that should be probed. + ''; + }; + }; +in +{ + port = 9225; + extraOpts = { + configFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Specify the mailexporter configuration file to use. + ''; + }; + configuration = mkOption { + type = types.submodule exporterOptions; + default = {}; + description = '' + Specify the mailexporter configuration file to use. + ''; + }; + telemetryPath = mkOption { + type = types.str; + default = "/metrics"; + description = '' + Path under which to expose metrics. + ''; + }; + }; + serviceOpts = { + serviceConfig = { + DynamicUser = false; + ExecStart = '' + ${pkgs.prometheus-mail-exporter}/bin/mailexporter \ + --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ + --config.file ${ + if cfg.configuration != {} then configurationFile else cfg.configFile + } \ + ${concatStringsSep " \\\n " cfg.extraFlags} + ''; + }; + }; +} diff --git a/nixos/modules/services/monitoring/prometheus/exporters/minio.nix b/nixos/modules/services/monitoring/prometheus/exporters/minio.nix index 3cc4ffdbc8fd..ab3e3d7d5d50 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/minio.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/minio.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs }: +{ config, lib, pkgs, options }: with lib; @@ -50,7 +50,6 @@ in }; serviceOpts = { serviceConfig = { - DynamicUser = true; ExecStart = '' ${pkgs.prometheus-minio-exporter}/bin/minio-exporter \ -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ diff --git a/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix b/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix index 431dd8b4ead7..554377df37ba 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs }: +{ config, lib, pkgs, options }: with lib; @@ -16,32 +16,39 @@ in Can be enabled with services.nginx.statusPage = true. ''; }; - telemetryEndpoint = mkOption { + telemetryPath = mkOption { type = types.str; default = "/metrics"; description = '' Path under which to expose metrics. ''; }; - insecure = mkOption { + sslVerify = mkOption { type = types.bool; default = true; description = '' - Ignore server certificate if using https. + Whether to perform certificate verification for https. ''; }; + }; serviceOpts = { serviceConfig = { - DynamicUser = true; ExecStart = '' - ${pkgs.prometheus-nginx-exporter}/bin/nginx_exporter \ - --nginx.scrape_uri '${cfg.scrapeUri}' \ - --telemetry.address ${cfg.listenAddress}:${toString cfg.port} \ - --telemetry.endpoint ${cfg.telemetryEndpoint} \ - --insecure ${toString cfg.insecure} \ + ${pkgs.prometheus-nginx-exporter}/bin/nginx-prometheus-exporter \ + --nginx.scrape-uri '${cfg.scrapeUri}' \ + --nginx.ssl-verify ${toString cfg.sslVerify} \ + --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ + --web.telemetry-path ${cfg.telemetryPath} \ ${concatStringsSep " \\\n " cfg.extraFlags} ''; }; }; + imports = [ + (mkRenamedOptionModule [ "telemetryEndpoint" ] [ "telemetryPath" ]) + (mkRemovedOptionModule [ "insecure" ] '' + This option was replaced by 'prometheus.exporters.nginx.sslVerify'. + '') + ({ options.warnings = options.warnings; }) + ]; } diff --git a/nixos/modules/services/monitoring/prometheus/exporters/node.nix b/nixos/modules/services/monitoring/prometheus/exporters/node.nix index 8c4128f9b634..7e394e8463e0 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/node.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/node.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs }: +{ config, lib, pkgs, options }: with lib; @@ -27,6 +27,7 @@ in }; serviceOpts = { serviceConfig = { + DynamicUser = false; RuntimeDirectory = "prometheus-node-exporter"; ExecStart = '' ${pkgs.prometheus-node-exporter}/bin/node_exporter \ diff --git a/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix b/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix index efe78ebcba86..f40819e826b0 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs }: +{ config, lib, pkgs, options }: with lib; @@ -62,6 +62,7 @@ in }; serviceOpts = { serviceConfig = { + DynamicUser = false; ExecStart = '' ${pkgs.prometheus-postfix-exporter}/bin/postfix_exporter \ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ diff --git a/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix b/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix new file mode 100644 index 000000000000..1ece73a1159a --- /dev/null +++ b/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix @@ -0,0 +1,47 @@ +{ config, lib, pkgs, options }: + +with lib; + +let + cfg = config.services.prometheus.exporters.postgres; +in +{ + port = 9187; + extraOpts = { + telemetryPath = mkOption { + type = types.str; + default = "/metrics"; + description = '' + Path under which to expose metrics. + ''; + }; + dataSourceName = mkOption { + type = types.str; + default = "user=postgres database=postgres host=/run/postgresql sslmode=disable"; + example = "postgresql://username:password@localhost:5432/postgres?sslmode=disable"; + description = '' + Accepts PostgreSQL URI form and key=value form arguments. + ''; + }; + runAsLocalSuperUser = mkOption { + type = types.bool; + default = false; + description = '' + Whether to run the exporter as the local 'postgres' super user. + ''; + }; + }; + serviceOpts = { + environment.DATA_SOURCE_NAME = cfg.dataSourceName; + serviceConfig = { + DynamicUser = false; + User = mkIf cfg.runAsLocalSuperUser (mkForce "postgres"); + ExecStart = '' + ${pkgs.prometheus-postgres-exporter}/bin/postgres_exporter \ + --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ + --web.telemetry-path ${cfg.telemetryPath} \ + ${concatStringsSep " \\\n " cfg.extraFlags} + ''; + }; + }; +} diff --git a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix index 0d9194124325..fe7ae8a8ac90 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs }: +{ config, lib, pkgs, options }: with lib; @@ -57,7 +57,6 @@ in else "${pkgs.writeText "snmp-eporter-conf.yml" (builtins.toJSON cfg.configuration)}"; in { serviceConfig = { - DynamicUser = true; ExecStart = '' ${pkgs.prometheus-snmp-exporter.bin}/bin/snmp_exporter \ --config.file=${configFile} \ diff --git a/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix b/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix index 715dba06a3dc..81c5c70ed93f 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs }: +{ config, lib, pkgs, options }: with lib; @@ -20,7 +20,6 @@ in description = "Prometheus exporter for surfboard cable modem"; unitConfig.Documentation = "https://github.com/ipstatic/surfboard_exporter"; serviceConfig = { - DynamicUser = true; ExecStart = '' ${pkgs.prometheus-surfboard-exporter}/bin/surfboard_exporter \ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ diff --git a/nixos/modules/services/monitoring/prometheus/exporters/tor.nix b/nixos/modules/services/monitoring/prometheus/exporters/tor.nix index e0ae83802425..36c473677efa 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/tor.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/tor.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs }: +{ config, lib, pkgs, options }: with lib; @@ -26,7 +26,6 @@ in }; serviceOpts = { serviceConfig = { - DynamicUser = true; ExecStart = '' ${pkgs.prometheus-tor-exporter}/bin/prometheus-tor-exporter \ -b ${cfg.listenAddress} \ diff --git a/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix b/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix index 011dcbe208e4..9aa0f1b85aac 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs }: +{ config, lib, pkgs, options }: with lib; @@ -51,7 +51,6 @@ in }; serviceOpts = { serviceConfig = { - DynamicUser = true; ExecStart = '' ${pkgs.prometheus-unifi-exporter}/bin/unifi_exporter \ -telemetry.addr ${cfg.listenAddress}:${toString cfg.port} \ diff --git a/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix b/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix index aaed76175b84..12153fa021ec 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs }: +{ config, lib, pkgs, options }: with lib; @@ -68,8 +68,8 @@ in serviceOpts = { path = [ pkgs.varnish ]; serviceConfig = { - DynamicUser = true; RestartSec = mkDefault 1; + DynamicUser = false; ExecStart = '' ${pkgs.prometheus-varnish-exporter}/bin/prometheus_varnish_exporter \ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ diff --git a/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix b/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix index c5b84e574b8d..aee7cba26384 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs }: +{ config, lib, pkgs, options }: with lib; @@ -23,20 +23,30 @@ in { to set the peers up. ''; }; + + singleSubnetPerField = mkOption { + type = types.bool; + default = false; + description = '' + By default, all allowed IPs and subnets are comma-separated in the + <literal>allowed_ips</literal> field. With this option enabled, + a single IP and subnet will be listed in fields like <literal>allowed_ip_0</literal>, + <literal>allowed_ip_1</literal> and so on. + ''; + }; }; serviceOpts = { - script = '' - ${pkgs.prometheus-wireguard-exporter}/bin/prometheus_wireguard_exporter \ - -p ${toString cfg.port} \ - ${optionalString cfg.verbose "-v"} \ - ${optionalString (cfg.wireguardConfig != null) "-n ${cfg.wireguardConfig}"} - ''; - path = [ pkgs.wireguard-tools ]; serviceConfig = { - DynamicUser = true; AmbientCapabilities = [ "CAP_NET_ADMIN" ]; + ExecStart = '' + ${pkgs.prometheus-wireguard-exporter}/bin/prometheus_wireguard_exporter \ + -p ${toString cfg.port} \ + ${optionalString cfg.verbose "-v"} \ + ${optionalString cfg.singleSubnetPerField "-s"} \ + ${optionalString (cfg.wireguardConfig != null) "-n ${cfg.wireguardConfig}"} + ''; }; }; } diff --git a/nixos/modules/services/monitoring/thanos.nix b/nixos/modules/services/monitoring/thanos.nix new file mode 100644 index 000000000000..b41e99b76477 --- /dev/null +++ b/nixos/modules/services/monitoring/thanos.nix @@ -0,0 +1,801 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.thanos; + + nullOpt = type: description: mkOption { + type = types.nullOr type; + default = null; + inherit description; + }; + + optionToArgs = opt: v : optional (v != null) ''--${opt}="${toString v}"''; + flagToArgs = opt: v : optional v ''--${opt}''; + listToArgs = opt: vs : map (v: ''--${opt}="${v}"'') vs; + attrsToArgs = opt: kvs: mapAttrsToList (k: v: ''--${opt}=${k}=\"${v}\"'') kvs; + + mkParamDef = type: default: description: mkParam type (description + '' + + Defaults to <literal>${toString default}</literal> in Thanos + when set to <literal>null</literal>. + ''); + + mkParam = type: description: { + toArgs = optionToArgs; + option = nullOpt type description; + }; + + mkFlagParam = description: { + toArgs = flagToArgs; + option = mkOption { + type = types.bool; + default = false; + inherit description; + }; + }; + + mkListParam = opt: description: { + toArgs = _opt: listToArgs opt; + option = mkOption { + type = types.listOf types.str; + default = []; + inherit description; + }; + }; + + mkAttrsParam = opt: description: { + toArgs = _opt: attrsToArgs opt; + option = mkOption { + type = types.attrsOf types.str; + default = {}; + inherit description; + }; + }; + + mkStateDirParam = opt: default: description: { + toArgs = _opt: stateDir: optionToArgs opt "/var/lib/${stateDir}"; + option = mkOption { + type = types.str; + inherit default; + inherit description; + }; + }; + + toYAML = name: attrs: pkgs.runCommandNoCC name { + preferLocalBuild = true; + json = builtins.toFile "${name}.json" (builtins.toJSON attrs); + nativeBuildInputs = [ pkgs.remarshal ]; + } ''json2yaml -i $json -o $out''; + + thanos = cmd: "${cfg.package}/bin/thanos ${cmd}" + + (let args = cfg."${cmd}".arguments; + in optionalString (length args != 0) (" \\\n " + + concatStringsSep " \\\n " args)); + + argumentsOf = cmd: concatLists (collect isList + (flip mapParamsRecursive params."${cmd}" (path: param: + let opt = concatStringsSep "." path; + v = getAttrFromPath path cfg."${cmd}"; + in param.toArgs opt v))); + + mkArgumentsOption = cmd: mkOption { + type = types.listOf types.str; + default = argumentsOf cmd; + description = '' + Arguments to the <literal>thanos ${cmd}</literal> command. + + Defaults to a list of arguments formed by converting the structured + options of <option>services.thanos.${cmd}</option> to a list of arguments. + + Overriding this option will cause none of the structured options to have + any effect. So only set this if you know what you're doing! + ''; + }; + + mapParamsRecursive = + let noParam = attr: !(attr ? "toArgs" && attr ? "option"); + in mapAttrsRecursiveCond noParam; + + paramsToOptions = mapParamsRecursive (_path: param: param.option); + + params = { + + log = { + + log.level = mkParamDef (types.enum ["debug" "info" "warn" "error" "fatal"]) "info" '' + Log filtering level. + ''; + + log.format = mkParam types.str '' + Log format to use. + ''; + }; + + tracing = cfg: { + tracing.config-file = { + toArgs = _opt: path: optionToArgs "tracing.config-file" path; + option = mkOption { + type = with types; nullOr str; + default = if cfg.tracing.config == null then null + else toString (toYAML "tracing.yaml" cfg.tracing.config); + defaultText = '' + if config.services.thanos.<cmd>.tracing.config == null then null + else toString (toYAML "tracing.yaml" config.services.thanos.<cmd>.tracing.config); + ''; + description = '' + Path to YAML file that contains tracing configuration. + ''; + }; + }; + + tracing.config = + { + toArgs = _opt: _attrs: []; + option = nullOpt types.attrs '' + Tracing configuration. + + When not <literal>null</literal> the attribute set gets converted to + a YAML file and stored in the Nix store. The option + <option>tracing.config-file</option> will default to its path. + + If <option>tracing.config-file</option> is set this option has no effect. + ''; + }; + }; + + common = cfg: params.log // params.tracing cfg // { + + http-address = mkParamDef types.str "0.0.0.0:10902" '' + Listen <literal>host:port</literal> for HTTP endpoints. + ''; + + grpc-address = mkParamDef types.str "0.0.0.0:10901" '' + Listen <literal>ip:port</literal> address for gRPC endpoints (StoreAPI). + + Make sure this address is routable from other components. + ''; + + grpc-server-tls-cert = mkParam types.str '' + TLS Certificate for gRPC server, leave blank to disable TLS + ''; + + grpc-server-tls-key = mkParam types.str '' + TLS Key for the gRPC server, leave blank to disable TLS + ''; + + grpc-server-tls-client-ca = mkParam types.str '' + TLS CA to verify clients against. + + If no client CA is specified, there is no client verification on server side. + (tls.NoClientCert) + ''; + }; + + objstore = cfg: { + + objstore.config-file = { + toArgs = _opt: path: optionToArgs "objstore.config-file" path; + option = mkOption { + type = with types; nullOr str; + default = if cfg.objstore.config == null then null + else toString (toYAML "objstore.yaml" cfg.objstore.config); + defaultText = '' + if config.services.thanos.<cmd>.objstore.config == null then null + else toString (toYAML "objstore.yaml" config.services.thanos.<cmd>.objstore.config); + ''; + description = '' + Path to YAML file that contains object store configuration. + ''; + }; + }; + + objstore.config = + { + toArgs = _opt: _attrs: []; + option = nullOpt types.attrs '' + Object store configuration. + + When not <literal>null</literal> the attribute set gets converted to + a YAML file and stored in the Nix store. The option + <option>objstore.config-file</option> will default to its path. + + If <option>objstore.config-file</option> is set this option has no effect. + ''; + }; + }; + + sidecar = params.common cfg.sidecar // params.objstore cfg.sidecar // { + + prometheus.url = mkParamDef types.str "http://localhost:9090" '' + URL at which to reach Prometheus's API. + + For better performance use local network. + ''; + + tsdb.path = { + toArgs = optionToArgs; + option = mkOption { + type = types.str; + default = "/var/lib/${config.services.prometheus2.stateDir}/data"; + defaultText = "/var/lib/\${config.services.prometheus2.stateDir}/data"; + description = '' + Data directory of TSDB. + ''; + }; + }; + + reloader.config-file = mkParam types.str '' + Config file watched by the reloader. + ''; + + reloader.config-envsubst-file = mkParam types.str '' + Output file for environment variable substituted config file. + ''; + + reloader.rule-dirs = mkListParam "reloader.rule-dir" '' + Rule directories for the reloader to refresh. + ''; + + }; + + store = params.common cfg.store // params.objstore cfg.store // { + + stateDir = mkStateDirParam "data-dir" "thanos-store" '' + Data directory relative to <literal>/var/lib</literal> + in which to cache remote blocks. + ''; + + index-cache-size = mkParamDef types.str "250MB" '' + Maximum size of items held in the index cache. + ''; + + chunk-pool-size = mkParamDef types.str "2GB" '' + Maximum size of concurrently allocatable bytes for chunks. + ''; + + store.grpc.series-sample-limit = mkParamDef types.int 0 '' + Maximum amount of samples returned via a single Series call. + + <literal>0</literal> means no limit. + + NOTE: for efficiency we take 120 as the number of samples in chunk (it + cannot be bigger than that), so the actual number of samples might be + lower, even though the maximum could be hit. + ''; + + store.grpc.series-max-concurrency = mkParamDef types.int 20 '' + Maximum number of concurrent Series calls. + ''; + + sync-block-duration = mkParamDef types.str "3m" '' + Repeat interval for syncing the blocks between local and remote view. + ''; + + block-sync-concurrency = mkParamDef types.int 20 '' + Number of goroutines to use when syncing blocks from object storage. + ''; + }; + + query = params.common cfg.query // { + + grpc-client-tls-secure = mkFlagParam '' + Use TLS when talking to the gRPC server + ''; + + grpc-client-tls-cert = mkParam types.str '' + TLS Certificates to use to identify this client to the server + ''; + + grpc-client-tls-key = mkParam types.str '' + TLS Key for the client's certificate + ''; + + grpc-client-tls-ca = mkParam types.str '' + TLS CA Certificates to use to verify gRPC servers + ''; + + grpc-client-server-name = mkParam types.str '' + Server name to verify the hostname on the returned gRPC certificates. + See <link xlink:href="https://tools.ietf.org/html/rfc4366#section-3.1"/> + ''; + + web.route-prefix = mkParam types.str '' + Prefix for API and UI endpoints. + + This allows thanos UI to be served on a sub-path. This option is + analogous to <option>web.route-prefix</option> of Promethus. + ''; + + web.external-prefix = mkParam types.str '' + Static prefix for all HTML links and redirect URLs in the UI query web + interface. + + Actual endpoints are still served on / or the + <option>web.route-prefix</option>. This allows thanos UI to be served + behind a reverse proxy that strips a URL sub-path. + ''; + + web.prefix-header = mkParam types.str '' + Name of HTTP request header used for dynamic prefixing of UI links and + redirects. + + This option is ignored if the option + <literal>web.external-prefix</literal> is set. + + Security risk: enable this option only if a reverse proxy in front of + thanos is resetting the header. + + The setting <literal>web.prefix-header="X-Forwarded-Prefix"</literal> + can be useful, for example, if Thanos UI is served via Traefik reverse + proxy with <literal>PathPrefixStrip</literal> option enabled, which + sends the stripped prefix value in <literal>X-Forwarded-Prefix</literal> + header. This allows thanos UI to be served on a sub-path. + ''; + + query.timeout = mkParamDef types.str "2m" '' + Maximum time to process query by query node. + ''; + + query.max-concurrent = mkParamDef types.int 20 '' + Maximum number of queries processed concurrently by query node. + ''; + + query.replica-label = mkParam types.str '' + Label to treat as a replica indicator along which data is + deduplicated. + + Still you will be able to query without deduplication using + <literal>dedup=false</literal> parameter. + ''; + + selector-labels = mkAttrsParam "selector-label" '' + Query selector labels that will be exposed in info endpoint. + ''; + + store.addresses = mkListParam "store" '' + Addresses of statically configured store API servers. + + The scheme may be prefixed with <literal>dns+</literal> or + <literal>dnssrv+</literal> to detect store API servers through + respective DNS lookups. + ''; + + store.sd-files = mkListParam "store.sd-files" '' + Path to files that contain addresses of store API servers. The path + can be a glob pattern. + ''; + + store.sd-interval = mkParamDef types.str "5m" '' + Refresh interval to re-read file SD files. It is used as a resync fallback. + ''; + + store.sd-dns-interval = mkParamDef types.str "30s" '' + Interval between DNS resolutions. + ''; + + store.unhealthy-timeout = mkParamDef types.str "5m" '' + Timeout before an unhealthy store is cleaned from the store UI page. + ''; + + query.auto-downsampling = mkFlagParam '' + Enable automatic adjustment (step / 5) to what source of data should + be used in store gateways if no + <literal>max_source_resolution</literal> param is specified. + ''; + + query.partial-response = mkFlagParam '' + Enable partial response for queries if no + <literal>partial_response</literal> param is specified. + ''; + + query.default-evaluation-interval = mkParamDef types.str "1m" '' + Set default evaluation interval for sub queries. + ''; + + store.response-timeout = mkParamDef types.str "0ms" '' + If a Store doesn't send any data in this specified duration then a + Store will be ignored and partial data will be returned if it's + enabled. <literal>0</literal> disables timeout. + ''; + }; + + rule = params.common cfg.rule // params.objstore cfg.rule // { + + labels = mkAttrsParam "label" '' + Labels to be applied to all generated metrics. + + Similar to external labels for Prometheus, + used to identify ruler and its blocks as unique source. + ''; + + stateDir = mkStateDirParam "data-dir" "thanos-rule" '' + Data directory relative to <literal>/var/lib</literal>. + ''; + + rule-files = mkListParam "rule-file" '' + Rule files that should be used by rule manager. Can be in glob format. + ''; + + eval-interval = mkParamDef types.str "30s" '' + The default evaluation interval to use. + ''; + + tsdb.block-duration = mkParamDef types.str "2h" '' + Block duration for TSDB block. + ''; + + tsdb.retention = mkParamDef types.str "48h" '' + Block retention time on local disk. + ''; + + alertmanagers.urls = mkListParam "alertmanagers.url" '' + Alertmanager replica URLs to push firing alerts. + + Ruler claims success if push to at least one alertmanager from + discovered succeeds. The scheme may be prefixed with + <literal>dns+</literal> or <literal>dnssrv+</literal> to detect + Alertmanager IPs through respective DNS lookups. The port defaults to + <literal>9093</literal> or the SRV record's value. The URL path is + used as a prefix for the regular Alertmanager API path. + ''; + + alertmanagers.send-timeout = mkParamDef types.str "10s" '' + Timeout for sending alerts to alertmanager. + ''; + + alert.query-url = mkParam types.str '' + The external Thanos Query URL that would be set in all alerts 'Source' field. + ''; + + alert.label-drop = mkListParam "alert.label-drop" '' + Labels by name to drop before sending to alertmanager. + + This allows alert to be deduplicated on replica label. + + Similar Prometheus alert relabelling + ''; + + web.route-prefix = mkParam types.str '' + Prefix for API and UI endpoints. + + This allows thanos UI to be served on a sub-path. + + This option is analogous to <literal>--web.route-prefix</literal> of Promethus. + ''; + + web.external-prefix = mkParam types.str '' + Static prefix for all HTML links and redirect URLs in the UI query web + interface. + + Actual endpoints are still served on / or the + <option>web.route-prefix</option>. This allows thanos UI to be served + behind a reverse proxy that strips a URL sub-path. + ''; + + web.prefix-header = mkParam types.str '' + Name of HTTP request header used for dynamic prefixing of UI links and + redirects. + + This option is ignored if the option + <option>web.external-prefix</option> is set. + + Security risk: enable this option only if a reverse proxy in front of + thanos is resetting the header. + + The header <literal>X-Forwarded-Prefix</literal> can be useful, for + example, if Thanos UI is served via Traefik reverse proxy with + <literal>PathPrefixStrip</literal> option enabled, which sends the + stripped prefix value in <literal>X-Forwarded-Prefix</literal> + header. This allows thanos UI to be served on a sub-path. + ''; + + query.addresses = mkListParam "query" '' + Addresses of statically configured query API servers. + + The scheme may be prefixed with <literal>dns+</literal> or + <literal>dnssrv+</literal> to detect query API servers through + respective DNS lookups. + ''; + + query.sd-files = mkListParam "query.sd-files" '' + Path to file that contain addresses of query peers. + The path can be a glob pattern. + ''; + + query.sd-interval = mkParamDef types.str "5m" '' + Refresh interval to re-read file SD files. (used as a fallback) + ''; + + query.sd-dns-interval = mkParamDef types.str "30s" '' + Interval between DNS resolutions. + ''; + }; + + compact = params.log // params.tracing cfg.compact // params.objstore cfg.compact // { + + http-address = mkParamDef types.str "0.0.0.0:10902" '' + Listen <literal>host:port</literal> for HTTP endpoints. + ''; + + stateDir = mkStateDirParam "data-dir" "thanos-compact" '' + Data directory relative to <literal>/var/lib</literal> + in which to cache blocks and process compactions. + ''; + + consistency-delay = mkParamDef types.str "30m" '' + Minimum age of fresh (non-compacted) blocks before they are being + processed. Malformed blocks older than the maximum of consistency-delay + and 30m0s will be removed. + ''; + + retention.resolution-raw = mkParamDef types.str "0d" '' + How long to retain raw samples in bucket. + + <literal>0d</literal> - disables this retention + ''; + + retention.resolution-5m = mkParamDef types.str "0d" '' + How long to retain samples of resolution 1 (5 minutes) in bucket. + + <literal>0d</literal> - disables this retention + ''; + + retention.resolution-1h = mkParamDef types.str "0d" '' + How long to retain samples of resolution 2 (1 hour) in bucket. + + <literal>0d</literal> - disables this retention + ''; + + startAt = { + toArgs = _opt: startAt: flagToArgs "wait" (startAt == null); + option = nullOpt types.str '' + When this option is set to a <literal>systemd.time</literal> + specification the Thanos compactor will run at the specified period. + + When this option is <literal>null</literal> the Thanos compactor service + will run continuously. So it will not exit after all compactions have + been processed but wait for new work. + ''; + }; + + block-sync-concurrency = mkParamDef types.int 20 '' + Number of goroutines to use when syncing block metadata from object storage. + ''; + + compact.concurrency = mkParamDef types.int 1 '' + Number of goroutines to use when compacting groups. + ''; + }; + + downsample = params.log // params.tracing cfg.downsample // params.objstore cfg.downsample // { + + stateDir = mkStateDirParam "data-dir" "thanos-downsample" '' + Data directory relative to <literal>/var/lib</literal> + in which to cache blocks and process downsamplings. + ''; + + }; + + receive = params.common cfg.receive // params.objstore cfg.receive // { + + remote-write.address = mkParamDef types.str "0.0.0.0:19291" '' + Address to listen on for remote write requests. + ''; + + stateDir = mkStateDirParam "tsdb.path" "thanos-receive" '' + Data directory relative to <literal>/var/lib</literal> of TSDB. + ''; + + labels = mkAttrsParam "labels" '' + External labels to announce. + + This flag will be removed in the future when handling multiple tsdb + instances is added. + ''; + + tsdb.retention = mkParamDef types.str "15d" '' + How long to retain raw samples on local storage. + + <literal>0d</literal> - disables this retention + ''; + }; + + }; + + assertRelativeStateDir = cmd: { + assertions = [ + { + assertion = !hasPrefix "/" cfg."${cmd}".stateDir; + message = + "The option services.thanos.${cmd}.stateDir should not be an absolute directory." + + " It should be a directory relative to /var/lib."; + } + ]; + }; + +in { + + options.services.thanos = { + + package = mkOption { + type = types.package; + default = pkgs.thanos; + defaultText = "pkgs.thanos"; + description = '' + The thanos package that should be used. + ''; + }; + + sidecar = paramsToOptions params.sidecar // { + enable = mkEnableOption + "the Thanos sidecar for Prometheus server"; + arguments = mkArgumentsOption "sidecar"; + }; + + store = paramsToOptions params.store // { + enable = mkEnableOption + "the Thanos store node giving access to blocks in a bucket provider."; + arguments = mkArgumentsOption "store"; + }; + + query = paramsToOptions params.query // { + enable = mkEnableOption + ("the Thanos query node exposing PromQL enabled Query API " + + "with data retrieved from multiple store nodes"); + arguments = mkArgumentsOption "query"; + }; + + rule = paramsToOptions params.rule // { + enable = mkEnableOption + ("the Thanos ruler service which evaluates Prometheus rules against" + + " given Query nodes, exposing Store API and storing old blocks in bucket"); + arguments = mkArgumentsOption "rule"; + }; + + compact = paramsToOptions params.compact // { + enable = mkEnableOption + "the Thanos compactor which continuously compacts blocks in an object store bucket"; + arguments = mkArgumentsOption "compact"; + }; + + downsample = paramsToOptions params.downsample // { + enable = mkEnableOption + "the Thanos downsampler which continuously downsamples blocks in an object store bucket"; + arguments = mkArgumentsOption "downsample"; + }; + + receive = paramsToOptions params.receive // { + enable = mkEnableOption + ("the Thanos receiver which accept Prometheus remote write API requests " + + "and write to local tsdb (EXPERIMENTAL, this may change drastically without notice)"); + arguments = mkArgumentsOption "receive"; + }; + }; + + config = mkMerge [ + + (mkIf cfg.sidecar.enable { + assertions = [ + { + assertion = config.services.prometheus2.enable; + message = + "Please enable services.prometheus2 when enabling services.thanos.sidecar."; + } + { + assertion = !(config.services.prometheus2.globalConfig.external_labels == null || + config.services.prometheus2.globalConfig.external_labels == {}); + message = + "services.thanos.sidecar requires uniquely identifying external labels " + + "to be configured in the Prometheus server. " + + "Please set services.prometheus2.globalConfig.external_labels."; + } + ]; + systemd.services.thanos-sidecar = { + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" "prometheus2.service" ]; + serviceConfig = { + User = "prometheus"; + Restart = "always"; + ExecStart = thanos "sidecar"; + }; + }; + }) + + (mkIf cfg.store.enable (mkMerge [ + (assertRelativeStateDir "store") + { + systemd.services.thanos-store = { + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + DynamicUser = true; + StateDirectory = cfg.store.stateDir; + Restart = "always"; + ExecStart = thanos "store"; + }; + }; + } + ])) + + (mkIf cfg.query.enable { + systemd.services.thanos-query = { + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + DynamicUser = true; + Restart = "always"; + ExecStart = thanos "query"; + }; + }; + }) + + (mkIf cfg.rule.enable (mkMerge [ + (assertRelativeStateDir "rule") + { + systemd.services.thanos-rule = { + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + DynamicUser = true; + StateDirectory = cfg.rule.stateDir; + Restart = "always"; + ExecStart = thanos "rule"; + }; + }; + } + ])) + + (mkIf cfg.compact.enable (mkMerge [ + (assertRelativeStateDir "compact") + { + systemd.services.thanos-compact = + let wait = cfg.compact.startAt == null; in { + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + Type = if wait then "simple" else "oneshot"; + Restart = if wait then "always" else "no"; + DynamicUser = true; + StateDirectory = cfg.compact.stateDir; + ExecStart = thanos "compact"; + }; + } // optionalAttrs (!wait) { inherit (cfg.compact) startAt; }; + } + ])) + + (mkIf cfg.downsample.enable (mkMerge [ + (assertRelativeStateDir "downsample") + { + systemd.services.thanos-downsample = { + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + DynamicUser = true; + StateDirectory = cfg.downsample.stateDir; + Restart = "always"; + ExecStart = thanos "downsample"; + }; + }; + } + ])) + + (mkIf cfg.receive.enable (mkMerge [ + (assertRelativeStateDir "receive") + { + systemd.services.thanos-receive = { + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + DynamicUser = true; + StateDirectory = cfg.receive.stateDir; + Restart = "always"; + ExecStart = thanos "receive"; + }; + }; + } + ])) + + ]; +} diff --git a/nixos/modules/services/monitoring/zabbix-proxy.nix b/nixos/modules/services/monitoring/zabbix-proxy.nix index c1a45fba4af3..9cfcd1697c11 100644 --- a/nixos/modules/services/monitoring/zabbix-proxy.nix +++ b/nixos/modules/services/monitoring/zabbix-proxy.nix @@ -23,6 +23,7 @@ let LogType = console ListenIP = ${cfg.listen.ip} ListenPort = ${toString cfg.listen.port} + Server = ${cfg.server} # TODO: set to cfg.database.socket if database type is pgsql? DBHost = ${optionalString (cfg.database.createLocally != true) cfg.database.host} ${optionalString (cfg.database.createLocally != true) "DBPort = ${cfg.database.port}"} @@ -50,6 +51,13 @@ in services.zabbixProxy = { enable = mkEnableOption "the Zabbix Proxy"; + server = mkOption { + type = types.str; + description = '' + The IP address or hostname of the Zabbix server to connect to. + ''; + }; + package = mkOption { type = types.package; default = diff --git a/nixos/modules/services/network-filesystems/kbfs.nix b/nixos/modules/services/network-filesystems/kbfs.nix index 7b2eea3b5850..263b70d04a56 100644 --- a/nixos/modules/services/network-filesystems/kbfs.nix +++ b/nixos/modules/services/network-filesystems/kbfs.nix @@ -48,6 +48,7 @@ in { requires = [ "keybase.service" ]; after = [ "keybase.service" ]; path = [ "/run/wrappers" ]; + unitConfig.ConditionUser = "!@system"; serviceConfig = { ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p ${cfg.mountPoint}"; ExecStart = "${pkgs.kbfs}/bin/kbfsfuse ${toString cfg.extraFlags} ${cfg.mountPoint}"; diff --git a/nixos/modules/services/networking/bind.nix b/nixos/modules/services/networking/bind.nix index 7f89cff22329..06af4dbcca4e 100644 --- a/nixos/modules/services/networking/bind.nix +++ b/nixos/modules/services/networking/bind.nix @@ -33,7 +33,7 @@ let ${cfg.extraConfig} ${ concatMapStrings - ({ name, file, master ? true, slaves ? [], masters ? [] }: + ({ name, file, master ? true, slaves ? [], masters ? [], extraConfig ? "" }: '' zone "${name}" { type ${if master then "master" else "slave"}; @@ -52,6 +52,7 @@ let '' } allow-query { any; }; + ${extraConfig} }; '') cfg.zones } @@ -131,6 +132,7 @@ in file = "/var/dns/example.com"; masters = ["192.168.0.1"]; slaves = []; + extraConfig = ""; }]; }; @@ -168,7 +170,9 @@ in ###### implementation - config = mkIf config.services.bind.enable { + config = mkIf cfg.enable { + + networking.resolvconf.useLocalResolver = mkDefault true; users.users = singleton { name = bindUser; diff --git a/nixos/modules/services/networking/dnsmasq.nix b/nixos/modules/services/networking/dnsmasq.nix index 24d16046c63e..714a5903bff1 100644 --- a/nixos/modules/services/networking/dnsmasq.nix +++ b/nixos/modules/services/networking/dnsmasq.nix @@ -79,7 +79,7 @@ in ###### implementation - config = mkIf config.services.dnsmasq.enable { + config = mkIf cfg.enable { networking.nameservers = optional cfg.resolveLocalQueries "127.0.0.1"; @@ -92,6 +92,15 @@ in description = "Dnsmasq daemon user"; }; + networking.resolvconf = mkIf cfg.resolveLocalQueries { + useLocalResolver = mkDefault true; + + extraConfig = '' + dnsmasq_conf=/etc/dnsmasq-conf.conf + dnsmasq_resolv=/etc/dnsmasq-resolv.conf + ''; + }; + systemd.services.dnsmasq = { description = "Dnsmasq Daemon"; after = [ "network.target" "systemd-resolved.service" ]; diff --git a/nixos/modules/services/networking/keybase.nix b/nixos/modules/services/networking/keybase.nix index a149f16a84cb..85f52be8a6ac 100644 --- a/nixos/modules/services/networking/keybase.nix +++ b/nixos/modules/services/networking/keybase.nix @@ -26,6 +26,7 @@ in { systemd.user.services.keybase = { description = "Keybase service"; + unitConfig.ConditionUser = "!@system"; serviceConfig = { ExecStart = '' ${pkgs.keybase}/bin/keybase service --auto-forked diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix index 995f548f5cef..551636a33d25 100644 --- a/nixos/modules/services/networking/networkmanager.nix +++ b/nixos/modules/services/networking/networkmanager.nix @@ -16,7 +16,8 @@ let plugins=keyfile dhcp=${cfg.dhcp} dns=${cfg.dns} - rc-manager=${cfg.rc-manager} + # If resolvconf is disabled that means that resolv.conf is managed by some other module. + rc-manager=${if config.networking.resolvconf.enable then "resolvconf" else "unmanaged"} [keyfile] ${optionalString (cfg.unmanaged != []) @@ -176,7 +177,7 @@ in { basePackages = mkOption { type = types.attrsOf types.package; default = { inherit (pkgs) - networkmanager modemmanager wpa_supplicant + networkmanager modemmanager wpa_supplicant crda networkmanager-openvpn networkmanager-vpnc networkmanager-openconnect networkmanager-fortisslvpn networkmanager-l2tp networkmanager-iodine; }; @@ -268,25 +269,6 @@ in { ''; }; - rc-manager = mkOption { - type = types.enum [ "symlink" "file" "resolvconf" "netconfig" "unmanaged" "none" ]; - default = "resolvconf"; - description = '' - Set the <literal>resolv.conf</literal> management mode. - </para> - <para> - A description of these modes can be found in the main section of - <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html"> - https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html - </link> - or in - <citerefentry> - <refentrytitle>NetworkManager.conf</refentrytitle> - <manvolnum>5</manvolnum> - </citerefentry>. - ''; - }; - dispatcherScripts = mkOption { type = types.listOf (types.submodule { options = { @@ -513,7 +495,7 @@ in { networking = { useDHCP = false; # use mkDefault to trigger the assertion about the conflict above - wireless.enable = lib.mkDefault false; + wireless.enable = mkDefault false; }; security.polkit.extraConfig = polkitConf; diff --git a/nixos/modules/services/networking/rdnssd.nix b/nixos/modules/services/networking/rdnssd.nix index 887772f6e5f0..bccab805beeb 100644 --- a/nixos/modules/services/networking/rdnssd.nix +++ b/nixos/modules/services/networking/rdnssd.nix @@ -35,6 +35,11 @@ in config = mkIf config.services.rdnssd.enable { + assertions = [{ + assertion = config.networking.resolvconf.enable; + message = "rdnssd needs resolvconf to work (probably something sets up a static resolv.conf)"; + }]; + systemd.services.rdnssd = { description = "RDNSS daemon"; after = [ "network.target" ]; diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix index d78a54a3327b..126f5b7b527b 100644 --- a/nixos/modules/services/networking/syncthing.nix +++ b/nixos/modules/services/networking/syncthing.nix @@ -291,7 +291,7 @@ in { group = mkOption { type = types.str; - default = "nogroup"; + default = defaultUser; description = '' Syncthing will be run under this group (group will not be created if it doesn't exist. This can be your user name). @@ -372,16 +372,18 @@ in { systemd.packages = [ pkgs.syncthing ]; - users = mkIf (cfg.systemService && cfg.user == defaultUser) { - users."${defaultUser}" = + users.users = mkIf (cfg.systemService && cfg.user == defaultUser) { + "${defaultUser}" = { group = cfg.group; home = cfg.dataDir; createHome = true; uid = config.ids.uids.syncthing; description = "Syncthing daemon user"; }; + }; - groups."${defaultUser}".gid = + users.groups = mkIf (cfg.systemService && cfg.group == defaultUser) { + "${defaultUser}".gid = config.ids.gids.syncthing; }; @@ -403,18 +405,12 @@ in { Group = cfg.group; ExecStartPre = mkIf (cfg.declarative.cert != null || cfg.declarative.key != null) "+${pkgs.writers.writeBash "syncthing-copy-keys" '' - mkdir -p ${cfg.configDir} - chown ${cfg.user}:${cfg.group} ${cfg.configDir} - chmod 700 ${cfg.configDir} + install -dm700 -o ${cfg.user} -g ${cfg.group} ${cfg.configDir} ${optionalString (cfg.declarative.cert != null) '' - cp ${toString cfg.declarative.cert} ${cfg.configDir}/cert.pem - chown ${cfg.user}:${cfg.group} ${cfg.configDir}/cert.pem - chmod 400 ${cfg.configDir}/cert.pem + install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.declarative.cert} ${cfg.configDir}/cert.pem ''} ${optionalString (cfg.declarative.key != null) '' - cp ${toString cfg.declarative.key} ${cfg.configDir}/key.pem - chown ${cfg.user}:${cfg.group} ${cfg.configDir}/key.pem - chmod 400 ${cfg.configDir}/key.pem + install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.declarative.key} ${cfg.configDir}/key.pem ''} ''}" ; diff --git a/nixos/modules/services/networking/thelounge.nix b/nixos/modules/services/networking/thelounge.nix new file mode 100644 index 000000000000..b1d23372955e --- /dev/null +++ b/nixos/modules/services/networking/thelounge.nix @@ -0,0 +1,75 @@ +{ pkgs, lib, config, ... }: + +with lib; + +let + cfg = config.services.thelounge; + dataDir = "/var/lib/thelounge"; + configJsData = "module.exports = " + builtins.toJSON ( + { private = cfg.private; port = cfg.port; } // cfg.extraConfig + ); +in { + options.services.thelounge = { + enable = mkEnableOption "The Lounge web IRC client"; + + private = mkOption { + type = types.bool; + default = false; + description = '' + Make your The Lounge instance private. You will need to configure user + accounts by using the (<command>thelounge</command>) command or by adding + entries in <filename>${dataDir}/users</filename>. You might need to restart + The Lounge after making changes to the state directory. + ''; + }; + + port = mkOption { + type = types.port; + default = 9000; + description = "TCP port to listen on for http connections."; + }; + + extraConfig = mkOption { + default = {}; + type = types.attrs; + example = literalExample ''{ + reverseProxy = true; + defaults = { + name = "Your Network"; + host = "localhost"; + port = 6697; + }; + }''; + description = '' + The Lounge's <filename>config.js</filename> contents as attribute set (will be + converted to JSON to generate the configuration file). + + The options defined here will be merged to the default configuration file. + Note: In case of duplicate configuration, options from <option>extraConfig</option> have priority. + + Documentation: <link xlink:href="https://thelounge.chat/docs/server/configuration" /> + ''; + }; + }; + + config = mkIf cfg.enable { + users.users.thelounge = { + description = "thelounge service user"; + group = "thelounge"; + }; + users.groups.thelounge = {}; + systemd.services.thelounge = { + description = "The Lounge web IRC client"; + wantedBy = [ "multi-user.target" ]; + environment = { THELOUNGE_HOME = dataDir; }; + preStart = "ln -sf ${pkgs.writeText "config.js" configJsData} ${dataDir}/config.js"; + serviceConfig = { + User = "thelounge"; + StateDirectory = baseNameOf dataDir; + ExecStart = "${pkgs.thelounge}/bin/thelounge start"; + }; + }; + + environment.systemPackages = [ pkgs.thelounge ]; + }; +} diff --git a/nixos/modules/services/networking/unbound.nix b/nixos/modules/services/networking/unbound.nix index 1a35979ad44c..3cf82e8839bb 100644 --- a/nixos/modules/services/networking/unbound.nix +++ b/nixos/modules/services/networking/unbound.nix @@ -101,6 +101,8 @@ in isSystemUser = true; }; + networking.resolvconf.useLocalResolver = mkDefault true; + systemd.services.unbound = { description = "Unbound recursive Domain Name Server"; after = [ "network.target" ]; diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix index 0bd9edf4a41c..63e59e7c8fac 100644 --- a/nixos/modules/services/networking/wpa_supplicant.nix +++ b/nixos/modules/services/networking/wpa_supplicant.nix @@ -204,6 +204,7 @@ in { environment.systemPackages = [ pkgs.wpa_supplicant ]; services.dbus.packages = [ pkgs.wpa_supplicant ]; + services.udev.packages = [ pkgs.crda ]; # FIXME: start a separate wpa_supplicant instance per interface. systemd.services.wpa_supplicant = let diff --git a/nixos/modules/services/security/sshguard.nix b/nixos/modules/services/security/sshguard.nix index 3892cd5c72b8..25cec5b5b105 100644 --- a/nixos/modules/services/security/sshguard.nix +++ b/nixos/modules/services/security/sshguard.nix @@ -107,8 +107,6 @@ in { path = with pkgs; [ iptables ipset iproute systemd ]; postStart = '' - ${pkgs.ipset}/bin/ipset -quiet create -exist sshguard4 hash:ip family inet - ${pkgs.ipset}/bin/ipset -quiet create -exist sshguard6 hash:ip family inet6 ${pkgs.iptables}/bin/iptables -I INPUT -m set --match-set sshguard4 src -j DROP ${pkgs.iptables}/bin/ip6tables -I INPUT -m set --match-set sshguard6 src -j DROP ''; diff --git a/nixos/modules/services/security/tor.nix b/nixos/modules/services/security/tor.nix index 6f4852c3ba1a..abdc0cd78b4d 100644 --- a/nixos/modules/services/security/tor.nix +++ b/nixos/modules/services/security/tor.nix @@ -81,7 +81,7 @@ let ${optionalString (elem cfg.relay.role ["bridge" "private-bridge"]) '' BridgeRelay 1 - ServerTransportPlugin ${concatStringsSep "," cfg.relay.bridgeTransports} exec ${obfs4}/bin/obfs4proxy managed + ServerTransportPlugin ${concatStringsSep "," cfg.relay.bridgeTransports} exec ${pkgs.obfs4}/bin/obfs4proxy managed ExtORPort auto ${optionalString (cfg.relay.role == "private-bridge") '' ExtraInfoStatistics 0 diff --git a/nixos/modules/services/system/nscd.conf b/nixos/modules/services/system/nscd.conf index 603a5d01acce..2b7523a7346d 100644 --- a/nixos/modules/services/system/nscd.conf +++ b/nixos/modules/services/system/nscd.conf @@ -7,46 +7,28 @@ # is not aware of the path in which the nss modules live. As a workaround, we # have `enable-cache yes` with an explicit ttl of 0 server-user nscd -threads 1 -paranoia no -debug-level 0 enable-cache passwd yes positive-time-to-live passwd 0 negative-time-to-live passwd 0 -suggested-size passwd 211 -check-files passwd yes -persistent passwd no shared passwd yes enable-cache group yes positive-time-to-live group 0 negative-time-to-live group 0 -suggested-size group 211 -check-files group yes -persistent group no shared group yes enable-cache netgroup yes positive-time-to-live netgroup 0 negative-time-to-live netgroup 0 -suggested-size netgroup 211 -check-files netgroup yes -persistent netgroup no shared netgroup yes enable-cache hosts yes positive-time-to-live hosts 600 negative-time-to-live hosts 0 -suggested-size hosts 211 -check-files hosts yes -persistent hosts no shared hosts yes enable-cache services yes positive-time-to-live services 0 negative-time-to-live services 0 -suggested-size services 211 -check-files services yes -persistent services no shared services yes diff --git a/nixos/modules/services/system/nscd.nix b/nixos/modules/services/system/nscd.nix index fd1570d11980..e11f7e049d8f 100644 --- a/nixos/modules/services/system/nscd.nix +++ b/nixos/modules/services/system/nscd.nix @@ -39,11 +39,6 @@ in config = mkIf cfg.enable { environment.etc."nscd.conf".text = cfg.config; - users.users.nscd = - { isSystemUser = true; - description = "Name service cache daemon user"; - }; - systemd.services.nscd = { description = "Name Service Cache Daemon"; @@ -51,22 +46,23 @@ in environment = { LD_LIBRARY_PATH = nssModulesPath; }; - preStart = - '' - mkdir -m 0755 -p /run/nscd - rm -f /run/nscd/nscd.pid - mkdir -m 0755 -p /var/db/nscd - ''; - restartTriggers = [ config.environment.etc.hosts.source config.environment.etc."nsswitch.conf".source config.environment.etc."nscd.conf".source ]; + # We use DynamicUser because in default configurations nscd doesn't + # create any files that need to survive restarts. However, in some + # configurations, nscd needs to be started as root; it will drop + # privileges after all the NSS modules have read their configuration + # files. So prefix the ExecStart command with "!" to prevent systemd + # from dropping privileges early. See ExecStart in systemd.service(5). serviceConfig = - { ExecStart = "@${pkgs.glibc.bin}/sbin/nscd nscd"; + { ExecStart = "!@${pkgs.glibc.bin}/sbin/nscd nscd"; Type = "forking"; + DynamicUser = true; + RuntimeDirectory = "nscd"; PIDFile = "/run/nscd/nscd.pid"; Restart = "always"; ExecReload = @@ -75,15 +71,6 @@ in "${pkgs.glibc.bin}/sbin/nscd --invalidate hosts" ]; }; - - # Urgggggh... Nscd forks before opening its socket and writing - # its pid. So wait until it's ready. - postStart = - '' - while ! ${pkgs.glibc.bin}/sbin/nscd -g > /dev/null; do - sleep 0.2 - done - ''; }; }; diff --git a/nixos/modules/services/web-apps/mediawiki.nix b/nixos/modules/services/web-apps/mediawiki.nix new file mode 100644 index 000000000000..5bd5977e592b --- /dev/null +++ b/nixos/modules/services/web-apps/mediawiki.nix @@ -0,0 +1,473 @@ +{ config, pkgs, lib, ... }: + +let + + inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption; + inherit (lib) concatStringsSep literalExample mapAttrsToList optional optionals optionalString types; + + cfg = config.services.mediawiki; + fpm = config.services.phpfpm.pools.mediawiki; + user = "mediawiki"; + group = config.services.httpd.group; + cacheDir = "/var/cache/mediawiki"; + stateDir = "/var/lib/mediawiki"; + + pkg = pkgs.stdenv.mkDerivation rec { + pname = "mediawiki-full"; + version = src.version; + src = cfg.package; + + installPhase = '' + mkdir -p $out + cp -r * $out/ + + rm -rf $out/share/mediawiki/skins/* + rm -rf $out/share/mediawiki/extensions/* + + ${concatStringsSep "\n" (mapAttrsToList (k: v: '' + ln -s ${v} $out/share/mediawiki/skins/${k} + '') cfg.skins)} + + ${concatStringsSep "\n" (mapAttrsToList (k: v: '' + ln -s ${v} $out/share/mediawiki/extensions/${k} + '') cfg.extensions)} + ''; + }; + + mediawikiScripts = pkgs.runCommand "mediawiki-scripts" { + buildInputs = [ pkgs.makeWrapper ]; + preferLocalBuild = true; + } '' + mkdir -p $out/bin + for i in changePassword.php createAndPromote.php userOptions.php edit.php nukePage.php update.php; do + makeWrapper ${pkgs.php}/bin/php $out/bin/mediawiki-$(basename $i .php) \ + --set MEDIAWIKI_CONFIG ${mediawikiConfig} \ + --add-flags ${pkg}/share/mediawiki/maintenance/$i + done + ''; + + mediawikiConfig = pkgs.writeText "LocalSettings.php" '' + <?php + # Protect against web entry + if ( !defined( 'MEDIAWIKI' ) ) { + exit; + } + + $wgSitename = "${cfg.name}"; + $wgMetaNamespace = false; + + ## The URL base path to the directory containing the wiki; + ## defaults for all runtime URL paths are based off of this. + ## For more information on customizing the URLs + ## (like /w/index.php/Page_title to /wiki/Page_title) please see: + ## https://www.mediawiki.org/wiki/Manual:Short_URL + $wgScriptPath = ""; + + ## The protocol and server name to use in fully-qualified URLs + $wgServer = "${if cfg.virtualHost.enableSSL then "https" else "http"}://${cfg.virtualHost.hostName}"; + + ## The URL path to static resources (images, scripts, etc.) + $wgResourceBasePath = $wgScriptPath; + + ## The URL path to the logo. Make sure you change this from the default, + ## or else you'll overwrite your logo when you upgrade! + $wgLogo = "$wgResourceBasePath/resources/assets/wiki.png"; + + ## UPO means: this is also a user preference option + + $wgEnableEmail = true; + $wgEnableUserEmail = true; # UPO + + $wgEmergencyContact = "${if cfg.virtualHost.adminAddr != null then cfg.virtualHost.adminAddr else config.services.httpd.adminAddr}"; + $wgPasswordSender = $wgEmergencyContact; + + $wgEnotifUserTalk = false; # UPO + $wgEnotifWatchlist = false; # UPO + $wgEmailAuthentication = true; + + ## Database settings + $wgDBtype = "${cfg.database.type}"; + $wgDBserver = "${cfg.database.host}:${if cfg.database.socket != null then cfg.database.socket else toString cfg.database.port}"; + $wgDBname = "${cfg.database.name}"; + $wgDBuser = "${cfg.database.user}"; + ${optionalString (cfg.database.passwordFile != null) "$wgDBpassword = file_get_contents(\"${cfg.database.passwordFile}\");"} + + ${optionalString (cfg.database.type == "mysql" && cfg.database.tablePrefix != null) '' + # MySQL specific settings + $wgDBprefix = "${cfg.database.tablePrefix}"; + ''} + + ${optionalString (cfg.database.type == "mysql") '' + # MySQL table options to use during installation or update + $wgDBTableOptions = "ENGINE=InnoDB, DEFAULT CHARSET=binary"; + ''} + + ## Shared memory settings + $wgMainCacheType = CACHE_NONE; + $wgMemCachedServers = []; + + ${optionalString (cfg.uploadsDir != null) '' + $wgEnableUploads = true; + $wgUploadDirectory = "${cfg.uploadsDir}"; + ''} + + $wgUseImageMagick = true; + $wgImageMagickConvertCommand = "${pkgs.imagemagick}/bin/convert"; + + # InstantCommons allows wiki to use images from https://commons.wikimedia.org + $wgUseInstantCommons = false; + + # Periodically send a pingback to https://www.mediawiki.org/ with basic data + # about this MediaWiki instance. The Wikimedia Foundation shares this data + # with MediaWiki developers to help guide future development efforts. + $wgPingback = true; + + ## If you use ImageMagick (or any other shell command) on a + ## Linux server, this will need to be set to the name of an + ## available UTF-8 locale + $wgShellLocale = "C.UTF-8"; + + ## Set $wgCacheDirectory to a writable directory on the web server + ## to make your wiki go slightly faster. The directory should not + ## be publically accessible from the web. + $wgCacheDirectory = "${cacheDir}"; + + # Site language code, should be one of the list in ./languages/data/Names.php + $wgLanguageCode = "en"; + + $wgSecretKey = file_get_contents("${stateDir}/secret.key"); + + # Changing this will log out all existing sessions. + $wgAuthenticationTokenVersion = ""; + + ## For attaching licensing metadata to pages, and displaying an + ## appropriate copyright notice / icon. GNU Free Documentation + ## License and Creative Commons licenses are supported so far. + $wgRightsPage = ""; # Set to the title of a wiki page that describes your license/copyright + $wgRightsUrl = ""; + $wgRightsText = ""; + $wgRightsIcon = ""; + + # Path to the GNU diff3 utility. Used for conflict resolution. + $wgDiff = "${pkgs.diffutils}/bin/diff"; + $wgDiff3 = "${pkgs.diffutils}/bin/diff3"; + + # Enabled skins. + ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadSkin('${k}');") cfg.skins)} + + # Enabled extensions. + ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadExtension('${k}');") cfg.extensions)} + + + # End of automatically generated settings. + # Add more configuration options below. + + ${cfg.extraConfig} + ''; + +in +{ + # interface + options = { + services.mediawiki = { + + enable = mkEnableOption "MediaWiki"; + + package = mkOption { + type = types.package; + default = pkgs.mediawiki; + description = "Which MediaWiki package to use."; + }; + + name = mkOption { + default = "MediaWiki"; + example = "Foobar Wiki"; + description = "Name of the wiki."; + }; + + uploadsDir = mkOption { + type = types.nullOr types.path; + default = "${stateDir}/uploads"; + description = '' + This directory is used for uploads of pictures. The directory passed here is automatically + created and permissions adjusted as required. + ''; + }; + + passwordFile = mkOption { + type = types.path; + description = "A file containing the initial password for the admin user."; + example = "/run/keys/mediawiki-password"; + }; + + skins = mkOption { + default = {}; + type = types.attrsOf types.path; + description = '' + List of paths whose content is copied to the 'skins' + subdirectory of the MediaWiki installation. + ''; + }; + + extensions = mkOption { + default = {}; + type = types.attrsOf types.path; + description = '' + List of paths whose content is copied to the 'extensions' + subdirectory of the MediaWiki installation. + ''; + }; + + database = { + type = mkOption { + type = types.enum [ "mysql" "postgres" "sqlite" "mssql" "oracle" ]; + default = "mysql"; + description = "Database engine to use. MySQL/MariaDB is the database of choice by MediaWiki developers."; + }; + + host = mkOption { + type = types.str; + default = "localhost"; + description = "Database host address."; + }; + + port = mkOption { + type = types.port; + default = 3306; + description = "Database host port."; + }; + + name = mkOption { + type = types.str; + default = "mediawiki"; + description = "Database name."; + }; + + user = mkOption { + type = types.str; + default = "mediawiki"; + description = "Database user."; + }; + + passwordFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/keys/mediawiki-dbpassword"; + description = '' + A file containing the password corresponding to + <option>database.user</option>. + ''; + }; + + tablePrefix = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + If you only have access to a single database and wish to install more than + one version of MediaWiki, or have other applications that also use the + database, you can give the table names a unique prefix to stop any naming + conflicts or confusion. + See <link xlink:href='https://www.mediawiki.org/wiki/Manual:$wgDBprefix'/>. + ''; + }; + + socket = mkOption { + type = types.nullOr types.path; + default = if cfg.database.createLocally then "/run/mysqld/mysqld.sock" else null; + defaultText = "/run/mysqld/mysqld.sock"; + description = "Path to the unix socket file to use for authentication."; + }; + + createLocally = mkOption { + type = types.bool; + default = cfg.database.type == "mysql"; + defaultText = "true"; + description = '' + Create the database and database user locally. + This currently only applies if database type "mysql" is selected. + ''; + }; + }; + + virtualHost = mkOption { + type = types.submodule ({ + options = import ../web-servers/apache-httpd/per-server-options.nix { + inherit lib; + forMainServer = false; + }; + }); + example = literalExample '' + { + hostName = "mediawiki.example.org"; + enableSSL = true; + adminAddr = "webmaster@example.org"; + sslServerCert = "/var/lib/acme/mediawiki.example.org/full.pem"; + sslServerKey = "/var/lib/acme/mediawiki.example.org/key.pem"; + } + ''; + description = '' + Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>. + See <xref linkend="opt-services.httpd.virtualHosts"/> for further information. + ''; + }; + + poolConfig = mkOption { + type = types.lines; + default = '' + pm = dynamic + pm.max_children = 32 + pm.start_servers = 2 + pm.min_spare_servers = 2 + pm.max_spare_servers = 4 + pm.max_requests = 500 + ''; + description = '' + Options for MediaWiki's PHP pool. See the documentation on <literal>php-fpm.conf</literal> + for details on configuration directives. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + description = '' + Any additional text to be appended to MediaWiki's + LocalSettings.php configuration file. For configuration + settings, see <link xlink:href="https://www.mediawiki.org/wiki/Manual:Configuration_settings"/>. + ''; + default = ""; + example = '' + $wgEnableEmail = false; + ''; + }; + + }; + }; + + # implementation + config = mkIf cfg.enable { + + assertions = [ + { assertion = cfg.database.createLocally -> cfg.database.type == "mysql"; + message = "services.mediawiki.createLocally is currently only supported for database type 'mysql'"; + } + { assertion = cfg.database.createLocally -> cfg.database.user == user; + message = "services.mediawiki.database.user must be set to ${user} if services.mediawiki.database.createLocally is set true"; + } + { assertion = cfg.database.createLocally -> cfg.database.socket != null; + message = "services.mediawiki.database.socket must be set if services.mediawiki.database.createLocally is set to true"; + } + { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null; + message = "a password cannot be specified if services.mediawiki.database.createLocally is set to true"; + } + ]; + + services.mediawiki.skins = { + MonoBook = "${cfg.package}/share/mediawiki/skins/MonoBook"; + Timeless = "${cfg.package}/share/mediawiki/skins/Timeless"; + Vector = "${cfg.package}/share/mediawiki/skins/Vector"; + }; + + services.mysql = mkIf cfg.database.createLocally { + enable = true; + package = mkDefault pkgs.mariadb; + ensureDatabases = [ cfg.database.name ]; + ensureUsers = [ + { name = cfg.database.user; + ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; }; + } + ]; + }; + + services.phpfpm.pools.mediawiki = { + listen = "/run/phpfpm/mediawiki.sock"; + extraConfig = '' + listen.owner = ${config.services.httpd.user} + listen.group = ${config.services.httpd.group} + user = ${user} + group = ${group} + + env[MEDIAWIKI_CONFIG] = ${mediawikiConfig} + + ${cfg.poolConfig} + ''; + }; + + services.httpd = { + enable = true; + adminAddr = mkDefault cfg.virtualHost.adminAddr; + extraModules = [ "proxy_fcgi" ]; + virtualHosts = [ (mkMerge [ + cfg.virtualHost { + documentRoot = mkForce "${pkg}/share/mediawiki"; + extraConfig = '' + <Directory "${pkg}/share/mediawiki"> + <FilesMatch "\.php$"> + <If "-f %{REQUEST_FILENAME}"> + SetHandler "proxy:unix:${fpm.listen}|fcgi://localhost/" + </If> + </FilesMatch> + + Require all granted + DirectoryIndex index.php + AllowOverride All + </Directory> + '' + optionalString (cfg.uploadsDir != null) '' + Alias "/images" "${cfg.uploadsDir}" + <Directory "${cfg.uploadsDir}"> + Require all granted + </Directory> + ''; + } + ]) ]; + }; + + systemd.tmpfiles.rules = [ + "d '${stateDir}' 0750 ${user} ${group} - -" + "d '${cacheDir}' 0750 ${user} ${group} - -" + ] ++ optionals (cfg.uploadsDir != null) [ + "d '${cfg.uploadsDir}' 0750 ${user} ${group} - -" + "Z '${cfg.uploadsDir}' 0750 ${user} ${group} - -" + ]; + + systemd.services.mediawiki-init = { + wantedBy = [ "multi-user.target" ]; + before = [ "phpfpm-mediawiki.service" ]; + after = optional cfg.database.createLocally "mysql.service"; + script = '' + if ! test -e "${stateDir}/secret.key"; then + tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c 64 > ${stateDir}/secret.key + fi + + echo "exit( wfGetDB( DB_MASTER )->tableExists( 'user' ) ? 1 : 0 );" | \ + ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/eval.php --conf ${mediawikiConfig} && \ + ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/install.php \ + --confpath /tmp \ + --scriptpath / \ + --dbserver ${cfg.database.host}${optionalString (cfg.database.socket != null) ":${cfg.database.socket}"} \ + --dbport ${toString cfg.database.port} \ + --dbname ${cfg.database.name} \ + ${optionalString (cfg.database.tablePrefix != null) "--dbprefix ${cfg.database.tablePrefix}"} \ + --dbuser ${cfg.database.user} \ + ${optionalString (cfg.database.passwordFile != null) "--dbpassfile ${cfg.database.passwordFile}"} \ + --passfile ${cfg.passwordFile} \ + ${cfg.name} \ + admin + + ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/update.php --conf ${mediawikiConfig} --quick + ''; + + serviceConfig = { + Type = "oneshot"; + User = user; + Group = group; + PrivateTmp = true; + }; + }; + + systemd.services.httpd.after = optional (cfg.database.createLocally && cfg.database.type == "mysql") "mysql.service"; + + users.users.${user}.group = group; + + environment.systemPackages = [ mediawikiScripts ]; + }; +} diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix index fa9a36d11892..a0214a75d93e 100644 --- a/nixos/modules/services/web-apps/nextcloud.nix +++ b/nixos/modules/services/web-apps/nextcloud.nix @@ -297,8 +297,23 @@ in { systemd.services = { "nextcloud-setup" = let + c = cfg.config; + writePhpArrary = a: "[${concatMapStringsSep "," (val: ''"${toString val}"'') a}]"; overrideConfig = pkgs.writeText "nextcloud-config.php" '' <?php + ${optionalString (c.dbpassFile != null) '' + function nix_read_pwd() { + $file = "${c.dbpassFile}"; + if (!file_exists($file)) { + throw new \RuntimeException(sprintf( + "Cannot start Nextcloud, dbpass file %s set by NixOS doesn't exist!", + $file + )); + } + + return trim(file_get_contents($file)); + } + ''} $CONFIG = [ 'apps_paths' => [ [ 'path' => '${cfg.home}/apps', 'url' => '/apps', 'writable' => false ], @@ -309,19 +324,27 @@ in { ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"} 'log_type' => 'syslog', 'log_level' => '${builtins.toString cfg.logLevel}', - ${optionalString (cfg.config.overwriteProtocol != null) "'overwriteprotocol' => '${cfg.config.overwriteProtocol}',"} + ${optionalString (c.overwriteProtocol != null) "'overwriteprotocol' => '${c.overwriteProtocol}',"} + ${optionalString (c.dbname != null) "'dbname' => '${c.dbname}',"} + ${optionalString (c.dbhost != null) "'dbhost' => '${c.dbhost}',"} + ${optionalString (c.dbport != null) "'dbport' => '${toString c.dbport}',"} + ${optionalString (c.dbuser != null) "'dbuser' => '${c.dbuser}',"} + ${optionalString (c.dbtableprefix != null) "'dbtableprefix' => '${toString c.dbtableprefix}',"} + ${optionalString (c.dbpass != null) "'dbpassword' => '${c.dbpass}',"} + ${optionalString (c.dbpassFile != null) "'dbpassword' => nix_read_pwd(),"} + 'dbtype' => '${c.dbtype}', + 'trusted_domains' => ${writePhpArrary ([ cfg.hostName ] ++ c.extraTrustedDomains)}, ]; ''; 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; + adminpass = if c.adminpassFile != null + then ''"$(<"${toString c.adminpassFile}")"'' + else ''"${toString c.adminpass}"''; installFlags = concatStringsSep " \\\n " (mapAttrsToList (k: v: "${k} ${toString v}") { "--database" = ''"${c.dbtype}"''; diff --git a/nixos/modules/services/web-apps/nextcloud.xml b/nixos/modules/services/web-apps/nextcloud.xml index d78d866086a6..d66e0f0c2997 100644 --- a/nixos/modules/services/web-apps/nextcloud.xml +++ b/nixos/modules/services/web-apps/nextcloud.xml @@ -42,10 +42,12 @@ services.postgresql = { <link linkend="opt-services.postgresql.enable">enable</link> = true; - <link linkend="opt-services.postgresql.initialScript">initialScript</link> = pkgs.writeText "psql-init" '' - CREATE ROLE nextcloud WITH LOGIN; - CREATE DATABASE nextcloud WITH OWNER nextcloud; - ''; + <link linkend="opt-services.postgresql.ensureDatabases">ensureDatabases</link> = [ "nextcloud" ]; + <link linkend="opt-services.postgresql.ensureUsers">ensureUsers</link> = [ + { name = "nextcloud"; + ensurePermissions."DATABASE nextcloud" = "ALL PRIVILEGES"; + } + ]; }; # ensure that postgres is running *before* running the setup @@ -63,17 +65,22 @@ are used internally to configure an HTTP server using <literal><link xlink:href="https://php-fpm.org/">PHP-FPM</link></literal> and <literal>nginx</literal>. The <literal>config</literal> attribute set is - used for the <literal>config.php</literal> which is used for the - application's configuration. <emphasis>Beware: this isn't entirely pure - since the config is modified by the application's runtime!</emphasis> + used by the imperative installer and all values are written to an additional file + to ensure that changes can be applied by changing the module's options. </para> <para> - In case the application serves multiple hosts (those are checked with + In case the application serves multiple domains (those are checked with <literal><link xlink:href="http://php.net/manual/en/reserved.variables.server.php">$_SERVER['HTTP_HOST']</link></literal>) - those can be added using + it's needed to add them to <literal><link linkend="opt-services.nextcloud.config.extraTrustedDomains">services.nextcloud.config.extraTrustedDomains</link></literal>. </para> + + <para> + Auto updates for Nextcloud apps can be enabled using + <literal><link linkend="opt-services.nextcloud.autoUpdateApps.enable">services.nextcloud.autoUpdateApps</link></literal>. +</para> + </section> <section xml:id="module-services-nextcloud-pitfalls-during-upgrade"> <title>Pitfalls</title> @@ -87,35 +94,24 @@ </para> <para> - Right now changes to the <literal>services.nextcloud.config</literal> - attribute set won't take effect after the first install (except - <literal><link linkend="opt-services.nextcloud.config.extraTrustedDomains">services.nextcloud.config.extraTrustedDomains</link></literal>) - since the actual configuration file is generated by the NextCloud installer - which also sets up critical parts such as the database structure. + All configuration parameters are also stored in + <literal>/var/lib/nextcloud/config/override.config.php</literal> which is generated by + the module and linked from the store to ensure that all values from <literal>config.php</literal> + can be modified by the module. + However <literal>config.php</literal> manages the application's state and shouldn't be touched + manually because of that. </para> - <para> - <emphasis>Warning: don't delete <literal>config.php</literal>! This file + <warning> + <para>Don't delete <literal>config.php</literal>! This file tracks the application's state and a deletion can cause unwanted - side-effects!</emphasis> - </para> + side-effects!</para> + </warning> - <para> - <emphasis>Warning: don't rerun <literal>nextcloud-occ + <warning> + <para>Don't rerun <literal>nextcloud-occ maintenance:install</literal>! This command tries to install the application - and can cause unwanted side-effects!</emphasis> - </para> - - <para> - The issues are known and reported in - <link xlink:href="https://github.com/NixOS/nixpkgs/issues/49783">#49783</link>, - for now it's unfortunately necessary to manually work around these issues. - </para> - - <para> - Right now app installation and configuration is done imperatively in the nextcloud web ui or via the <literal>nextcloud-occ</literal> command line utility. - You can activate auto updates for your apps via - <literal><link linkend="opt-services.nextcloud.autoUpdateApps.enable">services.nextcloud.autoUpdateApps</link></literal>. - </para> + and can cause unwanted side-effects!</para> + </warning> </section> </chapter> diff --git a/nixos/modules/services/web-apps/tt-rss.nix b/nixos/modules/services/web-apps/tt-rss.nix index b882f6c2ae7e..1bd9de93735d 100644 --- a/nixos/modules/services/web-apps/tt-rss.nix +++ b/nixos/modules/services/web-apps/tt-rss.nix @@ -16,6 +16,9 @@ let poolName = "tt-rss"; + mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql"; + pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql"; + tt-rss-config = pkgs.writeText "config.php" '' <?php @@ -200,6 +203,12 @@ let and 3306 for pgsql and mysql respectively). ''; }; + + createLocally = mkOption { + type = types.bool; + default = true; + description = "Create the database and database user locally."; + }; }; auth = { @@ -551,9 +560,13 @@ let }; }; - systemd.services.tt-rss = let - dbService = if cfg.database.type == "pgsql" then "postgresql.service" else "mysql.service"; - in { + systemd.tmpfiles.rules = [ + "d '${cfg.root}' 0755 ${cfg.user} tt_rss - -" + "Z '${cfg.root}' 0755 ${cfg.user} tt_rss - -" + ]; + + systemd.services.tt-rss = + { description = "Tiny Tiny RSS feeds update daemon"; @@ -562,14 +575,14 @@ let if cfg.database.type == "pgsql" then '' ${optionalString (cfg.database.password != null) "PGPASSWORD=${cfg.database.password}"} \ ${optionalString (cfg.database.passwordFile != null) "PGPASSWORD=$(cat ${cfg.database.passwordFile})"} \ - ${pkgs.sudo}/bin/sudo -u ${cfg.user} ${config.services.postgresql.package}/bin/psql \ + ${config.services.postgresql.package}/bin/psql \ -U ${cfg.database.user} \ ${optionalString (cfg.database.host != null) "-h ${cfg.database.host} --port ${toString dbPort}"} \ -c '${e}' \ ${cfg.database.name}'' else if cfg.database.type == "mysql" then '' - echo '${e}' | ${pkgs.sudo}/bin/sudo -u ${cfg.user} ${config.services.mysql.package}/bin/mysql \ + echo '${e}' | ${config.services.mysql.package}/bin/mysql \ -u ${cfg.database.user} \ ${optionalString (cfg.database.password != null) "-p${cfg.database.password}"} \ ${optionalString (cfg.database.host != null) "-h ${cfg.database.host} -P ${toString dbPort}"} \ @@ -579,7 +592,6 @@ let in '' rm -rf "${cfg.root}/*" - mkdir -m 755 -p "${cfg.root}" cp -r "${pkgs.tt-rss}/"* "${cfg.root}" ${optionalString (cfg.pluginPackages != []) '' for plugin in ${concatStringsSep " " cfg.pluginPackages}; do @@ -592,19 +604,10 @@ let done ''} ln -sf "${tt-rss-config}" "${cfg.root}/config.php" - chown -R "${cfg.user}" "${cfg.root}" chmod -R 755 "${cfg.root}" '' + (optionalString (cfg.database.type == "pgsql") '' - ${optionalString (cfg.database.host == null && cfg.database.password == null) '' - if ! [ -e ${cfg.root}/.db-created ]; then - ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} ${config.services.postgresql.package}/bin/createuser ${cfg.database.user} - ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} ${config.services.postgresql.package}/bin/createdb -O ${cfg.database.user} ${cfg.database.name} - touch ${cfg.root}/.db-created - fi - ''} - exists=$(${callSql "select count(*) > 0 from pg_tables where tableowner = user"} \ | tail -n+3 | head -n-2 | sed -e 's/[ \n\t]*//') @@ -628,18 +631,18 @@ let serviceConfig = { User = "${cfg.user}"; + Group = "tt_rss"; ExecStart = "${pkgs.php}/bin/php ${cfg.root}/update.php --daemon"; StandardOutput = "syslog"; StandardError = "syslog"; - PermissionsStartOnly = true; }; wantedBy = [ "multi-user.target" ]; - requires = ["${dbService}"]; - after = ["network.target" "${dbService}"]; + requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; + after = [ "network.target" ] ++ optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; }; - services.mysql = optionalAttrs (cfg.database.type == "mysql") { + services.mysql = mkIf mysqlLocal { enable = true; package = mkDefault pkgs.mysql; ensureDatabases = [ cfg.database.name ]; @@ -653,17 +656,22 @@ let ]; }; - services.postgresql = optionalAttrs (cfg.database.type == "pgsql") { + services.postgresql = mkIf pgsqlLocal { enable = mkDefault true; + ensureDatabases = [ cfg.database.name ]; + ensureUsers = [ + { name = cfg.user; + ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; }; + } + ]; }; - users = optionalAttrs (cfg.user == "tt_rss") { - users.tt_rss = { - description = "tt-rss service user"; - isSystemUser = true; - group = "tt_rss"; - }; - groups.tt_rss = {}; + users.users.tt_rss = optionalAttrs (cfg.user == "tt_rss") { + description = "tt-rss service user"; + isSystemUser = true; + group = "tt_rss"; }; + + users.groups.tt_rss = {}; }; } diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix index bf99f6c132af..ea9476a7c915 100644 --- a/nixos/modules/services/web-servers/apache-httpd/default.nix +++ b/nixos/modules/services/web-servers/apache-httpd/default.nix @@ -21,10 +21,9 @@ let else [{ip = "*"; port = 80;}]; getListen = cfg: - let list = (lib.optional (cfg.port != 0) {ip = "*"; port = cfg.port;}) ++ cfg.listen; - in if list == [] - then defaultListen cfg - else list; + if cfg.listen == [] + then defaultListen cfg + else cfg.listen; listenToString = l: "${l.ip}:${toString l.port}"; @@ -638,7 +637,7 @@ in message = "SSL is enabled for httpd, but sslServerCert and/or sslServerKey haven't been specified."; } ]; - warnings = map (cfg: ''apache-httpd's port option is deprecated. Use listen = [{/*ip = "*"; */ port = ${toString cfg.port};}]; instead'' ) (lib.filter (cfg: cfg.port != 0) allHosts); + warnings = map (cfg: "apache-httpd's extraSubservices option is deprecated. Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.") (lib.filter (cfg: cfg.extraSubservices != []) allHosts); users.users = optionalAttrs (mainCfg.user == "wwwrun") (singleton { name = "wwwrun"; @@ -672,7 +671,7 @@ in wantedBy = [ "multi-user.target" ]; wants = [ "keys.target" ]; - after = [ "network.target" "fs.target" "postgresql.service" "keys.target" ]; + after = [ "network.target" "fs.target" "keys.target" ]; path = [ httpd pkgs.coreutils pkgs.gnugrep ] diff --git a/nixos/modules/services/web-servers/apache-httpd/mediawiki.nix b/nixos/modules/services/web-servers/apache-httpd/mediawiki.nix deleted file mode 100644 index 6234478014ce..000000000000 --- a/nixos/modules/services/web-servers/apache-httpd/mediawiki.nix +++ /dev/null @@ -1,349 +0,0 @@ -{ config, lib, pkgs, serverInfo, php, ... }: - -with lib; - -let - - httpd = serverInfo.serverConfig.package; - - version24 = !versionOlder httpd.version "2.4"; - - allGranted = if version24 then '' - Require all granted - '' else '' - Order allow,deny - Allow from all - ''; - - mediawikiConfig = pkgs.writeText "LocalSettings.php" - '' - <?php - # Copied verbatim from the default (generated) LocalSettings.php. - if( defined( 'MW_INSTALL_PATH' ) ) { - $IP = MW_INSTALL_PATH; - } else { - $IP = dirname( __FILE__ ); - } - - $path = array( $IP, "$IP/includes", "$IP/languages" ); - set_include_path( implode( PATH_SEPARATOR, $path ) . PATH_SEPARATOR . get_include_path() ); - - require_once( "$IP/includes/DefaultSettings.php" ); - - if ( $wgCommandLineMode ) { - if ( isset( $_SERVER ) && array_key_exists( 'REQUEST_METHOD', $_SERVER ) ) { - die( "This script must be run from the command line\n" ); - } - } - - $wgScriptPath = "${config.urlPrefix}"; - - # We probably need to set $wgSecretKey and $wgCacheEpoch. - - # Paths to external programs. - $wgDiff3 = "${pkgs.diffutils}/bin/diff3"; - $wgDiff = "${pkgs.diffutils}/bin/diff"; - $wgImageMagickConvertCommand = "${pkgs.imagemagick.out}/bin/convert"; - - #$wgDebugLogFile = "/tmp/mediawiki_debug_log.txt"; - - # Database configuration. - $wgDBtype = "${config.dbType}"; - $wgDBserver = "${config.dbServer}"; - $wgDBuser = "${config.dbUser}"; - $wgDBpassword = "${config.dbPassword}"; - $wgDBname = "${config.dbName}"; - - # E-mail. - $wgEmergencyContact = "${config.emergencyContact}"; - $wgPasswordSender = "${config.passwordSender}"; - - $wgSitename = "${config.siteName}"; - - ${optionalString (config.logo != "") '' - $wgLogo = "${config.logo}"; - ''} - - ${optionalString (config.articleUrlPrefix != "") '' - $wgArticlePath = "${config.articleUrlPrefix}/$1"; - ''} - - ${optionalString config.enableUploads '' - $wgEnableUploads = true; - $wgUploadDirectory = "${config.uploadDir}"; - ''} - - ${optionalString (config.defaultSkin != "") '' - $wgDefaultSkin = "${config.defaultSkin}"; - ''} - - ${config.extraConfig} - ?> - ''; - - # Unpack Mediawiki and put the config file in its root directory. - mediawikiRoot = pkgs.stdenv.mkDerivation rec { - name= "mediawiki-1.31.1"; - - src = pkgs.fetchurl { - url = "https://releases.wikimedia.org/mediawiki/1.31/${name}.tar.gz"; - sha256 = "13x48clij21cmysjkpnx68vggchrdasqp7b290j87xlfgjhdhnnf"; - }; - - skins = config.skins; - extensions = config.extensions; - - buildPhase = - '' - for skin in $skins; do - cp -prvd $skin/* skins/ - done - for extension in $extensions; do - cp -prvd $extension/* extensions/ - done - ''; # */ - - installPhase = - '' - mkdir -p $out - cp -r * $out - cp ${mediawikiConfig} $out/LocalSettings.php - sed -i \ - -e 's|/bin/bash|${pkgs.bash}/bin/bash|g' \ - -e 's|/usr/bin/timeout|${pkgs.coreutils}/bin/timeout|g' \ - $out/includes/shell/limit.sh \ - $out/includes/GlobalFunctions.php - ''; - }; - - mediawikiScripts = pkgs.runCommand "mediawiki-${config.id}-scripts" { - buildInputs = [ pkgs.makeWrapper ]; - preferLocalBuild = true; - } '' - mkdir -p $out/bin - for i in changePassword.php createAndPromote.php userOptions.php edit.php nukePage.php update.php; do - makeWrapper ${php}/bin/php $out/bin/mediawiki-${config.id}-$(basename $i .php) \ - --add-flags ${mediawikiRoot}/maintenance/$i - done - ''; - -in - -{ - - extraConfig = - '' - ${optionalString config.enableUploads '' - Alias ${config.urlPrefix}/images ${config.uploadDir} - - <Directory ${config.uploadDir}> - ${allGranted} - Options -Indexes - </Directory> - ''} - - ${if config.urlPrefix != "" then "Alias ${config.urlPrefix} ${mediawikiRoot}" else '' - RewriteEngine On - RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f - RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-d - ${concatMapStringsSep "\n" (u: "RewriteCond %{REQUEST_URI} !^${u.urlPath}") serverInfo.vhostConfig.servedDirs} - ${concatMapStringsSep "\n" (u: "RewriteCond %{REQUEST_URI} !^${u.urlPath}") serverInfo.vhostConfig.servedFiles} - RewriteRule ${if config.enableUploads - then "!^/images" - else "^.*\$" - } %{DOCUMENT_ROOT}/${if config.articleUrlPrefix == "" - then "" - else "${config.articleUrlPrefix}/" - }index.php [L] - ''} - - <Directory ${mediawikiRoot}> - ${allGranted} - DirectoryIndex index.php - </Directory> - - ${optionalString (config.articleUrlPrefix != "") '' - Alias ${config.articleUrlPrefix} ${mediawikiRoot}/index.php - ''} - ''; - - documentRoot = if config.urlPrefix == "" then mediawikiRoot else null; - - enablePHP = true; - - options = { - - id = mkOption { - default = "main"; - description = '' - A unique identifier necessary to keep multiple MediaWiki server - instances on the same machine apart. This is used to - disambiguate the administrative scripts, which get names like - mediawiki-$id-change-password. - ''; - }; - - dbType = mkOption { - default = "postgres"; - example = "mysql"; - description = "Database type."; - }; - - dbName = mkOption { - default = "mediawiki"; - description = "Name of the database that holds the MediaWiki data."; - }; - - dbServer = mkOption { - default = ""; # use a Unix domain socket - example = "10.0.2.2"; - description = '' - The location of the database server. Leave empty to use a - database server running on the same machine through a Unix - domain socket. - ''; - }; - - dbUser = mkOption { - default = "mediawiki"; - description = "The user name for accessing the database."; - }; - - dbPassword = mkOption { - default = ""; - example = "foobar"; - description = '' - The password of the database user. Warning: this is stored in - cleartext in the Nix store! - ''; - }; - - emergencyContact = mkOption { - default = serverInfo.serverConfig.adminAddr; - example = "admin@example.com"; - description = '' - Emergency contact e-mail address. Defaults to the Apache - admin address. - ''; - }; - - passwordSender = mkOption { - default = serverInfo.serverConfig.adminAddr; - example = "password@example.com"; - description = '' - E-mail address from which password confirmations originate. - Defaults to the Apache admin address. - ''; - }; - - siteName = mkOption { - default = "MediaWiki"; - example = "Foobar Wiki"; - description = "Name of the wiki"; - }; - - logo = mkOption { - default = ""; - example = "/images/logo.png"; - description = "The URL of the site's logo (which should be a 135x135px image)."; - }; - - urlPrefix = mkOption { - default = "/w"; - description = '' - The URL prefix under which the Mediawiki service appears. - ''; - }; - - articleUrlPrefix = mkOption { - default = "/wiki"; - example = ""; - description = '' - The URL prefix under which article pages appear, - e.g. http://server/wiki/Page. Leave empty to use the main URL - prefix, e.g. http://server/w/index.php?title=Page. - ''; - }; - - enableUploads = mkOption { - default = false; - description = "Whether to enable file uploads."; - }; - - uploadDir = mkOption { - default = throw "You must specify `uploadDir'."; - example = "/data/mediawiki-upload"; - description = "The directory that stores uploaded files."; - }; - - defaultSkin = mkOption { - default = ""; - example = "nostalgia"; - description = "Set this value to change the default skin used by MediaWiki."; - }; - - skins = mkOption { - default = []; - type = types.listOf types.path; - description = - '' - List of paths whose content is copied to the ‘skins’ - subdirectory of the MediaWiki installation. - ''; - }; - - extensions = mkOption { - default = []; - type = types.listOf types.path; - description = - '' - List of paths whose content is copied to the 'extensions' - subdirectory of the MediaWiki installation. - ''; - }; - - extraConfig = mkOption { - type = types.lines; - default = ""; - example = - '' - $wgEnableEmail = false; - ''; - description = '' - Any additional text to be appended to MediaWiki's - configuration file. This is a PHP script. For configuration - settings, see <link xlink:href='https://www.mediawiki.org/wiki/Manual:Configuration_settings'/>. - ''; - }; - - }; - - extraPath = [ mediawikiScripts ]; - - # !!! Need to specify that Apache has a dependency on PostgreSQL! - - startupScript = pkgs.writeScript "mediawiki_startup.sh" - # Initialise the database automagically if we're using a Postgres - # server on localhost. - (optionalString (config.dbType == "postgres" && config.dbServer == "") '' - if ! ${pkgs.postgresql}/bin/psql -l | grep -q ' ${config.dbName} ' ; then - ${pkgs.postgresql}/bin/createuser --no-superuser --no-createdb --no-createrole "${config.dbUser}" || true - ${pkgs.postgresql}/bin/createdb "${config.dbName}" -O "${config.dbUser}" - ( echo 'CREATE LANGUAGE plpgsql;' - cat ${mediawikiRoot}/maintenance/postgres/tables.sql - echo 'CREATE TEXT SEARCH CONFIGURATION public.default ( COPY = pg_catalog.english );' - echo COMMIT - ) | ${pkgs.postgresql}/bin/psql -U "${config.dbUser}" "${config.dbName}" - fi - ${php}/bin/php ${mediawikiRoot}/maintenance/update.php - ''); - - robotsEntries = optionalString (config.articleUrlPrefix != "") - '' - User-agent: * - Disallow: ${config.urlPrefix}/ - Disallow: ${config.articleUrlPrefix}/Special:Search - Disallow: ${config.articleUrlPrefix}/Special:Random - ''; - -} diff --git a/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix b/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix index 4bbd041b6e04..536e707137c6 100644 --- a/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix +++ b/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix @@ -24,14 +24,6 @@ with lib; ''; }; - port = mkOption { - type = types.int; - default = 0; - description = '' - Port for the server. Option will be removed, use <option>listen</option> instead. - ''; - }; - listen = mkOption { type = types.listOf (types.submodule ( { diff --git a/nixos/modules/services/web-servers/apache-httpd/tomcat-connector.nix b/nixos/modules/services/web-servers/apache-httpd/tomcat-connector.nix deleted file mode 100644 index a883bb2b3433..000000000000 --- a/nixos/modules/services/web-servers/apache-httpd/tomcat-connector.nix +++ /dev/null @@ -1,103 +0,0 @@ -{ config, pkgs, serverInfo, lib, ... }: - -let - extraWorkersProperties = lib.optionalString (config ? extraWorkersProperties) config.extraWorkersProperties; - - workersProperties = pkgs.writeText "workers.properties" '' -# Define list of workers that will be used -# for mapping requests -# The configuration directives are valid -# for the mod_jk version 1.2.18 and later -# -worker.list=loadbalancer,status - -# Define Node1 -# modify the host as your host IP or DNS name. -worker.node1.port=8009 -worker.node1.host=localhost -worker.node1.type=ajp13 -worker.node1.lbfactor=1 - -# Load-balancing behaviour -worker.loadbalancer.type=lb -worker.loadbalancer.balance_workers=node1 - -# Status worker for managing load balancer -worker.status.type=status - -${extraWorkersProperties} - ''; -in -{ - - options = { - extraWorkersProperties = lib.mkOption { - default = ""; - description = "Additional configuration for the workers.properties file."; - }; - }; - - extraModules = [ - { name = "jk"; path = "${pkgs.tomcat_connectors}/modules/mod_jk.so"; } - ]; - - extraConfig = '' -# Where to find workers.properties -JkWorkersFile ${workersProperties} - -# Where to put jk logs -JkLogFile ${serverInfo.serverConfig.logDir}/mod_jk.log - -# Set the jk log level [debug/error/info] -JkLogLevel info - -# Select the log format -JkLogStampFormat "[%a %b %d %H:%M:%S %Y]" - -# JkOptions indicates to send SSK KEY SIZE -# Note: Changed from +ForwardURICompat. -# See http://tomcat.apache.org/security-jk.html -JkOptions +ForwardKeySize +ForwardURICompatUnparsed -ForwardDirectories - -# JkRequestLogFormat -JkRequestLogFormat "%w %V %T" - -# Mount your applications -JkMount /__application__/* loadbalancer - -# You can use external file for mount points. -# It will be checked for updates each 60 seconds. -# The format of the file is: /url=worker -# /examples/*=loadbalancer -#JkMountFile uriworkermap.properties - -# Add shared memory. -# This directive is present with 1.2.10 and -# later versions of mod_jk, and is needed for -# for load balancing to work properly -# Note: Replaced JkShmFile logs/jk.shm due to SELinux issues. Refer to -# https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=225452 -JkShmFile ${serverInfo.serverConfig.stateDir}/jk.shm - -# Static files in all Tomcat webapp context directories are served by apache -JkAutoAlias /var/tomcat/webapps - -# All requests go to worker by default -JkMount /* loadbalancer -# Serve some static files using httpd -#JkUnMount /*.html loadbalancer -#JkUnMount /*.jpg loadbalancer -#JkUnMount /*.gif loadbalancer -#JkUnMount /*.css loadbalancer -#JkUnMount /*.png loadbalancer -#JkUnMount /*.js loadbalancer - -# Add jkstatus for managing runtime data -<Location /jkstatus/> -JkMount status -Order deny,allow -Deny from all -Allow from 127.0.0.1 -</Location> - ''; -} diff --git a/nixos/modules/services/x11/compton.nix b/nixos/modules/services/x11/compton.nix index d4357324c870..c02c9bfd94e8 100644 --- a/nixos/modules/services/x11/compton.nix +++ b/nixos/modules/services/x11/compton.nix @@ -7,57 +7,35 @@ let cfg = config.services.compton; - literalAttrs = v: - if isString v then toString v - else if isAttrs v then "{\n" - + concatStringsSep "\n" (mapAttrsToList - (name: value: "${literalAttrs name} = ${literalAttrs value};") - v) - + "\n}" - else generators.toPretty {} v; + pairOf = x: with types; addCheck (listOf x) (y: length y == 2); floatBetween = a: b: with lib; with types; addCheck str (x: versionAtLeast x a && versionOlder x b); - pairOf = x: with types; addCheck (listOf x) (y: length y == 2); - - opacityRules = optionalString (length cfg.opacityRules != 0) - (concatMapStringsSep ",\n" (rule: ''"${rule}"'') cfg.opacityRules); - - configFile = pkgs.writeText "compton.conf" - (optionalString cfg.fade '' - # fading - fading = true; - fade-delta = ${toString cfg.fadeDelta}; - fade-in-step = ${elemAt cfg.fadeSteps 0}; - fade-out-step = ${elemAt cfg.fadeSteps 1}; - fade-exclude = ${toJSON cfg.fadeExclude}; - '' + optionalString cfg.shadow '' - - # shadows - shadow = true; - shadow-offset-x = ${toString (elemAt cfg.shadowOffsets 0)}; - shadow-offset-y = ${toString (elemAt cfg.shadowOffsets 1)}; - shadow-opacity = ${cfg.shadowOpacity}; - shadow-exclude = ${toJSON cfg.shadowExclude}; - '' + '' - - # opacity - active-opacity = ${cfg.activeOpacity}; - inactive-opacity = ${cfg.inactiveOpacity}; - - wintypes: - ${literalAttrs cfg.wintypes}; - - opacity-rule = [ - ${opacityRules} - ]; - - # other options - backend = ${toJSON cfg.backend}; - vsync = ${boolToString cfg.vSync}; - refresh-rate = ${toString cfg.refreshRate}; - '' + cfg.extraOptions); + toConf = attrs: concatStringsSep "\n" + (mapAttrsToList + (k: v: let + sep = if isAttrs v then ":" else "="; + # Basically a tinkered lib.generators.mkKeyValueDefault + mkValueString = v: + if isBool v then boolToString v + else if isInt v then toString v + else if isFloat v then toString v + else if isString v then ''"${escape [ ''"'' ] v}"'' + else if isList v then "[ " + + concatMapStringsSep " , " mkValueString v + + " ]" + else if isAttrs v then "{ " + + concatStringsSep " " + (mapAttrsToList + (key: value: "${toString key}=${mkValueString value};") + v) + + " }" + else abort "compton.mkValueString: unexpected type (v = ${v})"; + in "${escape [ sep ] k}${sep}${mkValueString v};") + attrs); + + configFile = pkgs.writeText "compton.conf" (toConf cfg.settings); in { @@ -236,23 +214,13 @@ in { ''; }; - package = mkOption { - type = types.package; - default = pkgs.compton; - defaultText = "pkgs.compton"; - example = literalExample "pkgs.compton"; - description = '' - Compton derivation to use. - ''; - }; - - extraOptions = mkOption { - type = types.lines; - default = ""; - example = '' - unredir-if-possible = true; - dbe = true; - ''; + settings = let + configTypes = with types; either bool (either int (either float str)); + # types.loaOf converts lists to sets + loaOf = t: with types; either (listOf t) (attrsOf t); + in mkOption { + type = loaOf (types.either configTypes (loaOf (types.either configTypes (loaOf configTypes)))); + default = {}; description = '' Additional Compton configuration. ''; @@ -260,6 +228,42 @@ in { }; config = mkIf cfg.enable { + services.compton.settings = let + # Hard conversion to float, literally lib.toInt but toFloat + toFloat = str: let + may_be_float = builtins.fromJSON str; + in if builtins.isFloat may_be_float + then may_be_float + else throw "Could not convert ${str} to float."; + in { + # fading + fading = mkDefault cfg.fade; + fade-delta = mkDefault cfg.fadeDelta; + fade-in-step = mkDefault (toFloat (elemAt cfg.fadeSteps 0)); + fade-out-step = mkDefault (toFloat (elemAt cfg.fadeSteps 1)); + fade-exclude = mkDefault cfg.fadeExclude; + + # shadows + shadow = mkDefault cfg.shadow; + shadow-offset-x = mkDefault (elemAt cfg.shadowOffsets 0); + shadow-offset-y = mkDefault (elemAt cfg.shadowOffsets 1); + shadow-opacity = mkDefault (toFloat cfg.shadowOpacity); + shadow-exclude = mkDefault cfg.shadowExclude; + + # opacity + active-opacity = mkDefault (toFloat cfg.activeOpacity); + inactive-opacity = mkDefault (toFloat cfg.inactiveOpacity); + + wintypes = mkDefault cfg.wintypes; + + opacity-rule = mkDefault cfg.opacityRules; + + # other options + backend = mkDefault cfg.backend; + vsync = mkDefault cfg.vSync; + refresh-rate = mkDefault cfg.refreshRate; + }; + systemd.user.services.compton = { description = "Compton composite manager"; wantedBy = [ "graphical-session.target" ]; @@ -271,13 +275,13 @@ in { }; serviceConfig = { - ExecStart = "${cfg.package}/bin/compton --config ${configFile}"; + ExecStart = "${pkgs.compton}/bin/compton --config ${configFile}"; RestartSec = 3; Restart = "always"; }; }; - environment.systemPackages = [ cfg.package ]; + environment.systemPackages = [ pkgs.compton ]; }; } diff --git a/nixos/modules/services/x11/desktop-managers/gnome3.nix b/nixos/modules/services/x11/desktop-managers/gnome3.nix index ef6820d33260..cd7502421255 100644 --- a/nixos/modules/services/x11/desktop-managers/gnome3.nix +++ b/nixos/modules/services/x11/desktop-managers/gnome3.nix @@ -154,7 +154,8 @@ in { services.hardware.bolt.enable = mkDefault true; services.xserver.libinput.enable = mkDefault true; # for controlling touchpad settings via gnome control center systemd.packages = [ pkgs.gnome3.vino ]; - services.flatpak.extraPortals = [ pkgs.xdg-desktop-portal-gtk ]; + xdg.portal.enable = true; + xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ]; # If gnome3 is installed, build vim for gtk3 too. nixpkgs.config.vim.gui = "gtk3"; @@ -229,7 +230,7 @@ in { # Use the correct gnome3 packageSet networking.networkmanager.basePackages = - { inherit (pkgs) networkmanager modemmanager wpa_supplicant; + { inherit (pkgs) networkmanager modemmanager wpa_supplicant crda; inherit (pkgs.gnome3) networkmanager-openvpn networkmanager-vpnc networkmanager-openconnect networkmanager-fortisslvpn networkmanager-iodine networkmanager-l2tp; }; diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix index 41903b33fae9..e27a479adadd 100644 --- a/nixos/modules/services/x11/desktop-managers/pantheon.nix +++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix @@ -147,7 +147,7 @@ in networking.networkmanager.enable = mkDefault true; networking.networkmanager.basePackages = - { inherit (pkgs) networkmanager modemmanager wpa_supplicant; + { inherit (pkgs) networkmanager modemmanager wpa_supplicant crda; inherit (pkgs.gnome3) networkmanager-openvpn networkmanager-vpnc networkmanager-openconnect networkmanager-fortisslvpn networkmanager-iodine networkmanager-l2tp; }; diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix index dc8bfc7dc172..6d148fba8954 100644 --- a/nixos/modules/services/x11/desktop-managers/plasma5.nix +++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix @@ -21,6 +21,13 @@ in description = "Enable the Plasma 5 (KDE 5) desktop environment."; }; + phononBackend = mkOption { + type = types.enum [ "gstreamer" "vlc" ]; + default = "gstreamer"; + example = "vlc"; + description = "Phonon audio backend to install."; + }; + enableQt4Support = mkOption { type = types.bool; default = true; @@ -64,8 +71,8 @@ in }; security.wrappers = { - kcheckpass.source = "${lib.getBin plasma5.kscreenlocker}/lib/libexec/kcheckpass"; - "start_kdeinit".source = "${lib.getBin pkgs.kinit}/lib/libexec/kf5/start_kdeinit"; + kcheckpass.source = "${lib.getBin plasma5.kscreenlocker}/libexec/kcheckpass"; + "start_kdeinit".source = "${lib.getBin pkgs.kinit}/libexec/kf5/start_kdeinit"; kwin_wayland = { source = "${lib.getBin plasma5.kwin}/bin/kwin_wayland"; capabilities = "cap_sys_nice+ep"; @@ -161,12 +168,14 @@ in qtvirtualkeyboard - libsForQt5.phonon-backend-gstreamer - xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/ ] - ++ lib.optionals cfg.enableQt4Support [ pkgs.phonon-backend-gstreamer ] + # Phonon audio backend + ++ lib.optional (cfg.phononBackend == "gstreamer") libsForQt5.phonon-backend-gstreamer + ++ lib.optional (cfg.phononBackend == "gstreamer" && cfg.enableQt4Support) pkgs.phonon-backend-gstreamer + ++ lib.optional (cfg.phononBackend == "vlc") libsForQt5.phonon-backend-vlc + ++ lib.optional (cfg.phononBackend == "vlc" && cfg.enableQt4Support) pkgs.phonon-backend-vlc # Optional hardware support features ++ lib.optional config.hardware.bluetooth.enable bluedevil @@ -224,6 +233,9 @@ in security.pam.services.sddm.enableKwallet = true; security.pam.services.slim.enableKwallet = true; + xdg.portal.enable = true; + xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-kde ]; + # Update the start menu for each user that is currently logged in system.userActivationScripts.plasmaSetup = '' # The KDE icon cache is supposed to update itself diff --git a/nixos/modules/services/x11/extra-layouts.nix b/nixos/modules/services/x11/extra-layouts.nix new file mode 100644 index 000000000000..5523dd2bf023 --- /dev/null +++ b/nixos/modules/services/x11/extra-layouts.nix @@ -0,0 +1,165 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + layouts = config.services.xserver.extraLayouts; + + layoutOpts = { + options = { + description = mkOption { + type = types.str; + description = "A short description of the layout."; + }; + + languages = mkOption { + type = types.listOf types.str; + description = + '' + A list of languages provided by the layout. + (Use ISO 639-2 codes, for example: "eng" for english) + ''; + }; + + compatFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + The path to the xkb compat file. + This file sets the compatibility state, used to preserve + compatibility with xkb-unaware programs. + It must contain a <literal>xkb_compat "name" { ... }</literal> block. + ''; + }; + + geometryFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + The path to the xkb geometry file. + This (completely optional) file describes the physical layout of + keyboard, which maybe be used by programs to depict it. + It must contain a <literal>xkb_geometry "name" { ... }</literal> block. + ''; + }; + + keycodesFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + The path to the xkb keycodes file. + This file specifies the range and the interpretation of the raw + keycodes sent by the keyboard. + It must contain a <literal>xkb_keycodes "name" { ... }</literal> block. + ''; + }; + + symbolsFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + The path to the xkb symbols file. + This is the most important file: it defines which symbol or action + maps to each key and must contain a + <literal>xkb_symbols "name" { ... }</literal> block. + ''; + }; + + typesFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + The path to the xkb types file. + This file specifies the key types that can be associated with + the various keyboard keys. + It must contain a <literal>xkb_types "name" { ... }</literal> block. + ''; + }; + + }; + }; + +in + +{ + + ###### interface + + options.services.xserver = { + extraLayouts = mkOption { + type = types.attrsOf (types.submodule layoutOpts); + default = {}; + example = literalExample + '' + { + mine = { + description = "My custom xkb layout."; + languages = [ "eng" ]; + symbolsFile = /path/to/my/layout; + }; + } + ''; + description = '' + Extra custom layouts that will be included in the xkb configuration. + Information on how to create a new layout can be found here: + <link xlink:href="https://www.x.org/releases/current/doc/xorg-docs/input/XKB-Enhancing.html#Defining_New_Layouts"></link>. + For more examples see + <link xlink:href="https://wiki.archlinux.org/index.php/X_KeyBoard_extension#Basic_examples"></link> + ''; + }; + + }; + + ###### implementation + + config = mkIf (layouts != { }) { + + # We don't override xkeyboard_config directly to + # reduce the amount of packages to be recompiled. + # Only the following packages are necessary to set + # a custom layout anyway: + nixpkgs.overlays = lib.singleton (self: super: { + + xkb_patched = self.xorg.xkeyboardconfig_custom { + layouts = config.services.xserver.extraLayouts; + }; + + xorg = super.xorg // { + xorgserver = super.xorg.xorgserver.overrideAttrs (old: { + configureFlags = old.configureFlags ++ [ + "--with-xkb-bin-directory=${self.xorg.xkbcomp}/bin" + "--with-xkb-path=${self.xkb_patched}/share/X11/xkb" + ]; + }); + + setxkbmap = super.xorg.setxkbmap.overrideAttrs (old: { + postInstall = + '' + mkdir -p $out/share + ln -sfn ${self.xkb_patched}/etc/X11 $out/share/X11 + ''; + }); + + xkbcomp = super.xorg.xkbcomp.overrideAttrs (old: { + configureFlags = "--with-xkb-config-root=${self.xkb_patched}/share/X11/xkb"; + }); + + }; + + ckbcomp = super.ckbcomp.override { + xkeyboard_config = self.xkb_patched; + }; + + xkbvalidate = super.xkbvalidate.override { + libxkbcommon = self.libxkbcommon.override { + xkeyboard_config = self.xkb_patched; + }; + }; + + }); + + services.xserver.xkbDir = "${pkgs.xkb_patched}/etc/X11/xkb"; + + }; + +} diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix index 82730c5e80c6..b1a316706976 100644 --- a/nixos/modules/services/x11/xserver.nix +++ b/nixos/modules/services/x11/xserver.nix @@ -14,6 +14,9 @@ let # Alias so people can keep using "virtualbox" instead of "vboxvideo". virtualbox = { modules = [ xorg.xf86videovboxvideo ]; driverName = "vboxvideo"; }; + # Alias so that "radeon" uses the xf86-video-ati driver. + radeon = { modules = [ xorg.xf86videoati ]; driverName = "ati"; }; + # modesetting does not have a xf86videomodesetting package as it is included in xorgserver modesetting = {}; }; @@ -241,7 +244,7 @@ in videoDrivers = mkOption { type = types.listOf types.str; # !!! We'd like "nv" here, but it segfaults the X server. - default = [ "ati" "cirrus" "vesa" "vmware" "modesetting" ]; + default = [ "radeon" "cirrus" "vesa" "vmware" "modesetting" ]; example = [ "ati_unfree" "amdgpu" "amdgpu-pro" "nv" "nvidia" "nvidiaLegacy390" "nvidiaLegacy340" "nvidiaLegacy304" |