diff options
Diffstat (limited to 'nixos/modules/services')
56 files changed, 1305 insertions, 355 deletions
diff --git a/nixos/modules/services/backup/znapzend.nix b/nixos/modules/services/backup/znapzend.nix index 203631a577f0..98cd647f61ac 100644 --- a/nixos/modules/services/backup/znapzend.nix +++ b/nixos/modules/services/backup/znapzend.nix @@ -372,6 +372,41 @@ in and <citerefentry><refentrytitle>zfs</refentrytitle><manvolnum>8</manvolnum></citerefentry> for more info. ''; + features.sendRaw = mkEnableOption '' + sendRaw feature which adds the options <literal>-w</literal> to the + <command>zfs send</command> command. For encrypted source datasets this + instructs zfs not to decrypt before sending which results in a remote + backup that can't be read without the encryption key/passphrase, useful + when the remote isn't fully trusted or not physically secure. This + option must be used consistently, raw incrementals cannot be based on + non-raw snapshots and vice versa. + ''; + features.skipIntermediates = mkEnableOption '' + Enable the skipIntermediates feature to send a single increment + between latest common snapshot and the newly made one. It may skip + several source snaps if the destination was offline for some time, and + it should skip snapshots not managed by znapzend. Normally for online + destinations, the new snapshot is sent as soon as it is created on the + source, so there are no automatic increments to skip. + ''; + features.lowmemRecurse = mkEnableOption '' + use lowmemRecurse on systems where you have too many datasets, so a + recursive listing of attributes to find backup plans exhausts the + memory available to <command>znapzend</command>: instead, go the slower + way to first list all impacted dataset names, and then query their + configs one by one. + ''; + features.zfsGetType = mkEnableOption '' + use zfsGetType if your <command>zfs get</command> supports a + <literal>-t</literal> argument for filtering by dataset type at all AND + lists properties for snapshots by default when recursing, so that there + is too much data to process while searching for backup plans. + If these two conditions apply to your system, the time needed for a + <literal>--recursive</literal> search for backup plans can literally + differ by hundreds of times (depending on the amount of snapshots in + that dataset tree... and a decent backup plan will ensure you have a lot + of those), so you would benefit from requesting this feature. + ''; }; }; diff --git a/nixos/modules/services/continuous-integration/gitlab-runner.nix b/nixos/modules/services/continuous-integration/gitlab-runner.nix index bd4cf6a37bad..eacfed85ddff 100644 --- a/nixos/modules/services/continuous-integration/gitlab-runner.nix +++ b/nixos/modules/services/continuous-integration/gitlab-runner.nix @@ -1,160 +1,494 @@ { config, lib, pkgs, ... }: - with lib; - let cfg = config.services.gitlab-runner; - configFile = - if (cfg.configFile == null) then - (pkgs.runCommand "config.toml" { - buildInputs = [ pkgs.remarshal ]; - preferLocalBuild = true; - } '' - remarshal -if json -of toml \ - < ${pkgs.writeText "config.json" (builtins.toJSON cfg.configOptions)} \ - > $out - '') - else - cfg.configFile; hasDocker = config.virtualisation.docker.enable; + hashedServices = with builtins; (mapAttrs' (name: service: nameValuePair + "${name}_${config.networking.hostName}_${ + substring 0 12 + (hashString "md5" (unsafeDiscardStringContext (toJSON service)))}" + service) + cfg.services); + configPath = "$HOME/.gitlab-runner/config.toml"; + configureScript = pkgs.writeShellScriptBin "gitlab-runner-configure" ( + if (cfg.configFile != null) then '' + mkdir -p $(dirname ${configPath}) + cp ${cfg.configFile} ${configPath} + # make config file readable by service + chown -R --reference=$HOME $(dirname ${configPath}) + '' else '' + export CONFIG_FILE=${configPath} + + mkdir -p $(dirname ${configPath}) + + # remove no longer existing services + gitlab-runner verify --delete + + # current and desired state + NEEDED_SERVICES=$(echo ${concatStringsSep " " (attrNames hashedServices)} | tr " " "\n") + REGISTERED_SERVICES=$(gitlab-runner list 2>&1 | grep 'Executor' | awk '{ print $1 }') + + # difference between current and desired state + NEW_SERVICES=$(grep -vxF -f <(echo "$REGISTERED_SERVICES") <(echo "$NEEDED_SERVICES") || true) + OLD_SERVICES=$(grep -vxF -f <(echo "$NEEDED_SERVICES") <(echo "$REGISTERED_SERVICES") || true) + + # register new services + ${concatStringsSep "\n" (mapAttrsToList (name: service: '' + if echo "$NEW_SERVICES" | grep -xq ${name}; then + bash -c ${escapeShellArg (concatStringsSep " \\\n " ([ + "set -a && source ${service.registrationConfigFile} &&" + "gitlab-runner register" + "--non-interactive" + "--name ${name}" + "--executor ${service.executor}" + "--limit ${toString service.limit}" + "--request-concurrency ${toString service.requestConcurrency}" + "--maximum-timeout ${toString service.maximumTimeout}" + ] ++ service.registrationFlags + ++ optional (service.buildsDir != null) + "--builds-dir ${service.buildsDir}" + ++ optional (service.preCloneScript != null) + "--pre-clone-script ${service.preCloneScript}" + ++ optional (service.preBuildScript != null) + "--pre-build-script ${service.preBuildScript}" + ++ optional (service.postBuildScript != null) + "--post-build-script ${service.postBuildScript}" + ++ optional (service.tagList != [ ]) + "--tag-list ${concatStringsSep "," service.tagList}" + ++ optional service.runUntagged + "--run-untagged" + ++ optional service.protected + "--access-level ref_protected" + ++ optional service.debugTraceDisabled + "--debug-trace-disabled" + ++ map (e: "--env ${escapeShellArg e}") (mapAttrsToList (name: value: "${name}=${value}") service.environmentVariables) + ++ optionals (service.executor == "docker") ( + assert ( + assertMsg (service.dockerImage != null) + "dockerImage option is required for docker executor (${name})"); + [ "--docker-image ${service.dockerImage}" ] + ++ optional service.dockerDisableCache + "--docker-disable-cache" + ++ optional service.dockerPrivileged + "--docker-privileged" + ++ map (v: "--docker-volumes ${escapeShellArg v}") service.dockerVolumes + ++ map (v: "--docker-extra-hosts ${escapeShellArg v}") service.dockerExtraHosts + ++ map (v: "--docker-allowed-images ${escapeShellArg v}") service.dockerAllowedImages + ++ map (v: "--docker-allowed-services ${escapeShellArg v}") service.dockerAllowedServices + ) + ))} && sleep 1 + fi + '') hashedServices)} + + # unregister old services + for NAME in $(echo "$OLD_SERVICES") + do + [ ! -z "$NAME" ] && gitlab-runner unregister \ + --name "$NAME" && sleep 1 + done + + # update global options + remarshal --if toml --of json ${configPath} \ + | jq -cM '.check_interval = ${toString cfg.checkInterval} | + .concurrent = ${toString cfg.concurrent}' \ + | remarshal --if json --of toml \ + | sponge ${configPath} + + # make config file readable by service + chown -R --reference=$HOME $(dirname ${configPath}) + ''); + startScript = pkgs.writeShellScriptBin "gitlab-runner-start" '' + export CONFIG_FILE=${configPath} + exec gitlab-runner run --working-directory $HOME + ''; in { options.services.gitlab-runner = { enable = mkEnableOption "Gitlab Runner"; - configFile = mkOption { + type = types.nullOr types.path; default = null; description = '' Configuration file for gitlab-runner. - Use this option in favor of configOptions to avoid placing CI tokens in the nix store. - <option>configFile</option> takes precedence over <option>configOptions</option>. + <option>configFile</option> takes precedence over <option>services</option>. + <option>checkInterval</option> and <option>concurrent</option> will be ignored too. - Warning: Not using <option>configFile</option> will potentially result in secrets - leaking into the WORLD-READABLE nix store. + This option is deprecated, please use <option>services</option> instead. + You can use <option>registrationConfigFile</option> and + <option>registrationFlags</option> + for settings not covered by this module. ''; - type = types.nullOr types.path; }; - - configOptions = mkOption { + checkInterval = mkOption { + type = types.int; + default = 0; + example = literalExample "with lib; (length (attrNames config.services.gitlab-runner.services)) * 3"; description = '' - Configuration for gitlab-runner - <option>configFile</option> will take precedence over this option. - - Warning: all Configuration, especially CI token, will be stored in a - WORLD-READABLE file in the Nix Store. - - If you want to protect your CI token use <option>configFile</option> instead. + Defines the interval length, in seconds, between new jobs check. + The default value is 3; + if set to 0 or lower, the default value will be used. + See <link xlink:href="https://docs.gitlab.com/runner/configuration/advanced-configuration.html#how-check_interval-works">runner documentation</link> for more information. + ''; + }; + concurrent = mkOption { + type = types.int; + default = 1; + example = literalExample "config.nix.maxJobs"; + description = '' + Limits how many jobs globally can be run concurrently. + The most upper limit of jobs using all defined runners. + 0 does not mean unlimited. ''; - type = types.attrs; - example = { - concurrent = 2; - runners = [{ - name = "docker-nix-1.11"; - url = "https://CI/"; - token = "TOKEN"; - executor = "docker"; - builds_dir = ""; - docker = { - host = ""; - image = "nixos/nix:1.11"; - privileged = true; - disable_cache = true; - cache_dir = ""; - }; - }]; - }; }; - gracefulTermination = mkOption { - default = false; type = types.bool; + default = false; description = '' - Finish all remaining jobs before stopping, restarting or reconfiguring. - If not set gitlab-runner will stop immediatly without waiting for jobs to finish, - which will lead to failed builds. + Finish all remaining jobs before stopping. + If not set gitlab-runner will stop immediatly without waiting + for jobs to finish, which will lead to failed builds. ''; }; - gracefulTimeout = mkOption { - default = "infinity"; type = types.str; + default = "infinity"; example = "5min 20s"; - description = ''Time to wait until a graceful shutdown is turned into a forceful one.''; - }; - - workDir = mkOption { - default = "/var/lib/gitlab-runner"; - type = types.path; - description = "The working directory used"; + description = '' + Time to wait until a graceful shutdown is turned into a forceful one. + ''; }; - package = mkOption { - description = "Gitlab Runner package to use"; + type = types.package; default = pkgs.gitlab-runner; defaultText = "pkgs.gitlab-runner"; - type = types.package; example = literalExample "pkgs.gitlab-runner_1_11"; + description = "Gitlab Runner package to use."; }; - - packages = mkOption { - default = [ pkgs.bash pkgs.docker-machine ]; - defaultText = "[ pkgs.bash pkgs.docker-machine ]"; + extraPackages = mkOption { type = types.listOf types.package; + default = [ ]; description = '' - Packages to add to PATH for the gitlab-runner process. + Extra packages to add to PATH for the gitlab-runner process. ''; }; + services = mkOption { + description = "GitLab Runner services."; + default = { }; + example = literalExample '' + { + # runner for building in docker via host's nix-daemon + # nix store will be readable in runner, might be insecure + nix = { + # File should contain at least these two variables: + # `CI_SERVER_URL` + # `REGISTRATION_TOKEN` + registrationConfigFile = "/run/secrets/gitlab-runner-registration"; + dockerImage = "alpine"; + dockerVolumes = [ + "/nix/store:/nix/store:ro" + "/nix/var/nix/db:/nix/var/nix/db:ro" + "/nix/var/nix/daemon-socket:/nix/var/nix/daemon-socket:ro" + ]; + dockerDisableCache = true; + preBuildScript = pkgs.writeScript "setup-container" ''' + mkdir -p -m 0755 /nix/var/log/nix/drvs + mkdir -p -m 0755 /nix/var/nix/gcroots + mkdir -p -m 0755 /nix/var/nix/profiles + mkdir -p -m 0755 /nix/var/nix/temproots + mkdir -p -m 0755 /nix/var/nix/userpool + mkdir -p -m 1777 /nix/var/nix/gcroots/per-user + mkdir -p -m 1777 /nix/var/nix/profiles/per-user + mkdir -p -m 0755 /nix/var/nix/profiles/per-user/root + mkdir -p -m 0700 "$HOME/.nix-defexpr" - }; + . ''${pkgs.nix}/etc/profile.d/nix.sh + ''${pkgs.nix}/bin/nix-env -i ''${concatStringsSep " " (with pkgs; [ nix cacert git openssh ])} + + ''${pkgs.nix}/bin/nix-channel --add https://nixos.org/channels/nixpkgs-unstable + ''${pkgs.nix}/bin/nix-channel --update nixpkgs + '''; + environmentVariables = { + ENV = "/etc/profile"; + USER = "root"; + NIX_REMOTE = "daemon"; + PATH = "/nix/var/nix/profiles/default/bin:/nix/var/nix/profiles/default/sbin:/bin:/sbin:/usr/bin:/usr/sbin"; + NIX_SSL_CERT_FILE = "/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt"; + }; + tagList = [ "nix" ]; + }; + # runner for building docker images + docker-images = { + # File should contain at least these two variables: + # `CI_SERVER_URL` + # `REGISTRATION_TOKEN` + registrationConfigFile = "/run/secrets/gitlab-runner-registration"; + dockerImage = "docker:stable"; + dockerVolumes = [ + "/var/run/docker.sock:/var/run/docker.sock" + ]; + tagList = [ "docker-images" ]; + }; + # runner for executing stuff on host system (very insecure!) + # make sure to add required packages (including git!) + # to `environment.systemPackages` + shell = { + # File should contain at least these two variables: + # `CI_SERVER_URL` + # `REGISTRATION_TOKEN` + registrationConfigFile = "/run/secrets/gitlab-runner-registration"; + executor = "shell"; + tagList = [ "shell" ]; + }; + # runner for everything else + default = { + # File should contain at least these two variables: + # `CI_SERVER_URL` + # `REGISTRATION_TOKEN` + registrationConfigFile = "/run/secrets/gitlab-runner-registration"; + dockerImage = "debian:stable"; + }; + } + ''; + type = types.attrsOf (types.submodule { + options = { + registrationConfigFile = mkOption { + type = types.path; + description = '' + Absolute path to a file with environment variables + used for gitlab-runner registration. + A list of all supported environment variables can be found in + <literal>gitlab-runner register --help</literal>. + + Ones that you probably want to set is + + <literal>CI_SERVER_URL=<CI server URL></literal> + + <literal>REGISTRATION_TOKEN=<registration secret></literal> + ''; + }; + registrationFlags = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "--docker-helper-image my/gitlab-runner-helper" ]; + description = '' + Extra command-line flags passed to + <literal>gitlab-runner register</literal>. + Execute <literal>gitlab-runner register --help</literal> + for a list of supported flags. + ''; + }; + environmentVariables = mkOption { + type = types.attrsOf types.str; + default = { }; + example = { NAME = "value"; }; + description = '' + Custom environment variables injected to build environment. + For secrets you can use <option>registrationConfigFile</option> + with <literal>RUNNER_ENV</literal> variable set. + ''; + }; + executor = mkOption { + type = types.str; + default = "docker"; + description = '' + Select executor, eg. shell, docker, etc. + See <link xlink:href="https://docs.gitlab.com/runner/executors/README.html">runner documentation</link> for more information. + ''; + }; + buildsDir = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/lib/gitlab-runner/builds"; + description = '' + Absolute path to a directory where builds will be stored + in context of selected executor (Locally, Docker, SSH). + ''; + }; + dockerImage = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Docker image to be used. + ''; + }; + dockerVolumes = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "/var/run/docker.sock:/var/run/docker.sock" ]; + description = '' + Bind-mount a volume and create it + if it doesn't exist prior to mounting. + ''; + }; + dockerDisableCache = mkOption { + type = types.bool; + default = false; + description = '' + Disable all container caching. + ''; + }; + dockerPrivileged = mkOption { + type = types.bool; + default = false; + description = '' + Give extended privileges to container. + ''; + }; + dockerExtraHosts = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "other-host:127.0.0.1" ]; + description = '' + Add a custom host-to-IP mapping. + ''; + }; + dockerAllowedImages = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "ruby:*" "python:*" "php:*" "my.registry.tld:5000/*:*" ]; + description = '' + Whitelist allowed images. + ''; + }; + dockerAllowedServices = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "postgres:9" "redis:*" "mysql:*" ]; + description = '' + Whitelist allowed services. + ''; + }; + preCloneScript = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Runner-specific command script executed before code is pulled. + ''; + }; + preBuildScript = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Runner-specific command script executed after code is pulled, + just before build executes. + ''; + }; + postBuildScript = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Runner-specific command script executed after code is pulled + and just after build executes. + ''; + }; + tagList = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + Tag list. + ''; + }; + runUntagged = mkOption { + type = types.bool; + default = false; + description = '' + Register to run untagged builds; defaults to + <literal>true</literal> when <option>tagList</option> is empty. + ''; + }; + limit = mkOption { + type = types.int; + default = 0; + description = '' + Limit how many jobs can be handled concurrently by this service. + 0 (default) simply means don't limit. + ''; + }; + requestConcurrency = mkOption { + type = types.int; + default = 0; + description = '' + Limit number of concurrent requests for new jobs from GitLab. + ''; + }; + maximumTimeout = mkOption { + type = types.int; + default = 0; + description = '' + What is the maximum timeout (in seconds) that will be set for + job when using this Runner. 0 (default) simply means don't limit. + ''; + }; + protected = mkOption { + type = types.bool; + default = false; + description = '' + When set to true Runner will only run on pipelines + triggered on protected branches. + ''; + }; + debugTraceDisabled = mkOption { + type = types.bool; + default = false; + description = '' + When set to true Runner will disable the possibility of + using the <literal>CI_DEBUG_TRACE</literal> feature. + ''; + }; + }; + }); + }; + }; config = mkIf cfg.enable { + warnings = optional (cfg.configFile != null) "services.gitlab-runner.`configFile` is deprecated, please use services.gitlab-runner.`services`."; + environment.systemPackages = [ cfg.package ]; systemd.services.gitlab-runner = { - path = cfg.packages; - environment = config.networking.proxy.envVars // { - # Gitlab runner will not start if the HOME variable is not set - HOME = cfg.workDir; - }; description = "Gitlab Runner"; + documentation = [ "https://docs.gitlab.com/runner/" ]; after = [ "network.target" ] ++ optional hasDocker "docker.service"; requires = optional hasDocker "docker.service"; wantedBy = [ "multi-user.target" ]; + environment = config.networking.proxy.envVars // { + HOME = "/var/lib/gitlab-runner"; + }; + path = with pkgs; [ + bash + gawk + jq + moreutils + remarshal + utillinux + cfg.package + ] ++ cfg.extraPackages; reloadIfChanged = true; - restartTriggers = [ - config.environment.etc."gitlab-runner/config.toml".source - ]; serviceConfig = { + # Set `DynamicUser` under `systemd.services.gitlab-runner.serviceConfig` + # to `lib.mkForce false` in your configuration to run this service as root. + # You can also set `User` and `Group` options to run this service as desired user. + # Make sure to restart service or changes won't apply. + DynamicUser = true; StateDirectory = "gitlab-runner"; - ExecReload= "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; - ExecStart = ''${cfg.package.bin}/bin/gitlab-runner run \ - --working-directory ${cfg.workDir} \ - --config /etc/gitlab-runner/config.toml \ - --service gitlab-runner \ - --user gitlab-runner \ - ''; - - } // optionalAttrs (cfg.gracefulTermination) { + SupplementaryGroups = optional hasDocker "docker"; + ExecStartPre = "!${configureScript}/bin/gitlab-runner-configure"; + ExecStart = "${startScript}/bin/gitlab-runner-start"; + ExecReload = "!${configureScript}/bin/gitlab-runner-configure"; + } // optionalAttrs (cfg.gracefulTermination) { TimeoutStopSec = "${cfg.gracefulTimeout}"; KillSignal = "SIGQUIT"; KillMode = "process"; }; }; - - # Make the gitlab-runner command availabe so users can query the runner - environment.systemPackages = [ cfg.package ]; - - # Make sure the config can be reloaded on change - environment.etc."gitlab-runner/config.toml".source = configFile; - - users.users.gitlab-runner = { - group = "gitlab-runner"; - extraGroups = optional hasDocker "docker"; - uid = config.ids.uids.gitlab-runner; - home = cfg.workDir; - createHome = true; - }; - - users.groups.gitlab-runner.gid = config.ids.gids.gitlab-runner; + # Enable docker if `docker` executor is used in any service + virtualisation.docker.enable = mkIf ( + any (s: s.executor == "docker") (attrValues cfg.services) + ) (mkDefault true); }; + imports = [ + (mkRenamedOptionModule [ "services" "gitlab-runner" "packages" ] [ "services" "gitlab-runner" "extraPackages" ] ) + (mkRemovedOptionModule [ "services" "gitlab-runner" "configOptions" ] "Use services.gitlab-runner.services option instead" ) + (mkRemovedOptionModule [ "services" "gitlab-runner" "workDir" ] "You should move contents of workDir (if any) to /var/lib/gitlab-runner" ) + ]; } diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index 809f61cfa818..8c2851c37ac2 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -231,6 +231,10 @@ in }; + meta = { + maintainers = lib.maintainers.mic92; + }; + ###### implementation diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix index 0b79a996dc78..93f5c1ca5f55 100644 --- a/nixos/modules/services/databases/postgresql.nix +++ b/nixos/modules/services/databases/postgresql.nix @@ -34,13 +34,7 @@ in services.postgresql = { - enable = mkOption { - type = types.bool; - default = false; - description = '' - Whether to run PostgreSQL. - ''; - }; + enable = mkEnableOption "PostgreSQL Server"; package = mkOption { type = types.package; diff --git a/nixos/modules/services/editors/emacs.xml b/nixos/modules/services/editors/emacs.xml index 03483f69fa2f..74c60014dcea 100644 --- a/nixos/modules/services/editors/emacs.xml +++ b/nixos/modules/services/editors/emacs.xml @@ -294,7 +294,7 @@ https://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides If you are not on NixOS or want to install this particular Emacs only for yourself, you can do so by adding it to your <filename>~/.config/nixpkgs/config.nix</filename> (see - <link xlink:href="http://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides">Nixpkgs + <link xlink:href="https://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides">Nixpkgs manual</link>): <example xml:id="module-services-emacs-config-nix"> <title>Custom Emacs in <filename>~/.config/nixpkgs/config.nix</filename></title> diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix index 19e11b31d9ca..608f64a68fb0 100644 --- a/nixos/modules/services/mail/postfix.nix +++ b/nixos/modules/services/mail/postfix.nix @@ -269,6 +269,7 @@ in }; enableSmtp = mkOption { + type = types.bool; default = true; description = "Whether to enable smtp in master.cf."; }; diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix index 21e92cfee016..ed1439745ac9 100644 --- a/nixos/modules/services/mail/roundcube.nix +++ b/nixos/modules/services/mail/roundcube.nix @@ -7,7 +7,7 @@ let fpm = config.services.phpfpm.pools.roundcube; localDB = cfg.database.host == "localhost"; user = cfg.database.username; - phpWithPspell = pkgs.php.withExtensions (e: [ e.pspell ] ++ pkgs.php.enabledExtensions); + phpWithPspell = pkgs.php.withExtensions ({ enabled, all }: [ all.pspell ] ++ enabled); in { options.services.roundcube = { diff --git a/nixos/modules/services/mail/spamassassin.nix b/nixos/modules/services/mail/spamassassin.nix index f6c0b9c794c9..4e642542ec66 100644 --- a/nixos/modules/services/mail/spamassassin.nix +++ b/nixos/modules/services/mail/spamassassin.nix @@ -15,6 +15,7 @@ in enable = mkEnableOption "the SpamAssassin daemon"; debug = mkOption { + type = types.bool; default = false; description = "Whether to run the SpamAssassin daemon in debug mode"; }; diff --git a/nixos/modules/services/misc/autofs.nix b/nixos/modules/services/misc/autofs.nix index 84f49f964b12..5e7c1e668288 100644 --- a/nixos/modules/services/misc/autofs.nix +++ b/nixos/modules/services/misc/autofs.nix @@ -57,6 +57,7 @@ in }; debug = mkOption { + type = types.bool; default = false; description = '' Pass -d and -7 to automount and write log to the system journal. diff --git a/nixos/modules/services/misc/confd.nix b/nixos/modules/services/misc/confd.nix index 8e9bec15dd4f..c1ebdb3dde91 100755 --- a/nixos/modules/services/misc/confd.nix +++ b/nixos/modules/services/misc/confd.nix @@ -75,7 +75,7 @@ in { wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; serviceConfig = { - ExecStart = "${cfg.package.bin}/bin/confd"; + ExecStart = "${cfg.package}/bin/confd"; }; }; diff --git a/nixos/modules/services/misc/disnix.nix b/nixos/modules/services/misc/disnix.nix index 0776ac679088..69386cdbb381 100644 --- a/nixos/modules/services/misc/disnix.nix +++ b/nixos/modules/services/misc/disnix.nix @@ -25,10 +25,7 @@ in description = "Whether to support multi-user mode by enabling the Disnix D-Bus service"; }; - useWebServiceInterface = mkOption { - default = false; - description = "Whether to enable the DisnixWebService interface running on Apache Tomcat"; - }; + useWebServiceInterface = mkEnableOption "the DisnixWebService interface running on Apache Tomcat"; package = mkOption { type = types.path; diff --git a/nixos/modules/services/misc/etcd.nix b/nixos/modules/services/misc/etcd.nix index 7322e1c080be..32360d43768a 100644 --- a/nixos/modules/services/misc/etcd.nix +++ b/nixos/modules/services/misc/etcd.nix @@ -178,7 +178,7 @@ in { serviceConfig = { Type = "notify"; - ExecStart = "${pkgs.etcd.bin}/bin/etcd"; + ExecStart = "${pkgs.etcd}/bin/etcd"; User = "etcd"; LimitNOFILE = 40000; }; diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix index 38910a5a005d..f8bcedc94fe2 100644 --- a/nixos/modules/services/misc/gitea.nix +++ b/nixos/modules/services/misc/gitea.nix @@ -14,53 +14,9 @@ let RUN_USER = ${cfg.user} RUN_MODE = prod - [database] - DB_TYPE = ${cfg.database.type} - ${optionalString (usePostgresql || useMysql) '' - HOST = ${if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port} - NAME = ${cfg.database.name} - USER = ${cfg.database.user} - PASSWD = #dbpass# - ''} - ${optionalString useSqlite '' - PATH = ${cfg.database.path} - ''} - ${optionalString usePostgresql '' - SSL_MODE = disable - ''} - - [repository] - ROOT = ${cfg.repositoryRoot} - - [server] - DOMAIN = ${cfg.domain} - HTTP_ADDR = ${cfg.httpAddress} - HTTP_PORT = ${toString cfg.httpPort} - ROOT_URL = ${cfg.rootUrl} - STATIC_ROOT_PATH = ${cfg.staticRootPath} - LFS_JWT_SECRET = #jwtsecret# - - [session] - COOKIE_NAME = session - COOKIE_SECURE = ${boolToString cfg.cookieSecure} - - [security] - SECRET_KEY = #secretkey# - INSTALL_LOCK = true - - [log] - ROOT_PATH = ${cfg.log.rootPath} - LEVEL = ${cfg.log.level} - - [service] - DISABLE_REGISTRATION = ${boolToString cfg.disableRegistration} - - ${optionalString (cfg.mailerPasswordFile != null) '' - [mailer] - PASSWD = #mailerpass# - ''} - - ${cfg.extraConfig} + ${generators.toINI {} cfg.settings} + + ${optionalString (cfg.extraConfig != null) cfg.extraConfig} ''; in @@ -279,9 +235,36 @@ in ''; }; + settings = mkOption { + type = with types; attrsOf (attrsOf (oneOf [ bool int str ])); + default = {}; + description = '' + Gitea configuration. Refer to <link xlink:href="https://docs.gitea.io/en-us/config-cheat-sheet/"/> + for details on supported values. + ''; + example = literalExample '' + { + "cron.sync_external_users" = { + RUN_AT_START = true; + SCHEDULE = "@every 24h"; + UPDATE_EXISTING = true; + }; + mailer = { + ENABLED = true; + MAILER_TYPE = "sendmail"; + FROM = "do-not-reply@example.org"; + SENDMAIL_PATH = "${pkgs.system-sendmail}/bin/sendmail"; + }; + other = { + SHOW_FOOTER_VERSION = false; + }; + } + ''; + }; + extraConfig = mkOption { - type = types.str; - default = ""; + type = with types; nullOr str; + default = null; description = "Configuration lines appended to the generated gitea configuration file."; }; }; @@ -294,6 +277,62 @@ in } ]; + services.gitea.settings = { + database = mkMerge [ + { + DB_TYPE = cfg.database.type; + } + (mkIf (useMysql || usePostgresql) { + HOST = if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port; + NAME = cfg.database.name; + USER = cfg.database.user; + PASSWD = "#dbpass#"; + }) + (mkIf useSqlite { + PATH = cfg.database.path; + }) + (mkIf usePostgresql { + SSL_MODE = "disable"; + }) + ]; + + repository = { + ROOT = cfg.repositoryRoot; + }; + + server = { + DOMAIN = cfg.domain; + HTTP_ADDR = cfg.httpAddress; + HTTP_PORT = cfg.httpPort; + ROOT_URL = cfg.rootUrl; + STATIC_ROOT_PATH = cfg.staticRootPath; + LFS_JWT_SECRET = "#jwtsecret#"; + }; + + session = { + COOKIE_NAME = "session"; + COOKIE_SECURE = cfg.cookieSecure; + }; + + security = { + SECRET_KEY = "#secretkey#"; + INSTALL_LOCK = true; + }; + + log = { + ROOT_PATH = cfg.log.rootPath; + LEVEL = cfg.log.level; + }; + + service = { + DISABLE_REGISTRATION = cfg.disableRegistration; + }; + + mailer = mkIf (cfg.mailerPasswordFile != null) { + PASSWD = "#mailerpass#"; + }; + }; + services.postgresql = optionalAttrs (usePostgresql && cfg.database.createDatabase) { enable = mkDefault true; @@ -335,7 +374,7 @@ in description = "gitea"; after = [ "network.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service"; wantedBy = [ "multi-user.target" ]; - path = [ gitea.bin pkgs.gitAndTools.git ]; + path = [ gitea pkgs.gitAndTools.git ]; preStart = let runConfig = "${cfg.stateDir}/custom/conf/app.ini"; @@ -347,11 +386,11 @@ in cp -f ${configFile} ${runConfig} if [ ! -e ${secretKey} ]; then - ${gitea.bin}/bin/gitea generate secret SECRET_KEY > ${secretKey} + ${gitea}/bin/gitea generate secret SECRET_KEY > ${secretKey} fi if [ ! -e ${jwtSecret} ]; then - ${gitea.bin}/bin/gitea generate secret LFS_JWT_SECRET > ${jwtSecret} + ${gitea}/bin/gitea generate secret LFS_JWT_SECRET > ${jwtSecret} fi KEY="$(head -n1 ${secretKey})" @@ -374,7 +413,7 @@ in HOOKS=$(find ${cfg.repositoryRoot} -mindepth 4 -maxdepth 6 -type f -wholename "*git/hooks/*") if [ "$HOOKS" ] then - sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${gitea.bin}/bin/gitea,g' $HOOKS + sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${gitea}/bin/gitea,g' $HOOKS sed -ri 's,/nix/store/[a-z0-9.-]+/bin/env,${pkgs.coreutils}/bin/env,g' $HOOKS sed -ri 's,/nix/store/[a-z0-9.-]+/bin/bash,${pkgs.bash}/bin/bash,g' $HOOKS sed -ri 's,/nix/store/[a-z0-9.-]+/bin/perl,${pkgs.perl}/bin/perl,g' $HOOKS @@ -383,7 +422,7 @@ in # update command option in authorized_keys if [ -r ${cfg.stateDir}/.ssh/authorized_keys ] then - sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${gitea.bin}/bin/gitea,g' ${cfg.stateDir}/.ssh/authorized_keys + sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${gitea}/bin/gitea,g' ${cfg.stateDir}/.ssh/authorized_keys fi ''; @@ -392,7 +431,7 @@ in User = cfg.user; Group = "gitea"; WorkingDirectory = cfg.stateDir; - ExecStart = "${gitea.bin}/bin/gitea web"; + ExecStart = "${gitea}/bin/gitea web"; Restart = "always"; # Filesystem @@ -435,9 +474,12 @@ in users.groups.gitea = {}; - warnings = optional (cfg.database.password != "") - ''config.services.gitea.database.password will be stored as plaintext - in the Nix store. Use database.passwordFile instead.''; + warnings = + optional (cfg.database.password != "") '' + config.services.gitea.database.password will be stored as plaintext in the Nix store. Use database.passwordFile instead.'' ++ + optional (cfg.extraConfig != null) '' + services.gitea.`extraConfig` is deprecated, please use services.gitea.`settings`. + ''; # Create database passwordFile default when password is configured. services.gitea.database.passwordFile = @@ -450,7 +492,7 @@ in description = "gitea dump"; after = [ "gitea.service" ]; wantedBy = [ "default.target" ]; - path = [ gitea.bin ]; + path = [ gitea ]; environment = { USER = cfg.user; @@ -461,7 +503,7 @@ in serviceConfig = { Type = "oneshot"; User = cfg.user; - ExecStart = "${gitea.bin}/bin/gitea dump"; + ExecStart = "${gitea}/bin/gitea dump"; WorkingDirectory = cfg.stateDir; }; }; diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix index aa9589853797..730166b04d20 100644 --- a/nixos/modules/services/misc/gitlab.nix +++ b/nixos/modules/services/misc/gitlab.nix @@ -180,7 +180,7 @@ let ${optionalString (cfg.smtp.passwordFile != null) ''password: "@smtpPassword@",''} domain: "${cfg.smtp.domain}", ${optionalString (cfg.smtp.authentication != null) "authentication: :${cfg.smtp.authentication},"} - enable_starttls_auto: ${toString cfg.smtp.enableStartTLSAuto}, + enable_starttls_auto: ${boolToString cfg.smtp.enableStartTLSAuto}, ca_file: "/etc/ssl/certs/ca-certificates.crt", openssl_verify_mode: '${cfg.smtp.opensslVerifyMode}' } diff --git a/nixos/modules/services/misc/gogs.nix b/nixos/modules/services/misc/gogs.nix index ee99967c261b..c5070aaa356a 100644 --- a/nixos/modules/services/misc/gogs.nix +++ b/nixos/modules/services/misc/gogs.nix @@ -200,7 +200,7 @@ in description = "Gogs (Go Git Service)"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; - path = [ pkgs.gogs.bin ]; + path = [ pkgs.gogs ]; preStart = let runConfig = "${cfg.stateDir}/custom/conf/app.ini"; @@ -230,7 +230,7 @@ in HOOKS=$(find ${cfg.repositoryRoot} -mindepth 4 -maxdepth 4 -type f -wholename "*git/hooks/*") if [ "$HOOKS" ] then - sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gogs,${pkgs.gogs.bin}/bin/gogs,g' $HOOKS + sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gogs,${pkgs.gogs}/bin/gogs,g' $HOOKS sed -ri 's,/nix/store/[a-z0-9.-]+/bin/env,${pkgs.coreutils}/bin/env,g' $HOOKS sed -ri 's,/nix/store/[a-z0-9.-]+/bin/bash,${pkgs.bash}/bin/bash,g' $HOOKS sed -ri 's,/nix/store/[a-z0-9.-]+/bin/perl,${pkgs.perl}/bin/perl,g' $HOOKS @@ -242,7 +242,7 @@ in User = cfg.user; Group = cfg.group; WorkingDirectory = cfg.stateDir; - ExecStart = "${pkgs.gogs.bin}/bin/gogs web"; + ExecStart = "${pkgs.gogs}/bin/gogs web"; Restart = "always"; }; diff --git a/nixos/modules/services/misc/leaps.nix b/nixos/modules/services/misc/leaps.nix index d4e88ecbebdb..ef89d3e64d0c 100644 --- a/nixos/modules/services/misc/leaps.nix +++ b/nixos/modules/services/misc/leaps.nix @@ -55,7 +55,7 @@ in Restart = "on-failure"; WorkingDirectory = stateDir; PrivateTmp = true; - ExecStart = "${pkgs.leaps.bin}/bin/leaps -path ${toString cfg.path} -address ${cfg.address}:${toString cfg.port}"; + ExecStart = "${pkgs.leaps}/bin/leaps -path ${toString cfg.path} -address ${cfg.address}:${toString cfg.port}"; }; }; }; diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix index 0c2407e1dd2f..2577cb78e96e 100644 --- a/nixos/modules/services/misc/nix-daemon.nix +++ b/nixos/modules/services/misc/nix-daemon.nix @@ -283,7 +283,7 @@ in trustedBinaryCaches = mkOption { type = types.listOf types.str; default = [ ]; - example = [ "http://hydra.nixos.org/" ]; + example = [ "https://hydra.nixos.org/" ]; description = '' List of binary cache URLs that non-root users can use (in addition to those specified using @@ -510,8 +510,7 @@ in system.activationScripts.nix = stringAfter [ "etc" "users" ] '' - # Create directories in /nix. - ${nix}/bin/nix ping-store --no-net + install -m 0755 -d /nix/var/nix/{gcroots,profiles}/per-user # Subscribe the root user to the NixOS channel by default. if [ ! -e "/root/.nix-channels" ]; then diff --git a/nixos/modules/services/misc/octoprint.nix b/nixos/modules/services/misc/octoprint.nix index 651ed3743884..7a71d2c8c6aa 100644 --- a/nixos/modules/services/misc/octoprint.nix +++ b/nixos/modules/services/misc/octoprint.nix @@ -17,9 +17,9 @@ let cfgUpdate = pkgs.writeText "octoprint-config.yaml" (builtins.toJSON fullConfig); - pluginsEnv = pkgs.python.buildEnv.override { - extraLibs = cfg.plugins pkgs.octoprint-plugins; - }; + pluginsEnv = package.python.withPackages (ps: [ps.octoprint] ++ (cfg.plugins ps)); + + package = pkgs.octoprint; in { @@ -106,7 +106,6 @@ in wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; path = [ pluginsEnv ]; - environment.PYTHONPATH = makeSearchPathOutput "lib" pkgs.python.sitePackages [ pluginsEnv ]; preStart = '' if [ -e "${cfg.stateDir}/config.yaml" ]; then @@ -119,7 +118,7 @@ in ''; serviceConfig = { - ExecStart = "${pkgs.octoprint}/bin/octoprint serve -b ${cfg.stateDir}"; + ExecStart = "${pluginsEnv}/bin/octoprint serve -b ${cfg.stateDir}"; User = cfg.user; Group = cfg.group; }; diff --git a/nixos/modules/services/misc/pykms.nix b/nixos/modules/services/misc/pykms.nix index 25aa27ae7673..d6aeae48ccb6 100644 --- a/nixos/modules/services/misc/pykms.nix +++ b/nixos/modules/services/misc/pykms.nix @@ -82,6 +82,7 @@ in { ]); ProtectHome = "tmpfs"; WorkingDirectory = libDir; + SyslogIdentifier = "pykms"; Restart = "on-failure"; MemoryLimit = cfg.memoryLimit; }; diff --git a/nixos/modules/services/misc/ssm-agent.nix b/nixos/modules/services/misc/ssm-agent.nix index e951a4c7ffa8..f7c05deeecb5 100644 --- a/nixos/modules/services/misc/ssm-agent.nix +++ b/nixos/modules/services/misc/ssm-agent.nix @@ -35,7 +35,7 @@ in { path = [ fake-lsb-release ]; serviceConfig = { - ExecStart = "${cfg.package.bin}/bin/agent"; + ExecStart = "${cfg.package}/bin/agent"; KillMode = "process"; Restart = "on-failure"; RestartSec = "15min"; @@ -43,4 +43,3 @@ in { }; }; } - diff --git a/nixos/modules/services/misc/sssd.nix b/nixos/modules/services/misc/sssd.nix index 36008d257410..77f6ccfe64f0 100644 --- a/nixos/modules/services/misc/sssd.nix +++ b/nixos/modules/services/misc/sssd.nix @@ -75,6 +75,11 @@ in { }; system.nssModules = optional cfg.enable pkgs.sssd; + system.nssDatabases = { + passwd = [ "sss" ]; + shadow = [ "sss" ]; + services = [ "sss" ]; + }; services.dbus.packages = [ pkgs.sssd ]; }) diff --git a/nixos/modules/services/monitoring/bosun.nix b/nixos/modules/services/monitoring/bosun.nix index b1c12cce1f80..04e9da1c81a3 100644 --- a/nixos/modules/services/monitoring/bosun.nix +++ b/nixos/modules/services/monitoring/bosun.nix @@ -148,7 +148,7 @@ in { User = cfg.user; Group = cfg.group; ExecStart = '' - ${cfg.package.bin}/bin/bosun -c ${configFile} + ${cfg.package}/bin/bosun -c ${configFile} ''; }; }; diff --git a/nixos/modules/services/monitoring/grafana-reporter.nix b/nixos/modules/services/monitoring/grafana-reporter.nix index b5a78e4583e1..893c15d568bd 100644 --- a/nixos/modules/services/monitoring/grafana-reporter.nix +++ b/nixos/modules/services/monitoring/grafana-reporter.nix @@ -59,7 +59,7 @@ in { "-templates ${cfg.templateDir}" ]; in { - ExecStart = "${pkgs.grafana_reporter.bin}/bin/grafana-reporter ${args}"; + ExecStart = "${pkgs.grafana_reporter}/bin/grafana-reporter ${args}"; }; }; }; diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix index 0f8bc2471e33..b0c81a46d4d8 100644 --- a/nixos/modules/services/monitoring/grafana.nix +++ b/nixos/modules/services/monitoring/grafana.nix @@ -535,7 +535,7 @@ in { ${optionalString cfg.provision.enable '' export GF_PATHS_PROVISIONING=${provisionConfDir}; ''} - exec ${cfg.package.bin}/bin/grafana-server -homepath ${cfg.dataDir} + exec ${cfg.package}/bin/grafana-server -homepath ${cfg.dataDir} ''; serviceConfig = { WorkingDirectory = cfg.dataDir; diff --git a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix index 045e48a3d0f8..01276366e97b 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix @@ -58,7 +58,7 @@ in in { serviceConfig = { ExecStart = '' - ${pkgs.prometheus-snmp-exporter.bin}/bin/snmp_exporter \ + ${pkgs.prometheus-snmp-exporter}/bin/snmp_exporter \ --config.file=${escapeShellArg configFile} \ --log.format=${escapeShellArg cfg.logFormat} \ --log.level=${cfg.logLevel} \ diff --git a/nixos/modules/services/monitoring/scollector.nix b/nixos/modules/services/monitoring/scollector.nix index 38cd2213de76..6f13ce889cba 100644 --- a/nixos/modules/services/monitoring/scollector.nix +++ b/nixos/modules/services/monitoring/scollector.nix @@ -118,7 +118,7 @@ in { serviceConfig = { User = cfg.user; Group = cfg.group; - ExecStart = "${cfg.package.bin}/bin/scollector -conf=${conf} ${lib.concatStringsSep " " cfg.extraOpts}"; + ExecStart = "${cfg.package}/bin/scollector -conf=${conf} ${lib.concatStringsSep " " cfg.extraOpts}"; }; }; diff --git a/nixos/modules/services/network-filesystems/ipfs.nix b/nixos/modules/services/network-filesystems/ipfs.nix index b6d881afd7bd..880f70ae1410 100644 --- a/nixos/modules/services/network-filesystems/ipfs.nix +++ b/nixos/modules/services/network-filesystems/ipfs.nix @@ -37,9 +37,7 @@ let baseService = recursiveUpdate commonEnv { wants = [ "ipfs-init.service" ]; # NB: migration must be performed prior to pre-start, else we get the failure message! - preStart = '' - ipfs repo fsck # workaround for BUG #4212 (https://github.com/ipfs/go-ipfs/issues/4214) - '' + optionalString cfg.autoMount '' + preStart = optionalString cfg.autoMount '' ipfs --local config Mounts.FuseAllowOther --json true ipfs --local config Mounts.IPFS ${cfg.ipfsMountDir} ipfs --local config Mounts.IPNS ${cfg.ipnsMountDir} diff --git a/nixos/modules/services/networking/consul.nix b/nixos/modules/services/networking/consul.nix index 689cbc8a986d..f7d2afead06c 100644 --- a/nixos/modules/services/networking/consul.nix +++ b/nixos/modules/services/networking/consul.nix @@ -179,15 +179,15 @@ in (filterAttrs (n: _: hasPrefix "consul.d/" n) config.environment.etc); serviceConfig = { - ExecStart = "@${cfg.package.bin}/bin/consul consul agent -config-dir /etc/consul.d" + ExecStart = "@${cfg.package}/bin/consul consul agent -config-dir /etc/consul.d" + concatMapStrings (n: " -config-file ${n}") configFiles; - ExecReload = "${cfg.package.bin}/bin/consul reload"; + ExecReload = "${cfg.package}/bin/consul reload"; PermissionsStartOnly = true; User = if cfg.dropPrivileges then "consul" else null; Restart = "on-failure"; TimeoutStartSec = "infinity"; } // (optionalAttrs (cfg.leaveOnStop) { - ExecStop = "${cfg.package.bin}/bin/consul leave"; + ExecStop = "${cfg.package}/bin/consul leave"; }); path = with pkgs; [ iproute gnugrep gawk consul ]; @@ -238,7 +238,7 @@ in serviceConfig = { ExecStart = '' - ${cfg.alerts.package.bin}/bin/consul-alerts start \ + ${cfg.alerts.package}/bin/consul-alerts start \ --alert-addr=${cfg.alerts.listenAddr} \ --consul-addr=${cfg.alerts.consulAddr} \ ${optionalString cfg.alerts.watchChecks "--watch-checks"} \ diff --git a/nixos/modules/services/networking/flannel.nix b/nixos/modules/services/networking/flannel.nix index dd2f6454e954..4c040112d28d 100644 --- a/nixos/modules/services/networking/flannel.nix +++ b/nixos/modules/services/networking/flannel.nix @@ -19,8 +19,8 @@ in { package = mkOption { description = "Package to use for flannel"; type = types.package; - default = pkgs.flannel.bin; - defaultText = "pkgs.flannel.bin"; + default = pkgs.flannel; + defaultText = "pkgs.flannel"; }; publicIp = mkOption { @@ -167,7 +167,7 @@ in { touch /run/flannel/docker '' + optionalString (cfg.storageBackend == "etcd") '' echo "setting network configuration" - until ${pkgs.etcdctl.bin}/bin/etcdctl set /coreos.com/network/config '${builtins.toJSON networkConfig}' + until ${pkgs.etcdctl}/bin/etcdctl set /coreos.com/network/config '${builtins.toJSON networkConfig}' do echo "setting network configuration, retry" sleep 1 diff --git a/nixos/modules/services/networking/gogoclient.nix b/nixos/modules/services/networking/gogoclient.nix index c9b03bca7112..99455b183144 100644 --- a/nixos/modules/services/networking/gogoclient.nix +++ b/nixos/modules/services/networking/gogoclient.nix @@ -19,6 +19,7 @@ in ''; }; autorun = mkOption { + type = types.bool; default = true; description = '' Whether to automatically start the tunnel. diff --git a/nixos/modules/services/networking/hostapd.nix b/nixos/modules/services/networking/hostapd.nix index 12c0626a9414..5d73038363a9 100644 --- a/nixos/modules/services/networking/hostapd.nix +++ b/nixos/modules/services/networking/hostapd.nix @@ -20,12 +20,14 @@ let ssid=${cfg.ssid} hw_mode=${cfg.hwMode} channel=${toString cfg.channel} + ${optionalString (cfg.countryCode != null) ''country_code=${cfg.countryCode}''} + ${optionalString (cfg.countryCode != null) ''ieee80211d=1''} # logging (debug level) logger_syslog=-1 - logger_syslog_level=2 + logger_syslog_level=${toString cfg.logLevel} logger_stdout=-1 - logger_stdout_level=2 + logger_stdout_level=${toString cfg.logLevel} ctrl_interface=/run/hostapd ctrl_interface_group=${cfg.group} @@ -72,6 +74,7 @@ in }; noScan = mkOption { + type = types.bool; default = false; description = '' Do not scan for overlapping BSSs in HT40+/- mode. @@ -127,6 +130,7 @@ in }; wpa = mkOption { + type = types.bool; default = true; description = '' Enable WPA (IEEE 802.11i/D3.0) to authenticate with the access point. @@ -145,6 +149,35 @@ in ''; }; + logLevel = mkOption { + default = 2; + type = types.int; + description = '' + Levels (minimum value for logged events): + 0 = verbose debugging + 1 = debugging + 2 = informational messages + 3 = notification + 4 = warning + ''; + }; + + countryCode = mkOption { + default = null; + example = "US"; + type = with types; nullOr str; + description = '' + Country code (ISO/IEC 3166-1). Used to set regulatory domain. + Set as needed to indicate country in which device is operating. + This can limit available channels and transmit power. + These two octets are used as the first two octets of the Country String + (dot11CountryString). + If set this enables IEEE 802.11d. This advertises the countryCode and + the set of allowed channels and transmit power levels based on the + regulatory limits. + ''; + }; + extraConfig = mkOption { default = ""; example = '' @@ -165,6 +198,8 @@ in environment.systemPackages = [ pkgs.hostapd ]; + services.udev.packages = optional (cfg.countryCode != null) [ pkgs.crda ]; + systemd.services.hostapd = { description = "hostapd wireless AP"; diff --git a/nixos/modules/services/networking/openfire.nix b/nixos/modules/services/networking/openfire.nix index c74f3611f79e..fe0499d52323 100644 --- a/nixos/modules/services/networking/openfire.nix +++ b/nixos/modules/services/networking/openfire.nix @@ -12,6 +12,7 @@ with lib; enable = mkEnableOption "OpenFire XMPP server"; usePostgreSQL = mkOption { + type = types.bool; default = true; description = " Whether you use PostgreSQL service for your storage back-end. diff --git a/nixos/modules/services/networking/prosody.nix b/nixos/modules/services/networking/prosody.nix index 7a503e711665..cdd341c9fb62 100644 --- a/nixos/modules/services/networking/prosody.nix +++ b/nixos/modules/services/networking/prosody.nix @@ -1,9 +1,7 @@ { config, lib, pkgs, ... }: with lib; - let - cfg = config.services.prosody; sslOpts = { ... }: { @@ -30,8 +28,21 @@ let }; }; + discoOpts = { + options = { + url = mkOption { + type = types.str; + description = "URL of the endpoint you want to make discoverable"; + }; + description = mkOption { + type = types.str; + description = "A short description of the endpoint you want to advertise"; + }; + }; + }; + moduleOpts = { - # Generally required + # Required for compliance with https://compliance.conversations.im/about/ roster = mkOption { type = types.bool; default = true; @@ -69,6 +80,18 @@ let description = "Keep multiple clients in sync"; }; + csi = mkOption { + type = types.bool; + default = true; + description = "Implements the CSI protocol that allows clients to report their active/inactive state to the server"; + }; + + cloud_notify = mkOption { + type = types.bool; + default = true; + description = "Push notifications to inform users of new messages or other pertinent information even when they have no XMPP clients online"; + }; + pep = mkOption { type = types.bool; default = true; @@ -89,10 +112,22 @@ let vcard = mkOption { type = types.bool; - default = true; + default = false; description = "Allow users to set vCards"; }; + vcard_legacy = mkOption { + type = types.bool; + default = true; + description = "Converts users profiles and Avatars between old and new formats"; + }; + + bookmarks = mkOption { + type = types.bool; + default = true; + description = "Allows interop between older clients that use XEP-0048: Bookmarks in its 1.0 version and recent clients which use it in PEP"; + }; + # Nice to have version = mkOption { type = types.bool; @@ -126,10 +161,16 @@ let mam = mkOption { type = types.bool; - default = false; + default = true; description = "Store messages in an archive and allow users to access it"; }; + smacks = mkOption { + type = types.bool; + default = true; + description = "Allow a client to resume a disconnected session, and prevent message loss"; + }; + # Admin interfaces admin_adhoc = mkOption { type = types.bool; @@ -137,6 +178,18 @@ let description = "Allows administration via an XMPP client that supports ad-hoc commands"; }; + http_files = mkOption { + type = types.bool; + default = true; + description = "Serve static files from a directory over HTTP"; + }; + + proxy65 = mkOption { + type = types.bool; + default = true; + description = "Enables a file transfer proxy service which clients behind NAT can use"; + }; + admin_telnet = mkOption { type = types.bool; default = false; @@ -156,12 +209,6 @@ let description = "Enable WebSocket support"; }; - http_files = mkOption { - type = types.bool; - default = false; - description = "Serve static files from a directory over HTTP"; - }; - # Other specific functionality limits = mkOption { type = types.bool; @@ -210,13 +257,6 @@ let default = false; description = "Legacy authentication. Only used by some old clients and bots"; }; - - proxy65 = mkOption { - type = types.bool; - default = false; - description = "Enables a file transfer proxy service which clients behind NAT can use"; - }; - }; toLua = x: @@ -235,6 +275,158 @@ let }; ''; + mucOpts = { ... }: { + options = { + domain = mkOption { + type = types.str; + description = "Domain name of the MUC"; + }; + name = mkOption { + type = types.str; + description = "The name to return in service discovery responses for the MUC service itself"; + default = "Prosody Chatrooms"; + }; + restrictRoomCreation = mkOption { + type = types.enum [ true false "admin" "local" ]; + default = false; + description = "Restrict room creation to server admins"; + }; + maxHistoryMessages = mkOption { + type = types.int; + default = 20; + description = "Specifies a limit on what each room can be configured to keep"; + }; + roomLocking = mkOption { + type = types.bool; + default = true; + description = '' + Enables room locking, which means that a room must be + configured before it can be used. Locked rooms are invisible + and cannot be entered by anyone but the creator + ''; + }; + roomLockTimeout = mkOption { + type = types.int; + default = 300; + description = '' + Timout after which the room is destroyed or unlocked if not + configured, in seconds + ''; + }; + tombstones = mkOption { + type = types.bool; + default = true; + description = '' + When a room is destroyed, it leaves behind a tombstone which + prevents the room being entered or recreated. It also allows + anyone who was not in the room at the time it was destroyed + to learn about it, and to update their bookmarks. Tombstones + prevents the case where someone could recreate a previously + semi-anonymous room in order to learn the real JIDs of those + who often join there. + ''; + }; + tombstoneExpiry = mkOption { + type = types.int; + default = 2678400; + description = '' + This settings controls how long a tombstone is considered + valid. It defaults to 31 days. After this time, the room in + question can be created again. + ''; + }; + + vcard_muc = mkOption { + type = types.bool; + default = true; + description = "Adds the ability to set vCard for Multi User Chat rooms"; + }; + + # Extra parameters. Defaulting to prosody default values. + # Adding them explicitly to make them visible from the options + # documentation. + # + # See https://prosody.im/doc/modules/mod_muc for more details. + roomDefaultPublic = mkOption { + type = types.bool; + default = true; + description = "If set, the MUC rooms will be public by default."; + }; + roomDefaultMembersOnly = mkOption { + type = types.bool; + default = false; + description = "If set, the MUC rooms will only be accessible to the members by default."; + }; + roomDefaultModerated = mkOption { + type = types.bool; + default = false; + description = "If set, the MUC rooms will be moderated by default."; + }; + roomDefaultPublicJids = mkOption { + type = types.bool; + default = false; + description = "If set, the MUC rooms will display the public JIDs by default."; + }; + roomDefaultChangeSubject = mkOption { + type = types.bool; + default = false; + description = "If set, the rooms will display the public JIDs by default."; + }; + roomDefaultHistoryLength = mkOption { + type = types.int; + default = 20; + description = "Number of history message sent to participants by default."; + }; + roomDefaultLanguage = mkOption { + type = types.str; + default = "en"; + description = "Default room language."; + }; + extraConfig = mkOption { + type = types.lines; + default = ""; + description = "Additional MUC specific configuration"; + }; + }; + }; + + uploadHttpOpts = { ... }: { + options = { + domain = mkOption { + type = types.nullOr types.str; + description = "Domain name for the http-upload service"; + }; + uploadFileSizeLimit = mkOption { + type = types.str; + default = "50 * 1024 * 1024"; + description = "Maximum file size, in bytes. Defaults to 50MB."; + }; + uploadExpireAfter = mkOption { + type = types.str; + default = "60 * 60 * 24 * 7"; + description = "Max age of a file before it gets deleted, in seconds."; + }; + userQuota = mkOption { + type = types.nullOr types.int; + default = null; + example = 1234; + description = '' + Maximum size of all uploaded files per user, in bytes. There + will be no quota if this option is set to null. + ''; + }; + httpUploadPath = mkOption { + type = types.str; + description = '' + Directory where the uploaded files will be stored. By + default, uploaded files are put in a sub-directory of the + default Prosody storage path (usually /var/lib/prosody). + ''; + default = "/var/lib/prosody"; + }; + }; + }; + vHostOpts = { ... }: { options = { @@ -283,6 +475,27 @@ in description = "Whether to enable the prosody server"; }; + xmppComplianceSuite = mkOption { + type = types.bool; + default = true; + description = '' + The XEP-0423 defines a set of recommended XEPs to implement + for a server. It's generally a good idea to implement this + set of extensions if you want to provide your users with a + good XMPP experience. + + This NixOS module aims to provide a "advanced server" + experience as per defined in the XEP-0423[1] specification. + + Setting this option to true will prevent you from building a + NixOS configuration which won't comply with this standard. + You can explicitely decide to ignore this standard if you + know what you are doing by setting this option to false. + + [1] https://xmpp.org/extensions/xep-0423.html + ''; + }; + package = mkOption { type = types.package; description = "Prosody package to use"; @@ -302,6 +515,12 @@ in default = "/var/lib/prosody"; }; + disco_items = mkOption { + type = types.listOf (types.submodule discoOpts); + default = []; + description = "List of discoverable items you want to advertise."; + }; + user = mkOption { type = types.str; default = "prosody"; @@ -320,6 +539,31 @@ in description = "Allow account creation"; }; + # HTTP server-related options + httpPorts = mkOption { + type = types.listOf types.int; + description = "Listening HTTP ports list for this service."; + default = [ 5280 ]; + }; + + httpInterfaces = mkOption { + type = types.listOf types.str; + default = [ "*" "::" ]; + description = "Interfaces on which the HTTP server will listen on."; + }; + + httpsPorts = mkOption { + type = types.listOf types.int; + description = "Listening HTTPS ports list for this service."; + default = [ 5281 ]; + }; + + httpsInterfaces = mkOption { + type = types.listOf types.str; + default = [ "*" "::" ]; + description = "Interfaces on which the HTTPS server will listen on."; + }; + c2sRequireEncryption = mkOption { type = types.bool; default = true; @@ -387,6 +631,26 @@ in description = "Addtional path in which to look find plugins/modules"; }; + uploadHttp = mkOption { + description = '' + Configures the Prosody builtin HTTP server to handle user uploads. + ''; + type = types.nullOr (types.submodule uploadHttpOpts); + default = null; + example = { + domain = "uploads.my-xmpp-example-host.org"; + }; + }; + + muc = mkOption { + type = types.listOf (types.submodule mucOpts); + default = [ ]; + example = [ { + domain = "conference.my-xmpp-example-host.org"; + } ]; + description = "Multi User Chat (MUC) configuration"; + }; + virtualHosts = mkOption { description = "Define the virtual hosts"; @@ -443,9 +707,44 @@ in config = mkIf cfg.enable { + assertions = let + genericErrMsg = '' + + Having a server not XEP-0423-compliant might make your XMPP + experience terrible. See the NixOS manual for further + informations. + + If you know what you're doing, you can disable this warning by + setting config.services.prosody.xmppComplianceSuite to false. + ''; + errors = [ + { assertion = (builtins.length cfg.muc > 0) || !cfg.xmppComplianceSuite; + message = '' + You need to setup at least a MUC domain to comply with + XEP-0423. + '' + genericErrMsg;} + { assertion = cfg.uploadHttp != null || !cfg.xmppComplianceSuite; + message = '' + You need to setup the uploadHttp module through + config.services.prosody.uploadHttp to comply with + XEP-0423. + '' + genericErrMsg;} + ]; + in errors; + environment.systemPackages = [ cfg.package ]; - environment.etc."prosody/prosody.cfg.lua".text = '' + environment.etc."prosody/prosody.cfg.lua".text = + let + httpDiscoItems = if (cfg.uploadHttp != null) + then [{ url = cfg.uploadHttp.domain; description = "HTTP upload endpoint";}] + else []; + mucDiscoItems = builtins.foldl' + (acc: muc: [{ url = muc.domain; description = "${muc.domain} MUC endpoint";}] ++ acc) + [] + cfg.muc; + discoItems = cfg.disco_items ++ httpDiscoItems ++ mucDiscoItems; + in '' pidfile = "/run/prosody/prosody.pid" @@ -472,6 +771,10 @@ in ${ lib.concatStringsSep "\n" (map (x: "${toLua x};") cfg.extraModules)} }; + disco_items = { + ${ lib.concatStringsSep "\n" (builtins.map (x: ''{ "${x.url}", "${x.description}"};'') discoItems)} + }; + allow_registration = ${toLua cfg.allowRegistration} c2s_require_encryption = ${toLua cfg.c2sRequireEncryption} @@ -486,8 +789,44 @@ in authentication = ${toLua cfg.authentication} + http_interfaces = ${toLua cfg.httpInterfaces} + + https_interfaces = ${toLua cfg.httpsInterfaces} + + http_ports = ${toLua cfg.httpPorts} + + https_ports = ${toLua cfg.httpsPorts} + ${ cfg.extraConfig } + ${lib.concatMapStrings (muc: '' + Component ${toLua muc.domain} "muc" + modules_enabled = { "muc_mam"; ${optionalString muc.vcard_muc ''"vcard_muc";'' } } + name = ${toLua muc.name} + restrict_room_creation = ${toLua muc.restrictRoomCreation} + max_history_messages = ${toLua muc.maxHistoryMessages} + muc_room_locking = ${toLua muc.roomLocking} + muc_room_lock_timeout = ${toLua muc.roomLockTimeout} + muc_tombstones = ${toLua muc.tombstones} + muc_tombstone_expiry = ${toLua muc.tombstoneExpiry} + muc_room_default_public = ${toLua muc.roomDefaultPublic} + muc_room_default_members_only = ${toLua muc.roomDefaultMembersOnly} + muc_room_default_moderated = ${toLua muc.roomDefaultModerated} + muc_room_default_public_jids = ${toLua muc.roomDefaultPublicJids} + muc_room_default_change_subject = ${toLua muc.roomDefaultChangeSubject} + muc_room_default_history_length = ${toLua muc.roomDefaultHistoryLength} + muc_room_default_language = ${toLua muc.roomDefaultLanguage} + ${ muc.extraConfig } + '') cfg.muc} + + ${ lib.optionalString (cfg.uploadHttp != null) '' + Component ${toLua cfg.uploadHttp.domain} "http_upload" + http_upload_file_size_limit = ${cfg.uploadHttp.uploadFileSizeLimit} + http_upload_expire_after = ${cfg.uploadHttp.uploadExpireAfter} + ${lib.optionalString (cfg.uploadHttp.userQuota != null) "http_upload_quota = ${toLua cfg.uploadHttp.userQuota}"} + http_upload_path = ${toLua cfg.uploadHttp.httpUploadPath} + ''} + ${ lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: '' VirtualHost "${v.domain}" enabled = ${boolToString v.enabled}; @@ -522,9 +861,22 @@ in PIDFile = "/run/prosody/prosody.pid"; ExecStart = "${cfg.package}/bin/prosodyctl start"; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + + MemoryDenyWriteExecute = true; + PrivateDevices = true; + PrivateMounts = true; + PrivateTmp = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; }; }; }; - + meta.doc = ./prosody.xml; } diff --git a/nixos/modules/services/networking/prosody.xml b/nixos/modules/services/networking/prosody.xml new file mode 100644 index 000000000000..7859cb1578b7 --- /dev/null +++ b/nixos/modules/services/networking/prosody.xml @@ -0,0 +1,88 @@ +<chapter xmlns="http://docbook.org/ns/docbook" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:xi="http://www.w3.org/2001/XInclude" + version="5.0" + xml:id="module-services-prosody"> + <title>Prosody</title> + <para> + <link xlink:href="https://prosody.im/">Prosody</link> is an open-source, modern XMPP server. + </para> + <section xml:id="module-services-prosody-basic-usage"> + <title>Basic usage</title> + + <para> + A common struggle for most XMPP newcomers is to find the right set + of XMPP Extensions (XEPs) to setup. Forget to activate a few of + those and your XMPP experience might turn into a nightmare! + </para> + + <para> + The XMPP community tackles this problem by creating a meta-XEP + listing a decent set of XEPs you should implement. This meta-XEP + is issued every year, the 2020 edition being + <link xlink:href="https://xmpp.org/extensions/xep-0423.html">XEP-0423</link>. + </para> + <para> + The NixOS Prosody module will implement most of these recommendend XEPs out of + the box. That being said, two components still require some + manual configuration: the + <link xlink:href="https://xmpp.org/extensions/xep-0045.html">Multi User Chat (MUC)</link> + and the <link xlink:href="https://xmpp.org/extensions/xep-0363.html">HTTP File Upload</link> ones. + You'll need to create a DNS subdomain for each of those. The current convention is to name your + MUC endpoint <literal>conference.example.org</literal> and your HTTP upload domain <literal>upload.example.org</literal>. + </para> + <para> + A good configuration to start with, including a + <link xlink:href="https://xmpp.org/extensions/xep-0045.html">Multi User Chat (MUC)</link> + endpoint as well as a <link xlink:href="https://xmpp.org/extensions/xep-0363.html">HTTP File Upload</link> + endpoint will look like this: + <programlisting> +services.prosody = { + <link linkend="opt-services.prosody.enable">enable</link> = true; + <link linkend="opt-services.prosody.admins">admins</link> = [ "root@example.org" ]; + <link linkend="opt-services.prosody.ssl.cert">ssl.cert</link> = "/var/lib/acme/example.org/fullchain.pem"; + <link linkend="opt-services.prosody.ssl.key">ssl.key</link> = "/var/lib/acme/example.org/key.pem"; + <link linkend="opt-services.prosody.virtualHosts">virtualHosts</link>."example.org" = { + <link linkend="opt-services.prosody.virtualHosts._name__.enabled">enabled</link> = true; + <link linkend="opt-services.prosody.virtualHosts._name__.domain">domain</link> = "example.org"; + <link linkend="opt-services.prosody.virtualHosts._name__.ssl.cert">ssl.cert</link> = "/var/lib/acme/example.org/fullchain.pem"; + <link linkend="opt-services.prosody.virtualHosts._name__.ssl.key">ssl.key</link> = "/var/lib/acme/example.org/key.pem"; + }; + <link linkend="opt-services.prosody.muc">muc</link> = [ { + <link linkend="opt-services.prosody.muc">domain</link> = "conference.example.org"; + } ]; + <link linkend="opt-services.prosody.uploadHttp">uploadHttp</link> = { + <link linkend="opt-services.prosody.uploadHttp.domain">domain</link> = "upload.example.org"; + }; +};</programlisting> + </para> + </section> + <section xml:id="module-services-prosody-letsencrypt"> + <title>Let's Encrypt Configuration</title> + <para> + As you can see in the code snippet from the + <link linkend="module-services-prosody-basic-usage">previous section</link>, + you'll need a single TLS certificate covering your main endpoint, + the MUC one as well as the HTTP Upload one. We can generate such a + certificate by leveraging the ACME + <link linkend="opt-security.acme.certs._name_.extraDomains">extraDomains</link> module option. + </para> + <para> + Provided the setup detailed in the previous section, you'll need the following acme configuration to generate + a TLS certificate for the three endponits: + <programlisting> +security.acme = { + <link linkend="opt-security.acme.email">email</link> = "root@example.org"; + <link linkend="opt-security.acme.acceptTerms">acceptTerms</link> = true; + <link linkend="opt-security.acme.certs">certs</link> = { + "example.org" = { + <link linkend="opt-security.acme.certs._name_.webroot">webroot</link> = "/var/www/example.org"; + <link linkend="opt-security.acme.certs._name_.email">email</link> = "root@example.org"; + <link linkend="opt-security.acme.certs._name_.extraDomains">extraDomains."conference.example.org"</link> = null; + <link linkend="opt-security.acme.certs._name_.extraDomains">extraDomains."upload.example.org"</link> = null; + }; + }; +};</programlisting> + </para> +</section> +</chapter> diff --git a/nixos/modules/services/networking/skydns.nix b/nixos/modules/services/networking/skydns.nix index 6ad18bb22403..e79d6de92644 100644 --- a/nixos/modules/services/networking/skydns.nix +++ b/nixos/modules/services/networking/skydns.nix @@ -83,7 +83,7 @@ in { SKYDNS_NAMESERVERS = concatStringsSep "," cfg.nameservers; }; serviceConfig = { - ExecStart = "${cfg.package.bin}/bin/skydns"; + ExecStart = "${cfg.package}/bin/skydns"; }; }; diff --git a/nixos/modules/services/networking/ssh/lshd.nix b/nixos/modules/services/networking/ssh/lshd.nix index 892e59778c34..41d0584080e4 100644 --- a/nixos/modules/services/networking/ssh/lshd.nix +++ b/nixos/modules/services/networking/ssh/lshd.nix @@ -54,21 +54,25 @@ in }; syslog = mkOption { + type = types.bool; default = true; description = ''Whether to enable syslog output.''; }; passwordAuthentication = mkOption { + type = types.bool; default = true; description = ''Whether to enable password authentication.''; }; publicKeyAuthentication = mkOption { + type = types.bool; default = true; description = ''Whether to enable public key authentication.''; }; rootLogin = mkOption { + type = types.bool; default = false; description = ''Whether to enable remote root login.''; }; @@ -90,11 +94,13 @@ in }; tcpForwarding = mkOption { + type = types.bool; default = true; description = ''Whether to enable TCP/IP forwarding.''; }; x11Forwarding = mkOption { + type = types.bool; default = true; description = ''Whether to enable X11 forwarding.''; }; diff --git a/nixos/modules/services/networking/tcpcrypt.nix b/nixos/modules/services/networking/tcpcrypt.nix index 18f2e135124b..5a91054e1668 100644 --- a/nixos/modules/services/networking/tcpcrypt.nix +++ b/nixos/modules/services/networking/tcpcrypt.nix @@ -15,6 +15,7 @@ in options = { networking.tcpcrypt.enable = mkOption { + type = types.bool; default = false; description = '' Whether to enable opportunistic TCP encryption. If the other end diff --git a/nixos/modules/services/networking/thelounge.nix b/nixos/modules/services/networking/thelounge.nix index 875d8f661697..a1b06703484b 100644 --- a/nixos/modules/services/networking/thelounge.nix +++ b/nixos/modules/services/networking/thelounge.nix @@ -62,7 +62,6 @@ in { 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"; diff --git a/nixos/modules/services/networking/wicd.nix b/nixos/modules/services/networking/wicd.nix index 03c6bd28aaba..aa10a50f876a 100644 --- a/nixos/modules/services/networking/wicd.nix +++ b/nixos/modules/services/networking/wicd.nix @@ -9,6 +9,7 @@ with lib; options = { networking.wicd.enable = mkOption { + type = types.bool; default = false; description = '' Whether to start <command>wicd</command>. Wired and diff --git a/nixos/modules/services/networking/yggdrasil.nix b/nixos/modules/services/networking/yggdrasil.nix index 9e675ecd6f4b..ecd1406b4832 100644 --- a/nixos/modules/services/networking/yggdrasil.nix +++ b/nixos/modules/services/networking/yggdrasil.nix @@ -83,6 +83,14 @@ in { ''; }; + group = mkOption { + type = types.str; + default = "root"; + example = "wheel"; + description = + "Group to grant acces to the Yggdrasil control socket."; + }; + openMulticastPort = mkOption { type = bool; default = false; @@ -144,8 +152,9 @@ in { ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; Restart = "always"; + Group = cfg.group; RuntimeDirectory = "yggdrasil"; - RuntimeDirectoryMode = "0700"; + RuntimeDirectoryMode = "0750"; BindReadOnlyPaths = mkIf configFileProvided [ "${cfg.configFile}" ]; diff --git a/nixos/modules/services/printing/cupsd.nix b/nixos/modules/services/printing/cupsd.nix index 59306d625e6b..e67badfcd29e 100644 --- a/nixos/modules/services/printing/cupsd.nix +++ b/nixos/modules/services/printing/cupsd.nix @@ -153,6 +153,16 @@ in ''; }; + allowFrom = mkOption { + type = types.listOf types.str; + default = [ "localhost" ]; + example = [ "all" ]; + apply = concatMapStringsSep "\n" (x: "Allow ${x}"); + description = '' + From which hosts to allow unconditional access. + ''; + }; + bindirCmds = mkOption { type = types.lines; internal = true; @@ -403,19 +413,19 @@ in <Location /> Order allow,deny - Allow localhost + ${cfg.allowFrom} </Location> <Location /admin> Order allow,deny - Allow localhost + ${cfg.allowFrom} </Location> <Location /admin/conf> AuthType Basic Require user @SYSTEM Order allow,deny - Allow localhost + ${cfg.allowFrom} </Location> <Policy default> diff --git a/nixos/modules/services/security/hologram-agent.nix b/nixos/modules/services/security/hologram-agent.nix index a5087b0a99b4..e37334b3cf5e 100644 --- a/nixos/modules/services/security/hologram-agent.nix +++ b/nixos/modules/services/security/hologram-agent.nix @@ -43,12 +43,12 @@ in { description = "Provide EC2 instance credentials to machines outside of EC2"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; - requires = [ "network-link-dummy0.service" "network-addresses-dummy0.service" ]; + requires = [ "network-link-dummy0.service" "network-addresses-dummy0.service" ]; preStart = '' /run/current-system/sw/bin/rm -fv /run/hologram.sock ''; serviceConfig = { - ExecStart = "${pkgs.hologram.bin}/bin/hologram-agent -debug -conf ${cfgFile} -port ${cfg.httpPort}"; + ExecStart = "${pkgs.hologram}/bin/hologram-agent -debug -conf ${cfgFile} -port ${cfg.httpPort}"; }; }; diff --git a/nixos/modules/services/security/hologram-server.nix b/nixos/modules/services/security/hologram-server.nix index bad02c7440ba..4acf6ae0e218 100644 --- a/nixos/modules/services/security/hologram-server.nix +++ b/nixos/modules/services/security/hologram-server.nix @@ -123,7 +123,7 @@ in { wantedBy = [ "multi-user.target" ]; serviceConfig = { - ExecStart = "${pkgs.hologram.bin}/bin/hologram-server --debug --conf ${cfgFile}"; + ExecStart = "${pkgs.hologram}/bin/hologram-server --debug --conf ${cfgFile}"; }; }; }; diff --git a/nixos/modules/services/security/oauth2_proxy.nix b/nixos/modules/services/security/oauth2_proxy.nix index 46caadee2042..d5c5437329ea 100644 --- a/nixos/modules/services/security/oauth2_proxy.nix +++ b/nixos/modules/services/security/oauth2_proxy.nix @@ -577,7 +577,7 @@ in serviceConfig = { User = "oauth2_proxy"; Restart = "always"; - ExecStart = "${cfg.package.bin}/bin/oauth2_proxy ${configString}"; + ExecStart = "${cfg.package}/bin/oauth2_proxy ${configString}"; EnvironmentFile = mkIf (cfg.keyFile != null) cfg.keyFile; }; }; diff --git a/nixos/modules/services/torrent/deluge.nix b/nixos/modules/services/torrent/deluge.nix index 2f1e7300ca38..45398cb26138 100644 --- a/nixos/modules/services/torrent/deluge.nix +++ b/nixos/modules/services/torrent/deluge.nix @@ -142,7 +142,7 @@ in { description = '' Extra packages available at runtime to enable Deluge's plugins. For example, extraction utilities are required for the built-in "Extractor" plugin. - This always contains unzip, gnutar, xz, p7zip and bzip2. + This always contains unzip, gnutar, xz and bzip2. ''; }; @@ -187,7 +187,7 @@ in { ); # Provide a default set of `extraPackages`. - services.deluge.extraPackages = with pkgs; [ unzip gnutar xz p7zip bzip2 ]; + services.deluge.extraPackages = with pkgs; [ unzip gnutar xz bzip2 ]; systemd.tmpfiles.rules = [ "d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group}" diff --git a/nixos/modules/services/web-apps/mattermost.nix b/nixos/modules/services/web-apps/mattermost.nix index 853347bf86e2..f5c2c356afce 100644 --- a/nixos/modules/services/web-apps/mattermost.nix +++ b/nixos/modules/services/web-apps/mattermost.nix @@ -224,7 +224,7 @@ in serviceConfig = { User = "nobody"; Group = "nogroup"; - ExecStart = "${pkgs.matterircd.bin}/bin/matterircd ${concatStringsSep " " cfg.matterircd.parameters}"; + ExecStart = "${pkgs.matterircd}/bin/matterircd ${concatStringsSep " " cfg.matterircd.parameters}"; WorkingDirectory = "/tmp"; PrivateTmp = true; Restart = "always"; diff --git a/nixos/modules/services/web-apps/mediawiki.nix b/nixos/modules/services/web-apps/mediawiki.nix index e9ed53857d81..0a5b6047bb58 100644 --- a/nixos/modules/services/web-apps/mediawiki.nix +++ b/nixos/modules/services/web-apps/mediawiki.nix @@ -29,7 +29,7 @@ let '') cfg.skins)} ${concatStringsSep "\n" (mapAttrsToList (k: v: '' - ln -s ${v} $out/share/mediawiki/extensions/${k} + ln -s ${if v != null then v else "$src/share/mediawiki/extensions/${k}"} $out/share/mediawiki/extensions/${k} '') cfg.extensions)} ''; }; @@ -204,17 +204,28 @@ in default = {}; type = types.attrsOf types.path; description = '' - List of paths whose content is copied to the 'skins' - subdirectory of the MediaWiki installation. + Attribute set of paths whose content is copied to the <filename>skins</filename> + subdirectory of the MediaWiki installation in addition to the default skins. ''; }; extensions = mkOption { default = {}; - type = types.attrsOf types.path; + type = types.attrsOf (types.nullOr types.path); description = '' - List of paths whose content is copied to the 'extensions' - subdirectory of the MediaWiki installation. + Attribute set of paths whose content is copied to the <filename>extensions</filename> + subdirectory of the MediaWiki installation and enabled in configuration. + + Use <literal>null</literal> instead of path to enable extensions that are part of MediaWiki. + ''; + example = literalExample '' + { + Matomo = pkgs.fetchzip { + url = "https://github.com/DaSchTour/matomo-mediawiki-extension/archive/v4.0.1.tar.gz"; + sha256 = "0g5rd3zp0avwlmqagc59cg9bbkn3r7wx7p6yr80s644mj6dlvs1b"; + }; + ParserFunctions = null; + } ''; }; diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix index 5f6f2bc7a16d..f826096bf608 100644 --- a/nixos/modules/services/web-apps/nextcloud.nix +++ b/nixos/modules/services/web-apps/nextcloud.nix @@ -11,8 +11,8 @@ let base = pkgs.php74; in base.buildEnv { - extensions = e: with e; - base.enabledExtensions ++ [ + extensions = { enabled, all }: with all; + enabled ++ [ apcu redis memcached imagick ]; extraConfig = phpOptionsStr; diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix index 653c17068346..8abee7130d7c 100644 --- a/nixos/modules/services/web-servers/apache-httpd/default.nix +++ b/nixos/modules/services/web-servers/apache-httpd/default.nix @@ -338,7 +338,7 @@ let } '' cat ${php}/etc/php.ini > $out - cat ${php}/lib/custom-php.ini > $out + cat ${php.phpIni} > $out echo "$options" >> $out ''; diff --git a/nixos/modules/services/web-servers/jboss/default.nix b/nixos/modules/services/web-servers/jboss/default.nix index 3a125982831b..ca5b8635fc00 100644 --- a/nixos/modules/services/web-servers/jboss/default.nix +++ b/nixos/modules/services/web-servers/jboss/default.nix @@ -60,6 +60,7 @@ in }; useJK = mkOption { + type = types.bool; default = false; description = "Whether to use to connector to the Apache HTTP server"; }; diff --git a/nixos/modules/services/web-servers/unit/default.nix b/nixos/modules/services/web-servers/unit/default.nix index f8a18954fc99..989866144e1e 100644 --- a/nixos/modules/services/web-servers/unit/default.nix +++ b/nixos/modules/services/web-servers/unit/default.nix @@ -91,41 +91,47 @@ in { description = "Unit App Server"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; - path = with pkgs; [ curl ]; preStart = '' - test -f '${cfg.stateDir}/conf.json' || rm -f '${cfg.stateDir}/conf.json' + [ ! -e '${cfg.stateDir}/conf.json' ] || rm -f '${cfg.stateDir}/conf.json' ''; postStart = '' - curl -X PUT --data-binary '@${configFile}' --unix-socket '/run/unit/control.unit.sock' 'http://localhost/config' + ${pkgs.curl}/bin/curl -X PUT --data-binary '@${configFile}' --unix-socket '/run/unit/control.unit.sock' 'http://localhost/config' ''; serviceConfig = { + Type = "forking"; + PIDFile = "/run/unit/unit.pid"; ExecStart = '' ${cfg.package}/bin/unitd --control 'unix:/run/unit/control.unit.sock' --pid '/run/unit/unit.pid' \ - --log '${cfg.logDir}/unit.log' --state '${cfg.stateDir}' --no-daemon \ + --log '${cfg.logDir}/unit.log' --state '${cfg.stateDir}' \ --user ${cfg.user} --group ${cfg.group} ''; - # User and group - User = cfg.user; - Group = cfg.group; - # Capabilities - AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_SETGID" "CAP_SETUID" ]; + ExecStop = '' + ${pkgs.curl}/bin/curl -X DELETE --unix-socket '/run/unit/control.unit.sock' 'http://localhost/config' + ''; + # Runtime directory and mode + RuntimeDirectory = "unit"; + RuntimeDirectoryMode = "0750"; + # Access write directories + ReadWritePaths = [ cfg.stateDir cfg.logDir ]; # Security NoNewPrivileges = true; # Sandboxing - ProtectSystem = "full"; + ProtectSystem = "strict"; ProtectHome = true; - RuntimeDirectory = "unit"; - RuntimeDirectoryMode = "0750"; PrivateTmp = true; PrivateDevices = true; ProtectHostname = true; ProtectKernelTunables = true; ProtectKernelModules = true; ProtectControlGroups = true; + RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; LockPersonality = true; MemoryDenyWriteExecute = true; RestrictRealtime = true; + RestrictSUIDSGID = true; PrivateMounts = true; + # System Call Filtering + SystemCallArchitectures = "native"; }; }; diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix index 01fe230b8a43..5fcc8590232a 100644 --- a/nixos/modules/services/x11/desktop-managers/pantheon.nix +++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix @@ -109,7 +109,7 @@ in # Without this, elementary LightDM greeter will pre-select non-existent `default` session # https://github.com/elementary/greeter/issues/368 - services.xserver.displayManager.defaultSession = "pantheon"; + services.xserver.displayManager.defaultSession = mkDefault "pantheon"; services.xserver.displayManager.sessionCommands = '' if test "$XDG_CURRENT_DESKTOP" = "Pantheon"; then diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.xml b/nixos/modules/services/x11/desktop-managers/pantheon.xml index 9541f2cfd4ee..7905ceebd9aa 100644 --- a/nixos/modules/services/x11/desktop-managers/pantheon.xml +++ b/nixos/modules/services/x11/desktop-managers/pantheon.xml @@ -1,7 +1,7 @@ <chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="chap-pantheon"> - <title>Pantheon Destkop</title> + <title>Pantheon Desktop</title> <para> Pantheon is the desktop environment created for the elementary OS distribution. It is written from scratch in Vala, utilizing GNOME technologies with GTK 3 and Granite. </para> diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix index d7bef68e5bcb..622ea62f3a91 100644 --- a/nixos/modules/services/x11/display-managers/gdm.nix +++ b/nixos/modules/services/x11/display-managers/gdm.nix @@ -93,16 +93,17 @@ in }; wayland = mkOption { + type = types.bool; default = true; description = '' Allow GDM to run on Wayland instead of Xserver. Note to enable Wayland with Nvidia you need to enable the <option>nvidiaWayland</option>. ''; - type = types.bool; }; nvidiaWayland = mkOption { + type = types.bool; default = false; description = '' Whether to allow wayland to be used with the proprietary diff --git a/nixos/modules/services/x11/hardware/digimend.nix b/nixos/modules/services/x11/hardware/digimend.nix index a9f5640905aa..b1b1682f00b2 100644 --- a/nixos/modules/services/x11/hardware/digimend.nix +++ b/nixos/modules/services/x11/hardware/digimend.nix @@ -16,12 +16,7 @@ in services.xserver.digimend = { - enable = mkOption { - default = false; - description = '' - Whether to enable the digimend drivers for Huion/XP-Pen/etc. tablets. - ''; - }; + enable = mkEnableOption "the digimend drivers for Huion/XP-Pen/etc. tablets"; }; diff --git a/nixos/modules/services/x11/picom.nix b/nixos/modules/services/x11/picom.nix index e3bd21be73e4..1289edd2904a 100644 --- a/nixos/modules/services/x11/picom.nix +++ b/nixos/modules/services/x11/picom.nix @@ -1,39 +1,48 @@ { config, lib, pkgs, ... }: with lib; -with builtins; let cfg = config.services.picom; - 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); - - 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 "picom.mkValueString: unexpected type (v = ${v})"; - in "${escape [ sep ] k}${sep}${mkValueString v};") - attrs); + pairOf = x: with types; + addCheck (listOf x) (y: length y == 2) + // { description = "pair of ${x.description}"; }; + + floatBetween = a: b: with types; + let + # toString prints floats with hardcoded high precision + floatToString = f: builtins.toJSON f; + in + addCheck float (x: x <= b && x >= a) + // { description = "a floating point number in " + + "range [${floatToString a}, ${floatToString b}]"; }; + + mkDefaultAttrs = mapAttrs (n: v: mkDefault v); + + # Basically a tinkered lib.generators.mkKeyValueDefault + # It either serializes a top-level definition "key: { values };" + # or an expression "key = { values };" + mkAttrsString = top: + mapAttrsToList (k: v: + let sep = if (top && isAttrs v) then ":" else "="; + in "${escape [ sep ] k}${sep}${mkValueString v};"); + + # This serializes a Nix expression to the libconfig format. + mkValueString = v: + if types.bool.check v then boolToString v + else if types.int.check v then toString v + else if types.float.check v then toString v + else if types.str.check v then "\"${escape [ "\"" ] v}\"" + else if builtins.isList v then "[ ${concatMapStringsSep " , " mkValueString v} ]" + else if types.attrs.check v then "{ ${concatStringsSep " " (mkAttrsString false v) } }" + else throw '' + invalid expression used in option services.picom.settings: + ${v} + ''; + + toConf = attrs: concatStringsSep "\n" (mkAttrsString true cfg.settings); configFile = pkgs.writeText "picom.conf" (toConf cfg.settings); @@ -61,7 +70,7 @@ in { }; fadeDelta = mkOption { - type = types.addCheck types.int (x: x > 0); + type = types.ints.positive; default = 10; example = 5; description = '' @@ -70,12 +79,11 @@ in { }; fadeSteps = mkOption { - type = pairOf (floatBetween "0.01" "1.01"); - default = [ "0.028" "0.03" ]; - example = [ "0.04" "0.04" ]; + type = pairOf (floatBetween 0.01 1); + default = [ 0.028 0.03 ]; + example = [ 0.04 0.04 ]; description = '' Opacity change between fade steps (in and out). - (numbers in range 0.01 - 1.0) ''; }; @@ -111,11 +119,11 @@ in { }; shadowOpacity = mkOption { - type = floatBetween "0.0" "1.01"; - default = "0.75"; - example = "0.8"; + type = floatBetween 0 1; + default = 0.75; + example = 0.8; description = '' - Window shadows opacity (number in range 0.0 - 1.0). + Window shadows opacity. ''; }; @@ -134,29 +142,29 @@ in { }; activeOpacity = mkOption { - type = floatBetween "0.0" "1.01"; - default = "1.0"; - example = "0.8"; + type = floatBetween 0 1; + default = 1.0; + example = 0.8; description = '' - Opacity of active windows (number in range 0.0 - 1.0). + Opacity of active windows. ''; }; inactiveOpacity = mkOption { - type = floatBetween "0.1" "1.01"; - default = "1.0"; - example = "0.8"; + type = floatBetween 0.1 1; + default = 1.0; + example = 0.8; description = '' - Opacity of inactive windows (number in range 0.1 - 1.0). + Opacity of inactive windows. ''; }; menuOpacity = mkOption { - type = floatBetween "0.0" "1.01"; - default = "1.0"; - example = "0.8"; + type = floatBetween 0 1; + default = 1.0; + example = 0.8; description = '' - Opacity of dropdown and popup menu (number in range 0.0 - 1.0). + Opacity of dropdown and popup menu. ''; }; @@ -210,7 +218,7 @@ in { }; refreshRate = mkOption { - type = types.addCheck types.int (x: x >= 0); + type = types.ints.unsigned; default = 0; example = 60; description = '' @@ -218,54 +226,69 @@ in { ''; }; - settings = let - configTypes = with types; oneOf [ bool int float str ]; - # types.loaOf converts lists to sets - loaOf = t: with types; either (listOf t) (attrsOf t); + settings = with types; + let + scalar = oneOf [ bool int float str ] + // { description = "scalar types"; }; + + libConfig = oneOf [ scalar (listOf libConfig) (attrsOf libConfig) ] + // { description = "libconfig type"; }; + + topLevel = attrsOf libConfig + // { description = '' + libconfig configuration. The format consists of an attributes + set (called a group) of settings. Each setting can be a scalar type + (boolean, integer, floating point number or string), a list of + scalars or a group itself + ''; + }; + in mkOption { - type = loaOf (types.either configTypes (loaOf (types.either configTypes (loaOf configTypes)))); - default = {}; + type = topLevel; + default = { }; + example = literalExample '' + blur = + { method = "gaussian"; + size = 10; + deviation = 5.0; + }; + ''; description = '' - Additional Picom configuration. + Picom settings. Use this option to configure Picom settings not exposed + in a NixOS option or to bypass one. For the available options see the + CONFIGURATION FILES section at <literal>picom(1)</literal>. ''; }; }; config = mkIf cfg.enable { - services.picom.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 { + services.picom.settings = mkDefaultAttrs { # 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; + fading = cfg.fade; + fade-delta = cfg.fadeDelta; + fade-in-step = elemAt cfg.fadeSteps 0; + fade-out-step = elemAt cfg.fadeSteps 1; + fade-exclude = 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; + shadow = cfg.shadow; + shadow-offset-x = elemAt cfg.shadowOffsets 0; + shadow-offset-y = elemAt cfg.shadowOffsets 1; + shadow-opacity = cfg.shadowOpacity; + shadow-exclude = cfg.shadowExclude; # opacity - active-opacity = mkDefault (toFloat cfg.activeOpacity); - inactive-opacity = mkDefault (toFloat cfg.inactiveOpacity); + active-opacity = cfg.activeOpacity; + inactive-opacity = cfg.inactiveOpacity; - wintypes = mkDefault cfg.wintypes; + wintypes = cfg.wintypes; - opacity-rule = mkDefault cfg.opacityRules; + opacity-rule = cfg.opacityRules; # other options - backend = mkDefault cfg.backend; - vsync = mkDefault cfg.vSync; - refresh-rate = mkDefault cfg.refreshRate; + backend = cfg.backend; + vsync = cfg.vSync; + refresh-rate = cfg.refreshRate; }; systemd.user.services.picom = { |