Merge branch 'NixOS:master' into vimix-cursor-theme

This commit is contained in:
Gabe Dunn
2023-09-25 18:24:12 -07:00
committed by GitHub
1539 changed files with 20486 additions and 32656 deletions
+9 -13
View File
@@ -23,7 +23,7 @@
# Libraries
/lib @edolstra @infinisil
/lib/systems @alyssais @ericson2314 @matthewbauer @amjoseph-nixpkgs
/lib/systems @alyssais @ericson2314 @amjoseph-nixpkgs
/lib/generators.nix @edolstra @Profpatsch
/lib/cli.nix @edolstra @Profpatsch
/lib/debug.nix @edolstra @Profpatsch
@@ -36,12 +36,12 @@
/default.nix @Ericson2314
/pkgs/top-level/default.nix @Ericson2314
/pkgs/top-level/impure.nix @Ericson2314
/pkgs/top-level/stage.nix @Ericson2314 @matthewbauer
/pkgs/top-level/splice.nix @Ericson2314 @matthewbauer
/pkgs/top-level/release-cross.nix @Ericson2314 @matthewbauer
/pkgs/stdenv/generic @Ericson2314 @matthewbauer @amjoseph-nixpkgs
/pkgs/stdenv/generic/check-meta.nix @Ericson2314 @matthewbauer @piegamesde
/pkgs/stdenv/cross @Ericson2314 @matthewbauer @amjoseph-nixpkgs
/pkgs/top-level/stage.nix @Ericson2314
/pkgs/top-level/splice.nix @Ericson2314
/pkgs/top-level/release-cross.nix @Ericson2314
/pkgs/stdenv/generic @Ericson2314 @amjoseph-nixpkgs
/pkgs/stdenv/generic/check-meta.nix @Ericson2314 @piegamesde
/pkgs/stdenv/cross @Ericson2314 @amjoseph-nixpkgs
/pkgs/build-support/cc-wrapper @Ericson2314 @amjoseph-nixpkgs
/pkgs/build-support/bintools-wrapper @Ericson2314
/pkgs/build-support/setup-hooks @Ericson2314
@@ -147,12 +147,8 @@
/doc/languages-frameworks/rust.section.md @zowoq @winterqt @figsoda
# C compilers
/pkgs/development/compilers/gcc @matthewbauer @amjoseph-nixpkgs
/pkgs/development/compilers/llvm @matthewbauer @RaitoBezarius
# Compatibility stuff
/pkgs/top-level/unix-tools.nix @matthewbauer
/pkgs/development/tools/xcbuild @matthewbauer
/pkgs/development/compilers/gcc @amjoseph-nixpkgs
/pkgs/development/compilers/llvm @RaitoBezarius
# Audio
/nixos/modules/services/audio/botamusique.nix @mweinelt
+1 -1
View File
@@ -9,7 +9,7 @@ File sets are easy and safe to use, providing obvious and composable semantics w
These sections apply to the entire library.
See the [function reference](#sec-functions-library-fileset) for function-specific documentation.
The file set library is currently very limited but is being expanded to include more functions over time.
The file set library is currently somewhat limited but is being expanded to include more functions over time.
## Implicit coercion from paths to file sets {#sec-fileset-path-coercion}
+7
View File
@@ -0,0 +1,7 @@
# bmake {#bmake-hook}
[bmake](https://www.crufty.net/help/sjg/bmake.html) is the portable variant of
NetBSD make utility.
In Nixpkgs, `bmake` comes with a hook that overrides the default build, check,
install and dist phases.
+1
View File
@@ -8,6 +8,7 @@ The stdenv built-in hooks are documented in [](#ssec-setup-hooks).
autoconf.section.md
automake.section.md
autopatchelf.section.md
bmake.section.md
breakpoint.section.md
cmake.section.md
gdk-pixbuf.section.md
+6 -5
View File
@@ -303,11 +303,8 @@ You can use the `dhall-to-nixpkgs` command-line utility to automate
packaging Dhall code. For example:
```ShellSession
$ nix-env --install --attr haskellPackages.dhall-nixpkgs
$ nix-env --install --attr nix-prefetch-git # Used by dhall-to-nixpkgs
$ dhall-to-nixpkgs github https://github.com/Gabriella439/dhall-semver.git
$ nix-shell -p haskellPackages.dhall-nixpkgs nix-prefetch-git
[nix-shell]$ dhall-to-nixpkgs github https://github.com/Gabriella439/dhall-semver.git
{ buildDhallGitHubPackage, Prelude }:
buildDhallGitHubPackage {
name = "dhall-semver";
@@ -325,6 +322,10 @@ $ dhall-to-nixpkgs github https://github.com/Gabriella439/dhall-semver.git
}
```
:::{.note}
`nix-prefetch-git` has to be in `$PATH` for `dhall-to-nixpkgs` to work.
:::
The utility takes care of automatically detecting remote imports and converting
them to package dependencies. You can also use the utility on local
Dhall directories, too:
@@ -217,6 +217,10 @@ $ prefetch-npm-deps package-lock.json
sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
```
### corepack {#javascript-corepack}
This package puts the corepack wrappers for pnpm and yarn in your PATH, and they will honor the `packageManager` setting in the `package.json`.
### node2nix {#javascript-node2nix}
#### Preparation {#javascript-node2nix-preparation}
+78 -79
View File
@@ -32,8 +32,8 @@ Each interpreter has the following attributes:
- `libPrefix`. Name of the folder in `${python}/lib/` for corresponding interpreter.
- `interpreter`. Alias for `${python}/bin/${executable}`.
- `buildEnv`. Function to build python interpreter environments with extra packages bundled together. See section *python.buildEnv function* for usage and documentation.
- `withPackages`. Simpler interface to `buildEnv`. See section *python.withPackages function* for usage and documentation.
- `buildEnv`. Function to build python interpreter environments with extra packages bundled together. See [](#python.buildenv-function) for usage and documentation.
- `withPackages`. Simpler interface to `buildEnv`. See [](#python.withpackages-function) for usage and documentation.
- `sitePackages`. Alias for `lib/${libPrefix}/site-packages`.
- `executable`. Name of the interpreter executable, e.g. `python3.10`.
- `pkgs`. Set of Python packages for that specific interpreter. The package set can be modified by overriding the interpreter and passing `packageOverrides`.
@@ -41,8 +41,8 @@ Each interpreter has the following attributes:
### Building packages and applications {#building-packages-and-applications}
Python libraries and applications that use `setuptools` or
`distutils` are typically built with respectively the `buildPythonPackage` and
`buildPythonApplication` functions. These two functions also support installing a `wheel`.
`distutils` are typically built with respectively the [`buildPythonPackage`](#buildpythonpackage-function) and
[`buildPythonApplication`](#buildpythonapplication-function) functions. These two functions also support installing a `wheel`.
All Python packages reside in `pkgs/top-level/python-packages.nix` and all
applications elsewhere. In case a package is used as both a library and an
@@ -141,23 +141,23 @@ buildPythonPackage rec {
The `buildPythonPackage` mainly does four things:
* In the `buildPhase`, it calls `${python.pythonForBuild.interpreter} setup.py bdist_wheel` to
* In the [`buildPhase`](#build-phase), it calls `${python.pythonForBuild.interpreter} setup.py bdist_wheel` to
build a wheel binary zipfile.
* In the `installPhase`, it installs the wheel file using `pip install *.whl`.
* In the `postFixup` phase, the `wrapPythonPrograms` bash function is called to
* In the [`installPhase`](#ssec-install-phase), it installs the wheel file using `pip install *.whl`.
* In the [`postFixup`](#var-stdenv-postFixup) phase, the `wrapPythonPrograms` bash function is called to
wrap all programs in the `$out/bin/*` directory to include `$PATH`
environment variable and add dependent libraries to script's `sys.path`.
* In the `installCheck` phase, `${python.interpreter} setup.py test` is run.
* In the [`installCheck`](#ssec-installCheck-phase) phase, `${python.interpreter} setup.py test` is run.
By default tests are run because `doCheck = true`. Test dependencies, like
e.g. the test runner, should be added to `nativeCheckInputs`.
By default tests are run because [`doCheck = true`](#var-stdenv-doCheck). Test dependencies, like
e.g. the test runner, should be added to [`nativeCheckInputs`](#var-stdenv-nativeCheckInputs).
By default `meta.platforms` is set to the same value
as the interpreter unless overridden otherwise.
##### `buildPythonPackage` parameters {#buildpythonpackage-parameters}
All parameters from `stdenv.mkDerivation` function are still supported. The
All parameters from [`stdenv.mkDerivation`](#sec-using-stdenv) function are still supported. The
following are specific to `buildPythonPackage`:
* `catchConflicts ? true`: If `true`, abort package build if a package name
@@ -177,8 +177,8 @@ following are specific to `buildPythonPackage`:
format. When unset, the legacy `setuptools` hooks are used for backwards
compatibility.
* `makeWrapperArgs ? []`: A list of strings. Arguments to be passed to
`makeWrapper`, which wraps generated binaries. By default, the arguments to
`makeWrapper` set `PATH` and `PYTHONPATH` environment variables before calling
[`makeWrapper`](#fun-makeWrapper), which wraps generated binaries. By default, the arguments to
[`makeWrapper`](#fun-makeWrapper) set `PATH` and `PYTHONPATH` environment variables before calling
the binary. Additional arguments here can allow a developer to set environment
variables which will be available when the binary is run. For example,
`makeWrapperArgs = ["--set FOO BAR" "--set BAZ QUX"]`.
@@ -190,7 +190,7 @@ following are specific to `buildPythonPackage`:
* `pipBuildFlags ? []`: A list of strings. Arguments to be passed to `pip wheel`.
* `pypaBuildFlags ? []`: A list of strings. Arguments to be passed to `python -m build --wheel`.
* `pythonPath ? []`: List of packages to be added into `$PYTHONPATH`. Packages
in `pythonPath` are not propagated (contrary to `propagatedBuildInputs`).
in `pythonPath` are not propagated (contrary to [`propagatedBuildInputs`](#var-stdenv-propagatedBuildInputs)).
* `preShellHook`: Hook to execute commands before `shellHook`.
* `postShellHook`: Hook to execute commands after `shellHook`.
* `removeBinByteCode ? true`: Remove bytecode from `/bin`. Bytecode is only
@@ -198,7 +198,7 @@ following are specific to `buildPythonPackage`:
* `setupPyGlobalFlags ? []`: List of flags passed to `setup.py` command.
* `setupPyBuildFlags ? []`: List of flags passed to `setup.py build_ext` command.
The `stdenv.mkDerivation` function accepts various parameters for describing
The [`stdenv.mkDerivation`](#sec-using-stdenv) function accepts various parameters for describing
build inputs (see "Specifying dependencies"). The following are of special
interest for Python packages, either because these are primarily used, or
because their behaviour is different:
@@ -208,8 +208,8 @@ because their behaviour is different:
* `buildInputs ? []`: Build and/or run-time dependencies that need to be
compiled for the host machine. Typically non-Python libraries which are being
linked.
* `nativeCheckInputs ? []`: Dependencies needed for running the `checkPhase`. These
are added to `nativeBuildInputs` when `doCheck = true`. Items listed in
* `nativeCheckInputs ? []`: Dependencies needed for running the [`checkPhase`](#ssec-check-phase). These
are added to [`nativeBuildInputs`](#var-stdenv-nativeBuildInputs) when [`doCheck = true`](#var-stdenv-doCheck). Items listed in
`tests_require` go here.
* `propagatedBuildInputs ? []`: Aside from propagating dependencies,
`buildPythonPackage` also injects code into and wraps executables with the
@@ -266,17 +266,17 @@ compilation issues, because scipy dependencies need to use the same blas impleme
#### `buildPythonApplication` function {#buildpythonapplication-function}
The `buildPythonApplication` function is practically the same as
`buildPythonPackage`. The main purpose of this function is to build a Python
The [`buildPythonApplication`](#buildpythonapplication-function) function is practically the same as
[`buildPythonPackage`](#buildpythonpackage-function). The main purpose of this function is to build a Python
package where one is interested only in the executables, and not importable
modules. For that reason, when adding this package to a `python.buildEnv`, the
modules. For that reason, when adding this package to a [`python.buildEnv`](#python.buildenv-function), the
modules won't be made available.
Another difference is that `buildPythonPackage` by default prefixes the names of
Another difference is that [`buildPythonPackage`](#buildpythonpackage-function) by default prefixes the names of
the packages with the version of the interpreter. Because this is irrelevant for
applications, the prefix is omitted.
When packaging a Python application with `buildPythonApplication`, it should be
When packaging a Python application with [`buildPythonApplication`](#buildpythonapplication-function), it should be
called with `callPackage` and passed `python` or `pythonPackages` (possibly
specifying an interpreter version), like this:
@@ -329,7 +329,7 @@ package is used as both. In this case the package is added as a library to
duplication the `toPythonApplication` can be used to convert a library to an
application.
The Nix expression shall use `buildPythonPackage` and be called from
The Nix expression shall use [`buildPythonPackage`](#buildpythonpackage-function) and be called from
`python-packages.nix`. A reference shall be created from `all-packages.nix` to
the attribute in `python-packages.nix`, and the `toPythonApplication` shall be
applied to the reference:
@@ -341,7 +341,7 @@ youtube-dl = with pythonPackages; toPythonApplication youtube-dl;
#### `toPythonModule` function {#topythonmodule-function}
In some cases, such as bindings, a package is created using
`stdenv.mkDerivation` and added as attribute in `all-packages.nix`. The Python
[`stdenv.mkDerivation`](#sec-using-stdenv) and added as attribute in `all-packages.nix`. The Python
bindings should be made available from `python-packages.nix`. The
`toPythonModule` function takes a derivation and makes certain Python-specific
modifications.
@@ -407,9 +407,9 @@ specified packages in its path.
#### `python.withPackages` function {#python.withpackages-function}
The `python.withPackages` function provides a simpler interface to the `python.buildEnv` functionality.
The [`python.withPackages`](#python.withpackages-function) function provides a simpler interface to the [`python.buildEnv`](#python.buildenv-function) functionality.
It takes a function as an argument that is passed the set of python packages and returns the list
of the packages to be included in the environment. Using the `withPackages` function, the previous
of the packages to be included in the environment. Using the [`withPackages`](#python.withpackages-function) function, the previous
example for the Pyramid Web Framework environment can be written like this:
```nix
@@ -418,7 +418,7 @@ with import <nixpkgs> {};
python.withPackages (ps: [ ps.pyramid ])
```
`withPackages` passes the correct package set for the specific interpreter
[`withPackages`](#python.withpackages-function) passes the correct package set for the specific interpreter
version as an argument to the function. In the above example, `ps` equals
`pythonPackages`. But you can also easily switch to using python3:
@@ -430,7 +430,7 @@ python3.withPackages (ps: [ ps.pyramid ])
Now, `ps` is set to `python3Packages`, matching the version of the interpreter.
As `python.withPackages` simply uses `python.buildEnv` under the hood, it also
As [`python.withPackages`](#python.withpackages-function) simply uses [`python.buildEnv`](#python.buildenv-function) under the hood, it also
supports the `env` attribute. The `shell.nix` file from the previous section can
thus be also written like this:
@@ -443,23 +443,22 @@ with import <nixpkgs> {};
])).env
```
In contrast to `python.buildEnv`, `python.withPackages` does not support the
In contrast to [`python.buildEnv`](#python.buildenv-function), [`python.withPackages`](#python.withpackages-function) does not support the
more advanced options such as `ignoreCollisions = true` or `postBuild`. If you
need them, you have to use `python.buildEnv`.
need them, you have to use [`python.buildEnv`](#python.buildenv-function).
Python 2 namespace packages may provide `__init__.py` that collide. In that case
`python.buildEnv` should be used with `ignoreCollisions = true`.
[`python.buildEnv`](#python.buildenv-function) should be used with `ignoreCollisions = true`.
#### Setup hooks {#setup-hooks}
The following are setup hooks specifically for Python packages. Most of these
are used in `buildPythonPackage`.
are used in [`buildPythonPackage`](#buildpythonpackage-function).
- `eggUnpackhook` to move an egg to the correct folder so it can be installed
with the `eggInstallHook`
- `eggBuildHook` to skip building for eggs.
- `eggInstallHook` to install eggs.
- `flitBuildHook` to build a wheel using `flit`.
- `pipBuildHook` to build a wheel using `pip` and PEP 517. Note a build system
(e.g. `setuptools` or `flit`) should still be added as `nativeBuildInput`.
- `pypaBuildHook` to build a wheel using
@@ -486,7 +485,7 @@ are used in `buildPythonPackage`.
### Development mode {#development-mode}
Development or editable mode is supported. To develop Python packages
`buildPythonPackage` has additional logic inside `shellPhase` to run `pip
[`buildPythonPackage`](#buildpythonpackage-function) has additional logic inside `shellPhase` to run `pip
install -e . --prefix $TMPDIR/`for the package.
Warning: `shellPhase` is executed only if `setup.py` exists.
@@ -567,7 +566,7 @@ without impacting other applications or polluting your user environment.
But Python libraries you would like to use for development cannot be installed,
at least not individually, because they won't be able to find each other
resulting in import errors. Instead, it is possible to create an environment
with `python.buildEnv` or `python.withPackages` where the interpreter and other
with [`python.buildEnv`](#python.buildenv-function) or [`python.withPackages`](#python.withpackages-function) where the interpreter and other
executables are wrapped to be able to find each other and all of the modules.
In the following examples we will start by creating a simple, ad-hoc environment
@@ -747,8 +746,8 @@ What's happening here?
imports the `<nixpkgs>` function, `{}` calls it and the `with` statement
brings all attributes of `nixpkgs` in the local scope. These attributes form
the main package set.
2. Then we create a Python 3.11 environment with the `withPackages` function, as before.
3. The `withPackages` function expects us to provide a function as an argument
2. Then we create a Python 3.11 environment with the [`withPackages`](#python.withpackages-function) function, as before.
3. The [`withPackages`](#python.withpackages-function) function expects us to provide a function as an argument
that takes the set of all Python packages and returns a list of packages to
include in the environment. Here, we select the packages `numpy` and `toolz`
from the package set.
@@ -859,7 +858,7 @@ we will look at how you can use development mode with your code.
#### Python library packages in Nixpkgs {#python-library-packages-in-nixpkgs}
With Nix all packages are built by functions. The main function in Nix for
building Python libraries is `buildPythonPackage`. Let's see how we can build the
building Python libraries is [`buildPythonPackage`](#buildpythonpackage-function). Let's see how we can build the
`toolz` package.
```nix
@@ -904,13 +903,13 @@ buildPythonPackage rec {
}
```
What happens here? The function `buildPythonPackage` is called and as argument
What happens here? The function [`buildPythonPackage`](#buildpythonpackage-function) is called and as argument
it accepts a set. In this case the set is a recursive set, `rec`. One of the
arguments is the name of the package, which consists of a basename (generally
following the name on PyPi) and a version. Another argument, `src` specifies the
source, which in this case is fetched from PyPI using the helper function
`fetchPypi`. The argument `doCheck` is used to set whether tests should be run
when building the package. Since there are no tests, we rely on `pythonImportsCheck`
when building the package. Since there are no tests, we rely on [`pythonImportsCheck`](#using-pythonimportscheck)
to test whether the package can be imported. Furthermore, we specify some meta
information. The output of the function is a derivation.
@@ -969,7 +968,7 @@ for which Python version we want to build a package.
So, what did we do here? Well, we took the Nix expression that we used earlier
to build a Python environment, and said that we wanted to include our own
version of `toolz`, named `my_toolz`. To introduce our own package in the scope
of `withPackages` we used a `let` expression. You can see that we used
of [`withPackages`](#python.withpackages-function) we used a `let` expression. You can see that we used
`ps.numpy` to select numpy from the nixpkgs package set (`ps`). We did not take
`toolz` from the Nixpkgs package set this time, but instead took our own version
that we introduced with the `let` expression.
@@ -977,14 +976,14 @@ that we introduced with the `let` expression.
#### Handling dependencies {#handling-dependencies}
Our example, `toolz`, does not have any dependencies on other Python packages or
system libraries. According to the manual, `buildPythonPackage` uses the
arguments `buildInputs` and `propagatedBuildInputs` to specify dependencies. If
system libraries. According to the manual, [`buildPythonPackage`](#buildpythonpackage-function) uses the
arguments [`buildInputs`](#var-stdenv-buildInputs) and [`propagatedBuildInputs`](#var-stdenv-propagatedBuildInputs) to specify dependencies. If
something is exclusively a build-time dependency, then the dependency should be
included in `buildInputs`, but if it is (also) a runtime dependency, then it
should be added to `propagatedBuildInputs`. Test dependencies are considered
build-time dependencies and passed to `nativeCheckInputs`.
included in [`buildInputs`](#var-stdenv-buildInputs), but if it is (also) a runtime dependency, then it
should be added to [`propagatedBuildInputs`](#var-stdenv-propagatedBuildInputs). Test dependencies are considered
build-time dependencies and passed to [`nativeCheckInputs`](#var-stdenv-nativeCheckInputs).
The following example shows which arguments are given to `buildPythonPackage` in
The following example shows which arguments are given to [`buildPythonPackage`](#buildpythonpackage-function) in
order to build [`datashape`](https://github.com/blaze/datashape).
```nix
@@ -1038,14 +1037,14 @@ buildPythonPackage rec {
```
We can see several runtime dependencies, `numpy`, `multipledispatch`, and
`python-dateutil`. Furthermore, we have `nativeCheckInputs` with `pytest`.
`pytest` is a test runner and is only used during the `checkPhase` and is
therefore not added to `propagatedBuildInputs`.
`python-dateutil`. Furthermore, we have [`nativeCheckInputs`](#var-stdenv-nativeCheckInputs) with `pytest`.
`pytest` is a test runner and is only used during the [`checkPhase`](#ssec-check-phase) and is
therefore not added to [`propagatedBuildInputs`](#var-stdenv-propagatedBuildInputs).
In the previous case we had only dependencies on other Python packages to consider.
Occasionally you have also system libraries to consider. E.g., `lxml` provides
Python bindings to `libxml2` and `libxslt`. These libraries are only required
when building the bindings and are therefore added as `buildInputs`.
when building the bindings and are therefore added as [`buildInputs`](#var-stdenv-buildInputs).
```nix
{ lib
@@ -1093,7 +1092,7 @@ files of the dependencies are. This is not always the case.
The example below shows bindings to The Fastest Fourier Transform in the West,
commonly known as FFTW. On Nix we have separate packages of FFTW for the
different types of floats (`"single"`, `"double"`, `"long-double"`). The
bindings need all three types, and therefore we add all three as `buildInputs`.
bindings need all three types, and therefore we add all three as [`buildInputs`](#var-stdenv-buildInputs).
The bindings don't expect to find each of them in a different folder, and
therefore we have to set `LDFLAGS` and `CFLAGS`.
@@ -1158,7 +1157,7 @@ buildPythonPackage rec {
}
```
Note also the line `doCheck = false;`, we explicitly disabled running the test-suite.
Note also the line [`doCheck = false;`](#var-stdenv-doCheck), we explicitly disabled running the test-suite.
#### Testing Python Packages {#testing-python-packages}
@@ -1167,10 +1166,10 @@ helps to avoid situations where the package was able to build and install,
but is not usable at runtime. Currently, all packages will use the `test`
command provided by the setup.py (i.e. `python setup.py test`). However,
this is currently deprecated https://github.com/pypa/setuptools/pull/1878
and your package should provide its own checkPhase.
and your package should provide its own [`checkPhase`](#ssec-check-phase).
::: {.note}
The `checkPhase` for python maps to the `installCheckPhase` on a
The [`checkPhase`](#ssec-check-phase) for python maps to the `installCheckPhase` on a
normal derivation. This is due to many python packages not behaving well
to the pre-installed version of the package. Version info, and natively
compiled extensions generally only exist in the install directory, and
@@ -1235,7 +1234,7 @@ been removed, in this case, it's recommended to use `pytestCheckHook`.
#### Using pytestCheckHook {#using-pytestcheckhook}
`pytestCheckHook` is a convenient hook which will substitute the setuptools
`test` command for a `checkPhase` which runs `pytest`. This is also beneficial
`test` command for a [`checkPhase`](#ssec-check-phase) which runs `pytest`. This is also beneficial
when a package may need many items disabled to run the test suite.
Using the example above, the analogous `pytestCheckHook` usage would be:
@@ -1280,14 +1279,14 @@ for example:
```
Trying to concatenate the related strings to disable tests in a regular
`checkPhase` would be much harder to read. This also enables us to comment on
[`checkPhase`](#ssec-check-phase) would be much harder to read. This also enables us to comment on
why specific tests are disabled.
#### Using pythonImportsCheck {#using-pythonimportscheck}
Although unit tests are highly preferred to validate correctness of a package, not
all packages have test suites that can be run easily, and some have none at all.
To help ensure the package still works, `pythonImportsCheck` can attempt to import
To help ensure the package still works, [`pythonImportsCheck`](#using-pythonimportscheck) can attempt to import
the listed modules.
```
@@ -1306,7 +1305,7 @@ roughly translates to:
'';
```
However, this is done in its own phase, and not dependent on whether `doCheck = true;`.
However, this is done in its own phase, and not dependent on whether [`doCheck = true;`](#var-stdenv-doCheck).
This can also be useful in verifying that the package doesn't assume commonly
present packages (e.g. `setuptools`).
@@ -1378,11 +1377,11 @@ instead of a dev dependency).
Keep in mind that while the examples above are done with `requirements.txt`,
`pythonRelaxDepsHook` works by modifying the resulting wheel file, so it should
work with any of the existing [hooks](#setup-hooks).
work with any of the [existing hooks](#setup-hooks).
#### Using unittestCheckHook {#using-unittestcheckhook}
`unittestCheckHook` is a hook which will substitute the setuptools `test` command for a `checkPhase` which runs `python -m unittest discover`:
`unittestCheckHook` is a hook which will substitute the setuptools `test` command for a [`checkPhase`](#ssec-check-phase) which runs `python -m unittest discover`:
```
nativeCheckInputs = [
@@ -1452,15 +1451,15 @@ mode is also available. Let's see how you can use it.
In the previous Nix expression the source was fetched from a url. We can also
refer to a local source instead using `src = ./path/to/source/tree;`
If we create a `shell.nix` file which calls `buildPythonPackage`, and if `src`
If we create a `shell.nix` file which calls [`buildPythonPackage`](#buildpythonpackage-function), and if `src`
is a local source, and if the local source has a `setup.py`, then development
mode is activated.
In the following example, we create a simple environment that has a Python 3.11
version of our package in it, as well as its dependencies and other packages we
like to have in the environment, all specified with `propagatedBuildInputs`.
like to have in the environment, all specified with [`propagatedBuildInputs`](#var-stdenv-propagatedBuildInputs).
Indeed, we can just add any package we like to have in our environment to
`propagatedBuildInputs`.
[`propagatedBuildInputs`](#var-stdenv-propagatedBuildInputs).
```nix
with import <nixpkgs> {};
@@ -1494,7 +1493,7 @@ own packages. The important functions here are `import` and `callPackage`.
### Including a derivation using `callPackage` {#including-a-derivation-using-callpackage}
Earlier we created a Python environment using `withPackages`, and included the
Earlier we created a Python environment using [`withPackages`](#python.withpackages-function), and included the
`toolz` package via a `let` expression.
Let's split the package definition from the environment definition.
@@ -1533,7 +1532,7 @@ buildPythonPackage rec {
}
```
It takes an argument `buildPythonPackage`. We now call this function using
It takes an argument [`buildPythonPackage`](#buildpythonpackage-function). We now call this function using
`callPackage` in the definition of our environment
```nix
@@ -1552,10 +1551,10 @@ Packages.buildPythonPackage;
```
Important to remember is that the Python version for which the package is made
depends on the `python` derivation that is passed to `buildPythonPackage`. Nix
depends on the `python` derivation that is passed to [`buildPythonPackage`](#buildpythonpackage-function). Nix
tries to automatically pass arguments when possible, which is why generally you
don't explicitly define which `python` derivation should be used. In the above
example we use `buildPythonPackage` that is part of the set `python3Packages`,
example we use [`buildPythonPackage`](#buildpythonpackage-function) that is part of the set `python3Packages`,
and in this case the `python3` interpreter is automatically used.
## FAQ {#faq}
@@ -1698,7 +1697,7 @@ Python, guarantees the right versions of the interpreter and libraries or
packages are available. There is therefore no need to maintain a global `site-packages`.
If you want to create a Python environment for development, then the recommended
method is to use `nix-shell`, either with or without the `python.buildEnv`
method is to use `nix-shell`, either with or without the [`python.buildEnv`](#python.buildenv-function)
function.
### How to consume Python modules using pip in a virtual environment like I am used to on other Operating Systems? {#how-to-consume-python-modules-using-pip-in-a-virtual-environment-like-i-am-used-to-on-other-operating-systems}
@@ -1875,7 +1874,7 @@ self: super: {
### How to override a Python package for all Python versions using extensions? {#how-to-override-a-python-package-for-all-python-versions-using-extensions}
The following overlay overrides the call to `buildPythonPackage` for the
The following overlay overrides the call to [`buildPythonPackage`](#buildpythonpackage-function) for the
`foo` package for all interpreters by appending a Python extension to the
`pythonPackagesExtensions` list of extensions.
@@ -1902,9 +1901,9 @@ configure alternatives](#sec-overlays-alternatives-blas-lapack)".
In a `setup.py` or `setup.cfg` it is common to declare dependencies:
* `setup_requires` corresponds to `nativeBuildInputs`
* `install_requires` corresponds to `propagatedBuildInputs`
* `tests_require` corresponds to `nativeCheckInputs`
* `setup_requires` corresponds to [`nativeBuildInputs`](#var-stdenv-nativeBuildInputs)
* `install_requires` corresponds to [`propagatedBuildInputs`](#var-stdenv-propagatedBuildInputs)
* `tests_require` corresponds to [`nativeCheckInputs`](#var-stdenv-nativeCheckInputs)
### How to enable interpreter optimizations? {#optimizations}
@@ -1951,7 +1950,7 @@ collisions.
### How to contribute a Python package to nixpkgs? {#tools}
Packages inside nixpkgs must use the `buildPythonPackage` or `buildPythonApplication` function directly,
Packages inside nixpkgs must use the [`buildPythonPackage`](#buildpythonpackage-function) or [`buildPythonApplication`](#buildpythonapplication-function) function directly,
because we can only provide security support for non-vendored dependencies.
We recommend [nix-init](https://github.com/nix-community/nix-init) for creating new python packages within nixpkgs,
@@ -1965,7 +1964,7 @@ has security implications and is relevant for those using Python in a
`nix-shell`.
When the environment variable `DETERMINISTIC_BUILD` is set, all bytecode will
have timestamp 1. The `buildPythonPackage` function sets `DETERMINISTIC_BUILD=1`
have timestamp 1. The [`buildPythonPackage`](#buildpythonpackage-function) function sets `DETERMINISTIC_BUILD=1`
and [PYTHONHASHSEED=0](https://docs.python.org/3.11/using/cmdline.html#envvar-PYTHONHASHSEED).
Both are also exported in `nix-shell`.
@@ -1975,12 +1974,12 @@ It is recommended to test packages as part of the build process.
Source distributions (`sdist`) often include test files, but not always.
By default the command `python setup.py test` is run as part of the
`checkPhase`, but often it is necessary to pass a custom `checkPhase`. An
[`checkPhase`](#ssec-check-phase), but often it is necessary to pass a custom [`checkPhase`](#ssec-check-phase). An
example of such a situation is when `py.test` is used.
#### Common issues {#common-issues}
* Non-working tests can often be deselected. By default `buildPythonPackage`
* Non-working tests can often be deselected. By default [`buildPythonPackage`](#buildpythonpackage-function)
runs `python setup.py test`. which is deprecated. Most Python modules however
do follow the standard test protocol where the pytest runner can be used
instead. `pytest` supports the `-k` and `--ignore` parameters to ignore test
@@ -2015,10 +2014,10 @@ example of such a situation is when `py.test` is used.
The following rules are desired to be respected:
* Python libraries are called from `python-packages.nix` and packaged with
`buildPythonPackage`. The expression of a library should be in
[`buildPythonPackage`](#buildpythonpackage-function). The expression of a library should be in
`pkgs/development/python-modules/<name>/default.nix`.
* Python applications live outside of `python-packages.nix` and are packaged
with `buildPythonApplication`.
with [`buildPythonApplication`](#buildpythonapplication-function).
* Make sure libraries build for all Python interpreters.
* By default we enable tests. Make sure the tests are found and, in the case of
libraries, are passing for all interpreters. If certain tests fail they can be
+5 -39
View File
@@ -1,7 +1,5 @@
# Multiple-output packages {#chap-multiple-output}
## Introduction {#sec-multiple-outputs-introduction}
The Nix language allows a derivation to produce multiple outputs, which is similar to what is utilized by other Linux distribution packaging systems. The outputs reside in separate Nix store paths, so they can be mostly handled independently of each other, including passing to build inputs, garbage collection or binary substitution. The exception is that building from source always produces all the outputs.
The main motivation is to save disk space by reducing runtime closure sizes; consequently also sizes of substituted binaries get reduced. Splitting can be used to have more granular runtime dependencies, for example the typical reduction is to split away development-only files, as those are typically not needed during runtime. As a result, closure sizes of many packages can get reduced to a half or even much less.
@@ -10,44 +8,12 @@ The main motivation is to save disk space by reducing runtime closure sizes; con
The reduction effects could be instead achieved by building the parts in completely separate derivations. That would often additionally reduce build-time closures, but it tends to be much harder to write such derivations, as build systems typically assume all parts are being built at once. This compromise approach of single source package producing multiple binary packages is also utilized often by rpm and deb.
:::
A number of attributes can be used to work with a derivation with multiple outputs. The attribute `outputs` is a list of strings, which are the names of the outputs. For each of these names, an identically named attribute is created, corresponding to that output. The attribute `meta.outputsToInstall` is used to determine the default set of outputs to install when using the derivation name unqualified.
A number of attributes can be used to work with a derivation with multiple outputs.
The attribute `outputs` is a list of strings, which are the names of the outputs.
For each of these names, an identically named attribute is created, corresponding to that output.
## Installing a split package {#sec-multiple-outputs-installing}
When installing a package with multiple outputs, the packages `meta.outputsToInstall` attribute determines which outputs are actually installed. `meta.outputsToInstall` is a list whose [default installs binaries and the associated man pages](https://github.com/NixOS/nixpkgs/blob/f1680774340d5443a1409c3421ced84ac1163ba9/pkgs/stdenv/generic/make-derivation.nix#L310-L320). The following sections describe ways to install different outputs.
### Selecting outputs to install via NixOS {#sec-multiple-outputs-installing-nixos}
NixOS provides two ways to select the outputs to install for packages listed in `environment.systemPackages`:
- The configuration option `environment.extraOutputsToInstall` is appended to each packages `meta.outputsToInstall` attribute to determine the outputs to install. It can for example be used to install `info` documentation or debug symbols for all packages.
- The outputs can be listed as packages in `environment.systemPackages`. For example, the `"out"` and `"info"` outputs for the `coreutils` package can be installed by including `coreutils` and `coreutils.info` in `environment.systemPackages`.
### Selecting outputs to install via `nix-env` {#sec-multiple-outputs-installing-nix-env}
`nix-env` lacks an easy way to select the outputs to install. When installing a package, `nix-env` always installs the outputs listed in `meta.outputsToInstall`, even when the user explicitly selects an output.
::: {.warning}
`nix-env` silently disregards the outputs selected by the user, and instead installs the outputs from `meta.outputsToInstall`. For example,
```ShellSession
$ nix-env -iA nixpkgs.coreutils.info
```
installs the `"out"` output (`coreutils.meta.outputsToInstall` is `[ "out" ]`) instead of the requested `"info"`.
:::
The only recourse to select an output with `nix-env` is to override the packages `meta.outputsToInstall`, using the functions described in [](#chap-overrides). For example, the following overlay adds the `"info"` output for the `coreutils` package:
```nix
self: super:
{
coreutils = super.coreutils.overrideAttrs (oldAttrs: {
meta = oldAttrs.meta // { outputsToInstall = oldAttrs.meta.outputsToInstall or [ "out" ] ++ [ "info" ]; };
});
}
```
The attribute `meta.outputsToInstall` is used to determine the [default set of outputs to install](https://github.com/NixOS/nixpkgs/blob/08c3198f1c6fd89a09f8f0ea09b425028a34de3e/pkgs/stdenv/generic/check-meta.nix#L411-L426) when using the derivation name unqualified:
`bin`, or `out`, or the first specified output; as well as `man` if that is specified.
## Using a split package {#sec-multiple-outputs-using-split-packages}
+13 -11
View File
@@ -41,13 +41,21 @@ An attribute set with these values:
- `_type` (constant string `"fileset"`):
Tag to indicate this value is a file set.
- `_internalVersion` (constant string equal to the current version):
Version of the representation
- `_internalVersion` (constant `2`, the current version):
Version of the representation.
- `_internalBase` (path):
Any files outside of this path cannot influence the set of files.
This is always a directory.
- `_internalBaseRoot` (path):
The filesystem root of `_internalBase`, same as `(lib.path.splitRoot _internalBase).root`.
This is here because this needs to be computed anyway, and this computation shouldn't be duplicated.
- `_internalBaseComponents` (list of strings):
The path components of `_internalBase`, same as `lib.path.subpath.components (lib.path.splitRoot _internalBase).subpath`.
This is here because this needs to be computed anyway, and this computation shouldn't be duplicated.
- `_internalTree` ([filesetTree](#filesettree)):
A tree representation of all included files under `_internalBase`.
@@ -59,8 +67,8 @@ An attribute set with these values:
One of the following:
- `{ <name> = filesetTree; }`:
A directory with a nested `filesetTree` value for every directory entry.
Even entries that aren't included are present as `null` because it improves laziness and allows using this as a sort of `builtins.readDir` cache.
A directory with a nested `filesetTree` value for directory entries.
Entries not included may either be omitted or set to `null`, as necessary to improve efficiency or laziness.
- `"directory"`:
A directory with all its files included recursively, allowing early cutoff for some operations.
@@ -169,15 +177,9 @@ Arguments:
## To update in the future
Here's a list of places in the library that need to be updated in the future:
- > The file set library is currently very limited but is being expanded to include more functions over time.
- > The file set library is currently somewhat limited but is being expanded to include more functions over time.
in [the manual](../../doc/functions/fileset.section.md)
- > Currently the only way to construct file sets is using implicit coercion from paths.
in [the `toSource` reference](./default.nix)
- > For now filesets are always paths
in [the `toSource` implementation](./default.nix), also update the variable name there
- Once a tracing function exists, `__noEval` in [internal.nix](./internal.nix) should mention it
- If/Once a function to convert `lib.sources` values into file sets exists, the `_coerce` and `toSource` functions should be updated to mention that function in the error when such a value is passed
- If/Once a function exists that can optionally include a path depending on whether it exists, the error message for the path not existing in `_coerce` should mention the new function
+93 -47
View File
@@ -1,4 +1,6 @@
#!/usr/bin/env bash
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p sta jq bc nix -I nixpkgs=../..
# shellcheck disable=SC2016
# Benchmarks lib.fileset
# Run:
@@ -28,38 +30,6 @@ work="$tmp/work"
mkdir "$work"
cd "$work"
# Create a fairly populated tree
touch f{0..5}
mkdir d{0..5}
mkdir e{0..5}
touch d{0..5}/f{0..5}
mkdir -p d{0..5}/d{0..5}
mkdir -p e{0..5}/e{0..5}
touch d{0..5}/d{0..5}/f{0..5}
mkdir -p d{0..5}/d{0..5}/d{0..5}
mkdir -p e{0..5}/e{0..5}/e{0..5}
touch d{0..5}/d{0..5}/d{0..5}/f{0..5}
mkdir -p d{0..5}/d{0..5}/d{0..5}/d{0..5}
mkdir -p e{0..5}/e{0..5}/e{0..5}/e{0..5}
touch d{0..5}/d{0..5}/d{0..5}/d{0..5}/f{0..5}
bench() {
NIX_PATH=nixpkgs=$1 NIX_SHOW_STATS=1 NIX_SHOW_STATS_PATH=$tmp/stats.json \
nix-instantiate --eval --strict --show-trace >/dev/null \
--expr '(import <nixpkgs/lib>).fileset.toSource { root = ./.; fileset = ./.; }'
cat "$tmp/stats.json"
}
echo "Running benchmark on index" >&2
bench "$nixpkgs" > "$tmp/new.json"
(
echo "Checking out $compareTo" >&2
git -C "$nixpkgs" worktree add --quiet "$tmp/worktree" "$compareTo"
trap 'git -C "$nixpkgs" worktree remove "$tmp/worktree"' EXIT
echo "Running benchmark on $compareTo" >&2
bench "$tmp/worktree" > "$tmp/old.json"
)
declare -a stats=(
".envs.elements"
".envs.number"
@@ -77,18 +47,94 @@ declare -a stats=(
".values.number"
)
different=0
for stat in "${stats[@]}"; do
oldValue=$(jq "$stat" "$tmp/old.json")
newValue=$(jq "$stat" "$tmp/new.json")
if (( oldValue != newValue )); then
percent=$(bc <<< "scale=100; result = 100/$oldValue*$newValue; scale=4; result / 1")
if (( oldValue < newValue )); then
echo -e "Statistic $stat ($newValue) is \e[0;31m$percent% (+$(( newValue - oldValue )))\e[0m of the old value $oldValue" >&2
else
echo -e "Statistic $stat ($newValue) is \e[0;32m$percent% (-$(( oldValue - newValue )))\e[0m of the old value $oldValue" >&2
runs=10
run() {
# Empty the file
: > cpuTimes
for i in $(seq 0 "$runs"); do
NIX_PATH=nixpkgs=$1 NIX_SHOW_STATS=1 NIX_SHOW_STATS_PATH=$tmp/stats.json \
nix-instantiate --eval --strict --show-trace >/dev/null \
--expr 'with import <nixpkgs/lib>; with fileset; '"$2"
# Only measure the time after the first run, one is warmup
if (( i > 0 )); then
jq '.cpuTime' "$tmp/stats.json" >> cpuTimes
fi
(( different++ )) || true
fi
done
echo "$different stats differ between the current tree and $compareTo"
done
# Compute mean and standard deviation
read -r mean sd < <(sta --mean --sd --brief <cpuTimes)
jq --argjson mean "$mean" --argjson sd "$sd" \
'.cpuTimeMean = $mean | .cpuTimeSd = $sd' \
"$tmp/stats.json"
}
bench() {
echo "Benchmarking expression $1" >&2
#echo "Running benchmark on index" >&2
run "$nixpkgs" "$1" > "$tmp/new.json"
(
#echo "Checking out $compareTo" >&2
git -C "$nixpkgs" worktree add --quiet "$tmp/worktree" "$compareTo"
trap 'git -C "$nixpkgs" worktree remove "$tmp/worktree"' EXIT
#echo "Running benchmark on $compareTo" >&2
run "$tmp/worktree" "$1" > "$tmp/old.json"
)
read -r oldMean oldSd newMean newSd percentageMean percentageSd < \
<(jq -rn --slurpfile old "$tmp/old.json" --slurpfile new "$tmp/new.json" \
' $old[0].cpuTimeMean as $om
| $old[0].cpuTimeSd as $os
| $new[0].cpuTimeMean as $nm
| $new[0].cpuTimeSd as $ns
| (100 / $om * $nm) as $pm
# Copied from https://github.com/sharkdp/hyperfine/blob/b38d550b89b1dab85139eada01c91a60798db9cc/src/benchmark/relative_speed.rs#L46-L53
| ($pm * pow(pow($ns / $nm; 2) + pow($os / $om; 2); 0.5)) as $ps
| [ $om, $os, $nm, $ns, $pm, $ps ]
| @sh')
echo -e "Mean CPU time $newMean (σ = $newSd) for $runs runs is \e[0;33m$percentageMean% (σ = $percentageSd%)\e[0m of the old value $oldMean (σ = $oldSd)" >&2
different=0
for stat in "${stats[@]}"; do
oldValue=$(jq "$stat" "$tmp/old.json")
newValue=$(jq "$stat" "$tmp/new.json")
if (( oldValue != newValue )); then
percent=$(bc <<< "scale=100; result = 100/$oldValue*$newValue; scale=4; result / 1")
if (( oldValue < newValue )); then
echo -e "Statistic $stat ($newValue) is \e[0;31m$percent% (+$(( newValue - oldValue )))\e[0m of the old value $oldValue" >&2
else
echo -e "Statistic $stat ($newValue) is \e[0;32m$percent% (-$(( oldValue - newValue )))\e[0m of the old value $oldValue" >&2
fi
(( different++ )) || true
fi
done
echo "$different stats differ between the current tree and $compareTo"
echo ""
}
# Create a fairly populated tree
touch f{0..5}
mkdir d{0..5}
mkdir e{0..5}
touch d{0..5}/f{0..5}
mkdir -p d{0..5}/d{0..5}
mkdir -p e{0..5}/e{0..5}
touch d{0..5}/d{0..5}/f{0..5}
mkdir -p d{0..5}/d{0..5}/d{0..5}
mkdir -p e{0..5}/e{0..5}/e{0..5}
touch d{0..5}/d{0..5}/d{0..5}/f{0..5}
mkdir -p d{0..5}/d{0..5}/d{0..5}/d{0..5}
mkdir -p e{0..5}/e{0..5}/e{0..5}/e{0..5}
touch d{0..5}/d{0..5}/d{0..5}/d{0..5}/f{0..5}
bench 'toSource { root = ./.; fileset = ./.; }'
rm -rf -- *
touch {0..1000}
bench 'toSource { root = ./.; fileset = unions (mapAttrsToList (name: value: ./. + "/${name}") (builtins.readDir ./.)); }'
rm -rf -- *
+168 -18
View File
@@ -3,15 +3,22 @@ let
inherit (import ./internal.nix { inherit lib; })
_coerce
_coerceMany
_toSourceFilter
_unionMany
;
inherit (builtins)
isList
isPath
pathExists
typeOf
;
inherit (lib.lists)
imap0
;
inherit (lib.path)
hasPrefix
splitRoot
@@ -29,6 +36,10 @@ let
cleanSourceWith
;
inherit (lib.trivial)
pipe
;
in {
/*
@@ -51,16 +62,51 @@ in {
} -> SourceLike
Example:
# Import the current directory into the store but only include files under ./src
toSource { root = ./.; fileset = ./src; }
# Import the current directory into the store
# but only include files under ./src
toSource {
root = ./.;
fileset = ./src;
}
=> "/nix/store/...-source"
# The file set coerced from path ./bar could contain files outside the root ./foo, which is not allowed
toSource { root = ./foo; fileset = ./bar; }
# Import the current directory into the store
# but only include ./Makefile and all files under ./src
toSource {
root = ./.;
fileset = union
./Makefile
./src;
}
=> "/nix/store/...-source"
# Trying to include a file outside the root will fail
toSource {
root = ./.;
fileset = unions [
./Makefile
./src
../LICENSE
];
}
=> <error>
# The root needs to point to a directory that contains all the files
toSource {
root = ../.;
fileset = unions [
./Makefile
./src
../LICENSE
];
}
=> "/nix/store/...-source"
# The root has to be a local filesystem path
toSource { root = "/nix/store/...-source"; fileset = ./.; }
toSource {
root = "/nix/store/...-source";
fileset = ./.;
}
=> <error>
*/
toSource = {
@@ -69,7 +115,7 @@ in {
Paths in [strings](https://nixos.org/manual/nix/stable/language/values.html#type-string), including Nix store paths, cannot be passed as `root`.
`root` has to be a directory.
<!-- Ignore the indentation here, this is a nixdoc rendering bug that needs to be fixed -->
<!-- Ignore the indentation here, this is a nixdoc rendering bug that needs to be fixed: https://github.com/nix-community/nixdoc/issues/75 -->
:::{.note}
Changing `root` only affects the directory structure of the resulting store path, it does not change which files are added to the store.
The only way to change which files get added to the store is by changing the `fileset` attribute.
@@ -78,25 +124,32 @@ The only way to change which files get added to the store is by changing the `fi
root,
/*
(required) The file set whose files to import into the store.
Currently the only way to construct file sets is using [implicit coercion from paths](#sec-fileset-path-coercion).
If a directory does not recursively contain any file, it is omitted from the store path contents.
File sets can be created using other functions in this library.
This argument can also be a path,
which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
<!-- Ignore the indentation here, this is a nixdoc rendering bug that needs to be fixed: https://github.com/nix-community/nixdoc/issues/75 -->
:::{.note}
If a directory does not recursively contain any file, it is omitted from the store path contents.
:::
*/
fileset,
}:
let
# We cannot rename matched attribute arguments, so let's work around it with an extra `let in` statement
# For now filesets are always paths
filesetPath = fileset;
filesetArg = fileset;
in
let
fileset = _coerce "lib.fileset.toSource: `fileset`" filesetPath;
fileset = _coerce "lib.fileset.toSource: `fileset`" filesetArg;
rootFilesystemRoot = (splitRoot root).root;
filesetFilesystemRoot = (splitRoot fileset._internalBase).root;
sourceFilter = _toSourceFilter fileset;
in
if ! isPath root then
if isStringLike root then
throw ''
lib.fileset.toSource: `root` "${toString root}" is a string-like value, but it should be a path instead.
lib.fileset.toSource: `root` ("${toString root}") is a string-like value, but it should be a path instead.
Paths in strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.''
else
throw ''
@@ -105,27 +158,124 @@ The only way to change which files get added to the store is by changing the `fi
# See also ../path/README.md
else if rootFilesystemRoot != filesetFilesystemRoot then
throw ''
lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` "${toString root}":
lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` ("${toString root}"):
`root`: root "${toString rootFilesystemRoot}"
`fileset`: root "${toString filesetFilesystemRoot}"
Different roots are not supported.''
else if ! pathExists root then
throw ''
lib.fileset.toSource: `root` ${toString root} does not exist.''
lib.fileset.toSource: `root` (${toString root}) does not exist.''
else if pathType root != "directory" then
throw ''
lib.fileset.toSource: `root` ${toString root} is a file, but it should be a directory instead. Potential solutions:
lib.fileset.toSource: `root` (${toString root}) is a file, but it should be a directory instead. Potential solutions:
- If you want to import the file into the store _without_ a containing directory, use string interpolation or `builtins.path` instead of this function.
- If you want to import the file into the store _with_ a containing directory, set `root` to the containing directory, such as ${toString (dirOf root)}, and set `fileset` to the file path.''
else if ! hasPrefix root fileset._internalBase then
throw ''
lib.fileset.toSource: `fileset` could contain files in ${toString fileset._internalBase}, which is not under the `root` ${toString root}. Potential solutions:
lib.fileset.toSource: `fileset` could contain files in ${toString fileset._internalBase}, which is not under the `root` (${toString root}). Potential solutions:
- Set `root` to ${toString fileset._internalBase} or any directory higher up. This changes the layout of the resulting store path.
- Set `fileset` to a file set that cannot contain files outside the `root` ${toString root}. This could change the files included in the result.''
- Set `fileset` to a file set that cannot contain files outside the `root` (${toString root}). This could change the files included in the result.''
else
builtins.seq sourceFilter
cleanSourceWith {
name = "source";
src = root;
filter = _toSourceFilter fileset;
filter = sourceFilter;
};
/*
The file set containing all files that are in either of two given file sets.
This is the same as [`unions`](#function-library-lib.fileset.unions),
but takes just two file sets instead of a list.
See also [Union (set theory)](https://en.wikipedia.org/wiki/Union_(set_theory)).
The given file sets are evaluated as lazily as possible,
with the first argument being evaluated first if needed.
Type:
union :: FileSet -> FileSet -> FileSet
Example:
# Create a file set containing the file `Makefile`
# and all files recursively in the `src` directory
union ./Makefile ./src
# Create a file set containing the file `Makefile`
# and the LICENSE file from the parent directory
union ./Makefile ../LICENSE
*/
union =
# The first file set.
# This argument can also be a path,
# which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
fileset1:
# The second file set.
# This argument can also be a path,
# which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
fileset2:
_unionMany
(_coerceMany "lib.fileset.union" [
{
context = "first argument";
value = fileset1;
}
{
context = "second argument";
value = fileset2;
}
]);
/*
The file set containing all files that are in any of the given file sets.
This is the same as [`union`](#function-library-lib.fileset.unions),
but takes a list of file sets instead of just two.
See also [Union (set theory)](https://en.wikipedia.org/wiki/Union_(set_theory)).
The given file sets are evaluated as lazily as possible,
with earlier elements being evaluated first if needed.
Type:
unions :: [ FileSet ] -> FileSet
Example:
# Create a file set containing selected files
unions [
# Include the single file `Makefile` in the current directory
# This errors if the file doesn't exist
./Makefile
# Recursively include all files in the `src/code` directory
# If this directory is empty this has no effect
./src/code
# Include the files `run.sh` and `unit.c` from the `tests` directory
./tests/run.sh
./tests/unit.c
# Include the `LICENSE` file from the parent directory
../LICENSE
]
*/
unions =
# A list of file sets.
# Must contain at least 1 element.
# The elements can also be paths,
# which get [implicitly coerced to file sets](#sec-fileset-path-coercion).
filesets:
if ! isList filesets then
throw "lib.fileset.unions: Expected argument to be a list, but got a ${typeOf filesets}."
else if filesets == [ ] then
# TODO: This could be supported, but requires an extra internal representation for the empty file set, which would be special for not having a base path.
throw "lib.fileset.unions: Expected argument to be a list with at least one element, but it contains no elements."
else
pipe filesets [
# Annotate the elements with context, used by _coerceMany for better errors
(imap0 (i: el: {
context = "element ${toString i}";
value = el;
}))
(_coerceMany "lib.fileset.unions")
_unionMany
];
}
+166 -58
View File
@@ -14,6 +14,8 @@ let
inherit (lib.attrsets)
attrValues
mapAttrs
setAttrByPath
zipAttrsWith
;
inherit (lib.filesystem)
@@ -22,8 +24,16 @@ let
inherit (lib.lists)
all
commonPrefix
drop
elemAt
filter
findFirstIndex
foldl'
head
length
sublist
tail
;
inherit (lib.path)
@@ -33,6 +43,7 @@ let
inherit (lib.path.subpath)
components
join
;
inherit (lib.strings)
@@ -50,28 +61,61 @@ in
rec {
# If you change the internal representation, make sure to:
# - Update this version
# - Adjust _coerce to also accept and coerce older versions
# - Increment this version
# - Add an additional migration function below
# - Update the description of the internal representation in ./README.md
_currentVersion = 0;
_currentVersion = 2;
# Migrations between versions. The 0th element converts from v0 to v1, and so on
migrations = [
# Convert v0 into v1: Add the _internalBase{Root,Components} attributes
(
filesetV0:
let
parts = splitRoot filesetV0._internalBase;
in
filesetV0 // {
_internalVersion = 1;
_internalBaseRoot = parts.root;
_internalBaseComponents = components parts.subpath;
}
)
# Convert v1 into v2: filesetTree's can now also omit attributes to signal paths not being included
(
filesetV1:
# This change is backwards compatible (but not forwards compatible, so we still need a new version)
filesetV1 // {
_internalVersion = 2;
}
)
];
# Create a fileset, see ./README.md#fileset
# Type: path -> filesetTree -> fileset
_create = base: tree: {
_type = "fileset";
_create = base: tree:
let
# Decompose the base into its components
# See ../path/README.md for why we're not just using `toString`
parts = splitRoot base;
in
{
_type = "fileset";
_internalVersion = _currentVersion;
_internalBase = base;
_internalTree = tree;
_internalVersion = _currentVersion;
_internalBase = base;
_internalBaseRoot = parts.root;
_internalBaseComponents = components parts.subpath;
_internalTree = tree;
# Double __ to make it be evaluated and ordered first
__noEval = throw ''
lib.fileset: Directly evaluating a file set is not supported. Use `lib.fileset.toSource` to turn it into a usable source instead.'';
};
# Double __ to make it be evaluated and ordered first
__noEval = throw ''
lib.fileset: Directly evaluating a file set is not supported. Use `lib.fileset.toSource` to turn it into a usable source instead.'';
};
# Coerce a value to a fileset, erroring when the value cannot be coerced.
# The string gives the context for error messages.
# Type: String -> Path -> fileset
# Type: String -> (fileset | Path) -> fileset
_coerce = context: value:
if value._type or "" == "fileset" then
if value._internalVersion > _currentVersion then
@@ -80,22 +124,53 @@ rec {
- Internal version of the file set: ${toString value._internalVersion}
- Internal version of the library: ${toString _currentVersion}
Make sure to update your Nixpkgs to have a newer version of `lib.fileset`.''
else if value._internalVersion < _currentVersion then
let
# Get all the migration functions necessary to convert from the old to the current version
migrationsToApply = sublist value._internalVersion (_currentVersion - value._internalVersion) migrations;
in
foldl' (value: migration: migration value) value migrationsToApply
else
value
else if ! isPath value then
if isStringLike value then
throw ''
${context} "${toString value}" is a string-like value, but it should be a path instead.
${context} ("${toString value}") is a string-like value, but it should be a path instead.
Paths represented as strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.''
else
throw ''
${context} is of type ${typeOf value}, but it should be a path instead.''
else if ! pathExists value then
throw ''
${context} ${toString value} does not exist.''
${context} (${toString value}) does not exist.''
else
_singleton value;
# Coerce many values to filesets, erroring when any value cannot be coerced,
# or if the filesystem root of the values doesn't match.
# Type: String -> [ { context :: String, value :: fileset | Path } ] -> [ fileset ]
_coerceMany = functionContext: list:
let
filesets = map ({ context, value }:
_coerce "${functionContext}: ${context}" value
) list;
firstBaseRoot = (head filesets)._internalBaseRoot;
# Finds the first element with a filesystem root different than the first element, if any
differentIndex = findFirstIndex (fileset:
firstBaseRoot != fileset._internalBaseRoot
) null filesets;
in
if differentIndex != null then
throw ''
${functionContext}: Filesystem roots are not the same:
${(head list).context}: root "${toString firstBaseRoot}"
${(elemAt list differentIndex).context}: root "${toString (elemAt filesets differentIndex)._internalBaseRoot}"
Different roots are not supported.''
else
filesets;
# Create a file set from a path.
# Type: Path -> fileset
_singleton = path:
@@ -109,50 +184,23 @@ rec {
# - _internalBase: ./.
# - _internalTree: {
# "default.nix" = <type>;
# # Other directory entries
# <name> = null;
# }
# See ./README.md#single-files
_create (dirOf path)
(_nestTree
(dirOf path)
[ (baseNameOf path) ]
type
);
{
${baseNameOf path} = type;
};
/*
Nest a filesetTree under some extra components, while filling out all the other directory entries that aren't included with null
_nestTree ./. [ "foo" "bar" ] tree == {
foo = {
bar = tree;
<other-entries> = null;
}
<other-entries> = null;
}
Type: Path -> [ String ] -> filesetTree -> filesetTree
*/
_nestTree = targetBase: extraComponents: tree:
let
recurse = index: focusPath:
if index == length extraComponents then
tree
else
mapAttrs (_: _: null) (readDir focusPath)
// {
${elemAt extraComponents index} = recurse (index + 1) (append focusPath (elemAt extraComponents index));
};
in
recurse 0 targetBase;
# Expand "directory" filesetTree representation to the equivalent { <name> = filesetTree; }
# Expand a directory representation to an equivalent one in attribute set form.
# All directory entries are included in the result.
# Type: Path -> filesetTree -> { <name> = filesetTree; }
_directoryEntries = path: value:
if isAttrs value then
value
if value == "directory" then
readDir path
else
readDir path;
# Set all entries not present to null
mapAttrs (name: value: null) (readDir path)
// value;
/*
Simplify a filesetTree recursively:
@@ -193,17 +241,13 @@ rec {
# which has the effect that they aren't included in the result
tree = _simplifyTree fileset._internalBase fileset._internalTree;
# Decompose the base into its components
# See ../path/README.md for why we're not just using `toString`
baseComponents = components (splitRoot fileset._internalBase).subpath;
# The base path as a string with a single trailing slash
baseString =
if baseComponents == [] then
if fileset._internalBaseComponents == [] then
# Need to handle the filesystem root specially
"/"
else
"/" + concatStringsSep "/" baseComponents + "/";
"/" + concatStringsSep "/" fileset._internalBaseComponents + "/";
baseLength = stringLength baseString;
@@ -266,9 +310,73 @@ rec {
in
# Special case because the code below assumes that the _internalBase is always included in the result
# which shouldn't be done when we have no files at all in the base
# This also forces the tree before returning the filter, leads to earlier error messages
if tree == null then
empty
else
nonEmpty;
# Computes the union of a list of filesets.
# The filesets must already be coerced and validated to be in the same filesystem root
# Type: [ Fileset ] -> Fileset
_unionMany = filesets:
let
first = head filesets;
# To be able to union filesetTree's together, they need to have the same base path.
# Base paths can be unioned by taking their common prefix,
# e.g. such that `union /foo/bar /foo/baz` has the base path `/foo`
# A list of path components common to all base paths.
# Note that commonPrefix can only be fully evaluated,
# so this cannot cause a stack overflow due to a build-up of unevaluated thunks.
commonBaseComponents = foldl'
(components: el: commonPrefix components el._internalBaseComponents)
first._internalBaseComponents
# We could also not do the `tail` here to avoid a list allocation,
# but then we'd have to pay for a potentially expensive
# but unnecessary `commonPrefix` call
(tail filesets);
# The common base path assembled from a filesystem root and the common components
commonBase = append first._internalBaseRoot (join commonBaseComponents);
# A list of filesetTree's that all have the same base path
# This is achieved by nesting the trees into the components they have over the common base path
# E.g. `union /foo/bar /foo/baz` has the base path /foo
# So the tree under `/foo/bar` gets nested under `{ bar = ...; ... }`,
# while the tree under `/foo/baz` gets nested under `{ baz = ...; ... }`
# Therefore allowing combined operations over them.
trees = map (fileset:
setAttrByPath
(drop (length commonBaseComponents) fileset._internalBaseComponents)
fileset._internalTree
) filesets;
# Folds all trees together into a single one using _unionTree
# We do not use a fold here because it would cause a thunk build-up
# which could cause a stack overflow for a large number of trees
resultTree = _unionTrees trees;
in
_create commonBase resultTree;
# The union of multiple filesetTree's with the same base path.
# Later elements are only evaluated if necessary.
# Type: [ filesetTree ] -> filesetTree
_unionTrees = trees:
let
stringIndex = findFirstIndex isString null trees;
withoutNull = filter (tree: tree != null) trees;
in
if stringIndex != null then
# If there's a string, it's always a fully included tree (dir or file),
# no need to look at other elements
elemAt trees stringIndex
else if withoutNull == [ ] then
# If all trees are null, then the resulting tree is also null
null
else
# The non-null elements have to be attribute sets representing partial trees
# We need to recurse into those
zipAttrsWith (name: _unionTrees) withoutNull;
}
+171 -42
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
# shellcheck disable=SC2016
# Tests lib.fileset
# Run:
@@ -50,27 +51,37 @@ with lib;
with internal;
with lib.fileset;'
# Check that a nix expression evaluates successfully (strictly, coercing to json, read-write-mode).
# The expression has `lib.fileset` in scope.
# If a second argument is provided, the result is checked against it as a regex.
# Otherwise, the result is output.
# Usage: expectSuccess NIX [REGEX]
expectSuccess() {
local expr=$1
if [[ "$#" -gt 1 ]]; then
local expectedResultRegex=$2
# Check that two nix expression successfully evaluate to the same value.
# The expressions have `lib.fileset` in scope.
# Usage: expectEqual NIX NIX
expectEqual() {
local actualExpr=$1
local expectedExpr=$2
if ! actualResult=$(nix-instantiate --eval --strict --show-trace \
--expr "$prefixExpression ($actualExpr)"); then
die "$actualExpr failed to evaluate, but it was expected to succeed"
fi
if ! expectedResult=$(nix-instantiate --eval --strict --show-trace \
--expr "$prefixExpression ($expectedExpr)"); then
die "$expectedExpr failed to evaluate, but it was expected to succeed"
fi
if [[ "$actualResult" != "$expectedResult" ]]; then
die "$actualExpr should have evaluated to $expectedExpr:\n$expectedResult\n\nbut it evaluated to\n$actualResult"
fi
}
# Check that a nix expression evaluates successfully to a store path and returns it (without quotes).
# The expression has `lib.fileset` in scope.
# Usage: expectStorePath NIX
expectStorePath() {
local expr=$1
if ! result=$(nix-instantiate --eval --strict --json --read-write-mode --show-trace \
--expr "$prefixExpression $expr"); then
--expr "$prefixExpression ($expr)"); then
die "$expr failed to evaluate, but it was expected to succeed"
fi
if [[ -v expectedResultRegex ]]; then
if [[ ! "$result" =~ $expectedResultRegex ]]; then
die "$expr should have evaluated to this regex pattern:\n\n$expectedResultRegex\n\nbut this was the actual result:\n\n$result"
fi
else
echo "$result"
fi
# This is safe because we assume to get back a store path in a string
crudeUnquoteJSON <<< "$result"
}
# Check that a nix expression fails to evaluate (strictly, coercing to json, read-write-mode).
@@ -114,18 +125,19 @@ checkFileset() (
local fileset=$1
# Process the tree into separate arrays for included paths, excluded paths and excluded files.
# Also create all the paths in the local directory
local -a included=()
local -a excluded=()
local -a excludedFiles=()
# Track which paths need to be created
local -a dirsToCreate=()
local -a filesToCreate=()
for p in "${!tree[@]}"; do
# If keys end with a `/` we treat them as directories, otherwise files
if [[ "$p" =~ /$ ]]; then
mkdir -p "$p"
dirsToCreate+=("$p")
isFile=
else
mkdir -p "$(dirname "$p")"
touch "$p"
filesToCreate+=("$p")
isFile=1
fi
case "${tree[$p]}" in
@@ -143,6 +155,19 @@ checkFileset() (
esac
done
# Create all the necessary paths.
# This is done with only a fixed number of processes,
# in order to not be too slow
# Though this does mean we're a bit limited with how many files can be created
if (( ${#dirsToCreate[@]} != 0 )); then
mkdir -p "${dirsToCreate[@]}"
fi
if (( ${#filesToCreate[@]} != 0 )); then
readarray -d '' -t parentsToCreate < <(dirname -z "${filesToCreate[@]}")
mkdir -p "${parentsToCreate[@]}"
touch "${filesToCreate[@]}"
fi
# Start inotifywait in the background to monitor all excluded files (if any)
if [[ -n "$canMonitorFiles" ]] && (( "${#excludedFiles[@]}" != 0 )); then
coproc watcher {
@@ -154,6 +179,7 @@ checkFileset() (
}
# This will trigger when this subshell exits, no matter if successful or not
# After exiting the subshell, the parent shell will continue executing
# shellcheck disable=SC2154
trap 'kill "${watcher_PID}"' exit
# Synchronously wait until inotifywait is ready
@@ -164,8 +190,7 @@ checkFileset() (
# Call toSource with the fileset, triggering open events for all files that are added to the store
expression="toSource { root = ./.; fileset = $fileset; }"
# crudeUnquoteJSON is safe because we get back a store path in a string
storePath=$(expectSuccess "$expression" | crudeUnquoteJSON)
storePath=$(expectStorePath "$expression")
# Remove all files immediately after, triggering delete_self events for all of them
rm -rf -- *
@@ -211,7 +236,7 @@ checkFileset() (
#### Error messages #####
# Absolute paths in strings cannot be passed as `root`
expectFailure 'toSource { root = "/nix/store/foobar"; fileset = ./.; }' 'lib.fileset.toSource: `root` "/nix/store/foobar" is a string-like value, but it should be a path instead.
expectFailure 'toSource { root = "/nix/store/foobar"; fileset = ./.; }' 'lib.fileset.toSource: `root` \("/nix/store/foobar"\) is a string-like value, but it should be a path instead.
\s*Paths in strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.'
# Only paths are accepted as `root`
@@ -221,56 +246,65 @@ expectFailure 'toSource { root = 10; fileset = ./.; }' 'lib.fileset.toSource: `r
mkdir -p {foo,bar}/mock-root
expectFailure 'with ((import <nixpkgs/lib>).extend (import <nixpkgs/lib/fileset/mock-splitRoot.nix>)).fileset;
toSource { root = ./foo/mock-root; fileset = ./bar/mock-root; }
' 'lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` "'"$work"'/foo/mock-root":
' 'lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` \("'"$work"'/foo/mock-root"\):
\s*`root`: root "'"$work"'/foo/mock-root"
\s*`fileset`: root "'"$work"'/bar/mock-root"
\s*Different roots are not supported.'
rm -rf *
# `root` needs to exist
expectFailure 'toSource { root = ./a; fileset = ./.; }' 'lib.fileset.toSource: `root` '"$work"'/a does not exist.'
expectFailure 'toSource { root = ./a; fileset = ./.; }' 'lib.fileset.toSource: `root` \('"$work"'/a\) does not exist.'
# `root` needs to be a file
touch a
expectFailure 'toSource { root = ./a; fileset = ./a; }' 'lib.fileset.toSource: `root` '"$work"'/a is a file, but it should be a directory instead. Potential solutions:
expectFailure 'toSource { root = ./a; fileset = ./a; }' 'lib.fileset.toSource: `root` \('"$work"'/a\) is a file, but it should be a directory instead. Potential solutions:
\s*- If you want to import the file into the store _without_ a containing directory, use string interpolation or `builtins.path` instead of this function.
\s*- If you want to import the file into the store _with_ a containing directory, set `root` to the containing directory, such as '"$work"', and set `fileset` to the file path.'
rm -rf *
# The fileset argument should be evaluated, even if the directory is empty
expectFailure 'toSource { root = ./.; fileset = abort "This should be evaluated"; }' 'evaluation aborted with the following error message: '\''This should be evaluated'\'
# Only paths under `root` should be able to influence the result
mkdir a
expectFailure 'toSource { root = ./a; fileset = ./.; }' 'lib.fileset.toSource: `fileset` could contain files in '"$work"', which is not under the `root` '"$work"'/a. Potential solutions:
expectFailure 'toSource { root = ./a; fileset = ./.; }' 'lib.fileset.toSource: `fileset` could contain files in '"$work"', which is not under the `root` \('"$work"'/a\). Potential solutions:
\s*- Set `root` to '"$work"' or any directory higher up. This changes the layout of the resulting store path.
\s*- Set `fileset` to a file set that cannot contain files outside the `root` '"$work"'/a. This could change the files included in the result.'
\s*- Set `fileset` to a file set that cannot contain files outside the `root` \('"$work"'/a\). This could change the files included in the result.'
rm -rf *
# Path coercion only works for paths
expectFailure 'toSource { root = ./.; fileset = 10; }' 'lib.fileset.toSource: `fileset` is of type int, but it should be a path instead.'
expectFailure 'toSource { root = ./.; fileset = "/some/path"; }' 'lib.fileset.toSource: `fileset` "/some/path" is a string-like value, but it should be a path instead.
expectFailure 'toSource { root = ./.; fileset = "/some/path"; }' 'lib.fileset.toSource: `fileset` \("/some/path"\) is a string-like value, but it should be a path instead.
\s*Paths represented as strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.'
# Path coercion errors for non-existent paths
expectFailure 'toSource { root = ./.; fileset = ./a; }' 'lib.fileset.toSource: `fileset` '"$work"'/a does not exist.'
expectFailure 'toSource { root = ./.; fileset = ./a; }' 'lib.fileset.toSource: `fileset` \('"$work"'/a\) does not exist.'
# File sets cannot be evaluated directly
expectFailure '_create ./. null' 'lib.fileset: Directly evaluating a file set is not supported. Use `lib.fileset.toSource` to turn it into a usable source instead.'
expectFailure 'union ./. ./.' 'lib.fileset: Directly evaluating a file set is not supported. Use `lib.fileset.toSource` to turn it into a usable source instead.'
# Past versions of the internal representation are supported
expectEqual '_coerce "<tests>: value" { _type = "fileset"; _internalVersion = 0; _internalBase = ./.; }' \
'{ _internalBase = ./.; _internalBaseComponents = path.subpath.components (path.splitRoot ./.).subpath; _internalBaseRoot = /.; _internalVersion = 2; _type = "fileset"; }'
expectEqual '_coerce "<tests>: value" { _type = "fileset"; _internalVersion = 1; }' \
'{ _type = "fileset"; _internalVersion = 2; }'
# Future versions of the internal representation are unsupported
expectFailure '_coerce "<tests>: value" { _type = "fileset"; _internalVersion = 1; }' '<tests>: value is a file set created from a future version of the file set library with a different internal representation:
\s*- Internal version of the file set: 1
\s*- Internal version of the library: 0
expectFailure '_coerce "<tests>: value" { _type = "fileset"; _internalVersion = 3; }' '<tests>: value is a file set created from a future version of the file set library with a different internal representation:
\s*- Internal version of the file set: 3
\s*- Internal version of the library: 2
\s*Make sure to update your Nixpkgs to have a newer version of `lib.fileset`.'
# _create followed by _coerce should give the inputs back without any validation
expectSuccess '{
inherit (_coerce "<test>" (_create "base" "tree"))
expectEqual '{
inherit (_coerce "<test>" (_create ./. "directory"))
_internalVersion _internalBase _internalTree;
}' '\{"_internalBase":"base","_internalTree":"tree","_internalVersion":0\}'
}' '{ _internalBase = ./.; _internalTree = "directory"; _internalVersion = 2; }'
#### Resulting store path ####
# The store path name should be "source"
expectSuccess 'toSource { root = ./.; fileset = ./.; }' '"'"${NIX_STORE_DIR:-/nix/store}"'/.*-source"'
expectEqual 'toSource { root = ./.; fileset = ./.; }' 'sources.cleanSourceWith { name = "source"; src = ./.; }'
# We should be able to import an empty directory and end up with an empty result
tree=(
@@ -341,9 +375,104 @@ checkFileset './c'
# Test the source filter for the somewhat special case of files in the filesystem root
# We can't easily test this with the above functions because we can't write to the filesystem root and we don't want to make any assumptions which files are there in the sandbox
expectSuccess '_toSourceFilter (_create /. null) "/foo" ""' 'false'
expectSuccess '_toSourceFilter (_create /. { foo = "regular"; }) "/foo" ""' 'true'
expectSuccess '_toSourceFilter (_create /. { foo = null; }) "/foo" ""' 'false'
expectEqual '_toSourceFilter (_create /. null) "/foo" ""' 'false'
expectEqual '_toSourceFilter (_create /. { foo = "regular"; }) "/foo" ""' 'true'
expectEqual '_toSourceFilter (_create /. { foo = null; }) "/foo" ""' 'false'
## lib.fileset.union, lib.fileset.unions
# Different filesystem roots in root and fileset are not supported
mkdir -p {foo,bar}/mock-root
expectFailure 'with ((import <nixpkgs/lib>).extend (import <nixpkgs/lib/fileset/mock-splitRoot.nix>)).fileset;
toSource { root = ./.; fileset = union ./foo/mock-root ./bar/mock-root; }
' 'lib.fileset.union: Filesystem roots are not the same:
\s*first argument: root "'"$work"'/foo/mock-root"
\s*second argument: root "'"$work"'/bar/mock-root"
\s*Different roots are not supported.'
expectFailure 'with ((import <nixpkgs/lib>).extend (import <nixpkgs/lib/fileset/mock-splitRoot.nix>)).fileset;
toSource { root = ./.; fileset = unions [ ./foo/mock-root ./bar/mock-root ]; }
' 'lib.fileset.unions: Filesystem roots are not the same:
\s*element 0: root "'"$work"'/foo/mock-root"
\s*element 1: root "'"$work"'/bar/mock-root"
\s*Different roots are not supported.'
rm -rf *
# Coercion errors show the correct context
expectFailure 'toSource { root = ./.; fileset = union ./a ./.; }' 'lib.fileset.union: first argument \('"$work"'/a\) does not exist.'
expectFailure 'toSource { root = ./.; fileset = union ./. ./b; }' 'lib.fileset.union: second argument \('"$work"'/b\) does not exist.'
expectFailure 'toSource { root = ./.; fileset = unions [ ./a ./. ]; }' 'lib.fileset.unions: element 0 \('"$work"'/a\) does not exist.'
expectFailure 'toSource { root = ./.; fileset = unions [ ./. ./b ]; }' 'lib.fileset.unions: element 1 \('"$work"'/b\) does not exist.'
# unions needs a list with at least 1 element
expectFailure 'toSource { root = ./.; fileset = unions null; }' 'lib.fileset.unions: Expected argument to be a list, but got a null.'
expectFailure 'toSource { root = ./.; fileset = unions [ ]; }' 'lib.fileset.unions: Expected argument to be a list with at least one element, but it contains no elements.'
# The tree of later arguments should not be evaluated if a former argument already includes all files
tree=()
checkFileset 'union ./. (_create ./. (abort "This should not be used!"))'
checkFileset 'unions [ ./. (_create ./. (abort "This should not be used!")) ]'
# union doesn't include files that weren't specified
tree=(
[x]=1
[y]=1
[z]=0
)
checkFileset 'union ./x ./y'
checkFileset 'unions [ ./x ./y ]'
# Also for directories
tree=(
[x/a]=1
[x/b]=1
[y/a]=1
[y/b]=1
[z/a]=0
[z/b]=0
)
checkFileset 'union ./x ./y'
checkFileset 'unions [ ./x ./y ]'
# And for very specific paths
tree=(
[x/a]=1
[x/b]=0
[y/a]=0
[y/b]=1
[z/a]=0
[z/b]=0
)
checkFileset 'union ./x/a ./y/b'
checkFileset 'unions [ ./x/a ./y/b ]'
# unions or chained union's can include more paths
tree=(
[x/a]=1
[x/b]=1
[y/a]=1
[y/b]=0
[z/a]=0
[z/b]=1
)
checkFileset 'unions [ ./x/a ./x/b ./y/a ./z/b ]'
checkFileset 'union (union ./x/a ./x/b) (union ./y/a ./z/b)'
checkFileset 'union (union (union ./x/a ./x/b) ./y/a) ./z/b'
# unions should not stack overflow, even if many elements are passed
tree=()
for i in $(seq 1000); do
tree[$i/a]=1
tree[$i/b]=0
done
(
# Locally limit the maximum stack size to 100 * 1024 bytes
# If unions was implemented recursively, this would stack overflow
ulimit -s 100
checkFileset 'unions (mapAttrsToList (name: _: ./. + "/${name}/a") (builtins.readDir ./.))'
)
# TODO: Once we have combinators and a property testing library, derive property tests from https://en.wikipedia.org/wiki/Algebra_of_sets
+6 -1
View File
@@ -610,7 +610,7 @@ in mkLicense lset) ({
};
inria-icesl = {
fullName = "INRIA Non-Commercial License Agreement for IceSL";
fullName = "End User License Agreement for IceSL Software";
url = "https://icesl.loria.fr/assets/pdf/EULA_IceSL_binary.pdf";
free = false;
};
@@ -856,6 +856,11 @@ in mkLicense lset) ({
free = false;
};
ocamlLgplLinkingException = {
spdxId = "OCaml-LGPL-linking-exception";
fullName = "OCaml LGPL Linking Exception";
};
ocamlpro_nc = {
fullName = "OCamlPro Non Commercial license version 1";
url = "https://alt-ergo.ocamlpro.com/http/alt-ergo-2.2.0/OCamlPro-Non-Commercial-License.pdf";
+1
View File
@@ -183,6 +183,7 @@ rec {
else if final.isS390 && !final.isS390x then null
else if final.isx86_64 then "x86_64"
else if final.isx86 then "i386"
else if final.isMips64n32 then "mipsn32${lib.optionalString final.isLittleEndian "el"}"
else if final.isMips64 then "mips64${lib.optionalString final.isLittleEndian "el"}"
else final.uname.processor;
+1
View File
@@ -206,6 +206,7 @@ rec {
aarch64-embedded = {
config = "aarch64-none-elf";
libc = "newlib";
rustc.config = "aarch64-unknown-none";
};
aarch64be-embedded = {
+73
View File
@@ -211,6 +211,16 @@
fingerprint = "7B59 F09E 0FE5 BC34 F032 1FB4 5270 1DE5 F5F5 1125";
}];
};
_9glenda = {
email = "plan9git@proton.me";
matrix = "@9front:matrix.org";
github = "9glenda";
githubId = 69043370;
name = "9glenda";
keys = [{
fingerprint = "DBF4 E6D0 90B8 BEA4 4BFE 1F1C 3442 4321 39B5 0691";
}];
};
a1russell = {
email = "adamlr6+pub@gmail.com";
github = "a1russell";
@@ -2326,6 +2336,15 @@
github = "blaggacao";
githubId = 7548295;
};
blankparticle = {
name = "BlankParticle";
email = "blankparticle@gmail.com";
github = "BlankParticle";
githubId = 130567419;
keys = [{
fingerprint = "1757 64C3 7065 AA8D 614D 41C9 0ACE 126D 7B35 9261";
}];
};
blanky0230 = {
email = "blanky0230@gmail.com";
github = "blanky0230";
@@ -3136,6 +3155,11 @@
fingerprint = "9C56 1D64 30B2 8D6B DCBC 9CEB 73D5 E7FD EE3D E49A";
}];
};
chrpinedo = {
github = "chrpinedo";
githubId = 2324630;
name = "Christian Pinedo";
};
chuahou = {
email = "human+github@chuahou.dev";
github = "chuahou";
@@ -4534,6 +4558,16 @@
fingerprint = "4749 0887 CF3B 85A1 6355 C671 78C7 DD40 DF23 FB16";
}];
};
dpc = {
email = "dpc@dpc.pw";
github = "dpc";
githubId = 9209;
matrix = "@dpc:matrix.org";
name = "Dawid Ciężarkiewicz";
keys = [{
fingerprint = "0402 11D2 0830 2D71 5792 8197 86BB 1D5B 5575 7D38";
}];
};
DPDmancul = {
name = "Davide Peressoni";
email = "davide.peressoni@tuta.io";
@@ -9471,6 +9505,11 @@
githubId = 894884;
name = "Jakub Kozłowski";
};
kupac = {
github = "Kupac";
githubId = 8224569;
name = "László Kupcsik";
};
kurnevsky = {
email = "kurnevsky@gmail.com";
github = "kurnevsky";
@@ -10750,6 +10789,12 @@
githubId = 7878181;
name = "Mateo Diaz";
};
materus = {
email = "materus@podkos.pl";
github = "materusPL";
githubId = 28183516;
name = "Mateusz Słodkowicz";
};
math-42 = {
email = "matheus.4200@gmail.com";
github = "Math-42";
@@ -12367,6 +12412,12 @@
fingerprint = "7BC1 77D9 C222 B1DC FB2F 0484 C061 089E FEBF 7A35";
}];
};
nicegamer7 = {
name = "Kermina Awad";
email = "kerminaawad@gmail.com";
github = "nicegamer7";
githubId = 8083772;
};
nickcao = {
name = "Nick Cao";
email = "nickcao@nichi.co";
@@ -13582,6 +13633,12 @@
githubId = 34967;
name = "Julius de Bruijn";
};
pineapplehunter = {
email = "peshogo+nixpkgs@gmail.com";
github = "pineapplehunter";
githubId = 8869894;
name = "Shogo Takata";
};
pingiun = {
email = "nixos@pingiun.com";
github = "pingiun";
@@ -14992,6 +15049,12 @@
github = "rubyowo";
githubId = 105302757;
};
rudolfvesely = {
name = "Rudolf Vesely";
email = "i@rudolfvesely.com";
github = "rudolfvesely";
githubId = 13966949;
};
Ruixi-rebirth = {
name = "Ruixi-rebirth";
email = "ruixirebirth@gmail.com";
@@ -18490,6 +18553,16 @@
githubId = 7121530;
name = "Wolf Honoré";
};
wigust = {
name = "Oleg Pykhalov";
email = "go.wigust@gmail.com";
github = "wigust";
githubId = 7709598;
keys = [{
# primary: "C955 CC5D C048 7FB1 7966 40A9 199A F6A3 67E9 4ABB"
fingerprint = "7238 7123 8EAC EB63 4548 5857 167F 8EA5 001A FA9C";
}];
};
wildsebastian = {
name = "Sebastian Wild";
email = "sebastian@wild-siena.com";
+228
View File
@@ -0,0 +1,228 @@
#!/usr/bin/env nix-shell
#! nix-shell -i "python3 -I" -p "python3.withPackages(p: with p; [ rich structlog ])"
from abc import ABC, abstractclassmethod, abstractmethod
from contextlib import contextmanager
from pathlib import Path
from structlog.contextvars import bound_contextvars as log_context
from typing import ClassVar, List, Tuple
import hashlib, re, structlog
logger = structlog.getLogger("sha-to-SRI")
class Encoding(ABC):
alphabet: ClassVar[str]
@classmethod
@property
def name(cls) -> str:
return cls.__name__.lower()
def toSRI(self, s: str) -> str:
digest = self.decode(s)
assert len(digest) == self.n
from base64 import b64encode
return f"{self.hashName}-{b64encode(digest).decode()}"
@classmethod
def all(cls, h) -> 'List[Encoding]':
return [ c(h) for c in cls.__subclasses__() ]
def __init__(self, h):
self.n = h.digest_size
self.hashName = h.name
@property
@abstractmethod
def length(self) -> int:
...
@property
def regex(self) -> str:
return f"[{self.alphabet}]{{{self.length}}}"
@abstractmethod
def decode(self, s: str) -> bytes:
...
class Nix32(Encoding):
alphabet = "0123456789abcdfghijklmnpqrsvwxyz"
inverted = { c: i for i, c in enumerate(alphabet) }
@property
def length(self):
return 1 + (8 * self.n) // 5
def decode(self, s: str):
assert len(s) == self.length
out = [ 0 for _ in range(self.n) ]
# TODO: Do better than a list of byte-sized ints
for n, c in enumerate(reversed(s)):
digit = self.inverted[c]
i, j = divmod(5 * n, 8)
out[i] = out[i] | (digit << j) & 0xff
rem = digit >> (8 - j)
if rem == 0:
continue
elif i < self.n:
out[i+1] = rem
else:
raise ValueError(f"Invalid nix32 hash: '{s}'")
return bytes(out)
class Hex(Encoding):
alphabet = "0-9A-Fa-f"
@property
def length(self):
return 2 * self.n
def decode(self, s: str):
from binascii import unhexlify
return unhexlify(s)
class Base64(Encoding):
alphabet = "A-Za-z0-9+/"
@property
def format(self) -> Tuple[int, int]:
"""Number of characters in data and padding."""
i, k = divmod(self.n, 3)
return 4 * i + (0 if k == 0 else k + 1), (3 - k) % 3
@property
def length(self):
return sum(self.format)
@property
def regex(self):
data, padding = self.format
return f"[{self.alphabet}]{{{data}}}={{{padding}}}"
def decode(self, s):
from base64 import b64decode
return b64decode(s, validate = True)
_HASHES = (hashlib.new(n) for n in ('SHA-256', 'SHA-512'))
ENCODINGS = {
h.name: Encoding.all(h)
for h in _HASHES
}
RE = {
h: "|".join(
(f"({h}-)?" if e.name == 'base64' else '') +
f"(?P<{h}_{e.name}>{e.regex})"
for e in encodings
) for h, encodings in ENCODINGS.items()
}
_DEF_RE = re.compile("|".join(
f"(?P<{h}>{h} = (?P<{h}_quote>['\"])({re})(?P={h}_quote);)"
for h, re in RE.items()
))
def defToSRI(s: str) -> str:
def f(m: re.Match[str]) -> str:
try:
for h, encodings in ENCODINGS.items():
if m.group(h) is None:
continue
for e in encodings:
s = m.group(f"{h}_{e.name}")
if s is not None:
return f'hash = "{e.toSRI(s)}";'
raise ValueError(f"Match with '{h}' but no subgroup")
raise ValueError("Match with no hash")
except ValueError as exn:
logger.error(
"Skipping",
exc_info = exn,
)
return m.group()
return _DEF_RE.sub(f, s)
@contextmanager
def atomicFileUpdate(target: Path):
'''Atomically replace the contents of a file.
Guarantees that no temporary files are left behind, and `target` is either
left untouched, or overwritten with new content if no exception was raised.
Yields a pair `(original, new)` of open files.
`original` is the pre-existing file at `target`, open for reading;
`new` is an empty, temporary file in the same filder, open for writing.
Upon exiting the context, the files are closed; if no exception was
raised, `new` (atomically) replaces the `target`, otherwise it is deleted.
'''
# That's mostly copied from noto-emoji.py, should DRY it out
from tempfile import mkstemp
fd, _p = mkstemp(
dir = target.parent,
prefix = target.name,
)
tmpPath = Path(_p)
try:
with target.open() as original:
with tmpPath.open('w') as new:
yield (original, new)
tmpPath.replace(target)
except Exception:
tmpPath.unlink(missing_ok = True)
raise
def fileToSRI(p: Path):
with atomicFileUpdate(p) as (og, new):
for i, line in enumerate(og):
with log_context(line=i):
new.write(defToSRI(line))
_SKIP_RE = re.compile(
"(generated by)|(do not edit)",
re.IGNORECASE
)
if __name__ == "__main__":
from sys import argv, stderr
logger.info("Starting!")
for arg in argv[1:]:
p = Path(arg)
with log_context(path=str(p)):
try:
if p.name == "yarn.nix" or p.name.find("generated") != -1:
logger.warning("File looks autogenerated, skipping!")
continue
with p.open() as f:
for line in f:
if line.strip():
break
if _SKIP_RE.search(line):
logger.warning("File looks autogenerated, skipping!")
continue
fileToSRI(p)
except Exception as exn:
logger.error(
"Unhandled exception, skipping file!",
exc_info = exn,
)
else:
logger.info("Finished processing file")
-149
View File
@@ -1,149 +0,0 @@
#!/usr/bin/env nix-shell
#! nix-shell -i "python3 -I" -p "python3.withPackages(p: with p; [ rich structlog ])"
from contextlib import contextmanager
from pathlib import Path
from structlog.contextvars import bound_contextvars as log_context
import re, structlog
logger = structlog.getLogger("sha256-to-SRI")
nix32alphabet = "0123456789abcdfghijklmnpqrsvwxyz"
nix32inverted = { c: i for i, c in enumerate(nix32alphabet) }
def nix32decode(s: str) -> bytes:
# only support sha256 hashes for now
assert len(s) == 52
out = [ 0 for _ in range(32) ]
# TODO: Do better than a list of byte-sized ints
for n, c in enumerate(reversed(s)):
digit = nix32inverted[c]
i, j = divmod(5 * n, 8)
out[i] = out[i] | (digit << j) & 0xff
rem = digit >> (8 - j)
if rem == 0:
continue
elif i < 31:
out[i+1] = rem
else:
raise ValueError(f"Invalid nix32 hash: '{s}'")
return bytes(out)
def toSRI(digest: bytes) -> str:
from base64 import b64encode
assert len(digest) == 32
return f"sha256-{b64encode(digest).decode()}"
RE = {
'nix32': f"[{nix32alphabet}]" "{52}",
'hex': "[0-9A-Fa-f]{64}",
'base64': "[A-Za-z0-9+/]{43}=",
}
RE['sha256'] = '|'.join(
f"{'(sha256-)?' if name == 'base64' else ''}"
f"(?P<{name}>{r})"
for name, r in RE.items()
)
def sha256toSRI(m: re.Match) -> str:
"""Produce the equivalent SRI string for any match of RE['sha256']"""
if m['nix32'] is not None:
return toSRI(nix32decode(m['nix32']))
if m['hex'] is not None:
from binascii import unhexlify
return toSRI(unhexlify(m['hex']))
if m['base64'] is not None:
from base64 import b64decode
return toSRI(b64decode(m['base64']))
raise ValueError("Got a match where none of the groups captured")
# Ohno I used evil, irregular backrefs instead of making 2 variants ^^'
_def_re = re.compile(
"sha256 = (?P<quote>[\"'])"
f"({RE['sha256']})"
"(?P=quote);"
)
def defToSRI(s: str) -> str:
def f(m: re.Match[str]) -> str:
try:
return f'hash = "{sha256toSRI(m)}";'
except ValueError as exn:
begin, end = m.span()
match = m.string[begin:end]
logger.error(
"Skipping",
exc_info = exn,
)
return match
return _def_re.sub(f, s)
@contextmanager
def atomicFileUpdate(target: Path):
'''Atomically replace the contents of a file.
Guarantees that no temporary files are left behind, and `target` is either
left untouched, or overwritten with new content if no exception was raised.
Yields a pair `(original, new)` of open files.
`original` is the pre-existing file at `target`, open for reading;
`new` is an empty, temporary file in the same filder, open for writing.
Upon exiting the context, the files are closed; if no exception was
raised, `new` (atomically) replaces the `target`, otherwise it is deleted.
'''
# That's mostly copied from noto-emoji.py, should DRY it out
from tempfile import mkstemp
fd, _p = mkstemp(
dir = target.parent,
prefix = target.name,
)
tmpPath = Path(_p)
try:
with target.open() as original:
with tmpPath.open('w') as new:
yield (original, new)
tmpPath.replace(target)
except Exception:
tmpPath.unlink(missing_ok = True)
raise
def fileToSRI(p: Path):
with atomicFileUpdate(p) as (og, new):
for i, line in enumerate(og):
with log_context(line=i):
new.write(defToSRI(line))
if __name__ == "__main__":
from sys import argv, stderr
for arg in argv[1:]:
p = Path(arg)
with log_context(path=str(p)):
try:
fileToSRI(p)
except Exception as exn:
logger.error(
"Unhandled exception, skipping file!",
exc_info = exn,
)
else:
logger.info("Finished processing file")
+3 -5
View File
@@ -649,15 +649,13 @@ with lib.maintainers; {
enableFeatureFreezePing = true;
};
nixos-modules = {
module-system = {
members = [
ericson2314
infinisil
qyliss
roberth
];
scope = "Maintain nixpkgs module system internals.";
shortName = "NixOS Modules / internals";
scope = "Maintain the Nixpkgs module system.";
shortName = "Module system";
enableFeatureFreezePing = true;
};
@@ -21,8 +21,9 @@ If the action is `switch` or `test`, the currently running system is inspected
and the actions to switch to the new system are calculated. This process takes
two data sources into account: `/etc/fstab` and the current systemd status.
Mounts and swaps are read from `/etc/fstab` and the corresponding actions are
generated. If a new mount is added, for example, the proper `.mount` unit is
marked to be started. The current systemd state is inspected, the difference
generated. If the options of a mount are modified, for example, the proper `.mount`
unit is reloaded (or restarted if anything else changed and it's neither the root
mount or the nix store). The current systemd state is inspected, the difference
between the current system and the desired configuration is calculated and
actions are generated to get to this state. There are a lot of nuances that can
be controlled by the units which are explained here.
@@ -10,6 +10,17 @@
- The `nixos-rebuild` command has been given a `list-generations` subcommand. See `man nixos-rebuild` for more details.
- [`sudo-rs`], a reimplementation of `sudo` in Rust, is now supported.
An experimental new module `security.sudo-rs` was added.
Switching to it (via `security.sudo.enable = false; security.sudo-rs.enable = true;`) introduces
slight changes in sudo behaviour, due to `sudo-rs`' current limitations:
- terminfo-related environment variables aren't preserved for `root` and `wheel`;
- `root` and `wheel` are not given the ability to set (or preserve)
arbitrary environment variables.
[`sudo-rs`]: https://github.com/memorysafety/sudo-rs/
## New Services {#sec-release-23.11-new-services}
- [MCHPRS](https://github.com/MCHPR/MCHPRS), a multithreaded Minecraft server built for redstone. Available as [services.mchprs](#opt-services.mchprs.enable).
@@ -87,6 +98,8 @@
- `pass` now does not contain `password-store.el`. Users should get `password-store.el` from Emacs lisp package set `emacs.pkgs.password-store`.
- `services.knot` now supports `.settings` from RFC42. The change is not 100% compatible with the previous `.extraConfig`.
- `mu` now does not install `mu4e` files by default. Users should get `mu4e` from Emacs lisp package set `emacs.pkgs.mu4e`.
- `mariadb` now defaults to `mariadb_1011` instead of `mariadb_106`, meaning the default version was upgraded from 10.6.x to 10.11.x. See the [upgrade notes](https://mariadb.com/kb/en/upgrading-from-mariadb-10-6-to-mariadb-10-11/) for potential issues.
@@ -114,6 +127,8 @@
- `himalaya` has been updated to `0.8.0`, which drops the native TLS support (in favor of Rustls) and add OAuth 2.0 support. See the [release note](https://github.com/soywod/himalaya/releases/tag/v0.8.0) for more details.
- `nix-prefetch-git` now ignores global and user git config, to improve reproducibility.
- The [services.caddy.acmeCA](#opt-services.caddy.acmeCA) option now defaults to `null` instead of `"https://acme-v02.api.letsencrypt.org/directory"`, to use all of Caddy's default ACME CAs and enable Caddy's automatic issuer fallback feature by default, as recommended by upstream.
- The default priorities of [`services.nextcloud.phpOptions`](#opt-services.nextcloud.phpOptions) have changed. This means that e.g.
@@ -202,6 +217,17 @@
- Package `pash` was removed due to being archived upstream. Use `powershell` as an alternative.
- `security.sudo.extraRules` now includes `root`'s default rule, with ordering
priority 400. This is functionally identical for users not specifying rule
order, or relying on `mkBefore` and `mkAfter`, but may impact users calling
`mkOrder n` with n ≤ 400.
- `networking.networkmanager.firewallBackend` was removed as NixOS is now using iptables-nftables-compat even when using iptables, therefore Networkmanager now uses the nftables backend unconditionally.
- `rome` was removed because it is no longer maintained and is succeeded by `biome`.
- The `services.mtr-exporter.target` has been removed in favor of `services.mtr-exporter.jobs` which allows specifying multiple targets.
## Other Notable Changes {#sec-release-23.11-notable-changes}
- The Cinnamon module now enables XDG desktop integration by default. If you are experiencing collisions related to xdg-desktop-portal-gtk you can safely remove `xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];` from your NixOS configuration.
@@ -242,6 +268,8 @@
Unfortunately all servers supporting new clients (newer version of anki-sync-server, anki's built in sync server and this new rust package) do not support the older sync protocol that was used in the old server, so such old clients will also need updating and in particular the anki package in nixpkgs is also being updated in this release.
The module update takes care of the new config syntax and the data itself (user login and cards) are compatible, so users of the module will be able to just log in again after updating both client and server without any extra action.
- `services.matrix-synapse` has new options to configure worker processes for matrix-synapse using [`services.matrix-synapse.workers`](#opt-services.matrix-synapse.workers). It's also now possible to configure a local redis server using [`services.matrix-synapse.configureRedisLocally`](#opt-services.matrix-synapse.configureRedisLocally).
- `services.nginx` gained a `defaultListen` option at server-level with support for PROXY protocol listeners, also `proxyProtocol` is now exposed in `services.nginx.virtualHosts.<name>.listen` option. It is now possible to run PROXY listeners and non-PROXY listeners at a server-level, see [#213510](https://github.com/NixOS/nixpkgs/pull/213510/) for more details.
- `services.restic.backups` now adds wrapper scripts to your system path, which set the same environment variables as the service, so restic operations can easly be run from the command line. This behavior can be disabled by setting `createWrapper` to `false`, per backup configuration.
@@ -252,6 +280,11 @@ The module update takes care of the new config syntax and the data itself (user
- The module `services.calibre-server` has new options to configure the `host`, `port`, `auth.enable`, `auth.mode` and `auth.userDb` path, see [#216497](https://github.com/NixOS/nixpkgs/pull/216497/) for more details.
- Mattermost has been upgraded to extended support version 8.1 as the previously
packaged extended support version 7.8 is [reaching end of life](https://docs.mattermost.com/upgrade/extended-support-release.html).
Migration may take some time, see the [changelog](https://docs.mattermost.com/install/self-managed-changelog.html#release-v8-1-extended-support-release)
and [important upgrade notes](https://docs.mattermost.com/upgrade/important-upgrade-notes.html).
- `services.prometheus.exporters` has a new [exporter](https://github.com/hipages/php-fpm_exporter) to monitor PHP-FPM processes, see [#240394](https://github.com/NixOS/nixpkgs/pull/240394) for more details.
- `services.github-runner` / `services.github-runners.<name>` gained the option `nodeRuntimes`. The option defaults to `[ "node20" ]`, i.e., the service supports Node.js 20 GitHub Actions only. The list of Node.js versions accepted by `nodeRuntimes` tracks the versions the upstream GitHub Actions runner supports. See [#249103](https://github.com/NixOS/nixpkgs/pull/249103) for details.
@@ -282,6 +315,15 @@ The module update takes care of the new config syntax and the data itself (user
- New `boot.bcache.enable` (default enabled) allows completely removing `bcache` mount support.
- The module `services.mbpfan` now has the option `aggressive` enabled by default for better heat moderation. You can disable it for upstream defaults.
- `security.sudo` now provides two extra options, that do not change the
module's default behaviour:
- `defaultOptions` controls the options used for the default rules;
- `keepTerminfo` controls whether `TERMINFO` and `TERMINFO_DIRS` are preserved
for `root` and the `wheel` group.
## Nixpkgs internals {#sec-release-23.11-nixpkgs-internals}
- The use of `sourceRoot = "source";`, `sourceRoot = "source/subdir";`, and similar lines in package derivations using the default `unpackPhase` is deprecated as it requires `unpackPhase` to always produce a directory named "source". Use `sourceRoot = src.name`, `sourceRoot = "${src.name}/subdir";`, or `setSourceRoot = "sourceRoot=$(echo */subdir)";` or similar instead.
@@ -310,4 +352,6 @@ The module update takes care of the new config syntax and the data itself (user
can automatically format the root device by setting
`virtualisation.fileSystems."/".autoFormat = true;`.
- `python3.pkgs.flitBuildHook` has been removed. Use `flit-core` and `format = "pyproject"` instead.
- The `electron` packages now places its application files in `$out/libexec/electron` instead of `$out/lib/electron`. Packages using electron-builder will fail to build and need to be adjusted by changing `lib` to `libexec`.
+3 -3
View File
@@ -274,7 +274,7 @@ in rec {
});
in "${out}/bin/${scriptName}";
unitConfig = { config, options, ... }: {
unitConfig = { config, name, options, ... }: {
config = {
unitConfig =
optionalAttrs (config.requires != [])
@@ -294,9 +294,9 @@ in rec {
// optionalAttrs (config.requisite != [])
{ Requisite = toString config.requisite; }
// optionalAttrs (config ? restartTriggers && config.restartTriggers != [])
{ X-Restart-Triggers = "${pkgs.writeText "X-Restart-Triggers" (toString config.restartTriggers)}"; }
{ X-Restart-Triggers = "${pkgs.writeText "X-Restart-Triggers-${name}" (toString config.restartTriggers)}"; }
// optionalAttrs (config ? reloadTriggers && config.reloadTriggers != [])
{ X-Reload-Triggers = "${pkgs.writeText "X-Reload-Triggers" (toString config.reloadTriggers)}"; }
{ X-Reload-Triggers = "${pkgs.writeText "X-Reload-Triggers-${name}" (toString config.reloadTriggers)}"; }
// optionalAttrs (config.description != "") {
Description = config.description; }
// optionalAttrs (config.documentation != []) {
+18 -14
View File
@@ -736,7 +736,7 @@ class Machine:
)
return output
def wait_until_tty_matches(self, tty: str, regexp: str) -> None:
def wait_until_tty_matches(self, tty: str, regexp: str, timeout: int = 900) -> None:
"""Wait until the visible output on the chosen TTY matches regular
expression. Throws an exception on timeout.
"""
@@ -752,7 +752,7 @@ class Machine:
return len(matcher.findall(text)) > 0
with self.nested(f"waiting for {regexp} to appear on tty {tty}"):
retry(tty_matches)
retry(tty_matches, timeout)
def send_chars(self, chars: str, delay: Optional[float] = 0.01) -> None:
"""
@@ -764,7 +764,7 @@ class Machine:
for char in chars:
self.send_key(char, delay, log=False)
def wait_for_file(self, filename: str) -> None:
def wait_for_file(self, filename: str, timeout: int = 900) -> None:
"""
Waits until the file exists in the machine's file system.
"""
@@ -774,9 +774,11 @@ class Machine:
return status == 0
with self.nested(f"waiting for file '{filename}'"):
retry(check_file)
retry(check_file, timeout)
def wait_for_open_port(self, port: int, addr: str = "localhost") -> None:
def wait_for_open_port(
self, port: int, addr: str = "localhost", timeout: int = 900
) -> None:
"""
Wait until a process is listening on the given TCP port and IP address
(default `localhost`).
@@ -787,9 +789,11 @@ class Machine:
return status == 0
with self.nested(f"waiting for TCP port {port} on {addr}"):
retry(port_is_open)
retry(port_is_open, timeout)
def wait_for_closed_port(self, port: int, addr: str = "localhost") -> None:
def wait_for_closed_port(
self, port: int, addr: str = "localhost", timeout: int = 900
) -> None:
"""
Wait until nobody is listening on the given TCP port and IP address
(default `localhost`).
@@ -800,7 +804,7 @@ class Machine:
return status != 0
with self.nested(f"waiting for TCP port {port} on {addr} to be closed"):
retry(port_is_closed)
retry(port_is_closed, timeout)
def start_job(self, jobname: str, user: Optional[str] = None) -> Tuple[int, str]:
return self.systemctl(f"start {jobname}", user)
@@ -974,7 +978,7 @@ class Machine:
"""
return self._get_screen_text_variants([2])[0]
def wait_for_text(self, regex: str) -> None:
def wait_for_text(self, regex: str, timeout: int = 900) -> None:
"""
Wait until the supplied regular expressions matches the textual
contents of the screen by using optical character recognition (see
@@ -997,7 +1001,7 @@ class Machine:
return False
with self.nested(f"waiting for {regex} to appear on screen"):
retry(screen_matches)
retry(screen_matches, timeout)
def wait_for_console_text(self, regex: str, timeout: int | None = None) -> None:
"""
@@ -1148,7 +1152,7 @@ class Machine:
self.send_key("ctrl-alt-delete")
self.connected = False
def wait_for_x(self) -> None:
def wait_for_x(self, timeout: int = 900) -> None:
"""
Wait until it is possible to connect to the X server.
"""
@@ -1165,14 +1169,14 @@ class Machine:
return status == 0
with self.nested("waiting for the X11 server"):
retry(check_x)
retry(check_x, timeout)
def get_window_names(self) -> List[str]:
return self.succeed(
r"xwininfo -root -tree | sed 's/.*0x[0-9a-f]* \"\([^\"]*\)\".*/\1/; t; d'"
).splitlines()
def wait_for_window(self, regexp: str) -> None:
def wait_for_window(self, regexp: str, timeout: int = 900) -> None:
"""
Wait until an X11 window has appeared whose name matches the given
regular expression, e.g., `wait_for_window("Terminal")`.
@@ -1190,7 +1194,7 @@ class Machine:
return any(pattern.search(name) for name in names)
with self.nested("waiting for a window to appear"):
retry(window_is_visible)
retry(window_is_visible, timeout)
def sleep(self, secs: int) -> None:
# We want to sleep in *guest* time, not *host* time.
+24
View File
@@ -0,0 +1,24 @@
#! /usr/bin/env bash
set -euo pipefail
export NIX_PATH=nixpkgs=$(dirname $(readlink -f $0))/../../../..
export NIXOS_CONFIG=$(dirname $(readlink -f $0))/../../../modules/virtualisation/oci-image.nix
if (( $# < 1 )); then
(
echo "Usage: create-image.sh <architecture>"
echo
echo "Where <architecture> is one of:"
echo " x86_64-linux"
echo " aarch64-linux"
) >&2
fi
system="$1"; shift
nix-build '<nixpkgs/nixos>' \
-A config.system.build.OCIImage \
--argstr system "$system" \
--option system-features kvm \
-o oci-image
+100
View File
@@ -0,0 +1,100 @@
#! /usr/bin/env bash
set -euo pipefail
script_dir="$(dirname $(readlink -f $0))"
nixpkgs_root="$script_dir/../../../.."
export NIX_PATH="nixpkgs=$nixpkgs_root"
cat - <<EOF
This script will locally build a NixOS image and upload it as a Custom Image
using oci-cli. Make sure that an API key for the tenancy administrator has been
added to '~/.oci'.
For more info about configuring oci-cli, please visit
https://docs.cloud.oracle.com/iaas/Content/API/Concepts/apisigningkey.htm#Required_Keys_and_OCIDs
EOF
qcow="oci-image/nixos.qcow2"
if [ ! -f "$qcow" ]; then
echo "OCI image $qcow does not exist"
echo "Building image with create-image.sh for 'x86_64-linux'"
"$script_dir/create-image.sh" x86_64-linux
[ -f "$qcow" ] || { echo "Build failed: image not present after build"; exit 1; }
else
echo "Using prebuilt image $qcow"
fi
cli="$(
nix-build '<nixpkgs>' \
--no-out-link \
-A oci-cli
)"
PATH="$cli/bin:$PATH"
bucket="_TEMP_NIXOS_IMAGES_$RANDOM"
echo "Creating a temporary bucket"
root_ocid="$(
oci iam compartment list \
--all \
--compartment-id-in-subtree true \
--access-level ACCESSIBLE \
--include-root \
--raw-output \
--query "data[?contains(\"id\",'tenancy')].id | [0]"
)"
bucket_ocid=$(
oci os bucket create \
-c "$root_ocid" \
--name "$bucket" \
--raw-output \
--query "data.id"
)
# Clean up bucket on script termination
trap 'echo Removing temporary bucket; oci os bucket delete --force --name "$bucket"' INT TERM EXIT
echo "Uploading image to temporary bucket"
oci os object put -bn "$bucket" --file "$qcow"
echo "Importing image as a Custom Image"
bucket_ns="$(oci os ns get --query "data" --raw-output)"
image_id="$(
oci compute image import from-object \
-c "$root_ocid" \
--namespace "$bucket_ns" \
--bucket-name "$bucket" \
--name nixos.qcow2 \
--operating-system NixOS \
--source-image-type QCOW2 \
--launch-mode PARAVIRTUALIZED \
--display-name NixOS \
--raw-output \
--query "data.id"
)"
cat - <<EOF
Image created! Please mark all available shapes as compatible with this image by
visiting the following link and by selecting the 'Edit Details' button on:
https://cloud.oracle.com/compute/images/$image_id
EOF
# Workaround until https://github.com/oracle/oci-cli/issues/399 is addressed
echo "Sleeping for 15 minutes before cleaning up files in the temporary bucket"
sleep $((15 * 60))
echo "Deleting image from bucket"
par_id="$(
oci os preauth-request list \
--bucket-name "$bucket" \
--raw-output \
--query "data[0].id"
)"
if [[ -n $par_id ]]; then
oci os preauth-request delete \
--bucket-name "$bucket" \
--par-id "$par_id"
fi
oci os object delete -bn "$bucket" --object-name nixos.qcow2 --force
+8 -2
View File
@@ -116,8 +116,14 @@ in
extraOutputsToInstall = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "doc" "info" "devdoc" ];
description = lib.mdDoc "List of additional package outputs to be symlinked into {file}`/run/current-system/sw`.";
example = [ "dev" "info" ];
description = lib.mdDoc ''
Entries listed here will be appended to the `meta.outputsToInstall` attribute for each package in `environment.systemPackages`, and the files from the corresponding derivation outputs symlinked into {file}`/run/current-system/sw`.
For example, this can be used to install the `dev` and `info` outputs for all packages in the system environment, if they are available.
To use specific outputs instead of configuring them globally, select the corresponding attribute on the package derivation, e.g. `libxml2.dev` or `coreutils.info`.
'';
};
extraSetup = mkOption {
+21 -7
View File
@@ -6,12 +6,26 @@ with lib;
{
options.environment.enableAllTerminfo = with lib; mkOption {
default = false;
type = types.bool;
description = lib.mdDoc ''
Whether to install all terminfo outputs
'';
options = with lib; {
environment.enableAllTerminfo = mkOption {
default = false;
type = types.bool;
description = lib.mdDoc ''
Whether to install all terminfo outputs
'';
};
security.sudo.keepTerminfo = mkOption {
default = config.security.sudo.package.pname != "sudo-rs";
defaultText = literalMD ''
`true` unless using `sudo-rs`
'';
type = types.bool;
description = lib.mdDoc ''
Whether to preserve the `TERMINFO` and `TERMINFO_DIRS`
environment variables, for `root` and the `wheel` group.
'';
};
};
config = {
@@ -54,7 +68,7 @@ with lib;
export TERM=$TERM
'';
security.sudo.extraConfig = ''
security.sudo.extraConfig = mkIf config.security.sudo.keepTerminfo ''
# Keep terminfo database for root and %wheel.
Defaults:root,%wheel env_keep+=TERMINFO_DIRS
+1
View File
@@ -700,6 +700,7 @@ in {
environment.profiles = [
"$HOME/.nix-profile"
"\${XDG_STATE_HOME:-$HOME/.local/state}/nix/profile"
"/etc/profiles/per-user/$USER"
];
+60 -31
View File
@@ -1,37 +1,43 @@
{ config, lib, ... }:
{ config, options, lib, ... }:
with lib;
let
cfg = config.hardware.cpu.amd.sev;
defaultGroup = "sev";
in
with lib; {
options.hardware.cpu.amd.sev = {
enable = mkEnableOption (lib.mdDoc "access to the AMD SEV device");
user = mkOption {
description = lib.mdDoc "Owner to assign to the SEV device.";
type = types.str;
default = "root";
};
group = mkOption {
description = lib.mdDoc "Group to assign to the SEV device.";
type = types.str;
default = defaultGroup;
};
mode = mkOption {
description = lib.mdDoc "Mode to set for the SEV device.";
type = types.str;
default = "0660";
};
};
cfgSev = config.hardware.cpu.amd.sev;
cfgSevGuest = config.hardware.cpu.amd.sevGuest;
config = mkIf cfg.enable {
optionsFor = device: group: {
enable = mkEnableOption (lib.mdDoc "access to the AMD ${device} device");
user = mkOption {
description = lib.mdDoc "Owner to assign to the ${device} device.";
type = types.str;
default = "root";
};
group = mkOption {
description = lib.mdDoc "Group to assign to the ${device} device.";
type = types.str;
default = group;
};
mode = mkOption {
description = lib.mdDoc "Mode to set for the ${device} device.";
type = types.str;
default = "0660";
};
};
in
with lib; {
options.hardware.cpu.amd.sev = optionsFor "SEV" "sev";
options.hardware.cpu.amd.sevGuest = optionsFor "SEV guest" "sev-guest";
config = mkMerge [
# /dev/sev
(mkIf cfgSev.enable {
assertions = [
{
assertion = hasAttr cfg.user config.users.users;
assertion = hasAttr cfgSev.user config.users.users;
message = "Given user does not exist";
}
{
assertion = (cfg.group == defaultGroup) || (hasAttr cfg.group config.users.groups);
assertion = (cfgSev.group == options.hardware.cpu.amd.sev.group.default) || (hasAttr cfgSev.group config.users.groups);
message = "Given group does not exist";
}
];
@@ -40,12 +46,35 @@ in
options kvm_amd sev=1
'';
users.groups = optionalAttrs (cfg.group == defaultGroup) {
"${cfg.group}" = {};
users.groups = optionalAttrs (cfgSev.group == options.hardware.cpu.amd.sev.group.default) {
"${cfgSev.group}" = { };
};
services.udev.extraRules = with cfg; ''
services.udev.extraRules = with cfgSev; ''
KERNEL=="sev", OWNER="${user}", GROUP="${group}", MODE="${mode}"
'';
};
}
})
# /dev/sev-guest
(mkIf cfgSevGuest.enable {
assertions = [
{
assertion = hasAttr cfgSevGuest.user config.users.users;
message = "Given user does not exist";
}
{
assertion = (cfgSevGuest.group == options.hardware.cpu.amd.sevGuest.group.default) || (hasAttr cfgSevGuest.group config.users.groups);
message = "Given group does not exist";
}
];
users.groups = optionalAttrs (cfgSevGuest.group == options.hardware.cpu.amd.sevGuest.group.default) {
"${cfgSevGuest.group}" = { };
};
services.udev.extraRules = with cfgSevGuest; ''
KERNEL=="sev-guest", OWNER="${user}", GROUP="${group}", MODE="${mode}"
'';
})
];
}
+23
View File
@@ -0,0 +1,23 @@
{ config, lib, pkgs, ... }:
let
cfg = config.hardware.glasgow;
in
{
options.hardware.glasgow = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc ''
Enables Glasgow udev rules and ensures 'plugdev' group exists.
This is a prerequisite to using Glasgow without being root.
'';
};
};
config = lib.mkIf cfg.enable {
services.udev.packages = [ pkgs.glasgow ];
users.groups.plugdev = { };
};
}
+2 -2
View File
@@ -134,8 +134,8 @@ in
system.nixos-generate-config.configuration = mkDefault ''
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running `nixos-help`).
# your system. Help is available in the configuration.nix(5) man page, on
# https://search.nixos.org/options and in the NixOS manual (`nixos-help`).
{ config, lib, pkgs, ... }:
+4
View File
@@ -61,6 +61,7 @@
./hardware/flipperzero.nix
./hardware/flirc.nix
./hardware/gkraken.nix
./hardware/glasgow.nix
./hardware/gpgsmartcards.nix
./hardware/hackrf.nix
./hardware/i2c.nix
@@ -310,6 +311,7 @@
./security/rngd.nix
./security/rtkit.nix
./security/sudo.nix
./security/sudo-rs.nix
./security/systemd-confinement.nix
./security/tpm2.nix
./security/wrappers/default.nix
@@ -497,6 +499,7 @@
./services/games/quake3-server.nix
./services/games/teeworlds.nix
./services/games/terraria.nix
./services/games/xonotic.nix
./services/hardware/acpid.nix
./services/hardware/actkbd.nix
./services/hardware/argonone.nix
@@ -1483,6 +1486,7 @@
./virtualisation/nixos-containers.nix
./virtualisation/oci-containers.nix
./virtualisation/openstack-options.nix
./virtualisation/oci-options.nix
./virtualisation/openvswitch.nix
./virtualisation/parallels-guest.nix
./virtualisation/podman/default.nix
-2
View File
@@ -37,8 +37,6 @@ in
environment.profileRelativeSessionVariables =
{ PATH = [ "/bin" ];
INFOPATH = [ "/info" "/share/info" ];
KDEDIRS = [ "" ];
QT_PLUGIN_PATH = [ "/lib/qt4/plugins" "/lib/kde4/plugins" ];
QTWEBKIT_PLUGIN_PATH = [ "/lib/mozilla/plugins/" ];
GTK_PATH = [ "/lib/gtk-2.0" "/lib/gtk-3.0" "/lib/gtk-4.0" ];
XDG_CONFIG_DIRS = [ "/etc/xdg" ];
+7 -10
View File
@@ -258,16 +258,13 @@ in
preferLocalBuild = true;
allowSubstitutes = false;
};
generateCompletions = package: pkgs.runCommand
"${package.name}_fish-completions"
(
{
inherit package;
preferLocalBuild = true;
allowSubstitutes = false;
}
// optionalAttrs (package ? meta.priority) { meta.priority = package.meta.priority; }
)
generateCompletions = package: pkgs.runCommandLocal
( with lib.strings; let
storeLength = stringLength storeDir + 34; # Nix' StorePath::HashLen + 2 for the separating slash and dash
pathName = substring storeLength (stringLength package - storeLength) package;
in (package.name or pathName) + "_fish-completions")
( { inherit package; } //
optionalAttrs (package ? meta.priority) { meta.priority = package.meta.priority; })
''
mkdir -p $out
if [ -d $package/share/man ]; then
+296
View File
@@ -0,0 +1,296 @@
{ config, lib, pkgs, ... }:
with lib;
let
inherit (pkgs) sudo sudo-rs;
cfg = config.security.sudo-rs;
enableSSHAgentAuth =
with config.security;
pam.enableSSHAgentAuth && pam.sudo.sshAgentAuth;
usingMillersSudo = cfg.package.pname == sudo.pname;
usingSudoRs = cfg.package.pname == sudo-rs.pname;
toUserString = user: if (isInt user) then "#${toString user}" else "${user}";
toGroupString = group: if (isInt group) then "%#${toString group}" else "%${group}";
toCommandOptionsString = options:
"${concatStringsSep ":" options}${optionalString (length options != 0) ":"} ";
toCommandsString = commands:
concatStringsSep ", " (
map (command:
if (isString command) then
command
else
"${toCommandOptionsString command.options}${command.command}"
) commands
);
in
{
###### interface
options.security.sudo-rs = {
defaultOptions = mkOption {
type = with types; listOf str;
default = optional usingMillersSudo "SETENV";
defaultText = literalMD ''
`[ "SETENV" ]` if using the default `sudo` implementation
'';
description = mdDoc ''
Options used for the default rules, granting `root` and the
`wheel` group permission to run any command as any user.
'';
};
enable = mkOption {
type = types.bool;
default = false;
description = mdDoc ''
Whether to enable the {command}`sudo` command, which
allows non-root users to execute commands as root.
'';
};
package = mkOption {
type = types.package;
default = pkgs.sudo-rs;
defaultText = literalExpression "pkgs.sudo-rs";
description = mdDoc ''
Which package to use for `sudo`.
'';
};
wheelNeedsPassword = mkOption {
type = types.bool;
default = true;
description = mdDoc ''
Whether users of the `wheel` group must
provide a password to run commands as super user via {command}`sudo`.
'';
};
execWheelOnly = mkOption {
type = types.bool;
default = false;
description = mdDoc ''
Only allow members of the `wheel` group to execute sudo by
setting the executable's permissions accordingly.
This prevents users that are not members of `wheel` from
exploiting vulnerabilities in sudo such as CVE-2021-3156.
'';
};
configFile = mkOption {
type = types.lines;
# Note: if syntax errors are detected in this file, the NixOS
# configuration will fail to build.
description = mdDoc ''
This string contains the contents of the
{file}`sudoers` file.
'';
};
extraRules = mkOption {
description = mdDoc ''
Define specific rules to be in the {file}`sudoers` file.
More specific rules should come after more general ones in order to
yield the expected behavior. You can use mkBefore/mkAfter to ensure
this is the case when configuration options are merged.
'';
default = [];
example = literalExpression ''
[
# 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 {
type = with types; listOf (either str int);
description = mdDoc ''
The usernames / UIDs this rule should apply for.
'';
default = [];
};
groups = mkOption {
type = with types; listOf (either str int);
description = mdDoc ''
The groups / GIDs this rule should apply for.
'';
default = [];
};
host = mkOption {
type = types.str;
default = "ALL";
description = mdDoc ''
For what host this rule should apply.
'';
};
runAs = mkOption {
type = with types; str;
default = "ALL:ALL";
description = mdDoc ''
Under which user/group the specified command is allowed to run.
A user can be specified using just the username: `"foo"`.
It is also possible to specify a user/group combination using `"foo:bar"`
or to only allow running as a specific group with `":bar"`.
'';
};
commands = mkOption {
description = mdDoc ''
The commands for which the rule should apply.
'';
type = with types; listOf (either str (submodule {
options = {
command = mkOption {
type = with types; str;
description = mdDoc ''
A command being either just a path to a binary to allow any arguments,
the full command with arguments pre-set or with `""` used as the argument,
not allowing arguments to the command at all.
'';
};
options = mkOption {
type = with types; listOf (enum [ "NOPASSWD" "PASSWD" "NOEXEC" "EXEC" "SETENV" "NOSETENV" "LOG_INPUT" "NOLOG_INPUT" "LOG_OUTPUT" "NOLOG_OUTPUT" ]);
description = mdDoc ''
Options for running the command. Refer to the [sudo manual](https://www.sudo.ws/man/1.7.10/sudoers.man.html).
'';
default = [];
};
};
}));
};
};
});
};
extraConfig = mkOption {
type = types.lines;
default = "";
description = mdDoc ''
Extra configuration text appended to {file}`sudoers`.
'';
};
};
###### implementation
config = mkIf cfg.enable {
security.sudo-rs.extraRules =
let
defaultRule = { users ? [], groups ? [], opts ? [] }: [ {
inherit users groups;
commands = [ {
command = "ALL";
options = opts ++ cfg.defaultOptions;
} ];
} ];
in mkMerge [
# This is ordered before users' `mkBefore` rules,
# so as not to introduce unexpected changes.
(mkOrder 400 (defaultRule { users = [ "root" ]; }))
# This is ordered to show before (most) other rules, but
# late-enough for a user to `mkBefore` it.
(mkOrder 600 (defaultRule {
groups = [ "wheel" ];
opts = (optional (!cfg.wheelNeedsPassword) "NOPASSWD");
}))
];
security.sudo-rs.configFile = concatStringsSep "\n" (filter (s: s != "") [
''
# Don't edit this file. Set the NixOS options security.sudo-rs.configFile
# or security.sudo-rs.extraRules instead.
''
(optionalString enableSSHAgentAuth ''
# Keep SSH_AUTH_SOCK so that pam_ssh_agent_auth.so can do its magic.
Defaults env_keep+=SSH_AUTH_SOCK
'')
(concatStringsSep "\n" (
lists.flatten (
map (
rule: optionals (length rule.commands != 0) [
(map (user: "${toUserString user} ${rule.host}=(${rule.runAs}) ${toCommandsString rule.commands}") rule.users)
(map (group: "${toGroupString group} ${rule.host}=(${rule.runAs}) ${toCommandsString rule.commands}") rule.groups)
]
) cfg.extraRules
)
) + "\n")
(optionalString (cfg.extraConfig != "") ''
# extraConfig
${cfg.extraConfig}
'')
]);
security.wrappers = let
owner = "root";
group = if cfg.execWheelOnly then "wheel" else "root";
setuid = true;
permissions = if cfg.execWheelOnly then "u+rx,g+x" else "u+rx,g+x,o+x";
in {
sudo = {
source = "${cfg.package.out}/bin/sudo";
inherit owner group setuid permissions;
};
# sudo-rs does not yet ship a sudoedit (as of v0.2.0)
sudoedit = mkIf usingMillersSudo {
source = "${cfg.package.out}/bin/sudoedit";
inherit owner group setuid permissions;
};
};
environment.systemPackages = [ sudo ];
security.pam.services.sudo = { sshAgentAuth = true; usshAuth = true; };
security.pam.services.sudo-i = mkIf usingSudoRs
{ sshAgentAuth = true; usshAuth = true; };
environment.etc.sudoers =
{ source =
pkgs.runCommand "sudoers"
{
src = pkgs.writeText "sudoers-in" cfg.configFile;
preferLocalBuild = true;
}
"${pkgs.buildPackages."${cfg.package.pname}"}/bin/visudo -f $src -c && cp $src $out";
mode = "0440";
};
};
meta.maintainers = [ lib.maintainers.nicoo ];
}
+198
View File
@@ -0,0 +1,198 @@
{ config
, pkgs
, lib
, ...
}:
let
cfg = config.services.xonotic;
serverCfg = pkgs.writeText "xonotic-server.cfg" (
toString cfg.prependConfig
+ "\n"
+ builtins.concatStringsSep "\n" (
lib.mapAttrsToList (key: option:
let
escape = s: lib.escape [ "\"" ] s;
quote = s: "\"${s}\"";
toValue = x: quote (escape (toString x));
value = (if lib.isList option then
builtins.concatStringsSep
" "
(builtins.map (x: toValue x) option)
else
toValue option
);
in
"${key} ${value}"
) cfg.settings
)
+ "\n"
+ toString cfg.appendConfig
);
in
{
options.services.xonotic = {
enable = lib.mkEnableOption (lib.mdDoc "Xonotic dedicated server");
package = lib.mkPackageOption pkgs "xonotic-dedicated" {};
openFirewall = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc ''
Open the firewall for TCP and UDP on the specified port.
'';
};
dataDir = lib.mkOption {
type = lib.types.path;
readOnly = true;
default = "/var/lib/xonotic";
description = lib.mdDoc ''
Data directory.
'';
};
settings = lib.mkOption {
description = lib.mdDoc ''
Generates the `server.cfg` file. Refer to [upstream's example][0] for
details.
[0]: https://gitlab.com/xonotic/xonotic/-/blob/master/server/server.cfg
'';
default = {};
type = lib.types.submodule {
freeformType = with lib.types; let
scalars = oneOf [ singleLineStr int float ];
in
attrsOf (oneOf [ scalars (nonEmptyListOf scalars) ]);
options.sv_public = lib.mkOption {
type = lib.types.int;
default = 0;
example = [ (-1) 1 ];
description = lib.mdDoc ''
Controls whether the server will be publicly listed.
'';
};
options.hostname = lib.mkOption {
type = lib.types.singleLineStr;
default = "Xonotic $g_xonoticversion Server";
description = lib.mdDoc ''
The name that will appear in the server list. `$g_xonoticversion`
gets replaced with the current version.
'';
};
options.sv_motd = lib.mkOption {
type = lib.types.singleLineStr;
default = "";
description = lib.mdDoc ''
Text displayed when players join the server.
'';
};
options.sv_termsofservice_url = lib.mkOption {
type = lib.types.singleLineStr;
default = "";
description = lib.mdDoc ''
URL for the Terms of Service for playing on your server.
'';
};
options.maxplayers = lib.mkOption {
type = lib.types.int;
default = 16;
description = lib.mdDoc ''
Number of player slots on the server, including spectators.
'';
};
options.net_address = lib.mkOption {
type = lib.types.singleLineStr;
default = "0.0.0.0";
description = lib.mdDoc ''
The address Xonotic will listen on.
'';
};
options.port = lib.mkOption {
type = lib.types.port;
default = 26000;
description = lib.mdDoc ''
The port Xonotic will listen on.
'';
};
};
};
# Still useful even though we're using RFC 42 settings because *some* keys
# can be repeated.
appendConfig = lib.mkOption {
type = with lib.types; nullOr lines;
default = null;
description = lib.mdDoc ''
Literal text to insert at the end of `server.cfg`.
'';
};
# Certain changes need to happen at the beginning of the file.
prependConfig = lib.mkOption {
type = with lib.types; nullOr lines;
default = null;
description = lib.mdDoc ''
Literal text to insert at the start of `server.cfg`.
'';
};
};
config = lib.mkIf cfg.enable {
systemd.services.xonotic = {
description = "Xonotic server";
wantedBy = [ "multi-user.target" ];
environment = {
# Required or else it tries to write the lock file into the nix store
HOME = cfg.dataDir;
};
serviceConfig = {
DynamicUser = true;
User = "xonotic";
StateDirectory = "xonotic";
ExecStart = "${cfg.package}/bin/xonotic-dedicated";
# Symlink the configuration from the nix store to where Xonotic actually
# looks for it
ExecStartPre = [
"${pkgs.coreutils}/bin/mkdir -p ${cfg.dataDir}/.xonotic/data"
''
${pkgs.coreutils}/bin/ln -sf ${serverCfg} \
${cfg.dataDir}/.xonotic/data/server.cfg
''
];
# Cargo-culted from search results about writing Xonotic systemd units
ExecReload = "${pkgs.util-linux}/bin/kill -HUP $MAINPID";
Restart = "on-failure";
RestartSec = 10;
StartLimitBurst = 5;
};
};
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [
cfg.settings.port
];
networking.firewall.allowedUDPPorts = lib.mkIf cfg.openFirewall [
cfg.settings.port
];
};
meta.maintainers = with lib.maintainers; [ CobaltCause ];
}
@@ -1,25 +0,0 @@
version: 1
# In systemd's journal, loglevel is implicitly stored, so let's omit it
# from the message text.
formatters:
journal_fmt:
format: '%(name)s: [%(request)s] %(message)s'
filters:
context:
(): synapse.util.logcontext.LoggingContextFilter
request: ""
handlers:
journal:
class: systemd.journal.JournalHandler
formatter: journal_fmt
filters: [context]
SYSLOG_IDENTIFIER: synapse
root:
level: INFO
handlers: [journal]
disable_existing_loggers: False
+466 -189
View File
@@ -4,7 +4,7 @@ with lib;
let
cfg = config.services.matrix-synapse;
format = pkgs.formats.yaml {};
format = pkgs.formats.yaml { };
# remove null values from the final configuration
finalSettings = lib.filterAttrsRecursive (_: v: v != null) cfg.settings;
@@ -13,27 +13,28 @@ let
usePostgresql = cfg.settings.database.name == "psycopg2";
hasLocalPostgresDB = let args = cfg.settings.database.args; in
usePostgresql && (!(args ? host) || (elem args.host [ "localhost" "127.0.0.1" "::1" ]));
hasWorkers = cfg.workers != { };
listenerSupportsResource = resource: listener:
lib.any ({ names, ... }: builtins.elem resource names) listener.resources;
clientListener = findFirst
(listenerSupportsResource "client")
null
(cfg.settings.listeners
++ concatMap ({ worker_listeners, ... }: worker_listeners) (attrValues cfg.workers));
registerNewMatrixUser =
let
isIpv6 = x: lib.length (lib.splitString ":" x) > 1;
listener =
lib.findFirst (
listener: lib.any (
resource: lib.any (
name: name == "client"
) resource.names
) listener.resources
) (lib.last cfg.settings.listeners) cfg.settings.listeners;
# FIXME: Handle cases with missing client listener properly,
# don't rely on lib.last, this will not work.
isIpv6 = hasInfix ":";
# add a tail, so that without any bind_addresses we still have a useable address
bindAddress = head (listener.bind_addresses ++ [ "127.0.0.1" ]);
listenerProtocol = if listener.tls
bindAddress = head (clientListener.bind_addresses ++ [ "127.0.0.1" ]);
listenerProtocol = if clientListener.tls
then "https"
else "http";
in
assert assertMsg (clientListener != null) "No client listener found in synapse or one of its workers";
pkgs.writeShellScriptBin "matrix-synapse-register_new_matrix_user" ''
exec ${cfg.package}/bin/register_new_matrix_user \
$@ \
@@ -43,7 +44,7 @@ let
"[${bindAddress}]"
else
"${bindAddress}"
}:${builtins.toString listener.port}/"
}:${builtins.toString clientListener.port}/"
'';
defaultExtras = [
@@ -68,6 +69,48 @@ let
extras = wantedExtras;
inherit (cfg) plugins;
};
logConfig = logName: {
version = 1;
formatters.journal_fmt.format = "%(name)s: [%(request)s] %(message)s";
handlers.journal = {
class = "systemd.journal.JournalHandler";
formatter = "journal_fmt";
SYSLOG_IDENTIFIER = logName;
};
root = {
level = "INFO";
handlers = [ "journal" ];
};
disable_existing_loggers = false;
};
logConfigText = logName:
let
expr = ''
{
version = 1;
formatters.journal_fmt.format = "%(name)s: [%(request)s] %(message)s";
handlers.journal = {
class = "systemd.journal.JournalHandler";
formatter = "journal_fmt";
SYSLOG_IDENTIFIER = "${logName}";
};
root = {
level = "INFO";
handlers = [ "journal" ];
};
disable_existing_loggers = false;
};
'';
in
lib.literalMD ''
Path to a yaml file generated from this Nix expression:
```
${expr}
```
'';
genLogConfigFile = logName: format.generate "synapse-log-${logName}.yaml" (logConfig logName);
in {
imports = [
@@ -154,7 +197,108 @@ in {
];
options = {
options = let
listenerType = workerContext: types.submodule {
options = {
port = mkOption {
type = types.port;
example = 8448;
description = lib.mdDoc ''
The port to listen for HTTP(S) requests on.
'';
};
bind_addresses = mkOption {
type = types.listOf types.str;
default = [
"::1"
"127.0.0.1"
];
example = literalExpression ''
[
"::"
"0.0.0.0"
]
'';
description = lib.mdDoc ''
IP addresses to bind the listener to.
'';
};
type = mkOption {
type = types.enum [
"http"
"manhole"
"metrics"
"replication"
];
default = "http";
example = "metrics";
description = lib.mdDoc ''
The type of the listener, usually http.
'';
};
tls = mkOption {
type = types.bool;
default = !workerContext;
example = false;
description = lib.mdDoc ''
Whether to enable TLS on the listener socket.
'';
};
x_forwarded = mkOption {
type = types.bool;
default = false;
example = true;
description = lib.mdDoc ''
Use the X-Forwarded-For (XFF) header as the client IP and not the
actual client IP.
'';
};
resources = mkOption {
type = types.listOf (types.submodule {
options = {
names = mkOption {
type = types.listOf (types.enum [
"client"
"consent"
"federation"
"health"
"keys"
"media"
"metrics"
"openid"
"replication"
"static"
]);
description = lib.mdDoc ''
List of resources to host on this listener.
'';
example = [
"client"
];
};
compress = mkOption {
default = false;
type = types.bool;
description = lib.mdDoc ''
Whether synapse should compress HTTP responses to clients that support it.
This should be disabled if running synapse behind a load balancer
that can do automatic compression.
'';
};
};
});
description = lib.mdDoc ''
List of HTTP resources to serve on this listener.
'';
};
};
};
in {
services.matrix-synapse = {
enable = mkEnableOption (lib.mdDoc "matrix.org synapse");
@@ -251,7 +395,7 @@ in {
};
settings = mkOption {
default = {};
default = { };
description = mdDoc ''
The primary synapse configuration. See the
[sample configuration](https://github.com/matrix-org/synapse/blob/v${pkgs.matrix-synapse-unwrapped.version}/docs/sample_config.yaml)
@@ -346,8 +490,8 @@ in {
log_config = mkOption {
type = types.path;
default = ./synapse-log_config.yaml;
defaultText = lib.literalExpression "nixos/modules/services/matrix/synapse-log_config.yaml";
default = genLogConfigFile "synapse";
defaultText = logConfigText "synapse";
description = lib.mdDoc ''
The file that holds the logging configuration.
'';
@@ -409,120 +553,37 @@ in {
};
listeners = mkOption {
type = types.listOf (types.submodule {
options = {
port = mkOption {
type = types.port;
example = 8448;
description = lib.mdDoc ''
The port to listen for HTTP(S) requests on.
'';
};
bind_addresses = mkOption {
type = types.listOf types.str;
default = [
"::1"
"127.0.0.1"
];
example = literalExpression ''
[
"::"
"0.0.0.0"
]
'';
description = lib.mdDoc ''
IP addresses to bind the listener to.
'';
};
type = mkOption {
type = types.enum [
"http"
"manhole"
"metrics"
"replication"
];
default = "http";
example = "metrics";
description = lib.mdDoc ''
The type of the listener, usually http.
'';
};
tls = mkOption {
type = types.bool;
default = true;
example = false;
description = lib.mdDoc ''
Whether to enable TLS on the listener socket.
'';
};
x_forwarded = mkOption {
type = types.bool;
default = false;
example = true;
description = lib.mdDoc ''
Use the X-Forwarded-For (XFF) header as the client IP and not the
actual client IP.
'';
};
resources = mkOption {
type = types.listOf (types.submodule {
options = {
names = mkOption {
type = types.listOf (types.enum [
"client"
"consent"
"federation"
"keys"
"media"
"metrics"
"openid"
"replication"
"static"
]);
description = lib.mdDoc ''
List of resources to host on this listener.
'';
example = [
"client"
];
};
compress = mkOption {
type = types.bool;
description = lib.mdDoc ''
Should synapse compress HTTP responses to clients that support it?
This should be disabled if running synapse behind a load balancer
that can do automatic compression.
'';
};
};
});
description = lib.mdDoc ''
List of HTTP resources to serve on this listener.
'';
};
};
});
default = [ {
type = types.listOf (listenerType false);
default = [{
port = 8008;
bind_addresses = [ "127.0.0.1" ];
type = "http";
tls = false;
x_forwarded = true;
resources = [ {
resources = [{
names = [ "client" ];
compress = true;
} {
names = [ "federation" ];
compress = false;
} ];
} ];
}];
}] ++ lib.optional hasWorkers {
port = 9093;
bind_addresses = [ "127.0.0.1" ];
type = "http";
tls = false;
x_forwarded = false;
resources = [{
names = [ "replication" ];
compress = false;
}];
};
description = lib.mdDoc ''
List of ports that Synapse should listen on, their purpose and their configuration.
By default, synapse will be configured for client and federation traffic on port 8008, and
for worker replication traffic on port 9093. See [`services.matrix-synapse.workers`](#opt-services.matrix-synapse.workers)
for more details.
'';
};
@@ -534,7 +595,7 @@ in {
default = if versionAtLeast config.system.stateVersion "18.03"
then "psycopg2"
else "sqlite3";
defaultText = literalExpression ''
defaultText = literalExpression ''
if versionAtLeast config.system.stateVersion "18.03"
then "psycopg2"
else "sqlite3"
@@ -551,10 +612,10 @@ in {
psycopg2 = "matrix-synapse";
}.${cfg.settings.database.name};
defaultText = literalExpression ''
{
sqlite3 = "''${${options.services.matrix-synapse.dataDir}}/homeserver.db";
psycopg2 = "matrix-synapse";
}.''${${options.services.matrix-synapse.settings}.database.name};
{
sqlite3 = "''${${options.services.matrix-synapse.dataDir}}/homeserver.db";
psycopg2 = "matrix-synapse";
}.''${${options.services.matrix-synapse.settings}.database.name};
'';
description = lib.mdDoc ''
Name of the database when using the psycopg2 backend,
@@ -622,7 +683,7 @@ in {
url_preview_ip_range_whitelist = mkOption {
type = types.listOf types.str;
default = [];
default = [ ];
description = lib.mdDoc ''
List of IP address CIDR ranges that the URL preview spider is allowed
to access even if they are specified in url_preview_ip_range_blacklist.
@@ -644,7 +705,7 @@ in {
on how to configure it properly.
''))
(types.attrsOf types.str));
default = [];
default = [ ];
example = literalExpression ''
[
{ scheme = "http"; } # no http previews
@@ -690,7 +751,7 @@ in {
turn_uris = mkOption {
type = types.listOf types.str;
default = [];
default = [ ];
example = [
"turn:turn.example.com:3487?transport=udp"
"turn:turn.example.com:3487?transport=tcp"
@@ -727,12 +788,12 @@ in {
};
};
});
default = [ {
default = [{
server_name = "matrix.org";
verify_keys = {
"ed25519:auto" = "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw";
};
} ];
}];
description = lib.mdDoc ''
The trusted servers to download signing keys from.
'';
@@ -746,13 +807,114 @@ in {
'';
};
redis = lib.mkOption {
type = types.submodule {
freeformType = format.type;
options = {
enabled = lib.mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
Whether to use redis support
'';
};
};
};
default = { };
description = lib.mdDoc ''
Redis configuration for synapse.
See the
[upstream documentation](https://github.com/matrix-org/synapse/blob/v${pkgs.matrix-synapse-unwrapped.version}/usage/configuration/config_documentation.md#redis)
for available options.
'';
};
};
};
};
workers = lib.mkOption {
default = { };
description = lib.mdDoc ''
Options for configuring workers. Worker support will be enabled if at least one worker is configured here.
See the [worker documention](https://matrix-org.github.io/synapse/latest/workers.html#worker-configuration)
for possible options for each worker. Worker-specific options overriding the shared homeserver configuration can be
specified here for each worker.
::: {.note}
Worker support will add a replication listener on port 9093 to the main synapse process using the default
value of [`services.matrix-synapse.settings.listeners`](#opt-services.matrix-synapse.settings.listeners) and configure that
listener as `services.matrix-synapse.settings.instance_map.main`.
If you set either of those options, make sure to configure a replication listener yourself.
A redis server is required for running workers. A local one can be enabled
using [`services.matrix-synapse.configureRedisLocally`](#opt-services.matrix-synapse.configureRedisLocally).
Workers also require a proper reverse proxy setup to direct incoming requests to the appropriate process. See
the [reverse proxy documentation](https://matrix-org.github.io/synapse/latest/reverse_proxy.html) for a
general reverse proxying setup and
the [worker documentation](https://matrix-org.github.io/synapse/latest/workers.html#available-worker-applications)
for the available endpoints per worker application.
:::
'';
type = types.attrsOf (types.submodule ({name, ...}: {
freeformType = format.type;
options = {
worker_app = lib.mkOption {
type = types.enum [
"synapse.app.generic_worker"
"synapse.app.media_repository"
];
description = "Type of this worker";
default = "synapse.app.generic_worker";
};
worker_listeners = lib.mkOption {
default = [ ];
type = types.listOf (listenerType true);
description = lib.mdDoc ''
List of ports that this worker should listen on, their purpose and their configuration.
'';
};
worker_log_config = lib.mkOption {
type = types.path;
default = genLogConfigFile "synapse-${name}";
defaultText = logConfigText "synapse-${name}";
description = lib.mdDoc ''
The file for log configuration.
See the [python documentation](https://docs.python.org/3/library/logging.config.html#configuration-dictionary-schema)
for the schema and the [upstream repository](https://github.com/matrix-org/synapse/blob/v${pkgs.matrix-synapse-unwrapped.version}/docs/sample_log_config.yaml)
for an example.
'';
};
};
}));
default = { };
example = lib.literalExpression ''
{
"federation_sender" = { };
"federation_receiver" = {
worker_listeners = [
{
type = "http";
port = 8009;
bind_addresses = [ "127.0.0.1" ];
tls = false;
x_forwarded = true;
resources = [{
names = [ "federation" ];
}];
}
];
};
}
'';
};
extraConfigFiles = mkOption {
type = types.listOf types.path;
default = [];
default = [ ];
description = lib.mdDoc ''
Extra config files to include.
@@ -762,12 +924,28 @@ in {
NixOps is in use.
'';
};
configureRedisLocally = lib.mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
Whether to automatically configure a local redis server for matrix-synapse.
'';
};
};
};
config = mkIf cfg.enable {
assertions = [
{ assertion = hasLocalPostgresDB -> config.services.postgresql.enable;
{
assertion = clientListener != null;
message = ''
At least one listener which serves the `client` resource via HTTP is required
by synapse in `services.matrix-synapse.settings.listeners` or in one of the workers!
'';
}
{
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
@@ -783,8 +961,47 @@ in {
For further information about this update, please read the release-notes of 20.03 carefully.
'';
}
{
assertion = hasWorkers -> cfg.settings.redis.enabled;
message = ''
Workers for matrix-synapse require configuring a redis instance. This can be done
automatically by setting `services.matrix-synapse.configureRedisLocally = true`.
'';
}
{
assertion =
let
main = cfg.settings.instance_map.main;
listener = lib.findFirst
(
listener:
listener.port == main.port
&& listenerSupportsResource "replication" listener
&& (lib.any (bind: bind == main.host || bind == "0.0.0.0" || bind == "::") listener.bind_addresses)
)
null
cfg.settings.listeners;
in
hasWorkers -> (cfg.settings.instance_map ? main && listener != null);
message = ''
Workers for matrix-synapse require setting `services.matrix-synapse.settings.instance_map.main`
to any listener configured in `services.matrix-synapse.settings.listeners` with a `"replication"`
resource.
This is done by default unless you manually configure either of those settings.
'';
}
];
services.matrix-synapse.settings.redis = lib.mkIf cfg.configureRedisLocally {
enabled = true;
path = config.services.redis.servers.matrix-synapse.unixSocket;
};
services.matrix-synapse.settings.instance_map.main = lib.mkIf hasWorkers (lib.mkDefault {
host = "127.0.0.1";
port = 9093;
});
services.matrix-synapse.configFile = configFile;
services.matrix-synapse.package = wrapped;
@@ -803,64 +1020,124 @@ in {
gid = config.ids.gids.matrix-synapse;
};
systemd.services.matrix-synapse = {
description = "Synapse Matrix homeserver";
after = [ "network.target" ] ++ optional hasLocalPostgresDB "postgresql.service";
systemd.targets.matrix-synapse = lib.mkIf hasWorkers {
description = "Synapse Matrix parent target";
after = [ "network-online.target" ] ++ optional hasLocalPostgresDB "postgresql.service";
wantedBy = [ "multi-user.target" ];
preStart = ''
${cfg.package}/bin/synapse_homeserver \
--config-path ${configFile} \
--keys-directory ${cfg.dataDir} \
--generate-keys
'';
environment = optionalAttrs (cfg.withJemalloc) {
LD_PRELOAD = "${pkgs.jemalloc}/lib/libjemalloc.so";
};
serviceConfig = {
Type = "notify";
User = "matrix-synapse";
Group = "matrix-synapse";
WorkingDirectory = cfg.dataDir;
ExecStartPre = [ ("+" + (pkgs.writeShellScript "matrix-synapse-fix-permissions" ''
chown matrix-synapse:matrix-synapse ${cfg.settings.signing_key_path}
chmod 0600 ${cfg.settings.signing_key_path}
'')) ];
ExecStart = ''
${cfg.package}/bin/synapse_homeserver \
${ concatMapStringsSep "\n " (x: "--config-path ${x} \\") ([ configFile ] ++ cfg.extraConfigFiles) }
--keys-directory ${cfg.dataDir}
'';
ExecReload = "${pkgs.util-linux}/bin/kill -HUP $MAINPID";
Restart = "on-failure";
UMask = "0077";
};
# Security Hardening
# Refer to systemd.exec(5) for option descriptions.
CapabilityBoundingSet = [ "" ];
LockPersonality = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
PrivateUsers = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
ReadWritePaths = [ cfg.dataDir ];
RemoveIPC = true;
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@resources" "~@privileged" ];
};
systemd.services =
let
targetConfig =
if hasWorkers
then {
partOf = [ "matrix-synapse.target" ];
wantedBy = [ "matrix-synapse.target" ];
unitConfig.ReloadPropagatedFrom = "matrix-synapse.target";
}
else {
after = [ "network-online.target" ] ++ optional hasLocalPostgresDB "postgresql.service";
wantedBy = [ "multi-user.target" ];
};
baseServiceConfig = {
environment = optionalAttrs (cfg.withJemalloc) {
LD_PRELOAD = "${pkgs.jemalloc}/lib/libjemalloc.so";
};
serviceConfig = {
Type = "notify";
User = "matrix-synapse";
Group = "matrix-synapse";
WorkingDirectory = cfg.dataDir;
ExecReload = "${pkgs.util-linux}/bin/kill -HUP $MAINPID";
Restart = "on-failure";
UMask = "0077";
# Security Hardening
# Refer to systemd.exec(5) for option descriptions.
CapabilityBoundingSet = [ "" ];
LockPersonality = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
PrivateUsers = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
ReadWritePaths = [ cfg.dataDir ];
RemoveIPC = true;
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@resources" "~@privileged" ];
};
}
// targetConfig;
genWorkerService = name: workerCfg:
let
finalWorkerCfg = workerCfg // { worker_name = name; };
workerConfigFile = format.generate "worker-${name}.yaml" finalWorkerCfg;
in
{
name = "matrix-synapse-worker-${name}";
value = lib.mkMerge [
baseServiceConfig
{
description = "Synapse Matrix worker ${name}";
# make sure the main process starts first for potential database migrations
after = [ "matrix-synapse.service" ];
requires = [ "matrix-synapse.service" ];
serviceConfig = {
ExecStart = ''
${cfg.package}/bin/synapse_worker \
${ concatMapStringsSep "\n " (x: "--config-path ${x} \\") ([ configFile workerConfigFile ] ++ cfg.extraConfigFiles) }
--keys-directory ${cfg.dataDir}
'';
};
}
];
};
in
{
matrix-synapse = lib.mkMerge [
baseServiceConfig
{
description = "Synapse Matrix homeserver";
preStart = ''
${cfg.package}/bin/synapse_homeserver \
--config-path ${configFile} \
--keys-directory ${cfg.dataDir} \
--generate-keys
'';
serviceConfig = {
ExecStartPre = [
("+" + (pkgs.writeShellScript "matrix-synapse-fix-permissions" ''
chown matrix-synapse:matrix-synapse ${cfg.settings.signing_key_path}
chmod 0600 ${cfg.settings.signing_key_path}
''))
];
ExecStart = ''
${cfg.package}/bin/synapse_homeserver \
${ concatMapStringsSep "\n " (x: "--config-path ${x} \\") ([ configFile ] ++ cfg.extraConfigFiles) }
--keys-directory ${cfg.dataDir}
'';
};
}
];
}
// (lib.mapAttrs' genWorkerService cfg.workers);
services.redis.servers.matrix-synapse = lib.mkIf cfg.configureRedisLocally {
enable = true;
user = "matrix-synapse";
};
environment.systemPackages = [ registerNewMatrixUser ];
+8 -11
View File
@@ -26,7 +26,7 @@ in {
aggressive = mkOption {
type = types.bool;
default = false;
default = true;
description = lib.mdDoc "If true, favors higher default fan speeds.";
};
@@ -38,17 +38,20 @@ in {
options.general.low_temp = mkOption {
type = types.int;
default = 63;
default = (if cfg.aggressive then 55 else 63);
defaultText = literalExpression "55";
description = lib.mdDoc "If temperature is below this, fans will run at minimum speed.";
};
options.general.high_temp = mkOption {
type = types.int;
default = 66;
default = (if cfg.aggressive then 58 else 66);
defaultText = literalExpression "58";
description = lib.mdDoc "If temperature is above this, fan speed will gradually increase.";
};
options.general.max_temp = mkOption {
type = types.int;
default = 86;
default = (if cfg.aggressive then 78 else 86);
defaultText = literalExpression "78";
description = lib.mdDoc "If temperature is above this, fans will run at maximum speed.";
};
options.general.polling_interval = mkOption {
@@ -70,13 +73,6 @@ in {
];
config = mkIf cfg.enable {
services.mbpfan.settings = mkIf cfg.aggressive {
general.min_fan1_speed = mkDefault 2000;
general.low_temp = mkDefault 55;
general.high_temp = mkDefault 58;
general.max_temp = mkDefault 70;
};
boot.kernelModules = [ "coretemp" "applesmc" ];
environment.systemPackages = [ cfg.package ];
environment.etc."mbpfan.conf".source = settingsFile;
@@ -86,6 +82,7 @@ in {
wantedBy = [ "sysinit.target" ];
after = [ "syslog.target" "sysinit.target" ];
restartTriggers = [ config.environment.etc."mbpfan.conf".source ];
serviceConfig = {
Type = "simple";
ExecStart = "${cfg.package}/bin/mbpfan -f${verbose}";
@@ -68,6 +68,7 @@ let
"redis"
"rspamd"
"rtl_433"
"sabnzbd"
"scaphandre"
"script"
"shelly"
@@ -303,6 +304,14 @@ in
The exporter is configured to run as 'services.mysql.user', but
'services.mysql.enable' is set to false.
'';
} {
assertion = cfg.nextcloud.enable -> (
(cfg.nextcloud.passwordFile == null) != (cfg.nextcloud.tokenFile == null)
);
message = ''
Please specify either 'services.prometheus.exporters.nextcloud.passwordFile' or
'services.prometheus.exporters.nextcloud.tokenFile'
'';
} {
assertion = cfg.sql.enable -> (
(cfg.sql.configFile == null) != (cfg.sql.configuration == null)
@@ -21,7 +21,7 @@ let
throw
"${logPrefix}: configuration file must not reside within /tmp - it won't be visible to the systemd service."
else
true;
file;
checkConfig = file:
pkgs.runCommand "checked-blackbox-exporter.conf" {
preferLocalBuild = true;
@@ -23,10 +23,12 @@ in
description = lib.mdDoc ''
Username for connecting to Nextcloud.
Note that this account needs to have admin privileges in Nextcloud.
Unused when using token authentication.
'';
};
passwordFile = mkOption {
type = types.path;
type = types.nullOr types.path;
default = null;
example = "/path/to/password-file";
description = lib.mdDoc ''
File containing the password for connecting to Nextcloud.
@@ -34,9 +36,9 @@ in
'';
};
tokenFile = mkOption {
type = types.path;
type = types.nullOr types.path;
default = null;
example = "/path/to/token-file";
default = "";
description = lib.mdDoc ''
File containing the token for connecting to Nextcloud.
Make sure that this file is readable by the exporter user.
@@ -58,12 +60,13 @@ in
--addr ${cfg.listenAddress}:${toString cfg.port} \
--timeout ${cfg.timeout} \
--server ${cfg.url} \
${if cfg.tokenFile == "" then ''
${if cfg.passwordFile != null then ''
--username ${cfg.username} \
--password ${escapeShellArg "@${cfg.passwordFile}"} \
'' else ''
'' else ''
--auth-token ${escapeShellArg "@${cfg.tokenFile}"} \
''} ${concatStringsSep " \\\n " cfg.extraFlags}'';
''} \
${concatStringsSep " \\\n " cfg.extraFlags}'';
};
};
}
@@ -0,0 +1,47 @@
{ config, lib, pkgs, options }:
let
inherit (lib) mkOption types;
cfg = config.services.prometheus.exporters.sabnzbd;
in
{
port = 9387;
extraOpts = {
servers = mkOption {
description = "List of sabnzbd servers to connect to.";
type = types.listOf (types.submodule {
options = {
baseUrl = mkOption {
type = types.str;
description = "Base URL of the sabnzbd server.";
example = "http://localhost:8080/sabnzbd";
};
apiKeyFile = mkOption {
type = types.str;
description = "File containing the API key.";
example = "/run/secrets/sabnzbd_apikey";
};
};
});
};
};
serviceOpts =
let
servers = lib.zipAttrs cfg.servers;
apiKeys = lib.concatStringsSep "," (builtins.map (file: "$(cat ${file})") servers.apiKeyFile);
in
{
environment = {
METRICS_PORT = toString cfg.port;
METRICS_ADDR = cfg.listenAddress;
SABNZBD_BASEURLS = lib.concatStringsSep "," servers.baseUrl;
};
script = ''
export SABNZBD_APIKEYS="${apiKeys}"
exec ${lib.getExe pkgs.prometheus-sabnzbd-exporter}
'';
};
}
+2 -2
View File
@@ -31,8 +31,8 @@ in
default = { };
description = mdDoc ''
Frp configuration, for configuration options
see the example of [client](https://github.com/fatedier/frp/blob/dev/conf/frpc_full.ini)
or [server](https://github.com/fatedier/frp/blob/dev/conf/frps_full.ini) on github.
see the example of [client](https://github.com/fatedier/frp/blob/dev/conf/frpc_legacy_full.ini)
or [server](https://github.com/fatedier/frp/blob/dev/conf/frps_legacy_full.ini) on github.
'';
example = literalExpression ''
{
+125 -7
View File
@@ -5,10 +5,110 @@ with lib;
let
cfg = config.services.knot;
configFile = pkgs.writeTextFile {
yamlConfig = let
result = assert secsCheck; nix2yaml cfg.settings;
secAllow = n: hasPrefix "mod-" n || elem n [
"module"
"server" "xdp" "control"
"log"
"statistics" "database"
"keystore" "key" "remote" "remotes" "acl" "submission" "policy"
"template"
"zone"
"include"
];
secsCheck = let
secsBad = filter (n: !secAllow n) (attrNames cfg.settings);
in if secsBad == [] then true else throw
("services.knot.settings contains unknown sections: " + toString secsBad);
nix2yaml = nix_def: concatStrings (
# We output the config section in the upstream-mandated order.
# Ordering is important due to forward-references not being allowed.
# See definition of conf_export and 'const yp_item_t conf_schema'
# upstream for reference. Last updated for 3.3.
# When changing the set of sections, also update secAllow above.
[ (sec_list_fa "id" nix_def "module") ]
++ map (sec_plain nix_def)
[ "server" "xdp" "control" ]
++ [ (sec_list_fa "target" nix_def "log") ]
++ map (sec_plain nix_def)
[ "statistics" "database" ]
++ map (sec_list_fa "id" nix_def)
[ "keystore" "key" "remote" "remotes" "acl" "submission" "policy" ]
# Export module sections before the template section.
++ map (sec_list_fa "id" nix_def) (filter (hasPrefix "mod-") (attrNames nix_def))
++ [ (sec_list_fa "id" nix_def "template") ]
++ [ (sec_list_fa "domain" nix_def "zone") ]
++ [ (sec_plain nix_def "include") ]
);
# A plain section contains directly attributes (we don't really check that ATM).
sec_plain = nix_def: sec_name: if !hasAttr sec_name nix_def then "" else
n2y "" { ${sec_name} = nix_def.${sec_name}; };
# This section contains a list of attribute sets. In each of the sets
# there's an attribute (`fa_name`, typically "id") that must exist and come first.
# Alternatively we support using attribute sets instead of lists; example diff:
# -template = [ { id = "default"; /* other attributes */ } { id = "foo"; } ]
# +template = { default = { /* those attributes */ }; foo = { }; }
sec_list_fa = fa_name: nix_def: sec_name: if !hasAttr sec_name nix_def then "" else
let
elem2yaml = fa_val: other_attrs:
" - " + n2y "" { ${fa_name} = fa_val; }
+ " " + n2y " " other_attrs
+ "\n";
sec = nix_def.${sec_name};
in
sec_name + ":\n" +
(if isList sec
then flip concatMapStrings sec
(elem: elem2yaml elem.${fa_name} (removeAttrs elem [ fa_name ]))
else concatStrings (mapAttrsToList elem2yaml sec)
);
# This convertor doesn't care about ordering of attributes.
# TODO: it could probably be simplified even more, now that it's not
# to be used directly, but we might want some other tweaks, too.
n2y = indent: val:
if doRecurse val then concatStringsSep "\n${indent}"
(mapAttrsToList
# This is a bit wacky - set directly under a set would start on bad indent,
# so we start those on a new line, but not other types of attribute values.
(aname: aval: "${aname}:${if doRecurse aval then "\n${indent} " else " "}"
+ n2y (indent + " ") aval)
val
)
+ "\n"
else
/*
if isList val && stringLength indent < 4 then concatMapStrings
(elem: "\n${indent}- " + n2y (indent + " ") elem)
val
else
*/
if isList val /* and long indent */ then
"[ " + concatMapStringsSep ", " quoteString val + " ]" else
if isBool val then (if val then "on" else "off") else
quoteString val;
# We don't want paths like ./my-zone.txt be converted to plain strings.
quoteString = s: ''"${if builtins.typeOf s == "path" then s else toString s}"'';
# We don't want to walk the insides of derivation attributes.
doRecurse = val: isAttrs val && !isDerivation val;
in result;
configFile = if cfg.settingsFile != null then
assert cfg.settings == {} && cfg.keyFiles == [];
cfg.settingsFile
else pkgs.writeTextFile {
name = "knot.conf";
text = (concatMapStringsSep "\n" (file: "include: ${file}") cfg.keyFiles) + "\n" +
cfg.extraConfig;
text = (concatMapStringsSep "\n" (file: "include: ${file}") cfg.keyFiles) + "\n" + yamlConfig;
# TODO: maybe we could do some checks even when private keys complicate this?
checkPhase = lib.optionalString (cfg.keyFiles == []) ''
${cfg.package}/bin/knotc --config=$out conf-check
'';
@@ -60,11 +160,21 @@ in {
'';
};
extraConfig = mkOption {
type = types.lines;
default = "";
settings = mkOption {
type = types.attrs;
default = {};
description = lib.mdDoc ''
Extra lines to be added verbatim to knot.conf
Extra configuration as nix values.
'';
};
settingsFile = mkOption {
type = types.nullOr types.path;
default = null;
description = lib.mdDoc ''
As alternative to ``settings``, you can provide whole configuration
directly in the almost-YAML format of Knot DNS.
You might want to utilize ``writeTextFile`` for this.
'';
};
@@ -78,6 +188,12 @@ in {
};
};
};
imports = [
# Compatibility with NixOS 23.05. At least partial, as it fails assert if used with keyFiles.
(mkChangedOptionModule [ "services" "knot" "extraConfig" ] [ "services" "knot" "settingsFile" ]
(config: pkgs.writeText "knot.conf" config.services.knot.extraConfig)
)
];
config = mkIf config.services.knot.enable {
users.groups.knot = {};
@@ -87,6 +203,8 @@ in {
description = "Knot daemon user";
};
environment.etc."knot/knot.conf".source = configFile; # just for user's convenience
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;
@@ -2,32 +2,26 @@
let
inherit (lib)
maintainers types mkEnableOption mkOption mkIf
literalExpression escapeShellArg escapeShellArgs;
maintainers types literalExpression
escapeShellArg escapeShellArgs
mkEnableOption mkOption mkRemovedOptionModule mkIf mdDoc
optionalString concatMapStrings concatStringsSep;
cfg = config.services.mtr-exporter;
jobsConfig = pkgs.writeText "mtr-exporter.conf" (concatMapStrings (job: ''
${job.name} -- ${job.schedule} -- ${concatStringsSep " " job.flags} ${job.address}
'') cfg.jobs);
in {
imports = [
(mkRemovedOptionModule [ "services" "mtr-exporter" "target" ] "Use services.mtr-exporter.jobs instead.")
(mkRemovedOptionModule [ "services" "mtr-exporter" "mtrFlags" ] "Use services.mtr-exporter.jobs.<job>.flags instead.")
];
options = {
services = {
mtr-exporter = {
enable = mkEnableOption (lib.mdDoc "a Prometheus exporter for MTR");
target = mkOption {
type = types.str;
example = "example.org";
description = lib.mdDoc "Target to check using MTR.";
};
interval = mkOption {
type = types.int;
default = 60;
description = lib.mdDoc "Interval between MTR checks in seconds.";
};
port = mkOption {
type = types.port;
default = 8080;
description = lib.mdDoc "Listen port for MTR exporter.";
};
enable = mkEnableOption (mdDoc "a Prometheus exporter for MTR");
address = mkOption {
type = types.str;
@@ -35,30 +29,87 @@ in {
description = lib.mdDoc "Listen address for MTR exporter.";
};
mtrFlags = mkOption {
type = with types; listOf str;
port = mkOption {
type = types.port;
default = 8080;
description = mdDoc "Listen port for MTR exporter.";
};
extraFlags = mkOption {
type = types.listOf types.str;
default = [];
example = ["-G1"];
description = lib.mdDoc "Additional flags to pass to MTR.";
example = ["-flag.deprecatedMetrics"];
description = mdDoc ''
Extra command line options to pass to MTR exporter.
'';
};
package = mkOption {
type = types.package;
default = pkgs.mtr-exporter;
defaultText = literalExpression "pkgs.mtr-exporter";
description = mdDoc "The MTR exporter package to use.";
};
mtrPackage = mkOption {
type = types.package;
default = pkgs.mtr;
defaultText = literalExpression "pkgs.mtr";
description = mdDoc "The MTR package to use.";
};
jobs = mkOption {
description = mdDoc "List of MTR jobs. Will be added to /etc/mtr-exporter.conf";
type = types.nonEmptyListOf (types.submodule {
options = {
name = mkOption {
type = types.str;
description = mdDoc "Name of ICMP pinging job.";
};
address = mkOption {
type = types.str;
example = "host.example.org:1234";
description = mdDoc "Target address for MTR client.";
};
schedule = mkOption {
type = types.str;
default = "@every 60s";
example = "@hourly";
description = mdDoc "Schedule of MTR checks. Also accepts Cron format.";
};
flags = mkOption {
type = with types; listOf str;
default = [];
example = ["-G1"];
description = mdDoc "Additional flags to pass to MTR.";
};
};
});
};
};
};
};
config = mkIf cfg.enable {
environment.etc."mtr-exporter.conf" = {
source = jobsConfig;
};
systemd.services.mtr-exporter = {
script = ''
exec ${pkgs.mtr-exporter}/bin/mtr-exporter \
-mtr ${pkgs.mtr}/bin/mtr \
-schedule '@every ${toString cfg.interval}s' \
-bind ${escapeShellArg cfg.address}:${toString cfg.port} \
-- \
${escapeShellArgs (cfg.mtrFlags ++ [ cfg.target ])}
'';
wantedBy = [ "multi-user.target" ];
requires = [ "network.target" ];
after = [ "network.target" ];
serviceConfig = {
ExecStart = ''
${cfg.package}/bin/mtr-exporter \
-mtr '${cfg.mtrPackage}/bin/mtr' \
-bind ${escapeShellArg "${cfg.address}:${toString cfg.port}"} \
-jobs '${jobsConfig}' \
${escapeShellArgs cfg.extraFlags}
'';
Restart = "on-failure";
# Hardening
CapabilityBoundingSet = [ "" ];
@@ -30,13 +30,11 @@ let
configFile = pkgs.writeText "NetworkManager.conf" (lib.concatStringsSep "\n" [
(mkSection "main" {
plugins = "keyfile";
dhcp = cfg.dhcp;
dns = cfg.dns;
inherit (cfg) dhcp dns;
# If resolvconf is disabled that means that resolv.conf is managed by some other module.
rc-manager =
if config.networking.resolvconf.enable then "resolvconf"
else "unmanaged";
firewall-backend = cfg.firewallBackend;
})
(mkSection "keyfile" {
unmanaged-devices =
@@ -233,15 +231,6 @@ in
'';
};
firewallBackend = mkOption {
type = types.enum [ "iptables" "nftables" "none" ];
default = "iptables";
description = lib.mdDoc ''
Which firewall backend should be used for configuring masquerading with shared mode.
If set to none, NetworkManager doesn't manage the configuration at all.
'';
};
logLevel = mkOption {
type = types.enum [ "OFF" "ERR" "WARN" "INFO" "DEBUG" "TRACE" ];
default = "WARN";
@@ -340,20 +329,20 @@ in
default = [ ];
example = literalExpression ''
[ {
source = pkgs.writeText "upHook" '''
source = pkgs.writeText "upHook" '''
if [ "$2" != "up" ]; then
logger "exit: event $2 != up"
exit
fi
if [ "$2" != "up" ]; then
logger "exit: event $2 != up"
exit
fi
# coreutils and iproute are in PATH too
logger "Device $DEVICE_IFACE coming up"
''';
type = "basic";
} ]'';
# coreutils and iproute are in PATH too
logger "Device $DEVICE_IFACE coming up"
''';
type = "basic";
} ]
'';
description = lib.mdDoc ''
A list of scripts which will be executed in response to network events.
A list of scripts which will be executed in response to network events.
'';
};
@@ -413,6 +402,9 @@ in
them via the DNS server in your network, or use environment.etc
to add a file into /etc/NetworkManager/dnsmasq.d reconfiguring hostsdir.
'')
(mkRemovedOptionModule [ "networking" "networkmanager" "firewallBackend" ] ''
This option was removed as NixOS is now using iptables-nftables-compat even when using iptables, therefore Networkmanager now uses the nftables backend unconditionally.
'')
];
@@ -248,7 +248,6 @@ in
config = mkIf cfg.enable {
boot.blacklistedKernelModules = [ "ip_tables" ];
environment.systemPackages = [ pkgs.nftables ];
networking.networkmanager.firewallBackend = mkDefault "nftables";
# versionOlder for backportability, remove afterwards
networking.nftables.flushRuleset = mkDefault (versionOlder config.system.stateVersion "23.11" || (cfg.rulesetFile != null || cfg.ruleset != ""));
systemd.services.nftables = {
+17 -4
View File
@@ -27,13 +27,11 @@ let
mkValueString = mkValueStringSshd;
} " ";});
configFile = settingsFormat.generate "config" cfg.settings;
sshconf = pkgs.runCommand "sshd.conf-validated" { nativeBuildInputs = [ validationPackage ]; } ''
configFile = settingsFormat.generate "sshd.conf-settings" cfg.settings;
sshconf = pkgs.runCommand "sshd.conf-final" { } ''
cat ${configFile} - >$out <<EOL
${cfg.extraConfig}
EOL
sshd -G -f $out
'';
cfg = config.services.openssh;
@@ -576,6 +574,21 @@ in
'')}
'';
system.checks = [
(pkgs.runCommand "check-sshd-config"
{
nativeBuildInputs = [ validationPackage ];
} ''
${concatMapStringsSep "\n"
(lport: "sshd -G -T -C lport=${toString lport} -f ${sshconf} > /dev/null")
cfg.ports}
${concatMapStringsSep "\n"
(la: "sshd -G -T -C laddr=${la.addr},lport=${toString la.port} -f ${sshconf} > /dev/null")
cfg.listenAddresses}
touch $out
'')
];
assertions = [{ assertion = if cfg.settings.X11Forwarding then cfgc.setXAuthLocation else true;
message = "cannot enable X11 forwarding without setting xauth location";}
(let
+6 -2
View File
@@ -5,6 +5,10 @@ let
stateDir = "/var/lib/unifi";
cmd = ''
@${cfg.jrePackage}/bin/java java \
${optionalString (lib.versionAtLeast (lib.getVersion cfg.jrePackage) "16")
"--add-opens java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED "
+ "--add-opens java.base/sun.security.util=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED "
+ "--add-opens java.rmi/sun.rmi.transport=ALL-UNNAMED"} \
${optionalString (cfg.initialJavaHeapSize != null) "-Xms${(toString cfg.initialJavaHeapSize)}m"} \
${optionalString (cfg.maximumJavaHeapSize != null) "-Xmx${(toString cfg.maximumJavaHeapSize)}m"} \
-jar ${stateDir}/lib/ace.jar
@@ -24,8 +28,8 @@ in
services.unifi.jrePackage = mkOption {
type = types.package;
default = if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.3") then pkgs.jdk11 else pkgs.jre8;
defaultText = literalExpression ''if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.3" then pkgs.jdk11 else pkgs.jre8'';
default = if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.5") then pkgs.jdk17_headless else if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.3") then pkgs.jdk11 else pkgs.jre8;
defaultText = literalExpression ''if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.5") then pkgs.jdk17_headless else if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.3" then pkgs.jdk11 else pkgs.jre8'';
description = lib.mdDoc ''
The JRE package to use. Check the release notes to ensure it is supported.
'';
@@ -17,6 +17,8 @@ let
type = with types; nullOr str;
description = lib.mdDoc ''
wg-quick .conf file, describing the interface.
Using this option can be a useful means of configuring WireGuard if
one has an existing .conf file.
This overrides any other configuration interface configuration options.
See wg-quick manpage for more details.
'';
+2 -2
View File
@@ -83,12 +83,12 @@ in {
Group = "typesense";
StateDirectory = "typesense";
StateDirectoryMode = "0700";
StateDirectoryMode = "0750";
# Hardening
CapabilityBoundingSet = "";
LockPersonality = true;
MemoryDenyWriteExecute = true;
# MemoryDenyWriteExecute = true; needed since 0.25.1
NoNewPrivileges = true;
PrivateUsers = true;
PrivateTmp = true;
+15 -6
View File
@@ -7,10 +7,8 @@ let
# valid policy options
policy = (types.enum [ "allow" "block" "reject" "keep" "apply-policy" ]);
defaultRuleFile = "/var/lib/usbguard/rules.conf";
# decide what file to use for rules
ruleFile = if cfg.rules != null then pkgs.writeText "usbguard-rules" cfg.rules else defaultRuleFile;
ruleFile = if cfg.rules != null then pkgs.writeText "usbguard-rules" cfg.rules else cfg.ruleFile;
daemonConf = ''
# generated by nixos/modules/services/security/usbguard.nix
@@ -51,6 +49,19 @@ in
'';
};
ruleFile = mkOption {
type = types.nullOr types.path;
default = /var/lib/usbguard/rules.conf;
example = /run/secrets/usbguard-rules;
description = lib.mdDoc ''
This tells the USBGuard daemon which file to load as policy rule set.
The file can be changed manually or via the IPC interface assuming it has the right file permissions.
For more details see {manpage}`usbguard-rules.conf(5)`.
'';
};
rules = mkOption {
type = types.nullOr types.lines;
default = null;
@@ -63,8 +74,7 @@ in
be changed by the IPC interface.
If you do not set this option, the USBGuard daemon will load
it's policy rule set from `${defaultRuleFile}`.
This file can be changed manually or via the IPC interface.
it's policy rule set from the option configured in `services.usbguard.ruleFile`.
Running `usbguard generate-policy` as root will
generate a config for your currently plugged in devices.
@@ -248,7 +258,6 @@ in
'';
};
imports = [
(mkRemovedOptionModule [ "services" "usbguard" "ruleFile" ] "The usbguard module now uses ${defaultRuleFile} as ruleFile. Alternatively, use services.usbguard.rules to configure rules.")
(mkRemovedOptionModule [ "services" "usbguard" "IPCAccessControlFiles" ] "The usbguard module now hardcodes IPCAccessControlFiles to /var/lib/usbguard/IPCAccessControl.d.")
(mkRemovedOptionModule [ "services" "usbguard" "auditFilePath" ] "Removed usbguard module audit log files. Audit logs can be found in the systemd journal.")
(mkRenamedOptionModule [ "services" "usbguard" "implictPolicyTarget" ] [ "services" "usbguard" "implicitPolicyTarget" ])
@@ -60,10 +60,8 @@ in {
config = mkOption {
type = attrsOf (nullOr (oneOf [ bool int str ]));
default = {
config = {
ROCKET_ADDRESS = "::1"; # default to localhost
ROCKET_PORT = 8222;
};
ROCKET_ADDRESS = "::1"; # default to localhost
ROCKET_PORT = 8222;
};
example = literalExpression ''
{
@@ -10,6 +10,8 @@ in
services.calibre-web = {
enable = mkEnableOption (lib.mdDoc "Calibre-Web");
package = lib.mkPackageOption pkgs "calibre-web" { };
listen = {
ip = mkOption {
type = types.str;
@@ -73,6 +75,8 @@ in
'';
};
enableKepubify = mkEnableOption (lib.mdDoc "kebup conversion support");
enableBookUploading = mkOption {
type = types.bool;
default = false;
@@ -106,7 +110,7 @@ in
systemd.services.calibre-web = let
appDb = "/var/lib/${cfg.dataDir}/app.db";
gdriveDb = "/var/lib/${cfg.dataDir}/gdrive.db";
calibreWebCmd = "${pkgs.calibre-web}/bin/calibre-web -p ${appDb} -g ${gdriveDb}";
calibreWebCmd = "${cfg.package}/bin/calibre-web -p ${appDb} -g ${gdriveDb}";
settings = concatStringsSep ", " (
[
@@ -117,6 +121,7 @@ in
]
++ optional (cfg.options.calibreLibrary != null) "config_calibre_dir = '${cfg.options.calibreLibrary}'"
++ optional cfg.options.enableBookConversion "config_converterpath = '${pkgs.calibre}/bin/ebook-convert'"
++ optional cfg.options.enableKepubify "config_kepubifypath = '${pkgs.kepubify}/bin/kepubify'"
);
in
{
+1 -1
View File
@@ -116,7 +116,7 @@ in
unitConfig = {
ConditionPathExists = [
# Skip this service if the database already exists
"!$STATE_DIRECTORY/honk.db"
"!%S/honk/honk.db"
];
};
};
@@ -248,11 +248,10 @@ in {
# setup
${cfg.package}/createdb.sh
${cfg.package}/migrate.sh
export IP_GEOLOCATION_DB=${pkgs.dbip-country-lite}/share/dbip/dbip-country-lite.mmdb
${cfg.package}/bin/plausible eval "(Plausible.Release.prepare() ; Plausible.Auth.create_user(\"$ADMIN_USER_NAME\", \"$ADMIN_USER_EMAIL\", \"$ADMIN_USER_PWD\"))"
${optionalString cfg.adminUser.activate ''
if ! ${cfg.package}/init-admin.sh | grep 'already exists'; then
psql -d plausible <<< "UPDATE users SET email_verified=true;"
fi
psql -d plausible <<< "UPDATE users SET email_verified=true where email = '$ADMIN_USER_EMAIL';"
''}
exec plausible start
@@ -147,5 +147,9 @@ in {
};
environment.etc."vikunja/config.yaml".source = configFile;
environment.systemPackages = [
cfg.package-api # for admin `vikunja` CLI
];
};
}
@@ -34,7 +34,7 @@ let
# copy additional plugin(s), theme(s) and language(s)
${concatStringsSep "\n" (mapAttrsToList (name: theme: "cp -r ${theme} $out/share/wordpress/wp-content/themes/${name}") cfg.themes)}
${concatStringsSep "\n" (mapAttrsToList (name: plugin: "cp -r ${plugin} $out/share/wordpress/wp-content/plugins/${name}") cfg.plugins)}
${concatMapStringsSep "\n" (language: "cp -r ${language} $out/share/wordpress/wp-content/languages/") cfg.languages}
${concatMapStringsSep "\n" (language: "cp -r ${language}/* $out/share/wordpress/wp-content/languages/") cfg.languages}
'';
};
@@ -23,6 +23,12 @@ in
example = { RUST_BACKTRACE="yes"; };
};
environmentFile = mkOption {
type = types.nullOr types.path;
description = lib.mdDoc "File containing environment variables to be passed to the Garage server.";
default = null;
};
logLevel = mkOption {
type = types.enum (["info" "debug" "trace"]);
default = "info";
@@ -80,7 +86,7 @@ in
after = [ "network.target" "network-online.target" ];
wants = [ "network.target" "network-online.target" ];
wantedBy = [ "multi-user.target" ];
restartTriggers = [ configFile ];
restartTriggers = [ configFile ] ++ (lib.optional (cfg.environmentFile != null) cfg.environmentFile);
serviceConfig = {
ExecStart = "${cfg.package}/bin/garage server";
@@ -88,6 +94,7 @@ in
DynamicUser = lib.mkDefault true;
ProtectHome = true;
NoNewPrivileges = true;
EnvironmentFile = lib.optional (cfg.environmentFile != null) cfg.environmentFile;
};
environment = {
RUST_LOG = lib.mkDefault "garage=${cfg.logLevel}";
@@ -172,24 +172,19 @@ in
(mkIf (cfg.enable || cfg.mobile.enable || cfg.bigscreen.enable) {
security.wrappers = {
kscreenlocker_greet = {
setuid = true;
owner = "root";
group = "root";
source = "${getBin libsForQt5.kscreenlocker}/libexec/kscreenlocker_greet";
};
start_kdeinit = {
setuid = true;
owner = "root";
group = "root";
source = "${getBin libsForQt5.kinit}/libexec/kf5/start_kdeinit";
};
kwin_wayland = {
owner = "root";
group = "root";
capabilities = "cap_sys_nice+ep";
source = "${getBin plasma5.kwin}/bin/kwin_wayland";
};
} // mkIf (!cfg.runUsingSystemd) {
start_kdeinit = {
setuid = true;
owner = "root";
group = "root";
source = "${getBin libsForQt5.kinit}/libexec/kf5/start_kdeinit";
};
};
environment.systemPackages =
@@ -97,6 +97,19 @@ in
type = types.bool;
};
banner = mkOption {
type = types.nullOr types.lines;
default = null;
example = ''
foo
bar
baz
'';
description = lib.mdDoc ''
Optional message to display on the login screen.
'';
};
settings = mkOption {
type = settingsFormat.type;
default = { };
@@ -238,6 +251,11 @@ in
sleep-inactive-ac-timeout = lib.gvariant.mkInt32 0;
sleep-inactive-battery-timeout = lib.gvariant.mkInt32 0;
};
}] ++ lib.optionals (cfg.gdm.banner != null) [{
settings."org/gnome/login-screen" = {
banner-message-enable = true;
banner-message-text = cfg.gdm.banner;
};
}] ++ [ "${gdm}/share/gdm/greeter-dconf-defaults" ];
# Use AutomaticLogin if delay is zero, because it's immediate.
@@ -74,7 +74,7 @@ if ("@localeArchive@" ne "") {
if (!defined($action) || ($action ne "switch" && $action ne "boot" && $action ne "test" && $action ne "dry-activate")) {
print STDERR <<"EOF";
Usage: $0 [switch|boot|test]
Usage: $0 [switch|boot|test|dry-activate]
switch: make the configuration the boot default and activate now
boot: make the configuration the boot default
@@ -661,10 +661,20 @@ foreach my $mount_point (keys(%{$cur_fss})) {
# Filesystem entry disappeared, so unmount it.
$units_to_stop{$unit} = 1;
} elsif ($cur->{fsType} ne $new->{fsType} || $cur->{device} ne $new->{device}) {
# Filesystem type or device changed, so unmount and mount it.
$units_to_stop{$unit} = 1;
$units_to_start{$unit} = 1;
record_unit($start_list_file, $unit);
if ($mount_point eq '/' or $mount_point eq '/nix') {
if ($cur->{options} ne $new->{options}) {
# Mount options changed, so remount it.
$units_to_reload{$unit} = 1;
record_unit($reload_list_file, $unit);
} else {
# Don't unmount / or /nix if the device changed
$units_to_skip{$unit} = 1;
}
} else {
# Filesystem type or device changed, so unmount and mount it.
$units_to_restart{$unit} = 1;
record_unit($restart_list_file, $unit);
}
} elsif ($cur->{options} ne $new->{options}) {
# Mount options changes, so remount it.
$units_to_reload{$unit} = 1;
+6
View File
@@ -351,6 +351,12 @@ let
new_response="$(ykchalresp -${toString dev.yubikey.slot} -x $new_challenge 2>/dev/null)"
if [ -z "$new_response" ]; then
echo "Warning: Unable to generate new challenge response, current challenge persists!"
umount /crypt-storage
return
fi
if [ ! -z "$k_user" ]; then
new_k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $new_iterations $new_response | rbtohex)"
else
+7 -1
View File
@@ -66,7 +66,7 @@ in
};
services.resolved.dnssec = mkOption {
default = "allow-downgrade";
default = "false";
example = "true";
type = types.enum [ "true" "allow-downgrade" "false" ];
description = lib.mdDoc ''
@@ -85,6 +85,12 @@ in
synthesizing a DNS response that suggests DNSSEC was not
supported.
- `"false"`: DNS lookups are not DNSSEC validated.
At the time of September 2023, systemd upstream advise
to disable DNSSEC by default as the current code
is not robust enough to deal with "in the wild" non-compliant
servers, which will usually give you a broken bad experience
in addition of insecure.
'';
};
@@ -173,6 +173,33 @@ let
}];
}));
bridgeNetworks = mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: {
netdevs."40-${name}" = {
netdevConfig = {
Name = name;
Kind = "bridge";
};
};
networks = listToAttrs (forEach bridge.interfaces (bi:
nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
DHCP = mkOverride 0 (dhcpStr false);
networkConfig.Bridge = name;
} ])));
}));
vlanNetworks = mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: {
netdevs."40-${name}" = {
netdevConfig = {
Name = name;
Kind = "vlan";
};
vlanConfig.Id = vlan.id;
};
networks."40-${vlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
vlan = [ name ];
} ]);
}));
in
{
@@ -182,7 +209,15 @@ in
# Note this is if initrd.network.enable, not if
# initrd.systemd.network.enable. By setting the latter and not the
# former, the user retains full control over the configuration.
boot.initrd.systemd.network = mkMerge [(genericDhcpNetworks true) interfaceNetworks];
boot.initrd.systemd.network = mkMerge [
(genericDhcpNetworks true)
interfaceNetworks
bridgeNetworks
vlanNetworks
];
boot.initrd.availableKernelModules =
optional (cfg.bridges != {}) "bridge" ++
optional (cfg.vlans != {}) "8021q";
})
(mkIf cfg.useNetworkd {
@@ -212,19 +247,7 @@ in
}
(genericDhcpNetworks false)
interfaceNetworks
(mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: {
netdevs."40-${name}" = {
netdevConfig = {
Name = name;
Kind = "bridge";
};
};
networks = listToAttrs (forEach bridge.interfaces (bi:
nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
DHCP = mkOverride 0 (dhcpStr false);
networkConfig.Bridge = name;
} ])));
})))
bridgeNetworks
(mkMerge (flip mapAttrsToList cfg.bonds (name: bond: {
netdevs."40-${name}" = {
netdevConfig = {
@@ -377,18 +400,7 @@ in
} ]);
};
})))
(mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: {
netdevs."40-${name}" = {
netdevConfig = {
Name = name;
Kind = "vlan";
};
vlanConfig.Id = vlan.id;
};
networks."40-${vlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
vlan = [ name ];
} ]);
})))
vlanNetworks
];
# We need to prefill the slaved devices with networking options
@@ -39,7 +39,7 @@ in
# Allow root logins only using SSH keys
# and disable password authentication in general
services.openssh.enable = true;
services.openssh.settings.PermitRootLogin = "prohibit-password";
services.openssh.settings.PermitRootLogin = mkDefault "prohibit-password";
services.openssh.settings.PasswordAuthentication = mkDefault false;
# enable OS Login. This also requires setting enable-oslogin=TRUE metadata on
@@ -0,0 +1,60 @@
{ config, lib, pkgs, ... }:
let
cfg = config.oci;
in
{
imports = [ ../profiles/qemu-guest.nix ];
# Taken from /proc/cmdline of Ubuntu 20.04.2 LTS on OCI
boot.kernelParams = [
"nvme.shutdown_timeout=10"
"nvme_core.shutdown_timeout=10"
"libiscsi.debug_libiscsi_eh=1"
"crash_kexec_post_notifiers"
# VNC console
"console=tty1"
# x86_64-linux
"console=ttyS0"
# aarch64-linux
"console=ttyAMA0,115200"
];
boot.growPartition = true;
fileSystems."/" = {
device = "/dev/disk/by-label/nixos";
fsType = "ext4";
autoResize = true;
};
fileSystems."/boot" = lib.mkIf cfg.efi {
device = "/dev/disk/by-label/ESP";
fsType = "vfat";
};
boot.loader.efi.canTouchEfiVariables = false;
boot.loader.grub = {
device = if cfg.efi then "nodev" else "/dev/sda";
splashImage = null;
extraConfig = ''
serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1
terminal_input --append serial
terminal_output --append serial
'';
efiInstallAsRemovable = cfg.efi;
efiSupport = cfg.efi;
};
# https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/configuringntpservice.htm#Configuring_the_Oracle_Cloud_Infrastructure_NTP_Service_for_an_Instance
networking.timeServers = [ "169.254.169.254" ];
services.openssh.enable = true;
# Otherwise the instance may not have a working network-online.target,
# making the fetch-ssh-keys.service fail
networking.useNetworkd = true;
}
@@ -0,0 +1,12 @@
{ modulesPath, ... }:
{
# To build the configuration or use nix-env, you need to run
# either nixos-rebuild --upgrade or nix-channel --update
# to fetch the nixos channel.
# This configures everything but bootstrap services,
# which only need to be run once and have already finished
# if you are able to see this comment.
imports = [ "${modulesPath}/virtualisation/oci-common.nix" ];
}
@@ -0,0 +1,50 @@
{ config, lib, pkgs, ... }:
let
cfg = config.oci;
in
{
imports = [ ./oci-common.nix ];
config = {
system.build.OCIImage = import ../../lib/make-disk-image.nix {
inherit config lib pkgs;
name = "oci-image";
configFile = ./oci-config-user.nix;
format = "qcow2";
diskSize = 8192;
partitionTableType = if cfg.efi then "efi" else "legacy";
};
systemd.services.fetch-ssh-keys = {
description = "Fetch authorized_keys for root user";
wantedBy = [ "sshd.service" ];
before = [ "sshd.service" ];
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
path = [ pkgs.coreutils pkgs.curl ];
script = ''
mkdir -m 0700 -p /root/.ssh
if [ -f /root/.ssh/authorized_keys ]; then
echo "Authorized keys have already been downloaded"
else
echo "Downloading authorized keys from Instance Metadata Service v2"
curl -s -S -L \
-H "Authorization: Bearer Oracle" \
-o /root/.ssh/authorized_keys \
http://169.254.169.254/opc/v2/instance/metadata/ssh_authorized_keys
chmod 600 /root/.ssh/authorized_keys
fi
'';
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
StandardError = "journal+console";
StandardOutput = "journal+console";
};
};
};
}
@@ -0,0 +1,14 @@
{ config, lib, pkgs, ... }:
{
options = {
oci = {
efi = lib.mkOption {
default = true;
internal = true;
description = ''
Whether the OCI instance is using EFI.
'';
};
};
};
}
+9 -2
View File
@@ -109,6 +109,7 @@ in {
allTerminfo = handleTest ./all-terminfo.nix {};
alps = handleTest ./alps.nix {};
amazon-init-shell = handleTest ./amazon-init-shell.nix {};
amd-sev = runTest ./amd-sev.nix;
anbox = runTest ./anbox.nix;
anuko-time-tracker = handleTest ./anuko-time-tracker.nix {};
apcupsd = handleTest ./apcupsd.nix {};
@@ -468,6 +469,7 @@ in {
matrix-appservice-irc = handleTest ./matrix/appservice-irc.nix {};
matrix-conduit = handleTest ./matrix/conduit.nix {};
matrix-synapse = handleTest ./matrix/synapse.nix {};
matrix-synapse-workers = handleTest ./matrix/synapse-workers.nix {};
mattermost = handleTest ./mattermost.nix {};
mediamtx = handleTest ./mediamtx.nix {};
mediatomb = handleTest ./mediatomb.nix {};
@@ -672,9 +674,11 @@ in {
qboot = handleTestOn ["x86_64-linux" "i686-linux"] ./qboot.nix {};
qemu-vm-restrictnetwork = handleTest ./qemu-vm-restrictnetwork.nix {};
qemu-vm-volatile-root = runTest ./qemu-vm-volatile-root.nix;
quorum = handleTest ./quorum.nix {};
quake3 = handleTest ./quake3.nix {};
qgis = handleTest ./qgis.nix { qgisPackage = pkgs.qgis; };
qgis-ltr = handleTest ./qgis.nix { qgisPackage = pkgs.qgis-ltr; };
qownnotes = handleTest ./qownnotes.nix {};
quake3 = handleTest ./quake3.nix {};
quorum = handleTest ./quorum.nix {};
rabbitmq = handleTest ./rabbitmq.nix {};
radarr = handleTest ./radarr.nix {};
radicale = handleTest ./radicale.nix {};
@@ -740,6 +744,7 @@ in {
strongswan-swanctl = handleTest ./strongswan-swanctl.nix {};
stunnel = handleTest ./stunnel.nix {};
sudo = handleTest ./sudo.nix {};
sudo-rs = handleTest ./sudo-rs.nix {};
swap-file-btrfs = handleTest ./swap-file-btrfs.nix {};
swap-partition = handleTest ./swap-partition.nix {};
swap-random-encryption = handleTest ./swap-random-encryption.nix {};
@@ -760,6 +765,7 @@ in {
systemd-cryptenroll = handleTest ./systemd-cryptenroll.nix {};
systemd-credentials-tpm2 = handleTest ./systemd-credentials-tpm2.nix {};
systemd-escaping = handleTest ./systemd-escaping.nix {};
systemd-initrd-bridge = handleTest ./systemd-initrd-bridge.nix {};
systemd-initrd-btrfs-raid = handleTest ./systemd-initrd-btrfs-raid.nix {};
systemd-initrd-luks-fido2 = handleTest ./systemd-initrd-luks-fido2.nix {};
systemd-initrd-luks-keyfile = handleTest ./systemd-initrd-luks-keyfile.nix {};
@@ -774,6 +780,7 @@ in {
systemd-initrd-networkd = handleTest ./systemd-initrd-networkd.nix {};
systemd-initrd-networkd-ssh = handleTest ./systemd-initrd-networkd-ssh.nix {};
systemd-initrd-networkd-openvpn = handleTest ./initrd-network-openvpn { systemdStage1 = true; };
systemd-initrd-vlan = handleTest ./systemd-initrd-vlan.nix {};
systemd-journal = handleTest ./systemd-journal.nix {};
systemd-machinectl = handleTest ./systemd-machinectl.nix {};
systemd-networkd = handleTest ./systemd-networkd.nix {};
+56
View File
@@ -0,0 +1,56 @@
{ lib, ... }: {
name = "amd-sev";
meta = {
maintainers = with lib.maintainers; [ trundle veehaitch ];
};
nodes.machine = { lib, ... }: {
hardware.cpu.amd.sev.enable = true;
hardware.cpu.amd.sevGuest.enable = true;
specialisation.sevCustomUserGroup.configuration = {
users.groups.sevtest = { };
hardware.cpu.amd.sev = {
enable = true;
group = "root";
mode = "0600";
};
hardware.cpu.amd.sevGuest = {
enable = true;
group = "sevtest";
};
};
};
testScript = { nodes, ... }:
let
specialisations = "${nodes.machine.system.build.toplevel}/specialisation";
in
''
machine.wait_for_unit("multi-user.target")
with subtest("Check default settings"):
out = machine.succeed("cat /etc/udev/rules.d/99-local.rules")
assert 'KERNEL=="sev", OWNER="root", GROUP="sev", MODE="0660"' in out
assert 'KERNEL=="sev-guest", OWNER="root", GROUP="sev-guest", MODE="0660"' in out
out = machine.succeed("cat /etc/group")
assert "sev:" in out
assert "sev-guest:" in out
assert "sevtest:" not in out
with subtest("Activate configuration with custom user/group"):
machine.succeed('${specialisations}/sevCustomUserGroup/bin/switch-to-configuration test')
with subtest("Check custom user and group"):
out = machine.succeed("cat /etc/udev/rules.d/99-local.rules")
assert 'KERNEL=="sev", OWNER="root", GROUP="root", MODE="0600"' in out
assert 'KERNEL=="sev-guest", OWNER="root", GROUP="sevtest", MODE="0660"' in out
out = machine.succeed("cat /etc/group")
assert "sev:" not in out
assert "sev-guest:" not in out
assert "sevtest:" in out
'';
}
+22 -21
View File
@@ -134,31 +134,32 @@ import ./make-test-python.nix ({ pkgs, lib, ...}: {
extraArgs = [
"-v"
];
extraConfig = ''
server:
listen: 0.0.0.0@53
settings = {
server.listen = [
"0.0.0.0@53"
];
log:
- target: syslog
any: debug
log.syslog.any = "info";
acl:
- id: dhcp_ddns
address: 10.0.0.1
action: update
acl.dhcp_ddns = {
address = "10.0.0.1";
action = "update";
};
template:
- id: default
storage: ${zonesDir}
zonefile-sync: -1
zonefile-load: difference-no-serial
journal-content: all
template.default = {
storage = zonesDir;
zonefile-sync = "-1";
zonefile-load = "difference-no-serial";
journal-content = "all";
};
zone:
- domain: lan.nixos.test
file: lan.nixos.test.zone
acl: [dhcp_ddns]
'';
zone."lan.nixos.test" = {
file = "lan.nixos.test.zone";
acl = [
"dhcp_ddns"
];
};
};
};
};
+1
View File
@@ -32,6 +32,7 @@ let
linux_5_15_hardened
linux_6_1_hardened
linux_6_4_hardened
linux_6_5_hardened
linux_rt_5_4
linux_rt_5_10
linux_rt_5_15
+58 -64
View File
@@ -60,44 +60,43 @@ in {
services.knot.enable = true;
services.knot.extraArgs = [ "-v" ];
services.knot.keyFiles = [ tsigFile ];
services.knot.extraConfig = ''
server:
listen: 0.0.0.0@53
listen: ::@53
automatic-acl: true
services.knot.settings = {
server = {
listen = [
"0.0.0.0@53"
"::@53"
];
automatic-acl = true;
};
remote:
- id: secondary
address: 192.168.0.2@53
key: xfr_key
acl.secondary_acl = {
address = "192.168.0.2";
key = "xfr_key";
action = "transfer";
};
template:
- id: default
storage: ${knotZonesEnv}
notify: [secondary]
dnssec-signing: on
# Input-only zone files
# https://www.knot-dns.cz/docs/2.8/html/operation.html#example-3
# prevents modification of the zonefiles, since the zonefiles are immutable
zonefile-sync: -1
zonefile-load: difference
journal-content: changes
# move databases below the state directory, because they need to be writable
journal-db: /var/lib/knot/journal
kasp-db: /var/lib/knot/kasp
timer-db: /var/lib/knot/timer
remote.secondary.address = "192.168.0.2@53";
zone:
- domain: example.com
file: example.com.zone
template.default = {
storage = knotZonesEnv;
notify = [ "secondary" ];
acl = [ "secondary_acl" ];
dnssec-signing = true;
# Input-only zone files
# https://www.knot-dns.cz/docs/2.8/html/operation.html#example-3
# prevents modification of the zonefiles, since the zonefiles are immutable
zonefile-sync = -1;
zonefile-load = "difference";
journal-content = "changes";
};
- domain: sub.example.com
file: sub.example.com.zone
zone = {
"example.com".file = "example.com.zone";
"sub.example.com".file = "sub.example.com.zone";
};
log:
- target: syslog
any: info
'';
log.syslog.any = "info";
};
};
secondary = { lib, ... }: {
@@ -113,41 +112,36 @@ in {
services.knot.enable = true;
services.knot.keyFiles = [ tsigFile ];
services.knot.extraArgs = [ "-v" ];
services.knot.extraConfig = ''
server:
listen: 0.0.0.0@53
listen: ::@53
automatic-acl: true
services.knot.settings = {
server = {
listen = [
"0.0.0.0@53"
"::@53"
];
automatic-acl = true;
};
remote:
- id: primary
address: 192.168.0.1@53
key: xfr_key
remote.primary = {
address = "192.168.0.1@53";
key = "xfr_key";
};
template:
- id: default
master: primary
# zonefileless setup
# https://www.knot-dns.cz/docs/2.8/html/operation.html#example-2
zonefile-sync: -1
zonefile-load: none
journal-content: all
# move databases below the state directory, because they need to be writable
journal-db: /var/lib/knot/journal
kasp-db: /var/lib/knot/kasp
timer-db: /var/lib/knot/timer
template.default = {
master = "primary";
# zonefileless setup
# https://www.knot-dns.cz/docs/2.8/html/operation.html#example-2
zonefile-sync = "-1";
zonefile-load = "none";
journal-content = "all";
};
zone:
- domain: example.com
file: example.com.zone
zone = {
"example.com".file = "example.com.zone";
"sub.example.com".file = "sub.example.com.zone";
};
- domain: sub.example.com
file: sub.example.com.zone
log:
- target: syslog
any: info
'';
log.syslog.any = "info";
};
};
client = { lib, nodes, ... }: {
imports = [ common ];
+50
View File
@@ -0,0 +1,50 @@
import ../make-test-python.nix ({ pkgs, ... }: {
name = "matrix-synapse-workers";
meta = with pkgs.lib; {
maintainers = teams.matrix.members;
};
nodes = {
homeserver =
{ pkgs
, nodes
, ...
}: {
services.postgresql = {
enable = true;
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";
'';
};
services.matrix-synapse = {
enable = true;
settings = {
database = {
name = "psycopg2";
args.password = "synapse";
};
enable_registration = true;
enable_registration_without_verification = true;
federation_sender_instances = [ "federation_sender" ];
};
configureRedisLocally = true;
workers = {
"federation_sender" = { };
};
};
};
};
testScript = ''
start_all()
homeserver.wait_for_unit("matrix-synapse.service");
homeserver.wait_for_unit("matrix-synapse-worker-federation_sender.service");
'';
})
+33
View File
@@ -52,6 +52,36 @@ in {
};
};
server_match_rule =
{ ... }:
{
services.openssh = {
enable = true; listenAddresses = [ { addr = "127.0.0.1"; port = 22; } ];
extraConfig = ''
# Combined test for two (predictable) Match criterias
Match LocalAddress 127.0.0.1 LocalPort 22
PermitRootLogin yes
# Separate tests for Match criterias
Match User root
PermitRootLogin yes
Match Group root
PermitRootLogin yes
Match Host nohost.example
PermitRootLogin yes
Match LocalAddress 127.0.0.1
PermitRootLogin yes
Match LocalPort 22
PermitRootLogin yes
Match RDomain nohost.example
PermitRootLogin yes
Match Address 127.0.0.1
PermitRootLogin yes
'';
};
};
client =
{ ... }: { };
@@ -114,5 +144,8 @@ in {
with subtest("localhost-only"):
server_localhost_only.succeed("ss -nlt | grep '127.0.0.1:22'")
server_localhost_only_lazy.succeed("ss -nlt | grep '127.0.0.1:22'")
with subtest("match-rules"):
server_match_rule.succeed("ss -nlt | grep '127.0.0.1:22'")
'';
})
+2
View File
@@ -30,6 +30,8 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
machine.succeed("curl -f localhost:8000 >&2")
machine.succeed("curl -f localhost:8000/js/script.js >&2")
csrf_token = machine.succeed(
"curl -c /tmp/cookies localhost:8000/login | grep '_csrf_token' | sed -E 's,.*value=\"(.*)\".*,\\1,g'"
)
+38
View File
@@ -1178,6 +1178,44 @@ let
'';
};
sabnzbd = {
exporterConfig = {
enable = true;
servers = [{
baseUrl = "http://localhost:8080";
apiKeyFile = "/var/sabnzbd-apikey";
}];
};
metricProvider = {
services.sabnzbd.enable = true;
# unrar is required for sabnzbd
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (pkgs.lib.getName pkg) [ "unrar" ];
# extract the generated api key before starting
systemd.services.sabnzbd-apikey = {
requires = [ "sabnzbd.service" ];
after = [ "sabnzbd.service" ];
requiredBy = [ "prometheus-sabnzbd-exporter.service" ];
before = [ "prometheus-sabnzbd-exporter.service" ];
script = ''
grep -Po '^api_key = \K.+' /var/lib/sabnzbd/sabnzbd.ini > /var/sabnzbd-apikey
'';
};
};
exporterTest = ''
wait_for_unit("sabnzbd.service")
wait_for_unit("prometheus-sabnzbd-exporter.service")
wait_for_open_port(8080)
wait_for_open_port(9387)
wait_until_succeeds(
"curl -sSf 'localhost:9387/metrics' | grep 'sabnzbd_queue_size{sabnzbd_instance=\"http://localhost:8080\"} 0.0'"
)
'';
};
scaphandre = {
exporterConfig = {
enable = true;
+30
View File
@@ -0,0 +1,30 @@
import ./make-test-python.nix ({ pkgs, lib, qgisPackage, ... }:
let
testScript = pkgs.writeTextFile {
name = "qgis-test.py";
text = (builtins.readFile ../../pkgs/applications/gis/qgis/test.py);
};
in
{
name = "qgis";
meta = {
maintainers = with lib; [ teams.geospatial.members ];
};
nodes = {
machine = { pkgs, ... }: {
virtualisation.diskSize = 2 * 1024;
imports = [ ./common/x11.nix ];
environment.systemPackages = [ qgisPackage ];
};
};
testScript = ''
start_all()
machine.succeed("${qgisPackage}/bin/qgis --version | grep 'QGIS ${qgisPackage.version}'")
machine.succeed("${qgisPackage}/bin/qgis --code ${testScript}")
'';
})
+101
View File
@@ -0,0 +1,101 @@
# Some tests to ensure sudo is working properly.
{ pkgs, ... }:
let
inherit (pkgs.lib) mkIf optionalString;
password = "helloworld";
in
import ./make-test-python.nix ({ lib, pkgs, ...} : {
name = "sudo-rs";
meta.maintainers = pkgs.sudo-rs.meta.maintainers;
nodes.machine =
{ lib, ... }:
{
environment.systemPackages = [ pkgs.faketty ];
users.groups = { foobar = {}; barfoo = {}; baz = { gid = 1337; }; };
users.users = {
test0 = { isNormalUser = true; extraGroups = [ "wheel" ]; };
test1 = { isNormalUser = true; password = password; };
test2 = { isNormalUser = true; extraGroups = [ "foobar" ]; password = password; };
test3 = { isNormalUser = true; extraGroups = [ "barfoo" ]; };
test4 = { isNormalUser = true; extraGroups = [ "baz" ]; };
test5 = { isNormalUser = true; };
};
security.sudo.enable = false;
security.sudo-rs = {
enable = true;
package = pkgs.sudo-rs;
wheelNeedsPassword = false;
extraRules = [
# SUDOERS SYNTAX CHECK (Test whether the module produces a valid output;
# errors being detected by the visudo checks.
# These should not create any entries
{ users = [ "notest1" ]; commands = [ ]; }
{ commands = [ { command = "ALL"; options = [ ]; } ]; }
# Test defining commands with the options syntax, though not setting any options
{ users = [ "notest2" ]; commands = [ { command = "ALL"; options = [ ]; } ]; }
# CONFIGURATION FOR TEST CASES
{ users = [ "test1" ]; groups = [ "foobar" ]; commands = [ "ALL" ]; }
{ groups = [ "barfoo" 1337 ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ]; }
{ users = [ "test5" ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ]; runAs = "test1:barfoo"; }
];
};
};
nodes.strict = { ... }: {
environment.systemPackages = [ pkgs.faketty ];
users.users = {
admin = { isNormalUser = true; extraGroups = [ "wheel" ]; };
noadmin = { isNormalUser = true; };
};
security.sudo.enable = false;
security.sudo-rs = {
package = pkgs.sudo-rs;
enable = true;
wheelNeedsPassword = false;
execWheelOnly = true;
};
};
testScript =
''
with subtest("users in wheel group should have passwordless sudo"):
machine.succeed('faketty -- su - test0 -c "sudo -u root true"')
with subtest("test1 user should have sudo with password"):
machine.succeed('faketty -- su - test1 -c "echo ${password} | sudo -S -u root true"')
with subtest("test1 user should not be able to use sudo without password"):
machine.fail('faketty -- su - test1 -c "sudo -n -u root true"')
with subtest("users in group 'foobar' should be able to use sudo with password"):
machine.succeed('faketty -- su - test2 -c "echo ${password} | sudo -S -u root true"')
with subtest("users in group 'barfoo' should be able to use sudo without password"):
machine.succeed("sudo -u test3 sudo -n -u root true")
with subtest("users in group 'baz' (GID 1337)"):
machine.succeed("sudo -u test4 sudo -n -u root echo true")
with subtest("test5 user should be able to run commands under test1"):
machine.succeed("sudo -u test5 sudo -n -u test1 true")
with subtest("test5 user should not be able to run commands under root"):
machine.fail("sudo -u test5 sudo -n -u root true 2>/dev/null")
with subtest("users in wheel should be able to run sudo despite execWheelOnly"):
strict.succeed('faketty -- su - admin -c "sudo -u root true"')
with subtest("non-wheel users should be unable to run sudo thanks to execWheelOnly"):
strict.fail('faketty -- su - noadmin -c "sudo --help"')
'';
})
+122
View File
@@ -58,6 +58,37 @@ in {
'');
specialisation = rec {
brokenInitInterface.configuration.config.system.extraSystemBuilderCmds = ''
echo "systemd 0" > $out/init-interface-version
'';
modifiedSystemConf.configuration.systemd.extraConfig = ''
# Hello world!
'';
addedMount.configuration.virtualisation.fileSystems."/test" = {
device = "tmpfs";
fsType = "tmpfs";
};
addedMountOptsModified.configuration = {
imports = [ addedMount.configuration ];
virtualisation.fileSystems."/test".options = [ "x-test" ];
};
addedMountDevModified.configuration = {
imports = [ addedMountOptsModified.configuration ];
virtualisation.fileSystems."/test".device = lib.mkForce "ramfs";
};
storeMountModified.configuration = {
virtualisation.fileSystems."/".device = lib.mkForce "auto";
};
swap.configuration.swapDevices = lib.mkVMOverride [
{ device = "/swapfile"; size = 1; }
];
simpleService.configuration = {
systemd.services.test = {
wantedBy = [ "multi-user.target" ];
@@ -643,6 +674,97 @@ in {
# test and dry-activate actions are tested further down below
# invalid action fails the script
switch_to_specialisation("${machine}", "", action="broken-action", fail=True)
# no action fails the script
assert "Usage:" in machine.fail("${machine}/bin/switch-to-configuration 2>&1")
with subtest("init interface version"):
# Do not try to switch to an invalid init interface version
assert "incompatible" in switch_to_specialisation("${machine}", "brokenInitInterface", fail=True)
with subtest("systemd restarts"):
# systemd is restarted when its system.conf changes
out = switch_to_specialisation("${machine}", "modifiedSystemConf")
assert_contains(out, "restarting systemd...")
with subtest("continuing from an aborted switch"):
# An aborted switch will write into a file what it tried to start
# and a second switch should continue from this
machine.succeed("echo dbus.service > /run/nixos/start-list")
out = switch_to_specialisation("${machine}", "modifiedSystemConf")
assert_contains(out, "starting the following units: dbus.service\n")
with subtest("fstab mounts"):
switch_to_specialisation("${machine}", "")
# add a mountpoint
out = switch_to_specialisation("${machine}", "addedMount")
assert_lacks(out, "stopping the following units:")
assert_lacks(out, "NOT restarting the following changed units:")
assert_lacks(out, "\nrestarting the following units:")
assert_lacks(out, "\nstarting the following units:")
assert_contains(out, "the following new units were started: test.mount\n")
# modify the mountpoint's options
out = switch_to_specialisation("${machine}", "addedMountOptsModified")
assert_lacks(out, "stopping the following units:")
assert_lacks(out, "NOT restarting the following changed units:")
assert_contains(out, "reloading the following units: test.mount\n")
assert_lacks(out, "\nrestarting the following units:")
assert_lacks(out, "\nstarting the following units:")
assert_lacks(out, "the following new units were started:")
# modify the device
out = switch_to_specialisation("${machine}", "addedMountDevModified")
assert_lacks(out, "stopping the following units:")
assert_lacks(out, "NOT restarting the following changed units:")
assert_lacks(out, "reloading the following units:")
assert_contains(out, "\nrestarting the following units: test.mount\n")
assert_lacks(out, "\nstarting the following units:")
assert_lacks(out, "the following new units were started:")
# modify both
out = switch_to_specialisation("${machine}", "addedMount")
assert_lacks(out, "stopping the following units:")
assert_lacks(out, "NOT restarting the following changed units:")
assert_lacks(out, "reloading the following units:")
assert_contains(out, "\nrestarting the following units: test.mount\n")
assert_lacks(out, "\nstarting the following units:")
assert_lacks(out, "the following new units were started:")
# remove the mount
out = switch_to_specialisation("${machine}", "")
assert_contains(out, "stopping the following units: test.mount\n")
assert_lacks(out, "NOT restarting the following changed units:")
assert_contains(out, "reloading the following units: dbus.service\n")
assert_lacks(out, "\nrestarting the following units:")
assert_lacks(out, "\nstarting the following units:")
assert_lacks(out, "the following new units were started:")
# change something about the / mount
out = switch_to_specialisation("${machine}", "storeMountModified")
assert_lacks(out, "stopping the following units:")
assert_contains(out, "NOT restarting the following changed units: -.mount")
assert_contains(out, "reloading the following units: dbus.service\n")
assert_lacks(out, "\nrestarting the following units:")
assert_lacks(out, "\nstarting the following units:")
assert_lacks(out, "the following new units were started:")
with subtest("swaps"):
switch_to_specialisation("${machine}", "")
# add a swap
out = switch_to_specialisation("${machine}", "swap")
assert_lacks(out, "stopping the following units:")
assert_lacks(out, "NOT restarting the following changed units:")
assert_contains(out, "reloading the following units: dbus.service\n")
assert_lacks(out, "\nrestarting the following units:")
assert_lacks(out, "\nstarting the following units:")
assert_contains(out, "the following new units were started: swapfile.swap")
# remove it
out = switch_to_specialisation("${machine}", "")
assert_contains(out, "stopping swap device: /swapfile")
assert_lacks(out, "stopping the following units:")
assert_lacks(out, "NOT restarting the following changed units:")
assert_contains(out, "reloading the following units: dbus.service\n")
assert_lacks(out, "\nrestarting the following units:")
assert_lacks(out, "\nstarting the following units:")
assert_lacks(out, "the following new units were started:")
with subtest("services"):
switch_to_specialisation("${machine}", "")
# Nothing happens when nothing is changed
+63
View File
@@ -0,0 +1,63 @@
import ./make-test-python.nix ({ lib, ... }: {
name = "systemd-initrd-bridge";
meta.maintainers = [ lib.maintainers.majiir ];
# Tests bridge interface configuration in systemd-initrd.
#
# The 'a' and 'b' nodes are connected to a 'bridge' node through different
# links. The 'bridge' node configures a bridge across them. It waits forever
# in initrd (stage 1) with networking enabled. 'a' and 'b' ping 'bridge' to
# test connectivity with the bridge interface. Then, 'a' pings 'b' to test
# the bridge itself.
nodes = {
bridge = { config, lib, ... }: {
boot.initrd.systemd.enable = true;
boot.initrd.network.enable = true;
boot.initrd.systemd.services.boot-blocker = {
before = [ "initrd.target" ];
wantedBy = [ "initrd.target" ];
script = "sleep infinity";
serviceConfig.Type = "oneshot";
};
networking.primaryIPAddress = "192.168.1.${toString config.virtualisation.test.nodeNumber}";
virtualisation.vlans = [ 1 2 ];
networking.bridges.br0.interfaces = [ "eth1" "eth2" ];
networking.interfaces = {
eth1.ipv4.addresses = lib.mkForce [];
eth2.ipv4.addresses = lib.mkForce [];
br0.ipv4.addresses = [{
address = config.networking.primaryIPAddress;
prefixLength = 24;
}];
};
};
a = {
virtualisation.vlans = [ 1 ];
};
b = { config, ... }: {
virtualisation.vlans = [ 2 ];
networking.primaryIPAddress = lib.mkForce "192.168.1.${toString config.virtualisation.test.nodeNumber}";
networking.interfaces.eth1.ipv4.addresses = lib.mkForce [{
address = config.networking.primaryIPAddress;
prefixLength = 24;
}];
};
};
testScript = ''
start_all()
a.wait_for_unit("network.target")
b.wait_for_unit("network.target")
a.succeed("ping -n -w 10 -c 1 bridge >&2")
b.succeed("ping -n -w 10 -c 1 bridge >&2")
a.succeed("ping -n -w 10 -c 1 b >&2")
'';
})
+59
View File
@@ -0,0 +1,59 @@
import ./make-test-python.nix ({ lib, ... }: {
name = "systemd-initrd-vlan";
meta.maintainers = [ lib.maintainers.majiir ];
# Tests VLAN interface configuration in systemd-initrd.
#
# Two nodes are configured for a tagged VLAN. (Note that they also still have
# their ordinary eth0 and eth1 interfaces, which are not VLAN-tagged.)
#
# The 'server' node waits forever in initrd (stage 1) with networking
# enabled. The 'client' node pings it to test network connectivity.
nodes = let
network = id: {
networking = {
vlans."eth1.10" = {
id = 10;
interface = "eth1";
};
interfaces."eth1.10" = {
ipv4.addresses = [{
address = "192.168.10.${id}";
prefixLength = 24;
}];
};
};
};
in {
# Node that will use initrd networking.
server = network "1" // {
boot.initrd.systemd.enable = true;
boot.initrd.network.enable = true;
boot.initrd.systemd.services.boot-blocker = {
before = [ "initrd.target" ];
wantedBy = [ "initrd.target" ];
script = "sleep infinity";
serviceConfig.Type = "oneshot";
};
};
# Node that will ping the server.
client = network "2";
};
testScript = ''
start_all()
client.wait_for_unit("network.target")
# Wait for the regular (untagged) interface to be up.
def server_is_up(_) -> bool:
status, _ = client.execute("ping -n -c 1 server >&2")
return status == 0
with client.nested("waiting for server to come up"):
retry(server_is_up)
# Try to ping the (tagged) VLAN interface.
client.succeed("ping -n -w 10 -c 1 192.168.10.1 >&2")
'';
})
+2
View File
@@ -16,6 +16,8 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
systemPackages = with pkgs; [ tinywl foot wayland-utils ];
};
hardware.opengl.enable = true;
# Automatically start TinyWL when logging in on tty1:
programs.bash.loginShellInit = ''
if [ "$(tty)" = "/dev/tty1" ]; then
+1 -1
View File
@@ -67,7 +67,7 @@ rec {
networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
};
}) {} [
"6_1" "6_2"
"6_1" "6_2" "6_3"
];
testScript = ''
+2 -2
View File
@@ -58,14 +58,14 @@
}:
stdenv.mkDerivation rec {
pname = "ardour";
version = "7.4";
version = "7.5";
# We can't use `fetchFromGitea` here, as attempting to fetch release archives from git.ardour.org
# result in an empty archive. See https://tracker.ardour.org/view.php?id=7328 for more info.
src = fetchgit {
url = "git://git.ardour.org/ardour/ardour.git";
rev = version;
hash = "sha256-CUGhJi3ji0F6v41Y08sQvo7oKITOJ96ojdJL+FyCxmw=";
hash = "sha256-cmYt6fGYuuVs6YhAXaO9AG6TrYLDVUaE1/iC67rt76I=";
};
bundledContent = fetchzip {
+2 -2
View File
@@ -13,12 +13,12 @@ stdenv.mkDerivation rec {
src =
if stdenv.hostPlatform.system == "x86_64-linux" then
fetchurl {
url = "http://www.baudline.com/baudline_${version}_linux_x86_64.tar.gz";
url = "https://www.baudline.com/baudline_${version}_linux_x86_64.tar.gz";
sha256 = "09fn0046i69in1jpizkzbaq5ggij0mpflcsparyskm3wh71mbzvr";
}
else if stdenv.hostPlatform.system == "i686-linux" then
fetchurl {
url = "http://www.baudline.com/baudline_${version}_linux_i686.tar.gz";
url = "https://www.baudline.com/baudline_${version}_linux_i686.tar.gz";
sha256 = "1waip5pmcf5ffcfvn8lf1rvsaq2ab66imrbfqs777scz7k8fhhjb";
}
else
@@ -27,11 +27,11 @@
stdenv.mkDerivation rec {
pname = "bitwig-studio";
version = "5.0.4";
version = "5.0.7";
src = fetchurl {
url = "https://downloads.bitwig.com/stable/${version}/${pname}-${version}.deb";
sha256 = "sha256-IkhUkKO+Ay1WceZNekII6aHLOmgcgGfx0hGo5ldFE5Y=";
sha256 = "sha256-jsHGUAVRUiz9soffW1PvF6UUGzbGhltaKtEW5ynq/Xk=";
};
nativeBuildInputs = [ dpkg makeWrapper wrapGAppsHook ];
+4 -5
View File
@@ -1,5 +1,4 @@
{
stdenv
{ stdenv
, fetchFromGitHub
, fetchurl
, cmake
@@ -28,11 +27,11 @@
stdenv.mkDerivation rec {
pname = "cardinal";
version = "23.07";
version = "23.09";
src = fetchurl {
url = "https://github.com/DISTRHO/Cardinal/releases/download/${version}/cardinal+deps-${version}.tar.xz";
hash = "sha256-Ng2E6ML9lffmdGgn9piIF3ko4uvV/uLDb3d7ytrfcLU=";
hash = "sha256-q42ry47y4tTkUbejv6iN5jXcadXSSTPQ3omhMUevfqU=";
};
prePatch = ''
@@ -87,7 +86,7 @@ stdenv.mkDerivation rec {
description = "Plugin wrapper around VCV Rack";
homepage = "https://github.com/DISTRHO/cardinal";
license = lib.licenses.gpl3;
maintainers = [ lib.maintainers.magnetophon ];
maintainers = with lib.maintainers; [ magnetophon PowerUser64 ];
mainProgram = "Cardinal";
platforms = lib.platforms.all;
# never built on aarch64-darwin, x86_64-darwin since first introduction in nixpkgs
@@ -28,22 +28,22 @@
, rnnoise
, rubberband
, speexdsp
, soundtouch
, tbb
, wrapGAppsHook4
, zam-plugins
, zita-convolver
, soundtouch
}:
stdenv.mkDerivation rec {
pname = "easyeffects";
version = "7.0.5";
version = "7.1.0";
src = fetchFromGitHub {
owner = "wwmm";
repo = "easyeffects";
rev = "v${version}";
hash = "sha256-Z/0O8dVZ3J901OdOc1wF1XibNE/33b8oSqY6RKPDfzg=";
hash = "sha256-TuVW2KBJciuFVdduzfFepGOa+UY9+sXRN1gWhfDvXgw=";
};
nativeBuildInputs = [
+2 -2
View File
@@ -5,13 +5,13 @@
stdenv.mkDerivation rec {
pname = "hydrogen";
version = "1.2.1";
version = "1.2.2";
src = fetchFromGitHub {
owner = "hydrogen-music";
repo = pname;
rev = version;
sha256 = "sha256-09zN6OVqVohk153gqXy6C0uHcBhZX2JJL4d6f4BU4Lg=";
sha256 = "sha256-A9mLiPh7ZMWJ11PcVP07IxZ8WdV2HkkKLix77egbC0M=";
};
nativeBuildInputs = [ cmake pkg-config wrapQtAppsHook ];
+20 -4
View File
@@ -1,18 +1,33 @@
{ mkDerivation, lib, fetchFromGitHub, pkg-config, qtscript, qmake, libjack2
{ mkDerivation
, lib
, fetchFromGitHub
, pkg-config
, qtscript
, qmake
, qtbase
, qtmultimedia
, qtdeclarative
, libjack2
}:
mkDerivation rec {
pname = "jamulus";
version = "3.8.2";
version = "3.10.0";
src = fetchFromGitHub {
owner = "jamulussoftware";
repo = "jamulus";
rev = "r${lib.replaceStrings [ "." ] [ "_" ] version}";
sha256 = "sha256-K2HznkntDhp+I8DHJk5Cuh5cR8yjwfzX+pGGzS8yVLQ=";
hash = "sha256-uqBre1Hcdmmifm/gii3MlP9LiAovQVsAaPZTmVm1nnM=";
};
nativeBuildInputs = [ pkg-config qmake ];
buildInputs = [ qtscript libjack2 ];
buildInputs = [
qtscript
qtbase
qtmultimedia
qtdeclarative
libjack2
];
qmakeFlags = [ "CONFIG+=noupcasename" ];
@@ -22,6 +37,7 @@ mkDerivation rec {
homepage = "https://github.com/corrados/jamulus/wiki";
license = lib.licenses.gpl2Plus;
platforms = lib.platforms.linux;
mainProgram = "jamulus";
maintainers = [ lib.maintainers.seb314 ];
};
}
@@ -1,11 +1,13 @@
{ lib
, rustPlatform
, fetchFromGitHub
, makeWrapper
, pkg-config
, stdenv
, openssl
, withALSA ? true
, withALSA ? stdenv.isLinux
, alsa-lib
, alsa-plugins
, withPortAudio ? false
, portaudio
, withPulseAudio ? false
@@ -26,7 +28,7 @@ rustPlatform.buildRustPackage rec {
cargoSha256 = "sha256-tbDlWP0sUIa0W9HhdYNOvo9cGeqFemclhA7quh7f/Rw=";
nativeBuildInputs = [ pkg-config ] ++ lib.optionals stdenv.isDarwin [
nativeBuildInputs = [ pkg-config makeWrapper ] ++ lib.optionals stdenv.isDarwin [
rustPlatform.bindgenHook
];
@@ -41,6 +43,11 @@ rustPlatform.buildRustPackage rec {
++ lib.optional withPortAudio "portaudio-backend"
++ lib.optional withPulseAudio "pulseaudio-backend";
postFixup = lib.optionalString withALSA ''
wrapProgram "$out/bin/librespot" \
--set ALSA_PLUGIN_DIR '${alsa-plugins}/lib/alsa-lib'
'';
meta = with lib; {
description = "Open Source Spotify client library and playback daemon";
homepage = "https://github.com/librespot-org/librespot";
@@ -3,7 +3,7 @@
, fetchFromGitHub
, qmake
, wrapQtAppsHook
, qscintilla-qt6
, qt6Packages
, bison
, flex
, which
@@ -45,7 +45,7 @@ stdenv.mkDerivation (finalAttrs: {
buildInputs = [
alsa-lib
libsndfile
qscintilla-qt6
qt6Packages.qscintilla
] ++ lib.optional (audioBackend == "pulse") libpulseaudio
++ lib.optional (audioBackend == "jack") libjack2;
@@ -4,13 +4,13 @@
, cmake
}:
stdenv.mkDerivation rec {
stdenv.mkDerivation (finalAttrs: {
version = "10.22";
pname = "monkeys-audio";
src = fetchzip {
url = "https://monkeysaudio.com/files/MAC_${
builtins.concatStringsSep "" (lib.strings.splitString "." version)}_SDK.zip";
builtins.concatStringsSep "" (lib.strings.splitString "." finalAttrs.version)}_SDK.zip";
sha256 = "sha256-JmDH9IudtuJdu1kSDI1RNaYiIgmPgH4RT2Myz9ihQH4=";
stripRoot = false;
};
@@ -21,10 +21,11 @@ stdenv.mkDerivation rec {
meta = with lib; {
description = "APE codec and decompressor";
platforms = platforms.linux;
mainProgram = "mac";
# This is not considered a GPL license, but it seems rather free although
# it's not standard, see a quote of it:
# https://github.com/NixOS/nixpkgs/pull/171682#issuecomment-1120260551
license = licenses.free;
maintainers = with maintainers; [ doronbehar ];
};
}
})
@@ -1,8 +1,8 @@
{ lib
, stdenv
, fetchFromGitHub
, autoreconfHook
, pkg-config
, autoreconfHook
, SDL2
, SDL2_ttf
, SDL2_image
@@ -14,13 +14,13 @@
, dejavu_fonts
}:
stdenv.mkDerivation rec {
stdenv.mkDerivation (finalAttrs: {
pname = "mpd-touch-screen-gui";
version = "unstable-2022-12-30";
src = fetchFromGitHub {
owner = "muesli4";
repo = pname;
repo = "mpd-touch-screen-gui";
rev = "156eaebede89da2b83a98d8f9dfa46af12282fb4";
sha256 = "sha256-vr/St4BghrndjUQ0nZI/uJq+F/MjEj6ulc4DYwQ/pgU=";
};
@@ -60,4 +60,4 @@ stdenv.mkDerivation rec {
maintainers = with maintainers; [ doronbehar ];
platforms = platforms.all;
};
}
})
@@ -46,14 +46,14 @@ let
Carbon
;
} else portaudio;
in stdenv'.mkDerivation rec {
in stdenv'.mkDerivation (finalAttrs: {
pname = "musescore";
version = "4.1.1";
src = fetchFromGitHub {
owner = "musescore";
repo = "MuseScore";
rev = "v${version}";
rev = "v${finalAttrs.version}";
sha256 = "sha256-jXievVIA0tqLdKLy6oPaOHPIbDoFstveEQBri9M0Aoo=";
};
patches = [
@@ -168,4 +168,4 @@ in stdenv'.mkDerivation rec {
broken = (stdenv.isLinux && stdenv.isAarch64);
mainProgram = "mscore";
};
}
})

Some files were not shown because too many files have changed in this diff Show More