diff options
Diffstat (limited to 'nixos')
867 files changed, 27141 insertions, 12474 deletions
diff --git a/nixos/doc/manual/configuration/abstractions.xml b/nixos/doc/manual/configuration/abstractions.xml index 5bf0635cc1aa6..df9ff2615e1ae 100644 --- a/nixos/doc/manual/configuration/abstractions.xml +++ b/nixos/doc/manual/configuration/abstractions.xml @@ -11,50 +11,46 @@ <programlisting> { <xref linkend="opt-services.httpd.virtualHosts"/> = - [ { hostName = "example.org"; - documentRoot = "/webroot"; + { "blog.example.org" = { + documentRoot = "/webroot/blog.example.org"; adminAddr = "alice@example.org"; - enableUserDir = true; - } - { hostName = "example.org"; - documentRoot = "/webroot"; + forceSSL = true; + enableACME = true; + enablePHP = true; + }; + "wiki.example.org" = { + documentRoot = "/webroot/wiki.example.org"; adminAddr = "alice@example.org"; - enableUserDir = true; - enableSSL = true; - sslServerCert = "/root/ssl-example-org.crt"; - sslServerKey = "/root/ssl-example-org.key"; - } - ]; + forceSSL = true; + enableACME = true; + enablePHP = true; + }; + }; } </programlisting> It defines two virtual hosts with nearly identical configuration; the only - difference is that the second one has SSL enabled. To prevent this + difference is the document root directories. To prevent this duplication, we can use a <literal>let</literal>: <programlisting> let - exampleOrgCommon = - { hostName = "example.org"; - documentRoot = "/webroot"; - adminAddr = "alice@example.org"; - enableUserDir = true; + commonConfig = + { adminAddr = "alice@example.org"; + forceSSL = true; + enableACME = true; }; in { <xref linkend="opt-services.httpd.virtualHosts"/> = - [ exampleOrgCommon - (exampleOrgCommon // { - enableSSL = true; - sslServerCert = "/root/ssl-example-org.crt"; - sslServerKey = "/root/ssl-example-org.key"; - }) - ]; + { "blog.example.org" = (commonConfig // { documentRoot = "/webroot/blog.example.org"; }); + "wiki.example.org" = (commonConfig // { documentRoot = "/webroot/wiki.example.com"; }); + }; } </programlisting> - The <literal>let exampleOrgCommon = <replaceable>...</replaceable></literal> - defines a variable named <literal>exampleOrgCommon</literal>. The + The <literal>let commonConfig = <replaceable>...</replaceable></literal> + defines a variable named <literal>commonConfig</literal>. The <literal>//</literal> operator merges two attribute sets, so the configuration of the second virtual host is the set - <literal>exampleOrgCommon</literal> extended with the SSL options. + <literal>commonConfig</literal> extended with the document root option. </para> <para> @@ -63,13 +59,13 @@ in <programlisting> { <xref linkend="opt-services.httpd.virtualHosts"/> = - let exampleOrgCommon = <replaceable>...</replaceable>; in - [ exampleOrgCommon - (exampleOrgCommon // { <replaceable>...</replaceable> }) - ]; + let commonConfig = <replaceable>...</replaceable>; in + { "blog.example.org" = (commonConfig // { <replaceable>...</replaceable> }) + "wiki.example.org" = (commonConfig // { <replaceable>...</replaceable> }) + }; } </programlisting> - but not <literal>{ let exampleOrgCommon = <replaceable>...</replaceable>; in + but not <literal>{ let commonConfig = <replaceable>...</replaceable>; in <replaceable>...</replaceable>; }</literal> since attributes (as opposed to attribute values) are not expressions. </para> @@ -77,80 +73,29 @@ in <para> <emphasis>Functions</emphasis> provide another method of abstraction. For instance, suppose that we want to generate lots of different virtual hosts, - all with identical configuration except for the host name. This can be done + all with identical configuration except for the document root. This can be done as follows: <programlisting> { <xref linkend="opt-services.httpd.virtualHosts"/> = let - makeVirtualHost = name: - { hostName = name; - documentRoot = "/webroot"; + makeVirtualHost = webroot: + { documentRoot = webroot; adminAddr = "alice@example.org"; + forceSSL = true; + enableACME = true; }; in - [ (makeVirtualHost "example.org") - (makeVirtualHost "example.com") - (makeVirtualHost "example.gov") - (makeVirtualHost "example.nl") - ]; + { "example.org" = (makeVirtualHost "/webroot/example.org"); + "example.com" = (makeVirtualHost "/webroot/example.com"); + "example.gov" = (makeVirtualHost "/webroot/example.gov"); + "example.nl" = (makeVirtualHost "/webroot/example.nl"); + }; } </programlisting> Here, <varname>makeVirtualHost</varname> is a function that takes a single - argument <literal>name</literal> and returns the configuration for a virtual + argument <literal>webroot</literal> and returns the configuration for a virtual host. That function is then called for several names to produce the list of virtual host configurations. </para> - - <para> - We can further improve on this by using the function <varname>map</varname>, - which applies another function to every element in a list: -<programlisting> -{ - <xref linkend="opt-services.httpd.virtualHosts"/> = - let - makeVirtualHost = <replaceable>...</replaceable>; - in map makeVirtualHost - [ "example.org" "example.com" "example.gov" "example.nl" ]; -} -</programlisting> - (The function <literal>map</literal> is called a <emphasis>higher-order - function</emphasis> because it takes another function as an argument.) - </para> - - <para> - What if you need more than one argument, for instance, if we want to use a - different <literal>documentRoot</literal> for each virtual host? Then we can - make <varname>makeVirtualHost</varname> a function that takes a - <emphasis>set</emphasis> as its argument, like this: -<programlisting> -{ - <xref linkend="opt-services.httpd.virtualHosts"/> = - let - makeVirtualHost = { name, root }: - { hostName = name; - documentRoot = root; - adminAddr = "alice@example.org"; - }; - in map makeVirtualHost - [ { name = "example.org"; root = "/sites/example.org"; } - { name = "example.com"; root = "/sites/example.com"; } - { name = "example.gov"; root = "/sites/example.gov"; } - { name = "example.nl"; root = "/sites/example.nl"; } - ]; -} -</programlisting> - But in this case (where every root is a subdirectory of - <filename>/sites</filename> named after the virtual host), it would have been - shorter to define <varname>makeVirtualHost</varname> as -<programlisting> -makeVirtualHost = name: - { hostName = name; - documentRoot = "/sites/${name}"; - adminAddr = "alice@example.org"; - }; -</programlisting> - Here, the construct <literal>${<replaceable>...</replaceable>}</literal> - allows the result of an expression to be spliced into a string. - </para> </section> diff --git a/nixos/doc/manual/configuration/config-file.xml b/nixos/doc/manual/configuration/config-file.xml index eadafb94b8f6d..7ccb5b3664ea3 100644 --- a/nixos/doc/manual/configuration/config-file.xml +++ b/nixos/doc/manual/configuration/config-file.xml @@ -27,7 +27,7 @@ { <xref linkend="opt-services.httpd.enable"/> = true; <xref linkend="opt-services.httpd.adminAddr"/> = "alice@example.org"; - <xref linkend="opt-services.httpd.documentRoot"/> = "/webroot"; + <link linkend="opt-services.httpd.virtualHosts">services.httpd.virtualHosts.localhost.documentRoot</link> = "/webroot"; } </programlisting> defines a configuration with three option definitions that together enable @@ -50,7 +50,11 @@ httpd = { enable = true; adminAddr = "alice@example.org"; - documentRoot = "/webroot"; + virtualHosts = { + localhost = { + documentRoot = "/webroot"; + }; + }; }; }; } diff --git a/nixos/doc/manual/configuration/configuration.xml b/nixos/doc/manual/configuration/configuration.xml index 5961209bc13ac..507d28814ead3 100644 --- a/nixos/doc/manual/configuration/configuration.xml +++ b/nixos/doc/manual/configuration/configuration.xml @@ -21,7 +21,6 @@ <xi:include href="xfce.xml" /> <xi:include href="networking.xml" /> <xi:include href="linux-kernel.xml" /> - <xi:include href="matrix.xml" /> <xi:include href="../generated/modules.xml" xpointer="xpointer(//section[@id='modules']/*)" /> <xi:include href="profiles.xml" /> <xi:include href="kubernetes.xml" /> diff --git a/nixos/doc/manual/configuration/declarative-packages.xml b/nixos/doc/manual/configuration/declarative-packages.xml index 5fb3bcb9f8f56..cd84d1951d247 100644 --- a/nixos/doc/manual/configuration/declarative-packages.xml +++ b/nixos/doc/manual/configuration/declarative-packages.xml @@ -19,6 +19,12 @@ <command>nixos-rebuild switch</command>. </para> + <note> + <para> + Some packages require additional global configuration such as D-Bus or systemd service registration so adding them to <xref linkend="opt-environment.systemPackages"/> might not be sufficient. You are advised to check the <link xlink:href="#ch-options">list of options</link> whether a NixOS module for the package does not exist. + </para> + </note> + <para> You can get a list of the available packages as follows: <screen> diff --git a/nixos/doc/manual/configuration/luks-file-systems.xml b/nixos/doc/manual/configuration/luks-file-systems.xml index 8a2b107e0ee8a..d3007843d68bd 100644 --- a/nixos/doc/manual/configuration/luks-file-systems.xml +++ b/nixos/doc/manual/configuration/luks-file-systems.xml @@ -37,4 +37,38 @@ Enter passphrase for /dev/disk/by-uuid/3f6b0024-3a44-4fde-a43a-767b872abe5d: *** on an encrypted partition, it is necessary to add the following grub option: <programlisting><xref linkend="opt-boot.loader.grub.enableCryptodisk"/> = true;</programlisting> </para> + <section xml:id="sec-luks-file-systems-fido2"> + <title>FIDO2</title> + + <para> + NixOS also supports unlocking your LUKS-Encrypted file system using a FIDO2 compatible token. In the following example, we will create a new FIDO2 credential + and add it as a new key to our existing device <filename>/dev/sda2</filename>: + + <screen> +# export FIDO2_LABEL="/dev/sda2 @ $HOSTNAME" +# fido2luks credential "$FIDO2_LABEL" +f1d00200108b9d6e849a8b388da457688e3dd653b4e53770012d8f28e5d3b269865038c346802f36f3da7278b13ad6a3bb6a1452e24ebeeaa24ba40eef559b1b287d2a2f80b7 + +# fido2luks -i add-key /dev/sda2 f1d00200108b9d6e849a8b388da457688e3dd653b4e53770012d8f28e5d3b269865038c346802f36f3da7278b13ad6a3bb6a1452e24ebeeaa24ba40eef559b1b287d2a2f80b7 +Password: +Password (again): +Old password: +Old password (again): +Added to key to device /dev/sda2, slot: 2 +</screen> + + To ensure that this file system is decrypted using the FIDO2 compatible key, add the following to <filename>configuration.nix</filename>: +<programlisting> +<link linkend="opt-boot.initrd.luks.fido2Support">boot.initrd.luks.fido2Support</link> = true; +<link linkend="opt-boot.initrd.luks.devices._name__.fido2.credential">boot.initrd.luks.devices."/dev/sda2".fido2.credential</link> = "f1d00200108b9d6e849a8b388da457688e3dd653b4e53770012d8f28e5d3b269865038c346802f36f3da7278b13ad6a3bb6a1452e24ebeeaa24ba40eef559b1b287d2a2f80b7"; +</programlisting> + + You can also use the FIDO2 passwordless setup, but for security reasons, you might want to enable it only when your device is PIN protected, such as <link xlink:href="https://trezor.io/">Trezor</link>. + +<programlisting> +<link linkend="opt-boot.initrd.luks.devices._name__.fido2.passwordLess">boot.initrd.luks.devices."/dev/sda2".fido2.passwordLess</link> = true; +</programlisting> + </para> + </section> + </section> diff --git a/nixos/doc/manual/configuration/modularity.xml b/nixos/doc/manual/configuration/modularity.xml index 7ad0ae80a48af..532a2c615e4df 100644 --- a/nixos/doc/manual/configuration/modularity.xml +++ b/nixos/doc/manual/configuration/modularity.xml @@ -36,6 +36,7 @@ { <xref linkend="opt-services.xserver.enable"/> = true; <xref linkend="opt-services.xserver.displayManager.sddm.enable"/> = true; <xref linkend="opt-services.xserver.desktopManager.plasma5.enable"/> = true; + <xref linkend="opt-environment.systemPackages"/> = [ pkgs.vim ]; } </programlisting> Note that both <filename>configuration.nix</filename> and diff --git a/nixos/doc/manual/configuration/network-manager.xml b/nixos/doc/manual/configuration/network-manager.xml index d103ee2497839..3953e0ffe851a 100644 --- a/nixos/doc/manual/configuration/network-manager.xml +++ b/nixos/doc/manual/configuration/network-manager.xml @@ -28,17 +28,21 @@ <command>nmtui</command> (curses-based terminal user interface). See their manual pages for details on their usage. Some desktop environments (GNOME, KDE) have their own configuration tools for NetworkManager. On XFCE, there is - no configuration tool for NetworkManager by default: by adding - <code>networkmanagerapplet</code> to the list of system packages, the - graphical applet will be installed and will launch automatically when XFCE is - starting (and will show in the status tray). + no configuration tool for NetworkManager by default: by enabling <xref linkend="opt-programs.nm-applet.enable"/>, the + graphical applet will be installed and will launch automatically when the graphical session is started. </para> <note> <para> <code>networking.networkmanager</code> and <code>networking.wireless</code> - (WPA Supplicant) cannot be enabled at the same time: you can still connect - to the wireless networks using NetworkManager. + (WPA Supplicant) can be used together if desired. To do this you need to instruct + NetworkManager to ignore those interfaces like: +<programlisting> +<xref linkend="opt-networking.networkmanager.unmanaged"/> = [ + "*" "except:type:wwan" "except:type:gsm" +]; +</programlisting> + Refer to the option description for the exact syntax and references to external documentation. </para> </note> </section> diff --git a/nixos/doc/manual/configuration/x-windows.xml b/nixos/doc/manual/configuration/x-windows.xml index 9206f43ea3926..06dd7c8bfb949 100644 --- a/nixos/doc/manual/configuration/x-windows.xml +++ b/nixos/doc/manual/configuration/x-windows.xml @@ -83,14 +83,16 @@ desktop environment. If you wanted no desktop environment and i3 as your your window manager, you'd define: <programlisting> -<xref linkend="opt-services.xserver.desktopManager.default"/> = "none"; -<xref linkend="opt-services.xserver.windowManager.default"/> = "i3"; +<xref linkend="opt-services.xserver.displayManager.defaultSession"/> = "none+i3"; </programlisting> - And, finally, to enable auto-login for a user <literal>johndoe</literal>: + Every display manager in NixOS supports auto-login, here is an example + using lightdm for a user <literal>alice</literal>: <programlisting> -<xref linkend="opt-services.xserver.displayManager.auto.enable"/> = true; -<xref linkend="opt-services.xserver.displayManager.auto.user"/> = "johndoe"; +<xref linkend="opt-services.xserver.displayManager.lightdm.enable"/> = true; +<xref linkend="opt-services.xserver.displayManager.lightdm.autoLogin.enable"/> = true; +<xref linkend="opt-services.xserver.displayManager.lightdm.autoLogin.user"/> = "alice"; </programlisting> + The options are named identically for all other display managers. </para> </simplesect> <simplesect xml:id="sec-x11-graphics-cards-nvidia"> diff --git a/nixos/doc/manual/configuration/xfce.xml b/nixos/doc/manual/configuration/xfce.xml index 6ac99c6b2bee4..ebf1f493c5ce5 100644 --- a/nixos/doc/manual/configuration/xfce.xml +++ b/nixos/doc/manual/configuration/xfce.xml @@ -7,47 +7,34 @@ <para> To enable the Xfce Desktop Environment, set <programlisting> -<link linkend="opt-services.xserver.desktopManager.default">services.xserver.desktopManager</link> = { - <link linkend="opt-services.xserver.desktopManager.xfce.enable">xfce.enable</link> = true; - <link linkend="opt-services.xserver.desktopManager.default">default</link> = "xfce"; -}; +<xref linkend="opt-services.xserver.desktopManager.xfce.enable" /> = true; +<xref linkend="opt-services.xserver.displayManager.defaultSession" /> = "xfce"; </programlisting> </para> <para> - Optionally, <emphasis>compton</emphasis> can be enabled for nice graphical + Optionally, <emphasis>picom</emphasis> can be enabled for nice graphical effects, some example settings: <programlisting> -<link linkend="opt-services.compton.enable">services.compton</link> = { - <link linkend="opt-services.compton.enable">enable</link> = true; - <link linkend="opt-services.compton.fade">fade</link> = true; - <link linkend="opt-services.compton.inactiveOpacity">inactiveOpacity</link> = "0.9"; - <link linkend="opt-services.compton.shadow">shadow</link> = true; - <link linkend="opt-services.compton.fadeDelta">fadeDelta</link> = 4; +<link linkend="opt-services.picom.enable">services.picom</link> = { + <link linkend="opt-services.picom.enable">enable</link> = true; + <link linkend="opt-services.picom.fade">fade</link> = true; + <link linkend="opt-services.picom.inactiveOpacity">inactiveOpacity</link> = "0.9"; + <link linkend="opt-services.picom.shadow">shadow</link> = true; + <link linkend="opt-services.picom.fadeDelta">fadeDelta</link> = 4; }; </programlisting> </para> <para> Some Xfce programs are not installed automatically. To install them manually (system wide), put them into your - <xref linkend="opt-environment.systemPackages"/>. + <xref linkend="opt-environment.systemPackages"/> from <literal>pkgs.xfce</literal>. </para> - <simplesect xml:id="sec-xfce-thunar-volumes"> - <title>Thunar Volume Support</title> - <para> - To enable <emphasis>Thunar</emphasis> volume support, put -<programlisting> -<xref linkend="opt-services.xserver.desktopManager.xfce.enable"/> = true; -</programlisting> - into your <emphasis>configuration.nix</emphasis>. - </para> - </simplesect> - <simplesect xml:id="sec-xfce-polkit"> - <title>Polkit Authentication Agent</title> + <simplesect xml:id="sec-xfce-thunar-plugins"> + <title>Thunar Plugins</title> <para> - There is no authentication agent automatically installed alongside Xfce. To - allow mounting of local (non-removable) filesystems, you will need to - install one. Installing <emphasis>polkit_gnome</emphasis>, a rebuild, logout - and login did the trick. + If you'd like to add extra plugins to Thunar, add them to + <xref linkend="opt-services.xserver.desktopManager.xfce.thunarPlugins"/>. + You shouldn't just add them to <xref linkend="opt-environment.systemPackages"/>. </para> </simplesect> <simplesect xml:id="sec-xfce-troubleshooting"> diff --git a/nixos/doc/manual/development/option-types.xml b/nixos/doc/manual/development/option-types.xml index 8fcbb627342b2..957349ad1811a 100644 --- a/nixos/doc/manual/development/option-types.xml +++ b/nixos/doc/manual/development/option-types.xml @@ -257,14 +257,68 @@ <listitem> <para> A set of sub options <replaceable>o</replaceable>. - <replaceable>o</replaceable> can be an attribute set or a function - returning an attribute set. Submodules are used in composed types to - create modular options. Submodule are detailed in + <replaceable>o</replaceable> can be an attribute set, a function + returning an attribute set, or a path to a file containing such a value. Submodules are used in + composed types to create modular options. This is equivalent to + <literal>types.submoduleWith { modules = toList o; shorthandOnlyDefinesConfig = true; }</literal>. + Submodules are detailed in <xref linkend='section-option-types-submodule' />. </para> </listitem> </varlistentry> + <varlistentry> + <term> + <varname>types.submoduleWith</varname> { + <replaceable>modules</replaceable>, + <replaceable>specialArgs</replaceable> ? {}, + <replaceable>shorthandOnlyDefinesConfig</replaceable> ? false } + </term> + <listitem> + <para> + Like <varname>types.submodule</varname>, but more flexible and with better defaults. + It has parameters + <itemizedlist> + <listitem><para> + <replaceable>modules</replaceable> + A list of modules to use by default for this submodule type. This gets combined + with all option definitions to build the final list of modules that will be included. + <note><para> + Only options defined with this argument are included in rendered documentation. + </para></note> + </para></listitem> + <listitem><para> + <replaceable>specialArgs</replaceable> + An attribute set of extra arguments to be passed to the module functions. + The option <literal>_module.args</literal> should be used instead + for most arguments since it allows overriding. <replaceable>specialArgs</replaceable> should only be + used for arguments that can't go through the module fixed-point, because of + infinite recursion or other problems. An example is overriding the + <varname>lib</varname> argument, because <varname>lib</varname> itself is used + to define <literal>_module.args</literal>, which makes using + <literal>_module.args</literal> to define it impossible. + </para></listitem> + <listitem><para> + <replaceable>shorthandOnlyDefinesConfig</replaceable> + Whether definitions of this type should default to the <literal>config</literal> + section of a module (see <xref linkend='ex-module-syntax'/>) if it is an attribute + set. Enabling this only has a benefit when the submodule defines an option named + <literal>config</literal> or <literal>options</literal>. In such a case it would + allow the option to be set with <literal>the-submodule.config = "value"</literal> + instead of requiring <literal>the-submodule.config.config = "value"</literal>. + This is because only when modules <emphasis>don't</emphasis> set the + <literal>config</literal> or <literal>options</literal> keys, all keys are interpreted + as option definitions in the <literal>config</literal> section. Enabling this option + implicitly puts all attributes in the <literal>config</literal> section. + </para> + <para> + With this option enabled, defining a non-<literal>config</literal> section requires + using a function: <literal>the-submodule = { ... }: { options = { ... }; }</literal>. + </para></listitem> + </itemizedlist> + </para> + </listitem> + </varlistentry> </variablelist> </section> @@ -298,6 +352,36 @@ An attribute set of where all the values are of <replaceable>t</replaceable> type. Multiple definitions result in the joined attribute set. + <note><para> + This type is <emphasis>strict</emphasis> in its values, which in turn + means attributes cannot depend on other attributes. See <varname> + types.lazyAttrsOf</varname> for a lazy version. + </para></note> + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <varname>types.lazyAttrsOf</varname> <replaceable>t</replaceable> + </term> + <listitem> + <para> + An attribute set of where all the values are of + <replaceable>t</replaceable> type. Multiple definitions result in the + joined attribute set. This is the lazy version of <varname>types.attrsOf + </varname>, allowing attributes to depend on each other. + <warning><para> + This version does not fully support conditional definitions! With an + option <varname>foo</varname> of this type and a definition + <literal>foo.attr = lib.mkIf false 10</literal>, evaluating + <literal>foo ? attr</literal> will return <literal>true</literal> + even though it should be false. Accessing the value will then throw + an error. For types <replaceable>t</replaceable> that have an + <literal>emptyValue</literal> defined, that value will be returned + instead of throwing an error. So if the type of <literal>foo.attr</literal> + was <literal>lazyAttrsOf (nullOr int)</literal>, <literal>null</literal> + would be returned instead for the same <literal>mkIf false</literal> definition. + </para></warning> </para> </listitem> </varlistentry> diff --git a/nixos/doc/manual/development/releases.xml b/nixos/doc/manual/development/releases.xml index 9371af9984d1d..cc0ec78cc74e2 100755 --- a/nixos/doc/manual/development/releases.xml +++ b/nixos/doc/manual/development/releases.xml @@ -71,8 +71,9 @@ <para> <link xlink:href="https://github.com/NixOS/nixpkgs/commit/d6b08acd1ccac0d9d502c4b635e00b04d3387f06"> Update <literal>versionSuffix</literal> in - <literal>nixos/release.nix</literal></link>, use <literal>git log - --format=%an|wc -l</literal> to get the commit count + <literal>nixos/release.nix</literal></link>, use + <literal>git rev-list --count 17.09-beta</literal> + to get the commit count. </para> </listitem> <listitem> @@ -187,7 +188,7 @@ </listitem> <listitem> <para> - Update "Chapter 4. Upgrading NixOS" section of the manual to match + Update "Chapter 4. Upgrading NixOS" section of the manual to match new stable release version. </para> </listitem> @@ -237,6 +238,10 @@ experience. </para> <para> + Release managers for the current NixOS release are tracked by GitHub team + <link xlink:href="https://github.com/orgs/NixOS/teams/nixos-release-managers/members"><literal>@NixOS/nixos-release-managers</literal></link>. + </para> + <para> A release manager's role and responsibilities are: </para> <itemizedlist> diff --git a/nixos/doc/manual/development/replace-modules.xml b/nixos/doc/manual/development/replace-modules.xml index 7b103c36d907d..b4a466e229422 100644 --- a/nixos/doc/manual/development/replace-modules.xml +++ b/nixos/doc/manual/development/replace-modules.xml @@ -6,8 +6,8 @@ <title>Replace Modules</title> <para> - Modules that are imported can also be disabled. The option declarations and - config implementation of a disabled module will be ignored, allowing another + Modules that are imported can also be disabled. The option declarations, + config implementation and the imports of a disabled module will be ignored, allowing another to take it's place. This can be used to import a set of modules from another channel while keeping the rest of the system on a stable release. </para> diff --git a/nixos/doc/manual/development/writing-nixos-tests.xml b/nixos/doc/manual/development/writing-nixos-tests.xml index 24efd2e3273ab..e5a887c18c770 100644 --- a/nixos/doc/manual/development/writing-nixos-tests.xml +++ b/nixos/doc/manual/development/writing-nixos-tests.xml @@ -419,4 +419,23 @@ machine.wait_for_unit("xautolock.service", "x-session-user") <literal>wait_for_unit</literal>, <literal>start_job</literal> and <literal>stop_job</literal>. </para> + + <para> + For faster dev cycles it's also possible to disable the code-linters (this shouldn't + be commited though): +<programlisting> +import ./make-test-python.nix { + skipLint = true; + machine = + { config, pkgs, ... }: + { <replaceable>configuration…</replaceable> + }; + + testScript = + '' + <replaceable>Python code…</replaceable> + ''; +} +</programlisting> + </para> </section> diff --git a/nixos/doc/manual/installation/installing-behind-a-proxy.xml b/nixos/doc/manual/installation/installing-behind-a-proxy.xml index 8f9baff44b51e..c1ef638e876ea 100644 --- a/nixos/doc/manual/installation/installing-behind-a-proxy.xml +++ b/nixos/doc/manual/installation/installing-behind-a-proxy.xml @@ -40,7 +40,7 @@ networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain"; <note> <para> If you are switching networks with different proxy configurations, use the - <literal>nesting.clone</literal> option in + <literal>specialisation</literal> option in <literal>configuration.nix</literal> to switch proxies at runtime. Refer to <xref linkend="ch-options" /> for more information. </para> diff --git a/nixos/doc/manual/installation/installing.xml b/nixos/doc/manual/installation/installing.xml index 7991c43e01fe9..0dbfb39c32b22 100644 --- a/nixos/doc/manual/installation/installing.xml +++ b/nixos/doc/manual/installation/installing.xml @@ -24,8 +24,7 @@ </para> <para> - The NixOS manual is available on virtual console 8 (press Alt+F8 to access) - or by running <command>nixos-help</command>. + The NixOS manual is available by running <command>nixos-help</command>. </para> <para> @@ -380,7 +379,10 @@ </para> <para> If you need to configure networking for your machine the configuration - options are described in <xref linkend="sec-networking"/>. + options are described in <xref linkend="sec-networking"/>. In particular, + while wifi is supported on the installation image, it is not enabled by + default in the configuration generated by + <command>nixos-generate-config</command>. </para> <para> Another critical option is <option>fileSystems</option>, specifying the @@ -475,7 +477,7 @@ Retype new UNIX password: ***</screen> shows what packages are available, and <screen> <prompt>$ </prompt>nix-env -f '<nixpkgs>' -iA w3m</screen> - install the <literal>w3m</literal> browser. + installs the <literal>w3m</literal> browser. </para> </listitem> </orderedlist> diff --git a/nixos/doc/manual/installation/upgrading.xml b/nixos/doc/manual/installation/upgrading.xml index 8d3f35b7c26f4..e5e02aa075264 100644 --- a/nixos/doc/manual/installation/upgrading.xml +++ b/nixos/doc/manual/installation/upgrading.xml @@ -14,7 +14,7 @@ <para> <emphasis>Stable channels</emphasis>, such as <literal - xlink:href="https://nixos.org/channels/nixos-19.09">nixos-19.09</literal>. + xlink:href="https://nixos.org/channels/nixos-20.03">nixos-20.03</literal>. These only get conservative bug fixes and package upgrades. For instance, a channel update may cause the Linux kernel on your system to be upgraded from 4.19.34 to 4.19.38 (a minor bug fix), but not from @@ -38,7 +38,7 @@ <para> <emphasis>Small channels</emphasis>, such as <literal - xlink:href="https://nixos.org/channels/nixos-19.09-small">nixos-19.09-small</literal> + xlink:href="https://nixos.org/channels/nixos-20.03-small">nixos-20.03-small</literal> or <literal xlink:href="https://nixos.org/channels/nixos-unstable-small">nixos-unstable-small</literal>. @@ -63,8 +63,8 @@ <para> When you first install NixOS, you’re automatically subscribed to the NixOS channel that corresponds to your installation source. For instance, if you - installed from a 19.09 ISO, you will be subscribed to the - <literal>nixos-19.09</literal> channel. To see which NixOS channel you’re + installed from a 20.03 ISO, you will be subscribed to the + <literal>nixos-20.03</literal> channel. To see which NixOS channel you’re subscribed to, run the following as root: <screen> # nix-channel --list | grep nixos @@ -75,13 +75,13 @@ nixos https://nixos.org/channels/nixos-unstable # nix-channel --add https://nixos.org/channels/<replaceable>channel-name</replaceable> nixos </screen> (Be sure to include the <literal>nixos</literal> parameter at the end.) For - instance, to use the NixOS 19.09 stable channel: + instance, to use the NixOS 20.03 stable channel: <screen> -# nix-channel --add https://nixos.org/channels/nixos-19.09 nixos +# nix-channel --add https://nixos.org/channels/nixos-20.03 nixos </screen> If you have a server, you may want to use the “small” channel instead: <screen> -# nix-channel --add https://nixos.org/channels/nixos-19.09-small nixos +# nix-channel --add https://nixos.org/channels/nixos-20.03-small nixos </screen> And if you want to live on the bleeding edge: <screen> @@ -120,14 +120,19 @@ nixos https://nixos.org/channels/nixos-unstable to <filename>configuration.nix</filename>: <programlisting> <xref linkend="opt-system.autoUpgrade.enable"/> = true; +<xref linkend="opt-system.autoUpgrade.allowReboot"/> = true; </programlisting> This enables a periodically executed systemd service named - <literal>nixos-upgrade.service</literal>. It runs <command>nixos-rebuild - switch --upgrade</command> to upgrade NixOS to the latest version in the - current channel. (To see when the service runs, see <command>systemctl - list-timers</command>.) You can also specify a channel explicitly, e.g. + <literal>nixos-upgrade.service</literal>. If the <literal>allowReboot</literal> + option is <literal>false</literal>, it runs <command>nixos-rebuild switch + --upgrade</command> to upgrade NixOS to the latest version in the current + channel. (To see when the service runs, see <command>systemctl list-timers</command>.) + If <literal>allowReboot</literal> is <literal>true</literal>, then the + system will automatically reboot if the new generation contains a different + kernel, initrd or kernel modules. + You can also specify a channel explicitly, e.g. <programlisting> -<xref linkend="opt-system.autoUpgrade.channel"/> = https://nixos.org/channels/nixos-19.09; +<xref linkend="opt-system.autoUpgrade.channel"/> = https://nixos.org/channels/nixos-20.03; </programlisting> </para> </section> diff --git a/nixos/doc/manual/man-nixos-install.xml b/nixos/doc/manual/man-nixos-install.xml index 45bbd5d81ff02..9255ce763efee 100644 --- a/nixos/doc/manual/man-nixos-install.xml +++ b/nixos/doc/manual/man-nixos-install.xml @@ -15,6 +15,26 @@ <cmdsynopsis> <command>nixos-install</command> <arg> + <group choice='req'> + <arg choice='plain'> + <option>--verbose</option> + </arg> + <arg choice='plain'> + <option>-v</option> + </arg> + </group> + </arg> + <arg> + <group choice='req'> + <arg choice='plain'> + <option>--print-build-logs</option> + </arg> + <arg choice='plain'> + <option>-L</option> + </arg> + </group> + </arg> + <arg> <arg choice='plain'> <option>-I</option> </arg> @@ -36,6 +56,13 @@ </arg> <arg> + <arg choice='plain'> + <option>--channel</option> + </arg> + <replaceable>channel</replaceable> + </arg> + + <arg> <arg choice='plain'> <option>--no-channel-copy</option> </arg> @@ -107,6 +134,12 @@ </para> </listitem> <listitem> + <para> + It installs the current channel <quote>nixos</quote> in the target channel + profile (unless <option>--no-channel-copy</option> is specified). + </para> + </listitem> + <listitem> <para> It installs the GRUB boot loader on the device specified in the option <option>boot.loader.grub.device</option> (unless @@ -135,6 +168,23 @@ </para> <variablelist> <varlistentry> + <term><option>--verbose</option> / <option>-v</option></term> + <listitem> + <para>Increases the level of verbosity of diagnostic messages + printed on standard error. For each Nix operation, the information + printed on standard output is well-defined; any diagnostic + information is printed on standard error, never on standard + output.</para> + <para>Please note that this option may be specified repeatedly.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><option>--print-build-logs</option> / <option>-L</option></term> + <listitem> + <para>Print the full build logs of <command>nix build</command> to stderr.</para> + </listitem> + </varlistentry> + <varlistentry> <term> <option>--root</option> </term> @@ -160,12 +210,24 @@ The closure must be an appropriately configured NixOS system, with boot loader and partition configuration that fits the target host. Such a closure is typically obtained with a command such as <command>nix-build - -I nixos-config=./configuration.nix '<nixos>' -A system + -I nixos-config=./configuration.nix '<nixpkgs/nixos>' -A system --no-out-link</command> </para> </listitem> </varlistentry> <varlistentry> + <term> + <option>--channel</option> + </term> + <listitem> + <para> + If this option is provided, do not copy the current + <quote>nixos</quote> channel to the target host. Instead, use the + specified derivation. + </para> + </listitem> + </varlistentry> + <varlistentry> <term> <option>-I</option> </term> diff --git a/nixos/doc/manual/man-nixos-option.xml b/nixos/doc/manual/man-nixos-option.xml index beabf020c92ae..b921386d0df01 100644 --- a/nixos/doc/manual/man-nixos-option.xml +++ b/nixos/doc/manual/man-nixos-option.xml @@ -14,12 +14,16 @@ <refsynopsisdiv> <cmdsynopsis> <command>nixos-option</command> + <arg> - <option>-I</option> <replaceable>path</replaceable> + <group choice='req'> + <arg choice='plain'><option>-r</option></arg> + <arg choice='plain'><option>--recursive</option></arg> + </group> </arg> <arg> - <option>--all</option> + <option>-I</option> <replaceable>path</replaceable> </arg> <arg> @@ -46,23 +50,22 @@ </para> <variablelist> <varlistentry> - <term> - <option>-I</option> <replaceable>path</replaceable> - </term> + <term><option>-r</option></term> + <term><option>--recursive</option></term> <listitem> <para> - This option is passed to the underlying - <command>nix-instantiate</command> invocation. + Print all the values at or below the specified path recursively. </para> </listitem> </varlistentry> <varlistentry> <term> - <option>--all</option> + <option>-I</option> <replaceable>path</replaceable> </term> <listitem> <para> - Print the values of all options. + This option is passed to the underlying + <command>nix-instantiate</command> invocation. </para> </listitem> </varlistentry> @@ -119,4 +122,13 @@ Defined by: bug, please report to Nicolas Pierron. </para> </refsection> + <refsection> + <title>See also</title> + <para> + <citerefentry> + <refentrytitle>configuration.nix</refentrytitle> + <manvolnum>5</manvolnum> + </citerefentry> + </para> + </refsection> </refentry> diff --git a/nixos/doc/manual/man-nixos-rebuild.xml b/nixos/doc/manual/man-nixos-rebuild.xml index 495dbc8859b1d..f4f663b84f056 100644 --- a/nixos/doc/manual/man-nixos-rebuild.xml +++ b/nixos/doc/manual/man-nixos-rebuild.xml @@ -77,7 +77,14 @@ <option>--builders</option> <replaceable>builder-spec</replaceable> </arg> + <sbr/> + + <arg> + <option>--flake</option> <replaceable>flake-uri</replaceable> + </arg> + <sbr /> + <arg> <group choice='req'> <arg choice='plain'> @@ -129,14 +136,17 @@ <title>Description</title> <para> - This command updates the system so that it corresponds to the configuration - specified in <filename>/etc/nixos/configuration.nix</filename>. Thus, every - time you modify <filename>/etc/nixos/configuration.nix</filename> or any - NixOS module, you must run <command>nixos-rebuild</command> to make the - changes take effect. It builds the new system in - <filename>/nix/store</filename>, runs its activation script, and stop and - (re)starts any system services if needed. Please note that user services need - to be started manually as they aren't detected by the activation script at the moment. + This command updates the system so that it corresponds to the + configuration specified in + <filename>/etc/nixos/configuration.nix</filename> or + <filename>/etc/nixos/flake.nix</filename>. Thus, every time you + modify the configuration or any other NixOS module, you must run + <command>nixos-rebuild</command> to make the changes take + effect. It builds the new system in + <filename>/nix/store</filename>, runs its activation script, and + stop and (re)starts any system services if needed. Please note that + user services need to be started manually as they aren't detected + by the activation script at the moment. </para> <para> @@ -508,6 +518,24 @@ </para> </listitem> </varlistentry> + + <varlistentry> + <term> + <option>--flake</option> <replaceable>flake-uri</replaceable>[<replaceable>name</replaceable>] + </term> + <listitem> + <para> + Build the NixOS system from the specified flake. It defaults to + the directory containing the target of the symlink + <filename>/etc/nixos/flake.nix</filename>, if it exists. The + flake must contain an output named + <literal>nixosConfigurations.<replaceable>name</replaceable></literal>. If + <replaceable>name</replaceable> is omitted, it default to the + current host name. + </para> + </listitem> + </varlistentry> + </variablelist> <para> @@ -556,6 +584,21 @@ <varlistentry> <term> + <filename>/etc/nixos/flake.nix</filename> + </term> + <listitem> + <para> + If this file exists, then <command>nixos-rebuild</command> will + use it as if the <option>--flake</option> option was given. This + file may be a symlink to a <filename>flake.nix</filename> in an + actual flake; thus <filename>/etc/nixos</filename> need not be a + flake. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> <filename>/run/current-system</filename> </term> <listitem> diff --git a/nixos/doc/manual/man-nixos-version.xml b/nixos/doc/manual/man-nixos-version.xml index e9ad8bddcace6..aada08c5b4a97 100644 --- a/nixos/doc/manual/man-nixos-version.xml +++ b/nixos/doc/manual/man-nixos-version.xml @@ -12,16 +12,22 @@ </refnamediv> <refsynopsisdiv> <cmdsynopsis> - <command>nixos-version</command> + <command>nixos-version</command> <arg> <option>--hash</option> </arg> - + <arg> <option>--revision</option> </arg> + + <arg> + <option>--json</option> + </arg> + </cmdsynopsis> </refsynopsisdiv> + <refsection> <title>Description</title> <para> @@ -84,12 +90,16 @@ </variablelist> </para> </refsection> + <refsection> <title>Options</title> + <para> This command accepts the following options: </para> + <variablelist> + <varlistentry> <term> <option>--hash</option> @@ -107,6 +117,21 @@ </para> </listitem> </varlistentry> + + <varlistentry> + <term> + <option>--json</option> + </term> + <listitem> + <para> + Print a JSON representation of the versions of NixOS and the + top-level configuration flake. + </para> + </listitem> + </varlistentry> + </variablelist> + </refsection> + </refentry> diff --git a/nixos/doc/manual/man-pages.xml b/nixos/doc/manual/man-pages.xml index f5a1dd2d69f49..49acfe7330b6d 100644 --- a/nixos/doc/manual/man-pages.xml +++ b/nixos/doc/manual/man-pages.xml @@ -6,7 +6,7 @@ <author><personname><firstname>Eelco</firstname><surname>Dolstra</surname></personname> <contrib>Author</contrib> </author> - <copyright><year>2007-2019</year><holder>Eelco Dolstra</holder> + <copyright><year>2007-2020</year><holder>Eelco Dolstra</holder> </copyright> </info> <xi:include href="man-configuration.xml" /> diff --git a/nixos/doc/manual/release-notes/release-notes.xml b/nixos/doc/manual/release-notes/release-notes.xml index 444862c5739bb..e2913b8a5353d 100644 --- a/nixos/doc/manual/release-notes/release-notes.xml +++ b/nixos/doc/manual/release-notes/release-notes.xml @@ -8,6 +8,7 @@ This section lists the release notes for each stable version of NixOS and current unstable revision. </para> + <xi:include href="rl-2009.xml" /> <xi:include href="rl-2003.xml" /> <xi:include href="rl-1909.xml" /> <xi:include href="rl-1903.xml" /> diff --git a/nixos/doc/manual/release-notes/rl-2003.xml b/nixos/doc/manual/release-notes/rl-2003.xml index 6f88a2f571e2f..393a9286ca4f5 100644 --- a/nixos/doc/manual/release-notes/rl-2003.xml +++ b/nixos/doc/manual/release-notes/rl-2003.xml @@ -3,7 +3,7 @@ xmlns:xi="http://www.w3.org/2001/XInclude" version="5.0" xml:id="sec-release-20.03"> - <title>Release 20.03 (“Markhor”, 2020.03/??)</title> + <title>Release 20.03 (“Markhor”, 2020.04/20)</title> <section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" @@ -24,6 +24,26 @@ </para> </listitem> <listitem> + <para>Core version changes:</para> + <para>gcc: 8.3.0 -> 9.2.0</para> + <para>glibc: 2.27 -> 2.30</para> + <para>linux: 4.19 -> 5.4</para> + <para>mesa: 19.1.5 -> 19.3.3</para> + <para>openssl: 1.0.2u -> 1.1.1d</para> + </listitem> + <listitem> + <para>Desktop version changes:</para> + <para>plasma5: 5.16.5 -> 5.17.5</para> + <para>kdeApplications: 19.08.2 -> 19.12.3</para> + <para>gnome3: 3.32 -> 3.34</para> + <para>pantheon: 5.0 -> 5.1.3</para> + </listitem> + <listitem> + <para> + Linux kernel is updated to branch 5.4 by default (from 4.19). + </para> + </listitem> + <listitem> <para> Postgresql for NixOS service now defaults to v11. </para> @@ -37,6 +57,24 @@ </para> </listitem> <listitem> + <para> + GNOME 3 has been upgraded to 3.34. Please take a look at their + <link xlink:href="https://help.gnome.org/misc/release-notes/3.34">Release Notes</link> + for details. + </para> + </listitem> + <listitem> + <para> + If you enable the Pantheon Desktop Manager via + <xref linkend="opt-services.xserver.desktopManager.pantheon.enable" />, we now default to also use + <link xlink:href="https://blog.elementary.io/say-hello-to-the-new-greeter/"> + Pantheon's newly designed greeter + </link>. + Contrary to NixOS's usual update policy, Pantheon will receive updates during the cycle of + NixOS 20.03 when backwards compatible. + </para> + </listitem> + <listitem> <para> By default zfs pools will now be trimmed on a weekly basis. Trimming is only done on supported devices (i.e. NVME or SSDs) @@ -52,7 +90,38 @@ <listitem> <para> <command>nixos-option</command> has been rewritten in C++, speeding it up, improving correctness, - and adding a <option>--all</option> option which prints all options and their values. + and adding a <option>-r</option> option which prints all options and their values recursively. + </para> + </listitem> + <listitem> + <para> + <option>services.xserver.desktopManager.default</option> and <option>services.xserver.windowManager.default</option> options were replaced by a single <xref linkend="opt-services.xserver.displayManager.defaultSession"/> option to improve support for upstream session files. If you used something like: +<programlisting> +services.xserver.desktopManager.default = "xfce"; +services.xserver.windowManager.default = "icewm"; +</programlisting> + you should change it to: +<programlisting> +services.xserver.displayManager.defaultSession = "xfce+icewm"; +</programlisting> + </para> + </listitem> + <listitem> + <para> + The testing driver implementation in NixOS is now in Python <filename>make-test-python.nix</filename>. + This was done by Jacek Galowicz (<link xlink:href="https://github.com/tfc">@tfc</link>), and with the + collaboration of Julian Stecklina (<link xlink:href="https://github.com/blitz">@blitz</link>) and + Jana Traue (<link xlink:href="https://github.com/jtraue">@jtraue</link>). All documentation has been updated to use this + testing driver, and a vast majority of the 286 tests in NixOS were ported to python driver. In 20.09 the Perl driver implementation, + <filename>make-test.nix</filename>, is slated for removal. This should give users of the NixOS integration framework + a transitory period to rewrite their tests to use the Python implementation. Users of the Perl driver will see + this warning everytime they use it: +<screen> +<prompt>$ </prompt>warning: Perl VM tests are deprecated and will be removed for 20.09. +Please update your tests to use the python test driver. +See https://github.com/NixOS/nixpkgs/pull/71684 for details. +</screen> + API compatibility is planned to be kept for at least the next release with the perl driver. </para> </listitem> </itemizedlist> @@ -77,6 +146,254 @@ be set if the hostname of the node should be non default. </para> </listitem> + <listitem> + <para> + UPower's configuration is now managed by NixOS and can be customized + via <option>services.upower</option>. + </para> + </listitem> + <listitem> + <para> + To use Geary you should enable <xref linkend="opt-programs.geary.enable"/> instead of + just adding it to <xref linkend="opt-environment.systemPackages"/>. + It was created so Geary could function properly outside of GNOME. + </para> + </listitem> + <listitem> + <para> + <filename>./config/console.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./hardware/brillo.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./hardware/tuxedo-keyboard.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./programs/bandwhich.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./programs/bash-my-aws.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./programs/liboping.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./programs/traceroute.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/backup/sanoid.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/backup/syncoid.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/backup/zfs-replication.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/continuous-integration/buildkite-agents.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/databases/victoriametrics.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/desktops/gnome3/gnome-initial-setup.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/desktops/neard.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/games/openarena.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/hardware/fancontrol.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/mail/sympa.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/misc/freeswitch.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/misc/mame.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/monitoring/do-agent.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/monitoring/prometheus/xmpp-alerts.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/network-filesystems/orangefs/server.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/network-filesystems/orangefs/client.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/networking/3proxy.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/networking/corerad.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/networking/go-shadowsocks2.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/networking/ntp/openntpd.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/networking/shorewall.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/networking/shorewall6.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/networking/spacecookie.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/networking/trickster.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/networking/v2ray.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/networking/xandikos.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/networking/yggdrasil.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/web-apps/dokuwiki.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/web-apps/gotify-server.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/web-apps/grocy.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/web-apps/ihatemoney</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/web-apps/moinmoin.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/web-apps/trac.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/web-apps/trilium.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/web-apps/shiori.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/web-servers/ttyd.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/x11/picom.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/x11/hardware/digimend.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./services/x11/imwheel.nix</filename> + </para> + </listitem> + <listitem> + <para> + <filename>./virtualisation/cri-o.nix</filename> + </para> + </listitem> </itemizedlist> </section> @@ -96,6 +413,17 @@ <itemizedlist> <listitem> <para> + The <package>dhcpcd</package> package <link xlink:href="https://roy.marples.name/archives/dhcpcd-discuss/0002621.html"> + does not request IPv4 addresses for tap and bridge interfaces anymore by default</link>. + In order to still get an address on a bridge interface, one has to disable + <literal>networking.useDHCP</literal> and explicitly enable + <literal>networking.interfaces.<name>.useDHCP</literal> on + every interface, that should get an address via DHCP. This way, dhcpcd + is configured in an explicit way about which interface to run on. + </para> + </listitem> + <listitem> + <para> GnuPG is now built without support for a graphical passphrase entry by default. Please enable the <literal>gpg-agent</literal> user service via the NixOS option <literal>programs.gnupg.agent.enable</literal>. @@ -107,7 +435,7 @@ <listitem> <para> The <literal>dynamicHosts</literal> option has been removed from the - <link linkend="opt-networking.networkmanager.enable">networkd</link> + <link linkend="opt-networking.networkmanager.enable">NetworkManager</link> module. Allowing (multiple) regular users to override host entries affecting the whole system opens up a huge attack vector. There seem to be very rare cases where this might be useful. @@ -121,18 +449,18 @@ </listitem> <listitem> <para> - The <literal>99-main.network</literal> file was removed. Maching all - network interfaces caused many breakages, see - <link xlink:href="https://github.com/NixOS/nixpkgs/pull/18962">#18962</link> - and <link xlink:href="https://github.com/NixOS/nixpkgs/pull/71106">#71106</link>. + The <literal>99-main.network</literal> file was removed. Matching all + network interfaces caused many breakages, see + <link xlink:href="https://github.com/NixOS/nixpkgs/pull/18962">#18962</link> + and <link xlink:href="https://github.com/NixOS/nixpkgs/pull/71106">#71106</link>. </para> <para> - We already don't support the global <link linkend="opt-networking.useDHCP">networking.useDHCP</link>, - <link linkend="opt-networking.defaultGateway">networking.defaultGateway</link> and - <link linkend="opt-networking.defaultGateway6">networking.defaultGateway6</link> options - if <link linkend="opt-networking.useNetworkd">networking.useNetworkd</link> is enabled, - but direct users to configure the per-device - <link linkend="opt-networking.interfaces">networking.interfaces.<name>.…</link> options. + We already don't support the global <link linkend="opt-networking.useDHCP">networking.useDHCP</link>, + <link linkend="opt-networking.defaultGateway">networking.defaultGateway</link> and + <link linkend="opt-networking.defaultGateway6">networking.defaultGateway6</link> options + if <link linkend="opt-networking.useNetworkd">networking.useNetworkd</link> is enabled, + but direct users to configure the per-device + <link linkend="opt-networking.interfaces">networking.interfaces.<name>.…</link> options. </para> </listitem> <listitem> @@ -151,16 +479,22 @@ </listitem> <listitem> <para> + The Way Cooler wayland compositor has been removed, as the project has been officially canceled. + There are no more <literal>way-cooler</literal> attribute and <literal>programs.way-cooler</literal> options. + </para> + </listitem> + <listitem> + <para> The BEAM package set has been deleted. You will only find there the different interpreters. You should now use the different build tools coming with the languages with sandbox mode disabled. </para> </listitem> <listitem> <para> - There is now only one Xfce package-set and module. This means attributes, <literal>xfce4-14</literal> - <literal>xfce4-12</literal>, and <literal>xfceUnstable</literal> all now point to the latest Xfce 4.14 - packages. And in future NixOS releases will be the latest released version of Xfce available at the - time during the releases development (if viable). + There is now only one Xfce package-set and module. This means that attributes <literal>xfce4-14</literal> + and <literal>xfceUnstable</literal> all now point to the latest Xfce 4.14 + packages. And in the future NixOS releases will be the latest released version of Xfce available at the + time of the release's development (if viable). </para> </listitem> <listitem> @@ -193,6 +527,565 @@ The aforementioned option was added this release. </para> </listitem> + <listitem> + <para> + The <literal>buildRustCrate</literal> infrastructure now produces <literal>lib</literal> outputs in addition to the <literal>out</literal> output. + This has led to drastically reduced closure sizes for some rust crates since development dependencies are now in the <literal>lib</literal> output. + </para> + </listitem> + <listitem> + <para> + Pango was upgraded to 1.44, which no longer uses freetype for font loading. This means that type1 + and bitmap fonts are no longer supported in applications relying on Pango for font rendering + (notably, GTK application). See <link xlink:href="https://gitlab.gnome.org/GNOME/pango/issues/386"> + upstream issue</link> for more information. + </para> + </listitem> + <listitem> + <para> + The <literal>roundcube</literal> module has been hardened. + <itemizedlist> + <listitem> + <para> + The password of the database is not written world readable in the store any more. If <literal>database.host</literal> is set to <literal>localhost</literal>, then a unix user of the same name as the database will be created and PostreSQL peer authentication will be used, removing the need for a password. Otherwise, a password is still needed and can be provided with the new option <literal>database.passwordFile</literal>, which should be set to the path of a file containing the password and readable by the user <literal>nginx</literal> only. The <literal>database.password</literal> option is insecure and deprecated. Usage of this option will print a warning. + </para> + </listitem> + <listitem> + <para> + A random <literal>des_key</literal> is set by default in the configuration of roundcube, instead of using the hardcoded and insecure default. To ensure a clean migration, all users will be logged out when you upgrade to this release. + </para> + </listitem> + </itemizedlist> + </para> + </listitem> + <listitem> + <para> + The packages <literal>openobex</literal> and <literal>obexftp</literal> + are no longer installed when enabling Bluetooth via + <option>hardware.bluetooth.enable</option>. + </para> + </listitem> + <listitem> + <para> + The <literal>dump1090</literal> derivation has been changed to use FlightAware's dump1090 + as its upstream. However, this version does not have an internal webserver anymore. The + assets in the <literal>share/dump1090</literal> directory of the derivation can be used + in conjunction with an external webserver to replace this functionality. + </para> + </listitem> + <listitem> + <para> + The fourStore and fourStoreEndpoint modules have been removed. + </para> + </listitem> + <listitem> + <para> + Polkit no longer has the user of uid 0 (root) as an admin identity. + We now follow the upstream default of only having every member of the wheel + group admin privileged. Before it was root and members of wheel. + The positive outcome of this is pkexec GUI popups or terminal prompts + will no longer require the user to choose between two essentially equivalent + choices (whether to perform the action as themselves with wheel permissions, or as the root user). + </para> + </listitem> + <listitem> + <para> + NixOS containers no longer build NixOS manual by default. This saves evaluation time, + especially if there are many declarative containers defined. Note that this is already done + when <literal><nixos/modules/profiles/minimal.nix></literal> module is included + in container config. + </para> + </listitem> + <listitem> + <para> + The <literal>kresd</literal> services deprecates the <literal>interfaces</literal> option + in favor of the <literal>listenPlain</literal> option which requires full + <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.socket.html#ListenStream=">systemd.socket compatible</link> + declaration which always include a port. + </para> + </listitem> + <listitem> + <para> + Virtual console options have been reorganized and can be found under + a single top-level attribute: <literal>console</literal>. + The full set of changes is as follows: + </para> + <itemizedlist> + <listitem> + <para> + <literal>i18n.consoleFont</literal> renamed to + <link linkend="opt-console.font">console.font</link> + </para> + </listitem> + <listitem> + <para> + <literal>i18n.consoleKeyMap</literal> renamed to + <link linkend="opt-console.keyMap">console.keyMap</link> + </para> + </listitem> + <listitem> + <para> + <literal>i18n.consoleColors</literal> renamed to + <link linkend="opt-console.colors">console.colors</link> + </para> + </listitem> + <listitem> + <para> + <literal>i18n.consolePackages</literal> renamed to + <link linkend="opt-console.packages">console.packages</link> + </para> + </listitem> + <listitem> + <para> + <literal>i18n.consoleUseXkbConfig</literal> renamed to + <link linkend="opt-console.useXkbConfig">console.useXkbConfig</link> + </para> + </listitem> + <listitem> + <para> + <literal>boot.earlyVconsoleSetup</literal> renamed to + <link linkend="opt-console.earlySetup">console.earlySetup</link> + </para> + </listitem> + <listitem> + <para> + <literal>boot.extraTTYs</literal> renamed to + <link linkend="opt-console.extraTTYs">console.extraTTYs</link> + </para> + </listitem> + </itemizedlist> + </listitem> + <listitem> + <para> + The <link linkend="opt-services.awstats.enable">awstats</link> module has been rewritten + to serve stats via static html pages, updated on a timer, over <link linkend="opt-services.nginx.virtualHosts">nginx</link>, + instead of dynamic cgi pages over <link linkend="opt-services.httpd.enable">apache</link>. + </para> + <para> + Minor changes will be required to migrate existing configurations. Details of the + required changes can seen by looking through the <link linkend="opt-services.awstats.enable">awstats</link> + module. + </para> + </listitem> + <listitem> + <para> + The httpd module no longer provides options to support serving web content without defining a virtual host. As a + result of this the <link linkend="opt-services.httpd.logPerVirtualHost">services.httpd.logPerVirtualHost</link> + option now defaults to <literal>true</literal> instead of <literal>false</literal>. Please update your + configuration to make use of <link linkend="opt-services.httpd.virtualHosts">services.httpd.virtualHosts</link>. + </para> + <para> + The <link linkend="opt-services.httpd.virtualHosts">services.httpd.virtualHosts.<name></link> + option has changed type from a list of submodules to an attribute set of submodules, better matching + <link linkend="opt-services.nginx.virtualHosts">services.nginx.virtualHosts.<name></link>. + </para> + <para> + This change comes with the addition of the following options which mimic the functionality of their <literal>nginx</literal> counterparts: + <link linkend="opt-services.httpd.virtualHosts">services.httpd.virtualHosts.<name>.addSSL</link>, + <link linkend="opt-services.httpd.virtualHosts">services.httpd.virtualHosts.<name>.forceSSL</link>, + <link linkend="opt-services.httpd.virtualHosts">services.httpd.virtualHosts.<name>.onlySSL</link>, + <link linkend="opt-services.httpd.virtualHosts">services.httpd.virtualHosts.<name>.enableACME</link>, + <link linkend="opt-services.httpd.virtualHosts">services.httpd.virtualHosts.<name>.acmeRoot</link>, and + <link linkend="opt-services.httpd.virtualHosts">services.httpd.virtualHosts.<name>.useACMEHost</link>. + </para> + </listitem> + <listitem> + <para> + For NixOS configuration options, the <literal>loaOf</literal> type has + been deprecated and will be removed in a future release. In nixpkgs, + options of this type will be changed to <literal>attrsOf</literal> + instead. If you were using one of these in your configuration, you will + see a warning suggesting what changes will be required. + </para> + <para> + For example, <link linkend="opt-users.users">users.users</link> is a + <literal>loaOf</literal> option that is commonly used as follows: + <programlisting> +users.users = + [ { name = "me"; + description = "My personal user."; + isNormalUser = true; + } + ]; + </programlisting> + This should be rewritten by removing the list and using the + value of <literal>name</literal> as the name of the attribute set: + <programlisting> +users.users.me = + { description = "My personal user."; + isNormalUser = true; + }; + </programlisting> + </para> + <para> + For more information on this change have look at these links: + <link xlink:href="https://github.com/NixOS/nixpkgs/issues/1800">issue #1800</link>, + <link xlink:href="https://github.com/NixOS/nixpkgs/pull/63103">PR #63103</link>. + </para> + </listitem> + <listitem> + <para> + For NixOS modules, the types <literal>types.submodule</literal> and <literal>types.submoduleWith</literal> now support + paths as allowed values, similar to how <literal>imports</literal> supports paths. + Because of this, if you have a module that defines an option of type + <literal>either (submodule ...) path</literal>, it will break since a path + is now treated as the first type instead of the second. To fix this, change + the type to <literal>either path (submodule ...)</literal>. + </para> + </listitem> + <listitem> + <para> + The <link linkend="opt-services.buildkite-agents">Buildkite + Agent</link> module and corresponding packages have been updated to + 3.x, and to support multiple instances of the agent running at the + same time. This means you will have to rename + <literal>services.buildkite-agent</literal> to + <literal>services.buildkite-agents.<name></literal>. Furthermore, + the following options have been changed: + </para> + <itemizedlist> + <listitem> + <para> + <literal>services.buildkite-agent.meta-data</literal> has been renamed to + <link linkend="opt-services.buildkite-agents">services.buildkite-agents.<name>.tags</link>, + to match upstreams naming for 3.x. + Its type has also changed - it now accepts an attrset of strings. + </para> + </listitem> + <listitem> + <para> + The<literal>services.buildkite-agent.openssh.publicKeyPath</literal> option + has been removed, as it's not necessary to deploy public keys to clone private + repositories. + </para> + </listitem> + <listitem> + <para> + <literal>services.buildkite-agent.openssh.privateKeyPath</literal> + has been renamed to + <link linkend="opt-services.buildkite-agents">buildkite-agents.<name>.privateSshKeyPath</link>, + as the whole <literal>openssh</literal> now only contained that single option. + </para> + </listitem> + <listitem> + <para> + <link linkend="opt-services.buildkite-agents">services.buildkite-agents.<name>.shell</link> + has been introduced, allowing to specify a custom shell to be used. + </para> + </listitem> + </itemizedlist> + </listitem> + <listitem> + <para> + The <literal>citrix_workspace_19_3_0</literal> package has been removed as + it will be EOLed within the lifespan of 20.03. For further information, + please refer to the <link xlink:href="https://www.citrix.com/de-de/support/product-lifecycle/milestones/receiver.html">support and maintenance information</link> from upstream. + </para> + </listitem> + <listitem> + <para> + The <literal>gcc5</literal> and <literal>gfortran5</literal> packages have been removed. + </para> + </listitem> + <listitem> + <para> + The <option>services.xserver.displayManager.auto</option> module has been removed. + It was only intended for use in internal NixOS tests, and gave the false impression + of it being a special display manager when it's actually LightDM. + Please use the <xref linkend="opt-services.xserver.displayManager.lightdm.autoLogin"/> options instead, + or any other display manager in NixOS as they all support auto-login. If you used this module specifically + because it permitted root auto-login you can override the lightdm-autologin pam module like: +<programlisting> +<link xlink:href="#opt-security.pam.services._name__.text">security.pam.services.lightdm-autologin.text</link> = lib.mkForce '' + auth requisite pam_nologin.so + auth required pam_succeed_if.so quiet + auth required pam_permit.so + + account include lightdm + + password include lightdm + + session include lightdm +''; +</programlisting> + The difference is the: +<programlisting> +auth required pam_succeed_if.so quiet +</programlisting> + line, where default it's: +<programlisting> +auth required pam_succeed_if.so uid >= 1000 quiet +</programlisting> + not permitting users with uid's below 1000 (like root). + All other display managers in NixOS are configured like this. + </para> + </listitem> + <listitem> + <para> + There have been lots of improvements to the Mailman module. As + a result, + </para> + <itemizedlist> + <listitem> + <para> + The <option>services.mailman.hyperkittyBaseUrl</option> + option has been renamed to <xref + linkend="opt-services.mailman.hyperkitty.baseUrl"/>. + </para> + </listitem> + <listitem> + <para> + The <option>services.mailman.hyperkittyApiKey</option> + option has been removed. This is because having an option + for the Hyperkitty API key meant that the API key would be + stored in the world-readable Nix store, which was a + security vulnerability. A new Hyperkitty API key will be + generated the first time the new Hyperkitty service is run, + and it will then be persisted outside of the Nix store. To + continue using Hyperkitty, you must set <xref + linkend="opt-services.mailman.hyperkitty.enable"/> to + <literal>true</literal>. + </para> + </listitem> + <listitem> + <para> + Additionally, some Postfix configuration must now be set + manually instead of automatically by the Mailman module: +<programlisting> +<xref linkend="opt-services.postfix.relayDomains"/> = [ "hash:/var/lib/mailman/data/postfix_domains" ]; +<xref linkend="opt-services.postfix.config"/>.transport_maps = [ "hash:/var/lib/mailman/data/postfix_lmtp" ]; +<xref linkend="opt-services.postfix.config"/>.local_recipient_maps = [ "hash:/var/lib/mailman/data/postfix_lmtp" ]; +</programlisting> + This is because some users may want to include other values + in these lists as well, and this was not possible if they + were set automatically by the Mailman module. It would not + have been possible to just concatenate values from multiple + modules each setting the values they needed, because the + order of elements in the list is significant. + </para> + </listitem> + </itemizedlist> + </listitem> + <listitem> + <para>The LLVM versions 3.5, 3.9 and 4 (including the corresponding CLang versions) have been dropped.</para> + </listitem> + <listitem> + <para> + The <option>networking.interfaces.*.preferTempAddress</option> option has + been replaced by <option>networking.interfaces.*.tempAddress</option>. + The new option allows better control of the IPv6 temporary addresses, + including completely disabling them for interfaces where they are not + needed. + </para> + </listitem> + <listitem> + <para> + Rspamd was updated to version 2.2. Read + <link xlink:href="https://rspamd.com/doc/migration.html#migration-to-rspamd-20"> + the upstream migration notes</link> carefully. Please be especially + aware that some modules were removed and the default Bayes backend is + now Redis. + </para> + </listitem> + <listitem> + <para> + The <literal>*psu</literal> versions of <package>oraclejdk8</package> have been removed + as they aren't provided by upstream anymore. + </para> + </listitem> + <listitem> + <para> + The <option>services.dnscrypt-proxy</option> module has been removed + as it used the deprecated version of dnscrypt-proxy. We've added + <xref linkend="opt-services.dnscrypt-proxy2.enable"/> to use the supported version. + This module supports configuration via the Nix attribute set + <xref linkend="opt-services.dnscrypt-proxy2.settings" />, or by passing a TOML configuration file via + <xref linkend="opt-services.dnscrypt-proxy2.configFile" />. +<programlisting> +# Example configuration: +services.dnscrypt-proxy2.enable = true; +services.dnscrypt-proxy2.settings = { + listen_addresses = [ "127.0.0.1:43" ]; + sources.public-resolvers = { + urls = [ "https://download.dnscrypt.info/resolvers-list/v2/public-resolvers.md" ]; + cache_file = "public-resolvers.md"; + minisign_key = "RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3"; + refresh_delay = 72; + }; +}; + +services.dnsmasq.enable = true; +services.dnsmasq.servers = [ "127.0.0.1#43" ]; +</programlisting> + </para> + </listitem> + <listitem> + <para> + <literal>qesteidutil</literal> has been deprecated in favor of <literal>qdigidoc</literal>. + </para> + </listitem> + <listitem> + <para> + <package>sqldeveloper_18</package> has been removed as it's not maintained anymore, + <package>sqldeveloper</package> has been updated to version <literal>19.4</literal>. + Please note that this means that this means that the <package>oraclejdk</package> is now + required. For further information please read the + <link xlink:href="https://www.oracle.com/technetwork/developer-tools/sql-developer/downloads/sqldev-relnotes-194-5908846.html">release notes</link>. + </para> + </listitem> + <listitem> + <para> + Haskell <varname>env</varname> and <varname>shellFor</varname> dev shell environments now organize dependencies the same way as regular builds. + In particular, rather than receiving all the different lists of dependencies mashed together as one big list, and then partitioning into Haskell and non-Hakell dependencies, they work from the original many different dependency parameters and don't need to algorithmically partition anything. + </para> + <para> + This means that if you incorrectly categorize a dependency, e.g. non-Haskell library dependency as a <varname>buildDepends</varname> or run-time Haskell dependency as a <varname>setupDepends</varname>, whereas things would have worked before they may not work now. + </para> + </listitem> + <listitem> + <para> + The <package>gcc-snapshot</package>-package has been removed. It's marked as broken for >2 years and used to point + to a fairly old snapshot from the <package>gcc7</package>-branch. + </para> + </listitem> + <listitem> + <para> + The <citerefentry><refentrytitle>nixos-build-vms</refentrytitle><manvolnum>8</manvolnum> + </citerefentry>-script now uses the python test-driver. + </para> + </listitem> + <listitem> + <para> + The <package>riot-web</package> package now accepts configuration overrides as an attribute set instead of a string. + A formerly used JSON configuration can be converted to an attribute set with <literal>builtins.fromJSON</literal>. + </para> + <para> + The new default configuration also disables automatic guest account registration and analytics to improve privacy. + The previous behavior can be restored by setting <literal>config.riot-web.conf = { disable_guests = false; piwik = true; }</literal>. + </para> + </listitem> + <listitem> + <para> + Stand-alone usage of <literal>Upower</literal> now requires + <option>services.upower.enable</option> instead of just installing into + <xref linkend="opt-environment.systemPackages"/>. + </para> + </listitem> + <listitem> + <para> + <package>nextcloud</package> has been updated to <literal>v18.0.2</literal>. This means + that users from NixOS 19.09 can't upgrade directly since you can only move one version + forward and 19.09 uses <literal>v16.0.8</literal>. + </para> + <para> + To provide a safe upgrade-path and to circumvent similar issues in the future, the following + measures were taken: + <itemizedlist> + <listitem> + <para> + The <package>pkgs.nextcloud</package>-attribute has been removed and replaced with + versioned attributes (currently <package>pkgs.nextcloud17</package> and + <package>pkgs.nextcloud18</package>). With this change major-releases can be backported + without breaking stuff and to make upgrade-paths easier. + </para> + </listitem> + <listitem> + <para> + Existing setups will be detected using + <link linkend="opt-system.stateVersion">system.stateVersion</link>: by default, + <package>nextcloud17</package> will be used, but will raise a warning which notes + that after that deploy it's recommended to update to the latest stable version + (<package>nextcloud18</package>) by declaring the newly introduced setting + <link linkend="opt-services.nextcloud.package">services.nextcloud.package</link>. + </para> + </listitem> + <listitem> + <para> + Users with an overlay (e.g. to use <package>nextcloud</package> at version + <literal>v18</literal> on <literal>19.09</literal>) will get an evaluation error + by default. This is done to ensure that our + <link linkend="opt-services.nextcloud.package">package</link>-option doesn't select an + older version by accident. It's recommended to use <package>pkgs.nextcloud18</package> + or to set <link linkend="opt-services.nextcloud.package">package</link> to + <package>pkgs.nextcloud</package> explicitly. + </para> + </listitem> + </itemizedlist> + </para> + <warning> + <para> + Please note that if you're coming from <literal>19.03</literal> or older, you have + to manually upgrade to <literal>19.09</literal> first to upgrade your server + to Nextcloud v16. + </para> + </warning> + </listitem> + <listitem> + <para> + <package>Hydra</package> has gained a massive performance improvement due to + <link xlink:href="https://github.com/NixOS/hydra/pull/710">some database schema + changes</link> by adding several IDs and better indexing. However, it's necessary + to upgrade Hydra in multiple steps: + <itemizedlist> + <listitem> + <para> + At first, an older version of Hydra needs to be deployed which adds those + (nullable) columns. When having set <link linkend="opt-system.stateVersion">stateVersion + </link> to a value older than <literal>20.03</literal>, this package will be selected + by default from the module when upgrading. Otherwise, the package can be deployed using + the following config: +<programlisting>{ pkgs, ... }: { + <link linkend="opt-services.hydra.package">services.hydra.package</link> = pkgs.hydra-migration; +}</programlisting> + </para> + </listitem> + <listitem> + <para> + Automatically fill the newly added ID columns on the server by running the following + command: +<screen> +<prompt>$ </prompt>hydra-backfill-ids +</screen> + <warning> + <para>Please note that this process can take a while depending on your database-size!</para> + </warning> + </para> + </listitem> + <listitem> + <para> + Deploy a newer version of Hydra to activate the DB optimizations. This can be done by + using <package>hydra-unstable</package>. This package already includes + <link xlink:href="https://github.com/nixos/rfcs/pull/49">flake-support</link> and is + therefore compiled against <package>pkgs.nixFlakes</package>. + <warning> + <para> + If your <link linkend="opt-system.stateVersion">stateVersion</link> is set to + <literal>20.03</literal> or greater, <package>hydra-unstable</package> will be used + automatically! This will break your setup if you didn't run the migration. + </para> + </warning> + Please note that Hydra is currently not available with <package>nixStable</package> + as this doesn't compile anymore. + </para> + </listitem> + </itemizedlist> + <warning> + <para> + <package>pkgs.hydra</package> has been removed to ensure a graceful database-migration + using the dedicated package-attributes. If you still have <package>pkgs.hydra</package> + defined in e.g. an overlay, an assertion error will be thrown. To circumvent this, + you need to set <xref linkend="opt-services.hydra.package" /> to <package>pkgs.hydra</package> + explicitly and make sure you know what you're doing! + </para> + </warning> + </para> + </listitem> + <listitem> + <para> + The TokuDB storage engine will be disabled in <package>mariadb</package> 10.5. It is recommended to switch + to RocksDB. See also <link xlink:href="https://mariadb.com/kb/en/tokudb/">TokuDB</link>. + </para> + </listitem> </itemizedlist> </section> @@ -209,12 +1102,142 @@ </listitem> <listitem> <para> + The nginx web server previously started its master process as root + privileged, then ran worker processes as a less privileged identity user + (the <literal>nginx</literal> user). + This was changed to start all of nginx as a less privileged user (defined by + <literal>services.nginx.user</literal> and + <literal>services.nginx.group</literal>). As a consequence, all files that + are needed for nginx to run (included configuration fragments, SSL + certificates and keys, etc.) must now be readable by this less privileged + user/group. + </para> + <para> + To continue to use the old approach, you can configure: + <programlisting> +services.nginx.appendConfig = let cfg = config.services.nginx; in ''user ${cfg.user} ${cfg.group};''; +systemd.services.nginx.serviceConfig.User = lib.mkForce "root"; + </programlisting> + </para> + </listitem> + <listitem> + <para> OpenSSH has been upgraded from 7.9 to 8.1, improving security and adding features but with potential incompatibilities. Consult the <link xlink:href="https://www.openssh.com/txt/release-8.1"> release announcement</link> for more information. </para> </listitem> + <listitem> + <para> + <literal>PRETTY_NAME</literal> in <literal>/etc/os-release</literal> + now uses the short rather than full version string. + </para> + </listitem> + <listitem> + <para> + The ACME module has switched from simp-le to <link xlink:href="https://github.com/go-acme/lego">lego</link> + which allows us to support DNS-01 challenges and wildcard certificates. The following options have been added: + <link linkend="opt-security.acme.acceptTerms">security.acme.acceptTerms</link>, + <link linkend="opt-security.acme.certs">security.acme.certs.<name>.dnsProvider</link>, + <link linkend="opt-security.acme.certs">security.acme.certs.<name>.credentialsFile</link>, + <link linkend="opt-security.acme.certs">security.acme.certs.<name>.dnsPropagationCheck</link>. + As well as this, the options <literal>security.acme.acceptTerms</literal> and either + <literal>security.acme.email</literal> or <literal>security.acme.certs.<name>.email</literal> + must be set in order to use the ACME module. + Certificates will be regenerated on activation, no account or certificate will be migrated from simp-le. + In particular private keys will not be preserved. However, the credentials for simp-le are preserved and + thus it is possible to roll back to previous versions without breaking certificate generation. + Note also that in contrary to simp-le a new private key is recreated at each renewal by default, which can + have consequences if you embed your public key in apps. + </para> + </listitem> + <listitem> + <para> + It is now possible to unlock LUKS-Encrypted file systems using a FIDO2 token + via <option>boot.initrd.luks.fido2Support</option>. + </para> + </listitem> + <listitem> + <para> + Predictably named network interfaces get renamed in stage-1. This means that it is possible + to use the proper interface name for e.g. Dropbear setups. + </para> + <para> + For further reference, please read <link xlink:href="https://github.com/NixOS/nixpkgs/pull/68953">#68953</link> or the corresponding <link xlink:href="https://discourse.nixos.org/t/predictable-network-interface-names-in-initrd/4055">discourse thread</link>. + </para> + </listitem> + <listitem> + <para> + The <package>matrix-synapse</package>-package has been updated to + <link xlink:href="https://github.com/matrix-org/synapse/releases/tag/v1.11.1">v1.11.1</link>. + Due to <link xlink:href="https://github.com/matrix-org/synapse/releases/tag/v1.10.0rc1">stricter requirements</link> + for database configuration when using <package>postgresql</package>, the automated database setup + of the module has been removed to avoid any further edge-cases. + </para> + <para> + <package>matrix-synapse</package> expects <literal>postgresql</literal>-databases to have the options + <literal>LC_COLLATE</literal> and <literal>LC_CTYPE</literal> set to + <link xlink:href="https://www.postgresql.org/docs/12/locale.html"><literal>'C'</literal></link> which basically + instructs <literal>postgresql</literal> to ignore any locale-based preferences. + </para> + <para> + Depending on your setup, you need to incorporate one of the following changes in your setup to + upgrade to 20.03: + <itemizedlist> + <listitem><para>If you use <literal>sqlite3</literal> you don't need to do anything.</para></listitem> + <listitem><para>If you use <literal>postgresql</literal> on a different server, you don't need + to change anything as well since this module was never designed to configure remote databases. + </para></listitem> + <listitem><para>If you use <literal>postgresql</literal> and configured your synapse initially on + <literal>19.09</literal> or older, you simply need to enable <package>postgresql</package>-support + explicitly: +<programlisting>{ ... }: { + services.matrix-synapse = { + <link linkend="opt-services.matrix-synapse.enable">enable</link> = true; + /* and all the other config you've defined here */ + }; + <link linkend="opt-services.postgresql.enable">services.postgresql.enable</link> = true; +}</programlisting> + </para></listitem> + <listitem><para>If you deploy a fresh <package>matrix-synapse</package>, you need to configure + the database yourself (e.g. by using the + <link linkend="opt-services.postgresql.initialScript">services.postgresql.initialScript</link> + option). An example for this can be found in the + <link linkend="module-services-matrix">documentation of the Matrix module</link>. + </para></listitem> + <listitem><para>If you initially deployed your <package>matrix-synapse</package> on + <literal>nixos-unstable</literal> <emphasis>after</emphasis> the <literal>19.09</literal>-release, + your database is misconfigured due to a regression in NixOS. For now, <package>matrix-synapse</package> will + startup with a warning, but it's recommended to reconfigure the database to set the values + <literal>LC_COLLATE</literal> and <literal>LC_CTYPE</literal> to + <link xlink:href="https://www.postgresql.org/docs/12/locale.html"><literal>'C'</literal></link>. + </para></listitem> + </itemizedlist> + </para> + </listitem> + <listitem> + <para> + The <link linkend="opt-systemd.network.links">systemd.network.links</link> option is now respected + even when <link linkend="opt-systemd.network.enable">systemd-networkd</link> is disabled. + This mirrors the behaviour of systemd - It's udev that parses <literal>.link</literal> files, + not <command>systemd-networkd</command>. + </para> + </listitem> + <listitem> + <para> + <package>mongodb</package> has been updated to version <literal>3.4.24</literal>. + <warning> + <para> + Please note that <package>mongodb</package> has been relicensed under their own + <link xlink:href="https://www.mongodb.com/licensing/server-side-public-license/faq"><literal> + sspl</literal></link>-license. Since it's not entirely free and not OSI-approved, + it's listed as non-free. This means that Hydra doesn't provide prebuilt + <package>mongodb</package>-packages and needs to be built locally. + </para> + </warning> + </para> + </listitem> </itemizedlist> </section> </section> diff --git a/nixos/doc/manual/release-notes/rl-2009.xml b/nixos/doc/manual/release-notes/rl-2009.xml new file mode 100644 index 0000000000000..955c19bd98e66 --- /dev/null +++ b/nixos/doc/manual/release-notes/rl-2009.xml @@ -0,0 +1,298 @@ +<section 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="sec-release-20.09"> + <title>Release 20.09 (“Nightingale”, 2020.09/??)</title> + + <section 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="sec-release-20.09-highlights"> + <title>Highlights</title> + + <para> + In addition to numerous new and upgraded packages, this release has the + following highlights: + </para> + + <itemizedlist> + <listitem> + <para> + Support is planned until the end of April 2021, handing over to 21.03. + </para> + </listitem> + <listitem> + <para>GNOME desktop environment was upgraded to 3.36, see its <link xlink:href="https://help.gnome.org/misc/release-notes/3.36/">release notes</link>.</para> + </listitem> + <listitem> + <para> + PHP now defaults to PHP 7.4, updated from 7.3. + </para> + </listitem> + <listitem> + <para> + Two new options, <link linkend="opt-services.openssh.authorizedKeysCommand">authorizedKeysCommand</link> + and <link linkend="opt-services.openssh.authorizedKeysCommandUser">authorizedKeysCommandUser</link>, have + been added to the <literal>openssh</literal> module. If you have <literal>AuthorizedKeysCommand</literal> + in your <link linkend="opt-services.openssh.extraConfig">services.openssh.extraConfig</link> you should + make use of these new options instead. + </para> + </listitem> + <listitem> + <para> + There is a new module for Podman(<varname>virtualisation.podman</varname>), a drop-in replacement for the Docker command line. + </para> + </listitem> + <listitem> + <para> + The new <varname>virtualisation.containers</varname> module manages configuration shared by the CRI-O and Podman modules. + </para> + </listitem> + </itemizedlist> + </section> + + <section 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="sec-release-20.09-new-services"> + <title>New Services</title> + + <para> + The following new services were added since the last release: + </para> + + <itemizedlist> + <listitem> + <para /> + </listitem> + </itemizedlist> + + </section> + + <section 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="sec-release-20.09-incompatibilities"> + <title>Backward Incompatibilities</title> + + <para> + When upgrading from a previous release, please be aware of the following + incompatible changes: + </para> + + <itemizedlist> + <listitem> + <para> + Grafana is now built without support for phantomjs by default. Phantomjs support has been + <link xlink:href="https://grafana.com/docs/grafana/latest/guides/whats-new-in-v6-4/">deprecated in Grafana</link> + and the <package>phantomjs</package> project is + <link xlink:href="https://github.com/ariya/phantomjs/issues/15344#issue-302015362">currently unmaintained</link>. + It can still be enabled by providing <literal>phantomJsSupport = true</literal> to the package instanciation: +<programlisting>{ + services.grafana.package = pkgs.grafana.overrideAttrs (oldAttrs: rec { + phantomJsSupport = false; + }); +}</programlisting> + </para> + </listitem> + <listitem> + <para> + The <link linkend="opt-services.supybot.enable">supybot</link> module now uses <literal>/var/lib/supybot</literal> + as its default <link linkend="opt-services.supybot.stateDir">stateDir</link> path if <literal>stateVersion</literal> + is 20.09 or higher. It also enables number of + <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.exec.html#Sandboxing">systemd sandboxing options</link> + which may possibly interfere with some plugins. If this is the case you can disable the options through attributes in + <option>systemd.services.supybot.serviceConfig</option>. + </para> + </listitem> + <listitem> + <para> + The <literal>security.duosec.skey</literal> option, which stored a secret in the + nix store, has been replaced by a new + <link linkend="opt-security.duosec.secretKeyFile">security.duosec.secretKeyFile</link> + option for better security. + </para> + <para> + <literal>security.duosec.ikey</literal> has been renamed to + <link linkend="opt-security.duosec.integrationKey">security.duosec.integrationKey</link>. + </para> + </listitem> + <listitem> + <para> + The initrd SSH support now uses OpenSSH rather than Dropbear to + allow the use of Ed25519 keys and other OpenSSH-specific + functionality. Host keys must now be in the OpenSSH format, and at + least one pre-generated key must be specified. + </para> + <para> + If you used the <option>boot.initrd.network.ssh.host*Key</option> + options, you'll get an error explaining how to convert your host + keys and migrate to the new + <option>boot.initrd.network.ssh.hostKeys</option> option. + Otherwise, if you don't have any host keys set, you'll need to + generate some; see the <option>hostKeys</option> option + documentation for instructions. + </para> + </listitem> + <listitem> + <para> + Since this release there's an easy way to customize your PHP install to get a much smaller + base PHP with only wanted extensions enabled. See the following snippet installing a smaller PHP + with the extensions <literal>imagick</literal>, <literal>opcache</literal> and + <literal>pdo_mysql</literal> loaded: + + <programlisting> +environment.systemPackages = [ +(pkgs.php.buildEnv { extensions = pp: with pp; [ + imagick + opcache + pdo_mysql + ]; }) +];</programlisting> + + The default <literal>php</literal> attribute hasn't lost any extensions - + the <literal>opcache</literal> extension was added there. + + All upstream PHP extensions are available under <package><![CDATA[php.extensions.<name?>]]></package>. + </para> + <para> + The updated <literal>php</literal> attribute is now easily customizable to your liking + by using extensions instead of writing config files or changing configure flags. + + Therefore we have removed the following configure flags: + + <itemizedlist> + <title>PHP <literal>config</literal> flags that we don't read anymore:</title> + <listitem><para><literal>config.php.argon2</literal></para></listitem> + <listitem><para><literal>config.php.bcmath</literal></para></listitem> + <listitem><para><literal>config.php.bz2</literal></para></listitem> + <listitem><para><literal>config.php.calendar</literal></para></listitem> + <listitem><para><literal>config.php.curl</literal></para></listitem> + <listitem><para><literal>config.php.exif</literal></para></listitem> + <listitem><para><literal>config.php.ftp</literal></para></listitem> + <listitem><para><literal>config.php.gd</literal></para></listitem> + <listitem><para><literal>config.php.gettext</literal></para></listitem> + <listitem><para><literal>config.php.gmp</literal></para></listitem> + <listitem><para><literal>config.php.imap</literal></para></listitem> + <listitem><para><literal>config.php.intl</literal></para></listitem> + <listitem><para><literal>config.php.ldap</literal></para></listitem> + <listitem><para><literal>config.php.libxml2</literal></para></listitem> + <listitem><para><literal>config.php.libzip</literal></para></listitem> + <listitem><para><literal>config.php.mbstring</literal></para></listitem> + <listitem><para><literal>config.php.mysqli</literal></para></listitem> + <listitem><para><literal>config.php.mysqlnd</literal></para></listitem> + <listitem><para><literal>config.php.openssl</literal></para></listitem> + <listitem><para><literal>config.php.pcntl</literal></para></listitem> + <listitem><para><literal>config.php.pdo_mysql</literal></para></listitem> + <listitem><para><literal>config.php.pdo_odbc</literal></para></listitem> + <listitem><para><literal>config.php.pdo_pgsql</literal></para></listitem> + <listitem><para><literal>config.php.phpdbg</literal></para></listitem> + <listitem><para><literal>config.php.postgresql</literal></para></listitem> + <listitem><para><literal>config.php.readline</literal></para></listitem> + <listitem><para><literal>config.php.soap</literal></para></listitem> + <listitem><para><literal>config.php.sockets</literal></para></listitem> + <listitem><para><literal>config.php.sodium</literal></para></listitem> + <listitem><para><literal>config.php.sqlite</literal></para></listitem> + <listitem><para><literal>config.php.tidy</literal></para></listitem> + <listitem><para><literal>config.php.xmlrpc</literal></para></listitem> + <listitem><para><literal>config.php.xsl</literal></para></listitem> + <listitem><para><literal>config.php.zip</literal></para></listitem> + <listitem><para><literal>config.php.zlib</literal></para></listitem> + </itemizedlist> + </para> + </listitem> + <listitem> + <para> + Gollum received a major update to version 5.x and you may have to change + some links in your wiki when migrating from gollum 4.x. More information + can be found + <link xlink:href="https://github.com/gollum/gollum/wiki/5.0-release-notes#migrating-your-wiki">here</link>. + </para> + </listitem> + <listitem> + <para> + Deluge 2.x was added and is used as default for new NixOS + installations where stateVersion is >= 20.09. If you are upgrading from a previous + NixOS version, you can set <literal>service.deluge.package = pkgs.deluge-2_x</literal> + to upgrade to Deluge 2.x and migrate the state to the new format. + Be aware that backwards state migrations are not supported by Deluge. + </para> + </listitem> + + <listitem> + <para> + The NixOS options <literal>nesting.clone</literal> and + <literal>nesting.children</literal> have been deleted, and + replaced with named <xref linkend="opt-specialisation"/> + configurations. + </para> + + <para> + Replace a <literal>nesting.clone</literal> entry with: + +<programlisting>{ +<link xlink:href="#opt-specialisation">specialisation.example-sub-configuration</link> = { + <link xlink:href="#opt-specialisation._name_.configuration">configuration</link> = { + ... + }; +};</programlisting> + + </para> + <para> + Replace a <literal>nesting.children</literal> entry with: + +<programlisting>{ +<link xlink:href="#opt-specialisation">specialisation.example-sub-configuration</link> = { + <link xlink:href="#opt-specialisation._name_.inheritParentConfig">inheritParentConfig</link> = false; + <link xlink:href="#opt-specialisation._name_.configuration">configuration</link> = { + ... + }; +};</programlisting> + </para> + + <para> + To switch to a specialised configuration at runtime you need to + run: +<programlisting> +# sudo /run/current-system/specialisation/example-sub-configuration/bin/switch-to-configuration test +</programlisting> + Before you would have used: +<programlisting> +# sudo /run/current-system/fine-tune/child-1/bin/switch-to-configuration test +</programlisting> + </para> + </listitem> + </itemizedlist> + </section> + + <section 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="sec-release-20.09-notable-changes"> + <title>Other Notable Changes</title> + + <itemizedlist> + <listitem> + <para> + <option>services.journald.rateLimitBurst</option> was updated from + <literal>1000</literal> to <literal>10000</literal> to follow the new + upstream systemd default. + </para> + </listitem> + <listitem> + <para> + The <package>notmuch</package> package move its emacs-related binaries and + emacs lisp files to a separate output. They're not part + of the default <literal>out</literal> output anymore - if you relied on the + <literal>notmuch-emacs-mua</literal> binary or the emacs lisp files, access them via + the <literal>notmuch.emacs</literal> output. + </para> + </listitem> + </itemizedlist> + </section> +</section> diff --git a/nixos/lib/eval-config.nix b/nixos/lib/eval-config.nix index 77490ca3762a5..c8824c2690d33 100644 --- a/nixos/lib/eval-config.nix +++ b/nixos/lib/eval-config.nix @@ -41,6 +41,12 @@ let # default to the argument. That way this new default could propagate all # they way through, but has the last priority behind everything else. nixpkgs.system = lib.mkDefault system; + + # Stash the value of the `system` argument. When using `nesting.children` + # we want to have the same default value behavior (immediately above) + # without any interference from the user's configuration. + nixpkgs.initialSystem = system; + _module.args.pkgs = lib.mkIf (pkgs_ != null) (lib.mkForce pkgs_); }; }; @@ -55,7 +61,7 @@ in rec { args = extraArgs; specialArgs = { modulesPath = builtins.toString ../modules; } // specialArgs; - }) config options; + }) config options _module; # These are the extra arguments passed to every module. In # particular, Nixpkgs is passed through the "pkgs" argument. @@ -63,5 +69,5 @@ in rec { inherit baseModules extraModules modules; }; - inherit (config._module.args) pkgs; + inherit (_module.args) pkgs; } diff --git a/nixos/lib/make-ext4-fs.nix b/nixos/lib/make-ext4-fs.nix index 932adcd979670..627ac324cf57e 100644 --- a/nixos/lib/make-ext4-fs.nix +++ b/nixos/lib/make-ext4-fs.nix @@ -4,8 +4,11 @@ # generated image is sized to only fit its contents, with the expectation # that a script resizes the filesystem at boot time. { pkgs +, lib # List of derivations to be included , storePaths +# Whether or not to compress the resulting image with zstd +, compressImage ? false, zstd # Shell commands to populate the ./files directory. # All files in that directory are copied to the root of the FS. , populateImageCommands ? "" @@ -20,18 +23,20 @@ let sdClosureInfo = pkgs.buildPackages.closureInfo { rootPaths = storePaths; }; in - pkgs.stdenv.mkDerivation { - name = "ext4-fs.img"; + name = "ext4-fs.img${lib.optionalString compressImage ".zst"}"; - nativeBuildInputs = [e2fsprogs.bin libfaketime perl lkl]; + nativeBuildInputs = [ e2fsprogs.bin libfaketime perl lkl ] + ++ lib.optional compressImage zstd; buildCommand = '' + ${if compressImage then "img=temp.img" else "img=$out"} ( mkdir -p ./files ${populateImageCommands} ) + # Add the closures of the top-level store objects. storePaths=$(cat ${sdClosureInfo}/store-paths) @@ -42,50 +47,40 @@ pkgs.stdenv.mkDerivation { bytes=$((2 * 4096 * $numInodes + 4096 * $numDataBlocks)) echo "Creating an EXT4 image of $bytes bytes (numInodes=$numInodes, numDataBlocks=$numDataBlocks)" - truncate -s $bytes $out - faketime -f "1970-01-01 00:00:01" mkfs.ext4 -L ${volumeLabel} -U ${uuid} $out + truncate -s $bytes $img + faketime -f "1970-01-01 00:00:01" mkfs.ext4 -L ${volumeLabel} -U ${uuid} $img # Also include a manifest of the closures in a format suitable for nix-store --load-db. cp ${sdClosureInfo}/registration nix-path-registration - cptofs -t ext4 -i $out nix-path-registration / + cptofs -t ext4 -i $img nix-path-registration / # Create nix/store before copying paths faketime -f "1970-01-01 00:00:01" mkdir -p nix/store - cptofs -t ext4 -i $out nix / + cptofs -t ext4 -i $img nix / echo "copying store paths to image..." - cptofs -t ext4 -i $out $storePaths /nix/store/ + cptofs -t ext4 -i $img $storePaths /nix/store/ - ( echo "copying files to image..." - cd ./files - cptofs -t ext4 -i $out ./* / - ) + cptofs -t ext4 -i $img ./files/* / + export EXT2FS_NO_MTAB_OK=yes # I have ended up with corrupted images sometimes, I suspect that happens when the build machine's disk gets full during the build. - if ! fsck.ext4 -n -f $out; then + if ! fsck.ext4 -n -f $img; then echo "--- Fsck failed for EXT4 image of $bytes bytes (numInodes=$numInodes, numDataBlocks=$numDataBlocks) ---" cat errorlog return 1 fi - ( - # Resizes **snugly** to its actual limits (or closer to) - free=$(dumpe2fs $out | grep '^Free blocks:') - blocksize=$(dumpe2fs $out | grep '^Block size:') - blocks=$(dumpe2fs $out | grep '^Block count:') - blocks=$((''${blocks##*:})) # format the number. - blocksize=$((''${blocksize##*:})) # format the number. - # System can't boot with 0 blocks free. - # Add 16MiB of free space - fudge=$(( 16 * 1024 * 1024 / blocksize )) - size=$(( blocks - ''${free##*:} + fudge )) - - echo "Resizing from $blocks blocks to $size blocks. (~ $((size*blocksize/1024/1024))MiB)" - EXT2FS_NO_MTAB_OK=yes resize2fs $out -f $size - ) + echo "Resizing to minimum allowed size" + resize2fs -M $img # And a final fsck, because of the previous truncating. - fsck.ext4 -n -f $out + fsck.ext4 -n -f $img + + if [ ${builtins.toString compressImage} ]; then + echo "Compressing image" + zstd -v --no-progress ./$img -o $out + fi ''; } diff --git a/nixos/lib/make-iso9660-image.sh b/nixos/lib/make-iso9660-image.sh index b7b1ab52a6373..d4633d2c8d169 100644 --- a/nixos/lib/make-iso9660-image.sh +++ b/nixos/lib/make-iso9660-image.sh @@ -107,6 +107,7 @@ xorriso="xorriso -publisher nixos -graft-points -full-iso9660-filenames + -joliet ${isoBootFlags} ${usbBootFlags} ${efiBootFlags} diff --git a/nixos/lib/make-options-doc/default.nix b/nixos/lib/make-options-doc/default.nix index eee8f612410de..772b7d3add951 100644 --- a/nixos/lib/make-options-doc/default.nix +++ b/nixos/lib/make-options-doc/default.nix @@ -86,7 +86,7 @@ let optionsList = lib.sort optionLess optionsListDesc; # Convert the list of options into an XML file. - optionsXML = pkgs.writeText "options.xml" (builtins.toXML optionsList); + optionsXML = builtins.toFile "options.xml" (builtins.toXML optionsList); optionsNix = builtins.listToAttrs (map (o: { name = o.name; value = removeAttrs o ["name" "visible" "internal"]; }) optionsList); @@ -133,6 +133,7 @@ in { optionsJSON = pkgs.runCommand "options.json" { meta.description = "List of NixOS options in JSON format"; + buildInputs = [ pkgs.brotli ]; } '' # Export list of options in different format. @@ -141,8 +142,11 @@ in { cp ${builtins.toFile "options.json" (builtins.unsafeDiscardStringContext (builtins.toJSON optionsNix))} $dst/options.json + brotli -9 < $dst/options.json > $dst/options.json.br + mkdir -p $out/nix-support echo "file json $dst/options.json" >> $out/nix-support/hydra-build-products + echo "file json-br $dst/options.json.br" >> $out/nix-support/hydra-build-products ''; # */ optionsDocBook = pkgs.runCommand "options-docbook.xml" {} '' diff --git a/nixos/lib/qemu-flags.nix b/nixos/lib/qemu-flags.nix index 774f66b4804e0..859d9e975fec7 100644 --- a/nixos/lib/qemu-flags.nix +++ b/nixos/lib/qemu-flags.nix @@ -17,9 +17,9 @@ in else throw "Unknown QEMU serial device for system '${pkgs.stdenv.hostPlatform.system}'"; qemuBinary = qemuPkg: { - x86_64-linux = "${qemuPkg}/bin/qemu-kvm -cpu kvm64"; + x86_64-linux = "${qemuPkg}/bin/qemu-kvm -cpu host"; armv7l-linux = "${qemuPkg}/bin/qemu-system-arm -enable-kvm -machine virt -cpu host"; aarch64-linux = "${qemuPkg}/bin/qemu-system-aarch64 -enable-kvm -machine virt,gic-version=host -cpu host"; - x86_64-darwin = "${qemuPkg}/bin/qemu-kvm -cpu kvm64"; + x86_64-darwin = "${qemuPkg}/bin/qemu-kvm -cpu host"; }.${pkgs.stdenv.hostPlatform.system} or "${qemuPkg}/bin/qemu-kvm"; } diff --git a/nixos/lib/test-driver/test-driver.py b/nixos/lib/test-driver/test-driver.py index e45521424de37..07f27515990bd 100644 --- a/nixos/lib/test-driver/test-driver.py +++ b/nixos/lib/test-driver/test-driver.py @@ -1,13 +1,18 @@ #! /somewhere/python3 from contextlib import contextmanager, _GeneratorContextManager +from queue import Queue, Empty +from typing import Tuple, Any, Callable, Dict, Iterator, Optional, List from xml.sax.saxutils import XMLGenerator import _thread import atexit +import base64 +import codecs import os +import pathlib import ptpython.repl import pty -from queue import Queue, Empty import re +import shlex import shutil import socket import subprocess @@ -15,7 +20,6 @@ import sys import tempfile import time import unicodedata -from typing import Tuple, Any, Callable, Dict, Iterator, Optional, List CHAR_TO_KEY = { "A": "shift-a", @@ -82,7 +86,7 @@ CHAR_TO_KEY = { # Forward references nr_tests: int -nr_succeeded: int +failed_tests: list log: "Logger" machines: "List[Machine]" @@ -91,13 +95,19 @@ def eprint(*args: object, **kwargs: Any) -> None: print(*args, file=sys.stderr, **kwargs) +def make_command(args: list) -> str: + return " ".join(map(shlex.quote, (map(str, args)))) + + def create_vlan(vlan_nr: str) -> Tuple[str, str, "subprocess.Popen[bytes]", Any]: global log log.log("starting VDE switch for network {}".format(vlan_nr)) - vde_socket = os.path.abspath("./vde{}.ctl".format(vlan_nr)) + vde_socket = tempfile.mkdtemp( + prefix="nixos-test-vde-", suffix="-vde{}.ctl".format(vlan_nr) + ) pty_master, pty_slave = pty.openpty() vde_process = subprocess.Popen( - ["vde_switch", "-s", vde_socket, "--dirmode", "0777"], + ["vde_switch", "-s", vde_socket, "--dirmode", "0700"], bufsize=1, stdin=pty_slave, stdout=subprocess.PIPE, @@ -108,6 +118,7 @@ def create_vlan(vlan_nr: str) -> Tuple[str, str, "subprocess.Popen[bytes]", Any] fd.write("version\n") # TODO: perl version checks if this can be read from # an if not, dies. we could hang here forever. Fix it. + assert vde_process.stdout is not None vde_process.stdout.readline() if not os.path.exists(os.path.join(vde_socket, "ctl")): raise Exception("cannot start vde_switch") @@ -132,7 +143,7 @@ def retry(fn: Callable) -> None: class Logger: def __init__(self) -> None: self.logfile = os.environ.get("LOGFILE", "/dev/null") - self.logfile_handle = open(self.logfile, "wb") + self.logfile_handle = codecs.open(self.logfile, "wb") self.xml = XMLGenerator(self.logfile_handle, encoding="utf-8") self.queue: "Queue[Dict[str, str]]" = Queue(1000) @@ -215,7 +226,7 @@ class Machine: return path self.state_dir = create_dir("vm-state-{}".format(self.name)) - self.shared_dir = create_dir("xchg-shared") + self.shared_dir = create_dir("shared-xchg") self.booted = False self.connected = False @@ -306,8 +317,13 @@ class Machine: self.monitor.send(message) return self.wait_for_monitor_prompt() - def wait_for_unit(self, unit: str, user: Optional[str] = None) -> bool: - while True: + def wait_for_unit(self, unit: str, user: Optional[str] = None) -> None: + """Wait for a systemd unit to get into "active" state. + Throws exceptions on "failed" and "inactive" states as well as + after timing out. + """ + + def check_active(_: Any) -> bool: info = self.get_unit_info(unit, user) state = info["ActiveState"] if state == "failed": @@ -323,8 +339,10 @@ class Machine: 'unit "{}" is inactive and there ' "are no pending jobs" ).format(unit) ) - if state == "active": - return True + + return state == "active" + + retry(check_active) def get_unit_info(self, unit: str, user: Optional[str] = None) -> Dict[str, str]: status, lines = self.systemctl('--no-pager show "{}"'.format(unit), user) @@ -369,7 +387,7 @@ class Machine: if state != require_state: raise Exception( "Expected unit ‘{}’ to to be in state ".format(unit) - + "'active' but it is in state ‘{}’".format(state) + + "'{}' but it is in state ‘{}’".format(require_state, state) ) def execute(self, command: str) -> Tuple[int, str]: @@ -382,7 +400,7 @@ class Machine: status_code_pattern = re.compile(r"(.*)\|\!EOF\s+(\d+)") while True: - chunk = self.shell.recv(4096).decode() + chunk = self.shell.recv(4096).decode(errors="ignore") match = status_code_pattern.match(chunk) if match: output += match[1] @@ -415,18 +433,34 @@ class Machine: ) def wait_until_succeeds(self, command: str) -> str: + """Wait until a command returns success and return its output. + Throws an exception on timeout. + """ + output = "" + + def check_success(_: Any) -> bool: + nonlocal output + status, output = self.execute(command) + return status == 0 + with self.nested("waiting for success: {}".format(command)): - while True: - status, output = self.execute(command) - if status == 0: - return output + retry(check_success) + return output def wait_until_fails(self, command: str) -> str: + """Wait until a command returns failure. + Throws an exception on timeout. + """ + output = "" + + def check_failure(_: Any) -> bool: + nonlocal output + status, output = self.execute(command) + return status != 0 + with self.nested("waiting for failure: {}".format(command)): - while True: - status, output = self.execute(command) - if status != 0: - return output + retry(check_failure) + return output def wait_for_shutdown(self) -> None: if not self.booted: @@ -447,25 +481,38 @@ class Machine: ) return output - def wait_until_tty_matches(self, tty: str, regexp: str) -> bool: + def wait_until_tty_matches(self, tty: str, regexp: str) -> None: + """Wait until the visible output on the chosen TTY matches regular + expression. Throws an exception on timeout. + """ matcher = re.compile(regexp) + + def tty_matches(last: bool) -> bool: + text = self.get_tty_text(tty) + if last: + self.log( + f"Last chance to match /{regexp}/ on TTY{tty}, " + f"which currently contains: {text}" + ) + return len(matcher.findall(text)) > 0 + with self.nested("waiting for {} to appear on tty {}".format(regexp, tty)): - while True: - text = self.get_tty_text(tty) - if len(matcher.findall(text)) > 0: - return True + retry(tty_matches) def send_chars(self, chars: List[str]) -> None: with self.nested("sending keys ‘{}‘".format(chars)): for char in chars: self.send_key(char) - def wait_for_file(self, filename: str) -> bool: + def wait_for_file(self, filename: str) -> None: + """Waits until the file exists in machine's file system.""" + + def check_file(_: Any) -> bool: + status, _ = self.execute("test -e {}".format(filename)) + return status == 0 + with self.nested("waiting for file ‘{}‘".format(filename)): - while True: - status, _ = self.execute("test -e {}".format(filename)) - if status == 0: - return True + retry(check_file) def wait_for_open_port(self, port: int) -> None: def port_is_open(_: Any) -> bool: @@ -488,8 +535,8 @@ class Machine: def stop_job(self, jobname: str, user: Optional[str] = None) -> Tuple[int, str]: return self.systemctl("stop {}".format(jobname), user) - def wait_for_job(self, jobname: str) -> bool: - return self.wait_for_unit(jobname) + def wait_for_job(self, jobname: str) -> None: + self.wait_for_unit(jobname) def connect(self) -> None: if self.connected: @@ -524,6 +571,68 @@ class Machine: if ret.returncode != 0: raise Exception("Cannot convert screenshot") + def copy_from_host_via_shell(self, source: str, target: str) -> None: + """Copy a file from the host into the guest by piping it over the + shell into the destination file. Works without host-guest shared folder. + Prefer copy_from_host for whenever possible. + """ + with open(source, "rb") as fh: + content_b64 = base64.b64encode(fh.read()).decode() + self.succeed( + f"mkdir -p $(dirname {target})", + f"echo -n {content_b64} | base64 -d > {target}", + ) + + def copy_from_host(self, source: str, target: str) -> None: + """Copy a file from the host into the guest via the `shared_dir` shared + among all the VMs (using a temporary directory). + """ + host_src = pathlib.Path(source) + vm_target = pathlib.Path(target) + with tempfile.TemporaryDirectory(dir=self.shared_dir) as shared_td: + shared_temp = pathlib.Path(shared_td) + host_intermediate = shared_temp / host_src.name + vm_shared_temp = pathlib.Path("/tmp/shared") / shared_temp.name + vm_intermediate = vm_shared_temp / host_src.name + + self.succeed(make_command(["mkdir", "-p", vm_shared_temp])) + if host_src.is_dir(): + shutil.copytree(host_src, host_intermediate) + else: + shutil.copy(host_src, host_intermediate) + self.succeed("sync") + self.succeed(make_command(["mkdir", "-p", vm_target.parent])) + self.succeed(make_command(["cp", "-r", vm_intermediate, vm_target])) + # Make sure the cleanup is synced into VM + self.succeed("sync") + + def copy_from_vm(self, source: str, target_dir: str = "") -> None: + """Copy a file from the VM (specified by an in-VM source path) to a path + relative to `$out`. The file is copied via the `shared_dir` shared among + all the VMs (using a temporary directory). + """ + # Compute the source, target, and intermediate shared file names + out_dir = pathlib.Path(os.environ.get("out", os.getcwd())) + vm_src = pathlib.Path(source) + with tempfile.TemporaryDirectory(dir=self.shared_dir) as shared_td: + shared_temp = pathlib.Path(shared_td) + vm_shared_temp = pathlib.Path("/tmp/shared") / shared_temp.name + vm_intermediate = vm_shared_temp / vm_src.name + intermediate = shared_temp / vm_src.name + # Copy the file to the shared directory inside VM + self.succeed(make_command(["mkdir", "-p", vm_shared_temp])) + self.succeed(make_command(["cp", "-r", vm_src, vm_intermediate])) + self.succeed("sync") + abs_target = out_dir / target_dir / vm_src.name + abs_target.parent.mkdir(exist_ok=True, parents=True) + # Copy the file from the shared directory outside VM + if intermediate.is_dir(): + shutil.copytree(intermediate, abs_target) + else: + shutil.copy(intermediate, abs_target) + # Make sure the cleanup is synced into VM + self.succeed("sync") + def dump_tty_contents(self, tty: str) -> None: """Debugging: Dump the contents of the TTY<n> """ @@ -634,8 +743,10 @@ class Machine: self.shell, _ = self.shell_socket.accept() def process_serial_output() -> None: + assert self.process.stdout is not None for _line in self.process.stdout: - line = _line.decode("unicode_escape").replace("\r", "").rstrip() + # Ignore undecodable bytes that may occur in boot menus + line = _line.decode(errors="ignore").replace("\r", "").rstrip() eprint("{} # {}".format(self.name, line)) self.logger.enqueue({"msg": line, "machine": self.name}) @@ -667,18 +778,20 @@ class Machine: """Wait until it is possible to connect to the X server. Note that testing the existence of /tmp/.X11-unix/X0 is insufficient. """ + + def check_x(_: Any) -> bool: + cmd = ( + "journalctl -b SYSLOG_IDENTIFIER=systemd | " + + 'grep "Reached target Current graphical"' + ) + status, _ = self.execute(cmd) + if status != 0: + return False + status, _ = self.execute("[ -e /tmp/.X11-unix/X0 ]") + return status == 0 + with self.nested("waiting for the X11 server"): - while True: - cmd = ( - "journalctl -b SYSLOG_IDENTIFIER=systemd | " - + 'grep "Reached target Current graphical"' - ) - status, _ = self.execute(cmd) - if status != 0: - continue - status, _ = self.execute("[ -e /tmp/.X11-unix/X0 ]") - if status == 0: - return + retry(check_x) def get_window_names(self) -> List[str]: return self.succeed( @@ -770,23 +883,31 @@ def run_tests() -> None: machine.execute("sync") if nr_tests != 0: + nr_succeeded = nr_tests - len(failed_tests) eprint("{} out of {} tests succeeded".format(nr_succeeded, nr_tests)) - if nr_tests > nr_succeeded: + if len(failed_tests) > 0: + eprint( + "The following tests have failed:\n - {}".format( + "\n - ".join(failed_tests) + ) + ) sys.exit(1) @contextmanager def subtest(name: str) -> Iterator[None]: global nr_tests - global nr_succeeded + global failed_tests with log.nested(name): nr_tests += 1 try: yield - nr_succeeded += 1 return True except Exception as e: + failed_tests.append( + 'Test "{}" failed with error: "{}"'.format(name, str(e)) + ) log.log("error: {}".format(str(e))) return False @@ -795,7 +916,7 @@ def subtest(name: str) -> Iterator[None]: if __name__ == "__main__": log = Logger() - vlan_nrs = list(dict.fromkeys(os.environ["VLANS"].split())) + vlan_nrs = list(dict.fromkeys(os.environ.get("VLANS", "").split())) vde_sockets = [create_vlan(v) for v in vlan_nrs] for nr, vde_socket, _, _ in vde_sockets: os.environ["QEMU_VDE_SOCKET_{}".format(nr)] = vde_socket @@ -808,7 +929,7 @@ if __name__ == "__main__": exec("\n".join(machine_eval)) nr_tests = 0 - nr_succeeded = 0 + failed_tests = [] @atexit.register def clean_up() -> None: @@ -820,7 +941,7 @@ if __name__ == "__main__": machine.process.kill() for _, _, process, _ in vde_sockets: - process.kill() + process.terminate() log.close() tic = time.time() diff --git a/nixos/lib/testing-python.nix b/nixos/lib/testing-python.nix index d567d26876538..3891adc10435b 100644 --- a/nixos/lib/testing-python.nix +++ b/nixos/lib/testing-python.nix @@ -95,6 +95,8 @@ in rec { , makeCoverageReport ? false , enableOCR ? false , name ? "unnamed" + # Skip linting (mainly intended for faster dev cycles) + , skipLint ? false , ... } @ t: @@ -133,7 +135,7 @@ in rec { # Generate onvenience wrappers for running the test driver # interactively with the specified network, and for starting the # VMs from the command line. - driver = runCommand testDriverName + driver = let warn = if skipLint then lib.warn "Linting is disabled!" else lib.id; in warn (runCommand testDriverName { buildInputs = [ makeWrapper]; testScript = testScript'; preferLocalBuild = true; @@ -143,7 +145,9 @@ in rec { mkdir -p $out/bin echo -n "$testScript" > $out/test-script - ${python3Packages.black}/bin/black --check --diff $out/test-script + ${lib.optionalString (!skipLint) '' + ${python3Packages.black}/bin/black --check --diff $out/test-script + ''} ln -s ${testDriver}/bin/nixos-test-driver $out/bin/ vms=($(for i in ${toString vms}; do echo $i/bin/run-*-vm; done)) @@ -151,7 +155,7 @@ in rec { --add-flags "''${vms[*]}" \ ${lib.optionalString enableOCR "--prefix PATH : '${ocrProg}/bin:${imagemagick_tiff}/bin'"} \ - --run "export testScript=\"\$(cat $out/test-script)\"" \ + --run "export testScript=\"\$(${coreutils}/bin/cat $out/test-script)\"" \ --set VLANS '${toString vlans}' ln -s ${testDriver}/bin/nixos-test-driver $out/bin/nixos-run-vms wrapProgram $out/bin/nixos-run-vms \ @@ -160,7 +164,7 @@ in rec { --set tests 'start_all(); join_all();' \ --set VLANS '${toString vlans}' \ ${lib.optionalString (builtins.length vms == 1) "--set USE_SERIAL 1"} - ''; # " + ''); # " passMeta = drv: drv // lib.optionalAttrs (t ? meta) { meta = (drv.meta or {}) // t.meta; @@ -171,13 +175,13 @@ in rec { nodeNames = builtins.attrNames nodes; invalidNodeNames = lib.filter - (node: builtins.match "^[A-z_][A-z0-9_]+$" node == null) nodeNames; + (node: builtins.match "^[A-z_]([A-z0-9_]+)?$" node == null) nodeNames; in if lib.length invalidNodeNames > 0 then throw '' Cannot create machines out of (${lib.concatStringsSep ", " invalidNodeNames})! - All machines are referenced as perl variables in the testing framework which will break the + All machines are referenced as python variables in the testing framework which will break the script when special characters are used. Please stick to alphanumeric chars and underscores as separation. @@ -214,12 +218,12 @@ in rec { ''; testScript = '' - startAll; - $client->waitForUnit("multi-user.target"); + start_all() + client.wait_for_unit("multi-user.target") ${preBuild} - $client->succeed("env -i ${bash}/bin/bash ${buildrunner} /tmp/xchg/saved-env >&2"); + client.succeed("env -i ${bash}/bin/bash ${buildrunner} /tmp/xchg/saved-env >&2") ${postBuild} - $client->succeed("sync"); # flush all data before pulling the plug + client.succeed("sync") # flush all data before pulling the plug ''; vmRunCommand = writeText "vm-run" '' @@ -259,19 +263,21 @@ in rec { { ... }: { inherit require; + imports = [ + ../tests/common/auto.nix + ]; virtualisation.memorySize = 1024; services.xserver.enable = true; - services.xserver.displayManager.auto.enable = true; - services.xserver.windowManager.default = "icewm"; + test-support.displayManager.auto.enable = true; + services.xserver.displayManager.defaultSession = "none+icewm"; services.xserver.windowManager.icewm.enable = true; - services.xserver.desktopManager.default = "none"; }; in runInMachine ({ machine = client; preBuild = '' - $client->waitForX; + client.wait_for_x() ''; } // args); diff --git a/nixos/lib/testing.nix b/nixos/lib/testing.nix index a5f060a8d8e30..7d6a5c0a29009 100644 --- a/nixos/lib/testing.nix +++ b/nixos/lib/testing.nix @@ -19,7 +19,11 @@ in rec { inherit pkgs; - testDriver = stdenv.mkDerivation { + testDriver = lib.warn '' + Perl VM tests are deprecated and will be removed for 20.09. + Please update your tests to use the python test driver. + See https://github.com/NixOS/nixpkgs/pull/71684 for details. + '' stdenv.mkDerivation { name = "nixos-test-driver"; buildInputs = [ makeWrapper perl ]; @@ -246,12 +250,14 @@ in rec { { ... }: { inherit require; + imports = [ + ../tests/common/auto.nix + ]; virtualisation.memorySize = 1024; services.xserver.enable = true; - services.xserver.displayManager.auto.enable = true; - services.xserver.windowManager.default = "icewm"; + test-support.displayManager.auto.enable = true; + services.xserver.displayManager.defaultSession = "none+icewm"; services.xserver.windowManager.icewm.enable = true; - services.xserver.desktopManager.default = "none"; }; in runInMachine ({ diff --git a/nixos/lib/testing/jquery-ui.nix b/nixos/lib/testing/jquery-ui.nix index e65107a3c2fbc..05bd8dc2f9469 100644 --- a/nixos/lib/testing/jquery-ui.nix +++ b/nixos/lib/testing/jquery-ui.nix @@ -4,7 +4,7 @@ stdenv.mkDerivation rec { name = "jquery-ui-1.11.4"; src = fetchurl { - url = "http://jqueryui.com/resources/download/${name}.zip"; + url = "https://jqueryui.com/resources/download/${name}.zip"; sha256 = "0ciyaj1acg08g8hpzqx6whayq206fvf4whksz2pjgxlv207lqgjh"; }; @@ -17,7 +17,7 @@ stdenv.mkDerivation rec { ''; meta = { - homepage = http://jqueryui.com/; + homepage = "https://jqueryui.com/"; description = "A library of JavaScript widgets and effects"; platforms = stdenv.lib.platforms.all; }; diff --git a/nixos/lib/testing/jquery.nix b/nixos/lib/testing/jquery.nix index e272f66a5765e..732fdb3ba879e 100644 --- a/nixos/lib/testing/jquery.nix +++ b/nixos/lib/testing/jquery.nix @@ -29,7 +29,7 @@ stdenv.mkDerivation rec { meta = with stdenv.lib; { description = "JavaScript library designed to simplify the client-side scripting of HTML"; - homepage = http://jquery.com/; + homepage = "http://jquery.com/"; license = licenses.mit; platforms = platforms.all; }; diff --git a/nixos/lib/utils.nix b/nixos/lib/utils.nix index a522834e42942..21f4c7c6988ff 100644 --- a/nixos/lib/utils.nix +++ b/nixos/lib/utils.nix @@ -14,7 +14,7 @@ rec { # becomes dev-xyzzy. FIXME: slow. escapeSystemdPath = s: replaceChars ["/" "-" " "] ["-" "\\x2d" "\\x20"] - (if hasPrefix "/" s then substring 1 (stringLength s) s else s); + (removePrefix "/" s); # Returns a system path for a given shell package toShellPath = shell: diff --git a/nixos/maintainers/scripts/azure-new/.gitignore b/nixos/maintainers/scripts/azure-new/.gitignore new file mode 100644 index 0000000000000..26905a8623473 --- /dev/null +++ b/nixos/maintainers/scripts/azure-new/.gitignore @@ -0,0 +1 @@ +azure \ No newline at end of file diff --git a/nixos/maintainers/scripts/azure-new/README.md b/nixos/maintainers/scripts/azure-new/README.md new file mode 100644 index 0000000000000..20e81c44ce5db --- /dev/null +++ b/nixos/maintainers/scripts/azure-new/README.md @@ -0,0 +1,42 @@ +# azure + +## Demo + +Here's a demo of this being used: https://asciinema.org/a/euXb9dIeUybE3VkstLWLbvhmp + +## Usage + +This is meant to be an example image that you can copy into your own +project and modify to your own needs. Notice that the example image +includes a built-in test user account, which by default uses your +`~/.ssh/id_ed25519.pub` as an `authorized_key`. + +Build and upload the image +```shell +$ ./upload-image.sh ./examples/basic/image.nix + +... ++ attr=azbasic ++ nix-build ./examples/basic/image.nix --out-link azure +/nix/store/qdpzknpskzw30vba92mb24xzll1dqsmd-azure-image +... +95.5 %, 0 Done, 0 Failed, 1 Pending, 0 Skipped, 1 Total, 2-sec Throughput (Mb/s): 932.9565 +... +/subscriptions/aff271ee-e9be-4441-b9bb-42f5af4cbaeb/resourceGroups/nixos-images/providers/Microsoft.Compute/images/azure-image-todo-makethisbetter +``` + +Take the output, boot an Azure VM: + +``` +img="/subscriptions/.../..." # use output from last command +./boot-vm.sh "${img}" +... +=> booted +``` + +## Future Work + +1. If the user specifies a hard-coded user, then the agent could be removed. + Probably has security benefits; definitely has closure-size benefits. + (It's likely the VM will need to be booted with a special flag. See: + https://github.com/Azure/azure-cli/issues/12775 for details.) diff --git a/nixos/maintainers/scripts/azure-new/boot-vm.sh b/nixos/maintainers/scripts/azure-new/boot-vm.sh new file mode 100755 index 0000000000000..1ce3a5f9db1e7 --- /dev/null +++ b/nixos/maintainers/scripts/azure-new/boot-vm.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail +set -x + +image="${1}" +location="westus2" +group="nixos-test-vm" +vm_size="Standard_D2s_v3"; os_size=42; + +# ensure group +az group create --location "westus2" --name "${group}" +group_id="$(az group show --name "${group}" -o tsv --query "[id]")" + +# (optional) identity +if ! az identity show -n "${group}-identity" -g "${group}" &>/dev/stderr; then + az identity create --name "${group}-identity" --resource-group "${group}" +fi + +# (optional) role assignment, to the resource group, bad but not really great alternatives +identity_id="$(az identity show --name "${group}-identity" --resource-group "${group}" -o tsv --query "[id]")" +principal_id="$(az identity show --name "${group}-identity" --resource-group "${group}" -o tsv --query "[principalId]")" +until az role assignment create --assignee "${principal_id}" --role "Owner" --scope "${group_id}"; do sleep 1; done + +# boot vm +az vm create \ + --name "${group}-vm" \ + --resource-group "${group}" \ + --assign-identity "${identity_id}" \ + --size "${vm_size}" \ + --os-disk-size-gb "${os_size}" \ + --image "${image}" \ + --admin-username "${USER}" \ + --location "westus2" \ + --storage-sku "Premium_LRS" \ + --ssh-key-values "$(ssh-add -L)" + diff --git a/nixos/maintainers/scripts/azure-new/common.sh b/nixos/maintainers/scripts/azure-new/common.sh new file mode 100644 index 0000000000000..eb87c3e065019 --- /dev/null +++ b/nixos/maintainers/scripts/azure-new/common.sh @@ -0,0 +1,7 @@ +export group="${AZURE_RESOURCE_GROUP:-"azure"}" +export location="${AZURE_LOCATION:-"westus2"}" + +img_file=$(echo azure/*.vhd) +img_name="$(basename "${img_file}")" +img_name="${img_name%".vhd"}" +export img_name="${img_name//[._]/-}" diff --git a/nixos/maintainers/scripts/azure-new/examples/basic/image.nix b/nixos/maintainers/scripts/azure-new/examples/basic/image.nix new file mode 100644 index 0000000000000..74b1281515879 --- /dev/null +++ b/nixos/maintainers/scripts/azure-new/examples/basic/image.nix @@ -0,0 +1,10 @@ +let + pkgs = (import <nixpkgs> {}); + machine = import "${pkgs.path}/nixos/lib/eval-config.nix" { + system = "x86_64-linux"; + modules = [ + ({config, ...}: { imports = [ ./system.nix ]; }) + ]; + }; +in + machine.config.system.build.azureImage diff --git a/nixos/maintainers/scripts/azure-new/examples/basic/system.nix b/nixos/maintainers/scripts/azure-new/examples/basic/system.nix new file mode 100644 index 0000000000000..855bd3bab7193 --- /dev/null +++ b/nixos/maintainers/scripts/azure-new/examples/basic/system.nix @@ -0,0 +1,34 @@ +{ pkgs, modulesPath, ... }: + +let username = "azurenixosuser"; +in +{ + imports = [ + "${modulesPath}/virtualisation/azure-common.nix" + "${modulesPath}/virtualisation/azure-image.nix" + ]; + + ## NOTE: This is just an example of how to hard-code a user. + ## The normal Azure agent IS included and DOES provision a user based + ## on the information passed at VM creation time. + users.users."${username}" = { + isNormalUser = true; + home = "/home/${username}"; + description = "Azure NixOS Test User"; + openssh.authorizedKeys.keys = [ (builtins.readFile ~/.ssh/id_ed25519.pub) ]; + }; + nix.trustedUsers = [ username ]; + + virtualisation.azureImage.diskSize = 2500; + + system.stateVersion = "20.03"; + boot.kernelPackages = pkgs.linuxPackages_latest; + + # test user doesn't have a password + services.openssh.passwordAuthentication = false; + security.sudo.wheelNeedsPassword = false; + + environment.systemPackages = with pkgs; [ + git file htop wget curl + ]; +} diff --git a/nixos/maintainers/scripts/azure-new/shell.nix b/nixos/maintainers/scripts/azure-new/shell.nix new file mode 100644 index 0000000000000..592f1bf9056e5 --- /dev/null +++ b/nixos/maintainers/scripts/azure-new/shell.nix @@ -0,0 +1,13 @@ +with (import ../../../../default.nix {}); +stdenv.mkDerivation { + name = "nixcfg-azure-devenv"; + + nativeBuildInputs = [ + azure-cli + bash + cacert + azure-storage-azcopy + ]; + + AZURE_CONFIG_DIR="/tmp/azure-cli/.azure"; +} diff --git a/nixos/maintainers/scripts/azure-new/upload-image.sh b/nixos/maintainers/scripts/azure-new/upload-image.sh new file mode 100755 index 0000000000000..1466dcd1f0a2e --- /dev/null +++ b/nixos/maintainers/scripts/azure-new/upload-image.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +set -euo pipefail +set -x + +image_nix="${1:-"./examples/basic/image.nix"}" + +nix-build "${image_nix}" --out-link "azure" + +group="nixos-images" +location="westus2" +img_name="nixos-image" +img_file="$(readlink -f ./azure/disk.vhd)" + +if ! az group show -n "${group}" &>/dev/null; then + az group create --name "${group}" --location "${location}" +fi + +# note: the disk access token song/dance is tedious +# but allows us to upload direct to a disk image +# thereby avoid storage accounts (and naming them) entirely! +if ! az disk show -g "${group}" -n "${img_name}" &>/dev/null; then + bytes="$(stat -c %s ${img_file})" + size="30" + az disk create \ + --resource-group "${group}" \ + --name "${img_name}" \ + --for-upload true --upload-size-bytes "${bytes}" + + timeout=$(( 60 * 60 )) # disk access token timeout + sasurl="$(\ + az disk grant-access \ + --access-level Write \ + --resource-group "${group}" \ + --name "${img_name}" \ + --duration-in-seconds ${timeout} \ + | jq -r '.accessSas' + )" + + azcopy copy "${img_file}" "${sasurl}" \ + --blob-type PageBlob + + az disk revoke-access \ + --resource-group "${group}" \ + --name "${img_name}" +fi + +if ! az image show -g "${group}" -n "${img_name}" &>/dev/null; then + diskid="$(az disk show -g "${group}" -n "${img_name}" -o json | jq -r .id)" + + az image create \ + --resource-group "${group}" \ + --name "${img_name}" \ + --source "${diskid}" \ + --os-type "linux" >/dev/null +fi + +imageid="$(az image show -g "${group}" -n "${img_name}" -o json | jq -r .id)" +echo "${imageid}" diff --git a/nixos/maintainers/scripts/azure/create-azure.sh b/nixos/maintainers/scripts/azure/create-azure.sh index 2b22cb5366190..0558f8dfffcb8 100755 --- a/nixos/maintainers/scripts/azure/create-azure.sh +++ b/nixos/maintainers/scripts/azure/create-azure.sh @@ -1,6 +1,6 @@ -#! /bin/sh -e +#! /bin/sh -eu -export NIX_PATH=nixpkgs=../../../.. +export NIX_PATH=nixpkgs=$(dirname $(readlink -f $0))/../../../.. export NIXOS_CONFIG=$(dirname $(readlink -f $0))/../../../modules/virtualisation/azure-image.nix export TIMESTAMP=$(date +%Y%m%d%H%M) diff --git a/nixos/maintainers/scripts/ec2/amazon-image.nix b/nixos/maintainers/scripts/ec2/amazon-image.nix index 31e15537179a9..36f3e7af873d2 100644 --- a/nixos/maintainers/scripts/ec2/amazon-image.nix +++ b/nixos/maintainers/scripts/ec2/amazon-image.nix @@ -8,10 +8,15 @@ in { imports = [ ../../../modules/virtualisation/amazon-image.nix ]; - # Required to provide good EBS experience, + # Amazon recomments setting this to the highest possible value for a good EBS + # experience, which prior to 4.15 was 255. # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nvme-ebs-volumes.html#timeout-nvme-ebs-volumes - # TODO change value to 4294967295 when kernel is updated to 4.15 or later - config.boot.kernelParams = [ "nvme_core.io_timeout=255" ]; + config.boot.kernelParams = + let timeout = + if pkgs.lib.versionAtLeast config.boot.kernelPackages.kernel.version "4.15" + then "4294967295" + else "255"; + in [ "nvme_core.io_timeout=${timeout}" ]; options.amazonImage = { name = mkOption { diff --git a/nixos/maintainers/scripts/ec2/create-amis.sh b/nixos/maintainers/scripts/ec2/create-amis.sh index 5dc1c5aaed57d..145eb49ced7a3 100755 --- a/nixos/maintainers/scripts/ec2/create-amis.sh +++ b/nixos/maintainers/scripts/ec2/create-amis.sh @@ -18,7 +18,7 @@ state_dir=$HOME/amis/ec2-images home_region=eu-west-1 bucket=nixos-amis -regions=(eu-west-1 eu-west-2 eu-west-3 eu-central-1 +regions=(eu-west-1 eu-west-2 eu-west-3 eu-central-1 eu-north-1 us-east-1 us-east-2 us-west-1 us-west-2 ca-central-1 ap-southeast-1 ap-southeast-2 ap-northeast-1 ap-northeast-2 diff --git a/nixos/modules/config/console.nix b/nixos/modules/config/console.nix new file mode 100644 index 0000000000000..f662ed62d31dc --- /dev/null +++ b/nixos/modules/config/console.nix @@ -0,0 +1,203 @@ + +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.console; + + makeColor = i: concatMapStringsSep "," (x: "0x" + substring (2*i) 2 x); + + isUnicode = hasSuffix "UTF-8" (toUpper config.i18n.defaultLocale); + + optimizedKeymap = pkgs.runCommand "keymap" { + nativeBuildInputs = [ pkgs.buildPackages.kbd ]; + LOADKEYS_KEYMAP_PATH = "${consoleEnv}/share/keymaps/**"; + preferLocalBuild = true; + } '' + loadkeys -b ${optionalString isUnicode "-u"} "${cfg.keyMap}" > $out + ''; + + # Sadly, systemd-vconsole-setup doesn't support binary keymaps. + vconsoleConf = pkgs.writeText "vconsole.conf" '' + KEYMAP=${cfg.keyMap} + FONT=${cfg.font} + ''; + + consoleEnv = pkgs.buildEnv { + name = "console-env"; + paths = [ pkgs.kbd ] ++ cfg.packages; + pathsToLink = [ + "/share/consolefonts" + "/share/consoletrans" + "/share/keymaps" + "/share/unimaps" + ]; + }; + + setVconsole = !config.boot.isContainer; +in + +{ + ###### interface + + options.console = { + font = mkOption { + type = types.str; + default = "Lat2-Terminus16"; + example = "LatArCyrHeb-16"; + description = '' + The font used for the virtual consoles. Leave empty to use + whatever the <command>setfont</command> program considers the + default font. + ''; + }; + + keyMap = mkOption { + type = with types; either str path; + default = "us"; + example = "fr"; + description = '' + The keyboard mapping table for the virtual consoles. + ''; + }; + + colors = mkOption { + type = types.listOf types.str; + default = []; + example = [ + "002b36" "dc322f" "859900" "b58900" + "268bd2" "d33682" "2aa198" "eee8d5" + "002b36" "cb4b16" "586e75" "657b83" + "839496" "6c71c4" "93a1a1" "fdf6e3" + ]; + description = '' + The 16 colors palette used by the virtual consoles. + Leave empty to use the default colors. + Colors must be in hexadecimal format and listed in + order from color 0 to color 15. + ''; + + }; + + packages = mkOption { + type = types.listOf types.package; + default = with pkgs.kbdKeymaps; [ dvp neo ]; + defaultText = ''with pkgs.kbdKeymaps; [ dvp neo ]''; + description = '' + List of additional packages that provide console fonts, keymaps and + other resources for virtual consoles use. + ''; + }; + + extraTTYs = mkOption { + default = []; + type = types.listOf types.str; + example = ["tty8" "tty9"]; + description = '' + TTY (virtual console) devices, in addition to the consoles on + which mingetty and syslogd run, that must be initialised. + Only useful if you have some program that you want to run on + some fixed console. For example, the NixOS installation CD + opens the manual in a web browser on console 7, so it sets + <option>console.extraTTYs</option> to <literal>["tty7"]</literal>. + ''; + }; + + useXkbConfig = mkOption { + type = types.bool; + default = false; + description = '' + If set, configure the virtual console keymap from the xserver + keyboard settings. + ''; + }; + + earlySetup = mkOption { + default = false; + type = types.bool; + description = '' + Enable setting virtual console options as early as possible (in initrd). + ''; + }; + + }; + + + ###### implementation + + config = mkMerge [ + { console.keyMap = with config.services.xserver; + mkIf cfg.useXkbConfig + (pkgs.runCommand "xkb-console-keymap" { preferLocalBuild = true; } '' + '${pkgs.ckbcomp}/bin/ckbcomp' -model '${xkbModel}' -layout '${layout}' \ + -option '${xkbOptions}' -variant '${xkbVariant}' > "$out" + ''); + } + + (mkIf (!setVconsole) { + systemd.services.systemd-vconsole-setup.enable = false; + }) + + (mkIf setVconsole (mkMerge [ + { environment.systemPackages = [ pkgs.kbd ]; + + # Let systemd-vconsole-setup.service do the work of setting up the + # virtual consoles. + environment.etc."vconsole.conf".source = vconsoleConf; + # Provide kbd with additional packages. + environment.etc.kbd.source = "${consoleEnv}/share"; + + boot.initrd.preLVMCommands = mkBefore '' + kbd_mode ${if isUnicode then "-u" else "-a"} -C /dev/console + printf "\033%%${if isUnicode then "G" else "@"}" >> /dev/console + loadkmap < ${optimizedKeymap} + + ${optionalString cfg.earlySetup '' + setfont -C /dev/console $extraUtils/share/consolefonts/font.psf + ''} + ''; + + systemd.services.systemd-vconsole-setup = + { before = [ "display-manager.service" ]; + after = [ "systemd-udev-settle.service" ]; + restartTriggers = [ vconsoleConf consoleEnv ]; + }; + } + + (mkIf (cfg.colors != []) { + boot.kernelParams = [ + "vt.default_red=${makeColor 0 cfg.colors}" + "vt.default_grn=${makeColor 1 cfg.colors}" + "vt.default_blu=${makeColor 2 cfg.colors}" + ]; + }) + + (mkIf cfg.earlySetup { + boot.initrd.extraUtilsCommands = '' + mkdir -p $out/share/consolefonts + ${if substring 0 1 cfg.font == "/" then '' + font="${cfg.font}" + '' else '' + font="$(echo ${consoleEnv}/share/consolefonts/${cfg.font}.*)" + ''} + if [[ $font == *.gz ]]; then + gzip -cd $font > $out/share/consolefonts/font.psf + else + cp -L $font $out/share/consolefonts/font.psf + fi + ''; + }) + ])) + ]; + + imports = [ + (mkRenamedOptionModule [ "i18n" "consoleFont" ] [ "console" "font" ]) + (mkRenamedOptionModule [ "i18n" "consoleKeyMap" ] [ "console" "keyMap" ]) + (mkRenamedOptionModule [ "i18n" "consoleColors" ] [ "console" "colors" ]) + (mkRenamedOptionModule [ "i18n" "consolePackages" ] [ "console" "packages" ]) + (mkRenamedOptionModule [ "i18n" "consoleUseXkbConfig" ] [ "console" "useXkbConfig" ]) + (mkRenamedOptionModule [ "boot" "earlyVconsoleSetup" ] [ "console" "earlySetup" ]) + (mkRenamedOptionModule [ "boot" "extraTTYs" ] [ "console" "extraTTYs" ]) + ]; +} diff --git a/nixos/modules/config/fonts/fontconfig.nix b/nixos/modules/config/fonts/fontconfig.nix index 8f227c423266b..6ac64b0ec9c6c 100644 --- a/nixos/modules/config/fonts/fontconfig.nix +++ b/nixos/modules/config/fonts/fontconfig.nix @@ -45,6 +45,9 @@ let # generate the font cache setting file for a fontconfig version # use latest when no version is passed + # When cross-compiling, we can’t generate the cache, so we skip the + # <cachedir> part. fontconfig still works but is a little slower in + # looking things up. makeCacheConf = { version ? null }: let fcPackage = if version == null @@ -60,11 +63,13 @@ let <fontconfig> <!-- Font directories --> ${concatStringsSep "\n" (map (font: "<dir>${font}</dir>") config.fonts.fonts)} + ${optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) '' <!-- Pre-generated font caches --> <cachedir>${cache}</cachedir> ${optionalString (pkgs.stdenv.isx86_64 && cfg.cache32Bit) '' <cachedir>${cache32}</cachedir> ''} + ''} </fontconfig> ''; @@ -264,6 +269,16 @@ let }; in { + imports = [ + (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "allowBitmaps" ] [ "fonts" "fontconfig" "allowBitmaps" ]) + (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "allowType1" ] [ "fonts" "fontconfig" "allowType1" ]) + (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "useEmbeddedBitmaps" ] [ "fonts" "fontconfig" "useEmbeddedBitmaps" ]) + (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "forceAutohint" ] [ "fonts" "fontconfig" "forceAutohint" ]) + (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "renderMonoTTFAsBitmap" ] [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ]) + (mkRemovedOptionModule [ "fonts" "fontconfig" "hinting" "style" ] "") + (mkRemovedOptionModule [ "fonts" "fontconfig" "forceAutohint" ] "") + (mkRemovedOptionModule [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ] "") + ]; options = { diff --git a/nixos/modules/config/fonts/fonts.nix b/nixos/modules/config/fonts/fonts.nix index abb806b601a76..b9bae44b2f9c1 100644 --- a/nixos/modules/config/fonts/fonts.nix +++ b/nixos/modules/config/fonts/fonts.nix @@ -3,6 +3,9 @@ with lib; { + imports = [ + (mkRemovedOptionModule [ "fonts" "enableCoreFonts" ] "Use fonts.fonts = [ pkgs.corefonts ]; instead.") + ]; options = { diff --git a/nixos/modules/config/gtk/gtk-icon-cache.nix b/nixos/modules/config/gtk/gtk-icon-cache.nix index 86a6bfb5af41a..7441f4de40eb8 100644 --- a/nixos/modules/config/gtk/gtk-icon-cache.nix +++ b/nixos/modules/config/gtk/gtk-icon-cache.nix @@ -77,7 +77,7 @@ with lib; if [ -w "$themedir" ]; then rm -f "$themedir"/icon-theme.cache - ${pkgs.gtk3.out}/bin/gtk-update-icon-cache --ignore-theme-index "$themedir" + ${pkgs.buildPackages.gtk3.out}/bin/gtk-update-icon-cache --ignore-theme-index "$themedir" fi done ''; diff --git a/nixos/modules/config/i18n.nix b/nixos/modules/config/i18n.nix index d0db8fedecd80..cc2ddda9d32fc 100644 --- a/nixos/modules/config/i18n.nix +++ b/nixos/modules/config/i18n.nix @@ -58,62 +58,6 @@ with lib; ''; }; - consolePackages = mkOption { - type = types.listOf types.package; - default = with pkgs.kbdKeymaps; [ dvp neo ]; - defaultText = ''with pkgs.kbdKeymaps; [ dvp neo ]''; - description = '' - List of additional packages that provide console fonts, keymaps and - other resources. - ''; - }; - - consoleFont = mkOption { - type = types.str; - default = "Lat2-Terminus16"; - example = "LatArCyrHeb-16"; - description = '' - The font used for the virtual consoles. Leave empty to use - whatever the <command>setfont</command> program considers the - default font. - ''; - }; - - consoleUseXkbConfig = mkOption { - type = types.bool; - default = false; - description = '' - If set, configure the console keymap from the xserver keyboard - settings. - ''; - }; - - consoleKeyMap = mkOption { - type = with types; either str path; - default = "us"; - example = "fr"; - description = '' - The keyboard mapping table for the virtual consoles. - ''; - }; - - consoleColors = mkOption { - type = types.listOf types.str; - default = []; - example = [ - "002b36" "dc322f" "859900" "b58900" - "268bd2" "d33682" "2aa198" "eee8d5" - "002b36" "cb4b16" "586e75" "657b83" - "839496" "6c71c4" "93a1a1" "fdf6e3" - ]; - description = '' - The 16 colors palette used by the virtual consoles. - Leave empty to use the default colors. - Colors must be in hexadecimal format and listed in - order from color 0 to color 15. - ''; - }; - }; }; @@ -123,13 +67,6 @@ with lib; config = { - i18n.consoleKeyMap = with config.services.xserver; - mkIf config.i18n.consoleUseXkbConfig - (pkgs.runCommand "xkb-console-keymap" { preferLocalBuild = true; } '' - '${pkgs.ckbcomp}/bin/ckbcomp' -model '${xkbModel}' -layout '${layout}' \ - -option '${xkbOptions}' -variant '${xkbVariant}' > "$out" - ''); - environment.systemPackages = optional (config.i18n.supportedLocales != []) config.i18n.glibcLocales; @@ -143,14 +80,11 @@ with lib; }; # ‘/etc/locale.conf’ is used by systemd. - environment.etc = singleton - { target = "locale.conf"; - source = pkgs.writeText "locale.conf" - '' - LANG=${config.i18n.defaultLocale} - ${concatStringsSep "\n" (mapAttrsToList (n: v: ''${n}=${v}'') config.i18n.extraLocaleSettings)} - ''; - }; + environment.etc."locale.conf".source = pkgs.writeText "locale.conf" + '' + LANG=${config.i18n.defaultLocale} + ${concatStringsSep "\n" (mapAttrsToList (n: v: ''${n}=${v}'') config.i18n.extraLocaleSettings)} + ''; }; } diff --git a/nixos/modules/config/ldap.nix b/nixos/modules/config/ldap.nix index e008497a2a6e0..b554f197dc4ba 100644 --- a/nixos/modules/config/ldap.nix +++ b/nixos/modules/config/ldap.nix @@ -28,8 +28,6 @@ let }; nslcdConfig = writeText "nslcd.conf" '' - uid nslcd - gid nslcd uri ${cfg.server} base ${cfg.base} timelimit ${toString cfg.timeLimit} @@ -224,7 +222,9 @@ in config = mkIf cfg.enable { - environment.etc = optional (!cfg.daemon.enable) ldapConfig; + environment.etc = optionalAttrs (!cfg.daemon.enable) { + "ldap.conf" = ldapConfig; + }; system.activationScripts = mkIf (!cfg.daemon.enable) { ldap = stringAfter [ "etc" "groups" "users" ] '' @@ -280,6 +280,7 @@ in Group = "nslcd"; RuntimeDirectory = [ "nslcd" ]; PIDFile = "/run/nslcd/nslcd.pid"; + AmbientCapabilities = "CAP_SYS_RESOURCE"; }; }; diff --git a/nixos/modules/config/networking.nix b/nixos/modules/config/networking.nix index a89667ea221cc..03944de82497a 100644 --- a/nixos/modules/config/networking.nix +++ b/nixos/modules/config/networking.nix @@ -16,6 +16,9 @@ let in { + imports = [ + (mkRemovedOptionModule [ "networking" "hostConf" ] "Use environment.etc.\"host.conf\" instead.") + ]; options = { @@ -32,25 +35,22 @@ in ''; }; - networking.extraHosts = lib.mkOption { - type = types.lines; - default = ""; - example = "192.168.0.1 lanlocalhost"; + networking.hostFiles = lib.mkOption { + type = types.listOf types.path; + defaultText = lib.literalExample "Hosts from `networking.hosts` and `networking.extraHosts`"; + example = lib.literalExample ''[ "''${pkgs.my-blocklist-package}/share/my-blocklist/hosts" ]''; description = '' - Additional verbatim entries to be appended to <filename>/etc/hosts</filename>. + Files that should be concatenated together to form <filename>/etc/hosts</filename>. ''; }; - networking.hostConf = lib.mkOption { + networking.extraHosts = lib.mkOption { type = types.lines; - default = "multi on"; - example = '' - multi on - reorder on - trim lan - ''; + default = ""; + example = "192.168.0.1 lanlocalhost"; description = '' - The contents of <filename>/etc/host.conf</filename>. See also <citerefentry><refentrytitle>host.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>. + Additional verbatim entries to be appended to <filename>/etc/hosts</filename>. + For adding hosts from derivation results, use <option>networking.hostFiles</option> instead. ''; }; @@ -169,6 +169,15 @@ in "::1" = [ "localhost" ]; }; + networking.hostFiles = let + stringHosts = + let + oneToString = set: ip: ip + " " + concatStringsSep " " set.${ip} + "\n"; + allToString = set: concatMapStrings (oneToString set) (attrNames set); + in pkgs.writeText "string-hosts" (allToString (filterAttrs (_: v: v != []) cfg.hosts)); + extraHosts = pkgs.writeText "extra-hosts" cfg.extraHosts; + in mkBefore [ stringHosts extraHosts ]; + environment.etc = { # /etc/services: TCP/UDP port assignments. services.source = pkgs.iana-etc + "/etc/services"; @@ -177,20 +186,18 @@ in protocols.source = pkgs.iana-etc + "/etc/protocols"; # /etc/hosts: Hostname-to-IP mappings. - hosts.text = let - oneToString = set: ip: ip + " " + concatStringsSep " " set.${ip}; - allToString = set: concatMapStringsSep "\n" (oneToString set) (attrNames set); - in '' - ${allToString (filterAttrs (_: v: v != []) cfg.hosts)} - ${cfg.extraHosts} + hosts.source = pkgs.runCommandNoCC "hosts" {} '' + cat ${escapeShellArgs cfg.hostFiles} > $out ''; # /etc/host.conf: resolver configuration file - "host.conf".text = cfg.hostConf; + "host.conf".text = '' + multi on + ''; } // optionalAttrs (pkgs.stdenv.hostPlatform.libc == "glibc") { # /etc/rpc: RPC program numbers. - rpc.source = pkgs.glibc.out + "/etc/rpc"; + rpc.source = pkgs.stdenv.cc.libc.out + "/etc/rpc"; }; networking.proxy.envVars = diff --git a/nixos/modules/config/pulseaudio.nix b/nixos/modules/config/pulseaudio.nix index 9baad9b585455..408d0a9c33f27 100644 --- a/nixos/modules/config/pulseaudio.nix +++ b/nixos/modules/config/pulseaudio.nix @@ -215,9 +215,8 @@ in { config = mkMerge [ { - environment.etc = singleton { - target = "pulse/client.conf"; - source = clientConf; + environment.etc = { + "pulse/client.conf".source = clientConf; }; hardware.pulseaudio.configFile = mkDefault "${getBin overriddenPackage}/etc/pulse/default.pa"; @@ -228,19 +227,16 @@ in { sound.enable = true; - environment.etc = [ - { target = "asound.conf"; - source = alsaConf; } + environment.etc = { + "asound.conf".source = alsaConf; - { target = "pulse/daemon.conf"; - source = writeText "daemon.conf" (lib.generators.toKeyValue {} cfg.daemon.config); } + "pulse/daemon.conf".source = writeText "daemon.conf" + (lib.generators.toKeyValue {} cfg.daemon.config); - { target = "openal/alsoft.conf"; - source = writeText "alsoft.conf" "drivers=pulse"; } + "openal/alsoft.conf".source = writeText "alsoft.conf" "drivers=pulse"; - { target = "libao.conf"; - source = writeText "libao.conf" "default_driver=pulse"; } - ]; + "libao.conf".source = writeText "libao.conf" "default_driver=pulse"; + }; # Disable flat volumes to enable relative ones hardware.pulseaudio.daemon.config.flat-volumes = mkDefault "no"; @@ -252,6 +248,9 @@ in { security.rtkit.enable = true; systemd.packages = [ overriddenPackage ]; + + # PulseAudio is packaged with udev rules to handle various audio device quirks + services.udev.packages = [ overriddenPackage ]; }) (mkIf (cfg.extraModules != []) { @@ -275,9 +274,8 @@ in { }) (mkIf nonSystemWide { - environment.etc = singleton { - target = "pulse/default.pa"; - source = myConfigFile; + environment.etc = { + "pulse/default.pa".source = myConfigFile; }; systemd.user = { services.pulseaudio = { diff --git a/nixos/modules/config/qt5.nix b/nixos/modules/config/qt5.nix index d9dec74f15525..eabba9ad95f00 100644 --- a/nixos/modules/config/qt5.nix +++ b/nixos/modules/config/qt5.nix @@ -6,8 +6,8 @@ let cfg = config.qt5; - isQGnome = cfg.platformTheme == "gnome" && cfg.style == "adwaita"; - isQtStyle = cfg.platformTheme == "gtk2" && cfg.style != "adwaita"; + isQGnome = cfg.platformTheme == "gnome" && builtins.elem cfg.style ["adwaita" "adwaita-dark"]; + isQtStyle = cfg.platformTheme == "gtk2" && !(builtins.elem cfg.style ["adwaita" "adwaita-dark"]); packages = if isQGnome then [ pkgs.qgnomeplatform pkgs.adwaita-qt ] else if isQtStyle then [ pkgs.libsForQt5.qtstyleplugins ] @@ -55,6 +55,7 @@ in style = mkOption { type = types.enum [ "adwaita" + "adwaita-dark" "cleanlooks" "gtk2" "motif" @@ -71,6 +72,7 @@ in <variablelist> <varlistentry> <term><literal>adwaita</literal></term> + <term><literal>adwaita-dark</literal></term> <listitem><para>Use Adwaita Qt style with <link xlink:href="https://github.com/FedoraQt/adwaita-qt">adwaita</link> </para></listitem> diff --git a/nixos/modules/config/resolvconf.nix b/nixos/modules/config/resolvconf.nix index 406c6a7ac3296..cc202bca6c4e4 100644 --- a/nixos/modules/config/resolvconf.nix +++ b/nixos/modules/config/resolvconf.nix @@ -33,6 +33,13 @@ let in { + imports = [ + (mkRenamedOptionModule [ "networking" "dnsSingleRequest" ] [ "networking" "resolvconf" "dnsSingleRequest" ]) + (mkRenamedOptionModule [ "networking" "dnsExtensionMechanism" ] [ "networking" "resolvconf" "dnsExtensionMechanism" ]) + (mkRenamedOptionModule [ "networking" "extraResolvconfConf" ] [ "networking" "resolvconf" "extraConfig" ]) + (mkRenamedOptionModule [ "networking" "resolvconfOptions" ] [ "networking" "resolvconf" "extraOptions" ]) + (mkRemovedOptionModule [ "networking" "resolvconf" "useHostResolvConf" ] "This option was never used for anything anyways") + ]; options = { @@ -47,15 +54,6 @@ in ''; }; - useHostResolvConf = mkOption { - type = types.bool; - default = false; - description = '' - In containers, whether to use the - <filename>resolv.conf</filename> supplied by the host. - ''; - }; - dnsSingleRequest = lib.mkOption { type = types.bool; default = false; diff --git a/nixos/modules/config/swap.nix b/nixos/modules/config/swap.nix index fed3fa3bc7c88..adb4e2294213d 100644 --- a/nixos/modules/config/swap.nix +++ b/nixos/modules/config/swap.nix @@ -58,7 +58,7 @@ let device = mkOption { example = "/dev/sda3"; type = types.str; - description = "Path of the device."; + description = "Path of the device or swap file."; }; label = mkOption { @@ -185,6 +185,8 @@ in { description = "Initialisation of swap device ${sw.device}"; wantedBy = [ "${realDevice'}.swap" ]; before = [ "${realDevice'}.swap" ]; + # If swap is encrypted, depending on rngd resolves a possible entropy starvation during boot + after = mkIf (config.security.rngd.enable && sw.randomEncryption.enable) [ "rngd.service" ]; path = [ pkgs.utillinux ] ++ optional sw.randomEncryption.enable pkgs.cryptsetup; script = diff --git a/nixos/modules/config/system-path.nix b/nixos/modules/config/system-path.nix index aba9bc0945b19..4100ec897016d 100644 --- a/nixos/modules/config/system-path.nix +++ b/nixos/modules/config/system-path.nix @@ -116,6 +116,7 @@ in "/lib" # FIXME: remove and update debug-info.nix "/sbin" "/share/emacs" + "/share/hunspell" "/share/nano" "/share/org" "/share/themes" diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix index ae3bdeb00e645..141e43fec39b2 100644 --- a/nixos/modules/config/users-groups.nix +++ b/nixos/modules/config/users-groups.nix @@ -403,6 +403,10 @@ let filter types.shellPackage.check shells; in { + imports = [ + (mkAliasOptionModule [ "users" "extraUsers" ] [ "users" "users" ]) + (mkAliasOptionModule [ "users" "extraGroups" ] [ "users" "groups" ]) + ]; ###### interface diff --git a/nixos/modules/config/vte.nix b/nixos/modules/config/vte.nix index d4a8c926fef2e..24d32a00fd458 100644 --- a/nixos/modules/config/vte.nix +++ b/nixos/modules/config/vte.nix @@ -16,6 +16,10 @@ in { + meta = { + maintainers = teams.gnome.members; + }; + options = { programs.bash.vteIntegration = mkOption { diff --git a/nixos/modules/config/xdg/autostart.nix b/nixos/modules/config/xdg/autostart.nix index 0ee94fed818b1..40984cb5ec53f 100644 --- a/nixos/modules/config/xdg/autostart.nix +++ b/nixos/modules/config/xdg/autostart.nix @@ -2,19 +2,23 @@ with lib; { + meta = { + maintainers = teams.freedesktop.members; + }; + options = { xdg.autostart.enable = mkOption { type = types.bool; default = true; description = '' - Whether to install files to support the + Whether to install files to support the <link xlink:href="https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html">XDG Autostart specification</link>. ''; }; }; config = mkIf config.xdg.autostart.enable { - environment.pathsToLink = [ + environment.pathsToLink = [ "/etc/xdg/autostart" ]; }; diff --git a/nixos/modules/config/xdg/icons.nix b/nixos/modules/config/xdg/icons.nix index 4677ce090b0bd..c83fdc251ef00 100644 --- a/nixos/modules/config/xdg/icons.nix +++ b/nixos/modules/config/xdg/icons.nix @@ -2,6 +2,10 @@ with lib; { + meta = { + maintainers = teams.freedesktop.members; + }; + options = { xdg.icons.enable = mkOption { type = types.bool; diff --git a/nixos/modules/config/xdg/menus.nix b/nixos/modules/config/xdg/menus.nix index c172692df5d7d..6735a7a5c430f 100644 --- a/nixos/modules/config/xdg/menus.nix +++ b/nixos/modules/config/xdg/menus.nix @@ -2,19 +2,23 @@ with lib; { + meta = { + maintainers = teams.freedesktop.members; + }; + options = { xdg.menus.enable = mkOption { type = types.bool; default = true; description = '' - Whether to install files to support the + Whether to install files to support the <link xlink:href="https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html">XDG Desktop Menu specification</link>. ''; }; }; config = mkIf config.xdg.menus.enable { - environment.pathsToLink = [ + environment.pathsToLink = [ "/share/applications" "/share/desktop-directories" "/etc/xdg/menus" diff --git a/nixos/modules/config/xdg/mime.nix b/nixos/modules/config/xdg/mime.nix index a5374c2b468da..4cdb3f30994b3 100644 --- a/nixos/modules/config/xdg/mime.nix +++ b/nixos/modules/config/xdg/mime.nix @@ -2,6 +2,10 @@ with lib; { + meta = { + maintainers = teams.freedesktop.members; + }; + options = { xdg.mime.enable = mkOption { type = types.bool; diff --git a/nixos/modules/config/xdg/portal.nix b/nixos/modules/config/xdg/portal.nix index bdbbfda2bb424..3c7cd729c60a6 100644 --- a/nixos/modules/config/xdg/portal.nix +++ b/nixos/modules/config/xdg/portal.nix @@ -3,6 +3,14 @@ with lib; { + imports = [ + (mkRenamedOptionModule [ "services" "flatpak" "extraPortals" ] [ "xdg" "portal" "extraPortals" ]) + ]; + + meta = { + maintainers = teams.freedesktop.members; + }; + options.xdg.portal = { enable = mkEnableOption "<link xlink:href='https://github.com/flatpak/xdg-desktop-portal'>xdg desktop integration</link>"//{ @@ -38,6 +46,10 @@ with lib; let cfg = config.xdg.portal; packages = [ pkgs.xdg-desktop-portal ] ++ cfg.extraPortals; + joinedPortals = pkgs.symlinkJoin { + name = "xdg-portals"; + paths = cfg.extraPortals; + }; in mkIf cfg.enable { @@ -52,7 +64,7 @@ with lib; environment.variables = { GTK_USE_PORTAL = mkIf cfg.gtkUsePortal "1"; - XDG_DESKTOP_PORTAL_PATH = map (p: "${p}/share/xdg-desktop-portal/portals") cfg.extraPortals; + XDG_DESKTOP_PORTAL_DIR = "${joinedPortals}/share/xdg-desktop-portal/portals"; }; }; } diff --git a/nixos/modules/config/xdg/sounds.nix b/nixos/modules/config/xdg/sounds.nix index 14d6340fc33bc..0b94f550929b4 100644 --- a/nixos/modules/config/xdg/sounds.nix +++ b/nixos/modules/config/xdg/sounds.nix @@ -2,6 +2,10 @@ with lib; { + meta = { + maintainers = teams.freedesktop.members; + }; + options = { xdg.sounds.enable = mkOption { type = types.bool; diff --git a/nixos/modules/hardware/all-firmware.nix b/nixos/modules/hardware/all-firmware.nix index 534fcc34276b4..16be8bcfdd7d9 100644 --- a/nixos/modules/hardware/all-firmware.nix +++ b/nixos/modules/hardware/all-firmware.nix @@ -6,6 +6,14 @@ let cfg = config.hardware; in { + imports = [ + (mkRenamedOptionModule [ "networking" "enableRT73Firmware" ] [ "hardware" "enableRedistributableFirmware" ]) + (mkRenamedOptionModule [ "networking" "enableIntel3945ABGFirmware" ] [ "hardware" "enableRedistributableFirmware" ]) + (mkRenamedOptionModule [ "networking" "enableIntel2100BGFirmware" ] [ "hardware" "enableRedistributableFirmware" ]) + (mkRenamedOptionModule [ "networking" "enableRalinkFirmware" ] [ "hardware" "enableRedistributableFirmware" ]) + (mkRenamedOptionModule [ "networking" "enableRTL8192cFirmware" ] [ "hardware" "enableRedistributableFirmware" ]) + ]; + ###### interface options = { diff --git a/nixos/modules/hardware/brightnessctl.nix b/nixos/modules/hardware/brightnessctl.nix deleted file mode 100644 index 2d54398d10df5..0000000000000 --- a/nixos/modules/hardware/brightnessctl.nix +++ /dev/null @@ -1,31 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; -let - cfg = config.hardware.brightnessctl; -in -{ - - options = { - - hardware.brightnessctl = { - - enable = mkOption { - default = false; - type = types.bool; - description = '' - Enable brightnessctl in userspace. - This will allow brightness control from users in the video group. - ''; - - }; - }; - }; - - - config = mkIf cfg.enable { - services.udev.packages = with pkgs; [ brightnessctl ]; - environment.systemPackages = with pkgs; [ brightnessctl ]; - }; - -} diff --git a/nixos/modules/hardware/ckb-next.nix b/nixos/modules/hardware/ckb-next.nix index 20b2756d8b266..fe0ca9f26d54d 100644 --- a/nixos/modules/hardware/ckb-next.nix +++ b/nixos/modules/hardware/ckb-next.nix @@ -7,6 +7,11 @@ let in { + imports = [ + (mkRenamedOptionModule [ "hardware" "ckb" "enable" ] [ "hardware" "ckb-next" "enable" ]) + (mkRenamedOptionModule [ "hardware" "ckb" "package" ] [ "hardware" "ckb-next" "package" ]) + ]; + options.hardware.ckb-next = { enable = mkEnableOption "the Corsair keyboard/mouse driver"; diff --git a/nixos/modules/hardware/ksm.nix b/nixos/modules/hardware/ksm.nix index 99d46c25236e0..0938dbdc11018 100644 --- a/nixos/modules/hardware/ksm.nix +++ b/nixos/modules/hardware/ksm.nix @@ -6,6 +6,10 @@ let cfg = config.hardware.ksm; in { + imports = [ + (mkRenamedOptionModule [ "hardware" "enableKSM" ] [ "hardware" "ksm" "enable" ]) + ]; + options.hardware.ksm = { enable = mkEnableOption "Kernel Same-Page Merging"; sleep = mkOption { diff --git a/nixos/modules/hardware/opengl.nix b/nixos/modules/hardware/opengl.nix index 57cac56bd8ab1..061528f4b1b53 100644 --- a/nixos/modules/hardware/opengl.nix +++ b/nixos/modules/hardware/opengl.nix @@ -10,14 +10,6 @@ let videoDrivers = config.services.xserver.videoDrivers; - makePackage = p: pkgs.buildEnv { - name = "mesa-drivers+txc-${p.mesa.version}"; - paths = - [ p.mesa.drivers - (if cfg.s3tcSupport then p.libtxc_dxtn else p.libtxc_dxtn_s2tc) - ]; - }; - package = pkgs.buildEnv { name = "opengl-drivers"; paths = [ cfg.package ] ++ cfg.extraPackages; @@ -31,6 +23,14 @@ let in { + + imports = [ + (mkRenamedOptionModule [ "services" "xserver" "vaapiDrivers" ] [ "hardware" "opengl" "extraPackages" ]) + (mkRemovedOptionModule [ "hardware" "opengl" "s3tcSupport" ] '' + S3TC support is now always enabled in Mesa. + '') + ]; + options = { hardware.opengl = { @@ -38,11 +38,11 @@ in description = '' Whether to enable OpenGL drivers. This is needed to enable OpenGL support in X11 systems, as well as for Wayland compositors - like sway, way-cooler and Weston. It is enabled by default + like sway and Weston. It is enabled by default by the corresponding modules, so you do not usually have to set it yourself, only if there is no module for your wayland - compositor of choice. See services.xserver.enable, - programs.sway.enable, and programs.way-cooler.enable. + compositor of choice. See services.xserver.enable and + programs.sway.enable. ''; type = types.bool; default = false; @@ -69,17 +69,6 @@ in ''; }; - s3tcSupport = mkOption { - type = types.bool; - default = false; - description = '' - Make S3TC(S3 Texture Compression) via libtxc_dxtn available - to OpenGL drivers instead of the patent-free S2TC replacement. - - Using this library may require a patent license depending on your location. - ''; - }; - package = mkOption { type = types.package; internal = true; @@ -161,8 +150,8 @@ in environment.sessionVariables.LD_LIBRARY_PATH = mkIf cfg.setLdLibraryPath ([ "/run/opengl-driver/lib" ] ++ optional cfg.driSupport32Bit "/run/opengl-driver-32/lib"); - hardware.opengl.package = mkDefault (makePackage pkgs); - hardware.opengl.package32 = mkDefault (makePackage pkgs.pkgsi686Linux); + hardware.opengl.package = mkDefault pkgs.mesa.drivers; + hardware.opengl.package32 = mkDefault pkgs.pkgsi686Linux.mesa.drivers; boot.extraModulePackages = optional (elem "virtualbox" videoDrivers) kernelPackages.virtualboxGuestAdditions; }; diff --git a/nixos/modules/hardware/openrazer.nix b/nixos/modules/hardware/openrazer.nix index 883db7f2f4f19..b5c3d67441422 100644 --- a/nixos/modules/hardware/openrazer.nix +++ b/nixos/modules/hardware/openrazer.nix @@ -49,7 +49,7 @@ in { options = { hardware.openrazer = { - enable = mkEnableOption "OpenRazer drivers and userspace daemon."; + enable = mkEnableOption "OpenRazer drivers and userspace daemon"; verboseLogging = mkOption { type = types.bool; diff --git a/nixos/modules/hardware/raid/hpsa.nix b/nixos/modules/hardware/raid/hpsa.nix index 4d7af138292cc..c4977e3fd70aa 100644 --- a/nixos/modules/hardware/raid/hpsa.nix +++ b/nixos/modules/hardware/raid/hpsa.nix @@ -34,7 +34,7 @@ let meta = with lib; { description = "HP Smart Array CLI"; - homepage = https://downloads.linux.hpe.com/SDR/downloads/MCP/Ubuntu/pool/non-free/; + homepage = "https://downloads.linux.hpe.com/SDR/downloads/MCP/Ubuntu/pool/non-free/"; license = licenses.unfreeRedistributable; platforms = [ "x86_64-linux" ]; maintainers = with maintainers; [ volth ]; diff --git a/nixos/modules/hardware/sensor/iio.nix b/nixos/modules/hardware/sensor/iio.nix index a8bc188000214..4c359c3b17255 100644 --- a/nixos/modules/hardware/sensor/iio.nix +++ b/nixos/modules/hardware/sensor/iio.nix @@ -8,7 +8,12 @@ with lib; options = { hardware.sensor.iio = { enable = mkOption { - description = "Enable this option to support IIO sensors."; + description = '' + Enable this option to support IIO sensors. + + IIO sensors are used for orientation and ambient light + sensors on some mobile devices. + ''; type = types.bool; default = false; }; diff --git a/nixos/modules/hardware/tuxedo-keyboard.nix b/nixos/modules/hardware/tuxedo-keyboard.nix new file mode 100644 index 0000000000000..898eed2449355 --- /dev/null +++ b/nixos/modules/hardware/tuxedo-keyboard.nix @@ -0,0 +1,35 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.hardware.tuxedo-keyboard; + tuxedo-keyboard = config.boot.kernelPackages.tuxedo-keyboard; +in + { + options.hardware.tuxedo-keyboard = { + enable = mkEnableOption '' + Enables the tuxedo-keyboard driver. + + To configure the driver, pass the options to the <option>boot.kernelParams</option> configuration. + There are several parameters you can change. It's best to check at the source code description which options are supported. + You can find all the supported parameters at: <link xlink:href="https://github.com/tuxedocomputers/tuxedo-keyboard#kernelparam" /> + + In order to use the <literal>custom</literal> lighting with the maximumg brightness and a color of <literal>0xff0a0a</literal> one would put pass <option>boot.kernelParams</option> like this: + + <programlisting> + boot.kernelParams = [ + "tuxedo_keyboard.mode=0" + "tuxedo_keyboard.brightness=255" + "tuxedo_keyboard.color_left=0xff0a0a" + ]; + </programlisting> + ''; + }; + + config = mkIf cfg.enable + { + boot.kernelModules = ["tuxedo_keyboard"]; + boot.extraModulePackages = [ tuxedo-keyboard ]; + }; + } diff --git a/nixos/modules/hardware/uinput.nix b/nixos/modules/hardware/uinput.nix new file mode 100644 index 0000000000000..55e86bfa6bdbf --- /dev/null +++ b/nixos/modules/hardware/uinput.nix @@ -0,0 +1,19 @@ +{ config, pkgs, lib, ... }: + +let + cfg = config.hardware.uinput; +in { + options.hardware.uinput = { + enable = lib.mkEnableOption "uinput support"; + }; + + config = lib.mkIf cfg.enable { + boot.kernelModules = [ "uinput" ]; + + users.groups.uinput = {}; + + services.udev.extraRules = '' + SUBSYSTEM=="misc", KERNEL=="uinput", MODE="0660", GROUP="uinput", OPTIONS+="static_node=uinput" + ''; + }; +} diff --git a/nixos/modules/hardware/usb-wwan.nix b/nixos/modules/hardware/usb-wwan.nix index 2d20421586a79..679a6c6497cb6 100644 --- a/nixos/modules/hardware/usb-wwan.nix +++ b/nixos/modules/hardware/usb-wwan.nix @@ -21,6 +21,19 @@ with lib; ###### implementation config = mkIf config.hardware.usbWwan.enable { + # Attaches device specific handlers. services.udev.packages = with pkgs; [ usb-modeswitch-data ]; + + # Triggered by udev, usb-modeswitch creates systemd services via a + # template unit in the usb-modeswitch package. + systemd.packages = with pkgs; [ usb-modeswitch ]; + + # The systemd service requires the usb-modeswitch-data. The + # usb-modeswitch package intends to discover this via the + # filesystem at /usr/share/usb_modeswitch, and merge it with user + # configuration in /etc/usb_modeswitch.d. Configuring the correct + # path in the package is difficult, as it would cause a cyclic + # dependency. + environment.etc."usb_modeswitch.d".source = "${pkgs.usb-modeswitch-data}/share/usb_modeswitch"; }; } diff --git a/nixos/modules/hardware/video/amdgpu-pro.nix b/nixos/modules/hardware/video/amdgpu-pro.nix index 8e91e9d2baa93..ec1c8c2d57a1a 100644 --- a/nixos/modules/hardware/video/amdgpu-pro.nix +++ b/nixos/modules/hardware/video/amdgpu-pro.nix @@ -30,7 +30,7 @@ in nixpkgs.config.xorg.abiCompat = "1.19"; services.xserver.drivers = singleton - { name = "amdgpu"; modules = [ package ]; }; + { name = "amdgpu"; modules = [ package ]; display = true; }; hardware.opengl.package = package; hardware.opengl.package32 = package32; diff --git a/nixos/modules/hardware/video/ati.nix b/nixos/modules/hardware/video/ati.nix index 0aab7bd6b92c9..06d3ea324d8d5 100644 --- a/nixos/modules/hardware/video/ati.nix +++ b/nixos/modules/hardware/video/ati.nix @@ -21,7 +21,7 @@ in nixpkgs.config.xorg.abiCompat = "1.17"; services.xserver.drivers = singleton - { name = "fglrx"; modules = [ ati_x11 ]; }; + { name = "fglrx"; modules = [ ati_x11 ]; display = true; }; hardware.opengl.package = ati_x11; hardware.opengl.package32 = pkgs.pkgsi686Linux.linuxPackages.ati_drivers_x11.override { libsOnly = true; kernel = null; }; diff --git a/nixos/modules/hardware/video/nvidia.nix b/nixos/modules/hardware/video/nvidia.nix index fcb30187fa2fb..7461e231402a5 100644 --- a/nixos/modules/hardware/video/nvidia.nix +++ b/nixos/modules/hardware/video/nvidia.nix @@ -34,26 +34,57 @@ let enabled = nvidia_x11 != null; cfg = config.hardware.nvidia; - optimusCfg = cfg.optimus_prime; + pCfg = cfg.prime; + syncCfg = pCfg.sync; + offloadCfg = pCfg.offload; + primeEnabled = syncCfg.enable || offloadCfg.enable; in { + imports = + [ + (mkRenamedOptionModule [ "hardware" "nvidia" "optimus_prime" "enable" ] [ "hardware" "nvidia" "prime" "sync" "enable" ]) + (mkRenamedOptionModule [ "hardware" "nvidia" "optimus_prime" "allowExternalGpu" ] [ "hardware" "nvidia" "prime" "sync" "allowExternalGpu" ]) + (mkRenamedOptionModule [ "hardware" "nvidia" "optimus_prime" "nvidiaBusId" ] [ "hardware" "nvidia" "prime" "nvidiaBusId" ]) + (mkRenamedOptionModule [ "hardware" "nvidia" "optimus_prime" "intelBusId" ] [ "hardware" "nvidia" "prime" "intelBusId" ]) + ]; + options = { - hardware.nvidia.modesetting.enable = lib.mkOption { - type = lib.types.bool; + hardware.nvidia.modesetting.enable = mkOption { + type = types.bool; default = false; description = '' Enable kernel modesetting when using the NVIDIA proprietary driver. Enabling this fixes screen tearing when using Optimus via PRIME (see - <option>hardware.nvidia.optimus_prime.enable</option>. This is not enabled + <option>hardware.nvidia.prime.sync.enable</option>. This is not enabled by default because it is not officially supported by NVIDIA and would not work with SLI. ''; }; - hardware.nvidia.optimus_prime.enable = lib.mkOption { - type = lib.types.bool; + hardware.nvidia.prime.nvidiaBusId = mkOption { + type = types.str; + default = ""; + example = "PCI:1:0:0"; + description = '' + Bus ID of the NVIDIA GPU. You can find it using lspci; for example if lspci + shows the NVIDIA GPU at "01:00.0", set this option to "PCI:1:0:0". + ''; + }; + + hardware.nvidia.prime.intelBusId = mkOption { + type = types.str; + default = ""; + example = "PCI:0:2:0"; + description = '' + Bus ID of the Intel GPU. You can find it using lspci; for example if lspci + shows the Intel GPU at "00:02.0", set this option to "PCI:0:2:0". + ''; + }; + + hardware.nvidia.prime.sync.enable = mkOption { + type = types.bool; default = false; description = '' Enable NVIDIA Optimus support using the NVIDIA proprietary driver via PRIME. @@ -66,8 +97,8 @@ in be the only driver there. If this is enabled, then the bus IDs of the NVIDIA and Intel GPUs have to be - specified (<option>hardware.nvidia.optimus_prime.nvidiaBusId</option> and - <option>hardware.nvidia.optimus_prime.intelBusId</option>). + specified (<option>hardware.nvidia.prime.nvidiaBusId</option> and + <option>hardware.nvidia.prime.intelBusId</option>). If you enable this, you may want to also enable kernel modesetting for the NVIDIA driver (<option>hardware.nvidia.modesetting.enable</option>) in order @@ -79,31 +110,23 @@ in ''; }; - hardware.nvidia.optimus_prime.allowExternalGpu = lib.mkOption { - type = lib.types.bool; + hardware.nvidia.prime.sync.allowExternalGpu = mkOption { + type = types.bool; default = false; description = '' Configure X to allow external NVIDIA GPUs when using optimus. ''; }; - hardware.nvidia.optimus_prime.nvidiaBusId = lib.mkOption { - type = lib.types.str; - default = ""; - example = "PCI:1:0:0"; + hardware.nvidia.prime.offload.enable = mkOption { + type = types.bool; + default = false; description = '' - Bus ID of the NVIDIA GPU. You can find it using lspci; for example if lspci - shows the NVIDIA GPU at "01:00.0", set this option to "PCI:1:0:0". - ''; - }; + Enable render offload support using the NVIDIA proprietary driver via PRIME. - hardware.nvidia.optimus_prime.intelBusId = lib.mkOption { - type = lib.types.str; - default = ""; - example = "PCI:0:2:0"; - description = '' - Bus ID of the Intel GPU. You can find it using lspci; for example if lspci - shows the Intel GPU at "00:02.0", set this option to "PCI:0:2:0". + If this is enabled, then the bus IDs of the NVIDIA and Intel GPUs have to be + specified (<option>hardware.nvidia.prime.nvidiaBusId</option> and + <option>hardware.nvidia.prime.intelBusId</option>). ''; }; }; @@ -116,12 +139,19 @@ in } { - assertion = !optimusCfg.enable || - (optimusCfg.nvidiaBusId != "" && optimusCfg.intelBusId != ""); + assertion = primeEnabled -> pCfg.nvidiaBusId != "" && pCfg.intelBusId != ""; message = '' - When NVIDIA Optimus via PRIME is enabled, the GPU bus IDs must configured. + When NVIDIA PRIME is enabled, the GPU bus IDs must configured. ''; } + { + assertion = offloadCfg.enable -> versionAtLeast nvidia_x11.version "435.21"; + message = "NVIDIA PRIME render offload is currently only supported on versions >= 435.21."; + } + { + assertion = !(syncCfg.enable && offloadCfg.enable); + message = "Only one NVIDIA PRIME solution may be used at a time."; + } ]; # If Optimus/PRIME is enabled, we: @@ -136,36 +166,38 @@ in # - Configure the display manager to run specific `xrandr` commands which will # configure/enable displays connected to the Intel GPU. - services.xserver.drivers = singleton { + services.xserver.useGlamor = mkDefault offloadCfg.enable; + + services.xserver.drivers = optional primeEnabled { + name = "modesetting"; + display = offloadCfg.enable; + deviceSection = '' + BusID "${pCfg.intelBusId}" + ${optionalString syncCfg.enable ''Option "AccelMethod" "none"''} + ''; + } ++ singleton { name = "nvidia"; modules = [ nvidia_x11.bin ]; - deviceSection = optionalString optimusCfg.enable + display = !offloadCfg.enable; + deviceSection = optionalString primeEnabled '' - BusID "${optimusCfg.nvidiaBusId}" - ${optionalString optimusCfg.allowExternalGpu "Option \"AllowExternalGpus\""} + BusID "${pCfg.nvidiaBusId}" + ${optionalString syncCfg.allowExternalGpu "Option \"AllowExternalGpus\""} ''; screenSection = '' Option "RandRRotation" "on" - ${optionalString optimusCfg.enable "Option \"AllowEmptyInitialConfiguration\""} + ${optionalString syncCfg.enable "Option \"AllowEmptyInitialConfiguration\""} ''; }; - services.xserver.extraConfig = optionalString optimusCfg.enable - '' - Section "Device" - Identifier "nvidia-optimus-intel" - Driver "modesetting" - BusID "${optimusCfg.intelBusId}" - Option "AccelMethod" "none" - EndSection - ''; - services.xserver.serverLayoutSection = optionalString optimusCfg.enable - '' - Inactive "nvidia-optimus-intel" - ''; + services.xserver.serverLayoutSection = optionalString syncCfg.enable '' + Inactive "Device-modesetting[0]" + '' + optionalString offloadCfg.enable '' + Option "AllowNVIDIAGPUScreens" + ''; - services.xserver.displayManager.setupCommands = optionalString optimusCfg.enable '' + services.xserver.displayManager.setupCommands = optionalString syncCfg.enable '' # Added by nvidia configuration module for Optimus/PRIME. ${pkgs.xorg.xrandr}/bin/xrandr --setprovideroutputsource modesetting NVIDIA-0 ${pkgs.xorg.xrandr}/bin/xrandr --auto @@ -175,11 +207,13 @@ in source = "${nvidia_x11.bin}/share/nvidia/nvidia-application-profiles-rc"; }; - hardware.opengl.package = nvidia_x11.out; - hardware.opengl.package32 = nvidia_libs32; + hardware.opengl.package = mkIf (!offloadCfg.enable) nvidia_x11.out; + hardware.opengl.package32 = mkIf (!offloadCfg.enable) nvidia_libs32; + hardware.opengl.extraPackages = optional offloadCfg.enable nvidia_x11.out; + hardware.opengl.extraPackages32 = optional offloadCfg.enable nvidia_libs32; environment.systemPackages = [ nvidia_x11.bin nvidia_x11.settings ] - ++ lib.filter (p: p != null) [ nvidia_x11.persistenced ]; + ++ filter (p: p != null) [ nvidia_x11.persistenced ]; systemd.tmpfiles.rules = optional config.virtualisation.docker.enableNvidia "L+ /run/nvidia-docker/bin - - - - ${nvidia_x11.bin}/origBin" @@ -190,18 +224,19 @@ in # nvidia-uvm is required by CUDA applications. boot.kernelModules = [ "nvidia-uvm" ] ++ - lib.optionals config.services.xserver.enable [ "nvidia" "nvidia_modeset" "nvidia_drm" ]; + optionals config.services.xserver.enable [ "nvidia" "nvidia_modeset" "nvidia_drm" ]; # If requested enable modesetting via kernel parameter. - boot.kernelParams = optional cfg.modesetting.enable "nvidia-drm.modeset=1"; + boot.kernelParams = optional (offloadCfg.enable || cfg.modesetting.enable) "nvidia-drm.modeset=1"; # Create /dev/nvidia-uvm when the nvidia-uvm module is loaded. services.udev.extraRules = '' - KERNEL=="nvidia", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidiactl c $(grep nvidia-frontend /proc/devices | cut -d \ -f 1) 255'" - KERNEL=="nvidia_modeset", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-modeset c $(grep nvidia-frontend /proc/devices | cut -d \ -f 1) 254'" - KERNEL=="card*", SUBSYSTEM=="drm", DRIVERS=="nvidia", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia%n c $(grep nvidia-frontend /proc/devices | cut -d \ -f 1) %n'" - KERNEL=="nvidia_uvm", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-uvm c $(grep nvidia-uvm /proc/devices | cut -d \ -f 1) 0'" + KERNEL=="nvidia", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidiactl c $$(grep nvidia-frontend /proc/devices | cut -d \ -f 1) 255'" + KERNEL=="nvidia_modeset", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-modeset c $$(grep nvidia-frontend /proc/devices | cut -d \ -f 1) 254'" + KERNEL=="card*", SUBSYSTEM=="drm", DRIVERS=="nvidia", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia%n c $$(grep nvidia-frontend /proc/devices | cut -d \ -f 1) %n'" + KERNEL=="nvidia_uvm", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-uvm c $$(grep nvidia-uvm /proc/devices | cut -d \ -f 1) 0'" + KERNEL=="nvidia_uvm", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-uvm-tools c $$(grep nvidia-uvm /proc/devices | cut -d \ -f 1) 0'" ''; boot.blacklistedKernelModules = [ "nouveau" "nvidiafb" ]; diff --git a/nixos/modules/hardware/wooting.nix b/nixos/modules/hardware/wooting.nix new file mode 100644 index 0000000000000..ee550cbbf6b83 --- /dev/null +++ b/nixos/modules/hardware/wooting.nix @@ -0,0 +1,12 @@ +{ config, lib, pkgs, ... }: + +with lib; +{ + options.hardware.wooting.enable = + mkEnableOption "Enable support for Wooting keyboards"; + + config = mkIf config.hardware.wooting.enable { + environment.systemPackages = [ pkgs.wootility ]; + services.udev.packages = [ pkgs.wooting-udev-rules ]; + }; +} diff --git a/nixos/modules/i18n/input-method/ibus.nix b/nixos/modules/i18n/input-method/ibus.nix index 8109ef76c4029..b4746b21b653f 100644 --- a/nixos/modules/i18n/input-method/ibus.nix +++ b/nixos/modules/i18n/input-method/ibus.nix @@ -27,6 +27,10 @@ let }; in { + imports = [ + (mkRenamedOptionModule [ "programs" "ibus" "plugins" ] [ "i18n" "inputMethod" "ibus" "engines" ]) + ]; + options = { i18n.inputMethod.ibus = { engines = mkOption { @@ -53,9 +57,17 @@ in config = mkIf (config.i18n.inputMethod.enabled == "ibus") { i18n.inputMethod.package = ibusPackage; + environment.systemPackages = [ + ibusAutostart + ]; + # Without dconf enabled it is impossible to use IBus - environment.systemPackages = with pkgs; [ - gnome3.dconf ibusAutostart + programs.dconf.enable = true; + + programs.dconf.profiles.ibus = "${ibusPackage}/etc/dconf/profile/ibus"; + + services.dbus.packages = [ + ibusAutostart ]; environment.variables = { @@ -63,5 +75,9 @@ in QT_IM_MODULE = "ibus"; XMODIFIERS = "@im=ibus"; }; + + xdg.portal.extraPortals = mkIf config.xdg.portal.enable [ + ibusPackage + ]; }; } diff --git a/nixos/modules/installer/cd-dvd/channel.nix b/nixos/modules/installer/cd-dvd/channel.nix index ab5e7c0645f3b..92164d65e5335 100644 --- a/nixos/modules/installer/cd-dvd/channel.nix +++ b/nixos/modules/installer/cd-dvd/channel.nix @@ -21,7 +21,9 @@ let if [ ! -e $out/nixos/nixpkgs ]; then ln -s . $out/nixos/nixpkgs fi - echo -n ${config.system.nixos.revision} > $out/nixos/.git-revision + ${optionalString (config.system.nixos.revision != null) '' + echo -n ${config.system.nixos.revision} > $out/nixos/.git-revision + ''} echo -n ${config.system.nixos.versionSuffix} > $out/nixos/.version-suffix echo ${config.system.nixos.versionSuffix} | sed -e s/pre// > $out/nixos/svn-revision ''; diff --git a/nixos/modules/installer/cd-dvd/installation-cd-base.nix b/nixos/modules/installer/cd-dvd/installation-cd-base.nix index 24070a786945a..9303e8fde13a8 100644 --- a/nixos/modules/installer/cd-dvd/installation-cd-base.nix +++ b/nixos/modules/installer/cd-dvd/installation-cd-base.nix @@ -18,8 +18,6 @@ with lib; # ISO naming. isoImage.isoName = "${config.isoImage.isoBaseName}-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.iso"; - isoImage.volumeID = substring 0 11 "NIXOS_ISO"; - # EFI booting isoImage.makeEfiBootable = true; diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix index e0b558dcb0d85..fa19daf132800 100644 --- a/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix +++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix @@ -44,6 +44,9 @@ with lib; pkgs.bvi # binary editor pkgs.joe + # Include some version control tools. + pkgs.git + # Firefox for reading the manual. pkgs.firefox diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix index 23c3426bff088..3707c4b7ec62b 100644 --- a/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix +++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix @@ -1,5 +1,4 @@ -# This module defines a NixOS installation CD that contains X11 and -# GNOME 3. +# This module defines a NixOS installation CD that contains GNOME. { lib, ... }: @@ -8,12 +7,23 @@ with lib; { imports = [ ./installation-cd-graphical-base.nix ]; + isoImage.edition = "gnome"; + services.xserver.desktopManager.gnome3.enable = true; - # Auto-login as root. - services.xserver.displayManager.gdm.autoLogin = { + services.xserver.displayManager.gdm = { enable = true; - user = "root"; + # autoSuspend makes the machine automatically suspend after inactivity. + # It's possible someone could/try to ssh'd into the machine and obviously + # have issues because it's inactive. + # See: + # * https://github.com/NixOS/nixpkgs/pull/63790 + # * https://gitlab.gnome.org/GNOME/gnome-control-center/issues/22 + autoSuspend = false; + autoLogin = { + enable = true; + user = "nixos"; + }; }; } diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde-new-kernel.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5-new-kernel.nix index 3336d512cfd86..d98325a99ac2a 100644 --- a/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde-new-kernel.nix +++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5-new-kernel.nix @@ -1,7 +1,7 @@ { pkgs, ... }: { - imports = [ ./installation-cd-graphical-kde.nix ]; + imports = [ ./installation-cd-graphical-plasma5.nix ]; boot.kernelPackages = pkgs.linuxPackages_latest; } diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5.nix index e00d3f7535b2f..e76e06654aca0 100644 --- a/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix +++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5.nix @@ -8,6 +8,8 @@ with lib; { imports = [ ./installation-cd-graphical-base.nix ]; + isoImage.edition = "plasma5"; + services.xserver = { desktopManager.plasma5 = { enable = true; diff --git a/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix b/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix index bcdbffdc20b7d..97506045e0e15 100644 --- a/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix +++ b/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix @@ -8,5 +8,7 @@ [ ./installation-cd-base.nix ]; + isoImage.edition = "minimal"; + fonts.fontconfig.enable = false; } diff --git a/nixos/modules/installer/cd-dvd/iso-image.nix b/nixos/modules/installer/cd-dvd/iso-image.nix index 009f1e2c543ad..cce7cc235ec81 100644 --- a/nixos/modules/installer/cd-dvd/iso-image.nix +++ b/nixos/modules/installer/cd-dvd/iso-image.nix @@ -417,8 +417,17 @@ in ''; }; + isoImage.edition = mkOption { + default = ""; + description = '' + Specifies which edition string to use in the volume ID of the generated + ISO image. + ''; + }; + isoImage.volumeID = mkOption { - default = "NIXOS_BOOT_CD"; + # nixos-$EDITION-$RELEASE-$ARCH + default = "nixos${optionalString (config.isoImage.edition != "") "-${config.isoImage.edition}"}-${config.system.nixos.release}-${pkgs.stdenv.hostPlatform.uname.processor}"; description = '' Specifies the label or volume ID of the generated ISO image. Note that the label is used by stage 1 of the boot process to @@ -474,7 +483,7 @@ in isoImage.efiSplashImage = mkOption { default = pkgs.fetchurl { - url = https://raw.githubusercontent.com/NixOS/nixos-artwork/a9e05d7deb38a8e005a2b52575a3f59a63a4dba0/bootloader/efi-background.png; + url = "https://raw.githubusercontent.com/NixOS/nixos-artwork/a9e05d7deb38a8e005a2b52575a3f59a63a4dba0/bootloader/efi-background.png"; sha256 = "18lfwmp8yq923322nlb9gxrh5qikj1wsk6g5qvdh31c4h5b1538x"; }; description = '' @@ -484,7 +493,7 @@ in isoImage.splashImage = mkOption { default = pkgs.fetchurl { - url = https://raw.githubusercontent.com/NixOS/nixos-artwork/a9e05d7deb38a8e005a2b52575a3f59a63a4dba0/bootloader/isolinux/bios-boot.png; + url = "https://raw.githubusercontent.com/NixOS/nixos-artwork/a9e05d7deb38a8e005a2b52575a3f59a63a4dba0/bootloader/isolinux/bios-boot.png"; sha256 = "1wp822zrhbg4fgfbwkr7cbkr4labx477209agzc0hr6k62fr6rxd"; }; description = '' @@ -515,6 +524,19 @@ in }; config = { + assertions = [ + { + assertion = !(stringLength config.isoImage.volumeID > 32); + # https://wiki.osdev.org/ISO_9660#The_Primary_Volume_Descriptor + # Volume Identifier can only be 32 bytes + message = let + length = stringLength config.isoImage.volumeID; + howmany = toString length; + toomany = toString (length - 32); + in + "isoImage.volumeID ${config.isoImage.volumeID} is ${howmany} characters. That is ${toomany} characters longer than the limit of 32."; + } + ]; boot.loader.grub.version = 2; @@ -569,14 +591,18 @@ in }; fileSystems."/nix/store" = - { fsType = "unionfs-fuse"; - device = "unionfs"; - options = [ "allow_other" "cow" "nonempty" "chroot=/mnt-root" "max_files=32768" "hide_meta_files" "dirs=/nix/.rw-store=rw:/nix/.ro-store=ro" ]; + { fsType = "overlay"; + device = "overlay"; + options = [ + "lowerdir=/nix/.ro-store" + "upperdir=/nix/.rw-store/store" + "workdir=/nix/.rw-store/work" + ]; }; - boot.initrd.availableKernelModules = [ "squashfs" "iso9660" "uas" ]; + boot.initrd.availableKernelModules = [ "squashfs" "iso9660" "uas" "overlay" ]; - boot.initrd.kernelModules = [ "loop" ]; + boot.initrd.kernelModules = [ "loop" "overlay" ]; # Closures to be copied to the Nix store on the CD, namely the init # script and the top-level system configuration directory. @@ -603,9 +629,6 @@ in { source = config.system.build.squashfsStore; target = "/nix-store.squashfs"; } - { source = config.isoImage.efiSplashImage; - target = "/EFI/boot/efi-background.png"; - } { source = config.isoImage.splashImage; target = "/isolinux/background.png"; } @@ -630,8 +653,8 @@ in { source = "${efiDir}/EFI"; target = "/EFI"; } - { source = pkgs.writeText "loopback.cfg" "source /EFI/boot/grub.cfg"; - target = "/boot/grub/loopback.cfg"; + { source = (pkgs.writeTextDir "grub/loopback.cfg" "source /EFI/boot/grub.cfg") + "/grub"; + target = "/boot/grub"; } ] ++ optionals (config.boot.loader.grub.memtest86.enable && canx86BiosBoot) [ { source = "${pkgs.memtest86plus}/memtest.bin"; @@ -641,6 +664,10 @@ in { source = config.isoImage.grubTheme; target = "/EFI/boot/grub-theme"; } + ] ++ [ + { source = config.isoImage.efiSplashImage; + target = "/EFI/boot/efi-background.png"; + } ]; boot.loader.timeout = 10; diff --git a/nixos/modules/installer/cd-dvd/sd-image.nix b/nixos/modules/installer/cd-dvd/sd-image.nix index 7865b767f0b77..901c60befb6ce 100644 --- a/nixos/modules/installer/cd-dvd/sd-image.nix +++ b/nixos/modules/installer/cd-dvd/sd-image.nix @@ -18,6 +18,7 @@ with lib; let rootfsImage = pkgs.callPackage ../../../lib/make-ext4-fs.nix ({ inherit (config.sdImage) storePaths; + compressImage = true; populateImageCommands = config.sdImage.populateRootCommands; volumeLabel = "NIXOS_SD"; } // optionalAttrs (config.sdImage.rootPartitionUUID != null) { @@ -128,10 +129,11 @@ in sdImage.storePaths = [ config.system.build.toplevel ]; - system.build.sdImage = pkgs.callPackage ({ stdenv, dosfstools, e2fsprogs, mtools, libfaketime, utillinux, bzip2 }: stdenv.mkDerivation { + system.build.sdImage = pkgs.callPackage ({ stdenv, dosfstools, e2fsprogs, + mtools, libfaketime, utillinux, bzip2, zstd }: stdenv.mkDerivation { name = config.sdImage.imageName; - nativeBuildInputs = [ dosfstools e2fsprogs mtools libfaketime utillinux bzip2 ]; + nativeBuildInputs = [ dosfstools e2fsprogs mtools libfaketime utillinux bzip2 zstd ]; inherit (config.sdImage) compressImage; @@ -146,11 +148,14 @@ in echo "file sd-image $img" >> $out/nix-support/hydra-build-products fi + echo "Decompressing rootfs image" + zstd -d --no-progress "${rootfsImage}" -o ./root-fs.img + # Gap in front of the first partition, in MiB gap=8 # Create the image file sized to fit /boot/firmware and /, plus slack for the gap. - rootSizeBlocks=$(du -B 512 --apparent-size ${rootfsImage} | awk '{ print $1 }') + rootSizeBlocks=$(du -B 512 --apparent-size ./root-fs.img | awk '{ print $1 }') firmwareSizeBlocks=$((${toString config.sdImage.firmwareSize} * 1024 * 1024 / 512)) imageSize=$((rootSizeBlocks * 512 + firmwareSizeBlocks * 512 + gap * 1024 * 1024)) truncate -s $imageSize $img @@ -168,7 +173,7 @@ in # Copy the rootfs into the SD image eval $(partx $img -o START,SECTORS --nr 2 --pairs) - dd conv=notrunc if=${rootfsImage} of=$img seek=$START count=$SECTORS + dd conv=notrunc if=./root-fs.img of=$img seek=$START count=$SECTORS # Create a FAT32 /boot/firmware partition of suitable size into firmware_part.img eval $(partx $img -o START,SECTORS --nr 1 --pairs) diff --git a/nixos/modules/installer/cd-dvd/system-tarball-pc.nix b/nixos/modules/installer/cd-dvd/system-tarball-pc.nix index bf8b7deb59eb8..f2af7dcde3d54 100644 --- a/nixos/modules/installer/cd-dvd/system-tarball-pc.nix +++ b/nixos/modules/installer/cd-dvd/system-tarball-pc.nix @@ -122,11 +122,10 @@ in /* fake entry, just to have a happy stage-1. Users may boot without having stage-1 though */ - fileSystems = [ + fileSystems.fake = { mountPoint = "/"; device = "/dev/something"; - } - ]; + }; nixpkgs.config = { packageOverrides = p: { diff --git a/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix b/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix index 90a5128c02a58..8408f56f94f91 100644 --- a/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix +++ b/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix @@ -117,11 +117,10 @@ in /* fake entry, just to have a happy stage-1. Users may boot without having stage-1 though */ - fileSystems = [ + fileSystems.fake = { mountPoint = "/"; device = "/dev/something"; - } - ]; + }; services.mingetty = { # Some more help text. diff --git a/nixos/modules/installer/cd-dvd/system-tarball.nix b/nixos/modules/installer/cd-dvd/system-tarball.nix index b84096861f56e..58098c45535db 100644 --- a/nixos/modules/installer/cd-dvd/system-tarball.nix +++ b/nixos/modules/installer/cd-dvd/system-tarball.nix @@ -41,7 +41,7 @@ in # In stage 1 of the boot, mount the CD/DVD as the root FS by label # so that we don't need to know its device. - fileSystems = [ ]; + fileSystems = { }; # boot.initrd.availableKernelModules = [ "mvsdio" "reiserfs" "ext3" "ext4" ]; diff --git a/nixos/modules/installer/netboot/netboot.nix b/nixos/modules/installer/netboot/netboot.nix index 5146858cccf5a..95eba86bcb654 100644 --- a/nixos/modules/installer/netboot/netboot.nix +++ b/nixos/modules/installer/netboot/netboot.nix @@ -50,14 +50,18 @@ with lib; }; fileSystems."/nix/store" = - { fsType = "unionfs-fuse"; - device = "unionfs"; - options = [ "allow_other" "cow" "nonempty" "chroot=/mnt-root" "max_files=32768" "hide_meta_files" "dirs=/nix/.rw-store=rw:/nix/.ro-store=ro" ]; + { fsType = "overlay"; + device = "overlay"; + options = [ + "lowerdir=/nix/.ro-store" + "upperdir=/nix/.rw-store/store" + "workdir=/nix/.rw-store/work" + ]; }; - boot.initrd.availableKernelModules = [ "squashfs" ]; + boot.initrd.availableKernelModules = [ "squashfs" "overlay" ]; - boot.initrd.kernelModules = [ "loop" ]; + boot.initrd.kernelModules = [ "loop" "overlay" ]; # Closures to be copied to the Nix store, namely the init # script and the top-level system configuration directory. diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix index d7149b35d4c0c..842976c3574f0 100644 --- a/nixos/modules/installer/tools/nix-fallback-paths.nix +++ b/nixos/modules/installer/tools/nix-fallback-paths.nix @@ -1,6 +1,6 @@ { - x86_64-linux = "/nix/store/6chjfy4j6hjwj5f8zcbbdg02i21x1qsi-nix-2.3.1"; - i686-linux = "/nix/store/xa8z7fwszjjm4kiwrxfc8xv9c1pzzm7a-nix-2.3.1"; - aarch64-linux = "/nix/store/8cac1ivcnchlpzmdjby2f71l1fwpnymr-nix-2.3.1"; - x86_64-darwin = "/nix/store/6639l9815ggdnb4aka22qcjy7p8w4hb9-nix-2.3.1"; + x86_64-linux = "/nix/store/8928ygfyf9iassfrnj76v55s6zid58ja-nix-2.3.4"; + i686-linux = "/nix/store/b5cx3nmba9ahx3wk5ybxa67k40pdpdxn-nix-2.3.4"; + aarch64-linux = "/nix/store/p6j4mis6agdjlk4j0cyg7yh58wpm3kif-nix-2.3.4"; + x86_64-darwin = "/nix/store/aizhr07dljmlbf17wfrj40x3s0b5iv3d-nix-2.3.4"; } diff --git a/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix b/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix index c1028a0ad7e93..0c9f8522cc12b 100644 --- a/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix +++ b/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix @@ -3,9 +3,14 @@ , networkExpr }: -let nodes = import networkExpr; in +let + nodes = builtins.mapAttrs (vm: module: { + _file = "${networkExpr}@node-${vm}"; + imports = [ module ]; + }) (import networkExpr); +in -with import ../../../../lib/testing.nix { +with import ../../../../lib/testing-python.nix { inherit system; pkgs = import ../../../../.. { inherit system config; }; }; diff --git a/nixos/modules/installer/tools/nixos-enter.sh b/nixos/modules/installer/tools/nixos-enter.sh index 4680cd8ae95a4..1fdd4627a902c 100644 --- a/nixos/modules/installer/tools/nixos-enter.sh +++ b/nixos/modules/installer/tools/nixos-enter.sh @@ -60,15 +60,15 @@ chmod 0755 "$mountPoint/dev" "$mountPoint/sys" mount --rbind /dev "$mountPoint/dev" mount --rbind /sys "$mountPoint/sys" -# If silent, write both stdout and stderr of activation script to /dev/null -# otherwise, write both streams to stderr of this process -if [ "$silent" -eq 0 ]; then - PIPE_TARGET="/dev/stderr" -else - PIPE_TARGET="/dev/null" -fi +( + # If silent, write both stdout and stderr of activation script to /dev/null + # otherwise, write both streams to stderr of this process + if [ "$silent" -eq 1 ]; then + exec 2>/dev/null + fi -# Run the activation script. Set $LOCALE_ARCHIVE to supress some Perl locale warnings. -LOCALE_ARCHIVE="$system/sw/lib/locale/locale-archive" chroot "$mountPoint" "$system/activate" >>$PIPE_TARGET 2>&1 || true + # Run the activation script. Set $LOCALE_ARCHIVE to supress some Perl locale warnings. + LOCALE_ARCHIVE="$system/sw/lib/locale/locale-archive" chroot "$mountPoint" "$system/activate" 1>&2 || true +) exec chroot "$mountPoint" "${command[@]}" diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl index f2ffe61c42cbe..422c405054d57 100644 --- a/nixos/modules/installer/tools/nixos-generate-config.pl +++ b/nixos/modules/installer/tools/nixos-generate-config.pl @@ -91,9 +91,6 @@ sub hasCPUFeature { } -# Detect the number of CPU cores. -my $cpus = scalar (grep {/^processor\s*:/} (split '\n', $cpuinfo)); - # Determine CPU governor to use if (-e "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors") { @@ -183,7 +180,7 @@ sub pciCheck { ) ) { # we need e.g. brcmfmac43602-pcie.bin - push @imports, "<nixpkgs/nixos/modules/hardware/network/broadcom-43xx.nix>"; + push @imports, "(modulesPath + \"/hardware/network/broadcom-43xx.nix\")"; } # Can't rely on $module here, since the module may not be loaded @@ -282,7 +279,7 @@ if ($virt eq "oracle") { # Likewise for QEMU. if ($virt eq "qemu" || $virt eq "kvm" || $virt eq "bochs") { - push @imports, "<nixpkgs/nixos/modules/profiles/qemu-guest.nix>"; + push @imports, "(modulesPath + \"/profiles/qemu-guest.nix\")"; } # Also for Hyper-V. @@ -299,7 +296,7 @@ if ($virt eq "systemd-nspawn") { # Provide firmware for devices that are not detected by this script, # unless we're in a VM/container. -push @imports, "<nixpkgs/nixos/modules/installer/scan/not-detected.nix>" +push @imports, "(modulesPath + \"/installer/scan/not-detected.nix\")" if $virt eq "none"; @@ -335,6 +332,9 @@ if (@swaps) { next unless -e $swapFilename; my $dev = findStableDevPath $swapFilename; if ($swapType =~ "partition") { + # zram devices are more likely created by configuration.nix, so + # ignore them here + next if ($swapFilename =~ /^\/dev\/zram/); push @swapDevices, "{ device = \"$dev\"; }"; } elsif ($swapType =~ "file") { # swap *files* are more likely specified in configuration.nix, so @@ -498,7 +498,7 @@ if (-f $fb_modes_file && -r $fb_modes_file) { my $console_width = $1, my $console_height = $2; if ($console_width > 1920) { push @attrs, "# High-DPI console"; - push @attrs, 'i18n.consoleFont = lib.mkDefault "${pkgs.terminus_font}/share/consolefonts/ter-u28n.psf.gz";'; + push @attrs, 'console.font = lib.mkDefault "${pkgs.terminus_font}/share/consolefonts/ter-u28n.psf.gz";'; } } @@ -549,7 +549,7 @@ my $hwConfig = <<EOF; # Do not modify this file! It was generated by ‘nixos-generate-config’ # and may be overwritten by future invocations. Please make changes # to /etc/nixos/configuration.nix instead. -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, modulesPath, ... }: { imports =${\multiLineList(" ", @imports)}; @@ -559,7 +559,6 @@ my $hwConfig = <<EOF; boot.kernelModules = [$kernelModules ]; boot.extraModulePackages = [$modulePackages ]; $fsAndSwap - nix.maxJobs = lib.mkDefault $cpus; ${\join "", (map { " $_\n" } (uniq @attrs))}} EOF diff --git a/nixos/modules/installer/tools/nixos-install.sh b/nixos/modules/installer/tools/nixos-install.sh index 8685cb345e1e1..a3ff3fe2c0c1a 100644 --- a/nixos/modules/installer/tools/nixos-install.sh +++ b/nixos/modules/installer/tools/nixos-install.sh @@ -14,6 +14,8 @@ extraBuildFlags=() mountPoint=/mnt channelPath= system= +verbosity=() +buildLogs= while [ "$#" -gt 0 ]; do i="$1"; shift 1 @@ -55,6 +57,12 @@ while [ "$#" -gt 0 ]; do --debug) set -x ;; + -v*|--verbose) + verbosity+=("$i") + ;; + -L|--print-build-logs) + buildLogs="$i" + ;; *) echo "$0: unknown option \`$i'" exit 1 @@ -94,7 +102,7 @@ if [[ -z $system ]]; then outLink="$tmpdir/system" nix build --out-link "$outLink" --store "$mountPoint" "${extraBuildFlags[@]}" \ --extra-substituters "$sub" \ - -f '<nixpkgs/nixos>' system -I "nixos-config=$NIXOS_CONFIG" + -f '<nixpkgs/nixos>' system -I "nixos-config=$NIXOS_CONFIG" ${verbosity[@]} ${buildLogs} system=$(readlink -f $outLink) fi @@ -103,7 +111,7 @@ fi # a progress bar. nix-env --store "$mountPoint" "${extraBuildFlags[@]}" \ --extra-substituters "$sub" \ - -p $mountPoint/nix/var/nix/profiles/system --set "$system" + -p $mountPoint/nix/var/nix/profiles/system --set "$system" ${verbosity[@]} # Copy the NixOS/Nixpkgs sources to the target as the initial contents # of the NixOS channel. @@ -115,7 +123,8 @@ if [[ -z $noChannelCopy ]]; then echo "copying channel..." mkdir -p $mountPoint/nix/var/nix/profiles/per-user/root nix-env --store "$mountPoint" "${extraBuildFlags[@]}" --extra-substituters "$sub" \ - -p $mountPoint/nix/var/nix/profiles/per-user/root/channels --set "$channelPath" --quiet + -p $mountPoint/nix/var/nix/profiles/per-user/root/channels --set "$channelPath" --quiet \ + ${verbosity[@]} install -m 0700 -d $mountPoint/root/.nix-defexpr ln -sfn /nix/var/nix/profiles/per-user/root/channels $mountPoint/root/.nix-defexpr/channels fi diff --git a/nixos/modules/installer/tools/nixos-option/nixos-option.cc b/nixos/modules/installer/tools/nixos-option/nixos-option.cc index 9b92dc829cd16..1a7b07a74f8ac 100644 --- a/nixos/modules/installer/tools/nixos-option/nixos-option.cc +++ b/nixos/modules/installer/tools/nixos-option/nixos-option.cc @@ -131,12 +131,12 @@ bool isOption(Context & ctx, const Value & v) if (v.type != tAttrs) { return false; } - const auto & atualType = v.attrs->find(ctx.underscoreType); - if (atualType == v.attrs->end()) { + const auto & actualType = v.attrs->find(ctx.underscoreType); + if (actualType == v.attrs->end()) { return false; } try { - Value evaluatedType = evaluateValue(ctx, *atualType->value); + Value evaluatedType = evaluateValue(ctx, *actualType->value); if (evaluatedType.type != tString) { return false; } @@ -197,9 +197,107 @@ void recurse(const std::function<bool(const std::string & path, std::variant<Val } } -// Calls f on all the option names -void mapOptions(const std::function<void(const std::string & path)> & f, Context & ctx, Value root) +bool optionTypeIs(Context & ctx, Value & v, const std::string & soughtType) { + try { + const auto & typeLookup = v.attrs->find(ctx.state.sType); + if (typeLookup == v.attrs->end()) { + return false; + } + Value type = evaluateValue(ctx, *typeLookup->value); + if (type.type != tAttrs) { + return false; + } + const auto & nameLookup = type.attrs->find(ctx.state.sName); + if (nameLookup == type.attrs->end()) { + return false; + } + Value name = evaluateValue(ctx, *nameLookup->value); + if (name.type != tString) { + return false; + } + return name.string.s == soughtType; + } catch (Error &) { + return false; + } +} + +bool isAggregateOptionType(Context & ctx, Value & v) +{ + return optionTypeIs(ctx, v, "attrsOf") || optionTypeIs(ctx, v, "listOf") || optionTypeIs(ctx, v, "loaOf"); +} + +MakeError(OptionPathError, EvalError); + +Value getSubOptions(Context & ctx, Value & option) +{ + Value getSubOptions = evaluateValue(ctx, *findAlongAttrPath(ctx.state, "type.getSubOptions", ctx.autoArgs, option)); + if (getSubOptions.type != tLambda) { + throw OptionPathError("Option's type.getSubOptions isn't a function"); + } + Value emptyString{}; + nix::mkString(emptyString, ""); + Value v; + ctx.state.callFunction(getSubOptions, emptyString, v, nix::Pos{}); + return v; +} + +// Carefully walk an option path, looking for sub-options when a path walks past +// an option value. +struct FindAlongOptionPathRet +{ + Value option; + std::string path; +}; +FindAlongOptionPathRet findAlongOptionPath(Context & ctx, const std::string & path) +{ + Strings tokens = parseAttrPath(path); + Value v = ctx.optionsRoot; + std::string processedPath; + for (auto i = tokens.begin(); i != tokens.end(); i++) { + const auto & attr = *i; + try { + bool lastAttribute = std::next(i) == tokens.end(); + v = evaluateValue(ctx, v); + if (attr.empty()) { + throw OptionPathError("empty attribute name"); + } + if (isOption(ctx, v) && optionTypeIs(ctx, v, "submodule")) { + v = getSubOptions(ctx, v); + } + if (isOption(ctx, v) && isAggregateOptionType(ctx, v)) { + auto subOptions = getSubOptions(ctx, v); + if (lastAttribute && subOptions.attrs->empty()) { + break; + } + v = subOptions; + // Note that we've consumed attr, but didn't actually use it. This is the path component that's looked + // up in the list or attribute set that doesn't name an option -- the "root" in "users.users.root.name". + } else if (v.type != tAttrs) { + throw OptionPathError("Value is %s while a set was expected", showType(v)); + } else { + const auto & next = v.attrs->find(ctx.state.symbols.create(attr)); + if (next == v.attrs->end()) { + throw OptionPathError("Attribute not found", attr, path); + } + v = *next->value; + } + processedPath = appendPath(processedPath, attr); + } catch (OptionPathError & e) { + throw OptionPathError("At '%s' in path '%s': %s", attr, path, e.msg()); + } + } + return {v, processedPath}; +} + +// Calls f on all the option names at or below the option described by `path`. +// Note that "the option described by `path`" is not trivial -- if path describes a value inside an aggregate +// option (such as users.users.root), the *option* described by that path is one path component shorter +// (eg: users.users), which results in f being called on sibling-paths (eg: users.users.nixbld1). If f +// doesn't want these, it must do its own filtering. +void mapOptions(const std::function<void(const std::string & path)> & f, Context & ctx, const std::string & path) +{ + auto root = findAlongOptionPath(ctx, path); recurse( [f, &ctx](const std::string & path, std::variant<Value, std::exception_ptr> v) { bool isOpt = std::holds_alternative<std::exception_ptr>(v) || isOption(ctx, std::get<Value>(v)); @@ -208,7 +306,7 @@ void mapOptions(const std::function<void(const std::string & path)> & f, Context } return !isOpt; }, - ctx, root, ""); + ctx, root.option, root.path); } // Calls f on all the config values inside one option. @@ -294,9 +392,11 @@ void printAttrs(Context & ctx, Out & out, Value & v, const std::string & path) Out attrsOut(out, "{", "}", v.attrs->size()); for (const auto & a : v.attrs->lexicographicOrder()) { std::string name = a->name; - attrsOut << name << " = "; - printValue(ctx, attrsOut, *a->value, appendPath(path, name)); - attrsOut << ";" << Out::sep; + if (!forbiddenRecursionName(name)) { + attrsOut << name << " = "; + printValue(ctx, attrsOut, *a->value, appendPath(path, name)); + attrsOut << ";" << Out::sep; + } } } @@ -380,17 +480,26 @@ void printConfigValue(Context & ctx, Out & out, const std::string & path, std::v out << ";\n"; } -void printAll(Context & ctx, Out & out) +// Replace with std::starts_with when C++20 is available +bool starts_with(const std::string & s, const std::string & prefix) +{ + return s.size() >= prefix.size() && + std::equal(s.begin(), std::next(s.begin(), prefix.size()), prefix.begin(), prefix.end()); +} + +void printRecursive(Context & ctx, Out & out, const std::string & path) { mapOptions( - [&ctx, &out](const std::string & optionPath) { + [&ctx, &out, &path](const std::string & optionPath) { mapConfigValuesInOption( - [&ctx, &out](const std::string & configPath, std::variant<Value, std::exception_ptr> v) { - printConfigValue(ctx, out, configPath, v); + [&ctx, &out, &path](const std::string & configPath, std::variant<Value, std::exception_ptr> v) { + if (starts_with(configPath, path)) { + printConfigValue(ctx, out, configPath, v); + } }, optionPath, ctx); }, - ctx, ctx.optionsRoot); + ctx, path); } void printAttr(Context & ctx, Out & out, const std::string & path, Value & root) @@ -450,95 +559,17 @@ void printListing(Out & out, Value & v) } } -bool optionTypeIs(Context & ctx, Value & v, const std::string & soughtType) -{ - try { - const auto & typeLookup = v.attrs->find(ctx.state.sType); - if (typeLookup == v.attrs->end()) { - return false; - } - Value type = evaluateValue(ctx, *typeLookup->value); - if (type.type != tAttrs) { - return false; - } - const auto & nameLookup = type.attrs->find(ctx.state.sName); - if (nameLookup == type.attrs->end()) { - return false; - } - Value name = evaluateValue(ctx, *nameLookup->value); - if (name.type != tString) { - return false; - } - return name.string.s == soughtType; - } catch (Error &) { - return false; - } -} - -bool isAggregateOptionType(Context & ctx, Value & v) -{ - return optionTypeIs(ctx, v, "attrsOf") || optionTypeIs(ctx, v, "listOf") || optionTypeIs(ctx, v, "loaOf"); -} - -MakeError(OptionPathError, EvalError); - -Value getSubOptions(Context & ctx, Value & option) -{ - Value getSubOptions = evaluateValue(ctx, *findAlongAttrPath(ctx.state, "type.getSubOptions", ctx.autoArgs, option)); - if (getSubOptions.type != tLambda) { - throw OptionPathError("Option's type.getSubOptions isn't a function"); - } - Value emptyString{}; - nix::mkString(emptyString, ""); - Value v; - ctx.state.callFunction(getSubOptions, emptyString, v, nix::Pos{}); - return v; -} - -// Carefully walk an option path, looking for sub-options when a path walks past -// an option value. -Value findAlongOptionPath(Context & ctx, const std::string & path) -{ - Strings tokens = parseAttrPath(path); - Value v = ctx.optionsRoot; - for (auto i = tokens.begin(); i != tokens.end(); i++) { - const auto & attr = *i; - try { - bool lastAttribute = std::next(i) == tokens.end(); - v = evaluateValue(ctx, v); - if (attr.empty()) { - throw OptionPathError("empty attribute name"); - } - if (isOption(ctx, v) && optionTypeIs(ctx, v, "submodule")) { - v = getSubOptions(ctx, v); - } - if (isOption(ctx, v) && isAggregateOptionType(ctx, v) && !lastAttribute) { - v = getSubOptions(ctx, v); - // Note that we've consumed attr, but didn't actually use it. This is the path component that's looked - // up in the list or attribute set that doesn't name an option -- the "root" in "users.users.root.name". - } else if (v.type != tAttrs) { - throw OptionPathError("Value is %s while a set was expected", showType(v)); - } else { - const auto & next = v.attrs->find(ctx.state.symbols.create(attr)); - if (next == v.attrs->end()) { - throw OptionPathError("Attribute not found", attr, path); - } - v = *next->value; - } - } catch (OptionPathError & e) { - throw OptionPathError("At '%s' in path '%s': %s", attr, path, e.msg()); - } - } - return v; -} - void printOne(Context & ctx, Out & out, const std::string & path) { try { - Value option = findAlongOptionPath(ctx, path); + auto result = findAlongOptionPath(ctx, path); + Value & option = result.option; option = evaluateValue(ctx, option); + if (path != result.path) { + out << "Note: showing " << result.path << " instead of " << path << "\n"; + } if (isOption(ctx, option)) { - printOption(ctx, out, path, option); + printOption(ctx, out, result.path, option); } else { printListing(out, option); } @@ -552,7 +583,7 @@ void printOne(Context & ctx, Out & out, const std::string & path) int main(int argc, char ** argv) { - bool all = false; + bool recursive = false; std::string path = "."; std::string optionsExpr = "(import <nixpkgs/nixos> {}).options"; std::string configExpr = "(import <nixpkgs/nixos> {}).config"; @@ -568,8 +599,8 @@ int main(int argc, char ** argv) nix::showManPage("nixos-option"); } else if (*arg == "--version") { nix::printVersion("nixos-option"); - } else if (*arg == "--all") { - all = true; + } else if (*arg == "-r" || *arg == "--recursive") { + recursive = true; } else if (*arg == "--path") { path = nix::getArg(*arg, arg, end); } else if (*arg == "--options_expr") { @@ -598,18 +629,12 @@ int main(int argc, char ** argv) Context ctx{*state, *myArgs.getAutoArgs(*state), optionsRoot, configRoot}; Out out(std::cout); - if (all) { - if (!args.empty()) { - throw UsageError("--all cannot be used with arguments"); - } - printAll(ctx, out); - } else { - if (args.empty()) { - printOne(ctx, out, ""); - } - for (const auto & arg : args) { - printOne(ctx, out, arg); - } + auto print = recursive ? printRecursive : printOne; + if (args.empty()) { + print(ctx, out, ""); + } + for (const auto & arg : args) { + print(ctx, out, arg); } ctx.state.printStats(); diff --git a/nixos/modules/installer/tools/nixos-rebuild.sh b/nixos/modules/installer/tools/nixos-rebuild.sh index c53dc1000c4ac..354274478a384 100644 --- a/nixos/modules/installer/tools/nixos-rebuild.sh +++ b/nixos/modules/installer/tools/nixos-rebuild.sh @@ -3,6 +3,9 @@ if [ -x "@shell@" ]; then export SHELL="@shell@"; fi; set -e +set -o pipefail + +export PATH=@path@:$PATH showSyntax() { exec man nixos-rebuild @@ -13,6 +16,7 @@ showSyntax() { # Parse the command line. origArgs=("$@") extraBuildFlags=() +lockFlags=() action= buildNix=1 fast= @@ -22,7 +26,7 @@ repair= profile=/nix/var/nix/profiles/system buildHost= targetHost= -maybeSudo= +maybeSudo=() while [ "$#" -gt 0 ]; do i="$1"; shift 1 @@ -58,7 +62,7 @@ while [ "$#" -gt 0 ]; do j="$1"; shift 1 extraBuildFlags+=("$i" "$j") ;; - --show-trace|--keep-failed|-K|--keep-going|-k|--verbose|-v|-vv|-vvv|-vvvv|-vvvvv|--fallback|--repair|--no-build-output|-Q|-j*) + --show-trace|--keep-failed|-K|--keep-going|-k|--verbose|-v|-vv|-vvv|-vvvv|-vvvvv|--fallback|--repair|--no-build-output|-Q|-j*|-L|--refresh|--no-net) extraBuildFlags+=("$i") ;; --option) @@ -91,10 +95,24 @@ while [ "$#" -gt 0 ]; do shift 1 ;; --use-remote-sudo) - # note the trailing space - maybeSudo="sudo " + maybeSudo=(sudo --) + ;; + --flake) + flake="$1" shift 1 ;; + --recreate-lock-file|--no-update-lock-file|--no-write-lock-file|--no-registries|--commit-lock-file) + lockFlags+=("$i") + ;; + --update-input) + j="$1"; shift 1 + lockFlags+=("$i" "$j") + ;; + --override-input) + j="$1"; shift 1 + k="$1"; shift 1 + lockFlags+=("$i" "$j" "$k") + ;; *) echo "$0: unknown option \`$i'" exit 1 @@ -102,6 +120,10 @@ while [ "$#" -gt 0 ]; do esac done +if [ -n "$SUDO_USER" ]; then + maybeSudo=(sudo --) +fi + if [ -z "$buildHost" -a -n "$targetHost" ]; then buildHost="$targetHost" fi @@ -116,17 +138,17 @@ buildHostCmd() { if [ -z "$buildHost" ]; then "$@" elif [ -n "$remoteNix" ]; then - ssh $SSHOPTS "$buildHost" env PATH="$remoteNix:$PATH" "$maybeSudo$@" + ssh $SSHOPTS "$buildHost" env PATH="$remoteNix:$PATH" "${maybeSudo[@]}" "$@" else - ssh $SSHOPTS "$buildHost" "$maybeSudo$@" + ssh $SSHOPTS "$buildHost" "${maybeSudo[@]}" "$@" fi } targetHostCmd() { if [ -z "$targetHost" ]; then - "$@" + "${maybeSudo[@]}" "$@" else - ssh $SSHOPTS "$targetHost" "$maybeSudo$@" + ssh $SSHOPTS "$targetHost" "${maybeSudo[@]}" "$@" fi } @@ -200,7 +222,7 @@ fi # If ‘--upgrade’ is given, run ‘nix-channel --update nixos’. -if [ -n "$upgrade" -a -z "$_NIXOS_REBUILD_REEXEC" ]; then +if [[ -n $upgrade && -z $_NIXOS_REBUILD_REEXEC && -z $flake ]]; then nix-channel --update nixos # If there are other channels that contain a file called @@ -223,8 +245,15 @@ if [ -z "$_NIXOS_REBUILD_REEXEC" ]; then export PATH=@nix@/bin:$PATH fi +# Use /etc/nixos/flake.nix if it exists. It can be a symlink to the +# actual flake. +if [[ -z $flake && -e /etc/nixos/flake.nix ]]; then + flake="$(dirname "$(readlink -f /etc/nixos/flake.nix)")" +fi + # Re-execute nixos-rebuild from the Nixpkgs tree. -if [ -z "$_NIXOS_REBUILD_REEXEC" -a -n "$canRun" -a -z "$fast" ]; then +# FIXME: get nixos-rebuild from $flake. +if [[ -z $_NIXOS_REBUILD_REEXEC && -n $canRun && -z $fast && -z $flake ]]; then if p=$(nix-build --no-out-link --expr 'with import <nixpkgs/nixos> {}; config.system.build.nixos-rebuild' "${extraBuildFlags[@]}"); then export _NIXOS_REBUILD_REEXEC=1 exec $p/bin/nixos-rebuild "${origArgs[@]}" @@ -232,10 +261,37 @@ if [ -z "$_NIXOS_REBUILD_REEXEC" -a -n "$canRun" -a -z "$fast" ]; then fi fi +# For convenience, use the hostname as the default configuration to +# build from the flake. +if [[ -n $flake ]]; then + if [[ $flake =~ ^(.*)\#([^\#\"]*)$ ]]; then + flake="${BASH_REMATCH[1]}" + flakeAttr="${BASH_REMATCH[2]}" + fi + if [[ -z $flakeAttr ]]; then + read -r hostname < /proc/sys/kernel/hostname + if [[ -z $hostname ]]; then + hostname=default + fi + flakeAttr="nixosConfigurations.\"$hostname\"" + else + flakeAttr="nixosConfigurations.\"$flakeAttr\"" + fi +fi + +# Resolve the flake. +if [[ -n $flake ]]; then + flake=$(nix flake info --json "${extraBuildFlags[@]}" "${lockFlags[@]}" -- "$flake" | jq -r .url) +fi + # Find configuration.nix and open editor instead of building. if [ "$action" = edit ]; then - NIXOS_CONFIG=${NIXOS_CONFIG:-$(nix-instantiate --find-file nixos-config)} - exec "${EDITOR:-nano}" "$NIXOS_CONFIG" + if [[ -z $flake ]]; then + NIXOS_CONFIG=${NIXOS_CONFIG:-$(nix-instantiate --find-file nixos-config)} + exec "${EDITOR:-nano}" "$NIXOS_CONFIG" + else + exec nix edit "${lockFlags[@]}" -- "$flake#$flakeAttr" + fi exit 1 fi @@ -294,7 +350,7 @@ prebuiltNix() { remotePATH= -if [ -n "$buildNix" ]; then +if [[ -n $buildNix && -z $flake ]]; then echo "building Nix..." >&2 nixDrv= if ! nixDrv="$(nix-instantiate '<nixpkgs/nixos>' --add-root $tmpDir/nix.drv --indirect -A config.nix.package.out "${extraBuildFlags[@]}")"; then @@ -335,7 +391,7 @@ fi # Update the version suffix if we're building from Git (so that # nixos-version shows something useful). -if [ -n "$canRun" ]; then +if [[ -n $canRun && -z $flake ]]; then if nixpkgs=$(nix-instantiate --find-file nixpkgs "${extraBuildFlags[@]}"); then suffix=$($SHELL $nixpkgs/nixos/modules/installer/tools/get-version-suffix "${extraBuildFlags[@]}" || true) if [ -n "$suffix" ]; then @@ -356,15 +412,37 @@ fi if [ -z "$rollback" ]; then echo "building the system configuration..." >&2 if [ "$action" = switch -o "$action" = boot ]; then - pathToConfig="$(nixBuild '<nixpkgs/nixos>' --no-out-link -A system "${extraBuildFlags[@]}")" + if [[ -z $flake ]]; then + pathToConfig="$(nixBuild '<nixpkgs/nixos>' --no-out-link -A system "${extraBuildFlags[@]}")" + else + outLink=$tmpDir/result + nix build "$flake#$flakeAttr.config.system.build.toplevel" \ + "${extraBuildFlags[@]}" "${lockFlags[@]}" --out-link $outLink + pathToConfig="$(readlink -f $outLink)" + fi copyToTarget "$pathToConfig" targetHostCmd nix-env -p "$profile" --set "$pathToConfig" elif [ "$action" = test -o "$action" = build -o "$action" = dry-build -o "$action" = dry-activate ]; then - pathToConfig="$(nixBuild '<nixpkgs/nixos>' -A system -k "${extraBuildFlags[@]}")" + if [[ -z $flake ]]; then + pathToConfig="$(nixBuild '<nixpkgs/nixos>' -A system -k "${extraBuildFlags[@]}")" + else + nix build "$flake#$flakeAttr.config.system.build.toplevel" "${extraBuildFlags[@]}" "${lockFlags[@]}" + pathToConfig="$(readlink -f ./result)" + fi elif [ "$action" = build-vm ]; then - pathToConfig="$(nixBuild '<nixpkgs/nixos>' -A vm -k "${extraBuildFlags[@]}")" + if [[ -z $flake ]]; then + pathToConfig="$(nixBuild '<nixpkgs/nixos>' -A vm -k "${extraBuildFlags[@]}")" + else + echo "$0: 'build-vm' is not supported with '--flake'" >&2 + exit 1 + fi elif [ "$action" = build-vm-with-bootloader ]; then - pathToConfig="$(nixBuild '<nixpkgs/nixos>' -A vmWithBootLoader -k "${extraBuildFlags[@]}")" + if [[ -z $flake ]]; then + pathToConfig="$(nixBuild '<nixpkgs/nixos>' -A vmWithBootLoader -k "${extraBuildFlags[@]}")" + else + echo "$0: 'build-vm-with-bootloader' is not supported with '--flake'" >&2 + exit 1 + fi else showSyntax fi diff --git a/nixos/modules/installer/tools/nixos-version.sh b/nixos/modules/installer/tools/nixos-version.sh index 190c49a33ec61..fb0fe26116a6e 100644 --- a/nixos/modules/installer/tools/nixos-version.sh +++ b/nixos/modules/installer/tools/nixos-version.sh @@ -6,8 +6,17 @@ case "$1" in exit 1 ;; --hash|--revision) + if ! [[ @revision@ =~ ^[0-9a-f]+$ ]]; then + echo "$0: Nixpkgs commit hash is unknown" + exit 1 + fi echo "@revision@" ;; + --json) + cat <<EOF +@json@ +EOF + ;; *) echo "@version@ (@codeName@)" ;; diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix index e4db39b5c8108..111286214248d 100644 --- a/nixos/modules/installer/tools/tools.nix +++ b/nixos/modules/installer/tools/tools.nix @@ -31,6 +31,7 @@ let nix = config.nix.package.out; nix_x86_64_linux = fallback.x86_64-linux; nix_i686_linux = fallback.i686-linux; + path = makeBinPath [ pkgs.jq ]; }; nixos-generate-config = makeProg { @@ -41,12 +42,23 @@ let inherit (config.system.nixos-generate-config) configuration; }; - nixos-option = pkgs.callPackage ./nixos-option { }; + nixos-option = + if lib.versionAtLeast (lib.getVersion pkgs.nix) "2.4pre" + then null + else pkgs.callPackage ./nixos-option { }; nixos-version = makeProg { name = "nixos-version"; src = ./nixos-version.sh; inherit (config.system.nixos) version codeName revision; + inherit (config.system) configurationRevision; + json = builtins.toJSON ({ + nixosVersion = config.system.nixos.version; + } // optionalAttrs (config.system.nixos.revision != null) { + nixpkgsRevision = config.system.nixos.revision; + } // optionalAttrs (config.system.configurationRevision != null) { + configurationRevision = config.system.configurationRevision; + }); }; nixos-enter = makeProg { @@ -99,10 +111,10 @@ in # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain"; # Select internationalisation properties. - # i18n = { - # consoleFont = "Lat2-Terminus16"; - # consoleKeyMap = "us"; - # defaultLocale = "en_US.UTF-8"; + # i18n.defaultLocale = "en_US.UTF-8"; + # console = { + # font = "Lat2-Terminus16"; + # keyMap = "us"; # }; # Set your time zone. @@ -159,10 +171,12 @@ in # extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user. # }; - # This value determines the NixOS release with which your system is to be - # compatible, in order to avoid breaking some software such as database - # servers. You should change this only after NixOS release notes say you - # should. + # This value determines the NixOS release from which the default + # settings for stateful data, like file locations and database versions + # on your system were taken. It‘s perfectly fine and recommended to leave + # this value at the release version of the first install of this system. + # Before changing this value read the documentation for this option + # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). system.stateVersion = "${config.system.nixos.release}"; # Did you read the comment? } @@ -173,10 +187,9 @@ in nixos-install nixos-rebuild nixos-generate-config - nixos-option nixos-version nixos-enter - ]; + ] ++ lib.optional (nixos-option != null) nixos-option; system.build = { inherit nixos-install nixos-generate-config nixos-option nixos-rebuild nixos-enter; diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix index deecb005270f0..7ad4be9a02e69 100644 --- a/nixos/modules/misc/documentation.nix +++ b/nixos/modules/misc/documentation.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, baseModules, extraModules, modules, ... }: +{ config, lib, pkgs, baseModules, extraModules, modules, modulesPath, ... }: with lib; @@ -17,12 +17,16 @@ let inherit pkgs config; version = config.system.nixos.release; revision = "release-${version}"; + extraSources = cfg.nixos.extraModuleSources; options = let scrubbedEval = evalModules { modules = [ { nixpkgs.localSystem = config.nixpkgs.localSystem; } ] ++ manualModules; args = (config._module.args) // { modules = [ ]; }; - specialArgs = { pkgs = scrubDerivations "pkgs" pkgs; }; + specialArgs = { + pkgs = scrubDerivations "pkgs" pkgs; + inherit modulesPath; + }; }; scrubDerivations = namePrefix: pkgSet: mapAttrs (name: value: @@ -67,6 +71,11 @@ let in { + imports = [ + (mkRenamedOptionModule [ "programs" "info" "enable" ] [ "documentation" "info" "enable" ]) + (mkRenamedOptionModule [ "programs" "man" "enable" ] [ "documentation" "man" "enable" ]) + (mkRenamedOptionModule [ "services" "nixosManual" "enable" ] [ "documentation" "nixos" "enable" ]) + ]; options = { @@ -155,6 +164,19 @@ in ''; }; + nixos.extraModuleSources = mkOption { + type = types.listOf (types.either types.path types.str); + default = [ ]; + description = '' + Which extra NixOS module paths the generated NixOS's documentation should strip + from options. + ''; + example = literalExample '' + # e.g. with options from modules in ''${pkgs.customModules}/nix: + [ pkgs.customModules ] + ''; + }; + }; }; @@ -196,9 +218,7 @@ in ++ optionals config.services.xserver.enable [ desktopItem pkgs.nixos-icons ]); services.mingetty.helpLine = mkIf cfg.doc.enable ( - "\nRun `nixos-help` " - + optionalString config.services.nixosManual.showManual "or press <Alt-F${toString config.services.nixosManual.ttyNumber}> " - + "for the NixOS manual." + "\nRun 'nixos-help' for the NixOS manual." ); }) diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix index f8b188e7b1c6d..85e5534e906fd 100644 --- a/nixos/modules/misc/ids.nix +++ b/nixos/modules/misc/ids.nix @@ -80,8 +80,8 @@ in #kdm = 39; # dropped in 17.03 #ghostone = 40; # dropped in 18.03 git = 41; - fourstore = 42; - fourstorehttp = 43; + #fourstore = 42; # dropped in 20.03 + #fourstorehttp = 43; # dropped in 20.03 virtuoso = 44; rtkit = 45; dovecot2 = 46; @@ -133,7 +133,7 @@ in tcpcryptd = 93; # tcpcryptd uses a hard-coded uid. We patch it in Nixpkgs to match this choice. firebird = 95; #keys = 96; # unused - #haproxy = 97; # DynamicUser as of 2019-11-08 + #haproxy = 97; # dynamically allocated as of 2020-03-11 mongodb = 98; openldap = 99; #users = 100; # unused @@ -299,7 +299,7 @@ in couchpotato = 267; gogs = 268; pdns-recursor = 269; - kresd = 270; + #kresd = 270; # switched to "knot-resolver" with dynamic ID rpc = 271; geoip = 272; fcron = 273; @@ -448,7 +448,7 @@ in #tcpcryptd = 93; # unused firebird = 95; keys = 96; - #haproxy = 97; # DynamicUser as of 2019-11-08 + #haproxy = 97; # dynamically allocated as of 2020-03-11 #mongodb = 98; # unused openldap = 99; munin = 102; @@ -600,7 +600,7 @@ in headphones = 266; couchpotato = 267; gogs = 268; - kresd = 270; + #kresd = 270; # switched to "knot-resolver" with dynamic ID #rpc = 271; # unused #geoip = 272; # unused fcron = 273; diff --git a/nixos/modules/misc/locate.nix b/nixos/modules/misc/locate.nix index 737ed5c0a3f6a..dc668796c7886 100644 --- a/nixos/modules/misc/locate.nix +++ b/nixos/modules/misc/locate.nix @@ -7,6 +7,11 @@ let isMLocate = hasPrefix "mlocate" cfg.locate.name; isFindutils = hasPrefix "findutils" cfg.locate.name; in { + imports = [ + (mkRenamedOptionModule [ "services" "locate" "period" ] [ "services" "locate" "interval" ]) + (mkRemovedOptionModule [ "services" "locate" "includeStore" ] "Use services.locate.prunePaths" ) + ]; + options.services.locate = with types; { enable = mkOption { type = bool; @@ -126,13 +131,6 @@ in { ++ optional (isFindutils && cfg.pruneNames != []) "findutils locate does not support pruning by directory component" ++ optional (isFindutils && cfg.pruneBindMounts) "findutils locate does not support skipping bind mounts"; - # directory creation needs to be separated from main service - # because ReadWritePaths fails when the directory doesn't already exist - systemd.tmpfiles.rules = - let dir = dirOf cfg.output; in - mkIf (dir != "/var/cache") - [ "d ${dir} 0755 root root -" ]; - systemd.services.update-locatedb = { description = "Update Locate Database"; path = mkIf (!isMLocate) [ pkgs.su ]; diff --git a/nixos/modules/misc/nixpkgs.nix b/nixos/modules/misc/nixpkgs.nix index afb74581e2398..4f5a9250eaaee 100644 --- a/nixos/modules/misc/nixpkgs.nix +++ b/nixos/modules/misc/nixpkgs.nix @@ -216,6 +216,14 @@ in Ignored when <code>nixpkgs.pkgs</code> is set. ''; }; + + initialSystem = mkOption { + type = types.str; + internal = true; + description = '' + Preserved value of <literal>system</literal> passed to <literal>eval-config.nix</literal>. + ''; + }; }; config = { @@ -228,8 +236,8 @@ in let nixosExpectedSystem = if config.nixpkgs.crossSystem != null - then config.nixpkgs.crossSystem.system - else config.nixpkgs.localSystem.system; + then config.nixpkgs.crossSystem.system or (lib.systems.parse.doubleFromSystem (lib.systems.parse.mkSystemFromString config.nixpkgs.crossSystem.config)) + else config.nixpkgs.localSystem.system or (lib.systems.parse.doubleFromSystem (lib.systems.parse.mkSystemFromString config.nixpkgs.localSystem.config)); nixosOption = if config.nixpkgs.crossSystem != null then "nixpkgs.crossSystem" diff --git a/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix index 773724ffbd5e8..a6fffb76f6e8a 100644 --- a/nixos/modules/misc/version.nix +++ b/nixos/modules/misc/version.nix @@ -4,12 +4,15 @@ with lib; let cfg = config.system.nixos; - - gitRepo = "${toString pkgs.path}/.git"; - gitCommitId = lib.substring 0 7 (commitIdFromGitRepo gitRepo); in { + imports = [ + (mkRenamedOptionModule [ "system" "nixosVersion" ] [ "system" "nixos" "version" ]) + (mkRenamedOptionModule [ "system" "nixosVersionSuffix" ] [ "system" "nixos" "versionSuffix" ]) + (mkRenamedOptionModule [ "system" "nixosRevision" ] [ "system" "nixos" "revision" ]) + (mkRenamedOptionModule [ "system" "nixosLabel" ] [ "system" "nixos" "label" ]) + ]; options.system = { @@ -35,8 +38,8 @@ in nixos.revision = mkOption { internal = true; - type = types.str; - default = trivial.revisionWithDefault "master"; + type = types.nullOr types.str; + default = trivial.revisionWithDefault null; description = "The Git revision from which this NixOS configuration was built."; }; @@ -55,21 +58,34 @@ in configuration defaults in a way incompatible with stateful data. For instance, if the default version of PostgreSQL changes, the new version will probably be unable to read your - existing databases. To prevent such breakage, you can set the + existing databases. To prevent such breakage, you should set the value of this option to the NixOS release with which you want - to be compatible. The effect is that NixOS will option + to be compatible. The effect is that NixOS will use defaults corresponding to the specified release (such as using an older version of PostgreSQL). + It‘s perfectly fine and recommended to leave this value at the + release version of the first install of this system. + Changing this option will not upgrade your system. In fact it + is meant to stay constant exactly when you upgrade your system. + You should only bump this option, if you are sure that you can + or have migrated all state on your system which is affected + by this option. ''; }; defaultChannel = mkOption { internal = true; type = types.str; - default = https://nixos.org/channels/nixos-unstable; + default = "https://nixos.org/channels/nixos-unstable"; description = "Default NixOS channel to which the root user is subscribed."; }; + configurationRevision = mkOption { + type = types.nullOr types.str; + default = null; + description = "The Git revision of the top-level flake from which this configuration was built."; + }; + }; config = { @@ -78,8 +94,6 @@ in # These defaults are set here rather than up there so that # changing them would not rebuild the manual version = mkDefault (cfg.release + cfg.versionSuffix); - revision = mkIf (pathIsDirectory gitRepo) (mkDefault gitCommitId); - versionSuffix = mkIf (pathIsDirectory gitRepo) (mkDefault (".git." + gitCommitId)); }; # Generate /etc/os-release. See @@ -92,11 +106,11 @@ in VERSION="${cfg.version} (${cfg.codeName})" VERSION_CODENAME=${toLower cfg.codeName} VERSION_ID="${cfg.version}" - PRETTY_NAME="NixOS ${cfg.version} (${cfg.codeName})" + PRETTY_NAME="NixOS ${cfg.release} (${cfg.codeName})" LOGO="nix-snowflake" HOME_URL="https://nixos.org/" - DOCUMENTATION_URL="https://nixos.org/nixos/manual/index.html" - SUPPORT_URL="https://nixos.org/nixos/support.html" + DOCUMENTATION_URL="https://nixos.org/learn.html" + SUPPORT_URL="https://nixos.org/community.html" BUG_REPORT_URL="https://github.com/NixOS/nixpkgs/issues" ''; diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 9e0d9478b5dea..7244a7e0a8948 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -11,6 +11,7 @@ ./config/xdg/mime.nix ./config/xdg/portal.nix ./config/appstream.nix + ./config/console.nix ./config/xdg/sounds.nix ./config/gtk/gtk-icon-cache.nix ./config/gnu.nix @@ -40,7 +41,6 @@ ./hardware/acpilight.nix ./hardware/all-firmware.nix ./hardware/bladeRF.nix - ./hardware/brightnessctl.nix ./hardware/brillo.nix ./hardware/ckb-next.nix ./hardware/cpu/amd-microcode.nix @@ -61,8 +61,11 @@ ./hardware/printers.nix ./hardware/raid/hpsa.nix ./hardware/steam-hardware.nix + ./hardware/tuxedo-keyboard.nix ./hardware/usb-wwan.nix ./hardware/onlykey.nix + ./hardware/wooting.nix + ./hardware/uinput.nix ./hardware/video/amdgpu.nix ./hardware/video/amdgpu-pro.nix ./hardware/video/ati.nix @@ -94,7 +97,9 @@ ./programs/adb.nix ./programs/atop.nix ./programs/autojump.nix + ./programs/bandwhich.nix ./programs/bash/bash.nix + ./programs/bash-my-aws.nix ./programs/bcc.nix ./programs/browserpass.nix ./programs/captive-browser.nix @@ -114,6 +119,7 @@ ./programs/fish.nix ./programs/freetds.nix ./programs/fuse.nix + ./programs/geary.nix ./programs/gnome-disks.nix ./programs/gnome-documents.nix ./programs/gnome-terminal.nix @@ -125,6 +131,7 @@ ./programs/java.nix ./programs/kbdlight.nix ./programs/less.nix + ./programs/liboping.nix ./programs/light.nix ./programs/mosh.nix ./programs/mininet.nix @@ -150,13 +157,13 @@ ./programs/system-config-printer.nix ./programs/thefuck.nix ./programs/tmux.nix + ./programs/traceroute.nix ./programs/tsm-client.nix ./programs/udevil.nix ./programs/usbtop.nix ./programs/venus.nix ./programs/vim.nix ./programs/wavemon.nix - ./programs/way-cooler.nix ./programs/waybar.nix ./programs/wireshark.nix ./programs/x2goserver.nix @@ -195,6 +202,7 @@ ./security/wrappers/default.nix ./security/sudo.nix ./security/systemd-confinement.nix + ./security/tpm2.nix ./services/admin/oxidized.nix ./services/admin/salt/master.nix ./services/admin/salt/minion.nix @@ -223,11 +231,14 @@ ./services/backup/restic.nix ./services/backup/restic-rest-server.nix ./services/backup/rsnapshot.nix + ./services/backup/sanoid.nix + ./services/backup/syncoid.nix ./services/backup/tarsnap.nix ./services/backup/tsm.nix ./services/backup/zfs-replication.nix ./services/backup/znapzend.nix ./services/cluster/hadoop/default.nix + ./services/cluster/k3s/default.nix ./services/cluster/kubernetes/addons/dns.nix ./services/cluster/kubernetes/addons/dashboard.nix ./services/cluster/kubernetes/addon-manager.nix @@ -240,12 +251,13 @@ ./services/cluster/kubernetes/proxy.nix ./services/cluster/kubernetes/scheduler.nix ./services/computing/boinc/client.nix - ./services/computing/torque/server.nix - ./services/computing/torque/mom.nix + ./services/computing/foldingathome/client.nix ./services/computing/slurm/slurm.nix + ./services/computing/torque/mom.nix + ./services/computing/torque/server.nix ./services/continuous-integration/buildbot/master.nix ./services/continuous-integration/buildbot/worker.nix - ./services/continuous-integration/buildkite-agent.nix + ./services/continuous-integration/buildkite-agents.nix ./services/continuous-integration/hail.nix ./services/continuous-integration/hydra/default.nix ./services/continuous-integration/gitlab-runner.nix @@ -254,8 +266,6 @@ ./services/continuous-integration/jenkins/default.nix ./services/continuous-integration/jenkins/job-builder.nix ./services/continuous-integration/jenkins/slave.nix - ./services/databases/4store-endpoint.nix - ./services/databases/4store.nix ./services/databases/aerospike.nix ./services/databases/cassandra.nix ./services/databases/clickhouse.nix @@ -278,6 +288,7 @@ ./services/databases/riak.nix ./services/databases/riak-cs.nix ./services/databases/stanchion.nix + ./services/databases/victoriametrics.nix ./services/databases/virtuoso.nix ./services/desktops/accountsservice.nix ./services/desktops/bamf.nix @@ -285,12 +296,12 @@ ./services/desktops/deepin/deepin.nix ./services/desktops/dleyna-renderer.nix ./services/desktops/dleyna-server.nix - ./services/desktops/pantheon/contractor.nix ./services/desktops/pantheon/files.nix ./services/desktops/flatpak.nix ./services/desktops/geoclue2.nix ./services/desktops/gsignond.nix ./services/desktops/gvfs.nix + ./services/desktops/malcontent.nix ./services/desktops/pipewire.nix ./services/desktops/gnome3/at-spi2-core.nix ./services/desktops/gnome3/chrome-gnome-shell.nix @@ -307,6 +318,7 @@ ./services/desktops/gnome3/sushi.nix ./services/desktops/gnome3/tracker.nix ./services/desktops/gnome3/tracker-miners.nix + ./services/desktops/neard.nix ./services/desktops/profile-sync-daemon.nix ./services/desktops/system-config-printer.nix ./services/desktops/telepathy.nix @@ -357,6 +369,7 @@ ./services/hardware/thermald.nix ./services/hardware/undervolt.nix ./services/hardware/vdr.nix + ./services/hardware/xow.nix ./services/logging/SystemdJournal2Gelf.nix ./services/logging/awstats.nix ./services/logging/fluentd.nix @@ -395,8 +408,10 @@ ./services/mail/rspamd.nix ./services/mail/rss2email.nix ./services/mail/roundcube.nix + ./services/mail/sympa.nix ./services/mail/nullmailer.nix ./services/misc/airsonic.nix + ./services/misc/ankisyncd.nix ./services/misc/apache-kafka.nix ./services/misc/autofs.nix ./services/misc/autorandr.nix @@ -422,7 +437,7 @@ ./services/misc/ethminer.nix ./services/misc/exhibitor.nix ./services/misc/felix.nix - ./services/misc/folding-at-home.nix + ./services/misc/freeswitch.nix ./services/misc/fstrim.nix ./services/misc/gammu-smsd.nix ./services/misc/geoip-updater.nix @@ -444,6 +459,7 @@ ./services/misc/logkeys.nix ./services/misc/leaps.nix ./services/misc/lidarr.nix + ./services/misc/mame.nix ./services/misc/mathics.nix ./services/misc/matrix-synapse.nix ./services/misc/mbpfan.nix @@ -455,7 +471,6 @@ ./services/misc/nix-daemon.nix ./services/misc/nix-gc.nix ./services/misc/nix-optimise.nix - ./services/misc/nixos-manual.nix ./services/misc/nix-ssh-serve.nix ./services/misc/novacomd.nix ./services/misc/nzbget.nix @@ -471,7 +486,6 @@ ./services/misc/redmine.nix ./services/misc/rippled.nix ./services/misc/ripple-data-api.nix - ./services/misc/rogue.nix ./services/misc/serviio.nix ./services/misc/safeeyes.nix ./services/misc/sickbeard.nix @@ -522,6 +536,7 @@ ./services/monitoring/prometheus/alertmanager.nix ./services/monitoring/prometheus/exporters.nix ./services/monitoring/prometheus/pushgateway.nix + ./services/monitoring/prometheus/xmpp-alerts.nix ./services/monitoring/riemann.nix ./services/monitoring/riemann-dash.nix ./services/monitoring/riemann-tools.nix @@ -531,6 +546,7 @@ ./services/monitoring/teamviewer.nix ./services/monitoring/telegraf.nix ./services/monitoring/thanos.nix + ./services/monitoring/tuptime.nix ./services/monitoring/ups.nix ./services/monitoring/uptime.nix ./services/monitoring/vnstat.nix @@ -557,6 +573,7 @@ ./services/network-filesystems/yandex-disk.nix ./services/network-filesystems/xtreemfs.nix ./services/network-filesystems/ceph.nix + ./services/networking/3proxy.nix ./services/networking/amuled.nix ./services/networking/aria2.nix ./services/networking/asterisk.nix @@ -574,6 +591,7 @@ ./services/networking/connman.nix ./services/networking/consul.nix ./services/networking/coredns.nix + ./services/networking/corerad.nix ./services/networking/coturn.nix ./services/networking/dante.nix ./services/networking/ddclient.nix @@ -581,7 +599,7 @@ ./services/networking/dhcpd.nix ./services/networking/dnscache.nix ./services/networking/dnschain.nix - ./services/networking/dnscrypt-proxy.nix + ./services/networking/dnscrypt-proxy2.nix ./services/networking/dnscrypt-wrapper.nix ./services/networking/dnsdist.nix ./services/networking/dnsmasq.nix @@ -627,6 +645,7 @@ ./services/networking/lldpd.nix ./services/networking/logmein-hamachi.nix ./services/networking/mailpile.nix + ./services/networking/magic-wormhole-mailbox-server.nix ./services/networking/matterbridge.nix ./services/networking/mjpg-streamer.nix ./services/networking/minidlna.nix @@ -637,6 +656,7 @@ ./services/networking/miredo.nix ./services/networking/mstpd.nix ./services/networking/mtprotoproxy.nix + ./services/networking/mullvad-vpn.nix ./services/networking/murmur.nix ./services/networking/mxisd.nix ./services/networking/namecoind.nix @@ -647,6 +667,7 @@ ./services/networking/ngircd.nix ./services/networking/nghttpx/default.nix ./services/networking/nix-serve.nix + ./services/networking/nix-store-gcs-proxy.nix ./services/networking/nixops-dns.nix ./services/networking/nntp-proxy.nix ./services/networking/nsd.nix @@ -664,6 +685,7 @@ ./services/networking/ostinato.nix ./services/networking/owamp.nix ./services/networking/pdnsd.nix + ./services/networking/pixiecore.nix ./services/networking/polipo.nix ./services/networking/powerdns.nix ./services/networking/pdns-recursor.nix @@ -674,6 +696,7 @@ ./services/networking/prosody.nix ./services/networking/quagga.nix ./services/networking/quassel.nix + ./services/networking/quorum.nix ./services/networking/quicktun.nix ./services/networking/racoon.nix ./services/networking/radicale.nix @@ -689,10 +712,14 @@ ./services/networking/skydns.nix ./services/networking/shadowsocks.nix ./services/networking/shairport-sync.nix + ./services/networking/shorewall.nix + ./services/networking/shorewall6.nix ./services/networking/shout.nix ./services/networking/sniproxy.nix + ./services/networking/smartdns.nix ./services/networking/smokeping.nix ./services/networking/softether.nix + ./services/networking/spacecookie.nix ./services/networking/spiped.nix ./services/networking/squid.nix ./services/networking/sslh.nix @@ -707,6 +734,7 @@ ./services/networking/syncthing.nix ./services/networking/syncthing-relay.nix ./services/networking/syncplay.nix + ./services/networking/tailscale.nix ./services/networking/tcpcrypt.nix ./services/networking/teamspeak3.nix ./services/networking/tedicross.nix @@ -721,6 +749,7 @@ ./services/networking/tvheadend.nix ./services/networking/unbound.nix ./services/networking/unifi.nix + ./services/networking/v2ray.nix ./services/networking/vsftpd.nix ./services/networking/wakeonlan.nix ./services/networking/websockify.nix @@ -728,6 +757,7 @@ ./services/networking/wicd.nix ./services/networking/wireguard.nix ./services/networking/wpa_supplicant.nix + ./services/networking/xandikos.nix ./services/networking/xinetd.nix ./services/networking/xl2tpd.nix ./services/networking/xrdp.nix @@ -789,16 +819,22 @@ ./services/ttys/agetty.nix ./services/ttys/gpm.nix ./services/ttys/kmscon.nix + ./services/wayland/cage.nix ./services/web-apps/atlassian/confluence.nix ./services/web-apps/atlassian/crowd.nix ./services/web-apps/atlassian/jira.nix ./services/web-apps/codimd.nix ./services/web-apps/cryptpad.nix ./services/web-apps/documize.nix + ./services/web-apps/dokuwiki.nix ./services/web-apps/frab.nix + ./services/web-apps/gerrit.nix ./services/web-apps/gotify-server.nix + ./services/web-apps/grocy.nix ./services/web-apps/icingaweb2/icingaweb2.nix ./services/web-apps/icingaweb2/module-monitoring.nix + ./services/web-apps/ihatemoney + ./services/web-apps/jirafeau.nix ./services/web-apps/limesurvey.nix ./services/web-apps/mattermost.nix ./services/web-apps/mediawiki.nix @@ -812,6 +848,7 @@ ./services/web-apps/restya-board.nix ./services/web-apps/tt-rss.nix ./services/web-apps/trac.nix + ./services/web-apps/trilium.nix ./services/web-apps/selfoss.nix ./services/web-apps/shiori.nix ./services/web-apps/virtlyst.nix @@ -846,11 +883,10 @@ ./services/x11/extra-layouts.nix ./services/x11/clight.nix ./services/x11/colord.nix - ./services/x11/compton.nix + ./services/x11/picom.nix ./services/x11/unclutter.nix ./services/x11/unclutter-xfixes.nix ./services/x11/desktop-managers/default.nix - ./services/x11/display-managers/auto.nix ./services/x11/display-managers/default.nix ./services/x11/display-managers/gdm.nix ./services/x11/display-managers/lightdm.nix @@ -860,12 +896,12 @@ ./services/x11/display-managers/xpra.nix ./services/x11/fractalart.nix ./services/x11/hardware/libinput.nix - ./services/x11/hardware/multitouch.nix ./services/x11/hardware/synaptics.nix ./services/x11/hardware/wacom.nix ./services/x11/hardware/digimend.nix ./services/x11/hardware/cmt.nix ./services/x11/gdk-pixbuf.nix + ./services/x11/imwheel.nix ./services/x11/redshift.nix ./services/x11/urxvtd.nix ./services/x11/window-managers/awesome.nix @@ -936,7 +972,6 @@ ./tasks/filesystems/vfat.nix ./tasks/filesystems/xfs.nix ./tasks/filesystems/zfs.nix - ./tasks/kbd.nix ./tasks/lvm.nix ./tasks/network-interfaces.nix ./tasks/network-interfaces-systemd.nix @@ -949,6 +984,7 @@ ./virtualisation/anbox.nix ./virtualisation/container-config.nix ./virtualisation/containers.nix + ./virtualisation/nixos-containers.nix ./virtualisation/cri-o.nix ./virtualisation/docker.nix ./virtualisation/docker-containers.nix @@ -962,6 +998,7 @@ ./virtualisation/kvmgt.nix ./virtualisation/openvswitch.nix ./virtualisation/parallels-guest.nix + ./virtualisation/podman.nix ./virtualisation/qemu-guest-agent.nix ./virtualisation/railcar.nix ./virtualisation/rkt.nix diff --git a/nixos/modules/profiles/hardened.nix b/nixos/modules/profiles/hardened.nix index f7b2f5c7fc1e8..ef8c0d74f0628 100644 --- a/nixos/modules/profiles/hardened.nix +++ b/nixos/modules/profiles/hardened.nix @@ -7,19 +7,20 @@ with lib; { meta = { - maintainers = [ maintainers.joachifm ]; + maintainers = [ maintainers.joachifm maintainers.emily ]; }; boot.kernelPackages = mkDefault pkgs.linuxPackages_hardened; nix.allowedUsers = mkDefault [ "@users" ]; + environment.memoryAllocator.provider = mkDefault "scudo"; + environment.variables.SCUDO_OPTIONS = mkDefault "ZeroContents=1"; + security.hideProcessInformation = mkDefault true; security.lockKernelModules = mkDefault true; - security.allowUserNamespaces = mkDefault false; - security.protectKernelImage = mkDefault true; security.allowSimultaneousMultithreading = mkDefault false; @@ -34,15 +35,9 @@ with lib; # Slab/slub sanity checks, redzoning, and poisoning "slub_debug=FZP" - # Disable slab merging to make certain heap overflow attacks harder - "slab_nomerge" - # Overwrite free'd memory "page_poison=1" - # Disable legacy virtual syscalls - "vsyscall=none" - # Enable page allocator randomization "page_alloc.shuffle=1" ]; @@ -79,38 +74,12 @@ with lib; # (e.g., parent/child) boot.kernel.sysctl."kernel.yama.ptrace_scope" = mkOverride 500 1; - # Restrict access to kernel ring buffer (information leaks) - boot.kernel.sysctl."kernel.dmesg_restrict" = mkDefault true; - # Hide kptrs even for processes with CAP_SYSLOG boot.kernel.sysctl."kernel.kptr_restrict" = mkOverride 500 2; - # Unprivileged access to bpf() has been used for privilege escalation in - # the past - boot.kernel.sysctl."kernel.unprivileged_bpf_disabled" = mkDefault true; - # Disable bpf() JIT (to eliminate spray attacks) boot.kernel.sysctl."net.core.bpf_jit_enable" = mkDefault false; - # ... or at least apply some hardening to it - boot.kernel.sysctl."net.core.bpf_jit_harden" = mkDefault true; - - # Raise ASLR entropy for 64bit & 32bit, respectively. - # - # Note: mmap_rnd_compat_bits may not exist on 64bit. - boot.kernel.sysctl."vm.mmap_rnd_bits" = mkDefault 32; - boot.kernel.sysctl."vm.mmap_rnd_compat_bits" = mkDefault 16; - - # Allowing users to mmap() memory starting at virtual address 0 can turn a - # NULL dereference bug in the kernel into code execution with elevated - # privilege. Mitigate by enforcing a minimum base addr beyond the NULL memory - # space. This breaks applications that require mapping the 0 page, such as - # dosemu or running 16bit applications under wine. It also breaks older - # versions of qemu. - # - # The value is taken from the KSPP recommendations (Debian uses 4096). - boot.kernel.sysctl."vm.mmap_min_addr" = mkDefault 65536; - # Disable ftrace debugging boot.kernel.sysctl."kernel.ftrace_enabled" = mkDefault false; @@ -137,7 +106,4 @@ with lib; # Ignore outgoing ICMP redirects (this is ipv4 only) boot.kernel.sysctl."net.ipv4.conf.all.send_redirects" = mkDefault false; boot.kernel.sysctl."net.ipv4.conf.default.send_redirects" = mkDefault false; - - # Restrict userfaultfd syscalls to processes with the SYS_PTRACE capability - boot.kernel.sysctl."vm.unprivileged_userfaultfd" = mkDefault false; } diff --git a/nixos/modules/profiles/installation-device.nix b/nixos/modules/profiles/installation-device.nix index 4596e163404ca..d05c0c50e82c1 100644 --- a/nixos/modules/profiles/installation-device.nix +++ b/nixos/modules/profiles/installation-device.nix @@ -26,10 +26,6 @@ with lib; # Show the manual. documentation.nixos.enable = mkForce true; - services.nixosManual.showManual = true; - - # Let the user play Rogue on TTY 8 during the installation. - #services.rogue.enable = true; # Use less privileged nixos user users.users.nixos = { diff --git a/nixos/modules/programs/bandwhich.nix b/nixos/modules/programs/bandwhich.nix new file mode 100644 index 0000000000000..5413044f46141 --- /dev/null +++ b/nixos/modules/programs/bandwhich.nix @@ -0,0 +1,29 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let cfg = config.programs.bandwhich; +in { + meta.maintainers = with maintainers; [ filalex77 ]; + + options = { + programs.bandwhich = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to add bandwhich to the global environment and configure a + setcap wrapper for it. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ bandwhich ]; + security.wrappers.bandwhich = { + source = "${pkgs.bandwhich}/bin/bandwhich"; + capabilities = "cap_net_raw,cap_net_admin+ep"; + }; + }; +} diff --git a/nixos/modules/programs/bash-my-aws.nix b/nixos/modules/programs/bash-my-aws.nix new file mode 100644 index 0000000000000..15e429a754976 --- /dev/null +++ b/nixos/modules/programs/bash-my-aws.nix @@ -0,0 +1,25 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + prg = config.programs; + cfg = prg.bash-my-aws; + + initScript = '' + eval $(${pkgs.bash-my-aws}/bin/bma-init) + ''; +in + { + options = { + programs.bash-my-aws = { + enable = mkEnableOption "bash-my-aws"; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ bash-my-aws ]; + + programs.bash.interactiveShellInit = initScript; + }; + } diff --git a/nixos/modules/programs/bash/bash.nix b/nixos/modules/programs/bash/bash.nix index 548babac38ca1..be964ce7f3f9a 100644 --- a/nixos/modules/programs/bash/bash.nix +++ b/nixos/modules/programs/bash/bash.nix @@ -32,6 +32,10 @@ let fi ''; + lsColors = optionalString cfg.enableLsColors '' + eval "$(${pkgs.coreutils}/bin/dircolors -b)" + ''; + bashAliases = concatStringsSep "\n" ( mapAttrsFlatten (k: v: "alias ${k}=${escapeShellArg v}") (filterAttrs (k: v: v != null) cfg.shellAliases) @@ -40,6 +44,10 @@ let in { + imports = [ + (mkRemovedOptionModule [ "programs" "bash" "enable" ] "") + ]; + options = { programs.bash = { @@ -123,6 +131,14 @@ in type = types.bool; }; + enableLsColors = mkOption { + default = true; + description = '' + Enable extra colors in directory listings. + ''; + type = types.bool; + }; + }; }; @@ -152,6 +168,7 @@ in ${cfg.promptInit} ${bashCompletion} + ${lsColors} ${bashAliases} ${cfge.interactiveShellInit} diff --git a/nixos/modules/programs/captive-browser.nix b/nixos/modules/programs/captive-browser.nix index 55d474e5c9db0..26db167507271 100644 --- a/nixos/modules/programs/captive-browser.nix +++ b/nixos/modules/programs/captive-browser.nix @@ -28,7 +28,7 @@ in browser = mkOption { type = types.str; default = concatStringsSep " " [ ''${pkgs.chromium}/bin/chromium'' - ''--user-data-dir=$HOME/.chromium-captive'' + ''--user-data-dir=''${XDG_DATA_HOME:-$HOME/.local/share}/chromium-captive'' ''--proxy-server="socks5://$PROXY"'' ''--host-resolver-rules="MAP * ~NOTFOUND , EXCLUDE localhost"'' ''--no-first-run'' diff --git a/nixos/modules/programs/cdemu.nix b/nixos/modules/programs/cdemu.nix index 6a0185d362c50..a59cd93cadfc0 100644 --- a/nixos/modules/programs/cdemu.nix +++ b/nixos/modules/programs/cdemu.nix @@ -8,6 +8,7 @@ in { options = { programs.cdemu = { enable = mkOption { + type = types.bool; default = false; description = '' <command>cdemu</command> for members of diff --git a/nixos/modules/programs/criu.nix b/nixos/modules/programs/criu.nix index 48cf5c88a9fc1..1714e1331a485 100644 --- a/nixos/modules/programs/criu.nix +++ b/nixos/modules/programs/criu.nix @@ -8,6 +8,7 @@ in { options = { programs.criu = { enable = mkOption { + type = types.bool; default = false; description = '' Install <command>criu</command> along with necessary kernel options. diff --git a/nixos/modules/programs/dconf.nix b/nixos/modules/programs/dconf.nix index eeebc3558bdf9..6702e8efd1cb4 100644 --- a/nixos/modules/programs/dconf.nix +++ b/nixos/modules/programs/dconf.nix @@ -6,7 +6,10 @@ let cfg = config.programs.dconf; mkDconfProfile = name: path: - { source = path; target = "dconf/profile/${name}"; }; + { + name = "dconf/profile/${name}"; + value.source = path; + }; in { @@ -29,16 +32,16 @@ in ###### implementation config = mkIf (cfg.profiles != {} || cfg.enable) { - environment.etc = optionals (cfg.profiles != {}) - (mapAttrsToList mkDconfProfile cfg.profiles); + environment.etc = optionalAttrs (cfg.profiles != {}) + (mapAttrs' mkDconfProfile cfg.profiles); - services.dbus.packages = [ pkgs.gnome3.dconf ]; + services.dbus.packages = [ pkgs.dconf ]; # For dconf executable - environment.systemPackages = [ pkgs.gnome3.dconf ]; + environment.systemPackages = [ pkgs.dconf ]; # Needed for unwrapped applications - environment.variables.GIO_EXTRA_MODULES = mkIf cfg.enable [ "${pkgs.gnome3.dconf.lib}/lib/gio/modules" ]; + environment.variables.GIO_EXTRA_MODULES = mkIf cfg.enable [ "${pkgs.dconf.lib}/lib/gio/modules" ]; }; } diff --git a/nixos/modules/programs/firejail.nix b/nixos/modules/programs/firejail.nix index 74c3e4425a7c5..484f9eb444060 100644 --- a/nixos/modules/programs/firejail.nix +++ b/nixos/modules/programs/firejail.nix @@ -5,28 +5,34 @@ with lib; let cfg = config.programs.firejail; - wrappedBins = pkgs.stdenv.mkDerivation { - name = "firejail-wrapped-binaries"; - nativeBuildInputs = with pkgs; [ makeWrapper ]; - buildCommand = '' + wrappedBins = pkgs.runCommand "firejail-wrapped-binaries" + { preferLocalBuild = true; + allowSubstitutes = false; + } + '' mkdir -p $out/bin ${lib.concatStringsSep "\n" (lib.mapAttrsToList (command: binary: '' - cat <<_EOF >$out/bin/${command} - #!${pkgs.stdenv.shell} -e - /run/wrappers/bin/firejail ${binary} "\$@" - _EOF - chmod 0755 $out/bin/${command} + cat <<_EOF >$out/bin/${command} + #! ${pkgs.runtimeShell} -e + exec /run/wrappers/bin/firejail ${binary} "\$@" + _EOF + chmod 0755 $out/bin/${command} '') cfg.wrappedBinaries)} ''; - }; in { options.programs.firejail = { enable = mkEnableOption "firejail"; wrappedBinaries = mkOption { - type = types.attrs; + type = types.attrsOf types.path; default = {}; + example = literalExample '' + { + firefox = "''${lib.getBin pkgs.firefox}/bin/firefox"; + mpv = "''${lib.getBin pkgs.mpv}/bin/mpv"; + } + ''; description = '' Wrap the binaries in firejail and place them in the global path. </para> @@ -41,7 +47,7 @@ in { config = mkIf cfg.enable { security.wrappers.firejail.source = "${lib.getBin pkgs.firejail}/bin/firejail"; - environment.systemPackages = [ wrappedBins ]; + environment.systemPackages = [ pkgs.firejail ] ++ [ wrappedBins ]; }; meta.maintainers = with maintainers; [ peterhoeg ]; diff --git a/nixos/modules/programs/fish_completion-generator.patch b/nixos/modules/programs/fish_completion-generator.patch index a8c797d185a68..997f38c5066dc 100644 --- a/nixos/modules/programs/fish_completion-generator.patch +++ b/nixos/modules/programs/fish_completion-generator.patch @@ -1,11 +1,13 @@ --- a/create_manpage_completions.py +++ b/create_manpage_completions.py -@@ -776,8 +776,6 @@ def parse_manpage_at_path(manpage_path, output_directory): - +@@ -844,10 +844,6 @@ def parse_manpage_at_path(manpage_path, output_directory): + built_command_output.insert(0, "# " + CMDNAME) - + - # Output the magic word Autogenerated so we can tell if we can overwrite this -- built_command_output.insert(1, "# Autogenerated from man page " + manpage_path) +- built_command_output.insert( +- 1, "# Autogenerated from man page " + manpage_path +- ) # built_command_output.insert(2, "# using " + parser.__class__.__name__) # XXX MISATTRIBUTES THE CULPABILE PARSER! Was really using Type2 but reporting TypeDeroffManParser - + for line in built_command_output: diff --git a/nixos/modules/programs/geary.nix b/nixos/modules/programs/geary.nix new file mode 100644 index 0000000000000..5e441a75cb602 --- /dev/null +++ b/nixos/modules/programs/geary.nix @@ -0,0 +1,24 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.programs.geary; + +in { + meta = { + maintainers = teams.gnome.members; + }; + + options = { + programs.geary.enable = mkEnableOption "Geary, a Mail client for GNOME 3"; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ pkgs.gnome3.geary ]; + programs.dconf.enable = true; + services.gnome3.gnome-keyring.enable = true; + services.gnome3.gnome-online-accounts.enable = true; + }; +} + diff --git a/nixos/modules/programs/gnome-disks.nix b/nixos/modules/programs/gnome-disks.nix index 1cf839a6ddb0d..80dc2983ea504 100644 --- a/nixos/modules/programs/gnome-disks.nix +++ b/nixos/modules/programs/gnome-disks.nix @@ -6,6 +6,10 @@ with lib; { + meta = { + maintainers = teams.gnome.members; + }; + # Added 2019-08-09 imports = [ (mkRenamedOptionModule diff --git a/nixos/modules/programs/gnome-documents.nix b/nixos/modules/programs/gnome-documents.nix index bfa3d409ee301..9dd53483055cd 100644 --- a/nixos/modules/programs/gnome-documents.nix +++ b/nixos/modules/programs/gnome-documents.nix @@ -6,6 +6,10 @@ with lib; { + meta = { + maintainers = teams.gnome.members; + }; + # Added 2019-08-09 imports = [ (mkRenamedOptionModule diff --git a/nixos/modules/programs/gnome-terminal.nix b/nixos/modules/programs/gnome-terminal.nix index 0036677a15765..f2617e5bc0385 100644 --- a/nixos/modules/programs/gnome-terminal.nix +++ b/nixos/modules/programs/gnome-terminal.nix @@ -12,6 +12,10 @@ in { + meta = { + maintainers = teams.gnome.members; + }; + # Added 2019-08-19 imports = [ (mkRenamedOptionModule @@ -20,9 +24,7 @@ in ]; options = { - programs.gnome-terminal.enable = mkEnableOption "GNOME Terminal"; - }; config = mkIf cfg.enable { diff --git a/nixos/modules/programs/gnupg.nix b/nixos/modules/programs/gnupg.nix index 2d262d9065796..7a3cb588ee719 100644 --- a/nixos/modules/programs/gnupg.nix +++ b/nixos/modules/programs/gnupg.nix @@ -96,7 +96,7 @@ in # This overrides the systemd user unit shipped with the gnupg package systemd.user.services.gpg-agent = mkIf (cfg.agent.pinentryFlavor != null) { serviceConfig.ExecStart = [ "" '' - ${pkgs.gnupg}/bin/gpg-agent --supervised \ + ${cfg.package}/bin/gpg-agent --supervised \ --pinentry-program ${pkgs.pinentry.${cfg.agent.pinentryFlavor}}/bin/pinentry '' ]; }; diff --git a/nixos/modules/programs/liboping.nix b/nixos/modules/programs/liboping.nix new file mode 100644 index 0000000000000..4e4c235ccde4a --- /dev/null +++ b/nixos/modules/programs/liboping.nix @@ -0,0 +1,22 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.programs.liboping; +in { + options.programs.liboping = { + enable = mkEnableOption "liboping"; + }; + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ liboping ]; + security.wrappers = mkMerge (map ( + exec: { + "${exec}" = { + source = "${pkgs.liboping}/bin/${exec}"; + capabilities = "cap_net_raw+p"; + }; + } + ) [ "oping" "noping" ]); + }; +} diff --git a/nixos/modules/programs/nm-applet.nix b/nixos/modules/programs/nm-applet.nix index e42219e9638c9..273a6dec59a58 100644 --- a/nixos/modules/programs/nm-applet.nix +++ b/nixos/modules/programs/nm-applet.nix @@ -1,6 +1,10 @@ { config, lib, pkgs, ... }: { + meta = { + maintainers = lib.teams.freedesktop.members; + }; + options.programs.nm-applet.enable = lib.mkEnableOption "nm-applet"; config = lib.mkIf config.programs.nm-applet.enable { @@ -10,5 +14,7 @@ partOf = [ "graphical-session.target" ]; serviceConfig.ExecStart = "${pkgs.networkmanagerapplet}/bin/nm-applet"; }; + + services.dbus.packages = [ pkgs.gcr ]; }; } diff --git a/nixos/modules/programs/npm.nix b/nixos/modules/programs/npm.nix index b351d80c7acfc..f101a44587a16 100644 --- a/nixos/modules/programs/npm.nix +++ b/nixos/modules/programs/npm.nix @@ -13,7 +13,14 @@ in programs.npm = { enable = mkEnableOption "<command>npm</command> global config"; - npmrc = lib.mkOption { + package = mkOption { + type = types.path; + description = "The npm package version / flavor to use"; + default = pkgs.nodePackages.npm; + example = literalExample "pkgs.nodePackages_13_x.npm"; + }; + + npmrc = mkOption { type = lib.types.lines; description = '' The system-wide npm configuration. @@ -40,7 +47,7 @@ in environment.variables.NPM_CONFIG_GLOBALCONFIG = "/etc/npmrc"; - environment.systemPackages = [ pkgs.nodePackages.npm ]; + environment.systemPackages = [ cfg.package ]; }; } diff --git a/nixos/modules/programs/oblogout.nix b/nixos/modules/programs/oblogout.nix index 720c29b1eaeef..a039b0623b52c 100644 --- a/nixos/modules/programs/oblogout.nix +++ b/nixos/modules/programs/oblogout.nix @@ -1,176 +1,11 @@ -# Global configuration for oblogout. - { config, lib, pkgs, ... }: with lib; -let cfg = config.programs.oblogout; - -in { - ###### interface - - options = { - - programs.oblogout = { - - enable = mkOption { - type = types.bool; - default = false; - description = '' - Whether to install OBLogout and create <filename>/etc/oblogout.conf</filename>. - See <filename>${pkgs.oblogout}/share/doc/README</filename>. - ''; - }; - - opacity = mkOption { - type = types.int; - default = 70; - description = '' - Opacity percentage of Cairo rendered backgrounds. - ''; - }; - - bgcolor = mkOption { - type = types.str; - default = "black"; - description = '' - Colour name or hex code (#ffffff) of the background color. - ''; - }; - - buttontheme = mkOption { - type = types.str; - default = "simplistic"; - description = '' - Icon theme for the buttons, must be in the themes folder of - the package, or in - <filename>~/.themes/<name>/oblogout/</filename>. - ''; - }; - - buttons = mkOption { - type = types.str; - default = "cancel, logout, restart, shutdown, suspend, hibernate"; - description = '' - List and order of buttons to show. - ''; - }; - - cancel = mkOption { - type = types.str; - default = "Escape"; - description = '' - Cancel logout/shutdown shortcut. - ''; - }; - - shutdown = mkOption { - type = types.str; - default = "S"; - description = '' - Shutdown shortcut. - ''; - }; - - restart = mkOption { - type = types.str; - default = "R"; - description = '' - Restart shortcut. - ''; - }; - - suspend = mkOption { - type = types.str; - default = "U"; - description = '' - Suspend shortcut. - ''; - }; - - logout = mkOption { - type = types.str; - default = "L"; - description = '' - Logout shortcut. - ''; - }; - - lock = mkOption { - type = types.str; - default = "K"; - description = '' - Lock session shortcut. - ''; - }; - - hibernate = mkOption { - type = types.str; - default = "H"; - description = '' - Hibernate shortcut. - ''; - }; - - clogout = mkOption { - type = types.str; - default = "openbox --exit"; - description = '' - Command to logout. - ''; - }; - - clock = mkOption { - type = types.str; - default = ""; - description = '' - Command to lock screen. - ''; - }; - - cswitchuser = mkOption { - type = types.str; - default = ""; - description = '' - Command to switch user. - ''; - }; - }; - }; - - ###### implementation - - config = mkIf cfg.enable { - environment.systemPackages = [ pkgs.oblogout ]; - - environment.etc."oblogout.conf".text = '' - [settings] - usehal = false - - [looks] - opacity = ${toString cfg.opacity} - bgcolor = ${cfg.bgcolor} - buttontheme = ${cfg.buttontheme} - buttons = ${cfg.buttons} - [shortcuts] - cancel = ${cfg.cancel} - shutdown = ${cfg.shutdown} - restart = ${cfg.restart} - suspend = ${cfg.suspend} - logout = ${cfg.logout} - lock = ${cfg.lock} - hibernate = ${cfg.hibernate} + imports = [ + (mkRemovedOptionModule [ "programs" "oblogout" ] "programs.oblogout has been removed from NixOS. This is because the oblogout repository has been archived upstream.") + ]; - [commands] - shutdown = systemctl poweroff - restart = systemctl reboot - suspend = systemctl suspend - hibernate = systemctl hibernate - logout = ${cfg.clogout} - lock = ${cfg.clock} - switchuser = ${cfg.cswitchuser} - ''; - }; } diff --git a/nixos/modules/programs/screen.nix b/nixos/modules/programs/screen.nix index 4fd800dbae79a..728a0eb8cea5b 100644 --- a/nixos/modules/programs/screen.nix +++ b/nixos/modules/programs/screen.nix @@ -27,6 +27,7 @@ in environment.etc.screenrc.text = cfg.screenrc; environment.systemPackages = [ pkgs.screen ]; + security.pam.services.screen = {}; }; } diff --git a/nixos/modules/programs/shadow.nix b/nixos/modules/programs/shadow.nix index 7eaf79d864e79..fc352795c017c 100644 --- a/nixos/modules/programs/shadow.nix +++ b/nixos/modules/programs/shadow.nix @@ -76,22 +76,18 @@ in config.users.defaultUserShell; environment.etc = - [ { # /etc/login.defs: global configuration for pwdutils. You - # cannot login without it! - source = pkgs.writeText "login.defs" loginDefs; - target = "login.defs"; - } - - { # /etc/default/useradd: configuration for useradd. - source = pkgs.writeText "useradd" - '' - GROUP=100 - HOME=/home - SHELL=${utils.toShellPath config.users.defaultUserShell} - ''; - target = "default/useradd"; - } - ]; + { # /etc/login.defs: global configuration for pwdutils. You + # cannot login without it! + "login.defs".source = pkgs.writeText "login.defs" loginDefs; + + # /etc/default/useradd: configuration for useradd. + "default/useradd".source = pkgs.writeText "useradd" + '' + GROUP=100 + HOME=/home + SHELL=${utils.toShellPath config.users.defaultUserShell} + ''; + }; security.pam.services = { chsh = { rootOK = true; }; diff --git a/nixos/modules/programs/ssh.nix b/nixos/modules/programs/ssh.nix index 80198990ed110..44e65ee8a9a05 100644 --- a/nixos/modules/programs/ssh.nix +++ b/nixos/modules/programs/ssh.nix @@ -61,12 +61,9 @@ in ''; }; - # Allow DSA keys for now. (These were deprecated in OpenSSH 7.0.) pubkeyAcceptedKeyTypes = mkOption { type = types.listOf types.str; - default = [ - "+ssh-dss" - ]; + default = []; example = [ "ssh-ed25519" "ssh-rsa" ]; description = '' Specifies the key types that will be used for public key authentication. @@ -75,9 +72,7 @@ in hostKeyAlgorithms = mkOption { type = types.listOf types.str; - default = [ - "+ssh-dss" - ]; + default = []; example = [ "ssh-ed25519" "ssh-rsa" ]; description = '' Specifies the host key algorithms that the client wants to use in order of preference. diff --git a/nixos/modules/programs/ssmtp.nix b/nixos/modules/programs/ssmtp.nix index 0e060e3f52261..c7a947393496d 100644 --- a/nixos/modules/programs/ssmtp.nix +++ b/nixos/modules/programs/ssmtp.nix @@ -8,18 +8,29 @@ with lib; let - - cfg = config.networking.defaultMailServer; + cfg = config.services.ssmtp; in - { + imports = [ + (mkRenamedOptionModule [ "networking" "defaultMailServer" "directDelivery" ] [ "services" "ssmtp" "enable" ]) + (mkRenamedOptionModule [ "networking" "defaultMailServer" "hostName" ] [ "services" "ssmtp" "hostName" ]) + (mkRenamedOptionModule [ "networking" "defaultMailServer" "domain" ] [ "services" "ssmtp" "domain" ]) + (mkRenamedOptionModule [ "networking" "defaultMailServer" "root" ] [ "services" "ssmtp" "root" ]) + (mkRenamedOptionModule [ "networking" "defaultMailServer" "useTLS" ] [ "services" "ssmtp" "useTLS" ]) + (mkRenamedOptionModule [ "networking" "defaultMailServer" "useSTARTTLS" ] [ "services" "ssmtp" "useSTARTTLS" ]) + (mkRenamedOptionModule [ "networking" "defaultMailServer" "authUser" ] [ "services" "ssmtp" "authUser" ]) + (mkRenamedOptionModule [ "networking" "defaultMailServer" "authPass" ] [ "services" "ssmtp" "authPass" ]) + (mkRenamedOptionModule [ "networking" "defaultMailServer" "authPassFile" ] [ "services" "ssmtp" "authPassFile" ]) + (mkRenamedOptionModule [ "networking" "defaultMailServer" "setSendmail" ] [ "services" "ssmtp" "setSendmail" ]) + ]; + options = { - networking.defaultMailServer = { + services.ssmtp = { - directDelivery = mkOption { + enable = mkOption { type = types.bool; default = false; description = '' @@ -29,7 +40,7 @@ in <command>sendmail</command> or <command>postfix</command> on your machine, set this option to <literal>true</literal>, and set the option - <option>networking.defaultMailServer.hostName</option> to the + <option>services.ssmtp.hostName</option> to the host name of your preferred mail server. ''; }; @@ -129,9 +140,9 @@ in }; - config = mkIf cfg.directDelivery { + config = mkIf cfg.enable { - networking.defaultMailServer.authPassFile = mkIf (cfg.authPass != "") + services.ssmtp.authPassFile = mkIf (cfg.authPass != "") (mkDefault (toString (pkgs.writeTextFile { name = "ssmtp-authpass"; text = cfg.authPass; diff --git a/nixos/modules/programs/sway.nix b/nixos/modules/programs/sway.nix index f92d09a7ef44d..364debddb0f1f 100644 --- a/nixos/modules/programs/sway.nix +++ b/nixos/modules/programs/sway.nix @@ -4,26 +4,33 @@ with lib; let cfg = config.programs.sway; - swayPackage = pkgs.sway; - swayWrapped = pkgs.writeShellScriptBin "sway" '' - set -o errexit - - if [ ! "$_SWAY_WRAPPER_ALREADY_EXECUTED" ]; then - export _SWAY_WRAPPER_ALREADY_EXECUTED=1 - ${cfg.extraSessionCommands} - fi + wrapperOptions = types.submodule { + options = + let + mkWrapperFeature = default: description: mkOption { + type = types.bool; + inherit default; + example = !default; + description = "Whether to make use of the ${description}"; + }; + in { + base = mkWrapperFeature true '' + base wrapper to execute extra session commands and prepend a + dbus-run-session to the sway command. + ''; + gtk = mkWrapperFeature false '' + wrapGAppsHook wrapper to execute sway with required environment + variables for GTK applications. + ''; + }; + }; - if [ "$DBUS_SESSION_BUS_ADDRESS" ]; then - export DBUS_SESSION_BUS_ADDRESS - exec ${swayPackage}/bin/sway "$@" - else - exec ${pkgs.dbus}/bin/dbus-run-session ${swayPackage}/bin/sway "$@" - fi - ''; - swayJoined = pkgs.symlinkJoin { - name = "sway-joined"; - paths = [ swayWrapped swayPackage ]; + swayPackage = pkgs.sway.override { + extraSessionCommands = cfg.extraSessionCommands; + extraOptions = cfg.extraOptions; + withBaseWrapper = cfg.wrapperFeatures.base; + withGtkWrapper = cfg.wrapperFeatures.gtk; }; in { options.programs.sway = { @@ -35,6 +42,15 @@ in { Please have a look at the "extraSessionCommands" example for running programs natively under Wayland''; + wrapperFeatures = mkOption { + type = wrapperOptions; + default = { }; + example = { gtk = true; }; + description = '' + Attribute set of features to enable in the wrapper. + ''; + }; + extraSessionCommands = mkOption { type = types.lines; default = ""; @@ -52,14 +68,30 @@ in { ''; }; + extraOptions = mkOption { + type = types.listOf types.str; + default = []; + example = [ + "--verbose" + "--debug" + "--unsupported-gpu" + "--my-next-gpu-wont-be-nvidia" + ]; + description = '' + Command line arguments passed to launch Sway. Please DO NOT report + issues if you use an unsupported GPU (proprietary drivers). + ''; + }; + extraPackages = mkOption { type = with types; listOf package; default = with pkgs; [ - swaylock swayidle swaybg - xwayland rxvt_unicode dmenu + swaylock swayidle + xwayland alacritty dmenu + rxvt-unicode # For backward compatibility (old default terminal) ]; defaultText = literalExample '' - with pkgs; [ swaylock swayidle xwayland rxvt_unicode dmenu ]; + with pkgs; [ swaylock swayidle xwayland rxvt-unicode dmenu ]; ''; example = literalExample '' with pkgs; [ @@ -75,8 +107,17 @@ in { }; config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.extraSessionCommands != "" -> cfg.wrapperFeatures.base; + message = '' + The extraSessionCommands for Sway will not be run if + wrapperFeatures.base is disabled. + ''; + } + ]; environment = { - systemPackages = [ swayJoined ] ++ cfg.extraPackages; + systemPackages = [ swayPackage ] ++ cfg.extraPackages; etc = { "sway/config".source = mkOptionDefault "${swayPackage}/etc/sway/config"; #"sway/security.d".source = mkOptionDefault "${swayPackage}/etc/sway/security.d/"; @@ -87,6 +128,8 @@ in { hardware.opengl.enable = mkDefault true; fonts.enableDefaultFonts = mkDefault true; programs.dconf.enable = mkDefault true; + # To make a Sway session available if a display manager like SDDM is enabled: + services.xserver.displayManager.sessionPackages = [ swayPackage ]; }; meta.maintainers = with lib.maintainers; [ gnidorah primeos colemickens ]; diff --git a/nixos/modules/programs/systemtap.nix b/nixos/modules/programs/systemtap.nix index ca81e018c9dc2..360e106678e65 100644 --- a/nixos/modules/programs/systemtap.nix +++ b/nixos/modules/programs/systemtap.nix @@ -8,6 +8,7 @@ in { options = { programs.systemtap = { enable = mkOption { + type = types.bool; default = false; description = '' Install <command>systemtap</command> along with necessary kernel options. diff --git a/nixos/modules/programs/tmux.nix b/nixos/modules/programs/tmux.nix index ed077e3daa762..c39908751d29f 100644 --- a/nixos/modules/programs/tmux.nix +++ b/nixos/modules/programs/tmux.nix @@ -52,7 +52,7 @@ let set -s escape-time ${toString cfg.escapeTime} set -g history-limit ${toString cfg.historyLimit} - ${cfg.extraTmuxConf} + ${cfg.extraConfig} ''; in { @@ -102,7 +102,7 @@ in { description = "Time in milliseconds for which tmux waits after an escape is input."; }; - extraTmuxConf = mkOption { + extraConfig = mkOption { default = ""; description = '' Additional contents of /etc/tmux.conf @@ -181,4 +181,8 @@ in { }; }; }; + + imports = [ + (lib.mkRenamedOptionModule [ "programs" "tmux" "extraTmuxConf" ] [ "programs" "tmux" "extraConfig" ]) + ]; } diff --git a/nixos/modules/programs/traceroute.nix b/nixos/modules/programs/traceroute.nix new file mode 100644 index 0000000000000..4eb0be3f0e0be --- /dev/null +++ b/nixos/modules/programs/traceroute.nix @@ -0,0 +1,26 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.programs.traceroute; +in { + options = { + programs.traceroute = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to configure a setcap wrapper for traceroute. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + security.wrappers.traceroute = { + source = "${pkgs.traceroute}/bin/traceroute"; + capabilities = "cap_net_raw+p"; + }; + }; +} diff --git a/nixos/modules/programs/way-cooler.nix b/nixos/modules/programs/way-cooler.nix deleted file mode 100644 index f27bd42bd764c..0000000000000 --- a/nixos/modules/programs/way-cooler.nix +++ /dev/null @@ -1,78 +0,0 @@ -{ config, pkgs, lib, ... }: - -with lib; - -let - cfg = config.programs.way-cooler; - way-cooler = pkgs.way-cooler; - - wcWrapped = pkgs.writeShellScriptBin "way-cooler" '' - ${cfg.extraSessionCommands} - exec ${pkgs.dbus}/bin/dbus-run-session ${way-cooler}/bin/way-cooler - ''; - wcJoined = pkgs.symlinkJoin { - name = "way-cooler-wrapped"; - paths = [ wcWrapped way-cooler ]; - }; - configFile = readFile "${way-cooler}/etc/way-cooler/init.lua"; - spawnBar = '' - util.program.spawn_at_startup("lemonbar"); - ''; -in -{ - options.programs.way-cooler = { - enable = mkEnableOption "way-cooler"; - - extraSessionCommands = mkOption { - default = ""; - type = types.lines; - example = '' - export XKB_DEFAULT_LAYOUT=us,de - export XKB_DEFAULT_VARIANT=,nodeadkeys - export XKB_DEFAULT_OPTIONS=grp:caps_toggle, - ''; - description = '' - Shell commands executed just before way-cooler is started. - ''; - }; - - extraPackages = mkOption { - type = with types; listOf package; - default = with pkgs; [ - westonLite xwayland dmenu - ]; - example = literalExample '' - with pkgs; [ - westonLite xwayland dmenu - ] - ''; - description = '' - Extra packages to be installed system wide. - ''; - }; - - enableBar = mkOption { - type = types.bool; - default = true; - description = '' - Whether to enable an unofficial bar. - ''; - }; - }; - - config = mkIf cfg.enable { - environment.systemPackages = [ wcJoined ] ++ cfg.extraPackages; - - security.pam.services.wc-lock = {}; - environment.etc."way-cooler/init.lua".text = '' - ${configFile} - ${optionalString cfg.enableBar spawnBar} - ''; - - hardware.opengl.enable = mkDefault true; - fonts.enableDefaultFonts = mkDefault true; - programs.dconf.enable = mkDefault true; - }; - - meta.maintainers = with maintainers; [ gnidorah ]; -} diff --git a/nixos/modules/programs/zsh/oh-my-zsh.nix b/nixos/modules/programs/zsh/oh-my-zsh.nix index f4df4e983e421..f24842a479193 100644 --- a/nixos/modules/programs/zsh/oh-my-zsh.nix +++ b/nixos/modules/programs/zsh/oh-my-zsh.nix @@ -29,9 +29,17 @@ let in { + imports = [ + (mkRenamedOptionModule [ "programs" "zsh" "oh-my-zsh" "enable" ] [ "programs" "zsh" "ohMyZsh" "enable" ]) + (mkRenamedOptionModule [ "programs" "zsh" "oh-my-zsh" "theme" ] [ "programs" "zsh" "ohMyZsh" "theme" ]) + (mkRenamedOptionModule [ "programs" "zsh" "oh-my-zsh" "custom" ] [ "programs" "zsh" "ohMyZsh" "custom" ]) + (mkRenamedOptionModule [ "programs" "zsh" "oh-my-zsh" "plugins" ] [ "programs" "zsh" "ohMyZsh" "plugins" ]) + ]; + options = { programs.zsh.ohMyZsh = { enable = mkOption { + type = types.bool; default = false; description = '' Enable oh-my-zsh. diff --git a/nixos/modules/programs/zsh/zsh-autosuggestions.nix b/nixos/modules/programs/zsh/zsh-autosuggestions.nix index ded17f38a618e..037888fdc5a84 100644 --- a/nixos/modules/programs/zsh/zsh-autosuggestions.nix +++ b/nixos/modules/programs/zsh/zsh-autosuggestions.nix @@ -6,6 +6,10 @@ let cfg = config.programs.zsh.autosuggestions; in { + imports = [ + (mkRenamedOptionModule [ "programs" "zsh" "enableAutosuggestions" ] [ "programs" "zsh" "autosuggestions" "enable" ]) + ]; + options.programs.zsh.autosuggestions = { enable = mkEnableOption "zsh-autosuggestions"; diff --git a/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix b/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix index c84d26a7921ed..927a904369d5e 100644 --- a/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix +++ b/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix @@ -6,6 +6,13 @@ let cfg = config.programs.zsh.syntaxHighlighting; in { + imports = [ + (mkRenamedOptionModule [ "programs" "zsh" "enableSyntaxHighlighting" ] [ "programs" "zsh" "syntaxHighlighting" "enable" ]) + (mkRenamedOptionModule [ "programs" "zsh" "syntax-highlighting" "enable" ] [ "programs" "zsh" "syntaxHighlighting" "enable" ]) + (mkRenamedOptionModule [ "programs" "zsh" "syntax-highlighting" "highlighters" ] [ "programs" "zsh" "syntaxHighlighting" "highlighters" ]) + (mkRenamedOptionModule [ "programs" "zsh" "syntax-highlighting" "patterns" ] [ "programs" "zsh" "syntaxHighlighting" "patterns" ]) + ]; + options = { programs.zsh.syntaxHighlighting = { enable = mkEnableOption "zsh-syntax-highlighting"; diff --git a/nixos/modules/programs/zsh/zsh.nix b/nixos/modules/programs/zsh/zsh.nix index c66c29ed45fb9..930cc1987a33f 100644 --- a/nixos/modules/programs/zsh/zsh.nix +++ b/nixos/modules/programs/zsh/zsh.nix @@ -15,6 +15,24 @@ let (filterAttrs (k: v: v != null) cfg.shellAliases) ); + zshStartupNotes = '' + # Note that generated /etc/zprofile and /etc/zshrc files do a lot of + # non-standard setup to make zsh usable with no configuration by default. + # + # Which means that unless you explicitly meticulously override everything + # generated, interactions between your ~/.zshrc and these files are likely + # to be rather surprising. + # + # Note however, that you can disable loading of the generated /etc/zprofile + # and /etc/zshrc (you can't disable loading of /etc/zshenv, but it is + # designed to not set anything surprising) by setting `no_global_rcs` option + # in ~/.zshenv: + # + # echo setopt no_global_rcs >> ~/.zshenv + # + # See "STARTUP/SHUTDOWN FILES" section of zsh(1) for more info. + ''; + in { @@ -69,6 +87,10 @@ in promptInit = mkOption { default = '' + # Note that to manually override this in ~/.zshrc you should run `prompt off` + # before setting your PS1 and etc. Otherwise this will likely to interact with + # your ~/.zshrc configuration in unexpected ways as the default prompt sets + # a lot of different prompt variables. autoload -U promptinit && promptinit && prompt walters && setopt prompt_sp ''; description = '' @@ -100,7 +122,8 @@ in ]; example = [ "EXTENDED_HISTORY" "RM_STAR_WAIT" ]; description = '' - Configure zsh options. + Configure zsh options. See + <citerefentry><refentrytitle>zshoptions</refentrytitle><manvolnum>1</manvolnum></citerefentry>. ''; }; @@ -139,14 +162,21 @@ in # This file is read for all shells. # Only execute this file once per shell. - # But don't clobber the environment of interactive non-login children! if [ -n "$__ETC_ZSHENV_SOURCED" ]; then return; fi - export __ETC_ZSHENV_SOURCED=1 + __ETC_ZSHENV_SOURCED=1 if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]; then . ${config.system.build.setEnvironment} fi + HELPDIR="${pkgs.zsh}/share/zsh/$ZSH_VERSION/help" + + # Tell zsh how to find installed completions. + for p in ''${(z)NIX_PROFILES}; do + fpath+=($p/share/zsh/site-functions $p/share/zsh/$ZSH_VERSION/functions $p/share/zsh/vendor-completions) + done + + # Setup custom shell init stuff. ${cfge.shellInit} ${cfg.shellInit} @@ -161,11 +191,14 @@ in '' # /etc/zprofile: DO NOT EDIT -- this file has been generated automatically. # This file is read for login shells. + # + ${zshStartupNotes} # Only execute this file once per shell. if [ -n "$__ETC_ZPROFILE_SOURCED" ]; then return; fi __ETC_ZPROFILE_SOURCED=1 + # Setup custom login shell init stuff. ${cfge.loginShellInit} ${cfg.loginShellInit} @@ -180,38 +213,44 @@ in '' # /etc/zshrc: DO NOT EDIT -- this file has been generated automatically. # This file is read for interactive shells. + # + ${zshStartupNotes} # Only execute this file once per shell. if [ -n "$__ETC_ZSHRC_SOURCED" -o -n "$NOSYSZSHRC" ]; then return; fi __ETC_ZSHRC_SOURCED=1 - . /etc/zinputrc + ${optionalString (cfg.setOptions != []) '' + # Set zsh options. + setopt ${concatStringsSep " " cfg.setOptions} + ''} - # Don't export these, otherwise other shells (bash) will try to use same histfile + # Setup command line history. + # Don't export these, otherwise other shells (bash) will try to use same HISTFILE. SAVEHIST=${toString cfg.histSize} HISTSIZE=${toString cfg.histSize} HISTFILE=${cfg.histFile} - HELPDIR="${pkgs.zsh}/share/zsh/$ZSH_VERSION/help" - - # Tell zsh how to find installed completions - for p in ''${(z)NIX_PROFILES}; do - fpath+=($p/share/zsh/site-functions $p/share/zsh/$ZSH_VERSION/functions $p/share/zsh/vendor-completions) - done + # Configure sane keyboard defaults. + . /etc/zinputrc - ${optionalString cfg.enableGlobalCompInit "autoload -U compinit && compinit"} + ${optionalString cfg.enableGlobalCompInit '' + # Enable autocompletion. + autoload -U compinit && compinit + ''} + # Setup custom interactive shell init stuff. ${cfge.interactiveShellInit} ${cfg.interactiveShellInit} - ${optionalString (cfg.setOptions != []) "setopt ${concatStringsSep " " cfg.setOptions}"} - + # Setup aliases. ${zshAliases} + # Setup prompt. ${cfg.promptInit} - # Need to disable features to support TRAMP + # Disable some features to support TRAMP. if [ "$TERM" = dumb ]; then unsetopt zle prompt_cr prompt_subst unset RPS1 RPROMPT diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix index d4bce3b49d366..410db8fd84e76 100644 --- a/nixos/modules/rename.nix +++ b/nixos/modules/rename.nix @@ -4,311 +4,51 @@ with lib; { imports = [ - (mkRenamedOptionModule [ "networking" "enableRT73Firmware" ] [ "hardware" "enableRedistributableFirmware" ]) - (mkRenamedOptionModule [ "networking" "enableIntel3945ABGFirmware" ] [ "hardware" "enableRedistributableFirmware" ]) - (mkRenamedOptionModule [ "networking" "enableIntel2100BGFirmware" ] [ "hardware" "enableRedistributableFirmware" ]) - (mkRenamedOptionModule [ "networking" "enableRalinkFirmware" ] [ "hardware" "enableRedistributableFirmware" ]) - (mkRenamedOptionModule [ "networking" "enableRTL8192cFirmware" ] [ "hardware" "enableRedistributableFirmware" ]) - (mkRenamedOptionModule [ "networking" "networkmanager" "useDnsmasq" ] [ "networking" "networkmanager" "dns" ]) - (mkRenamedOptionModule [ "networking" "connman" ] [ "services" "connman" ]) - (mkChangedOptionModule [ "services" "printing" "gutenprint" ] [ "services" "printing" "drivers" ] - (config: - let enabled = getAttrFromPath [ "services" "printing" "gutenprint" ] config; - in if enabled then [ pkgs.gutenprint ] else [ ])) - (mkChangedOptionModule [ "services" "ddclient" "domain" ] [ "services" "ddclient" "domains" ] - (config: - let value = getAttrFromPath [ "services" "ddclient" "domain" ] config; - in if value != "" then [ value ] else [])) - (mkRemovedOptionModule [ "services" "ddclient" "homeDir" ] "") - (mkRenamedOptionModule [ "services" "flatpak" "extraPortals" ] [ "xdg" "portal" "extraPortals" ]) - (mkRenamedOptionModule [ "services" "i2pd" "extIp" ] [ "services" "i2pd" "address" ]) - (mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "admissionControl" ] [ "services" "kubernetes" "apiserver" "enableAdmissionPlugins" ]) - (mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "address" ] ["services" "kubernetes" "apiserver" "bindAddress"]) - (mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "port" ] ["services" "kubernetes" "apiserver" "insecurePort"]) - (mkRemovedOptionModule [ "services" "kubernetes" "apiserver" "publicAddress" ] "") - (mkRenamedOptionModule [ "services" "kubernetes" "addons" "dashboard" "enableRBAC" ] [ "services" "kubernetes" "addons" "dashboard" "rbac" "enable" ]) - (mkRenamedOptionModule [ "services" "kubernetes" "controllerManager" "address" ] ["services" "kubernetes" "controllerManager" "bindAddress"]) - (mkRenamedOptionModule [ "services" "kubernetes" "controllerManager" "port" ] ["services" "kubernetes" "controllerManager" "insecurePort"]) - (mkRenamedOptionModule [ "services" "kubernetes" "etcd" "servers" ] [ "services" "kubernetes" "apiserver" "etcd" "servers" ]) - (mkRenamedOptionModule [ "services" "kubernetes" "etcd" "keyFile" ] [ "services" "kubernetes" "apiserver" "etcd" "keyFile" ]) - (mkRenamedOptionModule [ "services" "kubernetes" "etcd" "certFile" ] [ "services" "kubernetes" "apiserver" "etcd" "certFile" ]) - (mkRenamedOptionModule [ "services" "kubernetes" "etcd" "caFile" ] [ "services" "kubernetes" "apiserver" "etcd" "caFile" ]) - (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "applyManifests" ] "") - (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "cadvisorPort" ] "") - (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "allowPrivileged" ] "") - (mkRenamedOptionModule [ "services" "kubernetes" "proxy" "address" ] ["services" "kubernetes" "proxy" "bindAddress"]) - (mkRemovedOptionModule [ "services" "kubernetes" "verbose" ] "") - (mkRenamedOptionModule [ "services" "logstash" "address" ] [ "services" "logstash" "listenAddress" ]) - (mkRenamedOptionModule [ "services" "neo4j" "host" ] [ "services" "neo4j" "defaultListenAddress" ]) - (mkRenamedOptionModule [ "services" "neo4j" "listenAddress" ] [ "services" "neo4j" "defaultListenAddress" ]) - (mkRenamedOptionModule [ "services" "neo4j" "enableBolt" ] [ "services" "neo4j" "bolt" "enable" ]) - (mkRenamedOptionModule [ "services" "neo4j" "enableHttps" ] [ "services" "neo4j" "https" "enable" ]) - (mkRenamedOptionModule [ "services" "neo4j" "certDir" ] [ "services" "neo4j" "directories" "certificates" ]) - (mkRenamedOptionModule [ "services" "neo4j" "dataDir" ] [ "services" "neo4j" "directories" "home" ]) - (mkRemovedOptionModule [ "services" "neo4j" "port" ] "Use services.neo4j.http.listenAddress instead.") - (mkRemovedOptionModule [ "services" "neo4j" "boltPort" ] "Use services.neo4j.bolt.listenAddress instead.") - (mkRemovedOptionModule [ "services" "neo4j" "httpsPort" ] "Use services.neo4j.https.listenAddress instead.") - (mkRemovedOptionModule [ "services" "misc" "nzbget" "configFile" ] "The configuration of nzbget is now managed by users through the web interface.") - (mkRemovedOptionModule [ "services" "misc" "nzbget" "dataDir" ] "The data directory for nzbget is now /var/lib/nzbget.") - (mkRemovedOptionModule [ "services" "misc" "nzbget" "openFirewall" ] "The port used by nzbget is managed through the web interface so you should adjust your firewall rules accordingly.") - (mkRemovedOptionModule [ "services" "prometheus" "alertmanager" "user" ] "The alertmanager service is now using systemd's DynamicUser mechanism which obviates a user setting.") - (mkRemovedOptionModule [ "services" "prometheus" "alertmanager" "group" ] "The alertmanager service is now using systemd's DynamicUser mechanism which obviates a group setting.") - (mkRemovedOptionModule [ "services" "prometheus" "alertmanagerURL" ] '' - Due to incompatibility, the alertmanagerURL option has been removed, - please use 'services.prometheus2.alertmanagers' instead. - '') - (mkRenamedOptionModule [ "services" "prometheus2" ] [ "services" "prometheus" ]) - (mkRenamedOptionModule [ "services" "tor" "relay" "portSpec" ] [ "services" "tor" "relay" "port" ]) - (mkRenamedOptionModule [ "services" "vmwareGuest" ] [ "virtualisation" "vmware" "guest" ]) - (mkRenamedOptionModule [ "jobs" ] [ "systemd" "services" ]) - - (mkRenamedOptionModule [ "services" "gitlab" "stateDir" ] [ "services" "gitlab" "statePath" ]) - (mkRemovedOptionModule [ "services" "gitlab" "satelliteDir" ] "") - - (mkRenamedOptionModule [ "services" "clamav" "updater" "config" ] [ "services" "clamav" "updater" "extraConfig" ]) - - (mkRemovedOptionModule [ "services" "pykms" "verbose" ] "Use services.pykms.logLevel instead") - - (mkRemovedOptionModule [ "security" "setuidOwners" ] "Use security.wrappers instead") - (mkRemovedOptionModule [ "security" "setuidPrograms" ] "Use security.wrappers instead") - - (mkRenamedOptionModule [ "security" "virtualization" "flushL1DataCache" ] [ "security" "virtualisation" "flushL1DataCache" ]) - - # PAM - (mkRenamedOptionModule [ "security" "pam" "enableU2F" ] [ "security" "pam" "u2f" "enable" ]) - - # rmilter/rspamd - (mkRemovedOptionModule [ "services" "rmilter" ] "Use services.rspamd.* instead to set up milter service") - - # Xsession script - (mkRenamedOptionModule [ "services" "xserver" "displayManager" "job" "logsXsession" ] [ "services" "xserver" "displayManager" "job" "logToFile" ]) - (mkRenamedOptionModule [ "services" "xserver" "displayManager" "logToJournal" ] [ "services" "xserver" "displayManager" "job" "logToJournal" ]) - - # Old Grub-related options. - (mkRenamedOptionModule [ "boot" "loader" "grub" "timeout" ] [ "boot" "loader" "timeout" ]) - (mkRenamedOptionModule [ "boot" "loader" "gummiboot" "timeout" ] [ "boot" "loader" "timeout" ]) - - # OpenSSH - (mkAliasOptionModule [ "services" "sshd" "enable" ] [ "services" "openssh" "enable" ]) - (mkAliasOptionModule [ "services" "openssh" "knownHosts" ] [ "programs" "ssh" "knownHosts" ]) - - # libvirtd - (mkRemovedOptionModule [ "virtualisation" "libvirtd" "enableKVM" ] - "Set the option `virtualisation.libvirtd.qemuPackage' instead.") - - # ibus - (mkRenamedOptionModule [ "programs" "ibus" "plugins" ] [ "i18n" "inputMethod" "ibus" "engines" ]) - - # sandboxing - (mkRenamedOptionModule [ "nix" "useChroot" ] [ "nix" "useSandbox" ]) - (mkRenamedOptionModule [ "nix" "chrootDirs" ] [ "nix" "sandboxPaths" ]) + /* + This file defines some renaming/removing options for backwards compatibility - (mkRenamedOptionModule [ "services" "xserver" "vaapiDrivers" ] [ "hardware" "opengl" "extraPackages" ]) + It should ONLY be used when the relevant module can't define these imports + itself, such as when the module was removed completely. + See https://github.com/NixOS/nixpkgs/pull/61570 for explanation + */ + # This alias module can't be where _module.check is defined because it would + # be added to submodules as well there (mkAliasOptionModule [ "environment" "checkConfigurationOptions" ] [ "_module" "check" ]) - # opendkim - (mkRenamedOptionModule [ "services" "opendkim" "keyFile" ] [ "services" "opendkim" "keyPath" ]) - - # Enlightenment - (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "e19" "enable" ] [ "services" "xserver" "desktopManager" "enlightenment" "enable" ]) - - # Iodine - (mkRenamedOptionModule [ "services" "iodined" "enable" ] [ "services" "iodine" "server" "enable" ]) - (mkRenamedOptionModule [ "services" "iodined" "domain" ] [ "services" "iodine" "server" "domain" ]) - (mkRenamedOptionModule [ "services" "iodined" "ip" ] [ "services" "iodine" "server" "ip" ]) - (mkRenamedOptionModule [ "services" "iodined" "extraConfig" ] [ "services" "iodine" "server" "extraConfig" ]) - (mkRemovedOptionModule [ "services" "iodined" "client" ] "") - - # Unity3D - (mkRenamedOptionModule [ "programs" "unity3d" "enable" ] [ "security" "chromiumSuidSandbox" "enable" ]) - - # murmur - (mkRenamedOptionModule [ "services" "murmur" "welcome" ] [ "services" "murmur" "welcometext" ]) - (mkRemovedOptionModule [ "services" "murmur" "pidfile" ] "Hardcoded to /run/murmur/murmurd.pid now") - - # parsoid - (mkRemovedOptionModule [ "services" "parsoid" "interwikis" ] "Use services.parsoid.wikis instead") - - # plexpy / tautulli - (mkRenamedOptionModule [ "services" "plexpy" ] [ "services" "tautulli" ]) - - # piwik was renamed to matomo - (mkRenamedOptionModule [ "services" "piwik" "enable" ] [ "services" "matomo" "enable" ]) - (mkRenamedOptionModule [ "services" "piwik" "webServerUser" ] [ "services" "matomo" "webServerUser" ]) - (mkRemovedOptionModule [ "services" "piwik" "phpfpmProcessManagerConfig" ] "Use services.phpfpm.pools.<name>.settings") - (mkRemovedOptionModule [ "services" "matomo" "phpfpmProcessManagerConfig" ] "Use services.phpfpm.pools.<name>.settings") - (mkRenamedOptionModule [ "services" "piwik" "nginx" ] [ "services" "matomo" "nginx" ]) - - # tarsnap - (mkRemovedOptionModule [ "services" "tarsnap" "cachedir" ] "Use services.tarsnap.archives.<name>.cachedir") - - # alsa - (mkRenamedOptionModule [ "sound" "enableMediaKeys" ] [ "sound" "mediaKeys" "enable" ]) - - # postgrey - (mkMergedOptionModule [ [ "services" "postgrey" "inetAddr" ] [ "services" "postgrey" "inetPort" ] ] [ "services" "postgrey" "socket" ] (config: let - value = p: getAttrFromPath p config; - inetAddr = [ "services" "postgrey" "inetAddr" ]; - inetPort = [ "services" "postgrey" "inetPort" ]; - in - if value inetAddr == null - then { path = "/run/postgrey.sock"; } - else { addr = value inetAddr; port = value inetPort; } - )) - - # dhcpd - (mkRenamedOptionModule [ "services" "dhcpd" ] [ "services" "dhcpd4" ]) - - # locate - (mkRenamedOptionModule [ "services" "locate" "period" ] [ "services" "locate" "interval" ]) - (mkRemovedOptionModule [ "services" "locate" "includeStore" ] "Use services.locate.prunePaths" ) - - # nfs - (mkRenamedOptionModule [ "services" "nfs" "lockdPort" ] [ "services" "nfs" "server" "lockdPort" ]) - (mkRenamedOptionModule [ "services" "nfs" "statdPort" ] [ "services" "nfs" "server" "statdPort" ]) - - # KDE Plasma 5 - (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "kde5" ] [ "services" "xserver" "desktopManager" "plasma5" ]) - - # Fontconfig - (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "allowBitmaps" ] [ "fonts" "fontconfig" "allowBitmaps" ]) - (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "allowType1" ] [ "fonts" "fontconfig" "allowType1" ]) - (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "useEmbeddedBitmaps" ] [ "fonts" "fontconfig" "useEmbeddedBitmaps" ]) - (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "forceAutohint" ] [ "fonts" "fontconfig" "forceAutohint" ]) - (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "renderMonoTTFAsBitmap" ] [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ]) - - # postgresqlBackup - (mkRemovedOptionModule [ "services" "postgresqlBackup" "period" ] '' - A systemd timer is now used instead of cron. - The starting time can be configured via <literal>services.postgresqlBackup.startAt</literal>. - '') - - # phpfpm - (mkRemovedOptionModule [ "services" "phpfpm" "poolConfigs" ] "Use services.phpfpm.pools instead.") - - # zabbixServer - (mkRenamedOptionModule [ "services" "zabbixServer" "dbServer" ] [ "services" "zabbixServer" "database" "host" ]) - - # Profile splitting - (mkRenamedOptionModule [ "virtualisation" "growPartition" ] [ "boot" "growPartition" ]) - - # misc/version.nix - (mkRenamedOptionModule [ "system" "nixosVersion" ] [ "system" "nixos" "version" ]) - (mkRenamedOptionModule [ "system" "nixosVersionSuffix" ] [ "system" "nixos" "versionSuffix" ]) - (mkRenamedOptionModule [ "system" "nixosRevision" ] [ "system" "nixos" "revision" ]) - (mkRenamedOptionModule [ "system" "nixosLabel" ] [ "system" "nixos" "label" ]) - - # Users - (mkAliasOptionModule [ "users" "extraUsers" ] [ "users" "users" ]) - (mkAliasOptionModule [ "users" "extraGroups" ] [ "users" "groups" ]) - - # Options that are obsolete and have no replacement. - (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "") - (mkRemovedOptionModule [ "programs" "bash" "enable" ] "") - (mkRemovedOptionModule [ "services" "samba" "defaultShare" ] "") - (mkRemovedOptionModule [ "services" "syslog-ng" "serviceName" ] "") - (mkRemovedOptionModule [ "services" "syslog-ng" "listenToJournal" ] "") - (mkRemovedOptionModule [ "ec2" "metadata" ] "") - (mkRemovedOptionModule [ "services" "openvpn" "enable" ] "") - (mkRemovedOptionModule [ "services" "printing" "cupsFilesConf" ] "") - (mkRemovedOptionModule [ "services" "printing" "cupsdConf" ] "") - (mkRemovedOptionModule [ "services" "tor" "relay" "isBridge" ] "Use services.tor.relay.role instead.") - (mkRemovedOptionModule [ "services" "tor" "relay" "isExit" ] "Use services.tor.relay.role instead.") - (mkRemovedOptionModule [ "services" "xserver" "startGnuPGAgent" ] - "See the 16.09 release notes for more information.") - (mkRemovedOptionModule [ "services" "phpfpm" "phpIni" ] "") - (mkRemovedOptionModule [ "services" "dovecot2" "package" ] "") + # Completely removed modules (mkRemovedOptionModule [ "services" "firefox" "syncserver" "user" ] "") (mkRemovedOptionModule [ "services" "firefox" "syncserver" "group" ] "") - (mkRemovedOptionModule [ "fonts" "fontconfig" "hinting" "style" ] "") - (mkRemovedOptionModule [ "services" "xserver" "displayManager" "sddm" "themes" ] - "Set the option `services.xserver.displayManager.sddm.package' instead.") - (mkRemovedOptionModule [ "services" "xserver" "desktopManager" "xfce" "screenLock" ] "") - (mkRemovedOptionModule [ "fonts" "fontconfig" "forceAutohint" ] "") - (mkRemovedOptionModule [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ] "") - (mkRemovedOptionModule [ "virtualisation" "xen" "qemu" ] "You don't need this option anymore, it will work without it.") - (mkRemovedOptionModule [ "services" "logstash" "enableWeb" ] "The web interface was removed from logstash") - (mkRemovedOptionModule [ "boot" "zfs" "enableLegacyCrypto" ] "The corresponding package was removed from nixpkgs.") (mkRemovedOptionModule [ "services" "winstone" ] "The corresponding package was removed from nixpkgs.") - (mkRemovedOptionModule [ "services" "mysql" "pidDir" ] "Don't wait for pidfiles, describe dependencies through systemd") - (mkRemovedOptionModule [ "services" "mysql" "rootPassword" ] "Use socket authentication or set the password outside of the nix store.") - (mkRemovedOptionModule [ "services" "zabbixServer" "dbPassword" ] "Use services.zabbixServer.database.passwordFile instead.") - (mkRemovedOptionModule [ "systemd" "generator-packages" ] "Use systemd.packages instead.") - (mkRemovedOptionModule [ "fonts" "enableCoreFonts" ] "Use fonts.fonts = [ pkgs.corefonts ]; instead.") (mkRemovedOptionModule [ "networking" "vpnc" ] "Use environment.etc.\"vpnc/service.conf\" instead.") + (mkRemovedOptionModule [ "environment" "blcr" "enable" ] "The BLCR module has been removed") + (mkRemovedOptionModule [ "services" "beegfsEnable" ] "The BeeGFS module has been removed") + (mkRemovedOptionModule [ "services" "beegfs" ] "The BeeGFS module has been removed") + (mkRemovedOptionModule [ "services" "osquery" ] "The osquery module has been removed") + (mkRemovedOptionModule [ "services" "fourStore" ] "The fourStore module has been removed") + (mkRemovedOptionModule [ "services" "fourStoreEndpoint" ] "The fourStoreEndpoint module has been removed") + (mkRemovedOptionModule [ "programs" "way-cooler" ] ("way-cooler is abandoned by its author: " + + "https://way-cooler.org/blog/2020/01/09/way-cooler-post-mortem.html")) + (mkRemovedOptionModule [ "services" "xserver" "multitouch" ] '' + services.xserver.multitouch (which uses xf86_input_mtrack) has been removed + as the underlying package isn't being maintained. Working alternatives are + libinput and synaptics. + '') + (mkRemovedOptionModule [ "services" "xserver" "displayManager" "auto" ] '' + The services.xserver.displayManager.auto module has been removed + because it was only intended for use in internal NixOS tests, and gave the + false impression of it being a special display manager when it's actually + LightDM. Please use the services.xserver.displayManager.lightdm.autoLogin options + instead, or any other display manager in NixOS as they all support auto-login. + '') + (mkRemovedOptionModule [ "services" "dnscrypt-proxy" ] "Use services.dnscrypt-proxy2 instead") + (mkRemovedOptionModule ["hardware" "brightnessctl" ] '' + The brightnessctl module was removed because newer versions of + brightnessctl don't require the udev rules anymore (they can use the + systemd-logind API). Instead of using the module you can now + simply add the brightnessctl package to environment.systemPackages. + '') - # ZSH - (mkRenamedOptionModule [ "programs" "zsh" "enableSyntaxHighlighting" ] [ "programs" "zsh" "syntaxHighlighting" "enable" ]) - (mkRenamedOptionModule [ "programs" "zsh" "syntax-highlighting" "enable" ] [ "programs" "zsh" "syntaxHighlighting" "enable" ]) - (mkRenamedOptionModule [ "programs" "zsh" "syntax-highlighting" "highlighters" ] [ "programs" "zsh" "syntaxHighlighting" "highlighters" ]) - (mkRenamedOptionModule [ "programs" "zsh" "syntax-highlighting" "patterns" ] [ "programs" "zsh" "syntaxHighlighting" "patterns" ]) - (mkRenamedOptionModule [ "programs" "zsh" "oh-my-zsh" "enable" ] [ "programs" "zsh" "ohMyZsh" "enable" ]) - (mkRenamedOptionModule [ "programs" "zsh" "oh-my-zsh" "theme" ] [ "programs" "zsh" "ohMyZsh" "theme" ]) - (mkRenamedOptionModule [ "programs" "zsh" "oh-my-zsh" "custom" ] [ "programs" "zsh" "ohMyZsh" "custom" ]) - (mkRenamedOptionModule [ "programs" "zsh" "oh-my-zsh" "plugins" ] [ "programs" "zsh" "ohMyZsh" "plugins" ]) - - (mkRenamedOptionModule [ "programs" "zsh" "enableAutosuggestions" ] [ "programs" "zsh" "autosuggestions" "enable" ]) - - # Xen - (mkRenamedOptionModule [ "virtualisation" "xen" "qemu-package" ] [ "virtualisation" "xen" "package-qemu" ]) - - (mkRenamedOptionModule [ "programs" "info" "enable" ] [ "documentation" "info" "enable" ]) - (mkRenamedOptionModule [ "programs" "man" "enable" ] [ "documentation" "man" "enable" ]) - (mkRenamedOptionModule [ "services" "nixosManual" "enable" ] [ "documentation" "nixos" "enable" ]) - - # ckb - (mkRenamedOptionModule [ "hardware" "ckb" "enable" ] [ "hardware" "ckb-next" "enable" ]) - (mkRenamedOptionModule [ "hardware" "ckb" "package" ] [ "hardware" "ckb-next" "package" ]) - - # binfmt - (mkRenamedOptionModule [ "boot" "binfmtMiscRegistrations" ] [ "boot" "binfmt" "registrations" ]) - - # ACME - (mkRemovedOptionModule [ "security" "acme" "directory"] "ACME Directory is now hardcoded to /var/lib/acme and its permisisons are managed by systemd. See https://github.com/NixOS/nixpkgs/issues/53852 for more info.") - (mkRemovedOptionModule [ "security" "acme" "preDelay"] "This option has been removed. If you want to make sure that something executes before certificates are provisioned, add a RequiredBy=acme-\${cert}.service to the service you want to execute before the cert renewal") - (mkRemovedOptionModule [ "security" "acme" "activationDelay"] "This option has been removed. If you want to make sure that something executes before certificates are provisioned, add a RequiredBy=acme-\${cert}.service to the service you want to execute before the cert renewal") - - # KSM - (mkRenamedOptionModule [ "hardware" "enableKSM" ] [ "hardware" "ksm" "enable" ]) - - # resolvconf - (mkRenamedOptionModule [ "networking" "dnsSingleRequest" ] [ "networking" "resolvconf" "dnsSingleRequest" ]) - (mkRenamedOptionModule [ "networking" "dnsExtensionMechanism" ] [ "networking" "resolvconf" "dnsExtensionMechanism" ]) - (mkRenamedOptionModule [ "networking" "extraResolvconfConf" ] [ "networking" "resolvconf" "extraConfig" ]) - (mkRenamedOptionModule [ "networking" "resolvconfOptions" ] [ "networking" "resolvconf" "extraOptions" ]) - - # BLCR - (mkRemovedOptionModule [ "environment.blcr.enable" ] "The BLCR module has been removed") - - # beegfs - (mkRemovedOptionModule [ "services.beegfsEnable" ] "The BeeGFS module has been removed") - (mkRemovedOptionModule [ "services.beegfs" ] "The BeeGFS module has been removed") - - # osquery - (mkRemovedOptionModule [ "services.osquery" ] "The osquery module has been removed") - - # Redis - (mkRemovedOptionModule [ "services" "redis" "user" ] "The redis module now is hardcoded to the redis user.") - (mkRemovedOptionModule [ "services" "redis" "dbpath" ] "The redis module now uses /var/lib/redis as data directory.") - (mkRemovedOptionModule [ "services" "redis" "dbFilename" ] "The redis module now uses /var/lib/redis/dump.rdb as database dump location.") - (mkRemovedOptionModule [ "services" "redis" "appendOnlyFilename" ] "This option was never used.") - (mkRemovedOptionModule [ "services" "redis" "pidFile" ] "This option was removed.") - - ] ++ (forEach [ "blackboxExporter" "collectdExporter" "fritzboxExporter" - "jsonExporter" "minioExporter" "nginxExporter" "nodeExporter" - "snmpExporter" "unifiExporter" "varnishExporter" ] - (opt: mkRemovedOptionModule [ "services" "prometheus" "${opt}" ] '' - The prometheus exporters are now configured using `services.prometheus.exporters'. - See the 18.03 release notes for more information. - '' )) - - ++ (forEach [ "enable" "substitutions" "preset" ] - (opt: mkRemovedOptionModule [ "fonts" "fontconfig" "ultimate" "${opt}" ] '' - The fonts.fontconfig.ultimate module and configuration is obsolete. - The repository has since been archived and activity has ceased. - https://github.com/bohoomil/fontconfig-ultimate/issues/171. - No action should be needed for font configuration, as the fonts.fontconfig - module is already used by default. - '' )); + # Do NOT add any option renames here, see top of the file + ]; } diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix index 9563029f030a0..39976380e3b45 100644 --- a/nixos/modules/security/acme.nix +++ b/nixos/modules/security/acme.nix @@ -1,7 +1,5 @@ { config, lib, pkgs, ... }: - with lib; - let cfg = config.security.acme; @@ -9,7 +7,8 @@ let certOpts = { name, ... }: { options = { webroot = mkOption { - type = types.str; + type = types.nullOr types.str; + default = null; example = "/var/lib/acme/acme-challenges"; description = '' Where the webroot of the HTTP vhost is located. @@ -38,7 +37,7 @@ let email = mkOption { type = types.nullOr types.str; - default = null; + default = cfg.email; description = "Contact email address for the CA to be able to reach you."; }; @@ -76,20 +75,6 @@ let ''; }; - plugins = mkOption { - type = types.listOf (types.enum [ - "cert.der" "cert.pem" "chain.pem" "external.sh" - "fullchain.pem" "full.pem" "key.der" "key.pem" "account_key.json" "account_reg.json" - ]); - default = [ "fullchain.pem" "full.pem" "key.pem" "account_key.json" "account_reg.json" ]; - description = '' - Plugins to enable. With default settings simp_le will - store public certificate bundle in <filename>fullchain.pem</filename>, - private key in <filename>key.pem</filename> and those two previous - files combined in <filename>full.pem</filename> in its state directory. - ''; - }; - directory = mkOption { type = types.str; readOnly = true; @@ -111,6 +96,67 @@ let own server roots if needed. ''; }; + + keyType = mkOption { + type = types.str; + default = "ec384"; + description = '' + Key type to use for private keys. + For an up to date list of supported values check the --key-type option + at https://go-acme.github.io/lego/usage/cli/#usage. + ''; + }; + + dnsProvider = mkOption { + type = types.nullOr types.str; + default = null; + example = "route53"; + description = '' + DNS Challenge provider. For a list of supported providers, see the "code" + field of the DNS providers listed at https://go-acme.github.io/lego/dns/. + ''; + }; + + credentialsFile = mkOption { + type = types.path; + description = '' + Path to an EnvironmentFile for the cert's service containing any required and + optional environment variables for your selected dnsProvider. + To find out what values you need to set, consult the documentation at + https://go-acme.github.io/lego/dns/ for the corresponding dnsProvider. + ''; + example = "/var/src/secrets/example.org-route53-api-token"; + }; + + dnsPropagationCheck = mkOption { + type = types.bool; + default = true; + description = '' + Toggles lego DNS propagation check, which is used alongside DNS-01 + challenge to ensure the DNS entries required are available. + ''; + }; + + ocspMustStaple = mkOption { + type = types.bool; + default = false; + description = '' + Turns on the OCSP Must-Staple TLS extension. + Make sure you know what you're doing! See: + <itemizedlist> + <listitem><para><link xlink:href="https://blog.apnic.net/2019/01/15/is-the-web-ready-for-ocsp-must-staple/" /></para></listitem> + <listitem><para><link xlink:href="https://blog.hboeck.de/archives/886-The-Problem-with-OCSP-Stapling-and-Must-Staple-and-why-Certificate-Revocation-is-still-broken.html" /></para></listitem> + </itemizedlist> + ''; + }; + + extraLegoRenewFlags = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Additional flags to pass to lego renew. + ''; + }; }; }; @@ -127,19 +173,29 @@ in "https://acme-staging-v02.api.letsencrypt.org/directory". '' ) + (mkRemovedOptionModule [ "security" "acme" "directory"] "ACME Directory is now hardcoded to /var/lib/acme and its permisisons are managed by systemd. See https://github.com/NixOS/nixpkgs/issues/53852 for more info.") + (mkRemovedOptionModule [ "security" "acme" "preDelay"] "This option has been removed. If you want to make sure that something executes before certificates are provisioned, add a RequiredBy=acme-\${cert}.service to the service you want to execute before the cert renewal") + (mkRemovedOptionModule [ "security" "acme" "activationDelay"] "This option has been removed. If you want to make sure that something executes before certificates are provisioned, add a RequiredBy=acme-\${cert}.service to the service you want to execute before the cert renewal") + (mkChangedOptionModule [ "security" "acme" "validMin"] [ "security" "acme" "validMinDays"] (config: config.security.acme.validMin / (24 * 3600))) ]; options = { security.acme = { - validMin = mkOption { + validMinDays = mkOption { type = types.int; - default = 30 * 24 * 3600; - description = "Minimum remaining validity before renewal in seconds."; + default = 30; + description = "Minimum remaining validity before renewal in days."; + }; + + email = mkOption { + type = types.nullOr types.str; + default = null; + description = "Contact email address for the CA to be able to reach you."; }; renewInterval = mkOption { type = types.str; - default = "weekly"; + default = "daily"; description = '' Systemd calendar expression when to check for renewal. See <citerefentry><refentrytitle>systemd.time</refentrytitle> @@ -170,6 +226,15 @@ in ''; }; + acceptTerms = mkOption { + type = types.bool; + default = false; + description = '' + Accept the CA's terms of service. The default provier is Let's Encrypt, + you can find their ToS at https://letsencrypt.org/repository/ + ''; + }; + certs = mkOption { default = { }; type = with types; attrsOf (submodule certOpts); @@ -201,48 +266,100 @@ in config = mkMerge [ (mkIf (cfg.certs != { }) { + assertions = let + certs = (mapAttrsToList (k: v: v) cfg.certs); + in [ + { + assertion = all (certOpts: certOpts.dnsProvider == null || certOpts.webroot == null) certs; + message = '' + Options `security.acme.certs.<name>.dnsProvider` and + `security.acme.certs.<name>.webroot` are mutually exclusive. + ''; + } + { + assertion = cfg.email != null || all (certOpts: certOpts.email != null) certs; + message = '' + You must define `security.acme.certs.<name>.email` or + `security.acme.email` to register with the CA. + ''; + } + { + assertion = cfg.acceptTerms; + message = '' + You must accept the CA's terms of service before using + the ACME module by setting `security.acme.acceptTerms` + to `true`. For Let's Encrypt's ToS see https://letsencrypt.org/repository/ + ''; + } + ]; + systemd.services = let services = concatLists servicesLists; servicesLists = mapAttrsToList certToServices cfg.certs; certToServices = cert: data: let + # StateDirectory must be relative, and will be created under /var/lib by systemd lpath = "acme/${cert}"; - rights = if data.allowKeysForGroup then "750" else "700"; - cmdline = [ "-v" "-d" data.domain "--default_root" data.webroot "--valid_min" cfg.validMin ] - ++ optionals (data.email != null) [ "--email" data.email ] - ++ concatMap (p: [ "-f" p ]) data.plugins - ++ concatLists (mapAttrsToList (name: root: [ "-d" (if root == null then name else "${name}:${root}")]) data.extraDomains) + apath = "/var/lib/${lpath}"; + spath = "/var/lib/acme/.lego/${cert}"; + fileMode = if data.allowKeysForGroup then "640" else "600"; + globalOpts = [ "-d" data.domain "--email" data.email "--path" "." "--key-type" data.keyType ] + ++ optionals (cfg.acceptTerms) [ "--accept-tos" ] + ++ optionals (data.dnsProvider != null && !data.dnsPropagationCheck) [ "--dns.disable-cp" ] + ++ concatLists (mapAttrsToList (name: root: [ "-d" name ]) data.extraDomains) + ++ (if data.dnsProvider != null then [ "--dns" data.dnsProvider ] else [ "--http" "--http.webroot" data.webroot ]) ++ optionals (cfg.server != null || data.server != null) ["--server" (if data.server == null then cfg.server else data.server)]; + certOpts = optionals data.ocspMustStaple [ "--must-staple" ]; + runOpts = escapeShellArgs (globalOpts ++ [ "run" ] ++ certOpts); + renewOpts = escapeShellArgs (globalOpts ++ + [ "renew" "--days" (toString cfg.validMinDays) ] ++ + certOpts ++ data.extraLegoRenewFlags); acmeService = { description = "Renew ACME Certificate for ${cert}"; after = [ "network.target" "network-online.target" ]; wants = [ "network-online.target" ]; - # simp_le uses requests, which uses certifi under the hood, - # which doesn't respect the system trust store. - # At least in the acme test, we provision a fake CA, impersonating the LE endpoint. - # REQUESTS_CA_BUNDLE is a way to teach python requests to use something else - environment.REQUESTS_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt"; + wantedBy = mkIf (!config.boot.isContainer) [ "multi-user.target" ]; serviceConfig = { Type = "oneshot"; - # With RemainAfterExit the service is considered active even - # after the main process having exited, which means when it - # gets changed, the activation phase restarts it, meaning - # the permissions of the StateDirectory get adjusted - # according to the specified group - RemainAfterExit = true; - SuccessExitStatus = [ "0" "1" ]; User = data.user; Group = data.group; PrivateTmp = true; - StateDirectory = lpath; - StateDirectoryMode = rights; - WorkingDirectory = "/var/lib/${lpath}"; - ExecStart = "${pkgs.simp_le}/bin/simp_le ${escapeShellArgs cmdline}"; - ExecStopPost = + StateDirectory = "acme/.lego/${cert} acme/.lego/accounts ${lpath}"; + StateDirectoryMode = if data.allowKeysForGroup then "750" else "700"; + WorkingDirectory = spath; + # Only try loading the credentialsFile if the dns challenge is enabled + EnvironmentFile = if data.dnsProvider != null then data.credentialsFile else null; + ExecStart = pkgs.writeScript "acme-start" '' + #!${pkgs.runtimeShell} -e + test -L ${spath}/accounts -o -d ${spath}/accounts || ln -s ../accounts ${spath}/accounts + ${pkgs.lego}/bin/lego ${renewOpts} || ${pkgs.lego}/bin/lego ${runOpts} + ''; + ExecStartPost = let - script = pkgs.writeScript "acme-post-stop" '' + keyName = builtins.replaceStrings ["*"] ["_"] data.domain; + script = pkgs.writeScript "acme-post-start" '' #!${pkgs.runtimeShell} -e - ${data.postRun} + cd ${apath} + + # Test that existing cert is older than new cert + KEY=${spath}/certificates/${keyName}.key + KEY_CHANGED=no + if [ -e $KEY -a $KEY -nt key.pem ]; then + KEY_CHANGED=yes + cp -p ${spath}/certificates/${keyName}.key key.pem + cp -p ${spath}/certificates/${keyName}.crt fullchain.pem + cp -p ${spath}/certificates/${keyName}.issuer.crt chain.pem + ln -sf fullchain.pem cert.pem + cat key.pem fullchain.pem > full.pem + fi + + chmod ${fileMode} *.pem + chown '${data.user}:${data.group}' *.pem + + if [ "$KEY_CHANGED" = "yes" ]; then + : # noop in case postRun is empty + ${data.postRun} + fi ''; in "+${script}"; @@ -273,17 +390,17 @@ in -out $workdir/server.crt # Copy key to destination - cp $workdir/server.key /var/lib/${lpath}/key.pem + cp $workdir/server.key ${apath}/key.pem # Create fullchain.pem (same format as "simp_le ... -f fullchain.pem" creates) - cat $workdir/{server.crt,ca.crt} > "/var/lib/${lpath}/fullchain.pem" + cat $workdir/{server.crt,ca.crt} > "${apath}/fullchain.pem" # Create full.pem for e.g. lighttpd - cat $workdir/{server.key,server.crt,ca.crt} > "/var/lib/${lpath}/full.pem" + cat $workdir/{server.key,server.crt,ca.crt} > "${apath}/full.pem" # Give key acme permissions - chown '${data.user}:${data.group}' "/var/lib/${lpath}/"{key,fullchain,full}.pem - chmod ${rights} "/var/lib/${lpath}/"{key,fullchain,full}.pem + chown '${data.user}:${data.group}' "${apath}/"{key,fullchain,full}.pem + chmod ${fileMode} "${apath}/"{key,fullchain,full}.pem ''; serviceConfig = { Type = "oneshot"; @@ -294,7 +411,7 @@ in }; unitConfig = { # Do not create self-signed key when key already exists - ConditionPathExists = "!/var/lib/${lpath}/key.pem"; + ConditionPathExists = "!${apath}/key.pem"; }; }; in ( @@ -306,10 +423,19 @@ in servicesAttr; systemd.tmpfiles.rules = - flip mapAttrsToList cfg.certs - (cert: data: "d ${data.webroot}/.well-known/acme-challenge - ${data.user} ${data.group}"); - - systemd.timers = flip mapAttrs' cfg.certs (cert: data: nameValuePair + map (data: "d ${data.webroot}/.well-known/acme-challenge - ${data.user} ${data.group}") (filter (data: data.webroot != null) (attrValues cfg.certs)); + + systemd.timers = let + # Allow systemd to pick a convenient time within the day + # to run the check. + # This allows the coalescing of multiple timer jobs. + # We divide by the number of certificates so that if you + # have many certificates, the renewals are distributed over + # the course of the day to avoid rate limits. + numCerts = length (attrNames cfg.certs); + _24hSecs = 60 * 60 * 24; + AccuracySec = "${toString (_24hSecs / numCerts)}s"; + in flip mapAttrs' cfg.certs (cert: data: nameValuePair ("acme-${cert}") ({ description = "Renew ACME Certificate for ${cert}"; @@ -318,8 +444,9 @@ in OnCalendar = cfg.renewInterval; Unit = "acme-${cert}.service"; Persistent = "yes"; - AccuracySec = "5m"; - RandomizedDelaySec = "1h"; + inherit AccuracySec; + # Skew randomly within the day, per https://letsencrypt.org/docs/integration-guide/. + RandomizedDelaySec = "24h"; }; }) ); @@ -331,7 +458,7 @@ in ]; meta = { - maintainers = with lib.maintainers; [ abbradar fpletz globin ]; + maintainers = with lib.maintainers; [ abbradar fpletz globin m1cr0man ]; doc = ./acme.xml; }; } diff --git a/nixos/modules/security/acme.xml b/nixos/modules/security/acme.xml index 9d0a1995e0ff0..2b29c11748455 100644 --- a/nixos/modules/security/acme.xml +++ b/nixos/modules/security/acme.xml @@ -7,7 +7,7 @@ <para> NixOS supports automatic domain validation & certificate retrieval and renewal using the ACME protocol. This is currently only implemented by and - for Let's Encrypt. The alternative ACME client <literal>simp_le</literal> is + for Let's Encrypt. The alternative ACME client <literal>lego</literal> is used under the hood. </para> <section xml:id="module-security-acme-prerequisites"> diff --git a/nixos/modules/security/apparmor-suid.nix b/nixos/modules/security/apparmor-suid.nix index 498c2f25d1c0d..3c93f5440ab5b 100644 --- a/nixos/modules/security/apparmor-suid.nix +++ b/nixos/modules/security/apparmor-suid.nix @@ -4,6 +4,9 @@ let in with lib; { + imports = [ + (mkRenamedOptionModule [ "security" "virtualization" "flushL1DataCache" ] [ "security" "virtualisation" "flushL1DataCache" ]) + ]; options.security.apparmor.confineSUIDApplications = mkOption { default = true; diff --git a/nixos/modules/security/chromium-suid-sandbox.nix b/nixos/modules/security/chromium-suid-sandbox.nix index 2255477f26e4c..b83dbc4202a8d 100644 --- a/nixos/modules/security/chromium-suid-sandbox.nix +++ b/nixos/modules/security/chromium-suid-sandbox.nix @@ -7,6 +7,10 @@ let sandbox = pkgs.chromium.sandbox; in { + imports = [ + (mkRenamedOptionModule [ "programs" "unity3d" "enable" ] [ "security" "chromiumSuidSandbox" "enable" ]) + ]; + options.security.chromiumSuidSandbox.enable = mkOption { type = types.bool; default = false; diff --git a/nixos/modules/security/duosec.nix b/nixos/modules/security/duosec.nix index 997328ad9e6ab..71428b82f5dac 100644 --- a/nixos/modules/security/duosec.nix +++ b/nixos/modules/security/duosec.nix @@ -9,10 +9,9 @@ let configFilePam = '' [duo] - ikey=${cfg.ikey} - skey=${cfg.skey} + ikey=${cfg.integrationKey} host=${cfg.host} - ${optionalString (cfg.group != "") ("group="+cfg.group)} + ${optionalString (cfg.groups != "") ("groups="+cfg.groups)} failmode=${cfg.failmode} pushinfo=${boolToStr cfg.pushinfo} autopush=${boolToStr cfg.autopush} @@ -24,22 +23,14 @@ let motd=${boolToStr cfg.motd} accept_env_factor=${boolToStr cfg.acceptEnvFactor} ''; - - loginCfgFile = optional cfg.ssh.enable - { source = pkgs.writeText "login_duo.conf" configFileLogin; - mode = "0600"; - user = "sshd"; - target = "duo/login_duo.conf"; - }; - - pamCfgFile = optional cfg.pam.enable - { source = pkgs.writeText "pam_duo.conf" configFilePam; - mode = "0600"; - user = "sshd"; - target = "duo/pam_duo.conf"; - }; in { + imports = [ + (mkRenamedOptionModule [ "security" "duosec" "group" ] [ "security" "duosec" "groups" ]) + (mkRenamedOptionModule [ "security" "duosec" "ikey" ] [ "security" "duosec" "integrationKey" ]) + (mkRemovedOptionModule [ "security" "duosec" "skey" ] "The insecure security.duosec.skey option has been replaced by a new security.duosec.secretKeyFile option. Use this new option to store a secure copy of your key instead.") + ]; + options = { security.duosec = { ssh.enable = mkOption { @@ -54,14 +45,18 @@ in description = "If enabled, protect logins with Duo Security using PAM support."; }; - ikey = mkOption { + integrationKey = mkOption { type = types.str; description = "Integration key."; }; - skey = mkOption { - type = types.str; - description = "Secret key."; + secretKeyFile = mkOption { + type = types.path; + default = null; + description = '' + A file containing your secret key. The security of your Duo application is tied to the security of your secret key. + ''; + example = "/run/keys/duo-skey"; }; host = mkOption { @@ -69,10 +64,16 @@ in description = "Duo API hostname."; }; - group = mkOption { + groups = mkOption { type = types.str; default = ""; - description = "Use Duo authentication for users only in this group."; + example = "users,!wheel,!*admin guests"; + description = '' + If specified, Duo authentication is required only for users + whose primary group or supplementary group list matches one + of the space-separated pattern lists. Refer to + <link xlink:href="https://duo.com/docs/duounix"/> for details. + ''; }; failmode = mkOption { @@ -183,21 +184,52 @@ in }; config = mkIf (cfg.ssh.enable || cfg.pam.enable) { - environment.systemPackages = [ pkgs.duo-unix ]; - - security.wrappers.login_duo.source = "${pkgs.duo-unix.out}/bin/login_duo"; - environment.etc = loginCfgFile ++ pamCfgFile; - - /* If PAM *and* SSH are enabled, then don't do anything special. - If PAM isn't used, set the default SSH-only options. */ - services.openssh.extraConfig = mkIf (cfg.ssh.enable || cfg.pam.enable) ( - if cfg.pam.enable then "UseDNS no" else '' - # Duo Security configuration - ForceCommand ${config.security.wrapperDir}/login_duo - PermitTunnel no - ${optionalString (!cfg.allowTcpForwarding) '' - AllowTcpForwarding no - ''} - ''); + environment.systemPackages = [ pkgs.duo-unix ]; + + security.wrappers.login_duo.source = "${pkgs.duo-unix.out}/bin/login_duo"; + + system.activationScripts = { + login_duo = mkIf cfg.ssh.enable '' + if test -f "${cfg.secretKeyFile}"; then + mkdir -m 0755 -p /etc/duo + + umask 0077 + conf="$(mktemp)" + { + cat ${pkgs.writeText "login_duo.conf" configFileLogin} + printf 'skey = %s\n' "$(cat ${cfg.secretKeyFile})" + } >"$conf" + + chown sshd "$conf" + mv -fT "$conf" /etc/duo/login_duo.conf + fi + ''; + pam_duo = mkIf cfg.pam.enable '' + if test -f "${cfg.secretKeyFile}"; then + mkdir -m 0755 -p /etc/duo + + umask 0077 + conf="$(mktemp)" + { + cat ${pkgs.writeText "login_duo.conf" configFilePam} + printf 'skey = %s\n' "$(cat ${cfg.secretKeyFile})" + } >"$conf" + + mv -fT "$conf" /etc/duo/pam_duo.conf + fi + ''; + }; + + /* If PAM *and* SSH are enabled, then don't do anything special. + If PAM isn't used, set the default SSH-only options. */ + services.openssh.extraConfig = mkIf (cfg.ssh.enable || cfg.pam.enable) ( + if cfg.pam.enable then "UseDNS no" else '' + # Duo Security configuration + ForceCommand ${config.security.wrapperDir}/login_duo + PermitTunnel no + ${optionalString (!cfg.allowTcpForwarding) '' + AllowTcpForwarding no + ''} + ''); }; } diff --git a/nixos/modules/security/google_oslogin.nix b/nixos/modules/security/google_oslogin.nix index 246419b681af0..6f9962e1d6266 100644 --- a/nixos/modules/security/google_oslogin.nix +++ b/nixos/modules/security/google_oslogin.nix @@ -59,10 +59,8 @@ in exec ${package}/bin/google_authorized_keys "$@" ''; }; - services.openssh.extraConfig = '' - AuthorizedKeysCommand /etc/ssh/authorized_keys_command_google_oslogin %u - AuthorizedKeysCommandUser nobody - ''; + services.openssh.authorizedKeysCommand = "/etc/ssh/authorized_keys_command_google_oslogin %u"; + services.openssh.authorizedKeysCommandUser = "nobody"; }; } diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix index 11227354ad3bd..bfc2a881387f6 100644 --- a/nixos/modules/security/pam.nix +++ b/nixos/modules/security/pam.nix @@ -475,15 +475,19 @@ let motd = pkgs.writeText "motd" config.users.motd; - makePAMService = pamService: - { source = pkgs.writeText "${pamService.name}.pam" pamService.text; - target = "pam.d/${pamService.name}"; + makePAMService = name: service: + { name = "pam.d/${name}"; + value.source = pkgs.writeText "${name}.pam" service.text; }; in { + imports = [ + (mkRenamedOptionModule [ "security" "pam" "enableU2F" ] [ "security" "pam" "u2f" "enable" ]) + ]; + ###### interface options = { @@ -707,7 +711,7 @@ in Use "challenge-response" for offline validation using YubiKeys with HMAC-SHA-1 Challenge-Response configurations. See the man-page ykpamcfg(1) for further - details on how to configure offline Challenge-Response validation. + details on how to configure offline Challenge-Response validation. More information can be found <link xlink:href="https://developers.yubico.com/yubico-pam/Authentication_Using_Challenge-Response.html">here</link>. @@ -756,8 +760,7 @@ in }; }; - environment.etc = - mapAttrsToList (n: v: makePAMService v) config.security.pam.services; + environment.etc = mapAttrs' makePAMService config.security.pam.services; security.pam.services = { other.text = @@ -773,11 +776,8 @@ in ''; # Most of these should be moved to specific modules. - cups = {}; - ftp = {}; i3lock = {}; i3lock-color = {}; - screen = {}; vlock = {}; xlock = {}; xscreensaver = {}; diff --git a/nixos/modules/security/pam_mount.nix b/nixos/modules/security/pam_mount.nix index 75f58462d13d3..77e22a96b553c 100644 --- a/nixos/modules/security/pam_mount.nix +++ b/nixos/modules/security/pam_mount.nix @@ -36,8 +36,7 @@ in config = mkIf (cfg.enable || anyPamMount) { environment.systemPackages = [ pkgs.pam_mount ]; - environment.etc = [{ - target = "security/pam_mount.conf.xml"; + environment.etc."security/pam_mount.conf.xml" = { source = let extraUserVolumes = filterAttrs (n: u: u.cryptHomeLuks != null) config.users.users; @@ -66,7 +65,7 @@ in ${concatStringsSep "\n" cfg.extraVolumes} </pam_mount> ''; - }]; + }; }; } diff --git a/nixos/modules/security/polkit.nix b/nixos/modules/security/polkit.nix index f2b2df4004cb8..a6724bd758327 100644 --- a/nixos/modules/security/polkit.nix +++ b/nixos/modules/security/polkit.nix @@ -42,15 +42,14 @@ in security.polkit.adminIdentities = mkOption { type = types.listOf types.str; - default = [ "unix-user:0" "unix-group:wheel" ]; + default = [ "unix-group:wheel" ]; example = [ "unix-user:alice" "unix-group:admin" ]; description = '' Specifies which users are considered “administrators”, for those actions that require the user to authenticate as an administrator (i.e. have an <literal>auth_admin</literal> - value). By default, this is the <literal>root</literal> - user and all users in the <literal>wheel</literal> group. + value). By default, this is all users in the <literal>wheel</literal> group. ''; }; diff --git a/nixos/modules/security/rngd.nix b/nixos/modules/security/rngd.nix index d9d6d9c9f2536..cffa1a5849f9d 100644 --- a/nixos/modules/security/rngd.nix +++ b/nixos/modules/security/rngd.nix @@ -37,14 +37,24 @@ in after = [ "dev-random.device" ]; + # Clean shutdown without DefaultDependencies + conflicts = [ "shutdown.target" ]; + before = [ + "sysinit.target" + "shutdown.target" + ]; + description = "Hardware RNG Entropy Gatherer Daemon"; + # rngd may have to start early to avoid entropy starvation during boot with encrypted swap + unitConfig.DefaultDependencies = false; serviceConfig = { ExecStart = "${pkgs.rng-tools}/sbin/rngd -f" + optionalString cfg.debug " -d"; + # PrivateTmp would introduce a circular dependency if /tmp is on tmpfs and swap is encrypted, + # thus depending on rngd before swap, while swap depends on rngd to avoid entropy starvation. NoNewPrivileges = true; PrivateNetwork = true; - PrivateTmp = true; ProtectSystem = "full"; ProtectHome = true; }; diff --git a/nixos/modules/security/rtkit.nix b/nixos/modules/security/rtkit.nix index f6dda21c60064..a7b27cbcf215d 100644 --- a/nixos/modules/security/rtkit.nix +++ b/nixos/modules/security/rtkit.nix @@ -34,9 +34,8 @@ with lib; services.dbus.packages = [ pkgs.rtkit ]; - users.users = singleton - { name = "rtkit"; - uid = config.ids.uids.rtkit; + users.users.rtkit = + { uid = config.ids.uids.rtkit; description = "RealtimeKit daemon"; }; diff --git a/nixos/modules/security/sudo.nix b/nixos/modules/security/sudo.nix index 10ee036be84eb..e3e43177def32 100644 --- a/nixos/modules/security/sudo.nix +++ b/nixos/modules/security/sudo.nix @@ -71,23 +71,25 @@ in this is the case when configuration options are merged. ''; default = []; - example = [ - # Allow execution of any command by all users in group sudo, - # requiring a password. - { groups = [ "sudo" ]; commands = [ "ALL" ]; } - - # Allow execution of "/home/root/secret.sh" by user `backup`, `database` - # and the group with GID `1006` without a password. - { users = [ "backup" "database" ]; groups = [ 1006 ]; - commands = [ { command = "/home/root/secret.sh"; options = [ "SETENV" "NOPASSWD" ]; } ]; } - - # Allow all users of group `bar` to run two executables as user `foo` - # with arguments being pre-set. - { groups = [ "bar" ]; runAs = "foo"; - commands = - [ "/home/baz/cmd1.sh hello-sudo" - { command = ''/home/baz/cmd2.sh ""''; options = [ "SETENV" ]; } ]; } - ]; + example = literalExample '' + [ + # Allow execution of any command by all users in group sudo, + # requiring a password. + { groups = [ "sudo" ]; commands = [ "ALL" ]; } + + # Allow execution of "/home/root/secret.sh" by user `backup`, `database` + # and the group with GID `1006` without a password. + { users = [ "backup" "database" ]; groups = [ 1006 ]; + commands = [ { command = "/home/root/secret.sh"; options = [ "SETENV" "NOPASSWD" ]; } ]; } + + # Allow all users of group `bar` to run two executables as user `foo` + # with arguments being pre-set. + { groups = [ "bar" ]; runAs = "foo"; + commands = + [ "/home/baz/cmd1.sh hello-sudo" + { command = '''/home/baz/cmd2.sh ""'''; options = [ "SETENV" ]; } ]; } + ] + ''; type = with types; listOf (submodule { options = { users = mkOption { @@ -212,7 +214,7 @@ in security.pam.services.sudo = { sshAgentAuth = true; }; - environment.etc = singleton + environment.etc.sudoers = { source = pkgs.runCommand "sudoers" { @@ -222,7 +224,6 @@ in # Make sure that the sudoers file is syntactically valid. # (currently disabled - NIXOS-66) "${pkgs.buildPackages.sudo}/sbin/visudo -f $src -c && cp $src $out"; - target = "sudoers"; mode = "0440"; }; diff --git a/nixos/modules/security/tpm2.nix b/nixos/modules/security/tpm2.nix new file mode 100644 index 0000000000000..13804fb82cbaa --- /dev/null +++ b/nixos/modules/security/tpm2.nix @@ -0,0 +1,185 @@ +{ lib, pkgs, config, ... }: +let + cfg = config.security.tpm2; + + # This snippet is taken from tpm2-tss/dist/tpm-udev.rules, but modified to allow custom user/groups + # The idea is that the tssUser is allowed to acess the TPM and kernel TPM resource manager, while + # the tssGroup is only allowed to access the kernel resource manager + # Therefore, if either of the two are null, the respective part isn't generated + udevRules = tssUser: tssGroup: '' + ${lib.optionalString (tssUser != null) ''KERNEL=="tpm[0-9]*", MODE="0660", OWNER="${tssUser}"''} + ${lib.optionalString (tssUser != null || tssGroup != null) + ''KERNEL=="tpmrm[0-9]*", MODE="0660"'' + + lib.optionalString (tssUser != null) '', OWNER="${tssUser}"'' + + lib.optionalString (tssGroup != null) '', GROUP="${tssGroup}"'' + } + ''; + +in { + options.security.tpm2 = { + enable = lib.mkEnableOption "Trusted Platform Module 2 support"; + + tssUser = lib.mkOption { + description = '' + Name of the tpm device-owner and service user, set if applyUdevRules is + set. + ''; + type = lib.types.nullOr lib.types.str; + default = if cfg.abrmd.enable then "tss" else "root"; + defaultText = ''"tss" when using the userspace resource manager,'' + + ''"root" otherwise''; + }; + + tssGroup = lib.mkOption { + description = '' + Group of the tpm kernel resource manager (tpmrm) device-group, set if + applyUdevRules is set. + ''; + type = lib.types.nullOr lib.types.str; + default = "tss"; + }; + + applyUdevRules = lib.mkOption { + description = '' + Whether to make the /dev/tpm[0-9] devices accessible by the tssUser, or + the /dev/tpmrm[0-9] by tssGroup respectively + ''; + type = lib.types.bool; + default = true; + }; + + abrmd = { + enable = lib.mkEnableOption '' + Trusted Platform 2 userspace resource manager daemon + ''; + + package = lib.mkOption { + description = "tpm2-abrmd package to use"; + type = lib.types.package; + default = pkgs.tpm2-abrmd; + defaultText = "pkgs.tpm2-abrmd"; + }; + }; + + pkcs11 = { + enable = lib.mkEnableOption '' + TPM2 PKCS#11 tool and shared library in system path + (<literal>/run/current-system/sw/lib/libtpm2_pkcs11.so</literal>) + ''; + + package = lib.mkOption { + description = "tpm2-pkcs11 package to use"; + type = lib.types.package; + default = pkgs.tpm2-pkcs11; + defaultText = "pkgs.tpm2-pkcs11"; + }; + }; + + tctiEnvironment = { + enable = lib.mkOption { + description = '' + Set common TCTI environment variables to the specified value. + The variables are + <itemizedlist> + <listitem> + <para> + <literal>TPM2TOOLS_TCTI</literal> + </para> + </listitem> + <listitem> + <para> + <literal>TPM2_PKCS11_TCTI</literal> + </para> + </listitem> + </itemizedlist> + ''; + type = lib.types.bool; + default = false; + }; + + interface = lib.mkOption { + description = '' + The name of the TPM command transmission interface (TCTI) library to + use. + ''; + type = lib.types.enum [ "tabrmd" "device" ]; + default = "device"; + }; + + deviceConf = lib.mkOption { + description = '' + Configuration part of the device TCTI, e.g. the path to the TPM device. + Applies if interface is set to "device". + The format is specified in the + <link xlink:href="https://github.com/tpm2-software/tpm2-tools/blob/master/man/common/tcti.md#tcti-options"> + tpm2-tools repository</link>. + ''; + type = lib.types.str; + default = "/dev/tpmrm0"; + }; + + tabrmdConf = lib.mkOption { + description = '' + Configuration part of the tabrmd TCTI, like the D-Bus bus name. + Applies if interface is set to "tabrmd". + The format is specified in the + <link xlink:href="https://github.com/tpm2-software/tpm2-tools/blob/master/man/common/tcti.md#tcti-options"> + tpm2-tools repository</link>. + ''; + type = lib.types.str; + default = "bus_name=com.intel.tss2.Tabrmd"; + }; + }; + }; + + config = lib.mkIf cfg.enable (lib.mkMerge [ + { + # PKCS11 tools and library + environment.systemPackages = lib.mkIf cfg.pkcs11.enable [ + (lib.getBin cfg.pkcs11.package) + (lib.getLib cfg.pkcs11.package) + ]; + + services.udev.extraRules = lib.mkIf cfg.applyUdevRules + (udevRules cfg.tssUser cfg.tssGroup); + + # Create the tss user and group only if the default value is used + users.users.${cfg.tssUser} = lib.mkIf (cfg.tssUser == "tss") { + isSystemUser = true; + }; + users.groups.${cfg.tssGroup} = lib.mkIf (cfg.tssGroup == "tss") {}; + + environment.variables = lib.mkIf cfg.tctiEnvironment.enable ( + lib.attrsets.genAttrs [ + "TPM2TOOLS_TCTI" + "TPM2_PKCS11_TCTI" + ] (_: ''${cfg.tctiEnvironment.interface}:${ + if cfg.tctiEnvironment.interface == "tabrmd" then + cfg.tctiEnvironment.tabrmdConf + else + cfg.tctiEnvironment.deviceConf + }'') + ); + } + + (lib.mkIf cfg.abrmd.enable { + systemd.services."tpm2-abrmd" = { + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "dbus"; + Restart = "always"; + RestartSec = 30; + BusName = "com.intel.tss2.Tabrmd"; + StandardOutput = "syslog"; + ExecStart = "${cfg.abrmd.package}/bin/tpm2-abrmd"; + User = "tss"; + Group = "nogroup"; + }; + }; + + services.dbus.packages = lib.singleton cfg.abrmd.package; + }) + ]); + + meta.maintainers = with lib.maintainers; [ lschuermann ]; +} diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix index 47738e7962ea6..a0fadb018ecaa 100644 --- a/nixos/modules/security/wrappers/default.nix +++ b/nixos/modules/security/wrappers/default.nix @@ -94,6 +94,10 @@ let ) programs; in { + imports = [ + (lib.mkRemovedOptionModule [ "security" "setuidOwners" ] "Use security.wrappers instead") + (lib.mkRemovedOptionModule [ "security" "setuidPrograms" ] "Use security.wrappers instead") + ]; ###### interface diff --git a/nixos/modules/services/admin/oxidized.nix b/nixos/modules/services/admin/oxidized.nix index da81be3f23e85..94b44630ba6c4 100644 --- a/nixos/modules/services/admin/oxidized.nix +++ b/nixos/modules/services/admin/oxidized.nix @@ -111,6 +111,7 @@ in Restart = "always"; WorkingDirectory = cfg.dataDir; KillSignal = "SIGKILL"; + PIDFile = "${cfg.dataDir}/.config/oxidized/pid"; }; }; }; diff --git a/nixos/modules/services/amqp/activemq/default.nix b/nixos/modules/services/amqp/activemq/default.nix index 7729da27304bb..160dbddcd487d 100644 --- a/nixos/modules/services/amqp/activemq/default.nix +++ b/nixos/modules/services/amqp/activemq/default.nix @@ -63,9 +63,11 @@ in { javaProperties = mkOption { type = types.attrs; default = { }; - example = { - "java.net.preferIPv4Stack" = "true"; - }; + example = literalExample '' + { + "java.net.preferIPv4Stack" = "true"; + } + ''; apply = attrs: { "activemq.base" = "${cfg.baseDir}"; "activemq.data" = "${cfg.baseDir}/data"; diff --git a/nixos/modules/services/amqp/rabbitmq.nix b/nixos/modules/services/amqp/rabbitmq.nix index 697732426ccfb..646708e01c48f 100644 --- a/nixos/modules/services/amqp/rabbitmq.nix +++ b/nixos/modules/services/amqp/rabbitmq.nix @@ -17,6 +17,7 @@ in { options = { services.rabbitmq = { enable = mkOption { + type = types.bool; default = false; description = '' Whether to enable the RabbitMQ server, an Advanced Message @@ -98,8 +99,8 @@ in { will be merged into these options by RabbitMQ at runtime to form the final configuration. - See http://www.rabbitmq.com/configure.html#config-items - For the distinct formats, see http://www.rabbitmq.com/configure.html#config-file-formats + See https://www.rabbitmq.com/configure.html#config-items + For the distinct formats, see https://www.rabbitmq.com/configure.html#config-file-formats ''; }; @@ -116,8 +117,8 @@ in { The contents of this option will be merged into the <literal>configItems</literal> by RabbitMQ at runtime to form the final configuration. - See the second table on http://www.rabbitmq.com/configure.html#config-items - For the distinct formats, see http://www.rabbitmq.com/configure.html#config-file-formats + See the second table on https://www.rabbitmq.com/configure.html#config-items + For the distinct formats, see https://www.rabbitmq.com/configure.html#config-file-formats ''; }; @@ -165,7 +166,10 @@ in { after = [ "network.target" "epmd.socket" ]; wants = [ "network.target" "epmd.socket" ]; - path = [ cfg.package pkgs.procps ]; + path = [ + cfg.package + pkgs.coreutils # mkdir/chown/chmod for preStart + ]; environment = { RABBITMQ_MNESIA_BASE = "${cfg.dataDir}/mnesia"; diff --git a/nixos/modules/services/audio/alsa.nix b/nixos/modules/services/audio/alsa.nix index f632644af09ef..3fe76a1654010 100644 --- a/nixos/modules/services/audio/alsa.nix +++ b/nixos/modules/services/audio/alsa.nix @@ -12,6 +12,9 @@ let in { + imports = [ + (mkRenamedOptionModule [ "sound" "enableMediaKeys" ] [ "sound" "mediaKeys" "enable" ]) + ]; ###### interface @@ -88,11 +91,7 @@ in environment.systemPackages = [ alsaUtils ]; environment.etc = mkIf (!pulseaudioEnabled && config.sound.extraConfig != "") - [ - { source = pkgs.writeText "asound.conf" config.sound.extraConfig; - target = "asound.conf"; - } - ]; + { "asound.conf".text = config.sound.extraConfig; }; # ALSA provides a udev rule for restoring volume settings. services.udev.packages = [ alsaUtils ]; diff --git a/nixos/modules/services/audio/mopidy.nix b/nixos/modules/services/audio/mopidy.nix index a534b692f1771..6fd7eae5b892d 100644 --- a/nixos/modules/services/audio/mopidy.nix +++ b/nixos/modules/services/audio/mopidy.nix @@ -13,11 +13,11 @@ let mopidyEnv = buildEnv { name = "mopidy-with-extensions-${mopidy.version}"; paths = closePropagation cfg.extensionPackages; - pathsToLink = [ "/${python.sitePackages}" ]; + pathsToLink = [ "/${mopidyPackages.python.sitePackages}" ]; buildInputs = [ makeWrapper ]; postBuild = '' makeWrapper ${mopidy}/bin/mopidy $out/bin/mopidy \ - --prefix PYTHONPATH : $out/${python.sitePackages} + --prefix PYTHONPATH : $out/${mopidyPackages.python.sitePackages} ''; }; in { diff --git a/nixos/modules/services/audio/mpd.nix b/nixos/modules/services/audio/mpd.nix index 7932d094197b7..e20591b5beb4f 100644 --- a/nixos/modules/services/audio/mpd.nix +++ b/nixos/modules/services/audio/mpd.nix @@ -184,19 +184,19 @@ in { }; }; - users.users = optionalAttrs (cfg.user == name) (singleton { - inherit uid; - inherit name; - group = cfg.group; - extraGroups = [ "audio" ]; - description = "Music Player Daemon user"; - home = "${cfg.dataDir}"; - }); - - users.groups = optionalAttrs (cfg.group == name) (singleton { - inherit name; - gid = gid; - }); + users.users = optionalAttrs (cfg.user == name) { + ${name} = { + inherit uid; + group = cfg.group; + extraGroups = [ "audio" ]; + description = "Music Player Daemon user"; + home = "${cfg.dataDir}"; + }; + }; + + users.groups = optionalAttrs (cfg.group == name) { + ${name}.gid = gid; + }; }; } diff --git a/nixos/modules/services/audio/roon-server.nix b/nixos/modules/services/audio/roon-server.nix index 4eda3c5708da7..6aed485638ccf 100644 --- a/nixos/modules/services/audio/roon-server.nix +++ b/nixos/modules/services/audio/roon-server.nix @@ -66,7 +66,8 @@ in { if cfg.user == "roon-server" then { isSystemUser = true; description = "Roon Server user"; - groups = [ cfg.group "audio" ]; + group = cfg.group; + extraGroups = [ "audio" ]; } else {}; }; diff --git a/nixos/modules/services/backup/bacula.nix b/nixos/modules/services/backup/bacula.nix index 41bda7893a757..cef304734aeee 100644 --- a/nixos/modules/services/backup/bacula.nix +++ b/nixos/modules/services/backup/bacula.nix @@ -44,7 +44,17 @@ let Pid Directory = "/run"; ${sd_cfg.extraStorageConfig} } - + + ${concatStringsSep "\n" (mapAttrsToList (name: value: '' + Autochanger { + Name = "${name}"; + Device = ${concatStringsSep ", " (map (a: "\"${a}\"") value.devices)}; + Changer Device = "${value.changerDevice}"; + Changer Command = "${value.changerCommand}"; + ${value.extraAutochangerConfig} + } + '') sd_cfg.autochanger)} + ${concatStringsSep "\n" (mapAttrsToList (name: value: '' Device { Name = "${name}"; @@ -103,7 +113,19 @@ let password = mkOption { # TODO: required? description = '' - Specifies the password that must be supplied for a Director to b + Specifies the password that must be supplied for the default Bacula + Console to be authorized. The same password must appear in the + Director resource of the Console configuration file. For added + security, the password is never passed across the network but instead + a challenge response hash code created with the password. This + directive is required. If you have either /dev/random or bc on your + machine, Bacula will generate a random password during the + configuration process, otherwise it will be left blank and you must + manually supply it. + + The password is plain text. It is not generated through any special + process but as noted above, it is better to use random text for + security reasons. ''; }; @@ -111,26 +133,133 @@ let default = "no"; example = "yes"; description = '' - If Monitor is set to no (default), this director will have full + If Monitor is set to <literal>no</literal>, this director will have + full access to this Storage daemon. If Monitor is set to + <literal>yes</literal>, this director will only be able to fetch the + current status of this Storage daemon. + + Please note that if this director is being used by a Monitor, we + highly recommend to set this directive to yes to avoid serious + security problems. + ''; + }; + }; + }; + + autochangerOptions = {...}: + { + options = { + changerDevice = mkOption { + description = '' + The specified name-string must be the generic SCSI device name of the + autochanger that corresponds to the normal read/write Archive Device + specified in the Device resource. This generic SCSI device name + should be specified if you have an autochanger or if you have a + standard tape drive and want to use the Alert Command (see below). + For example, on Linux systems, for an Archive Device name of + <literal>/dev/nst0</literal>, you would specify + <literal>/dev/sg0</literal> for the Changer Device name. Depending + on your exact configuration, and the number of autochangers or the + type of autochanger, what you specify here can vary. This directive + is optional. See the Using AutochangersAutochangersChapter chapter of + this manual for more details of using this and the following + autochanger directives. + ''; + }; + + changerCommand = mkOption { + description = '' + The name-string specifies an external program to be called that will + automatically change volumes as required by Bacula. Normally, this + directive will be specified only in the AutoChanger resource, which + is then used for all devices. However, you may also specify the + different Changer Command in each Device resource. Most frequently, + you will specify the Bacula supplied mtx-changer script as follows: + + <literal>"/path/mtx-changer %c %o %S %a %d"</literal> + + and you will install the mtx on your system (found in the depkgs + release). An example of this command is in the default bacula-sd.conf + file. For more details on the substitution characters that may be + specified to configure your autochanger please see the + AutochangersAutochangersChapter chapter of this manual. For FreeBSD + users, you might want to see one of the several chio scripts in + examples/autochangers. + ''; + default = "/etc/bacula/mtx-changer %c %o %S %a %d"; + }; + + devices = mkOption { + description = '' + ''; + }; + + extraAutochangerConfig = mkOption { + default = ""; + description = '' + Extra configuration to be passed in Autochanger directive. + ''; + example = '' + ''; }; }; }; + deviceOptions = {...}: { options = { archiveDevice = mkOption { # TODO: required? description = '' - The specified name-string gives the system file name of the storage device managed by this storage daemon. This will usually be the device file name of a removable storage device (tape drive), for example " /dev/nst0" or "/dev/rmt/0mbn". For a DVD-writer, it will be for example /dev/hdc. It may also be a directory name if you are archiving to disk storage. + The specified name-string gives the system file name of the storage + device managed by this storage daemon. This will usually be the + device file name of a removable storage device (tape drive), for + example <literal>/dev/nst0</literal> or + <literal>/dev/rmt/0mbn</literal>. For a DVD-writer, it will be for + example <literal>/dev/hdc</literal>. It may also be a directory name + if you are archiving to disk storage. In this case, you must supply + the full absolute path to the directory. When specifying a tape + device, it is preferable that the "non-rewind" variant of the device + file name be given. ''; }; mediaType = mkOption { # TODO: required? description = '' - The specified name-string names the type of media supported by this device, for example, "DLT7000". Media type names are arbitrary in that you set them to anything you want, but they must be known to the volume database to keep track of which storage daemons can read which volumes. In general, each different storage type should have a unique Media Type associated with it. The same name-string must appear in the appropriate Storage resource definition in the Director's configuration file. + The specified name-string names the type of media supported by this + device, for example, <literal>DLT7000</literal>. Media type names are + arbitrary in that you set them to anything you want, but they must be + known to the volume database to keep track of which storage daemons + can read which volumes. In general, each different storage type + should have a unique Media Type associated with it. The same + name-string must appear in the appropriate Storage resource + definition in the Director's configuration file. + + Even though the names you assign are arbitrary (i.e. you choose the + name you want), you should take care in specifying them because the + Media Type is used to determine which storage device Bacula will + select during restore. Thus you should probably use the same Media + Type specification for all drives where the Media can be freely + interchanged. This is not generally an issue if you have a single + Storage daemon, but it is with multiple Storage daemons, especially + if they have incompatible media. + + For example, if you specify a Media Type of <literal>DDS-4</literal> + then during the restore, Bacula will be able to choose any Storage + Daemon that handles <literal>DDS-4</literal>. If you have an + autochanger, you might want to name the Media Type in a way that is + unique to the autochanger, unless you wish to possibly use the + Volumes in other drives. You should also ensure to have unique Media + Type names if the Media is not compatible between drives. This + specification is required for all devices. + + In addition, if you are using disk storage, each Device resource will + generally have a different mount point or directory. In order for + Bacula to select the correct Device resource, each one must have a + unique Media Type. ''; }; @@ -166,8 +295,8 @@ in { default = "${config.networking.hostName}-fd"; description = '' The client name that must be used by the Director when connecting. - Generally, it is a good idea to use a name related to the machine - so that error messages can be easily identified if you have multiple + Generally, it is a good idea to use a name related to the machine so + that error messages can be easily identified if you have multiple Clients. This directive is required. ''; }; @@ -232,7 +361,8 @@ in { default = 9103; type = types.int; description = '' - Specifies port number on which the Storage daemon listens for Director connections. The default is 9103. + Specifies port number on which the Storage daemon listens for + Director connections. ''; }; @@ -251,7 +381,15 @@ in { ''; type = with types; attrsOf (submodule deviceOptions); }; - + + autochanger = mkOption { + default = {}; + description = '' + This option defines Autochanger resources in Bacula Storage Daemon. + ''; + type = with types; attrsOf (submodule autochangerOptions); + }; + extraStorageConfig = mkOption { default = ""; description = '' @@ -287,7 +425,8 @@ in { name = mkOption { default = "${config.networking.hostName}-dir"; description = '' - The director name used by the system administrator. This directive is required. + The director name used by the system administrator. This directive is + required. ''; }; @@ -295,7 +434,12 @@ in { default = 9101; type = types.int; description = '' - Specify the port (a positive integer) on which the Director daemon will listen for Bacula Console connections. This same port number must be specified in the Director resource of the Console configuration file. The default is 9101, so normally this directive need not be specified. This directive should not be used if you specify DirAddresses (N.B plural) directive. + Specify the port (a positive integer) on which the Director daemon + will listen for Bacula Console connections. This same port number + must be specified in the Director resource of the Console + configuration file. The default is 9101, so normally this directive + need not be specified. This directive should not be used if you + specify DirAddresses (N.B plural) directive. ''; }; diff --git a/nixos/modules/services/backup/borgbackup.nix b/nixos/modules/services/backup/borgbackup.nix index 10d42325a6b16..be661b201f0df 100644 --- a/nixos/modules/services/backup/borgbackup.nix +++ b/nixos/modules/services/backup/borgbackup.nix @@ -68,7 +68,7 @@ let { BORG_PASSPHRASE = passphrase; } else { }; - mkBackupService = name: cfg: + mkBackupService = name: cfg: let userHome = config.users.users.${cfg.user}.home; in nameValuePair "borgbackup-job-${name}" { @@ -98,6 +98,23 @@ let inherit (cfg) startAt; }; + # utility function around makeWrapper + mkWrapperDrv = { + original, name, set ? {} + }: + pkgs.runCommandNoCC "${name}-wrapper" { + buildInputs = [ pkgs.makeWrapper ]; + } (with lib; '' + makeWrapper "${original}" "$out/bin/${name}" \ + ${concatStringsSep " \\\n " (mapAttrsToList (name: value: ''--set ${name} "${value}"'') set)} + ''); + + mkBorgWrapper = name: cfg: mkWrapperDrv { + original = "${pkgs.borgbackup}/bin/borg"; + name = "borg-job-${name}"; + set = { BORG_REPO = cfg.repo; } // (mkPassEnv cfg) // cfg.environment; + }; + # Paths listed in ReadWritePaths must exist before service is started mkActivationScript = name: cfg: let @@ -172,14 +189,20 @@ let in { meta.maintainers = with maintainers; [ dotlambda ]; + meta.doc = ./borgbackup.xml; ###### interface options.services.borgbackup.jobs = mkOption { - description = "Deduplicating backups using BorgBackup."; + description = '' + Deduplicating backups using BorgBackup. + Adding a job will cause a borg-job-NAME wrapper to be added + to your system path, so that you can perform maintenance easily. + See also the chapter about BorgBackup in the NixOS manual. + ''; default = { }; example = literalExample '' - { + { # for a local backup rootBackup = { paths = "/"; exclude = [ "/nix" ]; @@ -192,6 +215,23 @@ in { startAt = "weekly"; }; } + { # Root backing each day up to a remote backup server. We assume that you have + # * created a password less key: ssh-keygen -N "" -t ed25519 -f /path/to/ssh_key + # best practices are: use -t ed25519, /path/to = /run/keys + # * the passphrase is in the file /run/keys/borgbackup_passphrase + # * you have initialized the repository manually + paths = [ "/etc" "/home" ]; + exclude = [ "/nix" "'**/.cache'" ]; + doInit = false; + repo = "user3@arep.repo.borgbase.com:repo"; + encryption = { + mode = "repokey-blake2"; + passCommand = "cat /path/to/passphrase"; + }; + environment = { BORG_RSH = "ssh -i /path/to/ssh_key"; }; + compression = "auto,lzma"; + startAt = "daily"; + }; ''; type = types.attrsOf (types.submodule (let globalConfig = config; in { name, config, ... }: { @@ -247,6 +287,8 @@ in { <manvolnum>7</manvolnum></citerefentry>. If you do not want the backup to start automatically, use <literal>[ ]</literal>. + It will generate a systemd service borgbackup-job-NAME. + You may trigger it manually via systemctl restart borgbackup-job-NAME. ''; }; @@ -282,6 +324,10 @@ in { you to specify a <option>passCommand</option> or a <option>passphrase</option>. ''; + example = '' + encryption.mode = "repokey-blake2" ; + encryption.passphrase = "mySecretPassphrase" ; + ''; }; encryption.passCommand = mkOption { @@ -517,6 +563,7 @@ in { description = '' Serve BorgBackup repositories to given public SSH keys, restricting their access to the repository only. + See also the chapter about BorgBackup in the NixOS manual. Also, clients do not need to specify the absolute path when accessing the repository, i.e. <literal>user@machine:.</literal> is enough. (Note colon and dot.) ''; @@ -623,6 +670,6 @@ in { users = mkMerge (mapAttrsToList mkUsersConfig repos); - environment.systemPackages = with pkgs; [ borgbackup ]; + environment.systemPackages = with pkgs; [ borgbackup ] ++ (mapAttrsToList mkBorgWrapper jobs); }); } diff --git a/nixos/modules/services/backup/borgbackup.xml b/nixos/modules/services/backup/borgbackup.xml new file mode 100644 index 0000000000000..bef7db608f823 --- /dev/null +++ b/nixos/modules/services/backup/borgbackup.xml @@ -0,0 +1,227 @@ +<chapter xmlns="http://docbook.org/ns/docbook" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:xi="http://www.w3.org/2001/XInclude" + version="5.0" + xml:id="module-borgbase"> + <title>BorgBackup</title> + <para> + <emphasis>Source:</emphasis> + <filename>modules/services/backup/borgbackup.nix</filename> + </para> + <para> + <emphasis>Upstream documentation:</emphasis> + <link xlink:href="https://borgbackup.readthedocs.io/"/> + </para> + <para> + <link xlink:href="https://www.borgbackup.org/">BorgBackup</link> (short: Borg) + is a deduplicating backup program. Optionally, it supports compression and + authenticated encryption. + </para> + <para> + The main goal of Borg is to provide an efficient and secure way to backup + data. The data deduplication technique used makes Borg suitable for daily + backups since only changes are stored. The authenticated encryption technique + makes it suitable for backups to not fully trusted targets. + </para> + <section xml:id="module-services-backup-borgbackup-configuring"> + <title>Configuring</title> + <para> + A complete list of options for the Borgbase module may be found + <link linkend="opt-services.borgbackup.jobs">here</link>. + </para> +</section> + <section xml:id="opt-services-backup-borgbackup-local-directory"> + <title>Basic usage for a local backup</title> + + <para> + A very basic configuration for backing up to a locally accessible directory + is: +<programlisting> +{ + opt.services.borgbackup.jobs = { + { rootBackup = { + paths = "/"; + exclude = [ "/nix" "/path/to/local/repo" ]; + repo = "/path/to/local/repo"; + doInit = true; + encryption = { + mode = "repokey"; + passphrase = "secret"; + }; + compression = "auto,lzma"; + startAt = "weekly"; + }; + } + }; +}</programlisting> + </para> + <warning> + <para> + If you do not want the passphrase to be stored in the world-readable + Nix store, use passCommand. You find an example below. + </para> + </warning> + </section> +<section xml:id="opt-services-backup-create-server"> + <title>Create a borg backup server</title> + <para>You should use a different SSH key for each repository you write to, + because the specified keys are restricted to running borg serve and can only + access this single repository. You need the output of the generate pub file. + </para> + <para> + <programlisting> +# sudo ssh-keygen -N '' -t ed25519 -f /run/keys/id_ed25519_my_borg_repo +# cat /run/keys/id_ed25519_my_borg_repo +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID78zmOyA+5uPG4Ot0hfAy+sLDPU1L4AiIoRYEIVbbQ/ root@nixos</programlisting> + </para> + <para> + Add the following snippet to your NixOS configuration: + <programlisting> +{ + services.borgbackup.repos = { + my_borg_repo = { + authorizedKeys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID78zmOyA+5uPG4Ot0hfAy+sLDPU1L4AiIoRYEIVbbQ/ root@nixos" + ] ; + path = "/var/lib/my_borg_repo" ; + }; + }; +}</programlisting> + </para> +</section> + + <section xml:id="opt-services-backup-borgbackup-remote-server"> + <title>Backup to the borg repository server</title> + <para>The following NixOS snippet creates an hourly backup to the service + (on the host nixos) as created in the section above. We assume + that you have stored a secret passphrasse in the file + <code>/run/keys/borgbackup_passphrase</code>, which should be only + accessible by root + </para> + <para> + <programlisting> +{ + services.borgbackup.jobs = { + backupToLocalServer = { + paths = [ "/etc/nixos" ]; + doInit = true; + repo = "borg@nixos:." ; + encryption = { + mode = "repokey-blake2"; + passCommand = "cat /run/keys/borgbackup_passphrase"; + }; + environment = { BORG_RSH = "ssh -i /run/keys/id_ed25519_my_borg_repo"; }; + compression = "auto,lzma"; + startAt = "hourly"; + }; + }; +};</programlisting> + </para> + <para>The following few commands (run as root) let you test your backup. + <programlisting> +> nixos-rebuild switch +...restarting the following units: polkit.service +> systemctl restart borgbackup-job-backupToLocalServer +> sleep 10 +> systemctl restart borgbackup-job-backupToLocalServer +> export BORG_PASSPHRASE=topSecrect +> borg list --rsh='ssh -i /run/keys/id_ed25519_my_borg_repo' borg@nixos:. +nixos-backupToLocalServer-2020-03-30T21:46:17 Mon, 2020-03-30 21:46:19 [84feb97710954931ca384182f5f3cb90665f35cef214760abd7350fb064786ac] +nixos-backupToLocalServer-2020-03-30T21:46:30 Mon, 2020-03-30 21:46:32 [e77321694ecd160ca2228611747c6ad1be177d6e0d894538898de7a2621b6e68]</programlisting> + </para> +</section> + + <section xml:id="opt-services-backup-borgbackup-borgbase"> + <title>Backup to a hosting service</title> + + <para> + Several companies offer <link + xlink:href="https://www.borgbackup.org/support/commercial.html">(paid) + hosting services</link> for Borg repositories. + </para> + <para> + To backup your home directory to borgbase you have to: + </para> + <itemizedlist> + <listitem> + <para> + Generate a SSH key without a password, to access the remote server. E.g. + </para> + <para> + <programlisting>sudo ssh-keygen -N '' -t ed25519 -f /run/keys/id_ed25519_borgbase</programlisting> + </para> + </listitem> + <listitem> + <para> + Create the repository on the server by following the instructions for your + hosting server. + </para> + </listitem> + <listitem> + <para> + Initialize the repository on the server. Eg. + <programlisting> +sudo borg init --encryption=repokey-blake2 \ + -rsh "ssh -i /run/keys/id_ed25519_borgbase" \ + zzz2aaaaa@zzz2aaaaa.repo.borgbase.com:repo</programlisting> + </para> + </listitem> + <listitem> +<para>Add it to your NixOS configuration, e.g. +<programlisting> +{ + services.borgbackup.jobs = { + my_Remote_Backup = { + paths = [ "/" ]; + exclude = [ "/nix" "'**/.cache'" ]; + repo = "zzz2aaaaa@zzz2aaaaa.repo.borgbase.com:repo"; + encryption = { + mode = "repokey-blake2"; + passCommand = "cat /run/keys/borgbackup_passphrase"; + }; + BORG_RSH = "ssh -i /run/keys/id_ed25519_borgbase"; + compression = "auto,lzma"; + startAt = "daily"; + }; + }; +}}</programlisting> + </para> + </listitem> +</itemizedlist> + </section> + <section xml:id="opt-services-backup-borgbackup-vorta"> + <title>Vorta backup client for the desktop</title> + <para> + Vorta is a backup client for macOS and Linux desktops. It integrates the + mighty BorgBackup with your desktop environment to protect your data from + disk failure, ransomware and theft. + </para> + <para> + It is available as a flatpak package. To enable it you must set the + following two configuration items. + </para> + <para> + <programlisting> +services.flatpak.enable = true ; +# next line is needed to avoid the Error +# Error deploying: GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown: +services.accounts-daemon.enable = true; + </programlisting> + </para> + <para>As a normal user you must first install, then run vorta using the + following commands: + <programlisting> +flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo +flatpak install flathub com.borgbase.Vorta +flatpak run --branch=stable --arch=x86_64 --command=vorta com.borgbase.Vorta +</programlisting> + After running <code>flatpak install</code> you can start Vorta also via + the KDE application menu. + </para> + <para> + Details about using Vorta can be found under <link + xlink:href="https://vorta.borgbase.com/usage">https://vorta.borgbase.com + </link>. + </para> + </section> +</chapter> diff --git a/nixos/modules/services/backup/mysql-backup.nix b/nixos/modules/services/backup/mysql-backup.nix index dbd5605143f60..31d606b141a8d 100644 --- a/nixos/modules/services/backup/mysql-backup.nix +++ b/nixos/modules/services/backup/mysql-backup.nix @@ -37,12 +37,7 @@ in services.mysqlBackup = { - enable = mkOption { - default = false; - description = '' - Whether to enable MySQL backups. - ''; - }; + enable = mkEnableOption "MySQL backups"; calendar = mkOption { type = types.str; @@ -84,13 +79,14 @@ in }; config = mkIf cfg.enable { - users.users = optionalAttrs (cfg.user == defaultUser) (singleton - { name = defaultUser; + users.users = optionalAttrs (cfg.user == defaultUser) { + ${defaultUser} = { isSystemUser = true; createHome = false; home = cfg.location; group = "nogroup"; - }); + }; + }; services.mysql.ensureUsers = [{ name = cfg.user; diff --git a/nixos/modules/services/backup/postgresql-backup.nix b/nixos/modules/services/backup/postgresql-backup.nix index 13a36ae32ac0e..428861a7598a1 100644 --- a/nixos/modules/services/backup/postgresql-backup.nix +++ b/nixos/modules/services/backup/postgresql-backup.nix @@ -35,14 +35,16 @@ let in { + imports = [ + (mkRemovedOptionModule [ "services" "postgresqlBackup" "period" ] '' + A systemd timer is now used instead of cron. + The starting time can be configured via <literal>services.postgresqlBackup.startAt</literal>. + '') + ]; + options = { services.postgresqlBackup = { - enable = mkOption { - default = false; - description = '' - Whether to enable PostgreSQL dumps. - ''; - }; + enable = mkEnableOption "PostgreSQL dumps"; startAt = mkOption { default = "*-*-* 01:15:00"; @@ -82,7 +84,7 @@ in { pgdumpOptions = mkOption { type = types.separatedString " "; - default = "-Cbo"; + default = "-C"; description = '' Command line options for pg_dump. This options is not used if <literal>config.services.postgresqlBackup.backupAll</literal> is enabled. diff --git a/nixos/modules/services/backup/restic.nix b/nixos/modules/services/backup/restic.nix index 7e8e91e4b9c38..2388f1d6ca137 100644 --- a/nixos/modules/services/backup/restic.nix +++ b/nixos/modules/services/backup/restic.nix @@ -103,6 +103,34 @@ in Create the repository if it doesn't exist. ''; }; + + pruneOpts = mkOption { + type = types.listOf types.str; + default = []; + description = '' + A list of options (--keep-* et al.) for 'restic forget + --prune', to automatically prune old snapshots. The + 'forget' command is run *after* the 'backup' command, so + keep that in mind when constructing the --keep-* options. + ''; + example = [ + "--keep-daily 7" + "--keep-weekly 5" + "--keep-monthly 12" + "--keep-yearly 75" + ]; + }; + + dynamicFilesFrom = mkOption { + type = with types; nullOr str; + default = null; + description = '' + A script that produces a list of files to back up. The + results of this command are given to the '--files-from' + option. + ''; + example = "find /home/matt/git -type d -name .git"; + }; }; })); default = {}; @@ -134,25 +162,41 @@ in let extraOptions = concatMapStrings (arg: " -o ${arg}") backup.extraOptions; resticCmd = "${pkgs.restic}/bin/restic${extraOptions}"; + filesFromTmpFile = "/run/restic-backups-${name}/includes"; + backupPaths = if (backup.dynamicFilesFrom == null) + then concatStringsSep " " backup.paths + else "--files-from ${filesFromTmpFile}"; + pruneCmd = optionals (builtins.length backup.pruneOpts > 0) [ + ( resticCmd + " forget --prune " + (concatStringsSep " " backup.pruneOpts) ) + ( resticCmd + " check" ) + ]; in nameValuePair "restic-backups-${name}" ({ environment = { RESTIC_PASSWORD_FILE = backup.passwordFile; RESTIC_REPOSITORY = backup.repository; }; - path = with pkgs; [ - openssh - ]; + path = [ pkgs.openssh ]; restartIfChanged = false; serviceConfig = { Type = "oneshot"; - ExecStart = "${resticCmd} backup ${concatStringsSep " " backup.extraBackupArgs} ${concatStringsSep " " backup.paths}"; + ExecStart = [ "${resticCmd} backup ${concatStringsSep " " backup.extraBackupArgs} ${backupPaths}" ] ++ pruneCmd; User = backup.user; + RuntimeDirectory = "restic-backups-${name}"; } // optionalAttrs (backup.s3CredentialsFile != null) { EnvironmentFile = backup.s3CredentialsFile; }; - } // optionalAttrs backup.initialize { + } // optionalAttrs (backup.initialize || backup.dynamicFilesFrom != null) { preStart = '' - ${resticCmd} snapshots || ${resticCmd} init + ${optionalString (backup.initialize) '' + ${resticCmd} snapshots || ${resticCmd} init + ''} + ${optionalString (backup.dynamicFilesFrom != null) '' + ${pkgs.writeScript "dynamicFilesFromScript" backup.dynamicFilesFrom} > ${filesFromTmpFile} + ''} + ''; + } // optionalAttrs (backup.dynamicFilesFrom != null) { + postStart = '' + rm ${filesFromTmpFile} ''; }) ) config.services.restic.backups; diff --git a/nixos/modules/services/backup/sanoid.nix b/nixos/modules/services/backup/sanoid.nix new file mode 100644 index 0000000000000..0472fb4ba1e75 --- /dev/null +++ b/nixos/modules/services/backup/sanoid.nix @@ -0,0 +1,213 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.sanoid; + + datasetSettingsType = with types; + (attrsOf (nullOr (oneOf [ str int bool (listOf str) ]))) // { + description = "dataset/template options"; + }; + + # Default values from https://github.com/jimsalterjrs/sanoid/blob/master/sanoid.defaults.conf + + commonOptions = { + hourly = mkOption { + description = "Number of hourly snapshots."; + type = types.ints.unsigned; + default = 48; + }; + + daily = mkOption { + description = "Number of daily snapshots."; + type = types.ints.unsigned; + default = 90; + }; + + monthly = mkOption { + description = "Number of monthly snapshots."; + type = types.ints.unsigned; + default = 6; + }; + + yearly = mkOption { + description = "Number of yearly snapshots."; + type = types.ints.unsigned; + default = 0; + }; + + autoprune = mkOption { + description = "Whether to automatically prune old snapshots."; + type = types.bool; + default = true; + }; + + autosnap = mkOption { + description = "Whether to automatically take snapshots."; + type = types.bool; + default = true; + }; + + settings = mkOption { + description = '' + Free-form settings for this template/dataset. See + <link xlink:href="https://github.com/jimsalterjrs/sanoid/blob/master/sanoid.defaults.conf"/> + for allowed values. + ''; + type = datasetSettingsType; + }; + }; + + commonConfig = config: { + settings = { + hourly = mkDefault config.hourly; + daily = mkDefault config.daily; + monthly = mkDefault config.monthly; + yearly = mkDefault config.yearly; + autoprune = mkDefault config.autoprune; + autosnap = mkDefault config.autosnap; + }; + }; + + datasetOptions = { + useTemplate = mkOption { + description = "Names of the templates to use for this dataset."; + type = (types.listOf (types.enum (attrNames cfg.templates))) // { + description = "list of template names"; + }; + default = []; + }; + + recursive = mkOption { + description = "Whether to recursively snapshot dataset children."; + type = types.bool; + default = false; + }; + + processChildrenOnly = mkOption { + description = "Whether to only snapshot child datasets if recursing."; + type = types.bool; + default = false; + }; + }; + + datasetConfig = config: { + settings = { + use_template = mkDefault config.useTemplate; + recursive = mkDefault config.recursive; + process_children_only = mkDefault config.processChildrenOnly; + }; + }; + + # Extract pool names from configured datasets + pools = unique (map (d: head (builtins.match "([^/]+).*" d)) (attrNames cfg.datasets)); + + configFile = let + mkValueString = v: + if builtins.isList v then concatStringsSep "," v + else generators.mkValueStringDefault {} v; + + mkKeyValue = k: v: if v == null then "" + else generators.mkKeyValueDefault { inherit mkValueString; } "=" k v; + in generators.toINI { inherit mkKeyValue; } cfg.settings; + + configDir = pkgs.writeTextDir "sanoid.conf" configFile; + +in { + + # Interface + + options.services.sanoid = { + enable = mkEnableOption "Sanoid ZFS snapshotting service"; + + interval = mkOption { + type = types.str; + default = "hourly"; + example = "daily"; + description = '' + Run sanoid at this interval. The default is to run hourly. + + The format is described in + <citerefentry><refentrytitle>systemd.time</refentrytitle> + <manvolnum>7</manvolnum></citerefentry>. + ''; + }; + + datasets = mkOption { + type = types.attrsOf (types.submodule ({ config, ... }: { + options = commonOptions // datasetOptions; + config = mkMerge [ (commonConfig config) (datasetConfig config) ]; + })); + default = {}; + description = "Datasets to snapshot."; + }; + + templates = mkOption { + type = types.attrsOf (types.submodule ({ config, ... }: { + options = commonOptions; + config = commonConfig config; + })); + default = {}; + description = "Templates for datasets."; + }; + + settings = mkOption { + type = types.attrsOf datasetSettingsType; + description = '' + Free-form settings written directly to the config file. See + <link xlink:href="https://github.com/jimsalterjrs/sanoid/blob/master/sanoid.defaults.conf"/> + for allowed values. + ''; + }; + + extraArgs = mkOption { + type = types.listOf types.str; + default = []; + example = [ "--verbose" "--readonly" "--debug" ]; + description = '' + Extra arguments to pass to sanoid. See + <link xlink:href="https://github.com/jimsalterjrs/sanoid/#sanoid-command-line-options"/> + for allowed options. + ''; + }; + }; + + # Implementation + + config = mkIf cfg.enable { + services.sanoid.settings = mkMerge [ + (mapAttrs' (d: v: nameValuePair ("template_" + d) v.settings) cfg.templates) + (mapAttrs (d: v: v.settings) cfg.datasets) + ]; + + systemd.services.sanoid = { + description = "Sanoid snapshot service"; + serviceConfig = { + ExecStartPre = map (pool: lib.escapeShellArgs [ + "+/run/booted-system/sw/bin/zfs" "allow" + "sanoid" "snapshot,mount,destroy" pool + ]) pools; + ExecStart = lib.escapeShellArgs ([ + "${pkgs.sanoid}/bin/sanoid" + "--cron" + "--configdir" configDir + ] ++ cfg.extraArgs); + ExecStopPost = map (pool: lib.escapeShellArgs [ + "+/run/booted-system/sw/bin/zfs" "unallow" "sanoid" pool + ]) pools; + User = "sanoid"; + Group = "sanoid"; + DynamicUser = true; + RuntimeDirectory = "sanoid"; + CacheDirectory = "sanoid"; + }; + # Prevents missing snapshots during DST changes + environment.TZ = "UTC"; + after = [ "zfs.target" ]; + startAt = cfg.interval; + }; + }; + + meta.maintainers = with maintainers; [ lopsided98 ]; + } diff --git a/nixos/modules/services/backup/syncoid.nix b/nixos/modules/services/backup/syncoid.nix new file mode 100644 index 0000000000000..fff119c2cf009 --- /dev/null +++ b/nixos/modules/services/backup/syncoid.nix @@ -0,0 +1,172 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.syncoid; +in { + + # Interface + + options.services.syncoid = { + enable = mkEnableOption "Syncoid ZFS synchronization service"; + + interval = mkOption { + type = types.str; + default = "hourly"; + example = "*-*-* *:15:00"; + description = '' + Run syncoid at this interval. The default is to run hourly. + + The format is described in + <citerefentry><refentrytitle>systemd.time</refentrytitle> + <manvolnum>7</manvolnum></citerefentry>. + ''; + }; + + user = mkOption { + type = types.str; + default = "root"; + example = "backup"; + description = '' + The user for the service. Sudo or ZFS privilege delegation must be + configured to use a user other than root. + ''; + }; + + sshKey = mkOption { + type = types.nullOr types.path; + # Prevent key from being copied to store + apply = mapNullable toString; + default = null; + description = '' + SSH private key file to use to login to the remote system. Can be + overridden in individual commands. + ''; + }; + + commonArgs = mkOption { + type = types.listOf types.str; + default = []; + example = [ "--no-sync-snap" ]; + description = '' + Arguments to add to every syncoid command, unless disabled for that + command. See + <link xlink:href="https://github.com/jimsalterjrs/sanoid/#syncoid-command-line-options"/> + for available options. + ''; + }; + + commands = mkOption { + type = types.attrsOf (types.submodule ({ name, ... }: { + options = { + source = mkOption { + type = types.str; + example = "pool/dataset"; + description = '' + Source ZFS dataset. Can be either local or remote. Defaults to + the attribute name. + ''; + }; + + target = mkOption { + type = types.str; + example = "user@server:pool/dataset"; + description = '' + Target ZFS dataset. Can be either local + (<replaceable>pool/dataset</replaceable>) or remote + (<replaceable>user@server:pool/dataset</replaceable>). + ''; + }; + + recursive = mkOption { + type = types.bool; + default = false; + description = '' + Whether to also transfer child datasets. + ''; + }; + + sshKey = mkOption { + type = types.nullOr types.path; + # Prevent key from being copied to store + apply = mapNullable toString; + description = '' + SSH private key file to use to login to the remote system. + Defaults to <option>services.syncoid.sshKey</option> option. + ''; + }; + + sendOptions = mkOption { + type = types.separatedString " "; + default = ""; + example = "Lc e"; + description = '' + Advanced options to pass to zfs send. Options are specified + without their leading dashes and separated by spaces. + ''; + }; + + recvOptions = mkOption { + type = types.separatedString " "; + default = ""; + example = "ux recordsize o compression=lz4"; + description = '' + Advanced options to pass to zfs recv. Options are specified + without their leading dashes and separated by spaces. + ''; + }; + + useCommonArgs = mkOption { + type = types.bool; + default = true; + description = '' + Whether to add the configured common arguments to this command. + ''; + }; + + extraArgs = mkOption { + type = types.listOf types.str; + default = []; + example = [ "--sshport 2222" ]; + description = "Extra syncoid arguments for this command."; + }; + }; + config = { + source = mkDefault name; + sshKey = mkDefault cfg.sshKey; + }; + })); + default = {}; + example = literalExample '' + { + "pool/test".target = "root@target:pool/test"; + } + ''; + description = "Syncoid commands to run."; + }; + }; + + # Implementation + + config = mkIf cfg.enable { + systemd.services.syncoid = { + description = "Syncoid ZFS synchronization service"; + script = concatMapStringsSep "\n" (c: lib.escapeShellArgs + ([ "${pkgs.sanoid}/bin/syncoid" ] + ++ (optionals c.useCommonArgs cfg.commonArgs) + ++ (optional c.recursive "-r") + ++ (optionals (c.sshKey != null) [ "--sshkey" c.sshKey ]) + ++ c.extraArgs + ++ [ "--sendoptions" c.sendOptions + "--recvoptions" c.recvOptions + c.source c.target + ])) (attrValues cfg.commands); + after = [ "zfs.target" ]; + serviceConfig.User = cfg.user; + startAt = cfg.interval; + }; + }; + + meta.maintainers = with maintainers; [ lopsided98 ]; + } diff --git a/nixos/modules/services/backup/tarsnap.nix b/nixos/modules/services/backup/tarsnap.nix index 4fc7c24813a55..6d99a1efb6138 100644 --- a/nixos/modules/services/backup/tarsnap.nix +++ b/nixos/modules/services/backup/tarsnap.nix @@ -23,6 +23,10 @@ let ''; in { + imports = [ + (mkRemovedOptionModule [ "services" "tarsnap" "cachedir" ] "Use services.tarsnap.archives.<name>.cachedir") + ]; + options = { services.tarsnap = { enable = mkOption { diff --git a/nixos/modules/services/cluster/hadoop/default.nix b/nixos/modules/services/cluster/hadoop/default.nix index f0f5a6ecbfc54..bfb73f6837159 100644 --- a/nixos/modules/services/cluster/hadoop/default.nix +++ b/nixos/modules/services/cluster/hadoop/default.nix @@ -7,33 +7,41 @@ with lib; options.services.hadoop = { coreSite = mkOption { default = {}; - example = { - "fs.defaultFS" = "hdfs://localhost"; - }; + example = literalExample '' + { + "fs.defaultFS" = "hdfs://localhost"; + } + ''; description = "Hadoop core-site.xml definition"; }; hdfsSite = mkOption { default = {}; - example = { - "dfs.nameservices" = "namenode1"; - }; + example = literalExample '' + { + "dfs.nameservices" = "namenode1"; + } + ''; description = "Hadoop hdfs-site.xml definition"; }; mapredSite = mkOption { default = {}; - example = { - "mapreduce.map.cpu.vcores" = "1"; - }; + example = literalExample '' + { + "mapreduce.map.cpu.vcores" = "1"; + } + ''; description = "Hadoop mapred-site.xml definition"; }; yarnSite = mkOption { default = {}; - example = { - "yarn.resourcemanager.ha.id" = "resourcemanager1"; - }; + example = literalExample '' + { + "yarn.resourcemanager.ha.id" = "resourcemanager1"; + } + ''; description = "Hadoop yarn-site.xml definition"; }; diff --git a/nixos/modules/services/cluster/k3s/default.nix b/nixos/modules/services/cluster/k3s/default.nix new file mode 100644 index 0000000000000..2e8bf20a68fc4 --- /dev/null +++ b/nixos/modules/services/cluster/k3s/default.nix @@ -0,0 +1,101 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.services.k3s; +in +{ + # interface + options.services.k3s = { + enable = mkEnableOption "k3s"; + + package = mkOption { + type = types.package; + default = pkgs.k3s; + defaultText = "pkgs.k3s"; + example = literalExample "pkgs.k3s"; + description = "Package that should be used for k3s"; + }; + + role = mkOption { + description = '' + Whether k3s should run as a server or agent. + Note that the server, by default, also runs as an agent. + ''; + default = "server"; + type = types.enum [ "server" "agent" ]; + }; + + serverAddr = mkOption { + type = types.str; + description = "The k3s server to connect to. This option only makes sense for an agent."; + example = "https://10.0.0.10:6443"; + default = ""; + }; + + token = mkOption { + type = types.str; + description = "The k3s token to use when connecting to the server. This option only makes sense for an agent."; + default = ""; + }; + + docker = mkOption { + type = types.bool; + default = false; + description = "Use docker to run containers rather than the built-in containerd."; + }; + + extraFlags = mkOption { + description = "Extra flags to pass to the k3s command."; + default = ""; + example = "--no-deploy traefik --cluster-cidr 10.24.0.0/16"; + }; + + disableAgent = mkOption { + type = types.bool; + default = false; + description = "Only run the server. This option only makes sense for a server."; + }; + }; + + # implementation + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.role == "agent" -> cfg.serverAddr != ""; + message = "serverAddr should be set if role is 'agent'"; + } + { + assertion = cfg.role == "agent" -> cfg.token != ""; + message = "token should be set if role is 'agent'"; + } + ]; + + virtualisation.docker = mkIf cfg.docker { + enable = mkDefault true; + }; + + systemd.services.k3s = { + description = "k3s service"; + after = mkIf cfg.docker [ "docker.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + # See: https://github.com/rancher/k3s/blob/dddbd16305284ae4bd14c0aade892412310d7edc/install.sh#L197 + Type = if cfg.role == "agent" then "exec" else "notify"; + KillMode = "process"; + Delegate = "yes"; + Restart = "always"; + RestartSec = "5s"; + ExecStart = concatStringsSep " \\\n " ( + [ + "${cfg.package}/bin/k3s ${cfg.role}" + ] ++ (optional cfg.docker "--docker") + ++ (optional cfg.disableAgent "--disable-agent") + ++ (optional (cfg.role == "agent") "--server ${cfg.serverAddr} --token ${cfg.token}") + ++ [ cfg.extraFlags ] + ); + }; + }; + }; +} diff --git a/nixos/modules/services/cluster/kubernetes/addon-manager.nix b/nixos/modules/services/cluster/kubernetes/addon-manager.nix index 17f2dde31a71e..f55079300b15e 100644 --- a/nixos/modules/services/cluster/kubernetes/addon-manager.nix +++ b/nixos/modules/services/cluster/kubernetes/addon-manager.nix @@ -88,7 +88,7 @@ in services.kubernetes.addonManager.bootstrapAddons = mkIf isRBACEnabled (let - name = system:kube-addon-manager; + name = "system:kube-addon-manager"; namespace = "kube-system"; in { diff --git a/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix b/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix index 70f96d75a4619..2ed7742eda09b 100644 --- a/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix +++ b/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix @@ -5,6 +5,10 @@ with lib; let cfg = config.services.kubernetes.addons.dashboard; in { + imports = [ + (mkRenamedOptionModule [ "services" "kubernetes" "addons" "dashboard" "enableRBAC" ] [ "services" "kubernetes" "addons" "dashboard" "rbac" "enable" ]) + ]; + options.services.kubernetes.addons.dashboard = { enable = mkEnableOption "kubernetes dashboard addon"; diff --git a/nixos/modules/services/cluster/kubernetes/apiserver.nix b/nixos/modules/services/cluster/kubernetes/apiserver.nix index 33796bf2e0806..95bdb4c0d14e4 100644 --- a/nixos/modules/services/cluster/kubernetes/apiserver.nix +++ b/nixos/modules/services/cluster/kubernetes/apiserver.nix @@ -13,6 +13,18 @@ let )) + ".1"); in { + + imports = [ + (mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "admissionControl" ] [ "services" "kubernetes" "apiserver" "enableAdmissionPlugins" ]) + (mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "address" ] ["services" "kubernetes" "apiserver" "bindAddress"]) + (mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "port" ] ["services" "kubernetes" "apiserver" "insecurePort"]) + (mkRemovedOptionModule [ "services" "kubernetes" "apiserver" "publicAddress" ] "") + (mkRenamedOptionModule [ "services" "kubernetes" "etcd" "servers" ] [ "services" "kubernetes" "apiserver" "etcd" "servers" ]) + (mkRenamedOptionModule [ "services" "kubernetes" "etcd" "keyFile" ] [ "services" "kubernetes" "apiserver" "etcd" "keyFile" ]) + (mkRenamedOptionModule [ "services" "kubernetes" "etcd" "certFile" ] [ "services" "kubernetes" "apiserver" "etcd" "certFile" ]) + (mkRenamedOptionModule [ "services" "kubernetes" "etcd" "caFile" ] [ "services" "kubernetes" "apiserver" "etcd" "caFile" ]) + ]; + ###### interface options.services.kubernetes.apiserver = with lib.types; { diff --git a/nixos/modules/services/cluster/kubernetes/controller-manager.nix b/nixos/modules/services/cluster/kubernetes/controller-manager.nix index 0b73d090f2417..a99ef6640e974 100644 --- a/nixos/modules/services/cluster/kubernetes/controller-manager.nix +++ b/nixos/modules/services/cluster/kubernetes/controller-manager.nix @@ -7,6 +7,11 @@ let cfg = top.controllerManager; in { + imports = [ + (mkRenamedOptionModule [ "services" "kubernetes" "controllerManager" "address" ] ["services" "kubernetes" "controllerManager" "bindAddress"]) + (mkRenamedOptionModule [ "services" "kubernetes" "controllerManager" "port" ] ["services" "kubernetes" "controllerManager" "insecurePort"]) + ]; + ###### interface options.services.kubernetes.controllerManager = with lib.types; { diff --git a/nixos/modules/services/cluster/kubernetes/default.nix b/nixos/modules/services/cluster/kubernetes/default.nix index 3790ac9b6918f..3a11a6513a491 100644 --- a/nixos/modules/services/cluster/kubernetes/default.nix +++ b/nixos/modules/services/cluster/kubernetes/default.nix @@ -74,6 +74,10 @@ let }; in { + imports = [ + (mkRemovedOptionModule [ "services" "kubernetes" "verbose" ] "") + ]; + ###### interface options.services.kubernetes = { @@ -262,8 +266,7 @@ in { "d /var/lib/kubernetes 0755 kubernetes kubernetes -" ]; - users.users = singleton { - name = "kubernetes"; + users.users.kubernetes = { uid = config.ids.uids.kubernetes; description = "Kubernetes user"; extraGroups = [ "docker" ]; diff --git a/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixos/modules/services/cluster/kubernetes/kubelet.nix index 62d893dfefc6e..c3d67552cc8c3 100644 --- a/nixos/modules/services/cluster/kubernetes/kubelet.nix +++ b/nixos/modules/services/cluster/kubernetes/kubelet.nix @@ -52,6 +52,12 @@ let taints = concatMapStringsSep "," (v: "${v.key}=${v.value}:${v.effect}") (mapAttrsToList (n: v: v) cfg.taints); in { + imports = [ + (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "applyManifests" ] "") + (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "cadvisorPort" ] "") + (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "allowPrivileged" ] "") + ]; + ###### interface options.services.kubernetes.kubelet = with lib.types; { diff --git a/nixos/modules/services/cluster/kubernetes/pki.nix b/nixos/modules/services/cluster/kubernetes/pki.nix index 733479e24c977..4275563f1a36b 100644 --- a/nixos/modules/services/cluster/kubernetes/pki.nix +++ b/nixos/modules/services/cluster/kubernetes/pki.nix @@ -20,6 +20,7 @@ let size = 2048; }; CN = top.masterAddress; + hosts = cfg.cfsslAPIExtraSANs; }); cfsslAPITokenBaseName = "apitoken.secret"; @@ -66,6 +67,15 @@ in type = bool; }; + cfsslAPIExtraSANs = mkOption { + description = '' + Extra x509 Subject Alternative Names to be added to the cfssl API webserver TLS cert. + ''; + default = []; + example = [ "subdomain.example.com" ]; + type = listOf str; + }; + genCfsslAPIToken = mkOption { description = '' Whether to automatically generate cfssl API-token secret, diff --git a/nixos/modules/services/cluster/kubernetes/proxy.nix b/nixos/modules/services/cluster/kubernetes/proxy.nix index 3943c908840c0..86d1dc2439bd9 100644 --- a/nixos/modules/services/cluster/kubernetes/proxy.nix +++ b/nixos/modules/services/cluster/kubernetes/proxy.nix @@ -7,6 +7,9 @@ let cfg = top.proxy; in { + imports = [ + (mkRenamedOptionModule [ "services" "kubernetes" "proxy" "address" ] ["services" "kubernetes" "proxy" "bindAddress"]) + ]; ###### interface options.services.kubernetes.proxy = with lib.types; { diff --git a/nixos/modules/services/computing/boinc/client.nix b/nixos/modules/services/computing/boinc/client.nix index a7edac0253841..7becf6240710e 100644 --- a/nixos/modules/services/computing/boinc/client.nix +++ b/nixos/modules/services/computing/boinc/client.nix @@ -99,14 +99,16 @@ in environment.systemPackages = [cfg.package]; users.users.boinc = { + group = "boinc"; createHome = false; description = "BOINC Client"; home = cfg.dataDir; isSystemUser = true; }; + users.groups.boinc = {}; systemd.tmpfiles.rules = [ - "d '${cfg.dataDir}' - boinc - - -" + "d '${cfg.dataDir}' - boinc boinc - -" ]; systemd.services.boinc = { @@ -114,7 +116,7 @@ in after = ["network.target"]; wantedBy = ["multi-user.target"]; script = '' - ${fhsEnvExecutable} --dir ${cfg.dataDir} --redirectio ${allowRemoteGuiRpcFlag} + ${fhsEnvExecutable} --dir ${cfg.dataDir} ${allowRemoteGuiRpcFlag} ''; serviceConfig = { User = "boinc"; diff --git a/nixos/modules/services/computing/foldingathome/client.nix b/nixos/modules/services/computing/foldingathome/client.nix new file mode 100644 index 0000000000000..9f99af48c48a6 --- /dev/null +++ b/nixos/modules/services/computing/foldingathome/client.nix @@ -0,0 +1,81 @@ +{ config, lib, pkgs, ... }: +with lib; +let + cfg = config.services.foldingathome; + + args = + ["--team" "${toString cfg.team}"] + ++ lib.optionals (cfg.user != null) ["--user" cfg.user] + ++ cfg.extraArgs + ; +in +{ + imports = [ + (mkRenamedOptionModule [ "services" "foldingAtHome" ] [ "services" "foldingathome" ]) + (mkRenamedOptionModule [ "services" "foldingathome" "nickname" ] [ "services" "foldingathome" "user" ]) + (mkRemovedOptionModule [ "services" "foldingathome" "config" ] '' + Use <literal>services.foldingathome.extraArgs instead<literal> + '') + ]; + options.services.foldingathome = { + enable = mkEnableOption "Enable the Folding@home client"; + + package = mkOption { + type = types.package; + default = pkgs.fahclient; + defaultText = "pkgs.fahclient"; + description = '' + Which Folding@home client to use. + ''; + }; + + user = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The user associated with the reported computation results. This will + be used in the ranking statistics. + ''; + }; + + team = mkOption { + type = types.int; + default = 236565; + description = '' + The team ID associated with the reported computation results. This + will be used in the ranking statistics. + + By default, use the NixOS folding@home team ID is being used. + ''; + }; + + extraArgs = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Extra startup options for the FAHClient. Run + <literal>FAHClient --help</literal> to find all the available options. + ''; + }; + }; + + config = mkIf cfg.enable { + systemd.services.foldingathome = { + description = "Folding@home client"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + script = '' + exec ${cfg.package}/bin/FAHClient ${lib.escapeShellArgs args} + ''; + serviceConfig = { + DynamicUser = true; + StateDirectory = "foldingathome"; + WorkingDirectory = "%S/foldingathome"; + }; + }; + }; + + meta = { + maintainers = with lib.maintainers; [ zimbatm ]; + }; +} diff --git a/nixos/modules/services/computing/slurm/slurm.nix b/nixos/modules/services/computing/slurm/slurm.nix index c70d999ca96d9..050872e933fb1 100644 --- a/nixos/modules/services/computing/slurm/slurm.nix +++ b/nixos/modules/services/computing/slurm/slurm.nix @@ -355,6 +355,7 @@ in ExecStart = "${wrappedSlurm}/bin/slurmd"; PIDFile = "/run/slurmd.pid"; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + LimitMEMLOCK = "infinity"; }; preStart = '' diff --git a/nixos/modules/services/continuous-integration/buildbot/master.nix b/nixos/modules/services/continuous-integration/buildbot/master.nix index 9c615fbe885f6..e3da3092d4593 100644 --- a/nixos/modules/services/continuous-integration/buildbot/master.nix +++ b/nixos/modules/services/continuous-integration/buildbot/master.nix @@ -222,19 +222,20 @@ in { }; config = mkIf cfg.enable { - users.groups = optional (cfg.group == "buildbot") { - name = "buildbot"; + users.groups = optionalAttrs (cfg.group == "buildbot") { + buildbot = { }; }; - users.users = optional (cfg.user == "buildbot") { - name = "buildbot"; - description = "Buildbot User."; - isNormalUser = true; - createHome = true; - home = cfg.home; - group = cfg.group; - extraGroups = cfg.extraGroups; - useDefaultShell = true; + users.users = optionalAttrs (cfg.user == "buildbot") { + buildbot = { + description = "Buildbot User."; + isNormalUser = true; + createHome = true; + home = cfg.home; + group = cfg.group; + extraGroups = cfg.extraGroups; + useDefaultShell = true; + }; }; systemd.services.buildbot-master = { diff --git a/nixos/modules/services/continuous-integration/buildbot/worker.nix b/nixos/modules/services/continuous-integration/buildbot/worker.nix index 49e04ca36228c..52f24b8cee3c3 100644 --- a/nixos/modules/services/continuous-integration/buildbot/worker.nix +++ b/nixos/modules/services/continuous-integration/buildbot/worker.nix @@ -136,19 +136,20 @@ in { config = mkIf cfg.enable { services.buildbot-worker.workerPassFile = mkDefault (pkgs.writeText "buildbot-worker-password" cfg.workerPass); - users.groups = optional (cfg.group == "bbworker") { - name = "bbworker"; + users.groups = optionalAttrs (cfg.group == "bbworker") { + bbworker = { }; }; - users.users = optional (cfg.user == "bbworker") { - name = "bbworker"; - description = "Buildbot Worker User."; - isNormalUser = true; - createHome = true; - home = cfg.home; - group = cfg.group; - extraGroups = cfg.extraGroups; - useDefaultShell = true; + users.users = optionalAttrs (cfg.user == "bbworker") { + bbworker = { + description = "Buildbot Worker User."; + isNormalUser = true; + createHome = true; + home = cfg.home; + group = cfg.group; + extraGroups = cfg.extraGroups; + useDefaultShell = true; + }; }; systemd.services.buildbot-worker = { diff --git a/nixos/modules/services/continuous-integration/buildkite-agent.nix b/nixos/modules/services/continuous-integration/buildkite-agents.nix index 32f361454bc18..b0045409ae609 100644 --- a/nixos/modules/services/continuous-integration/buildkite-agent.nix +++ b/nixos/modules/services/continuous-integration/buildkite-agents.nix @@ -3,7 +3,7 @@ with lib; let - cfg = config.services.buildkite-agent; + cfg = config.services.buildkite-agents; mkHookOption = { name, description, example ? null }: { inherit name; @@ -15,7 +15,7 @@ let }; mkHookOptions = hooks: listToAttrs (map mkHookOption hooks); - hooksDir = let + hooksDir = cfg: let mkHookEntry = name: value: '' cat > $out/${name} <<'EOF' #! ${pkgs.runtimeShell} @@ -29,12 +29,13 @@ let ${concatStringsSep "\n" (mapAttrsToList mkHookEntry (filterAttrs (n: v: v != null) cfg.hooks))} ''; -in - -{ - options = { - services.buildkite-agent = { - enable = mkEnableOption "buildkite-agent"; + buildkiteOptions = { name ? "", config, ... }: { + options = { + enable = mkOption { + default = true; + type = types.bool; + description = "Whether to enable this buildkite agent"; + }; package = mkOption { default = pkgs.buildkite-agent; @@ -44,14 +45,14 @@ in }; dataDir = mkOption { - default = "/var/lib/buildkite-agent"; + default = "/var/lib/buildkite-agent-${name}"; description = "The workdir for the agent"; type = types.str; }; runtimePackages = mkOption { - default = [ pkgs.bash pkgs.nix ]; - defaultText = "[ pkgs.bash pkgs.nix ]"; + default = [ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ]; + defaultText = "[ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ]"; description = "Add programs to the buildkite-agent environment"; type = types.listOf types.package; }; @@ -68,19 +69,18 @@ in name = mkOption { type = types.str; - default = "%hostname-%n"; + default = "%hostname-${name}-%n"; description = '' - The name of the agent. + The name of the agent as seen in the buildkite dashboard. ''; }; - meta-data = mkOption { - type = types.str; - default = ""; - example = "queue=default,docker=true,ruby2=true"; + tags = mkOption { + type = types.attrsOf types.str; + default = {}; + example = { queue = "default"; docker = "true"; ruby2 ="true"; }; description = '' - Meta data for the agent. This is a comma-separated list of - <code>key=value</code> pairs. + Tags for the agent. ''; }; @@ -93,26 +93,20 @@ in ''; }; - openssh = - { privateKeyPath = mkOption { - type = types.path; - description = '' - Private agent key. + privateSshKeyPath = mkOption { + type = types.nullOr types.path; + default = null; + ## maximum care is taken so that secrets (ssh keys and the CI token) + ## don't end up in the Nix store. + apply = final: if final == null then null else toString final; - A run-time path to the key file, which is supposed to be provisioned - outside of Nix store. - ''; - }; - publicKeyPath = mkOption { - type = types.path; - description = '' - Public agent key. - - A run-time path to the key file, which is supposed to be provisioned - outside of Nix store. - ''; - }; - }; + description = '' + OpenSSH private key + + A run-time path to the key file, which is supposed to be provisioned + outside of Nix store. + ''; + }; hooks = mkHookOptions [ { name = "checkout"; @@ -173,34 +167,60 @@ in hooksPath = mkOption { type = types.path; - default = hooksDir; - defaultText = "generated from services.buildkite-agent.hooks"; + default = hooksDir config; + defaultText = "generated from services.buildkite-agents.<name>.hooks"; description = '' Path to the directory storing the hooks. - Consider using <option>services.buildkite-agent.hooks.<name></option> + Consider using <option>services.buildkite-agents.<name>.hooks.<name></option> instead. ''; }; - }; - }; - config = mkIf config.services.buildkite-agent.enable { - users.users.buildkite-agent = - { name = "buildkite-agent"; - home = cfg.dataDir; - createHome = true; - description = "Buildkite agent user"; - extraGroups = [ "keys" ]; - isSystemUser = true; + shell = mkOption { + type = types.str; + default = "${pkgs.bash}/bin/bash -e -c"; + description = '' + Command that buildkite-agent 3 will execute when it spawns a shell. + ''; }; + }; + }; + enabledAgents = lib.filterAttrs (n: v: v.enable) cfg; + mapAgents = function: lib.mkMerge (lib.mapAttrsToList function enabledAgents); +in +{ + options.services.buildkite-agents = mkOption { + type = types.attrsOf (types.submodule buildkiteOptions); + default = {}; + description = '' + Attribute set of buildkite agents. + The attribute key is combined with the hostname and a unique integer to + create the final agent name. This can be overridden by setting the `name` + attribute. + ''; + }; - environment.systemPackages = [ cfg.package ]; + config.users.users = mapAgents (name: cfg: { + "buildkite-agent-${name}" = { + name = "buildkite-agent-${name}"; + home = cfg.dataDir; + createHome = true; + description = "Buildkite agent user"; + extraGroups = [ "keys" ]; + isSystemUser = true; + group = "buildkite-agent-${name}"; + }; + }); + config.users.groups = mapAgents (name: cfg: { + "buildkite-agent-${name}" = {}; + }); - systemd.services.buildkite-agent = + config.systemd.services = mapAgents (name: cfg: { + "buildkite-agent-${name}" = { description = "Buildkite Agent"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; - path = cfg.runtimePackages ++ [ pkgs.coreutils ]; + path = cfg.runtimePackages ++ [ cfg.package pkgs.coreutils ]; environment = config.networking.proxy.envVars // { HOME = cfg.dataDir; NIX_REMOTE = "daemon"; @@ -210,17 +230,18 @@ in ## don't end up in the Nix store. preStart = let sshDir = "${cfg.dataDir}/.ssh"; + tagStr = lib.concatStringsSep "," (lib.mapAttrsToList (name: value: "${name}=${value}") cfg.tags); in - '' + optionalString (cfg.privateSshKeyPath != null) '' mkdir -m 0700 -p "${sshDir}" - cp -f "${toString cfg.openssh.privateKeyPath}" "${sshDir}/id_rsa" - cp -f "${toString cfg.openssh.publicKeyPath}" "${sshDir}/id_rsa.pub" - chmod 600 "${sshDir}"/id_rsa* - + cp -f "${toString cfg.privateSshKeyPath}" "${sshDir}/id_rsa" + chmod 600 "${sshDir}"/id_rsa + '' + '' cat > "${cfg.dataDir}/buildkite-agent.cfg" <<EOF token="$(cat ${toString cfg.tokenPath})" name="${cfg.name}" - meta-data="${cfg.meta-data}" + shell="${cfg.shell}" + tags="${tagStr}" build-path="${cfg.dataDir}/builds" hooks-path="${cfg.hooksPath}" ${cfg.extraConfig} @@ -228,26 +249,28 @@ in ''; serviceConfig = - { ExecStart = "${pkgs.buildkite-agent}/bin/buildkite-agent start --config /var/lib/buildkite-agent/buildkite-agent.cfg"; - User = "buildkite-agent"; + { ExecStart = "${cfg.package}/bin/buildkite-agent start --config ${cfg.dataDir}/buildkite-agent.cfg"; + User = "buildkite-agent-${name}"; RestartSec = 5; Restart = "on-failure"; TimeoutSec = 10; + # set a long timeout to give buildkite-agent a chance to finish current builds + TimeoutStopSec = "2 min"; + KillMode = "mixed"; }; }; + }); - assertions = [ - { assertion = cfg.hooksPath == hooksDir || all (v: v == null) (attrValues cfg.hooks); + config.assertions = mapAgents (name: cfg: [ + { assertion = cfg.hooksPath == (hooksDir cfg) || all (v: v == null) (attrValues cfg.hooks); message = '' - Options `services.buildkite-agent.hooksPath' and - `services.buildkite-agent.hooks.<name>' are mutually exclusive. + Options `services.buildkite-agents.${name}.hooksPath' and + `services.buildkite-agents.${name}.hooks.<name>' are mutually exclusive. ''; } - ]; - }; + ]); + imports = [ - (mkRenamedOptionModule [ "services" "buildkite-agent" "token" ] [ "services" "buildkite-agent" "tokenPath" ]) - (mkRenamedOptionModule [ "services" "buildkite-agent" "openssh" "privateKey" ] [ "services" "buildkite-agent" "openssh" "privateKeyPath" ]) - (mkRenamedOptionModule [ "services" "buildkite-agent" "openssh" "publicKey" ] [ "services" "buildkite-agent" "openssh" "publicKeyPath" ]) + (mkRemovedOptionModule [ "services" "buildkite-agent"] "services.buildkite-agent has been upgraded from version 2 to version 3 and moved to an attribute set at services.buildkite-agents. Please consult the 20.03 release notes for more information.") ]; } diff --git a/nixos/modules/services/continuous-integration/gitlab-runner.nix b/nixos/modules/services/continuous-integration/gitlab-runner.nix index 3d307b1abcf83..bd4cf6a37bad8 100644 --- a/nixos/modules/services/continuous-integration/gitlab-runner.nix +++ b/nixos/modules/services/continuous-integration/gitlab-runner.nix @@ -120,10 +120,16 @@ in ++ optional hasDocker "docker.service"; requires = optional hasDocker "docker.service"; wantedBy = [ "multi-user.target" ]; + reloadIfChanged = true; + restartTriggers = [ + config.environment.etc."gitlab-runner/config.toml".source + ]; serviceConfig = { + StateDirectory = "gitlab-runner"; + ExecReload= "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; ExecStart = ''${cfg.package.bin}/bin/gitlab-runner run \ --working-directory ${cfg.workDir} \ - --config ${configFile} \ + --config /etc/gitlab-runner/config.toml \ --service gitlab-runner \ --user gitlab-runner \ ''; @@ -138,6 +144,9 @@ in # 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"; diff --git a/nixos/modules/services/continuous-integration/gocd-agent/default.nix b/nixos/modules/services/continuous-integration/gocd-agent/default.nix index 8126f27c2b0c5..2e9e1c94857a0 100644 --- a/nixos/modules/services/continuous-integration/gocd-agent/default.nix +++ b/nixos/modules/services/continuous-integration/gocd-agent/default.nix @@ -135,20 +135,20 @@ in { }; config = mkIf cfg.enable { - users.groups = optional (cfg.group == "gocd-agent") { - name = "gocd-agent"; - gid = config.ids.gids.gocd-agent; + users.groups = optionalAttrs (cfg.group == "gocd-agent") { + gocd-agent.gid = config.ids.gids.gocd-agent; }; - users.users = optional (cfg.user == "gocd-agent") { - name = "gocd-agent"; - description = "gocd-agent user"; - createHome = true; - home = cfg.workDir; - group = cfg.group; - extraGroups = cfg.extraGroups; - useDefaultShell = true; - uid = config.ids.uids.gocd-agent; + users.users = optionalAttrs (cfg.user == "gocd-agent") { + gocd-agent = { + description = "gocd-agent user"; + createHome = true; + home = cfg.workDir; + group = cfg.group; + extraGroups = cfg.extraGroups; + useDefaultShell = true; + uid = config.ids.uids.gocd-agent; + }; }; systemd.services.gocd-agent = { diff --git a/nixos/modules/services/continuous-integration/gocd-server/default.nix b/nixos/modules/services/continuous-integration/gocd-server/default.nix index 8f177da129e50..4fa41ac49edfc 100644 --- a/nixos/modules/services/continuous-integration/gocd-server/default.nix +++ b/nixos/modules/services/continuous-integration/gocd-server/default.nix @@ -143,20 +143,20 @@ in { }; config = mkIf cfg.enable { - users.groups = optional (cfg.group == "gocd-server") { - name = "gocd-server"; - gid = config.ids.gids.gocd-server; + users.groups = optionalAttrs (cfg.group == "gocd-server") { + gocd-server.gid = config.ids.gids.gocd-server; }; - users.users = optional (cfg.user == "gocd-server") { - name = "gocd-server"; - description = "gocd-server user"; - createHome = true; - home = cfg.workDir; - group = cfg.group; - extraGroups = cfg.extraGroups; - useDefaultShell = true; - uid = config.ids.uids.gocd-server; + users.users = optionalAttrs (cfg.user == "gocd-server") { + gocd-server = { + description = "gocd-server user"; + createHome = true; + home = cfg.workDir; + group = cfg.group; + extraGroups = cfg.extraGroups; + useDefaultShell = true; + uid = config.ids.uids.gocd-server; + }; }; systemd.services.gocd-server = { diff --git a/nixos/modules/services/continuous-integration/hydra/default.nix b/nixos/modules/services/continuous-integration/hydra/default.nix index 30c5550f71c5a..502a5898a5de5 100644 --- a/nixos/modules/services/continuous-integration/hydra/default.nix +++ b/nixos/modules/services/continuous-integration/hydra/default.nix @@ -37,6 +37,38 @@ let haveLocalDB = cfg.dbi == localDB; + inherit (config.system) stateVersion; + + hydra-package = + let + makeWrapperArgs = concatStringsSep " " (mapAttrsToList (key: value: "--set \"${key}\" \"${value}\"") hydraEnv); + in pkgs.buildEnv rec { + name = "hydra-env"; + buildInputs = [ pkgs.makeWrapper ]; + paths = [ cfg.package ]; + + postBuild = '' + if [ -L "$out/bin" ]; then + unlink "$out/bin" + fi + mkdir -p "$out/bin" + + for path in ${concatStringsSep " " paths}; do + if [ -d "$path/bin" ]; then + cd "$path/bin" + for prg in *; do + if [ -f "$prg" ]; then + rm -f "$out/bin/$prg" + if [ -x "$prg" ]; then + makeWrapper "$path/bin/$prg" "$out/bin/$prg" ${makeWrapperArgs} + fi + fi + done + fi + done + ''; + }; + in { @@ -63,8 +95,7 @@ in }; package = mkOption { - type = types.path; - default = pkgs.hydra; + type = types.package; defaultText = "pkgs.hydra"; description = "The Hydra package."; }; @@ -167,7 +198,7 @@ in buildMachinesFiles = mkOption { type = types.listOf types.path; - default = [ "/etc/nix/machines" ]; + default = optional (config.nix.buildMachines != []) "/etc/nix/machines"; example = [ "/etc/nix/machines" "/var/lib/hydra/provisioner/machines" ]; description = "List of files containing build machines."; }; @@ -194,6 +225,34 @@ in config = mkIf cfg.enable { + warnings = optional (cfg.package.migration or false) '' + You're currently deploying an older version of Hydra which is needed to + make some required database changes[1]. As soon as this is done, it's recommended + to run `hydra-backfill-ids` and set `services.hydra.package` to `pkgs.hydra-unstable` + after that. + + [1] https://github.com/NixOS/hydra/pull/711 + ''; + + services.hydra.package = with pkgs; + mkDefault ( + if pkgs ? hydra + then throw '' + The Hydra package doesn't exist anymore in `nixpkgs`! It probably exists + due to an overlay. To upgrade Hydra, you need to take two steps as some + bigger changes in the database schema were implemented recently[1]. You first + need to deploy `pkgs.hydra-migration`, run `hydra-backfill-ids` on the server + and then deploy `pkgs.hydra-unstable`. + + If you want to use `pkgs.hydra` from your overlay, please set `services.hydra.package` + explicitly to `pkgs.hydra` and make sure you know what you're doing. + + [1] https://github.com/NixOS/hydra/pull/711 + '' + else if versionOlder stateVersion "20.03" then hydra-migration + else hydra-unstable + ); + users.groups.hydra = { gid = config.ids.gids.hydra; }; @@ -237,7 +296,7 @@ in use-substitutes = ${if cfg.useSubstitutes then "1" else "0"} ''; - environment.systemPackages = [ cfg.package ]; + environment.systemPackages = [ hydra-package ]; environment.variables = hydraEnv; @@ -298,7 +357,7 @@ in chown hydra.hydra ${cfg.gcRootsDir} chmod 2775 ${cfg.gcRootsDir} ''; - serviceConfig.ExecStart = "${cfg.package}/bin/hydra-init"; + serviceConfig.ExecStart = "${hydra-package}/bin/hydra-init"; serviceConfig.PermissionsStartOnly = true; serviceConfig.User = "hydra"; serviceConfig.Type = "oneshot"; @@ -313,7 +372,7 @@ in restartTriggers = [ hydraConf ]; serviceConfig = { ExecStart = - "@${cfg.package}/bin/hydra-server hydra-server -f -h '${cfg.listenHost}' " + "@${hydra-package}/bin/hydra-server hydra-server -f -h '${cfg.listenHost}' " + "-p ${toString cfg.port} --max_spare_servers 5 --max_servers 25 " + "--max_requests 100 ${optionalString cfg.debugServer "-d"}"; User = "hydra-www"; @@ -326,15 +385,15 @@ in { wantedBy = [ "multi-user.target" ]; requires = [ "hydra-init.service" ]; after = [ "hydra-init.service" "network.target" ]; - path = [ cfg.package pkgs.nettools pkgs.openssh pkgs.bzip2 config.nix.package ]; + path = [ hydra-package pkgs.nettools pkgs.openssh pkgs.bzip2 config.nix.package ]; restartTriggers = [ hydraConf ]; environment = env // { PGPASSFILE = "${baseDir}/pgpass-queue-runner"; # grrr IN_SYSTEMD = "1"; # to get log severity levels }; serviceConfig = - { ExecStart = "@${cfg.package}/bin/hydra-queue-runner hydra-queue-runner -v --option build-use-substitutes ${boolToString cfg.useSubstitutes}"; - ExecStopPost = "${cfg.package}/bin/hydra-queue-runner --unlock"; + { ExecStart = "@${hydra-package}/bin/hydra-queue-runner hydra-queue-runner -v"; + ExecStopPost = "${hydra-package}/bin/hydra-queue-runner --unlock"; User = "hydra-queue-runner"; Restart = "always"; @@ -348,11 +407,11 @@ in { wantedBy = [ "multi-user.target" ]; requires = [ "hydra-init.service" ]; after = [ "hydra-init.service" "network.target" ]; - path = with pkgs; [ cfg.package nettools jq ]; + path = with pkgs; [ hydra-package nettools jq ]; restartTriggers = [ hydraConf ]; environment = env; serviceConfig = - { ExecStart = "@${cfg.package}/bin/hydra-evaluator hydra-evaluator"; + { ExecStart = "@${hydra-package}/bin/hydra-evaluator hydra-evaluator"; User = "hydra"; Restart = "always"; WorkingDirectory = baseDir; @@ -364,7 +423,7 @@ in after = [ "hydra-init.service" ]; environment = env; serviceConfig = - { ExecStart = "@${cfg.package}/bin/hydra-update-gc-roots hydra-update-gc-roots"; + { ExecStart = "@${hydra-package}/bin/hydra-update-gc-roots hydra-update-gc-roots"; User = "hydra"; }; startAt = "2,14:15"; @@ -375,7 +434,7 @@ in after = [ "hydra-init.service" ]; environment = env; serviceConfig = - { ExecStart = "@${cfg.package}/bin/hydra-send-stats hydra-send-stats"; + { ExecStart = "@${hydra-package}/bin/hydra-send-stats hydra-send-stats"; User = "hydra"; }; }; @@ -389,7 +448,7 @@ in PGPASSFILE = "${baseDir}/pgpass-queue-runner"; }; serviceConfig = - { ExecStart = "@${cfg.package}/bin/hydra-notify hydra-notify"; + { ExecStart = "@${hydra-package}/bin/hydra-notify hydra-notify"; # FIXME: run this under a less privileged user? User = "hydra-queue-runner"; Restart = "always"; diff --git a/nixos/modules/services/continuous-integration/jenkins/default.nix b/nixos/modules/services/continuous-integration/jenkins/default.nix index 0ec906713885d..1477c471f8ab1 100644 --- a/nixos/modules/services/continuous-integration/jenkins/default.nix +++ b/nixos/modules/services/continuous-integration/jenkins/default.nix @@ -150,20 +150,20 @@ in { pkgs.dejavu_fonts ]; - users.groups = optional (cfg.group == "jenkins") { - name = "jenkins"; - gid = config.ids.gids.jenkins; + users.groups = optionalAttrs (cfg.group == "jenkins") { + jenkins.gid = config.ids.gids.jenkins; }; - users.users = optional (cfg.user == "jenkins") { - name = "jenkins"; - description = "jenkins user"; - createHome = true; - home = cfg.home; - group = cfg.group; - extraGroups = cfg.extraGroups; - useDefaultShell = true; - uid = config.ids.uids.jenkins; + users.users = optionalAttrs (cfg.user == "jenkins") { + jenkins = { + description = "jenkins user"; + createHome = true; + home = cfg.home; + group = cfg.group; + extraGroups = cfg.extraGroups; + useDefaultShell = true; + uid = config.ids.uids.jenkins; + }; }; systemd.services.jenkins = { diff --git a/nixos/modules/services/continuous-integration/jenkins/slave.nix b/nixos/modules/services/continuous-integration/jenkins/slave.nix index 92deabc3dd3b0..3c0e6f78e74ca 100644 --- a/nixos/modules/services/continuous-integration/jenkins/slave.nix +++ b/nixos/modules/services/continuous-integration/jenkins/slave.nix @@ -50,19 +50,19 @@ in { }; config = mkIf (cfg.enable && !masterCfg.enable) { - users.groups = optional (cfg.group == "jenkins") { - name = "jenkins"; - gid = config.ids.gids.jenkins; + users.groups = optionalAttrs (cfg.group == "jenkins") { + jenkins.gid = config.ids.gids.jenkins; }; - users.users = optional (cfg.user == "jenkins") { - name = "jenkins"; - description = "jenkins user"; - createHome = true; - home = cfg.home; - group = cfg.group; - useDefaultShell = true; - uid = config.ids.uids.jenkins; + users.users = optionalAttrs (cfg.user == "jenkins") { + jenkins = { + description = "jenkins user"; + createHome = true; + home = cfg.home; + group = cfg.group; + useDefaultShell = true; + uid = config.ids.uids.jenkins; + }; }; }; } diff --git a/nixos/modules/services/databases/4store-endpoint.nix b/nixos/modules/services/databases/4store-endpoint.nix deleted file mode 100644 index 59ed0e5f0afdf..0000000000000 --- a/nixos/modules/services/databases/4store-endpoint.nix +++ /dev/null @@ -1,74 +0,0 @@ -{ config, lib, pkgs, ... }: -let - cfg = config.services.fourStoreEndpoint; - endpointUser = "fourstorehttp"; - run = "${pkgs.su}/bin/su -s ${pkgs.runtimeShell} ${endpointUser} -c"; -in -with lib; -{ - - ###### interface - - options = { - - services.fourStoreEndpoint = { - - enable = mkOption { - default = false; - description = "Whether to enable 4Store SPARQL endpoint."; - }; - - database = mkOption { - default = config.services.fourStore.database; - description = "RDF database name to expose via the endpoint. Defaults to local 4Store database name."; - }; - - listenAddress = mkOption { - default = null; - description = "IP address to listen on."; - }; - - port = mkOption { - default = 8080; - description = "port to listen on."; - }; - - options = mkOption { - default = ""; - description = "Extra CLI options to pass to 4Store's 4s-httpd process."; - }; - - }; - - }; - - - ###### implementation - - config = mkIf cfg.enable { - - assertions = singleton - { assertion = cfg.enable -> cfg.database != ""; - message = "Must specify 4Store database name"; - }; - - users.users = singleton - { name = endpointUser; - uid = config.ids.uids.fourstorehttp; - description = "4Store SPARQL endpoint user"; - }; - - services.avahi.enable = true; - - systemd.services."4store-endpoint" = { - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - - script = '' - ${run} '${pkgs.rdf4store}/bin/4s-httpd -D ${cfg.options} ${if cfg.listenAddress!=null then "-H ${cfg.listenAddress}" else "" } -p ${toString cfg.port} ${cfg.database}' - ''; - }; - - }; - -} diff --git a/nixos/modules/services/databases/4store.nix b/nixos/modules/services/databases/4store.nix deleted file mode 100644 index be4351c1c38f4..0000000000000 --- a/nixos/modules/services/databases/4store.nix +++ /dev/null @@ -1,72 +0,0 @@ -{ config, lib, pkgs, ... }: -let - cfg = config.services.fourStore; - stateDir = "/var/lib/4store"; - fourStoreUser = "fourstore"; - run = "${pkgs.su}/bin/su -s ${pkgs.runtimeShell} ${fourStoreUser}"; -in -with lib; -{ - - ###### interface - - options = { - - services.fourStore = { - - enable = mkOption { - default = false; - description = "Whether to enable 4Store RDF database server."; - }; - - database = mkOption { - default = ""; - description = "RDF database name. If it doesn't exist, it will be created. Databases are stored in ${stateDir}."; - }; - - options = mkOption { - default = ""; - description = "Extra CLI options to pass to 4Store."; - }; - - }; - - }; - - - ###### implementation - - config = mkIf cfg.enable { - - assertions = singleton - { assertion = cfg.enable -> cfg.database != ""; - message = "Must specify 4Store database name."; - }; - - users.users = singleton - { name = fourStoreUser; - uid = config.ids.uids.fourstore; - description = "4Store database user"; - home = stateDir; - }; - - services.avahi.enable = true; - - systemd.services."4store" = { - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - - preStart = '' - mkdir -p ${stateDir}/ - chown ${fourStoreUser} ${stateDir} - if ! test -e "${stateDir}/${cfg.database}"; then - ${run} -c '${pkgs.rdf4store}/bin/4s-backend-setup ${cfg.database}' - fi - ''; - - script = '' - ${run} -c '${pkgs.rdf4store}/bin/4s-backend -D ${cfg.options} ${cfg.database}' - ''; - }; - }; -} diff --git a/nixos/modules/services/databases/clickhouse.nix b/nixos/modules/services/databases/clickhouse.nix index dbabcae43ee5e..27440fec4e10b 100644 --- a/nixos/modules/services/databases/clickhouse.nix +++ b/nixos/modules/services/databases/clickhouse.nix @@ -11,10 +11,7 @@ with lib; services.clickhouse = { - enable = mkOption { - default = false; - description = "Whether to enable ClickHouse database server."; - }; + enable = mkEnableOption "ClickHouse database server"; }; diff --git a/nixos/modules/services/databases/cockroachdb.nix b/nixos/modules/services/databases/cockroachdb.nix index 268fdcc819fd0..35fb46d69d8e5 100644 --- a/nixos/modules/services/databases/cockroachdb.nix +++ b/nixos/modules/services/databases/cockroachdb.nix @@ -153,7 +153,7 @@ in defaultText = "pkgs.cockroachdb"; description = '' The CockroachDB derivation to use for running the service. - + This would primarily be useful to enable Enterprise Edition features in your own custom CockroachDB build (Nixpkgs CockroachDB binaries only contain open source features and open source code). @@ -171,17 +171,17 @@ in environment.systemPackages = [ crdb ]; - users.users = optionalAttrs (cfg.user == "cockroachdb") (singleton - { name = "cockroachdb"; + users.users = optionalAttrs (cfg.user == "cockroachdb") { + cockroachdb = { description = "CockroachDB Server User"; uid = config.ids.uids.cockroachdb; group = cfg.group; - }); + }; + }; - users.groups = optionalAttrs (cfg.group == "cockroachdb") (singleton - { name = "cockroachdb"; - gid = config.ids.gids.cockroachdb; - }); + users.groups = optionalAttrs (cfg.group == "cockroachdb") { + cockroachdb.gid = config.ids.gids.cockroachdb; + }; networking.firewall.allowedTCPPorts = lib.optionals cfg.openPorts [ cfg.http.port cfg.listen.port ]; diff --git a/nixos/modules/services/databases/firebird.nix b/nixos/modules/services/databases/firebird.nix index 042c9841df548..95837aa1cea66 100644 --- a/nixos/modules/services/databases/firebird.nix +++ b/nixos/modules/services/databases/firebird.nix @@ -40,12 +40,7 @@ in services.firebird = { - enable = mkOption { - default = false; - description = '' - Whether to enable the Firebird super server. - ''; - }; + enable = mkEnableOption "the Firebird super server"; package = mkOption { default = pkgs.firebirdSuper; diff --git a/nixos/modules/services/databases/foundationdb.nix b/nixos/modules/services/databases/foundationdb.nix index 8f8d0da7c8d3d..18727acc7c756 100644 --- a/nixos/modules/services/databases/foundationdb.nix +++ b/nixos/modules/services/databases/foundationdb.nix @@ -341,17 +341,17 @@ in environment.systemPackages = [ pkg ]; - users.users = optionalAttrs (cfg.user == "foundationdb") (singleton - { name = "foundationdb"; + users.users = optionalAttrs (cfg.user == "foundationdb") { + foundationdb = { description = "FoundationDB User"; uid = config.ids.uids.foundationdb; group = cfg.group; - }); + }; + }; - users.groups = optionalAttrs (cfg.group == "foundationdb") (singleton - { name = "foundationdb"; - gid = config.ids.gids.foundationdb; - }); + users.groups = optionalAttrs (cfg.group == "foundationdb") { + foundationdb.gid = config.ids.gids.foundationdb; + }; networking.firewall.allowedTCPPortRanges = mkIf cfg.openFirewall [ { from = cfg.listenPortStart; diff --git a/nixos/modules/services/databases/influxdb.nix b/nixos/modules/services/databases/influxdb.nix index 2f176a0387299..dd5d69b1147a0 100644 --- a/nixos/modules/services/databases/influxdb.nix +++ b/nixos/modules/services/databases/influxdb.nix @@ -182,15 +182,15 @@ in ''; }; - users.users = optional (cfg.user == "influxdb") { - name = "influxdb"; - uid = config.ids.uids.influxdb; - description = "Influxdb daemon user"; + users.users = optionalAttrs (cfg.user == "influxdb") { + influxdb = { + uid = config.ids.uids.influxdb; + description = "Influxdb daemon user"; + }; }; - users.groups = optional (cfg.group == "influxdb") { - name = "influxdb"; - gid = config.ids.gids.influxdb; + users.groups = optionalAttrs (cfg.group == "influxdb") { + influxdb.gid = config.ids.gids.influxdb; }; }; diff --git a/nixos/modules/services/databases/memcached.nix b/nixos/modules/services/databases/memcached.nix index d1dfdb41bf402..f54bb6cc9b180 100644 --- a/nixos/modules/services/databases/memcached.nix +++ b/nixos/modules/services/databases/memcached.nix @@ -18,12 +18,7 @@ in services.memcached = { - enable = mkOption { - default = false; - description = " - Whether to enable Memcached. - "; - }; + enable = mkEnableOption "Memcached"; user = mkOption { default = "memcached"; @@ -64,10 +59,9 @@ in config = mkIf config.services.memcached.enable { - users.users = optional (cfg.user == "memcached") { - name = "memcached"; - description = "Memcached server user"; - isSystemUser = true; + users.users = optionalAttrs (cfg.user == "memcached") { + memcached.description = "Memcached server user"; + memcached.isSystemUser = true; }; environment.systemPackages = [ memcached ]; diff --git a/nixos/modules/services/databases/mongodb.nix b/nixos/modules/services/databases/mongodb.nix index 12879afed477b..4453a182990d1 100644 --- a/nixos/modules/services/databases/mongodb.nix +++ b/nixos/modules/services/databases/mongodb.nix @@ -29,12 +29,7 @@ in services.mongodb = { - enable = mkOption { - default = false; - description = " - Whether to enable the MongoDB server. - "; - }; + enable = mkEnableOption "the MongoDB server"; package = mkOption { default = pkgs.mongodb; diff --git a/nixos/modules/services/databases/mysql.nix b/nixos/modules/services/databases/mysql.nix index 5549cfa5cf4d5..f9e657f577425 100644 --- a/nixos/modules/services/databases/mysql.nix +++ b/nixos/modules/services/databases/mysql.nix @@ -10,20 +10,21 @@ let isMariaDB = lib.getName mysql == lib.getName pkgs.mariadb; - isMysqlAtLeast57 = - (lib.getName mysql == lib.getName pkgs.mysql57) - && (builtins.compareVersions mysql.version "5.7" >= 0); - mysqldOptions = "--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${mysql}"; - # For MySQL 5.7+, --insecure creates the root user without password - # (earlier versions and MariaDB do this by default). - installOptions = - "${mysqldOptions} ${lib.optionalString isMysqlAtLeast57 "--insecure"}"; + + settingsFile = pkgs.writeText "my.cnf" ( + generators.toINI { listsAsDuplicateKeys = true; } cfg.settings + + optionalString (cfg.extraOptions != null) "[mysqld]\n${cfg.extraOptions}" + ); in { + imports = [ + (mkRemovedOptionModule [ "services" "mysql" "pidDir" ] "Don't wait for pidfiles, describe dependencies through systemd") + (mkRemovedOptionModule [ "services" "mysql" "rootPassword" ] "Use socket authentication or set the password outside of the nix store.") + ]; ###### interface @@ -72,9 +73,64 @@ in description = "Location where MySQL stores its table files"; }; + configFile = mkOption { + type = types.path; + default = settingsFile; + defaultText = "settingsFile"; + description = '' + Override the configuration file used by MySQL. By default, + NixOS generates one automatically from <option>services.mysql.settings</option>. + ''; + example = literalExample '' + pkgs.writeText "my.cnf" ''' + [mysqld] + datadir = /var/lib/mysql + bind-address = 127.0.0.1 + port = 3336 + plugin-load-add = auth_socket.so + + !includedir /etc/mysql/conf.d/ + '''; + ''; + }; + + settings = mkOption { + type = with types; attrsOf (attrsOf (oneOf [ bool int str (listOf str) ])); + default = {}; + description = '' + MySQL configuration. Refer to + <link xlink:href="https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html"/>, + <link xlink:href="https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html"/>, + and <link xlink:href="https://mariadb.com/kb/en/server-system-variables/"/> + for details on supported values. + + <note> + <para> + MySQL configuration options such as <literal>--quick</literal> should be treated as + boolean options and provided values such as <literal>true</literal>, <literal>false</literal>, + <literal>1</literal>, or <literal>0</literal>. See the provided example below. + </para> + </note> + ''; + example = literalExample '' + { + mysqld = { + key_buffer_size = "6G"; + table_cache = 1600; + log-error = "/var/log/mysql_err.log"; + plugin-load-add = [ "server_audit" "ed25519=auth_ed25519" ]; + }; + mysqldump = { + quick = true; + max_allowed_packet = "16M"; + }; + } + ''; + }; + extraOptions = mkOption { - type = types.lines; - default = ""; + type = with types; nullOr lines; + default = null; example = '' key_buffer_size = 6G table_cache = 1600 @@ -248,10 +304,27 @@ in config = mkIf config.services.mysql.enable { + warnings = optional (cfg.extraOptions != null) "services.mysql.`extraOptions` is deprecated, please use services.mysql.`settings`."; + services.mysql.dataDir = mkDefault (if versionAtLeast config.system.stateVersion "17.09" then "/var/lib/mysql" else "/var/mysql"); + services.mysql.settings.mysqld = mkMerge [ + { + datadir = cfg.dataDir; + bind-address = mkIf (cfg.bind != null) cfg.bind; + port = cfg.port; + plugin-load-add = optional (cfg.ensureUsers != []) "auth_socket.so"; + } + (mkIf (cfg.replication.role == "master" || cfg.replication.role == "slave") { + log-bin = "mysql-bin-${toString cfg.replication.serverId}"; + log-bin-index = "mysql-bin-${toString cfg.replication.serverId}.index"; + relay-log = "mysql-relay-bin"; + server-id = cfg.replication.serverId; + }) + ]; + users.users.mysql = { description = "MySQL server user"; group = "mysql"; @@ -262,25 +335,7 @@ in environment.systemPackages = [mysql]; - environment.etc."my.cnf".text = - '' - [mysqld] - port = ${toString cfg.port} - datadir = ${cfg.dataDir} - ${optionalString (cfg.bind != null) "bind-address = ${cfg.bind}" } - ${optionalString (cfg.replication.role == "master" || cfg.replication.role == "slave") - '' - log-bin=mysql-bin-${toString cfg.replication.serverId} - log-bin-index=mysql-bin-${toString cfg.replication.serverId}.index - relay-log=mysql-relay-bin - server-id = ${toString cfg.replication.serverId} - ''} - ${optionalString (cfg.ensureUsers != []) - '' - plugin-load-add = auth_socket.so - ''} - ${cfg.extraOptions} - ''; + environment.etc."my.cnf".source = cfg.configFile; systemd.tmpfiles.rules = [ "d '${cfg.dataDir}' 0700 ${cfg.user} mysql -" @@ -293,7 +348,7 @@ in after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; - restartTriggers = [ config.environment.etc."my.cnf".source ]; + restartTriggers = [ cfg.configFile ]; unitConfig.RequiresMountsFor = "${cfg.dataDir}"; @@ -303,9 +358,14 @@ in pkgs.nettools ]; - preStart = '' + preStart = if isMariaDB then '' + if ! test -e ${cfg.dataDir}/mysql; then + ${mysql}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${mysqldOptions} + touch /tmp/mysql_init + fi + '' else '' if ! test -e ${cfg.dataDir}/mysql; then - ${mysql}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${installOptions} + ${mysql}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} --initialize-insecure touch /tmp/mysql_init fi ''; @@ -316,6 +376,8 @@ in Type = if hasNotify then "notify" else "simple"; RuntimeDirectory = "mysqld"; RuntimeDirectoryMode = "0755"; + Restart = "on-abort"; + RestartSec = "5s"; # The last two environment variables are used for starting Galera clusters ExecStart = "${mysql}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION"; ExecStartPost = diff --git a/nixos/modules/services/databases/neo4j.nix b/nixos/modules/services/databases/neo4j.nix index 5533182c31168..09b453e758451 100644 --- a/nixos/modules/services/databases/neo4j.nix +++ b/nixos/modules/services/databases/neo4j.nix @@ -103,6 +103,18 @@ let in { + imports = [ + (mkRenamedOptionModule [ "services" "neo4j" "host" ] [ "services" "neo4j" "defaultListenAddress" ]) + (mkRenamedOptionModule [ "services" "neo4j" "listenAddress" ] [ "services" "neo4j" "defaultListenAddress" ]) + (mkRenamedOptionModule [ "services" "neo4j" "enableBolt" ] [ "services" "neo4j" "bolt" "enable" ]) + (mkRenamedOptionModule [ "services" "neo4j" "enableHttps" ] [ "services" "neo4j" "https" "enable" ]) + (mkRenamedOptionModule [ "services" "neo4j" "certDir" ] [ "services" "neo4j" "directories" "certificates" ]) + (mkRenamedOptionModule [ "services" "neo4j" "dataDir" ] [ "services" "neo4j" "directories" "home" ]) + (mkRemovedOptionModule [ "services" "neo4j" "port" ] "Use services.neo4j.http.listenAddress instead.") + (mkRemovedOptionModule [ "services" "neo4j" "boltPort" ] "Use services.neo4j.bolt.listenAddress instead.") + (mkRemovedOptionModule [ "services" "neo4j" "httpsPort" ] "Use services.neo4j.https.listenAddress instead.") + ]; + ###### interface options.services.neo4j = { @@ -638,8 +650,7 @@ in { environment.systemPackages = [ cfg.package ]; - users.users = singleton { - name = "neo4j"; + users.users.neo4j = { uid = config.ids.uids.neo4j; description = "Neo4j daemon user"; home = cfg.directories.home; diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index 5bf57a1bf9cba..809f61cfa8186 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -259,6 +259,8 @@ in ${openldap.out}/bin/slapadd ${configOpts} -l ${dataFile} ''} chown -R "${cfg.user}:${cfg.group}" "${cfg.dataDir}" + + ${openldap}/bin/slaptest ${configOpts} ''; serviceConfig.ExecStart = "${openldap.out}/libexec/slapd -d '${cfg.logLevel}' " + diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix index 3bedfe96a1805..0b79a996dc788 100644 --- a/nixos/modules/services/databases/postgresql.nix +++ b/nixos/modules/services/databases/postgresql.nix @@ -20,7 +20,9 @@ let listen_addresses = '${if cfg.enableTCPIP then "*" else "localhost"}' port = ${toString cfg.port} ${cfg.extraConfig} - ''; + ''; + + groupAccessAvailable = versionAtLeast postgresql.version "11.0"; in @@ -88,6 +90,16 @@ in ''; }; + initdbArgs = mkOption { + type = with types; listOf str; + default = []; + example = [ "--data-checksums" "--allow-group-access" ]; + description = '' + Additional arguments passed to <literal>initdb</literal> during data dir + initialisation. + ''; + }; + initialScript = mkOption { type = types.nullOr types.path; default = null; @@ -220,7 +232,7 @@ in ###### implementation - config = mkIf config.services.postgresql.enable { + config = mkIf cfg.enable { services.postgresql.package = # Note: when changing the default, make it conditional on @@ -232,13 +244,14 @@ in else throw "postgresql_9_4 was removed, please upgrade your postgresql version."); services.postgresql.dataDir = - mkDefault (if versionAtLeast config.system.stateVersion "17.09" then "/var/lib/postgresql/${config.services.postgresql.package.psqlSchema}" - else "/var/db/postgresql"); + mkDefault (if versionAtLeast config.system.stateVersion "17.09" + then "/var/lib/postgresql/${cfg.package.psqlSchema}" + else "/var/db/postgresql"); services.postgresql.authentication = mkAfter '' # Generated file; do not edit! - local all all ident + local all all peer host all all 127.0.0.1/32 md5 host all all ::1/128 md5 ''; @@ -284,7 +297,7 @@ in '' # Initialise the database. if ! test -e ${cfg.dataDir}/PG_VERSION; then - initdb -U ${cfg.superUser} + initdb -U ${cfg.superUser} ${concatStringsSep " " cfg.initdbArgs} # See postStart! touch "${cfg.dataDir}/.first_startup" fi @@ -293,8 +306,12 @@ in ln -sfn "${pkgs.writeText "recovery.conf" cfg.recoveryConfig}" \ "${cfg.dataDir}/recovery.conf" ''} + ${optionalString (!groupAccessAvailable) '' + # postgresql pre 11.0 doesn't start if state directory mode is group accessible + chmod 0700 "${cfg.dataDir}" + ''} - exec postgres + exec postgres ''; serviceConfig = @@ -303,7 +320,7 @@ in Group = "postgres"; PermissionsStartOnly = true; RuntimeDirectory = "postgresql"; - Type = if lib.versionAtLeast cfg.package.version "9.6" + Type = if versionAtLeast cfg.package.version "9.6" then "notify" else "simple"; @@ -339,9 +356,9 @@ in '') cfg.ensureDatabases} '' + '' ${concatMapStrings (user: '' - $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user.name}'" | grep -q 1 || $PSQL -tAc "CREATE USER ${user.name}" + $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user.name}'" | grep -q 1 || $PSQL -tAc 'CREATE USER "${user.name}"' ${concatStringsSep "\n" (mapAttrsToList (database: permission: '' - $PSQL -tAc 'GRANT ${permission} ON ${database} TO ${user.name}' + $PSQL -tAc 'GRANT ${permission} ON ${database} TO "${user.name}"' '') user.ensurePermissions)} '') cfg.ensureUsers} ''; @@ -352,5 +369,5 @@ in }; meta.doc = ./postgresql.xml; - meta.maintainers = with lib.maintainers; [ thoughtpolice ]; + meta.maintainers = with lib.maintainers; [ thoughtpolice danbst ]; } diff --git a/nixos/modules/services/databases/postgresql.xml b/nixos/modules/services/databases/postgresql.xml index 72d4a8249a321..07af4c937f037 100644 --- a/nixos/modules/services/databases/postgresql.xml +++ b/nixos/modules/services/databases/postgresql.xml @@ -7,12 +7,10 @@ <!-- FIXME: render nicely --> <!-- FIXME: source can be added automatically --> <para> - <emphasis>Source:</emphasis> - <filename>modules/services/databases/postgresql.nix</filename> + <emphasis>Source:</emphasis> <filename>modules/services/databases/postgresql.nix</filename> </para> <para> - <emphasis>Upstream documentation:</emphasis> - <link xlink:href="http://www.postgresql.org/docs/"/> + <emphasis>Upstream documentation:</emphasis> <link xlink:href="http://www.postgresql.org/docs/"/> </para> <!-- FIXME: more stuff, like maintainer? --> <para> @@ -23,18 +21,12 @@ <title>Configuring</title> <para> - To enable PostgreSQL, add the following to your - <filename>configuration.nix</filename>: + To enable PostgreSQL, add the following to your <filename>configuration.nix</filename>: <programlisting> <xref linkend="opt-services.postgresql.enable"/> = true; <xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_11; </programlisting> - Note that you are required to specify the desired version of PostgreSQL - (e.g. <literal>pkgs.postgresql_11</literal>). Since upgrading your - PostgreSQL version requires a database dump and reload (see below), NixOS - cannot provide a default value for - <xref linkend="opt-services.postgresql.package"/> such as the most recent - release of PostgreSQL. + Note that you are required to specify the desired version of PostgreSQL (e.g. <literal>pkgs.postgresql_11</literal>). Since upgrading your PostgreSQL version requires a database dump and reload (see below), NixOS cannot provide a default value for <xref linkend="opt-services.postgresql.package"/> such as the most recent release of PostgreSQL. </para> <!-- @@ -51,9 +43,7 @@ Type "help" for help. --> <para> - By default, PostgreSQL stores its databases in - <filename>/var/lib/postgresql/$psqlSchema</filename>. You can override this using - <xref linkend="opt-services.postgresql.dataDir"/>, e.g. + By default, PostgreSQL stores its databases in <filename>/var/lib/postgresql/$psqlSchema</filename>. You can override this using <xref linkend="opt-services.postgresql.dataDir"/>, e.g. <programlisting> <xref linkend="opt-services.postgresql.dataDir"/> = "/data/postgresql"; </programlisting> @@ -63,25 +53,83 @@ Type "help" for help. <title>Upgrading</title> <para> - FIXME: document dump/upgrade/load cycle. + Major PostgreSQL upgrade requires PostgreSQL downtime and a few imperative steps to be called. To simplify this process, use the following NixOS module: +<programlisting> + containers.temp-pg.config.services.postgresql = { + enable = true; + package = pkgs.postgresql_12; + ## set a custom new dataDir + # dataDir = "/some/data/dir"; + }; + environment.systemPackages = + let newpg = config.containers.temp-pg.config.services.postgresql; + in [ + (pkgs.writeScriptBin "upgrade-pg-cluster" '' + set -x + export OLDDATA="${config.services.postgresql.dataDir}" + export NEWDATA="${newpg.dataDir}" + export OLDBIN="${config.services.postgresql.package}/bin" + export NEWBIN="${newpg.package}/bin" + + install -d -m 0700 -o postgres -g postgres "$NEWDATA" + cd "$NEWDATA" + sudo -u postgres $NEWBIN/initdb -D "$NEWDATA" + + systemctl stop postgresql # old one + + sudo -u postgres $NEWBIN/pg_upgrade \ + --old-datadir "$OLDDATA" --new-datadir "$NEWDATA" \ + --old-bindir $OLDBIN --new-bindir $NEWBIN \ + "$@" + '') + ]; +</programlisting> </para> + + <para> + The upgrade process is: + </para> + + <orderedlist> + <listitem> + <para> + Rebuild nixos configuration with the configuration above added to your <filename>configuration.nix</filename>. Alternatively, add that into separate file and reference it in <literal>imports</literal> list. + </para> + </listitem> + <listitem> + <para> + Login as root (<literal>sudo su -</literal>) + </para> + </listitem> + <listitem> + <para> + Run <literal>upgrade-pg-cluster</literal>. It will stop old postgresql, initialize new one and migrate old one to new one. You may supply arguments like <literal>--jobs 4</literal> and <literal>--link</literal> to speedup migration process. See <link xlink:href="https://www.postgresql.org/docs/current/pgupgrade.html" /> for details. + </para> + </listitem> + <listitem> + <para> + Change postgresql package in NixOS configuration to the one you were upgrading to, and change <literal>dataDir</literal> to the one you have migrated to. Rebuild NixOS. This should start new postgres using upgraded data directory. + </para> + </listitem> + <listitem> + <para> + After upgrade you may want to <literal>ANALYZE</literal> new db. + </para> + </listitem> + </orderedlist> </section> <section xml:id="module-services-postgres-options"> <title>Options</title> <para> - A complete list of options for the PostgreSQL module may be found - <link linkend="opt-services.postgresql.enable">here</link>. + A complete list of options for the PostgreSQL module may be found <link linkend="opt-services.postgresql.enable">here</link>. </para> </section> <section xml:id="module-services-postgres-plugins"> <title>Plugins</title> <para> - Plugins collection for each PostgreSQL version can be accessed with - <literal>.pkgs</literal>. For example, for - <literal>pkgs.postgresql_11</literal> package, its plugin collection is - accessed by <literal>pkgs.postgresql_11.pkgs</literal>: + Plugins collection for each PostgreSQL version can be accessed with <literal>.pkgs</literal>. For example, for <literal>pkgs.postgresql_11</literal> package, its plugin collection is accessed by <literal>pkgs.postgresql_11.pkgs</literal>: <screen> <prompt>$ </prompt>nix repl '<nixpkgs>' @@ -98,8 +146,9 @@ postgresql_11.pkgs.pg_partman postgresql_11.pkgs.pgroonga ... </screen> </para> + <para> - To add plugins via NixOS configuration, set <literal>services.postgresql.extraPlugins</literal>: + To add plugins via NixOS configuration, set <literal>services.postgresql.extraPlugins</literal>: <programlisting> <xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_11; <xref linkend="opt-services.postgresql.extraPlugins"/> = with pkgs.postgresql_11.pkgs; [ @@ -108,10 +157,9 @@ postgresql_11.pkgs.pg_partman postgresql_11.pkgs.pgroonga ]; </programlisting> </para> + <para> - You can build custom PostgreSQL-with-plugins (to be used outside of NixOS) using - function <literal>.withPackages</literal>. For example, creating a custom - PostgreSQL package in an overlay can look like: + You can build custom PostgreSQL-with-plugins (to be used outside of NixOS) using function <literal>.withPackages</literal>. For example, creating a custom PostgreSQL package in an overlay can look like: <programlisting> self: super: { postgresql_custom = self.postgresql_11.withPackages (ps: [ @@ -121,8 +169,9 @@ self: super: { } </programlisting> </para> + <para> - Here's a recipe on how to override a particular plugin through an overlay: + Here's a recipe on how to override a particular plugin through an overlay: <programlisting> self: super: { postgresql_11 = super.postgresql_11.override { this = self.postgresql_11; } // { diff --git a/nixos/modules/services/databases/redis.nix b/nixos/modules/services/databases/redis.nix index 95128a641d945..5c817422aae5a 100644 --- a/nixos/modules/services/databases/redis.nix +++ b/nixos/modules/services/databases/redis.nix @@ -32,6 +32,13 @@ let ''; in { + imports = [ + (mkRemovedOptionModule [ "services" "redis" "user" ] "The redis module now is hardcoded to the redis user.") + (mkRemovedOptionModule [ "services" "redis" "dbpath" ] "The redis module now uses /var/lib/redis as data directory.") + (mkRemovedOptionModule [ "services" "redis" "dbFilename" ] "The redis module now uses /var/lib/redis/dump.rdb as database dump location.") + (mkRemovedOptionModule [ "services" "redis" "appendOnlyFilename" ] "This option was never used.") + (mkRemovedOptionModule [ "services" "redis" "pidFile" ] "This option was removed.") + ]; ###### interface @@ -143,10 +150,20 @@ in requirePass = mkOption { type = with types; nullOr str; default = null; - description = "Password for database (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)"; + description = '' + Password for database (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE). + Use requirePassFile to store it outside of the nix store in a dedicated file. + ''; example = "letmein!"; }; + requirePassFile = mkOption { + type = with types; nullOr path; + default = null; + description = "File with password for the database."; + example = "/run/keys/redis-password"; + }; + appendOnly = mkOption { type = types.bool; default = false; @@ -185,6 +202,10 @@ in ###### implementation config = mkIf config.services.redis.enable { + assertions = [{ + assertion = cfg.requirePass != null -> cfg.requirePassFile == null; + message = "You can only set one services.redis.requirePass or services.redis.requirePassFile"; + }]; boot.kernel.sysctl = (mkMerge [ { "vm.nr_hugepages" = "0"; } ( mkIf cfg.vmOverCommit { "vm.overcommit_memory" = "1"; } ) @@ -201,21 +222,26 @@ in environment.systemPackages = [ cfg.package ]; - systemd.services.redis = - { description = "Redis Server"; + systemd.services.redis = { + description = "Redis Server"; - wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; - serviceConfig = { - ExecStart = "${cfg.package}/bin/redis-server ${redisConfig}"; - RuntimeDirectory = "redis"; - StateDirectory = "redis"; - Type = "notify"; - User = "redis"; - }; - }; + preStart = '' + install -m 600 ${redisConfig} /run/redis/redis.conf + '' + optionalString (cfg.requirePassFile != null) '' + password=$(cat ${escapeShellArg cfg.requirePassFile}) + echo "requirePass $password" >> /run/redis/redis.conf + ''; + serviceConfig = { + ExecStart = "${cfg.package}/bin/redis-server /run/redis/redis.conf"; + RuntimeDirectory = "redis"; + StateDirectory = "redis"; + Type = "notify"; + User = "redis"; + }; + }; }; - } diff --git a/nixos/modules/services/databases/victoriametrics.nix b/nixos/modules/services/databases/victoriametrics.nix new file mode 100644 index 0000000000000..cb6bf8508fb65 --- /dev/null +++ b/nixos/modules/services/databases/victoriametrics.nix @@ -0,0 +1,70 @@ +{ config, pkgs, lib, ... }: +let cfg = config.services.victoriametrics; in +{ + options.services.victoriametrics = with lib; { + enable = mkEnableOption "victoriametrics"; + package = mkOption { + type = types.package; + default = pkgs.victoriametrics; + defaultText = "pkgs.victoriametrics"; + description = '' + The VictoriaMetrics distribution to use. + ''; + }; + listenAddress = mkOption { + default = ":8428"; + type = types.str; + description = '' + The listen address for the http interface. + ''; + }; + retentionPeriod = mkOption { + type = types.int; + default = 1; + description = '' + Retention period in months. + ''; + }; + extraOptions = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Extra options to pass to VictoriaMetrics. See the README: <link + xlink:href="https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md" /> + or <command>victoriametrics -help</command> for more + information. + ''; + }; + }; + config = lib.mkIf cfg.enable { + systemd.services.victoriametrics = { + description = "VictoriaMetrics time series database"; + after = [ "network.target" ]; + serviceConfig = { + Restart = "on-failure"; + RestartSec = 1; + StartLimitBurst = 5; + StateDirectory = "victoriametrics"; + DynamicUser = true; + ExecStart = '' + ${cfg.package}/bin/victoria-metrics \ + -storageDataPath=/var/lib/victoriametrics \ + -httpListenAddr ${cfg.listenAddress} + -retentionPeriod ${toString cfg.retentionPeriod} + ${lib.escapeShellArgs cfg.extraOptions} + ''; + }; + wantedBy = [ "multi-user.target" ]; + + postStart = + let + bindAddr = (lib.optionalString (lib.hasPrefix ":" cfg.listenAddress) "127.0.0.1") + cfg.listenAddress; + in + lib.mkBefore '' + until ${lib.getBin pkgs.curl}/bin/curl -s -o /dev/null http://${bindAddr}/ping; do + sleep 1; + done + ''; + }; + }; +} diff --git a/nixos/modules/services/databases/virtuoso.nix b/nixos/modules/services/databases/virtuoso.nix index 6ffc44a5274e6..6eb09e0a58fcb 100644 --- a/nixos/modules/services/databases/virtuoso.nix +++ b/nixos/modules/services/databases/virtuoso.nix @@ -13,10 +13,7 @@ with lib; services.virtuoso = { - enable = mkOption { - default = false; - description = "Whether to enable Virtuoso Opensource database server."; - }; + enable = mkEnableOption "Virtuoso Opensource database server"; config = mkOption { default = ""; @@ -54,9 +51,8 @@ with lib; config = mkIf cfg.enable { - users.users = singleton - { name = virtuosoUser; - uid = config.ids.uids.virtuoso; + users.users.${virtuosoUser} = + { uid = config.ids.uids.virtuoso; description = "virtuoso user"; home = stateDir; }; diff --git a/nixos/modules/services/desktops/accountsservice.nix b/nixos/modules/services/desktops/accountsservice.nix index c48036a99e8fb..ae2ecb5ffeb71 100644 --- a/nixos/modules/services/desktops/accountsservice.nix +++ b/nixos/modules/services/desktops/accountsservice.nix @@ -6,6 +6,10 @@ with lib; { + meta = { + maintainers = teams.freedesktop.members; + }; + ###### interface options = { diff --git a/nixos/modules/services/desktops/bamf.nix b/nixos/modules/services/desktops/bamf.nix index 0928ee81a6487..4b35146d08449 100644 --- a/nixos/modules/services/desktops/bamf.nix +++ b/nixos/modules/services/desktops/bamf.nix @@ -5,6 +5,10 @@ with lib; { + meta = { + maintainers = with maintainers; [ worldofpeace ]; + }; + ###### interface options = { diff --git a/nixos/modules/services/desktops/gnome3/at-spi2-core.nix b/nixos/modules/services/desktops/gnome3/at-spi2-core.nix index cca98c43dc7a2..492242e3296da 100644 --- a/nixos/modules/services/desktops/gnome3/at-spi2-core.nix +++ b/nixos/modules/services/desktops/gnome3/at-spi2-core.nix @@ -6,6 +6,10 @@ with lib; { + meta = { + maintainers = teams.gnome.members; + }; + ###### interface options = { @@ -18,6 +22,9 @@ with lib; description = '' Whether to enable at-spi2-core, a service for the Assistive Technologies available on the GNOME platform. + + Enable this if you get the error or warning + <literal>The name org.a11y.Bus was not provided by any .service files</literal>. ''; }; diff --git a/nixos/modules/services/desktops/gnome3/chrome-gnome-shell.nix b/nixos/modules/services/desktops/gnome3/chrome-gnome-shell.nix index 3d2b3ed85e3a3..3c7f217b18df0 100644 --- a/nixos/modules/services/desktops/gnome3/chrome-gnome-shell.nix +++ b/nixos/modules/services/desktops/gnome3/chrome-gnome-shell.nix @@ -4,6 +4,10 @@ with lib; { + meta = { + maintainers = teams.gnome.members; + }; + ###### interface options = { services.gnome3.chrome-gnome-shell.enable = mkEnableOption '' diff --git a/nixos/modules/services/desktops/gnome3/evolution-data-server.nix b/nixos/modules/services/desktops/gnome3/evolution-data-server.nix index 7e312a1b81eb3..bd62d16f61ceb 100644 --- a/nixos/modules/services/desktops/gnome3/evolution-data-server.nix +++ b/nixos/modules/services/desktops/gnome3/evolution-data-server.nix @@ -6,6 +6,10 @@ with lib; { + meta = { + maintainers = teams.gnome.members; + }; + ###### interface options = { @@ -16,7 +20,7 @@ with lib; type = types.bool; default = false; description = '' - Whether to enable Evolution Data Server, a collection of services for + Whether to enable Evolution Data Server, a collection of services for storing addressbooks and calendars. ''; }; diff --git a/nixos/modules/services/desktops/gnome3/glib-networking.nix b/nixos/modules/services/desktops/gnome3/glib-networking.nix index fcd58509d6fce..7e667b6b1f047 100644 --- a/nixos/modules/services/desktops/gnome3/glib-networking.nix +++ b/nixos/modules/services/desktops/gnome3/glib-networking.nix @@ -6,6 +6,10 @@ with lib; { + meta = { + maintainers = teams.gnome.members; + }; + ###### interface options = { diff --git a/nixos/modules/services/desktops/gnome3/gnome-initial-setup.nix b/nixos/modules/services/desktops/gnome3/gnome-initial-setup.nix index d715d52c2d067..c391ad9694c9c 100644 --- a/nixos/modules/services/desktops/gnome3/gnome-initial-setup.nix +++ b/nixos/modules/services/desktops/gnome3/gnome-initial-setup.nix @@ -44,6 +44,10 @@ in { + meta = { + maintainers = teams.gnome.members; + }; + ###### interface options = { diff --git a/nixos/modules/services/desktops/gnome3/gnome-keyring.nix b/nixos/modules/services/desktops/gnome3/gnome-keyring.nix index db60445ef7731..2916a3c82b343 100644 --- a/nixos/modules/services/desktops/gnome3/gnome-keyring.nix +++ b/nixos/modules/services/desktops/gnome3/gnome-keyring.nix @@ -6,6 +6,10 @@ with lib; { + meta = { + maintainers = teams.gnome.members; + }; + ###### interface options = { @@ -35,6 +39,8 @@ with lib; services.dbus.packages = [ pkgs.gnome3.gnome-keyring pkgs.gcr ]; + xdg.portal.extraPortals = [ pkgs.gnome3.gnome-keyring ]; + security.pam.services.login.enableGnomeKeyring = true; security.wrappers.gnome-keyring-daemon = { diff --git a/nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix b/nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix index 748a025414a78..3f9ced5e86b18 100644 --- a/nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix +++ b/nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix @@ -6,6 +6,10 @@ with lib; { + meta = { + maintainers = teams.gnome.members; + }; + ###### interface options = { diff --git a/nixos/modules/services/desktops/gnome3/gnome-online-miners.nix b/nixos/modules/services/desktops/gnome3/gnome-online-miners.nix index d406bf6f5e391..39d669e8b30f6 100644 --- a/nixos/modules/services/desktops/gnome3/gnome-online-miners.nix +++ b/nixos/modules/services/desktops/gnome3/gnome-online-miners.nix @@ -6,6 +6,10 @@ with lib; { + meta = { + maintainers = teams.gnome.members; + }; + ###### interface options = { diff --git a/nixos/modules/services/desktops/gnome3/gnome-remote-desktop.nix b/nixos/modules/services/desktops/gnome3/gnome-remote-desktop.nix index 021f4f9534b42..164a0a44f8c81 100644 --- a/nixos/modules/services/desktops/gnome3/gnome-remote-desktop.nix +++ b/nixos/modules/services/desktops/gnome3/gnome-remote-desktop.nix @@ -4,6 +4,10 @@ with lib; { + meta = { + maintainers = teams.gnome.members; + }; + ###### interface options = { services.gnome3.gnome-remote-desktop = { @@ -13,6 +17,8 @@ with lib; ###### implementation config = mkIf config.services.gnome3.gnome-remote-desktop.enable { + services.pipewire.enable = true; + systemd.packages = [ pkgs.gnome3.gnome-remote-desktop ]; }; } diff --git a/nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix b/nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix index 2f83fd653bdee..1c33ed064a19a 100644 --- a/nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix +++ b/nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix @@ -12,6 +12,10 @@ in { + meta = { + maintainers = teams.gnome.members; + }; + imports = [ (mkRemovedOptionModule ["services" "gnome3" "gnome-settings-daemon" "package"] diff --git a/nixos/modules/services/desktops/gnome3/gnome-user-share.nix b/nixos/modules/services/desktops/gnome3/gnome-user-share.nix index f839628777008..f2fe8b41a9e2d 100644 --- a/nixos/modules/services/desktops/gnome3/gnome-user-share.nix +++ b/nixos/modules/services/desktops/gnome3/gnome-user-share.nix @@ -6,6 +6,10 @@ with lib; { + meta = { + maintainers = teams.gnome.members; + }; + ###### interface options = { diff --git a/nixos/modules/services/desktops/gnome3/rygel.nix b/nixos/modules/services/desktops/gnome3/rygel.nix index 55d5e703aa19d..917a1d6541e06 100644 --- a/nixos/modules/services/desktops/gnome3/rygel.nix +++ b/nixos/modules/services/desktops/gnome3/rygel.nix @@ -4,6 +4,10 @@ with lib; { + meta = { + maintainers = teams.gnome.members; + }; + ###### interface options = { services.gnome3.rygel = { @@ -26,5 +30,7 @@ with lib; services.dbus.packages = [ pkgs.gnome3.rygel ]; systemd.packages = [ pkgs.gnome3.rygel ]; + + environment.etc."rygel.conf".source = "${pkgs.gnome3.rygel}/etc/rygel.conf"; }; } diff --git a/nixos/modules/services/desktops/gnome3/sushi.nix b/nixos/modules/services/desktops/gnome3/sushi.nix index 7a4389038b220..83b17365d5dd7 100644 --- a/nixos/modules/services/desktops/gnome3/sushi.nix +++ b/nixos/modules/services/desktops/gnome3/sushi.nix @@ -6,6 +6,10 @@ with lib; { + meta = { + maintainers = teams.gnome.members; + }; + ###### interface options = { diff --git a/nixos/modules/services/desktops/gnome3/tracker-miners.nix b/nixos/modules/services/desktops/gnome3/tracker-miners.nix index b390d8368c652..f2af402492719 100644 --- a/nixos/modules/services/desktops/gnome3/tracker-miners.nix +++ b/nixos/modules/services/desktops/gnome3/tracker-miners.nix @@ -6,6 +6,10 @@ with lib; { + meta = { + maintainers = teams.gnome.members; + }; + ###### interface options = { @@ -25,7 +29,6 @@ with lib; }; - ###### implementation config = mkIf config.services.gnome3.tracker-miners.enable { diff --git a/nixos/modules/services/desktops/gnome3/tracker.nix b/nixos/modules/services/desktops/gnome3/tracker.nix index 2e82927422644..cd196e385539b 100644 --- a/nixos/modules/services/desktops/gnome3/tracker.nix +++ b/nixos/modules/services/desktops/gnome3/tracker.nix @@ -6,6 +6,10 @@ with lib; { + meta = { + maintainers = teams.gnome.members; + }; + ###### interface options = { diff --git a/nixos/modules/services/desktops/gvfs.nix b/nixos/modules/services/desktops/gvfs.nix index 1d002eac41def..250ea6d4575fc 100644 --- a/nixos/modules/services/desktops/gvfs.nix +++ b/nixos/modules/services/desktops/gvfs.nix @@ -12,6 +12,10 @@ in { + meta = { + maintainers = teams.gnome.members; + }; + # Added 2019-08-19 imports = [ (mkRenamedOptionModule diff --git a/nixos/modules/services/desktops/malcontent.nix b/nixos/modules/services/desktops/malcontent.nix new file mode 100644 index 0000000000000..5d6912595b52d --- /dev/null +++ b/nixos/modules/services/desktops/malcontent.nix @@ -0,0 +1,37 @@ +# Malcontent daemon. + +{ config, lib, pkgs, ... }: + +with lib; + +{ + + ###### interface + + options = { + + services.malcontent = { + + enable = mkEnableOption "Malcontent, parental control support for applications"; + + }; + + }; + + + ###### implementation + + config = mkIf config.services.malcontent.enable { + + environment.systemPackages = with pkgs; [ + malcontent + malcontent-ui + ]; + + services.dbus.packages = [ pkgs.malcontent ]; + + services.accounts-daemon.enable = true; + + }; + +} diff --git a/nixos/modules/services/desktops/neard.nix b/nixos/modules/services/desktops/neard.nix new file mode 100644 index 0000000000000..9b0f8d1b3a774 --- /dev/null +++ b/nixos/modules/services/desktops/neard.nix @@ -0,0 +1,23 @@ +# neard service. +{ config, lib, pkgs, ... }: + +with lib; + +{ + ###### interface + options = { + services.neard = { + enable = mkEnableOption "neard, NFC daemon"; + }; + }; + + + ###### implementation + config = mkIf config.services.neard.enable { + environment.systemPackages = [ pkgs.neard ]; + + services.dbus.packages = [ pkgs.neard ]; + + systemd.packages = [ pkgs.neard ]; + }; +} diff --git a/nixos/modules/services/desktops/pantheon/contractor.nix b/nixos/modules/services/desktops/pantheon/contractor.nix deleted file mode 100644 index 2638a21df7336..0000000000000 --- a/nixos/modules/services/desktops/pantheon/contractor.nix +++ /dev/null @@ -1,41 +0,0 @@ -# Contractor - -{ config, pkgs, lib, ... }: - -with lib; - -{ - - meta.maintainers = pkgs.pantheon.maintainers; - - ###### interface - - options = { - - services.pantheon.contractor = { - - enable = mkEnableOption "contractor, a desktop-wide extension service used by pantheon"; - - }; - - }; - - - ###### implementation - - config = mkIf config.services.pantheon.contractor.enable { - - environment.systemPackages = with pkgs.pantheon; [ - contractor - extra-elementary-contracts - ]; - - services.dbus.packages = [ pkgs.pantheon.contractor ]; - - environment.pathsToLink = [ - "/share/contractor" - ]; - - }; - -} diff --git a/nixos/modules/services/desktops/pantheon/files.nix b/nixos/modules/services/desktops/pantheon/files.nix index 577aad6c29872..8cee9f42b62ff 100644 --- a/nixos/modules/services/desktops/pantheon/files.nix +++ b/nixos/modules/services/desktops/pantheon/files.nix @@ -6,33 +6,8 @@ with lib; { - meta.maintainers = pkgs.pantheon.maintainers; - - ###### interface - - options = { - - services.pantheon.files = { - - enable = mkEnableOption "pantheon files daemon"; - - }; - - }; - - - ###### implementation - - config = mkIf config.services.pantheon.files.enable { - - environment.systemPackages = [ - pkgs.pantheon.elementary-files - ]; - - services.dbus.packages = [ - pkgs.pantheon.elementary-files - ]; - - }; + imports = [ + (mkRemovedOptionModule [ "services" "pantheon" "files" "enable" ] "Use `environment.systemPackages [ pkgs.pantheon.elementary-files ];`") + ]; } diff --git a/nixos/modules/services/desktops/pipewire.nix b/nixos/modules/services/desktops/pipewire.nix index 13f3d61e84ca1..5aee59cfdcce9 100644 --- a/nixos/modules/services/desktops/pipewire.nix +++ b/nixos/modules/services/desktops/pipewire.nix @@ -8,6 +8,11 @@ let packages = with pkgs; [ pipewire ]; in { + + meta = { + maintainers = teams.freedesktop.members; + }; + ###### interface options = { services.pipewire = { @@ -33,5 +38,4 @@ in { systemd.user.sockets.pipewire.wantedBy = lib.mkIf cfg.socketActivation [ "sockets.target" ]; }; - meta.maintainers = with lib.maintainers; [ jtojnar ]; } diff --git a/nixos/modules/services/desktops/telepathy.nix b/nixos/modules/services/desktops/telepathy.nix index f5401c180984f..34596bf781849 100644 --- a/nixos/modules/services/desktops/telepathy.nix +++ b/nixos/modules/services/desktops/telepathy.nix @@ -6,6 +6,10 @@ with lib; { + meta = { + maintainers = teams.gnome.members; + }; + ###### interface options = { diff --git a/nixos/modules/services/desktops/tumbler.nix b/nixos/modules/services/desktops/tumbler.nix index a833e99ff8c68..a09079517f042 100644 --- a/nixos/modules/services/desktops/tumbler.nix +++ b/nixos/modules/services/desktops/tumbler.nix @@ -18,6 +18,10 @@ in "") ]; + meta = { + maintainers = with maintainers; [ worldofpeace ]; + }; + ###### interface options = { diff --git a/nixos/modules/services/desktops/zeitgeist.nix b/nixos/modules/services/desktops/zeitgeist.nix index 20c82ccdd56cc..cf7dd5fe3a13c 100644 --- a/nixos/modules/services/desktops/zeitgeist.nix +++ b/nixos/modules/services/desktops/zeitgeist.nix @@ -5,6 +5,11 @@ with lib; { + + meta = { + maintainers = with maintainers; [ worldofpeace ]; + }; + ###### interface options = { diff --git a/nixos/modules/services/development/jupyter/default.nix b/nixos/modules/services/development/jupyter/default.nix index f20860af6e128..e598b0186450b 100644 --- a/nixos/modules/services/development/jupyter/default.nix +++ b/nixos/modules/services/development/jupyter/default.nix @@ -118,15 +118,15 @@ in { in { displayName = "Python 3 for machine learning"; argv = [ - "$ {env.interpreter}" + "''${env.interpreter}" "-m" "ipykernel_launcher" "-f" "{connection_file}" ]; language = "python"; - logo32 = "$ {env.sitePackages}/ipykernel/resources/logo-32x32.png"; - logo64 = "$ {env.sitePackages}/ipykernel/resources/logo-64x64.png"; + logo32 = "''${env.sitePackages}/ipykernel/resources/logo-32x32.png"; + logo64 = "''${env.sitePackages}/ipykernel/resources/logo-64x64.png"; }; } ''; diff --git a/nixos/modules/services/development/lorri.nix b/nixos/modules/services/development/lorri.nix index 68264ee869d19..c843aa56d133c 100644 --- a/nixos/modules/services/development/lorri.nix +++ b/nixos/modules/services/development/lorri.nix @@ -32,7 +32,7 @@ in { description = "Lorri Daemon"; requires = [ "lorri.socket" ]; after = [ "lorri.socket" ]; - path = with pkgs; [ config.nix.package gnutar gzip ]; + path = with pkgs; [ config.nix.package git gnutar gzip ]; serviceConfig = { ExecStart = "${pkgs.lorri}/bin/lorri daemon"; PrivateTmp = true; diff --git a/nixos/modules/services/editors/infinoted.nix b/nixos/modules/services/editors/infinoted.nix index be36676169427..8b997ccbf66e5 100644 --- a/nixos/modules/services/editors/infinoted.nix +++ b/nixos/modules/services/editors/infinoted.nix @@ -111,14 +111,15 @@ in { }; config = mkIf (cfg.enable) { - users.users = optional (cfg.user == "infinoted") - { name = "infinoted"; - description = "Infinoted user"; - group = cfg.group; - isSystemUser = true; + users.users = optionalAttrs (cfg.user == "infinoted") + { infinoted = { + description = "Infinoted user"; + group = cfg.group; + isSystemUser = true; + }; }; - users.groups = optional (cfg.group == "infinoted") - { name = "infinoted"; + users.groups = optionalAttrs (cfg.group == "infinoted") + { infinoted = { }; }; systemd.services.infinoted = diff --git a/nixos/modules/services/games/factorio.nix b/nixos/modules/services/games/factorio.nix index f3831156f453e..4b2e1a3c07f06 100644 --- a/nixos/modules/services/games/factorio.nix +++ b/nixos/modules/services/games/factorio.nix @@ -4,14 +4,13 @@ with lib; let cfg = config.services.factorio; - factorio = pkgs.factorio-headless; name = "Factorio"; stateDir = "/var/lib/${cfg.stateDirName}"; mkSavePath = name: "${stateDir}/saves/${name}.zip"; configFile = pkgs.writeText "factorio.conf" '' use-system-read-write-data-directories=true [path] - read-data=${factorio}/share/factorio/data + read-data=${cfg.package}/share/factorio/data write-data=${stateDir} ''; serverSettings = { @@ -37,7 +36,7 @@ let only_admins_can_pause_the_game = true; autosave_only_on_server = true; admins = []; - }; + } // cfg.extraSettings; serverSettingsFile = pkgs.writeText "server-settings.json" (builtins.toJSON (filterAttrsRecursive (n: v: v != null) serverSettings)); modDir = pkgs.factorio-utils.mkModDirDrv cfg.mods; in @@ -115,6 +114,14 @@ in Description of the game that will appear in the listing. ''; }; + extraSettings = mkOption { + type = types.attrs; + default = {}; + example = { admins = [ "username" ];}; + description = '' + Extra game configuration that will go into server-settings.json + ''; + }; public = mkOption { type = types.bool; default = false; @@ -136,6 +143,15 @@ in Your factorio.com login credentials. Required for games with visibility public. ''; }; + package = mkOption { + type = types.package; + default = pkgs.factorio-headless; + defaultText = "pkgs.factorio-headless"; + example = "pkgs.factorio-headless-experimental"; + description = '' + Factorio version to use. This defaults to the stable channel. + ''; + }; password = mkOption { type = types.nullOr types.str; default = null; @@ -184,7 +200,7 @@ in preStart = toString [ "test -e ${stateDir}/saves/${cfg.saveName}.zip" "||" - "${factorio}/bin/factorio" + "${cfg.package}/bin/factorio" "--config=${cfg.configFile}" "--create=${mkSavePath cfg.saveName}" (optionalString (cfg.mods != []) "--mod-directory=${modDir}") @@ -197,7 +213,7 @@ in StateDirectory = cfg.stateDirName; UMask = "0007"; ExecStart = toString [ - "${factorio}/bin/factorio" + "${cfg.package}/bin/factorio" "--config=${cfg.configFile}" "--port=${toString cfg.port}" "--start-server=${mkSavePath cfg.saveName}" diff --git a/nixos/modules/services/hardware/actkbd.nix b/nixos/modules/services/hardware/actkbd.nix index 4168140b287a6..daa407ca1f0e5 100644 --- a/nixos/modules/services/hardware/actkbd.nix +++ b/nixos/modules/services/hardware/actkbd.nix @@ -83,7 +83,7 @@ in See <command>actkbd</command> <filename>README</filename> for documentation. - The example shows a piece of what <option>sound.enableMediaKeys</option> does when enabled. + The example shows a piece of what <option>sound.mediaKeys.enable</option> does when enabled. ''; }; diff --git a/nixos/modules/services/hardware/bluetooth.nix b/nixos/modules/services/hardware/bluetooth.nix index c5f9d1f9b7253..dfa39e7f6024c 100644 --- a/nixos/modules/services/hardware/bluetooth.nix +++ b/nixos/modules/services/hardware/bluetooth.nix @@ -36,17 +36,25 @@ in { ''; }; + config = mkOption { + type = with types; attrsOf (attrsOf (oneOf [ bool int str ])); + example = { + General = { + ControllerMode = "bredr"; + }; + }; + description = "Set configuration for system-wide bluetooth (/etc/bluetooth/main.conf)."; + }; + extraConfig = mkOption { - type = types.lines; - default = ""; + type = with types; nullOr lines; + default = null; example = '' [General] ControllerMode = bredr ''; description = '' Set additional configuration for system-wide bluetooth (/etc/bluetooth/main.conf). - - NOTE: We already include [Policy], so any configuration under the Policy group should come first. ''; }; }; @@ -56,17 +64,19 @@ in { ###### implementation config = mkIf cfg.enable { + warnings = optional (cfg.extraConfig != null) "hardware.bluetooth.`extraConfig` is deprecated, please use hardware.bluetooth.`config`."; - environment.systemPackages = [ bluez-bluetooth pkgs.openobex pkgs.obexftp ]; + hardware.bluetooth.config = { + Policy = { + AutoEnable = mkDefault cfg.powerOnBoot; + }; + }; - environment.etc = singleton { - source = pkgs.writeText "main.conf" '' - [Policy] - AutoEnable=${lib.boolToString cfg.powerOnBoot} + environment.systemPackages = [ bluez-bluetooth ]; - ${cfg.extraConfig} - ''; - target = "bluetooth/main.conf"; + environment.etc."bluetooth/main.conf"= { + source = pkgs.writeText "main.conf" + (generators.toINI { } cfg.config + optionalString (cfg.extraConfig != null) cfg.extraConfig); }; services.udev.packages = [ bluez-bluetooth ]; diff --git a/nixos/modules/services/hardware/fwupd.nix b/nixos/modules/services/hardware/fwupd.nix index 51877970a8bcb..e586af25c2b1d 100644 --- a/nixos/modules/services/hardware/fwupd.nix +++ b/nixos/modules/services/hardware/fwupd.nix @@ -53,7 +53,7 @@ in { blacklistPlugins = mkOption { type = types.listOf types.str; - default = [ "test" ]; + default = []; example = [ "udev" ]; description = '' Allow blacklisting specific plugins @@ -91,6 +91,9 @@ in { ###### implementation config = mkIf cfg.enable { + # Disable test related plug-ins implicitly so that users do not have to care about them. + services.fwupd.blacklistPlugins = cfg.package.defaultBlacklistedPlugins; + environment.systemPackages = [ cfg.package ]; environment.etc = { diff --git a/nixos/modules/services/hardware/irqbalance.nix b/nixos/modules/services/hardware/irqbalance.nix index b139154432cf9..c79e0eb83ecea 100644 --- a/nixos/modules/services/hardware/irqbalance.nix +++ b/nixos/modules/services/hardware/irqbalance.nix @@ -13,18 +13,12 @@ in config = mkIf cfg.enable { - systemd.services = { - irqbalance = { - description = "irqbalance daemon"; - path = [ pkgs.irqbalance ]; - serviceConfig = - { ExecStart = "${pkgs.irqbalance}/bin/irqbalance --foreground"; }; - wantedBy = [ "multi-user.target" ]; - }; - }; - environment.systemPackages = [ pkgs.irqbalance ]; + systemd.services.irqbalance.wantedBy = ["multi-user.target"]; + + systemd.packages = [ pkgs.irqbalance ]; + }; } diff --git a/nixos/modules/services/hardware/ratbagd.nix b/nixos/modules/services/hardware/ratbagd.nix index 103e1d2315aea..01a8276750f20 100644 --- a/nixos/modules/services/hardware/ratbagd.nix +++ b/nixos/modules/services/hardware/ratbagd.nix @@ -10,12 +10,7 @@ in options = { services.ratbagd = { - enable = mkOption { - default = false; - description = '' - Whether to enable ratbagd for configuring gaming mice. - ''; - }; + enable = mkEnableOption "ratbagd for configuring gaming mice"; }; }; diff --git a/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix b/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix index f6ed4e25e9cb9..6f49a1ab6d406 100644 --- a/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix +++ b/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix @@ -67,11 +67,11 @@ in { options = { - hardware.sane.brscan4.enable = + hardware.sane.brscan4.enable = mkEnableOption "Brother's brscan4 scan backend" // { description = '' When enabled, will automatically register the "brscan4" sane - backend and bring configuration files to their expected location. + backend and bring configuration files to their expected location. ''; }; @@ -95,14 +95,11 @@ in pkgs.brscan4 ]; - environment.etc = singleton { - target = "opt/brother/scanner/brscan4"; - source = "${etcFiles}/etc/opt/brother/scanner/brscan4"; - }; + environment.etc."opt/brother/scanner/brscan4" = + { source = "${etcFiles}/etc/opt/brother/scanner/brscan4"; }; assertions = [ { assertion = all (x: !(null != x.ip && null != x.nodename)) netDeviceList; - message = '' When describing a network device as part of the attribute list `hardware.sane.brscan4.netDevices`, only one of its `ip` or `nodename` diff --git a/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix b/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix index 6bf31982b71a9..ec0457bbd5830 100644 --- a/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix +++ b/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix @@ -63,7 +63,7 @@ stdenv.mkDerivation { meta = { description = "Brother brscan4 sane backend driver etc files"; - homepage = http://www.brother.com; + homepage = "http://www.brother.com"; platforms = stdenv.lib.platforms.linux; license = stdenv.lib.licenses.unfree; maintainers = with stdenv.lib.maintainers; [ jraygauthier ]; diff --git a/nixos/modules/services/hardware/tcsd.nix b/nixos/modules/services/hardware/tcsd.nix index 3876280ee6bcd..68cb5d791aa35 100644 --- a/nixos/modules/services/hardware/tcsd.nix +++ b/nixos/modules/services/hardware/tcsd.nix @@ -137,15 +137,15 @@ in serviceConfig.ExecStart = "${pkgs.trousers}/sbin/tcsd -f -c ${tcsdConf}"; }; - users.users = optionalAttrs (cfg.user == "tss") (singleton - { name = "tss"; + users.users = optionalAttrs (cfg.user == "tss") { + tss = { group = "tss"; uid = config.ids.uids.tss; - }); + }; + }; - users.groups = optionalAttrs (cfg.group == "tss") (singleton - { name = "tss"; - gid = config.ids.gids.tss; - }); + users.groups = optionalAttrs (cfg.group == "tss") { + tss.gid = config.ids.gids.tss; + }; }; } diff --git a/nixos/modules/services/hardware/thermald.nix b/nixos/modules/services/hardware/thermald.nix index 69577bbe01810..ecb529e9bf018 100644 --- a/nixos/modules/services/hardware/thermald.nix +++ b/nixos/modules/services/hardware/thermald.nix @@ -8,12 +8,7 @@ in { ###### interface options = { services.thermald = { - enable = mkOption { - default = false; - description = '' - Whether to enable thermald, the temperature management daemon. - ''; - }; + enable = mkEnableOption "thermald, the temperature management daemon"; debug = mkOption { type = types.bool; diff --git a/nixos/modules/services/hardware/tlp.nix b/nixos/modules/services/hardware/tlp.nix index adc1881a525d7..3962d7b159894 100644 --- a/nixos/modules/services/hardware/tlp.nix +++ b/nixos/modules/services/hardware/tlp.nix @@ -1,39 +1,26 @@ { config, lib, pkgs, ... }: - with lib; - let - -cfg = config.services.tlp; - -enableRDW = config.networking.networkmanager.enable; - -tlp = pkgs.tlp.override { - inherit enableRDW; -}; - -# XXX: We can't use writeTextFile + readFile here because it triggers -# TLP build to get the .drv (even on --dry-run). -confFile = pkgs.runCommand "tlp" - { config = cfg.extraConfig; - passAsFile = [ "config" ]; - preferLocalBuild = true; - } - '' - cat ${tlp}/etc/default/tlp > $out - cat $configPath >> $out - ''; - + cfg = config.services.tlp; + enableRDW = config.networking.networkmanager.enable; + tlp = pkgs.tlp.override { inherit enableRDW; }; + # TODO: Use this for having proper parameters in the future + mkTlpConfig = tlpConfig: generators.toKeyValue { + mkKeyValue = generators.mkKeyValueDefault { + mkValueString = val: + if isInt val then toString val + else if isString val then val + else if true == val then "1" + else if false == val then "0" + else if isList val then "\"" + (concatStringsSep " " val) + "\"" + else err "invalid value provided to mkTlpConfig:" (toString val); + } "="; + } tlpConfig; in - { - ###### interface - options = { - services.tlp = { - enable = mkOption { type = types.bool; default = false; @@ -45,76 +32,64 @@ in default = ""; description = "Additional configuration variables for TLP"; }; - }; - }; - ###### implementation - config = mkIf cfg.enable { + boot.kernelModules = [ "msr" ]; - powerManagement.scsiLinkPolicy = null; - powerManagement.cpuFreqGovernor = null; - powerManagement.cpufreq.max = null; - powerManagement.cpufreq.min = null; + environment.etc = { + "tlp.conf".text = cfg.extraConfig; + } // optionalAttrs enableRDW { + "NetworkManager/dispatcher.d/99tlp-rdw-nm".source = + "${tlp}/etc/NetworkManager/dispatcher.d/99tlp-rdw-nm"; + }; - systemd.sockets.systemd-rfkill.enable = false; + environment.systemPackages = [ tlp ]; - systemd.services = { - "systemd-rfkill@".enable = false; - systemd-rfkill.enable = false; + # FIXME: When the config is parametrized we need to move these into a + # conditional on the relevant options being enabled. + powerManagement = { + scsiLinkPolicy = null; + cpuFreqGovernor = null; + cpufreq.max = null; + cpufreq.min = null; + }; - tlp = { - description = "TLP system startup/shutdown"; + services.udev.packages = [ tlp ]; - after = [ "multi-user.target" ]; + systemd = { + packages = [ tlp ]; + # XXX: These must always be disabled/masked according to [1]. + # + # [1]: https://github.com/linrunner/TLP/blob/a9ada09e0821f275ce5f93dc80a4d81a7ff62ae4/tlp-stat.in#L319 + sockets.systemd-rfkill.enable = false; + services.systemd-rfkill.enable = false; + + services.tlp = { + # XXX: The service should reload whenever the configuration changes, + # otherwise newly set power options remain inactive until reboot (or + # manual unit restart.) + restartTriggers = [ config.environment.etc."tlp.conf".source ]; + # XXX: When using systemd.packages (which we do above) the [Install] + # section of systemd units does not work (citation needed) so we manually + # enforce it here. wantedBy = [ "multi-user.target" ]; - before = [ "shutdown.target" ]; - restartTriggers = [ confFile ]; - - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - ExecStart = "${tlp}/bin/tlp init start"; - ExecStop = "${tlp}/bin/tlp init stop"; - }; }; - tlp-sleep = { - description = "TLP suspend/resume"; - - wantedBy = [ "sleep.target" ]; + services.tlp-sleep = { + # XXX: When using systemd.packages (which we do above) the [Install] + # section of systemd units does not work (citation needed) so we manually + # enforce it here. before = [ "sleep.target" ]; - - unitConfig = { - StopWhenUnneeded = true; - }; - - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - ExecStart = "${tlp}/bin/tlp suspend"; - ExecStop = "${tlp}/bin/tlp resume"; - }; + wantedBy = [ "sleep.target" ]; + # XXX: `tlp suspend` requires /var/lib/tlp to exist in order to save + # some stuff in there. There is no way, that I know of, to do this in + # the package itself, so we do it here instead making sure the unit + # won't fail due to the save dir not existing. + serviceConfig.StateDirectory = "tlp"; }; }; - - services.udev.packages = [ tlp ]; - - environment.etc = [{ source = confFile; - target = "default/tlp"; - } - ] ++ optional enableRDW { - source = "${tlp}/etc/NetworkManager/dispatcher.d/99tlp-rdw-nm"; - target = "NetworkManager/dispatcher.d/99tlp-rdw-nm"; - }; - - environment.systemPackages = [ tlp ]; - - boot.kernelModules = [ "msr" ]; - }; - } diff --git a/nixos/modules/services/hardware/udev.nix b/nixos/modules/services/hardware/udev.nix index 83ab93bd7cfc0..168056a475e5c 100644 --- a/nixos/modules/services/hardware/udev.nix +++ b/nixos/modules/services/hardware/udev.nix @@ -221,8 +221,8 @@ in type = types.lines; description = '' Additional <command>hwdb</command> files. They'll be written - into file <filename>10-local.hwdb</filename>. Thus they are - read before all other files. + into file <filename>99-local.hwdb</filename>. Thus they are + read after all other files. ''; }; @@ -281,13 +281,10 @@ in boot.kernelParams = mkIf (!config.networking.usePredictableInterfaceNames) [ "net.ifnames=0" ]; environment.etc = - [ { source = udevRules; - target = "udev/rules.d"; - } - { source = hwdbBin; - target = "udev/hwdb.bin"; - } - ]; + { + "udev/rules.d".source = udevRules; + "udev/hwdb.bin".source = hwdbBin; + }; system.requiredKernelConfig = with config.lib.kernelConfig; [ (isEnabled "UNIX") diff --git a/nixos/modules/services/hardware/upower.nix b/nixos/modules/services/hardware/upower.nix index 5e7ac7a6e6591..449810b53150f 100644 --- a/nixos/modules/services/hardware/upower.nix +++ b/nixos/modules/services/hardware/upower.nix @@ -37,6 +37,172 @@ in ''; }; + enableWattsUpPro = mkOption { + type = types.bool; + default = false; + description = '' + Enable the Watts Up Pro device. + + The Watts Up Pro contains a generic FTDI USB device without a specific + vendor and product ID. When we probe for WUP devices, we can cause + the user to get a perplexing "Device or resource busy" error when + attempting to use their non-WUP device. + + The generic FTDI device is known to also be used on: + + <itemizedlist> + <listitem><para>Sparkfun FT232 breakout board</para></listitem> + <listitem><para>Parallax Propeller</para></listitem> + </itemizedlist> + ''; + }; + + noPollBatteries = mkOption { + type = types.bool; + default = false; + description = '' + Don't poll the kernel for battery level changes. + + Some hardware will send us battery level changes through + events, rather than us having to poll for it. This option + allows disabling polling for hardware that sends out events. + ''; + }; + + ignoreLid = mkOption { + type = types.bool; + default = false; + description = '' + Do we ignore the lid state + + Some laptops are broken. The lid state is either inverted, or stuck + on or off. We can't do much to fix these problems, but this is a way + for users to make the laptop panel vanish, a state that might be used + by a couple of user-space daemons. On Linux systems, see also + logind.conf(5). + ''; + }; + + usePercentageForPolicy = mkOption { + type = types.bool; + default = true; + description = '' + Policy for warnings and action based on battery levels + + Whether battery percentage based policy should be used. The default + is to use the percentage, which + should work around broken firmwares. It is also more reliable than + the time left (frantically saving all your files is going to use more + battery than letting it rest for example). + ''; + }; + + percentageLow = mkOption { + type = types.ints.unsigned; + default = 10; + description = '' + When <literal>usePercentageForPolicy</literal> is + <literal>true</literal>, the levels at which UPower will consider the + battery low. + + This will also be used for batteries which don't have time information + such as that of peripherals. + + If any value (of <literal>percentageLow</literal>, + <literal>percentageCritical</literal> and + <literal>percentageAction</literal>) is invalid, or not in descending + order, the defaults will be used. + ''; + }; + + percentageCritical = mkOption { + type = types.ints.unsigned; + default = 3; + description = '' + When <literal>usePercentageForPolicy</literal> is + <literal>true</literal>, the levels at which UPower will consider the + battery critical. + + This will also be used for batteries which don't have time information + such as that of peripherals. + + If any value (of <literal>percentageLow</literal>, + <literal>percentageCritical</literal> and + <literal>percentageAction</literal>) is invalid, or not in descending + order, the defaults will be used. + ''; + }; + + percentageAction = mkOption { + type = types.ints.unsigned; + default = 2; + description = '' + When <literal>usePercentageForPolicy</literal> is + <literal>true</literal>, the levels at which UPower will take action + for the critical battery level. + + This will also be used for batteries which don't have time information + such as that of peripherals. + + If any value (of <literal>percentageLow</literal>, + <literal>percentageCritical</literal> and + <literal>percentageAction</literal>) is invalid, or not in descending + order, the defaults will be used. + ''; + }; + + timeLow = mkOption { + type = types.ints.unsigned; + default = 1200; + description = '' + When <literal>usePercentageForPolicy</literal> is + <literal>false</literal>, the time remaining at which UPower will + consider the battery low. + + If any value (of <literal>timeLow</literal>, + <literal>timeCritical</literal> and <literal>timeAction</literal>) is + invalid, or not in descending order, the defaults will be used. + ''; + }; + + timeCritical = mkOption { + type = types.ints.unsigned; + default = 300; + description = '' + When <literal>usePercentageForPolicy</literal> is + <literal>false</literal>, the time remaining at which UPower will + consider the battery critical. + + If any value (of <literal>timeLow</literal>, + <literal>timeCritical</literal> and <literal>timeAction</literal>) is + invalid, or not in descending order, the defaults will be used. + ''; + }; + + timeAction = mkOption { + type = types.ints.unsigned; + default = 120; + description = '' + When <literal>usePercentageForPolicy</literal> is + <literal>false</literal>, the time remaining at which UPower will + take action for the critical battery level. + + If any value (of <literal>timeLow</literal>, + <literal>timeCritical</literal> and <literal>timeAction</literal>) is + invalid, or not in descending order, the defaults will be used. + ''; + }; + + criticalPowerAction = mkOption { + type = types.enum [ "PowerOff" "Hibernate" "HybridSleep" ]; + default = "HybridSleep"; + description = '' + The action to take when <literal>timeAction</literal> or + <literal>percentageAction</literal> has been reached for the batteries + (UPS or laptop batteries) supplying the computer + ''; + }; + }; }; @@ -54,6 +220,21 @@ in systemd.packages = [ cfg.package ]; + environment.etc."UPower/UPower.conf".text = generators.toINI {} { + UPower = { + EnableWattsUpPro = cfg.enableWattsUpPro; + NoPollBatteries = cfg.noPollBatteries; + IgnoreLid = cfg.ignoreLid; + UsePercentageForPolicy = cfg.usePercentageForPolicy; + PercentageLow = cfg.percentageLow; + PercentageCritical = cfg.percentageCritical; + PercentageAction = cfg.percentageAction; + TimeLow = cfg.timeLow; + TimeCritical = cfg.timeCritical; + TimeAction = cfg.timeAction; + CriticalPowerAction = cfg.criticalPowerAction; + }; + }; }; } diff --git a/nixos/modules/services/hardware/usbmuxd.nix b/nixos/modules/services/hardware/usbmuxd.nix index 39bbcaf4627ca..11a4b0a858f9c 100644 --- a/nixos/modules/services/hardware/usbmuxd.nix +++ b/nixos/modules/services/hardware/usbmuxd.nix @@ -43,15 +43,16 @@ in config = mkIf cfg.enable { - users.users = optional (cfg.user == defaultUserGroup) { - name = cfg.user; - description = "usbmuxd user"; - group = cfg.group; - isSystemUser = true; + users.users = optionalAttrs (cfg.user == defaultUserGroup) { + ${cfg.user} = { + description = "usbmuxd user"; + group = cfg.group; + isSystemUser = true; + }; }; - users.groups = optional (cfg.group == defaultUserGroup) { - name = cfg.group; + users.groups = optionalAttrs (cfg.group == defaultUserGroup) { + ${cfg.group} = { }; }; # Give usbmuxd permission for Apple devices diff --git a/nixos/modules/services/hardware/xow.nix b/nixos/modules/services/hardware/xow.nix new file mode 100644 index 0000000000000..a18d60ad83bec --- /dev/null +++ b/nixos/modules/services/hardware/xow.nix @@ -0,0 +1,17 @@ +{ config, pkgs, lib, ... }: + +let + cfg = config.services.hardware.xow; +in { + options.services.hardware.xow = { + enable = lib.mkEnableOption "xow as a systemd service"; + }; + + config = lib.mkIf cfg.enable { + hardware.uinput.enable = true; + + systemd.packages = [ pkgs.xow ]; + + services.udev.packages = [ pkgs.xow ]; + }; +} diff --git a/nixos/modules/services/logging/awstats.nix b/nixos/modules/services/logging/awstats.nix index a92ff3bee490b..5939d7808f7f1 100644 --- a/nixos/modules/services/logging/awstats.nix +++ b/nixos/modules/services/logging/awstats.nix @@ -4,31 +4,117 @@ with lib; let cfg = config.services.awstats; - httpd = config.services.httpd; package = pkgs.awstats; -in + configOpts = {name, config, ...}: { + options = { + type = mkOption{ + type = types.enum [ "mail" "web" ]; + default = "web"; + example = "mail"; + description = '' + The type of log being collected. + ''; + }; + domain = mkOption { + type = types.str; + default = name; + description = "The domain name to collect stats for."; + example = "example.com"; + }; + + logFile = mkOption { + type = types.str; + example = "/var/spool/nginx/logs/access.log"; + description = '' + The log file to be scanned. + For mail, set this to + <literal> + journalctl $OLD_CURSOR -u postfix.service | ''${pkgs.perl}/bin/perl ''${pkgs.awstats.out}/share/awstats/tools/maillogconvert.pl standard | + </literal> + ''; + }; + + logFormat = mkOption { + type = types.str; + default = "1"; + description = '' + The log format being used. + + For mail, set this to + <literal> + %time2 %email %email_r %host %host_r %method %url %code %bytesd + </literal> + ''; + }; + + hostAliases = mkOption { + type = types.listOf types.str; + default = []; + example = "[ \"www.example.org\" ]"; + description = '' + List of aliases the site has. + ''; + }; + + extraConfig = mkOption { + type = types.attrsOf types.str; + default = {}; + example = literalExample '' + { + "ValidHTTPCodes" = "404"; + } + ''; + description = "Extra configuration to be appendend to awstats.\${name}.conf."; + }; + + webService = { + enable = mkEnableOption "awstats web service"; + + hostname = mkOption { + type = types.str; + default = config.domain; + description = "The hostname the web service appears under."; + }; + + urlPrefix = mkOption { + type = types.str; + default = "/awstats"; + description = "The URL prefix under which the awstats pages appear."; + }; + }; + }; + }; + webServices = filterAttrs (name: value: value.webService.enable) cfg.configs; +in { + imports = [ + (mkRemovedOptionModule [ "services" "awstats" "service" "enable" ] "Please enable per domain with `services.awstats.configs.<name>.webService.enable`") + (mkRemovedOptionModule [ "services" "awstats" "service" "urlPrefix" ] "Please set per domain with `services.awstats.configs.<name>.webService.urlPrefix`") + (mkRenamedOptionModule [ "services" "awstats" "vardir" ] [ "services" "awstats" "dataDir" ]) + ]; + options.services.awstats = { - enable = mkOption { - type = types.bool; - default = cfg.service.enable; - description = '' - Enable the awstats program (but not service). - Currently only simple httpd (Apache) configs are supported, - and awstats plugins may not work correctly. - ''; - }; - vardir = mkOption { + enable = mkEnableOption "awstats"; + + dataDir = mkOption { type = types.path; default = "/var/lib/awstats"; - description = "The directory where variable awstats data will be stored."; + description = "The directory where awstats data will be stored."; }; - extraConfig = mkOption { - type = types.lines; - default = ""; - description = "Extra configuration to be appendend to awstats.conf."; + configs = mkOption { + type = types.attrsOf (types.submodule configOpts); + default = {}; + example = literalExample '' + { + "mysite" = { + domain = "example.com"; + logFile = "/var/spool/nginx/logs/access.log"; + }; + } + ''; + description = "Attribute set of domains to collect stats for."; }; updateAt = mkOption { @@ -42,75 +128,129 @@ in <manvolnum>7</manvolnum></citerefentry>) ''; }; - - service = { - enable = mkOption { - type = types.bool; - default = false; - description = ''Enable the awstats web service. This switches on httpd.''; - }; - urlPrefix = mkOption { - type = types.str; - default = "/awstats"; - description = "The URL prefix under which the awstats service appears."; - }; - }; }; config = mkIf cfg.enable { environment.systemPackages = [ package.bin ]; - /* TODO: - - heed config.services.httpd.logPerVirtualHost, etc. - - Can't AllowToUpdateStatsFromBrowser, as CGI scripts don't have permission - to read the logs, and our httpd config apparently doesn't an option for that. - */ - environment.etc."awstats/awstats.conf".source = pkgs.runCommand "awstats.conf" + + environment.etc = mapAttrs' (name: opts: + nameValuePair "awstats/awstats.${name}.conf" { + source = pkgs.runCommand "awstats.${name}.conf" { preferLocalBuild = true; } - ( let - logFormat = - if httpd.logFormat == "combined" then "1" else - if httpd.logFormat == "common" then "4" else - throw "awstats service doesn't support Apache log format `${httpd.logFormat}`"; - in - '' - sed \ - -e 's|^\(DirData\)=.*$|\1="${cfg.vardir}"|' \ - -e 's|^\(DirIcons\)=.*$|\1="icons"|' \ - -e 's|^\(CreateDirDataIfNotExists\)=.*$|\1=1|' \ - -e 's|^\(SiteDomain\)=.*$|\1="${httpd.hostName}"|' \ - -e 's|^\(LogFile\)=.*$|\1="${httpd.logDir}/access_log"|' \ - -e 's|^\(LogFormat\)=.*$|\1=${logFormat}|' \ - < '${package.out}/wwwroot/cgi-bin/awstats.model.conf' > "$out" - echo '${cfg.extraConfig}' >> "$out" - ''); - - systemd.tmpfiles.rules = optionals cfg.service.enable [ - "d '${cfg.vardir}' - ${httpd.user} ${httpd.group} - -" - "Z '${cfg.vardir}' - ${httpd.user} ${httpd.group} - -" - ]; - - # The httpd sub-service showing awstats. - services.httpd = optionalAttrs cfg.service.enable { - enable = true; - extraConfig = '' - Alias ${cfg.service.urlPrefix}/classes "${package.out}/wwwroot/classes/" - Alias ${cfg.service.urlPrefix}/css "${package.out}/wwwroot/css/" - Alias ${cfg.service.urlPrefix}/icons "${package.out}/wwwroot/icon/" - ScriptAlias ${cfg.service.urlPrefix}/ "${package.out}/wwwroot/cgi-bin/" - - <Directory "${package.out}/wwwroot"> - Options None - Require all granted - </Directory> - ''; - }; + ('' + sed \ + '' + # set up mail stats + + optionalString (opts.type == "mail") + '' + -e 's|^\(LogType\)=.*$|\1=M|' \ + -e 's|^\(LevelForBrowsersDetection\)=.*$|\1=0|' \ + -e 's|^\(LevelForOSDetection\)=.*$|\1=0|' \ + -e 's|^\(LevelForRefererAnalyze\)=.*$|\1=0|' \ + -e 's|^\(LevelForRobotsDetection\)=.*$|\1=0|' \ + -e 's|^\(LevelForSearchEnginesDetection\)=.*$|\1=0|' \ + -e 's|^\(LevelForFileTypesDetection\)=.*$|\1=0|' \ + -e 's|^\(LevelForWormsDetection\)=.*$|\1=0|' \ + -e 's|^\(ShowMenu\)=.*$|\1=1|' \ + -e 's|^\(ShowSummary\)=.*$|\1=HB|' \ + -e 's|^\(ShowMonthStats\)=.*$|\1=HB|' \ + -e 's|^\(ShowDaysOfMonthStats\)=.*$|\1=HB|' \ + -e 's|^\(ShowDaysOfWeekStats\)=.*$|\1=HB|' \ + -e 's|^\(ShowHoursStats\)=.*$|\1=HB|' \ + -e 's|^\(ShowDomainsStats\)=.*$|\1=0|' \ + -e 's|^\(ShowHostsStats\)=.*$|\1=HB|' \ + -e 's|^\(ShowAuthenticatedUsers\)=.*$|\1=0|' \ + -e 's|^\(ShowRobotsStats\)=.*$|\1=0|' \ + -e 's|^\(ShowEMailSenders\)=.*$|\1=HBML|' \ + -e 's|^\(ShowEMailReceivers\)=.*$|\1=HBML|' \ + -e 's|^\(ShowSessionsStats\)=.*$|\1=0|' \ + -e 's|^\(ShowPagesStats\)=.*$|\1=0|' \ + -e 's|^\(ShowFileTypesStats\)=.*$|\1=0|' \ + -e 's|^\(ShowFileSizesStats\)=.*$|\1=0|' \ + -e 's|^\(ShowBrowsersStats\)=.*$|\1=0|' \ + -e 's|^\(ShowOSStats\)=.*$|\1=0|' \ + -e 's|^\(ShowOriginStats\)=.*$|\1=0|' \ + -e 's|^\(ShowKeyphrasesStats\)=.*$|\1=0|' \ + -e 's|^\(ShowKeywordsStats\)=.*$|\1=0|' \ + -e 's|^\(ShowMiscStats\)=.*$|\1=0|' \ + -e 's|^\(ShowHTTPErrorsStats\)=.*$|\1=0|' \ + -e 's|^\(ShowSMTPErrorsStats\)=.*$|\1=1|' \ + '' + + + # common options + '' + -e 's|^\(DirData\)=.*$|\1="${cfg.dataDir}/${name}"|' \ + -e 's|^\(DirIcons\)=.*$|\1="icons"|' \ + -e 's|^\(CreateDirDataIfNotExists\)=.*$|\1=1|' \ + -e 's|^\(SiteDomain\)=.*$|\1="${name}"|' \ + -e 's|^\(LogFile\)=.*$|\1="${opts.logFile}"|' \ + -e 's|^\(LogFormat\)=.*$|\1="${opts.logFormat}"|' \ + '' + + + # extra config + concatStringsSep "\n" (mapAttrsToList (n: v: '' + -e 's|^\(${n}\)=.*$|\1="${v}"|' \ + '') opts.extraConfig) + + + '' + < '${package.out}/wwwroot/cgi-bin/awstats.model.conf' > "$out" + ''); + }) cfg.configs; - systemd.services.awstats-update = mkIf (cfg.updateAt != null) { - description = "awstats log collector"; - script = "exec '${package.bin}/bin/awstats' -update -config=awstats.conf"; - startAt = cfg.updateAt; - }; + # create data directory with the correct permissions + systemd.tmpfiles.rules = + [ "d '${cfg.dataDir}' 755 root root - -" ] ++ + mapAttrsToList (name: opts: "d '${cfg.dataDir}/${name}' 755 root root - -") cfg.configs ++ + [ "Z '${cfg.dataDir}' 755 root root - -" ]; + + # nginx options + services.nginx.virtualHosts = mapAttrs'(name: opts: { + name = opts.webService.hostname; + value = { + locations = { + "${opts.webService.urlPrefix}/css/" = { + alias = "${package.out}/wwwroot/css/"; + }; + "${opts.webService.urlPrefix}/icons/" = { + alias = "${package.out}/wwwroot/icon/"; + }; + "${opts.webService.urlPrefix}/" = { + alias = "${cfg.dataDir}/${name}/"; + extraConfig = '' + autoindex on; + ''; + }; + }; + }; + }) webServices; + + # update awstats + systemd.services = mkIf (cfg.updateAt != null) (mapAttrs' (name: opts: + nameValuePair "awstats-${name}-update" { + description = "update awstats for ${name}"; + script = optionalString (opts.type == "mail") + '' + if [[ -f "${cfg.dataDir}/${name}-cursor" ]]; then + CURSOR="$(cat "${cfg.dataDir}/${name}-cursor" | tr -d '\n')" + if [[ -n "$CURSOR" ]]; then + echo "Using cursor: $CURSOR" + export OLD_CURSOR="--cursor $CURSOR" + fi + fi + NEW_CURSOR="$(journalctl $OLD_CURSOR -u postfix.service --show-cursor | tail -n 1 | tr -d '\n' | sed -e 's#^-- cursor: \(.*\)#\1#')" + echo "New cursor: $NEW_CURSOR" + ${package.bin}/bin/awstats -update -config=${name} + if [ -n "$NEW_CURSOR" ]; then + echo -n "$NEW_CURSOR" > ${cfg.dataDir}/${name}-cursor + fi + '' + '' + ${package.out}/share/awstats/tools/awstats_buildstaticpages.pl \ + -config=${name} -update -dir=${cfg.dataDir}/${name} \ + -awstatsprog=${package.bin}/bin/awstats + ''; + startAt = cfg.updateAt; + }) cfg.configs); }; } diff --git a/nixos/modules/services/logging/logcheck.nix b/nixos/modules/services/logging/logcheck.nix index 6d8be5b926d5f..4296b2270c29f 100644 --- a/nixos/modules/services/logging/logcheck.nix +++ b/nixos/modules/services/logging/logcheck.nix @@ -213,13 +213,14 @@ in mapAttrsToList writeIgnoreRule cfg.ignore ++ mapAttrsToList writeIgnoreCronRule cfg.ignoreCron; - users.users = optionalAttrs (cfg.user == "logcheck") (singleton - { name = "logcheck"; + users.users = optionalAttrs (cfg.user == "logcheck") { + logcheck = { uid = config.ids.uids.logcheck; shell = "/bin/sh"; description = "Logcheck user account"; extraGroups = cfg.extraGroups; - }); + }; + }; system.activationScripts.logcheck = '' mkdir -m 700 -p /var/{lib,lock}/logcheck diff --git a/nixos/modules/services/logging/logstash.nix b/nixos/modules/services/logging/logstash.nix index 4943e8d7db3a5..21a83803fd8c6 100644 --- a/nixos/modules/services/logging/logstash.nix +++ b/nixos/modules/services/logging/logstash.nix @@ -37,6 +37,11 @@ let in { + imports = [ + (mkRenamedOptionModule [ "services" "logstash" "address" ] [ "services" "logstash" "listenAddress" ]) + (mkRemovedOptionModule [ "services" "logstash" "enableWeb" ] "The web interface was removed from logstash") + ]; + ###### interface options = { diff --git a/nixos/modules/services/logging/syslog-ng.nix b/nixos/modules/services/logging/syslog-ng.nix index 65e103ac2ba58..35055311680bb 100644 --- a/nixos/modules/services/logging/syslog-ng.nix +++ b/nixos/modules/services/logging/syslog-ng.nix @@ -25,6 +25,10 @@ let ]; in { + imports = [ + (mkRemovedOptionModule [ "services" "syslog-ng" "serviceName" ] "") + (mkRemovedOptionModule [ "services" "syslog-ng" "listenToJournal" ] "") + ]; options = { diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix index 3fd06812c6751..230a2ae3f8251 100644 --- a/nixos/modules/services/mail/dovecot.nix +++ b/nixos/modules/services/mail/dovecot.nix @@ -14,18 +14,34 @@ let base_dir = ${baseDir} protocols = ${concatStringsSep " " cfg.protocols} sendmail_path = /run/wrappers/bin/sendmail + # defining mail_plugins must be done before the first protocol {} filter because of https://doc.dovecot.org/configuration_manual/config_file/config_file_syntax/#variable-expansion + mail_plugins = $mail_plugins ${concatStringsSep " " cfg.mailPlugins.globally.enable} '' - (if cfg.sslServerCert == null then '' - ssl = no - disable_plaintext_auth = no - '' else '' - ssl_cert = <${cfg.sslServerCert} - ssl_key = <${cfg.sslServerKey} - ${optionalString (cfg.sslCACert != null) ("ssl_ca = <" + cfg.sslCACert)} - ssl_dh = <${config.security.dhparams.params.dovecot2.path} - disable_plaintext_auth = yes - '') + ( + concatStringsSep "\n" ( + mapAttrsToList ( + protocol: plugins: '' + protocol ${protocol} { + mail_plugins = $mail_plugins ${concatStringsSep " " plugins.enable} + } + '' + ) cfg.mailPlugins.perProtocol + ) + ) + + ( + if cfg.sslServerCert == null then '' + ssl = no + disable_plaintext_auth = no + '' else '' + ssl_cert = <${cfg.sslServerCert} + ssl_key = <${cfg.sslServerKey} + ${optionalString (cfg.sslCACert != null) ("ssl_ca = <" + cfg.sslCACert)} + ssl_dh = <${config.security.dhparams.params.dovecot2.path} + disable_plaintext_auth = yes + '' + ) '' default_internal_user = ${cfg.user} @@ -45,55 +61,58 @@ let } '' - (optionalString cfg.enablePAM '' - userdb { - driver = passwd - } - - passdb { - driver = pam - args = ${optionalString cfg.showPAMFailure "failure_show_msg=yes"} dovecot2 - } - '') + ( + optionalString cfg.enablePAM '' + userdb { + driver = passwd + } - (optionalString (cfg.sieveScripts != {}) '' - plugin { - ${concatStringsSep "\n" (mapAttrsToList (to: from: "sieve_${to} = ${stateDir}/sieve/${to}") cfg.sieveScripts)} - } - '') + passdb { + driver = pam + args = ${optionalString cfg.showPAMFailure "failure_show_msg=yes"} dovecot2 + } + '' + ) - (optionalString (cfg.mailboxes != []) '' - protocol imap { - namespace inbox { - inbox=yes - ${concatStringsSep "\n" (map mailboxConfig cfg.mailboxes)} + ( + optionalString (cfg.sieveScripts != {}) '' + plugin { + ${concatStringsSep "\n" (mapAttrsToList (to: from: "sieve_${to} = ${stateDir}/sieve/${to}") cfg.sieveScripts)} } - } - '') - - (optionalString cfg.enableQuota '' - mail_plugins = $mail_plugins quota - service quota-status { - executable = ${dovecotPkg}/libexec/dovecot/quota-status -p postfix - inet_listener { - port = ${cfg.quotaPort} + '' + ) + + ( + optionalString (cfg.mailboxes != []) '' + protocol imap { + namespace inbox { + inbox=yes + ${concatStringsSep "\n" (map mailboxConfig cfg.mailboxes)} + } + } + '' + ) + + ( + optionalString cfg.enableQuota '' + service quota-status { + executable = ${dovecotPkg}/libexec/dovecot/quota-status -p postfix + inet_listener { + port = ${cfg.quotaPort} + } + client_limit = 1 } - client_limit = 1 - } - - protocol imap { - mail_plugins = $mail_plugins imap_quota - } - plugin { - quota_rule = *:storage=${cfg.quotaGlobalPerUser} - quota = maildir:User quota # per virtual mail user quota # BUG/FIXME broken, we couldn't get this working - quota_status_success = DUNNO - quota_status_nouser = DUNNO - quota_status_overquota = "552 5.2.2 Mailbox is full" - quota_grace = 10%% - } - '') + plugin { + quota_rule = *:storage=${cfg.quotaGlobalPerUser} + quota = maildir:User quota # per virtual mail user quota # BUG/FIXME broken, we couldn't get this working + quota_status_success = DUNNO + quota_status_nouser = DUNNO + quota_status_overquota = "552 5.2.2 Mailbox is full" + quota_grace = 10%% + } + '' + ) cfg.extraConfig ]; @@ -107,7 +126,7 @@ let mailbox "${mailbox.name}" { auto = ${toString mailbox.auto} '' + optionalString (mailbox.specialUse != null) '' - special_use = \${toString mailbox.specialUse} + special_use = \${toString mailbox.specialUse} '' + "}"; mailboxes = { ... }: { @@ -133,6 +152,9 @@ let }; in { + imports = [ + (mkRemovedOptionModule [ "services" "dovecot2" "package" ] "") + ]; options.services.dovecot2 = { enable = mkEnableOption "Dovecot 2.x POP3/IMAP server"; @@ -157,7 +179,7 @@ in protocols = mkOption { type = types.listOf types.str; - default = [ ]; + default = []; description = "Additional listeners to start when Dovecot is enabled."; }; @@ -180,6 +202,43 @@ in description = "Additional entries to put verbatim into Dovecot's config file."; }; + mailPlugins = + let + plugins = hint: types.submodule { + options = { + enable = mkOption { + type = types.listOf types.str; + default = []; + description = "mail plugins to enable as a list of strings to append to the ${hint} <literal>$mail_plugins</literal> configuration variable"; + }; + }; + }; + in + mkOption { + type = with types; submodule { + options = { + globally = mkOption { + description = "Additional entries to add to the mail_plugins variable for all protocols"; + type = plugins "top-level"; + example = { enable = [ "virtual" ]; }; + default = { enable = []; }; + }; + perProtocol = mkOption { + description = "Additional entries to add to the mail_plugins variable, per protocol"; + type = attrsOf (plugins "corresponding per-protocol"); + default = {}; + example = { imap = [ "imap_acl" ]; }; + }; + }; + }; + description = "Additional entries to add to the mail_plugins variable, globally and per protocol"; + example = { + globally.enable = [ "acl" ]; + perProtocol.imap.enable = [ "imap_acl" ]; + }; + default = { globally.enable = []; perProtocol = {}; }; + }; + configFile = mkOption { type = types.nullOr types.path; default = null; @@ -302,41 +361,43 @@ in enable = true; params.dovecot2 = {}; }; - services.dovecot2.protocols = - optional cfg.enableImap "imap" - ++ optional cfg.enablePop3 "pop3" - ++ optional cfg.enableLmtp "lmtp"; - - users.users = [ - { name = "dovenull"; - uid = config.ids.uids.dovenull2; - description = "Dovecot user for untrusted logins"; - group = "dovenull"; - } - ] ++ optional (cfg.user == "dovecot2") - { name = "dovecot2"; - uid = config.ids.uids.dovecot2; - description = "Dovecot user"; - group = cfg.group; - } - ++ optional (cfg.createMailUser && cfg.mailUser != null) - ({ name = cfg.mailUser; - description = "Virtual Mail User"; - } // optionalAttrs (cfg.mailGroup != null) { - group = cfg.mailGroup; - }); - - users.groups = optional (cfg.group == "dovecot2") - { name = "dovecot2"; - gid = config.ids.gids.dovecot2; - } - ++ optional (cfg.createMailUser && cfg.mailGroup != null) - { name = cfg.mailGroup; - } - ++ singleton - { name = "dovenull"; - gid = config.ids.gids.dovenull2; - }; + services.dovecot2.protocols = + optional cfg.enableImap "imap" + ++ optional cfg.enablePop3 "pop3" + ++ optional cfg.enableLmtp "lmtp"; + + services.dovecot2.mailPlugins = mkIf cfg.enableQuota { + globally.enable = [ "quota" ]; + perProtocol.imap.enable = [ "imap_quota" ]; + }; + + users.users = { + dovenull = + { + uid = config.ids.uids.dovenull2; + description = "Dovecot user for untrusted logins"; + group = "dovenull"; + }; + } // optionalAttrs (cfg.user == "dovecot2") { + dovecot2 = + { + uid = config.ids.uids.dovecot2; + description = "Dovecot user"; + group = cfg.group; + }; + } // optionalAttrs (cfg.createMailUser && cfg.mailUser != null) { + ${cfg.mailUser} = + { description = "Virtual Mail User"; } // optionalAttrs (cfg.mailGroup != null) + { group = cfg.mailGroup; }; + }; + + users.groups = { + dovenull.gid = config.ids.gids.dovenull2; + } // optionalAttrs (cfg.group == "dovecot2") { + dovecot2.gid = config.ids.gids.dovecot2; + } // optionalAttrs (cfg.createMailUser && cfg.mailGroup != null) { + ${cfg.mailGroup} = {}; + }; environment.etc."dovecot/modules".source = modulesDir; environment.etc."dovecot/dovecot.conf".source = cfg.configFile; @@ -364,15 +425,19 @@ in rm -rf ${stateDir}/sieve '' + optionalString (cfg.sieveScripts != {}) '' mkdir -p ${stateDir}/sieve - ${concatStringsSep "\n" (mapAttrsToList (to: from: '' - if [ -d '${from}' ]; then - mkdir '${stateDir}/sieve/${to}' - cp -p "${from}/"*.sieve '${stateDir}/sieve/${to}' - else - cp -p '${from}' '${stateDir}/sieve/${to}' - fi - ${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/sieve/${to}' - '') cfg.sieveScripts)} + ${concatStringsSep "\n" ( + mapAttrsToList ( + to: from: '' + if [ -d '${from}' ]; then + mkdir '${stateDir}/sieve/${to}' + cp -p "${from}/"*.sieve '${stateDir}/sieve/${to}' + else + cp -p '${from}' '${stateDir}/sieve/${to}' + fi + ${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/sieve/${to}' + '' + ) cfg.sieveScripts + )} chown -R '${cfg.mailUser}:${cfg.mailGroup}' '${stateDir}/sieve' ''; }; @@ -380,17 +445,21 @@ in environment.systemPackages = [ dovecotPkg ]; assertions = [ - { assertion = intersectLists cfg.protocols [ "pop3" "imap" ] != []; + { + assertion = intersectLists cfg.protocols [ "pop3" "imap" ] != []; message = "dovecot needs at least one of the IMAP or POP3 listeners enabled"; } - { assertion = (cfg.sslServerCert == null) == (cfg.sslServerKey == null) - && (cfg.sslCACert != null -> !(cfg.sslServerCert == null || cfg.sslServerKey == null)); + { + assertion = (cfg.sslServerCert == null) == (cfg.sslServerKey == null) + && (cfg.sslCACert != null -> !(cfg.sslServerCert == null || cfg.sslServerKey == null)); message = "dovecot needs both sslServerCert and sslServerKey defined for working crypto"; } - { assertion = cfg.showPAMFailure -> cfg.enablePAM; + { + assertion = cfg.showPAMFailure -> cfg.enablePAM; message = "dovecot is configured with showPAMFailure while enablePAM is disabled"; } - { assertion = cfg.sieveScripts != {} -> (cfg.mailUser != null && cfg.mailGroup != null); + { + assertion = cfg.sieveScripts != {} -> (cfg.mailUser != null && cfg.mailGroup != null); message = "dovecot requires mailUser and mailGroup to be set when sieveScripts is set"; } ]; diff --git a/nixos/modules/services/mail/dspam.nix b/nixos/modules/services/mail/dspam.nix index 72b8c4c08b926..766ebc8095a07 100644 --- a/nixos/modules/services/mail/dspam.nix +++ b/nixos/modules/services/mail/dspam.nix @@ -86,16 +86,16 @@ in { config = mkIf cfg.enable (mkMerge [ { - users.users = optionalAttrs (cfg.user == "dspam") (singleton - { name = "dspam"; + users.users = optionalAttrs (cfg.user == "dspam") { + dspam = { group = cfg.group; uid = config.ids.uids.dspam; - }); + }; + }; - users.groups = optionalAttrs (cfg.group == "dspam") (singleton - { name = "dspam"; - gid = config.ids.gids.dspam; - }); + users.groups = optionalAttrs (cfg.group == "dspam") { + dspam.gid = config.ids.gids.dspam; + }; environment.systemPackages = [ dspam ]; diff --git a/nixos/modules/services/mail/exim.nix b/nixos/modules/services/mail/exim.nix index 47812dd1e40ee..892fbd33214a2 100644 --- a/nixos/modules/services/mail/exim.nix +++ b/nixos/modules/services/mail/exim.nix @@ -87,15 +87,13 @@ in systemPackages = [ cfg.package ]; }; - users.users = singleton { - name = cfg.user; + users.users.${cfg.user} = { description = "Exim mail transfer agent user"; uid = config.ids.uids.exim; group = cfg.group; }; - users.groups = singleton { - name = cfg.group; + users.groups.${cfg.group} = { gid = config.ids.gids.exim; }; diff --git a/nixos/modules/services/mail/mailman.nix b/nixos/modules/services/mail/mailman.nix index e917209f3d1f5..f5e78b1829338 100644 --- a/nixos/modules/services/mail/mailman.nix +++ b/nixos/modules/services/mail/mailman.nix @@ -6,37 +6,18 @@ let cfg = config.services.mailman; - mailmanPyEnv = pkgs.python3.withPackages (ps: with ps; [mailman mailman-hyperkitty]); - - mailmanExe = with pkgs; stdenv.mkDerivation { - name = "mailman-" + python3Packages.mailman.version; - buildInputs = [makeWrapper]; - unpackPhase = ":"; - installPhase = '' - mkdir -p $out/bin - makeWrapper ${mailmanPyEnv}/bin/mailman $out/bin/mailman \ - --set MAILMAN_CONFIG_FILE /etc/mailman.cfg - ''; - }; - - mailmanWeb = pkgs.python3Packages.mailman-web.override { - serverEMail = cfg.siteOwner; - archiverKey = cfg.hyperkittyApiKey; - allowedHosts = cfg.webHosts; - }; - - mailmanWebPyEnv = pkgs.python3.withPackages (x: with x; [mailman-web]); - - mailmanWebExe = with pkgs; stdenv.mkDerivation { - inherit (mailmanWeb) name; - buildInputs = [makeWrapper]; - unpackPhase = ":"; - installPhase = '' - mkdir -p $out/bin - makeWrapper ${mailmanWebPyEnv}/bin/django-admin $out/bin/mailman-web \ - --set DJANGO_SETTINGS_MODULE settings - ''; - }; + # This deliberately doesn't use recursiveUpdate so users can + # override the defaults. + settings = { + DEFAULT_FROM_EMAIL = cfg.siteOwner; + SERVER_EMAIL = cfg.siteOwner; + ALLOWED_HOSTS = [ "localhost" "127.0.0.1" ] ++ cfg.webHosts; + COMPRESS_OFFLINE = true; + STATIC_ROOT = "/var/lib/mailman-web/static"; + MEDIA_ROOT = "/var/lib/mailman-web/media"; + } // cfg.webSettings; + + settingsJSON = pkgs.writeText "settings.json" (builtins.toJSON settings); mailmanCfg = '' [mailman] @@ -53,30 +34,42 @@ let etc_dir: /etc ext_dir: $etc_dir/mailman.d pid_file: /run/mailman/master.pid - '' + optionalString (cfg.hyperkittyApiKey != null) '' + '' + optionalString cfg.hyperkitty.enable '' + [archiver.hyperkitty] class: mailman_hyperkitty.Archiver enable: yes - configuration: ${pkgs.writeText "mailman-hyperkitty.cfg" mailmanHyperkittyCfg} + configuration: /var/lib/mailman/mailman-hyperkitty.cfg ''; - mailmanHyperkittyCfg = '' + mailmanHyperkittyCfg = pkgs.writeText "mailman-hyperkitty.cfg" '' [general] # This is your HyperKitty installation, preferably on the localhost. This # address will be used by Mailman to forward incoming emails to HyperKitty # for archiving. It does not need to be publicly available, in fact it's # better if it is not. - base_url: ${cfg.hyperkittyBaseUrl} + base_url: ${cfg.hyperkitty.baseUrl} # Shared API key, must be the identical to the value in HyperKitty's # settings. - api_key: ${cfg.hyperkittyApiKey} + api_key: @API_KEY@ ''; in { ###### interface + imports = [ + (mkRenamedOptionModule [ "services" "mailman" "hyperkittyBaseUrl" ] + [ "services" "mailman" "hyperkitty" "baseUrl" ]) + + (mkRemovedOptionModule [ "services" "mailman" "hyperkittyApiKey" ] '' + The Hyperkitty API key is now generated on first run, and not + stored in the world-readable Nix store. To continue using + Hyperkitty, you must set services.mailman.hyperkitty.enable = true. + '') + ]; + options = { services.mailman = { @@ -87,9 +80,17 @@ in { description = "Enable Mailman on this host. Requires an active Postfix installation."; }; + package = mkOption { + type = types.package; + default = pkgs.mailman; + defaultText = "pkgs.mailman"; + example = "pkgs.mailman.override { archivers = []; }"; + description = "Mailman package to use"; + }; + siteOwner = mkOption { type = types.str; - default = "postmaster@example.org"; + example = "postmaster@example.org"; description = '' Certain messages that must be delivered to a human, but which can't be delivered to a list owner (e.g. a bounce from a list owner), will @@ -99,12 +100,13 @@ in { webRoot = mkOption { type = types.path; - default = "${mailmanWeb}/${pkgs.python3.sitePackages}"; - defaultText = "pkgs.python3Packages.mailman-web"; + default = "${pkgs.mailman-web}/${pkgs.python3.sitePackages}"; + defaultText = "\${pkgs.mailman-web}/\${pkgs.python3.sitePackages}"; description = '' The web root for the Hyperkity + Postorius apps provided by Mailman. This variable can be set, of course, but it mainly exists so that site - admins can refer to it in their own hand-written httpd configuration files. + admins can refer to it in their own hand-written web server + configuration files. ''; }; @@ -120,26 +122,35 @@ in { ''; }; - hyperkittyBaseUrl = mkOption { + webUser = mkOption { type = types.str; - default = "http://localhost/hyperkitty/"; + default = config.services.httpd.user; description = '' - Where can Mailman connect to Hyperkitty's internal API, preferably on - localhost? + User to run mailman-web as ''; }; - hyperkittyApiKey = mkOption { - type = types.nullOr types.str; - default = null; + webSettings = mkOption { + type = types.attrs; + default = {}; description = '' - The shared secret used to authenticate Mailman's internal - communication with Hyperkitty. Must be set to enable support for the - Hyperkitty archiver. Note that this secret is going to be visible to - all local users in the Nix store. + Overrides for the default mailman-web Django settings. ''; }; + hyperkitty = { + enable = mkEnableOption "the Hyperkitty archiver for Mailman"; + + baseUrl = mkOption { + type = types.str; + default = "http://localhost/hyperkitty/"; + description = '' + Where can Mailman connect to Hyperkitty's internal API, preferably on + localhost? + ''; + }; + }; + }; }; @@ -147,25 +158,58 @@ in { config = mkIf cfg.enable { - assertions = [ - { assertion = cfg.enable -> config.services.postfix.enable; + assertions = let + inherit (config.services) postfix; + + requirePostfixHash = optionPath: dataFile: + with lib; + let + expected = "hash:/var/lib/mailman/data/${dataFile}"; + value = attrByPath optionPath [] postfix; + in + { assertion = postfix.enable -> isList value && elem expected value; + message = '' + services.postfix.${concatStringsSep "." optionPath} must contain + "${expected}". + See <https://mailman.readthedocs.io/en/latest/src/mailman/docs/mta.html>. + ''; + }; + in [ + { assertion = postfix.enable; message = "Mailman requires Postfix"; } + (requirePostfixHash [ "relayDomains" ] "postfix_domains") + (requirePostfixHash [ "config" "transport_maps" ] "postfix_lmtp") + (requirePostfixHash [ "config" "local_recipient_maps" ] "postfix_lmtp") ]; users.users.mailman = { description = "GNU Mailman"; isSystemUser = true; }; - environment = { - systemPackages = [ mailmanExe mailmanWebExe pkgs.sassc ]; - etc."mailman.cfg".text = mailmanCfg; - }; + environment.etc."mailman.cfg".text = mailmanCfg; + + environment.etc."mailman3/settings.py".text = '' + import os + + # Required by mailman_web.settings, but will be overridden when + # settings_local.json is loaded. + os.environ["SECRET_KEY"] = "" + + from mailman_web.settings import * + + import json + + with open('${settingsJSON}') as f: + globals().update(json.load(f)) + + with open('/var/lib/mailman-web/settings_local.json') as f: + globals().update(json.load(f)) + ''; + + environment.systemPackages = [ cfg.package ] ++ (with pkgs; [ mailman-web ]); services.postfix = { - relayDomains = [ "hash:/var/lib/mailman/data/postfix_domains" ]; recipientDelimiter = "+"; # bake recipient addresses in mail envelopes via VERP config = { - transport_maps = [ "hash:/var/lib/mailman/data/postfix_lmtp" ]; - local_recipient_maps = [ "hash:/var/lib/mailman/data/postfix_lmtp" ]; owner_request_special = "no"; # Mailman handles -owner addresses on its own }; }; @@ -173,34 +217,79 @@ in { systemd.services.mailman = { description = "GNU Mailman Master Process"; after = [ "network.target" ]; + restartTriggers = [ config.environment.etc."mailman.cfg".source ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { - ExecStart = "${mailmanExe}/bin/mailman start"; - ExecStop = "${mailmanExe}/bin/mailman stop"; + ExecStart = "${cfg.package}/bin/mailman start"; + ExecStop = "${cfg.package}/bin/mailman stop"; User = "mailman"; Type = "forking"; - StateDirectory = "mailman"; - StateDirectoryMode = "0700"; RuntimeDirectory = "mailman"; PIDFile = "/run/mailman/master.pid"; }; }; + systemd.services.mailman-settings = { + description = "Generate settings files (including secrets) for Mailman"; + before = [ "mailman.service" "mailman-web.service" "hyperkitty.service" "httpd.service" "uwsgi.service" ]; + requiredBy = [ "mailman.service" "mailman-web.service" "hyperkitty.service" "httpd.service" "uwsgi.service" ]; + path = with pkgs; [ jq ]; + script = '' + mailmanDir=/var/lib/mailman + mailmanWebDir=/var/lib/mailman-web + + mailmanCfg=$mailmanDir/mailman-hyperkitty.cfg + mailmanWebCfg=$mailmanWebDir/settings_local.json + + install -m 0700 -o mailman -g nogroup -d $mailmanDir + install -m 0700 -o ${cfg.webUser} -g nogroup -d $mailmanWebDir + + if [ ! -e $mailmanWebCfg ]; then + hyperkittyApiKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64) + secretKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64) + + mailmanWebCfgTmp=$(mktemp) + jq -n '.MAILMAN_ARCHIVER_KEY=$archiver_key | .SECRET_KEY=$secret_key' \ + --arg archiver_key "$hyperkittyApiKey" \ + --arg secret_key "$secretKey" \ + >"$mailmanWebCfgTmp" + chown ${cfg.webUser} "$mailmanWebCfgTmp" + mv -n "$mailmanWebCfgTmp" $mailmanWebCfg + fi + + hyperkittyApiKey="$(jq -r .MAILMAN_ARCHIVER_KEY $mailmanWebCfg)" + mailmanCfgTmp=$(mktemp) + sed "s/@API_KEY@/$hyperkittyApiKey/g" ${mailmanHyperkittyCfg} >"$mailmanCfgTmp" + chown mailman "$mailmanCfgTmp" + mv "$mailmanCfgTmp" $mailmanCfg + ''; + serviceConfig = { + Type = "oneshot"; + # RemainAfterExit makes restartIfChanged work for this service, so + # downstream services will get updated automatically when things like + # services.mailman.hyperkitty.baseUrl change. Otherwise users have to + # restart things manually, which is confusing. + RemainAfterExit = "yes"; + }; + }; + systemd.services.mailman-web = { description = "Init Postorius DB"; - before = [ "httpd.service" ]; - requiredBy = [ "httpd.service" ]; + before = [ "httpd.service" "uwsgi.service" ]; + requiredBy = [ "httpd.service" "uwsgi.service" ]; + restartTriggers = [ config.environment.etc."mailman3/settings.py".source ]; script = '' - ${mailmanWebExe}/bin/mailman-web migrate + ${pkgs.mailman-web}/bin/mailman-web migrate rm -rf static - ${mailmanWebExe}/bin/mailman-web collectstatic - ${mailmanWebExe}/bin/mailman-web compress + ${pkgs.mailman-web}/bin/mailman-web collectstatic + ${pkgs.mailman-web}/bin/mailman-web compress ''; serviceConfig = { - User = config.services.httpd.user; + User = cfg.webUser; Type = "oneshot"; - StateDirectory = "mailman-web"; - StateDirectoryMode = "0700"; + # Similar to mailman-settings.service, this makes restartTriggers work + # properly for this service. + RemainAfterExit = "yes"; WorkingDirectory = "/var/lib/mailman-web"; }; }; @@ -208,86 +297,94 @@ in { systemd.services.mailman-daily = { description = "Trigger daily Mailman events"; startAt = "daily"; + restartTriggers = [ config.environment.etc."mailman.cfg".source ]; serviceConfig = { - ExecStart = "${mailmanExe}/bin/mailman digests --send"; + ExecStart = "${cfg.package}/bin/mailman digests --send"; User = "mailman"; }; }; systemd.services.hyperkitty = { - enable = cfg.hyperkittyApiKey != null; + inherit (cfg.hyperkitty) enable; description = "GNU Hyperkitty QCluster Process"; after = [ "network.target" ]; + restartTriggers = [ config.environment.etc."mailman3/settings.py".source ]; wantedBy = [ "mailman.service" "multi-user.target" ]; serviceConfig = { - ExecStart = "${mailmanWebExe}/bin/mailman-web qcluster"; - User = config.services.httpd.user; + ExecStart = "${pkgs.mailman-web}/bin/mailman-web qcluster"; + User = cfg.webUser; WorkingDirectory = "/var/lib/mailman-web"; }; }; systemd.services.hyperkitty-minutely = { - enable = cfg.hyperkittyApiKey != null; + inherit (cfg.hyperkitty) enable; description = "Trigger minutely Hyperkitty events"; startAt = "minutely"; + restartTriggers = [ config.environment.etc."mailman3/settings.py".source ]; serviceConfig = { - ExecStart = "${mailmanWebExe}/bin/mailman-web runjobs minutely"; - User = config.services.httpd.user; + ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs minutely"; + User = cfg.webUser; WorkingDirectory = "/var/lib/mailman-web"; }; }; systemd.services.hyperkitty-quarter-hourly = { - enable = cfg.hyperkittyApiKey != null; + inherit (cfg.hyperkitty) enable; description = "Trigger quarter-hourly Hyperkitty events"; startAt = "*:00/15"; + restartTriggers = [ config.environment.etc."mailman3/settings.py".source ]; serviceConfig = { - ExecStart = "${mailmanWebExe}/bin/mailman-web runjobs quarter_hourly"; - User = config.services.httpd.user; + ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs quarter_hourly"; + User = cfg.webUser; WorkingDirectory = "/var/lib/mailman-web"; }; }; systemd.services.hyperkitty-hourly = { - enable = cfg.hyperkittyApiKey != null; + inherit (cfg.hyperkitty) enable; description = "Trigger hourly Hyperkitty events"; startAt = "hourly"; + restartTriggers = [ config.environment.etc."mailman3/settings.py".source ]; serviceConfig = { - ExecStart = "${mailmanWebExe}/bin/mailman-web runjobs hourly"; - User = config.services.httpd.user; + ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs hourly"; + User = cfg.webUser; WorkingDirectory = "/var/lib/mailman-web"; }; }; systemd.services.hyperkitty-daily = { - enable = cfg.hyperkittyApiKey != null; + inherit (cfg.hyperkitty) enable; description = "Trigger daily Hyperkitty events"; startAt = "daily"; + restartTriggers = [ config.environment.etc."mailman3/settings.py".source ]; serviceConfig = { - ExecStart = "${mailmanWebExe}/bin/mailman-web runjobs daily"; - User = config.services.httpd.user; + ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs daily"; + User = cfg.webUser; WorkingDirectory = "/var/lib/mailman-web"; }; }; systemd.services.hyperkitty-weekly = { - enable = cfg.hyperkittyApiKey != null; + inherit (cfg.hyperkitty) enable; description = "Trigger weekly Hyperkitty events"; startAt = "weekly"; + restartTriggers = [ config.environment.etc."mailman3/settings.py".source ]; serviceConfig = { - ExecStart = "${mailmanWebExe}/bin/mailman-web runjobs weekly"; - User = config.services.httpd.user; + ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs weekly"; + User = cfg.webUser; WorkingDirectory = "/var/lib/mailman-web"; }; }; systemd.services.hyperkitty-yearly = { - enable = cfg.hyperkittyApiKey != null; + inherit (cfg.hyperkitty) enable; description = "Trigger yearly Hyperkitty events"; startAt = "yearly"; + restartTriggers = [ config.environment.etc."mailman3/settings.py".source ]; serviceConfig = { - ExecStart = "${mailmanWebExe}/bin/mailman-web runjobs yearly"; - User = config.services.httpd.user; + ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs yearly"; + User = cfg.webUser; WorkingDirectory = "/var/lib/mailman-web"; }; }; diff --git a/nixos/modules/services/mail/mlmmj.nix b/nixos/modules/services/mail/mlmmj.nix index 7ae00f3e501eb..d58d93c4214c1 100644 --- a/nixos/modules/services/mail/mlmmj.nix +++ b/nixos/modules/services/mail/mlmmj.nix @@ -94,8 +94,7 @@ in config = mkIf cfg.enable { - users.users = singleton { - name = cfg.user; + users.users.${cfg.user} = { description = "mlmmj user"; home = stateDir; createHome = true; @@ -104,8 +103,7 @@ in useDefaultShell = true; }; - users.groups = singleton { - name = cfg.group; + users.groups.${cfg.group} = { gid = config.ids.gids.mlmmj; }; diff --git a/nixos/modules/services/mail/nullmailer.nix b/nixos/modules/services/mail/nullmailer.nix index 2c2910e0aa9b7..fe3f8ef9b3913 100644 --- a/nixos/modules/services/mail/nullmailer.nix +++ b/nixos/modules/services/mail/nullmailer.nix @@ -201,15 +201,12 @@ with lib; }; users = { - users = singleton { - name = cfg.user; + users.${cfg.user} = { description = "Nullmailer relay-only mta user"; group = cfg.group; }; - groups = singleton { - name = cfg.group; - }; + groups.${cfg.group} = { }; }; systemd.tmpfiles.rules = [ diff --git a/nixos/modules/services/mail/opendkim.nix b/nixos/modules/services/mail/opendkim.nix index 253823cbaf9c0..eb6a426684d42 100644 --- a/nixos/modules/services/mail/opendkim.nix +++ b/nixos/modules/services/mail/opendkim.nix @@ -18,6 +18,9 @@ let ] ++ optionals (cfg.configFile != null) [ "-x" cfg.configFile ]; in { + imports = [ + (mkRenamedOptionModule [ "services" "opendkim" "keyFile" ] [ "services" "opendkim" "keyPath" ]) + ]; ###### interface @@ -88,16 +91,16 @@ in { config = mkIf cfg.enable { - users.users = optionalAttrs (cfg.user == "opendkim") (singleton - { name = "opendkim"; + users.users = optionalAttrs (cfg.user == "opendkim") { + opendkim = { group = cfg.group; uid = config.ids.uids.opendkim; - }); + }; + }; - users.groups = optionalAttrs (cfg.group == "opendkim") (singleton - { name = "opendkim"; - gid = config.ids.gids.opendkim; - }); + users.groups = optionalAttrs (cfg.group == "opendkim") { + opendkim.gid = config.ids.gids.opendkim; + }; environment.systemPackages = [ pkgs.opendkim ]; diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix index df438a0c69d19..19e11b31d9ca6 100644 --- a/nixos/modules/services/mail/postfix.nix +++ b/nixos/modules/services/mail/postfix.nix @@ -612,10 +612,7 @@ in { environment = { - etc = singleton - { source = "/var/lib/postfix/conf"; - target = "postfix"; - }; + etc.postfix.source = "/var/lib/postfix/conf"; # This makes it comfortable to run 'postqueue/postdrop' for example. systemPackages = [ pkgs.postfix ]; @@ -655,21 +652,20 @@ in setgid = true; }; - users.users = optional (user == "postfix") - { name = "postfix"; - description = "Postfix mail server user"; - uid = config.ids.uids.postfix; - group = group; + users.users = optionalAttrs (user == "postfix") + { postfix = { + description = "Postfix mail server user"; + uid = config.ids.uids.postfix; + group = group; + }; }; users.groups = - optional (group == "postfix") - { name = group; - gid = config.ids.gids.postfix; + optionalAttrs (group == "postfix") + { ${group}.gid = config.ids.gids.postfix; } - ++ optional (setgidGroup == "postdrop") - { name = setgidGroup; - gid = config.ids.gids.postdrop; + // optionalAttrs (setgidGroup == "postdrop") + { ${setgidGroup}.gid = config.ids.gids.postdrop; }; systemd.services.postfix = diff --git a/nixos/modules/services/mail/postgrey.nix b/nixos/modules/services/mail/postgrey.nix index 88fb7f0b4ad17..709f6b21aa0ad 100644 --- a/nixos/modules/services/mail/postgrey.nix +++ b/nixos/modules/services/mail/postgrey.nix @@ -42,6 +42,17 @@ with lib; let }; in { + imports = [ + (mkMergedOptionModule [ [ "services" "postgrey" "inetAddr" ] [ "services" "postgrey" "inetPort" ] ] [ "services" "postgrey" "socket" ] (config: let + value = p: getAttrFromPath p config; + inetAddr = [ "services" "postgrey" "inetAddr" ]; + inetPort = [ "services" "postgrey" "inetPort" ]; + in + if value inetAddr == null + then { path = "/run/postgrey.sock"; } + else { addr = value inetAddr; port = value inetPort; } + )) + ]; options = { services.postgrey = with types; { diff --git a/nixos/modules/services/mail/postsrsd.nix b/nixos/modules/services/mail/postsrsd.nix index 8f12a16906c5a..2ebc675ab10af 100644 --- a/nixos/modules/services/mail/postsrsd.nix +++ b/nixos/modules/services/mail/postsrsd.nix @@ -90,16 +90,16 @@ in { services.postsrsd.domain = mkDefault config.networking.hostName; - users.users = optionalAttrs (cfg.user == "postsrsd") (singleton - { name = "postsrsd"; + users.users = optionalAttrs (cfg.user == "postsrsd") { + postsrsd = { group = cfg.group; uid = config.ids.uids.postsrsd; - }); + }; + }; - users.groups = optionalAttrs (cfg.group == "postsrsd") (singleton - { name = "postsrsd"; - gid = config.ids.gids.postsrsd; - }); + users.groups = optionalAttrs (cfg.group == "postsrsd") { + postsrsd.gid = config.ids.gids.postsrsd; + }; systemd.services.postsrsd = { description = "PostSRSd SRS rewriting server"; diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix index 36dda619ad063..21e92cfee0165 100644 --- a/nixos/modules/services/mail/roundcube.nix +++ b/nixos/modules/services/mail/roundcube.nix @@ -5,6 +5,9 @@ with lib; let cfg = config.services.roundcube; 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); in { options.services.roundcube = { @@ -44,7 +47,10 @@ in username = mkOption { type = types.str; default = "roundcube"; - description = "Username for the postgresql connection"; + description = '' + Username for the postgresql connection. + If <literal>database.host</literal> is set to <literal>localhost</literal>, a unix user and group of the same name will be created as well. + ''; }; host = mkOption { type = types.str; @@ -58,7 +64,12 @@ in }; password = mkOption { type = types.str; - description = "Password for the postgresql connection"; + description = "Password for the postgresql connection. Do not use: the password will be stored world readable in the store; use <literal>passwordFile</literal> instead."; + default = ""; + }; + passwordFile = mkOption { + type = types.str; + description = "Password file for the postgresql connection. Must be readable by user <literal>nginx</literal>. Ignored if <literal>database.host</literal> is set to <literal>localhost</literal>, as peer authentication will be used."; }; dbname = mkOption { type = types.str; @@ -75,6 +86,15 @@ in ''; }; + dicts = mkOption { + type = types.listOf types.package; + default = []; + example = literalExample "with pkgs.aspellDicts; [ en fr de ]"; + description = '' + List of aspell dictionnaries for spell checking. If empty, spell checking is disabled. + ''; + }; + extraConfig = mkOption { type = types.lines; default = ""; @@ -83,14 +103,27 @@ in }; config = mkIf cfg.enable { + # backward compatibility: if password is set but not passwordFile, make one. + services.roundcube.database.passwordFile = mkIf (!localDB && cfg.database.password != "") (mkDefault ("${pkgs.writeText "roundcube-password" cfg.database.password}")); + warnings = lib.optional (!localDB && cfg.database.password != "") "services.roundcube.database.password is deprecated and insecure; use services.roundcube.database.passwordFile instead"; + environment.etc."roundcube/config.inc.php".text = '' <?php + ${lib.optionalString (!localDB) "$password = file_get_contents('${cfg.database.passwordFile}');"} + $config = array(); - $config['db_dsnw'] = 'pgsql://${cfg.database.username}:${cfg.database.password}@${cfg.database.host}/${cfg.database.dbname}'; + $config['db_dsnw'] = 'pgsql://${cfg.database.username}${lib.optionalString (!localDB) ":' . $password . '"}@${if localDB then "unix(/run/postgresql)" else cfg.database.host}/${cfg.database.dbname}'; $config['log_driver'] = 'syslog'; $config['max_message_size'] = '25M'; $config['plugins'] = [${concatMapStringsSep "," (p: "'${p}'") cfg.plugins}]; + $config['des_key'] = file_get_contents('/var/lib/roundcube/des_key'); + $config['mime_types'] = '${pkgs.nginx}/conf/mime.types'; + $config['enable_spellcheck'] = ${if cfg.dicts == [] then "false" else "true"}; + # by default, spellchecking uses a third-party cloud services + $config['spellcheck_engine'] = 'pspell'; + $config['spellcheck_languages'] = array(${lib.concatMapStringsSep ", " (dict: let p = builtins.parseDrvName dict.shortName; in "'${p.name}' => '${dict.fullName}'") cfg.dicts}); + ${cfg.extraConfig} ''; @@ -116,12 +149,26 @@ in }; }; - services.postgresql = mkIf (cfg.database.host == "localhost") { + services.postgresql = mkIf localDB { enable = true; + ensureDatabases = [ cfg.database.dbname ]; + ensureUsers = [ { + name = cfg.database.username; + ensurePermissions = { + "DATABASE ${cfg.database.username}" = "ALL PRIVILEGES"; + }; + } ]; }; + users.users.${user} = mkIf localDB { + group = user; + isSystemUser = true; + createHome = false; + }; + users.groups.${user} = mkIf localDB {}; + services.phpfpm.pools.roundcube = { - user = "nginx"; + user = if localDB then user else "nginx"; phpOptions = '' error_log = 'stderr' log_errors = on @@ -140,12 +187,12 @@ in "pm.max_requests" = 500; "catch_workers_output" = true; }; + phpPackage = phpWithPspell; + phpEnv.ASPELL_CONF = "dict-dir ${pkgs.aspellWithDicts (_: cfg.dicts)}/lib/aspell"; }; systemd.services.phpfpm-roundcube.after = [ "roundcube-setup.service" ]; - systemd.services.roundcube-setup = let - pgSuperUser = config.services.postgresql.superUser; - in mkMerge [ + systemd.services.roundcube-setup = mkMerge [ (mkIf (cfg.database.host == "localhost") { requires = [ "postgresql.service" ]; after = [ "postgresql.service" ]; @@ -153,22 +200,31 @@ in }) { wantedBy = [ "multi-user.target" ]; - script = '' - mkdir -p /var/lib/roundcube - if [ ! -f /var/lib/roundcube/db-created ]; then - if [ "${cfg.database.host}" = "localhost" ]; then - ${pkgs.sudo}/bin/sudo -u ${pgSuperUser} psql postgres -c "create role ${cfg.database.username} with login password '${cfg.database.password}'"; - ${pkgs.sudo}/bin/sudo -u ${pgSuperUser} psql postgres -c "create database ${cfg.database.dbname} with owner ${cfg.database.username}"; - fi - PGPASSWORD="${cfg.database.password}" ${pkgs.postgresql}/bin/psql -U ${cfg.database.username} \ - -f ${cfg.package}/SQL/postgres.initial.sql \ - -h ${cfg.database.host} ${cfg.database.dbname} - touch /var/lib/roundcube/db-created + script = let + psql = "${lib.optionalString (!localDB) "PGPASSFILE=${cfg.database.passwordFile}"} ${pkgs.postgresql}/bin/psql ${lib.optionalString (!localDB) "-h ${cfg.database.host} -U ${cfg.database.username} "} ${cfg.database.dbname}"; + in + '' + version="$(${psql} -t <<< "select value from system where name = 'roundcube-version';" || true)" + if ! (grep -E '[a-zA-Z0-9]' <<< "$version"); then + ${psql} -f ${cfg.package}/SQL/postgres.initial.sql + fi + + if [ ! -f /var/lib/roundcube/des_key ]; then + base64 /dev/urandom | head -c 24 > /var/lib/roundcube/des_key; + # we need to log out everyone in case change the des_key + # from the default when upgrading from nixos 19.09 + ${psql} <<< 'TRUNCATE TABLE session;' fi - ${pkgs.php}/bin/php ${cfg.package}/bin/update.sh + ${phpWithPspell}/bin/php ${cfg.package}/bin/update.sh ''; - serviceConfig.Type = "oneshot"; + serviceConfig = { + Type = "oneshot"; + StateDirectory = "roundcube"; + User = if localDB then user else "nginx"; + # so that the des_key is not world readable + StateDirectoryMode = "0700"; + }; } ]; }; diff --git a/nixos/modules/services/mail/rspamd.nix b/nixos/modules/services/mail/rspamd.nix index 7ef23ad172622..aacdbe2aeed25 100644 --- a/nixos/modules/services/mail/rspamd.nix +++ b/nixos/modules/services/mail/rspamd.nix @@ -220,7 +220,6 @@ let in { - ###### interface options = { @@ -375,15 +374,13 @@ in # Allow users to run 'rspamc' and 'rspamadm'. environment.systemPackages = [ pkgs.rspamd ]; - users.users = singleton { - name = cfg.user; + users.users.${cfg.user} = { description = "rspamd daemon"; uid = config.ids.uids.rspamd; group = cfg.group; }; - users.groups = singleton { - name = cfg.group; + users.groups.${cfg.group} = { gid = config.ids.gids.rspamd; }; @@ -414,5 +411,6 @@ in "Socket activation never worked correctly and could at this time not be fixed and so was removed") (mkRenamedOptionModule [ "services" "rspamd" "bindSocket" ] [ "services" "rspamd" "workers" "normal" "bindSockets" ]) (mkRenamedOptionModule [ "services" "rspamd" "bindUISocket" ] [ "services" "rspamd" "workers" "controller" "bindSockets" ]) + (mkRemovedOptionModule [ "services" "rmilter" ] "Use services.rspamd.* instead to set up milter service") ]; } diff --git a/nixos/modules/services/mail/rss2email.nix b/nixos/modules/services/mail/rss2email.nix index c1e5964c4536d..7f8d2adac648a 100644 --- a/nixos/modules/services/mail/rss2email.nix +++ b/nixos/modules/services/mail/rss2email.nix @@ -91,6 +91,8 @@ in { }; }; + environment.systemPackages = with pkgs; [ rss2email ]; + services.rss2email.config.to = cfg.to; systemd.tmpfiles.rules = [ diff --git a/nixos/modules/services/mail/spamassassin.nix b/nixos/modules/services/mail/spamassassin.nix index 1fe77ce5a0c76..f6c0b9c794c9d 100644 --- a/nixos/modules/services/mail/spamassassin.nix +++ b/nixos/modules/services/mail/spamassassin.nix @@ -5,16 +5,6 @@ with lib; let cfg = config.services.spamassassin; spamassassin-local-cf = pkgs.writeText "local.cf" cfg.config; - spamassassin-init-pre = pkgs.writeText "init.pre" cfg.initPreConf; - - spamdEnv = pkgs.buildEnv { - name = "spamd-env"; - paths = []; - postBuild = '' - ln -sf ${spamassassin-init-pre} $out/init.pre - ln -sf ${spamassassin-local-cf} $out/local.cf - ''; - }; in @@ -22,10 +12,7 @@ in options = { services.spamassassin = { - enable = mkOption { - default = false; - description = "Whether to run the SpamAssassin daemon"; - }; + enable = mkEnableOption "the SpamAssassin daemon"; debug = mkOption { default = false; @@ -65,8 +52,9 @@ in }; initPreConf = mkOption { - type = types.str; + type = with types; either str path; description = "The SpamAssassin init.pre config."; + apply = val: if builtins.isPath val then val else pkgs.writeText "init.pre" val; default = '' # @@ -120,30 +108,26 @@ in }; config = mkIf cfg.enable { + environment.etc."mail/spamassassin/init.pre".source = cfg.initPreConf; + environment.etc."mail/spamassassin/local.cf".source = spamassassin-local-cf; # Allow users to run 'spamc'. + environment.systemPackages = [ pkgs.spamassassin ]; - environment = { - etc = singleton { source = spamdEnv; target = "spamassassin"; }; - systemPackages = [ pkgs.spamassassin ]; - }; - - users.users = singleton { - name = "spamd"; + users.users.spamd = { description = "Spam Assassin Daemon"; uid = config.ids.uids.spamd; group = "spamd"; }; - users.groups = singleton { - name = "spamd"; + users.groups.spamd = { gid = config.ids.gids.spamd; }; systemd.services.sa-update = { script = '' set +e - ${pkgs.su}/bin/su -s "${pkgs.bash}/bin/bash" -c "${pkgs.spamassassin}/bin/sa-update --gpghomedir=/var/lib/spamassassin/sa-update-keys/ --siteconfigpath=${spamdEnv}/" spamd + ${pkgs.su}/bin/su -s "${pkgs.bash}/bin/bash" -c "${pkgs.spamassassin}/bin/sa-update --gpghomedir=/var/lib/spamassassin/sa-update-keys/" spamd v=$? set -e @@ -174,7 +158,7 @@ in after = [ "network.target" ]; serviceConfig = { - ExecStart = "${pkgs.spamassassin}/bin/spamd ${optionalString cfg.debug "-D"} --username=spamd --groupname=spamd --siteconfigpath=${spamdEnv} --virtual-config-dir=/var/lib/spamassassin/user-%u --allow-tell --pidfile=/run/spamd.pid"; + ExecStart = "${pkgs.spamassassin}/bin/spamd ${optionalString cfg.debug "-D"} --username=spamd --groupname=spamd --virtual-config-dir=/var/lib/spamassassin/user-%u --allow-tell --pidfile=/run/spamd.pid"; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; }; @@ -185,7 +169,7 @@ in mkdir -p /var/lib/spamassassin chown spamd:spamd /var/lib/spamassassin -R set +e - ${pkgs.su}/bin/su -s "${pkgs.bash}/bin/bash" -c "${pkgs.spamassassin}/bin/sa-update --gpghomedir=/var/lib/spamassassin/sa-update-keys/ --siteconfigpath=${spamdEnv}/" spamd + ${pkgs.su}/bin/su -s "${pkgs.bash}/bin/bash" -c "${pkgs.spamassassin}/bin/sa-update --gpghomedir=/var/lib/spamassassin/sa-update-keys/" spamd v=$? set -e if [ $v -gt 1 ]; then diff --git a/nixos/modules/services/mail/sympa.nix b/nixos/modules/services/mail/sympa.nix new file mode 100644 index 0000000000000..0cad09927b2fb --- /dev/null +++ b/nixos/modules/services/mail/sympa.nix @@ -0,0 +1,594 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.sympa; + dataDir = "/var/lib/sympa"; + user = "sympa"; + group = "sympa"; + pkg = pkgs.sympa; + fqdns = attrNames cfg.domains; + usingNginx = cfg.web.enable && cfg.web.server == "nginx"; + mysqlLocal = cfg.database.createLocally && cfg.database.type == "MySQL"; + pgsqlLocal = cfg.database.createLocally && cfg.database.type == "PostgreSQL"; + + sympaSubServices = [ + "sympa-archive.service" + "sympa-bounce.service" + "sympa-bulk.service" + "sympa-task.service" + ]; + + # common for all services including wwsympa + commonServiceConfig = { + StateDirectory = "sympa"; + ProtectHome = true; + ProtectSystem = "full"; + ProtectControlGroups = true; + }; + + # wwsympa has its own service config + sympaServiceConfig = srv: { + Type = "simple"; + Restart = "always"; + ExecStart = "${pkg}/bin/${srv}.pl --foreground"; + PIDFile = "/run/sympa/${srv}.pid"; + User = user; + Group = group; + + # avoid duplicating log messageges in journal + StandardError = "null"; + } // commonServiceConfig; + + configVal = value: + if isBool value then + if value then "on" else "off" + else toString value; + configGenerator = c: concatStrings (flip mapAttrsToList c (key: val: "${key}\t${configVal val}\n")); + + mainConfig = pkgs.writeText "sympa.conf" (configGenerator cfg.settings); + robotConfig = fqdn: domain: pkgs.writeText "${fqdn}-robot.conf" (configGenerator domain.settings); + + transport = pkgs.writeText "transport.sympa" (concatStringsSep "\n" (flip map fqdns (domain: '' + ${domain} error:User unknown in recipient table + sympa@${domain} sympa:sympa@${domain} + listmaster@${domain} sympa:listmaster@${domain} + bounce@${domain} sympabounce:sympa@${domain} + abuse-feedback-report@${domain} sympabounce:sympa@${domain} + ''))); + + virtual = pkgs.writeText "virtual.sympa" (concatStringsSep "\n" (flip map fqdns (domain: '' + sympa-request@${domain} postmaster@localhost + sympa-owner@${domain} postmaster@localhost + ''))); + + listAliases = pkgs.writeText "list_aliases.tt2" '' + #--- [% list.name %]@[% list.domain %]: list transport map created at [% date %] + [% list.name %]@[% list.domain %] sympa:[% list.name %]@[% list.domain %] + [% list.name %]-request@[% list.domain %] sympa:[% list.name %]-request@[% list.domain %] + [% list.name %]-editor@[% list.domain %] sympa:[% list.name %]-editor@[% list.domain %] + #[% list.name %]-subscribe@[% list.domain %] sympa:[% list.name %]-subscribe@[%list.domain %] + [% list.name %]-unsubscribe@[% list.domain %] sympa:[% list.name %]-unsubscribe@[% list.domain %] + [% list.name %][% return_path_suffix %]@[% list.domain %] sympabounce:[% list.name %]@[% list.domain %] + ''; + + enabledFiles = filterAttrs (n: v: v.enable) cfg.settingsFile; +in +{ + + ###### interface + options.services.sympa = with types; { + + enable = mkEnableOption "Sympa mailing list manager"; + + lang = mkOption { + type = str; + default = "en_US"; + example = "cs"; + description = '' + Default Sympa language. + See <link xlink:href='https://github.com/sympa-community/sympa/tree/sympa-6.2/po/sympa' /> + for available options. + ''; + }; + + listMasters = mkOption { + type = listOf str; + example = [ "postmaster@sympa.example.org" ]; + description = '' + The list of the email addresses of the listmasters + (users authorized to perform global server commands). + ''; + }; + + mainDomain = mkOption { + type = nullOr str; + default = null; + example = "lists.example.org"; + description = '' + Main domain to be used in <filename>sympa.conf</filename>. + If <literal>null</literal>, one of the <option>services.sympa.domains</option> is chosen for you. + ''; + }; + + domains = mkOption { + type = attrsOf (submodule ({ name, config, ... }: { + options = { + webHost = mkOption { + type = nullOr str; + default = null; + example = "archive.example.org"; + description = '' + Domain part of the web interface URL (no web interface for this domain if <literal>null</literal>). + DNS record of type A (or AAAA or CNAME) has to exist with this value. + ''; + }; + webLocation = mkOption { + type = str; + default = "/"; + example = "/sympa"; + description = "URL path part of the web interface."; + }; + settings = mkOption { + type = attrsOf (oneOf [ str int bool ]); + default = {}; + example = { + default_max_list_members = 3; + }; + description = '' + The <filename>robot.conf</filename> configuration file as key value set. + See <link xlink:href='https://sympa-community.github.io/gpldoc/man/sympa.conf.5.html' /> + for list of configuration parameters. + ''; + }; + }; + + config.settings = mkIf (cfg.web.enable && config.webHost != null) { + wwsympa_url = mkDefault "https://${config.webHost}${strings.removeSuffix "/" config.webLocation}"; + }; + })); + + description = '' + Email domains handled by this instance. There have + to be MX records for keys of this attribute set. + ''; + example = literalExample '' + { + "lists.example.org" = { + webHost = "lists.example.org"; + webLocation = "/"; + }; + "sympa.example.com" = { + webHost = "example.com"; + webLocation = "/sympa"; + }; + } + ''; + }; + + database = { + type = mkOption { + type = enum [ "SQLite" "PostgreSQL" "MySQL" ]; + default = "SQLite"; + example = "MySQL"; + description = "Database engine to use."; + }; + + host = mkOption { + type = nullOr str; + default = null; + description = '' + Database host address. + + For MySQL, use <literal>localhost</literal> to connect using Unix domain socket. + + For PostgreSQL, use path to directory (e.g. <filename>/run/postgresql</filename>) + to connect using Unix domain socket located in this directory. + + Use <literal>null</literal> to fall back on Sympa default, or when using + <option>services.sympa.database.createLocally</option>. + ''; + }; + + port = mkOption { + type = nullOr port; + default = null; + description = "Database port. Use <literal>null</literal> for default port."; + }; + + name = mkOption { + type = str; + default = if cfg.database.type == "SQLite" then "${dataDir}/sympa.sqlite" else "sympa"; + defaultText = ''if database.type == "SQLite" then "${dataDir}/sympa.sqlite" else "sympa"''; + description = '' + Database name. When using SQLite this must be an absolute + path to the database file. + ''; + }; + + user = mkOption { + type = nullOr str; + default = user; + description = "Database user. The system user name is used as a default."; + }; + + passwordFile = mkOption { + type = nullOr path; + default = null; + example = "/run/keys/sympa-dbpassword"; + description = '' + A file containing the password for <option>services.sympa.database.user</option>. + ''; + }; + + createLocally = mkOption { + type = bool; + default = true; + description = "Whether to create a local database automatically."; + }; + }; + + web = { + enable = mkOption { + type = bool; + default = true; + description = "Whether to enable Sympa web interface."; + }; + + server = mkOption { + type = enum [ "nginx" "none" ]; + default = "nginx"; + description = '' + The webserver used for the Sympa web interface. Set it to `none` if you want to configure it yourself. + Further nginx configuration can be done by adapting + <option>services.nginx.virtualHosts.<replaceable>name</replaceable></option>. + ''; + }; + + https = mkOption { + type = bool; + default = true; + description = '' + Whether to use HTTPS. When nginx integration is enabled, this option forces SSL and enables ACME. + Please note that Sympa web interface always uses https links even when this option is disabled. + ''; + }; + + fcgiProcs = mkOption { + type = ints.positive; + default = 2; + description = "Number of FastCGI processes to fork."; + }; + }; + + mta = { + type = mkOption { + type = enum [ "postfix" "none" ]; + default = "postfix"; + description = '' + Mail transfer agent (MTA) integration. Use <literal>none</literal> if you want to configure it yourself. + + The <literal>postfix</literal> integration sets up local Postfix instance that will pass incoming + messages from configured domains to Sympa. You still need to configure at least outgoing message + handling using e.g. <option>services.postfix.relayHost</option>. + ''; + }; + }; + + settings = mkOption { + type = attrsOf (oneOf [ str int bool ]); + default = {}; + example = literalExample '' + { + default_home = "lists"; + viewlogs_page_size = 50; + } + ''; + description = '' + The <filename>sympa.conf</filename> configuration file as key value set. + See <link xlink:href='https://sympa-community.github.io/gpldoc/man/sympa.conf.5.html' /> + for list of configuration parameters. + ''; + }; + + settingsFile = mkOption { + type = attrsOf (submodule ({ name, config, ... }: { + options = { + enable = mkOption { + type = bool; + default = true; + description = "Whether this file should be generated. This option allows specific files to be disabled."; + }; + text = mkOption { + default = null; + type = nullOr lines; + description = "Text of the file."; + }; + source = mkOption { + type = path; + description = "Path of the source file."; + }; + }; + + config.source = mkIf (config.text != null) (mkDefault (pkgs.writeText "sympa-${baseNameOf name}" config.text)); + })); + default = {}; + example = literalExample '' + { + "list_data/lists.example.org/help" = { + text = "subject This list provides help to users"; + }; + } + ''; + description = "Set of files to be linked in <filename>${dataDir}</filename>."; + }; + }; + + ###### implementation + + config = mkIf cfg.enable { + + services.sympa.settings = (mapAttrs (_: v: mkDefault v) { + domain = if cfg.mainDomain != null then cfg.mainDomain else head fqdns; + listmaster = concatStringsSep "," cfg.listMasters; + lang = cfg.lang; + + home = "${dataDir}/list_data"; + arc_path = "${dataDir}/arc"; + bounce_path = "${dataDir}/bounce"; + + sendmail = "${pkgs.system-sendmail}/bin/sendmail"; + + db_type = cfg.database.type; + db_name = cfg.database.name; + } + // (optionalAttrs (cfg.database.host != null) { + db_host = cfg.database.host; + }) + // (optionalAttrs mysqlLocal { + db_host = "localhost"; # use unix domain socket + }) + // (optionalAttrs pgsqlLocal { + db_host = "/run/postgresql"; # use unix domain socket + }) + // (optionalAttrs (cfg.database.port != null) { + db_port = cfg.database.port; + }) + // (optionalAttrs (cfg.database.user != null) { + db_user = cfg.database.user; + }) + // (optionalAttrs (cfg.mta.type == "postfix") { + sendmail_aliases = "${dataDir}/sympa_transport"; + aliases_program = "${pkgs.postfix}/bin/postmap"; + aliases_db_type = "hash"; + }) + // (optionalAttrs cfg.web.enable { + static_content_path = "${dataDir}/static_content"; + css_path = "${dataDir}/static_content/css"; + pictures_path = "${dataDir}/static_content/pictures"; + mhonarc = "${pkgs.perlPackages.MHonArc}/bin/mhonarc"; + })); + + services.sympa.settingsFile = { + "virtual.sympa" = mkDefault { source = virtual; }; + "transport.sympa" = mkDefault { source = transport; }; + "etc/list_aliases.tt2" = mkDefault { source = listAliases; }; + } + // (flip mapAttrs' cfg.domains (fqdn: domain: + nameValuePair "etc/${fqdn}/robot.conf" (mkDefault { source = robotConfig fqdn domain; }))); + + environment = { + systemPackages = [ pkg ]; + }; + + users.users.${user} = { + description = "Sympa mailing list manager user"; + group = group; + home = dataDir; + createHome = false; + isSystemUser = true; + }; + + users.groups.${group} = {}; + + assertions = [ + { assertion = cfg.database.createLocally -> cfg.database.user == user; + message = "services.sympa.database.user must be set to ${user} if services.sympa.database.createLocally is set to true"; + } + { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null; + message = "a password cannot be specified if services.sympa.database.createLocally is set to true"; + } + ]; + + systemd.tmpfiles.rules = [ + "d ${dataDir} 0711 ${user} ${group} - -" + "d ${dataDir}/etc 0700 ${user} ${group} - -" + "d ${dataDir}/spool 0700 ${user} ${group} - -" + "d ${dataDir}/list_data 0700 ${user} ${group} - -" + "d ${dataDir}/arc 0700 ${user} ${group} - -" + "d ${dataDir}/bounce 0700 ${user} ${group} - -" + "f ${dataDir}/sympa_transport 0600 ${user} ${group} - -" + + # force-copy static_content so it's up to date with package + # set permissions for wwsympa which needs write access (...) + "R ${dataDir}/static_content - - - - -" + "C ${dataDir}/static_content 0711 ${user} ${group} - ${pkg}/var/lib/sympa/static_content" + "e ${dataDir}/static_content/* 0711 ${user} ${group} - -" + + "d /run/sympa 0755 ${user} ${group} - -" + ] + ++ (flip concatMap fqdns (fqdn: [ + "d ${dataDir}/etc/${fqdn} 0700 ${user} ${group} - -" + "d ${dataDir}/list_data/${fqdn} 0700 ${user} ${group} - -" + ])) + #++ (flip mapAttrsToList enabledFiles (k: v: + # "L+ ${dataDir}/${k} - - - - ${v.source}" + #)) + ++ (concatLists (flip mapAttrsToList enabledFiles (k: v: [ + # sympa doesn't handle symlinks well (e.g. fails to create locks) + # force-copy instead + "R ${dataDir}/${k} - - - - -" + "C ${dataDir}/${k} 0700 ${user} ${group} - ${v.source}" + ]))); + + systemd.services.sympa = { + description = "Sympa mailing list manager"; + + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = sympaSubServices; + before = sympaSubServices; + serviceConfig = sympaServiceConfig "sympa_msg"; + + preStart = '' + umask 0077 + + cp -f ${mainConfig} ${dataDir}/etc/sympa.conf + ${optionalString (cfg.database.passwordFile != null) '' + chmod u+w ${dataDir}/etc/sympa.conf + echo -n "db_passwd " >> ${dataDir}/etc/sympa.conf + cat ${cfg.database.passwordFile} >> ${dataDir}/etc/sympa.conf + ''} + + ${optionalString (cfg.mta.type == "postfix") '' + ${pkgs.postfix}/bin/postmap hash:${dataDir}/virtual.sympa + ${pkgs.postfix}/bin/postmap hash:${dataDir}/transport.sympa + ''} + ${pkg}/bin/sympa_newaliases.pl + ${pkg}/bin/sympa.pl --health_check + ''; + }; + systemd.services.sympa-archive = { + description = "Sympa mailing list manager (archiving)"; + bindsTo = [ "sympa.service" ]; + serviceConfig = sympaServiceConfig "archived"; + }; + systemd.services.sympa-bounce = { + description = "Sympa mailing list manager (bounce processing)"; + bindsTo = [ "sympa.service" ]; + serviceConfig = sympaServiceConfig "bounced"; + }; + systemd.services.sympa-bulk = { + description = "Sympa mailing list manager (message distribution)"; + bindsTo = [ "sympa.service" ]; + serviceConfig = sympaServiceConfig "bulk"; + }; + systemd.services.sympa-task = { + description = "Sympa mailing list manager (task management)"; + bindsTo = [ "sympa.service" ]; + serviceConfig = sympaServiceConfig "task_manager"; + }; + + systemd.services.wwsympa = mkIf usingNginx { + wantedBy = [ "multi-user.target" ]; + after = [ "sympa.service" ]; + serviceConfig = { + Type = "forking"; + PIDFile = "/run/sympa/wwsympa.pid"; + Restart = "always"; + ExecStart = ''${pkgs.spawn_fcgi}/bin/spawn-fcgi \ + -u ${user} \ + -g ${group} \ + -U nginx \ + -M 0600 \ + -F ${toString cfg.web.fcgiProcs} \ + -P /run/sympa/wwsympa.pid \ + -s /run/sympa/wwsympa.socket \ + -- ${pkg}/lib/sympa/cgi/wwsympa.fcgi + ''; + + } // commonServiceConfig; + }; + + services.nginx.enable = mkIf usingNginx true; + services.nginx.virtualHosts = mkIf usingNginx (let + vHosts = unique (remove null (mapAttrsToList (_k: v: v.webHost) cfg.domains)); + hostLocations = host: map (v: v.webLocation) (filter (v: v.webHost == host) (attrValues cfg.domains)); + httpsOpts = optionalAttrs cfg.web.https { forceSSL = mkDefault true; enableACME = mkDefault true; }; + in + genAttrs vHosts (host: { + locations = genAttrs (hostLocations host) (loc: { + extraConfig = '' + include ${config.services.nginx.package}/conf/fastcgi_params; + + fastcgi_pass unix:/run/sympa/wwsympa.socket; + fastcgi_split_path_info ^(${loc})(.*)$; + + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param SCRIPT_FILENAME ${pkg}/lib/sympa/cgi/wwsympa.fcgi; + ''; + }) // { + "/static-sympa/".alias = "${dataDir}/static_content/"; + }; + } // httpsOpts)); + + services.postfix = mkIf (cfg.mta.type == "postfix") { + enable = true; + recipientDelimiter = "+"; + config = { + virtual_alias_maps = [ "hash:${dataDir}/virtual.sympa" ]; + virtual_mailbox_maps = [ + "hash:${dataDir}/transport.sympa" + "hash:${dataDir}/sympa_transport" + "hash:${dataDir}/virtual.sympa" + ]; + virtual_mailbox_domains = [ "hash:${dataDir}/transport.sympa" ]; + transport_maps = [ + "hash:${dataDir}/transport.sympa" + "hash:${dataDir}/sympa_transport" + ]; + }; + masterConfig = { + "sympa" = { + type = "unix"; + privileged = true; + chroot = false; + command = "pipe"; + args = [ + "flags=hqRu" + "user=${user}" + "argv=${pkg}/libexec/queue" + "\${nexthop}" + ]; + }; + "sympabounce" = { + type = "unix"; + privileged = true; + chroot = false; + command = "pipe"; + args = [ + "flags=hqRu" + "user=${user}" + "argv=${pkg}/libexec/bouncequeue" + "\${nexthop}" + ]; + }; + }; + }; + + services.mysql = optionalAttrs mysqlLocal { + enable = true; + package = mkDefault pkgs.mariadb; + ensureDatabases = [ cfg.database.name ]; + ensureUsers = [ + { name = cfg.database.user; + ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; }; + } + ]; + }; + + services.postgresql = optionalAttrs pgsqlLocal { + enable = true; + ensureDatabases = [ cfg.database.name ]; + ensureUsers = [ + { name = cfg.database.user; + ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; }; + } + ]; + }; + + }; + + meta.maintainers = with maintainers; [ mmilata sorki ]; +} diff --git a/nixos/modules/services/misc/airsonic.nix b/nixos/modules/services/misc/airsonic.nix index c296e048cea41..5cc2ff7f4bd12 100644 --- a/nixos/modules/services/misc/airsonic.nix +++ b/nixos/modules/services/misc/airsonic.nix @@ -138,6 +138,7 @@ in { services.nginx = mkIf (cfg.virtualHost != null) { enable = true; + recommendedProxySettings = true; virtualHosts.${cfg.virtualHost} = { locations.${cfg.contextPath}.proxyPass = "http://${cfg.listenAddress}:${toString cfg.port}"; }; diff --git a/nixos/modules/services/misc/ankisyncd.nix b/nixos/modules/services/misc/ankisyncd.nix new file mode 100644 index 0000000000000..5fc19649d3d95 --- /dev/null +++ b/nixos/modules/services/misc/ankisyncd.nix @@ -0,0 +1,79 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.ankisyncd; + + name = "ankisyncd"; + + stateDir = "/var/lib/${name}"; + + authDbPath = "${stateDir}/auth.db"; + + sessionDbPath = "${stateDir}/session.db"; + + configFile = pkgs.writeText "ankisyncd.conf" (lib.generators.toINI {} { + sync_app = { + host = cfg.host; + port = cfg.port; + data_root = stateDir; + auth_db_path = authDbPath; + session_db_path = sessionDbPath; + + base_url = "/sync/"; + base_media_url = "/msync/"; + }; + }); +in + { + options.services.ankisyncd = { + enable = mkEnableOption "ankisyncd"; + + package = mkOption { + type = types.package; + default = pkgs.ankisyncd; + defaultText = literalExample "pkgs.ankisyncd"; + description = "The package to use for the ankisyncd command."; + }; + + host = mkOption { + type = types.str; + default = "localhost"; + description = "ankisyncd host"; + }; + + port = mkOption { + type = types.int; + default = 27701; + description = "ankisyncd port"; + }; + + openFirewall = mkOption { + default = false; + type = types.bool; + description = "Whether to open the firewall for the specified port."; + }; + }; + + config = mkIf cfg.enable { + networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ]; + + environment.etc."ankisyncd/ankisyncd.conf".source = configFile; + + systemd.services.ankisyncd = { + description = "ankisyncd - Anki sync server"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ cfg.package ]; + + serviceConfig = { + Type = "simple"; + DynamicUser = true; + StateDirectory = name; + ExecStart = "${cfg.package}/bin/ankisyncd"; + Restart = "always"; + }; + }; + }; + } diff --git a/nixos/modules/services/misc/apache-kafka.nix b/nixos/modules/services/misc/apache-kafka.nix index 798e902ccae42..f3a650a260f1e 100644 --- a/nixos/modules/services/misc/apache-kafka.nix +++ b/nixos/modules/services/misc/apache-kafka.nix @@ -124,14 +124,13 @@ in { environment.systemPackages = [cfg.package]; - users.users = singleton { - name = "apache-kafka"; + users.users.apache-kafka = { uid = config.ids.uids.apache-kafka; description = "Apache Kafka daemon user"; home = head cfg.logDirs; }; - systemd.tmpfiles.rules = map (logDir: "d '${logDir} 0700 apache-kafka - - -") cfg.logDirs; + systemd.tmpfiles.rules = map (logDir: "d '${logDir}' 0700 apache-kafka - - -") cfg.logDirs; systemd.services.apache-kafka = { description = "Apache Kafka Daemon"; diff --git a/nixos/modules/services/misc/autofs.nix b/nixos/modules/services/misc/autofs.nix index f1742177326af..84f49f964b127 100644 --- a/nixos/modules/services/misc/autofs.nix +++ b/nixos/modules/services/misc/autofs.nix @@ -19,6 +19,7 @@ in services.autofs = { enable = mkOption { + type = types.bool; default = false; description = '' Mount filesystems on demand. Unmount them automatically. diff --git a/nixos/modules/services/misc/autorandr.nix b/nixos/modules/services/misc/autorandr.nix index 4708e16e2a6c6..cf7fb5f78d3d5 100644 --- a/nixos/modules/services/misc/autorandr.nix +++ b/nixos/modules/services/misc/autorandr.nix @@ -48,5 +48,5 @@ in { }; - meta.maintainers = with maintainers; [ gnidorah ma27 ]; + meta.maintainers = with maintainers; [ gnidorah ]; } diff --git a/nixos/modules/services/misc/bepasty.nix b/nixos/modules/services/misc/bepasty.nix index 87d3606814459..f69832e5b2bd9 100644 --- a/nixos/modules/services/misc/bepasty.nix +++ b/nixos/modules/services/misc/bepasty.nix @@ -168,16 +168,12 @@ in }) ) cfg.servers; - users.users = [{ - uid = config.ids.uids.bepasty; - name = user; - group = group; - home = default_home; - }]; - - users.groups = [{ - name = group; - gid = config.ids.gids.bepasty; - }]; + users.users.${user} = + { uid = config.ids.uids.bepasty; + group = group; + home = default_home; + }; + + users.groups.${group}.gid = config.ids.gids.bepasty; }; } diff --git a/nixos/modules/services/misc/cgminer.nix b/nixos/modules/services/misc/cgminer.nix index b1cf5a7d11040..7635c2a0f4e95 100644 --- a/nixos/modules/services/misc/cgminer.nix +++ b/nixos/modules/services/misc/cgminer.nix @@ -31,13 +31,7 @@ in services.cgminer = { - enable = mkOption { - default = false; - description = '' - Whether to enable cgminer, an ASIC/FPGA/GPU miner for bitcoin and - litecoin. - ''; - }; + enable = mkEnableOption "cgminer, an ASIC/FPGA/GPU miner for bitcoin and litecoin"; package = mkOption { default = pkgs.cgminer; @@ -110,11 +104,12 @@ in config = mkIf config.services.cgminer.enable { - users.users = optionalAttrs (cfg.user == "cgminer") (singleton - { name = "cgminer"; + users.users = optionalAttrs (cfg.user == "cgminer") { + cgminer = { uid = config.ids.uids.cgminer; description = "Cgminer user"; - }); + }; + }; environment.systemPackages = [ cfg.package ]; diff --git a/nixos/modules/services/misc/couchpotato.nix b/nixos/modules/services/misc/couchpotato.nix index 528af486b414c..f5163cf86cf5f 100644 --- a/nixos/modules/services/misc/couchpotato.nix +++ b/nixos/modules/services/misc/couchpotato.nix @@ -29,17 +29,14 @@ in }; }; - users.users = singleton - { name = "couchpotato"; - group = "couchpotato"; + users.users.couchpotato = + { group = "couchpotato"; home = "/var/lib/couchpotato/"; description = "CouchPotato daemon user"; uid = config.ids.uids.couchpotato; }; - users.groups = singleton - { name = "couchpotato"; - gid = config.ids.gids.couchpotato; - }; + users.groups.couchpotato = + { gid = config.ids.gids.couchpotato; }; }; } diff --git a/nixos/modules/services/misc/devmon.nix b/nixos/modules/services/misc/devmon.nix index 9dc8fee2964b1..e4a3348646b1d 100644 --- a/nixos/modules/services/misc/devmon.nix +++ b/nixos/modules/services/misc/devmon.nix @@ -8,12 +8,7 @@ let in { options = { services.devmon = { - enable = mkOption { - default = false; - description = '' - Whether to enable devmon, an automatic device mounting daemon. - ''; - }; + enable = mkEnableOption "devmon, an automatic device mounting daemon"; }; }; diff --git a/nixos/modules/services/misc/dictd.nix b/nixos/modules/services/misc/dictd.nix index 8d3e294622d13..d175854d2d1ef 100644 --- a/nixos/modules/services/misc/dictd.nix +++ b/nixos/modules/services/misc/dictd.nix @@ -45,18 +45,14 @@ in # get the command line client on system path to make some use of the service environment.systemPackages = [ pkgs.dict ]; - users.users = singleton - { name = "dictd"; - group = "dictd"; + users.users.dictd = + { group = "dictd"; description = "DICT.org dictd server"; home = "${dictdb}/share/dictd"; uid = config.ids.uids.dictd; }; - users.groups = singleton - { name = "dictd"; - gid = config.ids.gids.dictd; - }; + users.groups.dictd.gid = config.ids.gids.dictd; systemd.services.dictd = { description = "DICT.org Dictionary Server"; diff --git a/nixos/modules/services/misc/disnix.nix b/nixos/modules/services/misc/disnix.nix index c21cb2afc3ca4..0776ac679088f 100644 --- a/nixos/modules/services/misc/disnix.nix +++ b/nixos/modules/services/misc/disnix.nix @@ -17,10 +17,7 @@ in services.disnix = { - enable = mkOption { - default = false; - description = "Whether to enable Disnix"; - }; + enable = mkEnableOption "Disnix"; enableMultiUser = mkOption { type = types.bool; @@ -61,10 +58,7 @@ in ++ optional cfg.useWebServiceInterface "${pkgs.dbus_java}/share/java/dbus.jar"; services.tomcat.webapps = optional cfg.useWebServiceInterface pkgs.DisnixWebService; - users.groups = singleton - { name = "disnix"; - gid = config.ids.gids.disnix; - }; + users.groups.disnix.gid = config.ids.gids.disnix; systemd.services = { disnix = mkIf cfg.enableMultiUser { diff --git a/nixos/modules/services/misc/dysnomia.nix b/nixos/modules/services/misc/dysnomia.nix index 33a6fb1526416..4b52963500d1b 100644 --- a/nixos/modules/services/misc/dysnomia.nix +++ b/nixos/modules/services/misc/dysnomia.nix @@ -177,7 +177,7 @@ in wrapper = {}; } // lib.optionalAttrs (config.services.httpd.enable) { apache-webapplication = { - documentRoot = config.services.httpd.documentRoot; + documentRoot = config.services.httpd.virtualHosts.localhost.documentRoot; }; } // lib.optionalAttrs (config.services.tomcat.axis2.enable) { axis2-webservice = {}; } // lib.optionalAttrs (config.services.ejabberd.enable) { ejabberd-dump = { diff --git a/nixos/modules/services/misc/etcd.nix b/nixos/modules/services/misc/etcd.nix index e4d5322f9b5f0..7322e1c080bef 100644 --- a/nixos/modules/services/misc/etcd.nix +++ b/nixos/modules/services/misc/etcd.nix @@ -186,8 +186,7 @@ in { environment.systemPackages = [ pkgs.etcdctl ]; - users.users = singleton { - name = "etcd"; + users.users.etcd = { uid = config.ids.uids.etcd; description = "Etcd daemon user"; home = cfg.dataDir; diff --git a/nixos/modules/services/misc/ethminer.nix b/nixos/modules/services/misc/ethminer.nix index 2958cf2144735..95afb0460fb8e 100644 --- a/nixos/modules/services/misc/ethminer.nix +++ b/nixos/modules/services/misc/ethminer.nix @@ -71,7 +71,7 @@ in maxPower = mkOption { type = types.int; - default = 115; + default = 113; description = "Miner max watt usage."; }; @@ -92,7 +92,9 @@ in serviceConfig = { DynamicUser = true; + ExecStartPre = "${pkgs.ethminer}/bin/.ethminer-wrapped --list-devices"; ExecStartPost = optional (cfg.toolkit == "cuda") "+${getBin config.boot.kernelPackages.nvidia_x11}/bin/nvidia-smi -pl ${toString cfg.maxPower}"; + Restart = "always"; }; environment = { diff --git a/nixos/modules/services/misc/exhibitor.nix b/nixos/modules/services/misc/exhibitor.nix index 74f4f671f4605..f8c79f892da36 100644 --- a/nixos/modules/services/misc/exhibitor.nix +++ b/nixos/modules/services/misc/exhibitor.nix @@ -410,8 +410,7 @@ in sed -i 's/'"$replace_what"'/'"$replace_with"'/g' ${cfg.baseDir}/zookeeper/bin/zk*.sh ''; }; - users.users = singleton { - name = "zookeeper"; + users.users.zookeeper = { uid = config.ids.uids.zookeeper; description = "Zookeeper daemon user"; home = cfg.baseDir; diff --git a/nixos/modules/services/misc/felix.nix b/nixos/modules/services/misc/felix.nix index 1c5ece8682587..21740c8c0b725 100644 --- a/nixos/modules/services/misc/felix.nix +++ b/nixos/modules/services/misc/felix.nix @@ -17,10 +17,7 @@ in services.felix = { - enable = mkOption { - default = false; - description = "Whether to enable the Apache Felix OSGi service"; - }; + enable = mkEnableOption "the Apache Felix OSGi service"; bundles = mkOption { type = types.listOf types.package; @@ -47,14 +44,10 @@ in ###### implementation config = mkIf cfg.enable { - users.groups = singleton - { name = "osgi"; - gid = config.ids.gids.osgi; - }; + users.groups.osgi.gid = config.ids.gids.osgi; - users.users = singleton - { name = "osgi"; - uid = config.ids.uids.osgi; + users.users.osgi = + { uid = config.ids.uids.osgi; description = "OSGi user"; home = "/homeless-shelter"; }; diff --git a/nixos/modules/services/misc/folding-at-home.nix b/nixos/modules/services/misc/folding-at-home.nix deleted file mode 100644 index 122c89ce0680a..0000000000000 --- a/nixos/modules/services/misc/folding-at-home.nix +++ /dev/null @@ -1,68 +0,0 @@ -{ config, lib, pkgs, ... }: -with lib; -let - stateDir = "/var/lib/foldingathome"; - cfg = config.services.foldingAtHome; - fahUser = "foldingathome"; -in { - - ###### interface - - options = { - - services.foldingAtHome = { - - enable = mkOption { - default = false; - description = '' - Whether to enable the Folding@Home to use idle CPU time. - ''; - }; - - nickname = mkOption { - default = "Anonymous"; - description = '' - A unique handle for statistics. - ''; - }; - - config = mkOption { - default = ""; - description = '' - Extra configuration. Contents will be added verbatim to the - configuration file. - ''; - }; - - }; - - }; - - ###### implementation - - config = mkIf cfg.enable { - - users.users = singleton - { name = fahUser; - uid = config.ids.uids.foldingathome; - description = "Folding@Home user"; - home = stateDir; - }; - - systemd.services.foldingathome = { - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - preStart = '' - mkdir -m 0755 -p ${stateDir} - chown ${fahUser} ${stateDir} - cp -f ${pkgs.writeText "client.cfg" cfg.config} ${stateDir}/client.cfg - ''; - script = "${pkgs.su}/bin/su -s ${pkgs.runtimeShell} ${fahUser} -c 'cd ${stateDir}; ${pkgs.foldingathome}/bin/fah6'"; - }; - - services.foldingAtHome.config = '' - [settings] - username=${cfg.nickname} - ''; - }; -} diff --git a/nixos/modules/services/misc/freeswitch.nix b/nixos/modules/services/misc/freeswitch.nix new file mode 100644 index 0000000000000..0de5ba428110a --- /dev/null +++ b/nixos/modules/services/misc/freeswitch.nix @@ -0,0 +1,103 @@ +{ config, lib, pkgs, ...}: +with lib; +let + cfg = config.services.freeswitch; + pkg = cfg.package; + configDirectory = pkgs.runCommand "freeswitch-config-d" { } '' + mkdir -p $out + cp -rT ${cfg.configTemplate} $out + chmod -R +w $out + ${concatStringsSep "\n" (mapAttrsToList (fileName: filePath: '' + mkdir -p $out/$(dirname ${fileName}) + cp ${filePath} $out/${fileName} + '') cfg.configDir)} + ''; + configPath = if cfg.enableReload + then "/etc/freeswitch" + else configDirectory; +in { + options = { + services.freeswitch = { + enable = mkEnableOption "FreeSWITCH"; + enableReload = mkOption { + default = false; + type = types.bool; + description = '' + Issue the <literal>reloadxml</literal> command to FreeSWITCH when configuration directory changes (instead of restart). + See <link xlink:href="https://freeswitch.org/confluence/display/FREESWITCH/Reloading">FreeSWITCH documentation</link> for more info. + The configuration directory is exposed at <filename>/etc/freeswitch</filename>. + See also <literal>systemd.services.*.restartIfChanged</literal>. + ''; + }; + configTemplate = mkOption { + type = types.path; + default = "${config.services.freeswitch.package}/share/freeswitch/conf/vanilla"; + defaultText = literalExample "\${config.services.freeswitch.package}/share/freeswitch/conf/vanilla"; + example = literalExample "\${config.services.freeswitch.package}/share/freeswitch/conf/minimal"; + description = '' + Configuration template to use. + See available templates in <link xlink:href="https://github.com/signalwire/freeswitch/tree/master/conf">FreeSWITCH repository</link>. + You can also set your own configuration directory. + ''; + }; + configDir = mkOption { + type = with types; attrsOf path; + default = { }; + example = literalExample '' + { + "freeswitch.xml" = ./freeswitch.xml; + "dialplan/default.xml" = pkgs.writeText "dialplan-default.xml" ''' + [xml lines] + '''; + } + ''; + description = '' + Override file in FreeSWITCH config template directory. + Each top-level attribute denotes a file path in the configuration directory, its value is the file path. + See <link xlink:href="https://freeswitch.org/confluence/display/FREESWITCH/Default+Configuration">FreeSWITCH documentation</link> for more info. + Also check available templates in <link xlink:href="https://github.com/signalwire/freeswitch/tree/master/conf">FreeSWITCH repository</link>. + ''; + }; + package = mkOption { + type = types.package; + default = pkgs.freeswitch; + defaultText = literalExample "pkgs.freeswitch"; + example = literalExample "pkgs.freeswitch"; + description = '' + FreeSWITCH package. + ''; + }; + }; + }; + config = mkIf cfg.enable { + environment.etc.freeswitch = mkIf cfg.enableReload { + source = configDirectory; + }; + systemd.services.freeswitch-config-reload = mkIf cfg.enableReload { + before = [ "freeswitch.service" ]; + wantedBy = [ "multi-user.target" ]; + restartTriggers = [ configDirectory ]; + serviceConfig = { + ExecStart = "${pkgs.systemd}/bin/systemctl try-reload-or-restart freeswitch.service"; + RemainAfterExit = true; + Type = "oneshot"; + }; + }; + systemd.services.freeswitch = { + description = "Free and open-source application server for real-time communication"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + DynamicUser = true; + StateDirectory = "freeswitch"; + ExecStart = "${pkg}/bin/freeswitch -nf \\ + -mod ${pkg}/lib/freeswitch/mod \\ + -conf ${configPath} \\ + -base /var/lib/freeswitch"; + ExecReload = "${pkg}/bin/fs_cli -x reloadxml"; + Restart = "always"; + RestartSec = "5s"; + }; + }; + }; +} diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix index c8c59fb256e86..38910a5a005d4 100644 --- a/nixos/modules/services/misc/gitea.nix +++ b/nixos/modules/services/misc/gitea.nix @@ -364,7 +364,7 @@ in ''} sed -e "s,#secretkey#,$KEY,g" \ -e "s,#dbpass#,$DBPASS,g" \ - -e "s,#jwtsecet#,$JWTSECET,g" \ + -e "s,#jwtsecret#,$JWTSECRET,g" \ -e "s,#mailerpass#,$MAILERPASSWORD,g" \ -i ${runConfig} chmod 640 ${runConfig} ${secretKey} ${jwtSecret} @@ -394,6 +394,26 @@ in WorkingDirectory = cfg.stateDir; ExecStart = "${gitea.bin}/bin/gitea web"; Restart = "always"; + + # Filesystem + ProtectHome = true; + PrivateDevices = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + ReadWritePaths = cfg.stateDir; + # Caps + CapabilityBoundingSet = ""; + NoNewPrivileges = true; + # Misc. + LockPersonality = true; + RestrictRealtime = true; + PrivateMounts = true; + PrivateUsers = true; + MemoryDenyWriteExecute = true; + SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @memlock @module @mount @obsolete @raw-io @reboot @resources @setuid @swap"; + SystemCallArchitectures = "native"; + RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6"; }; environment = { @@ -453,4 +473,5 @@ in timerConfig.OnCalendar = cfg.dump.interval; }; }; + meta.maintainers = with lib.maintainers; [ srhb ]; } diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix index 07ea9c4584371..aa95898537978 100644 --- a/nixos/modules/services/misc/gitlab.nix +++ b/nixos/modules/services/misc/gitlab.nix @@ -189,6 +189,11 @@ let in { + imports = [ + (mkRenamedOptionModule [ "services" "gitlab" "stateDir" ] [ "services" "gitlab" "statePath" ]) + (mkRemovedOptionModule [ "services" "gitlab" "satelliteDir" ] "") + ]; + options = { services.gitlab = { enable = mkOption { @@ -628,20 +633,14 @@ in { # Use postfix to send out mails. services.postfix.enable = mkDefault true; - users.users = [ - { name = cfg.user; - group = cfg.group; + users.users.${cfg.user} = + { group = cfg.group; home = "${cfg.statePath}/home"; shell = "${pkgs.bash}/bin/bash"; uid = config.ids.uids.gitlab; - } - ]; + }; - users.groups = [ - { name = cfg.group; - gid = config.ids.gids.gitlab; - } - ]; + users.groups.${cfg.group}.gid = config.ids.gids.gitlab; systemd.tmpfiles.rules = [ "d /run/gitlab 0755 ${cfg.user} ${cfg.group} -" diff --git a/nixos/modules/services/misc/gpsd.nix b/nixos/modules/services/misc/gpsd.nix index 3bfcb636a3c6c..f954249942a82 100644 --- a/nixos/modules/services/misc/gpsd.nix +++ b/nixos/modules/services/misc/gpsd.nix @@ -86,17 +86,13 @@ in config = mkIf cfg.enable { - users.users = singleton - { name = "gpsd"; - inherit uid; + users.users.gpsd = + { inherit uid; description = "gpsd daemon user"; home = "/var/empty"; }; - users.groups = singleton - { name = "gpsd"; - inherit gid; - }; + users.groups.gpsd = { inherit gid; }; systemd.services.gpsd = { description = "GPSD daemon"; diff --git a/nixos/modules/services/misc/headphones.nix b/nixos/modules/services/misc/headphones.nix index 4a77045be28e5..3ee0a4458bd0e 100644 --- a/nixos/modules/services/misc/headphones.nix +++ b/nixos/modules/services/misc/headphones.nix @@ -59,19 +59,19 @@ in config = mkIf cfg.enable { - users.users = optionalAttrs (cfg.user == name) (singleton { - name = name; - uid = config.ids.uids.headphones; - group = cfg.group; - description = "headphones user"; - home = cfg.dataDir; - createHome = true; - }); + users.users = optionalAttrs (cfg.user == name) { + ${name} = { + uid = config.ids.uids.headphones; + group = cfg.group; + description = "headphones user"; + home = cfg.dataDir; + createHome = true; + }; + }; - users.groups = optionalAttrs (cfg.group == name) (singleton { - name = name; - gid = config.ids.gids.headphones; - }); + users.groups = optionalAttrs (cfg.group == name) { + ${name}.gid = config.ids.gids.headphones; + }; systemd.services.headphones = { description = "Headphones Server"; diff --git a/nixos/modules/services/misc/home-assistant.nix b/nixos/modules/services/misc/home-assistant.nix index 74702c97f551f..86033d02bf3f2 100644 --- a/nixos/modules/services/misc/home-assistant.nix +++ b/nixos/modules/services/misc/home-assistant.nix @@ -11,6 +11,9 @@ let (recursiveUpdate defaultConfig cfg.config) else cfg.config)); configFile = pkgs.runCommand "configuration.yaml" { preferLocalBuild = true; } '' ${pkgs.remarshal}/bin/json2yaml -i ${configJSON} -o $out + # Hack to support secrets, that are encoded as custom yaml objects, + # https://www.home-assistant.io/docs/configuration/secrets/ + sed -i -e "s/'\!secret \(.*\)'/\!secret \1/" $out ''; lovelaceConfigJSON = pkgs.writeText "ui-lovelace.json" @@ -93,11 +96,28 @@ in { config = mkOption { default = null; - type = with types; nullOr attrs; + # Migrate to new option types later: https://github.com/NixOS/nixpkgs/pull/75584 + type = with lib.types; let + valueType = nullOr (oneOf [ + bool + int + float + str + (lazyAttrsOf valueType) + (listOf valueType) + ]) // { + description = "Yaml value"; + emptyValue.value = {}; + }; + in valueType; example = literalExample '' { homeassistant = { name = "Home"; + latitude = "!secret latitude"; + longitude = "!secret longitude"; + elevation = "!secret elevation"; + unit_system = "metric"; time_zone = "UTC"; }; frontend = { }; @@ -108,6 +128,8 @@ in { description = '' Your <filename>configuration.yaml</filename> as a Nix attribute set. Beware that setting this option will delete your previous <filename>configuration.yaml</filename>. + <link xlink:href="https://www.home-assistant.io/docs/configuration/secrets/">Secrets</link> + are encoded as strings as shown in the example. ''; }; @@ -242,6 +264,7 @@ in { home = cfg.configDir; createHome = true; group = "hass"; + extraGroups = [ "dialout" ]; uid = config.ids.uids.hass; }; diff --git a/nixos/modules/services/misc/ihaskell.nix b/nixos/modules/services/misc/ihaskell.nix index 11597706d0d16..684a242d7385b 100644 --- a/nixos/modules/services/misc/ihaskell.nix +++ b/nixos/modules/services/misc/ihaskell.nix @@ -15,6 +15,7 @@ in options = { services.ihaskell = { enable = mkOption { + type = types.bool; default = false; description = "Autostart an IHaskell notebook service."; }; diff --git a/nixos/modules/services/misc/mame.nix b/nixos/modules/services/misc/mame.nix new file mode 100644 index 0000000000000..c5d5e9e483719 --- /dev/null +++ b/nixos/modules/services/misc/mame.nix @@ -0,0 +1,67 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.mame; + mame = "mame${lib.optionalString pkgs.stdenv.is64bit "64"}"; +in +{ + options = { + services.mame = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to setup TUN/TAP Ethernet interface for MAME emulator. + ''; + }; + user = mkOption { + type = types.str; + description = '' + User from which you run MAME binary. + ''; + }; + hostAddr = mkOption { + type = types.str; + description = '' + IP address of the host system. Usually an address of the main network + adapter or the adapter through which you get an internet connection. + ''; + example = "192.168.31.156"; + }; + emuAddr = mkOption { + type = types.str; + description = '' + IP address of the guest system. The same you set inside guest OS under + MAME. Should be on the same subnet as <option>services.mame.hostAddr</option>. + ''; + example = "192.168.31.155"; + }; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ pkgs.mame ]; + + security.wrappers."${mame}" = { + source = "${pkgs.mame}/bin/${mame}"; + capabilities = "cap_net_admin,cap_net_raw+eip"; + }; + + systemd.services.mame = { + description = "MAME TUN/TAP Ethernet interface"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pkgs.iproute ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = "${pkgs.mame}/bin/taputil.sh -c ${cfg.user} ${cfg.emuAddr} ${cfg.hostAddr} -"; + ExecStop = "${pkgs.mame}/bin/taputil.sh -d ${cfg.user}"; + }; + }; + }; + + meta.maintainers = with lib.maintainers; [ gnidorah ]; +} diff --git a/nixos/modules/services/misc/matrix-synapse.nix b/nixos/modules/services/misc/matrix-synapse.nix index 50661b873f64c..703bc9416f886 100644 --- a/nixos/modules/services/misc/matrix-synapse.nix +++ b/nixos/modules/services/misc/matrix-synapse.nix @@ -31,7 +31,6 @@ bind_host: "${cfg.bind_host}" ''} server_name: "${cfg.server_name}" pid_file: "/run/matrix-synapse.pid" -web_client: ${boolToString cfg.web_client} ${optionalString (cfg.public_baseurl != null) '' public_baseurl: "${cfg.public_baseurl}" ''} @@ -111,6 +110,9 @@ app_service_config_files: ${builtins.toJSON cfg.app_service_config_files} ${cfg.extraConfig} ''; + + hasLocalPostgresDB = let args = cfg.database_args; in + usePostgresql && (!(args ? host) || (elem args.host [ "localhost" "127.0.0.1" "::1" ])); in { options = { services.matrix-synapse = { @@ -199,13 +201,6 @@ in { This is also the last part of your UserID. ''; }; - web_client = mkOption { - type = types.bool; - default = false; - description = '' - Whether to serve a web client from the HTTP/HTTPS root resource. - ''; - }; public_baseurl = mkOption { type = types.nullOr types.str; default = null; @@ -354,13 +349,6 @@ in { The database engine name. Can be sqlite or psycopg2. ''; }; - create_local_database = mkOption { - type = types.bool; - default = true; - description = '' - Whether to create a local database automatically. - ''; - }; database_name = mkOption { type = types.str; default = "matrix-synapse"; @@ -657,57 +645,52 @@ in { }; config = mkIf cfg.enable { - users.users = [ - { name = "matrix-synapse"; + assertions = [ + { assertion = hasLocalPostgresDB -> config.services.postgresql.enable; + message = '' + Cannot deploy matrix-synapse with a configuration for a local postgresql database + and a missing postgresql service. Since 20.03 it's mandatory to manually configure the + database (please read the thread in https://github.com/NixOS/nixpkgs/pull/80447 for + further reference). + + If you + - try to deploy a fresh synapse, you need to configure the database yourself. An example + for this can be found in <nixpkgs/nixos/tests/matrix-synapse.nix> + - update your existing matrix-synapse instance, you simply need to add `services.postgresql.enable = true` + to your configuration. + + For further information about this update, please read the release-notes of 20.03 carefully. + ''; + } + ]; + + users.users.matrix-synapse = { group = "matrix-synapse"; home = cfg.dataDir; createHome = true; shell = "${pkgs.bash}/bin/bash"; uid = config.ids.uids.matrix-synapse; - } ]; - - users.groups = [ - { name = "matrix-synapse"; - gid = config.ids.gids.matrix-synapse; - } ]; + }; - services.postgresql.enable = mkIf usePostgresql (mkDefault true); + users.groups.matrix-synapse = { + gid = config.ids.gids.matrix-synapse; + }; systemd.services.matrix-synapse = { description = "Synapse Matrix homeserver"; - after = [ "network.target" "postgresql.service" ]; + after = [ "network.target" ] ++ optional hasLocalPostgresDB "postgresql.service"; wantedBy = [ "multi-user.target" ]; preStart = '' ${cfg.package}/bin/homeserver \ --config-path ${configFile} \ --keys-directory ${cfg.dataDir} \ --generate-keys - '' + optionalString (usePostgresql && cfg.create_local_database) '' - if ! test -e "${cfg.dataDir}/db-created"; then - ${pkgs.sudo}/bin/sudo -u ${pg.superUser} \ - ${pg.package}/bin/createuser \ - --login \ - --no-createdb \ - --no-createrole \ - --encrypted \ - ${cfg.database_user} - ${pkgs.sudo}/bin/sudo -u ${pg.superUser} \ - ${pg.package}/bin/createdb \ - --owner=${cfg.database_user} \ - --encoding=UTF8 \ - --lc-collate=C \ - --lc-ctype=C \ - --template=template0 \ - ${cfg.database_name} - touch "${cfg.dataDir}/db-created" - fi ''; serviceConfig = { Type = "notify"; User = "matrix-synapse"; Group = "matrix-synapse"; WorkingDirectory = cfg.dataDir; - PermissionsStartOnly = true; ExecStart = '' ${cfg.package}/bin/homeserver \ ${ concatMapStringsSep "\n " (x: "--config-path ${x} \\") ([ configFile ] ++ cfg.extraConfigFiles) } @@ -724,6 +707,13 @@ in { The `trusted_third_party_id_servers` option as been removed in `matrix-synapse` v1.4.0 as the behavior is now obsolete. '') + (mkRemovedOptionModule [ "services" "matrix-synapse" "create_local_database" ] '' + Database configuration must be done manually. An exemplary setup is demonstrated in + <nixpkgs/nixos/tests/matrix-synapse.nix> + '') + (mkRemovedOptionModule [ "services" "matrix-synapse" "web_client" ] "") ]; + meta.doc = ./matrix-synapse.xml; + } diff --git a/nixos/doc/manual/configuration/matrix.xml b/nixos/modules/services/misc/matrix-synapse.xml index ef8d5cbda8895..2f2ac27eeb9d0 100644 --- a/nixos/doc/manual/configuration/matrix.xml +++ b/nixos/modules/services/misc/matrix-synapse.xml @@ -33,6 +33,7 @@ <link xlink:href="https://github.com/matrix-org/synapse#synapse-installation"> installation instructions of Synapse </link>. <programlisting> +{ pkgs, ... }: let fqdn = let @@ -40,26 +41,35 @@ let in join config.networking.hostName config.networking.domain; in { networking = { - hostName = "myhostname"; - domain = "example.org"; + <link linkend="opt-networking.hostName">hostName</link> = "myhostname"; + <link linkend="opt-networking.domain">domain</link> = "example.org"; }; - networking.firewall.allowedTCPPorts = [ 80 443 ]; + <link linkend="opt-networking.firewall.allowedTCPPorts">networking.firewall.allowedTCPPorts</link> = [ 80 443 ]; + + <link linkend="opt-services.postgresql.enable">services.postgresql.enable</link> = true; + <link linkend="opt-services.postgresql.initialScript">services.postgresql.initialScript</link> = pkgs.writeText "synapse-init.sql" '' + CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse'; + CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse" + TEMPLATE template0 + LC_COLLATE = "C" + LC_CTYPE = "C"; + ''; services.nginx = { - enable = true; + <link linkend="opt-services.nginx.enable">enable</link> = true; # only recommendedProxySettings and recommendedGzipSettings are strictly required, # but the rest make sense as well - recommendedTlsSettings = true; - recommendedOptimisation = true; - recommendedGzipSettings = true; - recommendedProxySettings = true; + <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; - virtualHosts = { + <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}" = { - locations."= /.well-known/matrix/server".extraConfig = + <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 @@ -68,7 +78,7 @@ in { add_header Content-Type application/json; return 200 '${builtins.toJSON server}'; ''; - locations."= /.well-known/matrix/client".extraConfig = + <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}"; }; @@ -84,34 +94,37 @@ in { # Reverse proxy for Matrix client-server and server-server communication ${fqdn} = { - enableACME = true; - forceSSL = true; + <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 Riot Web section below. - locations."/".extraConfig = '' + <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."/".extraConfig</link> = '' return 404; ''; # forward all Matrix API calls to the synapse Matrix homeserver locations."/_matrix" = { - proxyPass = "http://[::1]:8008"; # without a trailing / + <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.proxyPass">proxyPass</link> = "http://[::1]:8008"; # without a trailing / }; }; }; }; services.matrix-synapse = { - enable = true; - server_name = config.networking.domain; - listeners = [ + <link linkend="opt-services.matrix-synapse.enable">enable</link> = true; + <link linkend="opt-services.matrix-synapse.server_name">server_name</link> = config.networking.domain; + <link linkend="opt-services.matrix-synapse.listeners">listeners</link> = [ { - port = 8008; - bind_address = "::1"; - type = "http"; - tls = false; - x_forwarded = true; - resources = [ - { names = [ "client" "federation" ]; compress = false; } + <link linkend="opt-services.matrix-synapse.listeners._.port">port</link> = 8008; + <link linkend="opt-services.matrix-synapse.listeners._.bind_address">bind_address</link> = "::1"; + <link linkend="opt-services.matrix-synapse.listeners._.type">type</link> = "http"; + <link linkend="opt-services.matrix-synapse.listeners._.tls">tls</link> = false; + <link linkend="opt-services.matrix-synapse.listeners._.x_forwarded">x_forwarded</link> = true; + <link linkend="opt-services.matrix-synapse.listeners._.resources">resources</link> = [ + { + <link linkend="opt-services.matrix-synapse.listeners._.resources._.names">names</link> = [ "client" "federation" ]; + <link linkend="opt-services.matrix-synapse.listeners._.resources._.compress">compress</link> = false; + } ]; } ]; @@ -135,10 +148,10 @@ in { <para> If you want to run a server with public registration by anybody, you can - then enable <option>services.matrix-synapse.enable_registration = - true;</option>. Otherwise, or you can generate a registration secret with + then enable <literal><link linkend="opt-services.matrix-synapse.enable_registration">services.matrix-synapse.enable_registration</link> = + true;</literal>. Otherwise, or you can generate a registration secret with <command>pwgen -s 64 1</command> and set it with - <option>services.matrix-synapse.registration_shared_secret</option>. To + <option><link linkend="opt-services.matrix-synapse.registration_shared_secret">services.matrix-synapse.registration_shared_secret</link></option>. To create a new user or admin, run the following after you have set the secret and have rebuilt NixOS: <screen> @@ -154,8 +167,8 @@ Success! <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 - <option>registration_shared_secret</option> option until a better solution - for NixOS is in place. + <link linkend="opt-services.matrix-synapse.registration_shared_secret">registration_shared_secret</link> + option until a better solution for NixOS is in place. </para> </section> <section xml:id="module-services-matrix-riot-web"> @@ -177,15 +190,24 @@ Success! Matrix Now!</link> for a list of existing clients and their supported featureset. <programlisting> -services.nginx.virtualHosts."riot.${fqdn}" = { - enableACME = true; - forceSSL = true; - serverAliases = [ - "riot.${config.networking.domain}" - ]; +{ + services.nginx.virtualHosts."riot.${fqdn}" = { + <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true; + <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true; + <link linkend="opt-services.nginx.virtualHosts._name_.serverAliases">serverAliases</link> = [ + "riot.${config.networking.domain}" + ]; - root = pkgs.riot-web; -}; + <link linkend="opt-services.nginx.virtualHosts._name_.root">root</link> = pkgs.riot-web.override { + conf = { + default_server_config."m.homeserver" = { + "base_url" = "${config.networking.domain}"; + "server_name" = "${fqdn}"; + }; + }; + }; + }; +} </programlisting> </para> diff --git a/nixos/modules/services/misc/mediatomb.nix b/nixos/modules/services/misc/mediatomb.nix index 107fb57fe1c45..529f584a201e4 100644 --- a/nixos/modules/services/misc/mediatomb.nix +++ b/nixos/modules/services/misc/mediatomb.nix @@ -266,19 +266,19 @@ in { serviceConfig.User = "${cfg.user}"; }; - users.groups = optionalAttrs (cfg.group == "mediatomb") (singleton { - name = "mediatomb"; - gid = gid; - }); + users.groups = optionalAttrs (cfg.group == "mediatomb") { + mediatomb.gid = gid; + }; - users.users = optionalAttrs (cfg.user == "mediatomb") (singleton { - name = "mediatomb"; - isSystemUser = true; - group = cfg.group; - home = "${cfg.dataDir}"; - createHome = true; - description = "Mediatomb DLNA Server User"; - }); + users.users = optionalAttrs (cfg.user == "mediatomb") { + mediatomb = { + isSystemUser = true; + group = cfg.group; + home = "${cfg.dataDir}"; + createHome = true; + description = "Mediatomb DLNA Server User"; + }; + }; networking.firewall = { allowedUDPPorts = [ 1900 cfg.port ]; diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix index dcec4d4fc6cd3..0c2407e1dd2ff 100644 --- a/nixos/modules/services/misc/nix-daemon.nix +++ b/nixos/modules/services/misc/nix-daemon.nix @@ -12,8 +12,9 @@ let isNix23 = versionAtLeast nixVersion "2.3pre"; - makeNixBuildUser = nr: - { name = "nixbld${toString nr}"; + makeNixBuildUser = nr: { + name = "nixbld${toString nr}"; + value = { description = "Nix build user ${toString nr}"; /* For consistency with the setgid(2), setuid(2), and setgroups(2) @@ -23,8 +24,9 @@ let group = "nixbld"; extraGroups = [ "nixbld" ]; }; + }; - nixbldUsers = map makeNixBuildUser (range 1 cfg.nrBuildUsers); + nixbldUsers = listToAttrs (map makeNixBuildUser (range 1 cfg.nrBuildUsers)); nixConf = assert versionAtLeast nixVersion "2.2"; @@ -68,6 +70,10 @@ let in { + imports = [ + (mkRenamedOptionModule [ "nix" "useChroot" ] [ "nix" "useSandbox" ]) + (mkRenamedOptionModule [ "nix" "chrootDirs" ] [ "nix" "sandboxPaths" ]) + ]; ###### interface @@ -86,13 +92,14 @@ in maxJobs = mkOption { type = types.either types.int (types.enum ["auto"]); - default = 1; + default = "auto"; example = 64; description = '' - This option defines the maximum number of jobs that Nix will try - to build in parallel. The default is 1. You should generally - set it to the total number of logical cores in your system (e.g., 16 - for two CPUs with 4 cores each and hyper-threading). + This option defines the maximum number of jobs that Nix will try to + build in parallel. The default is auto, which means it will use all + available logical cores. It is recommend to set it to the total + number of logical cores in your system (e.g., 16 for two CPUs with 4 + cores each and hyper-threading). ''; }; @@ -276,7 +283,7 @@ in trustedBinaryCaches = mkOption { type = types.listOf types.str; default = [ ]; - example = [ http://hydra.nixos.org/ ]; + example = [ "http://hydra.nixos.org/" ]; description = '' List of binary cache URLs that non-root users can use (in addition to those specified using @@ -370,6 +377,59 @@ in If enabled (the default), checks that Nix can parse the generated nix.conf. ''; }; + + registry = mkOption { + type = types.attrsOf (types.submodule ( + let + inputAttrs = types.attrsOf (types.oneOf [types.str types.int types.bool types.package]); + in + { config, name, ... }: + { options = { + from = mkOption { + type = inputAttrs; + example = { type = "indirect"; id = "nixpkgs"; }; + description = "The flake reference to be rewritten."; + }; + to = mkOption { + type = inputAttrs; + example = { type = "github"; owner = "my-org"; repo = "my-nixpkgs"; }; + description = "The flake reference to which <option>from></option> is to be rewritten."; + }; + flake = mkOption { + type = types.unspecified; + default = null; + example = literalExample "nixpkgs"; + description = '' + The flake input to which <option>from></option> is to be rewritten. + ''; + }; + exact = mkOption { + type = types.bool; + default = true; + description = '' + Whether the <option>from</option> reference needs to match exactly. If set, + a <option>from</option> reference like <literal>nixpkgs</literal> does not + match with a reference like <literal>nixpkgs/nixos-20.03</literal>. + ''; + }; + }; + config = { + from = mkDefault { type = "indirect"; id = name; }; + to = mkIf (config.flake != null) + ({ type = "path"; + path = config.flake.outPath; + } // lib.filterAttrs + (n: v: n == "lastModified" || n == "rev" || n == "revCount" || n == "narHash") + config.flake); + }; + } + )); + default = {}; + description = '' + A system-wide flake registry. + ''; + }; + }; }; @@ -384,6 +444,11 @@ in environment.etc."nix/nix.conf".source = nixConf; + environment.etc."nix/registry.json".text = builtins.toJSON { + version = 2; + flakes = mapAttrsToList (n: v: { inherit (v) from to exact; }) cfg.registry; + }; + # List of machines for distributed Nix builds in the format # expected by build-remote.pl. environment.etc."nix/machines" = @@ -441,7 +506,7 @@ in users.users = nixbldUsers; - services.xserver.displayManager.hiddenUsers = map ({ name, ... }: name) nixbldUsers; + services.xserver.displayManager.hiddenUsers = attrNames nixbldUsers; system.activationScripts.nix = stringAfter [ "etc" "users" ] '' diff --git a/nixos/modules/services/misc/nixos-manual.nix b/nixos/modules/services/misc/nixos-manual.nix deleted file mode 100644 index 20ba3d8ef0bc1..0000000000000 --- a/nixos/modules/services/misc/nixos-manual.nix +++ /dev/null @@ -1,73 +0,0 @@ -# This module optionally starts a browser that shows the NixOS manual -# on one of the virtual consoles which is useful for the installation -# CD. - -{ config, lib, pkgs, ... }: - -with lib; - -let - cfg = config.services.nixosManual; - cfgd = config.documentation; -in - -{ - - options = { - - # TODO(@oxij): rename this to `.enable` eventually. - services.nixosManual.showManual = mkOption { - type = types.bool; - default = false; - description = '' - Whether to show the NixOS manual on one of the virtual - consoles. - ''; - }; - - services.nixosManual.ttyNumber = mkOption { - type = types.int; - default = 8; - description = '' - Virtual console on which to show the manual. - ''; - }; - - services.nixosManual.browser = mkOption { - type = types.path; - default = "${pkgs.w3m-nographics}/bin/w3m"; - description = '' - Browser used to show the manual. - ''; - }; - - }; - - - config = mkMerge [ - (mkIf cfg.showManual { - assertions = singleton { - assertion = cfgd.enable && cfgd.nixos.enable; - message = "Can't enable `services.nixosManual.showManual` without `documentation.nixos.enable`"; - }; - }) - (mkIf (cfg.showManual && cfgd.enable && cfgd.nixos.enable) { - boot.extraTTYs = [ "tty${toString cfg.ttyNumber}" ]; - - systemd.services.nixos-manual = { - description = "NixOS Manual"; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - ExecStart = "${cfg.browser} ${config.system.build.manual.manualHTMLIndex}"; - StandardInput = "tty"; - StandardOutput = "tty"; - TTYPath = "/dev/tty${toString cfg.ttyNumber}"; - TTYReset = true; - TTYVTDisallocate = true; - Restart = "always"; - }; - }; - }) - ]; - -} diff --git a/nixos/modules/services/misc/nzbget.nix b/nixos/modules/services/misc/nzbget.nix index eb7b4c05d82d0..715ec891cd689 100644 --- a/nixos/modules/services/misc/nzbget.nix +++ b/nixos/modules/services/misc/nzbget.nix @@ -27,6 +27,12 @@ let in { + imports = [ + (mkRemovedOptionModule [ "services" "misc" "nzbget" "configFile" ] "The configuration of nzbget is now managed by users through the web interface.") + (mkRemovedOptionModule [ "services" "misc" "nzbget" "dataDir" ] "The data directory for nzbget is now /var/lib/nzbget.") + (mkRemovedOptionModule [ "services" "misc" "nzbget" "openFirewall" ] "The port used by nzbget is managed through the web interface so you should adjust your firewall rules accordingly.") + ]; + # interface options = { diff --git a/nixos/modules/services/misc/octoprint.nix b/nixos/modules/services/misc/octoprint.nix index 8950010773cf0..651ed3743884b 100644 --- a/nixos/modules/services/misc/octoprint.nix +++ b/nixos/modules/services/misc/octoprint.nix @@ -86,16 +86,16 @@ in config = mkIf cfg.enable { - users.users = optionalAttrs (cfg.user == "octoprint") (singleton - { name = "octoprint"; + users.users = optionalAttrs (cfg.user == "octoprint") { + octoprint = { group = cfg.group; uid = config.ids.uids.octoprint; - }); + }; + }; - users.groups = optionalAttrs (cfg.group == "octoprint") (singleton - { name = "octoprint"; - gid = config.ids.gids.octoprint; - }); + users.groups = optionalAttrs (cfg.group == "octoprint") { + octoprint.gid = config.ids.gids.octoprint; + }; systemd.tmpfiles.rules = [ "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -" diff --git a/nixos/modules/services/misc/paperless.nix b/nixos/modules/services/misc/paperless.nix index 3985dc0b303c2..bfaf760fb8363 100644 --- a/nixos/modules/services/misc/paperless.nix +++ b/nixos/modules/services/misc/paperless.nix @@ -123,9 +123,9 @@ in config = mkIf cfg.enable { systemd.tmpfiles.rules = [ - "d '${cfg.dataDir}' - ${cfg.user} ${cfg.user} - -" + "d '${cfg.dataDir}' - ${cfg.user} ${config.users.users.${cfg.user}.group} - -" ] ++ (optional cfg.consumptionDirIsPublic - "d '${cfg.consumptionDir}' 777 ${cfg.user} ${cfg.user} - -" + "d '${cfg.consumptionDir}' 777 - - - -" # If the consumption dir is not created here, it's automatically created by # 'manage' with the default permissions. ); @@ -169,17 +169,15 @@ in }; users = optionalAttrs (cfg.user == defaultUser) { - users = [{ - name = defaultUser; + users.${defaultUser} = { group = defaultUser; uid = config.ids.uids.paperless; home = cfg.dataDir; - }]; + }; - groups = [{ - name = defaultUser; + groups.${defaultUser} = { gid = config.ids.gids.paperless; - }]; + }; }; }; } diff --git a/nixos/modules/services/misc/parsoid.nix b/nixos/modules/services/misc/parsoid.nix index c757093e5c1b2..09b7f977bfbfc 100644 --- a/nixos/modules/services/misc/parsoid.nix +++ b/nixos/modules/services/misc/parsoid.nix @@ -6,7 +6,7 @@ let cfg = config.services.parsoid; - parsoid = pkgs.nodePackages."parsoid-git://github.com/abbradar/parsoid#stable"; + parsoid = pkgs.nodePackages.parsoid; confTree = { worker_heartbeat_timeout = 300000; @@ -26,6 +26,10 @@ let in { + imports = [ + (mkRemovedOptionModule [ "services" "parsoid" "interwikis" ] "Use services.parsoid.wikis instead") + ]; + ##### interface options = { @@ -94,8 +98,29 @@ in wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; serviceConfig = { - User = "nobody"; ExecStart = "${parsoid}/lib/node_modules/parsoid/bin/server.js -c ${confFile} -n ${toString cfg.workers}"; + + DynamicUser = true; + User = "parsoid"; + Group = "parsoid"; + + CapabilityBoundingSet = ""; + NoNewPrivileges = true; + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + ProtectHostname = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + RestrictNamespaces = true; + LockPersonality = true; + #MemoryDenyWriteExecute = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + RemoveIPC = true; }; }; diff --git a/nixos/modules/services/misc/pykms.nix b/nixos/modules/services/misc/pykms.nix index e2d1254602b02..25aa27ae76733 100644 --- a/nixos/modules/services/misc/pykms.nix +++ b/nixos/modules/services/misc/pykms.nix @@ -9,6 +9,10 @@ let in { meta.maintainers = with lib.maintainers; [ peterhoeg ]; + imports = [ + (mkRemovedOptionModule [ "services" "pykms" "verbose" ] "Use services.pykms.logLevel instead") + ]; + options = { services.pykms = { enable = mkOption { diff --git a/nixos/modules/services/misc/redmine.nix b/nixos/modules/services/misc/redmine.nix index bf9a6914a4831..1febdba0c8f92 100644 --- a/nixos/modules/services/misc/redmine.nix +++ b/nixos/modules/services/misc/redmine.nix @@ -66,7 +66,7 @@ in type = types.package; default = pkgs.redmine; description = "Which Redmine package to use."; - example = "pkgs.redmine.override { ruby = pkgs.ruby_2_4; }"; + example = "pkgs.redmine.override { ruby = pkgs.ruby_2_7; }"; }; user = mkOption { @@ -132,7 +132,7 @@ in example = literalExample '' { dkuk-redmine_alex_skin = builtins.fetchurl { - url = https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip; + url = "https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip"; sha256 = "0hrin9lzyi50k4w2bd2b30vrf1i4fi1c0gyas5801wn8i7kpm9yl"; }; } @@ -146,7 +146,7 @@ in example = literalExample '' { redmine_env_auth = builtins.fetchurl { - url = https://github.com/Intera/redmine_env_auth/archive/0.6.zip; + url = "https://github.com/Intera/redmine_env_auth/archive/0.6.zip"; sha256 = "0yyr1yjd8gvvh832wdc8m3xfnhhxzk2pk3gm2psg5w9jdvd6skak"; }; } @@ -367,17 +367,17 @@ in }; - users.users = optionalAttrs (cfg.user == "redmine") (singleton - { name = "redmine"; + users.users = optionalAttrs (cfg.user == "redmine") { + redmine = { group = cfg.group; home = cfg.stateDir; uid = config.ids.uids.redmine; - }); + }; + }; - users.groups = optionalAttrs (cfg.group == "redmine") (singleton - { name = "redmine"; - gid = config.ids.gids.redmine; - }); + users.groups = optionalAttrs (cfg.group == "redmine") { + redmine.gid = config.ids.gids.redmine; + }; warnings = optional (cfg.database.password != "") ''config.services.redmine.database.password will be stored as plaintext diff --git a/nixos/modules/services/misc/ripple-data-api.nix b/nixos/modules/services/misc/ripple-data-api.nix index 042b496d35eeb..9fab462f7e3b4 100644 --- a/nixos/modules/services/misc/ripple-data-api.nix +++ b/nixos/modules/services/misc/ripple-data-api.nix @@ -185,9 +185,8 @@ in { ]; }; - users.users = singleton - { name = "ripple-data-api"; - description = "Ripple data api user"; + users.users.ripple-data-api = + { description = "Ripple data api user"; uid = config.ids.uids.ripple-data-api; }; }; diff --git a/nixos/modules/services/misc/rippled.nix b/nixos/modules/services/misc/rippled.nix index cdf61730de33a..ef34e3a779f01 100644 --- a/nixos/modules/services/misc/rippled.nix +++ b/nixos/modules/services/misc/rippled.nix @@ -406,9 +406,8 @@ in config = mkIf cfg.enable { - users.users = singleton - { name = "rippled"; - description = "Ripple server user"; + users.users.rippled = + { description = "Ripple server user"; uid = config.ids.uids.rippled; home = cfg.databasePath; createHome = true; diff --git a/nixos/modules/services/misc/rogue.nix b/nixos/modules/services/misc/rogue.nix deleted file mode 100644 index aae02e384c97f..0000000000000 --- a/nixos/modules/services/misc/rogue.nix +++ /dev/null @@ -1,62 +0,0 @@ -# Execute the game `rogue' on tty 9. Mostly used by the NixOS -# installation CD. - -{ config, lib, pkgs, ... }: - -with lib; - -let - - cfg = config.services.rogue; - -in - -{ - ###### interface - - options = { - - services.rogue.enable = mkOption { - type = types.bool; - default = false; - description = '' - Whether to enable the Rogue game on one of the virtual - consoles. - ''; - }; - - services.rogue.tty = mkOption { - type = types.str; - default = "tty9"; - description = '' - Virtual console on which to run Rogue. - ''; - }; - - }; - - - ###### implementation - - config = mkIf cfg.enable { - - boot.extraTTYs = [ cfg.tty ]; - - systemd.services.rogue = - { description = "Rogue dungeon crawling game"; - wantedBy = [ "multi-user.target" ]; - serviceConfig = - { ExecStart = "${pkgs.rogue}/bin/rogue"; - StandardInput = "tty"; - StandardOutput = "tty"; - TTYPath = "/dev/${cfg.tty}"; - TTYReset = true; - TTYVTDisallocate = true; - WorkingDirectory = "/tmp"; - Restart = "always"; - }; - }; - - }; - -} diff --git a/nixos/modules/services/misc/safeeyes.nix b/nixos/modules/services/misc/safeeyes.nix index 1a33971d92272..6ecb0d13187c1 100644 --- a/nixos/modules/services/misc/safeeyes.nix +++ b/nixos/modules/services/misc/safeeyes.nix @@ -16,10 +16,7 @@ in services.safeeyes = { - enable = mkOption { - default = false; - description = "Whether to enable the safeeyes OSGi service"; - }; + enable = mkEnableOption "the safeeyes OSGi service"; }; diff --git a/nixos/modules/services/misc/serviio.nix b/nixos/modules/services/misc/serviio.nix index 9868192724b50..0ead6a8169189 100644 --- a/nixos/modules/services/misc/serviio.nix +++ b/nixos/modules/services/misc/serviio.nix @@ -63,20 +63,15 @@ in { }; }; - users.users = [ - { - name = "serviio"; - group = "serviio"; + users.users.serviio = + { group = "serviio"; home = cfg.dataDir; description = "Serviio Media Server User"; createHome = true; isSystemUser = true; - } - ]; + }; - users.groups = [ - { name = "serviio";} - ]; + users.groups.serviio = { }; networking.firewall = { allowedTCPPorts = [ diff --git a/nixos/modules/services/misc/sickbeard.nix b/nixos/modules/services/misc/sickbeard.nix index 5cfbbe516ae1b..a32dbfa3108f9 100644 --- a/nixos/modules/services/misc/sickbeard.nix +++ b/nixos/modules/services/misc/sickbeard.nix @@ -63,19 +63,19 @@ in config = mkIf cfg.enable { - users.users = optionalAttrs (cfg.user == name) (singleton { - name = name; - uid = config.ids.uids.sickbeard; - group = cfg.group; - description = "sickbeard user"; - home = cfg.dataDir; - createHome = true; - }); + users.users = optionalAttrs (cfg.user == name) { + ${name} = { + uid = config.ids.uids.sickbeard; + group = cfg.group; + description = "sickbeard user"; + home = cfg.dataDir; + createHome = true; + }; + }; - users.groups = optionalAttrs (cfg.group == name) (singleton { - name = name; - gid = config.ids.gids.sickbeard; - }); + users.groups = optionalAttrs (cfg.group == name) { + ${name}.gid = config.ids.gids.sickbeard; + }; systemd.services.sickbeard = { description = "Sickbeard Server"; diff --git a/nixos/modules/services/misc/siproxd.nix b/nixos/modules/services/misc/siproxd.nix index dcaf73aca4482..ae7b27de8e706 100644 --- a/nixos/modules/services/misc/siproxd.nix +++ b/nixos/modules/services/misc/siproxd.nix @@ -161,8 +161,7 @@ in config = mkIf cfg.enable { - users.users = singleton { - name = "siproxyd"; + users.users.siproxyd = { uid = config.ids.uids.siproxd; }; diff --git a/nixos/modules/services/misc/sssd.nix b/nixos/modules/services/misc/sssd.nix index 6b64045dde88d..36008d257410d 100644 --- a/nixos/modules/services/misc/sssd.nix +++ b/nixos/modules/services/misc/sssd.nix @@ -88,9 +88,7 @@ in { exec ${pkgs.sssd}/bin/sss_ssh_authorizedkeys "$@" ''; }; - services.openssh.extraConfig = '' - AuthorizedKeysCommand /etc/ssh/authorized_keys_command - AuthorizedKeysCommandUser nobody - ''; + services.openssh.authorizedKeysCommand = "/etc/ssh/authorized_keys_command"; + services.openssh.authorizedKeysCommandUser = "nobody"; })]; } diff --git a/nixos/modules/services/misc/svnserve.nix b/nixos/modules/services/misc/svnserve.nix index 6292bc52b1e3a..3335ed09d40e0 100644 --- a/nixos/modules/services/misc/svnserve.nix +++ b/nixos/modules/services/misc/svnserve.nix @@ -18,6 +18,7 @@ in services.svnserve = { enable = mkOption { + type = types.bool; default = false; description = "Whether to enable svnserve to serve Subversion repositories through the SVN protocol."; }; diff --git a/nixos/modules/services/misc/synergy.nix b/nixos/modules/services/misc/synergy.nix index bfab8c534d8cb..5b7cf3ac46c3c 100644 --- a/nixos/modules/services/misc/synergy.nix +++ b/nixos/modules/services/misc/synergy.nix @@ -19,12 +19,8 @@ in # !!! All these option descriptions needs to be cleaned up. client = { - enable = mkOption { - default = false; - description = " - Whether to enable the Synergy client (receive keyboard and mouse events from a Synergy server). - "; - }; + enable = mkEnableOption "the Synergy client (receive keyboard and mouse events from a Synergy server)"; + screenName = mkOption { default = ""; description = '' @@ -47,12 +43,8 @@ in }; server = { - enable = mkOption { - default = false; - description = '' - Whether to enable the Synergy server (send keyboard and mouse events). - ''; - }; + enable = mkEnableOption "the Synergy server (send keyboard and mouse events)"; + configFile = mkOption { default = "/etc/synergy-server.conf"; description = "The Synergy server configuration file."; diff --git a/nixos/modules/services/misc/taskserver/default.nix b/nixos/modules/services/misc/taskserver/default.nix index 8a57277fafe76..a894caed1a34a 100644 --- a/nixos/modules/services/misc/taskserver/default.nix +++ b/nixos/modules/services/misc/taskserver/default.nix @@ -368,16 +368,16 @@ in { (mkIf cfg.enable { environment.systemPackages = [ nixos-taskserver ]; - users.users = optional (cfg.user == "taskd") { - name = "taskd"; - uid = config.ids.uids.taskd; - description = "Taskserver user"; - group = cfg.group; + users.users = optionalAttrs (cfg.user == "taskd") { + taskd = { + uid = config.ids.uids.taskd; + description = "Taskserver user"; + group = cfg.group; + }; }; - users.groups = optional (cfg.group == "taskd") { - name = "taskd"; - gid = config.ids.gids.taskd; + users.groups = optionalAttrs (cfg.group == "taskd") { + taskd.gid = config.ids.gids.taskd; }; services.taskserver.config = { diff --git a/nixos/modules/services/misc/tautulli.nix b/nixos/modules/services/misc/tautulli.nix index 50e450366478c..aded33629f1c2 100644 --- a/nixos/modules/services/misc/tautulli.nix +++ b/nixos/modules/services/misc/tautulli.nix @@ -6,6 +6,10 @@ let cfg = config.services.tautulli; in { + imports = [ + (mkRenamedOptionModule [ "services" "plexpy" ] [ "services" "tautulli" ]) + ]; + options = { services.tautulli = { enable = mkEnableOption "Tautulli Plex Monitor"; diff --git a/nixos/modules/services/misc/uhub.nix b/nixos/modules/services/misc/uhub.nix index 753580c3e404f..d1b388310280e 100644 --- a/nixos/modules/services/misc/uhub.nix +++ b/nixos/modules/services/misc/uhub.nix @@ -41,31 +41,31 @@ in enable = mkOption { type = types.bool; default = false; - description = "Whether to enable the uhub ADC hub."; + description = "Whether to enable the uhub ADC hub."; }; port = mkOption { type = types.int; default = 1511; - description = "TCP port to bind the hub to."; + description = "TCP port to bind the hub to."; }; address = mkOption { type = types.str; default = "any"; - description = "Address to bind the hub to."; + description = "Address to bind the hub to."; }; enableTLS = mkOption { type = types.bool; default = false; - description = "Whether to enable TLS support."; + description = "Whether to enable TLS support."; }; hubConfig = mkOption { type = types.lines; default = ""; - description = "Contents of uhub configuration file."; + description = "Contents of uhub configuration file."; }; aclConfig = mkOption { @@ -77,11 +77,11 @@ in plugins = { authSqlite = { - enable = mkOption { + enable = mkOption { type = types.bool; default = false; description = "Whether to enable the Sqlite authentication database plugin"; - }; + }; file = mkOption { type = types.path; example = "/var/db/uhub-users"; @@ -161,14 +161,8 @@ in config = mkIf cfg.enable { users = { - users = singleton { - name = "uhub"; - uid = config.ids.uids.uhub; - }; - groups = singleton { - name = "uhub"; - gid = config.ids.gids.uhub; - }; + users.uhub.uid = config.ids.uids.uhub; + groups.uhub.gid = config.ids.gids.uhub; }; systemd.services.uhub = { diff --git a/nixos/modules/services/misc/zoneminder.nix b/nixos/modules/services/misc/zoneminder.nix index d7f7324580c03..d5b3537068d34 100644 --- a/nixos/modules/services/misc/zoneminder.nix +++ b/nixos/modules/services/misc/zoneminder.nix @@ -77,6 +77,8 @@ in { `config.services.zoneminder.database.createLocally` to true. Otherwise, when set to `false` (the default), you will have to create the database and database user as well as populate the database yourself. + Additionally, you will need to run `zmupdate.pl` yourself when + upgrading to a newer version. ''; webserver = mkOption { @@ -330,6 +332,8 @@ in { ${config.services.mysql.package}/bin/mysql < ${pkg}/share/zoneminder/db/zm_create.sql touch "/var/lib/${dirName}/db-created" fi + + ${zoneminder}/bin/zmupdate.pl -nointeractive ''; serviceConfig = { User = user; diff --git a/nixos/modules/services/misc/zookeeper.nix b/nixos/modules/services/misc/zookeeper.nix index 5d91e44a199dd..f6af7c75ebae9 100644 --- a/nixos/modules/services/misc/zookeeper.nix +++ b/nixos/modules/services/misc/zookeeper.nix @@ -146,8 +146,7 @@ in { ''; }; - users.users = singleton { - name = "zookeeper"; + users.users.zookeeper = { uid = config.ids.uids.zookeeper; description = "Zookeeper daemon user"; home = cfg.dataDir; diff --git a/nixos/modules/services/monitoring/cadvisor.nix b/nixos/modules/services/monitoring/cadvisor.nix index 695a8c42e85e0..655a6934a266f 100644 --- a/nixos/modules/services/monitoring/cadvisor.nix +++ b/nixos/modules/services/monitoring/cadvisor.nix @@ -135,7 +135,6 @@ in { serviceConfig.TimeoutStartSec=300; }; - virtualisation.docker.enable = mkDefault true; }) ]; } diff --git a/nixos/modules/services/monitoring/collectd.nix b/nixos/modules/services/monitoring/collectd.nix index 731ac743b7c63..ef3663c62e048 100644 --- a/nixos/modules/services/monitoring/collectd.nix +++ b/nixos/modules/services/monitoring/collectd.nix @@ -129,9 +129,10 @@ in { }; }; - users.users = optional (cfg.user == "collectd") { - name = "collectd"; - isSystemUser = true; + users.users = optionalAttrs (cfg.user == "collectd") { + collectd = { + isSystemUser = true; + }; }; }; } diff --git a/nixos/modules/services/monitoring/datadog-agent.nix b/nixos/modules/services/monitoring/datadog-agent.nix index 02a9f316fc327..f1cb890794e15 100644 --- a/nixos/modules/services/monitoring/datadog-agent.nix +++ b/nixos/modules/services/monitoring/datadog-agent.nix @@ -22,9 +22,9 @@ let # Generate Datadog configuration files for each configured checks. # This works because check configurations have predictable paths, # and because JSON is a valid subset of YAML. - makeCheckConfigs = entries: mapAttrsToList (name: conf: { - source = pkgs.writeText "${name}-check-conf.yaml" (builtins.toJSON conf); - target = "datadog-agent/conf.d/${name}.d/conf.yaml"; + makeCheckConfigs = entries: mapAttrs' (name: conf: { + name = "datadog-agent/conf.d/${name}.d/conf.yaml"; + value.source = pkgs.writeText "${name}-check-conf.yaml" (builtins.toJSON conf); }) entries; defaultChecks = { @@ -34,10 +34,11 @@ let # Assemble all check configurations and the top-level agent # configuration. - etcfiles = with pkgs; with builtins; [{ - source = writeText "datadog.yaml" (toJSON ddConf); - target = "datadog-agent/datadog.yaml"; - }] ++ makeCheckConfigs (cfg.checks // defaultChecks); + etcfiles = with pkgs; with builtins; + { "datadog-agent/datadog.yaml" = { + source = writeText "datadog.yaml" (toJSON ddConf); + }; + } // makeCheckConfigs (cfg.checks // defaultChecks); # Apply the configured extraIntegrations to the provided agent # package. See the documentation of `dd-agent/integrations-core.nix` @@ -204,7 +205,7 @@ in { config = mkIf cfg.enable { environment.systemPackages = [ datadogPkg pkgs.sysstat pkgs.procps pkgs.iproute ]; - users.extraUsers.datadog = { + users.users.datadog = { description = "Datadog Agent User"; uid = config.ids.uids.datadog; group = "datadog"; @@ -212,7 +213,7 @@ in { createHome = true; }; - users.extraGroups.datadog.gid = config.ids.gids.datadog; + users.groups.datadog.gid = config.ids.gids.datadog; systemd.services = let makeService = attrs: recursiveUpdate { @@ -224,7 +225,7 @@ in { Restart = "always"; RestartSec = 2; }; - restartTriggers = [ datadogPkg ] ++ map (etc: etc.source) etcfiles; + restartTriggers = [ datadogPkg ] ++ map (x: x.source) (attrValues etcfiles); } attrs; in { datadog-agent = makeService { diff --git a/nixos/modules/services/monitoring/dd-agent/dd-agent.nix b/nixos/modules/services/monitoring/dd-agent/dd-agent.nix index 5ee6b092a6a47..e91717fb20547 100644 --- a/nixos/modules/services/monitoring/dd-agent/dd-agent.nix +++ b/nixos/modules/services/monitoring/dd-agent/dd-agent.nix @@ -78,37 +78,35 @@ let etcfiles = let defaultConfd = import ./dd-agent-defaults.nix; - in (map (f: { source = "${pkgs.dd-agent}/agent/conf.d-system/${f}"; - target = "dd-agent/conf.d/${f}"; - }) defaultConfd) ++ [ - { source = ddConf; - target = "dd-agent/datadog.conf"; - } - { source = diskConfig; - target = "dd-agent/conf.d/disk.yaml"; - } - { source = networkConfig; - target = "dd-agent/conf.d/network.yaml"; - } ] ++ - (optional (cfg.postgresqlConfig != null) - { source = postgresqlConfig; - target = "dd-agent/conf.d/postgres.yaml"; - }) ++ - (optional (cfg.nginxConfig != null) - { source = nginxConfig; - target = "dd-agent/conf.d/nginx.yaml"; - }) ++ - (optional (cfg.mongoConfig != null) - { source = mongoConfig; - target = "dd-agent/conf.d/mongo.yaml"; - }) ++ - (optional (cfg.processConfig != null) - { source = processConfig; - target = "dd-agent/conf.d/process.yaml"; - }) ++ - (optional (cfg.jmxConfig != null) - { source = jmxConfig; - target = "dd-agent/conf.d/jmx.yaml"; + in + listToAttrs (map (f: { + name = "dd-agent/conf.d/${f}"; + value.source = "${pkgs.dd-agent}/agent/conf.d-system/${f}"; + }) defaultConfd) // + { + "dd-agent/datadog.conf".source = ddConf; + "dd-agent/conf.d/disk.yaml".source = diskConfig; + "dd-agent/conf.d/network.yaml".source = networkConfig; + } // + (optionalAttrs (cfg.postgresqlConfig != null) + { + "dd-agent/conf.d/postgres.yaml".source = postgresqlConfig; + }) // + (optionalAttrs (cfg.nginxConfig != null) + { + "dd-agent/conf.d/nginx.yaml".source = nginxConfig; + }) // + (optionalAttrs (cfg.mongoConfig != null) + { + "dd-agent/conf.d/mongo.yaml".source = mongoConfig; + }) // + (optionalAttrs (cfg.processConfig != null) + { + "dd-agent/conf.d/process.yaml".source = processConfig; + }) // + (optionalAttrs (cfg.jmxConfig != null) + { + "dd-agent/conf.d/jmx.yaml".source = jmxConfig; }); in { diff --git a/nixos/modules/services/monitoring/fusion-inventory.nix b/nixos/modules/services/monitoring/fusion-inventory.nix index fe19ed5619542..9b65c76ce02e5 100644 --- a/nixos/modules/services/monitoring/fusion-inventory.nix +++ b/nixos/modules/services/monitoring/fusion-inventory.nix @@ -46,8 +46,7 @@ in { config = mkIf cfg.enable { - users.users = singleton { - name = "fusion-inventory"; + users.users.fusion-inventory = { description = "FusionInventory user"; isSystemUser = true; }; diff --git a/nixos/modules/services/monitoring/graphite.nix b/nixos/modules/services/monitoring/graphite.nix index f7874af3df29c..64d9d61950da1 100644 --- a/nixos/modules/services/monitoring/graphite.nix +++ b/nixos/modules/services/monitoring/graphite.nix @@ -39,8 +39,6 @@ let GRAPHITE_URL = cfg.seyren.graphiteUrl; } // cfg.seyren.extraConfig; - pagerConfig = pkgs.writeText "alarms.yaml" cfg.pager.alerts; - configDir = pkgs.buildEnv { name = "graphite-config"; paths = lists.filter (el: el != null) [ @@ -61,12 +59,10 @@ let carbonEnv = { PYTHONPATH = let - cenv = pkgs.python.buildEnv.override { - extraLibs = [ pkgs.python27Packages.carbon ]; + cenv = pkgs.python3.buildEnv.override { + extraLibs = [ pkgs.python3Packages.carbon ]; }; - cenvPack = "${cenv}/${pkgs.python.sitePackages}"; - # opt/graphite/lib contains twisted.plugins.carbon-cache - in "${cenvPack}/opt/graphite/lib:${cenvPack}"; + in "${cenv}/${pkgs.python3.sitePackages}"; GRAPHITE_ROOT = dataDir; GRAPHITE_CONF_DIR = configDir; GRAPHITE_STORAGE_DIR = dataDir; @@ -74,6 +70,10 @@ let in { + imports = [ + (mkRemovedOptionModule ["services" "graphite" "pager"] "") + ]; + ###### interface options.services.graphite = { @@ -132,7 +132,7 @@ in { finders = mkOption { description = "List of finder plugins to load."; default = []; - example = literalExample "[ pkgs.python27Packages.influxgraph ]"; + example = literalExample "[ pkgs.python3Packages.influxgraph ]"; type = types.listOf types.package; }; @@ -159,8 +159,8 @@ in { package = mkOption { description = "Package to use for graphite api."; - default = pkgs.python27Packages.graphite_api; - defaultText = "pkgs.python27Packages.graphite_api"; + default = pkgs.python3Packages.graphite_api; + defaultText = "pkgs.python3Packages.graphite_api"; type = types.package; }; @@ -344,49 +344,6 @@ in { }; }; - pager = { - enable = mkOption { - description = '' - Whether to enable graphite-pager service. For more information visit - <link xlink:href="https://github.com/seatgeek/graphite-pager"/> - ''; - default = false; - type = types.bool; - }; - - redisUrl = mkOption { - description = "Redis connection string."; - default = "redis://localhost:${toString config.services.redis.port}/"; - type = types.str; - }; - - graphiteUrl = mkOption { - description = "URL to your graphite service."; - default = "http://${cfg.web.listenAddress}:${toString cfg.web.port}"; - type = types.str; - }; - - alerts = mkOption { - description = "Alerts configuration for graphite-pager."; - default = '' - alerts: - - target: constantLine(100) - warning: 90 - critical: 200 - name: Test - ''; - example = '' - pushbullet_key: pushbullet_api_key - alerts: - - target: stats.seatgeek.app.deal_quality.venue_info_cache.hit - warning: .5 - critical: 1 - name: Deal quality venue cache hits - ''; - type = types.lines; - }; - }; - beacon = { enable = mkEnableOption "graphite beacon"; @@ -409,7 +366,7 @@ in { environment = carbonEnv; serviceConfig = { RuntimeDirectory = name; - ExecStart = "${pkgs.pythonPackages.twisted}/bin/twistd ${carbonOpts name}"; + ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}"; User = "graphite"; Group = "graphite"; PermissionsStartOnly = true; @@ -431,7 +388,7 @@ in { environment = carbonEnv; serviceConfig = { RuntimeDirectory = name; - ExecStart = "${pkgs.pythonPackages.twisted}/bin/twistd ${carbonOpts name}"; + ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}"; User = "graphite"; Group = "graphite"; PIDFile="/run/${name}/${name}.pid"; @@ -447,7 +404,7 @@ in { environment = carbonEnv; serviceConfig = { RuntimeDirectory = name; - ExecStart = "${pkgs.pythonPackages.twisted}/bin/twistd ${carbonOpts name}"; + ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}"; User = "graphite"; Group = "graphite"; PIDFile="/run/${name}/${name}.pid"; @@ -457,19 +414,11 @@ in { (mkIf (cfg.carbon.enableCache || cfg.carbon.enableAggregator || cfg.carbon.enableRelay) { environment.systemPackages = [ - pkgs.pythonPackages.carbon + pkgs.python3Packages.carbon ]; }) - (mkIf cfg.web.enable (let - python27' = pkgs.python27.override { - packageOverrides = self: super: { - django = self.django_1_8; - django_tagging = self.django_tagging_0_4_3; - }; - }; - pythonPackages = python27'.pkgs; - in { + (mkIf cfg.web.enable ({ systemd.services.graphiteWeb = { description = "Graphite Web Interface"; wantedBy = [ "multi-user.target" ]; @@ -477,28 +426,27 @@ in { path = [ pkgs.perl ]; environment = { PYTHONPATH = let - penv = pkgs.python.buildEnv.override { + penv = pkgs.python3.buildEnv.override { extraLibs = [ - pythonPackages.graphite-web - pythonPackages.pysqlite + pkgs.python3Packages.graphite-web ]; }; - penvPack = "${penv}/${pkgs.python.sitePackages}"; + penvPack = "${penv}/${pkgs.python3.sitePackages}"; in concatStringsSep ":" [ "${graphiteLocalSettingsDir}" - "${penvPack}/opt/graphite/webapp" "${penvPack}" # explicitly adding pycairo in path because it cannot be imported via buildEnv - "${pkgs.pythonPackages.pycairo}/${pkgs.python.sitePackages}" + "${pkgs.python3Packages.pycairo}/${pkgs.python3.sitePackages}" ]; DJANGO_SETTINGS_MODULE = "graphite.settings"; + GRAPHITE_SETTINGS_MODULE = "graphite_local_settings"; GRAPHITE_CONF_DIR = configDir; GRAPHITE_STORAGE_DIR = dataDir; LD_LIBRARY_PATH = "${pkgs.cairo.out}/lib"; }; serviceConfig = { ExecStart = '' - ${pkgs.python27Packages.waitress-django}/bin/waitress-serve-django \ + ${pkgs.python3Packages.waitress-django}/bin/waitress-serve-django \ --host=${cfg.web.listenAddress} --port=${toString cfg.web.port} ''; User = "graphite"; @@ -510,7 +458,7 @@ in { mkdir -p ${dataDir}/{whisper/,log/webapp/} chmod 0700 ${dataDir}/{whisper/,log/webapp/} - ${pkgs.pythonPackages.django_1_8}/bin/django-admin.py migrate --noinput + ${pkgs.python3Packages.django}/bin/django-admin.py migrate --noinput chown -R graphite:graphite ${dataDir} @@ -518,16 +466,16 @@ in { fi # Only collect static files when graphite_web changes. - if ! [ "${dataDir}/current_graphite_web" -ef "${pythonPackages.graphite-web}" ]; then + if ! [ "${dataDir}/current_graphite_web" -ef "${pkgs.python3Packages.graphite-web}" ]; then mkdir -p ${staticDir} - ${pkgs.pythonPackages.django_1_8}/bin/django-admin.py collectstatic --noinput --clear + ${pkgs.python3Packages.django}/bin/django-admin.py collectstatic --noinput --clear chown -R graphite:graphite ${staticDir} - ln -sfT "${pythonPackages.graphite-web}" "${dataDir}/current_graphite_web" + ln -sfT "${pkgs.python3Packages.graphite-web}" "${dataDir}/current_graphite_web" fi ''; }; - environment.systemPackages = [ pythonPackages.graphite-web ]; + environment.systemPackages = [ pkgs.python3Packages.graphite-web ]; })) (mkIf cfg.api.enable { @@ -537,16 +485,16 @@ in { after = [ "network.target" ]; environment = { PYTHONPATH = let - aenv = pkgs.python.buildEnv.override { - extraLibs = [ cfg.api.package pkgs.cairo pkgs.pythonPackages.cffi ] ++ cfg.api.finders; + aenv = pkgs.python3.buildEnv.override { + extraLibs = [ cfg.api.package pkgs.cairo pkgs.python3Packages.cffi ] ++ cfg.api.finders; }; - in "${aenv}/${pkgs.python.sitePackages}"; + in "${aenv}/${pkgs.python3.sitePackages}"; GRAPHITE_API_CONFIG = graphiteApiConfig; LD_LIBRARY_PATH = "${pkgs.cairo.out}/lib"; }; serviceConfig = { ExecStart = '' - ${pkgs.python27Packages.waitress}/bin/waitress-serve \ + ${pkgs.python3Packages.waitress}/bin/waitress-serve \ --host=${cfg.api.listenAddress} --port=${toString cfg.api.port} \ graphite_api.app:app ''; @@ -591,34 +539,13 @@ in { services.mongodb.enable = mkDefault true; }) - (mkIf cfg.pager.enable { - systemd.services.graphitePager = { - description = "Graphite Pager Alerting Daemon"; - wantedBy = [ "multi-user.target" ]; - after = [ "network.target" "redis.service" ]; - environment = { - REDIS_URL = cfg.pager.redisUrl; - GRAPHITE_URL = cfg.pager.graphiteUrl; - }; - serviceConfig = { - ExecStart = "${pkgs.pythonPackages.graphitepager}/bin/graphite-pager --config ${pagerConfig}"; - User = "graphite"; - Group = "graphite"; - }; - }; - - services.redis.enable = mkDefault true; - - environment.systemPackages = [ pkgs.pythonPackages.graphitepager ]; - }) - (mkIf cfg.beacon.enable { systemd.services.graphite-beacon = { description = "Grpahite Beacon Alerting Daemon"; wantedBy = [ "multi-user.target" ]; serviceConfig = { ExecStart = '' - ${pkgs.pythonPackages.graphite_beacon}/bin/graphite-beacon \ + ${pkgs.python3Packages.graphite_beacon}/bin/graphite-beacon \ --config=${pkgs.writeText "graphite-beacon.json" (builtins.toJSON cfg.beacon.config)} ''; User = "graphite"; @@ -630,10 +557,9 @@ in { (mkIf ( cfg.carbon.enableCache || cfg.carbon.enableAggregator || cfg.carbon.enableRelay || cfg.web.enable || cfg.api.enable || - cfg.seyren.enable || cfg.pager.enable || cfg.beacon.enable + cfg.seyren.enable || cfg.beacon.enable ) { - users.users = singleton { - name = "graphite"; + users.users.graphite = { uid = config.ids.uids.graphite; description = "Graphite daemon user"; home = dataDir; diff --git a/nixos/modules/services/monitoring/heapster.nix b/nixos/modules/services/monitoring/heapster.nix index 6da0831b4c5f6..0a9dfa12eaa51 100644 --- a/nixos/modules/services/monitoring/heapster.nix +++ b/nixos/modules/services/monitoring/heapster.nix @@ -49,8 +49,7 @@ in { }; }; - users.users = singleton { - name = "heapster"; + users.users.heapster = { uid = config.ids.uids.heapster; description = "Heapster user"; }; diff --git a/nixos/modules/services/monitoring/munin.nix b/nixos/modules/services/monitoring/munin.nix index 8af0650c7380d..1ebf7ee6a761c 100644 --- a/nixos/modules/services/monitoring/munin.nix +++ b/nixos/modules/services/monitoring/munin.nix @@ -317,18 +317,16 @@ in environment.systemPackages = [ pkgs.munin ]; - users.users = [{ - name = "munin"; + users.users.munin = { description = "Munin monitoring user"; group = "munin"; uid = config.ids.uids.munin; home = "/var/lib/munin"; - }]; + }; - users.groups = [{ - name = "munin"; + users.groups.munin = { gid = config.ids.gids.munin; - }]; + }; }) (mkIf nodeCfg.enable { diff --git a/nixos/modules/services/monitoring/nagios.nix b/nixos/modules/services/monitoring/nagios.nix index 6a3b977694620..9ac6869068f2b 100644 --- a/nixos/modules/services/monitoring/nagios.nix +++ b/nixos/modules/services/monitoring/nagios.nix @@ -8,6 +8,7 @@ let nagiosState = "/var/lib/nagios"; nagiosLogDir = "/var/log/nagios"; + urlPath = "/nagios"; nagiosObjectDefs = cfg.objectDefs; @@ -16,32 +17,39 @@ let preferLocalBuild = true; } "mkdir -p $out; ln -s $nagiosObjectDefs $out/"; - nagiosCfgFile = pkgs.writeText "nagios.cfg" - '' - # Paths for state and logs. - log_file=${nagiosLogDir}/current - log_archive_path=${nagiosLogDir}/archive - status_file=${nagiosState}/status.dat - object_cache_file=${nagiosState}/objects.cache - temp_file=${nagiosState}/nagios.tmp - lock_file=/run/nagios.lock # Not used I think. - state_retention_file=${nagiosState}/retention.dat - query_socket=${nagiosState}/nagios.qh - check_result_path=${nagiosState} - command_file=${nagiosState}/nagios.cmd - - # Configuration files. - #resource_file=resource.cfg - cfg_dir=${nagiosObjectDefsDir} - - # Uid/gid that the daemon runs under. - nagios_user=nagios - nagios_group=nagios - - # Misc. options. - illegal_macro_output_chars=`~$&|'"<> - retain_state_information=1 - ''; # " + nagiosCfgFile = let + default = { + log_file="${nagiosLogDir}/current"; + log_archive_path="${nagiosLogDir}/archive"; + status_file="${nagiosState}/status.dat"; + object_cache_file="${nagiosState}/objects.cache"; + temp_file="${nagiosState}/nagios.tmp"; + lock_file="/run/nagios.lock"; + state_retention_file="${nagiosState}/retention.dat"; + query_socket="${nagiosState}/nagios.qh"; + check_result_path="${nagiosState}"; + command_file="${nagiosState}/nagios.cmd"; + cfg_dir="${nagiosObjectDefsDir}"; + nagios_user="nagios"; + nagios_group="nagios"; + illegal_macro_output_chars="`~$&|'\"<>"; + retain_state_information="1"; + }; + lines = mapAttrsToList (key: value: "${key}=${value}") (default // cfg.extraConfig); + content = concatStringsSep "\n" lines; + file = pkgs.writeText "nagios.cfg" content; + validated = pkgs.runCommand "nagios-checked.cfg" {preferLocalBuild=true;} '' + cp ${file} nagios.cfg + # nagios checks the existence of /var/lib/nagios, but + # it does not exists in the build sandbox, so we fake it + mkdir lib + lib=$(readlink -f lib) + sed -i s@=${nagiosState}@=$lib@ nagios.cfg + ${pkgs.nagios}/bin/nagios -v nagios.cfg && cp ${file} $out + ''; + defaultCfgFile = if cfg.validateConfig then validated else file; + in + if cfg.mainConfigFile == null then defaultCfgFile else cfg.mainConfigFile; # Plain configuration for the Nagios web-interface with no # authentication. @@ -49,12 +57,12 @@ let '' main_config_file=${cfg.mainConfigFile} use_authentication=0 - url_html_path=${cfg.urlPath} + url_html_path=${urlPath} ''; extraHttpdConfig = '' - ScriptAlias ${cfg.urlPath}/cgi-bin ${pkgs.nagios}/sbin + ScriptAlias ${urlPath}/cgi-bin ${pkgs.nagios}/sbin <Directory "${pkgs.nagios}/sbin"> Options ExecCGI @@ -62,7 +70,7 @@ let SetEnv NAGIOS_CGI_CONFIG ${cfg.cgiConfigFile} </Directory> - Alias ${cfg.urlPath} ${pkgs.nagios}/share + Alias ${urlPath} ${pkgs.nagios}/share <Directory "${pkgs.nagios}/share"> Options None @@ -72,16 +80,15 @@ let in { + imports = [ + (mkRemovedOptionModule [ "services" "nagios" "urlPath" ] "The urlPath option has been removed as it is hard coded to /nagios in the nagios package.") + ]; + + meta.maintainers = with lib.maintainers; [ symphorien ]; + options = { services.nagios = { - enable = mkOption { - default = false; - description = " - Whether to use <link - xlink:href='http://www.nagios.org/'>Nagios</link> to monitor - your system or network. - "; - }; + enable = mkEnableOption "<link xlink:href='http://www.nagios.org/'>Nagios</link> to monitor your system or network."; objectDefs = mkOption { description = " @@ -89,12 +96,14 @@ in the hosts, host groups, services and contacts for the network that you want Nagios to monitor. "; + type = types.listOf types.path; + example = literalExample "[ ./objects.cfg ]"; }; plugins = mkOption { type = types.listOf types.package; - default = [pkgs.nagiosPluginsOfficial pkgs.ssmtp]; - defaultText = "[pkgs.nagiosPluginsOfficial pkgs.ssmtp]"; + default = with pkgs; [ nagiosPluginsOfficial ssmtp mailutils ]; + defaultText = "[pkgs.nagiosPluginsOfficial pkgs.ssmtp pkgs.mailutils]"; description = " Packages to be added to the Nagios <envar>PATH</envar>. Typically used to add plugins, but can be anything. @@ -102,14 +111,29 @@ in }; mainConfigFile = mkOption { - type = types.package; - default = nagiosCfgFile; - defaultText = "nagiosCfgFile"; + type = types.nullOr types.package; + default = null; description = " - Derivation for the main configuration file of Nagios. + If non-null, overrides the main configuration file of Nagios. "; }; + extraConfig = mkOption { + type = types.attrsOf types.str; + example = { + debug_level = "-1"; + debug_file = "/var/log/nagios/debug.log"; + }; + default = {}; + description = "Configuration to add to /etc/nagios.cfg"; + }; + + validateConfig = mkOption { + type = types.bool; + default = pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform; + description = "if true, the syntax of the nagios configuration file is checked at build time"; + }; + cgiConfigFile = mkOption { type = types.package; default = nagiosCGICfgFile; @@ -121,6 +145,7 @@ in }; enableWebInterface = mkOption { + type = types.bool; default = false; description = " Whether to enable the Nagios web interface. You should also @@ -128,13 +153,20 @@ in "; }; - urlPath = mkOption { - default = "/nagios"; - description = " - The URL path under which the Nagios web interface appears. - That is, you can access the Nagios web interface through - <literal>http://<replaceable>server</replaceable>/<replaceable>urlPath</replaceable></literal>. - "; + virtualHost = mkOption { + type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix); + example = literalExample '' + { hostName = "example.org"; + adminAddr = "webmaster@example.org"; + enableSSL = true; + sslServerCert = "/var/lib/acme/example.org/full.pem"; + sslServerKey = "/var/lib/acme/example.org/key.pem"; + } + ''; + description = '' + Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>. + See <xref linkend="opt-services.httpd.virtualHosts"/> for further information. + ''; }; }; }; @@ -152,16 +184,12 @@ in # This isn't needed, it's just so that the user can type "nagiostats # -c /etc/nagios.cfg". - environment.etc = [ - { source = cfg.mainConfigFile; - target = "nagios.cfg"; - } - ]; + environment.etc."nagios.cfg".source = nagiosCfgFile; environment.systemPackages = [ pkgs.nagios ]; systemd.services.nagios = { description = "Nagios monitoring daemon"; - path = [ pkgs.nagios ]; + path = [ pkgs.nagios ] ++ cfg.plugins; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; @@ -172,16 +200,13 @@ in RestartSec = 2; LogsDirectory = "nagios"; StateDirectory = "nagios"; + ExecStart = "${pkgs.nagios}/bin/nagios /etc/nagios.cfg"; + X-ReloadIfChanged = nagiosCfgFile; }; - - script = '' - for i in ${toString cfg.plugins}; do - export PATH=$i/bin:$i/sbin:$i/libexec:$PATH - done - exec ${pkgs.nagios}/bin/nagios ${cfg.mainConfigFile} - ''; }; - services.httpd.extraConfig = optionalString cfg.enableWebInterface extraHttpdConfig; + services.httpd.virtualHosts = optionalAttrs cfg.enableWebInterface { + ${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost { extraConfig = extraHttpdConfig; } ]; + }; }; } diff --git a/nixos/modules/services/monitoring/netdata.nix b/nixos/modules/services/monitoring/netdata.nix index 3ffde8e9bce22..a5233a46e3411 100644 --- a/nixos/modules/services/monitoring/netdata.nix +++ b/nixos/modules/services/monitoring/netdata.nix @@ -9,10 +9,12 @@ let mkdir -p $out/libexec/netdata/plugins.d ln -s /run/wrappers/bin/apps.plugin $out/libexec/netdata/plugins.d/apps.plugin ln -s /run/wrappers/bin/freeipmi.plugin $out/libexec/netdata/plugins.d/freeipmi.plugin + ln -s /run/wrappers/bin/perf.plugin $out/libexec/netdata/plugins.d/perf.plugin + ln -s /run/wrappers/bin/slabinfo.plugin $out/libexec/netdata/plugins.d/slabinfo.plugin ''; plugins = [ - "${pkgs.netdata}/libexec/netdata/plugins.d" + "${cfg.package}/libexec/netdata/plugins.d" "${wrappedPlugins}/libexec/netdata/plugins.d" ] ++ cfg.extraPluginPaths; @@ -35,6 +37,13 @@ in { services.netdata = { enable = mkEnableOption "netdata"; + package = mkOption { + type = types.package; + default = pkgs.netdata; + defaultText = "pkgs.netdata"; + description = "Netdata package to use."; + }; + user = mkOption { type = types.str; default = "netdata"; @@ -141,10 +150,11 @@ in { path = (with pkgs; [ curl gawk which ]) ++ lib.optional cfg.python.enable (pkgs.python3.withPackages cfg.python.extraPackages); serviceConfig = { - Environment="PYTHONPATH=${pkgs.netdata}/libexec/netdata/python.d/python_modules"; - ExecStart = "${pkgs.netdata}/bin/netdata -P /run/netdata/netdata.pid -D -c ${configFile}"; + Environment="PYTHONPATH=${cfg.package}/libexec/netdata/python.d/python_modules"; + ExecStart = "${cfg.package}/bin/netdata -P /run/netdata/netdata.pid -D -c ${configFile}"; ExecReload = "${pkgs.utillinux}/bin/kill -s HUP -s USR1 -s USR2 $MAINPID"; TimeoutStopSec = 60; + Restart = "on-failure"; # User and group User = cfg.user; Group = cfg.group; @@ -159,7 +169,7 @@ in { systemd.enableCgroupAccounting = true; security.wrappers."apps.plugin" = { - source = "${pkgs.netdata}/libexec/netdata/plugins.d/apps.plugin.org"; + source = "${cfg.package}/libexec/netdata/plugins.d/apps.plugin.org"; capabilities = "cap_dac_read_search,cap_sys_ptrace+ep"; owner = cfg.user; group = cfg.group; @@ -167,25 +177,42 @@ in { }; security.wrappers."freeipmi.plugin" = { - source = "${pkgs.netdata}/libexec/netdata/plugins.d/freeipmi.plugin.org"; + source = "${cfg.package}/libexec/netdata/plugins.d/freeipmi.plugin.org"; capabilities = "cap_dac_override,cap_fowner+ep"; owner = cfg.user; group = cfg.group; permissions = "u+rx,g+rx,o-rwx"; }; + security.wrappers."perf.plugin" = { + source = "${cfg.package}/libexec/netdata/plugins.d/perf.plugin.org"; + capabilities = "cap_sys_admin+ep"; + owner = cfg.user; + group = cfg.group; + permissions = "u+rx,g+rx,o-rx"; + }; + + security.wrappers."slabinfo.plugin" = { + source = "${cfg.package}/libexec/netdata/plugins.d/slabinfo.plugin.org"; + capabilities = "cap_dac_override+ep"; + owner = cfg.user; + group = cfg.group; + permissions = "u+rx,g+rx,o-rx"; + }; + security.pam.loginLimits = [ { domain = "netdata"; type = "soft"; item = "nofile"; value = "10000"; } { domain = "netdata"; type = "hard"; item = "nofile"; value = "30000"; } ]; - users.users = optional (cfg.user == defaultUser) { - name = defaultUser; - isSystemUser = true; + users.users = optionalAttrs (cfg.user == defaultUser) { + ${defaultUser} = { + isSystemUser = true; + }; }; - users.groups = optional (cfg.group == defaultUser) { - name = defaultUser; + users.groups = optionalAttrs (cfg.group == defaultUser) { + ${defaultUser} = { }; }; }; diff --git a/nixos/modules/services/monitoring/prometheus/alertmanager.nix b/nixos/modules/services/monitoring/prometheus/alertmanager.nix index 11d85e9c4fc3a..1b02ebf37045f 100644 --- a/nixos/modules/services/monitoring/prometheus/alertmanager.nix +++ b/nixos/modules/services/monitoring/prometheus/alertmanager.nix @@ -18,15 +18,26 @@ let in checkedConfig yml; cmdlineArgs = cfg.extraFlags ++ [ - "--config.file ${alertmanagerYml}" + "--config.file /tmp/alert-manager-substituted.yaml" "--web.listen-address ${cfg.listenAddress}:${toString cfg.port}" "--log.level ${cfg.logLevel}" + "--storage.path /var/lib/alertmanager" + (toString (map (peer: "--cluster.peer ${peer}:9094") cfg.clusterPeers)) ] ++ (optional (cfg.webExternalUrl != null) "--web.external-url ${cfg.webExternalUrl}" ) ++ (optional (cfg.logFormat != null) "--log.format ${cfg.logFormat}" ); in { + imports = [ + (mkRemovedOptionModule [ "services" "prometheus" "alertmanager" "user" ] "The alertmanager service is now using systemd's DynamicUser mechanism which obviates a user setting.") + (mkRemovedOptionModule [ "services" "prometheus" "alertmanager" "group" ] "The alertmanager service is now using systemd's DynamicUser mechanism which obviates a group setting.") + (mkRemovedOptionModule [ "services" "prometheus" "alertmanagerURL" ] '' + Due to incompatibility, the alertmanagerURL option has been removed, + please use 'services.prometheus2.alertmanagers' instead. + '') + ]; + options = { services.prometheus.alertmanager = { enable = mkEnableOption "Prometheus Alertmanager"; @@ -111,6 +122,14 @@ in { ''; }; + clusterPeers = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Initial peers for HA cluster. + ''; + }; + extraFlags = mkOption { type = types.listOf types.str; default = []; @@ -118,6 +137,18 @@ in { Extra commandline options when launching the Alertmanager. ''; }; + + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/root/alertmanager.env"; + description = '' + File to load as environment file. Environment variables + from this file will be interpolated into the config file + using envsubst with this syntax: + <literal>$ENVIRONMENT ''${VARIABLE}</literal> + ''; + }; }; }; @@ -134,10 +165,16 @@ in { systemd.services.alertmanager = { wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; + after = [ "network-online.target" ]; + preStart = '' + ${lib.getBin pkgs.envsubst}/bin/envsubst -o "/tmp/alert-manager-substituted.yaml" \ + -i "${alertmanagerYml}" + ''; serviceConfig = { Restart = "always"; - DynamicUser = true; + StateDirectory = "alertmanager"; + DynamicUser = true; # implies PrivateTmp + EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile; WorkingDirectory = "/tmp"; ExecStart = "${cfg.package}/bin/alertmanager" + optionalString (length cmdlineArgs != 0) (" \\\n " + diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix index 191c0bff9c841..84a72afac2f76 100644 --- a/nixos/modules/services/monitoring/prometheus/default.nix +++ b/nixos/modules/services/monitoring/prometheus/default.nix @@ -9,16 +9,17 @@ let # a wrapper that verifies that the configuration is valid promtoolCheck = what: name: file: - pkgs.runCommand - "${name}-${replaceStrings [" "] [""] what}-checked" - { buildInputs = [ cfg.package ]; } '' - ln -s ${file} $out - promtool ${what} $out - ''; + if cfg.checkConfig then + pkgs.runCommandNoCCLocal + "${name}-${replaceStrings [" "] [""] what}-checked" + { buildInputs = [ cfg.package ]; } '' + ln -s ${file} $out + promtool ${what} $out + '' else file; # Pretty-print JSON to a file writePrettyJSON = name: x: - pkgs.runCommand name { preferLocalBuild = true; } '' + pkgs.runCommandNoCCLocal name {} '' echo '${builtins.toJSON x}' | ${pkgs.jq}/bin/jq . > $out ''; @@ -464,6 +465,11 @@ let }; in { + + imports = [ + (mkRenamedOptionModule [ "services" "prometheus2" ] [ "services" "prometheus" ]) + ]; + options.services.prometheus = { enable = mkOption { @@ -596,6 +602,20 @@ in { if Prometheus is served via a reverse proxy). ''; }; + + checkConfig = mkOption { + type = types.bool; + default = true; + description = '' + Check configuration with <literal>promtool + check</literal>. The call to <literal>promtool</literal> is + subject to sandboxing by Nix. When credentials are stored in + external files (<literal>password_file</literal>, + <literal>bearer_token_file</literal>, etc), they will not be + visible to <literal>promtool</literal> and it will report + errors, despite a correct configuration. + ''; + }; }; config = mkIf cfg.enable { diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix index 53f32b8fadc7b..f9ad1457fc854 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters.nix @@ -29,6 +29,7 @@ let "fritzbox" "json" "mail" + "mikrotik" "minio" "nextcloud" "nginx" @@ -160,6 +161,24 @@ let }; in { + + imports = (lib.forEach [ "blackboxExporter" "collectdExporter" "fritzboxExporter" + "jsonExporter" "minioExporter" "nginxExporter" "nodeExporter" + "snmpExporter" "unifiExporter" "varnishExporter" ] + (opt: lib.mkRemovedOptionModule [ "services" "prometheus" "${opt}" ] '' + The prometheus exporters are now configured using `services.prometheus.exporters'. + See the 18.03 release notes for more information. + '' )) + + ++ (lib.forEach [ "enable" "substitutions" "preset" ] + (opt: lib.mkRemovedOptionModule [ "fonts" "fontconfig" "ultimate" "${opt}" ] '' + The fonts.fontconfig.ultimate module and configuration is obsolete. + The repository has since been archived and activity has ceased. + https://github.com/bohoomil/fontconfig-ultimate/issues/171. + No action should be needed for font configuration, as the fonts.fontconfig + module is already used by default. + '' )); + options.services.prometheus.exporters = mkOption { type = types.submodule { options = (mkSubModules); @@ -179,13 +198,25 @@ in config = mkMerge ([{ assertions = [ { - assertion = (cfg.snmp.configurationPath == null) != (cfg.snmp.configuration == null); + assertion = cfg.snmp.enable -> ( + (cfg.snmp.configurationPath == null) != (cfg.snmp.configuration == null) + ); message = '' Please ensure you have either `services.prometheus.exporters.snmp.configuration' or `services.prometheus.exporters.snmp.configurationPath' set! ''; } { - assertion = (cfg.mail.configFile == null) != (cfg.mail.configuration == {}); + assertion = cfg.mikrotik.enable -> ( + (cfg.mikrotik.configFile == null) != (cfg.mikrotik.configuration == null) + ); + message = '' + Please specify either `services.prometheus.exporters.mikrotik.configuration' + or `services.prometheus.exporters.mikrotik.configFile'. + ''; + } { + assertion = cfg.mail.enable -> ( + (cfg.mail.configFile == null) != (cfg.mail.configuration == null) + ); message = '' Please specify either 'services.prometheus.exporters.mail.configuration' or 'services.prometheus.exporters.mail.configFile'. diff --git a/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix b/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix index 8a90afa998423..fe8d905da3fe3 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix @@ -61,7 +61,7 @@ in { ExecStart = '' ${pkgs.prometheus-blackbox-exporter}/bin/blackbox_exporter \ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ - --config.file ${adjustedConfigFile} \ + --config.file ${escapeShellArg adjustedConfigFile} \ ${concatStringsSep " \\\n " cfg.extraFlags} ''; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; diff --git a/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix b/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix index 1cc3464180918..972104630275b 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix @@ -66,7 +66,7 @@ in serviceConfig = { ExecStart = '' ${pkgs.prometheus-collectd-exporter}/bin/collectd_exporter \ - -log.format ${cfg.logFormat} \ + -log.format ${escapeShellArg cfg.logFormat} \ -log.level ${cfg.logLevel} \ -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ ${collectSettingsArgs} \ diff --git a/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix b/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix index e9fa26cb1f5ad..68afba21d64a5 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix @@ -30,7 +30,7 @@ in ${pkgs.prometheus-dnsmasq-exporter}/bin/dnsmasq_exporter \ --listen ${cfg.listenAddress}:${toString cfg.port} \ --dnsmasq ${cfg.dnsmasqListenAddress} \ - --leases_path ${cfg.leasesPath} \ + --leases_path ${escapeShellArg cfg.leasesPath} \ ${concatStringsSep " \\\n " cfg.extraFlags} ''; }; diff --git a/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix b/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix index a01074758ff81..aba3533e4395c 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix @@ -64,7 +64,7 @@ in ${pkgs.prometheus-dovecot-exporter}/bin/dovecot_exporter \ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ --web.telemetry-path ${cfg.telemetryPath} \ - --dovecot.socket-path ${cfg.socketPath} \ + --dovecot.socket-path ${escapeShellArg cfg.socketPath} \ --dovecot.scopes ${concatStringsSep "," cfg.scopes} \ ${concatStringsSep " \\\n " cfg.extraFlags} ''; diff --git a/nixos/modules/services/monitoring/prometheus/exporters/json.nix b/nixos/modules/services/monitoring/prometheus/exporters/json.nix index 82a55bafc9821..bd0026b55f721 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/json.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/json.nix @@ -27,7 +27,7 @@ in ExecStart = '' ${pkgs.prometheus-json-exporter}/bin/prometheus-json-exporter \ --port ${toString cfg.port} \ - ${cfg.url} ${cfg.configFile} \ + ${cfg.url} ${escapeShellArg cfg.configFile} \ ${concatStringsSep " \\\n " cfg.extraFlags} ''; }; diff --git a/nixos/modules/services/monitoring/prometheus/exporters/mail.nix b/nixos/modules/services/monitoring/prometheus/exporters/mail.nix index 7d8c6fb614044..18c5c4dd16234 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/mail.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/mail.nix @@ -90,7 +90,7 @@ let Timeout until mails are considered "didn't make it". ''; }; - disableFileDelition = mkOption { + disableFileDeletion = mkOption { type = types.bool; default = false; description = '' @@ -127,8 +127,8 @@ in ''; }; configuration = mkOption { - type = types.submodule exporterOptions; - default = {}; + type = types.nullOr (types.submodule exporterOptions); + default = null; description = '' Specify the mailexporter configuration file to use. ''; @@ -147,8 +147,9 @@ in ExecStart = '' ${pkgs.prometheus-mail-exporter}/bin/mailexporter \ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ + --web.telemetry-path ${cfg.telemetryPath} \ --config.file ${ - if cfg.configuration != {} then configurationFile else cfg.configFile + if cfg.configuration != null then configurationFile else (escapeShellArg cfg.configFile) } \ ${concatStringsSep " \\\n " cfg.extraFlags} ''; diff --git a/nixos/modules/services/monitoring/prometheus/exporters/mikrotik.nix b/nixos/modules/services/monitoring/prometheus/exporters/mikrotik.nix new file mode 100644 index 0000000000000..62c2cc5684764 --- /dev/null +++ b/nixos/modules/services/monitoring/prometheus/exporters/mikrotik.nix @@ -0,0 +1,66 @@ +{ config, lib, pkgs, options }: + +with lib; + +let + cfg = config.services.prometheus.exporters.mikrotik; +in +{ + port = 9436; + extraOpts = { + configFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Path to a mikrotik exporter configuration file. Mutually exclusive with + <option>configuration</option> option. + ''; + example = literalExample "./mikrotik.yml"; + }; + + configuration = mkOption { + type = types.nullOr types.attrs; + default = null; + description = '' + Mikrotik exporter configuration as nix attribute set. Mutually exclusive with + <option>configFile</option> option. + + See <link xlink:href="https://github.com/nshttpd/mikrotik-exporter/blob/master/README.md"/> + for the description of the configuration file format. + ''; + example = literalExample '' + { + devices = [ + { + name = "my_router"; + address = "10.10.0.1"; + user = "prometheus"; + password = "changeme"; + } + ]; + features = { + bgp = true; + dhcp = true; + routes = true; + optics = true; + }; + } + ''; + }; + }; + serviceOpts = let + configFile = if cfg.configFile != null + then cfg.configFile + else "${pkgs.writeText "mikrotik-exporter.yml" (builtins.toJSON cfg.configuration)}"; + in { + serviceConfig = { + # -port is misleading name, it actually accepts address too + ExecStart = '' + ${pkgs.prometheus-mikrotik-exporter}/bin/mikrotik-exporter \ + -config-file=${escapeShellArg configFile} \ + -port=${cfg.listenAddress}:${toString cfg.port} \ + ${concatStringsSep " \\\n " cfg.extraFlags} + ''; + }; + }; +} diff --git a/nixos/modules/services/monitoring/prometheus/exporters/minio.nix b/nixos/modules/services/monitoring/prometheus/exporters/minio.nix index ab3e3d7d5d501..d6dd62f871bd1 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/minio.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/minio.nix @@ -54,8 +54,8 @@ in ${pkgs.prometheus-minio-exporter}/bin/minio-exporter \ -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ -minio.server ${cfg.minioAddress} \ - -minio.access-key ${cfg.minioAccessKey} \ - -minio.access-secret ${cfg.minioAccessSecret} \ + -minio.access-key ${escapeShellArg cfg.minioAccessKey} \ + -minio.access-secret ${escapeShellArg cfg.minioAccessSecret} \ ${optionalString cfg.minioBucketStats "-minio.bucket-stats"} \ ${concatStringsSep " \\\n " cfg.extraFlags} ''; diff --git a/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix b/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix index 5f9a52053f79e..aee6bd5e66cea 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix @@ -50,7 +50,7 @@ in -u ${cfg.username} \ -t ${cfg.timeout} \ -l ${cfg.url} \ - -p @${cfg.passwordFile} \ + -p ${escapeShellArg "@${cfg.passwordFile}"} \ ${concatStringsSep " \\\n " cfg.extraFlags} ''; }; diff --git a/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix b/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix index ba852fea4336d..56cddfc55b719 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix @@ -30,7 +30,17 @@ in Whether to perform certificate verification for https. ''; }; - + constLabels = mkOption { + type = types.listOf types.str; + default = []; + example = [ + "label1=value1" + "label2=value2" + ]; + description = '' + A list of constant labels that will be used in every metric. + ''; + }; }; serviceOpts = { serviceConfig = { @@ -40,6 +50,7 @@ in --nginx.ssl-verify ${toString cfg.sslVerify} \ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ --web.telemetry-path ${cfg.telemetryPath} \ + --prometheus.const-labels ${concatStringsSep "," cfg.constLabels} \ ${concatStringsSep " \\\n " cfg.extraFlags} ''; }; diff --git a/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix b/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix index f40819e826b0d..3b6ef1631f897 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix @@ -67,15 +67,15 @@ in ${pkgs.prometheus-postfix-exporter}/bin/postfix_exporter \ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ --web.telemetry-path ${cfg.telemetryPath} \ - --postfix.showq_path ${cfg.showqPath} \ + --postfix.showq_path ${escapeShellArg cfg.showqPath} \ ${concatStringsSep " \\\n " (cfg.extraFlags ++ optional cfg.systemd.enable "--systemd.enable" ++ optional cfg.systemd.enable (if cfg.systemd.slice != null then "--systemd.slice ${cfg.systemd.slice}" else "--systemd.unit ${cfg.systemd.unit}") ++ optional (cfg.systemd.enable && (cfg.systemd.journalPath != null)) - "--systemd.jounal_path ${cfg.systemd.journalPath}" - ++ optional (!cfg.systemd.enable) "--postfix.logfile_path ${cfg.logfilePath}")} + "--systemd.journal_path ${escapeShellArg cfg.systemd.journalPath}" + ++ optional (!cfg.systemd.enable) "--postfix.logfile_path ${escapeShellArg cfg.logfilePath}")} ''; }; }; diff --git a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix index fe7ae8a8ac90a..045e48a3d0f88 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix @@ -19,7 +19,7 @@ in configuration = mkOption { type = types.nullOr types.attrs; - default = {}; + default = null; description = '' Snmp exporter configuration as nix attribute set. Mutually exclusive with 'configurationPath' option. ''; @@ -36,15 +36,15 @@ in }; logFormat = mkOption { - type = types.str; - default = "logger:stderr"; + type = types.enum ["logfmt" "json"]; + default = "logfmt"; description = '' - Set the log target and format. + Output format of log messages. ''; }; logLevel = mkOption { - type = types.enum ["debug" "info" "warn" "error" "fatal"]; + type = types.enum ["debug" "info" "warn" "error"]; default = "info"; description = '' Only log messages with the given severity or above. @@ -54,13 +54,13 @@ in serviceOpts = let configFile = if cfg.configurationPath != null then cfg.configurationPath - else "${pkgs.writeText "snmp-eporter-conf.yml" (builtins.toJSON cfg.configuration)}"; + else "${pkgs.writeText "snmp-exporter-conf.yml" (builtins.toJSON cfg.configuration)}"; in { serviceConfig = { ExecStart = '' ${pkgs.prometheus-snmp-exporter.bin}/bin/snmp_exporter \ - --config.file=${configFile} \ - --log.format=${cfg.logFormat} \ + --config.file=${escapeShellArg configFile} \ + --log.format=${escapeShellArg cfg.logFormat} \ --log.level=${cfg.logLevel} \ --web.listen-address=${cfg.listenAddress}:${toString cfg.port} \ ${concatStringsSep " \\\n " cfg.extraFlags} diff --git a/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix b/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix index 9aa0f1b85aac7..8d0e8764001c4 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix @@ -55,8 +55,8 @@ in ${pkgs.prometheus-unifi-exporter}/bin/unifi_exporter \ -telemetry.addr ${cfg.listenAddress}:${toString cfg.port} \ -unifi.addr ${cfg.unifiAddress} \ - -unifi.username ${cfg.unifiUsername} \ - -unifi.password ${cfg.unifiPassword} \ + -unifi.username ${escapeShellArg cfg.unifiUsername} \ + -unifi.password ${escapeShellArg cfg.unifiPassword} \ -unifi.timeout ${cfg.unifiTimeout} \ ${optionalString cfg.unifiInsecure "-unifi.insecure" } \ ${concatStringsSep " \\\n " cfg.extraFlags} diff --git a/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix b/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix index 12153fa021eca..5b5a6e18fcd65 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix @@ -74,10 +74,10 @@ in ${pkgs.prometheus-varnish-exporter}/bin/prometheus_varnish_exporter \ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ --web.telemetry-path ${cfg.telemetryPath} \ - --varnishstat-path ${cfg.varnishStatPath} \ + --varnishstat-path ${escapeShellArg cfg.varnishStatPath} \ ${concatStringsSep " \\\n " (cfg.extraFlags ++ optional (cfg.healthPath != null) "--web.health-path ${cfg.healthPath}" - ++ optional (cfg.instance != null) "-n ${cfg.instance}" + ++ optional (cfg.instance != null) "-n ${escapeShellArg cfg.instance}" ++ optional cfg.noExit "--no-exit" ++ optional cfg.withGoMetrics "--with-go-metrics" ++ optional cfg.verbose "--verbose" diff --git a/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix b/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix index 374f83a2939d0..04421fc2d25a8 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix @@ -59,7 +59,7 @@ in { ${optionalString cfg.verbose "-v"} \ ${optionalString cfg.singleSubnetPerField "-s"} \ ${optionalString cfg.withRemoteIp "-r"} \ - ${optionalString (cfg.wireguardConfig != null) "-n ${cfg.wireguardConfig}"} + ${optionalString (cfg.wireguardConfig != null) "-n ${escapeShellArg cfg.wireguardConfig}"} ''; }; }; diff --git a/nixos/modules/services/monitoring/prometheus/xmpp-alerts.nix b/nixos/modules/services/monitoring/prometheus/xmpp-alerts.nix new file mode 100644 index 0000000000000..44b15cb2034c2 --- /dev/null +++ b/nixos/modules/services/monitoring/prometheus/xmpp-alerts.nix @@ -0,0 +1,47 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.prometheus.xmpp-alerts; + + configFile = pkgs.writeText "prometheus-xmpp-alerts.yml" (builtins.toJSON cfg.configuration); + +in + +{ + options.services.prometheus.xmpp-alerts = { + + enable = mkEnableOption "XMPP Web hook service for Alertmanager"; + + configuration = mkOption { + type = types.attrs; + description = "Configuration as attribute set which will be converted to YAML"; + }; + + }; + + config = mkIf cfg.enable { + systemd.services.prometheus-xmpp-alerts = { + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + serviceConfig = { + ExecStart = "${pkgs.prometheus-xmpp-alerts}/bin/prometheus-xmpp-alerts --config ${configFile}"; + Restart = "on-failure"; + DynamicUser = true; + PrivateTmp = true; + PrivateDevices = true; + ProtectHome = true; + ProtectSystem = "strict"; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + NoNewPrivileges = true; + SystemCallArchitectures = "native"; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + SystemCallFilter = [ "@system-service" ]; + }; + }; + }; +} diff --git a/nixos/modules/services/monitoring/statsd.nix b/nixos/modules/services/monitoring/statsd.nix index ea155821ecc9b..30b2916a99285 100644 --- a/nixos/modules/services/monitoring/statsd.nix +++ b/nixos/modules/services/monitoring/statsd.nix @@ -125,8 +125,7 @@ in message = "Only builtin backends (graphite, console, repeater) or backends enumerated in `pkgs.nodePackages` are allowed!"; }) cfg.backends; - users.users = singleton { - name = "statsd"; + users.users.statsd = { uid = config.ids.uids.statsd; description = "Statsd daemon user"; }; diff --git a/nixos/modules/services/monitoring/sysstat.nix b/nixos/modules/services/monitoring/sysstat.nix index d668faa53cc33..ca2cff8272320 100644 --- a/nixos/modules/services/monitoring/sysstat.nix +++ b/nixos/modules/services/monitoring/sysstat.nix @@ -5,15 +5,10 @@ let in { options = { services.sysstat = { - enable = mkOption { - type = types.bool; - default = false; - description = '' - Whether to enable sar system activity collection. - ''; - }; + enable = mkEnableOption "sar system activity collection"; collect-frequency = mkOption { + type = types.str; default = "*:00/10"; description = '' OnCalendar specification for sysstat-collect @@ -21,6 +16,7 @@ in { }; collect-args = mkOption { + type = types.str; default = "1 1"; description = '' Arguments to pass sa1 when collecting statistics @@ -33,13 +29,13 @@ in { systemd.services.sysstat = { description = "Resets System Activity Logs"; wantedBy = [ "multi-user.target" ]; - preStart = "test -d /var/log/sa || mkdir -p /var/log/sa"; serviceConfig = { User = "root"; RemainAfterExit = true; Type = "oneshot"; ExecStart = "${pkgs.sysstat}/lib/sa/sa1 --boot"; + LogsDirectory = "sa"; }; }; diff --git a/nixos/modules/services/monitoring/telegraf.nix b/nixos/modules/services/monitoring/telegraf.nix index d878673266821..5d131557e8be9 100644 --- a/nixos/modules/services/monitoring/telegraf.nix +++ b/nixos/modules/services/monitoring/telegraf.nix @@ -63,10 +63,9 @@ in { }; }; - users.users = [{ - name = "telegraf"; + users.users.telegraf = { uid = config.ids.uids.telegraf; description = "telegraf daemon user"; - }]; + }; }; } diff --git a/nixos/modules/services/monitoring/tuptime.nix b/nixos/modules/services/monitoring/tuptime.nix new file mode 100644 index 0000000000000..731260a5c20af --- /dev/null +++ b/nixos/modules/services/monitoring/tuptime.nix @@ -0,0 +1,84 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.tuptime; + +in { + + options.services.tuptime = { + + enable = mkEnableOption "the total uptime service"; + + timer = { + enable = mkOption { + type = types.bool; + default = true; + description = "Whether to regularly log uptime to detect bad shutdowns."; + }; + + period = mkOption { + type = types.str; + default = "*:0/5"; + description = "systemd calendar event"; + }; + }; + }; + + + config = mkIf cfg.enable { + + environment.systemPackages = [ pkgs.tuptime ]; + + users.users.tuptime.description = "tuptime database owner"; + + systemd = { + services = { + + tuptime = { + description = "the total uptime service"; + documentation = [ "man:tuptime(1)" ]; + after = [ "time-sync.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + StateDirectory = "tuptime"; + Type = "oneshot"; + User = "tuptime"; + RemainAfterExit = true; + ExecStart = "${pkgs.tuptime}/bin/tuptime -x"; + ExecStop = "${pkgs.tuptime}/bin/tuptime -xg"; + }; + }; + + tuptime-oneshot = mkIf cfg.timer.enable { + description = "the tuptime scheduled execution unit"; + serviceConfig = { + StateDirectory = "tuptime"; + Type = "oneshot"; + User = "tuptime"; + ExecStart = "${pkgs.tuptime}/bin/tuptime -x"; + }; + }; + }; + + timers.tuptime = mkIf cfg.timer.enable { + description = "the tuptime scheduled execution timer"; + # this timer should be started if the service is started + # even if the timer was previously stopped + wantedBy = [ "tuptime.service" "timers.target" ]; + # this timer should be stopped if the service is stopped + partOf = [ "tuptime.service" ]; + timerConfig = { + OnBootSec = "1min"; + OnCalendar = cfg.timer.period; + Unit = "tuptime-oneshot.service"; + }; + }; + }; + }; + + meta.maintainers = [ maintainers.evils ]; + +} diff --git a/nixos/modules/services/monitoring/ups.nix b/nixos/modules/services/monitoring/ups.nix index 1bdc4e4410f13..a45e806d4ad86 100644 --- a/nixos/modules/services/monitoring/ups.nix +++ b/nixos/modules/services/monitoring/ups.nix @@ -214,14 +214,12 @@ in environment.NUT_STATEPATH = "/var/lib/nut/"; }; - environment.etc = [ - { source = pkgs.writeText "nut.conf" + environment.etc = { + "nut/nut.conf".source = pkgs.writeText "nut.conf" '' MODE = ${cfg.mode} ''; - target = "nut/nut.conf"; - } - { source = pkgs.writeText "ups.conf" + "nut/ups.conf".source = pkgs.writeText "ups.conf" '' maxstartdelay = ${toString cfg.maxStartDelay} @@ -229,25 +227,15 @@ in "} ''; - target = "nut/ups.conf"; - } - { source = cfg.schedulerRules; - target = "nut/upssched.conf"; - } + "nut/upssched.conf".source = cfg.schedulerRules; # These file are containing private informations and thus should not # be stored inside the Nix store. /* - { source = ; - target = "nut/upsd.conf"; - } - { source = ; - target = "nut/upsd.users"; - } - { source = ; - target = "nut/upsmon.conf; - } + "nut/upsd.conf".source = ""; + "nut/upsd.users".source = ""; + "nut/upsmon.conf".source = ""; */ - ]; + }; power.ups.schedulerRules = mkDefault "${pkgs.nut}/etc/upssched.conf.sample"; @@ -259,21 +247,16 @@ in /* - users.users = [ - { name = "nut"; - uid = 84; + users.users.nut = + { uid = 84; home = "/var/lib/nut"; createHome = true; group = "nut"; description = "UPnP A/V Media Server user"; - } - ]; - - users.groups = [ - { name = "nut"; - gid = 84; - } - ]; + }; + + users.groups."nut" = + { gid = 84; }; */ }; diff --git a/nixos/modules/services/monitoring/zabbix-server.nix b/nixos/modules/services/monitoring/zabbix-server.nix index e9f1590760a46..b4e4378ce1e76 100644 --- a/nixos/modules/services/monitoring/zabbix-server.nix +++ b/nixos/modules/services/monitoring/zabbix-server.nix @@ -44,6 +44,11 @@ let in { + imports = [ + (lib.mkRenamedOptionModule [ "services" "zabbixServer" "dbServer" ] [ "services" "zabbixServer" "database" "host" ]) + (lib.mkRemovedOptionModule [ "services" "zabbixServer" "dbPassword" ] "Use services.zabbixServer.database.passwordFile instead.") + ]; + # interface options = { diff --git a/nixos/modules/services/network-filesystems/ceph.nix b/nixos/modules/services/network-filesystems/ceph.nix index 543a7b25d5d61..d17959a6a3059 100644 --- a/nixos/modules/services/network-filesystems/ceph.nix +++ b/nixos/modules/services/network-filesystems/ceph.nix @@ -371,15 +371,14 @@ in in generators.toINI {} totalConfig; - users.users = singleton { - name = "ceph"; + users.users.ceph = { uid = config.ids.uids.ceph; description = "Ceph daemon user"; group = "ceph"; extraGroups = [ "disk" ]; }; - users.groups = singleton { - name = "ceph"; + + users.groups.ceph = { gid = config.ids.gids.ceph; }; diff --git a/nixos/modules/services/network-filesystems/davfs2.nix b/nixos/modules/services/network-filesystems/davfs2.nix index 100d458d536c0..4b6f85e4a2c97 100644 --- a/nixos/modules/services/network-filesystems/davfs2.nix +++ b/nixos/modules/services/network-filesystems/davfs2.nix @@ -57,18 +57,19 @@ in environment.systemPackages = [ pkgs.davfs2 ]; environment.etc."davfs2/davfs2.conf".source = cfgFile; - users.groups = optionalAttrs (cfg.davGroup == "davfs2") (singleton { - name = "davfs2"; - gid = config.ids.gids.davfs2; - }); + users.groups = optionalAttrs (cfg.davGroup == "davfs2") { + davfs2.gid = config.ids.gids.davfs2; + }; + + users.users = optionalAttrs (cfg.davUser == "davfs2") { + davfs2 = { + createHome = false; + group = cfg.davGroup; + uid = config.ids.uids.davfs2; + description = "davfs2 user"; + }; + }; - users.users = optionalAttrs (cfg.davUser == "davfs2") (singleton { - name = "davfs2"; - createHome = false; - group = cfg.davGroup; - uid = config.ids.uids.davfs2; - description = "davfs2 user"; - }); }; } diff --git a/nixos/modules/services/network-filesystems/drbd.nix b/nixos/modules/services/network-filesystems/drbd.nix index 4ab74ed8e1c0e..916e7eaaaa949 100644 --- a/nixos/modules/services/network-filesystems/drbd.nix +++ b/nixos/modules/services/network-filesystems/drbd.nix @@ -47,10 +47,8 @@ let cfg = config.services.drbd; in options drbd usermode_helper=/run/current-system/sw/bin/drbdadm ''; - environment.etc = singleton - { source = pkgs.writeText "drbd.conf" cfg.config; - target = "drbd.conf"; - }; + environment.etc.drbd.conf = + { source = pkgs.writeText "drbd.conf" cfg.config; }; systemd.services.drbd = { after = [ "systemd-udev.settle.service" "network.target" ]; diff --git a/nixos/modules/services/network-filesystems/kbfs.nix b/nixos/modules/services/network-filesystems/kbfs.nix index 263b70d04a56d..a43ac656f6676 100644 --- a/nixos/modules/services/network-filesystems/kbfs.nix +++ b/nixos/modules/services/network-filesystems/kbfs.nix @@ -1,6 +1,7 @@ { config, lib, pkgs, ... }: with lib; let + inherit (config.security) wrapperDir; cfg = config.services.kbfs; in { @@ -17,6 +18,16 @@ in { description = "Whether to mount the Keybase filesystem."; }; + enableRedirector = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable the Keybase root redirector service, allowing + any user to access KBFS files via <literal>/keybase</literal>, + which will show different contents depending on the requester. + ''; + }; + mountPoint = mkOption { type = types.str; default = "%h/keybase"; @@ -41,26 +52,67 @@ in { ###### implementation - config = mkIf cfg.enable { - - systemd.user.services.kbfs = { - description = "Keybase File System"; - requires = [ "keybase.service" ]; - after = [ "keybase.service" ]; - path = [ "/run/wrappers" ]; - unitConfig.ConditionUser = "!@system"; - serviceConfig = { - ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p ${cfg.mountPoint}"; - ExecStart = "${pkgs.kbfs}/bin/kbfsfuse ${toString cfg.extraFlags} ${cfg.mountPoint}"; - ExecStopPost = "/run/wrappers/bin/fusermount -u ${cfg.mountPoint}"; - Restart = "on-failure"; - PrivateTmp = true; + config = mkIf cfg.enable (mkMerge [ + { + # Upstream: https://github.com/keybase/client/blob/master/packaging/linux/systemd/kbfs.service + systemd.user.services.kbfs = { + description = "Keybase File System"; + + # Note that the "Requires" directive will cause a unit to be restarted whenever its dependency is restarted. + # Do not issue a hard dependency on keybase, because kbfs can reconnect to a restarted service. + # Do not issue a hard dependency on keybase-redirector, because it's ok if it fails (e.g., if it is disabled). + wants = [ "keybase.service" ] ++ optional cfg.enableRedirector "keybase-redirector.service"; + path = [ "/run/wrappers" ]; + unitConfig.ConditionUser = "!@system"; + + serviceConfig = { + Type = "notify"; + # Keybase notifies from a forked process + EnvironmentFile = [ + "-%E/keybase/keybase.autogen.env" + "-%E/keybase/keybase.env" + ]; + ExecStartPre = [ + "${pkgs.coreutils}/bin/mkdir -p \"${cfg.mountPoint}\"" + "-${wrapperDir}/fusermount -uz \"${cfg.mountPoint}\"" + ]; + ExecStart = "${pkgs.kbfs}/bin/kbfsfuse ${toString cfg.extraFlags} \"${cfg.mountPoint}\""; + ExecStop = "${wrapperDir}/fusermount -uz \"${cfg.mountPoint}\""; + Restart = "on-failure"; + PrivateTmp = true; + }; + wantedBy = [ "default.target" ]; }; - wantedBy = [ "default.target" ]; - }; - services.keybase.enable = true; + services.keybase.enable = true; - environment.systemPackages = [ pkgs.kbfs ]; - }; + environment.systemPackages = [ pkgs.kbfs ]; + } + + (mkIf cfg.enableRedirector { + security.wrappers."keybase-redirector".source = "${pkgs.kbfs}/bin/redirector"; + + systemd.tmpfiles.rules = [ "d /keybase 0755 root root 0" ]; + + # Upstream: https://github.com/keybase/client/blob/master/packaging/linux/systemd/keybase-redirector.service + systemd.user.services.keybase-redirector = { + description = "Keybase Root Redirector for KBFS"; + wants = [ "keybase.service" ]; + unitConfig.ConditionUser = "!@system"; + + serviceConfig = { + EnvironmentFile = [ + "-%E/keybase/keybase.autogen.env" + "-%E/keybase/keybase.env" + ]; + # Note: The /keybase mount point is not currently configurable upstream. + ExecStart = "${wrapperDir}/keybase-redirector /keybase"; + Restart = "on-failure"; + PrivateTmp = true; + }; + + wantedBy = [ "default.target" ]; + }; + }) + ]); } diff --git a/nixos/modules/services/network-filesystems/netatalk.nix b/nixos/modules/services/network-filesystems/netatalk.nix index 1dd869043f0cc..7674c8f7fa8d1 100644 --- a/nixos/modules/services/network-filesystems/netatalk.nix +++ b/nixos/modules/services/network-filesystems/netatalk.nix @@ -43,10 +43,7 @@ in options = { services.netatalk = { - enable = mkOption { - default = false; - description = "Whether to enable the Netatalk AFP fileserver."; - }; + enable = mkEnableOption "the Netatalk AFP fileserver"; port = mkOption { default = 548; @@ -65,6 +62,7 @@ in homes = { enable = mkOption { + type = types.bool; default = false; description = "Enable sharing of the UNIX server user home directories."; }; @@ -98,13 +96,14 @@ in Set of AFP volumes to export. See <literal>man apf.conf</literal> for more information. ''; - example = + example = literalExample '' { srv = { path = "/srv"; "read only" = true; "hosts allow" = "10.1.0.0/16 10.2.1.100 2001:0db8:1234::/48"; }; - }; + } + ''; }; extmap = mkOption { diff --git a/nixos/modules/services/network-filesystems/nfsd.nix b/nixos/modules/services/network-filesystems/nfsd.nix index 1a78f9a76a331..1b62bfa820358 100644 --- a/nixos/modules/services/network-filesystems/nfsd.nix +++ b/nixos/modules/services/network-filesystems/nfsd.nix @@ -11,6 +11,10 @@ let in { + imports = [ + (mkRenamedOptionModule [ "services" "nfs" "lockdPort" ] [ "services" "nfs" "server" "lockdPort" ]) + (mkRenamedOptionModule [ "services" "nfs" "statdPort" ] [ "services" "nfs" "server" "statdPort" ]) + ]; ###### interface diff --git a/nixos/modules/services/network-filesystems/openafs/client.nix b/nixos/modules/services/network-filesystems/openafs/client.nix index 79c4b7aee066c..677111814a018 100644 --- a/nixos/modules/services/network-filesystems/openafs/client.nix +++ b/nixos/modules/services/network-filesystems/openafs/client.nix @@ -9,7 +9,7 @@ let cfg = config.services.openafsClient; cellServDB = pkgs.fetchurl { - url = http://dl.central.org/dl/cellservdb/CellServDB.2018-05-14; + url = "http://dl.central.org/dl/cellservdb/CellServDB.2018-05-14"; sha256 = "1wmjn6mmyy2r8p10nlbdzs4nrqxy8a9pjyrdciy5nmppg4053rk2"; }; diff --git a/nixos/modules/services/network-filesystems/rsyncd.nix b/nixos/modules/services/network-filesystems/rsyncd.nix index b17ec3aa93008..fa29e18a93950 100644 --- a/nixos/modules/services/network-filesystems/rsyncd.nix +++ b/nixos/modules/services/network-filesystems/rsyncd.nix @@ -29,10 +29,7 @@ in options = { services.rsyncd = { - enable = mkOption { - default = false; - description = "Whether to enable the rsync daemon."; - }; + enable = mkEnableOption "the rsync daemon"; motd = mkOption { type = types.str; @@ -74,13 +71,14 @@ in See <command>man rsyncd.conf</command> for options. ''; type = types.attrsOf (types.attrsOf types.str); - example = + example = literalExample '' { srv = { path = "/srv"; "read only" = "yes"; comment = "Public rsync share."; }; - }; + } + ''; }; user = mkOption { diff --git a/nixos/modules/services/network-filesystems/samba.nix b/nixos/modules/services/network-filesystems/samba.nix index 875ab70bfc73e..a115590ccaa00 100644 --- a/nixos/modules/services/network-filesystems/samba.nix +++ b/nixos/modules/services/network-filesystems/samba.nix @@ -65,6 +65,9 @@ let in { + imports = [ + (mkRemovedOptionModule [ "services" "samba" "defaultShare" ] "") + ]; ###### interface @@ -186,7 +189,7 @@ in See <command>man smb.conf</command> for options. ''; type = types.attrsOf (types.attrsOf types.unspecified); - example = + example = literalExample '' { public = { path = "/srv/public"; "read only" = true; @@ -194,7 +197,8 @@ in "guest ok" = "yes"; comment = "Public samba share."; }; - }; + } + ''; }; }; diff --git a/nixos/modules/services/network-filesystems/xtreemfs.nix b/nixos/modules/services/network-filesystems/xtreemfs.nix index c93e201da56ca..b8f8c1d71174b 100644 --- a/nixos/modules/services/network-filesystems/xtreemfs.nix +++ b/nixos/modules/services/network-filesystems/xtreemfs.nix @@ -100,11 +100,13 @@ in dir = { enable = mkOption { + type = types.bool; default = true; description = '' Whether to enable XtreemFS DIR service. ''; }; + uuid = mkOption { example = "eacb6bab-f444-4ebf-a06a-3f72d7465e40"; description = '' @@ -218,11 +220,13 @@ in mrc = { enable = mkOption { + type = types.bool; default = true; description = '' Whether to enable XtreemFS MRC service. ''; }; + uuid = mkOption { example = "eacb6bab-f444-4ebf-a06a-3f72d7465e41"; description = '' @@ -354,11 +358,13 @@ in osd = { enable = mkOption { + type = types.bool; default = true; description = '' Whether to enable XtreemFS OSD service. ''; }; + uuid = mkOption { example = "eacb6bab-f444-4ebf-a06a-3f72d7465e42"; description = '' diff --git a/nixos/modules/services/network-filesystems/yandex-disk.nix b/nixos/modules/services/network-filesystems/yandex-disk.nix index 0aa01ef9e6d92..cc73f13bf77ac 100644 --- a/nixos/modules/services/network-filesystems/yandex-disk.nix +++ b/nixos/modules/services/network-filesystems/yandex-disk.nix @@ -21,6 +21,7 @@ in services.yandex-disk = { enable = mkOption { + type = types.bool; default = false; description = " Whether to enable Yandex-disk client. See https://disk.yandex.ru/ diff --git a/nixos/modules/services/networking/3proxy.nix b/nixos/modules/services/networking/3proxy.nix new file mode 100644 index 0000000000000..ae8a4958ca965 --- /dev/null +++ b/nixos/modules/services/networking/3proxy.nix @@ -0,0 +1,426 @@ +{ config, lib, pkgs, ... }: +with lib; +let + pkg = pkgs._3proxy; + cfg = config.services._3proxy; + optionalList = list: if list == [ ] then "*" else concatMapStringsSep "," toString list; +in { + options.services._3proxy = { + enable = mkEnableOption "3proxy"; + confFile = mkOption { + type = types.path; + example = "/var/lib/3proxy/3proxy.conf"; + description = '' + Ignore all other 3proxy options and load configuration from this file. + ''; + }; + usersFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/lib/3proxy/3proxy.passwd"; + description = '' + Load users and passwords from this file. + + Example users file with plain-text passwords: + + <literal> + test1:CL:password1 + test2:CL:password2 + </literal> + + Example users file with md5-crypted passwords: + + <literal> + test1:CR:$1$tFkisVd2$1GA8JXkRmTXdLDytM/i3a1 + test2:CR:$1$rkpibm5J$Aq1.9VtYAn0JrqZ8M.1ME. + </literal> + + You can generate md5-crypted passwords via https://unix4lyfe.org/crypt/ + Note that htpasswd tool generates incompatible md5-crypted passwords. + Consult <link xlink:href="https://github.com/z3APA3A/3proxy/wiki/How-To-(incomplete)#USERS">documentation</link> for more information. + ''; + }; + services = mkOption { + type = types.listOf (types.submodule { + options = { + type = mkOption { + type = types.enum [ + "proxy" + "socks" + "pop3p" + "ftppr" + "admin" + "dnspr" + "tcppm" + "udppm" + ]; + example = "proxy"; + description = '' + Service type. The following values are valid: + + <itemizedlist> + <listitem><para> + <literal>"proxy"</literal>: HTTP/HTTPS proxy (default port 3128). + </para></listitem> + <listitem><para> + <literal>"socks"</literal>: SOCKS 4/4.5/5 proxy (default port 1080). + </para></listitem> + <listitem><para> + <literal>"pop3p"</literal>: POP3 proxy (default port 110). + </para></listitem> + <listitem><para> + <literal>"ftppr"</literal>: FTP proxy (default port 21). + </para></listitem> + <listitem><para> + <literal>"admin"</literal>: Web interface (default port 80). + </para></listitem> + <listitem><para> + <literal>"dnspr"</literal>: Caching DNS proxy (default port 53). + </para></listitem> + <listitem><para> + <literal>"tcppm"</literal>: TCP portmapper. + </para></listitem> + <listitem><para> + <literal>"udppm"</literal>: UDP portmapper. + </para></listitem> + </itemizedlist> + ''; + }; + bindAddress = mkOption { + type = types.str; + default = "[::]"; + example = "127.0.0.1"; + description = '' + Address used for service. + ''; + }; + bindPort = mkOption { + type = types.nullOr types.int; + default = null; + example = 3128; + description = '' + Override default port used for service. + ''; + }; + maxConnections = mkOption { + type = types.int; + default = 100; + example = 1000; + description = '' + Maximum number of simulationeous connections to this service. + ''; + }; + auth = mkOption { + type = types.listOf (types.enum [ "none" "iponly" "strong" ]); + example = [ "iponly" "strong" ]; + description = '' + Authentication type. The following values are valid: + + <itemizedlist> + <listitem><para> + <literal>"none"</literal>: disables both authentication and authorization. You can not use ACLs. + </para></listitem> + <listitem><para> + <literal>"iponly"</literal>: specifies no authentication. ACLs authorization is used. + </para></listitem> + <listitem><para> + <literal>"strong"</literal>: authentication by username/password. If user is not registered his access is denied regardless of ACLs. + </para></listitem> + </itemizedlist> + + Double authentication is possible, e.g. + + <literal> + { + auth = [ "iponly" "strong" ]; + acl = [ + { + rule = "allow"; + targets = [ "192.168.0.0/16" ]; + } + { + rule = "allow" + users = [ "user1" "user2" ]; + } + ]; + } + </literal> + In this example strong username authentication is not required to access 192.168.0.0/16. + ''; + }; + acl = mkOption { + type = types.listOf (types.submodule { + options = { + rule = mkOption { + type = types.enum [ "allow" "deny" ]; + example = "allow"; + description = '' + ACL rule. The following values are valid: + + <itemizedlist> + <listitem><para> + <literal>"allow"</literal>: connections allowed. + </para></listitem> + <listitem><para> + <literal>"deny"</literal>: connections not allowed. + </para></listitem> + </itemizedlist> + ''; + }; + users = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "user1" "user2" "user3" ]; + description = '' + List of users, use empty list for any. + ''; + }; + sources = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "127.0.0.1" "192.168.1.0/24" ]; + description = '' + List of source IP range, use empty list for any. + ''; + }; + targets = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "127.0.0.1" "192.168.1.0/24" ]; + description = '' + List of target IP ranges, use empty list for any. + May also contain host names instead of addresses. + It's possible to use wildmask in the begginning and in the the end of hostname, e.g. *badsite.com or *badcontent*. + Hostname is only checked if hostname presents in request. + ''; + }; + targetPorts = mkOption { + type = types.listOf types.int; + default = [ ]; + example = [ 80 443 ]; + description = '' + List of target ports, use empty list for any. + ''; + }; + }; + }); + default = [ ]; + example = literalExample '' + [ + { + rule = "allow"; + users = [ "user1" ]; + } + { + rule = "allow"; + sources = [ "192.168.1.0/24" ]; + } + { + rule = "deny"; + } + ] + ''; + description = '' + Use this option to limit user access to resources. + ''; + }; + extraArguments = mkOption { + type = types.nullOr types.str; + default = null; + example = "-46"; + description = '' + Extra arguments for service. + Consult "Options" section in <link xlink:href="https://github.com/z3APA3A/3proxy/wiki/3proxy.cfg">documentation</link> for available arguments. + ''; + }; + extraConfig = mkOption { + type = types.nullOr types.lines; + default = null; + description = '' + Extra configuration for service. Use this to configure things like bandwidth limiter or ACL-based redirection. + Consult <link xlink:href="https://github.com/z3APA3A/3proxy/wiki/3proxy.cfg">documentation</link> for available options. + ''; + }; + }; + }); + default = [ ]; + example = literalExample '' + [ + { + type = "proxy"; + bindAddress = "192.168.1.24"; + bindPort = 3128; + auth = [ "none" ]; + } + { + type = "proxy"; + bindAddress = "10.10.1.20"; + bindPort = 3128; + auth = [ "iponly" ]; + } + { + type = "socks"; + bindAddress = "172.17.0.1"; + bindPort = 1080; + auth = [ "strong" ]; + } + ] + ''; + description = '' + Use this option to define 3proxy services. + ''; + }; + denyPrivate = mkOption { + type = types.bool; + default = true; + description = '' + Whether to deny access to private IP ranges including loopback. + ''; + }; + privateRanges = mkOption { + type = types.listOf types.str; + default = [ + "0.0.0.0/8" + "127.0.0.0/8" + "10.0.0.0/8" + "100.64.0.0/10" + "172.16.0.0/12" + "192.168.0.0/16" + "::" + "::1" + "fc00::/7" + ]; + example = [ + "0.0.0.0/8" + "127.0.0.0/8" + "10.0.0.0/8" + "100.64.0.0/10" + "172.16.0.0/12" + "192.168.0.0/16" + "::" + "::1" + "fc00::/7" + ]; + description = '' + What IP ranges to deny access when denyPrivate is set tu true. + ''; + }; + resolution = mkOption { + type = types.submodule { + options = { + nserver = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "127.0.0.53" "192.168.1.3:5353/tcp" ]; + description = '' + List of nameservers to use. + + Up to 5 nservers may be specified. If no nserver is configured, + default system name resolution functions are used. + ''; + }; + nscache = mkOption { + type = types.int; + default = 65535; + example = 65535; + description = "Set name cache size for IPv4."; + }; + nscache6 = mkOption { + type = types.int; + default = 65535; + example = 65535; + description = "Set name cache size for IPv6."; + }; + nsrecord = mkOption { + type = types.attrsOf types.str; + default = { }; + example = literalExample '' + { + "files.local" = "192.168.1.12"; + "site.local" = "192.168.1.43"; + } + ''; + description = "Adds static nsrecords."; + }; + }; + }; + default = { }; + description = '' + Use this option to configure name resolution and DNS caching. + ''; + }; + extraConfig = mkOption { + type = types.nullOr types.lines; + default = null; + description = '' + Extra configuration, appended to the 3proxy configuration file. + Consult <link xlink:href="https://github.com/z3APA3A/3proxy/wiki/3proxy.cfg">documentation</link> for available options. + ''; + }; + }; + + config = mkIf cfg.enable { + services._3proxy.confFile = mkDefault (pkgs.writeText "3proxy.conf" '' + # log to stdout + log + + ${concatMapStringsSep "\n" (x: "nserver " + x) cfg.resolution.nserver} + + nscache ${toString cfg.resolution.nscache} + nscache6 ${toString cfg.resolution.nscache6} + + ${concatMapStringsSep "\n" (x: "nsrecord " + x) + (mapAttrsToList (name: value: "${name} ${value}") + cfg.resolution.nsrecord)} + + ${optionalString (cfg.usersFile != null) + ''users $"${cfg.usersFile}"'' + } + + ${concatMapStringsSep "\n" (service: '' + auth ${concatStringsSep " " service.auth} + + ${optionalString (cfg.denyPrivate) + "deny * * ${optionalList cfg.privateRanges}"} + + ${concatMapStringsSep "\n" (acl: + "${acl.rule} ${ + concatMapStringsSep " " optionalList [ + acl.users + acl.sources + acl.targets + acl.targetPorts + ] + }") service.acl} + + maxconn ${toString service.maxConnections} + + ${optionalString (service.extraConfig != null) service.extraConfig} + + ${service.type} -i${toString service.bindAddress} ${ + optionalString (service.bindPort != null) + "-p${toString service.bindPort}" + } ${ + optionalString (service.extraArguments != null) service.extraArguments + } + + flush + '') cfg.services} + ${optionalString (cfg.extraConfig != null) cfg.extraConfig} + ''); + systemd.services."3proxy" = { + description = "Tiny free proxy server"; + documentation = [ "https://github.com/z3APA3A/3proxy/wiki" ]; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + DynamicUser = true; + StateDirectory = "3proxy"; + ExecStart = "${pkg}/bin/3proxy ${cfg.confFile}"; + Restart = "on-failure"; + }; + }; + }; + + meta.maintainers = with maintainers; [ misuzu ]; +} diff --git a/nixos/modules/services/networking/amuled.nix b/nixos/modules/services/networking/amuled.nix index 57f02542eafd4..1128ee2c3e614 100644 --- a/nixos/modules/services/networking/amuled.nix +++ b/nixos/modules/services/networking/amuled.nix @@ -16,6 +16,7 @@ in services.amule = { enable = mkOption { + type = types.bool; default = false; description = '' Whether to run the AMule daemon. You need to manually run "amuled --ec-config" to configure the service for the first time. diff --git a/nixos/modules/services/networking/babeld.nix b/nixos/modules/services/networking/babeld.nix index de863461eab29..e62c74d0069df 100644 --- a/nixos/modules/services/networking/babeld.nix +++ b/nixos/modules/services/networking/babeld.nix @@ -35,12 +35,7 @@ in services.babeld = { - enable = mkOption { - default = false; - description = '' - Whether to run the babeld network routing daemon. - ''; - }; + enable = mkEnableOption "the babeld network routing daemon"; interfaceDefaults = mkOption { default = null; diff --git a/nixos/modules/services/networking/bind.nix b/nixos/modules/services/networking/bind.nix index d09c6735e123c..faad886357590 100644 --- a/nixos/modules/services/networking/bind.nix +++ b/nixos/modules/services/networking/bind.nix @@ -68,12 +68,7 @@ in services.bind = { - enable = mkOption { - default = false; - description = " - Whether to enable BIND domain name server. - "; - }; + enable = mkEnableOption "BIND domain name server"; cacheNetworks = mkOption { default = ["127.0.0.0/24"]; @@ -178,9 +173,8 @@ in networking.resolvconf.useLocalResolver = mkDefault true; - users.users = singleton - { name = bindUser; - uid = config.ids.uids.bind; + users.users.${bindUser} = + { uid = config.ids.uids.bind; description = "BIND daemon user"; }; diff --git a/nixos/modules/services/networking/bitlbee.nix b/nixos/modules/services/networking/bitlbee.nix index 274b36171608e..9ebf382fce42f 100644 --- a/nixos/modules/services/networking/bitlbee.nix +++ b/nixos/modules/services/networking/bitlbee.nix @@ -48,6 +48,7 @@ in services.bitlbee = { enable = mkOption { + type = types.bool; default = false; description = '' Whether to run the BitlBee IRC to other chat network gateway. @@ -161,16 +162,14 @@ in config = mkMerge [ (mkIf config.services.bitlbee.enable { - users.users = singleton { - name = "bitlbee"; + users.users.bitlbee = { uid = bitlbeeUid; description = "BitlBee user"; home = "/var/lib/bitlbee"; createHome = true; }; - users.groups = singleton { - name = "bitlbee"; + users.groups.bitlbee = { gid = config.ids.gids.bitlbee; }; diff --git a/nixos/modules/services/networking/charybdis.nix b/nixos/modules/services/networking/charybdis.nix index da26246e703e0..43829d36e4176 100644 --- a/nixos/modules/services/networking/charybdis.nix +++ b/nixos/modules/services/networking/charybdis.nix @@ -71,15 +71,13 @@ in config = mkIf cfg.enable (lib.mkMerge [ { - users.users = singleton { - name = cfg.user; + users.users.${cfg.user} = { description = "Charybdis IRC daemon user"; uid = config.ids.uids.ircd; group = cfg.group; }; - users.groups = singleton { - name = cfg.group; + users.groups.${cfg.group} = { gid = config.ids.gids.ircd; }; diff --git a/nixos/modules/services/networking/cjdns.nix b/nixos/modules/services/networking/cjdns.nix index 3fb85b16cbe20..5f8ac96b2292c 100644 --- a/nixos/modules/services/networking/cjdns.nix +++ b/nixos/modules/services/networking/cjdns.nix @@ -29,17 +29,13 @@ let }; # Additional /etc/hosts entries for peers with an associated hostname - cjdnsExtraHosts = import (pkgs.runCommand "cjdns-hosts" {} - # Generate a builder that produces an output usable as a Nix string value - '' - exec >$out - echo \'\' - ${concatStringsSep "\n" (mapAttrsToList (k: v: - optionalString (v.hostname != "") - "echo $(${pkgs.cjdns}/bin/publictoip6 ${v.publicKey}) ${v.hostname}") - (cfg.ETHInterface.connectTo // cfg.UDPInterface.connectTo))} - echo \'\' - ''); + cjdnsExtraHosts = pkgs.runCommandNoCC "cjdns-hosts" {} '' + exec >$out + ${concatStringsSep "\n" (mapAttrsToList (k: v: + optionalString (v.hostname != "") + "echo $(${pkgs.cjdns}/bin/publictoip6 ${v.publicKey}) ${v.hostname}") + (cfg.ETHInterface.connectTo // cfg.UDPInterface.connectTo))} + ''; parseModules = x: x // { connectTo = mapAttrs (name: value: { inherit (value) password publicKey; }) x.connectTo; }; @@ -144,13 +140,15 @@ in connectTo = mkOption { type = types.attrsOf ( types.submodule ( connectToSubmodule ) ); default = { }; - example = { - "192.168.1.1:27313" = { - hostname = "homer.hype"; - password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM"; - publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k"; - }; - }; + example = literalExample '' + { + "192.168.1.1:27313" = { + hostname = "homer.hype"; + password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM"; + publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k"; + }; + } + ''; description = '' Credentials for making UDP tunnels. ''; @@ -189,13 +187,15 @@ in connectTo = mkOption { type = types.attrsOf ( types.submodule ( connectToSubmodule ) ); default = { }; - example = { - "01:02:03:04:05:06" = { - hostname = "homer.hype"; - password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM"; - publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k"; - }; - }; + example = literalExample '' + { + "01:02:03:04:05:06" = { + hostname = "homer.hype"; + password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM"; + publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k"; + }; + } + ''; description = '' Credentials for connecting look similar to UDP credientials except they begin with the mac address. @@ -278,7 +278,7 @@ in }; }; - networking.extraHosts = mkIf cfg.addExtraHosts cjdnsExtraHosts; + networking.hostFiles = mkIf cfg.addExtraHosts [ cjdnsExtraHosts ]; assertions = [ { assertion = ( cfg.ETHInterface.bind != "" || cfg.UDPInterface.bind != "" || cfg.confFile != null ); diff --git a/nixos/modules/services/networking/cntlm.nix b/nixos/modules/services/networking/cntlm.nix index 4e4e3104c3a8a..5b5068e43d7c1 100644 --- a/nixos/modules/services/networking/cntlm.nix +++ b/nixos/modules/services/networking/cntlm.nix @@ -33,12 +33,7 @@ in options.services.cntlm = { - enable = mkOption { - default = false; - description = '' - Whether to enable the cntlm, which start a local proxy. - ''; - }; + enable = mkEnableOption "cntlm, which starts a local proxy"; username = mkOption { description = '' diff --git a/nixos/modules/services/networking/connman.nix b/nixos/modules/services/networking/connman.nix index cac517f410e5c..6ccc2dffb2676 100644 --- a/nixos/modules/services/networking/connman.nix +++ b/nixos/modules/services/networking/connman.nix @@ -11,8 +11,13 @@ let ${cfg.extraConfig} ''; + enableIwd = cfg.wifi.backend == "iwd"; in { + imports = [ + (mkRenamedOptionModule [ "networking" "connman" ] [ "services" "connman" ]) + ]; + ###### interface options = { @@ -52,6 +57,17 @@ in { ''; }; + wifi = { + backend = mkOption { + type = types.enum [ "wpa_supplicant" "iwd" ]; + default = "wpa_supplicant"; + description = '' + Specify the Wi-Fi backend used. + Currently supported are <option>wpa_supplicant</option> or <option>iwd</option>. + ''; + }; + }; + extraFlags = mkOption { type = with types; listOf str; default = [ ]; @@ -61,6 +77,13 @@ in { ''; }; + package = mkOption { + type = types.path; + description = "The connman package / build flavor"; + default = connman; + example = literalExample "pkgs.connmanFull"; + }; + }; }; @@ -73,24 +96,29 @@ in { assertion = !config.networking.useDHCP; message = "You can not use services.connman with networking.useDHCP"; }{ - assertion = config.networking.wireless.enable; - message = "You must use services.connman with networking.wireless"; - }{ + # TODO: connman seemingly can be used along network manager and + # connmanFull supports this - so this should be worked out somehow assertion = !config.networking.networkmanager.enable; message = "You can not use services.connman with networking.networkmanager"; }]; - environment.systemPackages = [ connman ]; + environment.systemPackages = [ cfg.package ]; systemd.services.connman = { description = "Connection service"; wantedBy = [ "multi-user.target" ]; - after = [ "syslog.target" ]; + after = [ "syslog.target" ] ++ optional enableIwd "iwd.service"; + requires = optional enableIwd "iwd.service"; serviceConfig = { Type = "dbus"; BusName = "net.connman"; Restart = "on-failure"; - ExecStart = "${pkgs.connman}/sbin/connmand --config=${configFile} --nodaemon ${toString cfg.extraFlags}"; + ExecStart = toString ([ + "${cfg.package}/sbin/connmand" + "--config=${configFile}" + "--nodaemon" + ] ++ optional enableIwd "--wifi=iwd_agent" + ++ cfg.extraFlags); StandardOutput = "null"; }; }; @@ -103,7 +131,7 @@ in { serviceConfig = { Type = "dbus"; BusName = "net.connman.vpn"; - ExecStart = "${pkgs.connman}/sbin/connman-vpnd -n"; + ExecStart = "${cfg.package}/sbin/connman-vpnd -n"; StandardOutput = "null"; }; }; @@ -113,7 +141,7 @@ in { serviceConfig = { Name = "net.connman.vpn"; before = [ "connman" ]; - ExecStart = "${pkgs.connman}/sbin/connman-vpnd -n"; + ExecStart = "${cfg.package}/sbin/connman-vpnd -n"; User = "root"; SystemdService = "connman-vpn.service"; }; @@ -121,7 +149,12 @@ in { networking = { useDHCP = false; - wireless.enable = true; + wireless = { + enable = mkIf (!enableIwd) true; + iwd = mkIf enableIwd { + enable = true; + }; + }; networkmanager.enable = false; }; }; diff --git a/nixos/modules/services/networking/corerad.nix b/nixos/modules/services/networking/corerad.nix new file mode 100644 index 0000000000000..1a2c4aec66514 --- /dev/null +++ b/nixos/modules/services/networking/corerad.nix @@ -0,0 +1,46 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.corerad; +in { + meta = { + maintainers = with maintainers; [ mdlayher ]; + }; + + options.services.corerad = { + enable = mkEnableOption "CoreRAD IPv6 NDP RA daemon"; + + configFile = mkOption { + type = types.path; + example = literalExample "\"\${pkgs.corerad}/etc/corerad/corerad.toml\""; + description = "Path to CoreRAD TOML configuration file."; + }; + + package = mkOption { + default = pkgs.corerad; + defaultText = literalExample "pkgs.corerad"; + type = types.package; + description = "CoreRAD package to use."; + }; + }; + + config = mkIf cfg.enable { + systemd.services.corerad = { + description = "CoreRAD IPv6 NDP RA daemon"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + LimitNPROC = 512; + LimitNOFILE = 1048576; + CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW"; + AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_RAW"; + NoNewPrivileges = true; + DynamicUser = true; + ExecStart = "${getBin cfg.package}/bin/corerad -c=${cfg.configFile}"; + Restart = "on-failure"; + }; + }; + }; +} diff --git a/nixos/modules/services/networking/coturn.nix b/nixos/modules/services/networking/coturn.nix index c430ce5af92a3..1bfbc307c59d1 100644 --- a/nixos/modules/services/networking/coturn.nix +++ b/nixos/modules/services/networking/coturn.nix @@ -294,16 +294,14 @@ in { }; config = mkIf cfg.enable { - users.users = [ - { name = "turnserver"; - uid = config.ids.uids.turnserver; + users.users.turnserver = + { uid = config.ids.uids.turnserver; description = "coturn TURN server user"; - } ]; - users.groups = [ - { name = "turnserver"; - gid = config.ids.gids.turnserver; + }; + users.groups.turnserver = + { gid = config.ids.gids.turnserver; members = [ "turnserver" ]; - } ]; + }; systemd.services.coturn = { description = "coturn TURN server"; diff --git a/nixos/modules/services/networking/ddclient.nix b/nixos/modules/services/networking/ddclient.nix index 04ce5ca3a8749..053efe7127091 100644 --- a/nixos/modules/services/networking/ddclient.nix +++ b/nixos/modules/services/networking/ddclient.nix @@ -30,6 +30,14 @@ with lib; { + imports = [ + (mkChangedOptionModule [ "services" "ddclient" "domain" ] [ "services" "ddclient" "domains" ] + (config: + let value = getAttrFromPath [ "services" "ddclient" "domain" ] config; + in if value != "" then [ value ] else [])) + (mkRemovedOptionModule [ "services" "ddclient" "homeDir" ] "") + ]; + ###### interface options = { diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix index 7b2786034552f..c0619211c2fe9 100644 --- a/nixos/modules/services/networking/dhcpcd.nix +++ b/nixos/modules/services/networking/dhcpcd.nix @@ -19,7 +19,7 @@ let map (i: i.name) (filter (i: if i.useDHCP != null then !i.useDHCP else i.ipv4.addresses != [ ]) interfaces) ++ mapAttrsToList (i: _: i) config.networking.sits ++ concatLists (attrValues (mapAttrs (n: v: v.interfaces) config.networking.bridges)) - ++ concatLists (attrValues (mapAttrs (n: v: v.interfaces) config.networking.vswitches)) + ++ flatten (concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (attrValues config.networking.vswitches)) ++ concatLists (attrValues (mapAttrs (n: v: v.interfaces) config.networking.bonds)) ++ config.networking.dhcpcd.denyInterfaces; @@ -59,6 +59,16 @@ let # Use the list of allowed interfaces if specified ${optionalString (allowInterfaces != null) "allowinterfaces ${toString allowInterfaces}"} + # Immediately fork to background if specified, otherwise wait for IP address to be assigned + ${{ + background = "background"; + any = "waitip"; + ipv4 = "waitip 4"; + ipv6 = "waitip 6"; + both = "waitip 4\nwaitip 6"; + if-carrier-up = ""; + }.${cfg.wait}} + ${cfg.extraConfig} ''; @@ -146,6 +156,21 @@ in ''; }; + networking.dhcpcd.wait = mkOption { + type = types.enum [ "background" "any" "ipv4" "ipv6" "both" "if-carrier-up" ]; + default = "any"; + description = '' + This option specifies when the dhcpcd service will fork to background. + If set to "background", dhcpcd will fork to background immediately. + If set to "ipv4" or "ipv6", dhcpcd will wait for the corresponding IP + address to be assigned. If set to "any", dhcpcd will wait for any type + (IPv4 or IPv6) to be assigned. If set to "both", dhcpcd will wait for + both an IPv4 and an IPv6 address before forking. + The option "if-carrier-up" is equivalent to "any" if either ethernet + is plugged nor WiFi is powered, and to "background" otherwise. + ''; + }; + }; @@ -165,6 +190,8 @@ in before = [ "network-online.target" ]; after = [ "systemd-udev-settle.service" ]; + restartTriggers = [ exitHook ]; + # Stopping dhcpcd during a reconfiguration is undesirable # because it brings down the network interfaces configured by # dhcpcd. So do a "systemctl restart" instead. @@ -177,7 +204,7 @@ in serviceConfig = { Type = "forking"; PIDFile = "/run/dhcpcd.pid"; - ExecStart = "@${dhcpcd}/sbin/dhcpcd dhcpcd -w --quiet ${optionalString cfg.persistent "--persistent"} --config ${dhcpcdConf}"; + ExecStart = "@${dhcpcd}/sbin/dhcpcd dhcpcd --quiet ${optionalString cfg.persistent "--persistent"} --config ${dhcpcdConf}"; ExecReload = "${dhcpcd}/sbin/dhcpcd --rebind"; Restart = "always"; }; @@ -185,11 +212,7 @@ in environment.systemPackages = [ dhcpcd ]; - environment.etc = - [ { source = exitHook; - target = "dhcpcd.exit-hook"; - } - ]; + environment.etc."dhcpcd.exit-hook".source = exitHook; powerManagement.resumeCommands = mkIf config.systemd.services.dhcpcd.enable '' diff --git a/nixos/modules/services/networking/dhcpd.nix b/nixos/modules/services/networking/dhcpd.nix index 0b2063bc42466..67f7d81188700 100644 --- a/nixos/modules/services/networking/dhcpd.nix +++ b/nixos/modules/services/networking/dhcpd.nix @@ -182,6 +182,10 @@ in { + imports = [ + (mkRenamedOptionModule [ "services" "dhcpd" ] [ "services" "dhcpd4" ]) + ]; + ###### interface options = { diff --git a/nixos/modules/services/networking/dnscache.nix b/nixos/modules/services/networking/dnscache.nix index d123bca932193..d06032daecc72 100644 --- a/nixos/modules/services/networking/dnscache.nix +++ b/nixos/modules/services/networking/dnscache.nix @@ -61,10 +61,12 @@ in { Table of {hostname: server} pairs to use as authoritative servers for hosts (and subhosts). If entry for @ is not specified predefined list of root servers is used. ''; - example = { - "@" = ["8.8.8.8" "8.8.4.4"]; - "example.com" = ["192.168.100.100"]; - }; + example = literalExample '' + { + "@" = ["8.8.8.8" "8.8.4.4"]; + "example.com" = ["192.168.100.100"]; + } + ''; }; forwardOnly = mkOption { diff --git a/nixos/modules/services/networking/dnschain.nix b/nixos/modules/services/networking/dnschain.nix index 5b58ea9b0c910..003609ea7054a 100644 --- a/nixos/modules/services/networking/dnschain.nix +++ b/nixos/modules/services/networking/dnschain.nix @@ -137,7 +137,7 @@ in ]; services.pdns-recursor = mkIf cfgs.pdns-recursor.resolveDNSChainQueries { - forwardZones = + forwardZonesRecurse = { bit = "127.0.0.1:${toString cfg.dns.port}"; dns = "127.0.0.1:${toString cfg.dns.port}"; }; @@ -147,8 +147,7 @@ in ''; }; - users.users = singleton { - name = username; + users.users.${username} = { description = "DNSChain daemon user"; home = dataDir; createHome = true; @@ -180,4 +179,6 @@ in }; + meta.maintainers = with lib.maintainers; [ rnhmjoj ]; + } diff --git a/nixos/modules/services/networking/dnscrypt-proxy.nix b/nixos/modules/services/networking/dnscrypt-proxy.nix deleted file mode 100644 index 8edcf925dbfa1..0000000000000 --- a/nixos/modules/services/networking/dnscrypt-proxy.nix +++ /dev/null @@ -1,328 +0,0 @@ -{ config, lib, pkgs, ... }: -with lib; - -let - cfg = config.services.dnscrypt-proxy; - - stateDirectory = "/var/lib/dnscrypt-proxy"; - - # The minisign public key used to sign the upstream resolver list. - # This is somewhat more flexible than preloading the key as an - # embedded string. - upstreamResolverListPubKey = pkgs.fetchurl { - url = https://raw.githubusercontent.com/dyne/dnscrypt-proxy/master/minisign.pub; - sha256 = "18lnp8qr6ghfc2sd46nn1rhcpr324fqlvgsp4zaigw396cd7vnnh"; - }; - - # Internal flag indicating whether the upstream resolver list is used. - useUpstreamResolverList = cfg.customResolver == null; - - # The final local address. - localAddress = "${cfg.localAddress}:${toString cfg.localPort}"; - - # The final resolvers list path. - resolverList = "${stateDirectory}/dnscrypt-resolvers.csv"; - - # Build daemon command line - - resolverArgs = - if (cfg.customResolver == null) - then - [ "-L ${resolverList}" - "-R ${cfg.resolverName}" - ] - else with cfg.customResolver; - [ "-N ${name}" - "-k ${key}" - "-r ${address}:${toString port}" - ]; - - daemonArgs = - [ "-a ${localAddress}" ] - ++ resolverArgs - ++ cfg.extraArgs; -in - -{ - meta = { - maintainers = with maintainers; [ joachifm ]; - doc = ./dnscrypt-proxy.xml; - }; - - options = { - # Before adding another option, consider whether it could - # equally well be passed via extraArgs. - - services.dnscrypt-proxy = { - enable = mkOption { - default = false; - type = types.bool; - description = "Whether to enable the DNSCrypt client proxy"; - }; - - localAddress = mkOption { - default = "127.0.0.1"; - type = types.str; - description = '' - Listen for DNS queries to relay on this address. The only reason to - change this from its default value is to proxy queries on behalf - of other machines (typically on the local network). - ''; - }; - - localPort = mkOption { - default = 53; - type = types.int; - description = '' - Listen for DNS queries to relay on this port. The default value - assumes that the DNSCrypt proxy should relay DNS queries directly. - When running as a forwarder for another DNS client, set this option - to a different value; otherwise leave the default. - ''; - }; - - resolverName = mkOption { - default = "random"; - example = "dnscrypt.eu-nl"; - type = types.nullOr types.str; - description = '' - The name of the DNSCrypt resolver to use, taken from - <filename>${resolverList}</filename>. The default is to - pick a random non-logging resolver that supports DNSSEC. - ''; - }; - - customResolver = mkOption { - default = null; - description = '' - Use an unlisted resolver (e.g., a private DNSCrypt provider). For - advanced users only. If specified, this option takes precedence. - ''; - type = types.nullOr (types.submodule ({ ... }: { options = { - address = mkOption { - type = types.str; - description = "IP address"; - example = "208.67.220.220"; - }; - - port = mkOption { - type = types.int; - description = "Port"; - default = 443; - }; - - name = mkOption { - type = types.str; - description = "Fully qualified domain name"; - example = "2.dnscrypt-cert.example.com"; - }; - - key = mkOption { - type = types.str; - description = "Public key"; - example = "B735:1140:206F:225D:3E2B:D822:D7FD:691E:A1C3:3CC8:D666:8D0C:BE04:BFAB:CA43:FB79"; - }; - }; })); - }; - - extraArgs = mkOption { - default = []; - type = types.listOf types.str; - description = '' - Additional command-line arguments passed verbatim to the daemon. - See <citerefentry><refentrytitle>dnscrypt-proxy</refentrytitle> - <manvolnum>8</manvolnum></citerefentry> for details. - ''; - example = [ "-X libdcplugin_example_cache.so,--min-ttl=60" ]; - }; - }; - }; - - config = mkIf cfg.enable (mkMerge [{ - assertions = [ - { assertion = (cfg.customResolver != null) || (cfg.resolverName != null); - message = "please configure upstream DNSCrypt resolver"; - } - ]; - - # make man 8 dnscrypt-proxy work - environment.systemPackages = [ pkgs.dnscrypt-proxy ]; - - users.users.dnscrypt-proxy = { - description = "dnscrypt-proxy daemon user"; - isSystemUser = true; - group = "dnscrypt-proxy"; - }; - users.groups.dnscrypt-proxy = {}; - - systemd.sockets.dnscrypt-proxy = { - description = "dnscrypt-proxy listening socket"; - documentation = [ "man:dnscrypt-proxy(8)" ]; - - wantedBy = [ "sockets.target" ]; - - socketConfig = { - ListenStream = localAddress; - ListenDatagram = localAddress; - }; - }; - - systemd.services.dnscrypt-proxy = { - description = "dnscrypt-proxy daemon"; - documentation = [ "man:dnscrypt-proxy(8)" ]; - - before = [ "nss-lookup.target" ]; - after = [ "network.target" ]; - requires = [ "dnscrypt-proxy.socket "]; - - serviceConfig = { - NonBlocking = "true"; - ExecStart = "${pkgs.dnscrypt-proxy}/bin/dnscrypt-proxy ${toString daemonArgs}"; - ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; - - User = "dnscrypt-proxy"; - - PrivateTmp = true; - PrivateDevices = true; - ProtectHome = true; - }; - }; - } - - (mkIf config.security.apparmor.enable { - systemd.services.dnscrypt-proxy.after = [ "apparmor.service" ]; - - security.apparmor.profiles = singleton (pkgs.writeText "apparmor-dnscrypt-proxy" '' - ${pkgs.dnscrypt-proxy}/bin/dnscrypt-proxy { - /dev/null rw, - /dev/random r, - /dev/urandom r, - - /etc/passwd r, - /etc/group r, - ${config.environment.etc."nsswitch.conf".source} r, - - ${getLib pkgs.glibc}/lib/*.so mr, - ${pkgs.tzdata}/share/zoneinfo/** r, - - network inet stream, - network inet6 stream, - network inet dgram, - network inet6 dgram, - - ${getLib pkgs.dnscrypt-proxy}/lib/dnscrypt-proxy/libdcplugin*.so mr, - - ${getLib pkgs.gcc.cc}/lib/libssp.so.* mr, - ${getLib pkgs.libsodium}/lib/libsodium.so.* mr, - ${getLib pkgs.systemd}/lib/libsystemd.so.* mr, - ${getLib pkgs.utillinuxMinimal.out}/lib/libmount.so.* mr, - ${getLib pkgs.utillinuxMinimal.out}/lib/libblkid.so.* mr, - ${getLib pkgs.utillinuxMinimal.out}/lib/libuuid.so.* mr, - ${getLib pkgs.xz}/lib/liblzma.so.* mr, - ${getLib pkgs.libgcrypt}/lib/libgcrypt.so.* mr, - ${getLib pkgs.libgpgerror}/lib/libgpg-error.so.* mr, - ${getLib pkgs.libcap}/lib/libcap.so.* mr, - ${getLib pkgs.lz4}/lib/liblz4.so.* mr, - ${getLib pkgs.attr}/lib/libattr.so.* mr, # */ - - ${resolverList} r, - - /run/systemd/notify rw, - } - ''); - }) - - (mkIf useUpstreamResolverList { - systemd.services.init-dnscrypt-proxy-statedir = { - description = "Initialize dnscrypt-proxy state directory"; - - wantedBy = [ "dnscrypt-proxy.service" ]; - before = [ "dnscrypt-proxy.service" ]; - - script = '' - mkdir -pv ${stateDirectory} - chown -c dnscrypt-proxy:dnscrypt-proxy ${stateDirectory} - cp -uv \ - ${pkgs.dnscrypt-proxy}/share/dnscrypt-proxy/dnscrypt-resolvers.csv \ - ${stateDirectory} - ''; - - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - }; - }; - - systemd.services.update-dnscrypt-resolvers = { - description = "Update list of DNSCrypt resolvers"; - - requires = [ "init-dnscrypt-proxy-statedir.service" ]; - after = [ "init-dnscrypt-proxy-statedir.service" ]; - - path = with pkgs; [ curl diffutils dnscrypt-proxy minisign ]; - script = '' - cd ${stateDirectory} - domain=raw.githubusercontent.com - get="curl -fSs --resolve $domain:443:$(hostip -r 8.8.8.8 $domain | head -1)" - $get -o dnscrypt-resolvers.csv.tmp \ - https://$domain/dyne/dnscrypt-proxy/master/dnscrypt-resolvers.csv - $get -o dnscrypt-resolvers.csv.minisig.tmp \ - https://$domain/dyne/dnscrypt-proxy/master/dnscrypt-resolvers.csv.minisig - mv dnscrypt-resolvers.csv.minisig{.tmp,} - if ! minisign -q -V -p ${upstreamResolverListPubKey} \ - -m dnscrypt-resolvers.csv.tmp -x dnscrypt-resolvers.csv.minisig ; then - echo "failed to verify resolver list!" >&2 - exit 1 - fi - [[ -f dnscrypt-resolvers.csv ]] && mv dnscrypt-resolvers.csv{,.old} - mv dnscrypt-resolvers.csv{.tmp,} - if cmp dnscrypt-resolvers.csv{,.old} ; then - echo "no change" - else - echo "resolver list updated" - fi - ''; - - serviceConfig = { - PrivateTmp = true; - PrivateDevices = true; - ProtectHome = true; - ProtectSystem = "strict"; - ReadWritePaths = "${dirOf stateDirectory} ${stateDirectory}"; - SystemCallFilter = "~@mount"; - }; - }; - - systemd.timers.update-dnscrypt-resolvers = { - wantedBy = [ "timers.target" ]; - timerConfig = { - OnBootSec = "5min"; - OnUnitActiveSec = "6h"; - }; - }; - }) - ]); - - imports = [ - (mkRenamedOptionModule [ "services" "dnscrypt-proxy" "port" ] [ "services" "dnscrypt-proxy" "localPort" ]) - - (mkChangedOptionModule - [ "services" "dnscrypt-proxy" "tcpOnly" ] - [ "services" "dnscrypt-proxy" "extraArgs" ] - (config: - let val = getAttrFromPath [ "services" "dnscrypt-proxy" "tcpOnly" ] config; in - optional val "-T")) - - (mkChangedOptionModule - [ "services" "dnscrypt-proxy" "ephemeralKeys" ] - [ "services" "dnscrypt-proxy" "extraArgs" ] - (config: - let val = getAttrFromPath [ "services" "dnscrypt-proxy" "ephemeralKeys" ] config; in - optional val "-E")) - - (mkRemovedOptionModule [ "services" "dnscrypt-proxy" "resolverList" ] '' - The current resolver listing from upstream is always used - unless a custom resolver is specified. - '') - ]; -} diff --git a/nixos/modules/services/networking/dnscrypt-proxy.xml b/nixos/modules/services/networking/dnscrypt-proxy.xml deleted file mode 100644 index afc7880392a1a..0000000000000 --- a/nixos/modules/services/networking/dnscrypt-proxy.xml +++ /dev/null @@ -1,66 +0,0 @@ -<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="sec-dnscrypt-proxy"> - <title>DNSCrypt client proxy</title> - <para> - The DNSCrypt client proxy relays DNS queries to a DNSCrypt enabled upstream - resolver. The traffic between the client and the upstream resolver is - encrypted and authenticated, mitigating the risk of MITM attacks, DNS - poisoning attacks, and third-party snooping (assuming the upstream is - trustworthy). - </para> - <sect1 xml:id="sec-dnscrypt-proxy-configuration"> - <title>Basic configuration</title> - - <para> - To enable the client proxy, set -<programlisting> -<xref linkend="opt-services.dnscrypt-proxy.enable"/> = true; -</programlisting> - </para> - - <para> - Enabling the client proxy does not alter the system nameserver; to relay - local queries, prepend <literal>127.0.0.1</literal> to - <option>networking.nameservers</option>. - </para> - </sect1> - <sect1 xml:id="sec-dnscrypt-proxy-forwarder"> - <title>As a forwarder for another DNS client</title> - - <para> - To run the DNSCrypt proxy client as a forwarder for another DNS client, - change the default proxy listening port to a non-standard value and point - the other client to it: -<programlisting> -<xref linkend="opt-services.dnscrypt-proxy.localPort"/> = 43; -</programlisting> - </para> - - <sect2 xml:id="sec-dnscrypt-proxy-forwarder-dsnmasq"> - <title>dnsmasq</title> - <para> -<programlisting> -{ - <xref linkend="opt-services.dnsmasq.enable"/> = true; - <xref linkend="opt-services.dnsmasq.servers"/> = [ "127.0.0.1#43" ]; -} -</programlisting> - </para> - </sect2> - - <sect2 xml:id="sec-dnscrypt-proxy-forwarder-unbound"> - <title>unbound</title> - <para> -<programlisting> -{ - <xref linkend="opt-services.unbound.enable"/> = true; - <xref linkend="opt-services.unbound.forwardAddresses"/> = [ "127.0.0.1@43" ]; -} -</programlisting> - </para> - </sect2> - </sect1> -</chapter> diff --git a/nixos/modules/services/networking/dnscrypt-proxy2.nix b/nixos/modules/services/networking/dnscrypt-proxy2.nix new file mode 100644 index 0000000000000..e48eb729103be --- /dev/null +++ b/nixos/modules/services/networking/dnscrypt-proxy2.nix @@ -0,0 +1,61 @@ +{ config, lib, pkgs, ... }: with lib; + +let + cfg = config.services.dnscrypt-proxy2; +in + +{ + options.services.dnscrypt-proxy2 = { + enable = mkEnableOption "dnscrypt-proxy2"; + + settings = mkOption { + description = '' + Attrset that is converted and passed as TOML config file. + For available params, see: <link xlink:href="https://github.com/DNSCrypt/dnscrypt-proxy/blob/master/dnscrypt-proxy/example-dnscrypt-proxy.toml"/> + ''; + example = literalExample '' + { + sources.public-resolvers = { + urls = [ "https://download.dnscrypt.info/resolvers-list/v2/public-resolvers.md" ]; + cache_file = "public-resolvers.md"; + minisign_key = "RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3"; + refresh_delay = 72; + }; + } + ''; + type = types.attrs; + default = {}; + }; + + configFile = mkOption { + description = '' + Path to TOML config file. See: <link xlink:href="https://github.com/DNSCrypt/dnscrypt-proxy/blob/master/dnscrypt-proxy/example-dnscrypt-proxy.toml"/> + If this option is set, it will override any configuration done in options.services.dnscrypt-proxy2.settings. + ''; + example = "/etc/dnscrypt-proxy/dnscrypt-proxy.toml"; + type = types.path; + default = pkgs.runCommand "dnscrypt-proxy.toml" { + json = builtins.toJSON cfg.settings; + passAsFile = [ "json" ]; + } '' + ${pkgs.remarshal}/bin/json2toml < $jsonPath > $out + ''; + defaultText = literalExample "TOML file generated from services.dnscrypt-proxy2.settings"; + }; + }; + + config = mkIf cfg.enable { + + networking.nameservers = lib.mkDefault [ "127.0.0.1" ]; + + systemd.services.dnscrypt-proxy2 = { + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + AmbientCapabilities = "CAP_NET_BIND_SERVICE"; + DynamicUser = true; + ExecStart = "${pkgs.dnscrypt-proxy2}/bin/dnscrypt-proxy -config ${cfg.configFile}"; + }; + }; + }; +} diff --git a/nixos/modules/services/networking/dnscrypt-wrapper.nix b/nixos/modules/services/networking/dnscrypt-wrapper.nix index 79f9e1a430837..e53fb7a157828 100644 --- a/nixos/modules/services/networking/dnscrypt-wrapper.nix +++ b/nixos/modules/services/networking/dnscrypt-wrapper.nix @@ -197,4 +197,7 @@ in { }; }; + + meta.maintainers = with lib.maintainers; [ rnhmjoj ]; + } diff --git a/nixos/modules/services/networking/dnsmasq.nix b/nixos/modules/services/networking/dnsmasq.nix index 714a5903bff16..377d7bc570587 100644 --- a/nixos/modules/services/networking/dnsmasq.nix +++ b/nixos/modules/services/networking/dnsmasq.nix @@ -86,8 +86,7 @@ in services.dbus.packages = [ dnsmasq ]; - users.users = singleton { - name = "dnsmasq"; + users.users.dnsmasq = { uid = config.ids.uids.dnsmasq; description = "Dnsmasq daemon user"; }; diff --git a/nixos/modules/services/networking/ejabberd.nix b/nixos/modules/services/networking/ejabberd.nix index 6a38f85c48a24..a5af25b983b91 100644 --- a/nixos/modules/services/networking/ejabberd.nix +++ b/nixos/modules/services/networking/ejabberd.nix @@ -94,18 +94,18 @@ in { config = mkIf cfg.enable { environment.systemPackages = [ cfg.package ]; - users.users = optionalAttrs (cfg.user == "ejabberd") (singleton - { name = "ejabberd"; + users.users = optionalAttrs (cfg.user == "ejabberd") { + ejabberd = { group = cfg.group; home = cfg.spoolDir; createHome = true; uid = config.ids.uids.ejabberd; - }); + }; + }; - users.groups = optionalAttrs (cfg.group == "ejabberd") (singleton - { name = "ejabberd"; - gid = config.ids.gids.ejabberd; - }); + users.groups = optionalAttrs (cfg.group == "ejabberd") { + ejabberd.gid = config.ids.gids.ejabberd; + }; systemd.services.ejabberd = { description = "ejabberd server"; diff --git a/nixos/modules/services/networking/fakeroute.nix b/nixos/modules/services/networking/fakeroute.nix index 82a9fb729d841..7916ad4098a7c 100644 --- a/nixos/modules/services/networking/fakeroute.nix +++ b/nixos/modules/services/networking/fakeroute.nix @@ -60,4 +60,6 @@ in }; + meta.maintainers = with lib.maintainers; [ rnhmjoj ]; + } diff --git a/nixos/modules/services/networking/firewall.nix b/nixos/modules/services/networking/firewall.nix index 5919962837a21..cdc3a172ea706 100644 --- a/nixos/modules/services/networking/firewall.nix +++ b/nixos/modules/services/networking/firewall.nix @@ -42,16 +42,7 @@ let kernelHasRPFilter = ((kernel.config.isEnabled or (x: false)) "IP_NF_MATCH_RPFILTER") || (kernel.features.netfilterRPFilter or false); - helpers = - '' - # Helper command to manipulate both the IPv4 and IPv6 tables. - ip46tables() { - iptables -w "$@" - ${optionalString config.networking.enableIPv6 '' - ip6tables -w "$@" - ''} - } - ''; + helpers = import ./helpers.nix { inherit config lib; }; writeShScript = name: text: let dir = pkgs.writeScriptBin name '' #! ${pkgs.runtimeShell} -e @@ -271,7 +262,7 @@ let apply = canonicalizePortList; example = [ 22 80 ]; description = - '' + '' List of TCP ports on which incoming connections are accepted. ''; @@ -282,7 +273,7 @@ let default = [ ]; example = [ { from = 8999; to = 9003; } ]; description = - '' + '' A range of TCP ports on which incoming connections are accepted. ''; @@ -555,9 +546,13 @@ in options nf_conntrack nf_conntrack_helper=1 ''; - assertions = [ { assertion = (cfg.checkReversePath != false) || kernelHasRPFilter; - message = "This kernel does not support rpfilter"; } - ]; + assertions = [ + # This is approximately "checkReversePath -> kernelHasRPFilter", + # but the checkReversePath option can include non-boolean + # values. + { assertion = cfg.checkReversePath == false || kernelHasRPFilter; + message = "This kernel does not support rpfilter"; } + ]; systemd.services.firewall = { description = "Firewall"; diff --git a/nixos/modules/services/networking/flashpolicyd.nix b/nixos/modules/services/networking/flashpolicyd.nix index 9c51b88ef6776..7f25083307c72 100644 --- a/nixos/modules/services/networking/flashpolicyd.nix +++ b/nixos/modules/services/networking/flashpolicyd.nix @@ -39,6 +39,7 @@ in services.flashpolicyd = { enable = mkOption { + type = types.bool; default = false; description = '' diff --git a/nixos/modules/services/networking/freeradius.nix b/nixos/modules/services/networking/freeradius.nix index e192b70c129c0..f3fdd576b65c0 100644 --- a/nixos/modules/services/networking/freeradius.nix +++ b/nixos/modules/services/networking/freeradius.nix @@ -10,14 +10,15 @@ let { description = "FreeRadius server"; wantedBy = ["multi-user.target"]; - after = ["network-online.target"]; - wants = ["network-online.target"]; + after = ["network.target"]; + wants = ["network.target"]; preStart = '' ${pkgs.freeradius}/bin/radiusd -C -d ${cfg.configDir} -l stdout ''; serviceConfig = { - ExecStart = "${pkgs.freeradius}/bin/radiusd -f -d ${cfg.configDir} -l stdout -xx"; + ExecStart = "${pkgs.freeradius}/bin/radiusd -f -d ${cfg.configDir} -l stdout" + + optionalString cfg.debug " -xx"; ExecReload = [ "${pkgs.freeradius}/bin/radiusd -C -d ${cfg.configDir} -l stdout" "${pkgs.coreutils}/bin/kill -HUP $MAINPID" @@ -41,6 +42,16 @@ let ''; }; + debug = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable debug logging for freeradius (-xx + option). This should not be left on, since it includes + sensitive data such as passwords in the logs. + ''; + }; + }; in @@ -66,6 +77,7 @@ in }; systemd.services.freeradius = freeradiusService cfg; + warnings = optional cfg.debug "Freeradius debug logging is enabled. This will log passwords in plaintext to the journal!"; }; diff --git a/nixos/modules/services/networking/gale.nix b/nixos/modules/services/networking/gale.nix index 7083d87c40735..cb954fd836bc4 100644 --- a/nixos/modules/services/networking/gale.nix +++ b/nixos/modules/services/networking/gale.nix @@ -104,14 +104,13 @@ in systemPackages = [ pkgs.gale ]; }; - users.users = [{ - name = cfg.user; + users.users.${cfg.user} = { description = "Gale daemon"; uid = config.ids.uids.gale; group = cfg.group; home = home; createHome = true; - }]; + }; users.groups = [{ name = cfg.group; diff --git a/nixos/modules/services/networking/git-daemon.nix b/nixos/modules/services/networking/git-daemon.nix index a638a3083fbaf..52c895215fbe4 100644 --- a/nixos/modules/services/networking/git-daemon.nix +++ b/nixos/modules/services/networking/git-daemon.nix @@ -104,16 +104,16 @@ in config = mkIf cfg.enable { - users.users = if cfg.user != "git" then {} else singleton - { name = "git"; + users.users = optionalAttrs (cfg.user == "git") { + git = { uid = config.ids.uids.git; description = "Git daemon user"; }; + }; - users.groups = if cfg.group != "git" then {} else singleton - { name = "git"; - gid = config.ids.gids.git; - }; + users.groups = optionalAttrs (cfg.group == "git") { + git.gid = config.ids.gids.git; + }; systemd.services.git-daemon = { after = [ "network.target" ]; diff --git a/nixos/modules/services/networking/gnunet.nix b/nixos/modules/services/networking/gnunet.nix index 178a832c166ef..69d4ed047756a 100644 --- a/nixos/modules/services/networking/gnunet.nix +++ b/nixos/modules/services/networking/gnunet.nix @@ -42,6 +42,7 @@ in services.gnunet = { enable = mkOption { + type = types.bool; default = false; description = '' Whether to run the GNUnet daemon. GNUnet is GNU's anonymous @@ -51,6 +52,7 @@ in fileSharing = { quota = mkOption { + type = types.int; default = 1024; description = '' Maximum file system usage (in MiB) for file sharing. @@ -60,6 +62,7 @@ in udp = { port = mkOption { + type = types.port; default = 2086; # assigned by IANA description = '' The UDP port for use by GNUnet. @@ -69,6 +72,7 @@ in tcp = { port = mkOption { + type = types.port; default = 2086; # assigned by IANA description = '' The TCP port for use by GNUnet. @@ -78,6 +82,7 @@ in load = { maxNetDownBandwidth = mkOption { + type = types.int; default = 50000; description = '' Maximum bandwidth usage (in bits per second) for GNUnet @@ -86,6 +91,7 @@ in }; maxNetUpBandwidth = mkOption { + type = types.int; default = 50000; description = '' Maximum bandwidth usage (in bits per second) for GNUnet @@ -94,6 +100,7 @@ in }; hardNetUpBandwidth = mkOption { + type = types.int; default = 0; description = '' Hard bandwidth limit (in bits per second) when uploading @@ -111,6 +118,7 @@ in }; extraOptions = mkOption { + type = types.lines; default = ""; description = '' Additional options that will be copied verbatim in `gnunet.conf'. diff --git a/nixos/modules/services/networking/gvpe.nix b/nixos/modules/services/networking/gvpe.nix index 3ef3548e0a08f..92e87cd4640d0 100644 --- a/nixos/modules/services/networking/gvpe.nix +++ b/nixos/modules/services/networking/gvpe.nix @@ -42,12 +42,8 @@ in { options = { services.gvpe = { - enable = mkOption { - default = false; - description = '' - Whether to run gvpe - ''; - }; + enable = lib.mkEnableOption "gvpe"; + nodename = mkOption { default = null; description ='' diff --git a/nixos/modules/services/networking/hans.nix b/nixos/modules/services/networking/hans.nix index 4f60300f5ff41..8334dc68d623f 100644 --- a/nixos/modules/services/networking/hans.nix +++ b/nixos/modules/services/networking/hans.nix @@ -135,8 +135,7 @@ in }; }; - users.users = singleton { - name = hansUser; + users.users.${hansUser} = { description = "Hans daemon user"; isSystemUser = true; }; diff --git a/nixos/modules/services/networking/haproxy.nix b/nixos/modules/services/networking/haproxy.nix index aff71e5e97daf..4678829986c64 100644 --- a/nixos/modules/services/networking/haproxy.nix +++ b/nixos/modules/services/networking/haproxy.nix @@ -26,6 +26,18 @@ with lib; ''; }; + user = mkOption { + type = types.str; + default = "haproxy"; + description = "User account under which haproxy runs."; + }; + + group = mkOption { + type = types.str; + default = "haproxy"; + description = "Group account under which haproxy runs."; + }; + config = mkOption { type = types.nullOr types.lines; default = null; @@ -49,7 +61,8 @@ with lib; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { - DynamicUser = true; + User = cfg.user; + Group = cfg.group; Type = "notify"; # when running the config test, don't be quiet so we can see what goes wrong ExecStartPre = "${pkgs.haproxy}/sbin/haproxy -c -f ${haproxyCfg}"; @@ -60,5 +73,16 @@ with lib; AmbientCapabilities = "CAP_NET_BIND_SERVICE"; }; }; + + users.users = optionalAttrs (cfg.user == "haproxy") { + haproxy = { + group = cfg.group; + isSystemUser = true; + }; + }; + + users.groups = optionalAttrs (cfg.group == "haproxy") { + haproxy = {}; + }; }; } diff --git a/nixos/modules/services/networking/helpers.nix b/nixos/modules/services/networking/helpers.nix new file mode 100644 index 0000000000000..d7d42de0e3a86 --- /dev/null +++ b/nixos/modules/services/networking/helpers.nix @@ -0,0 +1,11 @@ +{ config, lib, ... }: '' + # Helper command to manipulate both the IPv4 and IPv6 tables. + ip46tables() { + iptables -w "$@" + ${ + lib.optionalString config.networking.enableIPv6 '' + ip6tables -w "$@" + '' + } + } +'' diff --git a/nixos/modules/services/networking/hostapd.nix b/nixos/modules/services/networking/hostapd.nix index 2915b54f05b44..12c0626a94145 100644 --- a/nixos/modules/services/networking/hostapd.nix +++ b/nixos/modules/services/networking/hostapd.nix @@ -49,6 +49,7 @@ in services.hostapd = { enable = mkOption { + type = types.bool; default = false; description = '' Enable putting a wireless interface into infrastructure mode, diff --git a/nixos/modules/services/networking/i2pd.nix b/nixos/modules/services/networking/i2pd.nix index f2be417738ee8..93a21fd4c97ef 100644 --- a/nixos/modules/services/networking/i2pd.nix +++ b/nixos/modules/services/networking/i2pd.nix @@ -159,7 +159,7 @@ let (strOpt "defaulturl" cfg.addressbook.defaulturl) ] ++ (optionalEmptyList "subscriptions" cfg.addressbook.subscriptions) ++ (flip map - (collect (proto: proto ? port && proto ? address && proto ? name) cfg.proto) + (collect (proto: proto ? port && proto ? address) cfg.proto) (proto: let protoOpts = [ (sec proto.name) (boolOpt "enabled" proto.enable) @@ -235,6 +235,10 @@ in { + imports = [ + (mkRenamedOptionModule [ "services" "i2pd" "extIp" ] [ "services" "i2pd" "address" ]) + ]; + ###### interface options = { @@ -602,7 +606,7 @@ in outTunnels = mkOption { default = {}; - type = with types; loaOf (submodule ( + type = with types; attrsOf (submodule ( { name, ... }: { options = { destinationPort = mkOption { @@ -623,7 +627,7 @@ in inTunnels = mkOption { default = {}; - type = with types; loaOf (submodule ( + type = with types; attrsOf (submodule ( { name, ... }: { options = { inPort = mkOption { diff --git a/nixos/modules/services/networking/iodine.nix b/nixos/modules/services/networking/iodine.nix index 344f84374bbd6..46051d7044b5e 100644 --- a/nixos/modules/services/networking/iodine.nix +++ b/nixos/modules/services/networking/iodine.nix @@ -9,8 +9,17 @@ let iodinedUser = "iodined"; + /* is this path made unreadable by ProtectHome = true ? */ + isProtected = x: hasPrefix "/root" x || hasPrefix "/home" x; in { + imports = [ + (mkRenamedOptionModule [ "services" "iodined" "enable" ] [ "services" "iodine" "server" "enable" ]) + (mkRenamedOptionModule [ "services" "iodined" "domain" ] [ "services" "iodine" "server" "domain" ]) + (mkRenamedOptionModule [ "services" "iodined" "ip" ] [ "services" "iodine" "server" "ip" ]) + (mkRenamedOptionModule [ "services" "iodined" "extraConfig" ] [ "services" "iodine" "server" "extraConfig" ]) + (mkRemovedOptionModule [ "services" "iodined" "client" ] "") + ]; ### configuration @@ -28,45 +37,48 @@ in corresponding attribute name. ''; example = literalExample '' - { - foo = { - server = "tunnel.mdomain.com"; - relay = "8.8.8.8"; - extraConfig = "-v"; + { + foo = { + server = "tunnel.mdomain.com"; + relay = "8.8.8.8"; + extraConfig = "-v"; + } } - } ''; - type = types.attrsOf (types.submodule ( - { - options = { - server = mkOption { - type = types.str; - default = ""; - description = "Domain or Subdomain of server running iodined"; - example = "tunnel.mydomain.com"; - }; - - relay = mkOption { - type = types.str; - default = ""; - description = "DNS server to use as a intermediate relay to the iodined server"; - example = "8.8.8.8"; - }; - - extraConfig = mkOption { - type = types.str; - default = ""; - description = "Additional command line parameters"; - example = "-l 192.168.1.10 -p 23"; - }; - - passwordFile = mkOption { - type = types.str; - default = ""; - description = "File that contains password"; - }; - }; - })); + type = types.attrsOf ( + types.submodule ( + { + options = { + server = mkOption { + type = types.str; + default = ""; + description = "Hostname of server running iodined"; + example = "tunnel.mydomain.com"; + }; + + relay = mkOption { + type = types.str; + default = ""; + description = "DNS server to use as an intermediate relay to the iodined server"; + example = "8.8.8.8"; + }; + + extraConfig = mkOption { + type = types.str; + default = ""; + description = "Additional command line parameters"; + example = "-l 192.168.1.10 -p 23"; + }; + + passwordFile = mkOption { + type = types.str; + default = ""; + description = "Path to a file containing the password."; + }; + }; + } + ) + ); }; server = { @@ -114,34 +126,69 @@ in boot.kernelModules = [ "tun" ]; systemd.services = - let - createIodineClientService = name: cfg: - { - description = "iodine client - ${name}"; - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - script = "exec ${pkgs.iodine}/bin/iodine -f -u ${iodinedUser} ${cfg.extraConfig} ${optionalString (cfg.passwordFile != "") "< \"${cfg.passwordFile}\""} ${cfg.relay} ${cfg.server}"; - serviceConfig = { - RestartSec = "30s"; - Restart = "always"; + let + createIodineClientService = name: cfg: + { + description = "iodine client - ${name}"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + script = "exec ${pkgs.iodine}/bin/iodine -f -u ${iodinedUser} ${cfg.extraConfig} ${optionalString (cfg.passwordFile != "") "< \"${builtins.toString cfg.passwordFile}\""} ${cfg.relay} ${cfg.server}"; + serviceConfig = { + RestartSec = "30s"; + Restart = "always"; + + # hardening : + # Filesystem access + ProtectSystem = "strict"; + ProtectHome = if isProtected cfg.passwordFile then "read-only" else "true" ; + PrivateTmp = true; + ReadWritePaths = "/dev/net/tun"; + PrivateDevices = false; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + # Caps + NoNewPrivileges = true; + # Misc. + LockPersonality = true; + RestrictRealtime = true; + PrivateMounts = true; + MemoryDenyWriteExecute = true; + }; + }; + in + listToAttrs ( + mapAttrsToList + (name: value: nameValuePair "iodine-${name}" (createIodineClientService name value)) + cfg.clients + ) // { + iodined = mkIf (cfg.server.enable) { + description = "iodine, ip over dns server daemon"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + script = "exec ${pkgs.iodine}/bin/iodined -f -u ${iodinedUser} ${cfg.server.extraConfig} ${optionalString (cfg.server.passwordFile != "") "< \"${builtins.toString cfg.server.passwordFile}\""} ${cfg.server.ip} ${cfg.server.domain}"; + serviceConfig = { + # Filesystem access + ProtectSystem = "strict"; + ProtectHome = if isProtected cfg.server.passwordFile then "read-only" else "true" ; + PrivateTmp = true; + ReadWritePaths = "/dev/net/tun"; + PrivateDevices = false; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + # Caps + NoNewPrivileges = true; + # Misc. + LockPersonality = true; + RestrictRealtime = true; + PrivateMounts = true; + MemoryDenyWriteExecute = true; + }; + }; }; - }; - in - listToAttrs ( - mapAttrsToList - (name: value: nameValuePair "iodine-${name}" (createIodineClientService name value)) - cfg.clients - ) // { - iodined = mkIf (cfg.server.enable) { - description = "iodine, ip over dns server daemon"; - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - script = "exec ${pkgs.iodine}/bin/iodined -f -u ${iodinedUser} ${cfg.server.extraConfig} ${optionalString (cfg.server.passwordFile != "") "< \"${cfg.server.passwordFile}\""} ${cfg.server.ip} ${cfg.server.domain}"; - }; - }; - users.users = singleton { - name = iodinedUser; + users.users.${iodinedUser} = { uid = config.ids.uids.iodined; description = "Iodine daemon user"; }; diff --git a/nixos/modules/services/networking/ircd-hybrid/default.nix b/nixos/modules/services/networking/ircd-hybrid/default.nix index f5abe61a1baf4..91d0bf437d693 100644 --- a/nixos/modules/services/networking/ircd-hybrid/default.nix +++ b/nixos/modules/services/networking/ircd-hybrid/default.nix @@ -36,12 +36,7 @@ in services.ircdHybrid = { - enable = mkOption { - default = false; - description = " - Enable IRCD. - "; - }; + enable = mkEnableOption "IRCD"; serverName = mkOption { default = "hades.arpa"; @@ -112,9 +107,8 @@ in config = mkIf config.services.ircdHybrid.enable { - users.users = singleton - { name = "ircd"; - description = "IRCD owner"; + users.users.ircd = + { description = "IRCD owner"; group = "ircd"; uid = config.ids.uids.ircd; }; diff --git a/nixos/modules/services/networking/iwd.nix b/nixos/modules/services/networking/iwd.nix index 839fa48d9a423..6be67a8b96f4d 100644 --- a/nixos/modules/services/networking/iwd.nix +++ b/nixos/modules/services/networking/iwd.nix @@ -23,12 +23,7 @@ in { systemd.packages = [ pkgs.iwd ]; systemd.services.iwd.wantedBy = [ "multi-user.target" ]; - - systemd.tmpfiles.rules = [ - "d /var/lib/iwd 0700 root root -" - "d /var/lib/ead 0700 root root -" - ]; }; - meta.maintainers = with lib.maintainers; [ mic92 ]; + meta.maintainers = with lib.maintainers; [ mic92 dtzWill ]; } diff --git a/nixos/modules/services/networking/keybase.nix b/nixos/modules/services/networking/keybase.nix index 85f52be8a6ac9..495102cb7eeee 100644 --- a/nixos/modules/services/networking/keybase.nix +++ b/nixos/modules/services/networking/keybase.nix @@ -24,13 +24,18 @@ in { config = mkIf cfg.enable { + # Upstream: https://github.com/keybase/client/blob/master/packaging/linux/systemd/keybase.service systemd.user.services.keybase = { description = "Keybase service"; unitConfig.ConditionUser = "!@system"; + environment.KEYBASE_SERVICE_TYPE = "systemd"; serviceConfig = { - ExecStart = '' - ${pkgs.keybase}/bin/keybase service --auto-forked - ''; + Type = "notify"; + EnvironmentFile = [ + "-%E/keybase/keybase.autogen.env" + "-%E/keybase/keybase.env" + ]; + ExecStart = "${pkgs.keybase}/bin/keybase service"; Restart = "on-failure"; PrivateTmp = true; }; diff --git a/nixos/modules/services/networking/kippo.nix b/nixos/modules/services/networking/kippo.nix index bdea6a1d1caab..553415a2f3297 100644 --- a/nixos/modules/services/networking/kippo.nix +++ b/nixos/modules/services/networking/kippo.nix @@ -73,12 +73,11 @@ in ${cfg.extraConfig} ''; - users.users = singleton { - name = "kippo"; + users.users.kippo = { description = "kippo web server privilege separation user"; uid = 108; # why does config.ids.uids.kippo give an error? }; - users.groups = singleton { name = "kippo";gid=108; }; + users.groups.kippo.gid = 108; systemd.services.kippo = with pkgs; { description = "Kippo Web Server"; diff --git a/nixos/modules/services/networking/knot.nix b/nixos/modules/services/networking/knot.nix index 1cc1dd3f2f62b..12ff89fe84923 100644 --- a/nixos/modules/services/networking/knot.nix +++ b/nixos/modules/services/networking/knot.nix @@ -5,14 +5,16 @@ with lib; let cfg = config.services.knot; - configFile = pkgs.writeText "knot.conf" cfg.extraConfig; - socketFile = "/run/knot/knot.sock"; + configFile = pkgs.writeTextFile { + name = "knot.conf"; + text = (concatMapStringsSep "\n" (file: "include: ${file}") cfg.keyFiles) + "\n" + + cfg.extraConfig; + checkPhase = lib.optionalString (cfg.keyFiles == []) '' + ${cfg.package}/bin/knotc --config=$out conf-check + ''; + }; - knotConfCheck = file: pkgs.runCommand "knot-config-checked" - { buildInputs = [ cfg.package ]; } '' - ln -s ${configFile} $out - knotc --config=${configFile} conf-check - ''; + socketFile = "/run/knot/knot.sock"; knot-cli-wrappers = pkgs.stdenv.mkDerivation { name = "knot-cli-wrappers"; @@ -45,6 +47,19 @@ in { ''; }; + keyFiles = mkOption { + type = types.listOf types.path; + default = []; + description = '' + A list of files containing additional configuration + to be included using the include directive. This option + allows to include configuration like TSIG keys without + exposing them to the nix store readable to any process. + Note that using this option will also disable configuration + checks at build time. + ''; + }; + extraConfig = mkOption { type = types.lines; default = ""; @@ -56,6 +71,7 @@ in { package = mkOption { type = types.package; default = pkgs.knot-dns; + defaultText = "pkgs.knot-dns"; description = '' Which Knot DNS package to use ''; @@ -64,6 +80,13 @@ in { }; config = mkIf config.services.knot.enable { + users.users.knot = { + isSystemUser = true; + group = "knot"; + description = "Knot daemon user"; + }; + + users.groups.knot.gid = null; systemd.services.knot = { unitConfig.Documentation = "man:knotd(8) man:knot.conf(5) man:knotc(8) https://www.knot-dns.cz/docs/${cfg.package.version}/html/"; description = cfg.package.meta.description; @@ -73,12 +96,12 @@ in { serviceConfig = { Type = "notify"; - ExecStart = "${cfg.package}/bin/knotd --config=${knotConfCheck configFile} --socket=${socketFile} ${concatStringsSep " " cfg.extraArgs}"; + ExecStart = "${cfg.package}/bin/knotd --config=${configFile} --socket=${socketFile} ${concatStringsSep " " cfg.extraArgs}"; ExecReload = "${knot-cli-wrappers}/bin/knotc reload"; CapabilityBoundingSet = "CAP_NET_BIND_SERVICE CAP_SETPCAP"; AmbientCapabilities = "CAP_NET_BIND_SERVICE CAP_SETPCAP"; NoNewPrivileges = true; - DynamicUser = "yes"; + User = "knot"; RuntimeDirectory = "knot"; StateDirectory = "knot"; StateDirectoryMode = "0700"; @@ -92,4 +115,3 @@ in { environment.systemPackages = [ knot-cli-wrappers ]; }; } - diff --git a/nixos/modules/services/networking/kresd.nix b/nixos/modules/services/networking/kresd.nix index fc516c01230a3..c5a84eebd46f4 100644 --- a/nixos/modules/services/networking/kresd.nix +++ b/nixos/modules/services/networking/kresd.nix @@ -3,16 +3,53 @@ with lib; let - cfg = config.services.kresd; - package = pkgs.knot-resolver; - configFile = pkgs.writeText "kresd.conf" cfg.extraConfig; -in + # Convert systemd-style address specification to kresd config line(s). + # On Nix level we don't attempt to precisely validate the address specifications. + mkListen = kind: addr: let + al_v4 = builtins.match "([0-9.]\+):([0-9]\+)" addr; + al_v6 = builtins.match "\\[(.\+)]:([0-9]\+)" addr; + al_portOnly = builtins.match "()([0-9]\+)" addr; + al = findFirst (a: a != null) + (throw "services.kresd.*: incorrect address specification '${addr}'") + [ al_v4 al_v6 al_portOnly ]; + port = last al; + addrSpec = if al_portOnly == null then "'${head al}'" else "{'::', '127.0.0.1'}"; + in # freebind is set for compatibility with earlier kresd services; + # it could be configurable, for example. + '' + net.listen(${addrSpec}, ${port}, { kind = '${kind}', freebind = true }) + ''; -{ + configFile = pkgs.writeText "kresd.conf" ( + optionalString (cfg.listenDoH != []) '' + modules.load('http') + '' + + concatMapStrings (mkListen "dns") cfg.listenPlain + + concatMapStrings (mkListen "tls") cfg.listenTLS + + concatMapStrings (mkListen "doh") cfg.listenDoH + + cfg.extraConfig + ); + + package = if cfg.listenDoH == [] + then pkgs.knot-resolver # never force `extraFeatures = false` + else pkgs.knot-resolver.override { extraFeatures = true; }; +in { meta.maintainers = [ maintainers.vcunat /* upstream developer */ ]; + imports = [ + (mkChangedOptionModule [ "services" "kresd" "interfaces" ] [ "services" "kresd" "listenPlain" ] + (config: + let value = getAttrFromPath [ "services" "kresd" "interfaces" ] config; + in map + (iface: if elem ":" (stringToCharacters iface) then "[${iface}]:53" else "${iface}:53") # Syntax depends on being IPv6 or IPv4. + value + ) + ) + (mkRemovedOptionModule [ "services" "kresd" "cacheDir" ] "Please use (bind-)mounting instead.") + ]; + ###### interface options.services.kresd = { enable = mkOption { @@ -21,8 +58,8 @@ in description = '' Whether to enable knot-resolver domain name server. DNSSEC validation is turned on by default. - You can run <literal>sudo nc -U /run/kresd/control</literal> - and give commands interactively to kresd. + You can run <literal>sudo nc -U /run/knot-resolver/control/1</literal> + and give commands interactively to kresd@1.service. ''; }; extraConfig = mkOption { @@ -32,105 +69,77 @@ in Extra lines to be added verbatim to the generated configuration file. ''; }; - cacheDir = mkOption { - type = types.path; - default = "/var/cache/kresd"; + listenPlain = mkOption { + type = with types; listOf str; + default = [ "[::1]:53" "127.0.0.1:53" ]; + example = [ "53" ]; description = '' - Directory for caches. They are intended to survive reboots. + What addresses and ports the server should listen on. + For detailed syntax see ListenStream in man systemd.socket. ''; }; - interfaces = mkOption { + listenTLS = mkOption { type = with types; listOf str; - default = [ "::1" "127.0.0.1" ]; + default = []; + example = [ "198.51.100.1:853" "[2001:db8::1]:853" "853" ]; description = '' - What addresses the server should listen on. (UDP+TCP 53) + Addresses and ports on which kresd should provide DNS over TLS (see RFC 7858). + For detailed syntax see ListenStream in man systemd.socket. ''; }; - listenTLS = mkOption { + listenDoH = mkOption { type = with types; listOf str; default = []; - example = [ "198.51.100.1:853" "[2001:db8::1]:853" "853" ]; + example = [ "198.51.100.1:443" "[2001:db8::1]:443" "443" ]; description = '' - Addresses on which kresd should provide DNS over TLS (see RFC 7858). + Addresses and ports on which kresd should provide DNS over HTTPS (see RFC 8484). For detailed syntax see ListenStream in man systemd.socket. ''; }; + instances = mkOption { + type = types.ints.unsigned; + default = 1; + description = '' + The number of instances to start. They will be called kresd@{1,2,...}.service. + Knot Resolver uses no threads, so this is the way to scale. + You can dynamically start/stop them at will, so this is just system default. + ''; + }; # TODO: perhaps options for more common stuff like cache size or forwarding }; ###### implementation config = mkIf cfg.enable { - environment.etc."kresd.conf".source = configFile; # not required + environment.etc."knot-resolver/kresd.conf".source = configFile; # not required - users.users = singleton - { name = "kresd"; - uid = config.ids.uids.kresd; - group = "kresd"; + users.users.knot-resolver = + { isSystemUser = true; + group = "knot-resolver"; description = "Knot-resolver daemon user"; }; - users.groups = singleton - { name = "kresd"; - gid = config.ids.gids.kresd; - }; + users.groups.knot-resolver.gid = null; - systemd.sockets.kresd = rec { - wantedBy = [ "sockets.target" ]; - before = wantedBy; - listenStreams = map - # Syntax depends on being IPv6 or IPv4. - (iface: if elem ":" (stringToCharacters iface) then "[${iface}]:53" else "${iface}:53") - cfg.interfaces; - socketConfig = { - ListenDatagram = listenStreams; - FreeBind = true; - FileDescriptorName = "dns"; - }; - }; + systemd.packages = [ package ]; # the units are patched inside the package a bit - systemd.sockets.kresd-tls = mkIf (cfg.listenTLS != []) rec { - wantedBy = [ "sockets.target" ]; - before = wantedBy; - partOf = [ "kresd.socket" ]; - listenStreams = cfg.listenTLS; - socketConfig = { - FileDescriptorName = "tls"; - FreeBind = true; - Service = "kresd.service"; - }; + systemd.targets.kresd = { # configure units started by default + wantedBy = [ "multi-user.target" ]; + wants = [ "kres-cache-gc.service" ] + ++ map (i: "kresd@${toString i}.service") (range 1 cfg.instances); }; - - systemd.sockets.kresd-control = rec { - wantedBy = [ "sockets.target" ]; - before = wantedBy; - partOf = [ "kresd.socket" ]; - listenStreams = [ "/run/kresd/control" ]; - socketConfig = { - FileDescriptorName = "control"; - Service = "kresd.service"; - SocketMode = "0660"; # only root user/group may connect and control kresd - }; + systemd.services."kresd@".serviceConfig = { + ExecStart = "${package}/bin/kresd --noninteractive " + + "-c ${package}/lib/knot-resolver/distro-preconfig.lua -c ${configFile}"; + # Ensure correct ownership in case UID or GID changes. + CacheDirectory = "knot-resolver"; + CacheDirectoryMode = "0750"; }; - systemd.tmpfiles.rules = [ "d '${cfg.cacheDir}' 0770 kresd kresd - -" ]; + environment.etc."tmpfiles.d/knot-resolver.conf".source = + "${package}/lib/tmpfiles.d/knot-resolver.conf"; - systemd.services.kresd = { - description = "Knot-resolver daemon"; - - serviceConfig = { - User = "kresd"; - Type = "notify"; - WorkingDirectory = cfg.cacheDir; - Restart = "on-failure"; - Sockets = [ "kresd.socket" "kresd-control.socket" ] - ++ optional (cfg.listenTLS != []) "kresd-tls.socket"; - }; - - # Trust anchor goes from dns-root-data by default. - script = '' - exec '${package}/bin/kresd' --config '${configFile}' --forks=1 - ''; - - requires = [ "kresd.socket" ]; - }; + # Try cleaning up the previously default location of cache file. + # Note that /var/cache/* should always be safe to remove. + # TODO: remove later, probably between 20.09 and 21.03 + systemd.tmpfiles.rules = [ "R /var/cache/kresd" ]; }; } diff --git a/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix b/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix new file mode 100644 index 0000000000000..09d357cd2b6e4 --- /dev/null +++ b/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix @@ -0,0 +1,28 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.magic-wormhole-mailbox-server; + dataDir = "/var/lib/magic-wormhole-mailbox-server;"; + python = pkgs.python3.withPackages (py: [ py.magic-wormhole-mailbox-server py.twisted ]); +in +{ + options.services.magic-wormhole-mailbox-server = { + enable = mkEnableOption "Enable Magic Wormhole Mailbox Server"; + }; + + config = mkIf cfg.enable { + systemd.services.magic-wormhole-mailbox-server = { + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + DynamicUser = true; + ExecStart = "${python}/bin/twistd --nodaemon wormhole-mailbox"; + WorkingDirectory = dataDir; + StateDirectory = baseNameOf dataDir; + }; + }; + + }; +} diff --git a/nixos/modules/services/networking/mailpile.nix b/nixos/modules/services/networking/mailpile.nix index c42d3d5a44cb0..b79ee11d17db2 100644 --- a/nixos/modules/services/networking/mailpile.nix +++ b/nixos/modules/services/networking/mailpile.nix @@ -18,12 +18,8 @@ in options = { services.mailpile = { - enable = mkOption { - default = false; - description = " - Whether to enable Mailpile the mail client. - "; - }; + enable = mkEnableOption "Mailpile the mail client"; + hostname = mkOption { default = "localhost"; description = "Listen to this hostname or ip."; diff --git a/nixos/modules/services/networking/matterbridge.nix b/nixos/modules/services/networking/matterbridge.nix index 682eaa6eb297b..b8b4f37c84a89 100644 --- a/nixos/modules/services/networking/matterbridge.nix +++ b/nixos/modules/services/networking/matterbridge.nix @@ -92,14 +92,15 @@ in warnings = optional options.services.matterbridge.configFile.isDefined "The option services.matterbridge.configFile is insecure and should be replaced with services.matterbridge.configPath"; - users.users = optional (cfg.user == "matterbridge") - { name = "matterbridge"; - group = "matterbridge"; - isSystemUser = true; + users.users = optionalAttrs (cfg.user == "matterbridge") + { matterbridge = { + group = "matterbridge"; + isSystemUser = true; + }; }; - users.groups = optional (cfg.group == "matterbridge") - { name = "matterbridge"; + users.groups = optionalAttrs (cfg.group == "matterbridge") + { matterbridge = { }; }; systemd.services.matterbridge = { @@ -110,7 +111,7 @@ in serviceConfig = { User = cfg.user; Group = cfg.group; - ExecStart = "${pkgs.matterbridge.bin}/bin/matterbridge -conf ${matterbridgeConfToml}"; + ExecStart = "${pkgs.matterbridge}/bin/matterbridge -conf ${matterbridgeConfToml}"; Restart = "always"; RestartSec = "10"; }; diff --git a/nixos/modules/services/networking/minidlna.nix b/nixos/modules/services/networking/minidlna.nix index 3ddea3c9757b3..c580ba47dad33 100644 --- a/nixos/modules/services/networking/minidlna.nix +++ b/nixos/modules/services/networking/minidlna.nix @@ -95,6 +95,22 @@ in ''; }; + services.minidlna.announceInterval = mkOption { + type = types.int; + default = 895; + description = + '' + The interval between announces (in seconds). + + By default miniDLNA will announce its presence on the network + approximately every 15 minutes. + + Many people prefer shorter announce intervals (e.g. 60 seconds) + on their home networks, especially when DLNA clients are + started on demand. + ''; + }; + services.minidlna.config = mkOption { type = types.lines; description = @@ -144,6 +160,7 @@ in ${concatMapStrings (dir: '' media_dir=${dir} '') cfg.mediaDirs} + notify_interval=${toString cfg.announceInterval} ${cfg.extraConfig} ''; diff --git a/nixos/modules/services/networking/mjpg-streamer.nix b/nixos/modules/services/networking/mjpg-streamer.nix index e0a6c112e3cbf..dbc35e2e71c00 100644 --- a/nixos/modules/services/networking/mjpg-streamer.nix +++ b/nixos/modules/services/networking/mjpg-streamer.nix @@ -49,10 +49,11 @@ in { config = mkIf cfg.enable { - users.users = optional (cfg.user == "mjpg-streamer") { - name = "mjpg-streamer"; - uid = config.ids.uids.mjpg-streamer; - group = cfg.group; + users.users = optionalAttrs (cfg.user == "mjpg-streamer") { + mjpg-streamer = { + uid = config.ids.uids.mjpg-streamer; + group = cfg.group; + }; }; systemd.services.mjpg-streamer = { diff --git a/nixos/modules/services/networking/monero.nix b/nixos/modules/services/networking/monero.nix index 831e4d60d8da3..b953643086827 100644 --- a/nixos/modules/services/networking/monero.nix +++ b/nixos/modules/services/networking/monero.nix @@ -197,17 +197,15 @@ in config = mkIf cfg.enable { - users.users = singleton { - name = "monero"; + users.users.monero = { uid = config.ids.uids.monero; description = "Monero daemon user"; home = dataDir; createHome = true; }; - users.groups = singleton { - name = "monero"; - gid = config.ids.gids.monero; + users.groups.monero = { + gid = config.ids.gids.monero; }; systemd.services.monero = { @@ -224,15 +222,17 @@ in }; }; - assertions = singleton { - assertion = cfg.mining.enable -> cfg.mining.address != ""; - message = '' + assertions = singleton { + assertion = cfg.mining.enable -> cfg.mining.address != ""; + message = '' You need a Monero address to receive mining rewards: specify one using option monero.mining.address. - ''; - }; + ''; + }; }; + meta.maintainers = with lib.maintainers; [ rnhmjoj ]; + } diff --git a/nixos/modules/services/networking/mullvad-vpn.nix b/nixos/modules/services/networking/mullvad-vpn.nix new file mode 100644 index 0000000000000..cc98414257ca7 --- /dev/null +++ b/nixos/modules/services/networking/mullvad-vpn.nix @@ -0,0 +1,43 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.services.mullvad-vpn; +in +with lib; +{ + options.services.mullvad-vpn.enable = mkOption { + type = types.bool; + default = false; + description = '' + This option enables Mullvad VPN daemon. + ''; + }; + + config = mkIf cfg.enable { + boot.kernelModules = [ "tun" ]; + + systemd.services.mullvad-daemon = { + description = "Mullvad VPN daemon"; + wantedBy = [ "multi-user.target" ]; + wants = [ "network.target" ]; + after = [ + "network-online.target" + "NetworkManager.service" + "systemd-resolved.service" + ]; + path = [ + pkgs.iproute + # Needed for ping + "/run/wrappers" + ]; + serviceConfig = { + StartLimitBurst = 5; + StartLimitIntervalSec = 20; + ExecStart = "${pkgs.mullvad-vpn}/bin/mullvad-daemon -v --disable-stdout-timestamps"; + Restart = "always"; + RestartSec = 1; + }; + }; + }; + + meta.maintainers = [ maintainers.xfix ]; +} diff --git a/nixos/modules/services/networking/murmur.nix b/nixos/modules/services/networking/murmur.nix index 082953d2f6ab7..3054ae1b201f5 100644 --- a/nixos/modules/services/networking/murmur.nix +++ b/nixos/modules/services/networking/murmur.nix @@ -46,6 +46,11 @@ let ''; in { + imports = [ + (mkRenamedOptionModule [ "services" "murmur" "welcome" ] [ "services" "murmur" "welcometext" ]) + (mkRemovedOptionModule [ "services" "murmur" "pidfile" ] "Hardcoded to /run/murmur/murmurd.pid now") + ]; + options = { services.murmur = { enable = mkOption { diff --git a/nixos/modules/services/networking/mxisd.nix b/nixos/modules/services/networking/mxisd.nix index a3d61922e5785..482d6ff456b16 100644 --- a/nixos/modules/services/networking/mxisd.nix +++ b/nixos/modules/services/networking/mxisd.nix @@ -93,23 +93,19 @@ in { }; config = mkIf cfg.enable { - users.users = [ + users.users.mxisd = { - name = "mxisd"; group = "mxisd"; home = cfg.dataDir; createHome = true; shell = "${pkgs.bash}/bin/bash"; uid = config.ids.uids.mxisd; - } - ]; + }; - users.groups = [ + users.groups.mxisd = { - name = "mxisd"; gid = config.ids.gids.mxisd; - } - ]; + }; systemd.services.mxisd = { description = "a federated identity server for the matrix ecosystem"; diff --git a/nixos/modules/services/networking/namecoind.nix b/nixos/modules/services/networking/namecoind.nix index c8ee0a2f56474..ead7f0859434d 100644 --- a/nixos/modules/services/networking/namecoind.nix +++ b/nixos/modules/services/networking/namecoind.nix @@ -154,16 +154,14 @@ in config = ${configFile} ''; - users.users = singleton { - name = "namecoin"; + users.users.namecoin = { uid = config.ids.uids.namecoin; description = "Namecoin daemon user"; home = dataDir; createHome = true; }; - users.groups = singleton { - name = "namecoin"; + users.groups.namecoin = { gid = config.ids.gids.namecoin; }; @@ -201,4 +199,6 @@ in }; + meta.maintainers = with lib.maintainers; [ rnhmjoj ]; + } diff --git a/nixos/modules/services/networking/nat.nix b/nixos/modules/services/networking/nat.nix index 5681bda51cb44..21ae9eb8b6d46 100644 --- a/nixos/modules/services/networking/nat.nix +++ b/nixos/modules/services/networking/nat.nix @@ -7,26 +7,33 @@ with lib; let - cfg = config.networking.nat; dest = if cfg.externalIP == null then "-j MASQUERADE" else "-j SNAT --to-source ${cfg.externalIP}"; + helpers = import ./helpers.nix { inherit config lib; }; + flushNat = '' - iptables -w -t nat -D PREROUTING -j nixos-nat-pre 2>/dev/null|| true - iptables -w -t nat -F nixos-nat-pre 2>/dev/null || true - iptables -w -t nat -X nixos-nat-pre 2>/dev/null || true - iptables -w -t nat -D POSTROUTING -j nixos-nat-post 2>/dev/null || true - iptables -w -t nat -F nixos-nat-post 2>/dev/null || true - iptables -w -t nat -X nixos-nat-post 2>/dev/null || true + ${helpers} + ip46tables -w -t nat -D PREROUTING -j nixos-nat-pre 2>/dev/null|| true + ip46tables -w -t nat -F nixos-nat-pre 2>/dev/null || true + ip46tables -w -t nat -X nixos-nat-pre 2>/dev/null || true + ip46tables -w -t nat -D POSTROUTING -j nixos-nat-post 2>/dev/null || true + ip46tables -w -t nat -F nixos-nat-post 2>/dev/null || true + ip46tables -w -t nat -X nixos-nat-post 2>/dev/null || true + ip46tables -w -t nat -D OUTPUT -j nixos-nat-out 2>/dev/null || true + ip46tables -w -t nat -F nixos-nat-out 2>/dev/null || true + ip46tables -w -t nat -X nixos-nat-out 2>/dev/null || true ${cfg.extraStopCommands} ''; setupNat = '' + ${helpers} # Create subchain where we store rules - iptables -w -t nat -N nixos-nat-pre - iptables -w -t nat -N nixos-nat-post + ip46tables -w -t nat -N nixos-nat-pre + ip46tables -w -t nat -N nixos-nat-post + ip46tables -w -t nat -N nixos-nat-out # We can't match on incoming interface in POSTROUTING, so # mark packets coming from the internal interfaces. @@ -58,10 +65,10 @@ let let m = builtins.match "([0-9.]+):([0-9-]+)" fwd.destination; destinationIP = if (m == null) then throw "bad ip:ports `${fwd.destination}'" else elemAt m 0; - destinationPorts = if (m == null) then throw "bad ip:ports `${fwd.destination}'" else elemAt m 1; + destinationPorts = if (m == null) then throw "bad ip:ports `${fwd.destination}'" else builtins.replaceStrings ["-"] [":"] (elemAt m 1); in '' # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from the host itself - iptables -w -t nat -A OUTPUT \ + iptables -w -t nat -A nixos-nat-out \ -d ${loopbackip} -p ${fwd.proto} \ --dport ${builtins.toString fwd.sourcePort} \ -j DNAT --to-destination ${fwd.destination} @@ -88,8 +95,9 @@ let ${cfg.extraCommands} # Append our chains to the nat tables - iptables -w -t nat -A PREROUTING -j nixos-nat-pre - iptables -w -t nat -A POSTROUTING -j nixos-nat-post + ip46tables -w -t nat -A PREROUTING -j nixos-nat-pre + ip46tables -w -t nat -A POSTROUTING -j nixos-nat-post + ip46tables -w -t nat -A OUTPUT -j nixos-nat-out ''; in diff --git a/nixos/modules/services/networking/ndppd.nix b/nixos/modules/services/networking/ndppd.nix index 92088623517f8..77e979a8a424f 100644 --- a/nixos/modules/services/networking/ndppd.nix +++ b/nixos/modules/services/networking/ndppd.nix @@ -43,7 +43,7 @@ let timeout = mkOption { type = types.int; description = '' - Controls how long to wait for a Neighbor Advertisment Message before + Controls how long to wait for a Neighbor Advertisment Message before invalidating the entry, in milliseconds. ''; default = 500; @@ -51,7 +51,7 @@ let ttl = mkOption { type = types.int; description = '' - Controls how long a valid or invalid entry remains in the cache, in + Controls how long a valid or invalid entry remains in the cache, in milliseconds. ''; default = 30000; @@ -142,7 +142,11 @@ in { messages, and respond to them according to a set of rules. ''; default = {}; - example = { eth0.rules."1111::/64" = {}; }; + example = literalExample '' + { + eth0.rules."1111::/64" = {}; + } + ''; }; }; @@ -161,7 +165,25 @@ in { documentation = [ "man:ndppd(1)" "man:ndppd.conf(5)" ]; after = [ "network-pre.target" ]; wantedBy = [ "multi-user.target" ]; - serviceConfig.ExecStart = "${pkgs.ndppd}/bin/ndppd -c ${ndppdConf}"; + serviceConfig = { + ExecStart = "${pkgs.ndppd}/bin/ndppd -c ${ndppdConf}"; + + # Sandboxing + CapabilityBoundingSet = "CAP_NET_RAW CAP_NET_ADMIN"; + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + RestrictAddressFamilies = "AF_INET6 AF_PACKET AF_NETLINK"; + RestrictNamespaces = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + }; }; }; } diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix index 90d1032c41b43..6f24141b33cfd 100644 --- a/nixos/modules/services/networking/networkmanager.nix +++ b/nixos/modules/services/networking/networkmanager.nix @@ -131,6 +131,10 @@ let in { + meta = { + maintainers = teams.freedesktop.members; + }; + ###### interface options = { @@ -308,6 +312,7 @@ in { if [ "$2" != "up" ]; then logger "exit: event $2 != up" + exit fi # coreutils and iproute are in PATH too @@ -336,6 +341,7 @@ in { }; imports = [ + (mkRenamedOptionModule [ "networking" "networkmanager" "useDnsmasq" ] [ "networking" "networkmanager" "dns" ]) (mkRemovedOptionModule ["networking" "networkmanager" "dynamicHosts"] '' This option was removed because allowing (multiple) regular users to override host entries affecting the whole system opens up a huge attack @@ -360,62 +366,59 @@ in { } ]; - environment.etc = with pkgs; [ - { source = configFile; - target = "NetworkManager/NetworkManager.conf"; - } - { source = "${networkmanager-openvpn}/lib/NetworkManager/VPN/nm-openvpn-service.name"; - target = "NetworkManager/VPN/nm-openvpn-service.name"; - } - { source = "${networkmanager-vpnc}/lib/NetworkManager/VPN/nm-vpnc-service.name"; - target = "NetworkManager/VPN/nm-vpnc-service.name"; - } - { source = "${networkmanager-openconnect}/lib/NetworkManager/VPN/nm-openconnect-service.name"; - target = "NetworkManager/VPN/nm-openconnect-service.name"; - } - { source = "${networkmanager-fortisslvpn}/lib/NetworkManager/VPN/nm-fortisslvpn-service.name"; - target = "NetworkManager/VPN/nm-fortisslvpn-service.name"; - } - { source = "${networkmanager-l2tp}/lib/NetworkManager/VPN/nm-l2tp-service.name"; - target = "NetworkManager/VPN/nm-l2tp-service.name"; - } - { source = "${networkmanager-iodine}/lib/NetworkManager/VPN/nm-iodine-service.name"; - target = "NetworkManager/VPN/nm-iodine-service.name"; + environment.etc = with pkgs; { + "NetworkManager/NetworkManager.conf".source = configFile; + + "NetworkManager/VPN/nm-openvpn-service.name".source = + "${networkmanager-openvpn}/lib/NetworkManager/VPN/nm-openvpn-service.name"; + + "NetworkManager/VPN/nm-vpnc-service.name".source = + "${networkmanager-vpnc}/lib/NetworkManager/VPN/nm-vpnc-service.name"; + + "NetworkManager/VPN/nm-openconnect-service.name".source = + "${networkmanager-openconnect}/lib/NetworkManager/VPN/nm-openconnect-service.name"; + + "NetworkManager/VPN/nm-fortisslvpn-service.name".source = + "${networkmanager-fortisslvpn}/lib/NetworkManager/VPN/nm-fortisslvpn-service.name"; + + "NetworkManager/VPN/nm-l2tp-service.name".source = + "${networkmanager-l2tp}/lib/NetworkManager/VPN/nm-l2tp-service.name"; + + "NetworkManager/VPN/nm-iodine-service.name".source = + "${networkmanager-iodine}/lib/NetworkManager/VPN/nm-iodine-service.name"; } - ] ++ optional (cfg.appendNameservers != [] || cfg.insertNameservers != []) - { source = overrideNameserversScript; - target = "NetworkManager/dispatcher.d/02overridedns"; - } - ++ lib.imap1 (i: s: { - inherit (s) source; - target = "NetworkManager/dispatcher.d/${dispatcherTypesSubdirMap.${s.type}}03userscript${lib.fixedWidthNumber 4 i}"; - mode = "0544"; - }) cfg.dispatcherScripts - ++ optional cfg.enableStrongSwan - { source = "${pkgs.networkmanager_strongswan}/lib/NetworkManager/VPN/nm-strongswan-service.name"; - target = "NetworkManager/VPN/nm-strongswan-service.name"; - }; + // optionalAttrs (cfg.appendNameservers != [] || cfg.insertNameservers != []) + { + "NetworkManager/dispatcher.d/02overridedns".source = overrideNameserversScript; + } + // optionalAttrs cfg.enableStrongSwan + { + "NetworkManager/VPN/nm-strongswan-service.name".source = + "${pkgs.networkmanager_strongswan}/lib/NetworkManager/VPN/nm-strongswan-service.name"; + } + // listToAttrs (lib.imap1 (i: s: + { + name = "NetworkManager/dispatcher.d/${dispatcherTypesSubdirMap.${s.type}}03userscript${lib.fixedWidthNumber 4 i}"; + value = { mode = "0544"; inherit (s) source; }; + }) cfg.dispatcherScripts); environment.systemPackages = cfg.packages; - users.groups = [{ - name = "networkmanager"; - gid = config.ids.gids.networkmanager; - } - { - name = "nm-openvpn"; - gid = config.ids.gids.nm-openvpn; - }]; - users.users = [{ - name = "nm-openvpn"; - uid = config.ids.uids.nm-openvpn; - extraGroups = [ "networkmanager" ]; - } - { - name = "nm-iodine"; - isSystemUser = true; - group = "networkmanager"; - }]; + users.groups = { + networkmanager.gid = config.ids.gids.networkmanager; + nm-openvpn.gid = config.ids.gids.nm-openvpn; + }; + + users.users = { + nm-openvpn = { + uid = config.ids.uids.nm-openvpn; + extraGroups = [ "networkmanager" ]; + }; + nm-iodine = { + isSystemUser = true; + group = "networkmanager"; + }; + }; systemd.packages = cfg.packages; diff --git a/nixos/modules/services/networking/nftables.nix b/nixos/modules/services/networking/nftables.nix index ad7c013a54491..ec9d9753cfe25 100644 --- a/nixos/modules/services/networking/nftables.nix +++ b/nixos/modules/services/networking/nftables.nix @@ -52,7 +52,7 @@ in ip protocol icmp icmp type { destination-unreachable, router-advertisement, time-exceeded, parameter-problem } accept # allow "ping" - ip6 nexthdr icmp icmpv6 type echo-request accept + ip6 nexthdr icmpv6 icmpv6 type echo-request accept ip protocol icmp icmp type echo-request accept # accept SSH connections (required for a server) diff --git a/nixos/modules/services/networking/nix-store-gcs-proxy.nix b/nixos/modules/services/networking/nix-store-gcs-proxy.nix new file mode 100644 index 0000000000000..3f2ce5bca4da4 --- /dev/null +++ b/nixos/modules/services/networking/nix-store-gcs-proxy.nix @@ -0,0 +1,75 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + opts = { name, config, ... }: { + options = { + enable = mkOption { + default = true; + type = types.bool; + example = true; + description = "Whether to enable proxy for this bucket"; + }; + bucketName = mkOption { + type = types.str; + default = name; + example = "my-bucket-name"; + description = "Name of Google storage bucket"; + }; + address = mkOption { + type = types.str; + example = "localhost:3000"; + description = "The address of the proxy."; + }; + }; + }; + enabledProxies = lib.filterAttrs (n: v: v.enable) config.services.nix-store-gcs-proxy; + mapProxies = function: lib.mkMerge (lib.mapAttrsToList function enabledProxies); +in +{ + options.services.nix-store-gcs-proxy = mkOption { + type = types.attrsOf (types.submodule opts); + default = {}; + description = '' + An attribute set describing an HTTP to GCS proxy that allows us to use GCS + bucket via HTTP protocol. + ''; + }; + + config.systemd.services = mapProxies (name: cfg: { + "nix-store-gcs-proxy-${name}" = { + description = "A HTTP nix store that proxies requests to Google Storage"; + wantedBy = ["multi-user.target"]; + + serviceConfig = { + RestartSec = 5; + StartLimitInterval = 10; + ExecStart = '' + ${pkgs.nix-store-gcs-proxy}/bin/nix-store-gcs-proxy \ + --bucket-name ${cfg.bucketName} \ + --addr ${cfg.address} + ''; + + DynamicUser = true; + + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + PrivateMounts = true; + PrivateUsers = true; + + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + + NoNewPrivileges = true; + LockPersonality = true; + RestrictRealtime = true; + }; + }; + }); + + meta.maintainers = [ maintainers.mrkkrp ]; +} diff --git a/nixos/modules/services/networking/nntp-proxy.nix b/nixos/modules/services/networking/nntp-proxy.nix index d24d6f77a4916..cc061bf6e3b91 100644 --- a/nixos/modules/services/networking/nntp-proxy.nix +++ b/nixos/modules/services/networking/nntp-proxy.nix @@ -210,9 +210,8 @@ in config = mkIf cfg.enable { - users.users = singleton - { name = proxyUser; - uid = config.ids.uids.nntp-proxy; + users.users.${proxyUser} = + { uid = config.ids.uids.nntp-proxy; description = "NNTP-Proxy daemon user"; }; diff --git a/nixos/modules/services/networking/nsd.nix b/nixos/modules/services/networking/nsd.nix index bc0966e6b8e6a..429580e5c6c48 100644 --- a/nixos/modules/services/networking/nsd.nix +++ b/nixos/modules/services/networking/nsd.nix @@ -244,7 +244,7 @@ let }; data = mkOption { - type = types.str; + type = types.lines; default = ""; example = ""; description = '' @@ -484,7 +484,7 @@ in }; extraConfig = mkOption { - type = types.str; + type = types.lines; default = ""; description = '' Extra nsd config. @@ -899,13 +899,9 @@ in environment.systemPackages = [ nsdPkg ]; - users.groups = singleton { - name = username; - gid = config.ids.gids.nsd; - }; + users.groups.${username}.gid = config.ids.gids.nsd; - users.users = singleton { - name = username; + users.users.${username} = { description = "NSD service user"; home = stateDir; createHome = true; diff --git a/nixos/modules/services/networking/ntp/chrony.nix b/nixos/modules/services/networking/ntp/chrony.nix index c74476c7a1558..b7e4c89a155cd 100644 --- a/nixos/modules/services/networking/ntp/chrony.nix +++ b/nixos/modules/services/networking/ntp/chrony.nix @@ -30,6 +30,7 @@ in options = { services.chrony = { enable = mkOption { + type = types.bool; default = false; description = '' Whether to synchronise your machine's time using chrony. @@ -79,14 +80,10 @@ in environment.systemPackages = [ pkgs.chrony ]; - users.groups = singleton - { name = "chrony"; - gid = config.ids.gids.chrony; - }; + users.groups.chrony.gid = config.ids.gids.chrony; - users.users = singleton - { name = "chrony"; - uid = config.ids.uids.chrony; + users.users.chrony = + { uid = config.ids.uids.chrony; group = "chrony"; description = "chrony daemon user"; home = stateDir; @@ -96,6 +93,11 @@ in systemd.services.systemd-timedated.environment = { SYSTEMD_TIMEDATED_NTP_SERVICES = "chronyd.service"; }; + systemd.tmpfiles.rules = [ + "d ${stateDir} 0755 chrony chrony - -" + "f ${keyFile} 0640 chrony chrony -" + ]; + systemd.services.chronyd = { description = "chrony NTP daemon"; @@ -107,13 +109,6 @@ in path = [ pkgs.chrony ]; - preStart = '' - mkdir -m 0755 -p ${stateDir} - touch ${keyFile} - chmod 0640 ${keyFile} - chown chrony:chrony ${stateDir} ${keyFile} - ''; - unitConfig.ConditionCapability = "CAP_SYS_TIME"; serviceConfig = { Type = "simple"; @@ -122,7 +117,7 @@ in ProtectHome = "yes"; ProtectSystem = "full"; PrivateTmp = "yes"; - + StateDirectory = "chrony"; }; }; diff --git a/nixos/modules/services/networking/ntp/ntpd.nix b/nixos/modules/services/networking/ntp/ntpd.nix index 1197c84f0459f..51398851adc6d 100644 --- a/nixos/modules/services/networking/ntp/ntpd.nix +++ b/nixos/modules/services/networking/ntp/ntpd.nix @@ -23,6 +23,8 @@ let restrict -6 ::1 ${toString (map (server: "server " + server + " iburst\n") cfg.servers)} + + ${cfg.extraConfig} ''; ntpFlags = "-c ${configFile} -u ${ntpUser}:nogroup ${toString cfg.extraFlags}"; @@ -38,6 +40,7 @@ in services.ntp = { enable = mkOption { + type = types.bool; default = false; description = '' Whether to synchronise your machine's time using ntpd, as a peer in @@ -81,6 +84,17 @@ in ''; }; + extraConfig = mkOption { + type = types.lines; + default = ""; + example = '' + fudge 127.127.1.0 stratum 10 + ''; + description = '' + Additional text appended to <filename>ntp.conf</filename>. + ''; + }; + extraFlags = mkOption { type = types.listOf types.str; description = "Extra flags passed to the ntpd command."; @@ -104,9 +118,8 @@ in systemd.services.systemd-timedated.environment = { SYSTEMD_TIMEDATED_NTP_SERVICES = "ntpd.service"; }; - users.users = singleton - { name = ntpUser; - uid = config.ids.uids.ntp; + users.users.${ntpUser} = + { uid = config.ids.uids.ntp; description = "NTP daemon user"; home = stateDir; }; diff --git a/nixos/modules/services/networking/ntp/openntpd.nix b/nixos/modules/services/networking/ntp/openntpd.nix index 471d15b1687bd..67a04d48d3083 100644 --- a/nixos/modules/services/networking/ntp/openntpd.nix +++ b/nixos/modules/services/networking/ntp/openntpd.nix @@ -60,8 +60,7 @@ in environment.etc."ntpd.conf".text = configFile; - users.users = singleton { - name = "ntp"; + users.users.ntp = { uid = config.ids.uids.ntp; description = "OpenNTP daemon user"; home = "/var/empty"; diff --git a/nixos/modules/services/networking/openfire.nix b/nixos/modules/services/networking/openfire.nix index 4059eb3db83d8..c74f3611f79e3 100644 --- a/nixos/modules/services/networking/openfire.nix +++ b/nixos/modules/services/networking/openfire.nix @@ -9,12 +9,7 @@ with lib; services.openfire = { - enable = mkOption { - default = false; - description = " - Whether to enable OpenFire XMPP server. - "; - }; + enable = mkEnableOption "OpenFire XMPP server"; usePostgreSQL = mkOption { default = true; diff --git a/nixos/modules/services/networking/openvpn.nix b/nixos/modules/services/networking/openvpn.nix index 05be97e66a3d4..dcd7e9e5fa4cd 100644 --- a/nixos/modules/services/networking/openvpn.nix +++ b/nixos/modules/services/networking/openvpn.nix @@ -73,6 +73,9 @@ let in { + imports = [ + (mkRemovedOptionModule [ "services" "openvpn" "enable" ] "") + ]; ###### interface diff --git a/nixos/modules/services/networking/owamp.nix b/nixos/modules/services/networking/owamp.nix index dbb2e3b4c4092..637ed618b8938 100644 --- a/nixos/modules/services/networking/owamp.nix +++ b/nixos/modules/services/networking/owamp.nix @@ -17,16 +17,13 @@ in ###### implementation config = mkIf cfg.enable { - users.users = singleton { - name = "owamp"; + users.users.owamp = { group = "owamp"; description = "Owamp daemon"; isSystemUser = true; }; - users.groups = singleton { - name = "owamp"; - }; + users.groups.owamp = { }; systemd.services.owamp = { description = "Owamp server"; diff --git a/nixos/modules/services/networking/pdns-recursor.nix b/nixos/modules/services/networking/pdns-recursor.nix index ebfdd9f35b72b..6ff181377fccd 100644 --- a/nixos/modules/services/networking/pdns-recursor.nix +++ b/nixos/modules/services/networking/pdns-recursor.nix @@ -91,10 +91,18 @@ in { forwardZones = mkOption { type = types.attrs; + default = {}; + description = '' + DNS zones to be forwarded to other authoritative servers. + ''; + }; + + forwardZonesRecurse = mkOption { + type = types.attrs; example = { eth = "127.0.0.1:5353"; }; default = {}; description = '' - DNS zones to be forwarded to other servers. + DNS zones to be forwarded to other recursive servers. ''; }; @@ -158,7 +166,8 @@ in { webserver-port = cfg.api.port; webserver-allow-from = cfg.api.allowFrom; - forward-zones = mapAttrsToList (zone: uri: "${zone}.=${uri}") cfg.forwardZones; + forward-zones = mapAttrsToList (zone: uri: "${zone}.=${uri}") cfg.forwardZones; + forward-zones-recurse = mapAttrsToList (zone: uri: "${zone}.=${uri}") cfg.forwardZonesRecurse; export-etc-hosts = cfg.exportHosts; dnssec = cfg.dnssecValidation; serve-rfc1918 = cfg.serveRFC1918; @@ -210,4 +219,6 @@ in { "To change extra Recursor settings use services.pdns-recursor.settings instead.") ]; + meta.maintainers = with lib.maintainers; [ rnhmjoj ]; + } diff --git a/nixos/modules/services/networking/pdnsd.nix b/nixos/modules/services/networking/pdnsd.nix index f5b174dd7b7b9..24b5bbc5104e1 100644 --- a/nixos/modules/services/networking/pdnsd.nix +++ b/nixos/modules/services/networking/pdnsd.nix @@ -62,15 +62,13 @@ in }; config = mkIf cfg.enable { - users.users = singleton { - name = pdnsdUser; + users.users.${pdnsdUser} = { uid = config.ids.uids.pdnsd; group = pdnsdGroup; description = "pdnsd user"; }; - users.groups = singleton { - name = pdnsdGroup; + users.groups.${pdnsdGroup} = { gid = config.ids.gids.pdnsd; }; diff --git a/nixos/modules/services/networking/pixiecore.nix b/nixos/modules/services/networking/pixiecore.nix new file mode 100644 index 0000000000000..0e32f182e2a10 --- /dev/null +++ b/nixos/modules/services/networking/pixiecore.nix @@ -0,0 +1,134 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.pixiecore; +in +{ + meta.maintainers = with maintainers; [ bbigras danderson ]; + + options = { + services.pixiecore = { + enable = mkEnableOption "Pixiecore"; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = '' + Open ports (67, 69 UDP and 4011, 'port', 'statusPort' TCP) in the firewall for Pixiecore. + ''; + }; + + mode = mkOption { + description = "Which mode to use"; + default = "boot"; + type = types.enum [ "api" "boot" ]; + }; + + debug = mkOption { + type = types.bool; + default = false; + description = "Log more things that aren't directly related to booting a recognized client"; + }; + + dhcpNoBind = mkOption { + type = types.bool; + default = false; + description = "Handle DHCP traffic without binding to the DHCP server port"; + }; + + kernel = mkOption { + type = types.str or types.path; + default = ""; + description = "Kernel path. Ignored unless mode is set to 'boot'"; + }; + + initrd = mkOption { + type = types.str or types.path; + default = ""; + description = "Initrd path. Ignored unless mode is set to 'boot'"; + }; + + cmdLine = mkOption { + type = types.str; + default = ""; + description = "Kernel commandline arguments. Ignored unless mode is set to 'boot'"; + }; + + listen = mkOption { + type = types.str; + default = "0.0.0.0"; + description = "IPv4 address to listen on"; + }; + + port = mkOption { + type = types.port; + default = 80; + description = "Port to listen on for HTTP"; + }; + + statusPort = mkOption { + type = types.port; + default = 80; + description = "HTTP port for status information (can be the same as --port)"; + }; + + apiServer = mkOption { + type = types.str; + example = "localhost:8080"; + description = "host:port to connect to the API. Ignored unless mode is set to 'api'"; + }; + + extraArguments = mkOption { + type = types.listOf types.str; + default = []; + description = "Additional command line arguments to pass to Pixiecore"; + }; + }; + }; + + config = mkIf cfg.enable { + users.groups.pixiecore = {}; + users.users.pixiecore = { + description = "Pixiecore daemon user"; + group = "pixiecore"; + }; + + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ 4011 cfg.port cfg.statusPort ]; + allowedUDPPorts = [ 67 69 ]; + }; + + systemd.services.pixiecore = { + description = "Pixiecore server"; + after = [ "network.target"]; + wants = [ "network.target"]; + wantedBy = [ "multi-user.target"]; + serviceConfig = { + User = "pixiecore"; + Restart = "always"; + AmbientCapabilities = [ "cap_net_bind_service" ] ++ optional cfg.dhcpNoBind "cap_net_raw"; + ExecStart = + let + argString = + if cfg.mode == "boot" + then [ "boot" cfg.kernel ] + ++ optional (cfg.initrd != "") cfg.initrd + ++ optional (cfg.cmdLine != "") "--cmdline=${lib.escapeShellArg cfg.cmdLine}" + else [ "api" cfg.apiServer ]; + in + '' + ${pkgs.pixiecore}/bin/pixiecore \ + ${lib.escapeShellArgs argString} \ + ${optionalString cfg.debug "--debug"} \ + ${optionalString cfg.dhcpNoBind "--dhcp-no-bind"} \ + --listen-addr ${lib.escapeShellArg cfg.listen} \ + --port ${toString cfg.port} \ + --status-port ${toString cfg.statusPort} \ + ${escapeShellArgs cfg.extraArguments} + ''; + }; + }; + }; +} diff --git a/nixos/modules/services/networking/polipo.nix b/nixos/modules/services/networking/polipo.nix index dbe3b7380970b..1ff9388346b61 100644 --- a/nixos/modules/services/networking/polipo.nix +++ b/nixos/modules/services/networking/polipo.nix @@ -85,17 +85,15 @@ in config = mkIf cfg.enable { - users.users = singleton - { name = "polipo"; - uid = config.ids.uids.polipo; + users.users.polipo = + { uid = config.ids.uids.polipo; description = "Polipo caching proxy user"; home = "/var/cache/polipo"; createHome = true; }; - users.groups = singleton - { name = "polipo"; - gid = config.ids.gids.polipo; + users.groups.polipo = + { gid = config.ids.gids.polipo; members = [ "polipo" ]; }; diff --git a/nixos/modules/services/networking/pppd.nix b/nixos/modules/services/networking/pppd.nix index e96c27bd84b4a..c1cbdb461765b 100644 --- a/nixos/modules/services/networking/pppd.nix +++ b/nixos/modules/services/networking/pppd.nix @@ -64,11 +64,13 @@ in enabledConfigs = filter (f: f.enable) (attrValues cfg.peers); mkEtc = peerCfg: { - "ppp/peers/${peerCfg.name}".text = peerCfg.config; + name = "ppp/peers/${peerCfg.name}"; + value.text = peerCfg.config; }; mkSystemd = peerCfg: { - "pppd-${peerCfg.name}" = { + name = "pppd-${peerCfg.name}"; + value = { restartTriggers = [ config.environment.etc."ppp/peers/${peerCfg.name}".source ]; before = [ "network.target" ]; wants = [ "network.target" ]; @@ -124,11 +126,11 @@ in }; }; - etcFiles = map mkEtc enabledConfigs; - systemdConfigs = map mkSystemd enabledConfigs; + etcFiles = listToAttrs (map mkEtc enabledConfigs); + systemdConfigs = listToAttrs (map mkSystemd enabledConfigs); in mkIf cfg.enable { - environment.etc = mkMerge etcFiles; - systemd.services = mkMerge systemdConfigs; + environment.etc = etcFiles; + systemd.services = systemdConfigs; }; } diff --git a/nixos/modules/services/networking/prayer.nix b/nixos/modules/services/networking/prayer.nix index c936417e68cbd..f04dac01d9b8e 100644 --- a/nixos/modules/services/networking/prayer.nix +++ b/nixos/modules/services/networking/prayer.nix @@ -41,12 +41,7 @@ in services.prayer = { - enable = mkOption { - default = false; - description = '' - Whether to run the prayer webmail http server. - ''; - }; + enable = mkEnableOption "the prayer webmail http server"; port = mkOption { default = "2080"; @@ -72,17 +67,14 @@ in config = mkIf config.services.prayer.enable { environment.systemPackages = [ prayer ]; - users.users = singleton - { name = prayerUser; - uid = config.ids.uids.prayer; + users.users.${prayerUser} = + { uid = config.ids.uids.prayer; description = "Prayer daemon user"; home = stateDir; }; - users.groups = singleton - { name = prayerGroup; - gid = config.ids.gids.prayer; - }; + users.groups.${prayerGroup} = + { gid = config.ids.gids.prayer; }; systemd.services.prayer = { wantedBy = [ "multi-user.target" ]; diff --git a/nixos/modules/services/networking/privoxy.nix b/nixos/modules/services/networking/privoxy.nix index 49ca839a2c378..1f41c720adf58 100644 --- a/nixos/modules/services/networking/privoxy.nix +++ b/nixos/modules/services/networking/privoxy.nix @@ -109,4 +109,6 @@ in }; + meta.maintainers = with lib.maintainers; [ rnhmjoj ]; + } diff --git a/nixos/modules/services/networking/quassel.nix b/nixos/modules/services/networking/quassel.nix index b495b3948fb57..da723ec86adf6 100644 --- a/nixos/modules/services/networking/quassel.nix +++ b/nixos/modules/services/networking/quassel.nix @@ -16,12 +16,7 @@ in services.quassel = { - enable = mkOption { - default = false; - description = '' - Whether to run the Quassel IRC client daemon. - ''; - }; + enable = mkEnableOption "the Quassel IRC client daemon"; certificateFile = mkOption { type = types.nullOr types.str; @@ -92,17 +87,21 @@ in message = "Quassel needs a certificate file in order to require SSL"; }]; - users.users = mkIf (cfg.user == null) [ - { name = "quassel"; + users.users = optionalAttrs (cfg.user == null) { + quassel = { + name = "quassel"; description = "Quassel IRC client daemon"; group = "quassel"; uid = config.ids.uids.quassel; - }]; + }; + }; - users.groups = mkIf (cfg.user == null) [ - { name = "quassel"; + users.groups = optionalAttrs (cfg.user == null) { + quassel = { + name = "quassel"; gid = config.ids.gids.quassel; - }]; + }; + }; systemd.tmpfiles.rules = [ "d '${cfg.dataDir}' - ${user} - - -" diff --git a/nixos/modules/services/networking/quorum.nix b/nixos/modules/services/networking/quorum.nix new file mode 100644 index 0000000000000..2f612c9db6861 --- /dev/null +++ b/nixos/modules/services/networking/quorum.nix @@ -0,0 +1,229 @@ +{ config, pkgs, lib, ... }: +let + + inherit (lib) mkEnableOption mkIf mkOption literalExample types optionalString; + + cfg = config.services.quorum; + dataDir = "/var/lib/quorum"; + genesisFile = pkgs.writeText "genesis.json" (builtins.toJSON cfg.genesis); + staticNodesFile = pkgs.writeText "static-nodes.json" (builtins.toJSON cfg.staticNodes); + +in { + options = { + + services.quorum = { + enable = mkEnableOption "Quorum blockchain daemon"; + + user = mkOption { + type = types.str; + default = "quorum"; + description = "The user as which to run quorum."; + }; + + group = mkOption { + type = types.str; + default = cfg.user; + description = "The group as which to run quorum."; + }; + + port = mkOption { + type = types.port; + default = 21000; + description = "Override the default port on which to listen for connections."; + }; + + nodekeyFile = mkOption { + type = types.path; + default = "${dataDir}/nodekey"; + description = "Path to the nodekey."; + }; + + staticNodes = mkOption { + type = types.listOf types.str; + default = []; + example = [ "enode://dd333ec28f0a8910c92eb4d336461eea1c20803eed9cf2c056557f986e720f8e693605bba2f4e8f289b1162e5ac7c80c914c7178130711e393ca76abc1d92f57@0.0.0.0:30303?discport=0" ]; + description = "List of validator nodes."; + }; + + privateconfig = mkOption { + type = types.str; + default = "ignore"; + description = "Configuration of privacy transaction manager."; + }; + + syncmode = mkOption { + type = types.enum [ "fast" "full" "light" ]; + default = "full"; + description = "Blockchain sync mode."; + }; + + blockperiod = mkOption { + type = types.int; + default = 5; + description = "Default minimum difference between two consecutive block's timestamps in seconds."; + }; + + permissioned = mkOption { + type = types.bool; + default = true; + description = "Allow only a defined list of nodes to connect."; + }; + + rpc = { + enable = mkOption { + type = types.bool; + default = true; + description = "Enable RPC interface."; + }; + + address = mkOption { + type = types.str; + default = "0.0.0.0"; + description = "Listening address for RPC connections."; + }; + + port = mkOption { + type = types.port; + default = 22004; + description = "Override the default port on which to listen for RPC connections."; + }; + + api = mkOption { + type = types.str; + default = "admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,istanbul"; + description = "API's offered over the HTTP-RPC interface."; + }; + }; + + ws = { + enable = mkOption { + type = types.bool; + default = true; + description = "Enable WS-RPC interface."; + }; + + address = mkOption { + type = types.str; + default = "0.0.0.0"; + description = "Listening address for WS-RPC connections."; + }; + + port = mkOption { + type = types.port; + default = 8546; + description = "Override the default port on which to listen for WS-RPC connections."; + }; + + api = mkOption { + type = types.str; + default = "admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,istanbul"; + description = "API's offered over the WS-RPC interface."; + }; + + origins = mkOption { + type = types.str; + default = "*"; + description = "Origins from which to accept websockets requests"; + }; + }; + + genesis = mkOption { + type = types.nullOr types.attrs; + default = null; + example = literalExample '' { + alloc = { + a47385db68718bdcbddc2d2bb7c54018066ec111 = { + balance = "1000000000000000000000000000"; + }; + }; + coinbase = "0x0000000000000000000000000000000000000000"; + config = { + byzantiumBlock = 4; + chainId = 494702925; + eip150Block = 2; + eip155Block = 3; + eip158Block = 3; + homesteadBlock = 1; + isQuorum = true; + istanbul = { + epoch = 30000; + policy = 0; + }; + }; + difficulty = "0x1"; + extraData = "0x0000000000000000000000000000000000000000000000000000000000000000f85ad59438f0508111273d8e482f49410ca4078afc86a961b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0"; + gasLimit = "0x2FEFD800"; + mixHash = "0x63746963616c2062797a616e74696e65201111756c7420746f6c6572616e6365"; + nonce = "0x0"; + parentHash = "0x0000000000000000000000000000000000000000000000000000000000000000"; + timestamp = "0x00"; + }''; + description = "Blockchain genesis settings."; + }; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ pkgs.quorum ]; + systemd.tmpfiles.rules = [ + "d '${dataDir}' 0770 '${cfg.user}' '${cfg.group}' - -" + ]; + systemd.services.quorum = { + description = "Quorum daemon"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + environment = { + PRIVATE_CONFIG = "${cfg.privateconfig}"; + }; + preStart = '' + if [ ! -d ${dataDir}/geth ]; then + if [ ! -d ${dataDir}/keystore ]; then + echo ERROR: You need to create a wallet before initializing your genesis file, run: + echo # su -s /bin/sh - quorum + echo $ geth --datadir ${dataDir} account new + echo and configure your genesis file accordingly. + exit 1; + fi + ln -s ${staticNodesFile} ${dataDir}/static-nodes.json + ${pkgs.quorum}/bin/geth --datadir ${dataDir} init ${genesisFile} + fi + ''; + serviceConfig = { + User = cfg.user; + Group = cfg.group; + ExecStart = ''${pkgs.quorum}/bin/geth \ + --nodiscover \ + --verbosity 5 \ + --nodekey ${cfg.nodekeyFile} \ + --istanbul.blockperiod ${toString cfg.blockperiod} \ + --syncmode ${cfg.syncmode} \ + ${optionalString (cfg.permissioned) + "--permissioned"} \ + --mine --minerthreads 1 \ + ${optionalString (cfg.rpc.enable) + "--rpc --rpcaddr ${cfg.rpc.address} --rpcport ${toString cfg.rpc.port} --rpcapi ${cfg.rpc.api}"} \ + ${optionalString (cfg.ws.enable) + "--ws --wsaddr ${cfg.ws.address} --wsport ${toString cfg.ws.port} --wsapi ${cfg.ws.api} --wsorigins ${cfg.ws.origins}"} \ + --emitcheckpoints \ + --datadir ${dataDir} \ + --port ${toString cfg.port}''; + Restart = "on-failure"; + + # Hardening measures + PrivateTmp = "true"; + ProtectSystem = "full"; + NoNewPrivileges = "true"; + PrivateDevices = "true"; + MemoryDenyWriteExecute = "true"; + }; + }; + users.users.${cfg.user} = { + name = cfg.user; + group = cfg.group; + description = "Quorum daemon user"; + home = dataDir; + isSystemUser = true; + }; + users.groups.${cfg.group} = {}; + }; +} diff --git a/nixos/modules/services/networking/radicale.nix b/nixos/modules/services/networking/radicale.nix index 1daced4a6c70e..30bf22586f86f 100644 --- a/nixos/modules/services/networking/radicale.nix +++ b/nixos/modules/services/networking/radicale.nix @@ -59,18 +59,15 @@ in config = mkIf cfg.enable { environment.systemPackages = [ cfg.package ]; - users.users = singleton - { name = "radicale"; - uid = config.ids.uids.radicale; + users.users.radicale = + { uid = config.ids.uids.radicale; description = "radicale user"; home = "/var/lib/radicale"; createHome = true; }; - users.groups = singleton - { name = "radicale"; - gid = config.ids.gids.radicale; - }; + users.groups.radicale = + { gid = config.ids.gids.radicale; }; systemd.services.radicale = { description = "A Simple Calendar and Contact Server"; diff --git a/nixos/modules/services/networking/radvd.nix b/nixos/modules/services/networking/radvd.nix index 020faa34922a2..f4b00c9b356ef 100644 --- a/nixos/modules/services/networking/radvd.nix +++ b/nixos/modules/services/networking/radvd.nix @@ -19,6 +19,7 @@ in options = { services.radvd.enable = mkOption { + type = types.bool; default = false; description = '' diff --git a/nixos/modules/services/networking/rdnssd.nix b/nixos/modules/services/networking/rdnssd.nix index bccab805beeb0..469504c43172f 100644 --- a/nixos/modules/services/networking/rdnssd.nix +++ b/nixos/modules/services/networking/rdnssd.nix @@ -17,6 +17,7 @@ in options = { services.rdnssd.enable = mkOption { + type = types.bool; default = false; #default = config.networking.enableIPv6; description = diff --git a/nixos/modules/services/networking/resilio.nix b/nixos/modules/services/networking/resilio.nix index 9b25aa5758374..e74e03fc0b075 100644 --- a/nixos/modules/services/networking/resilio.nix +++ b/nixos/modules/services/networking/resilio.nix @@ -244,7 +244,7 @@ in group = "rslsync"; }; - users.groups = [ { name = "rslsync"; } ]; + users.groups.rslsync = {}; systemd.services.resilio = with pkgs; { description = "Resilio Sync Service"; diff --git a/nixos/modules/services/networking/rxe.nix b/nixos/modules/services/networking/rxe.nix index a6a069ec50c06..c7d174a00de2f 100644 --- a/nixos/modules/services/networking/rxe.nix +++ b/nixos/modules/services/networking/rxe.nix @@ -5,20 +5,6 @@ with lib; let cfg = config.networking.rxe; - runRxeCmd = cmd: ifcs: - concatStrings ( map (x: "${pkgs.rdma-core}/bin/rxe_cfg -n ${cmd} ${x};") ifcs); - - startScript = pkgs.writeShellScriptBin "rxe-start" '' - ${pkgs.rdma-core}/bin/rxe_cfg -n start - ${runRxeCmd "add" cfg.interfaces} - ${pkgs.rdma-core}/bin/rxe_cfg - ''; - - stopScript = pkgs.writeShellScriptBin "rxe-stop" '' - ${runRxeCmd "remove" cfg.interfaces } - ${pkgs.rdma-core}/bin/rxe_cfg -n stop - ''; - in { ###### interface @@ -31,9 +17,8 @@ in { example = [ "eth0" ]; description = '' Enable RDMA on the listed interfaces. The corresponding virtual - RDMA interfaces will be named rxe0 ... rxeN where the ordering - will be as they are named in the list. UDP port 4791 must be - open on the respective ethernet interfaces. + RDMA interfaces will be named rxe_<interface>. + UDP port 4791 must be open on the respective ethernet interfaces. ''; }; }; @@ -44,7 +29,6 @@ in { config = mkIf cfg.enable { systemd.services.rxe = { - path = with pkgs; [ kmod rdma-core ]; description = "RoCE interfaces"; wantedBy = [ "multi-user.target" ]; @@ -54,8 +38,13 @@ in { serviceConfig = { Type = "oneshot"; RemainAfterExit = true; - ExecStart = "${startScript}/bin/rxe-start"; - ExecStop = "${stopScript}/bin/rxe-stop"; + ExecStart = map ( x: + "${pkgs.iproute}/bin/rdma link add rxe_${x} type rxe netdev ${x}" + ) cfg.interfaces; + + ExecStop = map ( x: + "${pkgs.iproute}/bin/rdma link delete rxe_${x}" + ) cfg.interfaces; }; }; }; diff --git a/nixos/modules/services/networking/sabnzbd.nix b/nixos/modules/services/networking/sabnzbd.nix index 62b24d4377f8a..ff5aef7d1cb47 100644 --- a/nixos/modules/services/networking/sabnzbd.nix +++ b/nixos/modules/services/networking/sabnzbd.nix @@ -15,10 +15,8 @@ in options = { services.sabnzbd = { - enable = mkOption { - default = false; - description = "Whether to enable the sabnzbd server."; - }; + enable = mkEnableOption "the sabnzbd server"; + configFile = mkOption { default = "/var/lib/sabnzbd/sabnzbd.ini"; description = "Path to config file."; diff --git a/nixos/modules/services/networking/searx.nix b/nixos/modules/services/networking/searx.nix index 9412d0ef8a62e..60fb3d5d6d443 100644 --- a/nixos/modules/services/networking/searx.nix +++ b/nixos/modules/services/networking/searx.nix @@ -75,4 +75,6 @@ in }; + meta.maintainers = with lib.maintainers; [ rnhmjoj ]; + } diff --git a/nixos/modules/services/networking/shairport-sync.nix b/nixos/modules/services/networking/shairport-sync.nix index 68e005ab81dad..b4b86a2d55bee 100644 --- a/nixos/modules/services/networking/shairport-sync.nix +++ b/nixos/modules/services/networking/shairport-sync.nix @@ -17,6 +17,7 @@ in services.shairport-sync = { enable = mkOption { + type = types.bool; default = false; description = '' Enable the shairport-sync daemon. @@ -55,9 +56,8 @@ in services.avahi.publish.enable = true; services.avahi.publish.userServices = true; - users.users = singleton - { name = cfg.user; - description = "Shairport user"; + users.users.${cfg.user} = + { description = "Shairport user"; isSystemUser = true; createHome = true; home = "/var/lib/shairport-sync"; diff --git a/nixos/modules/services/networking/shorewall.nix b/nixos/modules/services/networking/shorewall.nix new file mode 100644 index 0000000000000..16383be2530f7 --- /dev/null +++ b/nixos/modules/services/networking/shorewall.nix @@ -0,0 +1,70 @@ +{ config, lib, pkgs, ... }: +let + types = lib.types; + cfg = config.services.shorewall; +in { + options = { + services.shorewall = { + enable = lib.mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable Shorewall IPv4 Firewall. + <warning> + <para> + Enabling this service WILL disable the existing NixOS + firewall! Default firewall rules provided by packages are not + considered at the moment. + </para> + </warning> + ''; + }; + package = lib.mkOption { + type = types.package; + default = pkgs.shorewall; + defaultText = "pkgs.shorewall"; + description = "The shorewall package to use."; + }; + configs = lib.mkOption { + type = types.attrsOf types.lines; + default = {}; + description = '' + This option defines the Shorewall configs. + The attribute name defines the name of the config, + and the attribute value defines the content of the config. + ''; + apply = lib.mapAttrs (name: text: pkgs.writeText "${name}" text); + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.firewall.enable = false; + systemd.services.shorewall = { + description = "Shorewall IPv4 Firewall"; + after = [ "ipset.target" ]; + before = [ "network-pre.target" ]; + wants = [ "network-pre.target" ]; + wantedBy = [ "multi-user.target" ]; + reloadIfChanged = true; + restartTriggers = lib.attrValues cfg.configs; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = "yes"; + ExecStart = "${cfg.package}/bin/shorewall start"; + ExecReload = "${cfg.package}/bin/shorewall reload"; + ExecStop = "${cfg.package}/bin/shorewall stop"; + }; + preStart = '' + install -D -d -m 750 /var/lib/shorewall + install -D -d -m 755 /var/lock/subsys + touch /var/log/shorewall.log + chown 750 /var/log/shorewall.log + ''; + }; + environment = { + etc = lib.mapAttrs' (name: conf: lib.nameValuePair "shorewall/${name}" {source=conf;}) cfg.configs; + systemPackages = [ cfg.package ]; + }; + }; +} diff --git a/nixos/modules/services/networking/shorewall6.nix b/nixos/modules/services/networking/shorewall6.nix new file mode 100644 index 0000000000000..e081aedc6c344 --- /dev/null +++ b/nixos/modules/services/networking/shorewall6.nix @@ -0,0 +1,70 @@ +{ config, lib, pkgs, ... }: +let + types = lib.types; + cfg = config.services.shorewall6; +in { + options = { + services.shorewall6 = { + enable = lib.mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable Shorewall IPv6 Firewall. + <warning> + <para> + Enabling this service WILL disable the existing NixOS + firewall! Default firewall rules provided by packages are not + considered at the moment. + </para> + </warning> + ''; + }; + package = lib.mkOption { + type = types.package; + default = pkgs.shorewall; + defaultText = "pkgs.shorewall"; + description = "The shorewall package to use."; + }; + configs = lib.mkOption { + type = types.attrsOf types.lines; + default = {}; + description = '' + This option defines the Shorewall configs. + The attribute name defines the name of the config, + and the attribute value defines the content of the config. + ''; + apply = lib.mapAttrs (name: text: pkgs.writeText "${name}" text); + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.firewall.enable = false; + systemd.services.shorewall6 = { + description = "Shorewall IPv6 Firewall"; + after = [ "ipset.target" ]; + before = [ "network-pre.target" ]; + wants = [ "network-pre.target" ]; + wantedBy = [ "multi-user.target" ]; + reloadIfChanged = true; + restartTriggers = lib.attrValues cfg.configs; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = "yes"; + ExecStart = "${cfg.package}/bin/shorewall6 start"; + ExecReload = "${cfg.package}/bin/shorewall6 reload"; + ExecStop = "${cfg.package}/bin/shorewall6 stop"; + }; + preStart = '' + install -D -d -m 750 /var/lib/shorewall6 + install -D -d -m 755 /var/lock/subsys + touch /var/log/shorewall6.log + chown 750 /var/log/shorewall6.log + ''; + }; + environment = { + etc = lib.mapAttrs' (name: conf: lib.nameValuePair "shorewall6/${name}" {source=conf;}) cfg.configs; + systemPackages = [ cfg.package ]; + }; + }; +} diff --git a/nixos/modules/services/networking/shout.nix b/nixos/modules/services/networking/shout.nix index e548ec66962a6..a808a7f39d050 100644 --- a/nixos/modules/services/networking/shout.nix +++ b/nixos/modules/services/networking/shout.nix @@ -82,8 +82,7 @@ in { }; config = mkIf cfg.enable { - users.users = singleton { - name = "shout"; + users.users.shout = { uid = config.ids.uids.shout; description = "Shout daemon user"; home = shoutHome; diff --git a/nixos/modules/services/networking/smartdns.nix b/nixos/modules/services/networking/smartdns.nix new file mode 100644 index 0000000000000..f1888af704164 --- /dev/null +++ b/nixos/modules/services/networking/smartdns.nix @@ -0,0 +1,61 @@ +{ lib, pkgs, config, ... }: + +with lib; + +let + inherit (lib.types) attrsOf coercedTo listOf oneOf str int bool; + cfg = config.services.smartdns; + + confFile = pkgs.writeText "smartdns.conf" (with generators; + toKeyValue { + mkKeyValue = mkKeyValueDefault { + mkValueString = v: + if isBool v then + if v then "yes" else "no" + else + mkValueStringDefault { } v; + } " "; + listsAsDuplicateKeys = + true; # Allowing duplications because we need to deal with multiple entries with the same key. + } cfg.settings); +in { + options.services.smartdns = { + enable = mkEnableOption "SmartDNS DNS server"; + + bindPort = mkOption { + type = types.port; + default = 53; + description = "DNS listening port number."; + }; + + settings = mkOption { + type = + let atom = oneOf [ str int bool ]; + in attrsOf (coercedTo atom toList (listOf atom)); + example = literalExample '' + { + bind = ":5353 -no-rule -group example"; + cache-size = 4096; + server-tls = [ "8.8.8.8:853" "1.1.1.1:853" ]; + server-https = "https://cloudflare-dns.com/dns-query -exclude-default-group"; + prefetch-domain = true; + speed-check-mode = "ping,tcp:80"; + }; + ''; + description = '' + A set that will be generated into configuration file, see the <link xlink:href="https://github.com/pymumu/smartdns/blob/master/ReadMe_en.md#configuration-parameter">SmartDNS README</link> for details of configuration parameters. + You could override the options here like <option>services.smartdns.bindPort</option> by writing <literal>settings.bind = ":5353 -no-rule -group example";</literal>. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + services.smartdns.settings.bind = mkDefault ":${toString cfg.bindPort}"; + + systemd.packages = [ pkgs.smartdns ]; + systemd.services.smartdns.wantedBy = [ "multi-user.target" ]; + environment.etc."smartdns/smartdns.conf".source = confFile; + environment.etc."default/smartdns".source = + "${pkgs.smartdns}/etc/default/smartdns"; + }; +} diff --git a/nixos/modules/services/networking/smokeping.nix b/nixos/modules/services/networking/smokeping.nix index b48b0b3a9d6bb..37ee2a8038908 100644 --- a/nixos/modules/services/networking/smokeping.nix +++ b/nixos/modules/services/networking/smokeping.nix @@ -280,8 +280,7 @@ in fping6.source = "${pkgs.fping}/bin/fping6"; }; environment.systemPackages = [ pkgs.fping ]; - users.users = singleton { - name = cfg.user; + users.users.${cfg.user} = { isNormalUser = false; isSystemUser = true; uid = config.ids.uids.smokeping; diff --git a/nixos/modules/services/networking/spacecookie.nix b/nixos/modules/services/networking/spacecookie.nix new file mode 100644 index 0000000000000..c4d06df6ad4ab --- /dev/null +++ b/nixos/modules/services/networking/spacecookie.nix @@ -0,0 +1,83 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.spacecookie; + configFile = pkgs.writeText "spacecookie.json" (lib.generators.toJSON {} { + inherit (cfg) hostname port root; + }); +in { + + options = { + + services.spacecookie = { + + enable = mkEnableOption "spacecookie"; + + hostname = mkOption { + type = types.str; + default = "localhost"; + description = "The hostname the service is reachable via. Clients will use this hostname for further requests after loading the initial gopher menu."; + }; + + port = mkOption { + type = types.port; + default = 70; + description = "Port the gopher service should be exposed on."; + }; + + root = mkOption { + type = types.path; + default = "/srv/gopher"; + description = "The root directory spacecookie serves via gopher."; + }; + }; + }; + + config = mkIf cfg.enable { + + systemd.sockets.spacecookie = { + description = "Socket for the Spacecookie Gopher Server"; + wantedBy = [ "sockets.target" ]; + listenStreams = [ "[::]:${toString cfg.port}" ]; + socketConfig = { + BindIPv6Only = "both"; + }; + }; + + systemd.services.spacecookie = { + description = "Spacecookie Gopher Server"; + wantedBy = [ "multi-user.target" ]; + requires = [ "spacecookie.socket" ]; + + serviceConfig = { + Type = "notify"; + ExecStart = "${pkgs.haskellPackages.spacecookie}/bin/spacecookie ${configFile}"; + FileDescriptorStoreMax = 1; + + DynamicUser = true; + + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + PrivateMounts = true; + PrivateUsers = true; + + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + + CapabilityBoundingSet = ""; + NoNewPrivileges = true; + LockPersonality = true; + RestrictRealtime = true; + + # AF_UNIX for communication with systemd + # AF_INET replaced by BindIPv6Only=both + RestrictAddressFamilies = "AF_UNIX AF_INET6"; + }; + }; + }; +} diff --git a/nixos/modules/services/networking/ssh/lshd.nix b/nixos/modules/services/networking/ssh/lshd.nix index eca599afb33b2..892e59778c34d 100644 --- a/nixos/modules/services/networking/ssh/lshd.nix +++ b/nixos/modules/services/networking/ssh/lshd.nix @@ -19,6 +19,7 @@ in services.lshd = { enable = mkOption { + type = types.bool; default = false; description = '' Whether to enable the GNU lshd SSH2 daemon, which allows diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix index 91fc7d72bc6d2..17f31e3a488d8 100644 --- a/nixos/modules/services/networking/ssh/sshd.nix +++ b/nixos/modules/services/networking/ssh/sshd.nix @@ -9,15 +9,15 @@ let # This middle-ground solution ensures *an* sshd can do their basic validation # on the configuration. validationPackage = if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform - then [ cfgc.package ] - else [ pkgs.buildPackages.openssh ]; + then cfgc.package + else pkgs.buildPackages.openssh; sshconf = pkgs.runCommand "sshd.conf-validated" { nativeBuildInputs = [ validationPackage ]; } '' cat >$out <<EOL ${cfg.extraConfig} EOL - ssh-keygen -f mock-hostkey -N "" + ssh-keygen -q -f mock-hostkey -N "" sshd -t -f $out -h mock-hostkey ''; @@ -74,6 +74,10 @@ let in { + imports = [ + (mkAliasOptionModule [ "services" "sshd" "enable" ] [ "services" "openssh" "enable" ]) + (mkAliasOptionModule [ "services" "openssh" "knownHosts" ] [ "programs" "ssh" "knownHosts" ]) + ]; ###### interface @@ -234,6 +238,26 @@ in description = "Files from which authorized keys are read."; }; + authorizedKeysCommand = mkOption { + type = types.str; + default = "none"; + description = '' + Specifies a program to be used to look up the user's public + keys. The program must be owned by root, not writable by group + or others and specified by an absolute path. + ''; + }; + + authorizedKeysCommandUser = mkOption { + type = types.str; + default = "nobody"; + description = '' + Specifies the user under whose account the AuthorizedKeysCommand + is run. It is recommended to use a dedicated user that has no + other role on the host than running authorized keys commands. + ''; + }; + kexAlgorithms = mkOption { type = types.listOf types.str; default = [ @@ -481,6 +505,10 @@ in PrintMotd no # handled by pam_motd AuthorizedKeysFile ${toString cfg.authorizedKeysFiles} + ${optionalString (cfg.authorizedKeysCommand != "none") '' + AuthorizedKeysCommand ${cfg.authorizedKeysCommand} + AuthorizedKeysCommandUser ${cfg.authorizedKeysCommandUser} + ''} ${flip concatMapStrings cfg.hostKeys (k: '' HostKey ${k.path} diff --git a/nixos/modules/services/networking/sslh.nix b/nixos/modules/services/networking/sslh.nix index 0222e8ce8b584..c4fa370a5fef3 100644 --- a/nixos/modules/services/networking/sslh.nix +++ b/nixos/modules/services/networking/sslh.nix @@ -77,19 +77,14 @@ in config = mkMerge [ (mkIf cfg.enable { - users.users.${user} = { - description = "sslh daemon user"; - isSystemUser = true; - }; - systemd.services.sslh = { description = "Applicative Protocol Multiplexer (e.g. share SSH and HTTPS on the same port)"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { - User = user; - Group = "nogroup"; + DynamicUser = true; + User = "sslh"; PermissionsStartOnly = true; Restart = "always"; RestartSec = "1s"; diff --git a/nixos/modules/services/networking/strongswan.nix b/nixos/modules/services/networking/strongswan.nix index 4ff9c486059c5..13a1a897c5ed8 100644 --- a/nixos/modules/services/networking/strongswan.nix +++ b/nixos/modules/services/networking/strongswan.nix @@ -4,7 +4,7 @@ let inherit (builtins) toFile; inherit (lib) concatMapStringsSep concatStringsSep mapAttrsToList - mkIf mkEnableOption mkOption types; + mkIf mkEnableOption mkOption types literalExample; cfg = config.services.strongswan; @@ -79,19 +79,21 @@ in connections = mkOption { type = types.attrsOf (types.attrsOf types.str); default = {}; - example = { - "%default" = { - keyexchange = "ikev2"; - keyingtries = "1"; - }; - roadwarrior = { - auto = "add"; - leftcert = "/run/keys/moonCert.pem"; - leftid = "@moon.strongswan.org"; - leftsubnet = "10.1.0.0/16"; - right = "%any"; - }; - }; + example = literalExample '' + { + "%default" = { + keyexchange = "ikev2"; + keyingtries = "1"; + }; + roadwarrior = { + auto = "add"; + leftcert = "/run/keys/moonCert.pem"; + leftid = "@moon.strongswan.org"; + leftsubnet = "10.1.0.0/16"; + right = "%any"; + }; + } + ''; description = '' A set of connections and their options for the ‘conn xxx’ sections of the <filename>ipsec.conf</filename> file. diff --git a/nixos/modules/services/networking/stubby.nix b/nixos/modules/services/networking/stubby.nix index b38bcd4cec058..c5e0f929a1267 100644 --- a/nixos/modules/services/networking/stubby.nix +++ b/nixos/modules/services/networking/stubby.nix @@ -72,6 +72,7 @@ let resolution_type: GETDNS_RESOLUTION_STUB dns_transport_list: ${fallbacks} + appdata_dir: "/var/cache/stubby" tls_authentication: ${cfg.authenticationMode} tls_query_padding_blocksize: ${toString cfg.queryPaddingBlocksize} edns_client_subnet_private: ${if cfg.subnetPrivate then "1" else "0"} @@ -204,10 +205,12 @@ in wantedBy = [ "multi-user.target" ]; serviceConfig = { + Type = "notify"; AmbientCapabilities = "CAP_NET_BIND_SERVICE"; CapabilityBoundingSet = "CAP_NET_BIND_SERVICE"; ExecStart = "${pkgs.stubby}/bin/stubby -C ${confFile} ${optionalString cfg.debugLogging "-l"}"; DynamicUser = true; + CacheDirectory = "stubby"; }; }; }; diff --git a/nixos/modules/services/networking/supplicant.nix b/nixos/modules/services/networking/supplicant.nix index 35c1e649e2e12..b5b9989ce1863 100644 --- a/nixos/modules/services/networking/supplicant.nix +++ b/nixos/modules/services/networking/supplicant.nix @@ -39,8 +39,6 @@ let bindsTo = deps; after = deps; before = [ "network.target" ]; - # Receive restart event after resume - partOf = [ "post-resume.target" ]; path = [ pkgs.coreutils ]; diff --git a/nixos/modules/services/networking/supybot.nix b/nixos/modules/services/networking/supybot.nix index 64eb110683290..dc9fb31ffd0bf 100644 --- a/nixos/modules/services/networking/supybot.nix +++ b/nixos/modules/services/networking/supybot.nix @@ -3,32 +3,35 @@ with lib; let - cfg = config.services.supybot; - + isStateDirHome = hasPrefix "/home/" cfg.stateDir; + isStateDirVar = cfg.stateDir == "/var/lib/supybot"; + pyEnv = pkgs.python3.withPackages (p: [ p.limnoria ] ++ (cfg.extraPackages p)); in - { - options = { services.supybot = { enable = mkOption { + type = types.bool; default = false; - description = "Enable Supybot, an IRC bot"; + description = "Enable Supybot, an IRC bot (also known as Limnoria)."; }; stateDir = mkOption { - # Setting this to /var/lib/supybot caused useradd to fail - default = "/home/supybot"; + type = types.path; + default = if versionAtLeast config.system.stateVersion "20.09" + then "/var/lib/supybot" + else "/home/supybot"; + defaultText = "/var/lib/supybot"; description = "The root directory, logs and plugins are stored here"; }; configFile = mkOption { type = types.path; description = '' - Path to a supybot config file. This can be generated by + Path to initial supybot config file. This can be generated by running supybot-wizard. Note: all paths should include the full path to the stateDir @@ -36,44 +39,72 @@ in ''; }; + plugins = mkOption { + type = types.attrsOf types.path; + default = {}; + description = '' + Attribute set of additional plugins that will be symlinked to the + <filename>plugin</filename> subdirectory. + + Please note that you still need to add the plugins to the config + file (or with <literal>!load</literal>) using their attribute name. + ''; + example = literalExample '' + let + plugins = pkgs.fetchzip { + url = "https://github.com/ProgVal/Supybot-plugins/archive/57c2450c.zip"; + sha256 = "077snf84ibnva3sbpzdfpfma6hcdw7dflwnhg6pw7mgnf0nd84qd"; + }; + in + { + Wikipedia = "''${plugins}/Wikipedia"; + Decide = ./supy-decide; + } + ''; + }; + + extraPackages = mkOption { + default = p: []; + description = '' + Extra Python packages available to supybot plugins. The + value must be a function which receives the attrset defined + in <varname>python3Packages</varname> as the sole argument. + ''; + example = literalExample ''p: [ p.lxml p.requests ]''; + }; + }; }; - config = mkIf cfg.enable { - environment.systemPackages = [ pkgs.pythonPackages.limnoria ]; + environment.systemPackages = [ pkgs.python3Packages.limnoria ]; - users.users = singleton { - name = "supybot"; + users.users.supybot = { uid = config.ids.uids.supybot; group = "supybot"; description = "Supybot IRC bot user"; home = cfg.stateDir; - createHome = true; + isSystemUser = true; }; users.groups.supybot = { - name = "supybot"; gid = config.ids.gids.supybot; }; systemd.services.supybot = { description = "Supybot, an IRC bot"; + documentation = [ "https://limnoria.readthedocs.io/" ]; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; - path = [ pkgs.pythonPackages.limnoria ]; preStart = '' - cd ${cfg.stateDir} - mkdir -p backup conf data plugins logs/plugins tmp web - ln -sf ${cfg.configFile} supybot.cfg # This needs to be created afresh every time - rm -f supybot.cfg.bak + rm -f '${cfg.stateDir}/supybot.cfg.bak' ''; serviceConfig = { - ExecStart = "${pkgs.pythonPackages.limnoria}/bin/supybot ${cfg.stateDir}/supybot.cfg"; + ExecStart = "${pyEnv}/bin/supybot ${cfg.stateDir}/supybot.cfg"; PIDFile = "/run/supybot.pid"; User = "supybot"; Group = "supybot"; @@ -81,8 +112,50 @@ in Restart = "on-abort"; StartLimitInterval = "5m"; StartLimitBurst = "1"; + + NoNewPrivileges = true; + PrivateDevices = true; + PrivateMounts = true; + PrivateTmp = true; + ProtectControlGroups = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + RestrictNamespaces = true; + RestrictRealtime = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + RemoveIPC = true; + ProtectHostname = true; + CapabilityBoundingSet = ""; + ProtectSystem = "full"; + } + // optionalAttrs isStateDirVar { + StateDirectory = "supybot"; + ProtectSystem = "strict"; + } + // optionalAttrs (!isStateDirHome) { + ProtectHome = true; }; }; + systemd.tmpfiles.rules = [ + "d '${cfg.stateDir}' 0700 supybot supybot - -" + "d '${cfg.stateDir}/backup' 0750 supybot supybot - -" + "d '${cfg.stateDir}/conf' 0750 supybot supybot - -" + "d '${cfg.stateDir}/data' 0750 supybot supybot - -" + "d '${cfg.stateDir}/plugins' 0750 supybot supybot - -" + "d '${cfg.stateDir}/logs' 0750 supybot supybot - -" + "d '${cfg.stateDir}/logs/plugins' 0750 supybot supybot - -" + "d '${cfg.stateDir}/tmp' 0750 supybot supybot - -" + "d '${cfg.stateDir}/web' 0750 supybot supybot - -" + "L '${cfg.stateDir}/supybot.cfg' - - - - ${cfg.configFile}" + ] + ++ (flip mapAttrsToList cfg.plugins (name: dest: + "L+ '${cfg.stateDir}/plugins/${name}' - - - - ${dest}" + )); + }; } diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix index b3f2af5b17941..e717d78feed57 100644 --- a/nixos/modules/services/networking/syncthing.nix +++ b/nixos/modules/services/networking/syncthing.nix @@ -112,12 +112,12 @@ in { addresses = [ "tcp://192.168.0.10:51820" ]; }; }; - type = types.attrsOf (types.submodule ({ config, ... }: { + type = types.attrsOf (types.submodule ({ name, ... }: { options = { name = mkOption { type = types.str; - default = config._module.args.name; + default = name; description = '' Name of the device ''; @@ -169,13 +169,15 @@ in { description = '' folders which should be shared by syncthing. ''; - example = { - "/home/user/sync" = { - id = "syncme"; - devices = [ "bigbox" ]; - }; - }; - type = types.attrsOf (types.submodule ({ config, ... }: { + example = literalExample '' + { + "/home/user/sync" = { + id = "syncme"; + devices = [ "bigbox" ]; + }; + } + ''; + type = types.attrsOf (types.submodule ({ name, ... }: { options = { enable = mkOption { @@ -190,7 +192,7 @@ in { path = mkOption { type = types.str; - default = config._module.args.name; + default = name; description = '' The path to the folder which should be shared. ''; @@ -198,7 +200,7 @@ in { id = mkOption { type = types.str; - default = config._module.args.name; + default = name; description = '' The id of the folder. Must be the same on all devices. ''; @@ -206,7 +208,7 @@ in { label = mkOption { type = types.str; - default = config._module.args.name; + default = name; description = '' The label of the folder. ''; @@ -484,6 +486,24 @@ in { -gui-address=${cfg.guiAddress} \ -home=${cfg.configDir} ''; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateMounts = true; + PrivateTmp = true; + PrivateUsers = true; + ProtectControlGroups = true; + ProtectHostname = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + CapabilityBoundingSet = [ + "~CAP_SYS_PTRACE" "~CAP_SYS_ADMIN" + "~CAP_SETGID" "~CAP_SETUID" "~CAP_SETPCAP" + "~CAP_SYS_TIME" "~CAP_KILL" + ]; }; }; syncthing-init = mkIf ( diff --git a/nixos/modules/services/networking/tailscale.nix b/nixos/modules/services/networking/tailscale.nix new file mode 100644 index 0000000000000..4d6aeb75ebd15 --- /dev/null +++ b/nixos/modules/services/networking/tailscale.nix @@ -0,0 +1,49 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let cfg = config.services.tailscale; +in { + meta.maintainers = with maintainers; [ danderson mbaillie ]; + + options.services.tailscale = { + enable = mkEnableOption "Tailscale client daemon"; + + port = mkOption { + type = types.port; + default = 41641; + description = "The port to listen on for tunnel traffic (0=autoselect)."; + }; + }; + + config = mkIf cfg.enable { + systemd.services.tailscale = { + description = "Tailscale client daemon"; + + after = [ "network-pre.target" ]; + wants = [ "network-pre.target" ]; + wantedBy = [ "multi-user.target" ]; + + unitConfig = { + StartLimitIntervalSec = 0; + StartLimitBurst = 0; + }; + + serviceConfig = { + ExecStart = + "${pkgs.tailscale}/bin/tailscaled --port ${toString cfg.port}"; + + RuntimeDirectory = "tailscale"; + RuntimeDirectoryMode = 755; + + StateDirectory = "tailscale"; + StateDirectoryMode = 750; + + CacheDirectory = "tailscale"; + CacheDirectoryMode = 750; + + Restart = "on-failure"; + }; + }; + }; +} diff --git a/nixos/modules/services/networking/tcpcrypt.nix b/nixos/modules/services/networking/tcpcrypt.nix index a0ccb99500941..18f2e135124b4 100644 --- a/nixos/modules/services/networking/tcpcrypt.nix +++ b/nixos/modules/services/networking/tcpcrypt.nix @@ -29,8 +29,7 @@ in config = mkIf cfg.enable { - users.users = singleton { - name = "tcpcryptd"; + users.users.tcpcryptd = { uid = config.ids.uids.tcpcryptd; description = "tcpcrypt daemon user"; }; diff --git a/nixos/modules/services/networking/tox-bootstrapd.nix b/nixos/modules/services/networking/tox-bootstrapd.nix index 1d34921516903..f88e34827d00f 100644 --- a/nixos/modules/services/networking/tox-bootstrapd.nix +++ b/nixos/modules/services/networking/tox-bootstrapd.nix @@ -56,9 +56,8 @@ in config = mkIf config.services.toxBootstrapd.enable { - users.users = singleton - { name = "tox-bootstrapd"; - uid = config.ids.uids.tox-bootstrapd; + users.users.tox-bootstrapd = + { uid = config.ids.uids.tox-bootstrapd; description = "Tox bootstrap daemon user"; inherit home; createHome = true; diff --git a/nixos/modules/services/networking/unbound.nix b/nixos/modules/services/networking/unbound.nix index 3cf82e8839bb1..baed83591e1ed 100644 --- a/nixos/modules/services/networking/unbound.nix +++ b/nixos/modules/services/networking/unbound.nix @@ -53,6 +53,13 @@ in enable = mkEnableOption "Unbound domain name server"; + package = mkOption { + type = types.package; + default = pkgs.unbound; + defaultText = "pkgs.unbound"; + description = "The unbound package to use"; + }; + allowedAccess = mkOption { default = [ "127.0.0.0/24" ]; type = types.listOf types.str; @@ -94,7 +101,7 @@ in config = mkIf cfg.enable { - environment.systemPackages = [ pkgs.unbound ]; + environment.systemPackages = [ cfg.package ]; users.users.unbound = { description = "unbound daemon user"; @@ -114,7 +121,7 @@ in mkdir -m 0755 -p ${stateDir}/dev/ cp ${confFile} ${stateDir}/unbound.conf ${optionalString cfg.enableRootTrustAnchor '' - ${pkgs.unbound}/bin/unbound-anchor -a ${rootTrustAnchorFile} || echo "Root anchor updated!" + ${cfg.package}/bin/unbound-anchor -a ${rootTrustAnchorFile} || echo "Root anchor updated!" chown unbound ${stateDir} ${rootTrustAnchorFile} ''} touch ${stateDir}/dev/random @@ -122,7 +129,7 @@ in ''; serviceConfig = { - ExecStart = "${pkgs.unbound}/bin/unbound -d -c ${stateDir}/unbound.conf"; + ExecStart = "${cfg.package}/bin/unbound -d -c ${stateDir}/unbound.conf"; ExecStopPost="${pkgs.utillinux}/bin/umount ${stateDir}/dev/random"; ProtectSystem = true; diff --git a/nixos/modules/services/networking/unifi.nix b/nixos/modules/services/networking/unifi.nix index c922ba15960fd..4bdfa8143dce0 100644 --- a/nixos/modules/services/networking/unifi.nix +++ b/nixos/modules/services/networking/unifi.nix @@ -147,8 +147,10 @@ in }) mountPoints; systemd.tmpfiles.rules = [ - "e '${stateDir}' 0700 unifi - - -" + "d '${stateDir}' 0700 unifi - - -" "d '${stateDir}/data' 0700 unifi - - -" + "d '${stateDir}/webapps' 0700 unifi - - -" + "L+ '${stateDir}/webapps/ROOT' - - - - ${cfg.unifiPackage}/webapps/ROOT" ]; systemd.services.unifi = { @@ -161,17 +163,6 @@ in # This a HACK to fix missing dependencies of dynamic libs extracted from jars environment.LD_LIBRARY_PATH = with pkgs.stdenv; "${cc.cc.lib}/lib"; - preStart = '' - # Create the volatile webapps - rm -rf "${stateDir}/webapps" - mkdir -p "${stateDir}/webapps" - ln -s "${cfg.unifiPackage}/webapps/ROOT" "${stateDir}/webapps/ROOT" - ''; - - postStop = '' - rm -rf "${stateDir}/webapps" - ''; - serviceConfig = { Type = "simple"; ExecStart = "${(removeSuffix "\n" cmd)} start"; diff --git a/nixos/modules/services/networking/v2ray.nix b/nixos/modules/services/networking/v2ray.nix new file mode 100644 index 0000000000000..a1774cdffbb9c --- /dev/null +++ b/nixos/modules/services/networking/v2ray.nix @@ -0,0 +1,81 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + options = { + + services.v2ray = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to run v2ray server. + + Either <literal>configFile</literal> or <literal>config</literal> must be specified. + ''; + }; + + configFile = mkOption { + type = types.nullOr types.str; + default = null; + example = "/etc/v2ray/config.json"; + description = '' + The absolute path to the configuration file. + + Either <literal>configFile</literal> or <literal>config</literal> must be specified. + + See <link xlink:href="https://v2ray.com/en/configuration/overview.html"/>. + ''; + }; + + config = mkOption { + type = types.nullOr (types.attrsOf types.unspecified); + default = null; + example = { + inbounds = [{ + port = 1080; + listen = "127.0.0.1"; + protocol = "http"; + }]; + outbounds = [{ + protocol = "freedom"; + }]; + }; + description = '' + The configuration object. + + Either `configFile` or `config` must be specified. + + See <link xlink:href="https://v2ray.com/en/configuration/overview.html"/>. + ''; + }; + }; + + }; + + config = let + cfg = config.services.v2ray; + configFile = if cfg.configFile != null + then cfg.configFile + else (pkgs.writeText "v2ray.json" (builtins.toJSON cfg.config)); + + in mkIf cfg.enable { + assertions = [ + { + assertion = (cfg.configFile == null) != (cfg.config == null); + message = "Either but not both `configFile` and `config` should be specified for v2ray."; + } + ]; + + systemd.services.v2ray = { + description = "v2ray Daemon"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pkgs.v2ray ]; + script = '' + exec v2ray -config ${configFile} + ''; + }; + }; +} diff --git a/nixos/modules/services/networking/vsftpd.nix b/nixos/modules/services/networking/vsftpd.nix index 90093d9a78d9e..b3e201844236e 100644 --- a/nixos/modules/services/networking/vsftpd.nix +++ b/nixos/modules/services/networking/vsftpd.nix @@ -133,8 +133,8 @@ let ${optionalString cfg.enableVirtualUsers '' guest_enable=YES guest_username=vsftpd - pam_service_name=vsftpd ''} + pam_service_name=vsftpd ${cfg.extraConfig} ''; @@ -279,21 +279,22 @@ in message = "vsftpd: If enableVirtualUsers is true, you need to setup both the userDbPath and localUsers options."; }]; - users.users = - [ { name = "vsftpd"; - uid = config.ids.uids.vsftpd; - description = "VSFTPD user"; - home = if cfg.localRoot != null - then cfg.localRoot # <= Necessary for virtual users. - else "/homeless-shelter"; - } - ] ++ optional cfg.anonymousUser - { name = "ftp"; + users.users = { + "vsftpd" = { + uid = config.ids.uids.vsftpd; + description = "VSFTPD user"; + home = if cfg.localRoot != null + then cfg.localRoot # <= Necessary for virtual users. + else "/homeless-shelter"; + }; + } // optionalAttrs cfg.anonymousUser { + "ftp" = { name = "ftp"; uid = config.ids.uids.ftp; group = "ftp"; description = "Anonymous FTP user"; home = cfg.anonymousUserHome; }; + }; users.groups.ftp.gid = config.ids.gids.ftp; diff --git a/nixos/modules/services/networking/wg-quick.nix b/nixos/modules/services/networking/wg-quick.nix index b770d47d269ef..ff1bdeed9f481 100644 --- a/nixos/modules/services/networking/wg-quick.nix +++ b/nixos/modules/services/networking/wg-quick.nix @@ -302,7 +302,7 @@ in { ###### implementation config = mkIf (cfg.interfaces != {}) { - boot.extraModulePackages = [ kernel.wireguard ]; + boot.extraModulePackages = optional (versionOlder kernel.kernel.version "5.6") kernel.wireguard; environment.systemPackages = [ pkgs.wireguard-tools ]; # This is forced to false for now because the default "--validmark" rpfilter we apply on reverse path filtering # breaks the wg-quick routing because wireguard packets leave with a fwmark from wireguard. diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix index 980961225c9e0..e8f83f6dd8bf8 100644 --- a/nixos/modules/services/networking/wireguard.nix +++ b/nixos/modules/services/networking/wireguard.nix @@ -151,7 +151,7 @@ let publicKey = mkOption { example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg="; type = types.str; - description = "The base64 public key the peer."; + description = "The base64 public key of the peer."; }; presharedKey = mkOption { @@ -428,14 +428,14 @@ in ++ (attrValues ( mapAttrs (name: value: { assertion = value.generatePrivateKeyFile -> (value.privateKey == null); - message = "networking.wireguard.interfaces.${name}.generatePrivateKey must not be set if networking.wireguard.interfaces.${name}.privateKey is set."; + message = "networking.wireguard.interfaces.${name}.generatePrivateKeyFile must not be set if networking.wireguard.interfaces.${name}.privateKey is set."; }) cfg.interfaces)) ++ map ({ interfaceName, peer, ... }: { assertion = (peer.presharedKey == null) || (peer.presharedKeyFile == null); message = "networking.wireguard.interfaces.${interfaceName} peer «${peer.publicKey}» has both presharedKey and presharedKeyFile set, but only one can be used."; }) all_peers; - boot.extraModulePackages = [ kernel.wireguard ]; + boot.extraModulePackages = optional (versionOlder kernel.kernel.version "5.6") kernel.wireguard; environment.systemPackages = [ pkgs.wireguard-tools ]; systemd.services = diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix index 8f05c3949fba7..de0f11595a949 100644 --- a/nixos/modules/services/networking/wpa_supplicant.nix +++ b/nixos/modules/services/networking/wpa_supplicant.nix @@ -233,6 +233,7 @@ in { path = [ pkgs.wpa_supplicant ]; script = '' + iface_args="-s -u -D${cfg.driver} -c ${configFile}" ${if ifaces == [] then '' for i in $(cd /sys/class/net && echo *); do DEVTYPE= @@ -240,14 +241,14 @@ in { if [ -e "$UEVENT_PATH" ]; then source "$UEVENT_PATH" if [ "$DEVTYPE" = "wlan" -o -e /sys/class/net/$i/wireless ]; then - ifaces="$ifaces''${ifaces:+ -N} -i$i" + args+="''${args:+ -N} -i$i $iface_args" fi fi done '' else '' - ifaces="${concatStringsSep " -N " (map (i: "-i${i}") ifaces)}" + args="${concatMapStringsSep " -N " (i: "-i${i} $iface_args") ifaces}" ''} - exec wpa_supplicant -s -u -D${cfg.driver} -c ${configFile} $ifaces + exec wpa_supplicant $args ''; }; diff --git a/nixos/modules/services/networking/xandikos.nix b/nixos/modules/services/networking/xandikos.nix new file mode 100644 index 0000000000000..87c029156b9e7 --- /dev/null +++ b/nixos/modules/services/networking/xandikos.nix @@ -0,0 +1,148 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.xandikos; +in +{ + + options = { + services.xandikos = { + enable = mkEnableOption "Xandikos CalDAV and CardDAV server"; + + package = mkOption { + type = types.package; + default = pkgs.xandikos; + defaultText = "pkgs.xandikos"; + description = "The Xandikos package to use."; + }; + + address = mkOption { + type = types.str; + default = "localhost"; + description = '' + The IP address on which Xandikos will listen. + By default listens on localhost. + ''; + }; + + port = mkOption { + type = types.port; + default = 8080; + description = "The port of the Xandikos web application"; + }; + + routePrefix = mkOption { + type = types.str; + default = "/"; + description = '' + Path to Xandikos. + Useful when Xandikos is behind a reverse proxy. + ''; + }; + + extraOptions = mkOption { + default = []; + type = types.listOf types.str; + example = literalExample '' + [ "--autocreate" + "--defaults" + "--current-user-principal user" + "--dump-dav-xml" + ] + ''; + description = '' + Extra command line arguments to pass to xandikos. + ''; + }; + + nginx = mkOption { + default = {}; + description = '' + Configuration for nginx reverse proxy. + ''; + + type = types.submodule { + options = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Configure the nginx reverse proxy settings. + ''; + }; + + hostName = mkOption { + type = types.str; + description = '' + The hostname use to setup the virtualhost configuration + ''; + }; + }; + }; + }; + + }; + + }; + + config = mkIf cfg.enable ( + mkMerge [ + { + meta.maintainers = [ lib.maintainers."0x4A6F" ]; + + systemd.services.xandikos = { + description = "A Simple Calendar and Contact Server"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + User = "xandikos"; + Group = "xandikos"; + DynamicUser = "yes"; + RuntimeDirectory = "xandikos"; + StateDirectory = "xandikos"; + StateDirectoryMode = "0700"; + PrivateDevices = true; + # Sandboxing + CapabilityBoundingSet = "CAP_NET_RAW CAP_NET_ADMIN"; + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_PACKET AF_NETLINK"; + RestrictNamespaces = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + ExecStart = '' + ${cfg.package}/bin/xandikos \ + --directory /var/lib/xandikos \ + --listen_address ${cfg.address} \ + --port ${toString cfg.port} \ + --route-prefix ${cfg.routePrefix} \ + ${lib.concatStringsSep " " cfg.extraOptions} + ''; + }; + }; + } + + ( + mkIf cfg.nginx.enable { + services.nginx = { + enable = true; + virtualHosts."${cfg.nginx.hostName}" = { + locations."/" = { + proxyPass = "http://${cfg.address}:${toString cfg.port}/"; + }; + }; + }; + } + ) + ] + ); +} diff --git a/nixos/modules/services/networking/xinetd.nix b/nixos/modules/services/networking/xinetd.nix index 8dc6f845ed853..2f527ab156aa7 100644 --- a/nixos/modules/services/networking/xinetd.nix +++ b/nixos/modules/services/networking/xinetd.nix @@ -44,12 +44,7 @@ in options = { - services.xinetd.enable = mkOption { - default = false; - description = '' - Whether to enable the xinetd super-server daemon. - ''; - }; + services.xinetd.enable = mkEnableOption "the xinetd super-server daemon"; services.xinetd.extraDefaults = mkOption { default = ""; diff --git a/nixos/modules/services/networking/yggdrasil.nix b/nixos/modules/services/networking/yggdrasil.nix index 5d65f8e341367..9e675ecd6f4b9 100644 --- a/nixos/modules/services/networking/yggdrasil.nix +++ b/nixos/modules/services/networking/yggdrasil.nix @@ -12,11 +12,11 @@ let configFileProvided = (cfg.configFile != null); generateConfig = ( if configProvided && configFileProvided then - "${pkgs.jq}/bin/jq -s add /run/yggdrasil/configFile.json ${configAsFile}" + "${pkgs.jq}/bin/jq -s add ${configAsFile} ${cfg.configFile}" else if configProvided then "cat ${configAsFile}" else if configFileProvided then - "cat /run/yggdrasil/configFile.json" + "cat ${cfg.configFile}" else "${cfg.package}/bin/yggdrasil -genconf" ); @@ -147,7 +147,7 @@ in { RuntimeDirectory = "yggdrasil"; RuntimeDirectoryMode = "0700"; BindReadOnlyPaths = mkIf configFileProvided - [ "${cfg.configFile}:/run/yggdrasil/configFile.json" ]; + [ "${cfg.configFile}" ]; # TODO: as of yggdrasil 0.3.8 and systemd 243, yggdrasil fails # to set up the network adapter when DynamicUser is set. See diff --git a/nixos/modules/services/networking/zerotierone.nix b/nixos/modules/services/networking/zerotierone.nix index 764af3846fe5d..cf39ed065a769 100644 --- a/nixos/modules/services/networking/zerotierone.nix +++ b/nixos/modules/services/networking/zerotierone.nix @@ -38,10 +38,13 @@ in config = mkIf cfg.enable { systemd.services.zerotierone = { description = "ZeroTierOne"; - path = [ cfg.package ]; - bindsTo = [ "network-online.target" ]; - after = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + wants = [ "network-online.target" ]; + + path = [ cfg.package ]; + preStart = '' mkdir -p /var/lib/zerotier-one/networks.d chmod 700 /var/lib/zerotier-one @@ -53,6 +56,7 @@ in ExecStart = "${cfg.package}/bin/zerotier-one -p${toString cfg.port}"; Restart = "always"; KillMode = "process"; + TimeoutStopSec = 5; }; }; @@ -63,5 +67,16 @@ in networking.firewall.allowedUDPPorts = [ cfg.port ]; environment.systemPackages = [ cfg.package ]; + + # Prevent systemd from potentially changing the MAC address + systemd.network.links."50-zerotier" = { + matchConfig = { + OriginalName = "zt*"; + }; + linkConfig = { + AutoNegotiation = false; + MACAddressPolicy = "none"; + }; + }; }; } diff --git a/nixos/modules/services/networking/znc/default.nix b/nixos/modules/services/networking/znc/default.nix index 0a9848a493490..a7315896c5063 100644 --- a/nixos/modules/services/networking/znc/default.nix +++ b/nixos/modules/services/networking/znc/default.nix @@ -287,20 +287,22 @@ in ''; }; - users.users = optional (cfg.user == defaultUser) - { name = defaultUser; - description = "ZNC server daemon owner"; - group = defaultUser; - uid = config.ids.uids.znc; - home = cfg.dataDir; - createHome = true; + users.users = optionalAttrs (cfg.user == defaultUser) { + ${defaultUser} = + { description = "ZNC server daemon owner"; + group = defaultUser; + uid = config.ids.uids.znc; + home = cfg.dataDir; + createHome = true; + }; }; - users.groups = optional (cfg.user == defaultUser) - { name = defaultUser; - gid = config.ids.gids.znc; - members = [ defaultUser ]; - }; + users.groups = optionalAttrs (cfg.user == defaultUser) { + ${defaultUser} = + { gid = config.ids.gids.znc; + members = [ defaultUser ]; + }; + }; }; } diff --git a/nixos/modules/services/printing/cupsd.nix b/nixos/modules/services/printing/cupsd.nix index 1071c05d514ef..59306d625e6bf 100644 --- a/nixos/modules/services/printing/cupsd.nix +++ b/nixos/modules/services/printing/cupsd.nix @@ -112,6 +112,15 @@ in { + imports = [ + (mkChangedOptionModule [ "services" "printing" "gutenprint" ] [ "services" "printing" "drivers" ] + (config: + let enabled = getAttrFromPath [ "services" "printing" "gutenprint" ] config; + in if enabled then [ pkgs.gutenprint ] else [ ])) + (mkRemovedOptionModule [ "services" "printing" "cupsFilesConf" ] "") + (mkRemovedOptionModule [ "services" "printing" "cupsdConf" ] "") + ]; + ###### interface options = { @@ -279,9 +288,8 @@ in config = mkIf config.services.printing.enable { - users.users = singleton - { name = "cups"; - uid = config.ids.uids.cups; + users.users.cups = + { uid = config.ids.uids.cups; group = "lp"; description = "CUPS printing services"; }; diff --git a/nixos/modules/services/scheduling/atd.nix b/nixos/modules/services/scheduling/atd.nix index a32907647a0d5..cefe72b0e999c 100644 --- a/nixos/modules/services/scheduling/atd.nix +++ b/nixos/modules/services/scheduling/atd.nix @@ -57,22 +57,16 @@ in security.pam.services.atd = {}; - users.users = singleton - { name = "atd"; - uid = config.ids.uids.atd; + users.users.atd = + { uid = config.ids.uids.atd; description = "atd user"; home = "/var/empty"; }; - users.groups = singleton - { name = "atd"; - gid = config.ids.gids.atd; - }; + users.groups.atd.gid = config.ids.gids.atd; systemd.services.atd = { description = "Job Execution Daemon (atd)"; - after = [ "systemd-udev-settle.service" ]; - wants = [ "systemd-udev-settle.service" ]; wantedBy = [ "multi-user.target" ]; path = [ at ]; diff --git a/nixos/modules/services/scheduling/fcron.nix b/nixos/modules/services/scheduling/fcron.nix index e43ca014e148a..42bed21bf25bd 100644 --- a/nixos/modules/services/scheduling/fcron.nix +++ b/nixos/modules/services/scheduling/fcron.nix @@ -86,7 +86,8 @@ in services.fcron.systab = systemCronJobs; - environment.etc = + environment.etc = listToAttrs + (map (x: { name = x.target; value = x; }) [ (allowdeny "allow" (cfg.allow)) (allowdeny "deny" cfg.deny) # see man 5 fcron.conf @@ -112,7 +113,7 @@ in gid = config.ids.gids.fcron; mode = "0644"; } - ]; + ]); environment.systemPackages = [ pkgs.fcron ]; users.users.fcron = { diff --git a/nixos/modules/services/search/hound.nix b/nixos/modules/services/search/hound.nix index 6740928db9a70..7a44489efe61f 100644 --- a/nixos/modules/services/search/hound.nix +++ b/nixos/modules/services/search/hound.nix @@ -88,19 +88,19 @@ in { }; config = mkIf cfg.enable { - users.groups = optional (cfg.group == "hound") { - name = "hound"; - gid = config.ids.gids.hound; + users.groups = optionalAttrs (cfg.group == "hound") { + hound.gid = config.ids.gids.hound; }; - users.users = optional (cfg.user == "hound") { - name = "hound"; - description = "hound code search"; - createHome = true; - home = cfg.home; - group = cfg.group; - extraGroups = cfg.extraGroups; - uid = config.ids.uids.hound; + users.users = optionalAttrs (cfg.user == "hound") { + hound = { + description = "hound code search"; + createHome = true; + home = cfg.home; + group = cfg.group; + extraGroups = cfg.extraGroups; + uid = config.ids.uids.hound; + }; }; systemd.services.hound = { diff --git a/nixos/modules/services/search/kibana.nix b/nixos/modules/services/search/kibana.nix index 43a63aa8fdc2a..2beb265ee5d11 100644 --- a/nixos/modules/services/search/kibana.nix +++ b/nixos/modules/services/search/kibana.nix @@ -198,8 +198,7 @@ in { environment.systemPackages = [ cfg.package ]; - users.users = singleton { - name = "kibana"; + users.users.kibana = { uid = config.ids.uids.kibana; description = "Kibana service user"; home = cfg.dataDir; diff --git a/nixos/modules/services/search/solr.nix b/nixos/modules/services/search/solr.nix index 5ef7d9893a49d..a8615a20a1cf2 100644 --- a/nixos/modules/services/search/solr.nix +++ b/nixos/modules/services/search/solr.nix @@ -13,19 +13,11 @@ in services.solr = { enable = mkEnableOption "Solr"; - # default to the 8.x series not forcing major version upgrade of those on the 7.x series package = mkOption { type = types.package; - default = if versionAtLeast config.system.stateVersion "19.09" - then pkgs.solr_8 - else pkgs.solr_7 - ; + default = pkgs.solr; defaultText = "pkgs.solr"; - description = '' - Which Solr package to use. This defaults to version 7.x if - <literal>system.stateVersion < 19.09</literal> and version 8.x - otherwise. - ''; + description = "Which Solr package to use."; }; port = mkOption { @@ -100,18 +92,18 @@ in }; }; - users.users = optionalAttrs (cfg.user == "solr") (singleton - { name = "solr"; + users.users = optionalAttrs (cfg.user == "solr") { + solr = { group = cfg.group; home = cfg.stateDir; createHome = true; uid = config.ids.uids.solr; - }); + }; + }; - users.groups = optionalAttrs (cfg.group == "solr") (singleton - { name = "solr"; - gid = config.ids.gids.solr; - }); + users.groups = optionalAttrs (cfg.group == "solr") { + solr.gid = config.ids.gids.solr; + }; }; diff --git a/nixos/modules/services/security/bitwarden_rs/default.nix b/nixos/modules/services/security/bitwarden_rs/default.nix index d1817db075550..903a532703774 100644 --- a/nixos/modules/services/security/bitwarden_rs/default.nix +++ b/nixos/modules/services/security/bitwarden_rs/default.nix @@ -18,15 +18,33 @@ let else key + toUpper x) "" parts; in if builtins.match "[A-Z0-9_]+" name != null then name else partsToEnvVar parts; - configFile = pkgs.writeText "bitwarden_rs.env" (concatMapStrings (s: s + "\n") ( - (concatLists (mapAttrsToList (name: value: - if value != null then [ "${nameToEnvVar name}=${if isBool value then boolToString value else toString value}" ] else [] - ) cfg.config)))); + # Due to the different naming schemes allowed for config keys, + # we can only check for values consistently after converting them to their corresponding environment variable name. + configEnv = + let + configEnv = listToAttrs (concatLists (mapAttrsToList (name: value: + if value != null then [ (nameValuePair (nameToEnvVar name) (if isBool value then boolToString value else toString value)) ] else [] + ) cfg.config)); + in { DATA_FOLDER = "/var/lib/bitwarden_rs"; } // optionalAttrs (!(configEnv ? WEB_VAULT_ENABLED) || configEnv.WEB_VAULT_ENABLED == "true") { + WEB_VAULT_FOLDER = "${pkgs.bitwarden_rs-vault}/share/bitwarden_rs/vault"; + } // configEnv; + + configFile = pkgs.writeText "bitwarden_rs.env" (concatStrings (mapAttrsToList (name: value: "${name}=${value}\n") configEnv)); + + bitwarden_rs = pkgs.bitwarden_rs.override { inherit (cfg) dbBackend; }; in { options.services.bitwarden_rs = with types; { enable = mkEnableOption "bitwarden_rs"; + dbBackend = mkOption { + type = enum [ "sqlite" "mysql" "postgresql" ]; + default = "sqlite"; + description = '' + Which database backend bitwarden_rs will be using. + ''; + }; + backupDir = mkOption { type = nullOr str; default = null; @@ -40,7 +58,7 @@ in { default = {}; example = literalExample '' { - domain = https://bw.domain.tld:8443; + domain = "https://bw.domain.tld:8443"; signupsAllowed = true; rocketPort = 8222; rocketLog = "critical"; @@ -56,23 +74,20 @@ in { even though foo2 would have been converted to FOO_2. This allows working around any potential future conflicting naming conventions. - Based on the attributes passed to this config option a environment file will be generated + Based on the attributes passed to this config option an environment file will be generated that is passed to bitwarden_rs's systemd service. The available configuration options can be found in - <link xlink:href="https://github.com/dani-garcia/bitwarden_rs/blob/1.8.0/.env.template">the environment template file</link>. + <link xlink:href="https://github.com/dani-garcia/bitwarden_rs/blob/${bitwarden_rs.version}/.env.template">the environment template file</link>. ''; - apply = config: optionalAttrs config.webVaultEnabled { - webVaultFolder = "${pkgs.bitwarden_rs-vault}/share/bitwarden_rs/vault"; - } // config; }; }; config = mkIf cfg.enable { - services.bitwarden_rs.config = { - dataFolder = "/var/lib/bitwarden_rs"; - webVaultEnabled = mkDefault true; - }; + assertions = [ { + assertion = cfg.backupDir != null -> cfg.dbBackend == "sqlite"; + message = "Backups for database backends other than sqlite will need customization"; + } ]; users.users.bitwarden_rs = { inherit group; @@ -87,7 +102,7 @@ in { User = user; Group = group; EnvironmentFile = configFile; - ExecStart = "${pkgs.bitwarden_rs}/bin/bitwarden_rs"; + ExecStart = "${bitwarden_rs}/bin/bitwarden_rs"; LimitNOFILE = "1048576"; LimitNPROC = "64"; PrivateTmp = "true"; @@ -109,6 +124,7 @@ in { path = with pkgs; [ sqlite ]; serviceConfig = { SyslogIdentifier = "backup-bitwarden_rs"; + Type = "oneshot"; User = mkDefault user; Group = mkDefault group; ExecStart = "${pkgs.bash}/bin/bash ${./backup.sh}"; diff --git a/nixos/modules/services/security/certmgr.nix b/nixos/modules/services/security/certmgr.nix index e89078883ebed..94c0ba141179e 100644 --- a/nixos/modules/services/security/certmgr.nix +++ b/nixos/modules/services/security/certmgr.nix @@ -113,7 +113,7 @@ in otherCert = "/var/certmgr/specs/other-cert.json"; } ''; - type = with types; attrsOf (either (submodule { + type = with types; attrsOf (either path (submodule { options = { service = mkOption { type = nullOr str; @@ -148,7 +148,7 @@ in description = "certmgr spec request object."; }; }; - }) path); + })); description = '' Certificate specs as described by: <link xlink:href="https://github.com/cloudflare/certmgr#certificate-specs" /> diff --git a/nixos/modules/services/security/clamav.nix b/nixos/modules/services/security/clamav.nix index 04b433f8f2bf6..aaf6fb0479baf 100644 --- a/nixos/modules/services/security/clamav.nix +++ b/nixos/modules/services/security/clamav.nix @@ -30,6 +30,10 @@ let ''; in { + imports = [ + (mkRenamedOptionModule [ "services" "clamav" "updater" "config" ] [ "services" "clamav" "updater" "extraConfig" ]) + ]; + options = { services.clamav = { daemon = { @@ -79,18 +83,15 @@ in config = mkIf (cfg.updater.enable || cfg.daemon.enable) { environment.systemPackages = [ pkg ]; - users.users = singleton { - name = clamavUser; + users.users.${clamavUser} = { uid = config.ids.uids.clamav; group = clamavGroup; description = "ClamAV daemon user"; home = stateDir; }; - users.groups = singleton { - name = clamavGroup; - gid = config.ids.gids.clamav; - }; + users.groups.${clamavGroup} = + { gid = config.ids.gids.clamav; }; environment.etc."clamav/freshclam.conf".source = freshclamConfigFile; environment.etc."clamav/clamd.conf".source = clamdConfigFile; diff --git a/nixos/modules/services/security/fail2ban.nix b/nixos/modules/services/security/fail2ban.nix index 716ae7a2d2f4c..3f84f9c2560cc 100644 --- a/nixos/modules/services/security/fail2ban.nix +++ b/nixos/modules/services/security/fail2ban.nix @@ -6,15 +6,32 @@ let cfg = config.services.fail2ban; - fail2banConf = pkgs.writeText "fail2ban.conf" cfg.daemonConfig; + fail2banConf = pkgs.writeText "fail2ban.local" cfg.daemonConfig; - jailConf = pkgs.writeText "jail.conf" - (concatStringsSep "\n" (attrValues (flip mapAttrs cfg.jails (name: def: + jailConf = pkgs.writeText "jail.local" '' + [INCLUDES] + + before = paths-nixos.conf + + ${concatStringsSep "\n" (attrValues (flip mapAttrs cfg.jails (name: def: optionalString (def != "") '' [${name}] ${def} - '')))); + '')))} + ''; + + pathsConf = pkgs.writeText "paths-nixos.conf" '' + # NixOS + + [INCLUDES] + + before = paths-common.conf + + after = paths-overrides.local + + [DEFAULT] + ''; in @@ -31,21 +48,135 @@ in description = "Whether to enable the fail2ban service."; }; + package = mkOption { + default = pkgs.fail2ban; + type = types.package; + example = "pkgs.fail2ban_0_11"; + description = "The fail2ban package to use for running the fail2ban service."; + }; + + packageFirewall = mkOption { + default = pkgs.iptables; + type = types.package; + example = "pkgs.nftables"; + description = "The firewall package used by fail2ban service."; + }; + + banaction = mkOption { + default = "iptables-multiport"; + type = types.str; + example = "nftables-multiport"; + description = '' + Default banning action (e.g. iptables, iptables-new, iptables-multiport, + shorewall, etc) It is used to define action_* variables. Can be overridden + globally or per section within jail.local file + ''; + }; + + banaction-allports = mkOption { + default = "iptables-allport"; + type = types.str; + example = "nftables-allport"; + description = '' + Default banning action (e.g. iptables, iptables-new, iptables-multiport, + shorewall, etc) It is used to define action_* variables. Can be overridden + globally or per section within jail.local file + ''; + }; + + bantime-increment.enable = mkOption { + default = false; + type = types.bool; + description = '' + Allows to use database for searching of previously banned ip's to increase + a default ban time using special formula, default it is banTime * 1, 2, 4, 8, 16, 32... + ''; + }; + + bantime-increment.rndtime = mkOption { + default = "4m"; + type = types.str; + example = "8m"; + description = '' + "bantime-increment.rndtime" is the max number of seconds using for mixing with random time + to prevent "clever" botnets calculate exact time IP can be unbanned again + ''; + }; + + bantime-increment.maxtime = mkOption { + default = "10h"; + type = types.str; + example = "48h"; + description = '' + "bantime-increment.maxtime" is the max number of seconds using the ban time can reach (don't grows further) + ''; + }; + + bantime-increment.factor = mkOption { + default = "1"; + type = types.str; + example = "4"; + description = '' + "bantime-increment.factor" is a coefficient to calculate exponent growing of the formula or common multiplier, + default value of factor is 1 and with default value of formula, the ban time grows by 1, 2, 4, 8, 16 ... + ''; + }; + + bantime-increment.formula = mkOption { + default = "ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor"; + type = types.str; + example = "ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)"; + description = '' + "bantime-increment.formula" used by default to calculate next value of ban time, default value bellow, + the same ban time growing will be reached by multipliers 1, 2, 4, 8, 16, 32... + ''; + }; + + bantime-increment.multipliers = mkOption { + default = "1 2 4 8 16 32 64"; + type = types.str; + example = "2 4 16 128"; + description = '' + "bantime-increment.multipliers" used to calculate next value of ban time instead of formula, coresponding + previously ban count and given "bantime.factor" (for multipliers default is 1); + following example grows ban time by 1, 2, 4, 8, 16 ... and if last ban count greater as multipliers count, + always used last multiplier (64 in example), for factor '1' and original ban time 600 - 10.6 hours + ''; + }; + + bantime-increment.overalljails = mkOption { + default = false; + type = types.bool; + example = true; + description = '' + "bantime-increment.overalljails" (if true) specifies the search of IP in the database will be executed + cross over all jails, if false (dafault), only current jail of the ban IP will be searched + ''; + }; + + ignoreIP = mkOption { + default = [ ]; + type = types.listOf types.str; + example = [ "192.168.0.0/16" "2001:DB8::42" ]; + description = '' + "ignoreIP" can be a list of IP addresses, CIDR masks or DNS hosts. Fail2ban will not ban a host which + matches an address in this list. Several addresses can be defined using space (and/or comma) separator. + ''; + }; + daemonConfig = mkOption { - default = - '' - [Definition] - loglevel = INFO - logtarget = SYSLOG - socket = /run/fail2ban/fail2ban.sock - pidfile = /run/fail2ban/fail2ban.pid - ''; + default = '' + [Definition] + logtarget = SYSLOG + socket = /run/fail2ban/fail2ban.sock + pidfile = /run/fail2ban/fail2ban.pid + dbfile = /var/lib/fail2ban/fail2ban.sqlite3 + ''; type = types.lines; - description = - '' - The contents of Fail2ban's main configuration file. It's - generally not necessary to change it. - ''; + description = '' + The contents of Fail2ban's main configuration file. It's + generally not necessary to change it. + ''; }; jails = mkOption { @@ -65,88 +196,111 @@ in } ''; type = types.attrsOf types.lines; - description = - '' - The configuration of each Fail2ban “jail”. A jail - consists of an action (such as blocking a port using - <command>iptables</command>) that is triggered when a - filter applied to a log file triggers more than a certain - number of times in a certain time period. Actions are - defined in <filename>/etc/fail2ban/action.d</filename>, - while filters are defined in - <filename>/etc/fail2ban/filter.d</filename>. - ''; + description = '' + The configuration of each Fail2ban “jail”. A jail + consists of an action (such as blocking a port using + <command>iptables</command>) that is triggered when a + filter applied to a log file triggers more than a certain + number of times in a certain time period. Actions are + defined in <filename>/etc/fail2ban/action.d</filename>, + while filters are defined in + <filename>/etc/fail2ban/filter.d</filename>. + ''; }; }; }; - ###### implementation config = mkIf cfg.enable { - environment.systemPackages = [ pkgs.fail2ban ]; + warnings = mkIf (config.networking.firewall.enable == false && config.networking.nftables.enable == false) [ + "fail2ban can not be used without a firewall" + ]; - environment.etc."fail2ban/fail2ban.conf".source = fail2banConf; - environment.etc."fail2ban/jail.conf".source = jailConf; - environment.etc."fail2ban/action.d".source = "${pkgs.fail2ban}/etc/fail2ban/action.d/*.conf"; - environment.etc."fail2ban/filter.d".source = "${pkgs.fail2ban}/etc/fail2ban/filter.d/*.conf"; + environment.systemPackages = [ cfg.package ]; - systemd.services.fail2ban = - { description = "Fail2ban Intrusion Prevention System"; + environment.etc = { + "fail2ban/fail2ban.local".source = fail2banConf; + "fail2ban/jail.local".source = jailConf; + "fail2ban/fail2ban.conf".source = "${cfg.package}/etc/fail2ban/fail2ban.conf"; + "fail2ban/jail.conf".source = "${cfg.package}/etc/fail2ban/jail.conf"; + "fail2ban/paths-common.conf".source = "${cfg.package}/etc/fail2ban/paths-common.conf"; + "fail2ban/paths-nixos.conf".source = pathsConf; + "fail2ban/action.d".source = "${cfg.package}/etc/fail2ban/action.d/*.conf"; + "fail2ban/filter.d".source = "${cfg.package}/etc/fail2ban/filter.d/*.conf"; + }; - wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; - partOf = optional config.networking.firewall.enable "firewall.service"; + systemd.services.fail2ban = { + description = "Fail2ban Intrusion Prevention System"; - restartTriggers = [ fail2banConf jailConf ]; - path = [ pkgs.fail2ban pkgs.iptables pkgs.iproute ]; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + partOf = optional config.networking.firewall.enable "firewall.service"; - preStart = - '' - mkdir -p /var/lib/fail2ban - ''; + restartTriggers = [ fail2banConf jailConf pathsConf ]; + reloadIfChanged = true; - unitConfig.Documentation = "man:fail2ban(1)"; + path = [ cfg.package cfg.packageFirewall pkgs.iproute ]; - serviceConfig = - { Type = "forking"; - ExecStart = "${pkgs.fail2ban}/bin/fail2ban-client -x start"; - ExecStop = "${pkgs.fail2ban}/bin/fail2ban-client stop"; - ExecReload = "${pkgs.fail2ban}/bin/fail2ban-client reload"; - PIDFile = "/run/fail2ban/fail2ban.pid"; - Restart = "always"; + unitConfig.Documentation = "man:fail2ban(1)"; - ReadOnlyDirectories = "/"; - ReadWriteDirectories = "/run/fail2ban /var/tmp /var/lib"; - PrivateTmp = "true"; - RuntimeDirectory = "fail2ban"; - CapabilityBoundingSet = "CAP_DAC_READ_SEARCH CAP_NET_ADMIN CAP_NET_RAW"; - }; + serviceConfig = { + ExecStart = "${cfg.package}/bin/fail2ban-server -xf start"; + ExecStop = "${cfg.package}/bin/fail2ban-server stop"; + ExecReload = "${cfg.package}/bin/fail2ban-server reload"; + Type = "simple"; + Restart = "on-failure"; + PIDFile = "/run/fail2ban/fail2ban.pid"; + # Capabilities + CapabilityBoundingSet = [ "CAP_AUDIT_READ" "CAP_DAC_READ_SEARCH" "CAP_NET_ADMIN" "CAP_NET_RAW" ]; + # Security + NoNewPrivileges = true; + # Directory + RuntimeDirectory = "fail2ban"; + RuntimeDirectoryMode = "0750"; + StateDirectory = "fail2ban"; + StateDirectoryMode = "0750"; + LogsDirectory = "fail2ban"; + LogsDirectoryMode = "0750"; + # Sandboxing + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + ProtectHostname = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; }; + }; # Add some reasonable default jails. The special "DEFAULT" jail # sets default values for all other jails. - services.fail2ban.jails.DEFAULT = - '' - ignoreip = 127.0.0.1/8 - bantime = 600 - findtime = 600 - maxretry = 3 - backend = systemd - enabled = true - ''; - + services.fail2ban.jails.DEFAULT = '' + ${optionalString cfg.bantime-increment.enable '' + # Bantime incremental + bantime.increment = ${if cfg.bantime-increment.enable then "true" else "false"} + bantime.maxtime = ${cfg.bantime-increment.maxtime} + bantime.factor = ${cfg.bantime-increment.factor} + bantime.formula = ${cfg.bantime-increment.formula} + bantime.multipliers = ${cfg.bantime-increment.multipliers} + bantime.overalljails = ${if cfg.bantime-increment.overalljails then "true" else "false"} + ''} + # Miscellaneous options + ignoreip = 127.0.0.1/8 ${optionalString config.networking.enableIPv6 "::1"} ${concatStringsSep " " cfg.ignoreIP} + maxretry = 3 + backend = systemd + # Actions + banaction = ${cfg.banaction} + banaction_allports = ${cfg.banaction-allports} + ''; # Block SSH if there are too many failing connection attempts. - services.fail2ban.jails.ssh-iptables = - '' - filter = sshd - action = iptables-multiport[name=SSH, port="${concatMapStringsSep "," (p: toString p) config.services.openssh.ports}", protocol=tcp] - maxretry = 5 - ''; - + services.fail2ban.jails.sshd = mkDefault '' + enabled = true + port = ${concatMapStringsSep "," (p: toString p) config.services.openssh.ports} + ''; }; - } diff --git a/nixos/modules/services/security/fprot.nix b/nixos/modules/services/security/fprot.nix index 474490391463f..3a0b08b3c6d83 100644 --- a/nixos/modules/services/security/fprot.nix +++ b/nixos/modules/services/security/fprot.nix @@ -10,12 +10,7 @@ in { services.fprot = { updater = { - enable = mkOption { - default = false; - description = '' - Whether to enable automatic F-Prot virus definitions database updates. - ''; - }; + enable = mkEnableOption "automatic F-Prot virus definitions database updates"; productData = mkOption { description = '' @@ -48,22 +43,18 @@ in { services.fprot.updater.licenseKeyfile = mkDefault "${pkgs.fprot}/opt/f-prot/license.key"; environment.systemPackages = [ pkgs.fprot ]; - environment.etc = singleton { + environment.etc."f-prot.conf" = { source = "${pkgs.fprot}/opt/f-prot/f-prot.conf"; - target = "f-prot.conf"; }; - users.users = singleton - { name = fprotUser; - uid = config.ids.uids.fprot; + users.users.${fprotUser} = + { uid = config.ids.uids.fprot; description = "F-Prot daemon user"; home = stateDir; }; - users.groups = singleton - { name = fprotGroup; - gid = config.ids.gids.fprot; - }; + users.groups.${fprotGroup} = + { gid = config.ids.gids.fprot; }; services.cron.systemCronJobs = [ "*/${toString cfg.updater.frequency} * * * * root start fprot-updater" ]; diff --git a/nixos/modules/services/security/oauth2_proxy.nix b/nixos/modules/services/security/oauth2_proxy.nix index 2abb9ec32acaa..46caadee20421 100644 --- a/nixos/modules/services/security/oauth2_proxy.nix +++ b/nixos/modules/services/security/oauth2_proxy.nix @@ -12,7 +12,7 @@ let # command-line to launch oauth2_proxy. providerSpecificOptions = { azure = cfg: { - azure.tenant = cfg.azure.tenant; + azure-tenant = cfg.azure.tenant; resource = cfg.azure.resource; }; @@ -44,6 +44,7 @@ let pass-access-token = passAccessToken; pass-basic-auth = passBasicAuth; pass-host-header = passHostHeader; + reverse-proxy = reverseProxy; proxy-prefix = proxyPrefix; profile-url = profileURL; redeem-url = redeemURL; @@ -65,8 +66,8 @@ let } // lib.optionalAttrs (cfg.htpasswd.file != null) { display-htpasswd-file = cfg.htpasswd.displayForm; } // lib.optionalAttrs tls.enable { - tls-cert = tls.certificate; - tls-key = tls.key; + tls-cert-file = tls.certificate; + tls-key-file = tls.key; https-address = tls.httpsAddress; } // (getProviderOptions cfg cfg.provider) // cfg.extraConfig; @@ -98,14 +99,21 @@ in ############################################## # PROVIDER configuration + # Taken from: https://github.com/pusher/oauth2_proxy/blob/master/providers/providers.go provider = mkOption { type = types.enum [ "google" - "github" "azure" + "facebook" + "github" + "keycloak" "gitlab" "linkedin" - "myusa" + "login.gov" + "bitbucket" + "nextcloud" + "digitalocean" + "oidc" ]; default = "google"; description = '' @@ -433,6 +441,17 @@ in ''; }; + reverseProxy = mkOption { + type = types.bool; + default = false; + description = '' + In case when running behind a reverse proxy, controls whether headers + like <literal>X-Real-Ip</literal> are accepted. Usage behind a reverse + proxy will require this flag to be set to avoid logging the reverse + proxy IP address. + ''; + }; + proxyPrefix = mkOption { type = types.str; default = "/oauth2"; diff --git a/nixos/modules/services/security/sshguard.nix b/nixos/modules/services/security/sshguard.nix index 4a174564dd2ca..e7a9cefdef30a 100644 --- a/nixos/modules/services/security/sshguard.nix +++ b/nixos/modules/services/security/sshguard.nix @@ -92,8 +92,11 @@ in { "-o cat" "-n1" ] ++ (map (name: "-t ${escapeShellArg name}") cfg.services)); + backend = if config.networking.nftables.enable + then "sshg-fw-nft-sets" + else "sshg-fw-ipset"; in '' - BACKEND="${pkgs.sshguard}/libexec/sshg-fw-ipset" + BACKEND="${pkgs.sshguard}/libexec/${backend}" LOGREADER="LANG=C ${pkgs.systemd}/bin/journalctl ${args}" ''; @@ -104,7 +107,9 @@ in { after = [ "network.target" ]; partOf = optional config.networking.firewall.enable "firewall.service"; - path = with pkgs; [ iptables ipset iproute systemd ]; + path = with pkgs; if config.networking.nftables.enable + then [ nftables iproute systemd ] + else [ iptables ipset iproute systemd ]; # The sshguard ipsets must exist before we invoke # iptables. sshguard creates the ipsets after startup if @@ -112,14 +117,14 @@ in { # the iptables rules because postStart races with the creation # of the ipsets. So instead, we create both the ipsets and # firewall rules before sshguard starts. - preStart = '' + preStart = optionalString config.networking.firewall.enable '' ${pkgs.ipset}/bin/ipset -quiet create -exist sshguard4 hash:net family inet ${pkgs.ipset}/bin/ipset -quiet create -exist sshguard6 hash:net family inet6 ${pkgs.iptables}/bin/iptables -I INPUT -m set --match-set sshguard4 src -j DROP ${pkgs.iptables}/bin/ip6tables -I INPUT -m set --match-set sshguard6 src -j DROP ''; - postStop = '' + postStop = optionalString config.networking.firewall.enable '' ${pkgs.iptables}/bin/iptables -D INPUT -m set --match-set sshguard4 src -j DROP ${pkgs.iptables}/bin/ip6tables -D INPUT -m set --match-set sshguard6 src -j DROP ${pkgs.ipset}/bin/ipset -quiet destroy sshguard4 diff --git a/nixos/modules/services/security/tor.nix b/nixos/modules/services/security/tor.nix index ed862387cce1d..18c105b2f5765 100644 --- a/nixos/modules/services/security/tor.nix +++ b/nixos/modules/services/security/tor.nix @@ -106,6 +106,12 @@ let in { + imports = [ + (mkRenamedOptionModule [ "services" "tor" "relay" "portSpec" ] [ "services" "tor" "relay" "port" ]) + (mkRemovedOptionModule [ "services" "tor" "relay" "isBridge" ] "Use services.tor.relay.role instead.") + (mkRemovedOptionModule [ "services" "tor" "relay" "isExit" ] "Use services.tor.relay.role instead.") + ]; + options = { services.tor = { enable = mkOption { diff --git a/nixos/modules/services/security/torify.nix b/nixos/modules/services/security/torify.nix index 08da726437ea8..39551190dd33f 100644 --- a/nixos/modules/services/security/torify.nix +++ b/nixos/modules/services/security/torify.nix @@ -25,6 +25,7 @@ in services.tor.tsocks = { enable = mkOption { + type = types.bool; default = false; description = '' Whether to build tsocks wrapper script to relay application traffic via Tor. @@ -40,6 +41,7 @@ in }; server = mkOption { + type = types.str; default = "localhost:9050"; example = "192.168.0.20"; description = '' @@ -48,6 +50,7 @@ in }; config = mkOption { + type = types.lines; default = ""; description = '' Extra configuration. Contents will be added verbatim to TSocks diff --git a/nixos/modules/services/security/torsocks.nix b/nixos/modules/services/security/torsocks.nix index c60c745443bcf..47ac95c4626e0 100644 --- a/nixos/modules/services/security/torsocks.nix +++ b/nixos/modules/services/security/torsocks.nix @@ -112,10 +112,9 @@ in config = mkIf cfg.enable { environment.systemPackages = [ pkgs.torsocks (wrapTorsocks "torsocks-faster" cfg.fasterServer) ]; - environment.etc = - [ { source = pkgs.writeText "torsocks.conf" (configFile cfg.server); - target = "tor/torsocks.conf"; - } - ]; + environment.etc."tor/torsocks.conf" = + { + source = pkgs.writeText "torsocks.conf" (configFile cfg.server); + }; }; } diff --git a/nixos/modules/services/security/vault.nix b/nixos/modules/services/security/vault.nix index b0ab8fadcbec9..6a8a3a93327eb 100644 --- a/nixos/modules/services/security/vault.nix +++ b/nixos/modules/services/security/vault.nix @@ -135,6 +135,7 @@ in User = "vault"; Group = "vault"; ExecStart = "${cfg.package}/bin/vault server -config ${configFile}"; + ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID"; PrivateDevices = true; PrivateTmp = true; ProtectSystem = "full"; diff --git a/nixos/modules/services/system/dbus.nix b/nixos/modules/services/system/dbus.nix index 936646a5fd78f..4a60fec1ca806 100644 --- a/nixos/modules/services/system/dbus.nix +++ b/nixos/modules/services/system/dbus.nix @@ -68,10 +68,7 @@ in environment.systemPackages = [ pkgs.dbus.daemon pkgs.dbus ]; - environment.etc = singleton - { source = configDir; - target = "dbus-1"; - }; + environment.etc."dbus-1".source = configDir; users.users.messagebus = { uid = config.ids.uids.messagebus; diff --git a/nixos/modules/services/system/kerberos/default.nix b/nixos/modules/services/system/kerberos/default.nix index c55241c4cff14..9a1e67399010e 100644 --- a/nixos/modules/services/system/kerberos/default.nix +++ b/nixos/modules/services/system/kerberos/default.nix @@ -51,12 +51,7 @@ in ###### interface options = { services.kerberos_server = { - enable = mkOption { - default = false; - description = '' - Enable the kerberos authentification server. - ''; - }; + enable = lib.mkEnableOption "the kerberos authentification server"; realms = mkOption { type = types.attrsOf (types.submodule realm); diff --git a/nixos/modules/services/system/localtime.nix b/nixos/modules/services/system/localtime.nix index c3c0b432b4940..8f8e2e2e9339e 100644 --- a/nixos/modules/services/system/localtime.nix +++ b/nixos/modules/services/system/localtime.nix @@ -8,6 +8,7 @@ in { options = { services.localtime = { enable = mkOption { + type = types.bool; default = false; description = '' Enable <literal>localtime</literal>, simple daemon for keeping the system @@ -35,6 +36,10 @@ in { # Install the systemd unit. systemd.packages = [ pkgs.localtime.out ]; + users.users.localtimed = { + description = "Taskserver user"; + }; + systemd.services.localtime = { wantedBy = [ "multi-user.target" ]; serviceConfig.Restart = "on-failure"; diff --git a/nixos/modules/services/system/nscd.nix b/nixos/modules/services/system/nscd.nix index e11f7e049d8fe..af26dd8470730 100644 --- a/nixos/modules/services/system/nscd.nix +++ b/nixos/modules/services/system/nscd.nix @@ -7,6 +7,10 @@ let nssModulesPath = config.system.nssModules.path; cfg = config.services.nscd; + nscd = if pkgs.stdenv.hostPlatform.libc == "glibc" + then pkgs.stdenv.cc.libc.bin + else pkgs.glibc.bin; + in { @@ -59,16 +63,16 @@ in # files. So prefix the ExecStart command with "!" to prevent systemd # from dropping privileges early. See ExecStart in systemd.service(5). serviceConfig = - { ExecStart = "!@${pkgs.glibc.bin}/sbin/nscd nscd"; + { ExecStart = "!@${nscd}/sbin/nscd nscd"; Type = "forking"; DynamicUser = true; RuntimeDirectory = "nscd"; PIDFile = "/run/nscd/nscd.pid"; Restart = "always"; ExecReload = - [ "${pkgs.glibc.bin}/sbin/nscd --invalidate passwd" - "${pkgs.glibc.bin}/sbin/nscd --invalidate group" - "${pkgs.glibc.bin}/sbin/nscd --invalidate hosts" + [ "${nscd}/sbin/nscd --invalidate passwd" + "${nscd}/sbin/nscd --invalidate group" + "${nscd}/sbin/nscd --invalidate hosts" ]; }; }; diff --git a/nixos/modules/services/system/uptimed.nix b/nixos/modules/services/system/uptimed.nix index 3c9978ab22692..1e256c51408e0 100644 --- a/nixos/modules/services/system/uptimed.nix +++ b/nixos/modules/services/system/uptimed.nix @@ -10,6 +10,7 @@ in options = { services.uptimed = { enable = mkOption { + type = types.bool; default = false; description = '' Enable <literal>uptimed</literal>, allowing you to track diff --git a/nixos/modules/services/torrent/deluge.nix b/nixos/modules/services/torrent/deluge.nix index 0c72505395ddb..2f1e7300ca385 100644 --- a/nixos/modules/services/torrent/deluge.nix +++ b/nixos/modules/services/torrent/deluge.nix @@ -5,6 +5,7 @@ with lib; let cfg = config.services.deluge; cfg_web = config.services.deluge.web; + isDeluge1 = versionOlder cfg.package.version "2.0.0"; openFilesLimit = 4096; listenPortsDefault = [ 6881 6889 ]; @@ -18,11 +19,11 @@ let preStart = if cfg.declarative then '' if [ -e ${declarativeLockFile} ]; then # Was declarative before, no need to back up anything - ln -sf ${configFile} ${configDir}/core.conf + ${if isDeluge1 then "ln -sf" else "cp"} ${configFile} ${configDir}/core.conf ln -sf ${cfg.authFile} ${configDir}/auth else # Declarative for the first time, backup stateful files - ln -sb --suffix=.stateful ${configFile} ${configDir}/core.conf + ${if isDeluge1 then "ln -s" else "cp"} -b --suffix=.stateful ${configFile} ${configDir}/core.conf ln -sb --suffix=.stateful ${cfg.authFile} ${configDir}/auth echo "Autogenerated file that signifies that this server configuration is managed declaratively by NixOS" \ > ${declarativeLockFile} @@ -144,6 +145,14 @@ in { This always contains unzip, gnutar, xz, p7zip and bzip2. ''; }; + + package = mkOption { + type = types.package; + example = literalExample "pkgs.deluge-1_x"; + description = '' + Deluge package to use. + ''; + }; }; deluge.web = { @@ -170,6 +179,13 @@ in { config = mkIf cfg.enable { + services.deluge.package = mkDefault ( + if versionAtLeast config.system.stateVersion "20.09" then + pkgs.deluge-2_x + else + pkgs.deluge-1_x + ); + # Provide a default set of `extraPackages`. services.deluge.extraPackages = with pkgs; [ unzip gnutar xz p7zip bzip2 ]; @@ -189,10 +205,10 @@ in { after = [ "network.target" ]; description = "Deluge BitTorrent Daemon"; wantedBy = [ "multi-user.target" ]; - path = [ pkgs.deluge ] ++ cfg.extraPackages; + path = [ cfg.package ] ++ cfg.extraPackages; serviceConfig = { ExecStart = '' - ${pkgs.deluge}/bin/deluged \ + ${cfg.package}/bin/deluged \ --do-not-daemonize \ --config ${configDir} ''; @@ -212,10 +228,11 @@ in { requires = [ "deluged.service" ]; description = "Deluge BitTorrent WebUI"; wantedBy = [ "multi-user.target" ]; - path = [ pkgs.deluge ]; + path = [ cfg.package ]; serviceConfig = { ExecStart = '' - ${pkgs.deluge}/bin/deluge-web \ + ${cfg.package}/bin/deluge-web \ + ${optionalString (!isDeluge1) "--do-not-daemonize"} \ --config ${configDir} \ --port ${toString cfg.web.port} ''; @@ -234,7 +251,7 @@ in { }) ]; - environment.systemPackages = [ pkgs.deluge ]; + environment.systemPackages = [ cfg.package ]; users.users = mkIf (cfg.user == "deluge") { deluge = { diff --git a/nixos/modules/services/torrent/magnetico.nix b/nixos/modules/services/torrent/magnetico.nix index 719827713ff92..7465c10e002c5 100644 --- a/nixos/modules/services/torrent/magnetico.nix +++ b/nixos/modules/services/torrent/magnetico.nix @@ -213,4 +213,6 @@ in { }; + meta.maintainers = with lib.maintainers; [ rnhmjoj ]; + } diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix index 7409eb8cdcbef..fd28b94f7be3b 100644 --- a/nixos/modules/services/torrent/transmission.nix +++ b/nixos/modules/services/torrent/transmission.nix @@ -7,6 +7,7 @@ let apparmor = config.security.apparmor.enable; homeDir = cfg.home; + downloadDirPermissions = cfg.downloadDirPermissions; downloadDir = "${homeDir}/Downloads"; incompleteDir = "${homeDir}/.incomplete"; @@ -16,16 +17,15 @@ let # for users in group "transmission" to have access to torrents fullSettings = { umask = 2; download-dir = downloadDir; incomplete-dir = incompleteDir; } // cfg.settings; - # Directories transmission expects to exist and be ug+rwx. - directoriesToManage = [ homeDir settingsDir fullSettings.download-dir fullSettings.incomplete-dir ]; - preStart = pkgs.writeScript "transmission-pre-start" '' #!${pkgs.runtimeShell} set -ex - for DIR in ${escapeShellArgs directoriesToManage}; do + for DIR in "${homeDir}" "${settingsDir}" "${fullSettings.download-dir}" "${fullSettings.incomplete-dir}"; do mkdir -p "$DIR" - chmod 770 "$DIR" done + chmod 755 "${homeDir}" + chmod 700 "${settingsDir}" + chmod ${downloadDirPermissions} "${fullSettings.download-dir}" "${fullSettings.incomplete-dir}" cp -f ${settingsFile} ${settingsDir}/settings.json ''; in @@ -71,6 +71,16 @@ in ''; }; + downloadDirPermissions = mkOption { + type = types.str; + default = "770"; + example = "775"; + description = '' + The permissions to set for download-dir and incomplete-dir. + They will be applied on every service start. + ''; + }; + port = mkOption { type = types.int; default = 9091; @@ -109,7 +119,7 @@ in # 1) Only the "transmission" user and group have access to torrents. # 2) Optionally update/force specific fields into the configuration file. serviceConfig.ExecStartPre = preStart; - serviceConfig.ExecStart = "${pkgs.transmission}/bin/transmission-daemon -f --port ${toString config.services.transmission.port}"; + serviceConfig.ExecStart = "${pkgs.transmission}/bin/transmission-daemon -f --port ${toString config.services.transmission.port} --config-dir ${settingsDir}"; serviceConfig.ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; serviceConfig.User = cfg.user; serviceConfig.Group = cfg.group; @@ -120,19 +130,23 @@ in # It's useful to have transmission in path, e.g. for remote control environment.systemPackages = [ pkgs.transmission ]; - users.users = optionalAttrs (cfg.user == "transmission") (singleton - { name = "transmission"; + users.users = optionalAttrs (cfg.user == "transmission") ({ + transmission = { + name = "transmission"; group = cfg.group; uid = config.ids.uids.transmission; description = "Transmission BitTorrent user"; home = homeDir; createHome = true; - }); + }; + }); - users.groups = optionalAttrs (cfg.group == "transmission") (singleton - { name = "transmission"; + users.groups = optionalAttrs (cfg.group == "transmission") ({ + transmission = { + name = "transmission"; gid = config.ids.gids.transmission; - }); + }; + }); # AppArmor profile security.apparmor.profiles = mkIf apparmor [ diff --git a/nixos/modules/services/ttys/agetty.nix b/nixos/modules/services/ttys/agetty.nix index f127d8a0276db..f3a629f7af700 100644 --- a/nixos/modules/services/ttys/agetty.nix +++ b/nixos/modules/services/ttys/agetty.nix @@ -102,7 +102,7 @@ in enable = mkDefault config.boot.isContainer; }; - environment.etc = singleton + environment.etc.issue = { # Friendly greeting on the virtual consoles. source = pkgs.writeText "issue" '' @@ -110,7 +110,6 @@ in ${config.services.mingetty.helpLine} ''; - target = "issue"; }; }; diff --git a/nixos/modules/services/wayland/cage.nix b/nixos/modules/services/wayland/cage.nix new file mode 100644 index 0000000000000..c59ca9983a6c6 --- /dev/null +++ b/nixos/modules/services/wayland/cage.nix @@ -0,0 +1,99 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.cage; +in { + options.services.cage.enable = mkEnableOption "cage kiosk service"; + + options.services.cage.user = mkOption { + type = types.str; + default = "demo"; + description = '' + User to log-in as. + ''; + }; + + options.services.cage.extraArguments = mkOption { + type = types.listOf types.str; + default = []; + defaultText = "[]"; + description = "Additional command line arguments to pass to Cage."; + example = ["-d"]; + }; + + options.services.cage.program = mkOption { + type = types.path; + default = "${pkgs.xterm}/bin/xterm"; + description = '' + Program to run in cage. + ''; + }; + + config = mkIf cfg.enable { + + # The service is partially based off of the one provided in the + # cage wiki at + # https://github.com/Hjdskes/cage/wiki/Starting-Cage-on-boot-with-systemd. + systemd.services."cage-tty1" = { + enable = true; + after = [ + "systemd-user-sessions.service" + "plymouth-start.service" + "plymouth-quit.service" + "systemd-logind.service" + "getty@tty1.service" + ]; + before = [ "graphical.target" ]; + wants = [ "dbus.socket" "systemd-logind.service" "plymouth-quit.service"]; + wantedBy = [ "graphical.target" ]; + conflicts = [ "getty@tty1.service" ]; + + restartIfChanged = false; + unitConfig.ConditionPathExists = "/dev/tty1"; + serviceConfig = { + ExecStart = '' + ${pkgs.cage}/bin/cage \ + ${escapeShellArgs cfg.extraArguments} \ + -- ${cfg.program} + ''; + User = cfg.user; + + IgnoreSIGPIPE = "no"; + + # Log this user with utmp, letting it show up with commands 'w' and + # 'who'. This is needed since we replace (a)getty. + UtmpIdentifier = "%n"; + UtmpMode = "user"; + # A virtual terminal is needed. + TTYPath = "/dev/tty1"; + TTYReset = "yes"; + TTYVHangup = "yes"; + TTYVTDisallocate = "yes"; + # Fail to start if not controlling the virtual terminal. + StandardInput = "tty-fail"; + StandardOutput = "syslog"; + StandardError = "syslog"; + # Set up a full (custom) user session for the user, required by Cage. + PAMName = "cage"; + }; + }; + + security.pam.services.cage.text = '' + auth required pam_unix.so nullok + account required pam_unix.so + session required pam_unix.so + session required ${pkgs.systemd}/lib/security/pam_systemd.so + ''; + + hardware.opengl.enable = mkDefault true; + + systemd.targets.graphical.wants = [ "cage-tty1.service" ]; + + systemd.defaultUnit = "graphical.target"; + }; + + meta.maintainers = with lib.maintainers; [ matthewbauer flokli ]; + +} diff --git a/nixos/modules/services/web-apps/codimd.nix b/nixos/modules/services/web-apps/codimd.nix index 5f56f8ed5a091..751f81649ddb4 100644 --- a/nixos/modules/services/web-apps/codimd.nix +++ b/nixos/modules/services/web-apps/codimd.nix @@ -156,7 +156,7 @@ in }; useCDN = mkOption { type = types.bool; - default = true; + default = false; description = '' Whether to use CDN resources or not. ''; diff --git a/nixos/modules/services/web-apps/documize.nix b/nixos/modules/services/web-apps/documize.nix index 1b90299aa23c8..a5f48e744fdc3 100644 --- a/nixos/modules/services/web-apps/documize.nix +++ b/nixos/modules/services/web-apps/documize.nix @@ -130,7 +130,7 @@ in { config = mkIf cfg.enable { systemd.services.documize-server = { description = "Documize Wiki"; - documentation = [ https://documize.com/ ]; + documentation = [ "https://documize.com/" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { diff --git a/nixos/modules/services/web-apps/dokuwiki.nix b/nixos/modules/services/web-apps/dokuwiki.nix new file mode 100644 index 0000000000000..76e18266a273f --- /dev/null +++ b/nixos/modules/services/web-apps/dokuwiki.nix @@ -0,0 +1,388 @@ +{ config, lib, pkgs, ... }: + +let + + inherit (lib) mkEnableOption mkForce mkIf mkMerge mkOption optionalAttrs recursiveUpdate types; + inherit (lib) concatMapStringsSep flatten mapAttrs mapAttrs' mapAttrsToList nameValuePair concatMapStringSep; + + eachSite = config.services.dokuwiki; + + user = "dokuwiki"; + group = config.services.nginx.group; + + dokuwikiAclAuthConfig = cfg: pkgs.writeText "acl.auth.php" '' + # acl.auth.php + # <?php exit()?> + # + # Access Control Lists + # + ${toString cfg.acl} + ''; + + dokuwikiLocalConfig = cfg: pkgs.writeText "local.php" '' + <?php + $conf['savedir'] = '${cfg.stateDir}'; + $conf['superuser'] = '${toString cfg.superUser}'; + $conf['useacl'] = '${toString cfg.aclUse}'; + $conf['disableactions'] = '${cfg.disableActions}'; + ${toString cfg.extraConfig} + ''; + + dokuwikiPluginsLocalConfig = cfg: pkgs.writeText "plugins.local.php" '' + <?php + ${cfg.pluginsConfig} + ''; + + pkg = hostName: cfg: pkgs.stdenv.mkDerivation rec { + pname = "dokuwiki-${hostName}"; + version = src.version; + src = cfg.package; + + installPhase = '' + mkdir -p $out + cp -r * $out/ + + # symlink the dokuwiki config + ln -s ${dokuwikiLocalConfig cfg} $out/share/dokuwiki/local.php + + # symlink plugins config + ln -s ${dokuwikiPluginsLocalConfig cfg} $out/share/dokuwiki/plugins.local.php + + # symlink acl + ln -s ${dokuwikiAclAuthConfig cfg} $out/share/dokuwiki/acl.auth.php + + # symlink additional plugin(s) and templates(s) + ${concatMapStringsSep "\n" (template: "ln -s ${template} $out/share/dokuwiki/lib/tpl/${template.name}") cfg.templates} + ${concatMapStringsSep "\n" (plugin: "ln -s ${plugin} $out/share/dokuwiki/lib/plugins/${plugin.name}") cfg.plugins} + ''; + }; + + siteOpts = { config, lib, name, ...}: { + options = { + enable = mkEnableOption "DokuWiki web application."; + + package = mkOption { + type = types.package; + default = pkgs.dokuwiki; + description = "Which dokuwiki package to use."; + }; + + hostName = mkOption { + type = types.str; + default = "localhost"; + description = "FQDN for the instance."; + }; + + stateDir = mkOption { + type = types.path; + default = "/var/lib/dokuwiki/${name}/data"; + description = "Location of the dokuwiki state directory."; + }; + + acl = mkOption { + type = types.nullOr types.lines; + default = null; + example = "* @ALL 8"; + description = '' + Access Control Lists: see <link xlink:href="https://www.dokuwiki.org/acl"/> + Mutually exclusive with services.dokuwiki.aclFile + Set this to a value other than null to take precedence over aclFile option. + + Warning: Consider using aclFile instead if you do not + want to store the ACL in the world-readable Nix store. + ''; + }; + + aclFile = mkOption { + type = with types; nullOr str; + default = if (config.aclUse && config.acl == null) then "/var/lib/dokuwiki/${name}/users.auth.php" else null; + description = '' + Location of the dokuwiki acl rules. Mutually exclusive with services.dokuwiki.acl + Mutually exclusive with services.dokuwiki.acl which is preferred. + Consult documentation <link xlink:href="https://www.dokuwiki.org/acl"/> for further instructions. + Example: <link xlink:href="https://github.com/splitbrain/dokuwiki/blob/master/conf/acl.auth.php.dist"/> + ''; + example = "/var/lib/dokuwiki/${name}/acl.auth.php"; + }; + + aclUse = mkOption { + type = types.bool; + default = true; + description = '' + Necessary for users to log in into the system. + Also limits anonymous users. When disabled, + everyone is able to create and edit content. + ''; + }; + + pluginsConfig = mkOption { + type = types.lines; + default = '' + $plugins['authad'] = 0; + $plugins['authldap'] = 0; + $plugins['authmysql'] = 0; + $plugins['authpgsql'] = 0; + ''; + description = '' + List of the dokuwiki (un)loaded plugins. + ''; + }; + + superUser = mkOption { + type = types.nullOr types.str; + default = "@admin"; + description = '' + You can set either a username, a list of usernames (“admin1,admin2”), + or the name of a group by prepending an @ char to the groupname + Consult documentation <link xlink:href="https://www.dokuwiki.org/config:superuser"/> for further instructions. + ''; + }; + + usersFile = mkOption { + type = with types; nullOr str; + default = if config.aclUse then "/var/lib/dokuwiki/${name}/users.auth.php" else null; + description = '' + Location of the dokuwiki users file. List of users. Format: + login:passwordhash:Real Name:email:groups,comma,separated + Create passwordHash easily by using:$ mkpasswd -5 password `pwgen 8 1` + Example: <link xlink:href="https://github.com/splitbrain/dokuwiki/blob/master/conf/users.auth.php.dist"/> + ''; + example = "/var/lib/dokuwiki/${name}/users.auth.php"; + }; + + disableActions = mkOption { + type = types.nullOr types.str; + default = ""; + example = "search,register"; + description = '' + Disable individual action modes. Refer to + <link xlink:href="https://www.dokuwiki.org/config:action_modes"/> + for details on supported values. + ''; + }; + + extraConfig = mkOption { + type = types.nullOr types.lines; + default = null; + example = '' + $conf['title'] = 'My Wiki'; + $conf['userewrite'] = 1; + ''; + description = '' + DokuWiki configuration. Refer to + <link xlink:href="https://www.dokuwiki.org/config"/> + for details on supported values. + ''; + }; + + plugins = mkOption { + type = types.listOf types.path; + default = []; + description = '' + List of path(s) to respective plugin(s) which are copied from the 'plugin' directory. + <note><para>These plugins need to be packaged before use, see example.</para></note> + ''; + example = '' + # Let's package the icalevents plugin + plugin-icalevents = pkgs.stdenv.mkDerivation { + name = "icalevents"; + # Download the plugin from the dokuwiki site + src = pkgs.fetchurl { + url = https://github.com/real-or-random/dokuwiki-plugin-icalevents/releases/download/2017-06-16/dokuwiki-plugin-icalevents-2017-06-16.zip; + sha256 = "e40ed7dd6bbe7fe3363bbbecb4de481d5e42385b5a0f62f6a6ce6bf3a1f9dfa8"; + }; + sourceRoot = "."; + # We need unzip to build this package + buildInputs = [ pkgs.unzip ]; + # Installing simply means copying all files to the output directory + installPhase = "mkdir -p $out; cp -R * $out/"; + }; + + # And then pass this theme to the plugin list like this: + plugins = [ plugin-icalevents ]; + ''; + }; + + templates = mkOption { + type = types.listOf types.path; + default = []; + description = '' + List of path(s) to respective template(s) which are copied from the 'tpl' directory. + <note><para>These templates need to be packaged before use, see example.</para></note> + ''; + example = '' + # Let's package the bootstrap3 theme + template-bootstrap3 = pkgs.stdenv.mkDerivation { + name = "bootstrap3"; + # Download the theme from the dokuwiki site + src = pkgs.fetchurl { + url = https://github.com/giterlizzi/dokuwiki-template-bootstrap3/archive/v2019-05-22.zip; + sha256 = "4de5ff31d54dd61bbccaf092c9e74c1af3a4c53e07aa59f60457a8f00cfb23a6"; + }; + # We need unzip to build this package + buildInputs = [ pkgs.unzip ]; + # Installing simply means copying all files to the output directory + installPhase = "mkdir -p $out; cp -R * $out/"; + }; + + # And then pass this theme to the template list like this: + templates = [ template-bootstrap3 ]; + ''; + }; + + poolConfig = mkOption { + type = with types; attrsOf (oneOf [ str int bool ]); + default = { + "pm" = "dynamic"; + "pm.max_children" = 32; + "pm.start_servers" = 2; + "pm.min_spare_servers" = 2; + "pm.max_spare_servers" = 4; + "pm.max_requests" = 500; + }; + description = '' + Options for the dokuwiki PHP pool. See the documentation on <literal>php-fpm.conf</literal> + for details on configuration directives. + ''; + }; + + nginx = mkOption { + type = types.submodule ( + recursiveUpdate + (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) + { + # Enable encryption by default, + options.forceSSL.default = true; + options.enableACME.default = true; + } + ); + default = {forceSSL = true; enableACME = true;}; + example = { + serverAliases = [ + "wiki.\${config.networking.domain}" + ]; + enableACME = false; + }; + description = '' + With this option, you can customize the nginx virtualHost which already has sensible defaults for DokuWiki. + ''; + }; + }; + }; +in +{ + # interface + options = { + services.dokuwiki = mkOption { + type = types.attrsOf (types.submodule siteOpts); + default = {}; + description = "Sepcification of one or more dokuwiki sites to service."; + }; + }; + + # implementation + + config = mkIf (eachSite != {}) { + + warnings = mapAttrsToList (hostName: cfg: mkIf (cfg.superUser == null) "Not setting services.dokuwiki.${hostName} superUser will impair your ability to administer DokuWiki") eachSite; + + assertions = flatten (mapAttrsToList (hostName: cfg: + [{ + assertion = cfg.aclUse -> (cfg.acl != null || cfg.aclFile != null); + message = "Either services.dokuwiki.${hostName}.acl or services.dokuwiki.${hostName}.aclFile is mandatory if aclUse true"; + } + { + assertion = cfg.usersFile != null -> cfg.aclUse != false; + message = "services.dokuwiki.${hostName}.aclUse must must be true if usersFile is not null"; + } + ]) eachSite); + + services.phpfpm.pools = mapAttrs' (hostName: cfg: ( + nameValuePair "dokuwiki-${hostName}" { + inherit user; + inherit group; + phpEnv = { + DOKUWIKI_LOCAL_CONFIG = "${dokuwikiLocalConfig cfg}"; + DOKUWIKI_PLUGINS_LOCAL_CONFIG = "${dokuwikiPluginsLocalConfig cfg}"; + } // optionalAttrs (cfg.usersFile != null) { + DOKUWIKI_USERS_AUTH_CONFIG = "${cfg.usersFile}"; + } //optionalAttrs (cfg.aclUse) { + DOKUWIKI_ACL_AUTH_CONFIG = if (cfg.acl != null) then "${dokuwikiAclAuthConfig cfg}" else "${toString cfg.aclFile}"; + }; + + settings = { + "listen.mode" = "0660"; + "listen.owner" = user; + "listen.group" = group; + } // cfg.poolConfig; + })) eachSite; + + services.nginx = { + enable = true; + virtualHosts = mapAttrs (hostName: cfg: mkMerge [ cfg.nginx { + root = mkForce "${pkg hostName cfg}/share/dokuwiki"; + extraConfig = "fastcgi_param HTTPS on;"; + + locations."~ /(conf/|bin/|inc/|install.php)" = { + extraConfig = "deny all;"; + }; + + locations."~ ^/data/" = { + root = "${cfg.stateDir}"; + extraConfig = "internal;"; + }; + + locations."~ ^/lib.*\.(js|css|gif|png|ico|jpg|jpeg)$" = { + extraConfig = "expires 365d;"; + }; + + locations."/" = { + priority = 1; + index = "doku.php"; + extraConfig = ''try_files $uri $uri/ @dokuwiki;''; + }; + + locations."@dokuwiki" = { + extraConfig = '' + # rewrites "doku.php/" out of the URLs if you set the userwrite setting to .htaccess in dokuwiki config page + rewrite ^/_media/(.*) /lib/exe/fetch.php?media=$1 last; + rewrite ^/_detail/(.*) /lib/exe/detail.php?media=$1 last; + rewrite ^/_export/([^/]+)/(.*) /doku.php?do=export_$1&id=$2 last; + rewrite ^/(.*) /doku.php?id=$1&$args last; + ''; + }; + + locations."~ \.php$" = { + extraConfig = '' + try_files $uri $uri/ /doku.php; + include ${pkgs.nginx}/conf/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param REDIRECT_STATUS 200; + fastcgi_pass unix:${config.services.phpfpm.pools."dokuwiki-${hostName}".socket}; + fastcgi_param HTTPS on; + ''; + }; + }]) eachSite; + }; + + systemd.tmpfiles.rules = flatten (mapAttrsToList (hostName: cfg: [ + "d ${cfg.stateDir}/attic 0750 ${user} ${group} - -" + "d ${cfg.stateDir}/cache 0750 ${user} ${group} - -" + "d ${cfg.stateDir}/index 0750 ${user} ${group} - -" + "d ${cfg.stateDir}/locks 0750 ${user} ${group} - -" + "d ${cfg.stateDir}/media 0750 ${user} ${group} - -" + "d ${cfg.stateDir}/media_attic 0750 ${user} ${group} - -" + "d ${cfg.stateDir}/media_meta 0750 ${user} ${group} - -" + "d ${cfg.stateDir}/meta 0750 ${user} ${group} - -" + "d ${cfg.stateDir}/pages 0750 ${user} ${group} - -" + "d ${cfg.stateDir}/tmp 0750 ${user} ${group} - -" + ] ++ lib.optional (cfg.aclFile != null) "C ${cfg.aclFile} 0640 ${user} ${group} - ${pkg hostName cfg}/share/dokuwiki/conf/acl.auth.php.dist" + ++ lib.optional (cfg.usersFile != null) "C ${cfg.usersFile} 0640 ${user} ${group} - ${pkg hostName cfg}/share/dokuwiki/conf/users.auth.php.dist" + ) eachSite); + + users.users.${user} = { + group = group; + isSystemUser = true; + }; + }; +} diff --git a/nixos/modules/services/web-apps/frab.nix b/nixos/modules/services/web-apps/frab.nix index a9a30b409220f..1b5890d6b0c73 100644 --- a/nixos/modules/services/web-apps/frab.nix +++ b/nixos/modules/services/web-apps/frab.nix @@ -173,15 +173,13 @@ in config = mkIf cfg.enable { environment.systemPackages = [ frab-rake ]; - users.users = [ - { name = cfg.user; - group = cfg.group; + users.users.${cfg.user} = + { group = cfg.group; home = "${cfg.statePath}"; isSystemUser = true; - } - ]; + }; - users.groups = [ { name = cfg.group; } ]; + users.groups.${cfg.group} = { }; systemd.tmpfiles.rules = [ "d '${cfg.statePath}/system/attachments' - ${cfg.user} ${cfg.group} - -" diff --git a/nixos/modules/services/web-apps/gerrit.nix b/nixos/modules/services/web-apps/gerrit.nix new file mode 100644 index 0000000000000..b184c0754d45b --- /dev/null +++ b/nixos/modules/services/web-apps/gerrit.nix @@ -0,0 +1,218 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.services.gerrit; + + # NixOS option type for git-like configs + gitIniType = with types; + let + primitiveType = either str (either bool int); + multipleType = either primitiveType (listOf primitiveType); + sectionType = lazyAttrsOf multipleType; + supersectionType = lazyAttrsOf (either multipleType sectionType); + in lazyAttrsOf supersectionType; + + gerritConfig = pkgs.writeText "gerrit.conf" ( + lib.generators.toGitINI cfg.settings + ); + + # Wrap the gerrit java with all the java options so it can be called + # like a normal CLI app + gerrit-cli = pkgs.writeShellScriptBin "gerrit" '' + set -euo pipefail + jvmOpts=( + ${lib.escapeShellArgs cfg.jvmOpts} + -Xmx${cfg.jvmHeapLimit} + ) + exec ${cfg.jvmPackage}/bin/java \ + "''${jvmOpts[@]}" \ + -jar ${cfg.package}/webapps/${cfg.package.name}.war \ + "$@" + ''; + + gerrit-plugins = pkgs.runCommand + "gerrit-plugins" + { + buildInputs = [ gerrit-cli ]; + } + '' + shopt -s nullglob + mkdir $out + + for name in ${toString cfg.builtinPlugins}; do + echo "Installing builtin plugin $name.jar" + gerrit cat plugins/$name.jar > $out/$name.jar + done + + for file in ${toString cfg.plugins}; do + name=$(echo "$file" | cut -d - -f 2-) + echo "Installing plugin $name" + ln -sf "$file" $out/$name + done + ''; +in +{ + options = { + services.gerrit = { + enable = mkEnableOption "Gerrit service"; + + package = mkOption { + type = types.package; + default = pkgs.gerrit; + description = "Gerrit package to use"; + }; + + jvmPackage = mkOption { + type = types.package; + default = pkgs.jre_headless; + defaultText = "pkgs.jre_headless"; + description = "Java Runtime Environment package to use"; + }; + + jvmOpts = mkOption { + type = types.listOf types.str; + default = [ + "-Dflogger.backend_factory=com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance" + "-Dflogger.logging_context=com.google.gerrit.server.logging.LoggingContext#getInstance" + ]; + description = "A list of JVM options to start gerrit with."; + }; + + jvmHeapLimit = mkOption { + type = types.str; + default = "1024m"; + description = '' + How much memory to allocate to the JVM heap + ''; + }; + + listenAddress = mkOption { + type = types.str; + default = "[::]:8080"; + description = '' + <literal>hostname:port</literal> to listen for HTTP traffic. + + This is bound using the systemd socket activation. + ''; + }; + + settings = mkOption { + type = gitIniType; + default = {}; + description = '' + Gerrit configuration. This will be generated to the + <literal>etc/gerrit.config</literal> file. + ''; + }; + + plugins = mkOption { + type = types.listOf types.package; + default = []; + description = '' + List of plugins to add to Gerrit. Each derivation is a jar file + itself where the name of the derivation is the name of plugin. + ''; + }; + + builtinPlugins = mkOption { + type = types.listOf (types.enum cfg.package.passthru.plugins); + default = []; + description = '' + List of builtins plugins to install. Those are shipped in the + <literal>gerrit.war</literal> file. + ''; + }; + + serverId = mkOption { + type = types.str; + description = '' + Set a UUID that uniquely identifies the server. + + This can be generated with + <literal>nix-shell -p utillinux --run uuidgen</literal>. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + + services.gerrit.settings = { + cache.directory = "/var/cache/gerrit"; + container.heapLimit = cfg.jvmHeapLimit; + gerrit.basePath = lib.mkDefault "git"; + gerrit.serverId = cfg.serverId; + httpd.inheritChannel = "true"; + httpd.listenUrl = lib.mkDefault "http://${cfg.listenAddress}"; + index.type = lib.mkDefault "lucene"; + }; + + # Add the gerrit CLI to the system to run `gerrit init` and friends. + environment.systemPackages = [ gerrit-cli ]; + + systemd.sockets.gerrit = { + unitConfig.Description = "Gerrit HTTP socket"; + wantedBy = [ "sockets.target" ]; + listenStreams = [ cfg.listenAddress ]; + }; + + systemd.services.gerrit = { + description = "Gerrit"; + + wantedBy = [ "multi-user.target" ]; + requires = [ "gerrit.socket" ]; + after = [ "gerrit.socket" "network.target" ]; + + path = [ + gerrit-cli + pkgs.bash + pkgs.coreutils + pkgs.git + pkgs.openssh + ]; + + environment = { + GERRIT_HOME = "%S/gerrit"; + GERRIT_TMP = "%T"; + HOME = "%S/gerrit"; + XDG_CONFIG_HOME = "%S/gerrit/.config"; + }; + + preStart = '' + set -euo pipefail + + # bootstrap if nothing exists + if [[ ! -d git ]]; then + gerrit init --batch --no-auto-start + fi + + # install gerrit.war for the plugin manager + rm -rf bin + mkdir bin + ln -sfv ${cfg.package}/webapps/${cfg.package.name}.war bin/gerrit.war + + # copy the config, keep it mutable because Gerrit + ln -sfv ${gerritConfig} etc/gerrit.config + + # install the plugins + rm -rf plugins + ln -sv ${gerrit-plugins} plugins + '' + ; + + serviceConfig = { + CacheDirectory = "gerrit"; + DynamicUser = true; + ExecStart = "${gerrit-cli}/bin/gerrit daemon --console-log"; + LimitNOFILE = 4096; + StandardInput = "socket"; + StandardOutput = "journal"; + StateDirectory = "gerrit"; + WorkingDirectory = "%S/gerrit"; + }; + }; + }; + + meta.maintainers = with lib.maintainers; [ edef zimbatm ]; +} diff --git a/nixos/modules/services/web-apps/grocy.nix b/nixos/modules/services/web-apps/grocy.nix new file mode 100644 index 0000000000000..568bdfd0c4297 --- /dev/null +++ b/nixos/modules/services/web-apps/grocy.nix @@ -0,0 +1,172 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.grocy; +in { + options.services.grocy = { + enable = mkEnableOption "grocy"; + + hostName = mkOption { + type = types.str; + description = '' + FQDN for the grocy instance. + ''; + }; + + nginx.enableSSL = mkOption { + type = types.bool; + default = true; + description = '' + Whether or not to enable SSL (with ACME and let's encrypt) + for the grocy vhost. + ''; + }; + + phpfpm.settings = mkOption { + type = with types; attrsOf (oneOf [ int str bool ]); + default = { + "pm" = "dynamic"; + "php_admin_value[error_log]" = "stderr"; + "php_admin_flag[log_errors]" = true; + "listen.owner" = "nginx"; + "catch_workers_output" = true; + "pm.max_children" = "32"; + "pm.start_servers" = "2"; + "pm.min_spare_servers" = "2"; + "pm.max_spare_servers" = "4"; + "pm.max_requests" = "500"; + }; + + description = '' + Options for grocy's PHPFPM pool. + ''; + }; + + dataDir = mkOption { + type = types.str; + default = "/var/lib/grocy"; + description = '' + Home directory of the <literal>grocy</literal> user which contains + the application's state. + ''; + }; + + settings = { + currency = mkOption { + type = types.str; + default = "USD"; + example = "EUR"; + description = '' + ISO 4217 code for the currency to display. + ''; + }; + + culture = mkOption { + type = types.enum [ "de" "en" "da" "en_GB" "es" "fr" "hu" "it" "nl" "no" "pl" "pt_BR" "ru" "sk_SK" "sv_SE" "tr" ]; + default = "en"; + description = '' + Display language of the frontend. + ''; + }; + + calendar = { + showWeekNumber = mkOption { + default = true; + type = types.bool; + description = '' + Show the number of the weeks in the calendar views. + ''; + }; + firstDayOfWeek = mkOption { + default = null; + type = types.nullOr (types.enum (range 0 6)); + description = '' + Which day of the week (0=Sunday, 1=Monday etc.) should be the + first day. + ''; + }; + }; + }; + }; + + config = mkIf cfg.enable { + environment.etc."grocy/config.php".text = '' + <?php + Setting('CULTURE', '${cfg.settings.culture}'); + Setting('CURRENCY', '${cfg.settings.currency}'); + Setting('CALENDAR_FIRST_DAY_OF_WEEK', '${toString cfg.settings.calendar.firstDayOfWeek}'); + Setting('CALENDAR_SHOW_WEEK_OF_YEAR', ${boolToString cfg.settings.calendar.showWeekNumber}); + ''; + + users.users.grocy = { + isSystemUser = true; + createHome = true; + home = cfg.dataDir; + group = "nginx"; + }; + + systemd.tmpfiles.rules = map ( + dirName: "d '${cfg.dataDir}/${dirName}' - grocy nginx - -" + ) [ "viewcache" "plugins" "settingoverrides" "storage" ]; + + services.phpfpm.pools.grocy = { + user = "grocy"; + group = "nginx"; + + # PHP 7.3 is the only version which is supported/tested by upstream: + # https://github.com/grocy/grocy/blob/v2.6.0/README.md#how-to-install + phpPackage = pkgs.php73; + + inherit (cfg.phpfpm) settings; + + phpEnv = { + GROCY_CONFIG_FILE = "/etc/grocy/config.php"; + GROCY_DB_FILE = "${cfg.dataDir}/grocy.db"; + GROCY_STORAGE_DIR = "${cfg.dataDir}/storage"; + GROCY_PLUGIN_DIR = "${cfg.dataDir}/plugins"; + GROCY_CACHE_DIR = "${cfg.dataDir}/viewcache"; + }; + }; + + services.nginx = { + enable = true; + virtualHosts."${cfg.hostName}" = mkMerge [ + { root = "${pkgs.grocy}/public"; + locations."/".extraConfig = '' + rewrite ^ /index.php; + ''; + locations."~ \\.php$".extraConfig = '' + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass unix:${config.services.phpfpm.pools.grocy.socket}; + include ${config.services.nginx.package}/conf/fastcgi.conf; + include ${config.services.nginx.package}/conf/fastcgi_params; + ''; + locations."~ \\.(js|css|ttf|woff2?|png|jpe?g|svg)$".extraConfig = '' + add_header Cache-Control "public, max-age=15778463"; + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Robots-Tag none; + add_header X-Download-Options noopen; + add_header X-Permitted-Cross-Domain-Policies none; + add_header Referrer-Policy no-referrer; + access_log off; + ''; + extraConfig = '' + try_files $uri /index.php; + ''; + } + (mkIf cfg.nginx.enableSSL { + enableACME = true; + forceSSL = true; + }) + ]; + }; + }; + + meta = { + maintainers = with maintainers; [ ma27 ]; + doc = ./grocy.xml; + }; +} diff --git a/nixos/modules/services/web-apps/grocy.xml b/nixos/modules/services/web-apps/grocy.xml new file mode 100644 index 0000000000000..fdf6d00f4b123 --- /dev/null +++ b/nixos/modules/services/web-apps/grocy.xml @@ -0,0 +1,77 @@ +<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-grocy"> + + <title>Grocy</title> + <para> + <link xlink:href="https://grocy.info/">Grocy</link> is a web-based self-hosted groceries + & household management solution for your home. + </para> + + <section xml:id="module-services-grocy-basic-usage"> + <title>Basic usage</title> + <para> + A very basic configuration may look like this: +<programlisting>{ pkgs, ... }: +{ + services.grocy = { + <link linkend="opt-services.grocy.enable">enable</link> = true; + <link linkend="opt-services.grocy.hostName">hostName</link> = "grocy.tld"; + }; +}</programlisting> + This configures a simple vhost using <link linkend="opt-services.nginx.enable">nginx</link> + which listens to <literal>grocy.tld</literal> with fully configured ACME/LE (this can be + disabled by setting <link linkend="opt-services.grocy.nginx.enableSSL">services.grocy.nginx.enableSSL</link> + to <literal>false</literal>). After the initial setup the credentials <literal>admin:admin</literal> + can be used to login. + </para> + <para> + The application's state is persisted at <literal>/var/lib/grocy/grocy.db</literal> in a + <package>sqlite3</package> database. The migration is applied when requesting the <literal>/</literal>-route + of the application. + </para> + </section> + + <section xml:id="module-services-grocy-settings"> + <title>Settings</title> + <para> + The configuration for <literal>grocy</literal> is located at <literal>/etc/grocy/config.php</literal>. + By default, the following settings can be defined in the NixOS-configuration: +<programlisting>{ pkgs, ... }: +{ + services.grocy.settings = { + # The default currency in the system for invoices etc. + # Please note that exchange rates aren't taken into account, this + # is just the setting for what's shown in the frontend. + <link linkend="opt-services.grocy.settings.currency">currency</link> = "EUR"; + + # The display language (and locale configuration) for grocy. + <link linkend="opt-services.grocy.settings.currency">culture</link> = "de"; + + calendar = { + # Whether or not to show the week-numbers + # in the calendar. + <link linkend="opt-services.grocy.settings.calendar.showWeekNumber">showWeekNumber</link> = true; + + # Index of the first day to be shown in the calendar (0=Sunday, 1=Monday, + # 2=Tuesday and so on). + <link linkend="opt-services.grocy.settings.calendar.firstDayOfWeek">firstDayOfWeek</link> = 2; + }; + }; +}</programlisting> + </para> + <para> + If you want to alter the configuration file on your own, you can do this manually with + an expression like this: +<programlisting>{ lib, ... }: +{ + environment.etc."grocy/config.php".text = lib.mkAfter '' + // Arbitrary PHP code in grocy's configuration file + ''; +}</programlisting> + </para> + </section> + +</chapter> diff --git a/nixos/modules/services/web-apps/ihatemoney/default.nix b/nixos/modules/services/web-apps/ihatemoney/default.nix new file mode 100644 index 0000000000000..68769ac8c0316 --- /dev/null +++ b/nixos/modules/services/web-apps/ihatemoney/default.nix @@ -0,0 +1,141 @@ +{ config, pkgs, lib, ... }: +with lib; +let + cfg = config.services.ihatemoney; + user = "ihatemoney"; + group = "ihatemoney"; + db = "ihatemoney"; + python3 = config.services.uwsgi.package.python3; + pkg = python3.pkgs.ihatemoney; + toBool = x: if x then "True" else "False"; + configFile = pkgs.writeText "ihatemoney.cfg" '' + from secrets import token_hex + # load a persistent secret key + SECRET_KEY_FILE = "/var/lib/ihatemoney/secret_key" + SECRET_KEY = "" + try: + with open(SECRET_KEY_FILE) as f: + SECRET_KEY = f.read() + except FileNotFoundError: + pass + if not SECRET_KEY: + print("ihatemoney: generating a new secret key") + SECRET_KEY = token_hex(50) + with open(SECRET_KEY_FILE, "w") as f: + f.write(SECRET_KEY) + del token_hex + del SECRET_KEY_FILE + + # "normal" configuration + DEBUG = False + SQLALCHEMY_DATABASE_URI = '${ + if cfg.backend == "sqlite" + then "sqlite:////var/lib/ihatemoney/ihatemoney.sqlite" + else "postgresql:///${db}"}' + SQLALCHEMY_TRACK_MODIFICATIONS = False + MAIL_DEFAULT_SENDER = ("${cfg.defaultSender.name}", "${cfg.defaultSender.email}") + ACTIVATE_DEMO_PROJECT = ${toBool cfg.enableDemoProject} + ADMIN_PASSWORD = "${toString cfg.adminHashedPassword /*toString null == ""*/}" + ALLOW_PUBLIC_PROJECT_CREATION = ${toBool cfg.enablePublicProjectCreation} + ACTIVATE_ADMIN_DASHBOARD = ${toBool cfg.enableAdminDashboard} + + ${cfg.extraConfig} + ''; +in + { + options.services.ihatemoney = { + enable = mkEnableOption "ihatemoney webapp. Note that this will set uwsgi to emperor mode running as root"; + backend = mkOption { + type = types.enum [ "sqlite" "postgresql" ]; + default = "sqlite"; + description = '' + The database engine to use for ihatemoney. + If <literal>postgresql</literal> is selected, then a database called + <literal>${db}</literal> will be created. If you disable this option, + it will however not be removed. + ''; + }; + adminHashedPassword = mkOption { + type = types.nullOr types.str; + default = null; + description = "The hashed password of the administrator. To obtain it, run <literal>ihatemoney generate_password_hash</literal>"; + }; + uwsgiConfig = mkOption { + type = types.attrs; + example = { + http = ":8000"; + }; + description = "Additionnal configuration of the UWSGI vassal running ihatemoney. It should notably specify on which interfaces and ports the vassal should listen."; + }; + defaultSender = { + name = mkOption { + type = types.str; + default = "Budget manager"; + description = "The display name of the sender of ihatemoney emails"; + }; + email = mkOption { + type = types.str; + default = "ihatemoney@${config.networking.hostName}"; + description = "The email of the sender of ihatemoney emails"; + }; + }; + enableDemoProject = mkEnableOption "access to the demo project in ihatemoney"; + enablePublicProjectCreation = mkEnableOption "permission to create projects in ihatemoney by anyone"; + enableAdminDashboard = mkEnableOption "ihatemoney admin dashboard"; + extraConfig = mkOption { + type = types.str; + default = ""; + description = "Extra configuration appended to ihatemoney's configuration file. It is a python file, so pay attention to indentation."; + }; + }; + config = mkIf cfg.enable { + services.postgresql = mkIf (cfg.backend == "postgresql") { + enable = true; + ensureDatabases = [ db ]; + ensureUsers = [ { + name = user; + ensurePermissions = { + "DATABASE ${db}" = "ALL PRIVILEGES"; + }; + } ]; + }; + systemd.services.postgresql = mkIf (cfg.backend == "postgresql") { + wantedBy = [ "uwsgi.service" ]; + before = [ "uwsgi.service" ]; + }; + systemd.tmpfiles.rules = [ + "d /var/lib/ihatemoney 770 ${user} ${group}" + ]; + users = { + users.${user} = { + isSystemUser = true; + inherit group; + }; + groups.${group} = {}; + }; + services.uwsgi = { + enable = true; + plugins = [ "python3" ]; + # the vassal needs to be able to setuid + user = "root"; + group = "root"; + instance = { + type = "emperor"; + vassals.ihatemoney = { + type = "normal"; + strict = true; + uid = user; + gid = group; + # apparently flask uses threads: https://github.com/spiral-project/ihatemoney/commit/c7815e48781b6d3a457eaff1808d179402558f8c + enable-threads = true; + module = "wsgi:application"; + chdir = "${pkg}/${pkg.pythonModule.sitePackages}/ihatemoney"; + env = [ "IHATEMONEY_SETTINGS_FILE_PATH=${configFile}" ]; + pythonPackages = self: [ self.ihatemoney ]; + } // cfg.uwsgiConfig; + }; + }; + }; + } + + diff --git a/nixos/modules/services/web-apps/jirafeau.nix b/nixos/modules/services/web-apps/jirafeau.nix new file mode 100644 index 0000000000000..4f181257ef7cd --- /dev/null +++ b/nixos/modules/services/web-apps/jirafeau.nix @@ -0,0 +1,169 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.services.jirafeau; + + group = config.services.nginx.group; + user = config.services.nginx.user; + + withTrailingSlash = str: if hasSuffix "/" str then str else "${str}/"; + + localConfig = pkgs.writeText "config.local.php" '' + <?php + $cfg['admin_password'] = '${cfg.adminPasswordSha256}'; + $cfg['web_root'] = 'http://${withTrailingSlash cfg.hostName}'; + $cfg['var_root'] = '${withTrailingSlash cfg.dataDir}'; + $cfg['maximal_upload_size'] = ${builtins.toString cfg.maxUploadSizeMegabytes}; + $cfg['installation_done'] = true; + + ${cfg.extraConfig} + ''; +in +{ + options.services.jirafeau = { + adminPasswordSha256 = mkOption { + type = types.str; + default = ""; + description = '' + SHA-256 of the desired administration password. Leave blank/unset for no password. + ''; + }; + + dataDir = mkOption { + type = types.path; + default = "/var/lib/jirafeau/data/"; + description = "Location of Jirafeau storage directory."; + }; + + enable = mkEnableOption "Jirafeau file upload application."; + + extraConfig = mkOption { + type = types.lines; + default = ""; + example = '' + $cfg['style'] = 'courgette'; + $cfg['organisation'] = 'ACME'; + ''; + description = let + documentationLink = + "https://gitlab.com/mojo42/Jirafeau/-/blob/${cfg.package.version}/lib/config.original.php"; + in + '' + Jirefeau configuration. Refer to <link xlink:href="${documentationLink}"/> for supported + values. + ''; + }; + + hostName = mkOption { + type = types.str; + default = "localhost"; + description = "URL of instance. Must have trailing slash."; + }; + + maxUploadSizeMegabytes = mkOption { + type = types.int; + default = 0; + description = "Maximum upload size of accepted files."; + }; + + maxUploadTimeout = mkOption { + type = types.str; + default = "30m"; + description = let + nginxCoreDocumentation = "http://nginx.org/en/docs/http/ngx_http_core_module.html"; + in + '' + Timeout for reading client request bodies and headers. Refer to + <link xlink:href="${nginxCoreDocumentation}#client_body_timeout"/> and + <link xlink:href="${nginxCoreDocumentation}#client_header_timeout"/> for accepted values. + ''; + }; + + nginxConfig = mkOption { + type = types.submodule + (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }); + default = {}; + example = { + serverAliases = [ "wiki.\${config.networking.domain}" ]; + }; + description = "Extra configuration for the nginx virtual host of Jirafeau."; + }; + + package = mkOption { + type = types.package; + default = pkgs.jirafeau; + defaultText = "pkgs.jirafeau"; + description = "Jirafeau package to use"; + example = "pkgs.jirafeau"; + }; + + poolConfig = mkOption { + type = with types; attrsOf (oneOf [ str int bool ]); + default = { + "pm" = "dynamic"; + "pm.max_children" = 32; + "pm.start_servers" = 2; + "pm.min_spare_servers" = 2; + "pm.max_spare_servers" = 4; + "pm.max_requests" = 500; + }; + description = '' + Options for Jirafeau PHP pool. See documentation on <literal>php-fpm.conf</literal> for + details on configuration directives. + ''; + }; + }; + + + config = mkIf cfg.enable { + services = { + nginx = { + enable = true; + virtualHosts."${cfg.hostName}" = mkMerge [ + cfg.nginxConfig + { + extraConfig = let + clientMaxBodySize = + if cfg.maxUploadSizeMegabytes == 0 then "0" else "${cfg.maxUploadSizeMegabytes}m"; + in + '' + index index.php; + client_max_body_size ${clientMaxBodySize}; + client_body_timeout ${cfg.maxUploadTimeout}; + client_header_timeout ${cfg.maxUploadTimeout}; + ''; + locations = { + "~ \\.php$".extraConfig = '' + include ${pkgs.nginx}/conf/fastcgi_params; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_index index.php; + fastcgi_pass unix:${config.services.phpfpm.pools.jirafeau.socket}; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + ''; + }; + root = mkForce "${cfg.package}"; + } + ]; + }; + + phpfpm.pools.jirafeau = { + inherit group user; + phpEnv."JIRAFEAU_CONFIG" = "${localConfig}"; + settings = { + "listen.mode" = "0660"; + "listen.owner" = user; + "listen.group" = group; + } // cfg.poolConfig; + }; + }; + + systemd.tmpfiles.rules = [ + "d ${cfg.dataDir} 0750 ${user} ${group} - -" + "d ${cfg.dataDir}/files/ 0750 ${user} ${group} - -" + "d ${cfg.dataDir}/links/ 0750 ${user} ${group} - -" + "d ${cfg.dataDir}/async/ 0750 ${user} ${group} - -" + ]; + }; +} diff --git a/nixos/modules/services/web-apps/limesurvey.nix b/nixos/modules/services/web-apps/limesurvey.nix index bd524524130d6..56265e80957ed 100644 --- a/nixos/modules/services/web-apps/limesurvey.nix +++ b/nixos/modules/services/web-apps/limesurvey.nix @@ -3,7 +3,7 @@ let inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption; - inherit (lib) mapAttrs optional optionalString types; + inherit (lib) literalExample mapAttrs optional optionalString types; cfg = config.services.limesurvey; fpm = config.services.phpfpm.pools.limesurvey; @@ -100,19 +100,15 @@ in }; virtualHost = mkOption { - type = types.submodule ({ - options = import ../web-servers/apache-httpd/per-server-options.nix { - inherit lib; - forMainServer = false; - }; - }); - example = { - hostName = "survey.example.org"; - enableSSL = true; - adminAddr = "webmaster@example.org"; - sslServerCert = "/var/lib/acme/survey.example.org/full.pem"; - sslServerKey = "/var/lib/acme/survey.example.org/key.pem"; - }; + type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix); + example = literalExample '' + { + hostName = "survey.example.org"; + adminAddr = "webmaster@example.org"; + forceSSL = true; + enableACME = true; + } + ''; description = '' Apache configuration can be done by adapting <literal>services.httpd.virtualHosts.<name></literal>. See <xref linkend="opt-services.httpd.virtualHosts"/> for further information. @@ -184,7 +180,7 @@ in config = { tempdir = "${stateDir}/tmp"; uploaddir = "${stateDir}/upload"; - force_ssl = mkIf cfg.virtualHost.enableSSL "on"; + force_ssl = mkIf (cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL) "on"; config.defaultlang = "en"; }; }; @@ -215,38 +211,36 @@ in enable = true; adminAddr = mkDefault cfg.virtualHost.adminAddr; extraModules = [ "proxy_fcgi" ]; - virtualHosts = [ (mkMerge [ - cfg.virtualHost { - documentRoot = mkForce "${pkg}/share/limesurvey"; - extraConfig = '' - Alias "/tmp" "${stateDir}/tmp" - <Directory "${stateDir}"> - AllowOverride all - Require all granted - Options -Indexes +FollowSymlinks - </Directory> - - Alias "/upload" "${stateDir}/upload" - <Directory "${stateDir}/upload"> - AllowOverride all - Require all granted - Options -Indexes - </Directory> - - <Directory "${pkg}/share/limesurvey"> - <FilesMatch "\.php$"> - <If "-f %{REQUEST_FILENAME}"> - SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/" - </If> - </FilesMatch> - - AllowOverride all - Options -Indexes - DirectoryIndex index.php - </Directory> - ''; - } - ]) ]; + virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost { + documentRoot = mkForce "${pkg}/share/limesurvey"; + extraConfig = '' + Alias "/tmp" "${stateDir}/tmp" + <Directory "${stateDir}"> + AllowOverride all + Require all granted + Options -Indexes +FollowSymlinks + </Directory> + + Alias "/upload" "${stateDir}/upload" + <Directory "${stateDir}/upload"> + AllowOverride all + Require all granted + Options -Indexes + </Directory> + + <Directory "${pkg}/share/limesurvey"> + <FilesMatch "\.php$"> + <If "-f %{REQUEST_FILENAME}"> + SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/" + </If> + </FilesMatch> + + AllowOverride all + Options -Indexes + DirectoryIndex index.php + </Directory> + ''; + } ]; }; systemd.tmpfiles.rules = [ diff --git a/nixos/modules/services/web-apps/matomo-doc.xml b/nixos/modules/services/web-apps/matomo-doc.xml index 79cece551d34c..69d1170e4523b 100644 --- a/nixos/modules/services/web-apps/matomo-doc.xml +++ b/nixos/modules/services/web-apps/matomo-doc.xml @@ -86,12 +86,6 @@ GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost'; <itemizedlist> <listitem> <para> - Matomo's file integrity check will warn you. This is due to the patches - necessary for NixOS, you can safely ignore this. - </para> - </listitem> - <listitem> - <para> Matomo will warn you that the JavaScript tracker is not writable. This is because it's located in the read-only nix store. You can safely ignore this, unless you need a plugin that needs JavaScript tracker access. diff --git a/nixos/modules/services/web-apps/matomo.nix b/nixos/modules/services/web-apps/matomo.nix index 352cc4c647bce..75da474dc4464 100644 --- a/nixos/modules/services/web-apps/matomo.nix +++ b/nixos/modules/services/web-apps/matomo.nix @@ -18,6 +18,14 @@ let in join config.networking.hostName config.networking.domain; in { + imports = [ + (mkRenamedOptionModule [ "services" "piwik" "enable" ] [ "services" "matomo" "enable" ]) + (mkRenamedOptionModule [ "services" "piwik" "webServerUser" ] [ "services" "matomo" "webServerUser" ]) + (mkRemovedOptionModule [ "services" "piwik" "phpfpmProcessManagerConfig" ] "Use services.phpfpm.pools.<name>.settings") + (mkRemovedOptionModule [ "services" "matomo" "phpfpmProcessManagerConfig" ] "Use services.phpfpm.pools.<name>.settings") + (mkRenamedOptionModule [ "services" "piwik" "nginx" ] [ "services" "matomo" "nginx" ]) + ]; + options = { services.matomo = { # NixOS PR for database setup: https://github.com/NixOS/nixpkgs/pull/6963 diff --git a/nixos/modules/services/web-apps/mattermost.nix b/nixos/modules/services/web-apps/mattermost.nix index 8c7fc4056adc9..853347bf86e2c 100644 --- a/nixos/modules/services/web-apps/mattermost.nix +++ b/nixos/modules/services/web-apps/mattermost.nix @@ -6,14 +6,18 @@ let cfg = config.services.mattermost; - defaultConfig = builtins.fromJSON (readFile "${pkgs.mattermost}/config/config.json"); + defaultConfig = builtins.fromJSON (builtins.replaceStrings [ "\\u0026" ] [ "&" ] + (readFile "${pkgs.mattermost}/config/config.json") + ); + + database = "postgres://${cfg.localDatabaseUser}:${cfg.localDatabasePassword}@localhost:5432/${cfg.localDatabaseName}?sslmode=disable&connect_timeout=10"; mattermostConf = foldl recursiveUpdate defaultConfig [ { ServiceSettings.SiteURL = cfg.siteUrl; ServiceSettings.ListenAddress = cfg.listenAddress; TeamSettings.SiteName = cfg.siteName; SqlSettings.DriverName = "postgres"; - SqlSettings.DataSource = "postgres://${cfg.localDatabaseUser}:${cfg.localDatabasePassword}@localhost:5432/${cfg.localDatabaseName}?sslmode=disable&connect_timeout=10"; + SqlSettings.DataSource = database; } cfg.extraConfig ]; @@ -146,17 +150,17 @@ in config = mkMerge [ (mkIf cfg.enable { - users.users = optionalAttrs (cfg.user == "mattermost") (singleton { - name = "mattermost"; - group = cfg.group; - uid = config.ids.uids.mattermost; - home = cfg.statePath; - }); - - users.groups = optionalAttrs (cfg.group == "mattermost") (singleton { - name = "mattermost"; - gid = config.ids.gids.mattermost; - }); + users.users = optionalAttrs (cfg.user == "mattermost") { + mattermost = { + group = cfg.group; + uid = config.ids.uids.mattermost; + home = cfg.statePath; + }; + }; + + users.groups = optionalAttrs (cfg.group == "mattermost") { + mattermost.gid = config.ids.gids.mattermost; + }; services.postgresql.enable = cfg.localDatabaseCreate; @@ -175,7 +179,9 @@ in mkdir -p ${cfg.statePath}/{data,config,logs} ln -sf ${pkgs.mattermost}/{bin,fonts,i18n,templates,client} ${cfg.statePath} '' + lib.optionalString (!cfg.mutableConfig) '' - ln -sf ${mattermostConfJSON} ${cfg.statePath}/config/config.json + rm -f ${cfg.statePath}/config/config.json + cp ${mattermostConfJSON} ${cfg.statePath}/config/config.json + ${pkgs.mattermost}/bin/mattermost config migrate ${cfg.statePath}/config/config.json ${database} '' + lib.optionalString cfg.mutableConfig '' if ! test -e "${cfg.statePath}/config/.initial-created"; then rm -f ${cfg.statePath}/config/config.json @@ -201,7 +207,8 @@ in PermissionsStartOnly = true; User = cfg.user; Group = cfg.group; - ExecStart = "${pkgs.mattermost}/bin/mattermost"; + ExecStart = "${pkgs.mattermost}/bin/mattermost" + + (lib.optionalString (!cfg.mutableConfig) " -c ${database}"); WorkingDirectory = "${cfg.statePath}"; Restart = "always"; RestartSec = "10"; @@ -227,4 +234,3 @@ in }) ]; } - diff --git a/nixos/modules/services/web-apps/mediawiki.nix b/nixos/modules/services/web-apps/mediawiki.nix index 43edc04e1a492..e9ed53857d811 100644 --- a/nixos/modules/services/web-apps/mediawiki.nix +++ b/nixos/modules/services/web-apps/mediawiki.nix @@ -64,7 +64,7 @@ let $wgScriptPath = ""; ## The protocol and server name to use in fully-qualified URLs - $wgServer = "${if cfg.virtualHost.enableSSL then "https" else "http"}://${cfg.virtualHost.hostName}"; + $wgServer = "${if cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL then "https" else "http"}://${cfg.virtualHost.hostName}"; ## The URL path to static resources (images, scripts, etc.) $wgResourceBasePath = $wgScriptPath; @@ -290,19 +290,13 @@ in }; virtualHost = mkOption { - type = types.submodule ({ - options = import ../web-servers/apache-httpd/per-server-options.nix { - inherit lib; - forMainServer = false; - }; - }); + type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix); example = literalExample '' { hostName = "mediawiki.example.org"; - enableSSL = true; adminAddr = "webmaster@example.org"; - sslServerCert = "/var/lib/acme/mediawiki.example.org/full.pem"; - sslServerKey = "/var/lib/acme/mediawiki.example.org/key.pem"; + forceSSL = true; + enableACME = true; } ''; description = '' @@ -389,31 +383,28 @@ in services.httpd = { enable = true; - adminAddr = mkDefault cfg.virtualHost.adminAddr; extraModules = [ "proxy_fcgi" ]; - virtualHosts = [ (mkMerge [ - cfg.virtualHost { - documentRoot = mkForce "${pkg}/share/mediawiki"; - extraConfig = '' - <Directory "${pkg}/share/mediawiki"> - <FilesMatch "\.php$"> - <If "-f %{REQUEST_FILENAME}"> - SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/" - </If> - </FilesMatch> - - Require all granted - DirectoryIndex index.php - AllowOverride All - </Directory> - '' + optionalString (cfg.uploadsDir != null) '' - Alias "/images" "${cfg.uploadsDir}" - <Directory "${cfg.uploadsDir}"> - Require all granted - </Directory> - ''; - } - ]) ]; + virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost { + documentRoot = mkForce "${pkg}/share/mediawiki"; + extraConfig = '' + <Directory "${pkg}/share/mediawiki"> + <FilesMatch "\.php$"> + <If "-f %{REQUEST_FILENAME}"> + SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/" + </If> + </FilesMatch> + + Require all granted + DirectoryIndex index.php + AllowOverride All + </Directory> + '' + optionalString (cfg.uploadsDir != null) '' + Alias "/images" "${cfg.uploadsDir}" + <Directory "${cfg.uploadsDir}"> + Require all granted + </Directory> + ''; + } ]; }; systemd.tmpfiles.rules = [ diff --git a/nixos/modules/services/web-apps/moinmoin.nix b/nixos/modules/services/web-apps/moinmoin.nix index 0fee64be0bb2f..dc7abce2a5cb5 100644 --- a/nixos/modules/services/web-apps/moinmoin.nix +++ b/nixos/modules/services/web-apps/moinmoin.nix @@ -299,5 +299,5 @@ in ]))); }; - meta.maintainers = with lib.maintainers; [ b42 ]; + meta.maintainers = with lib.maintainers; [ mmilata ]; } diff --git a/nixos/modules/services/web-apps/moodle.nix b/nixos/modules/services/web-apps/moodle.nix index ac59f9e0012a8..1196780cf6ef9 100644 --- a/nixos/modules/services/web-apps/moodle.nix +++ b/nixos/modules/services/web-apps/moodle.nix @@ -32,7 +32,7 @@ let 'dbcollation' => 'utf8mb4_unicode_ci', ); - $CFG->wwwroot = '${if cfg.virtualHost.enableSSL then "https" else "http"}://${cfg.virtualHost.hostName}'; + $CFG->wwwroot = '${if cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL then "https" else "http"}://${cfg.virtualHost.hostName}'; $CFG->dataroot = '${stateDir}'; $CFG->admin = 'admin'; @@ -140,19 +140,15 @@ in }; virtualHost = mkOption { - type = types.submodule ({ - options = import ../web-servers/apache-httpd/per-server-options.nix { - inherit lib; - forMainServer = false; - }; - }); - example = { - hostName = "moodle.example.org"; - enableSSL = true; - adminAddr = "webmaster@example.org"; - sslServerCert = "/var/lib/acme/moodle.example.org/full.pem"; - sslServerKey = "/var/lib/acme/moodle.example.org/key.pem"; - }; + type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix); + example = literalExample '' + { + hostName = "moodle.example.org"; + adminAddr = "webmaster@example.org"; + forceSSL = true; + enableACME = true; + } + ''; description = '' Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>. See <xref linkend="opt-services.httpd.virtualHosts"/> for further information. @@ -241,22 +237,20 @@ in enable = true; adminAddr = mkDefault cfg.virtualHost.adminAddr; extraModules = [ "proxy_fcgi" ]; - virtualHosts = [ (mkMerge [ - cfg.virtualHost { - documentRoot = mkForce "${cfg.package}/share/moodle"; - extraConfig = '' - <Directory "${cfg.package}/share/moodle"> - <FilesMatch "\.php$"> - <If "-f %{REQUEST_FILENAME}"> - SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/" - </If> - </FilesMatch> - Options -Indexes - DirectoryIndex index.php - </Directory> - ''; - } - ]) ]; + virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost { + documentRoot = mkForce "${cfg.package}/share/moodle"; + extraConfig = '' + <Directory "${cfg.package}/share/moodle"> + <FilesMatch "\.php$"> + <If "-f %{REQUEST_FILENAME}"> + SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/" + </If> + </FilesMatch> + Options -Indexes + DirectoryIndex index.php + </Directory> + ''; + } ]; }; systemd.tmpfiles.rules = [ diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix index b67f08808786d..5f6f2bc7a16db 100644 --- a/nixos/modules/services/web-apps/nextcloud.nix +++ b/nixos/modules/services/web-apps/nextcloud.nix @@ -6,38 +6,44 @@ let cfg = config.services.nextcloud; fpm = config.services.phpfpm.pools.nextcloud; - phpPackage = pkgs.php73; - phpPackages = pkgs.php73Packages; + phpPackage = + let + base = pkgs.php74; + in + base.buildEnv { + extensions = e: with e; + base.enabledExtensions ++ [ + apcu redis memcached imagick + ]; + extraConfig = phpOptionsStr; + }; toKeyValue = generators.toKeyValue { mkKeyValue = generators.mkKeyValueDefault {} " = "; }; - phpOptionsExtensions = '' - ${optionalString cfg.caching.apcu "extension=${phpPackages.apcu}/lib/php/extensions/apcu.so"} - ${optionalString cfg.caching.redis "extension=${phpPackages.redis}/lib/php/extensions/redis.so"} - ${optionalString cfg.caching.memcached "extension=${phpPackages.memcached}/lib/php/extensions/memcached.so"} - extension=${phpPackages.imagick}/lib/php/extensions/imagick.so - zend_extension = opcache.so - opcache.enable = 1 - ''; phpOptions = { upload_max_filesize = cfg.maxUploadSize; post_max_size = cfg.maxUploadSize; memory_limit = cfg.maxUploadSize; } // cfg.phpOptions; - phpOptionsStr = phpOptionsExtensions + (toKeyValue phpOptions); + phpOptionsStr = toKeyValue phpOptions; occ = pkgs.writeScriptBin "nextcloud-occ" '' - #! ${pkgs.stdenv.shell} - cd ${pkgs.nextcloud} - exec /run/wrappers/bin/sudo -u nextcloud \ - NEXTCLOUD_CONFIG_DIR="${cfg.home}/config" \ + #! ${pkgs.runtimeShell} + cd ${cfg.package} + sudo=exec + if [[ "$USER" != nextcloud ]]; then + sudo='exec /run/wrappers/bin/sudo -u nextcloud --preserve-env=NEXTCLOUD_CONFIG_DIR' + fi + export NEXTCLOUD_CONFIG_DIR="${cfg.home}/config" + $sudo \ ${phpPackage}/bin/php \ - -c ${pkgs.writeText "php.ini" phpOptionsStr}\ occ $* ''; + inherit (config.system) stateVersion; + in { options.services.nextcloud = { enable = mkEnableOption "nextcloud"; @@ -58,7 +64,12 @@ in { https = mkOption { type = types.bool; default = false; - description = "Enable if there is a TLS terminating proxy in front of nextcloud."; + description = "Use https for generated links."; + }; + package = mkOption { + type = types.package; + description = "Which package to use for the Nextcloud instance."; + relatedPackages = [ "nextcloud17" "nextcloud18" ]; }; maxUploadSize = mkOption { @@ -225,6 +236,15 @@ in { ''; }; + trustedProxies = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Trusted proxies, to provide if the nextcloud installation is being + proxied to secure against e.g. spoofing. + ''; + }; + overwriteProtocol = mkOption { type = types.nullOr (types.enum [ "http" "https" ]); default = null; @@ -296,10 +316,40 @@ in { } ]; - warnings = optional (cfg.poolConfig != null) '' - Using config.services.nextcloud.poolConfig is deprecated and will become unsupported in a future release. - Please migrate your configuration to config.services.nextcloud.poolSettings. - ''; + warnings = [] + ++ (optional (cfg.poolConfig != null) '' + Using config.services.nextcloud.poolConfig is deprecated and will become unsupported in a future release. + Please migrate your configuration to config.services.nextcloud.poolSettings. + '') + ++ (optional (versionOlder cfg.package.version "18") '' + A legacy Nextcloud install (from before NixOS 20.03) may be installed. + + You're currently deploying an older version of Nextcloud. This may be needed + since Nextcloud doesn't allow major version upgrades that skip multiple + versions (i.e. an upgrade from 16 is possible to 17, but not 16 to 18). + + It is assumed that Nextcloud will be upgraded from version 16 to 17. + + * If this is a fresh install, there will be no upgrade to do now. + + * If this server already had Nextcloud installed, first deploy this to your + server, and wait until the upgrade to 17 is finished. + + Then, set `services.nextcloud.package` to `pkgs.nextcloud18` to upgrade to + Nextcloud version 18. + ''); + + services.nextcloud.package = with pkgs; + mkDefault ( + if pkgs ? nextcloud + then throw '' + The `pkgs.nextcloud`-attribute has been removed. If it's supposed to be the default + nextcloud defined in an overlay, please set `services.nextcloud.package` to + `pkgs.nextcloud`. + '' + else if versionOlder stateVersion "20.03" then nextcloud17 + else nextcloud18 + ); } { systemd.timers.nextcloud-cron = { @@ -348,6 +398,7 @@ in { ${optionalString (c.dbpassFile != null) "'dbpassword' => nix_read_pwd(),"} 'dbtype' => '${c.dbtype}', 'trusted_domains' => ${writePhpArrary ([ cfg.hostName ] ++ c.extraTrustedDomains)}, + 'trusted_proxies' => ${writePhpArrary (c.trustedProxies)}, ]; ''; occInstallCmd = let @@ -393,7 +444,7 @@ in { path = [ occ ]; script = '' chmod og+x ${cfg.home} - ln -sf ${pkgs.nextcloud}/apps ${cfg.home}/ + ln -sf ${cfg.package}/apps ${cfg.home}/ mkdir -p ${cfg.home}/config ${cfg.home}/data ${cfg.home}/store-apps ln -sf ${overrideConfig} ${cfg.home}/config/override.config.php @@ -415,11 +466,12 @@ in { environment.NEXTCLOUD_CONFIG_DIR = "${cfg.home}/config"; serviceConfig.Type = "oneshot"; serviceConfig.User = "nextcloud"; - serviceConfig.ExecStart = "${phpPackage}/bin/php -f ${pkgs.nextcloud}/cron.php"; + serviceConfig.ExecStart = "${phpPackage}/bin/php -f ${cfg.package}/cron.php"; }; nextcloud-update-plugins = mkIf cfg.autoUpdateApps.enable { serviceConfig.Type = "oneshot"; serviceConfig.ExecStart = "${occ}/bin/nextcloud-occ app:update --all"; + serviceConfig.User = "nextcloud"; startAt = cfg.autoUpdateApps.startAt; }; }; @@ -428,7 +480,7 @@ in { pools.nextcloud = { user = "nextcloud"; group = "nginx"; - phpOptions = phpOptionsExtensions + phpOptionsStr; + phpOptions = phpOptionsStr; phpPackage = phpPackage; phpEnv = { NEXTCLOUD_CONFIG_DIR = "${cfg.home}/config"; @@ -456,7 +508,7 @@ in { enable = true; virtualHosts = { ${cfg.hostName} = { - root = pkgs.nextcloud; + root = cfg.package; locations = { "= /robots.txt" = { priority = 100; @@ -518,10 +570,11 @@ in { add_header X-Robots-Tag none; add_header X-Download-Options noopen; add_header X-Permitted-Cross-Domain-Policies none; + add_header X-Frame-Options sameorigin; add_header Referrer-Policy no-referrer; access_log off; ''; - "~ \\.(?:png|html|ttf|ico|jpg|jpeg)$".extraConfig = '' + "~ \\.(?:png|html|ttf|ico|jpg|jpeg|bcmap|mp4|webm)$".extraConfig = '' try_files $uri /index.php$request_uri; access_log off; ''; @@ -532,6 +585,7 @@ in { add_header X-Robots-Tag none; add_header X-Download-Options noopen; add_header X-Permitted-Cross-Domain-Policies none; + add_header X-Frame-Options sameorigin; add_header Referrer-Policy no-referrer; add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always; error_page 403 /core/templates/403.php; diff --git a/nixos/modules/services/web-apps/nextcloud.xml b/nixos/modules/services/web-apps/nextcloud.xml index d66e0f0c29973..fc454f8ba2549 100644 --- a/nixos/modules/services/web-apps/nextcloud.xml +++ b/nixos/modules/services/web-apps/nextcloud.xml @@ -113,5 +113,53 @@ maintenance:install</literal>! This command tries to install the application and can cause unwanted side-effects!</para> </warning> + + <para> + Nextcloud doesn't allow to move more than one major-version forward. If you're e.g. on + <literal>v16</literal>, you cannot upgrade to <literal>v18</literal>, you need to upgrade to + <literal>v17</literal> first. This is ensured automatically as long as the + <link linkend="opt-system.stateVersion">stateVersion</link> is declared properly. In that case + the oldest version available (one major behind the one from the previous NixOS + release) will be selected by default and the module will generate a warning that reminds + the user to upgrade to latest Nextcloud <emphasis>after</emphasis> that deploy. + </para> + </section> + + <section xml:id="module-services-nextcloud-maintainer-info"> + <title>Maintainer information</title> + + <para> + As stated in the previous paragraph, we must provide a clean upgrade-path for Nextcloud + since it cannot move more than one major version forward on a single upgrade. This chapter + adds some notes how Nextcloud updates should be rolled out in the future. + </para> + + <para> + While minor and patch-level updates are no problem and can be done directly in the + package-expression (and should be backported to supported stable branches after that), + major-releases should be added in a new attribute (e.g. Nextcloud <literal>v19.0.0</literal> + should be available in <literal>nixpkgs</literal> as <literal>pkgs.nextcloud19</literal>). + To provide simple upgrade paths it's generally useful to backport those as well to stable + branches. As long as the package-default isn't altered, this won't break existing setups. + After that, the versioning-warning in the <literal>nextcloud</literal>-module should be + updated to make sure that the + <link linkend="opt-services.nextcloud.package">package</link>-option selects the latest version + on fresh setups. + </para> + + <para> + If major-releases will be abandoned by upstream, we should check first if those are needed + in NixOS for a safe upgrade-path before removing those. In that case we shold keep those + packages, but mark them as insecure in an expression like this (in + <literal><nixpkgs/pkgs/servers/nextcloud/default.nix></literal>): +<programlisting>/* ... */ +{ + nextcloud17 = generic { + version = "17.0.x"; + sha256 = "0000000000000000000000000000000000000000000000000000"; + insecure = true; + }; +}</programlisting> + </para> </section> </chapter> diff --git a/nixos/modules/services/web-apps/restya-board.nix b/nixos/modules/services/web-apps/restya-board.nix index 2c2f36ac598ab..9d0a3f65253e9 100644 --- a/nixos/modules/services/web-apps/restya-board.nix +++ b/nixos/modules/services/web-apps/restya-board.nix @@ -116,7 +116,7 @@ in }; passwordFile = mkOption { - type = types.nullOr types.str; + type = types.nullOr types.path; default = null; description = '' The database user's password. 'null' if no password is set. @@ -285,7 +285,7 @@ in sed -i "s/^.*'R_DB_PASSWORD'.*$/define('R_DB_PASSWORD', 'restya');/g" "${runDir}/server/php/config.inc.php" '' else '' sed -i "s/^.*'R_DB_HOST'.*$/define('R_DB_HOST', '${cfg.database.host}');/g" "${runDir}/server/php/config.inc.php" - sed -i "s/^.*'R_DB_PASSWORD'.*$/define('R_DB_PASSWORD', '$(<${cfg.database.dbPassFile})');/g" "${runDir}/server/php/config.inc.php" + sed -i "s/^.*'R_DB_PASSWORD'.*$/define('R_DB_PASSWORD', ${if cfg.database.passwordFile == null then "''" else "'file_get_contents(${cfg.database.passwordFile})'"});/g" "${runDir}/server/php/config.inc.php ''} sed -i "s/^.*'R_DB_PORT'.*$/define('R_DB_PORT', '${toString cfg.database.port}');/g" "${runDir}/server/php/config.inc.php" sed -i "s/^.*'R_DB_NAME'.*$/define('R_DB_NAME', '${cfg.database.name}');/g" "${runDir}/server/php/config.inc.php" diff --git a/nixos/modules/services/web-apps/trilium.nix b/nixos/modules/services/web-apps/trilium.nix new file mode 100644 index 0000000000000..6f47193c62b9d --- /dev/null +++ b/nixos/modules/services/web-apps/trilium.nix @@ -0,0 +1,137 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.trilium-server; + configIni = pkgs.writeText "trilium-config.ini" '' + [General] + # Instance name can be used to distinguish between different instances + instanceName=${cfg.instanceName} + + # Disable automatically generating desktop icon + noDesktopIcon=true + + [Network] + # host setting is relevant only for web deployments - set the host on which the server will listen + host=${cfg.host} + # port setting is relevant only for web deployments, desktop builds run on random free port + port=${toString cfg.port} + # true for TLS/SSL/HTTPS (secure), false for HTTP (unsecure). + https=false + ''; +in +{ + + options.services.trilium-server = with lib; { + enable = mkEnableOption "trilium-server"; + + dataDir = mkOption { + type = types.str; + default = "/var/lib/trilium"; + description = '' + The directory storing the nodes database and the configuration. + ''; + }; + + instanceName = mkOption { + type = types.str; + default = "Trilium"; + description = '' + Instance name used to distinguish between different instances + ''; + }; + + host = mkOption { + type = types.str; + default = "127.0.0.1"; + description = '' + The host address to bind to (defaults to localhost). + ''; + }; + + port = mkOption { + type = types.int; + default = 8080; + description = '' + The port number to bind to. + ''; + }; + + nginx = mkOption { + default = {}; + description = '' + Configuration for nginx reverse proxy. + ''; + + type = types.submodule { + options = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Configure the nginx reverse proxy settings. + ''; + }; + + hostName = mkOption { + type = types.str; + description = '' + The hostname use to setup the virtualhost configuration + ''; + }; + }; + }; + }; + }; + + config = lib.mkIf cfg.enable (lib.mkMerge [ + { + meta.maintainers = with lib.maintainers; [ kampka ]; + + users.groups.trilium = {}; + users.users.trilium = { + description = "Trilium User"; + group = "trilium"; + home = cfg.dataDir; + isSystemUser = true; + }; + + systemd.services.trilium-server = { + wantedBy = [ "multi-user.target" ]; + environment.TRILIUM_DATA_DIR = cfg.dataDir; + serviceConfig = { + ExecStart = "${pkgs.trilium-server}/bin/trilium-server"; + User = "trilium"; + Group = "trilium"; + PrivateTmp = "true"; + }; + }; + + systemd.tmpfiles.rules = [ + "d ${cfg.dataDir} 0750 trilium trilium - -" + "L+ ${cfg.dataDir}/config.ini - - - - ${configIni}" + ]; + + } + + (lib.mkIf cfg.nginx.enable { + services.nginx = { + enable = true; + virtualHosts."${cfg.nginx.hostName}" = { + locations."/" = { + proxyPass = "http://${cfg.host}:${toString cfg.port}/"; + extraConfig = '' + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + ''; + }; + extraConfig = '' + client_max_body_size 0; + ''; + }; + }; + }) + ]); +} diff --git a/nixos/modules/services/web-apps/wordpress.nix b/nixos/modules/services/web-apps/wordpress.nix index f1370c2854b8d..5fbe53221ae87 100644 --- a/nixos/modules/services/web-apps/wordpress.nix +++ b/nixos/modules/services/web-apps/wordpress.nix @@ -3,7 +3,7 @@ let inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types; inherit (lib) any attrValues concatMapStringsSep flatten literalExample; - inherit (lib) mapAttrs' mapAttrsToList nameValuePair optional optionalAttrs optionalString; + inherit (lib) mapAttrs mapAttrs' mapAttrsToList nameValuePair optional optionalAttrs optionalString; eachSite = config.services.wordpress; user = "wordpress"; @@ -105,7 +105,7 @@ let name = "embed-pdf-viewer-plugin"; # Download the theme from the wordpress site src = pkgs.fetchurl { - url = https://downloads.wordpress.org/plugin/embed-pdf-viewer.2.0.3.zip; + url = "https://downloads.wordpress.org/plugin/embed-pdf-viewer.2.0.3.zip"; sha256 = "1rhba5h5fjlhy8p05zf0p14c9iagfh96y91r36ni0rmk6y891lyd"; }; # We need unzip to build this package @@ -127,12 +127,12 @@ let <note><para>These themes need to be packaged before use, see example.</para></note> ''; example = '' - # For shits and giggles, let's package the responsive theme + # Let's package the responsive theme responsiveTheme = pkgs.stdenv.mkDerivation { name = "responsive-theme"; # Download the theme from the wordpress site src = pkgs.fetchurl { - url = https://downloads.wordpress.org/theme/responsive.3.14.zip; + url = "https://downloads.wordpress.org/theme/responsive.3.14.zip"; sha256 = "0rjwm811f4aa4q43r77zxlpklyb85q08f9c8ns2akcarrvj5ydx3"; }; # We need unzip to build this package @@ -209,18 +209,12 @@ let }; virtualHost = mkOption { - type = types.submodule ({ - options = import ../web-servers/apache-httpd/per-server-options.nix { - inherit lib; - forMainServer = false; - }; - }); + type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix); example = literalExample '' { - enableSSL = true; adminAddr = "webmaster@example.org"; - sslServerCert = "/var/lib/acme/wordpress.example.org/full.pem"; - sslServerKey = "/var/lib/acme/wordpress.example.org/key.pem"; + forceSSL = true; + enableACME = true; } ''; description = '' @@ -304,41 +298,37 @@ in services.httpd = { enable = true; extraModules = [ "proxy_fcgi" ]; - virtualHosts = mapAttrsToList (hostName: cfg: - (mkMerge [ - cfg.virtualHost { - documentRoot = mkForce "${pkg hostName cfg}/share/wordpress"; - extraConfig = '' - <Directory "${pkg hostName cfg}/share/wordpress"> - <FilesMatch "\.php$"> - <If "-f %{REQUEST_FILENAME}"> - SetHandler "proxy:unix:${config.services.phpfpm.pools."wordpress-${hostName}".socket}|fcgi://localhost/" - </If> - </FilesMatch> - - # standard wordpress .htaccess contents - <IfModule mod_rewrite.c> - RewriteEngine On - RewriteBase / - RewriteRule ^index\.php$ - [L] - RewriteCond %{REQUEST_FILENAME} !-f - RewriteCond %{REQUEST_FILENAME} !-d - RewriteRule . /index.php [L] - </IfModule> - - DirectoryIndex index.php - Require all granted - Options +FollowSymLinks - </Directory> - - # https://wordpress.org/support/article/hardening-wordpress/#securing-wp-config-php - <Files wp-config.php> - Require all denied - </Files> - ''; - } - ]) - ) eachSite; + virtualHosts = mapAttrs (hostName: cfg: mkMerge [ cfg.virtualHost { + documentRoot = mkForce "${pkg hostName cfg}/share/wordpress"; + extraConfig = '' + <Directory "${pkg hostName cfg}/share/wordpress"> + <FilesMatch "\.php$"> + <If "-f %{REQUEST_FILENAME}"> + SetHandler "proxy:unix:${config.services.phpfpm.pools."wordpress-${hostName}".socket}|fcgi://localhost/" + </If> + </FilesMatch> + + # standard wordpress .htaccess contents + <IfModule mod_rewrite.c> + RewriteEngine On + RewriteBase / + RewriteRule ^index\.php$ - [L] + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule . /index.php [L] + </IfModule> + + DirectoryIndex index.php + Require all granted + Options +FollowSymLinks + </Directory> + + # https://wordpress.org/support/article/hardening-wordpress/#securing-wp-config-php + <Files wp-config.php> + Require all denied + </Files> + ''; + } ]) eachSite; }; systemd.tmpfiles.rules = flatten (mapAttrsToList (hostName: cfg: [ diff --git a/nixos/modules/services/web-apps/youtrack.nix b/nixos/modules/services/web-apps/youtrack.nix index 830edac20bac3..b4d653d2d77e3 100644 --- a/nixos/modules/services/web-apps/youtrack.nix +++ b/nixos/modules/services/web-apps/youtrack.nix @@ -46,9 +46,11 @@ in https://www.jetbrains.com/help/youtrack/standalone/YouTrack-Java-Start-Parameters.html for more information. ''; - example = { - "jetbrains.youtrack.overrideRootPassword" = "tortuga"; - }; + example = literalExample '' + { + "jetbrains.youtrack.overrideRootPassword" = "tortuga"; + } + ''; type = types.attrsOf types.str; }; diff --git a/nixos/modules/services/web-apps/zabbix.nix b/nixos/modules/services/web-apps/zabbix.nix index 09538726b7cd8..0071951283478 100644 --- a/nixos/modules/services/web-apps/zabbix.nix +++ b/nixos/modules/services/web-apps/zabbix.nix @@ -113,19 +113,15 @@ in }; virtualHost = mkOption { - type = types.submodule ({ - options = import ../web-servers/apache-httpd/per-server-options.nix { - inherit lib; - forMainServer = false; - }; - }); - example = { - hostName = "zabbix.example.org"; - enableSSL = true; - adminAddr = "webmaster@example.org"; - sslServerCert = "/var/lib/acme/zabbix.example.org/full.pem"; - sslServerKey = "/var/lib/acme/zabbix.example.org/key.pem"; - }; + type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix); + example = literalExample '' + { + hostName = "zabbix.example.org"; + adminAddr = "webmaster@example.org"; + forceSSL = true; + enableACME = true; + } + ''; description = '' Apache configuration can be done by adapting <literal>services.httpd.virtualHosts.<name></literal>. See <xref linkend="opt-services.httpd.virtualHosts"/> for further information. @@ -190,23 +186,21 @@ in enable = true; adminAddr = mkDefault cfg.virtualHost.adminAddr; extraModules = [ "proxy_fcgi" ]; - virtualHosts = [ (mkMerge [ - cfg.virtualHost { - documentRoot = mkForce "${cfg.package}/share/zabbix"; - extraConfig = '' - <Directory "${cfg.package}/share/zabbix"> - <FilesMatch "\.php$"> - <If "-f %{REQUEST_FILENAME}"> - SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/" - </If> - </FilesMatch> - AllowOverride all - Options -Indexes - DirectoryIndex index.php - </Directory> - ''; - } - ]) ]; + virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost { + documentRoot = mkForce "${cfg.package}/share/zabbix"; + extraConfig = '' + <Directory "${cfg.package}/share/zabbix"> + <FilesMatch "\.php$"> + <If "-f %{REQUEST_FILENAME}"> + SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/" + </If> + </FilesMatch> + AllowOverride all + Options -Indexes + DirectoryIndex index.php + </Directory> + ''; + } ]; }; users.users.${user} = mapAttrs (name: mkDefault) { diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix index f5a6051b4b5f5..5e55baa203a0e 100644 --- a/nixos/modules/services/web-servers/apache-httpd/default.nix +++ b/nixos/modules/services/web-servers/apache-httpd/default.nix @@ -4,36 +4,34 @@ with lib; let - mainCfg = config.services.httpd; + cfg = config.services.httpd; runtimeDir = "/run/httpd"; - httpd = mainCfg.package.out; + pkg = cfg.package.out; - httpdConf = mainCfg.configFile; + httpdConf = cfg.configFile; - php = mainCfg.phpPackage.override { apacheHttpd = httpd.dev; /* otherwise it only gets .out */ }; + php = cfg.phpPackage.override { apacheHttpd = pkg; }; phpMajorVersion = lib.versions.major (lib.getVersion php); - mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = httpd; }; + mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = pkg; }; - defaultListen = cfg: if cfg.enableSSL - then [{ip = "*"; port = 443;}] - else [{ip = "*"; port = 80;}]; + vhosts = attrValues cfg.virtualHosts; - getListen = cfg: - if cfg.listen == [] - then defaultListen cfg - else cfg.listen; + mkListenInfo = hostOpts: + if hostOpts.listen != [] then hostOpts.listen + else ( + optional (hostOpts.onlySSL || hostOpts.addSSL || hostOpts.forceSSL) { ip = "*"; port = 443; ssl = true; } ++ + optional (!hostOpts.onlySSL) { ip = "*"; port = 80; ssl = false; } + ); - listenToString = l: "${l.ip}:${toString l.port}"; + listenInfo = unique (concatMap mkListenInfo vhosts); - allHosts = [mainCfg] ++ mainCfg.virtualHosts; - - enableSSL = any (vhost: vhost.enableSSL) allHosts; - - enableUserDir = any (vhost: vhost.enableUserDir) allHosts; + enableHttp2 = any (vhost: vhost.http2) vhosts; + enableSSL = any (listen: listen.ssl) listenInfo; + enableUserDir = any (vhost: vhost.enableUserDir) vhosts; # NOTE: generally speaking order of modules is very important modules = @@ -43,23 +41,19 @@ let "mime" "autoindex" "negotiation" "dir" "alias" "rewrite" "unixd" "slotmem_shm" "socache_shmcb" - "mpm_${mainCfg.multiProcessingModule}" + "mpm_${cfg.multiProcessingModule}" ] - ++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ]) + ++ (if cfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ]) + ++ optional enableHttp2 "http2" ++ optional enableSSL "ssl" ++ optional enableUserDir "userdir" - ++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; } - ++ optional mainCfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; } - ++ optional mainCfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; } - ++ mainCfg.extraModules; - + ++ optional cfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; } + ++ optional cfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; } + ++ optional cfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; } + ++ cfg.extraModules; - allDenied = "Require all denied"; - allGranted = "Require all granted"; - - - loggingConf = (if mainCfg.logFormat != "none" then '' - ErrorLog ${mainCfg.logDir}/error.log + loggingConf = (if cfg.logFormat != "none" then '' + ErrorLog ${cfg.logDir}/error.log LogLevel notice @@ -68,7 +62,7 @@ let LogFormat "%{Referer}i -> %U" referer LogFormat "%{User-agent}i" agent - CustomLog ${mainCfg.logDir}/access.log ${mainCfg.logFormat} + CustomLog ${cfg.logDir}/access.log ${cfg.logFormat} '' else '' ErrorLog /dev/null ''); @@ -90,174 +84,208 @@ let sslConf = '' - SSLSessionCache shmcb:${runtimeDir}/ssl_scache(512000) + <IfModule mod_ssl.c> + SSLSessionCache shmcb:${runtimeDir}/ssl_scache(512000) - Mutex posixsem + Mutex posixsem - SSLRandomSeed startup builtin - SSLRandomSeed connect builtin + SSLRandomSeed startup builtin + SSLRandomSeed connect builtin - SSLProtocol ${mainCfg.sslProtocols} - SSLCipherSuite ${mainCfg.sslCiphers} - SSLHonorCipherOrder on + SSLProtocol ${cfg.sslProtocols} + SSLCipherSuite ${cfg.sslCiphers} + SSLHonorCipherOrder on + </IfModule> ''; mimeConf = '' - TypesConfig ${httpd}/conf/mime.types + TypesConfig ${pkg}/conf/mime.types AddType application/x-x509-ca-cert .crt AddType application/x-pkcs7-crl .crl AddType application/x-httpd-php .php .phtml <IfModule mod_mime_magic.c> - MIMEMagicFile ${httpd}/conf/magic + MIMEMagicFile ${pkg}/conf/magic </IfModule> ''; - - perServerConf = isMainServer: cfg: let - - # Canonical name must not include a trailing slash. - canonicalNames = - let defaultPort = (head (defaultListen cfg)).port; in - map (port: - (if cfg.enableSSL then "https" else "http") + "://" + - cfg.hostName + - (if port != defaultPort then ":${toString port}" else "") - ) (map (x: x.port) (getListen cfg)); - - maybeDocumentRoot = fold (svc: acc: - if acc == null then svc.documentRoot else assert svc.documentRoot == null; acc - ) null ([ cfg ]); - - documentRoot = if maybeDocumentRoot != null then maybeDocumentRoot else - pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out"; - - documentRootConf = '' - DocumentRoot "${documentRoot}" - - <Directory "${documentRoot}"> - Options Indexes FollowSymLinks - AllowOverride None - ${allGranted} - </Directory> - ''; - - # If this is a vhost, the include the entries for the main server as well. - robotsTxt = concatStringsSep "\n" (filter (x: x != "") ([ cfg.robotsEntries ] ++ lib.optional (!isMainServer) mainCfg.robotsEntries)); - - in '' - ${concatStringsSep "\n" (map (n: "ServerName ${n}") canonicalNames)} - - ${concatMapStrings (alias: "ServerAlias ${alias}\n") cfg.serverAliases} - - ${if cfg.sslServerCert != null then '' - SSLCertificateFile ${cfg.sslServerCert} - SSLCertificateKeyFile ${cfg.sslServerKey} - ${if cfg.sslServerChain != null then '' - SSLCertificateChainFile ${cfg.sslServerChain} - '' else ""} - '' else ""} - - ${if cfg.enableSSL then '' - SSLEngine on - '' else if enableSSL then /* i.e., SSL is enabled for some host, but not this one */ - '' - SSLEngine off - '' else ""} - - ${if isMainServer || cfg.adminAddr != null then '' - ServerAdmin ${cfg.adminAddr} - '' else ""} - - ${if !isMainServer && mainCfg.logPerVirtualHost then '' - ErrorLog ${mainCfg.logDir}/error-${cfg.hostName}.log - CustomLog ${mainCfg.logDir}/access-${cfg.hostName}.log ${cfg.logFormat} - '' else ""} - - ${optionalString (robotsTxt != "") '' - Alias /robots.txt ${pkgs.writeText "robots.txt" robotsTxt} - ''} - - ${if isMainServer || maybeDocumentRoot != null then documentRootConf else ""} - - ${if cfg.enableUserDir then '' - - UserDir public_html - UserDir disabled root - - <Directory "/home/*/public_html"> - AllowOverride FileInfo AuthConfig Limit Indexes - Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec - <Limit GET POST OPTIONS> - ${allGranted} - </Limit> - <LimitExcept GET POST OPTIONS> - ${allDenied} - </LimitExcept> - </Directory> - - '' else ""} - - ${if cfg.globalRedirect != null && cfg.globalRedirect != "" then '' - RedirectPermanent / ${cfg.globalRedirect} - '' else ""} - - ${ - let makeFileConf = elem: '' - Alias ${elem.urlPath} ${elem.file} - ''; - in concatMapStrings makeFileConf cfg.servedFiles - } - - ${ - let makeDirConf = elem: '' - Alias ${elem.urlPath} ${elem.dir}/ - <Directory ${elem.dir}> - Options +Indexes - ${allGranted} - AllowOverride All - </Directory> - ''; - in concatMapStrings makeDirConf cfg.servedDirs - } - - ${cfg.extraConfig} - ''; + mkVHostConf = hostOpts: + let + adminAddr = if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr; + listen = filter (listen: !listen.ssl) (mkListenInfo hostOpts); + listenSSL = filter (listen: listen.ssl) (mkListenInfo hostOpts); + + useACME = hostOpts.enableACME || hostOpts.useACMEHost != null; + sslCertDir = + if hostOpts.enableACME then config.security.acme.certs.${hostOpts.hostName}.directory + else if hostOpts.useACMEHost != null then config.security.acme.certs.${hostOpts.useACMEHost}.directory + else abort "This case should never happen."; + + sslServerCert = if useACME then "${sslCertDir}/full.pem" else hostOpts.sslServerCert; + sslServerKey = if useACME then "${sslCertDir}/key.pem" else hostOpts.sslServerKey; + sslServerChain = if useACME then "${sslCertDir}/fullchain.pem" else hostOpts.sslServerChain; + + acmeChallenge = optionalString useACME '' + Alias /.well-known/acme-challenge/ "${hostOpts.acmeRoot}/.well-known/acme-challenge/" + <Directory "${hostOpts.acmeRoot}"> + AllowOverride None + Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec + Require method GET POST OPTIONS + Require all granted + </Directory> + ''; + in + optionalString (listen != []) '' + <VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listen}> + ServerName ${hostOpts.hostName} + ${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases} + ServerAdmin ${adminAddr} + <IfModule mod_ssl.c> + SSLEngine off + </IfModule> + ${acmeChallenge} + ${if hostOpts.forceSSL then '' + <IfModule mod_rewrite.c> + RewriteEngine on + RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge [NC] + RewriteCond %{HTTPS} off + RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} + </IfModule> + '' else mkVHostCommonConf hostOpts} + </VirtualHost> + '' + + optionalString (listenSSL != []) '' + <VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listenSSL}> + ServerName ${hostOpts.hostName} + ${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases} + ServerAdmin ${adminAddr} + SSLEngine on + SSLCertificateFile ${sslServerCert} + SSLCertificateKeyFile ${sslServerKey} + ${optionalString (sslServerChain != null) "SSLCertificateChainFile ${sslServerChain}"} + ${optionalString hostOpts.http2 "Protocols h2 h2c http/1.1"} + ${acmeChallenge} + ${mkVHostCommonConf hostOpts} + </VirtualHost> + '' + ; + + mkVHostCommonConf = hostOpts: + let + documentRoot = if hostOpts.documentRoot != null + then hostOpts.documentRoot + else pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out" + ; + + mkLocations = locations: concatStringsSep "\n" (map (config: '' + <Location ${config.location}> + ${optionalString (config.proxyPass != null) '' + <IfModule mod_proxy.c> + ProxyPass ${config.proxyPass} + ProxyPassReverse ${config.proxyPass} + </IfModule> + ''} + ${optionalString (config.index != null) '' + <IfModule mod_dir.c> + DirectoryIndex ${config.index} + </IfModule> + ''} + ${optionalString (config.alias != null) '' + <IfModule mod_alias.c> + Alias "${config.alias}" + </IfModule> + ''} + ${config.extraConfig} + </Location> + '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations))); + in + '' + ${optionalString cfg.logPerVirtualHost '' + ErrorLog ${cfg.logDir}/error-${hostOpts.hostName}.log + CustomLog ${cfg.logDir}/access-${hostOpts.hostName}.log ${hostOpts.logFormat} + ''} + + ${optionalString (hostOpts.robotsEntries != "") '' + Alias /robots.txt ${pkgs.writeText "robots.txt" hostOpts.robotsEntries} + ''} + + DocumentRoot "${documentRoot}" + + <Directory "${documentRoot}"> + Options Indexes FollowSymLinks + AllowOverride None + Require all granted + </Directory> + + ${optionalString hostOpts.enableUserDir '' + UserDir public_html + UserDir disabled root + <Directory "/home/*/public_html"> + AllowOverride FileInfo AuthConfig Limit Indexes + Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec + <Limit GET POST OPTIONS> + Require all granted + </Limit> + <LimitExcept GET POST OPTIONS> + Require all denied + </LimitExcept> + </Directory> + ''} + + ${optionalString (hostOpts.globalRedirect != null && hostOpts.globalRedirect != "") '' + RedirectPermanent / ${hostOpts.globalRedirect} + ''} + + ${ + let makeDirConf = elem: '' + Alias ${elem.urlPath} ${elem.dir}/ + <Directory ${elem.dir}> + Options +Indexes + Require all granted + AllowOverride All + </Directory> + ''; + in concatMapStrings makeDirConf hostOpts.servedDirs + } + + ${mkLocations hostOpts.locations} + ${hostOpts.extraConfig} + '' + ; confFile = pkgs.writeText "httpd.conf" '' - ServerRoot ${httpd} - + ServerRoot ${pkg} + ServerName ${config.networking.hostName} DefaultRuntimeDir ${runtimeDir}/runtime PidFile ${runtimeDir}/httpd.pid - ${optionalString (mainCfg.multiProcessingModule != "prefork") '' + ${optionalString (cfg.multiProcessingModule != "prefork") '' # mod_cgid requires this. ScriptSock ${runtimeDir}/cgisock ''} <IfModule prefork.c> - MaxClients ${toString mainCfg.maxClients} - MaxRequestsPerChild ${toString mainCfg.maxRequestsPerChild} + MaxClients ${toString cfg.maxClients} + MaxRequestsPerChild ${toString cfg.maxRequestsPerChild} </IfModule> ${let - listen = concatMap getListen allHosts; - toStr = listen: "Listen ${listenToString listen}\n"; - uniqueListen = uniqList {inputList = map toStr listen;}; - in concatStrings uniqueListen + toStr = listen: "Listen ${listen.ip}:${toString listen.port} ${if listen.ssl then "https" else "http"}"; + uniqueListen = uniqList {inputList = map toStr listenInfo;}; + in concatStringsSep "\n" uniqueListen } - User ${mainCfg.user} - Group ${mainCfg.group} + User ${cfg.user} + Group ${cfg.group} ${let mkModule = module: - if isString module then { name = module; path = "${httpd}/modules/mod_${module}.so"; } + if isString module then { name = module; path = "${pkg}/modules/mod_${module}.so"; } else if isAttrs module then { inherit (module) name path; } else throw "Expecting either a string or attribute set including a name and path."; in @@ -267,57 +295,50 @@ let AddHandler type-map var <Files ~ "^\.ht"> - ${allDenied} + Require all denied </Files> ${mimeConf} ${loggingConf} ${browserHacks} - Include ${httpd}/conf/extra/httpd-default.conf - Include ${httpd}/conf/extra/httpd-autoindex.conf - Include ${httpd}/conf/extra/httpd-multilang-errordoc.conf - Include ${httpd}/conf/extra/httpd-languages.conf + Include ${pkg}/conf/extra/httpd-default.conf + Include ${pkg}/conf/extra/httpd-autoindex.conf + Include ${pkg}/conf/extra/httpd-multilang-errordoc.conf + Include ${pkg}/conf/extra/httpd-languages.conf TraceEnable off - ${if enableSSL then sslConf else ""} + ${sslConf} # Fascist default - deny access to everything. <Directory /> Options FollowSymLinks AllowOverride None - ${allDenied} + Require all denied </Directory> # But do allow access to files in the store so that we don't have # to generate <Directory> clauses for every generated file that we # want to serve. <Directory /nix/store> - ${allGranted} + Require all granted </Directory> - # Generate directives for the main server. - ${perServerConf true mainCfg} + ${cfg.extraConfig} - ${let - makeVirtualHost = vhost: '' - <VirtualHost ${concatStringsSep " " (map listenToString (getListen vhost))}> - ${perServerConf false vhost} - </VirtualHost> - ''; - in concatMapStrings makeVirtualHost mainCfg.virtualHosts - } + ${concatMapStringsSep "\n" mkVHostConf vhosts} ''; # Generate the PHP configuration file. Should probably be factored # out into a separate module. phpIni = pkgs.runCommand "php.ini" - { options = mainCfg.phpOptions; + { options = cfg.phpOptions; preferLocalBuild = true; } '' cat ${php}/etc/php.ini > $out + cat ${php}/lib/custom-php.ini > $out echo "$options" >> $out ''; @@ -329,19 +350,30 @@ in imports = [ (mkRemovedOptionModule [ "services" "httpd" "extraSubservices" ] "Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.") (mkRemovedOptionModule [ "services" "httpd" "stateDir" ] "The httpd module now uses /run/httpd as a runtime directory.") + + # virtualHosts options + (mkRemovedOptionModule [ "services" "httpd" "documentRoot" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "enableSSL" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "enableUserDir" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "globalRedirect" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "hostName" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "listen" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "robotsEntries" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "servedDirs" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "servedFiles" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "serverAliases" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "sslServerCert" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "sslServerChain" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "sslServerKey" ] "Please define a virtual host using `services.httpd.virtualHosts`.") ]; - ###### interface + # interface options = { services.httpd = { - enable = mkOption { - type = types.bool; - default = false; - description = "Whether to enable the Apache HTTP Server."; - }; + enable = mkEnableOption "the Apache HTTP Server"; package = mkOption { type = types.package; @@ -367,8 +399,8 @@ in type = types.lines; default = ""; description = '' - Cnfiguration lines appended to the generated Apache - configuration file. Note that this mechanism may not work + Configuration lines appended to the generated Apache + configuration file. Note that this mechanism will not work when <option>configFile</option> is overridden. ''; }; @@ -383,7 +415,7 @@ in ] ''; description = '' - Additional Apache modules to be used. These can be + Additional Apache modules to be used. These can be specified as a string in the case of modules distributed with Apache, or as an attribute set specifying the <varname>name</varname> and <varname>path</varname> of the @@ -391,9 +423,25 @@ in ''; }; + adminAddr = mkOption { + type = types.str; + example = "admin@example.org"; + description = "E-mail address of the server administrator."; + }; + + logFormat = mkOption { + type = types.str; + default = "common"; + example = "combined"; + description = '' + Log format for log files. Possible values are: combined, common, referer, agent. + See <link xlink:href="https://httpd.apache.org/docs/2.4/logs.html"/> for more details. + ''; + }; + logPerVirtualHost = mkOption { type = types.bool; - default = false; + default = true; description = '' If enabled, each virtual host gets its own <filename>access.log</filename> and @@ -406,8 +454,7 @@ in type = types.str; default = "wwwrun"; description = '' - User account under which httpd runs. The account is created - automatically if it doesn't exist. + User account under which httpd runs. ''; }; @@ -415,8 +462,7 @@ in type = types.str; default = "wwwrun"; description = '' - Group under which httpd runs. The account is created - automatically if it doesn't exist. + Group under which httpd runs. ''; }; @@ -424,31 +470,33 @@ in type = types.path; default = "/var/log/httpd"; description = '' - Directory for Apache's log files. It is created automatically. + Directory for Apache's log files. It is created automatically. ''; }; virtualHosts = mkOption { - type = types.listOf (types.submodule ( - { options = import ./per-server-options.nix { - inherit lib; - forMainServer = false; + type = with types; attrsOf (submodule (import ./vhost-options.nix)); + default = { + localhost = { + documentRoot = "${pkg}/htdocs"; + }; + }; + example = literalExample '' + { + "foo.example.com" = { + forceSSL = true; + documentRoot = "/var/www/foo.example.com" + }; + "bar.example.com" = { + addSSL = true; + documentRoot = "/var/www/bar.example.com"; }; - })); - default = []; - example = [ - { hostName = "foo"; - documentRoot = "/data/webroot-foo"; - } - { hostName = "bar"; - documentRoot = "/data/webroot-bar"; } - ]; + ''; description = '' - Specification of the virtual hosts served by Apache. Each + Specification of the virtual hosts served by Apache. Each element should be an attribute set specifying the - configuration of the virtual host. The available options - are the non-global options permissible for the main host. + configuration of the virtual host. ''; }; @@ -486,17 +534,18 @@ in '' date.timezone = "CET" ''; - description = - "Options appended to the PHP configuration file <filename>php.ini</filename>."; + description = '' + Options appended to the PHP configuration file <filename>php.ini</filename>. + ''; }; multiProcessingModule = mkOption { - type = types.str; + type = types.enum [ "event" "prefork" "worker" ]; default = "prefork"; example = "worker"; description = '' - Multi-processing module to be used by Apache. Available + Multi-processing module to be used by Apache. Available modules are <literal>prefork</literal> (the default; handles each request in a separate child process), <literal>worker</literal> (hybrid approach that starts a @@ -518,8 +567,9 @@ in type = types.int; default = 0; example = 500; - description = - "Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited"; + description = '' + Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited. + ''; }; sslCiphers = mkOption { @@ -530,44 +580,74 @@ in sslProtocols = mkOption { type = types.str; - default = "All -SSLv2 -SSLv3 -TLSv1"; + default = "All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1"; example = "All -SSLv2 -SSLv3"; description = "Allowed SSL/TLS protocol versions."; }; - } - - # Include the options shared between the main server and virtual hosts. - // (import ./per-server-options.nix { - inherit lib; - forMainServer = true; - }); + }; }; + # implementation - ###### implementation + config = mkIf cfg.enable { - config = mkIf config.services.httpd.enable { + assertions = [ + { + assertion = all (hostOpts: !hostOpts.enableSSL) vhosts; + message = '' + The option `services.httpd.virtualHosts.<name>.enableSSL` no longer has any effect; please remove it. + Select one of `services.httpd.virtualHosts.<name>.addSSL`, `services.httpd.virtualHosts.<name>.forceSSL`, + or `services.httpd.virtualHosts.<name>.onlySSL`. + ''; + } + { + assertion = all (hostOpts: with hostOpts; !(addSSL && onlySSL) && !(forceSSL && onlySSL) && !(addSSL && forceSSL)) vhosts; + message = '' + Options `services.httpd.virtualHosts.<name>.addSSL`, + `services.httpd.virtualHosts.<name>.onlySSL` and `services.httpd.virtualHosts.<name>.forceSSL` + are mutually exclusive. + ''; + } + { + assertion = all (hostOpts: !(hostOpts.enableACME && hostOpts.useACMEHost != null)) vhosts; + message = '' + Options `services.httpd.virtualHosts.<name>.enableACME` and + `services.httpd.virtualHosts.<name>.useACMEHost` are mutually exclusive. + ''; + } + ]; - assertions = [ { assertion = mainCfg.enableSSL == true - -> mainCfg.sslServerCert != null - && mainCfg.sslServerKey != null; - message = "SSL is enabled for httpd, but sslServerCert and/or sslServerKey haven't been specified."; } - ]; + warnings = + mapAttrsToList (name: hostOpts: '' + Using config.services.httpd.virtualHosts."${name}".servedFiles is deprecated and will become unsupported in a future release. Your configuration will continue to work as is but please migrate your configuration to config.services.httpd.virtualHosts."${name}".locations before the 20.09 release of NixOS. + '') (filterAttrs (name: hostOpts: hostOpts.servedFiles != []) cfg.virtualHosts); - users.users = optionalAttrs (mainCfg.user == "wwwrun") (singleton - { name = "wwwrun"; - group = mainCfg.group; + users.users = optionalAttrs (cfg.user == "wwwrun") { + wwwrun = { + group = cfg.group; description = "Apache httpd user"; uid = config.ids.uids.wwwrun; - }); + }; + }; + + users.groups = optionalAttrs (cfg.group == "wwwrun") { + wwwrun.gid = config.ids.gids.wwwrun; + }; + + security.acme.certs = mapAttrs (name: hostOpts: { + user = cfg.user; + group = mkDefault cfg.group; + email = if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr; + webroot = hostOpts.acmeRoot; + extraDomains = genAttrs hostOpts.serverAliases (alias: null); + postRun = "systemctl reload httpd.service"; + }) (filterAttrs (name: hostOpts: hostOpts.enableACME) cfg.virtualHosts); - users.groups = optionalAttrs (mainCfg.group == "wwwrun") (singleton - { name = "wwwrun"; - gid = config.ids.gids.wwwrun; - }); + environment.systemPackages = [ pkg ]; - environment.systemPackages = [httpd]; + # required for "apachectl configtest" + environment.etc."httpd/httpd.conf".source = httpdConf; services.httpd.phpOptions = '' @@ -604,42 +684,56 @@ in "access_compat" ]; + systemd.tmpfiles.rules = + let + svc = config.systemd.services.httpd.serviceConfig; + in + [ + "d '${cfg.logDir}' 0700 ${svc.User} ${svc.Group}" + "Z '${cfg.logDir}' - ${svc.User} ${svc.Group}" + ]; + systemd.services.httpd = + let + vhostsACME = filter (hostOpts: hostOpts.enableACME) vhosts; + in { description = "Apache HTTPD"; wantedBy = [ "multi-user.target" ]; - after = [ "network.target" "fs.target" ]; + wants = concatLists (map (hostOpts: [ "acme-${hostOpts.hostName}.service" "acme-selfsigned-${hostOpts.hostName}.service" ]) vhostsACME); + after = [ "network.target" "fs.target" ] ++ map (hostOpts: "acme-selfsigned-${hostOpts.hostName}.service") vhostsACME; path = - [ httpd pkgs.coreutils pkgs.gnugrep ] - ++ optional mainCfg.enablePHP pkgs.system-sendmail; # Needed for PHP's mail() function. + [ pkg pkgs.coreutils pkgs.gnugrep ] + ++ optional cfg.enablePHP pkgs.system-sendmail; # Needed for PHP's mail() function. environment = - optionalAttrs mainCfg.enablePHP { PHPRC = phpIni; } - // optionalAttrs mainCfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; }; + optionalAttrs cfg.enablePHP { PHPRC = phpIni; } + // optionalAttrs cfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; }; preStart = '' - mkdir -m 0700 -p ${mainCfg.logDir} - # Get rid of old semaphores. These tend to accumulate across # server restarts, eventually preventing it from restarting # successfully. - for i in $(${pkgs.utillinux}/bin/ipcs -s | grep ' ${mainCfg.user} ' | cut -f2 -d ' '); do + for i in $(${pkgs.utillinux}/bin/ipcs -s | grep ' ${cfg.user} ' | cut -f2 -d ' '); do ${pkgs.utillinux}/bin/ipcrm -s $i done ''; - serviceConfig.ExecStart = "@${httpd}/bin/httpd httpd -f ${httpdConf}"; - serviceConfig.ExecStop = "${httpd}/bin/httpd -f ${httpdConf} -k graceful-stop"; - serviceConfig.ExecReload = "${httpd}/bin/httpd -f ${httpdConf} -k graceful"; - serviceConfig.Group = mainCfg.group; - serviceConfig.Type = "forking"; - serviceConfig.PIDFile = "${runtimeDir}/httpd.pid"; - serviceConfig.Restart = "always"; - serviceConfig.RestartSec = "5s"; - serviceConfig.RuntimeDirectory = "httpd httpd/runtime"; - serviceConfig.RuntimeDirectoryMode = "0750"; + serviceConfig = { + ExecStart = "@${pkg}/bin/httpd httpd -f ${httpdConf}"; + ExecStop = "${pkg}/bin/httpd -f ${httpdConf} -k graceful-stop"; + ExecReload = "${pkg}/bin/httpd -f ${httpdConf} -k graceful"; + User = "root"; + Group = cfg.group; + Type = "forking"; + PIDFile = "${runtimeDir}/httpd.pid"; + Restart = "always"; + RestartSec = "5s"; + RuntimeDirectory = "httpd httpd/runtime"; + RuntimeDirectoryMode = "0750"; + }; }; }; diff --git a/nixos/modules/services/web-servers/apache-httpd/location-options.nix b/nixos/modules/services/web-servers/apache-httpd/location-options.nix new file mode 100644 index 0000000000000..8ea88f94f973f --- /dev/null +++ b/nixos/modules/services/web-servers/apache-httpd/location-options.nix @@ -0,0 +1,54 @@ +{ config, lib, name, ... }: +let + inherit (lib) mkOption types; +in +{ + options = { + + proxyPass = mkOption { + type = with types; nullOr str; + default = null; + example = "http://www.example.org/"; + description = '' + Sets up a simple reverse proxy as described by <link xlink:href="https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html#simple" />. + ''; + }; + + index = mkOption { + type = with types; nullOr str; + default = null; + example = "index.php index.html"; + description = '' + Adds DirectoryIndex directive. See <link xlink:href="https://httpd.apache.org/docs/2.4/mod/mod_dir.html#directoryindex" />. + ''; + }; + + alias = mkOption { + type = with types; nullOr path; + default = null; + example = "/your/alias/directory"; + description = '' + Alias directory for requests. See <link xlink:href="https://httpd.apache.org/docs/2.4/mod/mod_alias.html#alias" />. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + These lines go to the end of the location verbatim. + ''; + }; + + priority = mkOption { + type = types.int; + default = 1000; + description = '' + Order of this location block in relation to the others in the vhost. + The semantics are the same as with `lib.mkOrder`. Smaller values have + a greater priority. + ''; + }; + + }; +} diff --git a/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix b/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix deleted file mode 100644 index c36207d54607f..0000000000000 --- a/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix +++ /dev/null @@ -1,174 +0,0 @@ -# This file defines the options that can be used both for the Apache -# main server configuration, and for the virtual hosts. (The latter -# has additional options that affect the web server as a whole, like -# the user/group to run under.) - -{ forMainServer, lib }: - -with lib; - -{ - - hostName = mkOption { - type = types.str; - default = "localhost"; - description = "Canonical hostname for the server."; - }; - - serverAliases = mkOption { - type = types.listOf types.str; - default = []; - example = ["www.example.org" "www.example.org:8080" "example.org"]; - description = '' - Additional names of virtual hosts served by this virtual host configuration. - ''; - }; - - listen = mkOption { - type = types.listOf (types.submodule ( - { - options = { - port = mkOption { - type = types.int; - description = "port to listen on"; - }; - ip = mkOption { - type = types.str; - default = "*"; - description = "Ip to listen on. 0.0.0.0 for ipv4 only, * for all."; - }; - }; - } )); - description = '' - List of { /* ip: "*"; */ port = 80;} to listen on - ''; - - default = []; - }; - - enableSSL = mkOption { - type = types.bool; - default = false; - description = "Whether to enable SSL (https) support."; - }; - - # Note: sslServerCert and sslServerKey can be left empty, but this - # only makes sense for virtual hosts (they will inherit from the - # main server). - - sslServerCert = mkOption { - type = types.nullOr types.path; - default = null; - example = "/var/host.cert"; - description = "Path to server SSL certificate."; - }; - - sslServerKey = mkOption { - type = types.path; - example = "/var/host.key"; - description = "Path to server SSL certificate key."; - }; - - sslServerChain = mkOption { - type = types.nullOr types.path; - default = null; - example = "/var/ca.pem"; - description = "Path to server SSL chain file."; - }; - - adminAddr = mkOption ({ - type = types.nullOr types.str; - example = "admin@example.org"; - description = "E-mail address of the server administrator."; - } // (if forMainServer then {} else {default = null;})); - - documentRoot = mkOption { - type = types.nullOr types.path; - default = null; - example = "/data/webserver/docs"; - description = '' - The path of Apache's document root directory. If left undefined, - an empty directory in the Nix store will be used as root. - ''; - }; - - servedDirs = mkOption { - type = types.listOf types.attrs; - default = []; - example = [ - { urlPath = "/nix"; - dir = "/home/eelco/Dev/nix-homepage"; - } - ]; - description = '' - This option provides a simple way to serve static directories. - ''; - }; - - servedFiles = mkOption { - type = types.listOf types.attrs; - default = []; - example = [ - { urlPath = "/foo/bar.png"; - file = "/home/eelco/some-file.png"; - } - ]; - description = '' - This option provides a simple way to serve individual, static files. - ''; - }; - - extraConfig = mkOption { - type = types.lines; - default = ""; - example = '' - <Directory /home> - Options FollowSymlinks - AllowOverride All - </Directory> - ''; - description = '' - These lines go to httpd.conf verbatim. They will go after - directories and directory aliases defined by default. - ''; - }; - - enableUserDir = mkOption { - type = types.bool; - default = false; - description = '' - Whether to enable serving <filename>~/public_html</filename> as - <literal>/~<replaceable>username</replaceable></literal>. - ''; - }; - - globalRedirect = mkOption { - type = types.nullOr types.str; - default = null; - example = http://newserver.example.org/; - description = '' - If set, all requests for this host are redirected permanently to - the given URL. - ''; - }; - - logFormat = mkOption { - type = types.str; - default = "common"; - example = "combined"; - description = '' - Log format for Apache's log files. Possible values are: combined, common, referer, agent. - ''; - }; - - robotsEntries = mkOption { - type = types.lines; - default = ""; - example = "Disallow: /foo/"; - description = '' - Specification of pages to be ignored by web crawlers. See <link - xlink:href='http://www.robotstxt.org/'/> for details. - ''; - }; - -} diff --git a/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix b/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix new file mode 100644 index 0000000000000..2e806afb42c52 --- /dev/null +++ b/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix @@ -0,0 +1,275 @@ +{ config, lib, name, ... }: +let + inherit (lib) literalExample mkOption nameValuePair types; +in +{ + options = { + + hostName = mkOption { + type = types.str; + default = name; + description = "Canonical hostname for the server."; + }; + + serverAliases = mkOption { + type = types.listOf types.str; + default = []; + example = ["www.example.org" "www.example.org:8080" "example.org"]; + description = '' + Additional names of virtual hosts served by this virtual host configuration. + ''; + }; + + listen = mkOption { + type = with types; listOf (submodule ({ + options = { + port = mkOption { + type = types.port; + description = "Port to listen on"; + }; + ip = mkOption { + type = types.str; + default = "*"; + description = "IP to listen on. 0.0.0.0 for IPv4 only, * for all."; + }; + ssl = mkOption { + type = types.bool; + default = false; + description = "Whether to enable SSL (https) support."; + }; + }; + })); + default = []; + example = [ + { ip = "195.154.1.1"; port = 443; ssl = true;} + { ip = "192.154.1.1"; port = 80; } + { ip = "*"; port = 8080; } + ]; + description = '' + Listen addresses and ports for this virtual host. + <note><para> + This option overrides <literal>addSSL</literal>, <literal>forceSSL</literal> and <literal>onlySSL</literal>. + </para></note> + ''; + }; + + enableSSL = mkOption { + type = types.bool; + visible = false; + default = false; + }; + + addSSL = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable HTTPS in addition to plain HTTP. This will set defaults for + <literal>listen</literal> to listen on all interfaces on the respective default + ports (80, 443). + ''; + }; + + onlySSL = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable HTTPS and reject plain HTTP connections. This will set + defaults for <literal>listen</literal> to listen on all interfaces on port 443. + ''; + }; + + forceSSL = mkOption { + type = types.bool; + default = false; + description = '' + Whether to add a separate nginx server block that permanently redirects (301) + all plain HTTP traffic to HTTPS. This will set defaults for + <literal>listen</literal> to listen on all interfaces on the respective default + ports (80, 443), where the non-SSL listens are used for the redirect vhosts. + ''; + }; + + enableACME = mkOption { + type = types.bool; + default = false; + description = '' + Whether to ask Let's Encrypt to sign a certificate for this vhost. + Alternately, you can use an existing certificate through <option>useACMEHost</option>. + ''; + }; + + useACMEHost = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + A host of an existing Let's Encrypt certificate to use. + This is useful if you have many subdomains and want to avoid hitting the + <link xlink:href="https://letsencrypt.org/docs/rate-limits/">rate limit</link>. + Alternately, you can generate a certificate through <option>enableACME</option>. + <emphasis>Note that this option does not create any certificates, nor it does add subdomains to existing ones – you will need to create them manually using <xref linkend="opt-security.acme.certs"/>.</emphasis> + ''; + }; + + acmeRoot = mkOption { + type = types.str; + default = "/var/lib/acme/acme-challenges"; + description = "Directory for the acme challenge which is PUBLIC, don't put certs or keys in here"; + }; + + sslServerCert = mkOption { + type = types.path; + example = "/var/host.cert"; + description = "Path to server SSL certificate."; + }; + + sslServerKey = mkOption { + type = types.path; + example = "/var/host.key"; + description = "Path to server SSL certificate key."; + }; + + sslServerChain = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/ca.pem"; + description = "Path to server SSL chain file."; + }; + + http2 = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable HTTP 2. HTTP/2 is supported in all multi-processing modules that come with httpd. <emphasis>However, if you use the prefork mpm, there will + be severe restrictions.</emphasis> Refer to <link xlink:href="https://httpd.apache.org/docs/2.4/howto/http2.html#mpm-config"/> for details. + ''; + }; + + adminAddr = mkOption { + type = types.nullOr types.str; + default = null; + example = "admin@example.org"; + description = "E-mail address of the server administrator."; + }; + + documentRoot = mkOption { + type = types.nullOr types.path; + default = null; + example = "/data/webserver/docs"; + description = '' + The path of Apache's document root directory. If left undefined, + an empty directory in the Nix store will be used as root. + ''; + }; + + servedDirs = mkOption { + type = types.listOf types.attrs; + default = []; + example = [ + { urlPath = "/nix"; + dir = "/home/eelco/Dev/nix-homepage"; + } + ]; + description = '' + This option provides a simple way to serve static directories. + ''; + }; + + servedFiles = mkOption { + type = types.listOf types.attrs; + default = []; + example = [ + { urlPath = "/foo/bar.png"; + file = "/home/eelco/some-file.png"; + } + ]; + description = '' + This option provides a simple way to serve individual, static files. + + <note><para> + This option has been deprecated and will be removed in a future + version of NixOS. You can achieve the same result by making use of + the <literal>locations.<name>.alias</literal> option. + </para></note> + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + example = '' + <Directory /home> + Options FollowSymlinks + AllowOverride All + </Directory> + ''; + description = '' + These lines go to httpd.conf verbatim. They will go after + directories and directory aliases defined by default. + ''; + }; + + enableUserDir = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable serving <filename>~/public_html</filename> as + <literal>/~<replaceable>username</replaceable></literal>. + ''; + }; + + globalRedirect = mkOption { + type = types.nullOr types.str; + default = null; + example = "http://newserver.example.org/"; + description = '' + If set, all requests for this host are redirected permanently to + the given URL. + ''; + }; + + logFormat = mkOption { + type = types.str; + default = "common"; + example = "combined"; + description = '' + Log format for Apache's log files. Possible values are: combined, common, referer, agent. + ''; + }; + + robotsEntries = mkOption { + type = types.lines; + default = ""; + example = "Disallow: /foo/"; + description = '' + Specification of pages to be ignored by web crawlers. See <link + xlink:href='http://www.robotstxt.org/'/> for details. + ''; + }; + + locations = mkOption { + type = with types; attrsOf (submodule (import ./location-options.nix)); + default = {}; + example = literalExample '' + { + "/" = { + proxyPass = "http://localhost:3000"; + }; + "/foo/bar.png" = { + alias = "/home/eelco/some-file.png"; + }; + }; + ''; + description = '' + Declarative location config. See <link + xlink:href="https://httpd.apache.org/docs/2.4/mod/core.html#location"/> for details. + ''; + }; + + }; + + config = { + + locations = builtins.listToAttrs (map (elem: nameValuePair elem.urlPath { alias = elem.file; }) config.servedFiles); + + }; +} diff --git a/nixos/modules/services/web-servers/caddy.nix b/nixos/modules/services/web-servers/caddy.nix index 132c50735d96e..0e6e10a5f47d9 100644 --- a/nixos/modules/services/web-servers/caddy.nix +++ b/nixos/modules/services/web-servers/caddy.nix @@ -64,32 +64,38 @@ in { config = mkIf cfg.enable { systemd.services.caddy = { description = "Caddy web server"; + # upstream unit: https://github.com/caddyserver/caddy/blob/master/dist/init/linux-systemd/caddy.service after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; # systemd-networkd-wait-online.service wantedBy = [ "multi-user.target" ]; environment = mkIf (versionAtLeast config.system.stateVersion "17.09") { CADDYPATH = cfg.dataDir; }; serviceConfig = { ExecStart = '' - ${cfg.package}/bin/caddy -root=/var/tmp -conf=${configFile} \ + ${cfg.package}/bin/caddy -log stdout -log-timestamps=false \ + -root=/var/tmp -conf=${configFile} \ -ca=${cfg.ca} -email=${cfg.email} ${optionalString cfg.agree "-agree"} ''; - ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + ExecReload = "${pkgs.coreutils}/bin/kill -USR1 $MAINPID"; Type = "simple"; User = "caddy"; Group = "caddy"; - Restart = "on-failure"; - StartLimitInterval = 86400; - StartLimitBurst = 5; + Restart = "on-abnormal"; + StartLimitIntervalSec = 14400; + StartLimitBurst = 10; AmbientCapabilities = "cap_net_bind_service"; CapabilityBoundingSet = "cap_net_bind_service"; NoNewPrivileges = true; - LimitNPROC = 64; + LimitNPROC = 512; LimitNOFILE = 1048576; PrivateTmp = true; PrivateDevices = true; ProtectHome = true; ProtectSystem = "full"; ReadWriteDirectories = cfg.dataDir; + KillMode = "mixed"; + KillSignal = "SIGQUIT"; + TimeoutStopSec = "5s"; }; }; diff --git a/nixos/modules/services/web-servers/jboss/default.nix b/nixos/modules/services/web-servers/jboss/default.nix index d28724281a837..3a125982831be 100644 --- a/nixos/modules/services/web-servers/jboss/default.nix +++ b/nixos/modules/services/web-servers/jboss/default.nix @@ -24,6 +24,7 @@ in services.jboss = { enable = mkOption { + type = types.bool; default = false; description = "Whether to enable JBoss. WARNING : this package is outdated and is known to have vulnerabilities."; }; diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix index eb90dae94dfe6..8d49dc66eb1ab 100644 --- a/nixos/modules/services/web-servers/nginx/default.nix +++ b/nixos/modules/services/web-servers/nginx/default.nix @@ -46,8 +46,17 @@ let } '')); + commonHttpConfig = '' + # The mime type definitions included with nginx are very incomplete, so + # we use a list of mime types from the mailcap package, which is also + # used by most other Linux distributions by default. + include ${pkgs.mailcap}/etc/nginx/mime.types; + include ${cfg.package}/conf/fastcgi.conf; + include ${cfg.package}/conf/uwsgi_params; + ''; + configFile = pkgs.writers.writeNginxConfig "nginx.conf" '' - user ${cfg.user} ${cfg.group}; + pid /run/nginx/nginx.pid; error_log ${cfg.logError}; daemon off; @@ -61,12 +70,7 @@ let ${optionalString (cfg.httpConfig == "" && cfg.config == "") '' http { - # The mime type definitions included with nginx are very incomplete, so - # we use a list of mime types from the mailcap package, which is also - # used by most other Linux distributions by default. - include ${pkgs.mailcap}/etc/nginx/mime.types; - include ${cfg.package}/conf/fastcgi.conf; - include ${cfg.package}/conf/uwsgi_params; + ${commonHttpConfig} ${optionalString (cfg.resolver.addresses != []) '' resolver ${toString cfg.resolver.addresses} ${optionalString (cfg.resolver.valid != "") "valid=${cfg.resolver.valid}"} ${optionalString (!cfg.resolver.ipv6) "ipv6=off"}; @@ -79,7 +83,7 @@ let tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; - types_hash_max_size 2048; + types_hash_max_size 4096; ''} ssl_protocols ${cfg.sslProtocols}; @@ -87,10 +91,17 @@ let ${optionalString (cfg.sslDhparam != null) "ssl_dhparam ${cfg.sslDhparam};"} ${optionalString (cfg.recommendedTlsSettings) '' - ssl_session_cache shared:SSL:42m; - ssl_session_timeout 23m; - ssl_ecdh_curve secp384r1; - ssl_prefer_server_ciphers on; + # Keep in sync with https://ssl-config.mozilla.org/#server=nginx&config=intermediate + + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:10m; + # Breaks forward secrecy: https://github.com/mozilla/server-side-tls/issues/135 + ssl_session_tickets off; + # We don't enable insecure ciphers by default, so this allows + # clients to pick the most performant, per https://github.com/mozilla/server-side-tls/issues/260 + ssl_prefer_server_ciphers off; + + # OCSP stapling ssl_stapling on; ssl_stapling_verify on; ''} @@ -165,9 +176,7 @@ let ${optionalString (cfg.httpConfig != "") '' http { - include ${cfg.package}/conf/mime.types; - include ${cfg.package}/conf/fastcgi.conf; - include ${cfg.package}/conf/uwsgi_params; + ${commonHttpConfig} ${cfg.httpConfig} }''} @@ -178,6 +187,8 @@ let then "/etc/nginx/nginx.conf" else configFile; + execCommand = "${cfg.package}/bin/nginx -c '${configPath}' -p '${cfg.stateDir}'"; + vhosts = concatStringsSep "\n" (mapAttrsToList (vhostName: vhost: let onlySSL = vhost.onlySSL || vhost.enableSSL; @@ -366,12 +377,7 @@ in preStart = mkOption { type = types.lines; - default = '' - test -d ${cfg.stateDir}/logs || mkdir -m 750 -p ${cfg.stateDir}/logs - test `stat -c %a ${cfg.stateDir}` = "750" || chmod 750 ${cfg.stateDir} - test `stat -c %a ${cfg.stateDir}/logs` = "750" || chmod 750 ${cfg.stateDir}/logs - chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir} - ''; + default = ""; description = " Shell commands executed before the service's nginx is started. "; @@ -490,8 +496,9 @@ in sslCiphers = mkOption { type = types.str; - default = "EECDH+aRSA+AESGCM:EDH+aRSA:EECDH+aRSA:+AES256:+AES128:+SHA1:!CAMELLIA:!SEED:!3DES:!DES:!RC4:!eNULL"; - description = "Ciphers to choose from when negotiating tls handshakes."; + # Keep in sync with https://ssl-config.mozilla.org/#server=nginx&config=intermediate + default = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; + description = "Ciphers to choose from when negotiating TLS handshakes."; }; sslProtocols = mkOption { @@ -673,23 +680,36 @@ in } ]; + systemd.tmpfiles.rules = [ + "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -" + "d '${cfg.stateDir}/logs' 0750 ${cfg.user} ${cfg.group} - -" + "Z '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -" + ]; + systemd.services.nginx = { description = "Nginx Web Server"; wantedBy = [ "multi-user.target" ]; wants = concatLists (map (vhostConfig: ["acme-${vhostConfig.serverName}.service" "acme-selfsigned-${vhostConfig.serverName}.service"]) acmeEnabledVhosts); after = [ "network.target" ] ++ map (vhostConfig: "acme-selfsigned-${vhostConfig.serverName}.service") acmeEnabledVhosts; stopIfChanged = false; - preStart = - '' + preStart = '' ${cfg.preStart} - ${cfg.package}/bin/nginx -c ${configPath} -p ${cfg.stateDir} -t - ''; + ${execCommand} -t + ''; serviceConfig = { - ExecStart = "${cfg.package}/bin/nginx -c ${configPath} -p ${cfg.stateDir}"; + ExecStart = execCommand; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; Restart = "always"; RestartSec = "10s"; StartLimitInterval = "1min"; + # User and group + User = cfg.user; + Group = cfg.group; + # Runtime directory and mode + RuntimeDirectory = "nginx"; + RuntimeDirectoryMode = "0750"; + # Capabilities + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ]; }; }; @@ -698,11 +718,18 @@ in }; systemd.services.nginx-config-reload = mkIf cfg.enableReload { - wantedBy = [ "nginx.service" ]; + wants = [ "nginx.service" ]; + wantedBy = [ "multi-user.target" ]; restartTriggers = [ configFile ]; + # commented, because can cause extra delays during activate for this config: + # services.nginx.virtualHosts."_".locations."/".proxyPass = "http://blabla:3000"; + # stopIfChanged = false; + serviceConfig.Type = "oneshot"; + serviceConfig.TimeoutSec = 60; script = '' if ${pkgs.systemd}/bin/systemctl -q is-active nginx.service ; then - ${pkgs.systemd}/bin/systemctl reload nginx.service + ${execCommand} -t && \ + ${pkgs.systemd}/bin/systemctl reload nginx.service fi ''; serviceConfig.RemainAfterExit = true; @@ -723,15 +750,16 @@ in listToAttrs acmePairs ); - users.users = optionalAttrs (cfg.user == "nginx") (singleton - { name = "nginx"; + users.users = optionalAttrs (cfg.user == "nginx") { + nginx = { group = cfg.group; uid = config.ids.uids.nginx; - }); + }; + }; + + users.groups = optionalAttrs (cfg.group == "nginx") { + nginx.gid = config.ids.gids.nginx; + }; - users.groups = optionalAttrs (cfg.group == "nginx") (singleton - { name = "nginx"; - gid = config.ids.gids.nginx; - }); }; } diff --git a/nixos/modules/services/web-servers/nginx/gitweb.nix b/nixos/modules/services/web-servers/nginx/gitweb.nix index 272fd1480185e..f7fb07bb79755 100644 --- a/nixos/modules/services/web-servers/nginx/gitweb.nix +++ b/nixos/modules/services/web-servers/nginx/gitweb.nix @@ -3,8 +3,9 @@ with lib; let - cfg = config.services.gitweb; - package = pkgs.gitweb.override (optionalAttrs cfg.gitwebTheme { + cfg = config.services.nginx.gitweb; + gitwebConfig = config.services.gitweb; + package = pkgs.gitweb.override (optionalAttrs gitwebConfig.gitwebTheme { gitwebTheme = true; }); @@ -17,13 +18,45 @@ in default = false; type = types.bool; description = '' - If true, enable gitweb in nginx. Access it at http://yourserver/gitweb + If true, enable gitweb in nginx. + ''; + }; + + location = mkOption { + default = "/gitweb"; + type = types.str; + description = '' + Location to serve gitweb on. + ''; + }; + + user = mkOption { + default = "nginx"; + type = types.str; + description = '' + Existing user that the CGI process will belong to. (Default almost surely will do.) + ''; + }; + + group = mkOption { + default = "nginx"; + type = types.str; + description = '' + Group that the CGI process will belong to. (Set to <literal>config.services.gitolite.group</literal> if you are using gitolite.) + ''; + }; + + virtualHost = mkOption { + default = "_"; + type = types.str; + description = '' + VirtualHost to serve gitweb on. Default is catch-all. ''; }; }; - config = mkIf config.services.nginx.gitweb.enable { + config = mkIf cfg.enable { systemd.services.gitweb = { description = "GitWeb service"; @@ -32,22 +65,22 @@ in FCGI_SOCKET_PATH = "/run/gitweb/gitweb.sock"; }; serviceConfig = { - User = "nginx"; - Group = "nginx"; + User = cfg.user; + Group = cfg.group; RuntimeDirectory = [ "gitweb" ]; }; wantedBy = [ "multi-user.target" ]; }; services.nginx = { - virtualHosts.default = { - locations."/gitweb/static/" = { + virtualHosts.${cfg.virtualHost} = { + locations."${cfg.location}/static/" = { alias = "${package}/static/"; }; - locations."/gitweb/" = { + locations."${cfg.location}/" = { extraConfig = '' include ${pkgs.nginx}/conf/fastcgi_params; - fastcgi_param GITWEB_CONFIG ${cfg.gitwebConfigFile}; + fastcgi_param GITWEB_CONFIG ${gitwebConfig.gitwebConfigFile}; fastcgi_pass unix:/run/gitweb/gitweb.sock; ''; }; diff --git a/nixos/modules/services/web-servers/nginx/location-options.nix b/nixos/modules/services/web-servers/nginx/location-options.nix index 2b3749d8a7446..3d9e391ecf20f 100644 --- a/nixos/modules/services/web-servers/nginx/location-options.nix +++ b/nixos/modules/services/web-servers/nginx/location-options.nix @@ -67,7 +67,7 @@ with lib; return = mkOption { type = types.nullOr types.str; default = null; - example = "301 http://example.com$request_uri;"; + example = "301 http://example.com$request_uri"; description = '' Adds a return directive, for e.g. redirections. ''; diff --git a/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixos/modules/services/web-servers/nginx/vhost-options.nix index 7e488f33a4199..455854e2a9655 100644 --- a/nixos/modules/services/web-servers/nginx/vhost-options.nix +++ b/nixos/modules/services/web-servers/nginx/vhost-options.nix @@ -207,6 +207,7 @@ with lib; default = null; description = '' Basic Auth password file for a vhost. + Can be created via: <command>htpasswd -c <filename> <username></command> ''; }; diff --git a/nixos/modules/services/web-servers/phpfpm/default.nix b/nixos/modules/services/web-servers/phpfpm/default.nix index 7698f8c3a2638..d090885a8ca52 100644 --- a/nixos/modules/services/web-servers/phpfpm/default.nix +++ b/nixos/modules/services/web-servers/phpfpm/default.nix @@ -47,6 +47,7 @@ let Path to the unix socket file on which to accept FastCGI requests. <note><para>This option is read-only and managed by NixOS.</para></note> ''; + example = "${runtimeDir}/<name>.sock"; }; listen = mkOption { @@ -146,6 +147,10 @@ let }; in { + imports = [ + (mkRemovedOptionModule [ "services" "phpfpm" "poolConfigs" ] "Use services.phpfpm.pools instead.") + (mkRemovedOptionModule [ "services" "phpfpm" "phpIni" ] "") + ]; options = { services.phpfpm = { @@ -204,14 +209,14 @@ in { user = "php"; group = "php"; phpPackage = pkgs.php; - settings = ''' + settings = { "pm" = "dynamic"; "pm.max_children" = 75; "pm.start_servers" = 10; "pm.min_spare_servers" = 5; "pm.max_spare_servers" = 20; "pm.max_requests" = 500; - '''; + }; } }''; description = '' diff --git a/nixos/modules/services/web-servers/tomcat.nix b/nixos/modules/services/web-servers/tomcat.nix index 68261c50324d0..6d12925829f78 100644 --- a/nixos/modules/services/web-servers/tomcat.nix +++ b/nixos/modules/services/web-servers/tomcat.nix @@ -194,14 +194,10 @@ in config = mkIf config.services.tomcat.enable { - users.groups = singleton - { name = "tomcat"; - gid = config.ids.gids.tomcat; - }; + users.groups.tomcat.gid = config.ids.gids.tomcat; - users.users = singleton - { name = "tomcat"; - uid = config.ids.uids.tomcat; + users.users.tomcat = + { uid = config.ids.uids.tomcat; description = "Tomcat user"; home = "/homeless-shelter"; extraGroups = cfg.extraGroups; diff --git a/nixos/modules/services/web-servers/traefik.nix b/nixos/modules/services/web-servers/traefik.nix index 5b0fc467ea469..4ab7307c3b671 100644 --- a/nixos/modules/services/web-servers/traefik.nix +++ b/nixos/modules/services/web-servers/traefik.nix @@ -4,56 +4,102 @@ with lib; let cfg = config.services.traefik; - 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; - + jsonValue = with types; + let + valueType = nullOr (oneOf [ + bool + int + float + str + (lazyAttrsOf valueType) + (listOf valueType) + ]) // { + description = "JSON value"; + emptyValue.value = { }; + }; + in valueType; + dynamicConfigFile = if cfg.dynamicConfigFile == null then + pkgs.runCommand "config.toml" { + buildInputs = [ pkgs.remarshal ]; + preferLocalBuild = true; + } '' + remarshal -if json -of toml \ + < ${ + pkgs.writeText "dynamic_config.json" + (builtins.toJSON cfg.dynamicConfigOptions) + } \ + > $out + '' + else + cfg.dynamicConfigFile; + staticConfigFile = if cfg.staticConfigFile == null then + pkgs.runCommand "config.toml" { + buildInputs = [ pkgs.yj ]; + preferLocalBuild = true; + } '' + yj -jt -i \ + < ${ + pkgs.writeText "static_config.json" (builtins.toJSON + (recursiveUpdate cfg.staticConfigOptions { + providers.file.filename = "${dynamicConfigFile}"; + })) + } \ + > $out + '' + else + cfg.staticConfigFile; in { options.services.traefik = { enable = mkEnableOption "Traefik web server"; - configFile = mkOption { + staticConfigFile = mkOption { default = null; - example = literalExample "/path/to/config.toml"; + example = literalExample "/path/to/static_config.toml"; type = types.nullOr types.path; description = '' - Path to verbatim traefik.toml to use. - (Using that option has precedence over <literal>configOptions</literal>) + Path to traefik's static configuration to use. + (Using that option has precedence over <literal>staticConfigOptions</literal> and <literal>dynamicConfigOptions</literal>) ''; }; - configOptions = mkOption { + staticConfigOptions = mkOption { description = '' - Config for Traefik. + Static configuration for Traefik. ''; - type = types.attrs; - default = { - defaultEntryPoints = ["http"]; - entryPoints.http.address = ":80"; - }; + type = jsonValue; + default = { entryPoints.http.address = ":80"; }; example = { - defaultEntrypoints = [ "http" ]; - web.address = ":8080"; + entryPoints.web.address = ":8080"; entryPoints.http.address = ":80"; - file = {}; - frontends = { - frontend1 = { - backend = "backend1"; - routes.test_1.rule = "Host:localhost"; - }; - }; - backends.backend1 = { - servers.server1.url = "http://localhost:8000"; + api = { }; + }; + }; + + dynamicConfigFile = mkOption { + default = null; + example = literalExample "/path/to/dynamic_config.toml"; + type = types.nullOr types.path; + description = '' + Path to traefik's dynamic configuration to use. + (Using that option has precedence over <literal>dynamicConfigOptions</literal>) + ''; + }; + + dynamicConfigOptions = mkOption { + description = '' + Dynamic configuration for Traefik. + ''; + type = jsonValue; + default = { }; + example = { + http.routers.router1 = { + rule = "Host(`localhost`)"; + service = "service1"; }; + + http.services.service1.loadBalancer.servers = + [{ url = "http://localhost:8080"; }]; }; }; @@ -61,7 +107,7 @@ in { default = "/var/lib/traefik"; type = types.path; description = '' - Location for any persistent data traefik creates, ie. acme + Location for any persistent data traefik creates, ie. acme ''; }; @@ -84,16 +130,15 @@ in { }; config = mkIf cfg.enable { - systemd.tmpfiles.rules = [ - "d '${cfg.dataDir}' 0700 traefik traefik - -" - ]; + systemd.tmpfiles.rules = [ "d '${cfg.dataDir}' 0700 traefik traefik - -" ]; systemd.services.traefik = { description = "Traefik web server"; after = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { - ExecStart = ''${cfg.package.bin}/bin/traefik --configfile=${configFile}''; + ExecStart = + "${cfg.package}/bin/traefik --configfile=${staticConfigFile}"; Type = "simple"; User = "traefik"; Group = cfg.group; @@ -120,6 +165,6 @@ in { isSystemUser = true; }; - users.groups.traefik = {}; + users.groups.traefik = { }; }; } diff --git a/nixos/modules/services/web-servers/unit/default.nix b/nixos/modules/services/web-servers/unit/default.nix index 32f6d475b34ef..f8a18954fc99b 100644 --- a/nixos/modules/services/web-servers/unit/default.nix +++ b/nixos/modules/services/web-servers/unit/default.nix @@ -85,7 +85,7 @@ in { systemd.tmpfiles.rules = [ "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -" "d '${cfg.logDir}' 0750 ${cfg.user} ${cfg.group} - -" - ]; + ]; systemd.services.unit = { description = "Unit App Server"; @@ -93,34 +93,52 @@ in { wantedBy = [ "multi-user.target" ]; path = with pkgs; [ curl ]; preStart = '' - test -f '/run/unit/control.unit.sock' || rm -f '/run/unit/control.unit.sock' + test -f '${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' ''; serviceConfig = { - User = cfg.user; - Group = cfg.group; - AmbientCapabilities = "CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID"; - CapabilityBoundingSet = "CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID"; 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 \ --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" ]; + # Security + NoNewPrivileges = true; + # Sandboxing + ProtectSystem = "full"; + ProtectHome = true; RuntimeDirectory = "unit"; RuntimeDirectoryMode = "0750"; + PrivateTmp = true; + PrivateDevices = true; + ProtectHostname = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + RestrictRealtime = true; + PrivateMounts = true; }; }; - users.users = optionalAttrs (cfg.user == "unit") (singleton { - name = "unit"; - group = cfg.group; - isSystemUser = true; - }); + users.users = optionalAttrs (cfg.user == "unit") { + unit = { + group = cfg.group; + isSystemUser = true; + }; + }; + + users.groups = optionalAttrs (cfg.group == "unit") { + unit = { }; + }; - users.groups = optionalAttrs (cfg.group == "unit") (singleton { - name = "unit"; - }); }; } diff --git a/nixos/modules/services/web-servers/uwsgi.nix b/nixos/modules/services/web-servers/uwsgi.nix index af70f32f32d0f..4b74c329e3dc0 100644 --- a/nixos/modules/services/web-servers/uwsgi.nix +++ b/nixos/modules/services/web-servers/uwsgi.nix @@ -5,10 +5,6 @@ with lib; let cfg = config.services.uwsgi; - uwsgi = pkgs.uwsgi.override { - plugins = cfg.plugins; - }; - buildCfg = name: c: let plugins = @@ -23,8 +19,8 @@ let python = if hasPython2 && hasPython3 then throw "`plugins` attribute in UWSGI configuration shouldn't contain both python2 and python3" - else if hasPython2 then uwsgi.python2 - else if hasPython3 then uwsgi.python3 + else if hasPython2 then cfg.package.python2 + else if hasPython3 then cfg.package.python3 else null; pythonEnv = python.withPackages (c.pythonPackages or (self: [])); @@ -36,7 +32,7 @@ let inherit plugins; } // removeAttrs c [ "type" "pythonPackages" ] // optionalAttrs (python != null) { - pythonpath = "${pythonEnv}/${python.sitePackages}"; + pyhome = "${pythonEnv}"; env = # Argh, uwsgi expects list of key-values there instead of a dictionary. let env' = c.env or []; @@ -77,6 +73,11 @@ in { description = "Where uWSGI communication sockets can live"; }; + package = mkOption { + type = types.package; + internal = true; + }; + instance = mkOption { type = types.attrs; default = { @@ -138,7 +139,7 @@ in { ''; serviceConfig = { Type = "notify"; - ExecStart = "${uwsgi}/bin/uwsgi --uid ${cfg.user} --gid ${cfg.group} --json ${buildCfg "server" cfg.instance}/server.json"; + ExecStart = "${cfg.package}/bin/uwsgi --uid ${cfg.user} --gid ${cfg.group} --json ${buildCfg "server" cfg.instance}/server.json"; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID"; NotifyAccess = "main"; @@ -146,15 +147,19 @@ in { }; }; - users.users = optionalAttrs (cfg.user == "uwsgi") (singleton - { name = "uwsgi"; + users.users = optionalAttrs (cfg.user == "uwsgi") { + uwsgi = { group = cfg.group; uid = config.ids.uids.uwsgi; - }); + }; + }; + + users.groups = optionalAttrs (cfg.group == "uwsgi") { + uwsgi.gid = config.ids.gids.uwsgi; + }; - users.groups = optionalAttrs (cfg.group == "uwsgi") (singleton - { name = "uwsgi"; - gid = config.ids.gids.uwsgi; - }); + services.uwsgi.package = pkgs.uwsgi.override { + inherit (cfg) plugins; + }; }; } diff --git a/nixos/modules/services/x11/desktop-managers/cde.nix b/nixos/modules/services/x11/desktop-managers/cde.nix new file mode 100644 index 0000000000000..c1b6d3bf064ad --- /dev/null +++ b/nixos/modules/services/x11/desktop-managers/cde.nix @@ -0,0 +1,55 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + xcfg = config.services.xserver; + cfg = xcfg.desktopManager.cde; +in { + options.services.xserver.desktopManager.cde = { + enable = mkEnableOption "Common Desktop Environment"; + }; + + config = mkIf (xcfg.enable && cfg.enable) { + services.rpcbind.enable = true; + + services.xinetd.enable = true; + services.xinetd.services = [ + { + name = "cmsd"; + protocol = "udp"; + user = "root"; + server = "${pkgs.cdesktopenv}/opt/dt/bin/rpc.cmsd"; + extraConfig = '' + type = RPC UNLISTED + rpc_number = 100068 + rpc_version = 2-5 + only_from = 127.0.0.1/0 + ''; + } + ]; + + users.groups.mail = {}; + security.wrappers = { + dtmail = { + source = "${pkgs.cdesktopenv}/bin/dtmail"; + group = "mail"; + setgid = true; + }; + }; + + system.activationScripts.setup-cde = '' + mkdir -p /var/dt/{tmp,appconfig/appmanager} + chmod a+w+t /var/dt/{tmp,appconfig/appmanager} + ''; + + services.xserver.desktopManager.session = [ + { name = "CDE"; + start = '' + exec ${pkgs.cdesktopenv}/opt/dt/bin/Xsession + ''; + }]; + }; + + meta.maintainers = [ maintainers.gnidorah ]; +} diff --git a/nixos/modules/services/x11/desktop-managers/default.nix b/nixos/modules/services/x11/desktop-managers/default.nix index 671a959cdde1f..ea6aac9f6c92f 100644 --- a/nixos/modules/services/x11/desktop-managers/default.nix +++ b/nixos/modules/services/x11/desktop-managers/default.nix @@ -20,7 +20,7 @@ in imports = [ ./none.nix ./xterm.nix ./xfce.nix ./plasma5.nix ./lumina.nix ./lxqt.nix ./enlightenment.nix ./gnome3.nix ./kodi.nix ./maxx.nix - ./mate.nix ./pantheon.nix ./surf-display.nix + ./mate.nix ./pantheon.nix ./surf-display.nix ./cde.nix ]; options = { @@ -68,46 +68,31 @@ in scripts before forwarding the value to the <varname>displayManager</varname>. ''; - apply = list: { - list = map (d: d // { - manage = "desktop"; - start = d.start - + 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 - else - # Use a solid black background as fallback - ${pkgs.xorg.xsetroot}/bin/xsetroot -solid black - fi - ''; - }) list; - needBGPackages = [] != filter needBGCond list; - }; + apply = map (d: d // { + manage = "desktop"; + start = d.start + + 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 + ''; + }); }; default = mkOption { - type = types.str; - default = ""; + type = types.nullOr types.str; + default = null; example = "none"; - description = "Default desktop manager loaded if none have been chosen."; - apply = defaultDM: - if defaultDM == "" && cfg.session.list != [] then - (head cfg.session.list).name - else if any (w: w.name == defaultDM) cfg.session.list then - defaultDM - else - builtins.trace '' - Default desktop manager (${defaultDM}) not found at evaluation time. - These are the known valid session names: - ${concatMapStringsSep "\n " (w: "services.xserver.desktopManager.default = \"${w.name}\";") cfg.session.list} - It's also possible the default can be found in one of these packages: - ${concatMapStringsSep "\n " (p: p.name) config.services.xserver.displayManager.extraSessionFilePackages} - '' defaultDM; + description = '' + <emphasis role="strong">Deprecated</emphasis>, please use <xref linkend="opt-services.xserver.displayManager.defaultSession"/> instead. + + Default desktop manager loaded if none have been chosen. + ''; }; }; }; - config.services.xserver.displayManager.session = cfg.session.list; + config.services.xserver.displayManager.session = cfg.session; } diff --git a/nixos/modules/services/x11/desktop-managers/enlightenment.nix b/nixos/modules/services/x11/desktop-managers/enlightenment.nix index 3745069f6eaf9..1690a7d51a888 100644 --- a/nixos/modules/services/x11/desktop-managers/enlightenment.nix +++ b/nixos/modules/services/x11/desktop-managers/enlightenment.nix @@ -16,9 +16,14 @@ let in { + imports = [ + (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "e19" "enable" ] [ "services" "xserver" "desktopManager" "enlightenment" "enable" ]) + ]; + options = { services.xserver.desktopManager.enlightenment.enable = mkOption { + type = types.bool; default = false; description = "Enable the Enlightenment desktop environment."; }; @@ -34,7 +39,7 @@ in pkgs.gtk2 # To get GTK's themes. pkgs.tango-icon-theme - pkgs.gnome2.gnome_icon_theme + pkgs.gnome-icon-theme pkgs.xorg.xcursorthemes ]; @@ -64,10 +69,7 @@ in security.wrappers = (import "${e.enlightenment}/e-wrappers.nix").security.wrappers; - environment.etc = singleton - { source = xcfg.xkbDir; - target = "X11/xkb"; - }; + environment.etc."X11/xkb".source = xcfg.xkbDir; fonts.fonts = [ pkgs.dejavu_fonts pkgs.ubuntu_font_family ]; diff --git a/nixos/modules/services/x11/desktop-managers/gnome3.nix b/nixos/modules/services/x11/desktop-managers/gnome3.nix index 6725595e1cfdd..bbc7feb2d0496 100644 --- a/nixos/modules/services/x11/desktop-managers/gnome3.nix +++ b/nixos/modules/services/x11/desktop-managers/gnome3.nix @@ -57,6 +57,10 @@ in { + meta = { + maintainers = teams.gnome.members; + }; + options = { services.gnome3 = { @@ -68,6 +72,7 @@ in services.xserver.desktopManager.gnome3 = { enable = mkOption { + type = types.bool; default = false; description = "Enable Gnome 3 desktop manager."; }; @@ -144,7 +149,7 @@ in services.gnome3.core-shell.enable = true; services.gnome3.core-utilities.enable = mkDefault true; - services.xserver.displayManager.extraSessionFilePackages = [ pkgs.gnome3.gnome-session ]; + services.xserver.displayManager.sessionPackages = [ pkgs.gnome3.gnome-session.sessions ]; environment.extraInit = '' ${concatMapStrings (p: '' @@ -171,7 +176,7 @@ in }) (mkIf flashbackEnabled { - services.xserver.displayManager.extraSessionFilePackages = map + services.xserver.displayManager.sessionPackages = map (wm: pkgs.gnome3.gnome-flashback.mkSessionForWm { inherit (wm) wmName wmLabel wmCommand; }) (optional cfg.flashback.enableMetacity { @@ -180,7 +185,7 @@ in wmCommand = "${pkgs.gnome3.metacity}/bin/metacity"; } ++ cfg.flashback.customSessions); - security.pam.services.gnome-screensaver = { + security.pam.services.gnome-flashback = { enableGnomeKeyring = true; }; @@ -191,9 +196,10 @@ in inherit (wm) wmName; }) cfg.flashback.customSessions); - services.dbus.packages = [ - pkgs.gnome3.gnome-screensaver - ]; + # gnome-panel needs these for menu applet + environment.sessionVariables.XDG_DATA_DIRS = [ "${pkgs.gnome3.gnome-flashback}/share" ]; + # TODO: switch to sessionVariables (resolve conflict) + environment.variables.XDG_CONFIG_DIRS = [ "${pkgs.gnome3.gnome-flashback}/etc/xdg" ]; }) (mkIf serviceCfg.core-os-services.enable { @@ -249,11 +255,16 @@ in services.system-config-printer.enable = (mkIf config.services.printing.enable (mkDefault true)); services.telepathy.enable = mkDefault true; - systemd.packages = with pkgs.gnome3; [ vino gnome-session ]; + systemd.packages = with pkgs.gnome3; [ + gnome-session + gnome-shell + ]; services.avahi.enable = mkDefault true; - xdg.portal.extraPortals = [ pkgs.gnome3.gnome-shell ]; + xdg.portal.extraPortals = [ + pkgs.gnome3.gnome-shell + ]; services.geoclue2.enable = mkDefault true; services.geoclue2.enableDemoAgent = false; # GNOME has its own geoclue agent @@ -298,7 +309,7 @@ in environment = mkForce {}; }; - # Adapt from https://gitlab.gnome.org/GNOME/gnome-build-meta/blob/gnome-3-32/elements/core/meta-gnome-core-shell.bst + # Adapt from https://gitlab.gnome.org/GNOME/gnome-build-meta/blob/gnome-3-36/elements/core/meta-gnome-core-shell.bst environment.systemPackages = with pkgs.gnome3; [ adwaita-icon-theme gnome-backgrounds @@ -317,18 +328,16 @@ in pkgs.hicolor-icon-theme pkgs.shared-mime-info # for update-mime-database pkgs.xdg-user-dirs # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/ - vino ]; }) - # Adapt from https://gitlab.gnome.org/GNOME/gnome-build-meta/blob/gnome-3-32/elements/core/meta-gnome-core-utilities.bst + # Adapt from https://gitlab.gnome.org/GNOME/gnome-build-meta/blob/gnome-3-36/elements/core/meta-gnome-core-utilities.bst (mkIf serviceCfg.core-utilities.enable { environment.systemPackages = (with pkgs.gnome3; removePackagesByName [ baobab cheese eog epiphany - geary gedit gnome-calculator gnome-calendar @@ -355,6 +364,7 @@ in # Enable default programs programs.evince.enable = mkDefault true; programs.file-roller.enable = mkDefault true; + programs.geary.enable = mkDefault true; programs.gnome-disks.enable = mkDefault true; programs.gnome-terminal.enable = mkDefault true; programs.seahorse.enable = mkDefault true; diff --git a/nixos/modules/services/x11/desktop-managers/kodi.nix b/nixos/modules/services/x11/desktop-managers/kodi.nix index 65a7b9c628e52..bdae9c3afdb76 100644 --- a/nixos/modules/services/x11/desktop-managers/kodi.nix +++ b/nixos/modules/services/x11/desktop-managers/kodi.nix @@ -10,6 +10,7 @@ in options = { services.xserver.desktopManager.kodi = { enable = mkOption { + type = types.bool; default = false; description = "Enable the kodi multimedia center."; }; @@ -20,7 +21,7 @@ in services.xserver.desktopManager.session = [{ name = "kodi"; start = '' - ${pkgs.kodi}/bin/kodi --lircdev /run/lirc/lircd --standalone & + LIRC_SOCKET_PATH=/run/lirc/lircd ${pkgs.kodi}/bin/kodi --standalone & waitPID=$! ''; }]; diff --git a/nixos/modules/services/x11/desktop-managers/mate.nix b/nixos/modules/services/x11/desktop-managers/mate.nix index fe63f36cf96a2..f236c14fcf3e9 100644 --- a/nixos/modules/services/x11/desktop-managers/mate.nix +++ b/nixos/modules/services/x11/desktop-managers/mate.nix @@ -44,35 +44,35 @@ in config = mkIf cfg.enable { - services.xserver.desktopManager.session = singleton { - name = "mate"; - bgSupport = true; - start = '' - export XDG_MENU_PREFIX=mate- + services.xserver.displayManager.sessionPackages = [ + pkgs.mate.mate-session-manager + ]; - # Let caja find extensions - export CAJA_EXTENSION_DIRS=$CAJA_EXTENSION_DIRS''${CAJA_EXTENSION_DIRS:+:}${config.system.path}/lib/caja/extensions-2.0 + services.xserver.displayManager.sessionCommands = '' + if test "$XDG_CURRENT_DESKTOP" = "MATE"; then + export XDG_MENU_PREFIX=mate- - # Let caja extensions find gsettings schemas - ${concatMapStrings (p: '' + # Let caja find extensions + export CAJA_EXTENSION_DIRS=$CAJA_EXTENSION_DIRS''${CAJA_EXTENSION_DIRS:+:}${config.system.path}/lib/caja/extensions-2.0 + + # Let caja extensions find gsettings schemas + ${concatMapStrings (p: '' if [ -d "${p}/lib/caja/extensions-2.0" ]; then - ${addToXDGDirs p} + ${addToXDGDirs p} fi - '') - config.environment.systemPackages - } + '') config.environment.systemPackages} - # Let mate-panel find applets - export MATE_PANEL_APPLETS_DIR=$MATE_PANEL_APPLETS_DIR''${MATE_PANEL_APPLETS_DIR:+:}${config.system.path}/share/mate-panel/applets - export MATE_PANEL_EXTRA_MODULES=$MATE_PANEL_EXTRA_MODULES''${MATE_PANEL_EXTRA_MODULES:+:}${config.system.path}/lib/mate-panel/applets + # Add mate-control-center paths to some XDG variables because its schemas are needed by mate-settings-daemon, and mate-settings-daemon is a dependency for mate-control-center (that is, they are mutually recursive) + ${addToXDGDirs pkgs.mate.mate-control-center} + fi + ''; - # Add mate-control-center paths to some XDG variables because its schemas are needed by mate-settings-daemon, and mate-settings-daemon is a dependency for mate-control-center (that is, they are mutually recursive) - ${addToXDGDirs pkgs.mate.mate-control-center} + # Let mate-panel find applets + environment.sessionVariables."MATE_PANEL_APPLETS_DIR" = "${config.system.path}/share/mate-panel/applets"; + environment.sessionVariables."MATE_PANEL_EXTRA_MODULES" = "${config.system.path}/lib/mate-panel/applets"; - ${pkgs.mate.mate-session-manager}/bin/mate-session ${optionalString cfg.debug "--debug"} & - waitPID=$! - ''; - }; + # Debugging + environment.sessionVariables.MATE_SESSION_DEBUG = mkIf cfg.debug "1"; environment.systemPackages = pkgs.mate.basePackages ++ @@ -86,6 +86,7 @@ in pkgs.shared-mime-info pkgs.xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/ pkgs.mate.mate-settings-daemon + pkgs.yelp # for 'Contents' in 'Help' menus ]; programs.dconf.enable = true; @@ -98,7 +99,6 @@ in services.gnome3.at-spi2-core.enable = true; services.gnome3.gnome-keyring.enable = true; - services.gnome3.gnome-settings-daemon.enable = true; services.udev.packages = [ pkgs.mate.mate-settings-daemon ]; services.gvfs.enable = true; services.upower.enable = config.powerManagement.enable; diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix index 80dab135ee263..01fe230b8a433 100644 --- a/nixos/modules/services/x11/desktop-managers/pantheon.nix +++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix @@ -5,6 +5,7 @@ with lib; let cfg = config.services.xserver.desktopManager.pantheon; + serviceCfg = config.services.pantheon; nixos-gsettings-desktop-schemas = pkgs.pantheon.elementary-gsettings-schemas.override { extraGSettingsOverridePackages = cfg.extraGSettingsOverridePackages; @@ -15,10 +16,23 @@ in { - meta.maintainers = pkgs.pantheon.maintainers; + meta = { + doc = ./pantheon.xml; + maintainers = pkgs.pantheon.maintainers; + }; options = { + services.pantheon = { + + contractor = { + enable = mkEnableOption "contractor, a desktop-wide extension service used by Pantheon"; + }; + + apps.enable = mkEnableOption "Pantheon default applications"; + + }; + services.xserver.desktopManager.pantheon = { enable = mkOption { type = types.bool; @@ -41,6 +55,18 @@ in ]; }; + extraWingpanelIndicators = mkOption { + default = null; + type = with types; nullOr (listOf package); + description = "Indicators to add to Wingpanel."; + }; + + extraSwitchboardPlugs = mkOption { + default = null; + type = with types; nullOr (listOf package); + description = "Plugs to add to Switchboard."; + }; + extraGSettingsOverrides = mkOption { default = ""; type = types.lines; @@ -67,148 +93,204 @@ in }; - config = mkIf cfg.enable { + config = mkMerge [ + (mkIf cfg.enable { - services.xserver.displayManager.extraSessionFilePackages = [ pkgs.pantheon.elementary-session-settings ]; + services.xserver.displayManager.sessionPackages = [ pkgs.pantheon.elementary-session-settings ]; - # Ensure lightdm is used when Pantheon is enabled - # Without it screen locking will be nonfunctional because of the use of lightlocker + # Ensure lightdm is used when Pantheon is enabled + # Without it screen locking will be nonfunctional because of the use of lightlocker + warnings = optional (config.services.xserver.displayManager.lightdm.enable != true) + '' + Using Pantheon without LightDM as a displayManager will break screenlocking from the UI. + ''; - warnings = optional (config.services.xserver.displayManager.lightdm.enable != true) - '' - Using Pantheon without LightDM as a displayManager will break screenlocking from the UI. + services.xserver.displayManager.lightdm.greeters.pantheon.enable = mkDefault true; + + # 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.sessionCommands = '' + if test "$XDG_CURRENT_DESKTOP" = "Pantheon"; then + ${concatMapStrings (p: '' + if [ -d "${p}/share/gsettings-schemas/${p.name}" ]; then + export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${p}/share/gsettings-schemas/${p.name} + fi + + if [ -d "${p}/lib/girepository-1.0" ]; then + export GI_TYPELIB_PATH=$GI_TYPELIB_PATH''${GI_TYPELIB_PATH:+:}${p}/lib/girepository-1.0 + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}${p}/lib + fi + '') cfg.sessionPath} + fi ''; - services.xserver.displayManager.lightdm.greeters.pantheon.enable = mkDefault true; - - # If not set manually Pantheon session cannot be started - # Known issue of https://github.com/NixOS/nixpkgs/pull/43992 - services.xserver.desktopManager.default = mkForce "pantheon"; - - services.xserver.displayManager.sessionCommands = '' - if test "$XDG_CURRENT_DESKTOP" = "Pantheon"; then - ${concatMapStrings (p: '' - if [ -d "${p}/share/gsettings-schemas/${p.name}" ]; then - export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${p}/share/gsettings-schemas/${p.name} - fi - - if [ -d "${p}/lib/girepository-1.0" ]; then - export GI_TYPELIB_PATH=$GI_TYPELIB_PATH''${GI_TYPELIB_PATH:+:}${p}/lib/girepository-1.0 - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}${p}/lib - fi - '') cfg.sessionPath} - fi - ''; - - hardware.bluetooth.enable = mkDefault true; - hardware.pulseaudio.enable = mkDefault true; - security.polkit.enable = true; - services.accounts-daemon.enable = true; - services.bamf.enable = true; - services.colord.enable = mkDefault true; - services.pantheon.files.enable = mkDefault true; - services.tumbler.enable = mkDefault true; - services.system-config-printer.enable = (mkIf config.services.printing.enable (mkDefault true)); - services.dbus.packages = with pkgs.pantheon; [ - switchboard-plug-power - elementary-default-settings - ]; - services.pantheon.contractor.enable = mkDefault true; - services.gnome3.at-spi2-core.enable = true; - services.gnome3.evolution-data-server.enable = true; - services.gnome3.glib-networking.enable = true; - # TODO: gnome-keyring's xdg autostarts will still be in the environment (from elementary-session-settings) if disabled forcefully - services.gnome3.gnome-keyring.enable = true; - services.gnome3.gnome-settings-daemon.enable = true; - services.udev.packages = [ pkgs.pantheon.elementary-settings-daemon ]; - services.gvfs.enable = true; - services.gnome3.rygel.enable = mkDefault true; - services.gsignond.enable = mkDefault true; - services.gsignond.plugins = with pkgs.gsignondPlugins; [ lastfm mail oauth ]; - services.udisks2.enable = true; - services.upower.enable = config.powerManagement.enable; - services.xserver.libinput.enable = mkDefault true; - services.xserver.updateDbusEnvironment = true; - services.zeitgeist.enable = mkDefault true; - services.geoclue2.enable = mkDefault true; - # pantheon has pantheon-agent-geoclue2 - services.geoclue2.enableDemoAgent = false; - services.geoclue2.appConfig."io.elementary.desktop.agent-geoclue2" = { - isAllowed = true; - isSystem = true; - }; - - programs.dconf.enable = true; - programs.evince.enable = mkDefault true; - programs.file-roller.enable = mkDefault true; - # Otherwise you can't store NetworkManager Secrets with - # "Store the password only for this user" - programs.nm-applet.enable = true; - - # Shell integration for VTE terminals - programs.bash.vteIntegration = mkDefault true; - programs.zsh.vteIntegration = mkDefault true; - - # Harmonize Qt5 applications under Pantheon - qt5.enable = true; - qt5.platformTheme = "gnome"; - qt5.style = "adwaita"; - - networking.networkmanager.enable = mkDefault true; - - # Override GSettings schemas - environment.sessionVariables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-desktop-schemas}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas"; - - environment.sessionVariables.GNOME_SESSION_DEBUG = optionalString cfg.debug "1"; - - # Settings from elementary-default-settings - environment.sessionVariables.GTK_CSD = "1"; - environment.sessionVariables.GTK_MODULES = "pantheon-filechooser-module"; - environment.etc."gtk-3.0/settings.ini".source = "${pkgs.pantheon.elementary-default-settings}/etc/gtk-3.0/settings.ini"; - - environment.pathsToLink = [ - # FIXME: modules should link subdirs of `/share` rather than relying on this - "/share" - ]; - - environment.systemPackages = - pkgs.pantheon.artwork ++ pkgs.pantheon.desktop ++ pkgs.pantheon.services ++ cfg.sessionPath - ++ (with pkgs; gnome3.removePackagesByName - ([ - gnome3.geary - gnome3.epiphany - gnome3.gnome-font-viewer - ] ++ pantheon.apps) config.environment.pantheon.excludePackages) - ++ (with pkgs; - [ - adwaita-qt + # Default services + hardware.bluetooth.enable = mkDefault true; + hardware.pulseaudio.enable = mkDefault true; + security.polkit.enable = true; + services.accounts-daemon.enable = true; + services.bamf.enable = true; + services.colord.enable = mkDefault true; + services.tumbler.enable = mkDefault true; + services.system-config-printer.enable = (mkIf config.services.printing.enable (mkDefault true)); + services.dbus.packages = with pkgs.pantheon; [ + switchboard-plug-power + elementary-default-settings # accountsservice extensions + ]; + services.pantheon.apps.enable = mkDefault true; + services.pantheon.contractor.enable = mkDefault true; + services.gnome3.at-spi2-core.enable = true; + services.gnome3.evolution-data-server.enable = true; + services.gnome3.glib-networking.enable = true; + services.gnome3.gnome-keyring.enable = true; + services.gvfs.enable = true; + services.gnome3.rygel.enable = mkDefault true; + services.gsignond.enable = mkDefault true; + services.gsignond.plugins = with pkgs.gsignondPlugins; [ lastfm mail oauth ]; + services.udisks2.enable = true; + services.upower.enable = config.powerManagement.enable; + services.xserver.libinput.enable = mkDefault true; + services.xserver.updateDbusEnvironment = true; + services.zeitgeist.enable = mkDefault true; + services.geoclue2.enable = mkDefault true; + # pantheon has pantheon-agent-geoclue2 + services.geoclue2.enableDemoAgent = false; + services.geoclue2.appConfig."io.elementary.desktop.agent-geoclue2" = { + isAllowed = true; + isSystem = true; + }; + # Use gnome-settings-daemon fork + services.udev.packages = [ + pkgs.pantheon.elementary-settings-daemon + ]; + systemd.packages = [ + pkgs.pantheon.elementary-settings-daemon + ]; + programs.dconf.enable = true; + networking.networkmanager.enable = mkDefault true; + + # Global environment + environment.systemPackages = with pkgs; [ desktop-file-utils glib - glib-networking gnome-menus gnome3.adwaita-icon-theme gtk3.out hicolor-icon-theme lightlocker onboard - plank qgnomeplatform shared-mime-info sound-theme-freedesktop xdg-user-dirs - ]); + ] ++ (with pkgs.pantheon; [ + # Artwork + elementary-gtk-theme + elementary-icon-theme + elementary-sound-theme + elementary-wallpapers + + # Desktop + elementary-default-settings + elementary-dock + elementary-session-settings + elementary-shortcut-overlay + gala + (switchboard-with-plugs.override { + plugs = cfg.extraSwitchboardPlugs; + }) + (wingpanel-with-indicators.override { + indicators = cfg.extraWingpanelIndicators; + }) + + # Services + elementary-capnet-assist + elementary-dpms-helper + elementary-notifications + elementary-settings-daemon + pantheon-agent-geoclue2 + pantheon-agent-polkit + ]) ++ (gnome3.removePackagesByName [ + gnome3.geary + gnome3.epiphany + gnome3.gnome-font-viewer + ] config.environment.pantheon.excludePackages); - fonts.fonts = with pkgs; [ - open-sans - roboto-mono - pantheon.elementary-redacted-script # needed by screenshot-tool - ]; + programs.evince.enable = mkDefault true; + programs.file-roller.enable = mkDefault true; - fonts.fontconfig.defaultFonts = { - monospace = [ "Roboto Mono" ]; - sansSerif = [ "Open Sans" ]; - }; + # Settings from elementary-default-settings + environment.sessionVariables.GTK_CSD = "1"; + environment.sessionVariables.GTK3_MODULES = [ "pantheon-filechooser-module" ]; + environment.etc."gtk-3.0/settings.ini".source = "${pkgs.pantheon.elementary-default-settings}/etc/gtk-3.0/settings.ini"; - }; + # Override GSettings schemas + environment.sessionVariables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-desktop-schemas}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas"; + + environment.sessionVariables.GNOME_SESSION_DEBUG = mkIf cfg.debug "1"; + + environment.pathsToLink = [ + # FIXME: modules should link subdirs of `/share` rather than relying on this + "/share" + ]; + + # Otherwise you can't store NetworkManager Secrets with + # "Store the password only for this user" + programs.nm-applet.enable = true; + # Shell integration for VTE terminals + programs.bash.vteIntegration = mkDefault true; + programs.zsh.vteIntegration = mkDefault true; + + # Harmonize Qt5 applications under Pantheon + qt5.enable = true; + qt5.platformTheme = "gnome"; + qt5.style = "adwaita"; + + # Default Fonts + fonts.fonts = with pkgs; [ + open-sans + roboto-mono + ]; + + fonts.fontconfig.defaultFonts = { + monospace = [ "Roboto Mono" ]; + sansSerif = [ "Open Sans" ]; + }; + }) + + (mkIf serviceCfg.apps.enable { + environment.systemPackages = (with pkgs.pantheon; pkgs.gnome3.removePackagesByName [ + elementary-calculator + elementary-calendar + elementary-camera + elementary-code + elementary-files + elementary-music + elementary-photos + elementary-screenshot-tool + elementary-terminal + elementary-videos + ] config.environment.pantheon.excludePackages); + + # needed by screenshot-tool + fonts.fonts = [ + pkgs.pantheon.elementary-redacted-script + ]; + }) + + (mkIf serviceCfg.contractor.enable { + environment.systemPackages = with pkgs.pantheon; [ + contractor + extra-elementary-contracts + ]; + + environment.pathsToLink = [ + "/share/contractor" + ]; + }) + + ]; } diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.xml b/nixos/modules/services/x11/desktop-managers/pantheon.xml new file mode 100644 index 0000000000000..9541f2cfd4ee7 --- /dev/null +++ b/nixos/modules/services/x11/desktop-managers/pantheon.xml @@ -0,0 +1,114 @@ +<chapter xmlns="http://docbook.org/ns/docbook" + xmlns:xlink="http://www.w3.org/1999/xlink" + xml:id="chap-pantheon"> + <title>Pantheon Destkop</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> + <section xml:id="sec-pantheon-enable"> + <title>Enabling Pantheon</title> + + <para> + All of Pantheon is working in NixOS and the applications should be available, aside from a few <link xlink:href="https://github.com/NixOS/nixpkgs/issues/58161">exceptions</link>. To enable Pantheon, set +<programlisting> +<xref linkend="opt-services.xserver.desktopManager.pantheon.enable"/> = true; +</programlisting> + This automatically enables LightDM and Pantheon's LightDM greeter. If you'd like to disable this, set +<programlisting> +<xref linkend="opt-services.xserver.displayManager.lightdm.greeters.pantheon.enable"/> = false; +<xref linkend="opt-services.xserver.displayManager.lightdm.enable"/> = false; +</programlisting> + but please be aware using Pantheon without LightDM as a display manager will break screenlocking from the UI. The NixOS module for Pantheon installs all of Pantheon's default applications. If you'd like to not install Pantheon's apps, set +<programlisting> +<xref linkend="opt-services.pantheon.apps.enable"/> = false; +</programlisting> + You can also use <xref linkend="opt-environment.pantheon.excludePackages"/> to remove any other app (like <package>geary</package>). + </para> + </section> + <section xml:id="sec-pantheon-wingpanel-switchboard"> + <title>Wingpanel and Switchboard plugins</title> + + <para> + Wingpanel and Switchboard work differently than they do in other distributions, as far as using plugins. You cannot install a plugin globally (like with <option>environment.systemPackages</option>) to start using it. You should instead be using the following options: + <itemizedlist> + <listitem> + <para> + <xref linkend="opt-services.xserver.desktopManager.pantheon.extraWingpanelIndicators"/> + </para> + </listitem> + <listitem> + <para> + <xref linkend="opt-services.xserver.desktopManager.pantheon.extraSwitchboardPlugs"/> + </para> + </listitem> + </itemizedlist> + to configure the programs with plugs or indicators. + </para> + + <para> + The difference in NixOS is both these programs are patched to load plugins from a directory that is the value of an environment variable. All of which is controlled in Nix. If you need to configure the particular packages manually you can override the packages like: +<programlisting> +wingpanel-with-indicators.override { + indicators = [ + pkgs.some-special-indicator + ]; +}; + +switchboard-with-plugs.override { + plugs = [ + pkgs.some-special-plug + ]; +}; +</programlisting> + please note that, like how the NixOS options describe these as extra plugins, this would only add to the default plugins included with the programs. If for some reason you'd like to configure which plugins to use exactly, both packages have an argument for this: +<programlisting> +wingpanel-with-indicators.override { + useDefaultIndicators = false; + indicators = specialListOfIndicators; +}; + +switchboard-with-plugs.override { + useDefaultPlugs = false; + plugs = specialListOfPlugs; +}; +</programlisting> + this could be most useful for testing a particular plug-in in isolation. + </para> + </section> + <section xml:id="sec-pantheon-faq"> + <title>FAQ</title> + + <variablelist> + <varlistentry xml:id="sec-pantheon-faq-messed-up-theme"> + <term> + I have switched from a different desktop and Pantheon’s theming looks messed up. + </term> + <listitem> + <para> + Open Switchboard and go to: <guilabel>Administration</guilabel> → <guilabel>About</guilabel> → <guilabel>Restore Default Settings</guilabel> → <guibutton>Restore Settings</guibutton>. This will reset any dconf settings to their Pantheon defaults. Note this could reset certain GNOME specific preferences if that desktop was used prior. + </para> + </listitem> + </varlistentry> + <varlistentry xml:id="sec-pantheon-faq-gnome3-and-pantheon"> + <term> + I cannot enable both GNOME 3 and Pantheon. + </term> + <listitem> + <para> + This is a known <link xlink:href="https://github.com/NixOS/nixpkgs/issues/64611">issue</link> and there is no known workaround. + </para> + </listitem> + </varlistentry> + <varlistentry xml:id="sec-pantheon-faq-appcenter"> + <term> + Does AppCenter work, or is it available? + </term> + <listitem> + <para> + AppCenter has been available since 20.03, but it is of little use. This is because there is no functioning PackageKit backend for Nix 2.0. In the near future you will be able to install Flatpak applications from AppCenter on NixOS. See this <link xlink:href="https://github.com/NixOS/nixpkgs/issues/70214">issue</link>. + </para> + </listitem> + </varlistentry> + </variablelist> + </section> +</chapter> diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix index da8bdcb78c432..60ef0159ff1ab 100644 --- a/nixos/modules/services/x11/desktop-managers/plasma5.nix +++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix @@ -8,6 +8,137 @@ let cfg = xcfg.desktopManager.plasma5; inherit (pkgs) kdeApplications plasma5 libsForQt5 qt5; + inherit (pkgs) writeText; + + pulseaudio = config.hardware.pulseaudio; + pactl = "${getBin pulseaudio.package}/bin/pactl"; + startplasma-x11 = "${getBin plasma5.plasma-workspace}/bin/startplasma-x11"; + sed = "${getBin pkgs.gnused}/bin/sed"; + + gtkrc2 = writeText "gtkrc-2.0" '' + # Default GTK+ 2 config for NixOS Plasma 5 + include "/run/current-system/sw/share/themes/Breeze/gtk-2.0/gtkrc" + style "user-font" + { + font_name="Sans Serif Regular" + } + widget_class "*" style "user-font" + gtk-font-name="Sans Serif Regular 10" + gtk-theme-name="Breeze" + gtk-icon-theme-name="breeze" + gtk-fallback-icon-theme="hicolor" + gtk-cursor-theme-name="breeze_cursors" + gtk-toolbar-style=GTK_TOOLBAR_ICONS + gtk-menu-images=1 + gtk-button-images=1 + ''; + + gtk3_settings = writeText "settings.ini" '' + [Settings] + gtk-font-name=Sans Serif Regular 10 + gtk-theme-name=Breeze + gtk-icon-theme-name=breeze + gtk-fallback-icon-theme=hicolor + gtk-cursor-theme-name=breeze_cursors + gtk-toolbar-style=GTK_TOOLBAR_ICONS + gtk-menu-images=1 + gtk-button-images=1 + ''; + + kcminputrc = writeText "kcminputrc" '' + [Mouse] + cursorTheme=breeze_cursors + cursorSize=0 + ''; + + activationScript = '' + ${set_XDG_CONFIG_HOME} + + # The KDE icon cache is supposed to update itself automatically, but it uses + # the timestamp on the icon theme directory as a trigger. This doesn't work + # on NixOS because the timestamp never changes. As a workaround, delete the + # icon cache at login and session activation. + # See also: http://lists-archives.org/kde-devel/26175-what-when-will-icon-cache-refresh.html + rm -fv $HOME/.cache/icon-cache.kcache + + # xdg-desktop-settings generates this empty file but + # it makes kbuildsyscoca5 fail silently. To fix this + # remove that menu if it exists. + rm -fv ''${XDG_CONFIG_HOME}/menus/applications-merged/xdg-desktop-menu-dummy.menu + + # Qt writes a weird ‘libraryPath’ line to + # ~/.config/Trolltech.conf that causes the KDE plugin + # paths of previous KDE invocations to be searched. + # Obviously using mismatching KDE libraries is potentially + # disastrous, so here we nuke references to the Nix store + # in Trolltech.conf. A better solution would be to stop + # Qt from doing this wackiness in the first place. + trolltech_conf="''${XDG_CONFIG_HOME}/Trolltech.conf" + if [ -e "$trolltech_conf" ]; then + ${sed} -i "$trolltech_conf" -e '/nix\\store\|nix\/store/ d' + fi + + # Remove the kbuildsyscoca5 cache. It will be regenerated + # immediately after. This is necessary for kbuildsyscoca5 to + # recognize that software that has been removed. + rm -fv $HOME/.cache/ksycoca* + + ${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5 + ''; + + set_XDG_CONFIG_HOME = '' + # Set the default XDG_CONFIG_HOME if it is unset. + # Per the XDG Base Directory Specification: + # https://specifications.freedesktop.org/basedir-spec/latest + # 1. Never export this variable! If it is unset, then child processes are + # expected to set the default themselves. + # 2. Contaminate / if $HOME is unset; do not check if $HOME is set. + XDG_CONFIG_HOME=''${XDG_CONFIG_HOME:-$HOME/.config} + ''; + + startplasma = + '' + ${set_XDG_CONFIG_HOME} + mkdir -p "''${XDG_CONFIG_HOME}" + + '' + + optionalString pulseaudio.enable '' + # Load PulseAudio module for routing support. + # See also: http://colin.guthr.ie/2009/10/so-how-does-the-kde-pulseaudio-support-work-anyway/ + ${pactl} load-module module-device-manager "do_routing=1" + + '' + + '' + ${activationScript} + + # Create default configurations if Plasma has never been started. + kdeglobals="''${XDG_CONFIG_HOME}/kdeglobals" + if ! [ -f "$kdeglobals" ] + then + kcminputrc="''${XDG_CONFIG_HOME}/kcminputrc" + if ! [ -f "$kcminputrc" ] + then + cat ${kcminputrc} >"$kcminputrc" + fi + + gtkrc2="$HOME/.gtkrc-2.0" + if ! [ -f "$gtkrc2" ] + then + cat ${gtkrc2} >"$gtkrc2" + fi + + gtk3_settings="''${XDG_CONFIG_HOME}/gtk-3.0/settings.ini" + if ! [ -f "$gtk3_settings" ] + then + mkdir -p "$(dirname "$gtk3_settings")" + cat ${gtk3_settings} >"$gtk3_settings" + fi + fi + + '' + + '' + exec "${startplasma-x11}" + ''; in @@ -33,6 +164,7 @@ in imports = [ (mkRemovedOptionModule [ "services" "xserver" "desktopManager" "plasma5" "enableQt4Support" ] "Phonon no longer supports Qt 4.") + (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "kde5" ] [ "services" "xserver" "desktopManager" "plasma5" ]) ]; config = mkMerge [ @@ -40,27 +172,7 @@ in services.xserver.desktopManager.session = singleton { name = "plasma5"; bgSupport = true; - start = '' - # Load PulseAudio module for routing support. - # See http://colin.guthr.ie/2009/10/so-how-does-the-kde-pulseaudio-support-work-anyway/ - ${optionalString config.hardware.pulseaudio.enable '' - ${getBin config.hardware.pulseaudio.package}/bin/pactl load-module module-device-manager "do_routing=1" - ''} - - if [ -f "$HOME/.config/kdeglobals" ] - then - # Remove extraneous font style names. - # See also: https://phabricator.kde.org/D9070 - ${getBin pkgs.gnused}/bin/sed -i "$HOME/.config/kdeglobals" \ - -e '/^fixed=/ s/,Regular$//' \ - -e '/^font=/ s/,Regular$//' \ - -e '/^menuFont=/ s/,Regular$//' \ - -e '/^smallestReadableFont=/ s/,Regular$//' \ - -e '/^toolBarFont=/ s/,Regular$//' - fi - - exec "${getBin plasma5.plasma-workspace}/bin/startkde" - ''; + start = startplasma; }; security.wrappers = { @@ -136,6 +248,7 @@ in libkscreen libksysguard milou + plasma-browser-integration plasma-integration polkit-kde-agent systemsettings @@ -169,7 +282,7 @@ in ++ lib.optional (cfg.phononBackend == "vlc") libsForQt5.phonon-backend-vlc # Optional hardware support features - ++ lib.optionals config.hardware.bluetooth.enable [ bluedevil bluez-qt ] + ++ lib.optionals config.hardware.bluetooth.enable [ bluedevil bluez-qt openobex obexftp ] ++ lib.optional config.networking.networkmanager.enable plasma-nm ++ lib.optional config.hardware.pulseaudio.enable plasma-pa ++ lib.optional config.powerManagement.enable powerdevil @@ -182,10 +295,7 @@ in "/share" ]; - environment.etc = singleton { - source = xcfg.xkbDir; - target = "X11/xkb"; - }; + environment.etc."X11/xkb".source = xcfg.xkbDir; # Enable GTK applications to load SVG icons services.xserver.gdk-pixbuf.modulePackages = [ pkgs.librsvg ]; @@ -228,29 +338,7 @@ in xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-kde ]; # Update the start menu for each user that is currently logged in - system.userActivationScripts.plasmaSetup = '' - # The KDE icon cache is supposed to update itself - # automatically, but it uses the timestamp on the icon - # theme directory as a trigger. Since in Nix the - # timestamp is always the same, this doesn't work. So as - # a workaround, nuke the icon cache on login. This isn't - # perfect, since it may require logging out after - # installing new applications to update the cache. - # See http://lists-archives.org/kde-devel/26175-what-when-will-icon-cache-refresh.html - rm -fv $HOME/.cache/icon-cache.kcache - - # xdg-desktop-settings generates this empty file but - # it makes kbuildsyscoca5 fail silently. To fix this - # remove that menu if it exists. - rm -fv $HOME/.config/menus/applications-merged/xdg-desktop-menu-dummy.menu - - # Remove the kbuildsyscoca5 cache. It will be regenerated - # immediately after. This is necessary for kbuildsyscoca5 to - # recognize that software that has been removed. - rm -fv $HOME/.cache/ksycoca* - - ${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5 - ''; + system.userActivationScripts.plasmaSetup = activationScript; }) ]; diff --git a/nixos/modules/services/x11/desktop-managers/surf-display.nix b/nixos/modules/services/x11/desktop-managers/surf-display.nix index 140dde828daa3..9aeb0bbd2a887 100644 --- a/nixos/modules/services/x11/desktop-managers/surf-display.nix +++ b/nixos/modules/services/x11/desktop-managers/surf-display.nix @@ -118,7 +118,7 @@ in { }; config = mkIf cfg.enable { - services.xserver.displayManager.extraSessionFilePackages = [ + services.xserver.displayManager.sessionPackages = [ pkgs.surf-display ]; diff --git a/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixos/modules/services/x11/desktop-managers/xfce.nix index 0b70ad5f29c8f..d39b4d64904fb 100644 --- a/nixos/modules/services/x11/desktop-managers/xfce.nix +++ b/nixos/modules/services/x11/desktop-managers/xfce.nix @@ -8,6 +8,10 @@ in { + meta = { + maintainers = with maintainers; [ worldofpeace ]; + }; + imports = [ # added 2019-08-18 # needed to preserve some semblance of UI familarity @@ -31,6 +35,7 @@ in (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "xfce" "extraSessionCommands" ] [ "services" "xserver" "displayManager" "sessionCommands" ]) + (mkRemovedOptionModule [ "services" "xserver" "desktopManager" "xfce" "screenLock" ] "") ]; options = { @@ -128,6 +133,7 @@ in services.xserver.desktopManager.session = [{ name = "xfce"; + desktopNames = [ "XFCE" ]; bgSupport = true; start = '' ${pkgs.runtimeShell} ${pkgs.xfce.xfce4-session.xinitrc} & diff --git a/nixos/modules/services/x11/display-managers/account-service-util.nix b/nixos/modules/services/x11/display-managers/account-service-util.nix new file mode 100644 index 0000000000000..2b08c62d0ad13 --- /dev/null +++ b/nixos/modules/services/x11/display-managers/account-service-util.nix @@ -0,0 +1,44 @@ +{ accountsservice +, glib +, gobject-introspection +, python3 +, wrapGAppsHook +, lib +}: + +python3.pkgs.buildPythonApplication { + name = "set-session"; + + format = "other"; + + src = ./set-session.py; + + dontUnpack = true; + + strictDeps = false; + + nativeBuildInputs = [ + wrapGAppsHook + gobject-introspection + ]; + + buildInputs = [ + accountsservice + glib + ]; + + propagatedBuildInputs = with python3.pkgs; [ + pygobject3 + ordered-set + ]; + + installPhase = '' + mkdir -p $out/bin + cp $src $out/bin/set-session + chmod +x $out/bin/set-session + ''; + + meta = with lib; { + maintainers = with maintainers; [ worldofpeace ]; + }; +} diff --git a/nixos/modules/services/x11/display-managers/default.nix b/nixos/modules/services/x11/display-managers/default.nix index b66856fd4d44e..2a7a19e7695a6 100644 --- a/nixos/modules/services/x11/display-managers/default.nix +++ b/nixos/modules/services/x11/display-managers/default.nix @@ -27,16 +27,7 @@ let Xft.hintstyle: hintslight ''; - mkCases = session: - concatStrings ( - mapAttrsToList (name: starts: '' - (${name}) - ${concatMapStringsSep "\n " (n: n.start) starts} - ;; - '') (lib.groupBy (n: n.name) session) - ); - - # file provided by services.xserver.displayManager.session.wrapper + # file provided by services.xserver.displayManager.sessionData.wrapper xsessionWrapper = pkgs.writeScript "xsession-wrapper" '' #! ${pkgs.bash}/bin/bash @@ -63,14 +54,6 @@ let exec &> >(tee ~/.xsession-errors) ''} - # Start PulseAudio if enabled. - ${optionalString (config.hardware.pulseaudio.enable) '' - # Publish access credentials in the root window. - if ${config.hardware.pulseaudio.package.out}/bin/pulseaudio --dump-modules | grep module-x11-publish &> /dev/null; then - ${config.hardware.pulseaudio.package.out}/bin/pactl load-module module-x11-publish "display=$DISPLAY" - fi - ''} - # Tell systemd about our $DISPLAY and $XAUTHORITY. # This is needed by the ssh-agent unit. # @@ -116,98 +99,49 @@ let # Run the supplied session command. Remove any double quotes with eval. eval exec "$@" else - # Fall back to the default window/desktopManager - exec ${cfg.displayManager.session.script} + # TODO: Do we need this? Should not the session always exist? + echo "error: unknown session $1" 1>&2 + exit 1 fi ''; - # file provided by services.xserver.displayManager.session.script - xsession = wm: dm: pkgs.writeScript "xsession" - '' - #! ${pkgs.bash}/bin/bash - - # Legacy session script used to construct .desktop files from - # `services.xserver.displayManager.session` entries. Called from - # `sessionWrapper`. - - # Expected parameters: - # $1 = <desktop-manager>+<window-manager> - - # The first argument of this script is the session type. - sessionType="$1" - if [ "$sessionType" = default ]; then sessionType=""; fi - - # The session type is "<desktop-manager>+<window-manager>", so - # extract those (see: - # http://wiki.bash-hackers.org/syntax/pe#substring_removal). - windowManager="''${sessionType##*+}" - : ''${windowManager:=${cfg.windowManager.default}} - desktopManager="''${sessionType%%+*}" - : ''${desktopManager:=${cfg.desktopManager.default}} - - # Start the window manager. - case "$windowManager" in - ${mkCases wm} - (*) echo "$0: Window manager '$windowManager' not found.";; - esac - - # Start the desktop manager. - case "$desktopManager" in - ${mkCases dm} - (*) echo "$0: Desktop manager '$desktopManager' not found.";; - esac - - ${optionalString cfg.updateDbusEnvironment '' - ${lib.getBin pkgs.dbus}/bin/dbus-update-activation-environment --systemd --all - ''} - - test -n "$waitPID" && wait "$waitPID" - - ${config.systemd.package}/bin/systemctl --user stop graphical-session.target - - exit 0 - ''; - - # Desktop Entry Specification: - # - https://standards.freedesktop.org/desktop-entry-spec/latest/ - # - https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html - mkDesktops = names: pkgs.runCommand "desktops" + installedSessions = pkgs.runCommand "desktops" { # trivial derivation preferLocalBuild = true; allowSubstitutes = false; } '' - mkdir -p "$out/share/xsessions" - ${concatMapStrings (n: '' - cat - > "$out/share/xsessions/${n}.desktop" << EODESKTOP - [Desktop Entry] - Version=1.0 - Type=XSession - TryExec=${cfg.displayManager.session.script} - Exec=${cfg.displayManager.session.script} "${n}" - Name=${n} - Comment= - EODESKTOP - '') names} + mkdir -p "$out/share/"{xsessions,wayland-sessions} ${concatMapStrings (pkg: '' + for n in ${concatStringsSep " " pkg.providedSessions}; do + if ! test -f ${pkg}/share/wayland-sessions/$n.desktop -o \ + -f ${pkg}/share/xsessions/$n.desktop; then + echo "Couldn't find provided session name, $n.desktop, in session package ${pkg.name}:" + echo " ${pkg}" + return 1 + fi + done + if test -d ${pkg}/share/xsessions; then ${xorg.lndir}/bin/lndir ${pkg}/share/xsessions $out/share/xsessions fi - '') cfg.displayManager.extraSessionFilePackages} - - ${concatMapStrings (pkg: '' if test -d ${pkg}/share/wayland-sessions; then - mkdir -p "$out/share/wayland-sessions" ${xorg.lndir}/bin/lndir ${pkg}/share/wayland-sessions $out/share/wayland-sessions fi - '') cfg.displayManager.extraSessionFilePackages} + '') cfg.displayManager.sessionPackages} ''; + dmDefault = cfg.desktopManager.default; + # fallback default for cases when only default wm is set + dmFallbackDefault = if dmDefault != null then dmDefault else "none"; + wmDefault = cfg.windowManager.default; + + defaultSessionFromLegacyOptions = dmFallbackDefault + optionalString (wmDefault != null && wmDefault != "none") "+${wmDefault}"; + in { - options = { services.xserver.displayManager = { @@ -262,11 +196,24 @@ in ''; }; - extraSessionFilePackages = mkOption { - type = types.listOf types.package; + sessionPackages = mkOption { + type = with types; listOf (package // { + description = "package with provided sessions"; + check = p: assertMsg + (package.check p && p ? providedSessions + && p.providedSessions != [] && all isString p.providedSessions) + '' + Package, '${p.name}', did not specify any session names, as strings, in + 'passthru.providedSessions'. This is required when used as a session package. + + The session names can be looked up in: + ${p}/share/xsessions + ${p}/share/wayland-sessions + ''; + }); default = []; description = '' - A list of packages containing xsession files to be passed to the display manager. + A list of packages containing x11 or wayland session files to be passed to the display manager. ''; }; @@ -297,16 +244,48 @@ in inside the display manager with the desktop manager name followed by the window manager name. ''; - apply = list: rec { - wm = filter (s: s.manage == "window") list; - dm = filter (s: s.manage == "desktop") list; - names = flip concatMap dm - (d: map (w: d.name + optionalString (w.name != "none") ("+" + w.name)) - (filter (w: d.name != "none" || w.name != "none") wm)); - desktops = mkDesktops names; - script = xsession wm dm; + }; + + sessionData = mkOption { + description = "Data exported for display managers’ convenience"; + internal = true; + default = {}; + apply = val: { wrapper = xsessionWrapper; + desktops = installedSessions; + sessionNames = concatMap (p: p.providedSessions) cfg.displayManager.sessionPackages; + # We do not want to force users to set defaultSession when they have only single DE. + autologinSession = + if cfg.displayManager.defaultSession != null then + cfg.displayManager.defaultSession + else if cfg.displayManager.sessionData.sessionNames != [] then + head cfg.displayManager.sessionData.sessionNames + else + null; + }; + }; + + defaultSession = mkOption { + type = with types; nullOr str // { + description = "session name"; + check = d: + assertMsg (d != null -> (str.check d && elem d cfg.displayManager.sessionData.sessionNames)) '' + Default graphical session, '${d}', not found. + Valid names for 'services.xserver.displayManager.defaultSession' are: + ${concatStringsSep "\n " cfg.displayManager.sessionData.sessionNames} + ''; }; + default = + if dmDefault != null || wmDefault != null then + defaultSessionFromLegacyOptions + else + null; + example = "gnome"; + description = '' + Graphical session to pre-select in the session chooser (only effective for GDM and LightDM). + + On GDM, LightDM and SDDM, it will also be used as a session for auto-login. + ''; }; job = { @@ -357,6 +336,27 @@ in }; config = { + assertions = [ + { + assertion = cfg.desktopManager.default != null || cfg.windowManager.default != null -> cfg.displayManager.defaultSession == defaultSessionFromLegacyOptions; + message = "You cannot use both services.xserver.displayManager.defaultSession option and legacy options (services.xserver.desktopManager.default and services.xserver.windowManager.default)."; + } + ]; + + warnings = + mkIf (dmDefault != null || wmDefault != null) [ + '' + The following options are deprecated: + ${concatStringsSep "\n " (map ({c, t}: t) (filter ({c, t}: c != null) [ + { c = dmDefault; t = "- services.xserver.desktopManager.default"; } + { c = wmDefault; t = "- services.xserver.windowManager.default"; } + ]))} + Please use + services.xserver.displayManager.defaultSession = "${defaultSessionFromLegacyOptions}"; + instead. + '' + ]; + services.xserver.displayManager.xserverBin = "${xorg.xorgserver.out}/bin/X"; systemd.user.targets.graphical-session = { @@ -365,11 +365,79 @@ in StopWhenUnneeded = false; }; }; + + # Create desktop files and scripts for starting sessions for WMs/DMs + # that do not have upstream session files (those defined using services.{display,desktop,window}Manager.session options). + services.xserver.displayManager.sessionPackages = + let + dms = filter (s: s.manage == "desktop") cfg.displayManager.session; + wms = filter (s: s.manage == "window") cfg.displayManager.session; + + # Script responsible for starting the window manager and the desktop manager. + xsession = dm: wm: pkgs.writeScript "xsession" '' + #! ${pkgs.bash}/bin/bash + + # Legacy session script used to construct .desktop files from + # `services.xserver.displayManager.session` entries. Called from + # `sessionWrapper`. + + # Start the window manager. + ${wm.start} + + # Start the desktop manager. + ${dm.start} + + ${optionalString cfg.updateDbusEnvironment '' + ${lib.getBin pkgs.dbus}/bin/dbus-update-activation-environment --systemd --all + ''} + + test -n "$waitPID" && wait "$waitPID" + + ${config.systemd.package}/bin/systemctl --user stop graphical-session.target + + exit 0 + ''; + in + # We will generate every possible pair of WM and DM. + concatLists ( + crossLists + (dm: wm: let + sessionName = "${dm.name}${optionalString (wm.name != "none") ("+" + wm.name)}"; + script = xsession dm wm; + desktopNames = if dm ? desktopNames + then concatStringsSep ";" dm.desktopNames + else sessionName; + in + optional (dm.name != "none" || wm.name != "none") + (pkgs.writeTextFile { + name = "${sessionName}-xsession"; + destination = "/share/xsessions/${sessionName}.desktop"; + # Desktop Entry Specification: + # - https://standards.freedesktop.org/desktop-entry-spec/latest/ + # - https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html + text = '' + [Desktop Entry] + Version=1.0 + Type=XSession + TryExec=${script} + Exec=${script} + Name=${sessionName} + DesktopNames=${desktopNames} + ''; + } // { + providedSessions = [ sessionName ]; + }) + ) + [dms wms] + ); }; imports = [ - (mkRemovedOptionModule [ "services" "xserver" "displayManager" "desktopManagerHandlesLidAndPower" ] + (mkRemovedOptionModule [ "services" "xserver" "displayManager" "desktopManagerHandlesLidAndPower" ] "The option is no longer necessary because all display managers have already delegated lid management to systemd.") + (mkRenamedOptionModule [ "services" "xserver" "displayManager" "job" "logsXsession" ] [ "services" "xserver" "displayManager" "job" "logToFile" ]) + (mkRenamedOptionModule [ "services" "xserver" "displayManager" "logToJournal" ] [ "services" "xserver" "displayManager" "job" "logToJournal" ]) + (mkRenamedOptionModule [ "services" "xserver" "displayManager" "extraSessionFilesPackages" ] [ "services" "xserver" "displayManager" "sessionPackages" ]) ]; } diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix index 912ec5bd38edb..d7bef68e5bcbb 100644 --- a/nixos/modules/services/x11/display-managers/gdm.nix +++ b/nixos/modules/services/x11/display-managers/gdm.nix @@ -31,48 +31,17 @@ let load-module module-position-event-sounds ''; - dmDefault = config.services.xserver.desktopManager.default; - wmDefault = config.services.xserver.windowManager.default; - hasDefaultUserSession = dmDefault != "none" || wmDefault != "none"; - defaultSessionName = dmDefault + optionalString (wmDefault != "none") ("+" + wmDefault); - - setSessionScript = pkgs.python3.pkgs.buildPythonApplication { - name = "set-session"; - - format = "other"; - - src = ./set-session.py; - - dontUnpack = true; - - strictDeps = false; - - nativeBuildInputs = with pkgs; [ - wrapGAppsHook - gobject-introspection - ]; - - buildInputs = with pkgs; [ - accountsservice - glib - ]; - - propagatedBuildInputs = with pkgs.python3.pkgs; [ - pygobject3 - ordered-set - ]; - - installPhase = '' - mkdir -p $out/bin - cp $src $out/bin/set-session - chmod +x $out/bin/set-session - ''; - }; + defaultSessionName = config.services.xserver.displayManager.defaultSession; + setSessionScript = pkgs.callPackage ./account-service-util.nix { }; in { + meta = { + maintainers = teams.gnome.members; + }; + ###### interface options = { @@ -186,7 +155,7 @@ in environment = { GDM_X_SERVER_EXTRA_ARGS = toString (filter (arg: arg != "-terminate") cfg.xserverArgs); - XDG_DATA_DIRS = "${cfg.session.desktops}/share/"; + XDG_DATA_DIRS = "${cfg.sessionData.desktops}/share/"; } // optionalAttrs (xSessionWrapper != null) { # Make GDM use this wrapper before running the session, which runs the # configured setupCommands. This relies on a patched GDM which supports @@ -194,23 +163,36 @@ in GDM_X_SESSION_WRAPPER = "${xSessionWrapper}"; }; execCmd = "exec ${gdm}/bin/gdm"; - preStart = optionalString config.hardware.pulseaudio.enable '' - mkdir -p /run/gdm/.config/pulse - ln -sf ${pulseConfig} /run/gdm/.config/pulse/default.pa - chown -R gdm:gdm /run/gdm/.config - '' + optionalString config.services.gnome3.gnome-initial-setup.enable '' - # Create stamp file for gnome-initial-setup to prevent run. - mkdir -p /run/gdm/.config - cat - > /run/gdm/.config/gnome-initial-setup-done <<- EOF - yes - EOF - '' + optionalString hasDefaultUserSession '' - ${setSessionScript}/bin/set-session ${defaultSessionName} + preStart = optionalString (defaultSessionName != null) '' + # Set default session in session chooser to a specified values – basically ignore session history. + ${setSessionScript}/bin/set-session ${cfg.sessionData.autologinSession} ''; }; - # Because sd_login_monitor_new requires /run/systemd/machines - systemd.services.display-manager.wants = [ "systemd-machined.service" ]; + systemd.tmpfiles.rules = [ + "d /run/gdm/.config 0711 gdm gdm" + ] ++ optionals config.hardware.pulseaudio.enable [ + "d /run/gdm/.config/pulse 0711 gdm gdm" + "L+ /run/gdm/.config/pulse/${pulseConfig.name} - - - - ${pulseConfig}" + ] ++ optionals config.services.gnome3.gnome-initial-setup.enable [ + # Create stamp file for gnome-initial-setup to prevent it starting in GDM. + "f /run/gdm/.config/gnome-initial-setup-done 0711 gdm gdm - yes" + ]; + + # Otherwise GDM will not be able to start correctly and display Wayland sessions + systemd.packages = with pkgs.gnome3; [ gnome-session gnome-shell ]; + environment.systemPackages = [ pkgs.gnome3.adwaita-icon-theme ]; + + systemd.services.display-manager.wants = [ + # Because sd_login_monitor_new requires /run/systemd/machines + "systemd-machined.service" + # setSessionScript wants AccountsService + "accounts-daemon.service" + # Failed to open gpu '/dev/dri/card0': GDBus.Error:org.freedesktop.DBus.Error.AccessDenied: Operation not permitted + # https://github.com/NixOS/nixpkgs/pull/25311#issuecomment-609417621 + "systemd-udev-settle.service" + ]; + systemd.services.display-manager.after = [ "rc-local.service" "systemd-machined.service" @@ -218,6 +200,7 @@ in "getty@tty${gdm.initialVT}.service" "plymouth-quit.service" "plymouth-start.service" + "systemd-udev-settle.service" ]; systemd.services.display-manager.conflicts = [ "getty@tty${gdm.initialVT}.service" @@ -281,7 +264,7 @@ in customDconfDb = pkgs.stdenv.mkDerivation { name = "gdm-dconf-db"; buildCommand = '' - ${pkgs.gnome3.dconf}/bin/dconf compile $out ${customDconf}/dconf + ${pkgs.dconf}/bin/dconf compile $out ${customDconf}/dconf ''; }; in pkgs.stdenv.mkDerivation { @@ -326,7 +309,7 @@ in ${optionalString cfg.gdm.debug "Enable=true"} ''; - environment.etc."gdm/Xsession".source = config.services.xserver.displayManager.session.wrapper; + environment.etc."gdm/Xsession".source = config.services.xserver.displayManager.sessionData.wrapper; # GDM LFS PAM modules, adapted somehow to NixOS security.pam.services = { diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix index fa9445af32e70..16d7fdf15cf68 100644 --- a/nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix +++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix @@ -14,7 +14,9 @@ let user = ${cfg.user} show-password-label = true password-label-text = Password: + invalid-password-text = Invalid Password show-input-cursor = true + password-alignment = right [greeter-hotkeys] mod-key = meta @@ -26,6 +28,8 @@ let [greeter-theme] font = Sans font-size = 1em + font-weight = bold + font-style = normal text-color = "#080800" error-color = "#F8F8F0" background-image = "${ldmcfg.background}" @@ -36,6 +40,8 @@ let layout-space = 15 password-color = "#F8F8F0" password-background-color = "#1B1D1E" + password-border-color = "#080800" + password-border-width = 2px ${cfg.extraConfig} ''; @@ -53,9 +59,8 @@ in Whether to enable lightdm-mini-greeter as the lightdm greeter. Note that this greeter starts only the default X session. - You can configure the default X session by - <option>services.xserver.desktopManager.default</option> and - <option>services.xserver.windowManager.default</option>. + You can configure the default X session using + <xref linkend="opt-services.xserver.displayManager.defaultSession"/>. ''; }; diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix index 29cb6ccbc06bd..087c6b9c38ac0 100644 --- a/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix +++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix @@ -10,6 +10,10 @@ let in { + meta = { + maintainers = with maintainers; [ worldofpeace ]; + }; + options = { services.xserver.displayManager.lightdm.greeters.pantheon = { @@ -35,6 +39,9 @@ in name = "io.elementary.greeter"; }; + # Show manual login card. + services.xserver.displayManager.lightdm.extraSeatDefaults = "greeter-show-manual-login=true"; + environment.etc."lightdm/io.elementary.greeter.conf".source = "${pkgs.pantheon.elementary-greeter}/etc/lightdm/io.elementary.greeter.conf"; environment.etc."wingpanel.d/io.elementary.greeter.whitelist".source = "${pkgs.pantheon.elementary-default-settings}/etc/wingpanel.d/io.elementary.greeter.whitelist"; diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/tiny.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/tiny.nix new file mode 100644 index 0000000000000..a9ba8e6280d6c --- /dev/null +++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/tiny.nix @@ -0,0 +1,92 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + dmcfg = config.services.xserver.displayManager; + ldmcfg = dmcfg.lightdm; + cfg = ldmcfg.greeters.tiny; + +in +{ + options = { + + services.xserver.displayManager.lightdm.greeters.tiny = { + + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable lightdm-tiny-greeter as the lightdm greeter. + + Note that this greeter starts only the default X session. + You can configure the default X session using + <xref linkend="opt-services.xserver.displayManager.defaultSession"/>. + ''; + }; + + label = { + user = mkOption { + type = types.str; + default = "Username"; + description = '' + The string to represent the user_text label. + ''; + }; + + pass = mkOption { + type = types.str; + default = "Password"; + description = '' + The string to represent the pass_text label. + ''; + }; + }; + + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Section to describe style and ui. + ''; + }; + + }; + + }; + + config = mkIf (ldmcfg.enable && cfg.enable) { + + services.xserver.displayManager.lightdm.greeters.gtk.enable = false; + + nixpkgs.config.lightdm-tiny-greeter.conf = + let + configHeader = '' + #include <gtk/gtk.h> + static const char *user_text = "${cfg.label.user}"; + static const char *pass_text = "${cfg.label.pass}"; + static const char *session = "${dmcfg.defaultSession}"; + ''; + in + optionalString (cfg.extraConfig != "") + (configHeader + cfg.extraConfig); + + services.xserver.displayManager.lightdm.greeter = + mkDefault { + package = pkgs.lightdm-tiny-greeter.xgreeters; + name = "lightdm-tiny-greeter"; + }; + + assertions = [ + { + assertion = dmcfg.defaultSession != null; + message = '' + Please set: services.xserver.displayManager.defaultSession + ''; + } + ]; + + }; +} diff --git a/nixos/modules/services/x11/display-managers/lightdm.nix b/nixos/modules/services/x11/display-managers/lightdm.nix index cf4c05acbccd2..479548863b474 100644 --- a/nixos/modules/services/x11/display-managers/lightdm.nix +++ b/nixos/modules/services/x11/display-managers/lightdm.nix @@ -8,10 +8,9 @@ let dmcfg = xcfg.displayManager; xEnv = config.systemd.services.display-manager.environment; cfg = dmcfg.lightdm; + sessionData = dmcfg.sessionData; - dmDefault = xcfg.desktopManager.default; - wmDefault = xcfg.windowManager.default; - hasDefaultUserSession = dmDefault != "none" || wmDefault != "none"; + setSessionScript = pkgs.callPackage ./account-service-util.nix { }; inherit (pkgs) lightdm writeScript writeText; @@ -45,22 +44,19 @@ let greeter-user = ${config.users.users.lightdm.name} greeters-directory = ${cfg.greeter.package} ''} - sessions-directory = ${dmcfg.session.desktops}/share/xsessions + sessions-directory = ${dmcfg.sessionData.desktops}/share/xsessions:${dmcfg.sessionData.desktops}/share/wayland-sessions ${cfg.extraConfig} [Seat:*] xserver-command = ${xserverWrapper} - session-wrapper = ${dmcfg.session.wrapper} + session-wrapper = ${dmcfg.sessionData.wrapper} ${optionalString cfg.greeter.enable '' greeter-session = ${cfg.greeter.name} ''} ${optionalString cfg.autoLogin.enable '' autologin-user = ${cfg.autoLogin.user} autologin-user-timeout = ${toString cfg.autoLogin.timeout} - autologin-session = ${defaultSessionName} - ''} - ${optionalString hasDefaultUserSession '' - user-session=${defaultSessionName} + autologin-session = ${sessionData.autologinSession} ''} ${optionalString (dmcfg.setupCommands != "") '' display-setup-script=${pkgs.writeScript "lightdm-display-setup" '' @@ -71,9 +67,12 @@ let ${cfg.extraSeatDefaults} ''; - defaultSessionName = dmDefault + optionalString (wmDefault != "none") ("+" + wmDefault); in { + meta = { + maintainers = with maintainers; [ worldofpeace ]; + }; + # Note: the order in which lightdm greeter modules are imported # here determines the default: later modules (if enable) are # preferred. @@ -82,6 +81,7 @@ in ./lightdm-greeters/mini.nix ./lightdm-greeters/enso-os.nix ./lightdm-greeters/pantheon.nix + ./lightdm-greeters/tiny.nix ]; options = { @@ -199,11 +199,9 @@ in LightDM auto-login requires services.xserver.displayManager.lightdm.autoLogin.user to be set ''; } - { assertion = cfg.autoLogin.enable -> dmDefault != "none" || wmDefault != "none"; + { assertion = cfg.autoLogin.enable -> sessionData.autologinSession != null; message = '' - LightDM auto-login requires that services.xserver.desktopManager.default and - services.xserver.windowManager.default are set to valid values. The current - default session: ${defaultSessionName} is not valid. + LightDM auto-login requires that services.xserver.displayManager.defaultSession is set. ''; } { assertion = !cfg.greeter.enable -> (cfg.autoLogin.enable && cfg.autoLogin.timeout == 0); @@ -214,6 +212,20 @@ in } ]; + # Set default session in session chooser to a specified values – basically ignore session history. + # Auto-login is already covered by a config value. + services.xserver.displayManager.job.preStart = optionalString (!cfg.autoLogin.enable && dmcfg.defaultSession != null) '' + ${setSessionScript}/bin/set-session ${dmcfg.defaultSession} + ''; + + # setSessionScript needs session-files in XDG_DATA_DIRS + services.xserver.displayManager.job.environment.XDG_DATA_DIRS = "${dmcfg.sessionData.desktops}/share/"; + + # setSessionScript wants AccountsService + systemd.services.display-manager.wants = [ + "accounts-daemon.service" + ]; + # lightdm relaunches itself via just `lightdm`, so needs to be on the PATH services.xserver.displayManager.job.execCmd = '' export PATH=${lightdm}/sbin:$PATH diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix index 899dd8665a25d..4224c557ed638 100644 --- a/nixos/modules/services/x11/display-managers/sddm.nix +++ b/nixos/modules/services/x11/display-managers/sddm.nix @@ -50,8 +50,8 @@ let MinimumVT=${toString (if xcfg.tty != null then xcfg.tty else 7)} ServerPath=${xserverWrapper} XephyrPath=${pkgs.xorg.xorgserver.out}/bin/Xephyr - SessionCommand=${dmcfg.session.wrapper} - SessionDir=${dmcfg.session.desktops}/share/xsessions + SessionCommand=${dmcfg.sessionData.wrapper} + SessionDir=${dmcfg.sessionData.desktops}/share/xsessions XauthPath=${pkgs.xorg.xauth}/bin/xauth DisplayCommand=${Xsetup} DisplayStopCommand=${Xstop} @@ -59,26 +59,27 @@ let [Wayland] EnableHidpi=${if cfg.enableHidpi then "true" else "false"} - SessionDir=${dmcfg.session.desktops}/share/wayland-sessions + SessionDir=${dmcfg.sessionData.desktops}/share/wayland-sessions ${optionalString cfg.autoLogin.enable '' [Autologin] User=${cfg.autoLogin.user} - Session=${defaultSessionName}.desktop + Session=${autoLoginSessionName}.desktop Relogin=${boolToString cfg.autoLogin.relogin} ''} ${cfg.extraConfig} ''; - defaultSessionName = - let - dm = xcfg.desktopManager.default; - wm = xcfg.windowManager.default; - in dm + optionalString (wm != "none") ("+" + wm); + autoLoginSessionName = dmcfg.sessionData.autologinSession; in { + imports = [ + (mkRemovedOptionModule [ "services" "xserver" "displayManager" "sddm" "themes" ] + "Set the option `services.xserver.displayManager.sddm.package' instead.") + ]; + options = { services.xserver.displayManager.sddm = { @@ -205,11 +206,9 @@ in SDDM auto-login requires services.xserver.displayManager.sddm.autoLogin.user to be set ''; } - { assertion = cfg.autoLogin.enable -> elem defaultSessionName dmcfg.session.names; + { assertion = cfg.autoLogin.enable -> autoLoginSessionName != null; message = '' - SDDM auto-login requires that services.xserver.desktopManager.default and - services.xserver.windowManager.default are set to valid values. The current - default session: ${defaultSessionName} is not valid. + SDDM auto-login requires that services.xserver.displayManager.defaultSession is set. ''; } ]; diff --git a/nixos/modules/services/x11/display-managers/startx.nix b/nixos/modules/services/x11/display-managers/startx.nix index 570469843586a..3980203b9457f 100644 --- a/nixos/modules/services/x11/display-managers/startx.nix +++ b/nixos/modules/services/x11/display-managers/startx.nix @@ -15,6 +15,7 @@ in options = { services.xserver.displayManager.startx = { enable = mkOption { + type = types.bool; default = false; description = '' Whether to enable the dummy "startx" pseudo-display manager, diff --git a/nixos/modules/services/x11/extra-layouts.nix b/nixos/modules/services/x11/extra-layouts.nix index 1af98a1318bb3..f48216ff446f5 100644 --- a/nixos/modules/services/x11/extra-layouts.nix +++ b/nixos/modules/services/x11/extra-layouts.nix @@ -141,7 +141,7 @@ in }); xkbcomp = super.xorg.xkbcomp.overrideAttrs (old: { - configureFlags = "--with-xkb-config-root=${self.xkb_patched}/share/X11/xkb"; + configureFlags = [ "--with-xkb-config-root=${self.xkb_patched}/share/X11/xkb" ]; }); }; @@ -158,6 +158,12 @@ in }); + environment.sessionVariables = { + # runtime override supported by multiple libraries e. g. libxkbcommon + # https://xkbcommon.org/doc/current/group__include-path.html + XKB_CONFIG_ROOT = "${pkgs.xkb_patched}/etc/X11/xkb"; + }; + services.xserver = { xkbDir = "${pkgs.xkb_patched}/etc/X11/xkb"; exportConfiguration = config.services.xserver.displayManager.startx.enable; diff --git a/nixos/modules/services/x11/gdk-pixbuf.nix b/nixos/modules/services/x11/gdk-pixbuf.nix index 9ad926369ec77..e6a24a2f1a308 100644 --- a/nixos/modules/services/x11/gdk-pixbuf.nix +++ b/nixos/modules/services/x11/gdk-pixbuf.nix @@ -19,7 +19,7 @@ let continue fi GDK_PIXBUF_MODULEDIR="$module_dir" \ - ${pkgs.gdk-pixbuf.dev}/bin/gdk-pixbuf-query-loaders + ${pkgs.stdenv.hostPlatform.emulator pkgs.buildPackages} ${pkgs.gdk-pixbuf.dev}/bin/gdk-pixbuf-query-loaders done ) > "$out" ''; @@ -37,7 +37,7 @@ in # If there is any package configured in modulePackages, we generate the # loaders.cache based on that and set the environment variable # GDK_PIXBUF_MODULE_FILE to point to it. - config = mkIf (cfg.modulePackages != []) { + config = mkIf (cfg.modulePackages != [] || pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform) { environment.variables = { GDK_PIXBUF_MODULE_FILE = "${loadersCache}"; }; diff --git a/nixos/modules/services/x11/hardware/libinput.nix b/nixos/modules/services/x11/hardware/libinput.nix index 71065dfc26bb7..9548ecb8ef6da 100644 --- a/nixos/modules/services/x11/hardware/libinput.nix +++ b/nixos/modules/services/x11/hardware/libinput.nix @@ -184,7 +184,11 @@ in { '' Option "DragLockButtons" "L1 B1 L2 B2" ''; - description = "Additional options for libinput touchpad driver."; + description = '' + Additional options for libinput touchpad driver. See + <citerefentry><refentrytitle>libinput</refentrytitle><manvolnum>4</manvolnum></citerefentry> + for available options."; + ''; }; }; @@ -198,12 +202,13 @@ in { environment.systemPackages = [ pkgs.xorg.xf86inputlibinput ]; - environment.etc = [ - (let cfgPath = "X11/xorg.conf.d/40-libinput.conf"; in { - source = pkgs.xorg.xf86inputlibinput.out + "/share/" + cfgPath; - target = cfgPath; - }) - ]; + environment.etc = + let cfgPath = "X11/xorg.conf.d/40-libinput.conf"; + in { + ${cfgPath} = { + source = pkgs.xorg.xf86inputlibinput.out + "/share/" + cfgPath; + }; + }; services.udev.packages = [ pkgs.libinput.out ]; diff --git a/nixos/modules/services/x11/hardware/multitouch.nix b/nixos/modules/services/x11/hardware/multitouch.nix deleted file mode 100644 index c03bb3b494fb5..0000000000000 --- a/nixos/modules/services/x11/hardware/multitouch.nix +++ /dev/null @@ -1,94 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; - -let cfg = config.services.xserver.multitouch; - disabledTapConfig = '' - Option "MaxTapTime" "0" - Option "MaxTapMove" "0" - Option "TapButton1" "0" - Option "TapButton2" "0" - Option "TapButton3" "0" - ''; -in { - - options = { - - services.xserver.multitouch = { - - enable = mkOption { - default = false; - description = "Whether to enable multitouch touchpad support."; - }; - - invertScroll = mkOption { - default = false; - type = types.bool; - description = "Whether to invert scrolling direction à la OSX Lion"; - }; - - ignorePalm = mkOption { - default = false; - type = types.bool; - description = "Whether to ignore touches detected as being the palm (i.e when typing)"; - }; - - tapButtons = mkOption { - type = types.bool; - default = true; - description = "Whether to enable tap buttons."; - }; - - buttonsMap = mkOption { - type = types.listOf types.int; - default = [3 2 0]; - example = [1 3 2]; - description = "Remap touchpad buttons."; - apply = map toString; - }; - - additionalOptions = mkOption { - type = types.str; - default = ""; - example = '' - Option "ScaleDistance" "50" - Option "RotateDistance" "60" - ''; - description = '' - Additional options for mtrack touchpad driver. - ''; - }; - - }; - - }; - - config = mkIf cfg.enable { - - services.xserver.modules = [ pkgs.xf86_input_mtrack ]; - - services.xserver.config = - '' - # Automatically enable the multitouch driver - Section "InputClass" - MatchIsTouchpad "on" - Identifier "Touchpads" - Driver "mtrack" - Option "IgnorePalm" "${boolToString cfg.ignorePalm}" - Option "ClickFinger1" "${builtins.elemAt cfg.buttonsMap 0}" - Option "ClickFinger2" "${builtins.elemAt cfg.buttonsMap 1}" - Option "ClickFinger3" "${builtins.elemAt cfg.buttonsMap 2}" - ${optionalString (!cfg.tapButtons) disabledTapConfig} - ${optionalString cfg.invertScroll '' - Option "ScrollUpButton" "5" - Option "ScrollDownButton" "4" - Option "ScrollLeftButton" "7" - Option "ScrollRightButton" "6" - ''} - ${cfg.additionalOptions} - EndSection - ''; - - }; - -} diff --git a/nixos/modules/services/x11/hardware/wacom.nix b/nixos/modules/services/x11/hardware/wacom.nix index a27889c36a70c..dad2b308d1b49 100644 --- a/nixos/modules/services/x11/hardware/wacom.nix +++ b/nixos/modules/services/x11/hardware/wacom.nix @@ -15,6 +15,7 @@ in services.xserver.wacom = { enable = mkOption { + type = types.bool; default = false; description = '' Whether to enable the Wacom touchscreen/digitizer/tablet. diff --git a/nixos/modules/services/x11/imwheel.nix b/nixos/modules/services/x11/imwheel.nix new file mode 100644 index 0000000000000..3923df498e794 --- /dev/null +++ b/nixos/modules/services/x11/imwheel.nix @@ -0,0 +1,68 @@ +{ config, lib, pkgs, ... }: +with lib; +let + cfg = config.services.xserver.imwheel; +in + { + options = { + services.xserver.imwheel = { + enable = mkEnableOption "IMWheel service"; + + extraOptions = mkOption { + type = types.listOf types.str; + default = [ "--buttons=45" ]; + example = [ "--debug" ]; + description = '' + Additional command-line arguments to pass to + <command>imwheel</command>. + ''; + }; + + rules = mkOption { + type = types.attrsOf types.str; + default = {}; + example = literalExample '' + ".*" = ''' + None, Up, Button4, 8 + None, Down, Button5, 8 + Shift_L, Up, Shift_L|Button4, 4 + Shift_L, Down, Shift_L|Button5, 4 + Control_L, Up, Control_L|Button4 + Control_L, Down, Control_L|Button5 + '''; + ''; + description = '' + Window class translation rules. + /etc/X11/imwheelrc is generated based on this config + which means this config is global for all users. + See <link xlink:href="http://imwheel.sourceforge.net/imwheel.1.html">offical man pages</link> + for more informations. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ pkgs.imwheel ]; + + environment.etc."X11/imwheel/imwheelrc".source = + pkgs.writeText "imwheelrc" (concatStringsSep "\n\n" + (mapAttrsToList + (rule: conf: "\"${rule}\"\n${conf}") cfg.rules + )); + + systemd.user.services.imwheel = { + description = "imwheel service"; + wantedBy = [ "graphical-session.target" ]; + partOf = [ "graphical-session.target" ]; + serviceConfig = { + ExecStart = "${pkgs.imwheel}/bin/imwheel " + escapeShellArgs ([ + "--detach" + "--kill" + ] ++ cfg.extraOptions); + ExecStop = "${pkgs.procps}/bin/pkill imwheel"; + Restart = "on-failure"; + }; + }; + }; + } diff --git a/nixos/modules/services/x11/compton.nix b/nixos/modules/services/x11/picom.nix index a94a76ff0c0f2..e3bd21be73e4a 100644 --- a/nixos/modules/services/x11/compton.nix +++ b/nixos/modules/services/x11/picom.nix @@ -5,7 +5,7 @@ with builtins; let - cfg = config.services.compton; + cfg = config.services.picom; pairOf = x: with types; addCheck (listOf x) (y: length y == 2); @@ -31,20 +31,24 @@ let (key: value: "${toString key}=${mkValueString value};") v) + " }" - else abort "compton.mkValueString: unexpected type (v = ${v})"; + else abort "picom.mkValueString: unexpected type (v = ${v})"; in "${escape [ sep ] k}${sep}${mkValueString v};") attrs); - configFile = pkgs.writeText "compton.conf" (toConf cfg.settings); + configFile = pkgs.writeText "picom.conf" (toConf cfg.settings); in { - options.services.compton = { + imports = [ + (mkAliasOptionModule [ "services" "compton" ] [ "services" "picom" ]) + ]; + + options.services.picom = { enable = mkOption { type = types.bool; default = false; description = '' - Whether of not to enable Compton as the X.org composite manager. + Whether of not to enable Picom as the X.org composite manager. ''; }; @@ -85,7 +89,7 @@ in { ]; description = '' List of conditions of windows that should not be faded. - See <literal>compton(1)</literal> man page for more examples. + See <literal>picom(1)</literal> man page for more examples. ''; }; @@ -125,7 +129,7 @@ in { ]; description = '' List of conditions of windows that should have no shadow. - See <literal>compton(1)</literal> man page for more examples. + See <literal>picom(1)</literal> man page for more examples. ''; }; @@ -192,7 +196,7 @@ in { apply = x: let res = x != "none"; - msg = "The type of services.compton.vSync has changed to bool:" + msg = "The type of services.picom.vSync has changed to bool:" + " interpreting ${x} as ${boolToString res}"; in if isBool x then x @@ -222,13 +226,13 @@ in { type = loaOf (types.either configTypes (loaOf (types.either configTypes (loaOf configTypes)))); default = {}; description = '' - Additional Compton configuration. + Additional Picom configuration. ''; }; }; config = mkIf cfg.enable { - services.compton.settings = let + services.picom.settings = let # Hard conversion to float, literally lib.toInt but toFloat toFloat = str: let may_be_float = builtins.fromJSON str; @@ -264,8 +268,8 @@ in { refresh-rate = mkDefault cfg.refreshRate; }; - systemd.user.services.compton = { - description = "Compton composite manager"; + systemd.user.services.picom = { + description = "Picom composite manager"; wantedBy = [ "graphical-session.target" ]; partOf = [ "graphical-session.target" ]; @@ -275,13 +279,15 @@ in { }; serviceConfig = { - ExecStart = "${pkgs.compton}/bin/compton --config ${configFile}"; + ExecStart = "${pkgs.picom}/bin/picom --config ${configFile}"; RestartSec = 3; Restart = "always"; }; }; - environment.systemPackages = [ pkgs.compton ]; + environment.systemPackages = [ pkgs.picom ]; }; + meta.maintainers = with lib.maintainers; [ rnhmjoj ]; + } diff --git a/nixos/modules/services/x11/unclutter.nix b/nixos/modules/services/x11/unclutter.nix index 5f16a680050dd..56e30c79d1f1a 100644 --- a/nixos/modules/services/x11/unclutter.nix +++ b/nixos/modules/services/x11/unclutter.nix @@ -32,7 +32,7 @@ in { default = 1; }; - threeshold = mkOption { + threshold = mkOption { description = "Minimum number of pixels considered cursor movement"; type = types.int; default = 1; @@ -61,7 +61,7 @@ in { serviceConfig.ExecStart = '' ${cfg.package}/bin/unclutter \ -idle ${toString cfg.timeout} \ - -jitter ${toString (cfg.threeshold - 1)} \ + -jitter ${toString (cfg.threshold - 1)} \ ${optionalString cfg.keystroke "-keystroke"} \ ${concatMapStrings (x: " -"+x) cfg.extraOptions} \ -not ${concatStringsSep " " cfg.excluded} \ @@ -71,4 +71,12 @@ in { serviceConfig.Restart = "always"; }; }; + + imports = [ + (mkRenamedOptionModule [ "services" "unclutter" "threeshold" ] + [ "services" "unclutter" "threshold" ]) + ]; + + meta.maintainers = with lib.maintainers; [ rnhmjoj ]; + } diff --git a/nixos/modules/services/x11/urxvtd.nix b/nixos/modules/services/x11/urxvtd.nix index d916fa5bb393c..867ac38a944ff 100644 --- a/nixos/modules/services/x11/urxvtd.nix +++ b/nixos/modules/services/x11/urxvtd.nix @@ -18,10 +18,10 @@ in { }; package = mkOption { - default = pkgs.rxvt_unicode-with-plugins; - defaultText = "pkgs.rxvt_unicode-with-plugins"; + default = pkgs.rxvt-unicode; + defaultText = "pkgs.rxvt-unicode"; description = '' - Package to install. Usually pkgs.rxvt_unicode-with-plugins or pkgs.rxvt_unicode + Package to install. Usually pkgs.rxvt-unicode. ''; type = types.package; }; @@ -45,4 +45,6 @@ in { environment.variables.RXVT_SOCKET = "/run/user/$(id -u)/urxvtd-socket"; }; + meta.maintainers = with lib.maintainers; [ rnhmjoj ]; + } diff --git a/nixos/modules/services/x11/window-managers/default.nix b/nixos/modules/services/x11/window-managers/default.nix index c17f3830d0e9a..04a9fc46628cf 100644 --- a/nixos/modules/services/x11/window-managers/default.nix +++ b/nixos/modules/services/x11/window-managers/default.nix @@ -59,15 +59,14 @@ in }; default = mkOption { - type = types.str; - default = "none"; + type = types.nullOr types.str; + default = null; example = "wmii"; - description = "Default window manager loaded if none have been chosen."; - apply = defaultWM: - if any (w: w.name == defaultWM) cfg.session then - defaultWM - else - throw "Default window manager (${defaultWM}) not found."; + description = '' + <emphasis role="strong">Deprecated</emphasis>, please use <xref linkend="opt-services.xserver.displayManager.defaultSession"/> instead. + + Default window manager loaded if none have been chosen. + ''; }; }; diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix index 70f01dbdbf597..6aec1c0753a2f 100644 --- a/nixos/modules/services/x11/xserver.nix +++ b/nixos/modules/services/x11/xserver.nix @@ -149,6 +149,8 @@ in [ ./display-managers/default.nix ./window-managers/default.nix ./desktop-managers/default.nix + (mkRemovedOptionModule [ "services" "xserver" "startGnuPGAgent" ] + "See the 16.09 release notes for more information.") ]; @@ -329,9 +331,9 @@ in }; xkbOptions = mkOption { - type = types.str; + type = types.commas; default = "terminate:ctrl_alt_bksp"; - example = "grp:caps_toggle, grp_led:scroll"; + example = "grp:caps_toggle,grp_led:scroll"; description = '' X keyboard options; layout switching goes here. ''; @@ -554,8 +556,7 @@ in services.xserver.displayManager.lightdm.enable = let dmconf = cfg.displayManager; - default = !( dmconf.auto.enable - || dmconf.gdm.enable + default = !(dmconf.gdm.enable || dmconf.sddm.enable || dmconf.xpra.enable ); in mkIf (default) true; @@ -572,7 +573,7 @@ in then { modules = [xorg.${"xf86video" + name}]; } else null) knownVideoDrivers; - in optional (driver != null) ({ inherit name; modules = []; driverName = name; } // driver)); + in optional (driver != null) ({ inherit name; modules = []; driverName = name; display = true; } // driver)); assertions = [ { assertion = config.security.polkit.enable; @@ -588,19 +589,15 @@ in ]; environment.etc = - (optionals cfg.exportConfiguration - [ { source = "${configFile}"; - target = "X11/xorg.conf"; - } + (optionalAttrs cfg.exportConfiguration + { + "X11/xorg.conf".source = "${configFile}"; # -xkbdir command line option does not seems to be passed to xkbcomp. - { source = "${cfg.xkbDir}"; - target = "X11/xkb"; - } - ]) + "X11/xkb".source = "${cfg.xkbDir}"; + }) # localectl looks into 00-keyboard.conf - ++ [ - { - text = '' + //{ + "X11/xorg.conf.d/00-keyboard.conf".text = '' Section "InputClass" Identifier "Keyboard catchall" MatchIsKeyboard "on" @@ -610,16 +607,12 @@ in Option "XkbVariant" "${cfg.xkbVariant}" EndSection ''; - target = "X11/xorg.conf.d/00-keyboard.conf"; } - ] # Needed since 1.18; see https://bugs.freedesktop.org/show_bug.cgi?id=89023#c5 - ++ (let cfgPath = "/X11/xorg.conf.d/10-evdev.conf"; in - [{ - source = xorg.xf86inputevdev.out + "/share" + cfgPath; - target = cfgPath; - }] - ); + // (let cfgPath = "/X11/xorg.conf.d/10-evdev.conf"; in + { + ${cfgPath}.source = xorg.xf86inputevdev.out + "/share" + cfgPath; + }); environment.systemPackages = [ xorg.xorgserver.out @@ -658,8 +651,7 @@ in systemd.services.display-manager = { description = "X11 Server"; - after = [ "systemd-udev-settle.service" "acpid.service" "systemd-logind.service" ]; - wants = [ "systemd-udev-settle.service" ]; + after = [ "acpid.service" "systemd-logind.service" ]; restartIfChanged = false; @@ -747,7 +739,7 @@ in ${cfg.serverLayoutSection} # Reference the Screen sections for each driver. This will # cause the X server to try each in turn. - ${flip concatMapStrings cfg.drivers (d: '' + ${flip concatMapStrings (filter (d: d.display) cfg.drivers) (d: '' Screen "Screen-${d.name}[0]" '')} EndSection @@ -771,42 +763,44 @@ in ${driver.deviceSection or ""} ${xrandrDeviceSection} EndSection + ${optionalString driver.display '' + + Section "Screen" + Identifier "Screen-${driver.name}[0]" + Device "Device-${driver.name}[0]" + ${optionalString (cfg.monitorSection != "") '' + Monitor "Monitor[0]" + ''} + + ${cfg.screenSection} + ${driver.screenSection or ""} + + ${optionalString (cfg.defaultDepth != 0) '' + DefaultDepth ${toString cfg.defaultDepth} + ''} + + ${optionalString + (driver.name != "virtualbox" && + (cfg.resolutions != [] || + cfg.extraDisplaySettings != "" || + cfg.virtualScreen != null)) + (let + f = depth: + '' + SubSection "Display" + Depth ${toString depth} + ${optionalString (cfg.resolutions != []) + "Modes ${concatMapStrings (res: ''"${toString res.x}x${toString res.y}"'') cfg.resolutions}"} + ${cfg.extraDisplaySettings} + ${optionalString (cfg.virtualScreen != null) + "Virtual ${toString cfg.virtualScreen.x} ${toString cfg.virtualScreen.y}"} + EndSubSection + ''; + in concatMapStrings f [8 16 24] + )} - Section "Screen" - Identifier "Screen-${driver.name}[0]" - Device "Device-${driver.name}[0]" - ${optionalString (cfg.monitorSection != "") '' - Monitor "Monitor[0]" - ''} - - ${cfg.screenSection} - ${driver.screenSection or ""} - - ${optionalString (cfg.defaultDepth != 0) '' - DefaultDepth ${toString cfg.defaultDepth} - ''} - - ${optionalString - (driver.name != "virtualbox" && - (cfg.resolutions != [] || - cfg.extraDisplaySettings != "" || - cfg.virtualScreen != null)) - (let - f = depth: - '' - SubSection "Display" - Depth ${toString depth} - ${optionalString (cfg.resolutions != []) - "Modes ${concatMapStrings (res: ''"${toString res.x}x${toString res.y}"'') cfg.resolutions}"} - ${cfg.extraDisplaySettings} - ${optionalString (cfg.virtualScreen != null) - "Virtual ${toString cfg.virtualScreen.x} ${toString cfg.virtualScreen.y}"} - EndSubSection - ''; - in concatMapStrings f [8 16 24] - )} - - EndSection + EndSection + ''} '')} ${xrandrMonitorSections} diff --git a/nixos/modules/system/activation/no-clone.nix b/nixos/modules/system/activation/no-clone.nix index 7f4584435266b..912420347dc0f 100644 --- a/nixos/modules/system/activation/no-clone.nix +++ b/nixos/modules/system/activation/no-clone.nix @@ -4,6 +4,5 @@ with lib; { boot.loader.grub.device = mkOverride 0 "nodev"; - nesting.children = mkOverride 0 []; - nesting.clone = mkOverride 0 []; + specialisation = mkOverride 0 {}; } diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl index 12a80a12d19f4..b82d69b3bb853 100644 --- a/nixos/modules/system/activation/switch-to-configuration.pl +++ b/nixos/modules/system/activation/switch-to-configuration.pl @@ -183,7 +183,7 @@ while (my ($unit, $state) = each %{$activePrev}) { # active after the system has resumed, which probably # should not be the case. Just ignore it. if ($unit ne "suspend.target" && $unit ne "hibernate.target" && $unit ne "hybrid-sleep.target") { - unless (boolIsTrue($unitInfo->{'RefuseManualStart'} // "no")) { + unless (boolIsTrue($unitInfo->{'RefuseManualStart'} // "no") || boolIsTrue($unitInfo->{'X-OnlyManualStart'} // "no")) { $unitsToStart{$unit} = 1; recordUnit($startListFile, $unit); # Don't spam the user with target units that always get started. @@ -214,17 +214,7 @@ while (my ($unit, $state) = each %{$activePrev}) { # Reload the changed mount unit to force a remount. $unitsToReload{$unit} = 1; recordUnit($reloadListFile, $unit); - } elsif ($unit =~ /\.socket$/) { - my $unitInfo = parseUnit($newUnitFile); - # If a socket unit has been changed, the corresponding - # service unit has to be stopped before the socket can - # be restarted. The service will be started again on demand. - my $serviceUnit = $unitInfo->{'Unit'} // "$baseName.service"; - $unitsToStop{$serviceUnit} = 1; - $unitsToStop{$unit} = 1; - $unitsToStart{$unit} = 1; - recordUnit($startListFile, $unit); - } elsif ($unit =~ /\.path$/ || $unit =~ /\.slice$/) { + } elsif ($unit =~ /\.socket$/ || $unit =~ /\.path$/ || $unit =~ /\.slice$/) { # FIXME: do something? } else { my $unitInfo = parseUnit($newUnitFile); @@ -232,7 +222,7 @@ while (my ($unit, $state) = each %{$activePrev}) { $unitsToReload{$unit} = 1; recordUnit($reloadListFile, $unit); } - elsif (!boolIsTrue($unitInfo->{'X-RestartIfChanged'} // "yes") || boolIsTrue($unitInfo->{'RefuseManualStop'} // "no") ) { + elsif (!boolIsTrue($unitInfo->{'X-RestartIfChanged'} // "yes") || boolIsTrue($unitInfo->{'RefuseManualStop'} // "no") || boolIsTrue($unitInfo->{'X-OnlyManualStart'} // "no")) { $unitsToSkip{$unit} = 1; } else { if (!boolIsTrue($unitInfo->{'X-StopIfChanged'} // "yes")) { diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix index f67d290056169..f6739977fa4f1 100644 --- a/nixos/modules/system/activation/top-level.nix +++ b/nixos/modules/system/activation/top-level.nix @@ -11,20 +11,16 @@ let # you can provide an easy way to boot the same configuration # as you use, but with another kernel # !!! fix this - cloner = inheritParent: list: - map (childConfig: + children = mapAttrs (childName: childConfig: (import ../../../lib/eval-config.nix { inherit baseModules; + system = config.nixpkgs.initialSystem; modules = - (optionals inheritParent modules) + (optionals childConfig.inheritParentConfig modules) ++ [ ./no-clone.nix ] - ++ [ childConfig ]; + ++ [ childConfig.configuration ]; }).config.system.build.toplevel - ) list; - - children = - cloner false config.nesting.children - ++ cloner true config.nesting.clone; + ) config.specialisation; systemBuilder = let @@ -74,14 +70,11 @@ let echo -n "$configurationName" > $out/configuration-name echo -n "systemd ${toString config.systemd.package.interfaceVersion}" > $out/init-interface-version echo -n "$nixosLabel" > $out/nixos-version - echo -n "${pkgs.stdenv.hostPlatform.system}" > $out/system + echo -n "${config.boot.kernelPackages.stdenv.hostPlatform.system}" > $out/system - mkdir $out/fine-tune - childCount=0 - for i in $children; do - childCount=$(( childCount + 1 )) - ln -s $i $out/fine-tune/child-$childCount - done + mkdir $out/specialisation + ${concatStringsSep "\n" + (mapAttrsToList (name: path: "ln -s ${path} $out/specialisation/${name}") children)} mkdir $out/bin export localeArchive="${config.i18n.glibcLocales}/lib/locale/locale-archive" @@ -111,7 +104,6 @@ let shell = "${pkgs.bash}/bin/sh"; su = "${pkgs.shadow.su}/bin/su"; - inherit children; kernelParams = config.boot.kernelParams; installBootLoader = config.system.build.installBootLoader @@ -142,6 +134,11 @@ let in { + imports = [ + (mkRemovedOptionModule [ "nesting" "clone" ] "Use `specialisation.«name» = { inheritParentConfig = true; configuration = { ... }; }` instead.") + (mkRemovedOptionModule [ "nesting" "children" ] "Use `specialisation.«name».configuration = { ... }` instead.") + ]; + options = { system.build = mkOption { @@ -153,26 +150,35 @@ in ''; }; - nesting.children = mkOption { - default = []; - description = '' - Additional configurations to build. - ''; - }; - - nesting.clone = mkOption { - default = []; + specialisation = mkOption { + default = {}; + example = lib.literalExample "{ fewJobsManyCores.configuration = { nix.buildCores = 0; nix.maxJobs = 1; }; }"; description = '' - Additional configurations to build based on the current - configuration which then has a lower priority. + Additional configurations to build. If + <literal>inheritParentConfig</literal> is true, the system + will be based on the overall system configuration. - To switch to a cloned configuration (e.g. <literal>child-1</literal>) - at runtime, run + To switch to a specialised configuration + (e.g. <literal>fewJobsManyCores</literal>) at runtime, run: <programlisting> - # sudo /run/current-system/fine-tune/child-1/bin/switch-to-configuration test + # sudo /run/current-system/specialisation/fewJobsManyCores/bin/switch-to-configuration test </programlisting> ''; + type = types.attrsOf (types.submodule ( + { ... }: { + options.inheritParentConfig = mkOption { + type = types.bool; + default = true; + description = "Include the entire system's configuration. Set to false to make a completely differently configured system."; + }; + + options.configuration = mkOption { + default = {}; + description = "Arbitrary NixOS configuration options."; + }; + }) + ); }; system.boot.loader.id = mkOption { diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix index a32c9dc1f2b49..a677ab4cb71a7 100644 --- a/nixos/modules/system/boot/binfmt.nix +++ b/nixos/modules/system/boot/binfmt.nix @@ -134,6 +134,10 @@ let }; in { + imports = [ + (lib.mkRenamedOptionModule [ "boot" "binfmtMiscRegistrations" ] [ "boot" "binfmt" "registrations" ]) + ]; + options = { boot.binfmt = { registrations = mkOption { diff --git a/nixos/modules/system/boot/grow-partition.nix b/nixos/modules/system/boot/grow-partition.nix index 8c9b1502558af..71a86c74772e7 100644 --- a/nixos/modules/system/boot/grow-partition.nix +++ b/nixos/modules/system/boot/grow-partition.nix @@ -7,6 +7,9 @@ with lib; { + imports = [ + (mkRenamedOptionModule [ "virtualisation" "growPartition" ] [ "boot" "growPartition" ]) + ]; options = { boot.growPartition = mkEnableOption "grow the root partition on boot"; diff --git a/nixos/modules/system/boot/initrd-network.nix b/nixos/modules/system/boot/initrd-network.nix index cb8fc957a990b..0ab6e626b340e 100644 --- a/nixos/modules/system/boot/initrd-network.nix +++ b/nixos/modules/system/boot/initrd-network.nix @@ -6,7 +6,11 @@ let cfg = config.boot.initrd.network; - dhcpinterfaces = lib.attrNames (lib.filterAttrs (iface: v: v.useDHCP == true) (config.networking.interfaces or {})); + dhcpInterfaces = lib.attrNames (lib.filterAttrs (iface: v: v.useDHCP == true) (config.networking.interfaces or {})); + doDhcp = config.networking.useDHCP || dhcpInterfaces != []; + dhcpIfShellExpr = if config.networking.useDHCP + then "$(ls /sys/class/net/ | grep -v ^lo$)" + else lib.concatMapStringsSep " " lib.escapeShellArg dhcpInterfaces; udhcpcScript = pkgs.writeScript "udhcp-script" '' @@ -62,6 +66,16 @@ in ''; }; + boot.initrd.network.flushBeforeStage2 = mkOption { + type = types.bool; + default = true; + description = '' + Whether to clear the configuration of the interfaces that were set up in + the initrd right before stage 2 takes over. Stage 2 will do the regular network + configuration based on the NixOS networking options. + ''; + }; + boot.initrd.network.udhcpc.extraArgs = mkOption { default = []; type = types.listOf types.str; @@ -89,49 +103,45 @@ in boot.initrd.kernelModules = [ "af_packet" ]; boot.initrd.extraUtilsCommands = '' - copy_bin_and_libs ${pkgs.mkinitcpio-nfs-utils}/bin/ipconfig + copy_bin_and_libs ${pkgs.klibc}/lib/klibc/bin.static/ipconfig ''; boot.initrd.preLVMCommands = mkBefore ( # Search for interface definitions in command line. '' + ifaces="" for o in $(cat /proc/cmdline); do case $o in ip=*) - ipconfig $o && hasNetwork=1 + ipconfig $o && ifaces="$ifaces $(echo $o | cut -d: -f6)" ;; esac done '' # Otherwise, use DHCP. - + optionalString (config.networking.useDHCP || dhcpinterfaces != []) '' - if [ -z "$hasNetwork" ]; then - - # Bring up all interfaces. - for iface in $(ls /sys/class/net/); do - echo "bringing up network interface $iface..." - ip link set "$iface" up - done + + optionalString doDhcp '' + # Bring up all interfaces. + for iface in ${dhcpIfShellExpr}; do + echo "bringing up network interface $iface..." + ip link set "$iface" up && ifaces="$ifaces $iface" + done - # Acquire DHCP leases. - for iface in ${ if config.networking.useDHCP then - "$(ls /sys/class/net/ | grep -v ^lo$)" - else - lib.concatMapStringsSep " " lib.escapeShellArg dhcpinterfaces - }; do - echo "acquiring IP address via DHCP on $iface..." - udhcpc --quit --now -i $iface -O staticroutes --script ${udhcpcScript} ${udhcpcArgs} && hasNetwork=1 - done - fi + # Acquire DHCP leases. + for iface in ${dhcpIfShellExpr}; do + echo "acquiring IP address via DHCP on $iface..." + udhcpc --quit --now -i $iface -O staticroutes --script ${udhcpcScript} ${udhcpcArgs} + done '' - + '' - if [ -n "$hasNetwork" ]; then - echo "networking is up!" - ${cfg.postCommands} - fi - ''); + + cfg.postCommands); + + boot.initrd.postMountCommands = mkIf cfg.flushBeforeStage2 '' + for iface in $ifaces; do + ip address flush "$iface" + ip link down "$iface" + done + ''; }; diff --git a/nixos/modules/system/boot/initrd-ssh.nix b/nixos/modules/system/boot/initrd-ssh.nix index 2d3e3b05c9807..60760487a1d24 100644 --- a/nixos/modules/system/boot/initrd-ssh.nix +++ b/nixos/modules/system/boot/initrd-ssh.nix @@ -10,19 +10,21 @@ in { - options = { - - boot.initrd.network.ssh.enable = mkOption { + options.boot.initrd.network.ssh = { + enable = mkOption { type = types.bool; default = false; description = '' Start SSH service during initrd boot. It can be used to debug failing boot on a remote server, enter pasphrase for an encrypted partition etc. Service is killed when stage-1 boot is finished. + + The sshd configuration is largely inherited from + <option>services.openssh</option>. ''; }; - boot.initrd.network.ssh.port = mkOption { + port = mkOption { type = types.int; default = 22; description = '' @@ -30,7 +32,7 @@ in ''; }; - boot.initrd.network.ssh.shell = mkOption { + shell = mkOption { type = types.str; default = "/bin/ash"; description = '' @@ -38,95 +40,163 @@ in ''; }; - boot.initrd.network.ssh.hostRSAKey = mkOption { - type = types.nullOr types.path; - default = null; - description = '' - RSA SSH private key file in the Dropbear format. - - WARNING: Unless your bootloader supports initrd secrets, this key is - contained insecurely in the global Nix store. Do NOT use your regular - SSH host private keys for this purpose or you'll expose them to - regular users! - ''; - }; - - boot.initrd.network.ssh.hostDSSKey = mkOption { - type = types.nullOr types.path; - default = null; + hostKeys = mkOption { + type = types.listOf (types.either types.str types.path); + default = []; + example = [ + "/etc/secrets/initrd/ssh_host_rsa_key" + "/etc/secrets/initrd/ssh_host_ed25519_key" + ]; description = '' - DSS SSH private key file in the Dropbear format. - - WARNING: Unless your bootloader supports initrd secrets, this key is - contained insecurely in the global Nix store. Do NOT use your regular - SSH host private keys for this purpose or you'll expose them to - regular users! + Specify SSH host keys to import into the initrd. + + To generate keys, use + <citerefentry><refentrytitle>ssh-keygen</refentrytitle><manvolnum>1</manvolnum></citerefentry>: + + <screen> + <prompt># </prompt>ssh-keygen -t rsa -N "" -f /etc/secrets/initrd/ssh_host_rsa_key + <prompt># </prompt>ssh-keygen -t ed25519 -N "" -f /etc/secrets/initrd/ssh_host_ed25519_key + </screen> + + <warning> + <para> + Unless your bootloader supports initrd secrets, these keys + are stored insecurely in the global Nix store. Do NOT use + your regular SSH host private keys for this purpose or + you'll expose them to regular users! + </para> + <para> + Additionally, even if your initrd supports secrets, if + you're using initrd SSH to unlock an encrypted disk then + using your regular host keys exposes the private keys on + your unencrypted boot partition. + </para> + </warning> ''; }; - boot.initrd.network.ssh.hostECDSAKey = mkOption { - type = types.nullOr types.path; - default = null; - description = '' - ECDSA SSH private key file in the Dropbear format. - - WARNING: Unless your bootloader supports initrd secrets, this key is - contained insecurely in the global Nix store. Do NOT use your regular - SSH host private keys for this purpose or you'll expose them to - regular users! - ''; - }; - - boot.initrd.network.ssh.authorizedKeys = mkOption { + authorizedKeys = mkOption { type = types.listOf types.str; default = config.users.users.root.openssh.authorizedKeys.keys; + defaultText = "config.users.users.root.openssh.authorizedKeys.keys"; description = '' Authorized keys for the root user on initrd. - Note that Dropbear doesn't support OpenSSH's Ed25519 key type. ''; }; - }; - config = mkIf (config.boot.initrd.network.enable && cfg.enable) { + imports = + map (opt: mkRemovedOptionModule ([ "boot" "initrd" "network" "ssh" ] ++ [ opt ]) '' + The initrd SSH functionality now uses OpenSSH rather than Dropbear. + + If you want to keep your existing initrd SSH host keys, convert them with + $ dropbearconvert dropbear openssh dropbear_host_$type_key ssh_host_$type_key + and then set options.boot.initrd.network.ssh.hostKeys. + '') [ "hostRSAKey" "hostDSSKey" "hostECDSAKey" ]; + + config = let + # Nix complains if you include a store hash in initrd path names, so + # as an awful hack we drop the first character of the hash. + initrdKeyPath = path: if isString path + then path + else let name = builtins.baseNameOf path; in + builtins.unsafeDiscardStringContext ("/etc/ssh/" + + substring 1 (stringLength name) name); + + sshdCfg = config.services.openssh; + + sshdConfig = '' + Port ${toString cfg.port} + + PasswordAuthentication no + ChallengeResponseAuthentication no + + ${flip concatMapStrings cfg.hostKeys (path: '' + HostKey ${initrdKeyPath path} + '')} + + KexAlgorithms ${concatStringsSep "," sshdCfg.kexAlgorithms} + Ciphers ${concatStringsSep "," sshdCfg.ciphers} + MACs ${concatStringsSep "," sshdCfg.macs} + + LogLevel ${sshdCfg.logLevel} + + ${if sshdCfg.useDns then '' + UseDNS yes + '' else '' + UseDNS no + ''} + ''; + in mkIf (config.boot.initrd.network.enable && cfg.enable) { assertions = [ - { assertion = cfg.authorizedKeys != []; + { + assertion = cfg.authorizedKeys != []; message = "You should specify at least one authorized key for initrd SSH"; } + + { + assertion = cfg.hostKeys != []; + message = '' + You must now pre-generate the host keys for initrd SSH. + See the boot.initrd.network.ssh.hostKeys documentation + for instructions. + ''; + } ]; boot.initrd.extraUtilsCommands = '' - copy_bin_and_libs ${pkgs.dropbear}/bin/dropbear + copy_bin_and_libs ${pkgs.openssh}/bin/sshd cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib ''; boot.initrd.extraUtilsCommandsTest = '' - $out/bin/dropbear -V + # sshd requires a host key to check config, so we pass in the test's + echo -n ${escapeShellArg sshdConfig} | + $out/bin/sshd -t -f /dev/stdin \ + -h ${../../../tests/initrd-network-ssh/ssh_host_ed25519_key} ''; boot.initrd.network.postCommands = '' echo '${cfg.shell}' > /etc/shells echo 'root:x:0:0:root:/root:${cfg.shell}' > /etc/passwd + echo 'sshd:x:1:1:sshd:/var/empty:/bin/nologin' >> /etc/passwd echo 'passwd: files' > /etc/nsswitch.conf - mkdir -p /var/log + mkdir -p /var/log /var/empty touch /var/log/lastlog - mkdir -p /etc/dropbear + mkdir -p /etc/ssh + echo -n ${escapeShellArg sshdConfig} > /etc/ssh/sshd_config + + echo "export PATH=$PATH" >> /etc/profile + echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> /etc/profile mkdir -p /root/.ssh ${concatStrings (map (key: '' echo ${escapeShellArg key} >> /root/.ssh/authorized_keys '') cfg.authorizedKeys)} - dropbear -s -j -k -E -p ${toString cfg.port} ${optionalString (cfg.hostRSAKey == null && cfg.hostDSSKey == null && cfg.hostECDSAKey == null) "-R"} + ${flip concatMapStrings cfg.hostKeys (path: '' + # keys from Nix store are world-readable, which sshd doesn't like + chmod 0600 "${initrdKeyPath path}" + '')} + + /bin/sshd -e ''; - boot.initrd.secrets = - (optionalAttrs (cfg.hostRSAKey != null) { "/etc/dropbear/dropbear_rsa_host_key" = cfg.hostRSAKey; }) // - (optionalAttrs (cfg.hostDSSKey != null) { "/etc/dropbear/dropbear_dss_host_key" = cfg.hostDSSKey; }) // - (optionalAttrs (cfg.hostECDSAKey != null) { "/etc/dropbear/dropbear_ecdsa_host_key" = cfg.hostECDSAKey; }); + boot.initrd.postMountCommands = '' + # Stop sshd cleanly before stage 2. + # + # If you want to keep it around to debug post-mount SSH issues, + # run `touch /.keep_sshd` (either from an SSH session or in + # another initrd hook like preDeviceCommands). + if ! [ -e /.keep_sshd ]; then + pkill -x sshd + fi + ''; + boot.initrd.secrets = listToAttrs + (map (path: nameValuePair (initrdKeyPath path) path) cfg.hostKeys); }; } diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix index 8a309f3bc5fe1..43871f439f7f3 100644 --- a/nixos/modules/system/boot/kernel.nix +++ b/nixos/modules/system/boot/kernel.nix @@ -101,7 +101,12 @@ in type = types.bool; default = false; description = '' - Whether to activate VESA video mode on boot. + (Deprecated) This option, if set, activates the VESA 800x600 video + mode on boot and disables kernel modesetting. It is equivalent to + specifying <literal>[ "vga=0x317" "nomodeset" ]</literal> in the + <option>boot.kernelParams</option> option. This option is + deprecated as of 2020: Xorg now works better with modesetting, and + you might want a different VESA vga setting, anyway. ''; }; @@ -187,140 +192,144 @@ in ###### implementation - config = mkIf (!config.boot.isContainer) { - - system.build = { inherit kernel; }; - - system.modulesTree = [ kernel ] ++ config.boot.extraModulePackages; - - # Implement consoleLogLevel both in early boot and using sysctl - # (so you don't need to reboot to have changes take effect). - boot.kernelParams = - [ "loglevel=${toString config.boot.consoleLogLevel}" ] ++ - optionals config.boot.vesa [ "vga=0x317" "nomodeset" ]; - - boot.kernel.sysctl."kernel.printk" = mkDefault config.boot.consoleLogLevel; - - boot.kernelModules = [ "loop" "atkbd" ]; - - boot.initrd.availableKernelModules = - [ # Note: most of these (especially the SATA/PATA modules) - # shouldn't be included by default since nixos-generate-config - # detects them, but I'm keeping them for now for backwards - # compatibility. - - # Some SATA/PATA stuff. - "ahci" - "sata_nv" - "sata_via" - "sata_sis" - "sata_uli" - "ata_piix" - "pata_marvell" - - # Standard SCSI stuff. - "sd_mod" - "sr_mod" - - # SD cards and internal eMMC drives. - "mmc_block" - - # Support USB keyboards, in case the boot fails and we only have - # a USB keyboard, or for LUKS passphrase prompt. - "uhci_hcd" - "ehci_hcd" - "ehci_pci" - "ohci_hcd" - "ohci_pci" - "xhci_hcd" - "xhci_pci" - "usbhid" - "hid_generic" "hid_lenovo" "hid_apple" "hid_roccat" - "hid_logitech_hidpp" "hid_logitech_dj" - - ] ++ optionals (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [ - # Misc. x86 keyboard stuff. - "pcips2" "atkbd" "i8042" - - # x86 RTC needed by the stage 2 init script. - "rtc_cmos" - ]; - - boot.initrd.kernelModules = - [ # For LVM. - "dm_mod" - ]; - - # The Linux kernel >= 2.6.27 provides firmware. - hardware.firmware = [ kernel ]; - - # Create /etc/modules-load.d/nixos.conf, which is read by - # systemd-modules-load.service to load required kernel modules. - environment.etc = singleton - { target = "modules-load.d/nixos.conf"; - source = kernelModulesConf; - }; - - systemd.services.systemd-modules-load = - { wantedBy = [ "multi-user.target" ]; - restartTriggers = [ kernelModulesConf ]; - serviceConfig = - { # Ignore failed module loads. Typically some of the - # modules in ‘boot.kernelModules’ are "nice to have but - # not required" (e.g. acpi-cpufreq), so we don't want to - # barf on those. - SuccessExitStatus = "0 1"; + config = mkMerge + [ (mkIf config.boot.initrd.enable { + boot.initrd.availableKernelModules = + [ # Note: most of these (especially the SATA/PATA modules) + # shouldn't be included by default since nixos-generate-config + # detects them, but I'm keeping them for now for backwards + # compatibility. + + # Some SATA/PATA stuff. + "ahci" + "sata_nv" + "sata_via" + "sata_sis" + "sata_uli" + "ata_piix" + "pata_marvell" + + # Standard SCSI stuff. + "sd_mod" + "sr_mod" + + # SD cards and internal eMMC drives. + "mmc_block" + + # Support USB keyboards, in case the boot fails and we only have + # a USB keyboard, or for LUKS passphrase prompt. + "uhci_hcd" + "ehci_hcd" + "ehci_pci" + "ohci_hcd" + "ohci_pci" + "xhci_hcd" + "xhci_pci" + "usbhid" + "hid_generic" "hid_lenovo" "hid_apple" "hid_roccat" + "hid_logitech_hidpp" "hid_logitech_dj" + + ] ++ optionals (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [ + # Misc. x86 keyboard stuff. + "pcips2" "atkbd" "i8042" + + # x86 RTC needed by the stage 2 init script. + "rtc_cmos" + ]; + + boot.initrd.kernelModules = + [ # For LVM. + "dm_mod" + ]; + }) + + (mkIf (!config.boot.isContainer) { + system.build = { inherit kernel; }; + + system.modulesTree = [ kernel ] ++ config.boot.extraModulePackages; + + # Implement consoleLogLevel both in early boot and using sysctl + # (so you don't need to reboot to have changes take effect). + boot.kernelParams = + [ "loglevel=${toString config.boot.consoleLogLevel}" ] ++ + optionals config.boot.vesa [ "vga=0x317" "nomodeset" ]; + + boot.kernel.sysctl."kernel.printk" = mkDefault config.boot.consoleLogLevel; + + boot.kernelModules = [ "loop" "atkbd" ]; + + # The Linux kernel >= 2.6.27 provides firmware. + hardware.firmware = [ kernel ]; + + # Create /etc/modules-load.d/nixos.conf, which is read by + # systemd-modules-load.service to load required kernel modules. + environment.etc = + { "modules-load.d/nixos.conf".source = kernelModulesConf; }; - }; - - lib.kernelConfig = { - isYes = option: { - assertion = config: config.isYes option; - message = "CONFIG_${option} is not yes!"; - configLine = "CONFIG_${option}=y"; - }; - - isNo = option: { - assertion = config: config.isNo option; - message = "CONFIG_${option} is not no!"; - configLine = "CONFIG_${option}=n"; - }; - - isModule = option: { - assertion = config: config.isModule option; - message = "CONFIG_${option} is not built as a module!"; - configLine = "CONFIG_${option}=m"; - }; - - ### Usually you will just want to use these two - # True if yes or module - isEnabled = option: { - assertion = config: config.isEnabled option; - message = "CONFIG_${option} is not enabled!"; - configLine = "CONFIG_${option}=y"; - }; - - # True if no or omitted - isDisabled = option: { - assertion = config: config.isDisabled option; - message = "CONFIG_${option} is not disabled!"; - configLine = "CONFIG_${option}=n"; - }; - }; - # The config options that all modules can depend upon - system.requiredKernelConfig = with config.lib.kernelConfig; [ - # !!! Should this really be needed? - (isYes "MODULES") - (isYes "BINFMT_ELF") - ] ++ (optional (randstructSeed != "") (isYes "GCC_PLUGIN_RANDSTRUCT")); + systemd.services.systemd-modules-load = + { wantedBy = [ "multi-user.target" ]; + restartTriggers = [ kernelModulesConf ]; + serviceConfig = + { # Ignore failed module loads. Typically some of the + # modules in ‘boot.kernelModules’ are "nice to have but + # not required" (e.g. acpi-cpufreq), so we don't want to + # barf on those. + SuccessExitStatus = "0 1"; + }; + }; - # nixpkgs kernels are assumed to have all required features - assertions = if config.boot.kernelPackages.kernel ? features then [] else - let cfg = config.boot.kernelPackages.kernel.config; in map (attrs: - { assertion = attrs.assertion cfg; inherit (attrs) message; } - ) config.system.requiredKernelConfig; + lib.kernelConfig = { + isYes = option: { + assertion = config: config.isYes option; + message = "CONFIG_${option} is not yes!"; + configLine = "CONFIG_${option}=y"; + }; - }; + isNo = option: { + assertion = config: config.isNo option; + message = "CONFIG_${option} is not no!"; + configLine = "CONFIG_${option}=n"; + }; + + isModule = option: { + assertion = config: config.isModule option; + message = "CONFIG_${option} is not built as a module!"; + configLine = "CONFIG_${option}=m"; + }; + + ### Usually you will just want to use these two + # True if yes or module + isEnabled = option: { + assertion = config: config.isEnabled option; + message = "CONFIG_${option} is not enabled!"; + configLine = "CONFIG_${option}=y"; + }; + + # True if no or omitted + isDisabled = option: { + assertion = config: config.isDisabled option; + message = "CONFIG_${option} is not disabled!"; + configLine = "CONFIG_${option}=n"; + }; + }; + + # The config options that all modules can depend upon + system.requiredKernelConfig = with config.lib.kernelConfig; + [ + # !!! Should this really be needed? + (isYes "MODULES") + (isYes "BINFMT_ELF") + ] ++ (optional (randstructSeed != "") (isYes "GCC_PLUGIN_RANDSTRUCT")); + + # nixpkgs kernels are assumed to have all required features + assertions = if config.boot.kernelPackages.kernel ? features then [] else + let cfg = config.boot.kernelPackages.kernel.config; in map (attrs: + { assertion = attrs.assertion cfg; inherit (attrs) message; } + ) config.system.requiredKernelConfig; + + }) + + ]; } diff --git a/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix index 9a4db84f7b731..c775632a4aa09 100644 --- a/nixos/modules/system/boot/loader/grub/grub.nix +++ b/nixos/modules/system/boot/loader/grub/grub.nix @@ -224,7 +224,11 @@ in extraConfig = mkOption { default = ""; - example = "serial; terminal_output.serial"; + example = '' + serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1 + terminal_input --append serial + terminal_output --append serial + ''; type = types.lines; description = '' Additional GRUB commands inserted in the configuration file @@ -584,7 +588,7 @@ in { boot.loader.grub.splashImage = mkDefault ( if cfg.version == 1 then pkgs.fetchurl { - url = http://www.gnome-look.org/CONTENT/content-files/36909-soft-tux.xpm.gz; + url = "http://www.gnome-look.org/CONTENT/content-files/36909-soft-tux.xpm.gz"; sha256 = "14kqdx2lfqvh40h6fjjzqgff1mwk74dmbjvmqphi6azzra7z8d59"; } # GRUB 1.97 doesn't support gzipped XPMs. @@ -630,7 +634,7 @@ in boot.loader.grub.extraPrepareConfig = concatStrings (mapAttrsToList (n: v: '' - ${pkgs.coreutils}/bin/cp -pf "${v}" "/boot/${n}" + ${pkgs.coreutils}/bin/cp -pf "${v}" "@bootPath@/${n}" '') config.boot.loader.grub.extraFiles); assertions = [ diff --git a/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixos/modules/system/boot/loader/grub/install-grub.pl index a09c5dc476180..8df18cbd90136 100644 --- a/nixos/modules/system/boot/loader/grub/install-grub.pl +++ b/nixos/modules/system/boot/loader/grub/install-grub.pl @@ -409,7 +409,7 @@ $conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS; # Find all the children of the current default configuration # Do not search for grand children -my @links = sort (glob "$defaultConfig/fine-tune/*"); +my @links = sort (glob "$defaultConfig/specialisation/*"); foreach my $link (@links) { my $entryName = ""; @@ -425,7 +425,8 @@ foreach my $link (@links) { if ($cfgName) { $entryName = $cfgName; } else { - $entryName = "($date - $version)"; + my $linkname = basename($link); + $entryName = "($linkname - $date - $version)"; } addEntry("NixOS - $entryName", $link); } @@ -475,6 +476,9 @@ if ($grubVersion == 2) { } } +# extraPrepareConfig could refer to @bootPath@, which we have to substitute +$extraPrepareConfig =~ s/\@bootPath\@/$bootPath/g; + # Run extraPrepareConfig in sh if ($extraPrepareConfig ne "") { system((get("shell"), "-c", $extraPrepareConfig)); diff --git a/nixos/modules/system/boot/loader/grub/memtest.nix b/nixos/modules/system/boot/loader/grub/memtest.nix index 94e5a14174b0d..71e50dd0577ef 100644 --- a/nixos/modules/system/boot/loader/grub/memtest.nix +++ b/nixos/modules/system/boot/loader/grub/memtest.nix @@ -1,4 +1,4 @@ -# This module adds Memtest86+ to the GRUB boot menu. +# This module adds Memtest86+/Memtest86 to the GRUB boot menu. { config, lib, pkgs, ... }: @@ -6,6 +6,7 @@ with lib; let memtest86 = pkgs.memtest86plus; + efiSupport = config.boot.loader.grub.efiSupport; cfg = config.boot.loader.grub.memtest86; in @@ -18,8 +19,11 @@ in default = false; type = types.bool; description = '' - Make Memtest86+, a memory testing program, available from the - GRUB boot menu. + Make Memtest86+ (or MemTest86 if EFI support is enabled), + a memory testing program, available from the + GRUB boot menu. MemTest86 is an unfree program, so + this requires <literal>allowUnfree</literal> to be set to + <literal>true</literal>. ''; }; @@ -75,19 +79,38 @@ in }; }; - config = mkIf cfg.enable { - - boot.loader.grub.extraEntries = - if config.boot.loader.grub.version == 2 then - '' - menuentry "Memtest86+" { - linux16 @bootRoot@/memtest.bin ${toString cfg.params} - } - '' - else - throw "Memtest86+ is not supported with GRUB 1."; - - boot.loader.grub.extraFiles."memtest.bin" = "${memtest86}/memtest.bin"; + config = mkMerge [ + (mkIf (cfg.enable && efiSupport) { + assertions = [ + { + assertion = cfg.params == []; + message = "Parameters are not available for MemTest86"; + } + ]; + + boot.loader.grub.extraFiles = { + "memtest86.efi" = "${pkgs.memtest86-efi}/BOOTX64.efi"; + }; - }; + boot.loader.grub.extraEntries = '' + menuentry "Memtest86" { + chainloader /memtest86.efi + } + ''; + }) + + (mkIf (cfg.enable && !efiSupport) { + boot.loader.grub.extraEntries = + if config.boot.loader.grub.version == 2 then + '' + menuentry "Memtest86+" { + linux16 @bootRoot@/memtest.bin ${toString cfg.params} + } + '' + else + throw "Memtest86+ is not supported with GRUB 1."; + + boot.loader.grub.extraFiles."memtest.bin" = "${memtest86}/memtest.bin"; + }) + ]; } diff --git a/nixos/modules/system/boot/loader/init-script/init-script-builder.sh b/nixos/modules/system/boot/loader/init-script/init-script-builder.sh index 08d4ab14c9ca2..6f48d2539acee 100644 --- a/nixos/modules/system/boot/loader/init-script/init-script-builder.sh +++ b/nixos/modules/system/boot/loader/init-script/init-script-builder.sh @@ -69,7 +69,7 @@ addEntry "NixOS - Default" $defaultConfig "" # Add all generations of the system profile to the menu, in reverse # (most recent to least recent) order. -for link in $((ls -d $defaultConfig/fine-tune/* ) | sort -n); do +for link in $((ls -d $defaultConfig/specialisation/* ) | sort -n); do date=$(stat --printf="%y\n" $link | sed 's/\..*//') addEntry "NixOS - variation" $link "" done diff --git a/nixos/modules/system/boot/loader/loader.nix b/nixos/modules/system/boot/loader/loader.nix index 7fbda9ef0f576..01475f79b9c25 100644 --- a/nixos/modules/system/boot/loader/loader.nix +++ b/nixos/modules/system/boot/loader/loader.nix @@ -3,6 +3,11 @@ with lib; { + imports = [ + (mkRenamedOptionModule [ "boot" "loader" "grub" "timeout" ] [ "boot" "loader" "timeout" ]) + (mkRenamedOptionModule [ "boot" "loader" "gummiboot" "timeout" ] [ "boot" "loader" "timeout" ]) + ]; + options = { boot.loader.timeout = mkOption { default = 5; @@ -12,4 +17,4 @@ with lib; ''; }; }; -} \ No newline at end of file +} diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index a4029d766b05a..31f1e22cda32c 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -4,6 +4,7 @@ with lib; let luks = config.boot.initrd.luks; + kernelPackages = config.boot.kernelPackages; commonFunctions = '' die() { @@ -126,7 +127,7 @@ let gpg-agent --daemon --scdaemon-program $out/bin/scdaemon > /dev/null 2> /dev/null ''} - + # Disable all input echo for the whole stage. We could use read -s # instead but that would ocasionally leak characters between read # invocations. @@ -139,7 +140,7 @@ let umount /crypt-ramfs 2>/dev/null ''; - openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, gpgCard, fallbackToPassword, ... }: assert name' == name; + openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, gpgCard, fido2, fallbackToPassword, ... }: assert name' == name; let csopen = "cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} ${optionalString (header != null) "--header=${header}"}"; cschange = "cryptsetup luksChangeKey ${device} ${optionalString (header != null) "--header=${header}"}"; @@ -387,7 +388,31 @@ let } ''} - ${if (luks.yubikeySupport && (yubikey != null)) || (luks.gpgSupport && (gpgCard != null)) then '' + ${optionalString (luks.fido2Support && (fido2.credential != null)) '' + + open_with_hardware() { + local passsphrase + + ${if fido2.passwordLess then '' + export passphrase="" + '' else '' + read -rsp "FIDO2 salt for ${device}: " passphrase + echo + ''} + ${optionalString (lib.versionOlder kernelPackages.kernel.version "5.4") '' + echo "On systems with Linux Kernel < 5.4, it might take a while to initialize the CRNG, you might want to use linuxPackages_latest." + echo "Please move your mouse to create needed randomness." + ''} + echo "Waiting for your FIDO2 device..." + fido2luks -i open ${device} ${name} ${fido2.credential} --await-dev ${toString fido2.gracePeriod} --salt string:$passphrase + if [ $? -ne 0 ]; then + echo "No FIDO2 key found, falling back to normal open procedure" + open_normally + fi + } + ''} + + ${if (luks.yubikeySupport && (yubikey != null)) || (luks.gpgSupport && (gpgCard != null)) || (luks.fido2Support && (fido2.credential != null)) then '' open_with_hardware '' else '' open_normally @@ -417,6 +442,9 @@ let in { + imports = [ + (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "") + ]; options = { @@ -605,6 +633,31 @@ in }); }; + fido2 = { + credential = mkOption { + default = null; + example = "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2"; + type = types.str; + description = "The FIDO2 credential ID."; + }; + + gracePeriod = mkOption { + default = 10; + type = types.int; + description = "Time in seconds to wait for the FIDO2 key."; + }; + + passwordLess = mkOption { + default = false; + type = types.bool; + description = '' + Defines whatever to use an empty string as a default salt. + + Enable only when your device is PIN protected, such as <link xlink:href="https://trezor.io/">Trezor</link>. + ''; + }; + }; + yubikey = mkOption { default = null; description = '' @@ -703,6 +756,15 @@ in and a Yubikey to work with this feature. ''; }; + + boot.initrd.luks.fido2Support = mkOption { + default = false; + type = types.bool; + description = '' + Enables support for authenticating with FIDO2 devices. + ''; + }; + }; config = mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) { @@ -711,6 +773,14 @@ in [ { assertion = !(luks.gpgSupport && luks.yubikeySupport); message = "Yubikey and GPG Card may not be used at the same time."; } + + { assertion = !(luks.gpgSupport && luks.fido2Support); + message = "FIDO2 and GPG Card may not be used at the same time."; + } + + { assertion = !(luks.fido2Support && luks.yubikeySupport); + message = "FIDO2 and Yubikey may not be used at the same time."; + } ]; # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested @@ -750,6 +820,11 @@ in chmod +x $out/bin/openssl-wrap ''} + ${optionalString luks.fido2Support '' + copy_bin_and_libs ${pkgs.fido2luks}/bin/fido2luks + ''} + + ${optionalString luks.gpgSupport '' copy_bin_and_libs ${pkgs.gnupg}/bin/gpg copy_bin_and_libs ${pkgs.gnupg}/bin/gpg-agent @@ -780,6 +855,9 @@ in $out/bin/gpg-agent --version $out/bin/scdaemon --version ''} + ${optionalString luks.fido2Support '' + $out/bin/fido2luks --version + ''} ''; boot.initrd.preFailCommands = postCommands; diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix index 226769f105979..a7580fb199787 100644 --- a/nixos/modules/system/boot/networkd.nix +++ b/nixos/modules/system/boot/networkd.nix @@ -11,7 +11,7 @@ let checkLink = checkUnitConfig "Link" [ (assertOnlyFields [ "Description" "Alias" "MACAddressPolicy" "MACAddress" "NamePolicy" "Name" "OriginalName" - "MTUBytes" "BitsPerSecond" "Duplex" "AutoNegotiation" "WakeOnLan" "Port" + "MTUBytes" "BitsPerSecond" "Duplex" "AutoNegotiation" "WakeOnLan" "Port" "Advertise" "TCPSegmentationOffload" "TCP6SegmentationOffload" "GenericSegmentationOffload" "GenericReceiveOffload" "LargeReceiveOffload" "RxChannels" "TxChannels" "OtherChannels" "CombinedChannels" @@ -49,12 +49,17 @@ let (assertValueOneOf "Kind" [ "bond" "bridge" "dummy" "gre" "gretap" "ip6gre" "ip6tnl" "ip6gretap" "ipip" "ipvlan" "macvlan" "macvtap" "sit" "tap" "tun" "veth" "vlan" "vti" "vti6" - "vxlan" "geneve" "vrf" "vcan" "vxcan" "wireguard" "netdevsim" + "vxlan" "geneve" "vrf" "vcan" "vxcan" "wireguard" "netdevsim" "xfrm" ]) (assertByteFormat "MTUBytes") (assertMacAddress "MACAddress") ]; + checkVRF = checkUnitConfig "VRF" [ + (assertOnlyFields [ "Table" ]) + (assertMinimum "Table" 0) + ]; + # NOTE The PrivateKey directive is missing on purpose here, please # do not add it to this list. The nix store is world-readable let's # refrain ourselves from providing a footgun. @@ -62,7 +67,12 @@ let (assertOnlyFields [ "PrivateKeyFile" "ListenPort" "FwMark" ]) - (assertRange "FwMark" 1 4294967295) + # The following check won't work on nix <= 2.2 + # see https://github.com/NixOS/nix/pull/2378 + # + # Add this again when we'll have drop the + # nix < 2.2 support. + # (assertRange "FwMark" 1 4294967295) ]; # NOTE The PresharedKey directive is missing on purpose here, please @@ -172,6 +182,19 @@ let (assertValueOneOf "AllSlavesActive" boolValues) ]; + checkXfrm = checkUnitConfig "Xfrm" [ + (assertOnlyFields [ + "InterfaceId" "Independent" + ]) + # The following check won't work on nix <= 2.2 + # see https://github.com/NixOS/nix/pull/2378 + # + # Add this again when we'll have drop the + # nix < 2.2 support. + # (assertRange "InterfaceId" 1 4294967295) + (assertValueOneOf "Independent" boolValues) + ]; + checkNetwork = checkUnitConfig "Network" [ (assertOnlyFields [ "Description" "DHCP" "DHCPServer" "LinkLocalAddressing" "IPv4LLRoute" @@ -182,7 +205,7 @@ let "IPv6HopLimit" "IPv4ProxyARP" "IPv6ProxyNDP" "IPv6ProxyNDPAddress" "IPv6PrefixDelegation" "IPv6MTUBytes" "Bridge" "Bond" "VRF" "VLAN" "IPVLAN" "MACVLAN" "VXLAN" "Tunnel" "ActiveSlave" "PrimarySlave" - "ConfigureWithoutCarrier" + "ConfigureWithoutCarrier" "Xfrm" ]) # Note: For DHCP the values both, none, v4, v6 are deprecated (assertValueOneOf "DHCP" ["yes" "no" "ipv4" "ipv6" "both" "none" "v4" "v6"]) @@ -222,6 +245,26 @@ let (assertValueOneOf "AutoJoin" boolValues) ]; + checkRoutingPolicyRule = checkUnitConfig "RoutingPolicyRule" [ + (assertOnlyFields [ + "TypeOfService" "From" "To" "FirewallMark" "Table" "Priority" + "IncomingInterface" "OutgoingInterface" "SourcePort" "DestinationPort" + "IPProtocol" "InvertRule" "Family" + ]) + (assertRange "TypeOfService" 0 255) + # The following check won't work on nix <= 2.2 + # see https://github.com/NixOS/nix/pull/2378 + # + # Add this again when we'll have drop the + # nix < 2.2 support. + # (assertRange "FirewallMark" 1 4294967295) + (assertInt "Priority") + (assertPort "SourcePort") + (assertPort "DestinationPort") + (assertValueOneOf "InvertRule" boolValues) + (assertValueOneOf "Family" ["ipv4" "ipv6" "both"]) + ]; + checkRoute = checkUnitConfig "Route" [ (assertOnlyFields [ "Gateway" "GatewayOnLink" "Destination" "Source" "Metric" @@ -276,7 +319,7 @@ let (assertValueOneOf "ARP" boolValues) (assertValueOneOf "Multicast" boolValues) (assertValueOneOf "Unmanaged" boolValues) - (assertValueOneOf "RequiredForOnline" boolValues) + (assertValueOneOf "RequiredForOnline" (boolValues ++ ["off" "no-carrier" "dormant" "degraded-carrier" "carrier" "degraded" "enslaved" "routable"])) ]; @@ -312,6 +355,14 @@ let }; linkOptions = commonNetworkOptions // { + # overwrite enable option from above + enable = mkOption { + default = true; + type = types.bool; + description = '' + Whether to enable this .link unit. It's handled by udev no matter if <command>systemd-networkd</command> is enabled or not + ''; + }; linkConfig = mkOption { default = {}; @@ -341,6 +392,21 @@ let ''; }; + vrfConfig = mkOption { + default = {}; + example = { Table = 2342; }; + type = types.addCheck (types.attrsOf unitOption) checkVRF; + description = '' + Each attribute in this set specifies an option in the + <literal>[VRF]</literal> section of the unit. See + <citerefentry><refentrytitle>systemd.netdev</refentrytitle> + <manvolnum>5</manvolnum></citerefentry> for details. + A detailed explanation about how VRFs work can be found in the + <link xlink:href="https://www.kernel.org/doc/Documentation/networking/vrf.txt">kernel + docs</link>. + ''; + }; + wireguardConfig = mkOption { default = {}; example = { @@ -477,6 +543,18 @@ let ''; }; + xfrmConfig = mkOption { + default = {}; + example = { InterfaceId = 1; }; + type = types.addCheck (types.attrsOf unitOption) checkXfrm; + description = '' + Each attribute in this set specifies an option in the + <literal>[Xfrm]</literal> section of the unit. See + <citerefentry><refentrytitle>systemd.netdev</refentrytitle> + <manvolnum>5</manvolnum></citerefentry> for details. + ''; + }; + }; addressOptions = { @@ -495,6 +573,22 @@ let }; }; + routingPolicyRulesOptions = { + options = { + routingPolicyRuleConfig = mkOption { + default = { }; + example = { routingPolicyRuleConfig = { Table = 10; IncomingInterface = "eth1"; Family = "both"; } ;}; + type = types.addCheck (types.attrsOf unitOption) checkRoutingPolicyRule; + description = '' + Each attribute in this set specifies an option in the + <literal>[RoutingPolicyRule]</literal> section of the unit. See + <citerefentry><refentrytitle>systemd.network</refentrytitle> + <manvolnum>5</manvolnum></citerefentry> for details. + ''; + }; + }; + }; + routeOptions = { options = { routeConfig = mkOption { @@ -712,6 +806,16 @@ let ''; }; + xfrm = mkOption { + default = [ ]; + type = types.listOf types.str; + description = '' + A list of xfrm interfaces to be added to the network section of the + unit. See <citerefentry><refentrytitle>systemd.network</refentrytitle> + <manvolnum>5</manvolnum></citerefentry> for details. + ''; + }; + addresses = mkOption { default = [ ]; type = with types; listOf (submodule addressOptions); @@ -722,6 +826,16 @@ let ''; }; + routingPolicyRules = mkOption { + default = [ ]; + type = with types; listOf (submodule routingPolicyRulesOptions); + description = '' + A list of routing policy rules sections to be added to the unit. See + <citerefentry><refentrytitle>systemd.network</refentrytitle> + <manvolnum>5</manvolnum></citerefentry> for details. + ''; + }; + routes = mkOption { default = [ ]; type = with types; listOf (submodule routeOptions); @@ -810,6 +924,16 @@ let ${attrsToSection def.bondConfig} ''} + ${optionalString (def.xfrmConfig != { }) '' + [Xfrm] + ${attrsToSection def.xfrmConfig} + + ''} + ${optionalString (def.vrfConfig != { }) '' + [VRF] + ${attrsToSection def.vrfConfig} + + ''} ${optionalString (def.wireguardConfig != { }) '' [WireGuard] ${attrsToSection def.wireguardConfig} @@ -847,6 +971,7 @@ let ${concatStringsSep "\n" (map (s: "MACVLAN=${s}") def.macvlan)} ${concatStringsSep "\n" (map (s: "VXLAN=${s}") def.vxlan)} ${concatStringsSep "\n" (map (s: "Tunnel=${s}") def.tunnel)} + ${concatStringsSep "\n" (map (s: "Xfrm=${s}") def.xfrm)} ${optionalString (def.dhcpConfig != { }) '' [DHCP] @@ -868,14 +993,19 @@ let ${attrsToSection x.routeConfig} '')} + ${flip concatMapStrings def.routingPolicyRules (x: '' + [RoutingPolicyRule] + ${attrsToSection x.routingPolicyRuleConfig} + + '')} ${def.extraConfig} ''; }; - unitFiles = map (name: { - target = "systemd/network/${name}"; - source = "${cfg.units.${name}.unit}/${name}"; - }) (attrNames cfg.units); + unitFiles = listToAttrs (map (name: { + name = "systemd/network/${name}"; + value.source = "${cfg.units.${name}.unit}/${name}"; + }) (attrNames cfg.units)); in { @@ -911,9 +1041,10 @@ in systemd.network.units = mkOption { description = "Definition of networkd units."; default = {}; + internal = true; type = with types; attrsOf (submodule ( { name, config, ... }: - { options = concreteUnitOptions; + { options = mapAttrs (_: x: x // { internal = true; }) concreteUnitOptions; config = { unit = mkDefault (makeUnit name config); }; @@ -922,44 +1053,49 @@ in }; - config = mkIf config.systemd.network.enable { + config = mkMerge [ + # .link units are honored by udev, no matter if systemd-networkd is enabled or not. + { + systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.link" (linkToUnit n v)) cfg.links; + environment.etc = unitFiles; + } - users.users.systemd-network.group = "systemd-network"; + (mkIf config.systemd.network.enable { - systemd.additionalUpstreamSystemUnits = [ - "systemd-networkd.service" "systemd-networkd-wait-online.service" - ]; + users.users.systemd-network.group = "systemd-network"; - systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.link" (linkToUnit n v)) cfg.links - // mapAttrs' (n: v: nameValuePair "${n}.netdev" (netdevToUnit n v)) cfg.netdevs - // mapAttrs' (n: v: nameValuePair "${n}.network" (networkToUnit n v)) cfg.networks; + systemd.additionalUpstreamSystemUnits = [ + "systemd-networkd.service" "systemd-networkd-wait-online.service" + ]; - environment.etc = unitFiles; + systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.netdev" (netdevToUnit n v)) cfg.netdevs + // mapAttrs' (n: v: nameValuePair "${n}.network" (networkToUnit n v)) cfg.networks; - systemd.services.systemd-networkd = { - wantedBy = [ "multi-user.target" ]; - restartTriggers = map (f: f.source) (unitFiles); - # prevent race condition with interface renaming (#39069) - requires = [ "systemd-udev-settle.service" ]; - after = [ "systemd-udev-settle.service" ]; - }; + systemd.services.systemd-networkd = { + wantedBy = [ "multi-user.target" ]; + restartTriggers = map (x: x.source) (attrValues unitFiles); + # prevent race condition with interface renaming (#39069) + requires = [ "systemd-udev-settle.service" ]; + after = [ "systemd-udev-settle.service" ]; + }; - systemd.services.systemd-networkd-wait-online = { - wantedBy = [ "network-online.target" ]; - }; + systemd.services.systemd-networkd-wait-online = { + wantedBy = [ "network-online.target" ]; + }; - systemd.services."systemd-network-wait-online@" = { - description = "Wait for Network Interface %I to be Configured"; - conflicts = [ "shutdown.target" ]; - requisite = [ "systemd-networkd.service" ]; - after = [ "systemd-networkd.service" ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - ExecStart = "${config.systemd.package}/lib/systemd/systemd-networkd-wait-online -i %I"; + systemd.services."systemd-network-wait-online@" = { + description = "Wait for Network Interface %I to be Configured"; + conflicts = [ "shutdown.target" ]; + requisite = [ "systemd-networkd.service" ]; + after = [ "systemd-networkd.service" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = "${config.systemd.package}/lib/systemd/systemd-networkd-wait-online -i %I"; + }; }; - }; - services.resolved.enable = mkDefault true; - }; + services.resolved.enable = mkDefault true; + }) + ]; } diff --git a/nixos/modules/system/boot/stage-1-init.sh b/nixos/modules/system/boot/stage-1-init.sh index f520bf54ad1bd..607aec87f01e5 100644 --- a/nixos/modules/system/boot/stage-1-init.sh +++ b/nixos/modules/system/boot/stage-1-init.sh @@ -210,6 +210,8 @@ done # Create device nodes in /dev. @preDeviceCommands@ echo "running udev..." +mkdir -p /etc/systemd +ln -sfn @linkUnits@ /etc/systemd/network mkdir -p /etc/udev ln -sfn @udevRules@ /etc/udev/rules.d mkdir -p /dev/.mdadm @@ -266,7 +268,7 @@ checkFS() { return 0 fi - # Device might be already mounted manually + # Device might be already mounted manually # e.g. NBD-device or the host filesystem of the file which contains encrypted root fs if mount | grep -q "^$device on "; then echo "skip checking already mounted $device" @@ -334,8 +336,10 @@ mountFS() { # Filter out x- options, which busybox doesn't do yet. local optionsFiltered="$(IFS=,; for i in $options; do if [ "${i:0:2}" != "x-" ]; then echo -n $i,; fi; done)" + # Prefix (lower|upper|work)dir with /mnt-root (overlayfs) + local optionsPrefixed="$( echo "$optionsFiltered" | sed -E 's#\<(lowerdir|upperdir|workdir)=#\1=/mnt-root#g' )" - echo "$device /mnt-root$mountPoint $fsType $optionsFiltered" >> /etc/fstab + echo "$device /mnt-root$mountPoint $fsType $optionsPrefixed" >> /etc/fstab checkFS "$device" "$fsType" @@ -349,15 +353,16 @@ mountFS() { elif [ "$fsType" = f2fs ]; then echo "resizing $device..." fsck.f2fs -fp "$device" - resize.f2fs "$device" + resize.f2fs "$device" fi ;; esac - # Create backing directories for unionfs-fuse. - if [ "$fsType" = unionfs-fuse ]; then - for i in $(IFS=:; echo ${options##*,dirs=}); do - mkdir -m 0700 -p /mnt-root"${i%=*}" + # Create backing directories for overlayfs + if [ "$fsType" = overlay ]; then + for i in upper work; do + dir="$( echo "$optionsPrefixed" | grep -o "${i}dir=[^,]*" )" + mkdir -m 0700 -p "${dir##*=}" done fi diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix index 4c2d130d5a5d7..dfd158e2d75f4 100644 --- a/nixos/modules/system/boot/stage-1.nix +++ b/nixos/modules/system/boot/stage-1.nix @@ -120,6 +120,7 @@ let # Copy udev. copy_bin_and_libs ${udev}/lib/systemd/systemd-udevd + copy_bin_and_libs ${udev}/lib/systemd/systemd-sysctl copy_bin_and_libs ${udev}/bin/udevadm for BIN in ${udev}/lib/udev/*_id; do copy_bin_and_libs $BIN @@ -136,12 +137,17 @@ let ''} # Copy secrets if needed. + # + # TODO: move out to a separate script; see #85000. ${optionalString (!config.boot.loader.supportsInitrdSecrets) (concatStringsSep "\n" (mapAttrsToList (dest: source: let source' = if source == null then dest else source; in '' mkdir -p $(dirname "$out/secrets/${dest}") - cp -a ${source'} "$out/secrets/${dest}" + # Some programs (e.g. ssh) doesn't like secrets to be + # symlinks, so we use `cp -L` here to match the + # behaviour when secrets are natively supported. + cp -Lr ${source'} "$out/secrets/${dest}" '' ) config.boot.initrd.secrets)) } @@ -198,6 +204,14 @@ let ''; # */ + linkUnits = pkgs.runCommand "link-units" { + allowedReferences = [ extraUtils ]; + preferLocalBuild = true; + } '' + mkdir -p $out + cp -v ${udev}/lib/systemd/network/*.link $out/ + ''; + udevRules = pkgs.runCommand "udev-rules" { allowedReferences = [ extraUtils ]; preferLocalBuild = true; @@ -208,7 +222,9 @@ let cp -v ${udev}/lib/udev/rules.d/60-cdrom_id.rules $out/ cp -v ${udev}/lib/udev/rules.d/60-persistent-storage.rules $out/ + cp -v ${udev}/lib/udev/rules.d/75-net-description.rules $out/ cp -v ${udev}/lib/udev/rules.d/80-drivers.rules $out/ + cp -v ${udev}/lib/udev/rules.d/80-net-setup-link.rules $out/ cp -v ${pkgs.lvm2}/lib/udev/rules.d/*.rules $out/ ${config.boot.initrd.extraUdevRulesCommands} @@ -222,7 +238,7 @@ let --replace ${pkgs.lvm2}/sbin ${extraUtils}/bin \ --replace ${pkgs.mdadm}/sbin ${extraUtils}/sbin \ --replace ${pkgs.bash}/bin/sh ${extraUtils}/bin/sh \ - --replace ${udev}/bin/udevadm ${extraUtils}/bin/udevadm + --replace ${udev} ${extraUtils} done # Work around a bug in QEMU, which doesn't implement the "READ @@ -257,7 +273,7 @@ let ${pkgs.buildPackages.busybox}/bin/ash -n $target ''; - inherit udevRules extraUtils modulesClosure; + inherit linkUnits udevRules extraUtils modulesClosure; inherit (config.boot) resumeDevice; @@ -379,6 +395,17 @@ in ''; }; + boot.initrd.enable = mkOption { + type = types.bool; + default = !config.boot.isContainer; + defaultText = "!config.boot.isContainer"; + description = '' + Whether to enable the NixOS initial RAM disk (initrd). This may be + needed to perform some initialisation tasks (like mounting + network/encrypted file systems) before continuing the boot process. + ''; + }; + boot.initrd.prepend = mkOption { default = [ ]; type = types.listOf types.str; @@ -544,7 +571,7 @@ in }; - config = mkIf (!config.boot.isContainer) { + config = mkIf config.boot.initrd.enable { assertions = [ { assertion = any (fs: fs.mountPoint == "/") fileSystems; message = "The ‘fileSystems’ option does not specify your root file system."; @@ -554,6 +581,25 @@ in message = "boot.resumeDevice has to be an absolute path." + " Old \"x:y\" style is no longer supported."; } + # TODO: remove when #85000 is fixed + { assertion = !config.boot.loader.supportsInitrdSecrets -> + all (source: + builtins.isPath source || + (builtins.isString source && hasPrefix source builtins.storeDir)) + (attrValues config.boot.initrd.secrets); + message = '' + boot.loader.initrd.secrets values must be unquoted paths when + using a bootloader that doesn't natively support initrd + secrets, e.g.: + + boot.initrd.secrets = { + "/etc/secret" = /path/to/secret; + }; + + Note that this will result in all secrets being stored + world-readable in the Nix store! + ''; + } ]; system.build = diff --git a/nixos/modules/system/boot/systemd-lib.nix b/nixos/modules/system/boot/systemd-lib.nix index 28ad4f121bbee..fa109394fedbe 100644 --- a/nixos/modules/system/boot/systemd-lib.nix +++ b/nixos/modules/system/boot/systemd-lib.nix @@ -59,6 +59,11 @@ in rec { optional (attr ? ${name} && ! isMacAddress attr.${name}) "Systemd ${group} field `${name}' must be a valid mac address."; + isPort = i: i >= 0 && i <= 65535; + + assertPort = name: group: attr: + optional (attr ? ${name} && ! isPort attr.${name}) + "Error on the systemd ${group} field `${name}': ${attr.name} is not a valid port number."; assertValueOneOf = name: values: group: attr: optional (attr ? ${name} && !elem attr.${name} values) @@ -109,7 +114,9 @@ in rec { (if isList value then value else [value])) as)); - generateUnits = type: units: upstreamUnits: upstreamWants: + generateUnits = generateUnits' true; + + generateUnits' = allowCollisions: type: units: upstreamUnits: upstreamWants: pkgs.runCommand "${type}-units" { preferLocalBuild = true; allowSubstitutes = false; @@ -147,7 +154,13 @@ in rec { done # Symlink all units provided listed in systemd.packages. - for i in ${toString cfg.packages}; do + packages="${toString cfg.packages}" + + # Filter duplicate directories + declare -A unique_packages + for k in $packages ; do unique_packages[$k]=1 ; done + + for i in ''${!unique_packages[@]}; do for fn in $i/etc/systemd/${type}/* $i/lib/systemd/${type}/*; do if ! [[ "$fn" =~ .wants$ ]]; then if [[ -d "$fn" ]]; then @@ -171,8 +184,13 @@ in rec { if [ "$(readlink -f $i/$fn)" = /dev/null ]; then ln -sfn /dev/null $out/$fn else - mkdir -p $out/$fn.d - ln -s $i/$fn $out/$fn.d/overrides.conf + ${if allowCollisions then '' + mkdir -p $out/$fn.d + ln -s $i/$fn $out/$fn.d/overrides.conf + '' else '' + echo "Found multiple derivations configuring $fn!" + exit 1 + ''} fi else ln -fs $i/$fn $out/ diff --git a/nixos/modules/system/boot/systemd-nspawn.nix b/nixos/modules/system/boot/systemd-nspawn.nix index 3ddd45b134820..06ea5ee49f727 100644 --- a/nixos/modules/system/boot/systemd-nspawn.nix +++ b/nixos/modules/system/boot/systemd-nspawn.nix @@ -116,7 +116,7 @@ in { in mkMerge [ (mkIf (cfg != {}) { - environment.etc."systemd/nspawn".source = mkIf (cfg != {}) (generateUnits "nspawn" units [] []); + environment.etc."systemd/nspawn".source = mkIf (cfg != {}) (generateUnits' false "nspawn" units [] []); }) { systemd.targets.multi-user.wants = [ "machines.target" ]; @@ -126,7 +126,7 @@ in { systemd.services."systemd-nspawn@".serviceConfig.ExecStart = [ "" # deliberately empty. signals systemd to override the ExecStart # Only difference between upstream is that we do not pass the -U flag - "${pkgs.systemd}/bin/systemd-nspawn --quiet --keep-unit --boot --link-journal=try-guest --network-veth --settings=override --machine=%i" + "${config.systemd.package}/bin/systemd-nspawn --quiet --keep-unit --boot --link-journal=try-guest --network-veth --settings=override --machine=%i" ]; } ]; diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index 9e3c6149f9223..2167df60bc9aa 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -240,7 +240,7 @@ let serviceConfig = { name, config, ... }: { config = mkMerge [ { # Default path for systemd services. Should be quite minimal. - path = + path = mkAfter [ pkgs.coreutils pkgs.findutils pkgs.gnugrep @@ -408,7 +408,6 @@ let in { - ###### interface options = { @@ -594,17 +593,33 @@ in each other's limit. The value may be specified in the following units: s, min, h, ms, us. To turn off any kind of rate limiting, set either value to 0. + + See <option>services.journald.rateLimitBurst</option> for important + considerations when setting this value. ''; }; services.journald.rateLimitBurst = mkOption { - default = 1000; + default = 10000; type = types.int; description = '' Configures the rate limiting burst limit (number of messages per interval) that is applied to all messages generated on the system. This rate limiting is applied per-service, so that two services which log do not interfere with each other's limit. + + Note that the effective rate limit is multiplied by a factor derived + from the available free disk space for the journal as described on + <link xlink:href="https://www.freedesktop.org/software/systemd/man/journald.conf.html"> + journald.conf(5)</link>. + + Note that the total amount of logs stored is limited by journald settings + such as <literal>SystemMaxUse</literal>, which defaults to a 4 GB cap. + + It is thus recommended to compute what period of time that you will be + able to store logs for when an application logs at full burst rate. + With default settings for log lines that are 100 Bytes long, this can + amount to just a few hours. ''; }; @@ -698,6 +713,16 @@ in ''; }; + systemd.sleep.extraConfig = mkOption { + default = ""; + type = types.lines; + example = "HibernateDelaySec=1h"; + description = '' + Extra config options for systemd sleep state logic. + See sleep.conf.d(5) man page for available options. + ''; + }; + systemd.user.extraConfig = mkOption { default = ""; type = types.lines; @@ -777,6 +802,18 @@ in ''; }; + systemd.suppressedSystemUnits = mkOption { + default = [ ]; + type = types.listOf types.str; + example = [ "systemd-backlight@.service" ]; + description = '' + A list of units to suppress when generating system systemd configuration directory. This has + priority over upstream units, <option>systemd.units</option>, and + <option>systemd.additionalUpstreamSystemUnits</option>. The main purpose of this is to + suppress a upstream systemd unit with any modifications made to it by other NixOS modules. + ''; + }; + }; @@ -809,8 +846,11 @@ in done ${concatStrings (mapAttrsToList (exec: target: "ln -s ${target} $out/${exec};\n") links)} ''; + + enabledUpstreamSystemUnits = filter (n: ! elem n cfg.suppressedSystemUnits) upstreamSystemUnits; + enabledUnits = filterAttrs (n: v: ! elem n cfg.suppressedSystemUnits) cfg.units; in ({ - "systemd/system".source = generateUnits "system" cfg.units upstreamSystemUnits upstreamSystemWants; + "systemd/system".source = generateUnits "system" enabledUnits enabledUpstreamSystemUnits upstreamSystemWants; "systemd/user".source = generateUnits "user" cfg.user.units upstreamUserUnits []; @@ -818,7 +858,6 @@ in [Manager] ${optionalString config.systemd.enableCgroupAccounting '' DefaultCPUAccounting=yes - DefaultBlockIOAccounting=yes DefaultIOAccounting=yes DefaultBlockIOAccounting=yes DefaultIPAccounting=yes @@ -864,27 +903,32 @@ in "systemd/sleep.conf".text = '' [Sleep] + ${config.systemd.sleep.extraConfig} ''; # install provided sysctl snippets "sysctl.d/50-coredump.conf".source = "${systemd}/example/sysctl.d/50-coredump.conf"; "sysctl.d/50-default.conf".source = "${systemd}/example/sysctl.d/50-default.conf"; + "tmpfiles.d/00-nixos.conf".text = '' + # This file is created automatically and should not be modified. + # Please change the option ‘systemd.tmpfiles.rules’ instead. + + ${concatStringsSep "\n" cfg.tmpfiles.rules} + ''; + + "tmpfiles.d/home.conf".source = "${systemd}/example/tmpfiles.d/home.conf"; "tmpfiles.d/journal-nocow.conf".source = "${systemd}/example/tmpfiles.d/journal-nocow.conf"; + "tmpfiles.d/portables.conf".source = "${systemd}/example/tmpfiles.d/portables.conf"; "tmpfiles.d/static-nodes-permissions.conf".source = "${systemd}/example/tmpfiles.d/static-nodes-permissions.conf"; "tmpfiles.d/systemd.conf".source = "${systemd}/example/tmpfiles.d/systemd.conf"; + "tmpfiles.d/systemd-nologin.conf".source = "${systemd}/example/tmpfiles.d/systemd-nologin.conf"; "tmpfiles.d/systemd-nspawn.conf".source = "${systemd}/example/tmpfiles.d/systemd-nspawn.conf"; "tmpfiles.d/systemd-tmp.conf".source = "${systemd}/example/tmpfiles.d/systemd-tmp.conf"; + "tmpfiles.d/tmp.conf".source = "${systemd}/example/tmpfiles.d/tmp.conf"; "tmpfiles.d/var.conf".source = "${systemd}/example/tmpfiles.d/var.conf"; "tmpfiles.d/x11.conf".source = "${systemd}/example/tmpfiles.d/x11.conf"; - "tmpfiles.d/nixos.conf".text = '' - # This file is created automatically and should not be modified. - # Please change the option ‘systemd.tmpfiles.rules’ instead. - - ${concatStringsSep "\n" cfg.tmpfiles.rules} - ''; - "systemd/system-generators" = { source = hooks "generators" cfg.generators; }; "systemd/system-shutdown" = { source = hooks "shutdown" cfg.shutdown; }; }); @@ -1006,5 +1050,7 @@ in [ (mkRenamedOptionModule [ "boot" "systemd" "sockets" ] [ "systemd" "sockets" ]) (mkRenamedOptionModule [ "boot" "systemd" "targets" ] [ "systemd" "targets" ]) (mkRenamedOptionModule [ "boot" "systemd" "services" ] [ "systemd" "services" ]) + (mkRenamedOptionModule [ "jobs" ] [ "systemd" "services" ]) + (mkRemovedOptionModule [ "systemd" "generator-packages" ] "Use systemd.packages instead.") ]; } diff --git a/nixos/modules/system/etc/etc.nix b/nixos/modules/system/etc/etc.nix index 57ade28809625..1f4d54a1ae205 100644 --- a/nixos/modules/system/etc/etc.nix +++ b/nixos/modules/system/etc/etc.nix @@ -94,7 +94,7 @@ in default = 0; type = types.int; description = '' - UID of created file. Only takes affect when the file is + UID of created file. Only takes effect when the file is copied (that is, the mode is not 'symlink'). ''; }; @@ -103,7 +103,7 @@ in default = 0; type = types.int; description = '' - GID of created file. Only takes affect when the file is + GID of created file. Only takes effect when the file is copied (that is, the mode is not 'symlink'). ''; }; @@ -113,7 +113,7 @@ in type = types.str; description = '' User name of created file. - Only takes affect when the file is copied (that is, the mode is not 'symlink'). + Only takes effect when the file is copied (that is, the mode is not 'symlink'). Changing this option takes precedence over <literal>uid</literal>. ''; }; @@ -123,7 +123,7 @@ in type = types.str; description = '' Group name of created file. - Only takes affect when the file is copied (that is, the mode is not 'symlink'). + Only takes effect when the file is copied (that is, the mode is not 'symlink'). Changing this option takes precedence over <literal>gid</literal>. ''; }; diff --git a/nixos/modules/tasks/auto-upgrade.nix b/nixos/modules/tasks/auto-upgrade.nix index 7fe0669919187..e70004e643e10 100644 --- a/nixos/modules/tasks/auto-upgrade.nix +++ b/nixos/modules/tasks/auto-upgrade.nix @@ -24,7 +24,7 @@ let cfg = config.system.autoUpgrade; in channel = mkOption { type = types.nullOr types.str; default = null; - example = https://nixos.org/channels/nixos-14.12-small; + example = "https://nixos.org/channels/nixos-14.12-small"; description = '' The URI of the NixOS channel to use for automatic upgrades. By default, this is the channel set using @@ -63,6 +63,19 @@ let cfg = config.system.autoUpgrade; in ''; }; + randomizedDelaySec = mkOption { + default = "0"; + type = types.str; + example = "45min"; + description = '' + Add a randomized delay before each automatic upgrade. + The delay will be chozen between zero and this value. + This value must be a time span in the format specified by + <citerefentry><refentrytitle>systemd.time</refentrytitle> + <manvolnum>7</manvolnum></citerefentry> + ''; + }; + }; }; @@ -109,6 +122,8 @@ let cfg = config.system.autoUpgrade; in startAt = cfg.dates; }; + systemd.timers.nixos-upgrade.timerConfig.RandomizedDelaySec = cfg.randomizedDelaySec; + }; } diff --git a/nixos/modules/tasks/encrypted-devices.nix b/nixos/modules/tasks/encrypted-devices.nix index 2c9231f552363..bc0933f16fec4 100644 --- a/nixos/modules/tasks/encrypted-devices.nix +++ b/nixos/modules/tasks/encrypted-devices.nix @@ -65,7 +65,7 @@ in boot.initrd = { luks = { devices = - map (dev: { name = dev.encrypted.label; device = dev.encrypted.blkDev; } ) keylessEncDevs; + builtins.listToAttrs (map (dev: { name = dev.encrypted.label; value = { device = dev.encrypted.blkDev; }; }) keylessEncDevs); forceLuksSupportInInitrd = true; }; postMountCommands = diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix index 688c77cb22d15..0ade74b957a0c 100644 --- a/nixos/modules/tasks/filesystems.nix +++ b/nixos/modules/tasks/filesystems.nix @@ -304,6 +304,11 @@ in in listToAttrs (map formatDevice (filter (fs: fs.autoFormat) fileSystems)); + systemd.tmpfiles.rules = [ + "d /run/keys 0750 root ${toString config.ids.gids.keys}" + "z /run/keys 0750 root ${toString config.ids.gids.keys}" + ]; + # Sync mount options with systemd's src/core/mount-setup.c: mount_table. boot.specialFileSystems = { "/proc" = { fsType = "proc"; options = [ "nosuid" "noexec" "nodev" ]; }; @@ -312,8 +317,8 @@ in "/dev/shm" = { fsType = "tmpfs"; options = [ "nosuid" "nodev" "strictatime" "mode=1777" "size=${config.boot.devShmSize}" ]; }; "/dev/pts" = { fsType = "devpts"; options = [ "nosuid" "noexec" "mode=620" "ptmxmode=0666" "gid=${toString config.ids.gids.tty}" ]; }; - # To hold secrets that shouldn't be written to disk (generally used for NixOps, harmless elsewhere) - "/run/keys" = { fsType = "ramfs"; options = [ "nosuid" "nodev" "mode=750" "gid=${toString config.ids.gids.keys}" ]; }; + # To hold secrets that shouldn't be written to disk + "/run/keys" = { fsType = "ramfs"; options = [ "nosuid" "nodev" "mode=750" ]; }; } // optionalAttrs (!config.boot.isContainer) { # systemd-nspawn populates /sys by itself, and remounting it causes all # kinds of weird issues (most noticeably, waiting for host disk device diff --git a/nixos/modules/tasks/filesystems/btrfs.nix b/nixos/modules/tasks/filesystems/btrfs.nix index 48be18c710212..f64493e1a3c78 100644 --- a/nixos/modules/tasks/filesystems/btrfs.nix +++ b/nixos/modules/tasks/filesystems/btrfs.nix @@ -118,12 +118,17 @@ in fs' = utils.escapeSystemdPath fs; in nameValuePair "btrfs-scrub-${fs'}" { description = "btrfs scrub on ${fs}"; + # scrub prevents suspend2ram or proper shutdown + conflicts = [ "shutdown.target" "sleep.target" ]; + before = [ "shutdown.target" "sleep.target" ]; serviceConfig = { - Type = "oneshot"; + # simple and not oneshot, otherwise ExecStop is not used + Type = "simple"; Nice = 19; IOSchedulingClass = "idle"; ExecStart = "${pkgs.btrfs-progs}/bin/btrfs scrub start -B ${fs}"; + ExecStop = "${pkgs.btrfs-progs}/bin/btrfs scrub cancel ${fs}"; }; }; in listToAttrs (map scrubService cfgScrub.fileSystems); diff --git a/nixos/modules/tasks/filesystems/nfs.nix b/nixos/modules/tasks/filesystems/nfs.nix index e0e8bb1f03de9..ddcc0ed8f5a43 100644 --- a/nixos/modules/tasks/filesystems/nfs.nix +++ b/nixos/modules/tasks/filesystems/nfs.nix @@ -25,6 +25,9 @@ let ''; nfsConfFile = pkgs.writeText "nfs.conf" cfg.extraConfig; + requestKeyConfFile = pkgs.writeText "request-key.conf" '' + create id_resolver * * ${pkgs.nfs-utils}/bin/nfsidmap -t 600 %k %d + ''; cfg = config.services.nfs; @@ -57,9 +60,12 @@ in systemd.packages = [ pkgs.nfs-utils ]; + environment.systemPackages = [ pkgs.keyutils ]; + environment.etc = { "idmapd.conf".source = idmapdConfFile; "nfs.conf".source = nfsConfFile; + "request-key.conf".source = requestKeyConfFile; }; systemd.services.nfs-blkmap = diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix index fe11917c609cc..43347161a84c6 100644 --- a/nixos/modules/tasks/filesystems/zfs.nix +++ b/nixos/modules/tasks/filesystems/zfs.nix @@ -12,6 +12,7 @@ let cfgSnapFlags = cfgSnapshots.flags; cfgScrub = config.services.zfs.autoScrub; cfgTrim = config.services.zfs.trim; + cfgZED = config.services.zfs.zed; inInitrd = any (fs: fs == "zfs") config.boot.initrd.supportedFilesystems; inSystem = any (fs: fs == "zfs") config.boot.supportedFilesystems; @@ -87,10 +88,25 @@ let } ''; + zedConf = generators.toKeyValue { + mkKeyValue = generators.mkKeyValueDefault { + mkValueString = v: + if isInt v then toString v + else if isString v then "\"${v}\"" + else if true == v then "1" + else if false == v then "0" + else if isList v then "\"" + (concatStringsSep " " v) + "\"" + else err "this value is" (toString v); + } "="; + } cfgZED.settings; in { + imports = [ + (mkRemovedOptionModule [ "boot" "zfs" "enableLegacyCrypto" ] "The corresponding package was removed from nixpkgs.") + ]; + ###### interface options = { @@ -312,6 +328,32 @@ in ''; }; }; + + services.zfs.zed.settings = mkOption { + type = with types; attrsOf (oneOf [ str int bool (listOf str) ]); + example = literalExample '' + { + ZED_DEBUG_LOG = "/tmp/zed.debug.log"; + + ZED_EMAIL_ADDR = [ "root" ]; + ZED_EMAIL_PROG = "mail"; + ZED_EMAIL_OPTS = "-s '@SUBJECT@' @ADDRESS@"; + + ZED_NOTIFY_INTERVAL_SECS = 3600; + ZED_NOTIFY_VERBOSE = false; + + ZED_USE_ENCLOSURE_LEDS = true; + ZED_SCRUB_AFTER_RESILVER = false; + } + ''; + description = '' + ZFS Event Daemon /etc/zfs/zed.d/zed.rc content + + See + <citerefentry><refentrytitle>zed</refentrytitle><manvolnum>8</manvolnum></citerefentry> + for details on ZED and the scripts in /etc/zfs/zed.d to find the possible variables + ''; + }; }; ###### implementation @@ -389,8 +431,33 @@ in zfsSupport = true; }; - environment.etc."zfs/zed.d".source = "${packages.zfsUser}/etc/zfs/zed.d/"; - environment.etc."zfs/zpool.d".source = "${packages.zfsUser}/etc/zfs/zpool.d/"; + services.zfs.zed.settings = { + ZED_EMAIL_PROG = mkDefault "${pkgs.mailutils}/bin/mail"; + PATH = lib.makeBinPath [ packages.zfsUser pkgs.utillinux pkgs.gawk pkgs.gnused pkgs.gnugrep pkgs.coreutils pkgs.curl ]; + }; + + environment.etc = genAttrs + (map + (file: "zfs/zed.d/${file}") + [ + "all-syslog.sh" + "pool_import-led.sh" + "resilver_finish-start-scrub.sh" + "statechange-led.sh" + "vdev_attach-led.sh" + "zed-functions.sh" + "data-notify.sh" + "resilver_finish-notify.sh" + "scrub_finish-notify.sh" + "statechange-notify.sh" + "vdev_clear-led.sh" + ] + ) + (file: { source = "${packages.zfsUser}/etc/${file}"; }) + // { + "zfs/zed.d/zed.rc".text = zedConf; + "zfs/zpool.d".source = "${packages.zfsUser}/etc/zfs/zpool.d/"; + }; system.fsPackages = [ packages.zfsUser ]; # XXX: needed? zfs doesn't have (need) a fsck environment.systemPackages = [ packages.zfsUser ] @@ -412,6 +479,7 @@ in createImportService = pool: nameValuePair "zfs-import-${pool}" { description = "Import ZFS pool \"${pool}\""; + # we need systemd-udev-settle until https://github.com/zfsonlinux/zfs/pull/4943 is merged requires = [ "systemd-udev-settle.service" ]; after = [ "systemd-udev-settle.service" "systemd-modules-load.service" ]; wantedBy = (getPoolMounts pool) ++ [ "local-fs.target" ]; @@ -557,7 +625,11 @@ in after = [ "zfs-import.target" ]; path = [ packages.zfsUser ]; startAt = cfgTrim.interval; - serviceConfig.ExecStart = "${pkgs.runtimeShell} -c 'zpool list -H -o name | xargs --no-run-if-empty -n1 zpool trim'"; + # By default we ignore errors returned by the trim command, in case: + # - HDDs are mixed with SSDs + # - There is a SSDs in a pool that is currently trimmed. + # - There are only HDDs and we would set the system in a degraded state + serviceConfig.ExecStart = ''${pkgs.runtimeShell} -c 'for pool in $(zpool list -H -o name); do zpool trim $pool; done || true' ''; }; }) ]; diff --git a/nixos/modules/tasks/kbd.nix b/nixos/modules/tasks/kbd.nix deleted file mode 100644 index c6ba998b19e63..0000000000000 --- a/nixos/modules/tasks/kbd.nix +++ /dev/null @@ -1,127 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; - -let - - makeColor = n: value: "COLOR_${toString n}=${value}"; - makeColorCS = - let positions = [ "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "A" "B" "C" "D" "E" "F" ]; - in n: value: "\\033]P${elemAt positions (n - 1)}${value}"; - colors = concatImapStringsSep "\n" makeColor config.i18n.consoleColors; - - isUnicode = hasSuffix "UTF-8" (toUpper config.i18n.defaultLocale); - - optimizedKeymap = pkgs.runCommand "keymap" { - nativeBuildInputs = [ pkgs.buildPackages.kbd ]; - LOADKEYS_KEYMAP_PATH = "${kbdEnv}/share/keymaps/**"; - preferLocalBuild = true; - } '' - loadkeys -b ${optionalString isUnicode "-u"} "${config.i18n.consoleKeyMap}" > $out - ''; - - # Sadly, systemd-vconsole-setup doesn't support binary keymaps. - vconsoleConf = pkgs.writeText "vconsole.conf" '' - KEYMAP=${config.i18n.consoleKeyMap} - FONT=${config.i18n.consoleFont} - ${colors} - ''; - - kbdEnv = pkgs.buildEnv { - name = "kbd-env"; - paths = [ pkgs.kbd ] ++ config.i18n.consolePackages; - pathsToLink = [ "/share/consolefonts" "/share/consoletrans" "/share/keymaps" "/share/unimaps" ]; - }; - - setVconsole = !config.boot.isContainer; -in - -{ - ###### interface - - options = { - - # most options are defined in i18n.nix - - # FIXME: still needed? - boot.extraTTYs = mkOption { - default = []; - type = types.listOf types.str; - example = ["tty8" "tty9"]; - description = '' - Tty (virtual console) devices, in addition to the consoles on - which mingetty and syslogd run, that must be initialised. - Only useful if you have some program that you want to run on - some fixed console. For example, the NixOS installation CD - opens the manual in a web browser on console 7, so it sets - <option>boot.extraTTYs</option> to <literal>["tty7"]</literal>. - ''; - }; - - boot.earlyVconsoleSetup = mkOption { - default = false; - type = types.bool; - description = '' - Enable setting font as early as possible (in initrd). - ''; - }; - - }; - - - ###### implementation - - config = mkMerge [ - (mkIf (!setVconsole) { - systemd.services.systemd-vconsole-setup.enable = false; - }) - - (mkIf setVconsole (mkMerge [ - { environment.systemPackages = [ pkgs.kbd ]; - - # Let systemd-vconsole-setup.service do the work of setting up the - # virtual consoles. - environment.etc."vconsole.conf".source = vconsoleConf; - # Provide kbd with additional packages. - environment.etc.kbd.source = "${kbdEnv}/share"; - - boot.initrd.preLVMCommands = mkBefore '' - kbd_mode ${if isUnicode then "-u" else "-a"} -C /dev/console - printf "\033%%${if isUnicode then "G" else "@"}" >> /dev/console - loadkmap < ${optimizedKeymap} - - ${optionalString config.boot.earlyVconsoleSetup '' - setfont -C /dev/console $extraUtils/share/consolefonts/font.psf - ''} - - ${concatImapStringsSep "\n" (n: color: '' - printf "${makeColorCS n color}" >> /dev/console - '') config.i18n.consoleColors} - ''; - - systemd.services.systemd-vconsole-setup = - { before = [ "display-manager.service" ]; - after = [ "systemd-udev-settle.service" ]; - restartTriggers = [ vconsoleConf kbdEnv ]; - }; - } - - (mkIf config.boot.earlyVconsoleSetup { - boot.initrd.extraUtilsCommands = '' - mkdir -p $out/share/consolefonts - ${if substring 0 1 config.i18n.consoleFont == "/" then '' - font="${config.i18n.consoleFont}" - '' else '' - font="$(echo ${kbdEnv}/share/consolefonts/${config.i18n.consoleFont}.*)" - ''} - if [[ $font == *.gz ]]; then - gzip -cd $font > $out/share/consolefonts/font.psf - else - cp -L $font $out/share/consolefonts/font.psf - fi - ''; - }) - ])) - ]; - -} diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix index 1726d05115ea8..9720d90217c66 100644 --- a/nixos/modules/tasks/network-interfaces-scripted.nix +++ b/nixos/modules/tasks/network-interfaces-scripted.nix @@ -10,7 +10,7 @@ let slaves = concatMap (i: i.interfaces) (attrValues cfg.bonds) ++ concatMap (i: i.interfaces) (attrValues cfg.bridges) - ++ concatMap (i: i.interfaces) (attrValues cfg.vswitches) + ++ concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (attrValues cfg.vswitches) ++ concatMap (i: [i.interface]) (attrValues cfg.macvlans) ++ concatMap (i: [i.interface]) (attrValues cfg.vlans); @@ -237,6 +237,38 @@ let ''; }; + createNetworkLink = i: + let + deviceDependency = if (config.boot.isContainer || i.name == "lo") + then [] + else [ (subsystemDevice i.name) ]; + in + nameValuePair "network-link-${i.name}" + { description = "Link configuration of ${i.name}"; + wantedBy = [ "network-interfaces.target" ]; + before = [ "network-interfaces.target" ]; + bindsTo = deviceDependency; + after = [ "network-pre.target" ] ++ deviceDependency; + path = [ pkgs.iproute ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = + '' + echo "Configuring link..." + '' + optionalString (i.macAddress != null) '' + echo "setting MAC address to ${i.macAddress}..." + ip link set "${i.name}" address "${i.macAddress}" + '' + optionalString (i.mtu != null) '' + echo "setting MTU to ${toString i.mtu}..." + ip link set "${i.name}" mtu "${toString i.mtu}" + '' + '' + echo -n "bringing up interface... " + ip link set "${i.name}" up && echo "done" || (echo "failed"; exit 1) + ''; + }; + createTunDevice = i: nameValuePair "${i.name}-netdev" { description = "Virtual Network Interface ${i.name}"; bindsTo = [ "dev-net-tun.device" ]; @@ -291,13 +323,19 @@ let ${optionalString config.virtualisation.libvirtd.enable '' # Enslave dynamically added interfaces which may be lost on nixos-rebuild - for uri in qemu:///system lxc:///; do - for dom in $(${pkgs.libvirt}/bin/virsh -c $uri list --name); do - ${pkgs.libvirt}/bin/virsh -c $uri dumpxml "$dom" | \ - ${pkgs.xmlstarlet}/bin/xmlstarlet sel -t -m "//domain/devices/interface[@type='bridge'][source/@bridge='${n}'][target/@dev]" -v "concat('ip link set ',target/@dev,' master ',source/@bridge,';')" | \ - ${pkgs.bash}/bin/bash + # + # if `libvirtd.service` is not running, do not use `virsh` which would try activate it via 'libvirtd.socket' and thus start it out-of-order. + # `libvirtd.service` will set up bridge interfaces when it will start normally. + # + if ${pkgs.systemd}/bin/systemctl --quiet is-active 'libvirtd.service'; then + for uri in qemu:///system lxc:///; do + for dom in $(${pkgs.libvirt}/bin/virsh -c $uri list --name); do + ${pkgs.libvirt}/bin/virsh -c $uri dumpxml "$dom" | \ + ${pkgs.xmlstarlet}/bin/xmlstarlet sel -t -m "//domain/devices/interface[@type='bridge'][source/@bridge='${n}'][target/@dev]" -v "concat('ip link set ',target/@dev,' master ',source/@bridge,';')" | \ + ${pkgs.bash}/bin/bash + done done - done + fi ''} # Enable stp on the interface @@ -336,34 +374,47 @@ let createVswitchDevice = n: v: nameValuePair "${n}-netdev" (let - deps = concatLists (map deviceDependency v.interfaces); + deps = concatLists (map deviceDependency (attrNames (filterAttrs (_: config: config.type != "internal") v.interfaces))); + internalConfigs = concatMap (i: ["network-link-${i}.service" "network-addresses-${i}.service"]) (attrNames (filterAttrs (_: config: config.type == "internal") v.interfaces)); ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules; in { description = "Open vSwitch Interface ${n}"; - wantedBy = [ "network-setup.service" "vswitchd.service" ] ++ deps; - bindsTo = [ "vswitchd.service" (subsystemDevice n) ] ++ deps; - partOf = [ "network-setup.service" "vswitchd.service" ]; - after = [ "network-pre.target" "vswitchd.service" ] ++ deps; - before = [ "network-setup.service" ]; + wantedBy = [ "network-setup.service" (subsystemDevice n) ] ++ internalConfigs; + # before = [ "network-setup.service" ]; + # should work without internalConfigs dependencies because address/link configuration depends + # on the device, which is created by ovs-vswitchd with type=internal, but it does not... + before = [ "network-setup.service" ] ++ internalConfigs; + partOf = [ "network-setup.service" ]; # shutdown the bridge when network is shutdown + bindsTo = [ "ovs-vswitchd.service" ]; # requires ovs-vswitchd to be alive at all times + after = [ "network-pre.target" "ovs-vswitchd.service" ] ++ deps; # start switch after physical interfaces and vswitch daemon + wants = deps; # if one or more interface fails, the switch should continue to run serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; path = [ pkgs.iproute config.virtualisation.vswitch.package ]; + preStart = '' + echo "Resetting Open vSwitch ${n}..." + ovs-vsctl --if-exists del-br ${n} -- add-br ${n} \ + -- set bridge ${n} protocols=${concatStringsSep "," v.supportedOpenFlowVersions} + ''; script = '' - echo "Removing old Open vSwitch ${n}..." - ovs-vsctl --if-exists del-br ${n} - - echo "Adding Open vSwitch ${n}..." - ovs-vsctl -- add-br ${n} ${concatMapStrings (i: " -- add-port ${n} ${i}") v.interfaces} \ + echo "Configuring Open vSwitch ${n}..." + ovs-vsctl ${concatStrings (mapAttrsToList (name: config: " -- add-port ${n} ${name}" + optionalString (config.vlan != null) " tag=${toString config.vlan}") v.interfaces)} \ + ${concatStrings (mapAttrsToList (name: config: optionalString (config.type != null) " -- set interface ${name} type=${config.type}") v.interfaces)} \ ${concatMapStrings (x: " -- set-controller ${n} " + x) v.controllers} \ ${concatMapStrings (x: " -- " + x) (splitString "\n" v.extraOvsctlCmds)} + echo "Adding OpenFlow rules for Open vSwitch ${n}..." - ovs-ofctl add-flows ${n} ${ofRules} + ovs-ofctl --protocols=${v.openFlowVersion} add-flows ${n} ${ofRules} ''; postStop = '' + echo "Cleaning Open vSwitch ${n}" + echo "Shuting down internal ${n} interface" ip link set ${n} down || true - ovs-ofctl del-flows ${n} || true - ovs-vsctl --if-exists del-br ${n} + echo "Deleting flows for ${n}" + ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true + echo "Deleting Open vSwitch ${n}" + ovs-vsctl --if-exists del-br ${n} || true ''; }); @@ -476,9 +527,9 @@ let # Remove Dead Interfaces ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}" ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}" - - # We try to bring up the logical VLAN interface. If the master - # interface the logical interface is dependent upon is not up yet we will + + # We try to bring up the logical VLAN interface. If the master + # interface the logical interface is dependent upon is not up yet we will # fail to immediately bring up the logical interface. The resulting logical # interface will brought up later when the master interface is up. ip link set "${n}" up || true @@ -489,6 +540,7 @@ let }); in listToAttrs ( + map createNetworkLink interfaces ++ map configureAddrs interfaces ++ map createTunDevice (filter (i: i.virtual) interfaces)) // mapAttrs' createBridgeDevice cfg.bridges diff --git a/nixos/modules/tasks/network-interfaces-systemd.nix b/nixos/modules/tasks/network-interfaces-systemd.nix index 9ffa1089ee697..23e1e611a71e9 100644 --- a/nixos/modules/tasks/network-interfaces-systemd.nix +++ b/nixos/modules/tasks/network-interfaces-systemd.nix @@ -1,4 +1,4 @@ -{ config, lib, utils, ... }: +{ config, lib, utils, pkgs, ... }: with utils; with lib; @@ -18,7 +18,10 @@ let concatLists (map (bond: bond.interfaces) (attrValues cfg.bonds)) ++ concatLists (map (bridge: bridge.interfaces) (attrValues cfg.bridges)) ++ map (sit: sit.dev) (attrValues cfg.sits) - ++ map (vlan: vlan.interface) (attrValues cfg.vlans); + ++ map (vlan: vlan.interface) (attrValues cfg.vlans) + # add dependency to physical or independently created vswitch member interface + # TODO: warn the user that any address configured on those interfaces will be useless + ++ concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (attrValues cfg.vswitches); in @@ -51,17 +54,12 @@ in networking.dhcpcd.enable = mkDefault false; - systemd.services.network-local-commands = { - after = [ "systemd-networkd.service" ]; - bindsTo = [ "systemd-networkd.service" ]; - }; - systemd.network = let domains = cfg.search ++ (optional (cfg.domain != null) cfg.domain); genericNetwork = override: - let gateway = optional (cfg.defaultGateway != null) cfg.defaultGateway.address - ++ optional (cfg.defaultGateway6 != null) cfg.defaultGateway6.address; + let gateway = optional (cfg.defaultGateway != null && (cfg.defaultGateway.address or "") != "") cfg.defaultGateway.address + ++ optional (cfg.defaultGateway6 != null && (cfg.defaultGateway6.address or "") != "") cfg.defaultGateway6.address; in optionalAttrs (gateway != [ ]) { routes = override [ { @@ -96,7 +94,12 @@ in address = forEach (interfaceIps i) (ip: "${ip.address}/${toString ip.prefixLength}"); networkConfig.IPv6PrivacyExtensions = "kernel"; - } ]; + linkConfig = optionalAttrs (i.macAddress != null) { + MACAddress = i.macAddress; + } // optionalAttrs (i.mtu != null) { + MTUBytes = toString i.mtu; + }; + }]; }))) (mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: { netdevs."40-${name}" = { @@ -233,6 +236,63 @@ in # This forces the network interface creator to initialize slaves. networking.interfaces = listToAttrs (map (i: nameValuePair i { }) slaves); + systemd.services = let + # We must escape interfaces due to the systemd interpretation + subsystemDevice = interface: + "sys-subsystem-net-devices-${escapeSystemdPath interface}.device"; + # support for creating openvswitch switches + createVswitchDevice = n: v: nameValuePair "${n}-netdev" + (let + deps = map subsystemDevice (attrNames (filterAttrs (_: config: config.type != "internal") v.interfaces)); + ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules; + in + { description = "Open vSwitch Interface ${n}"; + wantedBy = [ "network.target" (subsystemDevice n) ]; + # and create bridge before systemd-networkd starts because it might create internal interfaces + before = [ "systemd-networkd.service" ]; + # shutdown the bridge when network is shutdown + partOf = [ "network.target" ]; + # requires ovs-vswitchd to be alive at all times + bindsTo = [ "ovs-vswitchd.service" ]; + # start switch after physical interfaces and vswitch daemon + after = [ "network-pre.target" "ovs-vswitchd.service" ] ++ deps; + wants = deps; # if one or more interface fails, the switch should continue to run + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + path = [ pkgs.iproute config.virtualisation.vswitch.package ]; + preStart = '' + echo "Resetting Open vSwitch ${n}..." + ovs-vsctl --if-exists del-br ${n} -- add-br ${n} \ + -- set bridge ${n} protocols=${concatStringsSep "," v.supportedOpenFlowVersions} + ''; + script = '' + echo "Configuring Open vSwitch ${n}..." + ovs-vsctl ${concatStrings (mapAttrsToList (name: config: " -- add-port ${n} ${name}" + optionalString (config.vlan != null) " tag=${toString config.vlan}") v.interfaces)} \ + ${concatStrings (mapAttrsToList (name: config: optionalString (config.type != null) " -- set interface ${name} type=${config.type}") v.interfaces)} \ + ${concatMapStrings (x: " -- set-controller ${n} " + x) v.controllers} \ + ${concatMapStrings (x: " -- " + x) (splitString "\n" v.extraOvsctlCmds)} + + + echo "Adding OpenFlow rules for Open vSwitch ${n}..." + ovs-ofctl --protocols=${v.openFlowVersion} add-flows ${n} ${ofRules} + ''; + postStop = '' + echo "Cleaning Open vSwitch ${n}" + echo "Shuting down internal ${n} interface" + ip link set ${n} down || true + echo "Deleting flows for ${n}" + ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true + echo "Deleting Open vSwitch ${n}" + ovs-vsctl --if-exists del-br ${n} || true + ''; + }); + in mapAttrs' createVswitchDevice cfg.vswitches + // { + "network-local-commands" = { + after = [ "systemd-networkd.service" ]; + bindsTo = [ "systemd-networkd.service" ]; + }; + }; }; } diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix index 31e2ed1cd1eae..44677d417ead0 100644 --- a/nixos/modules/tasks/network-interfaces.nix +++ b/nixos/modules/tasks/network-interfaces.nix @@ -13,7 +13,7 @@ let slaves = concatMap (i: i.interfaces) (attrValues cfg.bonds) ++ concatMap (i: i.interfaces) (attrValues cfg.bridges) - ++ concatMap (i: i.interfaces) (attrValues cfg.vswitches); + ++ concatMap (i: attrNames (filterAttrs (name: config: ! (config.type == "internal" || hasAttr name cfg.interfaces)) i.interfaces)) (attrValues cfg.vswitches); slaveIfs = map (i: cfg.interfaces.${i}) (filter (i: cfg.interfaces ? ${i}) slaves); @@ -143,13 +143,34 @@ let description = "Name of the interface."; }; - preferTempAddress = mkOption { - type = types.bool; - default = cfg.enableIPv6; - defaultText = literalExample "config.networking.enableIPv6"; + tempAddress = mkOption { + type = types.enum [ "default" "enabled" "disabled" ]; + default = if cfg.enableIPv6 then "default" else "disabled"; + defaultText = literalExample ''if cfg.enableIPv6 then "default" else "disabled"''; description = '' - When using SLAAC prefer a temporary (IPv6) address over the EUI-64 - address for originating connections. This is used to reduce tracking. + When IPv6 is enabled with SLAAC, this option controls the use of + temporary address (aka privacy extensions). This is used to reduce tracking. + The three possible values are: + + <itemizedlist> + <listitem> + <para> + <literal>"default"</literal> to generate temporary addresses and use + them by default; + </para> + </listitem> + <listitem> + <para> + <literal>"enabled"</literal> to generate temporary addresses but keep + using the standard EUI-64 ones by default; + </para> + </listitem> + <listitem> + <para> + <literal>"disabled"</literal> to completely disable temporary addresses. + </para> + </listitem> + </itemizedlist> ''; }; @@ -287,6 +308,11 @@ let let defined = x: x != "_mkMergedOptionModule"; in [ + (mkChangedOptionModule [ "preferTempAddress" ] [ "tempAddress" ] + (config: + let bool = getAttrFromPath [ "preferTempAddress" ] config; + in if bool then "default" else "enabled" + )) (mkRenamedOptionModule [ "ip4" ] [ "ipv4" "addresses"]) (mkRenamedOptionModule [ "ip6" ] [ "ipv6" "addresses"]) (mkRemovedOptionModule [ "subnetMask" ] '' @@ -310,6 +336,32 @@ let }; + vswitchInterfaceOpts = {name, ...}: { + + options = { + + name = mkOption { + description = "Name of the interface"; + example = "eth0"; + type = types.str; + }; + + vlan = mkOption { + description = "Vlan tag to apply to interface"; + example = 10; + type = types.nullOr types.int; + default = null; + }; + + type = mkOption { + description = "Openvswitch type to assign to interface"; + example = "internal"; + type = types.nullOr types.str; + default = null; + }; + }; + }; + hexChars = stringToCharacters "0123456789abcdef"; isHexString = s: all (c: elem c hexChars) (stringToCharacters (toLower s)); @@ -460,8 +512,8 @@ in networking.vswitches = mkOption { default = { }; example = - { vs0.interfaces = [ "eth0" "eth1" ]; - vs1.interfaces = [ "eth2" "wlan0" ]; + { vs0.interfaces = { eth0 = { }; lo1 = { type="internal"; }; }; + vs1.interfaces = [ { name = "eth2"; } { name = "lo2"; type="internal"; } ]; }; description = '' @@ -478,9 +530,8 @@ in interfaces = mkOption { example = [ "eth0" "eth1" ]; - type = types.listOf types.str; - description = - "The physical network interfaces connected by the vSwitch."; + description = "The physical network interfaces connected by the vSwitch."; + type = with types; loaOf (submodule vswitchInterfaceOpts); }; controllers = mkOption { @@ -504,6 +555,25 @@ in ''; }; + # TODO: custom "openflow version" type, with list from existing openflow protocols + supportedOpenFlowVersions = mkOption { + type = types.listOf types.str; + example = [ "OpenFlow10" "OpenFlow13" "OpenFlow14" ]; + default = [ "OpenFlow13" ]; + description = '' + Supported versions to enable on this switch. + ''; + }; + + # TODO: use same type as elements from supportedOpenFlowVersions + openFlowVersion = mkOption { + type = types.str; + default = "OpenFlow13"; + description = '' + Version of OpenFlow protocol to use when communicating with the switch internally (e.g. with <literal>openFlowRules</literal>). + ''; + }; + extraOvsctlCmds = mkOption { type = types.lines; default = ""; @@ -564,19 +634,23 @@ in networking.bonds = let - driverOptionsExample = { - miimon = "100"; - mode = "active-backup"; - }; + driverOptionsExample = '' + { + miimon = "100"; + mode = "active-backup"; + } + ''; in mkOption { default = { }; - example = literalExample { - bond0 = { - interfaces = [ "eth0" "wlan0" ]; - driverOptions = driverOptionsExample; - }; - anotherBond.interfaces = [ "enp4s0f0" "enp4s0f1" "enp5s0f0" "enp5s0f1" ]; - }; + example = literalExample '' + { + bond0 = { + interfaces = [ "eth0" "wlan0" ]; + driverOptions = ${driverOptionsExample}; + }; + anotherBond.interfaces = [ "enp4s0f0" "enp4s0f1" "enp5s0f0" "enp5s0f1" ]; + } + ''; description = '' This option allows you to define bond devices that aggregate multiple, underlying networking interfaces together. The value of this option is @@ -661,12 +735,14 @@ in networking.macvlans = mkOption { default = { }; - example = literalExample { - wan = { - interface = "enp2s0"; - mode = "vepa"; - }; - }; + example = literalExample '' + { + wan = { + interface = "enp2s0"; + mode = "vepa"; + }; + } + ''; description = '' This option allows you to define macvlan interfaces which should be automatically created. @@ -694,18 +770,20 @@ in networking.sits = mkOption { default = { }; - example = literalExample { - hurricane = { - remote = "10.0.0.1"; - local = "10.0.0.22"; - ttl = 255; - }; - msipv6 = { - remote = "192.168.0.1"; - dev = "enp3s0"; - ttl = 127; - }; - }; + example = literalExample '' + { + hurricane = { + remote = "10.0.0.1"; + local = "10.0.0.22"; + ttl = 255; + }; + msipv6 = { + remote = "192.168.0.1"; + dev = "enp3s0"; + ttl = 127; + }; + } + ''; description = '' This option allows you to define 6-to-4 interfaces which should be automatically created. ''; @@ -756,16 +834,18 @@ in networking.vlans = mkOption { default = { }; - example = literalExample { - vlan0 = { - id = 3; - interface = "enp3s0"; - }; - vlan1 = { - id = 1; - interface = "wlan0"; - }; - }; + example = literalExample '' + { + vlan0 = { + id = 3; + interface = "enp3s0"; + }; + vlan1 = { + id = 1; + interface = "wlan0"; + }; + } + ''; description = '' This option allows you to define vlan devices that tag packets @@ -798,24 +878,26 @@ in networking.wlanInterfaces = mkOption { default = { }; - example = literalExample { - wlan-station0 = { - device = "wlp6s0"; - }; - wlan-adhoc0 = { - type = "ibss"; - device = "wlp6s0"; - mac = "02:00:00:00:00:01"; - }; - wlan-p2p0 = { - device = "wlp6s0"; - mac = "02:00:00:00:00:02"; - }; - wlan-ap0 = { - device = "wlp6s0"; - mac = "02:00:00:00:00:03"; - }; - }; + example = literalExample '' + { + wlan-station0 = { + device = "wlp6s0"; + }; + wlan-adhoc0 = { + type = "ibss"; + device = "wlp6s0"; + mac = "02:00:00:00:00:01"; + }; + wlan-p2p0 = { + device = "wlp6s0"; + mac = "02:00:00:00:00:02"; + }; + wlan-ap0 = { + device = "wlp6s0"; + mac = "02:00:00:00:00:03"; + }; + } + ''; description = '' Creating multiple WLAN interfaces on top of one physical WLAN device (NIC). @@ -945,10 +1027,15 @@ in The networking.interfaces."${i.name}" must not have any defined ips when it is a slave. ''; })) ++ (forEach interfaces (i: { - assertion = i.preferTempAddress -> cfg.enableIPv6; + assertion = i.tempAddress != "disabled" -> cfg.enableIPv6; message = '' Temporary addresses are only needed when IPv6 is enabled. ''; + })) ++ (forEach interfaces (i: { + assertion = (i.virtual && i.virtualType == "tun") -> i.macAddress == null; + message = '' + Setting a MAC Address for tun device ${i.name} isn't supported. + ''; })) ++ [ { assertion = cfg.hostId == null || (stringLength cfg.hostId == 8 && isHexString cfg.hostId); @@ -973,8 +1060,11 @@ in "net.ipv6.conf.all.forwarding" = mkDefault (any (i: i.proxyARP) interfaces); } // listToAttrs (flip concatMap (filter (i: i.proxyARP) interfaces) (i: forEach [ "4" "6" ] (v: nameValuePair "net.ipv${v}.conf.${replaceChars ["."] ["/"] i.name}.proxy_arp" true))) - // listToAttrs (forEach (filter (i: i.preferTempAddress) interfaces) - (i: nameValuePair "net.ipv6.conf.${replaceChars ["."] ["/"] i.name}.use_tempaddr" 2)); + // listToAttrs (forEach interfaces + (i: let + opt = i.tempAddress; + val = { disabled = 0; enabled = 1; default = 2; }.${opt}; + in nameValuePair "net.ipv6.conf.${replaceChars ["."] ["/"] i.name}.use_tempaddr" val)); # Capabilities won't work unless we have at-least a 4.3 Linux # kernel because we need the ambient capability @@ -1055,38 +1145,7 @@ in ${cfg.localCommands} ''; }; - } // (listToAttrs (forEach interfaces (i: - let - deviceDependency = if (config.boot.isContainer || i.name == "lo") - then [] - else [ (subsystemDevice i.name) ]; - in - nameValuePair "network-link-${i.name}" - { description = "Link configuration of ${i.name}"; - wantedBy = [ "network-interfaces.target" ]; - before = [ "network-interfaces.target" ]; - bindsTo = deviceDependency; - after = [ "network-pre.target" ] ++ deviceDependency; - path = [ pkgs.iproute ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - }; - script = - '' - echo "Configuring link..." - '' + optionalString (i.macAddress != null) '' - echo "setting MAC address to ${i.macAddress}..." - ip link set "${i.name}" address "${i.macAddress}" - '' + optionalString (i.mtu != null) '' - echo "setting MTU to ${toString i.mtu}..." - ip link set "${i.name}" mtu "${toString i.mtu}" - '' + '' - echo -n "bringing up interface... " - ip link set "${i.name}" up && echo "done" || (echo "failed"; exit 1) - ''; - }))); - + }; services.mstpd = mkIf needsMstpd { enable = true; }; virtualisation.vswitch = mkIf (cfg.vswitches != { }) { enable = true; }; @@ -1103,10 +1162,18 @@ in (pkgs.writeTextFile rec { name = "ipv6-privacy-extensions.rules"; destination = "/etc/udev/rules.d/99-${name}"; - text = concatMapStrings (i: '' - # enable IPv6 privacy addresses but prefer EUI-64 addresses for ${i.name} - ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.procps}/bin/sysctl net.ipv6.conf.${replaceChars ["."] ["/"] i.name}.use_tempaddr=1" - '') (filter (i: !i.preferTempAddress) interfaces); + text = concatMapStrings (i: + let + opt = i.tempAddress; + val = if opt == "disabled" then 0 else 1; + msg = if opt == "disabled" + then "completely disable IPv6 privacy addresses" + else "enable IPv6 privacy addresses but prefer EUI-64 addresses"; + in + '' + # override to ${msg} for ${i.name} + ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.procps}/bin/sysctl net.ipv6.conf.${replaceChars ["."] ["/"] i.name}.use_tempaddr=${toString val}" + '') (filter (i: i.tempAddress != "default") interfaces); }) ] ++ lib.optional (cfg.wlanInterfaces != {}) (pkgs.writeTextFile { diff --git a/nixos/modules/tasks/powertop.nix b/nixos/modules/tasks/powertop.nix index 609831506e16d..e8064f9fa80c6 100644 --- a/nixos/modules/tasks/powertop.nix +++ b/nixos/modules/tasks/powertop.nix @@ -15,6 +15,7 @@ in { systemd.services = { powertop = { wantedBy = [ "multi-user.target" ]; + after = [ "multi-user.target" ]; description = "Powertop tunings"; path = [ pkgs.kmod ]; serviceConfig = { diff --git a/nixos/modules/testing/service-runner.nix b/nixos/modules/testing/service-runner.nix index 17d5e33769086..99a9f979068da 100644 --- a/nixos/modules/testing/service-runner.nix +++ b/nixos/modules/testing/service-runner.nix @@ -12,7 +12,10 @@ let sub run { my ($cmd) = @_; - my @args = split " ", $cmd; + my @args = (); + while ($cmd =~ /([^ \t\n']+)|(\'([^'])\')\s*/g) { + push @args, $1; + } my $prog; if (substr($args[0], 0, 1) eq "@") { $prog = substr($args[0], 1); @@ -48,15 +51,20 @@ let '') service.environment)} # Run the ExecStartPre program. FIXME: this could be a list. - my $preStart = '${service.serviceConfig.ExecStartPre or ""}'; - if ($preStart ne "") { + my $preStart = <<END_CMD; + ${service.serviceConfig.ExecStartPre or ""} + END_CMD + if (defined $preStart && $preStart ne "\n") { print STDERR "running ExecStartPre: $preStart\n"; my $res = run_wait $preStart; die "$0: ExecStartPre failed with status $res\n" if $res; }; # Run the ExecStart program. - my $cmd = '${service.serviceConfig.ExecStart}'; + my $cmd = <<END_CMD; + ${service.serviceConfig.ExecStart} + END_CMD + print STDERR "running ExecStart: $cmd\n"; my $mainPid = run $cmd; $ENV{'MAINPID'} = $mainPid; @@ -70,8 +78,10 @@ let $SIG{'QUIT'} = \&intHandler; # Run the ExecStartPost program. - my $postStart = '${service.serviceConfig.ExecStartPost or ""}'; - if ($postStart ne "") { + my $postStart = <<END_CMD; + ${service.serviceConfig.ExecStartPost or ""} + END_CMD + if (defined $postStart && $postStart ne "\n") { print STDERR "running ExecStartPost: $postStart\n"; my $res = run_wait $postStart; die "$0: ExecStartPost failed with status $res\n" if $res; @@ -82,8 +92,10 @@ let my $mainRes = $?; # Run the ExecStopPost program. - my $postStop = '${service.serviceConfig.ExecStopPost or ""}'; - if ($postStop ne "") { + my $postStop = <<END_CMD; + ${service.serviceConfig.ExecStopPost or ""} + END_CMD + if (defined $postStop && $postStop ne "\n") { print STDERR "running ExecStopPost: $postStop\n"; my $res = run_wait $postStop; die "$0: ExecStopPost failed with status $res\n" if $res; diff --git a/nixos/modules/virtualisation/amazon-init.nix b/nixos/modules/virtualisation/amazon-init.nix index 8032b2c6d7ca4..8c12e0e49bf5b 100644 --- a/nixos/modules/virtualisation/amazon-init.nix +++ b/nixos/modules/virtualisation/amazon-init.nix @@ -7,8 +7,8 @@ let echo "attempting to fetch configuration from EC2 user data..." export HOME=/root - export PATH=${pkgs.lib.makeBinPath [ config.nix.package pkgs.systemd pkgs.gnugrep pkgs.gnused config.system.build.nixos-rebuild]}:$PATH - export NIX_PATH=/nix/var/nix/profiles/per-user/root/channels/nixos:nixos-config=/etc/nixos/configuration.nix:/nix/var/nix/profiles/per-user/root/channels + export PATH=${pkgs.lib.makeBinPath [ config.nix.package pkgs.systemd pkgs.gnugrep pkgs.git pkgs.gnutar pkgs.gzip pkgs.gnused config.system.build.nixos-rebuild]}:$PATH + export NIX_PATH=nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos:nixos-config=/etc/nixos/configuration.nix:/nix/var/nix/profiles/per-user/root/channels userData=/etc/ec2-metadata/user-data @@ -18,9 +18,9 @@ let # that as the channel. if sed '/^\(#\|SSH_HOST_.*\)/d' < "$userData" | grep -q '\S'; then channels="$(grep '^###' "$userData" | sed 's|###\s*||')" - printf "%s" "$channels" | while read channel; do + while IFS= read -r channel; do echo "writing channel: $channel" - done + done < <(printf "%s\n" "$channels") if [[ -n "$channels" ]]; then printf "%s" "$channels" > /root/.nix-channels @@ -48,7 +48,7 @@ in { wantedBy = [ "multi-user.target" ]; after = [ "multi-user.target" ]; requires = [ "network-online.target" ]; - + restartIfChanged = false; unitConfig.X-StopOnRemoval = false; @@ -58,4 +58,3 @@ in { }; }; } - diff --git a/nixos/modules/virtualisation/azure-common.nix b/nixos/modules/virtualisation/azure-common.nix index 03239991b95a5..8efa177e30d16 100644 --- a/nixos/modules/virtualisation/azure-common.nix +++ b/nixos/modules/virtualisation/azure-common.nix @@ -15,6 +15,8 @@ with lib; boot.loader.grub.version = 2; boot.loader.timeout = 0; + boot.growPartition = true; + # Don't put old configurations in the GRUB menu. The user has no # way to select them anyway. boot.loader.grub.configurationLimit = 0; diff --git a/nixos/modules/virtualisation/azure-image.nix b/nixos/modules/virtualisation/azure-image.nix index e91dd72ff5d43..21fd58e5c9024 100644 --- a/nixos/modules/virtualisation/azure-image.nix +++ b/nixos/modules/virtualisation/azure-image.nix @@ -2,27 +2,38 @@ with lib; let - diskSize = 2048; + cfg = config.virtualisation.azureImage; in { - system.build.azureImage = import ../../lib/make-disk-image.nix { - name = "azure-image"; - postVM = '' - ${pkgs.vmTools.qemu}/bin/qemu-img convert -f raw -o subformat=fixed,force_size -O vpc $diskImage $out/disk.vhd - ''; - configFile = ./azure-config-user.nix; - format = "raw"; - inherit diskSize; - inherit config lib pkgs; - }; - imports = [ ./azure-common.nix ]; + + options = { + virtualisation.azureImage.diskSize = mkOption { + type = with types; int; + default = 2048; + description = '' + Size of disk image. Unit is MB. + ''; + }; + }; + config = { + system.build.azureImage = import ../../lib/make-disk-image.nix { + name = "azure-image"; + postVM = '' + ${pkgs.vmTools.qemu}/bin/qemu-img convert -f raw -o subformat=fixed,force_size -O vpc $diskImage $out/disk.vhd + rm $diskImage + ''; + configFile = ./azure-config-user.nix; + format = "raw"; + inherit (cfg) diskSize; + inherit config lib pkgs; + }; - # Azure metadata is available as a CD-ROM drive. - fileSystems."/metadata".device = "/dev/sr0"; + # Azure metadata is available as a CD-ROM drive. + fileSystems."/metadata".device = "/dev/sr0"; - systemd.services.fetch-ssh-keys = - { description = "Fetch host keys and authorized_keys for root user"; + systemd.services.fetch-ssh-keys = { + description = "Fetch host keys and authorized_keys for root user"; wantedBy = [ "sshd.service" "waagent.service" ]; before = [ "sshd.service" "waagent.service" ]; @@ -54,6 +65,6 @@ in serviceConfig.RemainAfterExit = true; serviceConfig.StandardError = "journal+console"; serviceConfig.StandardOutput = "journal+console"; - }; - + }; + }; } diff --git a/nixos/modules/virtualisation/container-config.nix b/nixos/modules/virtualisation/container-config.nix index f7a37d8c9f3b4..6ff6bdd30c20c 100644 --- a/nixos/modules/virtualisation/container-config.nix +++ b/nixos/modules/virtualisation/container-config.nix @@ -10,6 +10,7 @@ with lib; nix.optimise.automatic = mkDefault false; # the store is host managed services.udisks2.enable = mkDefault false; powerManagement.enable = mkDefault false; + documentation.nixos.enable = mkDefault false; networking.useHostResolvConf = mkDefault true; diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index 09678ce9ea716..7d184575640b6 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -1,835 +1,125 @@ { config, lib, pkgs, ... }: - -with lib; - let - - # The container's init script, a small wrapper around the regular - # NixOS stage-2 init script. - containerInit = (cfg: - let - renderExtraVeth = (name: cfg: - '' - echo "Bringing ${name} up" - ip link set dev ${name} up - ${optionalString (cfg.localAddress != null) '' - echo "Setting ip for ${name}" - ip addr add ${cfg.localAddress} dev ${name} - ''} - ${optionalString (cfg.localAddress6 != null) '' - echo "Setting ip6 for ${name}" - ip -6 addr add ${cfg.localAddress6} dev ${name} - ''} - ${optionalString (cfg.hostAddress != null) '' - echo "Setting route to host for ${name}" - ip route add ${cfg.hostAddress} dev ${name} - ''} - ${optionalString (cfg.hostAddress6 != null) '' - echo "Setting route6 to host for ${name}" - ip -6 route add ${cfg.hostAddress6} dev ${name} - ''} - '' - ); - in - pkgs.writeScript "container-init" - '' - #! ${pkgs.runtimeShell} -e - - # Initialise the container side of the veth pair. - if [ -n "$HOST_ADDRESS" ] || [ -n "$HOST_ADDRESS6" ] || - [ -n "$LOCAL_ADDRESS" ] || [ -n "$LOCAL_ADDRESS6" ] || - [ -n "$HOST_BRIDGE" ]; then - ip link set host0 name eth0 - ip link set dev eth0 up - - if [ -n "$LOCAL_ADDRESS" ]; then - ip addr add $LOCAL_ADDRESS dev eth0 - fi - if [ -n "$LOCAL_ADDRESS6" ]; then - ip -6 addr add $LOCAL_ADDRESS6 dev eth0 - fi - if [ -n "$HOST_ADDRESS" ]; then - ip route add $HOST_ADDRESS dev eth0 - ip route add default via $HOST_ADDRESS - fi - if [ -n "$HOST_ADDRESS6" ]; then - ip -6 route add $HOST_ADDRESS6 dev eth0 - ip -6 route add default via $HOST_ADDRESS6 - fi - - ${concatStringsSep "\n" (mapAttrsToList renderExtraVeth cfg.extraVeths)} - fi - - # Start the regular stage 1 script. - exec "$1" - '' - ); - - nspawnExtraVethArgs = (name: cfg: "--network-veth-extra=${name}"); - - startScript = cfg: - '' - mkdir -p -m 0755 "$root/etc" "$root/var/lib" - mkdir -p -m 0700 "$root/var/lib/private" "$root/root" /run/containers - if ! [ -e "$root/etc/os-release" ]; then - touch "$root/etc/os-release" - fi - - if ! [ -e "$root/etc/machine-id" ]; then - touch "$root/etc/machine-id" - fi - - mkdir -p -m 0755 \ - "/nix/var/nix/profiles/per-container/$INSTANCE" \ - "/nix/var/nix/gcroots/per-container/$INSTANCE" - - cp --remove-destination /etc/resolv.conf "$root/etc/resolv.conf" - - if [ "$PRIVATE_NETWORK" = 1 ]; then - extraFlags+=" --private-network" - fi - - if [ -n "$HOST_ADDRESS" ] || [ -n "$LOCAL_ADDRESS" ] || - [ -n "$HOST_ADDRESS6" ] || [ -n "$LOCAL_ADDRESS6" ]; then - extraFlags+=" --network-veth" - fi - - if [ -n "$HOST_PORT" ]; then - OIFS=$IFS - IFS="," - for i in $HOST_PORT - do - extraFlags+=" --port=$i" - done - IFS=$OIFS - fi - - if [ -n "$HOST_BRIDGE" ]; then - extraFlags+=" --network-bridge=$HOST_BRIDGE" - fi - - extraFlags+=" ${concatStringsSep " " (mapAttrsToList nspawnExtraVethArgs cfg.extraVeths)}" - - for iface in $INTERFACES; do - extraFlags+=" --network-interface=$iface" - done - - for iface in $MACVLANS; do - extraFlags+=" --network-macvlan=$iface" - done - - # If the host is 64-bit and the container is 32-bit, add a - # --personality flag. - ${optionalString (config.nixpkgs.localSystem.system == "x86_64-linux") '' - if [ "$(< ''${SYSTEM_PATH:-/nix/var/nix/profiles/per-container/$INSTANCE/system}/system)" = i686-linux ]; then - extraFlags+=" --personality=x86" - fi - ''} - - # Run systemd-nspawn without startup notification (we'll - # wait for the container systemd to signal readiness). - exec ${config.systemd.package}/bin/systemd-nspawn \ - --keep-unit \ - -M "$INSTANCE" -D "$root" $extraFlags \ - $EXTRA_NSPAWN_FLAGS \ - --notify-ready=yes \ - --bind-ro=/nix/store \ - --bind-ro=/nix/var/nix/db \ - --bind-ro=/nix/var/nix/daemon-socket \ - --bind="/nix/var/nix/profiles/per-container/$INSTANCE:/nix/var/nix/profiles" \ - --bind="/nix/var/nix/gcroots/per-container/$INSTANCE:/nix/var/nix/gcroots" \ - ${optionalString (!cfg.ephemeral) "--link-journal=try-guest"} \ - --setenv PRIVATE_NETWORK="$PRIVATE_NETWORK" \ - --setenv HOST_BRIDGE="$HOST_BRIDGE" \ - --setenv HOST_ADDRESS="$HOST_ADDRESS" \ - --setenv LOCAL_ADDRESS="$LOCAL_ADDRESS" \ - --setenv HOST_ADDRESS6="$HOST_ADDRESS6" \ - --setenv LOCAL_ADDRESS6="$LOCAL_ADDRESS6" \ - --setenv HOST_PORT="$HOST_PORT" \ - --setenv PATH="$PATH" \ - ${optionalString cfg.ephemeral "--ephemeral"} \ - ${if cfg.additionalCapabilities != null && cfg.additionalCapabilities != [] then - ''--capability="${concatStringsSep "," cfg.additionalCapabilities}"'' else "" - } \ - ${if cfg.tmpfs != null && cfg.tmpfs != [] then - ''--tmpfs=${concatStringsSep " --tmpfs=" cfg.tmpfs}'' else "" - } \ - ${containerInit cfg} "''${SYSTEM_PATH:-/nix/var/nix/profiles/system}/init" - ''; - - preStartScript = cfg: - '' - # Clean up existing machined registration and interfaces. - machinectl terminate "$INSTANCE" 2> /dev/null || true - - if [ -n "$HOST_ADDRESS" ] || [ -n "$LOCAL_ADDRESS" ] || - [ -n "$HOST_ADDRESS6" ] || [ -n "$LOCAL_ADDRESS6" ]; then - ip link del dev "ve-$INSTANCE" 2> /dev/null || true - ip link del dev "vb-$INSTANCE" 2> /dev/null || true - fi - - ${concatStringsSep "\n" ( - mapAttrsToList (name: cfg: - ''ip link del dev ${name} 2> /dev/null || true '' - ) cfg.extraVeths - )} - ''; - - postStartScript = (cfg: - let - ipcall = cfg: ipcmd: variable: attribute: - if cfg.${attribute} == null then - '' - if [ -n "${variable}" ]; then - ${ipcmd} add ${variable} dev $ifaceHost - fi - '' - else - ''${ipcmd} add ${cfg.${attribute}} dev $ifaceHost''; - renderExtraVeth = name: cfg: - if cfg.hostBridge != null then - '' - # Add ${name} to bridge ${cfg.hostBridge} - ip link set dev ${name} master ${cfg.hostBridge} up - '' - else - '' - echo "Bring ${name} up" - ip link set dev ${name} up - # Set IPs and routes for ${name} - ${optionalString (cfg.hostAddress != null) '' - ip addr add ${cfg.hostAddress} dev ${name} - ''} - ${optionalString (cfg.hostAddress6 != null) '' - ip -6 addr add ${cfg.hostAddress6} dev ${name} - ''} - ${optionalString (cfg.localAddress != null) '' - ip route add ${cfg.localAddress} dev ${name} - ''} - ${optionalString (cfg.localAddress6 != null) '' - ip -6 route add ${cfg.localAddress6} dev ${name} - ''} - ''; - in - '' - if [ -n "$HOST_ADDRESS" ] || [ -n "$LOCAL_ADDRESS" ] || - [ -n "$HOST_ADDRESS6" ] || [ -n "$LOCAL_ADDRESS6" ]; then - if [ -z "$HOST_BRIDGE" ]; then - ifaceHost=ve-$INSTANCE - ip link set dev $ifaceHost up - - ${ipcall cfg "ip addr" "$HOST_ADDRESS" "hostAddress"} - ${ipcall cfg "ip -6 addr" "$HOST_ADDRESS6" "hostAddress6"} - ${ipcall cfg "ip route" "$LOCAL_ADDRESS" "localAddress"} - ${ipcall cfg "ip -6 route" "$LOCAL_ADDRESS6" "localAddress6"} - fi - ${concatStringsSep "\n" (mapAttrsToList renderExtraVeth cfg.extraVeths)} - fi - - # Get the leader PID so that we can signal it in - # preStop. We can't use machinectl there because D-Bus - # might be shutting down. FIXME: in systemd 219 we can - # just signal systemd-nspawn to do a clean shutdown. - machinectl show "$INSTANCE" | sed 's/Leader=\(.*\)/\1/;t;d' > "/run/containers/$INSTANCE.pid" - '' - ); - - serviceDirectives = cfg: { - ExecReload = pkgs.writeScript "reload-container" - '' - #! ${pkgs.runtimeShell} -e - ${pkgs.nixos-container}/bin/nixos-container run "$INSTANCE" -- \ - bash --login -c "''${SYSTEM_PATH:-/nix/var/nix/profiles/system}/bin/switch-to-configuration test" - ''; - - SyslogIdentifier = "container %i"; - - EnvironmentFile = "-/etc/containers/%i.conf"; - - Type = "notify"; - - RuntimeDirectory = lib.optional cfg.ephemeral "containers/%i"; - - # Note that on reboot, systemd-nspawn returns 133, so this - # unit will be restarted. On poweroff, it returns 0, so the - # unit won't be restarted. - RestartForceExitStatus = "133"; - SuccessExitStatus = "133"; - - # Some containers take long to start - # especially when you automatically start many at once - TimeoutStartSec = cfg.timeoutStartSec; - - Restart = "on-failure"; - - Slice = "machine.slice"; - Delegate = true; - - # Hack: we don't want to kill systemd-nspawn, since we call - # "machinectl poweroff" in preStop to shut down the - # container cleanly. But systemd requires sending a signal - # (at least if we want remaining processes to be killed - # after the timeout). So send an ignored signal. - KillMode = "mixed"; - KillSignal = "WINCH"; - - DevicePolicy = "closed"; - DeviceAllow = map (d: "${d.node} ${d.modifier}") cfg.allowedDevices; + cfg = config.virtualisation.containers; + + inherit (lib) mkOption types; + + # Once https://github.com/NixOS/nixpkgs/pull/75584 is merged we can use the TOML generator + toTOML = name: value: pkgs.runCommandNoCC name { + nativeBuildInputs = [ pkgs.remarshal ]; + value = builtins.toJSON value; + passAsFile = [ "value" ]; + } '' + json2toml "$valuePath" "$out" + ''; + + # Copy configuration files to avoid having the entire sources in the system closure + copyFile = filePath: pkgs.runCommandNoCC (builtins.unsafeDiscardStringContext (builtins.baseNameOf filePath)) {} '' + cp ${filePath} $out + ''; +in +{ + meta = { + maintainers = [] ++ lib.teams.podman.members; }; + options.virtualisation.containers = { - system = config.nixpkgs.localSystem.system; - - bindMountOpts = { name, ... }: { - - options = { - mountPoint = mkOption { - example = "/mnt/usb"; - type = types.str; - description = "Mount point on the container file system."; - }; - hostPath = mkOption { - default = null; - example = "/home/alice"; - type = types.nullOr types.str; - description = "Location of the host path to be mounted."; - }; - isReadOnly = mkOption { - default = true; + enable = + mkOption { type = types.bool; - description = "Determine whether the mounted path will be accessed in read-only mode."; + default = false; + description = '' + This option enables the common /etc/containers configuration module. + ''; }; - }; - - config = { - mountPoint = mkDefault name; - }; - }; - - allowedDeviceOpts = { ... }: { - options = { - node = mkOption { - example = "/dev/net/tun"; - type = types.str; - description = "Path to device node"; - }; - modifier = mkOption { - example = "rw"; - type = types.str; + registries = { + search = mkOption { + type = types.listOf types.str; + default = [ "docker.io" "quay.io" ]; description = '' - Device node access modifier. Takes a combination - <literal>r</literal> (read), <literal>w</literal> (write), and - <literal>m</literal> (mknod). See the - <literal>systemd.resource-control(5)</literal> man page for more - information.''; + List of repositories to search. + ''; }; - }; - }; - - - mkBindFlag = d: - let flagPrefix = if d.isReadOnly then " --bind-ro=" else " --bind="; - mountstr = if d.hostPath != null then "${d.hostPath}:${d.mountPoint}" else "${d.mountPoint}"; - in flagPrefix + mountstr ; - mkBindFlags = bs: concatMapStrings mkBindFlag (lib.attrValues bs); + insecure = mkOption { + default = []; + type = types.listOf types.str; + description = '' + List of insecure repositories. + ''; + }; - networkOptions = { - hostBridge = mkOption { - type = types.nullOr types.str; - default = null; - example = "br0"; - description = '' - Put the host-side of the veth-pair into the named bridge. - Only one of hostAddress* or hostBridge can be given. - ''; + block = mkOption { + default = []; + type = types.listOf types.str; + description = '' + List of blocked repositories. + ''; + }; }; - forwardPorts = mkOption { - type = types.listOf (types.submodule { - options = { - protocol = mkOption { - type = types.str; - default = "tcp"; - description = "The protocol specifier for port forwarding between host and container"; - }; - hostPort = mkOption { - type = types.int; - description = "Source port of the external interface on host"; - }; - containerPort = mkOption { - type = types.nullOr types.int; - default = null; - description = "Target port of container"; + policy = mkOption { + default = {}; + type = types.attrs; + example = lib.literalExample '' + { + default = [ { type = "insecureAcceptAnything"; } ]; + transports = { + docker-daemon = { + "" = [ { type = "insecureAcceptAnything"; } ]; + }; }; - }; - }); - default = []; - example = [ { protocol = "tcp"; hostPort = 8080; containerPort = 80; } ]; - description = '' - List of forwarded ports from host to container. Each forwarded port - is specified by protocol, hostPort and containerPort. By default, - protocol is tcp and hostPort and containerPort are assumed to be - the same if containerPort is not explicitly given. - ''; - }; - - - hostAddress = mkOption { - type = types.nullOr types.str; - default = null; - example = "10.231.136.1"; - description = '' - The IPv4 address assigned to the host interface. - (Not used when hostBridge is set.) + } ''; - }; - - hostAddress6 = mkOption { - type = types.nullOr types.str; - default = null; - example = "fc00::1"; description = '' - The IPv6 address assigned to the host interface. - (Not used when hostBridge is set.) + Signature verification policy file. + If this option is empty the default policy file from + <literal>skopeo</literal> will be used. ''; }; - localAddress = mkOption { - type = types.nullOr types.str; - default = null; - example = "10.231.136.2"; - description = '' - The IPv4 address assigned to the interface in the container. - If a hostBridge is used, this should be given with netmask to access - the whole network. Otherwise the default netmask is /32 and routing is - set up from localAddress to hostAddress and back. - ''; - }; - - localAddress6 = mkOption { - type = types.nullOr types.str; - default = null; - example = "fc00::2"; + users = mkOption { + default = []; + type = types.listOf types.str; description = '' - The IPv6 address assigned to the interface in the container. - If a hostBridge is used, this should be given with netmask to access - the whole network. Otherwise the default netmask is /128 and routing is - set up from localAddress6 to hostAddress6 and back. + List of users to set up subuid/subgid mappings for. + This is a requirement for running rootless containers. ''; }; }; - dummyConfig = - { - extraVeths = {}; - additionalCapabilities = []; - ephemeral = false; - timeoutStartSec = "15s"; - allowedDevices = []; - hostAddress = null; - hostAddress6 = null; - localAddress = null; - localAddress6 = null; - tmpfs = null; - }; - -in - -{ - options = { - - boot.isContainer = mkOption { - type = types.bool; - default = false; - description = '' - Whether this NixOS machine is a lightweight container running - in another NixOS system. - ''; - }; + config = lib.mkIf cfg.enable { - boot.enableContainers = mkOption { - type = types.bool; - default = !config.boot.isContainer; - description = '' - Whether to enable support for NixOS containers. - ''; + environment.etc."containers/registries.conf".source = toTOML "registries.conf" { + registries = lib.mapAttrs (n: v: { registries = v; }) cfg.registries; }; - containers = mkOption { - type = types.attrsOf (types.submodule ( - { config, options, name, ... }: - { - options = { - - config = mkOption { - description = '' - A specification of the desired configuration of this - container, as a NixOS module. - ''; - type = lib.mkOptionType { - name = "Toplevel NixOS config"; - merge = loc: defs: (import ../../lib/eval-config.nix { - inherit system; - modules = - let - extraConfig = { - _file = "module at ${__curPos.file}:${toString __curPos.line}"; - config = { - boot.isContainer = true; - networking.hostName = mkDefault name; - networking.useDHCP = false; - assertions = [ - { - assertion = config.privateNetwork -> stringLength name < 12; - message = '' - Container name `${name}` is too long: When `privateNetwork` is enabled, container names can - not be longer than 11 characters, because the container's interface name is derived from it. - This might be fixed in the future. See https://github.com/NixOS/nixpkgs/issues/38509 - ''; - } - ]; - }; - }; - in [ extraConfig ] ++ (map (x: x.value) defs); - prefix = [ "containers" name ]; - }).config; - }; - }; - - path = mkOption { - type = types.path; - example = "/nix/var/nix/profiles/containers/webserver"; - description = '' - As an alternative to specifying - <option>config</option>, you can specify the path to - the evaluated NixOS system configuration, typically a - symlink to a system profile. - ''; - }; - - additionalCapabilities = mkOption { - type = types.listOf types.str; - default = []; - example = [ "CAP_NET_ADMIN" "CAP_MKNOD" ]; - description = '' - Grant additional capabilities to the container. See the - capabilities(7) and systemd-nspawn(1) man pages for more - information. - ''; - }; - - ephemeral = mkOption { - type = types.bool; - default = false; - description = '' - Runs container in ephemeral mode with the empty root filesystem at boot. - This way container will be bootstrapped from scratch on each boot - and will be cleaned up on shutdown leaving no traces behind. - Useful for completely stateless, reproducible containers. - - Note that this option might require to do some adjustments to the container configuration, - e.g. you might want to set - <varname>systemd.network.networks.$interface.dhcpConfig.ClientIdentifier</varname> to "mac" - if you use <varname>macvlans</varname> option. - This way dhcp client identifier will be stable between the container restarts. - - Note that the container journal will not be linked to the host if this option is enabled. - ''; - }; - - enableTun = mkOption { - type = types.bool; - default = false; - description = '' - Allows the container to create and setup tunnel interfaces - by granting the <literal>NET_ADMIN</literal> capability and - enabling access to <literal>/dev/net/tun</literal>. - ''; - }; - - privateNetwork = mkOption { - type = types.bool; - default = false; - description = '' - Whether to give the container its own private virtual - Ethernet interface. The interface is called - <literal>eth0</literal>, and is hooked up to the interface - <literal>ve-<replaceable>container-name</replaceable></literal> - on the host. If this option is not set, then the - container shares the network interfaces of the host, - and can bind to any port on any interface. - ''; - }; - - interfaces = mkOption { - type = types.listOf types.str; - default = []; - example = [ "eth1" "eth2" ]; - description = '' - The list of interfaces to be moved into the container. - ''; - }; - - macvlans = mkOption { - type = types.listOf types.str; - default = []; - example = [ "eth1" "eth2" ]; - description = '' - The list of host interfaces from which macvlans will be - created. For each interface specified, a macvlan interface - will be created and moved to the container. - ''; - }; - - extraVeths = mkOption { - type = with types; attrsOf (submodule { options = networkOptions; }); - default = {}; - description = '' - Extra veth-pairs to be created for the container - ''; - }; - - autoStart = mkOption { - type = types.bool; - default = false; - description = '' - Whether the container is automatically started at boot-time. - ''; - }; - - timeoutStartSec = mkOption { - type = types.str; - default = "1min"; - description = '' - Time for the container to start. In case of a timeout, - the container processes get killed. - See <citerefentry><refentrytitle>systemd.time</refentrytitle> - <manvolnum>7</manvolnum></citerefentry> - for more information about the format. - ''; - }; - - bindMounts = mkOption { - type = with types; loaOf (submodule bindMountOpts); - default = {}; - example = { "/home" = { hostPath = "/home/alice"; - isReadOnly = false; }; - }; - - description = - '' - An extra list of directories that is bound to the container. - ''; - }; - - allowedDevices = mkOption { - type = with types; listOf (submodule allowedDeviceOpts); - default = []; - example = [ { node = "/dev/net/tun"; modifier = "rw"; } ]; - description = '' - A list of device nodes to which the containers has access to. - ''; - }; - - tmpfs = mkOption { - type = types.listOf types.str; - default = []; - example = [ "/var" ]; - description = '' - Mounts a set of tmpfs file systems into the container. - Multiple paths can be specified. - Valid items must conform to the --tmpfs argument - of systemd-nspawn. See systemd-nspawn(1) for details. - ''; - }; - - extraFlags = mkOption { - type = types.listOf types.str; - default = []; - example = [ "--drop-capability=CAP_SYS_CHROOT" ]; - description = '' - Extra flags passed to the systemd-nspawn command. - See systemd-nspawn(1) for details. - ''; - }; - - } // networkOptions; - - config = mkMerge - [ - (mkIf options.config.isDefined { - path = config.config.system.build.toplevel; - }) + users.extraUsers = builtins.listToAttrs ( + ( + builtins.foldl' ( + acc: user: { + values = acc.values ++ [ + { + name = user; + value = { + subUidRanges = [ { startUid = acc.offset; count = 65536; } ]; + subGidRanges = [ { startGid = acc.offset; count = 65536; } ]; + }; + } ]; - })); - - default = {}; - example = literalExample - '' - { webserver = - { path = "/nix/var/nix/profiles/webserver"; - }; - database = - { config = - { config, pkgs, ... }: - { services.postgresql.enable = true; - services.postgresql.package = pkgs.postgresql_9_6; - - system.stateVersion = "17.03"; - }; - }; + offset = acc.offset + 65536; } - ''; - description = '' - A set of NixOS system configurations to be run as lightweight - containers. Each container appears as a service - <literal>container-<replaceable>name</replaceable></literal> - on the host system, allowing it to be started and stopped via - <command>systemctl</command>. - ''; - }; + ) + { values = []; offset = 100000; } (lib.unique cfg.users) + ).values + ); + environment.etc."containers/policy.json".source = + if cfg.policy != {} then pkgs.writeText "policy.json" (builtins.toJSON cfg.policy) + else copyFile "${pkgs.skopeo.src}/default-policy.json"; }; - - config = mkIf (config.boot.enableContainers) (let - - unit = { - description = "Container '%i'"; - - unitConfig.RequiresMountsFor = "/var/lib/containers/%i"; - - path = [ pkgs.iproute ]; - - environment = { - root = "/var/lib/containers/%i"; - INSTANCE = "%i"; - }; - - preStart = preStartScript dummyConfig; - - script = startScript dummyConfig; - - postStart = postStartScript dummyConfig; - - preStop = - '' - pid="$(cat /run/containers/$INSTANCE.pid)" - if [ -n "$pid" ]; then - kill -RTMIN+4 "$pid" - fi - rm -f "/run/containers/$INSTANCE.pid" - ''; - - restartIfChanged = false; - - serviceConfig = serviceDirectives dummyConfig; - }; - in { - systemd.targets.multi-user.wants = [ "machines.target" ]; - - systemd.services = listToAttrs (filter (x: x.value != null) ( - # The generic container template used by imperative containers - [{ name = "container@"; value = unit; }] - # declarative containers - ++ (mapAttrsToList (name: cfg: nameValuePair "container@${name}" (let - containerConfig = cfg // ( - if cfg.enableTun then - { - allowedDevices = cfg.allowedDevices - ++ [ { node = "/dev/net/tun"; modifier = "rw"; } ]; - additionalCapabilities = cfg.additionalCapabilities - ++ [ "CAP_NET_ADMIN" ]; - } - else {}); - in - recursiveUpdate unit { - preStart = preStartScript containerConfig; - script = startScript containerConfig; - postStart = postStartScript containerConfig; - serviceConfig = serviceDirectives containerConfig; - unitConfig.RequiresMountsFor = lib.optional (!containerConfig.ephemeral) "/var/lib/containers/%i"; - environment.root = if containerConfig.ephemeral then "/run/containers/%i" else "/var/lib/containers/%i"; - } // ( - if containerConfig.autoStart then - { - wantedBy = [ "machines.target" ]; - wants = [ "network.target" ]; - after = [ "network.target" ]; - restartTriggers = [ - containerConfig.path - config.environment.etc."containers/${name}.conf".source - ]; - restartIfChanged = true; - } - else {}) - )) config.containers) - )); - - # Generate a configuration file in /etc/containers for each - # container so that container@.target can get the container - # configuration. - environment.etc = - let mkPortStr = p: p.protocol + ":" + (toString p.hostPort) + ":" + (if p.containerPort == null then toString p.hostPort else toString p.containerPort); - in mapAttrs' (name: cfg: nameValuePair "containers/${name}.conf" - { text = - '' - SYSTEM_PATH=${cfg.path} - ${optionalString cfg.privateNetwork '' - PRIVATE_NETWORK=1 - ${optionalString (cfg.hostBridge != null) '' - HOST_BRIDGE=${cfg.hostBridge} - ''} - ${optionalString (length cfg.forwardPorts > 0) '' - HOST_PORT=${concatStringsSep "," (map mkPortStr cfg.forwardPorts)} - ''} - ${optionalString (cfg.hostAddress != null) '' - HOST_ADDRESS=${cfg.hostAddress} - ''} - ${optionalString (cfg.hostAddress6 != null) '' - HOST_ADDRESS6=${cfg.hostAddress6} - ''} - ${optionalString (cfg.localAddress != null) '' - LOCAL_ADDRESS=${cfg.localAddress} - ''} - ${optionalString (cfg.localAddress6 != null) '' - LOCAL_ADDRESS6=${cfg.localAddress6} - ''} - ''} - INTERFACES="${toString cfg.interfaces}" - MACVLANS="${toString cfg.macvlans}" - ${optionalString cfg.autoStart '' - AUTO_START=1 - ''} - EXTRA_NSPAWN_FLAGS="${mkBindFlags cfg.bindMounts + - optionalString (cfg.extraFlags != []) - (" " + concatStringsSep " " cfg.extraFlags)}" - ''; - }) config.containers; - - # Generate /etc/hosts entries for the containers. - networking.extraHosts = concatStrings (mapAttrsToList (name: cfg: optionalString (cfg.localAddress != null) - '' - ${head (splitString "/" cfg.localAddress)} ${name}.containers - '') config.containers); - - networking.dhcpcd.denyInterfaces = [ "ve-*" "vb-*" ]; - - services.udev.extraRules = optionalString config.networking.networkmanager.enable '' - # Don't manage interfaces created by nixos-container. - ENV{INTERFACE}=="v[eb]-*", ENV{NM_UNMANAGED}="1" - ''; - - environment.systemPackages = [ pkgs.nixos-container ]; - - boot.kernelModules = [ - "bridge" - "macvlan" - "tap" - "tun" - ]; - }); } diff --git a/nixos/modules/virtualisation/cri-o.nix b/nixos/modules/virtualisation/cri-o.nix index 14a435f6c8bb9..89ba9c3c66365 100644 --- a/nixos/modules/virtualisation/cri-o.nix +++ b/nixos/modules/virtualisation/cri-o.nix @@ -6,6 +6,14 @@ let cfg = config.virtualisation.cri-o; in { + imports = [ + (mkRenamedOptionModule [ "virtualisation" "cri-o" "registries" ] [ "virtualisation" "containers" "registries" "search" ]) + ]; + + meta = { + maintainers = lib.teams.podman.members; + }; + options.virtualisation.cri-o = { enable = mkEnableOption "Container Runtime Interface for OCI (CRI-O)"; @@ -32,17 +40,11 @@ in default = "/pause"; description = "Pause command to be executed"; }; - - registries = mkOption { - type = types.listOf types.str; - default = [ "docker.io" "quay.io" ]; - description = "Registries to be configured for unqualified image pull"; - }; }; config = mkIf cfg.enable { environment.systemPackages = with pkgs; - [ cri-o cri-tools conmon cni-plugins iptables runc utillinux ]; + [ cri-o cri-tools conmon iptables runc utillinux ]; environment.etc."crictl.yaml".text = '' runtime-endpoint: unix:///var/run/crio/crio.sock ''; @@ -53,18 +55,17 @@ in [crio.image] pause_image = "${cfg.pauseImage}" pause_command = "${cfg.pauseCommand}" - registries = [ - ${concatMapStringsSep ", " (x: "\"" + x + "\"") cfg.registries} - ] + + [crio.network] + plugin_dirs = ["${pkgs.cni-plugins}/bin/"] + network_dir = "/etc/cni/net.d/" [crio.runtime] conmon = "${pkgs.conmon}/bin/conmon" log_level = "${cfg.logLevel}" manage_network_ns_lifecycle = true ''; - environment.etc."containers/policy.json".text = '' - {"default": [{"type": "insecureAcceptAnything"}]} - ''; + environment.etc."cni/net.d/20-cri-o-bridge.conf".text = '' { "cniVersion": "0.3.1", @@ -83,6 +84,9 @@ in } ''; + # Enable common /etc/containers configuration + virtualisation.containers.enable = true; + systemd.services.crio = { description = "Container Runtime Interface for OCI (CRI-O)"; documentation = [ "https://github.com/cri-o/cri-o" ]; diff --git a/nixos/modules/virtualisation/docker-containers.nix b/nixos/modules/virtualisation/docker-containers.nix index 59b0943f591f1..5ab990a3d7cc3 100644 --- a/nixos/modules/virtualisation/docker-containers.nix +++ b/nixos/modules/virtualisation/docker-containers.nix @@ -10,11 +10,24 @@ let options = { image = mkOption { - type = types.str; + type = with types; str; description = "Docker image to run."; example = "library/hello-world"; }; + imageFile = mkOption { + type = with types; nullOr package; + default = null; + description = '' + Path to an image file to load instead of pulling from a registry. + If defined, do not pull from registry. + + You still need to set the <literal>image</literal> attribute, as it + will be used as the image name for docker to start a container. + ''; + example = literalExample "pkgs.dockerTools.buildDockerImage {...};"; + }; + cmd = mkOption { type = with types; listOf str; default = []; @@ -26,7 +39,7 @@ let entrypoint = mkOption { type = with types; nullOr str; - description = "Overwrite the default entrypoint of the image."; + description = "Override the default entrypoint of the image."; default = null; example = "/bin/my-app"; }; @@ -132,7 +145,7 @@ let Note that this is a list of <literal>"src:dst"</literal> strings to allow for <literal>src</literal> to refer to - <literal>/nix/store</literal> paths, which would difficult with an + <literal>/nix/store</literal> paths, which would be difficult with an attribute set. There are also a variety of mount options available as a third field; please refer to the <link xlink:href="https://docs.docker.com/engine/reference/run/#volume-shared-filesystems"> @@ -153,6 +166,24 @@ let example = "/var/lib/hello_world"; }; + dependsOn = mkOption { + type = with types; listOf str; + default = []; + description = '' + Define which other containers this one depends on. They will be added to both After and Requires for the unit. + + Use the same name as the attribute under <literal>services.docker-containers</literal>. + ''; + example = literalExample '' + services.docker-containers = { + node1 = {}; + node2 = { + dependsOn = [ "node1" ]; + } + } + ''; + }; + extraDockerOptions = mkOption { type = with types; listOf str; default = []; @@ -161,18 +192,39 @@ let ["--network=host"] ''; }; + + autoStart = mkOption { + type = types.bool; + default = true; + description = '' + When enabled, the container is automatically started on boot. + If this option is set to false, the container has to be started on-demand via its service. + ''; + }; }; }; - mkService = name: container: { - wantedBy = [ "multi-user.target" ]; - after = [ "docker.service" "docker.socket" ]; - requires = [ "docker.service" "docker.socket" ]; + mkService = name: container: let + mkAfter = map (x: "docker-${x}.service") container.dependsOn; + in rec { + wantedBy = [] ++ optional (container.autoStart) "multi-user.target"; + after = [ "docker.service" "docker.socket" ] ++ mkAfter; + requires = after; + path = [ pkgs.docker ]; + + preStart = '' + docker rm -f ${name} || true + ${optionalString (container.imageFile != null) '' + docker load -i ${container.imageFile} + ''} + ''; + postStop = "docker rm -f ${name} || true"; + serviceConfig = { ExecStart = concatStringsSep " \\\n " ([ "${pkgs.docker}/bin/docker run" "--rm" - "--name=%n" + "--name=${name}" "--log-driver=${container.log-driver}" ] ++ optional (container.entrypoint != null) "--entrypoint=${escapeShellArg container.entrypoint}" @@ -185,9 +237,8 @@ let ++ [container.image] ++ map escapeShellArg container.cmd ); - ExecStartPre = "-${pkgs.docker}/bin/docker rm -f %n"; - ExecStop = "${pkgs.docker}/bin/docker stop %n"; - ExecStopPost = "-${pkgs.docker}/bin/docker rm -f %n"; + + ExecStop = ''${pkgs.bash}/bin/sh -c "[ $SERVICE_RESULT = success ] || docker stop ${name}"''; ### There is no generalized way of supporting `reload` for docker ### containers. Some containers may respond well to SIGHUP sent to their diff --git a/nixos/modules/virtualisation/ec2-data.nix b/nixos/modules/virtualisation/ec2-data.nix index 82451787e8a1d..629125350182e 100644 --- a/nixos/modules/virtualisation/ec2-data.nix +++ b/nixos/modules/virtualisation/ec2-data.nix @@ -7,6 +7,10 @@ with lib; { + imports = [ + (mkRemovedOptionModule [ "ec2" "metadata" ] "") + ]; + config = { systemd.services.apply-ec2-data = diff --git a/nixos/modules/virtualisation/hyperv-guest.nix b/nixos/modules/virtualisation/hyperv-guest.nix index 0f1f052880c5f..adc2810a99398 100644 --- a/nixos/modules/virtualisation/hyperv-guest.nix +++ b/nixos/modules/virtualisation/hyperv-guest.nix @@ -32,7 +32,7 @@ in { ]; kernelParams = [ - "video=hyperv_fb:${cfg.videoMode}" + "video=hyperv_fb:${cfg.videoMode} elevator=noop" ]; }; diff --git a/nixos/modules/virtualisation/hyperv-image.nix b/nixos/modules/virtualisation/hyperv-image.nix new file mode 100644 index 0000000000000..be2f12b7d0147 --- /dev/null +++ b/nixos/modules/virtualisation/hyperv-image.nix @@ -0,0 +1,69 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.hyperv; + +in { + options = { + hyperv = { + baseImageSize = mkOption { + type = types.int; + default = 2048; + description = '' + The size of the hyper-v base image in MiB. + ''; + }; + vmDerivationName = mkOption { + type = types.str; + default = "nixos-hyperv-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}"; + description = '' + The name of the derivation for the hyper-v appliance. + ''; + }; + vmFileName = mkOption { + type = types.str; + default = "nixos-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.vhdx"; + description = '' + The file name of the hyper-v appliance. + ''; + }; + }; + }; + + config = { + system.build.hypervImage = import ../../lib/make-disk-image.nix { + name = cfg.vmDerivationName; + postVM = '' + ${pkgs.vmTools.qemu}/bin/qemu-img convert -f raw -o subformat=dynamic -O vhdx $diskImage $out/${cfg.vmFileName} + ''; + format = "raw"; + diskSize = cfg.baseImageSize; + partitionTableType = "efi"; + inherit config lib pkgs; + }; + + fileSystems."/" = { + device = "/dev/disk/by-label/nixos"; + autoResize = true; + fsType = "ext4"; + }; + + fileSystems."/boot" = { + device = "/dev/disk/by-label/ESP"; + fsType = "vfat"; + }; + + boot.growPartition = true; + + boot.loader.grub = { + version = 2; + device = "nodev"; + efiSupport = true; + efiInstallAsRemovable = true; + }; + + virtualisation.hypervGuest.enable = true; + }; +} diff --git a/nixos/modules/virtualisation/kvmgt.nix b/nixos/modules/virtualisation/kvmgt.nix index 36ef6d17df69d..0902d2dc2cb09 100644 --- a/nixos/modules/virtualisation/kvmgt.nix +++ b/nixos/modules/virtualisation/kvmgt.nix @@ -19,7 +19,8 @@ in { virtualisation.kvmgt = { enable = mkEnableOption '' KVMGT (iGVT-g) VGPU support. Allows Qemu/KVM guests to share host's Intel integrated graphics card. - Currently only one graphical device can be shared + Currently only one graphical device can be shared. To allow users to access the device without root add them + to the kvm group: <literal>users.extraUsers.<yourusername>.extraGroups = [ "kvm" ];</literal> ''; # multi GPU support is under the question device = mkOption { @@ -35,9 +36,7 @@ in { and find info about device via <command>cat /sys/bus/pci/devices/*/mdev_supported_types/i915-GVTg_V5_4/description</command> ''; example = { - i915-GVTg_V5_8 = { - uuid = "a297db4a-f4c2-11e6-90f6-d3b88d6c9525"; - }; + i915-GVTg_V5_8.uuid = "a297db4a-f4c2-11e6-90f6-d3b88d6c9525"; }; }; }; @@ -50,10 +49,7 @@ in { }; boot.kernelModules = [ "kvmgt" ]; - - boot.extraModprobeConfig = '' - options i915 enable_gvt=1 - ''; + boot.kernelParams = [ "i915.enable_gvt=1" ]; systemd.paths = mapAttrs' (name: value: nameValuePair "kvmgt-${name}" { @@ -65,6 +61,10 @@ in { } ) cfg.vgpus; + services.udev.extraRules = '' + SUBSYSTEM=="vfio", OWNER="root", GROUP="kvm" + ''; + systemd.services = mapAttrs' (name: value: nameValuePair "kvmgt-${name}" { description = "KVMGT VGPU ${name}"; diff --git a/nixos/modules/virtualisation/libvirtd.nix b/nixos/modules/virtualisation/libvirtd.nix index 9bdea78296f26..4f22099443f43 100644 --- a/nixos/modules/virtualisation/libvirtd.nix +++ b/nixos/modules/virtualisation/libvirtd.nix @@ -28,6 +28,11 @@ let in { + imports = [ + (mkRemovedOptionModule [ "virtualisation" "libvirtd" "enableKVM" ] + "Set the option `virtualisation.libvirtd.qemuPackage' instead.") + ]; + ###### interface options.virtualisation.libvirtd = { @@ -209,14 +214,14 @@ in { }; systemd.services.libvirtd = { - description = "Libvirt Virtual Machine Management Daemon"; - - wantedBy = [ "multi-user.target" ]; requires = [ "libvirtd-config.service" ]; after = [ "systemd-udev-settle.service" "libvirtd-config.service" ] - ++ optional vswitch.enable "vswitchd.service"; + ++ optional vswitch.enable "ovs-vswitchd.service"; - environment.LIBVIRTD_ARGS = ''--config "${configFile}" ${concatStringsSep " " cfg.extraOptions}''; + environment.LIBVIRTD_ARGS = escapeShellArgs ( + [ "--config" configFile + "--timeout" "120" # from ${libvirt}/var/lib/sysconfig/libvirtd + ] ++ cfg.extraOptions); path = [ cfg.qemuPackage ] # libvirtd requires qemu-img to manage disk images ++ optional vswitch.enable vswitch.package; @@ -261,5 +266,8 @@ in { serviceConfig.ExecStart = "@${pkgs.libvirt}/sbin/virtlockd virtlockd"; restartIfChanged = false; }; + + systemd.sockets.libvirtd .wantedBy = [ "sockets.target" ]; + systemd.sockets.libvirtd-tcp.wantedBy = [ "sockets.target" ]; }; } diff --git a/nixos/modules/virtualisation/lxc.nix b/nixos/modules/virtualisation/lxc.nix index 9b5adaf08249e..f484d5ee59a88 100644 --- a/nixos/modules/virtualisation/lxc.nix +++ b/nixos/modules/virtualisation/lxc.nix @@ -58,7 +58,7 @@ in '' This is the config file for managing unprivileged user network administration access in LXC. See <citerefentry> - <refentrytitle>lxc-user-net</refentrytitle><manvolnum>5</manvolnum> + <refentrytitle>lxc-usernet</refentrytitle><manvolnum>5</manvolnum> </citerefentry>. ''; }; diff --git a/nixos/modules/virtualisation/lxd.nix b/nixos/modules/virtualisation/lxd.nix index 505c11abd2087..de48d3a780e27 100644 --- a/nixos/modules/virtualisation/lxd.nix +++ b/nixos/modules/virtualisation/lxd.nix @@ -7,6 +7,7 @@ with lib; let cfg = config.virtualisation.lxd; + zfsCfg = config.boot.zfs; in @@ -26,15 +27,56 @@ in <command>lxc</command> command line tool, among others. ''; }; + + package = mkOption { + type = types.package; + default = pkgs.lxd; + defaultText = "pkgs.lxd"; + description = '' + The LXD package to use. + ''; + }; + + lxcPackage = mkOption { + type = types.package; + default = pkgs.lxc; + defaultText = "pkgs.lxc"; + description = '' + The LXC package to use with LXD (required for AppArmor profiles). + ''; + }; + + zfsPackage = mkOption { + type = types.package; + default = with pkgs; if zfsCfg.enableUnstable then zfsUnstable else zfs; + defaultText = "pkgs.zfs"; + description = '' + The ZFS package to use with LXD. + ''; + }; + zfsSupport = mkOption { type = types.bool; default = false; description = '' - enables lxd to use zfs as a storage for containers. + Enables lxd to use zfs as a storage for containers. + This option is enabled by default if a zfs pool is configured with nixos. ''; }; + recommendedSysctlSettings = mkOption { + type = types.bool; + default = false; + description = '' + enables various settings to avoid common pitfalls when + running containers requiring many file operations. + Fixes errors like "Too many open files" or + "neighbour: ndisc_cache: neighbor table overflow!". + See https://lxd.readthedocs.io/en/latest/production-setup/ + for details. + ''; + }; }; }; @@ -42,15 +84,15 @@ in config = mkIf cfg.enable { - environment.systemPackages = [ pkgs.lxd ]; + environment.systemPackages = [ cfg.package ]; security.apparmor = { enable = true; profiles = [ - "${pkgs.lxc}/etc/apparmor.d/usr.bin.lxc-start" - "${pkgs.lxc}/etc/apparmor.d/lxc-containers" + "${cfg.lxcPackage}/etc/apparmor.d/usr.bin.lxc-start" + "${cfg.lxcPackage}/etc/apparmor.d/lxc-containers" ]; - packages = [ pkgs.lxc ]; + packages = [ cfg.lxcPackage ]; }; systemd.services.lxd = { @@ -59,18 +101,21 @@ in wantedBy = [ "multi-user.target" ]; after = [ "systemd-udev-settle.service" ]; - path = lib.optional cfg.zfsSupport pkgs.zfs; + path = lib.optional cfg.zfsSupport cfg.zfsPackage; preStart = '' mkdir -m 0755 -p /var/lib/lxc/rootfs ''; serviceConfig = { - ExecStart = "@${pkgs.lxd.bin}/bin/lxd lxd --group lxd"; + ExecStart = "@${cfg.package.bin}/bin/lxd lxd --group lxd"; Type = "simple"; KillMode = "process"; # when stopping, leave the containers alone + LimitMEMLOCK = "infinity"; + LimitNOFILE = "1048576"; + LimitNPROC = "infinity"; + TasksMax = "infinity"; }; - }; users.groups.lxd.gid = config.ids.gids.lxd; @@ -79,5 +124,16 @@ in subUidRanges = [ { startUid = 1000000; count = 65536; } ]; subGidRanges = [ { startGid = 1000000; count = 65536; } ]; }; + + boot.kernel.sysctl = mkIf cfg.recommendedSysctlSettings { + "fs.inotify.max_queued_events" = 1048576; + "fs.inotify.max_user_instances" = 1048576; + "fs.inotify.max_user_watches" = 1048576; + "vm.max_map_count" = 262144; + "kernel.dmesg_restrict" = 1; + "net.ipv4.neigh.default.gc_thresh3" = 8192; + "net.ipv6.neigh.default.gc_thresh3" = 8192; + "kernel.keys.maxkeys" = 2000; + }; }; } diff --git a/nixos/modules/virtualisation/nixos-containers.nix b/nixos/modules/virtualisation/nixos-containers.nix new file mode 100644 index 0000000000000..016d68ea30183 --- /dev/null +++ b/nixos/modules/virtualisation/nixos-containers.nix @@ -0,0 +1,844 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + # The container's init script, a small wrapper around the regular + # NixOS stage-2 init script. + containerInit = (cfg: + let + renderExtraVeth = (name: cfg: + '' + echo "Bringing ${name} up" + ip link set dev ${name} up + ${optionalString (cfg.localAddress != null) '' + echo "Setting ip for ${name}" + ip addr add ${cfg.localAddress} dev ${name} + ''} + ${optionalString (cfg.localAddress6 != null) '' + echo "Setting ip6 for ${name}" + ip -6 addr add ${cfg.localAddress6} dev ${name} + ''} + ${optionalString (cfg.hostAddress != null) '' + echo "Setting route to host for ${name}" + ip route add ${cfg.hostAddress} dev ${name} + ''} + ${optionalString (cfg.hostAddress6 != null) '' + echo "Setting route6 to host for ${name}" + ip -6 route add ${cfg.hostAddress6} dev ${name} + ''} + '' + ); + in + pkgs.writeScript "container-init" + '' + #! ${pkgs.runtimeShell} -e + + # Initialise the container side of the veth pair. + if [ -n "$HOST_ADDRESS" ] || [ -n "$HOST_ADDRESS6" ] || + [ -n "$LOCAL_ADDRESS" ] || [ -n "$LOCAL_ADDRESS6" ] || + [ -n "$HOST_BRIDGE" ]; then + ip link set host0 name eth0 + ip link set dev eth0 up + + if [ -n "$LOCAL_ADDRESS" ]; then + ip addr add $LOCAL_ADDRESS dev eth0 + fi + if [ -n "$LOCAL_ADDRESS6" ]; then + ip -6 addr add $LOCAL_ADDRESS6 dev eth0 + fi + if [ -n "$HOST_ADDRESS" ]; then + ip route add $HOST_ADDRESS dev eth0 + ip route add default via $HOST_ADDRESS + fi + if [ -n "$HOST_ADDRESS6" ]; then + ip -6 route add $HOST_ADDRESS6 dev eth0 + ip -6 route add default via $HOST_ADDRESS6 + fi + + ${concatStringsSep "\n" (mapAttrsToList renderExtraVeth cfg.extraVeths)} + fi + + # Start the regular stage 1 script. + exec "$1" + '' + ); + + nspawnExtraVethArgs = (name: cfg: "--network-veth-extra=${name}"); + + startScript = cfg: + '' + mkdir -p -m 0755 "$root/etc" "$root/var/lib" + mkdir -p -m 0700 "$root/var/lib/private" "$root/root" /run/containers + if ! [ -e "$root/etc/os-release" ]; then + touch "$root/etc/os-release" + fi + + if ! [ -e "$root/etc/machine-id" ]; then + touch "$root/etc/machine-id" + fi + + mkdir -p -m 0755 \ + "/nix/var/nix/profiles/per-container/$INSTANCE" \ + "/nix/var/nix/gcroots/per-container/$INSTANCE" + + cp --remove-destination /etc/resolv.conf "$root/etc/resolv.conf" + + if [ "$PRIVATE_NETWORK" = 1 ]; then + extraFlags+=" --private-network" + fi + + if [ -n "$HOST_ADDRESS" ] || [ -n "$LOCAL_ADDRESS" ] || + [ -n "$HOST_ADDRESS6" ] || [ -n "$LOCAL_ADDRESS6" ]; then + extraFlags+=" --network-veth" + fi + + if [ -n "$HOST_PORT" ]; then + OIFS=$IFS + IFS="," + for i in $HOST_PORT + do + extraFlags+=" --port=$i" + done + IFS=$OIFS + fi + + if [ -n "$HOST_BRIDGE" ]; then + extraFlags+=" --network-bridge=$HOST_BRIDGE" + fi + + extraFlags+=" ${concatStringsSep " " (mapAttrsToList nspawnExtraVethArgs cfg.extraVeths)}" + + for iface in $INTERFACES; do + extraFlags+=" --network-interface=$iface" + done + + for iface in $MACVLANS; do + extraFlags+=" --network-macvlan=$iface" + done + + # If the host is 64-bit and the container is 32-bit, add a + # --personality flag. + ${optionalString (config.nixpkgs.localSystem.system == "x86_64-linux") '' + if [ "$(< ''${SYSTEM_PATH:-/nix/var/nix/profiles/per-container/$INSTANCE/system}/system)" = i686-linux ]; then + extraFlags+=" --personality=x86" + fi + ''} + + # Run systemd-nspawn without startup notification (we'll + # wait for the container systemd to signal readiness). + exec ${config.systemd.package}/bin/systemd-nspawn \ + --keep-unit \ + -M "$INSTANCE" -D "$root" $extraFlags \ + $EXTRA_NSPAWN_FLAGS \ + --notify-ready=yes \ + --bind-ro=/nix/store \ + --bind-ro=/nix/var/nix/db \ + --bind-ro=/nix/var/nix/daemon-socket \ + --bind="/nix/var/nix/profiles/per-container/$INSTANCE:/nix/var/nix/profiles" \ + --bind="/nix/var/nix/gcroots/per-container/$INSTANCE:/nix/var/nix/gcroots" \ + ${optionalString (!cfg.ephemeral) "--link-journal=try-guest"} \ + --setenv PRIVATE_NETWORK="$PRIVATE_NETWORK" \ + --setenv HOST_BRIDGE="$HOST_BRIDGE" \ + --setenv HOST_ADDRESS="$HOST_ADDRESS" \ + --setenv LOCAL_ADDRESS="$LOCAL_ADDRESS" \ + --setenv HOST_ADDRESS6="$HOST_ADDRESS6" \ + --setenv LOCAL_ADDRESS6="$LOCAL_ADDRESS6" \ + --setenv HOST_PORT="$HOST_PORT" \ + --setenv PATH="$PATH" \ + ${optionalString cfg.ephemeral "--ephemeral"} \ + ${if cfg.additionalCapabilities != null && cfg.additionalCapabilities != [] then + ''--capability="${concatStringsSep "," cfg.additionalCapabilities}"'' else "" + } \ + ${if cfg.tmpfs != null && cfg.tmpfs != [] then + ''--tmpfs=${concatStringsSep " --tmpfs=" cfg.tmpfs}'' else "" + } \ + ${containerInit cfg} "''${SYSTEM_PATH:-/nix/var/nix/profiles/system}/init" + ''; + + preStartScript = cfg: + '' + # Clean up existing machined registration and interfaces. + machinectl terminate "$INSTANCE" 2> /dev/null || true + + if [ -n "$HOST_ADDRESS" ] || [ -n "$LOCAL_ADDRESS" ] || + [ -n "$HOST_ADDRESS6" ] || [ -n "$LOCAL_ADDRESS6" ]; then + ip link del dev "ve-$INSTANCE" 2> /dev/null || true + ip link del dev "vb-$INSTANCE" 2> /dev/null || true + fi + + ${concatStringsSep "\n" ( + mapAttrsToList (name: cfg: + ''ip link del dev ${name} 2> /dev/null || true '' + ) cfg.extraVeths + )} + ''; + + postStartScript = (cfg: + let + ipcall = cfg: ipcmd: variable: attribute: + if cfg.${attribute} == null then + '' + if [ -n "${variable}" ]; then + ${ipcmd} add ${variable} dev $ifaceHost + fi + '' + else + ''${ipcmd} add ${cfg.${attribute}} dev $ifaceHost''; + renderExtraVeth = name: cfg: + if cfg.hostBridge != null then + '' + # Add ${name} to bridge ${cfg.hostBridge} + ip link set dev ${name} master ${cfg.hostBridge} up + '' + else + '' + echo "Bring ${name} up" + ip link set dev ${name} up + # Set IPs and routes for ${name} + ${optionalString (cfg.hostAddress != null) '' + ip addr add ${cfg.hostAddress} dev ${name} + ''} + ${optionalString (cfg.hostAddress6 != null) '' + ip -6 addr add ${cfg.hostAddress6} dev ${name} + ''} + ${optionalString (cfg.localAddress != null) '' + ip route add ${cfg.localAddress} dev ${name} + ''} + ${optionalString (cfg.localAddress6 != null) '' + ip -6 route add ${cfg.localAddress6} dev ${name} + ''} + ''; + in + '' + if [ -n "$HOST_ADDRESS" ] || [ -n "$LOCAL_ADDRESS" ] || + [ -n "$HOST_ADDRESS6" ] || [ -n "$LOCAL_ADDRESS6" ]; then + if [ -z "$HOST_BRIDGE" ]; then + ifaceHost=ve-$INSTANCE + ip link set dev $ifaceHost up + + ${ipcall cfg "ip addr" "$HOST_ADDRESS" "hostAddress"} + ${ipcall cfg "ip -6 addr" "$HOST_ADDRESS6" "hostAddress6"} + ${ipcall cfg "ip route" "$LOCAL_ADDRESS" "localAddress"} + ${ipcall cfg "ip -6 route" "$LOCAL_ADDRESS6" "localAddress6"} + fi + ${concatStringsSep "\n" (mapAttrsToList renderExtraVeth cfg.extraVeths)} + fi + '' + ); + + serviceDirectives = cfg: { + ExecReload = pkgs.writeScript "reload-container" + '' + #! ${pkgs.runtimeShell} -e + ${pkgs.nixos-container}/bin/nixos-container run "$INSTANCE" -- \ + bash --login -c "''${SYSTEM_PATH:-/nix/var/nix/profiles/system}/bin/switch-to-configuration test" + ''; + + SyslogIdentifier = "container %i"; + + EnvironmentFile = "-/etc/containers/%i.conf"; + + Type = "notify"; + + RuntimeDirectory = lib.optional cfg.ephemeral "containers/%i"; + + # Note that on reboot, systemd-nspawn returns 133, so this + # unit will be restarted. On poweroff, it returns 0, so the + # unit won't be restarted. + RestartForceExitStatus = "133"; + SuccessExitStatus = "133"; + + # Some containers take long to start + # especially when you automatically start many at once + TimeoutStartSec = cfg.timeoutStartSec; + + Restart = "on-failure"; + + Slice = "machine.slice"; + Delegate = true; + + # Hack: we don't want to kill systemd-nspawn, since we call + # "machinectl poweroff" in preStop to shut down the + # container cleanly. But systemd requires sending a signal + # (at least if we want remaining processes to be killed + # after the timeout). So send an ignored signal. + KillMode = "mixed"; + KillSignal = "WINCH"; + + DevicePolicy = "closed"; + DeviceAllow = map (d: "${d.node} ${d.modifier}") cfg.allowedDevices; + }; + + + system = config.nixpkgs.localSystem.system; + + bindMountOpts = { name, ... }: { + + options = { + mountPoint = mkOption { + example = "/mnt/usb"; + type = types.str; + description = "Mount point on the container file system."; + }; + hostPath = mkOption { + default = null; + example = "/home/alice"; + type = types.nullOr types.str; + description = "Location of the host path to be mounted."; + }; + isReadOnly = mkOption { + default = true; + type = types.bool; + description = "Determine whether the mounted path will be accessed in read-only mode."; + }; + }; + + config = { + mountPoint = mkDefault name; + }; + + }; + + allowedDeviceOpts = { ... }: { + options = { + node = mkOption { + example = "/dev/net/tun"; + type = types.str; + description = "Path to device node"; + }; + modifier = mkOption { + example = "rw"; + type = types.str; + description = '' + Device node access modifier. Takes a combination + <literal>r</literal> (read), <literal>w</literal> (write), and + <literal>m</literal> (mknod). See the + <literal>systemd.resource-control(5)</literal> man page for more + information.''; + }; + }; + }; + + + mkBindFlag = d: + let flagPrefix = if d.isReadOnly then " --bind-ro=" else " --bind="; + mountstr = if d.hostPath != null then "${d.hostPath}:${d.mountPoint}" else "${d.mountPoint}"; + in flagPrefix + mountstr ; + + mkBindFlags = bs: concatMapStrings mkBindFlag (lib.attrValues bs); + + networkOptions = { + hostBridge = mkOption { + type = types.nullOr types.str; + default = null; + example = "br0"; + description = '' + Put the host-side of the veth-pair into the named bridge. + Only one of hostAddress* or hostBridge can be given. + ''; + }; + + forwardPorts = mkOption { + type = types.listOf (types.submodule { + options = { + protocol = mkOption { + type = types.str; + default = "tcp"; + description = "The protocol specifier for port forwarding between host and container"; + }; + hostPort = mkOption { + type = types.int; + description = "Source port of the external interface on host"; + }; + containerPort = mkOption { + type = types.nullOr types.int; + default = null; + description = "Target port of container"; + }; + }; + }); + default = []; + example = [ { protocol = "tcp"; hostPort = 8080; containerPort = 80; } ]; + description = '' + List of forwarded ports from host to container. Each forwarded port + is specified by protocol, hostPort and containerPort. By default, + protocol is tcp and hostPort and containerPort are assumed to be + the same if containerPort is not explicitly given. + ''; + }; + + + hostAddress = mkOption { + type = types.nullOr types.str; + default = null; + example = "10.231.136.1"; + description = '' + The IPv4 address assigned to the host interface. + (Not used when hostBridge is set.) + ''; + }; + + hostAddress6 = mkOption { + type = types.nullOr types.str; + default = null; + example = "fc00::1"; + description = '' + The IPv6 address assigned to the host interface. + (Not used when hostBridge is set.) + ''; + }; + + localAddress = mkOption { + type = types.nullOr types.str; + default = null; + example = "10.231.136.2"; + description = '' + The IPv4 address assigned to the interface in the container. + If a hostBridge is used, this should be given with netmask to access + the whole network. Otherwise the default netmask is /32 and routing is + set up from localAddress to hostAddress and back. + ''; + }; + + localAddress6 = mkOption { + type = types.nullOr types.str; + default = null; + example = "fc00::2"; + description = '' + The IPv6 address assigned to the interface in the container. + If a hostBridge is used, this should be given with netmask to access + the whole network. Otherwise the default netmask is /128 and routing is + set up from localAddress6 to hostAddress6 and back. + ''; + }; + + }; + + dummyConfig = + { + extraVeths = {}; + additionalCapabilities = []; + ephemeral = false; + timeoutStartSec = "15s"; + allowedDevices = []; + hostAddress = null; + hostAddress6 = null; + localAddress = null; + localAddress6 = null; + tmpfs = null; + }; + +in + +{ + options = { + + boot.isContainer = mkOption { + type = types.bool; + default = false; + description = '' + Whether this NixOS machine is a lightweight container running + in another NixOS system. If set to true, support for nested + containers is disabled by default, but can be reenabled by + setting <option>boot.enableContainers</option> to true. + ''; + }; + + boot.enableContainers = mkOption { + type = types.bool; + default = !config.boot.isContainer; + description = '' + Whether to enable support for NixOS containers. Defaults to true + (at no cost if containers are not actually used), but only if the + system is not itself a lightweight container of a host. + To enable support for nested containers, this option has to be + explicitly set to true (in the outer container). + ''; + }; + + containers = mkOption { + type = types.attrsOf (types.submodule ( + { config, options, name, ... }: + { + options = { + + config = mkOption { + description = '' + A specification of the desired configuration of this + container, as a NixOS module. + ''; + type = let + confPkgs = if config.pkgs == null then pkgs else config.pkgs; + in lib.mkOptionType { + name = "Toplevel NixOS config"; + merge = loc: defs: (import (confPkgs.path + "/nixos/lib/eval-config.nix") { + inherit system; + pkgs = confPkgs; + baseModules = import (confPkgs.path + "/nixos/modules/module-list.nix"); + inherit (confPkgs) lib; + modules = + let + extraConfig = { + _file = "module at ${__curPos.file}:${toString __curPos.line}"; + config = { + boot.isContainer = true; + networking.hostName = mkDefault name; + networking.useDHCP = false; + assertions = [ + { + assertion = config.privateNetwork -> stringLength name < 12; + message = '' + Container name `${name}` is too long: When `privateNetwork` is enabled, container names can + not be longer than 11 characters, because the container's interface name is derived from it. + This might be fixed in the future. See https://github.com/NixOS/nixpkgs/issues/38509 + ''; + } + ]; + }; + }; + in [ extraConfig ] ++ (map (x: x.value) defs); + prefix = [ "containers" name ]; + }).config; + }; + }; + + path = mkOption { + type = types.path; + example = "/nix/var/nix/profiles/containers/webserver"; + description = '' + As an alternative to specifying + <option>config</option>, you can specify the path to + the evaluated NixOS system configuration, typically a + symlink to a system profile. + ''; + }; + + additionalCapabilities = mkOption { + type = types.listOf types.str; + default = []; + example = [ "CAP_NET_ADMIN" "CAP_MKNOD" ]; + description = '' + Grant additional capabilities to the container. See the + capabilities(7) and systemd-nspawn(1) man pages for more + information. + ''; + }; + + pkgs = mkOption { + type = types.nullOr types.attrs; + default = null; + example = literalExample "pkgs"; + description = '' + Customise which nixpkgs to use for this container. + ''; + }; + + ephemeral = mkOption { + type = types.bool; + default = false; + description = '' + Runs container in ephemeral mode with the empty root filesystem at boot. + This way container will be bootstrapped from scratch on each boot + and will be cleaned up on shutdown leaving no traces behind. + Useful for completely stateless, reproducible containers. + + Note that this option might require to do some adjustments to the container configuration, + e.g. you might want to set + <varname>systemd.network.networks.$interface.dhcpConfig.ClientIdentifier</varname> to "mac" + if you use <varname>macvlans</varname> option. + This way dhcp client identifier will be stable between the container restarts. + + Note that the container journal will not be linked to the host if this option is enabled. + ''; + }; + + enableTun = mkOption { + type = types.bool; + default = false; + description = '' + Allows the container to create and setup tunnel interfaces + by granting the <literal>NET_ADMIN</literal> capability and + enabling access to <literal>/dev/net/tun</literal>. + ''; + }; + + privateNetwork = mkOption { + type = types.bool; + default = false; + description = '' + Whether to give the container its own private virtual + Ethernet interface. The interface is called + <literal>eth0</literal>, and is hooked up to the interface + <literal>ve-<replaceable>container-name</replaceable></literal> + on the host. If this option is not set, then the + container shares the network interfaces of the host, + and can bind to any port on any interface. + ''; + }; + + interfaces = mkOption { + type = types.listOf types.str; + default = []; + example = [ "eth1" "eth2" ]; + description = '' + The list of interfaces to be moved into the container. + ''; + }; + + macvlans = mkOption { + type = types.listOf types.str; + default = []; + example = [ "eth1" "eth2" ]; + description = '' + The list of host interfaces from which macvlans will be + created. For each interface specified, a macvlan interface + will be created and moved to the container. + ''; + }; + + extraVeths = mkOption { + type = with types; attrsOf (submodule { options = networkOptions; }); + default = {}; + description = '' + Extra veth-pairs to be created for the container. + ''; + }; + + autoStart = mkOption { + type = types.bool; + default = false; + description = '' + Whether the container is automatically started at boot-time. + ''; + }; + + timeoutStartSec = mkOption { + type = types.str; + default = "1min"; + description = '' + Time for the container to start. In case of a timeout, + the container processes get killed. + See <citerefentry><refentrytitle>systemd.time</refentrytitle> + <manvolnum>7</manvolnum></citerefentry> + for more information about the format. + ''; + }; + + bindMounts = mkOption { + type = with types; loaOf (submodule bindMountOpts); + default = {}; + example = literalExample '' + { "/home" = { hostPath = "/home/alice"; + isReadOnly = false; }; + } + ''; + + description = + '' + An extra list of directories that is bound to the container. + ''; + }; + + allowedDevices = mkOption { + type = with types; listOf (submodule allowedDeviceOpts); + default = []; + example = [ { node = "/dev/net/tun"; modifier = "rw"; } ]; + description = '' + A list of device nodes to which the containers has access to. + ''; + }; + + tmpfs = mkOption { + type = types.listOf types.str; + default = []; + example = [ "/var" ]; + description = '' + Mounts a set of tmpfs file systems into the container. + Multiple paths can be specified. + Valid items must conform to the --tmpfs argument + of systemd-nspawn. See systemd-nspawn(1) for details. + ''; + }; + + extraFlags = mkOption { + type = types.listOf types.str; + default = []; + example = [ "--drop-capability=CAP_SYS_CHROOT" ]; + description = '' + Extra flags passed to the systemd-nspawn command. + See systemd-nspawn(1) for details. + ''; + }; + + } // networkOptions; + + config = mkMerge + [ + (mkIf options.config.isDefined { + path = config.config.system.build.toplevel; + }) + ]; + })); + + default = {}; + example = literalExample + '' + { webserver = + { path = "/nix/var/nix/profiles/webserver"; + }; + database = + { config = + { config, pkgs, ... }: + { services.postgresql.enable = true; + services.postgresql.package = pkgs.postgresql_9_6; + + system.stateVersion = "17.03"; + }; + }; + } + ''; + description = '' + A set of NixOS system configurations to be run as lightweight + containers. Each container appears as a service + <literal>container-<replaceable>name</replaceable></literal> + on the host system, allowing it to be started and stopped via + <command>systemctl</command>. + ''; + }; + + }; + + + config = mkIf (config.boot.enableContainers) (let + + unit = { + description = "Container '%i'"; + + unitConfig.RequiresMountsFor = "/var/lib/containers/%i"; + + path = [ pkgs.iproute ]; + + environment = { + root = "/var/lib/containers/%i"; + INSTANCE = "%i"; + }; + + preStart = preStartScript dummyConfig; + + script = startScript dummyConfig; + + postStart = postStartScript dummyConfig; + + preStop = "machinectl poweroff $INSTANCE"; + + restartIfChanged = false; + + serviceConfig = serviceDirectives dummyConfig; + }; + in { + systemd.targets.multi-user.wants = [ "machines.target" ]; + + systemd.services = listToAttrs (filter (x: x.value != null) ( + # The generic container template used by imperative containers + [{ name = "container@"; value = unit; }] + # declarative containers + ++ (mapAttrsToList (name: cfg: nameValuePair "container@${name}" (let + containerConfig = cfg // ( + if cfg.enableTun then + { + allowedDevices = cfg.allowedDevices + ++ [ { node = "/dev/net/tun"; modifier = "rw"; } ]; + additionalCapabilities = cfg.additionalCapabilities + ++ [ "CAP_NET_ADMIN" ]; + } + else {}); + in + recursiveUpdate unit { + preStart = preStartScript containerConfig; + script = startScript containerConfig; + postStart = postStartScript containerConfig; + serviceConfig = serviceDirectives containerConfig; + unitConfig.RequiresMountsFor = lib.optional (!containerConfig.ephemeral) "/var/lib/containers/%i"; + environment.root = if containerConfig.ephemeral then "/run/containers/%i" else "/var/lib/containers/%i"; + } // ( + if containerConfig.autoStart then + { + wantedBy = [ "machines.target" ]; + wants = [ "network.target" ]; + after = [ "network.target" ]; + restartTriggers = [ + containerConfig.path + config.environment.etc."containers/${name}.conf".source + ]; + restartIfChanged = true; + } + else {}) + )) config.containers) + )); + + # Generate a configuration file in /etc/containers for each + # container so that container@.target can get the container + # configuration. + environment.etc = + let mkPortStr = p: p.protocol + ":" + (toString p.hostPort) + ":" + (if p.containerPort == null then toString p.hostPort else toString p.containerPort); + in mapAttrs' (name: cfg: nameValuePair "containers/${name}.conf" + { text = + '' + SYSTEM_PATH=${cfg.path} + ${optionalString cfg.privateNetwork '' + PRIVATE_NETWORK=1 + ${optionalString (cfg.hostBridge != null) '' + HOST_BRIDGE=${cfg.hostBridge} + ''} + ${optionalString (length cfg.forwardPorts > 0) '' + HOST_PORT=${concatStringsSep "," (map mkPortStr cfg.forwardPorts)} + ''} + ${optionalString (cfg.hostAddress != null) '' + HOST_ADDRESS=${cfg.hostAddress} + ''} + ${optionalString (cfg.hostAddress6 != null) '' + HOST_ADDRESS6=${cfg.hostAddress6} + ''} + ${optionalString (cfg.localAddress != null) '' + LOCAL_ADDRESS=${cfg.localAddress} + ''} + ${optionalString (cfg.localAddress6 != null) '' + LOCAL_ADDRESS6=${cfg.localAddress6} + ''} + ''} + INTERFACES="${toString cfg.interfaces}" + MACVLANS="${toString cfg.macvlans}" + ${optionalString cfg.autoStart '' + AUTO_START=1 + ''} + EXTRA_NSPAWN_FLAGS="${mkBindFlags cfg.bindMounts + + optionalString (cfg.extraFlags != []) + (" " + concatStringsSep " " cfg.extraFlags)}" + ''; + }) config.containers; + + # Generate /etc/hosts entries for the containers. + networking.extraHosts = concatStrings (mapAttrsToList (name: cfg: optionalString (cfg.localAddress != null) + '' + ${head (splitString "/" cfg.localAddress)} ${name}.containers + '') config.containers); + + networking.dhcpcd.denyInterfaces = [ "ve-*" "vb-*" ]; + + services.udev.extraRules = optionalString config.networking.networkmanager.enable '' + # Don't manage interfaces created by nixos-container. + ENV{INTERFACE}=="v[eb]-*", ENV{NM_UNMANAGED}="1" + ''; + + environment.systemPackages = [ pkgs.nixos-container ]; + + boot.kernelModules = [ + "bridge" + "macvlan" + "tap" + "tun" + ]; + }); +} diff --git a/nixos/modules/virtualisation/openvswitch.nix b/nixos/modules/virtualisation/openvswitch.nix index 6b8ad83661fe7..c6a3ceddc3e03 100644 --- a/nixos/modules/virtualisation/openvswitch.nix +++ b/nixos/modules/virtualisation/openvswitch.nix @@ -124,7 +124,7 @@ in { ''; }; - systemd.services.vswitchd = { + systemd.services.ovs-vswitchd = { description = "Open_vSwitch Daemon"; wantedBy = [ "multi-user.target" ]; bindsTo = [ "ovsdb.service" ]; @@ -139,6 +139,8 @@ in { PIDFile = "/run/openvswitch/ovs-vswitchd.pid"; # Use service type 'forking' to correctly determine when vswitchd is ready. Type = "forking"; + Restart = "always"; + RestartSec = 3; }; }; @@ -182,4 +184,7 @@ in { ''; }; })])); + + meta.maintainers = with maintainers; [ netixx ]; + } diff --git a/nixos/modules/virtualisation/podman.nix b/nixos/modules/virtualisation/podman.nix new file mode 100644 index 0000000000000..41d50dc73084c --- /dev/null +++ b/nixos/modules/virtualisation/podman.nix @@ -0,0 +1,105 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.virtualisation.podman; + + inherit (lib) mkOption types; + + # Provides a fake "docker" binary mapping to podman + dockerCompat = pkgs.runCommandNoCC "${pkgs.podman.pname}-docker-compat-${pkgs.podman.version}" { + outputs = [ "out" "bin" "man" ]; + inherit (pkgs.podman) meta; + } '' + mkdir $out + + mkdir -p $bin/bin + ln -s ${pkgs.podman.bin}/bin/podman $bin/bin/docker + + mkdir -p $man/share/man/man1 + for f in ${pkgs.podman.man}/share/man/man1/*; do + basename=$(basename $f | sed s/podman/docker/g) + ln -s $f $man/share/man/man1/$basename + done + ''; + + # Copy configuration files to avoid having the entire sources in the system closure + copyFile = filePath: pkgs.runCommandNoCC (builtins.unsafeDiscardStringContext (builtins.baseNameOf filePath)) {} '' + cp ${filePath} $out + ''; + +in +{ + meta = { + maintainers = lib.teams.podman.members; + }; + + options.virtualisation.podman = { + + enable = + mkOption { + type = types.bool; + default = false; + description = '' + This option enables Podman, a daemonless container engine for + developing, managing, and running OCI Containers on your Linux System. + + It is a drop-in replacement for the <command>docker</command> command. + ''; + }; + + dockerCompat = mkOption { + type = types.bool; + default = false; + description = '' + Create an alias mapping <command>docker</command> to <command>podman</command>. + ''; + }; + + libpod = mkOption { + default = {}; + description = "Libpod configuration"; + type = types.submodule { + options = { + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Extra configuration that should be put in the libpod.conf + configuration file + ''; + + }; + }; + }; + }; + + }; + + config = lib.mkIf cfg.enable { + + environment.systemPackages = [ + pkgs.podman # Docker compat + pkgs.runc # Default container runtime + pkgs.crun # Default container runtime (cgroups v2) + pkgs.conmon # Container runtime monitor + pkgs.slirp4netns # User-mode networking for unprivileged namespaces + pkgs.fuse-overlayfs # CoW for images, much faster than default vfs + pkgs.utillinux # nsenter + pkgs.iptables + ] + ++ lib.optional cfg.dockerCompat dockerCompat; + + environment.etc."containers/libpod.conf".text = '' + cni_plugin_dir = ["${pkgs.cni-plugins}/bin/"] + cni_config_dir = "/etc/cni/net.d/" + + '' + cfg.libpod.extraConfig; + + environment.etc."cni/net.d/87-podman-bridge.conflist".source = copyFile "${pkgs.podman.src}/cni/87-podman-bridge.conflist"; + + # Enable common /etc/containers configuration + virtualisation.containers.enable = true; + + }; + +} diff --git a/nixos/modules/virtualisation/virtualbox-image.nix b/nixos/modules/virtualisation/virtualbox-image.nix index ab65523592d7a..788b4d9d9761d 100644 --- a/nixos/modules/virtualisation/virtualbox-image.nix +++ b/nixos/modules/virtualisation/virtualbox-image.nix @@ -45,10 +45,41 @@ in { The file name of the VirtualBox appliance. ''; }; + params = mkOption { + type = with types; attrsOf (oneOf [ str int bool (listOf str) ]); + example = { + audio = "alsa"; + rtcuseutc = "on"; + usb = "off"; + }; + description = '' + Parameters passed to the Virtualbox appliance. + + Run <literal>VBoxManage modifyvm --help</literal> to see more options. + ''; + }; }; }; config = { + + virtualbox.params = mkMerge [ + (mapAttrs (name: mkDefault) { + acpi = "on"; + vram = 32; + nictype1 = "virtio"; + nic1 = "nat"; + audiocontroller = "ac97"; + audio = "alsa"; + audioout = "on"; + rtcuseutc = "on"; + usb = "on"; + usbehci = "on"; + mouse = "usbtablet"; + }) + (mkIf (pkgs.stdenv.hostPlatform.system == "i686-linux") { pae = "on"; }) + ]; + system.build.virtualBoxOVA = import ../../lib/make-disk-image.nix { name = cfg.vmDerivationName; @@ -69,12 +100,8 @@ in { VBoxManage createvm --name "$vmName" --register \ --ostype ${if pkgs.stdenv.hostPlatform.system == "x86_64-linux" then "Linux26_64" else "Linux26"} VBoxManage modifyvm "$vmName" \ - --memory ${toString cfg.memorySize} --acpi on --vram 32 \ - ${optionalString (pkgs.stdenv.hostPlatform.system == "i686-linux") "--pae on"} \ - --nictype1 virtio --nic1 nat \ - --audiocontroller ac97 --audio alsa --audioout on \ - --rtcuseutc on \ - --usb on --usbehci on --mouse usbtablet + --memory ${toString cfg.memorySize} \ + ${lib.cli.toGNUCommandLineShell { } cfg.params} VBoxManage storagectl "$vmName" --name SATA --add sata --portcount 4 --bootable on --hostiocache on VBoxManage storageattach "$vmName" --storagectl SATA --port 0 --device 0 --type hdd \ --medium disk.vmdk diff --git a/nixos/modules/virtualisation/vmware-guest.nix b/nixos/modules/virtualisation/vmware-guest.nix index f418f849759f3..962a9059ea476 100644 --- a/nixos/modules/virtualisation/vmware-guest.nix +++ b/nixos/modules/virtualisation/vmware-guest.nix @@ -8,6 +8,10 @@ let xf86inputvmmouse = pkgs.xorg.xf86inputvmmouse; in { + imports = [ + (mkRenamedOptionModule [ "services" "vmwareGuest" ] [ "virtualisation" "vmware" "guest" ]) + ]; + options.virtualisation.vmware.guest = { enable = mkEnableOption "VMWare Guest Support"; headless = mkOption { diff --git a/nixos/modules/virtualisation/xen-dom0.nix b/nixos/modules/virtualisation/xen-dom0.nix index 06d5c63476f93..7f0af9901b9b6 100644 --- a/nixos/modules/virtualisation/xen-dom0.nix +++ b/nixos/modules/virtualisation/xen-dom0.nix @@ -9,6 +9,11 @@ let in { + imports = [ + (mkRemovedOptionModule [ "virtualisation" "xen" "qemu" ] "You don't need this option anymore, it will work without it.") + (mkRenamedOptionModule [ "virtualisation" "xen" "qemu-package" ] [ "virtualisation" "xen" "package-qemu" ]) + ]; + ###### interface options = { @@ -228,26 +233,19 @@ in environment.etc = - [ { source = "${cfg.package}/etc/xen/xl.conf"; - target = "xen/xl.conf"; - } - { source = "${cfg.package}/etc/xen/scripts"; - target = "xen/scripts"; - } - { text = '' - source ${cfg.package}/etc/default/xendomains - - ${cfg.domains.extraConfig} - ''; - target = "default/xendomains"; - } - ] - ++ lib.optionals (builtins.compareVersions cfg.package.version "4.10" >= 0) [ + { + "xen/xl.conf".source = "${cfg.package}/etc/xen/xl.conf"; + "xen/scripts".source = "${cfg.package}/etc/xen/scripts"; + "default/xendomains".text = '' + source ${cfg.package}/etc/default/xendomains + + ${cfg.domains.extraConfig} + ''; + } + // optionalAttrs (builtins.compareVersions cfg.package.version "4.10" >= 0) { # in V 4.10 oxenstored requires /etc/xen/oxenstored.conf to start - { source = "${cfg.package}/etc/xen/oxenstored.conf"; - target = "xen/oxenstored.conf"; - } - ]; + "xen/oxenstored.conf".source = "${cfg.package}/etc/xen/oxenstored.conf"; + }; # Xen provides udev rules. services.udev.packages = [ cfg.package ]; diff --git a/nixos/release-combined.nix b/nixos/release-combined.nix index 678ce3c288003..2f5dd28e8bb5a 100644 --- a/nixos/release-combined.nix +++ b/nixos/release-combined.nix @@ -20,11 +20,6 @@ let else pkgs.lib.mapAttrs (n: v: removeMaintainers v) set else set; - allSupportedNixpkgs = builtins.removeAttrs (removeMaintainers (import ../pkgs/top-level/release.nix { - supportedSystems = supportedSystems ++ limitedSupportedSystems; - nixpkgs = nixpkgsSrc; - })) [ "unstable" ]; - in rec { nixos = removeMaintainers (import ./release.nix { @@ -38,109 +33,106 @@ in rec { nixpkgs = nixpkgsSrc; })) [ "unstable" ]; - tested = pkgs.lib.hydraJob (pkgs.releaseTools.aggregate { - name = "nixos-${nixos.channel.version}"; - meta = { - description = "Release-critical builds for the NixOS channel"; - maintainers = with pkgs.lib.maintainers; [ eelco fpletz ]; - }; - constituents = - let - # Except for the given systems, return the system-specific constituent - except = systems: x: map (system: x.${system}) (pkgs.lib.subtractLists systems supportedSystems); - all = x: except [] x; - in [ - nixos.channel - (all nixos.dummy) - (all nixos.manual) - - nixos.iso_graphical.x86_64-linux or [] - nixos.iso_minimal.aarch64-linux or [] - nixos.iso_minimal.i686-linux or [] - nixos.iso_minimal.x86_64-linux or [] - nixos.ova.x86_64-linux or [] - nixos.sd_image.aarch64-linux or [] - - #(all nixos.tests.containers) - (all nixos.tests.containers-imperative) - (all nixos.tests.containers-ip) - nixos.tests.chromium.x86_64-linux or [] - (all nixos.tests.firefox) - (all nixos.tests.firewall) - (all nixos.tests.fontconfig-default-fonts) - (all nixos.tests.gnome3-xorg) - (all nixos.tests.gnome3) - (all nixos.tests.pantheon) - nixos.tests.installer.zfsroot.x86_64-linux or [] # ZFS is 64bit only - (except ["aarch64-linux"] nixos.tests.installer.lvm) - (except ["aarch64-linux"] nixos.tests.installer.luksroot) - (except ["aarch64-linux"] nixos.tests.installer.separateBoot) - (except ["aarch64-linux"] nixos.tests.installer.separateBootFat) - (except ["aarch64-linux"] nixos.tests.installer.simple) - (except ["aarch64-linux"] nixos.tests.installer.simpleLabels) - (except ["aarch64-linux"] nixos.tests.installer.simpleProvided) - (except ["aarch64-linux"] nixos.tests.installer.simpleUefiSystemdBoot) - (except ["aarch64-linux"] nixos.tests.installer.swraid) - (except ["aarch64-linux"] nixos.tests.installer.btrfsSimple) - (except ["aarch64-linux"] nixos.tests.installer.btrfsSubvols) - (except ["aarch64-linux"] nixos.tests.installer.btrfsSubvolDefault) - (except ["aarch64-linux"] nixos.tests.boot.biosCdrom) - #(except ["aarch64-linux"] nixos.tests.boot.biosUsb) # disabled due to issue #15690 - (except ["aarch64-linux"] nixos.tests.boot.uefiCdrom) - (except ["aarch64-linux"] nixos.tests.boot.uefiUsb) - (all nixos.tests.boot-stage1) - (all nixos.tests.hibernate) - nixos.tests.docker.x86_64-linux or [] - (all nixos.tests.ecryptfs) - (all nixos.tests.env) - (all nixos.tests.ipv6) - (all nixos.tests.i3wm) - # 2018-06-06: keymap tests temporarily removed from tested job - # since non-deterministic failure are blocking the channel (#41538) - #(all nixos.tests.keymap.azerty) - #(all nixos.tests.keymap.colemak) - #(all nixos.tests.keymap.dvorak) - #(all nixos.tests.keymap.dvp) - #(all nixos.tests.keymap.neo) - #(all nixos.tests.keymap.qwertz) - (all nixos.tests.plasma5) - (all nixos.tests.lightdm) - (all nixos.tests.login) - (all nixos.tests.misc) - (all nixos.tests.mutableUsers) - (all nixos.tests.nat.firewall) - (all nixos.tests.nat.firewall-conntrack) - (all nixos.tests.nat.standalone) - (all nixos.tests.networking.scripted.loopback) - (all nixos.tests.networking.scripted.static) - (all nixos.tests.networking.scripted.dhcpSimple) - (all nixos.tests.networking.scripted.dhcpOneIf) - (all nixos.tests.networking.scripted.bond) - (all nixos.tests.networking.scripted.bridge) - (all nixos.tests.networking.scripted.macvlan) - (all nixos.tests.networking.scripted.sit) - (all nixos.tests.networking.scripted.vlan) - (all nixos.tests.nfs3) - (all nixos.tests.nfs4) - (all nixos.tests.openssh) - (all nixos.tests.php-pcre) - (all nixos.tests.predictable-interface-names.predictable) - (all nixos.tests.predictable-interface-names.unpredictable) - (all nixos.tests.predictable-interface-names.predictableNetworkd) - (all nixos.tests.predictable-interface-names.unpredictableNetworkd) - (all nixos.tests.printing) - (all nixos.tests.proxy) - (all nixos.tests.sddm.default) - (all nixos.tests.simple) - (all nixos.tests.switchTest) - (all nixos.tests.udisks2) - (all nixos.tests.xfce) - - nixpkgs.tarball - (all allSupportedNixpkgs.emacs) - # The currently available aarch64 JDK is unfree - (except ["aarch64-linux"] allSupportedNixpkgs.jdk) + tested = + let + onFullSupported = x: map (system: "${x}.${system}") supportedSystems; + onAllSupported = x: map (system: "${x}.${system}") (supportedSystems ++ limitedSupportedSystems); + onSystems = systems: x: map (system: "${x}.${system}") + (pkgs.lib.intersectLists systems (supportedSystems ++ limitedSupportedSystems)); + in pkgs.releaseTools.aggregate { + name = "nixos-${nixos.channel.version}"; + meta = { + description = "Release-critical builds for the NixOS channel"; + maintainers = with pkgs.lib.maintainers; [ eelco fpletz ]; + }; + constituents = pkgs.lib.concatLists [ + [ "nixos.channel" ] + (onFullSupported "nixos.dummy") + (onAllSupported "nixos.iso_minimal") + (onSystems ["x86_64-linux"] "nixos.iso_plasma5") + (onFullSupported "nixos.manual") + (onSystems ["x86_64-linux"] "nixos.ova") + (onSystems ["aarch64-linux"] "nixos.sd_image") + (onSystems ["x86_64-linux"] "nixos.tests.boot.biosCdrom") + (onSystems ["x86_64-linux"] "nixos.tests.boot.biosUsb") + (onFullSupported "nixos.tests.boot-stage1") + (onSystems ["x86_64-linux"] "nixos.tests.boot.uefiCdrom") + (onSystems ["x86_64-linux"] "nixos.tests.boot.uefiUsb") + (onSystems ["x86_64-linux"] "nixos.tests.chromium") + (onFullSupported "nixos.tests.containers-imperative") + (onFullSupported "nixos.tests.containers-ip") + (onSystems ["x86_64-linux"] "nixos.tests.docker") + (onFullSupported "nixos.tests.ecryptfs") + (onFullSupported "nixos.tests.env") + (onFullSupported "nixos.tests.firefox-esr") + (onFullSupported "nixos.tests.firefox") + (onFullSupported "nixos.tests.firewall") + (onFullSupported "nixos.tests.fontconfig-default-fonts") + (onFullSupported "nixos.tests.gnome3") + (onFullSupported "nixos.tests.gnome3-xorg") + (onFullSupported "nixos.tests.hardened") + (onSystems ["x86_64-linux"] "nixos.tests.hibernate") + (onFullSupported "nixos.tests.i3wm") + (onSystems ["x86_64-linux"] "nixos.tests.installer.btrfsSimple") + (onSystems ["x86_64-linux"] "nixos.tests.installer.btrfsSubvolDefault") + (onSystems ["x86_64-linux"] "nixos.tests.installer.btrfsSubvols") + (onSystems ["x86_64-linux"] "nixos.tests.installer.luksroot") + (onSystems ["x86_64-linux"] "nixos.tests.installer.lvm") + (onSystems ["x86_64-linux"] "nixos.tests.installer.separateBootFat") + (onSystems ["x86_64-linux"] "nixos.tests.installer.separateBoot") + (onSystems ["x86_64-linux"] "nixos.tests.installer.simpleLabels") + (onSystems ["x86_64-linux"] "nixos.tests.installer.simpleProvided") + (onSystems ["x86_64-linux"] "nixos.tests.installer.simpleUefiSystemdBoot") + (onSystems ["x86_64-linux"] "nixos.tests.installer.simple") + (onSystems ["x86_64-linux"] "nixos.tests.installer.swraid") + (onFullSupported "nixos.tests.ipv6") + (onFullSupported "nixos.tests.keymap.azerty") + (onFullSupported "nixos.tests.keymap.colemak") + (onFullSupported "nixos.tests.keymap.dvorak") + (onFullSupported "nixos.tests.keymap.dvp") + (onFullSupported "nixos.tests.keymap.neo") + (onFullSupported "nixos.tests.keymap.qwertz") + (onFullSupported "nixos.tests.latestKernel.hardened") + (onFullSupported "nixos.tests.latestKernel.login") + (onFullSupported "nixos.tests.lightdm") + (onFullSupported "nixos.tests.login") + (onFullSupported "nixos.tests.misc") + (onFullSupported "nixos.tests.mutableUsers") + (onFullSupported "nixos.tests.nat.firewall-conntrack") + (onFullSupported "nixos.tests.nat.firewall") + (onFullSupported "nixos.tests.nat.standalone") + (onFullSupported "nixos.tests.networking.scripted.bond") + (onFullSupported "nixos.tests.networking.scripted.bridge") + (onFullSupported "nixos.tests.networking.scripted.dhcpOneIf") + (onFullSupported "nixos.tests.networking.scripted.dhcpSimple") + (onFullSupported "nixos.tests.networking.scripted.loopback") + (onFullSupported "nixos.tests.networking.scripted.macvlan") + (onFullSupported "nixos.tests.networking.scripted.sit") + (onFullSupported "nixos.tests.networking.scripted.static") + (onFullSupported "nixos.tests.networking.scripted.vlan") + (onFullSupported "nixos.tests.nfs3.simple") + (onFullSupported "nixos.tests.nfs4.simple") + (onFullSupported "nixos.tests.openssh") + (onFullSupported "nixos.tests.pantheon") + (onFullSupported "nixos.tests.php.fpm") + (onFullSupported "nixos.tests.php.httpd") + (onFullSupported "nixos.tests.php.pcre") + (onFullSupported "nixos.tests.plasma5") + (onFullSupported "nixos.tests.predictable-interface-names.predictableNetworkd") + (onFullSupported "nixos.tests.predictable-interface-names.predictable") + (onFullSupported "nixos.tests.predictable-interface-names.unpredictableNetworkd") + (onFullSupported "nixos.tests.predictable-interface-names.unpredictable") + (onFullSupported "nixos.tests.printing") + (onFullSupported "nixos.tests.proxy") + (onFullSupported "nixos.tests.sddm.default") + (onFullSupported "nixos.tests.simple") + (onFullSupported "nixos.tests.switchTest") + (onFullSupported "nixos.tests.udisks2") + (onFullSupported "nixos.tests.xfce") + (onSystems ["i686-linux"] "nixos.tests.zfs.installer") + (onFullSupported "nixpkgs.emacs") + (onFullSupported "nixpkgs.jdk") + ["nixpkgs.tarball"] ]; - }); - + }; } diff --git a/nixos/release-small.nix b/nixos/release-small.nix index 74c16e990f358..6da2c59cedd49 100644 --- a/nixos/release-small.nix +++ b/nixos/release-small.nix @@ -28,7 +28,7 @@ let in rec { nixos = { - inherit (nixos') channel manual iso_minimal dummy; + inherit (nixos') channel manual options iso_minimal dummy; tests = { inherit (nixos'.tests) containers-imperative @@ -40,7 +40,7 @@ in rec { nat nfs3 openssh - php-pcre + php predictable-interface-names proxy simple; @@ -82,18 +82,43 @@ in rec { vim; }; - tested = lib.hydraJob (pkgs.releaseTools.aggregate { + tested = pkgs.releaseTools.aggregate { name = "nixos-${nixos.channel.version}"; meta = { description = "Release-critical builds for the NixOS channel"; maintainers = [ lib.maintainers.eelco ]; }; constituents = - let all = x: map (system: x.${system}) supportedSystems; in - [ nixpkgs.tarball - (all nixpkgs.jdk) - ] - ++ lib.collect lib.isDerivation nixos; - }); + [ "nixos.channel" + "nixos.dummy.x86_64-linux" + "nixos.iso_minimal.x86_64-linux" + "nixos.manual.x86_64-linux" + "nixos.tests.boot.biosCdrom.x86_64-linux" + "nixos.tests.containers-imperative.x86_64-linux" + "nixos.tests.containers-ip.x86_64-linux" + "nixos.tests.firewall.x86_64-linux" + "nixos.tests.installer.lvm.x86_64-linux" + "nixos.tests.installer.separateBoot.x86_64-linux" + "nixos.tests.installer.simple.x86_64-linux" + "nixos.tests.ipv6.x86_64-linux" + "nixos.tests.login.x86_64-linux" + "nixos.tests.misc.x86_64-linux" + "nixos.tests.nat.firewall-conntrack.x86_64-linux" + "nixos.tests.nat.firewall.x86_64-linux" + "nixos.tests.nat.standalone.x86_64-linux" + "nixos.tests.nfs3.simple.x86_64-linux" + "nixos.tests.openssh.x86_64-linux" + "nixos.tests.php.fpm.x86_64-linux" + "nixos.tests.php.pcre.x86_64-linux" + "nixos.tests.predictable-interface-names.predictable.x86_64-linux" + "nixos.tests.predictable-interface-names.predictableNetworkd.x86_64-linux" + "nixos.tests.predictable-interface-names.unpredictable.x86_64-linux" + "nixos.tests.predictable-interface-names.unpredictableNetworkd.x86_64-linux" + "nixos.tests.proxy.x86_64-linux" + "nixos.tests.simple.x86_64-linux" + "nixpkgs.jdk.x86_64-linux" + "nixpkgs.tarball" + ]; + }; } diff --git a/nixos/release.nix b/nixos/release.nix index f40b5fa9bd7f7..6107f35297159 100644 --- a/nixos/release.nix +++ b/nixos/release.nix @@ -20,7 +20,7 @@ let allTestsForSystem = system: import ./tests/all-tests.nix { inherit system; - pkgs = import nixpkgs { inherit system; }; + pkgs = import ./.. { inherit system; }; callTest = t: { ${system} = hydraJob t.test; }; @@ -28,7 +28,7 @@ let allTests = foldAttrs recursiveUpdate {} (map allTestsForSystem supportedSystems); - pkgs = import nixpkgs { system = "x86_64-linux"; }; + pkgs = import ./.. { system = "x86_64-linux"; }; versionModule = @@ -41,7 +41,7 @@ let makeIso = { module, type, system, ... }: - with import nixpkgs { inherit system; }; + with import ./.. { inherit system; }; hydraJob ((import lib/eval-config.nix { inherit system; @@ -54,7 +54,7 @@ let makeSdImage = { module, system, ... }: - with import nixpkgs { inherit system; }; + with import ./.. { inherit system; }; hydraJob ((import lib/eval-config.nix { inherit system; @@ -65,7 +65,7 @@ let makeSystemTarball = { module, maintainers ? ["viric"], system }: - with import nixpkgs { inherit system; }; + with import ./.. { inherit system; }; let @@ -149,9 +149,9 @@ in rec { inherit system; }); - iso_graphical = forMatchingSystems [ "x86_64-linux" ] (system: makeIso { - module = ./modules/installer/cd-dvd/installation-cd-graphical-kde.nix; - type = "graphical"; + iso_plasma5 = forMatchingSystems [ "x86_64-linux" ] (system: makeIso { + module = ./modules/installer/cd-dvd/installation-cd-graphical-plasma5.nix; + type = "plasma5"; inherit system; }); @@ -188,7 +188,7 @@ in rec { # A bootable VirtualBox virtual appliance as an OVA file (i.e. packaged OVF). ova = forMatchingSystems [ "x86_64-linux" ] (system: - with import nixpkgs { inherit system; }; + with import ./.. { inherit system; }; hydraJob ((import lib/eval-config.nix { inherit system; @@ -204,12 +204,13 @@ in rec { # A disk image that can be imported to Amazon EC2 and registered as an AMI amazonImage = forMatchingSystems [ "x86_64-linux" "aarch64-linux" ] (system: - with import nixpkgs { inherit system; }; + with import ./.. { inherit system; }; hydraJob ((import lib/eval-config.nix { inherit system; modules = - [ versionModule + [ configuration + versionModule ./maintainers/scripts/ec2/amazon-image.nix ]; }).config.system.build.amazonImage) diff --git a/nixos/tests/3proxy.nix b/nixos/tests/3proxy.nix new file mode 100644 index 0000000000000..3e2061d7e42fd --- /dev/null +++ b/nixos/tests/3proxy.nix @@ -0,0 +1,185 @@ +import ./make-test-python.nix ({ pkgs, ...} : { + name = "3proxy"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ misuzu ]; + }; + + nodes = { + peer0 = { lib, ... }: { + networking.useDHCP = false; + networking.interfaces.eth1 = { + ipv4.addresses = [ + { + address = "192.168.0.1"; + prefixLength = 24; + } + { + address = "216.58.211.111"; + prefixLength = 24; + } + ]; + }; + }; + + peer1 = { lib, ... }: { + networking.useDHCP = false; + networking.interfaces.eth1 = { + ipv4.addresses = [ + { + address = "192.168.0.2"; + prefixLength = 24; + } + { + address = "216.58.211.112"; + prefixLength = 24; + } + ]; + }; + # test that binding to [::] is working when ipv6 is disabled + networking.enableIPv6 = false; + services._3proxy = { + enable = true; + services = [ + { + type = "admin"; + bindPort = 9999; + auth = [ "none" ]; + } + { + type = "proxy"; + bindPort = 3128; + auth = [ "none" ]; + } + ]; + }; + networking.firewall.allowedTCPPorts = [ 3128 9999 ]; + }; + + peer2 = { lib, ... }: { + networking.useDHCP = false; + networking.interfaces.eth1 = { + ipv4.addresses = [ + { + address = "192.168.0.3"; + prefixLength = 24; + } + { + address = "216.58.211.113"; + prefixLength = 24; + } + ]; + }; + services._3proxy = { + enable = true; + services = [ + { + type = "admin"; + bindPort = 9999; + auth = [ "none" ]; + } + { + type = "proxy"; + bindPort = 3128; + auth = [ "iponly" ]; + acl = [ + { + rule = "allow"; + } + ]; + } + ]; + }; + networking.firewall.allowedTCPPorts = [ 3128 9999 ]; + }; + + peer3 = { lib, ... }: { + networking.useDHCP = false; + networking.interfaces.eth1 = { + ipv4.addresses = [ + { + address = "192.168.0.4"; + prefixLength = 24; + } + { + address = "216.58.211.114"; + prefixLength = 24; + } + ]; + }; + services._3proxy = { + enable = true; + usersFile = pkgs.writeText "3proxy.passwd" '' + admin:CR:$1$.GUV4Wvk$WnEVQtaqutD9.beO5ar1W/ + ''; + services = [ + { + type = "admin"; + bindPort = 9999; + auth = [ "none" ]; + } + { + type = "proxy"; + bindPort = 3128; + auth = [ "strong" ]; + acl = [ + { + rule = "allow"; + } + ]; + } + ]; + }; + networking.firewall.allowedTCPPorts = [ 3128 9999 ]; + }; + }; + + testScript = '' + peer1.wait_for_unit("3proxy.service") + peer1.wait_for_open_port("9999") + + # test none auth + peer0.succeed( + "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.2:3128 -S -O /dev/null http://216.58.211.112:9999" + ) + peer0.succeed( + "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.2:3128 -S -O /dev/null http://192.168.0.2:9999" + ) + peer0.succeed( + "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.2:3128 -S -O /dev/null http://127.0.0.1:9999" + ) + + peer2.wait_for_unit("3proxy.service") + peer2.wait_for_open_port("9999") + + # test iponly auth + peer0.succeed( + "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.3:3128 -S -O /dev/null http://216.58.211.113:9999" + ) + peer0.fail( + "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.3:3128 -S -O /dev/null http://192.168.0.3:9999" + ) + peer0.fail( + "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.3:3128 -S -O /dev/null http://127.0.0.1:9999" + ) + + peer3.wait_for_unit("3proxy.service") + peer3.wait_for_open_port("9999") + + # test strong auth + peer0.succeed( + "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://admin:bigsecret\@192.168.0.4:3128 -S -O /dev/null http://216.58.211.114:9999" + ) + peer0.fail( + "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://admin:bigsecret\@192.168.0.4:3128 -S -O /dev/null http://192.168.0.4:9999" + ) + peer0.fail( + "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.4:3128 -S -O /dev/null http://216.58.211.114:9999" + ) + peer0.fail( + "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.4:3128 -S -O /dev/null http://192.168.0.4:9999" + ) + peer0.fail( + "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.4:3128 -S -O /dev/null http://127.0.0.1:9999" + ) + ''; +}) diff --git a/nixos/tests/acme.nix b/nixos/tests/acme.nix index 6bd315ff1eaa4..693f02962f452 100644 --- a/nixos/tests/acme.nix +++ b/nixos/tests/acme.nix @@ -1,54 +1,82 @@ let - commonConfig = ./common/letsencrypt/common.nix; + commonConfig = ./common/acme/client; + + dnsScript = {writeScript, dnsAddress, bash, curl}: writeScript "dns-hook.sh" '' + #!${bash}/bin/bash + set -euo pipefail + echo '[INFO]' "[$2]" 'dns-hook.sh' $* + if [ "$1" = "present" ]; then + ${curl}/bin/curl --data '{"host": "'"$2"'", "value": "'"$3"'"}' http://${dnsAddress}:8055/set-txt + else + ${curl}/bin/curl --data '{"host": "'"$2"'"}' http://${dnsAddress}:8055/clear-txt + fi + ''; + in import ./make-test-python.nix { name = "acme"; nodes = rec { - letsencrypt = ./common/letsencrypt; + acme = { nodes, lib, ... }: { + imports = [ ./common/acme/server ]; + networking.nameservers = lib.mkForce [ + nodes.dnsserver.config.networking.primaryIPAddress + ]; + }; + + dnsserver = { nodes, pkgs, ... }: { + networking.firewall.allowedTCPPorts = [ 8055 53 ]; + networking.firewall.allowedUDPPorts = [ 53 ]; + systemd.services.pebble-challtestsrv = { + enable = true; + description = "Pebble ACME challenge test server"; + wantedBy = [ "network.target" ]; + serviceConfig = { + ExecStart = "${pkgs.pebble}/bin/pebble-challtestsrv -dns01 ':53' -defaultIPv6 '' -defaultIPv4 '${nodes.webserver.config.networking.primaryIPAddress}'"; + # Required to bind on privileged ports. + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; + }; + }; + }; - acmeStandalone = { config, pkgs, ... }: { + acmeStandalone = { nodes, lib, config, pkgs, ... }: { imports = [ commonConfig ]; + networking.nameservers = lib.mkForce [ + nodes.dnsserver.config.networking.primaryIPAddress + ]; networking.firewall.allowedTCPPorts = [ 80 ]; - networking.extraHosts = '' - ${config.networking.primaryIPAddress} standalone.com - ''; - security.acme = { - server = "https://acme-v02.api.letsencrypt.org/dir"; - certs."standalone.com" = { - webroot = "/var/lib/acme/acme-challenges"; - }; + security.acme.certs."standalone.test" = { + webroot = "/var/lib/acme/acme-challenges"; }; - systemd.targets."acme-finished-standalone.com" = {}; - systemd.services."acme-standalone.com" = { - wants = [ "acme-finished-standalone.com.target" ]; - before = [ "acme-finished-standalone.com.target" ]; + systemd.targets."acme-finished-standalone.test" = {}; + systemd.services."acme-standalone.test" = { + wants = [ "acme-finished-standalone.test.target" ]; + before = [ "acme-finished-standalone.test.target" ]; }; services.nginx.enable = true; - services.nginx.virtualHosts."standalone.com" = { + services.nginx.virtualHosts."standalone.test" = { locations."/.well-known/acme-challenge".root = "/var/lib/acme/acme-challenges"; }; }; - webserver = { config, pkgs, ... }: { + webserver = { nodes, config, pkgs, lib, ... }: { imports = [ commonConfig ]; networking.firewall.allowedTCPPorts = [ 80 443 ]; - - networking.extraHosts = '' - ${config.networking.primaryIPAddress} a.example.com - ${config.networking.primaryIPAddress} b.example.com - ''; + networking.nameservers = lib.mkForce [ + nodes.dnsserver.config.networking.primaryIPAddress + ]; # A target remains active. Use this to probe the fact that # a service fired eventhough it is not RemainAfterExit - systemd.targets."acme-finished-a.example.com" = {}; - systemd.services."acme-a.example.com" = { - wants = [ "acme-finished-a.example.com.target" ]; - before = [ "acme-finished-a.example.com.target" ]; + systemd.targets."acme-finished-a.example.test" = {}; + systemd.services."acme-a.example.test" = { + wants = [ "acme-finished-a.example.test.target" ]; + before = [ "acme-finished-a.example.test.target" ]; + after = [ "nginx.service" ]; }; services.nginx.enable = true; - services.nginx.virtualHosts."a.example.com" = { + services.nginx.virtualHosts."a.example.test" = { enableACME = true; forceSSL = true; locations."/".root = pkgs.runCommand "docroot" {} '' @@ -57,37 +85,65 @@ in import ./make-test-python.nix { ''; }; - security.acme.server = "https://acme-v02.api.letsencrypt.org/dir"; - - nesting.clone = [ - ({pkgs, ...}: { + security.acme.server = "https://acme.test/dir"; - networking.extraHosts = '' - ${config.networking.primaryIPAddress} b.example.com + specialisation.second-cert.configuration = {pkgs, ...}: { + systemd.targets."acme-finished-b.example.test" = {}; + systemd.services."acme-b.example.test" = { + wants = [ "acme-finished-b.example.test.target" ]; + before = [ "acme-finished-b.example.test.target" ]; + after = [ "nginx.service" ]; + }; + services.nginx.virtualHosts."b.example.test" = { + enableACME = true; + forceSSL = true; + locations."/".root = pkgs.runCommand "docroot" {} '' + mkdir -p "$out" + echo hello world > "$out/index.html" ''; - systemd.targets."acme-finished-b.example.com" = {}; - systemd.services."acme-b.example.com" = { - wants = [ "acme-finished-b.example.com.target" ]; - before = [ "acme-finished-b.example.com.target" ]; - }; - services.nginx.virtualHosts."b.example.com" = { - enableACME = true; - forceSSL = true; - locations."/".root = pkgs.runCommand "docroot" {} '' - mkdir -p "$out" - echo hello world > "$out/index.html" - ''; - }; - }) - ]; + }; + }; + specialisation.dns-01.configuration = {pkgs, config, nodes, lib, ...}: { + security.acme.certs."example.test" = { + domain = "*.example.test"; + dnsProvider = "exec"; + dnsPropagationCheck = false; + credentialsFile = with pkgs; writeText "wildcard.env" '' + EXEC_PATH=${dnsScript { inherit writeScript bash curl; dnsAddress = nodes.dnsserver.config.networking.primaryIPAddress; }} + ''; + user = config.services.nginx.user; + group = config.services.nginx.group; + }; + systemd.targets."acme-finished-example.test" = {}; + systemd.services."acme-example.test" = { + wants = [ "acme-finished-example.test.target" ]; + before = [ "acme-finished-example.test.target" "nginx.service" ]; + wantedBy = [ "nginx.service" ]; + }; + services.nginx.virtualHosts."c.example.test" = { + forceSSL = true; + sslCertificate = config.security.acme.certs."example.test".directory + "/cert.pem"; + sslTrustedCertificate = config.security.acme.certs."example.test".directory + "/full.pem"; + sslCertificateKey = config.security.acme.certs."example.test".directory + "/key.pem"; + locations."/".root = pkgs.runCommand "docroot" {} '' + mkdir -p "$out" + echo hello world > "$out/index.html" + ''; + }; + }; }; - client = commonConfig; + client = {nodes, lib, ...}: { + imports = [ commonConfig ]; + networking.nameservers = lib.mkForce [ + nodes.dnsserver.config.networking.primaryIPAddress + ]; + }; }; testScript = {nodes, ...}: let - newServerSystem = nodes.webserver2.config.system.build.toplevel; + newServerSystem = nodes.webserver.config.system.build.toplevel; switchToNewServer = "${newServerSystem}/bin/switch-to-configuration test"; in # Note, wait_for_unit does not work for oneshot services that do not have RemainAfterExit=true, @@ -97,37 +153,58 @@ in import ./make-test-python.nix { # can use them to probe that a oneshot fired. It is a bit ugly, but it is the best we can do '' client.start() - letsencrypt.start() + dnsserver.start() + + acme.wait_for_unit("default.target") + dnsserver.wait_for_unit("pebble-challtestsrv.service") + client.succeed( + 'curl --data \'{"host": "acme.test", "addresses": ["${nodes.acme.config.networking.primaryIPAddress}"]}\' http://${nodes.dnsserver.config.networking.primaryIPAddress}:8055/add-a' + ) + client.succeed( + 'curl --data \'{"host": "standalone.test", "addresses": ["${nodes.acmeStandalone.config.networking.primaryIPAddress}"]}\' http://${nodes.dnsserver.config.networking.primaryIPAddress}:8055/add-a' + ) + + acme.start() acmeStandalone.start() - letsencrypt.wait_for_unit("default.target") - letsencrypt.wait_for_unit("pebble.service") + acme.wait_for_unit("default.target") + acme.wait_for_unit("pebble.service") with subtest("can request certificate with HTTPS-01 challenge"): acmeStandalone.wait_for_unit("default.target") - acmeStandalone.succeed("systemctl start acme-standalone.com.service") - acmeStandalone.wait_for_unit("acme-finished-standalone.com.target") + acmeStandalone.succeed("systemctl start acme-standalone.test.service") + acmeStandalone.wait_for_unit("acme-finished-standalone.test.target") client.wait_for_unit("default.target") - client.succeed("curl https://acme-v02.api.letsencrypt.org:15000/roots/0 > /tmp/ca.crt") - client.succeed( - "curl https://acme-v02.api.letsencrypt.org:15000/intermediate-keys/0 >> /tmp/ca.crt" - ) + client.succeed("curl https://acme.test:15000/roots/0 > /tmp/ca.crt") + client.succeed("curl https://acme.test:15000/intermediate-keys/0 >> /tmp/ca.crt") with subtest("Can request certificate for nginx service"): - webserver.wait_for_unit("acme-finished-a.example.com.target") + webserver.wait_for_unit("acme-finished-a.example.test.target") client.succeed( - "curl --cacert /tmp/ca.crt https://a.example.com/ | grep -qF 'hello world'" + "curl --cacert /tmp/ca.crt https://a.example.test/ | grep -qF 'hello world'" ) with subtest("Can add another certificate for nginx service"): webserver.succeed( - "/run/current-system/fine-tune/child-1/bin/switch-to-configuration test" + "/run/current-system/specialisation/second-cert/bin/switch-to-configuration test" + ) + webserver.wait_for_unit("acme-finished-b.example.test.target") + client.succeed( + "curl --cacert /tmp/ca.crt https://b.example.test/ | grep -qF 'hello world'" + ) + + with subtest("Can request wildcard certificates using DNS-01 challenge"): + webserver.succeed( + "${switchToNewServer}" + ) + webserver.succeed( + "/run/current-system/specialisation/dns-01/bin/switch-to-configuration test" ) - webserver.wait_for_unit("acme-finished-b.example.com.target") + webserver.wait_for_unit("acme-finished-example.test.target") client.succeed( - "curl --cacert /tmp/ca.crt https://b.example.com/ | grep -qF 'hello world'" + "curl --cacert /tmp/ca.crt https://c.example.test/ | grep -qF 'hello world'" ) ''; } diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index df65ef249e8a7..38e8980b7482d 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -21,7 +21,8 @@ let else {}; in { - acme = handleTestOn ["x86_64-linux"] ./acme.nix {}; + _3proxy = handleTest ./3proxy.nix {}; + acme = handleTest ./acme.nix {}; atd = handleTest ./atd.nix {}; automysqlbackup = handleTest ./automysqlbackup.nix {}; avahi = handleTest ./avahi.nix {}; @@ -31,13 +32,14 @@ in bees = handleTest ./bees.nix {}; bind = handleTest ./bind.nix {}; bittorrent = handleTest ./bittorrent.nix {}; - #blivet = handleTest ./blivet.nix {}; # broken since 2017-07024 + buildkite-agents = handleTest ./buildkite-agents.nix {}; boot = handleTestOn ["x86_64-linux"] ./boot.nix {}; # syslinux is unsupported on aarch64 boot-stage1 = handleTest ./boot-stage1.nix {}; borgbackup = handleTest ./borgbackup.nix {}; buildbot = handleTest ./buildbot.nix {}; caddy = handleTest ./caddy.nix {}; cadvisor = handleTestOn ["x86_64-linux"] ./cadvisor.nix {}; + cage = handleTest ./cage.nix {}; cassandra = handleTest ./cassandra.nix {}; ceph-single-node = handleTestOn ["x86_64-linux"] ./ceph-single-node.nix {}; ceph-multi-node = handleTestOn ["x86_64-linux"] ./ceph-multi-node.nix {}; @@ -48,7 +50,10 @@ in clickhouse = handleTest ./clickhouse.nix {}; cloud-init = handleTest ./cloud-init.nix {}; codimd = handleTest ./codimd.nix {}; + consul = handleTest ./consul.nix {}; + cockroachdb = handleTestOn ["x86_64-linux"] ./cockroachdb.nix {}; containers-bridge = handleTest ./containers-bridge.nix {}; + containers-custom-pkgs.nix = handleTest ./containers-custom-pkgs.nix {}; containers-ephemeral = handleTest ./containers-ephemeral.nix {}; containers-extra_veth = handleTest ./containers-extra_veth.nix {}; containers-hosts = handleTest ./containers-hosts.nix {}; @@ -56,12 +61,14 @@ in containers-ip = handleTest ./containers-ip.nix {}; containers-macvlans = handleTest ./containers-macvlans.nix {}; containers-physical_interfaces = handleTest ./containers-physical_interfaces.nix {}; + containers-portforward = handleTest ./containers-portforward.nix {}; containers-restart_networking = handleTest ./containers-restart_networking.nix {}; containers-tmpfs = handleTest ./containers-tmpfs.nix {}; + corerad = handleTest ./corerad.nix {}; couchdb = handleTest ./couchdb.nix {}; deluge = handleTest ./deluge.nix {}; dhparams = handleTest ./dhparams.nix {}; - dnscrypt-proxy = handleTestOn ["x86_64-linux"] ./dnscrypt-proxy.nix {}; + dnscrypt-proxy2 = handleTestOn ["x86_64-linux"] ./dnscrypt-proxy2.nix {}; docker = handleTestOn ["x86_64-linux"] ./docker.nix {}; docker-containers = handleTestOn ["x86_64-linux"] ./docker-containers.nix {}; docker-edge = handleTestOn ["x86_64-linux"] ./docker-edge.nix {}; @@ -70,6 +77,7 @@ in docker-tools = handleTestOn ["x86_64-linux"] ./docker-tools.nix {}; docker-tools-overlay = handleTestOn ["x86_64-linux"] ./docker-tools-overlay.nix {}; documize = handleTest ./documize.nix {}; + dokuwiki = handleTest ./dokuwiki.nix {}; dovecot = handleTest ./dovecot.nix {}; # ec2-config doesn't work in a sandbox as the simulated ec2 instance needs network access #ec2-config = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-config or {}; @@ -83,16 +91,22 @@ in fancontrol = handleTest ./fancontrol.nix {}; ferm = handleTest ./ferm.nix {}; firefox = handleTest ./firefox.nix {}; + firefox-esr = handleTest ./firefox.nix { esr = true; }; firewall = handleTest ./firewall.nix {}; fish = handleTest ./fish.nix {}; flannel = handleTestOn ["x86_64-linux"] ./flannel.nix {}; fluentd = handleTest ./fluentd.nix {}; fontconfig-default-fonts = handleTest ./fontconfig-default-fonts.nix {}; + freeswitch = handleTest ./freeswitch.nix {}; fsck = handleTest ./fsck.nix {}; + gerrit = handleTest ./gerrit.nix {}; gotify-server = handleTest ./gotify-server.nix {}; + grocy = handleTest ./grocy.nix {}; + gitdaemon = handleTest ./gitdaemon.nix {}; gitea = handleTest ./gitea.nix {}; gitlab = handleTest ./gitlab.nix {}; gitolite = handleTest ./gitolite.nix {}; + gitolite-fcgiwrap = handleTest ./gitolite-fcgiwrap.nix {}; glusterfs = handleTest ./glusterfs.nix {}; gnome3-xorg = handleTest ./gnome3-xorg.nix {}; gnome3 = handleTest ./gnome3.nix {}; @@ -103,40 +117,52 @@ in grafana = handleTest ./grafana.nix {}; graphite = handleTest ./graphite.nix {}; graylog = handleTest ./graylog.nix {}; + gvisor = handleTest ./gvisor.nix {}; hadoop.hdfs = handleTestOn [ "x86_64-linux" ] ./hadoop/hdfs.nix {}; hadoop.yarn = handleTestOn [ "x86_64-linux" ] ./hadoop/yarn.nix {}; handbrake = handleTestOn ["x86_64-linux"] ./handbrake.nix {}; haproxy = handleTest ./haproxy.nix {}; hardened = handleTest ./hardened.nix {}; - hibernate = handleTest ./hibernate.nix {}; + # 9pnet_virtio used to mount /nix partition doesn't support + # hibernation. This test happens to work on x86_64-linux but + # not on other platforms. + hibernate = handleTestOn ["x86_64-linux"] ./hibernate.nix {}; hitch = handleTest ./hitch {}; hocker-fetchdocker = handleTest ./hocker-fetchdocker {}; home-assistant = handleTest ./home-assistant.nix {}; hound = handleTest ./hound.nix {}; hydra = handleTest ./hydra {}; + hydra-db-migration = handleTest ./hydra/db-migration.nix {}; i3wm = handleTest ./i3wm.nix {}; icingaweb2 = handleTest ./icingaweb2.nix {}; iftop = handleTest ./iftop.nix {}; + ihatemoney = handleTest ./ihatemoney.nix {}; incron = handleTest ./incron.nix {}; influxdb = handleTest ./influxdb.nix {}; initrd-network-ssh = handleTest ./initrd-network-ssh {}; initrdNetwork = handleTest ./initrd-network.nix {}; installer = handleTest ./installer.nix {}; + iodine = handleTest ./iodine.nix {}; ipv6 = handleTest ./ipv6.nix {}; jackett = handleTest ./jackett.nix {}; jellyfin = handleTest ./jellyfin.nix {}; jenkins = handleTest ./jenkins.nix {}; + jirafeau = handleTest ./jirafeau.nix {}; + k3s = handleTest ./k3s.nix {}; kafka = handleTest ./kafka.nix {}; + keepalived = handleTest ./keepalived.nix {}; kerberos = handleTest ./kerberos/default.nix {}; kernel-latest = handleTest ./kernel-latest.nix {}; kernel-lts = handleTest ./kernel-lts.nix {}; kernel-testing = handleTest ./kernel-testing.nix {}; keymap = handleTest ./keymap.nix {}; knot = handleTest ./knot.nix {}; + krb5 = discoverTests (import ./krb5 {}); kubernetes.dns = handleTestOn ["x86_64-linux"] ./kubernetes/dns.nix {}; # kubernetes.e2e should eventually replace kubernetes.rbac when it works #kubernetes.e2e = handleTestOn ["x86_64-linux"] ./kubernetes/e2e.nix {}; kubernetes.rbac = handleTestOn ["x86_64-linux"] ./kubernetes/rbac.nix {}; + latestKernel.hardened = handleTest ./hardened.nix { latestKernel = true; }; latestKernel.login = handleTest ./login.nix { latestKernel = true; }; ldap = handleTest ./ldap.nix {}; leaps = handleTest ./leaps.nix {}; @@ -148,6 +174,7 @@ in #logstash = handleTest ./logstash.nix {}; lorri = handleTest ./lorri/default.nix {}; magnetico = handleTest ./magnetico.nix {}; + magic-wormhole-mailbox-server = handleTest ./magic-wormhole-mailbox-server.nix {}; mailcatcher = handleTest ./mailcatcher.nix {}; mathics = handleTest ./mathics.nix {}; matomo = handleTest ./matomo.nix {}; @@ -173,12 +200,13 @@ in mysql = handleTest ./mysql.nix {}; mysqlBackup = handleTest ./mysql-backup.nix {}; mysqlReplication = handleTest ./mysql-replication.nix {}; + nagios = handleTest ./nagios.nix {}; nat.firewall = handleTest ./nat.nix { withFirewall = true; }; nat.firewall-conntrack = handleTest ./nat.nix { withFirewall = true; withConntrackHelpers = true; }; nat.standalone = handleTest ./nat.nix { withFirewall = false; }; ndppd = handleTest ./ndppd.nix {}; neo4j = handleTest ./neo4j.nix {}; - nesting = handleTest ./nesting.nix {}; + specialisation = handleTest ./specialisation.nix {}; netdata = handleTest ./netdata.nix {}; networking.networkd = handleTest ./networking.nix { networkd = true; }; networking.scripted = handleTest ./networking.nix { networkd = false; }; @@ -186,10 +214,13 @@ in networkingProxy = handleTest ./networking-proxy.nix {}; nextcloud = handleTest ./nextcloud {}; nexus = handleTest ./nexus.nix {}; - nfs3 = handleTest ./nfs.nix { version = 3; }; - nfs4 = handleTest ./nfs.nix { version = 4; }; + # TODO: Test nfsv3 + Kerberos + nfs3 = handleTest ./nfs { version = 3; }; + nfs4 = handleTest ./nfs { version = 4; }; nghttpx = handleTest ./nghttpx.nix {}; nginx = handleTest ./nginx.nix {}; + nginx-etag = handleTest ./nginx-etag.nix {}; + nginx-pubhtml = handleTest ./nginx-pubhtml.nix {}; nginx-sso = handleTest ./nginx-sso.nix {}; nix-ssh-serve = handleTest ./nix-ssh-serve.nix {}; nixos-generate-config = handleTest ./nixos-generate-config.nix {}; @@ -200,8 +231,7 @@ in openldap = handleTest ./openldap.nix {}; opensmtpd = handleTest ./opensmtpd.nix {}; openssh = handleTest ./openssh.nix {}; - # openstack-image-userdata doesn't work in a sandbox as the simulated openstack instance needs network access - #openstack-image-userdata = (handleTestOn ["x86_64-linux"] ./openstack-image.nix {}).userdata or {}; + openstack-image-userdata = (handleTestOn ["x86_64-linux"] ./openstack-image.nix {}).userdata or {}; openstack-image-metadata = (handleTestOn ["x86_64-linux"] ./openstack-image.nix {}).metadata or {}; orangefs = handleTest ./orangefs.nix {}; os-prober = handleTestOn ["x86_64-linux"] ./os-prober.nix {}; @@ -215,9 +245,10 @@ in peerflix = handleTest ./peerflix.nix {}; pgjwt = handleTest ./pgjwt.nix {}; pgmanage = handleTest ./pgmanage.nix {}; - php-pcre = handleTest ./php-pcre.nix {}; + php = handleTest ./php {}; plasma5 = handleTest ./plasma5.nix {}; plotinus = handleTest ./plotinus.nix {}; + podman = handleTest ./podman.nix {}; postgis = handleTest ./postgis.nix {}; postgresql = handleTest ./postgresql.nix {}; postgresql-wal-receiver = handleTest ./postgresql-wal-receiver.nix {}; @@ -231,11 +262,13 @@ in prosodyMysql = handleTest ./xmpp/prosody-mysql.nix {}; proxy = handleTest ./proxy.nix {}; quagga = handleTest ./quagga.nix {}; + quorum = handleTest ./quorum.nix {}; rabbitmq = handleTest ./rabbitmq.nix {}; radarr = handleTest ./radarr.nix {}; radicale = handleTest ./radicale.nix {}; redis = handleTest ./redis.nix {}; redmine = handleTest ./redmine.nix {}; + restic = handleTest ./restic.nix {}; roundcube = handleTest ./roundcube.nix {}; rspamd = handleTest ./rspamd.nix {}; rss2email = handleTest ./rss2email.nix {}; @@ -243,7 +276,9 @@ in runInMachine = handleTest ./run-in-machine.nix {}; rxe = handleTest ./rxe.nix {}; samba = handleTest ./samba.nix {}; + sanoid = handleTest ./sanoid.nix {}; sddm = handleTest ./sddm.nix {}; + service-runner = handleTest ./service-runner.nix {}; shiori = handleTest ./shiori.nix {}; signal-desktop = handleTest ./signal-desktop.nix {}; simple = handleTest ./simple.nix {}; @@ -251,36 +286,50 @@ in smokeping = handleTest ./smokeping.nix {}; snapper = handleTest ./snapper.nix {}; solr = handleTest ./solr.nix {}; + spacecookie = handleTest ./spacecookie.nix {}; + spike = handleTest ./spike.nix {}; sonarr = handleTest ./sonarr.nix {}; strongswan-swanctl = handleTest ./strongswan-swanctl.nix {}; sudo = handleTest ./sudo.nix {}; switchTest = handleTest ./switch-test.nix {}; + sympa = handleTest ./sympa.nix {}; syncthing-init = handleTest ./syncthing-init.nix {}; syncthing-relay = handleTest ./syncthing-relay.nix {}; systemd = handleTest ./systemd.nix {}; + systemd-analyze = handleTest ./systemd-analyze.nix {}; systemd-confinement = handleTest ./systemd-confinement.nix {}; systemd-timesyncd = handleTest ./systemd-timesyncd.nix {}; - systemd-networkd-wireguard = handleTest ./systemd-networkd-wireguard.nix {}; + systemd-networkd-vrf = handleTest ./systemd-networkd-vrf.nix {}; + systemd-networkd = handleTest ./systemd-networkd.nix {}; + systemd-networkd-dhcpserver = handleTest ./systemd-networkd-dhcpserver.nix {}; systemd-nspawn = handleTest ./systemd-nspawn.nix {}; pdns-recursor = handleTest ./pdns-recursor.nix {}; taskserver = handleTest ./taskserver.nix {}; telegraf = handleTest ./telegraf.nix {}; tiddlywiki = handleTest ./tiddlywiki.nix {}; + timezone = handleTest ./timezone.nix {}; tinydns = handleTest ./tinydns.nix {}; tor = handleTest ./tor.nix {}; + # traefik test relies on docker-containers + traefik = handleTestOn ["x86_64-linux"] ./traefik.nix {}; transmission = handleTest ./transmission.nix {}; trac = handleTest ./trac.nix {}; + trilium-server = handleTestOn ["x86_64-linux"] ./trilium-server.nix {}; trezord = handleTest ./trezord.nix {}; trickster = handleTest ./trickster.nix {}; + tuptime = handleTest ./tuptime.nix {}; udisks2 = handleTest ./udisks2.nix {}; upnp = handleTest ./upnp.nix {}; uwsgi = handleTest ./uwsgi.nix {}; vault = handleTest ./vault.nix {}; + victoriametrics = handleTest ./victoriametrics.nix {}; virtualbox = handleTestOn ["x86_64-linux"] ./virtualbox.nix {}; + wg-quick = handleTest ./wireguard/wg-quick.nix {}; wireguard = handleTest ./wireguard {}; wireguard-generated = handleTest ./wireguard/generated.nix {}; wireguard-namespaces = handleTest ./wireguard/namespaces.nix {}; wordpress = handleTest ./wordpress.nix {}; + xandikos = handleTest ./xandikos.nix {}; xautolock = handleTest ./xautolock.nix {}; xfce = handleTest ./xfce.nix {}; xmonad = handleTest ./xmonad.nix {}; @@ -288,5 +337,7 @@ in xss-lock = handleTest ./xss-lock.nix {}; yabar = handleTest ./yabar.nix {}; yggdrasil = handleTest ./yggdrasil.nix {}; + zfs = handleTest ./zfs.nix {}; + zsh-history = handleTest ./zsh-history.nix {}; zookeeper = handleTest ./zookeeper.nix {}; } diff --git a/nixos/tests/bittorrent.nix b/nixos/tests/bittorrent.nix index e5be652c71124..0a97d5556a269 100644 --- a/nixos/tests/bittorrent.nix +++ b/nixos/tests/bittorrent.nix @@ -18,6 +18,17 @@ let externalRouterAddress = "80.100.100.1"; externalClient2Address = "80.100.100.2"; externalTrackerAddress = "80.100.100.3"; + + transmissionConfig = { ... }: { + environment.systemPackages = [ pkgs.transmission ]; + services.transmission = { + enable = true; + settings = { + dht-enabled = false; + message-level = 3; + }; + }; + }; in { @@ -26,88 +37,79 @@ in maintainers = [ domenkozar eelco rob bobvanderlinden ]; }; - nodes = - { tracker = - { pkgs, ... }: - { environment.systemPackages = [ pkgs.transmission ]; - - virtualisation.vlans = [ 1 ]; - networking.interfaces.eth1.ipv4.addresses = [ - { address = externalTrackerAddress; prefixLength = 24; } - ]; - - # We need Apache on the tracker to serve the torrents. - services.httpd.enable = true; - services.httpd.adminAddr = "foo@example.org"; - services.httpd.documentRoot = "/tmp"; - - networking.firewall.enable = false; - - services.opentracker.enable = true; - - services.transmission.enable = true; - services.transmission.settings.dht-enabled = false; - services.transmission.settings.port-forwaring-enabled = false; - }; - - router = - { pkgs, nodes, ... }: - { virtualisation.vlans = [ 1 2 ]; - networking.nat.enable = true; - networking.nat.internalInterfaces = [ "eth2" ]; - networking.nat.externalInterface = "eth1"; - networking.firewall.enable = true; - networking.firewall.trustedInterfaces = [ "eth2" ]; - networking.interfaces.eth0.ipv4.addresses = []; - networking.interfaces.eth1.ipv4.addresses = [ - { address = externalRouterAddress; prefixLength = 24; } - ]; - networking.interfaces.eth2.ipv4.addresses = [ - { address = internalRouterAddress; prefixLength = 24; } - ]; - services.miniupnpd = { - enable = true; - externalInterface = "eth1"; - internalIPs = [ "eth2" ]; - appendConfig = '' - ext_ip=${externalRouterAddress} - ''; + nodes = { + tracker = { pkgs, ... }: { + imports = [ transmissionConfig ]; + + virtualisation.vlans = [ 1 ]; + networking.firewall.enable = false; + networking.interfaces.eth1.ipv4.addresses = [ + { address = externalTrackerAddress; prefixLength = 24; } + ]; + + # We need Apache on the tracker to serve the torrents. + services.httpd = { + enable = true; + virtualHosts = { + "torrentserver.org" = { + adminAddr = "foo@example.org"; + documentRoot = "/tmp"; }; }; + }; + services.opentracker.enable = true; + }; - client1 = - { pkgs, nodes, ... }: - { environment.systemPackages = [ pkgs.transmission pkgs.miniupnpc ]; - virtualisation.vlans = [ 2 ]; - networking.interfaces.eth0.ipv4.addresses = []; - networking.interfaces.eth1.ipv4.addresses = [ - { address = internalClient1Address; prefixLength = 24; } - ]; - networking.defaultGateway = internalRouterAddress; - networking.firewall.enable = false; - services.transmission.enable = true; - services.transmission.settings.dht-enabled = false; - services.transmission.settings.message-level = 3; - }; + router = { pkgs, nodes, ... }: { + virtualisation.vlans = [ 1 2 ]; + networking.nat.enable = true; + networking.nat.internalInterfaces = [ "eth2" ]; + networking.nat.externalInterface = "eth1"; + networking.firewall.enable = true; + networking.firewall.trustedInterfaces = [ "eth2" ]; + networking.interfaces.eth0.ipv4.addresses = []; + networking.interfaces.eth1.ipv4.addresses = [ + { address = externalRouterAddress; prefixLength = 24; } + ]; + networking.interfaces.eth2.ipv4.addresses = [ + { address = internalRouterAddress; prefixLength = 24; } + ]; + services.miniupnpd = { + enable = true; + externalInterface = "eth1"; + internalIPs = [ "eth2" ]; + appendConfig = '' + ext_ip=${externalRouterAddress} + ''; + }; + }; - client2 = - { pkgs, ... }: - { environment.systemPackages = [ pkgs.transmission ]; - virtualisation.vlans = [ 1 ]; - networking.interfaces.eth0.ipv4.addresses = []; - networking.interfaces.eth1.ipv4.addresses = [ - { address = externalClient2Address; prefixLength = 24; } - ]; - networking.firewall.enable = false; - services.transmission.enable = true; - services.transmission.settings.dht-enabled = false; - services.transmission.settings.port-forwaring-enabled = false; - }; + client1 = { pkgs, nodes, ... }: { + imports = [ transmissionConfig ]; + environment.systemPackages = [ pkgs.miniupnpc ]; + + virtualisation.vlans = [ 2 ]; + networking.interfaces.eth0.ipv4.addresses = []; + networking.interfaces.eth1.ipv4.addresses = [ + { address = internalClient1Address; prefixLength = 24; } + ]; + networking.defaultGateway = internalRouterAddress; + networking.firewall.enable = false; }; - testScript = - { nodes, ... }: - '' + client2 = { pkgs, ... }: { + imports = [ transmissionConfig ]; + + virtualisation.vlans = [ 1 ]; + networking.interfaces.eth0.ipv4.addresses = []; + networking.interfaces.eth1.ipv4.addresses = [ + { address = externalClient2Address; prefixLength = 24; } + ]; + networking.firewall.enable = false; + }; + }; + + testScript = { nodes, ... }: '' start_all() # Wait for network and miniupnpd. @@ -159,5 +161,4 @@ in "cmp /tmp/test.tar.bz2 ${file}" ) ''; - }) diff --git a/nixos/tests/blivet.nix b/nixos/tests/blivet.nix deleted file mode 100644 index 2adc2ee1eeea3..0000000000000 --- a/nixos/tests/blivet.nix +++ /dev/null @@ -1,87 +0,0 @@ -import ./make-test.nix ({ pkgs, ... }: with pkgs.python2Packages; rec { - name = "blivet"; - meta = with pkgs.stdenv.lib.maintainers; { - maintainers = [ aszlig ]; - }; - - machine = { - environment.systemPackages = [ pkgs.python blivet mock ]; - boot.supportedFilesystems = [ "btrfs" "jfs" "reiserfs" "xfs" ]; - virtualisation.memorySize = 768; - }; - - debugBlivet = false; - debugProgramCalls = false; - - pythonTestRunner = pkgs.writeText "run-blivet-tests.py" '' - import sys - import logging - - from unittest import TestLoader - from unittest.runner import TextTestRunner - - ${pkgs.lib.optionalString debugProgramCalls '' - blivet_program_log = logging.getLogger("program") - blivet_program_log.setLevel(logging.DEBUG) - blivet_program_log.addHandler(logging.StreamHandler(sys.stderr)) - ''} - - ${pkgs.lib.optionalString debugBlivet '' - blivet_log = logging.getLogger("blivet") - blivet_log.setLevel(logging.DEBUG) - blivet_log.addHandler(logging.StreamHandler(sys.stderr)) - ''} - - runner = TextTestRunner(verbosity=2, failfast=False, buffer=False) - result = runner.run(TestLoader().discover('tests/', pattern='*_test.py')) - sys.exit(not result.wasSuccessful()) - ''; - - blivetTest = pkgs.writeScript "blivet-test.sh" '' - #!${pkgs.stdenv.shell} -e - - # Use the hosts temporary directory, because we have a tmpfs within the VM - # and we don't want to increase the memory size of the VM for no reason. - mkdir -p /tmp/xchg/bigtmp - TMPDIR=/tmp/xchg/bigtmp - export TMPDIR - - cp -Rd "${blivet.src}/tests" . - - # Skip SELinux tests - rm -f tests/formats_test/selinux_test.py - - # Race conditions in growing/shrinking during resync - rm -f tests/devicelibs_test/mdraid_* - - # Deactivate small BTRFS device test, because it fails with newer btrfsprogs - sed -i -e '/^class *BTRFSAsRootTestCase3(/,/^[^ ]/ { - /^class *BTRFSAsRootTestCase3(/d - /^$/d - /^ /d - }' tests/devicelibs_test/btrfs_test.py - - # How on earth can these tests ever work even upstream? O_o - sed -i -e '/def testDiskChunk[12]/,/^ *[^ ]/{n; s/^ */&return # /}' \ - tests/partitioning_test.py - - # fix hardcoded temporary directory - sed -i \ - -e '1i import tempfile' \ - -e 's|_STORE_FILE_PATH = .*|_STORE_FILE_PATH = tempfile.gettempdir()|' \ - -e 's|DEFAULT_STORE_SIZE = .*|DEFAULT_STORE_SIZE = 409600|' \ - tests/loopbackedtestcase.py - - PYTHONPATH=".:$(< "${pkgs.stdenv.mkDerivation { - name = "blivet-pythonpath"; - buildInputs = [ blivet mock ]; - buildCommand = "echo \"$PYTHONPATH\" > \"$out\""; - }}")" python "${pythonTestRunner}" - ''; - - testScript = '' - $machine->waitForUnit("multi-user.target"); - $machine->succeed("${blivetTest}"); - $machine->execute("rm -rf /tmp/xchg/bigtmp"); - ''; -}) diff --git a/nixos/tests/buildbot.nix b/nixos/tests/buildbot.nix index f5c8c4863b6f1..0d979dc2d0540 100644 --- a/nixos/tests/buildbot.nix +++ b/nixos/tests/buildbot.nix @@ -1,12 +1,11 @@ +# Test ensures buildbot master comes up correctly and workers can connect + { system ? builtins.currentSystem, config ? {}, pkgs ? import ../.. { inherit system config; } }: -with import ../lib/testing.nix { inherit system pkgs; }; - -# Test ensures buildbot master comes up correctly and workers can connect -makeTest { +import ./make-test-python.nix { name = "buildbot"; nodes = { @@ -39,75 +38,76 @@ makeTest { services.openssh.enable = true; networking.firewall.allowedTCPPorts = [ 22 9418 ]; environment.systemPackages = with pkgs; [ git ]; + systemd.services.git-daemon = { + description = "Git daemon for the test"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" "sshd.service" ]; + + serviceConfig.Restart = "always"; + path = with pkgs; [ coreutils git openssh ]; + environment = { HOME = "/root"; }; + preStart = '' + git config --global user.name 'Nobody Fakeuser' + git config --global user.email 'nobody\@fakerepo.com' + rm -rvf /srv/repos/fakerepo.git /tmp/fakerepo + mkdir -pv /srv/repos/fakerepo ~/.ssh + ssh-keyscan -H gitrepo > ~/.ssh/known_hosts + cat ~/.ssh/known_hosts + + mkdir -p /src/repos/fakerepo + cd /srv/repos/fakerepo + rm -rf * + git init + echo -e '#!/bin/sh\necho fakerepo' > fakerepo.sh + cat fakerepo.sh + touch .git/git-daemon-export-ok + git add fakerepo.sh .git/git-daemon-export-ok + git commit -m fakerepo + ''; + script = '' + git daemon --verbose --export-all --base-path=/srv/repos --reuseaddr + ''; + }; }; }; testScript = '' - #Start up and populate fake repo - $gitrepo->waitForUnit("multi-user.target"); - print($gitrepo->execute(" \ - git config --global user.name 'Nobody Fakeuser' && \ - git config --global user.email 'nobody\@fakerepo.com' && \ - rm -rvf /srv/repos/fakerepo.git /tmp/fakerepo && \ - mkdir -pv /srv/repos/fakerepo ~/.ssh && \ - ssh-keyscan -H gitrepo > ~/.ssh/known_hosts && \ - cat ~/.ssh/known_hosts && \ - cd /srv/repos/fakerepo && \ - git init && \ - echo -e '#!/bin/sh\necho fakerepo' > fakerepo.sh && \ - cat fakerepo.sh && \ - touch .git/git-daemon-export-ok && \ - git add fakerepo.sh .git/git-daemon-export-ok && \ - git commit -m fakerepo && \ - git daemon --verbose --export-all --base-path=/srv/repos --reuseaddr & \ - ")); - - # Test gitrepo - $bbmaster->waitForUnit("network-online.target"); - #$bbmaster->execute("nc -z gitrepo 9418"); - print($bbmaster->execute(" \ - rm -rfv /tmp/fakerepo && \ - git clone git://gitrepo/fakerepo /tmp/fakerepo && \ - pwd && \ - ls -la && \ - ls -la /tmp/fakerepo \ - ")); - - # Test start master and connect worker - $bbmaster->waitForUnit("buildbot-master.service"); - $bbmaster->waitUntilSucceeds("curl -s --head http://bbmaster:8010") =~ /200 OK/; - $bbworker->waitForUnit("network-online.target"); - $bbworker->execute("nc -z bbmaster 8010"); - $bbworker->execute("nc -z bbmaster 9989"); - $bbworker->waitForUnit("buildbot-worker.service"); - print($bbworker->execute("ls -la /home/bbworker/worker")); - + gitrepo.wait_for_unit("git-daemon.service") + gitrepo.wait_for_unit("multi-user.target") - # Test stop buildbot master and worker - print($bbmaster->execute(" \ - systemctl -l --no-pager status buildbot-master && \ - systemctl stop buildbot-master \ - ")); - $bbworker->fail("nc -z bbmaster 8010"); - $bbworker->fail("nc -z bbmaster 9989"); - print($bbworker->execute(" \ - systemctl -l --no-pager status buildbot-worker && \ - systemctl stop buildbot-worker && \ - ls -la /home/bbworker/worker \ - ")); + with subtest("Repo is accessible via git daemon"): + bbmaster.wait_for_unit("network-online.target") + bbmaster.succeed("rm -rfv /tmp/fakerepo") + bbmaster.succeed("git clone git://gitrepo/fakerepo /tmp/fakerepo") + with subtest("Master service and worker successfully connect"): + bbmaster.wait_for_unit("buildbot-master.service") + bbmaster.wait_until_succeeds("curl --fail -s --head http://bbmaster:8010") + bbworker.wait_for_unit("network-online.target") + bbworker.succeed("nc -z bbmaster 8010") + bbworker.succeed("nc -z bbmaster 9989") + bbworker.wait_for_unit("buildbot-worker.service") - # Test buildbot daemon mode - $bbmaster->execute("buildbot create-master /tmp"); - $bbmaster->execute("mv -fv /tmp/master.cfg.sample /tmp/master.cfg"); - $bbmaster->execute("sed -i 's/8010/8011/' /tmp/master.cfg"); - $bbmaster->execute("buildbot start /tmp"); - $bbworker->execute("nc -z bbmaster 8011"); - $bbworker->waitUntilSucceeds("curl -s --head http://bbmaster:8011") =~ /200 OK/; - $bbmaster->execute("buildbot stop /tmp"); - $bbworker->fail("nc -z bbmaster 8011"); + with subtest("Stop buildbot worker"): + bbmaster.succeed("systemctl -l --no-pager status buildbot-master") + bbmaster.succeed("systemctl stop buildbot-master") + bbworker.fail("nc -z bbmaster 8010") + bbworker.fail("nc -z bbmaster 9989") + bbworker.succeed("systemctl -l --no-pager status buildbot-worker") + bbworker.succeed("systemctl stop buildbot-worker") + with subtest("Buildbot daemon mode works"): + bbmaster.succeed( + "buildbot create-master /tmp", + "mv -fv /tmp/master.cfg.sample /tmp/master.cfg", + "sed -i 's/8010/8011/' /tmp/master.cfg", + "buildbot start /tmp", + "nc -z bbmaster 8011", + ) + bbworker.wait_until_succeeds("curl --fail -s --head http://bbmaster:8011") + bbmaster.wait_until_succeeds("buildbot stop /tmp") + bbworker.fail("nc -z bbmaster 8011") ''; meta.maintainers = with pkgs.stdenv.lib.maintainers; [ nand0p ]; -} +} {} diff --git a/nixos/tests/buildkite-agents.nix b/nixos/tests/buildkite-agents.nix new file mode 100644 index 0000000000000..a6f33e0143c5e --- /dev/null +++ b/nixos/tests/buildkite-agents.nix @@ -0,0 +1,31 @@ +import ./make-test-python.nix ({ pkgs, ... }: + +{ + name = "buildkite-agent"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ flokli ]; + }; + + machine = { pkgs, ... }: { + services.buildkite-agents = { + one = { + privateSshKeyPath = (import ./ssh-keys.nix pkgs).snakeOilPrivateKey; + tokenPath = (pkgs.writeText "my-token" "5678"); + }; + two = { + tokenPath = (pkgs.writeText "my-token" "1234"); + }; + }; + }; + + testScript = '' + start_all() + # we can't wait on the unit to start up, as we obviously can't connect to buildkite, + # but we can look whether files are set up correctly + + machine.wait_for_file("/var/lib/buildkite-agent-one/buildkite-agent.cfg") + machine.wait_for_file("/var/lib/buildkite-agent-one/.ssh/id_rsa") + + machine.wait_for_file("/var/lib/buildkite-agent-two/buildkite-agent.cfg") + ''; +}) diff --git a/nixos/tests/caddy.nix b/nixos/tests/caddy.nix index fc10df0c79b54..144d83179a16d 100644 --- a/nixos/tests/caddy.nix +++ b/nixos/tests/caddy.nix @@ -20,35 +20,33 @@ import ./make-test-python.nix ({ pkgs, ... }: { } ''; - nesting.clone = [ - { - services.caddy.config = lib.mkForce '' - http://localhost { - gzip + specialisation.etag.configuration = { + services.caddy.config = lib.mkForce '' + http://localhost { + gzip - root ${ - pkgs.runCommand "testdir2" {} '' - mkdir "$out" - echo changed > "$out/example.html" - '' - } + root ${ + pkgs.runCommand "testdir2" {} '' + mkdir "$out" + echo changed > "$out/example.html" + '' } - ''; - } + } + ''; + }; - { - services.caddy.config = '' - http://localhost:8080 { - } - ''; - } - ]; + specialisation.config-reload.configuration = { + services.caddy.config = '' + http://localhost:8080 { + } + ''; + }; }; }; testScript = { nodes, ... }: let - etagSystem = "${nodes.webserver.config.system.build.toplevel}/fine-tune/child-1"; - justReloadSystem = "${nodes.webserver.config.system.build.toplevel}/fine-tune/child-2"; + etagSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/etag"; + justReloadSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/config-reload"; in '' url = "http://localhost/example.html" webserver.wait_for_unit("caddy") @@ -77,7 +75,7 @@ import ./make-test-python.nix ({ pkgs, ... }: { assert old_etag != new_etag, "Old ETag {} is the same as {}".format( old_etag, new_etag ) - + with subtest("config is reloaded on nixos-rebuild switch"): webserver.succeed( "${justReloadSystem}/bin/switch-to-configuration test >&2" diff --git a/nixos/tests/cage.nix b/nixos/tests/cage.nix new file mode 100644 index 0000000000000..a6f73e00c066e --- /dev/null +++ b/nixos/tests/cage.nix @@ -0,0 +1,43 @@ +import ./make-test-python.nix ({ pkgs, ...} : + +{ + name = "cage"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ matthewbauer flokli ]; + }; + + machine = { ... }: + + { + imports = [ ./common/user-account.nix ]; + services.cage = { + enable = true; + user = "alice"; + program = "${pkgs.xterm}/bin/xterm -cm -pc"; # disable color and bold to make OCR easier + }; + + # this needs a fairly recent kernel, otherwise: + # [backend/drm/util.c:215] Unable to add DRM framebuffer: No such file or directory + # [backend/drm/legacy.c:15] Virtual-1: Failed to set CRTC: No such file or directory + # [backend/drm/util.c:215] Unable to add DRM framebuffer: No such file or directory + # [backend/drm/legacy.c:15] Virtual-1: Failed to set CRTC: No such file or directory + # [backend/drm/drm.c:618] Failed to initialize renderer on connector 'Virtual-1': initial page-flip failed + # [backend/drm/drm.c:701] Failed to initialize renderer for plane + boot.kernelPackages = pkgs.linuxPackages_latest; + + virtualisation.memorySize = 1024; + }; + + enableOCR = true; + + testScript = { nodes, ... }: let + user = nodes.machine.config.users.users.alice; + in '' + with subtest("Wait for cage to boot up"): + start_all() + machine.wait_for_file("/run/user/${toString user.uid}/wayland-0.lock") + machine.wait_until_succeeds("pgrep xterm") + machine.wait_for_text("alice@machine") + machine.screenshot("screen") + ''; +}) diff --git a/nixos/tests/ceph-multi-node.nix b/nixos/tests/ceph-multi-node.nix index 52a0b5caf2354..22fe5cada4800 100644 --- a/nixos/tests/ceph-multi-node.nix +++ b/nixos/tests/ceph-multi-node.nix @@ -19,6 +19,12 @@ let key = "AQBEEJNac00kExAAXEgy943BGyOpVH1LLlHafQ=="; uuid = "5e97a838-85b6-43b0-8950-cb56d554d1e5"; }; + osd2 = { + name = "2"; + ip = "192.168.1.4"; + key = "AQAdyhZeIaUlARAAGRoidDAmS6Vkp546UFEf5w=="; + uuid = "ea999274-13d0-4dd5-9af9-ad25a324f72f"; + }; }; generateCephConfig = { daemonConfig }: { enable = true; @@ -72,35 +78,20 @@ let }; }; }; - networkOsd0 = { + networkOsd = osd: { dhcpcd.enable = false; interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [ - { address = cfg.osd0.ip; prefixLength = 24; } + { address = osd.ip; prefixLength = 24; } ]; firewall = { allowedTCPPortRanges = [ { from = 6800; to = 7300; } ]; }; }; - cephConfigOsd0 = generateCephConfig { daemonConfig = { - osd = { - enable = true; - daemons = [ cfg.osd0.name ]; - }; - }; }; - networkOsd1 = { - dhcpcd.enable = false; - interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [ - { address = cfg.osd1.ip; prefixLength = 24; } - ]; - firewall = { - allowedTCPPortRanges = [ { from = 6800; to = 7300; } ]; - }; - }; - cephConfigOsd1 = generateCephConfig { daemonConfig = { + cephConfigOsd = osd: generateCephConfig { daemonConfig = { osd = { enable = true; - daemons = [ cfg.osd1.name ]; + daemons = [ osd.name ]; }; }; }; @@ -114,6 +105,7 @@ let monA.wait_for_unit("network.target") osd0.wait_for_unit("network.target") osd1.wait_for_unit("network.target") + osd2.wait_for_unit("network.target") # Bootstrap ceph-mon daemon monA.succeed( @@ -145,8 +137,9 @@ let monA.succeed("cp /etc/ceph/ceph.client.admin.keyring /tmp/shared") osd0.succeed("cp /tmp/shared/ceph.client.admin.keyring /etc/ceph") osd1.succeed("cp /tmp/shared/ceph.client.admin.keyring /etc/ceph") + osd2.succeed("cp /tmp/shared/ceph.client.admin.keyring /etc/ceph") - # Bootstrap both OSDs + # Bootstrap OSDs osd0.succeed( "mkfs.xfs /dev/vdb", "mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd0.name}", @@ -161,6 +154,13 @@ let "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd1.name}/keyring --name osd.${cfg.osd1.name} --add-key ${cfg.osd1.key}", 'echo \'{"cephx_secret": "${cfg.osd1.key}"}\' | ceph osd new ${cfg.osd1.uuid} -i -', ) + osd2.succeed( + "mkfs.xfs /dev/vdb", + "mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd2.name}", + "mount /dev/vdb /var/lib/ceph/osd/ceph-${cfg.osd2.name}", + "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd2.name}/keyring --name osd.${cfg.osd2.name} --add-key ${cfg.osd2.key}", + 'echo \'{"cephx_secret": "${cfg.osd2.key}"}\' | ceph osd new ${cfg.osd2.uuid} -i -', + ) # Initialize the OSDs with regular filestore osd0.succeed( @@ -173,20 +173,25 @@ let "chown -R ceph:ceph /var/lib/ceph/osd", "systemctl start ceph-osd-${cfg.osd1.name}", ) - monA.wait_until_succeeds("ceph osd stat | grep -e '2 osds: 2 up[^,]*, 2 in'") + osd2.succeed( + "ceph-osd -i ${cfg.osd2.name} --mkfs --osd-uuid ${cfg.osd2.uuid}", + "chown -R ceph:ceph /var/lib/ceph/osd", + "systemctl start ceph-osd-${cfg.osd2.name}", + ) + monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'") monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'") monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'") monA.succeed( - "ceph osd pool create multi-node-test 100 100", + "ceph osd pool create multi-node-test 128 128", "ceph osd pool ls | grep 'multi-node-test'", "ceph osd pool rename multi-node-test multi-node-other-test", "ceph osd pool ls | grep 'multi-node-other-test'", ) - monA.wait_until_succeeds("ceph -s | grep '1 pools, 100 pgs'") + monA.wait_until_succeeds("ceph -s | grep '1 pools, 128 pgs'") monA.succeed("ceph osd pool set multi-node-other-test size 2") monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'") - monA.wait_until_succeeds("ceph -s | grep '100 active+clean'") + monA.wait_until_succeeds("ceph -s | grep '128 active+clean'") monA.fail( "ceph osd pool ls | grep 'multi-node-test'", "ceph osd pool delete multi-node-other-test multi-node-other-test --yes-i-really-really-mean-it", @@ -196,16 +201,18 @@ let monA.crash() osd0.crash() osd1.crash() + osd2.crash() # Start it up osd0.start() osd1.start() + osd2.start() monA.start() # Ensure the cluster comes back up again monA.succeed("ceph -s | grep 'mon: 1 daemons'") monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'") - monA.wait_until_succeeds("ceph osd stat | grep -e '2 osds: 2 up[^,]*, 2 in'") + monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'") monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'") monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'") ''; @@ -217,8 +224,9 @@ in { nodes = { monA = generateHost { pkgs = pkgs; cephConfig = cephConfigMonA; networkConfig = networkMonA; }; - osd0 = generateHost { pkgs = pkgs; cephConfig = cephConfigOsd0; networkConfig = networkOsd0; }; - osd1 = generateHost { pkgs = pkgs; cephConfig = cephConfigOsd1; networkConfig = networkOsd1; }; + osd0 = generateHost { pkgs = pkgs; cephConfig = cephConfigOsd cfg.osd0; networkConfig = networkOsd cfg.osd0; }; + osd1 = generateHost { pkgs = pkgs; cephConfig = cephConfigOsd cfg.osd1; networkConfig = networkOsd cfg.osd1; }; + osd2 = generateHost { pkgs = pkgs; cephConfig = cephConfigOsd cfg.osd2; networkConfig = networkOsd cfg.osd2; }; }; testScript = testscript; diff --git a/nixos/tests/ceph-single-node.nix b/nixos/tests/ceph-single-node.nix index da92a73e14d93..01c4b41384513 100644 --- a/nixos/tests/ceph-single-node.nix +++ b/nixos/tests/ceph-single-node.nix @@ -17,6 +17,11 @@ let key = "AQBEEJNac00kExAAXEgy943BGyOpVH1LLlHafQ=="; uuid = "5e97a838-85b6-43b0-8950-cb56d554d1e5"; }; + osd2 = { + name = "2"; + key = "AQAdyhZeIaUlARAAGRoidDAmS6Vkp546UFEf5w=="; + uuid = "ea999274-13d0-4dd5-9af9-ad25a324f72f"; + }; }; generateCephConfig = { daemonConfig }: { enable = true; @@ -30,7 +35,7 @@ let generateHost = { pkgs, cephConfig, networkConfig, ... }: { virtualisation = { memorySize = 512; - emptyDiskImages = [ 20480 20480 ]; + emptyDiskImages = [ 20480 20480 20480 ]; vlans = [ 1 ]; }; @@ -65,7 +70,7 @@ let }; osd = { enable = true; - daemons = [ cfg.osd0.name cfg.osd1.name ]; + daemons = [ cfg.osd0.name cfg.osd1.name cfg.osd2.name ]; }; }; }; @@ -104,39 +109,46 @@ let monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'") monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'") - # Bootstrap both OSDs + # Bootstrap OSDs monA.succeed( "mkfs.xfs /dev/vdb", "mkfs.xfs /dev/vdc", + "mkfs.xfs /dev/vdd", "mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd0.name}", "mount /dev/vdb /var/lib/ceph/osd/ceph-${cfg.osd0.name}", "mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd1.name}", "mount /dev/vdc /var/lib/ceph/osd/ceph-${cfg.osd1.name}", + "mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd2.name}", + "mount /dev/vdd /var/lib/ceph/osd/ceph-${cfg.osd2.name}", "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd0.name}/keyring --name osd.${cfg.osd0.name} --add-key ${cfg.osd0.key}", "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd1.name}/keyring --name osd.${cfg.osd1.name} --add-key ${cfg.osd1.key}", + "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd2.name}/keyring --name osd.${cfg.osd2.name} --add-key ${cfg.osd2.key}", 'echo \'{"cephx_secret": "${cfg.osd0.key}"}\' | ceph osd new ${cfg.osd0.uuid} -i -', 'echo \'{"cephx_secret": "${cfg.osd1.key}"}\' | ceph osd new ${cfg.osd1.uuid} -i -', + 'echo \'{"cephx_secret": "${cfg.osd2.key}"}\' | ceph osd new ${cfg.osd2.uuid} -i -', ) # Initialize the OSDs with regular filestore monA.succeed( "ceph-osd -i ${cfg.osd0.name} --mkfs --osd-uuid ${cfg.osd0.uuid}", "ceph-osd -i ${cfg.osd1.name} --mkfs --osd-uuid ${cfg.osd1.uuid}", + "ceph-osd -i ${cfg.osd2.name} --mkfs --osd-uuid ${cfg.osd2.uuid}", "chown -R ceph:ceph /var/lib/ceph/osd", "systemctl start ceph-osd-${cfg.osd0.name}", "systemctl start ceph-osd-${cfg.osd1.name}", + "systemctl start ceph-osd-${cfg.osd2.name}", ) - monA.wait_until_succeeds("ceph osd stat | grep -e '2 osds: 2 up[^,]*, 2 in'") + monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'") monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'") monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'") monA.succeed( - "ceph osd pool create single-node-test 100 100", + "ceph osd pool create single-node-test 128 128", "ceph osd pool ls | grep 'single-node-test'", "ceph osd pool rename single-node-test single-node-other-test", "ceph osd pool ls | grep 'single-node-other-test'", ) - monA.wait_until_succeeds("ceph -s | grep '1 pools, 100 pgs'") + monA.wait_until_succeeds("ceph -s | grep '1 pools, 128 pgs'") monA.succeed( "ceph osd getcrushmap -o crush", "crushtool -d crush -o decrushed", @@ -146,7 +158,7 @@ let "ceph osd pool set single-node-other-test size 2", ) monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'") - monA.wait_until_succeeds("ceph -s | grep '100 active+clean'") + monA.wait_until_succeeds("ceph -s | grep '128 active+clean'") monA.fail( "ceph osd pool ls | grep 'multi-node-test'", "ceph osd pool delete single-node-other-test single-node-other-test --yes-i-really-really-mean-it", @@ -161,11 +173,12 @@ let monA.wait_for_unit("ceph-mgr-${cfg.monA.name}") monA.wait_for_unit("ceph-osd-${cfg.osd0.name}") monA.wait_for_unit("ceph-osd-${cfg.osd1.name}") + monA.wait_for_unit("ceph-osd-${cfg.osd2.name}") # Ensure the cluster comes back up again monA.succeed("ceph -s | grep 'mon: 1 daemons'") monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'") - monA.wait_until_succeeds("ceph osd stat | grep -e '2 osds: 2 up[^,]*, 2 in'") + monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'") monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'") monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'") ''; diff --git a/nixos/tests/certmgr.nix b/nixos/tests/certmgr.nix index cb69f35e862f8..ef32f54400e30 100644 --- a/nixos/tests/certmgr.nix +++ b/nixos/tests/certmgr.nix @@ -9,8 +9,8 @@ let inherit action; authority = { file = { - group = "nobody"; - owner = "nobody"; + group = "nginx"; + owner = "nginx"; path = "/tmp/${host}-ca.pem"; }; label = "www_ca"; @@ -18,14 +18,14 @@ let remote = "localhost:8888"; }; certificate = { - group = "nobody"; - owner = "nobody"; + group = "nginx"; + owner = "nginx"; path = "/tmp/${host}-cert.pem"; }; private_key = { - group = "nobody"; + group = "nginx"; mode = "0600"; - owner = "nobody"; + owner = "nginx"; path = "/tmp/${host}-key.pem"; }; request = { diff --git a/nixos/tests/chromium.nix b/nixos/tests/chromium.nix index af5db2a3dbe1f..fc5d3a5c52fe5 100644 --- a/nixos/tests/chromium.nix +++ b/nixos/tests/chromium.nix @@ -8,7 +8,7 @@ } }: -with import ../lib/testing.nix { inherit system pkgs; }; +with import ../lib/testing-python.nix { inherit system pkgs; }; with pkgs.lib; mapAttrs (channel: chromiumPkg: makeTest rec { @@ -21,9 +21,11 @@ mapAttrs (channel: chromiumPkg: makeTest rec { enableOCR = true; + user = "alice"; + machine.imports = [ ./common/user-account.nix ./common/x11.nix ]; machine.virtualisation.memorySize = 2047; - machine.services.xserver.displayManager.auto.user = "alice"; + machine.test-support.displayManager.auto.user = user; machine.environment.systemPackages = [ chromiumPkg ]; startupHTML = pkgs.writeText "chromium-startup.html" '' @@ -36,7 +38,7 @@ mapAttrs (channel: chromiumPkg: makeTest rec { <body onload="javascript:document.title='startup done'"> <img src="file://${pkgs.fetchurl { url = "http://nixos.org/logo/nixos-hex.svg"; - sha256 = "0wxpp65npdw2cg8m0cxc9qff1sb3b478cxpg1741d8951g948rg8"; + sha256 = "07ymq6nw8kc22m7kzxjxldhiq8gzmc7f45kq2bvhbdm0w5s112s4"; }}" /> </body> </html> @@ -47,155 +49,218 @@ mapAttrs (channel: chromiumPkg: makeTest rec { xdoScript = pkgs.writeText "${name}.xdo" text; in "${pkgs.xdotool}/bin/xdotool '${xdoScript}'"; in '' + import shlex + from contextlib import contextmanager, _GeneratorContextManager + + # Run as user alice - sub ru ($) { - my $esc = $_[0] =~ s/'/'\\${"'"}'/gr; - return "su - alice -c '$esc'"; - } - - sub createNewWin { - $machine->nest("creating a new Chromium window", sub { - $machine->execute(ru "${xdo "new-window" '' - search --onlyvisible --name "startup done" - windowfocus --sync - windowactivate --sync - ''}"); - $machine->execute(ru "${xdo "new-window" '' - key Ctrl+n - ''}"); - }); - } - - sub closeWin { - Machine::retry sub { - $machine->execute(ru "${xdo "close-window" '' - search --onlyvisible --name "new tab" - windowfocus --sync - windowactivate --sync - ''}"); - $machine->execute(ru "${xdo "close-window" '' - key Ctrl+w - ''}"); - for (1..20) { - my ($status, $out) = $machine->execute(ru "${xdo "wait-for-close" '' - search --onlyvisible --name "new tab" - ''}"); - return 1 if $status != 0; - $machine->sleep(1); - } - } - } - - sub waitForNewWin { - my $ret = 0; - $machine->nest("waiting for new Chromium window to appear", sub { - for (1..20) { - my ($status, $out) = $machine->execute(ru "${xdo "wait-for-window" '' - search --onlyvisible --name "new tab" - windowfocus --sync - windowactivate --sync - ''}"); - if ($status == 0) { - $ret = 1; - - # XXX: Somehow Chromium is not accepting keystrokes for a few - # seconds after a new window has appeared, so let's wait a while. - $machine->sleep(10); - - last; - } - $machine->sleep(1); - } - }); - return $ret; - } - - sub createAndWaitForNewWin { - for (1..3) { - createNewWin; - return 1 if waitForNewWin; - } - die "new window didn't appear within 60 seconds"; - } - - sub testNewWin { - my ($desc, $code) = @_; - createAndWaitForNewWin; - subtest($desc, $code); - closeWin; - } - - $machine->waitForX; - - my $url = "file://${startupHTML}"; - $machine->execute(ru "ulimit -c unlimited; chromium \"$url\" & disown"); - $machine->waitForText(qr/startup done/); - $machine->waitUntilSucceeds(ru "${xdo "check-startup" '' - search --sync --onlyvisible --name "startup done" - # close first start help popup - key -delay 1000 Escape - windowfocus --sync - windowactivate --sync - ''}"); - - createAndWaitForNewWin; - $machine->screenshot("empty_windows"); - closeWin; - - $machine->screenshot("startup_done"); - - testNewWin "check sandbox", sub { - $machine->succeed(ru "${xdo "type-url" '' - search --sync --onlyvisible --name "new tab" - windowfocus --sync - type --delay 1000 "chrome://sandbox" - ''}"); - - $machine->succeed(ru "${xdo "submit-url" '' - search --sync --onlyvisible --name "new tab" - windowfocus --sync - key --delay 1000 Return - ''}"); - - $machine->screenshot("sandbox_info"); - - $machine->succeed(ru "${xdo "find-window" '' - search --sync --onlyvisible --name "sandbox status" - windowfocus --sync - ''}"); - $machine->succeed(ru "${xdo "copy-sandbox-info" '' - key --delay 1000 Ctrl+a Ctrl+c - ''}"); - - my $clipboard = $machine->succeed(ru "${pkgs.xclip}/bin/xclip -o"); - die "sandbox not working properly: $clipboard" - unless $clipboard =~ /layer 1 sandbox.*namespace/mi - && $clipboard =~ /pid namespaces.*yes/mi - && $clipboard =~ /network namespaces.*yes/mi - && $clipboard =~ /seccomp.*sandbox.*yes/mi - && $clipboard =~ /you are adequately sandboxed/mi; - - $machine->sleep(1); - $machine->succeed(ru "${xdo "find-window-after-copy" '' - search --onlyvisible --name "sandbox status" - ''}"); - - my $clipboard = $machine->succeed(ru "echo void | ${pkgs.xclip}/bin/xclip -i"); - $machine->succeed(ru "${xdo "copy-sandbox-info" '' - key --delay 1000 Ctrl+a Ctrl+c - ''}"); - - my $clipboard = $machine->succeed(ru "${pkgs.xclip}/bin/xclip -o"); - die "copying twice in a row does not work properly: $clipboard" - unless $clipboard =~ /layer 1 sandbox.*namespace/mi - && $clipboard =~ /pid namespaces.*yes/mi - && $clipboard =~ /network namespaces.*yes/mi - && $clipboard =~ /seccomp.*sandbox.*yes/mi - && $clipboard =~ /you are adequately sandboxed/mi; - - $machine->screenshot("afer_copy_from_chromium"); - }; - - $machine->shutdown; + def ru(cmd): + return "su - ${user} -c " + shlex.quote(cmd) + + + def create_new_win(): + with machine.nested("Creating a new Chromium window"): + machine.execute( + ru( + "${xdo "new-window" '' + search --onlyvisible --name "startup done" + windowfocus --sync + windowactivate --sync + ''}" + ) + ) + machine.execute( + ru( + "${xdo "new-window" '' + key Ctrl+n + ''}" + ) + ) + + + def close_win(): + def try_close(_): + machine.execute( + ru( + "${xdo "close-window" '' + search --onlyvisible --name "new tab" + windowfocus --sync + windowactivate --sync + ''}" + ) + ) + machine.execute( + ru( + "${xdo "close-window" '' + key Ctrl+w + ''}" + ) + ) + for _ in range(1, 20): + status, out = machine.execute( + ru( + "${xdo "wait-for-close" '' + search --onlyvisible --name "new tab" + ''}" + ) + ) + if status != 0: + return True + machine.sleep(1) + return False + + retry(try_close) + + + def wait_for_new_win(): + ret = False + with machine.nested("Waiting for new Chromium window to appear"): + for _ in range(1, 20): + status, out = machine.execute( + ru( + "${xdo "wait-for-window" '' + search --onlyvisible --name "new tab" + windowfocus --sync + windowactivate --sync + ''}" + ) + ) + if status == 0: + ret = True + machine.sleep(10) + break + machine.sleep(1) + return ret + + + def create_and_wait_for_new_win(): + for _ in range(1, 3): + create_new_win() + if wait_for_new_win(): + return True + assert False, "new window did not appear within 60 seconds" + + + @contextmanager + def test_new_win(description): + create_and_wait_for_new_win() + with machine.nested(description): + yield + close_win() + + + machine.wait_for_x() + + url = "file://${startupHTML}" + machine.succeed(ru(f'ulimit -c unlimited; chromium "{url}" & disown')) + machine.wait_for_text("startup done") + machine.wait_until_succeeds( + ru( + "${xdo "check-startup" '' + search --sync --onlyvisible --name "startup done" + # close first start help popup + key -delay 1000 Escape + windowfocus --sync + windowactivate --sync + ''}" + ) + ) + + create_and_wait_for_new_win() + machine.screenshot("empty_windows") + close_win() + + machine.screenshot("startup_done") + + with test_new_win("check sandbox"): + machine.succeed( + ru( + "${xdo "type-url" '' + search --sync --onlyvisible --name "new tab" + windowfocus --sync + type --delay 1000 "chrome://sandbox" + ''}" + ) + ) + + machine.succeed( + ru( + "${xdo "submit-url" '' + search --sync --onlyvisible --name "new tab" + windowfocus --sync + key --delay 1000 Return + ''}" + ) + ) + + machine.screenshot("sandbox_info") + + machine.succeed( + ru( + "${xdo "find-window" '' + search --sync --onlyvisible --name "sandbox status" + windowfocus --sync + ''}" + ) + ) + machine.succeed( + ru( + "${xdo "copy-sandbox-info" '' + key --delay 1000 Ctrl+a Ctrl+c + ''}" + ) + ) + + clipboard = machine.succeed( + ru("${pkgs.xclip}/bin/xclip -o") + ) + + filters = [ + "layer 1 sandbox.*namespace", + "pid namespaces.*yes", + "network namespaces.*yes", + "seccomp.*sandbox.*yes", + "you are adequately sandboxed", + ] + if not all( + re.search(filter, clipboard, flags=re.DOTALL | re.IGNORECASE) + for filter in filters + ): + assert False, f"sandbox not working properly: {clipboard}" + + machine.sleep(1) + machine.succeed( + ru( + "${xdo "find-window-after-copy" '' + search --onlyvisible --name "sandbox status" + ''}" + ) + ) + + clipboard = machine.succeed( + ru( + "echo void | ${pkgs.xclip}/bin/xclip -i" + ) + ) + machine.succeed( + ru( + "${xdo "copy-sandbox-info" '' + key --delay 1000 Ctrl+a Ctrl+c + ''}" + ) + ) + + clipboard = machine.succeed( + ru("${pkgs.xclip}/bin/xclip -o") + ) + if not all( + re.search(filter, clipboard, flags=re.DOTALL | re.IGNORECASE) + for filter in filters + ): + assert False, f"copying twice in a row does not work properly: {clipboard}" + + machine.screenshot("after_copy_from_chromium") + + machine.shutdown() ''; }) channelMap diff --git a/nixos/tests/cockroachdb.nix b/nixos/tests/cockroachdb.nix index 496283fddc7b0..d0cc5e19837c6 100644 --- a/nixos/tests/cockroachdb.nix +++ b/nixos/tests/cockroachdb.nix @@ -1,7 +1,7 @@ # This performs a full 'end-to-end' test of a multi-node CockroachDB cluster # using the built-in 'cockroach workload' command, to simulate a semi-realistic # test load. It generally takes anywhere from 3-5 minutes to run and 1-2GB of -# RAM (though each of 3 workers gets 1GB allocated) +# RAM (though each of 3 workers gets 2GB allocated) # # CockroachDB requires synchronized system clocks within a small error window # (~500ms by default) on each node in order to maintain a multi-node cluster. @@ -55,7 +55,7 @@ let { # Bank/TPC-C benchmarks take some memory to complete - virtualisation.memorySize = 1024; + virtualisation.memorySize = 2048; # Install the KVM PTP "Virtualized Clock" driver. This allows a /dev/ptp0 # device to appear as a reference clock, synchronized to the host clock. @@ -88,6 +88,8 @@ let services.cockroachdb.listen.address = myAddr; services.cockroachdb.join = lib.mkIf (joinNode != null) joinNode; + systemd.services.chronyd.unitConfig.ConditionPathExists = "/dev/ptp0"; + # Hold startup until Chrony has performed its first measurement (which # will probably result in a full timeskip, thanks to makestep) systemd.services.cockroachdb.preStart = '' @@ -95,7 +97,7 @@ let ''; }; -in import ./make-test.nix ({ pkgs, ...} : { +in import ./make-test-python.nix ({ pkgs, ...} : { name = "cockroachdb"; meta.maintainers = with pkgs.stdenv.lib.maintainers; [ thoughtpolice ]; @@ -110,17 +112,13 @@ in import ./make-test.nix ({ pkgs, ...} : { # there's otherwise no way to guarantee that node1 will start before the others try # to join it. testScript = '' - $node1->start; - $node1->waitForUnit("cockroachdb"); - - $node2->start; - $node2->waitForUnit("cockroachdb"); - - $node3->start; - $node3->waitForUnit("cockroachdb"); - - $node1->mustSucceed("cockroach sql --host=192.168.1.1 --insecure -e 'SHOW ALL CLUSTER SETTINGS' 2>&1"); - $node1->mustSucceed("cockroach workload init bank 'postgresql://root\@192.168.1.1:26257?sslmode=disable'"); - $node1->mustSucceed("cockroach workload run bank --duration=1m 'postgresql://root\@192.168.1.1:26257?sslmode=disable'"); + for node in node1, node2, node3: + node.start() + node.wait_for_unit("cockroachdb") + node1.succeed( + "cockroach sql --host=192.168.1.1 --insecure -e 'SHOW ALL CLUSTER SETTINGS' 2>&1", + "cockroach workload init bank 'postgresql://root@192.168.1.1:26257?sslmode=disable'", + "cockroach workload run bank --duration=1m 'postgresql://root@192.168.1.1:26257?sslmode=disable'", + ) ''; }) diff --git a/nixos/tests/common/acme/client/default.nix b/nixos/tests/common/acme/client/default.nix new file mode 100644 index 0000000000000..80893da025244 --- /dev/null +++ b/nixos/tests/common/acme/client/default.nix @@ -0,0 +1,15 @@ +{ lib, nodes, pkgs, ... }: + +let + acme-ca = nodes.acme.config.test-support.acme.caCert; +in + +{ + security.acme = { + server = "https://acme.test/dir"; + email = "hostmaster@example.test"; + acceptTerms = true; + }; + + security.pki.certificateFiles = [ acme-ca ]; +} diff --git a/nixos/tests/common/letsencrypt/default.nix b/nixos/tests/common/acme/server/default.nix index 110a2520971d0..1a0ee882572ce 100644 --- a/nixos/tests/common/letsencrypt/default.nix +++ b/nixos/tests/common/acme/server/default.nix @@ -1,27 +1,27 @@ # The certificate for the ACME service is exported as: # -# config.test-support.letsencrypt.caCert +# config.test-support.acme.caCert # # This value can be used inside the configuration of other test nodes to inject # the snakeoil certificate into security.pki.certificateFiles or into package # overlays. # # Another value that's needed if you don't use a custom resolver (see below for -# notes on that) is to add the letsencrypt node as a nameserver to every node +# notes on that) is to add the acme node as a nameserver to every node # that needs to acquire certificates using ACME, because otherwise the API host -# for letsencrypt.org can't be resolved. +# for acme.test can't be resolved. # # A configuration example of a full node setup using this would be this: # # { -# letsencrypt = import ./common/letsencrypt; +# acme = import ./common/acme/server; # # example = { nodes, ... }: { # networking.nameservers = [ -# nodes.letsencrypt.config.networking.primaryIPAddress +# nodes.acme.config.networking.primaryIPAddress # ]; # security.pki.certificateFiles = [ -# nodes.letsencrypt.config.test-support.letsencrypt.caCert +# nodes.acme.config.test-support.acme.caCert # ]; # }; # } @@ -33,9 +33,9 @@ # override networking.nameservers like this: # # { -# letsencrypt = { nodes, ... }: { -# imports = [ ./common/letsencrypt ]; -# networking.nameservers = [ +# acme = { nodes, lib, ... }: { +# imports = [ ./common/acme/server ]; +# networking.nameservers = lib.mkForce [ # nodes.myresolver.config.networking.primaryIPAddress # ]; # }; @@ -55,16 +55,16 @@ let snakeOilCerts = import ./snakeoil-certs.nix; - wfeDomain = "acme-v02.api.letsencrypt.org"; + wfeDomain = "acme.test"; wfeCertFile = snakeOilCerts.${wfeDomain}.cert; wfeKeyFile = snakeOilCerts.${wfeDomain}.key; - siteDomain = "letsencrypt.org"; + siteDomain = "acme.test"; siteCertFile = snakeOilCerts.${siteDomain}.cert; siteKeyFile = snakeOilCerts.${siteDomain}.key; pebble = pkgs.pebble; resolver = let - message = "You need to define a resolver for the letsencrypt test module."; + message = "You need to define a resolver for the acme test module."; firstNS = lib.head config.networking.nameservers; in if config.networking.nameservers == [] then throw message else firstNS; @@ -76,15 +76,16 @@ let httpPort = 80; tlsPort = 443; ocspResponderURL = "http://0.0.0.0:4002"; + strict = true; }; pebbleConfFile = pkgs.writeText "pebble.conf" (builtins.toJSON pebbleConf); pebbleDataDir = "/root/pebble"; in { - imports = [ ../resolver.nix ]; + imports = [ ../../resolver.nix ]; - options.test-support.letsencrypt.caCert = lib.mkOption { + options.test-support.acme.caCert = lib.mkOption { type = lib.types.path; description = '' A certificate file to use with the <literal>nodes</literal> attribute to @@ -98,7 +99,7 @@ in { resolver.enable = let isLocalResolver = config.networking.nameservers == [ "127.0.0.1" ]; in lib.mkOverride 900 isLocalResolver; - letsencrypt.caCert = snakeOilCerts.ca.cert; + acme.caCert = snakeOilCerts.ca.cert; }; # This has priority 140, because modules/testing/test-instrumentation.nix @@ -126,8 +127,7 @@ in { ''; serviceConfig = { # Required to bind on privileged ports. - User = "root"; - Group = "root"; + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; }; }; }; diff --git a/nixos/tests/common/letsencrypt/mkcerts.nix b/nixos/tests/common/acme/server/mkcerts.nix index e7ac2bae46bd2..2474019cbac3b 100644 --- a/nixos/tests/common/letsencrypt/mkcerts.nix +++ b/nixos/tests/common/acme/server/mkcerts.nix @@ -1,10 +1,9 @@ { pkgs ? import <nixpkgs> {} , lib ? pkgs.lib - -, domains ? [ "acme-v02.api.letsencrypt.org" "letsencrypt.org" ] +, domains ? [ "acme.test" ] }: -pkgs.runCommand "letsencrypt-snakeoil-ca" { +pkgs.runCommand "acme-snakeoil-ca" { nativeBuildInputs = [ pkgs.openssl ]; } '' addpem() { diff --git a/nixos/tests/common/letsencrypt/mkcerts.sh b/nixos/tests/common/acme/server/mkcerts.sh index cc7f8ca650dd4..cc7f8ca650dd4 100755 --- a/nixos/tests/common/letsencrypt/mkcerts.sh +++ b/nixos/tests/common/acme/server/mkcerts.sh diff --git a/nixos/tests/common/acme/server/snakeoil-certs.nix b/nixos/tests/common/acme/server/snakeoil-certs.nix new file mode 100644 index 0000000000000..fd537c3260f1d --- /dev/null +++ b/nixos/tests/common/acme/server/snakeoil-certs.nix @@ -0,0 +1,171 @@ +# Generated via mkcert.sh in the same directory. +{ + ca.key = builtins.toFile "ca.key" '' + -----BEGIN PRIVATE KEY----- + MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDCnVZGEn68ezXl + DWE5gjsCPqutR4nxw/wvIbAxB2Vk2WeQ6HGvt2Jdrz5qer2IXd76YtpQeqd+ffet + aLtMeFTr+Xy9yqEpx2AfvmEEcLnuiWbsUGZzsHwW7/4kPgAFBy9TwJn/k892lR6u + QYa0QS39CX85kLMZ/LZXUyClIBa+IxT1OovmGqMOr4nGASRQP6d/nnyn41Knat/d + tpyaa5zgfYwA6YW6UxcywvBSpMOXM0/82BFZGyALt3nQ+ffmrtKcvMjsNLBFaslV + +zYO1PMbLbTCW8SmJTjhzuapXtBHruvoe24133XWlvcP1ylaTx0alwiQWJr1XEOU + WLEFTgOTeRyiVDxDunpz+7oGcwzcdOG8nCgd6w0aYaECz1zvS3FYTQz+MiqmEkx6 + s4bj1U90I0kwUJbeWjjrGO7Y9Qq4i19GafDg7cAMn9eHCiNbNrPj6t/gfaVbCrbk + m3ZVjkvLTQ2mb2lv7+tVii45227iNPuNS6lx2FVlr/DXiRrOVfghPvoOxUfXzogJ + hZLV4Zki+ycbGQa5w8YMDYCv4c08dKA7AatVhNS60c1zgQNjuWF3BvocSySyGUon + VT6h1DYlJ9YAqgqNpedgNR9kpp034SMhB7dj9leB6LRMA+c1fG/T+1lDbkA+vope + pt4+30oDcCTYfEifl1HwqNw/bXDm1wIDAQABAoICABPbd/UYaAQVUk93yQbUKe81 + s9CvbvzTMYUhm9e02Hyszitz/D2gqZHDksvMkFA8u8aylXIGwdZfRglUmV/ZG1kk + kLzQ0xbvN/ilNUL9uYsETBMqtPly9YZloHnUNa5NqF+UVGJGk7GWz5WaLANybx3V + fTzDbfLl3TkVy0vt9UQbUkUfXyzwZNjXwmgIr8rcY9vasP90a3eXqRX3Tw1Wk6A4 + TzO8oB994O0WBO150Fc6Lhwvc72yzddENlLDXq8UAXtqq9mmGqJKnhZ+1mo3AkMw + q7P1JyCIxcAMm26GtRvLVljXV0x5640kxDrCin6jeeW/qWkJEW6dpmuZjR5scmLI + /9n8H+fGzdZH8bOPPotMy12doj3vJqvew3p0eIkmVctYMJKD0j/CWjvKJNE3Yx4O + Ls47X/dEypX6anR1HQUXcpd6JfRWdIJANo2Duaz+HYbyA88bHcJL9shFYcjLs3sX + R/TvnnKHvw/ud7XBgvLGwGAf/cDEuLI2tv+V7tkMGrMUv+gUJNZaJaCpdt+1iUwO + QFq8APyBNn6FFw54TwXWfSjfSNh3geIMLHuErYVu9MIXvB7Yhh+ZvLcfLbmckhAX + wb39RRHnCWvnw5Bm9hnsDhqfDsIoP+2wvUkViyHOmrKi8nSJhSk19C8AuQtSVcJg + 5op+epEmjt70GHt52nuBAoIBAQD2a4Ftp4QxWE2d6oAFI6WPrX7nAwI5/ezCbO/h + yoYAn6ucTVnn5/5ITJ8V4WTWZ4lkoZP3YSJiCyBhs8fN63J+RaJ/bFRblHDns1HA + 2nlMVdNLg6uOfjgUJ8Y6xVM0J2dcFtwIFyK5pfZ7loxMZfvuovg74vDOi2vnO3dO + 16DP3zUx6B/yIt57CYn8NWTq+MO2bzKUnczUQRx0yEzPOfOmVbcqGP8f7WEdDWXm + 7scjjN53OPyKzLOVEhOMsUhIMBMO25I9ZpcVkyj3/nj+fFLf/XjOTM00M/S/KnOj + RwaWffx6mSYS66qNc5JSsojhIiYyiGVEWIznBpNWDU35y/uXAoIBAQDKLj0dyig2 + kj1r3HvdgK4sRULqBQFMqE9ylxDmpJxAj6/A8hJ0RCBR57vnIIZMzK4+6K0l3VBJ + ukzXJHJLPkZ0Uuo2zLuRLkyjBECH6KYznyTkUVRn50Oq6IoP6WTCfd3Eg+7AKYY1 + VFo2iR8sxeSQQ+AylFy6QcQ1xPIW30Jj1/LFjrRdRggapPEekpJec0pEqhasT8rR + UFhRL2NdZnL5b7ZlsJc7gZKEJgNfxgzaCzloqLcjCgGpOhLKx0fFsNOqHcbIGMwG + 6wQCOyNghQJ6AZtRD5TYCJow92FchWjoTIaMJ8RjMKQmxpiwM6wQG4J78Hd3mbhf + q0hiQhPHaNbBAoIBAFeIeMFq8BpXM7sUwcURlI4lIx8Mgo33FVM7PzsFpfQyw9MR + 5w3p6vnjvd8X4aoHvVZxzw3hA0WwjiAmrKMJL/KK6d45rP2bDUBBAplvAgeLtTLt + 4tMLIwCF4HSgA55TIPQlaqO1FDC+M4BTSiMZVxS970/WnZPBEuNgzFDFZ+pvb4X6 + 3t40ZLNwAAQHM4IEPAFiHqWMKGZ9eo5BWIeEHnjHmfjqSDYfLJAVYk1WJIcMUzom + lA76CBC8CxW/I94AtcRhWuFUv/Z5/+OYEYLUxtuqPm+J+JrCmf4OJmWppT1wI2+p + V00BSeRVWXTm1piieM8ahF5y1hp6y3uV3k0NmKECggEBAMC42Ms3s6NpPSE+99eJ + 3P0YPJOkl7uByNGbTKH+kW89SDRsy8iGVCSe9892gm5cwU/4LWyljO3qp2qBNG2i + /DfP/bCk8bqPXsAZwoWK8DrO3bTCDepJWYhlx40pVkHLBwVXGdOVAXh+YswPY2cj + cB9QhDrSj52AKU9z36yLvtY7uBA3Wph6tCjpx2n0H4/m6AmR9LDmEpf5tWYV/OrA + SKKaqUw/y7kOZyKOtbKqr/98qYmpIYFF/ZVZZSZkVXcNeoZzgdOlR37ksVqLEsrj + nxu7wli/uItBj/FTLjyqcvjUUYDyO1KtwBuyPUPgzYhBIN2Rt9+K6WRQelwnToFL + 30ECggEBALzozykZj2sr3z8tQQRZuXLGotUFGsQCB8ikeqoeB8FbNNkC+qgflQGv + zLRB2KWOvnboc94wVgBJH43xG0HBibZnBhUO8/HBI/WlmyEj9KQ/ZskUK4GVZkB6 + r/81ASLwH+P/rqrLEjcp1SIPPevjzCWD9VYR5m/qPHLNxStwGSrPjtPzgaFxhq84 + Jl+YVmNqVlrOKYYfIPh8exPLiTti3wfM61pVYFv56PI2gd5ysMWYnuN+vK0sbmZh + cIWwykcKlODIngI7IzYqt8NuIJI0jrYyHgtUw4jaJzdF4mEOplGONxdz15jAGHtg + JUsBXFNz132nP4iIr3UKrPedQZijSi4= + -----END PRIVATE KEY----- + ''; + ca.cert = builtins.toFile "ca.cert" '' + -----BEGIN CERTIFICATE----- + MIIFDzCCAvegAwIBAgIUTRDYSWJvmlhwIR3pzVrIQfnboLEwDQYJKoZIhvcNAQEL + BQAwFjEUMBIGA1UEAwwLU25ha2VvaWwgQ0EwIBcNMjAwMzIyMjI1NjE3WhgPMjEy + MDAyMjcyMjU2MTdaMBYxFDASBgNVBAMMC1NuYWtlb2lsIENBMIICIjANBgkqhkiG + 9w0BAQEFAAOCAg8AMIICCgKCAgEAwp1WRhJ+vHs15Q1hOYI7Aj6rrUeJ8cP8LyGw + MQdlZNlnkOhxr7diXa8+anq9iF3e+mLaUHqnfn33rWi7THhU6/l8vcqhKcdgH75h + BHC57olm7FBmc7B8Fu/+JD4ABQcvU8CZ/5PPdpUerkGGtEEt/Ql/OZCzGfy2V1Mg + pSAWviMU9TqL5hqjDq+JxgEkUD+nf558p+NSp2rf3bacmmuc4H2MAOmFulMXMsLw + UqTDlzNP/NgRWRsgC7d50Pn35q7SnLzI7DSwRWrJVfs2DtTzGy20wlvEpiU44c7m + qV7QR67r6HtuNd911pb3D9cpWk8dGpcIkFia9VxDlFixBU4Dk3kcolQ8Q7p6c/u6 + BnMM3HThvJwoHesNGmGhAs9c70txWE0M/jIqphJMerOG49VPdCNJMFCW3lo46xju + 2PUKuItfRmnw4O3ADJ/XhwojWzaz4+rf4H2lWwq25Jt2VY5Ly00Npm9pb+/rVYou + Odtu4jT7jUupcdhVZa/w14kazlX4IT76DsVH186ICYWS1eGZIvsnGxkGucPGDA2A + r+HNPHSgOwGrVYTUutHNc4EDY7lhdwb6HEskshlKJ1U+odQ2JSfWAKoKjaXnYDUf + ZKadN+EjIQe3Y/ZXgei0TAPnNXxv0/tZQ25APr6KXqbePt9KA3Ak2HxIn5dR8Kjc + P21w5tcCAwEAAaNTMFEwHQYDVR0OBBYEFCIoeYSYjtMiPrmxfHmcrsZkyTpvMB8G + A1UdIwQYMBaAFCIoeYSYjtMiPrmxfHmcrsZkyTpvMA8GA1UdEwEB/wQFMAMBAf8w + DQYJKoZIhvcNAQELBQADggIBAHPdwOgAxyhIhbqFObNftW8K3sptorB/Fj6jwYCm + mHleFueqQnjTHMWsflOjREvQp1M307FWooGj+KQkjwvAyDc/Hmy7WgJxBg9p3vc+ + /Xf/e7ZfBl8rv7vH8VXW/BC1vVsILdFncrgTrP8/4psV50/cl1F4+nPBiekvvxwZ + k+R7SgeSvcWT7YlOG8tm1M3al4F4mWzSRkYjkrXmwRCKAiya9xcGSt0Bob+LoM/O + mpDGV/PMC1WAoDc1mMuXN2hSc0n68xMcuFs+dj/nQYn8uL5pzOxpX9560ynKyLDv + yOzQlM2VuZ7H2hSIeYOFgrtHJJwhDtzjmUNDQpQdp9Fx+LONQTS1VLCTXND2i/3F + 10X6PkdnLEn09RiPt5qy20pQkICxoEydmlwpFs32musYfJPdBPkZqZWrwINBv2Wb + HfOmEB4xUvXufZ5Ju5icgggBkyNA3PCLo0GZFRrMtvA7i9IXOcXNR+njhKa9246V + QQfeWiz05RmIvgShJYVsnZWtael8ni366d+UXypBYncohimyNlAD1n+Bh3z0PvBB + +FK4JgOSeouM4SuBHdwmlZ/H0mvfUG81Y8Jbrw0yuRHtuCtX5HpN5GKpZPHDE7aQ + fEShVB/GElC3n3DvgK9OJBeVVhYQgUEfJi4rsSxt3cdEI0NrdckUoZbApWVJ3CBc + F8Y7 + -----END CERTIFICATE----- + ''; + "acme.test".key = builtins.toFile "acme.test.key" '' + -----BEGIN RSA PRIVATE KEY----- + MIIJKAIBAAKCAgEAlgQTZjKfs3aHw0J993k7jFAs+hVRPf//zHMAiUkPKUYPTSl1 + TxS/bPbhWzSoom00j4SLhGGGhbd+lnvTg0uvKbxskgATfw5clbm1ZN+gx4DuxwjL + V3xIxpeSY+PKzs5z8w/k+AJh+zOPyXwH3ut3C+ogp1S/5IhmzV3a/yU/6k0zpGxj + N6ZPRTXFrz93I1pPeCkJz90l7tj+2uFc9xtM20NQX52f0Y2oShcG8fKdNZVzuHHk + ZXkrZIhou55/nRy2jKgFeD3GQQfa9rwPWrVybQ6tKMMkoazB/Unky9xcTI2LJarf + xgHDO9v9yFBvmR4UM8B3kM82NHoENtHaZ2mmiMGZzTEQlf8xwYyHFrqBFIVRWEUr + 7rr/O5Qr9gIN0T4u367HCexVYAKzbO2P9h75czzjMMoGkbXze9SMQ/ikrxEmwAHg + r1Xxh6iQYmgPNk8AR3d9+o2I7WJZMUYZARLnuhVr9BNXv510iqZTqX8lcyL5fEj3 + ST4Ab+H7rfevZt6NU26iJLBYAjrA2mSvH+wvkboxrgSS8xYPkOW8NLNEbbodzofI + pB+SaK53OIk0bj9c1YAgrSNER/TDTgDXrWUNrlfVZ/M7+AEdeU06wi7sVhVif6OB + D3OpgKSNjeE6TuJH80Pi5MWugSFBr792Xb6uhVoPiVOFN+qiGB6UkwBgSKkCAwEA + AQKCAgAmN7OZfZwh5DiCDhZ5TXFWNba/n16rJOTN+R5R20L5iNetGLrCAs8hu2N+ + ENRFTPzu8x14BEB5IF4niDRCZq2hPFeMemh9HfOIUV9c63vSV459NkhXaVpA/axV + tlqchQwVCB+U70Z28JPZCLgYmnQhnOvktTqNxhIqj5aTGbJGxpQ5d0Nvkfbv8tsB + 4nE/mGpWel39jqFzT+Tdbjx414Ok+GkpcsacZDJTbbpfOSfD1uc8PgepskzTt8y2 + v5JTPFVlUAjUsSgouQ+XfCGNQlx8XBjRIaXbal+hX4niRald91FTr0yC7UAHp+vn + dFZ586fB526OfbuZctxP+vZhEhFSseQKxHQ0tB8me81xH44daVNr9PPUM69FDT3j + ygJaUJjNEG3vVzePCDzhmxTmz2/rAClp77WTWziBWDoA6YWDDzhgNPrXWzLIbZIx + ue9ZbGEOh/u5ZzrEXxKCz9FjDe9wQu3TeYUe0M+ejzwWgn7zdWDvjjmtLUUuun2Y + wW7WANpu32qvB/V+qssw4O63tbRiwneRCnb8AF2ixgyWr6xyZwch4kacv1KMiixf + gO/5GTj7ba5GcdGoktJb29cUEgz13yPd106RsHK4vcggFxfMbOVauNRIo6ddLwyS + 8UMxLf2i2cToOLkHZrIb8FgimmzRoBd3yYzwVJBydiVcsrHQAQKCAQEAxlzFYCiQ + hjEtblGnrkOC7Hx6HvqMelViOkGN8Y9VczG4GhwntmSE2nbpaAKhFBGdLfuSI3tJ + Lf24f0IGgAhzPmpo2TjbxPO3YSKFTH71fznVBhtQ1iSxwZ1InXktnuhot6VSDx6A + sbHSy1hMFy3nj+Zj5+fQ89tclzBzG9bCShaauO39KrPMwKi6CYoYdGhXBC3+OpHY + zBNvmDTxG2kW8L42rlf14EH4pAlgKs4eeZbpcbZ6fXURP2hToHJ8swyKw/1p12WA + cc19BKFJXL8nNP4uCf/fI0mVYpytz5KwUzG+z+umDqk+RRCH4mNB28xvEEuEyp/e + /C5Is+WrlDAA6QKCAQEAwZsK4AJ/w4Xf4Q/SsnZJO9bfP1ejJjzKElt8rG28JXeb + +FjykZZ6vw2gt2Boest2n9N4fBwaRkaHVtVS4iAmaDXozTlcvCLs2rVjPSguuQtW + 80CKg6+dux+6gFN8IGzDCiX3pWUnhhiXvCcRYEcvgpH6GA5vuCNrXrjH0JFC0kef + aaDMGMTbzhc2IIRztmWU4v8YJSSy5KOkIQLWO+7u9aGx9IqT5/z3gx3XrItyl0Bk + aQmZEh7JOSyhmGhhf5LdeTLu2YgRw3/tzS+lPMX3+UPw99k9MdTOFn2pww5AdRmg + aBIzV+/LBYG0pPRl0D8/6yzGVBPuUDQpmK9Z3gsxwQKCAQEAnNkMZN2Ocd1+6+V7 + LmtJog9HTSmWXMEZG7FsOJ661Yxx44txx2IyPsCaDNlPXxwSaiKrSo0Yr1oZQd8G + XsTPw4HGiETSWijQTulJ99PH8SLck6iTwdBgEhV5LrN75FQnQVdizHu1DUzrvkiC + Wi29FWb6howiCEDjNNVln5SwKn83NpVQgyyK8ag4+oQMlDdQ3wgzJ0Ld53hS3Eq4 + f5EYR6JQgIki7YGcxrB3L0GujTxMONMuhfdEfRvUTGFawwVe0FyYDW7AIrx2Z2vV + I5YuvVNjOhrt6OwtSD1VnnWCITaLh8LwmlUu3NOWbudHUzKSe5MLXGEPo95BNKad + hl5yyQKCAQBNo0gMJtRnawMpdLfwewDJL1SdSR6S0ePS0r8/Qk4l1D5GrByyB183 + yFY/0zhyra7nTt1NH9PlhJj3WFqBdZURSzUNP0iR5YuH9R9Twg5ihEqdB6/EOSOO + i521okTvl83q/ui9ecAMxUXr3NrZ+hHyUWmyRe/FLub6uCzg1a+vNauWpzXRZPgk + QCijh5oDdd7r3JIpKvtWNs01s7aHmDxZYjtDrmK7sDTtboUzm0QbpWXevUuV+aSF + +gDfZlRa3WFVHfisYSWGeYG6O7YOlfDoE7fJHGOu3QC8Ai6Wmtt8Wgd6VHokdHO8 + xJPVZnCBvyt5up3Zz5hMr25S3VazdVfBAoIBAHVteqTGqWpKFxekGwR0RqE30wmN + iIEwFhgOZ8sQ+6ViZJZUR4Nn2fchn2jVwF8V8J1GrJbTknqzAwdXtO3FbgfmmyF2 + 9VbS/GgomXhA9vJkM4KK3Iwo/y/nE9hRhtzuVE0QPudz2fyfaDgnWjcNM59064tH + 88361LVJm3ixyWSBD41UZ7NgWWJX1y2f073vErsfcPpavF5lhn1oSkQnOlgMJsnl + 24qeuzAgTWu/2rFpIA2EK30Bgvsl3pjJxHwyNDAgklV7C783LIoAHi7VO7tzZ6iF + dmD5XLfcUZc3eaB7XehNQKBXDGLJeI5AFmjsHka5GUoitkU2PFrg/3+nJmg= + -----END RSA PRIVATE KEY----- + ''; + "acme.test".cert = builtins.toFile "acme.test.cert" '' + -----BEGIN CERTIFICATE----- + MIIEoTCCAokCAgKaMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNVBAMMC1NuYWtlb2ls + IENBMCAXDTIwMDMyMjIyNTYxOFoYDzIxMjAwMjI3MjI1NjE4WjAUMRIwEAYDVQQD + DAlhY21lLnRlc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCWBBNm + Mp+zdofDQn33eTuMUCz6FVE9///McwCJSQ8pRg9NKXVPFL9s9uFbNKiibTSPhIuE + YYaFt36We9ODS68pvGySABN/DlyVubVk36DHgO7HCMtXfEjGl5Jj48rOznPzD+T4 + AmH7M4/JfAfe63cL6iCnVL/kiGbNXdr/JT/qTTOkbGM3pk9FNcWvP3cjWk94KQnP + 3SXu2P7a4Vz3G0zbQ1BfnZ/RjahKFwbx8p01lXO4ceRleStkiGi7nn+dHLaMqAV4 + PcZBB9r2vA9atXJtDq0owyShrMH9SeTL3FxMjYslqt/GAcM72/3IUG+ZHhQzwHeQ + zzY0egQ20dpnaaaIwZnNMRCV/zHBjIcWuoEUhVFYRSvuuv87lCv2Ag3RPi7frscJ + 7FVgArNs7Y/2HvlzPOMwygaRtfN71IxD+KSvESbAAeCvVfGHqJBiaA82TwBHd336 + jYjtYlkxRhkBEue6FWv0E1e/nXSKplOpfyVzIvl8SPdJPgBv4fut969m3o1TbqIk + sFgCOsDaZK8f7C+RujGuBJLzFg+Q5bw0s0Rtuh3Oh8ikH5Jornc4iTRuP1zVgCCt + I0RH9MNOANetZQ2uV9Vn8zv4AR15TTrCLuxWFWJ/o4EPc6mApI2N4TpO4kfzQ+Lk + xa6BIUGvv3Zdvq6FWg+JU4U36qIYHpSTAGBIqQIDAQABMA0GCSqGSIb3DQEBCwUA + A4ICAQBCDs0V4z00Ze6Ask3qDOLAPo4k85QCfItlRZmwl2XbPZq7kbe13MqF2wxx + yiLalm6veK+ehU9MYN104hJZnuce5iEcZurk+8A+Pwn1Ifz+oWKVbUtUP3uV8Sm3 + chktJ2H1bebXtNJE5TwvdHiUkXU9ywQt2FkxiTSl6+eac7JKEQ8lVN/o6uYxF5ds + +oIZplb7bv2XxsRCzq55F2tJX7fIzqXrSa+lQTnfLGmDVMAQX4TRB/lx0Gqd1a9y + qGfFnZ7xVyW97f6PiL8MoxPfd2I2JzrzGyP/igNbFOW0ho1OwfxVmvZeS7fQSc5e + +qu+nwnFfl0S4cHRif3G3zmz8Ryx9LM5TYkH41qePIHxoEO2sV0DgWJvbSjysV2S + EU2a31dJ0aZ+z6YtZVpHlujKMVzxVTrqj74trS4LvU5h/9hv7e1gjYdox1TO0HMK + mtDfgBevB21Tvxpz67Ijf31HvfTmCerKJEOjGnbYmyYpMeMNSONRDcToWk8sUwvi + OWa5jlUFRAxgXNM09vCTPi9aRUhcFqACqfAd6I1NqGVlfplLWrc7SWaSa+PsLfBf + 4EOZfk8iEKBVeYXNjg+CcD8j8yk/oEs816/jpihIk8haCDRWYWGKyyGnwn6OQb8d + MdRO2b7Oi/AAmEF3jMlICqv286GIYK5qTKk2/CKHlOLPnsWEuA== + -----END CERTIFICATE----- + ''; +} diff --git a/nixos/modules/services/x11/display-managers/auto.nix b/nixos/tests/common/auto.nix index 1068a344e0cfb..2c21a8d516737 100644 --- a/nixos/modules/services/x11/display-managers/auto.nix +++ b/nixos/tests/common/auto.nix @@ -5,7 +5,7 @@ with lib; let dmcfg = config.services.xserver.displayManager; - cfg = dmcfg.auto; + cfg = config.test-support.displayManager.auto; in @@ -15,7 +15,7 @@ in options = { - services.xserver.displayManager.auto = { + test-support.displayManager.auto = { enable = mkOption { default = false; diff --git a/nixos/tests/common/ec2.nix b/nixos/tests/common/ec2.nix index 1e69b63191a70..ba087bb600905 100644 --- a/nixos/tests/common/ec2.nix +++ b/nixos/tests/common/ec2.nix @@ -25,7 +25,7 @@ with pkgs.lib; my $imageDir = ($ENV{'TMPDIR'} // "/tmp") . "/vm-state-machine"; mkdir $imageDir, 0700; my $diskImage = "$imageDir/machine.qcow2"; - system("qemu-img create -f qcow2 -o backing_file=${image}/nixos.qcow2 $diskImage") == 0 or die; + system("qemu-img create -f qcow2 -o backing_file=${image} $diskImage") == 0 or die; system("qemu-img resize $diskImage 10G") == 0 or die; # Note: we use net=169.0.0.0/8 rather than @@ -35,7 +35,7 @@ with pkgs.lib; # again when it deletes link-local addresses.) Ideally we'd # turn off the DHCP server, but qemu does not have an option # to do that. - my $startCommand = "qemu-kvm -m 768"; + my $startCommand = "qemu-kvm -m 1024"; $startCommand .= " -device virtio-net-pci,netdev=vlan0"; $startCommand .= " -netdev 'user,id=vlan0,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${pkgs.micro-httpd}/bin/micro_httpd ${metaData}'"; $startCommand .= " -drive file=$diskImage,if=virtio,werror=report"; diff --git a/nixos/tests/common/letsencrypt/common.nix b/nixos/tests/common/letsencrypt/common.nix deleted file mode 100644 index c530de817bf2d..0000000000000 --- a/nixos/tests/common/letsencrypt/common.nix +++ /dev/null @@ -1,9 +0,0 @@ -{ lib, nodes, pkgs, ... }: let - letsencrypt-ca = nodes.letsencrypt.config.test-support.letsencrypt.caCert; -in { - networking.nameservers = [ - nodes.letsencrypt.config.networking.primaryIPAddress - ]; - - security.pki.certificateFiles = [ letsencrypt-ca ]; -} diff --git a/nixos/tests/common/letsencrypt/snakeoil-certs.nix b/nixos/tests/common/letsencrypt/snakeoil-certs.nix deleted file mode 100644 index ca4f71ae688a4..0000000000000 --- a/nixos/tests/common/letsencrypt/snakeoil-certs.nix +++ /dev/null @@ -1,254 +0,0 @@ -# Generated via mkcert.sh in the same directory. -{ - ca.key = builtins.toFile "ca.key" '' - -----BEGIN PRIVATE KEY----- - MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDQ0b23I1srJZwR - 2MMdvSJK5pcwLfrXU+4gEZEnWNyT8yeVweya+8vmNNOlvK3zxf+ZiY/7aQ0RZJMO - h2+VdlgHmr2QKhQTf1HwfZA/06FolD3/DcS+DMJMSTVr179/XLndeVVZUqU7tjvB - AWKSIS8H2hSF1UOPi9gBDR8MwCP6Qgj8WYhbkt9q47/lO96qAmm6U1F+Q7RYM9ZQ - IWI81N0Ms5wJocg7n6S19iV66ePh7APapZFYup61gFGWfahmA217ELIZd56n8yjO - F0epb9sC0XpYCDRrYKBWLqPiv+6wvdZtZvALItyIv08ZwXlBkFg3LbAAhPnf0Vxz - pYysQmyyyzkgy252n+Sie0kx+B4qm6fOkpfgYlPSVTb2dXx/be/SE08u0a9FO0fZ - pkByWEZJUUwngsJgLUa7MorQf3avxozfC25XqvzbieZfSXlA7mOUclZbC/WUFpyj - MlyJU2eCQ8wSwsPXl91oxcYlOkuVLgd41gr9pGXQSuKIkrgbfkftjg2tDC+7g7O8 - qrdF42FjbZjIx/74AasmsGh4GTQtiSkvEnTstioC6aCV44DlJWbBIMvkyawubjUl - Ppij0H66Y9Q4tEc/ktc7oGQfqqluyLb43TeobTPHALsNeAYb39rMtBo5DDCUc81s - fuDMhMr/oYXKrFstUsg5AY6mJaRG0QIDAQABAoICAF5ZVfmoPOoKzTB3GvmV2iez - dj4rmDmwT1gn98iqasdiRtFwVGJWQHNcDQDGdmY9YNZThD2Y4nGoWpVm9jC2zuFo - thusF3QTw8cARKvCCBzDVhumce1YwHVNYpi+W2TFValOyBRathN7rBXxdUMHQUOv - 8jPh/uudyNP4xL2zFs5dBchW/7g4bT/TdYGyglGYU4L/YEPHfXWYvk1oOAW6O8Ig - aPElKt5drEMW2yplATSzua4RvtEzSMBDIRn43pxxEgdXrNC67nF9+ULc2+Efi/oD - Ad9CncSiXO9zlVK/W655p6e4qd6uOqyCm8/MTegkuub7eplRe8D3zGjoNN4kCQ4S - rckVvIDDb6vZk7PKx9F7GWIqaG/YvFFFKO1MrAZg7SguFA6PtGOYAFocT03P6KXT - l2SnZQWKyxUAlh4tOBGlRFgGCx/krRIKbgNYn/qk/ezcRl8c7GpOPh+b7Icoq7u3 - l4tIVBBHqS8uGgtyi+YwuJeht2MV1aEcSkykKLh2ipp8tb6spORJUkhjawDjvxeQ - GztN30Xh2riTXYZ0HExVTtJa8jyvFyp/97ptPIJXaVt2A2KIS3sBFHKnpY+/OrQg - uUauYgi13WFHsKOxZL9GYGk7Ujd8bw4CEcJFxKY7bhpGVI6Du7NRkUDWN0+0yusI - 2szCJ7+ZqJkrc1+GrI/RAoIBAQDseAEggOLYZkpU2Pht15ZbxjM9ayT2ANq1+RTu - LjJx4gv2/o/XJCfMZCL0b9TJqtYeH+N6G9oDRJ99VIhUPedhWSYdj9Qj+rPd++TS - bp+MoSjmfUfxLTDrmFHL7ppquAE65aDy3B5c+OCb0I4X6CILUf0LynBzgl4kdrzN - U6BG3Mt0RiGPojlPV82B9ZUF/09YAz7BIz9X3KMhze1Gps5OeGuUnc9O2IAJYkrj - ur9H2YlNS4w+IjRLAXSXUqC8bqPZp6WTo1G/rlyAkIRXCGN90uk5JQvXoj9immFO - WaylbdcNG3YcGutreYeZL/UIWF6zCdc6pYG0cCBJS6S/RN7FAoIBAQDiERrLuUbV - 3fx/a8uMeZop6hXtQpF7jlFxqUmza7QSvBuwks4QVJF+qMSiSvKDkCKqZD4qVf4N - TMxEj5vNR0PbnmDshyKJNGVjEauKJSb65CFDUcL1eR/A/oJvxiIdN1Z4cPrpnRux - /zIfPuYfYHpdz52buxxmlD7bfwYmVKVpnzjB9z0I1CasZ5uqB0Z8H0OLyUu8S4ju - RfkKBDMgVl2q96i8ZvX4C1b7XuimIUqv4WHq5+ejcYirgrYtUbBIaDU3/LORcJdy - /K76L1/up70RTDUYYm/HKaRy+vMTpUsZJ7Qbh0hrvQkUvNQ1HXjprW2AePIYi33N - h3mb1ulqw4idAoIBAQCsn0YjVjNDShkFK4bfmLv4rw2Ezoyi0SjYIsb2wN6uaBfX - 7SlQIuKywH8L9f9eYMoCH8FNyLs0G4paUbVb2fzpAc1jUzXINiHL8TCvtXXfkV5s - NBSqqRTHR+CegMZVFZJATpVZ9PptYHmHBY5VQW5o2SdizhudFxRmhg95zIx6boBP - l0q0sfYoR66MKpzpTeG8HFJZZ8O7/iNQcCXAp9B/VEUkrrdBlaaSMyD8cb1lVBZ5 - SKdOTGXkQ2G7feQ86n/OSiYDSvxIc56vc9BIQKVwmuEKiFLGzXh8ILrcGXaBJVgS - B3QHPFeTk5o7Z9j2iJxJEuv9sginkhrfpsrTnhEJAoIBACkrUkTtjd/e2F/gIqaH - crLVZX7a06G7rktTuA9LuvR6e1Rxt8Mzk3eMhprDqVyaQCXlsYiGNoj3hm+p84az - xsDVG/OXPIveFeSv0ByNXYbtSr12w1lu4ICGGP0ACTBm5oFymc83hFarEdas3r2y - FTbGW36D2c04jCXvARCz85fDnlN8kgnskMpu5+NUBdsO2n83fmphGyPBbHQNhb4K - 3G4JQhplab/tWL7YbufqQi67jdh4uS+Duo75c/HW4ZKeH6r9gzomVf5j0/3N6NuO - gpkG1tiE/LQ5ejBSUTgvrvh6yYsF3QN53pB/PuoZXu63Xay62ePsa1GlrVjbD5EY - 4OUCggEAJFr7F7AQLMJTAxHFLCsZZ0ZZ+tXYclBC4eHPkZ6sD5jvL3KIpW3Q7jXk - oIoD/XEX4B+Qe5M3jQJ/Y5ZJETHcgfcHZbDpCKN2WHQgldQbAJiFd4GY1OegdVsr - 7TC8jh3Q2eYjzL8u4z7LSNI6aQSv1eWE7S1Q5j/sX/YYDR4W3CBMeIUpqoDWpn87 - czbIRyA/4L0Y/HLpg/ZCbvtJZbsQwYXhyqfbjlm4BRQ6JiC5uEBKvuDRUXToBJta - JU8XMm+Ae5Ogrw7P6hg68dWpagfjb7UZ7Zxv+VDsbrU6KsDcyGCAwrrRZou/6KUG - Eq4OVTSu/s8gmY94tgbjeOaLUPEPmg== - -----END PRIVATE KEY----- - ''; - ca.cert = builtins.toFile "ca.cert" '' - -----BEGIN CERTIFICATE----- - MIIFDzCCAvegAwIBAgIUU9rbCLTuvaI6gjSsFsJJjfLWIX8wDQYJKoZIhvcNAQEL - BQAwFjEUMBIGA1UEAwwLU25ha2VvaWwgQ0EwIBcNMTkxMDE4MDc1NDEyWhgPMjEx - OTA5MjQwNzU0MTJaMBYxFDASBgNVBAMMC1NuYWtlb2lsIENBMIICIjANBgkqhkiG - 9w0BAQEFAAOCAg8AMIICCgKCAgEA0NG9tyNbKyWcEdjDHb0iSuaXMC3611PuIBGR - J1jck/MnlcHsmvvL5jTTpbyt88X/mYmP+2kNEWSTDodvlXZYB5q9kCoUE39R8H2Q - P9OhaJQ9/w3EvgzCTEk1a9e/f1y53XlVWVKlO7Y7wQFikiEvB9oUhdVDj4vYAQ0f - DMAj+kII/FmIW5LfauO/5TveqgJpulNRfkO0WDPWUCFiPNTdDLOcCaHIO5+ktfYl - eunj4ewD2qWRWLqetYBRln2oZgNtexCyGXeep/MozhdHqW/bAtF6WAg0a2CgVi6j - 4r/usL3WbWbwCyLciL9PGcF5QZBYNy2wAIT539Fcc6WMrEJssss5IMtudp/kontJ - MfgeKpunzpKX4GJT0lU29nV8f23v0hNPLtGvRTtH2aZAclhGSVFMJ4LCYC1GuzKK - 0H92r8aM3wtuV6r824nmX0l5QO5jlHJWWwv1lBacozJciVNngkPMEsLD15fdaMXG - JTpLlS4HeNYK/aRl0EriiJK4G35H7Y4NrQwvu4OzvKq3ReNhY22YyMf++AGrJrBo - eBk0LYkpLxJ07LYqAumgleOA5SVmwSDL5MmsLm41JT6Yo9B+umPUOLRHP5LXO6Bk - H6qpbsi2+N03qG0zxwC7DXgGG9/azLQaOQwwlHPNbH7gzITK/6GFyqxbLVLIOQGO - piWkRtECAwEAAaNTMFEwHQYDVR0OBBYEFAZcEiVphGxBT4OWXbM6lKu96dvbMB8G - A1UdIwQYMBaAFAZcEiVphGxBT4OWXbM6lKu96dvbMA8GA1UdEwEB/wQFMAMBAf8w - DQYJKoZIhvcNAQELBQADggIBAGJ5Jnxq1IQ++IRYxCE7r7BqzzF+HTx0EWKkSOmt - eSPqeOdhC26hJlclgGZXAF/Xosmn8vkSQMHhj/jr4HI0VF9IyvDUJm8AKsnOgu/7 - DUey3lEUdOtJpTG9NyTOcrzxToMJ+hWlFLZKxx2dk4FLIvTLjmo1VHM97Bat7XYW - IrL9RRIZ25V+eCYtlR7XYjceGFQ0rCdp8SFIQwC6C/AH2tV3b1AJFsND9PcoLu7c - //fH+WUQCcD/N0grdC/QCX7AFWzd4rKQ8gjfND4TSYFTSDwW10Mud4kAVhY2P1sY - Y3ZpnxWrCHbIZMbszlbMyD+cjsCBnNvOtYGm7pDut/371rllVcB/uOWYWMCtKPoj - 0elPrwNMrK+P+wceNBCRQO+9gwzB589F2morFTtsob/qtpAygW8Sfl8M+iLWXeYS - c3LBLnj0TpgXKRWg7wgIWKSZx9v6pgy70U0qvkjNS1XseUCPf7hfAbxT3xF+37Dw - zZRwF4WAWqdnJoOey21mgc+a2DQzqtykA6KfHgCqNFfDbQXPXvNy25DDThbk+paX - G2M2EWtr+Nv9s/zm7Xv/pOXlgMFavaj+ikqZ4wfJf6c/sMOdZJtMA4TsYtAJgbc8 - ts+0eymTq4v5S8/fW51Lbjw6hc1Kcm8k7NbHSi9sEjBfxFLTZNQ5eb4NGr9Od3sU - kgwJ - -----END CERTIFICATE----- - ''; - "acme-v02.api.letsencrypt.org".key = builtins.toFile "acme-v02.api.letsencrypt.org.key" '' - -----BEGIN RSA PRIVATE KEY----- - MIIJKQIBAAKCAgEApny0WhfDwEXe6WDTCw8qBuMAPDr88pj6kbhQWfzAW2c0TggJ - Etjs9dktENeTpSl14nnLVMiSYIJPYY3KbOIFQH1qDaOuQ7NaOhj9CdMTm5r9bl+C - YAyqLIMQ9AAZDhUcQjOy3moiL7ClFHlkFYuEzZBO9DF7hJpfUFIs0Idg50mNoZh/ - K/fb4P2skNjfCjjomTRUmZHxT6G00ImSTtSaYbN/WHut1xXwJvOoT1nlEA/PghKm - JJ9ZuRMSddUJmjL+sT09L8LVkK8CKeHi4r58DHM0D0u8owIFV9qsXd5UvZHaNgvQ - 4OAWGukMX+TxRuqkUZkaj84vnNL+ttEMl4jedw0ImzNtCOYehDyTPRkfng5PLWMS - vWbwyP8jDd2578mSbx5BF7ypYX366+vknjIFyZ5WezcC1pscIHxLoEwuhuf+knN+ - kFkLOHeYbqQrU6mxSnu9q0hnNvGUkTP0a/1aLOGRfQ5C/pxpE/Rebi8qfM/OJFd4 - mSxGL93JUTXWAItiIeBnQpIne65/Ska9dWynOEfIb0okdet3kfmNHz3zc17dZ5g4 - AdOSCgHAlQgFt/Qd8W6xXUe4C5Mfv2ctxRrfQhDwtB6rMByPwzImnciC2h3vCwD3 - vS/vjUyWICyhZyi2LZDUQz+sCKBXCYYcYh8ThFO40j5x1OnYMq7XQvyl8QkCAwEA - AQKCAgBSAfdssWwRF9m3p6QNPIj9H3AMOxpB/azffqTFzsSJwYp4LWkayZPfffy+ - 4RGvN38D8e6ActP3ifjEGu3tOGBR5fUJhujeHEiDea+a2Ug9S9kuNwmnelWQ23bM - Wgf9cdSbn4+qEymHyEFolmsAWdsuzri1fHJVXR06GWBNz4GiLA8B3HY4GD1M1Gfe - aZVkGagpXyeVBdiR2xuP5VQWVI8/NQWzdiipW/sRlNABVkyI3uDeN4VzYLL3gTeE - p021kQz4DSxIjHZacHpmWwhBnIbKMy0fo7TlrqcnIWXqTwv63Q9Zs/RN8NOyqb0Y - t1NKFWafcwUsdOnrG9uv/cVwF1FNE8puydaOi8rL1zAeK89JH8NRQ02wohR9w8qy - b2tB6DyGMtuqBt8Il6GA16ZoEuaXeayvlsvDEmG1cS9ZwBvfgrVPAmlm2AYdIf5B - RHIJu4BJC6Nn2ehVLqxx1QDhog3SOnAsCmcfg5g/fCwxcVMLIhODFoiKYGeMitDG - Q4e5JKcOg+RR8PT/n4eY4rUDBGtsR+Nw8S2DWgXmSufyfDtKCjZB4IuLWPS29tNh - zF6iYfoiTWzrSs/yqPSKIFpv+PWZwkKSvjdxia6lSBYYEON4W2QICEtiEs+SvcG4 - 0eIqWM+rRmPnJyMfGqX6GCs3rHDQB2VNJPBCYPQalJ/KwZumAQKCAQEA0ezM6qPJ - 1JM/fddgeQ50h0T9TRXVUTCISxXza+l4NuFt1NdqUOdHsGtbL1JR4GaQUG8qD1/P - R39YgnQEQimxpmYLCZkobkwPxTZm9oiMXpcJrlN4PB5evaWShRSv3mgigpt3Wzml - Td+2R9RoA/hvF/wEyIvaWznYOyugBC7GXs20dNnZDULhUapeQu7r6JvgmxBOby7S - 0FbhGplBiSDETzZURqzH/GMJKaJtNgyyVf3Hbg4mZAQDWoBRr+8HxsNbDkxP6e91 - QrPHy2VZFiaTmJfoxRhyMTn7/JZaLJaUHDOniOsdMj/V7vMCgpfBqh5vR8bKzuPy - ZINggpcFPp1IYQKCAQEAywc7AQoktMBCru/3vzBqUveXbR3RKzNyZCTH5CMm3UNH - zmblFgqF2nxzNil21GqAXzSwZk5FyHbkeD3yvEZm+bXzsZTDNokAwoiTgyrr2tf8 - GLMlCHHl5euIh1xHuyg/oKajVGOoXUXK8piqiDpQKd3Zwc6u2oyQlh+gYTPKh+7i - ilipkYawoE6teb6JUGpvU+d27INgNhB2oDEXY3pG2PbV+wv229ykSZxh1sJUdDwT - a8eTg+3pCGXtOZiJoQTFwKUlD2WYTGqS4Gx6dIJco5k+ZikGNST1JGE64Jl4MZdI - rtyvpcYblh5Q14sJGvp4kWYS9tjEM8pA+4Z9th3JqQKCAQEAkidH0+UM1A9gmQCm - jiHeR39ky5Jz3f7oJT63J15479yrVxBTWNhtNQrJhXzOvGkr+JQsuF+ANMsYmFql - zFqy8KMC9D/JwmD6adeif+o5sHF/r/s1LsYGOAtao4TvnOzrefs7ciwERt+GTSQ4 - 9uq0jgJMYkPcVr9DKI8K7V6ThdW52dECKRVzQiRXVEp7vIsqKUuFECuNYrfaKWai - FhLWGkA9FKee5L0e1/naB1N3ph72Bk2btO6GVzAXr2HADEZe0umWiczJ2xLH+3go - Oh/JiufYi8ClYFh6dDVJutlrbOcZsV3gCegfzikqijmWABcIavSgpsJVNF2zh7gV - Uq62gQKCAQAdO2FHeQpn6/at8WceY/4rC/MFhvGC4tlpidIuCtGhsfo4wZ/iWImF - N73u4nF1jBAHpTJwyHxLrLKgjWrRqOFSutvniZ/BzmAJolh63kcvL0Hg3IpMePm8 - 7PivZJ3/WIAwxU1m7SJkq5PY8ho7mwnHvWWI/hU26l42/z68QBS9FawQd0uS5G2x - 5yIbEU/8ABcfYYhB7XiA0EYEMo1HiWeB/ag5iTN13ILbBmUf4sL+KVgygH3A1RRk - XSiWzluij2lZn22ClgIjnoSfQ38uH0bvVzUgyG9YX4XcQxOTGwWvPjT82FGB8NAw - ARVqs14QQFfzt1qrp/I38rsAfBDFk+xhAoIBAQCEKNk/oJcy9t/jMIbLcn6z3aCc - Fn8GBPSXtFj0t6weN5lHof+cggw4owMFWQQyAXxo/K6NnKNydMPZ5qjtLsHNpbpQ - aT1Or0/1YR1bJ8Lo82B4QM++7F761GWQPvE/tyrfPkfkWl92ITIpmnlw4wycRlkq - 9anI2fnj1nIZwixzE2peb6PcsZU2HOs9uZ5RRd9wia696I7IpNibs4O4J2WTm4va - +NeYif3V2g9qwgT0Va0c9/Jlg3b58R0vA8j/VCU5I0TyXpkB3Xapx+pvEdZ3viUL - mXZaVotmWjgBXGDtd2VQg2ZiAMXHn3RzXSgV4Z+A/XacRs75h9bNw0ZJYrz1 - -----END RSA PRIVATE KEY----- - ''; - "acme-v02.api.letsencrypt.org".cert = builtins.toFile "acme-v02.api.letsencrypt.org.cert" '' - -----BEGIN CERTIFICATE----- - MIIEtDCCApwCAgKaMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNVBAMMC1NuYWtlb2ls - IENBMCAXDTE5MTAxODA3NTQxM1oYDzIxMTkwOTI0MDc1NDEzWjAnMSUwIwYDVQQD - DBxhY21lLXYwMi5hcGkubGV0c2VuY3J5cHQub3JnMIICIjANBgkqhkiG9w0BAQEF - AAOCAg8AMIICCgKCAgEApny0WhfDwEXe6WDTCw8qBuMAPDr88pj6kbhQWfzAW2c0 - TggJEtjs9dktENeTpSl14nnLVMiSYIJPYY3KbOIFQH1qDaOuQ7NaOhj9CdMTm5r9 - bl+CYAyqLIMQ9AAZDhUcQjOy3moiL7ClFHlkFYuEzZBO9DF7hJpfUFIs0Idg50mN - oZh/K/fb4P2skNjfCjjomTRUmZHxT6G00ImSTtSaYbN/WHut1xXwJvOoT1nlEA/P - ghKmJJ9ZuRMSddUJmjL+sT09L8LVkK8CKeHi4r58DHM0D0u8owIFV9qsXd5UvZHa - NgvQ4OAWGukMX+TxRuqkUZkaj84vnNL+ttEMl4jedw0ImzNtCOYehDyTPRkfng5P - LWMSvWbwyP8jDd2578mSbx5BF7ypYX366+vknjIFyZ5WezcC1pscIHxLoEwuhuf+ - knN+kFkLOHeYbqQrU6mxSnu9q0hnNvGUkTP0a/1aLOGRfQ5C/pxpE/Rebi8qfM/O - JFd4mSxGL93JUTXWAItiIeBnQpIne65/Ska9dWynOEfIb0okdet3kfmNHz3zc17d - Z5g4AdOSCgHAlQgFt/Qd8W6xXUe4C5Mfv2ctxRrfQhDwtB6rMByPwzImnciC2h3v - CwD3vS/vjUyWICyhZyi2LZDUQz+sCKBXCYYcYh8ThFO40j5x1OnYMq7XQvyl8QkC - AwEAATANBgkqhkiG9w0BAQsFAAOCAgEAkx0GLPuCvKSLTHxVLh5tP4jxSGG/zN37 - PeZLu3QJTdRdRc8bgeOGXAVEVFbqOLTNTsuY1mvpiv2V6wxR6nns+PIHeLY/UOdc - mOreKPtMU2dWPp3ybec2Jwii6PhAXZJ26AKintmug1psMw7662crR3SCnn85/CvW - 192vhr5gM1PqLBIlbsX0tAqxAwBe1YkxBb9vCq8NVghJlKme49xnwGULMTGs15MW - hIPx6sW93zwrGiTsDImH49ILGF+NcX1AgAq90nG0j/l5zhDgXGJglX+K1xP99X1R - de3I4uoufPa5q+Pjmhy7muL+o4Qt0D0Vm86RqqjTkNPsr7gAJtt66A7TJrYiIoKn - GTIBsgM6egeFLLYQsT0ap/59HJismO2Pjx4Jk/jHOkC8TJsXQNRq1Km76VMBnuc0 - 2CMoD9pb38GjUUH94D4hJK4Ls/gJMF3ftKUyR8Sr/LjE6qU6Yj+ZpeEQP4kW9ANq - Lv9KSNDQQpRTL4LwGLTGomksLTQEekge7/q4J2TQRZNYJ/mxnrBKRcv9EAMgBMXq - Q+7GHtKDv9tJVlMfG/MRD3CMuuSRiT3OVbvMMkFzsPkqxYAP1CqE/JGvh67TzKI+ - MUfXKehA6TKuxrTVqCtoFIfGaqA9IWyoRTtugYq/xssB9ESeEYGeaM1A9Yueqz+h - KkBZO00jHSE= - -----END CERTIFICATE----- - ''; - "letsencrypt.org".key = builtins.toFile "letsencrypt.org.key" '' - -----BEGIN RSA PRIVATE KEY----- - MIIJKgIBAAKCAgEA9dpdPEyzD3/BBds7tA/51s+WmLFyWuFrq4yMd2R+vi5gvK7n - lLNVKhYgiTmK2Um+UEpGucJqZHcTSZA1Bz4S/8ND/AI9I6EmwvBinY5/PubxEALk - 9YiDA+IzH8ZGFM8wXg7fMbbJAsyv+SHAtr2jmCsggrpuD5fgzs2p+F2q0+oVoeFw - MAOUdAf2jNtNLEj2Q6MiR5Xq+wFOcRtXlNlXWIX3NrmubO/xOpDNpsyjyYC5Ld+W - 06MS5bTHSdv56AkUg2PugMChj15TOddEJIK8zPXFTlMYye9SKwjhNUZovfe4xXCa - Tj2nmzrcuMKLz+S3sKQeTWjiRcY3w4zTlAbhtGXDjXjhMObrHoWM8e3cTL4NJMvt - tNStXficxbeTbIiYu+7dtF0q+iWaZqexc6PdAaIpFZ0XSw+i5iLdQZmBwzY7NLlH - pQupfh6ze0qDUVZAMDubo4JKUTBzH6QTuhHx+uUm7Lc8YdNArn7o/vMZDQym1Eia - xKxZuCGaqFvq8ZK4nBVsHfcXbhF/XD2HMid3t7ImbREVu9qnc+En+acU/SJaaL3r - jMW6HLVMr6+vQrCzYkvLzKYpoUm9D1Kcn6d8Ofxl2iCaY9CkMr5/6J1p1wcTdcN7 - IVQ/DFBeTDauyWbyZkO/lPoZoakWyXOx9S9tgClzhFmNgRkZv9wN+QguNDcCAwEA - AQKCAgEA0ndlacGfaJ1NeN39dmBW2XZMzdrassJXkjx34528gsLhPaXdyobbWXQn - 1lHUc7+VlNaBRXUR73+gm1FAlDqnuRxIjuy7ukyzCh8PzSG3/PlnVPWlXCzJPAHh - EkqCpD3agirpF34LBsKDwxsKB2bBLft9kWxX3DGA2olmAKDvJQs4CaUcjX4DEHHg - tyTmJAsyByUYq3/D8a1koZ9ukpadF8NXpxm+ILQoJqLf6vM1I8N2w7atP/BStSLV - mH0gq2tajEB4ZPCDXmC5jsKiKz9gsXWUu0CX8AdYqE6pvRnRgQ8Ytq1265QMb+8s - FV82oXqDZkyZRFuNmX3fLyDX39kkTcVS37S56Gzk4EzDWE/u2RXCAPeWla2zUFYI - hg8X4ZAwbZRODtK2cZTuCZEILM/iKmtSgHC+aQhp18EUAefa7WGrRD4AvbTxH4VF - ek60bwISBk5Mhf39MwqIiQxGOFmfLsQReZvzH4jI5zfDXf/0yZ/1SdGeu6+Walt0 - V81Ua/DB6zshHpeSP74HMuJHZ4DOQfcV/ndyzvoP84pAjenSx6O034OwQTkpoMI/ - f/2rK8kdzYSL4f//kFMuRLqmAwOmAFYB2oMo0/YaIoQ4vgTHDKTSxj5mbno56GdT - huMAVMKskaCSVbyMB/xyQG7senLItVv+HafVk6ChMUbkIjv9zgECggEBAP+ux1RG - cETGjK2U3CRoHGxR7FwaX6hkSokG+aFdVLer+WUrZmR8Ccvh2ALpm8K1G6TTk/5X - ZeVX4+1VFYDeTHMN8g20usS5mw3v2GF3fGxGLe4q56l4/4kKMZOrSBuWH4niiIKD - 0QogdzWkpQJ93nMbZxZ5lk+lRZVf3qSm6nzyP468ndrfI57Ov5OUIWZ7KhTUH9IK - 8/urUk+lEvyzQmNTlt5ZZXRz7cR01K8chx1zevVAyynzSuGjTysaBN7LTT0v3yVu - 96yKNsxJvuIz2+4qSjhbnN4jH+feN0VsdF3+Qkru0lBmLVgJl4X67XFaAKMDU9yv - 3alS53Pkol+Dy1cCggEBAPYodofHC1ydoOmCvUAq4oJNtyI4iIOY/ch3sxVhkNyi - KBscQqbay/DiXFiNl+NsemzB1PrHzvCaqKcBKw537XzeKqUgYuVLkFGubf9bDhXi - wSRcYbU/oNTgiTgXPW8wH60uIoLaiNi1/YjO2zh4GEY/kFqSuD54Y91iFmcC75bv - OjCNugnRdpRjOFhaeNx75tdverR37w3APVZuBSv3bJlMPCtaf+fEAKxJxeqCs3Oq - rtsw2TQ4TqfE8/w9qPCVv3bQbMbO48SwjxAz47qH2h3qGu3Ov8badeARe+Ou7nuI - U13gPuPOhPXIQP/MYOyamPJdFyng1b8vyNsfjOcWMiECggEAEkMgl6NkV3U7DRbp - 1mvdQ9tiH33+wR9Qt5LY966b43aUHKbJ7Hlzla1u6V5YMsMO02oNUwhZDdWGQShn - ncnC+iDP3iy/flenfIpaETQgnfcxRqan31H2Joqk2eBNCTNi001r5K6XmrqQ6TL2 - WkQ1RFF7vn42vz+VxcKQO4B0lTIUWhSczcpMWAZ6ZocZD6HScqRoFW+U16/39Bpd - TdFb944742vNNFEndXXGzy8hc3gRGz1ihX+MJKuuduyn1mX9AVbPAHR5mkhQ+6x0 - xuFfXxaEMJxSiwdFOyGDHyFM+n2zrHh8ayOxL22X9gjjNspv6zTMo6GoGnUCdSOq - eVoHhwKCAQEAot5O3rOB/vuEljwcv7IgQJrvCsNg/8FgWR1p7kGpuXHJG3btWrz1 - pyH+e9DjqGQD9KWjJ3LAp02NPUJ2nJIZHj9Y8/yjspb2nDTPLt+uSCjKJibBt0ys - O219HRGzYjfzHYCi8PVrCggQAk7rmUdMuF4iQutE4ICDgtz9eZbls3YBiFKdvxVK - Yg/sHflucmPAbtah13prPyvs6ZzN6zNANYXNYdn1OwHieBwvyWRFG8jY/MorTHPd - BwA3drPNbbGHBzQMZNZKub8gSVYr3SU52gUlYCclmIq+50xqLlF2FWIz1q8irVPd - gUnIR/eQQbxgaivRwbGze1ZAjUsozVVQQQKCAQEA9uAKU3O06bEUGj+L0G+7R7r/ - bi2DNi2kLJ7jyq+n0OqcHEQ1zFK4LAPaXY0yMYXieUzhivMGLSNDiubGO2/KxkFF - REXUFgYWZYMwrKsUuscybB64cQDwzD0oXrhvEa2PHecdG6AZ63iLcHaaDzyCPID/ - wtljekLO2jbJ5esXZd016lykFfUd/K4KP1DGyI2Dkq6q0gTc/Y36gDAcPhIWtzna - UujYCe3a8DWCElH4geKXaB5ABbV1eJ8Lch599lXJ9Hszem6QNosFsPaHDCcqLS9H - yy2WA6CY2LVU7kONN+O0kxs2fVbxIkI+d/LZyX/yIGlkXcAzL07llIlrTAYebQ== - -----END RSA PRIVATE KEY----- - ''; - "letsencrypt.org".cert = builtins.toFile "letsencrypt.org.cert" '' - -----BEGIN CERTIFICATE----- - MIIEpzCCAo8CAgKaMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNVBAMMC1NuYWtlb2ls - IENBMCAXDTE5MTAxODA3NTQxNVoYDzIxMTkwOTI0MDc1NDE1WjAaMRgwFgYDVQQD - DA9sZXRzZW5jcnlwdC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC - AQD12l08TLMPf8EF2zu0D/nWz5aYsXJa4WurjIx3ZH6+LmC8rueUs1UqFiCJOYrZ - Sb5QSka5wmpkdxNJkDUHPhL/w0P8Aj0joSbC8GKdjn8+5vEQAuT1iIMD4jMfxkYU - zzBeDt8xtskCzK/5IcC2vaOYKyCCum4Pl+DOzan4XarT6hWh4XAwA5R0B/aM200s - SPZDoyJHler7AU5xG1eU2VdYhfc2ua5s7/E6kM2mzKPJgLkt35bToxLltMdJ2/no - CRSDY+6AwKGPXlM510QkgrzM9cVOUxjJ71IrCOE1Rmi997jFcJpOPaebOty4wovP - 5LewpB5NaOJFxjfDjNOUBuG0ZcONeOEw5usehYzx7dxMvg0ky+201K1d+JzFt5Ns - iJi77t20XSr6JZpmp7Fzo90BoikVnRdLD6LmIt1BmYHDNjs0uUelC6l+HrN7SoNR - VkAwO5ujgkpRMHMfpBO6EfH65Sbstzxh00Cufuj+8xkNDKbUSJrErFm4IZqoW+rx - kricFWwd9xduEX9cPYcyJ3e3siZtERW72qdz4Sf5pxT9IlpoveuMxboctUyvr69C - sLNiS8vMpimhSb0PUpyfp3w5/GXaIJpj0KQyvn/onWnXBxN1w3shVD8MUF5MNq7J - ZvJmQ7+U+hmhqRbJc7H1L22AKXOEWY2BGRm/3A35CC40NwIDAQABMA0GCSqGSIb3 - DQEBCwUAA4ICAQBbJwE+qc0j6JGHWe0TGjv1viJU3WuyJkMRi+ejx0p/k7Ntp5An - 2wLC7b/lVP/Nh+PKY/iXWn/BErv2MUo4POc1g8svgxsmMMh5KGGieIfGs7xT+JMH - dzZZM+pUpIB5fEO5JfjiOEOKDdAvRSs0mTAVYZEokGkXSNWyylvEaA16mHtMgPjo - Lm75d0O66RfJDdd/hTl8umGpF7kEGW1qYk2QmuPr7AqOa8na7olL5fMPh6Q7yRqx - GIS9JKQ0fWl8Ngk09WfwUN/kEMcp9Jl5iunNRkbpUJIM/lHFkSA7yOFFL+dVWzd4 - 2r+ddJXTFzW8Rwt65l8SV2MEhijEamKva3mqKLIRWxDsfFVT1T04LWFtnzMW4Z29 - UHF9Pi7XSyKz0Y/Lz31mNTkjJYbOvbnwok8lc3wFWHc+lummZk8IkCq8xfqzwmwX - Ow6EV+Q6VaQpOHumQZ12pBBLtL8DyDhWaRUgVy2vYpwYsMYa5BFMcKCynjlSewo9 - G2hNoW45cQZP1qHltRR9Xad7SaP7iTETDCiR7AWOqSpDipSh9eMfVW97ZbSfz+vl - xl8PZEZMTRIIRVXsPP+E8gtDUhUQp2+Vcz8r6q71qslXM09xl/501uaNjCc3hH2R - iw2N77Lho1F3FrBbHdML3RYHZI55eC9iQw6R4S+R4b+iWLJoHzHrW61itg== - -----END CERTIFICATE----- - ''; -} diff --git a/nixos/tests/common/resolver.nix b/nixos/tests/common/resolver.nix index 6be8d1d18e622..09a74de20faae 100644 --- a/nixos/tests/common/resolver.nix +++ b/nixos/tests/common/resolver.nix @@ -18,7 +18,7 @@ defining this option needs to be explicitly imported. The reason this option exists is for the - <filename>nixos/tests/common/letsencrypt</filename> module, which + <filename>nixos/tests/common/acme/server</filename> module, which needs that option to disable the resolver once the user has set its own resolver. ''; diff --git a/nixos/tests/common/user-account.nix b/nixos/tests/common/user-account.nix index 9cd531a1f96c8..a57ee2d59ae36 100644 --- a/nixos/tests/common/user-account.nix +++ b/nixos/tests/common/user-account.nix @@ -4,6 +4,7 @@ { isNormalUser = true; description = "Alice Foobar"; password = "foobar"; + uid = 1000; }; users.users.bob = diff --git a/nixos/tests/common/x11.nix b/nixos/tests/common/x11.nix index c5a7c165d1268..0d76a0e972ff5 100644 --- a/nixos/tests/common/x11.nix +++ b/nixos/tests/common/x11.nix @@ -1,12 +1,17 @@ -{ services.xserver.enable = true; +{ lib, ... }: + +{ + imports = [ + ./auto.nix + ]; + + services.xserver.enable = true; # Automatically log in. - services.xserver.displayManager.auto.enable = true; + test-support.displayManager.auto.enable = true; # Use IceWM as the window manager. - services.xserver.windowManager.default = "icewm"; - services.xserver.windowManager.icewm.enable = true; - # Don't use a desktop manager. - services.xserver.desktopManager.default = "none"; + services.xserver.displayManager.defaultSession = lib.mkDefault "none+icewm"; + services.xserver.windowManager.icewm.enable = true; } diff --git a/nixos/tests/consul.nix b/nixos/tests/consul.nix new file mode 100644 index 0000000000000..6600dae4770b2 --- /dev/null +++ b/nixos/tests/consul.nix @@ -0,0 +1,143 @@ +import ./make-test-python.nix ({pkgs, lib, ...}: + +let + # Settings for both servers and agents + webUi = true; + retry_interval = "1s"; + raft_multiplier = 1; + + defaultExtraConfig = { + inherit retry_interval; + performance = { + inherit raft_multiplier; + }; + }; + + allConsensusServerHosts = [ + "192.168.1.1" + "192.168.1.2" + "192.168.1.3" + ]; + + allConsensusClientHosts = [ + "192.168.2.1" + "192.168.2.2" + ]; + + firewallSettings = { + # See https://www.consul.io/docs/install/ports.html + allowedTCPPorts = [ 8301 8302 8600 8500 8300 ]; + allowedUDPPorts = [ 8301 8302 8600 ]; + }; + + client = index: { pkgs, ... }: + let + ip = builtins.elemAt allConsensusClientHosts index; + in + { + environment.systemPackages = [ pkgs.consul ]; + + networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [ + { address = ip; prefixLength = 16; } + ]; + networking.firewall = firewallSettings; + + services.consul = { + enable = true; + inherit webUi; + extraConfig = defaultExtraConfig // { + server = false; + retry_join = allConsensusServerHosts; + bind_addr = ip; + }; + }; + }; + + server = index: { pkgs, ... }: + let + ip = builtins.elemAt allConsensusServerHosts index; + in + { + networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [ + { address = builtins.elemAt allConsensusServerHosts index; prefixLength = 16; } + ]; + networking.firewall = firewallSettings; + + services.consul = + let + thisConsensusServerHost = builtins.elemAt allConsensusServerHosts index; + in + assert builtins.elem thisConsensusServerHost allConsensusServerHosts; + { + enable = true; + inherit webUi; + extraConfig = defaultExtraConfig // { + server = true; + bootstrap_expect = builtins.length allConsensusServerHosts; + retry_join = + # If there's only 1 node in the network, we allow self-join; + # otherwise, the node must not try to join itself, and join only the other servers. + # See https://github.com/hashicorp/consul/issues/2868 + if builtins.length allConsensusServerHosts == 1 + then allConsensusServerHosts + else builtins.filter (h: h != thisConsensusServerHost) allConsensusServerHosts; + bind_addr = ip; + }; + }; + }; +in { + name = "consul"; + + nodes = { + server1 = server 0; + server2 = server 1; + server3 = server 2; + + client1 = client 0; + client2 = client 1; + }; + + testScript = '' + servers = [server1, server2, server3] + machines = [server1, server2, server3, client1, client2] + + for m in machines: + m.wait_for_unit("consul.service") + + for m in machines: + m.wait_until_succeeds("[ $(consul members | grep -o alive | wc -l) == 5 ]") + + client1.succeed("consul kv put testkey 42") + client2.succeed("[ $(consul kv get testkey) == 42 ]") + + # Test that the cluster can tolearate failures of any single server: + for server in servers: + server.crash() + + # For each client, wait until they have connection again + # using `kv get -recurse` before issuing commands. + client1.wait_until_succeeds("consul kv get -recurse") + client2.wait_until_succeeds("consul kv get -recurse") + + # Do some consul actions while one server is down. + client1.succeed("consul kv put testkey 43") + client2.succeed("[ $(consul kv get testkey) == 43 ]") + client2.succeed("consul kv delete testkey") + + # Restart crashed machine. + server.start() + + # Wait for recovery. + for m in machines: + m.wait_until_succeeds("[ $(consul members | grep -o alive | wc -l) == 5 ]") + + # Wait for client connections. + client1.wait_until_succeeds("consul kv get -recurse") + client2.wait_until_succeeds("consul kv get -recurse") + + # Do some consul actions with server back up. + client1.succeed("consul kv put testkey 44") + client2.succeed("[ $(consul kv get testkey) == 44 ]") + client2.succeed("consul kv delete testkey") + ''; +}) diff --git a/nixos/tests/containers-custom-pkgs.nix b/nixos/tests/containers-custom-pkgs.nix new file mode 100644 index 0000000000000..397a4a905e6d9 --- /dev/null +++ b/nixos/tests/containers-custom-pkgs.nix @@ -0,0 +1,42 @@ +# Test for NixOS' container support. + +import ./make-test-python.nix ({ pkgs, lib, ...} : let + + customPkgs = pkgs // { + hello = pkgs.hello.overrideAttrs(old: { + name = "custom-hello"; + }); + }; + +in { + name = "containers-hosts"; + meta = with lib.maintainers; { + maintainers = [ adisbladis ]; + }; + + machine = + { ... }: + { + virtualisation.memorySize = 256; + virtualisation.vlans = []; + + containers.simple = { + autoStart = true; + pkgs = customPkgs; + config = {pkgs, config, ... }: { + environment.systemPackages = [ + pkgs.hello + ]; + }; + }; + + }; + + testScript = '' + start_all() + machine.wait_for_unit("default.target") + machine.succeed( + "test $(nixos-container run simple -- readlink -f /run/current-system/sw/bin/hello) = ${customPkgs.hello}/bin/hello" + ) + ''; +}) diff --git a/nixos/tests/containers-extra_veth.nix b/nixos/tests/containers-extra_veth.nix index b3d3bce87579c..7d30b3f76cd7b 100644 --- a/nixos/tests/containers-extra_veth.nix +++ b/nixos/tests/containers-extra_veth.nix @@ -1,7 +1,7 @@ # Test for NixOS' container support. -import ./make-test.nix ({ pkgs, ...} : { - name = "containers-bridge"; +import ./make-test-python.nix ({ pkgs, ...} : { + name = "containers-extra_veth"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ kampfschlaefer ]; }; @@ -52,52 +52,43 @@ import ./make-test.nix ({ pkgs, ...} : { testScript = '' - $machine->waitForUnit("default.target"); - $machine->succeed("nixos-container list") =~ /webserver/ or die; + machine.wait_for_unit("default.target") + assert "webserver" in machine.succeed("nixos-container list") - # Status of the webserver container. - $machine->succeed("nixos-container status webserver") =~ /up/ or die; + with subtest("Status of the webserver container is up"): + assert "up" in machine.succeed("nixos-container status webserver") - # Debug - #$machine->succeed("nixos-container run webserver -- ip link >&2"); + with subtest("Ensure that the veths are inside the container"): + assert "state UP" in machine.succeed( + "nixos-container run webserver -- ip link show veth1" + ) + assert "state UP" in machine.succeed( + "nixos-container run webserver -- ip link show veth2" + ) - # Ensure that the veths are inside the container - $machine->succeed("nixos-container run webserver -- ip link show veth1") =~ /state UP/ or die; - $machine->succeed("nixos-container run webserver -- ip link show veth2") =~ /state UP/ or die; + with subtest("Ensure the presence of the extra veths"): + assert "state UP" in machine.succeed("ip link show veth1") + assert "state UP" in machine.succeed("ip link show veth2") - # Debug - #$machine->succeed("ip link >&2"); + with subtest("Ensure the veth1 is part of br1 on the host"): + assert "master br1" in machine.succeed("ip link show veth1") - # Ensure the presence of the extra veths - $machine->succeed("ip link show veth1") =~ /state UP/ or die; - $machine->succeed("ip link show veth2") =~ /state UP/ or die; + with subtest("Ping on main veth"): + machine.succeed("ping -n -c 1 192.168.0.100") + machine.succeed("ping -n -c 1 fc00::2") - # Ensure the veth1 is part of br1 on the host - $machine->succeed("ip link show veth1") =~ /master br1/ or die; + with subtest("Ping on the first extra veth"): + machine.succeed("ping -n -c 1 192.168.1.100 >&2") - # Debug - #$machine->succeed("ip -4 a >&2"); - #$machine->succeed("ip -4 r >&2"); - #$machine->succeed("nixos-container run webserver -- ip link >&2"); - #$machine->succeed("nixos-container run webserver -- ip -4 a >&2"); - #$machine->succeed("nixos-container run webserver -- ip -4 r >&2"); + with subtest("Ping on the second extra veth"): + machine.succeed("ping -n -c 1 192.168.2.100 >&2") - # Ping on main veth - $machine->succeed("ping -n -c 1 192.168.0.100"); - $machine->succeed("ping -n -c 1 fc00::2"); + with subtest("Container can be stopped"): + machine.succeed("nixos-container stop webserver") + machine.fail("ping -n -c 1 192.168.1.100 >&2") + machine.fail("ping -n -c 1 192.168.2.100 >&2") - # Ping on the first extra veth - $machine->succeed("ping -n -c 1 192.168.1.100 >&2"); - - # Ping on the second extra veth - $machine->succeed("ping -n -c 1 192.168.2.100 >&2"); - - # Stop the container. - $machine->succeed("nixos-container stop webserver"); - $machine->fail("ping -n -c 1 192.168.1.100 >&2"); - $machine->fail("ping -n -c 1 192.168.2.100 >&2"); - - # Destroying a declarative container should fail. - $machine->fail("nixos-container destroy webserver"); + with subtest("Destroying a declarative container should fail"): + machine.fail("nixos-container destroy webserver") ''; }) diff --git a/nixos/tests/containers-imperative.nix b/nixos/tests/containers-imperative.nix index 61df74042cb31..c4f2002918fc7 100644 --- a/nixos/tests/containers-imperative.nix +++ b/nixos/tests/containers-imperative.nix @@ -46,6 +46,15 @@ import ./make-test-python.nix ({ pkgs, ...} : { }; } ''; + brokenCfg = pkgs.writeText "broken.nix" '' + { + assertions = [ + { assertion = false; + message = "I never evaluate"; + } + ]; + } + ''; in '' with subtest("Make sure we have a NixOS tree (required by ‘nixos-container create’)"): machine.succeed("PAGER=cat nix-env -qa -A nixos.hello >&2") @@ -130,5 +139,11 @@ import ./make-test-python.nix ({ pkgs, ...} : { with subtest("Ensure that the container path is gone"): print(machine.succeed("ls -lsa /var/lib/containers")) machine.succeed(f"test ! -e /var/lib/containers/{id1}") + + with subtest("Ensure that a failed container creation doesn'leave any state"): + machine.fail( + "nixos-container create b0rk --config-file ${brokenCfg}" + ) + machine.succeed(f"test ! -e /var/lib/containers/b0rk") ''; }) diff --git a/nixos/tests/containers-macvlans.nix b/nixos/tests/containers-macvlans.nix index 2bdb926a8e2b7..0e8f67bc76f0b 100644 --- a/nixos/tests/containers-macvlans.nix +++ b/nixos/tests/containers-macvlans.nix @@ -6,7 +6,7 @@ let containerIp2 = "192.168.1.254"; in -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, ...} : { name = "containers-macvlans"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ montag451 ]; @@ -64,19 +64,23 @@ import ./make-test.nix ({ pkgs, ...} : { }; testScript = '' - startAll; - $machine1->waitForUnit("default.target"); - $machine2->waitForUnit("default.target"); + start_all() + machine1.wait_for_unit("default.target") + machine2.wait_for_unit("default.target") - # Ping between containers to check that macvlans are created in bridge mode - $machine1->succeed("nixos-container run test1 -- ping -n -c 1 ${containerIp2}"); + with subtest( + "Ping between containers to check that macvlans are created in bridge mode" + ): + machine1.succeed("nixos-container run test1 -- ping -n -c 1 ${containerIp2}") - # Ping containers from the host (machine1) - $machine1->succeed("ping -n -c 1 ${containerIp1}"); - $machine1->succeed("ping -n -c 1 ${containerIp2}"); + with subtest("Ping containers from the host (machine1)"): + machine1.succeed("ping -n -c 1 ${containerIp1}") + machine1.succeed("ping -n -c 1 ${containerIp2}") - # Ping containers from the second machine to check that containers are reachable from the outside - $machine2->succeed("ping -n -c 1 ${containerIp1}"); - $machine2->succeed("ping -n -c 1 ${containerIp2}"); + with subtest( + "Ping containers from the second machine to check that containers are reachable from the outside" + ): + machine2.succeed("ping -n -c 1 ${containerIp1}") + machine2.succeed("ping -n -c 1 ${containerIp2}") ''; }) diff --git a/nixos/tests/containers-physical_interfaces.nix b/nixos/tests/containers-physical_interfaces.nix index 1e312f59f437e..e800751a23c23 100644 --- a/nixos/tests/containers-physical_interfaces.nix +++ b/nixos/tests/containers-physical_interfaces.nix @@ -1,5 +1,5 @@ -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, ...} : { name = "containers-physical_interfaces"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ kampfschlaefer ]; @@ -86,48 +86,51 @@ import ./make-test.nix ({ pkgs, ...} : { }; testScript = '' - startAll; - - subtest "prepare server", sub { - $server->waitForUnit("default.target"); - $server->succeed("ip link show dev eth1 >&2"); - }; - - subtest "simple physical interface", sub { - $server->succeed("nixos-container start server"); - $server->waitForUnit("container\@server"); - $server->succeed("systemctl -M server list-dependencies network-addresses-eth1.service >&2"); - - # The other tests will ping this container on its ip. Here we just check - # that the device is present in the container. - $server->succeed("nixos-container run server -- ip a show dev eth1 >&2"); - }; - - subtest "physical device in bridge in container", sub { - $bridged->waitForUnit("default.target"); - $bridged->succeed("nixos-container start bridged"); - $bridged->waitForUnit("container\@bridged"); - $bridged->succeed("systemctl -M bridged list-dependencies network-addresses-br0.service >&2"); - $bridged->succeed("systemctl -M bridged status -n 30 -l network-addresses-br0.service"); - $bridged->succeed("nixos-container run bridged -- ping -w 10 -c 1 -n 10.10.0.1"); - }; - - subtest "physical device in bond in container", sub { - $bonded->waitForUnit("default.target"); - $bonded->succeed("nixos-container start bonded"); - $bonded->waitForUnit("container\@bonded"); - $bonded->succeed("systemctl -M bonded list-dependencies network-addresses-bond0 >&2"); - $bonded->succeed("systemctl -M bonded status -n 30 -l network-addresses-bond0 >&2"); - $bonded->succeed("nixos-container run bonded -- ping -w 10 -c 1 -n 10.10.0.1"); - }; - - subtest "physical device in bond in bridge in container", sub { - $bridgedbond->waitForUnit("default.target"); - $bridgedbond->succeed("nixos-container start bridgedbond"); - $bridgedbond->waitForUnit("container\@bridgedbond"); - $bridgedbond->succeed("systemctl -M bridgedbond list-dependencies network-addresses-br0.service >&2"); - $bridgedbond->succeed("systemctl -M bridgedbond status -n 30 -l network-addresses-br0.service"); - $bridgedbond->succeed("nixos-container run bridgedbond -- ping -w 10 -c 1 -n 10.10.0.1"); - }; + start_all() + + with subtest("Prepare server"): + server.wait_for_unit("default.target") + server.succeed("ip link show dev eth1 >&2") + + with subtest("Simple physical interface is up"): + server.succeed("nixos-container start server") + server.wait_for_unit("container@server") + server.succeed( + "systemctl -M server list-dependencies network-addresses-eth1.service >&2" + ) + + # The other tests will ping this container on its ip. Here we just check + # that the device is present in the container. + server.succeed("nixos-container run server -- ip a show dev eth1 >&2") + + with subtest("Physical device in bridge in container can ping server"): + bridged.wait_for_unit("default.target") + bridged.succeed("nixos-container start bridged") + bridged.wait_for_unit("container@bridged") + bridged.succeed( + "systemctl -M bridged list-dependencies network-addresses-br0.service >&2", + "systemctl -M bridged status -n 30 -l network-addresses-br0.service", + "nixos-container run bridged -- ping -w 10 -c 1 -n 10.10.0.1", + ) + + with subtest("Physical device in bond in container can ping server"): + bonded.wait_for_unit("default.target") + bonded.succeed("nixos-container start bonded") + bonded.wait_for_unit("container@bonded") + bonded.succeed( + "systemctl -M bonded list-dependencies network-addresses-bond0 >&2", + "systemctl -M bonded status -n 30 -l network-addresses-bond0 >&2", + "nixos-container run bonded -- ping -w 10 -c 1 -n 10.10.0.1", + ) + + with subtest("Physical device in bond in bridge in container can ping server"): + bridgedbond.wait_for_unit("default.target") + bridgedbond.succeed("nixos-container start bridgedbond") + bridgedbond.wait_for_unit("container@bridgedbond") + bridgedbond.succeed( + "systemctl -M bridgedbond list-dependencies network-addresses-br0.service >&2", + "systemctl -M bridgedbond status -n 30 -l network-addresses-br0.service", + "nixos-container run bridgedbond -- ping -w 10 -c 1 -n 10.10.0.1", + ) ''; }) diff --git a/nixos/tests/containers-portforward.nix b/nixos/tests/containers-portforward.nix index ec8e9629c21d3..fc90e151bd9ef 100644 --- a/nixos/tests/containers-portforward.nix +++ b/nixos/tests/containers-portforward.nix @@ -7,7 +7,7 @@ let containerPort = 80; in -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, ...} : { name = "containers-portforward"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ aristid aszlig eelco kampfschlaefer ianwookim ]; @@ -36,27 +36,27 @@ import ./make-test.nix ({ pkgs, ...} : { testScript = '' - $machine->succeed("nixos-container list") =~ /webserver/ or die; + container_list = machine.succeed("nixos-container list") + assert "webserver" in container_list # Start the webserver container. - $machine->succeed("nixos-container start webserver"); + machine.succeed("nixos-container start webserver") # wait two seconds for the container to start and the network to be up - sleep 2; + machine.sleep(2) # Since "start" returns after the container has reached # multi-user.target, we should now be able to access it. - #my $ip = $machine->succeed("nixos-container show-ip webserver"); - #chomp $ip; - $machine->succeed("ping -n -c1 ${hostIp}"); - $machine->succeed("curl --fail http://${hostIp}:${toString hostPort}/ > /dev/null"); + # ip = machine.succeed("nixos-container show-ip webserver").strip() + machine.succeed("ping -n -c1 ${hostIp}") + machine.succeed("curl --fail http://${hostIp}:${toString hostPort}/ > /dev/null") # Stop the container. - $machine->succeed("nixos-container stop webserver"); - $machine->fail("curl --fail --connect-timeout 2 http://${hostIp}:${toString hostPort}/ > /dev/null"); + machine.succeed("nixos-container stop webserver") + machine.fail("curl --fail --connect-timeout 2 http://${hostIp}:${toString hostPort}/ > /dev/null") # Destroying a declarative container should fail. - $machine->fail("nixos-container destroy webserver"); + machine.fail("nixos-container destroy webserver") ''; }) diff --git a/nixos/tests/containers-restart_networking.nix b/nixos/tests/containers-restart_networking.nix index df15f5b2f455c..b50dadd13e479 100644 --- a/nixos/tests/containers-restart_networking.nix +++ b/nixos/tests/containers-restart_networking.nix @@ -16,7 +16,7 @@ let }; }; }; -in import ./make-test.nix ({ pkgs, ...} : +in import ./make-test-python.nix ({ pkgs, ...} : { name = "containers-restart_networking"; meta = with pkgs.stdenv.lib.maintainers; { @@ -64,50 +64,52 @@ in import ./make-test.nix ({ pkgs, ...} : eth1_bridged = nodes.client_eth1.config.system.build.toplevel; eth1_rstp = nodes.client_eth1_rstp.config.system.build.toplevel; in '' - $client->start(); - - $client->waitForUnit("default.target"); - - subtest "initial state", sub { - $client->succeed("ping 192.168.1.122 -c 1 -n >&2"); - $client->succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.1 >&2"); - - $client->fail("ip l show eth1 |grep \"master br0\" >&2"); - $client->fail("grep eth1 /run/br0.interfaces >&2"); - }; - - subtest "interfaces without stp", sub { - $client->succeed("${eth1_bridged}/bin/switch-to-configuration test >&2"); - - $client->succeed("ping 192.168.1.122 -c 1 -n >&2"); - $client->succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.2 >&2"); - - $client->succeed("ip l show eth1 |grep \"master br0\" >&2"); - $client->succeed("grep eth1 /run/br0.interfaces >&2"); - }; - - # activating rstp needs another service, therefor the bridge will restart and the container will loose its connectivity - #subtest "interfaces with rstp", sub { - # $client->succeed("${eth1_rstp}/bin/switch-to-configuration test >&2"); - # $client->execute("ip -4 a >&2"); - # $client->execute("ip l >&2"); + client.start() + + client.wait_for_unit("default.target") + + with subtest("Initial configuration connectivity check"): + client.succeed("ping 192.168.1.122 -c 1 -n >&2") + client.succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.1 >&2") + + client.fail("ip l show eth1 |grep 'master br0' >&2") + client.fail("grep eth1 /run/br0.interfaces >&2") + + with subtest("Bridged configuration without STP preserves connectivity"): + client.succeed( + "${eth1_bridged}/bin/switch-to-configuration test >&2" + ) + + client.succeed( + "ping 192.168.1.122 -c 1 -n >&2", + "nixos-container run webserver -- ping -c 1 -n 192.168.1.2 >&2", + "ip l show eth1 |grep 'master br0' >&2", + "grep eth1 /run/br0.interfaces >&2", + ) + + # activating rstp needs another service, therefore the bridge will restart and the container will lose its connectivity + # with subtest("Bridged configuration with STP"): + # client.succeed("${eth1_rstp}/bin/switch-to-configuration test >&2") + # client.execute("ip -4 a >&2") + # client.execute("ip l >&2") # - # $client->succeed("ping 192.168.1.122 -c 1 -n >&2"); - # $client->succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.2 >&2"); - # - # $client->succeed("ip l show eth1 |grep \"master br0\" >&2"); - # $client->succeed("grep eth1 /run/br0.interfaces >&2"); - #}; - - subtest "back to no interfaces and no stp", sub { - $client->succeed("${originalSystem}/bin/switch-to-configuration test >&2"); - - $client->succeed("ping 192.168.1.122 -c 1 -n >&2"); - $client->succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.1 >&2"); - - $client->fail("ip l show eth1 |grep \"master br0\" >&2"); - $client->fail("grep eth1 /run/br0.interfaces >&2"); - }; + # client.succeed( + # "ping 192.168.1.122 -c 1 -n >&2", + # "nixos-container run webserver -- ping -c 1 -n 192.168.1.2 >&2", + # "ip l show eth1 |grep 'master br0' >&2", + # "grep eth1 /run/br0.interfaces >&2", + # ) + + with subtest("Reverting to initial configuration preserves connectivity"): + client.succeed( + "${originalSystem}/bin/switch-to-configuration test >&2" + ) + + client.succeed("ping 192.168.1.122 -c 1 -n >&2") + client.succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.1 >&2") + + client.fail("ip l show eth1 |grep 'master br0' >&2") + client.fail("grep eth1 /run/br0.interfaces >&2") ''; }) diff --git a/nixos/tests/corerad.nix b/nixos/tests/corerad.nix new file mode 100644 index 0000000000000..741fa448f680a --- /dev/null +++ b/nixos/tests/corerad.nix @@ -0,0 +1,70 @@ +import ./make-test-python.nix ( + { + nodes = { + router = {config, pkgs, ...}: { + config = { + # This machines simulates a router with IPv6 forwarding and a static IPv6 address. + boot.kernel.sysctl = { + "net.ipv6.conf.all.forwarding" = true; + }; + networking.interfaces.eth1 = { + ipv6.addresses = [ { address = "fd00:dead:beef:dead::1"; prefixLength = 64; } ]; + }; + services.corerad = { + enable = true; + # Serve router advertisements to the client machine with prefix information matching + # any IPv6 /64 prefixes configured on this interface. + configFile = pkgs.writeText "corerad.toml" '' + [[interfaces]] + name = "eth1" + advertise = true + [[interfaces.prefix]] + prefix = "::/64" + ''; + }; + }; + }; + client = {config, pkgs, ...}: { + # Use IPv6 SLAAC from router advertisements, and install rdisc6 so we can + # trigger one immediately. + config = { + boot.kernel.sysctl = { + "net.ipv6.conf.all.autoconf" = true; + }; + environment.systemPackages = with pkgs; [ + ndisc6 + ]; + }; + }; + }; + + testScript = '' + start_all() + + with subtest("Wait for CoreRAD and network ready"): + # Ensure networking is online and CoreRAD is ready. + router.wait_for_unit("network-online.target") + client.wait_for_unit("network-online.target") + router.wait_for_unit("corerad.service") + + # Ensure the client can reach the router. + client.wait_until_succeeds("ping -c 1 fd00:dead:beef:dead::1") + + with subtest("Verify SLAAC on client"): + # Trigger a router solicitation and verify a SLAAC address is assigned from + # the prefix configured on the router. + client.wait_until_succeeds("rdisc6 -1 -r 10 eth1") + client.wait_until_succeeds( + "ip -6 addr show dev eth1 | grep -q 'fd00:dead:beef:dead:'" + ) + + addrs = client.succeed("ip -6 addr show dev eth1") + + assert ( + "fd00:dead:beef:dead:" in addrs + ), "SLAAC prefix was not found in client addresses after router advertisement" + assert ( + "/64 scope global temporary" in addrs + ), "SLAAC temporary address was not configured on client after router advertisement" + ''; + }) diff --git a/nixos/tests/deluge.nix b/nixos/tests/deluge.nix index 37689c3d91372..3cf179a382164 100644 --- a/nixos/tests/deluge.nix +++ b/nixos/tests/deluge.nix @@ -5,9 +5,10 @@ import ./make-test-python.nix ({ pkgs, ...} : { }; nodes = { - simple = { + simple1 = { services.deluge = { enable = true; + package = pkgs.deluge-1_x; web = { enable = true; openFirewall = true; @@ -15,50 +16,92 @@ import ./make-test-python.nix ({ pkgs, ...} : { }; }; - declarative = - { ... }: + declarative1 = { + services.deluge = { + enable = true; + package = pkgs.deluge-1_x; + openFirewall = true; + declarative = true; + config = { + allow_remote = true; + download_location = "/var/lib/deluge/my-download"; + daemon_port = 58846; + listen_ports = [ 6881 6889 ]; + }; + web = { + enable = true; + port = 3142; + }; + authFile = pkgs.writeText "deluge-auth" '' + localclient:a7bef72a890:10 + andrew:password:10 + user3:anotherpass:5 + ''; + }; + }; - { - services.deluge = { + simple2 = { + services.deluge = { + enable = true; + package = pkgs.deluge-2_x; + web = { enable = true; openFirewall = true; - declarative = true; - config = { - allow_remote = true; - download_location = "/var/lib/deluge/my-download"; - daemon_port = 58846; - listen_ports = [ 6881 6889 ]; - }; - web = { - enable = true; - port = 3142; - }; - authFile = pkgs.writeText "deluge-auth" '' - localclient:a7bef72a890:10 - andrew:password:10 - user3:anotherpass:5 - ''; }; - environment.systemPackages = [ pkgs.deluge ]; }; + }; + + declarative2 = { + services.deluge = { + enable = true; + package = pkgs.deluge-2_x; + openFirewall = true; + declarative = true; + config = { + allow_remote = true; + download_location = "/var/lib/deluge/my-download"; + daemon_port = 58846; + listen_ports = [ 6881 6889 ]; + }; + web = { + enable = true; + port = 3142; + }; + authFile = pkgs.writeText "deluge-auth" '' + localclient:a7bef72a890:10 + andrew:password:10 + user3:anotherpass:5 + ''; + }; + }; }; testScript = '' start_all() - simple.wait_for_unit("deluged") - simple.wait_for_unit("delugeweb") - simple.wait_for_open_port("8112") - declarative.wait_for_unit("network.target") - declarative.wait_until_succeeds("curl --fail http://simple:8112") + simple1.wait_for_unit("deluged") + simple2.wait_for_unit("deluged") + simple1.wait_for_unit("delugeweb") + simple2.wait_for_unit("delugeweb") + simple1.wait_for_open_port("8112") + simple2.wait_for_open_port("8112") + declarative1.wait_for_unit("network.target") + declarative2.wait_for_unit("network.target") + declarative1.wait_until_succeeds("curl --fail http://simple1:8112") + declarative2.wait_until_succeeds("curl --fail http://simple2:8112") - declarative.wait_for_unit("deluged") - declarative.wait_for_unit("delugeweb") - declarative.wait_until_succeeds("curl --fail http://declarative:3142") - declarative.succeed("deluge-console 'help' | grep -q 'rm - Remove a torrent'") - declarative.succeed( - "deluge-console 'connect 127.0.0.1:58846 andrew password; help' | grep -q 'rm - Remove a torrent'" + declarative1.wait_for_unit("deluged") + declarative2.wait_for_unit("deluged") + declarative1.wait_for_unit("delugeweb") + declarative2.wait_for_unit("delugeweb") + declarative1.wait_until_succeeds("curl --fail http://declarative1:3142") + declarative2.wait_until_succeeds("curl --fail http://declarative2:3142") + declarative1.succeed( + "deluge-console 'connect 127.0.0.1:58846 andrew password; help' | grep -q 'rm.*Remove a torrent'" + ) + declarative2.succeed( + "deluge-console 'connect 127.0.0.1:58846 andrew password; help' | grep -q 'rm.*Remove a torrent'" ) ''; }) diff --git a/nixos/tests/dhparams.nix b/nixos/tests/dhparams.nix index d11dfeec5d0c1..a0de2911777c7 100644 --- a/nixos/tests/dhparams.nix +++ b/nixos/tests/dhparams.nix @@ -4,7 +4,7 @@ let environment.systemPackages = [ pkgs.openssl ]; }; -in import ./make-test.nix { +in import ./make-test-python.nix { name = "dhparams"; nodes.generation1 = { pkgs, config, ... }: { @@ -66,79 +66,77 @@ in import ./make-test.nix { node = "generation${toString gen}"; in nodes.${node}.config.security.dhparams.params.${name}.path; - assertParamBits = gen: name: bits: let - path = getParamPath gen name; - in '' - $machine->nest('check bit size of ${path}', sub { - my $out = $machine->succeed('openssl dhparam -in ${path} -text'); - $out =~ /^\s*DH Parameters:\s+\((\d+)\s+bit\)\s*$/m; - die "bit size should be ${toString bits} but it is $1 instead." - if $1 != ${toString bits}; - }); - ''; - switchToGeneration = gen: let node = "generation${toString gen}"; inherit (nodes.${node}.config.system.build) toplevel; switchCmd = "${toplevel}/bin/switch-to-configuration test"; in '' - $machine->nest('switch to generation ${toString gen}', sub { - $machine->succeed('${switchCmd}'); - $main::machine = ''$${node}; - }); + with machine.nested("switch to generation ${toString gen}"): + machine.succeed( + "${switchCmd}" + ) + machine = ${node} ''; in '' - my $machine = $generation1; + import re - $machine->waitForUnit('multi-user.target'); - subtest "verify startup order", sub { - $machine->succeed('systemctl is-active foo.service'); - }; + def assert_param_bits(path, bits): + with machine.nested(f"check bit size of {path}"): + output = machine.succeed(f"openssl dhparam -in {path} -text") + pattern = re.compile(r"^\s*DH Parameters:\s+\((\d+)\s+bit\)\s*$", re.M) + match = pattern.match(output) + if match is None: + raise Exception("bla") + if match[1] != str(bits): + raise Exception(f"bit size should be {bits} but it is {match[1]} instead.") - subtest "check bit sizes of dhparam files", sub { - ${assertParamBits 1 "foo" 16} - ${assertParamBits 1 "bar" 17} - }; + + machine = generation1 + + machine.wait_for_unit("multi-user.target") + + with subtest("verify startup order"): + machine.succeed("systemctl is-active foo.service") + + with subtest("check bit sizes of dhparam files"): + assert_param_bits("${getParamPath 1 "foo"}", 16) + assert_param_bits("${getParamPath 1 "bar"}", 17) ${switchToGeneration 2} - subtest "check whether bit size has changed", sub { - ${assertParamBits 2 "foo" 18} - }; + with subtest("check whether bit size has changed"): + assert_param_bits("${getParamPath 2 "foo"}", 18) - subtest "ensure that dhparams file for 'bar' was deleted", sub { - $machine->fail('test -e ${getParamPath 1 "bar"}'); - }; + with subtest("ensure that dhparams file for 'bar' was deleted"): + machine.fail("test -e ${getParamPath 1 "bar"}") ${switchToGeneration 3} - subtest "ensure that 'security.dhparams.path' has been deleted", sub { - $machine->fail( - 'test -e ${nodes.generation3.config.security.dhparams.path}' - ); - }; + with subtest("ensure that 'security.dhparams.path' has been deleted"): + machine.fail("test -e ${nodes.generation3.config.security.dhparams.path}") ${switchToGeneration 4} - subtest "check bit sizes dhparam files", sub { - ${assertParamBits 4 "foo2" 18} - ${assertParamBits 4 "bar2" 19} - }; + with subtest("check bit sizes dhparam files"): + assert_param_bits( + "${getParamPath 4 "foo2"}", 18 + ) + assert_param_bits( + "${getParamPath 4 "bar2"}", 19 + ) - subtest "check whether dhparam files are in the Nix store", sub { - $machine->succeed( - 'expr match ${getParamPath 4 "foo2"} ${builtins.storeDir}', - 'expr match ${getParamPath 4 "bar2"} ${builtins.storeDir}', - ); - }; + with subtest("check whether dhparam files are in the Nix store"): + machine.succeed( + "expr match ${getParamPath 4 "foo2"} ${builtins.storeDir}", + "expr match ${getParamPath 4 "bar2"} ${builtins.storeDir}", + ) ${switchToGeneration 5} - subtest "check whether defaultBitSize works as intended", sub { - ${assertParamBits 5 "foo3" 30} - ${assertParamBits 5 "bar3" 30} - }; + with subtest("check whether defaultBitSize works as intended"): + assert_param_bits("${getParamPath 5 "foo3"}", 30) + assert_param_bits("${getParamPath 5 "bar3"}", 30) ''; } diff --git a/nixos/tests/dnscrypt-proxy.nix b/nixos/tests/dnscrypt-proxy2.nix index 98153d5c90470..b614d912a9f4e 100644 --- a/nixos/tests/dnscrypt-proxy.nix +++ b/nixos/tests/dnscrypt-proxy2.nix @@ -1,5 +1,5 @@ import ./make-test-python.nix ({ pkgs, ... }: { - name = "dnscrypt-proxy"; + name = "dnscrypt-proxy2"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ joachifm ]; }; @@ -13,9 +13,16 @@ import ./make-test-python.nix ({ pkgs, ... }: { { security.apparmor.enable = true; - services.dnscrypt-proxy.enable = true; - services.dnscrypt-proxy.localPort = localProxyPort; - services.dnscrypt-proxy.extraArgs = [ "-X libdcplugin_example.so" ]; + services.dnscrypt-proxy2.enable = true; + services.dnscrypt-proxy2.settings = { + listen_addresses = [ "127.0.0.1:${toString localProxyPort}" ]; + sources.public-resolvers = { + urls = [ "https://download.dnscrypt.info/resolvers-list/v2/public-resolvers.md" ]; + cache_file = "public-resolvers.md"; + minisign_key = "RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3"; + refresh_delay = 72; + }; + }; services.dnsmasq.enable = true; services.dnsmasq.servers = [ "127.0.0.1#${toString localProxyPort}" ]; @@ -24,12 +31,6 @@ import ./make-test-python.nix ({ pkgs, ... }: { testScript = '' client.wait_for_unit("dnsmasq") - - # The daemon is socket activated; sending a single ping should activate it. - client.fail("systemctl is-active dnscrypt-proxy") - client.execute( - "${pkgs.iputils}/bin/ping -c1 example.com" - ) - client.wait_until_succeeds("systemctl is-active dnscrypt-proxy") + client.wait_for_unit("dnscrypt-proxy2") ''; }) diff --git a/nixos/tests/docker-containers.nix b/nixos/tests/docker-containers.nix index 9725527352020..0e318a52d9f18 100644 --- a/nixos/tests/docker-containers.nix +++ b/nixos/tests/docker-containers.nix @@ -1,29 +1,27 @@ # Test Docker containers as systemd units -import ./make-test.nix ({ pkgs, lib, ... }: { +import ./make-test-python.nix ({ pkgs, lib, ... }: { name = "docker-containers"; meta = { - maintainers = with lib.maintainers; [ benley ]; + maintainers = with lib.maintainers; [ benley mkaito ]; }; nodes = { - docker = { pkgs, ... }: - { - virtualisation.docker.enable = true; + docker = { pkgs, ... }: { + virtualisation.docker.enable = true; - virtualisation.dockerPreloader.images = [ pkgs.dockerTools.examples.nginx ]; - - docker-containers.nginx = { - image = "nginx-container"; - ports = ["8181:80"]; - }; + docker-containers.nginx = { + image = "nginx-container"; + imageFile = pkgs.dockerTools.examples.nginx; + ports = ["8181:80"]; }; + }; }; testScript = '' - startAll; - $docker->waitForUnit("docker-nginx.service"); - $docker->waitForOpenPort(8181); - $docker->waitUntilSucceeds("curl http://localhost:8181|grep Hello"); + start_all() + docker.wait_for_unit("docker-nginx.service") + docker.wait_for_open_port(8181) + docker.wait_until_succeeds("curl http://localhost:8181 | grep Hello") ''; }) diff --git a/nixos/tests/docker-tools-overlay.nix b/nixos/tests/docker-tools-overlay.nix index 637957bd3e8b3..1a0e0ea67750a 100644 --- a/nixos/tests/docker-tools-overlay.nix +++ b/nixos/tests/docker-tools-overlay.nix @@ -1,6 +1,6 @@ # this test creates a simple GNU image with docker tools and sees if it executes -import ./make-test.nix ({ pkgs, ... }: +import ./make-test-python.nix ({ pkgs, ... }: { name = "docker-tools-overlay"; meta = with pkgs.stdenv.lib.maintainers; { @@ -16,17 +16,18 @@ import ./make-test.nix ({ pkgs, ... }: }; }; - testScript = - '' - $docker->waitForUnit("sockets.target"); + testScript = '' + docker.wait_for_unit("sockets.target") - $docker->succeed("docker load --input='${pkgs.dockerTools.examples.bash}'"); - $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.bash.imageName} bash --version"); + docker.succeed( + "docker load --input='${pkgs.dockerTools.examples.bash}'", + "docker run --rm ${pkgs.dockerTools.examples.bash.imageName} bash --version", + ) # Check if the nix store has correct user permissions depending on what # storage driver is used, incorrectly built images can show up as readonly. # drw------- 3 0 0 3 Apr 14 11:36 /nix # drw------- 99 0 0 100 Apr 14 11:36 /nix/store - $docker->succeed("docker run --rm -u 1000:1000 ${pkgs.dockerTools.examples.bash.imageName} bash --version"); + docker.succeed("docker run --rm -u 1000:1000 ${pkgs.dockerTools.examples.bash.imageName} bash --version") ''; }) diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix index 9ab1a71f3314a..51b472fcf9ce5 100644 --- a/nixos/tests/docker-tools.nix +++ b/nixos/tests/docker-tools.nix @@ -1,84 +1,158 @@ # this test creates a simple GNU image with docker tools and sees if it executes -import ./make-test.nix ({ pkgs, ... }: { +import ./make-test-python.nix ({ pkgs, ... }: { name = "docker-tools"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ lnl7 ]; }; nodes = { - docker = - { ... }: { - virtualisation = { - diskSize = 2048; - docker.enable = true; - }; + docker = { ... }: { + virtualisation = { + diskSize = 2048; + docker.enable = true; }; + }; }; - testScript = - '' - $docker->waitForUnit("sockets.target"); - - # Ensure Docker images use a stable date by default - $docker->succeed("docker load --input='${pkgs.dockerTools.examples.bash}'"); - $docker->succeed("[ '1970-01-01T00:00:01Z' = \"\$(docker inspect ${pkgs.dockerTools.examples.bash.imageName} | ${pkgs.jq}/bin/jq -r .[].Created)\" ]"); - - $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.bash.imageName} bash --version"); - $docker->succeed("docker rmi ${pkgs.dockerTools.examples.bash.imageName}"); - - # Check if the nix store is correctly initialized by listing dependencies of the installed Nix binary - $docker->succeed("docker load --input='${pkgs.dockerTools.examples.nix}'"); - $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.nix.imageName} nix-store -qR ${pkgs.nix}"); - $docker->succeed("docker rmi ${pkgs.dockerTools.examples.nix.imageName}"); - - # To test the pullImage tool - $docker->succeed("docker load --input='${pkgs.dockerTools.examples.nixFromDockerHub}'"); - $docker->succeed("docker run --rm nix:2.2.1 nix-store --version"); - $docker->succeed("docker rmi nix:2.2.1"); - - # To test runAsRoot and entry point - $docker->succeed("docker load --input='${pkgs.dockerTools.examples.nginx}'"); - $docker->succeed("docker run --name nginx -d -p 8000:80 ${pkgs.dockerTools.examples.nginx.imageName}"); - $docker->waitUntilSucceeds('curl http://localhost:8000/'); - $docker->succeed("docker rm --force nginx"); - $docker->succeed("docker rmi '${pkgs.dockerTools.examples.nginx.imageName}'"); - - # An pulled image can be used as base image - $docker->succeed("docker load --input='${pkgs.dockerTools.examples.onTopOfPulledImage}'"); - $docker->succeed("docker run --rm ontopofpulledimage hello"); - $docker->succeed("docker rmi ontopofpulledimage"); - - # Regression test for issue #34779 - $docker->succeed("docker load --input='${pkgs.dockerTools.examples.runAsRootExtraCommands}'"); - $docker->succeed("docker run --rm runasrootextracommands cat extraCommands"); - $docker->succeed("docker run --rm runasrootextracommands cat runAsRoot"); - $docker->succeed("docker rmi '${pkgs.dockerTools.examples.runAsRootExtraCommands.imageName}'"); - - # Ensure Docker images can use an unstable date - $docker->succeed("docker load --input='${pkgs.dockerTools.examples.bash}'"); - $docker->succeed("[ '1970-01-01T00:00:01Z' != \"\$(docker inspect ${pkgs.dockerTools.examples.unstableDate.imageName} | ${pkgs.jq}/bin/jq -r .[].Created)\" ]"); - - # Ensure Layered Docker images work - $docker->succeed("docker load --input='${pkgs.dockerTools.examples.layered-image}'"); - $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.layered-image.imageName}"); - $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.layered-image.imageName} cat extraCommands"); - - # Ensure building an image on top of a layered Docker images work - $docker->succeed("docker load --input='${pkgs.dockerTools.examples.layered-on-top}'"); - $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.layered-on-top.imageName}"); - - # Ensure layers are shared between images - $docker->succeed("docker load --input='${pkgs.dockerTools.examples.another-layered-image}'"); - $docker->succeed("docker inspect ${pkgs.dockerTools.examples.layered-image.imageName} | ${pkgs.jq}/bin/jq -r '.[] | .RootFS.Layers | .[]' | sort > layers1.sha256"); - $docker->succeed("docker inspect ${pkgs.dockerTools.examples.another-layered-image.imageName} | ${pkgs.jq}/bin/jq -r '.[] | .RootFS.Layers | .[]' | sort > layers2.sha256"); - $docker->succeed('[ $(comm -1 -2 layers1.sha256 layers2.sha256 | wc -l) -ne 0 ]'); - - # Ensure order of layers is correct - $docker->succeed("docker load --input='${pkgs.dockerTools.examples.layersOrder}'"); - $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.layersOrder.imageName} cat /tmp/layer1 | grep -q layer1"); - # This is to be sure the order of layers of the parent image is preserved - $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.layersOrder.imageName} cat /tmp/layer2 | grep -q layer2"); - $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.layersOrder.imageName} cat /tmp/layer3 | grep -q layer3"); - ''; + testScript = with pkgs.dockerTools; '' + unix_time_second1 = "1970-01-01T00:00:01Z" + + docker.wait_for_unit("sockets.target") + + with subtest("Ensure Docker images use a stable date by default"): + docker.succeed( + "docker load --input='${examples.bash}'" + ) + assert unix_time_second1 in docker.succeed( + "docker inspect ${examples.bash.imageName} " + + "| ${pkgs.jq}/bin/jq -r .[].Created", + ) + + docker.succeed("docker run --rm ${examples.bash.imageName} bash --version") + docker.succeed("docker rmi ${examples.bash.imageName}") + + with subtest( + "Check if the nix store is correctly initialized by listing " + "dependencies of the installed Nix binary" + ): + docker.succeed( + "docker load --input='${examples.nix}'", + "docker run --rm ${examples.nix.imageName} nix-store -qR ${pkgs.nix}", + "docker rmi ${examples.nix.imageName}", + ) + + with subtest("The pullImage tool works"): + docker.succeed( + "docker load --input='${examples.nixFromDockerHub}'", + "docker run --rm nix:2.2.1 nix-store --version", + "docker rmi nix:2.2.1", + ) + + with subtest("runAsRoot and entry point work"): + docker.succeed( + "docker load --input='${examples.nginx}'", + "docker run --name nginx -d -p 8000:80 ${examples.nginx.imageName}", + ) + docker.wait_until_succeeds("curl http://localhost:8000/") + docker.succeed( + "docker rm --force nginx", "docker rmi '${examples.nginx.imageName}'", + ) + + with subtest("A pulled image can be used as base image"): + docker.succeed( + "docker load --input='${examples.onTopOfPulledImage}'", + "docker run --rm ontopofpulledimage hello", + "docker rmi ontopofpulledimage", + ) + + with subtest("Regression test for issue #34779"): + docker.succeed( + "docker load --input='${examples.runAsRootExtraCommands}'", + "docker run --rm runasrootextracommands cat extraCommands", + "docker run --rm runasrootextracommands cat runAsRoot", + "docker rmi '${examples.runAsRootExtraCommands.imageName}'", + ) + + with subtest("Ensure Docker images can use an unstable date"): + docker.succeed( + "docker load --input='${examples.bash}'" + ) + assert unix_time_second1 not in docker.succeed( + "docker inspect ${examples.unstableDate.imageName} " + + "| ${pkgs.jq}/bin/jq -r .[].Created" + ) + + with subtest("Ensure Layered Docker images work"): + docker.succeed( + "docker load --input='${examples.layered-image}'", + "docker run --rm ${examples.layered-image.imageName}", + "docker run --rm ${examples.layered-image.imageName} cat extraCommands", + ) + + with subtest("Ensure building an image on top of a layered Docker images work"): + docker.succeed( + "docker load --input='${examples.layered-on-top}'", + "docker run --rm ${examples.layered-on-top.imageName}", + ) + + + def set_of_layers(image_name): + return set( + docker.succeed( + f"docker inspect {image_name} " + + "| ${pkgs.jq}/bin/jq -r '.[] | .RootFS.Layers | .[]'" + ).split() + ) + + + with subtest("Ensure layers are shared between images"): + docker.succeed( + "docker load --input='${examples.another-layered-image}'" + ) + layers1 = set_of_layers("${examples.layered-image.imageName}") + layers2 = set_of_layers("${examples.another-layered-image.imageName}") + assert bool(layers1 & layers2) + + with subtest("Ensure order of layers is correct"): + docker.succeed( + "docker load --input='${examples.layersOrder}'" + ) + + for index in 1, 2, 3: + assert f"layer{index}" in docker.succeed( + f"docker run --rm ${examples.layersOrder.imageName} cat /tmp/layer{index}" + ) + + with subtest("Ensure image with only 2 layers can be loaded"): + docker.succeed( + "docker load --input='${examples.two-layered-image}'" + ) + + with subtest( + "Ensure the bulk layer doesn't miss store paths (regression test for #78744)" + ): + docker.succeed( + "docker load --input='${pkgs.dockerTools.examples.bulk-layer}'", + # Ensure the two output paths (ls and hello) are in the layer + "docker run bulk-layer ls /bin/hello", + ) + + with subtest("Ensure correct behavior when no store is needed"): + # This check tests two requirements simultaneously + # 1. buildLayeredImage can build images that don't need a store. + # 2. Layers of symlinks are eliminated by the customization layer. + # + docker.succeed( + "docker load --input='${pkgs.dockerTools.examples.no-store-paths}'" + ) + + # Busybox will not recognize argv[0] and print an error message with argv[0], + # but it confirms that the custom-true symlink is present. + docker.succeed("docker run --rm no-store-paths custom-true |& grep custom-true") + + # This check may be loosened to allow an *empty* store rather than *no* store. + docker.succeed("docker run --rm no-store-paths ls /") + docker.fail("docker run --rm no-store-paths ls /nix/store") + ''; }) diff --git a/nixos/tests/dokuwiki.nix b/nixos/tests/dokuwiki.nix new file mode 100644 index 0000000000000..2b907133ed5ad --- /dev/null +++ b/nixos/tests/dokuwiki.nix @@ -0,0 +1,74 @@ +import ./make-test-python.nix ({ pkgs, ... }: + +let + template-bootstrap3 = pkgs.stdenv.mkDerivation { + name = "bootstrap3"; + # Download the theme from the dokuwiki site + src = pkgs.fetchurl { + url = https://github.com/giterlizzi/dokuwiki-template-bootstrap3/archive/v2019-05-22.zip; + sha256 = "4de5ff31d54dd61bbccaf092c9e74c1af3a4c53e07aa59f60457a8f00cfb23a6"; + }; + # We need unzip to build this package + buildInputs = [ pkgs.unzip ]; + # Installing simply means copying all files to the output directory + installPhase = "mkdir -p $out; cp -R * $out/"; + }; + + + # Let's package the icalevents plugin + plugin-icalevents = pkgs.stdenv.mkDerivation { + name = "icalevents"; + # Download the plugin from the dokuwiki site + src = pkgs.fetchurl { + url = https://github.com/real-or-random/dokuwiki-plugin-icalevents/releases/download/2017-06-16/dokuwiki-plugin-icalevents-2017-06-16.zip; + sha256 = "e40ed7dd6bbe7fe3363bbbecb4de481d5e42385b5a0f62f6a6ce6bf3a1f9dfa8"; + }; + # We need unzip to build this package + buildInputs = [ pkgs.unzip ]; + sourceRoot = "."; + # Installing simply means copying all files to the output directory + installPhase = "mkdir -p $out; cp -R * $out/"; + }; + +in { + name = "dokuwiki"; + meta.maintainers = with pkgs.lib.maintainers; [ "1000101" ]; + + machine = { ... }: { + services.dokuwiki."site1.local" = { + aclUse = false; + superUser = "admin"; + nginx = { + forceSSL = false; + enableACME = false; + }; + }; + services.dokuwiki."site2.local" = { + aclUse = true; + superUser = "admin"; + nginx = { + forceSSL = false; + enableACME = false; + }; + templates = [ template-bootstrap3 ]; + plugins = [ plugin-icalevents ]; + }; + networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ]; + }; + + testScript = '' + site_names = ["site1.local", "site2.local"] + + start_all() + + machine.wait_for_unit("phpfpm-dokuwiki-site1.local.service") + machine.wait_for_unit("phpfpm-dokuwiki-site2.local.service") + + machine.wait_for_unit("nginx.service") + + machine.wait_for_open_port(80) + + machine.succeed("curl -sSfL http://site1.local/ | grep 'DokuWiki'") + machine.succeed("curl -sSfL http://site2.local/ | grep 'DokuWiki'") + ''; +}) diff --git a/nixos/tests/dovecot.nix b/nixos/tests/dovecot.nix index c19850f418bc1..bcbe234fd8052 100644 --- a/nixos/tests/dovecot.nix +++ b/nixos/tests/dovecot.nix @@ -8,7 +8,7 @@ import ./make-test-python.nix { services.dovecot2.protocols = [ "imap" "pop3" ]; environment.systemPackages = let sendTestMail = pkgs.writeScriptBin "send-testmail" '' - #!${pkgs.stdenv.shell} + #!${pkgs.runtimeShell} exec sendmail -vt <<MAIL From: root@localhost To: alice@localhost @@ -19,7 +19,7 @@ import ./make-test-python.nix { ''; sendTestMailViaDeliveryAgent = pkgs.writeScriptBin "send-lda" '' - #!${pkgs.stdenv.shell} + #!${pkgs.runtimeShell} exec ${pkgs.dovecot}/libexec/dovecot/deliver -d bob <<MAIL From: root@localhost diff --git a/nixos/tests/ec2.nix b/nixos/tests/ec2.nix index 384fce67c227d..6aeeb17ba31ad 100644 --- a/nixos/tests/ec2.nix +++ b/nixos/tests/ec2.nix @@ -9,7 +9,7 @@ with pkgs.lib; with import common/ec2.nix { inherit makeTest pkgs; }; let - image = + imageCfg = (import ../lib/eval-config.nix { inherit system; modules = [ @@ -26,20 +26,32 @@ let ''; # Needed by nixos-rebuild due to the lack of network - # access. Mostly copied from - # modules/profiles/installation-device.nix. + # access. Determined by trial and error. system.extraDependencies = - with pkgs; [ - stdenv busybox perlPackages.ArchiveCpio unionfs-fuse mkinitcpio-nfs-utils - - # These are used in the configure-from-userdata tests for EC2. Httpd and valgrind are requested - # directly by the configuration we set, and libxslt.bin is used indirectly as a build dependency - # of the derivation for dbus configuration files. - apacheHttpd valgrind.doc libxslt.bin - ]; + with pkgs; ( + [ + # Needed for a nixos-rebuild. + busybox + stdenv + stdenvNoCC + mkinitcpio-nfs-utils + unionfs-fuse + cloud-utils + desktop-file-utils + texinfo + libxslt.bin + xorg.lndir + + # These are used in the configure-from-userdata tests + # for EC2. Httpd and valgrind are requested by the + # configuration. + apacheHttpd apacheHttpd.doc apacheHttpd.man valgrind.doc + ] + ); } ]; - }).config.system.build.amazonImage; + }).config; + image = "${imageCfg.system.build.amazonImage}/${imageCfg.amazonImage.name}.vhd"; sshKeys = import ./ssh-keys.nix pkgs; snakeOilPrivateKey = sshKeys.snakeOilPrivateKey.text; @@ -110,16 +122,23 @@ in { text = "whoa"; }; + networking.hostName = "ec2-test-vm"; # required by services.httpd + services.httpd = { enable = true; adminAddr = "test@example.org"; - documentRoot = "${pkgs.valgrind.doc}/share/doc/valgrind/html"; + virtualHosts.localhost.documentRoot = "''${pkgs.valgrind.doc}/share/doc/valgrind/html"; }; networking.firewall.allowedTCPPorts = [ 80 ]; } ''; script = '' $machine->start; + + # amazon-init must succeed. if it fails, make the test fail + # immediately instead of timing out in waitForFile. + $machine->waitForUnit('amazon-init.service'); + $machine->waitForFile("/etc/testFile"); $machine->succeed("cat /etc/testFile | grep -q 'whoa'"); diff --git a/nixos/tests/ecryptfs.nix b/nixos/tests/ecryptfs.nix index 3f02cecb8662d..ef7bd13eb92c4 100644 --- a/nixos/tests/ecryptfs.nix +++ b/nixos/tests/ecryptfs.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ ... }: +import ./make-test-python.nix ({ ... }: { name = "ecryptfs"; @@ -10,75 +10,76 @@ import ./make-test.nix ({ ... }: }; testScript = '' - $machine->waitForUnit("default.target"); + def login_as_alice(): + machine.wait_until_tty_matches(1, "login: ") + machine.send_chars("alice\n") + machine.wait_until_tty_matches(1, "Password: ") + machine.send_chars("foobar\n") + machine.wait_until_tty_matches(1, "alice\@machine") - # Set alice up with a password and a home - $machine->succeed("(echo foobar; echo foobar) | passwd alice"); - $machine->succeed("chown -R alice.users ~alice"); - # Migrate alice's home - my $out = $machine->succeed("echo foobar | ecryptfs-migrate-home -u alice"); - $machine->log("ecryptfs-migrate-home said: $out"); + def logout(): + machine.send_chars("logout\n") + machine.wait_until_tty_matches(1, "login: ") - # Log alice in (ecryptfs passwhrase is wrapped during first login) - $machine->waitUntilTTYMatches(1, "login: "); - $machine->sendChars("alice\n"); - $machine->waitUntilTTYMatches(1, "Password: "); - $machine->sendChars("foobar\n"); - $machine->waitUntilTTYMatches(1, "alice\@machine"); - $machine->sendChars("logout\n"); - $machine->waitUntilTTYMatches(1, "login: "); + + machine.wait_for_unit("default.target") + + with subtest("Set alice up with a password and a home"): + machine.succeed("(echo foobar; echo foobar) | passwd alice") + machine.succeed("chown -R alice.users ~alice") + + with subtest("Migrate alice's home"): + out = machine.succeed("echo foobar | ecryptfs-migrate-home -u alice") + machine.log(f"ecryptfs-migrate-home said: {out}") + + with subtest("Log alice in (ecryptfs passwhrase is wrapped during first login)"): + login_as_alice() + machine.send_chars("logout\n") + machine.wait_until_tty_matches(1, "login: ") # Why do I need to do this?? - $machine->succeed("su alice -c ecryptfs-umount-private || true"); - $machine->sleep(1); - $machine->fail("mount | grep ecryptfs"); # check that encrypted home is not mounted + machine.succeed("su alice -c ecryptfs-umount-private || true") + machine.sleep(1) + + with subtest("check that encrypted home is not mounted"): + machine.fail("mount | grep ecryptfs") - # Show contents of the user keyring - my $out = $machine->succeed("su - alice -c 'keyctl list \@u'"); - $machine->log("keyctl unlink said: " . $out); + with subtest("Show contents of the user keyring"): + out = machine.succeed("su - alice -c 'keyctl list \@u'") + machine.log(f"keyctl unlink said: {out}") - # Log alice again - $machine->waitUntilTTYMatches(1, "login: "); - $machine->sendChars("alice\n"); - $machine->waitUntilTTYMatches(1, "Password: "); - $machine->sendChars("foobar\n"); - $machine->waitUntilTTYMatches(1, "alice\@machine"); + with subtest("Log alice again"): + login_as_alice() - # Create some files in encrypted home - $machine->succeed("su alice -c 'touch ~alice/a'"); - $machine->succeed("su alice -c 'echo c > ~alice/b'"); + with subtest("Create some files in encrypted home"): + machine.succeed("su alice -c 'touch ~alice/a'") + machine.succeed("su alice -c 'echo c > ~alice/b'") - # Logout - $machine->sendChars("logout\n"); - $machine->waitUntilTTYMatches(1, "login: "); + with subtest("Logout"): + logout() # Why do I need to do this?? - $machine->succeed("su alice -c ecryptfs-umount-private || true"); - $machine->sleep(1); - - # Check that the filesystem is not accessible - $machine->fail("mount | grep ecryptfs"); - $machine->succeed("su alice -c 'test \! -f ~alice/a'"); - $machine->succeed("su alice -c 'test \! -f ~alice/b'"); - - # Log alice once more - $machine->waitUntilTTYMatches(1, "login: "); - $machine->sendChars("alice\n"); - $machine->waitUntilTTYMatches(1, "Password: "); - $machine->sendChars("foobar\n"); - $machine->waitUntilTTYMatches(1, "alice\@machine"); - - # Check that the files are there - $machine->sleep(1); - $machine->succeed("su alice -c 'test -f ~alice/a'"); - $machine->succeed("su alice -c 'test -f ~alice/b'"); - $machine->succeed(qq%test "\$(cat ~alice/b)" = "c"%); - - # Catch https://github.com/NixOS/nixpkgs/issues/16766 - $machine->succeed("su alice -c 'ls -lh ~alice/'"); - - $machine->sendChars("logout\n"); - $machine->waitUntilTTYMatches(1, "login: "); + machine.succeed("su alice -c ecryptfs-umount-private || true") + machine.sleep(1) + + with subtest("Check that the filesystem is not accessible"): + machine.fail("mount | grep ecryptfs") + machine.succeed("su alice -c 'test \! -f ~alice/a'") + machine.succeed("su alice -c 'test \! -f ~alice/b'") + + with subtest("Log alice once more"): + login_as_alice() + + with subtest("Check that the files are there"): + machine.sleep(1) + machine.succeed("su alice -c 'test -f ~alice/a'") + machine.succeed("su alice -c 'test -f ~alice/b'") + machine.succeed('test "$(cat ~alice/b)" = "c"') + + with subtest("Catch https://github.com/NixOS/nixpkgs/issues/16766"): + machine.succeed("su alice -c 'ls -lh ~alice/'") + + logout() ''; }) diff --git a/nixos/tests/elk.nix b/nixos/tests/elk.nix index b33d98b85d697..d3dc6dde13592 100644 --- a/nixos/tests/elk.nix +++ b/nixos/tests/elk.nix @@ -6,20 +6,11 @@ # NIXPKGS_ALLOW_UNFREE=1 nix-build nixos/tests/elk.nix -A ELK-6 --arg enableUnfree true }: -with import ../lib/testing.nix { inherit system pkgs; }; -with pkgs.lib; - let esUrl = "http://localhost:9200"; - totalHits = message : - "curl --silent --show-error '${esUrl}/_search' -H 'Content-Type: application/json' " + - ''-d '{\"query\" : { \"match\" : { \"message\" : \"${message}\"}}}' '' + - "| jq .hits.total"; - mkElkTest = name : elk : - let elasticsearchGe7 = builtins.compareVersions elk.elasticsearch.version "7" >= 0; - in makeTest { + import ./make-test-python.nix ({ inherit name; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ eelco offline basvandijk ]; @@ -50,15 +41,15 @@ let elk.journalbeat.version "6" < 0; in { enable = true; package = elk.journalbeat; - extraConfig = mkOptionDefault ('' + extraConfig = pkgs.lib.mkOptionDefault ('' logging: to_syslog: true level: warning metrics.enabled: false output.elasticsearch: hosts: [ "127.0.0.1:9200" ] - ${optionalString lt6 "template.enabled: false"} - '' + optionalString (!lt6) '' + ${pkgs.lib.optionalString lt6 "template.enabled: false"} + '' + pkgs.lib.optionalString (!lt6) '' journalbeat.inputs: - paths: [] seek: cursor @@ -99,8 +90,7 @@ let }; elasticsearch-curator = { - # The current version of curator (5.6) doesn't support elasticsearch >= 7.0.0. - enable = !elasticsearchGe7; + enable = true; actionYAML = '' --- actions: @@ -130,11 +120,23 @@ let }; testScript = '' - startAll; + import json + + + def total_hits(message): + dictionary = {"query": {"match": {"message": message}}} + return ( + "curl --silent --show-error '${esUrl}/_search' " + + "-H 'Content-Type: application/json' " + + "-d '{}' ".format(json.dumps(dictionary)) + + "| jq .hits.total" + ) + + + start_all() - # Wait until elasticsearch is listening for connections. - $one->waitForUnit("elasticsearch.service"); - $one->waitForOpenPort(9200); + one.wait_for_unit("elasticsearch.service") + one.wait_for_open_port(9200) # Continue as long as the status is not "red". The status is probably # "yellow" instead of "green" because we are using a single elasticsearch @@ -142,42 +144,43 @@ let # # TODO: extend this test with multiple elasticsearch nodes # and see if the status turns "green". - $one->waitUntilSucceeds( - "curl --silent --show-error '${esUrl}/_cluster/health' " . - "| jq .status | grep -v red"); - - # Perform some simple logstash tests. - $one->waitForUnit("logstash.service"); - $one->waitUntilSucceeds("cat /tmp/logstash.out | grep flowers"); - $one->waitUntilSucceeds("cat /tmp/logstash.out | grep -v dragons"); - - # See if kibana is healthy. - $one->waitForUnit("kibana.service"); - $one->waitUntilSucceeds( - "curl --silent --show-error 'http://localhost:5601/api/status' " . - "| jq .status.overall.state | grep green"); - - # See if logstash messages arive in elasticsearch. - $one->waitUntilSucceeds("${totalHits "flowers"} | grep -v 0"); - $one->waitUntilSucceeds("${totalHits "dragons"} | grep 0"); - - # Test if a message logged to the journal - # is ingested by elasticsearch via journalbeat. - $one->waitForUnit("journalbeat.service"); - $one->execute("echo 'Supercalifragilisticexpialidocious' | systemd-cat"); - $one->waitUntilSucceeds( - "${totalHits "Supercalifragilisticexpialidocious"} | grep -v 0"); - - '' + optionalString (!elasticsearchGe7) '' - # Test elasticsearch-curator. - $one->systemctl("stop logstash"); - $one->systemctl("start elasticsearch-curator"); - $one->waitUntilSucceeds( - "! curl --silent --show-error '${esUrl}/_cat/indices' " . - "| grep logstash | grep -q ^$1"); + one.wait_until_succeeds( + "curl --silent --show-error '${esUrl}/_cluster/health' | jq .status | grep -v red" + ) + + with subtest("Perform some simple logstash tests"): + one.wait_for_unit("logstash.service") + one.wait_until_succeeds("cat /tmp/logstash.out | grep flowers") + one.wait_until_succeeds("cat /tmp/logstash.out | grep -v dragons") + + with subtest("Kibana is healthy"): + one.wait_for_unit("kibana.service") + one.wait_until_succeeds( + "curl --silent --show-error 'http://localhost:5601/api/status' | jq .status.overall.state | grep green" + ) + + with subtest("Logstash messages arive in elasticsearch"): + one.wait_until_succeeds(total_hits("flowers") + " | grep -v 0") + one.wait_until_succeeds(total_hits("dragons") + " | grep 0") + + with subtest( + "A message logged to the journal is ingested by elasticsearch via journalbeat" + ): + one.wait_for_unit("journalbeat.service") + one.execute("echo 'Supercalifragilisticexpialidocious' | systemd-cat") + one.wait_until_succeeds( + total_hits("Supercalifragilisticexpialidocious") + " | grep -v 0" + ) + + with subtest("Elasticsearch-curator works"): + one.systemctl("stop logstash") + one.systemctl("start elasticsearch-curator") + one.wait_until_succeeds( + '! curl --silent --show-error "${esUrl}/_cat/indices" | grep logstash | grep -q ^' + ) ''; - }; -in mapAttrs mkElkTest { + }) {}; +in pkgs.lib.mapAttrs mkElkTest { ELK-6 = if enableUnfree then { diff --git a/nixos/tests/env.nix b/nixos/tests/env.nix index 6c681905b19fd..e603338e489b9 100644 --- a/nixos/tests/env.nix +++ b/nixos/tests/env.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, ...} : { name = "environment"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ nequissimus ]; @@ -20,16 +20,17 @@ import ./make-test.nix ({ pkgs, ...} : { }; }; - testScript = - '' - $machine->succeed('[ -L "/etc/plainFile" ]'); - $machine->succeed('cat "/etc/plainFile" | grep "Hello World"'); - $machine->succeed('[ -d "/etc/folder" ]'); - $machine->succeed('[ -d "/etc/folder/with" ]'); - $machine->succeed('[ -L "/etc/folder/with/file" ]'); - $machine->succeed('cat "/etc/plainFile" | grep "Hello World"'); + testScript = '' + machine.succeed('[ -L "/etc/plainFile" ]') + assert "Hello World" in machine.succeed('cat "/etc/plainFile"') + machine.succeed('[ -d "/etc/folder" ]') + machine.succeed('[ -d "/etc/folder/with" ]') + machine.succeed('[ -L "/etc/folder/with/file" ]') + assert "Hello World" in machine.succeed('cat "/etc/plainFile"') - $machine->succeed('echo ''${TERMINFO_DIRS} | grep "/run/current-system/sw/share/terminfo"'); - $machine->succeed('echo ''${NIXCON} | grep "awesome"'); - ''; + assert "/run/current-system/sw/share/terminfo" in machine.succeed( + "echo ''${TERMINFO_DIRS}" + ) + assert "awesome" in machine.succeed("echo ''${NIXCON}") + ''; }) diff --git a/nixos/tests/fenics.nix b/nixos/tests/fenics.nix new file mode 100644 index 0000000000000..7252d19e4e654 --- /dev/null +++ b/nixos/tests/fenics.nix @@ -0,0 +1,50 @@ +import ./make-test-python.nix ({ pkgs, ... }: + +let + fenicsScript = pkgs.writeScript "poisson.py" '' + #!/usr/bin/env python + from dolfin import * + + mesh = UnitSquareMesh(4, 4) + V = FunctionSpace(mesh, "Lagrange", 1) + + def boundary(x): + return x[0] < DOLFIN_EPS or x[0] > 1.0 - DOLFIN_EPS + + u0 = Constant(0.0) + bc = DirichletBC(V, u0, boundary) + + u = TrialFunction(V) + v = TestFunction(V) + f = Expression("10*exp(-(pow(x[0] - 0.5, 2) + pow(x[1] - 0.5, 2)) / 0.02)", degree=2) + g = Expression("sin(5*x[0])", degree=2) + a = inner(grad(u), grad(v))*dx + L = f*v*dx + g*v*ds + + u = Function(V) + solve(a == L, u, bc) + print(u) + ''; +in +{ + name = "fenics"; + meta = { + maintainers = with pkgs.stdenv.lib.maintainers; [ knedlsepp ]; + }; + + nodes = { + fenicsnode = { pkgs, ... }: { + environment.systemPackages = with pkgs; [ + gcc + (python3.withPackages (ps: with ps; [ fenics ])) + ]; + virtualisation.memorySize = 512; + }; + }; + testScript = + { nodes, ... }: + '' + start_all() + node1.succeed("${fenicsScript}") + ''; +}) diff --git a/nixos/tests/ferm.nix b/nixos/tests/ferm.nix index edf9c8036aca4..a73c9ce739cf1 100644 --- a/nixos/tests/ferm.nix +++ b/nixos/tests/ferm.nix @@ -1,5 +1,5 @@ -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, ...} : { name = "ferm"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ mic92 ]; @@ -53,24 +53,22 @@ import ./make-test.nix ({ pkgs, ...} : { testScript = '' - startAll; + start_all() - $client->waitForUnit("network-online.target"); - $server->waitForUnit("ferm.service"); - $server->waitForUnit("nginx.service"); - $server->waitUntilSucceeds("ss -ntl | grep -q 80"); + client.wait_for_unit("network-online.target") + server.wait_for_unit("ferm.service") + server.wait_for_unit("nginx.service") + server.wait_until_succeeds("ss -ntl | grep -q 80") - subtest "port 80 is allowed", sub { - $client->succeed("curl --fail -g http://192.168.1.1:80/status"); - $client->succeed("curl --fail -g http://[fd00::1]:80/status"); - }; + with subtest("port 80 is allowed"): + client.succeed("curl --fail -g http://192.168.1.1:80/status") + client.succeed("curl --fail -g http://[fd00::1]:80/status") - subtest "port 8080 is not allowed", sub { - $server->succeed("curl --fail -g http://192.168.1.1:8080/status"); - $server->succeed("curl --fail -g http://[fd00::1]:8080/status"); + with subtest("port 8080 is not allowed"): + server.succeed("curl --fail -g http://192.168.1.1:8080/status") + server.succeed("curl --fail -g http://[fd00::1]:8080/status") - $client->fail("curl --fail -g http://192.168.1.1:8080/status"); - $client->fail("curl --fail -g http://[fd00::1]:8080/status"); - }; + client.fail("curl --fail -g http://192.168.1.1:8080/status") + client.fail("curl --fail -g http://[fd00::1]:8080/status") ''; }) diff --git a/nixos/tests/firefox.nix b/nixos/tests/firefox.nix index 56ddabbae7714..7071baceba739 100644 --- a/nixos/tests/firefox.nix +++ b/nixos/tests/firefox.nix @@ -1,4 +1,4 @@ -import ./make-test-python.nix ({ pkgs, ... }: { +import ./make-test-python.nix ({ pkgs, esr ? false, ... }: { name = "firefox"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ eelco shlevy ]; @@ -8,7 +8,9 @@ import ./make-test-python.nix ({ pkgs, ... }: { { pkgs, ... }: { imports = [ ./common/x11.nix ]; - environment.systemPackages = [ pkgs.firefox pkgs.xdotool ]; + environment.systemPackages = + (if esr then [ pkgs.firefox-esr ] else [ pkgs.firefox ]) + ++ [ pkgs.xdotool ]; }; testScript = '' diff --git a/nixos/tests/flannel.nix b/nixos/tests/flannel.nix index 9991c5eaa329a..7615732c20cac 100644 --- a/nixos/tests/flannel.nix +++ b/nixos/tests/flannel.nix @@ -1,20 +1,24 @@ -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ lib, ...} : { name = "flannel"; - meta = with pkgs.stdenv.lib.maintainers; { + meta = with lib.maintainers; { maintainers = [ offline ]; }; nodes = let - flannelConfig = { + flannelConfig = { pkgs, ... } : { services.flannel = { enable = true; + backend = { + Type = "udp"; + Port = 8285; + }; network = "10.1.0.0/16"; iface = "eth1"; etcd.endpoints = ["http://etcd:2379"]; }; - networking.firewall.allowedUDPPorts = [ 8472 ]; + networking.firewall.allowedUDPPorts = [ 8285 ]; }; in { etcd = { ... }: { @@ -32,25 +36,22 @@ import ./make-test.nix ({ pkgs, ...} : { networking.firewall.allowedTCPPorts = [ 2379 ]; }; - node1 = { ... }: { - require = [flannelConfig]; - }; - - node2 = { ... }: { - require = [flannelConfig]; - }; + node1 = flannelConfig; + node2 = flannelConfig; }; testScript = '' - startAll; + start_all() - $node1->waitForUnit("flannel.service"); - $node2->waitForUnit("flannel.service"); + node1.wait_for_unit("flannel.service") + node2.wait_for_unit("flannel.service") - my $ip1 = $node1->succeed("ip -4 addr show flannel.1 | grep -oP '(?<=inet).*(?=/)'"); - my $ip2 = $node2->succeed("ip -4 addr show flannel.1 | grep -oP '(?<=inet).*(?=/)'"); + node1.wait_until_succeeds("ip l show dev flannel0") + ip1 = node1.succeed("ip -4 addr show flannel0 | grep -oP '(?<=inet).*(?=/)'") + node2.wait_until_succeeds("ip l show dev flannel0") + ip2 = node2.succeed("ip -4 addr show flannel0 | grep -oP '(?<=inet).*(?=/)'") - $node1->waitUntilSucceeds("ping -c 1 $ip2"); - $node2->waitUntilSucceeds("ping -c 1 $ip1"); + node1.wait_until_succeeds(f"ping -c 1 {ip2}") + node2.wait_until_succeeds(f"ping -c 1 {ip1}") ''; }) diff --git a/nixos/tests/freeswitch.nix b/nixos/tests/freeswitch.nix new file mode 100644 index 0000000000000..349d0e7bc6f0d --- /dev/null +++ b/nixos/tests/freeswitch.nix @@ -0,0 +1,29 @@ +import ./make-test-python.nix ({ pkgs, ...} : { + name = "freeswitch"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ misuzu ]; + }; + nodes = { + node0 = { config, lib, ... }: { + networking.useDHCP = false; + networking.interfaces.eth1 = { + ipv4.addresses = [ + { + address = "192.168.0.1"; + prefixLength = 24; + } + ]; + }; + services.freeswitch = { + enable = true; + enableReload = true; + configTemplate = "${config.services.freeswitch.package}/share/freeswitch/conf/minimal"; + }; + }; + }; + testScript = '' + node0.wait_for_unit("freeswitch.service") + # Wait for SIP port to be open + node0.wait_for_open_port("5060") + ''; +}) diff --git a/nixos/tests/gerrit.nix b/nixos/tests/gerrit.nix new file mode 100644 index 0000000000000..6cee64a20095a --- /dev/null +++ b/nixos/tests/gerrit.nix @@ -0,0 +1,55 @@ +import ./make-test-python.nix ({ pkgs, ... }: + +let + lfs = pkgs.fetchurl { + url = "https://gerrit-ci.gerritforge.com/job/plugin-lfs-bazel-master/90/artifact/bazel-bin/plugins/lfs/lfs.jar"; + sha256 = "023b0kd8djm3cn1lf1xl67yv3j12yl8bxccn42lkfmwxjwjfqw6h"; + }; + +in { + name = "gerrit"; + + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ flokli zimbatm ]; + }; + + nodes = { + server = + { config, pkgs, ... }: { + networking.firewall.allowedTCPPorts = [ 80 2222 ]; + + virtualisation.memorySize = 1024; + + services.gerrit = { + enable = true; + serverId = "aa76c84b-50b0-4711-a0a0-1ee30e45bbd0"; + listenAddress = "[::]:80"; + jvmHeapLimit = "1g"; + + plugins = [ lfs ]; + builtinPlugins = [ "hooks" "webhooks" ]; + settings = { + gerrit.canonicalWebUrl = "http://server"; + lfs.plugin = "lfs"; + plugins.allowRemoteAdmin = true; + sshd.listenAddress = "[::]:2222"; + sshd.advertisedAddress = "[::]:2222"; + }; + }; + }; + + client = + { ... }: { + }; + }; + + testScript = '' + start_all() + server.wait_for_unit("gerrit.service") + server.wait_for_open_port(80) + client.succeed("curl http://server") + + server.wait_for_open_port(2222) + client.succeed("nc -z server 2222") + ''; +}) diff --git a/nixos/tests/gitdaemon.nix b/nixos/tests/gitdaemon.nix new file mode 100644 index 0000000000000..b610caf06fb2a --- /dev/null +++ b/nixos/tests/gitdaemon.nix @@ -0,0 +1,64 @@ +import ./make-test-python.nix ({ pkgs, ... }: + +let + hashes = pkgs.writeText "hashes" '' + b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c /project/bar + ''; +in { + name = "gitdaemon"; + + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ tilpner ]; + }; + + nodes = { + server = + { config, ... }: { + networking.firewall.allowedTCPPorts = [ config.services.gitDaemon.port ]; + + environment.systemPackages = [ pkgs.git ]; + + services.gitDaemon = { + enable = true; + basePath = "/git"; + }; + }; + + client = + { pkgs, ... }: { + environment.systemPackages = [ pkgs.git ]; + }; + }; + + testScript = '' + start_all() + + with subtest("create project.git"): + server.succeed( + "mkdir /git", + "git init --bare /git/project.git", + "touch /git/project.git/git-daemon-export-ok", + ) + + with subtest("add file to project.git"): + server.succeed( + "git clone /git/project.git /project", + "echo foo > /project/bar", + "git config --global user.email 'you@example.com'", + "git config --global user.name 'Your Name'", + "git -C /project add bar", + "git -C /project commit -m 'quux'", + "git -C /project push", + "rm -r /project", + ) + + with subtest("git daemon starts"): + server.wait_for_unit("git-daemon.service") + + with subtest("client can clone project.git"): + client.succeed( + "git clone git://server/project.git /project", + "sha256sum -c ${hashes}", + ) + ''; +}) diff --git a/nixos/tests/gitea.nix b/nixos/tests/gitea.nix index ffbc07cfbb21f..aaed2486421f9 100644 --- a/nixos/tests/gitea.nix +++ b/nixos/tests/gitea.nix @@ -6,64 +6,104 @@ with import ../lib/testing-python.nix { inherit system pkgs; }; with pkgs.lib; -{ - mysql = makeTest { - name = "gitea-mysql"; - meta.maintainers = with maintainers; [ aanderse kolaente ]; - - machine = - { config, pkgs, ... }: - { services.gitea.enable = true; - services.gitea.database.type = "mysql"; +let + supportedDbTypes = [ "mysql" "postgres" "sqlite3" ]; + makeGiteaTest = type: nameValuePair type (makeTest { + name = "gitea-${type}"; + meta.maintainers = with maintainers; [ aanderse kolaente ma27 ]; + + nodes = { + server = { config, pkgs, ... }: { + services.gitea = { + enable = true; + database = { inherit type; }; + disableRegistration = true; + }; + environment.systemPackages = [ pkgs.gitea pkgs.jq ]; + services.openssh.enable = true; + }; + client1 = { config, pkgs, ... }: { + environment.systemPackages = [ pkgs.git ]; + }; + client2 = { config, pkgs, ... }: { + environment.systemPackages = [ pkgs.git ]; }; + }; + + testScript = let + inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey; + in '' + GIT_SSH_COMMAND = "ssh -i $HOME/.ssh/privk -o StrictHostKeyChecking=no" + REPO = "gitea@server:test/repo" + PRIVK = "${snakeOilPrivateKey}" - testScript = '' start_all() - machine.wait_for_unit("gitea.service") - machine.wait_for_open_port(3000) - machine.succeed("curl --fail http://localhost:3000/") - ''; - }; + client1.succeed("mkdir /tmp/repo") + client1.succeed("mkdir -p $HOME/.ssh") + client1.succeed(f"cat {PRIVK} > $HOME/.ssh/privk") + client1.succeed("chmod 0400 $HOME/.ssh/privk") + client1.succeed("git -C /tmp/repo init") + client1.succeed("echo hello world > /tmp/repo/testfile") + client1.succeed("git -C /tmp/repo add .") + client1.succeed("git config --global user.email test@localhost") + client1.succeed("git config --global user.name test") + client1.succeed("git -C /tmp/repo commit -m 'Initial import'") + client1.succeed(f"git -C /tmp/repo remote add origin {REPO}") - postgres = makeTest { - name = "gitea-postgres"; - meta.maintainers = [ maintainers.aanderse ]; + server.wait_for_unit("gitea.service") + server.wait_for_open_port(3000) + server.succeed("curl --fail http://localhost:3000/") - machine = - { config, pkgs, ... }: - { services.gitea.enable = true; - services.gitea.database.type = "postgres"; - }; + server.succeed( + "curl --fail http://localhost:3000/user/sign_up | grep 'Registration is disabled. " + + "Please contact your site administrator.'" + ) + server.succeed( + "su -l gitea -c 'GITEA_WORK_DIR=/var/lib/gitea gitea admin create-user " + + "--username test --password totallysafe --email test@localhost'" + ) - testScript = '' - start_all() + api_token = server.succeed( + "curl --fail -X POST http://test:totallysafe@localhost:3000/api/v1/users/test/tokens " + + "-H 'Accept: application/json' -H 'Content-Type: application/json' -d " + + "'{\"name\":\"token\"}' | jq '.sha1' | xargs echo -n" + ) - machine.wait_for_unit("gitea.service") - machine.wait_for_open_port(3000) - machine.succeed("curl --fail http://localhost:3000/") - ''; - }; + server.succeed( + "curl --fail -X POST http://localhost:3000/api/v1/user/repos " + + "-H 'Accept: application/json' -H 'Content-Type: application/json' " + + f"-H 'Authorization: token {api_token}'" + + ' -d \'{"auto_init":false, "description":"string", "license":"mit", "name":"repo", "private":false}\''' + ) - sqlite = makeTest { - name = "gitea-sqlite"; - meta.maintainers = [ maintainers.aanderse ]; + server.succeed( + "curl --fail -X POST http://localhost:3000/api/v1/user/keys " + + "-H 'Accept: application/json' -H 'Content-Type: application/json' " + + f"-H 'Authorization: token {api_token}'" + + ' -d \'{"key":"${snakeOilPublicKey}","read_only":true,"title":"SSH"}\''' + ) - machine = - { config, pkgs, ... }: - { services.gitea.enable = true; - services.gitea.disableRegistration = true; - }; + client1.succeed( + f"GIT_SSH_COMMAND='{GIT_SSH_COMMAND}' git -C /tmp/repo push origin master" + ) - testScript = '' - start_all() + client2.succeed("mkdir -p $HOME/.ssh") + client2.succeed(f"cat {PRIVK} > $HOME/.ssh/privk") + client2.succeed("chmod 0400 $HOME/.ssh/privk") + client2.succeed(f"GIT_SSH_COMMAND='{GIT_SSH_COMMAND}' git clone {REPO}") + client2.succeed('test "$(cat repo/testfile | xargs echo -n)" = "hello world"') - machine.wait_for_unit("gitea.service") - machine.wait_for_open_port(3000) - machine.succeed("curl --fail http://localhost:3000/") - machine.succeed( - "curl --fail http://localhost:3000/user/sign_up | grep 'Registration is disabled. Please contact your site administrator.'" + server.succeed( + 'test "$(curl http://localhost:3000/api/v1/repos/test/repo/commits ' + + '-H "Accept: application/json" | jq length)" = "1"' ) + + client1.shutdown() + client2.shutdown() + server.shutdown() ''; - }; -} + }); +in + +listToAttrs (map makeGiteaTest supportedDbTypes) diff --git a/nixos/tests/gitolite-fcgiwrap.nix b/nixos/tests/gitolite-fcgiwrap.nix new file mode 100644 index 0000000000000..414b7d6fe7efe --- /dev/null +++ b/nixos/tests/gitolite-fcgiwrap.nix @@ -0,0 +1,93 @@ +import ./make-test-python.nix ( + { pkgs, ... }: + + let + user = "gitolite-admin"; + password = "some_password"; + + # not used but needed to setup gitolite + adminPublicKey = '' + ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO7urFhAA90BTpGuEHeWWTY3W/g9PBxXNxfWhfbrm4Le root@client + ''; + in + { + name = "gitolite-fcgiwrap"; + + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ bbigras ]; + }; + + nodes = { + + server = + { ... }: + { + networking.firewall.allowedTCPPorts = [ 80 ]; + + services.fcgiwrap.enable = true; + services.gitolite = { + enable = true; + adminPubkey = adminPublicKey; + }; + + services.nginx = { + enable = true; + recommendedProxySettings = true; + virtualHosts."server".locations."/git".extraConfig = '' + # turn off gzip as git objects are already well compressed + gzip off; + + # use file based basic authentication + auth_basic "Git Repository Authentication"; + auth_basic_user_file /etc/gitolite/htpasswd; + + # common FastCGI parameters are required + include ${pkgs.nginx}/conf/fastcgi_params; + + # strip the CGI program prefix + fastcgi_split_path_info ^(/git)(.*)$; + fastcgi_param PATH_INFO $fastcgi_path_info; + + # pass authenticated user login(mandatory) to Gitolite + fastcgi_param REMOTE_USER $remote_user; + + # pass git repository root directory and hosting user directory + # these env variables can be set in a wrapper script + fastcgi_param GIT_HTTP_EXPORT_ALL ""; + fastcgi_param GIT_PROJECT_ROOT /var/lib/gitolite/repositories; + fastcgi_param GITOLITE_HTTP_HOME /var/lib/gitolite; + fastcgi_param SCRIPT_FILENAME ${pkgs.gitolite}/bin/gitolite-shell; + + # use Unix domain socket or inet socket + fastcgi_pass unix:/run/fcgiwrap.sock; + ''; + }; + + # WARNING: DON'T DO THIS IN PRODUCTION! + # This puts unhashed secrets directly into the Nix store for ease of testing. + environment.etc."gitolite/htpasswd".source = pkgs.runCommand "htpasswd" {} '' + ${pkgs.apacheHttpd}/bin/htpasswd -bc "$out" ${user} ${password} + ''; + }; + + client = + { pkgs, ... }: + { + environment.systemPackages = [ pkgs.git ]; + }; + }; + + testScript = '' + start_all() + + server.wait_for_unit("gitolite-init.service") + server.wait_for_unit("nginx.service") + server.wait_for_file("/run/fcgiwrap.sock") + + client.wait_for_unit("multi-user.target") + client.succeed( + "git clone http://${user}:${password}@server/git/gitolite-admin.git" + ) + ''; + } +) diff --git a/nixos/tests/glusterfs.nix b/nixos/tests/glusterfs.nix index 8f9cb8973d51e..cb07bc09511dd 100644 --- a/nixos/tests/glusterfs.nix +++ b/nixos/tests/glusterfs.nix @@ -4,10 +4,11 @@ let client = { pkgs, ... } : { environment.systemPackages = [ pkgs.glusterfs ]; fileSystems = pkgs.lib.mkVMOverride - [ { mountPoint = "/gluster"; - fsType = "glusterfs"; - device = "server1:/gv0"; - } ]; + { "/gluster" = + { device = "server1:/gv0"; + fsType = "glusterfs"; + }; + }; }; server = { pkgs, ... } : { @@ -22,11 +23,11 @@ let virtualisation.emptyDiskImages = [ 1024 ]; fileSystems = pkgs.lib.mkVMOverride - [ { mountPoint = "/data"; - device = "/dev/disk/by-label/data"; - fsType = "ext4"; - } - ]; + { "/data" = + { device = "/dev/disk/by-label/data"; + fsType = "ext4"; + }; + }; }; in { name = "glusterfs"; diff --git a/nixos/tests/gnome3-xorg.nix b/nixos/tests/gnome3-xorg.nix index eb4c376319be2..b59badcd5de40 100644 --- a/nixos/tests/gnome3-xorg.nix +++ b/nixos/tests/gnome3-xorg.nix @@ -1,41 +1,79 @@ -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, lib, ...} : { name = "gnome3-xorg"; - meta = with pkgs.stdenv.lib.maintainers; { - maintainers = pkgs.gnome3.maintainers; + meta = with lib; { + maintainers = teams.gnome.members; }; - machine = - { ... }: + machine = { nodes, ... }: let + user = nodes.machine.config.users.users.alice; + in { imports = [ ./common/user-account.nix ]; services.xserver.enable = true; - services.xserver.displayManager.gdm.enable = false; - services.xserver.displayManager.lightdm.enable = true; - services.xserver.displayManager.lightdm.autoLogin.enable = true; - services.xserver.displayManager.lightdm.autoLogin.user = "alice"; + services.xserver.displayManager.gdm = { + enable = true; + autoLogin = { + enable = true; + user = user.name; + }; + }; + services.xserver.desktopManager.gnome3.enable = true; - services.xserver.desktopManager.default = "gnome-xorg"; + services.xserver.displayManager.defaultSession = "gnome-xorg"; virtualisation.memorySize = 1024; }; - testScript = - '' - $machine->waitForX; + testScript = { nodes, ... }: let + user = nodes.machine.config.users.users.alice; + uid = toString user.uid; + bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${uid}/bus"; + xauthority = "/run/user/${uid}/gdm/Xauthority"; + display = "DISPLAY=:0.0"; + env = "${bus} XAUTHORITY=${xauthority} ${display}"; + gdbus = "${env} gdbus"; + su = command: "su - ${user.name} -c '${env} ${command}'"; + + # Call javascript in gnome shell, returns a tuple (success, output), where + # `success` is true if the dbus call was successful and output is what the + # javascript evaluates to. + eval = "call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval"; + + # False when startup is done + startingUp = su "${gdbus} ${eval} Main.layoutManager._startingUp"; + + # Start gnome-terminal + gnomeTerminalCommand = su "gnome-terminal"; - # wait for alice to be logged in - $machine->waitForUnit("default.target","alice"); + # Hopefully gnome-terminal's wm class + wmClass = su "${gdbus} ${eval} global.display.focus_window.wm_class"; + in '' + with subtest("Login to GNOME Xorg with GDM"): + machine.wait_for_x() + # Wait for alice to be logged in" + machine.wait_for_unit("default.target", "${user.name}") + machine.wait_for_file("${xauthority}") + machine.succeed("xauth merge ${xauthority}") + # Check that logging in has given the user ownership of devices + assert "alice" in machine.succeed("getfacl -p /dev/snd/timer") - # Check that logging in has given the user ownership of devices. - $machine->succeed("getfacl -p /dev/snd/timer | grep -q alice"); + with subtest("Wait for GNOME Shell"): + # correct output should be (true, 'false') + machine.wait_until_succeeds( + "${startingUp} | grep -q 'true,..false'" + ) - $machine->succeed("su - alice -c 'DISPLAY=:0.0 gnome-terminal &'"); - $machine->succeed("xauth merge ~alice/.Xauthority"); - $machine->waitForWindow(qr/alice.*machine/); - $machine->succeed("timeout 900 bash -c 'while read msg; do if [[ \$msg =~ \"GNOME Shell started\" ]]; then break; fi; done < <(journalctl -f)'"); - $machine->sleep(10); - $machine->screenshot("screen"); + with subtest("Open Gnome Terminal"): + machine.succeed( + "${gnomeTerminalCommand}" + ) + # correct output should be (true, '"Gnome-terminal"') + machine.wait_until_succeeds( + "${wmClass} | grep -q 'true,...Gnome-terminal'" + ) + machine.sleep(20) + machine.screenshot("screen") ''; }) diff --git a/nixos/tests/gnome3.nix b/nixos/tests/gnome3.nix index ab363efb6a197..17e72c5f65103 100644 --- a/nixos/tests/gnome3.nix +++ b/nixos/tests/gnome3.nix @@ -1,7 +1,7 @@ -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, lib, ...} : { name = "gnome3"; - meta = with pkgs.stdenv.lib.maintainers; { - maintainers = pkgs.gnome3.maintainers; + meta = with lib; { + maintainers = teams.gnome.members; }; machine = @@ -24,41 +24,53 @@ import ./make-test.nix ({ pkgs, ...} : { virtualisation.memorySize = 1024; }; - testScript = let + testScript = { nodes, ... }: let # Keep line widths somewhat managable - bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus"; + user = nodes.machine.config.users.users.alice; + uid = toString user.uid; + bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${uid}/bus"; gdbus = "${bus} gdbus"; + su = command: "su - ${user.name} -c '${command}'"; + # Call javascript in gnome shell, returns a tuple (success, output), where # `success` is true if the dbus call was successful and output is what the # javascript evaluates to. eval = "call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval"; - # False when startup is done - startingUp = "${gdbus} ${eval} Main.layoutManager._startingUp"; - # Hopefully gnome-terminal's wm class - wmClass = "${gdbus} ${eval} global.display.focus_window.wm_class"; - in '' - # wait for gdm to start - $machine->waitForUnit("display-manager.service"); - # wait for alice to be logged in - $machine->waitForUnit("default.target","alice"); - - # Check that logging in has given the user ownership of devices. - $machine->succeed("getfacl -p /dev/snd/timer | grep -q alice"); + # False when startup is done + startingUp = su "${gdbus} ${eval} Main.layoutManager._startingUp"; - # Wait for the wayland server - $machine->waitForFile("/run/user/1000/wayland-0"); + # Start gnome-terminal + gnomeTerminalCommand = su "${bus} gnome-terminal"; - # Wait for gnome shell, correct output should be "(true, 'false')" - $machine->waitUntilSucceeds("su - alice -c '${startingUp} | grep -q true,..false'"); + # Hopefully gnome-terminal's wm class + wmClass = su "${gdbus} ${eval} global.display.focus_window.wm_class"; + in '' + with subtest("Login to GNOME with GDM"): + # wait for gdm to start + machine.wait_for_unit("display-manager.service") + # wait for the wayland server + machine.wait_for_file("/run/user/${uid}/wayland-0") + # wait for alice to be logged in + machine.wait_for_unit("default.target", "${user.name}") + # check that logging in has given the user ownership of devices + assert "alice" in machine.succeed("getfacl -p /dev/snd/timer") - # open a terminal - $machine->succeed("su - alice -c '${bus} gnome-terminal'"); - # and check it's there - $machine->waitUntilSucceeds("su - alice -c '${wmClass} | grep -q gnome-terminal-server'"); + with subtest("Wait for GNOME Shell"): + # correct output should be (true, 'false') + machine.wait_until_succeeds( + "${startingUp} | grep -q 'true,..false'" + ) - # wait to get a nice screenshot - $machine->sleep(20); - $machine->screenshot("screen"); + with subtest("Open Gnome Terminal"): + machine.succeed( + "${gnomeTerminalCommand}" + ) + # correct output should be (true, '"gnome-terminal-server"') + machine.wait_until_succeeds( + "${wmClass} | grep -q 'gnome-terminal-server'" + ) + machine.sleep(20) + machine.screenshot("screen") ''; }) diff --git a/nixos/tests/gocd-agent.nix b/nixos/tests/gocd-agent.nix index 50470379576b1..5b630a40736e9 100644 --- a/nixos/tests/gocd-agent.nix +++ b/nixos/tests/gocd-agent.nix @@ -9,14 +9,18 @@ let header = "Accept: application/vnd.go.cd.v2+json"; in -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, ...} : { name = "gocd-agent"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ grahamc swarren83 ]; + + # gocd agent needs to register with the autoregister key created on first server startup, + # but NixOS module doesn't seem to allow to pass during runtime currently + broken = true; }; nodes = { - gocd_agent = + agent = { ... }: { virtualisation.memorySize = 2046; @@ -30,11 +34,15 @@ import ./make-test.nix ({ pkgs, ...} : { }; testScript = '' - startAll; - $gocd_agent->waitForUnit("gocd-server"); - $gocd_agent->waitForOpenPort("8153"); - $gocd_agent->waitForUnit("gocd-agent"); - $gocd_agent->waitUntilSucceeds("curl ${serverUrl} -H '${header}' | ${pkgs.jq}/bin/jq -e ._embedded.agents[0].uuid"); - $gocd_agent->succeed("curl ${serverUrl} -H '${header}' | ${pkgs.jq}/bin/jq -e ._embedded.agents[0].agent_state | grep -q Idle"); + start_all() + agent.wait_for_unit("gocd-server") + agent.wait_for_open_port("8153") + agent.wait_for_unit("gocd-agent") + agent.wait_until_succeeds( + "curl ${serverUrl} -H '${header}' | ${pkgs.jq}/bin/jq -e ._embedded.agents[0].uuid" + ) + agent.succeed( + "curl ${serverUrl} -H '${header}' | ${pkgs.jq}/bin/jq -e ._embedded.agents[0].agent_state | grep -q Idle" + ) ''; }) diff --git a/nixos/tests/gocd-server.nix b/nixos/tests/gocd-server.nix index 80cf04ed64049..20faf85a1ccd8 100644 --- a/nixos/tests/gocd-server.nix +++ b/nixos/tests/gocd-server.nix @@ -2,7 +2,7 @@ # 1. GoCD server starts # 2. GoCD server responds -import ./make-test.nix ({ pkgs, ...} : +import ./make-test-python.nix ({ pkgs, ...} : { name = "gocd-server"; @@ -10,19 +10,19 @@ import ./make-test.nix ({ pkgs, ...} : maintainers = [ swarren83 ]; }; -nodes = { - gocd_server = - { ... }: - { - virtualisation.memorySize = 2046; - services.gocd-server.enable = true; - }; -}; + nodes = { + server = + { ... }: + { + virtualisation.memorySize = 2046; + services.gocd-server.enable = true; + }; + }; testScript = '' - $gocd_server->start; - $gocd_server->waitForUnit("gocd-server"); - $gocd_server->waitForOpenPort("8153"); - $gocd_server->waitUntilSucceeds("curl -s -f localhost:8153/go"); + server.start() + server.wait_for_unit("gocd-server") + server.wait_for_open_port(8153) + server.wait_until_succeeds("curl -s -f localhost:8153/go") ''; }) diff --git a/nixos/tests/graphite.nix b/nixos/tests/graphite.nix index 27a87bdbb9f29..71776a94cbd64 100644 --- a/nixos/tests/graphite.nix +++ b/nixos/tests/graphite.nix @@ -1,41 +1,50 @@ -import ./make-test.nix ({ pkgs, ... } : +import ./make-test-python.nix ({ pkgs, ... } : { name = "graphite"; + meta = { + # Fails on dependency `python-2.7-Twisted`'s test suite + # complaining `ImportError: No module named zope.interface`. + broken = true; + }; nodes = { one = { ... }: { virtualisation.memorySize = 1024; time.timeZone = "UTC"; services.graphite = { - web.enable = true; + web = { + enable = true; + extraConfig = '' + SECRET_KEY = "abcd"; + ''; + }; api = { enable = true; port = 8082; - finders = [ pkgs.python27Packages.influxgraph ]; + finders = [ pkgs.python3Packages.influxgraph ]; }; carbon.enableCache = true; - seyren.enable = true; - pager.enable = true; + seyren.enable = false; # Implicitely requires openssl-1.0.2u which is marked insecure beacon.enable = true; }; }; }; testScript = '' - startAll; - $one->waitForUnit("default.target"); - $one->waitForUnit("graphiteWeb.service"); - $one->waitForUnit("graphiteApi.service"); - $one->waitForUnit("graphitePager.service"); - $one->waitForUnit("graphite-beacon.service"); - $one->waitForUnit("carbonCache.service"); - $one->waitForUnit("seyren.service"); + start_all() + one.wait_for_unit("default.target") + one.wait_for_unit("graphiteWeb.service") + one.wait_for_unit("graphiteApi.service") + one.wait_for_unit("graphite-beacon.service") + one.wait_for_unit("carbonCache.service") # The services above are of type "simple". systemd considers them active immediately # even if they're still in preStart (which takes quite long for graphiteWeb). # Wait for ports to open so we're sure the services are up and listening. - $one->waitForOpenPort(8080); - $one->waitForOpenPort(2003); - $one->succeed("echo \"foo 1 `date +%s`\" | nc -N localhost 2003"); - $one->waitUntilSucceeds("curl 'http://localhost:8080/metrics/find/?query=foo&format=treejson' --silent | grep foo >&2"); + one.wait_for_open_port(8080) + one.wait_for_open_port(2003) + one.succeed('echo "foo 1 `date +%s`" | nc -N localhost 2003') + one.wait_until_succeeds( + "curl 'http://localhost:8080/metrics/find/?query=foo&format=treejson' --silent | grep foo >&2" + ) ''; }) diff --git a/nixos/tests/grocy.nix b/nixos/tests/grocy.nix new file mode 100644 index 0000000000000..7fa479ed2c42e --- /dev/null +++ b/nixos/tests/grocy.nix @@ -0,0 +1,47 @@ +import ./make-test-python.nix ({ pkgs, ... }: { + name = "grocy"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ ma27 ]; + }; + + machine = { pkgs, ... }: { + services.grocy = { + enable = true; + hostName = "localhost"; + nginx.enableSSL = false; + }; + environment.systemPackages = [ pkgs.jq ]; + }; + + testScript = '' + machine.start() + machine.wait_for_open_port(80) + machine.wait_for_unit("multi-user.target") + + machine.succeed("curl -sSf http://localhost") + + machine.succeed( + "curl -c cookies -sSf -X POST http://localhost/login -d 'username=admin&password=admin'" + ) + + cookie = machine.succeed( + "grep -v '^#' cookies | awk '{ print $7 }' | sed -e '/^$/d' | perl -pe 'chomp'" + ) + + machine.succeed( + f"curl -sSf -X POST http://localhost/api/objects/tasks -b 'grocy_session={cookie}' " + + '-d \'{"assigned_to_user_id":1,"name":"Test Task","due_date":"1970-01-01"}\''' + + " --header 'Content-Type: application/json'" + ) + + task_name = machine.succeed( + f"curl -sSf http://localhost/api/tasks -b 'grocy_session={cookie}' --header 'Accept: application/json' | jq '.[].name' | xargs echo | perl -pe 'chomp'" + ) + + assert task_name == "Test Task" + + machine.succeed("curl -sSfI http://localhost/api/tasks 2>&1 | grep '401 Unauthorized'") + + machine.shutdown() + ''; +}) diff --git a/nixos/tests/gvisor.nix b/nixos/tests/gvisor.nix new file mode 100644 index 0000000000000..4d68a1d8a5f89 --- /dev/null +++ b/nixos/tests/gvisor.nix @@ -0,0 +1,49 @@ +# This test runs a container through gvisor and checks if simple container starts + +import ./make-test-python.nix ({ pkgs, ...} : { + name = "gvisor"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ andrew-d ]; + }; + + nodes = { + gvisor = + { pkgs, ... }: + { + virtualisation.docker = { + enable = true; + extraOptions = "--add-runtime runsc=${pkgs.gvisor}/bin/runsc"; + }; + + networking = { + dhcpcd.enable = false; + defaultGateway = "192.168.1.1"; + interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [ + { address = "192.168.1.2"; prefixLength = 24; } + ]; + }; + }; + }; + + testScript = '' + start_all() + + gvisor.wait_for_unit("network.target") + gvisor.wait_for_unit("sockets.target") + + # Start by verifying that gvisor itself works + output = gvisor.succeed( + "${pkgs.gvisor}/bin/runsc -alsologtostderr do ${pkgs.coreutils}/bin/echo hello world" + ) + assert output.strip() == "hello world" + + # Also test the Docker runtime + gvisor.succeed("tar cv --files-from /dev/null | docker import - scratchimg") + gvisor.succeed( + "docker run -d --name=sleeping --runtime=runsc -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10" + ) + gvisor.succeed("docker ps | grep sleeping") + gvisor.succeed("docker stop sleeping") + ''; +}) + diff --git a/nixos/tests/hadoop/hdfs.nix b/nixos/tests/hadoop/hdfs.nix index e7d72a56e1e7b..85aaab34b158f 100644 --- a/nixos/tests/hadoop/hdfs.nix +++ b/nixos/tests/hadoop/hdfs.nix @@ -1,4 +1,4 @@ -import ../make-test.nix ({...}: { +import ../make-test-python.nix ({...}: { nodes = { namenode = {pkgs, ...}: { services.hadoop = { @@ -35,20 +35,20 @@ import ../make-test.nix ({...}: { }; testScript = '' - startAll + start_all() - $namenode->waitForUnit("hdfs-namenode"); - $namenode->waitForUnit("network.target"); - $namenode->waitForOpenPort(8020); - $namenode->waitForOpenPort(9870); + namenode.wait_for_unit("hdfs-namenode") + namenode.wait_for_unit("network.target") + namenode.wait_for_open_port(8020) + namenode.wait_for_open_port(9870) - $datanode->waitForUnit("hdfs-datanode"); - $datanode->waitForUnit("network.target"); - $datanode->waitForOpenPort(9864); - $datanode->waitForOpenPort(9866); - $datanode->waitForOpenPort(9867); + datanode.wait_for_unit("hdfs-datanode") + datanode.wait_for_unit("network.target") + datanode.wait_for_open_port(9864) + datanode.wait_for_open_port(9866) + datanode.wait_for_open_port(9867) - $namenode->succeed("curl http://namenode:9870"); - $datanode->succeed("curl http://datanode:9864"); + namenode.succeed("curl http://namenode:9870") + datanode.succeed("curl http://datanode:9864") ''; }) diff --git a/nixos/tests/hadoop/yarn.nix b/nixos/tests/hadoop/yarn.nix index 031592301f172..2264ecaff1555 100644 --- a/nixos/tests/hadoop/yarn.nix +++ b/nixos/tests/hadoop/yarn.nix @@ -1,4 +1,4 @@ -import ../make-test.nix ({...}: { +import ../make-test-python.nix ({...}: { nodes = { resourcemanager = {pkgs, ...}: { services.hadoop.package = pkgs.hadoop_3_1; @@ -28,19 +28,19 @@ import ../make-test.nix ({...}: { }; testScript = '' - startAll; + start_all() - $resourcemanager->waitForUnit("yarn-resourcemanager"); - $resourcemanager->waitForUnit("network.target"); - $resourcemanager->waitForOpenPort(8031); - $resourcemanager->waitForOpenPort(8088); + resourcemanager.wait_for_unit("yarn-resourcemanager") + resourcemanager.wait_for_unit("network.target") + resourcemanager.wait_for_open_port(8031) + resourcemanager.wait_for_open_port(8088) - $nodemanager->waitForUnit("yarn-nodemanager"); - $nodemanager->waitForUnit("network.target"); - $nodemanager->waitForOpenPort(8042); - $nodemanager->waitForOpenPort(8041); + nodemanager.wait_for_unit("yarn-nodemanager") + nodemanager.wait_for_unit("network.target") + nodemanager.wait_for_open_port(8042) + nodemanager.wait_for_open_port(8041) - $resourcemanager->succeed("curl http://localhost:8088"); - $nodemanager->succeed("curl http://localhost:8042"); + resourcemanager.succeed("curl http://localhost:8088") + nodemanager.succeed("curl http://localhost:8042") ''; }) diff --git a/nixos/tests/haka.nix b/nixos/tests/haka.nix index 6277ebb4933fd..3ca19cb0971c7 100644 --- a/nixos/tests/haka.nix +++ b/nixos/tests/haka.nix @@ -1,6 +1,6 @@ # This test runs haka and probes it with hakactl -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, ...} : { name = "haka"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ tvestelind ]; @@ -15,10 +15,10 @@ import ./make-test.nix ({ pkgs, ...} : { }; testScript = '' - startAll; + start_all() - $haka->waitForUnit("haka.service"); - $haka->succeed("hakactl status"); - $haka->succeed("hakactl stop"); + haka.wait_for_unit("haka.service") + haka.succeed("hakactl status") + haka.succeed("hakactl stop") ''; }) diff --git a/nixos/tests/haproxy.nix b/nixos/tests/haproxy.nix index 72e77a68193e3..79f34b07faf4b 100644 --- a/nixos/tests/haproxy.nix +++ b/nixos/tests/haproxy.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, ...}: { +import ./make-test-python.nix ({ pkgs, ...}: { name = "haproxy"; nodes = { machine = { ... }: { @@ -23,21 +23,25 @@ import ./make-test.nix ({ pkgs, ...}: { }; services.httpd = { enable = true; - documentRoot = pkgs.writeTextDir "index.txt" "We are all good!"; - adminAddr = "notme@yourhost.local"; - listen = [{ - ip = "::1"; - port = 8000; - }]; + virtualHosts.localhost = { + documentRoot = pkgs.writeTextDir "index.txt" "We are all good!"; + adminAddr = "notme@yourhost.local"; + listen = [{ + ip = "::1"; + port = 8000; + }]; + }; }; }; }; testScript = '' - startAll; - $machine->waitForUnit('multi-user.target'); - $machine->waitForUnit('haproxy.service'); - $machine->waitForUnit('httpd.service'); - $machine->succeed('curl -k http://localhost:80/index.txt | grep "We are all good!"'); - $machine->succeed('curl -k http://localhost:80/metrics | grep haproxy_process_pool_allocated_bytes'); + start_all() + machine.wait_for_unit("multi-user.target") + machine.wait_for_unit("haproxy.service") + machine.wait_for_unit("httpd.service") + assert "We are all good!" in machine.succeed("curl -k http://localhost:80/index.txt") + assert "haproxy_process_pool_allocated_bytes" in machine.succeed( + "curl -k http://localhost:80/metrics" + ) ''; }) diff --git a/nixos/tests/hardened.nix b/nixos/tests/hardened.nix index cbf76f9e55877..5ed0dfcf9ab8f 100644 --- a/nixos/tests/hardened.nix +++ b/nixos/tests/hardened.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test.nix ({ pkgs, latestKernel ? false, ... } : { name = "hardened"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ joachifm ]; @@ -10,6 +10,8 @@ import ./make-test.nix ({ pkgs, ...} : { { users.users.alice = { isNormalUser = true; extraGroups = [ "proc" ]; }; users.users.sybil = { isNormalUser = true; group = "wheel"; }; imports = [ ../modules/profiles/hardened.nix ]; + boot.kernelPackages = + lib.mkIf latestKernel pkgs.linuxPackages_latest_hardened; environment.memoryAllocator.provider = "graphene-hardened"; nix.useSandbox = false; virtualisation.emptyDiskImages = [ 4096 ]; @@ -23,7 +25,9 @@ import ./make-test.nix ({ pkgs, ...} : { options = [ "noauto" ]; }; }; - boot.extraModulePackages = [ config.boot.kernelPackages.wireguard ]; + boot.extraModulePackages = + optional (versionOlder config.boot.kernelPackages.kernel.version "5.6") + config.boot.kernelPackages.wireguard; boot.kernelModules = [ "wireguard" ]; }; @@ -76,7 +80,8 @@ import ./make-test.nix ({ pkgs, ...} : { # Test userns subtest "userns", sub { - $machine->fail("unshare --user"); + $machine->succeed("unshare --user true"); + $machine->fail("su -l alice -c 'unshare --user true'"); }; # Test dmesg restriction diff --git a/nixos/tests/hitch/default.nix b/nixos/tests/hitch/default.nix index cb24c4dcffc24..904d12619d70e 100644 --- a/nixos/tests/hitch/default.nix +++ b/nixos/tests/hitch/default.nix @@ -1,4 +1,4 @@ -import ../make-test.nix ({ pkgs, ... }: +import ../make-test-python.nix ({ pkgs, ... }: { name = "hitch"; meta = with pkgs.stdenv.lib.maintainers; { @@ -16,18 +16,18 @@ import ../make-test.nix ({ pkgs, ... }: services.httpd = { enable = true; - documentRoot = ./example; + virtualHosts.localhost.documentRoot = ./example; adminAddr = "noone@testing.nowhere"; }; }; testScript = '' - startAll; + start_all() - $machine->waitForUnit('multi-user.target'); - $machine->waitForUnit('hitch.service'); - $machine->waitForOpenPort(443); - $machine->succeed('curl -k https://localhost:443/index.txt | grep "We are all good!"'); + machine.wait_for_unit("multi-user.target") + machine.wait_for_unit("hitch.service") + machine.wait_for_open_port(443) + assert "We are all good!" in machine.succeed("curl -k https://localhost:443/index.txt") ''; }) diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix index 6b53914fd8598..80dca43f1f3df 100644 --- a/nixos/tests/home-assistant.nix +++ b/nixos/tests/home-assistant.nix @@ -1,11 +1,10 @@ -import ./make-test.nix ({ pkgs, ... }: +import ./make-test-python.nix ({ pkgs, ... }: let configDir = "/var/lib/foobar"; apiPassword = "some_secret"; mqttPassword = "another_secret"; hassCli = "hass-cli --server http://hass:8123 --password '${apiPassword}'"; - in { name = "home-assistant"; meta = with pkgs.stdenv.lib; { @@ -69,36 +68,44 @@ in { }; testScript = '' - startAll; - $hass->waitForUnit("home-assistant.service"); - - # The config is specified using a Nix attribute set, - # converted from JSON to YAML, and linked to the config dir - $hass->succeed("test -L ${configDir}/configuration.yaml"); - # The lovelace config is copied because lovelaceConfigWritable = true - $hass->succeed("test -f ${configDir}/ui-lovelace.yaml"); - - # Check that Home Assistant's web interface and API can be reached - $hass->waitForOpenPort(8123); - $hass->succeed("curl --fail http://localhost:8123/states"); - $hass->succeed("curl --fail -H 'x-ha-access: ${apiPassword}' http://localhost:8123/api/ | grep -qF 'API running'"); - - # Toggle a binary sensor using MQTT - $hass->succeed("curl http://localhost:8123/api/states/binary_sensor.mqtt_binary_sensor -H 'x-ha-access: ${apiPassword}' | grep -qF '\"state\": \"off\"'"); - $hass->waitUntilSucceeds("mosquitto_pub -V mqttv311 -t home-assistant/test -u homeassistant -P '${mqttPassword}' -m let_there_be_light"); - $hass->succeed("curl http://localhost:8123/api/states/binary_sensor.mqtt_binary_sensor -H 'x-ha-access: ${apiPassword}' | grep -qF '\"state\": \"on\"'"); - - # Toggle a binary sensor using hass-cli - $hass->succeed("${hassCli} --output json state get binary_sensor.mqtt_binary_sensor | grep -qF '\"state\": \"on\"'"); - $hass->succeed("${hassCli} state edit binary_sensor.mqtt_binary_sensor --json='{\"state\": \"off\"}'"); - $hass->succeed("curl http://localhost:8123/api/states/binary_sensor.mqtt_binary_sensor -H 'x-ha-access: ${apiPassword}' | grep -qF '\"state\": \"off\"'"); - - # Print log to ease debugging - my $log = $hass->succeed("cat ${configDir}/home-assistant.log"); - print "\n### home-assistant.log ###\n"; - print "$log\n"; + start_all() + hass.wait_for_unit("home-assistant.service") + with subtest("Check that YAML configuration file is in place"): + hass.succeed("test -L ${configDir}/configuration.yaml") + with subtest("lovelace config is copied because lovelaceConfigWritable = true"): + hass.succeed("test -f ${configDir}/ui-lovelace.yaml") + with subtest("Check that Home Assistant's web interface and API can be reached"): + hass.wait_for_open_port(8123) + hass.succeed("curl --fail http://localhost:8123/states") + assert "API running" in hass.succeed( + "curl --fail -H 'x-ha-access: ${apiPassword}' http://localhost:8123/api/" + ) + with subtest("Toggle a binary sensor using MQTT"): + assert '"state": "off"' in hass.succeed( + "curl http://localhost:8123/api/states/binary_sensor.mqtt_binary_sensor -H 'x-ha-access: ${apiPassword}'" + ) + hass.wait_until_succeeds( + "mosquitto_pub -V mqttv311 -t home-assistant/test -u homeassistant -P '${mqttPassword}' -m let_there_be_light" + ) + assert '"state": "on"' in hass.succeed( + "curl http://localhost:8123/api/states/binary_sensor.mqtt_binary_sensor -H 'x-ha-access: ${apiPassword}'" + ) + with subtest("Toggle a binary sensor using hass-cli"): + assert '"state": "on"' in hass.succeed( + "${hassCli} --output json state get binary_sensor.mqtt_binary_sensor" + ) + hass.succeed( + "${hassCli} state edit binary_sensor.mqtt_binary_sensor --json='{\"state\": \"off\"}'" + ) + assert '"state": "off"' in hass.succeed( + "curl http://localhost:8123/api/states/binary_sensor.mqtt_binary_sensor -H 'x-ha-access: ${apiPassword}'" + ) + with subtest("Print log to ease debugging"): + output_log = hass.succeed("cat ${configDir}/home-assistant.log") + print("\n### home-assistant.log ###\n") + print(output_log + "\n") - # Check that no errors were logged - $hass->fail("cat ${configDir}/home-assistant.log | grep -qF ERROR"); + with subtest("Check that no errors were logged"): + assert "ERROR" not in output_log ''; }) diff --git a/nixos/tests/hydra/common.nix b/nixos/tests/hydra/common.nix new file mode 100644 index 0000000000000..f612717dc9686 --- /dev/null +++ b/nixos/tests/hydra/common.nix @@ -0,0 +1,47 @@ +{ system, ... }: +{ + baseConfig = { pkgs, ... }: let + trivialJob = pkgs.writeTextDir "trivial.nix" '' + { trivial = builtins.derivation { + name = "trivial"; + system = "${system}"; + builder = "/bin/sh"; + allowSubstitutes = false; + preferLocalBuild = true; + args = ["-c" "echo success > $out; exit 0"]; + }; + } + ''; + + createTrivialProject = pkgs.stdenv.mkDerivation { + name = "create-trivial-project"; + dontUnpack = true; + buildInputs = [ pkgs.makeWrapper ]; + installPhase = "install -m755 -D ${./create-trivial-project.sh} $out/bin/create-trivial-project.sh"; + postFixup = '' + wrapProgram "$out/bin/create-trivial-project.sh" --prefix PATH ":" ${pkgs.stdenv.lib.makeBinPath [ pkgs.curl ]} --set EXPR_PATH ${trivialJob} + ''; + }; + in { + virtualisation.memorySize = 2048; + time.timeZone = "UTC"; + environment.systemPackages = [ createTrivialProject pkgs.jq ]; + services.hydra = { + enable = true; + # Hydra needs those settings to start up, so we add something not harmfull. + hydraURL = "example.com"; + notificationSender = "example@example.com"; + extraConfig = '' + email_notification = 1 + ''; + }; + services.postfix.enable = true; + nix = { + buildMachines = [{ + hostName = "localhost"; + systems = [ system ]; + }]; + binaryCaches = []; + }; + }; +} diff --git a/nixos/tests/hydra/db-migration.nix b/nixos/tests/hydra/db-migration.nix new file mode 100644 index 0000000000000..cf74acfd67aab --- /dev/null +++ b/nixos/tests/hydra/db-migration.nix @@ -0,0 +1,92 @@ +{ system ? builtins.currentSystem +, pkgs ? import ../../.. { inherit system; } +, ... +}: + +let inherit (import ./common.nix { inherit system; }) baseConfig; in + +with import ../../lib/testing-python.nix { inherit system pkgs; }; +with pkgs.lib; + +{ mig = makeTest { + name = "hydra-db-migration"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ ma27 ]; + }; + + nodes = { + original = { pkgs, lib, ... }: { + imports = [ baseConfig ]; + + # An older version of Hydra before the db change + # for testing purposes. + services.hydra.package = pkgs.hydra-migration.overrideAttrs (old: { + inherit (old) pname; + version = "2020-02-06"; + src = pkgs.fetchFromGitHub { + owner = "NixOS"; + repo = "hydra"; + rev = "2b4f14963b16b21ebfcd6b6bfa7832842e9b2afc"; + sha256 = "16q0cffcsfx5pqd91n9k19850c1nbh4vvbd9h8yi64ihn7v8bick"; + }; + }); + }; + + migration_phase1 = { pkgs, lib, ... }: { + imports = [ baseConfig ]; + services.hydra.package = pkgs.hydra-migration; + }; + + finished = { pkgs, lib, ... }: { + imports = [ baseConfig ]; + services.hydra.package = pkgs.hydra-unstable; + }; + }; + + testScript = { nodes, ... }: let + next = nodes.migration_phase1.config.system.build.toplevel; + finished = nodes.finished.config.system.build.toplevel; + in '' + original.start() + original.wait_for_unit("multi-user.target") + original.wait_for_unit("postgresql.service") + original.wait_for_unit("hydra-init.service") + original.require_unit_state("hydra-queue-runner.service") + original.require_unit_state("hydra-evaluator.service") + original.require_unit_state("hydra-notify.service") + original.succeed("hydra-create-user admin --role admin --password admin") + original.wait_for_open_port(3000) + original.succeed("create-trivial-project.sh") + original.wait_until_succeeds( + 'curl -L -s http://localhost:3000/build/1 -H "Accept: application/json" | jq .buildstatus | xargs test 0 -eq' + ) + + out = original.succeed("su -l postgres -c 'psql -d hydra <<< \"\\d+ jobs\" -A'") + assert "jobset_id" not in out + + original.succeed( + "${next}/bin/switch-to-configuration test >&2" + ) + original.wait_for_unit("hydra-init.service") + + out = original.succeed("su -l postgres -c 'psql -d hydra <<< \"\\d+ jobs\" -A'") + assert "jobset_id|integer|||" in out + + original.succeed("hydra-backfill-ids") + + original.succeed( + "${finished}/bin/switch-to-configuration test >&2" + ) + original.wait_for_unit("hydra-init.service") + + out = original.succeed("su -l postgres -c 'psql -d hydra <<< \"\\d+ jobs\" -A'") + assert "jobset_id|integer||not null|" in out + + original.wait_until_succeeds( + 'curl -L -s http://localhost:3000/build/1 -H "Accept: application/json" | jq .buildstatus | xargs test 0 -eq' + ) + + original.shutdown() + ''; + }; +} diff --git a/nixos/tests/hydra/default.nix b/nixos/tests/hydra/default.nix index 6ca05a2c7797e..2336e4033d6dc 100644 --- a/nixos/tests/hydra/default.nix +++ b/nixos/tests/hydra/default.nix @@ -3,98 +3,57 @@ , pkgs ? import ../../.. { inherit system config; } }: -let - - trivialJob = pkgs.writeTextDir "trivial.nix" '' - { trivial = builtins.derivation { - name = "trivial"; - system = "${system}"; - builder = "/bin/sh"; - allowSubstitutes = false; - preferLocalBuild = true; - args = ["-c" "echo success > $out; exit 0"]; - }; - } - ''; +with import ../../lib/testing-python.nix { inherit system pkgs; }; +with pkgs.lib; - createTrivialProject = pkgs.stdenv.mkDerivation { - name = "create-trivial-project"; - dontUnpack = true; - buildInputs = [ pkgs.makeWrapper ]; - installPhase = "install -m755 -D ${./create-trivial-project.sh} $out/bin/create-trivial-project.sh"; - postFixup = '' - wrapProgram "$out/bin/create-trivial-project.sh" --prefix PATH ":" ${pkgs.stdenv.lib.makeBinPath [ pkgs.curl ]} --set EXPR_PATH ${trivialJob} - ''; - }; +let - callTest = f: f { inherit system pkgs; }; + inherit (import ./common.nix { inherit system; }) baseConfig; hydraPkgs = { - inherit (pkgs) nixStable nixUnstable; + inherit (pkgs) hydra-migration hydra-unstable; }; - tests = pkgs.lib.flip pkgs.lib.mapAttrs hydraPkgs (name: nix: - callTest (import ../make-test.nix ({ pkgs, lib, ... }: - { - name = "hydra-with-${name}"; - meta = with pkgs.stdenv.lib.maintainers; { - maintainers = [ pstn lewo ma27 ]; - }; - - machine = { pkgs, ... }: - { - virtualisation.memorySize = 1024; - time.timeZone = "UTC"; - - environment.systemPackages = [ createTrivialProject pkgs.jq ]; - services.hydra = { - enable = true; - - #Hydra needs those settings to start up, so we add something not harmfull. - hydraURL = "example.com"; - notificationSender = "example@example.com"; - - package = pkgs.hydra.override { inherit nix; }; - - extraConfig = '' - email_notification = 1 - ''; - }; - services.postfix.enable = true; - nix = { - buildMachines = [{ - hostName = "localhost"; - systems = [ system ]; - }]; - - binaryCaches = []; - }; - }; - - testScript = '' - # let the system boot up - $machine->waitForUnit("multi-user.target"); - # test whether the database is running - $machine->waitForUnit("postgresql.service"); - # test whether the actual hydra daemons are running - $machine->waitForUnit("hydra-init.service"); - $machine->requireActiveUnit("hydra-queue-runner.service"); - $machine->requireActiveUnit("hydra-evaluator.service"); - $machine->requireActiveUnit("hydra-notify.service"); - - $machine->succeed("hydra-create-user admin --role admin --password admin"); - - # create a project with a trivial job - $machine->waitForOpenPort(3000); - - # make sure the build as been successfully built - $machine->succeed("create-trivial-project.sh"); - - $machine->waitUntilSucceeds('curl -L -s http://localhost:3000/build/1 -H "Accept: application/json" | jq .buildstatus | xargs test 0 -eq'); - - $machine->waitUntilSucceeds('journalctl -eu hydra-notify.service -o cat | grep -q "sending mail notification to hydra@localhost"'); - ''; - }))); + makeHydraTest = with pkgs.lib; name: package: makeTest { + name = "hydra-${name}"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ pstn lewo ma27 ]; + }; + + machine = { pkgs, lib, ... }: { + imports = [ baseConfig ]; + services.hydra = { inherit package; }; + }; + + testScript = '' + # let the system boot up + machine.wait_for_unit("multi-user.target") + # test whether the database is running + machine.wait_for_unit("postgresql.service") + # test whether the actual hydra daemons are running + machine.wait_for_unit("hydra-init.service") + machine.require_unit_state("hydra-queue-runner.service") + machine.require_unit_state("hydra-evaluator.service") + machine.require_unit_state("hydra-notify.service") + + machine.succeed("hydra-create-user admin --role admin --password admin") + + # create a project with a trivial job + machine.wait_for_open_port(3000) + + # make sure the build as been successfully built + machine.succeed("create-trivial-project.sh") + + machine.wait_until_succeeds( + 'curl -L -s http://localhost:3000/build/1 -H "Accept: application/json" | jq .buildstatus | xargs test 0 -eq' + ) + + machine.wait_until_succeeds( + 'journalctl -eu hydra-notify.service -o cat | grep -q "sending mail notification to hydra@localhost"' + ) + ''; + }; in - tests + +mapAttrs makeHydraTest hydraPkgs diff --git a/nixos/tests/i3wm.nix b/nixos/tests/i3wm.nix index d309f19a0b4a1..b527aa706ad21 100644 --- a/nixos/tests/i3wm.nix +++ b/nixos/tests/i3wm.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, ...} : { name = "i3wm"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ aszlig ]; @@ -6,30 +6,41 @@ import ./make-test.nix ({ pkgs, ...} : { machine = { lib, ... }: { imports = [ ./common/x11.nix ./common/user-account.nix ]; - services.xserver.displayManager.auto.user = "alice"; - services.xserver.windowManager.default = lib.mkForce "i3"; + test-support.displayManager.auto.user = "alice"; + services.xserver.displayManager.defaultSession = lib.mkForce "none+i3"; services.xserver.windowManager.i3.enable = true; }; testScript = { ... }: '' - $machine->waitForX; - $machine->waitForFile("/home/alice/.Xauthority"); - $machine->succeed("xauth merge ~alice/.Xauthority"); - $machine->waitForWindow(qr/first configuration/); - $machine->sleep(2); - $machine->screenshot("started"); - $machine->sendKeys("ret"); - $machine->sleep(2); - $machine->sendKeys("alt"); - $machine->sleep(2); - $machine->screenshot("configured"); - $machine->sendKeys("ret"); - # make sure the config file is created before we continue - $machine->waitForFile("/home/alice/.config/i3/config"); - $machine->sleep(2); - $machine->sendKeys("alt-ret"); - $machine->waitForWindow(qr/alice.*machine/); - $machine->sleep(2); - $machine->screenshot("terminal"); + with subtest("ensure x starts"): + machine.wait_for_x() + machine.wait_for_file("/home/alice/.Xauthority") + machine.succeed("xauth merge ~alice/.Xauthority") + + with subtest("ensure we get first configuration window"): + machine.wait_for_window(r".*?first configuration.*?") + machine.sleep(2) + machine.screenshot("started") + + with subtest("ensure we generate and save a config"): + # press return to indicate we want to gen a new config + machine.send_key("\n") + machine.sleep(2) + machine.screenshot("preconfig") + # press alt then return to indicate we want to use alt as our Mod key + machine.send_key("alt") + machine.send_key("\n") + machine.sleep(2) + # make sure the config file is created before we continue + machine.wait_for_file("/home/alice/.config/i3/config") + machine.screenshot("postconfig") + machine.sleep(2) + + with subtest("ensure we can open a new terminal"): + machine.send_key("alt-ret") + machine.sleep(2) + machine.wait_for_window(r"alice.*?machine") + machine.sleep(2) + machine.screenshot("terminal") ''; }) diff --git a/nixos/tests/ihatemoney.nix b/nixos/tests/ihatemoney.nix new file mode 100644 index 0000000000000..7df0ea0b691fc --- /dev/null +++ b/nixos/tests/ihatemoney.nix @@ -0,0 +1,55 @@ +let + f = backend: import ./make-test-python.nix ({ pkgs, ... }: { + name = "ihatemoney-${backend}"; + machine = { lib, ... }: { + services.ihatemoney = { + enable = true; + enablePublicProjectCreation = true; + inherit backend; + uwsgiConfig = { + http = ":8000"; + }; + }; + boot.cleanTmpDir = true; + # ihatemoney needs a local smtp server otherwise project creation just crashes + services.opensmtpd = { + enable = true; + serverConfiguration = '' + listen on lo + action foo relay + match from any for any action foo + ''; + }; + }; + testScript = '' + machine.wait_for_open_port(8000) + machine.wait_for_unit("uwsgi.service") + + assert '"yay"' in machine.succeed( + "curl -X POST http://localhost:8000/api/projects -d 'name=yay&id=yay&password=yay&contact_email=yay\@example.com'" + ) + owner, timestamp = machine.succeed( + "stat --printf %U:%G___%Y /var/lib/ihatemoney/secret_key" + ).split("___") + assert "ihatemoney:ihatemoney" == owner + + with subtest("Restart machine and service"): + machine.shutdown() + machine.start() + machine.wait_for_open_port(8000) + machine.wait_for_unit("uwsgi.service") + + with subtest("check that the database is really persistent"): + machine.succeed("curl --basic -u yay:yay http://localhost:8000/api/projects/yay") + + with subtest("check that the secret key is really persistent"): + timestamp2 = machine.succeed("stat --printf %Y /var/lib/ihatemoney/secret_key") + assert timestamp == timestamp2 + + assert "ihatemoney" in machine.succeed("curl http://localhost:8000") + ''; + }); +in { + ihatemoney-sqlite = f "sqlite"; + ihatemoney-postgresql = f "postgresql"; +} diff --git a/nixos/tests/initdb.nix b/nixos/tests/initdb.nix deleted file mode 100644 index 749d7857a1341..0000000000000 --- a/nixos/tests/initdb.nix +++ /dev/null @@ -1,26 +0,0 @@ -let - pkgs = import <nixpkgs> { }; -in -with import <nixpkgs/nixos/lib/testing.nix> { inherit pkgs; system = builtins.currentSystem; }; -with pkgs.lib; - -makeTest { - name = "pg-initdb"; - - machine = {...}: - { - documentation.enable = false; - services.postgresql.enable = true; - services.postgresql.package = pkgs.postgresql_9_6; - environment.pathsToLink = [ - "/share/postgresql" - ]; - }; - - testScript = '' - $machine->start; - $machine->succeed("sudo -u postgres initdb -D /tmp/testpostgres2"); - $machine->shutdown; - ''; - - } \ No newline at end of file diff --git a/nixos/tests/initrd-network-ssh/default.nix b/nixos/tests/initrd-network-ssh/default.nix index 73d9f938e226c..017de6882081d 100644 --- a/nixos/tests/initrd-network-ssh/default.nix +++ b/nixos/tests/initrd-network-ssh/default.nix @@ -3,7 +3,7 @@ import ../make-test-python.nix ({ lib, ... }: { name = "initrd-network-ssh"; meta = with lib.maintainers; { - maintainers = [ willibutz ]; + maintainers = [ willibutz emily ]; }; nodes = with lib; { @@ -17,9 +17,9 @@ import ../make-test-python.nix ({ lib, ... }: enable = true; ssh = { enable = true; - authorizedKeys = [ "${readFile ./openssh.pub}" ]; + authorizedKeys = [ (readFile ./id_ed25519.pub) ]; port = 22; - hostRSAKey = ./dropbear.priv; + hostKeys = [ ./ssh_host_ed25519_key ]; }; }; boot.initrd.preLVMCommands = '' @@ -42,11 +42,11 @@ import ../make-test-python.nix ({ lib, ... }: "${toString (head (splitString " " ( toString (elemAt (splitString "\n" config.networking.extraHosts) 2) )))} " - "${readFile ./dropbear.pub}" + "${readFile ./ssh_host_ed25519_key.pub}" ]; }; sshKey = { - source = ./openssh.priv; # dont use this anywhere else + source = ./id_ed25519; mode = "0600"; }; }; @@ -56,7 +56,17 @@ import ../make-test-python.nix ({ lib, ... }: testScript = '' start_all() client.wait_for_unit("network.target") - client.wait_until_succeeds("ping -c 1 server") + + + def ssh_is_up(_) -> bool: + status, _ = client.execute("nc -z server 22") + return status == 0 + + + with client.nested("waiting for SSH server to come up"): + retry(ssh_is_up) + + client.succeed( "ssh -i /etc/sshKey -o UserKnownHostsFile=/etc/knownHosts server 'touch /fnord'" ) diff --git a/nixos/tests/initrd-network-ssh/dropbear.priv b/nixos/tests/initrd-network-ssh/dropbear.priv deleted file mode 100644 index af340535f0a38..0000000000000 --- a/nixos/tests/initrd-network-ssh/dropbear.priv +++ /dev/null Binary files differdiff --git a/nixos/tests/initrd-network-ssh/dropbear.pub b/nixos/tests/initrd-network-ssh/dropbear.pub deleted file mode 100644 index 385c625522aa1..0000000000000 --- a/nixos/tests/initrd-network-ssh/dropbear.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCzJ0OniLB91MpPC86I1m3wwJeAc+Gme7bAuaLIU/cSfPwxT5NO7MfCp0Pu94gYDKtDXMs/wXg0bTAVDeAFFkdIj6kBBumEmQLCTL48q2UxDIXVLT/E/AAgj6q7WwgCg7fwm4Vjn4z7aUyBx8EfRy+5/SQyeYla3D/lFYgMi5x4D6J+yeR+JPAptDE/IR5IizNV7mY0ZcoXYyHrrehI1tTYEEqjX13ZqS4OCBFWwHe1QHhRNM+jHhcATbgikjAj8FyFPtLvc+dSVtkuhQktQl36Bi8zMUQcV6+mM7Ln6DBcDlM9urHKLYPTWmUAyhxM955iglOn5z0RaAIcyNMT6hz0rHaNf0BIlmbXoTC0XGjHh/OnoOEC/zg0JqgQTnPiU45K4TnRSSXp2GfiDfiQAK0+HaXACkjuFR68u7WCZpB1Bse1OgKNClFqtRhIr5DilUb2/e5DCCmFkddMUcjmYqzZdbXNt7fo8CFULe+mbiCp8+tMg4aRTaDZ/Hk93nCvGE5TP2ypEMbfL6nRVKvXOjhdvSQQgKwx+O003FDEHCSG0Bpageh7yVpna+SPrbGklce7MjTpbx3iIwmvKpQ6asnK1L3KkahpY1S3NhQ+/S3Gs8KWQ5LAU+d3xiPX3jfIVHsCIIyxHDbwcJvxM4MFBFQpqRMD6E+LoM9RHjl4C9k2iQ== tmtynkky@duuni diff --git a/nixos/tests/initrd-network-ssh/generate-keys.nix b/nixos/tests/initrd-network-ssh/generate-keys.nix index 0183e12d7a88f..3d7978890ab08 100644 --- a/nixos/tests/initrd-network-ssh/generate-keys.nix +++ b/nixos/tests/initrd-network-ssh/generate-keys.nix @@ -1,12 +1,10 @@ with import ../../.. {}; runCommand "gen-keys" { - buildInputs = [ dropbear openssh ]; + buildInputs = [ openssh ]; } '' mkdir $out - dropbearkey -t rsa -f $out/dropbear.priv -s 4096 | sed -n 2p > $out/dropbear.pub - ssh-keygen -q -t rsa -b 4096 -N "" -f client - mv client $out/openssh.priv - mv client.pub $out/openssh.pub + ssh-keygen -q -t ed25519 -N "" -f $out/ssh_host_ed25519_key + ssh-keygen -q -t ed25519 -N "" -f $out/id_ed25519 '' diff --git a/nixos/tests/initrd-network-ssh/id_ed25519 b/nixos/tests/initrd-network-ssh/id_ed25519 new file mode 100644 index 0000000000000..f914b3f712fc5 --- /dev/null +++ b/nixos/tests/initrd-network-ssh/id_ed25519 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACAVcX+32Yqig25RxRA8bel/f604wV0p/63um+Oku/3vfwAAAJi/AJZMvwCW +TAAAAAtzc2gtZWQyNTUxOQAAACAVcX+32Yqig25RxRA8bel/f604wV0p/63um+Oku/3vfw +AAAEAPLjQusjrB90Lk3996G3AbtTeK+XweNgxaegYnml/A/RVxf7fZiqKDblHFEDxt6X9/ +rTjBXSn/re6b46S7/e9/AAAAEG5peGJsZEBsb2NhbGhvc3QBAgMEBQ== +-----END OPENSSH PRIVATE KEY----- diff --git a/nixos/tests/initrd-network-ssh/id_ed25519.pub b/nixos/tests/initrd-network-ssh/id_ed25519.pub new file mode 100644 index 0000000000000..40de4a8ac6027 --- /dev/null +++ b/nixos/tests/initrd-network-ssh/id_ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBVxf7fZiqKDblHFEDxt6X9/rTjBXSn/re6b46S7/e9/ nixbld@localhost diff --git a/nixos/tests/initrd-network-ssh/openssh.priv b/nixos/tests/initrd-network-ssh/openssh.priv deleted file mode 100644 index 816d65435fd70..0000000000000 --- a/nixos/tests/initrd-network-ssh/openssh.priv +++ /dev/null @@ -1,51 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIJKAIBAAKCAgEA7+9A2PCPOTAlFmrablrUWA+VZdAuLfM6JXeHsOF7ZbC2F6lv -WmvDM925DQqhiAjcgWnt5WHWS5Y+b7lGnuzT7fyKegXd80nCRmqlpSG3srX0/lxR -aQAJLzfoDjcsF+ceswQo6GSsYnCHVxMNs007gbbVY3f7o+sWZtLdxJPD2iHvl5Zr -LK0d1RLMmU6cfIhIABlL0S8EWiv29RROepsCQnS0dnK2b+von1SCYoggvAMe2ToA -IAJ8+uqaYfGAyn9q8fjZiRHxLmKDq90tKoCUL5r/2dmEIE+t8T/3PfHoq1QzZts9 -W9idhBdT21dEXBtGyoMtckp5njk5m82LQDYiOXkuSoIUhSOteh5g7fBv1BtVSERx -Jg3UeJjPeGKFwdnzapmAKC2w/6V8xcIINNA+fhZA7B9fD1RAi2TECZ+gyMYDc4T+ -USlMSm9cfvSOrf2+5ngtFb84nHjqvClxCMLu+bCWK8HamqUzhE/a5LbR+48E7PyG -s3KV+sWFN9KOnakTjj/6iQhXZRhgeAK39F2XTk5Ms5Y+BRSStnMoMZA2grIV+jHi -1zbWokVqXPI5YRo5isR/PgtKAV6FfNWumcYoFJ9F40pMHQ6hJVEmtrCBx7EApSl3 -mSGbQJUmilLC51qNhwQRbD//ZtpIrN82HTMKzZ6kj7kDCdsff+wsnkIXmmMCAwEA -AQKCAgA4tMINw6UF7hQF3VEsnbjr6xrzCiWv5HlMm5htPI1OdlpC81+G7ksfOfrf -UzDkFrwOtftsqBfem268Nvyy2OQprfMIbdSMCFWrEM9/XJ2u1gRGDYmMGF8TUtI8 -cduw9oWx53zHl+uKBHBoKu+k/c7flFeQf63wisIroRCawhWau0SF/h3sXCndzuie -Hw8q+4aQx2m80bDkotlmCNuXbIU3MZ/pEql9gDLlXTLHmMaryM0EqAmZhx0ErGe6 -WDqJIV4kPB0loSDwRoY6GzbugZ8ENUzcruTkQhCpIOYNNNw5idfwKkaxK1vm+SBv -iYt1fVjYyfH2vhVKSNoNsaGEloa1u4Dymt/FpFztEpRzHXcw93N8BdLxJ4OUhzm2 -iAbpiyjniTIeAVVi7BUwLXh5WAx8nT0eeb1zKoZg1p1ciK5cYl1Uel7j8xRycsSW -3YgmtuPqY4Agbc9v3eXbQZNDk48JFMEqpIxk97FAkRYpzfxg5Qq14WJCp60CkdRt -T60hXy8lT/BcI8OWLfGJuBbsVLNRiC7PpwqRKQAinXSv134FpP7jrhpkMybs2oIS -5obRG7J5OfOTp925erG5mrpwqa3BPkgqx347Wj9z8quOZyuhi+XaPvqmPtvs5JOl -4RCqjt6RQlHm7xos9ZZGI4jDAIFaFWgyVZrYplOgwxWma4DTgQKCAQEA9+tizQRU -lF0lxNcEPvsFnYJo80Y+MQK9VdtlhR19YuSfwP1NCaMG1MhQ+PVBVmepOwJMRJR7 -9PLfOouNMfixKBGP12dtStMuh7jowq/BxhRI6JWp3RhTZ1yJ9ouzHze7IDrEBa6w -p0hUu9H0Sbt51LXbC3JmTyhbdhfry559DfyGW1Ma/bv/pihL9B5Y7sNf1thNp1gi -GbQ9B+o2Yyw8ZD8zY+sl+aYDSWyCtcBV/KXEF74Bkfs/a5ExJ00X0jYj/TAp2ray -T4PY0FR8wN/O10bFLP9j+Xa/ywbcPhoj8nvVRIg9VfWT/QaEd+KR0EZVxdjCCqne -enbSQksTpAZNwQKCAQEA98E+BMmS+yHUVUhNZABtQ5avwuV4+DoSN8KTp3xwQ0CH -m9fWxSDs12FdyMhDxrJPeywvHtZ18/7cl3dr8wnFVE0s4ongnRDXsNk5xN6J3AaO -KqW4HF9cbwZqzLILy8TrO+EK/EQV9FypbrxqvxAlP1kezIA2CJNzVRAgimSuV/H7 -05HTnp5W06fjtEf8U1CUrdNetoSROUo1j/IMGPYGlsBFYAGrj5y/BlKd+3T3kjRp -Xje7HpiykjrZHn0WDp04Ln+u9nveEewXmHKch313emt7HpW0xspp8JM8OZtEKozk -D5PfYdBfMJJOUlqovCCzTTJ6kNOahknKXFeO/qs5IwKCAQEAjF0/zhWikXF/fcfD -Bql2z2vTYdEmSvdjHSYff1Nn90K71DdVk5wytOxJM/sfp/z+yoMNjVKIL/IGQw5Z -va4xFx+CUhGjxlZ0pLEjT37U9gHsGYsK5jvslLvG/MixfH5AOwoqi5ERQVTpbIF9 -jvVPEAh6YSu/ExglWGJIxTsRUIblxvTxdjEnl/p+rlM0RNJnA6vpo1J51BXA7CdF -7bZQ5u0Feo/bK1I70ClYg/DGfkmYEV0pZG5cxNkqfDbgwsqWa7YGLGd94xkh+ymq -jETqxeWyozxhbQ83nYpfzeVc7t//qlJ8b5uf0wUKoRmtNr9rtp13lzP/21REzPXW -w+oxwQKCAQAoAf2Y2lAw25KlPuq4ZlU+n9u8FkBFnWMJvBMJ7c9XHNmJMf6NkLaO -RTvWy3geYvbwxf7J9QnRH+vRTciR05cY+Olxn6A03N5nwXxRrToH3MsiWeZ0NnX/ -u8KNUYcUHbV60ulqOThuYHQ/3I9EUUAijaqqjV2sXts19ke68W0x6HKpBJhuudT9 -ktPzbdhyP8Xyl/pocNnerXwexZBsi3Ye6+eIDFz+8OnsBHVcgNPluS72tvsxgqj7 -ciNTiBGCxKKo55eCWBhRPpXE2WUrf/hGPYsBMl2h6FfZMH1+M/N7B4tgdJmS+woU -Ftws8lTjJEiwA6HFN1ZxrwLNjJobx9yPAoIBAE0igsBuWWn6rXeOPylYg4264XOq -8gb94pte2n9amDgCzyCn8m6AL3snLC/AoCD19DK+gyK0ukoesXPa3iX6w2xv69ZC -urDx36Jhd4zrJb4QsFPoeKfDP+UvNVZaS41vipRRzY/y11em15prUZ4U8FA/UT1Y -FzkBo9r6iUZRnyBLppMuEfWASDtuRNmeIHynoT1AcQOH3l9vR210iEpmAuJr0CYA -bvTuz3UzzGGEAuIUvuaiRtkfKY52jBmiEr7SSPCr1HvLj3Ccz8bgjgR2kiXmcU50 -1zLnaPAD44LZ/0Fjqj+PimQGT6K7CNXPllmYh7MvoU52g3SVPf6rHlIR0Nc= ------END RSA PRIVATE KEY----- diff --git a/nixos/tests/initrd-network-ssh/openssh.pub b/nixos/tests/initrd-network-ssh/openssh.pub deleted file mode 100644 index 5b72b8085f270..0000000000000 --- a/nixos/tests/initrd-network-ssh/openssh.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDv70DY8I85MCUWatpuWtRYD5Vl0C4t8zold4ew4XtlsLYXqW9aa8Mz3bkNCqGICNyBae3lYdZLlj5vuUae7NPt/Ip6Bd3zScJGaqWlIbeytfT+XFFpAAkvN+gONywX5x6zBCjoZKxicIdXEw2zTTuBttVjd/uj6xZm0t3Ek8PaIe+XlmssrR3VEsyZTpx8iEgAGUvRLwRaK/b1FE56mwJCdLR2crZv6+ifVIJiiCC8Ax7ZOgAgAnz66pph8YDKf2rx+NmJEfEuYoOr3S0qgJQvmv/Z2YQgT63xP/c98eirVDNm2z1b2J2EF1PbV0RcG0bKgy1ySnmeOTmbzYtANiI5eS5KghSFI616HmDt8G/UG1VIRHEmDdR4mM94YoXB2fNqmYAoLbD/pXzFwgg00D5+FkDsH18PVECLZMQJn6DIxgNzhP5RKUxKb1x+9I6t/b7meC0VvziceOq8KXEIwu75sJYrwdqapTOET9rkttH7jwTs/IazcpX6xYU30o6dqROOP/qJCFdlGGB4Arf0XZdOTkyzlj4FFJK2cygxkDaCshX6MeLXNtaiRWpc8jlhGjmKxH8+C0oBXoV81a6ZxigUn0XjSkwdDqElUSa2sIHHsQClKXeZIZtAlSaKUsLnWo2HBBFsP/9m2kis3zYdMwrNnqSPuQMJ2x9/7CyeQheaYw== tmtynkky@duuni diff --git a/nixos/tests/initrd-network-ssh/ssh_host_ed25519_key b/nixos/tests/initrd-network-ssh/ssh_host_ed25519_key new file mode 100644 index 0000000000000..f1e29459b7a33 --- /dev/null +++ b/nixos/tests/initrd-network-ssh/ssh_host_ed25519_key @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDP9Mz6qlxdQqA4omrgbOlVsxSGONCJstjW9zqquajlIAAAAJg0WGFGNFhh +RgAAAAtzc2gtZWQyNTUxOQAAACDP9Mz6qlxdQqA4omrgbOlVsxSGONCJstjW9zqquajlIA +AAAEA0Hjs7LfFPdTf3ThGx6GNKvX0ItgzgXs91Z3oGIaF6S8/0zPqqXF1CoDiiauBs6VWz +FIY40Imy2Nb3Oqq5qOUgAAAAEG5peGJsZEBsb2NhbGhvc3QBAgMEBQ== +-----END OPENSSH PRIVATE KEY----- diff --git a/nixos/tests/initrd-network-ssh/ssh_host_ed25519_key.pub b/nixos/tests/initrd-network-ssh/ssh_host_ed25519_key.pub new file mode 100644 index 0000000000000..3aa1587e1dce9 --- /dev/null +++ b/nixos/tests/initrd-network-ssh/ssh_host_ed25519_key.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM/0zPqqXF1CoDiiauBs6VWzFIY40Imy2Nb3Oqq5qOUg nixbld@localhost diff --git a/nixos/tests/initrd-network.nix b/nixos/tests/initrd-network.nix index ed9b82e2da773..9c35b7305768f 100644 --- a/nixos/tests/initrd-network.nix +++ b/nixos/tests/initrd-network.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, lib, ...} : { name = "initrd-network"; meta.maintainers = [ pkgs.stdenv.lib.maintainers.eelco ]; @@ -8,15 +8,26 @@ import ./make-test.nix ({ pkgs, ...} : { boot.initrd.network.enable = true; boot.initrd.network.postCommands = '' + ip addr show + ip route show ip addr | grep 10.0.2.15 || exit 1 ping -c1 10.0.2.2 || exit 1 ''; + # Check if cleanup was done correctly + boot.initrd.postMountCommands = lib.mkAfter + '' + ip addr show + ip route show + ip addr | grep 10.0.2.15 && exit 1 + ping -c1 10.0.2.2 && exit 1 + ''; }; testScript = '' - startAll; - $machine->waitForUnit("multi-user.target"); - $machine->succeed("ip link >&2"); + start_all() + machine.wait_for_unit("multi-user.target") + machine.succeed("ip addr show >&2") + machine.succeed("ip route show >&2") ''; }) diff --git a/nixos/tests/installed-tests/default.nix b/nixos/tests/installed-tests/default.nix index f4780bdcfc975..b6bdfea227701 100644 --- a/nixos/tests/installed-tests/default.nix +++ b/nixos/tests/installed-tests/default.nix @@ -29,36 +29,51 @@ let # Extra flags to pass to gnome-desktop-testing-runner. , testRunnerFlags ? "" - }: - makeTest rec { - name = tested.name; - - meta = { - maintainers = tested.meta.maintainers; - }; - - machine = { ... }: { - imports = [ - testConfig - ] ++ optional withX11 ../common/x11.nix; - - environment.systemPackages = with pkgs; [ gnome-desktop-testing ]; - - }; - - testScript = - optionalString withX11 '' - machine.wait_for_x() - '' + - optionalString (preTestScript != "") '' - ${preTestScript} - '' + - '' - machine.succeed( - "gnome-desktop-testing-runner ${testRunnerFlags} -d '${tested.installedTests}/share'" - ) - ''; - }; + + # Extra attributes to pass to makeTest. + # They will be recursively merged into the attrset created by this function. + , ... + }@args: + makeTest + (recursiveUpdate + rec { + name = tested.name; + + meta = { + maintainers = tested.meta.maintainers; + }; + + machine = { ... }: { + imports = [ + testConfig + ] ++ optional withX11 ../common/x11.nix; + + environment.systemPackages = with pkgs; [ gnome-desktop-testing ]; + + }; + + testScript = + optionalString withX11 '' + machine.wait_for_x() + '' + + optionalString (preTestScript != "") '' + ${preTestScript} + '' + + '' + machine.succeed( + "gnome-desktop-testing-runner ${testRunnerFlags} -d '${tested.installedTests}/share'" + ) + ''; + } + + (removeAttrs args [ + "tested" + "testConfig" + "preTestScript" + "withX11" + "testRunnerFlags" + ]) + ); in @@ -73,8 +88,12 @@ in glib-networking = callInstalledTest ./glib-networking.nix {}; gnome-photos = callInstalledTest ./gnome-photos.nix {}; graphene = callInstalledTest ./graphene.nix {}; + ibus = callInstalledTest ./ibus.nix {}; libgdata = callInstalledTest ./libgdata.nix {}; + glib-testing = callInstalledTest ./glib-testing.nix {}; + libjcat = callInstalledTest ./libjcat.nix {}; libxmlb = callInstalledTest ./libxmlb.nix {}; + malcontent = callInstalledTest ./malcontent.nix {}; ostree = callInstalledTest ./ostree.nix {}; xdg-desktop-portal = callInstalledTest ./xdg-desktop-portal.nix {}; } diff --git a/nixos/tests/installed-tests/fwupd.nix b/nixos/tests/installed-tests/fwupd.nix index b9f761e99582e..6a0ceb57dda48 100644 --- a/nixos/tests/installed-tests/fwupd.nix +++ b/nixos/tests/installed-tests/fwupd.nix @@ -1,11 +1,11 @@ -{ pkgs, makeInstalledTest, ... }: +{ pkgs, lib, makeInstalledTest, ... }: makeInstalledTest { tested = pkgs.fwupd; testConfig = { services.fwupd.enable = true; - services.fwupd.blacklistPlugins = []; # don't blacklist test plugin + services.fwupd.blacklistPlugins = lib.mkForce []; # don't blacklist test plugin services.fwupd.enableTestRemote = true; virtualisation.memorySize = 768; }; diff --git a/nixos/tests/installed-tests/glib-testing.nix b/nixos/tests/installed-tests/glib-testing.nix new file mode 100644 index 0000000000000..7a06cf792bdd1 --- /dev/null +++ b/nixos/tests/installed-tests/glib-testing.nix @@ -0,0 +1,5 @@ +{ pkgs, makeInstalledTest, ... }: + +makeInstalledTest { + tested = pkgs.glib-testing; +} diff --git a/nixos/tests/installed-tests/ibus.nix b/nixos/tests/installed-tests/ibus.nix new file mode 100644 index 0000000000000..af54b612b507b --- /dev/null +++ b/nixos/tests/installed-tests/ibus.nix @@ -0,0 +1,20 @@ +{ pkgs, makeInstalledTest, ... }: + +makeInstalledTest { + tested = pkgs.ibus; + + testConfig = { + i18n.inputMethod.enabled = "ibus"; + }; + + preTestScript = '' + # ibus has ibus-desktop-testing-runner but it tries to manage desktop session so we just spawn ibus-daemon ourselves + machine.succeed("ibus-daemon --daemonize --verbose") + ''; + + withX11 = true; + + # TODO: ibus-daemon is currently crashing or something + # maybe make ibus systemd service that auto-restarts? + meta.broken = true; +} diff --git a/nixos/tests/installed-tests/libjcat.nix b/nixos/tests/installed-tests/libjcat.nix new file mode 100644 index 0000000000000..41493a730890a --- /dev/null +++ b/nixos/tests/installed-tests/libjcat.nix @@ -0,0 +1,5 @@ +{ pkgs, makeInstalledTest, ... }: + +makeInstalledTest { + tested = pkgs.libjcat; +} diff --git a/nixos/tests/installed-tests/malcontent.nix b/nixos/tests/installed-tests/malcontent.nix new file mode 100644 index 0000000000000..d4e214c41988c --- /dev/null +++ b/nixos/tests/installed-tests/malcontent.nix @@ -0,0 +1,5 @@ +{ pkgs, makeInstalledTest, ... }: + +makeInstalledTest { + tested = pkgs.malcontent; +} diff --git a/nixos/tests/installed-tests/xdg-desktop-portal.nix b/nixos/tests/installed-tests/xdg-desktop-portal.nix index b16008ff4addb..90529d37ee0fa 100644 --- a/nixos/tests/installed-tests/xdg-desktop-portal.nix +++ b/nixos/tests/installed-tests/xdg-desktop-portal.nix @@ -2,4 +2,8 @@ makeInstalledTest { tested = pkgs.xdg-desktop-portal; + + # Ton of breakage. + # https://github.com/flatpak/xdg-desktop-portal/pull/428 + meta.broken = true; } diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix index eb1f4f192dd11..8d1bfa96d03d6 100644 --- a/nixos/tests/installer.nix +++ b/nixos/tests/installer.nix @@ -3,7 +3,7 @@ pkgs ? import ../.. { inherit system config; } }: -with import ../lib/testing.nix { inherit system pkgs; }; +with import ../lib/testing-python.nix { inherit system pkgs; }; with pkgs.lib; let @@ -29,7 +29,7 @@ let boot.loader.grub.splashImage = null; ''} - boot.loader.grub.extraConfig = "serial; terminal_output.serial"; + boot.loader.grub.extraConfig = "serial; terminal_output serial"; ${if grubUseEfi then '' boot.loader.grub.device = "nodev"; boot.loader.grub.efiSupport = true; @@ -65,163 +65,194 @@ let # partitions and filesystems. testScriptFun = { bootLoader, createPartitions, grubVersion, grubDevice, grubUseEfi , grubIdentifier, preBootCommands, extraConfig - , testCloneConfig + , testSpecialisationConfig }: - let - iface = if grubVersion == 1 then "ide" else "virtio"; - isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi); - - # FIXME don't duplicate the -enable-kvm etc. flags here yet again! - qemuFlags = - (if system == "x86_64-linux" then "-m 768 " else "-m 512 ") + - (optionalString (system == "x86_64-linux") "-cpu kvm64 ") + - (optionalString (system == "aarch64-linux") "-enable-kvm -machine virt,gic-version=host -cpu host "); - - hdFlags = ''hda => "vm-state-machine/machine.qcow2", hdaInterface => "${iface}", '' - + optionalString isEfi (if pkgs.stdenv.isAarch64 - then ''bios => "${pkgs.OVMF.fd}/FV/QEMU_EFI.fd", '' - else ''bios => "${pkgs.OVMF.fd}/FV/OVMF.fd", ''); + let iface = if grubVersion == 1 then "ide" else "virtio"; + isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi); + bios = if pkgs.stdenv.isAarch64 then "QEMU_EFI.fd" else "OVMF.fd"; in if !isEfi && !(pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) then throw "Non-EFI boot methods are only supported on i686 / x86_64" else '' + def assemble_qemu_flags(): + flags = "-cpu host" + ${if system == "x86_64-linux" + then ''flags += " -m 768"'' + else ''flags += " -m 512 -enable-kvm -machine virt,gic-version=host"'' + } + return flags - $machine->start; - # Make sure that we get a login prompt etc. - $machine->succeed("echo hello"); - #$machine->waitForUnit('getty@tty2'); - #$machine->waitForUnit("rogue"); - $machine->waitForUnit("nixos-manual"); + qemu_flags = {"qemuFlags": assemble_qemu_flags()} - # Wait for hard disks to appear in /dev - $machine->succeed("udevadm settle"); + hd_flags = { + "hdaInterface": "${iface}", + "hda": "vm-state-machine/machine.qcow2", + } + ${optionalString isEfi '' + hd_flags.update( + bios="${pkgs.OVMF.fd}/FV/${bios}" + )'' + } + default_flags = {**hd_flags, **qemu_flags} - # Partition the disk. - ${createPartitions} - # Create the NixOS configuration. - $machine->succeed("nixos-generate-config --root /mnt"); + def create_machine_named(name): + return create_machine({**default_flags, "name": "boot-after-install"}) - $machine->succeed("cat /mnt/etc/nixos/hardware-configuration.nix >&2"); - $machine->copyFileFromHost( - "${ makeConfig { inherit bootLoader grubVersion grubDevice grubIdentifier grubUseEfi extraConfig; } }", - "/mnt/etc/nixos/configuration.nix"); + machine.start() - # Perform the installation. - $machine->succeed("nixos-install < /dev/null >&2"); + with subtest("Assert readiness of login prompt"): + machine.succeed("echo hello") - # Do it again to make sure it's idempotent. - $machine->succeed("nixos-install < /dev/null >&2"); + with subtest("Wait for hard disks to appear in /dev"): + machine.succeed("udevadm settle") - $machine->succeed("umount /mnt/boot || true"); - $machine->succeed("umount /mnt"); - $machine->succeed("sync"); + ${createPartitions} - $machine->shutdown; + with subtest("Create the NixOS configuration"): + machine.succeed("nixos-generate-config --root /mnt") + machine.succeed("cat /mnt/etc/nixos/hardware-configuration.nix >&2") + machine.copy_from_host( + "${ makeConfig { + inherit bootLoader grubVersion grubDevice grubIdentifier + grubUseEfi extraConfig; + } + }", + "/mnt/etc/nixos/configuration.nix", + ) + + with subtest("Perform the installation"): + machine.succeed("nixos-install < /dev/null >&2") + + with subtest("Do it again to make sure it's idempotent"): + machine.succeed("nixos-install < /dev/null >&2") + + with subtest("Shutdown system after installation"): + machine.succeed("umount /mnt/boot || true") + machine.succeed("umount /mnt") + machine.succeed("sync") + machine.shutdown() # Now see if we can boot the installation. - $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}", name => "boot-after-install" }); + machine = create_machine_named("boot-after-install") # For example to enter LUKS passphrase. ${preBootCommands} - # Did /boot get mounted? - $machine->waitForUnit("local-fs.target"); - - ${if bootLoader == "grub" then - ''$machine->succeed("test -e /boot/grub");'' - else - ''$machine->succeed("test -e /boot/loader/loader.conf");'' - } - - # Check whether /root has correct permissions. - $machine->succeed("stat -c '%a' /root") =~ /700/ or die; - - # Did the swap device get activated? - # uncomment once https://bugs.freedesktop.org/show_bug.cgi?id=86930 is resolved - $machine->waitForUnit("swap.target"); - $machine->succeed("cat /proc/swaps | grep -q /dev"); - - # Check that the store is in good shape - $machine->succeed("nix-store --verify --check-contents >&2"); - - # Check whether the channel works. - $machine->succeed("nix-env -iA nixos.procps >&2"); - $machine->succeed("type -tP ps | tee /dev/stderr") =~ /.nix-profile/ - or die "nix-env failed"; - - # Check that the daemon works, and that non-root users can run builds (this will build a new profile generation through the daemon) - $machine->succeed("su alice -l -c 'nix-env -iA nixos.procps' >&2"); - - # We need a writable Nix store on next boot. - $machine->copyFileFromHost( - "${ makeConfig { inherit bootLoader grubVersion grubDevice grubIdentifier grubUseEfi extraConfig; forceGrubReinstallCount = 1; } }", - "/etc/nixos/configuration.nix"); - - # Check whether nixos-rebuild works. - $machine->succeed("nixos-rebuild switch >&2"); - - # Test nixos-option. - $machine->succeed("nixos-option boot.initrd.kernelModules | grep virtio_console"); - $machine->succeed("nixos-option boot.initrd.kernelModules | grep 'List of modules'"); - $machine->succeed("nixos-option boot.initrd.kernelModules | grep qemu-guest.nix"); - - $machine->shutdown; + with subtest("Assert that /boot get mounted"): + machine.wait_for_unit("local-fs.target") + ${if bootLoader == "grub" + then ''machine.succeed("test -e /boot/grub")'' + else ''machine.succeed("test -e /boot/loader/loader.conf")'' + } + + with subtest("Check whether /root has correct permissions"): + assert "700" in machine.succeed("stat -c '%a' /root") + + with subtest("Assert swap device got activated"): + # uncomment once https://bugs.freedesktop.org/show_bug.cgi?id=86930 is resolved + machine.wait_for_unit("swap.target") + machine.succeed("cat /proc/swaps | grep -q /dev") + + with subtest("Check that the store is in good shape"): + machine.succeed("nix-store --verify --check-contents >&2") + + with subtest("Check whether the channel works"): + machine.succeed("nix-env -iA nixos.procps >&2") + assert ".nix-profile" in machine.succeed("type -tP ps | tee /dev/stderr") + + with subtest( + "Check that the daemon works, and that non-root users can run builds " + "(this will build a new profile generation through the daemon)" + ): + machine.succeed("su alice -l -c 'nix-env -iA nixos.procps' >&2") + + with subtest("Configure system with writable Nix store on next boot"): + # we're not using copy_from_host here because the installer image + # doesn't know about the host-guest sharing mechanism. + machine.copy_from_host_via_shell( + "${ makeConfig { + inherit bootLoader grubVersion grubDevice grubIdentifier + grubUseEfi extraConfig; + forceGrubReinstallCount = 1; + } + }", + "/etc/nixos/configuration.nix", + ) + + with subtest("Check whether nixos-rebuild works"): + machine.succeed("nixos-rebuild switch >&2") + + with subtest("Test nixos-option"): + kernel_modules = machine.succeed("nixos-option boot.initrd.kernelModules") + assert "virtio_console" in kernel_modules + assert "List of modules" in kernel_modules + assert "qemu-guest.nix" in kernel_modules + + machine.shutdown() # Check whether a writable store build works - $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}", name => "rebuild-switch" }); + machine = create_machine_named("rebuild-switch") ${preBootCommands} - $machine->waitForUnit("multi-user.target"); - $machine->copyFileFromHost( - "${ makeConfig { inherit bootLoader grubVersion grubDevice grubIdentifier grubUseEfi extraConfig; forceGrubReinstallCount = 2; } }", - "/etc/nixos/configuration.nix"); - $machine->succeed("nixos-rebuild boot >&2"); - $machine->shutdown; + machine.wait_for_unit("multi-user.target") + + # we're not using copy_from_host here because the installer image + # doesn't know about the host-guest sharing mechanism. + machine.copy_from_host_via_shell( + "${ makeConfig { + inherit bootLoader grubVersion grubDevice grubIdentifier + grubUseEfi extraConfig; + forceGrubReinstallCount = 2; + } + }", + "/etc/nixos/configuration.nix", + ) + machine.succeed("nixos-rebuild boot >&2") + machine.shutdown() # And just to be sure, check that the machine still boots after # "nixos-rebuild switch". - $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}", "boot-after-rebuild-switch" }); + machine = create_machine_named("boot-after-rebuild-switch") ${preBootCommands} - $machine->waitForUnit("network.target"); - $machine->shutdown; + machine.wait_for_unit("network.target") + machine.shutdown() # Tests for validating clone configuration entries in grub menu - ${optionalString testCloneConfig '' - # Reboot Machine - $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}", name => "clone-default-config" }); - ${preBootCommands} - $machine->waitForUnit("multi-user.target"); - - # Booted configuration name should be Home - # This is not the name that shows in the grub menu. - # The default configuration is always shown as "Default" - $machine->succeed("cat /run/booted-system/configuration-name >&2"); - $machine->succeed("cat /run/booted-system/configuration-name | grep Home"); + '' + + optionalString testSpecialisationConfig '' + # Reboot Machine + machine = create_machine_named("clone-default-config") + ${preBootCommands} + machine.wait_for_unit("multi-user.target") - # We should find **not** a file named /etc/gitconfig - $machine->fail("test -e /etc/gitconfig"); + with subtest("Booted configuration name should be 'Home'"): + # This is not the name that shows in the grub menu. + # The default configuration is always shown as "Default" + machine.succeed("cat /run/booted-system/configuration-name >&2") + assert "Home" in machine.succeed("cat /run/booted-system/configuration-name") - # Set grub to boot the second configuration - $machine->succeed("grub-reboot 1"); + with subtest("We should **not** find a file named /etc/gitconfig"): + machine.fail("test -e /etc/gitconfig") - $machine->shutdown; + with subtest("Set grub to boot the second configuration"): + machine.succeed("grub-reboot 1") - # Reboot Machine - $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}", name => "clone-alternate-config" }); - ${preBootCommands} + machine.shutdown() - $machine->waitForUnit("multi-user.target"); - # Booted configuration name should be Work - $machine->succeed("cat /run/booted-system/configuration-name >&2"); - $machine->succeed("cat /run/booted-system/configuration-name | grep Work"); + # Reboot Machine + machine = create_machine_named("clone-alternate-config") + ${preBootCommands} - # We should find a file named /etc/gitconfig - $machine->succeed("test -e /etc/gitconfig"); + machine.wait_for_unit("multi-user.target") + with subtest("Booted configuration name should be Work"): + machine.succeed("cat /run/booted-system/configuration-name >&2") + assert "Work" in machine.succeed("cat /run/booted-system/configuration-name") - $machine->shutdown; - ''} + with subtest("We should find a file named /etc/gitconfig"): + machine.succeed("test -e /etc/gitconfig") + machine.shutdown() ''; @@ -231,7 +262,7 @@ let , bootLoader ? "grub" # either "grub" or "systemd-boot" , grubVersion ? 2, grubDevice ? "/dev/vda", grubIdentifier ? "uuid", grubUseEfi ? false , enableOCR ? false, meta ? {} - , testCloneConfig ? false + , testSpecialisationConfig ? false }: makeTest { inherit enableOCR; @@ -243,80 +274,80 @@ let nodes = { # The configuration of the machine used to run "nixos-install". - machine = - { pkgs, ... }: - - { imports = - [ ../modules/profiles/installation-device.nix - ../modules/profiles/base.nix - extraInstallerConfig - ]; - - virtualisation.diskSize = 8 * 1024; - virtualisation.memorySize = 1024; - - # Use a small /dev/vdb as the root disk for the - # installer. This ensures the target disk (/dev/vda) is - # the same during and after installation. - virtualisation.emptyDiskImages = [ 512 ]; - virtualisation.bootDevice = - if grubVersion == 1 then "/dev/sdb" else "/dev/vdb"; - virtualisation.qemu.diskInterface = - if grubVersion == 1 then "scsi" else "virtio"; - - boot.loader.systemd-boot.enable = mkIf (bootLoader == "systemd-boot") true; - - hardware.enableAllFirmware = mkForce false; - - # The test cannot access the network, so any packages we - # need must be included in the VM. - system.extraDependencies = with pkgs; - [ sudo - libxml2.bin - libxslt.bin - desktop-file-utils - docbook5 - docbook_xsl_ns - unionfs-fuse - ntp - nixos-artwork.wallpapers.simple-dark-gray-bottom - perlPackages.XMLLibXML - perlPackages.ListCompare - shared-mime-info - texinfo - xorg.lndir - - # add curl so that rather than seeing the test attempt to download - # curl's tarball, we see what it's trying to download - curl - ] - ++ optional (bootLoader == "grub" && grubVersion == 1) pkgs.grub - ++ optionals (bootLoader == "grub" && grubVersion == 2) [ pkgs.grub2 pkgs.grub2_efi ]; - - nix.binaryCaches = mkForce [ ]; - nix.extraOptions = - '' - hashed-mirrors = - connect-timeout = 1 - ''; - }; + machine = { pkgs, ... }: { + imports = [ + ../modules/profiles/installation-device.nix + ../modules/profiles/base.nix + extraInstallerConfig + ]; + + virtualisation.diskSize = 8 * 1024; + virtualisation.memorySize = 1024; + + # Use a small /dev/vdb as the root disk for the + # installer. This ensures the target disk (/dev/vda) is + # the same during and after installation. + virtualisation.emptyDiskImages = [ 512 ]; + virtualisation.bootDevice = + if grubVersion == 1 then "/dev/sdb" else "/dev/vdb"; + virtualisation.qemu.diskInterface = + if grubVersion == 1 then "scsi" else "virtio"; + + boot.loader.systemd-boot.enable = mkIf (bootLoader == "systemd-boot") true; + + hardware.enableAllFirmware = mkForce false; + + # The test cannot access the network, so any packages we + # need must be included in the VM. + system.extraDependencies = with pkgs; [ + desktop-file-utils + docbook5 + docbook_xsl_ns + libxml2.bin + libxslt.bin + nixos-artwork.wallpapers.simple-dark-gray-bottom + ntp + perlPackages.ListCompare + perlPackages.XMLLibXML + shared-mime-info + sudo + texinfo + unionfs-fuse + xorg.lndir + + # add curl so that rather than seeing the test attempt to download + # curl's tarball, we see what it's trying to download + curl + ] + ++ optional (bootLoader == "grub" && grubVersion == 1) pkgs.grub + ++ optionals (bootLoader == "grub" && grubVersion == 2) [ + pkgs.grub2 + pkgs.grub2_efi + ]; + + nix.binaryCaches = mkForce [ ]; + nix.extraOptions = '' + hashed-mirrors = + connect-timeout = 1 + ''; + }; }; testScript = testScriptFun { inherit bootLoader createPartitions preBootCommands grubVersion grubDevice grubIdentifier grubUseEfi extraConfig - testCloneConfig; + testSpecialisationConfig; }; }; - makeLuksRootTest = name: luksFormatOpts: makeInstallerTest name - { createPartitions = '' - $machine->succeed( + makeLuksRootTest = name: luksFormatOpts: makeInstallerTest name { + createPartitions = '' + machine.succeed( "flock /dev/vda parted --script /dev/vda -- mklabel msdos" - . " mkpart primary ext2 1M 50MB" # /boot - . " mkpart primary linux-swap 50M 1024M" - . " mkpart primary 1024M -1s", # LUKS + + " mkpart primary ext2 1M 50MB" # /boot + + " mkpart primary linux-swap 50M 1024M" + + " mkpart primary 1024M -1s", # LUKS "udevadm settle", "mkswap /dev/vda2 -L swap", "swapon -L swap", @@ -328,77 +359,74 @@ let "mkfs.ext3 -L boot /dev/vda1", "mkdir -p /mnt/boot", "mount LABEL=boot /mnt/boot", - ); - ''; - extraConfig = '' - boot.kernelParams = lib.mkAfter [ "console=tty0" ]; - ''; - enableOCR = true; - preBootCommands = '' - $machine->start; - $machine->waitForText(qr/Passphrase for/); - $machine->sendChars("supersecret\n"); - ''; - }; + ) + ''; + extraConfig = '' + boot.kernelParams = lib.mkAfter [ "console=tty0" ]; + ''; + enableOCR = true; + preBootCommands = '' + machine.start() + machine.wait_for_text("Passphrase for") + machine.send_chars("supersecret\n") + ''; + }; # The (almost) simplest partitioning scheme: a swap partition and # one big filesystem partition. - simple-test-config = { createPartitions = - '' - $machine->succeed( - "flock /dev/vda parted --script /dev/vda -- mklabel msdos" - . " mkpart primary linux-swap 1M 1024M" - . " mkpart primary ext2 1024M -1s", - "udevadm settle", - "mkswap /dev/vda1 -L swap", - "swapon -L swap", - "mkfs.ext3 -L nixos /dev/vda2", - "mount LABEL=nixos /mnt", - ); - ''; - }; - - simple-uefi-grub-config = - { createPartitions = - '' - $machine->succeed( - "flock /dev/vda parted --script /dev/vda -- mklabel gpt" - . " mkpart ESP fat32 1M 50MiB" # /boot - . " set 1 boot on" - . " mkpart primary linux-swap 50MiB 1024MiB" - . " mkpart primary ext2 1024MiB -1MiB", # / - "udevadm settle", - "mkswap /dev/vda2 -L swap", - "swapon -L swap", - "mkfs.ext3 -L nixos /dev/vda3", - "mount LABEL=nixos /mnt", - "mkfs.vfat -n BOOT /dev/vda1", - "mkdir -p /mnt/boot", - "mount LABEL=BOOT /mnt/boot", - ); - ''; - bootLoader = "grub"; - grubUseEfi = true; - }; + simple-test-config = { + createPartitions = '' + machine.succeed( + "flock /dev/vda parted --script /dev/vda -- mklabel msdos" + + " mkpart primary linux-swap 1M 1024M" + + " mkpart primary ext2 1024M -1s", + "udevadm settle", + "mkswap /dev/vda1 -L swap", + "swapon -L swap", + "mkfs.ext3 -L nixos /dev/vda2", + "mount LABEL=nixos /mnt", + ) + ''; + }; - clone-test-extraconfig = { extraConfig = - '' - environment.systemPackages = [ pkgs.grub2 ]; - boot.loader.grub.configurationName = "Home"; - nesting.clone = [ - { - boot.loader.grub.configurationName = lib.mkForce "Work"; - - environment.etc = { - "gitconfig".text = " - [core] - gitproxy = none for work.com - "; - }; - } - ]; - ''; - testCloneConfig = true; + simple-uefi-grub-config = { + createPartitions = '' + machine.succeed( + "flock /dev/vda parted --script /dev/vda -- mklabel gpt" + + " mkpart ESP fat32 1M 50MiB" # /boot + + " set 1 boot on" + + " mkpart primary linux-swap 50MiB 1024MiB" + + " mkpart primary ext2 1024MiB -1MiB", # / + "udevadm settle", + "mkswap /dev/vda2 -L swap", + "swapon -L swap", + "mkfs.ext3 -L nixos /dev/vda3", + "mount LABEL=nixos /mnt", + "mkfs.vfat -n BOOT /dev/vda1", + "mkdir -p /mnt/boot", + "mount LABEL=BOOT /mnt/boot", + ) + ''; + bootLoader = "grub"; + grubUseEfi = true; + }; + + specialisation-test-extraconfig = { + extraConfig = '' + environment.systemPackages = [ pkgs.grub2 ]; + boot.loader.grub.configurationName = "Home"; + specialisation.work.configuration = { + boot.loader.grub.configurationName = lib.mkForce "Work"; + + environment.etc = { + "gitconfig".text = " + [core] + gitproxy = none for work.com + "; + }; + }; + ''; + testSpecialisationConfig = true; }; @@ -412,138 +440,129 @@ in { simple = makeInstallerTest "simple" simple-test-config; # Test cloned configurations with the simple grub configuration - simpleClone = makeInstallerTest "simpleClone" (simple-test-config // clone-test-extraconfig); + simpleSpecialised = makeInstallerTest "simpleSpecialised" (simple-test-config // specialisation-test-extraconfig); # Simple GPT/UEFI configuration using systemd-boot with 3 partitions: ESP, swap & root filesystem - simpleUefiSystemdBoot = makeInstallerTest "simpleUefiSystemdBoot" - { createPartitions = - '' - $machine->succeed( - "flock /dev/vda parted --script /dev/vda -- mklabel gpt" - . " mkpart ESP fat32 1M 50MiB" # /boot - . " set 1 boot on" - . " mkpart primary linux-swap 50MiB 1024MiB" - . " mkpart primary ext2 1024MiB -1MiB", # / - "udevadm settle", - "mkswap /dev/vda2 -L swap", - "swapon -L swap", - "mkfs.ext3 -L nixos /dev/vda3", - "mount LABEL=nixos /mnt", - "mkfs.vfat -n BOOT /dev/vda1", - "mkdir -p /mnt/boot", - "mount LABEL=BOOT /mnt/boot", - ); - ''; - bootLoader = "systemd-boot"; - }; + simpleUefiSystemdBoot = makeInstallerTest "simpleUefiSystemdBoot" { + createPartitions = '' + machine.succeed( + "flock /dev/vda parted --script /dev/vda -- mklabel gpt" + + " mkpart ESP fat32 1M 50MiB" # /boot + + " set 1 boot on" + + " mkpart primary linux-swap 50MiB 1024MiB" + + " mkpart primary ext2 1024MiB -1MiB", # / + "udevadm settle", + "mkswap /dev/vda2 -L swap", + "swapon -L swap", + "mkfs.ext3 -L nixos /dev/vda3", + "mount LABEL=nixos /mnt", + "mkfs.vfat -n BOOT /dev/vda1", + "mkdir -p /mnt/boot", + "mount LABEL=BOOT /mnt/boot", + ) + ''; + bootLoader = "systemd-boot"; + }; simpleUefiGrub = makeInstallerTest "simpleUefiGrub" simple-uefi-grub-config; # Test cloned configurations with the uefi grub configuration - simpleUefiGrubClone = makeInstallerTest "simpleUefiGrubClone" (simple-uefi-grub-config // clone-test-extraconfig); + simpleUefiGrubSpecialisation = makeInstallerTest "simpleUefiGrubSpecialisation" (simple-uefi-grub-config // specialisation-test-extraconfig); # Same as the previous, but now with a separate /boot partition. - separateBoot = makeInstallerTest "separateBoot" - { createPartitions = - '' - $machine->succeed( - "flock /dev/vda parted --script /dev/vda -- mklabel msdos" - . " mkpart primary ext2 1M 50MB" # /boot - . " mkpart primary linux-swap 50MB 1024M" - . " mkpart primary ext2 1024M -1s", # / - "udevadm settle", - "mkswap /dev/vda2 -L swap", - "swapon -L swap", - "mkfs.ext3 -L nixos /dev/vda3", - "mount LABEL=nixos /mnt", - "mkfs.ext3 -L boot /dev/vda1", - "mkdir -p /mnt/boot", - "mount LABEL=boot /mnt/boot", - ); - ''; - }; + separateBoot = makeInstallerTest "separateBoot" { + createPartitions = '' + machine.succeed( + "flock /dev/vda parted --script /dev/vda -- mklabel msdos" + + " mkpart primary ext2 1M 50MB" # /boot + + " mkpart primary linux-swap 50MB 1024M" + + " mkpart primary ext2 1024M -1s", # / + "udevadm settle", + "mkswap /dev/vda2 -L swap", + "swapon -L swap", + "mkfs.ext3 -L nixos /dev/vda3", + "mount LABEL=nixos /mnt", + "mkfs.ext3 -L boot /dev/vda1", + "mkdir -p /mnt/boot", + "mount LABEL=boot /mnt/boot", + ) + ''; + }; # Same as the previous, but with fat32 /boot. - separateBootFat = makeInstallerTest "separateBootFat" - { createPartitions = - '' - $machine->succeed( - "flock /dev/vda parted --script /dev/vda -- mklabel msdos" - . " mkpart primary ext2 1M 50MB" # /boot - . " mkpart primary linux-swap 50MB 1024M" - . " mkpart primary ext2 1024M -1s", # / - "udevadm settle", - "mkswap /dev/vda2 -L swap", - "swapon -L swap", - "mkfs.ext3 -L nixos /dev/vda3", - "mount LABEL=nixos /mnt", - "mkfs.vfat -n BOOT /dev/vda1", - "mkdir -p /mnt/boot", - "mount LABEL=BOOT /mnt/boot", - ); - ''; - }; + separateBootFat = makeInstallerTest "separateBootFat" { + createPartitions = '' + machine.succeed( + "flock /dev/vda parted --script /dev/vda -- mklabel msdos" + + " mkpart primary ext2 1M 50MB" # /boot + + " mkpart primary linux-swap 50MB 1024M" + + " mkpart primary ext2 1024M -1s", # / + "udevadm settle", + "mkswap /dev/vda2 -L swap", + "swapon -L swap", + "mkfs.ext3 -L nixos /dev/vda3", + "mount LABEL=nixos /mnt", + "mkfs.vfat -n BOOT /dev/vda1", + "mkdir -p /mnt/boot", + "mount LABEL=BOOT /mnt/boot", + ) + ''; + }; # zfs on / with swap - zfsroot = makeInstallerTest "zfs-root" - { - extraInstallerConfig = { - boot.supportedFilesystems = [ "zfs" ]; - }; - - extraConfig = '' - boot.supportedFilesystems = [ "zfs" ]; - - # Using by-uuid overrides the default of by-id, and is unique - # to the qemu disks, as they don't produce by-id paths for - # some reason. - boot.zfs.devNodes = "/dev/disk/by-uuid/"; - networking.hostId = "00000000"; - ''; - - createPartitions = - '' - $machine->succeed( - "flock /dev/vda parted --script /dev/vda -- mklabel msdos" - . " mkpart primary linux-swap 1M 1024M" - . " mkpart primary 1024M -1s", - "udevadm settle", + zfsroot = makeInstallerTest "zfs-root" { + extraInstallerConfig = { + boot.supportedFilesystems = [ "zfs" ]; + }; - "mkswap /dev/vda1 -L swap", - "swapon -L swap", + extraConfig = '' + boot.supportedFilesystems = [ "zfs" ]; - "zpool create rpool /dev/vda2", - "zfs create -o mountpoint=legacy rpool/root", - "mount -t zfs rpool/root /mnt", + # Using by-uuid overrides the default of by-id, and is unique + # to the qemu disks, as they don't produce by-id paths for + # some reason. + boot.zfs.devNodes = "/dev/disk/by-uuid/"; + networking.hostId = "00000000"; + ''; - "udevadm settle" - ); - ''; - }; + createPartitions = '' + machine.succeed( + "flock /dev/vda parted --script /dev/vda -- mklabel msdos" + + " mkpart primary linux-swap 1M 1024M" + + " mkpart primary 1024M -1s", + "udevadm settle", + "mkswap /dev/vda1 -L swap", + "swapon -L swap", + "zpool create rpool /dev/vda2", + "zfs create -o mountpoint=legacy rpool/root", + "mount -t zfs rpool/root /mnt", + "udevadm settle", + ) + ''; + }; # Create two physical LVM partitions combined into one volume group # that contains the logical swap and root partitions. - lvm = makeInstallerTest "lvm" - { createPartitions = - '' - $machine->succeed( - "flock /dev/vda parted --script /dev/vda -- mklabel msdos" - . " mkpart primary 1M 2048M" # PV1 - . " set 1 lvm on" - . " mkpart primary 2048M -1s" # PV2 - . " set 2 lvm on", - "udevadm settle", - "pvcreate /dev/vda1 /dev/vda2", - "vgcreate MyVolGroup /dev/vda1 /dev/vda2", - "lvcreate --size 1G --name swap MyVolGroup", - "lvcreate --size 2G --name nixos MyVolGroup", - "mkswap -f /dev/MyVolGroup/swap -L swap", - "swapon -L swap", - "mkfs.xfs -L nixos /dev/MyVolGroup/nixos", - "mount LABEL=nixos /mnt", - ); - ''; - }; + lvm = makeInstallerTest "lvm" { + createPartitions = '' + machine.succeed( + "flock /dev/vda parted --script /dev/vda -- mklabel msdos" + + " mkpart primary 1M 2048M" # PV1 + + " set 1 lvm on" + + " mkpart primary 2048M -1s" # PV2 + + " set 2 lvm on", + "udevadm settle", + "pvcreate /dev/vda1 /dev/vda2", + "vgcreate MyVolGroup /dev/vda1 /dev/vda2", + "lvcreate --size 1G --name swap MyVolGroup", + "lvcreate --size 2G --name nixos MyVolGroup", + "mkswap -f /dev/MyVolGroup/swap -L swap", + "swapon -L swap", + "mkfs.xfs -L nixos /dev/MyVolGroup/nixos", + "mount LABEL=nixos /mnt", + ) + ''; + }; # Boot off an encrypted root partition with the default LUKS header format luksroot = makeLuksRootTest "luksroot-format1" ""; @@ -557,14 +576,14 @@ in { # Test whether opening encrypted filesystem with keyfile # Checks for regression of missing cryptsetup, when no luks device without # keyfile is configured - encryptedFSWithKeyfile = makeInstallerTest "encryptedFSWithKeyfile" - { createPartitions = '' - $machine->succeed( + encryptedFSWithKeyfile = makeInstallerTest "encryptedFSWithKeyfile" { + createPartitions = '' + machine.succeed( "flock /dev/vda parted --script /dev/vda -- mklabel msdos" - . " mkpart primary ext2 1M 50MB" # /boot - . " mkpart primary linux-swap 50M 1024M" - . " mkpart primary 1024M 1280M" # LUKS with keyfile - . " mkpart primary 1280M -1s", + + " mkpart primary ext2 1M 50MB" # /boot + + " mkpart primary linux-swap 50M 1024M" + + " mkpart primary 1024M 1280M" # LUKS with keyfile + + " mkpart primary 1280M -1s", "udevadm settle", "mkswap /dev/vda2 -L swap", "swapon -L swap", @@ -579,89 +598,88 @@ in { "cryptsetup luksOpen --key-file /mnt/keyfile /dev/vda3 crypt", "mkfs.ext3 -L test /dev/mapper/crypt", "cryptsetup luksClose crypt", - "mkdir -p /mnt/test" - ); - ''; - extraConfig = '' - fileSystems."/test" = - { device = "/dev/disk/by-label/test"; - fsType = "ext3"; - encrypted.enable = true; - encrypted.blkDev = "/dev/vda3"; - encrypted.label = "crypt"; - encrypted.keyFile = "/mnt-root/keyfile"; - }; - ''; - }; - + "mkdir -p /mnt/test", + ) + ''; + extraConfig = '' + fileSystems."/test" = { + device = "/dev/disk/by-label/test"; + fsType = "ext3"; + encrypted.enable = true; + encrypted.blkDev = "/dev/vda3"; + encrypted.label = "crypt"; + encrypted.keyFile = "/mnt-root/keyfile"; + }; + ''; + }; - swraid = makeInstallerTest "swraid" - { createPartitions = - '' - $machine->succeed( - "flock /dev/vda parted --script /dev/vda --" - . " mklabel msdos" - . " mkpart primary ext2 1M 100MB" # /boot - . " mkpart extended 100M -1s" - . " mkpart logical 102M 2102M" # md0 (root), first device - . " mkpart logical 2103M 4103M" # md0 (root), second device - . " mkpart logical 4104M 4360M" # md1 (swap), first device - . " mkpart logical 4361M 4617M", # md1 (swap), second device - "udevadm settle", - "ls -l /dev/vda* >&2", - "cat /proc/partitions >&2", - "udevadm control --stop-exec-queue", - "mdadm --create --force /dev/md0 --metadata 1.2 --level=raid1 --raid-devices=2 /dev/vda5 /dev/vda6", - "mdadm --create --force /dev/md1 --metadata 1.2 --level=raid1 --raid-devices=2 /dev/vda7 /dev/vda8", - "udevadm control --start-exec-queue", - "udevadm settle", - "mkswap -f /dev/md1 -L swap", - "swapon -L swap", - "mkfs.ext3 -L nixos /dev/md0", - "mount LABEL=nixos /mnt", - "mkfs.ext3 -L boot /dev/vda1", - "mkdir /mnt/boot", - "mount LABEL=boot /mnt/boot", - "udevadm settle", - ); - ''; - preBootCommands = '' - $machine->start; - $machine->fail("dmesg | grep 'immediate safe mode'"); - ''; - }; + swraid = makeInstallerTest "swraid" { + createPartitions = '' + machine.succeed( + "flock /dev/vda parted --script /dev/vda --" + + " mklabel msdos" + + " mkpart primary ext2 1M 100MB" # /boot + + " mkpart extended 100M -1s" + + " mkpart logical 102M 2102M" # md0 (root), first device + + " mkpart logical 2103M 4103M" # md0 (root), second device + + " mkpart logical 4104M 4360M" # md1 (swap), first device + + " mkpart logical 4361M 4617M", # md1 (swap), second device + "udevadm settle", + "ls -l /dev/vda* >&2", + "cat /proc/partitions >&2", + "udevadm control --stop-exec-queue", + "mdadm --create --force /dev/md0 --metadata 1.2 --level=raid1 " + + "--raid-devices=2 /dev/vda5 /dev/vda6", + "mdadm --create --force /dev/md1 --metadata 1.2 --level=raid1 " + + "--raid-devices=2 /dev/vda7 /dev/vda8", + "udevadm control --start-exec-queue", + "udevadm settle", + "mkswap -f /dev/md1 -L swap", + "swapon -L swap", + "mkfs.ext3 -L nixos /dev/md0", + "mount LABEL=nixos /mnt", + "mkfs.ext3 -L boot /dev/vda1", + "mkdir /mnt/boot", + "mount LABEL=boot /mnt/boot", + "udevadm settle", + ) + ''; + preBootCommands = '' + machine.start() + machine.fail("dmesg | grep 'immediate safe mode'") + ''; + }; # Test a basic install using GRUB 1. - grub1 = makeInstallerTest "grub1" - { createPartitions = - '' - $machine->succeed( - "flock /dev/sda parted --script /dev/sda -- mklabel msdos" - . " mkpart primary linux-swap 1M 1024M" - . " mkpart primary ext2 1024M -1s", - "udevadm settle", - "mkswap /dev/sda1 -L swap", - "swapon -L swap", - "mkfs.ext3 -L nixos /dev/sda2", - "mount LABEL=nixos /mnt", - "mkdir -p /mnt/tmp", - ); - ''; - grubVersion = 1; - grubDevice = "/dev/sda"; - }; + grub1 = makeInstallerTest "grub1" { + createPartitions = '' + machine.succeed( + "flock /dev/sda parted --script /dev/sda -- mklabel msdos" + + " mkpart primary linux-swap 1M 1024M" + + " mkpart primary ext2 1024M -1s", + "udevadm settle", + "mkswap /dev/sda1 -L swap", + "swapon -L swap", + "mkfs.ext3 -L nixos /dev/sda2", + "mount LABEL=nixos /mnt", + "mkdir -p /mnt/tmp", + ) + ''; + grubVersion = 1; + grubDevice = "/dev/sda"; + }; # Test using labels to identify volumes in grub simpleLabels = makeInstallerTest "simpleLabels" { createPartitions = '' - $machine->succeed( - "sgdisk -Z /dev/vda", - "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda", - "mkswap /dev/vda2 -L swap", - "swapon -L swap", - "mkfs.ext4 -L root /dev/vda3", - "mount LABEL=root /mnt", - ); + machine.succeed( + "sgdisk -Z /dev/vda", + "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda", + "mkswap /dev/vda2 -L swap", + "swapon -L swap", + "mkfs.ext4 -L root /dev/vda3", + "mount LABEL=root /mnt", + ) ''; grubIdentifier = "label"; }; @@ -670,22 +688,23 @@ in { # TODO: Fix udev so the symlinks are unneeded in /dev/disks simpleProvided = makeInstallerTest "simpleProvided" { createPartitions = '' - my $UUID = "\$(blkid -s UUID -o value /dev/vda2)"; - $machine->succeed( - "sgdisk -Z /dev/vda", - "sgdisk -n 1:0:+1M -n 2:0:+100M -n 3:0:+1G -N 4 -t 1:ef02 -t 2:8300 -t 3:8200 -t 4:8300 -c 2:boot -c 4:root /dev/vda", - "mkswap /dev/vda3 -L swap", - "swapon -L swap", - "mkfs.ext4 -L boot /dev/vda2", - "mkfs.ext4 -L root /dev/vda4", - ); - $machine->execute("ln -s ../../vda2 /dev/disk/by-uuid/$UUID"); - $machine->execute("ln -s ../../vda4 /dev/disk/by-label/root"); - $machine->succeed( - "mount /dev/disk/by-label/root /mnt", - "mkdir /mnt/boot", - "mount /dev/disk/by-uuid/$UUID /mnt/boot" - ); + uuid = "$(blkid -s UUID -o value /dev/vda2)" + machine.succeed( + "sgdisk -Z /dev/vda", + "sgdisk -n 1:0:+1M -n 2:0:+100M -n 3:0:+1G -N 4 -t 1:ef02 -t 2:8300 " + + "-t 3:8200 -t 4:8300 -c 2:boot -c 4:root /dev/vda", + "mkswap /dev/vda3 -L swap", + "swapon -L swap", + "mkfs.ext4 -L boot /dev/vda2", + "mkfs.ext4 -L root /dev/vda4", + ) + machine.execute(f"ln -s ../../vda2 /dev/disk/by-uuid/{uuid}") + machine.execute("ln -s ../../vda4 /dev/disk/by-label/root") + machine.succeed( + "mount /dev/disk/by-label/root /mnt", + "mkdir /mnt/boot", + f"mount /dev/disk/by-uuid/{uuid} /mnt/boot", + ) ''; grubIdentifier = "provided"; }; @@ -693,61 +712,62 @@ in { # Simple btrfs grub testing btrfsSimple = makeInstallerTest "btrfsSimple" { createPartitions = '' - $machine->succeed( - "sgdisk -Z /dev/vda", - "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda", - "mkswap /dev/vda2 -L swap", - "swapon -L swap", - "mkfs.btrfs -L root /dev/vda3", - "mount LABEL=root /mnt", - ); + machine.succeed( + "sgdisk -Z /dev/vda", + "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda", + "mkswap /dev/vda2 -L swap", + "swapon -L swap", + "mkfs.btrfs -L root /dev/vda3", + "mount LABEL=root /mnt", + ) ''; }; # Test to see if we can detect /boot and /nix on subvolumes btrfsSubvols = makeInstallerTest "btrfsSubvols" { createPartitions = '' - $machine->succeed( - "sgdisk -Z /dev/vda", - "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda", - "mkswap /dev/vda2 -L swap", - "swapon -L swap", - "mkfs.btrfs -L root /dev/vda3", - "btrfs device scan", - "mount LABEL=root /mnt", - "btrfs subvol create /mnt/boot", - "btrfs subvol create /mnt/nixos", - "btrfs subvol create /mnt/nixos/default", - "umount /mnt", - "mount -o defaults,subvol=nixos/default LABEL=root /mnt", - "mkdir /mnt/boot", - "mount -o defaults,subvol=boot LABEL=root /mnt/boot", - ); + machine.succeed( + "sgdisk -Z /dev/vda", + "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda", + "mkswap /dev/vda2 -L swap", + "swapon -L swap", + "mkfs.btrfs -L root /dev/vda3", + "btrfs device scan", + "mount LABEL=root /mnt", + "btrfs subvol create /mnt/boot", + "btrfs subvol create /mnt/nixos", + "btrfs subvol create /mnt/nixos/default", + "umount /mnt", + "mount -o defaults,subvol=nixos/default LABEL=root /mnt", + "mkdir /mnt/boot", + "mount -o defaults,subvol=boot LABEL=root /mnt/boot", + ) ''; }; # Test to see if we can detect default and aux subvolumes correctly btrfsSubvolDefault = makeInstallerTest "btrfsSubvolDefault" { createPartitions = '' - $machine->succeed( - "sgdisk -Z /dev/vda", - "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda", - "mkswap /dev/vda2 -L swap", - "swapon -L swap", - "mkfs.btrfs -L root /dev/vda3", - "btrfs device scan", - "mount LABEL=root /mnt", - "btrfs subvol create /mnt/badpath", - "btrfs subvol create /mnt/badpath/boot", - "btrfs subvol create /mnt/nixos", - "btrfs subvol set-default \$(btrfs subvol list /mnt | grep 'nixos' | awk '{print \$2}') /mnt", - "umount /mnt", - "mount -o defaults LABEL=root /mnt", - "mkdir -p /mnt/badpath/boot", # Help ensure the detection mechanism is actually looking up subvolumes - "mkdir /mnt/boot", - "mount -o defaults,subvol=badpath/boot LABEL=root /mnt/boot", - ); + machine.succeed( + "sgdisk -Z /dev/vda", + "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda", + "mkswap /dev/vda2 -L swap", + "swapon -L swap", + "mkfs.btrfs -L root /dev/vda3", + "btrfs device scan", + "mount LABEL=root /mnt", + "btrfs subvol create /mnt/badpath", + "btrfs subvol create /mnt/badpath/boot", + "btrfs subvol create /mnt/nixos", + "btrfs subvol set-default " + + "$(btrfs subvol list /mnt | grep 'nixos' | awk '{print \$2}') /mnt", + "umount /mnt", + "mount -o defaults LABEL=root /mnt", + "mkdir -p /mnt/badpath/boot", # Help ensure the detection mechanism + # is actually looking up subvolumes + "mkdir /mnt/boot", + "mount -o defaults,subvol=badpath/boot LABEL=root /mnt/boot", + ) ''; }; - } diff --git a/nixos/tests/iodine.nix b/nixos/tests/iodine.nix new file mode 100644 index 0000000000000..41fb2e7778d42 --- /dev/null +++ b/nixos/tests/iodine.nix @@ -0,0 +1,64 @@ +import ./make-test-python.nix ( + { pkgs, ... }: let + domain = "whatever.example.com"; + password = "false;foo;exit;withspecialcharacters"; + in + { + name = "iodine"; + nodes = { + server = + { ... }: + + { + networking.firewall = { + allowedUDPPorts = [ 53 ]; + trustedInterfaces = [ "dns0" ]; + }; + boot.kernel.sysctl = { + "net.ipv4.ip_forward" = 1; + "net.ipv6.ip_forward" = 1; + }; + + services.iodine.server = { + enable = true; + ip = "10.53.53.1/24"; + passwordFile = "${builtins.toFile "password" password}"; + inherit domain; + }; + + # test resource: accessible only via tunnel + services.openssh = { + enable = true; + openFirewall = false; + }; + }; + + client = + { ... }: { + services.iodine.clients.testClient = { + # test that ProtectHome is "read-only" + passwordFile = "/root/pw"; + relay = "server"; + server = domain; + }; + systemd.tmpfiles.rules = [ + "f /root/pw 0666 root root - ${password}" + ]; + environment.systemPackages = [ + pkgs.nagiosPluginsOfficial + ]; + }; + + }; + + testScript = '' + start_all() + + server.wait_for_unit("sshd") + server.wait_for_unit("iodined") + client.wait_for_unit("iodine-testClient") + + client.succeed("check_ssh -H 10.53.53.1") + ''; + } +) diff --git a/nixos/tests/ipv6.nix b/nixos/tests/ipv6.nix index d11eba764da3d..ba464b57447bb 100644 --- a/nixos/tests/ipv6.nix +++ b/nixos/tests/ipv6.nix @@ -1,7 +1,7 @@ # Test of IPv6 functionality in NixOS, including whether router # solicication/advertisement using radvd works. -import ./make-test.nix ({ pkgs, lib, ...} : { +import ./make-test-python.nix ({ pkgs, lib, ...} : { name = "ipv6"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ eelco ]; @@ -35,51 +35,56 @@ import ./make-test.nix ({ pkgs, lib, ...} : { testScript = '' + import re + # Start the router first so that it respond to router solicitations. - $router->waitForUnit("radvd"); + router.wait_for_unit("radvd") - startAll; + start_all() - $client->waitForUnit("network.target"); - $server->waitForUnit("network.target"); - $server->waitForUnit("httpd.service"); + client.wait_for_unit("network.target") + server.wait_for_unit("network.target") + server.wait_for_unit("httpd.service") # Wait until the given interface has a non-tentative address of # the desired scope (i.e. has completed Duplicate Address # Detection). - sub waitForAddress { - my ($machine, $iface, $scope) = @_; - $machine->waitUntilSucceeds("[ `ip -o -6 addr show dev $iface scope $scope | grep -v tentative | wc -l` -ge 1 ]"); - my $ip = (split /[ \/]+/, $machine->succeed("ip -o -6 addr show dev $iface scope $scope"))[3]; - $machine->log("$scope address on $iface is $ip"); - return $ip; - } - - subtest "loopback address", sub { - $client->succeed("ping -c 1 ::1 >&2"); - $client->fail("ping -c 1 ::2 >&2"); - }; - - subtest "local link addressing", sub { - my $clientIp = waitForAddress $client, "eth1", "link"; - my $serverIp = waitForAddress $server, "eth1", "link"; - $client->succeed("ping -c 1 $clientIp%eth1 >&2"); - $client->succeed("ping -c 1 $serverIp%eth1 >&2"); - }; - - subtest "global addressing", sub { - my $clientIp = waitForAddress $client, "eth1", "global"; - my $serverIp = waitForAddress $server, "eth1", "global"; - $client->succeed("ping -c 1 $clientIp >&2"); - $client->succeed("ping -c 1 $serverIp >&2"); - $client->succeed("curl --fail -g http://[$serverIp]"); - $client->fail("curl --fail -g http://[$clientIp]"); - }; - subtest "privacy extensions", sub { - my $ip = waitForAddress $client, "eth1", "global temporary"; + def wait_for_address(machine, iface, scope, temporary=False): + temporary_flag = "temporary" if temporary else "-temporary" + cmd = f"ip -o -6 addr show dev {iface} scope {scope} -tentative {temporary_flag}" + + machine.wait_until_succeeds(f"[ `{cmd} | wc -l` -eq 1 ]") + output = machine.succeed(cmd) + ip = re.search(r"inet6 ([0-9a-f:]{2,})/", output).group(1) + + if temporary: + scope = scope + " temporary" + machine.log(f"{scope} address on {iface} is {ip}") + return ip + + + with subtest("Loopback address can be pinged"): + client.succeed("ping -c 1 ::1 >&2") + client.fail("ping -c 1 ::2 >&2") + + with subtest("Local link addresses can be obtained and pinged"): + client_ip = wait_for_address(client, "eth1", "link") + server_ip = wait_for_address(server, "eth1", "link") + client.succeed(f"ping -c 1 {client_ip}%eth1 >&2") + client.succeed(f"ping -c 1 {server_ip}%eth1 >&2") + + with subtest("Global addresses can be obtained, pinged, and reached via http"): + client_ip = wait_for_address(client, "eth1", "global") + server_ip = wait_for_address(server, "eth1", "global") + client.succeed(f"ping -c 1 {client_ip} >&2") + client.succeed(f"ping -c 1 {server_ip} >&2") + client.succeed(f"curl --fail -g http://[{server_ip}]") + client.fail(f"curl --fail -g http://[{client_ip}]") + + with subtest("Privacy extensions: Global temporary address can be obtained and pinged"): + ip = wait_for_address(client, "eth1", "global", temporary=True) # Default route should have "src <temporary address>" in it - $client->succeed("ip r g ::2 | grep $ip"); - }; + client.succeed(f"ip r g ::2 | grep {ip}") # TODO: test reachability of a machine on another network. ''; diff --git a/nixos/tests/jirafeau.nix b/nixos/tests/jirafeau.nix new file mode 100644 index 0000000000000..0f5af7f718a40 --- /dev/null +++ b/nixos/tests/jirafeau.nix @@ -0,0 +1,22 @@ +import ./make-test-python.nix ({ lib, ... }: + +with lib; + +{ + name = "jirafeau"; + meta.maintainers = with maintainers; [ davidtwco ]; + + nodes.machine = { pkgs, ... }: { + services.jirafeau = { + enable = true; + }; + }; + + testScript = '' + machine.start() + machine.wait_for_unit("phpfpm-jirafeau.service") + machine.wait_for_unit("nginx.service") + machine.wait_for_open_port(80) + machine.succeed("curl -sSfL http://localhost/ | grep 'Jirafeau'") + ''; +}) diff --git a/nixos/tests/k3s.nix b/nixos/tests/k3s.nix new file mode 100644 index 0000000000000..5bda6f493f0e3 --- /dev/null +++ b/nixos/tests/k3s.nix @@ -0,0 +1,78 @@ +import ./make-test-python.nix ({ pkgs, ... }: + +let + # A suitable k3s pause image, also used for the test pod + pauseImage = pkgs.dockerTools.buildImage { + name = "test.local/pause"; + tag = "local"; + contents = with pkgs; [ tini coreutils busybox ]; + config.Entrypoint = [ "/bin/tini" "--" "/bin/sleep" "inf" ]; + }; + testPodYaml = pkgs.writeText "test.yml" '' + # Don't use the default service account because there's a race where it may + # not be created yet; make our own instead. + apiVersion: v1 + kind: ServiceAccount + metadata: + name: test + --- + apiVersion: v1 + kind: Pod + metadata: + name: test + spec: + serviceAccountName: test + containers: + - name: test + image: test.local/pause:local + imagePullPolicy: Never + command: ["sh", "-c", "sleep inf"] + ''; +in +{ + name = "k3s"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ euank ]; + }; + + nodes = { + k3s = + { pkgs, ... }: { + environment.systemPackages = [ pkgs.k3s pkgs.gzip ]; + + # k3s uses enough resources the default vm fails. + virtualisation.memorySize = pkgs.lib.mkDefault 1536; + virtualisation.diskSize = pkgs.lib.mkDefault 4096; + + services.k3s.enable = true; + services.k3s.role = "server"; + services.k3s.package = pkgs.k3s; + # Slightly reduce resource usage + services.k3s.extraFlags = "--no-deploy coredns,servicelb,traefik,local-storage,metrics-server --pause-image test.local/pause:local"; + + users.users = { + noprivs = { + isNormalUser = true; + description = "Can't access k3s by default"; + password = "*"; + }; + }; + }; + }; + + testScript = '' + start_all() + + k3s.wait_for_unit("k3s") + k3s.succeed("k3s kubectl cluster-info") + k3s.fail("sudo -u noprivs k3s kubectl cluster-info") + # k3s.succeed("k3s check-config") # fails with the current nixos kernel config, uncomment once this passes + + k3s.succeed( + "zcat ${pauseImage} | k3s ctr image import -" + ) + + k3s.succeed("k3s kubectl apply -f ${testPodYaml}") + k3s.succeed("k3s kubectl wait --for 'condition=Ready' pod/test") + ''; +}) diff --git a/nixos/tests/kafka.nix b/nixos/tests/kafka.nix index 48ca98da8fa1f..d29c802b47b42 100644 --- a/nixos/tests/kafka.nix +++ b/nixos/tests/kafka.nix @@ -3,11 +3,10 @@ pkgs ? import ../.. { inherit system config; } }: -with import ../lib/testing.nix { inherit system pkgs; }; with pkgs.lib; let - makeKafkaTest = name: kafkaPackage: (makeTest { + makeKafkaTest = name: kafkaPackage: (import ./make-test-python.nix ({ inherit name; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ nequissimus ]; @@ -45,24 +44,40 @@ let }; testScript = '' - startAll; + start_all() - $zookeeper1->waitForUnit("default.target"); - $zookeeper1->waitForUnit("zookeeper.service"); - $zookeeper1->waitForOpenPort(2181); + zookeeper1.wait_for_unit("default.target") + zookeeper1.wait_for_unit("zookeeper.service") + zookeeper1.wait_for_open_port(2181) - $kafka->waitForUnit("default.target"); - $kafka->waitForUnit("apache-kafka.service"); - $kafka->waitForOpenPort(9092); + kafka.wait_for_unit("default.target") + kafka.wait_for_unit("apache-kafka.service") + kafka.wait_for_open_port(9092) - $kafka->waitUntilSucceeds("${kafkaPackage}/bin/kafka-topics.sh --create --zookeeper zookeeper1:2181 --partitions 1 --replication-factor 1 --topic testtopic"); - $kafka->mustSucceed("echo 'test 1' | ${kafkaPackage}/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic testtopic"); + kafka.wait_until_succeeds( + "${kafkaPackage}/bin/kafka-topics.sh --create " + + "--zookeeper zookeeper1:2181 --partitions 1 " + + "--replication-factor 1 --topic testtopic" + ) + kafka.succeed( + "echo 'test 1' | " + + "${kafkaPackage}/bin/kafka-console-producer.sh " + + "--broker-list localhost:9092 --topic testtopic" + ) '' + (if name == "kafka_0_9" then '' - $kafka->mustSucceed("${kafkaPackage}/bin/kafka-console-consumer.sh --zookeeper zookeeper1:2181 --topic testtopic --from-beginning --max-messages 1 | grep 'test 1'"); + assert "test 1" in kafka.succeed( + "${kafkaPackage}/bin/kafka-console-consumer.sh " + + "--zookeeper zookeeper1:2181 --topic testtopic " + + "--from-beginning --max-messages 1" + ) '' else '' - $kafka->mustSucceed("${kafkaPackage}/bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic testtopic --from-beginning --max-messages 1 | grep 'test 1'"); + assert "test 1" in kafka.succeed( + "${kafkaPackage}/bin/kafka-console-consumer.sh " + + "--bootstrap-server localhost:9092 --topic testtopic " + + "--from-beginning --max-messages 1" + ) ''); - }); + }) { inherit system; }); in with pkgs; { kafka_0_9 = makeKafkaTest "kafka_0_9" apacheKafka_0_9; @@ -74,4 +89,5 @@ in with pkgs; { kafka_2_1 = makeKafkaTest "kafka_2_1" apacheKafka_2_1; kafka_2_2 = makeKafkaTest "kafka_2_2" apacheKafka_2_2; kafka_2_3 = makeKafkaTest "kafka_2_3" apacheKafka_2_3; + kafka_2_4 = makeKafkaTest "kafka_2_4" apacheKafka_2_4; } diff --git a/nixos/tests/keepalived.nix b/nixos/tests/keepalived.nix new file mode 100644 index 0000000000000..d0bf9d4652003 --- /dev/null +++ b/nixos/tests/keepalived.nix @@ -0,0 +1,42 @@ +import ./make-test-python.nix ({ pkgs, ... }: { + name = "keepalived"; + + nodes = { + node1 = { pkgs, ... }: { + networking.firewall.extraCommands = "iptables -A INPUT -p vrrp -j ACCEPT"; + services.keepalived.enable = true; + services.keepalived.vrrpInstances.test = { + interface = "eth1"; + state = "MASTER"; + priority = 50; + virtualIps = [{ addr = "192.168.1.200"; }]; + virtualRouterId = 1; + }; + environment.systemPackages = [ pkgs.tcpdump ]; + }; + node2 = { pkgs, ... }: { + networking.firewall.extraCommands = "iptables -A INPUT -p vrrp -j ACCEPT"; + services.keepalived.enable = true; + services.keepalived.vrrpInstances.test = { + interface = "eth1"; + state = "MASTER"; + priority = 100; + virtualIps = [{ addr = "192.168.1.200"; }]; + virtualRouterId = 1; + }; + environment.systemPackages = [ pkgs.tcpdump ]; + }; + }; + + testScript = '' + # wait for boot time delay to pass + for node in [node1, node2]: + node.wait_until_succeeds( + "systemctl show -p LastTriggerUSecMonotonic keepalived-boot-delay.timer | grep -vq 'LastTriggerUSecMonotonic=0'" + ) + node.wait_for_unit("keepalived") + node2.wait_until_succeeds("ip addr show dev eth1 | grep -q 192.168.1.200") + node1.fail("ip addr show dev eth1 | grep -q 192.168.1.200") + node1.succeed("ping -c1 192.168.1.200") + ''; +}) diff --git a/nixos/tests/kexec.nix b/nixos/tests/kexec.nix index b13b4131091fb..ec0cd9796b0e2 100644 --- a/nixos/tests/kexec.nix +++ b/nixos/tests/kexec.nix @@ -1,9 +1,15 @@ # Test whether fast reboots via kexec work. -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, lib, ...} : { name = "kexec"; - meta = with pkgs.stdenv.lib.maintainers; { + meta = with lib.maintainers; { maintainers = [ eelco ]; + # Currently hangs forever; last output is: + # machine # [ 10.239914] dhcpcd[707]: eth0: adding default route via fe80::2 + # machine: waiting for the VM to finish booting + # machine # Cannot find the ESP partition mount point. + # machine # [ 28.681197] nscd[692]: 692 checking for monitored file `/etc/netgroup': No such file or directory + broken = true; }; machine = { ... }: @@ -11,9 +17,9 @@ import ./make-test.nix ({ pkgs, ...} : { testScript = '' - $machine->waitForUnit("multi-user.target"); - $machine->execute("systemctl kexec &"); - $machine->{connected} = 0; - $machine->waitForUnit("multi-user.target"); + machine.wait_for_unit("multi-user.target") + machine.execute("systemctl kexec &") + machine.connected = False + machine.wait_for_unit("multi-user.target") ''; }) diff --git a/nixos/tests/keymap.nix b/nixos/tests/keymap.nix index 2b4c1ab7b0529..09d5d2a6c9e14 100644 --- a/nixos/tests/keymap.nix +++ b/nixos/tests/keymap.nix @@ -3,14 +3,13 @@ pkgs ? import ../.. { inherit system config; } }: -with import ../lib/testing.nix { inherit system pkgs; }; +with import ../lib/testing-python.nix { inherit system pkgs; }; let readyFile = "/tmp/readerReady"; resultFile = "/tmp/readerResult"; testReader = pkgs.writeScript "test-input-reader" '' - #!${pkgs.stdenv.shell} rm -f ${resultFile} ${resultFile}.tmp logger "testReader: START: Waiting for $1 characters, expecting '$2'." touch ${readyFile} @@ -27,56 +26,75 @@ let ''; - mkKeyboardTest = layout: { extraConfig ? {}, tests }: with pkgs.lib; let - combinedTests = foldAttrs (acc: val: acc ++ val) [] (builtins.attrValues tests); - perlStr = val: "'${escape ["'" "\\"] val}'"; - lq = length combinedTests.qwerty; - le = length combinedTests.expect; - msg = "length mismatch between qwerty (${toString lq}) and expect (${toString le}) lists!"; - send = concatMapStringsSep ", " perlStr combinedTests.qwerty; - expect = if (lq == le) then concatStrings combinedTests.expect else throw msg; - - in makeTest { + mkKeyboardTest = layout: { extraConfig ? {}, tests }: with pkgs.lib; makeTest { name = "keymap-${layout}"; + machine.console.keyMap = mkOverride 900 layout; machine.services.xserver.desktopManager.xterm.enable = false; - machine.i18n.consoleKeyMap = mkOverride 900 layout; machine.services.xserver.layout = mkOverride 900 layout; machine.imports = [ ./common/x11.nix extraConfig ]; testScript = '' - - sub mkTest ($$) { - my ($desc, $cmd) = @_; - - subtest $desc, sub { - # prepare and start testReader - $machine->execute("rm -f ${readyFile} ${resultFile}"); - $machine->succeed("$cmd ${testReader} ${toString le} ".q(${escapeShellArg expect} & )); - - if ($desc eq "Xorg keymap") { - # make sure the xterm window is open and has focus - $machine->waitForWindow(qr/testterm/); - $machine->waitUntilSucceeds("${pkgs.xdotool}/bin/xdotool search --sync --onlyvisible --class testterm windowfocus --sync"); - } - - # wait for reader to be ready - $machine->waitForFile("${readyFile}"); - $machine->sleep(1); - - # send all keys - foreach ((${send})) { $machine->sendKeys($_); }; - - # wait for result and check - $machine->waitForFile("${resultFile}"); - $machine->succeed("grep -q 'PASS:' ${resultFile}"); - }; - }; - - $machine->waitForX; - - mkTest "VT keymap", "openvt -sw --"; - mkTest "Xorg keymap", "DISPLAY=:0 xterm -title testterm -class testterm -fullscreen -e"; + import json + import shlex + + + def run_test_case(cmd, xorg_keymap, test_case_name, inputs, expected): + with subtest(test_case_name): + assert len(inputs) == len(expected) + machine.execute("rm -f ${readyFile} ${resultFile}") + + # set up process that expects all the keys to be entered + machine.succeed( + "{} {} {} {} &".format( + cmd, + "${testReader}", + len(inputs), + shlex.quote("".join(expected)), + ) + ) + + if xorg_keymap: + # make sure the xterm window is open and has focus + machine.wait_for_window("testterm") + machine.wait_until_succeeds( + "${pkgs.xdotool}/bin/xdotool search --sync --onlyvisible " + "--class testterm windowfocus --sync" + ) + + # wait for reader to be ready + machine.wait_for_file("${readyFile}") + machine.sleep(1) + + # send all keys + for key in inputs: + machine.send_key(key) + + # wait for result and check + machine.wait_for_file("${resultFile}") + machine.succeed("grep -q 'PASS:' ${resultFile}") + + + with open("${pkgs.writeText "tests.json" (builtins.toJSON tests)}") as json_file: + tests = json.load(json_file) + + keymap_environments = { + "VT Keymap": "openvt -sw --", + "Xorg Keymap": "DISPLAY=:0 xterm -title testterm -class testterm -fullscreen -e", + } + + machine.wait_for_x() + + for keymap_env_name, command in keymap_environments.items(): + with subtest(keymap_env_name): + for test_case_name, test_data in tests.items(): + run_test_case( + command, + False, + test_case_name, + test_data["qwerty"], + test_data["expect"], + ) ''; }; @@ -89,7 +107,7 @@ in pkgs.lib.mapAttrs mkKeyboardTest { altgr.expect = [ "~" "#" "{" "[" "|" ]; }; - extraConfig.i18n.consoleKeyMap = "azerty/fr"; + extraConfig.console.keyMap = "azerty/fr"; extraConfig.services.xserver.layout = "fr"; }; @@ -99,7 +117,7 @@ in pkgs.lib.mapAttrs mkKeyboardTest { homerow.expect = [ "a" "r" "s" "t" "n" "e" "i" "o" ]; }; - extraConfig.i18n.consoleKeyMap = "colemak/colemak"; + extraConfig.console.keyMap = "colemak/colemak"; extraConfig.services.xserver.layout = "us"; extraConfig.services.xserver.xkbVariant = "colemak"; }; @@ -151,7 +169,7 @@ in pkgs.lib.mapAttrs mkKeyboardTest { altgr.expect = [ "@" "|" "{" "[" "]" "}" ]; }; - extraConfig.i18n.consoleKeyMap = "de"; + extraConfig.console.keyMap = "de"; extraConfig.services.xserver.layout = "de"; }; } diff --git a/nixos/tests/knot.nix b/nixos/tests/knot.nix index 0588cf86ac09e..8bab917a351e3 100644 --- a/nixos/tests/knot.nix +++ b/nixos/tests/knot.nix @@ -28,6 +28,13 @@ let name = "knot-zones"; paths = [ exampleZone delegatedZone ]; }; + # DO NOT USE pkgs.writeText IN PRODUCTION. This put secrets in the nix store! + tsigFile = pkgs.writeText "tsig.conf" '' + key: + - id: slave_key + algorithm: hmac-sha256 + secret: zOYgOgnzx3TGe5J5I/0kxd7gTcxXhLYMEq3Ek3fY37s= + ''; in { name = "knot"; meta = with pkgs.stdenv.lib.maintainers; { @@ -48,6 +55,7 @@ in { }; services.knot.enable = true; services.knot.extraArgs = [ "-v" ]; + services.knot.keyFiles = [ tsigFile ]; services.knot.extraConfig = '' server: listen: 0.0.0.0@53 @@ -56,6 +64,7 @@ in { acl: - id: slave_acl address: 192.168.0.2 + key: slave_key action: transfer remote: @@ -103,6 +112,7 @@ in { ]; }; services.knot.enable = true; + services.knot.keyFiles = [ tsigFile ]; services.knot.extraArgs = [ "-v" ]; services.knot.extraConfig = '' server: @@ -117,6 +127,7 @@ in { remote: - id: master address: 192.168.0.1@53 + key: slave_key template: - id: default @@ -155,10 +166,10 @@ in { ]; }; environment.systemPackages = [ pkgs.knot-dns ]; - }; + }; }; - testScript = { nodes, ... }: let + testScript = { nodes, ... }: let master4 = (lib.head nodes.master.config.networking.interfaces.eth1.ipv4.addresses).address; master6 = (lib.head nodes.master.config.networking.interfaces.eth1.ipv6.addresses).address; diff --git a/nixos/tests/krb5/deprecated-config.nix b/nixos/tests/krb5/deprecated-config.nix index 7d7926309c95b..be6ebce9e0516 100644 --- a/nixos/tests/krb5/deprecated-config.nix +++ b/nixos/tests/krb5/deprecated-config.nix @@ -1,7 +1,7 @@ # Verifies that the configuration suggested in deprecated example values # will result in the expected output. -import ../make-test.nix ({ pkgs, ...} : { +import ../make-test-python.nix ({ pkgs, ...} : { name = "krb5-with-deprecated-config"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ eqyiel ]; @@ -43,6 +43,8 @@ import ../make-test.nix ({ pkgs, ...} : { ''; in '' - $machine->succeed("diff /etc/krb5.conf ${snapshot}"); + machine.succeed( + "diff /etc/krb5.conf ${snapshot}" + ) ''; }) diff --git a/nixos/tests/krb5/example-config.nix b/nixos/tests/krb5/example-config.nix index f01cf6988eef7..be195b5139352 100644 --- a/nixos/tests/krb5/example-config.nix +++ b/nixos/tests/krb5/example-config.nix @@ -1,7 +1,7 @@ # Verifies that the configuration suggested in (non-deprecated) example values # will result in the expected output. -import ../make-test.nix ({ pkgs, ...} : { +import ../make-test-python.nix ({ pkgs, ...} : { name = "krb5-with-example-config"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ eqyiel ]; @@ -101,6 +101,8 @@ import ../make-test.nix ({ pkgs, ...} : { default = SYSLOG:NOTICE ''; in '' - $machine->succeed("diff /etc/krb5.conf ${snapshot}"); + machine.succeed( + "diff /etc/krb5.conf ${snapshot}" + ) ''; }) diff --git a/nixos/tests/kubernetes/dns.nix b/nixos/tests/kubernetes/dns.nix index 46bcb01a52652..638942e15407d 100644 --- a/nixos/tests/kubernetes/dns.nix +++ b/nixos/tests/kubernetes/dns.nix @@ -3,8 +3,6 @@ with import ./base.nix { inherit system; }; let domain = "my.zyx"; - certs = import ./certs.nix { externalDomain = domain; kubelets = [ "machine1" "machine2" ]; }; - redisPod = pkgs.writeText "redis-pod.json" (builtins.toJSON { kind = "Pod"; apiVersion = "v1"; diff --git a/nixos/tests/ldap.nix b/nixos/tests/ldap.nix index 665b9ee09b55e..74b002fc00eec 100644 --- a/nixos/tests/ldap.nix +++ b/nixos/tests/ldap.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, lib, ...} : +import ./make-test-python.nix ({ pkgs, lib, ...} : let unlines = lib.concatStringsSep "\n"; @@ -288,108 +288,118 @@ in client1 = mkClient true; # use nss_pam_ldapd client2 = mkClient false; # use nss_ldap and pam_ldap - }; testScript = '' - $server->start; - $server->waitForUnit("default.target"); - - subtest "slapd", sub { - subtest "auth as database admin with SASL and check a POSIX account", sub { - $server->succeed(join ' ', 'test', - '"$(ldapsearch -LLL -H ldapi:// -Y EXTERNAL', - '-b \'uid=${ldapUser},ou=accounts,ou=posix,${dbSuffix}\' ', - '-s base uidNumber |', - 'sed -ne \'s/^uidNumber: \\(.*\\)/\\1/p\' ', - ')" -eq ${toString ldapUserId}'); - }; - subtest "auth as database admin with password and check a POSIX account", sub { - $server->succeed(join ' ', 'test', - '"$(ldapsearch -LLL -H ldap://server', - '-D \'cn=admin,${dbSuffix}\' -w \'${dbAdminPwd}\' ', - '-b \'uid=${ldapUser},ou=accounts,ou=posix,${dbSuffix}\' ', - '-s base uidNumber |', - 'sed -ne \'s/^uidNumber: \\(.*\\)/\\1/p\' ', - ')" -eq ${toString ldapUserId}'); - }; - }; - - $client1->start; - $client1->waitForUnit("default.target"); - - subtest "password", sub { - subtest "su with password to a POSIX account", sub { - $client1->succeed("${pkgs.expect}/bin/expect -c '" . join ';', - 'spawn su "${ldapUser}"', - 'expect "Password:"', - 'send "${ldapUserPwd}\n"', - 'expect "*"', - 'send "whoami\n"', - 'expect -ex "${ldapUser}" {exit}', - 'exit 1' . "'"); - }; - subtest "change password of a POSIX account as root", sub { - $client1->succeed("chpasswd <<<'${ldapUser}:new-password'"); - $client1->succeed("${pkgs.expect}/bin/expect -c '" . join ';', - 'spawn su "${ldapUser}"', - 'expect "Password:"', - 'send "new-password\n"', - 'expect "*"', - 'send "whoami\n"', - 'expect -ex "${ldapUser}" {exit}', - 'exit 1' . "'"); - $client1->succeed('chpasswd <<<\'${ldapUser}:${ldapUserPwd}\' '); - }; - subtest "change password of a POSIX account from itself", sub { - $client1->succeed('chpasswd <<<\'${ldapUser}:${ldapUserPwd}\' '); - $client1->succeed("${pkgs.expect}/bin/expect -c '" . join ';', - 'spawn su --login ${ldapUser} -c passwd', - 'expect "Password: "', - 'send "${ldapUserPwd}\n"', - 'expect "(current) UNIX password: "', - 'send "${ldapUserPwd}\n"', - 'expect "New password: "', - 'send "new-password\n"', - 'expect "Retype new password: "', - 'send "new-password\n"', - 'expect "passwd: password updated successfully" {exit}', - 'exit 1' . "'"); - $client1->succeed("${pkgs.expect}/bin/expect -c '" . join ';', - 'spawn su "${ldapUser}"', - 'expect "Password:"', - 'send "${ldapUserPwd}\n"', - 'expect "su: Authentication failure" {exit}', - 'exit 1' . "'"); - $client1->succeed("${pkgs.expect}/bin/expect -c '" . join ';', - 'spawn su "${ldapUser}"', - 'expect "Password:"', - 'send "new-password\n"', - 'expect "*"', - 'send "whoami\n"', - 'expect -ex "${ldapUser}" {exit}', - 'exit 1' . "'"); - $client1->succeed('chpasswd <<<\'${ldapUser}:${ldapUserPwd}\' '); - }; - }; - - $client2->start; - $client2->waitForUnit("default.target"); - - subtest "NSS", sub { - $client1->succeed("test \"\$(id -u '${ldapUser}')\" -eq ${toString ldapUserId}"); - $client1->succeed("test \"\$(id -u -n '${ldapUser}')\" = '${ldapUser}'"); - $client1->succeed("test \"\$(id -g '${ldapUser}')\" -eq ${toString ldapGroupId}"); - $client1->succeed("test \"\$(id -g -n '${ldapUser}')\" = '${ldapGroup}'"); - $client2->succeed("test \"\$(id -u '${ldapUser}')\" -eq ${toString ldapUserId}"); - $client2->succeed("test \"\$(id -u -n '${ldapUser}')\" = '${ldapUser}'"); - $client2->succeed("test \"\$(id -g '${ldapUser}')\" -eq ${toString ldapGroupId}"); - $client2->succeed("test \"\$(id -g -n '${ldapUser}')\" = '${ldapGroup}'"); - }; - - subtest "PAM", sub { - $client1->succeed("echo ${ldapUserPwd} | su -l '${ldapUser}' -c true"); - $client2->succeed("echo ${ldapUserPwd} | su -l '${ldapUser}' -c true"); - }; + def expect_script(*commands): + script = ";".join(commands) + return f"${pkgs.expect}/bin/expect -c '{script}'" + + + server.start() + server.wait_for_unit("default.target") + + with subtest("slapd: auth as database admin with SASL and check a POSIX account"): + server.succeed( + 'test "$(ldapsearch -LLL -H ldapi:// -Y EXTERNAL ' + + "-b 'uid=${ldapUser},ou=accounts,ou=posix,${dbSuffix}' " + + "-s base uidNumber | " + + "sed -ne 's/^uidNumber: \\(.*\\)/\\1/p')\" -eq ${toString ldapUserId}" + ) + + with subtest("slapd: auth as database admin with password and check a POSIX account"): + server.succeed( + "test \"$(ldapsearch -LLL -H ldap://server -D 'cn=admin,${dbSuffix}' " + + "-w '${dbAdminPwd}' -b 'uid=${ldapUser},ou=accounts,ou=posix,${dbSuffix}' " + + "-s base uidNumber | " + + "sed -ne 's/^uidNumber: \\(.*\\)/\\1/p')\" -eq ${toString ldapUserId}" + ) + + client1.start() + client1.wait_for_unit("default.target") + + with subtest("password: su with password to a POSIX account"): + client1.succeed( + expect_script( + 'spawn su "${ldapUser}"', + 'expect "Password:"', + 'send "${ldapUserPwd}\n"', + 'expect "*"', + 'send "whoami\n"', + 'expect -ex "${ldapUser}" {exit}', + "exit 1", + ) + ) + + with subtest("password: change password of a POSIX account as root"): + client1.succeed( + "chpasswd <<<'${ldapUser}:new-password'", + expect_script( + 'spawn su "${ldapUser}"', + 'expect "Password:"', + 'send "new-password\n"', + 'expect "*"', + 'send "whoami\n"', + 'expect -ex "${ldapUser}" {exit}', + "exit 1", + ), + "chpasswd <<<'${ldapUser}:${ldapUserPwd}'", + ) + + with subtest("password: change password of a POSIX account from itself"): + client1.succeed( + "chpasswd <<<'${ldapUser}:${ldapUserPwd}' ", + expect_script( + "spawn su --login ${ldapUser} -c passwd", + 'expect "Password: "', + 'send "${ldapUserPwd}\n"', + 'expect "(current) UNIX password: "', + 'send "${ldapUserPwd}\n"', + 'expect "New password: "', + 'send "new-password\n"', + 'expect "Retype new password: "', + 'send "new-password\n"', + 'expect "passwd: password updated successfully" {exit}', + "exit 1", + ), + expect_script( + 'spawn su "${ldapUser}"', + 'expect "Password:"', + 'send "${ldapUserPwd}\n"', + 'expect "su: Authentication failure" {exit}', + "exit 1", + ), + expect_script( + 'spawn su "${ldapUser}"', + 'expect "Password:"', + 'send "new-password\n"', + 'expect "*"', + 'send "whoami\n"', + 'expect -ex "${ldapUser}" {exit}', + "exit 1", + ), + "chpasswd <<<'${ldapUser}:${ldapUserPwd}'", + ) + + client2.start() + client2.wait_for_unit("default.target") + + with subtest("NSS"): + client1.succeed( + "test \"$(id -u '${ldapUser}')\" -eq ${toString ldapUserId}", + "test \"$(id -u -n '${ldapUser}')\" = '${ldapUser}'", + "test \"$(id -g '${ldapUser}')\" -eq ${toString ldapGroupId}", + "test \"$(id -g -n '${ldapUser}')\" = '${ldapGroup}'", + "test \"$(id -u '${ldapUser}')\" -eq ${toString ldapUserId}", + "test \"$(id -u -n '${ldapUser}')\" = '${ldapUser}'", + "test \"$(id -g '${ldapUser}')\" -eq ${toString ldapGroupId}", + "test \"$(id -g -n '${ldapUser}')\" = '${ldapGroup}'", + ) + + with subtest("PAM"): + client1.succeed( + "echo ${ldapUserPwd} | su -l '${ldapUser}' -c true", + "echo ${ldapUserPwd} | su -l '${ldapUser}' -c true", + ) ''; }) diff --git a/nixos/tests/leaps.nix b/nixos/tests/leaps.nix index 6163fed56b6f5..65b475d734ec8 100644 --- a/nixos/tests/leaps.nix +++ b/nixos/tests/leaps.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, ... }: +import ./make-test-python.nix ({ pkgs, ... }: { name = "leaps"; @@ -22,9 +22,11 @@ import ./make-test.nix ({ pkgs, ... }: testScript = '' - startAll; - $server->waitForOpenPort(6666); - $client->waitForUnit("network.target"); - $client->succeed("${pkgs.curl}/bin/curl http://server:6666/leaps/ | grep -i 'leaps'"); + start_all() + server.wait_for_open_port(6666) + client.wait_for_unit("network.target") + assert "leaps" in client.succeed( + "${pkgs.curl}/bin/curl http://server:6666/leaps/" + ) ''; }) diff --git a/nixos/tests/lidarr.nix b/nixos/tests/lidarr.nix index 85fcbd21d8c09..d3f83e5d9145b 100644 --- a/nixos/tests/lidarr.nix +++ b/nixos/tests/lidarr.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ lib, ... }: +import ./make-test-python.nix ({ lib, ... }: with lib; @@ -11,8 +11,10 @@ with lib; { services.lidarr.enable = true; }; testScript = '' - $machine->waitForUnit('lidarr.service'); - $machine->waitForOpenPort('8686'); - $machine->succeed("curl --fail http://localhost:8686/"); + start_all() + + machine.wait_for_unit("lidarr.service") + machine.wait_for_open_port("8686") + machine.succeed("curl --fail http://localhost:8686/") ''; }) diff --git a/nixos/tests/lightdm.nix b/nixos/tests/lightdm.nix index ef30f7741e234..46c2ed7ccc594 100644 --- a/nixos/tests/lightdm.nix +++ b/nixos/tests/lightdm.nix @@ -8,9 +8,8 @@ import ./make-test-python.nix ({ pkgs, ...} : { imports = [ ./common/user-account.nix ]; services.xserver.enable = true; services.xserver.displayManager.lightdm.enable = true; - services.xserver.windowManager.default = "icewm"; + services.xserver.displayManager.defaultSession = "none+icewm"; services.xserver.windowManager.icewm.enable = true; - services.xserver.desktopManager.default = "none"; }; enableOCR = true; diff --git a/nixos/tests/limesurvey.nix b/nixos/tests/limesurvey.nix index ad66ada106b71..7228fcb833155 100644 --- a/nixos/tests/limesurvey.nix +++ b/nixos/tests/limesurvey.nix @@ -1,21 +1,26 @@ -import ./make-test.nix ({ pkgs, ... }: { +import ./make-test-python.nix ({ pkgs, ... }: { name = "limesurvey"; meta.maintainers = [ pkgs.stdenv.lib.maintainers.aanderse ]; - machine = - { ... }: - { services.limesurvey.enable = true; - services.limesurvey.virtualHost.hostName = "example.local"; - services.limesurvey.virtualHost.adminAddr = "root@example.local"; - - # limesurvey won't work without a dot in the hostname - networking.hosts."127.0.0.1" = [ "example.local" ]; + machine = { ... }: { + services.limesurvey = { + enable = true; + virtualHost = { + hostName = "example.local"; + adminAddr = "root@example.local"; + }; }; + # limesurvey won't work without a dot in the hostname + networking.hosts."127.0.0.1" = [ "example.local" ]; + }; + testScript = '' - startAll; + start_all() - $machine->waitForUnit('phpfpm-limesurvey.service'); - $machine->succeed('curl http://example.local/') =~ /The following surveys are available/ or die; + machine.wait_for_unit("phpfpm-limesurvey.service") + assert "The following surveys are available" in machine.succeed( + "curl http://example.local/" + ) ''; }) diff --git a/nixos/tests/lorri/default.nix b/nixos/tests/lorri/default.nix index 53074385a652d..198171082d880 100644 --- a/nixos/tests/lorri/default.nix +++ b/nixos/tests/lorri/default.nix @@ -15,12 +15,12 @@ import ../make-test-python.nix { # Start the daemon and wait until it is ready machine.execute("lorri daemon > lorri.stdout 2> lorri.stderr &") - machine.wait_until_succeeds("grep --fixed-strings 'lorri: ready' lorri.stdout") + machine.wait_until_succeeds("grep --fixed-strings 'ready' lorri.stdout") # Ping the daemon - machine.execute("lorri ping_ $(readlink -f shell.nix)") + machine.succeed("lorri internal__ping shell.nix") # Wait for the daemon to finish the build - machine.wait_until_succeeds("grep --fixed-strings 'OutputPaths' lorri.stdout") + machine.wait_until_succeeds("grep --fixed-strings 'Completed' lorri.stdout") ''; } diff --git a/nixos/tests/magic-wormhole-mailbox-server.nix b/nixos/tests/magic-wormhole-mailbox-server.nix new file mode 100644 index 0000000000000..144a07e13492f --- /dev/null +++ b/nixos/tests/magic-wormhole-mailbox-server.nix @@ -0,0 +1,38 @@ +import ./make-test-python.nix ({ pkgs, ... }: { + name = "magic-wormhole-mailbox-server"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ mmahut ]; + }; + + nodes = { + server = { ... }: { + networking.firewall.allowedTCPPorts = [ 4000 ]; + services.magic-wormhole-mailbox-server.enable = true; + }; + + client_alice = { ... }: { + networking.firewall.enable = false; + environment.systemPackages = [ pkgs.magic-wormhole ]; + }; + + client_bob = { ... }: { + environment.systemPackages = [ pkgs.magic-wormhole ]; + }; + }; + + testScript = '' + start_all() + + # Start the wormhole relay server + server.wait_for_unit("magic-wormhole-mailbox-server.service") + server.wait_for_open_port(4000) + + # Create a secret file and send it to Bob + client_alice.succeed("echo mysecret > secretfile") + client_alice.succeed("wormhole --relay-url=ws://server:4000/v1 send -0 secretfile &") + + # Retrieve a secret file from Alice and check its content + client_bob.succeed("wormhole --relay-url=ws://server:4000/v1 receive -0 --accept-file") + client_bob.succeed("grep mysecret secretfile") + ''; +}) diff --git a/nixos/tests/mailcatcher.nix b/nixos/tests/mailcatcher.nix index d45b5d4edfc50..2ef38544fe0a3 100644 --- a/nixos/tests/mailcatcher.nix +++ b/nixos/tests/mailcatcher.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ lib, ... }: +import ./make-test-python.nix ({ lib, ... }: { name = "mailcatcher"; @@ -9,18 +9,22 @@ import ./make-test.nix ({ lib, ... }: { services.mailcatcher.enable = true; - networking.defaultMailServer.directDelivery = true; - networking.defaultMailServer.hostName = "localhost:1025"; + services.ssmtp.enable = true; + services.ssmtp.hostName = "localhost:1025"; environment.systemPackages = [ pkgs.mailutils ]; }; testScript = '' - startAll; + start_all() - $machine->waitForUnit('mailcatcher.service'); - $machine->waitForOpenPort('1025'); - $machine->succeed('echo "this is the body of the email" | mail -s "subject" root@example.org'); - $machine->succeed('curl http://localhost:1080/messages/1.source') =~ /this is the body of the email/ or die; + machine.wait_for_unit("mailcatcher.service") + machine.wait_for_open_port("1025") + machine.succeed( + 'echo "this is the body of the email" | mail -s "subject" root@example.org' + ) + assert "this is the body of the email" in machine.succeed( + "curl http://localhost:1080/messages/1.source" + ) ''; }) diff --git a/nixos/tests/matomo.nix b/nixos/tests/matomo.nix index 4efa65a7b6dea..2bea237c8bdde 100644 --- a/nixos/tests/matomo.nix +++ b/nixos/tests/matomo.nix @@ -1,7 +1,7 @@ { system ? builtins.currentSystem, config ? { } , pkgs ? import ../.. { inherit system config; } }: -with import ../lib/testing.nix { inherit system pkgs; }; +with import ../lib/testing-python.nix { inherit system pkgs; }; with pkgs.lib; let @@ -24,11 +24,16 @@ let }; testScript = '' - startAll; - $machine->waitForUnit("mysql.service"); - $machine->waitForUnit("phpfpm-matomo.service"); - $machine->waitForUnit("nginx.service"); - $machine->succeed("curl -sSfL http://localhost/ | grep '<title>Matomo[^<]*Installation'"); + start_all() + machine.wait_for_unit("mysql.service") + machine.wait_for_unit("phpfpm-matomo.service") + machine.wait_for_unit("nginx.service") + + # without the grep the command does not produce valid utf-8 for some reason + with subtest("welcome screen loads"): + machine.succeed( + "curl -sSfL http://localhost/ | grep '<title>Matomo[^<]*Installation'" + ) ''; }; in { diff --git a/nixos/tests/matrix-synapse.nix b/nixos/tests/matrix-synapse.nix index fca53009083a4..f3623aa3c094d 100644 --- a/nixos/tests/matrix-synapse.nix +++ b/nixos/tests/matrix-synapse.nix @@ -35,12 +35,31 @@ in { nodes = { # Since 0.33.0, matrix-synapse doesn't allow underscores in server names - serverpostgres = args: { + serverpostgres = { pkgs, ... }: { services.matrix-synapse = { enable = true; database_type = "psycopg2"; tls_certificate_path = "${cert}"; tls_private_key_path = "${key}"; + database_args = { + password = "synapse"; + }; + }; + services.postgresql = { + enable = true; + + # The database name and user are configured by the following options: + # - services.matrix-synapse.database_name + # - services.matrix-synapse.database_user + # + # The values used here represent the default values of the module. + initialScript = pkgs.writeText "synapse-init.sql" '' + CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse'; + CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse" + TEMPLATE template0 + LC_COLLATE = "C" + LC_CTYPE = "C"; + ''; }; }; diff --git a/nixos/tests/mediawiki.nix b/nixos/tests/mediawiki.nix index 6293e8a2f465a..9468c1de8ccb4 100644 --- a/nixos/tests/mediawiki.nix +++ b/nixos/tests/mediawiki.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, lib, ... }: { +import ./make-test-python.nix ({ pkgs, lib, ... }: { name = "mediawiki"; meta.maintainers = [ lib.maintainers.aanderse ]; @@ -11,9 +11,11 @@ import ./make-test.nix ({ pkgs, lib, ... }: { }; testScript = '' - startAll; + start_all() - $machine->waitForUnit('phpfpm-mediawiki.service'); - $machine->succeed('curl -L http://localhost/') =~ /MediaWiki has been installed/ or die; + machine.wait_for_unit("phpfpm-mediawiki.service") + + page = machine.succeed("curl -L http://localhost/") + assert "MediaWiki has been installed" in page ''; }) diff --git a/nixos/tests/misc.nix b/nixos/tests/misc.nix index ca28bc31cf1c2..17260ce640676 100644 --- a/nixos/tests/misc.nix +++ b/nixos/tests/misc.nix @@ -1,6 +1,6 @@ # Miscellaneous small tests that don't warrant their own VM run. -import ./make-test.nix ({ pkgs, ...} : rec { +import ./make-test-python.nix ({ pkgs, ...} : rec { name = "misc"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ eelco ]; @@ -34,109 +34,97 @@ import ./make-test.nix ({ pkgs, ...} : rec { testScript = '' - subtest "nix-db", sub { - my $json = $machine->succeed("nix path-info --json ${foo}"); - $json =~ /"narHash":"sha256:0afw0d9j1hvwiz066z93jiddc33nxg6i6qyp26vnqyglpyfivlq5"/ or die "narHash not set"; - $json =~ /"narSize":128/ or die "narSize not set"; - }; + import json - subtest "nixos-version", sub { - $machine->succeed("[ `nixos-version | wc -w` = 2 ]"); - }; - subtest "nixos-rebuild", sub { - $machine->succeed("nixos-rebuild --help | grep 'NixOS module' "); - }; + def get_path_info(path): + result = machine.succeed(f"nix path-info --json {path}") + parsed = json.loads(result) + return parsed - # Sanity check for uid/gid assignment. - subtest "users-groups", sub { - $machine->succeed("[ `id -u messagebus` = 4 ]"); - $machine->succeed("[ `id -g messagebus` = 4 ]"); - $machine->succeed("[ `getent group users` = 'users:x:100:' ]"); - }; - # Regression test for GMP aborts on QEMU. - subtest "gmp", sub { - $machine->succeed("expr 1 + 2"); - }; + with subtest("nix-db"): + info = get_path_info("${foo}") - # Test that the swap file got created. - subtest "swapfile", sub { - $machine->waitForUnit("root-swapfile.swap"); - $machine->succeed("ls -l /root/swapfile | grep 134217728"); - }; + if ( + info[0]["narHash"] + != "sha256:0afw0d9j1hvwiz066z93jiddc33nxg6i6qyp26vnqyglpyfivlq5" + ): + raise Exception("narHash not set") - # Test whether kernel.poweroff_cmd is set. - subtest "poweroff_cmd", sub { - $machine->succeed("[ -x \"\$(cat /proc/sys/kernel/poweroff_cmd)\" ]") - }; + if info[0]["narSize"] != 128: + raise Exception("narSize not set") - # Test whether the blkio controller is properly enabled. - subtest "blkio-cgroup", sub { - $machine->succeed("[ -n \"\$(cat /sys/fs/cgroup/blkio/blkio.sectors)\" ]") - }; + with subtest("nixos-version"): + machine.succeed("[ `nixos-version | wc -w` = 2 ]") - # Test whether we have a reboot record in wtmp. - subtest "reboot-wtmp", sub { - $machine->shutdown; - $machine->waitForUnit('multi-user.target'); - $machine->succeed("last | grep reboot >&2"); - }; + with subtest("nixos-rebuild"): + assert "NixOS module" in machine.succeed("nixos-rebuild --help") - # Test whether we can override environment variables. - subtest "override-env-var", sub { - $machine->succeed('[ "$EDITOR" = emacs ]'); - }; + with subtest("Sanity check for uid/gid assignment"): + assert "4" == machine.succeed("id -u messagebus").strip() + assert "4" == machine.succeed("id -g messagebus").strip() + assert "users:x:100:" == machine.succeed("getent group users").strip() - # Test whether hostname (and by extension nss_myhostname) works. - subtest "hostname", sub { - $machine->succeed('[ "`hostname`" = machine ]'); - #$machine->succeed('[ "`hostname -s`" = machine ]'); - }; + with subtest("Regression test for GMP aborts on QEMU."): + machine.succeed("expr 1 + 2") - # Test whether systemd-udevd automatically loads modules for our hardware. - $machine->succeed("systemctl start systemd-udev-settle.service"); - subtest "udev-auto-load", sub { - $machine->waitForUnit('systemd-udev-settle.service'); - $machine->succeed('lsmod | grep mousedev'); - }; + with subtest("the swap file got created"): + machine.wait_for_unit("root-swapfile.swap") + machine.succeed("ls -l /root/swapfile | grep 134217728") - # Test whether systemd-tmpfiles-clean works. - subtest "tmpfiles", sub { - $machine->succeed('touch /tmp/foo'); - $machine->succeed('systemctl start systemd-tmpfiles-clean'); - $machine->succeed('[ -e /tmp/foo ]'); - $machine->succeed('date -s "@$(($(date +%s) + 1000000))"'); # move into the future - $machine->succeed('systemctl start systemd-tmpfiles-clean'); - $machine->fail('[ -e /tmp/foo ]'); - }; + with subtest("whether kernel.poweroff_cmd is set"): + machine.succeed('[ -x "$(cat /proc/sys/kernel/poweroff_cmd)" ]') - # Test whether automounting works. - subtest "automount", sub { - $machine->fail("grep '/tmp2 tmpfs' /proc/mounts"); - $machine->succeed("touch /tmp2/x"); - $machine->succeed("grep '/tmp2 tmpfs' /proc/mounts"); - }; + with subtest("whether the blkio controller is properly enabled"): + machine.succeed("[ -e /sys/fs/cgroup/blkio/blkio.reset_stats ]") - subtest "shell-vars", sub { - $machine->succeed('[ -n "$NIX_PATH" ]'); - }; + with subtest("whether we have a reboot record in wtmp"): + machine.shutdown + machine.wait_for_unit("multi-user.target") + machine.succeed("last | grep reboot >&2") - subtest "nix-db", sub { - $machine->succeed("nix-store -qR /run/current-system | grep nixos-"); - }; + with subtest("whether we can override environment variables"): + machine.succeed('[ "$EDITOR" = emacs ]') - # Test sysctl - subtest "sysctl", sub { - $machine->waitForUnit("systemd-sysctl.service"); - $machine->succeed('[ `sysctl -ne vm.swappiness` = 1 ]'); - $machine->execute('sysctl vm.swappiness=60'); - $machine->succeed('[ `sysctl -ne vm.swappiness` = 60 ]'); - }; + with subtest("whether hostname (and by extension nss_myhostname) works"): + assert "machine" == machine.succeed("hostname").strip() + assert "machine" == machine.succeed("hostname -s").strip() - # Test boot parameters - subtest "bootparam", sub { - $machine->succeed('grep -Fq vsyscall=emulate /proc/cmdline'); - }; + with subtest("whether systemd-udevd automatically loads modules for our hardware"): + machine.succeed("systemctl start systemd-udev-settle.service") + machine.wait_for_unit("systemd-udev-settle.service") + assert "mousedev" in machine.succeed("lsmod") + + with subtest("whether systemd-tmpfiles-clean works"): + machine.succeed( + "touch /tmp/foo", "systemctl start systemd-tmpfiles-clean", "[ -e /tmp/foo ]" + ) + # move into the future + machine.succeed( + 'date -s "@$(($(date +%s) + 1000000))"', + "systemctl start systemd-tmpfiles-clean", + ) + machine.fail("[ -e /tmp/foo ]") + + with subtest("whether automounting works"): + machine.fail("grep '/tmp2 tmpfs' /proc/mounts") + machine.succeed("touch /tmp2/x") + machine.succeed("grep '/tmp2 tmpfs' /proc/mounts") + + with subtest("shell-vars"): + machine.succeed('[ -n "$NIX_PATH" ]') + + with subtest("nix-db"): + machine.succeed("nix-store -qR /run/current-system | grep nixos-") + + with subtest("Test sysctl"): + machine.wait_for_unit("systemd-sysctl.service") + assert "1" == machine.succeed("sysctl -ne vm.swappiness").strip() + machine.execute("sysctl vm.swappiness=60") + assert "60" == machine.succeed("sysctl -ne vm.swappiness").strip() + + with subtest("Test boot parameters"): + assert "vsyscall=emulate" in machine.succeed("cat /proc/cmdline") ''; }) diff --git a/nixos/tests/moinmoin.nix b/nixos/tests/moinmoin.nix index 2662b79aa0995..ac327498eba0f 100644 --- a/nixos/tests/moinmoin.nix +++ b/nixos/tests/moinmoin.nix @@ -1,6 +1,6 @@ -import ./make-test.nix ({ pkgs, lib, ... }: { +import ./make-test-python.nix ({ pkgs, lib, ... }: { name = "moinmoin"; - meta.maintainers = [ ]; # waiting for https://github.com/NixOS/nixpkgs/pull/65397 + meta.maintainers = with lib.maintainers; [ mmilata ]; machine = { ... }: @@ -13,12 +13,16 @@ import ./make-test.nix ({ pkgs, lib, ... }: { }; testScript = '' - startAll; + start_all() - $machine->waitForUnit('moin-ExampleWiki.service'); - $machine->waitForUnit('nginx.service'); - $machine->waitForFile('/run/moin/ExampleWiki/gunicorn.sock'); - $machine->succeed('curl -L http://localhost/') =~ /If you have just installed/ or die; - $machine->succeed('moin-ExampleWiki account create --name=admin --email=admin@example.com --password=foo 2>&1') =~ /status success/ or die; + machine.wait_for_unit("moin-ExampleWiki.service") + machine.wait_for_unit("nginx.service") + machine.wait_for_file("/run/moin/ExampleWiki/gunicorn.sock") + + assert "If you have just installed" in machine.succeed("curl -L http://localhost/") + + assert "status success" in machine.succeed( + "moin-ExampleWiki account create --name=admin --email=admin@example.com --password=foo 2>&1" + ) ''; }) diff --git a/nixos/tests/mongodb.nix b/nixos/tests/mongodb.nix index 9ebf84eed2325..a637ec4bfc00f 100644 --- a/nixos/tests/mongodb.nix +++ b/nixos/tests/mongodb.nix @@ -1,42 +1,52 @@ # This test start mongodb, runs a query using mongo shell -import ./make-test-python.nix ({ pkgs, ...} : let - testQuery = pkgs.writeScript "nixtest.js" '' - db.greetings.insert({ "greeting": "hello" }); - print(db.greetings.findOne().greeting); - ''; -in { - name = "mongodb"; - meta = with pkgs.stdenv.lib.maintainers; { - maintainers = [ bluescreen303 offline cstrahan rvl phile314 ]; - }; +import ./make-test-python.nix ({ pkgs, ... }: + let + testQuery = pkgs.writeScript "nixtest.js" '' + db.greetings.insert({ "greeting": "hello" }); + print(db.greetings.findOne().greeting); + ''; - nodes = { - one = - { ... }: - { - services = { - mongodb.enable = true; - mongodb.enableAuth = true; - mongodb.initialRootPassword = "root"; - mongodb.initialScript = pkgs.writeText "mongodb_initial.js" '' - db = db.getSiblingDB("nixtest"); - db.createUser({user:"nixtest",pwd:"nixtest",roles:[{role:"readWrite",db:"nixtest"}]}); - ''; - mongodb.extraConfig = '' - # Allow starting engine with only a small virtual disk - storage.journal.enabled: false - storage.mmapv1.smallFiles: true - ''; - }; - }; + runMongoDBTest = pkg: '' + node.execute("(rm -rf data || true) && mkdir data") + node.execute( + "${pkg}/bin/mongod --fork --logpath logs --dbpath data" + ) + node.wait_for_open_port(27017) + + assert "hello" in node.succeed( + "mongo ${testQuery}" + ) + + node.execute( + "${pkg}/bin/mongod --shutdown --dbpath data" + ) + node.wait_for_closed_port(27017) + ''; + + in { + name = "mongodb"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ bluescreen303 offline cstrahan rvl phile314 ]; + }; + + nodes = { + node = {...}: { + environment.systemPackages = with pkgs; [ + mongodb-3_4 + mongodb-3_6 + mongodb-4_0 + ]; + }; }; - testScript = '' - start_all() - one.wait_for_unit("mongodb.service") - one.succeed( - "mongo -u nixtest -p nixtest nixtest ${testQuery} | grep -q hello" - ) - ''; -}) + testScript = '' + node.start() + '' + + runMongoDBTest pkgs.mongodb-3_4 + + runMongoDBTest pkgs.mongodb-3_6 + + runMongoDBTest pkgs.mongodb-4_0 + + '' + node.shutdown() + ''; + }) diff --git a/nixos/tests/mpd.nix b/nixos/tests/mpd.nix index 895b7e2014c85..60aef586ad5c9 100644 --- a/nixos/tests/mpd.nix +++ b/nixos/tests/mpd.nix @@ -5,7 +5,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: # License: http://creativecommons.org/licenses/by-sa/4.0/ name = "Blue_Wave_Theory-Skyhawk_Beach.mp3"; - url = https://freemusicarchive.org/file/music/ccCommunity/Blue_Wave_Theory/Surf_Music_Month_Challenge/Blue_Wave_Theory_-_04_-_Skyhawk_Beach.mp3; + url = "https://freemusicarchive.org/file/music/ccCommunity/Blue_Wave_Theory/Surf_Music_Month_Challenge/Blue_Wave_Theory_-_04_-_Skyhawk_Beach.mp3"; sha256 = "0xw417bxkx4gqqy139bb21yldi37xx8xjfxrwaqa0gyw19dl6mgp"; }; diff --git a/nixos/tests/mumble.nix b/nixos/tests/mumble.nix index 652d49a24b1ca..e9b6d14c6a1fa 100644 --- a/nixos/tests/mumble.nix +++ b/nixos/tests/mumble.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, ...} : +import ./make-test-python.nix ({ pkgs, ...} : let client = { pkgs, ... }: { @@ -24,50 +24,50 @@ in }; testScript = '' - startAll; + start_all() - $server->waitForUnit("murmur.service"); - $client1->waitForX; - $client2->waitForX; + server.wait_for_unit("murmur.service") + client1.wait_for_x() + client2.wait_for_x() - $client1->execute("mumble mumble://client1\@server/test &"); - $client2->execute("mumble mumble://client2\@server/test &"); + client1.execute("mumble mumble://client1\@server/test &") + client2.execute("mumble mumble://client2\@server/test &") # cancel client audio configuration - $client1->waitForWindow(qr/Audio Tuning Wizard/); - $client2->waitForWindow(qr/Audio Tuning Wizard/); - $server->sleep(5); # wait because mumble is slow to register event handlers - $client1->sendKeys("esc"); - $client2->sendKeys("esc"); + client1.wait_for_window(r"Audio Tuning Wizard") + client2.wait_for_window(r"Audio Tuning Wizard") + server.sleep(5) # wait because mumble is slow to register event handlers + client1.send_key("esc") + client2.send_key("esc") # cancel client cert configuration - $client1->waitForWindow(qr/Certificate Management/); - $client2->waitForWindow(qr/Certificate Management/); - $server->sleep(5); # wait because mumble is slow to register event handlers - $client1->sendKeys("esc"); - $client2->sendKeys("esc"); + client1.wait_for_window(r"Certificate Management") + client2.wait_for_window(r"Certificate Management") + server.sleep(5) # wait because mumble is slow to register event handlers + client1.send_key("esc") + client2.send_key("esc") # accept server certificate - $client1->waitForWindow(qr/^Mumble$/); - $client2->waitForWindow(qr/^Mumble$/); - $server->sleep(5); # wait because mumble is slow to register event handlers - $client1->sendChars("y"); - $client2->sendChars("y"); - $server->sleep(5); # wait because mumble is slow to register event handlers + client1.wait_for_window(r"^Mumble$") + client2.wait_for_window(r"^Mumble$") + server.sleep(5) # wait because mumble is slow to register event handlers + client1.send_chars("y") + client2.send_chars("y") + server.sleep(5) # wait because mumble is slow to register event handlers # sometimes the wrong of the 2 windows is focused, we switch focus and try pressing "y" again - $client1->sendKeys("alt-tab"); - $client2->sendKeys("alt-tab"); - $server->sleep(5); # wait because mumble is slow to register event handlers - $client1->sendChars("y"); - $client2->sendChars("y"); + client1.send_key("alt-tab") + client2.send_key("alt-tab") + server.sleep(5) # wait because mumble is slow to register event handlers + client1.send_chars("y") + client2.send_chars("y") # Find clients in logs - $server->waitUntilSucceeds("journalctl -eu murmur -o cat | grep -q client1"); - $server->waitUntilSucceeds("journalctl -eu murmur -o cat | grep -q client2"); + server.wait_until_succeeds("journalctl -eu murmur -o cat | grep -q client1") + server.wait_until_succeeds("journalctl -eu murmur -o cat | grep -q client2") - $server->sleep(5); # wait to get screenshot - $client1->screenshot("screen1"); - $client2->screenshot("screen2"); + server.sleep(5) # wait to get screenshot + client1.screenshot("screen1") + client2.screenshot("screen2") ''; }) diff --git a/nixos/tests/munin.nix b/nixos/tests/munin.nix index 31374aaf77e74..7b674db7768d9 100644 --- a/nixos/tests/munin.nix +++ b/nixos/tests/munin.nix @@ -1,7 +1,7 @@ # This test runs basic munin setup with node and cron job running on the same # machine. -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, ...} : { name = "munin"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ domenkozar eelco ]; @@ -12,33 +12,33 @@ import ./make-test.nix ({ pkgs, ...} : { { config, ... }: { services = { - munin-node = { + munin-node = { + enable = true; + # disable a failing plugin to prevent irrelevant error message, see #23049 + disabledPlugins = [ "apc_nis" ]; + }; + munin-cron = { enable = true; - # disable a failing plugin to prevent irrelevant error message, see #23049 - disabledPlugins = [ "apc_nis" ]; - }; - munin-cron = { - enable = true; - hosts = '' - [${config.networking.hostName}] - address localhost - ''; - }; + hosts = '' + [${config.networking.hostName}] + address localhost + ''; + }; }; - # long timeout to prevent hydra failure on high load - systemd.services.munin-node.serviceConfig.TimeoutStartSec = "10min"; + + # increase the systemd timer interval so it fires more often + systemd.timers.munin-cron.timerConfig.OnCalendar = pkgs.stdenv.lib.mkForce "*:*:0/10"; }; }; testScript = '' - startAll; + start_all() - $one->waitForUnit("munin-node.service"); - # make sure the node is actually listening - $one->waitForOpenPort(4949); - $one->succeed('systemctl start munin-cron'); - # wait for munin-cron output - $one->waitForFile("/var/lib/munin/one/one-uptime-uptime-g.rrd"); - $one->waitForFile("/var/www/munin/one/index.html"); + with subtest("ensure munin-node starts and listens on 4949"): + one.wait_for_unit("munin-node.service") + one.wait_for_open_port(4949) + with subtest("ensure munin-cron output is correct"): + one.wait_for_file("/var/lib/munin/one/one-uptime-uptime-g.rrd") + one.wait_for_file("/var/www/munin/one/index.html") ''; }) diff --git a/nixos/tests/mutable-users.nix b/nixos/tests/mutable-users.nix index e590703ab2f48..49c7f78b82edf 100644 --- a/nixos/tests/mutable-users.nix +++ b/nixos/tests/mutable-users.nix @@ -1,6 +1,6 @@ # Mutable users tests. -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, ...} : { name = "mutable-users"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ gleber ]; @@ -19,21 +19,27 @@ import ./make-test.nix ({ pkgs, ...} : { immutableSystem = nodes.machine.config.system.build.toplevel; mutableSystem = nodes.mutable.config.system.build.toplevel; in '' - $machine->start(); - $machine->waitForUnit("default.target"); + machine.start() + machine.wait_for_unit("default.target") # Machine starts in immutable mode. Add a user and test if reactivating # configuration removes the user. - $machine->fail("cat /etc/passwd | grep ^foobar:"); - $machine->succeed("sudo useradd foobar"); - $machine->succeed("cat /etc/passwd | grep ^foobar:"); - $machine->succeed("${immutableSystem}/bin/switch-to-configuration test"); - $machine->fail("cat /etc/passwd | grep ^foobar:"); + with subtest("Machine in immutable mode"): + assert "foobar" not in machine.succeed("cat /etc/passwd") + machine.succeed("sudo useradd foobar") + assert "foobar" in machine.succeed("cat /etc/passwd") + machine.succeed( + "${immutableSystem}/bin/switch-to-configuration test" + ) + assert "foobar" not in machine.succeed("cat /etc/passwd") # In immutable mode passwd is not wrapped, while in mutable mode it is # wrapped. - $machine->succeed('which passwd | grep /run/current-system/'); - $machine->succeed("${mutableSystem}/bin/switch-to-configuration test"); - $machine->succeed('which passwd | grep /run/wrappers/'); + with subtest("Password is wrapped in mutable mode"): + assert "/run/current-system/" in machine.succeed("which passwd") + machine.succeed( + "${mutableSystem}/bin/switch-to-configuration test" + ) + assert "/run/wrappers/" in machine.succeed("which passwd") ''; }) diff --git a/nixos/tests/mxisd.nix b/nixos/tests/mxisd.nix index 0039256f58616..b2b60db4d8227 100644 --- a/nixos/tests/mxisd.nix +++ b/nixos/tests/mxisd.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, ... } : { +import ./make-test-python.nix ({ pkgs, ... } : { name = "mxisd"; meta = with pkgs.stdenv.lib.maintainers; { @@ -19,13 +19,12 @@ import ./make-test.nix ({ pkgs, ... } : { }; testScript = '' - startAll; - $server_mxisd->waitForUnit("mxisd.service"); - $server_mxisd->waitForOpenPort(8090); - $server_mxisd->succeed("curl -Ssf \"http://127.0.0.1:8090/_matrix/identity/api/v1\""); - $server_ma1sd->waitForUnit("mxisd.service"); - $server_ma1sd->waitForOpenPort(8090); - $server_ma1sd->succeed("curl -Ssf \"http://127.0.0.1:8090/_matrix/identity/api/v1\"") - + start_all() + server_mxisd.wait_for_unit("mxisd.service") + server_mxisd.wait_for_open_port(8090) + server_mxisd.succeed("curl -Ssf 'http://127.0.0.1:8090/_matrix/identity/api/v1'") + server_ma1sd.wait_for_unit("mxisd.service") + server_ma1sd.wait_for_open_port(8090) + server_ma1sd.succeed("curl -Ssf 'http://127.0.0.1:8090/_matrix/identity/api/v1'") ''; }) diff --git a/nixos/tests/mysql.nix b/nixos/tests/mysql.nix index 2c0d212c2f1da..11c1dabf9360e 100644 --- a/nixos/tests/mysql.nix +++ b/nixos/tests/mysql.nix @@ -22,11 +22,33 @@ import ./make-test-python.nix ({ pkgs, ...} : { services.mysql.package = pkgs.mysql57; }; + mysql80 = + { pkgs, ... }: + + { + # prevent oom: + # Kernel panic - not syncing: Out of memory: compulsory panic_on_oom is enabled + virtualisation.memorySize = 1024; + + services.mysql.enable = true; + services.mysql.initialDatabases = [ + { name = "testdb"; schema = ./testdb.sql; } + { name = "empty_testdb"; } + ]; + # note that using pkgs.writeText here is generally not a good idea, + # as it will store the password in world-readable /nix/store ;) + services.mysql.initialScript = pkgs.writeText "mysql-init.sql" '' + CREATE USER 'passworduser'@'localhost' IDENTIFIED BY 'password123'; + ''; + services.mysql.package = pkgs.mysql80; + }; + mariadb = { pkgs, ... }: { users.users.testuser = { }; + users.users.testuser2 = { }; services.mysql.enable = true; services.mysql.initialScript = pkgs.writeText "mariadb-init.sql" '' ALTER USER root@localhost IDENTIFIED WITH unix_socket; @@ -34,20 +56,30 @@ import ./make-test-python.nix ({ pkgs, ...} : { DELETE FROM mysql.user WHERE user = '''; FLUSH PRIVILEGES; ''; - services.mysql.ensureDatabases = [ "testdb" ]; + services.mysql.ensureDatabases = [ "testdb" "testdb2" ]; services.mysql.ensureUsers = [{ name = "testuser"; ensurePermissions = { "testdb.*" = "ALL PRIVILEGES"; }; + } { + name = "testuser2"; + ensurePermissions = { + "testdb2.*" = "ALL PRIVILEGES"; + }; }]; + services.mysql.settings = { + mysqld = { + plugin-load-add = [ "ha_tokudb.so" "ha_rocksdb.so" ]; + }; + }; services.mysql.package = pkgs.mariadb; }; }; testScript = '' - start_all + start_all() mysql.wait_for_unit("mysql") mysql.succeed("echo 'use empty_testdb;' | mysql -u root") @@ -55,6 +87,12 @@ import ./make-test-python.nix ({ pkgs, ...} : { # ';' acts as no-op, just check whether login succeeds with the user created from the initialScript mysql.succeed("echo ';' | mysql -u passworduser --password=password123") + mysql80.wait_for_unit("mysql") + mysql80.succeed("echo 'use empty_testdb;' | mysql -u root") + mysql80.succeed("echo 'use testdb; select * from tests;' | mysql -u root -N | grep 4") + # ';' acts as no-op, just check whether login succeeds with the user created from the initialScript + mysql80.succeed("echo ';' | mysql -u passworduser --password=password123") + mariadb.wait_for_unit("mysql") mariadb.succeed( "echo 'use testdb; create table tests (test_id INT, PRIMARY KEY (test_id));' | sudo -u testuser mysql -u testuser" @@ -62,8 +100,44 @@ import ./make-test-python.nix ({ pkgs, ...} : { mariadb.succeed( "echo 'use testdb; insert into tests values (42);' | sudo -u testuser mysql -u testuser" ) + # Ensure testuser2 is not able to insert into testdb as mysql testuser2 + mariadb.fail( + "echo 'use testdb; insert into tests values (23);' | sudo -u testuser2 mysql -u testuser2" + ) + # Ensure testuser2 is not able to authenticate as mysql testuser + mariadb.fail( + "echo 'use testdb; insert into tests values (23);' | sudo -u testuser2 mysql -u testuser" + ) mariadb.succeed( "echo 'use testdb; select test_id from tests;' | sudo -u testuser mysql -u testuser -N | grep 42" ) + + # Check if TokuDB plugin works + mariadb.succeed( + "echo 'use testdb; create table tokudb (test_id INT, PRIMARY KEY (test_id)) ENGINE = TokuDB;' | sudo -u testuser mysql -u testuser" + ) + mariadb.succeed( + "echo 'use testdb; insert into tokudb values (25);' | sudo -u testuser mysql -u testuser" + ) + mariadb.succeed( + "echo 'use testdb; select test_id from tokudb;' | sudo -u testuser mysql -u testuser -N | grep 25" + ) + mariadb.succeed( + "echo 'use testdb; drop table tokudb;' | sudo -u testuser mysql -u testuser" + ) + + # Check if RocksDB plugin works + mariadb.succeed( + "echo 'use testdb; create table rocksdb (test_id INT, PRIMARY KEY (test_id)) ENGINE = RocksDB;' | sudo -u testuser mysql -u testuser" + ) + mariadb.succeed( + "echo 'use testdb; insert into rocksdb values (28);' | sudo -u testuser mysql -u testuser" + ) + mariadb.succeed( + "echo 'use testdb; select test_id from rocksdb;' | sudo -u testuser mysql -u testuser -N | grep 28" + ) + mariadb.succeed( + "echo 'use testdb; drop table rocksdb;' | sudo -u testuser mysql -u testuser" + ) ''; }) diff --git a/nixos/tests/nagios.nix b/nixos/tests/nagios.nix new file mode 100644 index 0000000000000..6f5d444728788 --- /dev/null +++ b/nixos/tests/nagios.nix @@ -0,0 +1,116 @@ +import ./make-test-python.nix ( + { pkgs, ... }: { + name = "nagios"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ symphorien ]; + }; + + machine = { lib, ... }: let + writer = pkgs.writeShellScript "write" '' + set -x + echo "$@" >> /tmp/notifications + ''; + in + { + # tested service + services.sshd.enable = true; + # nagios + services.nagios = { + enable = true; + # make state transitions faster + extraConfig.interval_length = "5"; + objectDefs = + (map (x: "${pkgs.nagios}/etc/objects/${x}.cfg") [ "templates" "timeperiods" "commands" ]) ++ [ + ( + pkgs.writeText "objects.cfg" '' + # notifications are written to /tmp/notifications + define command { + command_name notify-host-by-file + command_line ${writer} "$HOSTNAME is $HOSTSTATE$" + } + define command { + command_name notify-service-by-file + command_line ${writer} "$SERVICEDESC$ is $SERVICESTATE$" + } + + # nagios boilerplate + define contact { + contact_name alice + alias alice + host_notifications_enabled 1 + service_notifications_enabled 1 + service_notification_period 24x7 + host_notification_period 24x7 + service_notification_options w,u,c,r,f,s + host_notification_options d,u,r,f,s + service_notification_commands notify-service-by-file + host_notification_commands notify-host-by-file + email foo@example.com + } + define contactgroup { + contactgroup_name admins + alias Admins + members alice + } + define hostgroup{ + hostgroup_name allhosts + alias All hosts + } + + # monitored objects + define host { + use generic-host + host_name localhost + alias localhost + address localhost + hostgroups allhosts + contact_groups admins + # make state transitions faster. + max_check_attempts 2 + check_interval 1 + retry_interval 1 + } + define service { + use generic-service + host_name localhost + service_description ssh + check_command check_ssh + # make state transitions faster. + max_check_attempts 2 + check_interval 1 + retry_interval 1 + } + '' + ) + ]; + }; + }; + + testScript = { ... }: '' + with subtest("ensure sshd starts"): + machine.wait_for_unit("sshd.service") + + + with subtest("ensure nagios starts"): + machine.wait_for_file("/var/log/nagios/current") + + + def assert_notify(text): + machine.wait_for_file("/tmp/notifications") + real = machine.succeed("cat /tmp/notifications").strip() + print(f"got {real!r}, expected {text!r}") + assert text == real + + + with subtest("ensure we get a notification when sshd is down"): + machine.succeed("systemctl stop sshd") + assert_notify("ssh is CRITICAL") + + + with subtest("ensure tests can succeed"): + machine.succeed("systemctl start sshd") + machine.succeed("rm /tmp/notifications") + assert_notify("ssh is OK") + ''; + } +) diff --git a/nixos/tests/nat.nix b/nixos/tests/nat.nix index 51d9cf166bb6c..0d1f7aaedfa23 100644 --- a/nixos/tests/nat.nix +++ b/nixos/tests/nat.nix @@ -3,7 +3,7 @@ # client on the inside network, a server on the outside network, and a # router connected to both that performs Network Address Translation # for the client. -import ./make-test.nix ({ pkgs, lib, withFirewall, withConntrackHelpers ? false, ... }: +import ./make-test-python.nix ({ pkgs, lib, withFirewall, withConntrackHelpers ? false, ... }: let unit = if withFirewall then "firewall" else "nat"; @@ -69,49 +69,52 @@ import ./make-test.nix ({ pkgs, lib, withFirewall, withConntrackHelpers ? false, routerDummyNoNatClosure = nodes.routerDummyNoNat.config.system.build.toplevel; routerClosure = nodes.router.config.system.build.toplevel; in '' - $client->start; - $router->start; - $server->start; + client.start() + router.start() + server.start() # The router should have access to the server. - $server->waitForUnit("network.target"); - $server->waitForUnit("httpd"); - $router->waitForUnit("network.target"); - $router->succeed("curl --fail http://server/ >&2"); + server.wait_for_unit("network.target") + server.wait_for_unit("httpd") + router.wait_for_unit("network.target") + router.succeed("curl --fail http://server/ >&2") # The client should be also able to connect via the NAT router. - $router->waitForUnit("${unit}"); - $client->waitForUnit("network.target"); - $client->succeed("curl --fail http://server/ >&2"); - $client->succeed("ping -c 1 server >&2"); + router.wait_for_unit("${unit}") + client.wait_for_unit("network.target") + client.succeed("curl --fail http://server/ >&2") + client.succeed("ping -c 1 server >&2") # Test whether passive FTP works. - $server->waitForUnit("vsftpd"); - $server->succeed("echo Hello World > /home/ftp/foo.txt"); - $client->succeed("curl -v ftp://server/foo.txt >&2"); + server.wait_for_unit("vsftpd") + server.succeed("echo Hello World > /home/ftp/foo.txt") + client.succeed("curl -v ftp://server/foo.txt >&2") # Test whether active FTP works. - $client->${if withConntrackHelpers then "succeed" else "fail"}( - "curl -v -P - ftp://server/foo.txt >&2"); + client.${if withConntrackHelpers then "succeed" else "fail"}("curl -v -P - ftp://server/foo.txt >&2") # Test ICMP. - $client->succeed("ping -c 1 router >&2"); - $router->succeed("ping -c 1 client >&2"); + client.succeed("ping -c 1 router >&2") + router.succeed("ping -c 1 client >&2") # If we turn off NAT, the client shouldn't be able to reach the server. - $router->succeed("${routerDummyNoNatClosure}/bin/switch-to-configuration test 2>&1"); - $client->fail("curl --fail --connect-timeout 5 http://server/ >&2"); - $client->fail("ping -c 1 server >&2"); + router.succeed( + "${routerDummyNoNatClosure}/bin/switch-to-configuration test 2>&1" + ) + client.fail("curl --fail --connect-timeout 5 http://server/ >&2") + client.fail("ping -c 1 server >&2") # And make sure that reloading the NAT job works. - $router->succeed("${routerClosure}/bin/switch-to-configuration test 2>&1"); + router.succeed( + "${routerClosure}/bin/switch-to-configuration test 2>&1" + ) # FIXME: this should not be necessary, but nat.service is not started because # network.target is not triggered # (https://github.com/NixOS/nixpkgs/issues/16230#issuecomment-226408359) ${lib.optionalString (!withFirewall) '' - $router->succeed("systemctl start nat.service"); + router.succeed("systemctl start nat.service") ''} - $client->succeed("curl --fail http://server/ >&2"); - $client->succeed("ping -c 1 server >&2"); + client.succeed("curl --fail http://server/ >&2") + client.succeed("ping -c 1 server >&2") ''; }) diff --git a/nixos/tests/nesting.nix b/nixos/tests/nesting.nix deleted file mode 100644 index 1306d6f8e0c55..0000000000000 --- a/nixos/tests/nesting.nix +++ /dev/null @@ -1,42 +0,0 @@ -import ./make-test.nix { - name = "nesting"; - nodes = { - clone = { pkgs, ... }: { - environment.systemPackages = [ pkgs.cowsay ]; - nesting.clone = [ - ({ pkgs, ... }: { - environment.systemPackages = [ pkgs.hello ]; - }) - ]; - }; - children = { pkgs, ... }: { - environment.systemPackages = [ pkgs.cowsay ]; - nesting.children = [ - ({ pkgs, ... }: { - environment.systemPackages = [ pkgs.hello ]; - }) - ]; - }; - }; - testScript = '' - $clone->waitForUnit("default.target"); - $clone->succeed("cowsay hey"); - $clone->fail("hello"); - - # Nested clones do inherit from parent - $clone->succeed("/run/current-system/fine-tune/child-1/bin/switch-to-configuration test"); - $clone->succeed("cowsay hey"); - $clone->succeed("hello"); - - - $children->waitForUnit("default.target"); - $children->succeed("cowsay hey"); - $children->fail("hello"); - - # Nested children do not inherit from parent - $children->succeed("/run/current-system/fine-tune/child-1/bin/switch-to-configuration test"); - $children->fail("cowsay hey"); - $children->succeed("hello"); - - ''; -} diff --git a/nixos/tests/netdata.nix b/nixos/tests/netdata.nix index 9bd147968e4b1..4ddc96e8bc224 100644 --- a/nixos/tests/netdata.nix +++ b/nixos/tests/netdata.nix @@ -1,6 +1,6 @@ # This test runs netdata and checks for data via apps.plugin -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, ...} : { name = "netdata"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ cransom ]; @@ -16,23 +16,23 @@ import ./make-test.nix ({ pkgs, ...} : { }; testScript = '' - startAll; + start_all() - $netdata->waitForUnit("netdata.service"); + netdata.wait_for_unit("netdata.service") # wait for the service to listen before sending a request - $netdata->waitForOpenPort(19999); + netdata.wait_for_open_port(19999) # check if the netdata main page loads. - $netdata->succeed("curl --fail http://localhost:19999/"); + netdata.succeed("curl --fail http://localhost:19999/") + netdata.succeed("sleep 4") # check if netdata can read disk ops for root owned processes. # if > 0, successful. verifies both netdata working and # apps.plugin has elevated capabilities. - my $cmd = <<'CMD'; - curl -s http://localhost:19999/api/v1/data\?chart=users.pwrites | \ - jq -e '[.data[range(10)][.labels | indices("root")[0]]] | add | . > 0' - CMD - $netdata->waitUntilSucceeds($cmd); + url = "http://localhost:19999/api/v1/data\?chart=users.pwrites" + filter = '[.data[range(10)][.labels | indices("root")[0]]] | add | . > 0' + cmd = f"curl -s {url} | jq -e '{filter}'" + netdata.wait_until_succeeds(cmd) ''; }) diff --git a/nixos/tests/networking-proxy.nix b/nixos/tests/networking-proxy.nix index ab908c96e5eea..bae9c66ed61a2 100644 --- a/nixos/tests/networking-proxy.nix +++ b/nixos/tests/networking-proxy.nix @@ -10,7 +10,7 @@ let default-config = { virtualisation.memorySize = 128; }; -in import ./make-test.nix ({ pkgs, ...} : { +in import ./make-test-python.nix ({ pkgs, ...} : { name = "networking-proxy"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ ]; @@ -66,46 +66,70 @@ in import ./make-test.nix ({ pkgs, ...} : { testScript = '' - startAll; - - # no proxy at all - print $machine->execute("env | grep -i proxy"); - print $machine->execute("su - alice -c 'env | grep -i proxy'"); - $machine->mustFail("env | grep -i proxy"); - $machine->mustFail("su - alice -c 'env | grep -i proxy'"); - - # Use a default proxy option - print $machine2->execute("env | grep -i proxy"); - print $machine2->execute("su - alice -c 'env | grep -i proxy'"); - $machine2->mustSucceed("env | grep -i proxy"); - $machine2->mustSucceed("su - alice -c 'env | grep -i proxy'"); - - # explicitly set each proxy option - print $machine3->execute("env | grep -i proxy"); - print $machine3->execute("su - alice -c 'env | grep -i proxy'"); - $machine3->mustSucceed("env | grep -i http_proxy | grep 123"); - $machine3->mustSucceed("env | grep -i https_proxy | grep 456"); - $machine3->mustSucceed("env | grep -i rsync_proxy | grep 789"); - $machine3->mustSucceed("env | grep -i ftp_proxy | grep 101112"); - $machine3->mustSucceed("env | grep -i no_proxy | grep 131415"); - $machine3->mustSucceed("su - alice -c 'env | grep -i http_proxy | grep 123'"); - $machine3->mustSucceed("su - alice -c 'env | grep -i https_proxy | grep 456'"); - $machine3->mustSucceed("su - alice -c 'env | grep -i rsync_proxy | grep 789'"); - $machine3->mustSucceed("su - alice -c 'env | grep -i ftp_proxy | grep 101112'"); - $machine3->mustSucceed("su - alice -c 'env | grep -i no_proxy | grep 131415'"); - - # set default proxy option + some other specifics - print $machine4->execute("env | grep -i proxy"); - print $machine4->execute("su - alice -c 'env | grep -i proxy'"); - $machine4->mustSucceed("env | grep -i http_proxy | grep 000"); - $machine4->mustSucceed("env | grep -i https_proxy | grep 000"); - $machine4->mustSucceed("env | grep -i rsync_proxy | grep 123"); - $machine4->mustSucceed("env | grep -i ftp_proxy | grep 000"); - $machine4->mustSucceed("env | grep -i no_proxy | grep 131415"); - $machine4->mustSucceed("su - alice -c 'env | grep -i http_proxy | grep 000'"); - $machine4->mustSucceed("su - alice -c 'env | grep -i https_proxy | grep 000'"); - $machine4->mustSucceed("su - alice -c 'env | grep -i rsync_proxy | grep 123'"); - $machine4->mustSucceed("su - alice -c 'env | grep -i ftp_proxy | grep 000'"); - $machine4->mustSucceed("su - alice -c 'env | grep -i no_proxy | grep 131415'"); + from typing import Dict, Optional + + + def get_machine_env(machine: Machine, user: Optional[str] = None) -> Dict[str, str]: + """ + Gets the environment from a given machine, and returns it as a + dictionary in the form: + {"lowercase_var_name": "value"} + + Duplicate environment variables with the same name + (e.g. "foo" and "FOO") are handled in an undefined manner. + """ + if user is not None: + env = machine.succeed("su - {} -c 'env -0'".format(user)) + else: + env = machine.succeed("env -0") + ret = {} + for line in env.split("\0"): + if "=" not in line: + continue + + key, val = line.split("=", 1) + ret[key.lower()] = val + return ret + + + start_all() + + with subtest("no proxy"): + assert "proxy" not in machine.succeed("env").lower() + assert "proxy" not in machine.succeed("su - alice -c env").lower() + + with subtest("default proxy"): + assert "proxy" in machine2.succeed("env").lower() + assert "proxy" in machine2.succeed("su - alice -c env").lower() + + with subtest("explicitly-set proxy"): + env = get_machine_env(machine3) + assert "123" in env["http_proxy"] + assert "456" in env["https_proxy"] + assert "789" in env["rsync_proxy"] + assert "101112" in env["ftp_proxy"] + assert "131415" in env["no_proxy"] + + env = get_machine_env(machine3, "alice") + assert "123" in env["http_proxy"] + assert "456" in env["https_proxy"] + assert "789" in env["rsync_proxy"] + assert "101112" in env["ftp_proxy"] + assert "131415" in env["no_proxy"] + + with subtest("default proxy + some other specifics"): + env = get_machine_env(machine4) + assert "000" in env["http_proxy"] + assert "000" in env["https_proxy"] + assert "123" in env["rsync_proxy"] + assert "000" in env["ftp_proxy"] + assert "131415" in env["no_proxy"] + + env = get_machine_env(machine4, "alice") + assert "000" in env["http_proxy"] + assert "000" in env["https_proxy"] + assert "123" in env["rsync_proxy"] + assert "000" in env["ftp_proxy"] + assert "131415" in env["no_proxy"] ''; }) diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix index e0585d8f1bb48..3d8ab761a4464 100644 --- a/nixos/tests/networking.nix +++ b/nixos/tests/networking.nix @@ -4,7 +4,7 @@ # bool: whether to use networkd in the tests , networkd }: -with import ../lib/testing.nix { inherit system pkgs; }; +with import ../lib/testing-python.nix { inherit system pkgs; }; with pkgs.lib; let @@ -75,10 +75,11 @@ let machine.networking.useDHCP = false; machine.networking.useNetworkd = networkd; testScript = '' - startAll; - $machine->waitForUnit("network.target"); - $machine->succeed("ip addr show lo | grep -q 'inet 127.0.0.1/8 '"); - $machine->succeed("ip addr show lo | grep -q 'inet6 ::1/128 '"); + start_all() + machine.wait_for_unit("network.target") + loopback_addresses = machine.succeed("ip addr show lo") + assert "inet 127.0.0.1/8" in loopback_addresses + assert "inet6 ::1/128" in loopback_addresses ''; }; static = { @@ -102,35 +103,35 @@ let }; testScript = { ... }: '' - startAll; + start_all() - $client->waitForUnit("network.target"); - $router->waitForUnit("network-online.target"); + client.wait_for_unit("network.target") + router.wait_for_unit("network-online.target") - # Make sure dhcpcd is not started - $client->fail("systemctl status dhcpcd.service"); + with subtest("Make sure dhcpcd is not started"): + client.fail("systemctl status dhcpcd.service") - # Test vlan 1 - $client->waitUntilSucceeds("ping -c 1 192.168.1.1"); - $client->waitUntilSucceeds("ping -c 1 192.168.1.2"); - $client->waitUntilSucceeds("ping -c 1 192.168.1.3"); - $client->waitUntilSucceeds("ping -c 1 192.168.1.10"); + with subtest("Test vlan 1"): + client.wait_until_succeeds("ping -c 1 192.168.1.1") + client.wait_until_succeeds("ping -c 1 192.168.1.2") + client.wait_until_succeeds("ping -c 1 192.168.1.3") + client.wait_until_succeeds("ping -c 1 192.168.1.10") - $router->waitUntilSucceeds("ping -c 1 192.168.1.1"); - $router->waitUntilSucceeds("ping -c 1 192.168.1.2"); - $router->waitUntilSucceeds("ping -c 1 192.168.1.3"); - $router->waitUntilSucceeds("ping -c 1 192.168.1.10"); + router.wait_until_succeeds("ping -c 1 192.168.1.1") + router.wait_until_succeeds("ping -c 1 192.168.1.2") + router.wait_until_succeeds("ping -c 1 192.168.1.3") + router.wait_until_succeeds("ping -c 1 192.168.1.10") - # Test vlan 2 - $client->waitUntilSucceeds("ping -c 1 192.168.2.1"); - $client->waitUntilSucceeds("ping -c 1 192.168.2.2"); + with subtest("Test vlan 2"): + client.wait_until_succeeds("ping -c 1 192.168.2.1") + client.wait_until_succeeds("ping -c 1 192.168.2.2") - $router->waitUntilSucceeds("ping -c 1 192.168.2.1"); - $router->waitUntilSucceeds("ping -c 1 192.168.2.2"); + router.wait_until_succeeds("ping -c 1 192.168.2.1") + router.wait_until_succeeds("ping -c 1 192.168.2.2") - # Test default gateway - $router->waitUntilSucceeds("ping -c 1 192.168.3.1"); - $client->waitUntilSucceeds("ping -c 1 192.168.3.1"); + with subtest("Test default gateway"): + router.wait_until_succeeds("ping -c 1 192.168.3.1") + client.wait_until_succeeds("ping -c 1 192.168.3.1") ''; }; dhcpSimple = { @@ -155,38 +156,38 @@ let }; testScript = { ... }: '' - startAll; - - $client->waitForUnit("network.target"); - $router->waitForUnit("network-online.target"); - - # Wait until we have an ip address on each interface - $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q '192.168.1'"); - $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'"); - $client->waitUntilSucceeds("ip addr show dev eth2 | grep -q '192.168.2'"); - $client->waitUntilSucceeds("ip addr show dev eth2 | grep -q 'fd00:1234:5678:2:'"); - - # Test vlan 1 - $client->waitUntilSucceeds("ping -c 1 192.168.1.1"); - $client->waitUntilSucceeds("ping -c 1 192.168.1.2"); - $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::1"); - $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::2"); - - $router->waitUntilSucceeds("ping -c 1 192.168.1.1"); - $router->waitUntilSucceeds("ping -c 1 192.168.1.2"); - $router->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::1"); - $router->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::2"); - - # Test vlan 2 - $client->waitUntilSucceeds("ping -c 1 192.168.2.1"); - $client->waitUntilSucceeds("ping -c 1 192.168.2.2"); - $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:2::1"); - $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:2::2"); - - $router->waitUntilSucceeds("ping -c 1 192.168.2.1"); - $router->waitUntilSucceeds("ping -c 1 192.168.2.2"); - $router->waitUntilSucceeds("ping -c 1 fd00:1234:5678:2::1"); - $router->waitUntilSucceeds("ping -c 1 fd00:1234:5678:2::2"); + start_all() + + client.wait_for_unit("network.target") + router.wait_for_unit("network-online.target") + + with subtest("Wait until we have an ip address on each interface"): + client.wait_until_succeeds("ip addr show dev eth1 | grep -q '192.168.1'") + client.wait_until_succeeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'") + client.wait_until_succeeds("ip addr show dev eth2 | grep -q '192.168.2'") + client.wait_until_succeeds("ip addr show dev eth2 | grep -q 'fd00:1234:5678:2:'") + + with subtest("Test vlan 1"): + client.wait_until_succeeds("ping -c 1 192.168.1.1") + client.wait_until_succeeds("ping -c 1 192.168.1.2") + client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1") + client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::2") + + router.wait_until_succeeds("ping -c 1 192.168.1.1") + router.wait_until_succeeds("ping -c 1 192.168.1.2") + router.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1") + router.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::2") + + with subtest("Test vlan 2"): + client.wait_until_succeeds("ping -c 1 192.168.2.1") + client.wait_until_succeeds("ping -c 1 192.168.2.2") + client.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::1") + client.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::2") + + router.wait_until_succeeds("ping -c 1 192.168.2.1") + router.wait_until_succeeds("ping -c 1 192.168.2.2") + router.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::1") + router.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::2") ''; }; dhcpOneIf = { @@ -199,6 +200,7 @@ let useDHCP = false; interfaces.eth1 = { ipv4.addresses = mkOverride 0 [ ]; + mtu = 1343; useDHCP = true; }; interfaces.eth2.ipv4.addresses = mkOverride 0 [ ]; @@ -206,28 +208,31 @@ let }; testScript = { ... }: '' - startAll; + start_all() - # Wait for networking to come up - $client->waitForUnit("network.target"); - $router->waitForUnit("network.target"); + with subtest("Wait for networking to come up"): + client.wait_for_unit("network.target") + router.wait_for_unit("network.target") - # Wait until we have an ip address on each interface - $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q '192.168.1'"); + with subtest("Wait until we have an ip address on each interface"): + client.wait_until_succeeds("ip addr show dev eth1 | grep -q '192.168.1'") - # Test vlan 1 - $client->waitUntilSucceeds("ping -c 1 192.168.1.1"); - $client->waitUntilSucceeds("ping -c 1 192.168.1.2"); + with subtest("ensure MTU is set"): + assert "mtu 1343" in client.succeed("ip link show dev eth1") - $router->waitUntilSucceeds("ping -c 1 192.168.1.1"); - $router->waitUntilSucceeds("ping -c 1 192.168.1.2"); + with subtest("Test vlan 1"): + client.wait_until_succeeds("ping -c 1 192.168.1.1") + client.wait_until_succeeds("ping -c 1 192.168.1.2") - # Test vlan 2 - $client->waitUntilSucceeds("ping -c 1 192.168.2.1"); - $client->fail("ping -c 1 192.168.2.2"); + router.wait_until_succeeds("ping -c 1 192.168.1.1") + router.wait_until_succeeds("ping -c 1 192.168.1.2") - $router->waitUntilSucceeds("ping -c 1 192.168.2.1"); - $router->fail("ping -c 1 192.168.2.2"); + with subtest("Test vlan 2"): + client.wait_until_succeeds("ping -c 1 192.168.2.1") + client.fail("ping -c 1 192.168.2.2") + + router.wait_until_succeeds("ping -c 1 192.168.2.1") + router.fail("ping -c 1 192.168.2.2") ''; }; bond = let @@ -252,18 +257,18 @@ let nodes.client2 = node "192.168.1.2"; testScript = { ... }: '' - startAll; + start_all() - # Wait for networking to come up - $client1->waitForUnit("network.target"); - $client2->waitForUnit("network.target"); + with subtest("Wait for networking to come up"): + client1.wait_for_unit("network.target") + client2.wait_for_unit("network.target") - # Test bonding - $client1->waitUntilSucceeds("ping -c 2 192.168.1.1"); - $client1->waitUntilSucceeds("ping -c 2 192.168.1.2"); + with subtest("Test bonding"): + client1.wait_until_succeeds("ping -c 2 192.168.1.1") + client1.wait_until_succeeds("ping -c 2 192.168.1.2") - $client2->waitUntilSucceeds("ping -c 2 192.168.1.1"); - $client2->waitUntilSucceeds("ping -c 2 192.168.1.2"); + client2.wait_until_succeeds("ping -c 2 192.168.1.1") + client2.wait_until_succeeds("ping -c 2 192.168.1.2") ''; }; bridge = let @@ -294,25 +299,24 @@ let }; testScript = { ... }: '' - startAll; + start_all() - # Wait for networking to come up - $client1->waitForUnit("network.target"); - $client2->waitForUnit("network.target"); - $router->waitForUnit("network.target"); + with subtest("Wait for networking to come up"): + for machine in client1, client2, router: + machine.wait_for_unit("network.target") - # Test bridging - $client1->waitUntilSucceeds("ping -c 1 192.168.1.1"); - $client1->waitUntilSucceeds("ping -c 1 192.168.1.2"); - $client1->waitUntilSucceeds("ping -c 1 192.168.1.3"); + with subtest("Test bridging"): + client1.wait_until_succeeds("ping -c 1 192.168.1.1") + client1.wait_until_succeeds("ping -c 1 192.168.1.2") + client1.wait_until_succeeds("ping -c 1 192.168.1.3") - $client2->waitUntilSucceeds("ping -c 1 192.168.1.1"); - $client2->waitUntilSucceeds("ping -c 1 192.168.1.2"); - $client2->waitUntilSucceeds("ping -c 1 192.168.1.3"); + client2.wait_until_succeeds("ping -c 1 192.168.1.1") + client2.wait_until_succeeds("ping -c 1 192.168.1.2") + client2.wait_until_succeeds("ping -c 1 192.168.1.3") - $router->waitUntilSucceeds("ping -c 1 192.168.1.1"); - $router->waitUntilSucceeds("ping -c 1 192.168.1.2"); - $router->waitUntilSucceeds("ping -c 1 192.168.1.3"); + router.wait_until_succeeds("ping -c 1 192.168.1.1") + router.wait_until_succeeds("ping -c 1 192.168.1.2") + router.wait_until_succeeds("ping -c 1 192.168.1.3") ''; }; macvlan = { @@ -340,35 +344,35 @@ let }; testScript = { ... }: '' - startAll; - - # Wait for networking to come up - $client->waitForUnit("network.target"); - $router->waitForUnit("network.target"); - - # Wait until we have an ip address on each interface - $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q '192.168.1'"); - $client->waitUntilSucceeds("ip addr show dev macvlan | grep -q '192.168.1'"); - - # Print lots of diagnostic information - $router->log('**********************************************'); - $router->succeed("ip addr >&2"); - $router->succeed("ip route >&2"); - $router->execute("iptables-save >&2"); - $client->log('=============================================='); - $client->succeed("ip addr >&2"); - $client->succeed("ip route >&2"); - $client->execute("iptables-save >&2"); - $client->log('##############################################'); - - # Test macvlan creates routable ips - $client->waitUntilSucceeds("ping -c 1 192.168.1.1"); - $client->waitUntilSucceeds("ping -c 1 192.168.1.2"); - $client->waitUntilSucceeds("ping -c 1 192.168.1.3"); - - $router->waitUntilSucceeds("ping -c 1 192.168.1.1"); - $router->waitUntilSucceeds("ping -c 1 192.168.1.2"); - $router->waitUntilSucceeds("ping -c 1 192.168.1.3"); + start_all() + + with subtest("Wait for networking to come up"): + client.wait_for_unit("network.target") + router.wait_for_unit("network.target") + + with subtest("Wait until we have an ip address on each interface"): + client.wait_until_succeeds("ip addr show dev eth1 | grep -q '192.168.1'") + client.wait_until_succeeds("ip addr show dev macvlan | grep -q '192.168.1'") + + with subtest("Print lots of diagnostic information"): + router.log("**********************************************") + router.succeed("ip addr >&2") + router.succeed("ip route >&2") + router.execute("iptables-save >&2") + client.log("==============================================") + client.succeed("ip addr >&2") + client.succeed("ip route >&2") + client.execute("iptables-save >&2") + client.log("##############################################") + + with subtest("Test macvlan creates routable ips"): + client.wait_until_succeeds("ping -c 1 192.168.1.1") + client.wait_until_succeeds("ping -c 1 192.168.1.2") + client.wait_until_succeeds("ping -c 1 192.168.1.3") + + router.wait_until_succeeds("ping -c 1 192.168.1.1") + router.wait_until_succeeds("ping -c 1 192.168.1.2") + router.wait_until_succeeds("ping -c 1 192.168.1.3") ''; }; sit = let @@ -395,22 +399,22 @@ let nodes.client2 = node { address4 = "192.168.1.2"; remote = "192.168.1.1"; address6 = "fc00::2"; }; testScript = { ... }: '' - startAll; + start_all() - # Wait for networking to be configured - $client1->waitForUnit("network.target"); - $client2->waitForUnit("network.target"); + with subtest("Wait for networking to be configured"): + client1.wait_for_unit("network.target") + client2.wait_for_unit("network.target") - # Print diagnostic information - $client1->succeed("ip addr >&2"); - $client2->succeed("ip addr >&2"); + # Print diagnostic information + client1.succeed("ip addr >&2") + client2.succeed("ip addr >&2") - # Test ipv6 - $client1->waitUntilSucceeds("ping -c 1 fc00::1"); - $client1->waitUntilSucceeds("ping -c 1 fc00::2"); + with subtest("Test ipv6"): + client1.wait_until_succeeds("ping -c 1 fc00::1") + client1.wait_until_succeeds("ping -c 1 fc00::2") - $client2->waitUntilSucceeds("ping -c 1 fc00::1"); - $client2->waitUntilSucceeds("ping -c 1 fc00::2"); + client2.wait_until_succeeds("ping -c 1 fc00::1") + client2.wait_until_succeeds("ping -c 1 fc00::2") ''; }; vlan = let @@ -435,15 +439,15 @@ let nodes.client2 = node "192.168.1.2"; testScript = { ... }: '' - startAll; + start_all() - # Wait for networking to be configured - $client1->waitForUnit("network.target"); - $client2->waitForUnit("network.target"); + with subtest("Wait for networking to be configured"): + client1.wait_for_unit("network.target") + client2.wait_for_unit("network.target") - # Test vlan is setup - $client1->succeed("ip addr show dev vlan >&2"); - $client2->succeed("ip addr show dev vlan >&2"); + with subtest("Test vlan is setup"): + client1.succeed("ip addr show dev vlan >&2") + client2.succeed("ip addr show dev vlan >&2") ''; }; virtual = { @@ -455,42 +459,55 @@ let ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ]; ipv6.addresses = [ { address = "2001:1470:fffd:2096::"; prefixLength = 64; } ]; virtual = true; + mtu = 1342; + macAddress = "02:de:ad:be:ef:01"; }; networking.interfaces.tun0 = { ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ]; ipv6.addresses = [ { address = "2001:1470:fffd:2097::"; prefixLength = 64; } ]; virtual = true; + mtu = 1343; }; }; testScript = '' - my $targetList = <<'END'; + targetList = """ tap0: tap persist user 0 tun0: tun persist user 0 - END - - # Wait for networking to come up - $machine->start; - $machine->waitForUnit("network-online.target"); - - # Test interfaces set up - my $list = $machine->succeed("ip tuntap list | sort"); - "$list" eq "$targetList" or die( - "The list of virtual interfaces does not match the expected one:\n", - "Result:\n", "$list\n", - "Expected:\n", "$targetList\n" - ); - - # Test interfaces clean up - $machine->succeed("systemctl stop network-addresses-tap0"); - $machine->sleep(10); - $machine->succeed("systemctl stop network-addresses-tun0"); - $machine->sleep(10); - my $residue = $machine->succeed("ip tuntap list"); - $residue eq "" or die( - "Some virtual interface has not been properly cleaned:\n", - "$residue\n" - ); + """.strip() + + with subtest("Wait for networking to come up"): + machine.start() + machine.wait_for_unit("network.target") + + with subtest("Test interfaces set up"): + list = machine.succeed("ip tuntap list | sort").strip() + assert ( + list == targetList + ), """ + The list of virtual interfaces does not match the expected one: + Result: + {} + Expected: + {} + """.format( + list, targetList + ) + with subtest("Test MTU and MAC Address are configured"): + assert "mtu 1342" in machine.succeed("ip link show dev tap0") + assert "mtu 1343" in machine.succeed("ip link show dev tun0") + assert "02:de:ad:be:ef:01" in machine.succeed("ip link show dev tap0") + '' # network-addresses-* only exist in scripted networking + + optionalString (!networkd) '' + with subtest("Test interfaces clean up"): + machine.succeed("systemctl stop network-addresses-tap0") + machine.sleep(10) + machine.succeed("systemctl stop network-addresses-tun0") + machine.sleep(10) + residue = machine.succeed("ip tuntap list") + assert ( + residue is "" + ), "Some virtual interface has not been properly cleaned:\n{}".format(residue) ''; }; privacy = { @@ -522,13 +539,13 @@ let ''; }; }; - nodes.clientWithPrivacy = { pkgs, ... }: with pkgs.lib; { + nodes.client_with_privacy = { pkgs, ... }: with pkgs.lib; { virtualisation.vlans = [ 1 ]; networking = { useNetworkd = networkd; useDHCP = false; interfaces.eth1 = { - preferTempAddress = true; + tempAddress = "default"; ipv4.addresses = mkOverride 0 [ ]; ipv6.addresses = mkOverride 0 [ ]; useDHCP = true; @@ -541,7 +558,7 @@ let useNetworkd = networkd; useDHCP = false; interfaces.eth1 = { - preferTempAddress = false; + tempAddress = "enabled"; ipv4.addresses = mkOverride 0 [ ]; ipv6.addresses = mkOverride 0 [ ]; useDHCP = true; @@ -550,25 +567,31 @@ let }; testScript = { ... }: '' - startAll; - - $client->waitForUnit("network.target"); - $clientWithPrivacy->waitForUnit("network.target"); - $router->waitForUnit("network-online.target"); - - # Wait until we have an ip address - $clientWithPrivacy->waitUntilSucceeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'"); - $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'"); - - # Test vlan 1 - $clientWithPrivacy->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::1"); - $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::1"); - - # Test address used is temporary - $clientWithPrivacy->waitUntilSucceeds("! ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'"); - - # Test address used is EUI-64 - $client->waitUntilSucceeds("ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'"); + start_all() + + client.wait_for_unit("network.target") + client_with_privacy.wait_for_unit("network.target") + router.wait_for_unit("network-online.target") + + with subtest("Wait until we have an ip address"): + client_with_privacy.wait_until_succeeds( + "ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'" + ) + client.wait_until_succeeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'") + + with subtest("Test vlan 1"): + client_with_privacy.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1") + client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1") + + with subtest("Test address used is temporary"): + client_with_privacy.wait_until_succeeds( + "! ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'" + ) + + with subtest("Test address used is EUI-64"): + client.wait_until_succeeds( + "ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'" + ) ''; }; routes = { @@ -591,47 +614,82 @@ let }; testScript = '' - my $targetIPv4Table = <<'END'; - 10.0.0.0/16 proto static scope link mtu 1500 - 192.168.1.0/24 proto kernel scope link src 192.168.1.2 - 192.168.2.0/24 via 192.168.1.1 proto static - END - - my $targetIPv6Table = <<'END'; - 2001:1470:fffd:2097::/64 proto kernel metric 256 pref medium - 2001:1470:fffd:2098::/64 via fdfd:b3f0::1 proto static metric 1024 pref medium - fdfd:b3f0::/48 proto static metric 1024 pref medium - END - - $machine->start; - $machine->waitForUnit("network.target"); - - # test routing tables - my $ipv4Table = $machine->succeed("ip -4 route list dev eth0 | head -n3"); - my $ipv6Table = $machine->succeed("ip -6 route list dev eth0 | head -n3"); - "$ipv4Table" eq "$targetIPv4Table" or die( - "The IPv4 routing table does not match the expected one:\n", - "Result:\n", "$ipv4Table\n", - "Expected:\n", "$targetIPv4Table\n" - ); - "$ipv6Table" eq "$targetIPv6Table" or die( - "The IPv6 routing table does not match the expected one:\n", - "Result:\n", "$ipv6Table\n", - "Expected:\n", "$targetIPv6Table\n" - ); - - # test clean-up of the tables - $machine->succeed("systemctl stop network-addresses-eth0"); - my $ipv4Residue = $machine->succeed("ip -4 route list dev eth0 | head -n-3"); - my $ipv6Residue = $machine->succeed("ip -6 route list dev eth0 | head -n-3"); - $ipv4Residue eq "" or die( - "The IPv4 routing table has not been properly cleaned:\n", - "$ipv4Residue\n" - ); - $ipv6Residue eq "" or die( - "The IPv6 routing table has not been properly cleaned:\n", - "$ipv6Residue\n" - ); + targetIPv4Table = [ + "10.0.0.0/16 proto static scope link mtu 1500", + "192.168.1.0/24 proto kernel scope link src 192.168.1.2", + "192.168.2.0/24 via 192.168.1.1 proto static", + ] + + targetIPv6Table = [ + "2001:1470:fffd:2097::/64 proto kernel metric 256 pref medium", + "2001:1470:fffd:2098::/64 via fdfd:b3f0::1 proto static metric 1024 pref medium", + "fdfd:b3f0::/48 proto static metric 1024 pref medium", + ] + + machine.start() + machine.wait_for_unit("network.target") + + with subtest("test routing tables"): + ipv4Table = machine.succeed("ip -4 route list dev eth0 | head -n3").strip() + ipv6Table = machine.succeed("ip -6 route list dev eth0 | head -n3").strip() + assert [ + l.strip() for l in ipv4Table.splitlines() + ] == targetIPv4Table, """ + The IPv4 routing table does not match the expected one: + Result: + {} + Expected: + {} + """.format( + ipv4Table, targetIPv4Table + ) + assert [ + l.strip() for l in ipv6Table.splitlines() + ] == targetIPv6Table, """ + The IPv6 routing table does not match the expected one: + Result: + {} + Expected: + {} + """.format( + ipv6Table, targetIPv6Table + ) + + with subtest("test clean-up of the tables"): + machine.succeed("systemctl stop network-addresses-eth0") + ipv4Residue = machine.succeed("ip -4 route list dev eth0 | head -n-3").strip() + ipv6Residue = machine.succeed("ip -6 route list dev eth0 | head -n-3").strip() + assert ( + ipv4Residue is "" + ), "The IPv4 routing table has not been properly cleaned:\n{}".format(ipv4Residue) + assert ( + ipv6Residue is "" + ), "The IPv6 routing table has not been properly cleaned:\n{}".format(ipv6Residue) + ''; + }; + # even with disabled networkd, systemd.network.links should work + # (as it's handled by udev, not networkd) + link = { + name = "Link"; + nodes.client = { pkgs, ... }: { + virtualisation.vlans = [ 1 ]; + networking = { + useNetworkd = networkd; + useDHCP = false; + }; + systemd.network.links."50-foo" = { + matchConfig = { + Name = "foo"; + Driver = "dummy"; + }; + linkConfig.MTUBytes = "1442"; + }; + }; + testScript = '' + print(client.succeed("ip l add name foo type dummy")) + print(client.succeed("stat /etc/systemd/network/50-foo.link")) + client.succeed("udevadm settle") + assert "mtu 1442" in client.succeed("ip l show dummy0") ''; }; }; diff --git a/nixos/tests/nextcloud/basic.nix b/nixos/tests/nextcloud/basic.nix index 75862feb202d2..92ac5c46e8f03 100644 --- a/nixos/tests/nextcloud/basic.nix +++ b/nixos/tests/nextcloud/basic.nix @@ -32,7 +32,7 @@ in { testScript = let withRcloneEnv = pkgs.writeScript "with-rclone-env" '' - #!${pkgs.stdenv.shell} + #!${pkgs.runtimeShell} export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/" export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud" @@ -41,12 +41,12 @@ in { "''${@}" ''; copySharedFile = pkgs.writeScript "copy-shared-file" '' - #!${pkgs.stdenv.shell} + #!${pkgs.runtimeShell} echo 'hi' | ${withRcloneEnv} ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file ''; diffSharedFile = pkgs.writeScript "diff-shared-file" '' - #!${pkgs.stdenv.shell} + #!${pkgs.runtimeShell} diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file) ''; in '' diff --git a/nixos/tests/nextcloud/with-mysql-and-memcached.nix b/nixos/tests/nextcloud/with-mysql-and-memcached.nix index b9ba5888187d5..8db630be893af 100644 --- a/nixos/tests/nextcloud/with-mysql-and-memcached.nix +++ b/nixos/tests/nextcloud/with-mysql-and-memcached.nix @@ -61,14 +61,14 @@ in { testScript = let configureMemcached = pkgs.writeScript "configure-memcached" '' - #!${pkgs.stdenv.shell} + #!${pkgs.runtimeShell} nextcloud-occ config:system:set memcached_servers 0 0 --value 127.0.0.1 --type string nextcloud-occ config:system:set memcached_servers 0 1 --value 11211 --type integer nextcloud-occ config:system:set memcache.local --value '\OC\Memcache\APCu' --type string nextcloud-occ config:system:set memcache.distributed --value '\OC\Memcache\Memcached' --type string ''; withRcloneEnv = pkgs.writeScript "with-rclone-env" '' - #!${pkgs.stdenv.shell} + #!${pkgs.runtimeShell} export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/" export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud" @@ -76,12 +76,12 @@ in { export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})" ''; copySharedFile = pkgs.writeScript "copy-shared-file" '' - #!${pkgs.stdenv.shell} + #!${pkgs.runtimeShell} echo 'hi' | ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file ''; diffSharedFile = pkgs.writeScript "diff-shared-file" '' - #!${pkgs.stdenv.shell} + #!${pkgs.runtimeShell} diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file) ''; in '' diff --git a/nixos/tests/nextcloud/with-postgresql-and-redis.nix b/nixos/tests/nextcloud/with-postgresql-and-redis.nix index 324853350af6e..95219cac9be8e 100644 --- a/nixos/tests/nextcloud/with-postgresql-and-redis.nix +++ b/nixos/tests/nextcloud/with-postgresql-and-redis.nix @@ -60,14 +60,14 @@ in { testScript = let configureRedis = pkgs.writeScript "configure-redis" '' - #!${pkgs.stdenv.shell} + #!${pkgs.runtimeShell} nextcloud-occ config:system:set redis 'host' --value 'localhost' --type string nextcloud-occ config:system:set redis 'port' --value 6379 --type integer nextcloud-occ config:system:set memcache.local --value '\OC\Memcache\Redis' --type string nextcloud-occ config:system:set memcache.locking --value '\OC\Memcache\Redis' --type string ''; withRcloneEnv = pkgs.writeScript "with-rclone-env" '' - #!${pkgs.stdenv.shell} + #!${pkgs.runtimeShell} export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/" export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud" @@ -76,12 +76,12 @@ in { "''${@}" ''; copySharedFile = pkgs.writeScript "copy-shared-file" '' - #!${pkgs.stdenv.shell} + #!${pkgs.runtimeShell} echo 'hi' | ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file ''; diffSharedFile = pkgs.writeScript "diff-shared-file" '' - #!${pkgs.stdenv.shell} + #!${pkgs.runtimeShell} diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file) ''; in '' diff --git a/nixos/tests/nfs.nix b/nixos/tests/nfs.nix deleted file mode 100644 index 2f655336e757e..0000000000000 --- a/nixos/tests/nfs.nix +++ /dev/null @@ -1,90 +0,0 @@ -import ./make-test.nix ({ pkgs, version ? 4, ... }: - -let - - client = - { pkgs, ... }: - { fileSystems = pkgs.lib.mkVMOverride - [ { mountPoint = "/data"; - # nfs4 exports the export with fsid=0 as a virtual root directory - device = if (version == 4) then "server:/" else "server:/data"; - fsType = "nfs"; - options = [ "vers=${toString version}" ]; - } - ]; - networking.firewall.enable = false; # FIXME: only open statd - }; - -in - -{ - name = "nfs"; - meta = with pkgs.stdenv.lib.maintainers; { - maintainers = [ eelco ]; - }; - - nodes = - { client1 = client; - client2 = client; - - server = - { ... }: - { services.nfs.server.enable = true; - services.nfs.server.exports = - '' - /data 192.168.1.0/255.255.255.0(rw,no_root_squash,no_subtree_check,fsid=0) - ''; - services.nfs.server.createMountPoints = true; - networking.firewall.enable = false; # FIXME: figure out what ports need to be allowed - }; - }; - - testScript = - '' - $server->waitForUnit("nfs-server"); - $server->succeed("systemctl start network-online.target"); - $server->waitForUnit("network-online.target"); - - startAll; - - $client1->waitForUnit("data.mount"); - $client1->succeed("echo bla > /data/foo"); - $server->succeed("test -e /data/foo"); - - $client2->waitForUnit("data.mount"); - $client2->succeed("echo bla > /data/bar"); - $server->succeed("test -e /data/bar"); - - # Test whether restarting ‘nfs-server’ works correctly. - $server->succeed("systemctl restart nfs-server"); - $client2->succeed("echo bla >> /data/bar"); # will take 90 seconds due to the NFS grace period - - # Test whether we can get a lock. - $client2->succeed("time flock -n -s /data/lock true"); - - # Test locking: client 1 acquires an exclusive lock, so client 2 - # should then fail to acquire a shared lock. - $client1->succeed("flock -x /data/lock -c 'touch locked; sleep 100000' &"); - $client1->waitForFile("locked"); - $client2->fail("flock -n -s /data/lock true"); - - # Test whether client 2 obtains the lock if we reset client 1. - $client2->succeed("flock -x /data/lock -c 'echo acquired; touch locked; sleep 100000' >&2 &"); - $client1->crash; - $client1->start; - $client2->waitForFile("locked"); - - # Test whether locks survive a reboot of the server. - $client1->waitForUnit("data.mount"); - $server->shutdown; - $server->start; - $client1->succeed("touch /data/xyzzy"); - $client1->fail("time flock -n -s /data/lock true"); - - # Test whether unmounting during shutdown happens quickly. - my $t1 = time; - $client1->shutdown; - my $duration = time - $t1; - die "shutdown took too long ($duration seconds)" if $duration > 30; - ''; -}) diff --git a/nixos/tests/nfs/default.nix b/nixos/tests/nfs/default.nix new file mode 100644 index 0000000000000..6bc803c91b460 --- /dev/null +++ b/nixos/tests/nfs/default.nix @@ -0,0 +1,9 @@ +{ version ? 4 +, system ? builtins.currentSystem +, pkgs ? import ../../.. { inherit system; } +}: { + simple = import ./simple.nix { inherit version system pkgs; }; +} // pkgs.lib.optionalAttrs (version == 4) { + # TODO: Test kerberos + nfsv3 + kerberos = import ./kerberos.nix { inherit version system pkgs; }; +} diff --git a/nixos/tests/nfs/kerberos.nix b/nixos/tests/nfs/kerberos.nix new file mode 100644 index 0000000000000..1f2d0d453ea0c --- /dev/null +++ b/nixos/tests/nfs/kerberos.nix @@ -0,0 +1,133 @@ +import ../make-test-python.nix ({ pkgs, lib, ... }: + +with lib; + +let + krb5 = + { enable = true; + domain_realm."nfs.test" = "NFS.TEST"; + libdefaults.default_realm = "NFS.TEST"; + realms."NFS.TEST" = + { admin_server = "server.nfs.test"; + kdc = "server.nfs.test"; + }; + }; + + hosts = + '' + 192.168.1.1 client.nfs.test + 192.168.1.2 server.nfs.test + ''; + + users = { + users.alice = { + isNormalUser = true; + name = "alice"; + uid = 1000; + }; + }; + +in + +{ + name = "nfsv4-with-kerberos"; + + nodes = { + client = { lib, ... }: + { inherit krb5 users; + + networking.extraHosts = hosts; + networking.domain = "nfs.test"; + networking.hostName = "client"; + + fileSystems = lib.mkVMOverride + { "/data" = { + device = "server.nfs.test:/"; + fsType = "nfs"; + options = [ "nfsvers=4" "sec=krb5p" "noauto" ]; + }; + }; + }; + + server = { lib, ...}: + { inherit krb5 users; + + networking.extraHosts = hosts; + networking.domain = "nfs.test"; + networking.hostName = "server"; + + networking.firewall.allowedTCPPorts = [ + 111 # rpc + 2049 # nfs + 88 # kerberos + 749 # kerberos admin + ]; + + services.kerberos_server.enable = true; + services.kerberos_server.realms = + { "NFS.TEST".acl = + [ { access = "all"; principal = "admin/admin"; } ]; + }; + + services.nfs.server.enable = true; + services.nfs.server.createMountPoints = true; + services.nfs.server.exports = + '' + /data *(rw,no_root_squash,fsid=0,sec=krb5p) + ''; + }; + }; + + testScript = + '' + server.succeed("mkdir -p /data/alice") + server.succeed("chown alice:users /data/alice") + + # set up kerberos database + server.succeed( + "kdb5_util create -s -r NFS.TEST -P master_key", + "systemctl restart kadmind.service kdc.service", + ) + server.wait_for_unit(f"kadmind.service") + server.wait_for_unit(f"kdc.service") + + # create principals + server.succeed( + "kadmin.local add_principal -randkey nfs/server.nfs.test", + "kadmin.local add_principal -randkey nfs/client.nfs.test", + "kadmin.local add_principal -pw admin_pw admin/admin", + "kadmin.local add_principal -pw alice_pw alice", + ) + + # add principals to server keytab + server.succeed("kadmin.local ktadd nfs/server.nfs.test") + server.succeed("systemctl start rpc-gssd.service rpc-svcgssd.service") + server.wait_for_unit(f"rpc-gssd.service") + server.wait_for_unit(f"rpc-svcgssd.service") + + client.wait_for_unit("network-online.target") + + # add principals to client keytab + client.succeed("echo admin_pw | kadmin -p admin/admin ktadd nfs/client.nfs.test") + client.succeed("systemctl start rpc-gssd.service") + client.wait_for_unit("rpc-gssd.service") + + with subtest("nfs share mounts"): + client.succeed("systemctl restart data.mount") + client.wait_for_unit("data.mount") + + with subtest("permissions on nfs share are enforced"): + client.fail("su alice -c 'ls /data'") + client.succeed("su alice -c 'echo alice_pw | kinit'") + client.succeed("su alice -c 'ls /data'") + + client.fail("su alice -c 'echo bla >> /data/foo'") + client.succeed("su alice -c 'echo bla >> /data/alice/foo'") + server.succeed("test -e /data/alice/foo") + + with subtest("uids/gids are mapped correctly on nfs share"): + ids = client.succeed("stat -c '%U %G' /data/alice").split() + expected = ["alice", "users"] + assert ids == expected, f"ids incorrect: got {ids} expected {expected}" + ''; +}) diff --git a/nixos/tests/nfs/simple.nix b/nixos/tests/nfs/simple.nix new file mode 100644 index 0000000000000..c49ebddc2fddf --- /dev/null +++ b/nixos/tests/nfs/simple.nix @@ -0,0 +1,94 @@ +import ../make-test-python.nix ({ pkgs, version ? 4, ... }: + +let + + client = + { pkgs, ... }: + { fileSystems = pkgs.lib.mkVMOverride + { "/data" = + { # nfs4 exports the export with fsid=0 as a virtual root directory + device = if (version == 4) then "server:/" else "server:/data"; + fsType = "nfs"; + options = [ "vers=${toString version}" ]; + }; + }; + networking.firewall.enable = false; # FIXME: only open statd + }; + +in + +{ + name = "nfs"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ eelco ]; + }; + + nodes = + { client1 = client; + client2 = client; + + server = + { ... }: + { services.nfs.server.enable = true; + services.nfs.server.exports = + '' + /data 192.168.1.0/255.255.255.0(rw,no_root_squash,no_subtree_check,fsid=0) + ''; + services.nfs.server.createMountPoints = true; + networking.firewall.enable = false; # FIXME: figure out what ports need to be allowed + }; + }; + + testScript = + '' + import time + + server.wait_for_unit("nfs-server") + server.succeed("systemctl start network-online.target") + server.wait_for_unit("network-online.target") + + start_all() + + client1.wait_for_unit("data.mount") + client1.succeed("echo bla > /data/foo") + server.succeed("test -e /data/foo") + + client2.wait_for_unit("data.mount") + client2.succeed("echo bla > /data/bar") + server.succeed("test -e /data/bar") + + with subtest("restarting 'nfs-server' works correctly"): + server.succeed("systemctl restart nfs-server") + # will take 90 seconds due to the NFS grace period + client2.succeed("echo bla >> /data/bar") + + with subtest("can get a lock"): + client2.succeed("time flock -n -s /data/lock true") + + with subtest("client 2 fails to acquire lock held by client 1"): + client1.succeed("flock -x /data/lock -c 'touch locked; sleep 100000' &") + client1.wait_for_file("locked") + client2.fail("flock -n -s /data/lock true") + + with subtest("client 2 obtains lock after resetting client 1"): + client2.succeed( + "flock -x /data/lock -c 'echo acquired; touch locked; sleep 100000' >&2 &" + ) + client1.crash() + client1.start() + client2.wait_for_file("locked") + + with subtest("locks survive server reboot"): + client1.wait_for_unit("data.mount") + server.shutdown() + server.start() + client1.succeed("touch /data/xyzzy") + client1.fail("time flock -n -s /data/lock true") + + with subtest("unmounting during shutdown happens quickly"): + t1 = time.monotonic() + client1.shutdown() + duration = time.monotonic() - t1 + assert duration < 30, f"shutdown took too long ({duration} seconds)" + ''; +}) diff --git a/nixos/tests/nghttpx.nix b/nixos/tests/nghttpx.nix index 11611bfe10632..d83c1c4cae63a 100644 --- a/nixos/tests/nghttpx.nix +++ b/nixos/tests/nghttpx.nix @@ -1,7 +1,7 @@ let nginxRoot = "/run/nginx"; in - import ./make-test.nix ({...}: { + import ./make-test-python.nix ({...}: { name = "nghttpx"; nodes = { webserver = { @@ -52,10 +52,10 @@ in }; testScript = '' - startAll; + start_all() - $webserver->waitForOpenPort("80"); - $proxy->waitForOpenPort("80"); - $client->waitUntilSucceeds("curl -s --fail http://proxy/hello-world.txt"); + webserver.wait_for_open_port("80") + proxy.wait_for_open_port("80") + client.wait_until_succeeds("curl -s --fail http://proxy/hello-world.txt") ''; }) diff --git a/nixos/tests/nginx-etag.nix b/nixos/tests/nginx-etag.nix new file mode 100644 index 0000000000000..63ab2e0c6c275 --- /dev/null +++ b/nixos/tests/nginx-etag.nix @@ -0,0 +1,89 @@ +import ./make-test-python.nix { + name = "nginx-etag"; + + nodes = { + server = { pkgs, lib, ... }: { + networking.firewall.enable = false; + services.nginx.enable = true; + services.nginx.virtualHosts.server = { + root = pkgs.runCommandLocal "testdir" {} '' + mkdir "$out" + cat > "$out/test.js" <<EOF + document.getElementById('foobar').setAttribute('foo', 'bar'); + EOF + cat > "$out/index.html" <<EOF + <!DOCTYPE html> + <div id="foobar">test</div> + <script src="test.js"></script> + EOF + ''; + }; + + specialisation.pass-checks.configuration = { + services.nginx.virtualHosts.server = { + root = lib.mkForce (pkgs.runCommandLocal "testdir2" {} '' + mkdir "$out" + cat > "$out/test.js" <<EOF + document.getElementById('foobar').setAttribute('foo', 'yay'); + EOF + cat > "$out/index.html" <<EOF + <!DOCTYPE html> + <div id="foobar">test</div> + <script src="test.js"></script> + EOF + ''); + }; + }; + }; + + client = { pkgs, lib, ... }: { + virtualisation.memorySize = 512; + environment.systemPackages = let + testRunner = pkgs.writers.writePython3Bin "test-runner" { + libraries = [ pkgs.python3Packages.selenium ]; + } '' + import os + import time + + from selenium.webdriver import Firefox + from selenium.webdriver.firefox.options import Options + + options = Options() + options.add_argument('--headless') + driver = Firefox(options=options) + + driver.implicitly_wait(20) + driver.get('http://server/') + driver.find_element_by_xpath('//div[@foo="bar"]') + open('/tmp/passed_stage1', 'w') + + while not os.path.exists('/tmp/proceed'): + time.sleep(0.5) + + driver.get('http://server/') + driver.find_element_by_xpath('//div[@foo="yay"]') + open('/tmp/passed', 'w') + ''; + in [ pkgs.firefox-unwrapped pkgs.geckodriver testRunner ]; + }; + }; + + testScript = { nodes, ... }: let + inherit (nodes.server.config.system.build) toplevel; + newSystem = "${toplevel}/specialisation/pass-checks"; + in '' + start_all() + + server.wait_for_unit("nginx.service") + client.wait_for_unit("multi-user.target") + client.execute("test-runner &") + client.wait_for_file("/tmp/passed_stage1") + + server.succeed( + "${newSystem}/bin/switch-to-configuration test >&2" + ) + client.succeed("touch /tmp/proceed") + + client.wait_for_file("/tmp/passed") + ''; +} diff --git a/nixos/tests/nginx-pubhtml.nix b/nixos/tests/nginx-pubhtml.nix new file mode 100644 index 0000000000000..432913cb42d26 --- /dev/null +++ b/nixos/tests/nginx-pubhtml.nix @@ -0,0 +1,20 @@ +import ./make-test-python.nix { + name = "nginx-pubhtml"; + + machine = { pkgs, ... }: { + services.nginx.enable = true; + services.nginx.virtualHosts.localhost = { + locations."~ ^/\\~([a-z0-9_]+)(/.*)?$".alias = "/home/$1/public_html$2"; + }; + users.users.foo.isNormalUser = true; + }; + + testScript = '' + machine.wait_for_unit("nginx") + machine.wait_for_open_port(80) + machine.succeed("chmod 0711 /home/foo") + machine.succeed("su -c 'mkdir -p /home/foo/public_html' foo") + machine.succeed("su -c 'echo bar > /home/foo/public_html/bar.txt' foo") + machine.succeed('test "$(curl -fvvv http://localhost/~foo/bar.txt)" = bar') + ''; +} diff --git a/nixos/tests/nginx-sso.nix b/nixos/tests/nginx-sso.nix index e19992cb6bf75..8834fc31c387b 100644 --- a/nixos/tests/nginx-sso.nix +++ b/nixos/tests/nginx-sso.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, ... }: { +import ./make-test-python.nix ({ pkgs, ... }: { name = "nginx-sso"; meta = { maintainers = with pkgs.stdenv.lib.maintainers; [ delroth ]; @@ -27,18 +27,22 @@ import ./make-test.nix ({ pkgs, ... }: { }; testScript = '' - startAll; + start_all() - $machine->waitForUnit("nginx-sso.service"); - $machine->waitForOpenPort(8080); + machine.wait_for_unit("nginx-sso.service") + machine.wait_for_open_port(8080) - # No valid user -> 401. - $machine->fail("curl -sSf http://localhost:8080/auth"); + with subtest("No valid user -> 401"): + machine.fail("curl -sSf http://localhost:8080/auth") - # Valid user but no matching ACL -> 403. - $machine->fail("curl -sSf -H 'Authorization: Token MyToken' http://localhost:8080/auth"); + with subtest("Valid user but no matching ACL -> 403"): + machine.fail( + "curl -sSf -H 'Authorization: Token MyToken' http://localhost:8080/auth" + ) - # Valid user and matching ACL -> 200. - $machine->succeed("curl -sSf -H 'Authorization: Token MyToken' -H 'X-Application: MyApp' http://localhost:8080/auth"); + with subtest("Valid user and matching ACL -> 200"): + machine.succeed( + "curl -sSf -H 'Authorization: Token MyToken' -H 'X-Application: MyApp' http://localhost:8080/auth" + ) ''; }) diff --git a/nixos/tests/nginx.nix b/nixos/tests/nginx.nix index d0b7306ae83b7..18822f095688b 100644 --- a/nixos/tests/nginx.nix +++ b/nixos/tests/nginx.nix @@ -4,10 +4,10 @@ # 2. whether the ETag header is properly generated whenever we're serving # files in Nix store paths # 3. nginx doesn't restart on configuration changes (only reloads) -import ./make-test.nix ({ pkgs, ... }: { +import ./make-test-python.nix ({ pkgs, ... }: { name = "nginx"; meta = with pkgs.stdenv.lib.maintainers; { - maintainers = [ mbbx6spp ]; + maintainers = [ mbbx6spp danbst ]; }; nodes = { @@ -42,70 +42,88 @@ import ./make-test.nix ({ pkgs, ... }: { services.nginx.enableReload = true; - nesting.clone = [ - { - services.nginx.virtualHosts.localhost = { - root = lib.mkForce (pkgs.runCommand "testdir2" {} '' - mkdir "$out" - echo content changed > "$out/index.html" - ''); - }; - } - - { - services.nginx.virtualHosts."1.my.test".listen = [ { addr = "127.0.0.1"; port = 8080; }]; - } - - { - services.nginx.package = pkgs.nginxUnstable; - } - ]; - }; + specialisation.etagSystem.configuration = { + services.nginx.virtualHosts.localhost = { + root = lib.mkForce (pkgs.runCommand "testdir2" {} '' + mkdir "$out" + echo content changed > "$out/index.html" + ''); + }; + }; + specialisation.justReloadSystem.configuration = { + services.nginx.virtualHosts."1.my.test".listen = [ { addr = "127.0.0.1"; port = 8080; }]; + }; + + specialisation.reloadRestartSystem.configuration = { + services.nginx.package = pkgs.nginxUnstable; + }; + + specialisation.reloadWithErrorsSystem.configuration = { + services.nginx.package = pkgs.nginxUnstable; + services.nginx.virtualHosts."!@$$(#*%".locations."~@#*$*!)".proxyPass = ";;;"; + }; + }; }; testScript = { nodes, ... }: let - etagSystem = "${nodes.webserver.config.system.build.toplevel}/fine-tune/child-1"; - justReloadSystem = "${nodes.webserver.config.system.build.toplevel}/fine-tune/child-2"; - reloadRestartSystem = "${nodes.webserver.config.system.build.toplevel}/fine-tune/child-3"; + etagSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/etagSystem"; + justReloadSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/justReloadSystem"; + reloadRestartSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/reloadRestartSystem"; + reloadWithErrorsSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/reloadWithErrorsSystem"; in '' - my $url = 'http://localhost/index.html'; - - sub checkEtag { - my $etag = $webserver->succeed( - 'curl -v '.$url.' 2>&1 | sed -n -e "s/^< [Ee][Tt][Aa][Gg]: *//p"' - ); - $etag =~ s/\r?\n$//; - my $httpCode = $webserver->succeed( - 'curl -w "%{http_code}" -X HEAD -H \'If-None-Match: '.$etag.'\' '.$url - ); - chomp $httpCode; - die "HTTP code is not 304" unless $httpCode == 304; - return $etag; - } - - $webserver->waitForUnit("nginx"); - $webserver->waitForOpenPort("80"); - - subtest "check ETag if serving Nix store paths", sub { - my $oldEtag = checkEtag; - $webserver->succeed("${etagSystem}/bin/switch-to-configuration test >&2"); - $webserver->sleep(1); # race condition - my $newEtag = checkEtag; - die "Old ETag $oldEtag is the same as $newEtag" if $oldEtag eq $newEtag; - }; + url = "http://localhost/index.html" - subtest "config is reloaded on nixos-rebuild switch", sub { - $webserver->succeed("${justReloadSystem}/bin/switch-to-configuration test >&2"); - $webserver->waitForOpenPort("8080"); - $webserver->fail("journalctl -u nginx | grep -q -i stopped"); - $webserver->succeed("journalctl -u nginx | grep -q -i reloaded"); - }; - subtest "restart when nginx package changes", sub { - $webserver->succeed("${reloadRestartSystem}/bin/switch-to-configuration test >&2"); - $webserver->waitForUnit("nginx"); - $webserver->succeed("journalctl -u nginx | grep -q -i stopped"); - }; + def check_etag(): + etag = webserver.succeed( + f'curl -v {url} 2>&1 | sed -n -e "s/^< etag: *//ip"' + ).rstrip() + http_code = webserver.succeed( + f"curl -w '%{{http_code}}' --head --fail -H 'If-None-Match: {etag}' {url}" + ) + assert http_code.split("\n")[-1] == "304" + + return etag + + + webserver.wait_for_unit("nginx") + webserver.wait_for_open_port(80) + + with subtest("check ETag if serving Nix store paths"): + old_etag = check_etag() + webserver.succeed( + "${etagSystem}/bin/switch-to-configuration test >&2" + ) + webserver.sleep(1) + new_etag = check_etag() + assert old_etag != new_etag + + with subtest("config is reloaded on nixos-rebuild switch"): + webserver.succeed( + "${justReloadSystem}/bin/switch-to-configuration test >&2" + ) + webserver.wait_for_open_port(8080) + webserver.fail("journalctl -u nginx | grep -q -i stopped") + webserver.succeed("journalctl -u nginx | grep -q -i reloaded") + + with subtest("restart when nginx package changes"): + webserver.succeed( + "${reloadRestartSystem}/bin/switch-to-configuration test >&2" + ) + webserver.wait_for_unit("nginx") + webserver.succeed("journalctl -u nginx | grep -q -i stopped") + + with subtest("nixos-rebuild --switch should fail when there are configuration errors"): + webserver.fail( + "${reloadWithErrorsSystem}/bin/switch-to-configuration test >&2" + ) + webserver.succeed("[[ $(systemctl is-failed nginx-config-reload) == failed ]]") + webserver.succeed("[[ $(systemctl is-failed nginx) == active ]]") + # just to make sure operation is idempotent. During development I had a situation + # when first time it shows error, but stops showing it on subsequent rebuilds + webserver.fail( + "${reloadWithErrorsSystem}/bin/switch-to-configuration test >&2" + ) ''; }) diff --git a/nixos/tests/novacomd.nix b/nixos/tests/novacomd.nix index 4eb60c0feb5ce..940210dee2355 100644 --- a/nixos/tests/novacomd.nix +++ b/nixos/tests/novacomd.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, ...} : { name = "novacomd"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ dtzWill ]; @@ -9,26 +9,20 @@ import ./make-test.nix ({ pkgs, ...} : { }; testScript = '' - $machine->waitForUnit("multi-user.target"); + machine.wait_for_unit("novacomd.service") - # multi-user.target wants novacomd.service, but let's make sure - $machine->waitForUnit("novacomd.service"); + with subtest("Make sure the daemon is really listening"): + machine.wait_for_open_port(6968) + machine.succeed("novacom -l") - # Check status and try connecting with novacom - $machine->succeed("systemctl status novacomd.service >&2"); - # to prevent non-deterministic failure, - # make sure the daemon is really listening - $machine->waitForOpenPort(6968); - $machine->succeed("novacom -l"); + with subtest("Stop the daemon, double-check novacom fails if daemon isn't working"): + machine.stop_job("novacomd") + machine.fail("novacom -l") - # Stop the daemon, double-check novacom fails if daemon isn't working - $machine->stopJob("novacomd"); - $machine->fail("novacom -l"); - - # And back again for good measure - $machine->startJob("novacomd"); - # make sure the daemon is really listening - $machine->waitForOpenPort(6968); - $machine->succeed("novacom -l"); + with subtest("Make sure the daemon starts back up again"): + machine.start_job("novacomd") + # make sure the daemon is really listening + machine.wait_for_open_port(6968) + machine.succeed("novacom -l") ''; }) diff --git a/nixos/tests/nsd.nix b/nixos/tests/nsd.nix index c3c91e71b5ca8..bcc14e817a876 100644 --- a/nixos/tests/nsd.nix +++ b/nixos/tests/nsd.nix @@ -5,7 +5,7 @@ let # for a host utility with IPv6 support environment.systemPackages = [ pkgs.bind ]; }; -in import ./make-test.nix ({ pkgs, ...} : { +in import ./make-test-python.nix ({ pkgs, ...} : { name = "nsd"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ aszlig ]; @@ -65,37 +65,35 @@ in import ./make-test.nix ({ pkgs, ...} : { }; testScript = '' - startAll; + start_all() - $clientv4->waitForUnit("network.target"); - $clientv6->waitForUnit("network.target"); - $server->waitForUnit("nsd.service"); + clientv4.wait_for_unit("network.target") + clientv6.wait_for_unit("network.target") + server.wait_for_unit("nsd.service") - sub assertHost { - my ($type, $rr, $query, $expected) = @_; - my $self = $type eq 4 ? $clientv4 : $clientv6; - my $out = $self->succeed("host -$type -t $rr $query"); - $self->log("output: $out"); - chomp $out; - die "DNS IPv$type query on $query gave '$out' instead of '$expected'" - if ($out !~ $expected); - } - foreach (4, 6) { - subtest "ipv$_", sub { - assertHost($_, "a", "example.com", qr/has no [^ ]+ record/); - assertHost($_, "aaaa", "example.com", qr/has no [^ ]+ record/); + def assert_host(type, rr, query, expected): + self = clientv4 if type == 4 else clientv6 + out = self.succeed(f"host -{type} -t {rr} {query}").rstrip() + self.log(f"output: {out}") + assert re.search( + expected, out + ), f"DNS IPv{type} query on {query} gave '{out}' instead of '{expected}'" - assertHost($_, "soa", "example.com", qr/SOA.*?noc\.example\.com/); - assertHost($_, "a", "ipv4.example.com", qr/address 1.2.3.4$/); - assertHost($_, "aaaa", "ipv6.example.com", qr/address abcd::eeff$/); - assertHost($_, "a", "deleg.example.com", qr/address 9.8.7.6$/); - assertHost($_, "aaaa", "deleg.example.com", qr/address fedc::bbaa$/); + for ipv in 4, 6: + with subtest(f"IPv{ipv}"): + assert_host(ipv, "a", "example.com", "has no [^ ]+ record") + assert_host(ipv, "aaaa", "example.com", "has no [^ ]+ record") - assertHost($_, "a", "root", qr/address 1.8.7.4$/); - assertHost($_, "aaaa", "root", qr/address acbd::4$/); - }; - } + assert_host(ipv, "soa", "example.com", "SOA.*?noc\.example\.com") + assert_host(ipv, "a", "ipv4.example.com", "address 1.2.3.4$") + assert_host(ipv, "aaaa", "ipv6.example.com", "address abcd::eeff$") + + assert_host(ipv, "a", "deleg.example.com", "address 9.8.7.6$") + assert_host(ipv, "aaaa", "deleg.example.com", "address fedc::bbaa$") + + assert_host(ipv, "a", "root", "address 1.8.7.4$") + assert_host(ipv, "aaaa", "root", "address acbd::4$") ''; }) diff --git a/nixos/tests/nzbget.nix b/nixos/tests/nzbget.nix index 042ccec98cf6c..12d8ed6ea8da1 100644 --- a/nixos/tests/nzbget.nix +++ b/nixos/tests/nzbget.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, ...} : { name = "nzbget"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ aanderse flokli ]; @@ -15,12 +15,16 @@ import ./make-test.nix ({ pkgs, ...} : { }; testScript = '' - startAll; + start_all() - $server->waitForUnit("nzbget.service"); - $server->waitForUnit("network.target"); - $server->waitForOpenPort(6789); - $server->succeed("curl -s -u nzbget:tegbzn6789 http://127.0.0.1:6789 | grep -q 'This file is part of nzbget'"); - $server->succeed("${pkgs.nzbget}/bin/nzbget -n -o ControlIP=127.0.0.1 -o ControlPort=6789 -o ControlPassword=tegbzn6789 -V"); + server.wait_for_unit("nzbget.service") + server.wait_for_unit("network.target") + server.wait_for_open_port(6789) + assert "This file is part of nzbget" in server.succeed( + "curl -s -u nzbget:tegbzn6789 http://127.0.0.1:6789" + ) + server.succeed( + "${pkgs.nzbget}/bin/nzbget -n -o Control_iP=127.0.0.1 -o Control_port=6789 -o Control_password=tegbzn6789 -V" + ) ''; }) diff --git a/nixos/tests/openarena.nix b/nixos/tests/openarena.nix index 4cc4db229637d..395ed9153ea11 100644 --- a/nixos/tests/openarena.nix +++ b/nixos/tests/openarena.nix @@ -1,36 +1,71 @@ -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, ...} : + +let + client = + { pkgs, ... }: + + { imports = [ ./common/x11.nix ]; + hardware.opengl.driSupport = true; + environment.systemPackages = [ pkgs.openarena ]; + }; + +in { name = "openarena"; meta = with pkgs.stdenv.lib.maintainers; { - maintainers = [ tomfitzhenry ]; + maintainers = [ fpletz ]; }; - machine = - { pkgs, ... }: + nodes = + { server = + { services.openarena = { + enable = true; + extraFlags = [ "+set g_gametype 0" "+map oa_dm7" "+addbot Angelyss" "+addbot Arachna" ]; + openPorts = true; + }; + }; - { imports = []; - environment.systemPackages = with pkgs; [ - socat - ]; - services.openarena = { - enable = true; - extraFlags = [ - "+set dedicated 2" - "+set sv_hostname 'My NixOS server'" - "+map oa_dm1" - ]; - }; + client1 = client; + client2 = client; }; testScript = '' - $machine->waitForUnit("openarena.service"); - $machine->waitUntilSucceeds("ss --numeric --udp --listening | grep -q 27960"); + start_all() + + server.wait_for_unit("openarena") + server.wait_until_succeeds("ss --numeric --udp --listening | grep -q 27960") + + client1.wait_for_x() + client2.wait_for_x() + + client1.execute("openarena +set r_fullscreen 0 +set name Foo +connect server &") + client2.execute("openarena +set r_fullscreen 0 +set name Bar +connect server &") - # The log line containing 'resolve address' is last and only message that occurs after - # the server starts accepting clients. - $machine->waitUntilSucceeds("journalctl -u openarena.service | grep 'resolve address: dpmaster.deathmask.net'"); + server.wait_until_succeeds( + "journalctl -u openarena -e | grep -q 'Foo.*entered the game'" + ) + server.wait_until_succeeds( + "journalctl -u openarena -e | grep -q 'Bar.*entered the game'" + ) - # Check it's possible to join the server. - $machine->succeed("echo -n -e '\\xff\\xff\\xff\\xffgetchallenge' | socat - UDP4-DATAGRAM:127.0.0.1:27960 | grep -q challengeResponse"); + server.sleep(10) # wait for a while to get a nice screenshot + + client1.screenshot("screen_client1_1") + client2.screenshot("screen_client2_1") + + client1.block() + + server.sleep(10) + + client1.screenshot("screen_client1_2") + client2.screenshot("screen_client2_2") + + client1.unblock() + + server.sleep(10) + + client1.screenshot("screen_client1_3") + client2.screenshot("screen_client2_3") ''; + }) diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix index 3ef90e1834796..f8321a2c522dc 100644 --- a/nixos/tests/openldap.nix +++ b/nixos/tests/openldap.nix @@ -1,4 +1,4 @@ -import ./make-test.nix { +import ./make-test-python.nix { name = "openldap"; machine = { pkgs, ... }: { @@ -24,8 +24,10 @@ import ./make-test.nix { }; testScript = '' - $machine->waitForUnit('openldap.service'); - $machine->succeed('systemctl status openldap.service'); - $machine->succeed('ldapsearch -LLL -D "cn=root,dc=example" -w notapassword -b "dc=example"'); + machine.wait_for_unit("openldap.service") + machine.succeed( + "systemctl status openldap.service", + 'ldapsearch -LLL -D "cn=root,dc=example" -w notapassword -b "dc=example"', + ) ''; } diff --git a/nixos/tests/opensmtpd.nix b/nixos/tests/opensmtpd.nix index e6f52db1d9843..17c1a569ba0d9 100644 --- a/nixos/tests/opensmtpd.nix +++ b/nixos/tests/opensmtpd.nix @@ -121,5 +121,5 @@ import ./make-test-python.nix { client.succeed("check-mail-landed >&2") ''; - meta.timeout = 30; + meta.timeout = 1800; } diff --git a/nixos/tests/openstack-image.nix b/nixos/tests/openstack-image.nix index d0225016ab762..97c9137fe1d67 100644 --- a/nixos/tests/openstack-image.nix +++ b/nixos/tests/openstack-image.nix @@ -16,8 +16,14 @@ let ../maintainers/scripts/openstack/openstack-image.nix ../modules/testing/test-instrumentation.nix ../modules/profiles/qemu-guest.nix + { + # Needed by nixos-rebuild due to lack of network access. + system.extraDependencies = with pkgs; [ + stdenv + ]; + } ]; - }).config.system.build.openstackImage; + }).config.system.build.openstackImage + "/nixos.qcow2"; sshKeys = import ./ssh-keys.nix pkgs; snakeOilPrivateKey = sshKeys.snakeOilPrivateKey.text; diff --git a/nixos/tests/orangefs.nix b/nixos/tests/orangefs.nix index bdf4fc10c4475..24b7737058c83 100644 --- a/nixos/tests/orangefs.nix +++ b/nixos/tests/orangefs.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ ... } : +import ./make-test-python.nix ({ ... } : let server = { pkgs, ... } : { @@ -10,11 +10,11 @@ let virtualisation.emptyDiskImages = [ 4096 ]; fileSystems = pkgs.lib.mkVMOverride - [ { mountPoint = "/data"; - device = "/dev/disk/by-label/data"; - fsType = "ext4"; - } - ]; + { "/data" = + { device = "/dev/disk/by-label/data"; + fsType = "ext4"; + }; + }; services.orangefs.server = { enable = true; @@ -52,37 +52,31 @@ in { testScript = '' # format storage - foreach my $server (($server1,$server2)) - { - $server->start(); - $server->waitForUnit("multi-user.target"); - $server->succeed("mkdir -p /data/storage /data/meta"); - $server->succeed("chown orangefs:orangefs /data/storage /data/meta"); - $server->succeed("chmod 0770 /data/storage /data/meta"); - $server->succeed("sudo -g orangefs -u orangefs pvfs2-server -f /etc/orangefs/server.conf"); - } + for server in server1, server2: + server.start() + server.wait_for_unit("multi-user.target") + server.succeed("mkdir -p /data/storage /data/meta") + server.succeed("chown orangefs:orangefs /data/storage /data/meta") + server.succeed("chmod 0770 /data/storage /data/meta") + server.succeed( + "sudo -g orangefs -u orangefs pvfs2-server -f /etc/orangefs/server.conf" + ) # start services after storage is formated on all machines - foreach my $server (($server1,$server2)) - { - $server->succeed("systemctl start orangefs-server.service"); - } - - # Check if clients can reach and mount the FS - foreach my $client (($client1,$client2)) - { - $client->start(); - $client->waitForUnit("orangefs-client.service"); - # Both servers need to be reachable - $client->succeed("pvfs2-check-server -h server1 -f orangefs -n tcp -p 3334"); - $client->succeed("pvfs2-check-server -h server2 -f orangefs -n tcp -p 3334"); - $client->waitForUnit("orangefs.mount"); - - } + for server in server1, server2: + server.succeed("systemctl start orangefs-server.service") - # R/W test between clients - $client1->succeed("echo test > /orangefs/file1"); - $client2->succeed("grep test /orangefs/file1"); + with subtest("clients can reach and mount the FS"): + for client in client1, client2: + client.start() + client.wait_for_unit("orangefs-client.service") + # Both servers need to be reachable + client.succeed("pvfs2-check-server -h server1 -f orangefs -n tcp -p 3334") + client.succeed("pvfs2-check-server -h server2 -f orangefs -n tcp -p 3334") + client.wait_for_unit("orangefs.mount") + with subtest("R/W test between clients"): + client1.succeed("echo test > /orangefs/file1") + client2.succeed("grep test /orangefs/file1") ''; }) diff --git a/nixos/tests/os-prober.nix b/nixos/tests/os-prober.nix index 5407a62339fe6..6a38f5ca531c5 100644 --- a/nixos/tests/os-prober.nix +++ b/nixos/tests/os-prober.nix @@ -51,6 +51,8 @@ let hashed-mirrors = connect-timeout = 1 ''; + # save some memory + documentation.enable = false; }; # /etc/nixos/configuration.nix for the vm configFile = pkgs.writeText "configuration.nix" '' diff --git a/nixos/tests/osrm-backend.nix b/nixos/tests/osrm-backend.nix index 6e2d098d4adbc..db67a5a589f9d 100644 --- a/nixos/tests/osrm-backend.nix +++ b/nixos/tests/osrm-backend.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, lib, ... }: +import ./make-test-python.nix ({ pkgs, lib, ... }: let port = 5000; in { @@ -45,9 +45,13 @@ in { testScript = let query = "http://localhost:${toString port}/route/v1/driving/7.41720,43.73304;7.42463,43.73886?steps=true"; in '' - $machine->waitForUnit("osrm.service"); - $machine->waitForOpenPort(${toString port}); - $machine->succeed("curl --silent '${query}' | jq .waypoints[0].name | grep -F 'Boulevard Rainier III'"); - $machine->succeed("curl --silent '${query}' | jq .waypoints[1].name | grep -F 'Avenue de la Costa'"); + machine.wait_for_unit("osrm.service") + machine.wait_for_open_port(${toString port}) + assert "Boulevard Rainier III" in machine.succeed( + "curl --silent '${query}' | jq .waypoints[0].name" + ) + assert "Avenue de la Costa" in machine.succeed( + "curl --silent '${query}' | jq .waypoints[1].name" + ) ''; }) diff --git a/nixos/tests/overlayfs.nix b/nixos/tests/overlayfs.nix index 99bb6b0f55312..33794deb9ed83 100644 --- a/nixos/tests/overlayfs.nix +++ b/nixos/tests/overlayfs.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, ... }: { +import ./make-test-python.nix ({ pkgs, ... }: { name = "overlayfs"; meta.maintainers = with pkgs.stdenv.lib.maintainers; [ bachp ]; @@ -9,49 +9,42 @@ import ./make-test.nix ({ pkgs, ... }: { }; testScript = '' - $machine->succeed("ls /dev"); + machine.succeed("ls /dev") - $machine->succeed("mkdir -p /tmp/mnt"); + machine.succeed("mkdir -p /tmp/mnt") # Test ext4 + overlayfs - $machine->succeed( - - "mkfs.ext4 -F -L overlay-ext4 /dev/vdb", - "mount -t ext4 /dev/vdb /tmp/mnt", - - "mkdir -p /tmp/mnt/upper /tmp/mnt/lower /tmp/mnt/work /tmp/mnt/merged", - - # Setup some existing files - "echo 'Replace' > /tmp/mnt/lower/replace.txt", - "echo 'Append' > /tmp/mnt/lower/append.txt", - "echo 'Overwrite' > /tmp/mnt/lower/overwrite.txt", - - "mount -t overlay overlay -o lowerdir=/tmp/mnt/lower,upperdir=/tmp/mnt/upper,workdir=/tmp/mnt/work /tmp/mnt/merged", - - # Test new - "echo 'New' > /tmp/mnt/merged/new.txt", - "[[ \"\$(cat /tmp/mnt/merged/new.txt)\" == \"New\" ]]", - - # Test replace - "[[ \"\$(cat /tmp/mnt/merged/replace.txt)\" == \"Replace\" ]]", - "echo 'Replaced' > /tmp/mnt/merged/replace-tmp.txt", - "mv /tmp/mnt/merged/replace-tmp.txt /tmp/mnt/merged/replace.txt", - "[[ \"\$(cat /tmp/mnt/merged/replace.txt)\" == \"Replaced\" ]]", - - # Overwrite - "[[ \"\$(cat /tmp/mnt/merged/overwrite.txt)\" == \"Overwrite\" ]]", - "echo 'Overwritten' > /tmp/mnt/merged/overwrite.txt", - "[[ \"\$(cat /tmp/mnt/merged/overwrite.txt)\" == \"Overwritten\" ]]", - - # Test append - "[[ \"\$(cat /tmp/mnt/merged/append.txt)\" == \"Append\" ]]", - "echo 'ed' >> /tmp/mnt/merged/append.txt", - #"cat /tmp/mnt/merged/append.txt && exit 1", - "[[ \"\$(cat /tmp/mnt/merged/append.txt)\" == \"Append\ned\" ]]", - - "umount /tmp/mnt/merged", - "umount /tmp/mnt", - "udevadm settle" - ); + machine.succeed( + """ + mkfs.ext4 -F -L overlay-ext4 /dev/vdb + mount -t ext4 /dev/vdb /tmp/mnt + mkdir -p /tmp/mnt/upper /tmp/mnt/lower /tmp/mnt/work /tmp/mnt/merged + # Setup some existing files + echo 'Replace' > /tmp/mnt/lower/replace.txt + echo 'Append' > /tmp/mnt/lower/append.txt + echo 'Overwrite' > /tmp/mnt/lower/overwrite.txt + mount -t overlay overlay -o lowerdir=/tmp/mnt/lower,upperdir=/tmp/mnt/upper,workdir=/tmp/mnt/work /tmp/mnt/merged + # Test new + echo 'New' > /tmp/mnt/merged/new.txt + [[ "\$(cat /tmp/mnt/merged/new.txt)" == "New" ]] + # Test replace + [[ "\$(cat /tmp/mnt/merged/replace.txt)" == "Replace" ]] + echo 'Replaced' > /tmp/mnt/merged/replace-tmp.txt + mv /tmp/mnt/merged/replace-tmp.txt /tmp/mnt/merged/replace.txt + [[ "\$(cat /tmp/mnt/merged/replace.txt)" == "Replaced" ]] + # Overwrite + [[ "\$(cat /tmp/mnt/merged/overwrite.txt)" == "Overwrite" ]] + echo 'Overwritten' > /tmp/mnt/merged/overwrite.txt + [[ "\$(cat /tmp/mnt/merged/overwrite.txt)" == "Overwritten" ]] + # Test append + [[ "\$(cat /tmp/mnt/merged/append.txt)" == "Append" ]] + echo 'ed' >> /tmp/mnt/merged/append.txt + #"cat /tmp/mnt/merged/append.txt && exit 1 + [[ "\$(cat /tmp/mnt/merged/append.txt)" == "Append\ned" ]] + umount /tmp/mnt/merged + umount /tmp/mnt + udevadm settle + """ + ) ''; }) diff --git a/nixos/tests/pam-oath-login.nix b/nixos/tests/pam-oath-login.nix index b9d489950e722..6d48199eda97f 100644 --- a/nixos/tests/pam-oath-login.nix +++ b/nixos/tests/pam-oath-login.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ ... }: +import ./make-test-python.nix ({ ... }: let oathSnakeoilSecret = "cdd4083ef8ff1fa9178c6d46bfb1a3"; @@ -55,70 +55,54 @@ in }; }; - testScript = - '' - $machine->waitForUnit('multi-user.target'); - $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty1'"); - $machine->screenshot("postboot"); - - - subtest "Invalid password", sub { - $machine->fail("pgrep -f 'agetty.*tty2'"); - $machine->sendKeys("alt-f2"); - $machine->waitUntilSucceeds("[ \$(fgconsole) = 2 ]"); - $machine->waitForUnit('getty@tty2.service'); - $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty2'"); - - $machine->waitUntilTTYMatches(2, "login: "); - $machine->sendChars("alice\n"); - $machine->waitUntilTTYMatches(2, "login: alice"); - $machine->waitUntilSucceeds("pgrep login"); - - $machine->waitUntilTTYMatches(2, "One-time password"); - $machine->sendChars("${oathSnakeOilPassword1}\n"); - $machine->waitUntilTTYMatches(2, "Password: "); - $machine->sendChars("blorg\n"); - $machine->waitUntilTTYMatches(2, "Login incorrect"); - }; + testScript = '' + def switch_to_tty(tty_number): + machine.fail(f"pgrep -f 'agetty.*tty{tty_number}'") + machine.send_key(f"alt-f{tty_number}") + machine.wait_until_succeeds(f"[ $(fgconsole) = {tty_number} ]") + machine.wait_for_unit(f"getty@tty{tty_number}.service") + machine.wait_until_succeeds(f"pgrep -f 'agetty.*tty{tty_number}'") - subtest "Invalid oath token", sub { - $machine->fail("pgrep -f 'agetty.*tty3'"); - $machine->sendKeys("alt-f3"); - $machine->waitUntilSucceeds("[ \$(fgconsole) = 3 ]"); - $machine->waitForUnit('getty@tty3.service'); - $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty3'"); - - $machine->waitUntilTTYMatches(3, "login: "); - $machine->sendChars("alice\n"); - $machine->waitUntilTTYMatches(3, "login: alice"); - $machine->waitUntilSucceeds("pgrep login"); - $machine->waitUntilTTYMatches(3, "One-time password"); - $machine->sendChars("000000\n"); - $machine->waitUntilTTYMatches(3, "Login incorrect"); - $machine->waitUntilTTYMatches(3, "login:"); - }; - subtest "Happy path (both passwords are mandatory to get us in)", sub { - $machine->fail("pgrep -f 'agetty.*tty4'"); - $machine->sendKeys("alt-f4"); - $machine->waitUntilSucceeds("[ \$(fgconsole) = 4 ]"); - $machine->waitForUnit('getty@tty4.service'); - $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty4'"); - - $machine->waitUntilTTYMatches(4, "login: "); - $machine->sendChars("alice\n"); - $machine->waitUntilTTYMatches(4, "login: alice"); - $machine->waitUntilSucceeds("pgrep login"); - $machine->waitUntilTTYMatches(4, "One-time password"); - $machine->sendChars("${oathSnakeOilPassword2}\n"); - $machine->waitUntilTTYMatches(4, "Password: "); - $machine->sendChars("${alicePassword}\n"); - - $machine->waitUntilSucceeds("pgrep -u alice bash"); - $machine->sendChars("touch done4\n"); - $machine->waitForFile("/home/alice/done4"); - }; + def enter_user_alice(tty_number): + machine.wait_until_tty_matches(tty_number, "login: ") + machine.send_chars("alice\n") + machine.wait_until_tty_matches(tty_number, "login: alice") + machine.wait_until_succeeds("pgrep login") + machine.wait_until_tty_matches(tty_number, "One-time password") - ''; + machine.wait_for_unit("multi-user.target") + machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'") + machine.screenshot("postboot") + + with subtest("Invalid password"): + switch_to_tty(2) + enter_user_alice(2) + + machine.send_chars("${oathSnakeOilPassword1}\n") + machine.wait_until_tty_matches(2, "Password: ") + machine.send_chars("blorg\n") + machine.wait_until_tty_matches(2, "Login incorrect") + + with subtest("Invalid oath token"): + switch_to_tty(3) + enter_user_alice(3) + + machine.send_chars("000000\n") + machine.wait_until_tty_matches(3, "Login incorrect") + machine.wait_until_tty_matches(3, "login:") + + with subtest("Happy path: Both passwords are mandatory to get us in"): + switch_to_tty(4) + enter_user_alice(4) + + machine.send_chars("${oathSnakeOilPassword2}\n") + machine.wait_until_tty_matches(4, "Password: ") + machine.send_chars("${alicePassword}\n") + + machine.wait_until_succeeds("pgrep -u alice bash") + machine.send_chars("touch done4\n") + machine.wait_for_file("/home/alice/done4") + ''; }) diff --git a/nixos/tests/pam-u2f.nix b/nixos/tests/pam-u2f.nix index 1052a2f3b916e..f492baa9e139d 100644 --- a/nixos/tests/pam-u2f.nix +++ b/nixos/tests/pam-u2f.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ ... }: +import ./make-test-python.nix ({ ... }: { name = "pam-u2f"; @@ -17,7 +17,9 @@ import ./make-test.nix ({ ... }: testScript = '' - $machine->waitForUnit('multi-user.target'); - $machine->succeed('egrep "auth required .*/lib/security/pam_u2f.so.*debug.*interactive.*cue" /etc/pam.d/ -R'); + machine.wait_for_unit("multi-user.target") + machine.succeed( + 'egrep "auth required .*/lib/security/pam_u2f.so.*debug.*interactive.*cue" /etc/pam.d/ -R' + ) ''; }) diff --git a/nixos/tests/paperless.nix b/nixos/tests/paperless.nix index 860ad0a6218fd..355e7041d3fe2 100644 --- a/nixos/tests/paperless.nix +++ b/nixos/tests/paperless.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ lib, ... } : { +import ./make-test-python.nix ({ lib, ... } : { name = "paperless"; meta = with lib.maintainers; { maintainers = [ earvstedt ]; @@ -13,17 +13,24 @@ import ./make-test.nix ({ lib, ... } : { }; testScript = '' - $machine->waitForUnit("paperless-consumer.service"); + machine.wait_for_unit("paperless-consumer.service") + # Create test doc - $machine->succeed('convert -size 400x40 xc:white -font "DejaVu-Sans" -pointsize 20 -fill black \ - -annotate +5+20 "hello world 16-10-2005" /var/lib/paperless/consume/doc.png'); + machine.succeed( + "convert -size 400x40 xc:white -font 'DejaVu-Sans' -pointsize 20 -fill black -annotate +5+20 'hello world 16-10-2005' /var/lib/paperless/consume/doc.png" + ) + + with subtest("Service gets ready"): + machine.wait_for_unit("paperless-server.service") + # Wait until server accepts connections + machine.wait_until_succeeds("curl -s localhost:28981") - $machine->waitForUnit("paperless-server.service"); - # Wait until server accepts connections - $machine->waitUntilSucceeds("curl -s localhost:28981"); - # Wait until document is consumed - $machine->waitUntilSucceeds('(($(curl -s localhost:28981/api/documents/ | jq .count) == 1))'); - $machine->succeed("curl -s localhost:28981/api/documents/ | jq '.results | .[0] | .created'") - =~ /2005-10-16/ or die; + with subtest("Test document is consumed"): + machine.wait_until_succeeds( + "(($(curl -s localhost:28981/api/documents/ | jq .count) == 1))" + ) + assert "2005-10-16" in machine.succeed( + "curl -s localhost:28981/api/documents/ | jq '.results | .[0] | .created'" + ) ''; }) diff --git a/nixos/tests/pdns-recursor.nix b/nixos/tests/pdns-recursor.nix index bf6e6093d69c5..de1b60e0b1c78 100644 --- a/nixos/tests/pdns-recursor.nix +++ b/nixos/tests/pdns-recursor.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, ... }: { +import ./make-test-python.nix ({ pkgs, ... }: { name = "powerdns"; nodes.server = { ... }: { @@ -6,7 +6,7 @@ import ./make-test.nix ({ pkgs, ... }: { }; testScript = '' - $server->waitForUnit("pdns-recursor"); - $server->waitForOpenPort("53"); + server.wait_for_unit("pdns-recursor") + server.wait_for_open_port("53") ''; }) diff --git a/nixos/tests/peerflix.nix b/nixos/tests/peerflix.nix index fae37fedaac78..37628604d49b5 100644 --- a/nixos/tests/peerflix.nix +++ b/nixos/tests/peerflix.nix @@ -1,6 +1,6 @@ # This test runs peerflix and checks if peerflix starts -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, ...} : { name = "peerflix"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ offline ]; @@ -15,9 +15,9 @@ import ./make-test.nix ({ pkgs, ...} : { }; testScript = '' - startAll; + start_all() - $peerflix->waitForUnit("peerflix.service"); - $peerflix->waitUntilSucceeds("curl localhost:9000"); + peerflix.wait_for_unit("peerflix.service") + peerflix.wait_until_succeeds("curl localhost:9000") ''; }) diff --git a/nixos/tests/pgmanage.nix b/nixos/tests/pgmanage.nix index bacaf3f415884..4f5dbed24a97f 100644 --- a/nixos/tests/pgmanage.nix +++ b/nixos/tests/pgmanage.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, ... } : +import ./make-test-python.nix ({ pkgs, ... } : let role = "test"; password = "secret"; @@ -29,11 +29,13 @@ in }; testScript = '' - startAll; - $one->waitForUnit("default.target"); - $one->requireActiveUnit("pgmanage.service"); + start_all() + one.wait_for_unit("default.target") + one.require_unit_state("pgmanage.service", "active") # Test if we can log in. - $one->waitUntilSucceeds("curl 'http://localhost:8080/pgmanage/auth' --data 'action=login&connname=${conn}&username=${role}&password=${password}' --fail"); + one.wait_until_succeeds( + "curl 'http://localhost:8080/pgmanage/auth' --data 'action=login&connname=${conn}&username=${role}&password=${password}' --fail" + ) ''; }) diff --git a/nixos/tests/php/default.nix b/nixos/tests/php/default.nix new file mode 100644 index 0000000000000..ee7a3b56a3ef0 --- /dev/null +++ b/nixos/tests/php/default.nix @@ -0,0 +1,8 @@ +{ system ? builtins.currentSystem, + config ? {}, + pkgs ? import ../../.. { inherit system config; } +}: { + fpm = import ./fpm.nix { inherit system pkgs; }; + httpd = import ./httpd.nix { inherit system pkgs; }; + pcre = import ./pcre.nix { inherit system pkgs; }; +} diff --git a/nixos/tests/php/fpm.nix b/nixos/tests/php/fpm.nix new file mode 100644 index 0000000000000..1e4ced24b6c39 --- /dev/null +++ b/nixos/tests/php/fpm.nix @@ -0,0 +1,53 @@ +import ../make-test-python.nix ({pkgs, ...}: { + name = "php-fpm-nginx-test"; + meta.maintainers = with pkgs.stdenv.lib.maintainers; [ etu ]; + + machine = { config, lib, pkgs, ... }: { + services.nginx = { + enable = true; + + virtualHosts."phpfpm" = let + testdir = pkgs.writeTextDir "web/index.php" "<?php phpinfo();"; + in { + root = "${testdir}/web"; + locations."~ \.php$".extraConfig = '' + fastcgi_pass unix:${config.services.phpfpm.pools.foobar.socket}; + fastcgi_index index.php; + include ${pkgs.nginx}/conf/fastcgi_params; + include ${pkgs.nginx}/conf/fastcgi.conf; + ''; + locations."/" = { + tryFiles = "$uri $uri/ index.php"; + index = "index.php index.html index.htm"; + }; + }; + }; + + services.phpfpm.pools."foobar" = { + user = "nginx"; + settings = { + "listen.group" = "nginx"; + "listen.mode" = "0600"; + "listen.owner" = "nginx"; + "pm" = "dynamic"; + "pm.max_children" = 5; + "pm.max_requests" = 500; + "pm.max_spare_servers" = 3; + "pm.min_spare_servers" = 1; + "pm.start_servers" = 2; + }; + }; + }; + testScript = { ... }: '' + machine.wait_for_unit("nginx.service") + machine.wait_for_unit("phpfpm-foobar.service") + + # Check so we get an evaluated PHP back + response = machine.succeed("curl -vvv -s http://127.0.0.1:80/") + assert "PHP Version ${pkgs.php.version}" in response, "PHP version not detected" + + # Check so we have database and some other extensions loaded + for ext in ["json", "opcache", "pdo_mysql", "pdo_pgsql", "pdo_sqlite"]: + assert ext in response, f"Missing {ext} extension" + ''; +}) diff --git a/nixos/tests/php/httpd.nix b/nixos/tests/php/httpd.nix new file mode 100644 index 0000000000000..fc3ff986734e9 --- /dev/null +++ b/nixos/tests/php/httpd.nix @@ -0,0 +1,31 @@ +import ../make-test-python.nix ({pkgs, ...}: { + name = "php-httpd-test"; + meta.maintainers = with pkgs.stdenv.lib.maintainers; [ etu ]; + + machine = { config, lib, pkgs, ... }: { + services.httpd = { + enable = true; + adminAddr = "admin@phpfpm"; + virtualHosts."phpfpm" = let + testdir = pkgs.writeTextDir "web/index.php" "<?php phpinfo();"; + in { + documentRoot = "${testdir}/web"; + locations."/" = { + index = "index.php index.html"; + }; + }; + enablePHP = true; + }; + }; + testScript = { ... }: '' + machine.wait_for_unit("httpd.service") + + # Check so we get an evaluated PHP back + response = machine.succeed("curl -vvv -s http://127.0.0.1:80/") + assert "PHP Version ${pkgs.php.version}" in response, "PHP version not detected" + + # Check so we have database and some other extensions loaded + for ext in ["json", "opcache", "pdo_mysql", "pdo_pgsql", "pdo_sqlite"]: + assert ext in response, f"Missing {ext} extension" + ''; +}) diff --git a/nixos/tests/php-pcre.nix b/nixos/tests/php/pcre.nix index ae44aec7944f4..016dca81d522f 100644 --- a/nixos/tests/php-pcre.nix +++ b/nixos/tests/php/pcre.nix @@ -1,7 +1,6 @@ - -let testString = "can-use-subgroups"; in - -import ./make-test.nix ({ ...}: { +let + testString = "can-use-subgroups"; +in import ../make-test-python.nix ({ ...}: { name = "php-httpd-pcre-jit-test"; machine = { lib, pkgs, ... }: { time.timeZone = "UTC"; @@ -10,15 +9,13 @@ import ./make-test.nix ({ ...}: { adminAddr = "please@dont.contact"; enablePHP = true; phpOptions = "pcre.jit = true"; - extraConfig = - let + extraConfig = let testRoot = pkgs.writeText "index.php" - '' - <?php + '' + <?php preg_match('/(${testString})/', '${testString}', $result); var_dump($result); - ?> - ''; + ''; in '' Alias / ${testRoot}/ @@ -30,10 +27,11 @@ import ./make-test.nix ({ ...}: { }; }; testScript = { ... }: - '' - $machine->waitForUnit('httpd.service'); - # Ensure php evaluation by matching on the var_dump syntax - $machine->succeed('curl -vvv -s http://127.0.0.1:80/index.php \ - | grep "string(${toString (builtins.stringLength testString)}) \"${testString}\""'); - ''; + '' + machine.wait_for_unit("httpd.service") + # Ensure php evaluation by matching on the var_dump syntax + response = machine.succeed("curl -vvv -s http://127.0.0.1:80/index.php") + expected = 'string(${toString (builtins.stringLength testString)}) "${testString}"' + assert expected in response, "Does not appear to be able to use subgroups." + ''; }) diff --git a/nixos/tests/plasma5.nix b/nixos/tests/plasma5.nix index 6884f17aabbe8..2eccfdf47f590 100644 --- a/nixos/tests/plasma5.nix +++ b/nixos/tests/plasma5.nix @@ -12,8 +12,8 @@ import ./make-test-python.nix ({ pkgs, ...} : imports = [ ./common/user-account.nix ]; services.xserver.enable = true; services.xserver.displayManager.sddm.enable = true; + services.xserver.displayManager.defaultSession = "plasma5"; services.xserver.desktopManager.plasma5.enable = true; - services.xserver.desktopManager.default = "plasma5"; services.xserver.displayManager.sddm.autoLogin = { enable = true; user = "alice"; diff --git a/nixos/tests/plotinus.nix b/nixos/tests/plotinus.nix index 609afe7b2145f..39a4234dbf73a 100644 --- a/nixos/tests/plotinus.nix +++ b/nixos/tests/plotinus.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, ... }: { +import ./make-test-python.nix ({ pkgs, ... }: { name = "plotinus"; meta = { maintainers = pkgs.plotinus.meta.maintainers; @@ -12,16 +12,17 @@ import ./make-test.nix ({ pkgs, ... }: { environment.systemPackages = [ pkgs.gnome3.gnome-calculator pkgs.xdotool ]; }; - testScript = - '' - $machine->waitForX; - $machine->succeed("gnome-calculator &"); - $machine->waitForWindow(qr/gnome-calculator/); - $machine->succeed("xdotool search --sync --onlyvisible --class gnome-calculator windowfocus --sync key ctrl+shift+p"); - $machine->sleep(5); # wait for the popup - $machine->succeed("xdotool key --delay 100 p r e f e r e n c e s Return"); - $machine->waitForWindow(qr/Preferences/); - $machine->screenshot("screen"); - ''; - + testScript = '' + machine.wait_for_x() + machine.succeed("gnome-calculator &") + machine.wait_for_window("gnome-calculator") + machine.succeed( + "xdotool search --sync --onlyvisible --class gnome-calculator " + + "windowfocus --sync key --clearmodifiers --delay 1 'ctrl+shift+p'" + ) + machine.sleep(5) # wait for the popup + machine.succeed("xdotool key --delay 100 p r e f e r e n c e s Return") + machine.wait_for_window("Preferences") + machine.screenshot("screen") + ''; }) diff --git a/nixos/tests/podman.nix b/nixos/tests/podman.nix new file mode 100644 index 0000000000000..283db71d9a498 --- /dev/null +++ b/nixos/tests/podman.nix @@ -0,0 +1,60 @@ +# This test runs podman and checks if simple container starts + +import ./make-test-python.nix ( + { pkgs, lib, ... }: { + name = "podman"; + meta = { + maintainers = lib.teams.podman.members; + }; + + nodes = { + podman = + { pkgs, ... }: + { + virtualisation.podman.enable = true; + virtualisation.containers.users = [ + "alice" + ]; + + users.users.alice = { + isNormalUser = true; + home = "/home/alice"; + description = "Alice Foobar"; + }; + + }; + }; + + testScript = '' + import shlex + + + def su_cmd(cmd): + cmd = shlex.quote(cmd) + return f"su alice -l -c {cmd}" + + + podman.wait_for_unit("sockets.target") + start_all() + + + with subtest("Run container as root"): + podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg") + podman.succeed( + "podman run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10" + ) + podman.succeed("podman ps | grep sleeping") + podman.succeed("podman stop sleeping") + + with subtest("Run container rootless"): + podman.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg")) + podman.succeed( + su_cmd( + "podman run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10" + ) + ) + podman.succeed(su_cmd("podman ps | grep sleeping")) + podman.succeed(su_cmd("podman stop sleeping")) + ''; + } +) diff --git a/nixos/tests/postgis.nix b/nixos/tests/postgis.nix index 294eb50b5fe50..84bbb0bc8ec63 100644 --- a/nixos/tests/postgis.nix +++ b/nixos/tests/postgis.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, ...} : { name = "postgis"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ lsix ]; @@ -20,10 +20,10 @@ import ./make-test.nix ({ pkgs, ...} : { }; testScript = '' - startAll; - $master->waitForUnit("postgresql"); - $master->sleep(10); # Hopefully this is long enough!! - $master->succeed("sudo -u postgres psql -c 'CREATE EXTENSION postgis;'"); - $master->succeed("sudo -u postgres psql -c 'CREATE EXTENSION postgis_topology;'"); + start_all() + master.wait_for_unit("postgresql") + master.sleep(10) # Hopefully this is long enough!! + master.succeed("sudo -u postgres psql -c 'CREATE EXTENSION postgis;'") + master.succeed("sudo -u postgres psql -c 'CREATE EXTENSION postgis_topology;'") ''; }) diff --git a/nixos/tests/postgresql-wal-receiver.nix b/nixos/tests/postgresql-wal-receiver.nix index 791b041ba95bd..372dd9d8c1c11 100644 --- a/nixos/tests/postgresql-wal-receiver.nix +++ b/nixos/tests/postgresql-wal-receiver.nix @@ -6,17 +6,24 @@ with import ../lib/testing.nix { inherit system pkgs; }; with pkgs.lib; let + makePostgresqlWalReceiverTest = subTestName: postgresqlPackage: let + postgresqlDataDir = "/var/db/postgresql/test"; replicationUser = "wal_receiver_user"; replicationSlot = "wal_receiver_slot"; replicationConn = "postgresql://${replicationUser}@localhost"; baseBackupDir = "/tmp/pg_basebackup"; walBackupDir = "/tmp/pg_wal"; - recoveryConf = pkgs.writeText "recovery.conf" '' + atLeast12 = versionAtLeast postgresqlPackage.version "12.0"; + restoreCommand = '' restore_command = 'cp ${walBackupDir}/%f %p' ''; - makePostgresqlWalReceiverTest = subTestName: postgresqlPackage: makeTest { + recoveryFile = if atLeast12 + then pkgs.writeTextDir "recovery.signal" "" + else pkgs.writeTextDir "recovery.conf" "${restoreCommand}"; + + in makeTest { name = "postgresql-wal-receiver-${subTestName}"; meta.maintainers = with maintainers; [ pacien ]; @@ -29,6 +36,9 @@ let wal_level = archive # alias for replica on pg >= 9.6 max_wal_senders = 10 max_replication_slots = 10 + '' + optionalString atLeast12 '' + ${restoreCommand} + recovery_end_command = 'touch recovery.done' ''; authentication = '' host replication ${replicationUser} all trust @@ -45,6 +55,9 @@ let slot = replicationSlot; directory = walBackupDir; }; + # This is only to speedup test, it isn't time racing. Service is set to autorestart always, + # default 60sec is fine for real system, but is too much for a test + systemd.services.postgresql-wal-receiver-main.serviceConfig.RestartSec = mkForce 5; }; testScript = '' @@ -70,7 +83,7 @@ let # prepare WAL and recovery $machine->succeed('chmod a+rX -R ${walBackupDir}'); $machine->execute('for part in ${walBackupDir}/*.partial; do mv $part ''${part%%.*}; done'); # make use of partial segments too - $machine->succeed('cp ${recoveryConf} ${postgresqlDataDir}/recovery.conf && chmod 666 ${postgresqlDataDir}/recovery.conf'); + $machine->succeed('cp ${recoveryFile}/* ${postgresqlDataDir}/ && chmod 666 ${postgresqlDataDir}/recovery*'); # replay WAL $machine->systemctl('start postgresql'); diff --git a/nixos/tests/postgresql.nix b/nixos/tests/postgresql.nix index e71c38882881f..3201e22555eab 100644 --- a/nixos/tests/postgresql.nix +++ b/nixos/tests/postgresql.nix @@ -29,11 +29,15 @@ let machine = {...}: { - services.postgresql.enable = true; - services.postgresql.package = postgresql-package; + services.postgresql = { + enable = true; + package = postgresql-package; + }; - services.postgresqlBackup.enable = true; - services.postgresqlBackup.databases = optional (!backup-all) "postgres"; + services.postgresqlBackup = { + enable = true; + databases = optional (!backup-all) "postgres"; + }; }; testScript = let @@ -49,23 +53,32 @@ let machine.start() machine.wait_for_unit("postgresql") - # postgresql should be available just after unit start - machine.succeed( - "cat ${test-sql} | sudo -u postgres psql" - ) - machine.shutdown() # make sure that postgresql survive restart (bug #1735) - time.sleep(2) - machine.start() - machine.wait_for_unit("postgresql") + with subtest("Postgresql is available just after unit start"): + machine.succeed( + "cat ${test-sql} | sudo -u postgres psql" + ) + + with subtest("Postgresql survives restart (bug #1735)"): + machine.shutdown() + time.sleep(2) + machine.start() + machine.wait_for_unit("postgresql") + machine.fail(check_count("SELECT * FROM sth;", 3)) machine.succeed(check_count("SELECT * FROM sth;", 5)) machine.fail(check_count("SELECT * FROM sth;", 4)) machine.succeed(check_count("SELECT xpath('/test/text()', doc) FROM xmltest;", 1)) - # Check backup service - machine.succeed("systemctl start ${backupService}.service") - machine.succeed("zcat /var/backup/postgresql/${backupName}.sql.gz | grep '<test>ok</test>'") - machine.succeed("stat -c '%a' /var/backup/postgresql/${backupName}.sql.gz | grep 600") + with subtest("Backup service works"): + machine.succeed( + "systemctl start ${backupService}.service", + "zcat /var/backup/postgresql/${backupName}.sql.gz | grep '<test>ok</test>'", + "stat -c '%a' /var/backup/postgresql/${backupName}.sql.gz | grep 600", + ) + + with subtest("Initdb works"): + machine.succeed("sudo -u postgres initdb -D /tmp/testpostgres2") + machine.shutdown() ''; diff --git a/nixos/tests/predictable-interface-names.nix b/nixos/tests/predictable-interface-names.nix index 194b4dafa772e..bab091d57acf1 100644 --- a/nixos/tests/predictable-interface-names.nix +++ b/nixos/tests/predictable-interface-names.nix @@ -4,7 +4,7 @@ }: let - inherit (import ../lib/testing.nix { inherit system pkgs; }) makeTest; + inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest; in pkgs.lib.listToAttrs (pkgs.lib.crossLists (predictable: withNetworkd: { name = pkgs.lib.optionalString (!predictable) "un" + "predictable" + pkgs.lib.optionalString withNetworkd "Networkd"; @@ -17,11 +17,17 @@ in pkgs.lib.listToAttrs (pkgs.lib.crossLists (predictable: withNetworkd: { networking.useNetworkd = withNetworkd; networking.dhcpcd.enable = !withNetworkd; networking.useDHCP = !withNetworkd; + + # Check if predictable interface names are working in stage-1 + boot.initrd.postDeviceCommands = '' + ip link + ip link show eth0 ${if predictable then "&&" else "||"} exit 1 + ''; }; testScript = '' - print $machine->succeed("ip link"); - $machine->${if predictable then "fail" else "succeed"}("ip link show eth0 "); + print(machine.succeed("ip link")) + machine.${if predictable then "fail" else "succeed"}("ip link show eth0") ''; }; }) [[true false] [true false]]) diff --git a/nixos/tests/printing.nix b/nixos/tests/printing.nix index 4d0df289cf75c..355c94a038616 100644 --- a/nixos/tests/printing.nix +++ b/nixos/tests/printing.nix @@ -1,19 +1,18 @@ # Test printing via CUPS. -import ./make-test.nix ({pkgs, ... }: +import ./make-test-python.nix ({pkgs, ... }: let printingServer = startWhenNeeded: { services.printing.enable = true; services.printing.startWhenNeeded = startWhenNeeded; services.printing.listenAddresses = [ "*:631" ]; services.printing.defaultShared = true; - services.printing.extraConf = - '' - <Location /> - Order allow,deny - Allow from all - </Location> - ''; + services.printing.extraConf = '' + <Location /> + Order allow,deny + Allow from all + </Location> + ''; networking.firewall.allowedTCPPorts = [ 631 ]; # Add a HP Deskjet printer connected via USB to the server. hardware.printers.ensurePrinters = [{ @@ -34,9 +33,7 @@ let hardware.printers.ensureDefaultPrinter = "DeskjetRemote"; }; -in - -{ +in { name = "printing"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ domenkozar eelco matthewbauer ]; @@ -50,65 +47,91 @@ in serviceClient = { ... }: (printingClient false); }; - testScript = - '' - startAll; - - # Make sure that cups is up on both sides. - $serviceServer->waitForUnit("cups.service"); - $serviceClient->waitForUnit("cups.service"); - # wait until cups is fully initialized and ensure-printers has executed with 10s delay - $serviceClient->sleep(20); - $socketActivatedClient->waitUntilSucceeds("systemctl status ensure-printers | grep -q -E 'code=exited, status=0/SUCCESS'"); - sub testPrinting { - my ($client, $server) = (@_); - my $clientHostname = $client->name(); - my $serverHostname = $server->name(); - $client->succeed("lpstat -r") =~ /scheduler is running/ or die; - # Test that UNIX socket is used for connections. - $client->succeed("lpstat -H") =~ "/var/run/cups/cups.sock" or die; - # Test that HTTP server is available too. - $client->succeed("curl --fail http://localhost:631/"); - $client->succeed("curl --fail http://$serverHostname:631/"); - $server->fail("curl --fail --connect-timeout 2 http://$clientHostname:631/"); - # Do some status checks. - $client->succeed("lpstat -a") =~ /DeskjetRemote accepting requests/ or die; - $client->succeed("lpstat -h $serverHostname:631 -a") =~ /DeskjetLocal accepting requests/ or die; - $client->succeed("cupsdisable DeskjetRemote"); - $client->succeed("lpq") =~ /DeskjetRemote is not ready.*no entries/s or die; - $client->succeed("cupsenable DeskjetRemote"); - $client->succeed("lpq") =~ /DeskjetRemote is ready.*no entries/s or die; - # Test printing various file types. - foreach my $file ("${pkgs.groff.doc}/share/doc/*/examples/mom/penguin.pdf", - "${pkgs.groff.doc}/share/doc/*/meref.ps", - "${pkgs.cups.out}/share/doc/cups/images/cups.png", - "${pkgs.pcre.doc}/share/doc/pcre/pcre.txt") - { - $file =~ /([^\/]*)$/; my $fn = $1; - subtest "print $fn", sub { - # Print the file on the client. - $client->succeed("lp $file"); - $client->waitUntilSucceeds("lpq | grep -q -E 'active.*root.*$fn'"); - # Ensure that a raw PCL file appeared in the server's queue - # (showing that the right filters have been applied). Of - # course, since there is no actual USB printer attached, the - # file will stay in the queue forever. - $server->waitForFile("/var/spool/cups/d*-001"); - $server->waitUntilSucceeds("lpq -a | grep -q -E '$fn'"); - # Delete the job on the client. It should disappear on the - # server as well. - $client->succeed("lprm"); - $client->waitUntilSucceeds("lpq -a | grep -q -E 'no entries'"); - Machine::retry sub { - return 1 if $server->succeed("lpq -a") =~ /no entries/; - }; - # The queue is empty already, so this should be safe. - # Otherwise, pairs of "c*"-"d*-001" files might persist. - $server->execute("rm /var/spool/cups/*"); - }; - } - } - testPrinting($serviceClient, $serviceServer); - testPrinting($socketActivatedClient, $socketActivatedServer); + testScript = '' + import os + import re + import sys + + start_all() + + with subtest("Make sure that cups is up on both sides"): + serviceServer.wait_for_unit("cups.service") + serviceClient.wait_for_unit("cups.service") + + with subtest( + "Wait until cups is fully initialized and ensure-printers has " + "executed with 10s delay" + ): + serviceClient.sleep(20) + socketActivatedClient.wait_until_succeeds( + "systemctl status ensure-printers | grep -q -E 'code=exited, status=0/SUCCESS'" + ) + + + def test_printing(client, server): + assert "scheduler is running" in client.succeed("lpstat -r") + + with subtest("UNIX socket is used for connections"): + assert "/var/run/cups/cups.sock" in client.succeed("lpstat -H") + with subtest("HTTP server is available too"): + client.succeed("curl --fail http://localhost:631/") + client.succeed(f"curl --fail http://{server.name}:631/") + server.fail(f"curl --fail --connect-timeout 2 http://{client.name}:631/") + + with subtest("LP status checks"): + assert "DeskjetRemote accepting requests" in client.succeed("lpstat -a") + assert "DeskjetLocal accepting requests" in client.succeed( + f"lpstat -h {server.name}:631 -a" + ) + client.succeed("cupsdisable DeskjetRemote") + out = client.succeed("lpq") + print(out) + assert re.search( + "DeskjetRemote is not ready.*no entries", + client.succeed("lpq"), + flags=re.DOTALL, + ) + client.succeed("cupsenable DeskjetRemote") + assert re.match( + "DeskjetRemote is ready.*no entries", client.succeed("lpq"), flags=re.DOTALL + ) + + # Test printing various file types. + for file in [ + "${pkgs.groff.doc}/share/doc/*/examples/mom/penguin.pdf", + "${pkgs.groff.doc}/share/doc/*/meref.ps", + "${pkgs.cups.out}/share/doc/cups/images/cups.png", + "${pkgs.pcre.doc}/share/doc/pcre/pcre.txt", + ]: + file_name = os.path.basename(file) + with subtest(f"print {file_name}"): + # Print the file on the client. + print(client.succeed("lpq")) + client.succeed(f"lp {file}") + client.wait_until_succeeds( + f"lpq; lpq | grep -q -E 'active.*root.*{file_name}'" + ) + + # Ensure that a raw PCL file appeared in the server's queue + # (showing that the right filters have been applied). Of + # course, since there is no actual USB printer attached, the + # file will stay in the queue forever. + server.wait_for_file("/var/spool/cups/d*-001") + server.wait_until_succeeds(f"lpq -a | grep -q -E '{file_name}'") + + # Delete the job on the client. It should disappear on the + # server as well. + client.succeed("lprm") + client.wait_until_succeeds("lpq -a | grep -q -E 'no entries'") + + retry(lambda _: "no entries" in server.succeed("lpq -a")) + + # The queue is empty already, so this should be safe. + # Otherwise, pairs of "c*"-"d*-001" files might persist. + server.execute("rm /var/spool/cups/*") + + + test_printing(serviceClient, serviceServer) + test_printing(socketActivatedClient, socketActivatedServer) ''; }) diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix index 563f247264775..4fc3668cfafb3 100644 --- a/nixos/tests/prometheus-exporters.nix +++ b/nixos/tests/prometheus-exporters.nix @@ -224,7 +224,7 @@ let after = [ "postfix.service" ]; requires = [ "postfix.service" ]; preStart = '' - mkdir -p 0600 mail-exporter/new + mkdir -p -m 0700 mail-exporter/new ''; serviceConfig = { ProtectHome = true; @@ -245,6 +245,46 @@ let ''; }; + mikrotik = { + exporterConfig = { + enable = true; + extraFlags = [ "-timeout=1s" ]; + configuration = { + devices = [ + { + name = "router"; + address = "192.168.42.48"; + user = "prometheus"; + password = "shh"; + } + ]; + features = { + bgp = true; + dhcp = true; + dhcpl = true; + dhcpv6 = true; + health = true; + routes = true; + poe = true; + pools = true; + optics = true; + w60g = true; + wlansta = true; + wlanif = true; + monitor = true; + ipsec = true; + }; + }; + }; + exporterTest = '' + wait_for_unit("prometheus-mikrotik-exporter.service") + wait_for_open_port(9436) + succeed( + "curl -sSf http://localhost:9436/metrics | grep -q 'mikrotik_scrape_collector_success{device=\"router\"} 0'" + ) + ''; + }; + nextcloud = { exporterConfig = { enable = true; @@ -287,7 +327,7 @@ let services.nginx = { enable = true; statusPage = true; - virtualHosts."/".extraConfig = "return 204;"; + virtualHosts."test".extraConfig = "return 204;"; }; }; exporterTest = '' @@ -363,6 +403,7 @@ let }; metricProvider = { services.rspamd.enable = true; + virtualisation.memorySize = 1024; }; exporterTest = '' wait_for_unit("rspamd.service") diff --git a/nixos/tests/proxy.nix b/nixos/tests/proxy.nix index 1f39e903cddb3..6a14a9af59aec 100644 --- a/nixos/tests/proxy.nix +++ b/nixos/tests/proxy.nix @@ -1,96 +1,90 @@ -import ./make-test.nix ({ pkgs, ...} : +import ./make-test-python.nix ({ pkgs, ...} : let - - backend = - { pkgs, ... }: - - { services.httpd.enable = true; - services.httpd.adminAddr = "foo@example.org"; - services.httpd.documentRoot = "${pkgs.valgrind.doc}/share/doc/valgrind/html"; - networking.firewall.allowedTCPPorts = [ 80 ]; + backend = { pkgs, ... }: { + services.httpd = { + enable = true; + adminAddr = "foo@example.org"; + virtualHosts.localhost.documentRoot = "${pkgs.valgrind.doc}/share/doc/valgrind/html"; }; - -in - -{ + networking.firewall.allowedTCPPorts = [ 80 ]; + }; +in { name = "proxy"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ eelco ]; }; - nodes = - { proxy = - { nodes, ... }: - - { services.httpd.enable = true; - services.httpd.adminAddr = "bar@example.org"; - services.httpd.extraModules = [ "proxy_balancer" "lbmethod_byrequests" ]; - - services.httpd.extraConfig = - '' - ExtendedStatus on - - <Location /server-status> - Require all granted - SetHandler server-status - </Location> - - <Proxy balancer://cluster> - Require all granted - BalancerMember http://${nodes.backend1.config.networking.hostName} retry=0 - BalancerMember http://${nodes.backend2.config.networking.hostName} retry=0 - </Proxy> - - ProxyStatus full - ProxyPass /server-status ! - ProxyPass / balancer://cluster/ - ProxyPassReverse / balancer://cluster/ - - # For testing; don't want to wait forever for dead backend servers. - ProxyTimeout 5 - ''; - - networking.firewall.allowedTCPPorts = [ 80 ]; + nodes = { + proxy = { nodes, ... }: { + services.httpd = { + enable = true; + adminAddr = "bar@example.org"; + extraModules = [ "proxy_balancer" "lbmethod_byrequests" ]; + extraConfig = '' + ExtendedStatus on + ''; + virtualHosts.localhost = { + extraConfig = '' + <Location /server-status> + Require all granted + SetHandler server-status + </Location> + + <Proxy balancer://cluster> + Require all granted + BalancerMember http://${nodes.backend1.config.networking.hostName} retry=0 + BalancerMember http://${nodes.backend2.config.networking.hostName} retry=0 + </Proxy> + + ProxyStatus full + ProxyPass /server-status ! + ProxyPass / balancer://cluster/ + ProxyPassReverse / balancer://cluster/ + + # For testing; don't want to wait forever for dead backend servers. + ProxyTimeout 5 + ''; }; - - backend1 = backend; - backend2 = backend; - - client = { ... }: { }; + }; + networking.firewall.allowedTCPPorts = [ 80 ]; }; - testScript = - '' - startAll; + backend1 = backend; + backend2 = backend; + + client = { ... }: { }; + }; - $proxy->waitForUnit("httpd"); - $backend1->waitForUnit("httpd"); - $backend2->waitForUnit("httpd"); - $client->waitForUnit("network.target"); + testScript = '' + start_all() - # With the back-ends up, the proxy should work. - $client->succeed("curl --fail http://proxy/"); + proxy.wait_for_unit("httpd") + backend1.wait_for_unit("httpd") + backend2.wait_for_unit("httpd") + client.wait_for_unit("network.target") - $client->succeed("curl --fail http://proxy/server-status"); + # With the back-ends up, the proxy should work. + client.succeed("curl --fail http://proxy/") - # Block the first back-end. - $backend1->block; + client.succeed("curl --fail http://proxy/server-status") - # The proxy should still work. - $client->succeed("curl --fail http://proxy/"); + # Block the first back-end. + backend1.block() - $client->succeed("curl --fail http://proxy/"); + # The proxy should still work. + client.succeed("curl --fail http://proxy/") + client.succeed("curl --fail http://proxy/") - # Block the second back-end. - $backend2->block; + # Block the second back-end. + backend2.block() - # Now the proxy should fail as well. - $client->fail("curl --fail http://proxy/"); + # Now the proxy should fail as well. + client.fail("curl --fail http://proxy/") - # But if the second back-end comes back, the proxy should start - # working again. - $backend2->unblock; - $client->succeed("curl --fail http://proxy/"); - ''; + # But if the second back-end comes back, the proxy should start + # working again. + backend2.unblock() + client.succeed("curl --fail http://proxy/") + ''; }) diff --git a/nixos/tests/quagga.nix b/nixos/tests/quagga.nix index 6aee7ea57f038..04590aa0eb386 100644 --- a/nixos/tests/quagga.nix +++ b/nixos/tests/quagga.nix @@ -5,7 +5,7 @@ # # All interfaces are in OSPF Area 0. -import ./make-test.nix ({ pkgs, ... }: +import ./make-test-python.nix ({ pkgs, ... }: let ifAddr = node: iface: (pkgs.lib.head node.config.networking.interfaces.${iface}.ipv4.addresses).address; @@ -74,23 +74,23 @@ import ./make-test.nix ({ pkgs, ... }: testScript = { ... }: '' - startAll; + start_all() # Wait for the networking to start on all machines - $_->waitForUnit("network.target") foreach values %vms; + for machine in client, router1, router2, server: + machine.wait_for_unit("network.target") - # Wait for OSPF to form adjacencies - for my $gw ($router1, $router2) { - $gw->waitForUnit("ospfd"); - $gw->waitUntilSucceeds("vtysh -c 'show ip ospf neighbor' | grep Full"); - $gw->waitUntilSucceeds("vtysh -c 'show ip route' | grep '^O>'"); - } + with subtest("Wait for OSPF to form adjacencies"): + for gw in router1, router2: + gw.wait_for_unit("ospfd") + gw.wait_until_succeeds("vtysh -c 'show ip ospf neighbor' | grep Full") + gw.wait_until_succeeds("vtysh -c 'show ip route' | grep '^O>'") - # Test ICMP. - $client->succeed("ping -c 3 server >&2"); + with subtest("Test ICMP"): + client.wait_until_succeeds("ping -c 3 server >&2") - # Test whether HTTP works. - $server->waitForUnit("httpd"); - $client->succeed("curl --fail http://server/ >&2"); + with subtest("Test whether HTTP works"): + server.wait_for_unit("httpd") + client.succeed("curl --fail http://server/ >&2") ''; }) diff --git a/nixos/tests/quorum.nix b/nixos/tests/quorum.nix new file mode 100644 index 0000000000000..846d2a9301889 --- /dev/null +++ b/nixos/tests/quorum.nix @@ -0,0 +1,79 @@ +import ./make-test-python.nix ({ pkgs, ... }: { + name = "quorum"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ mmahut ]; + }; + + nodes = { + machine = { ... }: { + services.quorum = { + enable = true; + permissioned = false; + staticNodes = [ "enode://dd333ec28f0a8910c92eb4d336461eea1c20803eed9cf2c056557f986e720f8e693605bba2f4e8f289b1162e5ac7c80c914c7178130711e393ca76abc1d92f57@0.0.0.0:30303?discport=0" ]; + genesis = { + alloc = { + "189d23d201b03ae1cf9113672df29a5d672aefa3" = { + balance = "0x446c3b15f9926687d2c40534fdb564000000000000"; + }; + "44b07d2c28b8ed8f02b45bd84ac7d9051b3349e6" = { + balance = "0x446c3b15f9926687d2c40534fdb564000000000000"; + }; + "4c1ccd426833b9782729a212c857f2f03b7b4c0d" = { + balance = "0x446c3b15f9926687d2c40534fdb564000000000000"; + }; + "7ae555d0f6faad7930434abdaac2274fd86ab516" = { + balance = "0x446c3b15f9926687d2c40534fdb564000000000000"; + }; + c1056df7c02b6f1a353052eaf0533cc7cb743b52 = { + balance = "0x446c3b15f9926687d2c40534fdb564000000000000"; + }; + }; + coinbase = "0x0000000000000000000000000000000000000000"; + config = { + byzantiumBlock = 1; + chainId = 10; + eip150Block = 1; + eip150Hash = + "0x0000000000000000000000000000000000000000000000000000000000000000"; + eip155Block = 1; + eip158Block = 1; + isQuorum = true; + istanbul = { + epoch = 30000; + policy = 0; + }; + }; + difficulty = "0x1"; + extraData = + "0x0000000000000000000000000000000000000000000000000000000000000000f8aff869944c1ccd426833b9782729a212c857f2f03b7b4c0d94189d23d201b03ae1cf9113672df29a5d672aefa39444b07d2c28b8ed8f02b45bd84ac7d9051b3349e694c1056df7c02b6f1a353052eaf0533cc7cb743b52947ae555d0f6faad7930434abdaac2274fd86ab516b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0"; + gasLimit = "0xe0000000"; + gasUsed = "0x0"; + mixHash = + "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365"; + nonce = "0x0"; + number = "0x0"; + parentHash = + "0x0000000000000000000000000000000000000000000000000000000000000000"; + timestamp = "0x5cffc201"; + }; + }; + }; + }; + + testScript = '' + start_all() + machine.wait_until_succeeds("mkdir -p /var/lib/quorum/keystore") + machine.wait_until_succeeds( + 'echo \{\\"address\\":\\"9377bc3936de934c497e22917b81aa8774ac3bb0\\",\\"crypto\\":\{\\"cipher\\":\\"aes-128-ctr\\",\\"ciphertext\\":\\"ad8341d8ef225650403fd366c955f41095e438dd966a3c84b3d406818c1e366c\\",\\"cipherparams\\":\{\\"iv\\":\\"2a09f7a72fd6dff7c43150ff437e6ac2\\"\},\\"kdf\\":\\"scrypt\\",\\"kdfparams\\":\{\\"dklen\\":32,\\"n\\":262144,\\"p\\":1,\\"r\\":8,\\"salt\\":\\"d1a153845bb80cd6274c87c5bac8ac09fdfac5ff131a6f41b5ed319667f12027\\"\},\\"mac\\":\\"a9621ad88fa1d042acca6fc2fcd711f7e05bfbadea3f30f379235570c8e270d3\\"\},\\"id\\":\\"89e847a3-1527-42f6-a321-77de0a14ce02\\",\\"version\\":3\}\\" > /var/lib/quorum/keystore/UTC--2020-03-23T11-08-34.144812212Z--9377bc3936de934c497e22917b81aa8774ac3bb0' + ) + machine.wait_until_succeeds( + "echo fe2725c4e8f7617764b845e8d939a65c664e7956eb47ed7d934573f16488efc1 > /var/lib/quorum/nodekey" + ) + machine.wait_until_succeeds("systemctl restart quorum") + machine.wait_for_unit("quorum.service") + machine.sleep(15) + machine.wait_until_succeeds( + 'geth attach /var/lib/quorum/geth.ipc --exec "eth.accounts" | grep 0x9377bc3936de934c497e22917b81aa8774ac3bb0' + ) + ''; +}) diff --git a/nixos/tests/rabbitmq.nix b/nixos/tests/rabbitmq.nix index 8e7f34d06e32e..f403e4ac2edc5 100644 --- a/nixos/tests/rabbitmq.nix +++ b/nixos/tests/rabbitmq.nix @@ -15,7 +15,7 @@ import ./make-test-python.nix ({ pkgs, ... }: { machine.wait_for_unit("rabbitmq.service") machine.wait_until_succeeds( - 'su -s ${pkgs.stdenv.shell} rabbitmq -c "rabbitmqctl status"' + 'su -s ${pkgs.runtimeShell} rabbitmq -c "rabbitmqctl status"' ) ''; }) diff --git a/nixos/tests/radicale.nix b/nixos/tests/radicale.nix index 6079642556407..c81e78a8f9943 100644 --- a/nixos/tests/radicale.nix +++ b/nixos/tests/radicale.nix @@ -28,7 +28,7 @@ let in - import ./make-test.nix ({ lib, ... }@args: { + import ./make-test-python.nix ({ lib, ... }@args: { name = "radicale"; meta.maintainers = with lib.maintainers; [ aneeshusa infinisil ]; @@ -64,43 +64,59 @@ in newSystem = nodes.${nodeName}.config.system.build.toplevel; in "${newSystem}/bin/switch-to-configuration test"; in '' - # Check Radicale 1 functionality - $radicale->succeed('${switchToConfig "radicale1"} >&2'); - $radicale->waitForUnit('radicale.service'); - $radicale->waitForOpenPort(${port}); - $radicale->succeed('curl --fail http://${user}:${password}@localhost:${port}/someuser/calendar.ics/'); + with subtest("Check Radicale 1 functionality"): + radicale.succeed( + "${switchToConfig "radicale1"} >&2" + ) + radicale.wait_for_unit("radicale.service") + radicale.wait_for_open_port(${port}) + radicale.succeed( + "curl --fail http://${user}:${password}@localhost:${port}/someuser/calendar.ics/" + ) - # Export data in Radicale 2 format - $radicale->succeed('systemctl stop radicale'); - $radicale->succeed('ls -al /tmp/collections'); - $radicale->fail('ls -al /tmp/collections-new'); - # Radicale exits immediately after exporting storage - $radicale->succeed('${switchToConfig "radicale1_export"} >&2'); - $radicale->waitUntilFails('systemctl status radicale'); - $radicale->succeed('ls -al /tmp/collections'); - $radicale->succeed('ls -al /tmp/collections-new'); + with subtest("Export data in Radicale 2 format"): + radicale.succeed("systemctl stop radicale") + radicale.succeed("ls -al /tmp/collections") + radicale.fail("ls -al /tmp/collections-new") - # Verify data in Radicale 2 format - $radicale->succeed('rm -r /tmp/collections/${user}'); - $radicale->succeed('mv /tmp/collections-new/collection-root /tmp/collections'); - $radicale->succeed('${switchToConfig "radicale2_verify"} >&2'); - $radicale->waitUntilFails('systemctl status radicale'); - my ($retcode, $logs) = $radicale->execute('journalctl -u radicale -n 10'); - if ($retcode != 0 || index($logs, 'Verifying storage') == -1) { - die "Radicale 2 didn't verify storage" - } - if (index($logs, 'failed') != -1 || index($logs, 'exception') != -1) { - die "storage verification failed" - } + with subtest("Radicale exits immediately after exporting storage"): + radicale.succeed( + "${switchToConfig "radicale1_export"} >&2" + ) + radicale.wait_until_fails("systemctl status radicale") + radicale.succeed("ls -al /tmp/collections") + radicale.succeed("ls -al /tmp/collections-new") - # Check Radicale 2 functionality - $radicale->succeed('${switchToConfig "radicale2"} >&2'); - $radicale->waitForUnit('radicale.service'); - $radicale->waitForOpenPort(${port}); - my ($retcode, $output) = $radicale->execute('curl --fail http://${user}:${password}@localhost:${port}/someuser/calendar.ics/'); - if ($retcode != 0 || index($output, 'VCALENDAR') == -1) { - die "Could not read calendar from Radicale 2" - } - $radicale->succeed('curl --fail http://${user}:${password}@localhost:${port}/.web/'); + with subtest("Verify data in Radicale 2 format"): + radicale.succeed("rm -r /tmp/collections/${user}") + radicale.succeed("mv /tmp/collections-new/collection-root /tmp/collections") + radicale.succeed( + "${switchToConfig "radicale2_verify"} >&2" + ) + radicale.wait_until_fails("systemctl status radicale") + + (retcode, logs) = radicale.execute("journalctl -u radicale -n 10") + assert ( + retcode == 0 and "Verifying storage" in logs + ), "Radicale 2 didn't verify storage" + assert ( + "failed" not in logs and "exception" not in logs + ), "storage verification failed" + + with subtest("Check Radicale 2 functionality"): + radicale.succeed( + "${switchToConfig "radicale2"} >&2" + ) + radicale.wait_for_unit("radicale.service") + radicale.wait_for_open_port(${port}) + + (retcode, output) = radicale.execute( + "curl --fail http://${user}:${password}@localhost:${port}/someuser/calendar.ics/" + ) + assert ( + retcode == 0 and "VCALENDAR" in output + ), "Could not read calendar from Radicale 2" + + radicale.succeed("curl --fail http://${user}:${password}@localhost:${port}/.web/") ''; }) diff --git a/nixos/tests/redmine.nix b/nixos/tests/redmine.nix index f0f4cbf6a21ce..3866a1f528c05 100644 --- a/nixos/tests/redmine.nix +++ b/nixos/tests/redmine.nix @@ -3,74 +3,42 @@ pkgs ? import ../.. { inherit system config; } }: -with import ../lib/testing.nix { inherit system pkgs; }; +with import ../lib/testing-python.nix { inherit system pkgs; }; with pkgs.lib; let - mysqlTest = package: makeTest { - machine = - { config, pkgs, ... }: - { services.redmine.enable = true; - services.redmine.package = package; - services.redmine.database.type = "mysql2"; - services.redmine.plugins = { + redmineTest = { name, type }: makeTest { + name = "redmine-${name}"; + machine = { config, pkgs, ... }: { + services.redmine = { + enable = true; + package = pkgs.redmine; + database.type = type; + plugins = { redmine_env_auth = pkgs.fetchurl { - url = https://github.com/Intera/redmine_env_auth/archive/0.7.zip; + url = "https://github.com/Intera/redmine_env_auth/archive/0.7.zip"; sha256 = "1xb8lyarc7mpi86yflnlgyllh9hfwb9z304f19dx409gqpia99sc"; }; }; - services.redmine.themes = { + themes = { dkuk-redmine_alex_skin = pkgs.fetchurl { - url = https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip; + url = "https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip"; sha256 = "0hrin9lzyi50k4w2bd2b30vrf1i4fi1c0gyas5801wn8i7kpm9yl"; }; }; }; + }; testScript = '' - startAll; - $machine->waitForUnit('redmine.service'); - $machine->waitForOpenPort('3000'); - $machine->succeed("curl --fail http://localhost:3000/"); + start_all() + machine.wait_for_unit("redmine.service") + machine.wait_for_open_port(3000) + machine.succeed("curl --fail http://localhost:3000/") ''; - }; - - pgsqlTest = package: makeTest { - machine = - { config, pkgs, ... }: - { services.redmine.enable = true; - services.redmine.package = package; - services.redmine.database.type = "postgresql"; - services.redmine.plugins = { - redmine_env_auth = pkgs.fetchurl { - url = https://github.com/Intera/redmine_env_auth/archive/0.7.zip; - sha256 = "1xb8lyarc7mpi86yflnlgyllh9hfwb9z304f19dx409gqpia99sc"; - }; - }; - services.redmine.themes = { - dkuk-redmine_alex_skin = pkgs.fetchurl { - url = https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip; - sha256 = "0hrin9lzyi50k4w2bd2b30vrf1i4fi1c0gyas5801wn8i7kpm9yl"; - }; - }; - }; - - testScript = '' - startAll; - $machine->waitForUnit('redmine.service'); - $machine->waitForOpenPort('3000'); - $machine->succeed("curl --fail http://localhost:3000/"); - ''; - }; -in -{ - mysql = mysqlTest pkgs.redmine // { - name = "mysql"; - meta.maintainers = [ maintainers.aanderse ]; - }; - - pgsql = pgsqlTest pkgs.redmine // { - name = "pgsql"; + } // { meta.maintainers = [ maintainers.aanderse ]; }; +in { + mysql = redmineTest { name = "mysql"; type = "mysql2"; }; + pgsql = redmineTest { name = "pgsql"; type = "postgresql"; }; } diff --git a/nixos/tests/resolv.nix b/nixos/tests/resolv.nix new file mode 100644 index 0000000000000..b506f87451ee7 --- /dev/null +++ b/nixos/tests/resolv.nix @@ -0,0 +1,46 @@ +# Test whether DNS resolving returns multiple records and all address families. +import ./make-test-python.nix ({ pkgs, ... } : { + name = "resolv"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ ckauhaus ]; + }; + + nodes.resolv = { ... }: { + networking.extraHosts = '' + # IPv4 only + 192.0.2.1 host-ipv4.example.net + 192.0.2.2 host-ipv4.example.net + # IP6 only + 2001:db8::2:1 host-ipv6.example.net + 2001:db8::2:2 host-ipv6.example.net + # dual stack + 192.0.2.1 host-dual.example.net + 192.0.2.2 host-dual.example.net + 2001:db8::2:1 host-dual.example.net + 2001:db8::2:2 host-dual.example.net + ''; + }; + + testScript = '' + def addrs_in(hostname, addrs): + res = resolv.succeed("getent ahosts {}".format(hostname)) + for addr in addrs: + assert addr in res, "Expected output '{}' not found in\n{}".format(addr, res) + + + start_all() + resolv.wait_for_unit("nscd") + + ipv4 = ["192.0.2.1", "192.0.2.2"] + ipv6 = ["2001:db8::2:1", "2001:db8::2:2"] + + with subtest("IPv4 resolves"): + addrs_in("host-ipv4.example.net", ipv4) + + with subtest("IPv6 resolves"): + addrs_in("host-ipv6.example.net", ipv6) + + with subtest("Dual stack resolves"): + addrs_in("host-dual.example.net", ipv4 + ipv6) + ''; +}) diff --git a/nixos/tests/restic.nix b/nixos/tests/restic.nix new file mode 100644 index 0000000000000..67bb7f1933d66 --- /dev/null +++ b/nixos/tests/restic.nix @@ -0,0 +1,63 @@ +import ./make-test-python.nix ( + { pkgs, ... }: + + let + password = "some_password"; + repository = "/tmp/restic-backup"; + passwordFile = pkgs.writeText "password" "correcthorsebatterystaple"; + in + { + name = "restic"; + + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ bbigras ]; + }; + + nodes = { + server = + { ... }: + { + services.restic.backups = { + remotebackup = { + inherit repository; + passwordFile = "${passwordFile}"; + initialize = true; + paths = [ "/opt" ]; + pruneOpts = [ + "--keep-daily 2" + "--keep-weekly 1" + "--keep-monthly 1" + "--keep-yearly 99" + ]; + }; + }; + }; + }; + + testScript = '' + server.start() + server.wait_for_unit("dbus.socket") + server.fail( + "${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots" + ) + server.succeed( + "mkdir -p /opt", + "touch /opt/some_file", + "timedatectl set-time '2016-12-13 13:45'", + "systemctl start restic-backups-remotebackup.service", + '${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"', + "timedatectl set-time '2017-12-13 13:45'", + "systemctl start restic-backups-remotebackup.service", + "timedatectl set-time '2018-12-13 13:45'", + "systemctl start restic-backups-remotebackup.service", + "timedatectl set-time '2018-12-14 13:45'", + "systemctl start restic-backups-remotebackup.service", + "timedatectl set-time '2018-12-15 13:45'", + "systemctl start restic-backups-remotebackup.service", + "timedatectl set-time '2018-12-16 13:45'", + "systemctl start restic-backups-remotebackup.service", + '${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots -c | grep -e "^4 snapshot"', + ) + ''; + } +) diff --git a/nixos/tests/riak.nix b/nixos/tests/riak.nix index 68a9b7315b350..6915779e7e9c2 100644 --- a/nixos/tests/riak.nix +++ b/nixos/tests/riak.nix @@ -1,21 +1,18 @@ -import ./make-test.nix { +import ./make-test-python.nix ({ lib, pkgs, ... }: { name = "riak"; + meta = with lib.maintainers; { + maintainers = [ filalex77 ]; + }; - nodes = { - master = - { pkgs, ... }: - - { - services.riak.enable = true; - services.riak.package = pkgs.riak; - }; + machine = { + services.riak.enable = true; + services.riak.package = pkgs.riak; }; testScript = '' - startAll; + machine.start() - $master->waitForUnit("riak"); - $master->sleep(20); # Hopefully this is long enough!! - $master->succeed("riak ping 2>&1"); + machine.wait_for_unit("riak") + machine.wait_until_succeeds("riak ping 2>&1") ''; -} +}) diff --git a/nixos/tests/roundcube.nix b/nixos/tests/roundcube.nix index 1897b53e283a0..97e1125694b6b 100644 --- a/nixos/tests/roundcube.nix +++ b/nixos/tests/roundcube.nix @@ -12,6 +12,7 @@ import ./make-test-python.nix ({ pkgs, ...} : { database.password = "not production"; package = pkgs.roundcube.withPlugins (plugins: [ plugins.persistent_login ]); plugins = [ "persistent_login" ]; + dicts = with pkgs.aspellDicts; [ en fr de ]; }; services.nginx.virtualHosts.roundcube = { forceSSL = false; diff --git a/nixos/tests/rspamd.nix b/nixos/tests/rspamd.nix index 0cc94728f80a3..bf3f0de620445 100644 --- a/nixos/tests/rspamd.nix +++ b/nixos/tests/rspamd.nix @@ -3,20 +3,20 @@ pkgs ? import ../.. { inherit system config; } }: -with import ../lib/testing.nix { inherit system pkgs; }; +with import ../lib/testing-python.nix { inherit system pkgs; }; with pkgs.lib; let initMachine = '' - startAll - $machine->waitForUnit("rspamd.service"); - $machine->succeed("id \"rspamd\" >/dev/null"); + start_all() + machine.wait_for_unit("rspamd.service") + machine.succeed("id rspamd >/dev/null") ''; checkSocket = socket: user: group: mode: '' - $machine->succeed("ls ${socket} >/dev/null"); - $machine->succeed("[[ \"\$(stat -c %U ${socket})\" == \"${user}\" ]]"); - $machine->succeed("[[ \"\$(stat -c %G ${socket})\" == \"${group}\" ]]"); - $machine->succeed("[[ \"\$(stat -c %a ${socket})\" == \"${mode}\" ]]"); + machine.succeed("ls ${socket} >/dev/null") + machine.succeed('[[ "$(stat -c %U ${socket})" == "${user}" ]]') + machine.succeed('[[ "$(stat -c %G ${socket})" == "${group}" ]]') + machine.succeed('[[ "$(stat -c %a ${socket})" == "${mode}" ]]') ''; simple = name: enableIPv6: makeTest { name = "rspamd-${name}"; @@ -25,22 +25,23 @@ let networking.enableIPv6 = enableIPv6; }; testScript = '' - startAll - $machine->waitForUnit("multi-user.target"); - $machine->waitForOpenPort(11334); - $machine->waitForUnit("rspamd.service"); - $machine->succeed("id \"rspamd\" >/dev/null"); + start_all() + machine.wait_for_unit("multi-user.target") + machine.wait_for_open_port(11334) + machine.wait_for_unit("rspamd.service") + machine.succeed("id rspamd >/dev/null") ${checkSocket "/run/rspamd/rspamd.sock" "rspamd" "rspamd" "660" } - sleep 10; - $machine->log($machine->succeed("cat /etc/rspamd/rspamd.conf")); - $machine->log($machine->succeed("grep 'CONFDIR/worker-controller.inc' /etc/rspamd/rspamd.conf")); - $machine->log($machine->succeed("grep 'CONFDIR/worker-normal.inc' /etc/rspamd/rspamd.conf")); - $machine->log($machine->succeed("systemctl cat rspamd.service")); - $machine->log($machine->succeed("curl http://localhost:11334/auth")); - $machine->log($machine->succeed("curl http://127.0.0.1:11334/auth")); - ${optionalString enableIPv6 '' - $machine->log($machine->succeed("curl http://[::1]:11334/auth")); - ''} + machine.sleep(10) + machine.log(machine.succeed("cat /etc/rspamd/rspamd.conf")) + machine.log( + machine.succeed("grep 'CONFDIR/worker-controller.inc' /etc/rspamd/rspamd.conf") + ) + machine.log(machine.succeed("grep 'CONFDIR/worker-normal.inc' /etc/rspamd/rspamd.conf")) + machine.log(machine.succeed("systemctl cat rspamd.service")) + machine.log(machine.succeed("curl http://localhost:11334/auth")) + machine.log(machine.succeed("curl http://127.0.0.1:11334/auth")) + ${optionalString enableIPv6 ''machine.log(machine.succeed("curl http://[::1]:11334/auth"))''} + # would not reformat ''; }; in @@ -69,14 +70,18 @@ in testScript = '' ${initMachine} - $machine->waitForFile("/run/rspamd.sock"); + machine.wait_for_file("/run/rspamd.sock") ${checkSocket "/run/rspamd.sock" "root" "root" "600" } ${checkSocket "/run/rspamd-worker.sock" "root" "root" "666" } - $machine->log($machine->succeed("cat /etc/rspamd/rspamd.conf")); - $machine->log($machine->succeed("grep 'CONFDIR/worker-controller.inc' /etc/rspamd/rspamd.conf")); - $machine->log($machine->succeed("grep 'CONFDIR/worker-normal.inc' /etc/rspamd/rspamd.conf")); - $machine->log($machine->succeed("rspamc -h /run/rspamd-worker.sock stat")); - $machine->log($machine->succeed("curl --unix-socket /run/rspamd-worker.sock http://localhost/ping")); + machine.log(machine.succeed("cat /etc/rspamd/rspamd.conf")) + machine.log( + machine.succeed("grep 'CONFDIR/worker-controller.inc' /etc/rspamd/rspamd.conf") + ) + machine.log(machine.succeed("grep 'CONFDIR/worker-normal.inc' /etc/rspamd/rspamd.conf")) + machine.log(machine.succeed("rspamc -h /run/rspamd-worker.sock stat")) + machine.log( + machine.succeed("curl --unix-socket /run/rspamd-worker.sock http://localhost/ping") + ) ''; }; @@ -111,18 +116,32 @@ in testScript = '' ${initMachine} - $machine->waitForFile("/run/rspamd.sock"); + machine.wait_for_file("/run/rspamd.sock") ${checkSocket "/run/rspamd.sock" "root" "root" "600" } ${checkSocket "/run/rspamd-worker.sock" "root" "root" "666" } - $machine->log($machine->succeed("cat /etc/rspamd/rspamd.conf")); - $machine->log($machine->succeed("grep 'CONFDIR/worker-controller.inc' /etc/rspamd/rspamd.conf")); - $machine->log($machine->succeed("grep 'CONFDIR/worker-normal.inc' /etc/rspamd/rspamd.conf")); - $machine->log($machine->succeed("grep 'LOCAL_CONFDIR/override.d/worker-controller2.inc' /etc/rspamd/rspamd.conf")); - $machine->log($machine->succeed("grep 'verysecretpassword' /etc/rspamd/override.d/worker-controller2.inc")); - $machine->waitUntilSucceeds("journalctl -u rspamd | grep -i 'starting controller process' >&2"); - $machine->log($machine->succeed("rspamc -h /run/rspamd-worker.sock stat")); - $machine->log($machine->succeed("curl --unix-socket /run/rspamd-worker.sock http://localhost/ping")); - $machine->log($machine->succeed("curl http://localhost:11335/ping")); + machine.log(machine.succeed("cat /etc/rspamd/rspamd.conf")) + machine.log( + machine.succeed("grep 'CONFDIR/worker-controller.inc' /etc/rspamd/rspamd.conf") + ) + machine.log(machine.succeed("grep 'CONFDIR/worker-normal.inc' /etc/rspamd/rspamd.conf")) + machine.log( + machine.succeed( + "grep 'LOCAL_CONFDIR/override.d/worker-controller2.inc' /etc/rspamd/rspamd.conf" + ) + ) + machine.log( + machine.succeed( + "grep 'verysecretpassword' /etc/rspamd/override.d/worker-controller2.inc" + ) + ) + machine.wait_until_succeeds( + "journalctl -u rspamd | grep -i 'starting controller process' >&2" + ) + machine.log(machine.succeed("rspamc -h /run/rspamd-worker.sock stat")) + machine.log( + machine.succeed("curl --unix-socket /run/rspamd-worker.sock http://localhost/ping") + ) + machine.log(machine.succeed("curl http://localhost:11335/ping")) ''; }; customLuaRules = makeTest { @@ -199,22 +218,34 @@ in }; testScript = '' ${initMachine} - $machine->waitForOpenPort(11334); - $machine->log($machine->succeed("cat /etc/rspamd/rspamd.conf")); - $machine->log($machine->succeed("cat /etc/rspamd/rspamd.local.lua")); - $machine->log($machine->succeed("cat /etc/rspamd/local.d/groups.conf")); + machine.wait_for_open_port(11334) + machine.log(machine.succeed("cat /etc/rspamd/rspamd.conf")) + machine.log(machine.succeed("cat /etc/rspamd/rspamd.local.lua")) + machine.log(machine.succeed("cat /etc/rspamd/local.d/groups.conf")) # Verify that redis.conf was not written - $machine->fail("cat /etc/rspamd/local.d/redis.conf >&2"); + machine.fail("cat /etc/rspamd/local.d/redis.conf >&2") # Verify that antivirus.conf was not written - $machine->fail("cat /etc/rspamd/local.d/antivirus.conf >&2"); + machine.fail("cat /etc/rspamd/local.d/antivirus.conf >&2") ${checkSocket "/run/rspamd/rspamd.sock" "rspamd" "rspamd" "660" } - $machine->log($machine->succeed("curl --unix-socket /run/rspamd/rspamd.sock http://localhost/ping")); - $machine->log($machine->succeed("rspamc -h 127.0.0.1:11334 stat")); - $machine->log($machine->succeed("cat /etc/tests/no-muh.eml | rspamc -h 127.0.0.1:11334")); - $machine->log($machine->succeed("cat /etc/tests/muh.eml | rspamc -h 127.0.0.1:11334 symbols")); - $machine->waitUntilSucceeds("journalctl -u rspamd | grep -i muh >&2"); - $machine->log($machine->fail("cat /etc/tests/no-muh.eml | rspamc -h 127.0.0.1:11334 symbols | grep NO_MUH")); - $machine->log($machine->succeed("cat /etc/tests/muh.eml | rspamc -h 127.0.0.1:11334 symbols | grep NO_MUH")); + machine.log( + machine.succeed("curl --unix-socket /run/rspamd/rspamd.sock http://localhost/ping") + ) + machine.log(machine.succeed("rspamc -h 127.0.0.1:11334 stat")) + machine.log(machine.succeed("cat /etc/tests/no-muh.eml | rspamc -h 127.0.0.1:11334")) + machine.log( + machine.succeed("cat /etc/tests/muh.eml | rspamc -h 127.0.0.1:11334 symbols") + ) + machine.wait_until_succeeds("journalctl -u rspamd | grep -i muh >&2") + machine.log( + machine.fail( + "cat /etc/tests/no-muh.eml | rspamc -h 127.0.0.1:11334 symbols | grep NO_MUH" + ) + ) + machine.log( + machine.succeed( + "cat /etc/tests/muh.eml | rspamc -h 127.0.0.1:11334 symbols | grep NO_MUH" + ) + ) ''; }; postfixIntegration = makeTest { @@ -250,16 +281,24 @@ in }; testScript = '' ${initMachine} - $machine->waitForOpenPort(11334); - $machine->waitForOpenPort(25); + machine.wait_for_open_port(11334) + machine.wait_for_open_port(25) ${checkSocket "/run/rspamd/rspamd-milter.sock" "rspamd" "postfix" "660" } - $machine->log($machine->succeed("rspamc -h 127.0.0.1:11334 stat")); - $machine->log($machine->succeed("msmtp --host=localhost -t --read-envelope-from < /etc/tests/example.eml")); - $machine->log($machine->fail("msmtp --host=localhost -t --read-envelope-from < /etc/tests/gtube.eml")); + machine.log(machine.succeed("rspamc -h 127.0.0.1:11334 stat")) + machine.log( + machine.succeed( + "msmtp --host=localhost -t --read-envelope-from < /etc/tests/example.eml" + ) + ) + machine.log( + machine.fail( + "msmtp --host=localhost -t --read-envelope-from < /etc/tests/gtube.eml" + ) + ) - $machine->waitUntilFails('[ "$(postqueue -p)" != "Mail queue is empty" ]'); - $machine->fail("journalctl -u postfix | grep -i error >&2"); - $machine->fail("journalctl -u postfix | grep -i warning >&2"); + machine.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + machine.fail("journalctl -u postfix | grep -i error >&2") + machine.fail("journalctl -u postfix | grep -i warning >&2") ''; }; } diff --git a/nixos/tests/rsyslogd.nix b/nixos/tests/rsyslogd.nix index f17e61814c5e9..50523920c60b2 100644 --- a/nixos/tests/rsyslogd.nix +++ b/nixos/tests/rsyslogd.nix @@ -3,40 +3,38 @@ pkgs ? import ../.. { inherit system config; } }: -with import ../lib/testing.nix { inherit system pkgs; }; +with import ../lib/testing-python.nix { inherit system pkgs; }; with pkgs.lib; { test1 = makeTest { name = "rsyslogd-test1"; - meta.maintainers = [ maintainers.aanderse ]; + meta.maintainers = [ pkgs.stdenv.lib.maintainers.aanderse ]; - machine = - { config, pkgs, ... }: - { services.rsyslogd.enable = true; - services.journald.forwardToSyslog = false; - }; + machine = { config, pkgs, ... }: { + services.rsyslogd.enable = true; + services.journald.forwardToSyslog = false; + }; # ensure rsyslogd isn't receiving messages from journald if explicitly disabled testScript = '' - $machine->waitForUnit("default.target"); - $machine->fail("test -f /var/log/messages"); + machine.wait_for_unit("default.target") + machine.fail("test -f /var/log/messages") ''; }; test2 = makeTest { name = "rsyslogd-test2"; - meta.maintainers = [ maintainers.aanderse ]; + meta.maintainers = [ pkgs.stdenv.lib.maintainers.aanderse ]; - machine = - { config, pkgs, ... }: - { services.rsyslogd.enable = true; - }; + machine = { config, pkgs, ... }: { + services.rsyslogd.enable = true; + }; # ensure rsyslogd is receiving messages from journald testScript = '' - $machine->waitForUnit("default.target"); - $machine->succeed("test -f /var/log/messages"); + machine.wait_for_unit("default.target") + machine.succeed("test -f /var/log/messages") ''; }; } diff --git a/nixos/tests/run-in-machine.nix b/nixos/tests/run-in-machine.nix index 339a4b9a7404b..67840f3e9fe7f 100644 --- a/nixos/tests/run-in-machine.nix +++ b/nixos/tests/run-in-machine.nix @@ -3,7 +3,7 @@ pkgs ? import ../.. { inherit system config; } }: -with import ../lib/testing.nix { inherit system pkgs; }; +with import ../lib/testing-python.nix { inherit system pkgs; }; let output = runInMachine { diff --git a/nixos/tests/rxe.nix b/nixos/tests/rxe.nix index 194a2e3d2b94f..10753c4ed0c85 100644 --- a/nixos/tests/rxe.nix +++ b/nixos/tests/rxe.nix @@ -28,7 +28,7 @@ in { # Test if rxe interface comes up server.wait_for_unit("default.target") server.succeed("systemctl status rxe.service") - server.succeed("ibv_devices | grep rxe0") + server.succeed("ibv_devices | grep rxe_eth1") client.wait_for_unit("default.target") diff --git a/nixos/tests/sanoid.nix b/nixos/tests/sanoid.nix new file mode 100644 index 0000000000000..284b38932cceb --- /dev/null +++ b/nixos/tests/sanoid.nix @@ -0,0 +1,90 @@ +import ./make-test-python.nix ({ pkgs, ... }: let + inherit (import ./ssh-keys.nix pkgs) + snakeOilPrivateKey snakeOilPublicKey; + + commonConfig = { pkgs, ... }: { + virtualisation.emptyDiskImages = [ 2048 ]; + boot.supportedFilesystems = [ "zfs" ]; + environment.systemPackages = [ pkgs.parted ]; + }; +in { + name = "sanoid"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ lopsided98 ]; + }; + + nodes = { + source = { ... }: { + imports = [ commonConfig ]; + networking.hostId = "daa82e91"; + + programs.ssh.extraConfig = '' + UserKnownHostsFile=/dev/null + StrictHostKeyChecking=no + ''; + + services.sanoid = { + enable = true; + templates.test = { + hourly = 12; + daily = 1; + monthly = 1; + yearly = 1; + + autosnap = true; + }; + datasets."pool/test".useTemplate = [ "test" ]; + }; + + services.syncoid = { + enable = true; + sshKey = "/root/.ssh/id_ecdsa"; + commonArgs = [ "--no-sync-snap" ]; + commands."pool/test".target = "root@target:pool/test"; + }; + }; + target = { ... }: { + imports = [ commonConfig ]; + networking.hostId = "dcf39d36"; + + services.openssh.enable = true; + users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; + }; + }; + + testScript = '' + source.succeed( + "mkdir /tmp/mnt", + "parted --script /dev/vdb -- mklabel msdos mkpart primary 1024M -1s", + "udevadm settle", + "zpool create pool /dev/vdb1", + "zfs create -o mountpoint=legacy pool/test", + "mount -t zfs pool/test /tmp/mnt", + "udevadm settle", + ) + target.succeed( + "parted --script /dev/vdb -- mklabel msdos mkpart primary 1024M -1s", + "udevadm settle", + "zpool create pool /dev/vdb1", + "udevadm settle", + ) + + source.succeed("mkdir -m 700 /root/.ssh") + source.succeed( + "cat '${snakeOilPrivateKey}' > /root/.ssh/id_ecdsa" + ) + source.succeed("chmod 600 /root/.ssh/id_ecdsa") + + source.succeed("touch /tmp/mnt/test.txt") + source.systemctl("start --wait sanoid.service") + + target.wait_for_open_port(22) + source.systemctl("start --wait syncoid.service") + target.succeed( + "mkdir /tmp/mnt", + "zfs set mountpoint=legacy pool/test", + "mount -t zfs pool/test /tmp/mnt", + ) + target.succeed("cat /tmp/mnt/test.txt") + ''; +}) diff --git a/nixos/tests/sddm.nix b/nixos/tests/sddm.nix index 4bdcd701dcf16..a145705250f71 100644 --- a/nixos/tests/sddm.nix +++ b/nixos/tests/sddm.nix @@ -16,9 +16,8 @@ let imports = [ ./common/user-account.nix ]; services.xserver.enable = true; services.xserver.displayManager.sddm.enable = true; - services.xserver.windowManager.default = "icewm"; + services.xserver.displayManager.defaultSession = "none+icewm"; services.xserver.windowManager.icewm.enable = true; - services.xserver.desktopManager.default = "none"; }; enableOCR = true; @@ -52,9 +51,8 @@ let user = "alice"; }; }; - services.xserver.windowManager.default = "icewm"; + services.xserver.displayManager.defaultSession = "none+icewm"; services.xserver.windowManager.icewm.enable = true; - services.xserver.desktopManager.default = "none"; }; testScript = { nodes, ... }: let diff --git a/nixos/tests/service-runner.nix b/nixos/tests/service-runner.nix new file mode 100644 index 0000000000000..adb3fcd36d7a3 --- /dev/null +++ b/nixos/tests/service-runner.nix @@ -0,0 +1,36 @@ +import ./make-test-python.nix ({ pkgs, ... }: { + name = "service-runner"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ roberth ]; + }; + + nodes = { + machine = { pkgs, lib, ... }: { + services.nginx.enable = true; + services.nginx.virtualHosts.machine.root = pkgs.runCommand "webroot" {} '' + mkdir $out + echo 'yay' >$out/index.html + ''; + systemd.services.nginx.enable = false; + }; + + }; + + testScript = { nodes, ... }: '' + url = "http://localhost/index.html" + + with subtest("check systemd.services.nginx.runner"): + machine.fail(f"curl {url}") + machine.succeed( + """ + mkdir -p /run/nginx /var/spool/nginx/logs + ${nodes.machine.config.systemd.services.nginx.runner} & + echo $!>my-nginx.pid + """ + ) + machine.wait_for_open_port(80) + machine.succeed(f"curl {url}") + machine.succeed("kill -INT $(cat my-nginx.pid)") + machine.wait_for_closed_port(80) + ''; +}) diff --git a/nixos/tests/signal-desktop.nix b/nixos/tests/signal-desktop.nix index c746d46dc5505..e4b830e9e237c 100644 --- a/nixos/tests/signal-desktop.nix +++ b/nixos/tests/signal-desktop.nix @@ -15,8 +15,9 @@ import ./make-test-python.nix ({ pkgs, ...} : ]; services.xserver.enable = true; - services.xserver.displayManager.auto.user = "alice"; + test-support.displayManager.auto.user = "alice"; environment.systemPackages = [ pkgs.signal-desktop ]; + virtualisation.memorySize = 1024; }; enableOCR = true; diff --git a/nixos/tests/slurm.nix b/nixos/tests/slurm.nix index 17527378cf0a5..d0e62d15437c0 100644 --- a/nixos/tests/slurm.nix +++ b/nixos/tests/slurm.nix @@ -119,7 +119,7 @@ in { with subtest("can_start_slurmctld"): control.succeed("systemctl restart slurmctld") - control.waitForUnit("slurmctld.service") + control.wait_for_unit("slurmctld.service") with subtest("can_start_slurmd"): for node in [node1, node2, node3]: diff --git a/nixos/tests/solr.nix b/nixos/tests/solr.nix index 2108e851bc595..dc5770e16bc72 100644 --- a/nixos/tests/solr.nix +++ b/nixos/tests/solr.nix @@ -1,40 +1,37 @@ -{ system ? builtins.currentSystem, - config ? {}, - pkgs ? import ../.. { inherit system config; } -}: +import ./make-test-python.nix ({ pkgs, ... }: -with import ../lib/testing.nix { inherit system pkgs; }; -with pkgs.lib; +{ + name = "solr"; + meta.maintainers = [ pkgs.stdenv.lib.maintainers.aanderse ]; -let - solrTest = package: makeTest { - machine = - { config, pkgs, ... }: - { - # Ensure the virtual machine has enough memory for Solr to avoid the following error: - # - # OpenJDK 64-Bit Server VM warning: - # INFO: os::commit_memory(0x00000000e8000000, 402653184, 0) - # failed; error='Cannot allocate memory' (errno=12) - # - # There is insufficient memory for the Java Runtime Environment to continue. - # Native memory allocation (mmap) failed to map 402653184 bytes for committing reserved memory. - virtualisation.memorySize = 2000; + machine = + { config, pkgs, ... }: + { + # Ensure the virtual machine has enough memory for Solr to avoid the following error: + # + # OpenJDK 64-Bit Server VM warning: + # INFO: os::commit_memory(0x00000000e8000000, 402653184, 0) + # failed; error='Cannot allocate memory' (errno=12) + # + # There is insufficient memory for the Java Runtime Environment to continue. + # Native memory allocation (mmap) failed to map 402653184 bytes for committing reserved memory. + virtualisation.memorySize = 2000; - services.solr.enable = true; - services.solr.package = package; - }; + services.solr.enable = true; + }; - testScript = '' - startAll; + testScript = '' + start_all() - $machine->waitForUnit('solr.service'); - $machine->waitForOpenPort('8983'); - $machine->succeed('curl --fail http://localhost:8983/solr/'); + machine.wait_for_unit("solr.service") + machine.wait_for_open_port(8983) + machine.succeed("curl --fail http://localhost:8983/solr/") - # adapted from pkgs.solr/examples/films/README.txt - $machine->succeed('sudo -u solr solr create -c films'); - $machine->succeed(q(curl http://localhost:8983/solr/films/schema -X POST -H 'Content-type:application/json' --data-binary '{ + # adapted from pkgs.solr/examples/films/README.txt + machine.succeed("sudo -u solr solr create -c films") + assert '"status":0' in machine.succeed( + """ + curl http://localhost:8983/solr/films/schema -X POST -H 'Content-type:application/json' --data-binary '{ "add-field" : { "name":"name", "type":"text_general", @@ -46,20 +43,14 @@ let "type":"pdate", "stored":true } - }')) =~ /"status":0/ or die; - $machine->succeed('sudo -u solr post -c films ${pkgs.solr}/example/films/films.json'); - $machine->succeed('curl http://localhost:8983/solr/films/query?q=name:batman') =~ /"name":"Batman Begins"/ or die; - ''; - }; -in -{ - solr_7 = solrTest pkgs.solr_7 // { - name = "solr_7"; - meta.maintainers = [ lib.maintainers.aanderse ]; - }; - - solr_8 = solrTest pkgs.solr_8 // { - name = "solr_8"; - meta.maintainers = [ lib.maintainers.aanderse ]; - }; -} + }' + """ + ) + machine.succeed( + "sudo -u solr post -c films ${pkgs.solr}/example/films/films.json" + ) + assert '"name":"Batman Begins"' in machine.succeed( + "curl http://localhost:8983/solr/films/query?q=name:batman" + ) + ''; +}) diff --git a/nixos/tests/sonarr.nix b/nixos/tests/sonarr.nix index 3e84445099abe..764a4d05b381b 100644 --- a/nixos/tests/sonarr.nix +++ b/nixos/tests/sonarr.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ lib, ... }: +import ./make-test-python.nix ({ lib, ... }: with lib; @@ -11,8 +11,8 @@ with lib; { services.sonarr.enable = true; }; testScript = '' - $machine->waitForUnit('sonarr.service'); - $machine->waitForOpenPort('8989'); - $machine->succeed("curl --fail http://localhost:8989/"); + machine.wait_for_unit("sonarr.service") + machine.wait_for_open_port("8989") + machine.succeed("curl --fail http://localhost:8989/") ''; }) diff --git a/nixos/tests/spacecookie.nix b/nixos/tests/spacecookie.nix new file mode 100644 index 0000000000000..6eff32a2e75d7 --- /dev/null +++ b/nixos/tests/spacecookie.nix @@ -0,0 +1,51 @@ +let + gopherRoot = "/tmp/gopher"; + gopherHost = "gopherd"; + fileContent = "Hello Gopher!"; + fileName = "file.txt"; +in + import ./make-test-python.nix ({...}: { + name = "spacecookie"; + nodes = { + ${gopherHost} = { + networking.firewall.allowedTCPPorts = [ 70 ]; + systemd.services.spacecookie = { + preStart = '' + mkdir -p ${gopherRoot}/directory + echo "${fileContent}" > ${gopherRoot}/${fileName} + ''; + }; + + services.spacecookie = { + enable = true; + root = gopherRoot; + hostname = gopherHost; + }; + }; + + client = {}; + }; + + testScript = '' + start_all() + ${gopherHost}.wait_for_open_port(70) + ${gopherHost}.wait_for_unit("spacecookie.service") + client.wait_for_unit("network.target") + + fileResponse = client.succeed("curl -s gopher://${gopherHost}//${fileName}") + + # the file response should return our created file exactly + if not (fileResponse == "${fileContent}\n"): + raise Exception("Unexpected file response") + + # sanity check on the directory listing: we serve a directory and a file + # via gopher, so the directory listing should have exactly two entries, + # one with gopher file type 0 (file) and one with file type 1 (directory). + dirResponse = client.succeed("curl -s gopher://${gopherHost}") + dirEntries = [l[0] for l in dirResponse.split("\n") if len(l) > 0] + dirEntries.sort() + + if not (["0", "1"] == dirEntries): + raise Exception("Unexpected directory response") + ''; + }) diff --git a/nixos/tests/specialisation.nix b/nixos/tests/specialisation.nix new file mode 100644 index 0000000000000..b8d4b8279f4da --- /dev/null +++ b/nixos/tests/specialisation.nix @@ -0,0 +1,43 @@ +import ./make-test-python.nix { + name = "specialisation"; + nodes = { + inheritconf = { pkgs, ... }: { + environment.systemPackages = [ pkgs.cowsay ]; + specialisation.inheritconf.configuration = { pkgs, ... }: { + environment.systemPackages = [ pkgs.hello ]; + }; + }; + noinheritconf = { pkgs, ... }: { + environment.systemPackages = [ pkgs.cowsay ]; + specialisation.noinheritconf = { + inheritParentConfig = false; + configuration = { pkgs, ... }: { + environment.systemPackages = [ pkgs.hello ]; + }; + }; + }; + }; + testScript = '' + inheritconf.wait_for_unit("default.target") + inheritconf.succeed("cowsay hey") + inheritconf.fail("hello") + + with subtest("Nested clones do inherit from parent"): + inheritconf.succeed( + "/run/current-system/specialisation/inheritconf/bin/switch-to-configuration test" + ) + inheritconf.succeed("cowsay hey") + inheritconf.succeed("hello") + + noinheritconf.wait_for_unit("default.target") + noinheritconf.succeed("cowsay hey") + noinheritconf.fail("hello") + + with subtest("Nested children do not inherit from parent"): + noinheritconf.succeed( + "/run/current-system/specialisation/noinheritconf/bin/switch-to-configuration test" + ) + noinheritconf.fail("cowsay hey") + noinheritconf.succeed("hello") + ''; +} diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix index 0dba3697980f9..9ef96cec5ef36 100644 --- a/nixos/tests/switch-test.nix +++ b/nixos/tests/switch-test.nix @@ -1,6 +1,6 @@ # Test configuration switching. -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, ...} : { name = "switch-test"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ gleber ]; @@ -22,13 +22,17 @@ import ./make-test.nix ({ pkgs, ...} : { # Ensures failures pass through using pipefail, otherwise failing to # switch-to-configuration is hidden by the success of `tee`. stderrRunner = pkgs.writeScript "stderr-runner" '' - #! ${pkgs.stdenv.shell} + #! ${pkgs.runtimeShell} set -e set -o pipefail exec env -i "$@" | tee /dev/stderr ''; in '' - $machine->succeed("${stderrRunner} ${originalSystem}/bin/switch-to-configuration test"); - $machine->succeed("${stderrRunner} ${otherSystem}/bin/switch-to-configuration test"); + machine.succeed( + "${stderrRunner} ${originalSystem}/bin/switch-to-configuration test" + ) + machine.succeed( + "${stderrRunner} ${otherSystem}/bin/switch-to-configuration test" + ) ''; }) diff --git a/nixos/tests/sympa.nix b/nixos/tests/sympa.nix new file mode 100644 index 0000000000000..280691f7cb402 --- /dev/null +++ b/nixos/tests/sympa.nix @@ -0,0 +1,36 @@ +import ./make-test-python.nix ({ pkgs, lib, ... }: { + name = "sympa"; + meta.maintainers = with lib.maintainers; [ mmilata ]; + + machine = + { ... }: + { + virtualisation.memorySize = 1024; + + services.sympa = { + enable = true; + domains = { + "lists.example.org" = { + webHost = "localhost"; + }; + }; + listMasters = [ "joe@example.org" ]; + web.enable = true; + web.https = false; + database = { + type = "PostgreSQL"; + createLocally = true; + }; + }; + }; + + testScript = '' + start_all() + + machine.wait_for_unit("sympa.service") + machine.wait_for_unit("wwsympa.service") + assert "Mailing lists service" in machine.succeed( + "curl --insecure -L http://localhost/" + ) + ''; +}) diff --git a/nixos/tests/systemd-analyze.nix b/nixos/tests/systemd-analyze.nix new file mode 100644 index 0000000000000..a78ba08cd55cb --- /dev/null +++ b/nixos/tests/systemd-analyze.nix @@ -0,0 +1,46 @@ +import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... }: + +{ + name = "systemd-analyze"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ raskin ]; + }; + + machine = + { pkgs, lib, ... }: + { boot.kernelPackages = lib.mkIf latestKernel pkgs.linuxPackages_latest; + sound.enable = true; # needed for the factl test, /dev/snd/* exists without them but udev doesn't care then + }; + + testScript = '' + machine.wait_for_unit("multi-user.target") + + # We create a special output directory to copy it as a whole + with subtest("Prepare output dir"): + machine.succeed("mkdir systemd-analyze") + + + # Save the output into a file with given name inside the common + # output directory + def run_systemd_analyze(args, name): + tgt_dir = "systemd-analyze" + machine.succeed( + "systemd-analyze {} > {}/{} 2> {}/{}.err".format( + " ".join(args), tgt_dir, name, tgt_dir, name + ) + ) + + + with subtest("Print statistics"): + run_systemd_analyze(["blame"], "blame.txt") + run_systemd_analyze(["critical-chain"], "critical-chain.txt") + run_systemd_analyze(["dot"], "dependencies.dot") + run_systemd_analyze(["plot"], "systemd-analyze.svg") + + # We copy the main graph into the $out (toplevel), and we also copy + # the entire output directory with additional data + with subtest("Copying the resulting data into $out"): + machine.copy_from_vm("systemd-analyze/", "") + machine.copy_from_vm("systemd-analyze/systemd-analyze.svg", "") + ''; +}) diff --git a/nixos/tests/systemd-confinement.nix b/nixos/tests/systemd-confinement.nix index b7b10fb36aac4..ebf6d218fd68c 100644 --- a/nixos/tests/systemd-confinement.nix +++ b/nixos/tests/systemd-confinement.nix @@ -1,23 +1,23 @@ -import ./make-test.nix { +import ./make-test-python.nix { name = "systemd-confinement"; machine = { pkgs, lib, ... }: let testServer = pkgs.writeScript "testserver.sh" '' - #!${pkgs.stdenv.shell} + #!${pkgs.runtimeShell} export PATH=${lib.escapeShellArg "${pkgs.coreutils}/bin"} - ${lib.escapeShellArg pkgs.stdenv.shell} 2>&1 + ${lib.escapeShellArg pkgs.runtimeShell} 2>&1 echo "exit-status:$?" ''; testClient = pkgs.writeScriptBin "chroot-exec" '' - #!${pkgs.stdenv.shell} -e + #!${pkgs.runtimeShell} -e output="$(echo "$@" | nc -NU "/run/test$(< /teststep).sock")" ret="$(echo "$output" | sed -nre '$s/^exit-status:([0-9]+)$/\1/p')" echo "$output" | head -n -1 exit "''${ret:-1}" ''; - mkTestStep = num: { description, config ? {}, testScript }: { + mkTestStep = num: { config ? {}, testScript }: { systemd.sockets."test${toString num}" = { description = "Socket for Test Service ${toString num}"; wantedBy = [ "sockets.target" ]; @@ -34,52 +34,48 @@ import ./make-test.nix { }; } // removeAttrs config [ "confinement" "serviceConfig" ]; - __testSteps = lib.mkOrder num '' - subtest '${lib.escape ["\\" "'"] description}', sub { - $machine->succeed('echo ${toString num} > /teststep'); - ${testScript} - }; - ''; + __testSteps = lib.mkOrder num ('' + machine.succeed("echo ${toString num} > /teststep") + '' + testScript); }; in { imports = lib.imap1 mkTestStep [ - { description = "chroot-only confinement"; - config.confinement.mode = "chroot-only"; + { config.confinement.mode = "chroot-only"; testScript = '' - $machine->succeed( - 'test "$(chroot-exec ls -1 / | paste -sd,)" = bin,nix', - 'test "$(chroot-exec id -u)" = 0', - 'chroot-exec chown 65534 /bin', - ); + with subtest("chroot-only confinement"): + machine.succeed( + 'test "$(chroot-exec ls -1 / | paste -sd,)" = bin,nix', + 'test "$(chroot-exec id -u)" = 0', + "chroot-exec chown 65534 /bin", + ) ''; } - { description = "full confinement with APIVFS"; - testScript = '' - $machine->fail( - 'chroot-exec ls -l /etc', - 'chroot-exec ls -l /run', - 'chroot-exec chown 65534 /bin', - ); - $machine->succeed( - 'test "$(chroot-exec id -u)" = 0', - 'chroot-exec chown 0 /bin', - ); + { testScript = '' + with subtest("full confinement with APIVFS"): + machine.fail( + "chroot-exec ls -l /etc", + "chroot-exec ls -l /run", + "chroot-exec chown 65534 /bin", + ) + machine.succeed( + 'test "$(chroot-exec id -u)" = 0', "chroot-exec chown 0 /bin", + ) ''; } - { description = "check existence of bind-mounted /etc"; - config.serviceConfig.BindReadOnlyPaths = [ "/etc" ]; + { config.serviceConfig.BindReadOnlyPaths = [ "/etc" ]; testScript = '' - $machine->succeed('test -n "$(chroot-exec cat /etc/passwd)"'); + with subtest("check existence of bind-mounted /etc"): + machine.succeed('test -n "$(chroot-exec cat /etc/passwd)"') ''; } - { description = "check if User/Group really runs as non-root"; - config.serviceConfig.User = "chroot-testuser"; + { config.serviceConfig.User = "chroot-testuser"; config.serviceConfig.Group = "chroot-testgroup"; testScript = '' - $machine->succeed('chroot-exec ls -l /dev'); - $machine->succeed('test "$(chroot-exec id -u)" != 0'); - $machine->fail('chroot-exec touch /bin/test'); + with subtest("check if User/Group really runs as non-root"): + machine.succeed("chroot-exec ls -l /dev") + machine.succeed('test "$(chroot-exec id -u)" != 0') + machine.fail("chroot-exec touch /bin/test") ''; } (let @@ -87,62 +83,60 @@ import ./make-test.nix { target = pkgs.writeText "symlink-target" "got me\n"; } "ln -s \"$target\" \"$out\""; in { - description = "check if symlinks are properly bind-mounted"; config.confinement.packages = lib.singleton symlink; testScript = '' - $machine->fail('chroot-exec test -e /etc'); - $machine->succeed('chroot-exec cat ${symlink} >&2'); - $machine->succeed('test "$(chroot-exec cat ${symlink})" = "got me"'); + with subtest("check if symlinks are properly bind-mounted"): + machine.fail("chroot-exec test -e /etc") + machine.succeed( + "chroot-exec cat ${symlink} >&2", + 'test "$(chroot-exec cat ${symlink})" = "got me"', + ) ''; }) - { description = "check if StateDirectory works"; - config.serviceConfig.User = "chroot-testuser"; + { config.serviceConfig.User = "chroot-testuser"; config.serviceConfig.Group = "chroot-testgroup"; config.serviceConfig.StateDirectory = "testme"; testScript = '' - $machine->succeed('chroot-exec touch /tmp/canary'); - $machine->succeed('chroot-exec "echo works > /var/lib/testme/foo"'); - $machine->succeed('test "$(< /var/lib/testme/foo)" = works'); - $machine->succeed('test ! -e /tmp/canary'); + with subtest("check if StateDirectory works"): + machine.succeed("chroot-exec touch /tmp/canary") + machine.succeed('chroot-exec "echo works > /var/lib/testme/foo"') + machine.succeed('test "$(< /var/lib/testme/foo)" = works') + machine.succeed("test ! -e /tmp/canary") ''; } - { description = "check if /bin/sh works"; - testScript = '' - $machine->succeed( - 'chroot-exec test -e /bin/sh', - 'test "$(chroot-exec \'/bin/sh -c "echo bar"\')" = bar', - ); + { testScript = '' + with subtest("check if /bin/sh works"): + machine.succeed( + "chroot-exec test -e /bin/sh", + 'test "$(chroot-exec \'/bin/sh -c "echo bar"\')" = bar', + ) ''; } - { description = "check if suppressing /bin/sh works"; - config.confinement.binSh = null; + { config.confinement.binSh = null; testScript = '' - $machine->succeed( - 'chroot-exec test ! -e /bin/sh', - 'test "$(chroot-exec \'/bin/sh -c "echo foo"\')" != foo', - ); + with subtest("check if suppressing /bin/sh works"): + machine.succeed("chroot-exec test ! -e /bin/sh") + machine.succeed('test "$(chroot-exec \'/bin/sh -c "echo foo"\')" != foo') ''; } - { description = "check if we can set /bin/sh to something different"; - config.confinement.binSh = "${pkgs.hello}/bin/hello"; + { config.confinement.binSh = "${pkgs.hello}/bin/hello"; testScript = '' - $machine->succeed( - 'chroot-exec test -e /bin/sh', - 'test "$(chroot-exec /bin/sh -g foo)" = foo', - ); + with subtest("check if we can set /bin/sh to something different"): + machine.succeed("chroot-exec test -e /bin/sh") + machine.succeed('test "$(chroot-exec /bin/sh -g foo)" = foo') ''; } - { description = "check if only Exec* dependencies are included"; - config.environment.FOOBAR = pkgs.writeText "foobar" "eek\n"; + { config.environment.FOOBAR = pkgs.writeText "foobar" "eek\n"; testScript = '' - $machine->succeed('test "$(chroot-exec \'cat "$FOOBAR"\')" != eek'); + with subtest("check if only Exec* dependencies are included"): + machine.succeed('test "$(chroot-exec \'cat "$FOOBAR"\')" != eek') ''; } - { description = "check if all unit dependencies are included"; - config.environment.FOOBAR = pkgs.writeText "foobar" "eek\n"; + { config.environment.FOOBAR = pkgs.writeText "foobar" "eek\n"; config.confinement.fullUnit = true; testScript = '' - $machine->succeed('test "$(chroot-exec \'cat "$FOOBAR"\')" = eek'); + with subtest("check if all unit dependencies are included"): + machine.succeed('test "$(chroot-exec \'cat "$FOOBAR"\')" = eek') ''; } ]; @@ -162,7 +156,6 @@ import ./make-test.nix { }; testScript = { nodes, ... }: '' - $machine->waitForUnit('multi-user.target'); - ${nodes.machine.config.__testSteps} - ''; + machine.wait_for_unit("multi-user.target") + '' + nodes.machine.config.__testSteps; } diff --git a/nixos/tests/systemd-networkd-dhcpserver.nix b/nixos/tests/systemd-networkd-dhcpserver.nix new file mode 100644 index 0000000000000..f1a2662f8cb4b --- /dev/null +++ b/nixos/tests/systemd-networkd-dhcpserver.nix @@ -0,0 +1,58 @@ +# This test predominantly tests systemd-networkd DHCP server, by +# setting up a DHCP server and client, and ensuring they are mutually +# reachable via the DHCP allocated address. +import ./make-test-python.nix ({pkgs, ...}: { + name = "systemd-networkd-dhcpserver"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ tomfitzhenry ]; + }; + nodes = { + router = { config, pkgs, ... }: { + virtualisation.vlans = [ 1 ]; + systemd.services.systemd-networkd.environment.SYSTEMD_LOG_LEVEL = "debug"; + networking = { + useNetworkd = true; + useDHCP = false; + firewall.enable = false; + }; + systemd.network = { + networks = { + # systemd-networkd will load the first network unit file + # that matches, ordered lexiographically by filename. + # /etc/systemd/network/{40-eth1,99-main}.network already + # exists. This network unit must be loaded for the test, + # however, hence why this network is named such. + "01-eth1" = { + name = "eth1"; + networkConfig = { + DHCPServer = true; + Address = "10.0.0.1/24"; + }; + dhcpServerConfig = { + PoolOffset = 100; + PoolSize = 1; + }; + }; + }; + }; + }; + + client = { config, pkgs, ... }: { + virtualisation.vlans = [ 1 ]; + systemd.services.systemd-networkd.environment.SYSTEMD_LOG_LEVEL = "debug"; + networking = { + useNetworkd = true; + useDHCP = false; + firewall.enable = false; + interfaces.eth1.useDHCP = true; + }; + }; + }; + testScript = { ... }: '' + start_all() + router.wait_for_unit("systemd-networkd-wait-online.service") + client.wait_for_unit("systemd-networkd-wait-online.service") + client.wait_until_succeeds("ping -c 5 10.0.0.1") + router.wait_until_succeeds("ping -c 5 10.0.0.100") + ''; +}) diff --git a/nixos/tests/systemd-networkd-vrf.nix b/nixos/tests/systemd-networkd-vrf.nix new file mode 100644 index 0000000000000..af7813a2e604a --- /dev/null +++ b/nixos/tests/systemd-networkd-vrf.nix @@ -0,0 +1,221 @@ +import ./make-test-python.nix ({ pkgs, lib, ... }: let + inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey; +in { + name = "systemd-networkd-vrf"; + meta.maintainers = with lib.maintainers; [ ma27 ]; + + nodes = { + client = { pkgs, ... }: { + virtualisation.vlans = [ 1 2 ]; + + networking = { + useDHCP = false; + useNetworkd = true; + firewall.checkReversePath = "loose"; + }; + + systemd.network = { + enable = true; + + netdevs."10-vrf1" = { + netdevConfig = { + Kind = "vrf"; + Name = "vrf1"; + MTUBytes = "1300"; + }; + vrfConfig.Table = 23; + }; + netdevs."10-vrf2" = { + netdevConfig = { + Kind = "vrf"; + Name = "vrf2"; + MTUBytes = "1300"; + }; + vrfConfig.Table = 42; + }; + + networks."10-vrf1" = { + matchConfig.Name = "vrf1"; + networkConfig.IPForward = "yes"; + routes = [ + { routeConfig = { Destination = "192.168.1.2"; Metric = "100"; }; } + ]; + }; + networks."10-vrf2" = { + matchConfig.Name = "vrf2"; + networkConfig.IPForward = "yes"; + routes = [ + { routeConfig = { Destination = "192.168.2.3"; Metric = "100"; }; } + ]; + }; + + networks."10-eth1" = { + matchConfig.Name = "eth1"; + linkConfig.RequiredForOnline = "no"; + networkConfig = { + VRF = "vrf1"; + Address = "192.168.1.1"; + IPForward = "yes"; + }; + }; + networks."10-eth2" = { + matchConfig.Name = "eth2"; + linkConfig.RequiredForOnline = "no"; + networkConfig = { + VRF = "vrf2"; + Address = "192.168.2.1"; + IPForward = "yes"; + }; + }; + }; + }; + + node1 = { pkgs, ... }: { + virtualisation.vlans = [ 1 ]; + networking = { + useDHCP = false; + useNetworkd = true; + }; + + services.openssh.enable = true; + users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; + + systemd.network = { + enable = true; + + networks."10-eth1" = { + matchConfig.Name = "eth1"; + linkConfig.RequiredForOnline = "no"; + networkConfig = { + Address = "192.168.1.2"; + IPForward = "yes"; + }; + }; + }; + }; + + node2 = { pkgs, ... }: { + virtualisation.vlans = [ 2 ]; + networking = { + useDHCP = false; + useNetworkd = true; + }; + + systemd.network = { + enable = true; + + networks."10-eth2" = { + matchConfig.Name = "eth2"; + linkConfig.RequiredForOnline = "no"; + networkConfig = { + Address = "192.168.2.3"; + IPForward = "yes"; + }; + }; + }; + }; + + node3 = { pkgs, ... }: { + virtualisation.vlans = [ 2 ]; + networking = { + useDHCP = false; + useNetworkd = true; + }; + + systemd.network = { + enable = true; + + networks."10-eth2" = { + matchConfig.Name = "eth2"; + linkConfig.RequiredForOnline = "no"; + networkConfig = { + Address = "192.168.2.4"; + IPForward = "yes"; + }; + }; + }; + }; + }; + + testScript = '' + def compare_tables(expected, actual): + assert ( + expected == actual + ), """ + Routing tables don't match! + Expected: + {} + Actual: + {} + """.format( + expected, actual + ) + + + start_all() + + client.wait_for_unit("network.target") + node1.wait_for_unit("network.target") + node2.wait_for_unit("network.target") + node3.wait_for_unit("network.target") + + client_ipv4_table = """ + 192.168.1.2 dev vrf1 proto static metric 100 + 192.168.2.3 dev vrf2 proto static metric 100 + """.strip() + vrf1_table = """ + broadcast 192.168.1.0 dev eth1 proto kernel scope link src 192.168.1.1 + 192.168.1.0/24 dev eth1 proto kernel scope link src 192.168.1.1 + local 192.168.1.1 dev eth1 proto kernel scope host src 192.168.1.1 + broadcast 192.168.1.255 dev eth1 proto kernel scope link src 192.168.1.1 + """.strip() + vrf2_table = """ + broadcast 192.168.2.0 dev eth2 proto kernel scope link src 192.168.2.1 + 192.168.2.0/24 dev eth2 proto kernel scope link src 192.168.2.1 + local 192.168.2.1 dev eth2 proto kernel scope host src 192.168.2.1 + broadcast 192.168.2.255 dev eth2 proto kernel scope link src 192.168.2.1 + """.strip() + + # Check that networkd properly configures the main routing table + # and the routing tables for the VRF. + with subtest("check vrf routing tables"): + compare_tables( + client_ipv4_table, client.succeed("ip -4 route list | head -n2").strip() + ) + compare_tables( + vrf1_table, client.succeed("ip -4 route list table 23 | head -n4").strip() + ) + compare_tables( + vrf2_table, client.succeed("ip -4 route list table 42 | head -n4").strip() + ) + + # Ensure that other nodes are reachable via ICMP through the VRF. + with subtest("icmp through vrf works"): + client.succeed("ping -c5 192.168.1.2") + client.succeed("ping -c5 192.168.2.3") + + # Test whether SSH through a VRF IP is possible. + # (Note: this seems to be an issue on Linux 5.x, so I decided to add this to + # ensure that we catch this when updating the default kernel). + # with subtest("tcp traffic through vrf works"): + # node1.wait_for_open_port(22) + # client.succeed( + # "cat ${snakeOilPrivateKey} > privkey.snakeoil" + # ) + # client.succeed("chmod 600 privkey.snakeoil") + # client.succeed( + # "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil root@192.168.1.2 true" + # ) + + # Only configured routes through the VRF from the main routing table should + # work. Additional IPs are only reachable when binding to the vrf interface. + with subtest("only routes from main routing table work by default"): + client.fail("ping -c5 192.168.2.4") + client.succeed("ping -I vrf2 -c5 192.168.2.4") + + client.shutdown() + node1.shutdown() + node2.shutdown() + node3.shutdown() + ''; +}) diff --git a/nixos/tests/systemd-networkd-wireguard.nix b/nixos/tests/systemd-networkd.nix index be5c0da981d2d..319e5e94eceb1 100644 --- a/nixos/tests/systemd-networkd-wireguard.nix +++ b/nixos/tests/systemd-networkd.nix @@ -41,15 +41,25 @@ let generateNodeConf = { lib, pkgs, config, privk, pubk, peerId, nodeId, ...}: { { routeConfig = { Gateway = "10.0.0.${nodeId}"; Destination = "10.0.0.0/24"; }; } ]; }; - "90-eth1" = { + "30-eth1" = { matchConfig = { Name = "eth1"; }; - address = [ "192.168.1.${nodeId}/24" ]; + address = [ + "192.168.1.${nodeId}/24" + "fe80::${nodeId}/64" + ]; + routingPolicyRules = [ + { routingPolicyRuleConfig = { Table = 10; IncomingInterface = "eth1"; Family = "both"; };} + { routingPolicyRuleConfig = { Table = 20; OutgoingInterface = "eth1"; };} + { routingPolicyRuleConfig = { Table = 30; From = "192.168.1.1"; To = "192.168.1.2"; SourcePort = 666 ; DestinationPort = 667; };} + { routingPolicyRuleConfig = { Table = 40; IPProtocol = "tcp"; InvertRule = true; };} + { routingPolicyRuleConfig = { Table = 50; IncomingInterface = "eth1"; Family = "ipv4"; };} + ]; }; }; }; }; in import ./make-test-python.nix ({pkgs, ... }: { - name = "networkd-wireguard"; + name = "networkd"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ ninjatrappeur ]; }; @@ -76,9 +86,28 @@ testScript = '' start_all() node1.wait_for_unit("systemd-networkd-wait-online.service") node2.wait_for_unit("systemd-networkd-wait-online.service") + + # ================================ + # Wireguard + # ================================ node1.succeed("ping -c 5 10.0.0.2") node2.succeed("ping -c 5 10.0.0.1") # Is the fwmark set? node2.succeed("wg | grep -q 42") + + # ================================ + # Routing Policies + # ================================ + # Testing all the routingPolicyRuleConfig members: + # Table + IncomingInterface + node1.succeed("sudo ip rule | grep 'from all iif eth1 lookup 10'") + # OutgoingInterface + node1.succeed("sudo ip rule | grep 'from all oif eth1 lookup 20'") + # From + To + SourcePort + DestinationPort + node1.succeed( + "sudo ip rule | grep 'from 192.168.1.1 to 192.168.1.2 sport 666 dport 667 lookup 30'" + ) + # IPProtocol + InvertRule + node1.succeed("sudo ip rule | grep 'not from all ipproto tcp lookup 40'") ''; }) diff --git a/nixos/tests/systemd-timesyncd.nix b/nixos/tests/systemd-timesyncd.nix index d12b8eb2bf7eb..ad5b9a47383bd 100644 --- a/nixos/tests/systemd-timesyncd.nix +++ b/nixos/tests/systemd-timesyncd.nix @@ -1,7 +1,7 @@ # Regression test for systemd-timesync having moved the state directory without # upstream providing a migration path. https://github.com/systemd/systemd/issues/12131 -import ./make-test.nix (let +import ./make-test-python.nix (let common = { lib, ... }: { # override the `false` value from the qemu-vm base profile services.timesyncd.enable = lib.mkForce true; @@ -25,28 +25,28 @@ in { }; testScript = '' - startAll; - $current->succeed('systemctl status systemd-timesyncd.service'); + start_all() + current.succeed("systemctl status systemd-timesyncd.service") # on a new install with a recent systemd there should not be any # leftovers from the dynamic user mess - $current->succeed('test -e /var/lib/systemd/timesync'); - $current->succeed('test ! -L /var/lib/systemd/timesync'); + current.succeed("test -e /var/lib/systemd/timesync") + current.succeed("test ! -L /var/lib/systemd/timesync") # timesyncd should be running on the upgrading system since we fixed the # file bits in the activation script - $pre1909->succeed('systemctl status systemd-timesyncd.service'); + pre1909.succeed("systemctl status systemd-timesyncd.service") # the path should be gone after the migration - $pre1909->succeed('test ! -e /var/lib/private/systemd/timesync'); + pre1909.succeed("test ! -e /var/lib/private/systemd/timesync") # and the new path should no longer be a symlink - $pre1909->succeed('test -e /var/lib/systemd/timesync'); - $pre1909->succeed('test ! -L /var/lib/systemd/timesync'); + pre1909.succeed("test -e /var/lib/systemd/timesync") + pre1909.succeed("test ! -L /var/lib/systemd/timesync") # after a restart things should still work and not fail in the activation # scripts and cause the boot to fail.. - $pre1909->shutdown; - $pre1909->start; - $pre1909->succeed('systemctl status systemd-timesyncd.service'); + pre1909.shutdown() + pre1909.start() + pre1909.succeed("systemctl status systemd-timesyncd.service") ''; }) diff --git a/nixos/tests/systemd.nix b/nixos/tests/systemd.nix index 4b71b4d67597d..ca2e36a443e99 100644 --- a/nixos/tests/systemd.nix +++ b/nixos/tests/systemd.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, ... }: { +import ./make-test-python.nix ({ pkgs, ... }: { name = "systemd"; machine = { lib, ... }: { @@ -19,10 +19,10 @@ import ./make-test.nix ({ pkgs, ... }: { systemd.extraConfig = "DefaultEnvironment=\"XXX_SYSTEM=foo\""; systemd.user.extraConfig = "DefaultEnvironment=\"XXX_USER=bar\""; services.journald.extraConfig = "Storage=volatile"; - services.xserver.displayManager.auto.user = "alice"; + test-support.displayManager.auto.user = "alice"; systemd.shutdown.test = pkgs.writeScript "test.shutdown" '' - #!${pkgs.stdenv.shell} + #!${pkgs.runtimeShell} PATH=${lib.makeBinPath (with pkgs; [ utillinux coreutils ])} mount -t 9p shared -o trans=virtio,version=9p2000.L /tmp/shared touch /tmp/shared/shutdown-test @@ -53,50 +53,69 @@ import ./make-test.nix ({ pkgs, ... }: { }; testScript = '' - $machine->waitForX; + import re + import subprocess + + machine.wait_for_x() # wait for user services - $machine->waitForUnit("default.target","alice"); + machine.wait_for_unit("default.target", "alice") # Regression test for https://github.com/NixOS/nixpkgs/issues/35415 - subtest "configuration files are recognized by systemd", sub { - $machine->succeed('test -e /system_conf_read'); - $machine->succeed('test -e /home/alice/user_conf_read'); - $machine->succeed('test -z $(ls -1 /var/log/journal)'); - }; + with subtest("configuration files are recognized by systemd"): + machine.succeed("test -e /system_conf_read") + machine.succeed("test -e /home/alice/user_conf_read") + machine.succeed("test -z $(ls -1 /var/log/journal)") # Regression test for https://github.com/NixOS/nixpkgs/issues/50273 - subtest "DynamicUser actually allocates a user", sub { - $machine->succeed('systemd-run --pty --property=Type=oneshot --property=DynamicUser=yes --property=User=iamatest whoami | grep iamatest'); - }; + with subtest("DynamicUser actually allocates a user"): + assert "iamatest" in machine.succeed( + "systemd-run --pty --property=Type=oneshot --property=DynamicUser=yes --property=User=iamatest whoami" + ) # Regression test for https://github.com/NixOS/nixpkgs/issues/35268 - subtest "file system with x-initrd.mount is not unmounted", sub { - $machine->succeed('mountpoint -q /test-x-initrd-mount'); - $machine->shutdown; - system('qemu-img', 'convert', '-O', 'raw', - 'vm-state-machine/empty2.qcow2', 'x-initrd-mount.raw'); - my $extinfo = `${pkgs.e2fsprogs}/bin/dumpe2fs x-initrd-mount.raw`; - die "File system was not cleanly unmounted: $extinfo" - unless $extinfo =~ /^Filesystem state: *clean$/m; - }; + with subtest("file system with x-initrd.mount is not unmounted"): + machine.succeed("mountpoint -q /test-x-initrd-mount") + machine.shutdown() - subtest "systemd-shutdown works", sub { - $machine->shutdown; - $machine->waitForUnit('multi-user.target'); - $machine->succeed('test -e /tmp/shared/shutdown-test'); - }; + subprocess.check_call( + [ + "qemu-img", + "convert", + "-O", + "raw", + "vm-state-machine/empty0.qcow2", + "x-initrd-mount.raw", + ] + ) + extinfo = subprocess.check_output( + [ + "${pkgs.e2fsprogs}/bin/dumpe2fs", + "x-initrd-mount.raw", + ] + ).decode("utf-8") + assert ( + re.search(r"^Filesystem state: *clean$", extinfo, re.MULTILINE) is not None + ), ("File system was not cleanly unmounted: " + extinfo) + + with subtest("systemd-shutdown works"): + machine.shutdown() + machine.wait_for_unit("multi-user.target") + machine.succeed("test -e /tmp/shared/shutdown-test") + + # Test settings from /etc/sysctl.d/50-default.conf are applied + with subtest("systemd sysctl settings are applied"): + machine.wait_for_unit("multi-user.target") + assert "fq_codel" in machine.succeed("sysctl net.core.default_qdisc") + + # Test cgroup accounting is enabled + with subtest("systemd cgroup accounting is enabled"): + machine.wait_for_unit("multi-user.target") + assert "yes" in machine.succeed( + "systemctl show testservice1.service -p IOAccounting" + ) - # Test settings from /etc/sysctl.d/50-default.conf are applied - subtest "systemd sysctl settings are applied", sub { - $machine->waitForUnit('multi-user.target'); - $machine->succeed('sysctl net.core.default_qdisc | grep -q "fq_codel"'); - }; - - # Test cgroup accounting is enabled - subtest "systemd cgroup accounting is enabled", sub { - $machine->waitForUnit('multi-user.target'); - $machine->succeed('systemctl show testservice1.service -p IOAccounting | grep -q "yes"'); - $machine->succeed('systemctl status testservice1.service | grep -q "CPU:"'); - }; + retcode, output = machine.execute("systemctl status testservice1.service") + assert retcode in [0, 3] # https://bugs.freedesktop.org/show_bug.cgi?id=77507 + assert "CPU:" in output ''; }) diff --git a/nixos/tests/tiddlywiki.nix b/nixos/tests/tiddlywiki.nix index 4a2014a4ec918..cf45578b0f989 100644 --- a/nixos/tests/tiddlywiki.nix +++ b/nixos/tests/tiddlywiki.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ ... }: { +import ./make-test-python.nix ({ ... }: { name = "tiddlywiki"; nodes = { default = { @@ -20,48 +20,50 @@ import ./make-test.nix ({ ... }: { }; }; - testScript = '' - startAll; + testScript = + '' + start_all() - subtest "by default works without configuration", sub { - $default->waitForUnit("tiddlywiki.service"); - }; - - subtest "by default available on port 8080 without auth", sub { - $default->waitForUnit("tiddlywiki.service"); - $default->waitForOpenPort(8080); - $default->succeed("curl --fail 127.0.0.1:8080"); - }; - - subtest "by default creates empty wiki", sub { - $default->succeed("test -f /var/lib/tiddlywiki/tiddlywiki.info"); - }; + with subtest("by default works without configuration"): + default.wait_for_unit("tiddlywiki.service") - subtest "configured on port 3000 with basic auth", sub { - $configured->waitForUnit("tiddlywiki.service"); - $configured->waitForOpenPort(3000); - $configured->fail("curl --fail 127.0.0.1:3000"); - $configured->succeed("curl --fail 127.0.0.1:3000 --user somelogin:somesecret"); - }; + with subtest("by default available on port 8080 without auth"): + default.wait_for_unit("tiddlywiki.service") + default.wait_for_open_port(8080) + # we output to /dev/null here to avoid a python UTF-8 decode error + # but the check will still fail if the service doesn't respond + default.succeed("curl --fail -o /dev/null 127.0.0.1:8080") - subtest "configured with different wikifolder", sub { - $configured->succeed("test -f /var/lib/tiddlywiki/tiddlywiki.info"); - }; + with subtest("by default creates empty wiki"): + default.succeed("test -f /var/lib/tiddlywiki/tiddlywiki.info") - subtest "restart preserves changes", sub { - # given running wiki - $default->waitForUnit("tiddlywiki.service"); - # with some changes - $default->succeed("curl --fail --request PUT --header 'X-Requested-With:TiddlyWiki' --data '{ \"title\": \"title\", \"text\": \"content\" }' --url 127.0.0.1:8080/recipes/default/tiddlers/somepage "); - $default->succeed("sleep 2"); # server syncs to filesystem on timer + with subtest("configured on port 3000 with basic auth"): + configured.wait_for_unit("tiddlywiki.service") + configured.wait_for_open_port(3000) + configured.fail("curl --fail -o /dev/null 127.0.0.1:3000") + configured.succeed( + "curl --fail -o /dev/null 127.0.0.1:3000 --user somelogin:somesecret" + ) + + with subtest("restart preserves changes"): + # given running wiki + default.wait_for_unit("tiddlywiki.service") + # with some changes + default.succeed( + 'curl --fail --request PUT --header \'X-Requested-With:TiddlyWiki\' \ + --data \'{ "title": "title", "text": "content" }\' \ + --url 127.0.0.1:8080/recipes/default/tiddlers/somepage ' + ) + default.succeed("sleep 2") - # when wiki is cycled - $default->systemctl("restart tiddlywiki.service"); - $default->waitForUnit("tiddlywiki.service"); - $default->waitForOpenPort(8080); + # when wiki is cycled + default.systemctl("restart tiddlywiki.service") + default.wait_for_unit("tiddlywiki.service") + default.wait_for_open_port(8080) - # the change is preserved - $default->succeed("curl --fail 127.0.0.1:8080/recipes/default/tiddlers/somepage"); - }; - ''; + # the change is preserved + default.succeed( + "curl --fail -o /dev/null 127.0.0.1:8080/recipes/default/tiddlers/somepage" + ) + ''; }) diff --git a/nixos/tests/timezone.nix b/nixos/tests/timezone.nix index 2204649a3fc4d..7fc9a5058eee9 100644 --- a/nixos/tests/timezone.nix +++ b/nixos/tests/timezone.nix @@ -1,45 +1,50 @@ -{ - timezone-static = import ./make-test.nix ({ pkgs, ... }: { - name = "timezone-static"; - meta.maintainers = with pkgs.lib.maintainers; [ lheckemann ]; - - machine.time.timeZone = "Europe/Amsterdam"; - - testScript = '' - $machine->waitForUnit("dbus.socket"); - $machine->fail("timedatectl set-timezone Asia/Tokyo"); - my @dateResult = $machine->execute('date -d @0 "+%Y-%m-%d %H:%M:%S"'); - $dateResult[1] eq "1970-01-01 01:00:00\n" or die "Timezone seems to be wrong"; - ''; - }); - - timezone-imperative = import ./make-test.nix ({ pkgs, ... }: { - name = "timezone-imperative"; - meta.maintainers = with pkgs.lib.maintainers; [ lheckemann ]; - - machine.time.timeZone = null; - - testScript = '' - $machine->waitForUnit("dbus.socket"); - - # Should default to UTC - my @dateResult = $machine->execute('date -d @0 "+%Y-%m-%d %H:%M:%S"'); - print $dateResult[1]; - $dateResult[1] eq "1970-01-01 00:00:00\n" or die "Timezone seems to be wrong"; - - $machine->succeed("timedatectl set-timezone Asia/Tokyo"); - - # Adjustment should be taken into account - my @dateResult = $machine->execute('date -d @0 "+%Y-%m-%d %H:%M:%S"'); - print $dateResult[1]; - $dateResult[1] eq "1970-01-01 09:00:00\n" or die "Timezone was not adjusted"; - - # Adjustment should persist across a reboot - $machine->shutdown; - $machine->waitForUnit("dbus.socket"); - my @dateResult = $machine->execute('date -d @0 "+%Y-%m-%d %H:%M:%S"'); - print $dateResult[1]; - $dateResult[1] eq "1970-01-01 09:00:00\n" or die "Timezone adjustment was not persisted"; - ''; - }); -} +import ./make-test-python.nix ({ pkgs, ...} : { + name = "timezone"; + meta.maintainers = with pkgs.lib.maintainers; [ lheckemann ]; + + nodes = { + node_eutz = { pkgs, ... }: { + time.timeZone = "Europe/Amsterdam"; + }; + + node_nulltz = { pkgs, ... }: { + time.timeZone = null; + }; + }; + + testScript = { nodes, ... }: '' + node_eutz.wait_for_unit("dbus.socket") + + with subtest("static - Ensure timezone change gives the correct result"): + node_eutz.fail("timedatectl set-timezone Asia/Tokyo") + date_result = node_eutz.succeed('date -d @0 "+%Y-%m-%d %H:%M:%S"') + assert date_result == "1970-01-01 01:00:00\n", "Timezone seems to be wrong" + + node_nulltz.wait_for_unit("dbus.socket") + + with subtest("imperative - Ensure timezone defaults to UTC"): + date_result = node_nulltz.succeed('date -d @0 "+%Y-%m-%d %H:%M:%S"') + print(date_result) + assert ( + date_result == "1970-01-01 00:00:00\n" + ), "Timezone seems to be wrong (not UTC)" + + with subtest("imperative - Ensure timezone adjustment produces expected result"): + node_nulltz.succeed("timedatectl set-timezone Asia/Tokyo") + + # Adjustment should be taken into account + date_result = node_nulltz.succeed('date -d @0 "+%Y-%m-%d %H:%M:%S"') + print(date_result) + assert date_result == "1970-01-01 09:00:00\n", "Timezone was not adjusted" + + with subtest("imperative - Ensure timezone adjustment persists across reboot"): + # Adjustment should persist across a reboot + node_nulltz.shutdown() + node_nulltz.wait_for_unit("dbus.socket") + date_result = node_nulltz.succeed('date -d @0 "+%Y-%m-%d %H:%M:%S"') + print(date_result) + assert ( + date_result == "1970-01-01 09:00:00\n" + ), "Timezone adjustment was not persisted" + ''; +}) diff --git a/nixos/tests/tinydns.nix b/nixos/tests/tinydns.nix index c7740d5ade353..b80e3451700a6 100644 --- a/nixos/tests/tinydns.nix +++ b/nixos/tests/tinydns.nix @@ -21,6 +21,6 @@ import ./make-test-python.nix ({ lib, ...} : { testScript = '' nameserver.start() nameserver.wait_for_unit("tinydns.service") - nameserver.succeed("host bla.foo.bar | grep '1\.2\.3\.4'") + nameserver.succeed("host bla.foo.bar 192.168.1.1 | grep '1\.2\.3\.4'") ''; }) diff --git a/nixos/tests/traefik.nix b/nixos/tests/traefik.nix new file mode 100644 index 0000000000000..0e21a7cf8437f --- /dev/null +++ b/nixos/tests/traefik.nix @@ -0,0 +1,87 @@ +# Test Traefik as a reverse proxy of a local web service +# and a Docker container. +import ./make-test-python.nix ({ pkgs, ... }: { + name = "traefik"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ joko ]; + }; + + nodes = { + client = { config, pkgs, ... }: { + environment.systemPackages = [ pkgs.curl ]; + }; + traefik = { config, pkgs, ... }: { + docker-containers.nginx = { + extraDockerOptions = [ + "-l" "traefik.enable=true" + "-l" "traefik.http.routers.nginx.entrypoints=web" + "-l" "traefik.http.routers.nginx.rule=Host(`nginx.traefik.test`)" + ]; + image = "nginx-container"; + imageFile = pkgs.dockerTools.examples.nginx; + }; + + networking.firewall.allowedTCPPorts = [ 80 ]; + + services.traefik = { + enable = true; + + dynamicConfigOptions = { + http.routers.simplehttp = { + rule = "Host(`simplehttp.traefik.test`)"; + entryPoints = [ "web" ]; + service = "simplehttp"; + }; + + http.services.simplehttp = { + loadBalancer.servers = [{ + url = "http://127.0.0.1:8000"; + }]; + }; + }; + + staticConfigOptions = { + global = { + checkNewVersion = false; + sendAnonymousUsage = false; + }; + + entryPoints.web.address = ":80"; + + providers.docker.exposedByDefault = false; + }; + }; + + systemd.services.simplehttp = { + script = "${pkgs.python3}/bin/python -m http.server 8000"; + serviceConfig.Type = "simple"; + wantedBy = [ "multi-user.target" ]; + }; + + users.users.traefik.extraGroups = [ "docker" ]; + }; + }; + + testScript = '' + start_all() + + traefik.wait_for_unit("docker-nginx.service") + traefik.wait_until_succeeds("docker ps | grep nginx-container") + traefik.wait_for_unit("simplehttp.service") + traefik.wait_for_unit("traefik.service") + traefik.wait_for_open_port(80) + traefik.wait_for_unit("multi-user.target") + + client.wait_for_unit("multi-user.target") + + with subtest("Check that a container can be reached via Traefik"): + assert "Hello from NGINX" in client.succeed( + "curl -sSf -H Host:nginx.traefik.test http://traefik/" + ) + + with subtest("Check that dynamic configuration works"): + assert "Directory listing for " in client.succeed( + "curl -sSf -H Host:simplehttp.traefik.test http://traefik/" + ) + ''; +}) diff --git a/nixos/tests/trilium-server.nix b/nixos/tests/trilium-server.nix new file mode 100644 index 0000000000000..6346575b33da6 --- /dev/null +++ b/nixos/tests/trilium-server.nix @@ -0,0 +1,53 @@ +import ./make-test-python.nix ({ ... }: { + name = "trilium-server"; + nodes = { + default = { + services.trilium-server.enable = true; + }; + configured = { + services.trilium-server = { + enable = true; + dataDir = "/data/trilium"; + }; + }; + + nginx = { + services.trilium-server = { + enable = true; + nginx.enable = true; + nginx.hostName = "trilium.example.com"; + }; + }; + }; + + testScript = + '' + start_all() + + with subtest("by default works without configuration"): + default.wait_for_unit("trilium-server.service") + + with subtest("by default available on port 8080"): + default.wait_for_unit("trilium-server.service") + default.wait_for_open_port(8080) + # we output to /dev/null here to avoid a python UTF-8 decode error + # but the check will still fail if the service doesn't respond + default.succeed("curl --fail -o /dev/null 127.0.0.1:8080") + + with subtest("by default creates empty document"): + default.wait_for_unit("trilium-server.service") + default.succeed("test -f /var/lib/trilium/document.db") + + with subtest("configured with custom data store"): + configured.wait_for_unit("trilium-server.service") + configured.succeed("test -f /data/trilium/document.db") + + with subtest("nginx with custom host name"): + nginx.wait_for_unit("trilium-server.service") + nginx.wait_for_unit("nginx.service") + + nginx.succeed( + "curl --resolve 'trilium.example.com:80:127.0.0.1' http://trilium.example.com/" + ) + ''; +}) diff --git a/nixos/tests/tuptime.nix b/nixos/tests/tuptime.nix new file mode 100644 index 0000000000000..36ce2b1ae1923 --- /dev/null +++ b/nixos/tests/tuptime.nix @@ -0,0 +1,29 @@ +import ./make-test-python.nix ({ pkgs, ...} : { + name = "tuptime"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ evils ]; + }; + + machine = { pkgs, ... }: { + imports = [ ../modules/profiles/minimal.nix ]; + services.tuptime.enable = true; + }; + + testScript = + '' + # see if it starts + start_all() + machine.wait_for_unit("multi-user.target") + machine.succeed("tuptime | grep 'System startups:[[:blank:]]*1'") + machine.succeed("tuptime | grep 'System uptime:[[:blank:]]*100.0%'") + machine.shutdown() + + # restart machine and see if it correctly reports the reboot + machine.start() + machine.wait_for_unit("multi-user.target") + machine.succeed("tuptime | grep 'System startups:[[:blank:]]*2'") + machine.succeed("tuptime | grep 'System shutdowns:[[:blank:]]*1 ok'") + machine.shutdown() + ''; +}) + diff --git a/nixos/tests/udisks2.nix b/nixos/tests/udisks2.nix index 0cbfa0c4c7bee..64f5b6c40d20d 100644 --- a/nixos/tests/udisks2.nix +++ b/nixos/tests/udisks2.nix @@ -3,7 +3,7 @@ import ./make-test-python.nix ({ pkgs, ... }: let stick = pkgs.fetchurl { - url = http://nixos.org/~eelco/nix/udisks-test.img.xz; + url = "http://nixos.org/~eelco/nix/udisks-test.img.xz"; sha256 = "0was1xgjkjad91nipzclaz5biv3m4b2nk029ga6nk7iklwi19l8b"; }; diff --git a/nixos/tests/upnp.nix b/nixos/tests/upnp.nix index d2e7fdd4fbeb7..a7d837ea0708e 100644 --- a/nixos/tests/upnp.nix +++ b/nixos/tests/upnp.nix @@ -56,9 +56,11 @@ in networking.firewall.enable = false; services.httpd.enable = true; - services.httpd.listen = [{ ip = "*"; port = 9000; }]; - services.httpd.adminAddr = "foo@example.org"; - services.httpd.documentRoot = "/tmp"; + services.httpd.virtualHosts.localhost = { + listen = [{ ip = "*"; port = 9000; }]; + adminAddr = "foo@example.org"; + documentRoot = "/tmp"; + }; }; client2 = diff --git a/nixos/tests/victoriametrics.nix b/nixos/tests/victoriametrics.nix new file mode 100644 index 0000000000000..73ef8b7286153 --- /dev/null +++ b/nixos/tests/victoriametrics.nix @@ -0,0 +1,31 @@ +# This test runs influxdb and checks if influxdb is up and running + +import ./make-test-python.nix ({ pkgs, ...} : { + name = "victoriametrics"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ yorickvp ]; + }; + + nodes = { + one = { ... }: { + services.victoriametrics.enable = true; + }; + }; + + testScript = '' + start_all() + + one.wait_for_unit("victoriametrics.service") + + # write some points and run simple query + out = one.succeed( + "curl -d 'measurement,tag1=value1,tag2=value2 field1=123,field2=1.23' -X POST 'http://localhost:8428/write'" + ) + cmd = """curl -s -G 'http://localhost:8428/api/v1/export' -d 'match={__name__!=""}'""" + # data takes a while to appear + one.wait_until_succeeds(f"[[ $({cmd} | wc -l) -ne 0 ]]") + out = one.succeed(cmd) + assert '"values":[123]' in out + assert '"values":[1.23]' in out + ''; +}) diff --git a/nixos/tests/virtualbox.nix b/nixos/tests/virtualbox.nix index 32637d2c1efe2..aec8da6a2af3c 100644 --- a/nixos/tests/virtualbox.nix +++ b/nixos/tests/virtualbox.nix @@ -23,7 +23,7 @@ let guestAdditions = pkgs.linuxPackages.virtualboxGuestAdditions; miniInit = '' - #!${pkgs.stdenv.shell} -xe + #!${pkgs.runtimeShell} -xe export PATH="${lib.makeBinPath [ pkgs.coreutils pkgs.utillinux ]}" mkdir -p /run/dbus @@ -80,7 +80,7 @@ let touch /mnt-root/boot-done hostname "${vmName}" mkdir -p /nix/store - unshare -m ${escapeShellArg pkgs.stdenv.shell} -c ' + unshare -m ${escapeShellArg pkgs.runtimeShell} -c ' mount -t vboxsf nixstore /nix/store exec "$stage2Init" ' @@ -356,7 +356,7 @@ let virtualisation.qemu.options = if useKvmNestedVirt then ["-cpu" "kvm64,vmx=on"] else []; virtualisation.virtualbox.host.enable = true; - services.xserver.displayManager.auto.user = "alice"; + test-support.displayManager.auto.user = "alice"; users.users.alice.extraGroups = let inherit (config.virtualisation.virtualbox.host) enableHardening; in lib.mkIf enableHardening (lib.singleton "vboxusers"); diff --git a/nixos/tests/wireguard/default.nix b/nixos/tests/wireguard/default.nix index 8206823a9181d..e3bc31c600f95 100644 --- a/nixos/tests/wireguard/default.nix +++ b/nixos/tests/wireguard/default.nix @@ -1,97 +1,71 @@ -let - wg-snakeoil-keys = import ./snakeoil-keys.nix; -in - -import ../make-test-python.nix ({ pkgs, ...} : { - name = "wireguard"; - meta = with pkgs.stdenv.lib.maintainers; { - maintainers = [ ma27 ]; - }; - - nodes = { - peer0 = { lib, ... }: { - boot.kernel.sysctl = { - "net.ipv6.conf.all.forwarding" = "1"; - "net.ipv6.conf.default.forwarding" = "1"; - "net.ipv4.ip_forward" = "1"; - }; - - networking.useDHCP = false; - networking.interfaces.eth1 = { - ipv4.addresses = lib.singleton { - address = "192.168.0.1"; - prefixLength = 24; - }; - ipv6.addresses = lib.singleton { - address = "fd00::1"; - prefixLength = 64; - }; - }; +import ../make-test-python.nix ({ pkgs, lib, ...} : + let + wg-snakeoil-keys = import ./snakeoil-keys.nix; + peer = (import ./make-peer.nix) { inherit lib; }; + in + { + name = "wireguard"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ ma27 ]; + }; - networking.firewall.allowedUDPPorts = [ 23542 ]; - networking.wireguard.interfaces.wg0 = { - ips = [ "10.23.42.1/32" "fc00::1/128" ]; - listenPort = 23542; + nodes = { + peer0 = peer { + ip4 = "192.168.0.1"; + ip6 = "fd00::1"; + extraConfig = { + networking.firewall.allowedUDPPorts = [ 23542 ]; + networking.wireguard.interfaces.wg0 = { + ips = [ "10.23.42.1/32" "fc00::1/128" ]; + listenPort = 23542; - inherit (wg-snakeoil-keys.peer0) privateKey; + inherit (wg-snakeoil-keys.peer0) privateKey; - peers = lib.singleton { - allowedIPs = [ "10.23.42.2/32" "fc00::2/128" ]; + peers = lib.singleton { + allowedIPs = [ "10.23.42.2/32" "fc00::2/128" ]; - inherit (wg-snakeoil-keys.peer1) publicKey; + inherit (wg-snakeoil-keys.peer1) publicKey; + }; + }; }; }; - }; - peer1 = { pkgs, lib, ... }: { - boot.kernel.sysctl = { - "net.ipv6.conf.all.forwarding" = "1"; - "net.ipv6.conf.default.forwarding" = "1"; - "net.ipv4.ip_forward" = "1"; - }; - - networking.useDHCP = false; - networking.interfaces.eth1 = { - ipv4.addresses = lib.singleton { - address = "192.168.0.2"; - prefixLength = 24; - }; - ipv6.addresses = lib.singleton { - address = "fd00::2"; - prefixLength = 64; + peer1 = peer { + ip4 = "192.168.0.2"; + ip6 = "fd00::2"; + extraConfig = { + networking.wireguard.interfaces.wg0 = { + ips = [ "10.23.42.2/32" "fc00::2/128" ]; + listenPort = 23542; + allowedIPsAsRoutes = false; + + inherit (wg-snakeoil-keys.peer1) privateKey; + + peers = lib.singleton { + allowedIPs = [ "0.0.0.0/0" "::/0" ]; + endpoint = "192.168.0.1:23542"; + persistentKeepalive = 25; + + inherit (wg-snakeoil-keys.peer0) publicKey; + }; + + postSetup = let inherit (pkgs) iproute; in '' + ${iproute}/bin/ip route replace 10.23.42.1/32 dev wg0 + ${iproute}/bin/ip route replace fc00::1/128 dev wg0 + ''; + }; }; }; - - networking.wireguard.interfaces.wg0 = { - ips = [ "10.23.42.2/32" "fc00::2/128" ]; - listenPort = 23542; - allowedIPsAsRoutes = false; - - inherit (wg-snakeoil-keys.peer1) privateKey; - - peers = lib.singleton { - allowedIPs = [ "0.0.0.0/0" "::/0" ]; - endpoint = "192.168.0.1:23542"; - persistentKeepalive = 25; - - inherit (wg-snakeoil-keys.peer0) publicKey; - }; - - postSetup = let inherit (pkgs) iproute; in '' - ${iproute}/bin/ip route replace 10.23.42.1/32 dev wg0 - ${iproute}/bin/ip route replace fc00::1/128 dev wg0 - ''; - }; }; - }; - testScript = '' - start_all() + testScript = '' + start_all() - peer0.wait_for_unit("wireguard-wg0.service") - peer1.wait_for_unit("wireguard-wg0.service") + peer0.wait_for_unit("wireguard-wg0.service") + peer1.wait_for_unit("wireguard-wg0.service") - peer1.succeed("ping -c5 fc00::1") - peer1.succeed("ping -c5 10.23.42.1") - ''; -}) + peer1.succeed("ping -c5 fc00::1") + peer1.succeed("ping -c5 10.23.42.1") + ''; + } +) diff --git a/nixos/tests/wireguard/make-peer.nix b/nixos/tests/wireguard/make-peer.nix new file mode 100644 index 0000000000000..d2740549738b4 --- /dev/null +++ b/nixos/tests/wireguard/make-peer.nix @@ -0,0 +1,23 @@ +{ lib, ... }: { ip4, ip6, extraConfig }: +lib.mkMerge [ + { + boot.kernel.sysctl = { + "net.ipv6.conf.all.forwarding" = "1"; + "net.ipv6.conf.default.forwarding" = "1"; + "net.ipv4.ip_forward" = "1"; + }; + + networking.useDHCP = false; + networking.interfaces.eth1 = { + ipv4.addresses = [{ + address = ip4; + prefixLength = 24; + }]; + ipv6.addresses = [{ + address = ip6; + prefixLength = 64; + }]; + }; + } + extraConfig +] diff --git a/nixos/tests/wireguard/namespaces.nix b/nixos/tests/wireguard/namespaces.nix index 94f993d9475dd..c8a4e3bb52a13 100644 --- a/nixos/tests/wireguard/namespaces.nix +++ b/nixos/tests/wireguard/namespaces.nix @@ -13,7 +13,7 @@ let in -import ../make-test.nix ({ pkgs, ...} : { +import ../make-test-python.nix ({ pkgs, ...} : { name = "wireguard-with-namespaces"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ asymmetric ]; @@ -65,16 +65,14 @@ import ../make-test.nix ({ pkgs, ...} : { }; testScript = '' - startAll(); + start_all() - $peer0->waitForUnit("wireguard-wg0.service"); - $peer1->waitForUnit("wireguard-wg0.service"); - $peer2->waitForUnit("wireguard-wg0.service"); - $peer3->waitForUnit("wireguard-wg0.service"); + for machine in peer0, peer1, peer2, peer3: + machine.wait_for_unit("wireguard-wg0.service") - $peer0->succeed("ip -n ${socketNamespace} link show wg0"); - $peer1->succeed("ip -n ${interfaceNamespace} link show wg0"); - $peer2->succeed("ip -n ${interfaceNamespace} link show wg0"); - $peer3->succeed("ip link show wg0"); + peer0.succeed("ip -n ${socketNamespace} link show wg0") + peer1.succeed("ip -n ${interfaceNamespace} link show wg0") + peer2.succeed("ip -n ${interfaceNamespace} link show wg0") + peer3.succeed("ip link show wg0") ''; }) diff --git a/nixos/tests/wireguard/wg-quick.nix b/nixos/tests/wireguard/wg-quick.nix new file mode 100644 index 0000000000000..7354dd01a34a7 --- /dev/null +++ b/nixos/tests/wireguard/wg-quick.nix @@ -0,0 +1,63 @@ +import ../make-test-python.nix ({ pkgs, lib, ... }: + let + wg-snakeoil-keys = import ./snakeoil-keys.nix; + peer = (import ./make-peer.nix) { inherit lib; }; + in + { + name = "wg-quick"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ xwvvvvwx ]; + }; + + nodes = { + peer0 = peer { + ip4 = "192.168.0.1"; + ip6 = "fd00::1"; + extraConfig = { + networking.firewall.allowedUDPPorts = [ 23542 ]; + networking.wg-quick.interfaces.wg0 = { + address = [ "10.23.42.1/32" "fc00::1/128" ]; + listenPort = 23542; + + inherit (wg-snakeoil-keys.peer0) privateKey; + + peers = lib.singleton { + allowedIPs = [ "10.23.42.2/32" "fc00::2/128" ]; + + inherit (wg-snakeoil-keys.peer1) publicKey; + }; + }; + }; + }; + + peer1 = peer { + ip4 = "192.168.0.2"; + ip6 = "fd00::2"; + extraConfig = { + networking.wg-quick.interfaces.wg0 = { + address = [ "10.23.42.2/32" "fc00::2/128" ]; + inherit (wg-snakeoil-keys.peer1) privateKey; + + peers = lib.singleton { + allowedIPs = [ "0.0.0.0/0" "::/0" ]; + endpoint = "192.168.0.1:23542"; + persistentKeepalive = 25; + + inherit (wg-snakeoil-keys.peer0) publicKey; + }; + }; + }; + }; + }; + + testScript = '' + start_all() + + peer0.wait_for_unit("wg-quick-wg0.service") + peer1.wait_for_unit("wg-quick-wg0.service") + + peer1.succeed("ping -c5 fc00::1") + peer1.succeed("ping -c5 10.23.42.1") + ''; + } +) diff --git a/nixos/tests/xandikos.nix b/nixos/tests/xandikos.nix new file mode 100644 index 0000000000000..0fded20ff1a9c --- /dev/null +++ b/nixos/tests/xandikos.nix @@ -0,0 +1,70 @@ +import ./make-test-python.nix ( + { pkgs, lib, ... }: + + { + name = "xandikos"; + + meta.maintainers = [ lib.maintainers."0x4A6F" ]; + + nodes = { + xandikos_client = {}; + xandikos_default = { + networking.firewall.allowedTCPPorts = [ 8080 ]; + services.xandikos.enable = true; + }; + xandikos_proxy = { + networking.firewall.allowedTCPPorts = [ 80 8080 ]; + services.xandikos.enable = true; + services.xandikos.address = "localhost"; + services.xandikos.port = 8080; + services.xandikos.routePrefix = "/xandikos/"; + services.xandikos.extraOptions = [ + "--defaults" + ]; + services.nginx = { + enable = true; + recommendedProxySettings = true; + virtualHosts."xandikos" = { + serverName = "xandikos.local"; + basicAuth.xandikos = "snakeOilPassword"; + locations."/xandikos/" = { + proxyPass = "http://localhost:8080/"; + }; + }; + }; + }; + }; + + testScript = '' + start_all() + + with subtest("Xandikos default"): + xandikos_default.wait_for_unit("multi-user.target") + xandikos_default.wait_for_unit("xandikos.service") + xandikos_default.wait_for_open_port(8080) + xandikos_default.succeed("curl --fail http://localhost:8080/") + xandikos_default.succeed( + "curl -s --fail --location http://localhost:8080/ | grep -qi Xandikos" + ) + xandikos_client.wait_for_unit("network.target") + xandikos_client.fail("curl --fail http://xandikos_default:8080/") + + with subtest("Xandikos proxy"): + xandikos_proxy.wait_for_unit("multi-user.target") + xandikos_proxy.wait_for_unit("xandikos.service") + xandikos_proxy.wait_for_open_port(8080) + xandikos_proxy.succeed("curl --fail http://localhost:8080/") + xandikos_proxy.succeed( + "curl -s --fail --location http://localhost:8080/ | grep -qi Xandikos" + ) + xandikos_client.wait_for_unit("network.target") + xandikos_client.fail("curl --fail http://xandikos_proxy:8080/") + xandikos_client.succeed( + "curl -s --fail -u xandikos:snakeOilPassword -H 'Host: xandikos.local' http://xandikos_proxy/xandikos/ | grep -qi Xandikos" + ) + xandikos_client.succeed( + "curl -s --fail -u xandikos:snakeOilPassword -H 'Host: xandikos.local' http://xandikos_proxy/xandikos/user/ | grep -qi Xandikos" + ) + ''; + } +) diff --git a/nixos/tests/xautolock.nix b/nixos/tests/xautolock.nix index 10e92b40e9562..4a8d3f4cebf7c 100644 --- a/nixos/tests/xautolock.nix +++ b/nixos/tests/xautolock.nix @@ -9,7 +9,7 @@ with lib; nodes.machine = { imports = [ ./common/x11.nix ./common/user-account.nix ]; - services.xserver.displayManager.auto.user = "bob"; + test-support.displayManager.auto.user = "bob"; services.xserver.xautolock.enable = true; services.xserver.xautolock.time = 1; }; diff --git a/nixos/tests/xfce.nix b/nixos/tests/xfce.nix index 3ea96b383631d..99065669661a0 100644 --- a/nixos/tests/xfce.nix +++ b/nixos/tests/xfce.nix @@ -4,12 +4,20 @@ import ./make-test-python.nix ({ pkgs, ...} : { machine = { pkgs, ... }: - { imports = [ ./common/user-account.nix ]; + { + imports = [ + ./common/user-account.nix + ]; services.xserver.enable = true; - services.xserver.displayManager.auto.enable = true; - services.xserver.displayManager.auto.user = "alice"; + services.xserver.displayManager.lightdm = { + enable = true; + autoLogin = { + enable = true; + user = "alice"; + }; + }; services.xserver.desktopManager.xfce.enable = true; diff --git a/nixos/tests/xmonad.nix b/nixos/tests/xmonad.nix index c2e5ba60d7bc5..56baae8b9d3cd 100644 --- a/nixos/tests/xmonad.nix +++ b/nixos/tests/xmonad.nix @@ -4,10 +4,10 @@ import ./make-test-python.nix ({ pkgs, ...} : { maintainers = [ nequissimus ]; }; - machine = { lib, pkgs, ... }: { + machine = { pkgs, ... }: { imports = [ ./common/x11.nix ./common/user-account.nix ]; - services.xserver.displayManager.auto.user = "alice"; - services.xserver.windowManager.default = lib.mkForce "xmonad"; + test-support.displayManager.auto.user = "alice"; + services.xserver.displayManager.defaultSession = "none+xmonad"; services.xserver.windowManager.xmonad = { enable = true; enableContribAndExtras = true; @@ -27,13 +27,13 @@ import ./make-test-python.nix ({ pkgs, ...} : { machine.wait_for_x() machine.wait_for_file("${user.home}/.Xauthority") machine.succeed("xauth merge ${user.home}/.Xauthority") - machine.send_chars("alt-ctrl-x") + machine.send_key("alt-ctrl-x") machine.wait_for_window("${user.name}.*machine") machine.sleep(1) machine.screenshot("terminal") machine.wait_until_succeeds("xmonad --restart") machine.sleep(3) - machine.send_chars("alt-shift-ret") + machine.send_key("alt-shift-ret") machine.wait_for_window("${user.name}.*machine") machine.sleep(1) machine.screenshot("terminal") diff --git a/nixos/tests/xrdp.nix b/nixos/tests/xrdp.nix index 0106aefe8318d..6d7f2b9249ffa 100644 --- a/nixos/tests/xrdp.nix +++ b/nixos/tests/xrdp.nix @@ -1,4 +1,4 @@ -import ./make-test.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, ...} : { name = "xrdp"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ volth ]; @@ -14,32 +14,34 @@ import ./make-test.nix ({ pkgs, ...} : { client = { pkgs, ... }: { imports = [ ./common/x11.nix ./common/user-account.nix ]; - services.xserver.displayManager.auto.user = "alice"; + test-support.displayManager.auto.user = "alice"; environment.systemPackages = [ pkgs.freerdp ]; services.xrdp.enable = true; services.xrdp.defaultWindowManager = "${pkgs.icewm}/bin/icewm"; }; }; - testScript = { ... }: '' - startAll; + testScript = { nodes, ... }: let + user = nodes.client.config.users.users.alice; + in '' + start_all() - $client->waitForX; - $client->waitForFile("/home/alice/.Xauthority"); - $client->succeed("xauth merge ~alice/.Xauthority"); + client.wait_for_x() + client.wait_for_file("${user.home}/.Xauthority") + client.succeed("xauth merge ${user.home}/.Xauthority") - $client->sleep(5); + client.sleep(5) - $client->execute("xterm &"); - $client->sleep(1); - $client->sendChars("xfreerdp /cert-tofu /w:640 /h:480 /v:127.0.0.1 /u:alice /p:foobar\n"); - $client->sleep(5); - $client->screenshot("localrdp"); + client.execute("xterm &") + client.sleep(1) + client.send_chars("xfreerdp /cert-tofu /w:640 /h:480 /v:127.0.0.1 /u:${user.name} /p:${user.password}\n") + client.sleep(5) + client.screenshot("localrdp") - $client->execute("xterm &"); - $client->sleep(1); - $client->sendChars("xfreerdp /cert-tofu /w:640 /h:480 /v:server /u:alice /p:foobar\n"); - $client->sleep(5); - $client->screenshot("remoterdp"); + client.execute("xterm &") + client.sleep(1) + client.send_chars("xfreerdp /cert-tofu /w:640 /h:480 /v:server /u:${user.name} /p:${user.password}\n") + client.sleep(5) + client.screenshot("remoterdp") ''; }) diff --git a/nixos/tests/xss-lock.nix b/nixos/tests/xss-lock.nix index 3a7dea07d53a5..b77bbbbb3c4e7 100644 --- a/nixos/tests/xss-lock.nix +++ b/nixos/tests/xss-lock.nix @@ -10,12 +10,12 @@ with lib; simple = { imports = [ ./common/x11.nix ./common/user-account.nix ]; programs.xss-lock.enable = true; - services.xserver.displayManager.auto.user = "alice"; + test-support.displayManager.auto.user = "alice"; }; custom_lockcmd = { pkgs, ... }: { imports = [ ./common/x11.nix ./common/user-account.nix ]; - services.xserver.displayManager.auto.user = "alice"; + test-support.displayManager.auto.user = "alice"; programs.xss-lock = { enable = true; diff --git a/nixos/tests/yabar.nix b/nixos/tests/yabar.nix index 9108004d4df9d..b374ef2968074 100644 --- a/nixos/tests/yabar.nix +++ b/nixos/tests/yabar.nix @@ -11,7 +11,7 @@ with lib; machine = { imports = [ ./common/x11.nix ./common/user-account.nix ]; - services.xserver.displayManager.auto.user = "bob"; + test-support.displayManager.auto.user = "bob"; programs.yabar.enable = true; programs.yabar.bars = { diff --git a/nixos/tests/zfs.nix b/nixos/tests/zfs.nix index 8f844aca41605..7ba60ee9806c5 100644 --- a/nixos/tests/zfs.nix +++ b/nixos/tests/zfs.nix @@ -3,12 +3,10 @@ pkgs ? import ../.. { inherit system config; } }: -with import ../lib/testing.nix { inherit system pkgs; }; +with import ../lib/testing-python.nix { inherit system pkgs; }; let - makeTest = import ./make-test-python.nix; - makeZfsTest = name: { kernelPackage ? pkgs.linuxPackages_latest , enableUnstable ? false @@ -20,41 +18,33 @@ let maintainers = [ adisbladis ]; }; - machine = { pkgs, ... }: - { - virtualisation.emptyDiskImages = [ 4096 ]; - networking.hostId = "deadbeef"; - boot.kernelPackages = kernelPackage; - boot.supportedFilesystems = [ "zfs" ]; - boot.zfs.enableUnstable = enableUnstable; + machine = { pkgs, ... }: { + virtualisation.emptyDiskImages = [ 4096 ]; + networking.hostId = "deadbeef"; + boot.kernelPackages = kernelPackage; + boot.supportedFilesystems = [ "zfs" ]; + boot.zfs.enableUnstable = enableUnstable; - environment.systemPackages = with pkgs; [ - parted - ]; - }; + environment.systemPackages = [ pkgs.parted ]; + }; testScript = '' - machine.succeed("modprobe zfs") - machine.succeed("zpool status") - - machine.succeed("ls /dev") - machine.succeed( - "mkdir /tmp/mnt", - - "udevadm settle", - "parted --script /dev/vdb mklabel msdos", - "parted --script /dev/vdb -- mkpart primary 1024M -1s", - "udevadm settle", - - "zpool create rpool /dev/vdb1", - "zfs create -o mountpoint=legacy rpool/root", - "mount -t zfs rpool/root /tmp/mnt", - "udevadm settle", - - "umount /tmp/mnt", - "zpool destroy rpool", - "udevadm settle" + "modprobe zfs", + "zpool status", + "ls /dev", + "mkdir /tmp/mnt", + "udevadm settle", + "parted --script /dev/vdb mklabel msdos", + "parted --script /dev/vdb -- mkpart primary 1024M -1s", + "udevadm settle", + "zpool create rpool /dev/vdb1", + "zfs create -o mountpoint=legacy rpool/root", + "mount -t zfs rpool/root /tmp/mnt", + "udevadm settle", + "umount /tmp/mnt", + "zpool destroy rpool", + "udevadm settle", ) '' + extraTest; @@ -69,18 +59,17 @@ in { enableUnstable = true; extraTest = '' machine.succeed( - "echo password | zpool create -o altroot=\"/tmp/mnt\" -O encryption=aes-256-gcm -O keyformat=passphrase rpool /dev/vdb1", - "zfs create -o mountpoint=legacy rpool/root", - "mount -t zfs rpool/root /tmp/mnt", - "udevadm settle", - - "umount /tmp/mnt", - "zpool destroy rpool", - "udevadm settle" + 'echo password | zpool create -o altroot="/tmp/mnt" ' + + "-O encryption=aes-256-gcm -O keyformat=passphrase rpool /dev/vdb1", + "zfs create -o mountpoint=legacy rpool/root", + "mount -t zfs rpool/root /tmp/mnt", + "udevadm settle", + "umount /tmp/mnt", + "zpool destroy rpool", + "udevadm settle", ) ''; }; installer = (import ./installer.nix { }).zfsroot; - } diff --git a/nixos/tests/zsh-history.nix b/nixos/tests/zsh-history.nix new file mode 100644 index 0000000000000..4380ec9adfd25 --- /dev/null +++ b/nixos/tests/zsh-history.nix @@ -0,0 +1,35 @@ +import ./make-test-python.nix ({ pkgs, ...} : { + name = "zsh-history"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ kampka ]; + }; + + nodes.default = { ... }: { + programs = { + zsh.enable = true; + }; + environment.systemPackages = [ pkgs.zsh-history ]; + programs.zsh.interactiveShellInit = '' + source ${pkgs.zsh-history.out}/share/zsh/init.zsh + ''; + users.users.root.shell = "${pkgs.zsh}/bin/zsh"; + }; + + testScript = '' + start_all() + default.wait_for_unit("multi-user.target") + default.wait_until_succeeds("pgrep -f 'agetty.*tty1'") + + # Login + default.wait_until_tty_matches(1, "login: ") + default.send_chars("root\n") + default.wait_until_tty_matches(1, "root@default>") + + # Generate some history + default.send_chars("echo foobar\n") + default.wait_until_tty_matches(1, "foobar") + + # Ensure that command was recorded in history + default.succeed("/run/current-system/sw/bin/history list | grep -q foobar") + ''; +}) |