diff options
Diffstat (limited to 'nixos')
54 files changed, 1329 insertions, 444 deletions
diff --git a/nixos/doc/manual/configuration/adding-custom-packages.section.md b/nixos/doc/manual/configuration/adding-custom-packages.section.md index 5d1198fb0f418..9219396722f03 100644 --- a/nixos/doc/manual/configuration/adding-custom-packages.section.md +++ b/nixos/doc/manual/configuration/adding-custom-packages.section.md @@ -1,11 +1,18 @@ # Adding Custom Packages {#sec-custom-packages} It's possible that a package you need is not available in NixOS. In that -case, you can do two things. First, you can clone the Nixpkgs -repository, add the package to your clone, and (optionally) submit a -patch or pull request to have it accepted into the main Nixpkgs repository. -This is described in detail in the [Nixpkgs manual](https://nixos.org/nixpkgs/manual). -In short, you clone Nixpkgs: +case, you can do two things. Either you can package it with Nix, or you can try +to use prebuilt packages from upstream. Due to the peculiarities of NixOS, it +is important to note that building software from source is often easier than +using pre-built executables. + +## Building with Nix {#sec-custom-packages-nix} + +This can be done either in-tree or out-of-tree. For an in-tree build, you can +clone the Nixpkgs repository, add the package to your clone, and (optionally) +submit a patch or pull request to have it accepted into the main Nixpkgs +repository. This is described in detail in the [Nixpkgs +manual](https://nixos.org/nixpkgs/manual). In short, you clone Nixpkgs: ```ShellSession $ git clone https://github.com/NixOS/nixpkgs @@ -72,3 +79,21 @@ $ nix-build my-hello.nix $ ./result/bin/hello Hello, world! ``` + +## Using pre-built executables {#sec-custom-packages-prebuilt} + +Most pre-built executables will not work on NixOS. There are two notable +exceptions: flatpaks and AppImages. For flatpaks see the [dedicated +section](#module-services-flatpak). AppImages will not run "as-is" on NixOS. +First you need to install `appimage-run`: add to `/etc/nixos/configuration.nix` + +```nix +environment.systemPackages = [ pkgs.appimage-run ]; +``` + +Then instead of running the AppImage "as-is", run `appimage-run foo.appimage`. + +To make other pre-built executables work on NixOS, you need to package them +with Nix and special helpers like `autoPatchelfHook` or `buildFHSUserEnv`. See +the [Nixpkgs manual](https://nixos.org/nixpkgs/manual) for details. This +is complex and often doing a source build is easier. diff --git a/nixos/doc/manual/from_md/configuration/adding-custom-packages.section.xml b/nixos/doc/manual/from_md/configuration/adding-custom-packages.section.xml index 4fa40d61966e7..07f541666cbe1 100644 --- a/nixos/doc/manual/from_md/configuration/adding-custom-packages.section.xml +++ b/nixos/doc/manual/from_md/configuration/adding-custom-packages.section.xml @@ -2,40 +2,50 @@ <title>Adding Custom Packages</title> <para> It’s possible that a package you need is not available in NixOS. In - that case, you can do two things. First, you can clone the Nixpkgs - repository, add the package to your clone, and (optionally) submit a - patch or pull request to have it accepted into the main Nixpkgs - repository. This is described in detail in the - <link xlink:href="https://nixos.org/nixpkgs/manual">Nixpkgs - manual</link>. In short, you clone Nixpkgs: + that case, you can do two things. Either you can package it with + Nix, or you can try to use prebuilt packages from upstream. Due to + the peculiarities of NixOS, it is important to note that building + software from source is often easier than using pre-built + executables. </para> - <programlisting> + <section xml:id="sec-custom-packages-nix"> + <title>Building with Nix</title> + <para> + This can be done either in-tree or out-of-tree. For an in-tree + build, you can clone the Nixpkgs repository, add the package to + your clone, and (optionally) submit a patch or pull request to + have it accepted into the main Nixpkgs repository. This is + described in detail in the + <link xlink:href="https://nixos.org/nixpkgs/manual">Nixpkgs + manual</link>. In short, you clone Nixpkgs: + </para> + <programlisting> $ git clone https://github.com/NixOS/nixpkgs $ cd nixpkgs </programlisting> - <para> - Then you write and test the package as described in the Nixpkgs - manual. Finally, you add it to - <xref linkend="opt-environment.systemPackages" />, e.g. - </para> - <programlisting language="bash"> + <para> + Then you write and test the package as described in the Nixpkgs + manual. Finally, you add it to + <xref linkend="opt-environment.systemPackages" />, e.g. + </para> + <programlisting language="bash"> environment.systemPackages = [ pkgs.my-package ]; </programlisting> - <para> - and you run <literal>nixos-rebuild</literal>, specifying your own - Nixpkgs tree: - </para> - <programlisting> + <para> + and you run <literal>nixos-rebuild</literal>, specifying your own + Nixpkgs tree: + </para> + <programlisting> # nixos-rebuild switch -I nixpkgs=/path/to/my/nixpkgs </programlisting> - <para> - The second possibility is to add the package outside of the Nixpkgs - tree. For instance, here is how you specify a build of the - <link xlink:href="https://www.gnu.org/software/hello/">GNU - Hello</link> package directly in - <literal>configuration.nix</literal>: - </para> - <programlisting language="bash"> + <para> + The second possibility is to add the package outside of the + Nixpkgs tree. For instance, here is how you specify a build of the + <link xlink:href="https://www.gnu.org/software/hello/">GNU + Hello</link> package directly in + <literal>configuration.nix</literal>: + </para> + <programlisting language="bash"> environment.systemPackages = let my-hello = with pkgs; stdenv.mkDerivation rec { @@ -48,17 +58,17 @@ environment.systemPackages = in [ my-hello ]; </programlisting> - <para> - Of course, you can also move the definition of - <literal>my-hello</literal> into a separate Nix expression, e.g. - </para> - <programlisting language="bash"> + <para> + Of course, you can also move the definition of + <literal>my-hello</literal> into a separate Nix expression, e.g. + </para> + <programlisting language="bash"> environment.systemPackages = [ (import ./my-hello.nix) ]; </programlisting> - <para> - where <literal>my-hello.nix</literal> contains: - </para> - <programlisting language="bash"> + <para> + where <literal>my-hello.nix</literal> contains: + </para> + <programlisting language="bash"> with import <nixpkgs> {}; # bring all of Nixpkgs into scope stdenv.mkDerivation rec { @@ -69,12 +79,40 @@ stdenv.mkDerivation rec { }; } </programlisting> - <para> - This allows testing the package easily: - </para> - <programlisting> + <para> + This allows testing the package easily: + </para> + <programlisting> $ nix-build my-hello.nix $ ./result/bin/hello Hello, world! </programlisting> + </section> + <section xml:id="sec-custom-packages-prebuilt"> + <title>Using pre-built executables</title> + <para> + Most pre-built executables will not work on NixOS. There are two + notable exceptions: flatpaks and AppImages. For flatpaks see the + <link linkend="module-services-flatpak">dedicated section</link>. + AppImages will not run <quote>as-is</quote> on NixOS. First you + need to install <literal>appimage-run</literal>: add to + <literal>/etc/nixos/configuration.nix</literal> + </para> + <programlisting language="bash"> +environment.systemPackages = [ pkgs.appimage-run ]; +</programlisting> + <para> + Then instead of running the AppImage <quote>as-is</quote>, run + <literal>appimage-run foo.appimage</literal>. + </para> + <para> + To make other pre-built executables work on NixOS, you need to + package them with Nix and special helpers like + <literal>autoPatchelfHook</literal> or + <literal>buildFHSUserEnv</literal>. See the + <link xlink:href="https://nixos.org/nixpkgs/manual">Nixpkgs + manual</link> for details. This is complex and often doing a + source build is easier. + </para> + </section> </section> diff --git a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml index 91d169d42ced3..2aa88d4bd93d4 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml @@ -287,6 +287,13 @@ </listitem> <listitem> <para> + The Redis module now disables RDB persistence when + <literal>services.redis.servers.<name>.save = []</literal> + instead of using the Redis default. + </para> + </listitem> + <listitem> + <para> Matrix Synapse now requires entries in the <literal>state_group_edges</literal> table to be unique, in order to prevent accidentally introducing duplicate @@ -314,6 +321,11 @@ </listitem> <listitem> <para> + Add udev rules for the Teensy family of microcontrollers. + </para> + </listitem> + <listitem> + <para> There is a new module for the <literal>thunar</literal> program (the Xfce file manager), which depends on the <literal>xfconf</literal> dbus service, and also has a dbus @@ -324,6 +336,13 @@ release it may be removed. </para> </listitem> + <listitem> + <para> + There is a new module for the <literal>xfconf</literal> + program (the Xfce configuration storage system), which has a + dbus service. + </para> + </listitem> </itemizedlist> </section> </section> diff --git a/nixos/doc/manual/release-notes/rl-2211.section.md b/nixos/doc/manual/release-notes/rl-2211.section.md index 3f799a2ad68f0..15c95c9206b22 100644 --- a/nixos/doc/manual/release-notes/rl-2211.section.md +++ b/nixos/doc/manual/release-notes/rl-2211.section.md @@ -110,6 +110,8 @@ Use `configure.packages` instead. - A new module was added for the Saleae Logic device family, providing the options `hardware.saleae-logic.enable` and `hardware.saleae-logic.package`. +- The Redis module now disables RDB persistence when `services.redis.servers.<name>.save = []` instead of using the Redis default. + - Matrix Synapse now requires entries in the `state_group_edges` table to be unique, in order to prevent accidentally introducing duplicate information (for example, because a database backup was restored multiple times). If your Synapse database already has duplicate rows in this table, this could fail with an error and require manual remediation. - `dockerTools.buildImage` deprecates the misunderstood `contents` parameter, in favor of `copyToRoot`. @@ -117,6 +119,10 @@ Use `configure.packages` instead. - memtest86+ was updated from 5.00-coreboot-002 to 6.00-beta2. It is now the upstream version from https://www.memtest.org/, as coreboot's fork is no longer available. +- Add udev rules for the Teensy family of microcontrollers. + - There is a new module for the `thunar` program (the Xfce file manager), which depends on the `xfconf` dbus service, and also has a dbus service and a systemd unit. The option `services.xserver.desktopManager.xfce.thunarPlugins` has been renamed to `programs.thunar.plugins`, and in a future release it may be removed. +- There is a new module for the `xfconf` program (the Xfce configuration storage system), which has a dbus service. + <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. --> diff --git a/nixos/lib/make-multi-disk-zfs-image.nix b/nixos/lib/make-multi-disk-zfs-image.nix index a84732aa11712..0a894c8b9888f 100644 --- a/nixos/lib/make-multi-disk-zfs-image.nix +++ b/nixos/lib/make-multi-disk-zfs-image.nix @@ -128,7 +128,7 @@ let gptfdisk nix parted - utillinux + util-linux zfs ] ); diff --git a/nixos/lib/make-single-disk-zfs-image.nix b/nixos/lib/make-single-disk-zfs-image.nix index 9310febd91792..98150584fb3e4 100644 --- a/nixos/lib/make-single-disk-zfs-image.nix +++ b/nixos/lib/make-single-disk-zfs-image.nix @@ -116,7 +116,7 @@ let gptfdisk nix parted - utillinux + util-linux zfs ] ); diff --git a/nixos/modules/config/qt5.nix b/nixos/modules/config/qt5.nix index eabba9ad95f00..542a96ba6b068 100644 --- a/nixos/modules/config/qt5.nix +++ b/nixos/modules/config/qt5.nix @@ -8,14 +8,17 @@ let isQGnome = cfg.platformTheme == "gnome" && builtins.elem cfg.style ["adwaita" "adwaita-dark"]; isQtStyle = cfg.platformTheme == "gtk2" && !(builtins.elem cfg.style ["adwaita" "adwaita-dark"]); + isQt5ct = cfg.platformTheme == "qt5ct"; packages = if isQGnome then [ pkgs.qgnomeplatform pkgs.adwaita-qt ] else if isQtStyle then [ pkgs.libsForQt5.qtstyleplugins ] + else if isQt5ct then [ pkgs.libsForQt5.qt5ct ] else throw "`qt5.platformTheme` ${cfg.platformTheme} and `qt5.style` ${cfg.style} are not compatible."; in { + meta.maintainers = [ maintainers.romildo ]; options = { qt5 = { @@ -26,11 +29,13 @@ in type = types.enum [ "gtk2" "gnome" + "qt5ct" ]; example = "gnome"; relatedPackages = [ "qgnomeplatform" ["libsForQt5" "qtstyleplugins"] + ["libsForQt5" "qt5ct"] ]; description = '' Selects the platform theme to use for Qt5 applications.</para> @@ -48,6 +53,13 @@ in <link xlink:href="https://github.com/FedoraQt/QGnomePlatform">qgnomeplatform</link> </para></listitem> </varlistentry> + <varlistentry> + <term><literal>qt5ct</literal></term> + <listitem><para>Use Qt style set using the + <link xlink:href="https://sourceforge.net/projects/qt5ct/">qt5ct</link> + application. + </para></listitem> + </varlistentry> </variablelist> ''; }; @@ -96,7 +108,7 @@ in environment.variables.QT_QPA_PLATFORMTHEME = cfg.platformTheme; - environment.variables.QT_STYLE_OVERRIDE = cfg.style; + environment.variables.QT_STYLE_OVERRIDE = mkIf (! isQt5ct) cfg.style; environment.systemPackages = packages; diff --git a/nixos/modules/config/xdg/portal.nix b/nixos/modules/config/xdg/portal.nix index 088f2af59e22a..1e6ddd7c4a26b 100644 --- a/nixos/modules/config/xdg/portal.nix +++ b/nixos/modules/config/xdg/portal.nix @@ -1,10 +1,30 @@ { config, pkgs, lib, ... }: -with lib; +let + inherit (lib) + mkEnableOption + mkIf + mkOption + mkRenamedOptionModule + teams + types; +in { imports = [ (mkRenamedOptionModule [ "services" "flatpak" "extraPortals" ] [ "xdg" "portal" "extraPortals" ]) + + ({ config, lib, options, ... }: + let + from = [ "xdg" "portal" "gtkUsePortal" ]; + fromOpt = lib.getAttrFromPath from options; + in + { + warnings = lib.mkIf config.xdg.portal.gtkUsePortal [ + "The option `${lib.showOption from}' defined in ${lib.showFiles fromOpt.files} has been deprecated. Setting the variable globally with `environment.sessionVariables' NixOS option can have unforseen side-effects." + ]; + } + ) ]; meta = { @@ -32,11 +52,12 @@ with lib; gtkUsePortal = mkOption { type = types.bool; + visible = false; default = false; description = '' Sets environment variable <literal>GTK_USE_PORTAL</literal> to <literal>1</literal>. - This is needed for packages ran outside Flatpak to respect and use XDG Desktop Portals. - For example, you'd need to set this for non-flatpak Firefox to use native filechoosers. + This will force GTK-based programs ran outside Flatpak to respect and use XDG Desktop Portals + for features like file chooser but it is an unsupported hack that can easily break things. Defaults to <literal>false</literal> to respect its opt-in nature. ''; }; diff --git a/nixos/modules/hardware/ckb-next.nix b/nixos/modules/hardware/ckb-next.nix index b2bbd77c9d7fa..751ed663aab57 100644 --- a/nixos/modules/hardware/ckb-next.nix +++ b/nixos/modules/hardware/ckb-next.nix @@ -48,6 +48,6 @@ in }; meta = { - maintainers = with lib.maintainers; [ kierdavis ]; + maintainers = with lib.maintainers; [ superherointj ]; }; } diff --git a/nixos/modules/hardware/raid/hpsa.nix b/nixos/modules/hardware/raid/hpsa.nix index fa6f0b8fc84a3..120348a74bfbe 100644 --- a/nixos/modules/hardware/raid/hpsa.nix +++ b/nixos/modules/hardware/raid/hpsa.nix @@ -40,7 +40,7 @@ let homepage = "https://downloads.linux.hpe.com/SDR/downloads/MCP/Ubuntu/pool/non-free/"; license = licenses.unfreeRedistributable; platforms = [ "x86_64-linux" ]; - maintainers = with maintainers; [ volth ]; + maintainers = with maintainers; [ ]; }; }; in { diff --git a/nixos/modules/installer/netboot/netboot.nix b/nixos/modules/installer/netboot/netboot.nix index 3127bdc436f92..fed6a7c372879 100644 --- a/nixos/modules/installer/netboot/netboot.nix +++ b/nixos/modules/installer/netboot/netboot.nix @@ -81,7 +81,7 @@ with lib; # Create the initrd - system.build.netbootRamdisk = pkgs.makeInitrd { + system.build.netbootRamdisk = pkgs.makeInitrdNG { inherit (config.boot.initrd) compressor; prepend = [ "${config.system.build.initialRamdisk}/initrd" ]; diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl index 1935d8252607c..0e6320e4695c3 100644 --- a/nixos/modules/installer/tools/nixos-generate-config.pl +++ b/nixos/modules/installer/tools/nixos-generate-config.pl @@ -300,6 +300,12 @@ if ($virt eq "oracle") { push @attrs, "virtualisation.virtualbox.guest.enable = true;" } +# Check if we're a Parallels guest. If so, enable the guest additions. +# It is blocked by https://github.com/systemd/systemd/pull/23859 +if ($virt eq "parallels") { + push @attrs, "hardware.parallels.enable = true;"; + push @attrs, "nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ \"prl-tools\" ];"; +} # Likewise for QEMU. if ($virt eq "qemu" || $virt eq "kvm" || $virt eq "bochs") { diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 759c31ef28b01..d04a4f93555fc 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -230,6 +230,7 @@ ./programs/weylus.nix ./programs/wireshark.nix ./programs/wshowkeys.nix + ./programs/xfconf.nix ./programs/xfs_quota.nix ./programs/xonsh.nix ./programs/xss-lock.nix @@ -826,6 +827,7 @@ ./services/networking/libreswan.nix ./services/networking/lldpd.nix ./services/networking/logmein-hamachi.nix + ./services/networking/lokinet.nix ./services/networking/lxd-image-server.nix ./services/networking/magic-wormhole-mailbox-server.nix ./services/networking/matterbridge.nix @@ -1045,7 +1047,6 @@ ./services/web-apps/code-server.nix ./services/web-apps/baget.nix ./services/web-apps/convos.nix - ./services/web-apps/cryptpad.nix ./services/web-apps/dex.nix ./services/web-apps/discourse.nix ./services/web-apps/documize.nix @@ -1057,6 +1058,7 @@ ./services/web-apps/gerrit.nix ./services/web-apps/gotify-server.nix ./services/web-apps/grocy.nix + ./services/web-apps/healthchecks.nix ./services/web-apps/hedgedoc.nix ./services/web-apps/hledger-web.nix ./services/web-apps/icingaweb2/icingaweb2.nix @@ -1080,6 +1082,7 @@ ./services/web-apps/nexus.nix ./services/web-apps/nifi.nix ./services/web-apps/node-red.nix + ./services/web-apps/phylactery.nix ./services/web-apps/pict-rs.nix ./services/web-apps/peertube.nix ./services/web-apps/plantuml-server.nix @@ -1271,7 +1274,6 @@ ./virtualisation/parallels-guest.nix ./virtualisation/podman/default.nix ./virtualisation/qemu-guest-agent.nix - ./virtualisation/railcar.nix ./virtualisation/spice-usb-redirection.nix ./virtualisation/virtualbox-guest.nix ./virtualisation/virtualbox-host.nix diff --git a/nixos/modules/programs/qt5ct.nix b/nixos/modules/programs/qt5ct.nix index 88e861bf4031a..3ff47b355915b 100644 --- a/nixos/modules/programs/qt5ct.nix +++ b/nixos/modules/programs/qt5ct.nix @@ -1,31 +1,9 @@ -{ config, lib, pkgs, ... }: +{ lib, ... }: with lib; { - meta.maintainers = [ maintainers.romildo ]; - - ###### interface - options = { - programs.qt5ct = { - enable = mkOption { - default = false; - type = types.bool; - description = '' - Whether to enable the Qt5 Configuration Tool (qt5ct), a - program that allows users to configure Qt5 settings (theme, - font, icons, etc.) under desktop environments or window - manager without Qt integration. - - Official home page: <link xlink:href="https://sourceforge.net/projects/qt5ct/">https://sourceforge.net/projects/qt5ct/</link> - ''; - }; - }; - }; - - ###### implementation - config = mkIf config.programs.qt5ct.enable { - environment.variables.QT_QPA_PLATFORMTHEME = "qt5ct"; - environment.systemPackages = with pkgs; [ libsForQt5.qt5ct ]; - }; + imports = [ + (mkRemovedOptionModule [ "programs" "qt5ct" "enable" ] "Use qt5.platformTheme = \"qt5ct\" instead.") + ]; } diff --git a/nixos/modules/programs/thunar.nix b/nixos/modules/programs/thunar.nix index 343f84698672a..5ea2982dd93cf 100644 --- a/nixos/modules/programs/thunar.nix +++ b/nixos/modules/programs/thunar.nix @@ -33,12 +33,13 @@ in { services.dbus.packages = [ package - pkgs.xfce.xfconf ]; systemd.packages = [ package ]; + + programs.xfconf.enable = true; } ); } diff --git a/nixos/modules/programs/xfconf.nix b/nixos/modules/programs/xfconf.nix new file mode 100644 index 0000000000000..8e854b40e513d --- /dev/null +++ b/nixos/modules/programs/xfconf.nix @@ -0,0 +1,27 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let cfg = config.programs.xfconf; + +in { + meta = { + maintainers = teams.xfce.members; + }; + + options = { + programs.xfconf = { + enable = mkEnableOption "Xfconf, the Xfce configuration storage system"; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ + pkgs.xfce.xfconf + ]; + + services.dbus.packages = [ + pkgs.xfce.xfconf + ]; + }; +} diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix index 7a6a6b5ed30bb..22fcb72e9ff40 100644 --- a/nixos/modules/rename.nix +++ b/nixos/modules/rename.nix @@ -68,6 +68,7 @@ with lib; prey-bash-client is deprecated upstream '') (mkRemovedOptionModule [ "services" "quagga" ] "the corresponding package has been removed from nixpkgs") + (mkRemovedOptionModule [ "services" "railcar" ] "the corresponding package has been removed from nixpkgs") (mkRemovedOptionModule [ "services" "seeks" ] "") (mkRemovedOptionModule [ "services" "ssmtp" ] '' The ssmtp package and the corresponding module have been removed due to @@ -98,6 +99,7 @@ with lib; (mkRemovedOptionModule [ "services" "virtuoso" ] "The corresponding package was removed from nixpkgs.") (mkRemovedOptionModule [ "services" "openfire" ] "The corresponding package was removed from nixpkgs.") (mkRemovedOptionModule [ "services" "riak" ] "The corresponding package was removed from nixpkgs.") + (mkRemovedOptionModule [ "services" "cryptpad" ] "The corresponding package was removed from nixpkgs.") # Do NOT add any option renames here, see top of the file ]; diff --git a/nixos/modules/services/databases/redis.nix b/nixos/modules/services/databases/redis.nix index a1bd73c9e371d..5532c54019706 100644 --- a/nixos/modules/services/databases/redis.nix +++ b/nixos/modules/services/databases/redis.nix @@ -166,7 +166,11 @@ in { save = mkOption { type = with types; listOf (listOf int); default = [ [900 1] [300 10] [60 10000] ]; - description = "The schedule in which data is persisted to disk, represented as a list of lists where the first element represent the amount of seconds and the second the number of changes."; + description = mdDoc '' + The schedule in which data is persisted to disk, represented as a list of lists where the first element represent the amount of seconds and the second the number of changes. + + If set to the empty list (`[]`) then RDB persistence will be disabled (useful if you are using AOF or don't want any persistence). + ''; }; slaveOf = mkOption { @@ -268,7 +272,11 @@ in { syslog-enabled = config.syslog; databases = config.databases; maxclients = config.maxclients; - save = map (d: "${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}") config.save; + save = if config.save == [] + then ''""'' # Disable saving with `save = ""` + else map + (d: "${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}") + config.save; dbfilename = "dump.rdb"; dir = "/var/lib/${redisName name}"; appendOnly = config.appendOnly; diff --git a/nixos/modules/services/development/jupyter/default.nix b/nixos/modules/services/development/jupyter/default.nix index 4eacc4782a9a4..7c86e8b647854 100644 --- a/nixos/modules/services/development/jupyter/default.nix +++ b/nixos/modules/services/development/jupyter/default.nix @@ -143,6 +143,9 @@ in { language = "python"; logo32 = "''${env.sitePackages}/ipykernel/resources/logo-32x32.png"; logo64 = "''${env.sitePackages}/ipykernel/resources/logo-64x64.png"; + extraPaths = { + "cool.txt" = pkgs.writeText "cool" "cool content"; + }; }; } ''; diff --git a/nixos/modules/services/development/jupyter/kernel-options.nix b/nixos/modules/services/development/jupyter/kernel-options.nix index 348a8b44b382b..0a9eaafa31856 100644 --- a/nixos/modules/services/development/jupyter/kernel-options.nix +++ b/nixos/modules/services/development/jupyter/kernel-options.nix @@ -56,5 +56,14 @@ with lib; Path to 64x64 logo png. ''; }; + + extraPaths = mkOption { + type = types.attrsOf types.path; + default = { }; + example = literalExpression ''"{ examples = ''${env.sitePack}/IRkernel/kernelspec/kernel.js"; }''; + description = '' + Extra paths to link in kernel directory + ''; + }; }; } diff --git a/nixos/modules/services/mail/mailman.nix b/nixos/modules/services/mail/mailman.nix index 5b714c384de3f..11ea169fe2691 100644 --- a/nixos/modules/services/mail/mailman.nix +++ b/nixos/modules/services/mail/mailman.nix @@ -6,7 +6,7 @@ let cfg = config.services.mailman; - inherit (pkgs.mailmanPackages.buildEnvs { withHyperkitty = cfg.hyperkitty.enable; }) + inherit (pkgs.mailmanPackages.buildEnvs { withHyperkitty = cfg.hyperkitty.enable; withLDAP = cfg.ldap.enable; }) mailmanEnv webEnv; withPostgresql = config.services.postgresql.enable; @@ -87,6 +87,114 @@ in { description = "Enable Mailman on this host. Requires an active MTA on the host (e.g. Postfix)."; }; + ldap = { + enable = mkEnableOption "LDAP auth"; + serverUri = mkOption { + type = types.str; + example = "ldaps://ldap.host"; + description = '' + LDAP host to connect against. + ''; + }; + bindDn = mkOption { + type = types.str; + example = "cn=root,dc=nixos,dc=org"; + description = '' + Service account to bind against. + ''; + }; + bindPasswordFile = mkOption { + type = types.str; + example = "/run/secrets/ldap-bind"; + description = '' + Path to the file containing the bind password of the servie account + defined by <xref linkend="opt-services.mailman.ldap.bindDn" />. + ''; + }; + superUserGroup = mkOption { + type = types.nullOr types.str; + default = null; + example = "cn=admin,ou=groups,dc=nixos,dc=org"; + description = '' + Group where a user must be a member of to gain superuser rights. + ''; + }; + userSearch = { + query = mkOption { + type = types.str; + example = "(&(objectClass=inetOrgPerson)(|(uid=%(user)s)(mail=%(user)s)))"; + description = '' + Query to find a user in the LDAP database. + ''; + }; + ou = mkOption { + type = types.str; + example = "ou=users,dc=nixos,dc=org"; + description = '' + Organizational unit to look up a user. + ''; + }; + }; + groupSearch = { + type = mkOption { + type = types.enum [ + "posixGroup" "groupOfNames" "memberDNGroup" "nestedMemberDNGroup" "nestedGroupOfNames" + "groupOfUniqueNames" "nestedGroupOfUniqueNames" "activeDirectoryGroup" "nestedActiveDirectoryGroup" + "organizationalRoleGroup" "nestedOrganizationalRoleGroup" + ]; + default = "posixGroup"; + apply = v: "${toUpper (substring 0 1 v)}${substring 1 (stringLength v) v}Type"; + description = '' + Type of group to perform a group search against. + ''; + }; + query = mkOption { + type = types.str; + example = "(objectClass=groupOfNames)"; + description = '' + Query to find a group associated to a user in the LDAP database. + ''; + }; + ou = mkOption { + type = types.str; + example = "ou=groups,dc=nixos,dc=org"; + description = '' + Organizational unit to look up a group. + ''; + }; + }; + attrMap = { + username = mkOption { + default = "uid"; + type = types.str; + description = '' + LDAP-attribute that corresponds to the <literal>username</literal>-attribute in mailman. + ''; + }; + firstName = mkOption { + default = "givenName"; + type = types.str; + description = '' + LDAP-attribute that corresponds to the <literal>firstName</literal>-attribute in mailman. + ''; + }; + lastName = mkOption { + default = "sn"; + type = types.str; + description = '' + LDAP-attribute that corresponds to the <literal>lastName</literal>-attribute in mailman. + ''; + }; + email = mkOption { + default = "mail"; + type = types.str; + description = '' + LDAP-attribute that corresponds to the <literal>email</literal>-attribute in mailman. + ''; + }; + }; + }; + enablePostfix = mkOption { type = types.bool; default = true; @@ -274,6 +382,34 @@ in { with open('/var/lib/mailman-web/settings_local.json') as f: globals().update(json.load(f)) + + ${optionalString (cfg.ldap.enable) '' + import ldap + from django_auth_ldap.config import LDAPSearch, ${cfg.ldap.groupSearch.type} + AUTH_LDAP_SERVER_URI = "${cfg.ldap.serverUri}" + AUTH_LDAP_BIND_DN = "${cfg.ldap.bindDn}" + with open("${cfg.ldap.bindPasswordFile}") as f: + AUTH_LDAP_BIND_PASSWORD = f.read().rstrip('\n') + AUTH_LDAP_USER_SEARCH = LDAPSearch("${cfg.ldap.userSearch.ou}", + ldap.SCOPE_SUBTREE, "${cfg.ldap.userSearch.query}") + AUTH_LDAP_GROUP_TYPE = ${cfg.ldap.groupSearch.type}() + AUTH_LDAP_GROUP_SEARCH = LDAPSearch("${cfg.ldap.groupSearch.ou}", + ldap.SCOPE_SUBTREE, "${cfg.ldap.groupSearch.query}") + AUTH_LDAP_USER_ATTR_MAP = { + ${concatStrings (flip mapAttrsToList cfg.ldap.attrMap (key: value: '' + "${key}": "${value}", + ''))} + } + ${optionalString (cfg.ldap.superUserGroup != null) '' + AUTH_LDAP_USER_FLAGS_BY_GROUP = { + "is_superuser": "${cfg.ldap.superUserGroup}" + } + ''} + AUTHENTICATION_BACKENDS = ( + "django_auth_ldap.backend.LDAPBackend", + "django.contrib.auth.backends.ModelBackend" + ) + ''} ''; services.nginx = mkIf (cfg.serve.enable && cfg.webHosts != []) { diff --git a/nixos/modules/services/matrix/synapse.xml b/nixos/modules/services/matrix/synapse.xml index cf33957d58ece..65bc53d33ac34 100644 --- a/nixos/modules/services/matrix/synapse.xml +++ b/nixos/modules/services/matrix/synapse.xml @@ -33,21 +33,26 @@ <link xlink:href="https://github.com/matrix-org/synapse#synapse-installation"> installation instructions of Synapse </link>. <programlisting> -{ pkgs, lib, ... }: +{ pkgs, lib, config, ... }: let - fqdn = - let - join = hostName: domain: hostName + lib.optionalString (domain != null) ".${domain}"; - in join config.networking.hostName config.networking.domain; -in { - networking = { - <link linkend="opt-networking.hostName">hostName</link> = "myhostname"; - <link linkend="opt-networking.domain">domain</link> = "example.org"; + fqdn = "${config.networking.hostName}.${config.networking.domain}"; + clientConfig = { + "m.homeserver".base_url = "https://${fqdn}"; + "m.identity_server" = {}; }; - <link linkend="opt-networking.firewall.allowedTCPPorts">networking.firewall.allowedTCPPorts</link> = [ 80 443 ]; + serverConfig."m.server" = "${config.services.matrix-synapse.settings.server_name}:443"; + mkWellKnown = data: '' + add_header Content-Type application/json; + add_header Access-Control-Allow-Origin *; + return 200 '${builtins.toJSON data}'; + ''; +in { + <xref linkend="opt-networking.hostName" /> = "myhostname"; + <xref linkend="opt-networking.domain" /> = "example.org"; + <xref linkend="opt-networking.firewall.allowedTCPPorts" /> = [ 80 443 ]; - <link linkend="opt-services.postgresql.enable">services.postgresql.enable</link> = true; - <link linkend="opt-services.postgresql.initialScript">services.postgresql.initialScript</link> = pkgs.writeText "synapse-init.sql" '' + <xref linkend="opt-services.postgresql.enable" /> = true; + <xref linkend="opt-services.postgresql.initialScript" /> = pkgs.writeText "synapse-init.sql" '' CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse'; CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse" TEMPLATE template0 @@ -57,78 +62,41 @@ in { services.nginx = { <link linkend="opt-services.nginx.enable">enable</link> = true; - # only recommendedProxySettings and recommendedGzipSettings are strictly required, - # but the rest make sense as well <link linkend="opt-services.nginx.recommendedTlsSettings">recommendedTlsSettings</link> = true; <link linkend="opt-services.nginx.recommendedOptimisation">recommendedOptimisation</link> = true; <link linkend="opt-services.nginx.recommendedGzipSettings">recommendedGzipSettings</link> = true; <link linkend="opt-services.nginx.recommendedProxySettings">recommendedProxySettings</link> = true; - <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = { - # This host section can be placed on a different host than the rest, - # i.e. to delegate from the host being accessible as ${config.networking.domain} - # to another host actually running the Matrix homeserver. - "${config.networking.domain}" = { + "${config.networking.domain}" = { <co xml:id='ex-matrix-synapse-dns' /> <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true; <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true; - - <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."= /.well-known/matrix/server".extraConfig</link> = - let - # use 443 instead of the default 8448 port to unite - # the client-server and server-server port for simplicity - server = { "m.server" = "${fqdn}:443"; }; - in '' - add_header Content-Type application/json; - return 200 '${builtins.toJSON server}'; - ''; - <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."= /.well-known/matrix/client".extraConfig</link> = - let - client = { - "m.homeserver" = { "base_url" = "https://${fqdn}"; }; - "m.identity_server" = { "base_url" = "https://vector.im"; }; - }; - # ACAO required to allow element-web on any URL to request this json file - in '' - add_header Content-Type application/json; - add_header Access-Control-Allow-Origin *; - return 200 '${builtins.toJSON client}'; - ''; + <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."= /.well-known/matrix/server".extraConfig</link> = mkWellKnown serverConfig; <co xml:id='ex-matrix-synapse-well-known-server' /> + <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."= /.well-known/matrix/client".extraConfig</link> = mkWellKnown clientConfig; <co xml:id='ex-matrix-synapse-well-known-client' /> }; - - # Reverse proxy for Matrix client-server and server-server communication - ${fqdn} = { + "${fqdn}" = { <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true; <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true; - - # Or do a redirect instead of the 404, or whatever is appropriate for you. - # But do not put a Matrix Web client here! See the Element web section below. - <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."/".extraConfig</link> = '' + <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."/".extraConfig</link> = '' <co xml:id='ex-matrix-synapse-rev-default' /> return 404; ''; - - # forward all Matrix API calls to the synapse Matrix homeserver - locations."/_matrix" = { - <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.proxyPass">proxyPass</link> = "http://[::1]:8008"; # without a trailing / - }; + <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.proxyPass">locations."/_matrix".proxyPass</link> = "http://[::1]:8008"; <co xml:id='ex-matrix-synapse-rev-proxy-pass' /> + <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.proxyPass">locations."/_synapse/client".proxyPass</link> = "http://[::1]:8008"; <co xml:id='ex-matrix-synapse-rev-client' /> }; }; }; + services.matrix-synapse = { <link linkend="opt-services.matrix-synapse.enable">enable</link> = true; - <link linkend="opt-services.matrix-synapse.settings.server_name">server_name</link> = config.networking.domain; - <link linkend="opt-services.matrix-synapse.settings.listeners">listeners</link> = [ - { - <link linkend="opt-services.matrix-synapse.settings.listeners._.port">port</link> = 8008; + <link linkend="opt-services.matrix-synapse.settings.server_name">settings.server_name</link> = config.networking.domain; + <link linkend="opt-services.matrix-synapse.settings.listeners">settings.listeners</link> = [ + { <link linkend="opt-services.matrix-synapse.settings.listeners._.port">port</link> = 8008; <link linkend="opt-services.matrix-synapse.settings.listeners._.bind_addresses">bind_addresses</link> = [ "::1" ]; <link linkend="opt-services.matrix-synapse.settings.listeners._.type">type</link> = "http"; <link linkend="opt-services.matrix-synapse.settings.listeners._.tls">tls</link> = false; <link linkend="opt-services.matrix-synapse.settings.listeners._.x_forwarded">x_forwarded</link> = true; <link linkend="opt-services.matrix-synapse.settings.listeners._.resources">resources</link> = [ { - <link linkend="opt-services.matrix-synapse.settings.listeners._.resources._.names">names</link> = [ "client" ]; + <link linkend="opt-services.matrix-synapse.settings.listeners._.resources._.names">names</link> = [ "client" "federation" ]; <link linkend="opt-services.matrix-synapse.settings.listeners._.resources._.compress">compress</link> = true; - } { - <link linkend="opt-services.matrix-synapse.settings.listeners._.resources._.names">names</link> = [ "federation" ]; - <link linkend="opt-services.matrix-synapse.settings.listeners._.resources._.compress">compress</link> = false; } ]; } ]; @@ -136,20 +104,59 @@ in { } </programlisting> </para> - - <para> - If the <code>A</code> and <code>AAAA</code> DNS records on - <literal>example.org</literal> do not point on the same host as the records - for <code>myhostname.example.org</code>, you can easily move the - <code>/.well-known</code> virtualHost section of the code to the host that - is serving <literal>example.org</literal>, while the rest stays on - <literal>myhostname.example.org</literal> with no other changes required. - This pattern also allows to seamlessly move the homeserver from - <literal>myhostname.example.org</literal> to - <literal>myotherhost.example.org</literal> by only changing the - <code>/.well-known</code> redirection target. - </para> - + <calloutlist> + <callout arearefs='ex-matrix-synapse-dns'> + <para> + If the <code>A</code> and <code>AAAA</code> DNS records on + <literal>example.org</literal> do not point on the same host as the records + for <code>myhostname.example.org</code>, you can easily move the + <code>/.well-known</code> virtualHost section of the code to the host that + is serving <literal>example.org</literal>, while the rest stays on + <literal>myhostname.example.org</literal> with no other changes required. + This pattern also allows to seamlessly move the homeserver from + <literal>myhostname.example.org</literal> to + <literal>myotherhost.example.org</literal> by only changing the + <code>/.well-known</code> redirection target. + </para> + </callout> + <callout arearefs='ex-matrix-synapse-well-known-server'> + <para> + This section is not needed if the <link linkend="opt-services.matrix-synapse.settings.server_name">server_name</link> + of <package>matrix-synapse</package> is equal to the domain (i.e. + <literal>example.org</literal> from <literal>@foo:example.org</literal>) + and the federation port is 8448. + Further reference can be found in the <link xlink:href="https://matrix-org.github.io/synapse/latest/delegate.html">docs + about delegation</link>. + </para> + </callout> + <callout arearefs='ex-matrix-synapse-well-known-client'> + <para> + This is usually needed for homeserver discovery (from e.g. other Matrix clients). + Further reference can be found in the <link xlink:href="https://spec.matrix.org/latest/client-server-api/#getwell-knownmatrixclient">upstream docs</link> + </para> + </callout> + <callout arearefs='ex-matrix-synapse-rev-default'> + <para> + It's also possible to do a redirect here or something else, this vhost is not + needed for Matrix. It's recommended though to <emphasis>not put</emphasis> element + here, see also the <link linkend='ex-matrix-synapse-rev-default'>section about Element</link>. + </para> + </callout> + <callout arearefs='ex-matrix-synapse-rev-proxy-pass'> + <para> + Forward all Matrix API calls to the synapse Matrix homeserver. A trailing slash + <emphasis>must not</emphasis> be used here. + </para> + </callout> + <callout arearefs='ex-matrix-synapse-rev-client'> + <para> + Forward requests for e.g. SSO and password-resets. + </para> + </callout> + </calloutlist> + </section> + <section xml:id="module-services-matrix-register-users"> + <title>Registering Matrix users</title> <para> If you want to run a server with public registration by anybody, you can then enable <literal><link linkend="opt-services.matrix-synapse.settings.enable_registration">services.matrix-synapse.settings.enable_registration</link> = @@ -159,7 +166,7 @@ in { To create a new user or admin, run the following after you have set the secret and have rebuilt NixOS: <screen> -<prompt>$ </prompt>nix run nixpkgs.matrix-synapse +<prompt>$ </prompt>nix-shell -p matrix-synapse <prompt>$ </prompt>register_new_matrix_user -k <replaceable>your-registration-shared-secret</replaceable> http://localhost:8008 <prompt>New user localpart: </prompt><replaceable>your-username</replaceable> <prompt>Password:</prompt> @@ -168,12 +175,51 @@ in { Success! </screen> In the example, this would create a user with the Matrix Identifier - <literal>@your-username:example.org</literal>. Note that the registration - secret ends up in the nix store and therefore is world-readable by any user - on your machine, so it makes sense to only temporarily activate the - <link linkend="opt-services.matrix-synapse.settings.registration_shared_secret">registration_shared_secret</link> - option until a better solution for NixOS is in place. + <literal>@your-username:example.org</literal>. + <warning> + <para> + When using <xref linkend="opt-services.matrix-synapse.settings.registration_shared_secret" />, the secret + will end up in the world-readable store. Instead it's recommended to deploy the secret + in an additional file like this: + <itemizedlist> + <listitem> + <para> + Create a file with the following contents: +<programlisting>registration_shared_secret: your-very-secret-secret</programlisting> + </para> + </listitem> + <listitem> + <para> + Deploy the file with a secret-manager such as <link xlink:href="https://nixops.readthedocs.io/en/latest/overview.html#managing-keys"><option>deployment.keys</option></link> + from <citerefentry><refentrytitle>nixops</refentrytitle><manvolnum>1</manvolnum></citerefentry> + or <link xlink:href="https://github.com/Mic92/sops-nix/">sops-nix</link> to + e.g. <filename>/run/secrets/matrix-shared-secret</filename> and ensure that it's readable + by <package>matrix-synapse</package>. + </para> + </listitem> + <listitem> + <para> + Include the file like this in your configuration: +<programlisting> +{ + <xref linkend="opt-services.matrix-synapse.extraConfigFiles" /> = [ + "/run/secrets/matrix-shared-secret" + ]; +} +</programlisting> + </para> + </listitem> + </itemizedlist> + </para> + </warning> </para> + <note> + <para> + It's also possible to user alternative authentication mechanism such as + <link xlink:href="https://github.com/matrix-org/matrix-synapse-ldap3">LDAP (via <literal>matrix-synapse-ldap3</literal>)</link> + or <link xlink:href="https://matrix-org.github.io/synapse/latest/openid.html">OpenID</link>. + </para> + </note> </section> <section xml:id="module-services-matrix-element-web"> <title>Element (formerly known as Riot) Web Client</title> @@ -206,10 +252,7 @@ Success! <link linkend="opt-services.nginx.virtualHosts._name_.root">root</link> = pkgs.element-web.override { conf = { - default_server_config."m.homeserver" = { - "base_url" = "https://${fqdn}"; - "server_name" = "${fqdn}"; - }; + default_server_config = clientConfig; # see `clientConfig` from the snippet above. }; }; }; @@ -217,15 +260,17 @@ Success! </programlisting> </para> - <para> - Note that the Element developers do not recommend running Element and your Matrix - homeserver on the same fully-qualified domain name for security reasons. In - the example, this means that you should not reuse the - <literal>myhostname.example.org</literal> virtualHost to also serve Element, - but instead serve it on a different subdomain, like - <literal>element.example.org</literal> in the example. See the - <link xlink:href="https://github.com/vector-im/riot-web#important-security-note">Element - Important Security Notes</link> for more information on this subject. - </para> + <note> + <para> + The Element developers do not recommend running Element and your Matrix + homeserver on the same fully-qualified domain name for security reasons. In + the example, this means that you should not reuse the + <literal>myhostname.example.org</literal> virtualHost to also serve Element, + but instead serve it on a different subdomain, like + <literal>element.example.org</literal> in the example. See the + <link xlink:href="https://github.com/vector-im/element-web/tree/v1.10.0#important-security-notes">Element + Important Security Notes</link> for more information on this subject. + </para> + </note> </section> </chapter> diff --git a/nixos/modules/services/misc/dictd.nix b/nixos/modules/services/misc/dictd.nix index 96e2a4e7c2602..8cb51bb0b7a7f 100644 --- a/nixos/modules/services/misc/dictd.nix +++ b/nixos/modules/services/misc/dictd.nix @@ -45,6 +45,10 @@ in # get the command line client on system path to make some use of the service environment.systemPackages = [ pkgs.dict ]; + environment.etc."dict.conf".text = '' + server localhost + ''; + users.users.dictd = { group = "dictd"; description = "DICT.org dictd server"; diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix index 0b8bd08a22bc5..ee59cea38dfd1 100644 --- a/nixos/modules/services/misc/gitlab.nix +++ b/nixos/modules/services/misc/gitlab.nix @@ -1063,7 +1063,7 @@ in { chown ${cfg.user}:${cfg.group} ${cfg.registry.certFile} ''; - serviceConfig = { + unitConfig = { ConditionPathExists = "!${cfg.registry.certFile}"; }; }; diff --git a/nixos/modules/services/networking/lokinet.nix b/nixos/modules/services/networking/lokinet.nix new file mode 100644 index 0000000000000..cf091341c8390 --- /dev/null +++ b/nixos/modules/services/networking/lokinet.nix @@ -0,0 +1,157 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.lokinet; + dataDir = "/var/lib/lokinet"; + settingsFormat = pkgs.formats.ini { listsAsDuplicateKeys = true; }; + configFile = settingsFormat.generate "lokinet.ini" (lib.filterAttrsRecursive (n: v: v != null) cfg.settings); +in with lib; { + options.services.lokinet = { + enable = mkEnableOption "Lokinet daemon"; + + package = mkOption { + type = types.package; + default = pkgs.lokinet; + defaultText = literalExpression "pkgs.lokinet"; + description = "Lokinet package to use."; + }; + + useLocally = mkOption { + type = types.bool; + default = false; + example = true; + description = "Whether to use Lokinet locally."; + }; + + settings = mkOption { + type = with types; + submodule { + freeformType = settingsFormat.type; + + options = { + dns = { + bind = mkOption { + type = str; + default = "127.3.2.1"; + description = "Address to bind to for handling DNS requests."; + }; + + upstream = mkOption { + type = listOf str; + default = [ "9.9.9.10" ]; + example = [ "1.1.1.1" "8.8.8.8" ]; + description = '' + Upstream resolver(s) to use as fallback for non-loki addresses. + Multiple values accepted. + ''; + }; + }; + + network = { + exit = mkOption { + type = bool; + default = false; + description = '' + Whether to act as an exit node. Beware that this + increases demand on the server and may pose liability concerns. + Enable at your own risk. + ''; + }; + + exit-node = mkOption { + type = nullOr (listOf str); + default = null; + example = '' + exit-node = [ "example.loki" ]; # maps all exit traffic to example.loki + exit-node = [ "example.loki:100.0.0.0/24" ]; # maps 100.0.0.0/24 to example.loki + ''; + description = '' + Specify a `.loki` address and an optional ip range to use as an exit broker. + See <link xlink:href="http://probably.loki/wiki/index.php?title=Exit_Nodes"/> for + a list of exit nodes. + ''; + }; + + keyfile = mkOption { + type = nullOr str; + default = null; + example = "snappkey.private"; + description = '' + The private key to persist address with. If not specified the address will be ephemeral. + This keyfile is generated automatically if the specified file doesn't exist. + ''; + }; + }; + }; + }; + default = { }; + example = literalExpression '' + { + dns = { + bind = "127.3.2.1"; + upstream = [ "1.1.1.1" "8.8.8.8" ]; + }; + + network.exit-node = [ "example.loki" "example2.loki" ]; + } + ''; + description = '' + Configuration for Lokinet. + Currently, the best way to view the available settings is by + generating a config file using `lokinet -g`. + ''; + }; + }; + + config = mkIf cfg.enable { + networking.resolvconf.extraConfig = mkIf cfg.useLocally '' + name_servers="${cfg.settings.dns.bind}" + ''; + + systemd.services.lokinet = { + description = "Lokinet"; + after = [ "network-online.target" "network.target" ]; + wants = [ "network-online.target" "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + preStart = '' + ln -sf ${cfg.package}/share/bootstrap.signed ${dataDir} + ${pkgs.coreutils}/bin/install -m 600 ${configFile} ${dataDir}/lokinet.ini + + ${optionalString (cfg.settings.network.keyfile != null) '' + ${pkgs.crudini}/bin/crudini --set ${dataDir}/lokinet.ini network keyfile "${dataDir}/${cfg.settings.network.keyfile}" + ''} + ''; + + serviceConfig = { + DynamicUser = true; + StateDirectory = "lokinet"; + AmbientCapabilities = [ "CAP_NET_ADMIN" "CAP_NET_BIND_SERVICE" ]; + ExecStart = "${cfg.package}/bin/lokinet ${dataDir}/lokinet.ini"; + Restart = "always"; + RestartSec = "5s"; + + # hardening + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateTmp = true; + PrivateMounts = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectSystem = "strict"; + ReadWritePaths = "/dev/net/tun"; + RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + }; + }; + + environment.systemPackages = [ cfg.package ]; + }; +} diff --git a/nixos/modules/services/networking/radvd.nix b/nixos/modules/services/networking/radvd.nix index 6e8db55bbf0d1..fb3eb71a8ce19 100644 --- a/nixos/modules/services/networking/radvd.nix +++ b/nixos/modules/services/networking/radvd.nix @@ -16,9 +16,9 @@ in ###### interface - options = { + options.services.radvd = { - services.radvd.enable = mkOption { + enable = mkOption { type = types.bool; default = false; description = @@ -32,7 +32,16 @@ in ''; }; - services.radvd.config = mkOption { + package = mkOption { + type = types.package; + default = pkgs.radvd; + defaultText = literalExpression "pkgs.radvd"; + description = '' + The RADVD package to use for the RADVD service. + ''; + }; + + config = mkOption { type = types.lines; example = '' @@ -67,7 +76,7 @@ in wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; serviceConfig = - { ExecStart = "@${pkgs.radvd}/bin/radvd radvd -n -u radvd -C ${confFile}"; + { ExecStart = "@${cfg.package}/bin/radvd radvd -n -u radvd -C ${confFile}"; Restart = "always"; }; }; diff --git a/nixos/modules/services/security/privacyidea.nix b/nixos/modules/services/security/privacyidea.nix index b8e2d9a8b0dfc..c1617348fb014 100644 --- a/nixos/modules/services/security/privacyidea.nix +++ b/nixos/modules/services/security/privacyidea.nix @@ -6,7 +6,7 @@ let cfg = config.services.privacyidea; opt = options.services.privacyidea; - uwsgi = pkgs.uwsgi.override { plugins = [ "python3" ]; }; + uwsgi = pkgs.uwsgi.override { plugins = [ "python3" ]; python3 = pkgs.python39; }; python = uwsgi.python3; penv = python.withPackages (const [ pkgs.privacyidea ]); logCfg = pkgs.writeText "privacyidea-log.cfg" '' diff --git a/nixos/modules/services/security/vault.nix b/nixos/modules/services/security/vault.nix index d48bc472cb826..4942a05fe73b7 100644 --- a/nixos/modules/services/security/vault.nix +++ b/nixos/modules/services/security/vault.nix @@ -7,6 +7,8 @@ let opt = options.services.vault; configFile = pkgs.writeText "vault.hcl" '' + # vault in dev mode will refuse to start if its configuration sets listener + ${lib.optionalString (!cfg.dev) '' listener "tcp" { address = "${cfg.address}" ${if (cfg.tlsCertFile == null || cfg.tlsKeyFile == null) then '' @@ -17,6 +19,7 @@ let ''} ${cfg.listenerExtraConfig} } + ''} storage "${cfg.storageBackend}" { ${optionalString (cfg.storagePath != null) ''path = "${cfg.storagePath}"''} ${optionalString (cfg.storageConfig != null) cfg.storageConfig} @@ -30,8 +33,10 @@ let ''; allConfigPaths = [configFile] ++ cfg.extraSettingsPaths; - - configOptions = escapeShellArgs (concatMap (p: ["-config" p]) allConfigPaths); + configOptions = escapeShellArgs + (lib.optional cfg.dev "-dev" ++ + lib.optional (cfg.dev && cfg.devRootTokenID != null) "-dev-root-token-id=${cfg.devRootTokenID}" + ++ (concatMap (p: ["-config" p]) allConfigPaths)); in @@ -47,6 +52,22 @@ in description = "This option specifies the vault package to use."; }; + dev = mkOption { + type = types.bool; + default = false; + description = '' + In this mode, Vault runs in-memory and starts unsealed. This option is not meant production but for development and testing i.e. for nixos tests. + ''; + }; + + devRootTokenID = mkOption { + type = types.str; + default = false; + description = '' + Initial root token. This only applies when <option>services.vault.dev</option> is true + ''; + }; + address = mkOption { type = types.str; default = "127.0.0.1:8200"; @@ -186,6 +207,9 @@ in Group = "vault"; ExecStart = "${cfg.package}/bin/vault server ${configOptions}"; ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID"; + StateDirectory = "vault"; + # In `dev` mode vault will put its token here + Environment = lib.optional (cfg.dev) "HOME=/var/lib/vault"; PrivateDevices = true; PrivateTmp = true; ProtectSystem = "full"; diff --git a/nixos/modules/services/system/cachix-agent/default.nix b/nixos/modules/services/system/cachix-agent/default.nix index f11d80d244d23..3d2e9bc374ba0 100644 --- a/nixos/modules/services/system/cachix-agent/default.nix +++ b/nixos/modules/services/system/cachix-agent/default.nix @@ -17,6 +17,12 @@ in { defaultText = "config.networking.hostName"; }; + verbose = mkOption { + type = types.bool; + description = "Enable verbose output"; + default = false; + }; + profile = mkOption { type = types.nullOr types.str; default = null; @@ -45,13 +51,16 @@ in { after = ["network-online.target"]; path = [ config.nix.package ]; wantedBy = [ "multi-user.target" ]; + # don't restart while changing - reloadIfChanged = true; + restartIfChanged = false; + unitConfig.X-StopOnRemoval = false; + environment.USER = "root"; serviceConfig = { Restart = "on-failure"; EnvironmentFile = cfg.credentialsFile; - ExecStart = "${cfg.package}/bin/cachix deploy agent ${cfg.name} ${if cfg.profile != null then profile else ""}"; + ExecStart = "${cfg.package}/bin/cachix ${lib.optionalString cfg.verbose "--verbose"} deploy agent ${cfg.name} ${if cfg.profile != null then profile else ""}"; }; }; }; diff --git a/nixos/modules/services/web-apps/calibre-web.nix b/nixos/modules/services/web-apps/calibre-web.nix index 704cd2cfa8a7c..2bc817343a9b7 100644 --- a/nixos/modules/services/web-apps/calibre-web.nix +++ b/nixos/modules/services/web-apps/calibre-web.nix @@ -136,7 +136,7 @@ in ${pkgs.sqlite}/bin/sqlite3 ${appDb} "update settings set ${settings}" '' + optionalString (cfg.options.calibreLibrary != null) '' - test -f ${cfg.options.calibreLibrary}/metadata.db || { echo "Invalid Calibre library"; exit 1; } + test -f "${cfg.options.calibreLibrary}/metadata.db" || { echo "Invalid Calibre library"; exit 1; } '' ); diff --git a/nixos/modules/services/web-apps/cryptpad.nix b/nixos/modules/services/web-apps/cryptpad.nix deleted file mode 100644 index e6772de768e0e..0000000000000 --- a/nixos/modules/services/web-apps/cryptpad.nix +++ /dev/null @@ -1,54 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; - -let - cfg = config.services.cryptpad; -in -{ - options.services.cryptpad = { - enable = mkEnableOption "the Cryptpad service"; - - package = mkOption { - default = pkgs.cryptpad; - defaultText = literalExpression "pkgs.cryptpad"; - type = types.package; - description = " - Cryptpad package to use. - "; - }; - - configFile = mkOption { - type = types.path; - default = "${cfg.package}/lib/node_modules/cryptpad/config/config.example.js"; - defaultText = literalExpression ''"''${package}/lib/node_modules/cryptpad/config/config.example.js"''; - description = '' - Path to the JavaScript configuration file. - - See <link - xlink:href="https://github.com/xwiki-labs/cryptpad/blob/master/config/config.example.js"/> - for a configuration example. - ''; - }; - }; - - config = mkIf cfg.enable { - systemd.services.cryptpad = { - description = "Cryptpad Service"; - wantedBy = [ "multi-user.target" ]; - after = [ "networking.target" ]; - serviceConfig = { - DynamicUser = true; - Environment = [ - "CRYPTPAD_CONFIG=${cfg.configFile}" - "HOME=%S/cryptpad" - ]; - ExecStart = "${cfg.package}/bin/cryptpad"; - PrivateTmp = true; - Restart = "always"; - StateDirectory = "cryptpad"; - WorkingDirectory = "%S/cryptpad"; - }; - }; - }; -} diff --git a/nixos/modules/services/web-apps/healthchecks.nix b/nixos/modules/services/web-apps/healthchecks.nix new file mode 100644 index 0000000000000..be025e7dd5518 --- /dev/null +++ b/nixos/modules/services/web-apps/healthchecks.nix @@ -0,0 +1,249 @@ +{ config, lib, pkgs, buildEnv, ... }: + +with lib; + +let + defaultUser = "healthchecks"; + cfg = config.services.healthchecks; + pkg = cfg.package; + boolToPython = b: if b then "True" else "False"; + environment = { + PYTHONPATH = pkg.pythonPath; + STATIC_ROOT = cfg.dataDir + "/static"; + DB_NAME = "${cfg.dataDir}/healthchecks.sqlite"; + } // cfg.settings; + + environmentFile = pkgs.writeText "healthchecks-environment" (lib.generators.toKeyValue { } environment); + + healthchecksManageScript = with pkgs; (writeShellScriptBin "healthchecks-manage" '' + if [[ "$USER" != "${cfg.user}" ]]; then + echo "please run as user 'healtchecks'." >/dev/stderr + exit 1 + fi + export $(cat ${environmentFile} | xargs); + exec ${pkg}/opt/healthchecks/manage.py "$@" + ''); +in +{ + options.services.healthchecks = { + enable = mkEnableOption "healthchecks" // { + description = '' + Enable healthchecks. + It is expected to be run behind a HTTP reverse proxy. + ''; + }; + + package = mkOption { + default = pkgs.healthchecks; + defaultText = literalExpression "pkgs.healthchecks"; + type = types.package; + description = "healthchecks package to use."; + }; + + user = mkOption { + default = defaultUser; + type = types.str; + description = '' + User account under which healthchecks runs. + + <note><para> + If left as the default value this user will automatically be created + on system activation, otherwise you are responsible for + ensuring the user exists before the healthchecks service starts. + </para></note> + ''; + }; + + group = mkOption { + default = defaultUser; + type = types.str; + description = '' + Group account under which healthchecks runs. + + <note><para> + If left as the default value this group will automatically be created + on system activation, otherwise you are responsible for + ensuring the group exists before the healthchecks service starts. + </para></note> + ''; + }; + + listenAddress = mkOption { + type = types.str; + default = "localhost"; + description = "Address the server will listen on."; + }; + + port = mkOption { + type = types.port; + default = 8000; + description = "Port the server will listen on."; + }; + + dataDir = mkOption { + type = types.str; + default = "/var/lib/healthchecks"; + description = '' + The directory used to store all data for healthchecks. + + <note><para> + If left as the default value this directory will automatically be created before + the healthchecks server starts, otherwise you are responsible for ensuring the + directory exists with appropriate ownership and permissions. + </para></note> + ''; + }; + + settings = lib.mkOption { + description = '' + Environment variables which are read by healthchecks <literal>(local)_settings.py</literal>. + + Settings which are explictly covered in options bewlow, are type-checked and/or transformed + before added to the environment, everything else is passed as a string. + + See <link xlink:href="">https://healthchecks.io/docs/self_hosted_configuration/</link> + for a full documentation of settings. + + We add two variables to this list inside the packages <literal>local_settings.py.</literal> + - STATIC_ROOT to set a state directory for dynamically generated static files. + - SECRET_KEY_FILE to read SECRET_KEY from a file at runtime and keep it out of /nix/store. + ''; + type = types.submodule { + freeformType = types.attrsOf types.str; + options = { + ALLOWED_HOSTS = lib.mkOption { + type = types.listOf types.str; + default = [ "*" ]; + description = "The host/domain names that this site can serve."; + apply = lib.concatStringsSep ","; + }; + + SECRET_KEY_FILE = mkOption { + type = types.path; + description = "Path to a file containing the secret key."; + }; + + DEBUG = mkOption { + type = types.bool; + default = false; + description = "Enable debug mode."; + apply = boolToPython; + }; + + REGISTRATION_OPEN = mkOption { + type = types.bool; + default = false; + description = '' + A boolean that controls whether site visitors can create new accounts. + Set it to false if you are setting up a private Healthchecks instance, + but it needs to be publicly accessible (so, for example, your cloud + services can send pings to it). + If you close new user registration, you can still selectively invite + users to your team account. + ''; + apply = boolToPython; + }; + }; + }; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ healthchecksManageScript ]; + + systemd.targets.healthchecks = { + description = "Target for all Healthchecks services"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" "network-online.target" ]; + }; + + systemd.services = + let + commonConfig = { + WorkingDirectory = cfg.dataDir; + User = cfg.user; + Group = cfg.group; + EnvironmentFile = environmentFile; + StateDirectory = mkIf (cfg.dataDir == "/var/lib/healthchecks") "healthchecks"; + StateDirectoryMode = mkIf (cfg.dataDir == "/var/lib/healthchecks") "0750"; + }; + in + { + healthchecks-migration = { + description = "Healthchecks migrations"; + wantedBy = [ "healthchecks.target" ]; + + serviceConfig = commonConfig // { + Restart = "on-failure"; + Type = "oneshot"; + ExecStart = '' + ${pkg}/opt/healthchecks/manage.py migrate + ''; + }; + }; + + healthchecks = { + description = "Healthchecks WSGI Service"; + wantedBy = [ "healthchecks.target" ]; + after = [ "healthchecks-migration.service" ]; + + preStart = '' + ${pkg}/opt/healthchecks/manage.py collectstatic --no-input + ${pkg}/opt/healthchecks/manage.py remove_stale_contenttypes --no-input + ${pkg}/opt/healthchecks/manage.py compress + ''; + + serviceConfig = commonConfig // { + Restart = "always"; + ExecStart = '' + ${pkgs.python3Packages.gunicorn}/bin/gunicorn hc.wsgi \ + --bind ${cfg.listenAddress}:${toString cfg.port} \ + --pythonpath ${pkg}/opt/healthchecks + ''; + }; + }; + + healthchecks-sendalerts = { + description = "Healthchecks Alert Service"; + wantedBy = [ "healthchecks.target" ]; + after = [ "healthchecks.service" ]; + + serviceConfig = commonConfig // { + Restart = "always"; + ExecStart = '' + ${pkg}/opt/healthchecks/manage.py sendalerts + ''; + }; + }; + + healthchecks-sendreports = { + description = "Healthchecks Reporting Service"; + wantedBy = [ "healthchecks.target" ]; + after = [ "healthchecks.service" ]; + + serviceConfig = commonConfig // { + Restart = "always"; + ExecStart = '' + ${pkg}/opt/healthchecks/manage.py sendreports --loop + ''; + }; + }; + }; + + users.users = optionalAttrs (cfg.user == defaultUser) { + ${defaultUser} = + { + description = "healthchecks service owner"; + isSystemUser = true; + group = defaultUser; + }; + }; + + users.groups = optionalAttrs (cfg.user == defaultUser) { + ${defaultUser} = + { + members = [ defaultUser ]; + }; + }; + }; +} diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix index be0b5b94fb26c..8ad92706b0647 100644 --- a/nixos/modules/services/web-apps/jitsi-meet.nix +++ b/nixos/modules/services/web-apps/jitsi-meet.nix @@ -253,9 +253,20 @@ in ''; }; }; - systemd.services.prosody.serviceConfig = mkIf cfg.prosody.enable { - EnvironmentFile = [ "/var/lib/jitsi-meet/secrets-env" ]; - SupplementaryGroups = [ "jitsi-meet" ]; + systemd.services.prosody = mkIf cfg.prosody.enable { + preStart = let + videobridgeSecret = if cfg.videobridge.passwordFile != null then cfg.videobridge.passwordFile else "/var/lib/jitsi-meet/videobridge-secret"; + in '' + ${config.services.prosody.package}/bin/prosodyctl register focus auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jicofo-user-secret)" + ${config.services.prosody.package}/bin/prosodyctl register jvb auth.${cfg.hostName} "$(cat ${videobridgeSecret})" + ${config.services.prosody.package}/bin/prosodyctl mod_roster_command subscribe focus.${cfg.hostName} focus@auth.${cfg.hostName} + ${config.services.prosody.package}/bin/prosodyctl register jibri auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-auth-secret)" + ${config.services.prosody.package}/bin/prosodyctl register recorder recorder.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-recorder-secret)" + ''; + serviceConfig = { + EnvironmentFile = [ "/var/lib/jitsi-meet/secrets-env" ]; + SupplementaryGroups = [ "jitsi-meet" ]; + }; }; users.groups.jitsi-meet = {}; @@ -266,14 +277,12 @@ in systemd.services.jitsi-meet-init-secrets = { wantedBy = [ "multi-user.target" ]; before = [ "jicofo.service" "jitsi-videobridge2.service" ] ++ (optional cfg.prosody.enable "prosody.service"); - path = [ config.services.prosody.package ]; serviceConfig = { Type = "oneshot"; }; script = let secrets = [ "jicofo-component-secret" "jicofo-user-secret" "jibri-auth-secret" "jibri-recorder-secret" ] ++ (optional (cfg.videobridge.passwordFile == null) "videobridge-secret"); - videobridgeSecret = if cfg.videobridge.passwordFile != null then cfg.videobridge.passwordFile else "/var/lib/jitsi-meet/videobridge-secret"; in '' cd /var/lib/jitsi-meet @@ -291,12 +300,6 @@ in chmod 640 secrets-env '' + optionalString cfg.prosody.enable '' - prosodyctl register focus auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jicofo-user-secret)" - prosodyctl register jvb auth.${cfg.hostName} "$(cat ${videobridgeSecret})" - prosodyctl mod_roster_command subscribe focus.${cfg.hostName} focus@auth.${cfg.hostName} - prosodyctl register jibri auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-auth-secret)" - prosodyctl register recorder recorder.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-recorder-secret)" - # generate self-signed certificates if [ ! -f /var/lib/jitsi-meet.crt ]; then ${getBin pkgs.openssl}/bin/openssl req \ diff --git a/nixos/modules/services/web-apps/phylactery.nix b/nixos/modules/services/web-apps/phylactery.nix new file mode 100644 index 0000000000000..f0e97da1f2023 --- /dev/null +++ b/nixos/modules/services/web-apps/phylactery.nix @@ -0,0 +1,51 @@ +{ config, lib, pkgs, ... }: + +with lib; +let cfg = config.services.phylactery; +in { + options.services.phylactery = { + enable = mkEnableOption "Whether to enable Phylactery server"; + + host = mkOption { + type = types.str; + default = "localhost"; + description = "Listen host for Phylactery"; + }; + + port = mkOption { + type = types.port; + description = "Listen port for Phylactery"; + }; + + library = mkOption { + type = types.path; + description = "Path to CBZ library"; + }; + + package = mkOption { + type = types.package; + default = pkgs.phylactery; + defaultText = literalExpression "pkgs.phylactery"; + description = "The Phylactery package to use"; + }; + }; + + config = mkIf cfg.enable { + systemd.services.phylactery = { + environment = { + PHYLACTERY_ADDRESS = "${cfg.host}:${toString cfg.port}"; + PHYLACTERY_LIBRARY = "${cfg.library}"; + }; + + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + ConditionPathExists = cfg.library; + DynamicUser = true; + ExecStart = "${cfg.package}/bin/phylactery"; + }; + }; + }; + + meta.maintainers = with maintainers; [ McSinyx ]; +} diff --git a/nixos/modules/services/x11/desktop-managers/default.nix b/nixos/modules/services/x11/desktop-managers/default.nix index 2c2f2cae4b749..ffdf7e9a86eb5 100644 --- a/nixos/modules/services/x11/desktop-managers/default.nix +++ b/nixos/modules/services/x11/desktop-managers/default.nix @@ -72,7 +72,9 @@ in apply = map (d: d // { manage = "desktop"; start = d.start - + optionalString (needBGCond d) ''\n\n + # literal newline to ensure d.start's last line is not appended to + + optionalString (needBGCond d) '' + if [ -e $HOME/.background-image ]; then ${pkgs.feh}/bin/feh --bg-${cfg.wallpaper.mode} ${optionalString cfg.wallpaper.combineScreens "--no-xinerama"} $HOME/.background-image fi diff --git a/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixos/modules/services/x11/desktop-managers/xfce.nix index 3c2dac386f5db..3a468b550627f 100644 --- a/nixos/modules/services/x11/desktop-managers/xfce.nix +++ b/nixos/modules/services/x11/desktop-managers/xfce.nix @@ -4,10 +4,9 @@ with lib; let cfg = config.services.xserver.desktopManager.xfce; -in +in { - meta = { maintainers = teams.xfce.members; }; @@ -95,7 +94,6 @@ in exo garcon libxfce4ui - xfconf mousepad parole @@ -125,6 +123,7 @@ in xfdesktop ] ++ optional cfg.enableScreensaver xfce4-screensaver; + programs.xfconf.enable = true; programs.thunar.enable = true; environment.pathsToLink = [ diff --git a/nixos/modules/system/boot/systemd/user.nix b/nixos/modules/system/boot/systemd/user.nix index 4951aef95584b..0b1e6277c2f5c 100644 --- a/nixos/modules/system/boot/systemd/user.nix +++ b/nixos/modules/system/boot/systemd/user.nix @@ -14,6 +14,7 @@ let generateUnits targetToUnit serviceToUnit + sliceToUnit socketToUnit timerToUnit pathToUnit; diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix index c8bbfe9769b21..05174e03754b6 100644 --- a/nixos/modules/tasks/filesystems/zfs.nix +++ b/nixos/modules/tasks/filesystems/zfs.nix @@ -549,7 +549,7 @@ in zfs load-key -a '' else concatMapStrings (fs: '' - zfs load-key ${fs} + zfs load-key -- ${escapeShellArg fs} '') cfgZfs.requestEncryptionCredentials} '') rootPools)); @@ -701,7 +701,7 @@ in # expand every pool. Otherwise we want to enumerate # just the specifically provided list of pools. poolListProvider = if cfgExpandOnBoot == "all" - then "$(zpool list -H | awk '{print $1}')" + then "$(zpool list -H -o name)" else lib.escapeShellArgs cfgExpandOnBoot; in { diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix index f622897aa6207..e87f540fd57cb 100644 --- a/nixos/modules/virtualisation/qemu-vm.nix +++ b/nixos/modules/virtualisation/qemu-vm.nix @@ -684,6 +684,21 @@ in ''; }; + virtualisation.useDefaultFilesystems = + mkOption { + type = types.bool; + default = true; + description = + '' + If enabled, the boot disk of the virtual machine will be + formatted and mounted with the default filesystems for + testing. Swap devices and LUKS will be disabled. + + If disabled, a root filesystem has to be specified and + formatted (for example in the initial ramdisk). + ''; + }; + virtualisation.efiVars = mkOption { type = types.str; @@ -754,13 +769,13 @@ in ); boot.loader.grub.gfxmodeBios = with cfg.resolution; "${toString x}x${toString y}"; - boot.initrd.extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) + boot.initrd.extraUtilsCommands = lib.mkIf (cfg.useDefaultFilesystems && !config.boot.initrd.systemd.enable) '' # We need mke2fs in the initrd. copy_bin_and_libs ${pkgs.e2fsprogs}/bin/mke2fs ''; - boot.initrd.postDeviceCommands = lib.mkIf (!config.boot.initrd.systemd.enable) + boot.initrd.postDeviceCommands = lib.mkIf (cfg.useDefaultFilesystems && !config.boot.initrd.systemd.enable) '' # If the disk image appears to be empty, run mke2fs to # initialise. @@ -930,38 +945,41 @@ in }; in mkVMOverride (cfg.fileSystems // - { + optionalAttrs cfg.useDefaultFilesystems { "/".device = cfg.bootDevice; "/".fsType = "ext4"; "/".autoFormat = true; - - "/tmp" = mkIf config.boot.tmpOnTmpfs - { device = "tmpfs"; - fsType = "tmpfs"; - neededForBoot = true; - # Sync with systemd's tmp.mount; - options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmpOnTmpfsSize}" ]; - }; - - "/nix/${if cfg.writableStore then ".ro-store" else "store"}" = - mkIf cfg.useNixStoreImage - { device = "${lookupDriveDeviceName "nix-store" cfg.qemu.drives}"; - neededForBoot = true; - options = [ "ro" ]; - }; - - "/nix/.rw-store" = mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs) - { fsType = "tmpfs"; - options = [ "mode=0755" ]; - neededForBoot = true; - }; - - "/boot" = mkIf cfg.useBootLoader - # see note [Disk layout with `useBootLoader`] - { device = "${lookupDriveDeviceName "boot" cfg.qemu.drives}2"; # 2 for e.g. `vdb2`, as created in `bootDisk` - fsType = "vfat"; - noCheck = true; # fsck fails on a r/o filesystem - }; + } // + optionalAttrs config.boot.tmpOnTmpfs { + "/tmp" = { + device = "tmpfs"; + fsType = "tmpfs"; + neededForBoot = true; + # Sync with systemd's tmp.mount; + options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmpOnTmpfsSize}" ]; + }; + } // + optionalAttrs cfg.useNixStoreImage { + "/nix/${if cfg.writableStore then ".ro-store" else "store"}" = { + device = "${lookupDriveDeviceName "nix-store" cfg.qemu.drives}"; + neededForBoot = true; + options = [ "ro" ]; + }; + } // + optionalAttrs (cfg.writableStore && cfg.writableStoreUseTmpfs) { + "/nix/.rw-store" = { + fsType = "tmpfs"; + options = [ "mode=0755" ]; + neededForBoot = true; + }; + } // + optionalAttrs cfg.useBootLoader { + # see note [Disk layout with `useBootLoader`] + "/boot" = { + device = "${lookupDriveDeviceName "boot" cfg.qemu.drives}2"; # 2 for e.g. `vdb2`, as created in `bootDisk` + fsType = "vfat"; + noCheck = true; # fsck fails on a r/o filesystem + }; } // lib.mapAttrs' mkSharedDir cfg.sharedDirectories); boot.initrd.systemd = lib.mkIf (config.boot.initrd.systemd.enable && cfg.writableStore) { @@ -986,8 +1004,8 @@ in }; }; - swapDevices = mkVMOverride [ ]; - boot.initrd.luks.devices = mkVMOverride {}; + swapDevices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) [ ]; + boot.initrd.luks.devices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) {}; # Don't run ntpd in the guest. It should get the correct time from KVM. services.timesyncd.enable = false; diff --git a/nixos/modules/virtualisation/railcar.nix b/nixos/modules/virtualisation/railcar.nix deleted file mode 100644 index e719e25650d37..0000000000000 --- a/nixos/modules/virtualisation/railcar.nix +++ /dev/null @@ -1,124 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; - -let - cfg = config.services.railcar; - generateUnit = name: containerConfig: - let - container = pkgs.ociTools.buildContainer { - args = [ - (pkgs.writeShellScript "run.sh" containerConfig.cmd).outPath - ]; - }; - in - nameValuePair "railcar-${name}" { - enable = true; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - ExecStart = '' - ${cfg.package}/bin/railcar -r ${cfg.stateDir} run ${name} -b ${container} - ''; - Type = containerConfig.runType; - }; - }; - mount = with types; (submodule { - options = { - type = mkOption { - type = str; - default = "none"; - description = '' - The type of the filesystem to be mounted. - Linux: filesystem types supported by the kernel as listed in - `/proc/filesystems` (e.g., "minix", "ext2", "ext3", "jfs", "xfs", - "reiserfs", "msdos", "proc", "nfs", "iso9660"). For bind mounts - (when options include either bind or rbind), the type is a dummy, - often "none" (not listed in /proc/filesystems). - ''; - }; - source = mkOption { - type = str; - description = "Source for the in-container mount"; - }; - options = mkOption { - type = listOf str; - default = [ "bind" ]; - description = '' - Mount options of the filesystem to be used. - - Support options are listed in the mount(8) man page. Note that - both filesystem-independent and filesystem-specific options - are listed. - ''; - }; - }; - }); -in -{ - options.services.railcar = { - enable = mkEnableOption "railcar"; - - containers = mkOption { - default = {}; - description = "Declarative container configuration"; - type = with types; attrsOf (submodule ({ name, config, ... }: { - options = { - cmd = mkOption { - type = types.lines; - description = "Command or script to run inside the container"; - }; - - mounts = mkOption { - type = with types; attrsOf mount; - default = {}; - description = '' - A set of mounts inside the container. - - The defaults have been chosen for simple bindmounts, meaning - that you only need to provide the "source" parameter. - ''; - example = { "/data" = { source = "/var/lib/data"; }; }; - }; - - runType = mkOption { - type = types.str; - default = "oneshot"; - description = "The systemd service run type"; - }; - - os = mkOption { - type = types.str; - default = "linux"; - description = "OS type of the container"; - }; - - arch = mkOption { - type = types.str; - default = "x86_64"; - description = "Computer architecture type of the container"; - }; - }; - })); - }; - - stateDir = mkOption { - type = types.path; - default = "/var/railcar"; - description = "Railcar persistent state directory"; - }; - - package = mkOption { - type = types.package; - default = pkgs.railcar; - defaultText = literalExpression "pkgs.railcar"; - description = "Railcar package to use"; - }; - }; - - config = mkIf cfg.enable { - systemd.services = flip mapAttrs' cfg.containers (name: containerConfig: - generateUnit name containerConfig - ); - }; -} - diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 671b1a8876a05..d596db8ab7467 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -110,7 +110,6 @@ in { cri-o = handleTestOn ["x86_64-linux"] ./cri-o.nix {}; custom-ca = handleTest ./custom-ca.nix {}; croc = handleTest ./croc.nix {}; - cryptpad = handleTest ./cryptpad.nix {}; deluge = handleTest ./deluge.nix {}; dendrite = handleTest ./matrix/dendrite.nix {}; dex-oidc = handleTest ./dex-oidc.nix {}; @@ -203,6 +202,7 @@ in { haste-server = handleTest ./haste-server.nix {}; haproxy = handleTest ./haproxy.nix {}; hardened = handleTest ./hardened.nix {}; + healthchecks = handleTest ./web-apps/healthchecks.nix {}; hbase1 = handleTest ./hbase.nix { package=pkgs.hbase1; }; hbase2 = handleTest ./hbase.nix { package=pkgs.hbase2; }; hbase3 = handleTest ./hbase.nix { package=pkgs.hbase3; }; @@ -383,6 +383,7 @@ in { nixpkgs = pkgs.callPackage ../modules/misc/nixpkgs/test.nix { inherit evalMinimalConfig; }; node-red = handleTest ./node-red.nix {}; nomad = handleTest ./nomad.nix {}; + non-default-filesystems = handleTest ./non-default-filesystems.nix {}; noto-fonts = handleTest ./noto-fonts.nix {}; novacomd = handleTestOn ["x86_64-linux"] ./novacomd.nix {}; nsd = handleTest ./nsd.nix {}; @@ -425,6 +426,7 @@ in { php = handleTest ./php {}; php80 = handleTest ./php { php = pkgs.php80; }; php81 = handleTest ./php { php = pkgs.php81; }; + phylactery = handleTest ./web-apps/phylactery.nix {}; pict-rs = handleTest ./pict-rs.nix {}; pinnwand = handleTest ./pinnwand.nix {}; plasma5 = handleTest ./plasma5.nix {}; @@ -517,6 +519,7 @@ in { step-ca = handleTestOn ["x86_64-linux"] ./step-ca.nix {}; strongswan-swanctl = handleTest ./strongswan-swanctl.nix {}; sudo = handleTest ./sudo.nix {}; + swap-partition = handleTest ./swap-partition.nix {}; sway = handleTest ./sway.nix {}; switchTest = handleTest ./switch-test.nix {}; sympa = handleTest ./sympa.nix {}; @@ -586,6 +589,7 @@ in { uwsgi = handleTest ./uwsgi.nix {}; v2ray = handleTest ./v2ray.nix {}; vault = handleTest ./vault.nix {}; + vault-dev = handleTest ./vault-dev.nix {}; vault-postgresql = handleTest ./vault-postgresql.nix {}; vaultwarden = handleTest ./vaultwarden.nix {}; vector = handleTest ./vector.nix {}; diff --git a/nixos/tests/cryptpad.nix b/nixos/tests/cryptpad.nix deleted file mode 100644 index db271f937ef42..0000000000000 --- a/nixos/tests/cryptpad.nix +++ /dev/null @@ -1,18 +0,0 @@ -import ./make-test-python.nix ({ lib, ... }: - -with lib; - -{ - name = "cryptpad"; - meta.maintainers = with maintainers; [ davhau ]; - - nodes.machine = - { pkgs, ... }: - { services.cryptpad.enable = true; }; - - testScript = '' - machine.wait_for_unit("cryptpad.service") - machine.wait_for_open_port(3000) - machine.succeed("curl -L --fail http://localhost:3000/sheet") - ''; -}) diff --git a/nixos/tests/installed-tests/default.nix b/nixos/tests/installed-tests/default.nix index c6fb37cfe5842..b81384aa8c0b5 100644 --- a/nixos/tests/installed-tests/default.nix +++ b/nixos/tests/installed-tests/default.nix @@ -92,6 +92,7 @@ in fwupd = callInstalledTest ./fwupd.nix {}; gcab = callInstalledTest ./gcab.nix {}; gdk-pixbuf = callInstalledTest ./gdk-pixbuf.nix {}; + geocode-glib = callInstalledTest ./geocode-glib.nix {}; gjs = callInstalledTest ./gjs.nix {}; glib-networking = callInstalledTest ./glib-networking.nix {}; gnome-photos = callInstalledTest ./gnome-photos.nix {}; diff --git a/nixos/tests/installed-tests/geocode-glib.nix b/nixos/tests/installed-tests/geocode-glib.nix new file mode 100644 index 0000000000000..fcb38c96ab0f6 --- /dev/null +++ b/nixos/tests/installed-tests/geocode-glib.nix @@ -0,0 +1,13 @@ +{ pkgs, makeInstalledTest, ... }: + +makeInstalledTest { + testConfig = { + i18n.supportedLocales = [ + "en_US.UTF-8/UTF-8" + # The tests require this locale available. + "en_GB.UTF-8/UTF-8" + ]; + }; + + tested = pkgs.geocode-glib; +} diff --git a/nixos/tests/jitsi-meet.nix b/nixos/tests/jitsi-meet.nix index 41d53bc73800e..c39cd19e1f0a7 100644 --- a/nixos/tests/jitsi-meet.nix +++ b/nixos/tests/jitsi-meet.nix @@ -34,9 +34,6 @@ import ./make-test-python.nix ({ pkgs, ... }: { server.wait_for_unit("prosody.service") server.wait_until_succeeds( - "journalctl -b -u jitsi-videobridge2 -o cat | grep -q 'Performed a successful health check'" - ) - server.wait_until_succeeds( "journalctl -b -u prosody -o cat | grep -q 'Authenticated as focus@auth.server'" ) server.wait_until_succeeds( diff --git a/nixos/tests/maddy.nix b/nixos/tests/maddy.nix index 581748c1fa59b..b9d0416482da1 100644 --- a/nixos/tests/maddy.nix +++ b/nixos/tests/maddy.nix @@ -49,7 +49,7 @@ import ./make-test-python.nix ({ pkgs, ... }: { server.wait_for_open_port(143) server.wait_for_open_port(587) - server.succeed("echo test | maddyctl creds create postmaster@server") + server.succeed("maddyctl creds create --password test postmaster@server") server.succeed("maddyctl imap-acct create postmaster@server") client.succeed("send-testmail") diff --git a/nixos/tests/nginx-http3.nix b/nixos/tests/nginx-http3.nix index edd0759464c8a..319f6aac184ab 100644 --- a/nixos/tests/nginx-http3.nix +++ b/nixos/tests/nginx-http3.nix @@ -70,6 +70,9 @@ in testScript = '' start_all() + server.wait_for_unit("nginx") + server.wait_for_open_port(443) + # Check http connections client.succeed("curl --verbose --http3 https://acme.test | grep 'Hello World!'") diff --git a/nixos/tests/non-default-filesystems.nix b/nixos/tests/non-default-filesystems.nix new file mode 100644 index 0000000000000..7fa75aaad724d --- /dev/null +++ b/nixos/tests/non-default-filesystems.nix @@ -0,0 +1,54 @@ +import ./make-test-python.nix ({ lib, pkgs, ... }: +{ + name = "non-default-filesystems"; + + nodes.machine = + { config, pkgs, lib, ... }: + let + disk = config.virtualisation.bootDevice; + in + { + virtualisation.useDefaultFilesystems = false; + + boot.initrd.availableKernelModules = [ "btrfs" ]; + boot.supportedFilesystems = [ "btrfs" ]; + + boot.initrd.postDeviceCommands = '' + FSTYPE=$(blkid -o value -s TYPE ${disk} || true) + if test -z "$FSTYPE"; then + modprobe btrfs + ${pkgs.btrfs-progs}/bin/mkfs.btrfs ${disk} + + mkdir /nixos + mount -t btrfs ${disk} /nixos + + ${pkgs.btrfs-progs}/bin/btrfs subvolume create /nixos/root + ${pkgs.btrfs-progs}/bin/btrfs subvolume create /nixos/home + + umount /nixos + fi + ''; + + virtualisation.fileSystems = { + "/" = { + device = disk; + fsType = "btrfs"; + options = [ "subvol=/root" ]; + }; + + "/home" = { + device = disk; + fsType = "btrfs"; + options = [ "subvol=/home" ]; + }; + }; + }; + + testScript = '' + machine.wait_for_unit("multi-user.target") + + with subtest("BTRFS filesystems are mounted correctly"): + machine.succeed("grep -E '/dev/vda / btrfs rw,relatime,space_cache=v2,subvolid=[0-9]+,subvol=/root 0 0' /proc/mounts") + machine.succeed("grep -E '/dev/vda /home btrfs rw,relatime,space_cache=v2,subvolid=[0-9]+,subvol=/home 0 0' /proc/mounts") + ''; +}) diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix index 849f5ee159f0c..a3092d101d87e 100644 --- a/nixos/tests/prometheus-exporters.nix +++ b/nixos/tests/prometheus-exporters.nix @@ -1335,7 +1335,7 @@ mapAttrs ''; meta = with maintainers; { - maintainers = [ willibutz elseym ]; + maintainers = [ willibutz ]; }; } ))) diff --git a/nixos/tests/swap-partition.nix b/nixos/tests/swap-partition.nix new file mode 100644 index 0000000000000..2279630b57b8f --- /dev/null +++ b/nixos/tests/swap-partition.nix @@ -0,0 +1,48 @@ +import ./make-test-python.nix ({ lib, pkgs, ... }: +{ + name = "swap-partition"; + + nodes.machine = + { config, pkgs, lib, ... }: + { + virtualisation.useDefaultFilesystems = false; + + virtualisation.bootDevice = "/dev/vda1"; + + boot.initrd.postDeviceCommands = '' + if ! test -b /dev/vda1; then + ${pkgs.parted}/bin/parted --script /dev/vda -- mklabel msdos + ${pkgs.parted}/bin/parted --script /dev/vda -- mkpart primary 1MiB -250MiB + ${pkgs.parted}/bin/parted --script /dev/vda -- mkpart primary -250MiB 100% + sync + fi + + FSTYPE=$(blkid -o value -s TYPE /dev/vda1 || true) + if test -z "$FSTYPE"; then + ${pkgs.e2fsprogs}/bin/mke2fs -t ext4 -L root /dev/vda1 + ${pkgs.util-linux}/bin/mkswap --label swap /dev/vda2 + fi + ''; + + virtualisation.fileSystems = { + "/" = { + device = "/dev/disk/by-label/root"; + fsType = "ext4"; + }; + }; + + swapDevices = [ + { + device = "/dev/disk/by-label/swap"; + } + ]; + }; + + testScript = '' + machine.wait_for_unit("multi-user.target") + + with subtest("Swap is active"): + # Doesn't matter if the numbers reported by `free` are slightly off due to unit conversions. + machine.succeed("free -h | grep -E 'Swap:\s+2[45][0-9]Mi'") + ''; +}) diff --git a/nixos/tests/vault-dev.nix b/nixos/tests/vault-dev.nix new file mode 100644 index 0000000000000..ba9a1015cc13c --- /dev/null +++ b/nixos/tests/vault-dev.nix @@ -0,0 +1,35 @@ +import ./make-test-python.nix ({ pkgs, ... }: +{ + name = "vault-dev"; + meta = with pkgs.lib.maintainers; { + maintainers = [ lnl7 mic92 ]; + }; + nodes.machine = { pkgs, config, ... }: { + environment.systemPackages = [ pkgs.vault ]; + environment.variables.VAULT_ADDR = "http://127.0.0.1:8200"; + environment.variables.VAULT_TOKEN = "phony-secret"; + + services.vault = { + enable = true; + dev = true; + devRootTokenID = config.environment.variables.VAULT_TOKEN; + }; + }; + + testScript = '' + import json + start_all() + machine.wait_for_unit("multi-user.target") + machine.wait_for_unit("vault.service") + machine.wait_for_open_port(8200) + out = machine.succeed("vault status -format=json") + print(out) + status = json.loads(out) + assert status.get("initialized") == True + machine.succeed("vault kv put secret/foo bar=baz") + out = machine.succeed("vault kv get -format=json secret/foo") + print(out) + status = json.loads(out) + assert status.get("data", {}).get("data", {}).get("bar") == "baz" + ''; +}) diff --git a/nixos/tests/web-apps/healthchecks.nix b/nixos/tests/web-apps/healthchecks.nix new file mode 100644 index 0000000000000..41374f5e314bb --- /dev/null +++ b/nixos/tests/web-apps/healthchecks.nix @@ -0,0 +1,42 @@ +import ../make-test-python.nix ({ lib, pkgs, ... }: { + name = "healthchecks"; + + meta = with lib.maintainers; { + maintainers = [ phaer ]; + }; + + nodes.machine = { ... }: { + services.healthchecks = { + enable = true; + settings = { + SITE_NAME = "MyUniqueInstance"; + COMPRESS_ENABLED = "True"; + SECRET_KEY_FILE = pkgs.writeText "secret" + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + }; + }; + }; + + testScript = '' + machine.start() + machine.wait_for_unit("healthchecks.target") + machine.wait_until_succeeds("journalctl --since -1m --unit healthchecks --grep Listening") + + with subtest("Home screen loads"): + machine.succeed( + "curl -sSfL http://localhost:8000 | grep '<title>Sign In'" + ) + + with subtest("Setting SITE_NAME via freeform option works"): + machine.succeed( + "curl -sSfL http://localhost:8000 | grep 'MyUniqueInstance</title>'" + ) + + with subtest("Manage script works"): + # Should fail if not called by healthchecks user + machine.fail("echo 'print(\"foo\")' | healthchecks-manage help") + + # "shell" sucommand should succeed, needs python in PATH. + assert "foo\n" == machine.succeed("echo 'print(\"foo\")' | sudo -u healthchecks healthchecks-manage shell") + ''; +}) diff --git a/nixos/tests/web-apps/phylactery.nix b/nixos/tests/web-apps/phylactery.nix new file mode 100644 index 0000000000000..cf2689d2300d3 --- /dev/null +++ b/nixos/tests/web-apps/phylactery.nix @@ -0,0 +1,20 @@ +import ../make-test-python.nix ({ pkgs, lib, ... }: { + name = "phylactery"; + + nodes.machine = { ... }: { + services.phylactery = rec { + enable = true; + port = 8080; + library = "/tmp"; + }; + }; + + testScript = '' + start_all() + machine.wait_for_unit('phylactery') + machine.wait_for_open_port(8080) + machine.wait_until_succeeds('curl localhost:8080') + ''; + + meta.maintainers = with lib.maintainers; [ McSinyx ]; +}) diff --git a/nixos/tests/xrdp.nix b/nixos/tests/xrdp.nix index 0e1d521c5aced..f277d4b795256 100644 --- a/nixos/tests/xrdp.nix +++ b/nixos/tests/xrdp.nix @@ -1,7 +1,7 @@ import ./make-test-python.nix ({ pkgs, ...} : { name = "xrdp"; meta = with pkgs.lib.maintainers; { - maintainers = [ volth ]; + maintainers = [ ]; }; nodes = { |