0
0
Fork 0
mirror of https://github.com/NixOS/nixpkgs.git synced 2025-07-14 06:00:33 +03:00

Merge branch 'master' into init/pythonPackages.mkdocs-git-authors-plugin

This commit is contained in:
Matthias Thym 2023-07-27 22:09:14 +02:00 committed by GitHub
commit 59eb55b5d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3435 changed files with 97704 additions and 95769 deletions

18
.github/CODEOWNERS vendored
View file

@ -23,7 +23,7 @@
# Libraries
/lib @edolstra @infinisil
/lib/systems @alyssais @ericson2314 @matthewbauer
/lib/systems @alyssais @ericson2314 @matthewbauer @amjoseph-nixpkgs
/lib/generators.nix @edolstra @Profpatsch
/lib/cli.nix @edolstra @Profpatsch
/lib/debug.nix @edolstra @Profpatsch
@ -37,10 +37,10 @@
/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
/pkgs/stdenv/generic @Ericson2314 @matthewbauer @amjoseph-nixpkgs
/pkgs/stdenv/generic/check-meta.nix @Ericson2314 @matthewbauer @piegamesde
/pkgs/stdenv/cross @Ericson2314 @matthewbauer
/pkgs/build-support/cc-wrapper @Ericson2314
/pkgs/stdenv/cross @Ericson2314 @matthewbauer @amjoseph-nixpkgs
/pkgs/build-support/cc-wrapper @Ericson2314 @amjoseph-nixpkgs
/pkgs/build-support/bintools-wrapper @Ericson2314
/pkgs/build-support/setup-hooks @Ericson2314
/pkgs/build-support/setup-hooks/auto-patchelf.sh @layus
@ -124,7 +124,7 @@
/doc/languages-frameworks/rust.section.md @zowoq @winterqt @figsoda
# C compilers
/pkgs/development/compilers/gcc @matthewbauer
/pkgs/development/compilers/gcc @matthewbauer @amjoseph-nixpkgs
/pkgs/development/compilers/llvm @matthewbauer @RaitoBezarius
# Compatibility stuff
@ -196,6 +196,11 @@ pkgs/development/python-modules/buildcatrust/ @ajs124 @lukegb @mweinelt
/nixos/tests/kea.nix @mweinelt
/nixos/tests/knot.nix @mweinelt
# Web servers
/doc/builders/packages/nginx.section.md @raitobezarius
/pkgs/servers/http/nginx/ @raitobezarius
/nixos/modules/services/web-servers/nginx/ @raitobezarius
# Dhall
/pkgs/development/dhall-modules @Gabriella439 @Profpatsch @ehmry
/pkgs/development/interpreters/dhall @Gabriella439 @Profpatsch @ehmry
@ -308,3 +313,6 @@ nixos/lib/make-single-disk-zfs-image.nix @raitobezarius
nixos/lib/make-multi-disk-zfs-image.nix @raitobezarius
nixos/modules/tasks/filesystems/zfs.nix @raitobezarius
nixos/tests/zfs.nix @raitobezarius
# Linux Kernel
pkgs/os-specific/linux/kernel/manual-config.nix @amjoseph-nixpkgs

View file

@ -34,5 +34,7 @@ The `ibus-engines.typing-booster` package contains a program named `emoji-picker
On NixOS, it can be installed using the following expression:
```nix
{ pkgs, ... }: { fonts.fonts = with pkgs; [ noto-fonts-emoji ]; }
{ pkgs, ... }: {
fonts.packages = with pkgs; [ noto-fonts-emoji ];
}
```

4
doc/common.nix Normal file
View file

@ -0,0 +1,4 @@
{
outputPath = "share/doc/nixpkgs";
indexPath = "manual.html";
}

View file

@ -456,7 +456,7 @@ In the file `pkgs/top-level/all-packages.nix` you can find fetch helpers, these
owner = "NixOS";
repo = "nix";
rev = "1f795f9f44607cc5bec70d1300150bfefcef2aae";
hash = "ha256-7D4m+saJjbSFP5hOwpQq2FGR2rr+psQMTcyb1ZvtXsQ=";
hash = "sha256-7D4m+saJjbSFP5hOwpQq2FGR2rr+psQMTcyb1ZvtXsQ=";
}
```

View file

@ -23,6 +23,14 @@ $ nix-shell
If the build succeeds, the manual will be in `./result/share/doc/nixpkgs/manual.html`.
## devmode {#sec-contributing-devmode}
The shell in the manual source directory makes available a command, `devmode`.
It is a daemon, that:
1. watches the manual's source for changes and when they occur — rebuilds
2. HTTP serves the manual, injecting a script that triggers reload on changes
3. opens the manual in the default browser
## Syntax {#sec-contributing-markup}
As per [RFC 0072](https://github.com/NixOS/rfcs/pull/72), all new documentation content should be written in [CommonMark](https://commonmark.org/) Markdown dialect.

View file

@ -62,6 +62,8 @@ Sample template for a package update review is provided below.
- [ ] package build on ARCHITECTURE
- [ ] executables tested on ARCHITECTURE
- [ ] all depending packages build
- [ ] patches have a comment describing either the upstream URL or a reason why the patch wasn't upstreamed
- [ ] patches that are remotely available are fetched rather than vendored
##### Possible improvements
@ -105,7 +107,8 @@ Sample template for a new package review is provided below.
- [ ] source is fetched using the appropriate function
- [ ] the list of `phases` is not overridden
- [ ] when a phase (like `installPhase`) is overridden it starts with `runHook preInstall` and ends with `runHook postInstall`.
- [ ] patches that are remotely available are fetched with `fetchpatch`
- [ ] patches have a comment describing either the upstream URL or a reason why the patch wasn't upstreamed
- [ ] patches that are remotely available are fetched rather than vendored
##### Possible improvements

View file

@ -214,26 +214,81 @@ The last checkbox is fits [CONTRIBUTING.md](https://github.com/NixOS/nixpkgs/blo
- Hydra builds for master and staging should not be used as testing platform, its a build farm for changes that have been already tested.
- When changing the bootloader installation process, extra care must be taken. Grub installations cannot be rolled back, hence changes may break peoples installations forever. For any non-trivial change to the bootloader please file a PR asking for review, especially from \@edolstra.
::: {.figure #fig-staging-workflow}
# Staging workflow
<!-- generated from ./staging-workflow.dot using: dot -Tsvg staging-workflow.dot > staging-workflow.svg -->
![Staging workflow](./staging-workflow.svg)
:::
### Branches {#submitting-changes-branches}
[This GitHub Action](https://github.com/NixOS/nixpkgs/blob/master/.github/workflows/periodic-merge-6h.yml) brings changes from `master` to `staging-next` and from `staging-next` to `staging` every 6 hours; these are the blue arrows in the diagram above. The purple arrows in the diagram above are done manually and much less frequently. You can get an idea of how often these merges occur by looking at the git history.
The `nixpkgs` repository has three major branches:
- `master`
- `staging`
- `staging-next`
The most important distinction between them is that `staging`
(colored red in the diagram below) can receive commits which cause
a mass-rebuild (for example, anything that changes the `drvPath` of
`stdenv`). The other two branches `staging-next` and `master`
(colored green in the diagram below) can *not* receive commits which
cause a mass-rebuild.
Arcs between the branches show possible merges into these branches,
either from other branches or from independently submitted PRs. The
colors of these edges likewise show whether or not they could
trigger a mass rebuild (red) or must not trigger a mass rebuild
(green).
Hydra runs automatic builds for the green branches.
Notice that the automatic merges are all green arrows. This is by
design. Any merge which might cause a mass rebuild on a branch
which has automatic builds (`staging-next`, `master`) will be a
manual merge to make sure it is good use of compute power.
Nixpkgs has two branches so that there is one branch (`staging`)
which accepts mass-rebuilding commits, and one fast-rebuilding
branch which accepts independent PRs (`master`). The `staging-next`
branch allows the Hydra operators to batch groups of commits to
`staging` to be built. By keeping the `staging-next` branch
separate from `staging`, this batching does not block
developers from merging changes into `staging`.
```{.graphviz caption="Staging workflow"}
digraph {
master [color="green" fontcolor=green]
"staging-next" [color="green" fontcolor=green]
staging [color="red" fontcolor=red]
"small changes" [fontcolor=green shape=none]
"small changes" -> master [color=green]
"mass-rebuilds and other large changes" [fontcolor=red shape=none]
"mass-rebuilds and other large changes" -> staging [color=red]
"critical security fixes" [fontcolor=green shape=none]
"critical security fixes" -> master [color=green]
"staging fixes which do not cause staging to mass-rebuild" [fontcolor=green shape=none]
"staging fixes which do not cause staging to mass-rebuild" -> "staging-next" [color=green]
"staging-next" -> master [color="red"] [label="manual merge"] [fontcolor="red"]
"staging" -> "staging-next" [color="red"] [label="manual merge"] [fontcolor="red"]
master -> "staging-next" [color="green"] [label="automatic merge (GitHub Action)"] [fontcolor="green"]
"staging-next" -> staging [color="green"] [label="automatic merge (GitHub Action)"] [fontcolor="green"]
}
```
[This GitHub Action](https://github.com/NixOS/nixpkgs/blob/master/.github/workflows/periodic-merge-6h.yml) brings changes from `master` to `staging-next` and from `staging-next` to `staging` every 6 hours; these are the green arrows in the diagram above. The red arrows in the diagram above are done manually and much less frequently. You can get an idea of how often these merges occur by looking at the git history.
### Master branch {#submitting-changes-master-branch}
#### Master branch {#submitting-changes-master-branch}
The `master` branch is the main development branch. It should only see non-breaking commits that do not cause mass rebuilds.
### Staging branch {#submitting-changes-staging-branch}
#### Staging branch {#submitting-changes-staging-branch}
The `staging` branch is a development branch where mass-rebuilds go. Mass rebuilds are commits that cause rebuilds for many packages, like more than 500 (or perhaps, if it's 'light' packages, 1000). It should only see non-breaking mass-rebuild commits. That means it is not to be used for testing, and changes must have been well tested already. If the branch is already in a broken state, please refrain from adding extra new breakages.
During the process of a releasing a new NixOS version, this branch or the release-critical packages can be restricted to non-breaking changes.
### Staging-next branch {#submitting-changes-staging-next-branch}
#### Staging-next branch {#submitting-changes-staging-next-branch}
The `staging-next` branch is for stabilizing mass-rebuilds submitted to the `staging` branch prior to merging them into `master`. Mass-rebuilds must go via the `staging` branch. It must only see non-breaking commits that are fixing issues blocking it from being merged into the `master` branch.
@ -241,7 +296,7 @@ If the branch is already in a broken state, please refrain from adding extra new
During the process of a releasing a new NixOS version, this branch or the release-critical packages can be restricted to non-breaking changes.
### Stable release branches {#submitting-changes-stable-release-branches}
#### Stable release branches {#submitting-changes-stable-release-branches}
The same staging workflow applies to stable release branches, but the main branch is called `release-*` instead of `master`.

View file

@ -3,6 +3,8 @@ let
inherit (pkgs) lib;
inherit (lib) hasPrefix removePrefix;
common = import ./common.nix;
lib-docs = import ./doc-support/lib-function-docs.nix {
inherit pkgs nixpkgs;
libsets = [
@ -132,15 +134,15 @@ in pkgs.stdenv.mkDerivation {
'';
installPhase = ''
dest="$out/share/doc/nixpkgs"
dest="$out/${common.outputPath}"
mkdir -p "$(dirname "$dest")"
mv out "$dest"
mv "$dest/index.html" "$dest/manual.html"
mv "$dest/index.html" "$dest/${common.indexPath}"
cp ${epub} "$dest/nixpkgs-manual.epub"
mkdir -p $out/nix-support/
echo "doc manual $dest manual.html" >> $out/nix-support/hydra-build-products
echo "doc manual $dest ${common.indexPath}" >> $out/nix-support/hydra-build-products
echo "doc manual $dest nixpkgs-manual.epub" >> $out/nix-support/hydra-build-products
'';
}

View file

@ -12,8 +12,11 @@ compatible are available as well. For example, there can be a
To use one or more CUDA packages in an expression, give the expression a `cudaPackages` parameter, and in case CUDA is optional
```nix
cudaSupport ? false
cudaPackages ? {}
{ config
, cudaSupport ? config.cudaSupport
, cudaPackages ? { }
, ...
}:
```
When using `callPackage`, you can choose to pass in a different variant, e.g.

View file

@ -20,7 +20,7 @@ In the following is an example expression using `buildGoModule`, the following a
To obtain the actual hash, set `vendorHash = lib.fakeSha256;` and run the build ([more details here](#sec-source-hashes)).
- `proxyVendor`: Fetches (go mod download) and proxies the vendor directory. This is useful if your code depends on c code and go mod tidy does not include the needed sources to build or if any dependency has case-insensitive conflicts which will produce platform-dependent `vendorHash` checksums.
- `modPostBuild`: Shell commands to run after the build of the go-modules executes `go mod vendor`, and before calculating fixed output derivation's `vendorHash` (or `vendorSha256`). Note that if you change this attribute, you need to update `vendorHash` (or `vendorSha256`) attribute.
- `modPostBuild`: Shell commands to run after the build of the goModules executes `go mod vendor`, and before calculating fixed output derivation's `vendorHash` (or `vendorSha256`). Note that if you change this attribute, you need to update `vendorHash` (or `vendorSha256`) attribute.
```nix
pet = buildGoModule rec {
@ -115,7 +115,7 @@ done
## Attributes used by the builders {#ssec-go-common-attributes}
Many attributes [controlling the build phase](#variables-controlling-the-build-phase) are respected by both `buildGoModule` and `buildGoPackage`. Note that `buildGoModule` reads the following attributes also when building the `vendor/` go-modules fixed output derivation as well:
Many attributes [controlling the build phase](#variables-controlling-the-build-phase) are respected by both `buildGoModule` and `buildGoPackage`. Note that `buildGoModule` reads the following attributes also when building the `vendor/` goModules fixed output derivation as well:
- [`sourceRoot`](#var-stdenv-sourceRoot)
- [`prePatch`](#var-stdenv-prePatch)

View file

@ -45,16 +45,17 @@ The attribute names in `haskellPackages` always correspond with their name on
Hackage. Since Hackage allows names that are not valid Nix without escaping,
you need to take care when handling attribute names like `3dmodels`.
For packages that are part of [Stackage], we use the version prescribed by a
Stackage solver (usually the current LTS one) as the default version. For all
other packages we use the latest version from Hackage. See
[below](#haskell-available-versions) to learn which versions are provided
exactly.
For packages that are part of [Stackage] (a curated set of known to be
compatible packages), we use the version prescribed by a Stackage snapshot
(usually the current LTS one) as the default version. For all other packages we
use the latest version from [Hackage](https://hackage.org) (the repository of
basically all open source Haskell packages). See [below](#haskell-available-
versions) for a few more details on this.
Roughly half of the 16K packages contained in `haskellPackages` don't actually
build and are marked as broken semi-automatically. Most of those packages are
deprecated or unmaintained, but sometimes packages that should build, do not
build. Very often fixing them is not a lot of work.
Roughly half of the 16K packages contained in `haskellPackages` dont actually
build and are [marked as broken semi-automatically](https://github.com/NixOS/nixpkgs/blob/haskell-updates/pkgs/development/haskell-modules/configuration-hackage2nix/broken.yaml).
Most of those packages are deprecated or unmaintained, but sometimes packages
that should build, do not build. Very often fixing them is not a lot of work.
<!--
TODO(@sternenseemann):
@ -126,19 +127,23 @@ Every package set also re-exposes the GHC used to build its packages as `haskell
### Available package versions {#haskell-available-versions}
We aim for a “blessed” package set which only contains one version of each
package, like Stackage (and based on it) but with more packages. Normally in
nixpkgs the number of building Haskell packages is roughly two to three times
the size of Stackage. For choosing the version to use for a certain package we
use the following rules:
package, like [Stackage], which is a curated set of known to be compatible
packages. We use the version information from Stackage snapshots and extend it
with more packages. Normally in Nixpkgs the number of building Haskell packages
is roughly two to three times the size of Stackage. For choosing the version to
use for a certain package we use the following rules:
1. By default, for every package `haskellPackages.foo` is the newest version
found on Hackage (at the time of the last update of our package set).
2. If the Stackage snapshot that we use (usually the newest LTS snapshot)
contains a package, we use the Stackage version as default version for that
package.
3. For some packages, which are not on Stackage, we have manual overrides to
set the default version to a version older than the newest on Hackage. We do
this to get them or their reverse dependencies to compile in our package set.
1. By default, for `haskellPackages.foo` is the newest version of the package
`foo` found on [Hackage](https://hackage.org), which is the central registry
of all open source Haskell packages. Nixpkgs contains a reference to a pinned
Hackage snapshot, thus we use the state of Hackage as of the last time we
updated this pin.
2. If the [Stackage] snapshot that we use (usually the newest LTS snapshot)
contains a package, [we use instead the version in the Stackage snapshot as
default version for that package.](https://github.com/NixOS/nixpkgs/blob/haskell-updates/pkgs/development/haskell-modules/configuration-hackage2nix/stackage.yaml)
3. For some packages, which are not on Stackage, we have if necessary [manual
overrides to set the default version to a version older than the newest on
Hackage.](https://github.com/NixOS/nixpkgs/blob/haskell-updates/pkgs/development/haskell-modules/configuration-hackage2nix/main.yaml)
4. For all packages, for which the newest Hackage version is not the default
version, there will also be a `haskellPackages.foo_x_y_z` package with the
newest version. The `x_y_z` part encodes the version with dots replaced by
@ -146,9 +151,12 @@ underscores. When the newest version changes by a new release to Hackage the
old package will disappear under that name and be replaced by a newer one under
the name with the new version. The package name including the version will
also disappear when the default version e.g. from Stackage catches up with the
newest version from Hackage.
5. For some packages, we also manually add other `haskellPackages.foo_x_y_z`
versions, if they are required for a certain build.
newest version from Hackage. E.g. if `haskellPackages.foo` gets updated from
1.0.0 to 1.1.0 the package `haskellPackages.foo_1_1_0` becomes obsolete and
gets dropped.
5. For some packages, we also [manually add other `haskellPackages.foo_x_y_z`
versions](https://github.com/NixOS/nixpkgs/blob/haskell-updates/pkgs/development/haskell-modules/configuration-hackage2nix/main.yaml),
if they are required for a certain build.
Relying on `haskellPackages.foo_x_y_z` attributes in derivations outside
nixpkgs is discouraged because they may change or disappear with every package

View file

@ -1514,11 +1514,11 @@ Note: There is a boolean value `lib.inNixShell` set to `true` if nix-shell is in
### Tools {#tools}
Packages inside nixpkgs are written by hand. However many tools exist in
community to help save time. No tool is preferred at the moment.
Packages inside nixpkgs must use the `buildPythonPackage` or `buildPythonApplication` function directly,
because we can only provide security support for non-vendored dependencies.
- [nixpkgs-pytools](https://github.com/nix-community/nixpkgs-pytools)
- [poetry2nix](https://github.com/nix-community/poetry2nix)
We recommend [nix-init](https://github.com/nix-community/nix-init) for creating new python packages within nixpkgs,
as it already prefetches the source, parses dependencies for common formats and prefills most things in `meta`.
### Deterministic builds {#deterministic-builds}

View file

@ -39,7 +39,7 @@ rustPlatform.buildRustPackage rec {
description = "A fast line-oriented regex search tool, similar to ag and ack";
homepage = "https://github.com/BurntSushi/ripgrep";
license = licenses.unlicense;
maintainers = [ maintainers.tailhook ];
maintainers = [];
};
}
```
@ -926,7 +926,7 @@ rustPlatform.buildRustPackage rec {
description = "A fast line-oriented regex search tool, similar to ag and ack";
homepage = "https://github.com/BurntSushi/ripgrep";
license = with licenses; [ mit unlicense ];
maintainers = with maintainers; [ tailhook ];
maintainers = with maintainers; [];
};
}
```

20
doc/shell.nix Normal file
View file

@ -0,0 +1,20 @@
let
pkgs = import ../. {
config = {};
overlays = [];
};
common = import ./common.nix;
inherit (common) outputPath indexPath;
web-devmode = import ../pkgs/tools/nix/web-devmode.nix {
inherit pkgs;
buildArgs = "./.";
open = "/${outputPath}/${indexPath}";
};
in
pkgs.mkShell {
packages = [
web-devmode
];
}

73
lib/README.md Normal file
View file

@ -0,0 +1,73 @@
# Nixpkgs lib
This directory contains the implementation, documentation and tests for the Nixpkgs `lib` library.
## Overview
The evaluation entry point for `lib` is [`default.nix`](default.nix).
This file evaluates to an attribute set containing two separate kinds of attributes:
- Sub-libraries:
Attribute sets grouping together similar functionality.
Each sub-library is defined in a separate file usually matching its attribute name.
Example: `lib.lists` is a sub-library containing list-related functionality such as `lib.lists.take` and `lib.lists.imap0`.
These are defined in the file [`lists.nix`](lists.nix).
- Aliases:
Attributes that point to an attribute of the same name in some sub-library.
Example: `lib.take` is an alias for `lib.lists.take`.
Most files in this directory are definitions of sub-libraries, but there are a few others:
- [`minver.nix`](minver.nix): A string of the minimum version of Nix that is required to evaluate Nixpkgs.
- [`tests`](tests): Tests, see [Running tests](#running-tests)
- [`release.nix`](tests/release.nix): A derivation aggregating all tests
- [`misc.nix`](tests/misc.nix): Evaluation unit tests for most sub-libraries
- `*.sh`: Bash scripts that run tests for specific sub-libraries
- All other files in this directory exist to support the tests
- [`systems`](systems): The `lib.systems` sub-library, structured into a directory instead of a file due to its complexity
- [`path`](path): The `lib.path` sub-library, which includes tests as well as a document describing the design goals of `lib.path`
- All other files in this directory are sub-libraries
### Module system
The [module system](https://nixos.org/manual/nixpkgs/#module-system) spans multiple sub-libraries:
- [`modules.nix`](modules.nix): `lib.modules` for the core functions and anything not relating to option definitions
- [`options.nix`](options.nix): `lib.options` for anything relating to option definitions
- [`types.nix`](types.nix): `lib.types` for module system types
## Reference documentation
Reference documentation for library functions is written above each function as a multi-line comment.
These comments are processed using [nixdoc](https://github.com/nix-community/nixdoc) and [rendered in the Nixpkgs manual](https://nixos.org/manual/nixpkgs/stable/#chap-functions).
The nixdoc README describes the [comment format](https://github.com/nix-community/nixdoc#comment-format).
See the [chapter on contributing to the Nixpkgs manual](https://nixos.org/manual/nixpkgs/#chap-contributing) for how to build the manual.
## Running tests
All library tests can be run by building the derivation in [`tests/release.nix`](tests/release.nix):
```bash
nix-build tests/release.nix
```
Some commands for quicker iteration over parts of the test suite are also available:
```bash
# Run all evaluation unit tests in tests/misc.nix
# if the resulting list is empty, all tests passed
nix-instantiate --eval --strict tests/misc.nix
# Run the module system tests
tests/modules.sh
# Run the lib.sources tests
tests/sources.sh
# Run the lib.filesystem tests
tests/filesystem.sh
# Run the lib.path property tests
path/tests/prop.sh
```

View file

@ -738,6 +738,42 @@ rec {
sets:
zipAttrsWith (name: values: values) sets;
/*
Merge a list of attribute sets together using the `//` operator.
In case of duplicate attributes, values from later list elements take precedence over earlier ones.
The result is the same as `foldl mergeAttrs { }`, but the performance is better for large inputs.
For n list elements, each with an attribute set containing m unique attributes, the complexity of this operation is O(nm log n).
Type:
mergeAttrsList :: [ Attrs ] -> Attrs
Example:
mergeAttrsList [ { a = 0; b = 1; } { c = 2; d = 3; } ]
=> { a = 0; b = 1; c = 2; d = 3; }
mergeAttrsList [ { a = 0; } { a = 1; } ]
=> { a = 1; }
*/
mergeAttrsList = list:
let
# `binaryMerge start end` merges the elements at indices `index` of `list` such that `start <= index < end`
# Type: Int -> Int -> Attrs
binaryMerge = start: end:
# assert start < end; # Invariant
if end - start >= 2 then
# If there's at least 2 elements, split the range in two, recurse on each part and merge the result
# The invariant is satisfied because each half will have at least 1 element
binaryMerge start (start + (end - start) / 2)
// binaryMerge (start + (end - start) / 2) end
else
# Otherwise there will be exactly 1 element due to the invariant, in which case we just return it directly
elemAt list start;
in
if list == [ ] then
# Calling binaryMerge as below would not satisfy its invariant
{ }
else
binaryMerge 0 (length list);
/* Does the same as the update operator '//' except that attributes are
merged until the given predicate is verified. The predicate should

View file

@ -81,9 +81,10 @@ rec {
*/
toKeyValue = {
mkKeyValue ? mkKeyValueDefault {} "=",
listsAsDuplicateKeys ? false
listsAsDuplicateKeys ? false,
indent ? ""
}:
let mkLine = k: v: mkKeyValue k v + "\n";
let mkLine = k: v: indent + mkKeyValue k v + "\n";
mkLines = if listsAsDuplicateKeys
then k: v: map (mkLine k) (if lib.isList v then v else [v])
else k: v: [ (mkLine k v) ];

View file

@ -657,6 +657,13 @@ in mkLicense lset) ({
redistributable = true;
};
hl3 = {
fullName = "Hippocratic License v3.0";
url = "https://firstdonoharm.dev/version/3/0/core.txt";
free = false;
redistributable = true;
};
issl = {
fullName = "Intel Simplified Software License";
url = "https://software.intel.com/en-us/license/intel-simplified-software-license";

View file

@ -3,7 +3,7 @@
{ lib }:
let
inherit (lib.strings) toInt;
inherit (lib.trivial) compare min;
inherit (lib.trivial) compare min id;
inherit (lib.attrsets) mapAttrs;
in
rec {
@ -180,18 +180,18 @@ rec {
else if len != 1 then multiple
else head found;
/* Find the first element in the list matching the specified
/* Find the first index in the list matching the specified
predicate or return `default` if no such element exists.
Type: findFirst :: (a -> bool) -> a -> [a] -> a
Type: findFirstIndex :: (a -> Bool) -> b -> [a] -> (Int | b)
Example:
findFirst (x: x > 3) 7 [ 1 6 4 ]
=> 6
findFirst (x: x > 9) 7 [ 1 6 4 ]
=> 7
findFirstIndex (x: x > 3) null [ 0 6 4 ]
=> 1
findFirstIndex (x: x > 9) null [ 0 6 4 ]
=> null
*/
findFirst =
findFirstIndex =
# Predicate
pred:
# Default value to return
@ -229,7 +229,33 @@ rec {
if resultIndex < 0 then
default
else
elemAt list resultIndex;
resultIndex;
/* Find the first element in the list matching the specified
predicate or return `default` if no such element exists.
Type: findFirst :: (a -> bool) -> a -> [a] -> a
Example:
findFirst (x: x > 3) 7 [ 1 6 4 ]
=> 6
findFirst (x: x > 9) 7 [ 1 6 4 ]
=> 7
*/
findFirst =
# Predicate
pred:
# Default value to return
default:
# Input list
list:
let
index = findFirstIndex pred null list;
in
if index == null then
default
else
elemAt list index;
/* Return true if function `pred` returns true for at least one
element of `list`.
@ -637,6 +663,32 @@ rec {
else if start + count > len then len - start
else count);
/* The common prefix of two lists.
Type: commonPrefix :: [a] -> [a] -> [a]
Example:
commonPrefix [ 1 2 3 4 5 6 ] [ 1 2 4 8 ]
=> [ 1 2 ]
commonPrefix [ 1 2 3 ] [ 1 2 3 4 5 ]
=> [ 1 2 3 ]
commonPrefix [ 1 2 3 ] [ 4 5 6 ]
=> [ ]
*/
commonPrefix =
list1:
list2:
let
# Zip the lists together into a list of booleans whether each element matches
matchings = zipListsWith (fst: snd: fst != snd) list1 list2;
# Find the first index where the elements don't match,
# which will then also be the length of the common prefix.
# If all elements match, we fall back to the length of the zipped list,
# which is the same as the length of the smaller list.
commonPrefixLength = findFirstIndex id (length matchings) matchings;
in
take commonPrefixLength list1;
/* Return the last element of a list.
This function throws an error if the list is empty.

View file

@ -539,59 +539,74 @@ let
mergeModules' = prefix: options: configs:
let
/* byName is like foldAttrs, but will look for attributes to merge in the
specified attribute name.
byName "foo" (module: value: ["module.hidden=${module.hidden},value=${value}"])
[
{
hidden="baz";
foo={qux="bar"; gla="flop";};
}
{
hidden="fli";
foo={qux="gne"; gli="flip";};
}
]
===>
{
gla = [ "module.hidden=baz,value=flop" ];
gli = [ "module.hidden=fli,value=flip" ];
qux = [ "module.hidden=baz,value=bar" "module.hidden=fli,value=gne" ];
}
*/
byName = attr: f: modules:
zipAttrsWith (n: concatLists)
(map (module: let subtree = module.${attr}; in
# an attrset 'name' => list of submodules that declare name.
declsByName =
zipAttrsWith
(n: concatLists)
(map
(module: let subtree = module.options; in
if !(builtins.isAttrs subtree) then
throw (if attr == "config" then ''
You're trying to define a value of type `${builtins.typeOf subtree}'
rather than an attribute set for the option
`${builtins.concatStringsSep "." prefix}'!
This usually happens if `${builtins.concatStringsSep "." prefix}' has option
definitions inside that are not matched. Please check how to properly define
this option by e.g. referring to `man 5 configuration.nix'!
'' else ''
throw ''
An option declaration for `${builtins.concatStringsSep "." prefix}' has type
`${builtins.typeOf subtree}' rather than an attribute set.
Did you mean to define this outside of `options'?
'')
''
else
mapAttrs (n: f module) subtree
) modules);
# an attrset 'name' => list of submodules that declare name.
declsByName = byName "options" (module: option:
[{ inherit (module) _file; options = option; }]
) options;
mapAttrs
(n: option:
[{ inherit (module) _file; options = option; }]
)
subtree
)
options);
# The root of any module definition must be an attrset.
checkedConfigs =
assert
lib.all
(c:
# TODO: I have my doubts that this error would occur when option definitions are not matched.
# The implementation of this check used to be tied to a superficially similar check for
# options, so maybe that's why this is here.
isAttrs c.config || throw ''
In module `${c.file}', you're trying to define a value of type `${builtins.typeOf c.config}'
rather than an attribute set for the option
`${builtins.concatStringsSep "." prefix}'!
This usually happens if `${builtins.concatStringsSep "." prefix}' has option
definitions inside that are not matched. Please check how to properly define
this option by e.g. referring to `man 5 configuration.nix'!
''
)
configs;
configs;
# an attrset 'name' => list of submodules that define name.
defnsByName = byName "config" (module: value:
map (config: { inherit (module) file; inherit config; }) (pushDownProperties value)
) configs;
pushedDownDefinitionsByName =
zipAttrsWith
(n: concatLists)
(map
(module:
mapAttrs
(n: value:
map (config: { inherit (module) file; inherit config; }) (pushDownProperties value)
)
module.config
)
checkedConfigs);
# extract the definitions for each loc
defnsByName' = byName "config" (module: value:
[{ inherit (module) file; inherit value; }]
) configs;
rawDefinitionsByName =
zipAttrsWith
(n: concatLists)
(map
(module:
mapAttrs
(n: value:
[{ inherit (module) file; inherit value; }]
)
module.config
)
checkedConfigs);
# Convert an option tree decl to a submodule option decl
optionTreeToOption = decl:
@ -613,8 +628,8 @@ let
# We're descending into attribute name.
let
loc = prefix ++ [name];
defns = defnsByName.${name} or [];
defns' = defnsByName'.${name} or [];
defns = pushedDownDefinitionsByName.${name} or [];
defns' = rawDefinitionsByName.${name} or [];
optionDecls = filter (m: isOption m.options) decls;
in
if length optionDecls == length decls then
@ -624,7 +639,7 @@ let
unmatchedDefns = [];
}
else if optionDecls != [] then
if all (x: x.options.type.name == "submodule") optionDecls
if all (x: x.options.type.name or null == "submodule") optionDecls
# Raw options can only be merged into submodules. Merging into
# attrsets might be nice, but ambiguous. Suppose we have
# attrset as a `attrsOf submodule`. User declares option
@ -657,7 +672,7 @@ let
# Propagate all unmatched definitions from nested option sets
mapAttrs (n: v: v.unmatchedDefns) resultsByName
# Plus the definitions for the current prefix that don't have a matching option
// removeAttrs defnsByName' (attrNames matchedOptions);
// removeAttrs rawDefinitionsByName (attrNames matchedOptions);
in {
inherit matchedOptions;

View file

@ -20,6 +20,7 @@ let
concatMap
foldl'
take
drop
;
inherit (lib.strings)
@ -217,9 +218,110 @@ in /* No rec! Add dependencies on this file at the top. */ {
second argument: "${toString path2}" with root "${toString path2Deconstructed.root}"'';
take (length path1Deconstructed.components) path2Deconstructed.components == path1Deconstructed.components;
/*
Remove the first path as a component-wise prefix from the second path.
The result is a normalised subpath string, see `lib.path.subpath.normalise`.
Laws:
- Inverts `append` for normalised subpaths:
removePrefix p (append p s) == subpath.normalise s
Type:
removePrefix :: Path -> Path -> String
Example:
removePrefix /foo /foo/bar/baz
=> "./bar/baz"
removePrefix /foo /foo
=> "./."
removePrefix /foo/bar /foo
=> <error>
removePrefix /. /foo
=> "./foo"
*/
removePrefix =
path1:
assert assertMsg
(isPath path1)
"lib.path.removePrefix: First argument is of type ${typeOf path1}, but a path was expected.";
let
path1Deconstructed = deconstructPath path1;
path1Length = length path1Deconstructed.components;
in
path2:
assert assertMsg
(isPath path2)
"lib.path.removePrefix: Second argument is of type ${typeOf path2}, but a path was expected.";
let
path2Deconstructed = deconstructPath path2;
success = take path1Length path2Deconstructed.components == path1Deconstructed.components;
components =
if success then
drop path1Length path2Deconstructed.components
else
throw ''
lib.path.removePrefix: The first path argument "${toString path1}" is not a component-wise prefix of the second path argument "${toString path2}".'';
in
assert assertMsg
(path1Deconstructed.root == path2Deconstructed.root) ''
lib.path.removePrefix: Filesystem roots must be the same for both paths, but paths with different roots were given:
first argument: "${toString path1}" with root "${toString path1Deconstructed.root}"
second argument: "${toString path2}" with root "${toString path2Deconstructed.root}"'';
joinRelPath components;
/*
Split the filesystem root from a [path](https://nixos.org/manual/nix/stable/language/values.html#type-path).
The result is an attribute set with these attributes:
- `root`: The filesystem root of the path, meaning that this directory has no parent directory.
- `subpath`: The [normalised subpath string](#function-library-lib.path.subpath.normalise) that when [appended](#function-library-lib.path.append) to `root` returns the original path.
Laws:
- [Appending](#function-library-lib.path.append) the `root` and `subpath` gives the original path:
p ==
append
(splitRoot p).root
(splitRoot p).subpath
- Trying to get the parent directory of `root` using [`readDir`](https://nixos.org/manual/nix/stable/language/builtins.html#builtins-readDir) returns `root` itself:
dirOf (splitRoot p).root == (splitRoot p).root
Type:
splitRoot :: Path -> { root :: Path, subpath :: String }
Example:
splitRoot /foo/bar
=> { root = /.; subpath = "./foo/bar"; }
splitRoot /.
=> { root = /.; subpath = "./."; }
# Nix neutralises `..` path components for all path values automatically
splitRoot /foo/../bar
=> { root = /.; subpath = "./bar"; }
splitRoot "/foo/bar"
=> <error>
*/
splitRoot = path:
assert assertMsg
(isPath path)
"lib.path.splitRoot: Argument is of type ${typeOf path}, but a path was expected";
let
deconstructed = deconstructPath path;
in {
root = deconstructed.root;
subpath = joinRelPath deconstructed.components;
};
/* Whether a value is a valid subpath string.
A subpath string points to a specific file or directory within an absolute base directory.
It is a stricter form of a relative path that excludes `..` components, since those could escape the base directory.
- The value is a string
- The string is not empty

View file

@ -1,9 +1,12 @@
#!/usr/bin/env bash
# Property tests for the `lib.path` library
#
# Property tests for lib/path/default.nix
# It generates random path-like strings and runs the functions on
# them, checking that the expected laws of the functions hold
# Run:
# [nixpkgs]$ lib/path/tests/prop.sh
# or:
# [nixpkgs]$ nix-build lib/tests/release.nix
set -euo pipefail
shopt -s inherit_errexit

View file

@ -3,7 +3,7 @@
{ libpath }:
let
lib = import libpath;
inherit (lib.path) hasPrefix append subpath;
inherit (lib.path) hasPrefix removePrefix append splitRoot subpath;
cases = lib.runTests {
# Test examples from the lib.path.append documentation
@ -57,6 +57,40 @@ let
expected = true;
};
testRemovePrefixExample1 = {
expr = removePrefix /foo /foo/bar/baz;
expected = "./bar/baz";
};
testRemovePrefixExample2 = {
expr = removePrefix /foo /foo;
expected = "./.";
};
testRemovePrefixExample3 = {
expr = (builtins.tryEval (removePrefix /foo/bar /foo)).success;
expected = false;
};
testRemovePrefixExample4 = {
expr = removePrefix /. /foo;
expected = "./foo";
};
testSplitRootExample1 = {
expr = splitRoot /foo/bar;
expected = { root = /.; subpath = "./foo/bar"; };
};
testSplitRootExample2 = {
expr = splitRoot /.;
expected = { root = /.; subpath = "./."; };
};
testSplitRootExample3 = {
expr = splitRoot /foo/../bar;
expected = { root = /.; subpath = "./bar"; };
};
testSplitRootExample4 = {
expr = (builtins.tryEval (splitRoot "/foo/bar")).success;
expected = false;
};
# Test examples from the lib.path.subpath.isValid documentation
testSubpathIsValidExample1 = {
expr = subpath.isValid null;

View file

@ -85,17 +85,18 @@ rec {
# is why we use the more obscure "bfd" and not "binutils" for this
# choice.
else "bfd";
extensions = rec {
extensions = lib.optionalAttrs final.hasSharedLibraries {
sharedLibrary =
/**/ if final.isDarwin then ".dylib"
if final.isDarwin then ".dylib"
else if final.isWindows then ".dll"
else ".so";
} // {
staticLibrary =
/**/ if final.isWindows then ".lib"
else ".a";
library =
/**/ if final.isStatic then staticLibrary
else sharedLibrary;
/**/ if final.isStatic then final.extensions.staticLibrary
else final.extensions.sharedLibrary;
executable =
/**/ if final.isWindows then ".exe"
else "";
@ -132,6 +133,25 @@ rec {
# uname -r
release = null;
};
# It is important that hasSharedLibraries==false when the platform has no
# dynamic library loader. Various tools (including the gcc build system)
# have knowledge of which platforms are incapable of dynamic linking, and
# will still build on/for those platforms with --enable-shared, but simply
# omit any `.so` build products such as libgcc_s.so. When that happens,
# it causes hard-to-troubleshoot build failures.
hasSharedLibraries = with final;
(isAndroid || isGnu || isMusl # Linux (allows multiple libcs)
|| isDarwin || isSunOS || isOpenBSD || isFreeBSD || isNetBSD # BSDs
|| isCygwin || isMinGW # Windows
) && !isStatic;
# The difference between `isStatic` and `hasSharedLibraries` is mainly the
# addition of the `staticMarker` (see make-derivation.nix). Some
# platforms, like embedded machines without a libc (e.g. arm-none-eabi)
# don't support dynamic linking, but don't get the `staticMarker`.
# `pkgsStatic` sets `isStatic=true`, so `pkgsStatic.hostPlatform` always
# has the `staticMarker`.
isStatic = final.isWasm || final.isRedox;
# Just a guess, based on `system`

View file

@ -37,6 +37,10 @@ rec {
config = "armv6l-unknown-linux-gnueabihf";
} // platforms.raspberrypi;
bluefield2 = {
config = "aarch64-unknown-linux-gnu";
} // platforms.bluefield2;
remarkable1 = {
config = "armv7l-unknown-linux-gnueabihf";
} // platforms.zero-gravitas;

View file

@ -209,6 +209,13 @@ rec {
# Legacy attribute, for compatibility with existing configs only.
raspberrypi2 = armv7l-hf-multiplatform;
# Nvidia Bluefield 2 (w. crypto support)
bluefield2 = {
gcc = {
arch = "armv8-a+fp+simd+crc+crypto";
};
};
zero-gravitas = {
linux-kernel = {
name = "zero-gravitas";

View file

@ -1,6 +1,18 @@
# to run these tests:
# nix-instantiate --eval --strict nixpkgs/lib/tests/misc.nix
# if the resulting list is empty, all tests passed
/*
Nix evaluation tests for various lib functions.
Since these tests are implemented with Nix evaluation, error checking is limited to what `builtins.tryEval` can detect, which is `throw`'s and `abort`'s, without error messages.
If you need to test error messages or more complex evaluations, see ./modules.sh, ./sources.sh or ./filesystem.sh as examples.
To run these tests:
[nixpkgs]$ nix-instantiate --eval --strict lib/tests/misc.nix
If the resulting list is empty, all tests passed.
Alternatively, to run all `lib` tests:
[nixpkgs]$ nix-build lib/tests/release.nix
*/
with import ../default.nix;
let
@ -488,6 +500,39 @@ runTests {
expected = { a = [ 2 3 ]; b = [7]; c = [8];};
};
testListCommonPrefixExample1 = {
expr = lists.commonPrefix [ 1 2 3 4 5 6 ] [ 1 2 4 8 ];
expected = [ 1 2 ];
};
testListCommonPrefixExample2 = {
expr = lists.commonPrefix [ 1 2 3 ] [ 1 2 3 4 5 ];
expected = [ 1 2 3 ];
};
testListCommonPrefixExample3 = {
expr = lists.commonPrefix [ 1 2 3 ] [ 4 5 6 ];
expected = [ ];
};
testListCommonPrefixEmpty = {
expr = lists.commonPrefix [ ] [ 1 2 3 ];
expected = [ ];
};
testListCommonPrefixSame = {
expr = lists.commonPrefix [ 1 2 3 ] [ 1 2 3 ];
expected = [ 1 2 3 ];
};
testListCommonPrefixLazy = {
expr = lists.commonPrefix [ 1 ] [ 1 (abort "lib.lists.commonPrefix shouldn't evaluate this")];
expected = [ 1 ];
};
# This would stack overflow if `commonPrefix` were implemented using recursion
testListCommonPrefixLong =
let
longList = genList (n: n) 100000;
in {
expr = lists.commonPrefix longList longList;
expected = longList;
};
testSort = {
expr = sort builtins.lessThan [ 40 2 30 42 ];
expected = [2 30 40 42];
@ -518,45 +563,55 @@ runTests {
expected = false;
};
testFindFirstExample1 = {
expr = findFirst (x: x > 3) 7 [ 1 6 4 ];
expected = 6;
testFindFirstIndexExample1 = {
expr = lists.findFirstIndex (x: x > 3) (abort "index found, so a default must not be evaluated") [ 1 6 4 ];
expected = 1;
};
testFindFirstExample2 = {
expr = findFirst (x: x > 9) 7 [ 1 6 4 ];
expected = 7;
testFindFirstIndexExample2 = {
expr = lists.findFirstIndex (x: x > 9) "a very specific default" [ 1 6 4 ];
expected = "a very specific default";
};
testFindFirstEmpty = {
expr = findFirst (abort "when the list is empty, the predicate is not needed") null [];
testFindFirstIndexEmpty = {
expr = lists.findFirstIndex (abort "when the list is empty, the predicate is not needed") null [];
expected = null;
};
testFindFirstSingleMatch = {
expr = findFirst (x: x == 5) null [ 5 ];
expected = 5;
testFindFirstIndexSingleMatch = {
expr = lists.findFirstIndex (x: x == 5) null [ 5 ];
expected = 0;
};
testFindFirstSingleDefault = {
expr = findFirst (x: false) null [ (abort "if the predicate doesn't access the value, it must not be evaluated") ];
testFindFirstIndexSingleDefault = {
expr = lists.findFirstIndex (x: false) null [ (abort "if the predicate doesn't access the value, it must not be evaluated") ];
expected = null;
};
testFindFirstNone = {
expr = builtins.tryEval (findFirst (x: x == 2) null [ 1 (throw "the last element must be evaluated when there's no match") ]);
testFindFirstIndexNone = {
expr = builtins.tryEval (lists.findFirstIndex (x: x == 2) null [ 1 (throw "the last element must be evaluated when there's no match") ]);
expected = { success = false; value = false; };
};
# Makes sure that the implementation doesn't cause a stack overflow
testFindFirstBig = {
expr = findFirst (x: x == 1000000) null (range 0 1000000);
testFindFirstIndexBig = {
expr = lists.findFirstIndex (x: x == 1000000) null (range 0 1000000);
expected = 1000000;
};
testFindFirstLazy = {
expr = findFirst (x: x == 1) 7 [ 1 (abort "list elements after the match must not be evaluated") ];
expected = 1;
testFindFirstIndexLazy = {
expr = lists.findFirstIndex (x: x == 1) null [ 1 (abort "list elements after the match must not be evaluated") ];
expected = 0;
};
testFindFirstExample1 = {
expr = lists.findFirst (x: x > 3) 7 [ 1 6 4 ];
expected = 6;
};
testFindFirstExample2 = {
expr = lists.findFirst (x: x > 9) 7 [ 1 6 4 ];
expected = 7;
};
# ATTRSETS
@ -609,6 +664,31 @@ runTests {
};
};
testMergeAttrsListExample1 = {
expr = attrsets.mergeAttrsList [ { a = 0; b = 1; } { c = 2; d = 3; } ];
expected = { a = 0; b = 1; c = 2; d = 3; };
};
testMergeAttrsListExample2 = {
expr = attrsets.mergeAttrsList [ { a = 0; } { a = 1; } ];
expected = { a = 1; };
};
testMergeAttrsListExampleMany =
let
list = genList (n:
listToAttrs (genList (m:
let
# Integer divide n by two to create duplicate attributes
str = "halfn${toString (n / 2)}m${toString m}";
in
nameValuePair str str
) 100)
) 100;
in {
expr = attrsets.mergeAttrsList list;
expected = foldl' mergeAttrs { } list;
};
# code from the example
testRecursiveUpdateUntil = {
expr = recursiveUpdateUntil (path: l: r: path == ["foo"]) {

View file

@ -1,7 +1,13 @@
#!/usr/bin/env bash
#
# This script is used to test that the module system is working as expected.
# Executing it runs tests for `lib.modules`, `lib.options` and `lib.types`.
# By default it test the version of nixpkgs which is defined in the NIX_PATH.
#
# Run:
# [nixpkgs]$ lib/tests/modules.sh
# or:
# [nixpkgs]$ nix-build lib/tests/release.nix
set -o errexit -o noclobber -o nounset -o pipefail
shopt -s failglob inherit_errexit
@ -207,7 +213,7 @@ checkConfigOutput '^"foo"$' config.submodule.foo ./declare-submoduleWith-special
## shorthandOnlyDefines config behaves as expected
checkConfigOutput '^true$' config.submodule.config ./declare-submoduleWith-shorthand.nix ./define-submoduleWith-shorthand.nix
checkConfigError 'is not of type `boolean' config.submodule.config ./declare-submoduleWith-shorthand.nix ./define-submoduleWith-noshorthand.nix
checkConfigError "You're trying to define a value of type \`bool'\n\s*rather than an attribute set for the option" config.submodule.config ./declare-submoduleWith-noshorthand.nix ./define-submoduleWith-shorthand.nix
checkConfigError "In module ..*define-submoduleWith-shorthand.nix., you're trying to define a value of type \`bool'\n\s*rather than an attribute set for the option" config.submodule.config ./declare-submoduleWith-noshorthand.nix ./define-submoduleWith-shorthand.nix
checkConfigOutput '^true$' config.submodule.config ./declare-submoduleWith-noshorthand.nix ./define-submoduleWith-noshorthand.nix
## submoduleWith should merge all modules in one swoop
@ -365,6 +371,9 @@ checkConfigError \
config.set \
./declare-set.nix ./declare-enable-nested.nix
# Check that that merging of option collisions doesn't depend on type being set
checkConfigError 'The option .group..*would be a parent of the following options, but its type .<no description>. does not support nested options.\n\s*- option.s. with prefix .group.enable..*' config.group.enable ./merge-typeless-option.nix
# Test that types.optionType merges types correctly
checkConfigOutput '^10$' config.theOption.int ./optionTypeMerging.nix
checkConfigOutput '^"hello"$' config.theOption.str ./optionTypeMerging.nix

View file

@ -0,0 +1,25 @@
{ lib, ... }:
let
typeless =
{ lib, ... }:
{
options.group = lib.mkOption { };
};
childOfTypeless =
{ lib, ... }:
{
options.group.enable = lib.mkEnableOption "nothing";
};
in
{
imports = [
typeless
childOfTypeless
];
config.group.enable = false;
}

View file

@ -1,4 +1,11 @@
#!/usr/bin/env bash
# Tests lib/sources.nix
# Run:
# [nixpkgs]$ lib/tests/sources.sh
# or:
# [nixpkgs]$ nix-build lib/tests/release.nix
set -euo pipefail
shopt -s inherit_errexit

View file

@ -581,6 +581,12 @@
githubId = 1318982;
name = "Anders Claesson";
};
akechishiro = {
email = "akechishiro-aur+nixpkgs@lahfa.xyz";
github = "AkechiShiro";
githubId = 14914796;
name = "Samy Lahfa";
};
a-kenji = {
email = "aks.kenji@protonmail.com";
github = "a-kenji";
@ -1231,6 +1237,18 @@
githubId = 914687;
name = "Alexis Praga";
};
aprl = {
email = "aprl@acab.dev";
github = "cutestnekoaqua";
githubId = 30842467;
name = "April John";
};
aqrln = {
email = "nix@aqrln.net";
github = "aqrln";
githubId = 4923335;
name = "Alexey Orlenko";
};
ar1a = {
email = "aria@ar1as.space";
github = "ar1a";
@ -1383,6 +1401,12 @@
githubId = 37193992;
name = "Arthur Teisseire";
};
arti5an = {
email = "artis4n@outlook.com";
github = "arti5an";
githubId = 14922630;
name = "Richard Smith";
};
artturin = {
email = "artturin@artturin.com";
matrix = "@artturin:matrix.org";
@ -1493,6 +1517,13 @@
fingerprint = "DD52 6BC7 767D BA28 16C0 95E5 6840 89CE 67EB B691";
}];
};
atalii = {
email = "taliauster@gmail.com";
github = "atalii";
githubId = 120901234;
name = "tali auster";
matrix = "@atalii:matrix.org";
};
ataraxiasjel = {
email = "nix@ataraxiadev.com";
github = "AtaraxiaSjel";
@ -1685,6 +1716,13 @@
fingerprint = "2688 0377 C31D 9E81 9BDF 83A8 C8C6 BDDB 3847 F72B";
}];
};
azazak123 = {
email = "azazaka2002@gmail.com";
matrix = "@ne_dvoeshnik:matrix.org";
name = "Volodymyr Antonov";
github = "azazak123";
githubId = 50211158;
};
azd325 = {
email = "tim.kleinschmidt@gmail.com";
github = "Azd325";
@ -1724,6 +1762,12 @@
fingerprint = "6FBC A462 4EAF C69C A7C4 98C1 F044 3098 48A0 7CAC";
}];
};
babeuh = {
name = "Raphael Le Goaller";
email = "babeuh@rlglr.fr";
github = "babeuh";
githubId = 60193302;
};
bachp = {
email = "pascal.bach@nextrem.ch";
matrix = "@bachp:matrix.org";
@ -2561,6 +2605,12 @@
}
];
};
CaitlinDavitt = {
email = "CaitlinDavitt@gmail.com";
github = "CaitlinDavitt";
githubId = 48105979;
name = "Caitlin Davitt";
};
calavera = {
email = "david.calavera@gmail.com";
github = "calavera";
@ -2753,6 +2803,13 @@
githubId = 64804;
name = "Dennis Gosnell";
};
cdmistman = {
name = "Colton Donnelly";
email = "colton@donn.io";
matrix = "@donnellycolton:matrix.org";
github = "cdmistman";
githubId = 23486351;
};
ceedubs = {
email = "ceedubs@gmail.com";
github = "ceedubs";
@ -2845,6 +2902,13 @@
githubId = 6608071;
name = "Charles Huyghues-Despointes";
};
chayleaf = {
email = "chayleaf-nix@pavluk.org";
github = "chayleaf";
githubId = 9590981;
matrix = "@chayleaf:matrix.pavluk.org";
name = "Anna Pavlyuk";
};
chekoopa = {
email = "chekoopa@mail.ru";
github = "chekoopa";
@ -2896,6 +2960,12 @@
githubId = 14790226;
name = "Hubert Jasudowicz";
};
c-h-johnson = {
name = "Charles Johnson";
email = "charles@charlesjohnson.name";
github = "c-h-johnson";
githubId = 138403247;
};
chkno = {
email = "scottworley@scottworley.com";
github = "chkno";
@ -4083,6 +4153,12 @@
fingerprint = "1C4E F4FE 7F8E D8B7 1E88 CCDF BAB1 D15F B7B4 D4CE";
}];
};
dgollings = {
email = "daniel.gollings+nixpkgs@gmail.com";
github = "dgollings";
githubId = 2032823;
name = "Daniel Gollings";
};
dgonyeo = {
email = "derek@gonyeo.com";
github = "dgonyeo";
@ -4284,6 +4360,12 @@
githubId = 10998835;
name = "Doron Behar";
};
dotemup = {
email = "dotemup.designs+nixpkgs@gmail.com";
github = "dotemup";
githubId = 11077277;
name = "Dote";
};
dotlambda = {
email = "rschuetz17@gmail.com";
matrix = "@robert:funklause.de";
@ -5171,6 +5253,12 @@
fingerprint = "FC1D 3E4F CBCA 80DF E870 6397 C811 6E3A 0C1C A76A";
}];
};
exploitoverload = {
email = "nix@exploitoverload.com";
github = "exploitoverload";
githubId = 99678549;
name = "Asier Armenteros";
};
extends = {
email = "sharosari@gmail.com";
github = "ImExtends";
@ -5281,6 +5369,12 @@
githubId = 4246921;
name = "Florian Beeres";
};
fd = {
email = "simon.menke@gmail.com";
github = "fd";
githubId = 591;
name = "Simon Menke";
};
fdns = {
email = "fdns02@gmail.com";
github = "fdns";
@ -5570,6 +5664,12 @@
githubId = 84968;
name = "Florian Paul Schmidt";
};
fptje = {
email = "fpeijnenburg@gmail.com";
github = "FPtje";
githubId = 1202014;
name = "Falco Peijnenburg";
};
fragamus = {
email = "innovative.engineer@gmail.com";
github = "fragamus";
@ -5611,6 +5711,12 @@
githubId = 609279;
name = "Isaac Shapira";
};
freyacodes = {
email = "freya@arbjerg.dev";
github = "freyacodes";
githubId = 2582617;
name = "Freya Arbjerg";
};
fricklerhandwerk = {
email = "valentin@fricklerhandwerk.de";
github = "fricklerhandwerk";
@ -5719,6 +5825,11 @@
githubId = 17859309;
name = "Fuzen";
};
fwc = {
github = "fwc";
githubId = 29337229;
name = "mtths";
};
fxfactorial = {
email = "edgar.factorial@gmail.com";
github = "fxfactorial";
@ -5974,6 +6085,15 @@
githubId = 127353;
name = "Geoffrey Huntley";
};
gigglesquid = {
email = "jack.connors@protonmail.com";
github = "gigglesquid";
githubId = 3685154;
name = "Jack connors";
keys = [{
fingerprint = "21DF 8034 B212 EDFF 9F19 9C19 F65B 7583 7ABF D019";
}];
};
gila = {
email = "jeffry.molanus@gmail.com";
github = "gila";
@ -6061,6 +6181,12 @@
githubId = 25820499;
name = "Roman Kretschmer";
};
goatchurchprime = {
email = "julian@goatchurch.org.uk";
github = "goatchurchprime";
githubId = 677254;
name = "Julian Todd";
};
gobidev = {
email = "adrian.groh@t-online.de";
github = "Gobidev";
@ -7336,6 +7462,11 @@
github = "jali-clarke";
githubId = 17733984;
};
james-atkins = {
name = "James Atkins";
github = "james-atkins";
githubId = 9221409;
};
jamiemagee = {
email = "jamie.magee@gmail.com";
github = "JamieMagee";
@ -8077,6 +8208,12 @@
email = "j.loos@posteo.net";
githubId = 57965027;
};
josephst = {
name = "Joseph Stahl";
email = "hello@josephstahl.com";
github = "josephst";
githubId = 1269177;
};
joshniemela = {
name = "Joshua Niemelä";
email = "josh@jniemela.dk";
@ -8963,6 +9100,12 @@
githubId = 5759930;
name = "Alexis Destrez";
};
krupkat = {
github = "krupkat";
githubId = 6817216;
name = "Tomas Krupka";
matrix = "@krupkat:matrix.org";
};
ktf = {
email = "giulio.eulisse@cern.ch";
github = "ktf";
@ -9037,6 +9180,12 @@
fingerprint = "5A9A 1C9B 2369 8049 3B48 CF5B 81A1 5409 4816 2372";
}];
};
l0b0 = {
email = "victor@engmark.name";
github = "l0b0";
githubId = 168301;
name = "Victor Engmark";
};
l3af = {
email = "L3afMeAlon3@gmail.com";
matrix = "@L3afMe:matrix.org";
@ -9716,6 +9865,15 @@
githubId = 22085373;
name = "Luis Hebendanz";
};
luisdaranda = {
email = "luisdomingoaranda@gmail.com";
github = "propet";
githubId = 8515861;
name = "Luis D. Aranda Sánchez";
keys = [{
fingerprint = "AB7C 81F4 9E07 CC64 F3E7 BC25 DCAC C6F4 AAFC C04E";
}];
};
luisnquin = {
email = "lpaandres2020@gmail.com";
matrix = "@luisnquin:matrix.org";
@ -9811,6 +9969,12 @@
githubId = 782440;
name = "Luna Nova";
};
lurkki = {
email = "jussi.kuokkanen@protonmail.com";
github = "Lurkki14";
githubId = 44469719;
name = "Jussi Kuokkanen";
};
lux = {
email = "lux@lux.name";
github = "luxzeitlos";
@ -9922,6 +10086,16 @@
githubId = 93990818;
name = "Madoura";
};
maeve = {
email = "mrey@mailbox.org";
matrix = "@maeve:catgirl.cloud";
github = "m-rey";
githubId = 42996147;
name = "Mæve";
keys = [{
fingerprint = "96C9 D086 CC9D 7BD7 EF24 80E2 9168 796A 1CC3 AEA2";
}];
};
mafo = {
email = "Marc.Fontaine@gmx.de";
github = "MarcFontaine";
@ -10005,6 +10179,11 @@
githubId = 2914269;
name = "Malo Bourgon";
};
malt3 = {
github = "malt3";
githubId = 1780588;
name = "Malte Poll";
};
malte-v = {
email = "nixpkgs@mal.tc";
github = "malte-v";
@ -10659,6 +10838,16 @@
fingerprint = "8CE3 2906 516F C4D8 D373 308A E189 648A 55F5 9A9F";
}];
};
mib = {
name = "mib";
email = "mib@kanp.ai";
matrix = "@mib:kanp.ai";
github = "mibmo";
githubId = 87388017;
keys = [{
fingerprint = "AB0D C647 B2F7 86EB 045C 7EFE CF6E 67DE D6DC 1E3F";
}];
};
mic92 = {
email = "joerg@thalheim.io";
matrix = "@mic92:nixos.dev";
@ -10755,6 +10944,12 @@
fingerprint = "FEF0 AE2D 5449 3482 5F06 40AA 186A 1EDA C5C6 3F83";
}];
};
mig4ng = {
email = "mig4ng@gmail.com";
github = "mig4ng";
githubId = 5817039;
name = "Miguel Carneiro";
};
mightyiam = {
email = "mightyiampresence@gmail.com";
github = "mightyiam";
@ -11241,6 +11436,12 @@
name = "Maxim Schuwalow";
email = "maxim.schuwalow@gmail.com";
};
mschwaig = {
name = "Martin Schwaighofer";
github = "mschwaig";
githubId = 3856390;
email = "mschwaig+nixpkgs@eml.cc";
};
msfjarvis = {
github = "msfjarvis";
githubId = 13348378;
@ -12038,6 +12239,12 @@
githubId = 2946283;
name = "Brian Cohen";
};
nova-madeline = {
matrix = "@nova:tchncs.de";
github = "nova-r";
githubId = 126072875;
name = "nova madeline";
};
novenary = {
email = "streetwalkermc@gmail.com";
github = "9ary";
@ -12376,6 +12583,12 @@
githubId = 75299;
name = "Malcolm Matalka";
};
orichter = {
email = "richter-oliver@gmx.net";
github = "RichterOliver";
githubId = 135209509;
name = "Oliver Richter";
};
orivej = {
email = "orivej@gmx.fr";
github = "orivej";
@ -12968,6 +13181,12 @@
githubId = 1830959;
name = "Piper McCorkle";
};
piturnah = {
email = "peterhebden6@gmail.com";
github = "piturnah";
githubId = 20472367;
name = "Peter Hebden";
};
pjbarnoy = {
email = "pjbarnoy@gmail.com";
github = "pjbarnoy";
@ -14102,6 +14321,12 @@
githubId = 1069318;
name = "Robin Lambertz";
};
robwalt = {
email = "robwalter96@gmail.com";
github = "robwalt";
githubId = 26892280;
name = "Robert Walter";
};
roconnor = {
email = "roconnor@theorem.ca";
github = "roconnor";
@ -14339,6 +14564,12 @@
githubId = 889991;
name = "Ryan Artecona";
};
ryanccn = {
email = "hello@ryanccn.dev";
github = "ryanccn";
githubId = 70191398;
name = "Ryan Cao";
};
ryane = {
email = "ryanesc@gmail.com";
github = "ryane";
@ -14393,6 +14624,12 @@
githubId = 3280280;
name = "Ryne Everett";
};
ryota-ka = {
email = "ok@ryota-ka.me";
github = "ryota-ka";
githubId = 7309170;
name = "Ryota Kameoka";
};
rytone = {
email = "max@ryt.one";
github = "rastertail";
@ -14798,6 +15035,16 @@
githubId = 4805746;
name = "Sebastian Jordan";
};
septem9er = {
name = "Septem9er";
email = "develop@septem9er.de";
matrix = "@septem9er:fairydust.space";
github = "septem9er";
githubId = 33379902;
keys = [{
fingerprint = "C408 07F9 8677 3D98 EFF3 0980 355A 9AFB FD8E AD33";
}];
};
seqizz = {
email = "seqizz@gmail.com";
github = "seqizz";
@ -15155,6 +15402,12 @@
githubId = 74881555;
name = "Fofanov Sergey";
};
sitaaax = {
email = "johannes@kle1n.com";
github = "SitAAAx";
githubId = 74413170;
name = "Johannes Klein";
};
sivteck = {
email = "sivaram1992@gmail.com";
github = "sivteck";
@ -15395,6 +15648,12 @@
githubId = 7669898;
name = "Katharina Fey";
};
spalf = {
email = "tom@tombarrett.xyz";
name = "tom barrett";
github = "70m6";
githubId = 105207964;
};
spease = {
email = "peasteven@gmail.com";
github = "spease";
@ -15426,6 +15685,12 @@
githubId = 6391601;
name = "Roger Mason";
};
sputn1ck = {
email = "kon@kon.ninja";
github = "sputn1ck";
githubId = 8904314;
name = "Konstantin Nick";
};
squalus = {
email = "squalus@squalus.net";
github = "squalus";
@ -15462,6 +15727,13 @@
githubId = 219362;
name = "Sarah Brofeldt";
};
srid = {
email = "srid@srid.ca";
matrix = "@srid:matrix.org";
github = "srid";
githubId = 3998;
name = "Sridhar Ratnakumar";
};
srounce = {
name = "Samuel Rounce";
email = "me@samuelrounce.co.uk";
@ -15730,6 +16002,12 @@
githubId = 16734772;
name = "Sumner Evans";
};
sund3RRR = {
email = "evenquantity@gmail.com";
github = "sund3RRR";
githubId = 73298492;
name = "Mikhail Kiselev";
};
suominen = {
email = "kimmo@suominen.com";
github = "suominen";
@ -16362,6 +16640,12 @@
github = "thielema";
githubId = 898989;
};
thillux = {
name = "Markus Theil";
email = "theil.markus@gmail.com";
github = "thillux";
githubId = 2171995;
};
thilobillerbeck = {
name = "Thilo Billerbeck";
email = "thilo.billerbeck@officerent.de";
@ -16625,6 +16909,14 @@
githubId = 8577941;
name = "Kevin Rauscher";
};
tomasajt = {
github = "TomaSajt";
githubId = 62384384;
name = "TomaSajt";
keys = [{
fingerprint = "8CA9 8016 F44D B717 5B44 6032 F011 163C 0501 22A1";
}];
};
tomaskala = {
email = "public+nixpkgs@tomaskala.com";
github = "tomaskala";
@ -16901,6 +17193,12 @@
matrix = "@ty:tjll.net";
name = "Tyler Langlois";
};
tymscar = {
email = "oscar@tymscar.com";
github = "tymscar";
githubId = 3742502;
name = "Oscar Molnar";
};
typetetris = {
email = "ericwolf42@mail.com";
github = "typetetris";
@ -17249,7 +17547,7 @@
githubId = 7953163;
name = "Vika Shleina";
keys = [{
fingerprint = "B3C0 DA1A C18B 82E8 CA8B B1D1 4F62 CD07 CE64 796A";
fingerprint = "5814 50EB 6E17 E715 7C63 E7F1 9879 8C3C 4D68 8D6D";
}];
};
vincentbernat = {
@ -17261,6 +17559,12 @@
fingerprint = "AEF2 3487 66F3 71C6 89A7 3600 95A4 2FE8 3535 25F9";
}];
};
vinetos = {
name = "vinetos";
email = "vinetosdev@gmail.com";
github = "vinetos";
githubId = 10145351;
};
vinnymeller = {
email = "vinnymeller@proton.me";
github = "vinnymeller";
@ -17364,6 +17668,12 @@
githubId = 3413119;
name = "Vonfry";
};
votava = {
email = "votava@gmail.com";
github = "janvotava";
githubId = 367185;
name = "Jan Votava";
};
vq = {
email = "vq@erq.se";
github = "vq";
@ -17947,6 +18257,12 @@
githubId = 73759599;
name = "Yaya";
};
yboettcher = {
name = "Yannik Böttcher";
github = "yboettcher";
githubId = 39460066;
email = "yannikboettcher@outlook.de";
};
ydlr = {
name = "ydlr";
email = "ydlr@ydlr.io";
@ -18266,12 +18582,6 @@
github = "zfnmxt";
githubId = 37446532;
};
zgrannan = {
email = "zgrannan@gmail.com";
github = "zgrannan";
githubId = 1141948;
name = "Zack Grannan";
};
zhaofengli = {
email = "hello@zhaofeng.li";
matrix = "@zhaofeng:zhaofeng.li";
@ -18303,6 +18613,19 @@
githubId = 1108325;
name = "Théo Zimmermann";
};
zmitchell = {
name = "Zach Mitchell";
email = "zmitchell@fastmail.com";
matrix = "@zmitchell:matrix.org";
github = "zmitchell";
githubId = 10246891;
};
znewman01 = {
email = "znewman01@gmail.com";
github = "znewman01";
githubId = 873857;
name = "Zack Newman";
};
zoedsoupe = {
github = "zoedsoupe";
githubId = 44469426;

View file

@ -1,5 +1,5 @@
#! /usr/bin/env nix-shell
#! nix-shell -i bash -p nix curl jq nix-prefetch-github git gnused -I nixpkgs=.
#! nix-shell -i bash -p nix curl jq git gnused -I nixpkgs=.
# See regenerate-hackage-packages.sh for details on the purpose of this script.

View file

@ -1,5 +1,5 @@
#! /usr/bin/env nix-shell
#! nix-shell -i bash -p nix curl jq nix-prefetch-github git gnused gnugrep -I nixpkgs=.
#! nix-shell -i bash -p nix curl jq git gnused gnugrep -I nixpkgs=.
# shellcheck shell=bash
set -eu -o pipefail

View file

@ -86,6 +86,7 @@ luuid,,,,,,
luv,,,,1.44.2-1,,
lush.nvim,https://github.com/rktjmp/lush.nvim,,,,,teto
lyaml,,,,,,lblasc
magick,,,,,,donovanglover
markdown,,,,,,
mediator_lua,,,,,,
mpack,,,,,,

1 name src ref server version luaversion maintainers
86 luv 1.44.2-1
87 lush.nvim https://github.com/rktjmp/lush.nvim teto
88 lyaml lblasc
89 magick donovanglover
90 markdown
91 mediator_lua
92 mpack

View file

@ -6,6 +6,7 @@ use warnings;
use CPAN::Meta();
use CPANPLUS::Backend();
use MIME::Base64;
use Module::CoreList;
use Getopt::Long::Descriptive qw( describe_options );
use JSON::PP qw( encode_json );
@ -354,6 +355,11 @@ sub render_license {
return $license_line;
}
sub sha256_to_sri {
my ($sha256) = @_;
return "sha256-" . encode_base64(pack("H*", $sha256), '');
}
my ( $opt, $module_name ) = handle_opts();
Log::Log4perl->easy_init(
@ -380,8 +386,9 @@ INFO( "package: ", $module->package, " (", "$pkg_name-$pkg_version", ", ", $attr
INFO( "path: ", $module->path );
my $tar_path = $module->fetch();
my $sri_hash = sha256_to_sri($module->status->checksum_value);
INFO( "downloaded to: ", $tar_path );
INFO( "sha-256: ", $module->status->checksum_value );
INFO( "hash: ", $sri_hash );
my $pkg_path = $module->extract();
INFO( "unpacked to: ", $pkg_path );
@ -436,7 +443,7 @@ print <<EOF;
version = "$pkg_version";
src = fetchurl {
url = "mirror://cpan/${\$module->path}/${\$module->package}";
sha256 = "${\$module->status->checksum_value}";
hash = "$sri_hash";
};
EOF
print <<EOF if scalar @build_deps > 0;

View file

@ -570,6 +570,7 @@ with lib.maintainers; {
ralith
dandellion
sumnerevans
nickcao
];
scope = "Maintain the ecosystem around Matrix, a decentralized messenger.";
shortName = "Matrix";
@ -820,9 +821,7 @@ with lib.maintainers; {
};
sphinx = {
members = [
SuperSandro2000
];
members = [ ];
scope = "Maintain Sphinx related packages.";
shortName = "Sphinx";
};

View file

@ -0,0 +1,4 @@
{
outputPath = "share/doc/nixos";
indexPath = "index.html";
}

View file

@ -11,6 +11,8 @@ $ nix-build nixos/release.nix -A manual.x86_64-linux
If the build succeeds, the manual will be in `./result/share/doc/nixos/index.html`.
There's also [a convenient development daemon](https://nixos.org/manual/nixpkgs/unstable/#sec-contributing-devmode).
**Contributing to the man pages**
The man pages are written in [DocBook] which is XML.

View file

@ -16,6 +16,8 @@ let
lib = pkgs.lib;
common = import ./common.nix;
manpageUrls = pkgs.path + "/doc/manpage-urls.json";
# We need to strip references to /nix/store/* from options,
@ -63,6 +65,9 @@ let
optionIdPrefix = "test-opt-";
};
testDriverMachineDocstrings = pkgs.callPackage
../../../nixos/lib/test-driver/nixos-test-driver-docstrings.nix {};
prepareManualFromMD = ''
cp -r --no-preserve=all $inputs/* .
@ -75,11 +80,13 @@ let
substituteInPlace ./nixos-options.md \
--replace \
'@NIXOS_OPTIONS_JSON@' \
${optionsDoc.optionsJSON}/share/doc/nixos/options.json
${optionsDoc.optionsJSON}/${common.outputPath}/options.json
substituteInPlace ./development/writing-nixos-tests.section.md \
--replace \
'@NIXOS_TEST_OPTIONS_JSON@' \
${testOptionsDoc.optionsJSON}/share/doc/nixos/options.json
${testOptionsDoc.optionsJSON}/${common.outputPath}/options.json
sed -e '/@PYTHON_MACHINE_METHODS@/ {' -e 'r ${testDriverMachineDocstrings}/machine-methods.md' -e 'd' -e '}' \
-i ./development/writing-nixos-tests.section.md
'';
in rec {
@ -94,7 +101,7 @@ in rec {
}
''
# Generate the HTML manual.
dst=$out/share/doc/nixos
dst=$out/${common.outputPath}
mkdir -p $dst
cp ${../../../doc/style.css} $dst/style.css
@ -115,7 +122,7 @@ in rec {
--toc-depth 1 \
--chunk-toc-depth 1 \
./manual.md \
$dst/index.html
$dst/${common.indexPath}
mkdir -p $out/nix-support
echo "nix-build out $out" >> $out/nix-support/hydra-build-products
@ -126,7 +133,7 @@ in rec {
manual = manualHTML;
# Index page of the NixOS manual.
manualHTMLIndex = "${manualHTML}/share/doc/nixos/index.html";
manualHTMLIndex = "${manualHTML}/${common.outputPath}/${common.indexPath}";
manualEpub = runCommand "nixos-manual-epub"
{ nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin buildPackages.zip ];
@ -157,7 +164,7 @@ in rec {
}
''
# Generate the epub manual.
dst=$out/share/doc/nixos
dst=$out/${common.outputPath}
xsltproc \
--param chapter.autolabel 0 \
@ -192,7 +199,7 @@ in rec {
mkdir -p $out/share/man/man5
nixos-render-docs -j $NIX_BUILD_CORES options manpage \
--revision ${lib.escapeShellArg revision} \
${optionsJSON}/share/doc/nixos/options.json \
${optionsJSON}/${common.outputPath}/options.json \
$out/share/man/man5/configuration.nix.5
'';

View file

@ -139,210 +139,7 @@ to Python as `machine_a`.
The following methods are available on machine objects:
`start`
: Start the virtual machine. This method is asynchronous --- it does
not wait for the machine to finish booting.
`shutdown`
: Shut down the machine, waiting for the VM to exit.
`crash`
: Simulate a sudden power failure, by telling the VM to exit
immediately.
`block`
: Simulate unplugging the Ethernet cable that connects the machine to
the other machines.
`unblock`
: Undo the effect of `block`.
`screenshot`
: Take a picture of the display of the virtual machine, in PNG format.
The screenshot is linked from the HTML log.
`get_screen_text_variants`
: Return a list of different interpretations of what is currently
visible on the machine's screen using optical character
recognition. The number and order of the interpretations is not
specified and is subject to change, but if no exception is raised at
least one will be returned.
::: {.note}
This requires [`enableOCR`](#test-opt-enableOCR) to be set to `true`.
:::
`get_screen_text`
: Return a textual representation of what is currently visible on the
machine's screen using optical character recognition.
::: {.note}
This requires [`enableOCR`](#test-opt-enableOCR) to be set to `true`.
:::
`send_monitor_command`
: Send a command to the QEMU monitor. This is rarely used, but allows
doing stuff such as attaching virtual USB disks to a running
machine.
`send_key`
: Simulate pressing keys on the virtual keyboard, e.g.,
`send_key("ctrl-alt-delete")`.
`send_chars`
: Simulate typing a sequence of characters on the virtual keyboard,
e.g., `send_chars("foobar\n")` will type the string `foobar`
followed by the Enter key.
`send_console`
: Send keys to the kernel console. This allows interaction with the systemd
emergency mode, for example. Takes a string that is sent, e.g.,
`send_console("\n\nsystemctl default\n")`.
`execute`
: Execute a shell command, returning a list `(status, stdout)`.
Commands are run with `set -euo pipefail` set:
- If several commands are separated by `;` and one fails, the
command as a whole will fail.
- For pipelines, the last non-zero exit status will be returned
(if there is one; otherwise zero will be returned).
- Dereferencing unset variables fails the command.
- It will wait for stdout to be closed.
If the command detaches, it must close stdout, as `execute` will wait
for this to consume all output reliably. This can be achieved by
redirecting stdout to stderr `>&2`, to `/dev/console`, `/dev/null` or
a file. Examples of detaching commands are `sleep 365d &`, where the
shell forks a new process that can write to stdout and `xclip -i`, where
the `xclip` command itself forks without closing stdout.
Takes an optional parameter `check_return` that defaults to `True`.
Setting this parameter to `False` will not check for the return code
and return -1 instead. This can be used for commands that shut down
the VM and would therefore break the pipe that would be used for
retrieving the return code.
A timeout for the command can be specified (in seconds) using the optional
`timeout` parameter, e.g., `execute(cmd, timeout=10)` or
`execute(cmd, timeout=None)`. The default is 900 seconds.
`succeed`
: Execute a shell command, raising an exception if the exit status is
not zero, otherwise returning the standard output. Similar to `execute`,
except that the timeout is `None` by default. See `execute` for details on
command execution.
`fail`
: Like `succeed`, but raising an exception if the command returns a zero
status.
`wait_until_succeeds`
: Repeat a shell command with 1-second intervals until it succeeds.
Has a default timeout of 900 seconds which can be modified, e.g.
`wait_until_succeeds(cmd, timeout=10)`. See `execute` for details on
command execution.
`wait_until_fails`
: Like `wait_until_succeeds`, but repeating the command until it fails.
`wait_for_unit`
: Wait until the specified systemd unit has reached the "active"
state.
`wait_for_file`
: Wait until the specified file exists.
`wait_for_open_port`
: Wait until a process is listening on the given TCP port and IP address
(default `localhost`).
`wait_for_closed_port`
: Wait until nobody is listening on the given TCP port and IP address
(default `localhost`).
`wait_for_x`
: Wait until the X11 server is accepting connections.
`wait_for_text`
: Wait until the supplied regular expressions matches the textual
contents of the screen by using optical character recognition (see
`get_screen_text` and `get_screen_text_variants`).
::: {.note}
This requires [`enableOCR`](#test-opt-enableOCR) to be set to `true`.
:::
`wait_for_console_text`
: Wait until the supplied regular expressions match a line of the
serial console output. This method is useful when OCR is not
possible or accurate enough.
`wait_for_window`
: Wait until an X11 window has appeared whose name matches the given
regular expression, e.g., `wait_for_window("Terminal")`.
`copy_from_host`
: Copies a file from host to machine, e.g.,
`copy_from_host("myfile", "/etc/my/important/file")`.
The first argument is the file on the host. The file needs to be
accessible while building the nix derivation. The second argument is
the location of the file on the machine.
`systemctl`
: Runs `systemctl` commands with optional support for
`systemctl --user`
```py
machine.systemctl("list-jobs --no-pager") # runs `systemctl list-jobs --no-pager`
machine.systemctl("list-jobs --no-pager", "any-user") # spawns a shell for `any-user` and runs `systemctl --user list-jobs --no-pager`
```
`shell_interact`
: Allows you to directly interact with the guest shell. This should
only be used during test development, not in production tests.
Killing the interactive session with `Ctrl-d` or `Ctrl-c` also ends
the guest session.
`console_interact`
: Allows you to directly interact with QEMU's stdin. This should
only be used during test development, not in production tests.
Output from QEMU is only read line-wise. `Ctrl-c` kills QEMU and
`Ctrl-d` closes console and returns to the test runner.
@PYTHON_MACHINE_METHODS@
To test user units declared by `systemd.user.services` the optional
`user` argument can be used:

View file

@ -249,14 +249,14 @@ update /etc/fstab.
which will be used by the boot partition.
```ShellSession
# parted /dev/sda -- mkpart primary 512MB -8GB
# parted /dev/sda -- mkpart root ext4 512MB -8GB
```
3. Next, add a *swap* partition. The size required will vary according
to needs, here a 8GB one is created.
```ShellSession
# parted /dev/sda -- mkpart primary linux-swap -8GB 100%
# parted /dev/sda -- mkpart swap linux-swap -8GB 100%
```
::: {.note}
@ -550,8 +550,8 @@ corresponding configuration Nix expression.
### Example partition schemes for NixOS on `/dev/sda` (UEFI)
```ShellSession
# parted /dev/sda -- mklabel gpt
# parted /dev/sda -- mkpart primary 512MB -8GB
# parted /dev/sda -- mkpart primary linux-swap -8GB 100%
# parted /dev/sda -- mkpart root ext4 512MB -8GB
# parted /dev/sda -- mkpart swap linux-swap -8GB 100%
# parted /dev/sda -- mkpart ESP fat32 1MB 512MB
# parted /dev/sda -- set 3 esp on
```

View file

@ -441,6 +441,8 @@ In addition to numerous new and upgraded packages, this release has the followin
- `pkgs.haskell-language-server` will now by default be linked dynamically to improve TemplateHaskell compatibility. To mitigate the increased closure size it will now by default only support our current default ghc (at the moment 9.0.2). Add other ghc versions via e.g. `pkgs.haskell-language-server.override { supportedGhcVersions = [ "90" "92" ]; }`.
- `pkgs.redis` is now built using the system jemalloc. This disables the experimental active defragmentation feature of redis. Users who require this feature can switch back to redis' vendored version of jemalloc by setting `services.redis.package = pkgs.redis.override { useSystemJemalloc = false; };`.
## Other Notable Changes {#sec-release-21.11-notable-changes}

View file

@ -83,6 +83,8 @@ In addition to numerous new and updated packages, this release has the following
- [gitea-actions-runner](https://gitea.com/gitea/act_runner), a CI runner for Gitea/Forgejo Actions. Available as [services.gitea-actions-runner](#opt-services.gitea-actions-runner.instances).
- [evdevremapkeys](https://github.com/philipl/evdevremapkeys), a daemon to remap key events. Available as [services.evdevremapkeys](#opt-services.evdevremapkeys.enable).
- [gmediarender](https://github.com/hzeller/gmrender-resurrect), a simple, headless UPnP/DLNA renderer. Available as [services.gmediarender](options.html#opt-services.gmediarender.enable).
- [go2rtc](https://github.com/AlexxIT/go2rtc), a camera streaming appliation with support for RTSP, WebRTC, HomeKit, FFMPEG, RTMP and other protocols. Available as [services.go2rtc](options.html#opt-services.go2rtc.enable).

View file

@ -18,12 +18,24 @@
- [GoToSocial](https://gotosocial.org/), an ActivityPub social network server, written in Golang. Available as [services.gotosocial](#opt-services.gotosocial.enable).
- [Typesense](https://github.com/typesense/typesense), a fast, typo-tolerant search engine for building delightful search experiences. Available as [services.typesense](#opt-services.typesense.enable).
- [Anuko Time Tracker](https://github.com/anuko/timetracker), a simple, easy to use, open source time tracking system. Available as [services.anuko-time-tracker](#opt-services.anuko-time-tracker.enable).
- [sitespeed-io](https://sitespeed.io), a tool that can generate metrics (timings, diagnostics) for websites. Available as [services.sitespeed-io](#opt-services.sitespeed-io.enable).
- [Apache Guacamole](https://guacamole.apache.org/), a cross-platform, clientless remote desktop gateway. Available as [services.guacamole-server](#opt-services.guacamole-server.enable) and [services.guacamole-client](#opt-services.guacamole-client.enable) services.
- [pgBouncer](https://www.pgbouncer.org), a PostgreSQL connection pooler. Available as [services.pgbouncer](#opt-services.pgbouncer.enable).
- [trust-dns](https://trust-dns.org/), a Rust based DNS server built to be safe and secure from the ground up. Available as [services.trust-dns](#opt-services.trust-dns.enable).
- [osquery](https://www.osquery.io/), a SQL powered operating system instrumentation, monitoring, and analytics.
- [ebusd](https://ebusd.eu), a daemon for handling communication with eBUS devices connected to a 2-wire bus system (“energy bus” used by numerous heating systems). Available as [services.ebusd](#opt-services.ebusd.enable).
- [systemd-sysupdate](https://www.freedesktop.org/software/systemd/man/systemd-sysupdate.html), atomically updates the host OS, container images, portable service images or other sources. Available as [systemd.sysupdate](opt-systemd.sysupdate).
## Backward Incompatibilities {#sec-release-23.11-incompatibilities}
- The `boot.loader.raspberryPi` options have been marked deprecated, with intent for removal for NixOS 24.11. They had a limited use-case, and do not work like people expect. They required either very old installs ([before mid-2019](https://github.com/NixOS/nixpkgs/pull/62462)) or customized builds out of scope of the standard and generic AArch64 support. That option set never supported the Raspberry Pi 4 family of devices.
@ -46,6 +58,8 @@
- `getent` has been moved from `glibc`'s `bin` output to its own dedicated output, reducing closure size for many dependents. Dependents using the `getent` alias should not be affected; others should move from using `glibc.bin` or `getBin glibc` to `getent` (which also improves compatibility with non-glibc platforms).
- The `services.ananicy.extraRules` option now has the type of `listOf attrs` instead of `string`.
- `etcd` has been updated to 3.5, you will want to read the [3.3 to 3.4](https://etcd.io/docs/v3.5/upgrades/upgrade_3_4/) and [3.4 to 3.5](https://etcd.io/docs/v3.5/upgrades/upgrade_3_5/) upgrade guides
- `consul` has been updated to `1.16.0`. See the [release note](https://github.com/hashicorp/consul/releases/tag/v1.16.0) for more details. Once a new Consul version has started and upgraded its data directory, it generally cannot be downgraded to the previous version.
@ -60,6 +74,8 @@
- `util-linux` is now supported on Darwin and is no longer an alias to `unixtools`. Use the `unixtools.util-linux` package for access to the Apple variants of the utilities.
- `services.keyd` changed API. Now you can create multiple configuration files.
- `services.ddclient` has been removed on the request of the upstream maintainer because it is unmaintained and has bugs. Please switch to a different software like `inadyn` or `knsupdate`.
- The `vlock` program from the `kbd` package has been moved into its own package output and should now be referenced explicitly as `kbd.vlock` or replaced with an alternative such as the standalone `vlock` package or `physlock`.
@ -72,12 +88,22 @@
- `services.lemmy.settings.federation` was removed in 0.17.0 and no longer has any effect. To enable federation, the hostname must be set in the configuration file and then federation must be enabled in the admin web UI. See the [release notes](https://github.com/LemmyNet/lemmy/blob/c32585b03429f0f76d1e4ff738786321a0a9df98/RELEASES.md#upgrade-instructions) for more details.
- `pict-rs` was upgraded from 0.3 to 0.4 and contains an incompatible database & configuration change. To upgrade on systems with `stateVersion = "23.05";` or older follow the migration steps from https://git.asonix.dog/asonix/pict-rs#user-content-0-3-to-0-4-migration-guide and set `services.pict-rs.package = pkgs.pict-rs;`.
- The following packages in `haskellPackages` have now a separate bin output: `cabal-fmt`, `calligraphy`, `eventlog2html`, `ghc-debug-brick`, `hindent`, `nixfmt`, `releaser`. This means you need to replace e.g. `"${pkgs.haskellPackages.nixfmt}/bin/nixfmt"` with `"${lib.getBin pkgs.haskellPackages.nixfmt}/bin/nixfmt"` or `"${lib.getExe pkgs.haskellPackages.nixfmt}"`. The binaries also wont be in scope if you rely on them being installed e.g. via `ghcWithPackages`. `environment.packages` picks the `bin` output automatically, so for normal installation no intervention is required. Also, toplevel attributes like `pkgs.nixfmt` are not impacted negatively by this change.
- `spamassassin` no longer supports the `Hashcash` module. The module needs to be removed from the `loadplugin` list if it was copied over from the default `initPreConf` option.
- `services.outline.sequelizeArguments` has been removed, as `outline` no longer executes database migrations via the `sequelize` cli.
- The Caddy module gained a new option named `services.caddy.enableReload` which is enabled by default. It allows reloading the service instead of restarting it, if only a config file has changed. This option must be disabled if you have turned off the [Caddy admin API](https://caddyserver.com/docs/caddyfile/options#admin). If you keep this option enabled, you should consider setting [`grace_period`](https://caddyserver.com/docs/caddyfile/options#grace-period) to a non-infinite value to prevent Caddy from delaying the reload indefinitely.
- mdraid support is now optional. This reduces initramfs size and prevents the potentially undesired automatic detection and activation of software RAID pools. It is disabled by default in new configurations (determined by `stateVersion`), but the appropriate settings will be generated by `nixos-generate-config` when installing to a software RAID device, so the standard installation procedure should be unaffected. If you have custom configs relying on mdraid, ensure that you use `stateVersion` correctly or set `boot.swraid.enable` manually.
- The `go-ethereum` package has been updated to v1.12.0. This drops support for proof-of-work. Its GraphQL API now encodes all numeric values as hex strings and the GraphQL UI is updated to version 2.0. The default database has changed from `leveldb` to `pebble` but `leveldb` can be forced with the --db.engine=leveldb flag. The `checkpoint-admin` command was [removed along with trusted checkpoints](https://github.com/ethereum/go-ethereum/pull/27147).
- The default `kops` version is now 1.27.0 and support for 1.24 and older has been dropped.
## 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.
@ -96,6 +122,10 @@
- DocBook option documentation is no longer supported, all module documentation now uses markdown.
- `buildGoModule` `go-modules` attrs have been renamed to `goModules`.
- The `fonts.fonts` and `fonts.enableDefaultFonts` options have been renamed to `fonts.packages` and `fonts.enableDefaultPackages` respectively.
- `services.fail2ban.jails` can now be configured with attribute sets defining settings and filters instead of lines. The stringed options `daemonConfig` and `extraSettings` have respectively been replaced by `daemonSettings` and `jails.DEFAULT.settings` which use attribute sets.
- The module [services.ankisyncd](#opt-services.ankisyncd.package) has been switched to [anki-sync-server-rs](https://github.com/ankicommunity/anki-sync-server-rs) from the old python version, which was difficult to update, had not been updated in a while, and did not support recent versions of anki.
@ -112,6 +142,8 @@ The module update takes care of the new config syntax and the data itself (user
- `programs.gnupg.agent.pinentryFlavor` is now set in `/etc/gnupg/gpg-agent.conf`, and will no longer take precedence over a `pinentry-program` set in `~/.gnupg/gpg-agent.conf`.
- `wrapHelm` now exposes `passthru.pluginsDir` which can be passed to `helmfile`. For convenience, a top-level package `helmfile-wrapped` has been added, which inherits `passthru.pluginsDir` from `kubernetes-helm-wrapped`. See [#217768](https://github.com/NixOS/nixpkgs/issues/217768) for details.
## Nixpkgs internals {#sec-release-23.11-nixpkgs-internals}
- The `qemu-vm.nix` module by default now identifies block devices via

View file

@ -0,0 +1,20 @@
let
pkgs = import ../../.. {
config = {};
overlays = [];
};
common = import ./common.nix;
inherit (common) outputPath indexPath;
web-devmode = import ../../../pkgs/tools/nix/web-devmode.nix {
inherit pkgs;
buildArgs = "../../release.nix -A manualHTML.${builtins.currentSystem}";
open = "/${outputPath}/${indexPath}";
};
in
pkgs.mkShell {
packages = [
web-devmode
];
}

View file

@ -31,7 +31,7 @@ evalConfigArgs@
, prefix ? []
, lib ? import ../../lib
, extraModules ? let e = builtins.getEnv "NIXOS_EXTRA_MODULE_PATH";
in if e == "" then [] else [(import e)]
in lib.optional (e != "") (import e)
}:
let pkgs_ = pkgs;

View file

@ -572,7 +572,7 @@ let format' = format; in let
${lib.optionalString installBootLoader ''
# In this throwaway resource, we only have /dev/vda, but the actual VM may refer to another disk for bootloader, e.g. /dev/vdb
# Use this option to create a symlink from vda to any arbitrary device you want.
${optionalString (config.boot.loader.grub.device != "/dev/vda") ''
${optionalString (config.boot.loader.grub.enable && config.boot.loader.grub.device != "/dev/vda") ''
mkdir -p $(dirname ${config.boot.loader.grub.device})
ln -s /dev/vda ${config.boot.loader.grub.device}
''}

View file

@ -63,7 +63,12 @@ in rec {
assertMacAddress = name: group: attr:
optional (attr ? ${name} && ! isMacAddress attr.${name})
"Systemd ${group} field `${name}' must be a valid mac address.";
"Systemd ${group} field `${name}' must be a valid MAC address.";
assertNetdevMacAddress = name: group: attr:
optional (attr ? ${name} && (! isMacAddress attr.${name} && attr.${name} != "none"))
"Systemd ${group} field `${name}` must be a valid MAC address or the special value `none`.";
isPort = i: i >= 0 && i <= 65535;
@ -438,4 +443,21 @@ in rec {
${attrsToSection def.sliceConfig}
'';
};
# Create a directory that contains systemd definition files from an attrset
# that contains the file names as keys and the content as values. The values
# in that attrset are determined by the supplied format.
definitions = directoryName: format: definitionAttrs:
let
listOfDefinitions = lib.mapAttrsToList
(name: format.generate "${name}.conf")
definitionAttrs;
in
pkgs.runCommand directoryName { } ''
mkdir -p $out
${(lib.concatStringsSep "\n"
(map (pkg: "cp ${pkg} $out/${pkg.name}") listOfDefinitions)
)}
'';
}

View file

@ -0,0 +1,66 @@
import ast
import sys
"""
This program takes all the Machine class methods and prints its methods in
markdown-style. These can then be included in the NixOS test driver
markdown style, assuming the docstrings themselves are also in markdown.
These are included in the test driver documentation in the NixOS manual.
See https://nixos.org/manual/nixos/stable/#ssec-machine-objects
The python input looks like this:
```py
...
class Machine(...):
...
def some_function(self, param1, param2):
""
documentation string of some_function.
foo bar baz.
""
...
```
Output will be:
```markdown
...
some_function(param1, param2)
: documentation string of some_function.
foo bar baz.
...
```
"""
assert len(sys.argv) == 2
with open(sys.argv[1], "r") as f:
module = ast.parse(f.read())
class_definitions = (node for node in module.body if isinstance(node, ast.ClassDef))
machine_class = next(filter(lambda x: x.name == "Machine", class_definitions))
assert machine_class is not None
function_definitions = [
node for node in machine_class.body if isinstance(node, ast.FunctionDef)
]
function_definitions.sort(key=lambda x: x.name)
for f in function_definitions:
docstr = ast.get_docstring(f)
if docstr is not None:
args = ", ".join((a.arg for a in f.args.args[1:]))
args = f"({args})"
docstr = "\n".join((f" {l}" for l in docstr.strip().splitlines()))
print(f"{f.name}{args}\n\n:{docstr[1:]}\n")

View file

@ -0,0 +1,13 @@
{ runCommand
, python3
}:
let
env = { nativeBuildInputs = [ python3 ]; };
in
runCommand "nixos-test-driver-docstrings" env ''
mkdir $out
python3 ${./extract-docstrings.py} ${./test_driver/machine.py} \
> $out/machine-methods.md
''

View file

@ -416,6 +416,10 @@ class Machine:
return answer
def send_monitor_command(self, command: str) -> str:
"""
Send a command to the QEMU monitor. This allows attaching
virtual USB disks to a running machine, among other things.
"""
self.run_callbacks()
message = f"{command}\n".encode()
assert self.monitor is not None
@ -425,9 +429,10 @@ class Machine:
def wait_for_unit(
self, unit: str, user: Optional[str] = None, timeout: int = 900
) -> None:
"""Wait for a systemd unit to get into "active" state.
Throws exceptions on "failed" and "inactive" states as well as
after timing out.
"""
Wait for a systemd unit to get into "active" state.
Throws exceptions on "failed" and "inactive" states as well as after
timing out.
"""
def check_active(_: Any) -> bool:
@ -476,6 +481,19 @@ class Machine:
)
def systemctl(self, q: str, user: Optional[str] = None) -> Tuple[int, str]:
"""
Runs `systemctl` commands with optional support for
`systemctl --user`
```py
# run `systemctl list-jobs --no-pager`
machine.systemctl("list-jobs --no-pager")
# spawn a shell for `any-user` and run
# `systemctl --user list-jobs --no-pager`
machine.systemctl("list-jobs --no-pager", "any-user")
```
"""
if user is not None:
q = q.replace("'", "\\'")
return self.execute(
@ -520,6 +538,38 @@ class Machine:
check_output: bool = True,
timeout: Optional[int] = 900,
) -> Tuple[int, str]:
"""
Execute a shell command, returning a list `(status, stdout)`.
Commands are run with `set -euo pipefail` set:
- If several commands are separated by `;` and one fails, the
command as a whole will fail.
- For pipelines, the last non-zero exit status will be returned
(if there is one; otherwise zero will be returned).
- Dereferencing unset variables fails the command.
- It will wait for stdout to be closed.
If the command detaches, it must close stdout, as `execute` will wait
for this to consume all output reliably. This can be achieved by
redirecting stdout to stderr `>&2`, to `/dev/console`, `/dev/null` or
a file. Examples of detaching commands are `sleep 365d &`, where the
shell forks a new process that can write to stdout and `xclip -i`, where
the `xclip` command itself forks without closing stdout.
Takes an optional parameter `check_return` that defaults to `True`.
Setting this parameter to `False` will not check for the return code
and return -1 instead. This can be used for commands that shut down
the VM and would therefore break the pipe that would be used for
retrieving the return code.
A timeout for the command can be specified (in seconds) using the optional
`timeout` parameter, e.g., `execute(cmd, timeout=10)` or
`execute(cmd, timeout=None)`. The default is 900 seconds.
"""
self.run_callbacks()
self.connect()
@ -533,7 +583,7 @@ class Machine:
# While sh is bash on NixOS, this is not the case for every distro.
# We explicitly call bash here to allow for the driver to boot other distros as well.
out_command = (
f"{timeout_str} bash -c {shlex.quote(command)} | (base64 --wrap 0; echo)\n"
f"{timeout_str} bash -c {shlex.quote(command)} | (base64 -w 0; echo)\n"
)
assert self.shell
@ -555,10 +605,11 @@ class Machine:
return (rc, output.decode(errors="replace"))
def shell_interact(self, address: Optional[str] = None) -> None:
"""Allows you to interact with the guest shell for debugging purposes.
@address string passed to socat that will be connected to the guest shell.
Check the `Running Tests interactivly` chapter of NixOS manual for an example.
"""
Allows you to directly interact with the guest shell. This should
only be used during test development, not in production tests.
Killing the interactive session with `Ctrl-d` or `Ctrl-c` also ends
the guest session.
"""
self.connect()
@ -577,12 +628,14 @@ class Machine:
pass
def console_interact(self) -> None:
"""Allows you to interact with QEMU's stdin
The shell can be exited with Ctrl+D. Note that Ctrl+C is not allowed to be used.
QEMU's stdout is read line-wise.
Should only be used during test development, not in the production test."""
"""
Allows you to directly interact with QEMU's stdin, by forwarding
terminal input to the QEMU process.
This is for use with the interactive test driver, not for production
tests, which run unattended.
Output from QEMU is only read line-wise. `Ctrl-c` kills QEMU and
`Ctrl-d` closes console and returns to the test runner.
"""
self.log("Terminal is ready (there is no prompt):")
assert self.process
@ -599,7 +652,12 @@ class Machine:
self.send_console(char.decode())
def succeed(self, *commands: str, timeout: Optional[int] = None) -> str:
"""Execute each command and check that it succeeds."""
"""
Execute a shell command, raising an exception if the exit status is
not zero, otherwise returning the standard output. Similar to `execute`,
except that the timeout is `None` by default. See `execute` for details on
command execution.
"""
output = ""
for command in commands:
with self.nested(f"must succeed: {command}"):
@ -611,7 +669,10 @@ class Machine:
return output
def fail(self, *commands: str, timeout: Optional[int] = None) -> str:
"""Execute each command and check that it fails."""
"""
Like `succeed`, but raising an exception if the command returns a zero
status.
"""
output = ""
for command in commands:
with self.nested(f"must fail: {command}"):
@ -622,7 +683,11 @@ class Machine:
return output
def wait_until_succeeds(self, command: str, timeout: int = 900) -> str:
"""Wait until a command returns success and return its output.
"""
Repeat a shell command with 1-second intervals until it succeeds.
Has a default timeout of 900 seconds which can be modified, e.g.
`wait_until_succeeds(cmd, timeout=10)`. See `execute` for details on
command execution.
Throws an exception on timeout.
"""
output = ""
@ -637,8 +702,8 @@ class Machine:
return output
def wait_until_fails(self, command: str, timeout: int = 900) -> str:
"""Wait until a command returns failure.
Throws an exception on timeout.
"""
Like `wait_until_succeeds`, but repeating the command until it fails.
"""
output = ""
@ -690,12 +755,19 @@ class Machine:
retry(tty_matches)
def send_chars(self, chars: str, delay: Optional[float] = 0.01) -> None:
"""
Simulate typing a sequence of characters on the virtual keyboard,
e.g., `send_chars("foobar\n")` will type the string `foobar`
followed by the Enter key.
"""
with self.nested(f"sending keys {repr(chars)}"):
for char in chars:
self.send_key(char, delay, log=False)
def wait_for_file(self, filename: str) -> None:
"""Waits until the file exists in machine's file system."""
"""
Waits until the file exists in the machine's file system.
"""
def check_file(_: Any) -> bool:
status, _ = self.execute(f"test -e {filename}")
@ -705,6 +777,11 @@ class Machine:
retry(check_file)
def wait_for_open_port(self, port: int, addr: str = "localhost") -> None:
"""
Wait until a process is listening on the given TCP port and IP address
(default `localhost`).
"""
def port_is_open(_: Any) -> bool:
status, _ = self.execute(f"nc -z {addr} {port}")
return status == 0
@ -713,6 +790,11 @@ class Machine:
retry(port_is_open)
def wait_for_closed_port(self, port: int, addr: str = "localhost") -> None:
"""
Wait until nobody is listening on the given TCP port and IP address
(default `localhost`).
"""
def port_is_closed(_: Any) -> bool:
status, _ = self.execute(f"nc -z {addr} {port}")
return status != 0
@ -766,6 +848,10 @@ class Machine:
self.connected = True
def screenshot(self, filename: str) -> None:
"""
Take a picture of the display of the virtual machine, in PNG format.
The screenshot will be available in the derivation output.
"""
if "." not in filename:
filename += ".png"
if "/" not in filename:
@ -795,8 +881,21 @@ class Machine:
)
def copy_from_host(self, source: str, target: str) -> None:
"""Copy a file from the host into the guest via the `shared_dir` shared
among all the VMs (using a temporary directory).
"""
Copies a file from host to machine, e.g.,
`copy_from_host("myfile", "/etc/my/important/file")`.
The first argument is the file on the host. Note that the "host" refers
to the environment in which the test driver runs, which is typically the
Nix build sandbox.
The second argument is the location of the file on the machine that will
be written to.
The file is copied via the `shared_dir` directory which is shared among
all the VMs (using a temporary directory).
The access rights bits will mimic the ones from the host file and
user:group will be root:root.
"""
host_src = Path(source)
vm_target = Path(target)
@ -848,12 +947,41 @@ class Machine:
return _perform_ocr_on_screenshot(screenshot_path, model_ids)
def get_screen_text_variants(self) -> List[str]:
"""
Return a list of different interpretations of what is currently
visible on the machine's screen using optical character
recognition. The number and order of the interpretations is not
specified and is subject to change, but if no exception is raised at
least one will be returned.
::: {.note}
This requires [`enableOCR`](#test-opt-enableOCR) to be set to `true`.
:::
"""
return self._get_screen_text_variants([0, 1, 2])
def get_screen_text(self) -> str:
"""
Return a textual representation of what is currently visible on the
machine's screen using optical character recognition.
::: {.note}
This requires [`enableOCR`](#test-opt-enableOCR) to be set to `true`.
:::
"""
return self._get_screen_text_variants([2])[0]
def wait_for_text(self, regex: str) -> None:
"""
Wait until the supplied regular expressions matches the textual
contents of the screen by using optical character recognition (see
`get_screen_text` and `get_screen_text_variants`).
::: {.note}
This requires [`enableOCR`](#test-opt-enableOCR) to be set to `true`.
:::
"""
def screen_matches(last: bool) -> bool:
variants = self.get_screen_text_variants()
for text in variants:
@ -870,12 +998,9 @@ class Machine:
def wait_for_console_text(self, regex: str, timeout: int | None = None) -> None:
"""
Wait for the provided regex to appear on console.
For each reads,
If timeout is None, timeout is infinite.
`timeout` is in seconds.
Wait until the supplied regular expressions match a line of the
serial console output.
This method is useful when OCR is not possible or inaccurate.
"""
# Buffer the console output, this is needed
# to match multiline regexes.
@ -903,6 +1028,13 @@ class Machine:
def send_key(
self, key: str, delay: Optional[float] = 0.01, log: Optional[bool] = True
) -> None:
"""
Simulate pressing keys on the virtual keyboard, e.g.,
`send_key("ctrl-alt-delete")`.
Please also refer to the QEMU documentation for more information on the
input syntax: https://en.wikibooks.org/wiki/QEMU/Monitor#sendkey_keys
"""
key = CHAR_TO_KEY.get(key, key)
context = self.nested(f"sending key {repr(key)}") if log else nullcontext()
with context:
@ -911,12 +1043,21 @@ class Machine:
time.sleep(delay)
def send_console(self, chars: str) -> None:
r"""
Send keys to the kernel console. This allows interaction with the systemd
emergency mode, for example. Takes a string that is sent, e.g.,
`send_console("\n\nsystemctl default\n")`.
"""
assert self.process
assert self.process.stdin
self.process.stdin.write(chars.encode())
self.process.stdin.flush()
def start(self, allow_reboot: bool = False) -> None:
"""
Start the virtual machine. This method is asynchronous --- it does
not wait for the machine to finish booting.
"""
if self.booted:
return
@ -974,6 +1115,9 @@ class Machine:
rootlog.log("if you want to keep the VM state, pass --keep-vm-state")
def shutdown(self) -> None:
"""
Shut down the machine, waiting for the VM to exit.
"""
if not self.booted:
return
@ -982,6 +1126,9 @@ class Machine:
self.wait_for_shutdown()
def crash(self) -> None:
"""
Simulate a sudden power failure, by telling the VM to exit immediately.
"""
if not self.booted:
return
@ -999,8 +1146,8 @@ class Machine:
self.connected = False
def wait_for_x(self) -> None:
"""Wait until it is possible to connect to the X server. Note that
testing the existence of /tmp/.X11-unix/X0 is insufficient.
"""
Wait until it is possible to connect to the X server.
"""
def check_x(_: Any) -> bool:
@ -1023,6 +1170,10 @@ class Machine:
).splitlines()
def wait_for_window(self, regexp: str) -> None:
"""
Wait until an X11 window has appeared whose name matches the given
regular expression, e.g., `wait_for_window("Terminal")`.
"""
pattern = re.compile(regexp)
def window_is_visible(last_try: bool) -> bool:
@ -1043,20 +1194,26 @@ class Machine:
self.succeed(f"sleep {secs}")
def forward_port(self, host_port: int = 8080, guest_port: int = 80) -> None:
"""Forward a TCP port on the host to a TCP port on the guest.
"""
Forward a TCP port on the host to a TCP port on the guest.
Useful during interactive testing.
"""
self.send_monitor_command(f"hostfwd_add tcp::{host_port}-:{guest_port}")
def block(self) -> None:
"""Make the machine unreachable by shutting down eth1 (the multicast
interface used to talk to the other VMs). We keep eth0 up so that
the test driver can continue to talk to the machine.
"""
Simulate unplugging the Ethernet cable that connects the machine to
the other machines.
This happens by shutting down eth1 (the multicast interface used to talk
to the other VMs). eth0 is kept online to still enable the test driver
to communicate with the machine.
"""
self.send_monitor_command("set_link virtio-net-pci.1 off")
def unblock(self) -> None:
"""Make the machine reachable."""
"""
Undo the effect of `block`.
"""
self.send_monitor_command("set_link virtio-net-pci.1 on")
def release(self) -> None:

View file

@ -65,7 +65,8 @@ let
echo "${builtins.toString vlanNames}" >> testScriptWithTypes
echo -n "$testScript" >> testScriptWithTypes
cat -n testScriptWithTypes
echo "Running type check (enable/disable: config.skipTypeCheck)"
echo "See https://nixos.org/manual/nixos/stable/#test-opt-skipTypeCheck"
mypy --no-implicit-optional \
--pretty \
@ -79,6 +80,9 @@ let
${testDriver}/bin/generate-driver-symbols
${lib.optionalString (!config.skipLint) ''
echo "Linting test script (enable/disable: config.skipLint)"
echo "See https://nixos.org/manual/nixos/stable/#test-opt-skipLint"
PYFLAKES_BUILTINS="$(
echo -n ${lib.escapeShellArg (lib.concatStringsSep "," pythonizedNames)},
< ${lib.escapeShellArg "driver-symbols"}

View file

@ -42,7 +42,7 @@ let
# looking things up.
makeCacheConf = { }:
let
makeCache = fontconfig: pkgs.makeFontsCache { inherit fontconfig; fontDirectories = config.fonts.fonts; };
makeCache = fontconfig: pkgs.makeFontsCache { inherit fontconfig; fontDirectories = config.fonts.packages; };
cache = makeCache pkgs.fontconfig;
cache32 = makeCache pkgs.pkgsi686Linux.fontconfig;
in
@ -51,7 +51,7 @@ let
<!DOCTYPE fontconfig SYSTEM 'urn:fontconfig:fonts.dtd'>
<fontconfig>
<!-- Font directories -->
${concatStringsSep "\n" (map (font: "<dir>${font}</dir>") config.fonts.fonts)}
${concatStringsSep "\n" (map (font: "<dir>${font}</dir>") config.fonts.packages)}
${optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) ''
<!-- Pre-generated font caches -->
<cachedir>${cache}</cachedir>

View file

@ -9,7 +9,7 @@ let
x11Fonts = pkgs.runCommand "X11-fonts" { preferLocalBuild = true; } ''
mkdir -p "$out/share/X11/fonts"
font_regexp='.*\.\(ttf\|ttc\|otb\|otf\|pcf\|pfa\|pfb\|bdf\)\(\.gz\)?'
find ${toString config.fonts.fonts} -regex "$font_regexp" \
find ${toString config.fonts.packages} -regex "$font_regexp" \
-exec ln -sf -t "$out/share/X11/fonts" '{}' \;
cd "$out/share/X11/fonts"
${optionalString cfg.decompressFonts ''

View file

@ -1,47 +0,0 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.fonts;
defaultFonts =
[ pkgs.dejavu_fonts
pkgs.freefont_ttf
pkgs.gyre-fonts # TrueType substitutes for standard PostScript fonts
pkgs.liberation_ttf
pkgs.unifont
pkgs.noto-fonts-emoji
];
in
{
imports = [
(mkRemovedOptionModule [ "fonts" "enableCoreFonts" ] "Use fonts.fonts = [ pkgs.corefonts ]; instead.")
];
options = {
fonts = {
# TODO: find another name for it.
fonts = mkOption {
type = types.listOf types.path;
default = [];
example = literalExpression "[ pkgs.dejavu_fonts ]";
description = lib.mdDoc "List of primary font paths.";
};
enableDefaultFonts = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
Enable a basic set of fonts providing several font styles
and families and reasonable coverage of Unicode.
'';
};
};
};
config = { fonts.fonts = mkIf cfg.enableDefaultFonts defaultFonts; };
}

View file

@ -3,31 +3,21 @@
with lib;
{
options = {
fonts = {
enableGhostscriptFonts = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
Whether to add the fonts provided by Ghostscript (such as
various URW fonts and the Base-14 Postscript fonts) to the
list of system fonts, making them available to X11
applications.
'';
};
fonts.enableGhostscriptFonts = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
Whether to add the fonts provided by Ghostscript (such as
various URW fonts and the Base-14 Postscript fonts) to the
list of system fonts, making them available to X11
applications.
'';
};
};
config = mkIf config.fonts.enableGhostscriptFonts {
fonts.fonts = [ "${pkgs.ghostscript}/share/ghostscript/fonts" ];
fonts.packages = [ "${pkgs.ghostscript}/share/ghostscript/fonts" ];
};
}

View file

@ -0,0 +1,43 @@
{ config, lib, pkgs, ... }:
let
cfg = config.fonts;
in
{
imports = [
(lib.mkRemovedOptionModule [ "fonts" "enableCoreFonts" ] "Use fonts.packages = [ pkgs.corefonts ]; instead.")
(lib.mkRenamedOptionModule [ "fonts" "enableDefaultFonts" ] [ "fonts" "enableDefaultPackages" ])
(lib.mkRenamedOptionModule [ "fonts" "fonts" ] [ "fonts" "packages" ])
];
options = {
fonts = {
packages = lib.mkOption {
type = with lib.types; listOf path;
default = [];
example = lib.literalExpression "[ pkgs.dejavu_fonts ]";
description = lib.mdDoc "List of primary font packages.";
};
enableDefaultPackages = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc ''
Enable a basic set of fonts providing several styles
and families and reasonable coverage of Unicode.
'';
};
};
};
config = {
fonts.packages = lib.mkIf cfg.enableDefaultPackages (with pkgs; [
dejavu_fonts
freefont_ttf
gyre-fonts # TrueType substitutes for standard PostScript fonts
liberation_ttf
unifont
noto-fonts-emoji
]);
};
}

View file

@ -3,12 +3,13 @@
configuration to work.
See also
- ./nix.nix
- ./nix-flakes.nix
- ./nix.nix
- ./nix-flakes.nix
*/
{ config, lib, ... }:
let
inherit (lib)
mkDefault
mkIf
mkOption
stringAfter
@ -21,13 +22,42 @@ in
{
options = {
nix = {
channel = {
enable = mkOption {
description = lib.mdDoc ''
Whether the `nix-channel` command and state files are made available on the machine.
The following files are initialized when enabled:
- `/nix/var/nix/profiles/per-user/root/channels`
- `/root/.nix-channels`
- `$HOME/.nix-defexpr/channels` (on login)
Disabling this option will not remove the state files from the system.
'';
type = types.bool;
default = true;
};
};
nixPath = mkOption {
type = types.listOf types.str;
default = [
"nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos"
"nixos-config=/etc/nixos/configuration.nix"
"/nix/var/nix/profiles/per-user/root/channels"
];
default =
if cfg.channel.enable
then [
"nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos"
"nixos-config=/etc/nixos/configuration.nix"
"/nix/var/nix/profiles/per-user/root/channels"
]
else [ ];
defaultText = ''
if nix.channel.enable
then [
"nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos"
"nixos-config=/etc/nixos/configuration.nix"
"/nix/var/nix/profiles/per-user/root/channels"
]
else [];
'';
description = lib.mdDoc ''
The default Nix expression search path, used by the Nix
evaluator to look up paths enclosed in angle brackets
@ -49,22 +79,30 @@ in
config = mkIf cfg.enable {
environment.extraInit =
''
mkIf cfg.channel.enable ''
if [ -e "$HOME/.nix-defexpr/channels" ]; then
export NIX_PATH="$HOME/.nix-defexpr/channels''${NIX_PATH:+:$NIX_PATH}"
fi
'';
environment.extraSetup = mkIf (!cfg.channel.enable) ''
rm --force $out/bin/nix-channel
'';
# NIX_PATH has a non-empty default according to Nix docs, so we don't unset
# it when empty.
environment.sessionVariables = {
NIX_PATH = cfg.nixPath;
};
system.activationScripts.nix-channel = stringAfter [ "etc" "users" ]
''
nix.settings.nix-path = mkIf (! cfg.channel.enable) (mkDefault "");
system.activationScripts.nix-channel = mkIf cfg.channel.enable
(stringAfter [ "etc" "users" ] ''
# Subscribe the root user to the NixOS channel by default.
if [ ! -e "/root/.nix-channels" ]; then
echo "${config.system.defaultChannel} nixos" > "/root/.nix-channels"
fi
'';
'');
};
}

View file

@ -26,7 +26,12 @@ with lib;
fonts.fontconfig.enable = false;
nixpkgs.overlays = singleton (const (super: {
nixpkgs.overlays = singleton (self: super: let
packageOverrides = const (python-prev: {
# tk feature requires wayland which fails to compile
matplotlib = python-prev.matplotlib.override { enableGtk3 = false; enableTk = false; enableQt = false; };
});
in {
beam = super.beam_nox;
cairo = super.cairo.override { x11Support = false; };
dbus = super.dbus.override { x11Support = false; };
@ -62,6 +67,8 @@ with lib;
pango = super.pango.override { x11Support = false; };
pinentry = super.pinentry.override { enabledFlavors = [ "curses" "tty" "emacs" ]; withLibsecret = false; };
pipewire = super.pipewire.override { x11Support = false; };
python3 = super.python3.override { inherit packageOverrides; };
python3Packages = self.python3.pkgs; # required otherwise overlays from above are not forwarded
qemu = super.qemu.override { gtkSupport = false; spiceSupport = false; sdlSupport = false; };
qrencode = super.qrencode.overrideAttrs (_: { doCheck = false; });
qt5 = super.qt5.overrideScope (const (super': {
@ -72,6 +79,6 @@ with lib;
util-linux = super.util-linux.override { translateManpages = false; };
vim-full = super.vim-full.override { guiSupport = false; };
zbar = super.zbar.override { enableVideo = false; withXorg = false; };
}));
});
};
}

View file

@ -19,7 +19,7 @@ let
pkgs.qgnomeplatform-qt6
pkgs.adwaita-qt6
]
else if isQtStyle then [ pkgs.libsForQt5.qtstyleplugins ]
else if isQtStyle then [ pkgs.libsForQt5.qtstyleplugins pkgs.qt6Packages.qt6gtk2 ]
else if isQt5ct then [ pkgs.libsForQt5.qt5ct pkgs.qt6Packages.qt6ct ]
else if isLxqt then [ pkgs.lxqt.lxqt-qtplugin pkgs.lxqt.lxqt-config ]
else if isKde then [ pkgs.libsForQt5.plasma-integration pkgs.libsForQt5.systemsettings ]
@ -86,6 +86,7 @@ in
"adwaita-qt"
"adwaita-qt6"
["libsForQt5" "qtstyleplugins"]
["qt6Packages" "qt6gtk2"]
];
description = lib.mdDoc ''
Selects the style to use for Qt applications.

View file

@ -252,6 +252,11 @@ in
let realDevice' = escapeSystemdPath sw.realDevice;
in nameValuePair "mkswap-${sw.deviceName}"
{ description = "Initialisation of swap device ${sw.device}";
# The mkswap service fails for file-backed swap devices if the
# loop module has not been loaded before the service runs.
# We add an ordering constraint to run after systemd-modules-load to
# avoid this race condition.
after = [ "systemd-modules-load.service" ];
wantedBy = [ "${realDevice'}.swap" ];
before = [ "${realDevice'}.swap" ];
path = [ pkgs.util-linux pkgs.e2fsprogs ]

View file

@ -147,7 +147,7 @@ foreach my $g (@{$spec->{groups}}) {
if (defined $existing) {
$g->{gid} = $existing->{gid} if !defined $g->{gid};
if ($g->{gid} != $existing->{gid}) {
dry_print("warning: not applying", "warning: would not apply", "GID change of group $name ($existing->{gid} -> $g->{gid})");
dry_print("warning: not applying", "warning: would not apply", "GID change of group $name ($existing->{gid} -> $g->{gid}) in /etc/group");
$g->{gid} = $existing->{gid};
}
$g->{password} = $existing->{password}; # do we want this?
@ -209,7 +209,7 @@ foreach my $u (@{$spec->{users}}) {
if (defined $existing) {
$u->{uid} = $existing->{uid} if !defined $u->{uid};
if ($u->{uid} != $existing->{uid}) {
dry_print("warning: not applying", "warning: would not apply", "UID change of user $name ($existing->{uid} -> $u->{uid})");
dry_print("warning: not applying", "warning: would not apply", "UID change of user $name ($existing->{uid} -> $u->{uid}) in /etc/passwd");
$u->{uid} = $existing->{uid};
}
} else {

View file

@ -7,12 +7,15 @@ with lib;
options = {
hardware.usbWwan = {
hardware.usb-modeswitch = {
enable = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
Enable this option to support USB WWAN adapters.
Enable this option to support certain USB WLAN and WWAN adapters.
These network adapters initial present themselves as Flash Drives containing their drivers.
This option enables automatic switching to the networking mode.
'';
};
};
@ -20,7 +23,11 @@ with lib;
###### implementation
config = mkIf config.hardware.usbWwan.enable {
imports = [
(mkRenamedOptionModule ["hardware" "usbWwan" ] ["hardware" "usb-modeswitch" ])
];
config = mkIf config.hardware.usb-modeswitch.enable {
# Attaches device specific handlers.
services.udev.packages = with pkgs; [ usb-modeswitch-data ];

View file

@ -2,8 +2,8 @@
with lib;
{
options.hardware.wooting.enable =
mkEnableOption (lib.mdDoc "support for Wooting keyboards");
options.hardware.wooting.enable = mkEnableOption (lib.mdDoc ''support for Wooting keyboards.
Note that users must be in the "input" group for udev rules to apply'');
config = mkIf config.hardware.wooting.enable {
environment.systemPackages = [ pkgs.wootility ];

View file

@ -0,0 +1,113 @@
#!/usr/bin/env python
"""Amend systemd-repart definiton files.
In order to avoid Import-From-Derivation (IFD) when building images with
systemd-repart, the definition files created by Nix need to be amended with the
store paths from the closure.
This is achieved by adding CopyFiles= instructions to the definition files.
The arbitrary files configured via `contents` are also added to the definition
files using the same mechanism.
"""
import json
import sys
import shutil
import os
import tempfile
from pathlib import Path
def add_contents_to_definition(
definition: Path, contents: dict[str, dict[str, str]] | None
) -> None:
"""Add CopyFiles= instructions to a definition for all files in contents."""
if not contents:
return
copy_files_lines: list[str] = []
for target, options in contents.items():
source = options["source"]
copy_files_lines.append(f"CopyFiles={source}:{target}\n")
with open(definition, "a") as f:
f.writelines(copy_files_lines)
def add_closure_to_definition(
definition: Path, closure: Path | None, strip_nix_store_prefix: bool | None
) -> None:
"""Add CopyFiles= instructions to a definition for all paths in the closure.
If strip_nix_store_prefix is True, `/nix/store` is stripped from the target path.
"""
if not closure:
return
copy_files_lines: list[str] = []
with open(closure, "r") as f:
for line in f:
if not isinstance(line, str):
continue
source = Path(line.strip())
target = str(source.relative_to("/nix/store/"))
target = f":{target}" if strip_nix_store_prefix else ""
copy_files_lines.append(f"CopyFiles={source}{target}\n")
with open(definition, "a") as f:
f.writelines(copy_files_lines)
def main() -> None:
"""Amend the provided repart definitions by adding CopyFiles= instructions.
For each file specified in the `contents` field of a partition in the
partiton config file, a `CopyFiles=` instruction is added to the
corresponding definition file.
The same is done for every store path of the `closure` field.
Print the path to a directory that contains the amended repart
definitions to stdout.
"""
partition_config_file = sys.argv[1]
if not partition_config_file:
print("No partition config file was supplied.")
sys.exit(1)
repart_definitions = sys.argv[2]
if not repart_definitions:
print("No repart definitions were supplied.")
sys.exit(1)
with open(partition_config_file, "rb") as f:
partition_config = json.load(f)
if not partition_config:
print("Partition config is empty.")
sys.exit(1)
temp = tempfile.mkdtemp()
shutil.copytree(repart_definitions, temp, dirs_exist_ok=True)
for name, config in partition_config.items():
definition = Path(f"{temp}/{name}.conf")
os.chmod(definition, 0o644)
contents = config.get("contents")
add_contents_to_definition(definition, contents)
closure = config.get("closure")
strip_nix_store_prefix = config.get("stripStorePaths")
add_closure_to_definition(definition, closure, strip_nix_store_prefix)
print(temp)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,137 @@
# Building Images via `systemd-repart` {#sec-image-repart}
You can build disk images in NixOS with the `image.repart` option provided by
the module [image/repart.nix][]. This module uses `systemd-repart` to build the
images and exposes it's entire interface via the `repartConfig` option.
[image/repart.nix]: https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/image/repart.nix
An example of how to build an image:
```nix
{ config, modulesPath, ... }: {
imports = [ "${modulesPath}/image/repart.nix" ];
image.repart = {
name = "image";
partitions = {
"esp" = {
contents = {
...
};
repartConfig = {
Type = "esp";
...
};
};
"root" = {
storePaths = [ config.system.build.toplevel ];
repartConfig = {
Type = "root";
Label = "nixos";
...
};
};
};
};
}
```
## Nix Store Partition {#sec-image-repart-store-partition}
You can define a partition that only contains the Nix store and then mount it
under `/nix/store`. Because the `/nix/store` part of the paths is already
determined by the mount point, you have to set `stripNixStorePrefix = true;` so
that the prefix is stripped from the paths before copying them into the image.
```nix
fileSystems."/nix/store".device = "/dev/disk/by-partlabel/nix-store"
image.repart.partitions = {
"store" = {
storePaths = [ config.system.build.toplevel ];
stripNixStorePrefix = true;
repartConfig = {
Type = "linux-generic";
Label = "nix-store";
...
};
};
};
```
## Appliance Image {#sec-image-repart-appliance}
The `image/repart.nix` module can also be used to build self-contained [software
appliances][].
[software appliances]: https://en.wikipedia.org/wiki/Software_appliance
The generation based update mechanism of NixOS is not suited for appliances.
Updates of appliances are usually either performed by replacing the entire
image with a new one or by updating partitions via an A/B scheme. See the
[Chrome OS update process][chrome-os-update] for an example of how to achieve
this. The appliance image built in the following example does not contain a
`configuration.nix` and thus you will not be able to call `nixos-rebuild` from
this system.
[chrome-os-update]: https://chromium.googlesource.com/aosp/platform/system/update_engine/+/HEAD/README.md
```nix
let
pkgs = import <nixpkgs> { };
efiArch = pkgs.stdenv.hostPlatform.efiArch;
in
(pkgs.nixos [
({ config, lib, pkgs, modulesPath, ... }: {
imports = [ "${modulesPath}/image/repart.nix" ];
boot.loader.grub.enable = false;
fileSystems."/".device = "/dev/disk/by-label/nixos";
image.repart = {
name = "image";
partitions = {
"esp" = {
contents = {
"/EFI/BOOT/BOOT${lib.toUpper efiArch}.EFI".source =
"${pkgs.systemd}/lib/systemd/boot/efi/systemd-boot${efiArch}.efi";
"/loader/entries/nixos.conf".source = pkgs.writeText "nixos.conf" ''
title NixOS
linux /EFI/nixos/kernel.efi
initrd /EFI/nixos/initrd.efi
options init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}
'';
"/EFI/nixos/kernel.efi".source =
"${config.boot.kernelPackages.kernel}/${config.system.boot.loader.kernelFile}";
"/EFI/nixos/initrd.efi".source =
"${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}";
};
repartConfig = {
Type = "esp";
Format = "vfat";
SizeMinBytes = "96M";
};
};
"root" = {
storePaths = [ config.system.build.toplevel ];
repartConfig = {
Type = "root";
Format = "ext4";
Label = "nixos";
Minimize = "guess";
};
};
};
};
})
]).image
```

View file

@ -0,0 +1,207 @@
# This module exposes options to build a disk image with a GUID Partition Table
# (GPT). It uses systemd-repart to build the image.
{ config, pkgs, lib, utils, ... }:
let
cfg = config.image.repart;
partitionOptions = {
options = {
storePaths = lib.mkOption {
type = with lib.types; listOf path;
default = [ ];
description = lib.mdDoc "The store paths to include in the partition.";
};
stripNixStorePrefix = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc ''
Whether to strip `/nix/store/` from the store paths. This is useful
when you want to build a partition that only contains store paths and
is mounted under `/nix/store`.
'';
};
contents = lib.mkOption {
type = with lib.types; attrsOf (submodule {
options = {
source = lib.mkOption {
type = types.path;
description = lib.mdDoc "Path of the source file.";
};
};
});
default = { };
example = lib.literalExpression '' {
"/EFI/BOOT/BOOTX64.EFI".source =
"''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi";
"/loader/entries/nixos.conf".source = systemdBootEntry;
}
'';
description = lib.mdDoc "The contents to end up in the filesystem image.";
};
repartConfig = lib.mkOption {
type = with lib.types; attrsOf (oneOf [ str int bool ]);
example = {
Type = "home";
SizeMinBytes = "512M";
SizeMaxBytes = "2G";
};
description = lib.mdDoc ''
Specify the repart options for a partiton as a structural setting.
See <https://www.freedesktop.org/software/systemd/man/repart.d.html>
for all available options.
'';
};
};
};
in
{
options.image.repart = {
name = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "The name of the image.";
};
seed = lib.mkOption {
type = with lib.types; nullOr str;
# Generated with `uuidgen`. Random but fixed to improve reproducibility.
default = "0867da16-f251-457d-a9e8-c31f9a3c220b";
description = lib.mdDoc ''
A UUID to use as a seed. You can set this to `null` to explicitly
randomize the partition UUIDs.
'';
};
split = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc ''
Enables generation of split artifacts from partitions. If enabled, for
each partition with SplitName= set, a separate output file containing
just the contents of that partition is generated.
'';
};
partitions = lib.mkOption {
type = with lib.types; attrsOf (submodule partitionOptions);
default = { };
example = lib.literalExpression '' {
"10-esp" = {
contents = {
"/EFI/BOOT/BOOTX64.EFI".source =
"''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi";
}
repartConfig = {
Type = "esp";
Format = "fat";
};
};
"20-root" = {
storePaths = [ config.system.build.toplevel ];
repartConfig = {
Type = "root";
Format = "ext4";
Minimize = "guess";
};
};
};
'';
description = lib.mdDoc ''
Specify partitions as a set of the names of the partitions with their
configuration as the key.
'';
};
};
config = {
system.build.image =
let
fileSystemToolMapping = with pkgs; {
"vfat" = [ dosfstools mtools ];
"ext4" = [ e2fsprogs.bin ];
"squashfs" = [ squashfsTools ];
"erofs" = [ erofs-utils ];
"btrfs" = [ btrfs-progs ];
"xfs" = [ xfsprogs ];
};
fileSystems = lib.filter
(f: f != null)
(lib.mapAttrsToList (_n: v: v.repartConfig.Format or null) cfg.partitions);
fileSystemTools = builtins.concatMap (f: fileSystemToolMapping."${f}") fileSystems;
makeClosure = paths: pkgs.closureInfo { rootPaths = paths; };
# Add the closure of the provided Nix store paths to cfg.partitions so
# that amend-repart-definitions.py can read it.
addClosure = _name: partitionConfig: partitionConfig // (
lib.optionalAttrs
(partitionConfig.storePaths or [ ] != [ ])
{ closure = "${makeClosure partitionConfig.storePaths}/store-paths"; }
);
finalPartitions = lib.mapAttrs addClosure cfg.partitions;
amendRepartDefinitions = pkgs.runCommand "amend-repart-definitions.py"
{
nativeBuildInputs = with pkgs; [ black ruff mypy ];
buildInputs = [ pkgs.python3 ];
} ''
install ${./amend-repart-definitions.py} $out
patchShebangs --host $out
black --check --diff $out
ruff --line-length 88 $out
mypy --strict $out
'';
format = pkgs.formats.ini { };
definitionsDirectory = utils.systemdUtils.lib.definitions
"repart.d"
format
(lib.mapAttrs (_n: v: { Partition = v.repartConfig; }) finalPartitions);
partitions = pkgs.writeText "partitions.json" (builtins.toJSON finalPartitions);
in
pkgs.runCommand cfg.name
{
nativeBuildInputs = with pkgs; [
fakeroot
systemd
] ++ fileSystemTools;
} ''
amendedRepartDefinitions=$(${amendRepartDefinitions} ${partitions} ${definitionsDirectory})
mkdir -p $out
cd $out
fakeroot systemd-repart \
--dry-run=no \
--empty=create \
--size=auto \
--seed="${cfg.seed}" \
--definitions="$amendedRepartDefinitions" \
--split="${lib.boolToString cfg.split}" \
image.raw
'';
meta = {
maintainers = with lib.maintainers; [ nikstur ];
doc = ./repart.md;
};
};
}

View file

@ -39,9 +39,7 @@ with lib;
# !!! Hack - attributes expected by other modules.
environment.systemPackages = [ pkgs.grub2_efi ]
++ (if pkgs.stdenv.hostPlatform.system == "aarch64-linux"
then []
else [ pkgs.grub2 pkgs.syslinux ]);
++ (lib.optionals (pkgs.stdenv.hostPlatform.system != "aarch64-linux") [pkgs.grub2 pkgs.syslinux]);
fileSystems."/" = mkImageMediaOverride
{ fsType = "tmpfs";

View file

@ -381,6 +381,7 @@ sub in {
my $fileSystems;
my %fsByDev;
my $useSwraid = 0;
foreach my $fs (read_file("/proc/self/mountinfo")) {
chomp $fs;
my @fields = split / /, $fs;
@ -510,8 +511,8 @@ EOF
# boot.initrd.luks.devices entry.
if (-e $device) {
my $deviceName = basename(abs_path($device));
if (-e "/sys/class/block/$deviceName"
&& read_file("/sys/class/block/$deviceName/dm/uuid", err_mode => 'quiet') =~ /^CRYPT-LUKS/)
my $dmUuid = read_file("/sys/class/block/$deviceName/dm/uuid", err_mode => 'quiet');
if ($dmUuid =~ /^CRYPT-LUKS/)
{
my @slaves = glob("/sys/class/block/$deviceName/slaves/*");
if (scalar @slaves == 1) {
@ -527,8 +528,14 @@ EOF
}
}
}
if (-e "/sys/class/block/$deviceName/md/uuid") {
$useSwraid = 1;
}
}
}
if ($useSwraid) {
push @attrs, "boot.swraid.enable = true;\n\n";
}
# Generate the hardware configuration file.

View file

@ -42,10 +42,7 @@ let
xserverEnabled = config.services.xserver.enable;
};
nixos-option =
if lib.versionAtLeast (lib.getVersion config.nix.package) "2.4pre"
then null
else pkgs.nixos-option;
inherit (pkgs) nixos-option;
nixos-version = makeProg {
name = "nixos-version";
@ -129,7 +126,7 @@ in
# your system. Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running `nixos-help`).
{ config, pkgs, ... }:
{ config, lib, pkgs, ... }:
{
imports =
@ -232,9 +229,10 @@ in
nixos-install
nixos-rebuild
nixos-generate-config
nixos-option
nixos-version
nixos-enter
] ++ lib.optional (nixos-option != null) nixos-option;
];
documentation.man.man-db.skipPackages = [ nixos-version ];

View file

@ -4,8 +4,8 @@
./config/debug-info.nix
./config/fonts/fontconfig.nix
./config/fonts/fontdir.nix
./config/fonts/fonts.nix
./config/fonts/ghostscript.nix
./config/fonts/packages.nix
./config/gnu.nix
./config/gtk/gtk-icon-cache.nix
./config/i18n.nix
@ -93,8 +93,8 @@
./hardware/tuxedo-keyboard.nix
./hardware/ubertooth.nix
./hardware/uinput.nix
./hardware/usb-modeswitch.nix
./hardware/usb-storage.nix
./hardware/usb-wwan.nix
./hardware/video/amdgpu-pro.nix
./hardware/video/bumblebee.nix
./hardware/video/capture/mwprocapture.nix
@ -160,6 +160,7 @@
./programs/darling.nix
./programs/dconf.nix
./programs/digitalbitbox/default.nix
./programs/direnv.nix
./programs/dmrconfig.nix
./programs/droidcam.nix
./programs/environment.nix
@ -221,6 +222,7 @@
./programs/noisetorch.nix
./programs/npm.nix
./programs/oblogout.nix
./programs/oddjobd.nix
./programs/openvpn3.nix
./programs/pantheon-tweaks.nix
./programs/partition-manager.nix
@ -262,6 +264,7 @@
./programs/wayland/river.nix
./programs/wayland/sway.nix
./programs/wayland/waybar.nix
./programs/wayland/wayfire.nix
./programs/weylus.nix
./programs/wireshark.nix
./programs/xastir.nix
@ -416,6 +419,7 @@
./services/databases/neo4j.nix
./services/databases/openldap.nix
./services/databases/opentsdb.nix
./services/databases/pgbouncer.nix
./services/databases/pgmanage.nix
./services/databases/postgresql.nix
./services/databases/redis.nix
@ -534,6 +538,7 @@
./services/hardware/usbrelayd.nix
./services/hardware/vdr.nix
./services/hardware/keyd.nix
./services/home-automation/ebusd.nix
./services/home-automation/esphome.nix
./services/home-automation/evcc.nix
./services/home-automation/home-assistant.nix
@ -597,6 +602,7 @@
./services/matrix/mjolnir.nix
./services/matrix/mx-puppet-discord.nix
./services/matrix/pantalaimon.nix
./services/matrix/matrix-sliding-sync.nix
./services/matrix/synapse.nix
./services/misc/airsonic.nix
./services/misc/ananicy.nix
@ -607,6 +613,7 @@
./services/misc/autorandr.nix
./services/misc/autosuspend.nix
./services/misc/bazarr.nix
./services/misc/bcg.nix
./services/misc/beanstalkd.nix
./services/misc/bees.nix
./services/misc/bepasty.nix
@ -630,6 +637,7 @@
./services/misc/etcd.nix
./services/misc/etebase-server.nix
./services/misc/etesync-dav.nix
./services/misc/evdevremapkeys.nix
./services/misc/felix.nix
./services/misc/freeswitch.nix
./services/misc/fstrim.nix
@ -645,6 +653,7 @@
./services/misc/greenclip.nix
./services/misc/headphones.nix
./services/misc/heisenbridge.nix
./services/misc/homepage-dashboard.nix
./services/misc/ihaskell.nix
./services/misc/input-remapper.nix
./services/misc/irkerd.nix
@ -663,6 +672,7 @@
./services/misc/mediatomb.nix
./services/misc/metabase.nix
./services/misc/moonraker.nix
./services/misc/mqtt2influxdb.nix
./services/misc/n8n.nix
./services/misc/nitter.nix
./services/misc/nix-gc.nix
@ -759,6 +769,7 @@
./services/monitoring/nagios.nix
./services/monitoring/netdata.nix
./services/monitoring/opentelemetry-collector.nix
./services/monitoring/osquery.nix
./services/monitoring/parsedmarc.nix
./services/monitoring/prometheus/alertmanager-irc-relay.nix
./services/monitoring/prometheus/alertmanager.nix
@ -1057,6 +1068,7 @@
./services/networking/tox-node.nix
./services/networking/toxvpn.nix
./services/networking/trickster.nix
./services/networking/trust-dns.nix
./services/networking/tvheadend.nix
./services/networking/twingate.nix
./services/networking/ucarp.nix
@ -1100,6 +1112,7 @@
./services/search/meilisearch.nix
./services/search/opensearch.nix
./services/search/qdrant.nix
./services/search/typesense.nix
./services/security/aesmd.nix
./services/security/authelia.nix
./services/security/certmgr.nix
@ -1107,6 +1120,7 @@
./services/security/clamav.nix
./services/security/endlessh-go.nix
./services/security/endlessh.nix
./services/security/esdm.nix
./services/security/fail2ban.nix
./services/security/fprintd.nix
./services/security/haka.nix
@ -1138,6 +1152,7 @@
./services/security/vaultwarden/default.nix
./services/security/yubikey-agent.nix
./services/system/automatic-timezoned.nix
./services/system/bpftune.nix
./services/system/cachix-agent/default.nix
./services/system/cachix-watch-store.nix
./services/system/cloud-init.nix
@ -1251,6 +1266,7 @@
./services/web-apps/rss-bridge.nix
./services/web-apps/selfoss.nix
./services/web-apps/shiori.nix
./services/web-apps/slskd.nix
./services/web-apps/snipe-it.nix
./services/web-apps/sogo.nix
./services/web-apps/trilium.nix
@ -1383,6 +1399,7 @@
./system/boot/systemd/oomd.nix
./system/boot/systemd/repart.nix
./system/boot/systemd/shutdown.nix
./system/boot/systemd/sysupdate.nix
./system/boot/systemd/tmpfiles.nix
./system/boot/systemd/user.nix
./system/boot/systemd/userdbd.nix

View file

@ -106,6 +106,8 @@ with lib;
systemdStage1Network
];
boot.swraid.enable = true;
# Show all debug messages from the kernel but don't log refused packets
# because we have the firewall enabled. This makes installs from the
# console less cumbersome if the machine has a public IP.

View file

@ -21,7 +21,8 @@ in
../virtualisation/nixos-containers.nix
../services/x11/desktop-managers/xterm.nix
];
config = { };
# swraid's default depends on stateVersion
config.boot.swraid.enable = false;
options.boot.isContainer = lib.mkOption { default = false; internal = true; };
}
];

View file

@ -123,8 +123,8 @@ in
boot.extraModulePackages = [ (lib.mkIf cfg.netatop.enable cfg.netatop.package) ];
systemd =
let
mkSystemd = type: cond: name: restartTriggers: {
${name} = lib.mkIf cond {
mkSystemd = type: name: restartTriggers: {
${name} = {
inherit restartTriggers;
wantedBy = [ (if type == "services" then "multi-user.target" else if type == "timers" then "timers.target" else null) ];
};
@ -134,42 +134,44 @@ in
in
{
packages = [ atop (lib.mkIf cfg.netatop.enable cfg.netatop.package) ];
services =
mkService cfg.atopService.enable "atop" [ atop ]
// lib.mkIf cfg.atopService.enable {
# always convert logs to newer version first
# XXX might trigger TimeoutStart but restarting atop.service will
# convert remainings logs and start eventually
atop.serviceConfig.ExecStartPre = pkgs.writeShellScript "atop-update-log-format" ''
set -e -u
shopt -s nullglob
for logfile in "$LOGPATH"/atop_*
do
${atop}/bin/atopconvert "$logfile" "$logfile".new
# only replace old file if version was upgraded to avoid
# false positives for atop-rotate.service
if ! ${pkgs.diffutils}/bin/cmp -s "$logfile" "$logfile".new
then
${pkgs.coreutils}/bin/mv -v -f "$logfile".new "$logfile"
else
${pkgs.coreutils}/bin/rm -f "$logfile".new
fi
done
'';
}
// mkService cfg.atopacctService.enable "atopacct" [ atop ]
// mkService cfg.netatop.enable "netatop" [ cfg.netatop.package ]
// mkService cfg.atopgpu.enable "atopgpu" [ atop ];
timers = mkTimer cfg.atopRotateTimer.enable "atop-rotate" [ atop ];
services = lib.mkMerge [
(lib.mkIf cfg.atopService.enable (lib.recursiveUpdate
(mkService "atop" [ atop ])
{
# always convert logs to newer version first
# XXX might trigger TimeoutStart but restarting atop.service will
# convert remainings logs and start eventually
atop.preStart = ''
set -e -u
shopt -s nullglob
for logfile in "$LOGPATH"/atop_*
do
${atop}/bin/atopconvert "$logfile" "$logfile".new
# only replace old file if version was upgraded to avoid
# false positives for atop-rotate.service
if ! ${pkgs.diffutils}/bin/cmp -s "$logfile" "$logfile".new
then
${pkgs.coreutils}/bin/mv -v -f "$logfile".new "$logfile"
else
${pkgs.coreutils}/bin/rm -f "$logfile".new
fi
done
'';
}))
(lib.mkIf cfg.atopacctService.enable (mkService "atopacct" [ atop ]))
(lib.mkIf cfg.netatop.enable (mkService "netatop" [ cfg.netatop.package ]))
(lib.mkIf cfg.atopgpu.enable (mkService "atopgpu" [ atop ]))
];
timers = lib.mkIf cfg.atopRotateTimer.enable (mkTimer "atop-rotate" [ atop ]);
};
security.wrappers = lib.mkIf cfg.setuidWrapper.enable {
atop =
{ setuid = true;
owner = "root";
group = "root";
source = "${atop}/bin/atop";
};
atop = {
setuid = true;
owner = "root";
group = "root";
source = "${atop}/bin/atop";
};
};
}
);

View file

@ -0,0 +1,147 @@
{
lib,
config,
pkgs,
...
}: let
cfg = config.programs.direnv;
in {
options.programs.direnv = {
enable = lib.mkEnableOption (lib.mdDoc ''
direnv integration. Takes care of both installation and
setting up the sourcing of the shell. Additionally enables nix-direnv
integration. Note that you need to logout and login for this change to apply.
'');
package = lib.mkPackageOptionMD pkgs "direnv" {};
direnvrcExtra = lib.mkOption {
type = lib.types.lines;
default = "";
example = ''
export FOO="foo"
echo "loaded direnv!"
'';
description = lib.mdDoc ''
Extra lines to append to the sourced direnvrc
'';
};
silent = lib.mkEnableOption (lib.mdDoc ''
the hiding of direnv logging
'');
persistDerivations =
(lib.mkEnableOption (lib.mdDoc ''
setting keep-derivations and keep-outputs to true
to prevent shells from getting garbage collected
''))
// {
default = true;
};
loadInNixShell =
lib.mkEnableOption (lib.mdDoc ''
loading direnv in `nix-shell` `nix shell` or `nix develop`
'')
// {
default = true;
};
nix-direnv = {
enable =
(lib.mkEnableOption (lib.mdDoc ''
a faster, persistent implementation of use_nix and use_flake, to replace the built-in one
''))
// {
default = true;
};
package = lib.mkPackageOptionMD pkgs "nix-direnv" {};
};
};
config = lib.mkIf cfg.enable {
programs = {
zsh.interactiveShellInit = ''
if ${lib.boolToString cfg.loadInNixShell} || printenv PATH | grep -vqc '/nix/store'; then
eval "$(${lib.getExe cfg.package} hook zsh)"
fi
'';
#$NIX_GCROOT for "nix develop" https://github.com/NixOS/nix/blob/6db66ebfc55769edd0c6bc70fcbd76246d4d26e0/src/nix/develop.cc#L530
#$IN_NIX_SHELL for "nix-shell"
bash.interactiveShellInit = ''
if ${lib.boolToString cfg.loadInNixShell} || [ -z "$IN_NIX_SHELL$NIX_GCROOT$(printenv PATH | grep '/nix/store')" ] ; then
eval "$(${lib.getExe cfg.package} hook bash)"
fi
'';
fish.interactiveShellInit = ''
if ${lib.boolToString cfg.loadInNixShell};
or printenv PATH | grep -vqc '/nix/store';
${lib.getExe cfg.package} hook fish | source
end
'';
};
nix.settings = lib.mkIf cfg.persistDerivations {
keep-outputs = true;
keep-derivations = true;
};
environment = {
systemPackages =
if cfg.loadInNixShell then [cfg.package]
else [
#direnv has a fish library which sources direnv for some reason
(cfg.package.overrideAttrs (old: {
installPhase =
(old.installPhase or "")
+ ''
rm -rf $out/share/fish
'';
}))
];
variables = {
DIRENV_CONFIG = "/etc/direnv";
DIRENV_LOG_FORMAT = lib.mkIf cfg.silent "";
};
etc = {
"direnv/direnvrc".text = ''
${lib.optionalString cfg.nix-direnv.enable ''
#Load nix-direnv
source ${cfg.nix-direnv.package}/share/nix-direnv/direnvrc
''}
#Load direnvrcExtra
${cfg.direnvrcExtra}
#Load user-configuration if present (~/.direnvrc or ~/.config/direnv/direnvrc)
direnv_config_dir_home="''${DIRENV_CONFIG_HOME:-''${XDG_CONFIG_HOME:-$HOME/.config}/direnv}"
if [[ -f $direnv_config_dir_home/direnvrc ]]; then
source "$direnv_config_dir_home/direnvrc" >&2
elif [[ -f $HOME/.direnvrc ]]; then
source "$HOME/.direnvrc" >&2
fi
unset direnv_config_dir_home
'';
"direnv/lib/zz-user.sh".text = ''
direnv_config_dir_home="''${DIRENV_CONFIG_HOME:-''${XDG_CONFIG_HOME:-$HOME/.config}/direnv}"
for lib in "$direnv_config_dir_home/lib/"*.sh; do
source "$lib"
done
unset direnv_config_dir_home
'';
};
};
};
}

View file

@ -233,7 +233,6 @@ in
nixpkgs.config.firefox = {
enableBrowserpass = nmh.browserpass;
enableBukubrow = nmh.bukubrow;
enableEUWebID = nmh.euwebid;
enableTridactylNative = nmh.tridactyl;
enableUgetIntegrator = nmh.ugetIntegrator;
enableFXCastBridge = nmh.fxCast;

View file

@ -6,10 +6,10 @@
with lib; let
cfg = config.programs.hyprland;
defaultHyprlandPackage = pkgs.hyprland.override {
enableXWayland = cfg.xwayland.enable;
hidpiXWayland = cfg.xwayland.hidpi;
nvidiaPatches = cfg.nvidiaPatches;
finalPortalPackage = cfg.portalPackage.override {
hyprland-share-picker = pkgs.hyprland-share-picker.override {
hyprland = cfg.finalPackage;
};
};
in
{
@ -25,24 +25,25 @@ in
'';
};
package = mkOption {
type = types.path;
default = defaultHyprlandPackage;
defaultText = literalExpression ''
pkgs.hyprland.override {
enableXWayland = config.programs.hyprland.xwayland.enable;
hidpiXWayland = config.programs.hyprland.xwayland.hidpi;
nvidiaPatches = config.programs.hyprland.nvidiaPatches;
}
'';
example = literalExpression "<Hyprland flake>.packages.<system>.default";
package = mkPackageOptionMD pkgs "hyprland" { };
finalPackage = mkOption {
type = types.package;
readOnly = true;
default = cfg.package.override {
enableXWayland = cfg.xwayland.enable;
hidpiXWayland = cfg.xwayland.hidpi;
nvidiaPatches = cfg.nvidiaPatches;
};
defaultText = literalExpression
"`wayland.windowManager.hyprland.package` with applied configuration";
description = mdDoc ''
The Hyprland package to use.
Setting this option will make {option}`programs.hyprland.xwayland` and
{option}`programs.hyprland.nvidiaPatches` not work.
The Hyprland package after applying configuration.
'';
};
portalPackage = mkPackageOptionMD pkgs "xdg-desktop-portal-hyprland" { };
xwayland = {
enable = mkEnableOption (mdDoc "XWayland") // { default = true; };
hidpi = mkEnableOption null // {
@ -57,9 +58,9 @@ in
};
config = mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
environment.systemPackages = [ cfg.finalPackage ];
fonts.enableDefaultFonts = mkDefault true;
fonts.enableDefaultPackages = mkDefault true;
hardware.opengl.enable = mkDefault true;
programs = {
@ -69,13 +70,11 @@ in
security.polkit.enable = true;
services.xserver.displayManager.sessionPackages = [ cfg.package ];
services.xserver.displayManager.sessionPackages = [ cfg.finalPackage ];
xdg.portal = {
enable = mkDefault true;
extraPortals = [
pkgs.xdg-desktop-portal-hyprland
];
extraPortals = [ finalPortalPackage ];
};
};
}

View file

@ -66,7 +66,7 @@ in {
};
hardware.opengl.enable = lib.mkDefault true;
fonts.enableDefaultFonts = lib.mkDefault true;
fonts.enableDefaultPackages = lib.mkDefault true;
programs.dconf.enable = lib.mkDefault true;
programs.xwayland.enable = lib.mkDefault true;

View file

@ -0,0 +1,28 @@
{ config, pkgs, lib, ... }:
let
cfg = config.programs.oddjobd;
in
{
options.programs.oddjobd = {
enable = lib.mkEnableOption "oddjob";
package = lib.mkPackageOption pkgs "oddjob" {};
};
config = lib.mkIf cfg.enable {
systemd.packages = [ cfg.package ];
systemd.services.oddjobd = {
wantedBy = [ "multi-user.target"];
after = [ "network.target"];
description = "DBUS Odd-job Daemon";
enable = true;
documentation = [ "man:oddjobd(8)" "man:oddjobd.conf(5)" ];
serviceConfig = {
Type = "dbus";
BusName = "org.freedesktop.oddjob";
ExecStart = "${lib.getExe cfg.package}/bin/oddjobd";
};
};
};
}

View file

@ -0,0 +1,48 @@
{ config, lib, pkgs, ...}:
let
cfg = config.programs.wayfire;
in
{
meta.maintainers = with lib.maintainers; [ rewine ];
options.programs.wayfire = {
enable = lib.mkEnableOption (lib.mdDoc "Wayfire, a wayland compositor based on wlroots.");
package = lib.mkPackageOptionMD pkgs "wayfire" { };
plugins = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = with pkgs.wayfirePlugins; [ wcm wf-shell ];
defaultText = lib.literalExpression "with pkgs.wayfirePlugins; [ wcm wf-shell ]";
example = lib.literalExpression ''
with pkgs.wayfirePlugins; [
wcm
wf-shell
wayfire-plugins-extra
];
'';
description = lib.mdDoc ''
Additional plugins to use with the wayfire window manager.
'';
};
};
config = let
finalPackage = pkgs.wayfire-with-plugins.override {
wayfire = cfg.package;
plugins = cfg.plugins;
};
in
lib.mkIf cfg.enable {
environment.systemPackages = [
finalPackage
];
services.xserver.displayManager.sessionPackages = [ finalPackage ];
xdg.portal = {
enable = lib.mkDefault true;
wlr.enable = lib.mkDefault true;
};
};
}

View file

@ -5,7 +5,7 @@
};
hardware.opengl.enable = mkDefault true;
fonts.enableDefaultFonts = mkDefault true;
fonts.enableDefaultPackages = mkDefault true;
programs = {
dconf.enable = mkDefault true;

View file

@ -72,7 +72,6 @@ in
(mkRemovedOptionModule [ "services" "mesos" ] "The corresponding package was removed from nixpkgs.")
(mkRemovedOptionModule [ "services" "moinmoin" ] "The corresponding package was removed from nixpkgs.")
(mkRemovedOptionModule [ "services" "mwlib" ] "The corresponding package was removed from nixpkgs.")
(mkRemovedOptionModule [ "services" "osquery" ] "The osquery module has been removed")
(mkRemovedOptionModule [ "services" "pantheon" "files" ] ''
This module was removed, please add pkgs.pantheon.elementary-files to environment.systemPackages directly.
'')

View file

@ -44,5 +44,5 @@ in
};
};
meta.maintainers = with maintainers; [ SuperSandro2000 ];
meta.maintainers = with maintainers; [ ];
}

View file

@ -33,7 +33,7 @@ let
}
trap on_exit EXIT
archiveName="${if cfg.archiveBaseName == null then "" else cfg.archiveBaseName + "-"}$(date ${cfg.dateFormat})"
archiveName="${optionalString (cfg.archiveBaseName != null) (cfg.archiveBaseName + "-")}$(date ${cfg.dateFormat})"
archiveSuffix="${optionalString cfg.appendFailedSuffix ".failed"}"
${cfg.preHook}
'' + optionalString cfg.doInit ''

View file

@ -32,6 +32,8 @@ in
services.tarsnap = {
enable = mkEnableOption (lib.mdDoc "periodic tarsnap backups");
package = mkPackageOption pkgs "tarsnap" { };
keyfile = mkOption {
type = types.str;
default = "/root/tarsnap.key";
@ -307,7 +309,7 @@ in
requires = [ "network-online.target" ];
after = [ "network-online.target" ];
path = with pkgs; [ iputils tarsnap util-linux ];
path = with pkgs; [ iputils gcfg.package util-linux ];
# In order for the persistent tarsnap timer to work reliably, we have to
# make sure that the tarsnap server is reachable after systemd starts up
@ -318,7 +320,7 @@ in
'';
script = let
tarsnap = ''tarsnap --configfile "/etc/tarsnap/${name}.conf"'';
tarsnap = ''${lib.getExe gcfg.package} --configfile "/etc/tarsnap/${name}.conf"'';
run = ''${tarsnap} -c -f "${name}-$(date +"%Y%m%d%H%M%S")" \
${optionalString cfg.verbose "-v"} \
${optionalString cfg.explicitSymlinks "-H"} \
@ -355,10 +357,10 @@ in
description = "Tarsnap restore '${name}'";
requires = [ "network-online.target" ];
path = with pkgs; [ iputils tarsnap util-linux ];
path = with pkgs; [ iputils gcfg.package util-linux ];
script = let
tarsnap = ''tarsnap --configfile "/etc/tarsnap/${name}.conf"'';
tarsnap = ''${lib.getExe gcfg.package} --configfile "/etc/tarsnap/${name}.conf"'';
lastArchive = "$(${tarsnap} --list-archives | sort | tail -1)";
run = ''${tarsnap} -x -f "${lastArchive}" ${optionalString cfg.verbose "-v"}'';
cachedir = escapeShellArg cfg.cachedir;
@ -402,6 +404,6 @@ in
{ text = configFile name cfg;
}) gcfg.archives;
environment.systemPackages = [ pkgs.tarsnap ];
environment.systemPackages = [ gcfg.package ];
};
}

View file

@ -6,9 +6,6 @@ let
defaultGroup = "patroni";
format = pkgs.formats.yaml { };
#boto doesn't support python 3.10 yet
patroni = pkgs.patroni.override { pythonPackages = pkgs.python39Packages; };
configFileName = "patroni-${cfg.scope}-${cfg.name}.yaml";
configFile = format.generate configFileName cfg.settings;
in
@ -224,7 +221,7 @@ in
script = ''
${concatStringsSep "\n" (attrValues (mapAttrs (name: path: ''export ${name}="$(< ${escapeShellArg path})"'') cfg.environmentFiles))}
exec ${patroni}/bin/patroni ${configFile}
exec ${pkgs.patroni}/bin/patroni ${configFile}
'';
serviceConfig = mkMerge [
@ -252,7 +249,7 @@ in
'';
environment.systemPackages = [
patroni
pkgs.patroni
cfg.postgresqlPackage
(mkIf cfg.raft pkgs.python310Packages.pysyncobj)
];

View file

@ -1,64 +1,49 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.buildkite-agents;
mkHookOption = { name, description, example ? null }: {
inherit name;
value = mkOption {
default = null;
description = lib.mdDoc description;
type = types.nullOr types.lines;
} // (lib.optionalAttrs (example != null) { inherit example; });
};
mkHookOptions = hooks: listToAttrs (map mkHookOption hooks);
hooksDir = cfg: let
mkHookEntry = name: value: ''
cat > $out/${name} <<'EOF'
#! ${pkgs.runtimeShell}
set -e
${value}
EOF
chmod 755 $out/${name}
hooksDir = hooks:
let
mkHookEntry = name: text: ''
ln --symbolic ${pkgs.writeShellApplication { inherit name text; }}/bin/${name} $out/${name}
'';
in
pkgs.runCommandLocal "buildkite-agent-hooks" { } ''
mkdir $out
${lib.concatStringsSep "\n" (lib.mapAttrsToList mkHookEntry hooks)}
'';
in pkgs.runCommand "buildkite-agent-hooks" { preferLocalBuild = true; } ''
mkdir $out
${concatStringsSep "\n" (mapAttrsToList mkHookEntry (filterAttrs (n: v: v != null) cfg.hooks))}
'';
buildkiteOptions = { name ? "", config, ... }: {
options = {
enable = mkOption {
enable = lib.mkOption {
default = true;
type = types.bool;
type = lib.types.bool;
description = lib.mdDoc "Whether to enable this buildkite agent";
};
package = mkOption {
package = lib.mkOption {
default = pkgs.buildkite-agent;
defaultText = literalExpression "pkgs.buildkite-agent";
defaultText = lib.literalExpression "pkgs.buildkite-agent";
description = lib.mdDoc "Which buildkite-agent derivation to use";
type = types.package;
type = lib.types.package;
};
dataDir = mkOption {
dataDir = lib.mkOption {
default = "/var/lib/buildkite-agent-${name}";
description = lib.mdDoc "The workdir for the agent";
type = types.str;
type = lib.types.str;
};
runtimePackages = mkOption {
runtimePackages = lib.mkOption {
default = [ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ];
defaultText = literalExpression "[ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ]";
defaultText = lib.literalExpression "[ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ]";
description = lib.mdDoc "Add programs to the buildkite-agent environment";
type = types.listOf types.package;
type = lib.types.listOf lib.types.package;
};
tokenPath = mkOption {
type = types.path;
tokenPath = lib.mkOption {
type = lib.types.path;
description = lib.mdDoc ''
The token from your Buildkite "Agents" page.
@ -67,25 +52,25 @@ let
'';
};
name = mkOption {
type = types.str;
name = lib.mkOption {
type = lib.types.str;
default = "%hostname-${name}-%n";
description = lib.mdDoc ''
The name of the agent as seen in the buildkite dashboard.
'';
};
tags = mkOption {
type = types.attrsOf (types.either types.str (types.listOf types.str));
default = {};
example = { queue = "default"; docker = "true"; ruby2 ="true"; };
tags = lib.mkOption {
type = lib.types.attrsOf (lib.types.either lib.types.str (lib.types.listOf lib.types.str));
default = { };
example = { queue = "default"; docker = "true"; ruby2 = "true"; };
description = lib.mdDoc ''
Tags for the agent.
'';
};
extraConfig = mkOption {
type = types.lines;
extraConfig = lib.mkOption {
type = lib.types.lines;
default = "";
example = "debug=true";
description = lib.mdDoc ''
@ -93,8 +78,8 @@ let
'';
};
privateSshKeyPath = mkOption {
type = types.nullOr types.path;
privateSshKeyPath = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
## maximum care is taken so that secrets (ssh keys and the CI token)
## don't end up in the Nix store.
@ -108,67 +93,25 @@ let
'';
};
hooks = mkHookOptions [
{ name = "checkout";
description = ''
The `checkout` hook script will replace the default checkout routine of the
bootstrap.sh script. You can use this hook to do your own SCM checkout
behaviour
''; }
{ name = "command";
description = ''
The `command` hook script will replace the default implementation of running
the build command.
''; }
{ name = "environment";
description = ''
The `environment` hook will run before all other commands, and can be used
to set up secrets, data, etc. Anything exported in hooks will be available
to the build script.
hooks = lib.mkOption {
type = lib.types.attrsOf lib.types.lines;
default = { };
example = lib.literalExpression ''
{
environment = '''
export SECRET_VAR=`head -1 /run/keys/secret`
''';
}'';
description = lib.mdDoc ''
"Agent" hooks to install.
See <https://buildkite.com/docs/agent/v3/hooks> for possible options.
'';
};
Note: the contents of this file will be copied to the world-readable
Nix store.
'';
example = ''
export SECRET_VAR=`head -1 /run/keys/secret`
''; }
{ name = "post-artifact";
description = ''
The `post-artifact` hook will run just after artifacts are uploaded
''; }
{ name = "post-checkout";
description = ''
The `post-checkout` hook will run after the bootstrap script has checked out
your projects source code.
''; }
{ name = "post-command";
description = ''
The `post-command` hook will run after the bootstrap script has run your
build commands
''; }
{ name = "pre-artifact";
description = ''
The `pre-artifact` hook will run just before artifacts are uploaded
''; }
{ name = "pre-checkout";
description = ''
The `pre-checkout` hook will run just before your projects source code is
checked out from your SCM provider
''; }
{ name = "pre-command";
description = ''
The `pre-command` hook will run just before your build command runs
''; }
{ name = "pre-exit";
description = ''
The `pre-exit` hook will run just before your build job finishes
''; }
];
hooksPath = mkOption {
type = types.path;
default = hooksDir config;
defaultText = literalMD "generated from {option}`services.buildkite-agents.<name>.hooks`";
hooksPath = lib.mkOption {
type = lib.types.path;
default = hooksDir config.hooks;
defaultText = lib.literalMD "generated from {option}`services.buildkite-agents.<name>.hooks`";
description = lib.mdDoc ''
Path to the directory storing the hooks.
Consider using {option}`services.buildkite-agents.<name>.hooks.<name>`
@ -176,10 +119,10 @@ let
'';
};
shell = mkOption {
type = types.str;
shell = lib.mkOption {
type = lib.types.str;
default = "${pkgs.bash}/bin/bash -e -c";
defaultText = literalExpression ''"''${pkgs.bash}/bin/bash -e -c"'';
defaultText = lib.literalExpression ''"''${pkgs.bash}/bin/bash -e -c"'';
description = lib.mdDoc ''
Command that buildkite-agent 3 will execute when it spawns a shell.
'';
@ -190,9 +133,9 @@ let
mapAgents = function: lib.mkMerge (lib.mapAttrsToList function enabledAgents);
in
{
options.services.buildkite-agents = mkOption {
type = types.attrsOf (types.submodule buildkiteOptions);
default = {};
options.services.buildkite-agents = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule buildkiteOptions);
default = { };
description = lib.mdDoc ''
Attribute set of buildkite agents.
The attribute key is combined with the hostname and a unique integer to
@ -213,23 +156,24 @@ in
};
});
config.users.groups = mapAgents (name: cfg: {
"buildkite-agent-${name}" = {};
"buildkite-agent-${name}" = { };
});
config.systemd.services = mapAgents (name: cfg: {
"buildkite-agent-${name}" =
{ description = "Buildkite Agent";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
path = cfg.runtimePackages ++ [ cfg.package pkgs.coreutils ];
environment = config.networking.proxy.envVars // {
HOME = cfg.dataDir;
NIX_REMOTE = "daemon";
};
"buildkite-agent-${name}" = {
description = "Buildkite Agent";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
path = cfg.runtimePackages ++ [ cfg.package pkgs.coreutils ];
environment = config.networking.proxy.envVars // {
HOME = cfg.dataDir;
NIX_REMOTE = "daemon";
};
## NB: maximum care is taken so that secrets (ssh keys and the CI token)
## don't end up in the Nix store.
preStart = let
## NB: maximum care is taken so that secrets (ssh keys and the CI token)
## don't end up in the Nix store.
preStart =
let
sshDir = "${cfg.dataDir}/.ssh";
tagStr = name: value:
if lib.isList value
@ -237,44 +181,39 @@ in
else "${name}=${value}";
tagsStr = lib.concatStringsSep "," (lib.mapAttrsToList tagStr cfg.tags);
in
optionalString (cfg.privateSshKeyPath != null) ''
mkdir -m 0700 -p "${sshDir}"
install -m600 "${toString cfg.privateSshKeyPath}" "${sshDir}/id_rsa"
'' + ''
cat > "${cfg.dataDir}/buildkite-agent.cfg" <<EOF
token="$(cat ${toString cfg.tokenPath})"
name="${cfg.name}"
shell="${cfg.shell}"
tags="${tagsStr}"
build-path="${cfg.dataDir}/builds"
hooks-path="${cfg.hooksPath}"
${cfg.extraConfig}
EOF
'';
lib.optionalString (cfg.privateSshKeyPath != null) ''
mkdir -m 0700 -p "${sshDir}"
install -m600 "${toString cfg.privateSshKeyPath}" "${sshDir}/id_rsa"
'' + ''
cat > "${cfg.dataDir}/buildkite-agent.cfg" <<EOF
token="$(cat ${toString cfg.tokenPath})"
name="${cfg.name}"
shell="${cfg.shell}"
tags="${tagsStr}"
build-path="${cfg.dataDir}/builds"
hooks-path="${cfg.hooksPath}"
${cfg.extraConfig}
EOF
'';
serviceConfig =
{ ExecStart = "${cfg.package}/bin/buildkite-agent start --config ${cfg.dataDir}/buildkite-agent.cfg";
User = "buildkite-agent-${name}";
RestartSec = 5;
Restart = "on-failure";
TimeoutSec = 10;
# set a long timeout to give buildkite-agent a chance to finish current builds
TimeoutStopSec = "2 min";
KillMode = "mixed";
};
serviceConfig = {
ExecStart = "${cfg.package}/bin/buildkite-agent start --config ${cfg.dataDir}/buildkite-agent.cfg";
User = "buildkite-agent-${name}";
RestartSec = 5;
Restart = "on-failure";
TimeoutSec = 10;
# set a long timeout to give buildkite-agent a chance to finish current builds
TimeoutStopSec = "2 min";
KillMode = "mixed";
};
};
});
config.assertions = mapAgents (name: cfg: [
{ assertion = cfg.hooksPath == (hooksDir cfg) || all (v: v == null) (attrValues cfg.hooks);
message = ''
Options `services.buildkite-agents.${name}.hooksPath' and
`services.buildkite-agents.${name}.hooks.<name>' are mutually exclusive.
'';
}
]);
imports = [
(mkRemovedOptionModule [ "services" "buildkite-agent"] "services.buildkite-agent has been upgraded from version 2 to version 3 and moved to an attribute set at services.buildkite-agents. Please consult the 20.03 release notes for more information.")
];
config.assertions = mapAgents (name: cfg: [{
assertion = cfg.hooksPath != hooksDir cfg.hooks -> cfg.hooks == { };
message = ''
Options `services.buildkite-agents.${name}.hooksPath' and
`services.buildkite-agents.${name}.hooks.<name>' are mutually exclusive.
'';
}]);
}

View file

@ -210,9 +210,7 @@ in {
preStart =
let replacePlugins =
if cfg.plugins == null
then ""
else
optionalString (cfg.plugins != null) (
let pluginCmds = lib.attrsets.mapAttrsToList
(n: v: "cp ${v} ${cfg.home}/plugins/${n}.jpi")
cfg.plugins;
@ -220,7 +218,7 @@ in {
rm -r ${cfg.home}/plugins || true
mkdir -p ${cfg.home}/plugins
${lib.strings.concatStringsSep "\n" pluginCmds}
'';
'');
in ''
rm -rf ${cfg.home}/war
${replacePlugins}

View file

@ -0,0 +1,632 @@
{ lib, pkgs, config, ... } :
with lib;
let
cfg = config.services.pgbouncer;
confFile = pkgs.writeTextFile {
name = "pgbouncer.ini";
text = ''
[databases]
${concatStringsSep "\n"
(mapAttrsToList (dbname : settings : "${dbname} = ${settings}") cfg.databases)}
[users]
${concatStringsSep "\n"
(mapAttrsToList (username : settings : "${username} = ${settings}") cfg.users)}
[peers]
${concatStringsSep "\n"
(mapAttrsToList (peerid : settings : "${peerid} = ${settings}") cfg.peers)}
[pgbouncer]
# general
${optionalString (cfg.ignoreStartupParameters != null) "ignore_startup_parameters = ${cfg.ignoreStartupParameters}"}
listen_port = ${toString cfg.listenPort}
${optionalString (cfg.listenAddress != null) "listen_addr = ${cfg.listenAddress}"}
pool_mode = ${cfg.poolMode}
max_client_conn = ${toString cfg.maxClientConn}
default_pool_size = ${toString cfg.defaultPoolSize}
max_user_connections = ${toString cfg.maxUserConnections}
max_db_connections = ${toString cfg.maxDbConnections}
#auth
auth_type = ${cfg.authType}
${optionalString (cfg.authHbaFile != null) "auth_hba_file = ${cfg.authHbaFile}"}
${optionalString (cfg.authFile != null) "auth_file = ${cfg.authFile}"}
${optionalString (cfg.authUser != null) "auth_user = ${cfg.authUser}"}
${optionalString (cfg.authQuery != null) "auth_query = ${cfg.authQuery}"}
${optionalString (cfg.authDbname != null) "auth_dbname = ${cfg.authDbname}"}
# TLS
${optionalString (cfg.tls.client != null) ''
client_tls_sslmode = ${cfg.tls.client.sslmode}
client_tls_key_file = ${cfg.tls.client.keyFile}
client_tls_cert_file = ${cfg.tls.client.certFile}
client_tls_ca_file = ${cfg.tls.client.caFile}
''}
${optionalString (cfg.tls.server != null) ''
server_tls_sslmode = ${cfg.tls.server.sslmode}
server_tls_key_file = ${cfg.tls.server.keyFile}
server_tls_cert_file = ${cfg.tls.server.certFile}
server_tls_ca_file = ${cfg.tls.server.caFile}
''}
# log
${optionalString (cfg.logFile != null) "logfile = ${cfg.homeDir}/${cfg.logFile}"}
${optionalString (cfg.syslog != null) ''
syslog = ${if cfg.syslog.enable then "1" else "0"}
syslog_ident = ${cfg.syslog.syslogIdent}
syslog_facility = ${cfg.syslog.syslogFacility}
''}
${optionalString (cfg.verbose != null) "verbose = ${toString cfg.verbose}"}
# console access
${optionalString (cfg.adminUsers != null) "admin_users = ${cfg.adminUsers}"}
${optionalString (cfg.statsUsers != null) "stats_users = ${cfg.statsUsers}"}
# linux
pidfile = /run/pgbouncer/pgbouncer.pid
# extra
${cfg.extraConfig}
'';
};
in {
options.services.pgbouncer = {
# NixOS settings
enable = mkEnableOption (lib.mdDoc "PostgreSQL connection pooler");
package = mkOption {
type = types.package;
default = pkgs.pgbouncer;
defaultText = literalExpression "pkgs.pgbouncer";
description = lib.mdDoc ''
The pgbouncer package to use.
'';
};
openFirewall = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
Whether to automatically open the specified TCP port in the firewall.
'';
};
# Generic settings
logFile = mkOption {
type = types.nullOr types.str;
default = "pgbouncer.log";
description = lib.mdDoc ''
Specifies the log file.
Either this or syslog has to be specified.
'';
};
listenAddress = mkOption {
type = types.nullOr types.commas;
example = "*";
default = null;
description = lib.mdDoc ''
Specifies a list (comma-separated) of addresses where to listen for TCP connections.
You may also use * meaning listen on all addresses.
When not set, only Unix socket connections are accepted.
Addresses can be specified numerically (IPv4/IPv6) or by name.
'';
};
listenPort = mkOption {
type = types.port;
default = 6432;
description = lib.mdDoc ''
Which port to listen on. Applies to both TCP and Unix sockets.
'';
};
poolMode = mkOption {
type = types.enum [ "session" "transaction" "statement" ];
default = "session";
description = lib.mdDoc ''
Specifies when a server connection can be reused by other clients.
session
Server is released back to pool after client disconnects. Default.
transaction
Server is released back to pool after transaction finishes.
statement
Server is released back to pool after query finishes.
Transactions spanning multiple statements are disallowed in this mode.
'';
};
maxClientConn = mkOption {
type = types.int;
default = 100;
description = lib.mdDoc ''
Maximum number of client connections allowed.
When this setting is increased, then the file descriptor limits in the operating system
might also have to be increased. Note that the number of file descriptors potentially
used is more than maxClientConn. If each user connects under its own user name to the server,
the theoretical maximum used is:
maxClientConn + (max pool_size * total databases * total users)
If a database user is specified in the connection string (all users connect under the same user name),
the theoretical maximum is:
maxClientConn + (max pool_size * total databases)
The theoretical maximum should never be reached, unless somebody deliberately crafts a special load for it.
Still, it means you should set the number of file descriptors to a safely high number.
'';
};
defaultPoolSize = mkOption {
type = types.int;
default = 20;
description = lib.mdDoc ''
How many server connections to allow per user/database pair.
Can be overridden in the per-database configuration.
'';
};
maxDbConnections = mkOption {
type = types.int;
default = 0;
description = lib.mdDoc ''
Do not allow more than this many server connections per database (regardless of user).
This considers the PgBouncer database that the client has connected to,
not the PostgreSQL database of the outgoing connection.
This can also be set per database in the [databases] section.
Note that when you hit the limit, closing a client connection to one pool will
not immediately allow a server connection to be established for another pool,
because the server connection for the first pool is still open.
Once the server connection closes (due to idle timeout),
a new server connection will immediately be opened for the waiting pool.
0 = unlimited
'';
};
maxUserConnections = mkOption {
type = types.int;
default = 0;
description = lib.mdDoc ''
Do not allow more than this many server connections per user (regardless of database).
This considers the PgBouncer user that is associated with a pool,
which is either the user specified for the server connection
or in absence of that the user the client has connected as.
This can also be set per user in the [users] section.
Note that when you hit the limit, closing a client connection to one pool
will not immediately allow a server connection to be established for another pool,
because the server connection for the first pool is still open.
Once the server connection closes (due to idle timeout), a new server connection
will immediately be opened for the waiting pool.
0 = unlimited
'';
};
ignoreStartupParameters = mkOption {
type = types.nullOr types.commas;
example = "extra_float_digits";
default = null;
description = lib.mdDoc ''
By default, PgBouncer allows only parameters it can keep track of in startup packets:
client_encoding, datestyle, timezone and standard_conforming_strings.
All others parameters will raise an error.
To allow others parameters, they can be specified here, so that PgBouncer knows that
they are handled by the admin and it can ignore them.
If you need to specify multiple values, use a comma-separated list.
IMPORTANT: When using prometheus-pgbouncer-exporter, you need:
extra_float_digits
<https://github.com/prometheus-community/pgbouncer_exporter#pgbouncer-configuration>
'';
};
# Section [databases]
databases = mkOption {
type = types.attrsOf types.str;
default = {};
example = {
exampledb = "host=/run/postgresql/ port=5432 auth_user=exampleuser dbname=exampledb sslmode=require";
bardb = "host=localhost dbname=bazdb";
foodb = "host=host1.example.com port=5432";
};
description = lib.mdDoc ''
Detailed information about PostgreSQL database definitions:
<https://www.pgbouncer.org/config.html#section-databases>
'';
};
# Section [users]
users = mkOption {
type = types.attrsOf types.str;
default = {};
example = {
user1 = "pool_mode=session";
};
description = lib.mdDoc ''
Optional.
Detailed information about PostgreSQL user definitions:
<https://www.pgbouncer.org/config.html#section-users>
'';
};
# Section [peers]
peers = mkOption {
type = types.attrsOf types.str;
default = {};
example = {
"1" = "host=host1.example.com";
"2" = "host=/tmp/pgbouncer-2 port=5555";
};
description = lib.mdDoc ''
Optional.
Detailed information about PostgreSQL database definitions:
<https://www.pgbouncer.org/config.html#section-peers>
'';
};
# Authentication settings
authType = mkOption {
type = types.enum [ "cert" "md5" "scram-sha-256" "plain" "trust" "any" "hba" "pam" ];
default = "md5";
description = lib.mdDoc ''
How to authenticate users.
cert
Client must connect over TLS connection with a valid client certificate.
The user name is then taken from the CommonName field from the certificate.
md5
Use MD5-based password check. This is the default authentication method.
authFile may contain both MD5-encrypted and plain-text passwords.
If md5 is configured and a user has a SCRAM secret, then SCRAM authentication is used automatically instead.
scram-sha-256
Use password check with SCRAM-SHA-256. authFile has to contain SCRAM secrets or plain-text passwords.
plain
The clear-text password is sent over the wire. Deprecated.
trust
No authentication is done. The user name must still exist in authFile.
any
Like the trust method, but the user name given is ignored.
Requires that all databases are configured to log in as a specific user.
Additionally, the console database allows any user to log in as admin.
hba
The actual authentication type is loaded from authHbaFile.
This allows different authentication methods for different access paths,
for example: connections over Unix socket use the peer auth method, connections over TCP must use TLS.
pam
PAM is used to authenticate users, authFile is ignored.
This method is not compatible with databases using the authUser option.
The service name reported to PAM is pgbouncer. pam is not supported in the HBA configuration file.
'';
};
authHbaFile = mkOption {
type = types.nullOr types.path;
default = null;
example = "/secrets/pgbouncer_hba";
description = lib.mdDoc ''
HBA configuration file to use when authType is hba.
See HBA file format details:
<https://www.pgbouncer.org/config.html#hba-file-format>
'';
};
authFile = mkOption {
type = types.nullOr types.path;
default = null;
example = "/secrets/pgbouncer_authfile";
description = lib.mdDoc ''
The name of the file to load user names and passwords from.
See section Authentication file format details:
<https://www.pgbouncer.org/config.html#authentication-file-format>
Most authentication types require that either authFile or authUser be set;
otherwise there would be no users defined.
'';
};
authUser = mkOption {
type = types.nullOr types.str;
default = null;
example = "pgbouncer";
description = lib.mdDoc ''
If authUser is set, then any user not specified in authFile will be queried
through the authQuery query from pg_shadow in the database, using authUser.
The password of authUser will be taken from authFile.
(If the authUser does not require a password then it does not need to be defined in authFile.)
Direct access to pg_shadow requires admin rights.
It's preferable to use a non-superuser that calls a SECURITY DEFINER function instead.
'';
};
authQuery = mkOption {
type = types.nullOr types.str;
default = null;
example = "SELECT usename, passwd FROM pg_shadow WHERE usename=$1";
description = lib.mdDoc ''
Query to load user's password from database.
Direct access to pg_shadow requires admin rights.
It's preferable to use a non-superuser that calls a SECURITY DEFINER function instead.
Note that the query is run inside the target database.
So if a function is used, it needs to be installed into each database.
'';
};
authDbname = mkOption {
type = types.nullOr types.str;
default = null;
example = "authdb";
description = lib.mdDoc ''
Database name in the [database] section to be used for authentication purposes.
This option can be either global or overriden in the connection string if this parameter is specified.
'';
};
# TLS settings
tls.client = mkOption {
type = types.nullOr (types.submodule {
options = {
sslmode = mkOption {
type = types.enum [ "disable" "allow" "prefer" "require" "verify-ca" "verify-full" ];
default = "disable";
description = lib.mdDoc ''
TLS mode to use for connections from clients.
TLS connections are disabled by default.
When enabled, tls.client.keyFile and tls.client.certFile
must be also configured to set up the key and certificate
PgBouncer uses to accept client connections.
disable
Plain TCP. If client requests TLS, it's ignored. Default.
allow
If client requests TLS, it is used. If not, plain TCP is used.
If the client presents a client certificate, it is not validated.
prefer
Same as allow.
require
Client must use TLS. If not, the client connection is rejected.
If the client presents a client certificate, it is not validated.
verify-ca
Client must use TLS with valid client certificate.
verify-full
Same as verify-ca
'';
};
certFile = mkOption {
type = types.path;
example = "/secrets/pgbouncer.key";
description = lib.mdDoc "Path to certificate for private key. Clients can validate it";
};
keyFile = mkOption {
type = types.path;
example = "/secrets/pgbouncer.crt";
description = lib.mdDoc "Path to private key for PgBouncer to accept client connections";
};
caFile = mkOption {
type = types.path;
example = "/secrets/pgbouncer.crt";
description = lib.mdDoc "Path to root certificate file to validate client certificates";
};
};
});
default = null;
description = lib.mdDoc ''
<https://www.pgbouncer.org/config.html#tls-settings>
'';
};
tls.server = mkOption {
type = types.nullOr (types.submodule {
options = {
sslmode = mkOption {
type = types.enum [ "disable" "allow" "prefer" "require" "verify-ca" "verify-full" ];
default = "disable";
description = lib.mdDoc ''
TLS mode to use for connections to PostgreSQL servers.
TLS connections are disabled by default.
disable
Plain TCP. TLS is not even requested from the server. Default.
allow
FIXME: if server rejects plain, try TLS?
prefer
TLS connection is always requested first from PostgreSQL.
If refused, the connection will be established over plain TCP.
Server certificate is not validated.
require
Connection must go over TLS. If server rejects it, plain TCP is not attempted.
Server certificate is not validated.
verify-ca
Connection must go over TLS and server certificate must be valid according to tls.server.caFile.
Server host name is not checked against certificate.
verify-full
Connection must go over TLS and server certificate must be valid according to tls.server.caFile.
Server host name must match certificate information.
'';
};
certFile = mkOption {
type = types.path;
example = "/secrets/pgbouncer_server.key";
description = lib.mdDoc "Certificate for private key. PostgreSQL server can validate it.";
};
keyFile = mkOption {
type = types.path;
example = "/secrets/pgbouncer_server.crt";
description = lib.mdDoc "Private key for PgBouncer to authenticate against PostgreSQL server.";
};
caFile = mkOption {
type = types.path;
example = "/secrets/pgbouncer_server.crt";
description = lib.mdDoc "Root certificate file to validate PostgreSQL server certificates.";
};
};
});
default = null;
description = lib.mdDoc ''
<https://www.pgbouncer.org/config.html#tls-settings>
'';
};
# Log settings
syslog = mkOption {
type = types.nullOr (types.submodule {
options = {
enable = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
Toggles syslog on/off.
'';
};
syslogIdent = mkOption {
type = types.str;
default = "pgbouncer";
description = lib.mdDoc ''
Under what name to send logs to syslog.
'';
};
syslogFacility = mkOption {
type = types.enum [ "auth" "authpriv" "daemon" "user" "local0" "local1" "local2" "local3" "local4" "local5" "local6" "local7" ];
default = "daemon";
description = lib.mdDoc ''
Under what facility to send logs to syslog.
'';
};
};
});
default = null;
description = lib.mdDoc ''
<https://www.pgbouncer.org/config.html#log-settings>
'';
};
verbose = lib.mkOption {
type = lib.types.int;
default = 0;
description = lib.mdDoc ''
Increase verbosity. Mirrors the -v switch on the command line.
'';
};
# Console access control
adminUsers = mkOption {
type = types.nullOr types.commas;
default = null;
description = lib.mdDoc ''
Comma-separated list of database users that are allowed to connect and run all commands on the console.
Ignored when authType is any, in which case any user name is allowed in as admin.
'';
};
statsUsers = mkOption {
type = types.nullOr types.commas;
default = null;
description = lib.mdDoc ''
Comma-separated list of database users that are allowed to connect and run read-only queries on the console.
That means all SHOW commands except SHOW FDS.
'';
};
# Linux settings
openFilesLimit = lib.mkOption {
type = lib.types.int;
default = 65536;
description = lib.mdDoc ''
Maximum number of open files.
'';
};
user = mkOption {
type = types.str;
default = "pgbouncer";
description = lib.mdDoc ''
The user pgbouncer is run as.
'';
};
group = mkOption {
type = types.str;
default = "pgbouncer";
description = lib.mdDoc ''
The group pgbouncer is run as.
'';
};
homeDir = mkOption {
type = types.path;
default = "/var/lib/pgbouncer";
description = lib.mdDoc ''
Specifies the home directory.
'';
};
# Extra settings
extraConfig = mkOption {
type = types.lines;
description = lib.mdDoc ''
Any additional text to be appended to config.ini
<https://www.pgbouncer.org/config.html>.
'';
default = "";
};
};
config = mkIf cfg.enable {
users.groups.${cfg.group} = { };
users.users.${cfg.user} = {
description = "PgBouncer service user";
group = cfg.group;
home = cfg.homeDir;
createHome = true;
isSystemUser = true;
};
systemd.services.pgbouncer = {
description = "PgBouncer - PostgreSQL connection pooler";
wants = [ "postgresql.service" ];
after = [ "postgresql.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "forking";
User = cfg.user;
Group = cfg.group;
ExecStart = "${pkgs.pgbouncer}/bin/pgbouncer -d ${confFile}";
ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
RuntimeDirectory = "pgbouncer";
PIDFile = "/run/pgbouncer/pgbouncer.pid";
LimitNOFILE = cfg.openFilesLimit;
};
};
networking.firewall.allowedTCPPorts = optional cfg.openFirewall cfg.port;
};
meta.maintainers = [ maintainers._1000101 ];
}

View file

@ -404,8 +404,8 @@ in
{
log_connections = true;
log_statement = "all";
logging_collector = true
log_disconnections = true
logging_collector = true;
log_disconnections = true;
log_destination = lib.mkForce "syslog";
}
'';

View file

@ -96,7 +96,7 @@ in
environment.systemPackages = [ cfg.package editorScript desktopApplicationFile ];
environment.variables.EDITOR = mkIf cfg.defaultEditor (mkOverride 900 "${editorScript}/bin/emacseditor");
environment.variables.EDITOR = mkIf cfg.defaultEditor (mkOverride 900 "emacseditor");
};
meta.doc = ./emacs.md;

View file

@ -3,12 +3,9 @@ with lib;
let
cfg = config.services.keyd;
settingsFormat = pkgs.formats.ini { };
in
{
options = {
services.keyd = {
enable = mkEnableOption (lib.mdDoc "keyd, a key remapping daemon");
keyboardOptions = { ... }: {
options = {
ids = mkOption {
type = types.listOf types.string;
default = [ "*" ];
@ -35,24 +32,71 @@ in
};
};
description = lib.mdDoc ''
Configuration, except `ids` section, that is written to {file}`/etc/keyd/default.conf`.
Configuration, except `ids` section, that is written to {file}`/etc/keyd/<keyboard>.conf`.
Appropriate names can be used to write non-alpha keys, for example "equal" instead of "=" sign (see <https://github.com/NixOS/nixpkgs/issues/236622>).
See <https://github.com/rvaiya/keyd> how to configure.
'';
};
};
};
in
{
imports = [
(mkRemovedOptionModule [ "services" "keyd" "ids" ]
''Use keyboards.<filename>.ids instead. If you don't need a multi-file configuration, just add keyboards.default before the ids. See https://github.com/NixOS/nixpkgs/pull/243271.'')
(mkRemovedOptionModule [ "services" "keyd" "settings" ]
''Use keyboards.<filename>.settings instead. If you don't need a multi-file configuration, just add keyboards.default before the settings. See https://github.com/NixOS/nixpkgs/pull/243271.'')
];
options.services.keyd = {
enable = mkEnableOption (lib.mdDoc "keyd, a key remapping daemon");
keyboards = mkOption {
type = types.attrsOf (types.submodule keyboardOptions);
default = { };
example = literalExpression ''
{
default = {
ids = [ "*" ];
settings = {
main = {
capslock = "overload(control, esc)";
};
};
};
externalKeyboard = {
ids = [ "1ea7:0907" ];
settings = {
main = {
esc = capslock;
};
};
};
}
'';
description = mdDoc ''
Configuration for one or more device IDs. Corresponding files in the /etc/keyd/ directory are created according to the name of the keys (like `default` or `externalKeyboard`).
'';
};
};
config = mkIf cfg.enable {
environment.etc."keyd/default.conf".source = pkgs.runCommand "default.conf"
{
ids = ''
[ids]
${concatStringsSep "\n" cfg.ids}
'';
passAsFile = [ "ids" ];
} ''
cat $idsPath <(echo) ${settingsFormat.generate "keyd-main.conf" cfg.settings} >$out
'';
# Creates separate files in the `/etc/keyd/` directory for each key in the dictionary
environment.etc = mapAttrs'
(name: options:
nameValuePair "keyd/${name}.conf" {
source = pkgs.runCommand "${name}.conf"
{
ids = ''
[ids]
${concatStringsSep "\n" options.ids}
'';
passAsFile = [ "ids" ];
} ''
cat $idsPath <(echo) ${settingsFormat.generate "keyd-${name}.conf" options.settings} >$out
'';
})
cfg.keyboards;
hardware.uinput.enable = lib.mkDefault true;
@ -62,9 +106,11 @@ in
wantedBy = [ "multi-user.target" ];
restartTriggers = [
config.environment.etc."keyd/default.conf".source
];
restartTriggers = mapAttrsToList
(name: options:
config.environment.etc."keyd/${name}.conf".source
)
cfg.keyboards;
# this is configurable in 2.4.2, later versions seem to remove this option.
# post-2.4.2 may need to set makeFlags in the derivation:

View file

@ -0,0 +1,270 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.ebusd;
package = pkgs.ebusd;
arguments = [
"${package}/bin/ebusd"
"--foreground"
"--updatecheck=off"
"--device=${cfg.device}"
"--port=${toString cfg.port}"
"--configpath=${cfg.configpath}"
"--scanconfig=${cfg.scanconfig}"
"--log=main:${cfg.logs.main}"
"--log=network:${cfg.logs.network}"
"--log=bus:${cfg.logs.bus}"
"--log=update:${cfg.logs.update}"
"--log=other:${cfg.logs.other}"
"--log=all:${cfg.logs.all}"
] ++ lib.optionals cfg.readonly [
"--readonly"
] ++ lib.optionals cfg.mqtt.enable [
"--mqtthost=${cfg.mqtt.host}"
"--mqttport=${toString cfg.mqtt.port}"
"--mqttuser=${cfg.mqtt.user}"
"--mqttpass=${cfg.mqtt.password}"
] ++ lib.optionals cfg.mqtt.home-assistant [
"--mqttint=${package}/etc/ebusd/mqtt-hassio.cfg"
"--mqttjson"
] ++ lib.optionals cfg.mqtt.retain [
"--mqttretain"
] ++ cfg.extraArguments;
usesDev = hasPrefix "/" cfg.device;
command = concatStringsSep " " arguments;
in
{
meta.maintainers = with maintainers; [ nathan-gs ];
options.services.ebusd = {
enable = mkEnableOption (lib.mdDoc "ebusd service");
device = mkOption {
type = types.str;
default = "";
example = "IP:PORT";
description = lib.mdDoc ''
Use DEV as eBUS device [/dev/ttyUSB0].
This can be either:
enh:DEVICE or enh:IP:PORT for enhanced device (only adapter v3 and newer),
ens:DEVICE for enhanced high speed serial device (only adapter v3 and newer with firmware since 20220731),
DEVICE for serial device (normal speed, for all other serial adapters like adapter v2 as well as adapter v3 in non-enhanced mode), or
[udp:]IP:PORT for network device.
https://github.com/john30/ebusd/wiki/2.-Run#device-options
'';
};
port = mkOption {
default = 8888;
type = types.port;
description = lib.mdDoc ''
The port on which to listen on
'';
};
readonly = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
Only read from device, never write to it
'';
};
configpath = mkOption {
type = types.str;
default = "https://cfg.ebusd.eu/";
description = lib.mdDoc ''
Read CSV config files from PATH (local folder or HTTPS URL) [https://cfg.ebusd.eu/]
'';
};
scanconfig = mkOption {
type = types.str;
default = "full";
description = lib.mdDoc ''
Pick CSV config files matching initial scan ("none" or empty for no initial scan message, "full" for full scan, or a single hex address to scan, default is to send a broadcast ident message).
If combined with --checkconfig, you can add scan message data as arguments for checking a particular scan configuration, e.g. "FF08070400/0AB5454850303003277201". For further details on this option,
see [Automatic configuration](https://github.com/john30/ebusd/wiki/4.7.-Automatic-configuration).
'';
};
logs = {
main = mkOption {
type = types.enum [ "error" "notice" "info" "debug"];
default = "info";
description = lib.mdDoc ''
Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (error|notice|info|debug) [all:notice].
'';
};
network = mkOption {
type = types.enum [ "error" "notice" "info" "debug"];
default = "info";
description = lib.mdDoc ''
Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (error|notice|info|debug) [all:notice].
'';
};
bus = mkOption {
type = types.enum [ "error" "notice" "info" "debug"];
default = "info";
description = lib.mdDoc ''
Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (error|notice|info|debug) [all:notice].
'';
};
update = mkOption {
type = types.enum [ "error" "notice" "info" "debug"];
default = "info";
description = lib.mdDoc ''
Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (error|notice|info|debug) [all:notice].
'';
};
other = mkOption {
type = types.enum [ "error" "notice" "info" "debug"];
default = "info";
description = lib.mdDoc ''
Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (error|notice|info|debug) [all:notice].
'';
};
all = mkOption {
type = types.enum [ "error" "notice" "info" "debug"];
default = "info";
description = lib.mdDoc ''
Only write log for matching AREAs (main|network|bus|update|other|all) below or equal to LEVEL (error|notice|info|debug) [all:notice].
'';
};
};
mqtt = {
enable = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
Adds support for MQTT
'';
};
host = mkOption {
type = types.str;
default = "localhost";
description = lib.mdDoc ''
Connect to MQTT broker on HOST.
'';
};
port = mkOption {
default = 1883;
type = types.port;
description = lib.mdDoc ''
The port on which to connect to MQTT
'';
};
home-assistant = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
Adds the Home Assistant topics to MQTT, read more at [MQTT Integration](https://github.com/john30/ebusd/wiki/MQTT-integration)
'';
};
retain = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
Set the retain flag on all topics instead of only selected global ones
'';
};
user = mkOption {
type = types.str;
description = lib.mdDoc ''
The MQTT user to use
'';
};
password = mkOption {
type = types.str;
description = lib.mdDoc ''
The MQTT password.
'';
};
};
extraArguments = mkOption {
type = types.listOf types.str;
default = [];
description = lib.mdDoc ''
Extra arguments to the ebus daemon
'';
};
};
config = mkIf (cfg.enable) {
systemd.services.ebusd = {
description = "EBUSd Service";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig = {
ExecStart = command;
DynamicUser = true;
Restart = "on-failure";
# Hardening
CapabilityBoundingSet = "";
DeviceAllow = lib.optionals usesDev [
cfg.device
] ;
DevicePolicy = "closed";
LockPersonality = true;
MemoryDenyWriteExecute = false;
NoNewPrivileges = true;
PrivateDevices = usesDev;
PrivateUsers = true;
PrivateTmp = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProcSubset = "pid";
ProtectSystem = "strict";
RemoveIPC = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SupplementaryGroups = [
"dialout"
];
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service @pkey"
"~@privileged @resources"
];
UMask = "0077";
};
};
};
}

View file

@ -50,7 +50,7 @@ in
];
environment.HOME = "/var/lib/evcc";
path = with pkgs; [
glibc # requires getent
getent
];
serviceConfig = {
ExecStart = "${package}/bin/evcc --config ${configFile} ${escapeShellArgs cfg.extraArgs}";

View file

@ -203,7 +203,7 @@ with lib;
users = {
users.${cfg.user} = {
description = "Nullmailer relay-only mta user";
group = cfg.group;
inherit (cfg) group;
isSystemUser = true;
};
@ -211,10 +211,10 @@ with lib;
};
systemd.tmpfiles.rules = [
"d /var/spool/nullmailer - ${cfg.user} - - -"
"d /var/spool/nullmailer/failed 750 ${cfg.user} - - -"
"d /var/spool/nullmailer/queue 750 ${cfg.user} - - -"
"d /var/spool/nullmailer/tmp 750 ${cfg.user} - - -"
"d /var/spool/nullmailer - ${cfg.user} ${cfg.group} - -"
"d /var/spool/nullmailer/failed 770 ${cfg.user} ${cfg.group} - -"
"d /var/spool/nullmailer/queue 770 ${cfg.user} ${cfg.group} - -"
"d /var/spool/nullmailer/tmp 770 ${cfg.user} ${cfg.group} - -"
];
systemd.services.nullmailer = {
@ -238,7 +238,7 @@ with lib;
program = "sendmail";
source = "${pkgs.nullmailer}/bin/sendmail";
owner = cfg.user;
group = cfg.group;
inherit (cfg) group;
setuid = true;
setgid = true;
};

View file

@ -187,7 +187,7 @@ in {
sed -i "s/^as_token:.*$/$as_token/g" ${registrationFile}
fi
# Allow synapse access to the registration
if ${getBin pkgs.glibc}/bin/getent group matrix-synapse > /dev/null; then
if ${pkgs.getent}/bin/getent group matrix-synapse > /dev/null; then
chgrp matrix-synapse ${registrationFile}
chmod g+r ${registrationFile}
fi

View file

@ -138,10 +138,12 @@ in
"~@privileged"
];
StateDirectory = "matrix-conduit";
StateDirectoryMode = "0700";
ExecStart = "${cfg.package}/bin/conduit";
Restart = "on-failure";
RestartSec = 10;
StartLimitBurst = 5;
UMask = "077";
};
};
};

View file

@ -0,0 +1,96 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.matrix-synapse.sliding-sync;
in
{
options.services.matrix-synapse.sliding-sync = {
enable = lib.mkEnableOption (lib.mdDoc "sliding sync");
package = lib.mkPackageOption pkgs "matrix-sliding-sync" { };
settings = lib.mkOption {
type = lib.types.submodule {
freeformType = with lib.types; attrsOf str;
options = {
SYNCV3_SERVER = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc ''
The destination homeserver to talk to not including `/_matrix/` e.g `https://matrix.example.org`.
'';
};
SYNCV3_DB = lib.mkOption {
type = lib.types.str;
default = "postgresql:///matrix-sliding-sync?host=/run/postgresql";
description = lib.mdDoc ''
The postgres connection string.
Refer to <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING>.
'';
};
SYNCV3_BINDADDR = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1:8009";
example = "[::]:8008";
description = lib.mdDoc "The interface and port to listen on.";
};
SYNCV3_LOG_LEVEL = lib.mkOption {
type = lib.types.enum [ "trace" "debug" "info" "warn" "error" "fatal" ];
default = "info";
description = lib.mdDoc "The level of verbosity for messages logged.";
};
};
};
default = { };
description = ''
Freeform environment variables passed to the sliding sync proxy.
Refer to <https://github.com/matrix-org/sliding-sync#setup> for all supported values.
'';
};
createDatabase = lib.mkOption {
type = lib.types.bool;
default = true;
description = lib.mdDoc ''
Whether to enable and configure `services.postgres` to ensure that the database user `matrix-sliding-sync`
and the database `matrix-sliding-sync` exist.
'';
};
environmentFile = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc ''
Environment file as defined in {manpage}`systemd.exec(5)`.
This must contain the {env}`SYNCV3_SECRET` variable which should
be generated with {command}`openssl rand -hex 32`.
'';
};
};
config = lib.mkIf cfg.enable {
services.postgresql = lib.optionalAttrs cfg.createDatabase {
enable = true;
ensureDatabases = [ "matrix-sliding-sync" ];
ensureUsers = [ rec {
name = "matrix-sliding-sync";
ensurePermissions."DATABASE \"${name}\"" = "ALL PRIVILEGES";
} ];
};
systemd.services.matrix-sliding-sync = {
after = lib.optional cfg.createDatabase "postgresql.service";
wantedBy = [ "multi-user.target" ];
environment = cfg.settings;
serviceConfig = {
DynamicUser = true;
EnvironmentFile = cfg.environmentFile;
ExecStart = lib.getExe cfg.package;
StateDirectory = "matrix-sliding-sync";
WorkingDirectory = "%S/matrix-sliding-sync";
};
};
};
}

View file

@ -5,7 +5,9 @@ with lib;
let
cfg = config.services.ananicy;
configFile = pkgs.writeText "ananicy.conf" (generators.toKeyValue { } cfg.settings);
extraRules = pkgs.writeText "extraRules" cfg.extraRules;
extraRules = pkgs.writeText "extraRules" (concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.extraRules);
extraTypes = pkgs.writeText "extraTypes" (concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.extraTypes);
extraCgroups = pkgs.writeText "extraCgroups" (concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.extraCgroups);
servicename = if ((lib.getName cfg.package) == (lib.getName pkgs.ananicy-cpp)) then "ananicy-cpp" else "ananicy";
in
{
@ -23,6 +25,16 @@ in
'';
};
rulesProvider = mkOption {
type = types.package;
default = pkgs.ananicy;
defaultText = literalExpression "pkgs.ananicy";
example = literalExpression "pkgs.ananicy-cpp";
description = lib.mdDoc ''
Which package to copy default rules,types,cgroups from.
'';
};
settings = mkOption {
type = with types; attrsOf (oneOf [ int bool str ]);
default = { };
@ -35,20 +47,40 @@ in
};
extraRules = mkOption {
type = types.str;
default = "";
type = with types; listOf attrs;
default = [ ];
description = lib.mdDoc ''
Extra rules in json format on separate lines. See:
Rules to write in 'nixRules.rules'. See:
<https://github.com/Nefelim4ag/Ananicy#configuration>
<https://gitlab.com/ananicy-cpp/ananicy-cpp/#global-configuration>
'';
example = literalExpression ''
'''
{ "name": "eog", "type": "Image-View" }
{ "name": "fdupes", "type": "BG_CPUIO" }
'''
example = [
{ name = "eog"; type = "Image-Viewer"; }
{ name = "fdupes"; type = "BG_CPUIO"; }
];
};
extraTypes = mkOption {
type = with types; listOf attrs;
default = [ ];
description = lib.mdDoc ''
Types to write in 'nixTypes.types'. See:
<https://gitlab.com/ananicy-cpp/ananicy-cpp/#types>
'';
example = [
{ type = "my_type"; nice = 19; other_parameter = "value"; }
{ type = "compiler"; nice = 19; sched = "batch"; ioclass = "idle"; }
];
};
extraCgroups = mkOption {
type = with types; listOf attrs;
default = [ ];
description = lib.mdDoc ''
Cgroups to write in 'nixCgroups.cgroups'. See:
<https://gitlab.com/ananicy-cpp/ananicy-cpp/#cgroups>
'';
example = [
{ cgroup = "cpu80"; CPUQuota = 80; }
];
};
};
};
@ -59,10 +91,18 @@ in
etc."ananicy.d".source = pkgs.runCommandLocal "ananicyfiles" { } ''
mkdir -p $out
# ananicy-cpp does not include rules or settings on purpose
cp -r ${pkgs.ananicy}/etc/ananicy.d/* $out
rm $out/ananicy.conf
if [[ -d "${cfg.rulesProvider}/etc/ananicy.d/00-default" ]]; then
cp -r ${cfg.rulesProvider}/etc/ananicy.d/* $out
else
cp -r ${cfg.rulesProvider}/* $out
fi
# configured through .setings
rm -f $out/ananicy.conf
cp ${configFile} $out/ananicy.conf
${optionalString (cfg.extraRules != "") "cp ${extraRules} $out/nixRules.rules"}
${optionalString (cfg.extraRules != [ ]) "cp ${extraRules} $out/nixRules.rules"}
${optionalString (cfg.extraTypes != [ ]) "cp ${extraTypes} $out/nixTypes.types"}
${optionalString (cfg.extraCgroups != [ ]) "cp ${extraCgroups} $out/nixCgroups.cgroups"}
'';
};
@ -85,6 +125,7 @@ in
# https://gitlab.com/ananicy-cpp/ananicy-cpp/-/blob/master/src/config.cpp#L12
loglevel = mkOD "warn"; # default is info but its spammy
cgroup_realtime_workaround = mkOD config.systemd.enableUnifiedCgroupHierarchy;
log_applied_rule = mkOD false;
} else {
# https://github.com/Nefelim4ag/Ananicy/blob/master/ananicy.d/ananicy.conf
check_disks_schedulers = mkOD true;

View file

@ -0,0 +1,175 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.services.bcg;
configFile = (pkgs.formats.yaml {}).generate "bcg.conf.yaml" (
filterAttrsRecursive (n: v: v != null) {
inherit (cfg) device name mqtt;
retain_node_messages = cfg.retainNodeMessages;
qos_node_messages = cfg.qosNodeMessages;
base_topic_prefix = cfg.baseTopicPrefix;
automatic_remove_kit_from_names = cfg.automaticRemoveKitFromNames;
automatic_rename_kit_nodes = cfg.automaticRenameKitNodes;
automatic_rename_generic_nodes = cfg.automaticRenameGenericNodes;
automatic_rename_nodes = cfg.automaticRenameNodes;
}
);
in
{
options = {
services.bcg = {
enable = mkEnableOption (mdDoc "BigClown gateway");
package = mkOption {
default = pkgs.python3Packages.bcg;
defaultText = literalExpression "pkgs.python3Packages.bcg";
description = mdDoc "Which bcg derivation to use.";
type = types.package;
};
environmentFiles = mkOption {
type = types.listOf types.path;
default = [];
example = [ "/run/keys/bcg.env" ];
description = mdDoc ''
File to load as environment file. Environment variables from this file
will be interpolated into the config file using envsubst with this
syntax: `$ENVIRONMENT` or `''${VARIABLE}`.
This is useful to avoid putting secrets into the nix store.
'';
};
verbose = mkOption {
type = types.enum ["CRITICAL" "ERROR" "WARNING" "INFO" "DEBUG"];
default = "WARNING";
description = mdDoc "Verbosity level.";
};
device = mkOption {
type = types.str;
description = mdDoc "Device name to configure gateway to use.";
};
name = mkOption {
type = with types; nullOr str;
default = null;
description = mdDoc ''
Name for the device.
Supported variables:
* `{ip}` IP address
* `{id}` The ID of the connected usb-dongle or core-module
`null` can be used for automatic detection from gateway firmware.
'';
};
mqtt = {
host = mkOption {
type = types.str;
default = "127.0.0.1";
description = mdDoc "Host where MQTT server is running.";
};
port = mkOption {
type = types.port;
default = 1883;
description = mdDoc "Port of MQTT server.";
};
username = mkOption {
type = with types; nullOr str;
default = null;
description = mdDoc "MQTT server access username.";
};
password = mkOption {
type = with types; nullOr str;
default = null;
description = mdDoc "MQTT server access password.";
};
cafile = mkOption {
type = with types; nullOr str;
default = null;
description = mdDoc "Certificate Authority file for MQTT server access.";
};
certfile = mkOption {
type = with types; nullOr str;
default = null;
description = mdDoc "Certificate file for MQTT server access.";
};
keyfile = mkOption {
type = with types; nullOr str;
default = null;
description = mdDoc "Key file for MQTT server access.";
};
};
retainNodeMessages = mkOption {
type = types.bool;
default = false;
description = mdDoc "Specify that node messages should be retaied in MQTT broker.";
};
qosNodeMessages = mkOption {
type = types.int;
default = 1;
description = mdDoc "Set the guarantee of MQTT message delivery.";
};
baseTopicPrefix = mkOption {
type = types.str;
default = "";
description = mdDoc "Topic prefix added to all MQTT messages.";
};
automaticRemoveKitFromNames = mkOption {
type = types.bool;
default = true;
description = mdDoc "Automatically remove kits.";
};
automaticRenameKitNodes = mkOption {
type = types.bool;
default = true;
description = mdDoc "Automatically rename kit's nodes.";
};
automaticRenameGenericNodes = mkOption {
type = types.bool;
default = true;
description = mdDoc "Automatically rename generic nodes.";
};
automaticRenameNodes = mkOption {
type = types.bool;
default = true;
description = mdDoc "Automatically rename all nodes.";
};
rename = mkOption {
type = with types; attrsOf str;
default = {};
description = mdDoc "Rename nodes to different name.";
};
};
};
config = mkIf cfg.enable {
environment.systemPackages = with pkgs; [
python3Packages.bcg
python3Packages.bch
];
systemd.services.bcg = let
envConfig = cfg.environmentFiles != [];
finalConfig = if envConfig
then "$RUNTIME_DIRECTORY/bcg.config.yaml"
else configFile;
in {
description = "BigClown Gateway";
wantedBy = [ "multi-user.target" ];
wants = mkIf config.services.mosquitto.enable [ "mosquitto.service" ];
after = [ "network-online.target" ];
preStart = ''
umask 077
${pkgs.envsubst}/bin/envsubst -i "${configFile}" -o "${finalConfig}"
'';
serviceConfig = {
EnvironmentFile = cfg.environmentFiles;
ExecStart="${cfg.package}/bin/bcg -c ${finalConfig} -v ${cfg.verbose}";
RuntimeDirectory = "bcg";
};
};
};
}

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