Merge branch 'NixOS:master' into vimix-cursor-theme
This commit is contained in:
+9
-13
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 package’s `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 package’s `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 package’s `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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -206,6 +206,7 @@ rec {
|
||||
aarch64-embedded = {
|
||||
config = "aarch64-none-elf";
|
||||
libc = "newlib";
|
||||
rustc.config = "aarch64-unknown-none";
|
||||
};
|
||||
|
||||
aarch64be-embedded = {
|
||||
|
||||
@@ -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";
|
||||
|
||||
Executable
+228
@@ -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")
|
||||
@@ -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")
|
||||
@@ -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`.
|
||||
|
||||
@@ -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 != []) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -700,6 +700,7 @@ in {
|
||||
|
||||
environment.profiles = [
|
||||
"$HOME/.nix-profile"
|
||||
"\${XDG_STATE_HOME:-$HOME/.local/state}/nix/profile"
|
||||
"/etc/profiles/per-user/$USER"
|
||||
];
|
||||
|
||||
|
||||
@@ -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}"
|
||||
'';
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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 = { };
|
||||
};
|
||||
}
|
||||
@@ -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, ... }:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" ];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ];
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 ];
|
||||
|
||||
@@ -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}
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -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 ''
|
||||
{
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
'';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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 {};
|
||||
|
||||
@@ -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
@@ -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"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
@@ -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
@@ -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 ];
|
||||
|
||||
@@ -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");
|
||||
'';
|
||||
})
|
||||
@@ -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'")
|
||||
'';
|
||||
})
|
||||
|
||||
@@ -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'"
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}")
|
||||
'';
|
||||
})
|
||||
@@ -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"')
|
||||
'';
|
||||
})
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
'';
|
||||
})
|
||||
@@ -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")
|
||||
'';
|
||||
})
|
||||
@@ -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
|
||||
|
||||
@@ -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 = ''
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 ];
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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 ];
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user